| File: Binder\BinderFactory.cs | Web Access |
| Project: src\roslyn\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. #nullable disable using System; using System.Diagnostics; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp { internal sealed partial class BinderFactory { // key in the binder cache. // PERF: we are not using ValueTuple because its Equals is relatively slow. internal readonly struct BinderCacheKey : IEquatable<BinderCacheKey> { public readonly CSharpSyntaxNode syntaxNode; public readonly NodeUsage usage; public BinderCacheKey(CSharpSyntaxNode syntaxNode, NodeUsage usage) { this.syntaxNode = syntaxNode; this.usage = usage; } bool IEquatable<BinderCacheKey>.Equals(BinderCacheKey other) { return syntaxNode == other.syntaxNode && this.usage == other.usage; } public override int GetHashCode() { return Hash.Combine(syntaxNode.GetHashCode(), (int)usage); } public override bool Equals(object obj) { throw new NotSupportedException(); } } // This dictionary stores contexts so we don't have to recreate them, which can be // expensive. private readonly ConcurrentCache<BinderCacheKey, Binder> _binderCache; private readonly CSharpCompilation _compilation; private readonly SyntaxTree _syntaxTree; private readonly BuckStopsHereBinder _buckStopsHereBinder; private readonly bool _ignoreAccessibility; // In a typing scenario, GetBinder is regularly called with a non-zero position. // This results in a lot of allocations of BinderFactoryVisitors. Pooling them // reduces this churn to almost nothing. private static readonly ObjectPool<BinderFactoryVisitor> s_binderFactoryVisitorPool = new ObjectPool<BinderFactoryVisitor>(static () => new BinderFactoryVisitor(), 64); private readonly ObjectPool<BinderFactoryVisitor> _binderFactoryVisitorPool; internal BinderFactory(CSharpCompilation compilation, SyntaxTree syntaxTree, bool ignoreAccessibility, ObjectPool<BinderFactoryVisitor> binderFactoryVisitorPoolOpt = null) { _compilation = compilation; _syntaxTree = syntaxTree; _ignoreAccessibility = ignoreAccessibility; _binderFactoryVisitorPool = binderFactoryVisitorPoolOpt ?? s_binderFactoryVisitorPool; // 50 is more or less a guess, but it seems to work fine for scenarios that I tried. // we need something big enough to keep binders for most classes and some methods // in a typical syntax tree. // On the other side, note that the whole factory is weakly referenced and therefore short lived, // making this cache big is not very useful. // I noticed that while compiling Roslyn C# compiler most caches never see // more than 50 items added before getting collected. _binderCache = new ConcurrentCache<BinderCacheKey, Binder>(50); _buckStopsHereBinder = new BuckStopsHereBinder(compilation, FileIdentifier.Create(syntaxTree, compilation.Options.SourceReferenceResolver)); } internal SyntaxTree SyntaxTree { get { return _syntaxTree; } } private bool InScript { get { return _syntaxTree.Options.Kind == SourceCodeKind.Script; } } /// <summary> /// Return binder for binding at node. /// <paramref name="memberDeclarationOpt"/> and <paramref name="memberOpt"/> /// are optional syntax and symbol for the member containing <paramref name="node"/>. /// If provided, the <see cref="BinderFactoryVisitor"/> will use the member symbol rather /// than looking up the member in the containing type, allowing this method to be called /// while calculating the member list. /// </summary> /// <remarks> /// Note, there is no guarantee that the factory always gives back the same binder instance for the same node. /// </remarks> internal Binder GetBinder(SyntaxNode node, CSharpSyntaxNode memberDeclarationOpt = null, Symbol memberOpt = null) { int position = node.SpanStart; // Unless this is interactive retrieving a binder for global statements // at the very top-level (i.e. in a completely empty file) use // node.Parent to maintain existing behavior. if ((!InScript || node.Kind() != SyntaxKind.CompilationUnit) && node.Parent != null) { node = node.Parent; } return GetBinder(node, position, memberDeclarationOpt, memberOpt); } internal Binder GetBinder(SyntaxNode node, int position, CSharpSyntaxNode memberDeclarationOpt = null, Symbol memberOpt = null) { Debug.Assert(node != null); #if DEBUG if (memberOpt is { ContainingSymbol: SourceMemberContainerTypeSymbol container }) { container.AssertMemberExposure(memberOpt); } #endif BinderFactoryVisitor visitor = GetBinderFactoryVisitor(position, memberDeclarationOpt, memberOpt); Binder result = visitor.Visit(node); ClearBinderFactoryVisitor(visitor); return result; } private BinderFactoryVisitor GetBinderFactoryVisitor(int position, CSharpSyntaxNode memberDeclarationOpt, Symbol memberOpt) { BinderFactoryVisitor visitor = _binderFactoryVisitorPool.Allocate(); visitor.Initialize(factory: this, position, memberDeclarationOpt, memberOpt); return visitor; } private void ClearBinderFactoryVisitor(BinderFactoryVisitor visitor) { visitor.Clear(); _binderFactoryVisitorPool.Free(visitor); } internal InMethodBinder GetPrimaryConstructorInMethodBinder(SynthesizedPrimaryConstructor constructor) { var typeDecl = constructor.GetSyntax(); Debug.Assert(typeDecl.ParameterList is not null); var extraInfo = NodeUsage.ConstructorBodyOrInitializer; var key = BinderFactoryVisitor.CreateBinderCacheKey(typeDecl, extraInfo); if (!_binderCache.TryGetValue(key, out Binder resultBinder)) { // Ctors cannot be generic Debug.Assert(constructor.Arity == 0, "Generic Ctor, What to do?"); resultBinder = new InMethodBinder(constructor, GetInTypeBodyBinder(typeDecl)); _binderCache.TryAdd(key, resultBinder); } return (InMethodBinder)resultBinder; } internal Binder GetInTypeBodyBinder(TypeDeclarationSyntax typeDecl) { BinderFactoryVisitor visitor = GetBinderFactoryVisitor(position: typeDecl.SpanStart, memberDeclarationOpt: null, memberOpt: null); Binder resultBinder = visitor.VisitTypeDeclarationCore(typeDecl, NodeUsage.NamedTypeBodyOrTypeParameters); ClearBinderFactoryVisitor(visitor); return resultBinder; } internal Binder GetInNamespaceBinder(CSharpSyntaxNode unit) { switch (unit.Kind()) { case SyntaxKind.NamespaceDeclaration: case SyntaxKind.FileScopedNamespaceDeclaration: { BinderFactoryVisitor visitor = GetBinderFactoryVisitor(0, null, null); Binder result = visitor.VisitNamespaceDeclaration((BaseNamespaceDeclarationSyntax)unit, unit.SpanStart, inBody: true, inUsing: false); ClearBinderFactoryVisitor(visitor); return result; } case SyntaxKind.CompilationUnit: // imports are bound by the Script class binder: { BinderFactoryVisitor visitor = GetBinderFactoryVisitor(0, null, null); Binder result = visitor.VisitCompilationUnit((CompilationUnitSyntax)unit, inUsing: false, inScript: InScript); ClearBinderFactoryVisitor(visitor); return result; } default: return null; } } } }