diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.PatternLocalRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.PatternLocalRewriter.cs index cc9f1d1ed430c..68e372f41e365 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.PatternLocalRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.PatternLocalRewriter.cs @@ -175,7 +175,8 @@ void addArg(RefKind refKind, BoundExpression expression) { Debug.Assert(method.IsExtensionMethod); receiver = _factory.Type(method.ContainingType); - addArg(method.ParameterRefKinds[0], input); + // Tracked by https://github.com/dotnet/roslyn/issues/78827 : MQ, Consider preserving the BoundConversion from initial binding instead of using markAsChecked here + addArg(method.ParameterRefKinds[0], _localRewriter.ConvertReceiverForExtensionIfNeeded(input, markAsChecked: true, method.Parameters[0])); extensionExtra = 1; } else diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.cs index b230509433b68..e8cfd169d41b8 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.cs @@ -1152,16 +1152,21 @@ private BoundExpression ConvertReceiverForExtensionMemberIfNeeded(Symbol member, Debug.Assert(!member.IsStatic); ParameterSymbol? extensionParameter = member.ContainingType.ExtensionParameter; Debug.Assert(extensionParameter is not null); -#if DEBUG - var discardedUseSiteInfo = CompoundUseSiteInfo.Discarded; - Debug.Assert(Conversions.IsValidExtensionMethodThisArgConversion(this._compilation.Conversions.ClassifyConversionFromType(receiver.Type, extensionParameter.Type, isChecked: false, ref discardedUseSiteInfo))); -#endif - - // We don't need to worry about checked context because only implicit conversions are allowed on the receiver of an extension member - return MakeConversionNode(receiver, extensionParameter.Type, @checked: false, acceptFailingConversion: false, markAsChecked: markAsChecked); + return ConvertReceiverForExtensionIfNeeded(receiver, markAsChecked, extensionParameter); } return receiver; } + + private BoundExpression ConvertReceiverForExtensionIfNeeded(BoundExpression receiver, bool markAsChecked, ParameterSymbol extensionParameter) + { +#if DEBUG + var discardedUseSiteInfo = CompoundUseSiteInfo.Discarded; + Debug.Assert(Conversions.IsValidExtensionMethodThisArgConversion(this._compilation.Conversions.ClassifyConversionFromType(receiver.Type, extensionParameter.Type, isChecked: false, ref discardedUseSiteInfo))); +#endif + + // We don't need to worry about checked context because only implicit conversions are allowed on the receiver of an extension member + return MakeConversionNode(receiver, extensionParameter.Type, @checked: false, acceptFailingConversion: false, markAsChecked: markAsChecked); + } } } diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_FixedStatement.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_FixedStatement.cs index 92102501e78db..25cb71140c1f6 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_FixedStatement.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_FixedStatement.cs @@ -356,10 +356,9 @@ private BoundStatement InitializeFixedStatementGetPinnable( // Tracked by https://github.com/dotnet/roslyn/issues/78827 : MQ, Consider preserving the BoundConversion from initial binding instead of using markAsChecked here // .GetPinnable() - callReceiver = this.ConvertReceiverForExtensionMemberIfNeeded(getPinnableMethod, callReceiver, markAsChecked: true); var getPinnableCall = getPinnableMethod.IsStatic ? - factory.Call(null, getPinnableMethod, callReceiver) : - factory.Call(callReceiver, getPinnableMethod); + factory.Call(null, getPinnableMethod, this.ConvertReceiverForExtensionIfNeeded(callReceiver, markAsChecked: true, getPinnableMethod.Parameters[0])) : + factory.Call(this.ConvertReceiverForExtensionMemberIfNeeded(getPinnableMethod, callReceiver, markAsChecked: true), getPinnableMethod); // temp =ref .GetPinnable() var tempAssignment = factory.AssignmentExpression( diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/PatternTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/PatternTests.cs index c3152c576ebf8..4880c07ba2b7f 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/PatternTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/PatternTests.cs @@ -7055,5 +7055,101 @@ class A : I // if (a is (I or null) and var x) Diagnostic(ErrorCode.WRN_IsPatternAlways, "a is (I or null) and var x").WithArguments("A").WithLocation(26, 9)); } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/81173")] + public void DeconstructExtension_01() + { + var source = """ +class Program +{ + static void Main() + { + System.Console.Write(Test2(new C())); + } + + static bool Test2(C u) + { + return u is var (_ , (i, _)) && (int)i == 10; + } + + static void Test3(C c, int i) + { + var (a, b) = c; + var (u, v) = i; + } +} + +public struct C +{ +} + +static class Extensions +{ + public static void Deconstruct(this object o, out int x, out int y) + { + x = 10; + y = 2; + } +} +"""; + var verifier = CompileAndVerify(source, expectedOutput: "True").VerifyDiagnostics(); + + verifier.VerifyIL("Program.Test2", @" +{ + // Code size 36 (0x24) + .maxstack 3 + .locals init (int V_0, //i + int V_1, + int V_2, + int V_3) + IL_0000: ldarg.0 + IL_0001: box ""C"" + IL_0006: ldloca.s V_1 + IL_0008: ldloca.s V_2 + IL_000a: call ""void Extensions.Deconstruct(object, out int, out int)"" + IL_000f: ldloc.2 + IL_0010: box ""int"" + IL_0015: ldloca.s V_0 + IL_0017: ldloca.s V_3 + IL_0019: call ""void Extensions.Deconstruct(object, out int, out int)"" + IL_001e: ldloc.0 + IL_001f: ldc.i4.s 10 + IL_0021: ceq + IL_0023: ret +} +"); + } + + [Fact] + public void DeconstructExtension_02() + { + // We check conversion during initial binding + var src = """ +#pragma warning disable CS0436 // The type 'Span' in '' conflicts with the imported type 'Span' + +var (x, y) = new int[] { 42 }; +System.Console.Write((x, y)); + +class C { } + +static class E +{ + public static void Deconstruct(this System.Span s, out int i, out int j) => throw null; +} + +namespace System +{ + public ref struct Span + { + } +} +"""; + var comp = CreateCompilation(src, targetFramework: TargetFramework.Net90); + comp.VerifyEmitDiagnostics( + // (3,1): error CS0656: Missing compiler required member 'Span.op_Implicit' + // var (x, y) = new int[] { 42 }; + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "var (x, y) = new int[] { 42 }").WithArguments("System.Span", "op_Implicit").WithLocation(3, 1) + ); + } } } diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/UnsafeTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/UnsafeTests.cs index 9d8a0e2463364..6e9a6c8c86f92 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/UnsafeTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/UnsafeTests.cs @@ -5353,6 +5353,123 @@ .locals init (Fixable V_0, //f "); } + [Fact] + public void CustomFixedStructObjectExtension_01() + { + var text = @" +unsafe class C +{ + public static void Main() + { + fixed (int* p = new Fixable(1)) + { + System.Console.Write(p[1]); + } + + var f = new Fixable(1); + fixed (int* p = f) + { + System.Console.Write(p[2]); + } + } +} + +public struct Fixable +{ + public Fixable(int arg){} +} + +public static class FixableExt +{ + public static ref readonly int GetPinnableReference(this object f) + { + return ref (new int[]{1,2,3})[0]; + } +} + +"; + + var compVerifier = CompileAndVerify(text, options: TestOptions.UnsafeReleaseExe, expectedOutput: @"23", verify: Verification.Fails); + + compVerifier.VerifyIL("C.Main", @" +{ + // Code size 64 (0x40) + .maxstack 3 + .locals init (pinned int& V_0) + IL_0000: ldc.i4.1 + IL_0001: newobj ""Fixable..ctor(int)"" + IL_0006: box ""Fixable"" + IL_000b: call ""ref readonly int FixableExt.GetPinnableReference(object)"" + IL_0010: stloc.0 + IL_0011: ldloc.0 + IL_0012: conv.u + IL_0013: ldc.i4.4 + IL_0014: add + IL_0015: ldind.i4 + IL_0016: call ""void System.Console.Write(int)"" + IL_001b: ldc.i4.0 + IL_001c: conv.u + IL_001d: stloc.0 + IL_001e: ldc.i4.1 + IL_001f: newobj ""Fixable..ctor(int)"" + IL_0024: box ""Fixable"" + IL_0029: call ""ref readonly int FixableExt.GetPinnableReference(object)"" + IL_002e: stloc.0 + IL_002f: ldloc.0 + IL_0030: conv.u + IL_0031: ldc.i4.2 + IL_0032: conv.i + IL_0033: ldc.i4.4 + IL_0034: mul + IL_0035: add + IL_0036: ldind.i4 + IL_0037: call ""void System.Console.Write(int)"" + IL_003c: ldc.i4.0 + IL_003d: conv.u + IL_003e: stloc.0 + IL_003f: ret +} +"); + } + + [Fact] + public void CustomFixedStructObjectExtension_02() + { + // We check conversion during initial binding + var text = """ +#pragma warning disable CS0436 // The type 'ReadOnlySpan' in '' conflicts with the imported type 'ReadOnlySpan' + +unsafe class C +{ + public static void M() + { + System.ReadOnlySpan x = default; + fixed (long* p = x) + { + } + } +} + +static class E +{ + public static ref long GetPinnableReference(this System.ReadOnlySpan s) => throw null; +} + +namespace System +{ + public ref struct ReadOnlySpan + { + } +} +"""; + var comp = CreateCompilation(text, options: TestOptions.UnsafeDebugDll, targetFramework: TargetFramework.Net90); + comp.VerifyDiagnostics( + // (8,26): error CS0656: Missing compiler required member 'ReadOnlySpan.CastUp' + // fixed (long* p = x) + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "x").WithArguments("System.ReadOnlySpan", "CastUp").WithLocation(8, 26) + ); + } + [Fact] public void CustomFixedStructRefExtension() {