@@ -15,7 +15,6 @@ import (
15
15
"sync"
16
16
"time"
17
17
18
- "golang.org/x/tools/gopls/internal/lsp/debug/log"
19
18
"golang.org/x/tools/gopls/internal/lsp/mod"
20
19
"golang.org/x/tools/gopls/internal/lsp/protocol"
21
20
"golang.org/x/tools/gopls/internal/lsp/source"
@@ -28,6 +27,10 @@ import (
28
27
"golang.org/x/tools/internal/xcontext"
29
28
)
30
29
30
+ // TODO(rfindley): simplify this very complicated logic for publishing
31
+ // diagnostics. While doing so, ensure that we can test subtle logic such as
32
+ // for multi-pass diagnostics.
33
+
31
34
// diagnosticSource differentiates different sources of diagnostics.
32
35
//
33
36
// Diagnostics from the same source overwrite each other, whereas diagnostics
@@ -85,7 +88,7 @@ type fileReports struct {
85
88
mustPublish bool
86
89
87
90
// The last stored diagnostics for each diagnostic source.
88
- reports map [diagnosticSource ]diagnosticReport
91
+ reports map [diagnosticSource ]* diagnosticReport
89
92
}
90
93
91
94
func (d diagnosticSource ) String () string {
@@ -114,17 +117,35 @@ func (d diagnosticSource) String() string {
114
117
}
115
118
116
119
// hashDiagnostics computes a hash to identify diags.
120
+ //
121
+ // hashDiagnostics mutates its argument (via sorting).
117
122
func hashDiagnostics (diags ... * source.Diagnostic ) string {
123
+ if len (diags ) == 0 {
124
+ return emptyDiagnosticsHash
125
+ }
126
+ return computeDiagnosticHash (diags ... )
127
+ }
128
+
129
+ // opt: pre-computed hash for empty diagnostics
130
+ var emptyDiagnosticsHash = computeDiagnosticHash ()
131
+
132
+ // computeDiagnosticHash should only be called from hashDiagnostics.
133
+ //
134
+ // TODO(rfindley): this should use source.Hash.
135
+ func computeDiagnosticHash (diags ... * source.Diagnostic ) string {
118
136
source .SortDiagnostics (diags )
119
137
h := sha256 .New ()
120
138
for _ , d := range diags {
121
139
for _ , t := range d .Tags {
122
- fmt .Fprintf (h , "%s " , t )
140
+ fmt .Fprintf (h , "tag: %s \n " , t )
123
141
}
124
142
for _ , r := range d .Related {
125
- fmt .Fprintf (h , "%s%s%s " , r .Location .URI .SpanURI (), r .Message , r .Location .Range )
143
+ fmt .Fprintf (h , "related: %s %s %s \n " , r .Location .URI .SpanURI (), r .Message , r .Location .Range )
126
144
}
127
- fmt .Fprintf (h , "%s%s%s%s" , d .Message , d .Range , d .Severity , d .Source )
145
+ fmt .Fprintf (h , "message: %s\n " , d .Message )
146
+ fmt .Fprintf (h , "range: %s\n " , d .Range )
147
+ fmt .Fprintf (h , "severity: %s\n " , d .Severity )
148
+ fmt .Fprintf (h , "source: %s\n " , d .Source )
128
149
}
129
150
return fmt .Sprintf ("%x" , h .Sum (nil ))
130
151
}
@@ -171,6 +192,9 @@ func (s *Server) diagnoseSnapshot(snapshot source.Snapshot, changedURIs []span.U
171
192
//
172
193
// TODO(rfindley): it would be cleaner to simply put the diagnostic
173
194
// debouncer on the view, and remove the "key" argument to debouncing.
195
+ //
196
+ // TODO(rfindley): debounce should accept a context, so that we don't hold onto
197
+ // the snapshot when the BackgroundContext is cancelled.
174
198
if ok := <- s .diagDebouncer .debounce (snapshot .View ().Name (), snapshot .SequenceID (), time .After (delay )); ok {
175
199
s .diagnose (ctx , snapshot , analyzeOpenPackages )
176
200
s .publishDiagnostics (ctx , true , snapshot )
@@ -280,31 +304,27 @@ func (s *Server) diagnose(ctx context.Context, snapshot source.Snapshot, analyze
280
304
// Diagnose go.work file.
281
305
workReports , workErr := work .Diagnostics (ctx , snapshot )
282
306
if ctx .Err () != nil {
283
- log .Trace .Log (ctx , "diagnose cancelled" )
284
307
return
285
308
}
286
309
store (workSource , "diagnosing go.work file" , workReports , workErr , true )
287
310
288
311
// Diagnose go.mod file.
289
312
modReports , modErr := mod .Diagnostics (ctx , snapshot )
290
313
if ctx .Err () != nil {
291
- log .Trace .Log (ctx , "diagnose cancelled" )
292
314
return
293
315
}
294
316
store (modParseSource , "diagnosing go.mod file" , modReports , modErr , true )
295
317
296
318
// Diagnose go.mod upgrades.
297
319
upgradeReports , upgradeErr := mod .UpgradeDiagnostics (ctx , snapshot )
298
320
if ctx .Err () != nil {
299
- log .Trace .Log (ctx , "diagnose cancelled" )
300
321
return
301
322
}
302
323
store (modCheckUpgradesSource , "diagnosing go.mod upgrades" , upgradeReports , upgradeErr , true )
303
324
304
325
// Diagnose vulnerabilities.
305
326
vulnReports , vulnErr := mod .VulnerabilityDiagnostics (ctx , snapshot )
306
327
if ctx .Err () != nil {
307
- log .Trace .Log (ctx , "diagnose cancelled" )
308
328
return
309
329
}
310
330
store (modVulncheckSource , "diagnosing vulnerabilities" , vulnReports , vulnErr , false )
@@ -479,8 +499,26 @@ func (s *Server) diagnosePkgs(ctx context.Context, snapshot source.Snapshot, toD
479
499
s .storeDiagnostics (snapshot , uri , analysisSource , adiags2 , true )
480
500
}
481
501
502
+ // golang/go#59587: guarantee that we store type-checking diagnostics for every compiled
503
+ // package file.
504
+ //
505
+ // Without explicitly storing empty diagnostics, the eager diagnostics
506
+ // publication for changed files will not publish anything for files with
507
+ // empty diagnostics.
508
+ storedPkgDiags := make (map [span.URI ]bool )
509
+ for _ , m := range toDiagnose {
510
+ for _ , uri := range m .CompiledGoFiles {
511
+ s .storeDiagnostics (snapshot , uri , typeCheckSource , pkgDiags [uri ], true )
512
+ storedPkgDiags [uri ] = true
513
+ }
514
+ }
482
515
// Store the package diagnostics.
483
516
for uri , diags := range pkgDiags {
517
+ if storedPkgDiags [uri ] {
518
+ continue
519
+ }
520
+ // builtin.go exists only for documentation purposes, and is not valid Go code.
521
+ // Don't report distracting errors
484
522
if snapshot .IsBuiltin (ctx , uri ) {
485
523
bug .Reportf ("type checking reported diagnostics for the builtin file: %v" , diags )
486
524
continue
@@ -549,7 +587,7 @@ func (s *Server) mustPublishDiagnostics(uri span.URI) {
549
587
if s .diagnostics [uri ] == nil {
550
588
s .diagnostics [uri ] = & fileReports {
551
589
publishedHash : hashDiagnostics (), // Hash for 0 diagnostics.
552
- reports : map [diagnosticSource ]diagnosticReport {},
590
+ reports : map [diagnosticSource ]* diagnosticReport {},
553
591
}
554
592
}
555
593
s .diagnostics [uri ].mustPublish = true
@@ -558,6 +596,8 @@ func (s *Server) mustPublishDiagnostics(uri span.URI) {
558
596
// storeDiagnostics stores results from a single diagnostic source. If merge is
559
597
// true, it merges results into any existing results for this snapshot.
560
598
//
599
+ // Mutates (sorts) diags.
600
+ //
561
601
// TODO(hyangah): investigate whether we can unconditionally overwrite previous report.diags
562
602
// with the new diags and eliminate the need for the `merge` flag.
563
603
func (s * Server ) storeDiagnostics (snapshot source.Snapshot , uri span.URI , dsource diagnosticSource , diags []* source.Diagnostic , merge bool ) {
@@ -573,10 +613,14 @@ func (s *Server) storeDiagnostics(snapshot source.Snapshot, uri span.URI, dsourc
573
613
if s .diagnostics [uri ] == nil {
574
614
s .diagnostics [uri ] = & fileReports {
575
615
publishedHash : hashDiagnostics (), // Hash for 0 diagnostics.
576
- reports : map [diagnosticSource ]diagnosticReport {},
616
+ reports : map [diagnosticSource ]* diagnosticReport {},
577
617
}
578
618
}
579
619
report := s .diagnostics [uri ].reports [dsource ]
620
+ if report == nil {
621
+ report = new (diagnosticReport )
622
+ s .diagnostics [uri ].reports [dsource ] = report
623
+ }
580
624
// Don't set obsolete diagnostics.
581
625
if report .snapshotID > snapshot .GlobalID () {
582
626
return
@@ -588,7 +632,6 @@ func (s *Server) storeDiagnostics(snapshot source.Snapshot, uri span.URI, dsourc
588
632
for _ , d := range diags {
589
633
report .diags [hashDiagnostics (d )] = d
590
634
}
591
- s .diagnostics [uri ].reports [dsource ] = report
592
635
}
593
636
594
637
// clearDiagnosticSource clears all diagnostics for a given source type. It is
@@ -735,6 +778,7 @@ func (s *Server) publishDiagnostics(ctx context.Context, final bool, snapshot so
735
778
diags = append (diags , d )
736
779
reportDiags = append (reportDiags , d )
737
780
}
781
+
738
782
hash := hashDiagnostics (reportDiags ... )
739
783
if hash != report .publishedHash {
740
784
anyReportsChanged = true
@@ -748,7 +792,6 @@ func (s *Server) publishDiagnostics(ctx context.Context, final bool, snapshot so
748
792
continue
749
793
}
750
794
751
- source .SortDiagnostics (diags )
752
795
hash := hashDiagnostics (diags ... )
753
796
if hash == r .publishedHash && ! r .mustPublish {
754
797
// Update snapshotID to be the latest snapshot for which this diagnostic
@@ -768,15 +811,22 @@ func (s *Server) publishDiagnostics(ctx context.Context, final bool, snapshot so
768
811
r .publishedHash = hash
769
812
r .mustPublish = false // diagnostics have been successfully published
770
813
r .publishedSnapshotID = snapshot .GlobalID ()
771
- for dsource , hash := range reportHashes {
772
- report := r .reports [dsource ]
773
- report .publishedHash = hash
774
- r .reports [dsource ] = report
814
+ // When we publish diagnostics for a file, we must update the
815
+ // publishedHash for every report, not just the reports that were
816
+ // published. Eliding a report is equivalent to publishing empty
817
+ // diagnostics.
818
+ for dsource , report := range r .reports {
819
+ if hash , ok := reportHashes [dsource ]; ok {
820
+ report .publishedHash = hash
821
+ } else {
822
+ // The report was not (yet) stored for this snapshot. Record that we
823
+ // published no diagnostics from this source.
824
+ report .publishedHash = hashDiagnostics ()
825
+ }
775
826
}
776
827
} else {
777
828
if ctx .Err () != nil {
778
829
// Publish may have failed due to a cancelled context.
779
- log .Trace .Log (ctx , "publish cancelled" )
780
830
return
781
831
}
782
832
event .Error (ctx , "publishReports: failed to deliver diagnostic" , err , tag .URI .Of (uri ))
@@ -852,7 +902,7 @@ func (s *Server) Diagnostics() map[string][]string {
852
902
return ans
853
903
}
854
904
855
- func auxStr (v * source.Diagnostic , d diagnosticReport , typ diagnosticSource ) string {
905
+ func auxStr (v * source.Diagnostic , d * diagnosticReport , typ diagnosticSource ) string {
856
906
// Tags? RelatedInformation?
857
907
msg := fmt .Sprintf ("(%s)%q(source:%q,code:%q,severity:%s,snapshot:%d,type:%s)" ,
858
908
v .Range , v .Message , v .Source , v .Code , v .Severity , d .snapshotID , typ )
0 commit comments