6
6
using System . Runtime . CompilerServices ;
7
7
using System . Threading ;
8
8
using System . Threading . Tasks ;
9
+ using Microsoft . CodeAnalysis . Diagnostics ;
9
10
using Microsoft . CodeAnalysis . LanguageService ;
10
11
using Microsoft . CodeAnalysis . Storage ;
11
12
using Roslyn . Utilities ;
@@ -21,26 +22,46 @@ private sealed class ProjectIndex(
21
22
MultiDictionary < DocumentId , DeclaredSymbolInfo > delegates ,
22
23
MultiDictionary < string , ( DocumentId , DeclaredSymbolInfo ) > namedTypes )
23
24
{
24
- private static readonly ConditionalWeakTable < ProjectState , AsyncLazy < ProjectIndex > > s_projectToIndex = new ( ) ;
25
+ /// <summary>
26
+ /// We cache the project instance per <see cref="ProjectState"/>. This allows us to reuse it over a wide set of
27
+ /// changes (for example, changing completely unrelated projects that a particular project doesn't depend on).
28
+ /// However, <see cref="ProjectState"/> doesn't change even when certain things change that will create a
29
+ /// substantively different <see cref="Project"/>. For example, if the <see
30
+ /// cref="SourceGeneratorExecutionVersion"/> for the project changes, we'll still have the same project state.
31
+ /// As such, we store the <see cref="Checksum"/> of the project as well, ensuring that if anything in it or its
32
+ /// dependencies changes, we recompute the index.
33
+ /// </summary>
34
+ private static readonly ConditionalWeakTable < ProjectState , StrongBox < ( Checksum checksum , AsyncLazy < ProjectIndex > lazyProjectIndex ) > > s_projectToIndex = new ( ) ;
25
35
26
36
public readonly MultiDictionary < DocumentId , DeclaredSymbolInfo > ClassesAndRecordsThatMayDeriveFromSystemObject = classesAndRecordsThatMayDeriveFromSystemObject ;
27
37
public readonly MultiDictionary < DocumentId , DeclaredSymbolInfo > ValueTypes = valueTypes ;
28
38
public readonly MultiDictionary < DocumentId , DeclaredSymbolInfo > Enums = enums ;
29
39
public readonly MultiDictionary < DocumentId , DeclaredSymbolInfo > Delegates = delegates ;
30
40
public readonly MultiDictionary < string , ( DocumentId , DeclaredSymbolInfo ) > NamedTypes = namedTypes ;
31
41
32
- public static Task < ProjectIndex > GetIndexAsync (
42
+ public static async Task < ProjectIndex > GetIndexAsync (
33
43
Project project , CancellationToken cancellationToken )
34
44
{
35
- if ( ! s_projectToIndex . TryGetValue ( project . State , out var lazyIndex ) )
45
+ // Use the checksum of the project. That way if its state *or* SG info changes, we compute a new index with
46
+ // accurate information in it.
47
+ var checksum = await project . GetDiagnosticChecksumAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
48
+ if ( ! s_projectToIndex . TryGetValue ( project . State , out var tuple ) ||
49
+ tuple . Value . checksum != checksum )
36
50
{
37
- lazyIndex = s_projectToIndex . GetValue (
38
- project . State , p => AsyncLazy . Create (
39
- static ( project , c ) => CreateIndexAsync ( project , c ) ,
40
- project ) ) ;
51
+ tuple = new ( ( checksum , AsyncLazy . Create ( CreateIndexAsync , project ) ) ) ;
52
+
53
+ #if NET
54
+ s_projectToIndex . AddOrUpdate ( project . State , tuple ) ;
55
+ #else
56
+ // Best effort try to update the map with the new data.
57
+ s_projectToIndex . Remove ( project . State ) ;
58
+ // Note: intentionally ignore the return value here. We want to use the value we've computed even if
59
+ // another thread beats us to adding things here.
60
+ _ = s_projectToIndex . GetValue ( project . State , _ => tuple ) ;
61
+ #endif
41
62
}
42
63
43
- return lazyIndex . GetValueAsync ( cancellationToken ) ;
64
+ return await tuple . Value . lazyProjectIndex . GetValueAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
44
65
}
45
66
46
67
private static async Task < ProjectIndex > CreateIndexAsync ( Project project , CancellationToken cancellationToken )
0 commit comments