Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion gs/reflect/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
178 changes: 178 additions & 0 deletions gs/reflect/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<unknown, unknown> | 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<unknown, unknown> | 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<unknown>
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<unknown, unknown> | 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<StructField, Value> {
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<Method, Value> {
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<boolean> {
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(
Expand Down
11 changes: 11 additions & 0 deletions tests/tests/reflect_value_methods_more/expected.log
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions tests/tests/reflect_value_methods_more/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { pair } from "./reflect_value_methods_more.gs.ts"
Original file line number Diff line number Diff line change
@@ -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)
}
Original file line number Diff line number Diff line change
@@ -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<void> {
// 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<any> = $.varRef($.interfaceValue($.arrayToSlice<number>([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<string, number>([["a", 1]])
$.markAsStructValue($.cloneStructValue(reflect.ValueOf($.interfaceValue(m, "map[string]int", /* @__PURE__ */ $.mapType(/* @__PURE__ */ $.basicType("string"), /* @__PURE__ */ $.basicType("int")))))).Clear()
let s: $.VarRef<$.Slice<number>> = $.varRef($.makeSlice<number>(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<string, number>([["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<reflect.MapIter>(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<number>(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()
}
Loading
Loading