File: FlowAnalysis\VariablesDeclaredWalker.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.Generic;
using System.Diagnostics;
using System.Linq;
using Microsoft.CodeAnalysis.Collections;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.CSharp
{
    /// <summary>
    /// A region analysis walker that records declared variables.
    /// </summary>
    internal class VariablesDeclaredWalker : AbstractRegionControlFlowPass
    {
        internal static IEnumerable<Symbol> Analyze(CSharpCompilation compilation, Symbol member, BoundNode node, BoundNode firstInRegion, BoundNode lastInRegion)
        {
            var walker = new VariablesDeclaredWalker(compilation, member, node, firstInRegion, lastInRegion);
            try
            {
                bool badRegion = false;
                walker.Analyze(ref badRegion);
                return badRegion ? SpecializedCollections.EmptyEnumerable<Symbol>() : walker._variablesDeclared;
            }
            finally
            {
                walker.Free();
            }
        }
 
        private HashSet<Symbol> _variablesDeclared = new HashSet<Symbol>();
 
        internal VariablesDeclaredWalker(CSharpCompilation compilation, Symbol member, BoundNode node, BoundNode firstInRegion, BoundNode lastInRegion)
            : base(compilation, member, node, firstInRegion, lastInRegion)
        {
        }
 
        protected override void Free()
        {
            base.Free();
            _variablesDeclared = null!;
        }
 
        public override void VisitPattern(BoundPattern pattern)
        {
            NoteDeclaredPatternVariables(pattern);
            base.VisitPattern(pattern);
        }
 
        protected override void VisitSwitchSection(BoundSwitchSection node, bool isLastSection)
        {
            foreach (var label in node.SwitchLabels)
            {
                NoteDeclaredPatternVariables(label.Pattern);
            }
 
            base.VisitSwitchSection(node, isLastSection);
        }
 
        /// <summary>
        /// Record declared variables in the pattern.
        /// </summary>
        private void NoteDeclaredPatternVariables(BoundPattern pattern)
        {
            switch (pattern)
            {
                case BoundDeclarationPattern declarationPattern:
                    noteOneVariable(declarationPattern.Variable);
                    break;
 
                case BoundRecursivePattern recursivePattern:
                    foreach (var subpattern in recursivePattern.Deconstruction.NullToEmpty())
                        NoteDeclaredPatternVariables(subpattern.Pattern);
 
                    foreach (var subpattern in recursivePattern.Properties.NullToEmpty())
                        NoteDeclaredPatternVariables(subpattern.Pattern);
 
                    noteOneVariable(recursivePattern.Variable);
                    break;
 
                case BoundITuplePattern ituplePattern:
                    foreach (var subpattern in ituplePattern.Subpatterns)
                        NoteDeclaredPatternVariables(subpattern.Pattern);
 
                    break;
 
                case BoundListPattern listPattern:
                    foreach (var elementPattern in listPattern.Subpatterns)
                        NoteDeclaredPatternVariables(elementPattern);
 
                    noteOneVariable(listPattern.Variable);
                    break;
 
                case BoundConstantPattern constantPattern:
                    // It is possible for the region to be the expression within a pattern.
                    VisitRvalue(constantPattern.Value);
                    break;
 
                case BoundRelationalPattern relationalPattern:
                    // It is possible for the region to be the expression within a pattern.
                    VisitRvalue(relationalPattern.Value);
                    break;
 
                case BoundNegatedPattern negatedPattern:
                    NoteDeclaredPatternVariables(negatedPattern.Negated);
                    break;
 
                case BoundSlicePattern slicePattern:
                    if (slicePattern.Pattern != null)
                        NoteDeclaredPatternVariables(slicePattern.Pattern);
 
                    break;
 
                case BoundDiscardPattern or BoundTypePattern:
                    // Does not contain variables or expressions. Nothing to visit.
                    break;
 
                case BoundBinaryPattern:
                    {
                        var binaryPattern = (BoundBinaryPattern)pattern;
                        if (binaryPattern.Left is not BoundBinaryPattern)
                        {
                            NoteDeclaredPatternVariables(binaryPattern.Left);
                            NoteDeclaredPatternVariables(binaryPattern.Right);
                            break;
                        }
 
                        // Use an explicit stack to avoid crashing on deeply nested binary patterns.
                        // Binary patterns are left-associative, so, a nested pattern like: 'A or B or C or D or E'
                        // is parsed/bound like: '((((A or B) or C) or D) or E)'
                        // 1) Push the binary patterns onto stack from outermost to innermost.
                        // 2) Pop the innermost binary off the stack, and visit its left and right (corresponding to A and B in above example).
                        // 3) Continue popping binaries off the stack, visiting each right operand (corresponding to C, D, E, ...).
                        var stack = ArrayBuilder<BoundBinaryPattern>.GetInstance();
                        do
                        {
                            stack.Push(binaryPattern);
                            binaryPattern = binaryPattern.Left as BoundBinaryPattern;
                        } while (binaryPattern is not null);
 
                        binaryPattern = stack.Pop();
                        NoteDeclaredPatternVariables(binaryPattern.Left);
 
                        do
                        {
                            NoteDeclaredPatternVariables(binaryPattern.Right);
                        } while (stack.TryPop(out binaryPattern));
 
                        stack.Free();
                        break;
                    }
                default:
                    throw ExceptionUtilities.UnexpectedValue(pattern.Kind);
            }
 
            void noteOneVariable(Symbol? symbol)
            {
                if (IsInside && symbol?.Kind == SymbolKind.Local)
                {
                    // Because this API only returns local symbols and parameters,
                    // we exclude pattern variables that have become fields in scripts.
                    _variablesDeclared.Add(symbol);
                }
            }
        }
 
        public override BoundNode? VisitLocalDeclaration(BoundLocalDeclaration node)
        {
            if (IsInside)
            {
                _variablesDeclared.Add(node.LocalSymbol);
            }
 
            return base.VisitLocalDeclaration(node);
        }
 
        public override BoundNode? VisitLambda(BoundLambda node)
        {
            if (IsInside && !node.WasCompilerGenerated)
            {
                foreach (var parameter in node.Symbol.Parameters)
                {
                    _variablesDeclared.Add(parameter);
                }
            }
 
            return base.VisitLambda(node);
        }
 
        public override BoundNode? VisitLocalFunctionStatement(BoundLocalFunctionStatement node)
        {
            if (IsInside && !node.WasCompilerGenerated)
            {
                foreach (var parameter in node.Symbol.Parameters)
                {
                    _variablesDeclared.Add(parameter);
                }
            }
 
            return base.VisitLocalFunctionStatement(node);
        }
 
        public override void VisitForEachIterationVariables(BoundForEachStatement node)
        {
            if (IsInside)
            {
                var deconstructionAssignment = node.DeconstructionOpt?.DeconstructionAssignment;
 
                if (deconstructionAssignment == null)
                {
                    _variablesDeclared.AddAll(node.IterationVariables);
                }
                else
                {
                    // Deconstruction foreach declares multiple variables.
                    ((BoundTupleExpression)deconstructionAssignment.Left).VisitAllElements((x, self) => self.Visit(x), this);
                }
            }
        }
 
        public override BoundNode? VisitCatchBlock(BoundCatchBlock catchBlock)
        {
            if (IsInside)
            {
                var local = catchBlock.Locals.FirstOrDefault();
 
                if (local?.DeclarationKind == LocalDeclarationKind.CatchVariable)
                {
                    _variablesDeclared.Add(local);
                }
            }
 
            base.VisitCatchBlock(catchBlock);
 
            return null;
        }
 
        public override BoundNode? VisitQueryClause(BoundQueryClause node)
        {
            if (IsInside)
            {
                if ((object?)node.DefinedSymbol != null)
                {
                    _variablesDeclared.Add(node.DefinedSymbol);
                }
            }
 
            return base.VisitQueryClause(node);
        }
 
        protected override void VisitLvalue(BoundLocal node)
        {
            VisitLocal(node);
        }
 
        public override BoundNode? VisitLocal(BoundLocal node)
        {
            if (IsInside && node.DeclarationKind != BoundLocalDeclarationKind.None)
            {
                _variablesDeclared.Add(node.LocalSymbol);
            }
 
            return null;
        }
    }
}