diff --git a/gs/reflect/meta.json b/gs/reflect/meta.json index fa0f564ca..d73080f07 100644 --- a/gs/reflect/meta.json +++ b/gs/reflect/meta.json @@ -7,7 +7,9 @@ "Value.Call": true, "Value.CallSlice": true, "Value.Recv": true, - "Value.Send": true + "Value.Send": true, + "Value.TryRecv": true, + "Value.TrySend": true }, "funcValueMakers": { "MakeFunc": true, diff --git a/gs/reflect/type.ts b/gs/reflect/type.ts index 9323739cb..a7c03eb2e 100644 --- a/gs/reflect/type.ts +++ b/gs/reflect/type.ts @@ -1983,6 +1983,184 @@ export class Value { } // SetZero sets v to be the zero value of v's type + // SetIterKey assigns to v the key of iter's current map entry, as + // v.Set(iter.Key()) does. + public SetIterKey(iter: MapIter | null): void { + this.Set(iter!.Key()) + } + + // SetIterValue assigns to v the value of iter's current map entry, as + // v.Set(iter.Value()) does. + public SetIterValue(iter: MapIter | null): void { + this.Set(iter!.Value()) + } + + // SetCap sets v's capacity to n. It panics if v's Kind is not Slice, or if + // n is smaller than the length or greater than the capacity of the slice. + public SetCap(n: number): void { + if (!this.CanSet()) { + throw new Error( + 'reflect: call of reflect.Value.SetCap on unaddressable value', + ) + } + if (this.Kind() !== Slice) { + throw new ValueError({ Kind: this.Kind(), Method: 'SetCap' }) + } + const value = this.currentValue() as $.Slice + if (n < $.len(value) || n > $.cap(value)) { + throw new Error('reflect: slice capacity out of range in SetCap') + } + this.storeValue($.goSlice(value, 0, $.len(value), n) as ReflectValue) + } + + // Clear clears the contents of a map or zeros the contents of a slice, as + // the clear builtin does. + public Clear(): void { + const k = this.Kind() + if (k === Map) { + ;(this.currentValue() as globalThis.Map | null)?.clear() + return + } + if (k !== Slice) { + throw new ValueError({ Kind: k, Method: 'Clear' }) + } + for (let i = 0; i < this.Len(); i++) { + this.Index(i).SetZero() + } + } + + // CanInt reports whether Int can be used without panicking. + public CanInt(): boolean { + const k = this.Kind() + return k === Int || k === Int8 || k === Int16 || k === Int32 || k === Int64 + } + + // CanUint reports whether Uint can be used without panicking. + public CanUint(): boolean { + const k = this.Kind() + return ( + k === Uint || + k === Uint8 || + k === Uint16 || + k === Uint32 || + k === Uint64 || + k === Uintptr + ) + } + + // CanFloat reports whether Float can be used without panicking. + public CanFloat(): boolean { + const k = this.Kind() + return k === Float32 || k === Float64 + } + + // CanComplex reports whether Complex can be used without panicking. + public CanComplex(): boolean { + const k = this.Kind() + return k === Complex64 || k === Complex128 + } + + // OverflowComplex reports whether the complex128 x cannot be represented by + // v's type. It panics if v's Kind is not Complex64 or Complex128. + public OverflowComplex(x: $.Complex): boolean { + const k = this.Kind() + if (k === Complex64) { + const f32max = 3.4028234663852886e38 + const overflows = (f: number) => isFinite(f) && Math.abs(f) > f32max + return overflows($.real(x)) || overflows($.imag(x)) + } + if (k === Complex128) { + return false + } + throw new ValueError({ Kind: k, Method: 'OverflowComplex' }) + } + + // Comparable reports whether v is comparable. For an interface, array or + // struct, it depends on the values it holds, as in Go. + public Comparable(): boolean { + switch (this.Kind()) { + case Invalid: + return false + case Interface: + return this.IsNil() || this.Elem().Comparable() + case Array: + case Struct: { + const n = this.Kind() === Array ? this.Len() : this.NumField() + for (let i = 0; i < n; i++) { + const elem = this.Kind() === Array ? this.Index(i) : this.Field(i) + if (!elem.Comparable()) { + return false + } + } + return true + } + default: + return this.Type().Comparable() + } + } + + // Fields returns an iterator over the fields of the struct v, yielding + // each field's StructField and Value. + public Fields(): iter.Seq2 { + return async ( + _yield: ((f: StructField, v: Value) => iter.YieldResult) | null, + ) => { + const t = this.Type() + for (let i = 0; i < this.NumField(); i++) { + if (!(await _yield!(t.Field(i), this.Field(i)))) { + return + } + } + } + } + + // Methods returns an iterator over the exported methods of v, yielding + // each method's Method and its value bound to v. + public Methods(): iter.Seq2 { + return async ( + _yield: ((m: Method, v: Value) => iter.YieldResult) | null, + ) => { + const t = this.Type() + for (let i = 0; i < this.NumMethod(); i++) { + if (!(await _yield!(t.Method(i), this.Method(i)))) { + return + } + } + } + } + + // TryRecv receives a value from the channel v if it can without blocking, + // reporting whether it did; ok is false for a closed channel. + public async TryRecv(): Promise<[Value, boolean]> { + const ch = this.recvChan() + if (ch === null || !ch.canReceiveNonBlocking()) { + return [new Value(null, invalidTypeInstance), false] + } + return this.Recv() + } + + // TrySend sends x on the channel v if it can without blocking, reporting + // whether it did. + public async TrySend(x: Value): Promise { + const ch = this.sendChan() + if (ch === null || !ch.canSendNonBlocking()) { + return false + } + await this.Send(x) + return true + } + + // InterfaceData is deprecated and needs the memory layout of an interface, + // which values do not have here. + public InterfaceData(): number[] { + throw new Error('reflect: InterfaceData is not supported') + } + + // SetPointer needs unsafe pointers, which values do not have here. + public SetPointer(_x: unknown): void { + throw new Error('reflect: SetPointer is not supported') + } + public SetZero(): void { if (!this.CanSet()) { throw new Error( diff --git a/tests/tests/reflect_value_methods_more/expected.log b/tests/tests/reflect_value_methods_more/expected.log new file mode 100644 index 000000000..5bab00f7c --- /dev/null +++ b/tests/tests/reflect_value_methods_more/expected.log @@ -0,0 +1,11 @@ +can: true false true true true false +overflow: true false +comparable: false true +clear: 0 0 2 4 +field: A int +field: B string +method: Sum 3 +iter: k 7 +trysend: true false +tryrecv: 3 true +empty: false false diff --git a/tests/tests/reflect_value_methods_more/index.ts b/tests/tests/reflect_value_methods_more/index.ts new file mode 100644 index 000000000..79d27c30e --- /dev/null +++ b/tests/tests/reflect_value_methods_more/index.ts @@ -0,0 +1 @@ +export { pair } from "./reflect_value_methods_more.gs.ts" diff --git a/tests/tests/reflect_value_methods_more/reflect_value_methods_more.go b/tests/tests/reflect_value_methods_more/reflect_value_methods_more.go new file mode 100644 index 000000000..606c5b341 --- /dev/null +++ b/tests/tests/reflect_value_methods_more/reflect_value_methods_more.go @@ -0,0 +1,57 @@ +package main + +import "reflect" + +type pair struct { + A int + B string +} + +func (p pair) Sum() int { return p.A + len(p.B) } + +func main() { + // The Can methods report the kinds that their accessors accept. + i, u, f, c := reflect.ValueOf(1), reflect.ValueOf(uint8(2)), reflect.ValueOf(1.5), reflect.ValueOf(1i) + println("can:", i.CanInt(), i.CanUint(), u.CanUint(), f.CanFloat(), c.CanComplex(), f.CanComplex()) + println("overflow:", reflect.ValueOf(complex64(0)).OverflowComplex(1e40), c.OverflowComplex(1e40)) + + // Comparable looks at the values an interface or struct holds. + var a any = []int{1} + println("comparable:", reflect.ValueOf(&a).Elem().Comparable(), reflect.ValueOf(pair{}).Comparable()) + + // Clear empties a map and zeros a slice; SetCap shrinks a slice's capacity. + m := map[string]int{"a": 1} + reflect.ValueOf(m).Clear() + s := make([]int, 2, 8) + s[0] = 5 + sv := reflect.ValueOf(&s).Elem() + sv.Clear() + sv.SetCap(4) + println("clear:", len(m), s[0], len(s), cap(s)) + + // Fields and Methods iterate with names and values. + p := pair{A: 1, B: "xy"} + for sf, v := range reflect.ValueOf(p).Fields() { + println("field:", sf.Name, v.Kind().String()) + } + for meth, v := range reflect.ValueOf(p).Methods() { + println("method:", meth.Name, v.Call(nil)[0].Int()) + } + + // SetIterKey and SetIterValue copy the current map entry. + k, e := reflect.New(reflect.TypeFor[string]()).Elem(), reflect.New(reflect.TypeFor[int]()).Elem() + it := reflect.ValueOf(map[string]int{"k": 7}).MapRange() + for it.Next() { + k.SetIterKey(it) + e.SetIterValue(it) + } + println("iter:", k.String(), e.Int()) + + // TrySend and TryRecv do not block. + ch := reflect.ValueOf(make(chan int, 1)) + println("trysend:", ch.TrySend(reflect.ValueOf(3)), ch.TrySend(reflect.ValueOf(4))) + x, ok := ch.TryRecv() + println("tryrecv:", x.Int(), ok) + x, ok = ch.TryRecv() + println("empty:", x.IsValid(), ok) +} diff --git a/tests/tests/reflect_value_methods_more/reflect_value_methods_more.gs.ts b/tests/tests/reflect_value_methods_more/reflect_value_methods_more.gs.ts new file mode 100644 index 000000000..2c129d1c3 --- /dev/null +++ b/tests/tests/reflect_value_methods_more/reflect_value_methods_more.gs.ts @@ -0,0 +1,122 @@ +// Generated file based on reflect_value_methods_more.go +// Updated when compliance tests are re-run, DO NOT EDIT! + +import * as $ from "@goscript/builtin/index.js" +import * as reflect from "@goscript/reflect/index.js" +import type * as iter from "@goscript/iter/index.js" + +export class pair { + public declare A: number + + public declare B: string + + constructor(__goscriptValue?: boolean, A$?: number, B$?: string) { + this.A = A$ ?? 0 + this.B = B$ ?? "" + ;(this as any)[$.structValueMarker] = __goscriptValue === true + } + + public clone(): pair { + return new pair(true, this.A, this.B) + } + + public __goscriptAssign(source: pair): this { + this.A = source.A + this.B = source.B + return this + } + + public Sum(): number { + const p = this; + return p.A + $.stringLen(p.B) + } + + static __typeInfo = $.registerStructType( + "main.pair", + () => new pair(), + () => [$.methodSignature("Sum", [], [/* @__PURE__ */ $.basicType("int")])], + pair, + () => [/* @__PURE__ */ $.structField("A", /* @__PURE__ */ $.basicType("int"), [0], 0, true), /* @__PURE__ */ $.structField("B", /* @__PURE__ */ $.basicType("string"), [1], 8, true)] + ) +} + +export async function main(): globalThis.Promise { + // The Can methods report the kinds that their accessors accept. + let i = $.markAsStructValue($.cloneStructValue(reflect.ValueOf($.basicInterfaceValue(1, "int")))) + let u = $.markAsStructValue($.cloneStructValue(reflect.ValueOf($.basicInterfaceValue(2, "uint8")))) + let f = $.markAsStructValue($.cloneStructValue(reflect.ValueOf($.basicInterfaceValue(1.5, "float64")))) + let c = $.markAsStructValue($.cloneStructValue(reflect.ValueOf($.basicInterfaceValue($.complex(0, 1), "complex128")))) + await $.println("can:", $.markAsStructValue($.cloneStructValue(i)).CanInt(), $.markAsStructValue($.cloneStructValue(i)).CanUint(), $.markAsStructValue($.cloneStructValue(u)).CanUint(), $.markAsStructValue($.cloneStructValue(f)).CanFloat(), $.markAsStructValue($.cloneStructValue(c)).CanComplex(), $.markAsStructValue($.cloneStructValue(f)).CanComplex()) + await $.println("overflow:", $.markAsStructValue($.cloneStructValue(reflect.ValueOf($.basicInterfaceValue($.complex(0, 0), "complex64")))).OverflowComplex($.complex(1e+40, 0)), $.markAsStructValue($.cloneStructValue(c)).OverflowComplex($.complex(1e+40, 0))) + + // Comparable looks at the values an interface or struct holds. + let a: $.VarRef = $.varRef($.interfaceValue($.arrayToSlice([1]), "[]int", /* @__PURE__ */ $.sliceType(/* @__PURE__ */ $.basicType("int")))) + await $.println("comparable:", $.markAsStructValue($.cloneStructValue($.markAsStructValue($.cloneStructValue(reflect.ValueOf($.interfaceValue(a, "*interface{}", /* @__PURE__ */ $.pointerType({ kind: $.TypeKind.Interface, methods: [] }))))).Elem())).Comparable(), $.markAsStructValue($.cloneStructValue(reflect.ValueOf($.structInterfaceValue(new pair(true), "main.pair", "main.pair")))).Comparable()) + + // Clear empties a map and zeros a slice; SetCap shrinks a slice's capacity. + let m = $.makeMap([["a", 1]]) + $.markAsStructValue($.cloneStructValue(reflect.ValueOf($.interfaceValue(m, "map[string]int", /* @__PURE__ */ $.mapType(/* @__PURE__ */ $.basicType("string"), /* @__PURE__ */ $.basicType("int")))))).Clear() + let s: $.VarRef<$.Slice> = $.varRef($.makeSlice(2, 8, "number")) + $.sliceElems(s.value!)[$.sliceSlot(s.value!, 0)] = 5 + let sv = $.markAsStructValue($.cloneStructValue($.markAsStructValue($.cloneStructValue(reflect.ValueOf($.interfaceValue(s, "*[]int", /* @__PURE__ */ $.pointerType(/* @__PURE__ */ $.sliceType(/* @__PURE__ */ $.basicType("int"))))))).Elem())) + $.markAsStructValue($.cloneStructValue(sv)).Clear() + $.markAsStructValue($.cloneStructValue(sv)).SetCap(4) + await $.println("clear:", $.len(m), $.arrayIndex(s.value!, 0), $.sliceLen(s.value), $.cap(s.value)) + + // Fields and Methods iterate with names and values. + let p = new pair(true, 1, "xy") + let __goscriptRangeReturn0 = false + ;await (async () => { + await $.markAsStructValue($.cloneStructValue(reflect.ValueOf($.structInterfaceValue((p).clone(), "main.pair", "main.pair")))).Fields()!(async (sf, v) => { + await $.println("field:", sf.Name, reflect.Kind_String($.markAsStructValue($.cloneStructValue(v)).Kind())) + return true + }) + })() + if (__goscriptRangeReturn0) { + return + } + let __goscriptRangeReturn1 = false + ;await (async () => { + await $.markAsStructValue($.cloneStructValue(reflect.ValueOf($.structInterfaceValue((p).clone(), "main.pair", "main.pair")))).Methods()!(async (meth, v) => { + await $.println("method:", meth.Name, $.markAsStructValue($.cloneStructValue($.arrayIndex((await $.markAsStructValue($.cloneStructValue(v)).Call(null))!, 0))).Int()) + return true + }) + })() + if (__goscriptRangeReturn1) { + return + } + + // SetIterKey and SetIterValue copy the current map entry. + let k = $.markAsStructValue($.cloneStructValue($.markAsStructValue($.cloneStructValue(reflect.New($.pointerValueOrNil(reflect.TypeFor(__goscriptStatic0()))!))).Elem())) + let e = $.markAsStructValue($.cloneStructValue($.markAsStructValue($.cloneStructValue(reflect.New($.pointerValueOrNil(reflect.TypeFor(__goscriptStatic1()))!))).Elem())) + let it = $.markAsStructValue($.cloneStructValue(reflect.ValueOf($.interfaceValue($.makeMap([["k", 7]]), "map[string]int", /* @__PURE__ */ $.mapType(/* @__PURE__ */ $.basicType("string"), /* @__PURE__ */ $.basicType("int")))))).MapRange() + while ((it !== null && (it as any).__isVarRef !== true && (it as any).__goValue === undefined ? ((it as any) as reflect.MapIter).Next() : reflect.MapIter.prototype.Next.call($.pointerValue(it)))) { + $.markAsStructValue($.cloneStructValue(k)).SetIterKey(it) + $.markAsStructValue($.cloneStructValue(e)).SetIterValue(it) + } + await $.println("iter:", $.markAsStructValue($.cloneStructValue(k)).String(), $.markAsStructValue($.cloneStructValue(e)).Int()) + + // TrySend and TryRecv do not block. + let ch = $.markAsStructValue($.cloneStructValue(reflect.ValueOf($.interfaceValue($.makeChannel(1, 0, "both"), "chan int", /* @__PURE__ */ $.channelType(/* @__PURE__ */ $.basicType("int"), "both"))))) + await $.println("trysend:", await $.markAsStructValue($.cloneStructValue(ch)).TrySend($.markAsStructValue($.cloneStructValue(reflect.ValueOf($.basicInterfaceValue(3, "int"))))), await $.markAsStructValue($.cloneStructValue(ch)).TrySend($.markAsStructValue($.cloneStructValue(reflect.ValueOf($.basicInterfaceValue(4, "int")))))) + let [x, ok] = await $.markAsStructValue($.cloneStructValue(ch)).TryRecv() + await $.println("tryrecv:", $.markAsStructValue($.cloneStructValue(x)).Int(), ok) + let __goscriptTuple0: any = await $.markAsStructValue($.cloneStructValue(ch)).TryRecv() + x = $.assignStruct(x, __goscriptTuple0[0]) + ok = __goscriptTuple0[1] + await $.println("empty:", $.markAsStructValue($.cloneStructValue(x)).IsValid(), ok) +} + +var __goscriptStatic0$value: any +function __goscriptStatic0(): any { + return __goscriptStatic0$value ??= ({[$.genericTypeArgsMarker]: $.genericTypeArgsBrand, T: { type: /* @__PURE__ */ $.basicType("string"), zero: () => "" }}) +} + +var __goscriptStatic1$value: any +function __goscriptStatic1(): any { + return __goscriptStatic1$value ??= ({[$.genericTypeArgsMarker]: $.genericTypeArgsBrand, T: { type: /* @__PURE__ */ $.basicType("int"), zero: () => 0 }}) +} + +if ($.isMainScript(import.meta)) { + await main() +} diff --git a/tests/tests/reflect_value_methods_more/tsconfig.json b/tests/tests/reflect_value_methods_more/tsconfig.json new file mode 100644 index 000000000..475df6348 --- /dev/null +++ b/tests/tests/reflect_value_methods_more/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "paths": { + "*": [ + "./*" + ], + "@go/github.com/s4wave/goscript/*": [ + "../../../*" + ], + "@goscript/*": [ + "../../../tests/tests/reflect_value_methods_more/run/output/@goscript/*", + "../../../gs/*", + "../../../tests/deps/*" + ], + "@goscript/github.com/s4wave/goscript/tests/tests/reflect_value_methods_more/*": [ + "./*" + ] + } + }, + "extends": "../../../tests/tsconfig.base.json", + "include": [ + "index.ts", + "reflect_value_methods_more.gs.ts" + ] +}