Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 42 additions & 2 deletions src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3761,6 +3761,11 @@ internal SafeContext GetRefEscape(BoundExpression expr, SafeContext localScopeDe
{
var call = (BoundCall)expr;

if (call.IsErroneousNode)
{
return SafeContext.CallingMethod;
}

var methodSymbol = call.Method;
if (methodSymbol.RefKind == RefKind.None)
{
Expand Down Expand Up @@ -3820,6 +3825,11 @@ internal SafeContext GetRefEscape(BoundExpression expr, SafeContext localScopeDe
return SafeContext.CallingMethod;

case BoundCall call:
if (call.IsErroneousNode)
{
return SafeContext.CallingMethod;
}

var methodSymbol = call.Method;
if (methodSymbol.RefKind == RefKind.None)
{
Expand Down Expand Up @@ -4043,6 +4053,11 @@ internal bool CheckRefEscape(SyntaxNode node, BoundExpression expr, SafeContext
{
var call = (BoundCall)expr;

if (call.IsErroneousNode)
{
return true;
}

var methodSymbol = call.Method;
if (methodSymbol.RefKind == RefKind.None)
{
Expand Down Expand Up @@ -4108,6 +4123,11 @@ internal bool CheckRefEscape(SyntaxNode node, BoundExpression expr, SafeContext
return true;

case BoundCall call:
if (call.IsErroneousNode)
{
return true;
}

var methodSymbol = call.Method;
if (methodSymbol.RefKind == RefKind.None)
{
Expand Down Expand Up @@ -4405,6 +4425,11 @@ internal SafeContext GetValEscape(BoundExpression expr, SafeContext localScopeDe
{
var call = (BoundCall)expr;

if (call.IsErroneousNode)
{
return SafeContext.CallingMethod;
}

return GetInvocationEscapeScope(
MethodInvocationInfo.FromCall(call),
localScopeDepth,
Expand Down Expand Up @@ -4451,6 +4476,11 @@ internal SafeContext GetValEscape(BoundExpression expr, SafeContext localScopeDe
return localScopeDepth;

case BoundCall call:
if (call.IsErroneousNode)
{
return SafeContext.CallingMethod;
}

return GetInvocationEscapeScope(
MethodInvocationInfo.FromCall(call, implicitIndexerAccess.Receiver),
localScopeDepth,
Expand Down Expand Up @@ -5082,6 +5112,11 @@ internal bool CheckValEscape(SyntaxNode node, BoundExpression expr, SafeContext
case BoundKind.Call:
{
var call = (BoundCall)expr;
if (call.IsErroneousNode)
{
return true;
}

var methodSymbol = call.Method;

return CheckInvocationEscape(
Expand Down Expand Up @@ -5146,6 +5181,11 @@ internal bool CheckValEscape(SyntaxNode node, BoundExpression expr, SafeContext
return false;

case BoundCall call:
if (call.IsErroneousNode)
{
return true;
}

var methodSymbol = call.Method;

return CheckInvocationEscape(
Expand Down Expand Up @@ -5820,7 +5860,7 @@ SafeContext getPartsScope(BoundInterpolatedString interpolatedString, SafeContex

foreach (var part in interpolatedString.Parts)
{
if (part is not BoundCall call)
if (part is not BoundCall { IsErroneousNode: false } call)
{
// Dynamic calls cannot have ref struct parameters.
continue;
Expand Down Expand Up @@ -5862,7 +5902,7 @@ bool checkParts(BoundInterpolatedString interpolatedString, SafeContext escapeFr
{
foreach (var part in interpolatedString.Parts)
{
if (part is not BoundCall call)
if (part is not BoundCall { IsErroneousNode: false } call)
{
// Dynamic calls cannot have ref struct parameters.
continue;
Expand Down
5 changes: 5 additions & 0 deletions src/Compilers/CSharp/Portable/Binder/Binder_Await.cs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ private bool CouldBeAwaited(BoundExpression expression)
}

var call = (BoundCall)expression;
Debug.Assert(!call.IsErroneousNode);

// First check if the target method is async.
if ((object)call.Method != null && call.Method.IsAsync)
Expand Down Expand Up @@ -591,6 +592,8 @@ private bool GetGetAwaiterMethod(BoundExpression expression, SyntaxNode node, Bi
}

var call = (BoundCall)getAwaiterCall;
Debug.Assert(!call.IsErroneousNode);

var getAwaiterMethod = call.Method;
if (getAwaiterMethod is ErrorMethodSymbol ||
call.Expanded || HasOptionalParameters(getAwaiterMethod) || // We might have been able to resolve a GetAwaiter overload with optional parameters, so check for that here
Expand Down Expand Up @@ -699,6 +702,8 @@ private bool GetGetResultMethod(BoundExpression awaiterExpression, SyntaxNode no
}

var call = (BoundCall)getAwaiterGetResultCall;
Debug.Assert(!call.IsErroneousNode);

getResultMethod = call.Method;
if (getResultMethod.IsExtensionMethod || getResultMethod.GetIsNewExtensionMember())
{
Expand Down
4 changes: 2 additions & 2 deletions src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ internal BoundExpression MakeInvocationExpression(
// the error has already been reported by BindInvocationExpression
Debug.Assert(diagnostics.DiagnosticBag is null || diagnostics.HasAnyErrors());

result = CreateBadCall(node, boundExpression, LookupResultKind.Viable, analyzedArguments);
result = CreateBadCall(node, boundExpression, LookupResultKind.NotInvocable, analyzedArguments);
}

result.WasCompilerGenerated = true;
Expand Down Expand Up @@ -369,7 +369,7 @@ private BoundExpression BindInvocationExpression(
{
if (ReportDelegateInvokeUseSiteDiagnostic(diagnostics, delegateType, node: node))
{
return CreateBadCall(node, boundExpression, LookupResultKind.Viable, analyzedArguments);
return CreateBadCall(node, boundExpression, LookupResultKind.NotInvocable, analyzedArguments);
}

result = BindDelegateInvocation(node, expression, methodName, boundExpression, analyzedArguments, diagnostics, queryClause, delegateType);
Expand Down
5 changes: 5 additions & 0 deletions src/Compilers/CSharp/Portable/Binder/Binder_Query.cs
Original file line number Diff line number Diff line change
Expand Up @@ -682,6 +682,11 @@ private void ReduceFrom(FromClauseSyntax from, QueryTranslationState state, Bind

private static BoundExpression? ExtractCastInvocation(BoundCall invocation)
{
if (invocation.IsErroneousNode)
{
return null;
}

int index = invocation.InvokedAsExtensionMethod ? 1 : 0;
var c1 = invocation.Arguments[index] as BoundConversion;
var l1 = c1 != null ? c1.Operand as BoundLambda : null;
Expand Down
41 changes: 35 additions & 6 deletions src/Compilers/CSharp/Portable/Binder/RefSafetyAnalysis.cs
Original file line number Diff line number Diff line change
Expand Up @@ -757,31 +757,60 @@ private void VisitArgumentsAndGetArgumentPlaceholders(BoundExpression? receiverO

BeforeVisitingSkippedBoundCallChildren(node);

VisitReceiver(in methodInvocationInfo);
visitReceiver(node, in methodInvocationInfo);

var nodeAndInvocationInfo = (call: node, methodInvocationInfo);
do
{
VisitArguments(nodeAndInvocationInfo.call, in nodeAndInvocationInfo.methodInvocationInfo);
visitArguments(nodeAndInvocationInfo.call, in nodeAndInvocationInfo.methodInvocationInfo);
}
while (calls.TryPop(out nodeAndInvocationInfo!));

calls.Free();
}
else
{
VisitReceiver(in methodInvocationInfo);
VisitArguments(node, in methodInvocationInfo);
visitReceiver(node, in methodInvocationInfo);
visitArguments(node, in methodInvocationInfo);
}

return null;

static MethodInvocationInfo getInvocationInfo(BoundCall node)
{
var methodInvocationInfo = MethodInvocationInfo.FromCall(node);
methodInvocationInfo = ReplaceWithExtensionImplementationIfNeeded(in methodInvocationInfo);

if (!node.IsErroneousNode)
{
methodInvocationInfo = ReplaceWithExtensionImplementationIfNeeded(in methodInvocationInfo);
}

return methodInvocationInfo;
}

void visitReceiver(BoundCall node, ref readonly MethodInvocationInfo methodInvocationInfo)
{
if (node.IsErroneousNode)
{
Visit(node.ReceiverOpt);
}
else
{
VisitReceiver(in methodInvocationInfo);
}
}

void visitArguments(BoundCall node, ref readonly MethodInvocationInfo methodInvocationInfo)
{
if (node.IsErroneousNode)
Copy link
Member

@jjonescz jjonescz Sep 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why have you introduced these local functions (visitReceiver and visitArguments)? Couldn't the if (node.IsErroneousNode)s be added to the existing instance methods? #Resolved

Copy link
Contributor Author

@AlekseyTs AlekseyTs Sep 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why have you introduced these local functions

Because my intent is to adjust behavior of VisitCall method. I have no intent to affect any other existing or future callers of the other methods.

{
VisitList(node.Arguments);
}
else
{
VisitArguments(node, in methodInvocationInfo);
}
}
}

protected override void VisitReceiver(BoundCall node)
Expand Down Expand Up @@ -1092,7 +1121,7 @@ private void VisitDeconstructionArguments(ArrayBuilder<DeconstructionVariable> v
return;
}

if (invocation.Method is null)
if (invocation.IsErroneousNode || invocation.Method is null)
{
return;
}
Expand Down
30 changes: 30 additions & 0 deletions src/Compilers/CSharp/Portable/BoundTree/BoundCall.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// 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.

using System.Diagnostics;

namespace Microsoft.CodeAnalysis.CSharp
{
internal partial class BoundCall
{
public bool IsErroneousNode => ResultKind is not LookupResultKind.Viable;

private partial void Validate()
{
Debug.Assert(ResultKind is not LookupResultKind.MemberGroup);
Debug.Assert(ResultKind is not LookupResultKind.StaticInstanceMismatch);
Debug.Assert(ResultKind is LookupResultKind.Viable || HasErrors);

/* Tracking issue: https://github.com/dotnet/roslyn/issues/79426
Debug.Assert(ResultKind is LookupResultKind.Viable ||
new StackTrace(fNeedFileInfo: false).GetFrame(2)?.GetMethod() switch
{
{ Name: nameof(ErrorCall), DeclaringType: { } declaringType } => declaringType == typeof(BoundCall),
{ Name: nameof(Update), DeclaringType: { } declaringType } => declaringType == typeof(BoundCall),
_ => false
});
*/
}
}
}
12 changes: 11 additions & 1 deletion src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1823,7 +1823,7 @@
<Field Name="Properties" Type="ImmutableArray&lt;PropertySymbol&gt;" />
</Node>

<Node Name="BoundCall" Base="BoundExpression">
<Node Name="BoundCall" Base="BoundExpression" HasValidate="true">
<!-- Non-null type is required for this node kind -->
<Field Name="Type" Type="TypeSymbol" Override="true" Null="disallow"/>

Expand All @@ -1839,7 +1839,17 @@
<Field Name="InvokedAsExtensionMethod" Type="bool"/>
<Field Name="ArgsToParamsOpt" Type="ImmutableArray&lt;int&gt;" Null="allow"/>
<Field Name="DefaultArguments" Type="BitVector" />

<!--
Result kind for the invocation. Never LookupResultKind.MemberGroup or LookupResultKind.StaticInstanceMismatch.
If the value is something other than LookupResultKind.Viable, then the node represents a bad invocation,
the consistency among information stored in other properties is not guaranteed, and the Method property might
be set to an ErrorMethodSymbol. A special care should be taken when dealing with such nodes. It is recommended
to treat them similar to BoundBadExpressions nodes, unless there is a good reason to have more complicated
error recovery.
-->
<Field Name="ResultKind" PropertyOverrides="true" Type="LookupResultKind"/>

<!--The set of method symbols from which this call's method was chosen.
Only kept in the tree if the call was an error and overload resolution
was unable to choose a best method.-->
Expand Down
7 changes: 7 additions & 0 deletions src/Compilers/CSharp/Portable/BoundTree/Constructors.cs
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,15 @@ public static BoundCall ErrorCall(
Binder binder)
{
if (!originalMethods.IsEmpty)
{
resultKind = resultKind.WorseResultKind(LookupResultKind.OverloadResolutionFailure);
}
else
{
Debug.Assert(method.OriginalDefinition is ErrorMethodSymbol);
}

Debug.Assert(resultKind is not LookupResultKind.Viable);
Debug.Assert(arguments.IsDefaultOrEmpty || (object)receiverOpt != (object)arguments[0]);

return new BoundCall(
Expand Down
Loading