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
63 changes: 21 additions & 42 deletions src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1619,8 +1619,7 @@ private bool IsValidDynamicCondition(BoundExpression left, bool isNegative, Bind
{
if (left.Type is not null)
{
var operandPlaceholder = new BoundValuePlaceholder(left.Syntax, left.Type).MakeCompilerGenerated();
CreateConversion(left.Syntax, operandPlaceholder, implicitConversion, isCast: false, conversionGroupOpt: null, booleanType, diagnostics);
CreateConversion(left.Syntax, new BoundValuePlaceholder(left.Syntax, left.Type).MakeCompilerGenerated(), implicitConversion, isCast: false, conversionGroupOpt: null, booleanType, diagnostics);
}
else
{
Expand All @@ -1631,7 +1630,7 @@ private bool IsValidDynamicCondition(BoundExpression left, bool isNegative, Bind
return true;
}

if (type.Kind != SymbolKind.NamedType)
if (type.Kind != SymbolKind.NamedType || type.IsNullableType())
{
diagnostics.Add(left.Syntax, useSiteInfo);
return false;
Expand All @@ -1651,52 +1650,32 @@ private bool IsValidDynamicCondition(BoundExpression left, bool isNegative, Bind
// at CallSite.Target(Closure, CallSite, Object, Nullable`1)
// at System.Dynamic.UpdateDelegates.UpdateAndExecute2[T0,T1,TRet](CallSite site, T0 arg0, T1 arg1)
var namedType = type as NamedTypeSymbol;
var result = hasApplicableBooleanOperator(namedType, isNegative ? WellKnownMemberNames.FalseOperatorName : WellKnownMemberNames.TrueOperatorName, type, ref useSiteInfo, out userDefinedOperator);
var operandPlaceholder = new BoundValuePlaceholder(left.Syntax, namedType).MakeCompilerGenerated();
UnaryOperatorAnalysisResult result = operatorOverloadResolution(left.Syntax, operandPlaceholder, isNegative ? UnaryOperatorKind.False : UnaryOperatorKind.True, diagnostics);

if (result)
if (result.HasValue)
{
Debug.Assert(userDefinedOperator is not null);
Debug.Assert(result.Conversion.IsImplicit);
userDefinedOperator = result.Signature.Method;

var operandPlaceholder = new BoundValuePlaceholder(left.Syntax, left.Type).MakeCompilerGenerated();
TypeSymbol parameterType = userDefinedOperator.Parameters[0].Type;

implicitConversion = this.Conversions.ClassifyConversionFromType(operandPlaceholder.Type, parameterType, isChecked: CheckOverflowAtRuntime, ref useSiteInfo);
Debug.Assert(implicitConversion.IsImplicit);

CreateConversion(left.Syntax, operandPlaceholder, implicitConversion, isCast: false, conversionGroupOpt: null, parameterType, diagnostics);
CreateConversion(left.Syntax, operandPlaceholder, result.Conversion, isCast: false, conversionGroupOpt: null, parameterType, diagnostics);
return true;
}

diagnostics.Add(left.Syntax, useSiteInfo);

return result;
return false;

bool hasApplicableBooleanOperator(NamedTypeSymbol containingType, string name, TypeSymbol argumentType, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo, out MethodSymbol @operator)
UnaryOperatorAnalysisResult operatorOverloadResolution(SyntaxNode node, BoundExpression operand, UnaryOperatorKind kind, BindingDiagnosticBag diagnostics)
{
var operators = ArrayBuilder<MethodSymbol>.GetInstance();
for (var type = containingType; (object)type != null; type = type.BaseTypeWithDefinitionUseSiteDiagnostics(ref useSiteInfo))
{
operators.Clear();
type.AddOperators(name, operators);
OverloadResolution.GetStaticUserDefinedUnaryOperatorMethodNames(kind, isChecked: false, out string staticOperatorName1, out string staticOperatorName2Opt);

for (var i = 0; i < operators.Count; i++)
{
var op = operators[i];
if (op.ParameterCount == 1 && op.DeclaredAccessibility == Accessibility.Public)
{
var conversion = this.Conversions.ClassifyConversionFromType(argumentType, op.GetParameterType(0), isChecked: CheckOverflowAtRuntime, ref useSiteInfo);
if (conversion.IsImplicit)
{
@operator = op;
operators.Free();
return true;
}
}
}
}
OperatorResolutionForReporting operatorResolutionForReporting = default;
var result = this.UnaryOperatorNonExtensionOverloadResolution(
kind, isChecked: false, staticOperatorName1, staticOperatorName2Opt,
operand: operand, node, diagnostics, ref operatorResolutionForReporting, resultKind: out _, originalUserDefinedOperators: out _);
operatorResolutionForReporting.Free();

operators.Free();
@operator = null;
return false;
return result;
}
}

Expand Down Expand Up @@ -2245,7 +2224,7 @@ private static BinaryOperatorAnalysisResult BinaryOperatorAnalyzeOverloadResolut
return possiblyBest;
}

private void ReportObsoleteAndFeatureAvailabilityDiagnostics(MethodSymbol operatorMethod, CSharpSyntaxNode node, BindingDiagnosticBag diagnostics)
private void ReportObsoleteAndFeatureAvailabilityDiagnostics(MethodSymbol operatorMethod, SyntaxNode node, BindingDiagnosticBag diagnostics)
{
if ((object)operatorMethod != null)
{
Expand Down Expand Up @@ -2326,7 +2305,7 @@ private UnaryOperatorAnalysisResult UnaryOperatorNonExtensionOverloadResolution(
string name1,
string name2Opt,
BoundExpression operand,
CSharpSyntaxNode node,
SyntaxNode node,
BindingDiagnosticBag diagnostics,
ref OperatorResolutionForReporting operatorResolutionForReporting,
out LookupResultKind resultKind,
Expand Down Expand Up @@ -2355,7 +2334,7 @@ UnaryOperatorAnalysisResult AnalyzeUnaryOperatorOverloadResolutionResult(
UnaryOperatorOverloadResolutionResult result,
UnaryOperatorKind kind,
BoundExpression operand,
CSharpSyntaxNode node,
SyntaxNode node,
BindingDiagnosticBag diagnostics,
out LookupResultKind resultKind,
out ImmutableArray<MethodSymbol> originalUserDefinedOperators)
Expand Down
158 changes: 158 additions & 0 deletions src/Compilers/CSharp/Test/Semantic/Semantics/OperatorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9755,6 +9755,164 @@ public static bool operator false(S2 x)
CompileAndVerify(comp, expectedOutput: "4343").VerifyDiagnostics();
}

[Fact]
[WorkItem("https://github.com/dotnet/roslyn/issues/78602")]
public void UserDefinedShortCircuitingOperators_TrueFalseIsOverloaded_Dynamic_01()
{
var src = $$$"""
public class Program
{
static void Main()
{
var x1 = new S1();
dynamic y1 = new S1();

_ = x1 && y1;
_ = x1 || y1;

var x2 = new S2();
dynamic y2 = new S2();

_ = x2 && y2;
_ = x2 || y2;
}
}

struct S1 // nullable operators come first
{
public static S1 operator &(S1 x, S1 y) => x;
public static S1 operator |(S1 x, S1 y) => x;

public static bool operator true(S1? x) => throw null;
public static bool operator false(S1? x) => throw null;

public static bool operator true(S1 x)
{
System.Console.Write(3);
return false;
}
public static bool operator false(S1 x)
{
System.Console.Write(4);
return false;
}
}

struct S2 // non-nullable operators come first
{
public static S2 operator &(S2 x, S2 y) => x;
public static S2 operator |(S2 x, S2 y) => x;
public static bool operator true(S2 x)
{
System.Console.Write(3);
return false;
}
public static bool operator false(S2 x)
{
System.Console.Write(4);
return false;
}

public static bool operator true(S2? x) => throw null;
public static bool operator false(S2? x) => throw null;
}
""";

var comp = CreateCompilation(src, targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.DebugExe);
CompileAndVerify(comp, expectedOutput: "44334433").VerifyDiagnostics();
}

[Fact]
[WorkItem("https://github.com/dotnet/roslyn/issues/78602")]
public void UserDefinedShortCircuitingOperators_TrueFalseIsOverloaded_Dynamic_02()
{
var src = $$$"""
public class Program
{
static void Main()
{
var x1 = new S1();
dynamic y1 = new S1();

_ = x1 && y1;
_ = x1 || y1;

var x2 = new S2();
dynamic y2 = new S2();

_ = x2 && y2;
_ = x2 || y2;
}
}

struct S1 {}
struct S2 {}

static class Extensions
{
extension(S1?) // nullable operators come first
{
public static bool operator true(S1? x) => throw null;
public static bool operator false(S1? x) => throw null;
}

extension(S1)
{
public static S1 operator &(S1 x, S1 y) => x;
public static S1 operator |(S1 x, S1 y) => x;
public static bool operator true(S1 x)
{
System.Console.Write(3);
return false;
}
public static bool operator false(S1 x)
{
System.Console.Write(4);
return false;
}
}

extension(S2) // non-nullable operators come first
{
public static S2 operator &(S2 x, S2 y) => x;
public static S2 operator |(S2 x, S2 y) => x;
public static bool operator true(S2 x)
{
System.Console.Write(3);
return false;
}
public static bool operator false(S2 x)
{
System.Console.Write(4);
return false;
}
}

extension(S2?)
{
public static bool operator true(S2? x) => throw null;
public static bool operator false(S2? x) => throw null;
}
}
""";

var comp = CreateCompilation(src, options: TestOptions.DebugExe);
comp.VerifyDiagnostics(
// (8,13): error CS7083: Expression must be implicitly convertible to Boolean or its type 'S1' must define operator 'false'.
// _ = x1 && y1;
Diagnostic(ErrorCode.ERR_InvalidDynamicCondition, "x1").WithArguments("S1", "false").WithLocation(8, 13),
// (9,13): error CS7083: Expression must be implicitly convertible to Boolean or its type 'S1' must define operator 'true'.
// _ = x1 || y1;
Diagnostic(ErrorCode.ERR_InvalidDynamicCondition, "x1").WithArguments("S1", "true").WithLocation(9, 13),
// (14,13): error CS7083: Expression must be implicitly convertible to Boolean or its type 'S2' must define operator 'false'.
// _ = x2 && y2;
Diagnostic(ErrorCode.ERR_InvalidDynamicCondition, "x2").WithArguments("S2", "false").WithLocation(14, 13),
// (15,13): error CS7083: Expression must be implicitly convertible to Boolean or its type 'S2' must define operator 'true'.
// _ = x2 || y2;
Diagnostic(ErrorCode.ERR_InvalidDynamicCondition, "x2").WithArguments("S2", "true").WithLocation(15, 13)
);
}

[Fact]
[WorkItem("https://github.com/dotnet/roslyn/issues/78617")]
public void UserDefinedShortCircuitingOperators_TrueFalseInBaseInterface_01()
Expand Down
Loading