all: drop support for Go 1.18 through 1.24 (#469)
Set the go.mod language version to 1.25.0 and trim the CI matrix to the two most recent Go releases. Exercising every release back to 1.18 made test runs long, and that cost grows as more targets are added (e.g. a future windows/arm64). Narrowing the supported range also lowers the maintenance burden of carrying version-gated workarounds. Remove the compatibility shims that only existed for older toolchains: - internal/xreflect, which polyfilled reflect.TypeAssert; callers now use the standard library function directly. - the objc clone helper, replaced by slices.Clone. Modernize the remaining code to the new baseline: integer range loops and reflect.TypeFor, and an iter.Seq-based field iterator in the tests. Updates #468 Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
- commit
- 3c869562eaac4f1afa41ceaa6d00721d6527c244
- author
- Hajime Hoshi · · 2026-06-23 03:13 UTC
- committer
- GitHub · · 2026-06-23 03:13 UTC
- parent
- 576826c
- signature
- GPG Unverified
B5690EEEBB952194The key is unknown
15 files · +56 -117
M .github/workflows/test.yml +2 -7
| @@ -10,7 +10,7 @@ jobs: | |||
| 10 | 10 | strategy: | |
| 11 | 11 | matrix: | |
| 12 | 12 | os: [ubuntu-latest, ubuntu-24.04-arm, macos-latest, windows-latest] | |
| 13 | - | go: ['1.18.x', '1.19.x', '1.20.x', '1.21.x', '1.22.x', '1.23.x', '1.24.x', '1.25.x', '1.26.x'] | |
| 13 | + | go: ['1.25.x', '1.26.x'] | |
| 14 | 14 | name: Test with Go ${{ matrix.go }} on ${{ matrix.os }} | |
| 15 | 15 | runs-on: ${{ matrix.os }} | |
| 16 | 16 | defaults: | |
| @@ -174,21 +174,18 @@ jobs: | |||
| 174 | 174 | go env -u CXX | |
| 175 | 175 | ||
| 176 | 176 | - name: go test race (no Cgo) | |
| 177 | - | if: ${{ runner.os == 'macOS' && !startsWith(matrix.go, '1.18.') && !startsWith(matrix.go, '1.19.') }} | |
| 177 | + | if: runner.os == 'macOS' | |
| 178 | 178 | run: | | |
| 179 | 179 | # -race usually requires Cgo, but macOS is an exception. See https://go.dev/doc/articles/race_detector#Requirements | |
| 180 | 180 | env CGO_ENABLED=0 go test -race -shuffle=on -v -count=10 ./... | |
| 181 | 181 | ||
| 182 | 182 | - name: go test race (Cgo) | |
| 183 | - | if: ${{ !startsWith(matrix.go, '1.18.') && !startsWith(matrix.go, '1.19.') }} | |
| 184 | 183 | run: | | |
| 185 | 184 | env CGO_ENABLED=1 go test -race -shuffle=on -v -count=10 ./... | |
| 186 | 185 | ||
| 187 | 186 | minor-arches: | |
| 188 | 187 | strategy: | |
| 189 | 188 | matrix: | |
| 190 | - | # Test only the latest two Go versions to save CI time. | |
| 191 | - | # See https://go.dev/doc/devel/release#policy | |
| 192 | 189 | go: ['1.25.x', '1.26.x'] | |
| 193 | 190 | name: Test with Go ${{ matrix.go }} on Linux minor architectures | |
| 194 | 191 | runs-on: ubuntu-latest | |
| @@ -285,8 +282,6 @@ jobs: | |||
| 285 | 282 | strategy: | |
| 286 | 283 | matrix: | |
| 287 | 284 | os: ['FreeBSD', 'NetBSD'] | |
| 288 | - | # Test only the latest two Go versions to save CI time. | |
| 289 | - | # See https://go.dev/doc/devel/release#policy | |
| 290 | 285 | go: ['1.25.8', '1.26.1'] | |
| 291 | 286 | name: Test with Go ${{ matrix.go }} on ${{ matrix.os }} | |
| 292 | 287 | runs-on: ubuntu-22.04 | |
| @@ -10,7 +10,7 @@ jobs: | |||||
| 10 | strategy: | 10 | strategy: | ||
| 11 | matrix: | 11 | matrix: | ||
| 12 | os: [ubuntu-latest, ubuntu-24.04-arm, macos-latest, windows-latest] | 12 | os: [ubuntu-latest, ubuntu-24.04-arm, macos-latest, windows-latest] | ||
| 13 | - | go: ['1.18.x', '1.19.x', '1.20.x', '1.21.x', '1.22.x', '1.23.x', '1.24.x', '1.25.x', '1.26.x'] | 13 | + | go: ['1.25.x', '1.26.x'] |
| 14 | name: Test with Go ${{ matrix.go }} on ${{ matrix.os }} | 14 | name: Test with Go ${{ matrix.go }} on ${{ matrix.os }} | ||
| 15 | runs-on: ${{ matrix.os }} | 15 | runs-on: ${{ matrix.os }} | ||
| 16 | defaults: | 16 | defaults: | ||
| @@ -174,21 +174,18 @@ jobs: | |||||
| 174 | go env -u CXX | 174 | go env -u CXX | ||
| 175 | 175 | ||||
| 176 | - name: go test race (no Cgo) | 176 | - name: go test race (no Cgo) | ||
| 177 | - | if: ${{ runner.os == 'macOS' && !startsWith(matrix.go, '1.18.') && !startsWith(matrix.go, '1.19.') }} | 177 | + | if: runner.os == 'macOS' |
| 178 | run: | | 178 | run: | | ||
| 179 | # -race usually requires Cgo, but macOS is an exception. See https://go.dev/doc/articles/race_detector#Requirements | 179 | # -race usually requires Cgo, but macOS is an exception. See https://go.dev/doc/articles/race_detector#Requirements | ||
| 180 | env CGO_ENABLED=0 go test -race -shuffle=on -v -count=10 ./... | 180 | env CGO_ENABLED=0 go test -race -shuffle=on -v -count=10 ./... | ||
| 181 | 181 | ||||
| 182 | - name: go test race (Cgo) | 182 | - name: go test race (Cgo) | ||
| 183 | - | if: ${{ !startsWith(matrix.go, '1.18.') && !startsWith(matrix.go, '1.19.') }} | |||
| 184 | run: | | 183 | run: | | ||
| 185 | env CGO_ENABLED=1 go test -race -shuffle=on -v -count=10 ./... | 184 | env CGO_ENABLED=1 go test -race -shuffle=on -v -count=10 ./... | ||
| 186 | 185 | ||||
| 187 | minor-arches: | 186 | minor-arches: | ||
| 188 | strategy: | 187 | strategy: | ||
| 189 | matrix: | 188 | matrix: | ||
| 190 | - | # Test only the latest two Go versions to save CI time. | |||
| 191 | - | # See https://go.dev/doc/devel/release#policy | |||
| 192 | go: ['1.25.x', '1.26.x'] | 189 | go: ['1.25.x', '1.26.x'] | ||
| 193 | name: Test with Go ${{ matrix.go }} on Linux minor architectures | 190 | name: Test with Go ${{ matrix.go }} on Linux minor architectures | ||
| 194 | runs-on: ubuntu-latest | 191 | runs-on: ubuntu-latest | ||
| @@ -285,8 +282,6 @@ jobs: | |||||
| 285 | strategy: | 282 | strategy: | ||
| 286 | matrix: | 283 | matrix: | ||
| 287 | os: ['FreeBSD', 'NetBSD'] | 284 | os: ['FreeBSD', 'NetBSD'] | ||
| 288 | - | # Test only the latest two Go versions to save CI time. | |||
| 289 | - | # See https://go.dev/doc/devel/release#policy | |||
| 290 | go: ['1.25.8', '1.26.1'] | 285 | go: ['1.25.8', '1.26.1'] | ||
| 291 | name: Test with Go ${{ matrix.go }} on ${{ matrix.os }} | 286 | name: Test with Go ${{ matrix.go }} on ${{ matrix.os }} | ||
| 292 | runs-on: ubuntu-22.04 | 287 | runs-on: ubuntu-22.04 | ||
M callback_test.go +1 -1
| @@ -43,7 +43,7 @@ func TestCallGoFromSharedLib(t *testing.T) { | |||
| 43 | 43 | ||
| 44 | 44 | const want = 10101 | |
| 45 | 45 | cb := purego.NewCallback(goFunc) | |
| 46 | - | for i := 0; i < 10; i++ { | |
| 46 | + | for i := range 10 { | |
| 47 | 47 | got := callCallback(cb, "a test string") | |
| 48 | 48 | if got != want { | |
| 49 | 49 | t.Fatalf("%d: callCallback() got %v want %v", i, got, want) | |
| @@ -43,7 +43,7 @@ func TestCallGoFromSharedLib(t *testing.T) { | |||||
| 43 | 43 | ||||
| 44 | const want = 10101 | 44 | const want = 10101 | ||
| 45 | cb := purego.NewCallback(goFunc) | 45 | cb := purego.NewCallback(goFunc) | ||
| 46 | - | for i := 0; i < 10; i++ { | 46 | + | for i := range 10 { |
| 47 | got := callCallback(cb, "a test string") | 47 | got := callCallback(cb, "a test string") | ||
| 48 | if got != want { | 48 | if got != want { | ||
| 49 | t.Fatalf("%d: callCallback() got %v want %v", i, got, want) | 49 | t.Fatalf("%d: callCallback() got %v want %v", i, got, want) | ||
M examples/objc/main_darwin.go +1 -1
| @@ -36,7 +36,7 @@ func main() { | |||
| 36 | 36 | []objc.FieldDef{ | |
| 37 | 37 | { | |
| 38 | 38 | Name: "bar", | |
| 39 | - | Type: reflect.TypeOf(int(0)), | |
| 39 | + | Type: reflect.TypeFor[int](), | |
| 40 | 40 | Attribute: objc.ReadWrite, | |
| 41 | 41 | }, | |
| 42 | 42 | }, | |
| @@ -36,7 +36,7 @@ func main() { | |||||
| 36 | []objc.FieldDef{ | 36 | []objc.FieldDef{ | ||
| 37 | { | 37 | { | ||
| 38 | Name: "bar", | 38 | Name: "bar", | ||
| 39 | - | Type: reflect.TypeOf(int(0)), | 39 | + | Type: reflect.TypeFor[int](), |
| 40 | Attribute: objc.ReadWrite, | 40 | Attribute: objc.ReadWrite, | ||
| 41 | }, | 41 | }, | ||
| 42 | }, | 42 | }, | ||
M func.go +2 -3
| @@ -14,7 +14,6 @@ import ( | |||
| 14 | 14 | "unsafe" | |
| 15 | 15 | ||
| 16 | 16 | "github.com/ebitengine/purego/internal/strings" | |
| 17 | - | "github.com/ebitengine/purego/internal/xreflect" | |
| 18 | 17 | ) | |
| 19 | 18 | ||
| 20 | 19 | const ( | |
| @@ -155,7 +154,7 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||
| 155 | 154 | // created in NewCallback. | |
| 156 | 155 | for j := 0; j < arg.NumIn(); j++ { | |
| 157 | 156 | in := arg.In(j) | |
| 158 | - | if !in.AssignableTo(reflect.TypeOf(CDecl{})) { | |
| 157 | + | if !in.AssignableTo(reflect.TypeFor[CDecl]()) { | |
| 159 | 158 | continue | |
| 160 | 159 | } | |
| 161 | 160 | if j != 0 { | |
| @@ -303,7 +302,7 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||
| 303 | 302 | } | |
| 304 | 303 | } | |
| 305 | 304 | for i, v := range args { | |
| 306 | - | if variadic, ok := xreflect.TypeAssert[[]any](args[i]); ok { | |
| 305 | + | if variadic, ok := reflect.TypeAssert[[]any](args[i]); ok { | |
| 307 | 306 | if i != len(args)-1 { | |
| 308 | 307 | panic("purego: can only expand last parameter") | |
| 309 | 308 | } | |
| @@ -14,7 +14,6 @@ import ( | |||||
| 14 | "unsafe" | 14 | "unsafe" | ||
| 15 | 15 | ||||
| 16 | "github.com/ebitengine/purego/internal/strings" | 16 | "github.com/ebitengine/purego/internal/strings" | ||
| 17 | - | "github.com/ebitengine/purego/internal/xreflect" | |||
| 18 | ) | 17 | ) | ||
| 19 | 18 | ||||
| 20 | const ( | 19 | const ( | ||
| @@ -155,7 +154,7 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||||
| 155 | // created in NewCallback. | 154 | // created in NewCallback. | ||
| 156 | for j := 0; j < arg.NumIn(); j++ { | 155 | for j := 0; j < arg.NumIn(); j++ { | ||
| 157 | in := arg.In(j) | 156 | in := arg.In(j) | ||
| 158 | - | if !in.AssignableTo(reflect.TypeOf(CDecl{})) { | 157 | + | if !in.AssignableTo(reflect.TypeFor[CDecl]()) { |
| 159 | continue | 158 | continue | ||
| 160 | } | 159 | } | ||
| 161 | if j != 0 { | 160 | if j != 0 { | ||
| @@ -303,7 +302,7 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||||
| 303 | } | 302 | } | ||
| 304 | } | 303 | } | ||
| 305 | for i, v := range args { | 304 | for i, v := range args { | ||
| 306 | - | if variadic, ok := xreflect.TypeAssert[[]any](args[i]); ok { | 305 | + | if variadic, ok := reflect.TypeAssert[[]any](args[i]); ok { |
| 307 | if i != len(args)-1 { | 306 | if i != len(args)-1 { | ||
| 308 | panic("purego: can only expand last parameter") | 307 | panic("purego: can only expand last parameter") | ||
| 309 | } | 308 | } | ||
M func_test.go +1 -1
| @@ -60,7 +60,7 @@ func TestRegisterFunc_ConcurrentPointerReturn(t *testing.T) { | |||
| 60 | 60 | wg.Add(1) | |
| 61 | 61 | go func(id int) { | |
| 62 | 62 | defer wg.Done() | |
| 63 | - | for j := 0; j < 400_000; j++ { | |
| 63 | + | for range 400_000 { | |
| 64 | 64 | ptr := alloc(5) | |
| 65 | 65 | if ptr == nil { | |
| 66 | 66 | continue | |
| @@ -60,7 +60,7 @@ func TestRegisterFunc_ConcurrentPointerReturn(t *testing.T) { | |||||
| 60 | wg.Add(1) | 60 | wg.Add(1) | ||
| 61 | go func(id int) { | 61 | go func(id int) { | ||
| 62 | defer wg.Done() | 62 | defer wg.Done() | ||
| 63 | - | for j := 0; j < 400_000; j++ { | 63 | + | for range 400_000 { |
| 64 | ptr := alloc(5) | 64 | ptr := alloc(5) | ||
| 65 | if ptr == nil { | 65 | if ptr == nil { | ||
| 66 | continue | 66 | continue | ||
M go.mod +1 -1
| @@ -1,3 +1,3 @@ | |||
| 1 | 1 | module github.com/ebitengine/purego | |
| 2 | 2 | ||
| 3 | - | go 1.18 | |
| 3 | + | go 1.25.0 | |
| @@ -1,3 +1,3 @@ | |||||
| 1 | module github.com/ebitengine/purego | 1 | module github.com/ebitengine/purego | ||
| 2 | 2 | ||||
| 3 | - | go 1.18 | 3 | + | go 1.25.0 |
D internal/xreflect/reflect_go124.go +0 -15
| @@ -1,15 +0,0 @@ | |||
| 1 | - | // SPDX-License-Identifier: Apache-2.0 | |
| 2 | - | // SPDX-FileCopyrightText: 2025 The Ebitengine Authors | |
| 3 | - | ||
| 4 | - | //go:build !go1.25 | |
| 5 | - | ||
| 6 | - | package xreflect | |
| 7 | - | ||
| 8 | - | import "reflect" | |
| 9 | - | ||
| 10 | - | // TODO: remove this and use Go 1.25's reflect.TypeAssert when minimum go.mod version is 1.25 | |
| 11 | - | ||
| 12 | - | func TypeAssert[T any](v reflect.Value) (T, bool) { | |
| 13 | - | v2, ok := v.Interface().(T) | |
| 14 | - | return v2, ok | |
| 15 | - | } | |
| @@ -1,15 +0,0 @@ | |||||
| 1 | - | // SPDX-License-Identifier: Apache-2.0 | |||
| 2 | - | // SPDX-FileCopyrightText: 2025 The Ebitengine Authors | |||
| 3 | - | ||||
| 4 | - | //go:build !go1.25 | |||
| 5 | - | ||||
| 6 | - | package xreflect | |||
| 7 | - | ||||
| 8 | - | import "reflect" | |||
| 9 | - | ||||
| 10 | - | // TODO: remove this and use Go 1.25's reflect.TypeAssert when minimum go.mod version is 1.25 | |||
| 11 | - | ||||
| 12 | - | func TypeAssert[T any](v reflect.Value) (T, bool) { | |||
| 13 | - | v2, ok := v.Interface().(T) | |||
| 14 | - | return v2, ok | |||
| 15 | - | } | |||
D internal/xreflect/reflect_go125.go +0 -12
| @@ -1,12 +0,0 @@ | |||
| 1 | - | // SPDX-License-Identifier: Apache-2.0 | |
| 2 | - | // SPDX-FileCopyrightText: 2025 The Ebitengine Authors | |
| 3 | - | ||
| 4 | - | //go:build go1.25 | |
| 5 | - | ||
| 6 | - | package xreflect | |
| 7 | - | ||
| 8 | - | import "reflect" | |
| 9 | - | ||
| 10 | - | func TypeAssert[T any](v reflect.Value) (T, bool) { | |
| 11 | - | return reflect.TypeAssert[T](v) | |
| 12 | - | } | |
| @@ -1,12 +0,0 @@ | |||||
| 1 | - | // SPDX-License-Identifier: Apache-2.0 | |||
| 2 | - | // SPDX-FileCopyrightText: 2025 The Ebitengine Authors | |||
| 3 | - | ||||
| 4 | - | //go:build go1.25 | |||
| 5 | - | ||||
| 6 | - | package xreflect | |||
| 7 | - | ||||
| 8 | - | import "reflect" | |||
| 9 | - | ||||
| 10 | - | func TypeAssert[T any](v reflect.Value) (T, bool) { | |||
| 11 | - | return reflect.TypeAssert[T](v) | |||
| 12 | - | } | |||
M objc/objc_block_darwin.go +3 -4
| @@ -10,7 +10,6 @@ import ( | |||
| 10 | 10 | "unsafe" | |
| 11 | 11 | ||
| 12 | 12 | "github.com/ebitengine/purego" | |
| 13 | - | "github.com/ebitengine/purego/internal/xreflect" | |
| 14 | 13 | ) | |
| 15 | 14 | ||
| 16 | 15 | const ( | |
| @@ -125,7 +124,7 @@ func (*blockCache) encode(typ reflect.Type) *uint8 { | |||
| 125 | 124 | encoding = returnType | |
| 126 | 125 | } | |
| 127 | 126 | ||
| 128 | - | if typ.NumIn() == 0 || typ.In(0) != reflect.TypeOf(Block(0)) { | |
| 127 | + | if typ.NumIn() == 0 || typ.In(0) != reflect.TypeFor[Block]() { | |
| 129 | 128 | panic(fmt.Sprintf("objc: A Block implementation must take a Block as its first argument; got %v", typ.String())) | |
| 130 | 129 | } | |
| 131 | 130 | ||
| @@ -168,7 +167,7 @@ func (b *blockCache) getLayout(typ reflect.Type) blockLayout { | |||
| 168 | 167 | reflect.MakeFunc( | |
| 169 | 168 | typ, | |
| 170 | 169 | func(args []reflect.Value) (results []reflect.Value) { | |
| 171 | - | block, ok := xreflect.TypeAssert[Block](args[0]) | |
| 170 | + | block, ok := reflect.TypeAssert[Block](args[0]) | |
| 172 | 171 | if !ok { | |
| 173 | 172 | panic(fmt.Sprintf("objc: block argument is not a block but %s", args[0].Type().String())) | |
| 174 | 173 | } | |
| @@ -263,7 +262,7 @@ func InvokeBlock[T any](block Block, args ...any) (result T, err error) { | |||
| 263 | 262 | callResult := fn.Call(reflectedArgs) | |
| 264 | 263 | ||
| 265 | 264 | var ok bool | |
| 266 | - | result, ok = xreflect.TypeAssert[T](callResult[0]) | |
| 265 | + | result, ok = reflect.TypeAssert[T](callResult[0]) | |
| 267 | 266 | if !ok { | |
| 268 | 267 | return result, fmt.Errorf("objc: the returned value type %s was not %T", callResult[0].Type().String(), result) | |
| 269 | 268 | } | |
| @@ -10,7 +10,6 @@ import ( | |||||
| 10 | "unsafe" | 10 | "unsafe" | ||
| 11 | 11 | ||||
| 12 | "github.com/ebitengine/purego" | 12 | "github.com/ebitengine/purego" | ||
| 13 | - | "github.com/ebitengine/purego/internal/xreflect" | |||
| 14 | ) | 13 | ) | ||
| 15 | 14 | ||||
| 16 | const ( | 15 | const ( | ||
| @@ -125,7 +124,7 @@ func (*blockCache) encode(typ reflect.Type) *uint8 { | |||||
| 125 | encoding = returnType | 124 | encoding = returnType | ||
| 126 | } | 125 | } | ||
| 127 | 126 | ||||
| 128 | - | if typ.NumIn() == 0 || typ.In(0) != reflect.TypeOf(Block(0)) { | 127 | + | if typ.NumIn() == 0 || typ.In(0) != reflect.TypeFor[Block]() { |
| 129 | panic(fmt.Sprintf("objc: A Block implementation must take a Block as its first argument; got %v", typ.String())) | 128 | panic(fmt.Sprintf("objc: A Block implementation must take a Block as its first argument; got %v", typ.String())) | ||
| 130 | } | 129 | } | ||
| 131 | 130 | ||||
| @@ -168,7 +167,7 @@ func (b *blockCache) getLayout(typ reflect.Type) blockLayout { | |||||
| 168 | reflect.MakeFunc( | 167 | reflect.MakeFunc( | ||
| 169 | typ, | 168 | typ, | ||
| 170 | func(args []reflect.Value) (results []reflect.Value) { | 169 | func(args []reflect.Value) (results []reflect.Value) { | ||
| 171 | - | block, ok := xreflect.TypeAssert[Block](args[0]) | 170 | + | block, ok := reflect.TypeAssert[Block](args[0]) |
| 172 | if !ok { | 171 | if !ok { | ||
| 173 | panic(fmt.Sprintf("objc: block argument is not a block but %s", args[0].Type().String())) | 172 | panic(fmt.Sprintf("objc: block argument is not a block but %s", args[0].Type().String())) | ||
| 174 | } | 173 | } | ||
| @@ -263,7 +262,7 @@ func InvokeBlock[T any](block Block, args ...any) (result T, err error) { | |||||
| 263 | callResult := fn.Call(reflectedArgs) | 262 | callResult := fn.Call(reflectedArgs) | ||
| 264 | 263 | ||||
| 265 | var ok bool | 264 | var ok bool | ||
| 266 | - | result, ok = xreflect.TypeAssert[T](callResult[0]) | 265 | + | result, ok = reflect.TypeAssert[T](callResult[0]) |
| 267 | if !ok { | 266 | if !ok { | ||
| 268 | return result, fmt.Errorf("objc: the returned value type %s was not %T", callResult[0].Type().String(), result) | 267 | return result, fmt.Errorf("objc: the returned value type %s was not %T", callResult[0].Type().String(), result) | ||
| 269 | } | 268 | } | ||
M objc/objc_runtime_darwin.go +13 -24
| @@ -12,13 +12,13 @@ import ( | |||
| 12 | 12 | "reflect" | |
| 13 | 13 | "regexp" | |
| 14 | 14 | "runtime" | |
| 15 | + | "slices" | |
| 15 | 16 | stdstrings "strings" | |
| 16 | 17 | "unicode" | |
| 17 | 18 | "unsafe" | |
| 18 | 19 | ||
| 19 | 20 | "github.com/ebitengine/purego" | |
| 20 | 21 | "github.com/ebitengine/purego/internal/strings" | |
| 21 | - | "github.com/ebitengine/purego/internal/xreflect" | |
| 22 | 22 | ) | |
| 23 | 23 | ||
| 24 | 24 | // TODO: support try/catch? | |
| @@ -322,7 +322,7 @@ func RegisterClass(name string, superClass Class, protocols []*Protocol, ivars [ | |||
| 322 | 322 | case ReadWrite: | |
| 323 | 323 | ty := reflect.FuncOf( | |
| 324 | 324 | []reflect.Type{ | |
| 325 | - | reflect.TypeOf(ID(0)), reflect.TypeOf(SEL(0)), ivar.Type, | |
| 325 | + | reflect.TypeFor[ID](), reflect.TypeFor[SEL](), ivar.Type, | |
| 326 | 326 | }, | |
| 327 | 327 | nil, false, | |
| 328 | 328 | ) | |
| @@ -343,7 +343,7 @@ func RegisterClass(name string, superClass Class, protocols []*Protocol, ivars [ | |||
| 343 | 343 | // })(unsafe.Pointer(args[0].Interface().(ID)))).v = 123 | |
| 344 | 344 | // | |
| 345 | 345 | // However, since the type of the variable is unknown reflection is used to actually assign the value | |
| 346 | - | id, ok := xreflect.TypeAssert[ID](args[0]) | |
| 346 | + | id, ok := reflect.TypeAssert[ID](args[0]) | |
| 347 | 347 | if !ok { | |
| 348 | 348 | panic(fmt.Sprintf("objc: id argument is not a ID but %s", args[0].Type().String())) | |
| 349 | 349 | } | |
| @@ -358,7 +358,7 @@ func RegisterClass(name string, superClass Class, protocols []*Protocol, ivars [ | |||
| 358 | 358 | case ReadOnly: | |
| 359 | 359 | ty := reflect.FuncOf( | |
| 360 | 360 | []reflect.Type{ | |
| 361 | - | reflect.TypeOf(ID(0)), reflect.TypeOf(SEL(0)), | |
| 361 | + | reflect.TypeFor[ID](), reflect.TypeFor[SEL](), | |
| 362 | 362 | }, | |
| 363 | 363 | []reflect.Type{ivar.Type}, false, | |
| 364 | 364 | ) | |
| @@ -371,7 +371,7 @@ func RegisterClass(name string, superClass Class, protocols []*Protocol, ivars [ | |||
| 371 | 371 | if len(args) != 2 { | |
| 372 | 372 | panic(fmt.Sprintf("objc: incorrect number of args. expected 2 got %d", len(args))) | |
| 373 | 373 | } | |
| 374 | - | id, ok := xreflect.TypeAssert[ID](args[0]) | |
| 374 | + | id, ok := reflect.TypeAssert[ID](args[0]) | |
| 375 | 375 | if !ok { | |
| 376 | 376 | panic(fmt.Sprintf("objc: id argument is not a ID but %s", args[0].Type().String())) | |
| 377 | 377 | } | |
| @@ -419,11 +419,11 @@ const ( | |||
| 419 | 419 | // Source: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html#//apple_ref/doc/uid/TP40008048-CH100 | |
| 420 | 420 | func encodeType(typ reflect.Type, insidePtr bool) (string, error) { | |
| 421 | 421 | switch typ { | |
| 422 | - | case reflect.TypeOf(Class(0)): | |
| 422 | + | case reflect.TypeFor[Class](): | |
| 423 | 423 | return encClass, nil | |
| 424 | - | case reflect.TypeOf(ID(0)), reflect.TypeOf(Block(0)): | |
| 424 | + | case reflect.TypeFor[ID](), reflect.TypeFor[Block](): | |
| 425 | 425 | return encId, nil | |
| 426 | - | case reflect.TypeOf(SEL(0)): | |
| 426 | + | case reflect.TypeFor[SEL](): | |
| 427 | 427 | return encSelector, nil | |
| 428 | 428 | } | |
| 429 | 429 | ||
| @@ -626,7 +626,7 @@ func (p *Protocol) Register() { | |||
| 626 | 626 | func (p *Protocol) CopyMethodDescriptionList(isRequiredMethod, isInstanceMethod bool) []MethodDescription { | |
| 627 | 627 | count := uint32(0) | |
| 628 | 628 | desc := protocol_copyMethodDescriptionList(p, isRequiredMethod, isInstanceMethod, &count) | |
| 629 | - | methods := clone(unsafe.Slice(desc, count)) | |
| 629 | + | methods := slices.Clone(unsafe.Slice(desc, count)) | |
| 630 | 630 | free(unsafe.Pointer(desc)) | |
| 631 | 631 | return methods | |
| 632 | 632 | } | |
| @@ -635,7 +635,7 @@ func (p *Protocol) CopyMethodDescriptionList(isRequiredMethod, isInstanceMethod | |||
| 635 | 635 | func (p *Protocol) CopyProtocolList() []*Protocol { | |
| 636 | 636 | count := uint32(0) | |
| 637 | 637 | desc := protocol_copyProtocolList(p, &count) | |
| 638 | - | protocols := clone(unsafe.Slice(desc, count)) | |
| 638 | + | protocols := slices.Clone(unsafe.Slice(desc, count)) | |
| 639 | 639 | free(unsafe.Pointer(desc)) | |
| 640 | 640 | return protocols | |
| 641 | 641 | } | |
| @@ -644,7 +644,7 @@ func (p *Protocol) CopyProtocolList() []*Protocol { | |||
| 644 | 644 | func (p *Protocol) CopyPropertyList(isRequiredProperty, isInstanceProperty bool) []Property { | |
| 645 | 645 | count := uint32(0) | |
| 646 | 646 | desc := protocol_copyPropertyList2(p, &count, isRequiredProperty, isInstanceProperty) | |
| 647 | - | protocols := clone(unsafe.Slice(desc, count)) | |
| 647 | + | protocols := slices.Clone(unsafe.Slice(desc, count)) | |
| 648 | 648 | free(unsafe.Pointer(desc)) | |
| 649 | 649 | return protocols | |
| 650 | 650 | } | |
| @@ -691,21 +691,10 @@ func NewIMP(fn any) IMP { | |||
| 691 | 691 | switch { | |
| 692 | 692 | case ty.NumIn() < 2: | |
| 693 | 693 | fallthrough | |
| 694 | - | case ty.In(0) != reflect.TypeOf(ID(0)): | |
| 694 | + | case ty.In(0) != reflect.TypeFor[ID](): | |
| 695 | 695 | fallthrough | |
| 696 | - | case ty.In(1) != reflect.TypeOf(SEL(0)): | |
| 696 | + | case ty.In(1) != reflect.TypeFor[SEL](): | |
| 697 | 697 | panic("objc: NewIMP must take a (id, SEL) as its first two arguments; got " + ty.String()) | |
| 698 | 698 | } | |
| 699 | 699 | return IMP(purego.NewCallback(fn)) | |
| 700 | 700 | } | |
| 701 | - | ||
| 702 | - | // TODO: remove and use slices.Clone when minimum version for purego is 1.21 | |
| 703 | - | func clone[S ~[]E, E any](s S) S { | |
| 704 | - | // Preserve nilness in case it matters. | |
| 705 | - | if s == nil { | |
| 706 | - | return nil | |
| 707 | - | } | |
| 708 | - | // Avoid s[:0:0] as it leads to unwanted liveness when cloning a | |
| 709 | - | // zero-length slice of a large array; see https://go.dev/issue/68488. | |
| 710 | - | return append(S{}, s...) | |
| 711 | - | } | |
| @@ -12,13 +12,13 @@ import ( | |||||
| 12 | "reflect" | 12 | "reflect" | ||
| 13 | "regexp" | 13 | "regexp" | ||
| 14 | "runtime" | 14 | "runtime" | ||
| 15 | + | "slices" | |||
| 15 | stdstrings "strings" | 16 | stdstrings "strings" | ||
| 16 | "unicode" | 17 | "unicode" | ||
| 17 | "unsafe" | 18 | "unsafe" | ||
| 18 | 19 | ||||
| 19 | "github.com/ebitengine/purego" | 20 | "github.com/ebitengine/purego" | ||
| 20 | "github.com/ebitengine/purego/internal/strings" | 21 | "github.com/ebitengine/purego/internal/strings" | ||
| 21 | - | "github.com/ebitengine/purego/internal/xreflect" | |||
| 22 | ) | 22 | ) | ||
| 23 | 23 | ||||
| 24 | // TODO: support try/catch? | 24 | // TODO: support try/catch? | ||
| @@ -322,7 +322,7 @@ func RegisterClass(name string, superClass Class, protocols []*Protocol, ivars [ | |||||
| 322 | case ReadWrite: | 322 | case ReadWrite: | ||
| 323 | ty := reflect.FuncOf( | 323 | ty := reflect.FuncOf( | ||
| 324 | []reflect.Type{ | 324 | []reflect.Type{ | ||
| 325 | - | reflect.TypeOf(ID(0)), reflect.TypeOf(SEL(0)), ivar.Type, | 325 | + | reflect.TypeFor[ID](), reflect.TypeFor[SEL](), ivar.Type, |
| 326 | }, | 326 | }, | ||
| 327 | nil, false, | 327 | nil, false, | ||
| 328 | ) | 328 | ) | ||
| @@ -343,7 +343,7 @@ func RegisterClass(name string, superClass Class, protocols []*Protocol, ivars [ | |||||
| 343 | // })(unsafe.Pointer(args[0].Interface().(ID)))).v = 123 | 343 | // })(unsafe.Pointer(args[0].Interface().(ID)))).v = 123 | ||
| 344 | // | 344 | // | ||
| 345 | // However, since the type of the variable is unknown reflection is used to actually assign the value | 345 | // However, since the type of the variable is unknown reflection is used to actually assign the value | ||
| 346 | - | id, ok := xreflect.TypeAssert[ID](args[0]) | 346 | + | id, ok := reflect.TypeAssert[ID](args[0]) |
| 347 | if !ok { | 347 | if !ok { | ||
| 348 | panic(fmt.Sprintf("objc: id argument is not a ID but %s", args[0].Type().String())) | 348 | panic(fmt.Sprintf("objc: id argument is not a ID but %s", args[0].Type().String())) | ||
| 349 | } | 349 | } | ||
| @@ -358,7 +358,7 @@ func RegisterClass(name string, superClass Class, protocols []*Protocol, ivars [ | |||||
| 358 | case ReadOnly: | 358 | case ReadOnly: | ||
| 359 | ty := reflect.FuncOf( | 359 | ty := reflect.FuncOf( | ||
| 360 | []reflect.Type{ | 360 | []reflect.Type{ | ||
| 361 | - | reflect.TypeOf(ID(0)), reflect.TypeOf(SEL(0)), | 361 | + | reflect.TypeFor[ID](), reflect.TypeFor[SEL](), |
| 362 | }, | 362 | }, | ||
| 363 | []reflect.Type{ivar.Type}, false, | 363 | []reflect.Type{ivar.Type}, false, | ||
| 364 | ) | 364 | ) | ||
| @@ -371,7 +371,7 @@ func RegisterClass(name string, superClass Class, protocols []*Protocol, ivars [ | |||||
| 371 | if len(args) != 2 { | 371 | if len(args) != 2 { | ||
| 372 | panic(fmt.Sprintf("objc: incorrect number of args. expected 2 got %d", len(args))) | 372 | panic(fmt.Sprintf("objc: incorrect number of args. expected 2 got %d", len(args))) | ||
| 373 | } | 373 | } | ||
| 374 | - | id, ok := xreflect.TypeAssert[ID](args[0]) | 374 | + | id, ok := reflect.TypeAssert[ID](args[0]) |
| 375 | if !ok { | 375 | if !ok { | ||
| 376 | panic(fmt.Sprintf("objc: id argument is not a ID but %s", args[0].Type().String())) | 376 | panic(fmt.Sprintf("objc: id argument is not a ID but %s", args[0].Type().String())) | ||
| 377 | } | 377 | } | ||
| @@ -419,11 +419,11 @@ const ( | |||||
| 419 | // Source: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html#//apple_ref/doc/uid/TP40008048-CH100 | 419 | // Source: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html#//apple_ref/doc/uid/TP40008048-CH100 | ||
| 420 | func encodeType(typ reflect.Type, insidePtr bool) (string, error) { | 420 | func encodeType(typ reflect.Type, insidePtr bool) (string, error) { | ||
| 421 | switch typ { | 421 | switch typ { | ||
| 422 | - | case reflect.TypeOf(Class(0)): | 422 | + | case reflect.TypeFor[Class](): |
| 423 | return encClass, nil | 423 | return encClass, nil | ||
| 424 | - | case reflect.TypeOf(ID(0)), reflect.TypeOf(Block(0)): | 424 | + | case reflect.TypeFor[ID](), reflect.TypeFor[Block](): |
| 425 | return encId, nil | 425 | return encId, nil | ||
| 426 | - | case reflect.TypeOf(SEL(0)): | 426 | + | case reflect.TypeFor[SEL](): |
| 427 | return encSelector, nil | 427 | return encSelector, nil | ||
| 428 | } | 428 | } | ||
| 429 | 429 | ||||
| @@ -626,7 +626,7 @@ func (p *Protocol) Register() { | |||||
| 626 | func (p *Protocol) CopyMethodDescriptionList(isRequiredMethod, isInstanceMethod bool) []MethodDescription { | 626 | func (p *Protocol) CopyMethodDescriptionList(isRequiredMethod, isInstanceMethod bool) []MethodDescription { | ||
| 627 | count := uint32(0) | 627 | count := uint32(0) | ||
| 628 | desc := protocol_copyMethodDescriptionList(p, isRequiredMethod, isInstanceMethod, &count) | 628 | desc := protocol_copyMethodDescriptionList(p, isRequiredMethod, isInstanceMethod, &count) | ||
| 629 | - | methods := clone(unsafe.Slice(desc, count)) | 629 | + | methods := slices.Clone(unsafe.Slice(desc, count)) |
| 630 | free(unsafe.Pointer(desc)) | 630 | free(unsafe.Pointer(desc)) | ||
| 631 | return methods | 631 | return methods | ||
| 632 | } | 632 | } | ||
| @@ -635,7 +635,7 @@ func (p *Protocol) CopyMethodDescriptionList(isRequiredMethod, isInstanceMethod | |||||
| 635 | func (p *Protocol) CopyProtocolList() []*Protocol { | 635 | func (p *Protocol) CopyProtocolList() []*Protocol { | ||
| 636 | count := uint32(0) | 636 | count := uint32(0) | ||
| 637 | desc := protocol_copyProtocolList(p, &count) | 637 | desc := protocol_copyProtocolList(p, &count) | ||
| 638 | - | protocols := clone(unsafe.Slice(desc, count)) | 638 | + | protocols := slices.Clone(unsafe.Slice(desc, count)) |
| 639 | free(unsafe.Pointer(desc)) | 639 | free(unsafe.Pointer(desc)) | ||
| 640 | return protocols | 640 | return protocols | ||
| 641 | } | 641 | } | ||
| @@ -644,7 +644,7 @@ func (p *Protocol) CopyProtocolList() []*Protocol { | |||||
| 644 | func (p *Protocol) CopyPropertyList(isRequiredProperty, isInstanceProperty bool) []Property { | 644 | func (p *Protocol) CopyPropertyList(isRequiredProperty, isInstanceProperty bool) []Property { | ||
| 645 | count := uint32(0) | 645 | count := uint32(0) | ||
| 646 | desc := protocol_copyPropertyList2(p, &count, isRequiredProperty, isInstanceProperty) | 646 | desc := protocol_copyPropertyList2(p, &count, isRequiredProperty, isInstanceProperty) | ||
| 647 | - | protocols := clone(unsafe.Slice(desc, count)) | 647 | + | protocols := slices.Clone(unsafe.Slice(desc, count)) |
| 648 | free(unsafe.Pointer(desc)) | 648 | free(unsafe.Pointer(desc)) | ||
| 649 | return protocols | 649 | return protocols | ||
| 650 | } | 650 | } | ||
| @@ -691,21 +691,10 @@ func NewIMP(fn any) IMP { | |||||
| 691 | switch { | 691 | switch { | ||
| 692 | case ty.NumIn() < 2: | 692 | case ty.NumIn() < 2: | ||
| 693 | fallthrough | 693 | fallthrough | ||
| 694 | - | case ty.In(0) != reflect.TypeOf(ID(0)): | 694 | + | case ty.In(0) != reflect.TypeFor[ID](): |
| 695 | fallthrough | 695 | fallthrough | ||
| 696 | - | case ty.In(1) != reflect.TypeOf(SEL(0)): | 696 | + | case ty.In(1) != reflect.TypeFor[SEL](): |
| 697 | panic("objc: NewIMP must take a (id, SEL) as its first two arguments; got " + ty.String()) | 697 | panic("objc: NewIMP must take a (id, SEL) as its first two arguments; got " + ty.String()) | ||
| 698 | } | 698 | } | ||
| 699 | return IMP(purego.NewCallback(fn)) | 699 | return IMP(purego.NewCallback(fn)) | ||
| 700 | } | 700 | } | ||
| 701 | - | ||||
| 702 | - | // TODO: remove and use slices.Clone when minimum version for purego is 1.21 | |||
| 703 | - | func clone[S ~[]E, E any](s S) S { | |||
| 704 | - | // Preserve nilness in case it matters. | |||
| 705 | - | if s == nil { | |||
| 706 | - | return nil | |||
| 707 | - | } | |||
| 708 | - | // Avoid s[:0:0] as it leads to unwanted liveness when cloning a | |||
| 709 | - | // zero-length slice of a large array; see https://go.dev/issue/68488. | |||
| 710 | - | return append(S{}, s...) | |||
| 711 | - | } | |||
M objc/objc_runtime_darwin_test.go +2 -2
| @@ -58,12 +58,12 @@ func ExampleRegisterClass() { | |||
| 58 | 58 | []objc.FieldDef{ | |
| 59 | 59 | { | |
| 60 | 60 | Name: "bar", | |
| 61 | - | Type: reflect.TypeOf(int(0)), | |
| 61 | + | Type: reflect.TypeFor[int](), | |
| 62 | 62 | Attribute: objc.ReadWrite, | |
| 63 | 63 | }, | |
| 64 | 64 | { | |
| 65 | 65 | Name: "foo", | |
| 66 | - | Type: reflect.TypeOf(false), | |
| 66 | + | Type: reflect.TypeFor[bool](), | |
| 67 | 67 | Attribute: objc.ReadWrite, | |
| 68 | 68 | }, | |
| 69 | 69 | }, | |
| @@ -58,12 +58,12 @@ func ExampleRegisterClass() { | |||||
| 58 | []objc.FieldDef{ | 58 | []objc.FieldDef{ | ||
| 59 | { | 59 | { | ||
| 60 | Name: "bar", | 60 | Name: "bar", | ||
| 61 | - | Type: reflect.TypeOf(int(0)), | 61 | + | Type: reflect.TypeFor[int](), |
| 62 | Attribute: objc.ReadWrite, | 62 | Attribute: objc.ReadWrite, | ||
| 63 | }, | 63 | }, | ||
| 64 | { | 64 | { | ||
| 65 | Name: "foo", | 65 | Name: "foo", | ||
| 66 | - | Type: reflect.TypeOf(false), | 66 | + | Type: reflect.TypeFor[bool](), |
| 67 | Attribute: objc.ReadWrite, | 67 | Attribute: objc.ReadWrite, | ||
| 68 | }, | 68 | }, | ||
| 69 | }, | 69 | }, | ||
M struct_arm64.go +3 -3
| @@ -311,7 +311,7 @@ func copyStruct8ByteChunks(ptr unsafe.Pointer, size uintptr, addChunk func(uintp | |||
| 311 | 311 | chunk = *(*uintptr)(unsafe.Add(ptr, offset)) | |
| 312 | 312 | } else { | |
| 313 | 313 | // Read byte-by-byte to avoid reading beyond allocation | |
| 314 | - | for i := uintptr(0); i < remaining; i++ { | |
| 314 | + | for i := range remaining { | |
| 315 | 315 | b := *(*byte)(unsafe.Add(ptr, offset+i)) | |
| 316 | 316 | chunk |= uintptr(b) << (i * 8) | |
| 317 | 317 | } | |
| @@ -514,7 +514,7 @@ func bundleStackArgs(stackArgs []reflect.Value, addStack func(uintptr)) { | |||
| 514 | 514 | paddingNeeded := uintptr(valAlign) - (currentOffset % uintptr(valAlign)) | |
| 515 | 515 | fields = append(fields, reflect.StructField{ | |
| 516 | 516 | Name: paddingFieldPrefix + strconv.Itoa(fieldIndex), | |
| 517 | - | Type: reflect.ArrayOf(int(paddingNeeded), reflect.TypeOf(byte(0))), | |
| 517 | + | Type: reflect.ArrayOf(int(paddingNeeded), reflect.TypeFor[byte]()), | |
| 518 | 518 | }) | |
| 519 | 519 | currentOffset += paddingNeeded | |
| 520 | 520 | fieldIndex++ | |
| @@ -610,7 +610,7 @@ func getCallbackStruct(inType reflect.Type, frame unsafe.Pointer, floatsN *int, | |||
| 610 | 610 | ||
| 611 | 611 | // Pointer on stack (rare: all integer registers exhausted). | |
| 612 | 612 | if isDarwin { | |
| 613 | - | ptrVal := callbackArgFromStack(frame, *stackSlot, stackByteOffset, reflect.TypeOf(uintptr(0))) | |
| 613 | + | ptrVal := callbackArgFromStack(frame, *stackSlot, stackByteOffset, reflect.TypeFor[uintptr]()) | |
| 614 | 614 | ptr := uintptr(ptrVal.Uint()) | |
| 615 | 615 | return reflect.NewAt(inType, *(*unsafe.Pointer)(unsafe.Pointer(&ptr))).Elem() | |
| 616 | 616 | } | |
| @@ -311,7 +311,7 @@ func copyStruct8ByteChunks(ptr unsafe.Pointer, size uintptr, addChunk func(uintp | |||||
| 311 | chunk = *(*uintptr)(unsafe.Add(ptr, offset)) | 311 | chunk = *(*uintptr)(unsafe.Add(ptr, offset)) | ||
| 312 | } else { | 312 | } else { | ||
| 313 | // Read byte-by-byte to avoid reading beyond allocation | 313 | // Read byte-by-byte to avoid reading beyond allocation | ||
| 314 | - | for i := uintptr(0); i < remaining; i++ { | 314 | + | for i := range remaining { |
| 315 | b := *(*byte)(unsafe.Add(ptr, offset+i)) | 315 | b := *(*byte)(unsafe.Add(ptr, offset+i)) | ||
| 316 | chunk |= uintptr(b) << (i * 8) | 316 | chunk |= uintptr(b) << (i * 8) | ||
| 317 | } | 317 | } | ||
| @@ -514,7 +514,7 @@ func bundleStackArgs(stackArgs []reflect.Value, addStack func(uintptr)) { | |||||
| 514 | paddingNeeded := uintptr(valAlign) - (currentOffset % uintptr(valAlign)) | 514 | paddingNeeded := uintptr(valAlign) - (currentOffset % uintptr(valAlign)) | ||
| 515 | fields = append(fields, reflect.StructField{ | 515 | fields = append(fields, reflect.StructField{ | ||
| 516 | Name: paddingFieldPrefix + strconv.Itoa(fieldIndex), | 516 | Name: paddingFieldPrefix + strconv.Itoa(fieldIndex), | ||
| 517 | - | Type: reflect.ArrayOf(int(paddingNeeded), reflect.TypeOf(byte(0))), | 517 | + | Type: reflect.ArrayOf(int(paddingNeeded), reflect.TypeFor[byte]()), |
| 518 | }) | 518 | }) | ||
| 519 | currentOffset += paddingNeeded | 519 | currentOffset += paddingNeeded | ||
| 520 | fieldIndex++ | 520 | fieldIndex++ | ||
| @@ -610,7 +610,7 @@ func getCallbackStruct(inType reflect.Type, frame unsafe.Pointer, floatsN *int, | |||||
| 610 | 610 | ||||
| 611 | // Pointer on stack (rare: all integer registers exhausted). | 611 | // Pointer on stack (rare: all integer registers exhausted). | ||
| 612 | if isDarwin { | 612 | if isDarwin { | ||
| 613 | - | ptrVal := callbackArgFromStack(frame, *stackSlot, stackByteOffset, reflect.TypeOf(uintptr(0))) | 613 | + | ptrVal := callbackArgFromStack(frame, *stackSlot, stackByteOffset, reflect.TypeFor[uintptr]()) |
| 614 | ptr := uintptr(ptrVal.Uint()) | 614 | ptr := uintptr(ptrVal.Uint()) | ||
| 615 | return reflect.NewAt(inType, *(*unsafe.Pointer)(unsafe.Pointer(&ptr))).Elem() | 615 | return reflect.NewAt(inType, *(*unsafe.Pointer)(unsafe.Pointer(&ptr))).Elem() | ||
| 616 | } | 616 | } | ||
M struct_test.go +17 -33
| @@ -6,11 +6,13 @@ | |||
| 6 | 6 | package purego_test | |
| 7 | 7 | ||
| 8 | 8 | import ( | |
| 9 | + | "iter" | |
| 9 | 10 | "math" | |
| 10 | 11 | "os" | |
| 11 | 12 | "path/filepath" | |
| 12 | 13 | "reflect" | |
| 13 | 14 | "runtime" | |
| 15 | + | "slices" | |
| 14 | 16 | "testing" | |
| 15 | 17 | "unsafe" | |
| 16 | 18 | ||
| @@ -76,7 +78,6 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||
| 76 | 78 | }, | |
| 77 | 79 | } | |
| 78 | 80 | for _, imp := range implementations { | |
| 79 | - | imp := imp | |
| 80 | 81 | if imp.usesCallbacks && runtime.GOOS == "windows" { | |
| 81 | 82 | // Callbacks on Windows use the stdlib syscall.NewCallback, which does | |
| 82 | 83 | // not support struct arguments or returns. | |
| @@ -834,38 +835,23 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||
| 834 | 835 | } | |
| 835 | 836 | } | |
| 836 | 837 | ||
| 837 | - | // TODO: this could use the iter.Seq interface when purego supports Go 1.23 | |
| 838 | - | func nextFieldFn(v reflect.Value) func() (reflect.Value, bool) { | |
| 839 | - | var fieldIndex int | |
| 840 | - | var tracker func() (reflect.Value, bool) | |
| 841 | - | return func() (reflect.Value, bool) { | |
| 842 | - | if v.NumField() == 0 { | |
| 843 | - | return reflect.Value{}, false | |
| 844 | - | } | |
| 845 | - | if tracker != nil { | |
| 846 | - | if field, ok := tracker(); ok { | |
| 847 | - | return field, ok | |
| 848 | - | } | |
| 849 | - | tracker = nil | |
| 850 | - | } | |
| 851 | - | for fieldIndex < v.NumField() { | |
| 852 | - | if v.Type().Field(fieldIndex).Name == "_" { | |
| 853 | - | fieldIndex++ | |
| 838 | + | func fields(v reflect.Value) iter.Seq[reflect.Value] { | |
| 839 | + | return func(yield func(reflect.Value) bool) { | |
| 840 | + | for i := range v.NumField() { | |
| 841 | + | if v.Type().Field(i).Name == "_" { | |
| 854 | 842 | continue | |
| 855 | 843 | } | |
| 856 | - | field := v.Field(fieldIndex) | |
| 857 | - | fieldIndex++ | |
| 844 | + | field := v.Field(i) | |
| 858 | 845 | if field.Kind() == reflect.Struct { | |
| 859 | - | tracker = nextFieldFn(field) | |
| 860 | - | if inner, ok := tracker(); ok { | |
| 861 | - | return inner, ok | |
| 846 | + | for inner := range fields(field) { | |
| 847 | + | if !yield(inner) { | |
| 848 | + | return | |
| 849 | + | } | |
| 862 | 850 | } | |
| 863 | - | tracker = nil | |
| 864 | - | } else { | |
| 865 | - | return field, true | |
| 851 | + | } else if !yield(field) { | |
| 852 | + | return | |
| 866 | 853 | } | |
| 867 | 854 | } | |
| 868 | - | return reflect.Value{}, false | |
| 869 | 855 | } | |
| 870 | 856 | } | |
| 871 | 857 | ||
| @@ -903,13 +889,12 @@ func TestRegisterFunc_structReturns(t *testing.T) { | |||
| 903 | 889 | fn := reflect.MakeFunc(reflect.TypeOf(fptr).Elem(), func(args []reflect.Value) []reflect.Value { | |
| 904 | 890 | retType := reflect.TypeOf(fptr).Elem().Out(0) | |
| 905 | 891 | ret := reflect.New(retType).Elem() | |
| 906 | - | next := nextFieldFn(ret) | |
| 907 | - | for _, a := range args { | |
| 908 | - | field, ok := next() | |
| 909 | - | if !ok { | |
| 892 | + | leaves := slices.Collect(fields(ret)) | |
| 893 | + | for i, a := range args { | |
| 894 | + | if i >= len(leaves) { | |
| 910 | 895 | panic("purego: no more fields") | |
| 911 | 896 | } | |
| 912 | - | field.Set(a) | |
| 897 | + | leaves[i].Set(a) | |
| 913 | 898 | } | |
| 914 | 899 | return []reflect.Value{ret} | |
| 915 | 900 | }) | |
| @@ -918,7 +903,6 @@ func TestRegisterFunc_structReturns(t *testing.T) { | |||
| 918 | 903 | }, | |
| 919 | 904 | } | |
| 920 | 905 | for _, imp := range implementations { | |
| 921 | - | imp := imp | |
| 922 | 906 | if imp.usesCallbacks && runtime.GOOS == "windows" { | |
| 923 | 907 | // Callbacks on Windows use the stdlib syscall.NewCallback, which does | |
| 924 | 908 | // not support struct arguments or returns. | |
| @@ -6,11 +6,13 @@ | |||||
| 6 | package purego_test | 6 | package purego_test | ||
| 7 | 7 | ||||
| 8 | import ( | 8 | import ( | ||
| 9 | + | "iter" | |||
| 9 | "math" | 10 | "math" | ||
| 10 | "os" | 11 | "os" | ||
| 11 | "path/filepath" | 12 | "path/filepath" | ||
| 12 | "reflect" | 13 | "reflect" | ||
| 13 | "runtime" | 14 | "runtime" | ||
| 15 | + | "slices" | |||
| 14 | "testing" | 16 | "testing" | ||
| 15 | "unsafe" | 17 | "unsafe" | ||
| 16 | 18 | ||||
| @@ -76,7 +78,6 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||||
| 76 | }, | 78 | }, | ||
| 77 | } | 79 | } | ||
| 78 | for _, imp := range implementations { | 80 | for _, imp := range implementations { | ||
| 79 | - | imp := imp | |||
| 80 | if imp.usesCallbacks && runtime.GOOS == "windows" { | 81 | if imp.usesCallbacks && runtime.GOOS == "windows" { | ||
| 81 | // Callbacks on Windows use the stdlib syscall.NewCallback, which does | 82 | // Callbacks on Windows use the stdlib syscall.NewCallback, which does | ||
| 82 | // not support struct arguments or returns. | 83 | // not support struct arguments or returns. | ||
| @@ -834,38 +835,23 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||||
| 834 | } | 835 | } | ||
| 835 | } | 836 | } | ||
| 836 | 837 | ||||
| 837 | - | // TODO: this could use the iter.Seq interface when purego supports Go 1.23 | 838 | + | func fields(v reflect.Value) iter.Seq[reflect.Value] { |
| 838 | - | func nextFieldFn(v reflect.Value) func() (reflect.Value, bool) { | 839 | + | return func(yield func(reflect.Value) bool) { |
| 839 | - | var fieldIndex int | 840 | + | for i := range v.NumField() { |
| 840 | - | var tracker func() (reflect.Value, bool) | 841 | + | if v.Type().Field(i).Name == "_" { |
| 841 | - | return func() (reflect.Value, bool) { | |||
| 842 | - | if v.NumField() == 0 { | |||
| 843 | - | return reflect.Value{}, false | |||
| 844 | - | } | |||
| 845 | - | if tracker != nil { | |||
| 846 | - | if field, ok := tracker(); ok { | |||
| 847 | - | return field, ok | |||
| 848 | - | } | |||
| 849 | - | tracker = nil | |||
| 850 | - | } | |||
| 851 | - | for fieldIndex < v.NumField() { | |||
| 852 | - | if v.Type().Field(fieldIndex).Name == "_" { | |||
| 853 | - | fieldIndex++ | |||
| 854 | continue | 842 | continue | ||
| 855 | } | 843 | } | ||
| 856 | - | field := v.Field(fieldIndex) | 844 | + | field := v.Field(i) |
| 857 | - | fieldIndex++ | |||
| 858 | if field.Kind() == reflect.Struct { | 845 | if field.Kind() == reflect.Struct { | ||
| 859 | - | tracker = nextFieldFn(field) | 846 | + | for inner := range fields(field) { |
| 860 | - | if inner, ok := tracker(); ok { | 847 | + | if !yield(inner) { |
| 861 | - | return inner, ok | 848 | + | return |
| 849 | + | } | |||
| 862 | } | 850 | } | ||
| 863 | - | tracker = nil | 851 | + | } else if !yield(field) { |
| 864 | - | } else { | 852 | + | return |
| 865 | - | return field, true | |||
| 866 | } | 853 | } | ||
| 867 | } | 854 | } | ||
| 868 | - | return reflect.Value{}, false | |||
| 869 | } | 855 | } | ||
| 870 | } | 856 | } | ||
| 871 | 857 | ||||
| @@ -903,13 +889,12 @@ func TestRegisterFunc_structReturns(t *testing.T) { | |||||
| 903 | fn := reflect.MakeFunc(reflect.TypeOf(fptr).Elem(), func(args []reflect.Value) []reflect.Value { | 889 | fn := reflect.MakeFunc(reflect.TypeOf(fptr).Elem(), func(args []reflect.Value) []reflect.Value { | ||
| 904 | retType := reflect.TypeOf(fptr).Elem().Out(0) | 890 | retType := reflect.TypeOf(fptr).Elem().Out(0) | ||
| 905 | ret := reflect.New(retType).Elem() | 891 | ret := reflect.New(retType).Elem() | ||
| 906 | - | next := nextFieldFn(ret) | 892 | + | leaves := slices.Collect(fields(ret)) |
| 907 | - | for _, a := range args { | 893 | + | for i, a := range args { |
| 908 | - | field, ok := next() | 894 | + | if i >= len(leaves) { |
| 909 | - | if !ok { | |||
| 910 | panic("purego: no more fields") | 895 | panic("purego: no more fields") | ||
| 911 | } | 896 | } | ||
| 912 | - | field.Set(a) | 897 | + | leaves[i].Set(a) |
| 913 | } | 898 | } | ||
| 914 | return []reflect.Value{ret} | 899 | return []reflect.Value{ret} | ||
| 915 | }) | 900 | }) | ||
| @@ -918,7 +903,6 @@ func TestRegisterFunc_structReturns(t *testing.T) { | |||||
| 918 | }, | 903 | }, | ||
| 919 | } | 904 | } | ||
| 920 | for _, imp := range implementations { | 905 | for _, imp := range implementations { | ||
| 921 | - | imp := imp | |||
| 922 | if imp.usesCallbacks && runtime.GOOS == "windows" { | 906 | if imp.usesCallbacks && runtime.GOOS == "windows" { | ||
| 923 | // Callbacks on Windows use the stdlib syscall.NewCallback, which does | 907 | // Callbacks on Windows use the stdlib syscall.NewCallback, which does | ||
| 924 | // not support struct arguments or returns. | 908 | // not support struct arguments or returns. | ||
M syscall_bench_test.go +7 -7
| @@ -205,40 +205,40 @@ func callRegisterFunc(registerFn any, n int, args []int64, iterations int) int64 | |||
| 205 | 205 | switch n { | |
| 206 | 206 | case 1: | |
| 207 | 207 | f := registerFn.(*func(int64) int64) | |
| 208 | - | for i := 0; i < iterations; i++ { | |
| 208 | + | for range iterations { | |
| 209 | 209 | result = (*f)(args[0]) | |
| 210 | 210 | } | |
| 211 | 211 | case 2: | |
| 212 | 212 | f := registerFn.(*func(int64, int64) int64) | |
| 213 | - | for i := 0; i < iterations; i++ { | |
| 213 | + | for range iterations { | |
| 214 | 214 | result = (*f)(args[0], args[1]) | |
| 215 | 215 | } | |
| 216 | 216 | case 3: | |
| 217 | 217 | f := registerFn.(*func(int64, int64, int64) int64) | |
| 218 | - | for i := 0; i < iterations; i++ { | |
| 218 | + | for range iterations { | |
| 219 | 219 | result = (*f)(args[0], args[1], args[2]) | |
| 220 | 220 | } | |
| 221 | 221 | case 5: | |
| 222 | 222 | f := registerFn.(*func(int64, int64, int64, int64, int64) int64) | |
| 223 | - | for i := 0; i < iterations; i++ { | |
| 223 | + | for range iterations { | |
| 224 | 224 | result = (*f)(args[0], args[1], args[2], args[3], args[4]) | |
| 225 | 225 | } | |
| 226 | 226 | case 10: | |
| 227 | 227 | f := registerFn.(*func(int64, int64, int64, int64, int64, int64, int64, int64, int64, int64) int64) | |
| 228 | - | for i := 0; i < iterations; i++ { | |
| 228 | + | for range iterations { | |
| 229 | 229 | result = (*f)(args[0], args[1], args[2], args[3], args[4], | |
| 230 | 230 | args[5], args[6], args[7], args[8], args[9]) | |
| 231 | 231 | } | |
| 232 | 232 | case 14: | |
| 233 | 233 | f := registerFn.(*func(int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64) int64) | |
| 234 | - | for i := 0; i < iterations; i++ { | |
| 234 | + | for range iterations { | |
| 235 | 235 | result = (*f)(args[0], args[1], args[2], args[3], args[4], | |
| 236 | 236 | args[5], args[6], args[7], args[8], args[9], | |
| 237 | 237 | args[10], args[11], args[12], args[13]) | |
| 238 | 238 | } | |
| 239 | 239 | case 15: | |
| 240 | 240 | f := registerFn.(*func(int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64) int64) | |
| 241 | - | for i := 0; i < iterations; i++ { | |
| 241 | + | for range iterations { | |
| 242 | 242 | result = (*f)(args[0], args[1], args[2], args[3], args[4], | |
| 243 | 243 | args[5], args[6], args[7], args[8], args[9], | |
| 244 | 244 | args[10], args[11], args[12], args[13], args[14]) | |
| @@ -205,40 +205,40 @@ func callRegisterFunc(registerFn any, n int, args []int64, iterations int) int64 | |||||
| 205 | switch n { | 205 | switch n { | ||
| 206 | case 1: | 206 | case 1: | ||
| 207 | f := registerFn.(*func(int64) int64) | 207 | f := registerFn.(*func(int64) int64) | ||
| 208 | - | for i := 0; i < iterations; i++ { | 208 | + | for range iterations { |
| 209 | result = (*f)(args[0]) | 209 | result = (*f)(args[0]) | ||
| 210 | } | 210 | } | ||
| 211 | case 2: | 211 | case 2: | ||
| 212 | f := registerFn.(*func(int64, int64) int64) | 212 | f := registerFn.(*func(int64, int64) int64) | ||
| 213 | - | for i := 0; i < iterations; i++ { | 213 | + | for range iterations { |
| 214 | result = (*f)(args[0], args[1]) | 214 | result = (*f)(args[0], args[1]) | ||
| 215 | } | 215 | } | ||
| 216 | case 3: | 216 | case 3: | ||
| 217 | f := registerFn.(*func(int64, int64, int64) int64) | 217 | f := registerFn.(*func(int64, int64, int64) int64) | ||
| 218 | - | for i := 0; i < iterations; i++ { | 218 | + | for range iterations { |
| 219 | result = (*f)(args[0], args[1], args[2]) | 219 | result = (*f)(args[0], args[1], args[2]) | ||
| 220 | } | 220 | } | ||
| 221 | case 5: | 221 | case 5: | ||
| 222 | f := registerFn.(*func(int64, int64, int64, int64, int64) int64) | 222 | f := registerFn.(*func(int64, int64, int64, int64, int64) int64) | ||
| 223 | - | for i := 0; i < iterations; i++ { | 223 | + | for range iterations { |
| 224 | result = (*f)(args[0], args[1], args[2], args[3], args[4]) | 224 | result = (*f)(args[0], args[1], args[2], args[3], args[4]) | ||
| 225 | } | 225 | } | ||
| 226 | case 10: | 226 | case 10: | ||
| 227 | f := registerFn.(*func(int64, int64, int64, int64, int64, int64, int64, int64, int64, int64) int64) | 227 | f := registerFn.(*func(int64, int64, int64, int64, int64, int64, int64, int64, int64, int64) int64) | ||
| 228 | - | for i := 0; i < iterations; i++ { | 228 | + | for range iterations { |
| 229 | result = (*f)(args[0], args[1], args[2], args[3], args[4], | 229 | result = (*f)(args[0], args[1], args[2], args[3], args[4], | ||
| 230 | args[5], args[6], args[7], args[8], args[9]) | 230 | args[5], args[6], args[7], args[8], args[9]) | ||
| 231 | } | 231 | } | ||
| 232 | case 14: | 232 | case 14: | ||
| 233 | f := registerFn.(*func(int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64) int64) | 233 | f := registerFn.(*func(int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64) int64) | ||
| 234 | - | for i := 0; i < iterations; i++ { | 234 | + | for range iterations { |
| 235 | result = (*f)(args[0], args[1], args[2], args[3], args[4], | 235 | result = (*f)(args[0], args[1], args[2], args[3], args[4], | ||
| 236 | args[5], args[6], args[7], args[8], args[9], | 236 | args[5], args[6], args[7], args[8], args[9], | ||
| 237 | args[10], args[11], args[12], args[13]) | 237 | args[10], args[11], args[12], args[13]) | ||
| 238 | } | 238 | } | ||
| 239 | case 15: | 239 | case 15: | ||
| 240 | f := registerFn.(*func(int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64) int64) | 240 | f := registerFn.(*func(int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64) int64) | ||
| 241 | - | for i := 0; i < iterations; i++ { | 241 | + | for range iterations { |
| 242 | result = (*f)(args[0], args[1], args[2], args[3], args[4], | 242 | result = (*f)(args[0], args[1], args[2], args[3], args[4], | ||
| 243 | args[5], args[6], args[7], args[8], args[9], | 243 | args[5], args[6], args[7], args[8], args[9], | ||
| 244 | args[10], args[11], args[12], args[13], args[14]) | 244 | args[10], args[11], args[12], args[13], args[14]) | ||
M syscall_unix.go +3 -3
| @@ -29,7 +29,7 @@ func NewCallback(fn any) uintptr { | |||
| 29 | 29 | ty := reflect.TypeOf(fn) | |
| 30 | 30 | for i := 0; i < ty.NumIn(); i++ { | |
| 31 | 31 | in := ty.In(i) | |
| 32 | - | if !in.AssignableTo(reflect.TypeOf(CDecl{})) { | |
| 32 | + | if !in.AssignableTo(reflect.TypeFor[CDecl]()) { | |
| 33 | 33 | continue | |
| 34 | 34 | } | |
| 35 | 35 | if i != 0 { | |
| @@ -62,7 +62,7 @@ func compileCallback(fn any) uintptr { | |||
| 62 | 62 | in := ty.In(i) | |
| 63 | 63 | switch in.Kind() { | |
| 64 | 64 | case reflect.Struct: | |
| 65 | - | if i == 0 && in.AssignableTo(reflect.TypeOf(CDecl{})) { | |
| 65 | + | if i == 0 && in.AssignableTo(reflect.TypeFor[CDecl]()) { | |
| 66 | 66 | continue | |
| 67 | 67 | } | |
| 68 | 68 | ensureStructSupported() | |
| @@ -190,7 +190,7 @@ func callbackWrap(a *callbackArgs) { | |||
| 190 | 190 | } | |
| 191 | 191 | floatsN += slots | |
| 192 | 192 | case reflect.Struct: | |
| 193 | - | if i == 0 && inType.AssignableTo(reflect.TypeOf(CDecl{})) { | |
| 193 | + | if i == 0 && inType.AssignableTo(reflect.TypeFor[CDecl]()) { | |
| 194 | 194 | args[i] = reflect.Zero(inType) | |
| 195 | 195 | continue | |
| 196 | 196 | } | |
| @@ -29,7 +29,7 @@ func NewCallback(fn any) uintptr { | |||||
| 29 | ty := reflect.TypeOf(fn) | 29 | ty := reflect.TypeOf(fn) | ||
| 30 | for i := 0; i < ty.NumIn(); i++ { | 30 | for i := 0; i < ty.NumIn(); i++ { | ||
| 31 | in := ty.In(i) | 31 | in := ty.In(i) | ||
| 32 | - | if !in.AssignableTo(reflect.TypeOf(CDecl{})) { | 32 | + | if !in.AssignableTo(reflect.TypeFor[CDecl]()) { |
| 33 | continue | 33 | continue | ||
| 34 | } | 34 | } | ||
| 35 | if i != 0 { | 35 | if i != 0 { | ||
| @@ -62,7 +62,7 @@ func compileCallback(fn any) uintptr { | |||||
| 62 | in := ty.In(i) | 62 | in := ty.In(i) | ||
| 63 | switch in.Kind() { | 63 | switch in.Kind() { | ||
| 64 | case reflect.Struct: | 64 | case reflect.Struct: | ||
| 65 | - | if i == 0 && in.AssignableTo(reflect.TypeOf(CDecl{})) { | 65 | + | if i == 0 && in.AssignableTo(reflect.TypeFor[CDecl]()) { |
| 66 | continue | 66 | continue | ||
| 67 | } | 67 | } | ||
| 68 | ensureStructSupported() | 68 | ensureStructSupported() | ||
| @@ -190,7 +190,7 @@ func callbackWrap(a *callbackArgs) { | |||||
| 190 | } | 190 | } | ||
| 191 | floatsN += slots | 191 | floatsN += slots | ||
| 192 | case reflect.Struct: | 192 | case reflect.Struct: | ||
| 193 | - | if i == 0 && inType.AssignableTo(reflect.TypeOf(CDecl{})) { | 193 | + | if i == 0 && inType.AssignableTo(reflect.TypeFor[CDecl]()) { |
| 194 | args[i] = reflect.Zero(inType) | 194 | args[i] = reflect.Zero(inType) | ||
| 195 | continue | 195 | continue | ||
| 196 | } | 196 | } | ||