diff --git a/src/AsmResolver.DotNet/Signatures/GenericContext.cs b/src/AsmResolver.DotNet/Signatures/GenericContext.cs index 79571b830..65b435eee 100644 --- a/src/AsmResolver.DotNet/Signatures/GenericContext.cs +++ b/src/AsmResolver.DotNet/Signatures/GenericContext.cs @@ -1,10 +1,12 @@ using System; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; using AsmResolver.DotNet.Signatures.Types; namespace AsmResolver.DotNet.Signatures { /// - /// Provides a context within a generic instantiation, including the type arguments of the enclosing type and method. + /// Provides a context within a generic instantiation, including the type arguments of the enclosing type and method. /// public readonly struct GenericContext { @@ -18,9 +20,9 @@ public GenericContext(IGenericArgumentsProvider? type, IGenericArgumentsProvider Type = type; Method = method; } - + /// - /// Gets the object responsible for providing type arguments defined by the current generic type instantiation. + /// Gets the object responsible for providing type arguments defined by the current generic type instantiation. /// public IGenericArgumentsProvider? Type { @@ -28,7 +30,7 @@ public IGenericArgumentsProvider? Type } /// - /// Gets the object responsible for providing type arguments defined by the current generic method instantiation. + /// Gets the object responsible for providing type arguments defined by the current generic method instantiation. /// public IGenericArgumentsProvider? Method { @@ -36,14 +38,19 @@ public IGenericArgumentsProvider? Method } /// - /// Enters a new generic context with a new type providing type arguments. + /// Returns true if both Type and Method providers are null + /// + public bool IsEmpty => Type is null && Method is null; + + /// + /// Enters a new generic context with a new type providing type arguments. /// /// The new type providing the type arguments. /// The new generic context. public GenericContext WithType(IGenericArgumentsProvider type) => new GenericContext(type, Method); - + /// - /// Enters a new generic context with a new method providing type arguments. + /// Enters a new generic context with a new method providing type arguments. /// /// The new method providing the type arguments. /// The new generic context. @@ -52,6 +59,11 @@ public IGenericArgumentsProvider? Method /// /// Resolves a type parameter to a type argument, based on the current generic context. /// + /// + /// If a type parameter within the signature references a parameter that is not captured by the context + /// (i.e. the corresponding generic argument provider is set to null), + /// then this type parameter will not be substituted. + /// /// The parameter to get the argument value for. /// The argument type. public TypeSignature GetTypeArgument(GenericParameterSignature parameter) @@ -62,14 +74,108 @@ public TypeSignature GetTypeArgument(GenericParameterSignature parameter) GenericParameterType.Method => Method, _ => throw new ArgumentOutOfRangeException() }; - + if (argumentProvider is null) - throw new ArgumentOutOfRangeException(); + return parameter; if (parameter.Index >= 0 && parameter.Index < argumentProvider.TypeArguments.Count) return argumentProvider.TypeArguments[parameter.Index]; throw new ArgumentOutOfRangeException(); } + + + /// + /// Gets a type generic context from . + /// + /// Type specification to get the generic context from. + /// Generic context. + public static GenericContext FromType(TypeSpecification type) => type.Signature switch + { + GenericInstanceTypeSignature typeSig => new GenericContext(typeSig, null), + _ => default + }; + + /// + /// Gets a type generic context from . + /// + /// Generic type signature to get the generic context from. + /// Generic context. + public static GenericContext FromType(GenericInstanceTypeSignature type) => new(type, null); + + /// + /// Gets a type generic context from . + /// + /// Type to get the generic context from. + /// Generic context. + public static GenericContext FromType(ITypeDescriptor type) => type switch + { + TypeSpecification typeSpecification => FromType(typeSpecification), + GenericInstanceTypeSignature typeSig => FromType(typeSig), + _ => default + }; + + /// + /// Gets a method and/or type generic context from . + /// + /// Method specification to get the generic context from. + /// Generic context. + public static GenericContext FromMethod(MethodSpecification method) + { + return method.DeclaringType is TypeSpecification {Signature: GenericInstanceTypeSignature typeSig} + ? new GenericContext(typeSig, method.Signature) + : new GenericContext(null, method.Signature); + } + + /// + /// Gets a method and/or type generic context from . + /// + /// Method to get the generic context from. + /// Generic context. + public static GenericContext FromMethod(IMethodDescriptor method) => + method switch + { + MethodSpecification methodSpecification => FromMethod(methodSpecification), + MemberReference member => FromMember(member), + _ => default + }; + + /// + /// Gets a type generic context from . + /// + /// Field to get the generic context from. + /// Generic context. + public static GenericContext FromField(IFieldDescriptor field) => + field switch + { + MemberReference member => FromMember(member), + _ => default + }; + + /// + /// Gets a type generic context from . + /// + /// Member reference to get the generic context from. + /// Generic context. + public static GenericContext FromMember(MemberReference member) => + member.DeclaringType switch + { + TypeSpecification type => FromType(type), + _ => default + }; + + /// + /// Gets a type generic context from . + /// + /// Member to get the generic context from. + /// Generic context. + public static GenericContext FromMember(IMemberDescriptor member) => + member switch + { + IMethodDescriptor method => FromMethod(method), + IFieldDescriptor field => FromField(field), + ITypeDescriptor type => FromType(type), + _ => default + }; } } diff --git a/test/AsmResolver.DotNet.Tests/Signatures/GenericContextTest.cs b/test/AsmResolver.DotNet.Tests/Signatures/GenericContextTest.cs index 73c2de250..aa9a55beb 100644 --- a/test/AsmResolver.DotNet.Tests/Signatures/GenericContextTest.cs +++ b/test/AsmResolver.DotNet.Tests/Signatures/GenericContextTest.cs @@ -1,15 +1,14 @@ -using System; using System.Collections.Generic; using AsmResolver.DotNet.Signatures; using AsmResolver.DotNet.Signatures.Types; +using AsmResolver.PE.DotNet.Metadata.Tables.Rows; using Xunit; -using Xunit.Sdk; namespace AsmResolver.DotNet.Tests.Signatures { public class GenericContextTest { - private ReferenceImporter _importer; + private readonly ReferenceImporter _importer; public GenericContextTest() { @@ -20,22 +19,22 @@ public GenericContextTest() [Theory] [InlineData(GenericParameterType.Type)] [InlineData(GenericParameterType.Method)] - public void ResolveGenericParameterWithEmptyTypeShouldThrow(GenericParameterType parameterType) + public void ResolveGenericParameterWithEmptyType(GenericParameterType parameterType) { var context = new GenericContext(); var parameter = new GenericParameterSignature(parameterType, 0); - Assert.Throws(() => context.GetTypeArgument(parameter)); + Assert.Equal(parameter, context.GetTypeArgument(parameter)); } - + [Fact] public void ResolveMethodGenericParameterWithMethod() { var genericInstance = new GenericInstanceMethodSignature(); genericInstance.TypeArguments.Add(_importer.ImportTypeSignature(typeof(string))); - + var context = new GenericContext(null, genericInstance); - + var parameter = new GenericParameterSignature(GenericParameterType.Method, 0); Assert.Equal("System.String", context.GetTypeArgument(parameter).FullName); } @@ -45,9 +44,9 @@ public void ResolveTypeGenericParameterWithType() { var genericInstance = new GenericInstanceTypeSignature(_importer.ImportType(typeof(List<>)), false); genericInstance.TypeArguments.Add(_importer.ImportTypeSignature(typeof(string))); - + var context = new GenericContext(genericInstance, null); - + var parameter = new GenericParameterSignature(GenericParameterType.Type, 0); Assert.Equal("System.String", context.GetTypeArgument(parameter).FullName); } @@ -57,12 +56,12 @@ public void ResolveTypeGenericParameterWithTypeAndMethod() { var genericType = new GenericInstanceTypeSignature(_importer.ImportType(typeof(List<>)), false); genericType.TypeArguments.Add(_importer.ImportTypeSignature(typeof(string))); - + var genericMethod = new GenericInstanceMethodSignature(); genericMethod.TypeArguments.Add(_importer.ImportTypeSignature(typeof(int))); - + var context = new GenericContext(genericType, genericMethod); - + var parameter = new GenericParameterSignature(GenericParameterType.Type, 0); Assert.Equal("System.String", context.GetTypeArgument(parameter).FullName); } @@ -72,38 +71,319 @@ public void ResolveMethodGenericParameterWithTypeAndMethod() { var genericType = new GenericInstanceTypeSignature(_importer.ImportType(typeof(List<>)), false); genericType.TypeArguments.Add(_importer.ImportTypeSignature(typeof(string))); - + var genericMethod = new GenericInstanceMethodSignature(); genericMethod.TypeArguments.Add(_importer.ImportTypeSignature(typeof(int))); - + var context = new GenericContext(genericType, genericMethod); - + var parameter = new GenericParameterSignature(GenericParameterType.Method, 0); Assert.Equal("System.Int32", context.GetTypeArgument(parameter).FullName); } [Fact] - public void ResolveTypeGenericParameterWithOnlyMethodShouldThrow() + public void ResolveTypeGenericParameterWithOnlyMethod() { var genericInstance = new GenericInstanceMethodSignature(); genericInstance.TypeArguments.Add(_importer.ImportTypeSignature(typeof(string))); - + var context = new GenericContext(null, genericInstance); - + var parameter = new GenericParameterSignature(GenericParameterType.Type, 0); - Assert.Throws(() => context.GetTypeArgument(parameter)); + Assert.Equal(parameter, context.GetTypeArgument(parameter)); } [Fact] - public void ResolveMethodGenericParameterWithOnlyTypeShouldThrow() + public void ResolveMethodGenericParameterWithOnlyType() { var genericInstance = new GenericInstanceTypeSignature(_importer.ImportType(typeof(List<>)), false); genericInstance.TypeArguments.Add(_importer.ImportTypeSignature(typeof(string))); - + var context = new GenericContext(genericInstance, null); - + var parameter = new GenericParameterSignature(GenericParameterType.Method, 0); - Assert.Throws(() => context.GetTypeArgument(parameter)); + Assert.Equal(parameter, context.GetTypeArgument(parameter)); + } + + + [Fact] + public void ParseGenericFromTypeSignature() + { + var genericInstance = new GenericInstanceTypeSignature(_importer.ImportType(typeof(List<>)), false); + genericInstance.TypeArguments.Add(_importer.ImportTypeSignature(typeof(string))); + + var context = GenericContext.FromType(genericInstance); + var context2 = GenericContext.FromMember(genericInstance); + var context3 = GenericContext.FromType((ITypeDescriptor) genericInstance); + + Assert.Equal(context, context3); + Assert.Equal(context, context2); + Assert.False(context.IsEmpty); + Assert.Equal(genericInstance, context.Type); + Assert.Null(context.Method); + } + + [Fact] + public void ParseGenericFromNonGenericTypeSignature() + { + var type = new TypeDefinition("", "Test type", TypeAttributes.Public); + var notGenericSignature = new TypeDefOrRefSignature(type); + + var context = GenericContext.FromType(notGenericSignature); + var context2 = GenericContext.FromMember(notGenericSignature); + + Assert.Equal(context, context2); + Assert.True(context.IsEmpty); + } + + [Fact] + public void ParseGenericFromTypeSpecification() + { + var genericInstance = new GenericInstanceTypeSignature(_importer.ImportType(typeof(List<>)), false); + genericInstance.TypeArguments.Add(_importer.ImportTypeSignature(typeof(string))); + var typeSpecification = new TypeSpecification(genericInstance); + + var context = GenericContext.FromType(typeSpecification); + var context2 = GenericContext.FromMember(typeSpecification); + var context3 = GenericContext.FromType((ITypeDescriptor) typeSpecification); + + Assert.Equal(context, context3); + Assert.Equal(context, context2); + Assert.False(context.IsEmpty); + Assert.Equal(genericInstance, context.Type); + Assert.Null(context.Method); + } + + [Fact] + public void ParseGenericFromMethodSpecification() + { + var genericParameter = new GenericParameterSignature(GenericParameterType.Method, 0); + var method = new MethodDefinition("TestMethod", MethodAttributes.Private, + MethodSignature.CreateStatic(genericParameter)); + var genericInstance = new GenericInstanceMethodSignature(); + genericInstance.TypeArguments.Add(_importer.ImportTypeSignature(typeof(int))); + var methodSpecification = new MethodSpecification(method, genericInstance); + + var context = GenericContext.FromMethod(methodSpecification); + var context2 = GenericContext.FromMember(methodSpecification); + var context3 = GenericContext.FromMethod((IMethodDescriptor) methodSpecification); + + Assert.Equal(context, context3); + Assert.Equal(context, context2); + Assert.False(context.IsEmpty); + Assert.Null(context.Type); + Assert.Equal(genericInstance, context.Method); + } + + [Fact] + public void ParseGenericFromMethodSpecificationWithTypeSpecification() + { + var genericTypeInstance = new GenericInstanceTypeSignature(_importer.ImportType(typeof(List<>)), false); + genericTypeInstance.TypeArguments.Add(_importer.ImportTypeSignature(typeof(string))); + var typeSpecification = new TypeSpecification(genericTypeInstance); + + var genericParameter = new GenericParameterSignature(GenericParameterType.Method, 0); + var method = new MemberReference(typeSpecification, "TestMethod", + MethodSignature.CreateStatic(genericParameter)); + + var genericMethodInstance = new GenericInstanceMethodSignature(); + genericMethodInstance.TypeArguments.Add(_importer.ImportTypeSignature(typeof(int))); + var methodSpecification = new MethodSpecification(method, genericMethodInstance); + + + var context = GenericContext.FromMethod(methodSpecification); + var context2 = GenericContext.FromMember(methodSpecification); + var context3 = GenericContext.FromMethod((IMethodDescriptor) methodSpecification); + + Assert.Equal(context, context3); + Assert.Equal(context, context2); + Assert.False(context.IsEmpty); + Assert.Equal(genericTypeInstance, context.Type); + Assert.Equal(genericMethodInstance, context.Method); + } + + [Fact] + public void ParseGenericFromNotGenericTypeSpecification() + { + var type = new TypeDefinition("", "Test type", TypeAttributes.Public); + var notGenericSignature = new TypeDefOrRefSignature(type); + + var typeSpecification = new TypeSpecification(notGenericSignature); + + var context = GenericContext.FromType(typeSpecification); + var context2 = GenericContext.FromMember(typeSpecification); + var context3 = GenericContext.FromType((ITypeDescriptor) typeSpecification); + + Assert.Equal(context, context3); + Assert.Equal(context, context2); + Assert.True(context.IsEmpty); + } + + [Fact] + public void ParseGenericFromNotGenericMethodSpecification() + { + var type = new TypeDefinition("", "Test type", TypeAttributes.Public); + var notGenericSignature = new TypeDefOrRefSignature(type); + + var method = new MethodDefinition("TestMethod", MethodAttributes.Private, + MethodSignature.CreateStatic(notGenericSignature)); + var methodSpecification = new MethodSpecification(method, null); + + var context = GenericContext.FromMethod(methodSpecification); + var context2 = GenericContext.FromMember(methodSpecification); + var context3 = GenericContext.FromMethod((IMethodDescriptor) methodSpecification); + + Assert.Equal(context, context3); + Assert.Equal(context, context2); + Assert.True(context.IsEmpty); + } + + [Fact] + public void ParseGenericFromNotGenericMethodSpecificationWithTypeSpecification() + { + var genericTypeInstance = new GenericInstanceTypeSignature(_importer.ImportType(typeof(List<>)), false); + genericTypeInstance.TypeArguments.Add(_importer.ImportTypeSignature(typeof(string))); + var typeSpecification = new TypeSpecification(genericTypeInstance); + + var type = new TypeDefinition("", "Test type", TypeAttributes.Public); + var notGenericSignature = new TypeDefOrRefSignature(type); + + var method = new MemberReference(typeSpecification, "TestMethod", + MethodSignature.CreateStatic(notGenericSignature)); + var methodSpecification = new MethodSpecification(method, null); + + var context = GenericContext.FromMethod(methodSpecification); + var context2 = GenericContext.FromMember(methodSpecification); + var context3 = GenericContext.FromMethod((IMethodDescriptor) methodSpecification); + + Assert.Equal(context, context3); + Assert.Equal(context, context2); + Assert.False(context.IsEmpty); + Assert.Equal(genericTypeInstance, context.Type); + Assert.Null(context.Method); + } + + [Fact] + public void ParseGenericFromField() + { + var genericTypeInstance = new GenericInstanceTypeSignature(_importer.ImportType(typeof(List<>)), false); + genericTypeInstance.TypeArguments.Add(_importer.ImportTypeSignature(typeof(string))); + var typeSpecification = new TypeSpecification(genericTypeInstance); + + var genericParameter = new GenericParameterSignature(GenericParameterType.Type, 0); + + var field = new FieldDefinition("Field", FieldAttributes.Private, + FieldSignature.CreateStatic(genericParameter)); + + var member = new MemberReference(typeSpecification, field.Name, field.Signature); + + var context = GenericContext.FromField(member); + var context2 = GenericContext.FromMember(member); + + Assert.Equal(context, context2); + Assert.False(context.IsEmpty); + Assert.Equal(genericTypeInstance, context.Type); + Assert.Null(context.Method); + } + + [Fact] + public void ParseGenericFromNotGenericField() + { + var type = new TypeDefinition("", "Test type", TypeAttributes.Public); + var notGenericSignature = new TypeDefOrRefSignature(type); + + var field = new FieldDefinition("Field", FieldAttributes.Private, + FieldSignature.CreateStatic(notGenericSignature)); + + var member = new MemberReference(type, field.Name, field.Signature); + + var context = GenericContext.FromField(member); + var context2 = GenericContext.FromMember(member); + + Assert.Equal(context, context2); + Assert.True(context.IsEmpty); + } + + [Fact] + public void ParseGenericFromMethod() + { + var genericTypeInstance = new GenericInstanceTypeSignature(_importer.ImportType(typeof(List<>)), false); + genericTypeInstance.TypeArguments.Add(_importer.ImportTypeSignature(typeof(string))); + var typeSpecification = new TypeSpecification(genericTypeInstance); + + var genericParameter = new GenericParameterSignature(GenericParameterType.Type, 0); + + var method = new MethodDefinition("Method", MethodAttributes.Private, + MethodSignature.CreateStatic(genericParameter)); + + var member = new MemberReference(typeSpecification, method.Name, method.Signature); + + var context = GenericContext.FromMethod(member); + var context2 = GenericContext.FromMember(member); + + Assert.Equal(context, context2); + Assert.False(context.IsEmpty); + Assert.Equal(genericTypeInstance, context.Type); + Assert.Null(context.Method); + } + + [Fact] + public void ParseGenericFromNotGenericMethod() + { + var type = new TypeDefinition("", "Test type", TypeAttributes.Public); + var notGenericSignature = new TypeDefOrRefSignature(type); + + var method = new MethodDefinition("Method", MethodAttributes.Private, + MethodSignature.CreateStatic(notGenericSignature)); + + var member = new MemberReference(type, method.Name, method.Signature); + + var context = GenericContext.FromMethod(member); + var context2 = GenericContext.FromMember(member); + + Assert.Equal(context, context2); + Assert.True(context.IsEmpty); + } + + [Fact] + public void ParseGenericFromProperty() + { + var genericTypeInstance = new GenericInstanceTypeSignature(_importer.ImportType(typeof(List<>)), false); + genericTypeInstance.TypeArguments.Add(_importer.ImportTypeSignature(typeof(string))); + var typeSpecification = new TypeSpecification(genericTypeInstance); + + var genericParameter = new GenericParameterSignature(GenericParameterType.Type, 0); + + var property = new PropertyDefinition("Property", PropertyAttributes.None, + PropertySignature.CreateStatic(genericParameter)); + + var member = new MemberReference(typeSpecification, property.Name, property.Signature); + + var context = GenericContext.FromMethod(member); + var context2 = GenericContext.FromMember(member); + + Assert.Equal(context, context2); + Assert.False(context.IsEmpty); + Assert.Equal(genericTypeInstance, context.Type); + Assert.Null(context.Method); + } + + [Fact] + public void ParseGenericFromNotGenericProperty() + { + var type = new TypeDefinition("", "Test type", TypeAttributes.Public); + var notGenericSignature = new TypeDefOrRefSignature(type); + + var property = new PropertyDefinition("Property", PropertyAttributes.None, + PropertySignature.CreateStatic(notGenericSignature)); + + var member = new MemberReference(type, property.Name, property.Signature); + + var context = GenericContext.FromMethod(member); + var context2 = GenericContext.FromMember(member); + + Assert.Equal(context, context2); + Assert.True(context.IsEmpty); } } -} \ No newline at end of file +}