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)
+ );
}
}