File: Binder\Binder.QueryTranslationState.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 Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.PooledObjects;
 
namespace Microsoft.CodeAnalysis.CSharp
{
    internal partial class Binder
    {
        private class QueryTranslationState
        {
            // Represents the current translation state for a query.  Consider a query of the form
            // from ID in EXPR { clauses } SELECT ...
 
#if DEBUG
            /// <summary>
            /// For debug assert only
            /// </summary>
            public string nextInvokedMethodName;
#endif
 
            // EXPR, above
            public BoundExpression fromExpression;
 
            // ID, above.  This may possibly be the special "transparent" identifier
            public RangeVariableSymbol rangeVariable;
 
            // the clauses, above.  The top of the stack is the leftmost clause
            public readonly Stack<QueryClauseSyntax> clauses = new Stack<QueryClauseSyntax>();
 
            // the SELECT clause above (or a groupby clause in its place)
            public SelectOrGroupClauseSyntax selectOrGroup;
 
            // all query variables in scope, including those visible through transparent identifiers
            // introduced in previous translation phases.  Every query variable in scope is a key in
            // this dictionary.  To compute its value, one consults the list of strings that are the
            // value in this map.  If it is empty, there is a lambda parameter for the variable.  If it
            // is nonempty, one starts with the transparent lambda identifier, and follows fields of the
            // given names in reverse order.  So, for example, if the strings are ["a", "b"], the query
            // variable is represented by the expression TRANSPARENT.b.a where TRANSPARENT is a parameter
            // of the current lambda expression.
            public readonly Dictionary<RangeVariableSymbol, ArrayBuilder<string>> allRangeVariables = new Dictionary<RangeVariableSymbol, ArrayBuilder<string>>();
 
            public static RangeVariableMap RangeVariableMap(params RangeVariableSymbol[] parameters)
            {
                var result = new RangeVariableMap();
                foreach (var vars in parameters)
                {
                    result.Add(vars, ImmutableArray<string>.Empty);
                }
                return result;
            }
 
            public RangeVariableMap RangeVariableMap()
            {
                var result = new RangeVariableMap();
                foreach (var vars in allRangeVariables.Keys)
                {
                    result.Add(vars, allRangeVariables[vars].ToImmutable());
                }
                return result;
            }
 
            internal RangeVariableSymbol AddRangeVariable(Binder binder, SyntaxToken identifier, BindingDiagnosticBag diagnostics)
            {
                string name = identifier.ValueText;
                var result = new RangeVariableSymbol(name, binder.ContainingMemberOrLambda, identifier.GetLocation());
                bool error = false;
 
                Debug.Assert(identifier.Parent is { });
 
                foreach (var existingRangeVariable in allRangeVariables.Keys)
                {
                    if (existingRangeVariable.Name == name)
                    {
                        diagnostics.Add(ErrorCode.ERR_QueryDuplicateRangeVariable, identifier.GetLocation(), name);
                        error = true;
                    }
                }
 
                if (!error && (object)diagnostics != BindingDiagnosticBag.Discarded)
                {
                    var collisionDetector = new LocalScopeBinder(binder);
                    collisionDetector.ValidateDeclarationNameConflictsInScope(result, diagnostics);
                }
 
                allRangeVariables.Add(result, ArrayBuilder<string>.GetInstance());
                return result;
            }
 
            // Add a new lambda that is a transparent identifier, by providing the name that is the
            // field of the new transparent lambda parameter that contains the old variables.
            internal void AddTransparentIdentifier(string name)
            {
                foreach (var b in allRangeVariables.Values)
                {
                    b.Add(name);
                }
            }
 
            private int _nextTransparentIdentifierNumber;
 
            internal string TransparentRangeVariableName()
            {
                return transparentIdentifierPrefix + _nextTransparentIdentifierNumber++;
            }
 
            internal RangeVariableSymbol TransparentRangeVariable(Binder binder)
            {
                var transparentIdentifier = TransparentRangeVariableName();
                return new RangeVariableSymbol(transparentIdentifier, binder.ContainingMemberOrLambda, null, true);
            }
 
            public void Clear()
            {
#if DEBUG
                Debug.Assert(nextInvokedMethodName is null);
                nextInvokedMethodName = null;
#endif
 
                fromExpression = null;
                rangeVariable = null;
                selectOrGroup = null;
                foreach (var b in allRangeVariables.Values) b.Free();
                allRangeVariables.Clear();
                clauses.Clear();
            }
 
            public void Free()
            {
                Clear();
            }
        }
    }
}