Skip to content

Commit 67d422c

Browse files
authored
Support extension operators in Linq Expression Trees (#78942)
1 parent 142eec5 commit 67d422c

20 files changed

+801
-8
lines changed

src/Compilers/CSharp/Portable/CSharpResources.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8206,4 +8206,7 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
82068206
<data name="ERR_InstanceOperatorExtensionWrongReceiverType" xml:space="preserve">
82078207
<value>Cannot declare instance extension operator for a type that is not known to be a struct and is not known to be a class</value>
82088208
</data>
8209+
<data name="ERR_ExpressionTreeContainsExtensionBasedConditionalLogicalOperator" xml:space="preserve">
8210+
<value>An expression tree may not contain '&amp;&amp;' or '||' operators that use extension user defined operators.</value>
8211+
</data>
82098212
</root>

src/Compilers/CSharp/Portable/Errors/ErrorCode.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2417,6 +2417,7 @@ internal enum ErrorCode
24172417
ERR_OperatorInExtensionOfStaticClass = 9555,
24182418
ERR_InstanceOperatorStructExtensionWrongReceiverRefKind = 9556,
24192419
ERR_InstanceOperatorExtensionWrongReceiverType = 9557,
2420+
ERR_ExpressionTreeContainsExtensionBasedConditionalLogicalOperator = 9558,
24202421

24212422
// Note: you will need to do the following after adding errors:
24222423
// 1) Update ErrorFacts.IsBuildOnlyDiagnostic (src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs)

src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2528,6 +2528,7 @@ or ErrorCode.ERR_BadExtensionShiftOperatorSignature
25282528
or ErrorCode.ERR_OperatorInExtensionOfStaticClass
25292529
or ErrorCode.ERR_InstanceOperatorStructExtensionWrongReceiverRefKind
25302530
or ErrorCode.ERR_InstanceOperatorExtensionWrongReceiverType
2531+
or ErrorCode.ERR_ExpressionTreeContainsExtensionBasedConditionalLogicalOperator
25312532
=> false,
25322533
};
25332534
#pragma warning restore CS8524 // The switch expression does not handle some values of its input type (it is not exhaustive) involving an unnamed enum value.

src/Compilers/CSharp/Portable/Lowering/DiagnosticsPass_ExpressionTrees.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -755,6 +755,23 @@ public override BoundNode VisitUserDefinedConditionalLogicalOperator(BoundUserDe
755755
{
756756
Error(ErrorCode.ERR_ExpressionTreeContainsAbstractStaticMemberAccess, node);
757757
}
758+
759+
if (binary.GetIsNewExtensionMember())
760+
{
761+
// PROTOTYPE: Communicate to LDM
762+
// An expression tree factory isn't happy in this case. It throws
763+
// System.ArgumentException : The user-defined operator method 'op_BitwiseOr' for operator 'OrElse' must have associated boolean True and False operators.
764+
// or
765+
// System.ArgumentException : The user-defined operator method 'op_BitwiseAnd' for operator 'AndAlso' must have associated boolean True and False operators.
766+
//
767+
// from Expression.ValidateUserDefinedConditionalLogicOperator(ExpressionType nodeType, Type left, Type right, MethodInfo method)
768+
Error(ErrorCode.ERR_ExpressionTreeContainsExtensionBasedConditionalLogicalOperator, node);
769+
}
770+
else
771+
{
772+
Debug.Assert(!node.TrueOperator.GetIsNewExtensionMember());
773+
Debug.Assert(!node.FalseOperator.GetIsNewExtensionMember());
774+
}
758775
}
759776

760777
return base.VisitUserDefinedConditionalLogicalOperator(node);

src/Compilers/CSharp/Portable/Lowering/ExtensionMethodBodyRewriter.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,5 +198,15 @@ public override BoundNode VisitFunctionPointerLoad(BoundFunctionPointerLoad node
198198
Debug.Assert(symbol?.GetIsNewExtensionMember() != true);
199199
return base.VisitPropertySymbol(symbol);
200200
}
201+
202+
public override BoundNode VisitUnaryOperator(BoundUnaryOperator node)
203+
{
204+
return ExtensionMethodReferenceRewriter.VisitUnaryOperator(this, node);
205+
}
206+
207+
protected override BoundBinaryOperator.UncommonData? VisitBinaryOperatorData(BoundBinaryOperator node)
208+
{
209+
return ExtensionMethodReferenceRewriter.VisitBinaryOperatorData(this, node);
210+
}
201211
}
202212
}

src/Compilers/CSharp/Portable/Lowering/ExtensionMethodReferenceRewriter.cs

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,6 @@ method.OriginalDefinition is ErrorMethodSymbol ||
176176
{ Name: nameof(VisitReadOnlySpanFromArray) } => method is { Name: "op_Implicit", IsExtensionMethod: false }, // Conversion operator from array to span cannot be an extension method
177177
{ Name: nameof(VisitLoweredConditionalAccess) } => // Nullable.HasValue cannot be an extension method
178178
method.ContainingAssembly.GetSpecialTypeMember(SpecialMember.System_Nullable_T_get_HasValue) == (object)method.OriginalDefinition,
179-
{ Name: nameof(VisitUnaryOperator) } => !method.IsExtensionMethod, // Expression tree context. At the moment an operator cannot be an extension method
180179
{ Name: nameof(VisitUserDefinedConditionalLogicalOperator) } => !method.IsExtensionMethod, // Expression tree context. At the moment an operator cannot be an extension method
181180
{ Name: nameof(VisitCollectionElementInitializer) } => !method.IsExtensionMethod, // Expression tree context. At the moment an extension method cannot be used in expression tree here.
182181
{ Name: nameof(VisitAwaitableInfo) } => method is { Name: "GetResult", IsExtensionMethod: false }, // Cannot be an extension method
@@ -231,10 +230,19 @@ public static BoundNode VisitFunctionPointerLoad(BoundTreeRewriter rewriter, Bou
231230

232231
protected override BoundBinaryOperator.UncommonData? VisitBinaryOperatorData(BoundBinaryOperator node)
233232
{
234-
Debug.Assert(node.Method is null ||
235-
(!node.Method.IsExtensionMethod && !node.Method.GetIsNewExtensionMember())); // Expression tree context. At the moment an operator cannot be an extension method
233+
return VisitBinaryOperatorData(this, node);
234+
}
236235

237-
return base.VisitBinaryOperatorData(node);
236+
public static BoundBinaryOperator.UncommonData? VisitBinaryOperatorData(BoundTreeRewriter rewriter, BoundBinaryOperator node)
237+
{
238+
// Local rewriter should have already rewritten interpolated strings into their final form of calls and gotos
239+
Debug.Assert(node.InterpolatedStringHandlerData is null);
240+
241+
return BoundBinaryOperator.UncommonData.CreateIfNeeded(
242+
node.ConstantValueOpt,
243+
VisitMethodSymbolWithExtensionRewrite(rewriter, node.Method),
244+
rewriter.VisitType(node.ConstrainedToType),
245+
node.OriginalUserDefinedOperatorsOpt);
238246
}
239247

240248
[return: NotNullIfNotNull(nameof(symbol))]
@@ -243,5 +251,20 @@ public static BoundNode VisitFunctionPointerLoad(BoundTreeRewriter rewriter, Bou
243251
Debug.Assert(symbol?.GetIsNewExtensionMember() != true);
244252
return base.VisitPropertySymbol(symbol);
245253
}
254+
255+
public override BoundNode VisitUnaryOperator(BoundUnaryOperator node)
256+
{
257+
return VisitUnaryOperator(this, node);
258+
}
259+
260+
public static BoundNode VisitUnaryOperator(BoundTreeRewriter rewriter, BoundUnaryOperator node)
261+
{
262+
MethodSymbol? methodOpt = VisitMethodSymbolWithExtensionRewrite(rewriter, node.MethodOpt);
263+
ImmutableArray<MethodSymbol> originalUserDefinedOperatorsOpt = rewriter.VisitSymbols<MethodSymbol>(node.OriginalUserDefinedOperatorsOpt);
264+
BoundExpression operand = (BoundExpression)rewriter.Visit(node.Operand);
265+
TypeSymbol? constrainedToTypeOpt = rewriter.VisitType(node.ConstrainedToTypeOpt);
266+
TypeSymbol? type = rewriter.VisitType(node.Type);
267+
return node.Update(node.OperatorKind, operand, node.ConstantValueOpt, methodOpt, constrainedToTypeOpt, node.ResultKind, originalUserDefinedOperatorsOpt, type);
268+
}
246269
}
247270
}

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

Lines changed: 5 additions & 0 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: 5 additions & 0 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: 5 additions & 0 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: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)