Master plan for replacing the flattened dim/dimExpr vectors on TypeDecl with a
structural Type::tFixedArray node, so fixed arrays resolve through the same recursive
machinery as every other container type. Living document on the
bbatkin/fixed-array-type branch; every stage is review-gated (plan first, then commits).
dim is a qualifier vector bolted onto every TypeDecl instead of type structure.
Verified consequences on master:
take_plain(a : auto(TT)) <- int[4] => TT = int // array-ness silently stripped
take_dim1(a : auto(TT)[]) <- float[4][4] => NO MATCH // dim.size() 1 != 2
take_dim1(a : auto(TT)[]) <- M4[10] => NO MATCH // M4=float[4][4] flattens to float[10][4][4]
var a : M4[3] => a is float[3][4][4] // M4 identity destroyed at declaration
a[0] => float[4][4] // anonymous; should be M4
Root causes:
updateAliasMapclearsdimwhen recordingauto(TT)— dim sits in the qualifier set next toconstant/ref/temporary, so the deduced alias lies about the type (body seesint const[4],TTclaimsint,default<TT>givesint).inferGenericTyperequires exact dim-count match;auto[]can never peel one level off a multi-dim array because there is no "rest of the type" node to bind.- Alias expansion concatenates dim vectors, destroying typedef boundaries irrecoverably.
- Every
isX()predicate pays adim.size()==0tax; daslib carries ~25 workaround overloads (table<K; auto(valT)[]>get/get_value/insert/insert_clone/emplace/values/get_key families,array<auto(numT)[]>push/emplace/push_clone siblings), each reconstructing the thrown-away type viavalT[typeinfo dim_table_value(Tab)]— and covering only ONE[]level.apply.das/contracts.dassimply reject fixed arrays.
Meanwhile array<T>, table<K;V>, pointers, iterators all infer correctly — they are
structural nodes with firstType recursion. Fixed arrays are the only container that isn't.
Type::tFixedArray, appended at the END of theTypeenum (existing values stay stable for serialized data). Element type infirstType.- Strict nesting, one size per node, outermost first:
float[4][4]=FA(4, FA(4, float))M4[3](M4 =float[4][4]) =FA(3, FA(4, FA(4, float)) alias="M4")- Alias is a display label (same as
akatoday); identity stays plain structural recursion. One type, one shape — no flatten-canonicalization anywhere.
TypeDeclfields:dimanddimExprdeleted. Added:int32_t fixedDim— size;dimAuto/dimConstsentinels kept (int[]param =FA(dimAuto, int)); meaningful only on tFixedArray nodesExpressionPtr fixedDimExpr— size expression whenfixedDim == dimConstvector<ExpressionPtr> typeMacroExpr— typeMacro/typeDecl/tag payload, contents and conventions byte-identical to today's dimExpr usage (typeMacro:[0]=name string,[1..]=args, type-args arrive asExprTypeDecl; typeDecl/tag:[0]=the expr). No typemacro argument-model redesign in this train.- Net ~−12B per node; every predicate drops the
dim.size()branch.
isNativeDimmoves onto the FA node;typeFactory<TT[dim]>wraps instead of pushes.- Qualifier discipline (canonical form, settled at 1a):
constant/ref/temporarylive ONLY on the outermost FA node; inner chain nodes are bare — same rule astArrayelements. Peeling a level (a[i], iteration,auto(TT)[]) =firstTypeplus the same constness propagation tArray element access uses. One type, one shape; Stage 1d enforces at construction sites, a debug assert inisSameTypecatches violations. -[](removeDim) = unwrap one FA level; kept parsing, mostly obsolete.- New inference semantics (no compat shim):
auto(TT)<-int[4]=>TT = int[4](whole array, consistent witharray<int>)auto(TT)[]<-float[4][4]=>TT = float[4](new match)auto(TT)[]<-M4[10]=>TT = M4(alias preserved on the node)a[0]onM4[3]=>M4- Overload ranking must keep
auto[]more specific than plainautoso existing call sites don't go ambiguous.
- Runtime
TypeInfostays flattened forever —dim/dimSizedescribe memory layout, and the flattened form IS the layout. Single AST->runtime conversion point (ast_debug_info_helper.cpp) walks the nested chain and emitsdim[]={3,4,4}exactly as today. GC walker, data walker, debug print, JIT TypeInfo globals,daslib/rttiuntouched. - Mangled name: natural recursion (settled at 1a). Each FA node emits its own
qualifiers +
[d]+ alias slot, recursing intofirstType; parse rebuilds the exact structure including alias placement (clean round-trip). Unaliased chains — the overwhelming majority, including every C++ binding — stay byte-identical to master ([3][4][4]f). Mid-chain aliases shift theY<>slot to the level it labels ([3][4]Y<M4>[4]fvs master's flattened[3][4][4]Y<M4>f); the already-planned one-time semantic-hash / AST-serializer /LLVM_JIT_CODEGEN_VERSIONbumps cover that churn. Text is self-consistent within a build and stable going forward. - Same SimNodes emitted — interpreter/runtime untouched; stride/count computed from the nested type instead of the dim vector.
- das macro API: during migration, read-only computed
.dim/.dimExprcompat properties (flattened view) keep aot_cpp.das / llvm_jit.das / macros green while they port to the structural API (baseType==tFixedArray,.fixedDim,.fixedDimExpr,.firstType,.typeMacroExpr). Compat properties are DELETED after the sweep (optionally keep adims(td)helper in ast_boost for code that wants the flattened list). The compat.dimExprproperty must be baseType-aware — on typeMacro/typeDecl/tag nodes it returnstypeMacroExpr(so typemacro_boost works unmodified until its Stage-5 rename); on tFixedArray nodes it returns the size-expr view. "Whatever dimExpr used to hold for this baseType." - Parser keeps the existing
MyMacro(...)[N]/typedecl(...)[N]errors (behavior- preserving); lifting them becomes representable and is a separate later decision.
Each stage: plan discussion -> implementation -> commit review. CI + /test green at every gate.
Pin current must-not-change behavior as a dastest suite (tests/fixed_array/) run against
master semantics: layout (sizeof/alignof/stride/field offsets incl. C++ handled types,
tuples, variants with FA fields), copy/clone/init semantics, indexing + bounds, iteration,
table<K;V[]> / array<T[]> operations, serialization round-trip, AOT, typeinfo dim
traits, current overload preference (auto vs auto[] with FA arg). Plus the
TARGET-behavior tests (initially disabled via leading-_ filenames) encoding the new
inference semantics and M4 preservation.
Also tests/typemacro/test_basic.das — direct coverage of the typeMacro payload surface
that Stage 1b migrates (all four grammar forms; typemacro_argument const-extraction for
int/bool/string; typeMacroName; typedecl(expr)). Deep indirect coverage already exists
via tests/option + tests/hash_map + tests/delegate (Option/Result/hash tables are
typemacro_boost-based), but the raw grammar forms and const-arg extraction had none.
Known-broken-on-master, expected fixed by this rework (target spec, NOT characterization):
get_key(table<K;V[]>, v) fails to resolve its own valT const[-2] contortion
(builtin.das:1407).
Exit: suite green on the branch point; target tests reviewed as the spec.
The indivisible piece, sub-staged for review:
-
1a (settled: ADDITIVE, byte-neutral, full CI green standalone) —
Type::tFixedArrayappended + new TypeDecl fields (fixedDim/fixedDimExpr/typeMacroExpr, dormant) + predicates + clone/visit/gc + describe + mangled name EMIT +isSameType+ semantic/lookup hash + getSizeOf/stride/align family. Everything gated onbaseType==tFixedArray; nothing produces FA nodes yet;dim/dimExprstay live until end of Stage 1. Dead arms proven by a hand-built-nodes tests-cpp doctest suite asserting FA-vs-old-node equivalence (text, sizes, identity, clone, gc, hashes). Mangled-name PARSE flip deferred to 1b —[N]text is identical in both worlds, so the parser must build whichever representation the program uses, and that flips with the world. -
1b (settled: TWO commits) Parsers (ds2 + ds1 + parser_impl) + the payload move.
- 1b-i — payload move, CI-green standalone. typeMacro/typeDecl/tag payload moves
dimExpr->typeMacroExpratomically: parser writes, all C++ reads (typeMacroName, describe, mangled emit, moreSpecialized, inferPartialAliases, inferTypeExpr, validator), PLUS two pieces pulled forward from 1f because atomicity forces them: the das-side.dimExprbinding becomes a read-only compat property ("whatever dimExpr used to hold for this node": non-emptytypeMacroExprwins, covers the tag payload on autoinfer nodes), and the AST serializer carriestypeMacroExpr(+version bump). typemacro_boost / clargs / ast_match / templates_boost keep working unmodified. Mangled text byte-identical. - 1b-ii — FA parser flip, breaks the FA world.
appendDimExprbecomes an FA-chain builder +attachDimChain(attach element bare at chain end; hoist the qualifier flags the old world fused onto the dim-carrying node — const/ref/temporary, their remove*, implicit/explicitConst/explicitRef/isExplicit/autoToAlias — to the chain head;aliasNEVER hoists: at parse it is the unresolved name on the element). dim_list/splice arms in both grammars, gen1's push-at-end[]arm and{{ }}synthesizedauto[], mangled PARSEcase '['builds FA — with the rule that aY<name>immediately following[d]labels THAT FA node (known cosmetic asymmetry:[3]Y<I>ire-parses with the label on the FA node even if it was on the element; structural identity unaffected). Grammar errors kept verbatim. Gate (settled at landing: commit-as-is, world fully dark): tests-cpp parse-shape + mangled round-trip suites green. The original "dastest still runs -> per-test inventory" gate proved unmeetable: daslib generic SIGNATURES with[](builtin.das bulk push/to_array_move family) are parser-produced FA now, while make-array literal ARG types still come from infer (dim-vector until 1d) — FA-vs-dim- vector is structurally unequal by design, every.das_moduleinitialize meets it atto_array_move, so daslang.exe cannot boot the in-repo project until 1d. Class-level burndown instead: (1) FA generic signatures vs infer-made dim-vector args — module load/startup, 1d (make-array flip + FA rides firstType recursion; half deleted at Stage 4); (2) concrete FA declarations through infer (let x : int[5]) — 1d; (3) typeFactory/interop C-array types — 1c; (4) FA simulate lowering — 1e. gen1 grammar fact recorded by the suite:int[3][]is a syntax error on master too (dim_list shift preference), so the push-at-end arm only composes viaint[]/int[][]/int[][3].
- 1b-i — payload move, CI-green standalone. typeMacro/typeDecl/tag payload moves
-
1c typeFactory / interop (settled at landing).
typeFactory<TDim<TT,size>>andtypeFactory<TT[dim]>wrap FA via the sharedmakeFixedArrayTypeDeclheader helper (hoists the canonical ref/const/temporary trio off the element);isNativeDimmoves onto the FA node (only consumer is aot_cpp.das reading the indexed type's head).makeArgumentTypeneeds no edit — composes via the FA-aware isRefType. SETTLED: the natural recursion fixes the latent multi-dim order bug (old: Cint[3][4]-> dim [4,3] = das int[4][3], wrong row stride; believed unexercised in-tree) — fix taken, pinned by tests. PULLED FORWARD from 1f by necessity: themakeTypeInfoFA-flatten arm (typeFactory feeds handled-struct FIELD types and builtin signatures whose runtime TypeInfo must keep the exact flattened shape — without it a C-array field silently produces dimSize=0 TypeInfo). ManagedVector's scratch dim node (ast_handle.h walk) flips with it. Builtin mangled names unchanged (unaliased FA chains byte-identical); semantic hashes shift — moot while dark. -
1d Infer (sub-plan settled): inferAlias/inferPartialAliases gain the tFixedArray recursion arm (WRAP, never concatenate); SETTLED: on alias resolution the typedef name is kept as display label ONLY when the resolved type is an FA chain (M4 design; non-FA aliases keep today's clear — zero display churn elsewhere). inferGenericType gains the FA arm (fixedDim match, dimAuto wildcard, firstType recursion; plain auto(TT) binds the whole chain via the existing clone path). updateAliasMap needs NO edit — auto(TT)[] rides the firstType recursion; the dim.clear() root-cause line is a dead no-op. applyAutoContracts gains the FA recursion arm; the removeDim peel moves to pointer-returning callers (can't reseat through const TypeDeclPtr& in-place). moreSpecialized ranks FA above plain auto. dimConst resolution per FA node (inferTypeExpr walks the chain, evals fixedDimExpr); verifyType walks the chain. ExprAt/ExprSafeAt/for-loop peel = the existing tArray pattern (clone element, ref=true, constant|=head). ExprNew rebuilds the chain around the pointer node. typeinfo is_dim/ dim read isFixedArray/head fixedDim; baseType-gated dim.size()==0 checks need no edit. Make-literal result typing wraps via makeFixedArrayTypeDecl (~8 sites + generate). SEQUENCING (settled): 1d+1e land as a two-commit train pushed together — 1d compiles FA but can't run it (simulate still reads dim vectors); boot gate applies at the train's tip: tests-cpp full green (the int[5] known-red flips), daslang boots, dastest runs -> per-test inventory becomes the 1f burndown. IMPLEMENTED. Equivalence rule that drove the edit set: dim-GATED classifiers (isStructure/isVariant/isString/isPointer/isVoid/isWorkhorseType/isClass-via-isStructure) return false on the FA wrapper exactly as they did on a dim'd node — call sites need NO edit; raw
baseType==/structType/annotationreads DID see through a dim'd node, so those need the two-line element walk (make-struct/-variant/-array visitors, escape analysis ascend branch, needAvoidNullPtr allowDim). Known intentional divergence:[[EnumT[2]]](isEnumT ignored dim, master collapsed to a single const — FA keeps the array). Two latent master bugs fixed by the natural wrap: gen2 fixed_array of an already-dim'd element appended the result dim INNER-first (same family as the 1c typeFactory order fix), and makeStructWhereBlock double-appended dim when makeType carried an explicit one. ExprNew dead dim-copy remnants removed. Gates: build green; tests-cpp 55/56 — the one red (int[5]) now passes COMPILE and throws in simulate, i.e. the failure crossed the 1d/1e boundary on schedule. -
1e Simulate lowering: ExprAt/iterator SimNode emission computed from nested type (same SimNodes), the fakeVariable flatten hack (ast_simulate.cpp ~:949), ExprNew dims. Note: gen2
new Foo[3]parses as(new Foo)[3](pointer index), NOT new-dim — the ExprNew-with-dim / das_new_dim path is reachable via other routes (gen1 et al.); map its actual reachability during this sub-stage before porting it. IMPLEMENTED. ast_simulate.cpp: make-variant/make-struct lowering element walks (findArgumentIndex/getVariantFieldOffset/structType/annotation through the wrapper; getStride needs NO walk — FA head getStride == old dim'd getStride), fakeVariable wrap, ExprAscend/ExprNew dispatch walks + pointee-size via pointer-node walk, sv_trySimulate_At/ExprSafeAt ranges from fixedDim, for-loop fixedSize + iterator arm. ExprDelete needs NO edit (infer routes FA delete to finalize_dim; the total==1 assert proves dim'd never reached it). Boot-gate fallout fixed en route, beyond ast_simulate: (1) expect_dim contract + sort transformCall in src/builtin (dim reads); (2) isAliasOrA2A/applyRefToRef/collectAliasList/isAotAlias were MISSING their FA arms (1a gap — isAlias() false on FA-of-alias made ExprCast never resolveTT -const[N]); (3) inferAlias FA arm now applies+clears the head's hoisted remove* contracts (the dim'd alias leaf used to do this; without itTT -const[N]kept the contract flag and isSameType failed); (4) AST-GC HAZARD (new failure class for the whole rework): a fresh node held only in a C++ local across inferFunctionCall gets collected (proven via cdb: ptrType freed, baadf00d). Discipline: derive scratch pointers from rooted slots AFTER such calls, never capture across them. Second instance: gc_local frees ONLY the head — TypeDecl's copy ctor deep-clones children, so guarded deep clones leak their subtree (makeTypeInfo FA-flatten arm now uses a SHALLOW borrow scratch; ManagedVector scratch got an element guard, at master parity); (5) updateAliasMap "dead no-op" claim was WRONG for bareauto(TT)matched against an FA-typed pass — actually surfaced via daslib: ast_boost walk_and_convert_array/table still BUILT dim-vector types via the das-side.dimpush — they now wrap via the new public ast_boost helpermake_fixed_array_type(canonical hoist); walk_and_convert dispatches on tFixedArray; fixedDim/fixedDimExpr exposed on the das TypeDecl (pulled forward from 1f by necessity — dastest→clargs→$vhits this path at boot). Gates: tests-cpp 56/56 (int[5] flipped), daslang boots, tests/fixed_array 86/86, zero GC leaks; full-tree dastest inventory = the 1f burndown list. -
1f debug-info helper flatten; AST serializer FA fields (payload side + version bump landed at 1b-i); module_builtin_ast bindings: expose new fields + extend the
.dim/.dimExprcompat properties with the flattened-array view (payload side landed at 1b-i). Inference semantics flip in this stage; fallout fixes in tests/daslib land here. Exit: full CI green with aot_cpp.das / llvm_jit.das / macro daslib UNMODIFIED (riding compat). 1f IMPLEMENTED. The 11-file burndown collapsed into four classes: (1) GENERIC ALIAS DIM SEMANTICS (PUGIXML/json/decs) — settled BY BORIS, REVISED once: NATURAL semantics. A generic alias binds the WHOLE matched type:auto(TT)←int[4]makesTT = int[4];TT[2]=int[2][4](array of 2 TT, natural nesting);array<auto(TT)>←array<int[3]>makesTT = int[3](inexpressible on master);def two(a : auto(TT); b : TT)accepts(int[3], int[3]). Module typedefs append as always. THE OLD (master/flattened-world) RULE IS DELIBERATELY DEAD: master stripped the bound dims at every use-site (bareTT←int[3]was the ELEMENTint;TT[4]REPLACED toint[4]; strip applied even througharray<auto(TT)>) — an artifact of one node carrying both element and dims; Boris: wrong, makes generics clumsy and unsupportable. (A master-compat peel was implemented first and reverted same-day — the probe matrix in D:/Work/fa_scratch/alias_dim_probe*.das documents both worlds.) IMPORTANT INVARIANT that keeps most of daslib working unchanged:auto(TT)[](the explicit[]suffix) still binds TT to the ELEMENT — the[]eats one dim level in matching. So builtin.das clone/to_array/table-[]families and decs get_ro'sTT[typeinfo dim(value)]reconstruct stay correct. Ported to the new binding: the bare-auto(TT)dim arms — json_boost from_JV (var ret : TT -const -&), PUGIXML from_XML (same), decs decs_array/get/get_default_ro/get_component/ComponentMap-get/ make_callbacks/make_component (everystatic_if is_dimarm collapsed into its else arm — visibly simpler, the point of the flip). The FA-recursion arm in inferAlias HOISTS the resolved element's ref/const/temporary to the chain head (canonical form). EXTERNAL BREAKAGE accepted: code using theTT[typeinfo dim(x)]reconstruct on bareauto(TT)params breaks loudly at compile; bodies using bareTTas the element type change meaning silently — test suites are the net. (2) DASLIB.dimREADERS (match tests, stbimage→is_local, aot_cpp): the das.dimbinding is now a READ-ONLY compat property (TypeDecl::dimCompat) returning the flattened outermost-first sizes of the FA chain, recomputed on read into a per-node transient member (dimCompatCache— NOT the legacydimfield: warming that would nondeterministically revive dead C++ dim-vector loops on FA nodes). 59 daslib read sites ride it unmodified. das-side WRITERS break at compile by design and were ported now (the forced exceptions to "daslib unmodified"): match.das pop→clone_type(what .firstType), tests/typemacro fixture + tutorials/macros twin →make_fixed_array_type, dasGlsl produce_zero, dasLLVM llvm_jit×3 dim-clear→element-walk (+ thenew Handle[N]tHandle gate now walks the chain; LLVM_JIT_CODEGEN_VERSION bumped 0x22→0x23). (3) aot_cpp had FOUR FA gaps (all red on the 1d+1e train's AOT lane too): describeCppTypeEx PANICKED on FA nodes (das_to_cppString"Missed type tFixedArray") and emission SWALLOWS macro panics — struct fields/local decls silently vanish from generated C++; dim'd make-VARIANT field emission dropped itsdas_get_variant_field:: setwrapper (find_argument_index on the chain head); dim'd make-STRUCT tHandle gate + get_tuple/variant_field helpers same class; dim'd ExprNew emitted the POINTER type where das_new_dim<> wants the POINTEE (TDim<T**> mismatch) and its isHandle/smartPtr reads missed (das_new_dim_handle silently mis-branched to das_new_dim). Fix: dims emit from the head's.dimview, all structural dispatch walks to the chain element via a newfa_elementclone-walk helper (clone-walks so the result is non-const whatever flavor the caller holds — bare field-walks inherit const and break downstream calls). TDim<TDim<float,3>,2> nesting verified; full test_aot regen+compile+run green. BUILD GOTCHA: genaot custom-commands and the consuming compile are separate vcxprojs — when emitter output CHANGES, one--target test_aotpass can compile stale outputs; run the build twice (second pass converges). Plus MSBuild skipped relinking test_aot.exe after a deleted-output (stale .tlog) — delete build/test_aot.dir/Release/ test_aot.tlog to force. (4) invalid_types ERROR CODES: verifyType's FA too-big check now multiplies element COUNTS across the chain (master parity: 32767² passes infer; lint reports byte-size with site-specific exceeds_* codes). Plus decs.das get_ro declared its dim'd return asTT[typeinfo sizeof(value)](bytes!) — worked on master only because an unresolvable dim-expr in a generic result type was silently discarded in favor of the body-inferred type; fixed totypeinfo dim(value). KNOWN GAP (deliberate):-[](removeDim) on FA-typed generic params still erases the legacy vector only — zero in-tree users; revisit at Stage 4/5. Gates (re-run after the natural-semantics flip): probe matrix = natural binding everywhere, all 11 burndown files green, tests-cpp 56/56, tests/fixed_array 86/86, full-tree dastest 10784/10784 interpreted + 10123/10123 under test_aot -use-aot, zero GC leaks both modes, lint 0 errors on every changed file. -
1g — container-FA
[]overload deletion (Stage 4's builtin.das half, pulled forward per Boris's review note on the 1d–1f train: "bunch of other ones, same 'identical' flavor"). IMPLEMENTED. Deleted the 22 dedicated[]overloads in builtin.das where the FA lives in a CONTAINER's element/value slot — natural binding makes the base generics cover them with byte-identical bodies: array<T[N]> push x2 / emplace / push_clone x2; table<K;V[N]> get x4 / get_value / insert x2 / insert_clone x2 / emplace / values x2 / get_key / clone x2; one-arg clone x2. Resolution check: PRE- deletion the dedicated forms still out-specialized the naturally-matching base (no ambiguity); post-deletion the base takes over. KEPT (C-array-as-subject, the[]IS the API): each / finalize_dim / clone_dim / sort / stable_sort / find_index(_if) / subarray / to_array(_move) / to_table(_move) / strings_boost join / decs get_ro / llvm_boost array_data_ptr. archive.das: the 6 stacked [][]..[] serialize overloads collapsed to ONE natural recursion (auto(TT)[] peels a level per call); bulk-memcpy gate =is_raw && (is_workhorse || is_dim)— exact wire-format parity incl. master's quirk (1-D raw STRUCT arrays go element-wise, 2-D+ memcpy), and >6-D now works. CAPABILITY GAINS: push_clone of clone-only rows (array[2]) — the dedicated form gated can_copy and concept_assert'd; its const-src sibling's body was un-instantiable rot (pushed scalar elements into FA slots); at-index push of FA rows; size mismatch is now a no-match type error instead of concept_assert.default<T[N]>FIXED (was broken on master too, just unreachable — TT never bound to FA): ExprMakeStruct with 0 provided elements on an FA makeType now means zero-init the whole declared shape, any depth. Two sites: infer dimension check tolerates 0 (ast_infer_type_make.cpp ~:537), simulate zero-fill uses getSizeOf() when total==0 (ast_simulate.cpp simulateExprMakeStruct — stride is ONE OUTER ELEMENT and under-zeroed). AOT side already correct (das_zero on the whole TDim). Target spec ENABLED:_target_inference.das→test_target_inference.das(expect line dropped; picked up by the AOT glob automatically). Decoration as landed in 1g: plainauto(TT)← int[4] boundint const[4], butauto(TT)[]← float[4][4] boundfloat[4]UNQUALIFIED — REVISED at Stage-2 review (Boris: "it needs to match"), see the decoration note under Stage 2. get_key subtest is in (Stage 4 gate satisfied); note get_key is ADDRESS-based — the value must reference the table slot (values() iteration), not a local copy.
daslib/aot_cpp.das ports to the structural API; nested TDim emission becomes natural
recursion; AOT version bump. Also fixes issue #3077 (iterate/pass/copy of a NATIVE C-array
field emits non-compiling C++ — found by Stage 0, test_interop.das AOT-excluded since) if
not fixed earlier; re-include test_interop in the AOT glob to verify.
Exit: tests/aot + AOT CI green, test_interop back in the AOT set.
IMPLEMENTED (pending review). describeCppTypeEx: flatten-prefix loop + elemDecl
walk + reversed-size closers replaced by one tFixedArray recursion arm
(TDim<{recurse(firstType)},{fixedDim}>); qualifiers stay on the head (canonical form),
unsized params emit TDim<T,-1> via the same dimAuto sentinel the compat view returned.
Mechanical compat-read swaps: isConstRedundantForCpp / ExprAt / ExprSafeAt / ExprNew gates
-> baseType==tFixedArray; dim[0] -> fixedDim; isLocalVec's dim conjunct dropped
(isVectorType is false on FA wrappers per the 1d classifier rule). Runtime-TypeInfo
writeDim untouched (flattened forever). ISSUE #3077 fixed emitter-side (option B): native
C-array member access (flags.isNativeDim) puns to its TDim view via the existing
das_reinterpret<TDim<...>>::pass() rail at the PRODUCER (both isHandle arms of
ExprField) — one edit point fixes every consumer: iterate (das_iterator ctor), pass
(das_arg), copy (das_copy decay static_assert), plus a 4th context the issue missed:
safe-at (safe_index wants TDim*, got int(*)[3]); aot.h untouched, direct indexing
keeps compiling through TDim::operator[] (unchecked, behavior preserved). test_interop:
options no_aot + AOT-glob exclusion dropped, safe-at + copy-into-native subtests added.
"AOT version bump" resolved as NOTHING TO BUMP: no AOT version constant exists in-tree;
stale-external invalidation rides the semantic hashes Stage 1 already shifted; in-tree
regenerates (genaot depends on daslib/*.das). Gates: 801-file generated-C++ corpus diff
vs pre-port snapshot = ZERO content changes (28 files differ reorder-only — known
hash-iteration emission nondeterminism; sorted-content identical), two-pass test_aot
build green, AOT run 10134/10134 with test_interop in the set, interpreted 10795/10795,
fixed_array 97/97, zero GC leaks both modes, format+lint clean.
DECORATION SETTLED at Stage-2 review (Boris: "it needs to match"): auto(TT)[]
binds with the param's constness, same as plain auto(TT) — non-var auto(TT)[] ←
float[4][4] = float const[4], var = float[4]. This is MASTER PARITY restored (the
flattened world kept label+const+dims on one node; 1g's unqualified peel was a
canonical-form artifact) and closes a const-soundness hole (each(auto(TT)[]) on a
const view instantiated a mutable-element iterator). Implementation: extraction-time,
NOT stored-type-time — TypeDecl::findAlias gains a bool * constUnderDim out-param
that reports a label found through a const FA head along the PURE FA chain (container
boundaries bind bare, as always); threaded through findFuncAlias / InferTypes::findAlias;
inferAlias + inferPartialAliases OR it into the constant merge. Stored instance types
stay canonical (no inner-node const → no mangled-name churn); the instancing alias MAP
still clears const (master parity — sibling b : TT params take use-site quals).
KNOWN LINT GAP surfaced by the var-param test probe (pre-existing on master, NOT
FA-specific): LINT003 suggests let for a var passed to a never-writing var param —
access_ref only sets via SideEffects::modifyArgument, and the write-tracking is
optimizer-shared (conservative marking would cascade modifyArgument up the call graph).
Proper fix = new access flag (PR-#2736-style 4-site checklist + serializer bump).
FILED as issue #3090 (Boris: "lets file an issue, fix it follow up at some point") —
follow-up outside this rework; the test exercises its var param instead (mutable row
copy through the peeled binding, a real assertion).
modules/dasLLVM/daslib/llvm_jit.das port (element type, bounds checks, loop ranges from
nesting; TypeInfo globals unchanged — flattened); bump LLVM_JIT_CODEGEN_VERSION.
Exit: JIT tests green.
IMPLEMENTED. llvm_jit_common.das: type_to_llvm_type's flatten-walk + reversed
LLVMArrayType wrap → one tFixedArray recursion arm (LLVMArrayType(recurse(firstType), fixedDim), identical nesting); dim_element_type_to_llvm_type gate swapped (leaf walk
kept — it wants the scalar element). llvm_jit.das: 8 mechanical gate/dim[0]→
fixedDim swaps (ExprNew codegen, call-arg classification, ExprAt, ExprSafeAt incl.
the ptr-to-FA arm, iterator first()/next(), cast, for-source assert); TypeInfo-globals
emission (info.dim) untouched — flattened forever. LATENT BUG FIXED in BOTH
registration passes (llvm_jit.das preVisitExprNew + llvm_exe.das twin): the
new Handle[N] tHandle gate read raw baseType==tHandle under the dim gate — dead in
the FA world (head is tFixedArray) — so the newhandle pointer was never registered; now walks to the chain element (1f fixed only the codegen visitor). The walk must BORROW, not clone: these passes run outside ast_gc_guard and run on DLL cache HITS too — a clone-walk version leaked 12 TypeDecl nodes per new <T[N]>` test and exposed a
SECOND pre-existing fragility: das::gc_thread_root_report_detailed AVs while
describing leaked nodes whose subtrees were collected (C++ side, surfaced to Boris,
not chased here). LLVM_JIT_CODEGEN_VERSION 0x23→0x24. Gates: full-tree dastest -jit
10374/10374 (gc folder excluded by -jit design) after a fresh-cache prewarm, zero GC
leaks, zero crashes; lint+format clean on all four files.
Delete the + [] workaround families in builtin.dasget_key subtest enablement —
DONE EARLY in 1g (see above), archive.das collapse included. Remaining here: generalize
each/sort/find_index/subarray/finalize_dim (multi-dim/typedef'd sources through
one overload where natural nesting allows). Un-reject fixed arrays in apply.das /
contracts.das where they now just work.
Exit: new multi-dim/typedef'd-array tests pass through the generic stdlib; LOC visibly
negative.
IMPLEMENTED. Probe (D:\Work\fa_scratch\stage4_generics_probe.das) showed the
generalization mostly ALREADY HOLDS via natural nesting — each (multi-dim rows +
typedef'd M4[N] elements), find_index(_if) over rows, subarray over rows (returns
array<int[2]>, FA element preserved), clone_dim multi-dim, finalize_dim over
array[N] all just work; pinned in tests/fixed_array/test_stdlib_generics.das.
ONE real gap fixed: sort-with-row-cmp — the [builtin_array_sort] transformCall
(module_builtin_runtime_sort.cpp) BYPASSES the daslib body and rewrote the call from
the chain LEAF (my 1e boot-gate port preserved master's dim.back() semantics: leaf
element + leaf size stride + INNERMOST length — wrong rows, silent garbage on master
too); now one-peel from the head (element=row, stride=row size, length=outer count;
1-D bit-identical), routing rows to __builtin_sort_dim_any_cblock — the daslib else
arm was already correct. No-cmp multi-dim sort now errors cleanly ("no matching <
(int[2], int[2])") where master silently flat-sorted dim.back() scalars.
apply.das: both "can't apply to dim" macro_verify rejects + 3 internal
assert(empty(.dim)) DELETED — dead post-flip (FA fails the adjacent baseType gates
with better messages). contracts.das: expect_any_array dim-read → baseType==
tFixedArray (still accepts FA); expect_any_enum / expect_any_vector_type dim clauses
dropped (redundant — FA head baseType already fails). DEFERRED to Stage 5: the 1f
-[] removeDim gap (zero in-tree users; macro-API territory). Cosmetic, noted:
typeinfo typename of an M4[N] element prints structural "float[4][4]", not "M4"
(display label vs typename canonicalization). Gates: 10807/10807 interp, 10146/10146
AOT (two-pass), 10386/10386 JIT, zero leaks all lanes, fmt+lint clean.
ast_match.das (structural FA matching), match.das, typemacro_boost (field rename), lints
(perf_lint/style_lint), is_local, templates_boost, decs_boost, clargs, rst.das; MCP
describe_type sanity.
Exit: no in-tree consumers of the compat properties left.
IMPLEMENTED. typeMacroExpr exposed das-side (module_builtin_ast_annotations_1);
all typemacro/typeDecl/tag payload readers renamed off the .dimExpr compat name
(typemacro_boost, templates_boost, clargs, ast_match $t-tag, sqlite_boost Option
unwrap, tutorials/macros + tests/typemacro fixtures). ~50 .dim gate reads swapped
to baseType==tFixedArray (or deleted where an adjacent baseType pin subsumed them)
across style_lint/perf_lint/is_local/sort_boost/ast_boost/shader_dsl_boost/
sqlite_linq/fix-lint-errors. match.das: the dim[last] INNERMOST reads (struct/variant/
array count checks) → head fixedDim (struct/variant dim arms were dead on master
too — the isStructure gate rejects dim'd values; array arm is the live one, now
consistent with its one-peel recT). ast_match generate_type_match: flattened dim
list+entries compare → per-node fixedDim compare (chain depth/shape rides the
existing firstType recursion); payload compare renamed. rst.das describe_type: FA
chains previously fell through to the raw enum-name fallback (quiet 1f-era doc
regression) — proper FA arm added (element text + per-level [d]); legacy suffix loop
deleted. dasGlsl glsl_internal: describe_glsl_type_ex element-walk + chain suffix
(was erroring on FA heads), write_dim_suffix helper replaces 3 suffix loops,
produce_zero head reads, for-loop bound was another INNERMOST read (sort-bug family)
→ head fixedDim. removeDim -[] GAP CLOSED (deferred from 1f): structural one-level
peel at the two pointer-returning alias-substitution sites (inferAlias +
inferPartialAliases; quals ride to the new head); dead legacy-vector erase in
applyAutoContracts deleted; pinned as type<TT - []> ← int[2][4] = "int const[4]"
in test_target_inference (note: formatter spells the contract - []). RIDE-ALONG:
18 pre-existing lint warnings in sqlite_boost fixed (file now touched → hook lints
it): 10 PERF020, 5 PERF023, 2 LINT010, 1 LINT013. Exit grep CLEAN: remaining
.dim hits are runtime-TypeInfo emission (aot_cpp/llvm_jit, flattened by design)
and a game field. Gates: 10808/10808 interp, 10147/10147 AOT (two-pass),
10387/10387 JIT, dasSQLITE 904/904, tests/match 51/51, typemacro 11/11, zero leaks
all lanes, fmt+lint clean on all 21 das files.
Sweep D:\DASPKG modules (dasImgui, dasSQLITE, ...); delete .dim/.dimExpr compat
properties; optional dims(td) helper lands in ast_boost.
Exit: grep-clean everywhere.
IMPLEMENTED. EXTERNALS: only consumer shape found across D:\DASPKG was the
ImVec2/ImVec4 pass-by-value fixup guard arg->type->dim.size()==0 (6 sites:
dasImgui cb_dasIMGUI.h + dasIMGUI.main.cpp, dasImguiNodeEditor ×2, dasImguiImplot
×2) → !arg->type->isFixedArray(). That helper doesn't exist on master, so the
edits live in the local checkouts and push upstream only after this branch merges.
daScript-plugin completion_boost.das dim consumers stay parked for the
post-everything plugin sweep. DELETION: TypeDecl dim/dimExpr fields,
dimCompat/dimExprCompat helpers + dimCompatCache, and the das-side .dim/.dimExpr
compat properties are gone. makeTypeInfo (ast_debug_info_helper) reworked
chain-native — the flattened-scratch TypeDecl (last real in-tree writer of legacy
dim, and the thing keeping legacy-dim semantics alive inside
getSizeOf/getMangledName/describeCppType/gcFlags) is deleted; dims emit straight
into info->dim, element fields read via elemType, whole-type semantics stay on the
chain head. ~120 vacuous guard sites simplified (ast_typedecl.h predicates,
ast_typedecl.cpp, ast_infer_type.cpp incl. the dead legacy ExprAt/ExprSafeAt/
for-source dim-peel arms, helper/make/validate/escape/program, dasSQLITE.main.cpp).
Semantic/lookup-hash dim loops deleted — empty vectors contributed zero bytes, so
hashes are unchanged (JIT DLL cache hits confirm; no LLVM_JIT_CODEGEN_VERSION bump).
Serialization drops dim/dimExpr from all TypeDecl chains; version 90 → 91. Legacy
2-arg appendDimExpr parser overload deleted; utils/dasFormatter vendored gen1
grammar got the minimal mechanical flip (3-arg appendDimExpr chains, attachDimChain,
appendAutoDim, typeMacroExpr payloads incl. MTAG_T tag, FA-typed {{...}}
make-table — mirrors the in-tree 1b edit; bison .cpp regenerated by CMake).
tests-cpp parity fixtures rewritten against pinned literals (the dim-vector
counterpart can no longer be constructed). FOUND ALONG THE WAY: daslang CLI startup
runs every modules//.das_module descriptor (dyn_modules.cpp), which dlopens
.shared_module DLLs — any DLL built pre-deletion is ABI-stale (TypeDecl shrank) and
CRASHES daslang at startup even for scripts that never require it. After this
commit, junctioned module stacks (dasImgui, dasImguiImplot under modules/, node
editor under D:\Work\IMGUI) must be rebuilt before the new daslang runs next to
them. NOTED: das-fmt (utils/dasFormatter converter) --tests has one failing
self-test (#10, gen1 {{k=>v}} global) — the conversion TEXT is correct and the
failure is an infer error in its verify-compile step that does NOT reproduce through
any in-tree parser path (original gen1, converted gen1, gen2 equivalents all compile
clean in daslang.exe); --tests has zero CI coverage (CI's das-fmt.exe is the
unrelated AOT-compiled dasfmt.das formatter). Converter functional verification
remains THE THIRD PARSER, scheduled last. dims(td) ast_boost helper SKIPPED — the
Stage-5 sweep left no das-side consumer needing a flattened view. Gates: tests-cpp
56/56 (1.1M assertions), 10808/10808 interp, 10147/10147 AOT (two-pass,
content-stable regen), 10387/10387 JIT (DLL cache hits = hash invariance proof),
dasSQLITE 904/904, dasImgui + nodeEditor + implot stacks rebuilt against the new
tree and daslang startup verified loading the fresh .shared_module DLLs (junctions
restored).
RST (type system, generic programming), CHANGELOG, version bump; final full-matrix CI +
AOT + JIT verification; merge to master. Optional separate decision: lift the
MyMacro(...)[N] parser restriction.
THE THIRD PARSER (settled: very last item, after everything else is done): fix
utils/dasFormatter/ds_parser.ypp — the gen1->gen2 conversion util vendors its own copy
of the gen1 grammar, writing the real TypeDecl::dim/dimExpr through the same 10
site-shapes as the in-tree gen1 parser. Caveat: deleting dim/dimExpr at the end of
Stage 1 breaks its COMPILE, so the field-deletion commit carries the minimal mechanical
flip (same edit as the in-tree gen1 parser got in 1b — by then the helpers exist);
the full port/verification of the converter is what lands here at the end.
RESOLVED (Boris, Stage-6 review): leave das-fmt alone, fix later → issue #3094.
The mechanical flip landed in Stage 6; its 8 failing --tests self-tests are
PRE-EXISTING — proven identical on master 4d2e98f22, branch pre-deletion 1cf7e4f66,
and post-deletion 326b9f925 (worktree A/B/C builds). All 8 are the converter's own
verify-compile environment (conversion text correct; same texts compile via
daslang.exe); zero CI coverage. Tool is migration-era, rarely used.
AFTER THE LAST STAGE (Boris, Stage-2 review): VSCode plugin fixes —
D:\DASPKG\daScript-plugin. Sweep it for rework fallout (TypeDecl dim/dimExpr
consumers, describe/typename output expectations) once everything else has landed.
- const/ref/temporary propagation through FA levels — the subtle-bug budget; audited with targeted tests per stage.
- Layout invariance — Stage 0 is the gate; any diff is a stop-the-line bug (C++ interop depends on identical layout).
- Overload-resolution shifts — new
auto[]matches that didn't exist before; explicit ranking rule + tests. - Known generic-instance mangling collisions (error 50609) may surface new shapes.
- AOT hash desync / serialization — version bumps; skills/aot_hash_desync_debugging.md.
- gen1 parser (
ds_parser.ypp) parity with gen2.