|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#nullable disable
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp
{
/// <summary>
/// This portion of the binder converts an <see cref="ExpressionSyntax"/> into a <see cref="BoundExpression"/>.
/// </summary>
internal partial class Binder
{
private BoundExpression BindMethodGroup(ExpressionSyntax node, bool invoked, bool indexed, BindingDiagnosticBag diagnostics)
{
switch (node.Kind())
{
case SyntaxKind.IdentifierName:
case SyntaxKind.GenericName:
return BindIdentifier((SimpleNameSyntax)node, invoked, indexed, diagnostics);
case SyntaxKind.SimpleMemberAccessExpression:
case SyntaxKind.PointerMemberAccessExpression:
return BindMemberAccess((MemberAccessExpressionSyntax)node, invoked, indexed, diagnostics);
case SyntaxKind.ParenthesizedExpression:
return BindMethodGroup(((ParenthesizedExpressionSyntax)node).Expression, invoked: false, indexed: false, diagnostics: diagnostics);
default:
return BindExpression(node, diagnostics, invoked, indexed);
}
}
private static ImmutableArray<MethodSymbol> GetOriginalMethods(OverloadResolutionResult<MethodSymbol> overloadResolutionResult)
{
// If overload resolution has failed then we want to stash away the original methods that we
// considered so that the IDE can display tooltips or other information about them.
// However, if a method group contained a generic method that was type inferred then
// the IDE wants information about the *inferred* method, not the original unconstructed
// generic method.
if (overloadResolutionResult == null)
{
return ImmutableArray<MethodSymbol>.Empty;
}
var builder = ArrayBuilder<MethodSymbol>.GetInstance();
foreach (var result in overloadResolutionResult.Results)
{
builder.Add(result.Member);
}
return builder.ToImmutableAndFree();
}
#nullable enable
/// <summary>
/// Helper method to create a synthesized method invocation expression.
/// </summary>
/// <param name="node">Syntax Node.</param>
/// <param name="receiver">Receiver for the method call.</param>
/// <param name="methodName">Method to be invoked on the receiver.</param>
/// <param name="args">Arguments to the method call.</param>
/// <param name="diagnostics">Diagnostics.</param>
/// <param name="typeArgsSyntax">Optional type arguments syntax.</param>
/// <param name="typeArgs">Optional type arguments.</param>
/// <param name="queryClause">The syntax for the query clause generating this invocation expression, if any.</param>
/// <param name="allowFieldsAndProperties">True to allow invocation of fields and properties of delegate type. Only methods are allowed otherwise.</param>
/// <param name="ignoreNormalFormIfHasValidParamsParameter">True to prevent selecting a params method in unexpanded form.</param>
/// <returns>Synthesized method invocation expression.</returns>
internal BoundExpression MakeInvocationExpression(
SyntaxNode node,
BoundExpression receiver,
string methodName,
ImmutableArray<BoundExpression> args,
BindingDiagnosticBag diagnostics,
SeparatedSyntaxList<TypeSyntax> typeArgsSyntax = default(SeparatedSyntaxList<TypeSyntax>),
ImmutableArray<TypeWithAnnotations> typeArgs = default(ImmutableArray<TypeWithAnnotations>),
ImmutableArray<(string Name, Location Location)?> names = default,
CSharpSyntaxNode? queryClause = null,
bool allowFieldsAndProperties = false,
bool ignoreNormalFormIfHasValidParamsParameter = false,
bool searchExtensionMethodsIfNecessary = true,
bool disallowExpandedNonArrayParams = false)
{
//
// !!! ATTENTION !!!
//
// In terms of errors relevant for HasCollectionExpressionApplicableAddMethod check
// this function should be kept in sync with local function
// HasCollectionExpressionApplicableAddMethod.makeInvocationExpression
//
Debug.Assert(receiver != null);
Debug.Assert(names.IsDefault || names.Length == args.Length);
receiver = BindToNaturalType(receiver, diagnostics);
var boundExpression = BindInstanceMemberAccess(node, node, receiver, methodName, typeArgs.NullToEmpty().Length, typeArgsSyntax, typeArgs, invoked: true, indexed: false, diagnostics, searchExtensionMethodsIfNecessary);
// The other consumers of this helper (await and collection initializers) require the target member to be a method.
if (!allowFieldsAndProperties && (boundExpression.Kind == BoundKind.FieldAccess || boundExpression.Kind == BoundKind.PropertyAccess))
{
ReportMakeInvocationExpressionBadMemberKind(node, methodName, boundExpression, diagnostics);
Symbol symbol;
if (boundExpression.Kind == BoundKind.FieldAccess)
{
symbol = ((BoundFieldAccess)boundExpression).FieldSymbol;
}
else
{
symbol = ((BoundPropertyAccess)boundExpression).PropertySymbol;
}
return BadExpression(node, LookupResultKind.Empty, ImmutableArray.Create(symbol), args.Add(receiver), wasCompilerGenerated: true);
}
Debug.Assert(allowFieldsAndProperties || boundExpression.Kind is (BoundKind.MethodGroup or BoundKind.BadExpression));
boundExpression = CheckValue(boundExpression, BindValueKind.RValueOrMethodGroup, diagnostics);
boundExpression.WasCompilerGenerated = true;
var analyzedArguments = AnalyzedArguments.GetInstance();
Debug.Assert(!args.Any(static e => e.Kind == BoundKind.OutVariablePendingInference ||
e.Kind == BoundKind.OutDeconstructVarPendingInference ||
e.Kind == BoundKind.DiscardExpression && !e.HasExpressionType()));
analyzedArguments.Arguments.AddRange(args);
if (!names.IsDefault)
{
analyzedArguments.Names.AddRange(names);
}
BoundExpression result = BindInvocationExpression(
node, node, methodName, boundExpression, analyzedArguments, diagnostics, queryClause,
ignoreNormalFormIfHasValidParamsParameter: ignoreNormalFormIfHasValidParamsParameter,
disallowExpandedNonArrayParams: disallowExpandedNonArrayParams);
// Query operator can't be called dynamically.
if (queryClause != null && result.Kind == BoundKind.DynamicInvocation)
{
// the error has already been reported by BindInvocationExpression
Debug.Assert(diagnostics.DiagnosticBag is null || diagnostics.HasAnyErrors());
result = CreateBadCall(node, boundExpression, LookupResultKind.Viable, analyzedArguments);
}
result.WasCompilerGenerated = true;
analyzedArguments.Free();
return result;
}
private static void ReportMakeInvocationExpressionBadMemberKind(SyntaxNode node, string methodName, BoundExpression boundExpression, BindingDiagnosticBag diagnostics)
{
MessageID msgId;
if (boundExpression.Kind == BoundKind.FieldAccess)
{
msgId = MessageID.IDS_SK_FIELD;
}
else
{
msgId = MessageID.IDS_SK_PROPERTY;
}
diagnostics.Add(
ErrorCode.ERR_BadSKknown,
node.Location,
methodName,
msgId.Localize(),
MessageID.IDS_SK_METHOD.Localize());
}
#nullable disable
/// <summary>
/// Bind an expression as a method invocation.
/// </summary>
private BoundExpression BindInvocationExpression(
InvocationExpressionSyntax node,
BindingDiagnosticBag diagnostics)
{
BoundExpression result;
if (TryBindNameofOperator(node, diagnostics, out result))
{
return result; // all of the binding is done by BindNameofOperator
}
// M(__arglist()) is legal, but M(__arglist(__arglist()) is not!
bool isArglist = node.Expression.Kind() == SyntaxKind.ArgListExpression;
AnalyzedArguments analyzedArguments = AnalyzedArguments.GetInstance();
if (isArglist)
{
BindArgumentsAndNames(node.ArgumentList, diagnostics, analyzedArguments, allowArglist: false);
result = BindArgListOperator(node, diagnostics, analyzedArguments);
}
else if (receiverIsInvocation(node, out InvocationExpressionSyntax nested))
{
var invocations = ArrayBuilder<InvocationExpressionSyntax>.GetInstance();
invocations.Push(node);
node = nested;
while (receiverIsInvocation(node, out nested))
{
invocations.Push(node);
node = nested;
}
BoundExpression boundExpression = BindMethodGroup(node.Expression, invoked: true, indexed: false, diagnostics: diagnostics);
while (true)
{
result = bindArgumentsAndInvocation(node, boundExpression, analyzedArguments, diagnostics);
nested = node;
if (!invocations.TryPop(out node))
{
break;
}
Debug.Assert(node.Expression.Kind() is SyntaxKind.SimpleMemberAccessExpression);
var memberAccess = (MemberAccessExpressionSyntax)node.Expression;
analyzedArguments.Clear();
CheckContextForPointerTypes(nested, diagnostics, result); // BindExpression does this after calling BindExpressionInternal
boundExpression = BindMemberAccessWithBoundLeft(memberAccess, result, memberAccess.Name, memberAccess.OperatorToken, invoked: true, indexed: false, diagnostics);
}
invocations.Free();
}
else
{
BoundExpression boundExpression = BindMethodGroup(node.Expression, invoked: true, indexed: false, diagnostics: diagnostics);
result = bindArgumentsAndInvocation(node, boundExpression, analyzedArguments, diagnostics);
}
analyzedArguments.Free();
return result;
BoundExpression bindArgumentsAndInvocation(InvocationExpressionSyntax node, BoundExpression boundExpression, AnalyzedArguments analyzedArguments, BindingDiagnosticBag diagnostics)
{
boundExpression = CheckValue(boundExpression, BindValueKind.RValueOrMethodGroup, diagnostics);
string name = boundExpression.Kind == BoundKind.MethodGroup ? GetName(node.Expression) : null;
BindArgumentsAndNames(node.ArgumentList, diagnostics, analyzedArguments, allowArglist: true);
return BindInvocationExpression(node, node.Expression, name, boundExpression, analyzedArguments, diagnostics);
}
static bool receiverIsInvocation(InvocationExpressionSyntax node, out InvocationExpressionSyntax nested)
{
if (node.Expression is MemberAccessExpressionSyntax { Expression: InvocationExpressionSyntax receiver, RawKind: (int)SyntaxKind.SimpleMemberAccessExpression } && !receiver.MayBeNameofOperator())
{
nested = receiver;
return true;
}
nested = null;
return false;
}
}
private BoundExpression BindArgListOperator(InvocationExpressionSyntax node, BindingDiagnosticBag diagnostics, AnalyzedArguments analyzedArguments)
{
bool hasErrors = analyzedArguments.HasErrors;
// We allow names, oddly enough; M(__arglist(x : 123)) is legal. We just ignore them.
TypeSymbol objType = GetSpecialType(SpecialType.System_Object, diagnostics, node);
for (int i = 0; i < analyzedArguments.Arguments.Count; ++i)
{
BoundExpression argument = analyzedArguments.Arguments[i];
if (argument.Kind == BoundKind.OutVariablePendingInference)
{
analyzedArguments.Arguments[i] = ((OutVariablePendingInference)argument).FailInference(this, diagnostics);
}
else if ((object)argument.Type == null && !argument.HasAnyErrors)
{
// We are going to need every argument in here to have a type. If we don't have one,
// try converting it to object. We'll either succeed (if it is a null literal)
// or fail with a good error message.
//
// Note that the native compiler converts null literals to object, and for everything
// else it either crashes, or produces nonsense code. Roslyn improves upon this considerably.
analyzedArguments.Arguments[i] = GenerateConversionForAssignment(objType, argument, diagnostics);
}
else if (argument.Type.IsVoidType())
{
Error(diagnostics, ErrorCode.ERR_CantUseVoidInArglist, argument.Syntax);
hasErrors = true;
}
else if (analyzedArguments.RefKind(i) == RefKind.None)
{
analyzedArguments.Arguments[i] = BindToNaturalType(analyzedArguments.Arguments[i], diagnostics);
}
switch (analyzedArguments.RefKind(i))
{
case RefKind.None:
case RefKind.Ref:
break;
default:
// Disallow "in" or "out" arguments
Error(diagnostics, ErrorCode.ERR_CantUseInOrOutInArglist, argument.Syntax);
hasErrors = true;
break;
}
}
ImmutableArray<BoundExpression> arguments = analyzedArguments.Arguments.ToImmutable();
ImmutableArray<RefKind> refKinds = analyzedArguments.RefKinds.ToImmutableOrNull();
return new BoundArgListOperator(node, arguments, refKinds, null, hasErrors);
}
/// <summary>
/// Bind an expression as a method invocation.
/// </summary>
private BoundExpression BindInvocationExpression(
SyntaxNode node,
SyntaxNode expression,
string methodName,
BoundExpression boundExpression,
AnalyzedArguments analyzedArguments,
BindingDiagnosticBag diagnostics,
CSharpSyntaxNode queryClause = null,
bool ignoreNormalFormIfHasValidParamsParameter = false,
bool disallowExpandedNonArrayParams = false)
{
//
// !!! ATTENTION !!!
//
// In terms of errors relevant for HasCollectionExpressionApplicableAddMethod check
// this function should be kept in sync with local function
// HasCollectionExpressionApplicableAddMethod.bindInvocationExpression
//
BoundExpression result;
NamedTypeSymbol delegateType;
if ((object)boundExpression.Type != null && boundExpression.Type.IsDynamic())
{
// Either we have a dynamic method group invocation "dyn.M(...)" or
// a dynamic delegate invocation "dyn(...)" -- either way, bind it as a dynamic
// invocation and let the lowering pass sort it out.
ReportSuppressionIfNeeded(boundExpression, diagnostics);
result = BindDynamicInvocation(node, boundExpression, analyzedArguments, ImmutableArray<MethodSymbol>.Empty, diagnostics, queryClause);
}
else if (boundExpression.Kind == BoundKind.MethodGroup)
{
ReportSuppressionIfNeeded(boundExpression, diagnostics);
result = BindMethodGroupInvocation(
node, expression, methodName, (BoundMethodGroup)boundExpression, analyzedArguments,
diagnostics, queryClause,
ignoreNormalFormIfHasValidParamsParameter: ignoreNormalFormIfHasValidParamsParameter,
disallowExpandedNonArrayParams: disallowExpandedNonArrayParams,
anyApplicableCandidates: out _);
}
else if ((object)(delegateType = GetDelegateType(boundExpression)) != null)
{
if (ReportDelegateInvokeUseSiteDiagnostic(diagnostics, delegateType, node: node))
{
return CreateBadCall(node, boundExpression, LookupResultKind.Viable, analyzedArguments);
}
result = BindDelegateInvocation(node, expression, methodName, boundExpression, analyzedArguments, diagnostics, queryClause, delegateType);
}
else if (boundExpression.Type?.Kind == SymbolKind.FunctionPointerType)
{
ReportSuppressionIfNeeded(boundExpression, diagnostics);
result = BindFunctionPointerInvocation(node, boundExpression, analyzedArguments, diagnostics);
}
else
{
if (!boundExpression.HasAnyErrors)
{
diagnostics.Add(new CSDiagnosticInfo(ErrorCode.ERR_MethodNameExpected), expression.Location);
}
result = CreateBadCall(node, boundExpression, LookupResultKind.NotInvocable, analyzedArguments);
}
CheckRestrictedTypeReceiver(result, this.Compilation, diagnostics);
return result;
}
#nullable enable
private BoundExpression BindDynamicInvocation(
SyntaxNode node,
BoundExpression expression,
AnalyzedArguments arguments,
ImmutableArray<MethodSymbol> applicableMethods,
BindingDiagnosticBag diagnostics,
CSharpSyntaxNode queryClause)
{
CheckNamedArgumentsForDynamicInvocation(arguments, diagnostics);
bool hasErrors = false;
BoundExpression? receiver;
if (expression.Kind == BoundKind.MethodGroup)
{
BoundMethodGroup methodGroup = (BoundMethodGroup)expression;
receiver = methodGroup.ReceiverOpt;
// receiver is null if we are calling a static method declared on an outer class via its simple name:
if (receiver != null)
{
switch (receiver.Kind)
{
case BoundKind.BaseReference:
Error(diagnostics, ErrorCode.ERR_NoDynamicPhantomOnBase, node, methodGroup.Name);
hasErrors = true;
break;
case BoundKind.ThisReference:
// Can't call the HasThis method due to EE doing odd things with containing member and its containing type.
if ((InConstructorInitializer || InFieldInitializer) && receiver.WasCompilerGenerated)
{
// Only a static method can be called in a constructor initializer. If we were not in a ctor initializer
// the runtime binder would ignore the receiver, but in a ctor initializer we can't read "this" before
// the base constructor is called. We need to handle this as a type qualified static method call.
// Also applicable to things like field initializers, which run before the ctor initializer.
Debug.Assert(ContainingType is not null);
expression = methodGroup.Update(
methodGroup.TypeArgumentsOpt,
methodGroup.Name,
methodGroup.Methods,
methodGroup.LookupSymbolOpt,
methodGroup.LookupError,
methodGroup.Flags & ~BoundMethodGroupFlags.HasImplicitReceiver,
methodGroup.FunctionType,
receiverOpt: new BoundTypeExpression(node, null, this.ContainingType).MakeCompilerGenerated(),
resultKind: methodGroup.ResultKind);
}
break;
case BoundKind.TypeOrValueExpression:
var typeOrValue = (BoundTypeOrValueExpression)receiver;
// Unfortunately, the runtime binder doesn't have APIs that would allow us to pass both "type or value".
// Ideally the runtime binder would choose between type and value based on the result of the overload resolution.
// We need to pick one or the other here. Dev11 compiler passes the type only if the value can't be accessed.
bool inStaticContext;
bool useType = IsInstance(typeOrValue.Data.ValueSymbol) && !HasThis(isExplicit: false, inStaticContext: out inStaticContext);
BoundExpression finalReceiver = ReplaceTypeOrValueReceiver(typeOrValue, useType, diagnostics);
expression = methodGroup.Update(
methodGroup.TypeArgumentsOpt,
methodGroup.Name,
methodGroup.Methods,
methodGroup.LookupSymbolOpt,
methodGroup.LookupError,
methodGroup.Flags,
methodGroup.FunctionType,
finalReceiver,
methodGroup.ResultKind);
break;
}
}
}
else
{
expression = BindToNaturalType(expression, diagnostics);
if (expression is BoundDynamicMemberAccess memberAccess)
{
receiver = memberAccess.Receiver;
}
else
{
receiver = expression;
}
}
ImmutableArray<BoundExpression> argArray = BuildArgumentsForDynamicInvocation(arguments, diagnostics);
var refKindsArray = arguments.RefKinds.ToImmutableOrNull();
hasErrors &= ReportBadDynamicArguments(node, receiver, argArray, refKindsArray, diagnostics, queryClause);
return new BoundDynamicInvocation(
node,
arguments.GetNames(),
refKindsArray,
applicableMethods,
expression,
argArray,
type: Compilation.DynamicType,
hasErrors: hasErrors);
}
#nullable disable
private void CheckNamedArgumentsForDynamicInvocation(AnalyzedArguments arguments, BindingDiagnosticBag diagnostics)
{
if (arguments.Names.Count == 0)
{
return;
}
if (!Compilation.LanguageVersion.AllowNonTrailingNamedArguments())
{
return;
}
bool seenName = false;
for (int i = 0; i < arguments.Names.Count; i++)
{
if (arguments.Names[i] != null)
{
seenName = true;
}
else if (seenName)
{
Error(diagnostics, ErrorCode.ERR_NamedArgumentSpecificationBeforeFixedArgumentInDynamicInvocation, arguments.Arguments[i].Syntax);
return;
}
}
}
private ImmutableArray<BoundExpression> BuildArgumentsForDynamicInvocation(AnalyzedArguments arguments, BindingDiagnosticBag diagnostics)
{
var builder = ArrayBuilder<BoundExpression>.GetInstance(arguments.Arguments.Count);
builder.AddRange(arguments.Arguments);
for (int i = 0, n = builder.Count; i < n; i++)
{
builder[i] = builder[i] switch
{
OutVariablePendingInference outvar => outvar.FailInference(this, diagnostics),
BoundDiscardExpression discard when !discard.HasExpressionType() => discard.FailInference(this, diagnostics),
var arg => BindToNaturalType(arg, diagnostics)
};
}
return builder.ToImmutableAndFree();
}
// Returns true if there were errors.
#nullable enable
private static bool ReportBadDynamicArguments(
SyntaxNode node,
BoundExpression? receiver,
ImmutableArray<BoundExpression> arguments,
ImmutableArray<RefKind> refKinds,
BindingDiagnosticBag diagnostics,
CSharpSyntaxNode? queryClause)
{
bool hasErrors = false;
bool reportedBadQuery = false;
if (receiver != null && !IsLegalDynamicOperand(receiver))
{
// Cannot perform a dynamic invocation on an expression with type '{0}'.
Debug.Assert(receiver.Type is not null);
Error(diagnostics, ErrorCode.ERR_CannotDynamicInvokeOnExpression, receiver.Syntax, receiver.Type);
hasErrors = true;
}
if (!refKinds.IsDefault)
{
for (int argIndex = 0; argIndex < refKinds.Length; argIndex++)
{
if (refKinds[argIndex] == RefKind.In)
{
Error(diagnostics, ErrorCode.ERR_InDynamicMethodArg, arguments[argIndex].Syntax);
hasErrors = true;
}
}
}
foreach (var arg in arguments)
{
if (!IsLegalDynamicOperand(arg))
{
if (queryClause != null && !reportedBadQuery)
{
reportedBadQuery = true;
Error(diagnostics, ErrorCode.ERR_BadDynamicQuery, node);
hasErrors = true;
continue;
}
if (arg.Kind == BoundKind.Lambda || arg.Kind == BoundKind.UnboundLambda)
{
// Cannot use a lambda expression as an argument to a dynamically dispatched operation without first casting it to a delegate or expression tree type.
Error(diagnostics, ErrorCode.ERR_BadDynamicMethodArgLambda, arg.Syntax);
hasErrors = true;
}
else if (arg.Kind == BoundKind.MethodGroup)
{
// Cannot use a method group as an argument to a dynamically dispatched operation. Did you intend to invoke the method?
Error(diagnostics, ErrorCode.ERR_BadDynamicMethodArgMemgrp, arg.Syntax);
hasErrors = true;
}
else if (arg.Kind == BoundKind.ArgListOperator)
{
// Not a great error message, since __arglist is not a type, but it'll do.
// error CS1978: Cannot use an expression of type '__arglist' as an argument to a dynamically dispatched operation
Error(diagnostics, ErrorCode.ERR_BadDynamicMethodArg, arg.Syntax, "__arglist");
}
else
{
// Lambdas,anonymous methods and method groups are the typeless expressions that
// are not usable as dynamic arguments; if we get here then the expression must have a type.
Debug.Assert((object?)arg.Type != null);
// error CS1978: Cannot use an expression of type 'int*' as an argument to a dynamically dispatched operation
Error(diagnostics, ErrorCode.ERR_BadDynamicMethodArg, arg.Syntax, arg.Type);
hasErrors = true;
}
}
}
return hasErrors;
}
#nullable disable
private BoundExpression BindDelegateInvocation(
SyntaxNode node,
SyntaxNode expression,
string methodName,
BoundExpression boundExpression,
AnalyzedArguments analyzedArguments,
BindingDiagnosticBag diagnostics,
CSharpSyntaxNode queryClause,
NamedTypeSymbol delegateType)
{
BoundExpression result;
var methodGroup = MethodGroup.GetInstance();
methodGroup.PopulateWithSingleMethod(boundExpression, delegateType.DelegateInvokeMethod);
var overloadResolutionResult = OverloadResolutionResult<MethodSymbol>.GetInstance();
CompoundUseSiteInfo<AssemblySymbol> useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics);
OverloadResolution.MethodInvocationOverloadResolution(
methods: methodGroup.Methods,
typeArguments: methodGroup.TypeArguments,
receiver: methodGroup.Receiver,
arguments: analyzedArguments,
result: overloadResolutionResult,
useSiteInfo: ref useSiteInfo,
options: analyzedArguments.HasDynamicArgument ? OverloadResolution.Options.DynamicResolution : OverloadResolution.Options.None);
diagnostics.Add(node, useSiteInfo);
// If overload resolution on the "Invoke" method found an applicable candidate, and one of the arguments
// was dynamic then treat this as a dynamic call.
if (analyzedArguments.HasDynamicArgument && overloadResolutionResult.HasAnyApplicableMember)
{
var applicable = overloadResolutionResult.Results.Single(r => r.IsApplicable);
ReportMemberNotSupportedByDynamicDispatch(node, applicable, diagnostics);
result = BindDynamicInvocation(node, boundExpression, analyzedArguments, overloadResolutionResult.GetAllApplicableMembers(), diagnostics, queryClause);
}
else
{
result = BindInvocationExpressionContinued(node, expression, methodName, overloadResolutionResult, analyzedArguments, methodGroup, delegateType, diagnostics, queryClause);
}
overloadResolutionResult.Free();
methodGroup.Free();
return result;
}
private static bool HasApplicableConditionalMethod(ImmutableArray<MemberResolutionResult<MethodSymbol>> finalApplicableCandidates)
{
foreach (var candidate in finalApplicableCandidates)
{
if (candidate.Member.IsConditional)
{
return true;
}
}
return false;
}
private void ReportMemberNotSupportedByDynamicDispatch<TMember>(SyntaxNode syntax, MemberResolutionResult<TMember> candidate, BindingDiagnosticBag diagnostics)
where TMember : Symbol
{
if (candidate.Result.Kind == MemberResolutionKind.ApplicableInExpandedForm &&
!candidate.Member.GetParameters().Last().Type.IsSZArray())
{
Error(diagnostics,
ErrorCode.ERR_DynamicDispatchToParamsCollection,
syntax, candidate.LeastOverriddenMember);
}
}
private BoundExpression BindMethodGroupInvocation(
SyntaxNode syntax,
SyntaxNode expression,
string methodName,
BoundMethodGroup methodGroup,
AnalyzedArguments analyzedArguments,
BindingDiagnosticBag diagnostics,
CSharpSyntaxNode queryClause,
bool ignoreNormalFormIfHasValidParamsParameter,
out bool anyApplicableCandidates,
bool disallowExpandedNonArrayParams = false)
{
//
// !!! ATTENTION !!!
//
// In terms of errors relevant for HasCollectionExpressionApplicableAddMethod check
// this function should be kept in sync with local function
// HasCollectionExpressionApplicableAddMethod.bindMethodGroupInvocation
//
BoundExpression result = null;
CompoundUseSiteInfo<AssemblySymbol> useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics);
var resolution = this.ResolveMethodGroup(
methodGroup, expression, methodName, analyzedArguments,
useSiteInfo: ref useSiteInfo,
options: (ignoreNormalFormIfHasValidParamsParameter ? OverloadResolution.Options.IgnoreNormalFormIfHasValidParamsParameter : OverloadResolution.Options.None) |
(disallowExpandedNonArrayParams ? OverloadResolution.Options.DisallowExpandedNonArrayParams : OverloadResolution.Options.None) |
(analyzedArguments.HasDynamicArgument ? OverloadResolution.Options.DynamicResolution : OverloadResolution.Options.None));
diagnostics.Add(expression, useSiteInfo);
anyApplicableCandidates = resolution.ResultKind == LookupResultKind.Viable && resolution.OverloadResolutionResult.HasAnyApplicableMember;
if (!methodGroup.HasAnyErrors) diagnostics.AddRange(resolution.Diagnostics); // Suppress cascading.
if (resolution.HasAnyErrors)
{
ImmutableArray<MethodSymbol> originalMethods;
LookupResultKind resultKind;
ImmutableArray<TypeWithAnnotations> typeArguments;
if (resolution.OverloadResolutionResult != null)
{
originalMethods = GetOriginalMethods(resolution.OverloadResolutionResult);
resultKind = resolution.MethodGroup.ResultKind;
typeArguments = resolution.MethodGroup.TypeArguments.ToImmutable();
}
else
{
originalMethods = methodGroup.Methods;
resultKind = methodGroup.ResultKind;
typeArguments = methodGroup.TypeArgumentsOpt;
}
result = CreateBadCall(
syntax,
methodName,
methodGroup.ReceiverOpt,
originalMethods,
resultKind,
typeArguments,
analyzedArguments,
invokedAsExtensionMethod: resolution.IsExtensionMethodGroup,
isDelegate: false);
}
else if (!resolution.IsEmpty)
{
// We're checking resolution.ResultKind, rather than methodGroup.HasErrors
// to better handle the case where there's a problem with the receiver
// (e.g. inaccessible), but the method group resolved correctly (e.g. because
// it's actually an accessible static method on a base type).
// CONSIDER: could check for error types amongst method group type arguments.
if (resolution.ResultKind != LookupResultKind.Viable)
{
if (resolution.MethodGroup != null)
{
// we want to force any unbound lambda arguments to cache an appropriate conversion if possible; see 9448.
result = BindInvocationExpressionContinued(
syntax, expression, methodName, resolution.OverloadResolutionResult, resolution.AnalyzedArguments,
resolution.MethodGroup, delegateTypeOpt: null, diagnostics: BindingDiagnosticBag.Discarded, queryClause: queryClause);
}
// Since the resolution is non-empty and has no diagnostics, the LookupResultKind in its MethodGroup is uninteresting.
result = CreateBadCall(syntax, methodGroup, methodGroup.ResultKind, analyzedArguments);
}
else
{
// If overload resolution found one or more applicable methods and at least one argument
// was dynamic then treat this as a dynamic call.
if (resolution.AnalyzedArguments.HasDynamicArgument &&
resolution.OverloadResolutionResult.HasAnyApplicableMember)
{
// Note that the runtime binder may consider candidates that haven't passed compile-time final validation
// and an ambiguity error may be reported. Also additional checks are performed in runtime final validation
// that are not performed at compile-time.
// Only if the set of final applicable candidates is empty we know for sure the call will fail at runtime.
var finalApplicableCandidates = GetCandidatesPassingFinalValidation(syntax, resolution.OverloadResolutionResult,
methodGroup.ReceiverOpt,
methodGroup.TypeArgumentsOpt,
invokedAsExtensionMethod: resolution.IsExtensionMethodGroup,
diagnostics);
if (finalApplicableCandidates.Length == 0)
{
result = CreateBadCall(syntax, methodGroup, methodGroup.ResultKind, analyzedArguments);
}
else if (finalApplicableCandidates.Length == 1)
{
Debug.Assert(finalApplicableCandidates[0].IsApplicable);
result = TryEarlyBindSingleCandidateInvocationWithDynamicArgument(syntax, expression, methodName, methodGroup, diagnostics, queryClause, resolution, finalApplicableCandidates[0]);
if (result is null && finalApplicableCandidates[0].LeastOverriddenMember.MethodKind != MethodKind.LocalFunction)
{
ReportMemberNotSupportedByDynamicDispatch(syntax, finalApplicableCandidates[0], diagnostics);
}
}
if (result is null)
{
Debug.Assert(finalApplicableCandidates.Length > 0);
if (resolution.IsExtensionMethodGroup)
{
// error CS1973: 'T' has no applicable method named 'M' but appears to have an
// extension method by that name. Extension methods cannot be dynamically dispatched. Consider
// casting the dynamic arguments or calling the extension method without the extension method
// syntax.
// We found an extension method, so the instance associated with the method group must have
// existed and had a type.
Debug.Assert(methodGroup.InstanceOpt != null && (object)methodGroup.InstanceOpt.Type != null);
Error(diagnostics, ErrorCode.ERR_BadArgTypeDynamicExtension, syntax, methodGroup.InstanceOpt.Type, methodGroup.Name);
result = CreateBadCall(syntax, methodGroup, methodGroup.ResultKind, analyzedArguments);
}
else
{
ReportDynamicInvocationWarnings(syntax, methodGroup, diagnostics, finalApplicableCandidates);
result = BindDynamicInvocation(syntax, methodGroup, resolution.AnalyzedArguments, finalApplicableCandidates.SelectAsArray(r => r.Member), diagnostics, queryClause);
}
}
}
else
{
result = BindInvocationExpressionContinued(
syntax, expression, methodName, resolution.OverloadResolutionResult, resolution.AnalyzedArguments,
resolution.MethodGroup, delegateTypeOpt: null, diagnostics: diagnostics, queryClause: queryClause);
}
}
}
else
{
result = CreateBadCall(syntax, methodGroup, methodGroup.ResultKind, analyzedArguments);
}
resolution.Free();
return result;
}
private void ReportDynamicInvocationWarnings(SyntaxNode syntax, BoundMethodGroup methodGroup, BindingDiagnosticBag diagnostics, ImmutableArray<MemberResolutionResult<MethodSymbol>> finalApplicableCandidates)
{
if (HasApplicableConditionalMethod(finalApplicableCandidates))
{
// warning CS1974: The dynamically dispatched call to method 'Goo' may fail at runtime
// because one or more applicable overloads are conditional methods
Error(diagnostics, ErrorCode.WRN_DynamicDispatchToConditionalMethod, syntax, methodGroup.Name);
}
}
private bool IsAmbiguousDynamicParamsArgument<TMethodOrPropertySymbol>(ArrayBuilder<BoundExpression> arguments, MemberResolutionResult<TMethodOrPropertySymbol> candidate, out SyntaxNode argumentSyntax)
where TMethodOrPropertySymbol : Symbol
{
if (OverloadResolution.IsValidParams(this, candidate.LeastOverriddenMember, disallowExpandedNonArrayParams: false, out _) &&
candidate.Result.Kind == MemberResolutionKind.ApplicableInNormalForm)
{
var parameters = candidate.Member.GetParameters();
var lastParamIndex = parameters.Length - 1;
for (int i = 0; i < arguments.Count; ++i)
{
var arg = arguments[i];
if (arg.HasDynamicType() &&
candidate.Result.ParameterFromArgument(i) == lastParamIndex)
{
argumentSyntax = arg.Syntax;
return true;
}
}
}
argumentSyntax = null;
return false;
}
private bool CanEarlyBindSingleCandidateInvocationWithDynamicArgument(
SyntaxNode syntax,
BoundMethodGroup boundMethodGroup,
BindingDiagnosticBag diagnostics,
MethodGroupResolution resolution,
MemberResolutionResult<MethodSymbol> methodResolutionResult,
MethodSymbol singleCandidate)
{
if (singleCandidate.MethodKind != MethodKind.LocalFunction)
{
return false;
}
if (boundMethodGroup.TypeArgumentsOpt.IsDefaultOrEmpty && singleCandidate.IsGenericMethod)
{
// If we call an unconstructed generic function with a
// dynamic argument in a place where it influences the type
// parameters, we need to dynamically dispatch the call (as the
// function must be constructed at runtime). We disallow that
// when we know that runtime binder will not be able to handle the case.
// See https://github.com/dotnet/roslyn/issues/21317
// However, doing a specific analysis of each
// argument and its corresponding parameter to check if it's
// generic (and allow dynamic in non-generic parameters) doesn't
// seem to worth the complexity. So, just disallow any mixing of dynamic and
// inferred generics. (Explicit generic arguments are fine)
Error(diagnostics,
ErrorCode.ERR_DynamicLocalFunctionTypeParameter,
syntax, singleCandidate.Name);
return false;
}
if (IsAmbiguousDynamicParamsArgument(resolution.AnalyzedArguments.Arguments, methodResolutionResult, out SyntaxNode argumentSyntax))
{
// We're only in trouble if a dynamic argument is passed to the
// params parameter and is ambiguous at compile time between normal
// and expanded form i.e., there is exactly one dynamic argument to
// a params parameter, and we know that runtime binder might not be
// able to handle the disambiguation
// See https://github.com/dotnet/roslyn/issues/10708
Error(diagnostics,
ErrorCode.ERR_DynamicLocalFunctionParamsParameter,
argumentSyntax, singleCandidate.Parameters.Last().Name, singleCandidate.Name);
return false;
}
return true;
}
private BoundExpression TryEarlyBindSingleCandidateInvocationWithDynamicArgument(
SyntaxNode syntax,
SyntaxNode expression,
string methodName,
BoundMethodGroup boundMethodGroup,
BindingDiagnosticBag diagnostics,
CSharpSyntaxNode queryClause,
MethodGroupResolution resolution,
MemberResolutionResult<MethodSymbol> methodResolutionResult)
{
MethodSymbol singleCandidate = methodResolutionResult.LeastOverriddenMember;
if (!CanEarlyBindSingleCandidateInvocationWithDynamicArgument(syntax, boundMethodGroup, diagnostics, resolution, methodResolutionResult, singleCandidate))
{
return null;
}
var resultWithSingleCandidate = OverloadResolutionResult<MethodSymbol>.GetInstance();
resultWithSingleCandidate.ResultsBuilder.Add(methodResolutionResult);
BoundExpression result = BindInvocationExpressionContinued(
node: syntax,
expression: expression,
methodName: methodName,
result: resultWithSingleCandidate,
analyzedArguments: resolution.AnalyzedArguments,
methodGroup: resolution.MethodGroup,
delegateTypeOpt: null,
diagnostics: diagnostics,
queryClause: queryClause);
resultWithSingleCandidate.Free();
return result;
}
private ImmutableArray<MemberResolutionResult<TMethodOrPropertySymbol>> GetCandidatesPassingFinalValidation<TMethodOrPropertySymbol>(
SyntaxNode syntax,
OverloadResolutionResult<TMethodOrPropertySymbol> overloadResolutionResult,
BoundExpression receiverOpt,
ImmutableArray<TypeWithAnnotations> typeArgumentsOpt,
bool invokedAsExtensionMethod,
BindingDiagnosticBag diagnostics) where TMethodOrPropertySymbol : Symbol
{
Debug.Assert(overloadResolutionResult.HasAnyApplicableMember);
var finalCandidates = ArrayBuilder<MemberResolutionResult<TMethodOrPropertySymbol>>.GetInstance();
BindingDiagnosticBag firstFailed = null;
var candidateDiagnostics = BindingDiagnosticBag.GetInstance(diagnostics);
for (int i = 0, n = overloadResolutionResult.ResultsBuilder.Count; i < n; i++)
{
var result = overloadResolutionResult.ResultsBuilder[i];
if (result.Result.IsApplicable)
{
// For F to pass the check, all of the following must hold:
// ...
// * If the type parameters of F were substituted in the step above, their constraints are satisfied.
// * If F is a static method, the method group must have resulted from a simple-name, a member-access through a type,
// or a member-access whose receiver can't be classified as a type or value until after overload resolution (see §7.6.4.1).
// * If F is an instance method, the method group must have resulted from a simple-name, a member-access through a variable or value,
// or a member-access whose receiver can't be classified as a type or value until after overload resolution (see §7.6.4.1).
if (!MemberGroupFinalValidationAccessibilityChecks(receiverOpt, result.Member, syntax, candidateDiagnostics, invokedAsExtensionMethod: invokedAsExtensionMethod) &&
(typeArgumentsOpt.IsDefault || ((MethodSymbol)(object)result.Member).CheckConstraints(new ConstraintsHelper.CheckConstraintsArgs(this.Compilation, this.Conversions, includeNullability: false, syntax.Location, candidateDiagnostics))))
{
finalCandidates.Add(result);
continue;
}
if (firstFailed == null)
{
firstFailed = candidateDiagnostics;
candidateDiagnostics = BindingDiagnosticBag.GetInstance(diagnostics);
}
else
{
candidateDiagnostics.Clear();
}
}
}
if (firstFailed != null)
{
// Report diagnostics of the first candidate that failed the validation
// unless we have at least one candidate that passes.
if (finalCandidates.Count == 0)
{
diagnostics.AddRange(firstFailed);
}
firstFailed.Free();
}
candidateDiagnostics.Free();
return finalCandidates.ToImmutableAndFree();
}
private void CheckRestrictedTypeReceiver(BoundExpression expression, CSharpCompilation compilation, BindingDiagnosticBag diagnostics)
{
Debug.Assert(diagnostics != null);
// It is never legal to box a restricted type, even if we are boxing it as the receiver
// of a method call. When must be box? We skip boxing when the method in question is defined
// on the restricted type or overridden by the restricted type.
switch (expression.Kind)
{
case BoundKind.Call:
{
var call = (BoundCall)expression;
if (!call.HasAnyErrors && call.ReceiverOpt != null && (object)call.ReceiverOpt.Type != null)
{
// error CS0029: Cannot implicitly convert type 'A' to 'B'
// Case 1: receiver is a restricted type, and method called is defined on a parent type
if (call.ReceiverOpt.Type.IsRestrictedType() && !call.Method.ContainingType.IsInterface && !TypeSymbol.Equals(call.Method.ContainingType, call.ReceiverOpt.Type, TypeCompareKind.ConsiderEverything2))
{
SymbolDistinguisher distinguisher = new SymbolDistinguisher(compilation, call.ReceiverOpt.Type, call.Method.ContainingType);
Error(diagnostics, ErrorCode.ERR_NoImplicitConv, call.ReceiverOpt.Syntax, distinguisher.First, distinguisher.Second);
}
// Case 2: receiver is a base reference, and the child type is restricted
else if (call.ReceiverOpt.Kind == BoundKind.BaseReference && this.ContainingType.IsRestrictedType())
{
SymbolDistinguisher distinguisher = new SymbolDistinguisher(compilation, this.ContainingType, call.Method.ContainingType);
Error(diagnostics, ErrorCode.ERR_NoImplicitConv, call.ReceiverOpt.Syntax, distinguisher.First, distinguisher.Second);
}
}
}
break;
case BoundKind.DynamicInvocation:
{
var dynInvoke = (BoundDynamicInvocation)expression;
if (!dynInvoke.HasAnyErrors &&
(object)dynInvoke.Expression.Type != null &&
dynInvoke.Expression.Type.IsRestrictedType())
{
// eg: b = typedReference.Equals(dyn);
// error CS1978: Cannot use an expression of type 'TypedReference' as an argument to a dynamically dispatched operation
Error(diagnostics, ErrorCode.ERR_BadDynamicMethodArg, dynInvoke.Expression.Syntax, dynInvoke.Expression.Type);
}
}
break;
case BoundKind.FunctionPointerInvocation:
break;
default:
throw ExceptionUtilities.UnexpectedValue(expression.Kind);
}
}
/// <summary>
/// Perform overload resolution on the method group or expression (BoundMethodGroup)
/// and arguments and return a BoundExpression representing the invocation.
/// </summary>
/// <param name="node">Invocation syntax node.</param>
/// <param name="expression">The syntax for the invoked method, including receiver.</param>
/// <param name="methodName">Name of the invoked method.</param>
/// <param name="result">Overload resolution result for method group executed by caller.</param>
/// <param name="analyzedArguments">Arguments bound by the caller.</param>
/// <param name="methodGroup">Method group if the invocation represents a potentially overloaded member.</param>
/// <param name="delegateTypeOpt">Delegate type if method group represents a delegate.</param>
/// <param name="diagnostics">Diagnostics.</param>
/// <param name="queryClause">The syntax for the query clause generating this invocation expression, if any.</param>
/// <returns>BoundCall or error expression representing the invocation.</returns>
private BoundCall BindInvocationExpressionContinued(
SyntaxNode node,
SyntaxNode expression,
string methodName,
OverloadResolutionResult<MethodSymbol> result,
AnalyzedArguments analyzedArguments,
MethodGroup methodGroup,
NamedTypeSymbol delegateTypeOpt,
BindingDiagnosticBag diagnostics,
CSharpSyntaxNode queryClause = null)
{
//
// !!! ATTENTION !!!
//
// In terms of errors relevant for HasCollectionExpressionApplicableAddMethod check
// this function should be kept in sync with local function
// HasCollectionExpressionApplicableAddMethod.bindInvocationExpressionContinued
//
Debug.Assert(node != null);
Debug.Assert(methodGroup != null);
Debug.Assert(methodGroup.Error == null);
Debug.Assert(methodGroup.Methods.Count > 0);
Debug.Assert(((object)delegateTypeOpt == null) || (methodGroup.Methods.Count == 1));
var invokedAsExtensionMethod = methodGroup.IsExtensionMethodGroup;
// Delegate invocations should never be considered extension method
// invocations (even though the delegate may refer to an extension method).
Debug.Assert(!invokedAsExtensionMethod || ((object)delegateTypeOpt == null));
// We have already determined that we are not in a situation where we can successfully do
// a dynamic binding. We might be in one of the following situations:
//
// * There were dynamic arguments but overload resolution still found zero applicable candidates.
// * There were no dynamic arguments and overload resolution found zero applicable candidates.
// * There were no dynamic arguments and overload resolution found multiple applicable candidates
// without being able to find the best one.
//
// In those three situations we might give an additional error.
if (!result.Succeeded)
{
if (analyzedArguments.HasErrors)
{
// Errors for arguments have already been reported, except for unbound lambdas and switch expressions.
// We report those now.
foreach (var argument in analyzedArguments.Arguments)
{
switch (argument)
{
case UnboundLambda unboundLambda:
var boundWithErrors = unboundLambda.BindForErrorRecovery();
diagnostics.AddRange(boundWithErrors.Diagnostics);
break;
case BoundUnconvertedObjectCreationExpression _:
case BoundTupleLiteral _:
// Tuple literals can contain unbound lambdas or switch expressions.
_ = BindToNaturalType(argument, diagnostics);
break;
case BoundUnconvertedSwitchExpression { Type: { } naturalType } switchExpr:
_ = ConvertSwitchExpression(switchExpr, naturalType, conversionIfTargetTyped: null, diagnostics);
break;
case BoundUnconvertedConditionalOperator { Type: { } naturalType } conditionalExpr:
_ = ConvertConditionalExpression(conditionalExpr, naturalType, conversionIfTargetTyped: null, diagnostics);
break;
}
}
}
else
{
// Since there were no argument errors to report, we report an error on the invocation itself.
string name = (object)delegateTypeOpt == null ? methodName : null;
result.ReportDiagnostics(
binder: this, location: GetLocationForOverloadResolutionDiagnostic(node, expression), nodeOpt: node, diagnostics: diagnostics, name: name,
receiver: methodGroup.Receiver, invokedExpression: expression, arguments: analyzedArguments, memberGroup: methodGroup.Methods.ToImmutable(),
typeContainingConstructor: null, delegateTypeBeingInvoked: delegateTypeOpt, queryClause: queryClause);
}
return CreateBadCall(node, methodGroup.Name, invokedAsExtensionMethod && analyzedArguments.Arguments.Count > 0 && (object)methodGroup.Receiver == (object)analyzedArguments.Arguments[0] ? null : methodGroup.Receiver,
GetOriginalMethods(result), methodGroup.ResultKind, methodGroup.TypeArguments.ToImmutable(), analyzedArguments, invokedAsExtensionMethod: invokedAsExtensionMethod, isDelegate: ((object)delegateTypeOpt != null));
}
// Otherwise, there were no dynamic arguments and overload resolution found a unique best candidate.
// We still have to determine if it passes final validation.
var methodResult = result.ValidResult;
var returnType = methodResult.Member.ReturnType;
var method = methodResult.Member;
// It is possible that overload resolution succeeded, but we have chosen an
// instance method and we're in a static method. A careful reading of the
// overload resolution spec shows that the "final validation" stage allows an
// "implicit this" on any method call, not just method calls from inside
// instance methods. Therefore we must detect this scenario here, rather than in
// overload resolution.
var receiver = ReplaceTypeOrValueReceiver(methodGroup.Receiver, !method.RequiresInstanceReceiver && !invokedAsExtensionMethod, diagnostics);
ImmutableArray<int> argsToParams;
this.CheckAndCoerceArguments(node, methodResult, analyzedArguments, diagnostics, receiver, invokedAsExtensionMethod: invokedAsExtensionMethod, out argsToParams);
var expanded = methodResult.Result.Kind == MemberResolutionKind.ApplicableInExpandedForm;
BindDefaultArguments(node, method.Parameters, analyzedArguments.Arguments, analyzedArguments.RefKinds, analyzedArguments.Names, ref argsToParams, out var defaultArguments, expanded, enableCallerInfo: true, diagnostics);
// Note: we specifically want to do final validation (7.6.5.1) without checking delegate compatibility (15.2),
// so we're calling MethodGroupFinalValidation directly, rather than via MethodGroupConversionHasErrors.
// Note: final validation wants the receiver that corresponds to the source representation
// (i.e. the first argument, if invokedAsExtensionMethod).
var gotError = MemberGroupFinalValidation(receiver, method, expression, diagnostics, invokedAsExtensionMethod);
CheckImplicitThisCopyInReadOnlyMember(receiver, method, diagnostics);
if (invokedAsExtensionMethod)
{
BoundExpression receiverArgument = analyzedArguments.Argument(0);
ParameterSymbol receiverParameter = method.Parameters.First();
// we will have a different receiver if ReplaceTypeOrValueReceiver has unwrapped TypeOrValue
if ((object)receiver != methodGroup.Receiver)
{
// Because the receiver didn't pass through CoerceArguments, we need to apply an appropriate conversion here.
Debug.Assert(argsToParams.IsDefault || argsToParams[0] == 0);
receiverArgument = CreateConversion(receiver, methodResult.Result.ConversionForArg(0),
receiverParameter.Type, diagnostics);
}
if (receiverParameter.RefKind == RefKind.Ref)
{
// If this was a ref extension method, receiverArgument must be checked for L-value constraints.
// This helper method will also replace it with a BoundBadExpression if it was invalid.
receiverArgument = CheckValue(receiverArgument, BindValueKind.RefOrOut, diagnostics);
if (analyzedArguments.RefKinds.Count == 0)
{
analyzedArguments.RefKinds.Count = analyzedArguments.Arguments.Count;
}
// receiver of a `ref` extension method is a `ref` argument. (and we have checked above that it can be passed as a Ref)
// we need to adjust the argument refkind as if we had a `ref` modifier in a call.
analyzedArguments.RefKinds[0] = RefKind.Ref;
CheckFeatureAvailability(receiverArgument.Syntax, MessageID.IDS_FeatureRefExtensionMethods, diagnostics);
}
else if (receiverParameter.RefKind == RefKind.In)
{
// NB: receiver of an `in` extension method is treated as a `byval` argument, so no changes from the default refkind is needed in that case.
Debug.Assert(analyzedArguments.RefKind(0) == RefKind.None);
CheckFeatureAvailability(receiverArgument.Syntax, MessageID.IDS_FeatureRefExtensionMethods, diagnostics);
}
analyzedArguments.Arguments[0] = receiverArgument;
}
// This will be the receiver of the BoundCall node that we create.
// For extension methods, there is no receiver because the receiver in source was actually the first argument.
// For instance methods, we may have synthesized an implicit this node. We'll keep it for the emitter.
// For static methods, we may have synthesized a type expression. It serves no purpose, so we'll drop it.
if (invokedAsExtensionMethod || (!method.RequiresInstanceReceiver && receiver != null && receiver.WasCompilerGenerated))
{
receiver = null;
}
var argNames = analyzedArguments.GetNames();
var argRefKinds = analyzedArguments.RefKinds.ToImmutableOrNull();
var args = analyzedArguments.Arguments.ToImmutable();
if (!gotError && method.RequiresInstanceReceiver && receiver != null && receiver.Kind == BoundKind.ThisReference && receiver.WasCompilerGenerated)
{
gotError = IsRefOrOutThisParameterCaptured(node, diagnostics);
}
// What if some of the arguments are implicit? Dev10 reports unsafe errors
// if the implied argument would have an unsafe type. We need to check
// the parameters explicitly, since there won't be bound nodes for the implied
// arguments until lowering.
if (method.HasParameterContainingPointerType())
{
// Don't worry about double reporting (i.e. for both the argument and the parameter)
// because only one unsafe diagnostic is allowed per scope - the others are suppressed.
gotError = ReportUnsafeIfNotAllowed(node, diagnostics) || gotError;
}
bool hasBaseReceiver = receiver != null && receiver.Kind == BoundKind.BaseReference;
ReportDiagnosticsIfObsolete(diagnostics, method, node, hasBaseReceiver);
ReportDiagnosticsIfUnmanagedCallersOnly(diagnostics, method, node, isDelegateConversion: false);
// No use site errors, but there could be use site warnings.
// If there are any use site warnings, they have already been reported by overload resolution.
Debug.Assert(!method.HasUseSiteError, "Shouldn't have reached this point if there were use site errors.");
if (method.IsRuntimeFinalizer())
{
ErrorCode code = hasBaseReceiver
? ErrorCode.ERR_CallingBaseFinalizeDeprecated
: ErrorCode.ERR_CallingFinalizeDeprecated;
Error(diagnostics, code, node);
gotError = true;
}
Debug.Assert(args.IsDefaultOrEmpty || (object)receiver != (object)args[0]);
bool isDelegateCall = (object)delegateTypeOpt != null;
if (!isDelegateCall)
{
if (method.RequiresInstanceReceiver)
{
WarnOnAccessOfOffDefault(node.Kind() == SyntaxKind.InvocationExpression ?
((InvocationExpressionSyntax)node).Expression :
node,
receiver,
diagnostics);
}
}
return new BoundCall(node, receiver, initialBindingReceiverIsSubjectToCloning: ReceiverIsSubjectToCloning(receiver, method), method, args, argNames, argRefKinds, isDelegateCall: isDelegateCall,
expanded: expanded, invokedAsExtensionMethod: invokedAsExtensionMethod,
argsToParamsOpt: argsToParams, defaultArguments, resultKind: LookupResultKind.Viable, type: returnType, hasErrors: gotError);
}
#nullable enable
internal ThreeState ReceiverIsSubjectToCloning(BoundExpression? receiver, PropertySymbol property)
{
var method = property.GetMethod ?? property.SetMethod;
// Property might be missing accessors in invalid code.
if (method is null)
{
return ThreeState.False;
}
return ReceiverIsSubjectToCloning(receiver, method);
}
internal ThreeState ReceiverIsSubjectToCloning(BoundExpression? receiver, MethodSymbol method)
{
if (receiver is BoundValuePlaceholderBase || receiver?.Type is null or { IsReferenceType: true })
{
return ThreeState.False;
}
var valueKind = method.IsEffectivelyReadOnly
? BindValueKind.RefersToLocation
: BindValueKind.RefersToLocation | BindValueKind.Assignable;
var result = !CheckValueKind(receiver.Syntax, receiver, valueKind, checkingReceiver: true, BindingDiagnosticBag.Discarded);
return result.ToThreeState();
}
private static SourceLocation GetCallerLocation(SyntaxNode syntax)
{
var token = syntax switch
{
InvocationExpressionSyntax invocation => invocation.ArgumentList.OpenParenToken,
BaseObjectCreationExpressionSyntax objectCreation => objectCreation.NewKeyword,
ConstructorInitializerSyntax constructorInitializer => constructorInitializer.ArgumentList.OpenParenToken,
PrimaryConstructorBaseTypeSyntax primaryConstructorBaseType => primaryConstructorBaseType.ArgumentList.OpenParenToken,
ElementAccessExpressionSyntax elementAccess => elementAccess.ArgumentList.OpenBracketToken,
_ => syntax.GetFirstToken()
};
return new SourceLocation(token);
}
private BoundExpression GetDefaultParameterSpecialNoConversion(SyntaxNode syntax, ParameterSymbol parameter, BindingDiagnosticBag diagnostics)
{
var parameterType = parameter.Type;
Debug.Assert(parameterType.IsDynamic() || parameterType.SpecialType == SpecialType.System_Object);
// We have a call to a method M([Optional] object x) which omits the argument. The value we generate
// for the argument depends on the presence or absence of other attributes. The rules are:
//
// * If we're generating a default argument for an attribute, it's a compile error.
// * If the parameter is marked as [MarshalAs(Interface)], [MarshalAs(IUnknown)] or [MarshalAs(IDispatch)]
// then the argument is null.
// * Otherwise, if the parameter is marked as [IUnknownConstant] then the argument is
// new UnknownWrapper(null)
// * Otherwise, if the parameter is marked as [IDispatchConstant] then the argument is
// new DispatchWrapper(null)
// * Otherwise, the argument is Type.Missing.
BoundExpression? defaultValue = null;
if (InAttributeArgument)
{
// CS7067: Attribute constructor parameter '{0}' is optional, but no default parameter value was specified.
diagnostics.Add(ErrorCode.ERR_BadAttributeParamDefaultArgument, syntax.Location, parameter.Name);
}
else if (parameter.IsMarshalAsObject)
{
// default(object)
defaultValue = new BoundDefaultExpression(syntax, parameterType) { WasCompilerGenerated = true };
}
else if (parameter.IsIUnknownConstant)
{
if (GetWellKnownTypeMember(Compilation, WellKnownMember.System_Runtime_InteropServices_UnknownWrapper__ctor, diagnostics, syntax: syntax) is MethodSymbol methodSymbol)
{
// new UnknownWrapper(default(object))
var unknownArgument = new BoundDefaultExpression(syntax, parameterType) { WasCompilerGenerated = true };
defaultValue = new BoundObjectCreationExpression(syntax, methodSymbol, unknownArgument) { WasCompilerGenerated = true };
}
}
else if (parameter.IsIDispatchConstant)
{
if (GetWellKnownTypeMember(Compilation, WellKnownMember.System_Runtime_InteropServices_DispatchWrapper__ctor, diagnostics, syntax: syntax) is MethodSymbol methodSymbol)
{
// new DispatchWrapper(default(object))
var dispatchArgument = new BoundDefaultExpression(syntax, parameterType) { WasCompilerGenerated = true };
defaultValue = new BoundObjectCreationExpression(syntax, methodSymbol, dispatchArgument) { WasCompilerGenerated = true };
}
}
else
{
if (GetWellKnownTypeMember(Compilation, WellKnownMember.System_Type__Missing, diagnostics, syntax: syntax) is FieldSymbol fieldSymbol)
{
// Type.Missing
defaultValue = new BoundFieldAccess(syntax, null, fieldSymbol, ConstantValue.NotAvailable) { WasCompilerGenerated = true };
}
}
return defaultValue ?? BadExpression(syntax).MakeCompilerGenerated();
}
internal static ParameterSymbol? GetCorrespondingParameter(
int argumentOrdinal,
ImmutableArray<ParameterSymbol> parameters,
ImmutableArray<int> argsToParamsOpt,
bool expanded)
{
int n = parameters.Length;
ParameterSymbol? parameter;
if (argsToParamsOpt.IsDefault)
{
if (argumentOrdinal < n)
{
parameter = parameters[argumentOrdinal];
}
else if (expanded)
{
parameter = parameters[n - 1];
}
else
{
parameter = null;
}
}
else
{
Debug.Assert(argumentOrdinal < argsToParamsOpt.Length);
int parameterOrdinal = argsToParamsOpt[argumentOrdinal];
if (parameterOrdinal < n)
{
parameter = parameters[parameterOrdinal];
}
else
{
parameter = null;
}
}
return parameter;
}
internal void BindDefaultArguments(
SyntaxNode node,
ImmutableArray<ParameterSymbol> parameters,
ArrayBuilder<BoundExpression> argumentsBuilder,
ArrayBuilder<RefKind>? argumentRefKindsBuilder,
ArrayBuilder<(string Name, Location Location)?>? namesBuilder,
ref ImmutableArray<int> argsToParamsOpt,
out BitVector defaultArguments,
bool expanded,
bool enableCallerInfo,
BindingDiagnosticBag diagnostics,
Symbol? attributedMember = null)
{
int paramsIndex = parameters.Length - 1;
var visitedParameters = BitVector.Create(parameters.Length);
for (var i = 0; i < argumentsBuilder.Count; i++)
{
var parameter = GetCorrespondingParameter(i, parameters, argsToParamsOpt, expanded);
if (parameter is not null)
{
visitedParameters[parameter.Ordinal] = true;
if (expanded && parameter.Ordinal == paramsIndex)
{
expanded = false; // For the reminder of the method treat this as non-expanded case
Debug.Assert(argumentsBuilder[i].IsParamsArrayOrCollection);
Debug.Assert(i + 1 == argumentsBuilder.Count ||
GetCorrespondingParameter(i + 1, parameters, argsToParamsOpt, expanded: true)?.Ordinal != paramsIndex);
}
}
}
if (expanded)
{
// expanded parameter array is not treated as an optional parameter
visitedParameters[paramsIndex] = true;
}
bool haveDefaultArguments = !parameters.All(static (param, visitedParameters) => visitedParameters[param.Ordinal], visitedParameters);
if (!haveDefaultArguments && !expanded)
{
Debug.Assert(argumentsBuilder.Count >= parameters.Length); // Accounting for arglist cases
Debug.Assert(argumentRefKindsBuilder is null || argumentRefKindsBuilder.Count == 0 || argumentRefKindsBuilder.Count == argumentsBuilder.Count);
Debug.Assert(namesBuilder is null || namesBuilder.Count == 0 || namesBuilder.Count == argumentsBuilder.Count);
Debug.Assert(argsToParamsOpt.IsDefault || argsToParamsOpt.Length == argumentsBuilder.Count);
defaultArguments = default;
return;
}
ArrayBuilder<int>? argsToParamsBuilder = null;
if (!argsToParamsOpt.IsDefault)
{
argsToParamsBuilder = ArrayBuilder<int>.GetInstance(argsToParamsOpt.Length);
argsToParamsBuilder.AddRange(argsToParamsOpt);
}
// only proceed with binding default arguments if we know there is some parameter that has not been matched by an explicit argument
if (haveDefaultArguments)
{
// In a scenario like `string Prop { get; } = M();`, the containing symbol could be the synthesized field.
// We want to use the associated user-declared symbol instead where possible.
var containingMember = InAttributeArgument ? attributedMember : ContainingMember() switch
{
FieldSymbol { AssociatedSymbol: { } symbol } => symbol,
var c => c
};
Debug.Assert(InAttributeArgument || (attributedMember is null && containingMember is not null));
defaultArguments = BitVector.Create(parameters.Length);
// Params methods can be invoked in normal form, so the strongest assertion we can make is that, if
// we're in an expanded context, the last param must be params. The inverse is not necessarily true.
Debug.Assert(!expanded || parameters[^1].IsParams);
var lastIndex = expanded ? ^1 : ^0;
var argumentsCount = argumentsBuilder.Count;
// Go over missing parameters, inserting default values for optional parameters
foreach (var parameter in parameters.AsSpan()[..lastIndex])
{
if (!visitedParameters[parameter.Ordinal])
{
Debug.Assert(parameter.IsOptional);
defaultArguments[argumentsBuilder.Count] = true;
argumentsBuilder.Add(bindDefaultArgument(node, parameter, containingMember, enableCallerInfo, diagnostics, argumentsBuilder, argumentsCount, argsToParamsOpt));
if (argumentRefKindsBuilder is { Count: > 0 })
{
argumentRefKindsBuilder.Add(RefKind.None);
}
argsToParamsBuilder?.Add(parameter.Ordinal);
if (namesBuilder?.Count > 0)
{
namesBuilder.Add(null);
}
}
}
}
else
{
defaultArguments = default;
}
if (expanded)
{
// Create an empty collection
BoundExpression collection = CreateParamsCollection(node, parameters[paramsIndex], collectionArgs: ImmutableArray<BoundExpression>.Empty, diagnostics);
argumentsBuilder.Add(collection);
argsToParamsBuilder?.Add(paramsIndex);
if (argumentRefKindsBuilder is { Count: > 0 })
{
argumentRefKindsBuilder.Add(RefKind.None);
}
if (namesBuilder is { Count: > 0 })
{
namesBuilder.Add(null);
}
}
Debug.Assert(argumentsBuilder.Count == parameters.Length);
Debug.Assert(argumentRefKindsBuilder is null || argumentRefKindsBuilder.Count == 0 || argumentRefKindsBuilder.Count == parameters.Length);
Debug.Assert(namesBuilder is null || namesBuilder.Count == 0 || namesBuilder.Count == parameters.Length);
Debug.Assert(argsToParamsBuilder is null || argsToParamsBuilder.Count == parameters.Length);
if (argsToParamsBuilder is object)
{
argsToParamsOpt = argsToParamsBuilder.ToImmutableOrNull();
argsToParamsBuilder.Free();
}
BoundExpression bindDefaultArgument(SyntaxNode syntax, ParameterSymbol parameter, Symbol? containingMember, bool enableCallerInfo, BindingDiagnosticBag diagnostics, ArrayBuilder<BoundExpression> argumentsBuilder, int argumentsCount, ImmutableArray<int> argsToParamsOpt)
{
TypeSymbol parameterType = parameter.Type;
if (Flags.Includes(BinderFlags.ParameterDefaultValue))
{
// This is only expected to occur in recursive error scenarios, for example: `object F(object param = F()) { }`
// We return a non-error expression here to ensure ERR_DefaultValueMustBeConstant (or another appropriate diagnostics) is produced by the caller.
return new BoundDefaultExpression(syntax, parameterType) { WasCompilerGenerated = true };
}
var parameterDefaultValue = parameter.ExplicitDefaultConstantValue;
if (InAttributeArgument && parameterDefaultValue?.IsBad == true)
{
diagnostics.Add(ErrorCode.ERR_BadAttributeArgument, syntax.Location);
return BadExpression(syntax).MakeCompilerGenerated();
}
var defaultConstantValue = parameterDefaultValue switch
{
// Bad default values are implicitly replaced with default(T) at call sites.
{ IsBad: true } => ConstantValue.Null,
var constantValue => constantValue
};
Debug.Assert((object?)defaultConstantValue != ConstantValue.Unset);
var discardedUseSiteInfo = CompoundUseSiteInfo<AssemblySymbol>.Discarded;
var callerSourceLocation = enableCallerInfo ? GetCallerLocation(syntax) : null;
BoundExpression defaultValue;
if (callerSourceLocation is object && parameter.IsCallerLineNumber)
{
int line = callerSourceLocation.SourceTree.GetDisplayLineNumber(callerSourceLocation.SourceSpan);
defaultValue = new BoundLiteral(syntax, ConstantValue.Create(line), Compilation.GetSpecialType(SpecialType.System_Int32)) { WasCompilerGenerated = true };
}
else if (callerSourceLocation is object && parameter.IsCallerFilePath)
{
string path = callerSourceLocation.SourceTree.GetDisplayPath(callerSourceLocation.SourceSpan, Compilation.Options.SourceReferenceResolver);
defaultValue = new BoundLiteral(syntax, ConstantValue.Create(path), Compilation.GetSpecialType(SpecialType.System_String)) { WasCompilerGenerated = true };
}
else if (callerSourceLocation is object && parameter.IsCallerMemberName && containingMember is not null)
{
var memberName = containingMember.GetMemberCallerName();
defaultValue = new BoundLiteral(syntax, ConstantValue.Create(memberName), Compilation.GetSpecialType(SpecialType.System_String)) { WasCompilerGenerated = true };
}
else if (callerSourceLocation is object
&& !parameter.IsCallerMemberName
&& Conversions.ClassifyBuiltInConversion(Compilation.GetSpecialType(SpecialType.System_String), parameterType, isChecked: false, ref discardedUseSiteInfo).Exists
&& getArgumentIndex(parameter.CallerArgumentExpressionParameterIndex, argsToParamsOpt) is int argumentIndex
&& argumentIndex > -1 && argumentIndex < argumentsCount)
{
var argument = argumentsBuilder[argumentIndex];
defaultValue = new BoundLiteral(syntax, ConstantValue.Create(argument.Syntax.ToString()), Compilation.GetSpecialType(SpecialType.System_String)) { WasCompilerGenerated = true };
}
else if (defaultConstantValue == ConstantValue.NotAvailable)
{
// There is no constant value given for the parameter in source/metadata.
if (parameterType.IsDynamic() || parameterType.SpecialType == SpecialType.System_Object)
{
// We have something like M([Optional] object x). We have special handling for such situations.
defaultValue = GetDefaultParameterSpecialNoConversion(syntax, parameter, diagnostics);
}
else
{
// The argument to M([Optional] int x) becomes default(int)
defaultValue = new BoundDefaultExpression(syntax, parameterType) { WasCompilerGenerated = true };
}
}
else if (defaultConstantValue.IsNull)
{
defaultValue = new BoundDefaultExpression(syntax, parameterType) { WasCompilerGenerated = true };
}
else
{
TypeSymbol constantType = Compilation.GetSpecialType(defaultConstantValue.SpecialType);
defaultValue = new BoundLiteral(syntax, defaultConstantValue, constantType) { WasCompilerGenerated = true };
if (InAttributeArgument && parameterType.SpecialType == SpecialType.System_Object)
{
// error CS1763: '{0}' is of type '{1}'. A default parameter value of a reference type other than string can only be initialized with null
diagnostics.Add(ErrorCode.ERR_NotNullRefDefaultParameter, syntax.Location, parameter.Name, parameterType);
}
}
CompoundUseSiteInfo<AssemblySymbol> useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics);
Conversion conversion = Conversions.ClassifyConversionFromExpression(defaultValue, parameterType, isChecked: CheckOverflowAtRuntime, ref useSiteInfo);
diagnostics.Add(syntax, useSiteInfo);
if (!conversion.IsValid && defaultConstantValue is { SpecialType: SpecialType.System_Decimal or SpecialType.System_DateTime })
{
// Usually, if a default constant value fails to convert to the parameter type, we want an error at the call site.
// For legacy reasons, decimal and DateTime constants are special. If such a constant fails to convert to the parameter type
// then we want to silently replace it with default(ParameterType).
defaultValue = new BoundDefaultExpression(syntax, parameterType) { WasCompilerGenerated = true };
}
else
{
if (!conversion.IsValid)
{
GenerateImplicitConversionError(diagnostics, syntax, conversion, defaultValue, parameterType);
}
var isCast = conversion.IsExplicit;
defaultValue = CreateConversion(
defaultValue.Syntax,
defaultValue,
conversion,
isCast,
isCast ? new ConversionGroup(conversion, parameter.TypeWithAnnotations) : null,
parameterType,
diagnostics);
}
return defaultValue;
static int getArgumentIndex(int parameterIndex, ImmutableArray<int> argsToParamsOpt)
=> argsToParamsOpt.IsDefault
? parameterIndex
: argsToParamsOpt.IndexOf(parameterIndex);
}
}
private BoundExpression CreateParamsCollection(SyntaxNode node, ParameterSymbol paramsParameter, ImmutableArray<BoundExpression> collectionArgs, BindingDiagnosticBag diagnostics)
{
TypeSymbol collectionType = paramsParameter.Type;
BoundExpression collection;
if (collectionType is ArrayTypeSymbol { IsSZArray: true })
{
TypeSymbol int32Type = GetSpecialType(SpecialType.System_Int32, diagnostics, node);
BoundExpression arraySize = new BoundLiteral(node, ConstantValue.Create(collectionArgs.Length), int32Type) { WasCompilerGenerated = true };
collection = new BoundArrayCreation(
node,
ImmutableArray.Create(arraySize),
new BoundArrayInitialization(node, isInferred: false, collectionArgs) { WasCompilerGenerated = true },
collectionType)
{ WasCompilerGenerated = true, IsParamsArrayOrCollection = true };
}
else
{
if (Compilation.SourceModule != paramsParameter.ContainingModule)
{
MessageID.IDS_FeatureParamsCollections.CheckFeatureAvailability(diagnostics, node);
}
var unconvertedCollection = new BoundUnconvertedCollectionExpression(node, ImmutableArray<BoundNode>.CastUp(collectionArgs)) { WasCompilerGenerated = true, IsParamsArrayOrCollection = true };
CompoundUseSiteInfo<AssemblySymbol> useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics);
Conversion conversion = Conversions.ClassifyImplicitConversionFromExpression(unconvertedCollection, collectionType, ref useSiteInfo);
diagnostics.Add(node, useSiteInfo);
BoundCollectionExpression converted;
if (!conversion.Exists)
{
Debug.Assert(false); // Add test if this code path is reachable
GenerateImplicitConversionErrorForCollectionExpression(unconvertedCollection, collectionType, diagnostics);
converted = BindCollectionExpressionForErrorRecovery(unconvertedCollection, collectionType, inConversion: true, diagnostics);
}
else
{
Debug.Assert(conversion.IsCollectionExpression);
converted = ConvertCollectionExpression(unconvertedCollection, collectionType, conversion, diagnostics);
}
collection = new BoundConversion(
node,
converted,
conversion,
@checked: CheckOverflowAtRuntime,
explicitCastInCode: false,
conversionGroupOpt: null,
constantValueOpt: null,
type: collectionType)
{ WasCompilerGenerated = true, IsParamsArrayOrCollection = true };
}
Debug.Assert(collection.IsParamsArrayOrCollection);
return collection;
}
#nullable disable
/// <summary>
/// Returns false if an implicit 'this' copy will occur due to an instance member invocation in a readonly member.
/// </summary>
internal bool CheckImplicitThisCopyInReadOnlyMember(BoundExpression receiver, MethodSymbol method, BindingDiagnosticBag diagnostics)
{
// For now we are warning only in implicit copy scenarios that are only possible with readonly members.
// Eventually we will warn on implicit value copies in more scenarios. See https://github.com/dotnet/roslyn/issues/33968.
if (receiver?.IsEquivalentToThisReference == true &&
receiver.Type.IsValueType &&
ContainingMemberOrLambda is MethodSymbol containingMethod &&
containingMethod.IsEffectivelyReadOnly &&
// Ignore calls to base members.
TypeSymbol.Equals(containingMethod.ContainingType, method.ContainingType, TypeCompareKind.ConsiderEverything) &&
!method.IsEffectivelyReadOnly &&
method.RequiresInstanceReceiver)
{
Error(diagnostics, ErrorCode.WRN_ImplicitCopyInReadOnlyMember, receiver.Syntax, method, ThisParameterSymbol.SymbolName);
return false;
}
return true;
}
/// <param name="node">Invocation syntax node.</param>
/// <param name="expression">The syntax for the invoked method, including receiver.</param>
private static Location GetLocationForOverloadResolutionDiagnostic(SyntaxNode node, SyntaxNode expression)
{
if (node != expression)
{
switch (expression.Kind())
{
case SyntaxKind.QualifiedName:
return ((QualifiedNameSyntax)expression).Right.GetLocation();
case SyntaxKind.SimpleMemberAccessExpression:
case SyntaxKind.PointerMemberAccessExpression:
return ((MemberAccessExpressionSyntax)expression).Name.GetLocation();
}
}
return expression.GetLocation();
}
/// <summary>
/// Replace a BoundTypeOrValueExpression with a BoundExpression for either a type (if useType is true)
/// or a value (if useType is false). Any other node is bound to its natural type.
/// </summary>
/// <remarks>
/// Call this once overload resolution has succeeded on the method group of which the BoundTypeOrValueExpression
/// is the receiver. Generally, useType will be true if the chosen method is static and false otherwise.
/// </remarks>
private BoundExpression ReplaceTypeOrValueReceiver(BoundExpression receiver, bool useType, BindingDiagnosticBag diagnostics)
{
if ((object)receiver == null)
{
return null;
}
switch (receiver.Kind)
{
case BoundKind.TypeOrValueExpression:
var typeOrValue = (BoundTypeOrValueExpression)receiver;
if (useType)
{
diagnostics.AddRange(typeOrValue.Data.TypeDiagnostics);
foreach (Diagnostic d in typeOrValue.Data.ValueDiagnostics.Diagnostics)
{
// Avoid forcing resolution of lazy diagnostics to avoid cycles.
var code = d is DiagnosticWithInfo { HasLazyInfo: true, LazyInfo.Code: var lazyCode } ? lazyCode : d.Code;
if (code == (int)ErrorCode.WRN_PrimaryConstructorParameterIsShadowedAndNotPassedToBase &&
!(d.Arguments is [ParameterSymbol shadowedParameter] && shadowedParameter.Type.Equals(typeOrValue.Data.ValueExpression.Type, TypeCompareKind.AllIgnoreOptions))) // If the type and the name match, we would resolve to the same type rather than a value at the end.
{
Debug.Assert(d is not DiagnosticWithInfo { HasLazyInfo: true }, "Adjust the Arguments access to handle lazy diagnostics to avoid cycles.");
diagnostics.Add(d);
}
}
return typeOrValue.Data.TypeExpression;
}
else
{
diagnostics.AddRange(typeOrValue.Data.ValueDiagnostics);
return CheckValue(typeOrValue.Data.ValueExpression, BindValueKind.RValue, diagnostics);
}
case BoundKind.QueryClause:
// a query clause may wrap a TypeOrValueExpression.
var q = (BoundQueryClause)receiver;
var value = q.Value;
var replaced = ReplaceTypeOrValueReceiver(value, useType, diagnostics);
return (value == replaced) ? q : q.Update(replaced, q.DefinedSymbol, q.Operation, q.Cast, q.Binder, q.UnoptimizedForm, q.Type);
default:
return BindToNaturalType(receiver, diagnostics);
}
}
private static BoundExpression GetValueExpressionIfTypeOrValueReceiver(BoundExpression receiver)
{
if ((object)receiver == null)
{
return null;
}
switch (receiver)
{
case BoundTypeOrValueExpression typeOrValueExpression:
return typeOrValueExpression.Data.ValueExpression;
case BoundQueryClause queryClause:
// a query clause may wrap a TypeOrValueExpression.
return GetValueExpressionIfTypeOrValueReceiver(queryClause.Value);
default:
return null;
}
}
/// <summary>
/// Return the delegate type if this expression represents a delegate.
/// </summary>
private static NamedTypeSymbol GetDelegateType(BoundExpression expr)
{
if ((object)expr != null && expr.Kind != BoundKind.TypeExpression)
{
var type = expr.Type as NamedTypeSymbol;
if (((object)type != null) && type.IsDelegateType())
{
return type;
}
}
return null;
}
private BoundCall CreateBadCall(
SyntaxNode node,
string name,
BoundExpression receiver,
ImmutableArray<MethodSymbol> methods,
LookupResultKind resultKind,
ImmutableArray<TypeWithAnnotations> typeArgumentsWithAnnotations,
AnalyzedArguments analyzedArguments,
bool invokedAsExtensionMethod,
bool isDelegate)
{
MethodSymbol method;
ImmutableArray<BoundExpression> args;
if (!typeArgumentsWithAnnotations.IsDefaultOrEmpty)
{
var constructedMethods = ArrayBuilder<MethodSymbol>.GetInstance();
foreach (var m in methods)
{
constructedMethods.Add(m.ConstructedFrom == m && m.Arity == typeArgumentsWithAnnotations.Length ? m.Construct(typeArgumentsWithAnnotations) : m);
}
methods = constructedMethods.ToImmutableAndFree();
}
if (methods.Length == 1 && !IsUnboundGeneric(methods[0]))
{
method = methods[0];
}
else
{
var returnType = GetCommonTypeOrReturnType(methods) ?? new ExtendedErrorTypeSymbol(this.Compilation, string.Empty, arity: 0, errorInfo: null);
var methodContainer = (object)receiver != null && (object)receiver.Type != null
? receiver.Type
: this.ContainingType;
method = new ErrorMethodSymbol(methodContainer, returnType, name);
}
args = BuildArgumentsForErrorRecovery(analyzedArguments, methods);
var argNames = analyzedArguments.GetNames();
var argRefKinds = analyzedArguments.RefKinds.ToImmutableOrNull();
receiver = BindToTypeForErrorRecovery(receiver);
return BoundCall.ErrorCall(node, receiver, method, args, argNames, argRefKinds, isDelegate, invokedAsExtensionMethod: invokedAsExtensionMethod, originalMethods: methods, resultKind: resultKind, binder: this);
}
private static bool IsUnboundGeneric(MethodSymbol method)
{
return method.IsGenericMethod && method.ConstructedFrom == method;
}
// Arbitrary limit on the number of parameter lists from overload
// resolution candidates considered when binding argument types.
// Any additional parameter lists are ignored.
internal const int MaxParameterListsForErrorRecovery = 10;
private ImmutableArray<BoundExpression> BuildArgumentsForErrorRecovery(AnalyzedArguments analyzedArguments, ImmutableArray<MethodSymbol> methods)
{
var parameterListList = ArrayBuilder<ImmutableArray<ParameterSymbol>>.GetInstance();
foreach (var m in methods)
{
if (!IsUnboundGeneric(m) && m.ParameterCount > 0)
{
parameterListList.Add(m.Parameters);
if (parameterListList.Count == MaxParameterListsForErrorRecovery)
{
break;
}
}
}
var result = BuildArgumentsForErrorRecovery(analyzedArguments, parameterListList);
parameterListList.Free();
return result;
}
private ImmutableArray<BoundExpression> BuildArgumentsForErrorRecovery(AnalyzedArguments analyzedArguments, ImmutableArray<PropertySymbol> properties)
{
var parameterListList = ArrayBuilder<ImmutableArray<ParameterSymbol>>.GetInstance();
foreach (var p in properties)
{
if (p.ParameterCount > 0)
{
parameterListList.Add(p.Parameters);
if (parameterListList.Count == MaxParameterListsForErrorRecovery)
{
break;
}
}
}
var result = BuildArgumentsForErrorRecovery(analyzedArguments, parameterListList);
parameterListList.Free();
return result;
}
private ImmutableArray<BoundExpression> BuildArgumentsForErrorRecovery(AnalyzedArguments analyzedArguments, IEnumerable<ImmutableArray<ParameterSymbol>> parameterListList)
{
int argumentCount = analyzedArguments.Arguments.Count;
ArrayBuilder<BoundExpression> newArguments = ArrayBuilder<BoundExpression>.GetInstance(argumentCount);
newArguments.AddRange(analyzedArguments.Arguments);
for (int i = 0; i < argumentCount; i++)
{
var argument = newArguments[i];
switch (argument.Kind)
{
case BoundKind.UnboundLambda:
{
var unboundArgument = (UnboundLambda)argument;
// If nested in other lambdas where type inference is involved,
// the target delegate type could be different each time.
// But if the lambda is explicitly typed, we can bind only once.
// https://github.com/dotnet/roslyn/issues/69093
if (unboundArgument.HasExplicitlyTypedParameterList &&
unboundArgument.HasExplicitReturnType(out _, out _) &&
unboundArgument.FunctionType is { } functionType &&
functionType.GetInternalDelegateType() is { } delegateType)
{
// Just assume we're not in an expression tree for the purposes of error recovery.
_ = unboundArgument.Bind(delegateType, isExpressionTree: false);
}
else
{
// bind the argument against each applicable parameter
foreach (var parameterList in parameterListList)
{
var parameterType = GetCorrespondingParameterType(analyzedArguments, i, parameterList);
if (parameterType?.Kind == SymbolKind.NamedType &&
(object)parameterType.GetDelegateType() != null)
{
// Just assume we're not in an expression tree for the purposes of error recovery.
var discarded = unboundArgument.Bind((NamedTypeSymbol)parameterType, isExpressionTree: false);
}
}
}
// replace the unbound lambda with its best inferred bound version
newArguments[i] = unboundArgument.BindForErrorRecovery();
break;
}
case BoundKind.OutVariablePendingInference:
case BoundKind.DiscardExpression:
{
if (argument.HasExpressionType())
{
break;
}
var candidateType = getCorrespondingParameterType(i);
if (argument.Kind == BoundKind.OutVariablePendingInference)
{
if ((object)candidateType == null)
{
newArguments[i] = ((OutVariablePendingInference)argument).FailInference(this, null);
}
else
{
newArguments[i] = ((OutVariablePendingInference)argument).SetInferredTypeWithAnnotations(TypeWithAnnotations.Create(candidateType), null);
}
}
else if (argument.Kind == BoundKind.DiscardExpression)
{
if ((object)candidateType == null)
{
newArguments[i] = ((BoundDiscardExpression)argument).FailInference(this, null);
}
else
{
newArguments[i] = ((BoundDiscardExpression)argument).SetInferredTypeWithAnnotations(TypeWithAnnotations.Create(candidateType));
}
}
break;
}
case BoundKind.OutDeconstructVarPendingInference:
{
newArguments[i] = ((OutDeconstructVarPendingInference)argument).FailInference(this);
break;
}
case BoundKind.Parameter:
case BoundKind.Local:
{
newArguments[i] = BindToTypeForErrorRecovery(argument);
break;
}
default:
{
newArguments[i] = BindToTypeForErrorRecovery(argument, getCorrespondingParameterType(i));
break;
}
}
}
return newArguments.ToImmutableAndFree();
TypeSymbol getCorrespondingParameterType(int i)
{
// See if all applicable parameters have the same type
TypeSymbol candidateType = null;
foreach (var parameterList in parameterListList)
{
var parameterType = GetCorrespondingParameterType(analyzedArguments, i, parameterList);
if ((object)parameterType != null)
{
if ((object)candidateType == null)
{
candidateType = parameterType;
}
else if (!candidateType.Equals(parameterType, TypeCompareKind.IgnoreCustomModifiersAndArraySizesAndLowerBounds | TypeCompareKind.IgnoreNullableModifiersForReferenceTypes))
{
// type mismatch
candidateType = null;
break;
}
}
}
return candidateType;
}
}
/// <summary>
/// Compute the type of the corresponding parameter, if any. This is used to improve error recovery,
/// for bad invocations, not for semantic analysis of correct invocations, so it is a heuristic.
/// If no parameter appears to correspond to the given argument, we return null.
/// </summary>
/// <param name="analyzedArguments">The analyzed argument list</param>
/// <param name="i">The index of the argument</param>
/// <param name="parameterList">The parameter list to match against</param>
/// <returns>The type of the corresponding parameter.</returns>
private static TypeSymbol GetCorrespondingParameterType(AnalyzedArguments analyzedArguments, int i, ImmutableArray<ParameterSymbol> parameterList)
{
string name = analyzedArguments.Name(i);
if (name != null)
{
// look for a parameter by that name
foreach (var parameter in parameterList)
{
if (parameter.Name == name) return parameter.Type;
}
return null;
}
return (i < parameterList.Length) ? parameterList[i].Type : null;
// CONSIDER: should we handle variable argument lists?
}
/// <summary>
/// Absent parameter types to bind the arguments, we simply use the arguments provided for error recovery.
/// </summary>
private ImmutableArray<BoundExpression> BuildArgumentsForErrorRecovery(AnalyzedArguments analyzedArguments)
{
return BuildArgumentsForErrorRecovery(analyzedArguments, Enumerable.Empty<ImmutableArray<ParameterSymbol>>());
}
private BoundCall CreateBadCall(
SyntaxNode node,
BoundExpression expr,
LookupResultKind resultKind,
AnalyzedArguments analyzedArguments)
{
TypeSymbol returnType = new ExtendedErrorTypeSymbol(this.Compilation, string.Empty, arity: 0, errorInfo: null);
var methodContainer = expr.Type ?? this.ContainingType;
MethodSymbol method = new ErrorMethodSymbol(methodContainer, returnType, string.Empty);
var args = BuildArgumentsForErrorRecovery(analyzedArguments);
var argNames = analyzedArguments.GetNames();
var argRefKinds = analyzedArguments.RefKinds.ToImmutableOrNull();
var originalMethods = (expr.Kind == BoundKind.MethodGroup) ? ((BoundMethodGroup)expr).Methods : ImmutableArray<MethodSymbol>.Empty;
return BoundCall.ErrorCall(node, expr, method, args, argNames, argRefKinds, isDelegateCall: false, invokedAsExtensionMethod: false, originalMethods: originalMethods, resultKind: resultKind, binder: this);
}
private static TypeSymbol GetCommonTypeOrReturnType<TMember>(ImmutableArray<TMember> members)
where TMember : Symbol
{
TypeSymbol type = null;
for (int i = 0, n = members.Length; i < n; i++)
{
TypeSymbol returnType = members[i].GetTypeOrReturnType().Type;
if ((object)type == null)
{
type = returnType;
}
else if (!TypeSymbol.Equals(type, returnType, TypeCompareKind.ConsiderEverything2))
{
return null;
}
}
return type;
}
private bool TryBindNameofOperator(InvocationExpressionSyntax node, BindingDiagnosticBag diagnostics, out BoundExpression result)
{
if (node.MayBeNameofOperator())
{
var binder = this.GetBinder(node);
if (binder.EnclosingNameofArgument == node.ArgumentList.Arguments[0].Expression)
{
result = binder.BindNameofOperatorInternal(node, diagnostics);
return true;
}
}
result = null;
return false;
}
private BoundExpression BindNameofOperatorInternal(InvocationExpressionSyntax node, BindingDiagnosticBag diagnostics)
{
CheckFeatureAvailability(node, MessageID.IDS_FeatureNameof, diagnostics);
var argument = node.ArgumentList.Arguments[0].Expression;
var boundArgument = BindExpression(argument, diagnostics);
bool syntaxIsOk = CheckSyntaxForNameofArgument(argument, out string name, boundArgument.HasAnyErrors ? BindingDiagnosticBag.Discarded : diagnostics);
if (!boundArgument.HasAnyErrors && syntaxIsOk && boundArgument.Kind == BoundKind.MethodGroup)
{
var methodGroup = (BoundMethodGroup)boundArgument;
if (!methodGroup.TypeArgumentsOpt.IsDefaultOrEmpty)
{
// method group with type parameters not allowed
diagnostics.Add(ErrorCode.ERR_NameofMethodGroupWithTypeParameters, argument.Location);
}
else
{
EnsureNameofExpressionSymbols(methodGroup, diagnostics);
}
}
if (boundArgument is BoundNamespaceExpression nsExpr)
{
diagnostics.AddAssembliesUsedByNamespaceReference(nsExpr.NamespaceSymbol);
}
boundArgument = BindToNaturalType(boundArgument, diagnostics, reportNoTargetType: false);
return new BoundNameOfOperator(node, boundArgument, ConstantValue.Create(name), Compilation.GetSpecialType(SpecialType.System_String));
}
private void EnsureNameofExpressionSymbols(BoundMethodGroup methodGroup, BindingDiagnosticBag diagnostics)
{
// Check that the method group contains something applicable. Otherwise error.
CompoundUseSiteInfo<AssemblySymbol> useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics);
var resolution = ResolveMethodGroup(methodGroup, analyzedArguments: null, useSiteInfo: ref useSiteInfo, options: OverloadResolution.Options.None);
diagnostics.Add(methodGroup.Syntax, useSiteInfo);
diagnostics.AddRange(resolution.Diagnostics);
if (resolution.IsExtensionMethodGroup)
{
diagnostics.Add(ErrorCode.ERR_NameofExtensionMethod, methodGroup.Syntax.Location);
}
}
/// <summary>
/// Returns true if syntax form is OK (so no errors were reported)
/// </summary>
private bool CheckSyntaxForNameofArgument(ExpressionSyntax argument, out string name, BindingDiagnosticBag diagnostics, bool top = true)
{
switch (argument.Kind())
{
case SyntaxKind.IdentifierName:
{
var syntax = (IdentifierNameSyntax)argument;
name = syntax.Identifier.ValueText;
return true;
}
case SyntaxKind.GenericName:
{
var syntax = (GenericNameSyntax)argument;
name = syntax.Identifier.ValueText;
return true;
}
case SyntaxKind.SimpleMemberAccessExpression:
{
var syntax = (MemberAccessExpressionSyntax)argument;
bool ok = true;
switch (syntax.Expression.Kind())
{
case SyntaxKind.BaseExpression:
case SyntaxKind.ThisExpression:
break;
default:
ok = CheckSyntaxForNameofArgument(syntax.Expression, out name, diagnostics, false);
break;
}
name = syntax.Name.Identifier.ValueText;
return ok;
}
case SyntaxKind.AliasQualifiedName:
{
var syntax = (AliasQualifiedNameSyntax)argument;
bool ok = true;
if (top)
{
diagnostics.Add(ErrorCode.ERR_AliasQualifiedNameNotAnExpression, argument.Location);
ok = false;
}
name = syntax.Name.Identifier.ValueText;
return ok;
}
case SyntaxKind.ThisExpression:
case SyntaxKind.BaseExpression:
case SyntaxKind.PredefinedType:
name = "";
if (top) goto default;
return true;
default:
{
var code = top ? ErrorCode.ERR_ExpressionHasNoName : ErrorCode.ERR_SubexpressionNotInNameof;
diagnostics.Add(code, argument.Location);
name = "";
return false;
}
}
}
/// <summary>
/// Helper method that checks whether there is an invocable 'nameof' in scope.
/// </summary>
internal bool InvocableNameofInScope()
{
var lookupResult = LookupResult.GetInstance();
const LookupOptions options = LookupOptions.AllMethodsOnArityZero | LookupOptions.MustBeInvocableIfMember;
var discardedUseSiteInfo = CompoundUseSiteInfo<AssemblySymbol>.Discarded;
this.LookupSymbolsWithFallback(lookupResult, SyntaxFacts.GetText(SyntaxKind.NameOfKeyword), useSiteInfo: ref discardedUseSiteInfo, arity: 0, options: options);
var result = lookupResult.IsMultiViable;
lookupResult.Free();
return result;
}
#nullable enable
private BoundFunctionPointerInvocation BindFunctionPointerInvocation(SyntaxNode node, BoundExpression boundExpression, AnalyzedArguments analyzedArguments, BindingDiagnosticBag diagnostics)
{
boundExpression = BindToNaturalType(boundExpression, diagnostics);
RoslynDebug.Assert(boundExpression.Type is FunctionPointerTypeSymbol);
var funcPtr = (FunctionPointerTypeSymbol)boundExpression.Type;
var overloadResolutionResult = OverloadResolutionResult<FunctionPointerMethodSymbol>.GetInstance();
CompoundUseSiteInfo<AssemblySymbol> useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics);
var methodsBuilder = ArrayBuilder<FunctionPointerMethodSymbol>.GetInstance(1);
methodsBuilder.Add(funcPtr.Signature);
OverloadResolution.FunctionPointerOverloadResolution(
methodsBuilder,
analyzedArguments,
overloadResolutionResult,
ref useSiteInfo);
diagnostics.Add(node, useSiteInfo);
if (!overloadResolutionResult.Succeeded)
{
ImmutableArray<FunctionPointerMethodSymbol> methods = methodsBuilder.ToImmutableAndFree();
overloadResolutionResult.ReportDiagnostics(
binder: this,
node.Location,
nodeOpt: null,
diagnostics,
name: null,
boundExpression,
boundExpression.Syntax,
analyzedArguments,
methods,
typeContainingConstructor: null,
delegateTypeBeingInvoked: null,
returnRefKind: funcPtr.Signature.RefKind);
return new BoundFunctionPointerInvocation(
node,
boundExpression,
BuildArgumentsForErrorRecovery(analyzedArguments, StaticCast<MethodSymbol>.From(methods)),
analyzedArguments.RefKinds.ToImmutableOrNull(),
LookupResultKind.OverloadResolutionFailure,
funcPtr.Signature.ReturnType,
hasErrors: true);
}
methodsBuilder.Free();
MemberResolutionResult<FunctionPointerMethodSymbol> methodResult = overloadResolutionResult.ValidResult;
CheckAndCoerceArguments(node, methodResult, analyzedArguments, diagnostics, receiver: null, invokedAsExtensionMethod: false, argsToParamsOpt: out _);
var args = analyzedArguments.Arguments.ToImmutable();
var refKinds = analyzedArguments.RefKinds.ToImmutableOrNull();
bool hasErrors = ReportUnsafeIfNotAllowed(node, diagnostics);
return new BoundFunctionPointerInvocation(
node,
boundExpression,
args,
refKinds,
LookupResultKind.Viable,
funcPtr.Signature.ReturnType,
hasErrors);
}
}
}
|