From 7b1530e814381695db7e89f166084735418531e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20K=C3=A4ll=C3=A9n?= Date: Sun, 23 Dec 2012 13:46:32 +0100 Subject: [PATCH 1/2] When invoking a method with dynamic arguments (and there is only one applicable method), convert the result of the call to 'dynamic' (http://blogs.msdn.com/b/ericlippert/archive/2012/10/22/a-method-group-of-one.aspx) --- .../Resolver/CSharpResolver.cs | 21 +++++---- .../CSharp/Resolver/DynamicTests.cs | 47 +++++++++++++++---- 2 files changed, 51 insertions(+), 17 deletions(-) diff --git a/ICSharpCode.NRefactory.CSharp/Resolver/CSharpResolver.cs b/ICSharpCode.NRefactory.CSharp/Resolver/CSharpResolver.cs index a65bbced93..d74ca9180a 100644 --- a/ICSharpCode.NRefactory.CSharp/Resolver/CSharpResolver.cs +++ b/ICSharpCode.NRefactory.CSharp/Resolver/CSharpResolver.cs @@ -1948,6 +1948,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver } MethodGroupResolveResult mgrr = target as MethodGroupResolveResult; + Func convert = x => x; if (mgrr != null) { if (arguments.Any(a => a.Type.Kind == TypeKind.Dynamic)) { // If we have dynamic arguments, we need to represent the invocation as a dynamic invocation if there is more than one applicable method. @@ -1969,41 +1970,43 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver } return new DynamicInvocationResolveResult(new MethodGroupResolveResult(actualTarget, mgrr.MethodName, l, mgrr.TypeArguments), DynamicInvocationType.Invocation, AddArgumentNamesIfNecessary(arguments, argumentNames)); } + else { + convert = x => x.Type.Kind == TypeKind.Dynamic ? x : Convert(x, SpecialType.Dynamic); + } } OverloadResolution or = mgrr.PerformOverloadResolution(compilation, arguments, argumentNames, checkForOverflow: checkForOverflow, conversions: conversions); if (or.BestCandidate != null) { if (or.BestCandidate.IsStatic && !or.IsExtensionMethodInvocation && !(mgrr.TargetResult is TypeResolveResult)) - return or.CreateResolveResult(new TypeResolveResult(mgrr.TargetType)); + return convert(or.CreateResolveResult(new TypeResolveResult(mgrr.TargetType))); else - return or.CreateResolveResult(mgrr.TargetResult); + return convert(or.CreateResolveResult(mgrr.TargetResult)); } else { // No candidate found at all (not even an inapplicable one). // This can happen with empty method groups (as sometimes used with extension methods) - return new UnknownMethodResolveResult( - mgrr.TargetType, mgrr.MethodName, mgrr.TypeArguments, CreateParameters(arguments, argumentNames)); + return convert(new UnknownMethodResolveResult(mgrr.TargetType, mgrr.MethodName, mgrr.TypeArguments, CreateParameters(arguments, argumentNames))); } } UnknownMemberResolveResult umrr = target as UnknownMemberResolveResult; if (umrr != null) { - return new UnknownMethodResolveResult(umrr.TargetType, umrr.MemberName, umrr.TypeArguments, CreateParameters(arguments, argumentNames)); + return convert(new UnknownMethodResolveResult(umrr.TargetType, umrr.MemberName, umrr.TypeArguments, CreateParameters(arguments, argumentNames))); } UnknownIdentifierResolveResult uirr = target as UnknownIdentifierResolveResult; if (uirr != null && CurrentTypeDefinition != null) { - return new UnknownMethodResolveResult(CurrentTypeDefinition, uirr.Identifier, EmptyList.Instance, CreateParameters(arguments, argumentNames)); + return convert(new UnknownMethodResolveResult(CurrentTypeDefinition, uirr.Identifier, EmptyList.Instance, CreateParameters(arguments, argumentNames))); } IMethod invokeMethod = target.Type.GetDelegateInvokeMethod(); if (invokeMethod != null) { OverloadResolution or = CreateOverloadResolution(arguments, argumentNames); or.AddCandidate(invokeMethod); - return new CSharpInvocationResolveResult( + return convert(new CSharpInvocationResolveResult( target, invokeMethod, //invokeMethod.ReturnType.Resolve(context), or.GetArgumentsWithConversionsAndNames(), or.BestCandidateErrors, isExpandedForm: or.BestCandidateIsExpandedForm, isDelegateInvocation: true, - argumentToParameterMap: or.GetArgumentToParameterMap()); + argumentToParameterMap: or.GetArgumentToParameterMap())); } - return ErrorResult; + return convert(ErrorResult); } List CreateParameters(ResolveResult[] arguments, string[] argumentNames) diff --git a/ICSharpCode.NRefactory.Tests/CSharp/Resolver/DynamicTests.cs b/ICSharpCode.NRefactory.Tests/CSharp/Resolver/DynamicTests.cs index 44f7daf1ab..d78982c100 100644 --- a/ICSharpCode.NRefactory.Tests/CSharp/Resolver/DynamicTests.cs +++ b/ICSharpCode.NRefactory.Tests/CSharp/Resolver/DynamicTests.cs @@ -109,20 +109,50 @@ class TestClass { [Test] public void InvocationWithDynamicArgumentWithOneApplicableMethod() { string program = @"using System; +class TestClass { + public int SomeMethod(int a) {} + public int SomeMethod(int a, string b) {} + + void F() { + dynamic obj = null; + var x = $this.SomeMethod(obj)$; + } +}"; + var rr = Resolve(program); + Assert.That(rr.IsError, Is.False); + Assert.That(rr.Type.Kind, Is.EqualTo(TypeKind.Dynamic)); + var irr = rr.Input as CSharpInvocationResolveResult; + Assert.That(irr, Is.Not.Null); + Assert.That(irr.Member.Name, Is.EqualTo("SomeMethod")); + Assert.That(((IParameterizedMember)irr.Member).Parameters.Count, Is.EqualTo(1)); + Assert.That(irr.Arguments.Count, Is.EqualTo(1)); + var cr = irr.Arguments[0] as ConversionResolveResult; + Assert.That(cr, Is.Not.Null); + Assert.That(cr.Conversion.IsImplicit, Is.True); + Assert.That(cr.Conversion.IsDynamicConversion, Is.True); + Assert.That(cr.Input is LocalResolveResult && ((LocalResolveResult)cr.Input).Variable.Name == "obj"); + } + + [Test] + public void InvocationWithDynamicArgumentWithOneApplicableMethodReturningVoid() { + string program = @"using System; class TestClass { public void SomeMethod(int a) {} - public void SomeMethod(int a, string b) {} void F() { dynamic obj = null; var x = $this.SomeMethod(obj)$; } }"; - var rr = Resolve(program); - Assert.That(rr.Member.Name, Is.EqualTo("SomeMethod")); - Assert.That(((IParameterizedMember)rr.Member).Parameters.Count, Is.EqualTo(1)); - Assert.That(rr.Arguments.Count, Is.EqualTo(1)); - var cr = rr.Arguments[0] as ConversionResolveResult; + var rr = Resolve(program); + Assert.That(rr.IsError, Is.False); + Assert.That(rr.Type.Kind, Is.EqualTo(TypeKind.Dynamic)); + var irr = rr.Input as CSharpInvocationResolveResult; + Assert.That(irr, Is.Not.Null); + Assert.That(irr.Member.Name, Is.EqualTo("SomeMethod")); + Assert.That(((IParameterizedMember)irr.Member).Parameters.Count, Is.EqualTo(1)); + Assert.That(irr.Arguments.Count, Is.EqualTo(1)); + var cr = irr.Arguments[0] as ConversionResolveResult; Assert.That(cr, Is.Not.Null); Assert.That(cr.Conversion.IsImplicit, Is.True); Assert.That(cr.Conversion.IsDynamicConversion, Is.True); @@ -286,8 +316,9 @@ class TestClass { var x = $this.SomeMethod(obj)$; } }"; - var rr = Resolve(program); - Assert.That(rr.IsError, Is.True); + var rr = Resolve(program); + var irr = rr.Input; + Assert.That(irr.IsError, Is.True); } [Test] From f186d27cc9a121dc992082d267c17ecb88bc34dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20K=C3=A4ll=C3=A9n?= Date: Fri, 11 Jan 2013 23:05:18 +0100 Subject: [PATCH 2/2] Return a CSharpInvocationResolveResult with the type overridden with Dynamic instead of a converted invocation --- .../Resolver/CSharpInvocationResolveResult.cs | 5 ++-- .../Resolver/CSharpResolver.cs | 25 +++++++++---------- .../Resolver/OverloadResolution.cs | 8 ++++-- .../CSharp/Resolver/DynamicTests.cs | 21 +++++++--------- .../Semantics/InvocationResolveResult.cs | 5 ++-- .../Semantics/MemberResolveResult.cs | 8 +++--- 6 files changed, 37 insertions(+), 35 deletions(-) diff --git a/ICSharpCode.NRefactory.CSharp/Resolver/CSharpInvocationResolveResult.cs b/ICSharpCode.NRefactory.CSharp/Resolver/CSharpInvocationResolveResult.cs index 066dc38ae4..3ef49d921c 100644 --- a/ICSharpCode.NRefactory.CSharp/Resolver/CSharpInvocationResolveResult.cs +++ b/ICSharpCode.NRefactory.CSharp/Resolver/CSharpInvocationResolveResult.cs @@ -57,9 +57,10 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver bool isExpandedForm = false, bool isDelegateInvocation = false, IList argumentToParameterMap = null, - IList initializerStatements = null + IList initializerStatements = null, + IType returnTypeOverride = null ) - : base(targetResult, member, arguments, initializerStatements) + : base(targetResult, member, arguments, initializerStatements, returnTypeOverride) { this.OverloadResolutionErrors = overloadResolutionErrors; this.IsExtensionMethodInvocation = isExtensionMethodInvocation; diff --git a/ICSharpCode.NRefactory.CSharp/Resolver/CSharpResolver.cs b/ICSharpCode.NRefactory.CSharp/Resolver/CSharpResolver.cs index d74ca9180a..f669ae49ac 100644 --- a/ICSharpCode.NRefactory.CSharp/Resolver/CSharpResolver.cs +++ b/ICSharpCode.NRefactory.CSharp/Resolver/CSharpResolver.cs @@ -1947,10 +1947,10 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver return new DynamicInvocationResolveResult(target, DynamicInvocationType.Invocation, AddArgumentNamesIfNecessary(arguments, argumentNames)); } + bool isDynamic = arguments.Any(a => a.Type.Kind == TypeKind.Dynamic); MethodGroupResolveResult mgrr = target as MethodGroupResolveResult; - Func convert = x => x; if (mgrr != null) { - if (arguments.Any(a => a.Type.Kind == TypeKind.Dynamic)) { + if (isDynamic) { // If we have dynamic arguments, we need to represent the invocation as a dynamic invocation if there is more than one applicable method. var or2 = CreateOverloadResolution(arguments, argumentNames, mgrr.TypeArguments.ToArray()); var applicableMethods = mgrr.MethodsGroupedByDeclaringType.SelectMany(m => m, (x, m) => new { x.DeclaringType, Method = m }).Where(x => OverloadResolution.IsApplicable(or2.AddCandidate(x.Method))).ToList(); @@ -1970,43 +1970,42 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver } return new DynamicInvocationResolveResult(new MethodGroupResolveResult(actualTarget, mgrr.MethodName, l, mgrr.TypeArguments), DynamicInvocationType.Invocation, AddArgumentNamesIfNecessary(arguments, argumentNames)); } - else { - convert = x => x.Type.Kind == TypeKind.Dynamic ? x : Convert(x, SpecialType.Dynamic); - } } OverloadResolution or = mgrr.PerformOverloadResolution(compilation, arguments, argumentNames, checkForOverflow: checkForOverflow, conversions: conversions); if (or.BestCandidate != null) { if (or.BestCandidate.IsStatic && !or.IsExtensionMethodInvocation && !(mgrr.TargetResult is TypeResolveResult)) - return convert(or.CreateResolveResult(new TypeResolveResult(mgrr.TargetType))); + return or.CreateResolveResult(new TypeResolveResult(mgrr.TargetType), returnTypeOverride: isDynamic ? SpecialType.Dynamic : null); else - return convert(or.CreateResolveResult(mgrr.TargetResult)); + return or.CreateResolveResult(mgrr.TargetResult, returnTypeOverride: isDynamic ? SpecialType.Dynamic : null); } else { // No candidate found at all (not even an inapplicable one). // This can happen with empty method groups (as sometimes used with extension methods) - return convert(new UnknownMethodResolveResult(mgrr.TargetType, mgrr.MethodName, mgrr.TypeArguments, CreateParameters(arguments, argumentNames))); + return new UnknownMethodResolveResult( + mgrr.TargetType, mgrr.MethodName, mgrr.TypeArguments, CreateParameters(arguments, argumentNames)); } } UnknownMemberResolveResult umrr = target as UnknownMemberResolveResult; if (umrr != null) { - return convert(new UnknownMethodResolveResult(umrr.TargetType, umrr.MemberName, umrr.TypeArguments, CreateParameters(arguments, argumentNames))); + return new UnknownMethodResolveResult(umrr.TargetType, umrr.MemberName, umrr.TypeArguments, CreateParameters(arguments, argumentNames)); } UnknownIdentifierResolveResult uirr = target as UnknownIdentifierResolveResult; if (uirr != null && CurrentTypeDefinition != null) { - return convert(new UnknownMethodResolveResult(CurrentTypeDefinition, uirr.Identifier, EmptyList.Instance, CreateParameters(arguments, argumentNames))); + return new UnknownMethodResolveResult(CurrentTypeDefinition, uirr.Identifier, EmptyList.Instance, CreateParameters(arguments, argumentNames)); } IMethod invokeMethod = target.Type.GetDelegateInvokeMethod(); if (invokeMethod != null) { OverloadResolution or = CreateOverloadResolution(arguments, argumentNames); or.AddCandidate(invokeMethod); - return convert(new CSharpInvocationResolveResult( + return new CSharpInvocationResolveResult( target, invokeMethod, //invokeMethod.ReturnType.Resolve(context), or.GetArgumentsWithConversionsAndNames(), or.BestCandidateErrors, isExpandedForm: or.BestCandidateIsExpandedForm, isDelegateInvocation: true, - argumentToParameterMap: or.GetArgumentToParameterMap())); + argumentToParameterMap: or.GetArgumentToParameterMap(), + returnTypeOverride: isDynamic ? SpecialType.Dynamic : null); } - return convert(ErrorResult); + return ErrorResult; } List CreateParameters(ResolveResult[] arguments, string[] argumentNames) diff --git a/ICSharpCode.NRefactory.CSharp/Resolver/OverloadResolution.cs b/ICSharpCode.NRefactory.CSharp/Resolver/OverloadResolution.cs index 6c4e814f20..d21f7e21df 100644 --- a/ICSharpCode.NRefactory.CSharp/Resolver/OverloadResolution.cs +++ b/ICSharpCode.NRefactory.CSharp/Resolver/OverloadResolution.cs @@ -933,8 +933,11 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver /// /// Statements for Objects/Collections initializer. /// + /// + /// If not null, use this instead of the ReturnType of the member as the type of the created resolve result. /// - public CSharpInvocationResolveResult CreateResolveResult(ResolveResult targetResolveResult, IList initializerStatements = null) + /// + public CSharpInvocationResolveResult CreateResolveResult(ResolveResult targetResolveResult, IList initializerStatements = null, IType returnTypeOverride = null) { IParameterizedMember member = GetBestCandidateWithSubstitutedTypeArguments(); if (member == null) @@ -949,7 +952,8 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver this.BestCandidateIsExpandedForm, isDelegateInvocation: false, argumentToParameterMap: this.GetArgumentToParameterMap(), - initializerStatements: initializerStatements); + initializerStatements: initializerStatements, + returnTypeOverride: returnTypeOverride); } #endregion } diff --git a/ICSharpCode.NRefactory.Tests/CSharp/Resolver/DynamicTests.cs b/ICSharpCode.NRefactory.Tests/CSharp/Resolver/DynamicTests.cs index d78982c100..11727fb4dd 100644 --- a/ICSharpCode.NRefactory.Tests/CSharp/Resolver/DynamicTests.cs +++ b/ICSharpCode.NRefactory.Tests/CSharp/Resolver/DynamicTests.cs @@ -118,15 +118,13 @@ class TestClass { var x = $this.SomeMethod(obj)$; } }"; - var rr = Resolve(program); - Assert.That(rr.IsError, Is.False); - Assert.That(rr.Type.Kind, Is.EqualTo(TypeKind.Dynamic)); - var irr = rr.Input as CSharpInvocationResolveResult; - Assert.That(irr, Is.Not.Null); - Assert.That(irr.Member.Name, Is.EqualTo("SomeMethod")); - Assert.That(((IParameterizedMember)irr.Member).Parameters.Count, Is.EqualTo(1)); - Assert.That(irr.Arguments.Count, Is.EqualTo(1)); - var cr = irr.Arguments[0] as ConversionResolveResult; + var rr = Resolve(program); + Assert.That(rr, Is.Not.Null); + Assert.That(rr.Member.Name, Is.EqualTo("SomeMethod")); + Assert.That(rr.Type.Kind == TypeKind.Dynamic); + Assert.That(((IParameterizedMember)rr.Member).Parameters.Count, Is.EqualTo(1)); + Assert.That(rr.Arguments.Count, Is.EqualTo(1)); + var cr = rr.Arguments[0] as ConversionResolveResult; Assert.That(cr, Is.Not.Null); Assert.That(cr.Conversion.IsImplicit, Is.True); Assert.That(cr.Conversion.IsDynamicConversion, Is.True); @@ -316,9 +314,8 @@ class TestClass { var x = $this.SomeMethod(obj)$; } }"; - var rr = Resolve(program); - var irr = rr.Input; - Assert.That(irr.IsError, Is.True); + var rr = Resolve(program); + Assert.That(rr.IsError, Is.True); } [Test] diff --git a/ICSharpCode.NRefactory/Semantics/InvocationResolveResult.cs b/ICSharpCode.NRefactory/Semantics/InvocationResolveResult.cs index d274e4d3b5..0f807940c1 100644 --- a/ICSharpCode.NRefactory/Semantics/InvocationResolveResult.cs +++ b/ICSharpCode.NRefactory/Semantics/InvocationResolveResult.cs @@ -45,8 +45,9 @@ namespace ICSharpCode.NRefactory.Semantics public InvocationResolveResult(ResolveResult targetResult, IParameterizedMember member, IList arguments = null, - IList initializerStatements = null) - : base(targetResult, member) + IList initializerStatements = null, + IType returnTypeOverride = null) + : base(targetResult, member, returnTypeOverride) { this.Arguments = arguments ?? EmptyList.Instance; this.InitializerStatements = initializerStatements ?? EmptyList.Instance; diff --git a/ICSharpCode.NRefactory/Semantics/MemberResolveResult.cs b/ICSharpCode.NRefactory/Semantics/MemberResolveResult.cs index 3a2430856f..3501e7afe9 100644 --- a/ICSharpCode.NRefactory/Semantics/MemberResolveResult.cs +++ b/ICSharpCode.NRefactory/Semantics/MemberResolveResult.cs @@ -38,8 +38,8 @@ namespace ICSharpCode.NRefactory.Semantics readonly ResolveResult targetResult; readonly bool isVirtualCall; - public MemberResolveResult(ResolveResult targetResult, IMember member) - : base(member.EntityType == EntityType.Constructor ? member.DeclaringType : member.ReturnType) + public MemberResolveResult(ResolveResult targetResult, IMember member, IType returnTypeOverride = null) + : base(returnTypeOverride ?? (member.EntityType == EntityType.Constructor ? member.DeclaringType : member.ReturnType)) { this.targetResult = targetResult; this.member = member; @@ -54,8 +54,8 @@ namespace ICSharpCode.NRefactory.Semantics } } - public MemberResolveResult(ResolveResult targetResult, IMember member, bool isVirtualCall) - : base(member.EntityType == EntityType.Constructor ? member.DeclaringType : member.ReturnType) + public MemberResolveResult(ResolveResult targetResult, IMember member, bool isVirtualCall, IType returnTypeOverride = null) + : base(returnTypeOverride ?? (member.EntityType == EntityType.Constructor ? member.DeclaringType : member.ReturnType)) { this.targetResult = targetResult; this.member = member;