Skip to content

Commit 1990b96

Browse files
ldezmvertes
andauthored
update to go1.21 (#1598)
* feat: generate go1.21 files * chore: update CI * feat: add support for generic symbols in standard library packages This is necessary to fully support go1.21 and beyond, which now provide some generic packages such as `cmp`, `maps` or `slices` in the standard library. The principle is to embed the generic symbols in source form (as strings) so they can be instantiated as required during interpretation. Extract() has been modified to skip the generic types, functions and constraint interfaces which can't be represented as reflect.Values. A new stdlib/generic package has been added to provide the corresponding source files as embedded strings. The `Use()` function has been changed to pre-parse generic symbols as doing lazy parsing was causing cyclic dependencies issues at compiling. This is something we may improve in the future. A unit test using `cmp` has been added. For now, there are still some issues with generic stdlib packages inter-dependencies, for example `slices` importing `cmp`, or when generic types or function signatures depends on pre-compiled types in the same package, which we will support shortly. * fixup * fixup * fixup * fixup * fixup * fixup * fixes for go1.20 * fix previous * update unsafe2 for go1.21, skip faky tests In go1.21, the reflect rtype definition has been move to internal/abi. We follow this change for maintainability, even if there is no layout change (the go1.20 unsafe2 is compatible with go1.21). We have isolated a few problematic tests which are failing sometimes in go1.21, but work in go1.20, and also in go1.22. Those tests are skipped if in go1.21. A preliminary investigation can not confirm that something is wrong in yaegi, and the problem disappears with go1.22. * add new wrapper for go1.21 package testing/slogtest * add missing wrapper for go/doc/comment * add support for slices generic package --------- Co-authored-by: Marc Vertes <[email protected]>
1 parent da27c4f commit 1990b96

File tree

540 files changed

+7711
-4114
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

540 files changed

+7711
-4114
lines changed

.github/workflows/go-cross.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ jobs:
1717

1818
strategy:
1919
matrix:
20-
go-version: [ 1.19, '1.20' ]
20+
go-version: [ '1.20', '1.21' ]
2121
os: [ubuntu-latest, macos-latest, windows-latest]
2222

2323
include:

.github/workflows/main.yml

+4-4
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ on:
77
pull_request:
88

99
env:
10-
GO_VERSION: '1.20'
11-
GOLANGCI_LINT_VERSION: v1.53.3
10+
GO_VERSION: '1.21'
11+
GOLANGCI_LINT_VERSION: v1.55.2
1212

1313
jobs:
1414

@@ -45,7 +45,7 @@ jobs:
4545
needs: linting
4646
strategy:
4747
matrix:
48-
go-version: [ 1.19, '1.20' ]
48+
go-version: [ '1.20', '1.21' ]
4949
steps:
5050
- name: Set up Go ${{ matrix.go-version }}
5151
uses: actions/setup-go@v2
@@ -76,7 +76,7 @@ jobs:
7676
working-directory: ${{ github.workspace }}/go/src/github.com/traefik/yaegi
7777
strategy:
7878
matrix:
79-
go-version: [ 1.19, '1.20' ]
79+
go-version: [ '1.20', '1.21' ]
8080

8181
steps:
8282
- name: Set up Go ${{ matrix.go-version }}

.github/workflows/release.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ on:
66
- v[0-9]+.[0-9]+*
77

88
env:
9-
GO_VERSION: '1.20'
9+
GO_VERSION: '1.21'
1010

1111
jobs:
1212

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ It powers executable Go scripts and plugins, in embedded interpreters or interac
1717
* Works everywhere Go works
1818
* All Go & runtime resources accessible from script (with control)
1919
* Security: `unsafe` and `syscall` packages neither used nor exported by default
20-
* Support the latest 2 major releases of Go (Go 1.19 and Go 1.20)
20+
* Support the latest 2 major releases of Go (Go 1.20 and Go 1.21)
2121

2222
## Install
2323

extract/extract.go

+12-2
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@ import (
3939
"{{$key}}"
4040
{{- end}}
4141
{{- end}}
42+
{{- if or .Val .Typ }}
4243
"{{.ImportPath}}"
44+
{{- end}}
4345
"reflect"
4446
)
4547
@@ -199,6 +201,10 @@ func (e *Extractor) genContent(importPath string, p *types.Package) ([]byte, err
199201
val[name] = Val{pname, false}
200202
}
201203
case *types.Func:
204+
// Skip generic functions and methods.
205+
if s := o.Type().(*types.Signature); s.TypeParams().Len() > 0 || s.RecvTypeParams().Len() > 0 {
206+
continue
207+
}
202208
val[name] = Val{pname, false}
203209
case *types.Var:
204210
val[name] = Val{pname, true}
@@ -207,9 +213,13 @@ func (e *Extractor) genContent(importPath string, p *types.Package) ([]byte, err
207213
if t, ok := o.Type().(*types.Named); ok && t.TypeParams().Len() > 0 {
208214
continue
209215
}
210-
211216
typ[name] = pname
212217
if t, ok := o.Type().Underlying().(*types.Interface); ok {
218+
if t.NumMethods() == 0 && t.NumEmbeddeds() != 0 {
219+
// Skip interfaces used to implement constraints for generics.
220+
delete(typ, name)
221+
continue
222+
}
213223
var methods []Method
214224
for i := 0; i < t.NumMethods(); i++ {
215225
f := t.Method(i)
@@ -468,7 +478,7 @@ func GetMinor(part string) string {
468478
return minor
469479
}
470480

471-
const defaultMinorVersion = 20
481+
const defaultMinorVersion = 21
472482

473483
func genBuildTags() (string, error) {
474484
version := runtime.Version()

go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
module github.com/traefik/yaegi
22

3-
go 1.19
3+
go 1.20

internal/unsafe2/unsafe.go internal/unsafe2/go1_20_unsafe.go

+3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
//go:build !go1.21
2+
// +build !go1.21
3+
14
// Package unsafe2 provides helpers to generate recursive struct types.
25
package unsafe2
36

internal/unsafe2/go1_21_unsafe.go

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
//go:build go1.21
2+
// +build go1.21
3+
4+
// Package unsafe2 provides helpers to generate recursive struct types.
5+
package unsafe2
6+
7+
import (
8+
"reflect"
9+
"unsafe"
10+
)
11+
12+
type dummy struct{}
13+
14+
// DummyType represents a stand-in for a recursive type.
15+
var DummyType = reflect.TypeOf(dummy{})
16+
17+
// The following type sizes must match their original definition in Go src/internal/abi/type.go.
18+
type abiType struct {
19+
_ uintptr
20+
_ uintptr
21+
_ uint32
22+
_ uint8
23+
_ uint8
24+
_ uint8
25+
_ uint8
26+
_ uintptr
27+
_ uintptr
28+
_ int32
29+
_ int32
30+
}
31+
32+
type abiName struct {
33+
Bytes *byte
34+
}
35+
36+
type abiStructField struct {
37+
Name abiName
38+
Typ *abiType
39+
Offset uintptr
40+
}
41+
42+
type abiStructType struct {
43+
abiType
44+
PkgPath abiName
45+
Fields []abiStructField
46+
}
47+
48+
type emptyInterface struct {
49+
typ *abiType
50+
_ unsafe.Pointer
51+
}
52+
53+
// SetFieldType sets the type of the struct field at the given index, to the given type.
54+
//
55+
// The struct type must have been created at runtime. This is very unsafe.
56+
func SetFieldType(s reflect.Type, idx int, t reflect.Type) {
57+
if s.Kind() != reflect.Struct || idx >= s.NumField() {
58+
return
59+
}
60+
61+
rtyp := unpackType(s)
62+
styp := (*abiStructType)(unsafe.Pointer(rtyp))
63+
f := styp.Fields[idx]
64+
f.Typ = unpackType(t)
65+
styp.Fields[idx] = f
66+
}
67+
68+
func unpackType(t reflect.Type) *abiType {
69+
v := reflect.New(t).Elem().Interface()
70+
eface := *(*emptyInterface)(unsafe.Pointer(&v))
71+
return eface.typ
72+
}

interp/generic.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -306,12 +306,12 @@ func checkConstraint(it, ct *itype) error {
306306
return nil
307307
}
308308
for _, c := range ct.constraint {
309-
if it.equals(c) {
309+
if it.equals(c) || it.matchDefault(c) {
310310
return nil
311311
}
312312
}
313313
for _, c := range ct.ulconstraint {
314-
if it.underlying().equals(c) {
314+
if it.underlying().equals(c) || it.matchDefault(c) {
315315
return nil
316316
}
317317
}

interp/interp_consistent_test.go

+4
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,10 @@ func TestInterpConsistencyBuild(t *testing.T) {
121121
file.Name() == "type33.go" { // expect error
122122
continue
123123
}
124+
// Skip some tests which are problematic in go1.21 only.
125+
if go121 && testsToSkipGo121[file.Name()] {
126+
continue
127+
}
124128

125129
file := file
126130
t.Run(file.Name(), func(t *testing.T) {

interp/interp_eval_test.go

+4
Original file line numberDiff line numberDiff line change
@@ -1095,6 +1095,10 @@ func main() {
10951095
}
10961096

10971097
func TestImportPathIsKey(t *testing.T) {
1098+
// FIXME(marc): support of stdlib generic packages like "cmp", "maps", "slices" has changed
1099+
// the scope layout by introducing new source packages when stdlib is used.
1100+
// The logic of the following test doesn't apply anymore.
1101+
t.Skip("This test needs to be reworked.")
10981102
// No need to check the results of Eval, as TestFile already does it.
10991103
i := interp.New(interp.Options{GoPath: filepath.FromSlash("../_test/testdata/redeclaration-global7")})
11001104
if err := i.Use(stdlib.Symbols); err != nil {

interp/interp_file_test.go

+12
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"os"
99
"path/filepath"
1010
"regexp"
11+
"runtime"
1112
"strings"
1213
"testing"
1314

@@ -16,6 +17,12 @@ import (
1617
"github.com/traefik/yaegi/stdlib/unsafe"
1718
)
1819

20+
// The following tests sometimes (not always) crash with go1.21 but not with go1.20 or go1.22.
21+
// The reason of failure is not obvious, maybe due to the runtime itself, and will be investigated separately.
22+
var testsToSkipGo121 = map[string]bool{"cli6.go": true, "cli7.go": true, "issue-1276.go": true, "issue-1330.go": true, "struct11.go": true}
23+
24+
var go121 = strings.HasPrefix(runtime.Version(), "go1.21")
25+
1926
func TestFile(t *testing.T) {
2027
filePath := "../_test/str.go"
2128
runCheck(t, filePath)
@@ -27,10 +34,15 @@ func TestFile(t *testing.T) {
2734
if err != nil {
2835
t.Fatal(err)
2936
}
37+
3038
for _, file := range files {
3139
if filepath.Ext(file.Name()) != ".go" {
3240
continue
3341
}
42+
// Skip some tests which are problematic in go1.21 only.
43+
if go121 && testsToSkipGo121[file.Name()] {
44+
continue
45+
}
3446
file := file
3547
t.Run(file.Name(), func(t *testing.T) {
3648
runCheck(t, filepath.Join(baseDir, file.Name()))

interp/type.go

+15-8
Original file line numberDiff line numberDiff line change
@@ -989,16 +989,18 @@ func nodeType2(interp *Interpreter, sc *scope, n *node, seen []*node) (t *itype,
989989
rtype = rtype.Elem()
990990
}
991991
t = valueTOf(rtype, withNode(n), withScope(sc))
992-
} else {
993-
err = n.cfgErrorf("undefined selector %s.%s", lt.path, name)
992+
break
994993
}
994+
// Continue search in source package, as it may exist if package contains generics.
995+
fallthrough
995996
case srcPkgT:
996-
pkg := interp.srcPkg[lt.path]
997-
if s, ok := pkg[name]; ok {
998-
t = s.typ
999-
} else {
1000-
err = n.cfgErrorf("undefined selector %s.%s", lt.path, name)
997+
if pkg, ok := interp.srcPkg[lt.path]; ok {
998+
if s, ok := pkg[name]; ok {
999+
t = s.typ
1000+
break
1001+
}
10011002
}
1003+
err = n.cfgErrorf("undefined selector %s.%s", lt.path, name)
10021004
default:
10031005
if m, _ := lt.lookupMethod(name); m != nil {
10041006
t, err = nodeType2(interp, sc, m.child[2], seen)
@@ -1533,7 +1535,7 @@ func (t *itype) ordered() bool {
15331535
return isInt(typ) || isFloat(typ) || isString(typ)
15341536
}
15351537

1536-
// Equals returns true if the given type is identical to the receiver one.
1538+
// equals returns true if the given type is identical to the receiver one.
15371539
func (t *itype) equals(o *itype) bool {
15381540
switch ti, oi := isInterface(t), isInterface(o); {
15391541
case ti && oi:
@@ -1547,6 +1549,11 @@ func (t *itype) equals(o *itype) bool {
15471549
}
15481550
}
15491551

1552+
// matchDefault returns true if the receiver default type is the same as the given one.
1553+
func (t *itype) matchDefault(o *itype) bool {
1554+
return t.untyped && t.id() == "untyped "+o.id()
1555+
}
1556+
15501557
// MethodSet defines the set of methods signatures as strings, indexed per method name.
15511558
type methodSet map[string]string
15521559

interp/use.go

+9
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import (
99
"os"
1010
"path"
1111
"reflect"
12+
13+
gen "github.com/traefik/yaegi/stdlib/generic"
1214
)
1315

1416
// Symbols returns a map of interpreter exported symbol values for the given
@@ -139,6 +141,13 @@ func (interp *Interpreter) Use(values Exports) error {
139141
// well known stdlib package path.
140142
if _, ok := values["fmt/fmt"]; ok {
141143
fixStdlib(interp)
144+
145+
// Load stdlib generic source.
146+
for _, s := range gen.Sources {
147+
if _, err := interp.Compile(s); err != nil {
148+
return err
149+
}
150+
}
142151
}
143152
return nil
144153
}

stdlib/generic/go1_20_generic.go

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
//go:build go1.20 && !go1.21
2+
// +build go1.20,!go1.21
3+
4+
package generic
5+
6+
var Sources = []string{}

stdlib/generic/go1_21_cmp.go.txt

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Copyright 2023 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+
5+
// Package cmp provides types and functions related to comparing
6+
// ordered values.
7+
package cmp
8+
9+
// Ordered is a constraint that permits any ordered type: any type
10+
// that supports the operators < <= >= >.
11+
// If future releases of Go add new ordered types,
12+
// this constraint will be modified to include them.
13+
//
14+
// Note that floating-point types may contain NaN ("not-a-number") values.
15+
// An operator such as == or < will always report false when
16+
// comparing a NaN value with any other value, NaN or not.
17+
// See the [Compare] function for a consistent way to compare NaN values.
18+
type Ordered interface {
19+
~int | ~int8 | ~int16 | ~int32 | ~int64 |
20+
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr |
21+
~float32 | ~float64 |
22+
~string
23+
}
24+
25+
// Less reports whether x is less than y.
26+
// For floating-point types, a NaN is considered less than any non-NaN,
27+
// and -0.0 is not less than (is equal to) 0.0.
28+
func Less[T Ordered](x, y T) bool {
29+
return (isNaN(x) && !isNaN(y)) || x < y
30+
}
31+
32+
// Compare returns
33+
//
34+
// -1 if x is less than y,
35+
// 0 if x equals y,
36+
// +1 if x is greater than y.
37+
//
38+
// For floating-point types, a NaN is considered less than any non-NaN,
39+
// a NaN is considered equal to a NaN, and -0.0 is equal to 0.0.
40+
func Compare[T Ordered](x, y T) int {
41+
xNaN := isNaN(x)
42+
yNaN := isNaN(y)
43+
if xNaN && yNaN {
44+
return 0
45+
}
46+
if xNaN || x < y {
47+
return -1
48+
}
49+
if yNaN || x > y {
50+
return +1
51+
}
52+
return 0
53+
}
54+
55+
// isNaN reports whether x is a NaN without requiring the math package.
56+
// This will always return false if T is not floating-point.
57+
func isNaN[T Ordered](x T) bool {
58+
return x != x
59+
}

0 commit comments

Comments
 (0)