// Copyright (c) 2010 AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) // This code is distributed under MIT X11 license (for details please see \doc\license.txt) using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using ICSharpCode.NRefactory.TypeSystem; using ICSharpCode.NRefactory.TypeSystem.Implementation; using NUnit.Framework; namespace ICSharpCode.NRefactory.CSharp.Resolver { [TestFixture] public class OverloadResolutionTests { readonly ITypeResolveContext context = new CompositeTypeResolveContext( new[] { CecilLoaderTests.Mscorlib, CecilLoaderTests.SystemCore }); readonly DefaultTypeDefinition dummyClass = new DefaultTypeDefinition(CecilLoaderTests.Mscorlib, string.Empty, "DummyClass"); ResolveResult[] MakeArgumentList(params Type[] argumentTypes) { return argumentTypes.Select(t => new ResolveResult(t.ToTypeReference().Resolve(context))).ToArray(); } DefaultMethod MakeMethod(params object[] parameterTypesOrDefaultValues) { DefaultMethod m = new DefaultMethod(dummyClass, "Method"); foreach (var typeOrDefaultValue in parameterTypesOrDefaultValues) { Type type = typeOrDefaultValue as Type; if (type != null) m.Parameters.Add(new DefaultParameter(type.ToTypeReference(), string.Empty)); else if (Type.GetTypeCode(typeOrDefaultValue.GetType()) > TypeCode.Object) m.Parameters.Add(new DefaultParameter(typeOrDefaultValue.GetType().ToTypeReference(), string.Empty) { DefaultValue = new SimpleConstantValue(typeOrDefaultValue.GetType().ToTypeReference(), typeOrDefaultValue) }); else throw new ArgumentException(typeOrDefaultValue.ToString()); } return m; } DefaultMethod MakeParamsMethod(params object[] parameterTypesOrDefaultValues) { DefaultMethod m = MakeMethod(parameterTypesOrDefaultValues); ((DefaultParameter)m.Parameters.Last()).IsParams = true; return m; } DefaultParameter MakeOptionalParameter(IType type, string name) { return new DefaultParameter(type, name) { DefaultValue = new SimpleConstantValue(type, null) }; } [Test] public void PreferIntOverUInt() { OverloadResolution r = new OverloadResolution(context, MakeArgumentList(typeof(ushort))); var c1 = MakeMethod(typeof(int)); Assert.AreEqual(OverloadResolutionErrors.None, r.AddCandidate(c1)); Assert.AreEqual(OverloadResolutionErrors.None, r.AddCandidate(MakeMethod(typeof(uint)))); Assert.IsFalse(r.IsAmbiguous); Assert.AreSame(c1, r.BestCandidate); } [Test] public void NullableIntAndNullableUIntIsAmbiguous() { OverloadResolution r = new OverloadResolution(context, MakeArgumentList(typeof(ushort?))); Assert.AreEqual(OverloadResolutionErrors.None, r.AddCandidate(MakeMethod(typeof(int?)))); Assert.AreEqual(OverloadResolutionErrors.None, r.AddCandidate(MakeMethod(typeof(uint?)))); Assert.AreEqual(OverloadResolutionErrors.AmbiguousMatch, r.BestCandidateErrors); // then adding a matching overload solves the ambiguity: Assert.AreEqual(OverloadResolutionErrors.None, r.AddCandidate(MakeMethod(typeof(ushort?)))); Assert.AreEqual(OverloadResolutionErrors.None, r.BestCandidateErrors); Assert.IsNull(r.BestCandidateAmbiguousWith); } [Test] public void ParamsMethodMatchesEmptyArgumentList() { OverloadResolution r = new OverloadResolution(context, MakeArgumentList()); Assert.AreEqual(OverloadResolutionErrors.None, r.AddCandidate(MakeParamsMethod(typeof(int[])))); Assert.IsTrue(r.BestCandidateIsExpandedForm); } [Test] public void ParamsMethodMatchesOneArgumentInExpandedForm() { OverloadResolution r = new OverloadResolution(context, MakeArgumentList(typeof(int))); Assert.AreEqual(OverloadResolutionErrors.None, r.AddCandidate(MakeParamsMethod(typeof(int[])))); Assert.IsTrue(r.BestCandidateIsExpandedForm); } [Test] public void ParamsMethodMatchesInUnexpandedForm() { OverloadResolution r = new OverloadResolution(context, MakeArgumentList(typeof(int[]))); Assert.AreEqual(OverloadResolutionErrors.None, r.AddCandidate(MakeParamsMethod(typeof(int[])))); Assert.IsFalse(r.BestCandidateIsExpandedForm); } [Test] public void LessArgumentsPassedToParamsIsBetter() { OverloadResolution r = new OverloadResolution(context, MakeArgumentList(typeof(int), typeof(int), typeof(int))); Assert.AreEqual(OverloadResolutionErrors.None, r.AddCandidate(MakeParamsMethod(typeof(int[])))); Assert.AreEqual(OverloadResolutionErrors.None, r.AddCandidate(MakeParamsMethod(typeof(int), typeof(int[])))); Assert.IsFalse(r.IsAmbiguous); Assert.AreEqual(2, r.BestCandidate.Parameters.Count); } [Test] public void CallInvalidParamsDeclaration() { OverloadResolution r = new OverloadResolution(context, MakeArgumentList(typeof(int[,]))); Assert.AreEqual(OverloadResolutionErrors.ArgumentTypeMismatch, r.AddCandidate(MakeParamsMethod(typeof(int)))); Assert.IsFalse(r.BestCandidateIsExpandedForm); } [Test] public void PreferMethodWithoutOptionalParameters() { var m1 = MakeMethod(); var m2 = MakeMethod(1); OverloadResolution r = new OverloadResolution(context, MakeArgumentList()); Assert.AreEqual(OverloadResolutionErrors.None, r.AddCandidate(m1)); Assert.AreEqual(OverloadResolutionErrors.None, r.AddCandidate(m2)); Assert.IsFalse(r.IsAmbiguous); Assert.AreSame(m1, r.BestCandidate); } [Test] public void SkeetEvilOverloadResolution() { // http://msmvps.com/blogs/jon_skeet/archive/2010/11/02/evil-code-overload-resolution-workaround.aspx // static void Foo(T? ignored = default(T?)) where T : struct var m1 = MakeMethod(); m1.TypeParameters.Add(new DefaultTypeParameter(EntityType.Method, 0, "T") { HasValueTypeConstraint = true }); m1.Parameters.Add(MakeOptionalParameter( NullableType.Create(m1.TypeParameters[0], context), "ignored" )); // class ClassConstraint where T : class {} DefaultTypeDefinition classConstraint = new DefaultTypeDefinition(dummyClass, "ClassConstraint"); classConstraint.TypeParameters.Add(new DefaultTypeParameter(EntityType.TypeDefinition, 0, "T") { HasReferenceTypeConstraint = true }); // static void Foo(ClassConstraint ignored = default(ClassConstraint)) // where T : class var m2 = MakeMethod(); m2.TypeParameters.Add(new DefaultTypeParameter(EntityType.Method, 0, "T") { HasReferenceTypeConstraint = true }); m2.Parameters.Add(MakeOptionalParameter( new ParameterizedType(classConstraint, new[] { m2.TypeParameters[0] }), "ignored" )); // static void Foo() var m3 = MakeMethod(); m3.TypeParameters.Add(new DefaultTypeParameter(EntityType.Method, 0, "T")); // Call: Foo(); OverloadResolution o; o = new OverloadResolution(context, new ResolveResult[0], typeArguments: new[] { typeof(int).ToTypeReference().Resolve(context) }); Assert.AreEqual(OverloadResolutionErrors.None, o.AddCandidate(m1)); Assert.AreEqual(OverloadResolutionErrors.ConstructedTypeDoesNotSatisfyConstraint, o.AddCandidate(m2)); Assert.AreSame(m1, o.BestCandidate); // Call: Foo(); o = new OverloadResolution(context, new ResolveResult[0], typeArguments: new[] { typeof(string).ToTypeReference().Resolve(context) }); Assert.AreEqual(OverloadResolutionErrors.ConstructedTypeDoesNotSatisfyConstraint, o.AddCandidate(m1)); Assert.AreEqual(OverloadResolutionErrors.None, o.AddCandidate(m2)); Assert.AreSame(m2, o.BestCandidate); // Call: Foo(); o = new OverloadResolution(context, new ResolveResult[0], typeArguments: new[] { typeof(int?).ToTypeReference().Resolve(context) }); Assert.AreEqual(OverloadResolutionErrors.ConstructedTypeDoesNotSatisfyConstraint, o.AddCandidate(m1)); Assert.AreEqual(OverloadResolutionErrors.ConstructedTypeDoesNotSatisfyConstraint, o.AddCandidate(m2)); Assert.AreEqual(OverloadResolutionErrors.None, o.AddCandidate(m3)); Assert.AreSame(m3, o.BestCandidate); } /// /// A lambda of the form "() => default(returnType)" /// class MockLambda : LambdaResolveResult { IType inferredReturnType; List parameters = new List(); public MockLambda(IType returnType) { this.inferredReturnType = returnType; } public override IList Parameters { get { return parameters; } } public override Conversion IsValid(IType[] parameterTypes, IType returnType, Conversions conversions) { return conversions.ImplicitConversion(inferredReturnType, returnType); } public override bool IsImplicitlyTyped { get { return false; } } public override bool IsAnonymousMethod { get { return false; } } public override bool HasParameterList { get { return true; } } public override IType GetInferredReturnType(IType[] parameterTypes) { return inferredReturnType; } } [Test] public void BetterConversionByLambdaReturnValue() { var m1 = MakeMethod(typeof(Func)); var m2 = MakeMethod(typeof(Func)); // M(() => default(byte)); ResolveResult[] args = { new MockLambda(KnownTypeReference.Byte.Resolve(context)) }; OverloadResolution r = new OverloadResolution(context, args); Assert.AreEqual(OverloadResolutionErrors.None, r.AddCandidate(m1)); Assert.AreEqual(OverloadResolutionErrors.None, r.AddCandidate(m2)); Assert.AreSame(m2, r.BestCandidate); Assert.AreEqual(OverloadResolutionErrors.None, r.BestCandidateErrors); } [Test] public void BetterConversionByLambdaReturnValue_ExpressionTree() { var m1 = MakeMethod(typeof(Func)); var m2 = MakeMethod(typeof(Expression>)); // M(() => default(byte)); ResolveResult[] args = { new MockLambda(KnownTypeReference.Byte.Resolve(context)) }; OverloadResolution r = new OverloadResolution(context, args); Assert.AreEqual(OverloadResolutionErrors.None, r.AddCandidate(m1)); Assert.AreEqual(OverloadResolutionErrors.None, r.AddCandidate(m2)); Assert.AreSame(m2, r.BestCandidate); Assert.AreEqual(OverloadResolutionErrors.None, r.BestCandidateErrors); } [Test] public void Lambda_DelegateAndExpressionTreeOverloadsAreAmbiguous() { var m1 = MakeMethod(typeof(Func)); var m2 = MakeMethod(typeof(Expression>)); // M(() => default(int)); ResolveResult[] args = { new MockLambda(KnownTypeReference.Int32.Resolve(context)) }; OverloadResolution r = new OverloadResolution(context, args); Assert.AreEqual(OverloadResolutionErrors.None, r.AddCandidate(m1)); Assert.AreEqual(OverloadResolutionErrors.None, r.AddCandidate(m2)); Assert.AreEqual(OverloadResolutionErrors.AmbiguousMatch, r.BestCandidateErrors); } } }