@@ -548,7 +548,13 @@ func (b *typeCheckBatch) importPackage(ctx context.Context, mp *metadata.Package
548
548
}
549
549
}
550
550
} else {
551
- id = importLookup (PackagePath (item .Path ))
551
+ var alt PackageID
552
+ id , alt = importLookup (PackagePath (item .Path ))
553
+ if alt != "" {
554
+ // Any bug leading to this scenario would have already been reported
555
+ // in importLookup.
556
+ return fmt .Errorf ("inconsistent metadata during import: for package path %q, found both IDs %q and %q" , item .Path , id , alt )
557
+ }
552
558
var err error
553
559
pkg , err = b .getImportPackage (ctx , id )
554
560
if err != nil {
@@ -665,8 +671,12 @@ func (b *typeCheckBatch) checkPackageForImport(ctx context.Context, ph *packageH
665
671
// a given package path, based on the forward transitive closure of the initial
666
672
// package (id).
667
673
//
674
+ // If the second result is non-empty, it is another ID discovered in the import
675
+ // graph for the same package path. This means the import graph is
676
+ // incoherent--see #63822 and the long comment below.
677
+ //
668
678
// The resulting function is not concurrency safe.
669
- func importLookup (mp * metadata.Package , source metadata.Source ) func (PackagePath ) PackageID {
679
+ func importLookup (mp * metadata.Package , source metadata.Source ) func (PackagePath ) ( id , altID PackageID ) {
670
680
assert (mp != nil , "nil metadata" )
671
681
672
682
// This function implements an incremental depth first scan through the
@@ -680,6 +690,10 @@ func importLookup(mp *metadata.Package, source metadata.Source) func(PackagePath
680
690
mp .PkgPath : mp .ID ,
681
691
}
682
692
693
+ // altIDs records alternative IDs for the given path, to report inconsistent
694
+ // metadata.
695
+ var altIDs map [PackagePath ]PackageID
696
+
683
697
// pending is a FIFO queue of package metadata that has yet to have its
684
698
// dependencies fully scanned.
685
699
// Invariant: all entries in pending are already mapped in impMap.
@@ -695,13 +709,82 @@ func importLookup(mp *metadata.Package, source metadata.Source) func(PackagePath
695
709
if prevID , ok := impMap [depPath ]; ok {
696
710
// debugging #63822
697
711
if prevID != depID {
712
+ if altIDs == nil {
713
+ altIDs = make (map [PackagePath ]PackageID )
714
+ }
715
+ if _ , ok := altIDs [depPath ]; ! ok {
716
+ altIDs [depPath ] = depID
717
+ }
698
718
prev := source .Metadata (prevID )
699
719
curr := source .Metadata (depID )
700
720
switch {
701
721
case prev == nil || curr == nil :
702
722
bug .Reportf ("inconsistent view of dependencies (missing dep)" )
703
723
case prev .ForTest != curr .ForTest :
704
- bug .Reportf ("inconsistent view of dependencies (mismatching ForTest)" )
724
+ // This case is unfortunately understood to be possible.
725
+ //
726
+ // To explain this, consider a package a_test testing the package
727
+ // a, and for brevity denote by b' the intermediate test variant of
728
+ // the package b, which is created for the import graph of a_test,
729
+ // if b imports a.
730
+ //
731
+ // Now imagine that we have the following import graph, where
732
+ // higher packages import lower ones.
733
+ //
734
+ // a_test
735
+ // / \
736
+ // b' c
737
+ // / \ /
738
+ // a d
739
+ //
740
+ // In this graph, there is one intermediate test variant b',
741
+ // because b imports a and so b' must hold the test variant import.
742
+ //
743
+ // Now, imagine that an on-disk change (perhaps due to a branch
744
+ // switch) affects the above import graph such that d imports a.
745
+ //
746
+ // a_test
747
+ // / \
748
+ // b' c*
749
+ // / \ /
750
+ // / d*
751
+ // a---/
752
+ //
753
+ // In this case, c and d should really be intermediate test
754
+ // variants, because they reach a. However, suppose that gopls does
755
+ // not know this yet (as indicated by '*').
756
+ //
757
+ // Now suppose that the metadata of package c is invalidated, for
758
+ // example due to a change in an unrelated import or an added file.
759
+ // This will invalidate the metadata of c and a_test (but NOT b),
760
+ // and now gopls observes this graph:
761
+ //
762
+ // a_test
763
+ // / \
764
+ // b' c'
765
+ // /| |
766
+ // / d d'
767
+ // a-----/
768
+ //
769
+ // That is: a_test now sees c', which sees d', but since b was not
770
+ // invalidated, gopls still thinks that b' imports d (not d')!
771
+ //
772
+ // The problem, of course, is that gopls never observed the change
773
+ // to d, which would have invalidated b. This may be due to racing
774
+ // file watching events, in which case the problem should
775
+ // self-correct when gopls sees the change to d, or it may be due
776
+ // to d being outside the coverage of gopls' file watching glob
777
+ // patterns, or it may be due to buggy or entirely absent
778
+ // client-side file watching.
779
+ //
780
+ // TODO(rfindley): fix this, one way or another. It would be hard
781
+ // or impossible to repair gopls' state here, during type checking.
782
+ // However, we could perhaps reload metadata in Snapshot.load until
783
+ // we achieve a consistent state, or better, until the loaded state
784
+ // is consistent with our view of the filesystem, by making the Go
785
+ // command report digests of the files it reads. Both of those are
786
+ // tricker than they may seem, and have significant performance
787
+ // implications.
705
788
default :
706
789
bug .Reportf ("inconsistent view of dependencies" )
707
790
}
@@ -723,16 +806,16 @@ func importLookup(mp *metadata.Package, source metadata.Source) func(PackagePath
723
806
return id , found
724
807
}
725
808
726
- return func (pkgPath PackagePath ) PackageID {
809
+ return func (pkgPath PackagePath ) ( id , altID PackageID ) {
727
810
if id , ok := impMap [pkgPath ]; ok {
728
- return id
811
+ return id , altIDs [ pkgPath ]
729
812
}
730
813
for len (pending ) > 0 {
731
814
if id , found := search (pkgPath ); found {
732
- return id
815
+ return id , altIDs [ pkgPath ]
733
816
}
734
817
}
735
- return ""
818
+ return "" , ""
736
819
}
737
820
}
738
821
0 commit comments