diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/FlowAnalysisPass.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/FlowAnalysisPass.cs index 653f0b3d2e50c..1f2f312f6bd72 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/FlowAnalysisPass.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/FlowAnalysisPass.cs @@ -113,11 +113,25 @@ private static BoundBlock PrependImplicitInitializations(BoundBlock body, Method var builder = ArrayBuilder.GetInstance(implicitlyInitializedFields.Length); foreach (var field in implicitlyInitializedFields) { - builder.Add( - F.ExpressionStatement( - F.AssignmentExpression( - F.Field(F.This(), field), - F.Default(field.Type)))); + if (field.RefKind == RefKind.None) + { + // field = default(T); + builder.Add( + F.ExpressionStatement( + F.AssignmentExpression( + F.Field(F.This(), field), + F.Default(field.Type)))); + } + else + { + // field = ref *default(T*); + builder.Add( + F.ExpressionStatement( + F.AssignmentExpression( + F.Field(F.This(), field), + F.NullRef(field.TypeWithAnnotations), + isRef: true))); + } } var initializations = F.HiddenSequencePoint(F.Block(builder.ToImmutableAndFree())); diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_FixedStatement.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_FixedStatement.cs index 35f386c718a42..ed20771faf0c0 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_FixedStatement.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_FixedStatement.cs @@ -43,12 +43,7 @@ public override BoundNode VisitFixedStatement(BoundFixedStatement node) Debug.Assert(!pinnedTemp.Type.IsManagedTypeNoUseSiteDiagnostics); // temp = ref *default(T*); - cleanup[i] = _factory.Assignment(_factory.Local(pinnedTemp), new BoundPointerIndirectionOperator( - _factory.Syntax, - _factory.Default(new PointerTypeSymbol(pinnedTemp.TypeWithAnnotations)), - refersToLocation: false, - pinnedTemp.Type), - isRef: true); + cleanup[i] = _factory.Assignment(_factory.Local(pinnedTemp), _factory.NullRef(pinnedTemp.TypeWithAnnotations), isRef: true); } } diff --git a/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs b/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs index 761d756887d6f..c5cb6b343c5b1 100644 --- a/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs +++ b/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs @@ -1137,6 +1137,13 @@ public BoundExpression Null(TypeSymbol type) return Null(type, Syntax); } + // Produce a ByRef null of given type, like `ref T Unsafe.NullRef()`. + public BoundExpression NullRef(TypeWithAnnotations type) + { + // *default(T*) + return new BoundPointerIndirectionOperator(Syntax, Default(new PointerTypeSymbol(type)), refersToLocation: false, type.Type); + } + public static BoundExpression Null(TypeSymbol type, SyntaxNode syntax) { Debug.Assert(type.CanBeAssignedNull()); diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/RefFieldTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/RefFieldTests.cs index 00c213b5d3aa5..7d7dfe8a31abd 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/RefFieldTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/RefFieldTests.cs @@ -568,6 +568,200 @@ ref struct R Diagnostic(ErrorCode.ERR_BadMemberFlag, "_v2").WithArguments("volatile").WithLocation(9, 31)); } + [Fact, WorkItem(63018, "https://github.com/dotnet/roslyn/issues/63018")] + public void InitRefField_AutoDefault() + { + var source = """ +using System; + +var x = 42; +scoped var r = new R(); +r.field = ref x; + +ref struct R +{ + public ref int field; + + public R() + { + Console.WriteLine("explicit ctor"); + } +} +"""; + var comp = CreateCompilation(source, runtimeFeature: RuntimeFlag.ByRefFields); + comp.VerifyDiagnostics(); + var verifier = CompileAndVerify(comp, verify: Verification.Skipped, expectedOutput: IncludeExpectedOutput("explicit ctor")); + verifier.VerifyIL("R..ctor()", @" +{ + // Code size 19 (0x13) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldc.i4.0 + IL_0002: conv.u + IL_0003: stfld ""ref int R.field"" + IL_0008: ldstr ""explicit ctor"" + IL_000d: call ""void System.Console.WriteLine(string)"" + IL_0012: ret +}"); + } + + // Test skipped because we don't allow faking runtime feature flags when using specific TargetFramework + [Fact(Skip = "https://github.com/dotnet/roslyn/issues/61463"), WorkItem(63018, "https://github.com/dotnet/roslyn/issues/63018")] + public void InitRefField_UnsafeNullRef() + { + var source = """ +using System; + +var x = 42; +scoped var r = new R(); +r.field = ref x; + +ref struct R +{ + public ref int field; + + public R() + { + field = ref System.Runtime.CompilerServices.Unsafe.NullRef(); + Console.WriteLine("explicit ctor"); + } +} +"""; + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net60); + comp.VerifyDiagnostics(); + var verifier = CompileAndVerify(comp, verify: Verification.Skipped, expectedOutput: IncludeExpectedOutput("explicit ctor")); + verifier.VerifyIL("R..ctor()", @" +{ + // Code size 22 (0x16) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: call ""ref int System.Runtime.CompilerServices.Unsafe.NullRef()"" + IL_0006: stfld ""ref int R.field"" + IL_000b: ldstr ""explicit ctor"" + IL_0010: call ""void System.Console.WriteLine(string)"" + IL_0015: ret +}"); + } + + [Fact, WorkItem(63018, "https://github.com/dotnet/roslyn/issues/63018")] + public void InitRefField_AutoDefault_RefReadonly() + { + var source = """ +using System; + +var x = 42; +scoped var r = new R(); +r.field = ref x; + +ref struct R +{ + public ref readonly int field; + + public R() + { + Console.WriteLine("explicit ctor"); + } +} +"""; + var comp = CreateCompilation(source, runtimeFeature: RuntimeFlag.ByRefFields); + comp.VerifyDiagnostics(); + + var verifier = CompileAndVerify(comp, verify: Verification.Skipped, expectedOutput: IncludeExpectedOutput("explicit ctor")); + verifier.VerifyIL("R..ctor()", @" +{ + // Code size 19 (0x13) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldc.i4.0 + IL_0002: conv.u + IL_0003: stfld ""ref readonly int R.field"" + IL_0008: ldstr ""explicit ctor"" + IL_000d: call ""void System.Console.WriteLine(string)"" + IL_0012: ret +}"); + } + + [Fact, WorkItem(63018, "https://github.com/dotnet/roslyn/issues/63018")] + public void InitRefField_AutoDefault_ReadonlyRef() + { + var source = """ +using System; + +var r = new R(); + +ref struct R +{ + public readonly ref int field; + + public R() + { + Console.WriteLine("explicit ctor"); + } +} +"""; + var comp = CreateCompilation(source, runtimeFeature: RuntimeFlag.ByRefFields); + comp.VerifyDiagnostics( + // (7,29): warning CS0649: Field 'R.field' is never assigned to, and will always have its default value 0 + // public readonly ref int field; + Diagnostic(ErrorCode.WRN_UnassignedInternalField, "field").WithArguments("R.field", "0").WithLocation(7, 29) + ); + + var verifier = CompileAndVerify(comp, verify: Verification.Skipped, expectedOutput: IncludeExpectedOutput("explicit ctor")); + verifier.VerifyIL("R..ctor()", @" +{ + // Code size 19 (0x13) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldc.i4.0 + IL_0002: conv.u + IL_0003: stfld ""ref int R.field"" + IL_0008: ldstr ""explicit ctor"" + IL_000d: call ""void System.Console.WriteLine(string)"" + IL_0012: ret +}"); + } + + [Fact, WorkItem(63018, "https://github.com/dotnet/roslyn/issues/63018")] + public void InitRefField_AutoDefault_WithOtherFieldInitializer() + { + var source = """ +using System; + +var x = 42; +scoped var r = new R(); +r.field = ref x; + +ref struct R +{ + public ref int field; + public int otherField = 42; + + public R() + { + Console.WriteLine("explicit ctor"); + } +} +"""; + var comp = CreateCompilation(source, parseOptions: TestOptions.Regular11, runtimeFeature: RuntimeFlag.ByRefFields); + comp.VerifyDiagnostics(); + var verifier = CompileAndVerify(comp, verify: Verification.Skipped, expectedOutput: IncludeExpectedOutput("explicit ctor")); + verifier.VerifyIL("R..ctor()", @" + { + // Code size 27 (0x1b) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldc.i4.0 + IL_0002: conv.u + IL_0003: stfld ""ref int R.field"" + IL_0008: ldarg.0 + IL_0009: ldc.i4.s 42 + IL_000b: stfld ""int R.otherField"" + IL_0010: ldstr ""explicit ctor"" + IL_0015: call ""void System.Console.WriteLine(string)"" + IL_001a: ret +}"); + } + /// /// Unexpected modreq(). /// diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/StructConstructorTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/StructConstructorTests.cs index 4bee8eeeb96b4..6fd294b8f8934 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/StructConstructorTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/StructConstructorTests.cs @@ -3929,6 +3929,46 @@ .maxstack 2 "); } + [Fact] + public void ImplicitlyInitializedField_Pointer() + { + var source = """ +using System; + +_ = new R(); + +unsafe struct R +{ + public int* field; + + public R() + { + Console.WriteLine("explicit ctor"); + } +} +"""; + var comp = CreateCompilation(source, options: TestOptions.UnsafeDebugExe); + comp.VerifyDiagnostics( + // (7,17): warning CS0649: Field 'R.field' is never assigned to, and will always have its default value + // public int* field; + Diagnostic(ErrorCode.WRN_UnassignedInternalField, "field").WithArguments("R.field", "").WithLocation(7, 17) + ); + var verifier = CompileAndVerify(comp, verify: Verification.Skipped, expectedOutput: "explicit ctor"); + verifier.VerifyIL("R..ctor()", @" +{ + // Code size 25 (0x19) + .maxstack 1 + IL_0000: nop + IL_0001: ldarg.0 + IL_0002: ldflda ""int* R.field"" + IL_0007: initobj ""int*"" + IL_000d: ldstr ""explicit ctor"" + IL_0012: call ""void System.Console.WriteLine(string)"" + IL_0017: nop + IL_0018: ret +}"); + } + [Fact] public void ImplicitlyInitializedField_NotOtherStruct() {