File: DiagnosticAnalyzer\CachingSemanticModelProvider.cs
Web Access
Project: src\src\Compilers\Core\Portable\Microsoft.CodeAnalysis.csproj (Microsoft.CodeAnalysis)
// 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.
 
#pragma warning disable RSEXPERIMENTAL001 // internal usage of experimental API
 
using System;
using System.Collections.Concurrent;
using System.Runtime.CompilerServices;
 
namespace Microsoft.CodeAnalysis.Diagnostics
{
    /// <summary>
    /// Provider that caches semantic models for requested trees, with a strong reference to the model.
    /// Clients using this provider are responsible for maintaining the lifetime of the entries in this cache,
    /// and should invoke <see cref="ClearCache(SyntaxTree, Compilation)"/> and <see cref="ClearCache(Compilation)"/> to clear entries when appropriate.
    /// For example, <see cref="CompilationWithAnalyzers"/> uses this provider to ensure that semantic model instances
    /// are shared between the compiler and analyzers for improved analyzer execution performance. The underlying
    /// <see cref="AnalyzerDriver"/> executing analyzers clears per-tree entries in the cache whenever a <see cref="CompilationUnitCompletedEvent"/>
    /// has been processed, indicating all relevant analyzers have executed on the corresponding syntax tree for the event.
    /// Similarly, it clears the entire compilation wide cache whenever a <see cref="CompilationCompletedEvent"/> has been processed,
    /// indicating all relevant analyzers have executed on the entire compilation.
    /// </summary>
    internal sealed class CachingSemanticModelProvider : SemanticModelProvider
    {
        private static readonly ConditionalWeakTable<Compilation, PerCompilationProvider>.CreateValueCallback s_createProviderCallback
            = new ConditionalWeakTable<Compilation, PerCompilationProvider>.CreateValueCallback(compilation => new PerCompilationProvider(compilation));
 
        private readonly ConditionalWeakTable<Compilation, PerCompilationProvider> _providerCache;
 
        public CachingSemanticModelProvider()
        {
            _providerCache = new ConditionalWeakTable<Compilation, PerCompilationProvider>();
        }
 
        public override SemanticModel GetSemanticModel(SyntaxTree tree, Compilation compilation, SemanticModelOptions options = default)
            => _providerCache.GetValue(compilation, s_createProviderCallback).GetSemanticModel(tree, options);
 
        internal void ClearCache(SyntaxTree tree, Compilation compilation)
        {
            if (_providerCache.TryGetValue(compilation, out var provider))
            {
                provider.ClearCachedSemanticModel(tree);
            }
        }
 
        internal void ClearCache(Compilation compilation)
        {
            _providerCache.Remove(compilation);
        }
 
        private sealed class PerCompilationProvider
        {
            private readonly Compilation _compilation;
            private readonly ConcurrentDictionary<SyntaxTree, SemanticModel> _semanticModelsMap;
 
            // Cached delegate to avoid allocations in ConcurrentDictionary.GetOrAdd invocations.
            // We only care about caching semantic models for internal callers, which use the default 'ignoreAccessibility = false'.
            private readonly Func<SyntaxTree, SemanticModel> _createSemanticModel;
 
            public PerCompilationProvider(Compilation compilation)
            {
                _compilation = compilation;
                _semanticModelsMap = new ConcurrentDictionary<SyntaxTree, SemanticModel>();
                _createSemanticModel = tree => compilation.CreateSemanticModel(tree, options: default);
            }
 
            public SemanticModel GetSemanticModel(SyntaxTree tree, SemanticModelOptions options)
            {
                // We only care about caching semantic models for internal callers, which use the default 'ignoreAccessibility = false'.
                return options == SemanticModelOptions.None
                    ? _semanticModelsMap.GetOrAdd(tree, _createSemanticModel)
                    : _compilation.CreateSemanticModel(tree, options);
            }
 
            public void ClearCachedSemanticModel(SyntaxTree tree)
            {
                _semanticModelsMap.TryRemove(tree, out _);
            }
        }
    }
}