diff --git a/src/Compilers/CSharp/Portable/Lowering/Instrumentation/DebugInfoInjector.cs b/src/Compilers/CSharp/Portable/Lowering/Instrumentation/DebugInfoInjector.cs index 29dbfd4a3326d..5f12229415f08 100644 --- a/src/Compilers/CSharp/Portable/Lowering/Instrumentation/DebugInfoInjector.cs +++ b/src/Compilers/CSharp/Portable/Lowering/Instrumentation/DebugInfoInjector.cs @@ -58,19 +58,59 @@ public override BoundStatement InstrumentExpressionStatement(BoundExpressionStat if (original.IsConstructorInitializer()) { - switch (original.Syntax.Kind()) + switch (original.Syntax) { - case SyntaxKind.ConstructorDeclaration: - // This is an implicit constructor initializer. - var decl = (ConstructorDeclarationSyntax)original.Syntax; - return new BoundSequencePointWithSpan(decl, rewritten, CreateSpanForConstructorInitializer(decl)); - case SyntaxKind.BaseConstructorInitializer: - case SyntaxKind.ThisConstructorInitializer: - var init = (ConstructorInitializerSyntax)original.Syntax; - Debug.Assert(init.Parent is object); - return new BoundSequencePointWithSpan(init, rewritten, CreateSpanForConstructorInitializer((ConstructorDeclarationSyntax)init.Parent)); + case ConstructorDeclarationSyntax ctorDecl: + // Implicit constructor initializer: + Debug.Assert(ctorDecl.Initializer == null); + + TextSpan span; + if (ctorDecl.Modifiers.Any(SyntaxKind.StaticKeyword)) + { + Debug.Assert(ctorDecl.Body != null); + + // [SomeAttribute] static MyCtorName(...) [|{|] ... } + var start = ctorDecl.Body.OpenBraceToken.SpanStart; + var end = ctorDecl.Body.OpenBraceToken.Span.End; + span = TextSpan.FromBounds(start, end); + } + else + { + // [SomeAttribute] [|public MyCtorName(params int[] values)|] { ... } + span = CreateSpan(ctorDecl.Modifiers, ctorDecl.Identifier, ctorDecl.ParameterList.CloseParenToken); + } + + return new BoundSequencePointWithSpan(ctorDecl, rewritten, span); + + case ConstructorInitializerSyntax ctorInit: + // Explicit constructor initializer: + + // [SomeAttribute] public MyCtorName(params int[] values): [|base()|] { ... } + return new BoundSequencePointWithSpan(ctorInit, rewritten, + TextSpan.FromBounds(ctorInit.ThisOrBaseKeyword.SpanStart, ctorInit.ArgumentList.CloseParenToken.Span.End)); + + case TypeDeclarationSyntax typeDecl: + // Primary constructor with implicit base initializer: + // class [|C(int X, int Y)|] : B; + Debug.Assert(typeDecl.ParameterList != null); + Debug.Assert(typeDecl.BaseList?.Types.Any(t => t is PrimaryConstructorBaseTypeSyntax { ArgumentList: not null }) != true); + + return new BoundSequencePointWithSpan(typeDecl, rewritten, TextSpan.FromBounds(typeDecl.Identifier.SpanStart, typeDecl.ParameterList.Span.End)); + + case PrimaryConstructorBaseTypeSyntax baseInit: + // Explicit base initializer: + // class C(int X, int Y) : [|B(...)|]; + return new BoundSequencePointWithSpan(baseInit, rewritten, baseInit.Span); + + default: + throw ExceptionUtilities.UnexpectedValue(original.Syntax.Kind()); } } + else if (original.Syntax is ParameterSyntax parameterSyntax) + { + // Record property setter sequence point. + return new BoundSequencePointWithSpan(parameterSyntax, rewritten, CreateSpan(parameterSyntax)); + } return AddSequencePoint(rewritten); } @@ -94,7 +134,8 @@ private static BoundStatement InstrumentFieldOrPropertyInitializer(BoundStatemen if (syntax.IsKind(SyntaxKind.Parameter)) { // This is an initialization of a generated property based on record parameter. - return AddSequencePoint(rewritten); + // Do not add sequence point - the initialization is trivial and there is no value stepping over every backing field assignment. + return rewritten; } Debug.Assert(syntax is { Parent: { Parent: { } } }); @@ -163,14 +204,18 @@ public override void InstrumentBlock(BoundBlock original, LocalRewriter rewriter } else if (original == rewriter.CurrentMethodBody) { - if (previousPrologue != null) + // Add hidden sequence point at the start of + // 1) a prologue added by instrumentation. + // 2) a synthesized main entry point for top-level code. + // 3) synthesized body of primary and copy constructors of a record type that has at least one parameter. + // This hidden sequence point covers assignments of parameters/original fields to the current instance backing fields. + // These assignments do not have their own sequence points. + if (previousPrologue != null || + rewriter.Factory.TopLevelMethod is SynthesizedSimpleProgramEntryPointSymbol || + original.Syntax is RecordDeclarationSyntax { ParameterList.Parameters.Count: > 0 }) { prologue = BoundSequencePoint.CreateHidden(previousPrologue); } - else if (rewriter.Factory.TopLevelMethod is SynthesizedSimpleProgramEntryPointSymbol) - { - prologue = BoundSequencePoint.CreateHidden(); - } if (previousEpilogue != null) { @@ -364,6 +409,14 @@ public override BoundStatement InstrumentReturnStatement(BoundReturnStatement or return new BoundSequencePointWithSpan(original.Syntax, rewritten, ((BlockSyntax)original.Syntax).CloseBraceToken.Span); } + if (original.Syntax is ParameterSyntax parameterSyntax) + { + Debug.Assert(parameterSyntax is { Parent.Parent: RecordDeclarationSyntax }); + + // Record property getter sequence point + return new BoundSequencePointWithSpan(parameterSyntax, rewritten, CreateSpan(parameterSyntax)); + } + return new BoundSequencePoint(original.Syntax, rewritten); } diff --git a/src/Compilers/CSharp/Portable/Lowering/Instrumentation/DebugInfoInjector_SequencePoints.cs b/src/Compilers/CSharp/Portable/Lowering/Instrumentation/DebugInfoInjector_SequencePoints.cs index 575615d34a2b5..bed18cb6964c4 100644 --- a/src/Compilers/CSharp/Portable/Lowering/Instrumentation/DebugInfoInjector_SequencePoints.cs +++ b/src/Compilers/CSharp/Portable/Lowering/Instrumentation/DebugInfoInjector_SequencePoints.cs @@ -45,29 +45,10 @@ internal static BoundStatement AddSequencePoint(UsingStatementSyntax usingSyntax return new BoundSequencePointWithSpan(usingSyntax, rewrittenStatement, span); } - private static TextSpan CreateSpanForConstructorInitializer(ConstructorDeclarationSyntax constructorSyntax) - { - if (constructorSyntax.Initializer != null) - { - // [SomeAttribute] public MyCtorName(params int[] values): [|base()|] { ... } - var start = constructorSyntax.Initializer.ThisOrBaseKeyword.SpanStart; - var end = constructorSyntax.Initializer.ArgumentList.CloseParenToken.Span.End; - return TextSpan.FromBounds(start, end); - } - - if (constructorSyntax.Modifiers.Any(SyntaxKind.StaticKeyword)) - { - Debug.Assert(constructorSyntax.Body != null); - - // [SomeAttribute] static MyCtorName(...) [|{|] ... } - var start = constructorSyntax.Body.OpenBraceToken.SpanStart; - var end = constructorSyntax.Body.OpenBraceToken.Span.End; - return TextSpan.FromBounds(start, end); - } - - // [SomeAttribute] [|public MyCtorName(params int[] values)|] { ... } - return CreateSpan(constructorSyntax.Modifiers, constructorSyntax.Identifier, constructorSyntax.ParameterList.CloseParenToken); - } + private static TextSpan CreateSpan(ParameterSyntax parameter) + // exclude attributes and default value: + // [A] [|in T p|] = default + => CreateSpan(parameter.Modifiers, parameter.Type, parameter.Identifier); private static TextSpan CreateSpan(SyntaxTokenList startOpt, SyntaxNodeOrToken startFallbackOpt, SyntaxNodeOrToken endOpt) { diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordCopyCtor.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordCopyCtor.cs index 47baaace29767..fadf78c917471 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordCopyCtor.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordCopyCtor.cs @@ -5,7 +5,9 @@ using System.Collections.Immutable; using System.Diagnostics; using Microsoft.CodeAnalysis.CSharp.Emit; +using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.CSharp.Symbols { @@ -53,6 +55,22 @@ internal override void GenerateMethodBodyStatements(SyntheticBoundNodeFactory F, statements.Add(F.Assignment(F.Field(F.This(), field), F.Field(param, field))); } } + + // Add a sequence point at the end of the copy-constructor, so that a breakpoint placed on the record constructor + // can be hit whenever a new instance of the record is created (either via a primary constructor or via a copy constructor). + // + // If the record doesn't have base initializer the span overlaps the span of the sequence point generated for primary + // constructor implicit base initializer: + // + // record [|C(int P, int Q)|] // implicit base constructor initializer span + // record C(int P, int Q) : [|B(...)|] // explicit base constructor initializer span + // record [|C|](int P, int Q) // copy-constructor span + // + // The copy-constructor span does not include the parameter list since the parameters are not in scope. + var recordDeclaration = (RecordDeclarationSyntax)F.Syntax; + statements.Add(new BoundSequencePointWithSpan(recordDeclaration, statementOpt: null, + (recordDeclaration.TypeParameterList == null) ? recordDeclaration.Identifier.Span : + TextSpan.FromBounds(recordDeclaration.Identifier.Span.Start, recordDeclaration.TypeParameterList.Span.End))); } internal override void AddSynthesizedAttributes(PEModuleBuilder moduleBuilder, ref ArrayBuilder attributes) diff --git a/src/Compilers/CSharp/Test/Emit/PDB/PDBLambdaTests.cs b/src/Compilers/CSharp/Test/Emit/PDB/PDBLambdaTests.cs index 0c623da3363ab..3eac5be19f705 100644 --- a/src/Compilers/CSharp/Test/Emit/PDB/PDBLambdaTests.cs +++ b/src/Compilers/CSharp/Test/Emit/PDB/PDBLambdaTests.cs @@ -1912,7 +1912,7 @@ public static void M(int a) } [Fact] - public void Record_1() + public void LiftedPrimaryParameter_Record() { var source = WithWindowsLineBreaks(@" using System; @@ -1947,9 +1947,9 @@ record D(int X) @@ -1976,6 +1976,15 @@ record D(int X) + + + + + + + @@ -1989,7 +1998,7 @@ record D(int X) } [Fact] - public void Record_2() + public void PrimaryBaseInitializer() { var source = WithWindowsLineBreaks(@" using System; @@ -2028,8 +2037,8 @@ static int F(int x, out int p) - - + @@ -2055,6 +2064,15 @@ static int F(int x, out int p) + + + + + + + @@ -2089,6 +2107,12 @@ static int F(int x, out int p) + + + + diff --git a/src/Compilers/CSharp/Test/Emit/PDB/PDBTests.cs b/src/Compilers/CSharp/Test/Emit/PDB/PDBTests.cs index 0f7f25e7cf185..40709f0d06d4b 100644 --- a/src/Compilers/CSharp/Test/Emit/PDB/PDBTests.cs +++ b/src/Compilers/CSharp/Test/Emit/PDB/PDBTests.cs @@ -28,6 +28,12 @@ namespace Microsoft.CodeAnalysis.CSharp.UnitTests.PDB { public class PDBTests : CSharpPDBTestBase { + public const PdbValidationOptions SyntaxOffsetPdbValidationOptions = + PdbValidationOptions.ExcludeDocuments | + PdbValidationOptions.ExcludeSequencePoints | + PdbValidationOptions.ExcludeScopes | + PdbValidationOptions.ExcludeNamespaces; + private static readonly MetadataReference[] s_valueTupleRefs = new[] { SystemRuntimeFacadeRef, ValueTupleRef }; #region General @@ -8530,10 +8536,8 @@ public void SyntaxOffset_IsPattern() var source = @"class C { bool F(object o) => o is int i && o is 3 && o is bool; }"; var c = CreateCompilationWithMscorlib40AndSystemCore(source, options: TestOptions.DebugDll); - c.VerifyPdb("C.F", @" - - - + c.VerifyPdb("C.F", @" + @@ -8544,15 +8548,10 @@ public void SyntaxOffset_IsPattern() - - - - - - -"); + +", options: SyntaxOffsetPdbValidationOptions); } [WorkItem(37172, "https://github.com/dotnet/roslyn/issues/37172")] @@ -10759,47 +10758,26 @@ public void SyntaxOffset_OutVarInInitializers_SwitchExpression() var c = CreateCompilationWithMscorlib40AndSystemCore(source, options: TestOptions.DebugDll); c.VerifyPdb("C..ctor", @" - - - - - - - - - - - - - - - 2 - - - - - - - - - - - - - - -"); + + + + + + + + + + + + 2 + + + + + + + +", options: SyntaxOffsetPdbValidationOptions); } [WorkItem(43468, "https://github.com/dotnet/roslyn/issues/43468")] @@ -11188,10 +11166,8 @@ public void SyntaxOffset_TupleDeconstruction() var source = @"class C { int F() { (int a, (_, int c)) = (1, (2, 3)); return a + c; } }"; var c = CreateCompilationWithMscorlib40AndSystemCore(source, options: TestOptions.DebugDll, references: s_valueTupleRefs); - c.VerifyPdb("C.F", @" - - - + c.VerifyPdb("C.F", @" + @@ -11204,19 +11180,9 @@ public void SyntaxOffset_TupleDeconstruction() - - - - - - - - - - -"); +", options: SyntaxOffsetPdbValidationOptions); } [Fact] @@ -11271,10 +11237,8 @@ public void SyntaxOffset_TupleParenthesized() var source = @"class C { int F() { (int, (int, int)) x = (1, (2, 3)); return x.Item1 + x.Item2.Item1 + x.Item2.Item2; } }"; var c = CreateCompilationWithMscorlib40AndSystemCore(source, options: TestOptions.DebugDll, references: s_valueTupleRefs); - c.VerifyPdb("C.F", @" - - - + c.VerifyPdb("C.F", @" + @@ -11286,19 +11250,9 @@ public void SyntaxOffset_TupleParenthesized() - - - - - - - - - -" -); +", options: SyntaxOffsetPdbValidationOptions); } [Fact] @@ -11307,10 +11261,8 @@ public void SyntaxOffset_TupleVarDefined() var source = @"class C { int F() { var x = (1, 2); return x.Item1 + x.Item2; } }"; var c = CreateCompilationWithMscorlib40AndSystemCore(source, options: TestOptions.DebugDll, references: s_valueTupleRefs); - c.VerifyPdb("C.F", @" - - - + c.VerifyPdb("C.F", @" + @@ -11322,18 +11274,9 @@ public void SyntaxOffset_TupleVarDefined() - - - - - - - - - -"); +", options: SyntaxOffsetPdbValidationOptions); } [Fact] @@ -11342,10 +11285,8 @@ public void SyntaxOffset_TupleIgnoreDeconstructionIfVariableDeclared() var source = @"class C { int F() { (int x, int y) a = (1, 2); return a.Item1 + a.Item2; } }"; var c = CreateCompilationWithMscorlib40AndSystemCore(source, options: TestOptions.DebugDll, references: s_valueTupleRefs); - c.VerifyPdb("C.F", @" - - - + c.VerifyPdb("C.F", @" + @@ -11360,18 +11301,9 @@ public void SyntaxOffset_TupleIgnoreDeconstructionIfVariableDeclared() - - - - - - - - - -"); +", options: SyntaxOffsetPdbValidationOptions); } #endregion @@ -11424,9 +11356,6 @@ public void SyntaxOffset_OutVarInMethod() var c = CreateCompilationWithMscorlib40AndSystemCore(source, options: TestOptions.DebugDll); c.VerifyPdb("C.G", @" - - - @@ -11441,23 +11370,10 @@ public void SyntaxOffset_OutVarInMethod() - - - - - - - - - - - - - -"); +", options: SyntaxOffsetPdbValidationOptions); } [Fact] @@ -11489,9 +11405,6 @@ public A(int x) {} var c = CreateCompilationWithMscorlib40AndSystemCore(source, options: TestOptions.DebugDll); c.VerifyPdb("C..ctor", @" - - - @@ -11504,28 +11417,10 @@ public A(int x) {} - - - - - - - - - - - - - - - - - - -"); +", options: SyntaxOffsetPdbValidationOptions); } [Fact] @@ -11556,9 +11451,6 @@ public A(int x) {} var c = CreateCompilationWithMscorlib40AndSystemCore(source, options: TestOptions.DebugDll); c.VerifyPdb("C..ctor", @" - - - @@ -11570,23 +11462,10 @@ public A(int x) {} - - - - - - - - - - - - - -"); +", options: SyntaxOffsetPdbValidationOptions); } [Fact] @@ -11614,9 +11493,6 @@ public A(int x) {} var c = CreateCompilationWithMscorlib40AndSystemCore(source, options: TestOptions.DebugDll); c.VerifyPdb("C..ctor", @" - - - @@ -11628,20 +11504,10 @@ public A(int x) {} - - - - - - - - - - -"); +", options: SyntaxOffsetPdbValidationOptions); } [Fact] @@ -11673,9 +11539,6 @@ static int F(System.Func x) c.VerifyPdb("C..ctor", @" - - - @@ -11689,40 +11552,22 @@ static int F(System.Func x) - - - - - - - -"); +", options: SyntaxOffsetPdbValidationOptions); c.VerifyPdb("C+<>c__DisplayClass2_0.<.ctor>b__0", @" - - - - - - -"); +", options: SyntaxOffsetPdbValidationOptions); } [Fact] @@ -11750,9 +11595,6 @@ static int F(System.Func x) c.VerifyPdb("C..ctor", @" - - - @@ -11766,37 +11608,22 @@ static int F(System.Func x) - - - - - - - -"); +", options: SyntaxOffsetPdbValidationOptions); c.VerifyPdb("C+<>c__DisplayClass5_0.<.ctor>b__0", @" - - - - - - -"); +", options: SyntaxOffsetPdbValidationOptions); } [Fact] @@ -11862,9 +11689,6 @@ .locals init (C.<>c__DisplayClass4_0 V_0, //CS$<>8__locals0 c.VerifyPdb("C..ctor", @" - - - @@ -11881,60 +11705,34 @@ .locals init (C.<>c__DisplayClass4_0 V_0, //CS$<>8__locals0 - - - - - - - - - - -"); +", options: SyntaxOffsetPdbValidationOptions); c.VerifyPdb("C+<>c__DisplayClass4_0.<.ctor>b__0", @" - - - - - - -"); +", options: SyntaxOffsetPdbValidationOptions); c.VerifyPdb("C+<>c__DisplayClass4_1.<.ctor>b__1", @" - - - - - - -"); +", options: SyntaxOffsetPdbValidationOptions); } [Fact] @@ -11968,9 +11766,6 @@ public A(int x) {} var c = CreateCompilationWithMscorlib40AndSystemCore(source, options: TestOptions.DebugDll); c.VerifyPdb("C..ctor", @" - - - @@ -11986,37 +11781,22 @@ public A(int x) {} - - - - - -"); +", options: SyntaxOffsetPdbValidationOptions); c.VerifyPdb("C+<>c__DisplayClass0_0.<.ctor>b__0", @" - - - - - - -"); +", options: SyntaxOffsetPdbValidationOptions); } [Fact] @@ -12046,9 +11826,6 @@ static int G(out int x) var c = CreateCompilationWithMscorlib40AndSystemCore(source, options: TestOptions.DebugDll); c.VerifyPdb("C..ctor", @" - - - @@ -12063,28 +11840,13 @@ static int G(out int x) - - - - - - - - - - - - -"); +", options: SyntaxOffsetPdbValidationOptions); c.VerifyPdb("C+<>c.<.ctor>b__0_0", @" - - - @@ -12093,16 +11855,10 @@ static int G(out int x) - - - - - - -"); +", options: SyntaxOffsetPdbValidationOptions); } [Fact] @@ -12137,9 +11893,6 @@ static int F(System.Func x) var c = CreateCompilationWithMscorlib40AndSystemCore(source, options: TestOptions.DebugDll); c.VerifyPdb("C..ctor", @" - - - @@ -12156,28 +11909,13 @@ static int F(System.Func x) - - - - - - - - - - - - -"); +", options: SyntaxOffsetPdbValidationOptions); c.VerifyPdb("C+<>c.<.ctor>b__0_0", @" - - - @@ -12186,35 +11924,22 @@ static int F(System.Func x) - - - - - -"); +", options: SyntaxOffsetPdbValidationOptions); c.VerifyPdb("C+<>c__DisplayClass0_0.<.ctor>b__1", @" - - - - - - -"); +", options: SyntaxOffsetPdbValidationOptions); } [Fact] @@ -12224,46 +11949,256 @@ public void SyntaxOffset_OutVarInSwitchExpression() var c = CreateCompilationWithMscorlib40AndSystemCore(source, options: TestOptions.DebugDll); c.VerifyPdb("C.G", @" - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + +", options: SyntaxOffsetPdbValidationOptions); + } + + [Fact] + public void SyntaxOffset_OutVarInPrimaryConstructorInitializer() + { + var source = @" +class B(int x, int y) +{ +} + +class C(int x) : B(F(out var y), y) +{ + int Z = F(out var z); + static int F(out int a) => a = 1; +}"; + + var c = CreateCompilationWithMscorlib40AndSystemCore(source, options: TestOptions.DebugDll); + c.VerifyPdb(@" + + + + + + + + + + + + + + + + + + + + + + + + + +", options: SyntaxOffsetPdbValidationOptions); + } + + #endregion + + #region Primary Constructors + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/63299")] + public void SequencePoints_PrimaryConstructor_ExplicitBaseInitializer() + { + var source = @" +class B() : object() +{ +} + +class C(int x) : B() +{ + int y = 1; +}"; + + var c = CompileAndVerify(source); + c.VerifyMethodBody("B..ctor", @" +{ + // Code size 7 (0x7) + .maxstack 1 + // sequence point: object() + IL_0000: ldarg.0 + IL_0001: call ""object..ctor()"" + IL_0006: ret +} +"); + c.VerifyMethodBody("C..ctor", @" + { + // Code size 14 (0xe) + .maxstack 2 + // sequence point: int y = 1; + IL_0000: ldarg.0 + IL_0001: ldc.i4.1 + IL_0002: stfld ""int C.y"" + // sequence point: B() + IL_0007: ldarg.0 + IL_0008: call ""B..ctor()"" + IL_000d: ret +} +"); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/63299")] + public void SequencePoints_PrimaryConstructor_ImplicitBaseInitializer() + { + var source = @" +class B() +{ +} + +class C(int x) : B +{ +}"; + + var c = CompileAndVerify(source); + c.VerifyMethodBody("B..ctor", @" +{ + // Code size 7 (0x7) + .maxstack 1 + // sequence point: B() + IL_0000: ldarg.0 + IL_0001: call ""object..ctor()"" + IL_0006: ret +} +"); + c.VerifyMethodBody("C..ctor", @" +{ + // Code size 7 (0x7) + .maxstack 1 + // sequence point: C(int x) + IL_0000: ldarg.0 + IL_0001: call ""B..ctor()"" + IL_0006: ret +} +"); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/63299")] + public void SequencePoints_RecordConstructors_ModifiersAndDefault() + { + var source = @" +record C([A]in T P = default) where T : struct; + +class A : System.Attribute {} +" + IsExternalInitTypeDefinition; + + var c = CompileAndVerify(source, verify: Verification.Skipped); + + // primary constructor + c.VerifyMethodBody("C..ctor(in T)", @" +{ + // Code size 19 (0x13) + .maxstack 2 + // sequence point: + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: ldobj ""T"" + IL_0007: stfld ""T C.

k__BackingField"" + // sequence point: C([A]in T P = default) + IL_000c: ldarg.0 + IL_000d: call ""object..ctor()"" + IL_0012: ret +} +"); + // copy constructor + c.VerifyMethodBody("C..ctor(C)", @" +{ + // Code size 19 (0x13) + .maxstack 2 + // sequence point: + IL_0000: ldarg.0 + IL_0001: call ""object..ctor()"" + IL_0006: ldarg.0 + IL_0007: ldarg.1 + IL_0008: ldfld ""T C.

k__BackingField"" + IL_000d: stfld ""T C.

k__BackingField"" + // sequence point: C + IL_0012: ret +} +"); + // primary auto-property getter + c.VerifyMethodBody("C.P.get", @" +{ + // Code size 7 (0x7) + .maxstack 1 + // sequence point: in T P + IL_0000: ldarg.0 + IL_0001: ldfld ""T C.

k__BackingField"" + IL_0006: ret +} +"); + // primary auto-property setter + c.VerifyMethodBody("C.P.init", @" +{ + // Code size 8 (0x8) + .maxstack 2 + // sequence point: in T P + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: stfld ""T C.

k__BackingField"" + IL_0007: ret +} +"); + } + + [Theory] + [InlineData("int[] P = default", "int[] P")] + [InlineData("[A]int[] P", "int[] P")] + [InlineData("params int[] P", "params int[] P")] + [WorkItem("https://github.com/dotnet/roslyn/issues/63299")] + public void SequencePoints_RecordPropertyAccessors(string parameterSyntax, string expectedSpan) + { + var source = + "record C(" + parameterSyntax + ");" + + "class A : System.Attribute { }" + + IsExternalInitTypeDefinition; + + var c = CompileAndVerify(source, verify: Verification.Skipped); + + // primary auto-property getter + c.VerifyMethodBody("C.P.get", $@" +{{ + // Code size 7 (0x7) + .maxstack 1 + // sequence point: {expectedSpan} + IL_0000: ldarg.0 + IL_0001: ldfld ""int[] C.

k__BackingField"" + IL_0006: ret +}} +"); + // primary auto-property setter + c.VerifyMethodBody("C.P.init", $@" +{{ + // Code size 8 (0x8) + .maxstack 2 + // sequence point: {expectedSpan} + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: stfld ""int[] C.

k__BackingField"" + IL_0007: ret +}} "); } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/RecordTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/RecordTests.cs index b65892db19231..9b794c3a5d5bf 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/RecordTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/RecordTests.cs @@ -13220,24 +13220,27 @@ public static void Main() "; var comp = CreateCompilation(new[] { source, IsExternalInitTypeDefinition }, parseOptions: TestOptions.Regular9, options: TestOptions.DebugExe); var verifier = CompileAndVerify(comp, expectedOutput: "(1, 2, 42) (10, 20, 42)", verify: ExecutionConditionUtil.IsCoreClr ? Verification.Skipped : Verification.Fails).VerifyDiagnostics(); - verifier.VerifyIL("D..ctor(D)", @" - { - // Code size 33 (0x21) - .maxstack 2 - IL_0000: ldarg.0 - IL_0001: ldarg.1 - IL_0002: call ""C..ctor(C)"" - IL_0007: nop - IL_0008: ldarg.0 - IL_0009: ldarg.1 - IL_000a: ldfld ""int D.k__BackingField"" - IL_000f: stfld ""int D.k__BackingField"" - IL_0014: ldarg.0 - IL_0015: ldarg.1 - IL_0016: ldfld ""int D.field"" - IL_001b: stfld ""int D.field"" - IL_0020: ret - } + verifier.VerifyMethodBody("D..ctor(D)", @" +{ + // Code size 34 (0x22) + .maxstack 2 + // sequence point: + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: call ""C..ctor(C)"" + IL_0007: nop + IL_0008: ldarg.0 + IL_0009: ldarg.1 + IL_000a: ldfld ""int D.k__BackingField"" + IL_000f: stfld ""int D.k__BackingField"" + IL_0014: ldarg.0 + IL_0015: ldarg.1 + IL_0016: ldfld ""int D.field"" + IL_001b: stfld ""int D.field"" + // sequence point: D + IL_0020: nop + IL_0021: ret +} "); } diff --git a/src/Compilers/Core/Portable/CodeGen/ILBuilder.cs b/src/Compilers/Core/Portable/CodeGen/ILBuilder.cs index 02604889c347c..31c1053d5c5e9 100644 --- a/src/Compilers/Core/Portable/CodeGen/ILBuilder.cs +++ b/src/Compilers/Core/Portable/CodeGen/ILBuilder.cs @@ -56,10 +56,10 @@ internal sealed partial class ILBuilder /// don't know the actual IL offset, but only {block/offset-in-the-block} pair. /// /// Thus, whenever we need to mark some IL position we allocate a new marker id, store it - /// in allocatedILMarkers and reference this IL marker in the entity requiring the IL offset. + /// in and reference this IL marker in the entity requiring the IL offset. /// /// IL markers will be 'materialized' when the builder is realized; the resulting offsets - /// will be put into allocatedILMarkers array. Note that only markers from reachable blocks + /// will be put into array. Note that only markers from reachable blocks /// are materialized, the rest will have offset -1. /// private ArrayBuilder _allocatedILMarkers; diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/BreakpointSpansTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/BreakpointSpansTests.cs index 5cb1ea0b82e1f..cc8db14a4feca 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/BreakpointSpansTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/BreakpointSpansTests.cs @@ -54,7 +54,10 @@ private static void Test(string markup, bool isMissing, bool isLine, ParseOption else { Assert.True(hasBreakpoint); - Assert.Equal(expectedSpan.Value, breakpointSpan); + AssertEx.AreEqual( + expectedSpan.Value, + breakpointSpan, + message: $"Expected: [|{source.Substring(expectedSpan.Value.Start, expectedSpan.Value.Length)}|], Actual: [|{source.Substring(breakpointSpan.Start, breakpointSpan.Length)}|]"); } } @@ -1291,7 +1294,7 @@ public void GroupBy_NoLambda3() #endregion - #region Field and Veriable Declarators + #region Field and Variable Declarators [Fact] public void FieldDeclarator_WithoutInitializer_All() @@ -4409,6 +4412,160 @@ public void InstanceConstructor_NoInitializer_Attributes() }"); } + [Theory] + [CombinatorialData] + public void InstanceConstructor_Primary_ImplicitBaseInitializer_OutsideOfIdentifierAndNonEmptyParameters( + [CombinatorialValues("class", "struct", "record", "record struct")] string keyword, + [CombinatorialValues( + "$$[A]class [|C()|];", + "$$class [|C()|];", + "class$$ [|C()|];", + "class [|C($$)|];", + "$$class [|C(int a)|];", + "$$class [|C(int a, int b)|];", + "class [|C(int a, int b)|]$$;", + "class [|C(int a, int b)|]$$ { }", + "class [|C(int a, int b)|]$$ : B { }", + "class [|C(int a, int b)|] : B$$ { }", + "class [|C(int a, int b)|] : B, $$I { }", + "class [|C(int a, int b)|]$$ where T : notnull;", + "class [|C(int a, int b)|] where $$T : notnull;", + "class [|C(int a, int b)|] where T : notnull$$;", + "class [|C(int a, int b)|] where T : notnull$$ { }")] string source) + { + TestSpan(source.Replace("class", keyword)); + } + + [Theory] + [CombinatorialData] + public void InstanceConstructor_Primary_ImplicitBaseInitializer_OnIdentifierOrNonEmptyParameters_NonRecord( + [CombinatorialValues("class", "struct")] string keyword, + [CombinatorialValues( + "class [|$$C(int a, int b)|];", + "class [|C$$(int a, int b)|];", + "class [|C$$(int a, int b)|];", + "class [|C<$$T>(int a, int b)|];", + "class [|C<$$[A]T>(int a, int b)|];", + "class [|C$$(int a, int b)|];", + "class [|C($$int a, int b)|];", + "class [|C($$[A]int a, int b)|];", + "class [|C(int a, int b$$)|];")] string source) + { + TestSpan(source.Replace("class", keyword)); + } + + [Theory] + [CombinatorialData] + public void InstanceConstructor_Primary_ImplicitBaseInitializer_OnIdentifierOrNonEmptyParameters_Record( + [CombinatorialValues("record", "record struct")] string keyword, + [CombinatorialValues( + "record [|$$C|](int a, int b);", // copy-ctor + "record [|C$$|](int a, int b);", // copy-ctor + "record [|C$$|](int a, int b);", // copy-ctor + "record [|C<$$T>|](int a, int b);", // copy-ctor + "record [|C<$$[A]T>|](int a, int b);", // copy-ctor + "record [|C$$|](int a, int b);", // copy-ctor + "record C([|$$int a|], int b);", // property getter and setter + "record C($$[A][|int a|], int b);", // property getter and setter + "record C($$ [A][|int a|], int b);", // property getter and setter + "record C(int a, [|int b$$|]);", // property getter and setter + "record C(int a, $$ [|int b|]);", // property getter and setter + "record C(int a, [|params int[] b|] $$);", // property getter and setter + "record C(int a, [|int b|] = default$$);")] string source) // property getter and setter + { + TestSpan(source.Replace("record", keyword)); + } + + [Theory] + [CombinatorialData] + public void InstanceConstructor_Primary_ImplicitBaseInitializer_NoBreakpoint( + [CombinatorialValues("class", "struct", "record", "record struct")] string keyword, + [CombinatorialValues( + "$$[A]class C;", + "$$class C;", + "class C$$;", + "class C;$$", + "class C(int a, int b);$$", + "class C(int a, int b) : B;$$", + "class C(int a, int b) : B { }$$")] string source) + { + TestMissing(source.Replace("class", keyword)); + } + + [Theory] + [CombinatorialData] + public void InstanceConstructor_Primary_ExplicitBaseInitializer_OutsideOfIdentifierAndNonEmptyParameters( + [CombinatorialValues("class", "struct", "record", "record struct")] string keyword, + [CombinatorialValues( + "$$[A]class C() : [|B()|];", + "$$class C() : [|B()|];", + "$$class C(int a) : [|B()|];", + "$$class C(int a, int b) : [|B()|];", + "class C(int a, int b)$$ : [|B()|];", + "class C(int a, int b) :$$ [|B()|];", + "class C(int a, int b) : [|$$B()|];", + "class C(int a, int b) : [|B($$)|];", + "class C(int a, int b) : [|B()$$|];", + "class C(int a, int b) : [|B()|] $$;", + "class C(int a, int b) : [|B()|] $$ {}", + "class C(int a, int b) : [|B()|], $$I {}", + "class C(int a, int b) : [|B()|], I$$ {}", + "class C(int a, int b) : [|B()|] $$ where T : notnull;", + "class C(int a, int b) : [|B()|], $$I where T : notnull { }", + "class C(int a, int b) : [|B()|], I$$ where T : notnull { }", + "class C(int a, int b) : [|B()|] where $$T : notnull;", + "class C(int a, int b) : [|B()|] where T : notnull$$;", + "class C(int a, int b) : [|B()|] where T : notnull$$ { }")] string source) + { + TestSpan(source.Replace("class", keyword)); + } + + [Theory] + [CombinatorialData] + public void InstanceConstructor_Primary_ExplicitBaseInitializer_OnIdentifierOrNonEmptyParameters_NonRecord( + [CombinatorialValues("class", "struct")] string keyword, + [CombinatorialValues( + "class $$C(int a, int b) : [|B()|];", + "class C$$(int a, int b) : [|B()|];", + "class C<$$[A]T>(int a, int b) : [|B()|];", + "class C$$(int a, int b) : [|B()|];", + "class C($$int a, int b) : [|B()|];", + "class C(int a, $$int b) : [|B()|];", + "class C(int a, int b =$$ 1) : [|B()|];")] string source) + { + TestSpan(source.Replace("class", keyword)); + } + + [Theory] + [CombinatorialData] + public void InstanceConstructor_Primary_ExplicitBaseInitializer_OnIdentifierOrNonEmptyParameters_Record( + [CombinatorialValues("record", "record struct")] string keyword, + [CombinatorialValues( + "record [|$$C|](int a, int b) : B();", // copy-constructor + "record [|C$$|](int a, int b) : B();", // copy-constructor + "record [|C<$$[A]T>|](int a, int b) : B();", // copy-constructor + "record [|C$$|](int a, int b) : B();", // copy-constructor + "record C([|$$int a|], int b) : B();", // property getter and setter + "record C(int a, [|$$int b|]) : B();", // property getter and setter + "record C(int a, [|$$int b|] = 1) : B();")] string source) // property getter and setter + { + TestSpan(source.Replace("record", keyword)); + } + + [Theory] + [CombinatorialData] + public void InstanceConstructor_Primary_ExplicitBaseInitializer_NoBreakpoint( + [CombinatorialValues("class", "struct", "record", "record struct")] string keyword, + [CombinatorialValues( + "class C(int a, int b) : B() {$$ }", + "class C(int a, int b) : B();$$", + "class C(int a, int b) : B() { }$$", + "class C(int a, int b) : B {$$ }", + "class C(int a, int b) : B(), I { }$$")] string source) + { + TestMissing(source.Replace("class", keyword)); + } + [Fact] public void InstanceConstructor_BaseInitializer_BlockBody_All() { diff --git a/src/Features/CSharp/Portable/EditAndContinue/BreakpointSpans.cs b/src/Features/CSharp/Portable/EditAndContinue/BreakpointSpans.cs index 9dfcfb0276f06..1af4568ed92ba 100644 --- a/src/Features/CSharp/Portable/EditAndContinue/BreakpointSpans.cs +++ b/src/Features/CSharp/Portable/EditAndContinue/BreakpointSpans.cs @@ -153,6 +153,86 @@ private static int GetEndPosition(SyntaxNodeOrToken nodeOrToken) case SyntaxKind.ConstructorDeclaration: return CreateSpanForConstructorDeclaration((ConstructorDeclarationSyntax)node, position); + case SyntaxKind.RecordDeclaration: + case SyntaxKind.RecordStructDeclaration: + case SyntaxKind.StructDeclaration: + case SyntaxKind.ClassDeclaration: + var typeDeclaration = (TypeDeclarationSyntax)node; + if (typeDeclaration.ParameterList != null) + { + // after brace or semicolon + // class C(...) {$$ ... } + // class C(...) ;$$ + if (position > LastNotMissing(typeDeclaration.SemicolonToken, typeDeclaration.OpenBraceToken).SpanStart) + { + return null; + } + + // on or after explicit base initializer: + // C(...) :$$ [|B(...)|], I + // C(...) : [|B(...)|], I where ... $$ + var baseInitializer = typeDeclaration.BaseList?.Types.FirstOrDefault(t => t.IsKind(SyntaxKind.PrimaryConstructorBaseType)); + if (baseInitializer != null && position > typeDeclaration.BaseList!.ColonToken.SpanStart) + { + return baseInitializer.Span; + } + + // record properties and copy constructor + if (position >= typeDeclaration.Identifier.SpanStart && node is RecordDeclarationSyntax) + { + // on identifier: + // record $$C(...) : B(...); + // record C$$(...) : B(...); + if (position <= typeDeclaration.ParameterList.SpanStart) + { + // copy-constructor: [|C|] + return CreateSpan( + typeDeclaration.Identifier, + LastNotMissing(typeDeclaration.Identifier, typeDeclaration.TypeParameterList?.GreaterThanToken ?? default)); + } + + // on parameter: + // record C(..., $$ int p, ...) : B(...); + if (position < typeDeclaration.ParameterList.CloseParenToken.Span.End) + { + var parameter = GetParameter(position, typeDeclaration.ParameterList.Parameters); + if (parameter != null) + { + // [A][|int p|] = default + return CreateSpan(parameter.Modifiers, parameter.Type, parameter.Identifier); + } + + static ParameterSyntax? GetParameter(int position, SeparatedSyntaxList parameters) + { + if (parameters.Count == 0) + { + return null; + } + + for (var i = 0; i < parameters.SeparatorCount; i++) + { + var separator = parameters.GetSeparator(i); + if (position <= separator.SpanStart) + { + return parameters[i]; + } + } + + return parameters.Last(); + } + } + } + + // explicit base initializer + // C(...) : [|B(...)|] + // implicit base initializer + // [|C(...)|] + return (baseInitializer != null) ? baseInitializer.Span : + TextSpan.FromBounds(typeDeclaration.Identifier.SpanStart, typeDeclaration.ParameterList.Span.End); + } + + return null; + case SyntaxKind.VariableDeclarator: // handled by the parent node return null; @@ -586,7 +666,7 @@ private static TextSpan CreateSpanForBlock(BlockSyntax block, int position) } private static SyntaxToken LastNotMissing(SyntaxToken token1, SyntaxToken token2) - => token2.IsMissing ? token1 : token2; + => token2.IsKind(SyntaxKind.None) || token2.IsMissing ? token1 : token2; private static TextSpan? TryCreateSpanForVariableDeclaration(VariableDeclarationSyntax declaration, int position) => declaration.Parent!.Kind() switch