| // 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) | |
| } | |
| } | |
| } |