File: Binder\Binder_Initializers.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.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.CSharp
{
    internal partial class Binder
    {
        internal struct ProcessedFieldInitializers
        {
            internal ImmutableArray<BoundInitializer> BoundInitializers { get; set; }
            internal BoundStatement? LoweredInitializers { get; set; }
            internal bool HasErrors { get; set; }
            internal ImportChain? FirstImportChain { get; set; }
        }
 
        internal static void BindFieldInitializers(
            CSharpCompilation compilation,
            SynthesizedInteractiveInitializerMethod? scriptInitializerOpt,
            ImmutableArray<ImmutableArray<FieldOrPropertyInitializer>> fieldInitializers,
            BindingDiagnosticBag diagnostics,
            ref ProcessedFieldInitializers processedInitializers)
        {
            var diagsForInstanceInitializers = BindingDiagnosticBag.GetInstance(withDiagnostics: true, diagnostics.AccumulatesDependencies);
            ImportChain? firstImportChain;
            processedInitializers.BoundInitializers = BindFieldInitializers(compilation, scriptInitializerOpt, fieldInitializers, diagsForInstanceInitializers, out firstImportChain);
            processedInitializers.HasErrors = diagsForInstanceInitializers.HasAnyErrors();
            processedInitializers.FirstImportChain = firstImportChain;
            diagnostics.AddRange(diagsForInstanceInitializers);
            diagsForInstanceInitializers.Free();
        }
 
        internal static ImmutableArray<BoundInitializer> BindFieldInitializers(
            CSharpCompilation compilation,
            SynthesizedInteractiveInitializerMethod? scriptInitializerOpt,
            ImmutableArray<ImmutableArray<FieldOrPropertyInitializer>> initializers,
            BindingDiagnosticBag diagnostics,
            out ImportChain? firstImportChain)
        {
            if (initializers.IsEmpty)
            {
                firstImportChain = null;
                return ImmutableArray<BoundInitializer>.Empty;
            }
 
            var boundInitializers = ArrayBuilder<BoundInitializer>.GetInstance();
            if (scriptInitializerOpt is null)
            {
                BindRegularCSharpFieldInitializers(compilation, initializers, boundInitializers, diagnostics, out firstImportChain);
            }
            else
            {
                BindScriptFieldInitializers(compilation, scriptInitializerOpt, initializers, boundInitializers, diagnostics, out firstImportChain);
            }
            return boundInitializers.ToImmutableAndFree();
        }
 
        /// <summary>
        /// In regular C#, all field initializers are assignments to fields and the assigned expressions
        /// may not reference instance members.
        /// </summary>
        internal static void BindRegularCSharpFieldInitializers(
            CSharpCompilation compilation,
            ImmutableArray<ImmutableArray<FieldOrPropertyInitializer>> initializers,
            ArrayBuilder<BoundInitializer> boundInitializers,
            BindingDiagnosticBag diagnostics,
            out ImportChain? firstDebugImports)
        {
            firstDebugImports = null;
 
            foreach (ImmutableArray<FieldOrPropertyInitializer> siblingInitializers in initializers)
            {
                // All sibling initializers share the same parent node and tree so we can reuse the binder 
                // factory across siblings.  Unfortunately, we cannot reuse the binder itself, because
                // individual fields might have their own binders (e.g. because of being declared unsafe).
                BinderFactory? binderFactory = null;
 
                foreach (FieldOrPropertyInitializer initializer in siblingInitializers)
                {
                    FieldSymbol fieldSymbol = initializer.FieldOpt;
                    Debug.Assert((object)fieldSymbol != null);
 
                    // A constant field of type decimal needs a field initializer, so
                    // check if it is a metadata constant, not just a constant to exclude
                    // decimals. Other constants do not need field initializers.
                    if (!fieldSymbol.IsMetadataConstant)
                    {
                        //Can't assert that this is a regular C# compilation, because we could be in a nested type of a script class.
                        SyntaxReference syntaxRef = initializer.Syntax;
 
                        switch (syntaxRef.GetSyntax())
                        {
                            case EqualsValueClauseSyntax initializerNode:
                                if (binderFactory == null)
                                {
                                    binderFactory = compilation.GetBinderFactory(syntaxRef.SyntaxTree);
                                }
 
                                Binder parentBinder = binderFactory.GetBinder(initializerNode);
 
                                if (firstDebugImports == null)
                                {
                                    firstDebugImports = parentBinder.ImportChain;
                                }
 
                                parentBinder = parentBinder.GetFieldInitializerBinder(fieldSymbol);
 
                                BoundFieldEqualsValue boundInitializer = BindFieldInitializer(parentBinder, fieldSymbol, initializerNode, diagnostics);
                                boundInitializers.Add(boundInitializer);
                                break;
 
                            case ParameterSyntax parameterSyntax: // Initializer for a generated property based on record parameters
 
                                if (firstDebugImports == null)
                                {
                                    if (binderFactory == null)
                                    {
                                        binderFactory = compilation.GetBinderFactory(syntaxRef.SyntaxTree);
                                    }
 
                                    firstDebugImports = binderFactory.GetBinder(parameterSyntax).ImportChain;
                                }
 
                                boundInitializers.Add(new BoundFieldEqualsValue(parameterSyntax, fieldSymbol, ImmutableArray<LocalSymbol>.Empty,
                                                                                new BoundParameter(parameterSyntax,
                                                                                                   ((SynthesizedRecordPropertySymbol)fieldSymbol.AssociatedSymbol).BackingParameter).MakeCompilerGenerated()));
                                break;
 
                            default:
                                throw ExceptionUtilities.Unreachable();
                        }
                    }
                }
            }
        }
 
        internal Binder GetFieldInitializerBinder(FieldSymbol fieldSymbol, bool suppressBinderFlagsFieldInitializer = false)
        {
            Debug.Assert((ContainingMemberOrLambda is TypeSymbol containing && TypeSymbol.Equals(containing, fieldSymbol.ContainingType, TypeCompareKind.ConsiderEverything2)) || //should be the binder for the type
                    fieldSymbol.ContainingType.IsImplicitClass); //however, we also allow fields in namespaces to help support script scenarios
 
            Binder binder = this;
 
            binder = new WithPrimaryConstructorParametersBinder(fieldSymbol.ContainingType, binder);
 
            return new LocalScopeBinder(binder).WithAdditionalFlagsAndContainingMemberOrLambda(suppressBinderFlagsFieldInitializer ? BinderFlags.None : BinderFlags.FieldInitializer, fieldSymbol);
        }
 
        /// <summary>
        /// In script C#, some field initializers are assignments to fields and others are global
        /// statements.  There are no restrictions on accessing instance members.
        /// </summary>
        private static void BindScriptFieldInitializers(
            CSharpCompilation compilation,
            SynthesizedInteractiveInitializerMethod scriptInitializer,
            ImmutableArray<ImmutableArray<FieldOrPropertyInitializer>> initializers,
            ArrayBuilder<BoundInitializer> boundInitializers,
            BindingDiagnosticBag diagnostics,
            out ImportChain? firstDebugImports)
        {
            firstDebugImports = null;
 
            for (int i = 0; i < initializers.Length; i++)
            {
                ImmutableArray<FieldOrPropertyInitializer> siblingInitializers = initializers[i];
 
                // All sibling initializers share the same parent node and tree so we can reuse the binder 
                // factory across siblings.  Unfortunately, we cannot reuse the binder itself, because
                // individual fields might have their own binders (e.g. because of being declared unsafe).
                BinderFactory? binderFactory = null;
                // Label instances must be shared across all global statements.
                ScriptLocalScopeBinder.Labels? labels = null;
 
                for (int j = 0; j < siblingInitializers.Length; j++)
                {
                    var initializer = siblingInitializers[j];
                    var fieldSymbol = initializer.FieldOpt;
 
                    if ((object)fieldSymbol != null && fieldSymbol.IsConst)
                    {
                        // Constants do not need field initializers.
                        continue;
                    }
 
                    var syntaxRef = initializer.Syntax;
                    var syntaxTree = syntaxRef.SyntaxTree;
                    Debug.Assert(syntaxTree.Options.Kind != SourceCodeKind.Regular);
 
                    var syntax = (CSharpSyntaxNode)syntaxRef.GetSyntax();
                    var syntaxRoot = syntaxTree.GetCompilationUnitRoot();
 
                    if (binderFactory == null)
                    {
                        binderFactory = compilation.GetBinderFactory(syntaxTree);
                        labels = new ScriptLocalScopeBinder.Labels(scriptInitializer, syntaxRoot);
                    }
 
                    Binder scriptClassBinder = binderFactory.GetBinder(syntax);
                    Debug.Assert(scriptClassBinder.ContainingMemberOrLambda is NamedTypeSymbol { IsScriptClass: true });
 
                    if (firstDebugImports == null)
                    {
                        firstDebugImports = scriptClassBinder.ImportChain;
                    }
 
                    Binder parentBinder = new ExecutableCodeBinder(
                        syntaxRoot,
                        scriptInitializer,
                        new ScriptLocalScopeBinder(labels, scriptClassBinder));
 
                    BoundInitializer boundInitializer;
                    if ((object?)fieldSymbol != null)
                    {
                        boundInitializer = BindFieldInitializer(
                            parentBinder.WithAdditionalFlagsAndContainingMemberOrLambda(BinderFlags.FieldInitializer, fieldSymbol),
                            fieldSymbol,
                            (EqualsValueClauseSyntax)syntax,
                            diagnostics);
                    }
                    else
                    {
                        boundInitializer = BindGlobalStatement(
                            parentBinder,
                            scriptInitializer,
                            (StatementSyntax)syntax,
                            diagnostics,
                            isLast: i == initializers.Length - 1 && j == siblingInitializers.Length - 1);
                    }
 
                    boundInitializers.Add(boundInitializer);
                }
            }
        }
 
        private static BoundInitializer BindGlobalStatement(
            Binder binder,
            SynthesizedInteractiveInitializerMethod scriptInitializer,
            StatementSyntax statementNode,
            BindingDiagnosticBag diagnostics,
            bool isLast)
        {
            var statement = binder.BindStatement(statementNode, diagnostics);
            if (isLast && !statement.HasAnyErrors)
            {
                // the result of the last global expression is assigned to the result storage for submission result:
                if (binder.Compilation.IsSubmission)
                {
                    // insert an implicit conversion for the submission return type (if needed):
                    var expression = InitializerRewriter.GetTrailingScriptExpression(statement);
                    if (expression != null &&
                        ((object?)expression.Type == null || !expression.Type.IsVoidType()))
                    {
                        var submissionResultType = scriptInitializer.ResultType;
                        expression = binder.GenerateConversionForAssignment(submissionResultType, expression, diagnostics);
                        statement = new BoundExpressionStatement(statement.Syntax, expression, expression.HasErrors);
                    }
                }
 
                // don't allow trailing expressions after labels (as in regular C#, labels must be followed by a statement):
                if (statement.Kind == BoundKind.LabeledStatement)
                {
                    var labeledStatementBody = ((BoundLabeledStatement)statement).Body;
                    while (labeledStatementBody.Kind == BoundKind.LabeledStatement)
                    {
                        labeledStatementBody = ((BoundLabeledStatement)labeledStatementBody).Body;
                    }
 
                    if (InitializerRewriter.GetTrailingScriptExpression(labeledStatementBody) != null)
                    {
                        Error(diagnostics, ErrorCode.ERR_SemicolonExpected, ((ExpressionStatementSyntax)labeledStatementBody.Syntax).SemicolonToken);
                    }
                }
            }
 
            return new BoundGlobalStatementInitializer(statementNode, statement);
        }
 
        private static BoundFieldEqualsValue BindFieldInitializer(Binder binder, FieldSymbol fieldSymbol, EqualsValueClauseSyntax equalsValueClauseNode,
            BindingDiagnosticBag diagnostics)
        {
            Debug.Assert(!fieldSymbol.IsMetadataConstant);
 
            var fieldsBeingBound = binder.FieldsBeingBound;
 
            var sourceField = fieldSymbol as SourceMemberFieldSymbolFromDeclarator;
            bool isImplicitlyTypedField = (object?)sourceField != null && sourceField.FieldTypeInferred(fieldsBeingBound);
 
            // If the type is implicitly typed, the initializer diagnostics have already been reported, so ignore them here:
            // CONSIDER (tomat): reusing the bound field initializers for implicitly typed fields.
            BindingDiagnosticBag initializerDiagnostics;
            if (isImplicitlyTypedField)
            {
                initializerDiagnostics = BindingDiagnosticBag.Discarded;
            }
            else
            {
                initializerDiagnostics = diagnostics;
            }
 
            binder = new ExecutableCodeBinder(equalsValueClauseNode, fieldSymbol, new LocalScopeBinder(binder));
            BoundFieldEqualsValue boundInitValue = binder.BindWithLambdaBindingCountDiagnostics(
                equalsValueClauseNode,
                fieldSymbol,
                initializerDiagnostics,
                static (binder, equalsValueClauseNode, fieldSymbol, initializerDiagnostics) => binder.BindFieldInitializer(fieldSymbol, equalsValueClauseNode, initializerDiagnostics));
 
            return boundInitValue;
        }
    }
}