Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Auto-default ref fields with a null ref-assignment #63065

Merged
merged 5 commits into from
Aug 9, 2022
Merged
Show file tree
Hide file tree
Changes from 2 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
24 changes: 19 additions & 5 deletions src/Compilers/CSharp/Portable/FlowAnalysis/FlowAnalysisPass.cs
Original file line number Diff line number Diff line change
Expand Up @@ -113,11 +113,25 @@ private static BoundBlock PrependImplicitInitializations(BoundBlock body, Method
var builder = ArrayBuilder<BoundStatement>.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()));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>()`.
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());
Expand Down
235 changes: 214 additions & 21 deletions src/Compilers/CSharp/Test/Semantic/Semantics/RefFieldTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,220 @@ ref struct R
Diagnostic(ErrorCode.ERR_BadMemberFlag, "_v2").WithArguments("volatile").WithLocation(9, 31));
}

[Fact, WorkItem(62931, "https://github.com/dotnet/roslyn/issues/62931")]
public void ScopedReserved_Alias_Escaped()
{
var source = """
using @scoped = System.Int32;
""";
var comp = CreateCompilation(source, parseOptions: TestOptions.Regular10);
comp.VerifyDiagnostics(
// (1,1): hidden CS8019: Unnecessary using directive.
// using @scoped = System.Int32;
Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "using @scoped = System.Int32;").WithLocation(1, 1)
);

comp = CreateCompilation(source, parseOptions: TestOptions.Regular11);
comp.VerifyDiagnostics(
// (1,1): hidden CS8019: Unnecessary using directive.
// using @scoped = System.Int32;
Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "using @scoped = System.Int32;").WithLocation(1, 1)
);
}

[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);
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
}");
}

[Fact, 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<int>();
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<int>()""
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);
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);
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);
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
}");
}

/// <summary>
/// Unexpected modreq().
/// </summary>
Expand Down Expand Up @@ -12571,26 +12785,5 @@ public void ScopedReserved_Alias()
Diagnostic(ErrorCode.ERR_ScopedTypeNameDisallowed, "scoped").WithLocation(1, 7)
);
}

[Fact, WorkItem(62931, "https://github.com/dotnet/roslyn/issues/62931")]
public void ScopedReserved_Alias_Escaped()
{
var source = """
using @scoped = System.Int32;
""";
var comp = CreateCompilation(source, parseOptions: TestOptions.Regular10);
comp.VerifyDiagnostics(
// (1,1): hidden CS8019: Unnecessary using directive.
// using @scoped = System.Int32;
Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "using @scoped = System.Int32;").WithLocation(1, 1)
);

comp = CreateCompilation(source, parseOptions: TestOptions.Regular11);
comp.VerifyDiagnostics(
// (1,1): hidden CS8019: Unnecessary using directive.
// using @scoped = System.Int32;
Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "using @scoped = System.Int32;").WithLocation(1, 1)
);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
Expand Down