diff --git a/build/Rulesets/Roslyn_BuildRules.ruleset b/build/Rulesets/Roslyn_BuildRules.ruleset index cdd2d050c7ccd..b3b1231c550d2 100644 --- a/build/Rulesets/Roslyn_BuildRules.ruleset +++ b/build/Rulesets/Roslyn_BuildRules.ruleset @@ -89,7 +89,7 @@ - + diff --git a/src/Compilers/CSharp/Portable/BoundTree/Expression.cs b/src/Compilers/CSharp/Portable/BoundTree/Expression.cs index b41e6fa9d4fce..53c2964ee6228 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/Expression.cs +++ b/src/Compilers/CSharp/Portable/BoundTree/Expression.cs @@ -1,6 +1,8 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Concurrent; +using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Runtime.CompilerServices; @@ -46,26 +48,9 @@ internal partial class BoundCall : IInvocationExpression (object)this.Method.ReplacedBy == null && !this.ReceiverOpt.SuppressVirtualCalls; - ImmutableArray IInvocationExpression.ArgumentsInSourceOrder - { - get - { - ArrayBuilder sourceOrderArguments = ArrayBuilder.GetInstance(this.Arguments.Length); - for (int argumentIndex = 0; argumentIndex < this.Arguments.Length; argumentIndex++) - { - IArgument argument = DeriveArgument(this.ArgsToParamsOpt.IsDefault ? argumentIndex : this.ArgsToParamsOpt[argumentIndex], argumentIndex, this.Arguments, this.ArgumentNamesOpt, this.ArgumentRefKindsOpt, this.Method.Parameters, this.Syntax); - sourceOrderArguments.Add(argument); - if (argument.ArgumentKind == ArgumentKind.ParamArray) - { - break; - } - } - - return sourceOrderArguments.ToImmutableAndFree(); - } - } + ImmutableArray IHasArgumentsExpression.ArgumentsInEvaluationOrder => DeriveArgumentsInEvaluationOrder(this.Arguments, this.ArgumentNamesOpt, this.ArgsToParamsOpt, this.ArgumentRefKindsOpt, this.Method.Parameters, this.Syntax, this.Method); - ImmutableArray IHasArgumentsExpression.ArgumentsInParameterOrder => DeriveArguments(this.Arguments, this.ArgumentNamesOpt, this.ArgsToParamsOpt, this.ArgumentRefKindsOpt, this.Method.Parameters, this.Syntax); + ImmutableArray IHasArgumentsExpression.ArgumentsInParameterOrder => DeriveArgumentsInParameterOrder(this.Arguments, this.ArgumentNamesOpt, this.ArgsToParamsOpt, this.ArgumentRefKindsOpt, this.Method.Parameters, this.Syntax, this.Method); IArgument IHasArgumentsExpression.GetArgumentMatchingParameter(IParameterSymbol parameter) { @@ -84,67 +69,109 @@ public override TResult Accept(OperationVisitor DeriveArguments(ImmutableArray boundArguments, ImmutableArray argumentNames, ImmutableArray argumentsToParameters, ImmutableArray argumentRefKinds, ImmutableArray parameters, SyntaxNode invocationSyntax) + internal static ImmutableArray DeriveArgumentsInParameterOrder(ImmutableArray boundArguments, ImmutableArray argumentNames, ImmutableArray argumentsToParameters, ImmutableArray argumentRefKinds, ImmutableArray parameters, CSharpSyntaxNode invocationSyntax, Symbols.MethodSymbol targetMethod) { - ArrayBuilder arguments = ArrayBuilder.GetInstance(boundArguments.Length); - for (int parameterIndex = 0; parameterIndex < parameters.Length; parameterIndex++) + ImmutableArray argumentsInEvaluationOrder = DeriveArgumentsInEvaluationOrder(boundArguments, argumentNames, argumentsToParameters, argumentRefKinds, parameters, invocationSyntax, targetMethod); + + // If all of the arguments were specified positionally the evaluation order is the same as the parameter order. + if (ArgumentsAreInParameterOrder(argumentsInEvaluationOrder)) { - int argumentIndex = -1; - if (argumentsToParameters.IsDefault) + return argumentsInEvaluationOrder; + } + + return argumentsInEvaluationOrder.Sort( + (x, y) => { - argumentIndex = parameterIndex; - } - else + int x1 = x.Parameter.Ordinal; + int y1 = y.Parameter.Ordinal; + + return x1 == y1 ? 0 : (x1 < y1 ? -1 : 1); + }); + } + + private static bool ArgumentsAreInParameterOrder(ImmutableArray arguments) + { + for (int argumentIndex = 0; argumentIndex < arguments.Length; argumentIndex++) + { + if (arguments[argumentIndex].Parameter.Ordinal != argumentIndex) { - argumentIndex = argumentsToParameters.IndexOf(parameterIndex); + return false; } + } - if ((uint)argumentIndex >= (uint)boundArguments.Length) - { - // No argument has been supplied for the parameter at `parameterIndex`: - // 1. `argumentIndex == -1' when the arguments are specified out of parameter order, and no argument is provided for parameter corresponding to `parameters[parameterIndex]`. - // 2. `argumentIndex >= boundArguments.Length` when the arguments are specified in parameter order, and no argument is provided at `parameterIndex`. + return true; + } - Symbols.ParameterSymbol parameter = parameters[parameterIndex]; - if (parameter.HasExplicitDefaultValue) - { - // The parameter is optional with a default value. - arguments.Add(new Argument(ArgumentKind.DefaultValue, parameter, new Literal(parameter.ExplicitDefaultConstantValue, parameter.Type, invocationSyntax))); - } - else - { - // If the invocation is semantically valid, the parameter will be a params array and an empty array will be provided. - // If the argument is otherwise omitted for a parameter with no default value, the invocation is not valid and a null argument will be provided. - arguments.Add(DeriveArgument(parameterIndex, boundArguments.Length, boundArguments, argumentNames, argumentRefKinds, parameters, invocationSyntax)); - } + internal static ImmutableArray DeriveArgumentsInEvaluationOrder(ImmutableArray boundArguments, ImmutableArray argumentNames, ImmutableArray argumentsToParameters, ImmutableArray argumentRefKinds, ImmutableArray parameters, CSharpSyntaxNode invocationSyntax, Symbols.MethodSymbol targetMethod) + { + DiagnosticBag diagnostics = new DiagnosticBag(); + SyntheticBoundNodeFactory factory = new SyntheticBoundNodeFactory(invocationSyntax, diagnostics); + LocalRewriter rewriter = new LocalRewriter(null, null, 0, null, factory, null, false, diagnostics, inIOperationContext: true); + ImmutableArray temporaries; + var args = rewriter.MakeArguments(invocationSyntax, boundArguments, targetMethod, targetMethod, paramsArrayExpanded, argumentsToParameters, ref argumentRefKinds, out temporaries); + + + + + HashSet matchedParameters = new HashSet(); + ArrayBuilder evaluationOrderArguments = ArrayBuilder.GetInstance(parameters.Length); + for (int argumentIndex = 0; argumentIndex < boundArguments.Length; argumentIndex++) + { + int parameterIndex = argumentsToParameters.IsDefault ? argumentIndex : argumentsToParameters[argumentIndex]; + IArgument argument = DeriveArgument(parameterIndex, argumentIndex, boundArguments, argumentNames, argumentRefKinds, parameters, invocationSyntax); + evaluationOrderArguments.Add(argument); + matchedParameters.Add(parameterIndex); + // If the current argument matches a params parameter and is unnamed, following explicit arguments are treated as part of the params arrray. + if ((uint)parameterIndex < parameters.Length && parameters[parameterIndex].IsParams && (argumentNames.IsDefaultOrEmpty || argumentNames[argumentIndex] == null)) + { + break; } - else + } + + // Include implicit arguments after the explicit arguments. + foreach (Symbols.ParameterSymbol parameter in parameters) + { + if (!matchedParameters.Contains(parameter.Ordinal)) { - arguments.Add(DeriveArgument(parameterIndex, argumentIndex, boundArguments, argumentNames, argumentRefKinds, parameters, invocationSyntax)); + evaluationOrderArguments.Add(DeriveArgument(parameter.Ordinal, -1, boundArguments, argumentNames, argumentRefKinds, parameters, invocationSyntax)); } } - return arguments.ToImmutableAndFree(); + return evaluationOrderArguments.ToImmutableAndFree(); } private static readonly ConditionalWeakTable s_argumentMappings = new ConditionalWeakTable(); + private static readonly ConditionalWeakTable> s_omittedArgumentMappings = new ConditionalWeakTable>(); private static IArgument DeriveArgument(int parameterIndex, int argumentIndex, ImmutableArray boundArguments, ImmutableArray argumentNames, ImmutableArray argumentRefKinds, ImmutableArray parameters, SyntaxNode invocationSyntax) { if ((uint)argumentIndex >= (uint)boundArguments.Length) { - // Check for an omitted argument that becomes an empty params array. - if (parameters.Length > 0) + ConcurrentDictionary omittedArguments = s_omittedArgumentMappings.GetValue(invocationSyntax, syntax => new ConcurrentDictionary()); + + return omittedArguments.GetOrAdd( + parameters[parameterIndex], + (parameter) => { - Symbols.ParameterSymbol lastParameter = parameters[parameters.Length - 1]; - if (lastParameter.IsParams) + // No argument has been supplied for the parameter at `parameterIndex`: + // 1. `argumentIndex == -1' when the arguments are specified out of parameter order, and no argument is provided for the parameter corresponding to `parameters[parameterIndex]`. + // 2. `argumentIndex >= boundArguments.Length` when the arguments are specified in parameter order, and no argument is provided at `parameterIndex`. + + // Check for a parameter with a default value. + if (parameter.HasExplicitDefaultValue) { - return new Argument(ArgumentKind.ParamArray, lastParameter, CreateParamArray(lastParameter, boundArguments, argumentIndex, invocationSyntax)); + return new Argument(parameter, new Literal(parameter.ExplicitDefaultConstantValue, parameter.Type, invocationSyntax)); } - } - // There is no supplied argument and there is no params parameter. Any action is suspect at this point. - return new SimpleArgument(null, new InvalidExpression(invocationSyntax)); + // Check for an omitted argument that becomes an empty params array. + if (parameter.IsParams) + { + return new Argument(parameter, CreateParamArray(parameter, boundArguments, argumentIndex, invocationSyntax)); + } + + // There is no supplied argument and there is no params parameter. Any action is suspect at this point. + return new Argument(parameter, new InvalidExpression(invocationSyntax)); + }); } return s_argumentMappings.GetValue( @@ -160,7 +187,7 @@ private static IArgument DeriveArgument(int parameterIndex, int argumentIndex, I if (refMode != RefKind.None) { - return new Argument(ArgumentKind.Positional, parameter, argument); + return new Argument(parameter, argument); } if (argumentIndex >= parameters.Length - 1 && @@ -171,30 +198,39 @@ private static IArgument DeriveArgument(int parameterIndex, int argumentIndex, I argument.Type.TypeKind != TypeKind.Array || !argument.Type.Equals(parameters[parameters.Length - 1].Type, ignoreCustomModifiersAndArraySizesAndLowerBounds: true))) { - return new Argument(ArgumentKind.ParamArray, parameters[parameters.Length - 1], CreateParamArray(parameters[parameters.Length - 1], boundArguments, argumentIndex, invocationSyntax)); + return new Argument(parameters[parameters.Length - 1], CreateParamArray(parameters[parameters.Length - 1], boundArguments, argumentIndex, invocationSyntax)); } else { - return new SimpleArgument(parameter, argument); + return new Argument(parameter, argument); } } - return new Argument(ArgumentKind.Named, parameter, argument); + return new Argument(parameter, argument); }); } - + private static IOperation CreateParamArray(IParameterSymbol parameter, ImmutableArray boundArguments, int firstArgumentElementIndex, SyntaxNode invocationSyntax) { if (parameter.Type.TypeKind == TypeKind.Array) { IArrayTypeSymbol arrayType = (IArrayTypeSymbol)parameter.Type; - ArrayBuilder builder = ArrayBuilder.GetInstance(boundArguments.Length - firstArgumentElementIndex); + ImmutableArray paramArrayArguments; - for (int index = firstArgumentElementIndex; index < boundArguments.Length; index++) + // If there are no matching arguments, then the argument index is negative. + if (firstArgumentElementIndex >= 0) + { + ArrayBuilder builder = ArrayBuilder.GetInstance(boundArguments.Length - firstArgumentElementIndex); + for (int index = firstArgumentElementIndex; index < boundArguments.Length; index++) + { + builder.Add(boundArguments[index]); + } + paramArrayArguments = builder.ToImmutableAndFree(); + } + else { - builder.Add(boundArguments[index]); + paramArrayArguments = ImmutableArray.Empty; } - var paramArrayArguments = builder.ToImmutableAndFree(); // Use the invocation syntax node if there is no actual syntax available for the argument (because the paramarray is empty.) return new ArrayCreation(arrayType, paramArrayArguments, paramArrayArguments.Length > 0 ? paramArrayArguments[0].Syntax : invocationSyntax); @@ -206,12 +242,7 @@ private static IOperation CreateParamArray(IParameterSymbol parameter, Immutable internal static IArgument ArgumentMatchingParameter(ImmutableArray arguments, ImmutableArray argumentsToParameters, ImmutableArray argumentNames, ImmutableArray argumentRefKinds, ISymbol targetMethod, ImmutableArray parameters, IParameterSymbol parameter, SyntaxNode invocationSyntax) { int argumentIndex = ArgumentIndexMatchingParameter(arguments, argumentsToParameters, targetMethod, parameter); - if (argumentIndex >= 0) - { - return DeriveArgument(parameter.Ordinal, argumentIndex, arguments, argumentNames, argumentRefKinds, parameters, invocationSyntax); - } - - return null; + return DeriveArgument(parameter.Ordinal, argumentIndex, arguments, argumentNames, argumentRefKinds, parameters, invocationSyntax); } private static int ArgumentIndexMatchingParameter(ImmutableArray arguments, ImmutableArray argumentsToParameters, ISymbol targetMethod, IParameterSymbol parameter) @@ -219,10 +250,9 @@ private static int ArgumentIndexMatchingParameter(ImmutableArray parameterIndices = argumentsToParameters; - if (!parameterIndices.IsDefaultOrEmpty) + if (!argumentsToParameters.IsDefaultOrEmpty) { - return parameterIndices.IndexOf(parameterIndex); + return argumentsToParameters.IndexOf(parameterIndex); } return parameterIndex; @@ -231,9 +261,9 @@ private static int ArgumentIndexMatchingParameter(ImmutableArray ConstantValue => default(Optional); - public abstract ArgumentKind ArgumentKind { get; } - void IOperation.Accept(OperationVisitor visitor) { visitor.VisitArgument(this); @@ -271,26 +299,6 @@ TResult IOperation.Accept(OperationVisitor ArgumentKind.Positional; - } - - private sealed class Argument : ArgumentBase - { - public Argument(ArgumentKind kind, IParameterSymbol parameter, IOperation value) - : base(parameter, value) - { - this.ArgumentKind = kind; - } - - public override ArgumentKind ArgumentKind { get; } - } } internal partial class BoundLocal : ILocalReferenceExpression @@ -360,7 +368,10 @@ internal partial class BoundIndexerAccess : IIndexedPropertyReferenceExpression ISymbol IMemberReferenceExpression.Member => this.Indexer; - ImmutableArray IHasArgumentsExpression.ArgumentsInParameterOrder => BoundCall.DeriveArguments(this.Arguments, this.ArgumentNamesOpt, this.ArgsToParamsOpt, this.ArgumentRefKindsOpt, this.Indexer.Parameters, this.Syntax); + // PROTOTYPE: Unconditional use of the Get method here is probably incorrect. + ImmutableArray IHasArgumentsExpression.ArgumentsInEvaluationOrder => BoundCall.DeriveArgumentsInEvaluationOrder(this.Arguments, this.ArgumentNamesOpt, this.ArgsToParamsOpt, this.ArgumentRefKindsOpt, this.Indexer.Parameters, this.Syntax, this.Indexer.GetMethod); + + ImmutableArray IHasArgumentsExpression.ArgumentsInParameterOrder => BoundCall.DeriveArgumentsInParameterOrder(this.Arguments, this.ArgumentNamesOpt, this.ArgsToParamsOpt, this.ArgumentRefKindsOpt, this.Indexer.Parameters, this.Syntax, this.Indexer.GetMethod); IArgument IHasArgumentsExpression.GetArgumentMatchingParameter(IParameterSymbol parameter) { @@ -500,7 +511,7 @@ public override TResult Accept(OperationVisitor OperationKind.None; @@ -522,7 +533,9 @@ internal partial class BoundObjectCreationExpression : IObjectCreationExpression IMethodSymbol IObjectCreationExpression.Constructor => this.Constructor; - ImmutableArray IHasArgumentsExpression.ArgumentsInParameterOrder => BoundCall.DeriveArguments(this.Arguments, this.ArgumentNamesOpt, this.ArgsToParamsOpt, this.ArgumentRefKindsOpt, this.Constructor.Parameters, this.Syntax); + ImmutableArray IHasArgumentsExpression.ArgumentsInEvaluationOrder => BoundCall.DeriveArgumentsInEvaluationOrder(this.Arguments, this.ArgumentNamesOpt, this.ArgsToParamsOpt, this.ArgumentRefKindsOpt, this.Constructor.Parameters, this.Syntax, this.Constructor); + + ImmutableArray IHasArgumentsExpression.ArgumentsInParameterOrder => BoundCall.DeriveArgumentsInParameterOrder(this.Arguments, this.ArgumentNamesOpt, this.ArgsToParamsOpt, this.ArgumentRefKindsOpt, this.Constructor.Parameters, this.Syntax, this.Constructor); IArgument IHasArgumentsExpression.GetArgumentMatchingParameter(IParameterSymbol parameter) { @@ -1277,7 +1290,7 @@ public override TResult Accept(OperationVisitor InstanceReferenceKind.Implicit; - + protected override OperationKind ExpressionKind => OperationKind.InstanceReferenceExpression; public override void Accept(OperationVisitor visitor) @@ -1906,7 +1919,7 @@ public override TResult Accept(OperationVisitor OperationKind.None; @@ -2798,5 +2811,4 @@ public override TResult Accept(OperationVisitor OperationKind.None; } - } diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.cs index 714e47c39e9a7..79488aa5e2841 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.cs @@ -24,8 +24,9 @@ internal sealed partial class LocalRewriter : BoundTreeRewriterWithStackGuard private bool _sawAwait; private bool _sawAwaitInExceptionHandler; private readonly DiagnosticBag _diagnostics; + private readonly bool _inIOperationContext; - private LocalRewriter( + internal LocalRewriter( CSharpCompilation compilation, MethodSymbol containingMethod, int containingMethodOrdinal, @@ -33,16 +34,18 @@ private LocalRewriter( SyntheticBoundNodeFactory factory, SynthesizedSubmissionFields previousSubmissionFields, bool allowOmissionOfConditionalCalls, - DiagnosticBag diagnostics) + DiagnosticBag diagnostics, + bool inIOperationContext = false) { _compilation = compilation; _factory = factory; _factory.CurrentMethod = containingMethod; - Debug.Assert(factory.CurrentType == (containingType ?? containingMethod.ContainingType)); + Debug.Assert(factory.CurrentType == (containingType ?? containingMethod?.ContainingType)); _dynamicFactory = new LoweredDynamicOperationFactory(factory, containingMethodOrdinal); _previousSubmissionFields = previousSubmissionFields; _allowOmissionOfConditionalCalls = allowOmissionOfConditionalCalls; _diagnostics = diagnostics; + _inIOperationContext = inIOperationContext; } /// diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Call.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Call.cs index 697675bcf4cf9..2694be9fbe2e0 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Call.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Call.cs @@ -344,7 +344,7 @@ private static bool IsSafeForReordering(BoundExpression expression, RefKind kind /// itself. is needed for indexers since getter and setter /// may have distinct optional parameter values. /// - private ImmutableArray MakeArguments( + internal ImmutableArray MakeArguments( CSharpSyntaxNode syntax, ImmutableArray rewrittenArguments, Symbol methodOrIndexer, @@ -621,7 +621,7 @@ private BoundExpression BuildParamsArray( // if it's available. However, we also disable the optimization if we're in an expression lambda, the // point of which is just to represent the semantics of an operation, and we don't know that all consumers // of expression lambdas will appropriately understand Array.Empty(). - if (arrayArgs.Length == 0 && !_inExpressionLambda) + if (arrayArgs.Length == 0 && !_inExpressionLambda && !_inIOperationContext) { ArrayTypeSymbol ats = paramArrayType as ArrayTypeSymbol; if (ats != null) // could be null if there's a semantic error, e.g. the params parameter type isn't an array @@ -797,8 +797,8 @@ private void InsertMissingOptionalArguments(CSharpSyntaxNode syntax, if (arguments[p] == null) { ParameterSymbol parameter = parameters[p]; - Debug.Assert(parameter.IsOptional); - arguments[p] = GetDefaultParameterValue(syntax, parameter, enableCallerInfo); + Debug.Assert(parameter.IsOptional || _inIOperationContext); + arguments[p] = parameter.IsOptional ? GetDefaultParameterValue(syntax, parameter, enableCallerInfo) : new BoundBadExpression(syntax, LookupResultKind.Empty, ImmutableArray.Empty, ImmutableArray.Empty, parameter.Type); Debug.Assert(arguments[p].Type == parameter.Type); } } diff --git a/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs b/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs index 64e04ddade272..37a4382b24880 100644 --- a/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs +++ b/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs @@ -165,6 +165,17 @@ public SyntheticBoundNodeFactory(MethodSymbol topLevelMethodOpt, NamedTypeSymbol this.Diagnostics = diagnostics; } + /// + /// Constructor used in circumstances where type/method context is not available, producing a factory of limited capability. + /// + /// The syntax node to which generated code should be attributed + /// A bag where any diagnostics should be output + internal SyntheticBoundNodeFactory(CSharpSyntaxNode node, DiagnosticBag diagnostics) + { + this.Syntax = node; + this.Diagnostics = diagnostics; + } + [Conditional("DEBUG")] private void CheckCurrentType() { diff --git a/src/Compilers/CSharp/Test/Semantic/CSharpCompilerSemanticTest.csproj b/src/Compilers/CSharp/Test/Semantic/CSharpCompilerSemanticTest.csproj index d671254b2e8ac..7ef8418012a86 100644 --- a/src/Compilers/CSharp/Test/Semantic/CSharpCompilerSemanticTest.csproj +++ b/src/Compilers/CSharp/Test/Semantic/CSharpCompilerSemanticTest.csproj @@ -68,6 +68,7 @@ + diff --git a/src/Compilers/CSharp/Test/Semantic/Diagnostics/IInvocationExpressionTests.cs b/src/Compilers/CSharp/Test/Semantic/Diagnostics/IInvocationExpressionTests.cs new file mode 100644 index 0000000000000..714519f801b27 --- /dev/null +++ b/src/Compilers/CSharp/Test/Semantic/Diagnostics/IInvocationExpressionTests.cs @@ -0,0 +1,500 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Immutable; +using System.Linq; +using Microsoft.CodeAnalysis.CSharp.Symbols; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.CSharp.Test.Utilities; +using Microsoft.CodeAnalysis.Semantics; +using Xunit; + +namespace Microsoft.CodeAnalysis.CSharp.UnitTests.Semantics +{ + public class IInvocationExpressionTests : CSharpTestBase + { + [Fact] + public void SimpleInvocations() + { + const string source = @" +class C +{ + void M1() + { + C local = this; + M2(1, 2); + local.M2(b: 2, a: 1); + int x = 1; + M3(x); + } + + void M2(int a, int b) { } + static double M3(double d) { return d; } +}"; + + SemanticModel model; + InvocationExpressionSyntax[] nodes = GetInvocations(source, 3, out model); + + // M2(1, 2) + + IInvocationExpression invocation = CheckInvocation(nodes[0], model, "M2(1, 2)", "M2", 2, SpecialType.System_Void); + CheckInstanceReference(invocation.Instance, InstanceReferenceKind.Implicit, "C"); + + // 1 + + IArgument argument = GetArgument(invocation, 0, 0); + CheckConstantArgument(invocation, argument, "a", 1); + + // 2 + + argument = GetArgument(invocation, 1, 1); + CheckConstantArgument(invocation, argument, "b", 2); + + // local.M2(b: 2, a: 1) + + invocation = CheckInvocation(nodes[1], model, "local.M2(b: 2, a: 1)", "M2", 2, SpecialType.System_Void); + CheckLocalReference(invocation.Instance, "local", "C"); + + // a: 1 + + argument = GetArgument(invocation, 0, 1); + CheckConstantArgument(invocation, argument, "a", 1); + + // b: 2 + + argument = GetArgument(invocation, 1, 0); + CheckConstantArgument(invocation, argument, "b", 2); + + // M3(x) + + invocation = CheckInvocation(nodes[2], model, "M3(x)", "M3", 1, SpecialType.System_Double); + Assert.Null(invocation.Instance); + + // x + + argument = GetArgument(invocation, 0, 0); + IOperation argumentValue = CheckArgument(invocation, argument, "d"); + + Assert.Equal(argumentValue.Kind, OperationKind.ConversionExpression); + Assert.Equal(argumentValue.Type.SpecialType, SpecialType.System_Double); + CheckLocalReference(((IConversionExpression)argumentValue).Operand, "x", "Int32"); + } + + [Fact] + public void ParamArrayInvocations() + { + const string source = @" +class C +{ + void M1() + { + M2(1, 2, 3); + M2(1); + M2(); + M2(1, new int[] { 2, 3 }); + M2(c: new int[] { 2, 3 }, a: 1); + M2(c: 2, a: 1); + } + + static void M2(int a, params int[] c) { } +}"; + + SemanticModel model; + InvocationExpressionSyntax[] nodes = GetInvocations(source, 6, out model); + + // M2(1, 2, 3) + + IInvocationExpression invocation = CheckInvocation(nodes[0], model, "M2(1, 2, 3)", "M2", 2, SpecialType.System_Void); + Assert.Null(invocation.Instance); + + // 1 + + IArgument argument = GetArgument(invocation, 0, 0); + CheckConstantArgument(invocation, argument, "a", 1); + + // 2, 3 + + argument = GetArgument(invocation, 1, 1); + IOperation argumentValue = CheckArgument(invocation, argument, "c"); + CheckArrayCreation(argumentValue, 2, 3); + + // M2(1) + + invocation = CheckInvocation(nodes[1], model, "M2(1)", "M2", 2, SpecialType.System_Void); + Assert.Null(invocation.Instance); + + // 1 + + argument = GetArgument(invocation, 0, 0); + CheckConstantArgument(invocation, argument, "a", 1); + + // () + + argument = GetArgument(invocation, 1, 1); + argumentValue = CheckArgument(invocation, argument, "c"); + CheckArrayCreation(argumentValue); + + // M2() + + invocation = CheckInvocation(nodes[2], model, "M2()", "M2", 2, SpecialType.System_Void, isInvalid: true); + Assert.Null(invocation.Instance); + + // , + + argument = GetArgument(invocation, 0, 0); + argumentValue = CheckArgument(invocation, argument, "a", isInvalid: true); + Assert.Equal(argumentValue.Kind, OperationKind.InvalidExpression); + Assert.True(argumentValue.IsInvalid); + + // () + + argument = GetArgument(invocation, 1, 1); + argumentValue = CheckArgument(invocation, argument, "c"); + CheckArrayCreation(argumentValue); + + // M2(1, new int[] { 2, 3 }) + + invocation = CheckInvocation(nodes[3], model, "M2(1, new int[] { 2, 3 })", "M2", 2, SpecialType.System_Void); + Assert.Null(invocation.Instance); + + // 1 + + argument = GetArgument(invocation, 0, 0); + CheckConstantArgument(invocation, argument, "a", 1); + + // new int [] { 2, 3 } + + argument = GetArgument(invocation, 1, 1); + argumentValue = CheckArgument(invocation, argument, "c"); + CheckArrayCreation(argumentValue, 2, 3); + + // M2(c: new int[] { 2, 3 }, a: 1) + + invocation = CheckInvocation(nodes[4], model, "M2(c: new int[] { 2, 3 }, a: 1)", "M2", 2, SpecialType.System_Void); + Assert.Null(invocation.Instance); + + // 1 + + argument = GetArgument(invocation, 0, 1); + CheckConstantArgument(invocation, argument, "a", 1); + + // new int [] { 2, 3 } + + argument = GetArgument(invocation, 1, 0); + argumentValue = CheckArgument(invocation, argument, "c"); + CheckArrayCreation(argumentValue, 2, 3); + + // M2(c: 2, a: 1) + + invocation = CheckInvocation(nodes[5], model, "M2(c: 2, a: 1)", "M2", 2, SpecialType.System_Void); + Assert.Null(invocation.Instance); + + // 1 + + argument = GetArgument(invocation, 0, 1); + CheckConstantArgument(invocation, argument, "a", 1); + + // 2 + + argument = GetArgument(invocation, 1, 0); + argumentValue = CheckArgument(invocation, argument, "c"); + CheckArrayCreation(argumentValue, 2); + } + + [Fact] + public void VirtualInvocations() + { + const string source = @" +class Base +{ + public virtual void M2() { } +} + +class Derived : Base +{ + void M1() + { + M2(); + this.M2(); + base.M2(); + } + + public override void M2() { } +}"; + + SemanticModel model; + InvocationExpressionSyntax[] nodes = GetInvocations(source, 3, out model); + + // M2() + + IInvocationExpression invocation = CheckInvocation(nodes[0], model, "M2()", "M2", 0, SpecialType.System_Void, isVirtual: true); + CheckInstanceReference(invocation.Instance, InstanceReferenceKind.Implicit, "Derived"); + + // this.M2() + + invocation = CheckInvocation(nodes[1], model, "this.M2()", "M2", 0, SpecialType.System_Void, isVirtual: true); + CheckInstanceReference(invocation.Instance, InstanceReferenceKind.Explicit, "Derived"); + + // base.M2() + + invocation = CheckInvocation(nodes[2], model, "base.M2()", "M2", 0, SpecialType.System_Void, isVirtual: false); + CheckInstanceReference(invocation.Instance, InstanceReferenceKind.BaseClass, "Base"); + } + + [Fact] + public void DefaultArgumentInvocations() + { + const string source = @" +class C +{ + void M1() + { + M2(1, c: 3); + M2(b: 2); + } + + void M2(int a = 10, int b = 20, int c = 30) { } +}"; + + SemanticModel model; + InvocationExpressionSyntax[] nodes = GetInvocations(source, 2, out model); + + // M2(1, c: 3) + + IInvocationExpression invocation = CheckInvocation(nodes[0], model, "M2(1, c: 3)", "M2", 3, SpecialType.System_Void); + CheckInstanceReference(invocation.Instance, InstanceReferenceKind.Implicit, "C"); + + // 1 + + IArgument argument = GetArgument(invocation, 0, 0); + CheckConstantArgument(invocation, argument, "a", 1); + + // 20 + + argument = GetArgument(invocation, 1, 2); + CheckConstantArgument(invocation, argument, "b", 20); + + // c: 3 + + argument = GetArgument(invocation, 2, 1); + CheckConstantArgument(invocation, argument, "c", 3); + + // M2(b: 2) + + invocation = CheckInvocation(nodes[1], model, "M2(b: 2)", "M2", 3, SpecialType.System_Void); + CheckInstanceReference(invocation.Instance, InstanceReferenceKind.Implicit, "C"); + + // 10 + + argument = GetArgument(invocation, 0, 1); + CheckConstantArgument(invocation, argument, "a", 10); + + // b: 2 + + argument = GetArgument(invocation, 1, 0); + CheckConstantArgument(invocation, argument, "b", 2); + + // 30 + + argument = GetArgument(invocation, 2, 2); + CheckConstantArgument(invocation, argument, "c", 30); + } + + [Fact] + public void DelegateInvocations() + { + const string source = @" +class C +{ + void M1() + { + System.Func f = null; + bool b = f(1, 2); + b = f(arg2: 2, arg1: 1); + } +}"; + + SemanticModel model; + InvocationExpressionSyntax[] nodes = GetInvocations(source, 2, out model); + + // f(1, 2) + + IInvocationExpression invocation = CheckInvocation(nodes[0], model, "f(1, 2)", "Invoke", 2, SpecialType.System_Boolean, isVirtual: true); + + // 1 + + IArgument argument = GetArgument(invocation, 0, 0); + CheckConstantArgument(invocation, argument, "arg1", 1); + + // 2 + + argument = GetArgument(invocation, 1, 1); + CheckConstantArgument(invocation, argument, "arg2", 2); + + // f(arg2: 2, arg1: 1) + + invocation = CheckInvocation(nodes[1], model, "f(arg2: 2, arg1: 1)", "Invoke", 2, SpecialType.System_Boolean, isVirtual: true); + + // arg1: 1 + + argument = GetArgument(invocation, 0, 1); + CheckConstantArgument(invocation, argument, "arg1", 1); + + // arg2: 2 + + argument = GetArgument(invocation, 1, 0); + CheckConstantArgument(invocation, argument, "arg2", 2); + } + + [Fact] + public void RefAndOutInvocations() + { + const string source = @" +class C +{ + void M1() + { + int x = 10; + int y; + F(ref x, out y); + F(yy: out y, xx: ref x); + } + + void F(ref int xx, out int yy) { yy = 12; } +}"; + + SemanticModel model; + InvocationExpressionSyntax[] nodes = GetInvocations(source, 2, out model); + + // F(ref x, out y) + + IInvocationExpression invocation = CheckInvocation(nodes[0], model, "F(ref x, out y)", "F", 2, SpecialType.System_Void); + + // ref x + + IArgument argument = GetArgument(invocation, 0, 0); + IOperation argumentValue = CheckArgument(invocation, argument, "xx"); + CheckLocalReference(argumentValue, "x", "Int32"); + + // ref y + + argument = GetArgument(invocation, 1, 1); + argumentValue = CheckArgument(invocation, argument, "yy"); + CheckLocalReference(argumentValue, "y", "Int32"); + + // F(yy: out y, xx: ref x) + + invocation = CheckInvocation(nodes[1], model, "F(yy: out y, xx: ref x)", "F", 2, SpecialType.System_Void); + + // xx: ref x + + argument = GetArgument(invocation, 0, 1); + argumentValue = CheckArgument(invocation, argument, "xx"); + CheckLocalReference(argumentValue, "x", "Int32"); + + // yy: ref y + + argument = GetArgument(invocation, 1, 0); + argumentValue = CheckArgument(invocation, argument, "yy"); + CheckLocalReference(argumentValue, "y", "Int32"); + } + + private static InvocationExpressionSyntax[] GetInvocations(string source, int invocationsCount, out SemanticModel model) + { + var compilation = CreateCompilationWithMscorlib45(source, parseOptions: TestOptions.Regular); + var tree = compilation.SyntaxTrees.Single(); + model = compilation.GetSemanticModel(tree); + var nodes = tree.GetRoot().DescendantNodes().OfType().ToArray(); + Assert.Equal(invocationsCount, nodes.Length); + + return nodes; + } + + private static IInvocationExpression CheckInvocation(InvocationExpressionSyntax node, SemanticModel model, string expressionText, string methodName, int argumentCount, SpecialType resultType, bool isInvalid = false, bool isVirtual = false) + { + Assert.Equal(expressionText, node.ToString()); + IOperation operation = model.GetOperation(node); + Assert.Equal(operation.Kind, OperationKind.InvocationExpression); + Assert.Equal(isInvalid, operation.IsInvalid); + IInvocationExpression invocation = (IInvocationExpression)operation; + Assert.False(invocation.ConstantValue.HasValue); + Assert.Equal(isVirtual, invocation.IsVirtual); + Assert.Equal(methodName, invocation.TargetMethod.Name); + Assert.Equal(argumentCount, invocation.ArgumentsInParameterOrder.Length); + Assert.Equal(argumentCount, invocation.ArgumentsInEvaluationOrder.Length); + Assert.Equal(resultType, invocation.Type.SpecialType); + + return invocation; + } + + private static IArgument GetArgument(IInvocationExpression invocation, int parameterIndex, int evaluationOrderIndex) + { + IArgument argument = invocation.ArgumentsInParameterOrder[parameterIndex]; + Assert.True(argument == invocation.ArgumentsInEvaluationOrder[evaluationOrderIndex]); + return argument; + } + + private static IOperation CheckArgument(IInvocationExpression invocation, IArgument argument, string parameterName, bool isInvalid = false) + { + Assert.Equal(isInvalid, argument.IsInvalid); + Assert.Null(argument.InConversion); + Assert.Null(argument.OutConversion); + Assert.Equal(parameterName, argument.Parameter.Name); + Assert.True(invocation.GetArgumentMatchingParameter(argument.Parameter) == argument); + IOperation argumentValue = argument.Value; + Assert.Equal(isInvalid, argumentValue.IsInvalid); + + return argumentValue; + } + + private static void CheckConstantArgument(IInvocationExpression invocation, IArgument argument, string parameterName, int argumentConstantValue) + { + IOperation argumentValue = CheckArgument(invocation, argument, parameterName); + Assert.Equal(argumentValue.Kind, OperationKind.LiteralExpression); + Assert.Equal(argumentValue.Type.SpecialType, SpecialType.System_Int32); + Assert.True(argumentValue.ConstantValue.HasValue); + Assert.Equal(argumentConstantValue, argumentValue.ConstantValue.Value); + } + + private static void CheckInstanceReference(IOperation invocationInstance, InstanceReferenceKind instanceKind, string instanceType) + { + Assert.NotNull(invocationInstance); + Assert.Equal(invocationInstance.Kind, OperationKind.InstanceReferenceExpression); + IInstanceReferenceExpression instanceReference = (IInstanceReferenceExpression)invocationInstance; + Assert.False(instanceReference.IsInvalid); + Assert.Equal(instanceKind, instanceReference.InstanceReferenceKind); + Assert.Equal(instanceType, instanceReference.Type.Name); + } + + private static void CheckLocalReference(IOperation reference, string localName, string localType) + { + Assert.NotNull(reference); + Assert.Equal(reference.Kind, OperationKind.LocalReferenceExpression); + ILocalReferenceExpression localReference = (ILocalReferenceExpression)reference; + Assert.False(localReference.IsInvalid); + Assert.Equal(localName, localReference.Local.Name); + Assert.Equal(localType, localReference.Type.Name); + } + + private static void CheckArrayCreation(IOperation value, params int[] elements) + { + Assert.Equal(value.Kind, OperationKind.ArrayCreationExpression); + Assert.Equal(value.Type.TypeKind, TypeKind.Array); + Assert.Equal(((ArrayTypeSymbol)value.Type).ElementType.SpecialType, SpecialType.System_Int32); + Assert.False(value.ConstantValue.HasValue); + IArrayCreationExpression argumentArray = (IArrayCreationExpression)value; + Assert.Equal(argumentArray.Initializer.Kind, OperationKind.ArrayInitializer); + ImmutableArray elementValues = argumentArray.Initializer.ElementValues; + Assert.Equal(elementValues.Length, elements.Length); + + for (int index = 0; index < elements.Length; index++) + { + IOperation elementValue = elementValues[index]; + Assert.Equal(elementValue.Kind, OperationKind.LiteralExpression); + Assert.False(elementValue.IsInvalid); + Assert.Equal(elementValue.Type.SpecialType, SpecialType.System_Int32); + Assert.True(elementValue.ConstantValue.HasValue); + Assert.Equal(elementValue.ConstantValue.Value, elements[index]); + } + } + } +} \ No newline at end of file diff --git a/src/Compilers/CSharp/Test/Semantic/Diagnostics/OperationAnalyzerTests.cs b/src/Compilers/CSharp/Test/Semantic/Diagnostics/OperationAnalyzerTests.cs index 9072e82117c4e..531e58b93cc16 100644 --- a/src/Compilers/CSharp/Test/Semantic/Diagnostics/OperationAnalyzerTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Diagnostics/OperationAnalyzerTests.cs @@ -344,12 +344,7 @@ public void M6() Diagnostic(InvocationTestAnalyzer.BigParamArrayArgumentsDescriptor.Id, "M0(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)").WithLocation(19, 9), Diagnostic(InvocationTestAnalyzer.BigParamArrayArgumentsDescriptor.Id, "M0(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13)").WithLocation(20, 9), Diagnostic(InvocationTestAnalyzer.OutOfNumericalOrderArgumentsDescriptor.Id, "3").WithLocation(23, 21), - Diagnostic(InvocationTestAnalyzer.UseDefaultArgumentDescriptor.Id, "M3(0)").WithArguments("y").WithLocation(33, 9), - Diagnostic(InvocationTestAnalyzer.UseDefaultArgumentDescriptor.Id, "M3(y: null)").WithArguments("x").WithLocation(34, 9), - Diagnostic(InvocationTestAnalyzer.UseDefaultArgumentDescriptor.Id, "M3(x: 0)").WithArguments("y").WithLocation(35, 9), - Diagnostic(InvocationTestAnalyzer.UseDefaultArgumentDescriptor.Id, "M3()").WithArguments("x").WithLocation(36, 9), - Diagnostic(InvocationTestAnalyzer.UseDefaultArgumentDescriptor.Id, "M3()").WithArguments("y").WithLocation(36, 9), - Diagnostic(InvocationTestAnalyzer.UseDefaultArgumentDescriptor.Id, "M5(b: new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11})").WithArguments("x").WithLocation(47, 9) + Diagnostic(InvocationTestAnalyzer.BigParamArrayArgumentsDescriptor.Id, "M5(b: new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11})").WithLocation(47, 9) ); } @@ -1653,7 +1648,11 @@ public void M1() .VerifyAnalyzerDiagnostics(new DiagnosticAnalyzer[] { new NullOperationSyntaxTestAnalyzer() }, null, null, false, Diagnostic(NullOperationSyntaxTestAnalyzer.ParamsArrayOperationDescriptor.Id, "M0()").WithLocation(10, 9), Diagnostic(NullOperationSyntaxTestAnalyzer.ParamsArrayOperationDescriptor.Id, "1").WithLocation(11, 12), - Diagnostic(NullOperationSyntaxTestAnalyzer.ParamsArrayOperationDescriptor.Id, "1").WithLocation(12, 12)); + Diagnostic(NullOperationSyntaxTestAnalyzer.ParamsArrayOperationDescriptor.Id, "1").WithLocation(12, 12), + Diagnostic(NullOperationSyntaxTestAnalyzer.ParamsArrayOperationDescriptor.Id, "new int[] { }").WithLocation(13, 12), + Diagnostic(NullOperationSyntaxTestAnalyzer.ParamsArrayOperationDescriptor.Id, "new int[] { 1 }").WithLocation(14, 12), + Diagnostic(NullOperationSyntaxTestAnalyzer.ParamsArrayOperationDescriptor.Id, "new int[] { 1, 2}").WithLocation(15, 12) + ); } [WorkItem(9113, "https://github.com/dotnet/roslyn/issues/9113")] diff --git a/src/Compilers/Core/CodeAnalysisTest/Diagnostics/OperationTestAnalyzer.cs b/src/Compilers/Core/CodeAnalysisTest/Diagnostics/OperationTestAnalyzer.cs index ed551eb02ff04..6a6c4bceb3129 100644 --- a/src/Compilers/Core/CodeAnalysisTest/Diagnostics/OperationTestAnalyzer.cs +++ b/src/Compilers/Core/CodeAnalysisTest/Diagnostics/OperationTestAnalyzer.cs @@ -464,7 +464,7 @@ private void Report(OperationAnalysisContext context, SyntaxNode syntax, Diagnos } } - /// Analyzer used to test invocaton IOperations. + /// Analyzer used to test invocation IOperations. public class InvocationTestAnalyzer : DiagnosticAnalyzer { /// Diagnostic category "Reliability". @@ -486,14 +486,6 @@ public class InvocationTestAnalyzer : DiagnosticAnalyzer DiagnosticSeverity.Warning, isEnabledByDefault: true); - public static readonly DiagnosticDescriptor UseDefaultArgumentDescriptor = new DiagnosticDescriptor( - "UseDefaultArgument", - "Use default argument", - "Invocation uses default argument {0}", - ReliabilityCategory, - DiagnosticSeverity.Warning, - isEnabledByDefault: true); - public static readonly DiagnosticDescriptor InvalidArgumentDescriptor = new DiagnosticDescriptor( "InvalidArgument", "Invalid argument", @@ -509,7 +501,6 @@ public sealed override ImmutableArray SupportedDiagnostics { return ImmutableArray.Create(BigParamArrayArgumentsDescriptor, OutOfNumericalOrderArgumentsDescriptor, - UseDefaultArgumentDescriptor, InvalidArgumentDescriptor); } } @@ -529,14 +520,9 @@ public sealed override void Initialize(AnalysisContext context) return; } - if (argument.ArgumentKind == ArgumentKind.DefaultValue) - { - operationContext.ReportDiagnostic(Diagnostic.Create(UseDefaultArgumentDescriptor, invocation.Syntax.GetLocation(), argument.Parameter.Name)); - } - TestAscendingArgument(operationContext, argument.Value, ref priorArgumentValue); - if (argument.ArgumentKind == ArgumentKind.ParamArray) + if (argument.Parameter.IsParams) { IArrayCreationExpression arrayArgument = argument.Value as IArrayCreationExpression; if (arrayArgument != null) @@ -1085,7 +1071,7 @@ public sealed override void Initialize(AnalysisContext context) { IInvocationExpression invocation = (IInvocationExpression)operationContext.Operation; - foreach (IArgument argument in invocation.ArgumentsInSourceOrder) + foreach (IArgument argument in invocation.ArgumentsInEvaluationOrder) { if (argument.Parameter.IsParams) { @@ -1777,7 +1763,7 @@ public override void Visit(IOperation operation) } if (operation.Kind == OperationKind.Argument) { - if (((IArgument)operation).ArgumentKind == ArgumentKind.ParamArray) + if (((IArgument)operation).Parameter.IsParams) { _paramsList.Add(operation); } diff --git a/src/Compilers/Core/Portable/Compilation/IExpression.cs b/src/Compilers/Core/Portable/Compilation/IExpression.cs index 4cf6988a2218f..33998c418836f 100644 --- a/src/Compilers/Core/Portable/Compilation/IExpression.cs +++ b/src/Compilers/Core/Portable/Compilation/IExpression.cs @@ -10,6 +10,12 @@ namespace Microsoft.CodeAnalysis.Semantics /// public interface IHasArgumentsExpression : IOperation { + /// + /// Arguments of the invocation, excluding the instance argument. Arguments are in the order that they will be evaluated, + /// and params/ParamArray arguments have been collected into arrays. Default values are supplied for + /// optional arguments missing in source. + /// + ImmutableArray ArgumentsInEvaluationOrder { get; } /// /// Arguments of the invocation, excluding the instance argument. Arguments are in parameter order, /// and params/ParamArray arguments have been collected into arrays. Default values are supplied for @@ -45,12 +51,6 @@ public interface IInvocationExpression : IHasArgumentsExpression /// True if the invocation uses a virtual mechanism, and false otherwise. /// bool IsVirtual { get; } - /// - /// Arguments of the invocation, excluding the instance argument. Arguments are in the order specified in source, - /// and params/ParamArray arguments have been collected into arrays. Arguments are not present - /// unless supplied in source. - /// - ImmutableArray ArgumentsInSourceOrder { get; } } /// @@ -62,10 +62,6 @@ public interface IInvocationExpression : IHasArgumentsExpression /// public interface IArgument : IOperation { - /// - /// Kind of argument. - /// - ArgumentKind ArgumentKind { get; } /// /// Parameter the argument matches. /// @@ -84,31 +80,6 @@ public interface IArgument : IOperation IOperation OutConversion { get; } } - /// - /// Kinds of arguments. - /// - public enum ArgumentKind - { - None = 0x0, - - /// - /// Argument is specified positionally and matches the parameter of the same ordinality. - /// - Positional = 0x1, - /// - /// Argument is specified by name and matches the parameter of the same name. - /// - Named = 0x2, - /// - /// Argument becomes an element of an array that matches a trailing C# params or VB ParamArray parameter. - /// - ParamArray = 0x3, - /// - /// Argument was omitted in source but has a default value supplied automatically. - /// - DefaultValue = 0x4 - } - /// /// Represents a reference to an array element. /// diff --git a/src/Compilers/Core/Portable/Compilation/OperationWalker.cs b/src/Compilers/Core/Portable/Compilation/OperationWalker.cs index f98a7905aa441..cedd4ea7386dd 100644 --- a/src/Compilers/Core/Portable/Compilation/OperationWalker.cs +++ b/src/Compilers/Core/Portable/Compilation/OperationWalker.cs @@ -194,7 +194,7 @@ public override void VisitEndStatement(IEndStatement operation) public override void VisitInvocationExpression(IInvocationExpression operation) { Visit(operation.Instance); - VisitArray(operation.ArgumentsInParameterOrder); + VisitArray(operation.ArgumentsInEvaluationOrder); } public override void VisitArgument(IArgument operation) @@ -273,7 +273,7 @@ public override void VisitPlaceholderExpression(IPlaceholderExpression operation public override void VisitIndexedPropertyReferenceExpression(IIndexedPropertyReferenceExpression operation) { Visit(operation.Instance); - VisitArray(operation.ArgumentsInParameterOrder); + VisitArray(operation.ArgumentsInEvaluationOrder); } public override void VisitUnaryOperatorExpression(IUnaryOperatorExpression operation) @@ -336,7 +336,7 @@ public override void VisitAddressOfExpression(IAddressOfExpression operation) public override void VisitObjectCreationExpression(IObjectCreationExpression operation) { - VisitArray(operation.ArgumentsInParameterOrder); + VisitArray(operation.ArgumentsInEvaluationOrder); VisitArray(operation.MemberInitializers); } diff --git a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt index 58b6f0b9c3bd9..272f716aeeac6 100644 --- a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt +++ b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt @@ -137,12 +137,6 @@ Microsoft.CodeAnalysis.OperationKind.YieldBreakStatement = 12 -> Microsoft.CodeA Microsoft.CodeAnalysis.OperationKind.YieldReturnStatement = 16 -> Microsoft.CodeAnalysis.OperationKind Microsoft.CodeAnalysis.PortableExecutableReference.GetMetadata() -> Microsoft.CodeAnalysis.Metadata Microsoft.CodeAnalysis.SemanticModel.GetOperation(Microsoft.CodeAnalysis.SyntaxNode node, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> Microsoft.CodeAnalysis.IOperation -Microsoft.CodeAnalysis.Semantics.ArgumentKind -Microsoft.CodeAnalysis.Semantics.ArgumentKind.DefaultValue = 4 -> Microsoft.CodeAnalysis.Semantics.ArgumentKind -Microsoft.CodeAnalysis.Semantics.ArgumentKind.Named = 2 -> Microsoft.CodeAnalysis.Semantics.ArgumentKind -Microsoft.CodeAnalysis.Semantics.ArgumentKind.None = 0 -> Microsoft.CodeAnalysis.Semantics.ArgumentKind -Microsoft.CodeAnalysis.Semantics.ArgumentKind.ParamArray = 3 -> Microsoft.CodeAnalysis.Semantics.ArgumentKind -Microsoft.CodeAnalysis.Semantics.ArgumentKind.Positional = 1 -> Microsoft.CodeAnalysis.Semantics.ArgumentKind Microsoft.CodeAnalysis.Semantics.BinaryOperandsKind Microsoft.CodeAnalysis.Semantics.BinaryOperandsKind.Boolean = 1536 -> Microsoft.CodeAnalysis.Semantics.BinaryOperandsKind Microsoft.CodeAnalysis.Semantics.BinaryOperandsKind.Decimal = 1280 -> Microsoft.CodeAnalysis.Semantics.BinaryOperandsKind @@ -334,7 +328,6 @@ Microsoft.CodeAnalysis.Semantics.ConversionKind.TryCast = 2 -> Microsoft.CodeAna Microsoft.CodeAnalysis.Semantics.IAddressOfExpression Microsoft.CodeAnalysis.Semantics.IAddressOfExpression.Reference.get -> Microsoft.CodeAnalysis.IOperation Microsoft.CodeAnalysis.Semantics.IArgument -Microsoft.CodeAnalysis.Semantics.IArgument.ArgumentKind.get -> Microsoft.CodeAnalysis.Semantics.ArgumentKind Microsoft.CodeAnalysis.Semantics.IArgument.InConversion.get -> Microsoft.CodeAnalysis.IOperation Microsoft.CodeAnalysis.Semantics.IArgument.OutConversion.get -> Microsoft.CodeAnalysis.IOperation Microsoft.CodeAnalysis.Semantics.IArgument.Parameter.get -> Microsoft.CodeAnalysis.IParameterSymbol @@ -413,6 +406,7 @@ Microsoft.CodeAnalysis.Semantics.IForLoopStatement.Locals.get -> System.Collecti Microsoft.CodeAnalysis.Semantics.IForWhileUntilLoopStatement Microsoft.CodeAnalysis.Semantics.IForWhileUntilLoopStatement.Condition.get -> Microsoft.CodeAnalysis.IOperation Microsoft.CodeAnalysis.Semantics.IHasArgumentsExpression +Microsoft.CodeAnalysis.Semantics.IHasArgumentsExpression.ArgumentsInEvaluationOrder.get -> System.Collections.Immutable.ImmutableArray Microsoft.CodeAnalysis.Semantics.IHasArgumentsExpression.ArgumentsInParameterOrder.get -> System.Collections.Immutable.ImmutableArray Microsoft.CodeAnalysis.Semantics.IHasArgumentsExpression.GetArgumentMatchingParameter(Microsoft.CodeAnalysis.IParameterSymbol parameter) -> Microsoft.CodeAnalysis.Semantics.IArgument Microsoft.CodeAnalysis.Semantics.IHasOperatorMethodExpression @@ -430,7 +424,6 @@ Microsoft.CodeAnalysis.Semantics.IInstanceReferenceExpression.InstanceReferenceK Microsoft.CodeAnalysis.Semantics.IInvalidExpression Microsoft.CodeAnalysis.Semantics.IInvalidStatement Microsoft.CodeAnalysis.Semantics.IInvocationExpression -Microsoft.CodeAnalysis.Semantics.IInvocationExpression.ArgumentsInSourceOrder.get -> System.Collections.Immutable.ImmutableArray Microsoft.CodeAnalysis.Semantics.IInvocationExpression.Instance.get -> Microsoft.CodeAnalysis.IOperation Microsoft.CodeAnalysis.Semantics.IInvocationExpression.IsVirtual.get -> bool Microsoft.CodeAnalysis.Semantics.IInvocationExpression.TargetMethod.get -> Microsoft.CodeAnalysis.IMethodSymbol diff --git a/src/Compilers/VisualBasic/Portable/BoundTree/Expression.vb b/src/Compilers/VisualBasic/Portable/BoundTree/Expression.vb index 473b6340c663b..a914d4e48f754 100644 --- a/src/Compilers/VisualBasic/Portable/BoundTree/Expression.vb +++ b/src/Compilers/VisualBasic/Portable/BoundTree/Expression.vb @@ -297,9 +297,9 @@ Namespace Microsoft.CodeAnalysis.VisualBasic Return ArgumentMatchingParameter(Me.Arguments, parameter, Me.Method.Parameters) End Function - Private ReadOnly Property IInvocationExpression_ArgumentsInSourceOrder As ImmutableArray(Of IArgument) Implements IInvocationExpression.ArgumentsInSourceOrder + Private ReadOnly Property IHasArgumentsExpression_ArgumentsInEvaluationOrder As ImmutableArray(Of IArgument) Implements IHasArgumentsExpression.ArgumentsInEvaluationOrder Get - Return DeriveArguments(Me.Arguments, Me.Method.Parameters) + Return IHasArgumentsExpression_ArgumentsInParameterOrder End Get End Property @@ -311,13 +311,14 @@ Namespace Microsoft.CodeAnalysis.VisualBasic Private ReadOnly Property IInvocationExpression_IsVirtual As Boolean Implements IInvocationExpression.IsVirtual Get - Dim method As IMethodSymbol = Me.Method - Dim instance As IOperation = Me.ReceiverOpt - - Return method IsNot Nothing AndAlso instance IsNot Nothing AndAlso (method.IsVirtual OrElse method.IsAbstract OrElse method.IsOverride) AndAlso instance.Kind <> BoundKind.MyBaseReference AndAlso instance.Kind <> BoundKind.MyClassReference + Return IsVirtualReference(Me.Method, Me.ReceiverOpt) End Get End Property + Friend Shared Function IsVirtualReference(method As Symbols.MethodSymbol, receiver As BoundExpression) As Boolean + Return method IsNot Nothing AndAlso receiver IsNot Nothing AndAlso (method.IsOverridable OrElse method.IsMustOverride OrElse method.IsOverrides) AndAlso Not receiver.SuppressVirtualCalls + End Function + Private ReadOnly Property IInvocationExpression_TargetMethod As IMethodSymbol Implements IInvocationExpression.TargetMethod Get Return Me.Method @@ -374,16 +375,9 @@ Namespace Microsoft.CodeAnalysis.VisualBasic argument, Function(argumentValue) New ByRefArgument(If(CUInt(index) < CUInt(parameters.Length), parameters(index), Nothing), DirectCast(argumentValue, BoundByRefArgumentWithCopyBack))) Case Else - ' Apparently the VB bound trees don't encode named arguments, which seems unnecesarily lossy. Return s_argumentMappings.GetValue( argument, - Function(argumentValue) - If index >= parameters.Length - 1 AndAlso parameters.Length > 0 AndAlso parameters(parameters.Length - 1).IsParamArray Then - Return New Argument(ArgumentKind.ParamArray, parameters(parameters.Length - 1), argumentValue) - Else - Return New Argument(ArgumentKind.Positional, If(CUInt(index) < CUInt(parameters.Length), parameters(index), Nothing), argumentValue) - End If - End Function) + Function(argumentValue) New Argument(If(CUInt(index) < CUInt(parameters.Length), parameters(index), Nothing), argumentValue)) End Select End Function @@ -420,7 +414,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic End Get End Property - Public MustOverride ReadOnly Property ArgumentKind As ArgumentKind Implements IArgument.ArgumentKind Public MustOverride ReadOnly Property Value As IOperation Implements IArgument.Value Public MustOverride ReadOnly Property InConversion As IOperation Implements IArgument.InConversion Public MustOverride ReadOnly Property OutConversion As IOperation Implements IArgument.OutConversion @@ -450,12 +443,10 @@ Namespace Microsoft.CodeAnalysis.VisualBasic Inherits ArgumentBase Private ReadOnly _value As IOperation - Private ReadOnly _kind As ArgumentKind - Public Sub New(kind As ArgumentKind, parameter As IParameterSymbol, value As IOperation) + Public Sub New(parameter As IParameterSymbol, value As IOperation) MyBase.New(parameter) _value = value - _kind = kind End Sub Public Overrides ReadOnly Property Value As IOperation @@ -475,12 +466,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic Return Nothing End Get End Property - - Public Overrides ReadOnly Property ArgumentKind As ArgumentKind - Get - Return _kind - End Get - End Property End Class Private NotInheritable Class ByRefArgument @@ -493,22 +478,27 @@ Namespace Microsoft.CodeAnalysis.VisualBasic _argument = argument End Sub - Public Overrides ReadOnly Property ArgumentKind As ArgumentKind - Get - ' Do the VB bound trees encode named arguments? - Return ArgumentKind.Positional - End Get - End Property - Public Overrides ReadOnly Property InConversion As IOperation Get - Return _argument.InConversion + Dim conversion As BoundExpression = _argument.InConversion + ' The bound trees can contain a simple placeholder as a conversion. Don't treat this as a conversion. + If conversion IsNot Nothing AndAlso conversion.Kind <> BoundKind.ByRefArgumentPlaceholder Then + Return conversion + End If + + Return Nothing End Get End Property Public Overrides ReadOnly Property OutConversion As IOperation Get - Return _argument.OutConversion + Dim conversion As BoundExpression = _argument.OutConversion + ' The bound trees can contain a simple placeholder as a conversion. Don't treat this as a conversion. + If conversion IsNot Nothing AndAlso conversion.Kind <> BoundKind.RValuePlaceholder Then + Return conversion + End If + + Return Nothing End Get End Property @@ -1178,6 +1168,12 @@ Namespace Microsoft.CodeAnalysis.VisualBasic End Get End Property + Private ReadOnly Property IHasArgumentsExpression_ArgumentsInEvaluationOrder As ImmutableArray(Of IArgument) Implements IHasArgumentsExpression.ArgumentsInEvaluationOrder + Get + Return IHasArgumentsExpression_ArgumentsInParameterOrder + End Get + End Property + Private ReadOnly Property IHasArgumentsExpression_ArgumentsInParameterOrder As ImmutableArray(Of IArgument) Implements IHasArgumentsExpression.ArgumentsInParameterOrder Get Debug.Assert(Me.ConstructorOpt IsNot Nothing OrElse Me.Arguments.IsEmpty()) @@ -1457,6 +1453,12 @@ Namespace Microsoft.CodeAnalysis.VisualBasic End Get End Property + Private ReadOnly Property IHasArgumentsExpression_ArgumentsInEvaluationOrder As ImmutableArray(Of IArgument) Implements IHasArgumentsExpression.ArgumentsInEvaluationOrder + Get + Return IHasArgumentsExpression_ArgumentsInParameterOrder + End Get + End Property + Private ReadOnly Property IHasArgumentsExpression_ArgumentsInParameterOrder As ImmutableArray(Of IArgument) Implements IHasArgumentsExpression.ArgumentsInParameterOrder Get Return BoundCall.DeriveArguments(Me.Arguments, Me.PropertySymbol.Parameters) @@ -1533,7 +1535,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic Private ReadOnly Property IMethodBindingExpression_IsVirtual As Boolean Implements IMethodBindingExpression.IsVirtual Get - Return Me.Method IsNot Nothing AndAlso (Me.Method.IsOverridable OrElse Me.Method.IsOverrides OrElse Me.Method.IsMustOverride) AndAlso Not Me.SuppressVirtualCalls + Return BoundCall.IsVirtualReference(Me.Method, Me.ReceiverOpt) End Get End Property diff --git a/src/Compilers/VisualBasic/Test/Semantic/BasicCompilerSemanticTest.vbproj b/src/Compilers/VisualBasic/Test/Semantic/BasicCompilerSemanticTest.vbproj index bdb15d29677f2..e1a46df248b0b 100644 --- a/src/Compilers/VisualBasic/Test/Semantic/BasicCompilerSemanticTest.vbproj +++ b/src/Compilers/VisualBasic/Test/Semantic/BasicCompilerSemanticTest.vbproj @@ -118,6 +118,7 @@ + diff --git a/src/Compilers/VisualBasic/Test/Semantic/Diagnostics/IInvocationExpressionTests.vb b/src/Compilers/VisualBasic/Test/Semantic/Diagnostics/IInvocationExpressionTests.vb new file mode 100644 index 0000000000000..d0ea8ea4f8ec5 --- /dev/null +++ b/src/Compilers/VisualBasic/Test/Semantic/Diagnostics/IInvocationExpressionTests.vb @@ -0,0 +1,541 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports System.Collections.Immutable +Imports Microsoft.CodeAnalysis.Semantics +Imports Microsoft.CodeAnalysis.VisualBasic.Symbols +Imports Microsoft.CodeAnalysis.VisualBasic.Syntax + +Namespace Microsoft.CodeAnalysis.VisualBasic.UnitTests.Semantics + + Public Class IInvocationExpressionTests + Inherits BasicTestBase + + + Public Sub SimpleInvocations() + + Dim source = + + + + + + + Dim model As SemanticModel = Nothing + Dim nodes As InvocationExpressionSyntax() = GetInvocations(source, 3, model) + + ' M2(1, 2) + + Dim invocation As IInvocationExpression = CheckInvocation(nodes(0), model, "M2(1, 2)", "M2", 2, SpecialType.System_Void) + CheckInstanceReference(invocation.Instance, InstanceReferenceKind.Implicit, "C") + + ' 1 + + Dim argument As IArgument = GetArgument(invocation, 0) + CheckConstantArgument(invocation, argument, "a", 1) + + ' 2 + + argument = GetArgument(invocation, 1) + CheckConstantArgument(invocation, argument, "b", 2) + + ' local.M2(b:=2, a:=1) + + invocation = CheckInvocation(nodes(1), model, "local.M2(b:=2, a:=1)", "M2", 2, SpecialType.System_Void) + CheckLocalReference(invocation.Instance, "local", "C") + + ' a:=1 + + argument = GetArgument(invocation, 0) + CheckConstantArgument(invocation, argument, "a", 1) + + ' b:=2 + + argument = GetArgument(invocation, 1) + CheckConstantArgument(invocation, argument, "b", 2) + + ' M3(x) + + invocation = CheckInvocation(nodes(2), model, "M3(x)", "M3", 1, SpecialType.System_Double) + Assert.Null(invocation.Instance) + + ' x + + argument = GetArgument(invocation, 0) + Dim argumentValue As IOperation = CheckArgument(invocation, argument, "d") + + Assert.Equal(argumentValue.Kind, OperationKind.ConversionExpression) + Assert.Equal(argumentValue.Type.SpecialType, SpecialType.System_Double) + CheckLocalReference(DirectCast(argumentValue, IConversionExpression).Operand, "x", "Int32") + End Sub + + + Public Sub ParamArrayInvocations() + + Dim source = + + + + + + Dim model As SemanticModel = Nothing + Dim nodes As InvocationExpressionSyntax() = GetInvocations(source, 5, model) + + ' M2(1, 2, 3) + + Dim invocation As IInvocationExpression = CheckInvocation(nodes(0), model, "M2(1, 2, 3)", "M2", 2, SpecialType.System_Void) + Assert.Null(invocation.Instance()) + + ' 1 + + Dim argument As IArgument = GetArgument(invocation, 0) + CheckConstantArgument(invocation, argument, "a", 1) + + ' 2, 3 + + argument = GetArgument(invocation, 1) + Dim argumentValue As IOperation = CheckArgument(invocation, argument, "c") + CheckArrayCreation(argumentValue, 2, 3) + + ' M2(1) + + invocation = CheckInvocation(nodes(1), model, "M2(1)", "M2", 2, SpecialType.System_Void) + Assert.Null(invocation.Instance) + + ' 1 + + argument = GetArgument(invocation, 0) + CheckConstantArgument(invocation, argument, "a", 1) + + ' () + + argument = GetArgument(invocation, 1) + argumentValue = CheckArgument(invocation, argument, "c") + CheckArrayCreation(argumentValue) + + ' M2() + + Assert.Equal("M2()", nodes(2).ToString()) + Dim operation As IOperation = model.GetOperation(nodes(2)) + ' The VB compiler does not treat this as invocation expression that is invalid--instead it's just an invalid expression. + Assert.Equal(operation.Kind, OperationKind.InvalidExpression) + Assert.True(operation.IsInvalid) + Dim invalid As IInvalidExpression = DirectCast(operation, IInvalidExpression) + Assert.Equal(invalid.Type.SpecialType, SpecialType.System_Void) + + ' M2(1, New Integer() { 2, 3 }) + + invocation = CheckInvocation(nodes(3), model, "M2(1, New Integer() { 2, 3 })", "M2", 2, SpecialType.System_Void) + Assert.Null(invocation.Instance) + + ' 1 + + argument = GetArgument(invocation, 0) + CheckConstantArgument(invocation, argument, "a", 1) + + ' New Integer() { 2, 3 } + + argument = GetArgument(invocation, 1) + argumentValue = CheckArgument(invocation, argument, "c") + CheckArrayCreation(argumentValue, 2, 3) + + ' M2(c:=New Integer() { 2, 3 }, a:=1) + + Assert.Equal("M2(c:=New Integer() { 2, 3 }, a:=1)", nodes(4).ToString()) + operation = model.GetOperation(nodes(4)) + ' VB does not allow using a named argument to match a ParamArray parameter. + Assert.Equal(operation.Kind, OperationKind.InvalidExpression) + Assert.True(operation.IsInvalid) + End Sub + + + Public Sub VirtualInvocations() + + Dim source = + + + + + + Dim model As SemanticModel = Nothing + Dim nodes As InvocationExpressionSyntax() = GetInvocations(source, 4, model) + + ' M2() + + Dim invocation As IInvocationExpression = CheckInvocation(nodes(0), model, "M2()", "M2", 0, SpecialType.System_Void, IsVirtual:=True) + CheckInstanceReference(invocation.Instance, InstanceReferenceKind.Implicit, "Derived") + + ' Me.M2() + + invocation = CheckInvocation(nodes(1), model, "Me.M2()", "M2", 0, SpecialType.System_Void, IsVirtual:=True) + CheckInstanceReference(invocation.Instance, InstanceReferenceKind.Explicit, "Derived") + + ' MyClass.M2() + + invocation = CheckInvocation(nodes(2), model, "MyClass.M2()", "M2", 0, SpecialType.System_Void, IsVirtual:=False) + CheckInstanceReference(invocation.Instance, InstanceReferenceKind.ThisClass, "Derived") + + ' MyBase.M2() + + invocation = CheckInvocation(nodes(3), model, "MyBase.M2()", "M2", 0, SpecialType.System_Void, IsVirtual:=False) + CheckInstanceReference(invocation.Instance, InstanceReferenceKind.BaseClass, "Base") + End Sub + + + Public Sub DefaultArgumentInvocations() + + Dim source = + + + + + + Dim model As SemanticModel = Nothing + Dim nodes As InvocationExpressionSyntax() = GetInvocations(source, 2, model) + + ' M2(1, c:=3) + + Dim invocation As IInvocationExpression = CheckInvocation(nodes(0), model, "M2(1, c:=3)", "M2", 3, SpecialType.System_Void) + CheckInstanceReference(invocation.Instance, InstanceReferenceKind.Implicit, "C") + + ' 1 + + Dim argument As IArgument = GetArgument(invocation, 0) + CheckConstantArgument(invocation, argument, "a", 1) + + ' 20 + + argument = GetArgument(invocation, 1) + CheckConstantArgument(invocation, argument, "b", 20) + + ' c:=3 + + argument = GetArgument(invocation, 2) + CheckConstantArgument(invocation, argument, "c", 3) + + ' M2(b:=2) + + invocation = CheckInvocation(nodes(1), model, "M2(b:=2)", "M2", 3, SpecialType.System_Void) + CheckInstanceReference(invocation.Instance, InstanceReferenceKind.Implicit, "C") + + ' 10 + + argument = GetArgument(invocation, 0) + CheckConstantArgument(invocation, argument, "a", 10) + + ' b:=2 + + argument = GetArgument(invocation, 1) + CheckConstantArgument(invocation, argument, "b", 2) + + ' 30 + + argument = GetArgument(invocation, 2) + CheckConstantArgument(invocation, argument, "c", 30) + End Sub + + + Public Sub DelegateInvocations() + + Dim source = + + + + + + Dim model As SemanticModel = Nothing + Dim nodes As InvocationExpressionSyntax() = GetInvocations(source, 2, model) + + ' f(1, 2) + + Dim invocation As IInvocationExpression = CheckInvocation(nodes(0), model, "f(1, 2)", "Invoke", 2, SpecialType.System_Boolean, IsVirtual:=True) + + ' 1 + + Dim argument As IArgument = GetArgument(invocation, 0) + CheckConstantArgument(invocation, argument, "arg1", 1) + + ' 2 + + argument = GetArgument(invocation, 1) + CheckConstantArgument(invocation, argument, "arg2", 2) + + ' f(arg2:=2, arg1:=1) + + invocation = CheckInvocation(nodes(1), model, "f(arg2:=2, arg1:=1)", "Invoke", 2, SpecialType.System_Boolean, IsVirtual:=True) + + ' arg1:=1 + + argument = GetArgument(invocation, 0) + CheckConstantArgument(invocation, argument, "arg1", 1) + + ' arg2:=2 + + argument = GetArgument(invocation, 1) + CheckConstantArgument(invocation, argument, "arg2", 2) + End Sub + + + Public Sub SimpleRefInvocations() + + Dim source = + + + + + + Dim model As SemanticModel = Nothing + Dim nodes As InvocationExpressionSyntax() = GetInvocations(source, 2, model) + + ' F(x, y, y) + + Dim invocation As IInvocationExpression = CheckInvocation(nodes(0), model, "F(x, y, y)", "F", 3, SpecialType.System_Void) + + ' x + + Dim argument As IArgument = GetArgument(invocation, 0) + Dim argumentValue As IOperation = CheckArgument(invocation, argument, "xx") + CheckLocalReference(argumentValue, "x", "Int32") + + ' y + + argument = GetArgument(invocation, 1) + argumentValue = CheckArgument(invocation, argument, "yy") + CheckPropertyReference(argumentValue, "y", "Int32") + + ' y + + argument = GetArgument(invocation, 2) + argumentValue = CheckArgument(invocation, argument, "zz", allowConversions:=True) + CheckPropertyReference(argumentValue, "y", "Int32") + CheckArgumentConversions(argument, SpecialType.System_Int32, SpecialType.System_Double) + + ' F(zz:=y, yy:=y, xx:=x) + + invocation = CheckInvocation(nodes(1), model, "F(zz:=y, yy:=y, xx:=x)", "F", 3, SpecialType.System_Void) + + ' xx:=x + + argument = GetArgument(invocation, 0) + argumentValue = CheckArgument(invocation, argument, "xx") + CheckLocalReference(argumentValue, "x", "Int32") + + ' yy:=y + + argument = GetArgument(invocation, 1) + argumentValue = CheckArgument(invocation, argument, "yy") + CheckPropertyReference(argumentValue, "y", "Int32") + + ' zz:=y + + argument = GetArgument(invocation, 2) + argumentValue = CheckArgument(invocation, argument, "zz", allowConversions:=True) + CheckPropertyReference(argumentValue, "y", "Int32") + CheckArgumentConversions(argument, SpecialType.System_Int32, SpecialType.System_Double) + End Sub + + Private Shared Function GetInvocations(source As Xml.Linq.XElement, invocationsCount As Integer, ByRef model As SemanticModel) As InvocationExpressionSyntax() + Dim compilation = CompilationUtils.CreateCompilationWithMscorlibAndVBRuntime(source, parseOptions:=TestOptions.Regular) + Dim tree = compilation.SyntaxTrees.Single() + model = compilation.GetSemanticModel(tree) + Dim nodes = tree.GetRoot().DescendantNodes().OfType(Of InvocationExpressionSyntax).ToArray() + Assert.Equal(invocationsCount, nodes.Length) + + Return nodes + End Function + + Private Shared Function CheckInvocation(node As InvocationExpressionSyntax, model As SemanticModel, expressionText As String, methodName As String, argumentCount As Integer, resultType As SpecialType, Optional isInvalid As Boolean = False, Optional IsVirtual As Boolean = False) As IInvocationExpression + Assert.Equal(expressionText, node.ToString()) + Dim operation As IOperation = model.GetOperation(node) + Assert.Equal(operation.Kind, OperationKind.InvocationExpression) + Assert.Equal(isInvalid, operation.IsInvalid) + Dim invocation As IInvocationExpression = DirectCast(operation, IInvocationExpression) + Assert.False(invocation.ConstantValue.HasValue) + Assert.Equal(IsVirtual, invocation.IsVirtual) + Assert.Equal(methodName, invocation.TargetMethod.Name) + Assert.Equal(argumentCount, invocation.ArgumentsInParameterOrder.Length) + Assert.Equal(argumentCount, invocation.ArgumentsInEvaluationOrder.Length) + Assert.Equal(resultType, invocation.Type.SpecialType) + + Return invocation + End Function + + + Private Shared Function GetArgument(invocation As IInvocationExpression, index As Integer) As IArgument + Dim argument As IArgument = invocation.ArgumentsInParameterOrder(index) + Assert.True(argument Is invocation.ArgumentsInEvaluationOrder(index)) + Return argument + End Function + + Private Shared Function CheckArgument(invocation As IInvocationExpression, argument As IArgument, parameterName As String, Optional isInvalid As Boolean = False, Optional allowConversions As Boolean = False) As IOperation + Assert.Equal(isInvalid, argument.IsInvalid) + If Not allowConversions Then + Assert.Null(argument.InConversion) + Assert.Null(argument.OutConversion) + End If + Assert.Equal(parameterName, argument.Parameter.Name) + Assert.True(invocation.GetArgumentMatchingParameter(argument.Parameter) Is argument) + Dim argumentValue As IOperation = argument.Value + Assert.Equal(isInvalid, argumentValue.IsInvalid) + + Return argumentValue + End Function + + Private Shared Sub CheckConstantArgument(invocation As IInvocationExpression, argument As IArgument, parameterName As String, argumentConstantValue As Integer) + Dim argumentValue As IOperation = CheckArgument(invocation, argument, parameterName) + Assert.Equal(argumentValue.Kind, OperationKind.LiteralExpression) + Assert.Equal(argumentValue.Type.SpecialType, SpecialType.System_Int32) + Assert.True(argumentValue.ConstantValue.HasValue) + Assert.Equal(argumentConstantValue, argumentValue.ConstantValue.Value) + End Sub + + Private Shared Sub CheckArgumentConversions(argument As IArgument, argumentType As SpecialType, parameterType As SpecialType) + Dim inConversion As IOperation = argument.InConversion + Assert.NotNull(inConversion) + Assert.Equal(inConversion.Kind, OperationKind.ConversionExpression) + Dim conversion As IConversionExpression = DirectCast(inConversion, IConversionExpression) + Assert.Equal(conversion.Type.SpecialType, parameterType) + Assert.Equal(conversion.Operand.Type.SpecialType, argumentType) + Dim outConversion As IOperation = argument.OutConversion + Assert.NotNull(outConversion) + Assert.Equal(outConversion.Kind, OperationKind.ConversionExpression) + conversion = DirectCast(outConversion, IConversionExpression) + Assert.Equal(conversion.Type.SpecialType, argumentType) + Assert.Equal(conversion.Operand.Type.SpecialType, parameterType) + End Sub + + Private Shared Sub CheckInstanceReference(invocationInstance As IOperation, instanceKind As InstanceReferenceKind, instanceType As String) + Assert.NotNull(invocationInstance) + Assert.Equal(invocationInstance.Kind, OperationKind.InstanceReferenceExpression) + Dim instanceReference As IInstanceReferenceExpression = DirectCast(invocationInstance, IInstanceReferenceExpression) + Assert.False(instanceReference.IsInvalid) + Assert.Equal(instanceKind, instanceReference.InstanceReferenceKind) + Assert.Equal(instanceType, instanceReference.Type.Name) + End Sub + + Private Shared Sub CheckLocalReference(reference As IOperation, localName As String, localType As String) + Assert.NotNull(reference) + Assert.Equal(reference.Kind, OperationKind.LocalReferenceExpression) + Dim localReference As ILocalReferenceExpression = DirectCast(reference, ILocalReferenceExpression) + Assert.False(localReference.IsInvalid) + Assert.Equal(localName, localReference.Local.Name) + Assert.Equal(localType, localReference.Type.Name) + Assert.False(localReference.ConstantValue.HasValue) + End Sub + + Private Shared Sub CheckPropertyReference(reference As IOperation, propertyName As String, propertyType As String) + Assert.Equal(reference.Kind, OperationKind.PropertyReferenceExpression) + Dim propertyReference As IPropertyReferenceExpression = DirectCast(reference, IPropertyReferenceExpression) + Assert.False(propertyReference.IsInvalid) + Assert.Equal(propertyName, propertyReference.Property.Name) + Assert.Equal(propertyType, propertyReference.Type.Name) + Assert.False(propertyReference.ConstantValue.HasValue) + End Sub + + Private Shared Sub CheckArrayCreation(value As IOperation, ParamArray elements As Integer()) + Assert.Equal(value.Kind, OperationKind.ArrayCreationExpression) + Assert.Equal(value.Type.TypeKind, TypeKind.Array) + Assert.Equal(DirectCast(value.Type, ArrayTypeSymbol).ElementType.SpecialType, SpecialType.System_Int32) + Assert.False(value.ConstantValue.HasValue) + Dim argumentArray As IArrayCreationExpression = DirectCast(value, IArrayCreationExpression) + Assert.Equal(argumentArray.Initializer.Kind, OperationKind.ArrayInitializer) + Dim elementValues As ImmutableArray(Of IOperation) = argumentArray.Initializer.ElementValues + Assert.Equal(elementValues.Length, elements.Length) + + For index As Integer = 0 To elements.Length - 1 + Dim elementValue As IOperation = elementValues(index) + Assert.Equal(elementValue.Kind, OperationKind.LiteralExpression) + Assert.False(elementValue.IsInvalid) + Assert.Equal(elementValue.Type.SpecialType, SpecialType.System_Int32) + Assert.True(elementValue.ConstantValue.HasValue) + Assert.Equal(elementValue.ConstantValue.Value, elements(index)) + Next + End Sub + End Class +End Namespace + diff --git a/src/Test/Utilities/Shared/Compilation/TestOperationWalker.cs b/src/Test/Utilities/Shared/Compilation/TestOperationWalker.cs index 8506775231dfc..49bf0f7ea999f 100644 --- a/src/Test/Utilities/Shared/Compilation/TestOperationWalker.cs +++ b/src/Test/Utilities/Shared/Compilation/TestOperationWalker.cs @@ -1,5 +1,6 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System.Collections.Immutable; using Microsoft.CodeAnalysis.Semantics; using Xunit; @@ -202,28 +203,31 @@ public override void VisitEndStatement(IEndStatement operation) public override void VisitInvocationExpression(IInvocationExpression operation) { - var targetMethod = operation.TargetMethod; var isVirtual = operation.IsVirtual; - // base.VisitInvocationExpression only visit operations in ArgumentsInSourceOrder + // base.VisitInvocationExpression only visits operations in ArgumentsInEvaluationOrder. + VisitArgumentsInParameterOrder(operation, operation.TargetMethod?.Parameters); + base.VisitInvocationExpression(operation); + } + + private void VisitArgumentsInParameterOrder(IHasArgumentsExpression operation, ImmutableArray? parameters) + { foreach (var argument in operation.ArgumentsInParameterOrder) { Visit(argument); } - if (targetMethod != null) + + if (parameters.HasValue) { - foreach (var parameter in targetMethod.Parameters) + foreach (var parameter in parameters.Value) { var matchingArgument = operation.GetArgumentMatchingParameter(parameter); Visit(matchingArgument); } } - - base.VisitInvocationExpression(operation); } public override void VisitArgument(IArgument operation) { - var argumentKind = operation.ArgumentKind; var parameter = operation.Parameter; base.VisitArgument(operation); @@ -331,16 +335,8 @@ public override void VisitPlaceholderExpression(IPlaceholderExpression operation public override void VisitIndexedPropertyReferenceExpression(IIndexedPropertyReferenceExpression operation) { var member = operation.Member; - var property = operation.Property; - if (property != null) - { - foreach (var parameter in property.Parameters) - { - var matchingArgument = operation.GetArgumentMatchingParameter(parameter); - Visit(matchingArgument); - } - } - + // base.VisitPropertyReferenceExpression only visits operations in ArgumentsInEvaluationOrder. + VisitArgumentsInParameterOrder(operation, operation.Property?.Parameters); base.VisitIndexedPropertyReferenceExpression(operation); } @@ -429,16 +425,8 @@ public override void VisitAddressOfExpression(IAddressOfExpression operation) public override void VisitObjectCreationExpression(IObjectCreationExpression operation) { - var ctor = operation.Constructor; - if (ctor != null) - { - foreach (var parameter in ctor.Parameters) - { - var matchingArgument = operation.GetArgumentMatchingParameter(parameter); - Visit(matchingArgument); - } - } - + // base.VisitObjectCreationExpression only visits operations in ArgumentsInEvaluationOrder. + VisitArgumentsInParameterOrder(operation, operation.Constructor?.Parameters); base.VisitObjectCreationExpression(operation); }