Merge pull request #15625 from michaelnebel/csharp/primaryconstructorinitializer

C# 12: Primary constructor inititalizers.
This commit is contained in:
Michael Nebel
2024-02-20 15:12:19 +01:00
committed by GitHub
15 changed files with 222 additions and 82 deletions

View File

@@ -1,3 +1,5 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
@@ -10,8 +12,16 @@ namespace Semmle.Extraction.CSharp.Entities
{
internal class Constructor : Method
{
private readonly List<SyntaxNode> declaringReferenceSyntax;
private Constructor(Context cx, IMethodSymbol init)
: base(cx, init) { }
: base(cx, init)
{
declaringReferenceSyntax =
Symbol.DeclaringSyntaxReferences
.Select(r => r.GetSyntax())
.ToList();
}
public override void Populate(TextWriter trapFile)
{
@@ -22,6 +32,12 @@ namespace Semmle.Extraction.CSharp.Entities
trapFile.constructors(this, Symbol.ContainingType.Name, ContainingType, (Constructor)OriginalDefinition);
trapFile.constructor_location(this, Location);
if (IsPrimary)
{
// Create a synthetic empty body for primary constructors.
Statements.SyntheticEmptyBlock.Create(Context, this, 0, Location);
}
if (Symbol.IsImplicitlyDeclared)
{
var lineCounts = new LineCounts() { Total = 2, Code = 1, Comment = 0 };
@@ -33,68 +49,79 @@ namespace Semmle.Extraction.CSharp.Entities
protected override void ExtractInitializers(TextWriter trapFile)
{
// Do not extract initializers for constructed types.
if (!IsSourceDeclaration)
return;
var syntax = Syntax;
var initializer = syntax?.Initializer;
if (initializer is null)
// Only extract initializers for constructors with a body and primary constructors.
if (Block is null && ExpressionBody is null && !IsPrimary ||
!IsSourceDeclaration)
{
if (Symbol.MethodKind is MethodKind.Constructor)
return;
}
if (OrdinaryConstructorSyntax?.Initializer is ConstructorInitializerSyntax initializer)
{
ITypeSymbol initializerType;
var initializerInfo = Context.GetSymbolInfo(initializer);
switch (initializer.Kind())
{
var baseType = Symbol.ContainingType.BaseType;
if (baseType is null)
{
if (Symbol.ContainingType.SpecialType != SpecialType.System_Object)
{
Context.ModelError(Symbol, "Unable to resolve base type in implicit constructor initializer");
}
case SyntaxKind.BaseConstructorInitializer:
initializerType = Symbol.ContainingType.BaseType!;
break;
case SyntaxKind.ThisConstructorInitializer:
initializerType = Symbol.ContainingType;
break;
default:
Context.ModelError(initializer, "Unknown initializer");
return;
}
var baseConstructor = baseType.InstanceConstructors.FirstOrDefault(c => c.Arity is 0);
if (baseConstructor is null)
{
Context.ModelError(Symbol, "Unable to resolve implicit constructor initializer call");
return;
}
var baseConstructorTarget = Create(Context, baseConstructor);
var info = new ExpressionInfo(Context,
AnnotatedTypeSymbol.CreateNotAnnotated(baseType),
Location,
Kinds.ExprKind.CONSTRUCTOR_INIT,
this,
-1,
isCompilerGenerated: true,
null);
trapFile.expr_call(new Expression(info), baseConstructorTarget);
}
return;
ExtractSourceInitializer(trapFile, initializerType, (IMethodSymbol?)initializerInfo.Symbol, initializer.ArgumentList, initializer.ThisOrBaseKeyword.GetLocation());
}
ITypeSymbol initializerType;
var symbolInfo = Context.GetSymbolInfo(initializer);
switch (initializer.Kind())
else if (PrimaryBase is PrimaryConstructorBaseTypeSyntax primaryInitializer)
{
case SyntaxKind.BaseConstructorInitializer:
initializerType = Symbol.ContainingType.BaseType!;
break;
case SyntaxKind.ThisConstructorInitializer:
initializerType = Symbol.ContainingType;
break;
default:
Context.ModelError(initializer, "Unknown initializer");
return;
}
var primaryInfo = Context.GetSymbolInfo(primaryInitializer);
var primarySymbol = primaryInfo.Symbol;
ExtractSourceInitializer(trapFile, primarySymbol?.ContainingType, (IMethodSymbol?)primarySymbol, primaryInitializer.ArgumentList, primaryInitializer.GetLocation());
}
else if (Symbol.MethodKind is MethodKind.Constructor)
{
var baseType = Symbol.ContainingType.BaseType;
if (baseType is null)
{
if (Symbol.ContainingType.SpecialType != SpecialType.System_Object)
{
Context.ModelError(Symbol, "Unable to resolve base type in implicit constructor initializer");
}
return;
}
var baseConstructor = baseType.InstanceConstructors.FirstOrDefault(c => c.Arity is 0);
if (baseConstructor is null)
{
Context.ModelError(Symbol, "Unable to resolve implicit constructor initializer call");
return;
}
var baseConstructorTarget = Create(Context, baseConstructor);
var info = new ExpressionInfo(Context,
AnnotatedTypeSymbol.CreateNotAnnotated(baseType),
Location,
Kinds.ExprKind.CONSTRUCTOR_INIT,
this,
-1,
isCompilerGenerated: true,
null);
trapFile.expr_call(new Expression(info), baseConstructorTarget);
}
}
private void ExtractSourceInitializer(TextWriter trapFile, ITypeSymbol? type, IMethodSymbol? symbol, ArgumentListSyntax arguments, Location location)
{
var initInfo = new ExpressionInfo(Context,
AnnotatedTypeSymbol.CreateNotAnnotated(initializerType),
Context.CreateLocation(initializer.ThisOrBaseKeyword.GetLocation()),
AnnotatedTypeSymbol.CreateNotAnnotated(type),
Context.CreateLocation(location),
Kinds.ExprKind.CONSTRUCTOR_INIT,
this,
-1,
@@ -103,7 +130,7 @@ namespace Semmle.Extraction.CSharp.Entities
var init = new Expression(initInfo);
var target = Constructor.Create(Context, (IMethodSymbol?)symbolInfo.Symbol);
var target = Constructor.Create(Context, symbol);
if (target is null)
{
Context.ModelError(Symbol, "Unable to resolve call");
@@ -112,19 +139,27 @@ namespace Semmle.Extraction.CSharp.Entities
trapFile.expr_call(init, target);
init.PopulateArguments(trapFile, initializer.ArgumentList, 0);
init.PopulateArguments(trapFile, arguments, 0);
}
private ConstructorDeclarationSyntax? Syntax
{
get
{
return Symbol.DeclaringSyntaxReferences
.Select(r => r.GetSyntax())
.OfType<ConstructorDeclarationSyntax>()
.FirstOrDefault();
}
}
private ConstructorDeclarationSyntax? OrdinaryConstructorSyntax =>
declaringReferenceSyntax
.OfType<ConstructorDeclarationSyntax>()
.FirstOrDefault();
private TypeDeclarationSyntax? PrimaryConstructorSyntax =>
declaringReferenceSyntax
.OfType<TypeDeclarationSyntax>()
.FirstOrDefault(t => t is ClassDeclarationSyntax or StructDeclarationSyntax or RecordDeclarationSyntax);
private PrimaryConstructorBaseTypeSyntax? PrimaryBase =>
PrimaryConstructorSyntax?
.BaseList?
.Types
.OfType<PrimaryConstructorBaseTypeSyntax>()
.FirstOrDefault();
private bool IsPrimary => PrimaryConstructorSyntax is not null;
[return: NotNullIfNotNull(nameof(constructor))]
public static new Constructor? Create(Context cx, IMethodSymbol? constructor)
@@ -160,19 +195,20 @@ namespace Semmle.Extraction.CSharp.Entities
trapFile.Write(";constructor");
}
private ConstructorDeclarationSyntax? GetSyntax() =>
Symbol.DeclaringSyntaxReferences.Select(r => r.GetSyntax()).OfType<ConstructorDeclarationSyntax>().FirstOrDefault();
public override Microsoft.CodeAnalysis.Location? FullLocation => ReportingLocation;
public override Microsoft.CodeAnalysis.Location? ReportingLocation
{
get
{
var syn = GetSyntax();
if (syn is not null)
if (OrdinaryConstructorSyntax is not null)
{
return syn.Identifier.GetLocation();
return OrdinaryConstructorSyntax.Identifier.GetLocation();
}
if (PrimaryConstructorSyntax is not null)
{
return PrimaryConstructorSyntax.Identifier.GetLocation();
}
if (Symbol.IsImplicitlyDeclared)

View File

@@ -54,12 +54,13 @@ namespace Semmle.Extraction.CSharp.Entities
var block = Block;
var expr = ExpressionBody;
Context.PopulateLater(() => ExtractInitializers(trapFile));
if (block is not null || expr is not null)
{
Context.PopulateLater(
() =>
{
ExtractInitializers(trapFile);
if (block is not null)
Statements.Block.Create(Context, block, this, 0);
else

View File

@@ -0,0 +1,24 @@
using System.IO;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Semmle.Extraction.Entities;
using Semmle.Extraction.Kinds;
namespace Semmle.Extraction.CSharp.Entities.Statements
{
internal class SyntheticEmptyBlock : Statement<BlockSyntax>
{
private SyntheticEmptyBlock(Context cx, BlockSyntax block, IStatementParentEntity parent, int child, Location location)
: base(cx, block, StmtKind.BLOCK, parent, child, location) { }
public static SyntheticEmptyBlock Create(Context cx, IStatementParentEntity parent, int child, Location location)
{
var block = SyntaxFactory.Block();
var ret = new SyntheticEmptyBlock(cx, block, parent, child, location);
ret.TryPopulate();
return ret;
}
protected override void PopulateStatement(TextWriter trapFile) { }
}
}

View File

@@ -57,7 +57,7 @@ namespace Semmle.Extraction.CSharp.Entities
{
return Kinds.TypeKind.TUPLE;
}
return Symbol.IsInlineArray()
return Symbol.IsInlineArray()
? Kinds.TypeKind.INLINE_ARRAY
: Kinds.TypeKind.STRUCT;
}