File: Symbols\Source\SourceFieldSymbol.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.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Globalization;
using System.Threading;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Text;
 
namespace Microsoft.CodeAnalysis.CSharp.Symbols
{
    internal abstract class SourceFieldSymbol : FieldSymbolWithAttributesAndModifiers
    {
        protected readonly SourceMemberContainerTypeSymbol containingType;
 
        protected SourceFieldSymbol(SourceMemberContainerTypeSymbol containingType)
        {
            Debug.Assert((object)containingType != null);
 
            this.containingType = containingType;
        }
 
        public abstract override string Name { get; }
 
        protected override IAttributeTargetSymbol AttributeOwner
        {
            get { return this; }
        }
 
        internal sealed override bool RequiresCompletion
        {
            get { return true; }
        }
 
        internal bool IsNew
        {
            get
            {
                return (Modifiers & DeclarationModifiers.New) != 0;
            }
        }
 
        protected void CheckAccessibility(BindingDiagnosticBag diagnostics)
        {
            ModifierUtils.CheckAccessibility(Modifiers, this, isExplicitInterfaceImplementation: false, diagnostics, ErrorLocation);
        }
 
        protected void ReportModifiersDiagnostics(BindingDiagnosticBag diagnostics)
        {
            if (ContainingType.IsSealed && this.DeclaredAccessibility.HasProtected())
            {
                diagnostics.Add(AccessCheck.GetProtectedMemberInSealedTypeError(containingType), ErrorLocation, this);
            }
            else if (IsVolatile && IsReadOnly)
            {
                diagnostics.Add(ErrorCode.ERR_VolatileAndReadonly, ErrorLocation, this);
            }
            else if (containingType.IsStatic && !IsStatic)
            {
                diagnostics.Add(ErrorCode.ERR_InstanceMemberInStaticClass, ErrorLocation, this);
            }
            else if (!IsStatic && !IsReadOnly && containingType.IsReadOnly)
            {
                diagnostics.Add(ErrorCode.ERR_FieldsInRoStruct, ErrorLocation);
            }
 
            // TODO: Consider checking presence of core type System.Runtime.CompilerServices.IsVolatile
            // if there is a volatile modifier. Perhaps an appropriate error should be reported if the
            // type isn't available.
        }
 
        protected ImmutableArray<CustomModifier> RequiredCustomModifiers
        {
            get
            {
                if (!IsVolatile)
                {
                    return ImmutableArray<CustomModifier>.Empty;
                }
                else
                {
                    return ImmutableArray.Create<CustomModifier>(
                            CSharpCustomModifier.CreateRequired(this.ContainingAssembly.GetSpecialType(SpecialType.System_Runtime_CompilerServices_IsVolatile)));
                }
            }
        }
 
        // Currently, source symbols cannot declare RefCustomModifiers. If that changes, and this
        // property is updated, test retargeting. (Update RefFieldTests.RetargetingField for instance.)
        public sealed override ImmutableArray<CustomModifier> RefCustomModifiers => ImmutableArray<CustomModifier>.Empty;
 
        public sealed override Symbol ContainingSymbol
        {
            get
            {
                return containingType;
            }
        }
 
        public override NamedTypeSymbol ContainingType
        {
            get
            {
                return this.containingType;
            }
        }
 
        protected sealed override void DecodeWellKnownAttributeImpl(ref DecodeWellKnownAttributeArguments<AttributeSyntax, CSharpAttributeData, AttributeLocation> arguments)
        {
            Debug.Assert((object)arguments.AttributeSyntaxOpt != null);
            Debug.Assert(arguments.Diagnostics is BindingDiagnosticBag);
 
            var attribute = arguments.Attribute;
            Debug.Assert(!attribute.HasErrors);
            Debug.Assert(arguments.SymbolPart == AttributeLocation.None);
 
            if (attribute.IsTargetAttribute(AttributeDescription.FixedBufferAttribute))
            {
                // error CS1716: Do not use 'System.Runtime.CompilerServices.FixedBuffer' attribute. Use the 'fixed' field modifier instead.
                ((BindingDiagnosticBag)arguments.Diagnostics).Add(ErrorCode.ERR_DoNotUseFixedBufferAttr, arguments.AttributeSyntaxOpt.Name.Location);
            }
            else
            {
                base.DecodeWellKnownAttributeImpl(ref arguments);
            }
        }
 
        internal override void AfterAddingTypeMembersChecks(ConversionsBase conversions, BindingDiagnosticBag diagnostics)
        {
            var compilation = DeclaringCompilation;
            var location = ErrorLocation;
 
            if (RefKind == RefKind.RefReadOnly)
            {
                compilation.EnsureIsReadOnlyAttributeExists(diagnostics, location, modifyCompilation: true);
            }
 
            if (compilation.ShouldEmitNativeIntegerAttributes(Type))
            {
                compilation.EnsureNativeIntegerAttributeExists(diagnostics, location, modifyCompilation: true);
            }
 
            if (compilation.ShouldEmitNullableAttributes(this) &&
                TypeWithAnnotations.NeedsNullableAttribute())
            {
                compilation.EnsureNullableAttributeExists(diagnostics, location, modifyCompilation: true);
            }
        }
 
        internal sealed override bool HasRuntimeSpecialName
        {
            get
            {
                return this.Name == WellKnownMemberNames.EnumBackingFieldName;
            }
        }
 
        internal override bool IsRequired => (Modifiers & DeclarationModifiers.Required) != 0;
    }
 
    internal abstract class SourceFieldSymbolWithSyntaxReference : SourceFieldSymbol
    {
        private readonly string _name;
        private readonly TextSpan _locationSpan;
        private readonly SyntaxReference _syntaxReference;
 
        private string _lazyDocComment;
        private string _lazyExpandedDocComment;
        private ConstantValue _lazyConstantEarlyDecodingValue = Microsoft.CodeAnalysis.ConstantValue.Unset;
        private ConstantValue _lazyConstantValue = Microsoft.CodeAnalysis.ConstantValue.Unset;
 
        protected SourceFieldSymbolWithSyntaxReference(SourceMemberContainerTypeSymbol containingType, string name, SyntaxReference syntax, TextSpan locationSpan)
            : base(containingType)
        {
            Debug.Assert(name != null);
            Debug.Assert(syntax != null);
 
            _name = name;
            _syntaxReference = syntax;
            _locationSpan = locationSpan;
        }
 
        public SyntaxTree SyntaxTree
        {
            get
            {
                return _syntaxReference.SyntaxTree;
            }
        }
 
        public CSharpSyntaxNode SyntaxNode
        {
            get
            {
                return (CSharpSyntaxNode)_syntaxReference.GetSyntax();
            }
        }
 
        public sealed override string Name
        {
            get
            {
                return _name;
            }
        }
 
        internal override LexicalSortKey GetLexicalSortKey()
            => new LexicalSortKey(_syntaxReference, this.DeclaringCompilation);
 
        public override Location TryGetFirstLocation()
            => _syntaxReference.SyntaxTree.GetLocation(_locationSpan);
 
        public sealed override ImmutableArray<Location> Locations
            => ImmutableArray.Create(GetFirstLocation());
 
        internal sealed override Location ErrorLocation
            => GetFirstLocation();
 
        public sealed override ImmutableArray<SyntaxReference> DeclaringSyntaxReferences
            => ImmutableArray.Create(_syntaxReference);
 
        public override bool IsDefinedInSourceTree(SyntaxTree tree, TextSpan? definedWithinSpan, CancellationToken cancellationToken = default)
            => IsDefinedInSourceTree(_syntaxReference, tree, definedWithinSpan);
 
        public sealed override string GetDocumentationCommentXml(CultureInfo preferredCulture = null, bool expandIncludes = false, CancellationToken cancellationToken = default(CancellationToken))
        {
            ref var lazyDocComment = ref expandIncludes ? ref _lazyExpandedDocComment : ref _lazyDocComment;
            return SourceDocumentationCommentUtils.GetAndCacheDocumentationComment(this, expandIncludes, ref lazyDocComment);
        }
 
        internal sealed override ConstantValue GetConstantValue(ConstantFieldsInProgress inProgress, bool earlyDecodingWellKnownAttributes)
        {
            var value = this.GetLazyConstantValue(earlyDecodingWellKnownAttributes);
            if (value != Microsoft.CodeAnalysis.ConstantValue.Unset)
            {
                return value;
            }
 
            if (!inProgress.IsEmpty)
            {
                // Add this field as a dependency of the original field, and
                // return Unset. The outer GetConstantValue caller will call
                // this method again after evaluating any dependencies.
                inProgress.AddDependency(this);
                return Microsoft.CodeAnalysis.ConstantValue.Unset;
            }
 
            // Order dependencies.
            var order = ArrayBuilder<ConstantEvaluationHelpers.FieldInfo>.GetInstance();
            this.OrderAllDependencies(order, earlyDecodingWellKnownAttributes);
 
            // Evaluate fields in order.
            foreach (var info in order)
            {
                // Bind the field value regardless of whether the field represents
                // the start of a cycle. In the cycle case, there will be unevaluated
                // dependencies and the result will be ConstantValue.Bad plus cycle error.
                var field = info.Field;
                field.BindConstantValueIfNecessary(earlyDecodingWellKnownAttributes, startsCycle: info.StartsCycle);
            }
 
            order.Free();
 
            // Return the value of this field.
            return this.GetLazyConstantValue(earlyDecodingWellKnownAttributes);
        }
 
        /// <summary>
        /// Return the constant value dependencies. Compute the dependencies
        /// if necessary by evaluating the constant value but only persist the
        /// constant value if there were no dependencies. (If there are dependencies,
        /// the constant value will be re-evaluated after evaluating dependencies.)
        /// </summary>
        internal ImmutableHashSet<SourceFieldSymbolWithSyntaxReference> GetConstantValueDependencies(bool earlyDecodingWellKnownAttributes)
        {
            var value = this.GetLazyConstantValue(earlyDecodingWellKnownAttributes);
            if (value != Microsoft.CodeAnalysis.ConstantValue.Unset)
            {
                // Constant value already determined. No need to
                // compute dependencies since the constant values
                // of all dependencies should be evaluated as well.
                return ImmutableHashSet<SourceFieldSymbolWithSyntaxReference>.Empty;
            }
 
            ImmutableHashSet<SourceFieldSymbolWithSyntaxReference> dependencies;
            var builder = PooledHashSet<SourceFieldSymbolWithSyntaxReference>.GetInstance();
            var diagnostics = BindingDiagnosticBag.GetInstance();
            value = MakeConstantValue(builder, earlyDecodingWellKnownAttributes, diagnostics);
 
            // Only persist if there are no dependencies and the calculation
            // completed successfully. (We could probably persist in other
            // scenarios but it's probably not worth the added complexity.)
            if ((builder.Count == 0) &&
                (value != null) &&
                !value.IsBad &&
                (value != Microsoft.CodeAnalysis.ConstantValue.Unset) &&
                !diagnostics.HasAnyResolvedErrors())
            {
                this.SetLazyConstantValue(
                    value,
                    earlyDecodingWellKnownAttributes,
                    diagnostics,
                    startsCycle: false);
                dependencies = ImmutableHashSet<SourceFieldSymbolWithSyntaxReference>.Empty;
            }
            else
            {
                dependencies = ImmutableHashSet<SourceFieldSymbolWithSyntaxReference>.Empty.Union(builder);
            }
 
            diagnostics.Free();
            builder.Free();
            return dependencies;
        }
 
        private void BindConstantValueIfNecessary(bool earlyDecodingWellKnownAttributes, bool startsCycle)
        {
            if (this.GetLazyConstantValue(earlyDecodingWellKnownAttributes) != Microsoft.CodeAnalysis.ConstantValue.Unset)
            {
                return;
            }
 
            var builder = PooledHashSet<SourceFieldSymbolWithSyntaxReference>.GetInstance();
            var diagnostics = BindingDiagnosticBag.GetInstance();
            if (startsCycle)
            {
                diagnostics.Add(ErrorCode.ERR_CircConstValue, GetFirstLocation(), this);
            }
 
            var value = MakeConstantValue(builder, earlyDecodingWellKnownAttributes, diagnostics);
            this.SetLazyConstantValue(
                value,
                earlyDecodingWellKnownAttributes,
                diagnostics,
                startsCycle);
            diagnostics.Free();
            builder.Free();
        }
 
        private ConstantValue GetLazyConstantValue(bool earlyDecodingWellKnownAttributes)
        {
            return earlyDecodingWellKnownAttributes ? _lazyConstantEarlyDecodingValue : _lazyConstantValue;
        }
 
        private void SetLazyConstantValue(
            ConstantValue value,
            bool earlyDecodingWellKnownAttributes,
            BindingDiagnosticBag diagnostics,
            bool startsCycle)
        {
            Debug.Assert(value != Microsoft.CodeAnalysis.ConstantValue.Unset);
            Debug.Assert((GetLazyConstantValue(earlyDecodingWellKnownAttributes) == Microsoft.CodeAnalysis.ConstantValue.Unset) ||
                (GetLazyConstantValue(earlyDecodingWellKnownAttributes) == value));
 
            if (earlyDecodingWellKnownAttributes)
            {
                Interlocked.CompareExchange(ref _lazyConstantEarlyDecodingValue, value, Microsoft.CodeAnalysis.ConstantValue.Unset);
            }
            else
            {
                if (Interlocked.CompareExchange(ref _lazyConstantValue, value, Microsoft.CodeAnalysis.ConstantValue.Unset) == Microsoft.CodeAnalysis.ConstantValue.Unset)
                {
#if REPORT_ALL
                    Console.WriteLine("Thread {0}, Field {1}, StartsCycle {2}", Thread.CurrentThread.ManagedThreadId, this, startsCycle);
#endif
                    this.AddDeclarationDiagnostics(diagnostics);
                    // CompletionPart.ConstantValue is the last part for a field
                    DeclaringCompilation.SymbolDeclaredEvent(this);
                    var wasSetThisThread = this.state.NotePartComplete(CompletionPart.ConstantValue);
                    Debug.Assert(wasSetThisThread);
                }
            }
        }
 
        protected abstract ConstantValue MakeConstantValue(HashSet<SourceFieldSymbolWithSyntaxReference> dependencies, bool earlyDecodingWellKnownAttributes, BindingDiagnosticBag diagnostics);
    }
}