7
7
using System . Collections . Immutable ;
8
8
using System . IO ;
9
9
using System . Linq ;
10
+ using System . Runtime . CompilerServices ;
10
11
using System . Threading ;
11
12
using System . Threading . Tasks ;
12
13
using Microsoft . CodeAnalysis . Diagnostics ;
@@ -548,14 +549,20 @@ private static ProjectUpdateState ConvertMetadataReferencesToProjectReferences_N
548
549
string outputPath ,
549
550
ProjectUpdateState projectUpdateState )
550
551
{
551
- foreach ( var projectIdToRetarget in solutionChanges . Solution . ProjectIds )
552
+ // PERF: call GetRequiredProjectState instead of GetRequiredProject, otherwise creating a new project
553
+ // might force all Project instances to get created.
554
+ var candidateProjectState = solutionChanges . Solution . GetRequiredProjectState ( projectIdToReference ) ;
555
+
556
+ foreach ( var projectToRetarget in solutionChanges . Solution . SortedProjectStates )
552
557
{
553
- if ( CanConvertMetadataReferenceToProjectReference ( solutionChanges . Solution , projectIdToRetarget , referencedProjectId : projectIdToReference ) )
558
+ // PERF: If we don't even have any metadata references yet, then don't even call CanConvertMetadataReferenceToProjectReference.
559
+ // This optimizes the early parts of solution load, where projects may be created with their output paths right away,
560
+ // but metadata references come in later. CanConvertMetadataReferenceToProjectReference isn't terribly expensive
561
+ // but when called enough times things can start to add up.
562
+ if ( projectToRetarget . MetadataReferences . Count > 0 &&
563
+ CanConvertMetadataReferenceToProjectReference ( solutionChanges . Solution , projectToRetarget , candidateProjectState ) )
554
564
{
555
- // PERF: call GetRequiredProjectState instead of GetRequiredProject, otherwise creating a new project
556
- // might force all Project instances to get created.
557
- var projectState = solutionChanges . Solution . GetRequiredProjectState ( projectIdToRetarget ) ;
558
- foreach ( var reference in projectState . MetadataReferences )
565
+ foreach ( var reference in projectToRetarget . MetadataReferences )
559
566
{
560
567
if ( reference is PortableExecutableReference peReference
561
568
&& string . Equals ( peReference . FilePath , outputPath , StringComparison . OrdinalIgnoreCase ) )
@@ -564,13 +571,13 @@ private static ProjectUpdateState ConvertMetadataReferencesToProjectReferences_N
564
571
565
572
var projectReference = new ProjectReference ( projectIdToReference , peReference . Properties . Aliases , peReference . Properties . EmbedInteropTypes ) ;
566
573
var newSolution = solutionChanges . Solution
567
- . RemoveMetadataReference ( projectIdToRetarget , peReference )
568
- . AddProjectReference ( projectIdToRetarget , projectReference ) ;
574
+ . RemoveMetadataReference ( projectToRetarget . Id , peReference )
575
+ . AddProjectReference ( projectToRetarget . Id , projectReference ) ;
569
576
570
- solutionChanges . UpdateSolutionForProjectAction ( projectIdToRetarget , newSolution ) ;
577
+ solutionChanges . UpdateSolutionForProjectAction ( projectToRetarget . Id , newSolution ) ;
571
578
572
- projectUpdateState = GetReferenceInformation ( projectIdToRetarget , projectUpdateState , out var projectInfo ) ;
573
- projectUpdateState = projectUpdateState . WithProjectReferenceInfo ( projectIdToRetarget ,
579
+ projectUpdateState = GetReferenceInformation ( projectToRetarget . Id , projectUpdateState , out var projectInfo ) ;
580
+ projectUpdateState = projectUpdateState . WithProjectReferenceInfo ( projectToRetarget . Id ,
574
581
projectInfo . WithConvertedProjectReference ( peReference . FilePath ! , projectReference ) ) ;
575
582
576
583
// We have converted one, but you could have more than one reference with different aliases that
@@ -585,33 +592,25 @@ private static ProjectUpdateState ConvertMetadataReferencesToProjectReferences_N
585
592
586
593
[ PerformanceSensitive ( "https://github.com/dotnet/roslyn/issues/31306" ,
587
594
Constraint = "Avoid calling " + nameof ( CodeAnalysis . Solution . GetProject ) + " to avoid realizing all projects." ) ]
588
- private static bool CanConvertMetadataReferenceToProjectReference ( Solution solution , ProjectId projectIdWithMetadataReference , ProjectId referencedProjectId )
595
+ private static bool CanConvertMetadataReferenceToProjectReference ( Solution solution , ProjectState projectWithMetadataReference , ProjectState candidateProjectToReference )
589
596
{
590
597
// We can never make a project reference ourselves. This isn't a meaningful scenario, but if somebody does this by accident
591
598
// we do want to throw exceptions.
592
- if ( projectIdWithMetadataReference == referencedProjectId )
599
+ if ( projectWithMetadataReference . Id == candidateProjectToReference . Id )
593
600
{
594
601
return false ;
595
602
}
596
603
597
- // PERF: call GetProjectState instead of GetProject, otherwise creating a new project might force all
598
- // Project instances to get created.
599
- var projectWithMetadataReference = solution . GetProjectState ( projectIdWithMetadataReference ) ;
600
- var referencedProject = solution . GetProjectState ( referencedProjectId ) ;
601
-
602
- Contract . ThrowIfNull ( projectWithMetadataReference ) ;
603
- Contract . ThrowIfNull ( referencedProject ) ;
604
-
605
604
// We don't want to convert a metadata reference to a project reference if the project being referenced isn't
606
605
// something we can create a Compilation for. For example, if we have a C# project, and it's referencing a F#
607
606
// project via a metadata reference everything would be fine if we left it a metadata reference. Converting it
608
607
// to a project reference means we couldn't create a Compilation anymore in the IDE, since the C# compilation
609
608
// would need to reference an F# compilation. F# projects referencing other F# projects though do expect this to
610
609
// work, and so we'll always allow references through of the same language.
611
- if ( projectWithMetadataReference . Language != referencedProject . Language )
610
+ if ( projectWithMetadataReference . Language != candidateProjectToReference . Language )
612
611
{
613
612
if ( projectWithMetadataReference . LanguageServices . GetService < ICompilationFactoryService > ( ) != null &&
614
- referencedProject . LanguageServices . GetService < ICompilationFactoryService > ( ) == null )
613
+ candidateProjectToReference . LanguageServices . GetService < ICompilationFactoryService > ( ) == null )
615
614
{
616
615
// We're referencing something that we can't create a compilation from something that can, so keep the metadata reference
617
616
return false ;
@@ -621,11 +620,11 @@ private static bool CanConvertMetadataReferenceToProjectReference(Solution solut
621
620
// Getting a metadata reference from a 'module' is not supported from the compilation layer. Nor is emitting a
622
621
// 'metadata-only' stream for it (a 'skeleton' reference). So converting a NetModule reference to a project
623
622
// reference won't actually help us out. Best to keep this as a plain metadata reference.
624
- if ( referencedProject . CompilationOptions ? . OutputKind == OutputKind . NetModule )
623
+ if ( candidateProjectToReference . CompilationOptions ? . OutputKind == OutputKind . NetModule )
625
624
return false ;
626
625
627
626
// If this is going to cause a circular reference, also disallow it
628
- if ( solution . GetProjectDependencyGraph ( ) . GetProjectsThatThisProjectTransitivelyDependsOn ( referencedProjectId ) . Contains ( projectIdWithMetadataReference ) )
627
+ if ( solution . GetProjectDependencyGraph ( ) . DoesProjectTransitivelyDependOnProject ( candidateProjectToReference . Id , projectWithMetadataReference . Id ) )
629
628
{
630
629
return false ;
631
630
}
@@ -696,7 +695,7 @@ private static ProjectUpdateState ConvertProjectReferencesToMetadataReferences_N
696
695
/// during a workspace update (which will attempt to apply the update multiple times).
697
696
/// </summary>
698
697
public static ProjectUpdateState TryCreateConvertedProjectReference_NoLock (
699
- ProjectId referencingProject ,
698
+ ProjectState referencingProjectState ,
700
699
string path ,
701
700
MetadataReferenceProperties properties ,
702
701
ProjectUpdateState projectUpdateState ,
@@ -707,15 +706,15 @@ public static ProjectUpdateState TryCreateConvertedProjectReference_NoLock(
707
706
{
708
707
var projectIdToReference = ids . First ( ) ;
709
708
710
- if ( CanConvertMetadataReferenceToProjectReference ( currentSolution , referencingProject , projectIdToReference ) )
709
+ if ( CanConvertMetadataReferenceToProjectReference ( currentSolution , referencingProjectState , currentSolution . GetRequiredProjectState ( projectIdToReference ) ) )
711
710
{
712
711
projectReference = new ProjectReference (
713
712
projectIdToReference ,
714
713
aliases : properties . Aliases ,
715
714
embedInteropTypes : properties . EmbedInteropTypes ) ;
716
715
717
- projectUpdateState = GetReferenceInformation ( referencingProject , projectUpdateState , out var projectReferenceInfo ) ;
718
- projectUpdateState = projectUpdateState . WithProjectReferenceInfo ( referencingProject , projectReferenceInfo . WithConvertedProjectReference ( path , projectReference ) ) ;
716
+ projectUpdateState = GetReferenceInformation ( referencingProjectState . Id , projectUpdateState , out var projectReferenceInfo ) ;
717
+ projectUpdateState = projectUpdateState . WithProjectReferenceInfo ( referencingProjectState . Id , projectReferenceInfo . WithConvertedProjectReference ( path , projectReference ) ) ;
719
718
return projectUpdateState ;
720
719
}
721
720
else
0 commit comments