diff --git a/src/Features/Core/Portable/Diagnostics/AnalyzerHelper.cs b/src/Features/Core/Portable/Diagnostics/AnalyzerHelper.cs index de4d8eca38e1bc4a982e4c8bde92752fa068db9e..6adff93c7c991908c64a3763aa048caf201ecc10 100644 --- a/src/Features/Core/Portable/Diagnostics/AnalyzerHelper.cs +++ b/src/Features/Core/Portable/Diagnostics/AnalyzerHelper.cs @@ -128,12 +128,12 @@ public static bool IsCompilerAnalyzer(this DiagnosticAnalyzer analyzer) return false; } - public static ValueTuple GetAnalyzerIdAndVersion(this DiagnosticAnalyzer analyzer) + public static (string analyzerId, VersionStamp version) GetAnalyzerIdAndVersion(this DiagnosticAnalyzer analyzer) { // Get the unique ID for given diagnostic analyzer. // note that we also put version stamp so that we can detect changed analyzer. var typeInfo = analyzer.GetType().GetTypeInfo(); - return ValueTuple.Create(analyzer.GetAnalyzerId(), GetAnalyzerVersion(CorLightup.Desktop.GetAssemblyLocation(typeInfo.Assembly))); + return (analyzer.GetAnalyzerId(), GetAnalyzerVersion(CorLightup.Desktop.GetAssemblyLocation(typeInfo.Assembly))); } public static string GetAnalyzerAssemblyName(this DiagnosticAnalyzer analyzer) diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateSet.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateSet.cs index 7cf84e2c6f61699c984058f2d697d376555d2596..b9b1bdbe758d78dd284ad3bfc012528de6f31975 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateSet.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateSet.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -17,20 +18,13 @@ internal partial class DiagnosticIncrementalAnalyzer /// private class StateSet { - private const string UserDiagnosticsPrefixTableName = ""; - private readonly string _language; private readonly DiagnosticAnalyzer _analyzer; private readonly string _errorSourceName; // analyzer version this state belong to private readonly VersionStamp _analyzerVersion; - - // name of each analysis kind persistent storage - private readonly string _stateName; - private readonly string _syntaxStateName; - private readonly string _semanticStateName; - private readonly string _nonLocalStateName; + private readonly AnalyzerTypeData _analyzerTypeData; private readonly ConcurrentDictionary _activeFileStates; private readonly ConcurrentDictionary _projectStates; @@ -41,23 +35,19 @@ public StateSet(string language, DiagnosticAnalyzer analyzer, string errorSource _analyzer = analyzer; _errorSourceName = errorSourceName; - var nameAndVersion = GetNameAndVersion(_analyzer); - _analyzerVersion = nameAndVersion.Item2; - - _stateName = nameAndVersion.Item1; - - _syntaxStateName = _stateName + ".Syntax"; - _semanticStateName = _stateName + ".Semantic"; - _nonLocalStateName = _stateName + ".NonLocal"; + var (analyzerId, version) = _analyzer.GetAnalyzerIdAndVersion(); + _analyzerVersion = version; + _analyzerTypeData = AnalyzerTypeData.ForType(_analyzer.GetType()); + Debug.Assert(_analyzerTypeData.StateName == $"{AnalyzerTypeData.UserDiagnosticsPrefixTableName}_{analyzerId}", "Expected persistence information for analyzer instance to be derived from its type alone."); _activeFileStates = new ConcurrentDictionary(concurrencyLevel: 2, capacity: 10); _projectStates = new ConcurrentDictionary(concurrencyLevel: 2, capacity: 1); } - public string StateName => _stateName; - public string SyntaxStateName => _syntaxStateName; - public string SemanticStateName => _semanticStateName; - public string NonLocalStateName => _nonLocalStateName; + public string StateName => _analyzerTypeData.StateName; + public string SyntaxStateName => _analyzerTypeData.SyntaxStateName; + public string SemanticStateName => _analyzerTypeData.SemanticStateName; + public string NonLocalStateName => _analyzerTypeData.NonLocalStateName; public string Language => _language; public string ErrorSourceName => _errorSourceName; @@ -265,19 +255,34 @@ public void OnRemoved() InMemoryStorage.DropCache(Analyzer); } - /// - /// Get the unique state name for the given analyzer. - /// Note that this name is used by the underlying persistence stream of the corresponding to Read/Write diagnostic data into the stream. - /// If any two distinct analyzer have the same diagnostic state name, we will end up sharing the persistence stream between them, leading to duplicate/missing/incorrect diagnostic data. - /// - private static ValueTuple GetNameAndVersion(DiagnosticAnalyzer analyzer) + private sealed class AnalyzerTypeData { - Contract.ThrowIfNull(analyzer); + internal const string UserDiagnosticsPrefixTableName = ""; + private static readonly ConcurrentDictionary s_analyzerTypeData + = new ConcurrentDictionary(); - // Get the unique ID for given diagnostic analyzer. - // note that we also put version stamp so that we can detect changed analyzer. - var tuple = analyzer.GetAnalyzerIdAndVersion(); - return ValueTuple.Create(UserDiagnosticsPrefixTableName + "_" + tuple.Item1, tuple.Item2); + private AnalyzerTypeData(Type type) + { + StateName = UserDiagnosticsPrefixTableName + "_" + type.AssemblyQualifiedName; + SyntaxStateName = StateName + ".Syntax"; + SemanticStateName = StateName + ".Semantic"; + NonLocalStateName = StateName + ".NonLocal"; + } + + /// + /// Get the unique state name for the given analyzer. + /// Note that this name is used by the underlying persistence stream of the corresponding to Read/Write diagnostic data into the stream. + /// If any two distinct analyzer have the same diagnostic state name, we will end up sharing the persistence stream between them, leading to duplicate/missing/incorrect diagnostic data. + /// + public string StateName { get; } + public string SyntaxStateName { get; } + public string SemanticStateName { get; } + public string NonLocalStateName { get; } + + public static AnalyzerTypeData ForType(Type type) + { + return s_analyzerTypeData.GetOrAdd(type, t => new AnalyzerTypeData(t)); + } } } }