File: Symbols\Synthesized\SynthesizedBackingFieldSymbol.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.Threading;
using Microsoft.CodeAnalysis.CSharp.Emit;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.CSharp.Symbols
{
    /// <summary>
    /// Represents a compiler generated backing field for an automatically implemented property or
    /// a Primary Constructor parameter.
    /// </summary>
    internal abstract class SynthesizedBackingFieldSymbolBase : FieldSymbolWithAttributesAndModifiers
    {
        private readonly string _name;
        internal abstract bool HasInitializer { get; }
        protected override DeclarationModifiers Modifiers { get; }
 
        public SynthesizedBackingFieldSymbolBase(
            string name,
            bool isReadOnly,
            bool isStatic)
        {
            Debug.Assert(!string.IsNullOrEmpty(name));
 
            _name = name;
 
            Modifiers = DeclarationModifiers.Private |
                (isReadOnly ? DeclarationModifiers.ReadOnly : DeclarationModifiers.None) |
                (isStatic ? DeclarationModifiers.Static : DeclarationModifiers.None);
        }
 
        internal override void AddSynthesizedAttributes(PEModuleBuilder moduleBuilder, ref ArrayBuilder<CSharpAttributeData> attributes)
        {
            base.AddSynthesizedAttributes(moduleBuilder, ref attributes);
 
            var compilation = this.DeclaringCompilation;
 
            // do not emit CompilerGenerated attributes for fields inside compiler generated types:
            if (!this.ContainingType.IsImplicitlyDeclared)
            {
                AddSynthesizedAttribute(ref attributes, compilation.TrySynthesizeAttribute(WellKnownMember.System_Runtime_CompilerServices_CompilerGeneratedAttribute__ctor));
            }
 
            // Dev11 doesn't synthesize this attribute, the debugger has a knowledge
            // of special name C# compiler uses for backing fields, which is not desirable.
            AddSynthesizedAttribute(ref attributes, compilation.SynthesizeDebuggerBrowsableNeverAttribute());
        }
 
        public override string Name
            => _name;
 
        internal override ConstantValue GetConstantValue(ConstantFieldsInProgress inProgress, bool earlyDecodingWellKnownAttributes)
            => null;
 
        public override ImmutableArray<SyntaxReference> DeclaringSyntaxReferences
            => ImmutableArray<SyntaxReference>.Empty;
 
        internal override bool HasRuntimeSpecialName
            => false;
 
        public override bool IsImplicitlyDeclared
            => true;
 
        internal override bool IsRequired => false;
    }
 
    /// <summary>
    /// Represents a compiler generated backing field for an automatically implemented property.
    /// </summary>
    internal sealed class SynthesizedBackingFieldSymbol : SynthesizedBackingFieldSymbolBase
    {
        private readonly SourcePropertySymbolBase _property;
        private int _inferredNullableAnnotation = (int)NullableAnnotation.Ignored;
        internal override bool HasInitializer { get; }
 
        public SynthesizedBackingFieldSymbol(
            SourcePropertySymbolBase property,
            string name,
            bool isReadOnly,
            bool isStatic,
            bool hasInitializer)
            : base(name, isReadOnly, isStatic)
        {
            Debug.Assert(!string.IsNullOrEmpty(name));
            Debug.Assert(property.RefKind is RefKind.None or RefKind.Ref or RefKind.RefReadOnly);
            _property = property;
            HasInitializer = hasInitializer;
        }
 
        protected override IAttributeTargetSymbol AttributeOwner
            => _property.AttributesOwner;
 
        internal override Location ErrorLocation
            => _property.Location;
 
        protected override OneOrMany<SyntaxList<AttributeListSyntax>> GetAttributeDeclarations()
        {
            // The backing field for a partial property may have been calculated for either
            // the definition part or the implementation part. Regardless, we should use
            // the attributes from the definition part.
            var property = (_property as SourcePropertySymbol)?.SourcePartialDefinitionPart ?? _property;
            return property.GetAttributeDeclarations();
        }
 
        public override Symbol AssociatedSymbol
            => _property;
 
        public override ImmutableArray<Location> Locations
            => _property.Locations;
 
        public override RefKind RefKind => _property.RefKind;
 
        public override ImmutableArray<CustomModifier> RefCustomModifiers => _property.RefCustomModifiers;
 
        internal override TypeWithAnnotations GetFieldType(ConsList<FieldSymbol> fieldsBeingBound)
            => _property.TypeWithAnnotations;
 
#nullable enable
        internal bool InfersNullableAnnotation
        {
            get
            {
                if (FlowAnalysisAnnotations != FlowAnalysisAnnotations.None)
                {
                    return false;
                }
 
                var propertyType = _property.TypeWithAnnotations;
                if (propertyType.NullableAnnotation != NullableAnnotation.NotAnnotated
                    || !_property.UsesFieldKeyword)
                {
                    return false;
                }
 
                return true;
            }
        }
 
        internal NullableAnnotation GetInferredNullableAnnotation()
        {
            if (_inferredNullableAnnotation == (int)NullableAnnotation.Ignored)
            {
                var inferredAnnotation = ComputeInferredNullableAnnotation();
                Debug.Assert(inferredAnnotation is not NullableAnnotation.Ignored);
                Interlocked.CompareExchange(ref _inferredNullableAnnotation, (int)inferredAnnotation, (int)NullableAnnotation.Ignored);
            }
            Debug.Assert((NullableAnnotation)_inferredNullableAnnotation is NullableAnnotation.NotAnnotated or NullableAnnotation.Annotated);
            return (NullableAnnotation)_inferredNullableAnnotation;
        }
 
        private NullableAnnotation ComputeInferredNullableAnnotation()
        {
            // https://github.com/dotnet/csharplang/blob/94205582d0f5c73e5765cb5888311c2f14890b95/proposals/field-keyword.md#nullability-of-the-backing-field
            Debug.Assert(InfersNullableAnnotation);
 
            // If the property does not have a get accessor, it is (vacuously) null-resilient.
            if (_property.GetMethod is not SourcePropertyAccessorSymbol getAccessor)
            {
                Debug.Assert(_property.GetMethod is null);
                return NullableAnnotation.Annotated;
            }
 
            // If the get accessor is auto-implemented, the property is not null-resilient.
            if (getAccessor.IsAutoPropertyAccessor)
                return NullableAnnotation.NotAnnotated;
 
            getAccessor = (SourcePropertyAccessorSymbol?)getAccessor.PartialImplementationPart ?? getAccessor;
            var binder = getAccessor.TryGetBodyBinder() ?? throw ExceptionUtilities.UnexpectedValue(getAccessor);
            var boundGetAccessor = binder.BindMethodBody(getAccessor.SyntaxNode, BindingDiagnosticBag.Discarded);
 
            var annotatedDiagnostics = nullableAnalyzeAndFilterDiagnostics(assumedNullableAnnotation: NullableAnnotation.Annotated);
            if (annotatedDiagnostics.IsEmptyWithoutResolution)
            {
                // If the pass where the field was annotated results in no diagnostics at all, then the property is null-resilient and the not-annotated pass can be skipped.
                annotatedDiagnostics.Free();
                return NullableAnnotation.Annotated;
            }
 
            var notAnnotatedDiagnostics = nullableAnalyzeAndFilterDiagnostics(assumedNullableAnnotation: NullableAnnotation.NotAnnotated);
            if (notAnnotatedDiagnostics.IsEmptyWithoutResolution)
            {
                // annotated pass had diagnostics, and not-annotated pass had no diagnostics.
                annotatedDiagnostics.Free();
                notAnnotatedDiagnostics.Free();
                return NullableAnnotation.NotAnnotated;
            }
 
            // Both annotated and not-annotated cases had nullable warnings.
            var notAnnotatedDiagnosticsSet = new HashSet<Diagnostic>(notAnnotatedDiagnostics.AsEnumerable(), SameDiagnosticComparer.Instance);
            notAnnotatedDiagnostics.Free();
 
            foreach (var diagnostic in annotatedDiagnostics.AsEnumerable())
            {
                if (!notAnnotatedDiagnosticsSet.Contains(diagnostic))
                {
                    // There is a nullable diagnostic in the pass where the field was *annotated*,
                    // which was not present in the pass where the field is *not-annotated*. The property is not null-resilient.
                    annotatedDiagnostics.Free();
                    return NullableAnnotation.NotAnnotated;
                }
            }
 
            annotatedDiagnostics.Free();
            return NullableAnnotation.Annotated;
 
            DiagnosticBag nullableAnalyzeAndFilterDiagnostics(NullableAnnotation assumedNullableAnnotation)
            {
                var diagnostics = DiagnosticBag.GetInstance();
                NullableWalker.AnalyzeIfNeeded(binder, boundGetAccessor, boundGetAccessor.Syntax, diagnostics, getterNullResilienceData: (getAccessor, _property.BackingField, assumedNullableAnnotation));
                if (diagnostics.IsEmptyWithoutResolution)
                {
                    return diagnostics;
                }
 
                var filteredDiagnostics = DiagnosticBag.GetInstance();
                _ = DeclaringCompilation.FilterAndAppendAndFreeDiagnostics(filteredDiagnostics, ref diagnostics, cancellationToken: default);
                return filteredDiagnostics;
            }
        }
#nullable disable
 
        internal override bool HasPointerType
            => _property.HasPointerType;
 
        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 CS8362: Do not use 'System.Runtime.CompilerServices.FixedBuffer' attribute on property
                ((BindingDiagnosticBag)arguments.Diagnostics).Add(ErrorCode.ERR_DoNotUseFixedBufferAttrOnProperty, arguments.AttributeSyntaxOpt.Name.Location);
            }
            else
            {
                base.DecodeWellKnownAttributeImpl(ref arguments);
            }
        }
 
        public override Symbol ContainingSymbol
            => _property.ContainingSymbol;
 
        public override NamedTypeSymbol ContainingType
            => _property.ContainingType;
 
        internal override void PostDecodeWellKnownAttributes(ImmutableArray<CSharpAttributeData> boundAttributes, ImmutableArray<AttributeSyntax> allAttributeSyntaxNodes, BindingDiagnosticBag diagnostics, AttributeLocation symbolPart, WellKnownAttributeData decodedData)
        {
            base.PostDecodeWellKnownAttributes(boundAttributes, allAttributeSyntaxNodes, diagnostics, symbolPart, decodedData);
 
            if (!allAttributeSyntaxNodes.IsEmpty && _property.IsAutoPropertyOrUsesFieldKeyword)
            {
                CheckForFieldTargetedAttribute(diagnostics);
            }
        }
 
        private void CheckForFieldTargetedAttribute(BindingDiagnosticBag diagnostics)
        {
            var languageVersion = this.DeclaringCompilation.LanguageVersion;
            if (languageVersion.AllowAttributesOnBackingFields())
            {
                return;
            }
 
            foreach (var attributeList in GetAttributeDeclarations())
            {
                foreach (var attribute in attributeList)
                {
                    if (attribute.Target?.GetAttributeLocation() == AttributeLocation.Field)
                    {
                        diagnostics.Add(
                            new CSDiagnosticInfo(ErrorCode.WRN_AttributesOnBackingFieldsNotAvailable,
                                languageVersion.ToDisplayString(),
                                new CSharpRequiredLanguageVersion(MessageID.IDS_FeatureAttributesOnBackingFields.RequiredVersion())),
                            attribute.Target.Location);
                    }
                }
            }
        }
    }
}