diff --git a/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs b/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs index 6832ae7bce416..61a9cf14c1ebc 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs @@ -3026,13 +3026,13 @@ private bool CheckInvocationArgMixingWithUpdatedRules( } } - inferDeclarationExpressionValEscape(); + inferDeclarationExpressionValEscape(argsOpt, localScopeDepth, escapeValues); mixableArguments.Free(); escapeValues.Free(); return valid; - void inferDeclarationExpressionValEscape() + void inferDeclarationExpressionValEscape(ImmutableArray argsOpt, SafeContext localScopeDepth, ArrayBuilder escapeValues) { // find the widest scope that arguments could safely escape to. // use this scope as the inferred STE of declaration expressions. diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.PatternLocalRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.PatternLocalRewriter.cs index ac58ea84b9d5a..99d4efd16e1c3 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.PatternLocalRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.PatternLocalRewriter.cs @@ -152,7 +152,8 @@ protected BoundExpression LowerEvaluation(BoundDagEvaluation evaluation) PropertySymbol property = p.Property; var outputTemp = new BoundDagTemp(p.Syntax, property.Type, p); BoundExpression output = _tempAllocator.GetTemp(outputTemp); - input = _factory.ConvertReceiverForExtensionMemberIfNeeded(property, input); + // Tracked by https://github.com/dotnet/roslyn/issues/76130 : Consider preserving the BoundConversion from initial binding instead of using markAsChecked here + input = _localRewriter.ConvertReceiverForExtensionMemberIfNeeded(property, input, markAsChecked: true); return _factory.AssignmentExpression(output, _localRewriter.MakePropertyAccess(_factory.Syntax, input, property, LookupResultKind.Viable, property.Type, isLeftOfAssignment: false)); } @@ -191,7 +192,8 @@ void addArg(RefKind refKind, BoundExpression expression) addArg(RefKind.Out, _tempAllocator.GetTemp(outputTemp)); } - receiver = _factory.ConvertReceiverForExtensionMemberIfNeeded(method, receiver); + // Tracked by https://github.com/dotnet/roslyn/issues/76130 : Consider preserving the BoundConversion from initial binding instead of using markAsChecked here + receiver = _localRewriter.ConvertReceiverForExtensionMemberIfNeeded(method, receiver, markAsChecked: true); return _factory.Call(receiver, method, refKindBuilder.ToImmutableAndFree(), argBuilder.ToImmutableAndFree()); } diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.cs index 6f8840715c10a..0c90604e3f347 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.cs @@ -1136,6 +1136,25 @@ private CompoundUseSiteInfo GetNewCompoundUseSiteInfo() return new CompoundUseSiteInfo(_diagnostics, _compilation.Assembly); } + private BoundExpression ConvertReceiverForExtensionMemberIfNeeded(Symbol member, BoundExpression receiver, bool markAsChecked) + { + if (member.GetIsNewExtensionMember()) + { + 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 receiver; + } + #if DEBUG /// /// Note: do not use a static/singleton instance of this type, as it holds state. diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Conversion.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Conversion.cs index 0dfa62d65d160..d4be4632631af 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Conversion.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Conversion.cs @@ -781,6 +781,10 @@ bool IsInRange(SpecialType type, SpecialType low, SpecialType high) => /// (e.g. if the default value was specified by an attribute and was, therefore, not checked by the compiler). /// Set acceptFailingConversion if you want to see default(rewrittenType) in such cases. /// The error will be suppressed only for conversions from or . + /// + /// Generally, conversions should be checked and BoundConversion nodes created during initial binding and re-used in lowering. But in some cases, + /// we do not preserve the conversion node. In such case, is set to true + /// to indicate that the conversion has been previously checked during initial binding. /// private BoundExpression MakeConversionNode(BoundExpression rewrittenOperand, TypeSymbol rewrittenType, bool @checked, bool acceptFailingConversion = false, bool markAsChecked = false) { diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_FixedStatement.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_FixedStatement.cs index 718c3fda82af6..ec334729f326c 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_FixedStatement.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_FixedStatement.cs @@ -354,8 +354,9 @@ private BoundStatement InitializeFixedStatementGetPinnable( callReceiver = initializerExpr; } + // Tracked by https://github.com/dotnet/roslyn/issues/76130 : Consider preserving the BoundConversion from initial binding instead of using markAsChecked here // .GetPinnable() - callReceiver = factory.ConvertReceiverForExtensionMemberIfNeeded(getPinnableMethod, callReceiver); + callReceiver = this.ConvertReceiverForExtensionMemberIfNeeded(getPinnableMethod, callReceiver, markAsChecked: true); var getPinnableCall = getPinnableMethod.IsStatic ? factory.Call(null, getPinnableMethod, callReceiver) : factory.Call(callReceiver, getPinnableMethod); diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_ObjectOrCollectionInitializerExpression.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_ObjectOrCollectionInitializerExpression.cs index da2b10c2df251..ee0fe551e9633 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_ObjectOrCollectionInitializerExpression.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_ObjectOrCollectionInitializerExpression.cs @@ -702,7 +702,8 @@ private BoundExpression MakeObjectInitializerMemberAccess( _compilation.Conversions.HasImplicitConversionToOrImplementsVarianceCompatibleInterface(rewrittenReceiver.Type, memberSymbol.ContainingType, ref discardedUseSiteInfo, out _)); // 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 #endif - rewrittenReceiver = _factory.ConvertReceiverForExtensionMemberIfNeeded(memberSymbol, rewrittenReceiver); + // Tracked by https://github.com/dotnet/roslyn/issues/76130 : Consider preserving the BoundConversion from initial binding instead of using markAsChecked here + rewrittenReceiver = this.ConvertReceiverForExtensionMemberIfNeeded(memberSymbol, rewrittenReceiver, markAsChecked: true); switch (memberSymbol.Kind) { diff --git a/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs b/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs index 72e2a86bf7c93..15748f836eb9c 100644 --- a/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs +++ b/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs @@ -431,24 +431,6 @@ public BoundExpression AssignmentExpression(BoundExpression left, BoundExpressio return AssignmentExpression(Syntax, left, right, isRef: isRef, wasCompilerGenerated: true); } - public BoundExpression ConvertReceiverForExtensionMemberIfNeeded(Symbol member, BoundExpression receiver) - { - if (member.GetIsNewExtensionMember()) - { - 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 - - return this.Convert(extensionParameter.Type, receiver); - } - - return receiver; - } - /// /// Creates a general assignment that might be instrumented. /// diff --git a/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs b/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs index b0656529312c0..0df392c8d5fe3 100644 --- a/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs +++ b/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs @@ -8,6 +8,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics; using System.Linq; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Symbols.Metadata.PE; @@ -20417,7 +20418,7 @@ static class E } [Fact] - public void ExtensionMemberLookup_PatternBased_Deconstruct_Conversion() + public void ExtensionMemberLookup_PatternBased_Deconstruct_Conversion_01() { var src = """ var (x, y) = new C(); @@ -20437,6 +20438,91 @@ static class E CompileAndVerify(comp, expectedOutput: "(42, 43)").VerifyDiagnostics(); } + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/78682")] + public void ExtensionMemberLookup_PatternBased_Deconstruct_Conversion_02() + { + // array to Span + var src = """ +var (x, y) = new int[] { 42 }; +System.Console.Write((x, y)); + +class C { } + +static class E +{ + extension(System.Span s) + { + public void Deconstruct(out int i, out int j) { i = 42; j = 43; } + } +} +"""; + + // Tracked by https://github.com/dotnet/roslyn/issues/78682 : ref analysis fails with an implicit span conversion on receiver of a deconstruction + try + { + var comp = CreateCompilation(src, targetFramework: TargetFramework.Net90); + CompileAndVerify(comp, expectedOutput: ExpectedOutput("(42, 43)"), verify: Verification.Skipped).VerifyDiagnostics(); + } + catch (InvalidOperationException) + { + return; + } + + Debug.Assert(false); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/78682")] + public void ExtensionMemberLookup_PatternBased_Deconstruct_Conversion_03() + { + // We check conversion during initial binding + var src = """ +var (x, y) = new int[] { 42 }; +System.Console.Write((x, y)); + +class C { } + +static class E +{ + extension(System.Span s) + { + public void Deconstruct(out int i, out int j) => throw null; + } +} + +namespace System +{ + public ref struct Span + { + } +} +"""; +#if RELEASE + var comp = CreateCompilation(src, targetFramework: TargetFramework.Net90); + comp.VerifyEmitDiagnostics( + // (1,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(1, 1), + // (8,22): warning CS0436: The type 'Span' in '' conflicts with the imported type 'Span' in 'System.Runtime, Version=9.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'. Using the type defined in ''. + // extension(System.Span s) + Diagnostic(ErrorCode.WRN_SameFullNameThisAggAgg, "Span").WithArguments("", "System.Span", "System.Runtime, Version=9.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", "System.Span").WithLocation(8, 22)); +#endif + +#if DEBUG + // Tracked by https://github.com/dotnet/roslyn/issues/78682 : ref analysis fails with an implicit span conversion on receiver of a deconstruction + try + { + var comp = CreateCompilation(src, targetFramework: TargetFramework.Net90); + comp.VerifyEmitDiagnostics(); + } + catch (InvalidOperationException) + { + return; + } + + Debug.Assert(false); +#endif + } + [Fact] public void ExtensionMemberLookup_PatternBased_Deconstruct_Generic() { @@ -20930,7 +21016,7 @@ static class E } [Fact] - public void ExtensionMemberLookup_PatternBased_Fixed_02_Conversion() + public void ExtensionMemberLookup_PatternBased_Fixed_Conversion_01() { var text = """ unsafe class C @@ -20958,6 +21044,77 @@ static class E CompileAndVerify(comp, expectedOutput: "pin 2", verify: Verification.Skipped).VerifyDiagnostics(); } + [Fact] + public void ExtensionMemberLookup_PatternBased_Fixed_Conversion_02() + { + var text = """ +unsafe class C +{ + public static void Main() + { + fixed (int* p = (0, "")) + { + System.Console.WriteLine(p[1]); + } + } +} + +static class E +{ + extension((object, object) t) + { + public ref int GetPinnableReference() { System.Console.Write("pin "); return ref (new int[] { 1, 2, 3 })[0]; } + } +} +"""; + var comp = CreateCompilation(text, options: TestOptions.UnsafeReleaseExe, targetFramework: TargetFramework.Net90); + CompileAndVerify(comp, expectedOutput: ExpectedOutput("pin 2"), verify: Verification.Skipped).VerifyDiagnostics(); + } + + [Fact] + public void ExtensionMemberLookup_PatternBased_Fixed_Conversion_03() + { + // We check conversion during initial binding + var text = """ +unsafe class C +{ + public static void M() + { + System.ReadOnlySpan x = default; + fixed (long* p = x) + { + } + } +} + +static class E +{ + extension(System.ReadOnlySpan s) + { + public ref long GetPinnableReference() => throw null; + } +} + +namespace System +{ + public ref struct ReadOnlySpan + { + } +} +"""; + var comp = CreateCompilation(text, options: TestOptions.UnsafeDebugDll, targetFramework: TargetFramework.Net90); + comp.VerifyDiagnostics( + // (5,16): warning CS0436: The type 'ReadOnlySpan' in '' conflicts with the imported type 'ReadOnlySpan' in 'System.Runtime, Version=9.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'. Using the type defined in ''. + // System.ReadOnlySpan x = default; + Diagnostic(ErrorCode.WRN_SameFullNameThisAggAgg, "ReadOnlySpan").WithArguments("", "System.ReadOnlySpan", "System.Runtime, Version=9.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", "System.ReadOnlySpan").WithLocation(5, 16), + // (6,26): error CS0656: Missing compiler required member 'ReadOnlySpan.CastUp' + // fixed (long* p = x) + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "x").WithArguments("System.ReadOnlySpan", "CastUp").WithLocation(6, 26), + // (14,22): warning CS0436: The type 'ReadOnlySpan' in '' conflicts with the imported type 'ReadOnlySpan' in 'System.Runtime, Version=9.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'. Using the type defined in ''. + // extension(System.ReadOnlySpan s) + Diagnostic(ErrorCode.WRN_SameFullNameThisAggAgg, "ReadOnlySpan").WithArguments("", "System.ReadOnlySpan", "System.Runtime, Version=9.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", "System.ReadOnlySpan").WithLocation(14, 22)); + } + [Fact] public void ExtensionMemberLookup_PatternBased_Fixed_03_DelegateTypeProperty() { @@ -22180,7 +22337,7 @@ public int Property } [Fact] - public void ExtensionMemberLookup_Patterns_Conversion() + public void ExtensionMemberLookup_Patterns_Conversion_01() { var src = """ var c = new C(); @@ -22203,6 +22360,382 @@ public int Property CompileAndVerify(comp, expectedOutput: "property").VerifyDiagnostics(); } + [Fact] + public void ExtensionMemberLookup_Patterns_Conversion_02() + { + // implicit span conversion: array to Span + var src = """ +int[] i = [42]; +_ = i is { Property: 42 }; +i.M(); + +class C { } + +static class E +{ + extension(System.Span s) + { + public int Property + { + get { System.Console.Write("property "); return 42; } + } + } + public static void M(this System.Span s) { System.Console.Write("method"); } +} +"""; + var comp = CreateCompilation(src, targetFramework: TargetFramework.Net90); + var verify = CompileAndVerify(comp, expectedOutput: ExpectedOutput("property method"), verify: Verification.Skipped).VerifyDiagnostics(); + verify.VerifyIL("", """ +{ + // Code size 46 (0x2e) + .maxstack 4 + .locals init (int[] V_0) //i + IL_0000: ldc.i4.1 + IL_0001: newarr "int" + IL_0006: dup + IL_0007: ldc.i4.0 + IL_0008: ldc.i4.s 42 + IL_000a: stelem.i4 + IL_000b: stloc.0 + IL_000c: ldloc.0 + IL_000d: brfalse.s IL_0020 + IL_000f: ldloc.0 + IL_0010: call "System.Span System.Span.op_Implicit(int[])" + IL_0015: call "int E.get_Property(System.Span)" + IL_001a: ldc.i4.s 42 + IL_001c: ceq + IL_001e: br.s IL_0021 + IL_0020: ldc.i4.0 + IL_0021: pop + IL_0022: ldloc.0 + IL_0023: call "System.Span System.Span.op_Implicit(int[])" + IL_0028: call "void E.M(System.Span)" + IL_002d: ret +} +"""); + } + + [Fact] + public void ExtensionMemberLookup_Patterns_Conversion_03() + { + // implicit span conversion: array to Span + var src = """ +string[] i = [""]; +_ = i is { Property: 42 }; +i.M(); + +class C { } + +static class E +{ + extension(System.Span s) + { + public int Property + { + get { System.Console.Write("property "); return 42; } + } + } + public static void M(this System.Span s) { System.Console.Write("method"); } +} +"""; + var comp = CreateCompilation(src, targetFramework: TargetFramework.Net90); + comp.VerifyEmitDiagnostics( + // (2,12): error CS9286: 'string[]' does not contain a definition for 'Property' and no accessible extension member 'Property' for receiver of type 'string[]' could be found (are you missing a using directive or an assembly reference?) + // _ = i is { Property: 42 }; + Diagnostic(ErrorCode.ERR_ExtensionResolutionFailed, "Property").WithArguments("string[]", "Property").WithLocation(2, 12), + // (3,1): error CS1929: 'string[]' does not contain a definition for 'M' and the best extension method overload 'E.M(Span)' requires a receiver of type 'System.Span' + // i.M(); + Diagnostic(ErrorCode.ERR_BadInstanceArgType, "i").WithArguments("string[]", "M", "E.M(System.Span)", "System.Span").WithLocation(3, 1)); + } + + [Fact] + public void ExtensionMemberLookup_Patterns_Conversion_04() + { + // implicit span conversion: Span to ReadOnlySpan + var src = """ +System.Span i = [""]; +_ = i is { Property: 42 }; +i.M(); + +class C { } + +static class E +{ + extension(System.ReadOnlySpan s) + { + public int Property + { + get { System.Console.Write("property "); return 42; } + } + } + public static void M(this System.ReadOnlySpan s) { System.Console.Write("method"); } +} +"""; + var comp = CreateCompilation(src, targetFramework: TargetFramework.Net90); + var verify = CompileAndVerify(comp, expectedOutput: ExpectedOutput("property method"), verify: Verification.Skipped).VerifyDiagnostics(); + verify.VerifyIL("", """ +{ + // Code size 46 (0x2e) + .maxstack 2 + .locals init (string V_0) + IL_0000: ldstr "" + IL_0005: stloc.0 + IL_0006: ldloca.s V_0 + IL_0008: newobj "System.Span..ctor(ref string)" + IL_000d: dup + IL_000e: call "System.ReadOnlySpan System.Span.op_Implicit(System.Span)" + IL_0013: call "System.ReadOnlySpan System.ReadOnlySpan.CastUp(System.ReadOnlySpan)" + IL_0018: call "int E.get_Property(System.ReadOnlySpan)" + IL_001d: pop + IL_001e: call "System.ReadOnlySpan System.Span.op_Implicit(System.Span)" + IL_0023: call "System.ReadOnlySpan System.ReadOnlySpan.CastUp(System.ReadOnlySpan)" + IL_0028: call "void E.M(System.ReadOnlySpan)" + IL_002d: ret +} +"""); + } + + [Fact] + public void ExtensionMemberLookup_Patterns_Conversion_05() + { + // implicit span conversion: ReadOnlySpan to ReadOnlySpan + var src = """ +System.ReadOnlySpan i = [""]; +_ = i is { Property: 42 }; +i.M(); + +class C { } + +static class E +{ + extension(System.ReadOnlySpan s) + { + public int Property + { + get { System.Console.Write("property "); return 42; } + } + } + public static void M(this System.ReadOnlySpan s) { System.Console.Write("method"); } +} +"""; + var comp = CreateCompilation(src, targetFramework: TargetFramework.Net90); + var verify = CompileAndVerify(comp, expectedOutput: ExpectedOutput("property method"), verify: Verification.Skipped).VerifyDiagnostics(); + verify.VerifyIL("", """ +{ + // Code size 36 (0x24) + .maxstack 2 + .locals init (string V_0) + IL_0000: ldstr "" + IL_0005: stloc.0 + IL_0006: ldloca.s V_0 + IL_0008: newobj "System.ReadOnlySpan..ctor(ref readonly string)" + IL_000d: dup + IL_000e: call "System.ReadOnlySpan System.ReadOnlySpan.CastUp(System.ReadOnlySpan)" + IL_0013: call "int E.get_Property(System.ReadOnlySpan)" + IL_0018: pop + IL_0019: call "System.ReadOnlySpan System.ReadOnlySpan.CastUp(System.ReadOnlySpan)" + IL_001e: call "void E.M(System.ReadOnlySpan)" + IL_0023: ret +} +"""); + } + + [Fact] + public void ExtensionMemberLookup_Patterns_Conversion_06() + { + // implicit span conversion: string to ReadOnlySpan + var src = """ +string s = ""; +_ = s is { Property: 42 }; +s.M(); + +class C { } + +static class E +{ + extension(System.ReadOnlySpan s) + { + public int Property + { + get { System.Console.Write("property "); return 42; } + } + } + public static void M(this System.ReadOnlySpan s) { System.Console.Write("method"); } +} +"""; + var comp = CreateCompilation(src, targetFramework: TargetFramework.Net90); + var verify = CompileAndVerify(comp, expectedOutput: ExpectedOutput("property method"), verify: Verification.Skipped).VerifyDiagnostics(); + verify.VerifyIL("", """ +{ + // Code size 40 (0x28) + .maxstack 2 + .locals init (string V_0) //s + IL_0000: ldstr "" + IL_0005: stloc.0 + IL_0006: ldloc.0 + IL_0007: brfalse.s IL_001a + IL_0009: ldloc.0 + IL_000a: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_000f: call "int E.get_Property(System.ReadOnlySpan)" + IL_0014: ldc.i4.s 42 + IL_0016: ceq + IL_0018: br.s IL_001b + IL_001a: ldc.i4.0 + IL_001b: pop + IL_001c: ldloc.0 + IL_001d: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_0022: call "void E.M(System.ReadOnlySpan)" + IL_0027: ret +} +"""); + + var spanSrc = """ +namespace System; + +public readonly ref struct ReadOnlySpan +{ +} +"""; + comp = CreateCompilation([src, spanSrc]); + comp.VerifyEmitDiagnostics( + // (2,12): error CS0656: Missing compiler required member 'System.MemoryExtensions.AsSpan' + // _ = s is { Property: 42 }; + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "Property").WithArguments("System.MemoryExtensions", "AsSpan").WithLocation(2, 12), + // (3,1): error CS0656: Missing compiler required member 'System.MemoryExtensions.AsSpan' + // s.M(); + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "s").WithArguments("System.MemoryExtensions", "AsSpan").WithLocation(3, 1)); + } + + [Fact] + public void ExtensionMemberLookup_Patterns_Conversion_07() + { + // boxing + var src = """ +int i = 42; +_ = i is { Property: 42 }; +i.M(); + +class C { } + +static class E +{ + extension(object o) + { + public int Property + { + get { System.Console.Write("property "); return 42; } + } + } + public static void M(this object o) { System.Console.Write("method"); } +} +"""; + var comp = CreateCompilation(src); + var verify = CompileAndVerify(comp, expectedOutput: "property method").VerifyDiagnostics(); + verify.VerifyIL("", """ +{ + // Code size 25 (0x19) + .maxstack 2 + IL_0000: ldc.i4.s 42 + IL_0002: dup + IL_0003: box "int" + IL_0008: call "int E.get_Property(object)" + IL_000d: pop + IL_000e: box "int" + IL_0013: call "void E.M(object)" + IL_0018: ret +} +"""); + } + + [Fact] + public void ExtensionMemberLookup_Patterns_Conversion_08() + { + // implicit tuple conversion + var src = """ +(int, string) t = (42, ""); +_ = t is { Property: 42 }; +t.M(); + +class C { } + +static class E +{ + extension((object, object) t) + { + public int Property + { + get { System.Console.Write("property "); return 42; } + } + } + public static void M(this (object, object) t) { System.Console.Write("method"); } +} +"""; + var comp = CreateCompilation(src, targetFramework: TargetFramework.Net90); + var verify = CompileAndVerify(comp, expectedOutput: ExpectedOutput("property method"), verify: Verification.Skipped).VerifyDiagnostics(); + verify.VerifyIL("", """ +{ + // Code size 71 (0x47) + .maxstack 3 + .locals init (System.ValueTuple V_0) + IL_0000: ldc.i4.s 42 + IL_0002: ldstr "" + IL_0007: newobj "System.ValueTuple..ctor(int, string)" + IL_000c: dup + IL_000d: stloc.0 + IL_000e: ldloc.0 + IL_000f: ldfld "int System.ValueTuple.Item1" + IL_0014: box "int" + IL_0019: ldloc.0 + IL_001a: ldfld "string System.ValueTuple.Item2" + IL_001f: newobj "System.ValueTuple..ctor(object, object)" + IL_0024: call "int E.get_Property(System.ValueTuple)" + IL_0029: pop + IL_002a: stloc.0 + IL_002b: ldloc.0 + IL_002c: ldfld "int System.ValueTuple.Item1" + IL_0031: box "int" + IL_0036: ldloc.0 + IL_0037: ldfld "string System.ValueTuple.Item2" + IL_003c: newobj "System.ValueTuple..ctor(object, object)" + IL_0041: call "void E.M(System.ValueTuple)" + IL_0046: ret +} +"""); + } + + [Fact] + public void ExtensionMemberLookup_Patterns_Conversion_09() + { + // We check conversion during initial binding + var src = """ +int[] i = []; +_ = i is { Property: 42 }; + +static class E +{ + extension(System.ReadOnlySpan r) + { + public int Property => throw null; + } +} + +namespace System +{ + public ref struct ReadOnlySpan + { + } +} +"""; + var comp = CreateCompilation(src, targetFramework: TargetFramework.Net90); + comp.VerifyEmitDiagnostics( + // (2,12): error CS0656: Missing compiler required member 'ReadOnlySpan.op_Implicit' + // _ = i is { Property: 42 }; + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "Property").WithArguments("System.ReadOnlySpan", "op_Implicit").WithLocation(2, 12), + // (6,22): warning CS0436: The type 'ReadOnlySpan' in '' conflicts with the imported type 'ReadOnlySpan' in 'System.Runtime, Version=9.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'. Using the type defined in ''. + // extension(System.ReadOnlySpan r) + Diagnostic(ErrorCode.WRN_SameFullNameThisAggAgg, "ReadOnlySpan").WithArguments("", "System.ReadOnlySpan", "System.Runtime, Version=9.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", "System.ReadOnlySpan").WithLocation(6, 22)); + } + [Fact] public void ExtensionMemberLookup_Patterns_ExtendedPropertyPattern() { @@ -22360,7 +22893,7 @@ static class E } [Fact] - public void ExtensionMemberLookup_ObjectInitializer_Conversion() + public void ExtensionMemberLookup_ObjectInitializer_Conversion_01() { var src = """ _ = new C() { Property = 42 }; @@ -22380,13 +22913,106 @@ static class E CompileAndVerify(comp, expectedOutput: "property").VerifyDiagnostics(); } + [Fact] + public void ExtensionMemberLookup_ObjectInitializer_Conversion_02() + { + var src = """ +_ = new System.ReadOnlySpan() { Property = 42 }; + +new System.ReadOnlySpan().Property = 43; + +class C { } + +static class E +{ + extension(System.ReadOnlySpan s) + { + public int Property { set { } } + } +} +"""; + + // Tracked by https://github.com/dotnet/roslyn/issues/76130 : consider adjusting receiver requirements for extension members + var comp = CreateCompilation(src, targetFramework: TargetFramework.Net90); + comp.VerifyEmitDiagnostics( + // (1,41): error CS0131: The left-hand side of an assignment must be a variable, property or indexer + // _ = new System.ReadOnlySpan() { Property = 42 }; + Diagnostic(ErrorCode.ERR_AssgLvalueExpected, "Property").WithLocation(1, 41), + // (3,1): error CS0131: The left-hand side of an assignment must be a variable, property or indexer + // new System.ReadOnlySpan().Property = 43; + Diagnostic(ErrorCode.ERR_AssgLvalueExpected, "new System.ReadOnlySpan().Property").WithLocation(3, 1)); + + src = """ +new S().Property = 42; + +struct S +{ + public int Property { set { } } +} +"""; + + comp = CreateCompilation(src, targetFramework: TargetFramework.Net90); + comp.VerifyEmitDiagnostics( + // (1,1): error CS0131: The left-hand side of an assignment must be a variable, property or indexer + // new S().Property = 42; + Diagnostic(ErrorCode.ERR_AssgLvalueExpected, "new S().Property").WithLocation(1, 1)); + } + + [Fact] + public void ExtensionMemberLookup_ObjectInitializer_Conversion_03() + { + // implicit tuple conversion + var src = """ +_ = new System.ValueTuple() { Property = 42 }; + +static class E +{ + extension((object, object) t) + { + public int Property + { + set { System.Console.Write("property"); } + } + } +} +"""; + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics( + // (1,44): error CS0131: The left-hand side of an assignment must be a variable, property or indexer + // _ = new System.ValueTuple() { Property = 42 }; + Diagnostic(ErrorCode.ERR_AssgLvalueExpected, "Property").WithLocation(1, 44)); + } + + [Fact] + public void ExtensionMemberLookup_ObjectInitializer_Conversion_04() + { + // implicit span conversion from string + var src = """ +_ = new System.String('a', 10) { Property = 42 }; + +static class E +{ + extension(System.ReadOnlySpan s) + { + public int Property + { + set { System.Console.Write(value); } + } + } +} +"""; + var comp = CreateCompilation(src, targetFramework: TargetFramework.Net90); + comp.VerifyDiagnostics( + // (1,34): error CS0131: The left-hand side of an assignment must be a variable, property or indexer + // _ = new System.String('a', 10) { Property = 42 }; + Diagnostic(ErrorCode.ERR_AssgLvalueExpected, "Property").WithLocation(1, 34)); + } + [Fact] public void ExtensionMemberLookup_With() { var src = """ -/**/ _ = new S() with { Property = 42 }; -/**/ struct S { } diff --git a/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests2.cs b/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests2.cs index 764c7a9e8c10e..974b99f57f8fe 100644 --- a/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests2.cs +++ b/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests2.cs @@ -215,6 +215,78 @@ static class E comp.VerifyEmitDiagnostics(); } + [Fact] + public void PositionalPattern_03() + { + // implicit span conversion + var src = """ +int[] i = new int[] { 1, 2 }; +if (i is var (x, y)) + System.Console.Write((x, y)); + +static class E +{ + extension(System.Span s) + { + public void Deconstruct(out int i, out int j) { i = 42; j = 43; } + } +} +"""; + var comp = CreateCompilation(src, targetFramework: TargetFramework.Net90); + CompileAndVerify(comp, expectedOutput: ExpectedOutput("(42, 43)"), verify: Verification.Skipped).VerifyDiagnostics(); + } + + [Fact] + public void PositionalPattern_04() + { + // implicit tuple conversion + var src = """ +var t = (42, "ran"); +if (t is var (x, y)) + System.Console.Write((x, y)); + +static class E +{ + extension((object, object) t) + { + public void Deconstruct(out int i, out int j) { i = 42; j = 43; } + } +} +"""; + var comp = CreateCompilation(src); + CompileAndVerify(comp, expectedOutput: ExpectedOutput("(42, ran)")).VerifyDiagnostics(); + } + + [Fact] + public void PositionalPattern_05() + { + // We check conversion during initial binding + var src = """ +int[] i = []; +_ = i is var (x, y); + +static class E +{ + extension(System.ReadOnlySpan r) + { + public void Deconstruct(out int i, out int j) => throw null; + } +} + +namespace System +{ + public ref struct ReadOnlySpan + { + } +} +"""; + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics( + // (2,14): error CS0656: Missing compiler required member 'ReadOnlySpan.op_Implicit' + // _ = i is var (x, y); + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "(x, y)").WithArguments("System.ReadOnlySpan", "op_Implicit").WithLocation(2, 14)); + } + [Fact] public void InvocationOnNull() {