From d6b4420940dca8da89a4029689ab165a2e9796d2 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sun, 29 Jul 2012 10:28:36 +0200 Subject: [PATCH] Introduced NamedArgumentResolveResult. --- .../Resolver/CSharpAstResolver.cs | 2 +- .../Resolver/CSharpInvocationResolveResult.cs | 11 +- .../Resolver/CSharpResolver.cs | 2 +- .../Resolver/OverloadResolution.cs | 55 +++++-- .../Resolver/ResolveVisitor.cs | 135 +++++++++++------- .../CSharp/Resolver/InvocationTests.cs | 70 +++++++++ .../ICSharpCode.NRefactory.csproj | 1 + .../Semantics/NamedArgumentResolveResult.cs | 81 +++++++++++ 8 files changed, 285 insertions(+), 72 deletions(-) create mode 100644 ICSharpCode.NRefactory/Semantics/NamedArgumentResolveResult.cs diff --git a/ICSharpCode.NRefactory.CSharp/Resolver/CSharpAstResolver.cs b/ICSharpCode.NRefactory.CSharp/Resolver/CSharpAstResolver.cs index 234e17f261..858abf7da2 100644 --- a/ICSharpCode.NRefactory.CSharp/Resolver/CSharpAstResolver.cs +++ b/ICSharpCode.NRefactory.CSharp/Resolver/CSharpAstResolver.cs @@ -275,7 +275,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver } return true; } - return (node.NodeType == NodeType.Whitespace || node is ArraySpecifier || node is NamedArgumentExpression); + return (node.NodeType == NodeType.Whitespace || node is ArraySpecifier); } } } diff --git a/ICSharpCode.NRefactory.CSharp/Resolver/CSharpInvocationResolveResult.cs b/ICSharpCode.NRefactory.CSharp/Resolver/CSharpInvocationResolveResult.cs index 401365a5c9..e1e34a2606 100644 --- a/ICSharpCode.NRefactory.CSharp/Resolver/CSharpInvocationResolveResult.cs +++ b/ICSharpCode.NRefactory.CSharp/Resolver/CSharpInvocationResolveResult.cs @@ -96,10 +96,15 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver mappedTo = IsExpandedForm ? Math.Min(i, results.Length - 1) : i; if (mappedTo >= 0 && mappedTo < results.Length) { - if (IsExpandedForm && mappedTo == results.Length - 1) + if (IsExpandedForm && mappedTo == results.Length - 1) { paramsArguments.Add(Arguments[i]); - else - results[mappedTo] = Arguments[i]; + } else { + var narr = Arguments[i] as NamedArgumentResolveResult; + if (narr != null) + results[mappedTo] = narr.Argument; + else + results[mappedTo] = Arguments[i]; + } } } if (IsExpandedForm) diff --git a/ICSharpCode.NRefactory.CSharp/Resolver/CSharpResolver.cs b/ICSharpCode.NRefactory.CSharp/Resolver/CSharpResolver.cs index 20d34852fb..bd429df0d4 100644 --- a/ICSharpCode.NRefactory.CSharp/Resolver/CSharpResolver.cs +++ b/ICSharpCode.NRefactory.CSharp/Resolver/CSharpResolver.cs @@ -1954,7 +1954,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver or.AddCandidate(invokeMethod); return new CSharpInvocationResolveResult( target, invokeMethod, //invokeMethod.ReturnType.Resolve(context), - or.GetArgumentsWithConversions(), or.BestCandidateErrors, + or.GetArgumentsWithConversionsAndNames(), or.BestCandidateErrors, isExpandedForm: or.BestCandidateIsExpandedForm, isDelegateInvocation: true, argumentToParameterMap: or.GetArgumentToParameterMap()); diff --git a/ICSharpCode.NRefactory.CSharp/Resolver/OverloadResolution.cs b/ICSharpCode.NRefactory.CSharp/Resolver/OverloadResolution.cs index d7f8da21a9..ba407c76fa 100644 --- a/ICSharpCode.NRefactory.CSharp/Resolver/OverloadResolution.cs +++ b/ICSharpCode.NRefactory.CSharp/Resolver/OverloadResolution.cs @@ -781,15 +781,35 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver return null; } + /// + /// Returns the arguments for the method call in the order they were provided (not in the order of the parameters). + /// Arguments are wrapped in a if an implicit conversion is being applied + /// to them when calling the method. + /// public IList GetArgumentsWithConversions() { if (bestCandidate == null) return arguments; else - return GetArgumentsWithConversions(null); + return GetArgumentsWithConversions(null, null); + } + + /// + /// Returns the arguments for the method call in the order they were provided (not in the order of the parameters). + /// Arguments are wrapped in a if an implicit conversion is being applied + /// to them when calling the method. + /// For arguments where an explicit argument name was provided, the argument will + /// be wrapped in a . + /// + public IList GetArgumentsWithConversionsAndNames() + { + if (bestCandidate == null) + return arguments; + else + return GetArgumentsWithConversions(null, GetBestCandidateWithSubstitutedTypeArguments()); } - IList GetArgumentsWithConversions(ResolveResult targetResolveResult) + IList GetArgumentsWithConversions(ResolveResult targetResolveResult, IParameterizedMember bestCandidateForNamedArguments) { var conversions = this.ArgumentConversions; ResolveResult[] args = new ResolveResult[arguments.Length]; @@ -797,22 +817,27 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver var argument = arguments[i]; if (this.IsExtensionMethodInvocation && i == 0 && targetResolveResult != null) argument = targetResolveResult; - if (conversions[i] == Conversion.IdentityConversion) { - args[i] = argument; - } else { - int parameterIndex = bestCandidate.ArgumentToParameterMap[i]; - IType parameterType; - if (parameterIndex >= 0) { - parameterType = bestCandidate.ParameterTypes[parameterIndex]; - } else { - parameterType = SpecialType.UnknownType; + int parameterIndex = bestCandidate.ArgumentToParameterMap[i]; + if (parameterIndex >= 0 && conversions[i] != Conversion.IdentityConversion) { + // Wrap argument in ConversionResolveResult + IType parameterType = bestCandidate.ParameterTypes[parameterIndex]; + if (parameterType.Kind != TypeKind.Unknown) { + if (arguments[i].IsCompileTimeConstant && conversions[i] != Conversion.None) { + argument = new CSharpResolver(compilation).WithCheckForOverflow(CheckForOverflow).ResolveCast(parameterType, argument); + } else { + argument = new ConversionResolveResult(parameterType, argument, conversions[i], CheckForOverflow); + } } - if (arguments[i].IsCompileTimeConstant && conversions[i] != Conversion.None) { - args[i] = new CSharpResolver(compilation).WithCheckForOverflow(CheckForOverflow).ResolveCast(parameterType, argument); + } + if (bestCandidateForNamedArguments != null && argumentNames[i] != null) { + // Wrap argument in NamedArgumentResolveResult + if (parameterIndex >= 0) { + argument = new NamedArgumentResolveResult(bestCandidateForNamedArguments.Parameters[parameterIndex], argument, bestCandidateForNamedArguments); } else { - args[i] = new ConversionResolveResult(parameterType, argument, conversions[i], CheckForOverflow); + argument = new NamedArgumentResolveResult(argumentNames[i], argument); } } + args[i] = argument; } return args; } @@ -857,7 +882,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver return new CSharpInvocationResolveResult( this.IsExtensionMethodInvocation ? new TypeResolveResult(member.DeclaringType) : targetResolveResult, member, - GetArgumentsWithConversions(targetResolveResult), + GetArgumentsWithConversions(targetResolveResult, member), this.BestCandidateErrors, this.IsExtensionMethodInvocation, this.BestCandidateIsExpandedForm, diff --git a/ICSharpCode.NRefactory.CSharp/Resolver/ResolveVisitor.cs b/ICSharpCode.NRefactory.CSharp/Resolver/ResolveVisitor.cs index 8f428e1494..ee1aa5b64f 100644 --- a/ICSharpCode.NRefactory.CSharp/Resolver/ResolveVisitor.cs +++ b/ICSharpCode.NRefactory.CSharp/Resolver/ResolveVisitor.cs @@ -27,7 +27,6 @@ using ICSharpCode.NRefactory.CSharp.TypeSystem; using ICSharpCode.NRefactory.Semantics; using ICSharpCode.NRefactory.TypeSystem; using ICSharpCode.NRefactory.TypeSystem.Implementation; -using ICSharpCode.NRefactory.Utils; namespace ICSharpCode.NRefactory.CSharp.Resolver { @@ -393,10 +392,21 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver } } + void MarkUnknownNamedArguments(IEnumerable arguments) + { + foreach (var nae in arguments.OfType()) { + StoreCurrentState(nae); + StoreResult(nae, new NamedArgumentResolveResult(nae.Name, resolveResultCache[nae.Expression])); + } + } + void ProcessConversionsInInvocation(Expression target, IEnumerable arguments, CSharpInvocationResolveResult invocation) { - if (invocation == null) + if (invocation == null) { + // we still need to handle the named arguments if invocation==null + MarkUnknownNamedArguments(arguments); return; + } int i = 0; if (invocation.IsExtensionMethodInvocation) { Debug.Assert(arguments.Count() + 1 == invocation.Arguments.Count); @@ -406,11 +416,17 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver Debug.Assert(arguments.Count() == invocation.Arguments.Count); } foreach (Expression arg in arguments) { + ResolveResult argRR = invocation.Arguments[i++]; NamedArgumentExpression nae = arg as NamedArgumentExpression; - if (nae != null) - ProcessConversionResult(nae.Expression, invocation.Arguments[i++] as ConversionResolveResult); - else - ProcessConversionResult(arg, invocation.Arguments[i++] as ConversionResolveResult); + NamedArgumentResolveResult nrr = argRR as NamedArgumentResolveResult; + Debug.Assert((nae == null) == (nrr == null)); + if (nae != null && nrr != null) { + StoreCurrentState(nae); + StoreResult(nae, nrr); + ProcessConversionResult(nae.Expression, nrr.Argument as ConversionResolveResult); + } else { + ProcessConversionResult(arg, argRR as ConversionResolveResult); + } } } #endregion @@ -1398,7 +1414,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver ResolveResult IAstVisitor.VisitIndexerExpression(IndexerExpression indexerExpression) { - if (resolverEnabled) { + if (resolverEnabled || NeedsResolvingDueToNamedArguments(indexerExpression)) { Expression target = indexerExpression.Target; ResolveResult targetResult = Resolve(target); string[] argumentNames; @@ -1406,6 +1422,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver ResolveResult rr = resolver.ResolveIndexer(targetResult, arguments, argumentNames); ArrayAccessResolveResult aarr = rr as ArrayAccessResolveResult; if (aarr != null) { + MarkUnknownNamedArguments(indexerExpression.Arguments); ProcessConversionResults(indexerExpression.Arguments, aarr.Indexes); } else { ProcessConversionsInInvocation(target, indexerExpression.Arguments, rr as CSharpInvocationResolveResult); @@ -1437,8 +1454,12 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver // by calling GetArguments(). // This method gets called only when scanning, or when the named argument is used // in an invalid context. - Scan(namedArgumentExpression.Expression); - return null; + if (resolverEnabled) { + return new NamedArgumentResolveResult(namedArgumentExpression.Name, Resolve(namedArgumentExpression.Expression)); + } else { + Scan(namedArgumentExpression.Expression); + return null; + } } // NamedExpression is "identifier = Expression" in object initializers and attributes @@ -1477,48 +1498,44 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver ResolveResult IAstVisitor.VisitObjectCreateExpression(ObjectCreateExpression objectCreateExpression) { - if (resolverEnabled || !objectCreateExpression.Initializer.IsNull) { - var typeResolveResult = Resolve(objectCreateExpression.Type); - if (typeResolveResult.IsError) { - ScanChildren (objectCreateExpression); - return typeResolveResult; - } - IType type = typeResolveResult.Type; - - List initializerStatements = null; - var initializer = objectCreateExpression.Initializer; - if (!initializer.IsNull) { - initializerStatements = new List(); - HandleObjectInitializer(new InitializedObjectResolveResult(type), initializer, initializerStatements); - } - - string[] argumentNames; - ResolveResult[] arguments = GetArguments(objectCreateExpression.Arguments, out argumentNames); - - ResolveResult rr = resolver.ResolveObjectCreation(type, arguments, argumentNames, false, initializerStatements); - if (arguments.Length == 1 && rr.Type.Kind == TypeKind.Delegate) { - // Apply conversion to argument if it directly wraps the argument - // (but not when creating a delegate from a delegate, as then there would be a MGRR for .Invoke in between) - // This is necessary for lambda type inference. - var crr = rr as ConversionResolveResult; - if (crr != null && crr.Input == arguments[0]) { - ProcessConversionResult(objectCreateExpression.Arguments.Single(), crr); - - // wrap the result so that the delegate creation is not handled as a reference - // to the target method - otherwise FindReferencedEntities would produce two results for - // the same delegate creation. - return WrapResult(rr); - } else { - return rr; - } + var typeResolveResult = Resolve(objectCreateExpression.Type); + if (typeResolveResult.IsError) { + ScanChildren (objectCreateExpression); + return typeResolveResult; + } + IType type = typeResolveResult.Type; + + List initializerStatements = null; + var initializer = objectCreateExpression.Initializer; + if (!initializer.IsNull) { + initializerStatements = new List(); + HandleObjectInitializer(new InitializedObjectResolveResult(type), initializer, initializerStatements); + } + + string[] argumentNames; + ResolveResult[] arguments = GetArguments(objectCreateExpression.Arguments, out argumentNames); + + ResolveResult rr = resolver.ResolveObjectCreation(type, arguments, argumentNames, false, initializerStatements); + if (arguments.Length == 1 && rr.Type.Kind == TypeKind.Delegate) { + MarkUnknownNamedArguments(objectCreateExpression.Arguments); + // Apply conversion to argument if it directly wraps the argument + // (but not when creating a delegate from a delegate, as then there would be a MGRR for .Invoke in between) + // This is necessary for lambda type inference. + var crr = rr as ConversionResolveResult; + if (crr != null && crr.Input == arguments[0]) { + ProcessConversionResult(objectCreateExpression.Arguments.Single(), crr); + + // wrap the result so that the delegate creation is not handled as a reference + // to the target method - otherwise FindReferencedEntities would produce two results for + // the same delegate creation. + return WrapResult(rr); } else { - // process conversions in all other cases - ProcessConversionsInInvocation(null, objectCreateExpression.Arguments, rr as CSharpInvocationResolveResult); return rr; } } else { - ScanChildren(objectCreateExpression); - return null; + // process conversions in all other cases + ProcessConversionsInInvocation(null, objectCreateExpression.Arguments, rr as CSharpInvocationResolveResult); + return rr; } } @@ -1701,6 +1718,15 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver return result; } + /// + /// Gets and resolves the arguments; unpacking any NamedArgumentExpressions. + /// + /// + /// Callers of GetArguments must also call either ProcessConversionsInInvocation or MarkUnknownNamedArguments + /// to ensure the named arguments get resolved. + /// Also, as named arguments get resolved by the parent node, the parent node must not scan + /// into the argument list without being resolved - see NeedsResolvingDueToNamedArguments(). + /// ResolveResult[] GetArguments(IEnumerable argumentExpressions, out string[] argumentNames) { argumentNames = null; @@ -1722,6 +1748,15 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver return arguments; } + bool NeedsResolvingDueToNamedArguments(Expression nodeWithArguments) + { + for (AstNode child = nodeWithArguments.FirstChild; child != null; child = child.NextSibling) { + if (child is NamedArgumentExpression) + return true; + } + return false; + } + static NameLookupMode GetNameLookupMode(Expression expr) { InvocationExpression ie = expr.Parent as InvocationExpression; @@ -1840,7 +1875,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver } } else { // Regular code path - if (resolverEnabled) { + if (resolverEnabled || NeedsResolvingDueToNamedArguments(invocationExpression)) { ResolveResult target = Resolve(invocationExpression.Target); return ResolveInvocationOnGivenTarget(target, invocationExpression); } else { @@ -3779,10 +3814,6 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver #region Constructor Initializer ResolveResult IAstVisitor.VisitConstructorInitializer(ConstructorInitializer constructorInitializer) { - if (!resolverEnabled) { - ScanChildren(constructorInitializer); - return null; - } ResolveResult target; if (constructorInitializer.ConstructorInitializerType == ConstructorInitializerType.Base) { target = resolver.ResolveBaseReference(); diff --git a/ICSharpCode.NRefactory.Tests/CSharp/Resolver/InvocationTests.cs b/ICSharpCode.NRefactory.Tests/CSharp/Resolver/InvocationTests.cs index 6dd3b80762..6f1946dd3a 100644 --- a/ICSharpCode.NRefactory.Tests/CSharp/Resolver/InvocationTests.cs +++ b/ICSharpCode.NRefactory.Tests/CSharp/Resolver/InvocationTests.cs @@ -539,5 +539,75 @@ class D : B { Assert.IsFalse(rr.IsError); Assert.IsFalse(rr.IsVirtualCall); } + + [Test] + public void NamedArgument() + { + string program = @" +class Test { + public void F(int x) {} + public void Test() { + F($x: 0$); + } +}"; + var narr = Resolve(program); + Assert.IsInstanceOf(narr.Argument); + Assert.AreEqual("x", narr.ParameterName); + Assert.AreEqual("Test.F", narr.Member.FullName); + Assert.AreSame(narr.Member.Parameters.Single(), narr.Parameter); + } + + [Test] + public void NamedArgumentInInvocation() + { + string program = @" +class Test { + public void F(int x) {} + public void Test() { + $F(x: 0)$; + } +}"; + var rr = Resolve(program); + Assert.IsInstanceOf(rr.Arguments.Single()); + var narr = (NamedArgumentResolveResult)rr.Arguments.Single(); + Assert.IsInstanceOf(narr.Argument); + Assert.AreEqual("x", narr.ParameterName); + Assert.AreEqual("Test.F", narr.Member.FullName); + Assert.AreSame(narr.Member.Parameters.Single(), narr.Parameter); + + // but GetArgumentsForCall() should directly return the constant: + Assert.IsInstanceOf(rr.GetArgumentsForCall().Single()); + } + + [Test] + public void UnknownNamedArgument() + { + string program = @" +class Test { + public void F(int x) {} + public void Test() { + F($y: 0$); + } +}"; + var narr = Resolve(program); + Assert.IsInstanceOf(narr.Argument); + Assert.AreEqual("y", narr.ParameterName); + Assert.IsNull(narr.Parameter); + } + + [Test] + public void NamedArgumentInMissingMethod() + { + string program = @" +class Test { + public void Test() { + Missing($x: 0$); + } +}"; + var narr = Resolve(program); + Assert.IsInstanceOf(narr.Argument); + Assert.AreEqual("x", narr.ParameterName); + Assert.IsNull(narr.Parameter); + } } } diff --git a/ICSharpCode.NRefactory/ICSharpCode.NRefactory.csproj b/ICSharpCode.NRefactory/ICSharpCode.NRefactory.csproj index 6df2122ae5..7213b78cdb 100644 --- a/ICSharpCode.NRefactory/ICSharpCode.NRefactory.csproj +++ b/ICSharpCode.NRefactory/ICSharpCode.NRefactory.csproj @@ -120,6 +120,7 @@ + diff --git a/ICSharpCode.NRefactory/Semantics/NamedArgumentResolveResult.cs b/ICSharpCode.NRefactory/Semantics/NamedArgumentResolveResult.cs new file mode 100644 index 0000000000..7a69afebf8 --- /dev/null +++ b/ICSharpCode.NRefactory/Semantics/NamedArgumentResolveResult.cs @@ -0,0 +1,81 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; +using ICSharpCode.NRefactory.TypeSystem; + +namespace ICSharpCode.NRefactory.Semantics +{ + /// + /// Represents a named argument. + /// + public class NamedArgumentResolveResult : ResolveResult + { + /// + /// Gets the member to which the parameter belongs. + /// This field can be null. + /// + public readonly IParameterizedMember Member; + + /// + /// Gets the parameter. + /// This field can be null. + /// + public readonly IParameter Parameter; + + /// + /// Gets the parameter name. + /// + public readonly string ParameterName; + + /// + /// Gets the argument passed to the parameter. + /// + public readonly ResolveResult Argument; + + public NamedArgumentResolveResult(IParameter parameter, ResolveResult argument, IParameterizedMember member = null) + : base(argument.Type) + { + if (parameter == null) + throw new ArgumentNullException("parameter"); + if (argument == null) + throw new ArgumentNullException("argument"); + this.Member = member; + this.Parameter = parameter; + this.ParameterName = parameter.Name; + this.Argument = argument; + } + + public NamedArgumentResolveResult(string parameterName, ResolveResult argument) + : base(argument.Type) + { + if (parameterName == null) + throw new ArgumentNullException("parameterName"); + if (argument == null) + throw new ArgumentNullException("argument"); + this.ParameterName = parameterName; + this.Argument = argument; + } + + public override IEnumerable GetChildResults() + { + return new [] { Argument }; + } + } +}