Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<AssemblySymbol>.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<AssemblySymbol>.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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
96 changes: 96 additions & 0 deletions src/Compilers/CSharp/Test/Emit/CodeGen/PatternTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>' in '' conflicts with the imported type 'Span<T>'

var (x, y) = new int[] { 42 };
System.Console.Write((x, y));

class C { }

static class E
{
public static void Deconstruct(this System.Span<int> s, out int i, out int j) => throw null;
}

namespace System
{
public ref struct Span<T>
{
}
}
""";
var comp = CreateCompilation(src, targetFramework: TargetFramework.Net90);
comp.VerifyEmitDiagnostics(
// (3,1): error CS0656: Missing compiler required member 'Span<T>.op_Implicit'
// var (x, y) = new int[] { 42 };
Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "var (x, y) = new int[] { 42 }").WithArguments("System.Span<T>", "op_Implicit").WithLocation(3, 1)
);
}
}
}
117 changes: 117 additions & 0 deletions src/Compilers/CSharp/Test/Emit/CodeGen/UnsafeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>' in '' conflicts with the imported type 'ReadOnlySpan<T>'

unsafe class C
{
public static void M()
{
System.ReadOnlySpan<string> x = default;
fixed (long* p = x)
{
}
}
}

static class E
{
public static ref long GetPinnableReference(this System.ReadOnlySpan<object> s) => throw null;
}

namespace System
{
public ref struct ReadOnlySpan<T>
{
}
}
""";
var comp = CreateCompilation(text, options: TestOptions.UnsafeDebugDll, targetFramework: TargetFramework.Net90);
comp.VerifyDiagnostics(
// (8,26): error CS0656: Missing compiler required member 'ReadOnlySpan<T>.CastUp'
// fixed (long* p = x)
Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "x").WithArguments("System.ReadOnlySpan<T>", "CastUp").WithLocation(8, 26)
);
}

[Fact]
public void CustomFixedStructRefExtension()
{
Expand Down