11package lint
22
33import (
4+ "flag"
45 "go/ast"
56 "go/token"
67 "go/types"
8+ "log/slog"
9+ "os"
710 "sort"
811 "strings"
912
1013 "golang.org/x/tools/go/analysis"
1114)
1215
16+ func init () {
17+ var verboseFlag bool
18+
19+ fs1 := flag .NewFlagSet (Sum .Name , flag .ExitOnError )
20+ fs1 .BoolVar (& verboseFlag , "verbose" , false , "enable verbose output" )
21+ Sum .Flags = * fs1
22+ fs2 := flag .NewFlagSet (Oneof .Name , flag .ExitOnError )
23+ fs2 .BoolVar (& verboseFlag , "verbose" , false , "enable verbose output" )
24+ Oneof .Flags = * fs2
25+ }
26+
1327// InterfaceImplementorsFact stores (packagePath.InterfaceName) -> implementor type names (qualified)
1428type InterfaceImplementorsFact struct {
1529 Implementors []string // fully qualified (pkgpath.TypeName)
@@ -45,11 +59,28 @@ type analyzer struct {
4559}
4660
4761func (a analyzer ) run (pass * analysis.Pass ) (any , error ) {
62+ flagVerbose := getFlag (pass .Analyzer .Flags , "verbose" , false )
63+ if flagVerbose {
64+ sumLog := slog .New (slog .NewTextHandler (os .Stderr , & slog.HandlerOptions {Level : slog .LevelDebug }))
65+ sumLog = sumLog .With ("analyzer" , pass .Analyzer .Name )
66+ slog .SetDefault (sumLog )
67+ }
68+
4869 sumIfaces := a .discoverSumInterfaces (pass )
4970 if len (sumIfaces ) > 0 {
71+ slog .Debug ("discovered interfaces" , "count" , len (sumIfaces ))
5072 exportImplementorFacts (pass , sumIfaces )
5173 }
5274 ifaceImpls := loadAllInterfaceFacts (pass )
75+ if flagVerbose {
76+ count := 0
77+ for _ , impls := range ifaceImpls {
78+ count += len (impls )
79+ }
80+ if count > 0 {
81+ slog .Debug ("discovered implementations" , "count" , count )
82+ }
83+ }
5384
5485 for _ , f := range pass .Files {
5586 ast .Inspect (f , func (n ast.Node ) bool {
@@ -157,6 +188,7 @@ func exportImplementorFacts(pass *analysis.Pass, sumIfaces map[*types.TypeName]*
157188 if types .Implements (named , ifaceType ) || types .Implements (types .NewPointer (named ), ifaceType ) {
158189 key := qualifiedTypeName (tn )
159190 byIface [ifaceObj ][key ] = struct {}{}
191+
160192 }
161193 }
162194 }
@@ -167,6 +199,7 @@ func exportImplementorFacts(pass *analysis.Pass, sumIfaces map[*types.TypeName]*
167199 list := make ([]string , 0 , len (implSet ))
168200 for k := range implSet {
169201 list = append (list , k )
202+ slog .Debug ("exporting implementation" , "interface" , ifaceObj .Name (), "name" , k )
170203 }
171204 sort .Strings (list ) // deterministic ordering for tests
172205 pass .ExportObjectFact (ifaceObj , & InterfaceImplementorsFact {Implementors : list })
@@ -190,6 +223,7 @@ func loadAllInterfaceFacts(pass *analysis.Pass) map[*types.TypeName]map[string]s
190223 if pass .ImportObjectFact (tn , & fact ) {
191224 set := make (map [string ]struct {}, len (fact .Implementors ))
192225 for _ , t := range fact .Implementors {
226+ slog .Debug ("loadAllInterfaceFacts: scopeNames" , "tn" , tn , "t" , t )
193227 set [t ] = struct {}{}
194228 }
195229 res [tn ] = set
@@ -211,14 +245,14 @@ func loadAllInterfaceFacts(pass *analysis.Pass) map[*types.TypeName]map[string]s
211245 if pass .ImportObjectFact (tn , & fact ) {
212246 set := make (map [string ]struct {}, len (fact .Implementors ))
213247 for _ , t := range fact .Implementors {
248+ slog .Debug ("loadAllInterfaceFacts: ImportObjectFact" , "tn" , tn , "t" , t )
214249 set [t ] = struct {}{}
215250 }
216251 res [tn ] = set
217252 }
218253 }
219254 return res
220255}
221-
222256func checkTypeSwitch (pass * analysis.Pass , ts * ast.TypeSwitchStmt , ifaceImpls map [* types.TypeName ]map [string ]struct {}) {
223257 var asserted types.Type
224258 switch ass := ts .Assign .(type ) {
@@ -230,30 +264,58 @@ func checkTypeSwitch(pass *analysis.Pass, ts *ast.TypeSwitchStmt, ifaceImpls map
230264 if ! ok || ta .Type != nil {
231265 return
232266 }
233- asserted = pass .TypesInfo .TypeOf (ta .X )
267+ asserted = pass .TypesInfo .TypeOf (ta .X ) // e.g. msg.GetPayload()
234268 case * ast.ExprStmt :
235269 ta , ok := ass .X .(* ast.TypeAssertExpr )
236270 if ! ok || ta .Type != nil {
237271 return
238272 }
239- asserted = pass .TypesInfo .TypeOf (ta .X )
273+ asserted = pass .TypesInfo .TypeOf (ta .X ) // e.g. msg.GetPayload()
240274 default :
241275 return
242276 }
243277
244278 if asserted == nil {
245279 return
246280 }
281+ if ptr , ok := asserted .(* types.Pointer ); ok {
282+ asserted = ptr .Elem ()
283+ }
247284 if _ , ok := asserted .Underlying ().(* types.Interface ); ! ok {
248285 return
249286 }
287+
250288 var ifaceObj * types.TypeName
251289 if named , ok := asserted .(* types.Named ); ok {
252290 ifaceObj = named .Obj ()
253291 } else {
292+ // Try match by underlying interface identity
293+ for tn := range ifaceImpls {
294+ if tn .Type ().Underlying () == asserted .Underlying () {
295+ ifaceObj = tn
296+ break
297+ }
298+ }
299+ }
300+ if ifaceObj == nil {
254301 return
255302 }
303+
256304 implSet , hasFact := ifaceImpls [ifaceObj ]
305+ // If this package did not discover the interface (e.g. unexported in another package),
306+ // attempt to import its fact now that we have the *types.TypeName from the type switch expression.
307+ if ! hasFact || len (implSet ) == 0 {
308+ var fact InterfaceImplementorsFact
309+ if pass .ImportObjectFact (ifaceObj , & fact ) && len (fact .Implementors ) > 0 {
310+ newSet := make (map [string ]struct {}, len (fact .Implementors ))
311+ for _ , implObj := range fact .Implementors {
312+ newSet [implObj ] = struct {}{}
313+ }
314+ ifaceImpls [ifaceObj ] = newSet
315+ implSet = newSet
316+ hasFact = true
317+ }
318+ }
257319 if ! hasFact || len (implSet ) == 0 {
258320 return
259321 }
@@ -309,3 +371,14 @@ func qualifiedTypeName(tn *types.TypeName) string {
309371 }
310372 return tn .Pkg ().Path () + "." + tn .Name ()
311373}
374+
375+ func getFlag [T any ](fs flag.FlagSet , key string , def T ) T {
376+ if fl := fs .Lookup (key ); fl != nil {
377+ if g , ok := fl .Value .(flag.Getter ); ok {
378+ if t , ok := g .Get ().(T ); ok {
379+ return t
380+ }
381+ }
382+ }
383+ return def
384+ }
0 commit comments