Skip to content

Commit de0f4d1

Browse files
committed
go/types, types2: don't build unnecessary error strings in implements
When accessing (*Checker).implements from types.AssignableTo or types.ComparableTo, we don't need to build error strings -- they won't be used. This string manipulation showed up as a hot spot in gopls completion, which checks a lot of type predicates when searching for candidate completions. This CL yields the following results for gopls' completion benchmarks: StructCompletion-8 24.7ms ±34% 26.0ms ±17% ~ (p=0.447 n=10+9) ImportCompletion-8 1.41ms ± 2% 1.45ms ± 4% +2.42% (p=0.027 n=8+9) SliceCompletion-8 27.0ms ±18% 25.2ms ± 3% -6.67% (p=0.008 n=9+8) FuncDeepCompletion-8 57.6ms ± 4% 22.4ms ± 4% -61.18% (p=0.000 n=8+9) CompletionFollowingEdit-8 157ms ±13% 103ms ±15% -34.70% (p=0.000 n=10+10) Notably, deep completion (which searches many candidates) is almost 3x faster after this change. Fixes #54172 Change-Id: If8303a411aed3a20bd91f7b61e346d703084166c Reviewed-on: https://go-review.googlesource.com/c/go/+/423360 Run-TryBot: Robert Findley <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Reviewed-by: Robert Griesemer <[email protected]>
1 parent 7b45edb commit de0f4d1

File tree

8 files changed

+112
-80
lines changed

8 files changed

+112
-80
lines changed

src/cmd/compile/internal/types2/api.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -429,7 +429,7 @@ func AssertableTo(V *Interface, T Type) bool {
429429
if T.Underlying() == Typ[Invalid] {
430430
return false
431431
}
432-
return (*Checker)(nil).newAssertableTo(V, T) == nil
432+
return (*Checker)(nil).newAssertableTo(V, T)
433433
}
434434

435435
// AssignableTo reports whether a value of type V is assignable to a variable
@@ -467,7 +467,7 @@ func Implements(V Type, T *Interface) bool {
467467
if V.Underlying() == Typ[Invalid] {
468468
return false
469469
}
470-
return (*Checker)(nil).implements(V, T) == nil
470+
return (*Checker)(nil).implements(V, T, nil)
471471
}
472472

473473
// Identical reports whether x and y are identical types.

src/cmd/compile/internal/types2/instantiate.go

Lines changed: 49 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -174,28 +174,27 @@ func (check *Checker) verify(pos syntax.Pos, tparams []*TypeParam, targs []Type,
174174
// need to instantiate it with the type arguments with which we instantiated
175175
// the parameterized type.
176176
bound := check.subst(pos, tpar.bound, smap, nil, ctxt)
177-
if err := check.implements(targs[i], bound); err != nil {
178-
return i, err
177+
var reason string
178+
if !check.implements(targs[i], bound, &reason) {
179+
return i, errors.New(reason)
179180
}
180181
}
181182
return -1, nil
182183
}
183184

184-
// implements checks if V implements T and reports an error if it doesn't.
185-
// The receiver may be nil if implements is called through an exported
186-
// API call such as AssignableTo.
187-
func (check *Checker) implements(V, T Type) error {
185+
// implements checks if V implements T. The receiver may be nil if implements
186+
// is called through an exported API call such as AssignableTo.
187+
//
188+
// If the provided reason is non-nil, it may be set to an error string
189+
// explaining why V does not implement T.
190+
func (check *Checker) implements(V, T Type, reason *string) bool {
188191
Vu := under(V)
189192
Tu := under(T)
190193
if Vu == Typ[Invalid] || Tu == Typ[Invalid] {
191-
return nil // avoid follow-on errors
194+
return true // avoid follow-on errors
192195
}
193196
if p, _ := Vu.(*Pointer); p != nil && under(p.base) == Typ[Invalid] {
194-
return nil // avoid follow-on errors (see issue #49541 for an example)
195-
}
196-
197-
errorf := func(format string, args ...interface{}) error {
198-
return errors.New(check.sprintf(format, args...))
197+
return true // avoid follow-on errors (see issue #49541 for an example)
199198
}
200199

201200
Ti, _ := Tu.(*Interface)
@@ -206,44 +205,58 @@ func (check *Checker) implements(V, T Type) error {
206205
} else {
207206
cause = check.sprintf("%s is not an interface", T)
208207
}
209-
return errorf("%s does not implement %s (%s)", V, T, cause)
208+
if reason != nil {
209+
*reason = check.sprintf("%s does not implement %s (%s)", V, T, cause)
210+
}
211+
return false
210212
}
211213

212214
// Every type satisfies the empty interface.
213215
if Ti.Empty() {
214-
return nil
216+
return true
215217
}
216218
// T is not the empty interface (i.e., the type set of T is restricted)
217219

218220
// An interface V with an empty type set satisfies any interface.
219221
// (The empty set is a subset of any set.)
220222
Vi, _ := Vu.(*Interface)
221223
if Vi != nil && Vi.typeSet().IsEmpty() {
222-
return nil
224+
return true
223225
}
224226
// type set of V is not empty
225227

226228
// No type with non-empty type set satisfies the empty type set.
227229
if Ti.typeSet().IsEmpty() {
228-
return errorf("cannot implement %s (empty type set)", T)
230+
if reason != nil {
231+
*reason = check.sprintf("cannot implement %s (empty type set)", T)
232+
}
233+
return false
229234
}
230235

231236
// V must implement T's methods, if any.
232237
if m, wrong := check.missingMethod(V, Ti, true); m != nil /* !Implements(V, Ti) */ {
233-
return errorf("%s does not implement %s %s", V, T, check.missingMethodReason(V, T, m, wrong))
238+
if reason != nil {
239+
*reason = check.sprintf("%s does not implement %s %s", V, T, check.missingMethodReason(V, T, m, wrong))
240+
}
241+
return false
234242
}
235243

236-
// If T is comparable, V must be comparable.
237-
// Remember as a pending error and report only if we don't have a more specific error.
238-
var pending error
239-
if Ti.IsComparable() && !comparable(V, false, nil, nil) {
240-
pending = errorf("%s does not implement comparable", V)
244+
// Only check comparability if we don't have a more specific error.
245+
checkComparability := func() bool {
246+
// If T is comparable, V must be comparable.
247+
if Ti.IsComparable() && !comparable(V, false, nil, nil) {
248+
if reason != nil {
249+
*reason = check.sprintf("%s does not implement comparable", V)
250+
}
251+
return false
252+
}
253+
return true
241254
}
242255

243256
// V must also be in the set of types of T, if any.
244257
// Constraints with empty type sets were already excluded above.
245258
if !Ti.typeSet().hasTerms() {
246-
return pending // nothing to do
259+
return checkComparability() // nothing to do
247260
}
248261

249262
// If V is itself an interface, each of its possible types must be in the set
@@ -252,9 +265,12 @@ func (check *Checker) implements(V, T Type) error {
252265
if Vi != nil {
253266
if !Vi.typeSet().subsetOf(Ti.typeSet()) {
254267
// TODO(gri) report which type is missing
255-
return errorf("%s does not implement %s", V, T)
268+
if reason != nil {
269+
*reason = check.sprintf("%s does not implement %s", V, T)
270+
}
271+
return false
256272
}
257-
return pending
273+
return checkComparability()
258274
}
259275

260276
// Otherwise, V's type must be included in the iface type set.
@@ -275,12 +291,15 @@ func (check *Checker) implements(V, T Type) error {
275291
}
276292
return false
277293
}) {
278-
if alt != nil {
279-
return errorf("%s does not implement %s (possibly missing ~ for %s in constraint %s)", V, T, alt, T)
280-
} else {
281-
return errorf("%s does not implement %s (%s missing in %s)", V, T, V, Ti.typeSet().terms)
294+
if reason != nil {
295+
if alt != nil {
296+
*reason = check.sprintf("%s does not implement %s (possibly missing ~ for %s in constraint %s)", V, T, alt, T)
297+
} else {
298+
*reason = check.sprintf("%s does not implement %s (%s missing in %s)", V, T, V, Ti.typeSet().terms)
299+
}
282300
}
301+
return false
283302
}
284303

285-
return pending
304+
return checkComparability()
286305
}

src/cmd/compile/internal/types2/lookup.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -449,14 +449,14 @@ func (check *Checker) assertableTo(V *Interface, T Type) (method, wrongType *Fun
449449
// newAssertableTo reports whether a value of type V can be asserted to have type T.
450450
// It also implements behavior for interfaces that currently are only permitted
451451
// in constraint position (we have not yet defined that behavior in the spec).
452-
func (check *Checker) newAssertableTo(V *Interface, T Type) error {
452+
func (check *Checker) newAssertableTo(V *Interface, T Type) bool {
453453
// no static check is required if T is an interface
454454
// spec: "If T is an interface type, x.(T) asserts that the
455455
// dynamic type of x implements the interface T."
456456
if IsInterface(T) {
457-
return nil
457+
return true
458458
}
459-
return check.implements(T, V)
459+
return check.implements(T, V, nil)
460460
}
461461

462462
// deref dereferences typ if it is a *Pointer and returns its base and true.

src/cmd/compile/internal/types2/operand.go

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -288,18 +288,15 @@ func (x *operand) assignableTo(check *Checker, T Type, reason *string) (bool, er
288288
// T is an interface type and x implements T and T is not a type parameter.
289289
// Also handle the case where T is a pointer to an interface.
290290
if _, ok := Tu.(*Interface); ok && Tp == nil || isInterfacePtr(Tu) {
291-
if err := check.implements(V, T); err != nil {
292-
if reason != nil {
293-
*reason = err.Error()
294-
}
291+
if !check.implements(V, T, reason) {
295292
return false, _InvalidIfaceAssign
296293
}
297294
return true, 0
298295
}
299296

300297
// If V is an interface, check if a missing type assertion is the problem.
301298
if Vi, _ := Vu.(*Interface); Vi != nil && Vp == nil {
302-
if check.implements(T, V) == nil {
299+
if check.implements(T, V, nil) {
303300
// T implements V, so give hint about type assertion.
304301
if reason != nil {
305302
*reason = "need type assertion"

src/go/types/api.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -424,7 +424,7 @@ func AssertableTo(V *Interface, T Type) bool {
424424
if T.Underlying() == Typ[Invalid] {
425425
return false
426426
}
427-
return (*Checker)(nil).newAssertableTo(V, T) == nil
427+
return (*Checker)(nil).newAssertableTo(V, T)
428428
}
429429

430430
// AssignableTo reports whether a value of type V is assignable to a variable
@@ -462,7 +462,7 @@ func Implements(V Type, T *Interface) bool {
462462
if V.Underlying() == Typ[Invalid] {
463463
return false
464464
}
465-
return (*Checker)(nil).implements(V, T) == nil
465+
return (*Checker)(nil).implements(V, T, nil)
466466
}
467467

468468
// Identical reports whether x and y are identical types.

src/go/types/instantiate.go

Lines changed: 49 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -174,28 +174,27 @@ func (check *Checker) verify(pos token.Pos, tparams []*TypeParam, targs []Type,
174174
// need to instantiate it with the type arguments with which we instantiated
175175
// the parameterized type.
176176
bound := check.subst(pos, tpar.bound, smap, nil, ctxt)
177-
if err := check.implements(targs[i], bound); err != nil {
178-
return i, err
177+
var reason string
178+
if !check.implements(targs[i], bound, &reason) {
179+
return i, errors.New(reason)
179180
}
180181
}
181182
return -1, nil
182183
}
183184

184-
// implements checks if V implements T and reports an error if it doesn't.
185-
// The receiver may be nil if implements is called through an exported
186-
// API call such as AssignableTo.
187-
func (check *Checker) implements(V, T Type) error {
185+
// implements checks if V implements T. The receiver may be nil if implements
186+
// is called through an exported API call such as AssignableTo.
187+
//
188+
// If the provided reason is non-nil, it may be set to an error string
189+
// explaining why V does not implement T.
190+
func (check *Checker) implements(V, T Type, reason *string) bool {
188191
Vu := under(V)
189192
Tu := under(T)
190193
if Vu == Typ[Invalid] || Tu == Typ[Invalid] {
191-
return nil // avoid follow-on errors
194+
return true // avoid follow-on errors
192195
}
193196
if p, _ := Vu.(*Pointer); p != nil && under(p.base) == Typ[Invalid] {
194-
return nil // avoid follow-on errors (see issue #49541 for an example)
195-
}
196-
197-
errorf := func(format string, args ...any) error {
198-
return errors.New(check.sprintf(format, args...))
197+
return true // avoid follow-on errors (see issue #49541 for an example)
199198
}
200199

201200
Ti, _ := Tu.(*Interface)
@@ -206,44 +205,58 @@ func (check *Checker) implements(V, T Type) error {
206205
} else {
207206
cause = check.sprintf("%s is not an interface", T)
208207
}
209-
return errorf("%s does not implement %s (%s)", V, T, cause)
208+
if reason != nil {
209+
*reason = check.sprintf("%s does not implement %s (%s)", V, T, cause)
210+
}
211+
return false
210212
}
211213

212214
// Every type satisfies the empty interface.
213215
if Ti.Empty() {
214-
return nil
216+
return true
215217
}
216218
// T is not the empty interface (i.e., the type set of T is restricted)
217219

218220
// An interface V with an empty type set satisfies any interface.
219221
// (The empty set is a subset of any set.)
220222
Vi, _ := Vu.(*Interface)
221223
if Vi != nil && Vi.typeSet().IsEmpty() {
222-
return nil
224+
return true
223225
}
224226
// type set of V is not empty
225227

226228
// No type with non-empty type set satisfies the empty type set.
227229
if Ti.typeSet().IsEmpty() {
228-
return errorf("cannot implement %s (empty type set)", T)
230+
if reason != nil {
231+
*reason = check.sprintf("cannot implement %s (empty type set)", T)
232+
}
233+
return false
229234
}
230235

231236
// V must implement T's methods, if any.
232237
if m, wrong := check.missingMethod(V, Ti, true); m != nil /* !Implements(V, Ti) */ {
233-
return errorf("%s does not implement %s %s", V, T, check.missingMethodReason(V, T, m, wrong))
238+
if reason != nil {
239+
*reason = check.sprintf("%s does not implement %s %s", V, T, check.missingMethodReason(V, T, m, wrong))
240+
}
241+
return false
234242
}
235243

236-
// If T is comparable, V must be comparable.
237-
// Remember as a pending error and report only if we don't have a more specific error.
238-
var pending error
239-
if Ti.IsComparable() && !comparable(V, false, nil, nil) {
240-
pending = errorf("%s does not implement comparable", V)
244+
// Only check comparability if we don't have a more specific error.
245+
checkComparability := func() bool {
246+
// If T is comparable, V must be comparable.
247+
if Ti.IsComparable() && !comparable(V, false, nil, nil) {
248+
if reason != nil {
249+
*reason = check.sprintf("%s does not implement comparable", V)
250+
}
251+
return false
252+
}
253+
return true
241254
}
242255

243256
// V must also be in the set of types of T, if any.
244257
// Constraints with empty type sets were already excluded above.
245258
if !Ti.typeSet().hasTerms() {
246-
return pending // nothing to do
259+
return checkComparability() // nothing to do
247260
}
248261

249262
// If V is itself an interface, each of its possible types must be in the set
@@ -252,9 +265,12 @@ func (check *Checker) implements(V, T Type) error {
252265
if Vi != nil {
253266
if !Vi.typeSet().subsetOf(Ti.typeSet()) {
254267
// TODO(gri) report which type is missing
255-
return errorf("%s does not implement %s", V, T)
268+
if reason != nil {
269+
*reason = check.sprintf("%s does not implement %s", V, T)
270+
}
271+
return false
256272
}
257-
return pending
273+
return checkComparability()
258274
}
259275

260276
// Otherwise, V's type must be included in the iface type set.
@@ -275,12 +291,15 @@ func (check *Checker) implements(V, T Type) error {
275291
}
276292
return false
277293
}) {
278-
if alt != nil {
279-
return errorf("%s does not implement %s (possibly missing ~ for %s in constraint %s)", V, T, alt, T)
280-
} else {
281-
return errorf("%s does not implement %s (%s missing in %s)", V, T, V, Ti.typeSet().terms)
294+
if reason != nil {
295+
if alt != nil {
296+
*reason = check.sprintf("%s does not implement %s (possibly missing ~ for %s in constraint %s)", V, T, alt, T)
297+
} else {
298+
*reason = check.sprintf("%s does not implement %s (%s missing in %s)", V, T, V, Ti.typeSet().terms)
299+
}
282300
}
301+
return false
283302
}
284303

285-
return pending
304+
return checkComparability()
286305
}

src/go/types/lookup.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -448,14 +448,14 @@ func (check *Checker) assertableTo(V *Interface, T Type) (method, wrongType *Fun
448448
// newAssertableTo reports whether a value of type V can be asserted to have type T.
449449
// It also implements behavior for interfaces that currently are only permitted
450450
// in constraint position (we have not yet defined that behavior in the spec).
451-
func (check *Checker) newAssertableTo(V *Interface, T Type) error {
451+
func (check *Checker) newAssertableTo(V *Interface, T Type) bool {
452452
// no static check is required if T is an interface
453453
// spec: "If T is an interface type, x.(T) asserts that the
454454
// dynamic type of x implements the interface T."
455455
if IsInterface(T) {
456-
return nil
456+
return true
457457
}
458-
return check.implements(T, V)
458+
return check.implements(T, V, nil)
459459
}
460460

461461
// deref dereferences typ if it is a *Pointer and returns its base and true.

0 commit comments

Comments
 (0)