Skip to content

Commit

Permalink
Generate implicit accessor bodies
Browse files Browse the repository at this point in the history
  • Loading branch information
cston committed Aug 4, 2024
1 parent 14072de commit 46ab842
Show file tree
Hide file tree
Showing 7 changed files with 255 additions and 70 deletions.
3 changes: 1 addition & 2 deletions src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1879,8 +1879,7 @@ syntaxNode is ConstructorDeclarationSyntax constructorSyntax &&
}
else
{
var property = sourceMethod.AssociatedSymbol as SourcePropertySymbolBase;
if (property is not null && property.IsAutoPropertyWithGetAccessor)
if (sourceMethod is SourcePropertyAccessorSymbol { GenerateAccessorBody: true })
{
return MethodBodySynthesizer.ConstructAutoPropertyAccessorBody(sourceMethod);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,15 @@ internal class SourcePropertyAccessorSymbol : SourceMemberMethodSymbol
private ImmutableArray<CustomModifier> _lazyRefCustomModifiers;
private ImmutableArray<MethodSymbol> _lazyExplicitInterfaceImplementations;
private string _lazyName;
private readonly bool _isAutoPropertyAccessor;
private readonly bool _generateAccessorBody;
private readonly bool _usesInit;

public static SourcePropertyAccessorSymbol CreateAccessorSymbol(
NamedTypeSymbol containingType,
SourcePropertySymbol property,
DeclarationModifiers propertyModifiers,
AccessorDeclarationSyntax syntax,
bool isAutoPropertyAccessor,
bool generateAccessorBody,
BindingDiagnosticBag diagnostics)
{
Debug.Assert(syntax.Kind() == SyntaxKind.GetAccessorDeclaration ||
Expand All @@ -57,7 +57,7 @@ public static SourcePropertyAccessorSymbol CreateAccessorSymbol(
syntax.Modifiers,
methodKind,
syntax.Keyword.IsKind(SyntaxKind.InitKeyword),
isAutoPropertyAccessor,
generateAccessorBody,
isNullableAnalysisEnabled: isNullableAnalysisEnabled,
diagnostics);
}
Expand Down Expand Up @@ -104,7 +104,7 @@ public static SourcePropertyAccessorSymbol CreateAccessorSymbol(
modifiers: default,
methodKind,
usesInit,
isAutoPropertyAccessor: true,
generateAccessorBody: true,
isNullableAnalysisEnabled: false,
diagnostics);
}
Expand Down Expand Up @@ -159,7 +159,7 @@ private SourcePropertyAccessorSymbol(
out var modifierErrors))
{
_property = property;
_isAutoPropertyAccessor = false;
_generateAccessorBody = false;

CheckFeatureAvailabilityAndRuntimeSupport(syntax, location, hasBody: true, diagnostics: diagnostics);
CheckModifiersForBody(location, diagnostics);
Expand All @@ -182,7 +182,7 @@ protected SourcePropertyAccessorSymbol(
SyntaxTokenList modifiers,
MethodKind methodKind,
bool usesInit,
bool isAutoPropertyAccessor,
bool generateAccessorBody,
bool isNullableAnalysisEnabled,
BindingDiagnosticBag diagnostics)
: base(containingType,
Expand All @@ -192,7 +192,7 @@ protected SourcePropertyAccessorSymbol(
MakeModifiersAndFlags(containingType, property, propertyModifiers, location, hasBlockBody, hasExpressionBody, modifiers, methodKind, isNullableAnalysisEnabled, diagnostics, out bool modifierErrors))
{
_property = property;
_isAutoPropertyAccessor = isAutoPropertyAccessor;
_generateAccessorBody = generateAccessorBody;
Debug.Assert(!_property.IsExpressionBodied, "Cannot have accessors in expression bodied lightweight properties");
var hasAnyBody = hasBlockBody || hasExpressionBody;
_usesInit = usesInit;
Expand All @@ -201,7 +201,7 @@ protected SourcePropertyAccessorSymbol(
Binder.CheckFeatureAvailability(syntax, MessageID.IDS_FeatureInitOnlySetters, diagnostics, location);
}

CheckFeatureAvailabilityAndRuntimeSupport(syntax, location, hasBody: hasAnyBody || isAutoPropertyAccessor, diagnostics);
CheckFeatureAvailabilityAndRuntimeSupport(syntax, location, hasBody: hasAnyBody || generateAccessorBody, diagnostics);

if (hasAnyBody)
{
Expand All @@ -212,7 +212,7 @@ protected SourcePropertyAccessorSymbol(

if (!modifierErrors)
{
this.CheckModifiers(location, hasAnyBody, isAutoPropertyAccessor, diagnostics);
this.CheckModifiers(location, hasAnyBody, generateAccessorBody, diagnostics);
}

if (modifiers.Count > 0)
Expand Down Expand Up @@ -480,11 +480,13 @@ internal sealed override bool IsDeclaredReadOnly

return ContainingType.IsStructType() &&
!_property.IsStatic &&
_isAutoPropertyAccessor &&
_generateAccessorBody &&
MethodKind == MethodKind.PropertyGet;
}
}

internal bool GenerateAccessorBody => _generateAccessorBody;

internal sealed override bool IsInitOnly => !IsStatic && _usesInit;

private static DeclarationModifiers MakeModifiers(NamedTypeSymbol containingType, SyntaxTokenList modifiers, bool isExplicitInterfaceImplementation,
Expand Down Expand Up @@ -557,7 +559,7 @@ private void CheckModifiers(Location location, bool hasBody, bool isAutoProperty
// 'init' accessors cannot be marked 'readonly'. Mark '{0}' readonly instead.
diagnostics.Add(ErrorCode.ERR_InitCannotBeReadonly, location, _property);
}
else if (LocalDeclaredReadOnly && _isAutoPropertyAccessor && MethodKind == MethodKind.PropertySet)
else if (LocalDeclaredReadOnly && _generateAccessorBody && MethodKind == MethodKind.PropertySet)
{
// Auto-implemented accessor '{0}' cannot be marked 'readonly'.
diagnostics.Add(ErrorCode.ERR_AutoSetterCantBeReadOnly, location, this);
Expand Down Expand Up @@ -803,7 +805,7 @@ internal override void AddSynthesizedAttributes(PEModuleBuilder moduleBuilder, r
{
base.AddSynthesizedAttributes(moduleBuilder, ref attributes);

if (_isAutoPropertyAccessor)
if (_generateAccessorBody)
{
var compilation = this.DeclaringCompilation;
AddSynthesizedAttribute(ref attributes, compilation.TrySynthesizeAttribute(WellKnownMember.System_Runtime_CompilerServices_CompilerGeneratedAttribute__ctor));
Expand Down
110 changes: 66 additions & 44 deletions src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbol.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,12 @@ private static SourcePropertySymbol Create(
GetAccessorDeclarations(
syntax,
diagnostics,
out bool hasAccessorList,
out bool accessorsHaveImplementation,
out bool usesFieldKeyword,
out bool isExpressionBodied,
out bool isInitOnly,
out var getSyntax,
out var setSyntax);
out var getAccessorInfo,
out var setAccessorInfo);

bool accessorsHaveImplementation = getAccessorInfo.HasImplementation || setAccessorInfo.HasImplementation; // PROTOTYPE: Remove if not needed.

var explicitInterfaceSpecifier = GetExplicitInterfaceSpecifier(syntax);
SyntaxTokenList modifiersTokenList = GetModifierTokensSyntax(syntax);
Expand All @@ -61,18 +61,27 @@ private static SourcePropertySymbol Create(
out _);

bool isAutoProperty = (modifiers & DeclarationModifiers.Partial) == 0 && !accessorsHaveImplementation;
bool isExpressionBodied = !hasAccessorList && GetArrowExpression(syntax) != null;

binder = binder.SetOrClearUnsafeRegionIfNecessary(modifiersTokenList);
TypeSymbol? explicitInterfaceType;
string? aliasQualifierOpt;
string memberName = ExplicitInterfaceHelpers.GetMemberNameAndInterfaceSymbol(binder, explicitInterfaceSpecifier, name, diagnostics, out explicitInterfaceType, out aliasQualifierOpt);
// PROTOTYPE: Rename corresponding parameters to match these names.
bool isAutoGetAccessor = false;
bool isAutoSetAccessor = false;
if ((modifiers & DeclarationModifiers.Partial) == 0)
{
isAutoGetAccessor = !getAccessorInfo.IsDefault && !getAccessorInfo.HasImplementation;
isAutoSetAccessor = !setAccessorInfo.IsDefault && !setAccessorInfo.HasImplementation;
}

return new SourcePropertySymbol(
containingType,
syntax,
hasGetAccessor: getSyntax != null || isExpressionBodied,
hasSetAccessor: setSyntax != null,
hasGetAccessor: !getAccessorInfo.IsDefault || isExpressionBodied,
hasSetAccessor: !setAccessorInfo.IsDefault,
generateGetAccessorBody: isAutoGetAccessor,
generateSetAccessorBody: isAutoSetAccessor,
isExplicitInterfaceImplementation,
explicitInterfaceType,
aliasQualifierOpt,
Expand All @@ -82,7 +91,7 @@ private static SourcePropertySymbol Create(
isExpressionBodied: isExpressionBodied,
isInitOnly: isInitOnly,
accessorsHaveImplementation: accessorsHaveImplementation,
usesFieldKeyword: usesFieldKeyword,
usesFieldKeyword: getAccessorInfo.UsesFieldKeyword || setAccessorInfo.UsesFieldKeyword,
memberName,
location,
diagnostics);
Expand All @@ -93,6 +102,8 @@ private SourcePropertySymbol(
BasePropertyDeclarationSyntax syntax,
bool hasGetAccessor,
bool hasSetAccessor,
bool generateGetAccessorBody,
bool generateSetAccessorBody,
bool isExplicitInterfaceImplementation,
TypeSymbol? explicitInterfaceType,
string? aliasQualifierOpt,
Expand All @@ -109,8 +120,10 @@ private SourcePropertySymbol(
: base(
containingType,
syntax,
hasGetAccessor,
hasSetAccessor,
hasGetAccessor: hasGetAccessor,
hasSetAccessor: hasSetAccessor,
generateGetAccessorBody: generateGetAccessorBody,
generateSetAccessorBody: generateSetAccessorBody,
isExplicitInterfaceImplementation,
explicitInterfaceType,
aliasQualifierOpt,
Expand Down Expand Up @@ -199,34 +212,46 @@ public override OneOrMany<SyntaxList<AttributeListSyntax>> GetAttributeDeclarati

public override IAttributeTargetSymbol AttributesOwner => this;

private readonly struct AccessorInfo
{
public AccessorInfo(CSharpSyntaxNode syntax, bool hasImplementation, bool usesFieldKeyword)
{
Syntax = syntax;
HasImplementation = hasImplementation;
UsesFieldKeyword = usesFieldKeyword;
}

public readonly CSharpSyntaxNode? Syntax;
public readonly bool HasImplementation;
public readonly bool UsesFieldKeyword;

public bool IsDefault => Syntax is null;
}

private static void GetAccessorDeclarations(
CSharpSyntaxNode syntaxNode,
BindingDiagnosticBag diagnostics,
out bool hasAccessorList,
out bool accessorsHaveImplementation,
out bool usesFieldKeyword,
out bool isExpressionBodied,
out bool isInitOnly,
out CSharpSyntaxNode? getSyntax,
out CSharpSyntaxNode? setSyntax)
out AccessorInfo getAccessor,
out AccessorInfo setAccessor)
{
var syntax = (BasePropertyDeclarationSyntax)syntaxNode;
hasAccessorList = syntax.AccessorList != null;
getSyntax = null;
setSyntax = null;
isExpressionBodied = syntax.AccessorList is null;
isInitOnly = false;
getAccessor = default;
setAccessor = default;

if (hasAccessorList)
if (!isExpressionBodied)
{
usesFieldKeyword = false;
accessorsHaveImplementation = false;
foreach (var accessor in syntax.AccessorList!.Accessors)
{
switch (accessor.Kind())
{
case SyntaxKind.GetAccessorDeclaration:
if (getSyntax == null)
if (getAccessor.IsDefault)
{
getSyntax = accessor;
getAccessor = getAccessorInfo(accessor);
}
else
{
Expand All @@ -235,9 +260,9 @@ private static void GetAccessorDeclarations(
break;
case SyntaxKind.SetAccessorDeclaration:
case SyntaxKind.InitAccessorDeclaration:
if (setSyntax == null)
if (setAccessor.IsDefault)
{
setSyntax = accessor;
setAccessor = getAccessorInfo(accessor);
if (accessor.Keyword.IsKind(SyntaxKind.InitKeyword))
{
isInitOnly = true;
Expand All @@ -259,22 +284,19 @@ private static void GetAccessorDeclarations(
default:
throw ExceptionUtilities.UnexpectedValue(accessor.Kind());
}

var body = (SyntaxNode?)accessor.Body ?? accessor.ExpressionBody;
if (body != null)
{
accessorsHaveImplementation = true;
}

usesFieldKeyword = usesFieldKeyword || containsFieldKeyword(accessor);
}
}
else
{
var body = GetArrowExpression(syntax);
accessorsHaveImplementation = body is object;
usesFieldKeyword = body is { } && containsFieldKeyword(body);
Debug.Assert(accessorsHaveImplementation); // it's not clear how this even parsed as a property if it has no accessor list and no arrow expression.
Debug.Assert(body is { }); // it's not clear how this even parsed as a property if it has no accessor list and no arrow expression.
getAccessor = new AccessorInfo(body, hasImplementation: true, usesFieldKeyword: body is { } && containsFieldKeyword(body));
}

static AccessorInfo getAccessorInfo(AccessorDeclarationSyntax accessor)
{
var body = (SyntaxNode?)accessor.Body ?? accessor.ExpressionBody;
return new AccessorInfo(accessor, hasImplementation: body is { }, usesFieldKeyword: containsFieldKeyword(accessor));
}

static bool containsFieldKeyword(SyntaxNode syntax)
Expand Down Expand Up @@ -324,7 +346,7 @@ private static (DeclarationModifiers modifiers, bool hasExplicitAccessMod) MakeM
SyntaxTokenList modifiers,
bool isExplicitInterfaceImplementation,
bool isIndexer,
bool accessorsHaveImplementation,
bool accessorsHaveImplementation, // PROTOTYPE: How is this used?
Location location,
BindingDiagnosticBag diagnostics,
out bool modifierErrors)
Expand Down Expand Up @@ -451,7 +473,7 @@ private static (DeclarationModifiers modifiers, bool hasExplicitAccessMod) MakeM
return (mods, hasExplicitAccessMod);
}

protected override SourcePropertyAccessorSymbol CreateGetAccessorSymbol(bool isAutoPropertyAccessor, BindingDiagnosticBag diagnostics)
protected override SourcePropertyAccessorSymbol CreateGetAccessorSymbol(bool generateAccessorBody, BindingDiagnosticBag diagnostics)
{
var syntax = (BasePropertyDeclarationSyntax)CSharpSyntaxNode;
ArrowExpressionClauseSyntax? arrowExpression = GetArrowExpression(syntax);
Expand All @@ -464,29 +486,29 @@ protected override SourcePropertyAccessorSymbol CreateGetAccessorSymbol(bool isA
}
else
{
return CreateAccessorSymbol(GetGetAccessorDeclaration(syntax), isAutoPropertyAccessor, diagnostics);
return CreateAccessorSymbol(GetGetAccessorDeclaration(syntax), generateAccessorBody, diagnostics);
}
}

protected override SourcePropertyAccessorSymbol CreateSetAccessorSymbol(bool isAutoPropertyAccessor, BindingDiagnosticBag diagnostics)
protected override SourcePropertyAccessorSymbol CreateSetAccessorSymbol(bool generateAccessorBody, BindingDiagnosticBag diagnostics)
{
var syntax = (BasePropertyDeclarationSyntax)CSharpSyntaxNode;
Debug.Assert(!(syntax.AccessorList is null && GetArrowExpression(syntax) != null));

return CreateAccessorSymbol(GetSetAccessorDeclaration(syntax), isAutoPropertyAccessor, diagnostics);
return CreateAccessorSymbol(GetSetAccessorDeclaration(syntax), generateAccessorBody, diagnostics);
}

private SourcePropertyAccessorSymbol CreateAccessorSymbol(
AccessorDeclarationSyntax syntax,
bool isAutoPropertyAccessor,
bool generateAccessorBody,
BindingDiagnosticBag diagnostics)
{
return SourcePropertyAccessorSymbol.CreateAccessorSymbol(
ContainingType,
this,
_modifiers,
syntax,
isAutoPropertyAccessor,
generateAccessorBody,
diagnostics);
}

Expand Down
Loading

0 comments on commit 46ab842

Please sign in to comment.