Skip to content

Commit 5d414d1

Browse files
committed
cmd/compile/internal/types2: more detailed error messages for generic conversions
- slightly refactor convertibleTo and convertibleToImpl - provide ability to return a conversion failure cause - add detailed cause for generic conversions For #47150. Change-Id: Ie97d89be0234414ef4df22a6920e18acc944a102 Reviewed-on: https://go-review.googlesource.com/c/go/+/357249 Trust: Robert Griesemer <[email protected]> Reviewed-by: Robert Findley <[email protected]>
1 parent 76dd01f commit 5d414d1

File tree

4 files changed

+67
-42
lines changed

4 files changed

+67
-42
lines changed

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -438,7 +438,7 @@ func AssignableTo(V, T Type) bool {
438438
// ConvertibleTo reports whether a value of type V is convertible to a value of type T.
439439
func ConvertibleTo(V, T Type) bool {
440440
x := operand{mode: value, typ: V}
441-
return x.convertibleTo(nil, T) // check not needed for non-constant x
441+
return x.convertibleTo(nil, T, nil) // check not needed for non-constant x; if check == nil, cause can be nil
442442
}
443443

444444
// Implements reports whether type V implements interface T.

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

+54-30
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ func (check *Checker) conversion(x *operand, T Type) {
1717
constArg := x.mode == constant_
1818

1919
var ok bool
20+
var cause string
2021
switch {
2122
case constArg && isConstType(T):
2223
// constant conversion
@@ -31,17 +32,20 @@ func (check *Checker) conversion(x *operand, T Type) {
3132
x.val = constant.MakeString(string(codepoint))
3233
ok = true
3334
}
34-
case x.convertibleTo(check, T):
35+
case x.convertibleTo(check, T, &cause):
3536
// non-constant conversion
3637
x.mode = value
3738
ok = true
3839
}
3940

4041
if !ok {
41-
if x.mode != invalid {
42-
check.errorf(x, "cannot convert %s to %s", x, T)
43-
x.mode = invalid
42+
var err error_
43+
err.errorf(x, "cannot convert %s to %s", x, T)
44+
if cause != "" {
45+
err.errorf(nopos, cause)
4446
}
47+
check.report(&err)
48+
x.mode = invalid
4549
return
4650
}
4751

@@ -80,57 +84,74 @@ func (check *Checker) conversion(x *operand, T Type) {
8084
// is tricky because we'd have to run updateExprType on the argument first.
8185
// (Issue #21982.)
8286

83-
// convertibleTo reports whether T(x) is valid.
87+
// convertibleTo reports whether T(x) is valid. In the failure case, *cause
88+
// may be set to the cause for the failure.
8489
// The check parameter may be nil if convertibleTo is invoked through an
8590
// exported API call, i.e., when all methods have been type-checked.
86-
func (x *operand) convertibleTo(check *Checker, T Type) bool {
91+
func (x *operand) convertibleTo(check *Checker, T Type, cause *string) bool {
8792
// "x is assignable to T"
88-
if ok, _ := x.assignableTo(check, T, nil); ok {
93+
if ok, _ := x.assignableTo(check, T, cause); ok {
8994
return true
9095
}
9196

92-
// TODO(gri) consider passing under(x.typ), under(T) into convertibleToImpl (optimization)
9397
Vp, _ := under(x.typ).(*TypeParam)
9498
Tp, _ := under(T).(*TypeParam)
9599

100+
errorf := func(format string, args ...interface{}) {
101+
if check != nil && cause != nil {
102+
msg := check.sprintf(format, args...)
103+
if *cause != "" {
104+
msg += "\n\t" + *cause
105+
}
106+
*cause = msg
107+
}
108+
}
109+
96110
// generic cases
97111
// (generic operands cannot be constants, so we can ignore x.val)
98112
switch {
99113
case Vp != nil && Tp != nil:
100-
x := *x // don't modify outer x
101114
return Vp.is(func(V *term) bool {
102-
x.typ = V.typ
103115
return Tp.is(func(T *term) bool {
104-
return x.convertibleToImpl(check, T.typ)
116+
if !convertibleToImpl(check, V.typ, T.typ, cause) {
117+
errorf("cannot convert %s (in %s) to %s (in %s)", V.typ, Vp, T.typ, Tp)
118+
return false
119+
}
120+
return true
105121
})
106122
})
107123
case Vp != nil:
108-
x := *x // don't modify outer x
109124
return Vp.is(func(V *term) bool {
110-
x.typ = V.typ
111-
return x.convertibleToImpl(check, T)
125+
if !convertibleToImpl(check, V.typ, T, cause) {
126+
errorf("cannot convert %s (in %s) to %s", V.typ, Vp, T)
127+
return false
128+
}
129+
return true
112130
})
113131
case Tp != nil:
114132
return Tp.is(func(T *term) bool {
115-
return x.convertibleToImpl(check, T.typ)
133+
if !convertibleToImpl(check, x.typ, T.typ, cause) {
134+
errorf("cannot convert %s to %s (in %s)", x.typ, T.typ, Tp)
135+
return false
136+
}
137+
return true
116138
})
117139
}
118140

119141
// non-generic case
120-
return x.convertibleToImpl(check, T)
142+
return convertibleToImpl(check, x.typ, T, cause)
121143
}
122144

123145
// convertibleToImpl should only be called by convertibleTo
124-
func (x *operand) convertibleToImpl(check *Checker, T Type) bool {
125-
// "x's type and T have identical underlying types if tags are ignored"
126-
V := x.typ
146+
func convertibleToImpl(check *Checker, V, T Type, cause *string) bool {
147+
// "V and T have identical underlying types if tags are ignored"
127148
Vu := under(V)
128149
Tu := under(T)
129150
if IdenticalIgnoreTags(Vu, Tu) {
130151
return true
131152
}
132153

133-
// "x's type and T are unnamed pointer types and their pointer base types
154+
// "V and T are unnamed pointer types and their pointer base types
134155
// have identical underlying types if tags are ignored"
135156
if V, ok := V.(*Pointer); ok {
136157
if T, ok := T.(*Pointer); ok {
@@ -140,22 +161,22 @@ func (x *operand) convertibleToImpl(check *Checker, T Type) bool {
140161
}
141162
}
142163

143-
// "x's type and T are both integer or floating point types"
164+
// "V and T are both integer or floating point types"
144165
if isIntegerOrFloat(V) && isIntegerOrFloat(T) {
145166
return true
146167
}
147168

148-
// "x's type and T are both complex types"
169+
// "V and T are both complex types"
149170
if isComplex(V) && isComplex(T) {
150171
return true
151172
}
152173

153-
// "x is an integer or a slice of bytes or runes and T is a string type"
174+
// "V an integer or a slice of bytes or runes and T is a string type"
154175
if (isInteger(V) || isBytesOrRunes(Vu)) && isString(T) {
155176
return true
156177
}
157178

158-
// "x is a string and T is a slice of bytes or runes"
179+
// "V a string and T is a slice of bytes or runes"
159180
if isString(V) && isBytesOrRunes(Tu) {
160181
return true
161182
}
@@ -170,7 +191,7 @@ func (x *operand) convertibleToImpl(check *Checker, T Type) bool {
170191
return true
171192
}
172193

173-
// "x is a slice, T is a pointer-to-array type,
194+
// "V a slice, T is a pointer-to-array type,
174195
// and the slice and array types have identical element types."
175196
if s := asSlice(V); s != nil {
176197
if p := asPointer(T); p != nil {
@@ -180,12 +201,15 @@ func (x *operand) convertibleToImpl(check *Checker, T Type) bool {
180201
return true
181202
}
182203
// check != nil
183-
if check.conf.CompilerErrorMessages {
184-
check.error(x, "conversion of slices to array pointers only supported as of -lang=go1.17")
185-
} else {
186-
check.error(x, "conversion of slices to array pointers requires go1.17 or later")
204+
if cause != nil {
205+
if check.conf.CompilerErrorMessages {
206+
// compiler error message assumes a -lang flag
207+
*cause = "conversion of slices to array pointers only supported as of -lang=go1.17"
208+
} else {
209+
*cause = "conversion of slices to array pointers requires go1.17 or later"
210+
}
187211
}
188-
x.mode = invalid // avoid follow-up error
212+
return false
189213
}
190214
}
191215
}

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

+1
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,7 @@ func (x *operand) assignableTo(check *Checker, T Type, reason *string) (bool, er
296296
if Ti, ok := Tu.(*Interface); ok {
297297
if m, wrongType := check.missingMethod(V, Ti, true); m != nil /* Implements(V, Ti) */ {
298298
if reason != nil {
299+
// TODO(gri) the error messages here should follow the style in Checker.typeAssertion (factor!)
299300
if wrongType != nil {
300301
if Identical(m.typ, wrongType.typ) {
301302
*reason = fmt.Sprintf("missing method %s (%s has pointer receiver)", m.name, m.name)

src/cmd/compile/internal/types2/testdata/examples/conversions.go2

+11-11
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,15 @@ type Far struct{f float64 }
2121
func _[X Foo, T Bar](x X) T { return T(x) }
2222
func _[X Foo|Bar, T Bar](x X) T { return T(x) }
2323
func _[X Foo, T Foo|Bar](x X) T { return T(x) }
24-
func _[X Foo, T Far](x X) T { return T(x /* ERROR cannot convert */ ) }
24+
func _[X Foo, T Far](x X) T { return T(x /* ERROR cannot convert x \(variable of type X constrained by Foo\) to T\n\tcannot convert Foo \(in X\) to Far \(in T\) */ ) }
2525

2626
// "x's type and T are unnamed pointer types and their pointer base types
2727
// have identical underlying types if tags are ignored"
2828

2929
func _[X ~*Foo, T ~*Bar](x X) T { return T(x) }
3030
func _[X ~*Foo|~*Bar, T ~*Bar](x X) T { return T(x) }
3131
func _[X ~*Foo, T ~*Foo|~*Bar](x X) T { return T(x) }
32-
func _[X ~*Foo, T ~*Far](x X) T { return T(x /* ERROR cannot convert */ ) }
32+
func _[X ~*Foo, T ~*Far](x X) T { return T(x /* ERROR cannot convert x \(variable of type X constrained by ~\*Foo\) to T\n\tcannot convert \*Foo \(in X\) to \*Far \(in T\) */ ) }
3333

3434
// Verify that the defined types in constraints are considered for the rule above.
3535

@@ -60,12 +60,12 @@ func _[X Unsigned, T Float](x X) T { return T(x) }
6060
func _[X Float, T Float](x X) T { return T(x) }
6161

6262
func _[X, T Integer|Unsigned|Float](x X) T { return T(x) }
63-
func _[X, T Integer|~string](x X) T { return T(x /* ERROR cannot convert */ ) }
63+
func _[X, T Integer|~string](x X) T { return T(x /* ERROR cannot convert x \(variable of type X constrained by Integer\|~string\) to T\n\tcannot convert string \(in X\) to int \(in T\) */ ) }
6464

6565
// "x's type and T are both complex types"
6666

6767
func _[X, T Complex](x X) T { return T(x) }
68-
func _[X, T Float|Complex](x X) T { return T(x /* ERROR cannot convert */ ) }
68+
func _[X, T Float|Complex](x X) T { return T(x /* ERROR cannot convert x \(variable of type X constrained by Float\|Complex\) to T\n\tcannot convert float32 \(in X\) to complex64 \(in T\) */ ) }
6969

7070
// "x is an integer or a slice of bytes or runes and T is a string type"
7171

@@ -76,25 +76,25 @@ func _[T ~string](x int) T { return T(x) }
7676
func _[T ~string](x myInt) T { return T(x) }
7777
func _[X Integer](x X) string { return string(x) }
7878
func _[X Integer](x X) myString { return myString(x) }
79-
func _[X Integer](x X) *string { return (*string)(x /* ERROR cannot convert */ ) }
79+
func _[X Integer](x X) *string { return (*string)(x /* ERROR cannot convert x \(variable of type X constrained by Integer\) to \*string\n\tcannot convert int \(in X\) to \*string */ ) }
8080

8181
func _[T ~string](x []byte) T { return T(x) }
8282
func _[T ~string](x []rune) T { return T(x) }
8383
func _[X ~[]byte, T ~string](x X) T { return T(x) }
8484
func _[X ~[]rune, T ~string](x X) T { return T(x) }
8585
func _[X Integer|~[]byte|~[]rune, T ~string](x X) T { return T(x) }
86-
func _[X Integer|~[]byte|~[]rune, T ~*string](x X) T { return T(x /* ERROR cannot convert */ ) }
86+
func _[X Integer|~[]byte|~[]rune, T ~*string](x X) T { return T(x /* ERROR cannot convert x \(variable of type X constrained by Integer\|~\[\]byte\|~\[\]rune\) to T\n\tcannot convert int \(in X\) to \*string \(in T\) */ ) }
8787

8888
// "x is a string and T is a slice of bytes or runes"
8989

9090
func _[T ~[]byte](x string) T { return T(x) }
9191
func _[T ~[]rune](x string) T { return T(x) }
92-
func _[T ~[]rune](x *string) T { return T(x /* ERROR cannot convert */ ) }
92+
func _[T ~[]rune](x *string) T { return T(x /* ERROR cannot convert x \(variable of type \*string\) to T\n\tcannot convert \*string to \[\]rune \(in T\) */ ) }
9393

9494
func _[X ~string, T ~[]byte](x X) T { return T(x) }
9595
func _[X ~string, T ~[]rune](x X) T { return T(x) }
9696
func _[X ~string, T ~[]byte|~[]rune](x X) T { return T(x) }
97-
func _[X ~*string, T ~[]byte|~[]rune](x X) T { return T(x /* ERROR cannot convert */ ) }
97+
func _[X ~*string, T ~[]byte|~[]rune](x X) T { return T(x /* ERROR cannot convert x \(variable of type X constrained by ~\*string\) to T\n\tcannot convert \*string \(in X\) to \[\]byte \(in T\) */ ) }
9898

9999
// package unsafe:
100100
// "any pointer or value of underlying type uintptr can be converted into a unsafe.Pointer"
@@ -103,20 +103,20 @@ type myUintptr uintptr
103103

104104
func _[X ~uintptr](x X) unsafe.Pointer { return unsafe.Pointer(x) }
105105
func _[T unsafe.Pointer](x myUintptr) T { return T(x) }
106-
func _[T unsafe.Pointer](x int64) T { return T(x /* ERROR cannot convert */ ) }
106+
func _[T unsafe.Pointer](x int64) T { return T(x /* ERROR cannot convert x \(variable of type int64\) to T\n\tcannot convert int64 to unsafe\.Pointer \(in T\) */ ) }
107107

108108
// "and vice versa"
109109

110110
func _[T ~uintptr](x unsafe.Pointer) T { return T(x) }
111111
func _[X unsafe.Pointer](x X) uintptr { return uintptr(x) }
112112
func _[X unsafe.Pointer](x X) myUintptr { return myUintptr(x) }
113-
func _[X unsafe.Pointer](x X) int64 { return int64(x /* ERROR cannot convert */ ) }
113+
func _[X unsafe.Pointer](x X) int64 { return int64(x /* ERROR cannot convert x \(variable of type X constrained by unsafe\.Pointer\) to int64\n\tcannot convert unsafe\.Pointer \(in X\) to int64 */ ) }
114114

115115
// "x is a slice, T is a pointer-to-array type,
116116
// and the slice and array types have identical element types."
117117

118118
func _[X ~[]E, T ~*[10]E, E any](x X) T { return T(x) }
119-
func _[X ~[]E, T ~[10]E, E any](x X) T { return T(x /* ERROR cannot convert */ ) }
119+
func _[X ~[]E, T ~[10]E, E any](x X) T { return T(x /* ERROR cannot convert x \(variable of type X constrained by ~\[\]E\) to T\n\tcannot convert \[\]E \(in X\) to \[10\]E \(in T\) */ ) }
120120

121121
// ----------------------------------------------------------------------------
122122
// The following declarations can be replaced by the exported types of the

0 commit comments

Comments
 (0)