@yabgu_gm/tensionscript 0.11.4 → 0.12.0

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.
@@ -338,6 +338,8 @@ declare module "types:tensionscript/src/common" {
338
338
  const Object = "Object";
339
339
  const Array = "Array";
340
340
  const StaticArray = "StaticArray";
341
+ /** The descriptor behind `@managed @dynamic [T * n]` (Landing 4d.1). */
342
+ const DynamicArray = "DynamicArray";
341
343
  const Set = "Set";
342
344
  const Map = "Map";
343
345
  const Function = "Function";
@@ -2332,7 +2334,37 @@ declare module "types:tensionscript/src/types" {
2332
2334
  /** String iterator. */
2333
2335
  StringviewIter = 25,
2334
2336
  /** No return type. */
2335
- Void = 26
2337
+ Void = 26,
2338
+ /**
2339
+ * A pointer to `T` with unknown length, `[T *]`.
2340
+ *
2341
+ * **Appended at the end**, not beside {@link FixedArray}, on purpose. This is
2342
+ * a `const enum` with implicit numbering, so an insert would renumber the
2343
+ * twelve kinds between `FixedArray` and `Void`; the compiler is self-hosted, so
2344
+ * a partial rebuild could then disagree with `dist/` about what
2345
+ * `TypeKind.Extern` means -- silently. The order here carries no meaning.
2346
+ *
2347
+ * One slot holding an address: not a value (no extent to inline), not a
2348
+ * reference (the collector owns nothing). `toRef` answers the pointer type,
2349
+ * which is what lets a `[T *]` parameter, local, global and return bind as one
2350
+ * slot where `T[N]` reaches `Type#toRef`'s `assert(false)`.
2351
+ */
2352
+ UnmanagedArray = 27,
2353
+ /**
2354
+ * `@unmanaged @dynamic [T * n]` — a header-prefixed array whose length is a
2355
+ * runtime value.
2356
+ *
2357
+ * Appended for the same reason {@link UnmanagedArray} was: an insert would
2358
+ * renumber every kind after it, and the compiler is self-hosted.
2359
+ *
2360
+ * A separate kind rather than a flag on `UnmanagedArray` because the two have
2361
+ * different layouts — one addresses bare elements, the other a
2362
+ * `[u32 length][pad][data]` header — and every element access has to know
2363
+ * which it is looking at. The storage *model* is a field
2364
+ * ({@link dynamicMemoryModel}), not a kind, so `@managed @dynamic` (Landing 4d)
2365
+ * is the same kind under a different owner.
2366
+ */
2367
+ DynamicArray = 28
2336
2368
  }
2337
2369
  /** Indicates capabilities of a type. */
2338
2370
  export const enum TypeFlags {
@@ -2382,6 +2414,28 @@ declare module "types:tensionscript/src/types" {
2382
2414
  fixedElementType: Type | null;
2383
2415
  /** Element count of a {@link TypeKind.FixedArray}, else `0`. */
2384
2416
  fixedLength: number;
2417
+ /**
2418
+ * Element type of a {@link TypeKind.UnmanagedArray}, else `null`.
2419
+ *
2420
+ * Kept apart from {@link fixedElementType} rather than shared. The two kinds
2421
+ * say different things -- an extent versus an address -- and a shared field
2422
+ * would force every `kind == FixedArray` test in the compiler to become a
2423
+ * three-way test, with the risk that one of them silently starts treating an
2424
+ * address as an extent. `size` differs for the same reason: this kind's size is
2425
+ * the pointer's, not the element's replicated.
2426
+ */
2427
+ unmanagedElementType: Type | null;
2428
+ /** Element type of a {@link TypeKind.DynamicArray}, else `null`. */
2429
+ dynamicArrayElementType: Type | null;
2430
+ /**
2431
+ * Who owns a {@link TypeKind.DynamicArray}'s buffer.
2432
+ *
2433
+ * {@link MemoryModelCode.Unmanaged} for a C-compatible header-prefixed buffer;
2434
+ * {@link MemoryModelCode.Managed} is Landing 4d. Part of {@link equals}, so the
2435
+ * two cannot be confused for one another even though they share a kind and a
2436
+ * layout.
2437
+ */
2438
+ dynamicMemoryModel: MemoryModelCode;
2385
2439
  /**
2386
2440
  * Alignment (log2) of a {@link TypeKind.FixedArray}: its element's.
2387
2441
  *
@@ -2405,7 +2459,15 @@ declare module "types:tensionscript/src/types" {
2405
2459
  get exceptVoid(): Type;
2406
2460
  /** Size in bytes. */
2407
2461
  get byteSize(): number;
2408
- /** Gets this type's logarithmic alignment in memory. */
2462
+ /**
2463
+ * Gets this type's logarithmic alignment in memory.
2464
+ *
2465
+ * An `UnmanagedArray` deliberately does **not** take the {@link fixedAlignLog2}
2466
+ * branch. It is one pointer, so its alignment is the pointer's -- which is what
2467
+ * the `byteSize` fallback already yields, since this kind's `size` is the
2468
+ * pointer's and not the element's replicated. There is no element alignment to
2469
+ * borrow here; the element is not stored in this type.
2470
+ */
2409
2471
  get alignLog2(): number;
2410
2472
  /** Tests if this type represents a basic value. */
2411
2473
  get isValue(): boolean;
@@ -2545,6 +2607,55 @@ declare module "types:tensionscript/src/types" {
2545
2607
  static fixedArray(elementType: Type, length: number): Type;
2546
2608
  /** Gets the element type of a {@link TypeKind.FixedArray}. */
2547
2609
  get fixedArrayElementType(): Type;
2610
+ /** Interned unmanaged-array types, keyed by element type and pointer width. */
2611
+ private static _unmanagedArrays;
2612
+ /**
2613
+ * Gets the {@link TypeKind.UnmanagedArray} of `elementType`: a pointer to a run
2614
+ * of `elementType`, with no length.
2615
+ *
2616
+ * `pointerSize` is `options.usizeType.size` -- 32 or 64 bits. It is a
2617
+ * parameter rather than a constant because a `Type` carries its own size, and
2618
+ * `size` here must be the **pointer's**, not the element's: there is no extent
2619
+ * to measure. It is part of the cache key for the same reason -- `equals`
2620
+ * compares element types only, so two types differing solely in pointer width
2621
+ * must not be the same object.
2622
+ *
2623
+ * Unlike {@link Type.fixedArray} this consults nothing about the element but
2624
+ * its identity: the count is unknown, which is the entire point of the kind.
2625
+ *
2626
+ * The cache is validated by **identity** of the element, not just by the key.
2627
+ * This is what keeps interning a per-program property rather than a
2628
+ * per-process one: the map is a static that outlives a `Program`, so a second
2629
+ * `asc.main` in the same process (which is exactly what the compiler test
2630
+ * harness does -- debug then release) would otherwise be handed the first
2631
+ * program's `Type`, whose `unmanagedElementType` carries the first program's
2632
+ * `Class`. A property read through that class then emitted a call to a getter
2633
+ * the new module never compiled, and the module failed to validate:
2634
+ * `wasm-validator error ... call target must exist`.
2635
+ *
2636
+ * So a cached entry is returned only when its element is the *same object* the
2637
+ * current program just resolved. Within one program that is always true --
2638
+ * `resolveType` interns a record's `Type` -- so interning still holds there.
2639
+ */
2640
+ static unmanagedArray(elementType: Type, pointerSize: number): Type;
2641
+ /** Gets the element type of a {@link TypeKind.UnmanagedArray}. */
2642
+ get unmanagedArrayElementType(): Type;
2643
+ /** Interned dynamic-array types, keyed by model, element and pointer width. */
2644
+ private static _dynamicArrays;
2645
+ /**
2646
+ * Gets the {@link TypeKind.DynamicArray} of `elementType`.
2647
+ *
2648
+ * The key carries the **model** as well as the element, because `equals`
2649
+ * compares both: two models over one element are different types, and
2650
+ * interning them together would make them the same object.
2651
+ *
2652
+ * Like {@link Type.unmanagedArray} the entry is validated by element identity
2653
+ * on the way out — the map outlives a `Program`, so a second compile in one
2654
+ * process must not be handed the first one's element `Class`.
2655
+ */
2656
+ static dynamicArray(elementType: Type, memoryModel: MemoryModelCode, pointerSize: number): Type;
2657
+ /** Gets the element type of a {@link TypeKind.DynamicArray}. */
2658
+ get dynamicElementType(): Type;
2548
2659
  static readonly f64: Type;
2549
2660
  /** A 128-bit vector. */
2550
2661
  static readonly v128: Type;
@@ -3260,6 +3371,57 @@ declare module "types:tensionscript/src/resolver" {
3260
3371
  * `@unmanaged` records carry GC edges they cannot describe. Refused by name.
3261
3372
  */
3262
3373
  private resolveFixedArrayType;
3374
+ /**
3375
+ * Resolves an {@link UnmanagedArrayTypeNode} to a
3376
+ * {@link TypeKind.UnmanagedArray}.
3377
+ *
3378
+ * Deliberately **not** a copy of {@link resolveFixedArrayType}: none of that
3379
+ * function's refusals belong here, and copying them would refuse the form's
3380
+ * whole purpose.
3381
+ *
3382
+ * A fixed array *contains* its elements, so a record element would claim `N`
3383
+ * records sit inline -- refused by name (`resolver.ts`, the `AS100` record
3384
+ * arm). An unmanaged array contains one address, so a record element is the
3385
+ * ordinary case: `[BodyState *]` is a pointer to a run of records, which is
3386
+ * exactly what the collision solver needs and what `BodyState[N]` cannot say.
3387
+ *
3388
+ * What *is* refused is an element with no bytes to point at, which is the same
3389
+ * `byteSize` question `sizeof` asks. An `@inline` element is admitted here on
3390
+ * purpose: it is a value type with a layout, and a pointer to it is a pointer
3391
+ * to those bytes -- the refusal that applies to it is the *field* form's, not
3392
+ * the pointer's.
3393
+ *
3394
+ * A **GC-managed** element is refused (see the arm below). Measured before it
3395
+ * existed: `[Foo *]` over a `class Foo` compiled and emitted
3396
+ * `base + i*4`, `i32.load`, `call Foo#get:x` -- the buffer holds bare
3397
+ * references that nothing scans.
3398
+ */
3399
+ private resolveUnmanagedArrayType;
3400
+ /**
3401
+ * Resolves a {@link DynamicArrayTypeNode} to a {@link TypeKind.DynamicArray}.
3402
+ *
3403
+ * The type is **one pointer** to a `[u32 length][pad][data]` header; the length
3404
+ * is not in the type, which is the whole point — it is a runtime value, read
3405
+ * from the buffer. `n` is spelling here and a binding where a function scope
3406
+ * exists (see `Program#bindDynamicLengthLocals`).
3407
+ *
3408
+ * Three refusals, each for a different reason:
3409
+ *
3410
+ * - **`@managed`** is Landing 4d. The buffer would need a collector-visible
3411
+ * header, which this landing does not produce.
3412
+ *
3413
+ * - **No model at all** cannot be honoured by any emitter: an inline
3414
+ * runtime-length array would need variable stack allocation, which WASM does
3415
+ * not have. Naming a model is the source saying where the bytes live.
3416
+ *
3417
+ * - **A GC-managed element** is the same hazard Landing 3's CX1 closed for
3418
+ * `[T *]`: the buffer is not scanned, so a reference stored in it is not a
3419
+ * root.
3420
+ *
3421
+ * An `@inline` element is accepted: it is a value type, so its extent is
3422
+ * `byteSize` and Landing 3 measured that stride works.
3423
+ */
3424
+ private resolveDynamicArrayType;
3263
3425
  private resolveTupleType;
3264
3426
  private resolveBuiltinNotNullableType;
3265
3427
  /** Resolves a type name to the program element it refers to. */
@@ -3372,6 +3534,28 @@ declare module "types:tensionscript/src/resolver" {
3372
3534
  private lookupPropertyAccessExpression;
3373
3535
  /** Resolves a property access expression to its static type. */
3374
3536
  private resolvePropertyAccessExpression;
3537
+ /**
3538
+ * The element type of an array-kind receiver, or `null` when `type` is not one.
3539
+ *
3540
+ * Three kinds answer here, and they are one family even though only two are a
3541
+ * `TypeKind`:
3542
+ *
3543
+ * - `[T *]` — `TypeKind.UnmanagedArray`, element on the type;
3544
+ * - `@unmanaged @dynamic [T * n]` — `TypeKind.DynamicArray`, element on the type;
3545
+ * - `@managed @dynamic [T * n]` — a **class reference** to the std
3546
+ * `DynamicArray<T>` descriptor (Landing 4d.1), so the element is the class's
3547
+ * type argument.
3548
+ *
3549
+ * The third case is why this is a helper. Landing 2 added the first arm by
3550
+ * hand, Landing 4b added the second by hand, and the managed form would be the
3551
+ * third copy of the same two-line idea — in two places, since the element is
3552
+ * wanted both as a *type* (`resolveElementAccessExpression`) and as an
3553
+ * *element* (`lookupElementAccessExpression`, which peels `y[i].field` through
3554
+ * the element's class). One definition, three kinds, two consumers.
3555
+ */
3556
+ private arrayKindElementType;
3557
+ /** Whether `classReference` is the std `DynamicArray<T>` descriptor class. */
3558
+ private isManagedDynamicArrayClass;
3375
3559
  /** Looks up the program element the specified element access expression refers to. */
3376
3560
  private lookupElementAccessExpression;
3377
3561
  /** Resolves an element access expression to its static type. */
@@ -3642,6 +3826,25 @@ declare module "types:tensionscript/src/parser" {
3642
3826
  finish(): void;
3643
3827
  /** Parses a type name. */
3644
3828
  parseTypeName(tn: Tokenizer): TypeName | null;
3829
+ /**
3830
+ * Whether a `[` at the start of a type begins the unmanaged-array family
3831
+ * (`[T *]`, `[T * N]`) rather than a tuple.
3832
+ *
3833
+ * The discriminator is one token after the element type: `*` commits, and
3834
+ * anything else (`]`, `,`, `:`, `|`) belongs to the tuple arm. The lookahead
3835
+ * speculates with errors suppressed and rewinds either way -- the same shape
3836
+ * as `tryParseFunctionType` / `tryParseSignatureIsSignature`, and the reason
3837
+ * the tuple arm's body needs no change: it is entered with the tokenizer
3838
+ * exactly where its own `tn.skip(Token.CloseBracket)` expects it.
3839
+ *
3840
+ * A pre-type decorator is excluded before speculating. `parseType`'s decorator
3841
+ * arm reports `AS212` ("Decorator '@x' is not valid here") *before*
3842
+ * `suppressErrors` is consulted, so speculating into `[@unmanaged T *]` would
3843
+ * emit a diagnostic for a form this family does not accept anyway -- and then
3844
+ * emit the tuple/`Type expected` error on the rewind, giving two errors for
3845
+ * one mistake.
3846
+ */
3847
+ startsUnmanagedArrayType(tn: Tokenizer): boolean;
3645
3848
  /** Parses a type. */
3646
3849
  parseType(tn: Tokenizer, acceptParenthesized?: boolean, suppressErrors?: boolean): TypeNode | null;
3647
3850
  private tryParseSignatureIsSignature;
@@ -3941,6 +4144,17 @@ declare module "types:tensionscript/src/program" {
3941
4144
  /** Gets the standard `StaticArray` prototype. */
3942
4145
  get staticArrayPrototype(): ClassPrototype;
3943
4146
  private _staticArrayPrototype;
4147
+ /**
4148
+ * Gets the `DynamicArray` prototype — the descriptor class behind
4149
+ * `@managed @dynamic [T * n]`.
4150
+ *
4151
+ * Follows `staticArrayPrototype` exactly: a lazily-cached `require` of the
4152
+ * std prototype, so `@managed @dynamic [T * n]` can resolve through
4153
+ * `resolveClass(program.dynamicArrayPrototype, [elementType])` and come back
4154
+ * as an ordinary generic class instance.
4155
+ */
4156
+ get dynamicArrayPrototype(): ClassPrototype;
4157
+ private _dynamicArrayPrototype;
3944
4158
  /** Gets the standard `Set` prototype. */
3945
4159
  get setPrototype(): ClassPrototype;
3946
4160
  private _setPrototype;
@@ -4607,6 +4821,15 @@ declare module "types:tensionscript/src/program" {
4607
4821
  * is what gives it one `i32` slot instead of the record's reference type.
4608
4822
  */
4609
4823
  isViewLocal: boolean;
4824
+ /**
4825
+ * Whether this local is a **dynamic-array length binding** — the `n` in
4826
+ * `[T * n]`, introduced by the signature rather than written as a declaration.
4827
+ *
4828
+ * It is read-only by construction: its value comes from the buffer header at
4829
+ * function entry, so assigning it would make the compiler's idea of the length
4830
+ * disagree with the buffer's. The assignment and shadowing rules key on this.
4831
+ */
4832
+ isDynamicLength: boolean;
4610
4833
  /** Constructs a new local variable. */
4611
4834
  constructor(
4612
4835
  /** Simple name. */
@@ -5859,6 +6082,15 @@ declare module "types:tensionscript/src/compiler" {
5859
6082
  indexExpression: Expression | null,
5860
6083
  /** Whether to tee the value. */
5861
6084
  tee: boolean): ExpressionRef;
6085
+ /**
6086
+ * The {@link DynamicArrayTypeNode} a declaration names, if it names one.
6087
+ *
6088
+ * Two spellings carry the length's name: the annotation (`const y: [T * n]`)
6089
+ * and `changetype`'s type argument (`const y = changetype<[T * n]>(p)`). Both
6090
+ * are read here so the local-position binding does not depend on which one the
6091
+ * source chose.
6092
+ */
6093
+ private dynamicArrayTypeNodeOf;
5862
6094
  /** Makes an assignment to a local, keeping track of wrap and null states. */
5863
6095
  private makeLocalAssignment;
5864
6096
  /** Makes an assignment to a global. */
@@ -5931,6 +6163,177 @@ declare module "types:tensionscript/src/compiler" {
5931
6163
  /** Creates an indirect call to a first-class function. */
5932
6164
  makeCallIndirect(signature: Signature, functionArg: ExpressionRef, reportNode: Node, operands?: ExpressionRef[] | null, immediatelyDropped?: boolean): ExpressionRef;
5933
6165
  private compileCommaExpression;
6166
+ /**
6167
+ * The byte extent of one element of a `[T *]` — the stride `y[i]` walks by.
6168
+ *
6169
+ * For a scalar this is `Type.byteSize`. For a record it is **not**: an
6170
+ * `@unmanaged`/`@managed` record's `Type` is reference-like, so its `byteSize`
6171
+ * is the 4-byte address, and the extent lives on the `Class` that laid the
6172
+ * fields out. `builtin_sizeof` already answers this way for the same reason
6173
+ * (`builtins.ts`, the `Unmanaged | ManagedStruct` branch answers
6174
+ * `nextMemoryOffset`), so the two cannot disagree about how big a record is.
6175
+ *
6176
+ * An `@inline struct` needs no arm: it *is* a value type, so its
6177
+ * `byteSize` is already the extent. Measured — `[Inl *]` over
6178
+ * `@inline struct Inl { x: f64; y: f64 }` strides 16, which is
6179
+ * `sizeof<Inl>()`.
6180
+ *
6181
+ * Measured, and the reason this helper exists: using `byteSize` directly gave
6182
+ * a stride of 4 for a two-`f64` record of 16 bytes — the same "alignment or
6183
+ * reference size instead of the extent" mistake the fixed-array arm avoids
6184
+ * only because it refuses records outright.
6185
+ */
6186
+ private unmanagedArrayElementSize;
6187
+ /**
6188
+ * Whether `type` is a record whose elements live **at** the address rather
6189
+ * than being a value loaded from it: `@unmanaged`, `@managed` or `@inline`.
6190
+ *
6191
+ * This is the discriminator between "the address is the element" and "load the
6192
+ * element from the address", and it is deliberately about the *element kind*
6193
+ * rather than about type equality — see {@link compileUnmanagedArrayElementAccess}.
6194
+ */
6195
+ private isRecordValueType;
6196
+ /**
6197
+ * The header an `@unmanaged @dynamic [T * n]` buffer carries in front of its
6198
+ * elements: `align_up(4, alignof(T))`.
6199
+ *
6200
+ * Four bytes hold the `u32` length; anything needing stronger alignment than
6201
+ * that gets padding after it. So `u32` elements sit at `ptr + 4`, and a
6202
+ * two-`f64` record sits at `ptr + 8`.
6203
+ *
6204
+ * Reported in Landing 4a: **no header-size helper existed**, because nothing
6205
+ * in 4a needed one — `.length` reads offset 0, and 4a's own V5 recorded that
6206
+ * the constant could not be observed from outside. This is its first
6207
+ * definition; `.length` and the element address both go through it so they
6208
+ * cannot drift.
6209
+ *
6210
+ * A record's alignment is `Class#alignLog2` rather than `Type#alignLog2` for
6211
+ * the same reason its size is `nextMemoryOffset`: a record's `Type` is
6212
+ * reference-like and would report the 4-byte pointer's alignment for every
6213
+ * record, including the `f64`-only ones that are aligned 8.
6214
+ */
6215
+ /**
6216
+ * Whether `type` is the **managed** dynamic-array descriptor — the std
6217
+ * `DynamicArray<T>` class that `@managed @dynamic [T * n]` resolves to
6218
+ * (Landing 4d.1).
6219
+ *
6220
+ * Answered by class identity rather than by a memory-model field, because the
6221
+ * managed form is a class reference now: its `Type` is the class instance's
6222
+ * type, and the "model" is implicit in `isManaged`. That is the whole point of
6223
+ * the redesign — `TypeKind.DynamicArray` carries `TypeFlags.None` and is not
6224
+ * traced, so a descriptor stored as one would not be rooted.
6225
+ */
6226
+ /**
6227
+ * The element type of either dynamic-array form.
6228
+ *
6229
+ * `@unmanaged @dynamic [T * n]` carries the element on its type;
6230
+ * `@managed @dynamic [T * n]` is a `DynamicArray<T>` class reference, so the
6231
+ * element is the class's type argument. One accessor for both, because every
6232
+ * consumer below — access, compound, increment — would otherwise branch.
6233
+ */
6234
+ private dynamicArrayElementOf;
6235
+ private isManagedDynamicArrayType;
6236
+ private dynamicArrayHeaderSize;
6237
+ /**
6238
+ * Compiles the byte address of `y[i]` for a dynamic array:
6239
+ * `ptr + headerSize + i*sizeof(T)`.
6240
+ *
6241
+ * Shared by the read and the write arm, so the two cannot disagree about which
6242
+ * bytes they address — the same split the fixed and unmanaged arms use.
6243
+ *
6244
+ * Two constants, each of which was a bug somewhere else in this family:
6245
+ *
6246
+ * - **`headerSize` is present.** It is what separates a dynamic array from a
6247
+ * `[T *]`: the data starts after the length word and its padding.
6248
+ * - **The stride is a multiply by the element's extent**, not a shift by its
6249
+ * alignment. For a two-`f64` record those differ (16 vs 8), which is Landing
6250
+ * 2's lesson and the reason `unmanagedArrayElementSize` exists.
6251
+ */
6252
+ private compileDynamicArrayElementAddress;
6253
+ /**
6254
+ * The bounds check an `@unmanaged @dynamic [T * n]` access performs before it
6255
+ * touches memory: `if ((u32)index >= (u32)length) trap`.
6256
+ *
6257
+ * **Unsigned** on purpose. A negative index is a huge `u32`, so it fails the
6258
+ * same comparison as any other out-of-range value — one check covers both ends
6259
+ * without a second comparison, and `y[-1]` cannot walk backwards out of the
6260
+ * buffer.
6261
+ *
6262
+ * The length comes from the header directly rather than from the `n` binding.
6263
+ * `n` is a snapshot taken at entry (D5 of 4a); the header is what the buffer
6264
+ * currently says, and a bounds check is about the memory that exists now.
6265
+ *
6266
+ * The message is a literal: `makeAbort` takes a string expression, and nothing
6267
+ * in this compiler formats an index and a length into one (measured — 4a's
6268
+ * `changetype` trap is the same shape). Naming the two causes would mean
6269
+ * building a string at every check.
6270
+ */
6271
+ private compileDynamicArrayBoundsCheck;
6272
+ /**
6273
+ * Compiles the byte address of `y[i]` for an unmanaged array: `base + i*sizeof(T)`.
6274
+ *
6275
+ * Shared by the read and the write arm, so the two cannot disagree about which
6276
+ * bytes they address — the same reason `compileFixedArrayElementAccess` and
6277
+ * `compileFixedArrayElementAddress` are split.
6278
+ *
6279
+ * The stride is a **multiply**, not the fixed-array arm's `1 << alignof(T)`.
6280
+ * That shift is right only for the elements the fixed arm admits (records are
6281
+ * refused there, so every element is a scalar, where size and alignment
6282
+ * coincide). A record element is the ordinary case here, and for a record the
6283
+ * two disagree: two `f64`s give `sizeof` 16 and `alignof` 8.
6284
+ */
6285
+ private compileUnmanagedArrayElementAddress;
6286
+ /**
6287
+ * Compiles `y[i]` for an unmanaged array, `[T *]` — both directions.
6288
+ *
6289
+ * Two things separate this from {@link compileFixedArrayElementAccess}:
6290
+ *
6291
+ * - **The stride is a multiply** (see the address helper).
6292
+ *
6293
+ * - **A record element compiles to an address, not a load.** A record's
6294
+ * `toRef` is the 4-byte reference, so loading there would read the record's
6295
+ * first four bytes and call them a pointer. So the address is yielded typed
6296
+ * as the element, and `.field` on it resolves through the record's own
6297
+ * accessor — a load or a store at `addr + offsetof`. **That is also how a
6298
+ * field write works**: `y[i].x = v` reaches the property setter, whose
6299
+ * `this` is this address. No separate arm.
6300
+ *
6301
+ * Reads are unchecked. `*` says the length is unknown, so there is nothing to
6302
+ * check against; `@dynamic` is where a checked form lives.
6303
+ *
6304
+ * A whole-record **write** is refused, keyed on the element being a record.
6305
+ * The key is the element kind, not "both sides have the same type": a scalar
6306
+ * element assignment (`a[i] = 5` over `[u32 *]`) is an ordinary store and must
6307
+ * keep compiling. Refusing by type equality would have caught that case too,
6308
+ * which is the trap this comment exists to keep shut.
6309
+ */
6310
+ private compileUnmanagedArrayElementAccess;
6311
+ /**
6312
+ * Compiles `y[i]` for an `@unmanaged @dynamic [T * n]` — both directions.
6313
+ *
6314
+ * Differs from {@link compileUnmanagedArrayElementAccess} in exactly one place,
6315
+ * and it is the whole point of the kind: **the bounds check**. A `[T *]` has no
6316
+ * length to check against, so its access is unchecked by contract; a dynamic
6317
+ * array carries one in its header, and an index outside it is a diagnostic
6318
+ * rather than whatever memory happens to follow.
6319
+ *
6320
+ * Everything else is the unmanaged arm's shape, deliberately:
6321
+ *
6322
+ * - a **record element** compiles to the address, typed as the element, so
6323
+ * `.field` reads and writes go through the record's own accessor — which is
6324
+ * also how `y[i].field = v` works, with no arm of its own;
6325
+ * - a **scalar element** is loaded or stored at its own width;
6326
+ * - a whole-record **write** is refused, keyed on the element being a record
6327
+ * rather than on type equality, so `a[i] = 5` over `[u32 * n]` still
6328
+ * compiles.
6329
+ *
6330
+ * Compound assignment and increment call this **twice** — once by way of the
6331
+ * read the operator performs, once for the store — so the check is emitted
6332
+ * twice. That is the smaller of the two correct shapes: sharing one check
6333
+ * would mean threading the checked address through the operator's own codegen,
6334
+ * which does not know this arm exists.
6335
+ */
6336
+ private compileDynamicArrayElementAccess;
5934
6337
  /**
5935
6338
  * Compiles `array[index]` for a `T[N]` inline array: read and write.
5936
6339
  *
@@ -8855,7 +9258,26 @@ declare module "types:tensionscript/src/ast" {
8855
9258
  ExportMember = 67,
8856
9259
  SwitchCase = 68,
8857
9260
  IndexSignature = 69,
8858
- Comment = 70
9261
+ Comment = 70,
9262
+ /**
9263
+ * `[T *]` — a pointer to T with unknown length.
9264
+ *
9265
+ * Appended at the end rather than beside {@link FixedArrayType}. `NodeKind`
9266
+ * is a `const enum` with implicit numbering, so an insert would renumber every
9267
+ * node kind after it; the compiler is self-hosted, so a partial rebuild could
9268
+ * then disagree with `dist/` about what `NodeKind.Comment` means -- silently.
9269
+ * The position carries no meaning.
9270
+ */
9271
+ UnmanagedArrayType = 71,
9272
+ /**
9273
+ * `@unmanaged @dynamic [T * n]` — a header-prefixed array whose length is a
9274
+ * runtime value read from the buffer.
9275
+ *
9276
+ * Appended for the same reason {@link UnmanagedArrayType} was: this is an
9277
+ * implicit-numbering `const enum`, so an insert would renumber every node kind
9278
+ * after it.
9279
+ */
9280
+ DynamicArrayType = 72
8859
9281
  }
8860
9282
  /** Base class of all nodes. */
8861
9283
  export abstract class Node {
@@ -8873,6 +9295,8 @@ declare module "types:tensionscript/src/ast" {
8873
9295
  static createFunctionType(parameters: ParameterNode[], returnType: TypeNode, explicitThisType: NamedTypeNode | null, isNullable: boolean, range: Range): FunctionTypeNode;
8874
9296
  static createTupleType(elements: TypeNode[], elementNames: (IdentifierExpression | null)[] | null, isNullable: boolean, range: Range): TupleTypeNode;
8875
9297
  static createFixedArrayType(elementType: TypeNode, length: number, range: Range): FixedArrayTypeNode;
9298
+ static createUnmanagedArrayType(elementType: TypeNode, range: Range): UnmanagedArrayTypeNode;
9299
+ static createDynamicArrayType(elementType: TypeNode, lengthName: IdentifierExpression, memoryModel: MemoryModel, range: Range): DynamicArrayTypeNode;
8876
9300
  static createOmittedType(range: Range): NamedTypeNode;
8877
9301
  static createTypeParameter(name: IdentifierExpression, extendsType: NamedTypeNode | null, defaultType: NamedTypeNode | null, range: Range): TypeParameterNode;
8878
9302
  static createParameter(parameterKind: ParameterKind, name: IdentifierExpression, type: TypeNode, initializer: Expression | null, range: Range): ParameterNode;
@@ -9112,6 +9536,56 @@ declare module "types:tensionscript/src/ast" {
9112
9536
  /** Source range. */
9113
9537
  range: Range);
9114
9538
  }
9539
+ /**
9540
+ * Represents an unmanaged array type, `[T *]` — a pointer to `T` with no length.
9541
+ *
9542
+ * The difference from {@link FixedArrayTypeNode} is the missing `length`, and it
9543
+ * is the whole difference: a fixed array *contains* its elements, so it has an
9544
+ * extent the layout can size; this one *addresses* them, so its own size is the
9545
+ * pointer's. That is why `[T *]` binds as one `i32` slot where `T[N]` has no
9546
+ * reference representation at all (`Type#toRef` asserts on a fixed array).
9547
+ *
9548
+ * Not a reference: the collector owns nothing here, the address is the caller's.
9549
+ * Not a value either: there is nothing to inline. `changetype` and a parameter
9550
+ * are the two ways one comes into being.
9551
+ */
9552
+ export class UnmanagedArrayTypeNode extends TypeNode {
9553
+ /** Element type. */
9554
+ elementType: TypeNode;
9555
+ constructor(
9556
+ /** Element type. */
9557
+ elementType: TypeNode,
9558
+ /** Source range. */
9559
+ range: Range);
9560
+ }
9561
+ /**
9562
+ * Represents a dynamic array type, `@unmanaged @dynamic [T * n]`.
9563
+ *
9564
+ * The `n` is **spelling, not an expression**: the parser cannot evaluate it (it
9565
+ * has no symbol table), so it is carried as a name and the length is read at
9566
+ * runtime from the buffer header. That is why the form is admissible at parse
9567
+ * time without knowing whether `n` is a constant — resolution decides that, and
9568
+ * `resolveDynamicArrayType` refuses a name that turns out to be compile-time.
9569
+ *
9570
+ * One i32 slot at the ABI, holding a pointer to `[u32 length][pad][T data[n]]`.
9571
+ */
9572
+ export class DynamicArrayTypeNode extends TypeNode {
9573
+ /** Element type. */
9574
+ elementType: TypeNode;
9575
+ /** The length's name. Bound as a read-only local where a function scope exists. */
9576
+ lengthName: IdentifierExpression;
9577
+ /** Storage model of the buffer. `Unmanaged` for now; `Managed` is Landing 4d. */
9578
+ dynamicMemoryModel: MemoryModel;
9579
+ constructor(
9580
+ /** Element type. */
9581
+ elementType: TypeNode,
9582
+ /** The length's name. Bound as a read-only local where a function scope exists. */
9583
+ lengthName: IdentifierExpression,
9584
+ /** Storage model of the buffer. `Unmanaged` for now; `Managed` is Landing 4d. */
9585
+ dynamicMemoryModel: MemoryModel,
9586
+ /** Source range. */
9587
+ range: Range);
9588
+ }
9115
9589
  /** Represents a type parameter. */
9116
9590
  export class TypeParameterNode extends Node {
9117
9591
  /** Identifier reference. */