/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Compilers/CSharp/Portable/Binder/LocalScopeBinder.cs
566 строк
22 KB
Julien Couvreur
Add locals to binder from out vars in array rank specifiers on local functions (#69597)
22 авг 2023, 20:58
Не верифицирован
22 авг 2023, 20:58
850bd68
Код
Авторство
О чём код?
// 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.Immutable; using System.Diagnostics; using System.Reflection.Metadata; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp { internal partial class LocalScopeBinder : Binder { protected const int DefaultLocalSymbolArrayCapacity = 16; private ImmutableArray<LocalSymbol> _locals; private ImmutableArray<LocalFunctionSymbol> _localFunctions; private ImmutableArray<LabelSymbol> _labels; internal LocalScopeBinder(Binder next) : this(next, next.Flags) { } internal LocalScopeBinder(Binder next, BinderFlags flags) : base(next, flags) { } internal sealed override ImmutableArray<LocalSymbol> Locals { get { if (_locals.IsDefault) { ImmutableInterlocked.InterlockedCompareExchange(ref _locals, BuildLocals(), default(ImmutableArray<LocalSymbol>)); } return _locals; } } protected virtual ImmutableArray<LocalSymbol> BuildLocals() { return ImmutableArray<LocalSymbol>.Empty; } internal sealed override ImmutableArray<LocalFunctionSymbol> LocalFunctions { get { if (_localFunctions.IsDefault) { ImmutableInterlocked.InterlockedCompareExchange(ref _localFunctions, BuildLocalFunctions(), default(ImmutableArray<LocalFunctionSymbol>)); } return _localFunctions; } } protected virtual ImmutableArray<LocalFunctionSymbol> BuildLocalFunctions() { return ImmutableArray<LocalFunctionSymbol>.Empty; } internal sealed override ImmutableArray<LabelSymbol> Labels { get { if (_labels.IsDefault) { ImmutableInterlocked.InterlockedCompareExchange(ref _labels, BuildLabels(), default(ImmutableArray<LabelSymbol>)); } return _labels; } } protected virtual ImmutableArray<LabelSymbol> BuildLabels() { return ImmutableArray<LabelSymbol>.Empty; } private SmallDictionary<string, LocalSymbol> _lazyLocalsMap; private SmallDictionary<string, LocalSymbol> LocalsMap { get { if (_lazyLocalsMap == null && this.Locals.Length > 0) { _lazyLocalsMap = BuildMap(this.Locals); } return _lazyLocalsMap; } } private SmallDictionary<string, LocalFunctionSymbol> _lazyLocalFunctionsMap; private SmallDictionary<string, LocalFunctionSymbol> LocalFunctionsMap { get { if (_lazyLocalFunctionsMap == null && this.LocalFunctions.Length > 0) { _lazyLocalFunctionsMap = BuildMap(this.LocalFunctions); } return _lazyLocalFunctionsMap; } } private SmallDictionary<string, LabelSymbol> _lazyLabelsMap; private SmallDictionary<string, LabelSymbol> LabelsMap { get { if (_lazyLabelsMap == null && this.Labels.Length > 0) { _lazyLabelsMap = BuildMap(this.Labels); } return _lazyLabelsMap; } } private static SmallDictionary<string, TSymbol> BuildMap<TSymbol>(ImmutableArray<TSymbol> array) where TSymbol : Symbol { Debug.Assert(array.Length > 0); var map = new SmallDictionary<string, TSymbol>(); // NOTE: in a rare case of having two symbols with same name the one closer to the array's start wins. for (int i = array.Length - 1; i >= 0; i--) { var symbol = array[i]; map[symbol.Name] = symbol; } return map; } protected ImmutableArray<LocalSymbol> BuildLocals(SyntaxList<StatementSyntax> statements, Binder enclosingBinder) { #if DEBUG Binder currentBinder = enclosingBinder; while (true) { if (this == currentBinder) { break; } currentBinder = currentBinder.Next; } #endif ArrayBuilder<LocalSymbol> locals = ArrayBuilder<LocalSymbol>.GetInstance(DefaultLocalSymbolArrayCapacity); foreach (var statement in statements) { BuildLocals(enclosingBinder, statement, locals); } return locals.ToImmutableAndFree(); } internal void BuildLocals(Binder enclosingBinder, StatementSyntax statement, ArrayBuilder<LocalSymbol> locals) { var innerStatement = statement; // drill into any LabeledStatements -- atomic LabelStatements have been bound into // wrapped LabeledStatements by this point while (innerStatement.Kind() == SyntaxKind.LabeledStatement) { innerStatement = ((LabeledStatementSyntax)innerStatement).Statement; } switch (innerStatement.Kind()) { case SyntaxKind.LocalDeclarationStatement: { Binder localDeclarationBinder = enclosingBinder.GetBinder(innerStatement) ?? enclosingBinder; var decl = (LocalDeclarationStatementSyntax)innerStatement; decl.Declaration.Type.VisitRankSpecifiers((rankSpecifier, args) => { foreach (var expression in rankSpecifier.Sizes) { findExpressionVariablesInRankSpecifier(expression, args); } }, (localScopeBinder: this, locals: locals, localDeclarationBinder: localDeclarationBinder)); LocalDeclarationKind kind; if (decl.IsConst) { kind = LocalDeclarationKind.Constant; } else if (decl.UsingKeyword != default(SyntaxToken)) { kind = LocalDeclarationKind.UsingVariable; } else { kind = LocalDeclarationKind.RegularVariable; } foreach (var vdecl in decl.Declaration.Variables) { var localSymbol = MakeLocal(decl.Declaration, vdecl, kind, allowScoped: true, localDeclarationBinder); locals.Add(localSymbol); // also gather expression-declared variables from the bracketed argument lists and the initializers ExpressionVariableFinder.FindExpressionVariables(this, locals, vdecl, localDeclarationBinder); } } break; case SyntaxKind.LocalFunctionStatement: { Binder localFunctionDeclarationBinder = enclosingBinder.GetBinder(innerStatement) ?? enclosingBinder; var decl = (LocalFunctionStatementSyntax)innerStatement; foreach (var parameter in decl.ParameterList.Parameters) { parameter.Type?.VisitRankSpecifiers((rankSpecifier, args) => { foreach (var expression in rankSpecifier.Sizes) { findExpressionVariablesInRankSpecifier(expression, args); } }, (localScopeBinder: this, locals: locals, localDeclarationBinder: localFunctionDeclarationBinder)); } foreach (var constraintClause in decl.ConstraintClauses) { foreach (var constraint in constraintClause.Constraints) { if (constraint is TypeConstraintSyntax typeConstraint) { typeConstraint.Type.VisitRankSpecifiers((rankSpecifier, args) => { foreach (var expression in rankSpecifier.Sizes) { findExpressionVariablesInRankSpecifier(expression, args); } }, (localScopeBinder: this, locals: locals, localDeclarationBinder: localFunctionDeclarationBinder)); } } } } break; case SyntaxKind.ExpressionStatement: case SyntaxKind.IfStatement: case SyntaxKind.YieldReturnStatement: case SyntaxKind.ReturnStatement: case SyntaxKind.ThrowStatement: case SyntaxKind.GotoCaseStatement: ExpressionVariableFinder.FindExpressionVariables(this, locals, innerStatement, enclosingBinder.GetBinder(innerStatement) ?? enclosingBinder); break; case SyntaxKind.SwitchStatement: var switchStatement = (SwitchStatementSyntax)innerStatement; ExpressionVariableFinder.FindExpressionVariables(this, locals, innerStatement, enclosingBinder.GetBinder(switchStatement.Expression) ?? enclosingBinder); break; case SyntaxKind.LockStatement: Binder statementBinder = enclosingBinder.GetBinder(innerStatement); Debug.Assert(statementBinder != null); // Lock always has a binder. ExpressionVariableFinder.FindExpressionVariables(this, locals, innerStatement, statementBinder); break; default: // no other statement introduces local variables into the enclosing scope break; } return; static void findExpressionVariablesInRankSpecifier(ExpressionSyntax expression, (LocalScopeBinder localScopeBinder, ArrayBuilder<LocalSymbol> locals, Binder localDeclarationBinder) args) { if (expression.Kind() != SyntaxKind.OmittedArraySizeExpression) { ExpressionVariableFinder.FindExpressionVariables(args.localScopeBinder, args.locals, expression, args.localDeclarationBinder); } } } protected ImmutableArray<LocalFunctionSymbol> BuildLocalFunctions(SyntaxList<StatementSyntax> statements) { ArrayBuilder<LocalFunctionSymbol> locals = null; foreach (var statement in statements) { BuildLocalFunctions(statement, ref locals); } return locals?.ToImmutableAndFree() ?? ImmutableArray<LocalFunctionSymbol>.Empty; } internal void BuildLocalFunctions(StatementSyntax statement, ref ArrayBuilder<LocalFunctionSymbol> locals) { var innerStatement = statement; // drill into any LabeledStatements -- atomic LabelStatements have been bound into // wrapped LabeledStatements by this point while (innerStatement.Kind() == SyntaxKind.LabeledStatement) { innerStatement = ((LabeledStatementSyntax)innerStatement).Statement; } if (innerStatement.Kind() == SyntaxKind.LocalFunctionStatement) { var decl = (LocalFunctionStatementSyntax)innerStatement; if (locals == null) { locals = ArrayBuilder<LocalFunctionSymbol>.GetInstance(); } var localSymbol = MakeLocalFunction(decl); locals.Add(localSymbol); } } protected SourceLocalSymbol MakeLocal(VariableDeclarationSyntax declaration, VariableDeclaratorSyntax declarator, LocalDeclarationKind kind, bool allowScoped, Binder initializerBinderOpt = null) { return SourceLocalSymbol.MakeLocal( this.ContainingMemberOrLambda, this, allowRefKind: true, allowScoped: allowScoped, declaration.Type, declarator.Identifier, kind, declarator.Initializer, initializerBinderOpt); } protected LocalFunctionSymbol MakeLocalFunction(LocalFunctionStatementSyntax declaration) { return new LocalFunctionSymbol( this, this.ContainingMemberOrLambda, declaration); } protected void BuildLabels(SyntaxList<StatementSyntax> statements, ref ArrayBuilder<LabelSymbol> labels) { var containingMethod = (MethodSymbol)this.ContainingMemberOrLambda; foreach (var statement in statements) { BuildLabels(containingMethod, statement, ref labels); } } internal static void BuildLabels(MethodSymbol containingMethod, StatementSyntax statement, ref ArrayBuilder<LabelSymbol> labels) { while (statement.Kind() == SyntaxKind.LabeledStatement) { var labeledStatement = (LabeledStatementSyntax)statement; if (labels == null) { labels = ArrayBuilder<LabelSymbol>.GetInstance(); } var labelSymbol = new SourceLabelSymbol(containingMethod, labeledStatement.Identifier); labels.Add(labelSymbol); statement = labeledStatement.Statement; } } /// <summary> /// Call this when you are sure there is a local declaration on this token. Returns the local. /// </summary> protected override SourceLocalSymbol LookupLocal(SyntaxToken nameToken) { LocalSymbol result = null; if (LocalsMap != null && LocalsMap.TryGetValue(nameToken.ValueText, out result)) { if (result.IdentifierToken == nameToken) return (SourceLocalSymbol)result; // in error cases we might have more than one declaration of the same name in the same scope foreach (var local in this.Locals) { if (local.IdentifierToken == nameToken) { return (SourceLocalSymbol)local; } } } return base.LookupLocal(nameToken); } protected override LocalFunctionSymbol LookupLocalFunction(SyntaxToken nameToken) { LocalFunctionSymbol result = null; if (LocalFunctionsMap != null && LocalFunctionsMap.TryGetValue(nameToken.ValueText, out result)) { if (result.NameToken == nameToken) return result; // in error cases we might have more than one declaration of the same name in the same scope foreach (var local in this.LocalFunctions) { if (local.NameToken == nameToken) { return local; } } } return base.LookupLocalFunction(nameToken); } internal override void LookupSymbolsInSingleBinder( LookupResult result, string name, int arity, ConsList<TypeSymbol> basesBeingResolved, LookupOptions options, Binder originalBinder, bool diagnose, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo) { Debug.Assert(options.AreValid()); Debug.Assert(result.IsClear); if ((options & LookupOptions.LabelsOnly) != 0) { var labelsMap = this.LabelsMap; if (labelsMap != null) { LabelSymbol labelSymbol; if (labelsMap.TryGetValue(name, out labelSymbol)) { result.MergeEqual(LookupResult.Good(labelSymbol)); } } return; } var localsMap = this.LocalsMap; if (localsMap != null && (options & LookupOptions.NamespaceAliasesOnly) == 0) { LocalSymbol localSymbol; if (localsMap.TryGetValue(name, out localSymbol)) { result.MergeEqual(originalBinder.CheckViability(localSymbol, arity, options, null, diagnose, ref useSiteInfo, basesBeingResolved)); } } var localFunctionsMap = this.LocalFunctionsMap; if (localFunctionsMap != null && options.CanConsiderLocals()) { LocalFunctionSymbol localSymbol; if (localFunctionsMap.TryGetValue(name, out localSymbol)) { result.MergeEqual(originalBinder.CheckViability(localSymbol, arity, options, null, diagnose, ref useSiteInfo, basesBeingResolved)); } } } internal override void AddLookupSymbolsInfoInSingleBinder(LookupSymbolsInfo result, LookupOptions options, Binder originalBinder) { Debug.Assert(options.AreValid()); if ((options & LookupOptions.LabelsOnly) != 0) { if (this.LabelsMap != null) { foreach (var label in this.LabelsMap) { result.AddSymbol(label.Value, label.Key, 0); } } } if (options.CanConsiderLocals()) { if (this.LocalsMap != null) { foreach (var local in this.LocalsMap) { if (originalBinder.CanAddLookupSymbolInfo(local.Value, options, result, null)) { result.AddSymbol(local.Value, local.Key, 0); } } } if (this.LocalFunctionsMap != null) { foreach (var local in this.LocalFunctionsMap) { if (originalBinder.CanAddLookupSymbolInfo(local.Value, options, result, null)) { result.AddSymbol(local.Value, local.Key, 0); } } } } } private bool ReportConflictWithLocal(Symbol local, Symbol newSymbol, string name, Location newLocation, BindingDiagnosticBag diagnostics) { // Quirk of the way we represent lambda parameters. SymbolKind newSymbolKind = (object)newSymbol == null ? SymbolKind.Parameter : newSymbol.Kind; if (newSymbolKind == SymbolKind.ErrorType) return true; var declaredInThisScope = false; declaredInThisScope |= newSymbolKind == SymbolKind.Local && this.Locals.Contains((LocalSymbol)newSymbol); declaredInThisScope |= newSymbolKind == SymbolKind.Method && this.LocalFunctions.Contains((LocalFunctionSymbol)newSymbol); if (declaredInThisScope && newLocation.SourceSpan.Start >= local.GetFirstLocation().SourceSpan.Start) { // A local variable or function named '{0}' is already defined in this scope diagnostics.Add(ErrorCode.ERR_LocalDuplicate, newLocation, name); return true; } switch (newSymbolKind) { case SymbolKind.Local: case SymbolKind.Parameter: case SymbolKind.Method: case SymbolKind.TypeParameter: // A local or parameter named '{0}' cannot be declared in this scope because that name is used in an enclosing local scope to define a local or parameter diagnostics.Add(ErrorCode.ERR_LocalIllegallyOverrides, newLocation, name); return true; case SymbolKind.RangeVariable: // The range variable '{0}' conflicts with a previous declaration of '{0}' diagnostics.Add(ErrorCode.ERR_QueryRangeVariableOverrides, newLocation, name); return true; } Debug.Assert(false, "what else can be declared inside a local scope?"); diagnostics.Add(ErrorCode.ERR_InternalError, newLocation); return false; } internal virtual bool EnsureSingleDefinition(Symbol symbol, string name, Location location, BindingDiagnosticBag diagnostics) { LocalSymbol existingLocal = null; LocalFunctionSymbol existingLocalFunction = null; var localsMap = this.LocalsMap; var localFunctionsMap = this.LocalFunctionsMap; // TODO: Handle case where 'name' exists in both localsMap and localFunctionsMap. Right now locals are preferred over local functions. if ((localsMap != null && localsMap.TryGetValue(name, out existingLocal)) || (localFunctionsMap != null && localFunctionsMap.TryGetValue(name, out existingLocalFunction))) { var existingSymbol = (Symbol)existingLocal ?? existingLocalFunction; if (symbol == existingSymbol) { // reference to same symbol, by far the most common case. return false; } return ReportConflictWithLocal(existingSymbol, symbol, name, location, diagnostics); } return false; } } }