From e75e49eb79016ca60cefc762b3eeb47ab1fcfd21 Mon Sep 17 00:00:00 2001 From: Jason Malinowski Date: Tue, 4 Sep 2018 14:03:46 -0700 Subject: [PATCH] Handle dangling project references We support dangling project references in the workspace API, and the ProjectDependencyGraph is only supposed to return references that exist within the project. Therefore, if we add a project, we have to check to make sure we didn't have a dangling reference becoming a real reference, and give inconsistent results. I expect this to be a rare situation in reality so don't want to spend a lot of time optimizing for it, but if it happens this will keep everything in sync. --- .../Workspace/Solution/SolutionState.cs | 18 ++++++++++++- .../ProjectDependencyGraphTests.cs | 27 ++++++++++++++++++- 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs index 0bd0edb555c..90daf191372 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs @@ -419,7 +419,23 @@ private SolutionState AddProject(ProjectId projectId, ProjectState projectState) var newStateMap = _projectIdToProjectStateMap.Add(projectId, projectState); var newDependencyGraph = _dependencyGraph .WithAdditionalProjects(SpecializedCollections.SingletonEnumerable(projectId)) - .WithAdditionalProjectReferences(projectId, projectState.ProjectReferences.Select(r => r.ProjectId).ToList()); + .WithAdditionalProjectReferences(projectId, + projectState.ProjectReferences.Where(r => _projectIdToProjectStateMap.ContainsKey(r.ProjectId)).Select(r => r.ProjectId).ToList()); + + // It's possible that another project already in newStateMap has a reference to this project that we're adding, since we allow + // dangling references like that. If so, we'll need to link those in too. + foreach (var newState in newStateMap) + { + foreach (var projectReference in newState.Value.ProjectReferences) + { + if (projectReference.ProjectId == projectId) + { + newDependencyGraph = newDependencyGraph.WithAdditionalProjectReferences(newState.Key, ImmutableArray.Create(projectId)); + break; + } + } + } + var newTrackerMap = CreateCompilationTrackerMap(projectId, newDependencyGraph); var newLinkedFilesMap = CreateLinkedFilesMapWithAddedProject(newStateMap[projectId]); diff --git a/src/Workspaces/CoreTest/SolutionTests/ProjectDependencyGraphTests.cs b/src/Workspaces/CoreTest/SolutionTests/ProjectDependencyGraphTests.cs index 488d052cd1f..925ddfe807f 100644 --- a/src/Workspaces/CoreTest/SolutionTests/ProjectDependencyGraphTests.cs +++ b/src/Workspaces/CoreTest/SolutionTests/ProjectDependencyGraphTests.cs @@ -178,7 +178,6 @@ void VerifyAllTransitiveReferences() VerifyDirectReferences(solution, "A", new string[] { "B", "C" }); } - [Fact, Trait(Traits.Feature, Traits.Features.Workspace)] public void TestTransitiveReferencesIncrementalUpdateWithProjectThatHasUnknownReferences() { @@ -208,6 +207,32 @@ public void TestTransitiveReferencesIncrementalUpdateWithProjectThatHasUnknownRe VerifyTransitiveReferences(solution, dependencyGraph, project: "A", expectedResults: new string[] { "B" }); } + [Fact, Trait(Traits.Feature, Traits.Features.Workspace)] + public void TestTransitiveReferencesWithDanglingProjectReference() + { + // We are going to create a solution with the references: + // + // A -> B + // + // but we're going to add A as a reference with B not existing yet. Then we'll add in B and ask. + + var solution = CreateSolution(); + var projectAId = ProjectId.CreateNewId("A"); + var projectBId = ProjectId.CreateNewId("B"); + + var projectAInfo = ProjectInfo.Create(projectAId, VersionStamp.Create(), "A", "A", LanguageNames.CSharp, + projectReferences: new[] { new ProjectReference(projectBId) }); + + solution = solution.AddProject(projectAInfo); + + VerifyDirectReferences(solution, "A", new string[] { }); + VerifyTransitiveReferences(solution, "A", new string[] { }); + + solution = solution.AddProject(projectBId, "B", "B", LanguageNames.CSharp); + + VerifyTransitiveReferences(solution, "A", new string[] { "B" }); + } + private void VerifyDirectReferences(Solution solution, string project, string[] expectedResults) { var projectDependencyGraph = solution.GetProjectDependencyGraph(); -- GitLab