| File: Analysis\ForLoopVerification.vb | Web Access |
| Project: src\roslyn\src\Compilers\VisualBasic\Portable\Microsoft.CodeAnalysis.VisualBasic.vbproj (Microsoft.CodeAnalysis.VisualBasic) |
' 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. Imports Microsoft.CodeAnalysis.Text Imports Microsoft.CodeAnalysis.VisualBasic.Symbols Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Namespace Microsoft.CodeAnalysis.VisualBasic Friend Class ForLoopVerification ''' <summary> ''' A BoundForLoopStatement node has a list of control variables (from the attached next statement). ''' When binding the control variable of a for/for each loop that is nested in another for/for each loop, it must be ''' checked that the control variable has not been used by a containing for/for each loop. Because bound nodes do not ''' know their parents and we try to avoid passing around a stack of variables, we just walk the bound tree after the ''' initial binding to report this error. ''' In addition, it must be checked that the control variables of the next statement match the loop. Because the inner ''' most loop contains the next with control variables from outer binders, checking this here is also convenient. ''' ''' There are two diagnostics reported by this walker: ''' 1. BC30069: For loop control variable '{0}' already in use by an enclosing For loop. ''' 2. BC30070: Next control variable does not match For loop control variable '{0}'. ''' </summary> Public Shared Sub VerifyForLoops(block As BoundBlock, diagnostics As DiagnosticBag) Try Dim verifier As New ForLoopVerificationWalker(diagnostics) verifier.Visit(block) Catch ex As BoundTreeVisitor.CancelledByStackGuardException ex.AddAnError(diagnostics) End Try End Sub Private NotInheritable Class ForLoopVerificationWalker Inherits BoundTreeWalkerWithStackGuardWithoutRecursionOnTheLeftOfBinaryOperator Private ReadOnly _diagnostics As DiagnosticBag Private ReadOnly _controlVariables As Stack(Of BoundExpression) Public Sub New(diagnostics As DiagnosticBag) _diagnostics = diagnostics _controlVariables = New Stack(Of BoundExpression) End Sub Public Overrides Function VisitForToStatement(node As BoundForToStatement) As BoundNode PreVisitForAndForEachStatement(node) MyBase.VisitForToStatement(node) PostVisitForAndForEachStatement(node) Return Nothing End Function Public Overrides Function VisitForEachStatement(node As BoundForEachStatement) As BoundNode PreVisitForAndForEachStatement(node) MyBase.VisitForEachStatement(node) PostVisitForAndForEachStatement(node) Return Nothing End Function ''' <summary> ''' Checks if the control variable was already used in an enclosing for loop ''' </summary> Private Sub PreVisitForAndForEachStatement(boundForStatement As BoundForStatement) ' check if the control variable is used previously in enclosing for loop blocks ' because the ExpressionSymbol is coming from the symbol declaration, the check cannot distinguish ' a member access to a field from different or the same instance. ' This is consistent with VB6 and Dev10. Dim controlVariable = boundForStatement.ControlVariable Dim controlVariableSymbol = ForLoopVerification.ReferencedSymbol(controlVariable) Debug.Assert(controlVariableSymbol IsNot Nothing OrElse controlVariable.Kind = BoundKind.BadExpression OrElse controlVariable.HasErrors) ' Symbol may be Nothing for BadExpression. If controlVariableSymbol IsNot Nothing Then For Each boundVariable In _controlVariables If ForLoopVerification.ReferencedSymbol(boundVariable) = controlVariableSymbol Then _diagnostics.Add(ERRID.ERR_ForIndexInUse1, controlVariable.Syntax.GetLocation(), CustomSymbolDisplayFormatter.ShortErrorName(controlVariableSymbol)) Exit For End If Next End If _controlVariables.Push(controlVariable) End Sub ''' <summary> ''' Checks if the control variables from the next statement match the control variable of the enclosing ''' for loop. ''' Some loops may contain a next with multiple variables. ''' </summary> Private Sub PostVisitForAndForEachStatement(boundForStatement As BoundForStatement) ' do nothing if the array is nothing If Not boundForStatement.NextVariablesOpt.IsDefault Then ' if it's empty we just have to adjust the index for one variable If boundForStatement.NextVariablesOpt.IsEmpty Then _controlVariables.Pop() Else For Each nextVariable In boundForStatement.NextVariablesOpt ' m_controlVariables will not contain too much or too few elements because ' 1. parser will fill up with missing next statements ' 2. binding will not bind spare control variables (see binding of next statement ' in BindForBlockParts) Dim controlVariable = _controlVariables.Pop() If Not controlVariable.HasErrors AndAlso Not nextVariable.HasErrors AndAlso ForLoopVerification.ReferencedSymbol(nextVariable) <> ForLoopVerification.ReferencedSymbol(controlVariable) Then _diagnostics.Add(ERRID.ERR_NextForMismatch1, nextVariable.Syntax.GetLocation(), CustomSymbolDisplayFormatter.ShortErrorName(ForLoopVerification.ReferencedSymbol(controlVariable))) End If Next End If End If End Sub End Class ''' <summary> ''' Gets the referenced symbol of the bound expression. ''' Used for matching variables between For and Next statements. ''' </summary> ''' <param name="expression">The bound expression.</param> Friend Shared Function ReferencedSymbol(expression As BoundExpression) As Symbol Select Case expression.Kind Case BoundKind.ArrayAccess Return ReferencedSymbol(DirectCast(expression, BoundArrayAccess).Expression) Case BoundKind.PropertyAccess Return DirectCast(expression, BoundPropertyAccess).PropertySymbol Case BoundKind.Call Return DirectCast(expression, BoundCall).Method Case BoundKind.Local Return DirectCast(expression, BoundLocal).LocalSymbol Case BoundKind.RangeVariable Return DirectCast(expression, BoundRangeVariable).RangeVariable Case BoundKind.FieldAccess Return DirectCast(expression, BoundFieldAccess).FieldSymbol Case BoundKind.Parameter Return DirectCast(expression, BoundParameter).ParameterSymbol Case BoundKind.Parenthesized Return ReferencedSymbol(DirectCast(expression, BoundParenthesized).Expression) End Select Return Nothing End Function End Class End Namespace