diff --git a/ICSharpCode.NRefactory.CSharp/Completion/CSharpCompletionEngine.cs b/ICSharpCode.NRefactory.CSharp/Completion/CSharpCompletionEngine.cs index 21ca4f8169..d097d9c89b 100644 --- a/ICSharpCode.NRefactory.CSharp/Completion/CSharpCompletionEngine.cs +++ b/ICSharpCode.NRefactory.CSharp/Completion/CSharpCompletionEngine.cs @@ -1,4 +1,4 @@ -// +// // CSharpCompletionEngine.cs // // Author: @@ -1778,7 +1778,7 @@ namespace ICSharpCode.NRefactory.CSharp.Completion } } else { - foreach (var meths in state.GetAllExtensionMethods (type)) { + foreach (var meths in state.GetExtensionMethods (type)) { foreach (var m in meths) { result.AddMember (m); } diff --git a/ICSharpCode.NRefactory.CSharp/Resolver/CSharpAstResolver.cs b/ICSharpCode.NRefactory.CSharp/Resolver/CSharpAstResolver.cs index 3efd3abe88..23bc165629 100644 --- a/ICSharpCode.NRefactory.CSharp/Resolver/CSharpAstResolver.cs +++ b/ICSharpCode.NRefactory.CSharp/Resolver/CSharpAstResolver.cs @@ -59,7 +59,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver /// /// Creates a new C# AST resolver. /// - /// The resolver state at the root node. + /// The resolver state at the root node (to be more precise: outside the root node). /// The root node of the resolved tree. /// The parsed file for the nodes being resolved. This parameter is used only /// when the root node is on the type level; it is not necessary when an expression is passed. diff --git a/ICSharpCode.NRefactory.CSharp/Resolver/CSharpResolver.cs b/ICSharpCode.NRefactory.CSharp/Resolver/CSharpResolver.cs index a3a3ff2a2a..4a8adf7fcc 100644 --- a/ICSharpCode.NRefactory.CSharp/Resolver/CSharpResolver.cs +++ b/ICSharpCode.NRefactory.CSharp/Resolver/CSharpResolver.cs @@ -1530,7 +1530,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver MemberLookup lookup = CreateMemberLookup(); ResolveResult result = lookup.Lookup(target, identifier, typeArguments, isInvocationTarget); if (result is UnknownMemberResolveResult) { - var extensionMethods = GetExtensionMethods(target.Type, identifier, typeArguments); + var extensionMethods = GetExtensionMethods(identifier, typeArguments); if (extensionMethods.Count > 0) { return new MethodGroupResolveResult(target, identifier, EmptyList.Instance, typeArguments) { extensionMethods = extensionMethods @@ -1599,12 +1599,34 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver #endregion #region GetExtensionMethods + /// + /// Gets all extension methods that are available in the current context. + /// + /// Name of the extension method. Pass null to retrieve all extension methods. + /// Explicitly provided type arguments. + /// An empty list will return all matching extension method definitions; + /// a non-empty list will return s for all extension methods + /// with the matching number of type parameters. + /// + /// The results are stored in nested lists because they are grouped by using scope. + /// That is, for "using SomeExtensions; namespace X { using MoreExtensions; ... }", + /// the return value will be + /// new List { + /// new List { all extensions from MoreExtensions }, + /// new List { all extensions from SomeExtensions } + /// } + /// + public List> GetExtensionMethods(string name = null, IList typeArguments = null) + { + return GetExtensionMethods(null, name, typeArguments); + } + /// /// Gets the extension methods that are called 'name' /// and are applicable with a first argument type of 'targetType'. /// /// Type of the 'this' argument - /// Name of the extension method + /// Name of the extension method. Pass null to retrieve all extension methods. /// Explicitly provided type arguments. /// An empty list will return all matching extension method definitions; /// a non-empty list will return s for all extension methods @@ -1618,24 +1640,24 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver /// new List { all extensions from SomeExtensions } /// } /// - public List> GetExtensionMethods(IType targetType, string name, IList typeArguments = null) + public List> GetExtensionMethods(IType targetType, string name = null, IList typeArguments = null) { List> extensionMethodGroups = new List>(); foreach (var inputGroup in GetAllExtensionMethods()) { List outputGroup = new List(); foreach (var method in inputGroup) { - if (method.Name != name) + if (name != null && method.Name != name) continue; if (typeArguments != null && typeArguments.Count > 0) { if (method.TypeParameters.Count != typeArguments.Count) continue; SpecializedMethod sm = new SpecializedMethod(method.DeclaringType, method, typeArguments); - // TODO: verify targetType - outputGroup.Add(sm); + if (IsEligibleExtensionMethod(targetType, method, false)) + outputGroup.Add(sm); } else { - // TODO: verify targetType - outputGroup.Add(method); + if (IsEligibleExtensionMethod(targetType, method, true)) + outputGroup.Add(method); } } if (outputGroup.Count > 0) @@ -1644,22 +1666,34 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver return extensionMethodGroups; } - public List> GetAllExtensionMethods(IType targetType) + bool IsEligibleExtensionMethod(IType targetType, IMethod method, bool useTypeInference) { - List> extensionMethodGroups = new List>(); - foreach (var inputGroup in GetAllExtensionMethods()) { - List outputGroup = new List(); - foreach (var method in inputGroup) { - outputGroup.Add(method); + if (targetType == null) + return true; + if (method.Parameters.Count == 0) + return false; + IType thisParameterType = method.Parameters[0].Type; + if (useTypeInference && method.TypeParameters.Count > 0) { + // We need to infer type arguments from targetType: + TypeInference ti = new TypeInference(compilation, conversions); + ResolveResult[] arguments = { new ResolveResult(targetType) }; + IType[] parameterTypes = { method.Parameters[0].Type }; + bool success; + IType[] inferredTypes = ti.InferTypeArguments(method.TypeParameters, arguments, parameterTypes, out success); + var substitution = new TypeParameterSubstitution(null, inferredTypes); + // Validate that the types that could be inferred (aren't unknown) satisfy the constraints: + for (int i = 0; i < inferredTypes.Length; i++) { + if (inferredTypes[i].Kind != TypeKind.Unknown && inferredTypes[i].Kind != TypeKind.UnboundTypeArgument) { + if (!OverloadResolution.ValidateConstraints(method.TypeParameters[i], inferredTypes[i], substitution, conversions)) + return false; + } } - if (outputGroup.Count > 0) - extensionMethodGroups.Add(outputGroup); + thisParameterType = thisParameterType.AcceptVisitor(substitution); } - return extensionMethodGroups; + Conversion c = conversions.ImplicitConversion(targetType, thisParameterType); + return c.IsValid && (c.IsIdentityConversion || c.IsReferenceConversion || c.IsBoxingConversion); } - // - /// /// Gets all extension methods available in the current using scope. /// This list includes unaccessible diff --git a/ICSharpCode.NRefactory.CSharp/Resolver/MethodGroupResolveResult.cs b/ICSharpCode.NRefactory.CSharp/Resolver/MethodGroupResolveResult.cs index 6e40f64ba2..b04b6b77d3 100644 --- a/ICSharpCode.NRefactory.CSharp/Resolver/MethodGroupResolveResult.cs +++ b/ICSharpCode.NRefactory.CSharp/Resolver/MethodGroupResolveResult.cs @@ -126,6 +126,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver /// /// Gets all candidate extension methods. + /// Note: this includes candidates that are not eligible due to a /// /// /// The results are stored in nested lists because they are grouped by using scope. @@ -141,7 +142,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver if (resolver != null) { Debug.Assert(extensionMethods == null); try { - extensionMethods = resolver.GetExtensionMethods(this.TargetType, methodName, typeArguments); + extensionMethods = resolver.GetExtensionMethods(methodName, typeArguments); } finally { resolver = null; } diff --git a/ICSharpCode.NRefactory.CSharp/Resolver/OverloadResolution.cs b/ICSharpCode.NRefactory.CSharp/Resolver/OverloadResolution.cs index 8123aaf5d2..9b661b35df 100644 --- a/ICSharpCode.NRefactory.CSharp/Resolver/OverloadResolution.cs +++ b/ICSharpCode.NRefactory.CSharp/Resolver/OverloadResolution.cs @@ -418,36 +418,11 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver if (newParameterizedType != null) { // C# 4.0 spec: §4.4.4 Satisfying constraints var typeParameters = newParameterizedType.GetDefinition().TypeParameters; + var substitution = newParameterizedType.GetSubstitution(); for (int i = 0; i < typeParameters.Count; i++) { - ITypeParameter tp = typeParameters[i]; - IType typeArg = newParameterizedType.GetTypeArgument(i); - switch (typeArg.Kind) { // void, null, and pointers cannot be used as type arguments - case TypeKind.Void: - case TypeKind.Null: - case TypeKind.Pointer: - ConstraintsValid = false; - break; - } - if (tp.HasReferenceTypeConstraint) { - if (typeArg.IsReferenceType != true) - ConstraintsValid = false; - } - if (tp.HasValueTypeConstraint) { - if (!NullableType.IsNonNullableValueType(typeArg)) - ConstraintsValid = false; - } - if (tp.HasDefaultConstructorConstraint) { - ITypeDefinition def = typeArg.GetDefinition(); - if (def != null && def.IsAbstract) - ConstraintsValid = false; - ConstraintsValid &= typeArg.GetConstructors( - m => m.Parameters.Count == 0 && m.Accessibility == Accessibility.Public, - GetMemberOptions.IgnoreInheritedMembers | GetMemberOptions.ReturnMemberDefinitions - ).Any(); - } - foreach (IType constraintType in tp.DirectBaseTypes) { - IType c = constraintType.AcceptVisitor(newParameterizedType.GetSubstitution()); - ConstraintsValid &= conversions.IsConstraintConvertible(typeArg, c); + if (!ValidateConstraints(typeParameters[i], newParameterizedType.GetTypeArgument(i), substitution, conversions)) { + ConstraintsValid = false; + break; } } } @@ -457,6 +432,64 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver } #endregion + #region Validate Constraints + /// + /// Validates whether the given type argument satisfies the constraints for the given type parameter. + /// + /// The type parameter. + /// The type argument. + /// The substitution that defines how type parameters are replaced with type arguments. + /// The substitution is used to check constraints that depend on other type parameters (or recursively on the same type parameter). + /// True if the constraints are satisfied; false otherwise. + public static bool ValidateConstraints(ITypeParameter typeParameter, IType typeArgument, TypeVisitor substitution) + { + if (typeParameter == null) + throw new ArgumentNullException("typeParameter"); + if (typeParameter.Owner == null) + throw new ArgumentNullException("typeParameter.Owner"); + if (typeArgument == null) + throw new ArgumentNullException("typeArgument"); + return ValidateConstraints(typeParameter, typeArgument, substitution, Conversions.Get(typeParameter.Owner.Compilation)); + } + + internal static bool ValidateConstraints(ITypeParameter typeParameter, IType typeArgument, TypeVisitor substitution, Conversions conversions) + { + switch (typeArgument.Kind) { // void, null, and pointers cannot be used as type arguments + case TypeKind.Void: + case TypeKind.Null: + case TypeKind.Pointer: + return false; + } + if (typeParameter.HasReferenceTypeConstraint) { + if (typeArgument.IsReferenceType != true) + return false; + } + if (typeParameter.HasValueTypeConstraint) { + if (!NullableType.IsNonNullableValueType(typeArgument)) + return false; + } + if (typeParameter.HasDefaultConstructorConstraint) { + ITypeDefinition def = typeArgument.GetDefinition(); + if (def != null && def.IsAbstract) + return false; + var ctors = typeArgument.GetConstructors( + m => m.Parameters.Count == 0 && m.Accessibility == Accessibility.Public, + GetMemberOptions.IgnoreInheritedMembers | GetMemberOptions.ReturnMemberDefinitions + ); + if (!ctors.Any()) + return false; + } + foreach (IType constraintType in typeParameter.DirectBaseTypes) { + IType c = constraintType; + if (substitution != null) + c = c.AcceptVisitor(substitution); + if (!conversions.IsConstraintConvertible(typeArgument, c)) + return false; + } + return true; + } + #endregion + #region CheckApplicability void CheckApplicability(Candidate candidate) {