src

Go monorepo.
git clone git://code.dwrz.net/src
Log | Files | Refs

sa5010.go (3341B)


      1 package sa5010
      2 
      3 import (
      4 	"fmt"
      5 	"go/types"
      6 	"strings"
      7 
      8 	"honnef.co/go/tools/analysis/lint"
      9 	"honnef.co/go/tools/analysis/report"
     10 	"honnef.co/go/tools/go/ir"
     11 	"honnef.co/go/tools/internal/passes/buildir"
     12 
     13 	"golang.org/x/tools/go/analysis"
     14 )
     15 
     16 var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
     17 	Analyzer: &analysis.Analyzer{
     18 		Name:     "SA5010",
     19 		Run:      run,
     20 		Requires: []*analysis.Analyzer{buildir.Analyzer},
     21 	},
     22 	Doc: &lint.RawDocumentation{
     23 		Title: `Impossible type assertion`,
     24 
     25 		Text: `Some type assertions can be statically proven to be
     26 impossible. This is the case when the method sets of both
     27 arguments of the type assertion conflict with each other, for
     28 example by containing the same method with different
     29 signatures.
     30 
     31 The Go compiler already applies this check when asserting from an
     32 interface value to a concrete type. If the concrete type misses
     33 methods from the interface, or if function signatures don't match,
     34 then the type assertion can never succeed.
     35 
     36 This check applies the same logic when asserting from one interface to
     37 another. If both interface types contain the same method but with
     38 different signatures, then the type assertion can never succeed,
     39 either.`,
     40 
     41 		Since:    "2020.1",
     42 		Severity: lint.SeverityWarning,
     43 		// Technically this should be MergeIfAll, but the Go compiler
     44 		// already flags some impossible type assertions, so
     45 		// MergeIfAny is consistent with the compiler.
     46 		MergeIf: lint.MergeIfAny,
     47 	},
     48 })
     49 
     50 var Analyzer = SCAnalyzer.Analyzer
     51 
     52 func run(pass *analysis.Pass) (any, error) {
     53 	type entry struct {
     54 		l, r *types.Func
     55 	}
     56 
     57 	msc := &pass.ResultOf[buildir.Analyzer].(*buildir.IR).Pkg.Prog.MethodSets
     58 	for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
     59 		for _, b := range fn.Blocks {
     60 		instrLoop:
     61 			for _, instr := range b.Instrs {
     62 				assert, ok := instr.(*ir.TypeAssert)
     63 				if !ok {
     64 					continue
     65 				}
     66 				var wrong []entry
     67 				left := assert.X.Type()
     68 				right := assert.AssertedType
     69 				righti, ok := right.Underlying().(*types.Interface)
     70 
     71 				if !ok {
     72 					// We only care about interface->interface
     73 					// assertions. The Go compiler already catches
     74 					// impossible interface->concrete assertions.
     75 					continue
     76 				}
     77 
     78 				ms := msc.MethodSet(left)
     79 				for mr := range righti.Methods() {
     80 					sel := ms.Lookup(mr.Pkg(), mr.Name())
     81 					if sel == nil {
     82 						continue
     83 					}
     84 					ml := sel.Obj().(*types.Func)
     85 					if ml.Origin() != ml || mr.Origin() != mr {
     86 						// Give up when we see generics.
     87 						//
     88 						// TODO(dh): support generics once go/types gets an
     89 						// exported API for type unification.
     90 						continue instrLoop
     91 					}
     92 					if types.AssignableTo(ml.Type(), mr.Type()) {
     93 						continue
     94 					}
     95 
     96 					wrong = append(wrong, entry{ml, mr})
     97 				}
     98 
     99 				if len(wrong) != 0 {
    100 					var s strings.Builder
    101 					s.WriteString(fmt.Sprintf("impossible type assertion; %s and %s contradict each other:",
    102 						types.TypeString(left, types.RelativeTo(pass.Pkg)),
    103 						types.TypeString(right, types.RelativeTo(pass.Pkg))))
    104 					for _, e := range wrong {
    105 						s.WriteString(fmt.Sprintf("\n\twrong type for %s method", e.l.Name()))
    106 						s.WriteString(fmt.Sprintf("\n\t\thave %s", e.l.Type()))
    107 						s.WriteString(fmt.Sprintf("\n\t\twant %s", e.r.Type()))
    108 					}
    109 					report.Report(pass, assert, s.String())
    110 				}
    111 			}
    112 		}
    113 	}
    114 	return nil, nil
    115 }