Skip to content

Commit 7e962b9

Browse files
authored
Ensure extension receiver is converted for pattern matching Deconstruct and pattern based using scenarios. (#81816)
Fixes #81173
1 parent a1bda55 commit 7e962b9

File tree

5 files changed

+229
-11
lines changed

5 files changed

+229
-11
lines changed

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,8 @@ void addArg(RefKind refKind, BoundExpression expression)
175175
{
176176
Debug.Assert(method.IsExtensionMethod);
177177
receiver = _factory.Type(method.ContainingType);
178-
addArg(method.ParameterRefKinds[0], input);
178+
// Tracked by https://github.com/dotnet/roslyn/issues/78827 : MQ, Consider preserving the BoundConversion from initial binding instead of using markAsChecked here
179+
addArg(method.ParameterRefKinds[0], _localRewriter.ConvertReceiverForExtensionIfNeeded(input, markAsChecked: true, method.Parameters[0]));
179180
extensionExtra = 1;
180181
}
181182
else

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

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1152,16 +1152,21 @@ private BoundExpression ConvertReceiverForExtensionMemberIfNeeded(Symbol member,
11521152
Debug.Assert(!member.IsStatic);
11531153
ParameterSymbol? extensionParameter = member.ContainingType.ExtensionParameter;
11541154
Debug.Assert(extensionParameter is not null);
1155-
#if DEBUG
1156-
var discardedUseSiteInfo = CompoundUseSiteInfo<AssemblySymbol>.Discarded;
1157-
Debug.Assert(Conversions.IsValidExtensionMethodThisArgConversion(this._compilation.Conversions.ClassifyConversionFromType(receiver.Type, extensionParameter.Type, isChecked: false, ref discardedUseSiteInfo)));
1158-
#endif
1159-
1160-
// We don't need to worry about checked context because only implicit conversions are allowed on the receiver of an extension member
1161-
return MakeConversionNode(receiver, extensionParameter.Type, @checked: false, acceptFailingConversion: false, markAsChecked: markAsChecked);
1155+
return ConvertReceiverForExtensionIfNeeded(receiver, markAsChecked, extensionParameter);
11621156
}
11631157

11641158
return receiver;
11651159
}
1160+
1161+
private BoundExpression ConvertReceiverForExtensionIfNeeded(BoundExpression receiver, bool markAsChecked, ParameterSymbol extensionParameter)
1162+
{
1163+
#if DEBUG
1164+
var discardedUseSiteInfo = CompoundUseSiteInfo<AssemblySymbol>.Discarded;
1165+
Debug.Assert(Conversions.IsValidExtensionMethodThisArgConversion(this._compilation.Conversions.ClassifyConversionFromType(receiver.Type, extensionParameter.Type, isChecked: false, ref discardedUseSiteInfo)));
1166+
#endif
1167+
1168+
// We don't need to worry about checked context because only implicit conversions are allowed on the receiver of an extension member
1169+
return MakeConversionNode(receiver, extensionParameter.Type, @checked: false, acceptFailingConversion: false, markAsChecked: markAsChecked);
1170+
}
11661171
}
11671172
}

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -356,10 +356,9 @@ private BoundStatement InitializeFixedStatementGetPinnable(
356356

357357
// Tracked by https://github.com/dotnet/roslyn/issues/78827 : MQ, Consider preserving the BoundConversion from initial binding instead of using markAsChecked here
358358
// .GetPinnable()
359-
callReceiver = this.ConvertReceiverForExtensionMemberIfNeeded(getPinnableMethod, callReceiver, markAsChecked: true);
360359
var getPinnableCall = getPinnableMethod.IsStatic ?
361-
factory.Call(null, getPinnableMethod, callReceiver) :
362-
factory.Call(callReceiver, getPinnableMethod);
360+
factory.Call(null, getPinnableMethod, this.ConvertReceiverForExtensionIfNeeded(callReceiver, markAsChecked: true, getPinnableMethod.Parameters[0])) :
361+
factory.Call(this.ConvertReceiverForExtensionMemberIfNeeded(getPinnableMethod, callReceiver, markAsChecked: true), getPinnableMethod);
363362

364363
// temp =ref .GetPinnable()
365364
var tempAssignment = factory.AssignmentExpression(

src/Compilers/CSharp/Test/Emit/CodeGen/PatternTests.cs

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7055,5 +7055,101 @@ class A : I
70557055
// if (a is (I or null) and var x)
70567056
Diagnostic(ErrorCode.WRN_IsPatternAlways, "a is (I or null) and var x").WithArguments("A").WithLocation(26, 9));
70577057
}
7058+
7059+
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/81173")]
7060+
public void DeconstructExtension_01()
7061+
{
7062+
var source = """
7063+
class Program
7064+
{
7065+
static void Main()
7066+
{
7067+
System.Console.Write(Test2(new C()));
7068+
}
7069+
7070+
static bool Test2(C u)
7071+
{
7072+
return u is var (_ , (i, _)) && (int)i == 10;
7073+
}
7074+
7075+
static void Test3(C c, int i)
7076+
{
7077+
var (a, b) = c;
7078+
var (u, v) = i;
7079+
}
7080+
}
7081+
7082+
public struct C
7083+
{
7084+
}
7085+
7086+
static class Extensions
7087+
{
7088+
public static void Deconstruct(this object o, out int x, out int y)
7089+
{
7090+
x = 10;
7091+
y = 2;
7092+
}
7093+
}
7094+
""";
7095+
var verifier = CompileAndVerify(source, expectedOutput: "True").VerifyDiagnostics();
7096+
7097+
verifier.VerifyIL("Program.Test2", @"
7098+
{
7099+
// Code size 36 (0x24)
7100+
.maxstack 3
7101+
.locals init (int V_0, //i
7102+
int V_1,
7103+
int V_2,
7104+
int V_3)
7105+
IL_0000: ldarg.0
7106+
IL_0001: box ""C""
7107+
IL_0006: ldloca.s V_1
7108+
IL_0008: ldloca.s V_2
7109+
IL_000a: call ""void Extensions.Deconstruct(object, out int, out int)""
7110+
IL_000f: ldloc.2
7111+
IL_0010: box ""int""
7112+
IL_0015: ldloca.s V_0
7113+
IL_0017: ldloca.s V_3
7114+
IL_0019: call ""void Extensions.Deconstruct(object, out int, out int)""
7115+
IL_001e: ldloc.0
7116+
IL_001f: ldc.i4.s 10
7117+
IL_0021: ceq
7118+
IL_0023: ret
7119+
}
7120+
");
7121+
}
7122+
7123+
[Fact]
7124+
public void DeconstructExtension_02()
7125+
{
7126+
// We check conversion during initial binding
7127+
var src = """
7128+
#pragma warning disable CS0436 // The type 'Span<T>' in '' conflicts with the imported type 'Span<T>'
7129+
7130+
var (x, y) = new int[] { 42 };
7131+
System.Console.Write((x, y));
7132+
7133+
class C { }
7134+
7135+
static class E
7136+
{
7137+
public static void Deconstruct(this System.Span<int> s, out int i, out int j) => throw null;
7138+
}
7139+
7140+
namespace System
7141+
{
7142+
public ref struct Span<T>
7143+
{
7144+
}
7145+
}
7146+
""";
7147+
var comp = CreateCompilation(src, targetFramework: TargetFramework.Net90);
7148+
comp.VerifyEmitDiagnostics(
7149+
// (3,1): error CS0656: Missing compiler required member 'Span<T>.op_Implicit'
7150+
// var (x, y) = new int[] { 42 };
7151+
Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "var (x, y) = new int[] { 42 }").WithArguments("System.Span<T>", "op_Implicit").WithLocation(3, 1)
7152+
);
7153+
}
70587154
}
70597155
}

src/Compilers/CSharp/Test/Emit/CodeGen/UnsafeTests.cs

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5353,6 +5353,123 @@ .locals init (Fixable V_0, //f
53535353
");
53545354
}
53555355

5356+
[Fact]
5357+
public void CustomFixedStructObjectExtension_01()
5358+
{
5359+
var text = @"
5360+
unsafe class C
5361+
{
5362+
public static void Main()
5363+
{
5364+
fixed (int* p = new Fixable(1))
5365+
{
5366+
System.Console.Write(p[1]);
5367+
}
5368+
5369+
var f = new Fixable(1);
5370+
fixed (int* p = f)
5371+
{
5372+
System.Console.Write(p[2]);
5373+
}
5374+
}
5375+
}
5376+
5377+
public struct Fixable
5378+
{
5379+
public Fixable(int arg){}
5380+
}
5381+
5382+
public static class FixableExt
5383+
{
5384+
public static ref readonly int GetPinnableReference(this object f)
5385+
{
5386+
return ref (new int[]{1,2,3})[0];
5387+
}
5388+
}
5389+
5390+
";
5391+
5392+
var compVerifier = CompileAndVerify(text, options: TestOptions.UnsafeReleaseExe, expectedOutput: @"23", verify: Verification.Fails);
5393+
5394+
compVerifier.VerifyIL("C.Main", @"
5395+
{
5396+
// Code size 64 (0x40)
5397+
.maxstack 3
5398+
.locals init (pinned int& V_0)
5399+
IL_0000: ldc.i4.1
5400+
IL_0001: newobj ""Fixable..ctor(int)""
5401+
IL_0006: box ""Fixable""
5402+
IL_000b: call ""ref readonly int FixableExt.GetPinnableReference(object)""
5403+
IL_0010: stloc.0
5404+
IL_0011: ldloc.0
5405+
IL_0012: conv.u
5406+
IL_0013: ldc.i4.4
5407+
IL_0014: add
5408+
IL_0015: ldind.i4
5409+
IL_0016: call ""void System.Console.Write(int)""
5410+
IL_001b: ldc.i4.0
5411+
IL_001c: conv.u
5412+
IL_001d: stloc.0
5413+
IL_001e: ldc.i4.1
5414+
IL_001f: newobj ""Fixable..ctor(int)""
5415+
IL_0024: box ""Fixable""
5416+
IL_0029: call ""ref readonly int FixableExt.GetPinnableReference(object)""
5417+
IL_002e: stloc.0
5418+
IL_002f: ldloc.0
5419+
IL_0030: conv.u
5420+
IL_0031: ldc.i4.2
5421+
IL_0032: conv.i
5422+
IL_0033: ldc.i4.4
5423+
IL_0034: mul
5424+
IL_0035: add
5425+
IL_0036: ldind.i4
5426+
IL_0037: call ""void System.Console.Write(int)""
5427+
IL_003c: ldc.i4.0
5428+
IL_003d: conv.u
5429+
IL_003e: stloc.0
5430+
IL_003f: ret
5431+
}
5432+
");
5433+
}
5434+
5435+
[Fact]
5436+
public void CustomFixedStructObjectExtension_02()
5437+
{
5438+
// We check conversion during initial binding
5439+
var text = """
5440+
#pragma warning disable CS0436 // The type 'ReadOnlySpan<T>' in '' conflicts with the imported type 'ReadOnlySpan<T>'
5441+
5442+
unsafe class C
5443+
{
5444+
public static void M()
5445+
{
5446+
System.ReadOnlySpan<string> x = default;
5447+
fixed (long* p = x)
5448+
{
5449+
}
5450+
}
5451+
}
5452+
5453+
static class E
5454+
{
5455+
public static ref long GetPinnableReference(this System.ReadOnlySpan<object> s) => throw null;
5456+
}
5457+
5458+
namespace System
5459+
{
5460+
public ref struct ReadOnlySpan<T>
5461+
{
5462+
}
5463+
}
5464+
""";
5465+
var comp = CreateCompilation(text, options: TestOptions.UnsafeDebugDll, targetFramework: TargetFramework.Net90);
5466+
comp.VerifyDiagnostics(
5467+
// (8,26): error CS0656: Missing compiler required member 'ReadOnlySpan<T>.CastUp'
5468+
// fixed (long* p = x)
5469+
Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "x").WithArguments("System.ReadOnlySpan<T>", "CastUp").WithLocation(8, 26)
5470+
);
5471+
}
5472+
53565473
[Fact]
53575474
public void CustomFixedStructRefExtension()
53585475
{

0 commit comments

Comments
 (0)