goscript 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/cmd/goscript-wasm/main.go +38 -6
  2. package/compiler/diagnostic.go +104 -12
  3. package/compiler/diagnostic_test.go +106 -0
  4. package/compiler/gotest/runner.go +1 -17
  5. package/compiler/gotest/runner_test.go +20 -0
  6. package/compiler/index.test.ts +23 -0
  7. package/compiler/lowered-program.go +9 -7
  8. package/compiler/lowering.go +359 -72
  9. package/compiler/lowering_bench_test.go +1 -0
  10. package/compiler/lowering_internal_test.go +18 -0
  11. package/compiler/protobuf-ts-binding.go +65 -12
  12. package/compiler/protobuf-ts-binding_test.go +230 -0
  13. package/compiler/runtime-contract.go +4 -0
  14. package/compiler/runtime-contract_test.go +2 -0
  15. package/compiler/service.go +1 -0
  16. package/compiler/skeleton_test.go +56 -2
  17. package/compiler/wasm/compile_test.go +37 -4
  18. package/compiler/wasm-api.go +57 -7
  19. package/dist/gs/builtin/hostio.js +5 -0
  20. package/dist/gs/builtin/hostio.js.map +1 -1
  21. package/dist/gs/builtin/slice.d.ts +11 -1
  22. package/dist/gs/builtin/slice.js +158 -2
  23. package/dist/gs/builtin/slice.js.map +1 -1
  24. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.d.ts +1 -0
  25. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.js +30 -5
  26. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.js.map +1 -1
  27. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/json/index.d.ts +1 -0
  28. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/json/index.js +17 -11
  29. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/json/index.js.map +1 -1
  30. package/dist/gs/internal/byteorder/index.js +2 -2
  31. package/dist/gs/internal/byteorder/index.js.map +1 -1
  32. package/dist/gs/reflect/type.js +57 -0
  33. package/dist/gs/reflect/type.js.map +1 -1
  34. package/dist/gs/sync/atomic/doc_64.gs.js +7 -6
  35. package/dist/gs/sync/atomic/doc_64.gs.js.map +1 -1
  36. package/gs/builtin/hostio.test.ts +16 -0
  37. package/gs/builtin/hostio.ts +7 -0
  38. package/gs/builtin/runtime-contract.test.ts +28 -0
  39. package/gs/builtin/slice.ts +225 -20
  40. package/gs/github.com/aperturerobotics/protobuf-go-lite/index.test.ts +162 -0
  41. package/gs/github.com/aperturerobotics/protobuf-go-lite/index.ts +41 -5
  42. package/gs/github.com/aperturerobotics/protobuf-go-lite/json/index.test.ts +18 -0
  43. package/gs/github.com/aperturerobotics/protobuf-go-lite/json/index.ts +17 -11
  44. package/gs/internal/byteorder/index.test.ts +2 -2
  45. package/gs/internal/byteorder/index.ts +2 -2
  46. package/gs/reflect/type.ts +64 -0
  47. package/gs/reflect/typefor.test.ts +21 -1
  48. package/gs/sync/atomic/doc_64.gs.ts +6 -7
  49. package/gs/sync/atomic/doc_64.test.ts +43 -0
  50. package/package.json +1 -1
@@ -345,6 +345,7 @@ export function EqualVTMapImplicit<K, V extends EqualVT<V>>(
345
345
  }
346
346
 
347
347
  type BoundMessageType = {
348
+ typeName?: string
348
349
  fields?: { list(): readonly BoundFieldInfo[] }
349
350
  clone?: (value: any) => any
350
351
  equals?: (a: any, b: any) => boolean
@@ -371,6 +372,8 @@ type BoundMessageCtor<T = any> = {
371
372
  __protobufTypeScriptMessage?: BoundMessageType
372
373
  }
373
374
 
375
+ const protobufScalarTypeBytes = 12
376
+
374
377
  function boundMessageType(ctor: BoundMessageCtor): BoundMessageType {
375
378
  const messageType = ctor.__protobufTypeScriptMessage
376
379
  if (messageType == null) {
@@ -401,8 +404,16 @@ function boundFieldValue(source: any, field: BoundFieldInfo): any {
401
404
  return source[boundFieldGoName(field)]
402
405
  }
403
406
 
404
- function toTypeScriptScalarValue(value: any): any {
407
+ function toTypeScriptScalarValue(value: any, field?: BoundFieldInfo): any {
405
408
  const unwrapped = $.pointerValueOrNil(value)
409
+ if (
410
+ field?.kind === 'scalar' &&
411
+ field.T === protobufScalarTypeBytes &&
412
+ unwrapped != null &&
413
+ !(unwrapped instanceof Uint8Array)
414
+ ) {
415
+ return Uint8Array.from($.asArray(unwrapped))
416
+ }
406
417
  if (unwrapped instanceof Uint8Array) {
407
418
  return unwrapped
408
419
  }
@@ -440,7 +451,7 @@ function toTypeScriptFieldValue(
440
451
  const messageType = resolveBoundMessageType(field.T)
441
452
  return toTypeScriptMessage(value, messageType, ctor)
442
453
  }
443
- return toTypeScriptScalarValue(value)
454
+ return toTypeScriptScalarValue(value, field)
444
455
  }
445
456
 
446
457
  function toTypeScriptMessage(
@@ -520,6 +531,23 @@ function fromTypeScriptFieldValue(
520
531
  return fromTypeScriptScalarValue(value)
521
532
  }
522
533
 
534
+ function fromTypeScriptWellKnownMessage(
535
+ value: any,
536
+ ctor: BoundMessageCtor,
537
+ messageType?: BoundMessageType,
538
+ ): any {
539
+ if (
540
+ messageType?.typeName === 'google.protobuf.Timestamp' &&
541
+ value instanceof Date
542
+ ) {
543
+ const ms = value.getTime()
544
+ const seconds = Math.floor(ms / 1000)
545
+ const nanos = Math.trunc((ms - seconds * 1000) * 1000000)
546
+ return new ctor({ Seconds: seconds, Nanos: nanos })
547
+ }
548
+ return undefined
549
+ }
550
+
523
551
  function fromTypeScriptMessage(
524
552
  value: any,
525
553
  ctor: BoundMessageCtor,
@@ -528,6 +556,10 @@ function fromTypeScriptMessage(
528
556
  if (value == null) {
529
557
  return null
530
558
  }
559
+ const wellKnown = fromTypeScriptWellKnownMessage(value, ctor, messageType)
560
+ if (wellKnown !== undefined) {
561
+ return wellKnown
562
+ }
531
563
  const out = new ctor()
532
564
  const fields = (messageType ?? boundMessageType(ctor)).fields?.list()
533
565
  if (fields == null) {
@@ -579,14 +611,17 @@ export function MarshalBoundMessageVT<T>(
579
611
  ctor: BoundMessageCtor<T>,
580
612
  value: T | $.VarRef<T> | null,
581
613
  ): [$.Slice<number>, $.GoError] {
614
+ let typeName = ctor.name
582
615
  try {
583
616
  const messageType = boundMessageType(ctor)
617
+ typeName = messageType.typeName ?? typeName
584
618
  return [
585
619
  messageType.toBinary(toTypeScriptMessage(value, messageType, ctor)),
586
620
  null,
587
621
  ]
588
622
  } catch (err) {
589
- return [null, $.toGoError(err as Error)]
623
+ const msg = err instanceof Error ? err.message : String(err)
624
+ return [null, $.toGoError(new Error(`marshal ${typeName}: ${msg}`))]
590
625
  }
591
626
  }
592
627
 
@@ -599,12 +634,13 @@ export function MarshalBoundMessageToSizedBufferVT<T>(
599
634
  if (err != null) {
600
635
  return [0, err]
601
636
  }
602
- const offset = $.len(dAtA) - $.len(bytes)
637
+ const byteCount = $.len(bytes)
638
+ const offset = $.len(dAtA) - byteCount
603
639
  const src = $.asArray(bytes)
604
640
  for (let i = 0; i < src.length; i++) {
605
641
  ;(dAtA as any)[offset + i] = src[i]
606
642
  }
607
- return [offset, null]
643
+ return [byteCount, null]
608
644
  }
609
645
 
610
646
  export function SizeBoundMessageVT<T>(
@@ -113,6 +113,24 @@ describe('protobuf-go-lite/json override', () => {
113
113
  ])
114
114
  })
115
115
 
116
+ it('matches jsoniter value types while reading object fields', () => {
117
+ const state = NewUnmarshalState(
118
+ jsonBytes('{"id":"step","config":{"kind":"inline"}}'),
119
+ DefaultUnmarshalerConfig,
120
+ )
121
+
122
+ expect(state?.ReadObjectField()).toBe('id')
123
+ expect(state?.WhatIsNext()).toBe(1)
124
+ expect(state?.ReadString()).toBe('step')
125
+ expect(state?.ReadObjectField()).toBe('config')
126
+ expect(state?.WhatIsNext()).toBe(6)
127
+ expect(
128
+ new TextDecoder().decode(new Uint8Array(state?.SkipAndReturnBytes())),
129
+ ).toBe('{"kind":"inline"}')
130
+ expect(state?.ReadObjectField()).toBe('')
131
+ expect(state?.Err()).toBeNull()
132
+ })
133
+
116
134
  it('tracks unmarshaled field masks through nested fields', () => {
117
135
  const state = NewUnmarshalState(jsonBytes('{}'), DefaultUnmarshalerConfig)
118
136
  state?.AddField('top')
@@ -423,6 +423,7 @@ export class UnmarshalState {
423
423
  private path: string[]
424
424
  private objectEntries: Array<[string, unknown]> | null = null
425
425
  private objectIndex = 0
426
+ private objectValue: unknown = undefined
426
427
 
427
428
  constructor(
428
429
  init?: Partial<{
@@ -612,16 +613,21 @@ export class UnmarshalState {
612
613
  }
613
614
 
614
615
  public ReadObjectField(): string {
615
- const record = recordValue(this.value)
616
- if (record == null) {
617
- this.SetErrorf('expected JSON object')
618
- return ''
619
- }
620
616
  if (this.objectEntries == null) {
617
+ const record = recordValue(this.value)
618
+ if (record == null) {
619
+ this.SetErrorf('expected JSON object')
620
+ return ''
621
+ }
621
622
  this.objectEntries = Object.entries(record)
622
623
  this.objectIndex = 0
624
+ this.objectValue = this.value
623
625
  }
624
626
  if (this.objectIndex >= this.objectEntries.length) {
627
+ this.value = this.objectValue
628
+ this.objectEntries = null
629
+ this.objectIndex = 0
630
+ this.objectValue = undefined
625
631
  return ''
626
632
  }
627
633
  const [key, value] = this.objectEntries[this.objectIndex]
@@ -747,22 +753,22 @@ export class UnmarshalState {
747
753
 
748
754
  public WhatIsNext(): number {
749
755
  if (this.value === null) {
750
- return 0
756
+ return 3
751
757
  }
752
758
  if (Array.isArray(this.value)) {
753
- return 1
759
+ return 5
754
760
  }
755
761
  if (typeof this.value === 'object') {
756
- return 2
762
+ return 6
757
763
  }
758
764
  if (typeof this.value === 'string') {
759
- return 3
765
+ return 1
760
766
  }
761
767
  if (typeof this.value === 'number') {
762
- return 4
768
+ return 2
763
769
  }
764
770
  if (typeof this.value === 'boolean') {
765
- return 5
771
+ return 4
766
772
  }
767
773
  return 0
768
774
  }
@@ -16,7 +16,7 @@ describe('internal/byteorder uint64', () => {
16
16
  BEPutUint64(bytes, 0x0102030405060708n)
17
17
 
18
18
  expect(Array.from(bytes)).toEqual([1, 2, 3, 4, 5, 6, 7, 8])
19
- expect(BEUint64(bytes)).toBe(Number(0x0102030405060708n))
19
+ expect(BEUint64(bytes)).toBe(0x0102030405060708n)
20
20
  })
21
21
 
22
22
  test('reads and writes little-endian bigint values', () => {
@@ -25,7 +25,7 @@ describe('internal/byteorder uint64', () => {
25
25
  LEPutUint64(bytes, 0x0102030405060708n)
26
26
 
27
27
  expect(Array.from(bytes)).toEqual([8, 7, 6, 5, 4, 3, 2, 1])
28
- expect(LEUint64(bytes)).toBe(Number(0x0102030405060708n))
28
+ expect(LEUint64(bytes)).toBe(0x0102030405060708n)
29
29
  })
30
30
 
31
31
  test('appends uint64 values', () => {
@@ -13,7 +13,7 @@ export function BEUint32(b: $.Bytes): number {
13
13
  }
14
14
 
15
15
  export function BEUint64(b: $.Bytes): number {
16
- return Number(
16
+ return $.uint(
17
17
  (BigInt(b![0]) << 56n) |
18
18
  (BigInt(b![1]) << 48n) |
19
19
  (BigInt(b![2]) << 40n) |
@@ -34,7 +34,7 @@ export function LEUint32(b: $.Bytes): number {
34
34
  }
35
35
 
36
36
  export function LEUint64(b: $.Bytes): number {
37
- return Number(
37
+ return $.uint(
38
38
  BigInt(b![0]) |
39
39
  (BigInt(b![1]) << 8n) |
40
40
  (BigInt(b![2]) << 16n) |
@@ -3326,6 +3326,60 @@ function basicTypeFromName(name: string, typeName = ''): BasicType {
3326
3326
  }
3327
3327
  }
3328
3328
 
3329
+ function typeFromGoTypeName(typeName: string): Type | null {
3330
+ const trimmed = typeName.trim()
3331
+ if (trimmed === '') return null
3332
+
3333
+ if (trimmed.startsWith('<-chan ')) {
3334
+ return new ChannelType(typeFromGoTypeName(trimmed.slice(7)) ?? anyType(), RecvDir)
3335
+ }
3336
+ if (trimmed.startsWith('chan<- ')) {
3337
+ return new ChannelType(typeFromGoTypeName(trimmed.slice(7)) ?? anyType(), SendDir)
3338
+ }
3339
+ if (trimmed.startsWith('chan ')) {
3340
+ return new ChannelType(typeFromGoTypeName(trimmed.slice(5)) ?? anyType(), BothDir)
3341
+ }
3342
+ if (trimmed.startsWith('[]')) {
3343
+ return new SliceType(typeFromGoTypeName(trimmed.slice(2)) ?? anyType())
3344
+ }
3345
+ if (trimmed.startsWith('*')) {
3346
+ return new PointerType(typeFromGoTypeName(trimmed.slice(1)) ?? anyType())
3347
+ }
3348
+ if (trimmed === 'struct{}' || trimmed === 'struct {}') {
3349
+ return new StructType('', [], '', 'struct {}')
3350
+ }
3351
+ if (trimmed === 'interface{}' || trimmed === 'any') {
3352
+ return anyType()
3353
+ }
3354
+ if (trimmed === 'error') {
3355
+ return new InterfaceType('error', 'error')
3356
+ }
3357
+
3358
+ const registered = builtinGetTypeByName(trimmed)
3359
+ if (registered) {
3360
+ return typeFromTypeInfo(registered)
3361
+ }
3362
+
3363
+ const basic = basicTypeFromName(trimmed)
3364
+ if (basic.Kind() !== Invalid) {
3365
+ return basic
3366
+ }
3367
+
3368
+ return null
3369
+ }
3370
+
3371
+ function channelTypeFromGoTypeName(typeName: string): Type | null {
3372
+ const typ = typeFromGoTypeName(typeName)
3373
+ if (typ?.Kind() === Chan) {
3374
+ return typ
3375
+ }
3376
+ return null
3377
+ }
3378
+
3379
+ function anyType(): Type {
3380
+ return new BasicType(Interface, 'interface{}', 16)
3381
+ }
3382
+
3329
3383
  function structFieldsFromTypeInfo(
3330
3384
  ti: $.StructTypeInfo,
3331
3385
  seen = new Set<string>(),
@@ -3743,6 +3797,16 @@ function getTypeOf(value: ReflectValue): Type {
3743
3797
  )
3744
3798
  }
3745
3799
 
3800
+ if ('__goType' in value) {
3801
+ const goType = (value as { __goType?: unknown }).__goType
3802
+ if (typeof goType === 'string') {
3803
+ const channelType = channelTypeFromGoTypeName(goType)
3804
+ if (channelType) {
3805
+ return channelType
3806
+ }
3807
+ }
3808
+ }
3809
+
3746
3810
  if (
3747
3811
  'real' in value &&
3748
3812
  'imag' in value &&
@@ -1,5 +1,7 @@
1
1
  import { describe, it, expect } from 'vitest'
2
2
  import {
3
+ interfaceValue,
4
+ makeChannel,
3
5
  makeMap,
4
6
  mapGet,
5
7
  mapSet,
@@ -11,7 +13,9 @@ import {
11
13
  varRef,
12
14
  } from '../builtin/index.js'
13
15
  import { StructField } from './types.js'
16
+ import { SelectCase, SelectRecv } from './types.js'
14
17
  import {
18
+ Chan,
15
19
  Int,
16
20
  Ptr,
17
21
  Struct,
@@ -21,7 +25,7 @@ import {
21
25
  Uint64,
22
26
  ValueOf,
23
27
  } from './type.js'
24
- import { Indirect, New, Zero } from './value.js'
28
+ import { Indirect, New, Select, Zero } from './value.js'
25
29
 
26
30
  describe('TypeFor', () => {
27
31
  it('exposes StructField PkgPath and exported semantics', () => {
@@ -184,6 +188,22 @@ describe('TypeFor', () => {
184
188
  expect(target.value).toBe(15)
185
189
  })
186
190
 
191
+ it('preserves channel type metadata on interface boxes', () => {
192
+ const ch = makeChannel<{}>(0, {}, 'both')
193
+ const chanValue = ValueOf(interfaceValue(ch, '<-chan struct{}'))
194
+
195
+ expect(chanValue.Type().Kind()).toBe(Chan)
196
+ expect(chanValue.Type().String()).toBe('<-chan struct {}')
197
+ expect(chanValue.Type().Elem().Kind()).toBe(Struct)
198
+
199
+ const [chosen, recv, ok] = Select([
200
+ new SelectCase({ Dir: SelectRecv, Chan: chanValue }),
201
+ ])
202
+ expect(chosen).toBe(0)
203
+ expect(ok).toBe(true)
204
+ expect(recv.Type().String()).toBe('struct {}')
205
+ })
206
+
187
207
  it('formats literal interface methods from type metadata', () => {
188
208
  const ifaceType = TypeFor({
189
209
  T: {
@@ -59,7 +59,7 @@ export function CompareAndSwapUint64(addr: $.VarRef<number> | null, old: number,
59
59
  //go:noescape
60
60
  export function AddInt64(addr: $.VarRef<number> | null, delta: number): number {
61
61
  if (!addr) return 0;
62
- addr.value = addr.value + delta;
62
+ addr.value = $.int64Add(addr.value, delta);
63
63
  return addr.value;
64
64
  }
65
65
 
@@ -72,7 +72,7 @@ export function AddInt64(addr: $.VarRef<number> | null, delta: number): number {
72
72
  //go:noescape
73
73
  export function AddUint64(addr: $.VarRef<number> | null, delta: number): number {
74
74
  if (!addr) return 0;
75
- addr.value = addr.value + delta;
75
+ addr.value = $.uint64Add(addr.value, delta);
76
76
  return addr.value;
77
77
  }
78
78
 
@@ -84,7 +84,7 @@ export function AddUint64(addr: $.VarRef<number> | null, delta: number): number
84
84
  export function AndInt64(addr: $.VarRef<number> | null, mask: number): number {
85
85
  if (!addr) return 0;
86
86
  let old = addr.value;
87
- addr.value = addr.value & mask;
87
+ addr.value = $.int64And(addr.value, mask);
88
88
  return old;
89
89
  }
90
90
 
@@ -96,7 +96,7 @@ export function AndInt64(addr: $.VarRef<number> | null, mask: number): number {
96
96
  export function AndUint64(addr: $.VarRef<number> | null, mask: number): number {
97
97
  if (!addr) return 0;
98
98
  let old = addr.value;
99
- addr.value = addr.value & mask;
99
+ addr.value = $.uint64And(addr.value, mask);
100
100
  return old;
101
101
  }
102
102
 
@@ -108,7 +108,7 @@ export function AndUint64(addr: $.VarRef<number> | null, mask: number): number {
108
108
  export function OrInt64(addr: $.VarRef<number> | null, mask: number): number {
109
109
  if (!addr) return 0;
110
110
  let old = addr.value;
111
- addr.value = addr.value | mask;
111
+ addr.value = $.int64Or(addr.value, mask);
112
112
  return old;
113
113
  }
114
114
 
@@ -120,7 +120,7 @@ export function OrInt64(addr: $.VarRef<number> | null, mask: number): number {
120
120
  export function OrUint64(addr: $.VarRef<number> | null, mask: number): number {
121
121
  if (!addr) return 0;
122
122
  let old = addr.value;
123
- addr.value = addr.value | mask;
123
+ addr.value = $.uint64Or(addr.value, mask);
124
124
  return old;
125
125
  }
126
126
 
@@ -165,4 +165,3 @@ export function StoreUint64(addr: $.VarRef<number> | null, val: number): void {
165
165
  addr.value = val;
166
166
  }
167
167
  }
168
-
@@ -0,0 +1,43 @@
1
+ import { describe, expect, test } from 'vitest'
2
+
3
+ import * as $ from '../../builtin/index.js'
4
+ import {
5
+ AddInt64,
6
+ AddUint64,
7
+ AndUint64,
8
+ OrUint64,
9
+ } from './doc_64.gs.js'
10
+
11
+ function asBigInt(value: number): bigint {
12
+ return typeof value === 'bigint' ? value : BigInt(value)
13
+ }
14
+
15
+ describe('sync/atomic 64-bit operations', () => {
16
+ test('adds uint64 values without mixing number and bigint arithmetic', () => {
17
+ const value = $.varRef($.uint('18446744073709551614', 64))
18
+
19
+ expect(AddUint64(value, $.uint(2, 64))).toBe(0)
20
+ expect(value.value).toBe(0)
21
+ })
22
+
23
+ test('preserves high uint64 bits for bitwise operations', () => {
24
+ const value = $.varRef($.uint('9223372036854775808', 64))
25
+
26
+ expect(asBigInt(OrUint64(value, $.uint(1, 64)))).toBe(
27
+ 9223372036854775808n,
28
+ )
29
+ expect(asBigInt(value.value)).toBe(9223372036854775809n)
30
+
31
+ expect(asBigInt(AndUint64(value, $.uint('18446744073709551614', 64)))).toBe(
32
+ 9223372036854775809n,
33
+ )
34
+ expect(asBigInt(value.value)).toBe(9223372036854775808n)
35
+ })
36
+
37
+ test('adds int64 values without number coercion', () => {
38
+ const value = $.varRef($.int('9223372036854775807', 64))
39
+
40
+ expect(AddInt64(value, 1)).toBe(-9223372036854775808)
41
+ expect(value.value).toBe(-9223372036854775808)
42
+ })
43
+ })
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "goscript",
3
3
  "description": "Go to TypeScript transpiler",
4
- "version": "0.2.0",
4
+ "version": "0.2.1",
5
5
  "author": {
6
6
  "name": "Aperture Robotics LLC.",
7
7
  "email": "support@aperture.us",