Skip to content

Commit 6c492e1

Browse files
author
Julien Couvreur
authored
Extensions: fix lowering of implicit conversion on receiver in pattern-based scenarios (#78685)
1 parent 3f34ede commit 6c492e1

File tree

9 files changed

+737
-30
lines changed

9 files changed

+737
-30
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3030,13 +3030,13 @@ private bool CheckInvocationArgMixingWithUpdatedRules(
30303030
}
30313031
}
30323032

3033-
inferDeclarationExpressionValEscape();
3033+
inferDeclarationExpressionValEscape(argsOpt, localScopeDepth, escapeValues);
30343034

30353035
mixableArguments.Free();
30363036
escapeValues.Free();
30373037
return valid;
30383038

3039-
void inferDeclarationExpressionValEscape()
3039+
void inferDeclarationExpressionValEscape(ImmutableArray<BoundExpression> argsOpt, SafeContext localScopeDepth, ArrayBuilder<EscapeValue> escapeValues)
30403040
{
30413041
// find the widest scope that arguments could safely escape to.
30423042
// use this scope as the inferred STE of declaration expressions.

src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.PatternLocalRewriter.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,8 @@ protected BoundExpression LowerEvaluation(BoundDagEvaluation evaluation)
152152
PropertySymbol property = p.Property;
153153
var outputTemp = new BoundDagTemp(p.Syntax, property.Type, p);
154154
BoundExpression output = _tempAllocator.GetTemp(outputTemp);
155-
input = _factory.ConvertReceiverForExtensionMemberIfNeeded(property, input);
155+
// Tracked by https://github.com/dotnet/roslyn/issues/76130 : Consider preserving the BoundConversion from initial binding instead of using markAsChecked here
156+
input = _localRewriter.ConvertReceiverForExtensionMemberIfNeeded(property, input, markAsChecked: true);
156157
return _factory.AssignmentExpression(output, _localRewriter.MakePropertyAccess(_factory.Syntax, input, property, LookupResultKind.Viable, property.Type, isLeftOfAssignment: false));
157158
}
158159

@@ -191,7 +192,8 @@ void addArg(RefKind refKind, BoundExpression expression)
191192
addArg(RefKind.Out, _tempAllocator.GetTemp(outputTemp));
192193
}
193194

194-
receiver = _factory.ConvertReceiverForExtensionMemberIfNeeded(method, receiver);
195+
// Tracked by https://github.com/dotnet/roslyn/issues/76130 : Consider preserving the BoundConversion from initial binding instead of using markAsChecked here
196+
receiver = _localRewriter.ConvertReceiverForExtensionMemberIfNeeded(method, receiver, markAsChecked: true);
195197
return _factory.Call(receiver, method, refKindBuilder.ToImmutableAndFree(), argBuilder.ToImmutableAndFree());
196198
}
197199

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1136,6 +1136,25 @@ private CompoundUseSiteInfo<AssemblySymbol> GetNewCompoundUseSiteInfo()
11361136
return new CompoundUseSiteInfo<AssemblySymbol>(_diagnostics, _compilation.Assembly);
11371137
}
11381138

1139+
private BoundExpression ConvertReceiverForExtensionMemberIfNeeded(Symbol member, BoundExpression receiver, bool markAsChecked)
1140+
{
1141+
if (member.GetIsNewExtensionMember())
1142+
{
1143+
Debug.Assert(!member.IsStatic);
1144+
ParameterSymbol? extensionParameter = member.ContainingType.ExtensionParameter;
1145+
Debug.Assert(extensionParameter is not null);
1146+
#if DEBUG
1147+
var discardedUseSiteInfo = CompoundUseSiteInfo<AssemblySymbol>.Discarded;
1148+
Debug.Assert(Conversions.IsValidExtensionMethodThisArgConversion(this._compilation.Conversions.ClassifyConversionFromType(receiver.Type, extensionParameter.Type, isChecked: false, ref discardedUseSiteInfo)));
1149+
#endif
1150+
1151+
// We don't need to worry about checked context because only implicit conversions are allowed on the receiver of an extension member
1152+
return MakeConversionNode(receiver, extensionParameter.Type, @checked: false, acceptFailingConversion: false, markAsChecked: markAsChecked);
1153+
}
1154+
1155+
return receiver;
1156+
}
1157+
11391158
#if DEBUG
11401159
/// <summary>
11411160
/// Note: do not use a static/singleton instance of this type, as it holds state.

src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Conversion.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -781,6 +781,10 @@ bool IsInRange(SpecialType type, SpecialType low, SpecialType high) =>
781781
/// (e.g. if the default value was specified by an attribute and was, therefore, not checked by the compiler).
782782
/// Set acceptFailingConversion if you want to see default(rewrittenType) in such cases.
783783
/// The error will be suppressed only for conversions from <see cref="decimal"/> or <see cref="DateTime"/>.
784+
///
785+
/// Generally, conversions should be checked and BoundConversion nodes created during initial binding and re-used in lowering. But in some cases,
786+
/// we do not preserve the conversion node. In such case, <paramref name="markAsChecked"/> is set to true
787+
/// to indicate that the conversion has been previously checked during initial binding.
784788
/// </remarks>
785789
private BoundExpression MakeConversionNode(BoundExpression rewrittenOperand, TypeSymbol rewrittenType, bool @checked, bool acceptFailingConversion = false, bool markAsChecked = false)
786790
{

src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_FixedStatement.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -354,8 +354,9 @@ private BoundStatement InitializeFixedStatementGetPinnable(
354354
callReceiver = initializerExpr;
355355
}
356356

357+
// Tracked by https://github.com/dotnet/roslyn/issues/76130 : Consider preserving the BoundConversion from initial binding instead of using markAsChecked here
357358
// .GetPinnable()
358-
callReceiver = factory.ConvertReceiverForExtensionMemberIfNeeded(getPinnableMethod, callReceiver);
359+
callReceiver = this.ConvertReceiverForExtensionMemberIfNeeded(getPinnableMethod, callReceiver, markAsChecked: true);
359360
var getPinnableCall = getPinnableMethod.IsStatic ?
360361
factory.Call(null, getPinnableMethod, callReceiver) :
361362
factory.Call(callReceiver, getPinnableMethod);

src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_ObjectOrCollectionInitializerExpression.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -702,7 +702,8 @@ private BoundExpression MakeObjectInitializerMemberAccess(
702702
_compilation.Conversions.HasImplicitConversionToOrImplementsVarianceCompatibleInterface(rewrittenReceiver.Type, memberSymbol.ContainingType, ref discardedUseSiteInfo, out _));
703703
// It is possible there are use site diagnostics from the above, but none that we need report as we aren't generating code for the conversion
704704
#endif
705-
rewrittenReceiver = _factory.ConvertReceiverForExtensionMemberIfNeeded(memberSymbol, rewrittenReceiver);
705+
// Tracked by https://github.com/dotnet/roslyn/issues/76130 : Consider preserving the BoundConversion from initial binding instead of using markAsChecked here
706+
rewrittenReceiver = this.ConvertReceiverForExtensionMemberIfNeeded(memberSymbol, rewrittenReceiver, markAsChecked: true);
706707

707708
switch (memberSymbol.Kind)
708709
{

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

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -431,24 +431,6 @@ public BoundExpression AssignmentExpression(BoundExpression left, BoundExpressio
431431
return AssignmentExpression(Syntax, left, right, isRef: isRef, wasCompilerGenerated: true);
432432
}
433433

434-
public BoundExpression ConvertReceiverForExtensionMemberIfNeeded(Symbol member, BoundExpression receiver)
435-
{
436-
if (member.GetIsNewExtensionMember())
437-
{
438-
Debug.Assert(!member.IsStatic);
439-
ParameterSymbol? extensionParameter = member.ContainingType.ExtensionParameter;
440-
Debug.Assert(extensionParameter is not null);
441-
#if DEBUG
442-
var discardedUseSiteInfo = CompoundUseSiteInfo<AssemblySymbol>.Discarded;
443-
Debug.Assert(Conversions.IsValidExtensionMethodThisArgConversion(this.Compilation.Conversions.ClassifyConversionFromType(receiver.Type, extensionParameter.Type, isChecked: false, ref discardedUseSiteInfo)));
444-
#endif
445-
446-
return this.Convert(extensionParameter.Type, receiver);
447-
}
448-
449-
return receiver;
450-
}
451-
452434
/// <summary>
453435
/// Creates a general assignment that might be instrumented.
454436
/// </summary>

0 commit comments

Comments
 (0)