Skip to content

Commit 25b0003

Browse files
committed
internal/refactor/inline: more precise redundant conversion detection
Add more precision (and hopefully clarity) to the detection of redundant conversions, by using more granular analysis of references. We now record, for each reference, (1) whether it appears in an assignment context, perhaps subject to implicit conversions, (2) whether this assignment is to an interface value, and (3) whether this assignment may affect type inference. With these conditions, we can more precisely detect scenarios where conversion is necessary, as annotated in the code. Notably, this avoids unnecessary concrete-to-concrete conversions. Also: - Fix a crash in falcon analysis for named literal types. - Handle index expression assignability (previously missing). - Avoid the print builtin as an example of a function that takes 'any', as the type checker records the effective type for print, and the spec is unclear about whether implementations are allowed to specialize for effective types. For golang/go#70599 Updates golang/go#70638 Change-Id: I9730deba54d864928a1dc02a1ab00481a2ce9998 Reviewed-on: https://go-review.googlesource.com/c/tools/+/632935 LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Alan Donovan <adonovan@google.com>
1 parent eb46939 commit 25b0003

File tree

5 files changed

+315
-83
lines changed

5 files changed

+315
-83
lines changed

gopls/internal/test/marker/testdata/codeaction/moveparam_issue70599.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@ func Cache(value any, key [32]byte) { //@codeaction("key", "refactor.rewrite.mov
4141

4242
func _() {
4343
var k Hash
44-
Cache(0, [32]byte(k))
45-
Cache(1, [32]byte(Hash{}))
44+
Cache(0, k)
45+
Cache(1, Hash{})
4646
Cache(2, [32]byte{})
4747
}
4848
-- shortvardecl.go --

internal/refactor/inline/callee.go

Lines changed: 178 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818

1919
"golang.org/x/tools/go/types/typeutil"
2020
"golang.org/x/tools/internal/typeparams"
21+
"golang.org/x/tools/internal/typesinternal"
2122
)
2223

2324
// A Callee holds information about an inlinable function. Gob-serializable.
@@ -382,19 +383,22 @@ func parseCompact(content []byte) (*token.FileSet, *ast.FuncDecl, error) {
382383

383384
// A paramInfo records information about a callee receiver, parameter, or result variable.
384385
type paramInfo struct {
385-
Name string // parameter name (may be blank, or even "")
386-
Index int // index within signature
387-
IsResult bool // false for receiver or parameter, true for result variable
388-
Assigned bool // parameter appears on left side of an assignment statement
389-
Escapes bool // parameter has its address taken
390-
Refs []refInfo // information about references to parameter within body
391-
Shadow map[string]bool // names shadowed at one of the above refs
392-
FalconType string // name of this parameter's type (if basic) in the falcon system
386+
Name string // parameter name (may be blank, or even "")
387+
Index int // index within signature
388+
IsResult bool // false for receiver or parameter, true for result variable
389+
IsInterface bool // parameter has a (non-type parameter) interface type
390+
Assigned bool // parameter appears on left side of an assignment statement
391+
Escapes bool // parameter has its address taken
392+
Refs []refInfo // information about references to parameter within body
393+
Shadow map[string]bool // names shadowed at one of the above refs
394+
FalconType string // name of this parameter's type (if basic) in the falcon system
393395
}
394396

395397
type refInfo struct {
396-
Offset int // FuncDecl-relative byte offset of parameter ref within body
397-
NeedType bool // type of substituted arg must be identical to type of param
398+
Offset int // FuncDecl-relative byte offset of parameter ref within body
399+
Assignable bool // ref appears in context of assignment to known type
400+
IfaceAssignment bool // ref is being assigned to an interface
401+
AffectsInference bool // ref type may affect type inference
398402
// IsSelectionOperand indicates whether the parameter reference is the
399403
// operand of a selection (param.f). If so, and param's argument is itself
400404
// a receiver parameter (a common case), we don't need to desugar (&v or *ptr)
@@ -421,9 +425,10 @@ func analyzeParams(logf func(string, ...any), fset *token.FileSet, info *types.I
421425
sig := fnobj.Type().(*types.Signature)
422426
newParamInfo := func(param *types.Var, isResult bool) *paramInfo {
423427
info := &paramInfo{
424-
Name: param.Name(),
425-
IsResult: isResult,
426-
Index: len(paramInfos),
428+
Name: param.Name(),
429+
IsResult: isResult,
430+
Index: len(paramInfos),
431+
IsInterface: isNonTypeParamInterface(param.Type()),
427432
}
428433
paramInfos[param] = info
429434
return info
@@ -479,10 +484,12 @@ func analyzeParams(logf func(string, ...any), fset *token.FileSet, info *types.I
479484
// Contrapositively, if param is not an interface type, then the
480485
// assignment may lose type information, for example in the case that
481486
// the substituted expression is an untyped constant or unnamed type.
482-
ifaceParam := isNonTypeParamInterface(v.Type())
487+
assignable, ifaceAssign, affectsInference := analyzeAssignment(info, stack)
483488
ref := refInfo{
484489
Offset: int(n.Pos() - decl.Pos()),
485-
NeedType: !ifaceParam || !isAssignableOperand(info, stack),
490+
Assignable: assignable,
491+
IfaceAssignment: ifaceAssign,
492+
AffectsInference: affectsInference,
486493
IsSelectionOperand: isSelectionOperand(stack),
487494
}
488495
pinfo.Refs = append(pinfo.Refs, ref)
@@ -505,27 +512,58 @@ func analyzeParams(logf func(string, ...any), fset *token.FileSet, info *types.I
505512

506513
// -- callee helpers --
507514

508-
// isAssignableOperand reports whether the given outer-to-inner stack has an
509-
// innermost expression operand for which assignability rules apply, meaning it
510-
// is used in a position where its type need only be assignable to the type
511-
// implied by its surrounding syntax.
515+
// analyzeAssignment looks at the the given stack, and analyzes certain
516+
// attributes of the innermost expression.
512517
//
513-
// As this function is intended to be used for inlining analysis, it reports
514-
// false in one case where the operand technically need only be assignable: if
515-
// the type being assigned to is a call argument and contains type parameters,
516-
// then passing a different (yet assignable) type may affect type inference,
517-
// and so isAssignableOperand reports false.
518-
func isAssignableOperand(info *types.Info, stack []ast.Node) bool {
519-
parent, expr := exprContext(stack)
518+
// In all cases we 'fail closed' when we cannot detect (or for simplicity
519+
// choose not to detect) the condition in question, meaning we err on the side
520+
// of the more restrictive rule. This is noted for each result below.
521+
//
522+
// - assignable reports whether the expression is used in a position where
523+
// assignability rules apply, such as in an actual assignment, as call
524+
// argument, or in a send to a channel. Defaults to 'false'. If assignable
525+
// is false, the other two results are irrelevant.
526+
// - ifaceAssign reports whether that assignment is to an interface type.
527+
// This is important as we want to preserve the concrete type in that
528+
// assignment. Defaults to 'true'. Notably, if the assigned type is a type
529+
// parameter, we assume that it could have interface type.
530+
// - affectsInference is (somewhat vaguely) defined as whether or not the
531+
// type of the operand may affect the type of the surrounding syntax,
532+
// through type inference. It is infeasible to completely reverse engineer
533+
// type inference, so we over approximate: if the expression is an argument
534+
// to a call to a generic function (but not method!) that uses type
535+
// parameters, assume that unification of that argument may affect the
536+
// inferred types.
537+
func analyzeAssignment(info *types.Info, stack []ast.Node) (assignable, ifaceAssign, affectsInference bool) {
538+
remaining, parent, expr := exprContext(stack)
520539
if parent == nil {
521-
return false
540+
return false, false, false
522541
}
523542

543+
// TODO(golang/go#70638): simplify when types.Info records implicit conversions.
544+
524545
// Types do not need to match for assignment to a variable.
525-
if assign, ok := parent.(*ast.AssignStmt); ok && assign.Tok == token.ASSIGN {
526-
for _, v := range assign.Rhs {
546+
if assign, ok := parent.(*ast.AssignStmt); ok {
547+
for i, v := range assign.Rhs {
527548
if v == expr {
528-
return true
549+
if i >= len(assign.Lhs) {
550+
return false, false, false // ill typed
551+
}
552+
// Check to see if the assignment is to an interface type.
553+
if i < len(assign.Lhs) {
554+
// TODO: We could handle spread calls here, but in current usage expr
555+
// is an ident.
556+
if id, _ := assign.Lhs[i].(*ast.Ident); id != nil && info.Defs[id] != nil {
557+
// Types must match for a defining identifier in a short variable
558+
// declaration.
559+
return false, false, false
560+
}
561+
// In all other cases, types should be known.
562+
typ := info.TypeOf(assign.Lhs[i])
563+
return true, typ == nil || types.IsInterface(typ), false
564+
}
565+
// Default:
566+
return assign.Tok == token.ASSIGN, true, false
529567
}
530568
}
531569
}
@@ -534,74 +572,155 @@ func isAssignableOperand(info *types.Info, stack []ast.Node) bool {
534572
if spec, ok := parent.(*ast.ValueSpec); ok && spec.Type != nil {
535573
for _, v := range spec.Values {
536574
if v == expr {
537-
return true
575+
typ := info.TypeOf(spec.Type)
576+
return true, typ == nil || types.IsInterface(typ), false
577+
}
578+
}
579+
}
580+
581+
// Types do not need to match for index expresions.
582+
if ix, ok := parent.(*ast.IndexExpr); ok {
583+
if ix.Index == expr {
584+
typ := info.TypeOf(ix.X)
585+
if typ == nil {
586+
return true, true, false
538587
}
588+
m, _ := typeparams.CoreType(typ).(*types.Map)
589+
return true, m == nil || types.IsInterface(m.Key()), false
539590
}
540591
}
541592

542593
// Types do not need to match for composite literal keys, values, or
543594
// fields.
544595
if kv, ok := parent.(*ast.KeyValueExpr); ok {
545-
if kv.Key == expr || kv.Value == expr {
546-
return true
596+
var under types.Type
597+
if len(remaining) > 0 {
598+
if complit, ok := remaining[len(remaining)-1].(*ast.CompositeLit); ok {
599+
if typ := info.TypeOf(complit); typ != nil {
600+
// Unpointer to allow for pointers to slices or arrays, which are
601+
// permitted as the types of nested composite literals without a type
602+
// name.
603+
under = typesinternal.Unpointer(typeparams.CoreType(typ))
604+
}
605+
}
606+
}
607+
if kv.Key == expr { // M{expr: ...}: assign to map key
608+
m, _ := under.(*types.Map)
609+
return true, m == nil || types.IsInterface(m.Key()), false
610+
}
611+
if kv.Value == expr {
612+
switch under := under.(type) {
613+
case interface{ Elem() types.Type }: // T{...: expr}: assign to map/array/slice element
614+
return true, types.IsInterface(under.Elem()), false
615+
case *types.Struct: // Struct{k: expr}
616+
if id, _ := kv.Key.(*ast.Ident); id != nil {
617+
for fi := 0; fi < under.NumFields(); fi++ {
618+
field := under.Field(fi)
619+
if info.Uses[id] == field {
620+
return true, types.IsInterface(field.Type()), false
621+
}
622+
}
623+
}
624+
default:
625+
return true, true, false
626+
}
547627
}
548628
}
549629
if lit, ok := parent.(*ast.CompositeLit); ok {
550-
for _, v := range lit.Elts {
630+
for i, v := range lit.Elts {
551631
if v == expr {
552-
return true
632+
typ := info.TypeOf(lit)
633+
if typ == nil {
634+
return true, true, false
635+
}
636+
// As in the KeyValueExpr case above, unpointer to handle pointers to
637+
// array/slice literals.
638+
under := typesinternal.Unpointer(typeparams.CoreType(typ))
639+
switch under := under.(type) {
640+
case interface{ Elem() types.Type }: // T{expr}: assign to map/array/slice element
641+
return true, types.IsInterface(under.Elem()), false
642+
case *types.Struct: // Struct{expr}: assign to unkeyed struct field
643+
if i < under.NumFields() {
644+
return true, types.IsInterface(under.Field(i).Type()), false
645+
}
646+
}
647+
return true, true, false
553648
}
554649
}
555650
}
556651

557652
// Types do not need to match for values sent to a channel.
558653
if send, ok := parent.(*ast.SendStmt); ok {
559654
if send.Value == expr {
560-
return true
655+
typ := info.TypeOf(send.Chan)
656+
if typ == nil {
657+
return true, true, false
658+
}
659+
ch, _ := typeparams.CoreType(typ).(*types.Chan)
660+
return true, ch == nil || types.IsInterface(ch.Elem()), false
561661
}
562662
}
563663

564664
// Types do not need to match for an argument to a call, unless the
565665
// corresponding parameter has type parameters, as in that case the
566666
// argument type may affect inference.
567667
if call, ok := parent.(*ast.CallExpr); ok {
668+
if _, ok := isConversion(info, call); ok {
669+
return false, false, false // redundant conversions are handled at the call site
670+
}
671+
// Ordinary call. Could be a call of a func, builtin, or function value.
568672
for i, arg := range call.Args {
569673
if arg == expr {
570-
if fn, _ := typeutil.Callee(info, call).(*types.Func); fn != nil { // Incidentally, this check rejects a conversion T(id).
571-
sig := fn.Type().(*types.Signature)
572-
674+
typ := info.TypeOf(call.Fun)
675+
if typ == nil {
676+
return true, true, false
677+
}
678+
sig, _ := typeparams.CoreType(typ).(*types.Signature)
679+
if sig != nil {
573680
// Find the relevant parameter type, accounting for variadics.
574-
var paramType types.Type
575-
if plen := sig.Params().Len(); sig.Variadic() && i >= plen-1 && !call.Ellipsis.IsValid() {
576-
if s, ok := sig.Params().At(plen - 1).Type().(*types.Slice); ok {
577-
paramType = s.Elem()
681+
paramType := paramTypeAtIndex(sig, call, i)
682+
ifaceAssign := paramType == nil || types.IsInterface(paramType)
683+
affectsInference := false
684+
if fn := typeutil.StaticCallee(info, call); fn != nil {
685+
if sig2 := fn.Type().(*types.Signature); sig2.Recv() == nil {
686+
originParamType := paramTypeAtIndex(sig2, call, i)
687+
affectsInference = originParamType == nil || new(typeparams.Free).Has(originParamType)
578688
}
579-
} else if i < plen {
580-
paramType = sig.Params().At(i).Type()
581-
}
582-
583-
if paramType != nil && !new(typeparams.Free).Has(paramType) {
584-
return true
585689
}
690+
return true, ifaceAssign, affectsInference
586691
}
587-
break
588692
}
589693
}
590694
}
591695

592-
return false // In all other cases, preserve the parameter type.
696+
return false, false, false
697+
}
698+
699+
// paramTypeAtIndex returns the effective parameter type at the given argument
700+
// index in call, if valid.
701+
func paramTypeAtIndex(sig *types.Signature, call *ast.CallExpr, index int) types.Type {
702+
if plen := sig.Params().Len(); sig.Variadic() && index >= plen-1 && !call.Ellipsis.IsValid() {
703+
if s, ok := sig.Params().At(plen - 1).Type().(*types.Slice); ok {
704+
return s.Elem()
705+
}
706+
} else if index < plen {
707+
return sig.Params().At(index).Type()
708+
}
709+
return nil // ill typed
593710
}
594711

595712
// exprContext returns the innermost parent->child expression nodes for the
596-
// given outer-to-inner stack, after stripping parentheses.
713+
// given outer-to-inner stack, after stripping parentheses, along with the
714+
// remaining stack up to the parent node.
597715
//
598716
// If no such context exists, returns (nil, nil).
599-
func exprContext(stack []ast.Node) (parent ast.Node, expr ast.Expr) {
717+
func exprContext(stack []ast.Node) (remaining []ast.Node, parent ast.Node, expr ast.Expr) {
600718
expr, _ = stack[len(stack)-1].(ast.Expr)
601719
if expr == nil {
602-
return nil, nil
720+
return nil, nil, nil
603721
}
604-
for i := len(stack) - 2; i >= 0; i-- {
722+
i := len(stack) - 2
723+
for ; i >= 0; i-- {
605724
if pexpr, ok := stack[i].(*ast.ParenExpr); ok {
606725
expr = pexpr
607726
} else {
@@ -610,15 +729,16 @@ func exprContext(stack []ast.Node) (parent ast.Node, expr ast.Expr) {
610729
}
611730
}
612731
if parent == nil {
613-
return nil, nil
732+
return nil, nil, nil
614733
}
615-
return parent, expr
734+
// inv: i is the index of parent in the stack.
735+
return stack[:i], parent, expr
616736
}
617737

618738
// isSelectionOperand reports whether the innermost node of stack is operand
619739
// (x) of a selection x.f.
620740
func isSelectionOperand(stack []ast.Node) bool {
621-
parent, expr := exprContext(stack)
741+
_, parent, expr := exprContext(stack)
622742
if parent == nil {
623743
return false
624744
}

internal/refactor/inline/falcon.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -446,7 +446,7 @@ func (st *falconState) expr(e ast.Expr) (res any) { // = types.TypeAndValue | as
446446
// - for an array or *array, use [n]int.
447447
// The last two entail progressively stronger index checks.
448448
var ct ast.Expr // type syntax for constraint
449-
switch t := t.(type) {
449+
switch t := typeparams.CoreType(t).(type) {
450450
case *types.Map:
451451
if types.IsInterface(t.Key()) {
452452
ct = &ast.MapType{
@@ -465,7 +465,7 @@ func (st *falconState) expr(e ast.Expr) (res any) { // = types.TypeAndValue | as
465465
Elt: makeIdent(st.int),
466466
}
467467
default:
468-
panic(t)
468+
panic(fmt.Sprintf("%T: %v", t, t))
469469
}
470470
st.emitUnique(ct, uniques)
471471
}

0 commit comments

Comments
 (0)