diff --git a/ICSharpCode.NRefactory/CSharp/Resolver/OverloadResolution.cs b/ICSharpCode.NRefactory/CSharp/Resolver/OverloadResolution.cs index e40d15565c..a895b4f8cb 100644 --- a/ICSharpCode.NRefactory/CSharp/Resolver/OverloadResolution.cs +++ b/ICSharpCode.NRefactory/CSharp/Resolver/OverloadResolution.cs @@ -36,6 +36,8 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver public bool HasUnmappedOptionalParameters; + public IType[] InferredTypes; + public IList Parameters { get { return Member.Parameters; } } public bool IsGenericMethod { @@ -80,9 +82,10 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver //List candidates = new List(); Candidate bestCandidate; Candidate bestCandidateAmbiguousWith; + IType[] explicitlyGivenTypeArguments; #region Constructor - public OverloadResolution(ITypeResolveContext context, ResolveResult[] arguments, string[] argumentNames = null) + public OverloadResolution(ITypeResolveContext context, ResolveResult[] arguments, string[] argumentNames = null, IType[] typeArguments = null) { if (context == null) throw new ArgumentNullException("context"); @@ -95,6 +98,11 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver this.context = context; this.arguments = arguments; this.argumentNames = argumentNames; + + // keep explicitlyGivenTypeArguments==null when no type arguments were specified + if (typeArguments != null && typeArguments.Length > 0) + this.explicitlyGivenTypeArguments = typeArguments; + this.conversions = new Conversions(context); } #endregion @@ -132,6 +140,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver if (!ResolveParameterTypes(candidate)) return false; MapCorrespondingParameters(candidate); + RunTypeInference(candidate); CheckApplicability(candidate); ConsiderIfNewCandidateIsBest(candidate); return true; @@ -184,6 +193,120 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver } #endregion + #region RunTypeInference + void RunTypeInference(Candidate candidate) + { + IMethod method = candidate.Member as IMethod; + if (method == null || method.TypeParameters.Count == 0) { + if (explicitlyGivenTypeArguments != null) { + // method does not expect type arguments, but was given some + candidate.AddError(OverloadResolutionErrors.WrongNumberOfTypeArguments); + } + return; + } + // The method is generic: + if (explicitlyGivenTypeArguments != null) { + if (explicitlyGivenTypeArguments.Length == method.TypeParameters.Count) { + candidate.InferredTypes = explicitlyGivenTypeArguments; + } else { + candidate.AddError(OverloadResolutionErrors.WrongNumberOfTypeArguments); + // wrong number of type arguments given, so truncate the list or pad with UnknownType + candidate.InferredTypes = new IType[method.TypeParameters.Count]; + for (int i = 0; i < candidate.InferredTypes.Length; i++) { + if (i < explicitlyGivenTypeArguments.Length) + candidate.InferredTypes[i] = explicitlyGivenTypeArguments[i]; + else + candidate.InferredTypes[i] = SharedTypes.UnknownType; + } + } + } else { + TypeInference ti = new TypeInference(context); + bool success; + candidate.InferredTypes = ti.InferTypeArguments(method.TypeParameters, arguments, candidate.ParameterTypes, out success); + if (!success) + candidate.AddError(OverloadResolutionErrors.TypeInferenceFailed); + } + // Now substitute in the formal parameters: + var substitution = new ConstraintValidatingSubstitution(candidate.InferredTypes, this); + for (int i = 0; i < candidate.ParameterTypes.Length; i++) { + candidate.ParameterTypes[i] = candidate.ParameterTypes[i].AcceptVisitor(substitution); + } + if (!substitution.ConstraintsValid) + candidate.AddError(OverloadResolutionErrors.ConstructedTypeDoesNotSatisfyContraint); + } + + sealed class ConstraintValidatingSubstitution : TypeVisitor + { + readonly IType[] typeArguments; + readonly OverloadResolution overloadResolution; + public bool ConstraintsValid = true; + + public ConstraintValidatingSubstitution(IType[] typeArguments, OverloadResolution overloadResolution) + { + this.typeArguments = typeArguments; + this.overloadResolution = overloadResolution; + } + + public override IType VisitTypeParameter(ITypeParameter type) + { + int index = type.Index; + if (type.ParentMethod != null) { + if (index >= 0 && index < typeArguments.Length) + return typeArguments[index]; + else + return SharedTypes.UnknownType; + } else { + return base.VisitTypeParameter(type); + } + } + + public override IType VisitParameterizedType(ParameterizedType type) + { + IType newType = base.VisitParameterizedType(type); + if (newType != type && ConstraintsValid) { + // something was changed, so we need to validate the constraints + ParameterizedType newParameterizedType = newType as ParameterizedType; + if (newParameterizedType != null) { + // C# 4.0 spec: ยง4.4.4 Satisfying constraints + var typeParameters = newParameterizedType.GetDefinition().TypeParameters; + for (int i = 0; i < typeParameters.Count; i++) { + ITypeParameter tp = typeParameters[i]; + IType typeArg = newParameterizedType.TypeArguments[i]; + if (tp.HasReferenceTypeConstraint) { + if (typeArg.IsReferenceType != true) + ConstraintsValid = false; + } + if (tp.HasValueTypeConstraint) { + if (typeArg.IsReferenceType != false) + ConstraintsValid = false; + } + if (tp.HasDefaultConstructorConstraint) { + ITypeDefinition def = typeArg.GetDefinition(); + if (def != null && def.IsAbstract) + ConstraintsValid = false; + ConstraintsValid &= typeArg.GetConstructors( + overloadResolution.context, + m => m.Parameters.Count == 0 && m.Accessibility == Accessibility.Public + ).Any(); + } + foreach (IType constraintType in tp.Constraints) { + IType c = newParameterizedType.SubstituteInType(constraintType); + ConstraintsValid &= overloadResolution.IsConstraintConvertible(typeArg, c); + } + } + } + } + return newType; + } + } + + bool IsConstraintConvertible(IType typeArg, IType constraintType) + { + // TODO: this isn't exactly correct; not all kinds of implicit conversions are allowed here + return conversions.ImplicitConversion(typeArg, constraintType); + } + #endregion + #region CheckApplicability void CheckApplicability(Candidate candidate) { @@ -422,5 +545,14 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver public bool IsAmbiguous { get { return bestCandidateAmbiguousWith != null; } } + + public IList InferredTypeArguments { + get { + if (bestCandidate != null && bestCandidate.InferredTypes != null) + return Array.AsReadOnly(bestCandidate.InferredTypes); + else + return EmptyList.Instance; + } + } } } diff --git a/ICSharpCode.NRefactory/CSharp/Resolver/OverloadResolutionErrors.cs b/ICSharpCode.NRefactory/CSharp/Resolver/OverloadResolutionErrors.cs index e53babe239..672744eb4e 100644 --- a/ICSharpCode.NRefactory/CSharp/Resolver/OverloadResolutionErrors.cs +++ b/ICSharpCode.NRefactory/CSharp/Resolver/OverloadResolutionErrors.cs @@ -22,24 +22,33 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver /// TypeInferenceFailed = 0x0004, /// + /// Type arguments were explicitly specified, but did not match the number of type parameters. + /// + WrongNumberOfTypeArguments = 0x0008, + /// + /// After substituting type parameters with the inferred types; a constructed type within the formal parameters + /// does not satisfy its contraint. + /// + ConstructedTypeDoesNotSatisfyContraint = 0x0010, + /// /// No argument was mapped to a non-optional parameter /// - MissingArgumentForRequiredParameter = 0x0008, + MissingArgumentForRequiredParameter = 0x0020, /// /// Several arguments were mapped to a single (non-params-array) parameter /// - MultipleArgumentsForSingleParameter = 0x0010, + MultipleArgumentsForSingleParameter = 0x0040, /// /// 'ref'/'out' passing mode doesn't match for at least 1 parameter /// - ParameterPassingModeMismatch = 0x0020, + ParameterPassingModeMismatch = 0x0080, /// /// Argument type cannot be converted to parameter type /// - ArgumentTypeMismatch = 0x0040, + ArgumentTypeMismatch = 0x0100, /// /// There is no unique best overload /// - AmbiguousMatch = 0x0080 + AmbiguousMatch = 0x0200 } } diff --git a/ICSharpCode.NRefactory/TypeSystem/ParameterizedType.cs b/ICSharpCode.NRefactory/TypeSystem/ParameterizedType.cs index 08a77e94ab..0d0bb77f49 100644 --- a/ICSharpCode.NRefactory/TypeSystem/ParameterizedType.cs +++ b/ICSharpCode.NRefactory/TypeSystem/ParameterizedType.cs @@ -44,11 +44,6 @@ namespace ICSharpCode.NRefactory.TypeSystem return base.VisitTypeParameter(type); } } - - public IType Apply(IType type) - { - return type.AcceptVisitor(this); - } } readonly ITypeDefinition genericType; @@ -152,10 +147,19 @@ namespace ICSharpCode.NRefactory.TypeSystem return this; } + /// + /// Substitutes the class type parameters in the with the + /// type arguments of this parameterized type. + /// + public IType SubstituteInType(IType type) + { + return type.AcceptVisitor(new Substitution(typeArguments)); + } + public IEnumerable GetBaseTypes(ITypeResolveContext context) { Substitution substitution = new Substitution(typeArguments); - return genericType.GetBaseTypes(context).Select(substitution.Apply); + return genericType.GetBaseTypes(context).Select(t => t.AcceptVisitor(substitution)); } public IEnumerable GetNestedTypes(ITypeResolveContext context, Predicate filter = null)