From b4b50b9b0a264499cbf7f94bdbded9c01bb1c56e Mon Sep 17 00:00:00 2001 From: "Randall C. O'Reilly" Date: Sun, 25 Oct 2026 10:54:42 +0100 Subject: [PATCH 1/2] fix(fmt): register the ScanState and Scanner interfaces reflect.TypeFor[fmt.ScanState]() returned an invalid type, since the fmt override did not register the interface, so code that checks whether a type implements it panicked in Type.Implements. ScanState and Scanner are now registered with their methods. Signed-off-by: Randall C. O'Reilly --- gs/fmt/fmt.ts | 40 +++++++++++++++++++ tests/tests/reflect_typefor/expected.log | 2 + .../tests/reflect_typefor/reflect_typefor.go | 6 +++ .../reflect_typefor/reflect_typefor.gs.ts | 34 +++++++++++----- 4 files changed, 73 insertions(+), 9 deletions(-) diff --git a/gs/fmt/fmt.ts b/gs/fmt/fmt.ts index 5c0100a70..57f4ca44b 100644 --- a/gs/fmt/fmt.ts +++ b/gs/fmt/fmt.ts @@ -87,6 +87,46 @@ $.registerInterfaceType('fmt.Formatter', null, [ ['verb', $.basicType('int32')], ]), ]) +$.registerInterfaceType('fmt.ScanState', null, [ + $.methodSignature( + 'Read', + [['buf', $.sliceType($.basicType('uint8'))]], + [$.basicType('int'), 'error'], + ), + $.methodSignature( + 'ReadRune', + [], + [$.basicType('int32'), $.basicType('int'), 'error'], + ), + $.methodSignature('SkipSpace'), + $.methodSignature( + 'Token', + [ + ['skipSpace', $.basicType('bool')], + [ + 'f', + { + kind: $.TypeKind.Function, + params: [$.basicType('int32')], + results: [$.basicType('bool')], + }, + ], + ], + [$.sliceType($.basicType('uint8')), 'error'], + ), + $.methodSignature('UnreadRune', [], ['error']), + $.methodSignature('Width', [], [$.basicType('int'), $.basicType('bool')]), +]) +$.registerInterfaceType('fmt.Scanner', null, [ + $.methodSignature( + 'Scan', + [ + ['state', 'fmt.ScanState'], + ['verb', $.basicType('int32')], + ], + ['error'], + ), +]) // basicValue returns the primitive inside a boxed basic interface value, or // value itself when it is not boxed. diff --git a/tests/tests/reflect_typefor/expected.log b/tests/tests/reflect_typefor/expected.log index 6419a72bc..c1d81b7f4 100644 --- a/tests/tests/reflect_typefor/expected.log +++ b/tests/tests/reflect_typefor/expected.log @@ -21,4 +21,6 @@ index kind: float32 type: []int elem kind: int len: 0 map type: map[string]main.MyStruct pointer type: *main.MyStruct +fmt.ScanState: fmt.ScanState interface 6 +fmt.Scanner: fmt.Scanner interface 1 reflect_typefor test finished diff --git a/tests/tests/reflect_typefor/reflect_typefor.go b/tests/tests/reflect_typefor/reflect_typefor.go index 636b435fd..d90200f1b 100644 --- a/tests/tests/reflect_typefor/reflect_typefor.go +++ b/tests/tests/reflect_typefor/reflect_typefor.go @@ -74,6 +74,12 @@ func main() { genericComposites() + // The fmt scanning interfaces are interface types. + scanState := reflect.TypeFor[fmt.ScanState]() + println("fmt.ScanState:", scanState.String(), scanState.Kind().String(), scanState.NumMethod()) + scanner := reflect.TypeFor[fmt.Scanner]() + println("fmt.Scanner:", scanner.String(), scanner.Kind().String(), scanner.NumMethod()) + println("reflect_typefor test finished") } diff --git a/tests/tests/reflect_typefor/reflect_typefor.gs.ts b/tests/tests/reflect_typefor/reflect_typefor.gs.ts index 64b2f738b..fd199c64e 100644 --- a/tests/tests/reflect_typefor/reflect_typefor.gs.ts +++ b/tests/tests/reflect_typefor/reflect_typefor.gs.ts @@ -152,6 +152,12 @@ export async function main(): globalThis.Promise { await genericComposites() + // The fmt scanning interfaces are interface types. + let scanState = reflect.TypeFor(__goscriptStatic15()) + await $.println("fmt.ScanState:", (__goscriptAwait = (scanState !== null && (scanState as any).__isVarRef !== true && ((scanState as any).__goValue === undefined || (scanState as any).__goType?.charCodeAt(0) !== 42) ? (scanState as Exclude) : $.pointerValue>(scanState)).String(), __goscriptAwait instanceof Promise ? await __goscriptAwait : __goscriptAwait), reflect.Kind_String((__goscriptAwait = (scanState !== null && (scanState as any).__isVarRef !== true && ((scanState as any).__goValue === undefined || (scanState as any).__goType?.charCodeAt(0) !== 42) ? (scanState as Exclude) : $.pointerValue>(scanState)).Kind(), __goscriptAwait instanceof Promise ? await __goscriptAwait : __goscriptAwait)), (__goscriptAwait = (scanState !== null && (scanState as any).__isVarRef !== true && ((scanState as any).__goValue === undefined || (scanState as any).__goType?.charCodeAt(0) !== 42) ? (scanState as Exclude) : $.pointerValue>(scanState)).NumMethod(), __goscriptAwait instanceof Promise ? await __goscriptAwait : __goscriptAwait)) + let scanner = reflect.TypeFor(__goscriptStatic16()) + await $.println("fmt.Scanner:", (__goscriptAwait = (scanner !== null && (scanner as any).__isVarRef !== true && ((scanner as any).__goValue === undefined || (scanner as any).__goType?.charCodeAt(0) !== 42) ? (scanner as Exclude) : $.pointerValue>(scanner)).String(), __goscriptAwait instanceof Promise ? await __goscriptAwait : __goscriptAwait), reflect.Kind_String((__goscriptAwait = (scanner !== null && (scanner as any).__isVarRef !== true && ((scanner as any).__goValue === undefined || (scanner as any).__goType?.charCodeAt(0) !== 42) ? (scanner as Exclude) : $.pointerValue>(scanner)).Kind(), __goscriptAwait instanceof Promise ? await __goscriptAwait : __goscriptAwait)), (__goscriptAwait = (scanner !== null && (scanner as any).__isVarRef !== true && ((scanner as any).__goValue === undefined || (scanner as any).__goType?.charCodeAt(0) !== 42) ? (scanner as Exclude) : $.pointerValue>(scanner)).NumMethod(), __goscriptAwait instanceof Promise ? await __goscriptAwait : __goscriptAwait)) + await $.println("reflect_typefor test finished") } @@ -172,11 +178,11 @@ export async function describePointer(__typeArgs: $.GenericTypeArgs | undefined, } export async function genericComposites(): globalThis.Promise { - await describe(__goscriptStatic15(), $.arrayToSlice([new MyStruct(true, "a", 1)])) - await describe(__goscriptStatic16(), $.typedSliceOf(Float32Array, [1, 2, 3]) as $.Slice) - await describe(__goscriptStatic17(), $.arrayToSlice([])) - await describeMap(__goscriptStatic18(), $.makeMap([])) - await describePointer(__goscriptStatic15(), new MyStruct()) + await describe(__goscriptStatic17(), $.arrayToSlice([new MyStruct(true, "a", 1)])) + await describe(__goscriptStatic18(), $.typedSliceOf(Float32Array, [1, 2, 3]) as $.Slice) + await describe(__goscriptStatic19(), $.arrayToSlice([])) + await describeMap(__goscriptStatic20(), $.makeMap([])) + await describePointer(__goscriptStatic17(), new MyStruct()) } var __goscriptStatic0$value: any @@ -256,22 +262,32 @@ function __goscriptStatic14(): any { var __goscriptStatic15$value: any function __goscriptStatic15(): any { - return __goscriptStatic15$value ??= ({[$.genericTypeArgsMarker]: $.genericTypeArgsBrand, E: { type: "main.MyStruct", zero: () => new MyStruct(true) }}) + return __goscriptStatic15$value ??= ({[$.genericTypeArgsMarker]: $.genericTypeArgsBrand, T: { type: "fmt.ScanState", zero: () => null, methods: {Read: (receiver: any, ...args: any[]) => receiver.Read(...$.stripGenericTypeArgs(args)), ReadRune: (receiver: any) => receiver.ReadRune(), SkipSpace: (receiver: any) => receiver.SkipSpace(), Token: (receiver: any, ...args: any[]) => receiver.Token(...$.stripGenericTypeArgs(args)), UnreadRune: (receiver: any) => receiver.UnreadRune(), Width: (receiver: any) => receiver.Width()} }}) } var __goscriptStatic16$value: any function __goscriptStatic16(): any { - return __goscriptStatic16$value ??= ({[$.genericTypeArgsMarker]: $.genericTypeArgsBrand, E: { type: /* @__PURE__ */ $.basicType("float32"), zero: () => 0 }}) + return __goscriptStatic16$value ??= ({[$.genericTypeArgsMarker]: $.genericTypeArgsBrand, T: { type: "fmt.Scanner", zero: () => null, methods: {Scan: (receiver: any, ...args: any[]) => receiver.Scan(...$.stripGenericTypeArgs(args))} }}) } var __goscriptStatic17$value: any function __goscriptStatic17(): any { - return __goscriptStatic17$value ??= ({[$.genericTypeArgsMarker]: $.genericTypeArgsBrand, E: { type: /* @__PURE__ */ $.basicType("int"), zero: () => 0 }}) + return __goscriptStatic17$value ??= ({[$.genericTypeArgsMarker]: $.genericTypeArgsBrand, E: { type: "main.MyStruct", zero: () => new MyStruct(true) }}) } var __goscriptStatic18$value: any function __goscriptStatic18(): any { - return __goscriptStatic18$value ??= ({[$.genericTypeArgsMarker]: $.genericTypeArgsBrand, K: { type: /* @__PURE__ */ $.basicType("string"), zero: () => "" }, V: { type: "main.MyStruct", zero: () => new MyStruct(true) }}) + return __goscriptStatic18$value ??= ({[$.genericTypeArgsMarker]: $.genericTypeArgsBrand, E: { type: /* @__PURE__ */ $.basicType("float32"), zero: () => 0 }}) +} + +var __goscriptStatic19$value: any +function __goscriptStatic19(): any { + return __goscriptStatic19$value ??= ({[$.genericTypeArgsMarker]: $.genericTypeArgsBrand, E: { type: /* @__PURE__ */ $.basicType("int"), zero: () => 0 }}) +} + +var __goscriptStatic20$value: any +function __goscriptStatic20(): any { + return __goscriptStatic20$value ??= ({[$.genericTypeArgsMarker]: $.genericTypeArgsBrand, K: { type: /* @__PURE__ */ $.basicType("string"), zero: () => "" }, V: { type: "main.MyStruct", zero: () => new MyStruct(true) }}) } var __goscriptAwait: any From 00391e54876869ac76c219bb7f69759e4fc282f0 Mon Sep 17 00:00:00 2001 From: "Randall C. O'Reilly" Date: Sun, 25 Oct 2026 11:10:26 +0100 Subject: [PATCH 2/2] fix(gs): register sort.Interface and sync.Locker Like fmt.ScanState, sort.Interface and sync.Locker were declared only as TypeScript interfaces, so reflection saw them as invalid types and a reflect call passing a value for a sort.Interface parameter, as sort.Sort takes, failed. They are now registered with their Go methods. Signed-off-by: Randall C. O'Reilly --- gs/sort/sort.gs.ts | 18 ++++++++++ gs/sync/sync.ts | 9 +++++ tests/tests/reflect_typefor/expected.log | 2 ++ .../tests/reflect_typefor/reflect_typefor.go | 5 +++ .../reflect_typefor/reflect_typefor.gs.ts | 36 ++++++++++++++----- 5 files changed, 61 insertions(+), 9 deletions(-) diff --git a/gs/sort/sort.gs.ts b/gs/sort/sort.gs.ts index e9fa68610..9328c14ea 100644 --- a/gs/sort/sort.gs.ts +++ b/gs/sort/sort.gs.ts @@ -13,6 +13,24 @@ export interface Interface { Swap(i: number, j: number): void | Promise } +// Interface is registered with the runtime type system, so reflection sees +// an interface type with its methods. +$.registerInterfaceType('sort.Interface', null, [ + $.methodSignature('Len', [], [$.basicType('int')]), + $.methodSignature( + 'Less', + [ + ['i', $.basicType('int')], + ['j', $.basicType('int')], + ], + [$.basicType('bool')], + ), + $.methodSignature('Swap', [ + ['i', $.basicType('int')], + ['j', $.basicType('int')], + ]), +]) + // Helper type for slice metadata interface SliceMetadata { backing: T[] diff --git a/gs/sync/sync.ts b/gs/sync/sync.ts index 52f5b1c8d..d45aa2e82 100644 --- a/gs/sync/sync.ts +++ b/gs/sync/sync.ts @@ -5,7 +5,9 @@ import { comparableEqual, + methodSignature, pointerValue, + registerInterfaceType, runtimePanic, } from '@goscript/builtin/index.js' @@ -15,6 +17,13 @@ export interface Locker { Unlock(): void } +// Locker is registered with the runtime type system, so reflection sees an +// interface type with its methods. +registerInterfaceType('sync.Locker', null, [ + methodSignature('Lock'), + methodSignature('Unlock'), +]) + // Mutex is a mutual exclusion lock. export class Mutex implements Locker { private _locked: boolean = false diff --git a/tests/tests/reflect_typefor/expected.log b/tests/tests/reflect_typefor/expected.log index c1d81b7f4..53f440b35 100644 --- a/tests/tests/reflect_typefor/expected.log +++ b/tests/tests/reflect_typefor/expected.log @@ -23,4 +23,6 @@ map type: map[string]main.MyStruct pointer type: *main.MyStruct fmt.ScanState: fmt.ScanState interface 6 fmt.Scanner: fmt.Scanner interface 1 +interface: sort.Interface interface 3 +interface: sync.Locker interface 2 reflect_typefor test finished diff --git a/tests/tests/reflect_typefor/reflect_typefor.go b/tests/tests/reflect_typefor/reflect_typefor.go index d90200f1b..6a5e9169b 100644 --- a/tests/tests/reflect_typefor/reflect_typefor.go +++ b/tests/tests/reflect_typefor/reflect_typefor.go @@ -4,6 +4,8 @@ import ( "fmt" "io" "reflect" + "sort" + "sync" "time" ) @@ -79,6 +81,9 @@ func main() { println("fmt.ScanState:", scanState.String(), scanState.Kind().String(), scanState.NumMethod()) scanner := reflect.TypeFor[fmt.Scanner]() println("fmt.Scanner:", scanner.String(), scanner.Kind().String(), scanner.NumMethod()) + for _, t := range []reflect.Type{reflect.TypeFor[sort.Interface](), reflect.TypeFor[sync.Locker]()} { + println("interface:", t.String(), t.Kind().String(), t.NumMethod()) + } println("reflect_typefor test finished") } diff --git a/tests/tests/reflect_typefor/reflect_typefor.gs.ts b/tests/tests/reflect_typefor/reflect_typefor.gs.ts index fd199c64e..0fcd47fb0 100644 --- a/tests/tests/reflect_typefor/reflect_typefor.gs.ts +++ b/tests/tests/reflect_typefor/reflect_typefor.gs.ts @@ -5,10 +5,14 @@ import * as $ from "@goscript/builtin/index.js" import * as fmt from "@goscript/fmt/index.js" import * as io from "@goscript/io/index.js" import * as reflect from "@goscript/reflect/index.js" +import * as sort from "@goscript/sort/index.js" +import * as sync from "@goscript/sync/index.js" import * as time from "@goscript/time/index.js" import * as __goscript_other from "./other.gs.ts" import "@goscript/fmt/index.js" import "@goscript/io/index.js" +import "@goscript/sort/index.js" +import "@goscript/sync/index.js" export type MyInterface = { SomeMethod(): void @@ -157,6 +161,10 @@ export async function main(): globalThis.Promise { await $.println("fmt.ScanState:", (__goscriptAwait = (scanState !== null && (scanState as any).__isVarRef !== true && ((scanState as any).__goValue === undefined || (scanState as any).__goType?.charCodeAt(0) !== 42) ? (scanState as Exclude) : $.pointerValue>(scanState)).String(), __goscriptAwait instanceof Promise ? await __goscriptAwait : __goscriptAwait), reflect.Kind_String((__goscriptAwait = (scanState !== null && (scanState as any).__isVarRef !== true && ((scanState as any).__goValue === undefined || (scanState as any).__goType?.charCodeAt(0) !== 42) ? (scanState as Exclude) : $.pointerValue>(scanState)).Kind(), __goscriptAwait instanceof Promise ? await __goscriptAwait : __goscriptAwait)), (__goscriptAwait = (scanState !== null && (scanState as any).__isVarRef !== true && ((scanState as any).__goValue === undefined || (scanState as any).__goType?.charCodeAt(0) !== 42) ? (scanState as Exclude) : $.pointerValue>(scanState)).NumMethod(), __goscriptAwait instanceof Promise ? await __goscriptAwait : __goscriptAwait)) let scanner = reflect.TypeFor(__goscriptStatic16()) await $.println("fmt.Scanner:", (__goscriptAwait = (scanner !== null && (scanner as any).__isVarRef !== true && ((scanner as any).__goValue === undefined || (scanner as any).__goType?.charCodeAt(0) !== 42) ? (scanner as Exclude) : $.pointerValue>(scanner)).String(), __goscriptAwait instanceof Promise ? await __goscriptAwait : __goscriptAwait), reflect.Kind_String((__goscriptAwait = (scanner !== null && (scanner as any).__isVarRef !== true && ((scanner as any).__goValue === undefined || (scanner as any).__goType?.charCodeAt(0) !== 42) ? (scanner as Exclude) : $.pointerValue>(scanner)).Kind(), __goscriptAwait instanceof Promise ? await __goscriptAwait : __goscriptAwait)), (__goscriptAwait = (scanner !== null && (scanner as any).__isVarRef !== true && ((scanner as any).__goValue === undefined || (scanner as any).__goType?.charCodeAt(0) !== 42) ? (scanner as Exclude) : $.pointerValue>(scanner)).NumMethod(), __goscriptAwait instanceof Promise ? await __goscriptAwait : __goscriptAwait)) + for (let __goscriptRangeTarget0 = $.arrayToSlice([reflect.TypeFor(__goscriptStatic17()), reflect.TypeFor(__goscriptStatic18())]), __goscriptRangeWindow0 = $.sliceRange(__goscriptRangeTarget0), __rangeIndex = 0; __rangeIndex < __goscriptRangeWindow0.length; __rangeIndex++) { + let t = (__goscriptRangeWindow0.backing[__goscriptRangeWindow0.offset + __rangeIndex] as reflect.Type | null) + await $.println("interface:", (__goscriptAwait = (t !== null && (t as any).__isVarRef !== true && ((t as any).__goValue === undefined || (t as any).__goType?.charCodeAt(0) !== 42) ? (t as Exclude) : $.pointerValue>(t)).String(), __goscriptAwait instanceof Promise ? await __goscriptAwait : __goscriptAwait), reflect.Kind_String((__goscriptAwait = (t !== null && (t as any).__isVarRef !== true && ((t as any).__goValue === undefined || (t as any).__goType?.charCodeAt(0) !== 42) ? (t as Exclude) : $.pointerValue>(t)).Kind(), __goscriptAwait instanceof Promise ? await __goscriptAwait : __goscriptAwait)), (__goscriptAwait = (t !== null && (t as any).__isVarRef !== true && ((t as any).__goValue === undefined || (t as any).__goType?.charCodeAt(0) !== 42) ? (t as Exclude) : $.pointerValue>(t)).NumMethod(), __goscriptAwait instanceof Promise ? await __goscriptAwait : __goscriptAwait)) + } await $.println("reflect_typefor test finished") } @@ -178,11 +186,11 @@ export async function describePointer(__typeArgs: $.GenericTypeArgs | undefined, } export async function genericComposites(): globalThis.Promise { - await describe(__goscriptStatic17(), $.arrayToSlice([new MyStruct(true, "a", 1)])) - await describe(__goscriptStatic18(), $.typedSliceOf(Float32Array, [1, 2, 3]) as $.Slice) - await describe(__goscriptStatic19(), $.arrayToSlice([])) - await describeMap(__goscriptStatic20(), $.makeMap([])) - await describePointer(__goscriptStatic17(), new MyStruct()) + await describe(__goscriptStatic19(), $.arrayToSlice([new MyStruct(true, "a", 1)])) + await describe(__goscriptStatic20(), $.typedSliceOf(Float32Array, [1, 2, 3]) as $.Slice) + await describe(__goscriptStatic21(), $.arrayToSlice([])) + await describeMap(__goscriptStatic22(), $.makeMap([])) + await describePointer(__goscriptStatic19(), new MyStruct()) } var __goscriptStatic0$value: any @@ -272,22 +280,32 @@ function __goscriptStatic16(): any { var __goscriptStatic17$value: any function __goscriptStatic17(): any { - return __goscriptStatic17$value ??= ({[$.genericTypeArgsMarker]: $.genericTypeArgsBrand, E: { type: "main.MyStruct", zero: () => new MyStruct(true) }}) + return __goscriptStatic17$value ??= ({[$.genericTypeArgsMarker]: $.genericTypeArgsBrand, T: { type: "sort.Interface", zero: () => null, methods: {Len: (receiver: any) => receiver.Len(), Less: (receiver: any, ...args: any[]) => receiver.Less(...$.stripGenericTypeArgs(args)), Swap: (receiver: any, ...args: any[]) => receiver.Swap(...$.stripGenericTypeArgs(args))} }}) } var __goscriptStatic18$value: any function __goscriptStatic18(): any { - return __goscriptStatic18$value ??= ({[$.genericTypeArgsMarker]: $.genericTypeArgsBrand, E: { type: /* @__PURE__ */ $.basicType("float32"), zero: () => 0 }}) + return __goscriptStatic18$value ??= ({[$.genericTypeArgsMarker]: $.genericTypeArgsBrand, T: { type: "sync.Locker", zero: () => null, methods: {Lock: (receiver: any) => receiver.Lock(), Unlock: (receiver: any) => receiver.Unlock()} }}) } var __goscriptStatic19$value: any function __goscriptStatic19(): any { - return __goscriptStatic19$value ??= ({[$.genericTypeArgsMarker]: $.genericTypeArgsBrand, E: { type: /* @__PURE__ */ $.basicType("int"), zero: () => 0 }}) + return __goscriptStatic19$value ??= ({[$.genericTypeArgsMarker]: $.genericTypeArgsBrand, E: { type: "main.MyStruct", zero: () => new MyStruct(true) }}) } var __goscriptStatic20$value: any function __goscriptStatic20(): any { - return __goscriptStatic20$value ??= ({[$.genericTypeArgsMarker]: $.genericTypeArgsBrand, K: { type: /* @__PURE__ */ $.basicType("string"), zero: () => "" }, V: { type: "main.MyStruct", zero: () => new MyStruct(true) }}) + return __goscriptStatic20$value ??= ({[$.genericTypeArgsMarker]: $.genericTypeArgsBrand, E: { type: /* @__PURE__ */ $.basicType("float32"), zero: () => 0 }}) +} + +var __goscriptStatic21$value: any +function __goscriptStatic21(): any { + return __goscriptStatic21$value ??= ({[$.genericTypeArgsMarker]: $.genericTypeArgsBrand, E: { type: /* @__PURE__ */ $.basicType("int"), zero: () => 0 }}) +} + +var __goscriptStatic22$value: any +function __goscriptStatic22(): any { + return __goscriptStatic22$value ??= ({[$.genericTypeArgsMarker]: $.genericTypeArgsBrand, K: { type: /* @__PURE__ */ $.basicType("string"), zero: () => "" }, V: { type: "main.MyStruct", zero: () => new MyStruct(true) }}) } var __goscriptAwait: any