File: Symbols\Source\SourceConstructorSymbol.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.
 
using System;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.CSharp.Symbols
{
    internal sealed class SourceConstructorSymbol : SourceConstructorSymbolBase
    {
        private SourceConstructorSymbol? _otherPartOfPartial;
 
#nullable disable
        public static SourceConstructorSymbol CreateConstructorSymbol(
            SourceMemberContainerTypeSymbol containingType,
            ConstructorDeclarationSyntax syntax,
            bool isNullableAnalysisEnabled,
            BindingDiagnosticBag diagnostics)
        {
            var methodKind = syntax.Modifiers.Any(SyntaxKind.StaticKeyword) ? MethodKind.StaticConstructor : MethodKind.Constructor;
            return new SourceConstructorSymbol(containingType, syntax.Identifier.GetLocation(), syntax, methodKind, isNullableAnalysisEnabled, diagnostics);
        }
 
        private SourceConstructorSymbol(
             SourceMemberContainerTypeSymbol containingType,
             Location location,
             ConstructorDeclarationSyntax syntax,
             MethodKind methodKind,
             bool isNullableAnalysisEnabled,
             BindingDiagnosticBag diagnostics) :
             base(containingType, location, syntax, SyntaxFacts.HasYieldOperations(syntax),
                  MakeModifiersAndFlags(
                      containingType, syntax, methodKind, isNullableAnalysisEnabled, syntax.Initializer?.Kind() == SyntaxKind.ThisConstructorInitializer, location, diagnostics, out bool modifierErrors, out bool report_ERR_StaticConstructorWithAccessModifiers))
        {
            this.CheckUnsafeModifier(DeclarationModifiers, diagnostics);
 
            if (report_ERR_StaticConstructorWithAccessModifiers)
            {
                diagnostics.Add(ErrorCode.ERR_StaticConstructorWithAccessModifiers, location, this);
            }
 
            if (syntax.Identifier.ValueText != containingType.Name)
            {
                // This is probably a method declaration with the type missing.
                diagnostics.Add(ErrorCode.ERR_MemberNeedsType, location);
            }
 
            bool hasAnyBody = syntax.HasAnyBody();
 
            if (IsExtern)
            {
                if (methodKind == MethodKind.Constructor && syntax.Initializer != null)
                {
                    diagnostics.Add(ErrorCode.ERR_ExternHasConstructorInitializer, location, this);
                }
 
                if (hasAnyBody)
                {
                    diagnostics.Add(ErrorCode.ERR_ExternHasBody, location, this);
                }
            }
 
            if (IsPartialDefinition && syntax.Initializer is { } initializer)
            {
                diagnostics.Add(ErrorCode.ERR_PartialConstructorInitializer, initializer, this);
            }
 
            if (methodKind == MethodKind.StaticConstructor)
            {
                CheckFeatureAvailabilityAndRuntimeSupport(syntax, location, hasAnyBody, diagnostics);
            }
 
            ModifierUtils.CheckAccessibility(this.DeclarationModifiers, this, isExplicitInterfaceImplementation: false, diagnostics, location);
 
            if (!modifierErrors)
            {
                this.CheckModifiers(methodKind, hasAnyBody, location, diagnostics);
            }
 
            CheckForBlockAndExpressionBody(
                syntax.Body, syntax.ExpressionBody, syntax, diagnostics);
        }
 
        private static (DeclarationModifiers, Flags) MakeModifiersAndFlags(
            NamedTypeSymbol containingType,
            ConstructorDeclarationSyntax syntax,
            MethodKind methodKind,
            bool isNullableAnalysisEnabled,
            bool hasThisInitializer,
            Location location,
            BindingDiagnosticBag diagnostics,
            out bool modifierErrors,
            out bool report_ERR_StaticConstructorWithAccessModifiers)
        {
            bool hasAnyBody = syntax.HasAnyBody();
            DeclarationModifiers declarationModifiers = MakeModifiers(containingType, syntax, methodKind, hasAnyBody, location, diagnostics, out modifierErrors, out bool hasExplicitAccessModifier, out report_ERR_StaticConstructorWithAccessModifiers);
            Flags flags = new Flags(
                methodKind, RefKind.None, declarationModifiers, returnsVoid: true, returnsVoidIsSet: true, hasAnyBody: hasAnyBody,
                isExpressionBodied: syntax.IsExpressionBodied(), isExtensionMethod: false, isVararg: syntax.IsVarArg(),
                isNullableAnalysisEnabled: isNullableAnalysisEnabled, isExplicitInterfaceImplementation: false,
                hasThisInitializer: hasThisInitializer, hasExplicitAccessModifier: hasExplicitAccessModifier);
 
            return (declarationModifiers, flags);
        }
 
        internal ConstructorDeclarationSyntax GetSyntax()
        {
            Debug.Assert(syntaxReferenceOpt != null);
            return (ConstructorDeclarationSyntax)syntaxReferenceOpt.GetSyntax();
        }
 
        internal override ExecutableCodeBinder TryGetBodyBinder(BinderFactory binderFactoryOpt = null, bool ignoreAccessibility = false)
        {
            return TryGetBodyBinderFromSyntax(binderFactoryOpt, ignoreAccessibility);
        }
 
        protected override ParameterListSyntax GetParameterList()
        {
            return GetSyntax().ParameterList;
        }
 
        protected override CSharpSyntaxNode GetInitializer()
        {
            return GetSyntax().Initializer;
        }
 
        private static DeclarationModifiers MakeModifiers(
            NamedTypeSymbol containingType, ConstructorDeclarationSyntax syntax, MethodKind methodKind, bool hasBody, Location location, BindingDiagnosticBag diagnostics,
            out bool modifierErrors, out bool hasExplicitAccessModifier, out bool report_ERR_StaticConstructorWithAccessModifiers)
        {
            var defaultAccess = (methodKind == MethodKind.StaticConstructor) ? DeclarationModifiers.None : DeclarationModifiers.Private;
 
            // Check that the set of modifiers is allowed
            DeclarationModifiers allowedModifiers =
                DeclarationModifiers.AccessibilityMask |
                DeclarationModifiers.Static |
                DeclarationModifiers.Extern |
                DeclarationModifiers.Unsafe;
 
            if (methodKind == MethodKind.Constructor)
            {
                allowedModifiers |= DeclarationModifiers.Partial;
            }
 
            bool isInterface = containingType.IsInterface;
            var mods = ModifierUtils.MakeAndCheckNonTypeMemberModifiers(isOrdinaryMethod: false, isForInterfaceMember: isInterface, syntax.Modifiers, defaultAccess, allowedModifiers, location, diagnostics, out modifierErrors, out hasExplicitAccessModifier);
 
            report_ERR_StaticConstructorWithAccessModifiers = false;
            if (methodKind == MethodKind.StaticConstructor)
            {
                // Don't report ERR_StaticConstructorWithAccessModifiers if the ctor symbol name doesn't match the containing type name.
                // This avoids extra unnecessary errors.
                // There will already be a diagnostic saying Method must have a return type.
                if ((mods & DeclarationModifiers.AccessibilityMask) != 0 &&
                    containingType.Name == syntax.Identifier.ValueText)
                {
                    mods = mods & ~DeclarationModifiers.AccessibilityMask;
                    report_ERR_StaticConstructorWithAccessModifiers = true;
                    modifierErrors = true;
                }
 
                mods |= DeclarationModifiers.Private; // we mark static constructors private in the symbol table
 
                if (isInterface)
                {
                    ModifierUtils.ReportDefaultInterfaceImplementationModifiers(hasBody, mods,
                                                                                DeclarationModifiers.Extern,
                                                                                location, diagnostics);
                }
            }
 
            return mods;
        }
 
        private void CheckModifiers(MethodKind methodKind, bool hasBody, Location location, BindingDiagnosticBag diagnostics)
        {
            if (!hasBody && !IsExtern && !IsPartial)
            {
                diagnostics.Add(ErrorCode.ERR_ConcreteMissingBody, location, this);
            }
            else if (ContainingType.IsSealed && this.DeclaredAccessibility.HasProtected() && !this.IsOverride)
            {
                diagnostics.Add(AccessCheck.GetProtectedMemberInSealedTypeError(ContainingType), location, this);
            }
            else if (ContainingType.IsStatic && methodKind == MethodKind.Constructor)
            {
                diagnostics.Add(ErrorCode.ERR_ConstructorInStaticClass, location);
            }
            else if (IsPartial && !ContainingType.IsPartial())
            {
                diagnostics.Add(ErrorCode.ERR_PartialMemberOnlyInPartialClass, location);
            }
        }
 
#nullable enable
        internal override OneOrMany<SyntaxList<AttributeListSyntax>> GetAttributeDeclarations()
        {
            // Attributes on partial constructors are owned by the definition part.
            // If this symbol has a non-null PartialDefinitionPart, we should have accessed this method through that definition symbol instead.
            Debug.Assert(PartialDefinitionPart is null);
 
            if (SourcePartialImplementationPart is { } implementationPart)
            {
                return OneOrMany.Create(
                    this.AttributeDeclarationSyntaxList,
                    implementationPart.AttributeDeclarationSyntaxList);
            }
 
            return OneOrMany.Create(this.AttributeDeclarationSyntaxList);
        }
 
        private SyntaxList<AttributeListSyntax> AttributeDeclarationSyntaxList
        {
            get
            {
                if (this.ContainingType is SourceMemberContainerTypeSymbol { AnyMemberHasAttributes: true })
                {
                    return this.GetSyntax().AttributeLists;
                }
 
                return default;
            }
        }
 
        protected override SourceMemberMethodSymbol? BoundAttributesSource => SourcePartialDefinitionPart;
#nullable disable
 
        internal override bool IsNullableAnalysisEnabled()
            => flags.HasThisInitializer
                ? flags.IsNullableAnalysisEnabled
                : ((SourceMemberContainerTypeSymbol)ContainingType).IsNullableEnabledForConstructorsAndInitializers(IsStatic);
 
        protected override bool AllowRefOrOut
        {
            get
            {
                return true;
            }
        }
 
        protected override bool IsWithinExpressionOrBlockBody(int position, out int offset)
        {
            ConstructorDeclarationSyntax ctorSyntax = GetSyntax();
            if (ctorSyntax.Body?.Span.Contains(position) == true)
            {
                offset = position - ctorSyntax.Body.Span.Start;
                return true;
            }
            else if (ctorSyntax.ExpressionBody?.Span.Contains(position) == true)
            {
                offset = position - ctorSyntax.ExpressionBody.Span.Start;
                return true;
            }
 
            offset = -1;
            return false;
        }
 
#nullable enable
        internal sealed override void ForceComplete(SourceLocation? locationOpt, Predicate<Symbol>? filter, CancellationToken cancellationToken)
        {
            SourcePartialImplementationPart?.ForceComplete(locationOpt, filter, cancellationToken);
            base.ForceComplete(locationOpt, filter, cancellationToken);
        }
 
        protected override void PartialConstructorChecks(BindingDiagnosticBag diagnostics)
        {
            if (SourcePartialImplementationPart is { } implementation)
            {
                PartialConstructorChecks(implementation, diagnostics);
            }
        }
 
        private void PartialConstructorChecks(SourceConstructorSymbol implementation, BindingDiagnosticBag diagnostics)
        {
            Debug.Assert(this.IsPartialDefinition);
            Debug.Assert(!ReferenceEquals(this, implementation));
            Debug.Assert(ReferenceEquals(this.OtherPartOfPartial, implementation));
 
            if (MemberSignatureComparer.ConsideringTupleNamesCreatesDifference(this, implementation))
            {
                diagnostics.Add(ErrorCode.ERR_PartialMemberInconsistentTupleNames, implementation.GetFirstLocation(), this, implementation);
            }
            else if (!MemberSignatureComparer.PartialMethodsStrictComparer.Equals(this, implementation)
                || !Parameters.SequenceEqual(implementation.Parameters, static (a, b) => a.Name == b.Name))
            {
                diagnostics.Add(ErrorCode.WRN_PartialMemberSignatureDifference, implementation.GetFirstLocation(),
                    new FormattedSymbol(this, SymbolDisplayFormat.MinimallyQualifiedFormat),
                    new FormattedSymbol(implementation, SymbolDisplayFormat.MinimallyQualifiedFormat));
            }
 
            if (IsUnsafe != implementation.IsUnsafe && this.CompilationAllowsUnsafe())
            {
                diagnostics.Add(ErrorCode.ERR_PartialMemberUnsafeDifference, implementation.GetFirstLocation());
            }
 
            if (this.IsParams() != implementation.IsParams())
            {
                diagnostics.Add(ErrorCode.ERR_PartialMemberParamsDifference, implementation.GetFirstLocation());
            }
 
            if (DeclaredAccessibility != implementation.DeclaredAccessibility
                || HasExplicitAccessModifier != implementation.HasExplicitAccessModifier)
            {
                diagnostics.Add(ErrorCode.ERR_PartialMemberAccessibilityDifference, implementation.GetFirstLocation());
            }
 
            Debug.Assert(this.ParameterCount == implementation.ParameterCount);
            for (var i = 0; i < this.ParameterCount; i++)
            {
                // An error is only reported for a modifier difference here, regardless of whether the difference is safe or not.
                // Presence of UnscopedRefAttribute is also not considered when checking partial signatures, because when the attribute is used, it will affect both parts the same way.
                var definitionParameter = (SourceParameterSymbol)this.Parameters[i];
                var implementationParameter = (SourceParameterSymbol)implementation.Parameters[i];
                if (definitionParameter.DeclaredScope != implementationParameter.DeclaredScope)
                {
                    diagnostics.Add(ErrorCode.ERR_ScopedMismatchInParameterOfPartial, implementation.GetFirstLocation(), new FormattedSymbol(implementation.Parameters[i], SymbolDisplayFormat.ShortFormat));
                }
            }
        }
 
        public sealed override bool IsExtern => PartialImplementationPart is { } implementation ? implementation.IsExtern : HasExternModifier;
 
        private bool HasAnyBody => flags.HasAnyBody;
 
        private bool HasExplicitAccessModifier => flags.HasExplicitAccessModifier;
 
        internal bool IsPartialDefinition => IsPartial && !HasAnyBody && !HasExternModifier;
 
        internal bool IsPartialImplementation => IsPartial && (HasAnyBody || HasExternModifier);
 
        internal SourceConstructorSymbol? OtherPartOfPartial => _otherPartOfPartial;
 
        internal SourceConstructorSymbol? SourcePartialDefinitionPart => IsPartialImplementation ? OtherPartOfPartial : null;
 
        internal SourceConstructorSymbol? SourcePartialImplementationPart => IsPartialDefinition ? OtherPartOfPartial : null;
 
        public sealed override MethodSymbol? PartialDefinitionPart => SourcePartialDefinitionPart;
 
        public sealed override MethodSymbol? PartialImplementationPart => SourcePartialImplementationPart;
 
        internal static void InitializePartialConstructorParts(SourceConstructorSymbol definition, SourceConstructorSymbol implementation)
        {
            Debug.Assert(definition.IsPartialDefinition);
            Debug.Assert(implementation.IsPartialImplementation);
 
            Debug.Assert(definition._otherPartOfPartial is not { } alreadySetImplPart || ReferenceEquals(alreadySetImplPart, implementation));
            Debug.Assert(implementation._otherPartOfPartial is not { } alreadySetDefPart || ReferenceEquals(alreadySetDefPart, definition));
 
            definition._otherPartOfPartial = implementation;
            implementation._otherPartOfPartial = definition;
 
            Debug.Assert(ReferenceEquals(definition._otherPartOfPartial, implementation));
            Debug.Assert(ReferenceEquals(implementation._otherPartOfPartial, definition));
        }
    }
}