blob: c2f2dda3ff89c544dda6f7b613a53db4ed340393 [file]
// Copyright 2026 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package typesinternal
import (
"bytes"
"go/ast"
"go/parser"
"go/printer"
"go/token"
"go/types"
"strings"
"testing"
"golang.org/x/tools/go/ast/inspector"
)
// TestIsAssignedOrAddressTaken tests [IsAssignedOrAddressTaken].
//
// Each /*true*/ or /*false*/ comment in the test source asserts the result of
// IsAssignedOrAddressTaken(info, expr), where expr is the outermost expression
// ending immediately before that comment.
// There can be no space before the comment.
func TestIsAssignedOrAddressTaken(t *testing.T) {
const src = `package p
type S struct {
f int
}
type NestedS struct {
inner S
}
type M struct{}
func (M) ValRecv() {}
func (*M) PtrRecv() {}
type EmbedPtrM struct {
*M
}
type EmbedValM struct {
M
}
func ptr(*int) {}
func fn(int) {}
type StructWithArray struct {
arr [3]int
}
func _() {
var x/*false*/ int // declarations do not count as assigned or address taken
fn(x/*false*/)
_ = x/*false*/
_/*true*/ = 10
x/*true*/ = 10
x/*true*/ += 5
(x/*true*/) = 1 // testing that IsAssignedOrAddressTaken correctly unwraps enclosing parentheses
(x)/*true*/ = 1
x/*true*/ ++
x/*true*/ --
ptr(&(x/*true*/)) // targets "x", which is address-taken
ptr(&(x)/*false*/) // targets compound expression "&x", which itself is not address-taken. the value of the pointer is copied during argument passing
y/*false*/ := 1 // declarations do not count as assigned or address taken
_ = y/*false*/
x/*true*/, z/*false*/ := 2, 3 // x is re-assigned, z is declared
_ = z/*false*/
var a [3]int
a/*true*/ [0] = 1 // targets "a"
a[0]/*true*/ = 1 // targets "a[0]"
ptr(&a/*true*/ [0]) // targets "a"
ptr(&(a[0]/*true*/)) // targets "a[0]"
ptr(&(a[0])/*false*/) // targets "&(a[0])"
// A slice descriptor contains a pointer to the start of its underlying array.
var s []int
s/*false*/ [0] = 1 // a load of the pointer in the slice "s", not a direct assignment of "s"
s[0]/*true*/ = 1 // a store to the array element s[0]
ptr(&s/*false*/ [0])
ptr(&(s[0]/*true*/))
ptr(&(s[0])/*false*/)
var st S
st/*true*/ .f = 1
st.f/*true*/ = 1
var pst *S
pst/*false*/ .f = 1
pst.f/*true*/ = 1
var pst2 *NestedS
pst2/*false*/ .inner.f = 1 // indirect reference through pointer
pst2.inner/*true*/ .f = 1
pst2.inner.f/*true*/ = 1
var m M
m/*true*/ .PtrRecv() // calling a method with pointer receiver on a value expression takes the address
m/*false*/ .ValRecv()
var epm EmbedPtrM
epm/*false*/ .PtrRecv()
var evm EmbedValM
evm/*true*/ .PtrRecv()
var pm *M
pm/*false*/ .PtrRecv()
pm/*false*/ .ValRecv() // dereferences and passes value
var p *int
*(p/*false*/) = 1
*(p)/*true*/ = 1
_ = *(p/*false*/)
ptr(&(*(p)/*true*/)) // targets "*(p)", which is address-taken
var sa StructWithArray
sa.arr/*true*/ [0] = 1
_ = sa.arr/*false*/ [0] // rvalue
var mp map[int]int
mp/*false*/ [0] = 1 // targets "mp", which is a pointer to the map header and itself is not modified
mp[0]/*true*/ = 1
var ch chan int
ch/*false*/ <- x/*false*/
var k, v int
for k/*true*/, v/*true*/ = range mp {}
for k2/*false*/, v2/*false*/ := range mp {
_ = k2
_ = v2
}
_ = k
_ = v
}
`
fset := token.NewFileSet()
file, err := parser.ParseFile(fset, "p.go", src, parser.ParseComments)
if err != nil {
t.Fatal(err)
}
var conf types.Config
info := NewTypesInfo() // need info.Selections
_, err = conf.Check("p", fset, []*ast.File{file}, info)
if err != nil {
t.Fatal(err)
}
inspect := inspector.New([]*ast.File{file})
for _, cg := range file.Comments {
var (
pos = cg.Pos()
line = fset.Position(pos).Line
text = strings.TrimSpace(cg.Text())
)
if text != "true" && text != "false" {
continue // skip other comments
}
// No spaces allowed before the comment.
cur, ok := inspect.Root().FindByPos(pos, pos)
if !ok {
t.Errorf("comment %q at line %d: no cursor found", text, line)
continue
}
// Find the outermost expression ending at or before the comment position.
for cur.Parent().Node() != nil && cur.Parent().Node().End() <= pos {
cur = cur.Parent()
}
got := IsAssignedOrAddressTaken(info, cur)
want := text == "true"
if got != want {
var buf bytes.Buffer
printer.Fprint(&buf, fset, cur.Node())
t.Errorf("line %d: IsLValue for expression %s = %t, want %t", line, buf.String(), got, want)
}
}
}