diff --git a/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 11.md b/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 11.md index 69b352bdb1bf5..ca7c8a21180b3 100644 --- a/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 11.md +++ b/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 11.md @@ -103,3 +103,54 @@ void Method() If your code is impacted by this breaking change, consider adding a reference to an assembly defining `System.Runtime.InteropServices.InAttribute` to your project. + +## Dynamic evaluation of `&&`/`||` operators is not allowed with the left operand statically typed as an interface. + +***Introduced in Visual Studio 2026 version 18.3*** + +The C# compiler now reports an error when an interface type is used as the left operand of +a logical `&&` or `||` operator with a `dynamic` right operand. +Previously, code would compile for an interface type with `true`/`false` operators, +but fail at runtime with a `RuntimeBinderException` because the runtime binder cannot +invoke operators defined on interfaces. + +This change prevents a runtime error by reporting it at compile time instead. The error message is: + +> error CS7083: Expression must be implicitly convertible to Boolean or its type 'I1' must not be an interface and must define operator 'false'. + +```cs +interface I1 +{ + static bool operator true(I1 x) => false; + static bool operator false(I1 x) => false; +} + +class C1 : I1 +{ + public static C1 operator &(C1 x, C1 y) => x; + public static bool operator true(C1 x) => false; + public static bool operator false(C1 x) => false; +} + +void M() +{ + I1 x = new C1(); + dynamic y = new C1(); + _ = x && y; // error CS7083: Expression must be implicitly convertible to Boolean or its type 'I1' must not be an interface and must define operator 'false'. +} +``` + +If your code is impacted by this breaking change, consider changing the static type of the left operand from an interface type to a concrete class type, +or to `dynamic` type: + +```cs +void M() +{ + I1 x = new C1(); + dynamic y = new C1(); + _ = (C1)x && y; // Valid - uses operators defined on C1 + _ = (dynamic)x && y; // Valid - uses operators defined on C1 +} +``` + +See also https://github.com/dotnet/roslyn/issues/80954. diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs index f49f4ccdc5d63..f6cfcda22996a 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs @@ -1630,7 +1630,7 @@ private bool IsValidDynamicCondition(BoundExpression left, bool isNegative, Bind return true; } - if (type.Kind != SymbolKind.NamedType || type.IsNullableType()) + if (type is not NamedTypeSymbol { IsInterface: false } namedType || namedType.IsNullableType()) { diagnostics.Add(left.Syntax, useSiteInfo); return false; @@ -1649,7 +1649,6 @@ private bool IsValidDynamicCondition(BoundExpression left, bool isNegative, Bind // Stack Trace: // 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 operandPlaceholder = new BoundValuePlaceholder(left.Syntax, namedType).MakeCompilerGenerated(); UnaryOperatorAnalysisResult result = operatorOverloadResolution(left.Syntax, operandPlaceholder, isNegative ? UnaryOperatorKind.False : UnaryOperatorKind.True, diagnostics); diff --git a/src/Compilers/CSharp/Portable/CSharpResources.resx b/src/Compilers/CSharp/Portable/CSharpResources.resx index b4b9c076fbbd6..1f92e123dbfb8 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.resx +++ b/src/Compilers/CSharp/Portable/CSharpResources.resx @@ -4251,7 +4251,7 @@ You should consider suppressing the warning only if you're sure that you don't w The CallerFilePathAttribute will have no effect; it is overridden by the CallerLineNumberAttribute - Expression must be implicitly convertible to Boolean or its type '{0}' must define operator '{1}'. + Expression must be implicitly convertible to Boolean or its type '{0}' must not be an interface and must define operator '{1}'. '{0}' cannot implement '{1}' because '{2}' is a Windows Runtime event and '{3}' is a regular .NET event. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf index 0fee9eae6d5b6..677679638353f 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf @@ -11818,8 +11818,8 @@ Potlačení upozornění zvažte jenom v případě, když určitě nechcete če - Expression must be implicitly convertible to Boolean or its type '{0}' must define operator '{1}'. - Výraz musí být implicitně převeditelný na logickou hodnotu nebo její typ {0} musí definovat operátor {1}. + Expression must be implicitly convertible to Boolean or its type '{0}' must not be an interface and must define operator '{1}'. + Výraz musí být implicitně převeditelný na logickou hodnotu nebo její typ {0} musí definovat operátor {1}. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf index 6adc8fbaf373e..116ed279c5843 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf @@ -11818,8 +11818,8 @@ Sie sollten das Unterdrücken der Warnung nur in Betracht ziehen, wenn Sie siche - Expression must be implicitly convertible to Boolean or its type '{0}' must define operator '{1}'. - Der Ausdruck muss implizit in einen booleschen Ausdruck konvertiert werden können, oder der Typ "{0}" muss den Operator "{1}" definieren. + Expression must be implicitly convertible to Boolean or its type '{0}' must not be an interface and must define operator '{1}'. + Der Ausdruck muss implizit in einen booleschen Ausdruck konvertiert werden können, oder der Typ "{0}" muss den Operator "{1}" definieren. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf index 88a84407eb3bd..7a31fd8bb29a7 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf @@ -11818,8 +11818,8 @@ Considere la posibilidad de suprimir la advertencia solo si tiene la seguridad d - Expression must be implicitly convertible to Boolean or its type '{0}' must define operator '{1}'. - La expresión se debe poder convertir implícitamente en 'Boolean' o su tipo '{0}' debe definir el operador '{1}'. + Expression must be implicitly convertible to Boolean or its type '{0}' must not be an interface and must define operator '{1}'. + La expresión se debe poder convertir implícitamente en 'Boolean' o su tipo '{0}' debe definir el operador '{1}'. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf index f39d4aea1e56a..4a5d84c0e265c 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf @@ -11818,8 +11818,8 @@ Supprimez l'avertissement seulement si vous êtes sûr de ne pas vouloir attendr - Expression must be implicitly convertible to Boolean or its type '{0}' must define operator '{1}'. - L'expression doit être explicitement convertible en booléen ou son type '{0}' doit définir l'opérateur '{1}'. + Expression must be implicitly convertible to Boolean or its type '{0}' must not be an interface and must define operator '{1}'. + L'expression doit être explicitement convertible en booléen ou son type '{0}' doit définir l'opérateur '{1}'. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf index 8429c78c0e20f..89f3faf568195 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf @@ -11818,8 +11818,8 @@ Come procedura consigliata, è consigliabile attendere sempre la chiamata. - Expression must be implicitly convertible to Boolean or its type '{0}' must define operator '{1}'. - L'espressione deve essere convertibile in modo implicito in un valore booleano oppure il relativo tipo '{0}' deve definire l'operatore '{1}'. + Expression must be implicitly convertible to Boolean or its type '{0}' must not be an interface and must define operator '{1}'. + L'espressione deve essere convertibile in modo implicito in un valore booleano oppure il relativo tipo '{0}' deve definire l'operatore '{1}'. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf index d7469555b0568..9399e317b1db4 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf @@ -11818,8 +11818,8 @@ You should consider suppressing the warning only if you're sure that you don't w - Expression must be implicitly convertible to Boolean or its type '{0}' must define operator '{1}'. - 式はブール型に暗黙的に変換できるか、式の型 '{0}' で演算子 '{1}' を定義する必要があります。 + Expression must be implicitly convertible to Boolean or its type '{0}' must not be an interface and must define operator '{1}'. + 式はブール型に暗黙的に変換できるか、式の型 '{0}' で演算子 '{1}' を定義する必要があります。 diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf index bd9d92da9e968..87e67f2cc6c05 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf @@ -11818,8 +11818,8 @@ You should consider suppressing the warning only if you're sure that you don't w - Expression must be implicitly convertible to Boolean or its type '{0}' must define operator '{1}'. - 식은 부울로 암시적으로 변환할 수 있어야 하거나 '{0}' 형식은 '{1}' 연산자를 정의해야 합니다. + Expression must be implicitly convertible to Boolean or its type '{0}' must not be an interface and must define operator '{1}'. + 식은 부울로 암시적으로 변환할 수 있어야 하거나 '{0}' 형식은 '{1}' 연산자를 정의해야 합니다. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf index ebc28c8f661ee..0026c69608764 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf @@ -11818,8 +11818,8 @@ Pominięcie ostrzeżenia należy wziąć pod uwagę tylko w sytuacji, gdy na pew - Expression must be implicitly convertible to Boolean or its type '{0}' must define operator '{1}'. - Wyrażenie musi umożliwiać niejawną konwersję na typ Boolean lub jego typ „{0}” musi definiować operator „{1}”. + Expression must be implicitly convertible to Boolean or its type '{0}' must not be an interface and must define operator '{1}'. + Wyrażenie musi umożliwiać niejawną konwersję na typ Boolean lub jego typ „{0}” musi definiować operator „{1}”. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf index ec7f0024ebbf8..d4440342d0eda 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf @@ -11818,8 +11818,8 @@ Você pode suprimir o aviso se tiver certeza de que não vai querer aguardar a c - Expression must be implicitly convertible to Boolean or its type '{0}' must define operator '{1}'. - A expressão deve ser implicitamente convertível em Booliano ou o tipo "{0}" deve definir o operador"{1}". + Expression must be implicitly convertible to Boolean or its type '{0}' must not be an interface and must define operator '{1}'. + A expressão deve ser implicitamente convertível em Booliano ou o tipo "{0}" deve definir o operador"{1}". diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf index 343980a1a3c35..bbb2640e52abd 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf @@ -11819,8 +11819,8 @@ You should consider suppressing the warning only if you're sure that you don't w - Expression must be implicitly convertible to Boolean or its type '{0}' must define operator '{1}'. - Выражение должно быть неявно преобразуемым в логическое значение, или его тип "{0}" должен определять оператор "{1}". + Expression must be implicitly convertible to Boolean or its type '{0}' must not be an interface and must define operator '{1}'. + Выражение должно быть неявно преобразуемым в логическое значение, или его тип "{0}" должен определять оператор "{1}". diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf index ea4d26823b5bb..4cf1f1b16fb71 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf @@ -11818,8 +11818,8 @@ Yalnızca asenkron çağrının tamamlanmasını beklemek istemediğinizden ve - Expression must be implicitly convertible to Boolean or its type '{0}' must define operator '{1}'. - İfade açıkça Boolean öğesine dönüştürülebilir olmalı ya da '{0}' türü '{1}' işlecini tanımlamalıdır. + Expression must be implicitly convertible to Boolean or its type '{0}' must not be an interface and must define operator '{1}'. + İfade açıkça Boolean öğesine dönüştürülebilir olmalı ya da '{0}' türü '{1}' işlecini tanımlamalıdır. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf index c2eebbb836bd2..2939cf845b757 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf @@ -11818,8 +11818,8 @@ You should consider suppressing the warning only if you're sure that you don't w - Expression must be implicitly convertible to Boolean or its type '{0}' must define operator '{1}'. - 表达式必须可隐式转换为布尔值,或其类型“{0}”必须定义运算符“{1}”。 + Expression must be implicitly convertible to Boolean or its type '{0}' must not be an interface and must define operator '{1}'. + 表达式必须可隐式转换为布尔值,或其类型“{0}”必须定义运算符“{1}”。 diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf index 395d221ce82b4..25569cc9b6b56 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf @@ -11818,8 +11818,8 @@ You should consider suppressing the warning only if you're sure that you don't w - Expression must be implicitly convertible to Boolean or its type '{0}' must define operator '{1}'. - 運算式必須可隱含轉換成布林值,或是其類型 '{0}' 必須定義運算子 '{1}'。 + Expression must be implicitly convertible to Boolean or its type '{0}' must not be an interface and must define operator '{1}'. + 運算式必須可隱含轉換成布林值,或是其類型 '{0}' 必須定義運算子 '{1}'。 diff --git a/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionOperatorsTests.cs b/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionOperatorsTests.cs index 6aa134b274be5..57e6b2363f4dd 100644 --- a/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionOperatorsTests.cs +++ b/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionOperatorsTests.cs @@ -15889,7 +15889,7 @@ static void Main() // Note, an attempt to do compile time optimization using non-dynamic static type of 's2' ignores true/false extensions. // This is desirable because runtime binder wouldn't be able to use them as well. comp.VerifyEmitDiagnostics( - // (26,13): error CS7083: Expression must be implicitly convertible to Boolean or its type 'object' must define operator 'false'. + // (26,13): error CS7083: Expression must be implicitly convertible to Boolean or its type 'object' must not be an interface and must define operator 'false'. // _ = s2 && s1; Diagnostic(ErrorCode.ERR_InvalidDynamicCondition, "s2").WithArguments("object", "false").WithLocation(26, 13) ); diff --git a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IBinaryOperatorExpression.cs b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IBinaryOperatorExpression.cs index a1504c40d812f..e041864a61882 100644 --- a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IBinaryOperatorExpression.cs +++ b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IBinaryOperatorExpression.cs @@ -6349,7 +6349,7 @@ void M(C a, dynamic b, dynamic result) Statements (0) "; var expectedDiagnostics = new[] { - // file.cs(6,18): error CS7083: Expression must be implicitly convertible to Boolean or its type 'C' must define operator 'false'. + // file.cs(6,18): error CS7083: Expression must be implicitly convertible to Boolean or its type 'C' must not be an interface and must define operator 'false'. // result = a && b; Diagnostic(ErrorCode.ERR_InvalidDynamicCondition, "a").WithArguments("C", "false").WithLocation(6, 18) }; @@ -6861,7 +6861,7 @@ void M(C? a, dynamic b, dynamic result) Statements (0) "; var expectedDiagnostics = new[] { - // file.cs(6,18): error CS7083: Expression must be implicitly convertible to Boolean or its type 'C?' must define operator 'false'. + // file.cs(6,18): error CS7083: Expression must be implicitly convertible to Boolean or its type 'C?' must not be an interface and must define operator 'false'. // result = a && b; Diagnostic(ErrorCode.ERR_InvalidDynamicCondition, "a").WithArguments("C?", "false").WithLocation(6, 18) }; @@ -7582,7 +7582,7 @@ void M(C? a, dynamic b, dynamic result) Statements (0) "; var expectedDiagnostics = new[] { - // file.cs(6,18): error CS7083: Expression must be implicitly convertible to Boolean or its type 'C?' must define operator 'false'. + // file.cs(6,18): error CS7083: Expression must be implicitly convertible to Boolean or its type 'C?' must not be an interface and must define operator 'false'. // result = a && b; Diagnostic(ErrorCode.ERR_InvalidDynamicCondition, "a").WithArguments("C?", "false").WithLocation(6, 18) }; @@ -7691,7 +7691,7 @@ void M(C? a, dynamic b, dynamic result) Statements (0) "; var expectedDiagnostics = new[] { - // file.cs(6,18): error CS7083: Expression must be implicitly convertible to Boolean or its type 'C?' must define operator 'false'. + // file.cs(6,18): error CS7083: Expression must be implicitly convertible to Boolean or its type 'C?' must not be an interface and must define operator 'false'. // result = a && b; Diagnostic(ErrorCode.ERR_InvalidDynamicCondition, "a").WithArguments("C?", "false").WithLocation(6, 18) }; @@ -8013,7 +8013,7 @@ void M(bool? a, dynamic b, dynamic result) Statements (0) "; var expectedDiagnostics = new[] { - // file.cs(6,18): error CS7083: Expression must be implicitly convertible to Boolean or its type 'bool?' must define operator 'false'. + // file.cs(6,18): error CS7083: Expression must be implicitly convertible to Boolean or its type 'bool?' must not be an interface and must define operator 'false'. // result = a && b; Diagnostic(ErrorCode.ERR_InvalidDynamicCondition, "a").WithArguments("bool?", "false").WithLocation(6, 18) }; diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/DynamicTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/DynamicTests.cs index c524708a94914..01befe22aa69c 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/DynamicTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/DynamicTests.cs @@ -3604,7 +3604,7 @@ dynamic M(dynamic d) } }"; CreateCompilationWithMscorlib40AndSystemCore(source).VerifyDiagnostics( - // (10,12): error CS7083: Expression must be implicitly convertible to Boolean or its type 'B' must define operator 'false'. + // (10,12): error CS7083: Expression must be implicitly convertible to Boolean or its type 'B' must not be an interface and must define operator 'false'. Diagnostic(ErrorCode.ERR_InvalidDynamicCondition, "b").WithArguments("B", "false") ); } @@ -3625,7 +3625,7 @@ dynamic M(dynamic d) } }"; CreateCompilationWithMscorlib40AndSystemCore(source).VerifyDiagnostics( - // (10,12): error CS7083: Expression must be implicitly convertible to Boolean or its type 'B' must define operator 'true'. + // (10,12): error CS7083: Expression must be implicitly convertible to Boolean or its type 'B' must not be an interface and must define operator 'true'. Diagnostic(ErrorCode.ERR_InvalidDynamicCondition, "b").WithArguments("B", "true") ); } @@ -3649,6 +3649,104 @@ dynamic M(dynamic d) ); } + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/80954")] + public void DynamicBooleanExpression_InterfaceOperator_And() + { + const string source = @" +interface I1 +{ + static bool operator true(I1 x) => false; + static bool operator false(I1 x) => false; +} + +class C1 : I1 +{ + public static C1 operator &(C1 x, C1 y) => x; + public static bool operator true(C1 x) => false; + public static bool operator false(C1 x) => false; +} + +class Test +{ + void M() + { + I1 x = new C1(); + dynamic y = new C1(); + _ = x && y; + } +}"; + CreateCompilation(source, targetFramework: TargetFramework.Net70).VerifyDiagnostics( + // (21,13): error CS7083: Expression must be implicitly convertible to Boolean or its type 'I1' must not be an interface and must define operator 'false'. + // _ = x && y; + Diagnostic(ErrorCode.ERR_InvalidDynamicCondition, "x").WithArguments("I1", "false").WithLocation(21, 13) + ); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/80954")] + public void DynamicBooleanExpression_InterfaceOperator_Or() + { + const string source = @" +interface I1 +{ + static bool operator true(I1 x) => false; + static bool operator false(I1 x) => false; +} + +class C1 : I1 +{ + public static C1 operator |(C1 x, C1 y) => x; + public static bool operator true(C1 x) => false; + public static bool operator false(C1 x) => false; +} + +class Test +{ + void M() + { + I1 x = new C1(); + dynamic y = new C1(); + _ = x || y; + } +}"; + CreateCompilation(source, targetFramework: TargetFramework.Net70).VerifyDiagnostics( + // (21,13): error CS7083: Expression must be implicitly convertible to Boolean or its type 'I1' must not be an interface and must define operator 'true'. + // _ = x || y; + Diagnostic(ErrorCode.ERR_InvalidDynamicCondition, "x").WithArguments("I1", "true").WithLocation(21, 13) + ); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/80954")] + public void DynamicBooleanExpression_ClassWithoutOperator() + { + const string source = @" +class C0 { } + +class C1 : C0 +{ + public static C1 operator &(C1 x, C1 y) => x; + public static bool operator true(C1 x) => false; + public static bool operator false(C1 x) => false; +} + +class Test +{ + void M() + { + C0 x = new C1(); + dynamic y = new C1(); + _ = x && y; + } +}"; + CreateCompilation(source, targetFramework: TargetFramework.Net70).VerifyDiagnostics( + // (17,13): error CS7083: Expression must be implicitly convertible to Boolean or its type 'C0' must not be an interface and must define operator 'false'. + // _ = x && y; + Diagnostic(ErrorCode.ERR_InvalidDynamicCondition, "x").WithArguments("C0", "false").WithLocation(17, 13) + ); + } + [Fact] public void DynamicConstructorCall1() { diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/OperatorTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/OperatorTests.cs index ae77f24723dec..c719b250739be 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/OperatorTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/OperatorTests.cs @@ -9271,7 +9271,7 @@ static void Main() var comp = CreateCompilation(src, targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.DebugExe); comp.VerifyDiagnostics( - // (27,37): error CS7083: Expression must be implicitly convertible to Boolean or its type 'S1?' must define operator 'false'. + // (27,37): error CS7083: Expression must be implicitly convertible to Boolean or its type 'S1?' must not be an interface and must define operator 'false'. // static dynamic Test1(S1? s1) => s1 && (dynamic)s1; Diagnostic(ErrorCode.ERR_InvalidDynamicCondition, "s1").WithArguments("S1?", (op == "&&" ? "false" : "true")).WithLocation(27, 37) ); @@ -9316,7 +9316,7 @@ static void Main() var comp = CreateCompilation(src, targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.DebugExe); comp.VerifyDiagnostics( - // (27,37): error CS7083: Expression must be implicitly convertible to Boolean or its type 'S1?' must define operator 'false'. + // (27,37): error CS7083: Expression must be implicitly convertible to Boolean or its type 'S1?' must not be an interface and must define operator 'false'. // static dynamic Test1(S1? s1) => s1 && (dynamic)s1; Diagnostic(ErrorCode.ERR_InvalidDynamicCondition, "s1").WithArguments("S1?", (op == "&&" ? "false" : "true")).WithLocation(27, 37) ); diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/StaticAbstractMembersInInterfacesTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/StaticAbstractMembersInInterfacesTests.cs index 0055609a4cf9f..7e4fa035b597c 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/StaticAbstractMembersInInterfacesTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/StaticAbstractMembersInInterfacesTests.cs @@ -10035,25 +10035,6 @@ static void MT2() where T : I1 { _ = (System.Linq.Expressions.Expression>)((T b) => (b " + op + op + @" b).ToString()); } - - static void MT3(I1 b, dynamic c) - { - _ = b " + op + op + @" c; - } -"; - if (!success) - { - source1 += - @" - static void MT4() where T : I1 - { - _ = (System.Linq.Expressions.Expression>)((T d, dynamic e) => (d " + op + op + @" e).ToString()); - } -"; - } - - source1 += -@" } "; var compilation1 = CreateCompilation(source1, options: TestOptions.DebugDll, @@ -10089,107 +10070,10 @@ .locals init (I1 V_0) IL_0015: ret } "); - - if (op == "&") - { - verifier.VerifyIL("Test.MT3(I1, dynamic)", -@" -{ - // Code size 97 (0x61) - .maxstack 8 - IL_0000: nop - IL_0001: ldarg.0 - IL_0002: call ""bool I1.op_False(I1)"" - IL_0007: brtrue.s IL_0060 - IL_0009: ldsfld ""System.Runtime.CompilerServices.CallSite> Test.<>o__2.<>p__0"" - IL_000e: brfalse.s IL_0012 - IL_0010: br.s IL_0047 - IL_0012: ldc.i4.8 - IL_0013: ldc.i4.2 - IL_0014: ldtoken ""Test"" - IL_0019: call ""System.Type System.Type.GetTypeFromHandle(System.RuntimeTypeHandle)"" - IL_001e: ldc.i4.2 - IL_001f: newarr ""Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo"" - IL_0024: dup - IL_0025: ldc.i4.0 - IL_0026: ldc.i4.1 - IL_0027: ldnull - IL_0028: call ""Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo.Create(Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags, string)"" - IL_002d: stelem.ref - IL_002e: dup - IL_002f: ldc.i4.1 - IL_0030: ldc.i4.0 - IL_0031: ldnull - IL_0032: call ""Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo.Create(Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags, string)"" - IL_0037: stelem.ref - IL_0038: call ""System.Runtime.CompilerServices.CallSiteBinder Microsoft.CSharp.RuntimeBinder.Binder.BinaryOperation(Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags, System.Linq.Expressions.ExpressionType, System.Type, System.Collections.Generic.IEnumerable)"" - IL_003d: call ""System.Runtime.CompilerServices.CallSite> System.Runtime.CompilerServices.CallSite>.Create(System.Runtime.CompilerServices.CallSiteBinder)"" - IL_0042: stsfld ""System.Runtime.CompilerServices.CallSite> Test.<>o__2.<>p__0"" - IL_0047: ldsfld ""System.Runtime.CompilerServices.CallSite> Test.<>o__2.<>p__0"" - IL_004c: ldfld ""System.Func System.Runtime.CompilerServices.CallSite>.Target"" - IL_0051: ldsfld ""System.Runtime.CompilerServices.CallSite> Test.<>o__2.<>p__0"" - IL_0056: ldarg.0 - IL_0057: ldarg.1 - IL_0058: callvirt ""dynamic System.Func.Invoke(System.Runtime.CompilerServices.CallSite, I1, dynamic)"" - IL_005d: pop - IL_005e: br.s IL_0060 - IL_0060: ret -} -"); - } - else - { - verifier.VerifyIL("Test.MT3(I1, dynamic)", -@" -{ - // Code size 98 (0x62) - .maxstack 8 - IL_0000: nop - IL_0001: ldarg.0 - IL_0002: call ""bool I1.op_True(I1)"" - IL_0007: brtrue.s IL_0061 - IL_0009: ldsfld ""System.Runtime.CompilerServices.CallSite> Test.<>o__2.<>p__0"" - IL_000e: brfalse.s IL_0012 - IL_0010: br.s IL_0048 - IL_0012: ldc.i4.8 - IL_0013: ldc.i4.s 36 - IL_0015: ldtoken ""Test"" - IL_001a: call ""System.Type System.Type.GetTypeFromHandle(System.RuntimeTypeHandle)"" - IL_001f: ldc.i4.2 - IL_0020: newarr ""Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo"" - IL_0025: dup - IL_0026: ldc.i4.0 - IL_0027: ldc.i4.1 - IL_0028: ldnull - IL_0029: call ""Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo.Create(Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags, string)"" - IL_002e: stelem.ref - IL_002f: dup - IL_0030: ldc.i4.1 - IL_0031: ldc.i4.0 - IL_0032: ldnull - IL_0033: call ""Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo.Create(Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags, string)"" - IL_0038: stelem.ref - IL_0039: call ""System.Runtime.CompilerServices.CallSiteBinder Microsoft.CSharp.RuntimeBinder.Binder.BinaryOperation(Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags, System.Linq.Expressions.ExpressionType, System.Type, System.Collections.Generic.IEnumerable)"" - IL_003e: call ""System.Runtime.CompilerServices.CallSite> System.Runtime.CompilerServices.CallSite>.Create(System.Runtime.CompilerServices.CallSiteBinder)"" - IL_0043: stsfld ""System.Runtime.CompilerServices.CallSite> Test.<>o__2.<>p__0"" - IL_0048: ldsfld ""System.Runtime.CompilerServices.CallSite> Test.<>o__2.<>p__0"" - IL_004d: ldfld ""System.Func System.Runtime.CompilerServices.CallSite>.Target"" - IL_0052: ldsfld ""System.Runtime.CompilerServices.CallSite> Test.<>o__2.<>p__0"" - IL_0057: ldarg.0 - IL_0058: ldarg.1 - IL_0059: callvirt ""dynamic System.Func.Invoke(System.Runtime.CompilerServices.CallSite, I1, dynamic)"" - IL_005e: pop - IL_005f: br.s IL_0061 - IL_0061: ret -} -"); - } } else { - var builder = ArrayBuilder.GetInstance(); - - builder.AddRange( + compilation1.VerifyDiagnostics( // (10,13): error CS8926: A static virtual or abstract interface member can be accessed only on a type parameter. // _ = x && x; Diagnostic(ErrorCode.ERR_BadAbstractStaticMemberAccess, "x " + op + op + " x").WithLocation(10, 13), @@ -10203,24 +10087,62 @@ .maxstack 8 // _ = (System.Linq.Expressions.Expression>)((T b) => (b && b).ToString()); Diagnostic(ErrorCode.ERR_ExpressionTreeContainsAbstractStaticMemberAccess, "b " + op + op + " b").WithLocation(28, 78) ); + } + } + } - if (op == "&" ? falseIsAbstract : trueIsAbstract) - { - builder.Add( - // (33,13): error CS8926: A static virtual or abstract interface member can be accessed only on a type parameter. - // _ = b || c; - Diagnostic(ErrorCode.ERR_BadAbstractStaticMemberAccess, "b " + op + op + " c").WithLocation(33, 13) - ); - } + [Theory] + [InlineData("&", true, false, false)] + [InlineData("|", true, false, false)] + [InlineData("&", false, false, true)] + [InlineData("|", false, true, false)] + [InlineData("&", true, false, true)] + [InlineData("|", true, true, false)] + [InlineData("&", false, true, false)] + [InlineData("|", false, false, true)] + public void ConsumeAbstractLogicalBinaryOperator_01_Dynamic(string op, bool binaryIsAbstract, bool trueIsAbstract, bool falseIsAbstract) + { + consumeAbstractLogicalBinaryOperator_01_Dynamic(op, binaryIsAbstract, trueIsAbstract, falseIsAbstract, isVirtual: false); + consumeAbstractLogicalBinaryOperator_01_Dynamic(op, binaryIsAbstract, trueIsAbstract, falseIsAbstract, isVirtual: true); - builder.Add( - // (38,98): error CS7083: Expression must be implicitly convertible to Boolean or its type 'T' must define operator 'true'. - // _ = (System.Linq.Expressions.Expression>)((T d, dynamic e) => (d || e).ToString()); - Diagnostic(ErrorCode.ERR_InvalidDynamicCondition, "d").WithArguments("T", op == "&" ? "false" : "true").WithLocation(38, 98) - ); + void consumeAbstractLogicalBinaryOperator_01_Dynamic(string op, bool binaryIsAbstract, bool trueIsAbstract, bool falseIsAbstract, bool isVirtual) + { + var (modifier, body) = GetModifierAndBody(isVirtual); - compilation1.VerifyDiagnostics(builder.ToArrayAndFree()); - } + var source1 = +@" +interface I1 +{ + " + (binaryIsAbstract ? modifier : "") + @" static I1 operator" + op + @" (I1 x, I1 y)" + (binaryIsAbstract ? body : " => throw null;") + @" + " + (trueIsAbstract ? modifier : "") + @" static bool operator true (I1 x)" + (trueIsAbstract ? body : " => throw null;") + @" + " + (falseIsAbstract ? modifier : "") + @" static bool operator false (I1 x)" + (falseIsAbstract ? body : " => throw null;") + @" +} + +class Test +{ + static void MT3(I1 b, dynamic c) + { + _ = b " + op + op + @" c; + } + + static void MT4() where T : I1 + { + _ = (System.Linq.Expressions.Expression>)((T d, dynamic e) => (d " + op + op + @" e).ToString()); + } +} +"; + var compilation1 = CreateCompilation(source1, options: TestOptions.DebugDll, + parseOptions: TestOptions.RegularPreview, + targetFramework: _supportingFramework); + + compilation1.VerifyDiagnostics( + // (13,13): error CS7083: Expression must be implicitly convertible to Boolean or its type 'I1' must not be an interface and must define operator 'false'. + // _ = b && c; + Diagnostic(ErrorCode.ERR_InvalidDynamicCondition, "b").WithArguments("I1", op == "&" ? "false" : "true").WithLocation(13, 13), + // (18,98): error CS7083: Expression must be implicitly convertible to Boolean or its type 'T' must not be an interface and must define operator 'false'. + // _ = (System.Linq.Expressions.Expression>)((T d, dynamic e) => (d && e).ToString()); + Diagnostic(ErrorCode.ERR_InvalidDynamicCondition, "d").WithArguments("T", op == "&" ? "false" : "true").WithLocation(18, 98) + ); } }