File: FlowAnalysis\AbstractFlowPass_LocalFunctions.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 Microsoft.CodeAnalysis.CSharp.Symbols;
 
namespace Microsoft.CodeAnalysis.CSharp
{
    internal partial class AbstractFlowPass<TLocalState, TLocalFunctionState>
    {
        internal abstract class AbstractLocalFunctionState
        {
            /// <summary>
            /// This is the state from the local function which makes the
            /// current state less specific. For example, in nullable analysis
            /// this would be captured variables that may be nullable after
            /// calling the local function. When a local function is called,
            /// this state is <see cref="Join(ref TLocalState, ref TLocalState)"/>
            /// with the current state.
            /// </summary>
            public TLocalState StateFromBottom;
 
            /// <summary>
            /// This is the part of the local function transfer function which
            /// transfers knowledge additively. For example, in definite
            /// assignment this would be captured state which is assigned by
            /// the local function.  When a local function is called, this
            /// state is <see cref="Meet(ref TLocalState, ref TLocalState)"/>
            /// with the current state.
            /// </summary>
            public TLocalState StateFromTop;
 
            public AbstractLocalFunctionState(TLocalState stateFromBottom, TLocalState stateFromTop)
            {
                StateFromBottom = stateFromBottom;
                StateFromTop = stateFromTop;
            }
 
            public bool Visited = false;
        }
 
        protected abstract TLocalFunctionState CreateLocalFunctionState(LocalFunctionSymbol symbol);
 
        private SmallDictionary<LocalFunctionSymbol, TLocalFunctionState>? _localFuncVarUsages = null;
 
        protected TLocalFunctionState GetOrCreateLocalFuncUsages(LocalFunctionSymbol localFunc)
        {
            _localFuncVarUsages ??= new SmallDictionary<LocalFunctionSymbol, TLocalFunctionState>();
 
            if (!_localFuncVarUsages.TryGetValue(localFunc, out TLocalFunctionState? usages))
            {
                usages = CreateLocalFunctionState(localFunc);
                _localFuncVarUsages[localFunc] = usages;
            }
            return usages;
        }
 
        protected bool HasLocalFuncUsagesCreated(LocalFunctionSymbol localFunc)
        {
            return _localFuncVarUsages?.ContainsKey(localFunc) == true;
        }
 
        public override BoundNode? VisitLocalFunctionStatement(BoundLocalFunctionStatement localFunc)
        {
            if (localFunc.Symbol.IsExtern)
            {
                // an extern local function is not permitted to have a body and thus shouldn't be flow analyzed
                return null;
            }
 
            var oldSymbol = this.CurrentSymbol;
            var localFuncSymbol = localFunc.Symbol;
            this.CurrentSymbol = localFuncSymbol;
 
            var oldPending = SavePending(); // we do not support branches into a lambda
 
            // SPEC: The entry point to a local function is always reachable.
            // Captured variables are definitely assigned if they are definitely assigned on
            // all branches into the local function.
 
            var savedState = this.State;
            this.State = this.TopState();
 
            Optional<TLocalState> savedNonMonotonicState = NonMonotonicState;
            if (_nonMonotonicTransfer)
            {
                NonMonotonicState = ReachableBottomState();
            }
 
            if (!localFunc.WasCompilerGenerated) EnterParameters(localFuncSymbol.Parameters);
 
            // State changes to captured variables are recorded, as calls to local functions
            // transition the state of captured variables if the variables have state changes
            // across all branches leaving the local function
 
            var localFunctionState = GetOrCreateLocalFuncUsages(localFuncSymbol);
            var savedLocalFunctionState = LocalFunctionStart(localFunctionState);
 
            var oldPending2 = SavePending();
 
            // If this is an iterator, there's an implicit branch before the first statement
            // of the function where the enumerable is returned.
            if (localFuncSymbol.IsIterator)
            {
                PendingBranches.Add(new PendingBranch(null, this.State, null));
            }
 
            VisitAlways(localFunc.Body);
            RestorePending(oldPending2); // process any forward branches within the lambda body
            ImmutableArray<PendingBranch> pendingReturns = RemoveReturns();
            RestorePending(oldPending);
 
            Location? location = localFuncSymbol.TryGetFirstLocation();
 
            LeaveParameters(localFuncSymbol.Parameters, localFunc.Syntax, location);
 
            // Intersect the state of all branches out of the local function
            var stateAtReturn = this.State;
            foreach (PendingBranch pending in pendingReturns)
            {
                this.State = pending.State;
                BoundNode branch = pending.Branch;
 
                // Pass the local function identifier as a location if the branch
                // is null or compiler generated.
                LeaveParameters(localFuncSymbol.Parameters,
                  branch?.Syntax,
                  branch?.WasCompilerGenerated == false ? null : location);
 
                Join(ref stateAtReturn, ref this.State);
            }
 
            // Record any changes to the state of captured variables
            if (RecordStateChange(
                    savedLocalFunctionState,
                    localFunctionState,
                    ref stateAtReturn) &&
                localFunctionState.Visited)
            {
                // If the sets have changed and we already used the results
                // of this local function in another computation, the previous
                // calculations may be invalid. We need to analyze until we
                // reach a fixed-point. 
                stateChangedAfterUse = true;
                localFunctionState.Visited = false;
            }
 
            this.State = savedState;
            NonMonotonicState = savedNonMonotonicState;
            this.CurrentSymbol = oldSymbol;
 
            return null;
        }
 
        private bool RecordStateChange(
            TLocalFunctionState savedState,
            TLocalFunctionState currentState,
            ref TLocalState stateAtReturn)
        {
            bool anyChanged = LocalFunctionEnd(savedState, currentState, ref stateAtReturn);
            anyChanged |= Join(ref currentState.StateFromTop, ref stateAtReturn);
 
            if (NonMonotonicState.HasValue)
            {
                var value = NonMonotonicState.Value;
                // Since only state moving up gets stored in the non-monotonic state,
                // Meet with the stateAtReturn, which records all state changes. If
                // a state moved up, then down, the final state should be down.
                Meet(ref value, ref stateAtReturn);
                anyChanged |= Join(ref currentState.StateFromBottom, ref value);
            }
            return anyChanged;
        }
 
        /// <summary>
        /// Executed at the start of visiting a local function body. The <paramref name="state"/>
        /// parameter holds the current state information for the local function being visited. To
        /// save state information across the analysis, return an instance of <typeparamref name="TLocalFunctionState"/>.
        /// </summary>
        protected virtual TLocalFunctionState LocalFunctionStart(TLocalFunctionState state) => state;
 
        /// <summary>
        /// Executed after visiting a local function body. The <paramref name="savedState"/> is the
        /// return value from <see cref="LocalFunctionStart(TLocalFunctionState)"/>. The <paramref name="currentState"/>
        /// is state information for the local function that was just visited. <paramref name="stateAtReturn"/> is
        /// the state after visiting the method.
        /// </summary>
        protected virtual bool LocalFunctionEnd(
            TLocalFunctionState savedState,
            TLocalFunctionState currentState,
            ref TLocalState stateAtReturn)
        {
            return false;
        }
    }
}