|
6 | 6 | using System.Collections.Generic; |
7 | 7 | using System.Collections.Immutable; |
8 | 8 | using System.Diagnostics; |
| 9 | +using System.Linq; |
| 10 | +using System.Threading; |
9 | 11 | using Roslyn.Utilities; |
10 | 12 |
|
11 | 13 | namespace Microsoft.CodeAnalysis |
@@ -39,21 +41,55 @@ private readonly struct CompilationTrackerGeneratorInfo |
39 | 41 | /// </summary> |
40 | 42 | public readonly bool DocumentsAreFinal; |
41 | 43 |
|
| 44 | + /// <summary> |
| 45 | + /// Whether the generated documents are frozen and generators should never be ran again, ever, even if a document |
| 46 | + /// is later changed. This is used to ensure that when we produce a frozen solution for partial semantics, |
| 47 | + /// further downstream forking of that solution won't rerun generators. This is because of two reasons: |
| 48 | + /// <list type="number"> |
| 49 | + /// <item>Generally once we've produced a frozen solution with partial semantics, we now want speed rather |
| 50 | + /// than accuracy; a generator running in a later path will still cause issues there.</item> |
| 51 | + /// <item>The frozen solution with partial semantics makes no guarantee that other syntax trees exist or |
| 52 | + /// whether we even have references -- it's pretty likely that running a generator might produce worse results |
| 53 | + /// than what we originally had.</item> |
| 54 | + /// </list> |
| 55 | + /// </summary> |
| 56 | + public readonly bool DocumentsAreFinalAndFrozen; |
| 57 | + |
42 | 58 | public CompilationTrackerGeneratorInfo( |
43 | 59 | TextDocumentStates<SourceGeneratedDocumentState> documents, |
44 | 60 | GeneratorDriver? driver, |
45 | | - bool documentsAreFinal) |
| 61 | + bool documentsAreFinal, |
| 62 | + bool documentsAreFinalAndFrozen = false) |
46 | 63 | { |
47 | 64 | Documents = documents; |
48 | 65 | Driver = driver; |
49 | 66 | DocumentsAreFinal = documentsAreFinal; |
| 67 | + DocumentsAreFinalAndFrozen = documentsAreFinalAndFrozen; |
| 68 | + |
| 69 | + // If we're frozen, that implies final as well |
| 70 | + Contract.ThrowIfTrue(documentsAreFinalAndFrozen && !documentsAreFinal); |
50 | 71 | } |
51 | 72 |
|
52 | 73 | public CompilationTrackerGeneratorInfo WithDocumentsAreFinal(bool documentsAreFinal) |
53 | | - => DocumentsAreFinal == documentsAreFinal ? this : new(Documents, Driver, documentsAreFinal); |
| 74 | + { |
| 75 | + // If we're already frozen, then we won't do anything even if somebody calls WithDocumentsAreFinal(false); |
| 76 | + // this for example would happen if we had a frozen snapshot, and then we fork it further with additional changes. |
| 77 | + // In that case we would be calling WithDocumentsAreFinal(false) to force generators to run again, but if we've |
| 78 | + // frozen in partial semantics, we're done running them period. So we'll just keep treating them as final, |
| 79 | + // no matter the wishes of the caller. |
| 80 | + if (DocumentsAreFinalAndFrozen || DocumentsAreFinal == documentsAreFinal) |
| 81 | + return this; |
| 82 | + else |
| 83 | + return new(Documents, Driver, documentsAreFinal); |
| 84 | + } |
| 85 | + |
| 86 | + public CompilationTrackerGeneratorInfo WithDocumentsAreFinalAndFrozen() |
| 87 | + { |
| 88 | + return DocumentsAreFinalAndFrozen ? this : new(Documents, Driver, documentsAreFinal: true, documentsAreFinalAndFrozen: true); |
| 89 | + } |
54 | 90 |
|
55 | 91 | public CompilationTrackerGeneratorInfo WithDriver(GeneratorDriver? driver) |
56 | | - => Driver == driver ? this : new(Documents, driver, DocumentsAreFinal); |
| 92 | + => Driver == driver ? this : new(Documents, driver, DocumentsAreFinal, DocumentsAreFinalAndFrozen); |
57 | 93 | } |
58 | 94 |
|
59 | 95 | /// <summary> |
@@ -101,6 +137,21 @@ protected CompilationTrackerState( |
101 | 137 | { |
102 | 138 | CompilationWithoutGeneratedDocuments = compilationWithoutGeneratedDocuments; |
103 | 139 | GeneratorInfo = generatorInfo; |
| 140 | + |
| 141 | +#if DEBUG |
| 142 | + |
| 143 | + // As a sanity check, we should never see the generated trees inside of the compilation that should not |
| 144 | + // have generated trees. |
| 145 | + var compilation = compilationWithoutGeneratedDocuments?.GetValueOrNull(); |
| 146 | + |
| 147 | + if (compilation != null) |
| 148 | + { |
| 149 | + foreach (var generatedDocument in generatorInfo.Documents.States.Values) |
| 150 | + { |
| 151 | + Contract.ThrowIfTrue(compilation.SyntaxTrees.Contains(generatedDocument.GetSyntaxTree(CancellationToken.None))); |
| 152 | + } |
| 153 | + } |
| 154 | +#endif |
104 | 155 | } |
105 | 156 |
|
106 | 157 | public static CompilationTrackerState Create( |
|
0 commit comments