|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#nullable disable
using Microsoft.CodeAnalysis.CSharp.Emit;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Threading;
using Microsoft.CodeAnalysis.Text;
namespace Microsoft.CodeAnalysis.CSharp.Symbols
{
internal abstract class SourceMemberFieldSymbol : SourceFieldSymbolWithSyntaxReference
{
private readonly DeclarationModifiers _modifiers;
internal SourceMemberFieldSymbol(
SourceMemberContainerTypeSymbol containingType,
DeclarationModifiers modifiers,
string name,
SyntaxReference syntax,
TextSpan locationSpan)
: base(containingType, name, syntax, locationSpan)
{
_modifiers = modifiers;
}
protected sealed override DeclarationModifiers Modifiers
{
get
{
return _modifiers;
}
}
protected abstract TypeSyntax TypeSyntax { get; }
protected abstract SyntaxTokenList ModifiersTokenList { get; }
protected void TypeChecks(TypeSymbol type, BindingDiagnosticBag diagnostics)
{
if (type.HasFileLocalTypes() && !ContainingType.HasFileLocalTypes())
{
diagnostics.Add(ErrorCode.ERR_FileTypeDisallowedInSignature, this.ErrorLocation, type, ContainingType);
}
else if (type.IsStatic)
{
// Cannot declare a variable of static type '{0}'
diagnostics.Add(ErrorCode.ERR_VarDeclIsStaticClass, this.ErrorLocation, type);
}
else if (type.IsVoidType())
{
diagnostics.Add(ErrorCode.ERR_FieldCantHaveVoidType, TypeSyntax?.Location ?? this.GetFirstLocation());
}
else if (type.IsRestrictedType(ignoreSpanLikeTypes: true))
{
diagnostics.Add(ErrorCode.ERR_FieldCantBeRefAny, TypeSyntax?.Location ?? this.GetFirstLocation(), type);
}
else if (type.IsRefLikeOrAllowsRefLikeType() && (this.IsStatic || !containingType.IsRefLikeType))
{
diagnostics.Add(ErrorCode.ERR_FieldAutoPropCantBeByRefLike, TypeSyntax?.Location ?? this.GetFirstLocation(), type);
}
else if (IsConst && !type.CanBeConst())
{
SyntaxToken constToken = default(SyntaxToken);
foreach (var modifier in ModifiersTokenList)
{
if (modifier.Kind() == SyntaxKind.ConstKeyword)
{
constToken = modifier;
break;
}
}
Debug.Assert(constToken.Kind() == SyntaxKind.ConstKeyword);
diagnostics.Add(ErrorCode.ERR_BadConstType, constToken.GetLocation(), type);
}
else if (IsVolatile && !type.IsValidVolatileFieldType())
{
// '{0}': a volatile field cannot be of the type '{1}'
diagnostics.Add(ErrorCode.ERR_VolatileStruct, this.ErrorLocation, this, type);
}
CompoundUseSiteInfo<AssemblySymbol> useSiteInfo = new CompoundUseSiteInfo<AssemblySymbol>(diagnostics, ContainingAssembly);
if (!this.IsNoMoreVisibleThan(type, ref useSiteInfo))
{
// Inconsistent accessibility: field type '{1}' is less accessible than field '{0}'
diagnostics.Add(ErrorCode.ERR_BadVisFieldType, this.ErrorLocation, this, type);
}
diagnostics.Add(this.ErrorLocation, useSiteInfo);
}
public abstract bool HasInitializer { get; }
internal override void AddSynthesizedAttributes(PEModuleBuilder moduleBuilder, ref ArrayBuilder<SynthesizedAttributeData> attributes)
{
base.AddSynthesizedAttributes(moduleBuilder, ref attributes);
var compilation = this.DeclaringCompilation;
var value = this.GetConstantValue(ConstantFieldsInProgress.Empty, earlyDecodingWellKnownAttributes: false);
// Synthesize DecimalConstantAttribute when the default value is of type decimal
if (this.IsConst && value != null
&& this.Type.SpecialType == SpecialType.System_Decimal)
{
var data = GetDecodedWellKnownAttributeData();
if (data == null || data.ConstValue == CodeAnalysis.ConstantValue.Unset)
{
AddSynthesizedAttribute(ref attributes, compilation.SynthesizeDecimalConstantAttribute(value.DecimalValue));
}
}
// Synthesize RequiredMemberAttribute if this field is required
if (IsRequired)
{
AddSynthesizedAttribute(
ref attributes,
this.DeclaringCompilation.TrySynthesizeAttribute(WellKnownMember.System_Runtime_CompilerServices_RequiredMemberAttribute__ctor));
}
}
internal override void PostDecodeWellKnownAttributes(ImmutableArray<CSharpAttributeData> boundAttributes, ImmutableArray<AttributeSyntax> allAttributeSyntaxNodes, BindingDiagnosticBag diagnostics, AttributeLocation symbolPart, WellKnownAttributeData decodedData)
{
base.PostDecodeWellKnownAttributes(boundAttributes, allAttributeSyntaxNodes, diagnostics, symbolPart, decodedData);
// Ensure availability of `DecimalConstantAttribute`.
if (IsConst && Type.SpecialType == SpecialType.System_Decimal &&
GetConstantValue(ConstantFieldsInProgress.Empty, earlyDecodingWellKnownAttributes: false) is { } value &&
!(decodedData is FieldWellKnownAttributeData fieldData && fieldData.ConstValue != CodeAnalysis.ConstantValue.Unset))
{
Binder.ReportUseSiteDiagnosticForSynthesizedAttribute(DeclaringCompilation,
WellKnownMember.System_Runtime_CompilerServices_DecimalConstantAttribute__ctor,
diagnostics,
syntax: SyntaxNode);
}
}
public override Symbol AssociatedSymbol
{
get
{
return null;
}
}
public override int FixedSize
{
get
{
Debug.Assert(!this.IsFixedSizeBuffer, "Subclasses representing fixed fields must override");
state.NotePartComplete(CompletionPart.FixedSize);
return 0;
}
}
internal static DeclarationModifiers MakeModifiers(NamedTypeSymbol containingType, SyntaxToken firstIdentifier, SyntaxTokenList modifiers, bool isRefField, BindingDiagnosticBag diagnostics, out bool modifierErrors)
{
bool isInterface = containingType.IsInterface;
DeclarationModifiers defaultAccess =
isInterface ? DeclarationModifiers.Public : DeclarationModifiers.Private;
DeclarationModifiers allowedModifiers =
DeclarationModifiers.AccessibilityMask |
DeclarationModifiers.Const |
DeclarationModifiers.New |
DeclarationModifiers.ReadOnly |
DeclarationModifiers.Static |
DeclarationModifiers.Volatile |
DeclarationModifiers.Fixed |
DeclarationModifiers.Unsafe |
DeclarationModifiers.Abstract |
DeclarationModifiers.Required; // Some of these are filtered out later, when illegal, for better error messages.
var errorLocation = new SourceLocation(firstIdentifier);
DeclarationModifiers result = ModifierUtils.MakeAndCheckNonTypeMemberModifiers(
isOrdinaryMethod: false, isForInterfaceMember: isInterface,
modifiers, defaultAccess, allowedModifiers, errorLocation, diagnostics, out modifierErrors);
if ((result & DeclarationModifiers.Abstract) != 0)
{
diagnostics.Add(ErrorCode.ERR_AbstractField, errorLocation);
result &= ~DeclarationModifiers.Abstract;
}
if ((result & DeclarationModifiers.Fixed) != 0)
{
foreach (var modifier in modifiers)
{
if (modifier.IsKind(SyntaxKind.FixedKeyword))
MessageID.IDS_FeatureFixedBuffer.CheckFeatureAvailability(diagnostics, modifier);
}
reportBadMemberFlagIfAny(result, DeclarationModifiers.Static, diagnostics, errorLocation);
reportBadMemberFlagIfAny(result, DeclarationModifiers.ReadOnly, diagnostics, errorLocation);
reportBadMemberFlagIfAny(result, DeclarationModifiers.Const, diagnostics, errorLocation);
reportBadMemberFlagIfAny(result, DeclarationModifiers.Volatile, diagnostics, errorLocation);
reportBadMemberFlagIfAny(result, DeclarationModifiers.Required, diagnostics, errorLocation);
result &= ~(DeclarationModifiers.Static | DeclarationModifiers.ReadOnly | DeclarationModifiers.Const | DeclarationModifiers.Volatile | DeclarationModifiers.Required);
Debug.Assert((result & ~(DeclarationModifiers.AccessibilityMask | DeclarationModifiers.Fixed | DeclarationModifiers.Unsafe | DeclarationModifiers.New)) == 0);
}
if ((result & DeclarationModifiers.Const) != 0)
{
if ((result & DeclarationModifiers.Static) != 0)
{
// The constant '{0}' cannot be marked static
diagnostics.Add(ErrorCode.ERR_StaticConstant, errorLocation, firstIdentifier.ValueText);
}
reportBadMemberFlagIfAny(result, DeclarationModifiers.ReadOnly, diagnostics, errorLocation);
reportBadMemberFlagIfAny(result, DeclarationModifiers.Volatile, diagnostics, errorLocation);
reportBadMemberFlagIfAny(result, DeclarationModifiers.Unsafe, diagnostics, errorLocation);
if (reportBadMemberFlagIfAny(result, DeclarationModifiers.Required, diagnostics, errorLocation))
{
result &= ~DeclarationModifiers.Required;
}
result |= DeclarationModifiers.Static; // "constants are considered static members"
}
else
{
if ((result & DeclarationModifiers.Static) != 0 && (result & DeclarationModifiers.Required) != 0)
{
// The modifier 'required' is not valid for this item
diagnostics.Add(ErrorCode.ERR_BadMemberFlag, errorLocation, SyntaxFacts.GetText(SyntaxKind.RequiredKeyword));
result &= ~DeclarationModifiers.Required;
}
// NOTE: always cascading on a const, so suppress.
// NOTE: we're being a bit sneaky here - we're using the containingType rather than this symbol
// to determine whether or not unsafe is allowed. Since this symbol and the containing type are
// in the same compilation, it won't make a difference. We do, however, have to pass the error
// location explicitly.
containingType.CheckUnsafeModifier(result, errorLocation, diagnostics);
}
if (isRefField)
{
reportBadMemberFlagIfAny(result, DeclarationModifiers.Static, diagnostics, errorLocation);
reportBadMemberFlagIfAny(result, DeclarationModifiers.Const, diagnostics, errorLocation);
reportBadMemberFlagIfAny(result, DeclarationModifiers.Volatile, diagnostics, errorLocation);
}
return result;
static bool reportBadMemberFlagIfAny(DeclarationModifiers result, DeclarationModifiers modifier, BindingDiagnosticBag diagnostics, SourceLocation errorLocation)
{
if ((result & modifier) != 0)
{
// The modifier '{0}' is not valid for this item
diagnostics.Add(ErrorCode.ERR_BadMemberFlag, errorLocation, ModifierUtils.ConvertSingleModifierToSyntaxText(modifier));
return true;
}
return false;
}
}
#nullable enable
internal sealed override void ForceComplete(SourceLocation? locationOpt, Predicate<Symbol>? filter, CancellationToken cancellationToken)
{
if (filter?.Invoke(this) == false)
{
return;
}
while (true)
{
cancellationToken.ThrowIfCancellationRequested();
var incompletePart = state.NextIncompletePart;
switch (incompletePart)
{
case CompletionPart.Attributes:
GetAttributes();
break;
case CompletionPart.Type:
GetFieldType(ConsList<FieldSymbol>.Empty);
break;
case CompletionPart.FixedSize:
int discarded = this.FixedSize;
break;
case CompletionPart.ConstantValue:
GetConstantValue(ConstantFieldsInProgress.Empty, earlyDecodingWellKnownAttributes: false);
break;
case CompletionPart.None:
return;
default:
// any other values are completion parts intended for other kinds of symbols
state.NotePartComplete(CompletionPart.All & ~CompletionPart.FieldSymbolAll);
break;
}
state.SpinWaitComplete(incompletePart, cancellationToken);
}
}
#nullable disable
internal override NamedTypeSymbol FixedImplementationType(PEModuleBuilder emitModule)
{
Debug.Assert(!this.IsFixedSizeBuffer, "Subclasses representing fixed fields must override");
return null;
}
}
internal class SourceMemberFieldSymbolFromDeclarator : SourceMemberFieldSymbol
{
private readonly bool _hasInitializer;
private sealed class TypeAndRefKind
{
internal readonly RefKind RefKind;
internal readonly TypeWithAnnotations Type;
internal TypeAndRefKind(RefKind refKind, TypeWithAnnotations type)
{
RefKind = refKind;
Type = type;
}
}
private TypeAndRefKind _lazyTypeAndRefKind;
// Non-zero if the type of the field has been inferred from the type of its initializer expression
// and the errors of binding the initializer have been or are being reported to compilation diagnostics.
private int _lazyFieldTypeInferred;
internal SourceMemberFieldSymbolFromDeclarator(
SourceMemberContainerTypeSymbol containingType,
VariableDeclaratorSyntax declarator,
DeclarationModifiers modifiers,
bool modifierErrors,
BindingDiagnosticBag diagnostics)
: base(containingType, modifiers, declarator.Identifier.ValueText, declarator.GetReference(), declarator.Identifier.Span)
{
_hasInitializer = declarator.Initializer != null;
this.CheckAccessibility(diagnostics);
if (!modifierErrors)
{
this.ReportModifiersDiagnostics(diagnostics);
}
if (containingType.IsInterface)
{
if (this.IsStatic)
{
Binder.CheckFeatureAvailability(declarator, MessageID.IDS_DefaultInterfaceImplementation, diagnostics, ErrorLocation);
if (!ContainingAssembly.RuntimeSupportsDefaultInterfaceImplementation)
{
diagnostics.Add(ErrorCode.ERR_RuntimeDoesNotSupportDefaultInterfaceImplementation, ErrorLocation);
}
}
else
{
diagnostics.Add(ErrorCode.ERR_InterfacesCantContainFields, ErrorLocation);
}
}
}
protected sealed override TypeSyntax TypeSyntax
{
get
{
return GetFieldDeclaration(VariableDeclaratorNode).Declaration.Type;
}
}
protected sealed override SyntaxTokenList ModifiersTokenList
{
get
{
return GetFieldDeclaration(VariableDeclaratorNode).Modifiers;
}
}
public sealed override bool HasInitializer
{
get { return _hasInitializer; }
}
protected VariableDeclaratorSyntax VariableDeclaratorNode
{
get
{
return (VariableDeclaratorSyntax)this.SyntaxNode;
}
}
private static BaseFieldDeclarationSyntax GetFieldDeclaration(CSharpSyntaxNode declarator)
{
return (BaseFieldDeclarationSyntax)declarator.Parent.Parent;
}
protected override OneOrMany<SyntaxList<AttributeListSyntax>> GetAttributeDeclarations()
{
if (this.containingType.AnyMemberHasAttributes)
{
return OneOrMany.Create(GetFieldDeclaration(this.SyntaxNode).AttributeLists);
}
return OneOrMany<SyntaxList<AttributeListSyntax>>.Empty;
}
public sealed override RefKind RefKind => GetTypeAndRefKind(ConsList<FieldSymbol>.Empty).RefKind;
internal override bool HasPointerType
{
get
{
return TypeWithAnnotations.DefaultType.IsPointerOrFunctionPointer();
}
}
internal sealed override TypeWithAnnotations GetFieldType(ConsList<FieldSymbol> fieldsBeingBound)
{
return GetTypeAndRefKind(fieldsBeingBound).Type;
}
private TypeAndRefKind GetTypeAndRefKind(ConsList<FieldSymbol> fieldsBeingBound)
{
Debug.Assert(fieldsBeingBound != null);
if (_lazyTypeAndRefKind != null)
{
return _lazyTypeAndRefKind;
}
var declarator = VariableDeclaratorNode;
var fieldSyntax = GetFieldDeclaration(declarator);
var typeSyntax = fieldSyntax.Declaration.Type;
var compilation = this.DeclaringCompilation;
var diagnostics = BindingDiagnosticBag.GetInstance();
RefKind refKind = RefKind.None;
TypeWithAnnotations type;
if (typeSyntax is ScopedTypeSyntax scopedType)
{
diagnostics.Add(ErrorCode.ERR_BadMemberFlag, ErrorLocation, SyntaxFacts.GetText(SyntaxKind.ScopedKeyword));
}
// When we have multiple declarators, we report the type diagnostics on only the first.
var diagnosticsForFirstDeclarator = BindingDiagnosticBag.GetInstance();
Symbol associatedPropertyOrEvent = this.AssociatedSymbol;
if ((object)associatedPropertyOrEvent != null && associatedPropertyOrEvent.Kind == SymbolKind.Event)
{
EventSymbol @event = (EventSymbol)associatedPropertyOrEvent;
if (@event.IsWindowsRuntimeEvent)
{
NamedTypeSymbol tokenTableType = this.DeclaringCompilation.GetWellKnownType(WellKnownType.System_Runtime_InteropServices_WindowsRuntime_EventRegistrationTokenTable_T);
Binder.ReportUseSite(tokenTableType, diagnosticsForFirstDeclarator, this.ErrorLocation);
// CONSIDER: Do we want to guard against the possibility that someone has created their own EventRegistrationTokenTable<T>
// type that has additional generic constraints?
type = TypeWithAnnotations.Create(tokenTableType.Construct(ImmutableArray.Create(@event.TypeWithAnnotations)));
}
else
{
type = @event.TypeWithAnnotations;
}
}
else
{
var binderFactory = compilation.GetBinderFactory(SyntaxTree);
var binder = binderFactory.GetBinder(typeSyntax);
binder = binder.WithAdditionalFlagsAndContainingMemberOrLambda(BinderFlags.SuppressConstraintChecks, this);
if (!ContainingType.IsScriptClass)
{
var typeOnly = typeSyntax.SkipScoped(out _).SkipRefInField(out refKind);
Debug.Assert(refKind is RefKind.None or RefKind.Ref or RefKind.RefReadOnly);
type = binder.BindType(typeOnly, diagnosticsForFirstDeclarator);
if (refKind != RefKind.None)
{
MessageID.IDS_FeatureRefFields.CheckFeatureAvailability(diagnostics, compilation, typeSyntax.SkipScoped(out _).Location);
if (!compilation.Assembly.RuntimeSupportsByRefFields)
diagnostics.Add(ErrorCode.ERR_RuntimeDoesNotSupportRefFields, ErrorLocation);
if (!containingType.IsRefLikeType)
diagnostics.Add(ErrorCode.ERR_RefFieldInNonRefStruct, ErrorLocation);
if (type.Type.IsRefLikeOrAllowsRefLikeType())
diagnostics.Add(ErrorCode.ERR_RefFieldCannotReferToRefStruct, typeSyntax.SkipScoped(out _).Location);
}
}
else
{
bool isVar;
type = binder.BindTypeOrVarKeyword(typeSyntax.SkipScoped(out _).SkipRefInField(out RefKind refKindToAssert), diagnostics, out isVar);
Debug.Assert(refKindToAssert == RefKind.None); // Otherwise we might need to report an error
Debug.Assert(type.HasType || isVar);
if (isVar)
{
if (this.IsConst)
{
diagnosticsForFirstDeclarator.Add(ErrorCode.ERR_ImplicitlyTypedVariableCannotBeConst, typeSyntax.Location);
}
if (fieldsBeingBound.ContainsReference(this))
{
diagnostics.Add(ErrorCode.ERR_RecursivelyTypedVariable, this.ErrorLocation, this);
type = default;
}
else if (fieldSyntax.Declaration.Variables.Count > 1)
{
diagnosticsForFirstDeclarator.Add(ErrorCode.ERR_ImplicitlyTypedVariableMultipleDeclarator, typeSyntax.Location);
}
else if (this.IsConst && this.ContainingType.IsScriptClass)
{
// For const var in script, we won't try to bind the initializer (case below), as it can lead to an unbound recursion
type = default;
}
else
{
fieldsBeingBound = new ConsList<FieldSymbol>(this, fieldsBeingBound);
var syntaxNode = (EqualsValueClauseSyntax)declarator.Initializer;
var initializerBinder = new ImplicitlyTypedFieldBinder(binder, fieldsBeingBound);
var executableBinder = new ExecutableCodeBinder(syntaxNode, this, initializerBinder);
var initializerOpt = executableBinder.BindInferredVariableInitializer(diagnostics, RefKind.None, syntaxNode, declarator);
if (initializerOpt != null)
{
if ((object)initializerOpt.Type != null && !initializerOpt.Type.IsErrorType())
{
type = TypeWithAnnotations.Create(initializerOpt.Type);
}
_lazyFieldTypeInferred = 1;
}
}
if (!type.HasType)
{
type = TypeWithAnnotations.Create(binder.CreateErrorType("var"));
}
}
}
if (IsFixedSizeBuffer)
{
type = TypeWithAnnotations.Create(new PointerTypeSymbol(type));
if (ContainingType.TypeKind != TypeKind.Struct)
{
diagnostics.Add(ErrorCode.ERR_FixedNotInStruct, ErrorLocation);
}
if (refKind != RefKind.None)
{
diagnostics.Add(ErrorCode.ERR_FixedFieldMustNotBeRef, ErrorLocation);
}
var elementType = ((PointerTypeSymbol)type.Type).PointedAtType;
int elementSize = elementType.FixedBufferElementSizeInBytes();
if (elementSize == 0)
{
var loc = typeSyntax.Location;
diagnostics.Add(ErrorCode.ERR_IllegalFixedType, loc);
}
if (!binder.InUnsafeRegion)
{
diagnosticsForFirstDeclarator.Add(ErrorCode.ERR_UnsafeNeeded, declarator.Location);
}
}
}
// update the lazyType only if it contains value last seen by the current thread:
if (Interlocked.CompareExchange(ref _lazyTypeAndRefKind, new TypeAndRefKind(refKind, type.WithModifiers(this.RequiredCustomModifiers)), null) == null)
{
TypeChecks(type.Type, diagnostics);
// CONSIDER: SourceEventFieldSymbol would like to suppress these diagnostics.
AddDeclarationDiagnostics(diagnostics);
bool isFirstDeclarator = fieldSyntax.Declaration.Variables[0] == declarator;
if (isFirstDeclarator)
{
AddDeclarationDiagnostics(diagnosticsForFirstDeclarator);
}
state.NotePartComplete(CompletionPart.Type);
}
diagnostics.Free();
diagnosticsForFirstDeclarator.Free();
return _lazyTypeAndRefKind;
}
internal bool FieldTypeInferred(ConsList<FieldSymbol> fieldsBeingBound)
{
if (!ContainingType.IsScriptClass)
{
return false;
}
GetFieldType(fieldsBeingBound);
// lazyIsImplicitlyTypedField can only transition from value 0 to 1:
return _lazyFieldTypeInferred != 0 || Volatile.Read(ref _lazyFieldTypeInferred) != 0;
}
protected sealed override ConstantValue MakeConstantValue(HashSet<SourceFieldSymbolWithSyntaxReference> dependencies, bool earlyDecodingWellKnownAttributes, BindingDiagnosticBag diagnostics)
{
if (!this.IsConst || VariableDeclaratorNode.Initializer == null)
{
return null;
}
return ConstantValueUtils.EvaluateFieldConstant(this, (EqualsValueClauseSyntax)VariableDeclaratorNode.Initializer, dependencies, earlyDecodingWellKnownAttributes, diagnostics);
}
public override bool IsDefinedInSourceTree(SyntaxTree tree, TextSpan? definedWithinSpan, CancellationToken cancellationToken = default(CancellationToken))
{
if (this.SyntaxTree == tree)
{
if (!definedWithinSpan.HasValue)
{
return true;
}
var fieldDeclaration = GetFieldDeclaration(this.SyntaxNode);
return fieldDeclaration.SyntaxTree.HasCompilationUnitRoot && fieldDeclaration.Span.IntersectsWith(definedWithinSpan.Value);
}
return false;
}
internal override void AfterAddingTypeMembersChecks(ConversionsBase conversions, BindingDiagnosticBag diagnostics)
{
// This check prevents redundant ManagedAddr diagnostics on the underlying pointer field of a fixed-size buffer
if (!IsFixedSizeBuffer)
{
Type.CheckAllConstraints(DeclaringCompilation, conversions, ErrorLocation, diagnostics);
}
base.AfterAddingTypeMembersChecks(conversions, diagnostics);
}
}
}
|