|
// 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;
using System.Collections.Immutable;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;
using System.Collections.Generic;
using System.Diagnostics;
namespace Microsoft.CodeAnalysis.CSharp
{
internal abstract class ExpressionVariableFinder<TFieldOrLocalSymbol> : CSharpSyntaxWalker where TFieldOrLocalSymbol : Symbol
{
private ArrayBuilder<TFieldOrLocalSymbol> _variablesBuilder;
private SyntaxNode _nodeToBind;
protected void FindExpressionVariables(
ArrayBuilder<TFieldOrLocalSymbol> builder,
CSharpSyntaxNode node)
{
Debug.Assert(node != null);
ArrayBuilder<TFieldOrLocalSymbol> save = _variablesBuilder;
_variablesBuilder = builder;
#if DEBUG
// These are all of the kinds of nodes we should need to handle in this class.
// If you add to this list, make sure you handle that node kind with a visitor.
switch (node.Kind())
{
case SyntaxKind.EqualsValueClause:
case SyntaxKind.ArrowExpressionClause:
case SyntaxKind.SwitchSection:
case SyntaxKind.Attribute:
case SyntaxKind.ThrowStatement:
case SyntaxKind.ReturnStatement:
case SyntaxKind.YieldReturnStatement:
case SyntaxKind.ExpressionStatement:
case SyntaxKind.LockStatement:
case SyntaxKind.IfStatement:
case SyntaxKind.SwitchStatement:
case SyntaxKind.VariableDeclarator:
case SyntaxKind.ConstructorDeclaration:
case SyntaxKind.SwitchExpressionArm:
case SyntaxKind.GotoCaseStatement:
case SyntaxKind.PrimaryConstructorBaseType:
break;
case SyntaxKind.ArgumentList:
Debug.Assert(node.Parent is ConstructorInitializerSyntax || node.Parent is PrimaryConstructorBaseTypeSyntax);
break;
default:
Debug.Assert(node is ExpressionSyntax);
break;
}
#endif
VisitNodeToBind(node);
_variablesBuilder = save;
}
public override void VisitSwitchExpression(SwitchExpressionSyntax node)
{
Visit(node.GoverningExpression);
// Each case has its own scope.
}
public override void VisitSwitchExpressionArm(SwitchExpressionArmSyntax node)
{
SyntaxNode previousNodeToBind = _nodeToBind;
_nodeToBind = node;
Visit(node.Pattern);
Visit(node.WhenClause?.Condition);
Visit(node.Expression);
_nodeToBind = previousNodeToBind;
}
public override void VisitVariableDeclarator(VariableDeclaratorSyntax node)
{
if (node.ArgumentList != null)
{
foreach (ArgumentSyntax arg in node.ArgumentList.Arguments)
{
Visit(arg.Expression);
}
}
VisitNodeToBind(node.Initializer);
}
public override void VisitGotoStatement(GotoStatementSyntax node)
{
if (node.Kind() == SyntaxKind.GotoCaseStatement)
Visit(node.Expression);
}
private void VisitNodeToBind(CSharpSyntaxNode node)
{
SyntaxNode previousNodeToBind = _nodeToBind;
_nodeToBind = node;
Visit(node);
_nodeToBind = previousNodeToBind;
}
protected void FindExpressionVariables(
ArrayBuilder<TFieldOrLocalSymbol> builder,
SeparatedSyntaxList<ExpressionSyntax> nodes)
{
Debug.Assert(nodes.Count > 0);
ArrayBuilder<TFieldOrLocalSymbol> save = _variablesBuilder;
_variablesBuilder = builder;
foreach (ExpressionSyntax n in nodes)
{
VisitNodeToBind(n);
}
_variablesBuilder = save;
}
public override void VisitEqualsValueClause(EqualsValueClauseSyntax node)
{
VisitNodeToBind(node.Value);
}
public override void VisitArrowExpressionClause(ArrowExpressionClauseSyntax node)
{
VisitNodeToBind(node.Expression);
}
public override void VisitSwitchSection(SwitchSectionSyntax node)
{
foreach (SwitchLabelSyntax label in node.Labels)
{
switch (label.Kind())
{
case SyntaxKind.CasePatternSwitchLabel:
{
var switchLabel = (CasePatternSwitchLabelSyntax)label;
SyntaxNode previousNodeToBind = _nodeToBind;
_nodeToBind = switchLabel;
Visit(switchLabel.Pattern);
if (switchLabel.WhenClause != null)
{
VisitNodeToBind(switchLabel.WhenClause.Condition);
}
_nodeToBind = previousNodeToBind;
break;
}
case SyntaxKind.CaseSwitchLabel:
{
var switchlabel = (CaseSwitchLabelSyntax)label;
VisitNodeToBind(switchlabel.Value);
break;
}
}
}
}
public override void VisitAttribute(AttributeSyntax node)
{
if (node.ArgumentList != null)
{
foreach (AttributeArgumentSyntax argument in node.ArgumentList.Arguments)
{
VisitNodeToBind(argument.Expression);
}
}
}
public override void VisitThrowStatement(ThrowStatementSyntax node)
{
VisitNodeToBind(node.Expression);
}
public override void VisitReturnStatement(ReturnStatementSyntax node)
{
VisitNodeToBind(node.Expression);
}
public override void VisitYieldStatement(YieldStatementSyntax node)
{
VisitNodeToBind(node.Expression);
}
public override void VisitExpressionStatement(ExpressionStatementSyntax node)
{
VisitNodeToBind(node.Expression);
}
public override void VisitLockStatement(LockStatementSyntax node)
{
VisitNodeToBind(node.Expression);
}
public override void VisitIfStatement(IfStatementSyntax node)
{
VisitNodeToBind(node.Condition);
}
public override void VisitSwitchStatement(SwitchStatementSyntax node)
{
VisitNodeToBind(node.Expression);
}
public override void VisitDeclarationPattern(DeclarationPatternSyntax node)
{
if (node.Designation?.Kind() == SyntaxKind.SingleVariableDesignation)
{
TFieldOrLocalSymbol variable = MakePatternVariable(node.Type, (SingleVariableDesignationSyntax)node.Designation, _nodeToBind);
if ((object)variable != null)
{
_variablesBuilder.Add(variable);
}
}
else
{
// The declaration pattern does not permit a ParenthesizedVariableDesignation
Debug.Assert(node.Designation == null || node.Designation.Kind() == SyntaxKind.DiscardDesignation);
}
base.VisitDeclarationPattern(node);
}
public override void VisitVarPattern(VarPatternSyntax node)
{
VisitPatternDesignation(node.Designation);
base.VisitVarPattern(node);
}
private void VisitPatternDesignation(VariableDesignationSyntax node)
{
switch (node.Kind())
{
case SyntaxKind.SingleVariableDesignation:
TFieldOrLocalSymbol variable = MakePatternVariable(type: null, (SingleVariableDesignationSyntax)node, _nodeToBind);
if ((object)variable != null)
{
_variablesBuilder.Add(variable);
}
break;
case SyntaxKind.DiscardDesignation:
break;
case SyntaxKind.ParenthesizedVariableDesignation:
foreach (VariableDesignationSyntax nested in ((ParenthesizedVariableDesignationSyntax)node).Variables)
{
VisitPatternDesignation(nested);
}
break;
default:
throw ExceptionUtilities.UnexpectedValue(node.Kind());
}
}
public override void VisitRecursivePattern(RecursivePatternSyntax node)
{
Debug.Assert(node.Designation is null or SingleVariableDesignationSyntax or DiscardDesignationSyntax);
TFieldOrLocalSymbol variable = MakePatternVariable(node.Type, node.Designation as SingleVariableDesignationSyntax, _nodeToBind);
if ((object)variable != null)
{
_variablesBuilder.Add(variable);
}
base.VisitRecursivePattern(node);
}
public override void VisitListPattern(ListPatternSyntax node)
{
Debug.Assert(node.Designation is null or SingleVariableDesignationSyntax or DiscardDesignationSyntax);
TFieldOrLocalSymbol variable = MakePatternVariable(type: null, node.Designation as SingleVariableDesignationSyntax, _nodeToBind);
if ((object)variable != null)
{
_variablesBuilder.Add(variable);
}
base.VisitListPattern(node);
}
protected abstract TFieldOrLocalSymbol MakePatternVariable(TypeSyntax type, SingleVariableDesignationSyntax designation, SyntaxNode nodeToBind);
public override void VisitParenthesizedLambdaExpression(ParenthesizedLambdaExpressionSyntax node) { }
public override void VisitSimpleLambdaExpression(SimpleLambdaExpressionSyntax node) { }
public override void VisitAnonymousMethodExpression(AnonymousMethodExpressionSyntax node) { }
public override void VisitQueryExpression(QueryExpressionSyntax node)
{
// Variables declared in [in] expressions of top level from clause and
// join clauses are in scope
VisitNodeToBind(node.FromClause.Expression);
Visit(node.Body);
}
public override void VisitQueryBody(QueryBodySyntax node)
{
// Variables declared in [in] expressions of top level from clause and
// join clauses are in scope
foreach (QueryClauseSyntax clause in node.Clauses)
{
if (clause.Kind() == SyntaxKind.JoinClause)
{
VisitNodeToBind(((JoinClauseSyntax)clause).InExpression);
}
}
Visit(node.Continuation);
}
public override void VisitBinaryExpression(BinaryExpressionSyntax node)
{
// The binary operators (except ??) are left-associative, and expressions of the form
// a + b + c + d .... are relatively common in machine-generated code. The parser can handle
// creating a deep-on-the-left syntax tree no problem, and then we promptly blow the stack during
// semantic analysis. Here we build an explicit stack to handle left recursion.
var operands = ArrayBuilder<ExpressionSyntax>.GetInstance();
ExpressionSyntax current = node;
do
{
var binOp = (BinaryExpressionSyntax)current;
operands.Push(binOp.Right);
current = binOp.Left;
}
while (current is BinaryExpressionSyntax);
Visit(current);
while (operands.Count > 0)
{
Visit(operands.Pop());
}
operands.Free();
}
public override void VisitBinaryPattern(BinaryPatternSyntax node)
{
// Like binary expressions, binary patterns are left-associative, and can be deeply nested (even in our own code).
// Handle this with manual recursion.
PatternSyntax currentPattern = node;
var rightPatternStack = ArrayBuilder<PatternSyntax>.GetInstance();
while (currentPattern is BinaryPatternSyntax binaryPattern)
{
rightPatternStack.Push(binaryPattern.Right);
currentPattern = binaryPattern.Left;
}
do
{
Visit(currentPattern);
} while (rightPatternStack.TryPop(out currentPattern));
rightPatternStack.Free();
}
public override void VisitInvocationExpression(InvocationExpressionSyntax node)
{
if (receiverIsInvocation(node, out InvocationExpressionSyntax nested))
{
var invocations = ArrayBuilder<InvocationExpressionSyntax>.GetInstance();
invocations.Push(node);
node = nested;
while (receiverIsInvocation(node, out nested))
{
invocations.Push(node);
node = nested;
}
Visit(node.Expression);
do
{
Visit(node.ArgumentList);
}
while (invocations.TryPop(out node));
invocations.Free();
}
else
{
Visit(node.Expression);
Visit(node.ArgumentList);
}
static bool receiverIsInvocation(InvocationExpressionSyntax node, out InvocationExpressionSyntax nested)
{
if (node.Expression is MemberAccessExpressionSyntax { Expression: InvocationExpressionSyntax receiver })
{
nested = receiver;
return true;
}
nested = null;
return false;
}
}
public override void VisitDeclarationExpression(DeclarationExpressionSyntax node)
{
var argumentSyntax = node.Parent as ArgumentSyntax;
var argumentListSyntaxOpt = argumentSyntax?.Parent as BaseArgumentListSyntax;
VisitDeclarationExpressionDesignation(node, node.Designation, argumentListSyntaxOpt);
}
private void VisitDeclarationExpressionDesignation(DeclarationExpressionSyntax node, VariableDesignationSyntax designation, BaseArgumentListSyntax argumentListSyntaxOpt)
{
switch (designation.Kind())
{
case SyntaxKind.SingleVariableDesignation:
TFieldOrLocalSymbol variable = MakeDeclarationExpressionVariable(node, (SingleVariableDesignationSyntax)designation, argumentListSyntaxOpt, _nodeToBind);
if ((object)variable != null)
{
_variablesBuilder.Add(variable);
}
break;
case SyntaxKind.DiscardDesignation:
break;
case SyntaxKind.ParenthesizedVariableDesignation:
foreach (VariableDesignationSyntax nested in ((ParenthesizedVariableDesignationSyntax)designation).Variables)
{
VisitDeclarationExpressionDesignation(node, nested, argumentListSyntaxOpt);
}
break;
default:
throw ExceptionUtilities.UnexpectedValue(designation.Kind());
}
}
public override void VisitAssignmentExpression(AssignmentExpressionSyntax node)
{
if (node.IsDeconstruction())
{
CollectVariablesFromDeconstruction(node.Left, node);
}
else
{
Visit(node.Left);
}
Visit(node.Right);
}
public override void VisitConstructorDeclaration(ConstructorDeclarationSyntax node)
{
if (node.Initializer != null)
{
VisitNodeToBind(node.Initializer);
}
}
private void CollectVariablesFromDeconstruction(
ExpressionSyntax possibleTupleDeclaration,
AssignmentExpressionSyntax deconstruction)
{
switch (possibleTupleDeclaration.Kind())
{
case SyntaxKind.TupleExpression:
{
var tuple = (TupleExpressionSyntax)possibleTupleDeclaration;
foreach (ArgumentSyntax arg in tuple.Arguments)
{
CollectVariablesFromDeconstruction(arg.Expression, deconstruction);
}
break;
}
case SyntaxKind.DeclarationExpression:
{
var declarationExpression = (DeclarationExpressionSyntax)possibleTupleDeclaration;
CollectVariablesFromDeconstruction(declarationExpression.Designation, declarationExpression.Type, deconstruction);
break;
}
default:
{
Visit(possibleTupleDeclaration);
break;
}
}
}
private void CollectVariablesFromDeconstruction(
VariableDesignationSyntax designation,
TypeSyntax closestTypeSyntax,
AssignmentExpressionSyntax deconstruction)
{
switch (designation.Kind())
{
case SyntaxKind.SingleVariableDesignation:
{
var single = (SingleVariableDesignationSyntax)designation;
TFieldOrLocalSymbol variable = MakeDeconstructionVariable(closestTypeSyntax, single, deconstruction);
if ((object)variable != null)
{
_variablesBuilder.Add(variable);
}
break;
}
case SyntaxKind.ParenthesizedVariableDesignation:
{
var tuple = (ParenthesizedVariableDesignationSyntax)designation;
foreach (VariableDesignationSyntax d in tuple.Variables)
{
CollectVariablesFromDeconstruction(d, closestTypeSyntax, deconstruction);
}
break;
}
case SyntaxKind.DiscardDesignation:
break;
default:
throw ExceptionUtilities.UnexpectedValue(designation.Kind());
}
}
/// <summary>
/// Make a variable for a declaration expression other than a deconstruction left-hand-side. The only
/// other legal place for a declaration expression today is an out variable declaration; this method
/// handles that and the error cases as well.
/// </summary>
protected abstract TFieldOrLocalSymbol MakeDeclarationExpressionVariable(DeclarationExpressionSyntax node, SingleVariableDesignationSyntax designation, BaseArgumentListSyntax argumentListSyntax, SyntaxNode nodeToBind);
/// <summary>
/// Make a variable for a declaration expression appearing as one of the declared variables of the left-hand-side
/// of a deconstruction assignment.
/// </summary>
protected abstract TFieldOrLocalSymbol MakeDeconstructionVariable(
TypeSyntax closestTypeSyntax,
SingleVariableDesignationSyntax designation,
AssignmentExpressionSyntax deconstruction);
}
internal class ExpressionVariableFinder : ExpressionVariableFinder<LocalSymbol>
{
private Binder _scopeBinder;
private Binder _enclosingBinder;
internal static void FindExpressionVariables(
Binder scopeBinder,
ArrayBuilder<LocalSymbol> builder,
CSharpSyntaxNode node,
Binder enclosingBinderOpt = null)
{
if (node == null)
{
return;
}
ExpressionVariableFinder finder = s_poolInstance.Allocate();
finder._scopeBinder = scopeBinder;
finder._enclosingBinder = enclosingBinderOpt ?? scopeBinder;
finder.FindExpressionVariables(builder, node);
finder._scopeBinder = null;
finder._enclosingBinder = null;
s_poolInstance.Free(finder);
}
internal static void FindExpressionVariables(
Binder binder,
ArrayBuilder<LocalSymbol> builder,
SeparatedSyntaxList<ExpressionSyntax> nodes)
{
if (nodes.Count == 0)
{
return;
}
ExpressionVariableFinder finder = s_poolInstance.Allocate();
finder._scopeBinder = binder;
finder._enclosingBinder = binder;
finder.FindExpressionVariables(builder, nodes);
finder._scopeBinder = null;
finder._enclosingBinder = null;
s_poolInstance.Free(finder);
}
protected override LocalSymbol MakePatternVariable(TypeSyntax type, SingleVariableDesignationSyntax designation, SyntaxNode nodeToBind)
{
if (designation == null)
{
return null;
}
NamedTypeSymbol container = _scopeBinder.ContainingType;
if ((object)container != null && container.IsScriptClass &&
(object)_scopeBinder.LookupDeclaredField(designation) != null)
{
// This is a field declaration
return null;
}
return SourceLocalSymbol.MakeLocalSymbolWithEnclosingContext(
_scopeBinder.ContainingMemberOrLambda,
scopeBinder: _scopeBinder,
nodeBinder: _enclosingBinder,
typeSyntax: type,
identifierToken: designation.Identifier,
kind: LocalDeclarationKind.PatternVariable,
nodeToBind: nodeToBind,
forbiddenZone: null);
}
protected override LocalSymbol MakeDeclarationExpressionVariable(DeclarationExpressionSyntax node, SingleVariableDesignationSyntax designation, BaseArgumentListSyntax argumentListSyntaxOpt, SyntaxNode nodeToBind)
{
NamedTypeSymbol container = _scopeBinder.ContainingType;
if ((object)container != null && container.IsScriptClass &&
(object)_scopeBinder.LookupDeclaredField(designation) != null)
{
// This is a field declaration
return null;
}
return SourceLocalSymbol.MakeLocalSymbolWithEnclosingContext(
containingSymbol: _scopeBinder.ContainingMemberOrLambda,
scopeBinder: _scopeBinder,
nodeBinder: _enclosingBinder,
typeSyntax: node.Type,
identifierToken: designation.Identifier,
kind: node.IsOutVarDeclaration() ? LocalDeclarationKind.OutVariable : LocalDeclarationKind.DeclarationExpressionVariable,
nodeToBind: nodeToBind,
forbiddenZone: argumentListSyntaxOpt);
}
protected override LocalSymbol MakeDeconstructionVariable(
TypeSyntax closestTypeSyntax,
SingleVariableDesignationSyntax designation,
AssignmentExpressionSyntax deconstruction)
{
NamedTypeSymbol container = _scopeBinder.ContainingType;
if ((object)container != null && container.IsScriptClass &&
(object)_scopeBinder.LookupDeclaredField(designation) != null)
{
// This is a field declaration
return null;
}
return SourceLocalSymbol.MakeDeconstructionLocal(
containingSymbol: _scopeBinder.ContainingMemberOrLambda,
scopeBinder: _scopeBinder,
nodeBinder: _enclosingBinder,
closestTypeSyntax: closestTypeSyntax,
identifierToken: designation.Identifier,
kind: LocalDeclarationKind.DeconstructionVariable,
deconstruction: deconstruction);
}
#region pool
private static readonly ObjectPool<ExpressionVariableFinder> s_poolInstance = CreatePool();
public static ObjectPool<ExpressionVariableFinder> CreatePool()
{
return new ObjectPool<ExpressionVariableFinder>(() => new ExpressionVariableFinder(), 10);
}
#endregion
}
internal class ExpressionFieldFinder : ExpressionVariableFinder<Symbol>
{
private SourceMemberContainerTypeSymbol _containingType;
private DeclarationModifiers _modifiers;
private FieldSymbol _containingFieldOpt;
internal static void FindExpressionVariables(
ArrayBuilder<Symbol> builder,
CSharpSyntaxNode node,
SourceMemberContainerTypeSymbol containingType,
DeclarationModifiers modifiers,
FieldSymbol containingFieldOpt)
{
if (node == null)
{
return;
}
ExpressionFieldFinder finder = s_poolInstance.Allocate();
finder._containingType = containingType;
finder._modifiers = modifiers;
finder._containingFieldOpt = containingFieldOpt;
finder.FindExpressionVariables(builder, node);
finder._containingType = null;
finder._modifiers = DeclarationModifiers.None;
finder._containingFieldOpt = null;
s_poolInstance.Free(finder);
}
protected override Symbol MakePatternVariable(TypeSyntax type, SingleVariableDesignationSyntax designation, SyntaxNode nodeToBind)
{
return designation == null ? null : GlobalExpressionVariable.Create(
_containingType, _modifiers, type,
designation.Identifier.ValueText, designation, designation.Span,
_containingFieldOpt, nodeToBind);
}
protected override Symbol MakeDeclarationExpressionVariable(DeclarationExpressionSyntax node, SingleVariableDesignationSyntax designation, BaseArgumentListSyntax argumentListSyntaxOpt, SyntaxNode nodeToBind)
{
return GlobalExpressionVariable.Create(
_containingType, _modifiers, node.Type,
designation.Identifier.ValueText, designation, designation.Identifier.Span,
_containingFieldOpt, nodeToBind);
}
protected override Symbol MakeDeconstructionVariable(
TypeSyntax closestTypeSyntax,
SingleVariableDesignationSyntax designation,
AssignmentExpressionSyntax deconstruction)
{
return GlobalExpressionVariable.Create(
containingType: _containingType,
modifiers: DeclarationModifiers.Private,
typeSyntax: closestTypeSyntax,
name: designation.Identifier.ValueText,
syntax: designation,
locationSpan: designation.Span,
containingFieldOpt: null,
nodeToBind: deconstruction);
}
#region pool
private static readonly ObjectPool<ExpressionFieldFinder> s_poolInstance = CreatePool();
public static ObjectPool<ExpressionFieldFinder> CreatePool()
{
return new ObjectPool<ExpressionFieldFinder>(() => new ExpressionFieldFinder(), 10);
}
#endregion
}
}
|