@@ -18,6 +18,7 @@ import (
18
18
19
19
"golang.org/x/tools/go/types/typeutil"
20
20
"golang.org/x/tools/internal/typeparams"
21
+ "golang.org/x/tools/internal/typesinternal"
21
22
)
22
23
23
24
// A Callee holds information about an inlinable function. Gob-serializable.
@@ -382,19 +383,22 @@ func parseCompact(content []byte) (*token.FileSet, *ast.FuncDecl, error) {
382
383
383
384
// A paramInfo records information about a callee receiver, parameter, or result variable.
384
385
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
393
395
}
394
396
395
397
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
398
402
// IsSelectionOperand indicates whether the parameter reference is the
399
403
// operand of a selection (param.f). If so, and param's argument is itself
400
404
// 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
421
425
sig := fnobj .Type ().(* types.Signature )
422
426
newParamInfo := func (param * types.Var , isResult bool ) * paramInfo {
423
427
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 ()),
427
432
}
428
433
paramInfos [param ] = info
429
434
return info
@@ -479,10 +484,12 @@ func analyzeParams(logf func(string, ...any), fset *token.FileSet, info *types.I
479
484
// Contrapositively, if param is not an interface type, then the
480
485
// assignment may lose type information, for example in the case that
481
486
// the substituted expression is an untyped constant or unnamed type.
482
- ifaceParam := isNonTypeParamInterface ( v . Type () )
487
+ assignable , ifaceAssign , affectsInference := analyzeAssignment ( info , stack )
483
488
ref := refInfo {
484
489
Offset : int (n .Pos () - decl .Pos ()),
485
- NeedType : ! ifaceParam || ! isAssignableOperand (info , stack ),
490
+ Assignable : assignable ,
491
+ IfaceAssignment : ifaceAssign ,
492
+ AffectsInference : affectsInference ,
486
493
IsSelectionOperand : isSelectionOperand (stack ),
487
494
}
488
495
pinfo .Refs = append (pinfo .Refs , ref )
@@ -505,27 +512,58 @@ func analyzeParams(logf func(string, ...any), fset *token.FileSet, info *types.I
505
512
506
513
// -- callee helpers --
507
514
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.
512
517
//
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 )
520
539
if parent == nil {
521
- return false
540
+ return false , false , false
522
541
}
523
542
543
+ // TODO(golang/go#70638): simplify when types.Info records implicit conversions.
544
+
524
545
// 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 {
527
548
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
529
567
}
530
568
}
531
569
}
@@ -534,74 +572,155 @@ func isAssignableOperand(info *types.Info, stack []ast.Node) bool {
534
572
if spec , ok := parent .(* ast.ValueSpec ); ok && spec .Type != nil {
535
573
for _ , v := range spec .Values {
536
574
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
538
587
}
588
+ m , _ := typeparams .CoreType (typ ).(* types.Map )
589
+ return true , m == nil || types .IsInterface (m .Key ()), false
539
590
}
540
591
}
541
592
542
593
// Types do not need to match for composite literal keys, values, or
543
594
// fields.
544
595
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
+ }
547
627
}
548
628
}
549
629
if lit , ok := parent .(* ast.CompositeLit ); ok {
550
- for _ , v := range lit .Elts {
630
+ for i , v := range lit .Elts {
551
631
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
553
648
}
554
649
}
555
650
}
556
651
557
652
// Types do not need to match for values sent to a channel.
558
653
if send , ok := parent .(* ast.SendStmt ); ok {
559
654
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
561
661
}
562
662
}
563
663
564
664
// Types do not need to match for an argument to a call, unless the
565
665
// corresponding parameter has type parameters, as in that case the
566
666
// argument type may affect inference.
567
667
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.
568
672
for i , arg := range call .Args {
569
673
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 {
573
680
// 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 )
578
688
}
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
585
689
}
690
+ return true , ifaceAssign , affectsInference
586
691
}
587
- break
588
692
}
589
693
}
590
694
}
591
695
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
593
710
}
594
711
595
712
// 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.
597
715
//
598
716
// 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 ) {
600
718
expr , _ = stack [len (stack )- 1 ].(ast.Expr )
601
719
if expr == nil {
602
- return nil , nil
720
+ return nil , nil , nil
603
721
}
604
- for i := len (stack ) - 2 ; i >= 0 ; i -- {
722
+ i := len (stack ) - 2
723
+ for ; i >= 0 ; i -- {
605
724
if pexpr , ok := stack [i ].(* ast.ParenExpr ); ok {
606
725
expr = pexpr
607
726
} else {
@@ -610,15 +729,16 @@ func exprContext(stack []ast.Node) (parent ast.Node, expr ast.Expr) {
610
729
}
611
730
}
612
731
if parent == nil {
613
- return nil , nil
732
+ return nil , nil , nil
614
733
}
615
- return parent , expr
734
+ // inv: i is the index of parent in the stack.
735
+ return stack [:i ], parent , expr
616
736
}
617
737
618
738
// isSelectionOperand reports whether the innermost node of stack is operand
619
739
// (x) of a selection x.f.
620
740
func isSelectionOperand (stack []ast.Node ) bool {
621
- parent , expr := exprContext (stack )
741
+ _ , parent , expr := exprContext (stack )
622
742
if parent == nil {
623
743
return false
624
744
}
0 commit comments