Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create shared WellKnownTypes enum #2692

Merged
merged 9 commits into from
Mar 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions src/ILLink.RoslynAnalyzer/COMAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Collections.Immutable;
using System.Runtime.InteropServices;
using ILLink.Shared;
using ILLink.Shared.TypeSystemProxy;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Operations;
Expand Down Expand Up @@ -88,11 +89,11 @@ static bool IsComInterop (ISymbol symbol)
if (typeSymbol == null)
return false;

if (typeSymbol.ContainingNamespace.Name == "System" && typeSymbol.Name == "Array") {
if (typeSymbol.IsTypeOf (WellKnownType.System_Array)) {
// System.Array marshals as IUnknown by default
return true;
} else if (typeSymbol.ContainingNamespace.Name == "System" && typeSymbol.Name == "String" ||
typeSymbol.ContainingNamespace.Name == "System.Text" && typeSymbol.Name == "StringBuilder") {
} else if (typeSymbol.IsTypeOf (WellKnownType.System_String) ||
typeSymbol.IsTypeOf ("System.Text", "StringBuilder")) {
// String and StringBuilder are special cased by interop
return false;
}
Expand All @@ -103,8 +104,7 @@ static bool IsComInterop (ISymbol symbol)
} else if (typeSymbol.IsInterface ()) {
// Interface types marshal as COM by default
return true;
} else if (typeSymbol.ContainingNamespace.Name == "System" &&
typeSymbol.Name == "MulticastDelegate") {
} else if (typeSymbol.IsTypeOf ("System", "MulticastDelegate")) {
// Delegates are special cased by interop
return false;
} else if (typeSymbol.IsSubclassOf ("System.Runtime.InteropServices", "CriticalHandle") ||
Expand Down
5 changes: 2 additions & 3 deletions src/ILLink.RoslynAnalyzer/ISymbolExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ public static string GetDisplayName (this ISymbol symbol)
// Use definition type parameter names, not instance type parameters
methodSymbol = methodSymbol.OriginalDefinition;
// Format the declaring type with namespace and containing types.
if (methodSymbol.ContainingSymbol.Kind == SymbolKind.NamedType) {
if (methodSymbol.ContainingSymbol?.Kind == SymbolKind.NamedType) {
// If the containing symbol is a method (for example for local functions),
// don't include the containing type's name. This matches the behavior of
// CSharpErrorMessageFormat.
Expand Down Expand Up @@ -147,8 +147,7 @@ public static bool IsSubclassOf (this ISymbol symbol, string ns, string type)
return false;

while (typeSymbol != null) {
if (typeSymbol.ContainingNamespace.Name == ns &&
typeSymbol.ContainingType.Name == type)
if (typeSymbol.IsTypeOf (ns, type))
return true;

typeSymbol = typeSymbol.ContainingType;
Expand Down
24 changes: 21 additions & 3 deletions src/ILLink.RoslynAnalyzer/ITypeSymbolExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Diagnostics;
using ILLink.Shared.TypeSystemProxy;
using Microsoft.CodeAnalysis;

namespace ILLink.RoslynAnalyzer
Expand All @@ -27,17 +29,17 @@ public static bool IsTypeInterestingForDataflow (this ITypeSymbol type)
private static HierarchyFlags GetFlags (ITypeSymbol type)
{
HierarchyFlags flags = 0;
if (type.Name == "IReflect" && type.ContainingNamespace.GetDisplayName () == "System.Reflection") {
if (type.IsTypeOf (WellKnownType.System_Reflection_IReflect)) {
flags |= HierarchyFlags.IsSystemReflectionIReflect;
}

ITypeSymbol? baseType = type;
while (baseType != null) {
if (baseType.Name == "Type" && baseType.ContainingNamespace.GetDisplayName () == "System")
if (baseType.IsTypeOf (WellKnownType.System_Type))
flags |= HierarchyFlags.IsSystemType;

foreach (var iface in baseType.Interfaces) {
if (iface.Name == "IReflect" && iface.ContainingNamespace.GetDisplayName () == "System.Reflection") {
if (iface.IsTypeOf (WellKnownType.System_Reflection_IReflect)) {
flags |= HierarchyFlags.IsSystemReflectionIReflect;
}
}
Expand All @@ -50,5 +52,21 @@ private static HierarchyFlags GetFlags (ITypeSymbol type)
private static bool IsSystemType (HierarchyFlags flags) => (flags & HierarchyFlags.IsSystemType) != 0;

private static bool IsSystemReflectionIReflect (HierarchyFlags flags) => (flags & HierarchyFlags.IsSystemReflectionIReflect) != 0;

public static bool IsTypeOf (this ITypeSymbol symbol, string @namespace, string name)
{
return symbol.ContainingNamespace?.GetDisplayName () == @namespace && symbol.MetadataName == name;
}

public static bool IsTypeOf (this ITypeSymbol symbol, WellKnownType wellKnownType)
{
if (wellKnownType.TryGetSpecialType (out var specialType)) {
// Make sure checking the special type is the same as checking the metadata string names.
Debug.Assert (symbol.IsTypeOf (wellKnownType.GetNamespace (), wellKnownType.GetName ()) == (symbol.SpecialType == specialType));
return symbol.SpecialType == specialType;
}
var (Namespace, Name) = wellKnownType.GetNamespaceAndName ();
return symbol.IsTypeOf (Namespace, Name);
}
}
}
8 changes: 5 additions & 3 deletions src/ILLink.RoslynAnalyzer/TrimAnalysis/TypeProxy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,14 @@ internal readonly partial struct TypeProxy

public string Name { get => Type.MetadataName; }

public string Namespace { get => Type.ContainingNamespace.Name; }
public string? Namespace { get => Type.ContainingNamespace?.Name; }

public bool IsTypeOf (string @namespace, string name) => Type.IsTypeOf (@namespace, name);

public bool IsTypeOf (WellKnownType wellKnownType) => Type.IsTypeOf (wellKnownType);

public string GetDisplayName () => Type.GetDisplayName ();

public override string ToString () => Type.ToString ();

public bool IsTypeOf (string @namespace, string name) => Namespace == @namespace && Name == name;
}
}
23 changes: 23 additions & 0 deletions src/ILLink.RoslynAnalyzer/TrimAnalysis/WellKnownTypeExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics.CodeAnalysis;
using Microsoft.CodeAnalysis;

namespace ILLink.Shared.TypeSystemProxy
{
public static partial class WellKnownTypeExtensions
{
public static bool TryGetSpecialType (this WellKnownType wellKnownType, [NotNullWhen (true)] out SpecialType? specialType)
{
specialType = wellKnownType switch {
WellKnownType.System_String => SpecialType.System_String,
WellKnownType.System_Nullable_T => SpecialType.System_Nullable_T,
WellKnownType.System_Array => SpecialType.System_Array,
WellKnownType.System_Object => SpecialType.System_Object,
_ => null,
};
return specialType is not null;
}
}
}
37 changes: 37 additions & 0 deletions src/ILLink.Shared/TypeSystemProxy/WellKnownType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;

namespace ILLink.Shared.TypeSystemProxy
{
public enum WellKnownType
{
System_String,
System_Nullable_T,
System_Type,
System_Reflection_IReflect,
System_Array,
System_Object,
System_Attribute
}

public static partial class WellKnownTypeExtensions
{
public static (string Namespace, string Name) GetNamespaceAndName (this WellKnownType type)
{
return type switch {
WellKnownType.System_String => ("System", "String"),
WellKnownType.System_Nullable_T => ("System", "Nullable`1"),
WellKnownType.System_Type => ("System", "Type"),
WellKnownType.System_Reflection_IReflect => ("System.Reflection", "IReflect"),
WellKnownType.System_Array => ("System", "Array"),
WellKnownType.System_Object => ("System", "Object"),
WellKnownType.System_Attribute => ("System", "Attribute"),
_ => throw new ArgumentException ($"{nameof (type)} is not a well-known type."),
};
}
public static string GetNamespace (this WellKnownType type) => GetNamespaceAndName (type).Namespace;
public static string GetName (this WellKnownType type) => GetNamespaceAndName (type).Name;
}
}
5 changes: 3 additions & 2 deletions src/linker/Linker.Dataflow/ReflectionMethodBodyScanner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using ILLink.Shared;
using ILLink.Shared.DataFlow;
using ILLink.Shared.TrimAnalysis;
using ILLink.Shared.TypeSystemProxy;
using Mono.Cecil;
using Mono.Cecil.Cil;
using Mono.Linker.Steps;
Expand Down Expand Up @@ -894,10 +895,10 @@ bool IsComInterop (IMarshalInfoProvider marshalInfoProvider, TypeReference param
var parameterTypeDef = _context.TryResolve (parameterType);

if (parameterTypeDef != null) {
if (parameterTypeDef.IsTypeOf ("System", "Array")) {
if (parameterTypeDef.IsTypeOf (WellKnownType.System_Array)) {
// System.Array marshals as IUnknown by default
return true;
} else if (parameterTypeDef.IsTypeOf ("System", "String") ||
} else if (parameterTypeDef.IsTypeOf (WellKnownType.System_String) ||
parameterTypeDef.IsTypeOf ("System.Text", "StringBuilder")) {
// String and StringBuilder are special cased by interop
return false;
Expand Down
8 changes: 5 additions & 3 deletions src/linker/Linker.Dataflow/TypeProxy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,14 @@ internal readonly partial struct TypeProxy

public string Name { get => Type.Name; }

public string Namespace { get => Type.Namespace; }
public string? Namespace { get => Type.Namespace; }

public bool IsTypeOf (string @namespace, string name) => Type.IsTypeOf (@namespace, name);

public bool IsTypeOf (WellKnownType wellKnownType) => Type.IsTypeOf (wellKnownType);

public string GetDisplayName () => Type.GetDisplayName ();

public override string ToString () => Type.ToString ();

public bool IsTypeOf (string @namespace, string name) => Type.IsTypeOf (@namespace, name);
}
}
7 changes: 7 additions & 0 deletions src/linker/Linker/TypeReferenceExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Diagnostics;
using System.Linq;
using System.Text;
using ILLink.Shared.TypeSystemProxy;
using Mono.Cecil;

namespace Mono.Linker
Expand Down Expand Up @@ -358,6 +359,12 @@ public static bool IsTypeOf<T> (this TypeReference tr)
return tr.Name == type.Name && tr.Namespace == tr.Namespace;
}

public static bool IsTypeOf (this TypeReference tr, WellKnownType type)
{
var (@namespace, name) = type.GetNamespaceAndName ();
return tr.IsTypeOf (@namespace, name);
Comment on lines +364 to +365
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cecil has a similar concept to SpecialType - it's called MetadataType and through it one can detect primitive types (and couple of other cases) faster than string compares.

For example

if ((parameters.Count == 3 && parameters[2].ParameterType.MetadataType == MetadataType.Boolean && methodParams[2].AsConstInt () != 0) ||
does a boolean check.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good to know, I'll add that in a new PR. Thanks!

}

public static bool IsSubclassOf (this TypeReference type, string ns, string name, ITryResolveMetadata resolver)
{
TypeDefinition? baseType = resolver.TryResolve (type);
Expand Down