Skip to content

Commit c407108

Browse files
authored
Ensure FindSourceDefinitionAsync works in frozen compilation cases (#78195)
(This is the main branch version of #78164) FindSourceDefinitionAsync was assuming that in the cross-language case, if you have a symbol from another project, that the compilation must always be available for that project -- for example, that calling TryGetCompilation on the project must always work. That's not the case if we're not generating skeleton references all the time. The fix to make that method work reliably again is simply to call GetCompilationAsync(). This results in a cascade of making more things async though: although our public entrypoint for FindSourceDefinitionAsync was Task-returning, it wasn't async and called into a non-async helper. So this removes those internal helpers and makes everything async again. Fixes https://devdiv.visualstudio.com/DevDiv/_workitems/edit/2443981 Fixes https://developercommunity.visualstudio.com/t/Go-to-definition-stopped-working-after-u/10796494
2 parents 749a789 + 39e3a54 commit c407108

File tree

32 files changed

+246
-171
lines changed

32 files changed

+246
-171
lines changed

src/Analyzers/Core/CodeFixes/GenerateConstructor/AbstractGenerateConstructorService.State.cs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -90,17 +90,17 @@ private async Task<bool> TryInitializeAsync(
9090
{
9191
if (_service.IsConstructorInitializerGeneration(_document, node, cancellationToken))
9292
{
93-
if (!TryInitializeConstructorInitializerGeneration(node, cancellationToken))
93+
if (!(await TryInitializeConstructorInitializerGenerationAsync(node, cancellationToken).ConfigureAwait(false)))
9494
return false;
9595
}
9696
else if (_service.IsSimpleNameGeneration(_document, node, cancellationToken))
9797
{
98-
if (!TryInitializeSimpleNameGeneration(node, cancellationToken))
98+
if (!(await TryInitializeSimpleNameGenerationAsync(node, cancellationToken).ConfigureAwait(false)))
9999
return false;
100100
}
101101
else if (_service.IsImplicitObjectCreation(_document, node, cancellationToken))
102102
{
103-
if (!TryInitializeImplicitObjectCreation(node, cancellationToken))
103+
if (!(await TryInitializeImplicitObjectCreationAsync(node, cancellationToken).ConfigureAwait(false)))
104104
return false;
105105
}
106106
else
@@ -296,7 +296,7 @@ private static ITypeSymbol FixType(ITypeSymbol typeSymbol, SemanticModel semanti
296296
.RemoveUnnamedErrorTypes(compilation);
297297
}
298298

299-
private bool TryInitializeConstructorInitializerGeneration(
299+
private async ValueTask<bool> TryInitializeConstructorInitializerGenerationAsync(
300300
SyntaxNode constructorInitializer, CancellationToken cancellationToken)
301301
{
302302
if (_service.TryInitializeConstructorInitializerGeneration(
@@ -309,13 +309,13 @@ private bool TryInitializeConstructorInitializerGeneration(
309309

310310
var semanticInfo = _document.SemanticModel.GetSymbolInfo(constructorInitializer, cancellationToken);
311311
if (semanticInfo.Symbol == null)
312-
return TryDetermineTypeToGenerateIn(typeToGenerateIn, cancellationToken);
312+
return await TryDetermineTypeToGenerateInAsync(typeToGenerateIn, cancellationToken).ConfigureAwait(false);
313313
}
314314

315315
return false;
316316
}
317317

318-
private bool TryInitializeImplicitObjectCreation(SyntaxNode implicitObjectCreation, CancellationToken cancellationToken)
318+
private async ValueTask<bool> TryInitializeImplicitObjectCreationAsync(SyntaxNode implicitObjectCreation, CancellationToken cancellationToken)
319319
{
320320
if (_service.TryInitializeImplicitObjectCreation(
321321
_document, implicitObjectCreation, cancellationToken,
@@ -326,13 +326,13 @@ private bool TryInitializeImplicitObjectCreation(SyntaxNode implicitObjectCreati
326326

327327
var semanticInfo = _document.SemanticModel.GetSymbolInfo(implicitObjectCreation, cancellationToken);
328328
if (semanticInfo.Symbol == null)
329-
return TryDetermineTypeToGenerateIn(typeToGenerateIn, cancellationToken);
329+
return await TryDetermineTypeToGenerateInAsync(typeToGenerateIn, cancellationToken).ConfigureAwait(false);
330330
}
331331

332332
return false;
333333
}
334334

335-
private bool TryInitializeSimpleNameGeneration(
335+
private async ValueTask<bool> TryInitializeSimpleNameGenerationAsync(
336336
SyntaxNode simpleName,
337337
CancellationToken cancellationToken)
338338
{
@@ -359,7 +359,7 @@ private bool TryInitializeSimpleNameGeneration(
359359

360360
cancellationToken.ThrowIfCancellationRequested();
361361

362-
return TryDetermineTypeToGenerateIn(typeToGenerateIn, cancellationToken);
362+
return await TryDetermineTypeToGenerateInAsync(typeToGenerateIn, cancellationToken).ConfigureAwait(false);
363363
}
364364

365365
private static bool IsValidAttributeParameterType(ITypeSymbol type)
@@ -397,10 +397,10 @@ private static bool IsValidAttributeParameterType(ITypeSymbol type)
397397
}
398398
}
399399

400-
private bool TryDetermineTypeToGenerateIn(
400+
private async ValueTask<bool> TryDetermineTypeToGenerateInAsync(
401401
INamedTypeSymbol original, CancellationToken cancellationToken)
402402
{
403-
var definition = SymbolFinderInternal.FindSourceDefinition(original, _document.Project.Solution, cancellationToken);
403+
var definition = await SymbolFinderInternal.FindSourceDefinitionAsync(original, _document.Project.Solution, cancellationToken).ConfigureAwait(false);
404404
TypeToGenerateIn = definition as INamedTypeSymbol;
405405

406406
return TypeToGenerateIn?.TypeKind is (TypeKind?)TypeKind.Class or (TypeKind?)TypeKind.Struct;

src/Analyzers/Core/CodeFixes/GenerateEnumMember/AbstractGenerateEnumMemberService.State.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
using System.Linq;
66
using System.Threading;
7+
using System.Threading.Tasks;
78
using Microsoft.CodeAnalysis.CodeGeneration;
89
using Microsoft.CodeAnalysis.FindSymbols;
910
using Microsoft.CodeAnalysis.LanguageService;
@@ -23,17 +24,17 @@ private sealed partial class State
2324
public TSimpleNameSyntax SimpleName { get; private set; } = null!;
2425
public TExpressionSyntax SimpleNameOrMemberAccessExpression { get; private set; } = null!;
2526

26-
public static State? Generate(
27+
public static async Task<State?> GenerateAsync(
2728
TService service,
2829
SemanticDocument document,
2930
SyntaxNode node,
3031
CancellationToken cancellationToken)
3132
{
3233
var state = new State();
33-
return state.TryInitialize(service, document, node, cancellationToken) ? state : null;
34+
return await state.TryInitializeAsync(service, document, node, cancellationToken).ConfigureAwait(false) ? state : null;
3435
}
3536

36-
private bool TryInitialize(
37+
private async ValueTask<bool> TryInitializeAsync(
3738
TService service,
3839
SemanticDocument document,
3940
SyntaxNode node,
@@ -63,7 +64,7 @@ private bool TryInitialize(
6364
}
6465

6566
cancellationToken.ThrowIfCancellationRequested();
66-
var sourceType = SymbolFinderInternal.FindSourceDefinition(TypeToGenerateIn, document.Project.Solution, cancellationToken) as INamedTypeSymbol;
67+
var sourceType = (await SymbolFinderInternal.FindSourceDefinitionAsync(TypeToGenerateIn, document.Project.Solution, cancellationToken).ConfigureAwait(false)) as INamedTypeSymbol;
6768
if (!ValidateTypeToGenerateIn(sourceType, true, EnumType))
6869
return false;
6970

src/Analyzers/Core/CodeFixes/GenerateEnumMember/AbstractGenerateEnumMemberService.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public async Task<ImmutableArray<CodeAction>> GenerateEnumMemberAsync(Document d
2727
using (Logger.LogBlock(FunctionId.Refactoring_GenerateMember_GenerateEnumMember, cancellationToken))
2828
{
2929
var semanticDocument = await SemanticDocument.CreateAsync(document, cancellationToken).ConfigureAwait(false);
30-
var state = State.Generate((TService)this, semanticDocument, node, cancellationToken);
30+
var state = await State.GenerateAsync((TService)this, semanticDocument, node, cancellationToken).ConfigureAwait(false);
3131
if (state == null)
3232
{
3333
return [];

src/Analyzers/Core/CodeFixes/GenerateParameterizedMember/AbstractGenerateParameterizedMemberService.State.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ public Location Location
5858
protected async Task<bool> TryFinishInitializingStateAsync(TService service, SemanticDocument document, CancellationToken cancellationToken)
5959
{
6060
cancellationToken.ThrowIfCancellationRequested();
61-
TypeToGenerateIn = SymbolFinderInternal.FindSourceDefinition(TypeToGenerateIn, document.Project.Solution, cancellationToken) as INamedTypeSymbol;
61+
TypeToGenerateIn = await SymbolFinderInternal.FindSourceDefinitionAsync(TypeToGenerateIn, document.Project.Solution, cancellationToken).ConfigureAwait(false) as INamedTypeSymbol;
6262
if (TypeToGenerateIn.IsErrorType())
6363
{
6464
return false;

src/Analyzers/Core/CodeFixes/GenerateVariable/AbstractGenerateVariableService.State.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using System.Collections.Immutable;
99
using System.Linq;
1010
using System.Threading;
11+
using System.Threading.Tasks;
1112
using Microsoft.CodeAnalysis;
1213
using Microsoft.CodeAnalysis.CodeGeneration;
1314
using Microsoft.CodeAnalysis.FindSymbols;
@@ -69,14 +70,14 @@ private State(
6970
_document = document;
7071
}
7172

72-
public static State Generate(
73+
public static async ValueTask<State> GenerateAsync(
7374
TService service,
7475
SemanticDocument document,
7576
SyntaxNode interfaceNode,
7677
CancellationToken cancellationToken)
7778
{
7879
var state = new State(service, document);
79-
return state.TryInitialize(interfaceNode, cancellationToken) ? state : null;
80+
return await state.TryInitializeAsync(interfaceNode, cancellationToken).ConfigureAwait(false) ? state : null;
8081
}
8182

8283
public Accessibility DetermineMaximalAccessibility()
@@ -102,7 +103,7 @@ public Accessibility DetermineMaximalAccessibility()
102103
return accessibility;
103104
}
104105

105-
private bool TryInitialize(
106+
private async ValueTask<bool> TryInitializeAsync(
106107
SyntaxNode node, CancellationToken cancellationToken)
107108
{
108109
if (_service.IsIdentifierNameGeneration(node))
@@ -148,8 +149,8 @@ private bool TryInitialize(
148149
return false;
149150
}
150151

151-
TypeToGenerateIn = SymbolFinderInternal.FindSourceDefinition(
152-
TypeToGenerateIn, _document.Project.Solution, cancellationToken) as INamedTypeSymbol;
152+
TypeToGenerateIn = await SymbolFinder.FindSourceDefinitionAsync(
153+
TypeToGenerateIn, _document.Project.Solution, cancellationToken).ConfigureAwait(false) as INamedTypeSymbol;
153154

154155
if (!ValidateTypeToGenerateIn(TypeToGenerateIn, IsStatic, ClassInterfaceModuleStructTypes))
155156
{

src/Analyzers/Core/CodeFixes/GenerateVariable/AbstractGenerateVariableService.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public async Task<ImmutableArray<CodeAction>> GenerateVariableAsync(
3838
{
3939
var semanticDocument = await SemanticDocument.CreateAsync(document, cancellationToken).ConfigureAwait(false);
4040

41-
var state = State.Generate((TService)this, semanticDocument, node, cancellationToken);
41+
var state = await State.GenerateAsync((TService)this, semanticDocument, node, cancellationToken).ConfigureAwait(false);
4242
if (state == null)
4343
return [];
4444

src/Analyzers/Core/CodeFixes/UnsealClass/AbstractUnsealClassCodeFixProvider.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context)
3939
if (semanticModel.GetSymbolInfo(node, cancellationToken).Symbol is INamedTypeSymbol type &&
4040
type.TypeKind == TypeKind.Class && type.IsSealed && !type.IsStatic)
4141
{
42-
var definition = SymbolFinderInternal.FindSourceDefinition(
43-
type, document.Project.Solution, cancellationToken);
42+
var definition = await SymbolFinderInternal.FindSourceDefinitionAsync(
43+
type, document.Project.Solution, cancellationToken).ConfigureAwait(false);
4444
if (definition is not null && definition.DeclaringSyntaxReferences.Length > 0)
4545
{
4646
var title = string.Format(TitleFormat, type.Name);

src/EditorFeatures/Core.Wpf/Peek/PeekableItemFactory.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ public async Task<IEnumerable<IPeekableItem>> GetPeekableItemsAsync(
5959
throw new ArgumentNullException(nameof(peekResultFactory));
6060

6161
var solution = project.Solution;
62-
symbol = SymbolFinder.FindSourceDefinition(symbol, solution, cancellationToken) ?? symbol;
63-
symbol = GoToDefinitionFeatureHelpers.TryGetPreferredSymbol(solution, symbol, cancellationToken);
62+
symbol = await SymbolFinder.FindSourceDefinitionAsync(symbol, solution, cancellationToken).ConfigureAwait(false) ?? symbol;
63+
symbol = await GoToDefinitionFeatureHelpers.TryGetPreferredSymbolAsync(solution, symbol, cancellationToken).ConfigureAwait(false);
6464
if (symbol is null)
6565
return [];
6666

src/EditorFeatures/Core/RenameTracking/RenameTrackingTaggerProvider.TrackingSession.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ private async Task<TriggerIdentifierKind> DetermineIfRenamableIdentifierAsync(Sn
197197
// This is a reference from a nameof expression. Allow the rename but set the RenameOverloads option
198198
ForceRenameOverloads = true;
199199

200-
return DetermineIfRenamableSymbols(renameSymbolInfo.Symbols, document);
200+
return await DetermineIfRenamableSymbolsAsync(renameSymbolInfo.Symbols, document).ConfigureAwait(false);
201201
}
202202
else
203203
{
@@ -208,20 +208,20 @@ private async Task<TriggerIdentifierKind> DetermineIfRenamableIdentifierAsync(Sn
208208
return TriggerIdentifierKind.NotRenamable;
209209
}
210210

211-
return DetermineIfRenamableSymbol(renameSymbolInfo.Symbols.Single(), document, token);
211+
return await DetermineIfRenamableSymbolAsync(renameSymbolInfo.Symbols.Single(), document, token).ConfigureAwait(false);
212212
}
213213
}
214214
}
215215

216216
return TriggerIdentifierKind.NotRenamable;
217217
}
218218

219-
private TriggerIdentifierKind DetermineIfRenamableSymbols(IEnumerable<ISymbol> symbols, Document document)
219+
private async ValueTask<TriggerIdentifierKind> DetermineIfRenamableSymbolsAsync(IEnumerable<ISymbol> symbols, Document document)
220220
{
221221
foreach (var symbol in symbols)
222222
{
223223
// Get the source symbol if possible
224-
var sourceSymbol = SymbolFinder.FindSourceDefinition(symbol, document.Project.Solution, _cancellationToken) ?? symbol;
224+
var sourceSymbol = await SymbolFinder.FindSourceDefinitionAsync(symbol, document.Project.Solution, _cancellationToken).ConfigureAwait(false) ?? symbol;
225225

226226
if (!sourceSymbol.IsFromSource())
227227
{
@@ -232,10 +232,10 @@ private TriggerIdentifierKind DetermineIfRenamableSymbols(IEnumerable<ISymbol> s
232232
return TriggerIdentifierKind.RenamableReference;
233233
}
234234

235-
private TriggerIdentifierKind DetermineIfRenamableSymbol(ISymbol symbol, Document document, SyntaxToken token)
235+
private async ValueTask<TriggerIdentifierKind> DetermineIfRenamableSymbolAsync(ISymbol symbol, Document document, SyntaxToken token)
236236
{
237237
// Get the source symbol if possible
238-
var sourceSymbol = SymbolFinder.FindSourceDefinition(symbol, document.Project.Solution, _cancellationToken) ?? symbol;
238+
var sourceSymbol = await SymbolFinder.FindSourceDefinitionAsync(symbol, document.Project.Solution, _cancellationToken).ConfigureAwait(false) ?? symbol;
239239

240240
if (sourceSymbol is IFieldSymbol { ContainingType.IsTupleType: true, IsImplicitlyDeclared: true })
241241
{

src/EditorFeatures/Test/SymbolFinder/DependentTypeFinderTests.cs renamed to src/EditorFeatures/Test/SymbolFinder/SymbolFinderTests.cs

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -796,4 +796,90 @@ public interface I
796796
// verify that we don't crash here.
797797
var implementedMembers = await SymbolFinder.FindImplementedInterfaceMembersArrayAsync(namespaceSymbol, solution, CancellationToken.None);
798798
}
799+
800+
[Theory, CombinatorialData]
801+
public async Task FindSourceDefinition_CrossAssembly(TestHost host)
802+
{
803+
using var workspace = CreateWorkspace(host);
804+
var solution = workspace.CurrentSolution;
805+
806+
// Create a source assembly with a class in Visual Basic
807+
solution = AddProjectWithMetadataReferences(solution, "ReferencedProject", LanguageNames.VisualBasic, @"
808+
Namespace N
809+
Public Class ReferencedClass
810+
End Class
811+
End Namespace
812+
", Net40.References.mscorlib);
813+
814+
var referencedProjectId = solution.Projects.Single(p => p.Name == "ReferencedProject").Id;
815+
816+
// Create a project that uses the class from the other assembly in C#
817+
solution = AddProjectWithMetadataReferences(solution, "SourceProject", LanguageNames.CSharp, "", Net40.References.mscorlib, referencedProjectId);
818+
819+
// Get the symbol for ReferencedClass from the using project's compilation
820+
var sourceCompilation = await solution.Projects.Single(p => p.Name == "SourceProject").GetCompilationAsync();
821+
var classInSource = sourceCompilation.GetTypeByMetadataName("N.ReferencedClass");
822+
823+
// It should be a metadata symbol (from assembly reference)
824+
Assert.True(classInSource.Locations.Any(loc => loc.IsInMetadata));
825+
826+
// Find the source definition
827+
var sourceDefinition = await SymbolFinder.FindSourceDefinitionAsync(classInSource, solution, CancellationToken.None);
828+
829+
// Verify we found the source definition
830+
Assert.NotNull(sourceDefinition);
831+
Assert.True(sourceDefinition.Locations.Any(loc => loc.IsInSource));
832+
833+
// Verify it comes from the VB project
834+
var document = solution.GetDocument(sourceDefinition.Locations.First(loc => loc.IsInSource).SourceTree);
835+
Assert.Equal(referencedProjectId, document.Project.Id);
836+
}
837+
838+
[Theory, CombinatorialData, WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/2443981")]
839+
public async Task FindSourceDefinition_CrossAssembly_WithFrozen(TestHost host)
840+
{
841+
using var workspace = CreateWorkspace(host);
842+
var solution = workspace.CurrentSolution;
843+
844+
var code = @"
845+
Namespace N
846+
Public Class ReferencedClass
847+
Public Sub M()
848+
Dim x As Integer = 0
849+
End Sub
850+
End Class
851+
End Namespace
852+
";
853+
854+
// Create a source assembly with a class in Visual Basic
855+
solution = AddProjectWithMetadataReferences(solution, "ReferencedProject", LanguageNames.VisualBasic, code, Net40.References.mscorlib);
856+
857+
var referencedProjectId = solution.Projects.Single(p => p.Name == "ReferencedProject").Id;
858+
859+
// Create a project that uses the class from the other assembly in C#
860+
solution = AddProjectWithMetadataReferences(solution, "SourceProject", LanguageNames.CSharp, "", Net40.References.mscorlib, referencedProjectId);
861+
862+
// Fetch the C# compilation to ensure we produce all compilations, and then make a change to the VB project and freeze it.
863+
// At this point we won't create more skeleton references for the VB reference, but we also won't have an up-to-date compilation.
864+
await solution.Projects.Single(p => p.Name == "SourceProject").GetCompilationAsync();
865+
solution = solution.GetProject(referencedProjectId).Documents.Single().WithText(SourceText.From(code.Replace('0', '1')))
866+
.Project.Solution.WithFrozenPartialCompilations(CancellationToken.None);
867+
868+
var sourceCompilation = await solution.Projects.Single(p => p.Name == "SourceProject").GetCompilationAsync();
869+
var classInSource = sourceCompilation.GetTypeByMetadataName("N.ReferencedClass");
870+
871+
// It should be a metadata symbol (from assembly reference)
872+
Assert.True(classInSource.Locations.Any(loc => loc.IsInMetadata));
873+
874+
// Find the source definition
875+
var sourceDefinition = await SymbolFinder.FindSourceDefinitionAsync(classInSource, solution, CancellationToken.None);
876+
877+
// Verify we found the source definition
878+
Assert.NotNull(sourceDefinition);
879+
Assert.True(sourceDefinition.Locations.Any(loc => loc.IsInSource));
880+
881+
// Verify it comes from the VB project
882+
var document = solution.GetDocument(sourceDefinition.Locations.First(loc => loc.IsInSource).SourceTree);
883+
Assert.Equal(referencedProjectId, document.Project.Id);
884+
}
799885
}

0 commit comments

Comments
 (0)