Skip to content

Commit ed3dba8

Browse files
authored
Merge pull request #15625 from michaelnebel/csharp/primaryconstructorinitializer
C# 12: Primary constructor inititalizers.
2 parents 029db21 + f246272 commit ed3dba8

File tree

15 files changed

+214
-74
lines changed

15 files changed

+214
-74
lines changed

csharp/extractor/Semmle.Extraction.CSharp/Entities/Constructor.cs

Lines changed: 101 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
using System;
2+
using System.Collections.Generic;
13
using System.Diagnostics.CodeAnalysis;
24
using System.IO;
35
using System.Linq;
@@ -10,8 +12,16 @@ namespace Semmle.Extraction.CSharp.Entities
1012
{
1113
internal class Constructor : Method
1214
{
15+
private readonly List<SyntaxNode> declaringReferenceSyntax;
16+
1317
private Constructor(Context cx, IMethodSymbol init)
14-
: base(cx, init) { }
18+
: base(cx, init)
19+
{
20+
declaringReferenceSyntax =
21+
Symbol.DeclaringSyntaxReferences
22+
.Select(r => r.GetSyntax())
23+
.ToList();
24+
}
1525

1626
public override void Populate(TextWriter trapFile)
1727
{
@@ -22,6 +32,12 @@ public override void Populate(TextWriter trapFile)
2232
trapFile.constructors(this, Symbol.ContainingType.Name, ContainingType, (Constructor)OriginalDefinition);
2333
trapFile.constructor_location(this, Location);
2434

35+
if (IsPrimary)
36+
{
37+
// Create a synthetic empty body for primary constructors.
38+
Statements.SyntheticEmptyBlock.Create(Context, this, 0, Location);
39+
}
40+
2541
if (Symbol.IsImplicitlyDeclared)
2642
{
2743
var lineCounts = new LineCounts() { Total = 2, Code = 1, Comment = 0 };
@@ -33,68 +49,79 @@ public override void Populate(TextWriter trapFile)
3349
protected override void ExtractInitializers(TextWriter trapFile)
3450
{
3551
// Do not extract initializers for constructed types.
36-
if (!IsSourceDeclaration)
52+
// Only extract initializers for constructors with a body and primary constructors.
53+
if (Block is null && ExpressionBody is null && !IsPrimary ||
54+
!IsSourceDeclaration)
55+
{
3756
return;
57+
}
3858

39-
var syntax = Syntax;
40-
var initializer = syntax?.Initializer;
41-
42-
if (initializer is null)
59+
if (OrdinaryConstructorSyntax?.Initializer is ConstructorInitializerSyntax initializer)
4360
{
44-
if (Symbol.MethodKind is MethodKind.Constructor)
61+
ITypeSymbol initializerType;
62+
var initializerInfo = Context.GetSymbolInfo(initializer);
63+
64+
switch (initializer.Kind())
4565
{
46-
var baseType = Symbol.ContainingType.BaseType;
47-
if (baseType is null)
48-
{
49-
if (Symbol.ContainingType.SpecialType != SpecialType.System_Object)
50-
{
51-
Context.ModelError(Symbol, "Unable to resolve base type in implicit constructor initializer");
52-
}
66+
case SyntaxKind.BaseConstructorInitializer:
67+
initializerType = Symbol.ContainingType.BaseType!;
68+
break;
69+
case SyntaxKind.ThisConstructorInitializer:
70+
initializerType = Symbol.ContainingType;
71+
break;
72+
default:
73+
Context.ModelError(initializer, "Unknown initializer");
5374
return;
54-
}
75+
}
5576

56-
var baseConstructor = baseType.InstanceConstructors.FirstOrDefault(c => c.Arity is 0);
77+
ExtractSourceInitializer(trapFile, initializerType, (IMethodSymbol?)initializerInfo.Symbol, initializer.ArgumentList, initializer.ThisOrBaseKeyword.GetLocation());
78+
}
79+
else if (PrimaryBase is PrimaryConstructorBaseTypeSyntax primaryInitializer)
80+
{
81+
var primaryInfo = Context.GetSymbolInfo(primaryInitializer);
82+
var primarySymbol = primaryInfo.Symbol;
5783

58-
if (baseConstructor is null)
84+
ExtractSourceInitializer(trapFile, primarySymbol?.ContainingType, (IMethodSymbol?)primarySymbol, primaryInitializer.ArgumentList, primaryInitializer.GetLocation());
85+
}
86+
else if (Symbol.MethodKind is MethodKind.Constructor)
87+
{
88+
var baseType = Symbol.ContainingType.BaseType;
89+
if (baseType is null)
90+
{
91+
if (Symbol.ContainingType.SpecialType != SpecialType.System_Object)
5992
{
60-
Context.ModelError(Symbol, "Unable to resolve implicit constructor initializer call");
61-
return;
93+
Context.ModelError(Symbol, "Unable to resolve base type in implicit constructor initializer");
6294
}
63-
64-
var baseConstructorTarget = Create(Context, baseConstructor);
65-
var info = new ExpressionInfo(Context,
66-
AnnotatedTypeSymbol.CreateNotAnnotated(baseType),
67-
Location,
68-
Kinds.ExprKind.CONSTRUCTOR_INIT,
69-
this,
70-
-1,
71-
isCompilerGenerated: true,
72-
null);
73-
74-
trapFile.expr_call(new Expression(info), baseConstructorTarget);
95+
return;
7596
}
76-
return;
77-
}
7897

79-
ITypeSymbol initializerType;
80-
var symbolInfo = Context.GetSymbolInfo(initializer);
98+
var baseConstructor = baseType.InstanceConstructors.FirstOrDefault(c => c.Arity is 0);
8199

82-
switch (initializer.Kind())
83-
{
84-
case SyntaxKind.BaseConstructorInitializer:
85-
initializerType = Symbol.ContainingType.BaseType!;
86-
break;
87-
case SyntaxKind.ThisConstructorInitializer:
88-
initializerType = Symbol.ContainingType;
89-
break;
90-
default:
91-
Context.ModelError(initializer, "Unknown initializer");
100+
if (baseConstructor is null)
101+
{
102+
Context.ModelError(Symbol, "Unable to resolve implicit constructor initializer call");
92103
return;
104+
}
105+
106+
var baseConstructorTarget = Create(Context, baseConstructor);
107+
var info = new ExpressionInfo(Context,
108+
AnnotatedTypeSymbol.CreateNotAnnotated(baseType),
109+
Location,
110+
Kinds.ExprKind.CONSTRUCTOR_INIT,
111+
this,
112+
-1,
113+
isCompilerGenerated: true,
114+
null);
115+
116+
trapFile.expr_call(new Expression(info), baseConstructorTarget);
93117
}
118+
}
94119

120+
private void ExtractSourceInitializer(TextWriter trapFile, ITypeSymbol? type, IMethodSymbol? symbol, ArgumentListSyntax arguments, Location location)
121+
{
95122
var initInfo = new ExpressionInfo(Context,
96-
AnnotatedTypeSymbol.CreateNotAnnotated(initializerType),
97-
Context.CreateLocation(initializer.ThisOrBaseKeyword.GetLocation()),
123+
AnnotatedTypeSymbol.CreateNotAnnotated(type),
124+
Context.CreateLocation(location),
98125
Kinds.ExprKind.CONSTRUCTOR_INIT,
99126
this,
100127
-1,
@@ -103,7 +130,7 @@ protected override void ExtractInitializers(TextWriter trapFile)
103130

104131
var init = new Expression(initInfo);
105132

106-
var target = Constructor.Create(Context, (IMethodSymbol?)symbolInfo.Symbol);
133+
var target = Constructor.Create(Context, symbol);
107134
if (target is null)
108135
{
109136
Context.ModelError(Symbol, "Unable to resolve call");
@@ -112,19 +139,27 @@ protected override void ExtractInitializers(TextWriter trapFile)
112139

113140
trapFile.expr_call(init, target);
114141

115-
init.PopulateArguments(trapFile, initializer.ArgumentList, 0);
142+
init.PopulateArguments(trapFile, arguments, 0);
116143
}
117144

118-
private ConstructorDeclarationSyntax? Syntax
119-
{
120-
get
121-
{
122-
return Symbol.DeclaringSyntaxReferences
123-
.Select(r => r.GetSyntax())
124-
.OfType<ConstructorDeclarationSyntax>()
125-
.FirstOrDefault();
126-
}
127-
}
145+
private ConstructorDeclarationSyntax? OrdinaryConstructorSyntax =>
146+
declaringReferenceSyntax
147+
.OfType<ConstructorDeclarationSyntax>()
148+
.FirstOrDefault();
149+
150+
private TypeDeclarationSyntax? PrimaryConstructorSyntax =>
151+
declaringReferenceSyntax
152+
.OfType<TypeDeclarationSyntax>()
153+
.FirstOrDefault(t => t is ClassDeclarationSyntax or StructDeclarationSyntax or RecordDeclarationSyntax);
154+
155+
private PrimaryConstructorBaseTypeSyntax? PrimaryBase =>
156+
PrimaryConstructorSyntax?
157+
.BaseList?
158+
.Types
159+
.OfType<PrimaryConstructorBaseTypeSyntax>()
160+
.FirstOrDefault();
161+
162+
private bool IsPrimary => PrimaryConstructorSyntax is not null;
128163

129164
[return: NotNullIfNotNull(nameof(constructor))]
130165
public static new Constructor? Create(Context cx, IMethodSymbol? constructor)
@@ -160,19 +195,20 @@ public override void WriteId(EscapingTextWriter trapFile)
160195
trapFile.Write(";constructor");
161196
}
162197

163-
private ConstructorDeclarationSyntax? GetSyntax() =>
164-
Symbol.DeclaringSyntaxReferences.Select(r => r.GetSyntax()).OfType<ConstructorDeclarationSyntax>().FirstOrDefault();
165-
166198
public override Microsoft.CodeAnalysis.Location? FullLocation => ReportingLocation;
167199

168200
public override Microsoft.CodeAnalysis.Location? ReportingLocation
169201
{
170202
get
171203
{
172-
var syn = GetSyntax();
173-
if (syn is not null)
204+
if (OrdinaryConstructorSyntax is not null)
205+
{
206+
return OrdinaryConstructorSyntax.Identifier.GetLocation();
207+
}
208+
209+
if (PrimaryConstructorSyntax is not null)
174210
{
175-
return syn.Identifier.GetLocation();
211+
return PrimaryConstructorSyntax.Identifier.GetLocation();
176212
}
177213

178214
if (Symbol.IsImplicitlyDeclared)

csharp/extractor/Semmle.Extraction.CSharp/Entities/Method.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,13 @@ protected virtual void PopulateMethodBody(TextWriter trapFile)
5454
var block = Block;
5555
var expr = ExpressionBody;
5656

57+
Context.PopulateLater(() => ExtractInitializers(trapFile));
58+
5759
if (block is not null || expr is not null)
5860
{
5961
Context.PopulateLater(
6062
() =>
6163
{
62-
ExtractInitializers(trapFile);
6364
if (block is not null)
6465
Statements.Block.Create(Context, block, this, 0);
6566
else
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using System.IO;
2+
using Microsoft.CodeAnalysis.CSharp;
3+
using Microsoft.CodeAnalysis.CSharp.Syntax;
4+
using Semmle.Extraction.Entities;
5+
using Semmle.Extraction.Kinds;
6+
7+
namespace Semmle.Extraction.CSharp.Entities.Statements
8+
{
9+
internal class SyntheticEmptyBlock : Statement<BlockSyntax>
10+
{
11+
private SyntheticEmptyBlock(Context cx, BlockSyntax block, IStatementParentEntity parent, int child, Location location)
12+
: base(cx, block, StmtKind.BLOCK, parent, child, location) { }
13+
14+
public static SyntheticEmptyBlock Create(Context cx, IStatementParentEntity parent, int child, Location location)
15+
{
16+
var block = SyntaxFactory.Block();
17+
var ret = new SyntheticEmptyBlock(cx, block, parent, child, location);
18+
ret.TryPopulate();
19+
return ret;
20+
}
21+
22+
protected override void PopulateStatement(TextWriter trapFile) { }
23+
}
24+
}

csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/Type.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ public Kinds.TypeKind GetTypeKind(Context cx, bool constructUnderlyingTupleType)
5757
{
5858
return Kinds.TypeKind.TUPLE;
5959
}
60-
return Symbol.IsInlineArray()
60+
return Symbol.IsInlineArray()
6161
? Kinds.TypeKind.INLINE_ARRAY
6262
: Kinds.TypeKind.STRUCT;
6363
}

csharp/ql/lib/semmle/code/csharp/Callable.qll

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -416,7 +416,9 @@ class InstanceConstructor extends Constructor {
416416
*/
417417
class PrimaryConstructor extends Constructor {
418418
PrimaryConstructor() {
419-
not this.hasBody() and
419+
// In the extractor we use the constructor location as the location for the
420+
// synthesized empty body of the constructor.
421+
this.getLocation() = this.getBody().getLocation() and
420422
this.getDeclaringType().fromSource() and
421423
this.fromSource()
422424
}

csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowPrivate.qll

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -953,12 +953,8 @@ private module Cached {
953953
callCfn = any(Call c | isParamsArg(c, _, _)).getAControlFlowNode()
954954
} or
955955
TFlowInsensitiveFieldNode(FieldOrProperty f) { f.isFieldLike() } or
956-
TInstanceParameterAccessNode(ControlFlow::Node cfn, boolean isPostUpdate) {
957-
exists(ParameterAccess pa | cfn = getAPrimaryConstructorParameterCfn(pa) |
958-
isPostUpdate = false
959-
or
960-
pa instanceof ParameterWrite and isPostUpdate = true
961-
)
956+
TInstanceParameterAccessNode(ControlFlow::Node cfn, Boolean isPostUpdate) {
957+
cfn = getAPrimaryConstructorParameterCfn(_)
962958
} or
963959
TPrimaryConstructorThisAccessNode(Parameter p, Boolean isPostUpdate) {
964960
p.getCallable() instanceof PrimaryConstructor

csharp/ql/test/library-tests/constructors/PrintAst.expected

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ constructors.cs:
2525
# 23| -1: [TypeMention] object
2626
# 23| 1: [Parameter] s
2727
# 23| -1: [TypeMention] string
28+
# 23| 4: [BlockStmt] {...}
2829
# 25| 5: [InstanceConstructor] C1
2930
#-----| 2: (Parameters)
3031
# 25| 0: [Parameter] o
@@ -44,3 +45,7 @@ constructors.cs:
4445
# 28| -1: [TypeMention] string
4546
# 28| 2: [Parameter] i
4647
# 28| -1: [TypeMention] int
48+
# 28| 3: [ConstructorInitializer] call to constructor C1
49+
# 28| 0: [ParameterAccess] access to parameter o
50+
# 28| 1: [ParameterAccess] access to parameter s
51+
# 28| 4: [BlockStmt] {...}

csharp/ql/test/library-tests/csharp9/PrintAst.expected

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -884,6 +884,7 @@ Record.cs:
884884
# 27| -1: [TypeMention] string
885885
# 27| 1: [Parameter] LastName
886886
# 27| -1: [TypeMention] string
887+
# 27| 4: [BlockStmt] {...}
887888
# 27| 16: [Property] FirstName
888889
# 27| 3: [Getter] get_FirstName
889890
# 27| 4: [Setter] set_FirstName
@@ -913,6 +914,10 @@ Record.cs:
913914
# 29| -1: [TypeMention] string
914915
# 29| 2: [Parameter] Subject
915916
# 29| -1: [TypeMention] string
917+
# 30| 3: [ConstructorInitializer] call to constructor Person1
918+
# 30| 0: [ParameterAccess] access to parameter FirstName
919+
# 30| 1: [ParameterAccess] access to parameter LastName
920+
# 29| 4: [BlockStmt] {...}
916921
# 29| 17: [Property] Subject
917922
# 29| 3: [Getter] get_Subject
918923
# 29| 4: [Setter] set_Subject
@@ -937,6 +942,10 @@ Record.cs:
937942
# 32| -1: [TypeMention] string
938943
# 32| 2: [Parameter] Level
939944
# 32| -1: [TypeMention] int
945+
# 33| 3: [ConstructorInitializer] call to constructor Person1
946+
# 33| 0: [ParameterAccess] access to parameter FirstName
947+
# 33| 1: [ParameterAccess] access to parameter LastName
948+
# 32| 4: [BlockStmt] {...}
940949
# 32| 17: [Property] Level
941950
# 32| 3: [Getter] get_Level
942951
# 32| 4: [Setter] set_Level
@@ -957,6 +966,7 @@ Record.cs:
957966
#-----| 2: (Parameters)
958967
# 35| 0: [Parameter] Name
959968
# 35| -1: [TypeMention] string
969+
# 35| 4: [BlockStmt] {...}
960970
# 35| 16: [Property] Name
961971
# 35| 3: [Getter] get_Name
962972
# 35| 4: [Setter] set_Name
@@ -981,6 +991,9 @@ Record.cs:
981991
#-----| 2: (Parameters)
982992
# 41| 0: [Parameter] Name
983993
# 41| -1: [TypeMention] string
994+
# 41| 3: [ConstructorInitializer] call to constructor Pet
995+
# 41| 0: [ParameterAccess] access to parameter Name
996+
# 41| 4: [BlockStmt] {...}
984997
# 41| 15: [Property] EqualityContract
985998
# 41| 3: [Getter] get_EqualityContract
986999
# 43| 16: [Method] WagTail
@@ -1022,6 +1035,7 @@ Record.cs:
10221035
#-----| 2: (Parameters)
10231036
# 54| 0: [Parameter] A
10241037
# 54| -1: [TypeMention] string
1038+
# 54| 4: [BlockStmt] {...}
10251039
# 54| 16: [Property] A
10261040
# 54| 3: [Getter] get_A
10271041
# 54| 4: [Setter] set_A
@@ -1044,6 +1058,9 @@ Record.cs:
10441058
# 56| -1: [TypeMention] string
10451059
# 56| 1: [Parameter] B
10461060
# 56| -1: [TypeMention] string
1061+
# 56| 3: [ConstructorInitializer] call to constructor R1
1062+
# 56| 0: [ParameterAccess] access to parameter A
1063+
# 56| 4: [BlockStmt] {...}
10471064
# 56| 17: [Property] B
10481065
# 56| 3: [Getter] get_B
10491066
# 56| 4: [Setter] set_B

0 commit comments

Comments
 (0)