private static ArgumentAnalysisResult AnalyzeArguments( Symbol symbol, AnalyzedArguments arguments, bool isMethodGroupConversion, bool expanded) { Debug.Assert((object)symbol != null); Debug.Assert(arguments != null); ImmutableArray <ParameterSymbol> parameters = symbol.GetParameters(); bool isVararg = symbol.GetIsVararg(); // The easy out is that we have no named arguments and are in normal form. if (!expanded && arguments.Names.Count == 0) { return(AnalyzeArgumentsForNormalFormNoNamedArguments(parameters, arguments, isMethodGroupConversion, isVararg)); } // We simulate an additional non-optional parameter for a vararg method. int argumentCount = arguments.Arguments.Count; int[] parametersPositions = null; int? unmatchedArgumentIndex = null; bool? unmatchedArgumentIsNamed = null; // Try to map every argument position to a formal parameter position: bool seenNamedParams = false; bool seenOutOfPositionNamedArgument = false; bool isValidParams = IsValidParams(symbol); for (int argumentPosition = 0; argumentPosition < argumentCount; ++argumentPosition) { // We use -1 as a sentinel to mean that no parameter was found that corresponded to this argument. bool isNamedArgument; int parameterPosition = CorrespondsToAnyParameter(parameters, expanded, arguments, argumentPosition, isValidParams, isVararg, out isNamedArgument, ref seenNamedParams, ref seenOutOfPositionNamedArgument) ?? -1; if (parameterPosition == -1 && unmatchedArgumentIndex == null) { unmatchedArgumentIndex = argumentPosition; unmatchedArgumentIsNamed = isNamedArgument; } if (parameterPosition != argumentPosition && parametersPositions == null) { parametersPositions = new int[argumentCount]; for (int i = 0; i < argumentPosition; ++i) { parametersPositions[i] = i; } } if (parametersPositions != null) { parametersPositions[argumentPosition] = parameterPosition; } } ParameterMap argsToParameters = new ParameterMap(parametersPositions, argumentCount); // We have analyzed every argument and tried to make it correspond to a particular parameter. // We must now answer the following questions: // // (1) Is there any named argument used out-of-position and followed by unnamed arguments? // (2) Is there any argument without a corresponding parameter? // (3) Was there any named argument that specified a parameter that was already // supplied with a positional parameter? // (4) Is there any non-optional parameter without a corresponding argument? // (5) Is there any named argument that were specified twice? // // If the answer to any of these questions is "yes" then the method is not applicable. // It is possible that the answer to any number of these questions is "yes", and so // we must decide which error condition to prioritize when reporting the error, // should we need to report why a given method is not applicable. We prioritize // them in the given order. // (1) Is there any named argument used out-of-position and followed by unnamed arguments? int?badNonTrailingNamedArgument = CheckForBadNonTrailingNamedArgument(arguments, argsToParameters, parameters); if (badNonTrailingNamedArgument != null) { return(ArgumentAnalysisResult.BadNonTrailingNamedArgument(badNonTrailingNamedArgument.Value)); } // (2) Is there any argument without a corresponding parameter? if (unmatchedArgumentIndex != null) { if (unmatchedArgumentIsNamed.Value) { return(ArgumentAnalysisResult.NoCorrespondingNamedParameter(unmatchedArgumentIndex.Value)); } else { return(ArgumentAnalysisResult.NoCorrespondingParameter(unmatchedArgumentIndex.Value)); } } // (3) was there any named argument that specified a parameter that was already // supplied with a positional parameter? int?nameUsedForPositional = NameUsedForPositional(arguments, argsToParameters); if (nameUsedForPositional != null) { return(ArgumentAnalysisResult.NameUsedForPositional(nameUsedForPositional.Value)); } // (4) Is there any non-optional parameter without a corresponding argument? int?requiredParameterMissing = CheckForMissingRequiredParameter(argsToParameters, parameters, isMethodGroupConversion, expanded); if (requiredParameterMissing != null) { return(ArgumentAnalysisResult.RequiredParameterMissing(requiredParameterMissing.Value)); } // __arglist cannot be used with named arguments (as it doesn't have a name) if (arguments.Names.Any() && arguments.Names.Last() != null && isVararg) { return(ArgumentAnalysisResult.RequiredParameterMissing(parameters.Length)); } // (5) Is there any named argument that were specified twice? int?duplicateNamedArgument = CheckForDuplicateNamedArgument(arguments); if (duplicateNamedArgument != null) { return(ArgumentAnalysisResult.DuplicateNamedArgument(duplicateNamedArgument.Value)); } // We're good; this one might be applicable in the given form. return(expanded ? ArgumentAnalysisResult.ExpandedForm(argsToParameters.ToImmutableArray()) : ArgumentAnalysisResult.NormalForm(argsToParameters.ToImmutableArray())); }
private static ArgumentAnalysisResult AnalyzeArguments( Symbol symbol, AnalyzedArguments arguments, bool isMethodGroupConversion, bool expanded) { Debug.Assert((object)symbol != null); Debug.Assert(arguments != null); ImmutableArray<ParameterSymbol> parameters = symbol.GetParameters(); // The easy out is that we have no named arguments and are in normal form. if (!expanded && arguments.Names.Count == 0) { return AnalyzeArgumentsForNormalFormNoNamedArguments(parameters, arguments, isMethodGroupConversion, symbol.GetIsVararg()); } // We simulate an additional non-optional parameter for a vararg method. int argumentCount = arguments.Arguments.Count; int[] parametersPositions = null; int? unmatchedArgumentIndex = null; bool? unmatchedArgumentIsNamed = null; // Try to map every argument position to a formal parameter position: for (int argumentPosition = 0; argumentPosition < argumentCount; ++argumentPosition) { // We use -1 as a sentinel to mean that no parameter was found that corresponded to this argument. bool isNamedArgument; int parameterPosition = CorrespondsToAnyParameter(parameters, expanded, arguments, argumentPosition, out isNamedArgument) ?? -1; if (parameterPosition == -1 && unmatchedArgumentIndex == null) { unmatchedArgumentIndex = argumentPosition; unmatchedArgumentIsNamed = isNamedArgument; } if (parameterPosition != argumentPosition && parametersPositions == null) { parametersPositions = new int[argumentCount]; for (int i = 0; i < argumentPosition; ++i) { parametersPositions[i] = i; } } if (parametersPositions != null) { parametersPositions[argumentPosition] = parameterPosition; } } ParameterMap argsToParameters = new ParameterMap(parametersPositions, argumentCount); // We have analyzed every argument and tried to make it correspond to a particular parameter. // There are now three questions we must answer: // // (1) Is there any argument without a corresponding parameter? // (2) was there any named argument that specified a parameter that was already // supplied with a positional parameter? // (3) Is there any non-optional parameter without a corresponding argument? // // If the answer to any of these questions is "yes" then the method is not applicable. // It is possible that the answer to any number of these questions is "yes", and so // we must decide which error condition to prioritize when reporting the error, // should we need to report why a given method is not applicable. We prioritize // them in the given order. // (1) Is there any argument without a corresponding parameter? if (unmatchedArgumentIndex != null) { if (unmatchedArgumentIsNamed.Value) { return ArgumentAnalysisResult.NoCorrespondingNamedParameter(unmatchedArgumentIndex.Value); } else { return ArgumentAnalysisResult.NoCorrespondingParameter(unmatchedArgumentIndex.Value); } } // (2) was there any named argument that specified a parameter that was already // supplied with a positional parameter? int? nameUsedForPositional = NameUsedForPositional(arguments, argsToParameters); if (nameUsedForPositional != null) { return ArgumentAnalysisResult.NameUsedForPositional(nameUsedForPositional.Value); } // (3) Is there any non-optional parameter without a corresponding argument? int? requiredParameterMissing = CheckForMissingRequiredParameter(argsToParameters, parameters, isMethodGroupConversion, expanded); if (requiredParameterMissing != null) { return ArgumentAnalysisResult.RequiredParameterMissing(requiredParameterMissing.Value); } // __arglist cannot be used with named arguments (as it doesn't have a name) if (arguments.Names.Count != 0 && symbol.GetIsVararg()) { return ArgumentAnalysisResult.RequiredParameterMissing(parameters.Length); } // We're good; this one might be applicable in the given form. return expanded ? ArgumentAnalysisResult.ExpandedForm(argsToParameters.ToImmutableArray()) : ArgumentAnalysisResult.NormalForm(argsToParameters.ToImmutableArray()); }
/// <remarks> /// Similar to SymbolExtensions.GetParameters, but returns empty for unsupported symbols /// and handles delegates. /// </remarks> private static ImmutableArray<ParameterSymbol> GetParameters(Symbol symbol) { switch (symbol.Kind) { case SymbolKind.NamedType: MethodSymbol delegateInvoke = ((NamedTypeSymbol)symbol).DelegateInvokeMethod; if ((object)delegateInvoke != null) { return delegateInvoke.Parameters; } break; case SymbolKind.Method: case SymbolKind.Property: case SymbolKind.Event: return symbol.GetParameters(); } return ImmutableArray<ParameterSymbol>.Empty; }
/// <summary> /// Rewrites arguments of an invocation according to the receiving method or indexer. /// It is assumed that each argument has already been lowered, but we may need /// additional rewriting for the arguments, such as generating a params array, re-ordering /// arguments based on <paramref name="argsToParamsOpt"/> map, inserting arguments for optional parameters, etc. /// <paramref name="optionalParametersMethod"/> is the method used for values of any optional parameters. /// For indexers, this method must be an accessor, and for methods it must be the method /// itself. <paramref name="optionalParametersMethod"/> is needed for indexers since getter and setter /// may have distinct optional parameter values. /// </summary> private ImmutableArray<BoundExpression> MakeArguments( SyntaxNode syntax, ImmutableArray<BoundExpression> rewrittenArguments, Symbol methodOrIndexer, MethodSymbol optionalParametersMethod, bool expanded, ImmutableArray<int> argsToParamsOpt, ref ImmutableArray<RefKind> argumentRefKindsOpt, out ImmutableArray<LocalSymbol> temps, bool invokedAsExtensionMethod = false, ThreeState enableCallerInfo = ThreeState.Unknown) { // Either the methodOrIndexer is a property, in which case the method used // for optional parameters is an accessor of that property (or an overridden // property), or the methodOrIndexer is used for optional parameters directly. Debug.Assert(((methodOrIndexer.Kind == SymbolKind.Property) && optionalParametersMethod.IsAccessor()) || ReferenceEquals(methodOrIndexer, optionalParametersMethod)); // We need to do a fancy rewrite under the following circumstances: // (1) a params array is being used; we need to generate the array. // (2) there were named arguments that reordered the arguments; we might // have to generate temporaries to ensure that the arguments are // evaluated in source code order, not the actual call order. // (3) there were optional parameters that had no corresponding arguments. // // If none of those are the case then we can just take an early out. // An applicable "vararg" method could not possibly be applicable in its expanded // form, and cannot possibly have named arguments or used optional parameters, // because the __arglist() argument has to be positional and in the last position. if (methodOrIndexer.GetIsVararg()) { Debug.Assert(rewrittenArguments.Length == methodOrIndexer.GetParameterCount() + 1); Debug.Assert(argsToParamsOpt.IsDefault); Debug.Assert(!expanded); temps = default(ImmutableArray<LocalSymbol>); return rewrittenArguments; } var receiverNamedType = invokedAsExtensionMethod ? ((MethodSymbol)methodOrIndexer).Parameters[0].Type as NamedTypeSymbol : methodOrIndexer.ContainingType; bool isComReceiver = (object)receiverNamedType != null && receiverNamedType.IsComImport; if (rewrittenArguments.Length == methodOrIndexer.GetParameterCount() && argsToParamsOpt.IsDefault && !expanded && !isComReceiver) { temps = default(ImmutableArray<LocalSymbol>); return rewrittenArguments; } // We have: // * a list of arguments, already converted to their proper types, // in source code order. Some optional arguments might be missing. // * a map showing which parameter each argument corresponds to. If // this is null, then the argument to parameter mapping is one-to-one. // * the ref kind of each argument, in source code order. That is, whether // the argument was marked as ref, out, or value (neither). // * a method symbol. // * whether the call is expanded or normal form. // We rewrite the call so that: // * if in its expanded form, we create the params array. // * if the call requires reordering of arguments because of named arguments, temporaries are generated as needed // Doing this transformation can move around refness in interesting ways. For example, consider // // A().M(y : ref B()[C()], x : out D()); // // This will be created as a call with receiver A(), symbol M, argument list ( B()[C()], D() ), // name list ( y, x ) and ref list ( ref, out ). We can rewrite this into temporaries: // // A().M( // seq ( ref int temp_y = ref B()[C()], out D() ), // temp_y ); // // Now we have a call with receiver A(), symbol M, argument list as shown, no name list, // and ref list ( out, value ). We do not want to pass a *ref* to temp_y; the temporary // storage is not the thing being ref'd! We want to pass the *value* of temp_y, which // *contains* a reference. // We attempt to minimize the number of temporaries required. Arguments which neither // produce nor observe a side effect can be placed into their proper position without // recourse to a temporary. For example: // // Where(predicate: x=>x.Length!=0, sequence: S()) // // can be rewritten without any temporaries because the conversion from lambda to // delegate does not produce any side effect that could be observed by S(). // // By contrast: // // Foo(z: this.p, y: this.Q(), x: (object)10) // // The boxing of 10 can be reordered, but the fetch of this.p has to happen before the // call to this.Q() because the call could change the value of this.p. // // We start by binding everything that is not obviously reorderable as a temporary, and // then run an optimizer to remove unnecessary temporaries. ImmutableArray<ParameterSymbol> parameters = methodOrIndexer.GetParameters(); BoundExpression[] actualArguments = new BoundExpression[parameters.Length]; // The actual arguments that will be passed; one actual argument per formal parameter. ArrayBuilder<BoundAssignmentOperator> storesToTemps = ArrayBuilder<BoundAssignmentOperator>.GetInstance(rewrittenArguments.Length); ArrayBuilder<RefKind> refKinds = ArrayBuilder<RefKind>.GetInstance(parameters.Length, RefKind.None); // Step one: Store everything that is non-trivial into a temporary; record the // stores in storesToTemps and make the actual argument a reference to the temp. // Do not yet attempt to deal with params arrays or optional arguments. BuildStoresToTemps(expanded, argsToParamsOpt, argumentRefKindsOpt, rewrittenArguments, actualArguments, refKinds, storesToTemps); // all the formal arguments, except missing optionals, are now in place. // Optimize away unnecessary temporaries. // Necessary temporaries have their store instructions merged into the appropriate // argument expression. ArrayBuilder<LocalSymbol> temporariesBuilder = ArrayBuilder<LocalSymbol>.GetInstance(); OptimizeTemporaries(actualArguments, refKinds, storesToTemps, temporariesBuilder); // Step two: If we have a params array, build the array and fill in the argument. if (expanded) { actualArguments[actualArguments.Length - 1] = BuildParamsArray(syntax, methodOrIndexer, argsToParamsOpt, rewrittenArguments, parameters, actualArguments[actualArguments.Length - 1]); } // Step three: Now fill in the optional arguments. InsertMissingOptionalArguments(syntax, optionalParametersMethod.Parameters, actualArguments, enableCallerInfo); if (isComReceiver) { RewriteArgumentsForComCall(parameters, actualArguments, refKinds, temporariesBuilder); } temps = temporariesBuilder.ToImmutableAndFree(); storesToTemps.Free(); // * The refkind map is now filled out to match the arguments. // * The list of parameter names is now null because the arguments have been reordered. // * The args-to-params map is now null because every argument exactly matches its parameter. // * The call is no longer in its expanded form. argumentRefKindsOpt = GetRefKindsOrNull(refKinds); refKinds.Free(); return actualArguments.AsImmutableOrNull(); }
/// <summary> /// Rewrites arguments of an invocation according to the receiving method or indexer. /// It is assumed that the each argument has already been lowered, but we may need /// additional rewriting for the arguments, such as generating a params array, re-ordering /// arguments based on argsToParamsOpt map, inserting arguments for optional parameters, etc. /// 'optionalParametersMethod' is the method used for values of any optional parameters. /// For indexers, this method must be an accessor, and for methods it must be the method /// itself. 'optionalParametersMethod' is needed for indexers since the getter and setter /// may have distinct optional parameter values. /// </summary> private ImmutableArray <BoundExpression> MakeArguments( CSharpSyntaxNode syntax, ImmutableArray <BoundExpression> rewrittenArguments, Symbol methodOrIndexer, MethodSymbol optionalParametersMethod, bool expanded, ImmutableArray <int> argsToParamsOpt, ref ImmutableArray <RefKind> argumentRefKindsOpt, out ImmutableArray <LocalSymbol> temps, bool invokedAsExtensionMethod = false) { // Either the methodOrIndexer is a property, in which case the method used // for optional parameters is an accessor of that property (or an overridden // property), or the methodOrIndexer is used for optional parameters directly. Debug.Assert(((methodOrIndexer.Kind == SymbolKind.Property) && optionalParametersMethod.IsAccessor()) || ReferenceEquals(methodOrIndexer, optionalParametersMethod)); // We need to do a fancy rewrite under the following circumstances: // (1) a params array is being used; we need to generate the array. // (2) there were named arguments that reordered the arguments; we might // have to generate temporaries to ensure that the arguments are // evaluated in source code order, not the actual call order. // (3) there were optional parameters that had no corresponding arguments. // // If none of those are the case then we can just take an early out. // An applicable "vararg" method could not possibly be applicable in its expanded // form, and cannot possibly have named arguments or used optional parameters, // because the __arglist() argument has to be positional and in the last position. if (methodOrIndexer.GetIsVararg()) { Debug.Assert(rewrittenArguments.Length == methodOrIndexer.GetParameterCount() + 1); Debug.Assert(argsToParamsOpt.IsDefault); Debug.Assert(!expanded); temps = default(ImmutableArray <LocalSymbol>); return(rewrittenArguments); } var receiverNamedType = invokedAsExtensionMethod ? ((MethodSymbol)methodOrIndexer).Parameters[0].Type as NamedTypeSymbol : methodOrIndexer.ContainingType; bool isComReceiver = (object)receiverNamedType != null && receiverNamedType.IsComImport; if (rewrittenArguments.Length == methodOrIndexer.GetParameterCount() && argsToParamsOpt.IsDefault && !expanded && !isComReceiver) { temps = default(ImmutableArray <LocalSymbol>); return(rewrittenArguments); } // We have: // * a list of arguments, already converted to their proper types, // in source code order. Some optional arguments might be missing. // * a map showing which parameter each argument corresponds to. If // this is null, then the argument to parameter mapping is one-to-one. // * the ref kind of each argument, in source code order. That is, whether // the argument was marked as ref, out, or value (neither). // * a method symbol. // * whether the call is expanded or normal form. // We rewrite the call so that: // * if in its expanded form, we create the params array. // * if the call requires reordering of arguments because of named arguments, temporaries are generated as needed // Doing this transformation can move around refness in interesting ways. For example, consider // // A().M(y : ref B()[C()], x : out D()); // // This will be created as a call with receiver A(), symbol M, argument list ( B()[C()], D() ), // name list ( y, x ) and ref list ( ref, out ). We can rewrite this into temporaries: // // A().M( // seq ( ref int temp_y = ref B()[C()], out D() ), // temp_y ); // // Now we have a call with receiver A(), symbol M, argument list as shown, no name list, // and ref list ( out, value ). We do not want to pass a *ref* to temp_y; the temporary // storage is not the thing being ref'd! We want to pass the *value* of temp_y, which // *contains* a reference. // We attempt to minimize the number of temporaries required. Arguments which neither // produce nor observe a side effect can be placed into their proper position without // recourse to a temporary. For example: // // Where(predicate: x=>x.Length!=0, sequence: S()) // // can be rewritten without any temporaries because the conversion from lambda to // delegate does not produce any side effect that could be observed by S(). // // By contrast: // // Foo(z: this.p, y: this.Q(), x: (object)10) // // The boxing of 10 can be reordered, but the fetch of this.p has to happen before the // call to this.Q() because the call could change the value of this.p. // // We start by binding everything that is not obviously reorderable as a temporary, and // then run an optimizer to remove unnecessary temporaries. ImmutableArray <ParameterSymbol> parameters = methodOrIndexer.GetParameters(); BoundExpression[] actualArguments = new BoundExpression[parameters.Length]; // The actual arguments that will be passed; one actual argument per formal parameter. ArrayBuilder <BoundAssignmentOperator> storesToTemps = ArrayBuilder <BoundAssignmentOperator> .GetInstance(rewrittenArguments.Length); ArrayBuilder <RefKind> refKinds = ArrayBuilder <RefKind> .GetInstance(parameters.Length, RefKind.None); // Step one: Store everything that is non-trivial into a temporary; record the // stores in storesToTemps and make the actual argument a reference to the temp. // Do not yet attempt to deal with params arrays or optional arguments. BuildStoresToTemps(expanded, argsToParamsOpt, argumentRefKindsOpt, rewrittenArguments, actualArguments, refKinds, storesToTemps); // Step two: If we have a params array, build the array and fill in the argument. if (expanded) { actualArguments[actualArguments.Length - 1] = BuildParamsArray(syntax, methodOrIndexer, argsToParamsOpt, rewrittenArguments, parameters, actualArguments[actualArguments.Length - 1]); } // Step three: Now fill in the optional arguments. InsertMissingOptionalArguments(syntax, optionalParametersMethod.Parameters, actualArguments); // Step four: all the arguments are now in place. Optimize away unnecessary temporaries. // Necessary temporaries have their store instructions merged into the appropriate // argument expression. ArrayBuilder <LocalSymbol> temporariesBuilder = ArrayBuilder <LocalSymbol> .GetInstance(); OptimizeTemporaries(actualArguments, refKinds, storesToTemps, temporariesBuilder); if (isComReceiver) { RewriteArgumentsForComCall(parameters, actualArguments, refKinds, temporariesBuilder); } temps = temporariesBuilder.ToImmutableAndFree(); storesToTemps.Free(); // * The refkind map is now filled out to match the arguments. // * The list of parameter names is now null because the arguments have been reordered. // * The args-to-params map is now null because every argument exactly matches its parameter. // * The call is no longer in its expanded form. argumentRefKindsOpt = GetRefKindsOrNull(refKinds); refKinds.Free(); return(actualArguments.AsImmutableOrNull()); }