...

Source file src/cmd/vendor/golang.org/x/tools/go/analysis/passes/stringintconv/string.go

Documentation: cmd/vendor/golang.org/x/tools/go/analysis/passes/stringintconv

     1  // Copyright 2020 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package stringintconv
     6  
     7  import (
     8  	_ "embed"
     9  	"fmt"
    10  	"go/ast"
    11  	"go/types"
    12  	"strings"
    13  
    14  	"golang.org/x/tools/go/analysis"
    15  	"golang.org/x/tools/go/analysis/passes/inspect"
    16  	"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
    17  	"golang.org/x/tools/go/ast/inspector"
    18  	"golang.org/x/tools/internal/typeparams"
    19  )
    20  
    21  //go:embed doc.go
    22  var doc string
    23  
    24  var Analyzer = &analysis.Analyzer{
    25  	Name:     "stringintconv",
    26  	Doc:      analysisutil.MustExtractDoc(doc, "stringintconv"),
    27  	URL:      "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/stringintconv",
    28  	Requires: []*analysis.Analyzer{inspect.Analyzer},
    29  	Run:      run,
    30  }
    31  
    32  // describe returns a string describing the type typ contained within the type
    33  // set of inType. If non-empty, inName is used as the name of inType (this is
    34  // necessary so that we can use alias type names that may not be reachable from
    35  // inType itself).
    36  func describe(typ, inType types.Type, inName string) string {
    37  	name := inName
    38  	if typ != inType {
    39  		name = typeName(typ)
    40  	}
    41  	if name == "" {
    42  		return ""
    43  	}
    44  
    45  	var parentheticals []string
    46  	if underName := typeName(typ.Underlying()); underName != "" && underName != name {
    47  		parentheticals = append(parentheticals, underName)
    48  	}
    49  
    50  	if typ != inType && inName != "" && inName != name {
    51  		parentheticals = append(parentheticals, "in "+inName)
    52  	}
    53  
    54  	if len(parentheticals) > 0 {
    55  		name += " (" + strings.Join(parentheticals, ", ") + ")"
    56  	}
    57  
    58  	return name
    59  }
    60  
    61  func typeName(typ types.Type) string {
    62  	if v, _ := typ.(interface{ Name() string }); v != nil {
    63  		return v.Name()
    64  	}
    65  	if v, _ := typ.(interface{ Obj() *types.TypeName }); v != nil {
    66  		return v.Obj().Name()
    67  	}
    68  	return ""
    69  }
    70  
    71  func run(pass *analysis.Pass) (interface{}, error) {
    72  	inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
    73  	nodeFilter := []ast.Node{
    74  		(*ast.CallExpr)(nil),
    75  	}
    76  	inspect.Preorder(nodeFilter, func(n ast.Node) {
    77  		call := n.(*ast.CallExpr)
    78  
    79  		if len(call.Args) != 1 {
    80  			return
    81  		}
    82  		arg := call.Args[0]
    83  
    84  		// Retrieve target type name.
    85  		var tname *types.TypeName
    86  		switch fun := call.Fun.(type) {
    87  		case *ast.Ident:
    88  			tname, _ = pass.TypesInfo.Uses[fun].(*types.TypeName)
    89  		case *ast.SelectorExpr:
    90  			tname, _ = pass.TypesInfo.Uses[fun.Sel].(*types.TypeName)
    91  		}
    92  		if tname == nil {
    93  			return
    94  		}
    95  
    96  		// In the conversion T(v) of a value v of type V to a target type T, we
    97  		// look for types T0 in the type set of T and V0 in the type set of V, such
    98  		// that V0->T0 is a problematic conversion. If T and V are not type
    99  		// parameters, this amounts to just checking if V->T is a problematic
   100  		// conversion.
   101  
   102  		// First, find a type T0 in T that has an underlying type of string.
   103  		T := tname.Type()
   104  		ttypes, err := structuralTypes(T)
   105  		if err != nil {
   106  			return // invalid type
   107  		}
   108  
   109  		var T0 types.Type // string type in the type set of T
   110  
   111  		for _, tt := range ttypes {
   112  			u, _ := tt.Underlying().(*types.Basic)
   113  			if u != nil && u.Kind() == types.String {
   114  				T0 = tt
   115  				break
   116  			}
   117  		}
   118  
   119  		if T0 == nil {
   120  			// No target types have an underlying type of string.
   121  			return
   122  		}
   123  
   124  		// Next, find a type V0 in V that has an underlying integral type that is
   125  		// not byte or rune.
   126  		V := pass.TypesInfo.TypeOf(arg)
   127  		vtypes, err := structuralTypes(V)
   128  		if err != nil {
   129  			return // invalid type
   130  		}
   131  
   132  		var V0 types.Type // integral type in the type set of V
   133  
   134  		for _, vt := range vtypes {
   135  			u, _ := vt.Underlying().(*types.Basic)
   136  			if u != nil && u.Info()&types.IsInteger != 0 {
   137  				switch u.Kind() {
   138  				case types.Byte, types.Rune, types.UntypedRune:
   139  					continue
   140  				}
   141  				V0 = vt
   142  				break
   143  			}
   144  		}
   145  
   146  		if V0 == nil {
   147  			// No source types are non-byte or rune integer types.
   148  			return
   149  		}
   150  
   151  		convertibleToRune := true // if true, we can suggest a fix
   152  		for _, t := range vtypes {
   153  			if !types.ConvertibleTo(t, types.Typ[types.Rune]) {
   154  				convertibleToRune = false
   155  				break
   156  			}
   157  		}
   158  
   159  		target := describe(T0, T, tname.Name())
   160  		source := describe(V0, V, typeName(V))
   161  
   162  		if target == "" || source == "" {
   163  			return // something went wrong
   164  		}
   165  
   166  		diag := analysis.Diagnostic{
   167  			Pos:     n.Pos(),
   168  			Message: fmt.Sprintf("conversion from %s to %s yields a string of one rune, not a string of digits (did you mean fmt.Sprint(x)?)", source, target),
   169  		}
   170  
   171  		if convertibleToRune {
   172  			diag.SuggestedFixes = []analysis.SuggestedFix{
   173  				{
   174  					Message: "Did you mean to convert a rune to a string?",
   175  					TextEdits: []analysis.TextEdit{
   176  						{
   177  							Pos:     arg.Pos(),
   178  							End:     arg.Pos(),
   179  							NewText: []byte("rune("),
   180  						},
   181  						{
   182  							Pos:     arg.End(),
   183  							End:     arg.End(),
   184  							NewText: []byte(")"),
   185  						},
   186  					},
   187  				},
   188  			}
   189  		}
   190  		pass.Report(diag)
   191  	})
   192  	return nil, nil
   193  }
   194  
   195  func structuralTypes(t types.Type) ([]types.Type, error) {
   196  	var structuralTypes []types.Type
   197  	switch t := t.(type) {
   198  	case *types.TypeParam:
   199  		terms, err := typeparams.StructuralTerms(t)
   200  		if err != nil {
   201  			return nil, err
   202  		}
   203  		for _, term := range terms {
   204  			structuralTypes = append(structuralTypes, term.Type())
   205  		}
   206  	default:
   207  		structuralTypes = append(structuralTypes, t)
   208  	}
   209  	return structuralTypes, nil
   210  }
   211  

View as plain text