Skip to content

Commit 66b241b

Browse files
authored
Merge pull request #57009 from dotnet/merges/release/dev17.0-to-release/dev17.0-vs-deps
Merge release/dev17.0 to release/dev17.0-vs-deps
2 parents d435bb2 + e7cf432 commit 66b241b

File tree

8 files changed

+252
-94
lines changed

8 files changed

+252
-94
lines changed

src/EditorFeatures/CSharpTest/Completion/CompletionServiceTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public class C1
5252
var generatorRanCount = 0;
5353
var generator = new CallbackGenerator(onInit: _ => { }, onExecute: _ => Interlocked.Increment(ref generatorRanCount));
5454

55-
using var workspace = WorkspaceTestUtilities.CreateWorkspaceWithPartalSemantics();
55+
using var workspace = WorkspaceTestUtilities.CreateWorkspaceWithPartialSemantics();
5656
var analyzerReference = new TestGeneratorReference(generator);
5757
var project = SolutionUtilities.AddEmptyProject(workspace.CurrentSolution)
5858
.AddAnalyzerReference(analyzerReference)
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System;
6+
using System.Collections.Generic;
7+
8+
namespace Microsoft.CodeAnalysis
9+
{
10+
internal partial class SolutionState
11+
{
12+
private partial class CompilationTracker
13+
{
14+
/// <summary>
15+
/// When we're working with compilations, we often have two: a compilation that does not contain generated files
16+
/// (which we might need later to run generators again), and one that has the stale generated files that we might
17+
/// be able to reuse as well. In those cases we have to do the same transformations to both, and this gives us
18+
/// a handy way to do precisely that while not forking compilations twice if there are no generated files anywhere.
19+
/// </summary>
20+
internal readonly struct CompilationPair
21+
{
22+
public CompilationPair(Compilation withoutGeneratedDocuments, Compilation withGeneratedDocuments) : this()
23+
{
24+
CompilationWithoutGeneratedDocuments = withoutGeneratedDocuments;
25+
CompilationWithGeneratedDocuments = withGeneratedDocuments;
26+
}
27+
28+
public Compilation CompilationWithoutGeneratedDocuments { get; }
29+
public Compilation CompilationWithGeneratedDocuments { get; }
30+
31+
public CompilationPair ReplaceSyntaxTree(SyntaxTree oldTree, SyntaxTree newTree)
32+
{
33+
return WithChange(static (compilation, trees) => compilation.ReplaceSyntaxTree(trees.oldTree, trees.newTree), (oldTree, newTree));
34+
}
35+
36+
public CompilationPair AddSyntaxTree(SyntaxTree newTree)
37+
{
38+
return WithChange(static (compilation, t) => compilation.AddSyntaxTrees(t), newTree);
39+
}
40+
41+
public CompilationPair WithPreviousScriptCompilation(Compilation previousScriptCompilation)
42+
{
43+
return WithChange(static (compilation, priorCompilation) => compilation.WithScriptCompilationInfo(compilation.ScriptCompilationInfo!.WithPreviousScriptCompilation(priorCompilation)), previousScriptCompilation);
44+
}
45+
46+
public CompilationPair WithReferences(IReadOnlyCollection<MetadataReference> metadataReferences)
47+
{
48+
return WithChange(static (c, r) => c.WithReferences(r), metadataReferences);
49+
}
50+
51+
private CompilationPair WithChange<TArg>(Func<Compilation, TArg, Compilation> change, TArg arg)
52+
{
53+
var changedWithoutGeneratedDocuments = change(CompilationWithoutGeneratedDocuments, arg);
54+
55+
if (CompilationWithoutGeneratedDocuments == CompilationWithGeneratedDocuments)
56+
{
57+
// If we didn't have any generated files, then no reason to transform twice
58+
return new CompilationPair(changedWithoutGeneratedDocuments, changedWithoutGeneratedDocuments);
59+
}
60+
61+
var changedWithGeneratedDocuments = change(CompilationWithGeneratedDocuments, arg);
62+
63+
return new CompilationPair(changedWithoutGeneratedDocuments, changedWithGeneratedDocuments);
64+
}
65+
}
66+
}
67+
}
68+
}

src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.CompilationTrackerState.cs

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
using System.Collections.Generic;
77
using System.Collections.Immutable;
88
using System.Diagnostics;
9+
using System.Linq;
10+
using System.Threading;
911
using Roslyn.Utilities;
1012

1113
namespace Microsoft.CodeAnalysis
@@ -39,21 +41,55 @@ private readonly struct CompilationTrackerGeneratorInfo
3941
/// </summary>
4042
public readonly bool DocumentsAreFinal;
4143

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+
4258
public CompilationTrackerGeneratorInfo(
4359
TextDocumentStates<SourceGeneratedDocumentState> documents,
4460
GeneratorDriver? driver,
45-
bool documentsAreFinal)
61+
bool documentsAreFinal,
62+
bool documentsAreFinalAndFrozen = false)
4663
{
4764
Documents = documents;
4865
Driver = driver;
4966
DocumentsAreFinal = documentsAreFinal;
67+
DocumentsAreFinalAndFrozen = documentsAreFinalAndFrozen;
68+
69+
// If we're frozen, that implies final as well
70+
Contract.ThrowIfTrue(documentsAreFinalAndFrozen && !documentsAreFinal);
5071
}
5172

5273
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+
}
5490

5591
public CompilationTrackerGeneratorInfo WithDriver(GeneratorDriver? driver)
56-
=> Driver == driver ? this : new(Documents, driver, DocumentsAreFinal);
92+
=> Driver == driver ? this : new(Documents, driver, DocumentsAreFinal, DocumentsAreFinalAndFrozen);
5793
}
5894

5995
/// <summary>
@@ -101,6 +137,21 @@ protected CompilationTrackerState(
101137
{
102138
CompilationWithoutGeneratedDocuments = compilationWithoutGeneratedDocuments;
103139
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
104155
}
105156

106157
public static CompilationTrackerState Create(

0 commit comments

Comments
 (0)