blob: 281c8bbcad85116f477853c909fec1e23cb69b82 [file] [log] [blame]
Dan Willemsenbc60c3c2021-12-15 01:09:00 -08001// Copyright 2021 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
5package types_test
6
7import (
8 "go/token"
9 . "go/types"
10 "strings"
11 "testing"
12)
13
14func TestInstantiateEquality(t *testing.T) {
15 emptySignature := NewSignatureType(nil, nil, nil, nil, nil, false)
16 tests := []struct {
17 src string
18 name1 string
19 targs1 []Type
20 name2 string
21 targs2 []Type
22 wantEqual bool
23 }{
24 {
25 "package basictype; type T[P any] int",
26 "T", []Type{Typ[Int]},
27 "T", []Type{Typ[Int]},
28 true,
29 },
30 {
31 "package differenttypeargs; type T[P any] int",
32 "T", []Type{Typ[Int]},
33 "T", []Type{Typ[String]},
34 false,
35 },
36 {
37 "package typeslice; type T[P any] int",
38 "T", []Type{NewSlice(Typ[Int])},
39 "T", []Type{NewSlice(Typ[Int])},
40 true,
41 },
42 {
43 // interface{interface{...}} is equivalent to interface{...}
44 "package equivalentinterfaces; type T[P any] int",
45 "T", []Type{
46 NewInterfaceType([]*Func{NewFunc(token.NoPos, nil, "M", emptySignature)}, nil),
47 },
48 "T", []Type{
49 NewInterfaceType(
50 nil,
51 []Type{
52 NewInterfaceType([]*Func{NewFunc(token.NoPos, nil, "M", emptySignature)}, nil),
53 },
54 ),
55 },
56 true,
57 },
58 {
59 // int|string is equivalent to string|int
60 "package equivalenttypesets; type T[P any] int",
61 "T", []Type{
62 NewInterfaceType(nil, []Type{
63 NewUnion([]*Term{NewTerm(false, Typ[Int]), NewTerm(false, Typ[String])}),
64 }),
65 },
66 "T", []Type{
67 NewInterfaceType(nil, []Type{
68 NewUnion([]*Term{NewTerm(false, Typ[String]), NewTerm(false, Typ[Int])}),
69 }),
70 },
71 true,
72 },
73 {
74 "package basicfunc; func F[P any]() {}",
75 "F", []Type{Typ[Int]},
76 "F", []Type{Typ[Int]},
77 true,
78 },
79 {
80 "package funcslice; func F[P any]() {}",
81 "F", []Type{NewSlice(Typ[Int])},
82 "F", []Type{NewSlice(Typ[Int])},
83 true,
84 },
85 {
86 "package funcwithparams; func F[P any](x string) float64 { return 0 }",
87 "F", []Type{Typ[Int]},
88 "F", []Type{Typ[Int]},
89 true,
90 },
91 {
92 "package differentfuncargs; func F[P any](x string) float64 { return 0 }",
93 "F", []Type{Typ[Int]},
94 "F", []Type{Typ[String]},
95 false,
96 },
97 {
98 "package funcequality; func F1[P any](x int) {}; func F2[Q any](x int) {}",
99 "F1", []Type{Typ[Int]},
100 "F2", []Type{Typ[Int]},
101 false,
102 },
103 {
104 "package funcsymmetry; func F1[P any](x P) {}; func F2[Q any](x Q) {}",
105 "F1", []Type{Typ[Int]},
106 "F2", []Type{Typ[Int]},
107 false,
108 },
109 }
110
111 for _, test := range tests {
112 pkg, err := pkgForMode(".", test.src, nil, 0)
113 if err != nil {
114 t.Fatal(err)
115 }
116
117 t.Run(pkg.Name(), func(t *testing.T) {
118 ctxt := NewContext()
119
120 T1 := pkg.Scope().Lookup(test.name1).Type()
121 res1, err := Instantiate(ctxt, T1, test.targs1, false)
122 if err != nil {
123 t.Fatal(err)
124 }
125
126 T2 := pkg.Scope().Lookup(test.name2).Type()
127 res2, err := Instantiate(ctxt, T2, test.targs2, false)
128 if err != nil {
129 t.Fatal(err)
130 }
131
132 if gotEqual := res1 == res2; gotEqual != test.wantEqual {
133 t.Errorf("%s == %s: %t, want %t", res1, res2, gotEqual, test.wantEqual)
134 }
135 })
136 }
137}
138
139func TestInstantiateNonEquality(t *testing.T) {
140 const src = "package p; type T[P any] int"
141
142 pkg1, err := pkgForMode(".", src, nil, 0)
143 if err != nil {
144 t.Fatal(err)
145 }
146 pkg2, err := pkgForMode(".", src, nil, 0)
147 if err != nil {
148 t.Fatal(err)
149 }
150
151 // We consider T1 and T2 to be distinct types, so their instances should not
152 // be deduplicated by the context.
153 T1 := pkg1.Scope().Lookup("T").Type().(*Named)
154 T2 := pkg2.Scope().Lookup("T").Type().(*Named)
155
156 ctxt := NewContext()
157 res1, err := Instantiate(ctxt, T1, []Type{Typ[Int]}, false)
158 if err != nil {
159 t.Fatal(err)
160 }
161 res2, err := Instantiate(ctxt, T2, []Type{Typ[Int]}, false)
162 if err != nil {
163 t.Fatal(err)
164 }
165
166 if res1 == res2 {
167 t.Errorf("instance from pkg1 (%s) is pointer-equivalent to instance from pkg2 (%s)", res1, res2)
168 }
169 if Identical(res1, res2) {
170 t.Errorf("instance from pkg1 (%s) is identical to instance from pkg2 (%s)", res1, res2)
171 }
172}
173
174func TestMethodInstantiation(t *testing.T) {
175 const prefix = `package p
176
177type T[P any] struct{}
178
179var X T[int]
180
181`
182 tests := []struct {
183 decl string
184 want string
185 }{
186 {"func (r T[P]) m() P", "func (T[int]).m() int"},
187 {"func (r T[P]) m(P)", "func (T[int]).m(int)"},
188 {"func (r *T[P]) m(P)", "func (*T[int]).m(int)"},
189 {"func (r T[P]) m() T[P]", "func (T[int]).m() T[int]"},
190 {"func (r T[P]) m(T[P])", "func (T[int]).m(T[int])"},
191 {"func (r T[P]) m(T[P], P, string)", "func (T[int]).m(T[int], int, string)"},
192 {"func (r T[P]) m(T[P], T[string], T[int])", "func (T[int]).m(T[int], T[string], T[int])"},
193 }
194
195 for _, test := range tests {
196 src := prefix + test.decl
197 pkg, err := pkgForMode(".", src, nil, 0)
198 if err != nil {
199 t.Fatal(err)
200 }
201 typ := NewPointer(pkg.Scope().Lookup("X").Type())
202 obj, _, _ := LookupFieldOrMethod(typ, false, pkg, "m")
203 m, _ := obj.(*Func)
204 if m == nil {
205 t.Fatalf(`LookupFieldOrMethod(%s, "m") = %v, want func m`, typ, obj)
206 }
207 if got := ObjectString(m, RelativeTo(pkg)); got != test.want {
208 t.Errorf("instantiated %q, want %q", got, test.want)
209 }
210 }
211}
212
213func TestImmutableSignatures(t *testing.T) {
214 const src = `package p
215
216type T[P any] struct{}
217
218func (T[P]) m() {}
219
220var _ T[int]
221`
222 pkg, err := pkgForMode(".", src, nil, 0)
223 if err != nil {
224 t.Fatal(err)
225 }
226 typ := pkg.Scope().Lookup("T").Type().(*Named)
227 obj, _, _ := LookupFieldOrMethod(typ, false, pkg, "m")
228 if obj == nil {
229 t.Fatalf(`LookupFieldOrMethod(%s, "m") = %v, want func m`, typ, obj)
230 }
231
232 // Verify that the original method is not mutated by instantiating T (this
233 // bug manifested when subst did not return a new signature).
234 want := "func (T[P]).m()"
235 if got := stripAnnotations(ObjectString(obj, RelativeTo(pkg))); got != want {
236 t.Errorf("instantiated %q, want %q", got, want)
237 }
238}
239
240// Copied from errors.go.
241func stripAnnotations(s string) string {
242 var b strings.Builder
243 for _, r := range s {
244 // strip #'s and subscript digits
245 if r < '₀' || '₀'+10 <= r { // '₀' == U+2080
246 b.WriteRune(r)
247 }
248 }
249 if b.Len() < len(s) {
250 return b.String()
251 }
252 return s
253}