File: Binder\Binder.IdentifierUsedAsValueFinder.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.Diagnostics.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.CSharp
{
    partial class Binder
    {
        internal abstract class IdentifierUsedAsValueFinder
        {
            private LookupResult? _lookupResult;
 
            protected void Free()
            {
                _lookupResult?.Free();
            }
 
            protected bool CheckIdentifiersInNode(CSharpSyntaxNode? node, Binder binder)
            {
                if (node == null)
                {
                    return true;
                }
 
                var nodesOfInterest = node.DescendantNodesAndSelf(descendIntoChildren: childrenNeedChecking, descendIntoTrivia: false);
 
                foreach (var n in nodesOfInterest)
                {
                    Binder enclosingBinder = getEnclosingBinderForNode(contextNode: node, contextBinder: binder, n);
 
                    switch (n)
                    {
                        case AnonymousFunctionExpressionSyntax lambdaSyntax:
                            if (!CheckLambda(lambdaSyntax, enclosingBinder))
                            {
                                return false;
                            }
 
                            break;
 
                        case IdentifierNameSyntax id:
 
                            switch (id.Parent)
                            {
                                case MemberAccessExpressionSyntax memberAccess:
                                    if (memberAccess.Expression != id)
                                    {
                                        continue;
                                    }
                                    break;
 
                                case QualifiedNameSyntax qualifiedName:
                                    if (qualifiedName.Left != id)
                                    {
                                        continue;
                                    }
                                    break;
 
                                case AssignmentExpressionSyntax assignment:
                                    if (assignment.Left == id &&
                                        assignment.Parent?.Kind() is SyntaxKind.ObjectInitializerExpression or SyntaxKind.WithInitializerExpression)
                                    {
                                        continue;
                                    }
                                    break;
                            }
 
                            if (SyntaxFacts.IsInTypeOnlyContext(id) &&
                                !(id.Parent is BinaryExpressionSyntax { RawKind: (int)SyntaxKind.IsExpression } isExpression &&
                                    isExpression.Right == id))
                            {
                                continue;
                            }
 
                            if (!IsIdentifierOfInterest(id))
                            {
                                continue;
                            }
 
                            if (!CheckIdentifier(enclosingBinder, id))
                            {
                                return false;
                            }
 
                            break;
 
                        case QueryExpressionSyntax query:
                            if (!CheckQuery(query, enclosingBinder))
                            {
                                return false;
                            }
 
                            break;
                    }
                }
 
                return true;
 
                static Binder getEnclosingBinderForNode(CSharpSyntaxNode contextNode, Binder contextBinder, SyntaxNode targetNode)
                {
                    while (true)
                    {
                        Binder? enclosingBinder = contextBinder.GetBinder(targetNode);
 
                        if (enclosingBinder is not null)
                        {
                            return enclosingBinder;
                        }
 
                        if (targetNode == contextNode)
                        {
                            return contextBinder;
                        }
 
                        Debug.Assert(targetNode.Parent is not null);
                        targetNode = targetNode.Parent;
                    }
                }
 
                static bool childrenNeedChecking(SyntaxNode n)
                {
                    switch (n)
                    {
                        case MemberBindingExpressionSyntax:
                        case BaseExpressionColonSyntax:
                        case NameEqualsSyntax:
                        case GotoStatementSyntax { RawKind: (int)SyntaxKind.GotoStatement }:
                        case TypeParameterConstraintClauseSyntax:
                        case AliasQualifiedNameSyntax:
                            // These nodes do not have anything interesting for us
                            return false;
 
                        case AttributeListSyntax:
                            // References in attributes, if any, are errors
                            // Skip them
                            return false;
 
                        case ParameterSyntax:
                            // Same as attributes
                            return false;
 
                        case AnonymousFunctionExpressionSyntax:
                        case QueryExpressionSyntax:
                            // Lambdas need special handling
                            return false;
 
                        case ExpressionSyntax expression:
                            if (SyntaxFacts.IsInTypeOnlyContext(expression) &&
                                !(expression.Parent is BinaryExpressionSyntax { RawKind: (int)SyntaxKind.IsExpression } isExpression &&
                                    isExpression.Right == expression))
                            {
                                return false;
                            }
                            break;
                    }
 
                    return true;
                }
            }
 
            protected abstract bool IsIdentifierOfInterest(IdentifierNameSyntax id);
 
            private bool CheckLambda(AnonymousFunctionExpressionSyntax lambdaSyntax, Binder enclosingBinder)
            {
                UnboundLambda unboundLambda = enclosingBinder.AnalyzeAnonymousFunction(lambdaSyntax, BindingDiagnosticBag.Discarded);
                var lambdaBodyBinder = CreateLambdaBodyBinder(enclosingBinder, unboundLambda);
                return CheckIdentifiersInNode(lambdaSyntax.Body, lambdaBodyBinder.GetBinder(lambdaSyntax.Body) ?? lambdaBodyBinder);
            }
 
            private static ExecutableCodeBinder CreateLambdaBodyBinder(Binder enclosingBinder, UnboundLambda unboundLambda)
            {
                unboundLambda.HasExplicitReturnType(out RefKind refKind, out TypeWithAnnotations returnType);
                var lambdaSymbol = new LambdaSymbol(
                                        enclosingBinder,
                                        enclosingBinder.Compilation,
                                        enclosingBinder.ContainingMemberOrLambda!,
                                        unboundLambda,
                                        ImmutableArray<TypeWithAnnotations>.Empty,
                                        ImmutableArray<RefKind>.Empty,
                                        refKind,
                                        returnType);
 
                return new ExecutableCodeBinder(unboundLambda.Syntax, lambdaSymbol, unboundLambda.GetWithParametersBinder(lambdaSymbol, enclosingBinder));
            }
 
            private bool CheckIdentifier(Binder enclosingBinder, IdentifierNameSyntax id)
            {
                Debug.Assert(_lookupResult?.IsClear != false);
                _lookupResult ??= LookupResult.GetInstance();
 
                var useSiteInfo = CompoundUseSiteInfo<AssemblySymbol>.Discarded;
                enclosingBinder.LookupIdentifier(_lookupResult, id, SyntaxFacts.IsInvoked(id), ref useSiteInfo);
 
                return CheckAndClearLookupResult(enclosingBinder, id, _lookupResult);
            }
 
            protected abstract bool CheckAndClearLookupResult(Binder enclosingBinder, IdentifierNameSyntax id, LookupResult lookupResult);
 
            protected static bool isTypeOrValueReceiver(
                Binder enclosingBinder,
                IdentifierNameSyntax id,
                TypeSymbol type,
                [NotNullWhen(true)] out SyntaxNode? memberAccessNode,
                [NotNullWhen(true)] out string? memberName,
                out int targetMemberArity,
                out bool invoked)
            {
                memberAccessNode = null;
                memberName = null;
                targetMemberArity = 0;
                invoked = false;
 
                switch (id.Parent)
                {
                    case MemberAccessExpressionSyntax { RawKind: (int)SyntaxKind.SimpleMemberAccessExpression } memberAccess when memberAccess.Expression == id:
                        var simpleName = memberAccess.Name;
                        memberAccessNode = simpleName;
                        memberName = simpleName.Identifier.ValueText;
                        targetMemberArity = simpleName.Arity;
                        invoked = SyntaxFacts.IsInvoked(memberAccess);
                        break;
                    case QualifiedNameSyntax qualifiedName when qualifiedName.Left == id:
                        simpleName = qualifiedName.Right;
                        memberAccessNode = simpleName;
                        memberName = simpleName.Identifier.ValueText;
                        targetMemberArity = simpleName.Arity;
                        invoked = false;
                        break;
                    case FromClauseSyntax { Parent: QueryExpressionSyntax query } fromClause when query.FromClause == fromClause && fromClause.Expression == id:
                        memberName = GetFirstInvokedMethodName(query, out memberAccessNode);
                        targetMemberArity = 0;
                        invoked = true;
                        break;
                }
 
                return memberAccessNode is not null && enclosingBinder.IsPotentialColorColorReceiver(id, type);
            }
 
            /// <summary>
            /// Follows the logic of <see cref="Binder.BindInstanceMemberAccess"/> and <see cref="Binder.BindMemberOfType"/>
            /// </summary>
            protected static bool TreatAsInstanceMemberAccess(
                Binder enclosingBinder,
                TypeSymbol type,
                SyntaxNode memberAccessNode,
                string memberName,
                int targetMemberArity,
                bool invoked,
                LookupResult lookupResult)
            {
                Debug.Assert(!type.IsDynamic());
                Debug.Assert(lookupResult.IsClear);
 
                var useSiteInfo = CompoundUseSiteInfo<AssemblySymbol>.Discarded;
                enclosingBinder.LookupInstanceMember(lookupResult, type, leftIsBaseReference: false, memberName, targetMemberArity, invoked, ref useSiteInfo);
 
                bool treatAsInstanceMemberAccess;
                if (lookupResult.IsMultiViable)
                {
                    // This branch follows the logic of BindMemberOfType
                    Debug.Assert(lookupResult.Symbols.Any());
 
                    var members = ArrayBuilder<Symbol>.GetInstance();
                    Symbol symbol = enclosingBinder.GetSymbolOrMethodOrPropertyGroup(lookupResult, memberAccessNode, memberName, targetMemberArity, members, BindingDiagnosticBag.Discarded, wasError: out _,
                                                                                     qualifierOpt: null);
 
                    if ((object)symbol == null)
                    {
                        Debug.Assert(members.Count > 0);
 
                        bool haveInstanceCandidates;
                        lookupResult.Clear();
                        enclosingBinder.CheckWhatCandidatesWeHave(members, type, memberName, targetMemberArity,
                                                                  ref lookupResult, ref useSiteInfo,
                                                                  out haveInstanceCandidates, out _);
 
                        treatAsInstanceMemberAccess = haveInstanceCandidates;
                    }
                    else
                    {
                        // methods are special because of extension methods.
                        Debug.Assert(symbol.Kind != SymbolKind.Method);
                        treatAsInstanceMemberAccess = !(symbol.IsStatic || symbol.Kind == SymbolKind.NamedType);
                    }
 
                    members.Free();
                }
                else
                {
                    // At this point this could only be an extension method access or an error
                    treatAsInstanceMemberAccess = true;
                }
 
                lookupResult.Clear();
                return treatAsInstanceMemberAccess;
            }
 
            /// <summary>
            /// Follows the logic of <see cref="Binder.BindQuery"/>
            /// </summary>
            private bool CheckQuery(QueryExpressionSyntax query, Binder enclosingBinder)
            {
                if (CheckIdentifiersInNode(query.FromClause.Expression, enclosingBinder))
                {
                    (QueryTranslationState state, _) = enclosingBinder.MakeInitialQueryTranslationState(query, BindingDiagnosticBag.Discarded);
 
                    bool result = BindQueryInternal(enclosingBinder, state);
 
                    for (QueryContinuationSyntax? continuation = query.Body.Continuation; continuation != null && result; continuation = continuation.Body.Continuation)
                    {
                        // A query expression with a continuation
                        //     from ... into x ...
                        // is translated into
                        //     from x in ( from ... ) ...
                        enclosingBinder.PrepareQueryTranslationStateForContinuation(state, continuation, BindingDiagnosticBag.Discarded);
                        result = BindQueryInternal(enclosingBinder, state);
                    }
 
                    state.Free();
                    return result;
                }
 
                return false;
            }
 
            /// <summary>
            /// Follows the logic of <see cref="Binder.BindQueryInternal2"/>
            /// </summary>
            private bool BindQueryInternal(Binder enclosingBinder, QueryTranslationState state)
            {
                // we continue reducing the query until it is reduced away.
                do
                {
                    if (state.clauses.IsEmpty())
                    {
                        return FinalTranslation(enclosingBinder, state);
                    }
                }
                while (ReduceQuery(enclosingBinder, state));
 
                return false;
            }
 
            /// <summary>
            /// Follows the logic of <see cref="Binder.FinalTranslation"/>
            /// </summary>
            private bool FinalTranslation(Binder enclosingBinder, QueryTranslationState state)
            {
                Debug.Assert(state.clauses.IsEmpty());
                switch (state.selectOrGroup.Kind())
                {
                    case SyntaxKind.SelectClause:
                        {
                            // A query expression of the form
                            //     from x in e select v
                            // is translated into
                            //     ( e ) . Select ( x => v )
                            var selectClause = (SelectClauseSyntax)state.selectOrGroup;
                            var x = state.rangeVariable;
                            var v = selectClause.Expression;
                            return MakeQueryUnboundLambda(enclosingBinder, state.RangeVariableMap(), x, v);
                        }
                    case SyntaxKind.GroupClause:
                        {
                            // A query expression of the form
                            //     from x in e group v by k
                            // is translated into
                            //     ( e ) . GroupBy ( x => k , x => v )
                            // except when v is the identifier x, the translation is
                            //     ( e ) . GroupBy ( x => k )
                            var groupClause = (GroupClauseSyntax)state.selectOrGroup;
                            var x = state.rangeVariable;
                            var v = groupClause.GroupExpression;
                            var k = groupClause.ByExpression;
 
                            return MakeQueryUnboundLambda(enclosingBinder, state.RangeVariableMap(), x, k) &&
                                   MakeQueryUnboundLambda(enclosingBinder, state.RangeVariableMap(), x, v);
                        }
                    default:
                        {
                            // there should have been a syntax error if we get here.
                            return true;
                        }
                }
            }
 
            /// <summary>
            /// Follows the logic of <see cref="Binder.ReduceQuery"/>
            /// </summary>
            private bool ReduceQuery(Binder enclosingBinder, QueryTranslationState state)
            {
                var topClause = state.clauses.Pop();
                switch (topClause.Kind())
                {
                    case SyntaxKind.WhereClause:
                        return ReduceWhere(enclosingBinder, (WhereClauseSyntax)topClause, state);
                    case SyntaxKind.JoinClause:
                        return ReduceJoin(enclosingBinder, (JoinClauseSyntax)topClause, state);
                    case SyntaxKind.OrderByClause:
                        return ReduceOrderBy(enclosingBinder, (OrderByClauseSyntax)topClause, state);
                    case SyntaxKind.FromClause:
                        return ReduceFrom(enclosingBinder, (FromClauseSyntax)topClause, state);
                    case SyntaxKind.LetClause:
                        return ReduceLet(enclosingBinder, (LetClauseSyntax)topClause, state);
                    default:
                        throw ExceptionUtilities.UnexpectedValue(topClause.Kind());
                }
            }
 
            /// <summary>
            /// Follows the logic of <see cref="Binder.ReduceWhere"/>
            /// </summary>
            private bool ReduceWhere(Binder enclosingBinder, WhereClauseSyntax where, QueryTranslationState state)
            {
                // A query expression with a where clause
                //     from x in e
                //     where f
                //     ...
                // is translated into
                //     from x in ( e ) . Where ( x => f )
                return MakeQueryUnboundLambda(enclosingBinder, state.RangeVariableMap(), state.rangeVariable, where.Condition);
            }
 
            /// <summary>
            /// Follows the logic of <see cref="Binder.ReduceJoin"/>
            /// </summary>
            private bool ReduceJoin(Binder enclosingBinder, JoinClauseSyntax join, QueryTranslationState state)
            {
                if (CheckIdentifiersInNode(join.InExpression, enclosingBinder) &&
                    MakeQueryUnboundLambda(enclosingBinder, state.RangeVariableMap(), state.rangeVariable, join.LeftExpression))
                {
                    var x2 = state.AddRangeVariable(enclosingBinder, join.Identifier, BindingDiagnosticBag.Discarded);
 
                    if (MakeQueryUnboundLambda(enclosingBinder, QueryTranslationState.RangeVariableMap(x2), x2, join.RightExpression))
                    {
                        if (join.Into != null)
                        {
                            state.allRangeVariables[x2].Free();
                            state.allRangeVariables.Remove(x2);
 
                            state.AddRangeVariable(enclosingBinder, join.Into.Identifier, BindingDiagnosticBag.Discarded);
                        }
 
                        return true;
                    }
                }
 
                return false;
            }
 
            /// <summary>
            /// Follows the logic of <see cref="Binder.ReduceOrderBy"/>
            /// </summary>
            private bool ReduceOrderBy(Binder enclosingBinder, OrderByClauseSyntax orderby, QueryTranslationState state)
            {
                // A query expression with an orderby clause
                //     from x in e
                //     orderby k1 , k2 , ... , kn
                //     ...
                // is translated into
                //     from x in ( e ) . 
                //     OrderBy ( x => k1 ) . 
                //     ThenBy ( x => k2 ) .
                //     ... .
                //     ThenBy ( x => kn )
                //     ...
                // If an ordering clause specifies a descending direction indicator,
                // an invocation of OrderByDescending or ThenByDescending is produced instead.
                foreach (var ordering in orderby.Orderings)
                {
                    if (!MakeQueryUnboundLambda(enclosingBinder, state.RangeVariableMap(), state.rangeVariable, ordering.Expression))
                    {
                        return false;
                    }
                }
 
                return true;
            }
 
            /// <summary>
            /// Follows the logic of <see cref="Binder.ReduceFrom"/>
            /// </summary>
            private bool ReduceFrom(Binder enclosingBinder, FromClauseSyntax from, QueryTranslationState state)
            {
                var x1 = state.rangeVariable;
                if (MakeQueryUnboundLambda(enclosingBinder, state.RangeVariableMap(), x1, from.Expression))
                {
                    state.AddRangeVariable(enclosingBinder, from.Identifier, BindingDiagnosticBag.Discarded);
                    return true;
                }
 
                return false;
            }
 
            /// <summary>
            /// Follows the logic of <see cref="Binder.ReduceLet"/>
            /// </summary>
            private bool ReduceLet(Binder enclosingBinder, LetClauseSyntax let, QueryTranslationState state)
            {
                // A query expression with a let clause
                //     from x in e
                //     let y = f
                //     ...
                // is translated into
                //     from * in ( e ) . Select ( x => new { x , y = f } )
                //     ...
                var x = state.rangeVariable;
 
                if (MakeQueryUnboundLambda(enclosingBinder, state.RangeVariableMap(), x, let.Expression))
                {
                    state.rangeVariable = state.TransparentRangeVariable(enclosingBinder);
                    state.AddTransparentIdentifier(x.Name);
                    var y = state.AddRangeVariable(enclosingBinder, let.Identifier, BindingDiagnosticBag.Discarded);
                    state.allRangeVariables[y].Add(let.Identifier.ValueText);
                    return true;
                }
 
                return false;
            }
 
            private bool MakeQueryUnboundLambda(Binder enclosingBinder, RangeVariableMap qvm, RangeVariableSymbol parameter, ExpressionSyntax expression)
            {
                UnboundLambda unboundLambda = Binder.MakeQueryUnboundLambda(
                    expression,
                    new QueryUnboundLambdaState(
                        enclosingBinder, qvm, ImmutableArray.Create(parameter),
                        (LambdaSymbol lambdaSymbol, Binder lambdaBodyBinder, BindingDiagnosticBag diagnostics) => throw ExceptionUtilities.Unreachable()),
                    withDependencies: false);
 
                var lambdaBodyBinder = CreateLambdaBodyBinder(enclosingBinder, unboundLambda);
                return CheckIdentifiersInNode(expression, lambdaBodyBinder.GetRequiredBinder(expression));
            }
        }
    }
}