/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Compilers/CSharp/Portable/Binder/ExpressionVariableFinder.cs
759 строк
29 KB
AlekseyTs
Improve detection of invalid references for implicitly typed expression variables declared within implicit object creation expressions. (#80546)
22 окт 2025, 17:53
Не верифицирован
22 окт 2025, 17:53
f230001
Код
Авторство
О чём код?
// 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; } } #nullable enable public override void VisitDeclarationExpression(DeclarationExpressionSyntax node) { VisitDeclarationExpressionDesignation(node, node.Designation); } private void VisitDeclarationExpressionDesignation(DeclarationExpressionSyntax node, VariableDesignationSyntax designation) { switch (designation.Kind()) { case SyntaxKind.SingleVariableDesignation: TFieldOrLocalSymbol? variable = MakeDeclarationExpressionVariable(node, (SingleVariableDesignationSyntax)designation, _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); } break; default: throw ExceptionUtilities.UnexpectedValue(designation.Kind()); } } #nullable disable 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()); } } #nullable enable /// <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, SyntaxNode nodeToBind); #nullable disable /// <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); } #nullable enable 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; } Debug.Assert(_scopeBinder.ContainingMemberOrLambda != null); return SourceLocalSymbol.MakeLocalSymbolWithEnclosingContext( _scopeBinder.ContainingMemberOrLambda, scopeBinder: _scopeBinder, nodeBinder: _enclosingBinder, typeSyntax: type, identifierToken: designation.Identifier, kind: LocalDeclarationKind.PatternVariable, nodeToBind: nodeToBind); } protected override LocalSymbol? MakeDeclarationExpressionVariable(DeclarationExpressionSyntax node, SingleVariableDesignationSyntax designation, SyntaxNode nodeToBind) { NamedTypeSymbol? container = _scopeBinder.ContainingType; if ((object?)container != null && container.IsScriptClass && (object)_scopeBinder.LookupDeclaredField(designation) != null) { // This is a field declaration return null; } Debug.Assert(_scopeBinder.ContainingMemberOrLambda != 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); } #nullable disable 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); } #nullable enable protected override Symbol MakeDeclarationExpressionVariable(DeclarationExpressionSyntax node, SingleVariableDesignationSyntax designation, SyntaxNode nodeToBind) { return GlobalExpressionVariable.Create( _containingType, _modifiers, node.Type, designation.Identifier.ValueText, designation, designation.Identifier.Span, _containingFieldOpt, nodeToBind); } #nullable disable 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 } }