Skip to content

Adjust nullability analysis for extension operators. #79103

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: features/extensions
Choose a base branch
from
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public bool Equals(BinaryOperatorSignature other)
TypeSymbol.Equals(this.LeftType, other.LeftType, TypeCompareKind.ConsiderEverything2) &&
TypeSymbol.Equals(this.RightType, other.RightType, TypeCompareKind.ConsiderEverything2) &&
TypeSymbol.Equals(this.ReturnType, other.ReturnType, TypeCompareKind.ConsiderEverything2) &&
this.Method == other.Method;
Symbol.Equals(this.Method, other.Method, TypeCompareKind.ConsiderEverything);
}

public static bool operator ==(BinaryOperatorSignature x, BinaryOperatorSignature y)
Expand Down
7 changes: 7 additions & 0 deletions src/Compilers/CSharp/Portable/BoundTree/BoundExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,13 @@ internal partial class BoundBinaryOperator

internal partial class BoundUserDefinedConditionalLogicalOperator
{
private partial void Validate()
{
Debug.Assert(LogicalOperator.ParameterCount == 2);
Debug.Assert(TrueOperator.ParameterCount == 1);
Debug.Assert(FalseOperator.ParameterCount == 1);
}

public override Symbol ExpressionSymbol
{
get { return this.LogicalOperator; }
Expand Down
4 changes: 2 additions & 2 deletions src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml
Original file line number Diff line number Diff line change
Expand Up @@ -496,7 +496,7 @@
<Field Name="Operators" Type="TupleBinaryOperatorInfo.Multiple"/>
</Node>

<Node Name="BoundUserDefinedConditionalLogicalOperator" Base="BoundBinaryOperatorBase" SkipInNullabilityRewriter="true">
<Node Name="BoundUserDefinedConditionalLogicalOperator" Base="BoundBinaryOperatorBase" SkipInNullabilityRewriter="true" HasValidate="true">
<Field Name="OperatorKind" Type="BinaryOperatorKind"/>
<Field Name="LogicalOperator" Type="MethodSymbol"/>
<Field Name="TrueOperator" Type="MethodSymbol"/>
Expand All @@ -518,7 +518,7 @@
<Field Name="OriginalUserDefinedOperatorsOpt" Type="ImmutableArray&lt;MethodSymbol&gt;" Null="Allow" SkipInNullabilityRewriter="true"/>
</Node>

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

Expand Down
46 changes: 44 additions & 2 deletions src/Compilers/CSharp/Portable/BoundTree/NullabilityRewriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,20 @@ private BoundNode VisitBinaryOperatorBase(BoundBinaryOperatorBase binaryOperator
leftChild,
right,
type!),
// https://github.com/dotnet/roslyn/issues/35031: We'll need to update logical.LogicalOperator
BoundUserDefinedConditionalLogicalOperator logical => logical.Update(logical.OperatorKind, logical.LogicalOperator, logical.TrueOperator, logical.FalseOperator, logical.TrueFalseOperandPlaceholder, logical.TrueFalseOperandConversion, logical.ConstrainedToTypeOpt, logical.ResultKind, logical.OriginalUserDefinedOperatorsOpt, leftChild, right, type!),

BoundUserDefinedConditionalLogicalOperator logical => logical.Update(
logical.OperatorKind,
GetUpdatedSymbol(logical, logical.LogicalOperator),
logical.TrueOperator,
logical.FalseOperator,
logical.TrueFalseOperandPlaceholder,
logical.TrueFalseOperandConversion,
logical.ConstrainedToTypeOpt,
logical.ResultKind,
logical.OriginalUserDefinedOperatorsOpt,
leftChild,
right,
type!),
_ => throw ExceptionUtilities.UnexpectedValue(currentBinary.Kind),
};

Expand All @@ -122,6 +134,36 @@ private BoundNode VisitBinaryOperatorBase(BoundBinaryOperatorBase binaryOperator
return currentBinary!;
}

public override BoundNode? VisitCompoundAssignmentOperator(BoundCompoundAssignmentOperator node)
{
ImmutableArray<MethodSymbol> originalUserDefinedOperatorsOpt = GetUpdatedArray(node, node.OriginalUserDefinedOperatorsOpt);
Copy link
Member

@RikkiGibson RikkiGibson Jun 24, 2025

Choose a reason for hiding this comment

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

It looks like originalUserDefinedOperatorsOpt is not used. Was that intentional?

BoundExpression left = (BoundExpression)this.Visit(node.Left);
BoundExpression right = (BoundExpression)this.Visit(node.Right);
BoundValuePlaceholder? leftPlaceholder = node.LeftPlaceholder;
BoundExpression? leftConversion = node.LeftConversion;
BoundValuePlaceholder? finalPlaceholder = node.FinalPlaceholder;
BoundExpression? finalConversion = node.FinalConversion;
BoundCompoundAssignmentOperator updatedNode;

var op = node.Operator;

if (op.Method is not null)
{
op = new BinaryOperatorSignature(op.Kind, op.LeftType, op.RightType, op.ReturnType, GetUpdatedSymbol(node, op.Method), op.ConstrainedToTypeOpt);
}

if (_updatedNullabilities.TryGetValue(node, out (NullabilityInfo Info, TypeSymbol? Type) infoAndType))
{
updatedNode = node.Update(op, left, right, leftPlaceholder, leftConversion, finalPlaceholder, finalConversion, node.ResultKind, node.OriginalUserDefinedOperatorsOpt, infoAndType.Type!);
updatedNode.TopLevelNullability = infoAndType.Info;
}
else
{
updatedNode = node.Update(op, left, right, leftPlaceholder, leftConversion, finalPlaceholder, finalConversion, node.ResultKind, node.OriginalUserDefinedOperatorsOpt, node.Type);
}
return updatedNode;
}

private T GetUpdatedSymbol<T>(BoundNode expr, T sym) where T : Symbol?
{
if (sym is null) return sym;
Expand Down
33 changes: 27 additions & 6 deletions src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs
Original file line number Diff line number Diff line change
Expand Up @@ -628,7 +628,7 @@ protected void VisitCondition(BoundExpression node)
AdjustConditionalState(node);
}

private void AdjustConditionalState(BoundExpression node)
protected void AdjustConditionalState(BoundExpression node)
{
if (IsConstantTrue(node))
{
Expand Down Expand Up @@ -2450,14 +2450,36 @@ private void VisitBinaryLogicalOperatorChildren(BoundExpression node)
stack.Push(binary);
}

VisitBinaryLogicalOperatorChildren(stack);
stack.Free();
}

protected virtual void VisitBinaryLogicalOperatorChildren(ArrayBuilder<BoundExpression> stack)
{
BoundExpression binary;
Debug.Assert(stack.Count > 0);

binary = stack.Pop();

BoundExpression child;
switch (binary.Kind)
{
case BoundKind.BinaryOperator:
var binOp = (BoundBinaryOperator)binary;
child = binOp.Left;
break;
case BoundKind.UserDefinedConditionalLogicalOperator:
var udBinOp = (BoundUserDefinedConditionalLogicalOperator)binary;
child = udBinOp.Left;
break;
default:
throw ExceptionUtilities.UnexpectedValue(binary.Kind);
}

VisitCondition(child);

while (true)
{
binary = stack.Pop();

BinaryOperatorKind kind;
BoundExpression right;
switch (binary.Kind)
Expand All @@ -2479,6 +2501,7 @@ private void VisitBinaryLogicalOperatorChildren(BoundExpression node)
var op = kind.Operator();
var isAnd = op == BinaryOperatorKind.And;
var isBool = kind.OperandTypes() == BinaryOperatorKind.Bool;
Debug.Assert(!isBool || binary.Kind != BoundKind.UserDefinedConditionalLogicalOperator);

Debug.Assert(isAnd || op == BinaryOperatorKind.Or);

Expand All @@ -2494,10 +2517,8 @@ private void VisitBinaryLogicalOperatorChildren(BoundExpression node)
}

AdjustConditionalState(binary);
binary = stack.Pop();
}

Debug.Assert((object)binary == node);
stack.Free();
}

protected virtual void AfterLeftChildOfBinaryLogicalOperatorHasBeenVisited(BoundExpression binary, BoundExpression right, bool isAnd, bool isBool, ref TLocalState leftTrue, ref TLocalState leftFalse)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,8 @@ private void VisitForEachEnumeratorInfo(ForEachEnumeratorInfo enumeratorInfo)

public override BoundNode? VisitCompoundAssignmentOperator(BoundCompoundAssignmentOperator node)
{
if (node.LeftConversion is BoundConversion leftConversion)
if (node.LeftConversion is BoundConversion leftConversion &&
!(node.Operator.Method is { IsStatic: false } method && method.GetIsNewExtensionMember()))
{
VerifyExpression(leftConversion);
}
Expand Down
Loading