Skip to content

Commit 2bf680e

Browse files
authored
Report an error for dynamic evaluation of &&/|| when left operand is statically typed as an interface (#80962)
Fixes #80954
1 parent 5138002 commit 2bf680e

21 files changed

+241
-171
lines changed

docs/compilers/CSharp/Compiler Breaking Changes - DotNet 11.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,3 +103,54 @@ void Method()
103103
If your code is impacted by this breaking change, consider adding a reference to an assembly defining `System.Runtime.InteropServices.InAttribute`
104104
to your project.
105105

106+
107+
## Dynamic evaluation of `&&`/`||` operators is not allowed with the left operand statically typed as an interface.
108+
109+
***Introduced in Visual Studio 2026 version 18.3***
110+
111+
The C# compiler now reports an error when an interface type is used as the left operand of
112+
a logical `&&` or `||` operator with a `dynamic` right operand.
113+
Previously, code would compile for an interface type with `true`/`false` operators,
114+
but fail at runtime with a `RuntimeBinderException` because the runtime binder cannot
115+
invoke operators defined on interfaces.
116+
117+
This change prevents a runtime error by reporting it at compile time instead. The error message is:
118+
119+
> error CS7083: Expression must be implicitly convertible to Boolean or its type 'I1' must not be an interface and must define operator 'false'.
120+
121+
```cs
122+
interface I1
123+
{
124+
static bool operator true(I1 x) => false;
125+
static bool operator false(I1 x) => false;
126+
}
127+
128+
class C1 : I1
129+
{
130+
public static C1 operator &(C1 x, C1 y) => x;
131+
public static bool operator true(C1 x) => false;
132+
public static bool operator false(C1 x) => false;
133+
}
134+
135+
void M()
136+
{
137+
I1 x = new C1();
138+
dynamic y = new C1();
139+
_ = 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'.
140+
}
141+
```
142+
143+
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,
144+
or to `dynamic` type:
145+
146+
```cs
147+
void M()
148+
{
149+
I1 x = new C1();
150+
dynamic y = new C1();
151+
_ = (C1)x && y; // Valid - uses operators defined on C1
152+
_ = (dynamic)x && y; // Valid - uses operators defined on C1
153+
}
154+
```
155+
156+
See also https://github.com/dotnet/roslyn/issues/80954.

src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1630,7 +1630,7 @@ private bool IsValidDynamicCondition(BoundExpression left, bool isNegative, Bind
16301630
return true;
16311631
}
16321632

1633-
if (type.Kind != SymbolKind.NamedType || type.IsNullableType())
1633+
if (type is not NamedTypeSymbol { IsInterface: false } namedType || namedType.IsNullableType())
16341634
{
16351635
diagnostics.Add(left.Syntax, useSiteInfo);
16361636
return false;
@@ -1649,7 +1649,6 @@ private bool IsValidDynamicCondition(BoundExpression left, bool isNegative, Bind
16491649
// Stack Trace:
16501650
// at CallSite.Target(Closure, CallSite, Object, Nullable`1)
16511651
// at System.Dynamic.UpdateDelegates.UpdateAndExecute2[T0,T1,TRet](CallSite site, T0 arg0, T1 arg1)
1652-
var namedType = type as NamedTypeSymbol;
16531652
var operandPlaceholder = new BoundValuePlaceholder(left.Syntax, namedType).MakeCompilerGenerated();
16541653
UnaryOperatorAnalysisResult result = operatorOverloadResolution(left.Syntax, operandPlaceholder, isNegative ? UnaryOperatorKind.False : UnaryOperatorKind.True, diagnostics);
16551654

src/Compilers/CSharp/Portable/CSharpResources.resx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4251,7 +4251,7 @@ You should consider suppressing the warning only if you're sure that you don't w
42514251
<value>The CallerFilePathAttribute will have no effect; it is overridden by the CallerLineNumberAttribute</value>
42524252
</data>
42534253
<data name="ERR_InvalidDynamicCondition" xml:space="preserve">
4254-
<value>Expression must be implicitly convertible to Boolean or its type '{0}' must define operator '{1}'.</value>
4254+
<value>Expression must be implicitly convertible to Boolean or its type '{0}' must not be an interface and must define operator '{1}'.</value>
42554255
</data>
42564256
<data name="ERR_MixingWinRTEventWithRegular" xml:space="preserve">
42574257
<value>'{0}' cannot implement '{1}' because '{2}' is a Windows Runtime event and '{3}' is a regular .NET event.</value>

src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)