diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs index 293a9c9c53f1c..1e81912b14d95 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs @@ -1156,17 +1156,36 @@ private BoundExpression BindConditionalLogicalOperator(BinaryExpressionSyntax no CheckConstraintLanguageVersionAndRuntimeSupportForOperator(node, kind == BinaryOperatorKind.LogicalAnd ? falseOperator : trueOperator, isUnsignedRightShift: false, signature.ConstrainedToTypeOpt, diagnostics); + var operandPlaceholder = new BoundValuePlaceholder(resultLeft.Syntax, resultLeft.Type).MakeCompilerGenerated(); + var trueFalseParameterType = (resultKind.Operator() == BinaryOperatorKind.And ? falseOperator : trueOperator).Parameters[0].Type; + CompoundUseSiteInfo useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics); + var conversion = this.Conversions.ClassifyConversionFromType(resultLeft.Type, trueFalseParameterType, isChecked: CheckOverflowAtRuntime, ref useSiteInfo); + Debug.Assert(conversion.IsImplicit); + + BoundExpression operandConversion = CreateConversion( + resultLeft.Syntax, + operandPlaceholder, + conversion, + isCast: false, + conversionGroupOpt: null, + trueFalseParameterType, + diagnostics); + + diagnostics.Add(operandConversion.Syntax, useSiteInfo); + return new BoundUserDefinedConditionalLogicalOperator( node, resultKind, - resultLeft, - resultRight, signature.Method, trueOperator, falseOperator, + operandPlaceholder, + operandConversion, signature.ConstrainedToTypeOpt, lookupResult, originalUserDefinedOperators, + resultLeft, + resultRight, signature.ReturnType); } else @@ -1234,8 +1253,35 @@ private bool IsValidDynamicCondition(BoundExpression left, bool isNegative, Bind return false; } + // Strictly speaking, we probably should be digging through nullable type here to look for an operator + // declared by the underlying type. However, it doesn't look like dynamic binder is able to deal with + // nullable types while evaluating logical binary operators. Exceptions that it throws look like: + // + // Microsoft.CSharp.RuntimeBinder.RuntimeBinderException : The type ('S1?') must contain declarations of operator true and operator false + // Stack Trace: + // at CallSite.Target(Closure, CallSite, Object, Object) + // at System.Dynamic.UpdateDelegates.UpdateAndExecute2[T0,T1,TRet](CallSite site, T0 arg0, T1 arg1) + // + // Microsoft.CSharp.RuntimeBinder.RuntimeBinderException : Operator '&&' cannot be applied to operands of type 'S1' and 'S1?' + // 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 result = HasApplicableBooleanOperator(namedType, isNegative ? WellKnownMemberNames.FalseOperatorName : WellKnownMemberNames.TrueOperatorName, type, ref useSiteInfo, out userDefinedOperator); + + if (result) + { + Debug.Assert(userDefinedOperator is not null); + + 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); + } + diagnostics.Add(left.Syntax, useSiteInfo); return result; diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml b/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml index 663c321f9aada..2d030ec18fbf2 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml +++ b/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml @@ -501,6 +501,15 @@ + + + + + +