-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathinstantiate.go
401 lines (364 loc) · 13.2 KB
/
instantiate.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
// Code generated by "go test -run=Generate -write=all"; DO NOT EDIT.
// Source: ../../cmd/compile/internal/types2/instantiate.go
// Copyright 2021 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.
// This file implements instantiation of generic types
// through substitution of type parameters by type arguments.
package types
import (
"errors"
"fmt"
"go/token"
"internal/buildcfg"
. "internal/types/errors"
)
// A genericType implements access to its type parameters.
type genericType interface {
Type
TypeParams() *TypeParamList
}
// Instantiate instantiates the type orig with the given type arguments targs.
// orig must be an *Alias, *Named, or *Signature type. If there is no error,
// the resulting Type is an instantiated type of the same kind (*Alias, *Named
// or *Signature, respectively).
//
// Methods attached to a *Named type are also instantiated, and associated with
// a new *Func that has the same position as the original method, but nil function
// scope.
//
// If ctxt is non-nil, it may be used to de-duplicate the instance against
// previous instances with the same identity. As a special case, generic
// *Signature origin types are only considered identical if they are pointer
// equivalent, so that instantiating distinct (but possibly identical)
// signatures will yield different instances. The use of a shared context does
// not guarantee that identical instances are deduplicated in all cases.
//
// If validate is set, Instantiate verifies that the number of type arguments
// and parameters match, and that the type arguments satisfy their respective
// type constraints. If verification fails, the resulting error may wrap an
// *ArgumentError indicating which type argument did not satisfy its type parameter
// constraint, and why.
//
// If validate is not set, Instantiate does not verify the type argument count
// or whether the type arguments satisfy their constraints. Instantiate is
// guaranteed to not return an error, but may panic. Specifically, for
// *Signature types, Instantiate will panic immediately if the type argument
// count is incorrect; for *Named types, a panic may occur later inside the
// *Named API.
func Instantiate(ctxt *Context, orig Type, targs []Type, validate bool) (Type, error) {
assert(len(targs) > 0)
if ctxt == nil {
ctxt = NewContext()
}
orig_ := orig.(genericType) // signature of Instantiate must not change for backward-compatibility
if validate {
tparams := orig_.TypeParams().list()
assert(len(tparams) > 0)
if len(targs) != len(tparams) {
return nil, fmt.Errorf("got %d type arguments but %s has %d type parameters", len(targs), orig, len(tparams))
}
if i, err := (*Checker)(nil).verify(nopos, tparams, targs, ctxt); err != nil {
return nil, &ArgumentError{i, err}
}
}
inst := (*Checker)(nil).instance(nopos, orig_, targs, nil, ctxt)
return inst, nil
}
// instance instantiates the given original (generic) function or type with the
// provided type arguments and returns the resulting instance. If an identical
// instance exists already in the given contexts, it returns that instance,
// otherwise it creates a new one. If there is an error (such as wrong number
// of type arguments), the result is Typ[Invalid].
//
// If expanding is non-nil, it is the Named instance type currently being
// expanded. If ctxt is non-nil, it is the context associated with the current
// type-checking pass or call to Instantiate. At least one of expanding or ctxt
// must be non-nil.
//
// For Named types the resulting instance may be unexpanded.
//
// check may be nil (when not type-checking syntax); pos is used only only if check is non-nil.
func (check *Checker) instance(pos token.Pos, orig genericType, targs []Type, expanding *Named, ctxt *Context) (res Type) {
// The order of the contexts below matters: we always prefer instances in the
// expanding instance context in order to preserve reference cycles.
//
// Invariant: if expanding != nil, the returned instance will be the instance
// recorded in expanding.inst.ctxt.
var ctxts []*Context
if expanding != nil {
ctxts = append(ctxts, expanding.inst.ctxt)
}
if ctxt != nil {
ctxts = append(ctxts, ctxt)
}
assert(len(ctxts) > 0)
// Compute all hashes; hashes may differ across contexts due to different
// unique IDs for Named types within the hasher.
hashes := make([]string, len(ctxts))
for i, ctxt := range ctxts {
hashes[i] = ctxt.instanceHash(orig, targs)
}
// Record the result in all contexts.
// Prefer to re-use existing types from expanding context, if it exists, to reduce
// the memory pinned by the Named type.
updateContexts := func(res Type) Type {
for i := len(ctxts) - 1; i >= 0; i-- {
res = ctxts[i].update(hashes[i], orig, targs, res)
}
return res
}
// typ may already have been instantiated with identical type arguments. In
// that case, re-use the existing instance.
for i, ctxt := range ctxts {
if inst := ctxt.lookup(hashes[i], orig, targs); inst != nil {
return updateContexts(inst)
}
}
switch orig := orig.(type) {
case *Named:
res = check.newNamedInstance(pos, orig, targs, expanding) // substituted lazily
case *Alias:
if !buildcfg.Experiment.AliasTypeParams {
assert(expanding == nil) // Alias instances cannot be reached from Named types
}
// verify type parameter count (see go.dev/issue/71198 for a test case)
tparams := orig.TypeParams()
if !check.validateTArgLen(pos, orig.obj.Name(), tparams.Len(), len(targs)) {
// TODO(gri) Consider returning a valid alias instance with invalid
// underlying (aliased) type to match behavior of *Named
// types. Then this function will never return an invalid
// result.
return Typ[Invalid]
}
if tparams.Len() == 0 {
return orig // nothing to do (minor optimization)
}
res = check.newAliasInstance(pos, orig, targs, expanding, ctxt)
case *Signature:
assert(expanding == nil) // function instances cannot be reached from Named types
tparams := orig.TypeParams()
// TODO(gri) investigate if this is needed (type argument and parameter count seem to be correct here)
if !check.validateTArgLen(pos, orig.String(), tparams.Len(), len(targs)) {
return Typ[Invalid]
}
if tparams.Len() == 0 {
return orig // nothing to do (minor optimization)
}
sig := check.subst(pos, orig, makeSubstMap(tparams.list(), targs), nil, ctxt).(*Signature)
// If the signature doesn't use its type parameters, subst
// will not make a copy. In that case, make a copy now (so
// we can set tparams to nil w/o causing side-effects).
if sig == orig {
copy := *sig
sig = ©
}
// After instantiating a generic signature, it is not generic
// anymore; we need to set tparams to nil.
sig.tparams = nil
res = sig
default:
// only types and functions can be generic
panic(fmt.Sprintf("%v: cannot instantiate %v", pos, orig))
}
// Update all contexts; it's possible that we've lost a race.
return updateContexts(res)
}
// validateTArgLen checks that the number of type arguments (got) matches the
// number of type parameters (want); if they don't match an error is reported.
// If validation fails and check is nil, validateTArgLen panics.
func (check *Checker) validateTArgLen(pos token.Pos, name string, want, got int) bool {
var qual string
switch {
case got < want:
qual = "not enough"
case got > want:
qual = "too many"
default:
return true
}
msg := check.sprintf("%s type arguments for type %s: have %d, want %d", qual, name, got, want)
if check != nil {
check.error(atPos(pos), WrongTypeArgCount, msg)
return false
}
panic(fmt.Sprintf("%v: %s", pos, msg))
}
// check may be nil; pos is used only if check is non-nil.
func (check *Checker) verify(pos token.Pos, tparams []*TypeParam, targs []Type, ctxt *Context) (int, error) {
smap := makeSubstMap(tparams, targs)
for i, tpar := range tparams {
// Ensure that we have a (possibly implicit) interface as type bound (go.dev/issue/51048).
tpar.iface()
// The type parameter bound is parameterized with the same type parameters
// as the instantiated type; before we can use it for bounds checking we
// need to instantiate it with the type arguments with which we instantiated
// the parameterized type.
bound := check.subst(pos, tpar.bound, smap, nil, ctxt)
var cause string
if !check.implements(targs[i], bound, true, &cause) {
return i, errors.New(cause)
}
}
return -1, nil
}
// implements checks if V implements T. The receiver may be nil if implements
// is called through an exported API call such as AssignableTo. If constraint
// is set, T is a type constraint.
//
// If the provided cause is non-nil, it may be set to an error string
// explaining why V does not implement (or satisfy, for constraints) T.
func (check *Checker) implements(V, T Type, constraint bool, cause *string) bool {
Vu := under(V)
Tu := under(T)
if !isValid(Vu) || !isValid(Tu) {
return true // avoid follow-on errors
}
if p, _ := Vu.(*Pointer); p != nil && !isValid(under(p.base)) {
return true // avoid follow-on errors (see go.dev/issue/49541 for an example)
}
verb := "implement"
if constraint {
verb = "satisfy"
}
Ti, _ := Tu.(*Interface)
if Ti == nil {
if cause != nil {
var detail string
if isInterfacePtr(Tu) {
detail = check.sprintf("type %s is pointer to interface, not interface", T)
} else {
detail = check.sprintf("%s is not an interface", T)
}
*cause = check.sprintf("%s does not %s %s (%s)", V, verb, T, detail)
}
return false
}
// Every type satisfies the empty interface.
if Ti.Empty() {
return true
}
// T is not the empty interface (i.e., the type set of T is restricted)
// An interface V with an empty type set satisfies any interface.
// (The empty set is a subset of any set.)
Vi, _ := Vu.(*Interface)
if Vi != nil && Vi.typeSet().IsEmpty() {
return true
}
// type set of V is not empty
// No type with non-empty type set satisfies the empty type set.
if Ti.typeSet().IsEmpty() {
if cause != nil {
*cause = check.sprintf("cannot %s %s (empty type set)", verb, T)
}
return false
}
// V must implement T's methods, if any.
if !check.hasAllMethods(V, T, true, Identical, cause) /* !Implements(V, T) */ {
if cause != nil {
*cause = check.sprintf("%s does not %s %s %s", V, verb, T, *cause)
}
return false
}
// Only check comparability if we don't have a more specific error.
checkComparability := func() bool {
if !Ti.IsComparable() {
return true
}
// If T is comparable, V must be comparable.
// If V is strictly comparable, we're done.
if comparableType(V, false /* strict comparability */, nil) == nil {
return true
}
// For constraint satisfaction, use dynamic (spec) comparability
// so that ordinary, non-type parameter interfaces implement comparable.
if constraint && comparableType(V, true /* spec comparability */, nil) == nil {
// V is comparable if we are at Go 1.20 or higher.
if check == nil || check.allowVersion(go1_20) {
return true
}
if cause != nil {
*cause = check.sprintf("%s to %s comparable requires go1.20 or later", V, verb)
}
return false
}
if cause != nil {
*cause = check.sprintf("%s does not %s comparable", V, verb)
}
return false
}
// V must also be in the set of types of T, if any.
// Constraints with empty type sets were already excluded above.
if !Ti.typeSet().hasTerms() {
return checkComparability() // nothing to do
}
// If V is itself an interface, each of its possible types must be in the set
// of T types (i.e., the V type set must be a subset of the T type set).
// Interfaces V with empty type sets were already excluded above.
if Vi != nil {
if !Vi.typeSet().subsetOf(Ti.typeSet()) {
// TODO(gri) report which type is missing
if cause != nil {
*cause = check.sprintf("%s does not %s %s", V, verb, T)
}
return false
}
return checkComparability()
}
// Otherwise, V's type must be included in the iface type set.
var alt Type
if Ti.typeSet().is(func(t *term) bool {
if !t.includes(V) {
// If V ∉ t.typ but V ∈ ~t.typ then remember this type
// so we can suggest it as an alternative in the error
// message.
if alt == nil && !t.tilde && Identical(t.typ, under(t.typ)) {
tt := *t
tt.tilde = true
if tt.includes(V) {
alt = t.typ
}
}
return true
}
return false
}) {
if cause != nil {
var detail string
switch {
case alt != nil:
detail = check.sprintf("possibly missing ~ for %s in %s", alt, T)
case mentions(Ti, V):
detail = check.sprintf("%s mentions %s, but %s is not in the type set of %s", T, V, V, T)
default:
detail = check.sprintf("%s missing in %s", V, Ti.typeSet().terms)
}
*cause = check.sprintf("%s does not %s %s (%s)", V, verb, T, detail)
}
return false
}
return checkComparability()
}
// mentions reports whether type T "mentions" typ in an (embedded) element or term
// of T (whether typ is in the type set of T or not). For better error messages.
func mentions(T, typ Type) bool {
switch T := T.(type) {
case *Interface:
for _, e := range T.embeddeds {
if mentions(e, typ) {
return true
}
}
case *Union:
for _, t := range T.terms {
if mentions(t.typ, typ) {
return true
}
}
default:
if Identical(T, typ) {
return true
}
}
return false
}