File: Symbols\Source\FieldSymbolWithAttributesAndModifiers.cs
Web Access
Project: src\src\Compilers\CSharp\Portable\Microsoft.CodeAnalysis.CSharp.csproj (Microsoft.CodeAnalysis.CSharp)
// 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 System;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using Microsoft.CodeAnalysis.CSharp.Emit;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.CSharp.Symbols
{
    internal abstract class FieldSymbolWithAttributesAndModifiers : FieldSymbol, IAttributeTargetSymbol
    {
        private CustomAttributesBag<CSharpAttributeData> _lazyCustomAttributesBag;
        protected SymbolCompletionState state;
        internal abstract Location ErrorLocation { get; }
        protected abstract DeclarationModifiers Modifiers { get; }
 
        /// <summary>
        /// Gets the syntax list of custom attributes applied on the symbol.
        /// </summary>
        protected abstract OneOrMany<SyntaxList<AttributeListSyntax>> GetAttributeDeclarations();
 
        protected abstract IAttributeTargetSymbol AttributeOwner { get; }
 
        IAttributeTargetSymbol IAttributeTargetSymbol.AttributesOwner
            => this.AttributeOwner;
 
        AttributeLocation IAttributeTargetSymbol.DefaultAttributeLocation
            => AttributeLocation.Field;
 
        AttributeLocation IAttributeTargetSymbol.AllowedAttributeLocations
            => AttributeLocation.Field;
 
        internal sealed override bool HasComplete(CompletionPart part)
            => state.HasComplete(part);
 
        public sealed override bool IsStatic
            => (Modifiers & DeclarationModifiers.Static) != 0;
 
        public sealed override bool IsReadOnly
            => (Modifiers & DeclarationModifiers.ReadOnly) != 0;
 
        public sealed override Accessibility DeclaredAccessibility
            => ModifierUtils.EffectiveAccessibility(Modifiers);
 
        public sealed override bool IsConst
            => (Modifiers & DeclarationModifiers.Const) != 0;
 
        public sealed override bool IsVolatile
            => (Modifiers & DeclarationModifiers.Volatile) != 0;
 
        public sealed override bool IsFixedSizeBuffer
            => (Modifiers & DeclarationModifiers.Fixed) != 0;
 
        /// <summary>
        /// Gets the attributes applied on this symbol.
        /// Returns an empty array if there are no attributes.
        /// </summary>
        /// <remarks>
        /// NOTE: This method should always be kept as a sealed override.
        /// If you want to override attribute binding logic for a sub-class, then override <see cref="GetAttributesBag"/> method.
        /// </remarks>
        public sealed override ImmutableArray<CSharpAttributeData> GetAttributes()
            => this.GetAttributesBag().Attributes;
 
        /// <summary>
        /// Returns a bag of applied custom attributes and data decoded from well-known attributes.
        /// Returns an empty bag if there are no attributes applied on the symbol.
        /// </summary>
        /// <remarks>
        /// Forces binding and decoding of attributes.
        /// </remarks>
        private CustomAttributesBag<CSharpAttributeData> GetAttributesBag()
        {
            var bag = _lazyCustomAttributesBag;
            if (bag != null && bag.IsSealed)
            {
                return bag;
            }
 
            if (LoadAndValidateAttributes(this.GetAttributeDeclarations(), ref _lazyCustomAttributesBag))
            {
                var completed = state.NotePartComplete(CompletionPart.Attributes);
                Debug.Assert(completed);
            }
 
            Debug.Assert(_lazyCustomAttributesBag.IsSealed);
            return _lazyCustomAttributesBag;
        }
 
        /// <summary>
        /// Returns data decoded from well-known attributes applied to the symbol or null if there are no applied attributes.
        /// </summary>
        /// <remarks>
        /// Forces binding and decoding of attributes.
        /// </remarks>
        protected FieldWellKnownAttributeData GetDecodedWellKnownAttributeData()
        {
            var attributesBag = _lazyCustomAttributesBag;
            if (attributesBag == null || !attributesBag.IsDecodedWellKnownAttributeDataComputed)
            {
                attributesBag = this.GetAttributesBag();
            }
 
            return (FieldWellKnownAttributeData)attributesBag.DecodedWellKnownAttributeData;
        }
 
#nullable enable
        internal sealed override (CSharpAttributeData?, BoundAttribute?) EarlyDecodeWellKnownAttribute(ref EarlyDecodeWellKnownAttributeArguments<EarlyWellKnownAttributeBinder, NamedTypeSymbol, AttributeSyntax, AttributeLocation> arguments)
        {
            CSharpAttributeData? attributeData;
            BoundAttribute? boundAttribute;
            ObsoleteAttributeData? obsoleteData;
 
            if (EarlyDecodeDeprecatedOrExperimentalOrObsoleteAttribute(ref arguments, out attributeData, out boundAttribute, out obsoleteData))
            {
                if (obsoleteData != null)
                {
                    arguments.GetOrCreateData<CommonFieldEarlyWellKnownAttributeData>().ObsoleteAttributeData = obsoleteData;
                }
 
                return (attributeData, boundAttribute);
            }
 
            return base.EarlyDecodeWellKnownAttribute(ref arguments);
        }
#nullable disable
 
        /// <summary>
        /// Returns data decoded from Obsolete attribute or null if there is no Obsolete attribute.
        /// This property returns ObsoleteAttributeData.Uninitialized if attribute arguments haven't been decoded yet.
        /// </summary>
        internal sealed override ObsoleteAttributeData ObsoleteAttributeData
        {
            get
            {
                var containingSourceType = (SourceMemberContainerTypeSymbol)ContainingType;
                if (!containingSourceType.AnyMemberHasAttributes)
                {
                    return null;
                }
 
                var lazyCustomAttributesBag = _lazyCustomAttributesBag;
                if (lazyCustomAttributesBag != null && lazyCustomAttributesBag.IsEarlyDecodedWellKnownAttributeDataComputed)
                {
                    var data = (CommonFieldEarlyWellKnownAttributeData)lazyCustomAttributesBag.EarlyDecodedWellKnownAttributeData;
                    return data != null ? data.ObsoleteAttributeData : null;
                }
 
                return ObsoleteAttributeData.Uninitialized;
            }
        }
 
        protected override void DecodeWellKnownAttributeImpl(ref DecodeWellKnownAttributeArguments<AttributeSyntax, CSharpAttributeData, AttributeLocation> arguments)
        {
            Debug.Assert((object)arguments.AttributeSyntaxOpt != null);
            var diagnostics = (BindingDiagnosticBag)arguments.Diagnostics;
 
            var attribute = arguments.Attribute;
            Debug.Assert(!attribute.HasErrors);
            Debug.Assert(arguments.SymbolPart == AttributeLocation.None);
 
            if (attribute.IsTargetAttribute(AttributeDescription.SpecialNameAttribute))
            {
                arguments.GetOrCreateData<FieldWellKnownAttributeData>().HasSpecialNameAttribute = true;
            }
            else if (attribute.IsTargetAttribute(AttributeDescription.NonSerializedAttribute))
            {
                arguments.GetOrCreateData<FieldWellKnownAttributeData>().HasNonSerializedAttribute = true;
            }
            else if (attribute.IsTargetAttribute(AttributeDescription.FieldOffsetAttribute))
            {
                if (this.IsStatic || this.IsConst)
                {
                    // CS0637: The FieldOffset attribute is not allowed on static or const fields
                    diagnostics.Add(ErrorCode.ERR_StructOffsetOnBadField, arguments.AttributeSyntaxOpt.Name.Location);
                }
                else
                {
                    int offset = attribute.CommonConstructorArguments[0].DecodeValue<int>(SpecialType.System_Int32);
                    if (offset < 0)
                    {
                        // Dev10 reports CS0647: "Error emitting attribute ..."
                        diagnostics.Add(ErrorCode.ERR_InvalidAttributeArgument, attribute.GetAttributeArgumentLocation(0), arguments.AttributeSyntaxOpt.GetErrorDisplayName());
                        offset = 0;
                    }
 
                    // Set field offset even if the attribute specifies an invalid value, so that
                    // post-validation knows that the attribute is applied and reports better errors.
                    arguments.GetOrCreateData<FieldWellKnownAttributeData>().SetFieldOffset(offset);
                }
            }
            else if (attribute.IsTargetAttribute(AttributeDescription.MarshalAsAttribute))
            {
                MarshalAsAttributeDecoder<FieldWellKnownAttributeData, AttributeSyntax, CSharpAttributeData, AttributeLocation>.Decode(ref arguments, AttributeTargets.Field, MessageProvider.Instance);
            }
            else if (ReportExplicitUseOfReservedAttributes(in arguments,
                ReservedAttributes.DynamicAttribute
                | ReservedAttributes.IsReadOnlyAttribute
                | ReservedAttributes.RequiresLocationAttribute
                | ReservedAttributes.IsUnmanagedAttribute
                | ReservedAttributes.IsByRefLikeAttribute
                | ReservedAttributes.TupleElementNamesAttribute
                | ReservedAttributes.NullableAttribute
                | ReservedAttributes.NativeIntegerAttribute
                | ReservedAttributes.RequiredMemberAttribute))
            {
            }
            else if (attribute.IsTargetAttribute(AttributeDescription.DateTimeConstantAttribute))
            {
                VerifyConstantValueMatches(attribute.DecodeDateTimeConstantValue(), ref arguments);
            }
            else if (attribute.IsTargetAttribute(AttributeDescription.DecimalConstantAttribute))
            {
                VerifyConstantValueMatches(attribute.DecodeDecimalConstantValue(), ref arguments);
            }
            else if (attribute.IsTargetAttribute(AttributeDescription.AllowNullAttribute))
            {
                arguments.GetOrCreateData<FieldWellKnownAttributeData>().HasAllowNullAttribute = true;
            }
            else if (attribute.IsTargetAttribute(AttributeDescription.DisallowNullAttribute))
            {
                arguments.GetOrCreateData<FieldWellKnownAttributeData>().HasDisallowNullAttribute = true;
            }
            else if (attribute.IsTargetAttribute(AttributeDescription.MaybeNullAttribute))
            {
                arguments.GetOrCreateData<FieldWellKnownAttributeData>().HasMaybeNullAttribute = true;
            }
            else if (attribute.IsTargetAttribute(AttributeDescription.NotNullAttribute))
            {
                arguments.GetOrCreateData<FieldWellKnownAttributeData>().HasNotNullAttribute = true;
            }
        }
 
        public override FlowAnalysisAnnotations FlowAnalysisAnnotations
            => DecodeFlowAnalysisAttributes(GetDecodedWellKnownAttributeData());
 
        private static FlowAnalysisAnnotations DecodeFlowAnalysisAttributes(FieldWellKnownAttributeData attributeData)
        {
            var annotations = FlowAnalysisAnnotations.None;
            if (attributeData != null)
            {
                if (attributeData.HasAllowNullAttribute) annotations |= FlowAnalysisAnnotations.AllowNull;
                if (attributeData.HasDisallowNullAttribute) annotations |= FlowAnalysisAnnotations.DisallowNull;
                if (attributeData.HasMaybeNullAttribute) annotations |= FlowAnalysisAnnotations.MaybeNull;
                if (attributeData.HasNotNullAttribute) annotations |= FlowAnalysisAnnotations.NotNull;
            }
            return annotations;
        }
 
        /// <summary>
        /// Verify the constant value matches the default value from any earlier attribute
        /// (DateTimeConstantAttribute or DecimalConstantAttribute).
        /// If not, report ERR_FieldHasMultipleDistinctConstantValues.
        /// </summary>
        private void VerifyConstantValueMatches(ConstantValue attrValue, ref DecodeWellKnownAttributeArguments<AttributeSyntax, CSharpAttributeData, AttributeLocation> arguments)
        {
            if (!attrValue.IsBad)
            {
                var data = arguments.GetOrCreateData<FieldWellKnownAttributeData>();
                ConstantValue constValue;
                var diagnostics = (BindingDiagnosticBag)arguments.Diagnostics;
 
                if (this.IsConst)
                {
                    if (this.Type.SpecialType == SpecialType.System_Decimal)
                    {
                        constValue = this.GetConstantValue(ConstantFieldsInProgress.Empty, earlyDecodingWellKnownAttributes: false);
 
                        if ((object)constValue != null && !constValue.IsBad && constValue != attrValue)
                        {
                            diagnostics.Add(ErrorCode.ERR_FieldHasMultipleDistinctConstantValues, arguments.AttributeSyntaxOpt.Location);
                        }
                    }
                    else
                    {
                        diagnostics.Add(ErrorCode.ERR_FieldHasMultipleDistinctConstantValues, arguments.AttributeSyntaxOpt.Location);
                    }
 
                    if (data.ConstValue == CodeAnalysis.ConstantValue.Unset)
                    {
                        data.ConstValue = attrValue;
                    }
                }
                else
                {
                    constValue = data.ConstValue;
 
                    if (constValue != CodeAnalysis.ConstantValue.Unset)
                    {
                        if (constValue != attrValue)
                        {
                            diagnostics.Add(ErrorCode.ERR_FieldHasMultipleDistinctConstantValues, arguments.AttributeSyntaxOpt.Location);
                        }
                    }
                    else
                    {
                        data.ConstValue = attrValue;
                    }
                }
            }
        }
 
        internal override void PostDecodeWellKnownAttributes(ImmutableArray<CSharpAttributeData> boundAttributes, ImmutableArray<AttributeSyntax> allAttributeSyntaxNodes, BindingDiagnosticBag diagnostics, AttributeLocation symbolPart, WellKnownAttributeData decodedData)
        {
            Debug.Assert(!boundAttributes.IsDefault);
            Debug.Assert(!allAttributeSyntaxNodes.IsDefault);
            Debug.Assert(boundAttributes.Length == allAttributeSyntaxNodes.Length);
            Debug.Assert(_lazyCustomAttributesBag != null);
            Debug.Assert(_lazyCustomAttributesBag.IsDecodedWellKnownAttributeDataComputed);
            Debug.Assert(symbolPart == AttributeLocation.None);
 
            var data = (FieldWellKnownAttributeData)decodedData;
            int? fieldOffset = data != null ? data.Offset : null;
 
            if (fieldOffset.HasValue)
            {
                if (this.ContainingType.Layout.Kind != LayoutKind.Explicit)
                {
                    Debug.Assert(boundAttributes.Any());
 
                    // error CS0636: The FieldOffset attribute can only be placed on members of types marked with the StructLayout(LayoutKind.Explicit)
                    int i = boundAttributes.IndexOfAttribute(AttributeDescription.FieldOffsetAttribute);
                    diagnostics.Add(ErrorCode.ERR_StructOffsetOnBadStruct, allAttributeSyntaxNodes[i].Name.Location);
                }
            }
            else if (!this.IsStatic && !this.IsConst)
            {
                if (this.ContainingType.Layout.Kind == LayoutKind.Explicit)
                {
                    // error CS0625: '<field>': instance field types marked with StructLayout(LayoutKind.Explicit) must have a FieldOffset attribute
                    diagnostics.Add(ErrorCode.ERR_MissingStructOffset, this.ErrorLocation, this.AttributeOwner);
                }
            }
 
            base.PostDecodeWellKnownAttributes(boundAttributes, allAttributeSyntaxNodes, diagnostics, symbolPart, decodedData);
        }
 
        internal sealed override bool HasSpecialName
        {
            get
            {
                if (this.HasRuntimeSpecialName)
                {
                    return true;
                }
 
                var data = GetDecodedWellKnownAttributeData();
                return data != null && data.HasSpecialNameAttribute;
            }
        }
 
        internal sealed override bool IsNotSerialized
        {
            get
            {
                var data = GetDecodedWellKnownAttributeData();
                return data != null && data.HasNonSerializedAttribute;
            }
        }
 
        internal sealed override MarshalPseudoCustomAttributeData MarshallingInformation
        {
            get
            {
                var data = GetDecodedWellKnownAttributeData();
                return data != null ? data.MarshallingInformation : null;
            }
        }
 
        internal sealed override int? TypeLayoutOffset
        {
            get
            {
                var data = GetDecodedWellKnownAttributeData();
                return data != null ? data.Offset : null;
            }
        }
 
        internal override void AddSynthesizedAttributes(PEModuleBuilder moduleBuilder, ref ArrayBuilder<SynthesizedAttributeData> attributes)
        {
            base.AddSynthesizedAttributes(moduleBuilder, ref attributes);
 
            if (this.RefKind == RefKind.RefReadOnly)
            {
                AddSynthesizedAttribute(ref attributes, moduleBuilder.SynthesizeIsReadOnlyAttribute(this));
            }
 
            var compilation = this.DeclaringCompilation;
            var type = this.TypeWithAnnotations;
 
            if (type.Type.ContainsDynamic())
            {
                AddSynthesizedAttribute(ref attributes,
                    compilation.SynthesizeDynamicAttribute(type.Type, type.CustomModifiers.Length));
            }
 
            if (compilation.ShouldEmitNativeIntegerAttributes(type.Type))
            {
                AddSynthesizedAttribute(ref attributes, moduleBuilder.SynthesizeNativeIntegerAttribute(this, type.Type));
            }
 
            if (type.Type.ContainsTupleNames())
            {
                AddSynthesizedAttribute(ref attributes,
                    compilation.SynthesizeTupleNamesAttribute(type.Type));
            }
 
            if (compilation.ShouldEmitNullableAttributes(this))
            {
                AddSynthesizedAttribute(ref attributes, moduleBuilder.SynthesizeNullableAttributeIfNecessary(this, ContainingType.GetNullableContextValue(), type));
            }
        }
    }
}