github.com/amarpal/go-tools@v0.0.0-20240422043104-40142f59f616/staticcheck/sa5010/sa5010.go (about)

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