Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 2052d8c

Browse filesBrowse files
committed
Handle untyped constants with type params.
Untyped constants present a challenge that the same literal value (e.g. "0xFFFF") has different runtime representations for different types. With type params we don't know the exact type at run time, so we can't pick the right representation at compile time. Instead, we do this in two steps: 1. Pick a basic type that can precisely represent the constant and initialize the value for that type. 2. Generate code to perform runtime type conversion from the known type to the type parameter's concrete type. Untyped nil is a special, simpler case: since for all types that untyped nil can be converted to also happened to have nil as zero value, whenever we detect such conversion we call for type's zero value, which would give us the appropriate nil representation.
1 parent e60629c commit 2052d8c
Copy full SHA for 2052d8c

File tree

6 files changed

+243
-10
lines changed
Filter options

6 files changed

+243
-10
lines changed

‎compiler/astutil/astutil.go

Copy file name to clipboardExpand all lines: compiler/astutil/astutil.go
+70Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package astutil
33
import (
44
"fmt"
55
"go/ast"
6+
"go/constant"
67
"go/token"
78
"go/types"
89
"strings"
@@ -25,6 +26,12 @@ func SetType(info *types.Info, t types.Type, e ast.Expr) ast.Expr {
2526
return e
2627
}
2728

29+
// SetTypeAndValue of the expression e to type t.
30+
func SetTypeAndValue(info *types.Info, t types.Type, val constant.Value, e ast.Expr) ast.Expr {
31+
info.Types[e] = types.TypeAndValue{Type: t, Value: val}
32+
return e
33+
}
34+
2835
// NewVarIdent creates a new variable object with the given name and type.
2936
func NewVarIdent(name string, t types.Type, info *types.Info, pkg *types.Package) *ast.Ident {
3037
obj := types.NewVar(token.NoPos, pkg, name, t)
@@ -226,3 +233,66 @@ func TakeAddress(info *types.Info, e ast.Expr) *ast.UnaryExpr {
226233
SetType(info, ptrType, addrOf)
227234
return addrOf
228235
}
236+
237+
// MakeTypedConstant takes an untyped constant value and makes an AST expression
238+
// representing it with a concrete type that can represent the constant precisely.
239+
func MakeTypedConstant(info *types.Info, val constant.Value) ast.Expr {
240+
switch val.Kind() {
241+
case constant.String:
242+
e := &ast.BasicLit{
243+
Kind: token.STRING,
244+
Value: val.ExactString(),
245+
}
246+
247+
return SetTypeAndValue(info, types.Typ[types.String], val, e)
248+
case constant.Float:
249+
e := &ast.BasicLit{
250+
Kind: token.FLOAT,
251+
Value: val.ExactString(),
252+
}
253+
254+
return SetTypeAndValue(info, types.Typ[types.Float64], val, e)
255+
case constant.Int:
256+
bits := constant.BitLen(val)
257+
sign := constant.Sign(val)
258+
259+
var t types.Type
260+
if bits <= 32 && sign >= 0 {
261+
t = types.Typ[types.Uint32]
262+
} else if bits <= 32 && sign < 0 {
263+
t = types.Typ[types.Int32]
264+
} else if sign >= 0 {
265+
t = types.Typ[types.Uint64]
266+
} else {
267+
t = types.Typ[types.Int64]
268+
}
269+
270+
e := &ast.BasicLit{
271+
Kind: token.INT,
272+
Value: val.ExactString(),
273+
}
274+
return SetTypeAndValue(info, t, val, e)
275+
case constant.Complex:
276+
e := &ast.BasicLit{
277+
Kind: token.IMAG,
278+
// Cheat: don't bother with generating a plausible complex expression.
279+
// We would have to construct a complicated expression to construct a
280+
// complex value. However, when dealing with constants, GopherJS doesn't
281+
// actually inspect the AST, only the types.TypeAndValue object it gets
282+
// from type analyzer.
283+
//
284+
// All we really need here is an ast.Expr we can associate with a
285+
// types.TypeAndValue instance, so we just do a token effort to return
286+
// something.
287+
Value: "",
288+
}
289+
return SetTypeAndValue(info, types.Typ[types.Complex128], val, e)
290+
case constant.Bool:
291+
e := &ast.Ident{
292+
Name: val.ExactString(),
293+
}
294+
return SetTypeAndValue(info, types.Typ[types.Bool], val, e)
295+
default:
296+
panic(fmt.Errorf("unexpected constant kind %s: %v", val.Kind(), val))
297+
}
298+
}

‎compiler/astutil/astutil_test.go

Copy file name to clipboardExpand all lines: compiler/astutil/astutil_test.go
+41Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
package astutil
22

33
import (
4+
"go/ast"
5+
"go/constant"
46
"go/token"
7+
"go/types"
58
"testing"
69

710
"github.com/gopherjs/gopherjs/internal/srctesting"
@@ -181,3 +184,41 @@ func TestEndsWithReturn(t *testing.T) {
181184
})
182185
}
183186
}
187+
188+
func TestMakeTypedConstant(t *testing.T) {
189+
tests := []struct {
190+
value constant.Value
191+
want types.Type
192+
}{{
193+
value: constant.MakeString("abc"),
194+
want: types.Typ[types.String],
195+
}, {
196+
value: constant.MakeInt64(0xFFFFFFFF),
197+
want: types.Typ[types.Uint32],
198+
}, {
199+
value: constant.MakeInt64(-0x80000000),
200+
want: types.Typ[types.Int32],
201+
}, {
202+
value: constant.MakeUint64(0xFFFFFFFFFFFFFFFF),
203+
want: types.Typ[types.Uint64],
204+
}, {
205+
value: constant.MakeInt64(-0x8000000000000000),
206+
want: types.Typ[types.Int64],
207+
}}
208+
209+
for _, test := range tests {
210+
t.Run(test.value.ExactString(), func(t *testing.T) {
211+
info := &types.Info{Types: map[ast.Expr]types.TypeAndValue{}}
212+
e := MakeTypedConstant(info, test.value)
213+
tv := info.Types[e]
214+
215+
if tv.Type != test.want {
216+
t.Errorf("Got: constant %s assigned type %s. Want: %s", test.value, tv.Type, test.want)
217+
}
218+
219+
if tv.Value != test.value {
220+
t.Errorf("Got: associated constant value is %s. Want: %s (the same as original).", tv.Value, test.value)
221+
}
222+
})
223+
}
224+
}

‎compiler/expressions.go

Copy file name to clipboardExpand all lines: compiler/expressions.go
+17-1Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,19 @@ func (e *expression) StringWithParens() string {
3535
func (fc *funcContext) translateExpr(expr ast.Expr) *expression {
3636
exprType := fc.pkgCtx.TypeOf(expr)
3737
if value := fc.pkgCtx.Types[expr].Value; value != nil {
38+
if tParam, ok := exprType.(*types.TypeParam); ok {
39+
// If we are dealing with a type param, we don't know which concrete type
40+
// it will be instantiated with, so we don't know how to represent the
41+
// constant value ahead of time. Instead, generate a typed constant and
42+
// perform type conversion to the instantiated type at runtime.
43+
return fc.translateExpr(
44+
fc.typeCastExpr(
45+
astutil.MakeTypedConstant(fc.pkgCtx.Info.Info, value),
46+
fc.newIdentFor(tParam.Obj()),
47+
),
48+
)
49+
}
50+
3851
basic := exprType.Underlying().(*types.Basic)
3952
switch {
4053
case isBoolean(basic):
@@ -633,7 +646,7 @@ func (fc *funcContext) translateExpr(expr ast.Expr) *expression {
633646
switch t := exprType.Underlying().(type) {
634647
case *types.Basic:
635648
if t.Kind() != types.UnsafePointer {
636-
panic("unexpected basic type")
649+
panic(fmt.Errorf("unexpected basic type: %v", t))
637650
}
638651
return fc.formatExpr("0")
639652
case *types.Slice, *types.Pointer:
@@ -1178,6 +1191,9 @@ func (fc *funcContext) translateConversion(expr ast.Expr, desiredType types.Type
11781191
_, fromTypeParam := exprType.(*types.TypeParam)
11791192
_, toTypeParam := desiredType.(*types.TypeParam)
11801193
if fromTypeParam || toTypeParam {
1194+
if t, ok := exprType.Underlying().(*types.Basic); ok && t.Kind() == types.UntypedNil {
1195+
return fc.formatExpr("%s.zero()", fc.typeName(desiredType))
1196+
}
11811197
// Conversion from or to a type param can only be done at runtime, since the
11821198
// concrete type is not known to the compiler at compile time.
11831199
return fc.formatExpr("%s.convertFrom(%s.wrap(%e))", fc.typeName(desiredType), fc.typeName(exprType), expr)

‎compiler/prelude/types.js

Copy file name to clipboardExpand all lines: compiler/prelude/types.js
+10-1Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1133,7 +1133,16 @@ const $convertToComplex = (src, dstType) => {
11331133
return src;
11341134
}
11351135

1136-
return new dstType(src.$real, src.$imag);
1136+
switch (srcType.kind) {
1137+
case $kindComplex64:
1138+
case $kindComplex128:
1139+
return new dstType(src.$real, src.$imag);
1140+
default:
1141+
// Although normally Go doesn't allow conversion from int/float types
1142+
// to complex, it does allow conversion from untyped constants.
1143+
const real = (srcType.kind === $kindUint64 || srcType.kind === $kindInt64) ? $flatten64(src) : src.$val;
1144+
return new dstType(real, 0);
1145+
}
11371146
};
11381147

11391148
/**

‎tests/gorepo/run.go

Copy file name to clipboardExpand all lines: tests/gorepo/run.go
+3-8Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -155,22 +155,17 @@ var knownFails = map[string]failReason{
155155
// Failures related to the lack of generics support. Ideally, this section
156156
// should be emptied once https://github.com/gopherjs/gopherjs/issues/1013 is
157157
// fixed.
158-
"typeparam/absdiff2.go": {category: generics, desc: "missing support for basic literals with type params"},
159-
"typeparam/absdiff3.go": {category: generics, desc: "missing support for basic literals with type params"},
158+
"typeparam/absdiff2.go": {category: generics, desc: "missing support for unary minus operator"},
159+
"typeparam/absdiff3.go": {category: generics, desc: "missing support for unary minus operator"},
160160
"typeparam/boundmethod.go": {category: generics, desc: "missing support for method expressions with a type param"},
161-
"typeparam/dictionaryCapture.go": {category: generics, desc: "missing support for basic literals with type params"},
161+
"typeparam/dictionaryCapture.go": {category: generics, desc: "make() doesn't support generic slice types"},
162162
"typeparam/double.go": {category: generics, desc: "make() doesn't support generic slice types"},
163-
"typeparam/fact.go": {category: generics, desc: "missing support for basic literals with type params"},
164163
"typeparam/index2.go": {category: generics, desc: "missing index operator support for generic types"},
165-
"typeparam/issue47258.go": {category: generics, desc: "missing support for basic literals with type params"},
166164
"typeparam/issue47716.go": {category: generics, desc: "unsafe.Sizeof() doesn't work with generic types"},
167165
"typeparam/issue48453.go": {category: generics, desc: "make() doesn't support generic slice types"},
168166
"typeparam/issue49295.go": {category: generics, desc: "len() doesn't support generic pointer to array types"},
169-
"typeparam/issue50193.go": {category: generics, desc: "invalid print format for complex numbers"},
170167
"typeparam/issue51303.go": {category: generics, desc: "missing support for range over type parameter"},
171-
"typeparam/list.go": {category: generics, desc: "missing support for basic literals with type params"},
172168
"typeparam/nested.go": {category: generics, desc: "missing comparison operator support for generic types"},
173-
"typeparam/slices.go": {category: generics, desc: "missing support for basic literals with type params"},
174169
"typeparam/typeswitch2.go": {category: generics, desc: "complex types have different print() format"},
175170
"typeparam/typeswitch3.go": {category: generics, desc: "missing support for type switching on generic types"},
176171
"typeparam/typeswitch5.go": {category: generics, desc: "different print() format for floating point types"},

‎tests/typeparams/literals_test.go

Copy file name to clipboard
+102Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
package typeparams_test
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
7+
"golang.org/x/exp/constraints"
8+
)
9+
10+
func intLit[T constraints.Integer]() T {
11+
var i T = 1
12+
return i
13+
}
14+
15+
func runeLit[T rune]() T {
16+
var r T = 'a'
17+
return r
18+
}
19+
20+
func floatLit[T constraints.Float]() T {
21+
var f T = 1.1
22+
return f
23+
}
24+
25+
func complexLit[T constraints.Complex]() T {
26+
var c T = 1 + 2i
27+
return c
28+
}
29+
30+
func complexLit2[T constraints.Complex]() T {
31+
var c T = 1
32+
return c
33+
}
34+
35+
func strLit[T string]() T {
36+
var s T = "abc"
37+
return s
38+
}
39+
40+
func boolLit[T bool]() T {
41+
var b T = true
42+
return b
43+
}
44+
45+
func nilLit[T *int]() T {
46+
var p T = nil
47+
return p
48+
}
49+
50+
func TestLiterals(t *testing.T) {
51+
tests := []struct {
52+
got any
53+
want any
54+
}{{
55+
got: intLit[int32](),
56+
want: int32(1),
57+
}, {
58+
got: intLit[uint32](),
59+
want: uint32(1),
60+
}, {
61+
got: intLit[int64](),
62+
want: int64(1),
63+
}, {
64+
got: intLit[uint64](),
65+
want: uint64(1),
66+
}, {
67+
got: runeLit[rune](),
68+
want: 'a',
69+
}, {
70+
got: floatLit[float32](),
71+
want: float32(1.1),
72+
}, {
73+
got: floatLit[float64](),
74+
want: float64(1.1),
75+
}, {
76+
got: complexLit[complex64](),
77+
want: complex64(1 + 2i),
78+
}, {
79+
got: complexLit[complex128](),
80+
want: complex128(1 + 2i),
81+
}, {
82+
got: complexLit2[complex128](),
83+
want: complex128(1),
84+
}, {
85+
got: strLit[string](),
86+
want: "abc",
87+
}, {
88+
got: boolLit[bool](),
89+
want: true,
90+
}, {
91+
got: nilLit[*int](),
92+
want: (*int)(nil),
93+
}}
94+
95+
for _, test := range tests {
96+
t.Run(fmt.Sprintf("%T/%v", test.want, test.want), func(t *testing.T) {
97+
if test.got != test.want {
98+
t.Errorf("Got: %v. Want: %v.", test.got, test.want)
99+
}
100+
})
101+
}
102+
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.