From e119bb79b3c7bf3ce14c82361a03f3f2dba54437 Mon Sep 17 00:00:00 2001 From: Boris Batkin Date: Wed, 10 Jun 2026 01:19:42 -0700 Subject: [PATCH 01/23] fixed-array rework stage 1a: additive Type::tFixedArray core machinery Adds the structural fixed-array node and all core TypeDecl support, gated on baseType==tFixedArray throughout. Nothing produces FA nodes yet (parsers flip in 1b), so this stage is behavior-neutral: describe/typename text, mangled names, semantic/lookup hashes and AOT hashes are unchanged for every type constructible today. dim/dimExpr stay live until the end of Stage 1. - Type::tFixedArray appended at the END of the enum (AST-only; runtime TypeInfo stays flattened forever). Registered in das_to_string / das_to_cppCTypeString / the rtti-side Type enum binding. The C API das_base_type deliberately does NOT get the value - it describes runtime TypeInfo, which never carries it. - TypeDecl fields: fixedDim / fixedDimExpr (one size per node, element in firstType, dimAuto/dimConst sentinels) + typeMacroExpr (dormant until 1b moves the typeMacro/typeDecl/tag payload off dimExpr). - Lifecycle: copy ctor, static clone, gc_collect, visit cover the new fields. - Identity: isSameType gets a tFixedArray case (fixedDim + element recursion mirroring the tArray arm) with the canonical-form debug assert - settled: ref/const/temporary live on the OUTERMOST FA node only. isSameExactType compares fixedDim. - Text: describe emits the flattened element-first form ("float[3][4][4]"), byte-matching the dim-vector output. Mangled name emits by NATURAL RECURSION (settled): per-node [d] + Y, element inline - unaliased chains byte-identical to master; mid-chain aliases shift the Y<> slot to the level it labels ("[3][4]Y[4]f"), covered by the planned one-time version bumps. Mangled-name PARSE flip rides 1b - the [N] text is identical in both worlds, so the parser must build whichever representation the program uses. describeCppType reproduces TDim,N> nesting exactly. - Size family: getCountOf64 / getStride64 / getBaseSizeOf64 / getAlignOf / getSizeOf64-failed chain-walking arms preserve the dim-vector meanings. - Classifier sweep: FA is transparent to classification exactly like the old dim vector (peel/recurse arms beside every tArray arm). Deliberate calls: gcFlags recurses WITHOUT gcFlag_heap (inline storage); hasNonTrivialCtor DOES recurse (FA elements are live at init, unlike empty array); isCircularType descends FA (no heap indirection to break cycles); isVecPolicyType guard extended; canDelete peels FA on both self and pointee; collectAliasing recursion is slightly conservative vs the old form (extra bare-element append, noted in-source for the 1b review). - tests-cpp/small/test_fixed_array_typedecl.cpp: hand-built FA nodes proven against equivalent dim-vector nodes (gc_guard cleanup) - describe/mangled/ cppType text equality, size family incl. float3[4]=48, isSameType matrix, deep clone of fixedDimExpr, semantic/lookup hash discrimination, classification parity, and the settled mid-chain-alias mangling golden. Gate: full local Release build green (incl. utils -exe steps + AOT objects), tests-cpp-small 50/50 (1.1M assertions, leak-check clean), dastest 10784/10784, test_aot 10123/10123 run exactly as CI does. Note for local setups: the TypeDecl layout change requires rebuilding external .shared_module binaries (dasImgui / dasImguiImplot junctions rebuilt here). Co-Authored-By: Claude Fable 5 --- FIXED_ARRAY_REWORK.md | 33 +- include/daScript/ast/ast_typedecl.h | 15 +- include/daScript/simulate/debug_info.h | 5 +- src/ast/ast_typedecl.cpp | 299 ++++++++++++++++-- src/builtin/module_builtin_rtti.h | 3 +- src/simulate/debug_info.cpp | 1 + tests-cpp/small/test_fixed_array_typedecl.cpp | 241 ++++++++++++++ 7 files changed, 555 insertions(+), 42 deletions(-) create mode 100644 tests-cpp/small/test_fixed_array_typedecl.cpp diff --git a/FIXED_ARRAY_REWORK.md b/FIXED_ARRAY_REWORK.md index a769c23c21..4982c65951 100644 --- a/FIXED_ARRAY_REWORK.md +++ b/FIXED_ARRAY_REWORK.md @@ -54,9 +54,11 @@ structural nodes with `firstType` recursion. Fixed arrays are the only container No typemacro argument-model redesign in this train. - Net ~−12B per node; every predicate drops the `dim.size()` branch. - `isNativeDim` moves onto the FA node; `typeFactory` wraps instead of pushes. -- **Qualifier discipline**: `constant`/`ref`/`temporary` on the OUTER node describe the - whole object — same rule as `tArray`. Peeling a level (`a[i]`, iteration, `auto(TT)[]`) - = `firstType` plus the same constness propagation tArray element access uses. +- **Qualifier discipline (canonical form, settled at 1a)**: `constant`/`ref`/`temporary` + live ONLY on the outermost FA node; inner chain nodes are bare — same rule as `tArray` + elements. Peeling a level (`a[i]`, iteration, `auto(TT)[]`) = `firstType` plus the same + constness propagation tArray element access uses. One type, one shape; Stage 1d enforces + at construction sites, a debug assert in `isSameType` catches 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 with `array`) @@ -69,9 +71,14 @@ structural nodes with `firstType` recursion. Fixed arrays are the only container and the flattened form IS the layout. Single AST->runtime conversion point (`ast_debug_info_helper.cpp`) walks the nested chain and emits `dim[]={3,4,4}` exactly as today. GC walker, data walker, debug print, JIT TypeInfo globals, `daslib/rtti` untouched. -- **Mangled-name TEXT unchanged** (`[3][4][4]f` is already a nested prefix encoding): - emitted from nesting, parsed into nesting. AOT symbol names don't churn. Semantic-hash, - AST-serializer, and `LLVM_JIT_CODEGEN_VERSION` bumps happen once. +- **Mangled name: natural recursion (settled at 1a)**. Each FA node emits its own + qualifiers + `[d]` + alias slot, recursing into `firstType`; 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 the `Y<>` slot to the level it labels + (`[3][4]Y[4]f` vs master's flattened `[3][4][4]Yf`); the already-planned + one-time semantic-hash / AST-serializer / `LLVM_JIT_CODEGEN_VERSION` bumps 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`/`.dimExpr` compat @@ -110,11 +117,17 @@ Exit: suite green on the branch point; target tests reviewed as the spec. ### Stage 1 — Core representation flip The indivisible piece, sub-staged for review: -- **1a** TypeDecl fields + `Type::tFixedArray` + predicates + clone/visit/gc/sanitize + - describe + mangled name (emit + parse) + `isSameType` + semantic/lookup hash + - getSizeOf/stride/align family. +- **1a** (settled: ADDITIVE, byte-neutral, full CI green standalone) — `Type::tFixedArray` + appended + 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 on + `baseType==tFixedArray`; nothing produces FA nodes yet; `dim`/`dimExpr` stay 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** Parsers (ds2 + ds1 + parser_impl): build nested FA chains; typeMacro/typeDecl/tag - move to `typeMacroExpr`; keep existing grammar errors. + move to `typeMacroExpr`; mangled-name parse builds FA; keep existing grammar errors. - **1c** typeFactory / interop (`TT[dim]`, `TDim<>`, `isNativeDim`, makeArgumentType, ast_handle). - **1d** Infer: inferAlias (WRAP, don't concatenate — alias label preserved), diff --git a/include/daScript/ast/ast_typedecl.h b/include/daScript/ast/ast_typedecl.h index 8122090726..d9130028ac 100644 --- a/include/daScript/ast/ast_typedecl.h +++ b/include/daScript/ast/ast_typedecl.h @@ -75,6 +75,7 @@ namespace das { __forceinline bool isSimpleType () const; __forceinline bool isSimpleType ( Type typ ) const; __forceinline bool isArray() const; + __forceinline bool isFixedArray() const; __forceinline bool isGoodIteratorType() const; __forceinline bool isGoodArrayType() const; __forceinline bool isGoodTableType() const; @@ -258,6 +259,14 @@ namespace das { vector argNames; vector dim; vector dimExpr; + // tFixedArray rework (FIXED_ARRAY_REWORK.md), Stage 1a. fixedDim/fixedDimExpr are + // meaningful only on baseType==tFixedArray nodes (one size per node, element in + // firstType, dimAuto/dimConst sentinels apply). typeMacroExpr takes over dimExpr's + // typeMacro/typeDecl/tag payload duty in Stage 1b. dim/dimExpr above are deleted + // at the end of Stage 1. + int32_t fixedDim = 0; + ExpressionPtr fixedDimExpr = nullptr; + vector typeMacroExpr; union { struct { bool ref : 1 ; @@ -796,7 +805,11 @@ namespace das { } __forceinline bool TypeDecl::isArray() const { - return (bool) dim.size(); + return dim.size()!=0 || baseType==Type::tFixedArray; + } + + __forceinline bool TypeDecl::isFixedArray() const { + return baseType==Type::tFixedArray; } __forceinline bool TypeDecl::isRef() const { diff --git a/include/daScript/simulate/debug_info.h b/include/daScript/simulate/debug_info.h index e9b304408f..94e7f1fdc8 100644 --- a/include/daScript/simulate/debug_info.h +++ b/include/daScript/simulate/debug_info.h @@ -70,7 +70,10 @@ namespace das tTable, tBlock, tTuple, - tVariant + tVariant, + tFixedArray // AST-only (FIXED_ARRAY_REWORK.md): structural fixed-array TypeDecl node. + // Runtime TypeInfo never carries it — fixed arrays stay flattened to dim[] + // at the single AST->TypeInfo conversion point (ast_debug_info_helper.cpp). }; enum class RefMatters { diff --git a/src/ast/ast_typedecl.cpp b/src/ast/ast_typedecl.cpp index 5feed6b7de..c19c6b9f3d 100644 --- a/src/ast/ast_typedecl.cpp +++ b/src/ast/ast_typedecl.cpp @@ -121,6 +121,9 @@ namespace das return true; } } + if ( baseType==Type::tFixedArray && fixedDim==TypeDecl::dimConst ) { + return true; + } if ( baseType==Type::tStructure ) { if (structType) { if (dep.find(structType) != dep.end()) return false; @@ -144,6 +147,9 @@ namespace das return true; } } + if ( baseType==Type::tFixedArray && fixedDim==TypeDecl::dimConst ) { + return true; + } if ( firstType && firstType->isExprType() ) return true; if ( secondType && secondType->isExprType() ) return true; for ( auto & arg : argTypes ) { @@ -161,6 +167,15 @@ namespace das dimExpr[i] = dimExpr[i]->visit(vis); } } + for ( size_t i=0, is=typeMacroExpr.size(); i!=is; ++i ) { + if ( typeMacroExpr[i] ) { + typeMacroExpr[i] = typeMacroExpr[i]->visit(vis); + } + } + } else if ( baseType==Type::tFixedArray ) { + if ( fixedDim==TypeDecl::dimConst && fixedDimExpr ) { + fixedDimExpr = fixedDimExpr->visit(vis); + } } else { for ( size_t i=0, is=dim.size(); i!=is; ++i ) { if ( dim[i]==TypeDecl::dimConst ) { @@ -501,6 +516,9 @@ namespace das for ( auto d : dim ) { hash = hashmix(hash, d); } + if ( baseType==Type::tFixedArray ) { + hash = hashmix(hash, fixedDim); + } if ( structType ) { hash = hashmix(hash, intptr_t(structType)); } else if ( enumType ) { @@ -570,6 +588,19 @@ namespace das wr << *(de); } } + if ( baseType==Type::tFixedArray ) { + hb.update(fixedDim); + hb.update(fixedDimExpr != nullptr); + if ( fixedDimExpr ) { + wr << *fixedDimExpr; + } + } + for ( auto & de : typeMacroExpr ) { + hb.update(de != nullptr); + if ( de ) { + wr << *(de); + } + } hb.update(flagsWithoutAliasCache(this)); hb.updateString(wr.str()); return hb.getHash(); @@ -624,6 +655,19 @@ namespace das wr << *(de); } } + if ( baseType==Type::tFixedArray ) { + hb.update(fixedDim); + hb.update(fixedDimExpr != nullptr); + if ( fixedDimExpr ) { + wr << *fixedDimExpr; + } + } + for ( auto & de : typeMacroExpr ) { + hb.update(de != nullptr); + if ( de ) { + wr << *(de); + } + } hb.update(flagsWithoutAliasCache(this)); hb.updateString(wr.str()); return hb.getHash(); @@ -689,6 +733,18 @@ namespace das } } stream << ")"; + } else if ( baseType==Type::tFixedArray ) { + // element-first flattened text ("float[3][4][4]"), byte-matching the old + // dim-vector output; the chain's dims print in the suffix section below + const TypeDecl * elem = this; + while ( elem->baseType==Type::tFixedArray && elem->firstType ) { + elem = elem->firstType; + } + if ( elem->baseType==Type::tFixedArray ) { + stream << "fixed array"; // malformed chain (no element) + } else { + stream << elem->describe(extra, contracts, dmodule, aliasDefs); + } } else if ( baseType==Type::tHandle ) { if ( annotation ) { if (dmodule == DescribeModule::yes && annotation->module && !annotation->module->name.empty()) { @@ -839,6 +895,15 @@ namespace das stream << "[" << d << "]"; } } + if ( baseType==Type::tFixedArray ) { + for ( const TypeDecl * t = this; t && t->baseType==Type::tFixedArray; t = t->firstType ) { + if ( t->fixedDim==TypeDecl::dimAuto ) { + stream << "[]"; + } else { + stream << "[" << t->fixedDim << "]"; + } + } + } if ( ref ) { stream << "&"; } @@ -899,6 +964,18 @@ namespace das dimExpr.push_back(nullptr); } } + fixedDim = decl.fixedDim; + if ( decl.fixedDimExpr ) { + fixedDimExpr = decl.fixedDimExpr->clone(); + } + typeMacroExpr.reserve(decl.typeMacroExpr.size()); + for ( auto & de : decl.typeMacroExpr ) { + if ( de ) { + typeMacroExpr.push_back(de->clone()); + } else { + typeMacroExpr.push_back(nullptr); + } + } flags = decl.flags; aliasCacheValid = false; // not cloned — recompute lazily aliasCacheHasAlias = false; @@ -943,6 +1020,16 @@ namespace das dest->dimExpr[i] = nullptr; } } + dest->fixedDim = src->fixedDim; + dest->fixedDimExpr = src->fixedDimExpr ? src->fixedDimExpr->clone() : nullptr; + dest->typeMacroExpr.resize(src->typeMacroExpr.size()); + for ( size_t i=0; i!=src->typeMacroExpr.size(); ++i ) { + if ( src->typeMacroExpr[i] ) { + dest->typeMacroExpr[i] = src->typeMacroExpr[i]->clone(); + } else { + dest->typeMacroExpr[i] = nullptr; + } + } dest->flags = src->flags; dest->aliasCacheValid = false; // not cloned — recompute lazily dest->aliasCacheHasAlias = false; @@ -968,6 +1055,8 @@ namespace das if ( secondType ) secondType->gc_collect(target, from); for ( auto t : argTypes ) if ( t ) t->gc_collect(target, from); for ( auto & de : dimExpr ) if ( de ) de->gc_collect(target, from); + if ( fixedDimExpr ) fixedDimExpr->gc_collect(target, from); + for ( auto & de : typeMacroExpr ) if ( de ) de->gc_collect(target, from); // this?? if ( structType ) structType->gc_collect(target, from); if ( enumType ) enumType->gc_collect(target, from); @@ -1022,18 +1111,26 @@ namespace das } bool TypeDecl::canDelete() const { - if ( baseType==Type::tHandle ) { + if ( baseType==Type::tFixedArray ) { + // fixed-array dims are transparent to classification, same as the old dim vector + return firstType ? firstType->canDelete() : false; + } else if ( baseType==Type::tHandle ) { return annotation->canDelete(); } else if ( baseType==Type::tPointer ) { - if ( !firstType ) { + // peel fixed-array levels — delete of `new Foo[4]` classifies by the element + auto pointee = firstType; + while ( pointee && pointee->baseType==Type::tFixedArray ) { + pointee = pointee->firstType; + } + if ( !pointee ) { return false; - } else if (firstType->baseType==Type::tHandle ) { - return firstType->annotation->canDeletePtr(); - } else if ( firstType->baseType==Type::tStructure ) { + } else if (pointee->baseType==Type::tHandle ) { + return pointee->annotation->canDeletePtr(); + } else if ( pointee->baseType==Type::tStructure ) { return true; - } else if ( firstType->baseType==Type::tTuple ) { + } else if ( pointee->baseType==Type::tTuple ) { return true; - } else if ( firstType->baseType==Type::tVariant ) { + } else if ( pointee->baseType==Type::tVariant ) { return true; } else { return false; @@ -1056,7 +1153,9 @@ namespace das } bool TypeDecl::canDeletePtr() const { - if ( baseType==Type::tHandle ) { + if ( baseType==Type::tFixedArray ) { + return firstType ? firstType->canDeletePtr() : false; + } else if ( baseType==Type::tHandle ) { return annotation->canDeletePtr(); } else if ( baseType==Type::tStructure ) { return true; @@ -1068,7 +1167,9 @@ namespace das } bool TypeDecl::canNew() const { - if ( baseType==Type::tHandle ) { + if ( baseType==Type::tFixedArray ) { + return firstType ? firstType->canNew() : false; + } else if ( baseType==Type::tHandle ) { return annotation->canNew(); } else if ( baseType==Type::tStructure ) { return true; @@ -1080,7 +1181,9 @@ namespace das } bool TypeDecl::needDelete() const { - if ( baseType==Type::tHandle ) { + if ( baseType==Type::tFixedArray ) { + return firstType ? firstType->needDelete() : false; + } else if ( baseType==Type::tHandle ) { return annotation->needDelete(); } else if ( baseType==Type::tPointer ) { return canDelete(); @@ -1123,7 +1226,9 @@ namespace das } bool TypeDecl::canMove() const { - if (baseType == Type::tHandle) { + if (baseType == Type::tFixedArray) { + return firstType ? firstType->canMove() : true; + } else if (baseType == Type::tHandle) { return annotation->canMove(); } else if (baseType == Type::tBlock) { return false; @@ -1140,7 +1245,9 @@ namespace das } bool TypeDecl::canCloneFromConst() const { - if (baseType == Type::tHandle) { + if (baseType == Type::tFixedArray) { + return firstType ? firstType->canCloneFromConst() : true; + } else if (baseType == Type::tHandle) { return annotation->canClone(); } else if (baseType == Type::tStructure && structType) { return !structType->circular ? structType->canCloneFromConst() : false; @@ -1167,7 +1274,9 @@ namespace das } bool TypeDecl::canClone() const { - if (baseType == Type::tHandle) { + if (baseType == Type::tFixedArray) { + return firstType ? firstType->canClone() : true; + } else if (baseType == Type::tHandle) { return annotation->canClone(); } else if (baseType == Type::tStructure && structType) { return !structType->circular ? structType->canClone() : false; @@ -1190,7 +1299,9 @@ namespace das } bool TypeDecl::canCopy(bool tempMatters) const { - if ( baseType == Type::tHandle ) { + if ( baseType == Type::tFixedArray ) { + return firstType ? firstType->canCopy(tempMatters) : true; + } else if ( baseType == Type::tHandle ) { return annotation->canCopy(); } else if (baseType == Type::tArray || baseType == Type::tTable || baseType == Type::tBlock || baseType == Type::tIterator) { return false; @@ -1213,6 +1324,8 @@ namespace das } bool TypeDecl::isNoHeapType() const { + if ( baseType==Type::tFixedArray ) + return firstType ? firstType->isNoHeapType() : true; if ( baseType==Type::tArray || baseType==Type::tTable || baseType==Type::tBlock || baseType==Type::tLambda ) return false; @@ -1232,6 +1345,8 @@ namespace das } bool TypeDecl::isPod() const { + if ( baseType==Type::tFixedArray ) + return firstType ? firstType->isPod() : true; if ( baseType==Type::tArray || baseType==Type::tTable || baseType==Type::tString || baseType==Type::tBlock || baseType==Type::tLambda ) return false; @@ -1252,6 +1367,8 @@ namespace das } bool TypeDecl::isRawPod() const { + if ( baseType==Type::tFixedArray ) + return firstType ? firstType->isRawPod() : true; if ( baseType==Type::tArray || baseType==Type::tTable || baseType==Type::tString || baseType==Type::tBlock || baseType==Type::tLambda || baseType==Type::tFunction ) return false; @@ -1296,7 +1413,7 @@ namespace das } } return false; - } else if ( baseType==Type::tArray || baseType==Type::tTable ) { + } else if ( baseType==Type::tArray || baseType==Type::tTable || baseType==Type::tFixedArray ) { if ( firstType && firstType->hasStringData(dep) ) return true; if ( secondType && secondType->hasStringData(dep) ) return true; } else if ( baseType==Type::tPointer) { @@ -1313,6 +1430,9 @@ namespace das bool TypeDecl::unsafeInit( das_set & dep ) const { if ( safeWhenUninitialized ) return false; + if ( baseType==Type::tFixedArray ) { + return firstType ? firstType->unsafeInit(dep) : false; + } if ( baseType==Type::tHandle ) { return annotation->hasNonTrivialCtor(); } if ( baseType==Type::tStructure ) { @@ -1353,7 +1473,7 @@ namespace das } } return false; - } else if ( baseType==Type::tArray || baseType==Type::tTable ) { + } else if ( baseType==Type::tArray || baseType==Type::tTable || baseType==Type::tFixedArray ) { if ( firstType && firstType->needInScope(dep) ) return true; if ( secondType && secondType->needInScope(dep) ) return true; } else if ( baseType==Type::tPointer) { @@ -1385,7 +1505,7 @@ namespace das return true; } else if ( baseType==Type::tBlock ) { return false; - } else if ( baseType==Type::tArray || baseType==Type::tTable ) { + } else if ( baseType==Type::tArray || baseType==Type::tTable || baseType==Type::tFixedArray ) { if ( firstType && !firstType->canBePlacedInContainer(dep) ) return false; if ( secondType && !secondType->canBePlacedInContainer(dep) ) return false; } @@ -1398,7 +1518,11 @@ namespace das } bool TypeDecl::hasNonTrivialCtor(das_set & dep) const { - if ( baseType==Type::tHandle ) { + if ( baseType==Type::tFixedArray ) { + // unlike tArray (excluded below — an empty array stays uninitialized), a + // fixed array's elements are live at init, so the element's ctor-ness counts + return firstType ? firstType->hasNonTrivialCtor(dep) : false; + } else if ( baseType==Type::tHandle ) { return annotation->hasNonTrivialCtor(); } else if ( baseType==Type::tStructure ) { if (structType) { @@ -1442,7 +1566,7 @@ namespace das return true; } } - } else if ( baseType==Type::tArray || baseType==Type::tTable ) { + } else if ( baseType==Type::tArray || baseType==Type::tTable || baseType==Type::tFixedArray ) { if ( firstType && firstType->hasNonTrivialDtor(dep) ) return true; if ( secondType && secondType->hasNonTrivialDtor(dep) ) return true; } @@ -1469,7 +1593,7 @@ namespace das return true; } } - } else if ( baseType==Type::tArray || baseType==Type::tTable ) { + } else if ( baseType==Type::tArray || baseType==Type::tTable || baseType==Type::tFixedArray ) { if ( firstType && firstType->hasNonTrivialCopy(dep) ) return true; if ( secondType && secondType->hasNonTrivialCopy(dep) ) return true; } @@ -1494,7 +1618,7 @@ namespace das return true; } } - } else if ( baseType==Type::tArray || baseType==Type::tTable ) { + } else if ( baseType==Type::tArray || baseType==Type::tTable || baseType==Type::tFixedArray ) { if ( firstType && firstType->hasClasses(dep) ) return true; if ( secondType && secondType->hasClasses(dep) ) return true; } @@ -1526,6 +1650,10 @@ namespace das gcf |= gcFlag_heap; if ( firstType ) gcf |= firstType->gcFlags(dep,depA); if ( secondType ) gcf |= secondType->gcFlags(dep,depA); + } else if ( baseType==Type::tFixedArray ) { + // inline storage — element flags only, no gcFlag_heap (matches the old + // dim-vector behavior where dims never added heap-ness) + if ( firstType ) gcf |= firstType->gcFlags(dep,depA); } else if ( baseType==Type::tHandle ) { if (depA.find(annotation) != depA.end()) return 0; depA.insert(annotation); @@ -1575,7 +1703,7 @@ namespace das return false; } else if ( baseType==Type::tTable ) { if ( secondType && !secondType->isSafeToDelete(dep) ) return false; - } else if ( baseType==Type::tArray ) { + } else if ( baseType==Type::tArray || baseType==Type::tFixedArray ) { if ( firstType && !firstType->isSafeToDelete(dep) ) return false; } return true; @@ -1607,7 +1735,7 @@ namespace das return true; } else if ( baseType==Type::tBlock ) { return false; - } else if ( baseType==Type::tArray || baseType==Type::tTable ) { + } else if ( baseType==Type::tArray || baseType==Type::tTable || baseType==Type::tFixedArray ) { if ( firstType && !firstType->isLocal(dep) ) return false; if ( secondType && !secondType->isLocal(dep) ) return false; } @@ -1639,6 +1767,7 @@ namespace das || annotation!=decl.annotation || flags!=decl.flags || alias!=decl.alias ) { return false; } + if ( fixedDim != decl.fixedDim ) return false; if ( dim.size() != decl.dim.size() ) return false; for ( size_t i=0, is=dim.size(); i!=is; ++i ) { if ( dim[i] != decl.dim[i] ) { @@ -1787,6 +1916,18 @@ namespace das return false; } break; + case Type::tFixedArray: + // canonical form: ref/const/temporary live on the outermost FA node only + DAS_ASSERT(!firstType || (!firstType->ref && !firstType->constant && !firstType->temporary)); + DAS_ASSERT(!decl.firstType || (!decl.firstType->ref && !decl.firstType->constant && !decl.firstType->temporary)); + if ( fixedDim != decl.fixedDim ) { + return false; + } + if ( firstType && decl.firstType && !firstType->isSameType(*decl.firstType,RefMatters::yes,ConstMatters::yes, + TemporaryMatters::yes,AllowSubstitute::no,false) ) { + return false; + } + break; case Type::tTable: if ( firstType && decl.firstType && !firstType->isSameType(*decl.firstType,RefMatters::yes,ConstMatters::yes, TemporaryMatters::yes,AllowSubstitute::no,false) ) { @@ -2187,6 +2328,11 @@ namespace das } else if ( baseType==Type::tArray ) { if ( firstType ) return firstType->isAliasOrExpr(); + } else if ( baseType==Type::tFixedArray ) { + if ( fixedDim==TypeDecl::dimConst ) + return true; + if ( firstType ) + return firstType->isAliasOrExpr(); } else if ( baseType==Type::tTable ) { bool any = false; if ( firstType ) @@ -2213,6 +2359,11 @@ namespace das return false; } } + for ( const TypeDecl * t = this; t && t->baseType==Type::tFixedArray; t = t->firstType ) { + if ( t->fixedDim==TypeDecl::dimAuto || t->fixedDim==TypeDecl::dimConst ) { + return false; + } + } return true; } @@ -2234,6 +2385,11 @@ namespace das case Type::tIterator: case Type::tArray: return firstType ? firstType->isAuto() : false; + case Type::tFixedArray: + if ( fixedDim==TypeDecl::dimAuto ) { + return true; + } + return firstType ? firstType->isAuto() : false; case Type::tTable: if ( firstType && firstType->isAuto() ) { return true; @@ -2281,6 +2437,11 @@ namespace das } else if ( baseType==Type::tArray ) { if ( firstType ) return firstType->isAutoWithoutOptions(hasOptions); + } else if ( baseType==Type::tFixedArray ) { + if ( fixedDim==TypeDecl::dimAuto ) + return true; + if ( firstType ) + return firstType->isAutoWithoutOptions(hasOptions); } else if ( baseType==Type::tTable ) { bool any = false; if ( firstType ) @@ -2322,6 +2483,11 @@ namespace das case Type::tIterator: case Type::tArray: return firstType ? firstType->isAutoOrAlias() : false; + case Type::tFixedArray: + if ( fixedDim==TypeDecl::dimAuto ) { + return true; + } + return firstType ? firstType->isAutoOrAlias() : false; case Type::tTable: if ( firstType && firstType->isAutoOrAlias() ) { return true; @@ -2552,7 +2718,7 @@ namespace das } bool TypeDecl::isVecPolicyType() const { - if ( dim.size() ) + if ( dim.size() || baseType==Type::tFixedArray ) return false; if ( baseType==Type::tString ) { return false; @@ -2586,7 +2752,7 @@ namespace das return baseType==Type::tTuple || baseType==Type::tVariant || baseType==Type::tStructure || baseType==Type::tArray || baseType==Type::tTable || baseType==Type::tBlock || - baseType==Type::tIterator; + baseType==Type::tIterator || baseType==Type::tFixedArray; } bool TypeDecl::isTemp( bool topLevel, bool refMatters ) const { @@ -2609,7 +2775,7 @@ namespace das } } else if ( baseType==Type::tPointer || baseType==Type::tIterator ) { return firstType ? firstType->isTemp(false, true, dep) : false; - } else if ( baseType==Type::tArray ) { + } else if ( baseType==Type::tArray || baseType==Type::tFixedArray ) { return firstType ? firstType->isTemp(false, true, dep) : false; } else if ( baseType==Type::tTable ) { if ( firstType && firstType->isTemp(false, true, dep) ) { @@ -2640,6 +2806,9 @@ namespace das return false; } } + if ( baseType==Type::tFixedArray && fixedDim==TypeDecl::dimAuto ) { + return false; + } if ( baseType==Type::tStructure ) { if ( structType ) { if ( all.find(structType)!=all.end() ) return true; @@ -2701,7 +2870,7 @@ namespace das } else { return true; } - } else if ( baseType==Type::tArray ) { + } else if ( baseType==Type::tArray || baseType==Type::tFixedArray ) { return firstType ? firstType->isShareable(dep) : true; } else if ( baseType==Type::tTable ) { if ( firstType && !firstType->isShareable(dep) ) { @@ -3062,6 +3231,10 @@ namespace das } uint64_t TypeDecl::getBaseSizeOf64() const { + if ( baseType==Type::tFixedArray ) { + // size of the chain-end element, dims excluded — the dim-vector meaning + return firstType ? firstType->getBaseSizeOf64() : 0; + } if ( baseType==Type::tHandle ) { return annotation->getSizeOf(); } else if ( baseType==Type::tStructure ) { @@ -3078,6 +3251,13 @@ namespace das } uint64_t TypeDecl::getBaseSizeOf64(bool & failed) const { + if ( baseType==Type::tFixedArray ) { + if ( !firstType ) { + failed = true; + return 0; + } + return firstType->getBaseSizeOf64(failed); + } if ( baseType==Type::tHandle ) { return annotation->getSizeOf(); } else if ( baseType==Type::tStructure ) { @@ -3098,6 +3278,9 @@ namespace das } int TypeDecl::getAlignOf() const { + if ( baseType==Type::tFixedArray ) { + return firstType ? firstType->getAlignOf() : 1; + } if ( baseType==Type::tHandle ) { return int(annotation->getAlignOf()); } else if ( baseType==Type::tStructure ) { @@ -3114,6 +3297,13 @@ namespace das } int TypeDecl::getAlignOfFailed(bool &failed) const { + if ( baseType==Type::tFixedArray ) { + if ( !firstType ) { + failed = true; + return 1; + } + return firstType->getAlignOfFailed(failed); + } if ( baseType==Type::tHandle ) { return int(annotation->getAlignOf()); } else if ( baseType==Type::tStructure ) { @@ -3144,6 +3334,9 @@ namespace das for ( auto i : dim ) { size *= i; } + for ( const TypeDecl * t = this; t && t->baseType==Type::tFixedArray; t = t->firstType ) { + size *= uint64_t(t->fixedDim); + } return size; } @@ -3164,6 +3357,12 @@ namespace das return 0; } } + for ( const TypeDecl * t = this; t && t->baseType==Type::tFixedArray; t = t->firstType ) { + if ( t->fixedDim==TypeDecl::dimAuto || t->fixedDim==TypeDecl::dimConst ) { + failed = true; + return 0; + } + } return getBaseSizeOf64(failed) * getCountOf64(); } @@ -3174,6 +3373,10 @@ namespace das } uint64_t TypeDecl::getStride64() const { + if ( baseType==Type::tFixedArray ) { + // size of one outer-level element, same meaning the dim-vector form had + return firstType ? firstType->getSizeOf64() : 0; + } uint64_t size = 1; if ( dim.size() > 1 ) { for ( size_t i=1, is=dim.size(); i!=is; ++i ) { @@ -3223,6 +3426,9 @@ namespace das for ( auto & arg : type->argTypes ) { if ( isCircularType(arg, all) ) return true; } + } else if ( type->baseType==Type::tFixedArray ) { + // inline storage — no heap indirection to break the cycle, must descend + if ( type->firstType && isCircularType(type->firstType, all) ) return true; } all.pop_back(); return false; @@ -3254,6 +3460,24 @@ namespace das if ( explicitRef ) ss << "R"; if ( isExplicit ) ss << "X"; if ( aotAlias ) ss << "F"; + if ( baseType==Type::tFixedArray ) { + // natural recursion: this node's [d] + alias, then the element directly (no 1<> + // wrapper) — unaliased chains stay byte-identical to the old dim-vector text + ss << "[" << fixedDim << "]"; + if ( fullName ) { + if ( removeDim ) ss << "-[]"; + if ( removeConstant ) ss << "-C"; + if ( removeRef ) ss << "-&"; + if ( removeTemporary ) ss << "-#"; + } + if ( !alias.empty() ) { + ss << "Y<" << alias << ">"; + } + if ( firstType ) { + firstType->getMangledName(ss, fullName); + } + return; + } if ( dim.size() ) { for ( auto d : dim ) { ss << "[" << d << "]"; @@ -3411,7 +3635,9 @@ namespace das void TypeDecl::collectAliasing ( TypeAliasMap & aliases, das_set & dep, bool viaPointer ) const { append(aliases, (TypeDecl *) this, viaPointer); if ( temporary ) return; // temporary types never alias - if ( baseType==Type::tArray ) { + if ( baseType==Type::tArray || baseType==Type::tFixedArray ) { + // for tFixedArray this is slightly more conservative than the old dim-vector + // form (the recursion also appends the bare element); conservative == safe if ( firstType ) { firstType->collectAliasing(aliases, dep, viaPointer); } @@ -3440,7 +3666,13 @@ namespace das void TypeDecl::collectContainerAliasing ( TypeAliasMap & aliases, das_set & dep, bool viaPointer ) const { if ( constant ) return; - if ( baseType==Type::tArray ) { + if ( baseType==Type::tFixedArray ) { + // dispatch as the element would — dims were transparent qualifiers before the + // rework, so no element append here (parity with the dim-vector behavior) + if ( firstType && !firstType->constant ) { + firstType->collectContainerAliasing(aliases, dep, viaPointer); + } + } else if ( baseType==Type::tArray ) { if ( firstType && !firstType->constant ) { append(aliases, firstType, viaPointer); firstType->collectContainerAliasing(aliases, dep, viaPointer); @@ -3548,6 +3780,7 @@ namespace das { Type::tLambda, "tLambda"}, { Type::tTuple, "tTuple"}, { Type::tVariant, "tVariant"}, + { Type::tFixedArray, "tFixedArray"}, { Type::tHandle, "tHandle"} }; @@ -3730,6 +3963,14 @@ namespace das } else { stream << "Array"; } + } else if ( baseType==Type::tFixedArray ) { + // natural recursion reproduces the old TDim,3> nesting exactly + if ( type->firstType ) { + stream << "TDim<" << describeCppTypeEx(type->firstType,CpptSubstitureRef::no,CpptSkipRef::no,CpptSkipConst::no,CpptRedundantConst::yes, chooseSmartPtr) + << "," << type->fixedDim << ">"; + } else { + stream << "TDim"; + } } else if ( baseType==Type::tTable ) { if ( type->firstType && type->secondType ) { stream << "TTable<" << describeCppTypeEx(type->firstType,CpptSubstitureRef::no,CpptSkipRef::no,CpptSkipConst::yes,CpptRedundantConst::yes, chooseSmartPtr) diff --git a/src/builtin/module_builtin_rtti.h b/src/builtin/module_builtin_rtti.h index 00a3881c21..ea9bc4d79f 100644 --- a/src/builtin/module_builtin_rtti.h +++ b/src/builtin/module_builtin_rtti.h @@ -14,7 +14,8 @@ DAS_BASE_BIND_ENUM(das::Type, Type, tDouble, tRange, tURange, tRange64, tURange64, tString, tStructure, tHandle, tEnumeration, tEnumeration8, tEnumeration16, tEnumeration64, tBitfield, tBitfield8, tBitfield16, tBitfield64, tPointer, tFunction, - tLambda, tIterator, tArray, tTable, tBlock, tTuple, tVariant + tLambda, tIterator, tArray, tTable, tBlock, tTuple, tVariant, + tFixedArray ) DAS_BASE_BIND_ENUM(das::RefMatters, RefMatters, no, yes) diff --git a/src/simulate/debug_info.cpp b/src/simulate/debug_info.cpp index cb3861448a..461e438f43 100644 --- a/src/simulate/debug_info.cpp +++ b/src/simulate/debug_info.cpp @@ -60,6 +60,7 @@ namespace das { Type::tLambda, "lambda"}, { Type::tTuple, "tuple"}, { Type::tVariant, "variant"}, + { Type::tFixedArray, "fixed array"}, { Type::fakeContext, "__context"}, { Type::fakeLineInfo, "__lineInfo"}, }; diff --git a/tests-cpp/small/test_fixed_array_typedecl.cpp b/tests-cpp/small/test_fixed_array_typedecl.cpp new file mode 100644 index 0000000000..21a3f9e383 --- /dev/null +++ b/tests-cpp/small/test_fixed_array_typedecl.cpp @@ -0,0 +1,241 @@ +// Stage 1a of the tFixedArray rework (FIXED_ARRAY_REWORK.md): the structural +// Type::tFixedArray machinery is additive and nothing in the language produces FA nodes +// yet, so this suite hand-builds them and proves the new arms against equivalent +// old-style dim-vector nodes — text (describe/mangled/cpp), size family, identity, +// lifecycle, hashes, and classification parity. +#include +#include "daScript/daScript.h" +#include "daScript/ast/ast.h" +#include "daScript/ast/ast_expressions.h" +using namespace das; + +namespace { + +TypeDecl * makeFA ( int32_t d, TypeDecl * elem ) { + auto fa = new TypeDecl(Type::tFixedArray); + fa->fixedDim = d; + fa->firstType = elem; + return fa; +} + +TypeDecl * makeOldDim ( Type bt, std::initializer_list dims ) { + auto t = new TypeDecl(bt); + for ( auto d : dims ) t->dim.push_back(d); + return t; +} + +TypeDecl * makeFAChain ( Type bt, std::initializer_list dims ) { + auto t = new TypeDecl(bt); + TypeDecl * result = t; + for ( auto it = rbegin(dims); it != rend(dims); ++it ) { + result = makeFA(*it, result); + } + return result; +} + +} + +TEST_CASE("tFixedArray text matches dim-vector nodes") { + gc_guard guard; + SUBCASE("describe") { + CHECK_EQ(makeFAChain(Type::tInt,{4})->describe(), + makeOldDim(Type::tInt,{4})->describe()); + CHECK_EQ(makeFAChain(Type::tFloat,{3,4,4})->describe(), + makeOldDim(Type::tFloat,{3,4,4})->describe()); + CHECK_EQ(makeFAChain(Type::tFloat,{3,4,4})->describe(), "float[3][4][4]"); + auto faAuto = makeFAChain(Type::tInt,{TypeDecl::dimAuto}); + auto oldAuto = makeOldDim(Type::tInt,{TypeDecl::dimAuto}); + CHECK_EQ(faAuto->describe(), oldAuto->describe()); + CHECK_EQ(faAuto->describe(), "int[]"); + auto faConst = makeFAChain(Type::tInt,{4}); + faConst->constant = true; + auto oldConst = makeOldDim(Type::tInt,{4}); + oldConst->constant = true; + CHECK_EQ(faConst->describe(), oldConst->describe()); + CHECK_EQ(faConst->describe(), "int const[4]"); + } + SUBCASE("mangled name") { + CHECK_EQ(makeFAChain(Type::tInt,{4})->getMangledName(), + makeOldDim(Type::tInt,{4})->getMangledName()); + CHECK_EQ(makeFAChain(Type::tInt,{4})->getMangledName(), "[4]i"); + CHECK_EQ(makeFAChain(Type::tFloat,{3,4,4})->getMangledName(), + makeOldDim(Type::tFloat,{3,4,4})->getMangledName()); + CHECK_EQ(makeFAChain(Type::tFloat,{3,4,4})->getMangledName(), "[3][4][4]f"); + auto faQ = makeFAChain(Type::tInt,{4}); + faQ->constant = true; + faQ->ref = true; + auto oldQ = makeOldDim(Type::tInt,{4}); + oldQ->constant = true; + oldQ->ref = true; + CHECK_EQ(faQ->getMangledName(), oldQ->getMangledName()); + CHECK_EQ(faQ->getMangledName(), "C&[4]i"); + CHECK_EQ(makeFAChain(Type::tInt,{4})->getMangledNameHash(), + makeOldDim(Type::tInt,{4})->getMangledNameHash()); + } + SUBCASE("mid-chain alias mangles per level (settled: natural recursion)") { + auto m4 = makeFAChain(Type::tFloat,{4,4}); + m4->alias = "M4"; + auto m4x3 = makeFA(3, m4); + CHECK_EQ(m4x3->getMangledName(), "[3][4]Y[4]f"); + } + SUBCASE("describeCppType") { + CHECK_EQ(describeCppType(makeFAChain(Type::tInt,{3,4})), + describeCppType(makeOldDim(Type::tInt,{3,4}))); + CHECK_EQ(describeCppType(makeFAChain(Type::tInt,{3,4})), "TDim,3>"); + } +} + +TEST_CASE("tFixedArray size family matches dim-vector nodes") { + gc_guard guard; + auto fa = makeFAChain(Type::tFloat,{3,4,4}); + auto old = makeOldDim(Type::tFloat,{3,4,4}); + CHECK_EQ(fa->getSizeOf64(), old->getSizeOf64()); + CHECK_EQ(fa->getSizeOf64(), uint64_t(192)); + CHECK_EQ(fa->getCountOf64(), old->getCountOf64()); + CHECK_EQ(fa->getCountOf64(), uint64_t(48)); + CHECK_EQ(fa->getStride64(), old->getStride64()); + CHECK_EQ(fa->getStride64(), uint64_t(64)); + CHECK_EQ(fa->getBaseSizeOf64(), old->getBaseSizeOf64()); + CHECK_EQ(fa->getBaseSizeOf64(), uint64_t(4)); + CHECK_EQ(fa->getAlignOf(), old->getAlignOf()); + SUBCASE("float3[4] stays 48 bytes (memory matters)") { + auto fa3 = makeFAChain(Type::tFloat3,{4}); + auto old3 = makeOldDim(Type::tFloat3,{4}); + CHECK_EQ(fa3->getSizeOf64(), old3->getSizeOf64()); + CHECK_EQ(fa3->getSizeOf64(), uint64_t(48)); + CHECK_EQ(fa3->getAlignOf(), old3->getAlignOf()); + } + SUBCASE("unresolved dims fail sizeof") { + bool faFailed = false, oldFailed = false; + makeFAChain(Type::tInt,{TypeDecl::dimAuto})->getSizeOf64(faFailed); + makeOldDim(Type::tInt,{TypeDecl::dimAuto})->getSizeOf64(oldFailed); + CHECK(faFailed); + CHECK_EQ(faFailed, oldFailed); + } +} + +TEST_CASE("tFixedArray identity") { + gc_guard guard; + auto i4a = makeFAChain(Type::tInt,{4}); + auto i4b = makeFAChain(Type::tInt,{4}); + auto i3 = makeFAChain(Type::tInt,{3}); + auto f44a = makeFAChain(Type::tFloat,{4,4}); + auto f44b = makeFAChain(Type::tFloat,{4,4}); + auto f34 = makeFAChain(Type::tFloat,{3,4}); + auto justInt = new TypeDecl(Type::tInt); + auto oldI4 = makeOldDim(Type::tInt,{4}); + auto arrInt = new TypeDecl(Type::tArray); + arrInt->firstType = new TypeDecl(Type::tInt); + auto same = [](TypeDecl * a, TypeDecl * b) { + return a->isSameType(*b, RefMatters::yes, ConstMatters::yes, TemporaryMatters::yes); + }; + CHECK(same(i4a,i4b)); + CHECK(same(f44a,f44b)); + CHECK_FALSE(same(i4a,i3)); + CHECK_FALSE(same(f44a,f34)); + CHECK_FALSE(same(i4a,justInt)); + CHECK_FALSE(same(i4a,arrInt)); + // the two representations never meet within one build; structural inequality is by design + CHECK_FALSE(same(i4a,oldI4)); + CHECK(i4a->isSameExactType(*i4b)); + CHECK_FALSE(i4a->isSameExactType(*i3)); + SUBCASE("outer const participates per constMatters") { + auto cFA = makeFAChain(Type::tInt,{4}); + cFA->constant = true; + CHECK_FALSE(same(cFA,i4a)); + CHECK(cFA->isSameType(*i4a, RefMatters::no, ConstMatters::no, TemporaryMatters::no)); + } +} + +TEST_CASE("tFixedArray lifecycle and hashes") { + gc_guard guard; + SUBCASE("copy constructor deep-copies the new fields") { + auto src = makeFA(TypeDecl::dimConst, new TypeDecl(Type::tInt)); + src->fixedDimExpr = new ExprConstInt(7); + auto copy = new TypeDecl(*src); + CHECK_EQ(copy->fixedDim, TypeDecl::dimConst); + REQUIRE(copy->fixedDimExpr != nullptr); + CHECK(copy->fixedDimExpr != src->fixedDimExpr); + REQUIRE(copy->firstType != nullptr); + CHECK_EQ(copy->firstType->baseType, Type::tInt); + } + SUBCASE("static clone deep-copies the new fields") { + auto src = makeFA(TypeDecl::dimConst, new TypeDecl(Type::tInt)); + src->fixedDimExpr = new ExprConstInt(7); + TypeDeclPtr dest = nullptr; + TypeDecl::clone(dest, src); + REQUIRE(dest != nullptr); + CHECK_EQ(dest->fixedDim, TypeDecl::dimConst); + REQUIRE(dest->fixedDimExpr != nullptr); + CHECK(dest->fixedDimExpr != src->fixedDimExpr); + } + SUBCASE("hashes discriminate size, rank, and element") { + auto i4 = makeFAChain(Type::tInt,{4}); + auto i3 = makeFAChain(Type::tInt,{3}); + auto f4 = makeFAChain(Type::tFloat,{4}); + auto i44 = makeFAChain(Type::tInt,{4,4}); + auto justInt = new TypeDecl(Type::tInt); + CHECK_NE(i4->getSemanticHash(), i3->getSemanticHash()); + CHECK_NE(i4->getSemanticHash(), f4->getSemanticHash()); + CHECK_NE(i4->getSemanticHash(), i44->getSemanticHash()); + CHECK_NE(i4->getSemanticHash(), justInt->getSemanticHash()); + auto lookup = [](TypeDecl * t) { uint64_t h = 0; t->getLookupHash(h); return h; }; + CHECK_NE(lookup(i4), lookup(i3)); + CHECK_NE(lookup(i4), lookup(i44)); + CHECK_NE(lookup(i4), lookup(justInt)); + } +} + +TEST_CASE("tFixedArray classification parity with dim-vector nodes") { + gc_guard guard; + auto fa = makeFAChain(Type::tInt,{4}); + auto old = makeOldDim(Type::tInt,{4}); + CHECK(fa->isArray()); + CHECK_EQ(fa->isArray(), old->isArray()); + CHECK(fa->isFixedArray()); + CHECK_EQ(fa->isRefType(), old->isRefType()); + CHECK_EQ(fa->canCopy(), old->canCopy()); + CHECK_EQ(fa->canMove(), old->canMove()); + CHECK_EQ(fa->canClone(), old->canClone()); + CHECK_EQ(fa->isPod(), old->isPod()); + CHECK_EQ(fa->isRawPod(), old->isRawPod()); + CHECK_EQ(fa->isNoHeapType(), old->isNoHeapType()); + CHECK_EQ(fa->isLocal(), old->isLocal()); + CHECK_EQ(fa->isShareable(), old->isShareable()); + CHECK_EQ(fa->isWorkhorseType(), old->isWorkhorseType()); + CHECK_EQ(fa->isFoldable(), old->isFoldable()); + CHECK_EQ(fa->canInitWithZero(), old->canInitWithZero()); + CHECK_EQ(fa->isPolicyType(), old->isPolicyType()); + CHECK_EQ(fa->isVecPolicyType(), old->isVecPolicyType()); + CHECK_EQ(fa->unsafeInit(), old->unsafeInit()); + CHECK_EQ(fa->needInScope(), old->needInScope()); + CHECK_EQ(fa->gcFlags(), old->gcFlags()); + CHECK_EQ(fa->isAuto(), old->isAuto()); + CHECK_EQ(fa->isAutoOrAlias(), old->isAutoOrAlias()); + CHECK_EQ(fa->isAutoArrayResolved(), old->isAutoArrayResolved()); + CHECK_EQ(fa->isExprType(), old->isExprType()); + SUBCASE("string element carries string gc flags through the FA level") { + auto faS = makeFAChain(Type::tString,{4}); + auto oldS = makeOldDim(Type::tString,{4}); + CHECK_EQ(faS->gcFlags(), oldS->gcFlags()); + CHECK_EQ(faS->hasStringData(), oldS->hasStringData()); + CHECK(faS->hasStringData()); + } + SUBCASE("auto dims register as auto") { + auto faA = makeFAChain(Type::tInt,{TypeDecl::dimAuto}); + auto oldA = makeOldDim(Type::tInt,{TypeDecl::dimAuto}); + CHECK_EQ(faA->isAuto(), oldA->isAuto()); + CHECK(faA->isAuto()); + CHECK_EQ(faA->isAutoArrayResolved(), oldA->isAutoArrayResolved()); + CHECK_FALSE(faA->isAutoArrayResolved()); + auto faElemAuto = makeFA(4, new TypeDecl(Type::autoinfer)); + CHECK(faElemAuto->isAuto()); + } + SUBCASE("dimConst registers as expression type") { + auto faC = makeFAChain(Type::tInt,{TypeDecl::dimConst}); + auto oldC = makeOldDim(Type::tInt,{TypeDecl::dimConst}); + CHECK_EQ(faC->isExprType(), oldC->isExprType()); + CHECK(faC->isExprType()); + CHECK_EQ(faC->isAliasOrExpr(), oldC->isAliasOrExpr()); + } +} From 1918ba12cc99a1d714416345ed1e4106f3923793 Mon Sep 17 00:00:00 2001 From: Boris Batkin Date: Wed, 10 Jun 2026 02:07:12 -0700 Subject: [PATCH 02/23] fixed-array 1b-i: typeMacro/typeDecl/tag payload moves dimExpr -> typeMacroExpr MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Atomic payload flip (FIXED_ARRAY_REWORK.md, Stage 1b first commit; CI-green standalone — arrays stay on dim vectors until 1b-ii): - parsers: gen2 x6 / gen1 x4 payload writes go to typeMacroExpr (typeMacro name+args, typedecl(expr), $t(expr) tag on its autoinfer firstType) - C++ reads: typeMacroName, describe (tag/typeDecl/typeMacro arms), mangled emit (^ — text byte-identical), moreSpecialized typeMacro comparison, inferPartialAliases, inferTypeExpr typeDecl resolution - validator: tracks typeMacroExpr (incl. tag payload on non-payload baseTypes, which the standard visitor never walks) + resolved-FA fixedDimExpr analog - serializer: typeMacroExpr serialized unconditionally after the baseType switch (covers payload nodes AND tag-on-autoinfer); payload arms drop dimExpr; tFixedArray arm added; version 89 -> 90. Note: no in-tree test lane exercises AstSerializer — symmetry of the shared read/write path + version bump is the safety here - das binding (pulled forward from 1f, settled): .dimExpr field becomes a read-only compat property dimExprCompat() — non-empty typeMacroExpr wins, "whatever dimExpr used to hold for this node" — so typemacro_boost, clargs, ast_match, templates_boost keep working unmodified through Stage 1 - remaining dimExpr references classified: array semantics (infer erase/ dimConst resolution -> 1d) or still-live-field infrastructure (visit/clone/ gc/hash/serializer arms — die with the field at end of Stage 1) Gates (all green): full Release build incl. utils -exe + AOT regen; tests-cpp-small 50/50 (1,103,300 asserts); dastest 10784/10784; test_aot 10123/10123 (CI invocation). Co-Authored-By: Claude Fable 5 --- FIXED_ARRAY_REWORK.md | 28 ++++++++++++++++--- include/daScript/ast/ast_typedecl.h | 9 ++++++ src/ast/ast.cpp | 6 ++-- src/ast/ast_infer_type_function.cpp | 12 ++++---- src/ast/ast_infer_type_helper.cpp | 10 +++---- src/ast/ast_typedecl.cpp | 18 ++++++------ src/ast/ast_validate.cpp | 13 +++++++++ .../module_builtin_ast_annotations_1.cpp | 7 ++++- src/builtin/module_builtin_ast_serialize.cpp | 13 +++++++-- src/parser/ds2_parser.cpp | 20 ++++++------- src/parser/ds2_parser.ypp | 20 ++++++------- src/parser/ds_parser.cpp | 12 ++++---- src/parser/ds_parser.ypp | 12 ++++---- 13 files changed, 118 insertions(+), 62 deletions(-) diff --git a/FIXED_ARRAY_REWORK.md b/FIXED_ARRAY_REWORK.md index 4982c65951..e95db5c2e8 100644 --- a/FIXED_ARRAY_REWORK.md +++ b/FIXED_ARRAY_REWORK.md @@ -126,8 +126,27 @@ The indivisible piece, sub-staged for review: 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** Parsers (ds2 + ds1 + parser_impl): build nested FA chains; typeMacro/typeDecl/tag - move to `typeMacroExpr`; mangled-name parse builds FA; keep existing grammar errors. +- **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` -> `typeMacroExpr` atomically: 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 + `.dimExpr` binding becomes a read-only compat property ("whatever dimExpr used to hold + for this node": non-empty `typeMacroExpr` wins, covers the tag payload on autoinfer + nodes), and the AST serializer carries `typeMacroExpr` (+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.** `appendDimExpr` becomes 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; `alias` + NEVER 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 `{{ }}` synthesized `auto[]`, mangled + PARSE `case '['` builds FA — with the rule that a `Y` immediately following `[d]` + labels THAT FA node (known cosmetic asymmetry: `[3]Yi` re-parses with the label on + the FA node even if it was on the element; structural identity unaffected). Grammar + errors kept verbatim. Gate: tests-cpp parse-shape + mangled round-trip suites green; + dastest framework still runs (concrete FA in daslib is only decs/faker/profiler/regex) + and its FA-failure inventory becomes the 1c/1d/1e burndown list. - **1c** typeFactory / interop (`TT[dim]`, `TDim<>`, `isNativeDim`, makeArgumentType, ast_handle). - **1d** Infer: inferAlias (WRAP, don't concatenate — alias label preserved), @@ -139,8 +158,9 @@ The indivisible piece, sub-staged for review: 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. -- **1f** debug-info helper flatten; AST serializer (+version bump); module_builtin_ast - bindings: expose new fields + computed read-only `.dim`/`.dimExpr` compat. +- **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`/ + `.dimExpr` compat 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). diff --git a/include/daScript/ast/ast_typedecl.h b/include/daScript/ast/ast_typedecl.h index d9130028ac..f5d030d77d 100644 --- a/include/daScript/ast/ast_typedecl.h +++ b/include/daScript/ast/ast_typedecl.h @@ -267,6 +267,15 @@ namespace das { int32_t fixedDim = 0; ExpressionPtr fixedDimExpr = nullptr; vector typeMacroExpr; + // das-binding compat view for `.dimExpr` — "whatever dimExpr used to hold for this + // node" (the typeMacro/typeDecl/tag payload lives in typeMacroExpr since Stage 1b). + // Dies with dim/dimExpr at the end of Stage 1, replaced by the flattened-array view. + __forceinline vector & dimExprCompat() { + return typeMacroExpr.empty() ? dimExpr : typeMacroExpr; + } + __forceinline const vector & dimExprCompat() const { + return typeMacroExpr.empty() ? dimExpr : typeMacroExpr; + } union { struct { bool ref : 1 ; diff --git a/src/ast/ast.cpp b/src/ast/ast.cpp index ee61a4d758..417d384b55 100644 --- a/src/ast/ast.cpp +++ b/src/ast/ast.cpp @@ -1401,9 +1401,9 @@ namespace das { } string TypeDecl::typeMacroName() const { - if ( dimExpr.size()<1 ) return ""; - if ( dimExpr[0]->rtti_isStringConstant() ) { - return ((ExprConstString *)dimExpr[0])->text; + if ( typeMacroExpr.size()<1 ) return ""; + if ( typeMacroExpr[0]->rtti_isStringConstant() ) { + return ((ExprConstString *)typeMacroExpr[0])->text; } else { return ""; } diff --git a/src/ast/ast_infer_type_function.cpp b/src/ast/ast_infer_type_function.cpp index d5d8857d77..e2551063fd 100644 --- a/src/ast/ast_infer_type_function.cpp +++ b/src/ast/ast_infer_type_function.cpp @@ -1152,8 +1152,8 @@ namespace das { // 5. if both are typemacros, we need to pick the more specialized one if (t1->baseType == Type::typeMacro && t2->baseType == Type::typeMacro) { // the one with more arguments wins - size_t d1 = t1->dimExpr.size(); - size_t d2 = t2->dimExpr.size(); + size_t d1 = t1->typeMacroExpr.size(); + size_t d2 = t2->typeMacroExpr.size(); if (d1 != d2) { return d1 < d2 ? -1 : 1; } @@ -1162,11 +1162,11 @@ namespace das { bool more = false; for (size_t d = 0; d != d1; ++d) { TypeDeclPtr t1Arg = nullptr, t2Arg = nullptr; - if (t1->dimExpr[d]->rtti_isTypeDecl()) { - t1Arg = static_cast(t1->dimExpr[d])->typeexpr; + if (t1->typeMacroExpr[d]->rtti_isTypeDecl()) { + t1Arg = static_cast(t1->typeMacroExpr[d])->typeexpr; } - if (t2->dimExpr[d]->rtti_isTypeDecl()) { - t2Arg = static_cast(t2->dimExpr[d])->typeexpr; + if (t2->typeMacroExpr[d]->rtti_isTypeDecl()) { + t2Arg = static_cast(t2->typeMacroExpr[d])->typeexpr; } if (t1Arg && t2Arg) { // only if both are types, we can compare diff --git a/src/ast/ast_infer_type_helper.cpp b/src/ast/ast_infer_type_helper.cpp index 006ac35ac4..0a311110c4 100644 --- a/src/ast/ast_infer_type_helper.cpp +++ b/src/ast/ast_infer_type_helper.cpp @@ -453,7 +453,7 @@ namespace das { TypeDeclPtr InferTypes::inferPartialAliases(const TypeDeclPtr &decl, const TypeDeclPtr &passType, const FunctionPtr &fptr, AliasMap *aliases) const { if (decl->baseType == Type::typeDecl || decl->baseType == Type::typeMacro) { auto resT = new TypeDecl(*decl); - for (auto &de : resT->dimExpr) { + for (auto &de : resT->typeMacroExpr) { if (de && de->rtti_isTypeDecl()) { auto td = static_cast(de); // since we don't have passType in typeexpr(3), we pass what we have @@ -674,12 +674,12 @@ namespace das { } } } else if (type->baseType == Type::typeDecl) { - if (type->dimExpr.size() != 1) { + if (type->typeMacroExpr.size() != 1) { error("typeDecl must have exactly one dimension", "", "", type->at, CompilationError::invalid_type_dimension); - } else if (type->dimExpr[0]->type) { - if (!type->dimExpr[0]->type->isAutoOrAlias()) { - auto resType = new TypeDecl(*type->dimExpr[0]->type); + } else if (type->typeMacroExpr[0]->type) { + if (!type->typeMacroExpr[0]->type->isAutoOrAlias()) { + auto resType = new TypeDecl(*type->typeMacroExpr[0]->type); resType->ref = false; TypeDecl::applyAutoContracts(resType, type); type = resType; diff --git a/src/ast/ast_typedecl.cpp b/src/ast/ast_typedecl.cpp index c19c6b9f3d..8d36a86d43 100644 --- a/src/ast/ast_typedecl.cpp +++ b/src/ast/ast_typedecl.cpp @@ -694,8 +694,8 @@ namespace das if ( isTag ) { if ( firstType) { stream << "$$("; - if ( firstType->dimExpr.size()==1 ) { - stream << *(firstType->dimExpr[0]); + if ( firstType->typeMacroExpr.size()==1 ) { + stream << *(firstType->typeMacroExpr[0]); } stream << ")"; } else { @@ -717,17 +717,17 @@ namespace das } } } else if ( baseType==Type::typeDecl ) { - if ( dimExpr.size()==1 ) { - stream << "typedecl(" << (*dimExpr[0]) << ")"; + if ( typeMacroExpr.size()==1 ) { + stream << "typedecl(" << (*typeMacroExpr[0]) << ")"; } else { stream << "typedecl(/*invalid expression*/)"; } } else if ( baseType==Type::typeMacro ) { stream << "$" << typeMacroName() << "("; - for ( size_t i=1; i!=dimExpr.size(); ++i ) { + for ( size_t i=1; i!=typeMacroExpr.size(); ++i ) { if ( i!=1 ) stream << ","; - if ( dimExpr[i] ) { - stream << *(dimExpr[i]); + if ( typeMacroExpr[i] ) { + stream << *(typeMacroExpr[i]); } else { stream << "/*invalid expression*/"; } @@ -3551,9 +3551,9 @@ namespace das } } else if ( baseType==Type::typeMacro ) { TextWriter tw; - for ( size_t i=1; i"; } else { diff --git a/src/ast/ast_validate.cpp b/src/ast/ast_validate.cpp index 76c3446745..325af26e0d 100644 --- a/src/ast/ast_validate.cpp +++ b/src/ast/ast_validate.cpp @@ -98,6 +98,10 @@ namespace das { for ( auto de : td->dimExpr ) { trackExpression(de); } + for ( auto de : td->typeMacroExpr ) { + trackExpression(de); + } + trackExpression(td->fixedDimExpr); currentField = savedField; } void trackExpression ( Expression * expr ) { @@ -161,6 +165,15 @@ namespace das { trackExpression(td->dimExpr[i]); } } + // tag payload sits in typeMacroExpr of an autoinfer node — never visited by + // the standard visitor (it only walks typeMacroExpr on typeDecl/typeMacro) + for ( auto de : td->typeMacroExpr ) { + trackExpression(de); + } + } + // resolved tFixedArray keeps fixedDimExpr on gc_root but the visitor skips it + if ( td->baseType == Type::tFixedArray && td->fixedDim != TypeDecl::dimConst ) { + trackExpression(td->fixedDimExpr); } } // Expression — standard visitor path diff --git a/src/builtin/module_builtin_ast_annotations_1.cpp b/src/builtin/module_builtin_ast_annotations_1.cpp index b04d74397a..9b40919ffb 100644 --- a/src/builtin/module_builtin_ast_annotations_1.cpp +++ b/src/builtin/module_builtin_ast_annotations_1.cpp @@ -36,7 +36,12 @@ namespace das { addField("argTypes"); addField("argNames"); addField("dim"); - addField("dimExpr"); + // compat view (FIXED_ARRAY_REWORK.md, 1b): the typeMacro/typeDecl/tag payload + // moved to typeMacroExpr; daslib keeps reading it under the .dimExpr name + addPropertyExtConst< + vector & (TypeDecl::*)(), &TypeDecl::dimExprCompat, + const vector & (TypeDecl::*)() const, &TypeDecl::dimExprCompat + >("dimExpr","dimExprCompat"); addFieldEx ( "flags", "flags", offsetof(TypeDecl, flags), makeTypeDeclFlags() ); addField("alias"); addField("at"); diff --git a/src/builtin/module_builtin_ast_serialize.cpp b/src/builtin/module_builtin_ast_serialize.cpp index 2933c46a53..81878de6f9 100644 --- a/src/builtin/module_builtin_ast_serialize.cpp +++ b/src/builtin/module_builtin_ast_serialize.cpp @@ -1135,10 +1135,15 @@ namespace das { switch ( baseType ) { case Type::typeMacro: case Type::typeDecl: - ser << alias << dimExpr; + ser << alias; DAS_VERIFYF_MULTI(!annotation, !structType, !enumType, !firstType, !secondType, argTypes.empty(), argNames.empty()); break; + case Type::tFixedArray: + ser << alias << firstType << fixedDim << fixedDimExpr; + DAS_VERIFYF_MULTI(!annotation, !structType, !enumType, !secondType, + argTypes.empty(), argNames.empty()); + break; case Type::alias: ser << alias << firstType << dim << dimExpr; DAS_VERIFYF_MULTI(!annotation, !structType, !enumType, !secondType, @@ -1247,6 +1252,10 @@ namespace das { break; } + // unconditional: typeMacro/typeDecl payload, and the tag payload riding on an + // autoinfer firstType (FIXED_ARRAY_REWORK.md, 1b) + ser << typeMacroExpr; + ser << flags << at << module; } @@ -2698,7 +2707,7 @@ namespace das { } uint32_t AstSerializer::getVersion () { - static constexpr uint32_t currentVersion = 89; + static constexpr uint32_t currentVersion = 90; // 90: typeMacroExpr/fixedDim/fixedDimExpr (FIXED_ARRAY_REWORK.md, 1b) return currentVersion; } diff --git a/src/parser/ds2_parser.cpp b/src/parser/ds2_parser.cpp index f037b19d38..5559a9428e 100644 --- a/src/parser/ds2_parser.cpp +++ b/src/parser/ds2_parser.cpp @@ -9747,7 +9747,7 @@ YYLTYPE yylloc = yyloc_default; (yyval.pTypeDecl)->isTag = true; (yyval.pTypeDecl)->firstType = new TypeDecl(Type::autoinfer); (yyval.pTypeDecl)->firstType->at = tokAt(scanner, (yylsp[-1])); - (yyval.pTypeDecl)->firstType->dimExpr.push_back((yyvsp[-1].pExpression)); + (yyval.pTypeDecl)->firstType->typeMacroExpr.push_back((yyvsp[-1].pExpression)); } break; @@ -9996,7 +9996,7 @@ YYLTYPE yylloc = yyloc_default; { (yyval.pTypeDecl) = new TypeDecl(Type::typeDecl); (yyval.pTypeDecl)->at = tokRangeAt(scanner,(yylsp[-3]),(yylsp[-1])); - (yyval.pTypeDecl)->dimExpr.push_back((yyvsp[-1].pExpression)); + (yyval.pTypeDecl)->typeMacroExpr.push_back((yyvsp[-1].pExpression)); } break; @@ -10004,8 +10004,8 @@ YYLTYPE yylloc = yyloc_default; { (yyval.pTypeDecl) = new TypeDecl(Type::typeMacro); (yyval.pTypeDecl)->at = tokRangeAt(scanner,(yylsp[-3]), (yylsp[-1])); - (yyval.pTypeDecl)->dimExpr = sequenceToList((yyvsp[-1].pExpression)); - (yyval.pTypeDecl)->dimExpr.insert((yyval.pTypeDecl)->dimExpr.begin(), new ExprConstString(tokAt(scanner,(yylsp[-3])), *(yyvsp[-3].s))); + (yyval.pTypeDecl)->typeMacroExpr = sequenceToList((yyvsp[-1].pExpression)); + (yyval.pTypeDecl)->typeMacroExpr.insert((yyval.pTypeDecl)->typeMacroExpr.begin(), new ExprConstString(tokAt(scanner,(yylsp[-3])), *(yyvsp[-3].s))); delete (yyvsp[-3].s); } break; @@ -10014,8 +10014,8 @@ YYLTYPE yylloc = yyloc_default; { (yyval.pTypeDecl) = new TypeDecl(Type::typeMacro); (yyval.pTypeDecl)->at = tokRangeAt(scanner,(yylsp[-1]), (yylsp[0])); - (yyval.pTypeDecl)->dimExpr = sequenceToList((yyvsp[0].pExpression)); - (yyval.pTypeDecl)->dimExpr.insert((yyval.pTypeDecl)->dimExpr.begin(), new ExprConstString(tokAt(scanner,(yylsp[-1])), *(yyvsp[-1].s))); + (yyval.pTypeDecl)->typeMacroExpr = sequenceToList((yyvsp[0].pExpression)); + (yyval.pTypeDecl)->typeMacroExpr.insert((yyval.pTypeDecl)->typeMacroExpr.begin(), new ExprConstString(tokAt(scanner,(yylsp[-1])), *(yyvsp[-1].s))); delete (yyvsp[-1].s); } break; @@ -10028,8 +10028,8 @@ YYLTYPE yylloc = yyloc_default; { (yyval.pTypeDecl) = new TypeDecl(Type::typeMacro); (yyval.pTypeDecl)->at = tokRangeAt(scanner,(yylsp[-5]), (yylsp[0])); - (yyval.pTypeDecl)->dimExpr = typesAndSequenceToList((yyvsp[-2].pTypeDeclList),(yyvsp[0].pExpression)); - (yyval.pTypeDecl)->dimExpr.insert((yyval.pTypeDecl)->dimExpr.begin(), new ExprConstString(tokAt(scanner,(yylsp[-5])), *(yyvsp[-5].s))); + (yyval.pTypeDecl)->typeMacroExpr = typesAndSequenceToList((yyvsp[-2].pTypeDeclList),(yyvsp[0].pExpression)); + (yyval.pTypeDecl)->typeMacroExpr.insert((yyval.pTypeDecl)->typeMacroExpr.begin(), new ExprConstString(tokAt(scanner,(yylsp[-5])), *(yyvsp[-5].s))); delete (yyvsp[-5].s); } break; @@ -10042,8 +10042,8 @@ YYLTYPE yylloc = yyloc_default; { (yyval.pTypeDecl) = new TypeDecl(Type::typeMacro); (yyval.pTypeDecl)->at = tokRangeAt(scanner,(yylsp[-5]), (yylsp[0])); - (yyval.pTypeDecl)->dimExpr = typesAndSequenceToList((yyvsp[-2].pTypeDeclList),(yyvsp[0].pExpression)); - (yyval.pTypeDecl)->dimExpr.insert((yyval.pTypeDecl)->dimExpr.begin(), new ExprConstString(tokAt(scanner,(yylsp[-5])), *(yyvsp[-5].s))); + (yyval.pTypeDecl)->typeMacroExpr = typesAndSequenceToList((yyvsp[-2].pTypeDeclList),(yyvsp[0].pExpression)); + (yyval.pTypeDecl)->typeMacroExpr.insert((yyval.pTypeDecl)->typeMacroExpr.begin(), new ExprConstString(tokAt(scanner,(yylsp[-5])), *(yyvsp[-5].s))); delete (yyvsp[-5].s); } break; diff --git a/src/parser/ds2_parser.ypp b/src/parser/ds2_parser.ypp index 53e50fae71..1dad80cd61 100644 --- a/src/parser/ds2_parser.ypp +++ b/src/parser/ds2_parser.ypp @@ -3145,7 +3145,7 @@ auto_type_declaration $$->isTag = true; $$->firstType = new TypeDecl(Type::autoinfer); $$->firstType->at = tokAt(scanner, @subexpr); - $$->firstType->dimExpr.push_back($subexpr); + $$->firstType->typeMacroExpr.push_back($subexpr); } ; @@ -3318,34 +3318,34 @@ type_declaration_no_options_no_dim | DAS_TYPEDECL[td] '(' expr[subexpr] ')' { $$ = new TypeDecl(Type::typeDecl); $$->at = tokRangeAt(scanner,@td,@subexpr); - $$->dimExpr.push_back($subexpr); + $$->typeMacroExpr.push_back($subexpr); } | name_in_namespace[name] '(' optional_expr_list[arguments] ')' { $$ = new TypeDecl(Type::typeMacro); $$->at = tokRangeAt(scanner,@name, @arguments); - $$->dimExpr = sequenceToList($arguments); - $$->dimExpr.insert($$->dimExpr.begin(), new ExprConstString(tokAt(scanner,@name), *$name)); + $$->typeMacroExpr = sequenceToList($arguments); + $$->typeMacroExpr.insert($$->typeMacroExpr.begin(), new ExprConstString(tokAt(scanner,@name), *$name)); delete $name; } | '$' name_in_namespace[name] optional_expr_list_in_braces[arguments] { $$ = new TypeDecl(Type::typeMacro); $$->at = tokRangeAt(scanner,@name, @arguments); - $$->dimExpr = sequenceToList($arguments); - $$->dimExpr.insert($$->dimExpr.begin(), new ExprConstString(tokAt(scanner,@name), *$name)); + $$->typeMacroExpr = sequenceToList($arguments); + $$->typeMacroExpr.insert($$->typeMacroExpr.begin(), new ExprConstString(tokAt(scanner,@name), *$name)); delete $name; } | name_in_namespace[name] '<' { yyextra->das_arrow_depth ++; } type_declaration_no_options_list[declL] '>' optional_expr_list_in_braces[arguments] { $$ = new TypeDecl(Type::typeMacro); $$->at = tokRangeAt(scanner,@name, @arguments); - $$->dimExpr = typesAndSequenceToList($declL,$arguments); - $$->dimExpr.insert($$->dimExpr.begin(), new ExprConstString(tokAt(scanner,@name), *$name)); + $$->typeMacroExpr = typesAndSequenceToList($declL,$arguments); + $$->typeMacroExpr.insert($$->typeMacroExpr.begin(), new ExprConstString(tokAt(scanner,@name), *$name)); delete $name; } | '$' name_in_namespace[name] '<' { yyextra->das_arrow_depth ++; } type_declaration_no_options_list[declL] '>' optional_expr_list_in_braces[arguments] { $$ = new TypeDecl(Type::typeMacro); $$->at = tokRangeAt(scanner,@name, @arguments); - $$->dimExpr = typesAndSequenceToList($declL,$arguments); - $$->dimExpr.insert($$->dimExpr.begin(), new ExprConstString(tokAt(scanner,@name), *$name)); + $$->typeMacroExpr = typesAndSequenceToList($declL,$arguments); + $$->typeMacroExpr.insert($$->typeMacroExpr.begin(), new ExprConstString(tokAt(scanner,@name), *$name)); delete $name; } | type_declaration_no_options[typeDecl] '-' '[' ']' { diff --git a/src/parser/ds_parser.cpp b/src/parser/ds_parser.cpp index 1d186264ba..063863579f 100644 --- a/src/parser/ds_parser.cpp +++ b/src/parser/ds_parser.cpp @@ -10861,7 +10861,7 @@ YYLTYPE yylloc = yyloc_default; (yyval.pTypeDecl)->isTag = true; (yyval.pTypeDecl)->firstType = new TypeDecl(Type::autoinfer); (yyval.pTypeDecl)->firstType->at = tokAt(scanner, (yylsp[-1])); - (yyval.pTypeDecl)->firstType->dimExpr.push_back((yyvsp[-1].pExpression)); + (yyval.pTypeDecl)->firstType->typeMacroExpr.push_back((yyvsp[-1].pExpression)); } break; @@ -11071,7 +11071,7 @@ YYLTYPE yylloc = yyloc_default; { (yyval.pTypeDecl) = new TypeDecl(Type::typeDecl); (yyval.pTypeDecl)->at = tokRangeAt(scanner,(yylsp[-3]),(yylsp[-1])); - (yyval.pTypeDecl)->dimExpr.push_back((yyvsp[-1].pExpression)); + (yyval.pTypeDecl)->typeMacroExpr.push_back((yyvsp[-1].pExpression)); } break; @@ -11079,8 +11079,8 @@ YYLTYPE yylloc = yyloc_default; { (yyval.pTypeDecl) = new TypeDecl(Type::typeMacro); (yyval.pTypeDecl)->at = tokRangeAt(scanner,(yylsp[-1]), (yylsp[0])); - (yyval.pTypeDecl)->dimExpr = sequenceToList((yyvsp[0].pExpression)); - (yyval.pTypeDecl)->dimExpr.insert((yyval.pTypeDecl)->dimExpr.begin(), new ExprConstString(tokAt(scanner,(yylsp[-1])), *(yyvsp[-1].s))); + (yyval.pTypeDecl)->typeMacroExpr = sequenceToList((yyvsp[0].pExpression)); + (yyval.pTypeDecl)->typeMacroExpr.insert((yyval.pTypeDecl)->typeMacroExpr.begin(), new ExprConstString(tokAt(scanner,(yylsp[-1])), *(yyvsp[-1].s))); delete (yyvsp[-1].s); } break; @@ -11093,8 +11093,8 @@ YYLTYPE yylloc = yyloc_default; { (yyval.pTypeDecl) = new TypeDecl(Type::typeMacro); (yyval.pTypeDecl)->at = tokRangeAt(scanner,(yylsp[-5]), (yylsp[0])); - (yyval.pTypeDecl)->dimExpr = typesAndSequenceToList((yyvsp[-2].pTypeDeclList),(yyvsp[0].pExpression)); - (yyval.pTypeDecl)->dimExpr.insert((yyval.pTypeDecl)->dimExpr.begin(), new ExprConstString(tokAt(scanner,(yylsp[-5])), *(yyvsp[-5].s))); + (yyval.pTypeDecl)->typeMacroExpr = typesAndSequenceToList((yyvsp[-2].pTypeDeclList),(yyvsp[0].pExpression)); + (yyval.pTypeDecl)->typeMacroExpr.insert((yyval.pTypeDecl)->typeMacroExpr.begin(), new ExprConstString(tokAt(scanner,(yylsp[-5])), *(yyvsp[-5].s))); delete (yyvsp[-5].s); } break; diff --git a/src/parser/ds_parser.ypp b/src/parser/ds_parser.ypp index e1b9e61d3d..fe3848688b 100644 --- a/src/parser/ds_parser.ypp +++ b/src/parser/ds_parser.ypp @@ -3079,7 +3079,7 @@ auto_type_declaration $$->isTag = true; $$->firstType = new TypeDecl(Type::autoinfer); $$->firstType->at = tokAt(scanner, @subexpr); - $$->firstType->dimExpr.push_back($subexpr); + $$->firstType->typeMacroExpr.push_back($subexpr); } ; @@ -3221,20 +3221,20 @@ type_declaration_no_options | DAS_TYPEDECL[td] '(' expr[subexpr] ')' { $$ = new TypeDecl(Type::typeDecl); $$->at = tokRangeAt(scanner,@td,@subexpr); - $$->dimExpr.push_back($subexpr); + $$->typeMacroExpr.push_back($subexpr); } | '$' name_in_namespace[name] optional_expr_list_in_braces[arguments] { $$ = new TypeDecl(Type::typeMacro); $$->at = tokRangeAt(scanner,@name, @arguments); - $$->dimExpr = sequenceToList($arguments); - $$->dimExpr.insert($$->dimExpr.begin(), new ExprConstString(tokAt(scanner,@name), *$name)); + $$->typeMacroExpr = sequenceToList($arguments); + $$->typeMacroExpr.insert($$->typeMacroExpr.begin(), new ExprConstString(tokAt(scanner,@name), *$name)); delete $name; } | '$' name_in_namespace[name] '<' { yyextra->das_arrow_depth ++; } type_declaration_no_options_list[declL] '>' optional_expr_list_in_braces[arguments] { $$ = new TypeDecl(Type::typeMacro); $$->at = tokRangeAt(scanner,@name, @arguments); - $$->dimExpr = typesAndSequenceToList($declL,$arguments); - $$->dimExpr.insert($$->dimExpr.begin(), new ExprConstString(tokAt(scanner,@name), *$name)); + $$->typeMacroExpr = typesAndSequenceToList($declL,$arguments); + $$->typeMacroExpr.insert($$->typeMacroExpr.begin(), new ExprConstString(tokAt(scanner,@name), *$name)); delete $name; } | type_declaration_no_options[typeDecl] '-' '[' ']' { From c39423f8063641fee97f8514c406006e7b91e874 Mon Sep 17 00:00:00 2001 From: Boris Batkin Date: Wed, 10 Jun 2026 02:12:59 -0700 Subject: [PATCH 03/23] fixed-array plan: the third parser (utils/dasFormatter gen1->gen2 converter) is fixed last Per review: the conversion util's vendored gen1 grammar (writes real TypeDecl dim/dimExpr, same 10 site-shapes as the in-tree gen1 parser) ports at the very end of the train, not Stage 5. The dim/dimExpr deletion commit carries only the minimal mechanical compile-flip. Co-Authored-By: Claude Fable 5 --- FIXED_ARRAY_REWORK.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/FIXED_ARRAY_REWORK.md b/FIXED_ARRAY_REWORK.md index e95db5c2e8..e431a5da1c 100644 --- a/FIXED_ARRAY_REWORK.md +++ b/FIXED_ARRAY_REWORK.md @@ -189,8 +189,8 @@ FA values); LOC visibly negative. ### Stage 5 — Macro/tooling ports 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; decision on -utils/dasFormatter's vendored parser (port vs freeze); MCP describe_type sanity. +(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. ### Stage 6 — Externals + compat removal @@ -203,6 +203,14 @@ RST (type system, generic programming), CHANGELOG, version bump; final full-matr 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. + ## Standing risk ledger (checked at every stage gate) 1. **const/ref/temporary propagation through FA levels** — the subtle-bug budget; audited From da2231827495bf6187b860c064909a707ac8abe8 Mon Sep 17 00:00:00 2001 From: Boris Batkin Date: Wed, 10 Jun 2026 02:55:46 -0700 Subject: [PATCH 04/23] fixed-array 1b-ii: parsers + mangled-name parse build tFixedArray chains MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The FA parser flip (FIXED_ARRAY_REWORK.md, Stage 1b second commit). Both grammars and the mangled-name parser now produce structural tFixedArray chains instead of dim/dimExpr vectors; nothing downstream understands them yet — the world stays dark until 1c/1d (commit-as-is settled with Boris, class burndown in the plan). - parser_impl: appendDimExpr becomes the FA-chain builder (3-arg, returns chain head; const-int shortcut preserved, fixedDimExpr always retained); attachDimChain wraps the dim chain around the element and hoists 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 — alias NEVER hoists; appendAutoDim covers gen1's push-at-end `[]` arm (wrap-innermost when already FA). The legacy 2-arg appendDimExpr stays for THE THIRD PARSER (utils/dasFormatter) and dies with the fields. - ds2/ds parser: dim_list arms build chains, splice arms attachDimChain (the `$dimlist->dimExpr.clear()` ownership hack disappears), gen1 `{{ }}` table literal synthesizes FA(dimAuto, autoinfer). Grammar errors kept verbatim. Old-world quirks reproduce structurally: late-dim front-splice (`int[3] const [4]` = [4][3]) and gen1 push-at-end (`int[][]` auto innermost). - mangled-name PARSE case '[' builds an FA node and claims the fullName remove-suffixes plus an immediately-following Y for THAT node ([3][4]Y[4]f labels the two-dim node; known cosmetic re-parse asymmetry for element-side labels, structural identity unaffected). - tests-cpp/small/test_fixed_array_parser.cpp: parse-shape suites for both grammars (parse-only via a deliberate trailing parse error — infer cannot digest FA until 1d), {{ }} makeType shape, and mangled emit<->parse round trips. Suite also pins a master fact: `int[3][]` is a gen1 syntax error (dim_list shift preference), so push-at-end only composes via int[]/int[][]. Gates: full Release build green; tests-cpp-small 53/54 (the 1 red compiles `let arr : int[5]` through infer — recorded 1d burndown). dastest runs ZERO tests: builtin.das bulk-push signatures with `[]` are FA now, make-array arg types are still infer-made dim-vectors, and every .das_module initialize meets the mismatch at to_array_move — daslang.exe cannot boot the in-repo project until 1d. test_aot not attempted (AOT regen needs a booting daslang). Co-Authored-By: Claude Fable 5 --- FIXED_ARRAY_REWORK.md | 17 +- src/ast/ast_typedecl.cpp | 19 +- src/parser/ds2_parser.cpp | 54 ++-- src/parser/ds2_parser.ypp | 18 +- src/parser/ds_parser.cpp | 64 ++--- src/parser/ds_parser.ypp | 24 +- src/parser/parser_impl.cpp | 66 +++++ src/parser/parser_impl.h | 5 + tests-cpp/small/test_fixed_array_parser.cpp | 283 ++++++++++++++++++++ 9 files changed, 451 insertions(+), 99 deletions(-) create mode 100644 tests-cpp/small/test_fixed_array_parser.cpp diff --git a/FIXED_ARRAY_REWORK.md b/FIXED_ARRAY_REWORK.md index e431a5da1c..565ea8e755 100644 --- a/FIXED_ARRAY_REWORK.md +++ b/FIXED_ARRAY_REWORK.md @@ -144,9 +144,20 @@ The indivisible piece, sub-staged for review: PARSE `case '['` builds FA — with the rule that a `Y` immediately following `[d]` labels THAT FA node (known cosmetic asymmetry: `[3]Yi` re-parses with the label on the FA node even if it was on the element; structural identity unaffected). Grammar - errors kept verbatim. Gate: tests-cpp parse-shape + mangled round-trip suites green; - dastest framework still runs (concrete FA in daslib is only decs/faker/profiler/regex) - and its FA-failure inventory becomes the 1c/1d/1e burndown list. + 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_module` initialize meets it at + `to_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 via `int[]`/ + `int[][]`/`int[][3]`. - **1c** typeFactory / interop (`TT[dim]`, `TDim<>`, `isNativeDim`, makeArgumentType, ast_handle). - **1d** Infer: inferAlias (WRAP, don't concatenate — alias label preserved), diff --git a/src/ast/ast_typedecl.cpp b/src/ast/ast_typedecl.cpp index 8d36a86d43..32c810ec91 100644 --- a/src/ast/ast_typedecl.cpp +++ b/src/ast/ast_typedecl.cpp @@ -4126,8 +4126,23 @@ namespace das ch ++; int di = atoi(numT.c_str()); if ( neg ) di = -di; - auto pt = parseTypeFromMangledName(ch,library,thisModule); - pt->dim.insert(pt->dim.begin(), di); + auto pt = new TypeDecl(Type::tFixedArray); + pt->fixedDim = di; + // fullName remove-suffixes emitted right after this node's [d] land back on it + for ( ;; ) { + if ( ch[0]=='-' && ch[1]=='[' && ch[2]==']' ) { ch += 3; pt->removeDim = true; } + else if ( ch[0]=='-' && ch[1]=='C' ) { ch += 2; pt->removeConstant = true; } + else if ( ch[0]=='-' && ch[1]=='&' ) { ch += 2; pt->removeRef = true; } + else if ( ch[0]=='-' && ch[1]=='#' ) { ch += 2; pt->removeTemporary = true; } + else break; + } + // Y immediately following [d] labels THIS node - the standalone case 'Y' + // would label one level too deep ([4]Y[4]f labels the two-dim node) + if ( *ch=='Y' ) { + ch ++; + pt->alias = parseAnyNameInBrackets(ch,false); + } + pt->firstType = parseTypeFromMangledName(ch,library,thisModule); return pt; } case '1': { diff --git a/src/parser/ds2_parser.cpp b/src/parser/ds2_parser.cpp index 5559a9428e..935b25c45c 100644 --- a/src/parser/ds2_parser.cpp +++ b/src/parser/ds2_parser.cpp @@ -1155,24 +1155,24 @@ static const yytype_int16 yyrline[] = 3112, 3113, 3114, 3115, 3119, 3130, 3134, 3141, 3153, 3160, 3166, 3175, 3180, 3190, 3200, 3210, 3223, 3224, 3225, 3226, 3227, 3231, 3235, 3235, 3235, 3249, 3250, 3254, 3258, 3265, - 3269, 3273, 3277, 3284, 3287, 3305, 3306, 3310, 3311, 3312, - 3313, 3314, 3314, 3314, 3318, 3323, 3330, 3337, 3337, 3344, - 3344, 3351, 3355, 3359, 3364, 3369, 3374, 3379, 3383, 3387, - 3392, 3396, 3400, 3405, 3405, 3405, 3411, 3418, 3418, 3418, - 3423, 3423, 3423, 3429, 3429, 3429, 3434, 3439, 3439, 3439, - 3444, 3444, 3444, 3453, 3458, 3458, 3458, 3463, 3463, 3463, - 3472, 3477, 3477, 3477, 3482, 3482, 3482, 3491, 3491, 3491, - 3497, 3497, 3497, 3506, 3509, 3520, 3536, 3538, 3543, 3548, - 3536, 3574, 3576, 3581, 3587, 3574, 3613, 3615, 3620, 3625, - 3613, 3666, 3667, 3668, 3669, 3670, 3671, 3672, 3676, 3677, - 3678, 3679, 3680, 3684, 3691, 3698, 3704, 3710, 3717, 3724, - 3730, 3739, 3742, 3748, 3756, 3761, 3768, 3773, 3779, 3780, - 3784, 3785, 3789, 3789, 3789, 3797, 3797, 3797, 3804, 3804, - 3804, 3815, 3815, 3815, 3822, 3822, 3822, 3833, 3839, 3839, - 3839, 3853, 3872, 3872, 3872, 3882, 3882, 3882, 3896, 3896, - 3896, 3910, 3919, 3919, 3919, 3939, 3946, 3946, 3946, 3956, - 3959, 3970, 3976, 3999, 4007, 4027, 4052, 4053, 4057, 4058, - 4063, 4066, 4076 + 3268, 3271, 3274, 3280, 3283, 3297, 3298, 3302, 3303, 3304, + 3305, 3306, 3306, 3306, 3310, 3315, 3322, 3329, 3329, 3336, + 3336, 3343, 3347, 3351, 3356, 3361, 3366, 3371, 3375, 3379, + 3384, 3388, 3392, 3397, 3397, 3397, 3403, 3410, 3410, 3410, + 3415, 3415, 3415, 3421, 3421, 3421, 3426, 3431, 3431, 3431, + 3436, 3436, 3436, 3445, 3450, 3450, 3450, 3455, 3455, 3455, + 3464, 3469, 3469, 3469, 3474, 3474, 3474, 3483, 3483, 3483, + 3489, 3489, 3489, 3498, 3501, 3512, 3528, 3530, 3535, 3540, + 3528, 3566, 3568, 3573, 3579, 3566, 3605, 3607, 3612, 3617, + 3605, 3658, 3659, 3660, 3661, 3662, 3663, 3664, 3668, 3669, + 3670, 3671, 3672, 3676, 3683, 3690, 3696, 3702, 3709, 3716, + 3722, 3731, 3734, 3740, 3748, 3753, 3760, 3765, 3771, 3772, + 3776, 3777, 3781, 3781, 3781, 3789, 3789, 3789, 3796, 3796, + 3796, 3807, 3807, 3807, 3814, 3814, 3814, 3825, 3831, 3831, + 3831, 3845, 3864, 3864, 3864, 3874, 3874, 3874, 3888, 3888, + 3888, 3902, 3911, 3911, 3911, 3931, 3938, 3938, 3938, 3948, + 3951, 3962, 3968, 3991, 3999, 4019, 4044, 4045, 4049, 4050, + 4055, 4058, 4068 }; #endif @@ -9904,29 +9904,25 @@ YYLTYPE yylloc = yyloc_default; case 779: /* dim_list: '[' expr ']' */ { - (yyval.pTypeDecl) = new TypeDecl(Type::autoinfer); - appendDimExpr((yyval.pTypeDecl), (yyvsp[-1].pExpression)); + (yyval.pTypeDecl) = appendDimExpr(nullptr, (yyvsp[-1].pExpression), tokAt(scanner,(yylsp[-1]))); } break; case 780: /* dim_list: '[' ']' */ { - (yyval.pTypeDecl) = new TypeDecl(Type::autoinfer); - appendDimExpr((yyval.pTypeDecl), nullptr); + (yyval.pTypeDecl) = appendDimExpr(nullptr, nullptr, tokAt(scanner,(yylsp[-1]))); } break; case 781: /* dim_list: dim_list '[' expr ']' */ { - (yyval.pTypeDecl) = (yyvsp[-3].pTypeDecl); - appendDimExpr((yyval.pTypeDecl), (yyvsp[-1].pExpression)); + (yyval.pTypeDecl) = appendDimExpr((yyvsp[-3].pTypeDecl), (yyvsp[-1].pExpression), tokAt(scanner,(yylsp[-1]))); } break; case 782: /* dim_list: dim_list '[' ']' */ { - (yyval.pTypeDecl) = (yyvsp[-2].pTypeDecl); - appendDimExpr((yyval.pTypeDecl), nullptr); + (yyval.pTypeDecl) = appendDimExpr((yyvsp[-2].pTypeDecl), nullptr, tokAt(scanner,(yylsp[-1]))); } break; @@ -9945,11 +9941,7 @@ YYLTYPE yylloc = yyloc_default; das2_yyerror(scanner,"macro can`t be used as array base type",tokAt(scanner,(yylsp[-1])), CompilationError::invalid_array_type); } - (yyvsp[-1].pTypeDecl)->dim.insert((yyvsp[-1].pTypeDecl)->dim.begin(), (yyvsp[0].pTypeDecl)->dim.begin(), (yyvsp[0].pTypeDecl)->dim.end()); - (yyvsp[-1].pTypeDecl)->dimExpr.insert((yyvsp[-1].pTypeDecl)->dimExpr.begin(), (yyvsp[0].pTypeDecl)->dimExpr.begin(), (yyvsp[0].pTypeDecl)->dimExpr.end()); - (yyvsp[-1].pTypeDecl)->removeDim = false; - (yyval.pTypeDecl) = (yyvsp[-1].pTypeDecl); - (yyvsp[0].pTypeDecl)->dimExpr.clear(); + (yyval.pTypeDecl) = attachDimChain((yyvsp[0].pTypeDecl), (yyvsp[-1].pTypeDecl)); } break; diff --git a/src/parser/ds2_parser.ypp b/src/parser/ds2_parser.ypp index 1dad80cd61..2ec25bb08e 100644 --- a/src/parser/ds2_parser.ypp +++ b/src/parser/ds2_parser.ypp @@ -3263,20 +3263,16 @@ table_type_pair dim_list : '[' expr[dimExpr] ']' { - $$ = new TypeDecl(Type::autoinfer); - appendDimExpr($$, $dimExpr); + $$ = appendDimExpr(nullptr, $dimExpr, tokAt(scanner,@dimExpr)); } | '[' ']' { - $$ = new TypeDecl(Type::autoinfer); - appendDimExpr($$, nullptr); + $$ = appendDimExpr(nullptr, nullptr, tokAt(scanner,@1)); } | dim_list[list] '[' expr[dimExpr] ']' { - $$ = $list; - appendDimExpr($$, $dimExpr); + $$ = appendDimExpr($list, $dimExpr, tokAt(scanner,@dimExpr)); } | dim_list[list] '[' ']' { - $$ = $list; - appendDimExpr($$, nullptr); + $$ = appendDimExpr($list, nullptr, tokAt(scanner,@2)); } ; @@ -3292,11 +3288,7 @@ type_declaration_no_options das2_yyerror(scanner,"macro can`t be used as array base type",tokAt(scanner,@typeDecl), CompilationError::invalid_array_type); } - $typeDecl->dim.insert($typeDecl->dim.begin(), $dimlist->dim.begin(), $dimlist->dim.end()); - $typeDecl->dimExpr.insert($typeDecl->dimExpr.begin(), $dimlist->dimExpr.begin(), $dimlist->dimExpr.end()); - $typeDecl->removeDim = false; - $$ = $typeDecl; - $dimlist->dimExpr.clear(); + $$ = attachDimChain($dimlist, $typeDecl); } ; diff --git a/src/parser/ds_parser.cpp b/src/parser/ds_parser.cpp index 063863579f..ccfa97645a 100644 --- a/src/parser/ds_parser.cpp +++ b/src/parser/ds_parser.cpp @@ -1151,26 +1151,26 @@ static const yytype_int16 yyrline[] = 3042, 3043, 3044, 3045, 3046, 3047, 3048, 3049, 3053, 3064, 3068, 3075, 3087, 3094, 3100, 3109, 3114, 3117, 3127, 3140, 3141, 3142, 3143, 3144, 3148, 3152, 3152, 3152, 3166, 3167, - 3171, 3175, 3182, 3186, 3193, 3194, 3195, 3196, 3197, 3211, - 3217, 3217, 3217, 3221, 3226, 3233, 3233, 3240, 3244, 3248, - 3253, 3258, 3263, 3268, 3272, 3276, 3281, 3285, 3289, 3294, - 3294, 3294, 3300, 3307, 3307, 3307, 3312, 3312, 3312, 3318, - 3318, 3318, 3323, 3328, 3328, 3328, 3333, 3333, 3333, 3342, - 3347, 3347, 3347, 3352, 3352, 3352, 3361, 3366, 3366, 3366, - 3371, 3371, 3371, 3380, 3380, 3380, 3386, 3386, 3386, 3395, - 3398, 3409, 3425, 3425, 3430, 3435, 3425, 3460, 3460, 3465, - 3471, 3460, 3496, 3496, 3501, 3506, 3496, 3546, 3547, 3548, - 3549, 3550, 3554, 3561, 3568, 3574, 3580, 3587, 3594, 3600, - 3609, 3612, 3618, 3626, 3631, 3638, 3643, 3650, 3655, 3661, - 3662, 3666, 3667, 3672, 3673, 3677, 3678, 3682, 3683, 3687, - 3688, 3689, 3693, 3694, 3695, 3699, 3700, 3704, 3710, 3717, - 3725, 3732, 3740, 3749, 3749, 3749, 3757, 3757, 3757, 3764, - 3764, 3764, 3775, 3775, 3775, 3786, 3789, 3795, 3809, 3815, - 3821, 3827, 3827, 3827, 3841, 3846, 3853, 3872, 3877, 3884, - 3884, 3884, 3894, 3894, 3894, 3908, 3908, 3908, 3922, 3931, - 3931, 3931, 3951, 3958, 3958, 3958, 3968, 3973, 3980, 3983, - 3989, 4008, 4017, 4025, 4045, 4070, 4071, 4075, 4076, 4081, - 4084, 4087, 4090, 4093, 4096 + 3171, 3175, 3182, 3185, 3191, 3192, 3193, 3194, 3195, 3205, + 3208, 3208, 3208, 3212, 3217, 3224, 3224, 3231, 3235, 3239, + 3244, 3249, 3254, 3259, 3263, 3267, 3272, 3276, 3280, 3285, + 3285, 3285, 3291, 3298, 3298, 3298, 3303, 3303, 3303, 3309, + 3309, 3309, 3314, 3319, 3319, 3319, 3324, 3324, 3324, 3333, + 3338, 3338, 3338, 3343, 3343, 3343, 3352, 3357, 3357, 3357, + 3362, 3362, 3362, 3371, 3371, 3371, 3377, 3377, 3377, 3386, + 3389, 3400, 3416, 3416, 3421, 3426, 3416, 3451, 3451, 3456, + 3462, 3451, 3487, 3487, 3492, 3497, 3487, 3537, 3538, 3539, + 3540, 3541, 3545, 3552, 3559, 3565, 3571, 3578, 3585, 3591, + 3600, 3603, 3609, 3617, 3622, 3629, 3634, 3641, 3646, 3652, + 3653, 3657, 3658, 3663, 3664, 3668, 3669, 3673, 3674, 3678, + 3679, 3680, 3684, 3685, 3686, 3690, 3691, 3695, 3701, 3708, + 3716, 3723, 3731, 3740, 3740, 3740, 3748, 3748, 3748, 3755, + 3755, 3755, 3766, 3766, 3766, 3777, 3780, 3786, 3800, 3806, + 3812, 3818, 3818, 3818, 3832, 3837, 3844, 3863, 3868, 3875, + 3875, 3875, 3885, 3885, 3885, 3899, 3899, 3899, 3913, 3922, + 3922, 3922, 3942, 3949, 3949, 3949, 3959, 3964, 3971, 3974, + 3980, 3999, 4011, 4019, 4039, 4064, 4065, 4069, 4070, 4075, + 4078, 4081, 4084, 4087, 4090 }; #endif @@ -10998,15 +10998,13 @@ YYLTYPE yylloc = yyloc_default; case 772: /* dim_list: '[' expr ']' */ { - (yyval.pTypeDecl) = new TypeDecl(Type::autoinfer); - appendDimExpr((yyval.pTypeDecl), (yyvsp[-1].pExpression)); + (yyval.pTypeDecl) = appendDimExpr(nullptr, (yyvsp[-1].pExpression), tokAt(scanner,(yylsp[-1]))); } break; case 773: /* dim_list: dim_list '[' expr ']' */ { - (yyval.pTypeDecl) = (yyvsp[-3].pTypeDecl); - appendDimExpr((yyval.pTypeDecl), (yyvsp[-1].pExpression)); + (yyval.pTypeDecl) = appendDimExpr((yyvsp[-3].pTypeDecl), (yyvsp[-1].pExpression), tokAt(scanner,(yylsp[-1]))); } break; @@ -11035,20 +11033,13 @@ YYLTYPE yylloc = yyloc_default; das_yyerror(scanner,"macro can`t be used as array base type",tokAt(scanner,(yylsp[-1])), CompilationError::invalid_array_type); } - (yyvsp[-1].pTypeDecl)->dim.insert((yyvsp[-1].pTypeDecl)->dim.begin(), (yyvsp[0].pTypeDecl)->dim.begin(), (yyvsp[0].pTypeDecl)->dim.end()); - (yyvsp[-1].pTypeDecl)->dimExpr.insert((yyvsp[-1].pTypeDecl)->dimExpr.begin(), (yyvsp[0].pTypeDecl)->dimExpr.begin(), (yyvsp[0].pTypeDecl)->dimExpr.end()); - (yyvsp[-1].pTypeDecl)->removeDim = false; - (yyval.pTypeDecl) = (yyvsp[-1].pTypeDecl); - (yyvsp[0].pTypeDecl)->dimExpr.clear(); + (yyval.pTypeDecl) = attachDimChain((yyvsp[0].pTypeDecl), (yyvsp[-1].pTypeDecl)); } break; case 779: /* type_declaration_no_options: type_declaration_no_options '[' ']' */ { - (yyvsp[-2].pTypeDecl)->dim.push_back(TypeDecl::dimAuto); - (yyvsp[-2].pTypeDecl)->dimExpr.push_back(nullptr); - (yyvsp[-2].pTypeDecl)->removeDim = false; - (yyval.pTypeDecl) = (yyvsp[-2].pTypeDecl); + (yyval.pTypeDecl) = appendAutoDim((yyvsp[-2].pTypeDecl), tokAt(scanner,(yylsp[-1]))); } break; @@ -12283,8 +12274,11 @@ YYLTYPE yylloc = yyloc_default; case 951: /* make_table_decl: "{{" make_table optional_trailing_semicolon_cur_cur */ { - auto mkt = new TypeDecl(Type::autoinfer); - mkt->dim.push_back(TypeDecl::dimAuto); + auto mkt = new TypeDecl(Type::tFixedArray); + mkt->fixedDim = TypeDecl::dimAuto; + mkt->at = tokAt(scanner,(yylsp[-2])); + mkt->firstType = new TypeDecl(Type::autoinfer); + mkt->firstType->at = mkt->at; ((ExprMakeArray *)(yyvsp[-1].pExpression))->makeType = mkt; (yyvsp[-1].pExpression)->at = tokAt(scanner,(yylsp[-2])); auto ttm = yyextra->g_Program->makeCall(tokAt(scanner,(yylsp[-2])),"to_table_move"); diff --git a/src/parser/ds_parser.ypp b/src/parser/ds_parser.ypp index fe3848688b..068f566ec1 100644 --- a/src/parser/ds_parser.ypp +++ b/src/parser/ds_parser.ypp @@ -3180,12 +3180,10 @@ table_type_pair dim_list : '[' expr[dimExpr] ']' { - $$ = new TypeDecl(Type::autoinfer); - appendDimExpr($$, $dimExpr); + $$ = appendDimExpr(nullptr, $dimExpr, tokAt(scanner,@dimExpr)); } | dim_list[list] '[' expr[dimExpr] ']' { - $$ = $list; - appendDimExpr($$, $dimExpr); + $$ = appendDimExpr($list, $dimExpr, tokAt(scanner,@dimExpr)); } ; @@ -3202,17 +3200,10 @@ type_declaration_no_options das_yyerror(scanner,"macro can`t be used as array base type",tokAt(scanner,@typeDecl), CompilationError::invalid_array_type); } - $typeDecl->dim.insert($typeDecl->dim.begin(), $dimlist->dim.begin(), $dimlist->dim.end()); - $typeDecl->dimExpr.insert($typeDecl->dimExpr.begin(), $dimlist->dimExpr.begin(), $dimlist->dimExpr.end()); - $typeDecl->removeDim = false; - $$ = $typeDecl; - $dimlist->dimExpr.clear(); + $$ = attachDimChain($dimlist, $typeDecl); } | type_declaration_no_options[typeDecl] '[' ']' { - $typeDecl->dim.push_back(TypeDecl::dimAuto); - $typeDecl->dimExpr.push_back(nullptr); - $typeDecl->removeDim = false; - $$ = $typeDecl; + $$ = appendAutoDim($typeDecl, tokAt(scanner,@2)); } | DAS_TYPE '<' { yyextra->das_arrow_depth ++; } type_declaration[typeDecl] '>' { yyextra->das_arrow_depth --; } { $typeDecl->autoToAlias = true; @@ -4006,8 +3997,11 @@ make_table_decl } } | CBRCBRB [loc] make_table[mka] optional_trailing_semicolon_cur_cur { - auto mkt = new TypeDecl(Type::autoinfer); - mkt->dim.push_back(TypeDecl::dimAuto); + auto mkt = new TypeDecl(Type::tFixedArray); + mkt->fixedDim = TypeDecl::dimAuto; + mkt->at = tokAt(scanner,@loc); + mkt->firstType = new TypeDecl(Type::autoinfer); + mkt->firstType->at = mkt->at; ((ExprMakeArray *)$mka)->makeType = mkt; $mka->at = tokAt(scanner,@loc); auto ttm = yyextra->g_Program->makeCall(tokAt(scanner,@loc),"to_table_move"); diff --git a/src/parser/parser_impl.cpp b/src/parser/parser_impl.cpp index ea32081df9..af6ccaf258 100644 --- a/src/parser/parser_impl.cpp +++ b/src/parser/parser_impl.cpp @@ -28,6 +28,72 @@ namespace das { } } + static TypeDecl * makeFixedArrayNode ( Expression * dimExpr, const LineInfo & at ) { + auto fa = new TypeDecl(Type::tFixedArray); + fa->at = at; + if ( dimExpr ) { + fa->fixedDim = TypeDecl::dimConst; + if ( dimExpr->rtti_isConstant() ) { // note: this shortcut is here so we don`t get extra infer pass on every array + auto cI = (ExprConst *) dimExpr; + auto bt = cI->baseType; + if ( bt==Type::tInt || bt==Type::tUInt ) { + fa->fixedDim = cast::to(cI->value); + } + } + fa->fixedDimExpr = dimExpr; + } else { + fa->fixedDim = TypeDecl::dimAuto; + } + return fa; + } + + TypeDecl * appendDimExpr ( TypeDecl * chain, Expression * dimExpr, const LineInfo & at ) { + auto fa = makeFixedArrayNode(dimExpr, at); + if ( !chain ) return fa; + auto inner = chain; // chain under construction - innermost firstType is null + while ( inner->firstType ) inner = inner->firstType; + inner->firstType = fa; + return chain; + } + + TypeDecl * attachDimChain ( TypeDecl * chain, TypeDecl * element ) { + auto inner = chain; + while ( inner->firstType ) inner = inner->firstType; + inner->firstType = element; + // the old world fused these qualifiers onto the single dim-carrying node; canonical + // form keeps them on the outermost FA node only. `alias` stays on the element - at + // parse time it is the unresolved type name, not a label on the array + chain->ref |= element->ref; element->ref = false; + chain->removeRef |= element->removeRef; element->removeRef = false; + chain->constant |= element->constant; element->constant = false; + chain->removeConstant |= element->removeConstant; element->removeConstant = false; + chain->temporary |= element->temporary; element->temporary = false; + chain->removeTemporary |= element->removeTemporary; element->removeTemporary = false; + chain->implicit |= element->implicit; element->implicit = false; + chain->explicitConst |= element->explicitConst; element->explicitConst = false; + chain->explicitRef |= element->explicitRef; element->explicitRef = false; + chain->isExplicit |= element->isExplicit; element->isExplicit = false; + chain->autoToAlias |= element->autoToAlias; element->autoToAlias = false; + element->removeDim = false; // old splice cleared it outright + return chain; + } + + TypeDecl * appendAutoDim ( TypeDecl * typeDecl, const LineInfo & at ) { + auto fa = makeFixedArrayNode(nullptr, at); + if ( typeDecl->baseType==Type::tFixedArray ) { + // gen1 `foo[3][]` pushed the auto dim at the END - innermost position + auto inner = typeDecl; + while ( inner->firstType && inner->firstType->baseType==Type::tFixedArray ) inner = inner->firstType; + fa->firstType = inner->firstType; + inner->firstType = fa; + typeDecl->removeDim = false; + return typeDecl; + } + return attachDimChain(fa, typeDecl); + } + + // THE THIRD PARSER (FIXED_ARRAY_REWORK.md): utils/dasFormatter still builds the old + // dim/dimExpr world; this overload dies with those fields at the end of Stage 1 void appendDimExpr ( TypeDecl * typeDecl, Expression * dimExpr ) { if ( dimExpr ) { int32_t dI = TypeDecl::dimConst; diff --git a/src/parser/parser_impl.h b/src/parser/parser_impl.h index 6394df6233..fa7dcac59e 100644 --- a/src/parser/parser_impl.h +++ b/src/parser/parser_impl.h @@ -80,6 +80,11 @@ namespace das { void varDeclToTypeDecl ( yyscan_t scanner, TypeDecl * pType, vector * list, bool needNames = true ); Annotation * findAnnotation ( yyscan_t scanner, const string & name, const LineInfo & at ); void runFunctionAnnotations ( yyscan_t scanner, DasParserState * extra, Function * func, AnnotationList * annL, const LineInfo & at, bool genericMode = false ); + TypeDecl * appendDimExpr ( TypeDecl * chain, Expression * dimExpr, const LineInfo & at ); + TypeDecl * attachDimChain ( TypeDecl * chain, TypeDecl * element ); + TypeDecl * appendAutoDim ( TypeDecl * typeDecl, const LineInfo & at ); + // THE THIRD PARSER (FIXED_ARRAY_REWORK.md): utils/dasFormatter still builds the old + // dim/dimExpr world; this overload dies with those fields at the end of Stage 1 void appendDimExpr ( TypeDecl * typeDecl, Expression * dimExpr ); void implAddGenericFunction ( yyscan_t scanner, Function * func ); Expression * ast_arrayComprehension (yyscan_t scanner, const LineInfo & loc, vector * iters, diff --git a/tests-cpp/small/test_fixed_array_parser.cpp b/tests-cpp/small/test_fixed_array_parser.cpp new file mode 100644 index 0000000000..7b4460e841 --- /dev/null +++ b/tests-cpp/small/test_fixed_array_parser.cpp @@ -0,0 +1,283 @@ +// Stage 1b-ii of the tFixedArray rework (FIXED_ARRAY_REWORK.md): both grammars and the +// mangled-name parser now BUILD tFixedArray chains. The parse-shape suites stop the +// pipeline right after the parse leg — a deliberate trailing parse error, since infer +// can't digest FA chains until 1d — and assert the exact chains the grammar actions +// built (splice order, qualifier hoist, gen1 push-at-end quirk, alias-on-element). +// The mangled suite proves the new case '[' parse round-trips the 1a emit. +#include +#include "daScript/daScript.h" +#include "daScript/ast/ast.h" +#include "daScript/ast/ast_expressions.h" +using namespace das; + +namespace { + +// parse-only: the trailing `)` is a parse error in both grammars, so compileDaScript +// returns right after the parse leg with the already-reduced module contents intact +ProgramPtr parseLiteral ( const char * src, const char * name ) { + TextPrinter tout; + ModuleGroup dummyLibGroup; + auto fAccess = make_smart(); + fAccess->setFileInfo(name, make_unique(src, uint32_t(strlen(src)), /*own*/false)); + return compileDaScript(name, fAccess, tout, dummyLibGroup); +} + +TypeDecl * fieldType ( const ProgramPtr & p, const char * structName, const char * fieldName ) { + auto st = p->thisModule->findStructure(structName); + REQUIRE(st != nullptr); + auto fd = st->findField(fieldName); + REQUIRE(fd != nullptr); + REQUIRE(fd->type != nullptr); + return fd->type; +} + +// assert an FA chain of the given dims (outermost first) over the given element baseType +TypeDecl * checkChain ( TypeDecl * t, std::initializer_list dims, Type elemBt ) { + for ( auto d : dims ) { + REQUIRE(t != nullptr); + REQUIRE(t->baseType == Type::tFixedArray); + CHECK_EQ(t->fixedDim, d); + t = t->firstType; + } + REQUIRE(t != nullptr); + CHECK_EQ(t->baseType, elemBt); + CHECK(t->baseType != Type::tFixedArray); + return t; // the element, for further checks +} + +TypeDecl * makeFA ( int32_t d, TypeDecl * elem ) { + auto fa = new TypeDecl(Type::tFixedArray); + fa->fixedDim = d; + fa->firstType = elem; + return fa; +} + +TypeDecl * makeFAChain ( Type bt, std::initializer_list dims ) { + auto t = new TypeDecl(bt); + TypeDecl * result = t; + for ( auto it = rbegin(dims); it != rend(dims); ++it ) { + result = makeFA(*it, result); + } + return result; +} + +TypeDeclPtr reparse ( const string & mangled ) { + ModuleLibrary lib; + MangledNameParser parser; + const char * ch = mangled.c_str(); + auto t = parser.parseTypeFromMangledName(ch, lib, nullptr); + CHECK_EQ(*ch, 0); // consumed the whole name + return t; +} + +} + +TEST_CASE("gen2 grammar builds tFixedArray chains") { + static const char src[] = + "options gen2\n" + "struct Foo {\n" + " f1 : int[3]\n" + " f2 : int[3][4]\n" + " f3 : int const [4]\n" + " f4 : int[4] const\n" + " f5 : int[]\n" + " f6 : int[SIZE]\n" + " f7 : I3[4]\n" + " f8 : int[3] const [4]\n" + " f9 : int[3][]\n" + "}\n" + "struct ZEnd {\n" + " z : int\n" + "}\n" + ")\n"; + auto p = parseLiteral(src, "fixed_array_parse_gen2.das"); + REQUIRE(p); + REQUIRE(p->failed()); // the trailing `)` — proves infer never ran + SUBCASE("single dim - const-int shortcut folds, expr retained") { + auto t = fieldType(p,"Foo","f1"); + checkChain(t,{3},Type::tInt); + REQUIRE(t->fixedDimExpr != nullptr); + CHECK(t->fixedDimExpr->rtti_isConstant()); + } + SUBCASE("multi dim - text order is outermost first") { + checkChain(fieldType(p,"Foo","f2"),{3,4},Type::tInt); + } + SUBCASE("const hoists to the chain head, element stays bare") { + auto h3 = fieldType(p,"Foo","f3"); + auto e3 = checkChain(h3,{4},Type::tInt); + CHECK(h3->constant); + CHECK_FALSE(e3->constant); + auto h4 = fieldType(p,"Foo","f4"); + CHECK(h4->constant); + CHECK(h3->isSameType(*h4, RefMatters::yes, ConstMatters::yes, TemporaryMatters::yes)); + } + SUBCASE("[] is the dimAuto sentinel, no dim expr") { + auto t = fieldType(p,"Foo","f5"); + checkChain(t,{TypeDecl::dimAuto},Type::tInt); + CHECK(t->fixedDimExpr == nullptr); + } + SUBCASE("non-const dim expr is the dimConst sentinel, expr retained") { + auto t = fieldType(p,"Foo","f6"); + checkChain(t,{TypeDecl::dimConst},Type::tInt); + CHECK(t->fixedDimExpr != nullptr); + } + SUBCASE("alias never hoists - it is the unresolved element name") { + auto t = fieldType(p,"Foo","f7"); + auto elem = checkChain(t,{4},Type::alias); + CHECK(t->alias.empty()); + CHECK_EQ(elem->alias, "I3"); + } + SUBCASE("late dim splices in FRONT (gen1 quirk preserved): int[3] const [4] is [4][3]") { + auto t = fieldType(p,"Foo","f8"); + checkChain(t,{4,3},Type::tInt); + CHECK(t->constant); // hoisted from the inner chain head + CHECK_FALSE(t->firstType->constant); + } + SUBCASE("mixed dim_list [3][] keeps text order (gen2-only - error in gen1)") { + checkChain(fieldType(p,"Foo","f9"),{3,TypeDecl::dimAuto},Type::tInt); + } +} + +TEST_CASE("gen1 grammar builds tFixedArray chains") { + static const char src[] = + "options gen2 = false\n" + "struct Foo\n" + " f1 : int[3]\n" + " f2 : int[3][4]\n" + " f3 : int[][]\n" + " f4 : int[]\n" + " f5 : int const [3]\n" + " f6 : int[3] const [4]\n" + " f7 : int[][3]\n" + "struct ZEnd\n" + " z : int\n" + ")\n"; + auto p = parseLiteral(src, "fixed_array_parse_gen1.das"); + REQUIRE(p); + REQUIRE(p->failed()); + SUBCASE("single and multi dim") { + checkChain(fieldType(p,"Foo","f1"),{3},Type::tInt); + checkChain(fieldType(p,"Foo","f2"),{3,4},Type::tInt); + } + SUBCASE("push-at-end quirk: the second [] of int[][] goes INNERMOST") { + // (`int[3][]` is a syntax error in gen1 - dim_list shift preference - so the + // is-already-FA branch of appendAutoDim is only reachable via repeated []) + checkChain(fieldType(p,"Foo","f3"),{TypeDecl::dimAuto,TypeDecl::dimAuto},Type::tInt); + } + SUBCASE("bare [] wraps a plain type") { + checkChain(fieldType(p,"Foo","f4"),{TypeDecl::dimAuto},Type::tInt); + } + SUBCASE("int[][3] splices the late dim in FRONT of the auto dim") { + checkChain(fieldType(p,"Foo","f7"),{3,TypeDecl::dimAuto},Type::tInt); + } + SUBCASE("const hoists to the chain head") { + auto t = fieldType(p,"Foo","f5"); + auto e = checkChain(t,{3},Type::tInt); + CHECK(t->constant); + CHECK_FALSE(e->constant); + } + SUBCASE("late dim splices in FRONT: int[3] const [4] is [4][3], const on head") { + auto t = fieldType(p,"Foo","f6"); + checkChain(t,{4,3},Type::tInt); + CHECK(t->constant); + CHECK_FALSE(t->firstType->constant); + } +} + +TEST_CASE("gen1 {{ }} table literal synthesizes auto[] as a tFixedArray") { + static const char src[] = + "options gen2 = false\n" + "def foo\n" + " let t <- {{ \"a\" => 1 }}\n" + "struct ZEnd\n" + " z : int\n" + ")\n"; + auto p = parseLiteral(src, "fixed_array_parse_gen1_tab.das"); + REQUIRE(p); + REQUIRE(p->failed()); + Function * foo = nullptr; + p->thisModule->functions.foreach([&](auto && fn){ + if ( fn->name=="foo" ) foo = fn; + }); + REQUIRE(foo != nullptr); + REQUIRE(foo->body != nullptr); + REQUIRE(foo->body->rtti_isBlock()); + auto blk = static_cast(foo->body); + REQUIRE(!blk->list.empty()); + REQUIRE(blk->list[0]->rtti_isLet()); + auto let = static_cast(blk->list[0]); + REQUIRE(!let->variables.empty()); + auto init = let->variables[0]->init; + REQUIRE(init != nullptr); + REQUIRE(init->rtti_isCallLikeExpr()); + auto call = static_cast(init); + REQUIRE(!call->arguments.empty()); + REQUIRE(call->arguments[0]->rtti_isMakeArray()); + auto mka = static_cast(call->arguments[0]); + REQUIRE(mka->makeType != nullptr); + checkChain(mka->makeType,{TypeDecl::dimAuto},Type::autoinfer); +} + +TEST_CASE("mangled name parse builds tFixedArray and round-trips the emit") { + gc_guard guard; + SUBCASE("plain chains round-trip byte-identically") { + for ( auto txt : { "[4]i", "[3][4]i", "[3][4][4]f" } ) { + auto t = reparse(txt); + CHECK_EQ(t->getMangledName(), txt); + } + auto t = reparse("[3][4]i"); + checkChain(t,{3,4},Type::tInt); + CHECK(t->fixedDimExpr == nullptr); // mangled text carries no exprs + CHECK(t->isSameType(*makeFAChain(Type::tInt,{3,4}), + RefMatters::yes, ConstMatters::yes, TemporaryMatters::yes)); + } + SUBCASE("sentinels round-trip") { + auto a = reparse("[-1]i"); + checkChain(a,{TypeDecl::dimAuto},Type::tInt); + CHECK_EQ(a->getMangledName(), "[-1]i"); + auto c = reparse("[-2]i"); + checkChain(c,{TypeDecl::dimConst},Type::tInt); + CHECK_EQ(c->getMangledName(), "[-2]i"); + } + SUBCASE("qualifier prefixes land on the FA head") { + auto t = reparse("C&[4]i"); + checkChain(t,{4},Type::tInt); + CHECK(t->constant); + CHECK(t->ref); + CHECK_EQ(t->getMangledName(), "C&[4]i"); + } + SUBCASE("Y<> immediately after [d] labels THAT node - the [3][4]Y[4]f golden") { + auto t = reparse("[3][4]Y[4]f"); + checkChain(t,{3,4,4},Type::tFloat); + CHECK(t->alias.empty()); + CHECK_EQ(t->firstType->alias, "M4"); + CHECK(t->firstType->firstType->alias.empty()); + CHECK_EQ(t->getMangledName(), "[3][4]Y[4]f"); + // and the asymmetry: [3]Yi re-parses with the label claimed by the FA node + auto asym = reparse("[3]Yi"); + CHECK_EQ(asym->alias, "I"); + CHECK(asym->firstType->alias.empty()); + } + SUBCASE("standalone Y<> on a non-FA type is untouched") { + auto t = reparse("Yi"); + CHECK_EQ(t->baseType, Type::tInt); + CHECK_EQ(t->alias, "foo"); + } + SUBCASE("fullName remove-suffixes round-trip on the FA node") { + auto built = makeFAChain(Type::tInt,{3}); + built->removeDim = true; + built->removeConstant = true; + auto full = built->getMangledName(true); + CHECK_EQ(full, "[3]-[]-Ci"); + auto t = reparse(full); + CHECK(t->removeDim); + CHECK(t->removeConstant); + CHECK_EQ(t->getMangledName(true), full); + } + SUBCASE("FA nested in containers round-trips") { + auto arr = new TypeDecl(Type::tArray); + arr->firstType = makeFAChain(Type::tInt,{4}); + auto t = reparse(arr->getMangledName()); + CHECK(t->isSameType(*arr, RefMatters::yes, ConstMatters::yes, TemporaryMatters::yes)); + } +} From 235832eefe29a9974d86fbede903da3e0313587d Mon Sep 17 00:00:00 2001 From: Boris Batkin Date: Wed, 10 Jun 2026 03:47:11 -0700 Subject: [PATCH 05/23] fixed-array 1c: typeFactory/interop produce tFixedArray; makeTypeInfo flattens MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Stage 1c of FIXED_ARRAY_REWORK.md: the C++ interop side now produces FA chains. - typeFactory> and typeFactory wrap via the new makeFixedArrayTypeDecl header helper, which hoists the canonical ref/const/temporary trio off the element (mirrors the parser's attachDimChain). isNativeDim moves onto the FA node — its only consumer is aot_cpp.das reading the indexed type's head, unchanged. - SETTLED (discussed): the natural recursion fixes the latent multi-dim order bug — old typeFactory produced inner-first dims [4,3] (das int[4][3], row stride 12 instead of 16); believed unexercised in-tree. New shape FA(3, FA(4, int)) is correct by construction and pinned by tests (describe "int[3][4]", stride 16). - makeTypeInfo gains the FA-flatten arm, pulled forward from 1f by necessity: 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. The arm collects chain dims onto a scratch element clone (head qualifiers ride along); mangled-name cache key unaffected since chain and flattened text are identical. ManagedVector's walk scratch (ast_handle.h) flips to FA(1, clone) and exercises the arm. - makeArgumentType needs no edit — composes via the FA-aware isRefType. Builtin mangled names unchanged (unaliased FA chains byte-identical to the old text); semantic hashes shift, moot while the world is dark. - tests-cpp/small/test_fixed_array_interop.cpp: typeFactory shapes (incl. multi-dim fix + const-hoist canonical form), makeArgumentType composition, and makeTypeInfo flatten parity (dimSize/dim/size/flags/ hash byte-equal vs dim-vector input, shared cache key). Gates: full Release build green; tests-cpp-small 55/56 (the 1 red is the known 1d burndown item — int[5] through infer). World stays dark until 1d. Co-Authored-By: Claude Fable 5 --- FIXED_ARRAY_REWORK.md | 15 ++- include/daScript/ast/ast_handle.h | 6 +- include/daScript/ast/ast_typedecl.h | 21 +++- src/ast/ast_debug_info_helper.cpp | 19 ++++ tests-cpp/small/test_fixed_array_interop.cpp | 105 +++++++++++++++++++ 5 files changed, 156 insertions(+), 10 deletions(-) create mode 100644 tests-cpp/small/test_fixed_array_interop.cpp diff --git a/FIXED_ARRAY_REWORK.md b/FIXED_ARRAY_REWORK.md index 565ea8e755..6b59755204 100644 --- a/FIXED_ARRAY_REWORK.md +++ b/FIXED_ARRAY_REWORK.md @@ -158,8 +158,19 @@ The indivisible piece, sub-staged for review: 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 via `int[]`/ `int[][]`/`int[][3]`. -- **1c** typeFactory / interop (`TT[dim]`, `TDim<>`, `isNativeDim`, makeArgumentType, - ast_handle). +- **1c** typeFactory / interop (settled at landing). `typeFactory>` and + `typeFactory` wrap FA via the shared `makeFixedArrayTypeDecl` header helper + (hoists the canonical ref/const/temporary trio off the element); `isNativeDim` moves + onto the FA node (only consumer is aot_cpp.das reading the indexed type's head). + `makeArgumentType` needs no edit — composes via the FA-aware isRefType. SETTLED: the + natural recursion fixes the latent multi-dim order bug (old: C `int[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: the `makeTypeInfo` FA-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: inferAlias (WRAP, don't concatenate — alias label preserved), inferGenericType / updateAliasMap / applyAutoContracts (dim arms deleted; FA rides the firstType recursion), ExprAt/ExprSafeAt result typing, for-loop sources, make-array / diff --git a/include/daScript/ast/ast_handle.h b/include/daScript/ast/ast_handle.h index 5dd4f7f996..5d391c5330 100644 --- a/include/daScript/ast/ast_handle.h +++ b/include/daScript/ast/ast_handle.h @@ -505,9 +505,9 @@ namespace das // gc_local: scratch TypeDecl just to drive makeTypeInfo — // read once, never stored on the result. RAII unlinks from // the thread gc_root immediately and deletes at scope exit. - gc_local dimType(new TypeDecl(*vecType)); - dimType->ref = 0; - dimType->dim.push_back(1); + auto elemType = new TypeDecl(*vecType); + elemType->ref = 0; + gc_local dimType(makeFixedArrayTypeDecl(1, elemType)); ati = helpA.makeTypeInfo(nullptr, dimType); ati->flags |= TypeInfo::flag_isHandled; } diff --git a/include/daScript/ast/ast_typedecl.h b/include/daScript/ast/ast_typedecl.h index f5d030d77d..4853728494 100644 --- a/include/daScript/ast/ast_typedecl.h +++ b/include/daScript/ast/ast_typedecl.h @@ -636,12 +636,22 @@ namespace das { template struct TDim; + // FIXED_ARRAY_REWORK.md, 1c: wrap an element type in a tFixedArray node, hoisting the + // canonical qualifiers — ref/const/temporary live on the outermost FA node only + inline TypeDeclPtr makeFixedArrayTypeDecl ( int32_t size, TypeDeclPtr element ) { + auto fa = new TypeDecl(Type::tFixedArray); + fa->fixedDim = size; + fa->firstType = element; + fa->ref = element->ref; element->ref = false; + fa->constant = element->constant; element->constant = false; + fa->temporary = element->temporary; element->temporary = false; + return fa; + } + template struct typeFactory> { static ___noinline TypeDeclPtr make(const ModuleLibrary & lib) { - auto t = typeFactory::make(lib); - t->dim.push_back(size); - return t; + return makeFixedArrayTypeDecl(size, typeFactory::make(lib)); } }; @@ -674,8 +684,9 @@ namespace das { template struct typeFactory { static ___noinline TypeDeclPtr make(const ModuleLibrary & lib) { - auto t = typeFactory::make(lib); - t->dim.push_back(dim); + // natural recursion maps C int[3][4] to FA(3, FA(4, int)) — outermost first + // (the old dim-vector push produced the inner-first order, a latent bug) + auto t = makeFixedArrayTypeDecl(dim, typeFactory::make(lib)); t->ref = false; t->isNativeDim = true; return t; diff --git a/src/ast/ast_debug_info_helper.cpp b/src/ast/ast_debug_info_helper.cpp index f0417d7fb7..60a57b679a 100644 --- a/src/ast/ast_debug_info_helper.cpp +++ b/src/ast/ast_debug_info_helper.cpp @@ -208,6 +208,25 @@ namespace das { } TypeInfo * DebugInfoHelper::makeTypeInfo ( TypeInfo * info, const TypeDeclPtr & type ) { + if ( type->baseType==Type::tFixedArray ) { + // runtime TypeInfo stays flattened forever (FIXED_ARRAY_REWORK.md): collect the + // chain dims onto a scratch element clone; canonical head qualifiers ride along. + // The mangled-name cache key is unaffected — chain and flattened text are identical. + const TypeDecl * t = type; + vector dims; + while ( t->baseType==Type::tFixedArray ) { + dims.push_back(t->fixedDim); + DAS_ASSERTF(t->firstType, "tFixedArray chain without an element"); + t = t->firstType; + } + gc_local flat(new TypeDecl(*t)); + flat->dim.insert(flat->dim.begin(), dims.begin(), dims.end()); + flat->ref = type->ref; + flat->constant = type->constant; + flat->temporary = type->temporary; + flat->implicit = type->implicit; + return makeTypeInfo(info, flat); + } if ( info==nullptr ) { string mangledName = type->getMangledName(); auto it = tmn2t.find(mangledName); diff --git a/tests-cpp/small/test_fixed_array_interop.cpp b/tests-cpp/small/test_fixed_array_interop.cpp new file mode 100644 index 0000000000..cc39df4d73 --- /dev/null +++ b/tests-cpp/small/test_fixed_array_interop.cpp @@ -0,0 +1,105 @@ +// Stage 1c of the tFixedArray rework (FIXED_ARRAY_REWORK.md): typeFactory / interop +// produce tFixedArray chains instead of dim vectors, and makeTypeInfo flattens FA chains +// into the (forever-flattened) runtime TypeInfo. Includes the settled multi-dim fix: +// C int[3][4] maps to FA(3, FA(4, int)) — the old dim-vector push produced the +// inner-first order, a latent stride bug. +#include +#include "daScript/daScript.h" +#include "daScript/ast/ast.h" +using namespace das; + +namespace { + +TypeDecl * makeOldDim ( Type bt, std::initializer_list dims ) { + auto t = new TypeDecl(bt); + for ( auto d : dims ) t->dim.push_back(d); + return t; +} + +} + +TEST_CASE("typeFactory produces tFixedArray chains") { + gc_guard guard; + ModuleLibrary lib; + SUBCASE("native C array - isNativeDim on the FA node, non-ref") { + auto t = typeFactory::make(lib); + REQUIRE(t->baseType == Type::tFixedArray); + CHECK_EQ(t->fixedDim, 4); + CHECK(t->isNativeDim); + CHECK_FALSE(t->ref); + REQUIRE(t->firstType != nullptr); + CHECK_EQ(t->firstType->baseType, Type::tInt); + CHECK_EQ(t->describe(), "int[4]"); + } + SUBCASE("multi-dim C array maps outermost-first (the settled fix)") { + auto t = typeFactory::make(lib); + REQUIRE(t->baseType == Type::tFixedArray); + CHECK_EQ(t->fixedDim, 3); + REQUIRE(t->firstType != nullptr); + REQUIRE(t->firstType->baseType == Type::tFixedArray); + CHECK_EQ(t->firstType->fixedDim, 4); + CHECK_EQ(t->firstType->firstType->baseType, Type::tInt); + CHECK_EQ(t->describe(), "int[3][4]"); + CHECK_EQ(t->getSizeOf64(), uint64_t(48)); + CHECK_EQ(t->getStride64(), uint64_t(16)); // row stride of int[4] — was 12 pre-fix + } + SUBCASE("TDim<> - das-side fixed array, no isNativeDim") { + auto t = typeFactory>::make(lib); + REQUIRE(t->baseType == Type::tFixedArray); + CHECK_EQ(t->fixedDim, 4); + CHECK_FALSE(t->isNativeDim); + CHECK_EQ(t->firstType->baseType, Type::tInt); + auto nested = typeFactory,3>>::make(lib); + CHECK_EQ(nested->describe(), "int[3][4]"); + } + SUBCASE("const element hoists to the FA head (canonical form)") { + auto elem = new TypeDecl(Type::tInt); + elem->constant = true; + auto t = makeFixedArrayTypeDecl(4, elem); + CHECK(t->constant); + CHECK_FALSE(t->firstType->constant); + } + SUBCASE("makeArgumentType composes through the FA-aware classifiers") { + auto t = makeArgumentType(lib); + REQUIRE(t->baseType == Type::tFixedArray); + CHECK_FALSE(t->ref); // isRefType() branch clears ref, skips constant + CHECK_FALSE(t->constant); + } +} + +TEST_CASE("makeTypeInfo flattens tFixedArray chains byte-equal to dim-vector input") { + gc_guard guard; + ModuleLibrary lib; + SUBCASE("flattened TypeInfo parity") { + DebugInfoHelper faHelp, oldHelp; + auto fa = typeFactory::make(lib); + auto faInfo = faHelp.makeTypeInfo(nullptr, fa); + auto oldInfo = oldHelp.makeTypeInfo(nullptr, makeOldDim(Type::tInt,{3,4})); + CHECK_EQ(faInfo->type, Type::tInt); + REQUIRE_EQ(faInfo->dimSize, oldInfo->dimSize); + REQUIRE_EQ(faInfo->dimSize, 2u); + CHECK_EQ(faInfo->dim[0], 3u); + CHECK_EQ(faInfo->dim[1], 4u); + CHECK_EQ(faInfo->size, oldInfo->size); + CHECK_EQ(faInfo->flags, oldInfo->flags); + CHECK_EQ(faInfo->hash, oldInfo->hash); + } + SUBCASE("head qualifiers ride into the flattened flags") { + DebugInfoHelper faHelp, oldHelp; + auto fa = makeFixedArrayTypeDecl(4, new TypeDecl(Type::tInt)); + fa->constant = true; + auto faInfo = faHelp.makeTypeInfo(nullptr, fa); + auto old = makeOldDim(Type::tInt,{4}); + old->constant = true; + auto oldInfo = oldHelp.makeTypeInfo(nullptr, old); + CHECK((faInfo->flags & TypeInfo::flag_isConst) != 0u); + CHECK_EQ(faInfo->flags, oldInfo->flags); + CHECK_EQ(faInfo->hash, oldInfo->hash); + } + SUBCASE("cache key is shared - chain and flattened mangled text are identical") { + DebugInfoHelper help; + auto a = help.makeTypeInfo(nullptr, typeFactory::make(lib)); + auto b = help.makeTypeInfo(nullptr, makeOldDim(Type::tInt,{4})); + CHECK_EQ(a, b); // same helper, same mangled key, same TypeInfo node + } +} From 5f276cd793888896b3befb69a3747fa6554924a0 Mon Sep 17 00:00:00 2001 From: Boris Batkin Date: Wed, 10 Jun 2026 09:11:52 -0700 Subject: [PATCH 06/23] fixed-array 1d: infer produces/consumes tFixedArray; dim-vector reads flipped Matching core: inferGenericType FA arm (fixedDim match, dimAuto wildcard, firstType recursion; plain auto(TT) binds whole chains via the clone path), applyAutoContracts FA recursion. updateAliasMap untouched by design (auto(TT)[] rides firstType recursion; the dim.clear() root-cause line is now a dead no-op). Alias/verify: inferAlias/inferPartialAliases FA arms with the FA-only label rule (typedef name kept as display label only when the resolved type is an FA chain); per-node dimConst eval in inferTypeExpr; verifyType FA arm. Expression typing: ExprAt/ExprSafeAt FA peel (direct + pointer-to-FA), for-loop source peel, ExprNew chain rewrap around the pointer (dead dim-copy remnants removed), typeinfo is_dim/dim/dim_table_value/is_iterable, finalize_dim gate, ExprAscend FA-of-handle error, ExprWith, each-promotion gate. Make-literals: make-variant/make-struct/make-array convert to the element-walk (mkBaseT) + makeFixedArrayTypeDecl result-wrap pattern; structToTuple walks FA at entry; where-block passT wraps. makeStructWhereBlock wraps too, fixing a latent double-dim append when makeType carried an explicit dim. gen2 fixed_array of an already-dim'd element now wraps OUTER-most (old push_back appended inner-first - same latent order-bug family as the 1c typeFactory fix). Singles: moreSpecialized ranks via fixedDim + FA subtype recursion; allocate_stack ExprNew gate; escape-analysis stack-alloc gate + element walks (persistent flag was missed through the wrapper); tryPromoteConstInt and clone_dim dispatch gates; needAvoidNullPtr sees through FA when allowDim; isLocalOrGlobal ExprAt gate. No-edits proven: dim-GATED classifiers (isStructure/isVariant/isString/ isPointer/isVoid/isWorkhorseType) return false on the FA wrapper exactly as they did on a dim'd node, so baseType-gated call sites keep master behavior for free. Intentional divergence: [[EnumT[2]]] no longer collapses to a single const (master quirk - isEnumT ignored dim). Gates: build green; tests-cpp 55/56 - the known-red int[5] test now passes compile and fails in simulate, i.e. the failure crossed the 1d/1e boundary on schedule. World stays dark until 1e (simulate lowering); 1d+1e push as a train. Co-Authored-By: Claude Fable 5 --- FIXED_ARRAY_REWORK.md | 36 +++++- src/ast/ast.cpp | 2 +- src/ast/ast_allocate_stack.cpp | 2 +- src/ast/ast_escape_analysis.cpp | 11 +- src/ast/ast_generate.cpp | 6 +- src/ast/ast_infer_type.cpp | 149 ++++++++++++++++-------- src/ast/ast_infer_type_function.cpp | 8 +- src/ast/ast_infer_type_helper.cpp | 60 +++++++++- src/ast/ast_infer_type_make.cpp | 173 +++++++++++++++------------- src/ast/ast_infer_type_op.cpp | 4 +- src/ast/ast_lint.cpp | 10 +- src/ast/ast_typedecl.cpp | 15 +++ 12 files changed, 319 insertions(+), 157 deletions(-) diff --git a/FIXED_ARRAY_REWORK.md b/FIXED_ARRAY_REWORK.md index 6b59755204..d65c7219b0 100644 --- a/FIXED_ARRAY_REWORK.md +++ b/FIXED_ARRAY_REWORK.md @@ -171,10 +171,38 @@ The indivisible piece, sub-staged for review: 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: inferAlias (WRAP, don't concatenate — alias label preserved), - inferGenericType / updateAliasMap / applyAutoContracts (dim arms deleted; FA rides the - firstType recursion), ExprAt/ExprSafeAt result typing, for-loop sources, make-array / - initializers, inferTypeExpr (dimConst resolution per node), variable checks. +- **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`/`annotation` reads 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 diff --git a/src/ast/ast.cpp b/src/ast/ast.cpp index 417d384b55..b08a2c472f 100644 --- a/src/ast/ast.cpp +++ b/src/ast/ast.cpp @@ -13,7 +13,7 @@ namespace das { return ev->local || !(ev->argument || ev->block); } else if ( expr->rtti_isAt() ) { auto ea = static_cast(expr); - if ( ea->subexpr && ea->subexpr->type && ea->subexpr->type->dim.size() ) { + if ( ea->subexpr && ea->subexpr->type && ea->subexpr->type->baseType==Type::tFixedArray ) { return isLocalOrGlobal(ea->subexpr); } } else if ( expr->rtti_isField() ) { diff --git a/src/ast/ast_allocate_stack.cpp b/src/ast/ast_allocate_stack.cpp index 161d804440..855389033b 100644 --- a/src/ast/ast_allocate_stack.cpp +++ b/src/ast/ast_allocate_stack.cpp @@ -771,7 +771,7 @@ namespace das { virtual void preVisit ( ExprNew * expr ) override { Visitor::preVisit(expr); if ( inStruct ) return; - if ( expr->type->dim.size() ) { + if ( expr->type->baseType==Type::tFixedArray ) { auto sz = uint32_t(expr->type->getCountOf()*sizeof(char *)); expr->stackTop = allocateStack(sz); if ( log ) { diff --git a/src/ast/ast_escape_analysis.cpp b/src/ast/ast_escape_analysis.cpp index 4016c9e584..9f5c7dc9e8 100644 --- a/src/ast/ast_escape_analysis.cpp +++ b/src/ast/ast_escape_analysis.cpp @@ -232,7 +232,7 @@ namespace das { // use the non-asserting 64-bit size: a malformed/oversized type (e.g. in invalid_types.das) // yields a huge value that simply fails the cap, instead of tripping the size<=0x7fffffff assert bool fits = en->typeexpr && en->typeexpr->getBaseSizeOf64() <= uint64_t(MAX_STACK_ALLOC_SIZE); - if ( !en->initializer && en->typeexpr && en->typeexpr->dim.empty() && !persistent && !isClass && fits && !en->allocate_on_stack ) { + if ( !en->initializer && en->typeexpr && en->typeexpr->baseType!=Type::tFixedArray && !persistent && !isClass && fits && !en->allocate_on_stack ) { en->allocate_on_stack = true; return true; } } else if ( init && init->rtti_isAscend() ) { @@ -240,14 +240,17 @@ namespace das { bool plainStruct = false; if ( ea->subexpr && ea->subexpr->rtti_isMakeStruct() ) { auto mks = (ExprMakeStruct *) ea->subexpr; - auto st = mks->makeType ? mks->makeType->structType : nullptr; + auto mkT = mks->makeType; + while ( mkT && mkT->baseType==Type::tFixedArray && mkT->firstType ) mkT = mkT->firstType; + auto st = mkT ? mkT->structType : nullptr; // note: isNewClass means "make-struct produced by a `new`" (set for plain structs too), // NOT "is a class" - the class signal is the constructor / forceClass / structType->isClass plainStruct = !mks->constructor && !mks->isNewHandle && !mks->forceClass && !( st && st->isClass ); } - bool persistent = ea->subexpr && ea->subexpr->type && ea->subexpr->type->structType - && ea->subexpr->type->structType->persistent; + auto subT = ea->subexpr ? ea->subexpr->type : nullptr; + while ( subT && subT->baseType==Type::tFixedArray && subT->firstType ) subT = subT->firstType; + bool persistent = subT && subT->structType && subT->structType->persistent; bool fits = ea->subexpr && ea->subexpr->type && ea->subexpr->type->getSizeOf64() <= uint64_t(MAX_STACK_ALLOC_SIZE); if ( plainStruct && !persistent && fits && !ea->allocate_on_stack ) { ea->allocate_on_stack = true; return true; diff --git a/src/ast/ast_generate.cpp b/src/ast/ast_generate.cpp index 9325f3f03f..78147c3602 100644 --- a/src/ast/ast_generate.cpp +++ b/src/ast/ast_generate.cpp @@ -2294,10 +2294,12 @@ namespace das { block->isClosure = true; block->returnType = new TypeDecl(Type::tVoid); // only one argument, and its 'self' - auto argT = new TypeDecl(*(mks->makeType)); + auto mkBaseT = mks->makeType; + while ( mkBaseT->baseType==Type::tFixedArray && mkBaseT->firstType ) mkBaseT = mkBaseT->firstType; + TypeDeclPtr argT = new TypeDecl(*mkBaseT); argT->constant = false; if ( mks->structs.size() > 1 ) { - argT->dim.push_back(int32_t(mks->structs.size())); + argT = makeFixedArrayTypeDecl(int32_t(mks->structs.size()), argT); } auto argV = new Variable(); argV->name = "__self__" + to_string(mks->at.line) + "_" + to_string(mks->at.column); diff --git a/src/ast/ast_infer_type.cpp b/src/ast/ast_infer_type.cpp index 9291546bdc..1f001a8d41 100644 --- a/src/ast/ast_infer_type.cpp +++ b/src/ast/ast_infer_type.cpp @@ -2202,14 +2202,17 @@ namespace das { return new ExprConstInt(expr->at, align); } else if (expr->trait == "is_dim") { reportAstChanged(); - return new ExprConstBool(expr->at, expr->typeexpr->dim.size() != 0); + return new ExprConstBool(expr->at, expr->typeexpr->dim.size() != 0 || expr->typeexpr->baseType == Type::tFixedArray); } else if (expr->trait == "dim") { if (expr->typeexpr->isExprTypeAnywhere()) { error("typeinfo(dim " + describeType(expr->typeexpr) + ") is not fully inferred, expecting resolved dim", "", "", expr->at, CompilationError::not_resolved_yet_typeinfo_dim); return Visitor::visit(expr); } - if (expr->typeexpr->dim.size()) { + if (expr->typeexpr->baseType == Type::tFixedArray) { + reportAstChanged(); + return new ExprConstInt(expr->at, expr->typeexpr->fixedDim); + } else if (expr->typeexpr->dim.size()) { reportAstChanged(); return new ExprConstInt(expr->at, expr->typeexpr->dim[0]); } else { @@ -2232,7 +2235,10 @@ namespace das { expr->at, CompilationError::not_resolved_yet_typeinfo_dim_table); return Visitor::visit(expr); } - if (expr->typeexpr->secondType->dim.size()) { + if (expr->typeexpr->secondType->baseType == Type::tFixedArray) { + reportAstChanged(); + return new ExprConstInt(expr->at, expr->typeexpr->secondType->fixedDim); + } else if (expr->typeexpr->secondType->dim.size()) { reportAstChanged(); return new ExprConstInt(expr->at, expr->typeexpr->secondType->dim[0]); } else { @@ -2396,7 +2402,7 @@ namespace das { } else if (expr->trait == "is_iterable") { reportAstChanged(); bool iterable = false; - if (expr->typeexpr->dim.size()) { + if (expr->typeexpr->dim.size() || expr->typeexpr->baseType == Type::tFixedArray) { iterable = true; } else if (expr->typeexpr->isGoodIteratorType()) { iterable = true; @@ -2914,7 +2920,7 @@ namespace das { reportMissingFinalizer("finalizer mismatch ", expr->at, expr->subexpr->type); return Visitor::visit(expr); } - } else if (finalizeType->dim.size()) { + } else if (finalizeType->dim.size() || finalizeType->baseType == Type::tFixedArray) { reportAstChanged(); auto cloneFn = new ExprCall(expr->at, "finalize_dim"); cloneFn->arguments.push_back(expr->subexpr->clone()); @@ -3006,6 +3012,13 @@ namespace das { error("cannot allocate on the heap this handled type at all: '" + describeType(subt) + "'", "", "", expr->at, CompilationError::invalid_ascend_handle_type); } + } else if (expr->subexpr->type->baseType == Type::tFixedArray) { + const TypeDecl * elemT = expr->subexpr->type; + while (elemT->baseType == Type::tFixedArray && elemT->firstType) elemT = elemT->firstType; + if (elemT->baseType == Type::tHandle) { + error("array of handled type cannot be allocated on the heap: '" + describeType(expr->subexpr->type) + "'", "", "", + expr->at, CompilationError::invalid_ascend_array_handle_type); + } } if (expr->ascType) { TypeDecl::clone(expr->type, expr->ascType); @@ -3075,53 +3088,63 @@ namespace das { } expr->name.clear(); expr->func = nullptr; + // `new Foo[10]` types as the typeexpr's dim chain rewrapped around a + // pointer-to-element ('array of pointers', as the old flat dim copy did) + TypeDecl * baseT = expr->typeexpr; + while (baseT->baseType == Type::tFixedArray && baseT->firstType) baseT = baseT->firstType; + TypeDeclPtr ptrType = nullptr; + auto wrapNewDimChain = [&](TypeDeclPtr pt) -> TypeDeclPtr { + ptrType = pt; + if (expr->typeexpr->baseType != Type::tFixedArray) return pt; + auto chain = new TypeDecl(*expr->typeexpr); // deep clone of the chain + auto inner = chain; + while (inner->firstType && inner->firstType->baseType == Type::tFixedArray) inner = inner->firstType; + inner->firstType = pt; // replace the element with the pointer + return chain; + }; if (expr->typeexpr->ref) { error("a reference cannot be allocated on the heap", "", "", expr->at, CompilationError::invalid_new_type); - } else if (expr->typeexpr->baseType == Type::tStructure) { - if (!expr->initializer && expr->typeexpr->structType->isClass) { + } else if (baseT->baseType == Type::tStructure) { + if (!expr->initializer && baseT->structType->isClass) { error("invalid syntax for 'new' of class, expected syntax: 'new " + describeType(expr->typeexpr) + "()'", "", "", expr->at, CompilationError::invalid_new_class_syntax); } - expr->type = new TypeDecl(Type::tPointer); - expr->type->firstType = new TypeDecl(*expr->typeexpr); - expr->type->firstType->dim.clear(); - expr->type->dim = expr->typeexpr->dim; - expr->name = expr->typeexpr->structType->getMangledName(); - } else if (expr->typeexpr->baseType == Type::tHandle) { - if (expr->typeexpr->annotation->canNew()) { - expr->type = new TypeDecl(Type::tPointer); - expr->type->firstType = new TypeDecl(*expr->typeexpr); - expr->type->firstType->dim.clear(); - expr->type->dim = expr->typeexpr->dim; - expr->type->smartPtr = expr->typeexpr->annotation->isSmart(); - expr->name = expr->typeexpr->annotation->module->name + "::" + expr->typeexpr->annotation->name; + auto pt = new TypeDecl(Type::tPointer); + pt->firstType = new TypeDecl(*baseT); + expr->type = wrapNewDimChain(pt); + expr->name = baseT->structType->getMangledName(); + } else if (baseT->baseType == Type::tHandle) { + if (baseT->annotation->canNew()) { + auto pt = new TypeDecl(Type::tPointer); + pt->firstType = new TypeDecl(*baseT); + pt->smartPtr = baseT->annotation->isSmart(); + expr->type = wrapNewDimChain(pt); + expr->name = baseT->annotation->module->name + "::" + baseT->annotation->name; } else { error("cannot allocate this type on the heap: '" + describeType(expr->typeexpr) + "'", "", "", expr->at, CompilationError::invalid_new_type); } - } else if (expr->typeexpr->baseType == Type::tTuple) { - if ( expr->typeexpr->isAutoOrAlias() ) { + } else if (baseT->baseType == Type::tTuple) { + if ( baseT->isAutoOrAlias() ) { error("new expression cannot be auto or alias type '" + describeType(expr->typeexpr) + "'", "", "", expr->at, CompilationError::invalid_new_type); return Visitor::visit(expr); } - expr->type = new TypeDecl(Type::tPointer); - expr->type->firstType = new TypeDecl(*expr->typeexpr); - expr->type->firstType->dim.clear(); - expr->type->dim = expr->typeexpr->dim; - expr->name = expr->typeexpr->getMangledName(); - } else if (expr->typeexpr->baseType == Type::tVariant) { - if ( expr->typeexpr->isAutoOrAlias() ) { + auto pt = new TypeDecl(Type::tPointer); + pt->firstType = new TypeDecl(*baseT); + expr->type = wrapNewDimChain(pt); + expr->name = baseT->getMangledName(); + } else if (baseT->baseType == Type::tVariant) { + if ( baseT->isAutoOrAlias() ) { error("new expression cannot be auto or alias type '" + describeType(expr->typeexpr) + "'", "", "", expr->at, CompilationError::invalid_new_type); return Visitor::visit(expr); } - expr->type = new TypeDecl(Type::tPointer); - expr->type->firstType = new TypeDecl(*expr->typeexpr); - expr->type->firstType->dim.clear(); - expr->type->dim = expr->typeexpr->dim; - expr->name = expr->typeexpr->getMangledName(); + auto pt = new TypeDecl(Type::tPointer); + pt->firstType = new TypeDecl(*baseT); + expr->type = wrapNewDimChain(pt); + expr->name = baseT->getMangledName(); } else { error("only tuples, variants, structures or handled types can be allocated on the heap, not '" + describeType(expr->typeexpr) + "'", "", "", expr->at, CompilationError::invalid_new_type); @@ -3133,22 +3156,22 @@ namespace das { if (expr->type && expr->initializer && !expr->name.empty()) { auto resultType = new TypeDecl(*expr->type); expr->func = inferFunctionCall(expr, InferCallError::functionOrGeneric, nullptr, false); - if (!expr->func && expr->typeexpr->baseType == Type::tStructure) { + if (!expr->func && baseT->baseType == Type::tStructure) { auto saveName = expr->name; - expr->name = "_::" + expr->typeexpr->structType->name; + expr->name = "_::" + baseT->structType->name; expr->func = inferFunctionCall(expr, InferCallError::functionOrGeneric, nullptr, false); if (!expr->func) expr->name = saveName; } swap(resultType, expr->type); if (expr->func) { - if (!expr->type->firstType->isSameType(*resultType, RefMatters::yes, ConstMatters::yes, TemporaryMatters::yes)) { - error("initializer returns '" + describeType(resultType) + "' vs '" + describeType(expr->type->firstType) + "'", "", "", + if (!ptrType->firstType->isSameType(*resultType, RefMatters::yes, ConstMatters::yes, TemporaryMatters::yes)) { + error("initializer returns '" + describeType(resultType) + "' vs '" + describeType(ptrType->firstType) + "'", "", "", expr->at, CompilationError::invalid_new_initializer_result_type); } } else { - if (expr->typeexpr->baseType == Type::tStructure && - !expr->typeexpr->structType->hasAnyInitializers() && expr->arguments.empty()) { + if (baseT->baseType == Type::tStructure && + !baseT->structType->hasAnyInitializers() && expr->arguments.empty()) { expr->initializer = false; reportAstChanged(); } @@ -3156,7 +3179,7 @@ namespace das { if (func) { extraError = "while compiling function " + func->describe(); } - error("'" + describeType(expr->type->firstType) + "' does not have default initializer", extraError, "", + error("'" + describeType(ptrType->firstType) + "' does not have default initializer", extraError, "", expr->at, CompilationError::missing_new_default_initializer); } } @@ -3252,7 +3275,7 @@ namespace das { expr->type->constant |= seT->constant; } } else { - if (ixT->isRange() && (seT->isGoodArrayType() || seT->dim.size())) { // a[range(b)] into subset(a,range(b)) + if (ixT->isRange() && (seT->isGoodArrayType() || seT->dim.size() || seT->baseType==Type::tFixedArray)) { // a[range(b)] into subset(a,range(b)) auto subset = new ExprCall(expr->at, "subarray"); subset->arguments.push_back(expr->subexpr->clone()); subset->arguments.push_back(expr->index->clone()); @@ -3278,7 +3301,7 @@ namespace das { expr->type = new TypeDecl(seT->getVectorBaseType()); expr->type->ref = seT->ref; expr->type->constant = seT->constant; - } else if (!seT->dim.size()) { + } else if (!seT->dim.size() && seT->baseType != Type::tFixedArray) { error("type can't be indexed: '" + describeType(seT) + "'", "", "", expr->subexpr->at, CompilationError::cant_index); return Visitor::visit(expr); @@ -3286,6 +3309,11 @@ namespace das { error("type dimensions are not resolved yet: '" + describeType(seT) + "'", "", "", expr->subexpr->at, CompilationError::not_resolved_yet_array_dimension); return Visitor::visit(expr); + } else if (seT->baseType == Type::tFixedArray) { + // peel one level - same element-access pattern as tArray + TypeDecl::clone(expr->type, seT->firstType); + expr->type->ref = true; + expr->type->constant |= seT->constant; } else { TypeDecl::clone(expr->type, seT); expr->type->ref = true; @@ -3347,7 +3375,7 @@ namespace das { // } // expr->type = seT->annotation->makeIndexType(expr->subexpr, expr->index); // expr->type->constant |= seT->constant; - } else if (seT->isVectorType() || seT->isGoodArrayType() || seT->dim.size()) { + } else if (seT->isVectorType() || seT->isGoodArrayType() || seT->dim.size() || seT->baseType==Type::tFixedArray) { // arrays accept int/int64/uint/uint64; vector and fixed_array — int/uint only if (seT->isGoodArrayType() ? !ixT->isIndexExt() : !ixT->isIndex()) { expr->type = nullptr; @@ -3368,6 +3396,16 @@ namespace das { expr->type = new TypeDecl(Type::tPointer); expr->type->firstType = new TypeDecl(*seT->firstType); expr->type->firstType->constant |= seT->constant; + } else if (seT->baseType==Type::tFixedArray) { + if (!seT->isAutoArrayResolved()) { + error("type dimensions are not resolved yet '" + describeType(seT) + "'", "", "", + expr->subexpr->at, CompilationError::not_resolved_yet_array_dimension); + return Visitor::visit(expr); + } else { + expr->type = new TypeDecl(Type::tPointer); + expr->type->firstType = new TypeDecl(*seT->firstType); + expr->type->firstType->constant |= seT->constant; + } } else if (seT->dim.size()) { if (!seT->isAutoArrayResolved()) { error("type dimensions are not resolved yet '" + describeType(seT) + "'", "", "", @@ -3432,7 +3470,7 @@ namespace das { expr->type = new TypeDecl(Type::tPointer); expr->type->firstType = new TypeDecl(*seT->secondType); expr->type->constant |= seT->constant; - } else if (expr->subexpr->type->dim.size()) { + } else if (expr->subexpr->type->dim.size() || expr->subexpr->type->baseType==Type::tFixedArray) { if (!safeExpression(expr)) { error("safe-index of fixed_array<> must be inside the 'unsafe' block", "", "", expr->at, CompilationError::unsafe_fixed_array_safe_index); @@ -3448,10 +3486,14 @@ namespace das { expr->subexpr->at, CompilationError::not_resolved_yet_array_dimension); } expr->type = new TypeDecl(Type::tPointer); - expr->type->firstType = new TypeDecl(*seT); - expr->type->firstType->dim.erase(expr->type->firstType->dim.begin()); - if (!expr->type->firstType->dimExpr.empty()) { - expr->type->firstType->dimExpr.erase(expr->type->firstType->dimExpr.begin()); + if (seT->baseType==Type::tFixedArray) { + expr->type->firstType = new TypeDecl(*seT->firstType); + } else { + expr->type->firstType = new TypeDecl(*seT); + expr->type->firstType->dim.erase(expr->type->firstType->dim.begin()); + if (!expr->type->firstType->dimExpr.empty()) { + expr->type->firstType->dimExpr.erase(expr->type->firstType->dimExpr.begin()); + } } expr->type->firstType->constant |= seT->constant; } else if (expr->subexpr->type->isVectorType() && expr->subexpr->type->isRef()) { @@ -4825,7 +4867,7 @@ namespace das { ExpressionPtr InferTypes::visit(ExprWith *expr) { if (auto wT = expr->with->type) { StructurePtr pSt = nullptr; - if (wT->dim.size()) { + if (wT->dim.size() || wT->baseType == Type::tFixedArray) { error("with array in undefined, " + describeType(wT), "", "", expr->at, CompilationError::invalid_with_array_type); } else if (wT->isStructure()) { @@ -4917,7 +4959,11 @@ namespace das { pVar->name = expr->iterators[idx]; pVar->aka = expr->iteratorsAka[idx]; pVar->at = expr->iteratorsAt[idx]; - if (src->type->dim.size()) { + if (src->type->baseType==Type::tFixedArray) { + pVar->type = new TypeDecl(*src->type->firstType); + pVar->type->ref = true; + pVar->type->constant |= src->type->constant; + } else if (src->type->dim.size()) { pVar->type = new TypeDecl(*src->type); pVar->type->ref = true; pVar->type->dim.erase(pVar->type->dim.begin()); @@ -5020,6 +5066,7 @@ namespace das { // now, for the one where we did not find anything if (that->type) { if (!that->type->dim.size() && + that->type->baseType != Type::tFixedArray && !that->type->isGoodIteratorType() && !that->type->isGoodArrayType() && !that->type->isRange() && diff --git a/src/ast/ast_infer_type_function.cpp b/src/ast/ast_infer_type_function.cpp index e2551063fd..876b440688 100644 --- a/src/ast/ast_infer_type_function.cpp +++ b/src/ast/ast_infer_type_function.cpp @@ -1130,11 +1130,11 @@ namespace das { // 3. one with dim is more specialized, than one without // if both have dim, one with actual value is more specialized, than the other one { - int d1 = t1->dim.size() ? t1->dim[0] : 0; - int d2 = t2->dim.size() ? t2->dim[0] : 0; + int d1 = t1->baseType==Type::tFixedArray ? t1->fixedDim : 0; + int d2 = t2->baseType==Type::tFixedArray ? t2->fixedDim : 0; if (d1 != d2) { if (d1 && d2) { - return d1 == -1 ? -1 : 1; + return d1 == TypeDecl::dimAuto ? -1 : 1; } else { return d1 ? 1 : -1; } @@ -1195,7 +1195,7 @@ namespace das { // DAS_ASSERT(t2->baseType==passType->baseType && "how did it match otherwise?"); // if its an array or a pointer, we compare specialization of subtype - if (t1->baseType == Type::tPointer || t1->baseType == Type::tArray || t1->baseType == Type::tIterator) { + if (t1->baseType == Type::tPointer || t1->baseType == Type::tArray || t1->baseType == Type::tIterator || t1->baseType == Type::tFixedArray) { return moreSpecialized(t1->firstType, t2->firstType, passType->firstType); // if its a table, we compare both subtypes } else if (t1->baseType == Type::tTable) { diff --git a/src/ast/ast_infer_type_helper.cpp b/src/ast/ast_infer_type_helper.cpp index 0a311110c4..1ae57cfa3c 100644 --- a/src/ast/ast_infer_type_helper.cpp +++ b/src/ast/ast_infer_type_helper.cpp @@ -152,6 +152,28 @@ namespace das { if (auto ptrType = decl->firstType) { verifyType(ptrType); } + } else if (decl->baseType == Type::tFixedArray) { + if (decl->fixedDim <= 0) { + error("array dimension can't be 0 or less: '" + describeType(decl) + "'", "", "", + decl->at, CompilationError::invalid_array_dimension); + } else { + bool failed = false; + if (decl->getSizeOf64(failed) > 0x7fffffff && !failed) { + error("array is too big: '" + describeType(decl) + "'", "", "", + decl->at, CompilationError::exceeds_array); + } + } + if (auto elemType = decl->firstType) { + if (elemType->ref) { + error("can't declare an array of references: '" + describeType(elemType) + "'", "", "", + elemType->at, CompilationError::invalid_array); + } + if (elemType->baseType == Type::tVoid) { + error("can't declare an array of void: '" + describeType(decl) + "'", "", "", + decl->at, CompilationError::invalid_array); + } + verifyType(elemType); + } } else if (decl->baseType == Type::tArray) { if (auto arrayType = decl->firstType) { if (arrayType->isAutoOrAlias()) { @@ -388,7 +410,11 @@ namespace das { resT->temporary = (resT->temporary || decl->temporary) && !decl->removeTemporary; resT->dim = decl->dim; resT->aotAlias = false; - resT->alias.clear(); + if (resT->baseType == Type::tFixedArray) { + resT->alias = decl->alias; // typedef name survives as a display label on the chain head (M4 design) + } else { + resT->alias.clear(); + } return resT; } else { return nullptr; @@ -407,7 +433,7 @@ namespace das { if (!resT->firstType) return nullptr; } - } else if (decl->baseType == Type::tArray) { + } else if (decl->baseType == Type::tArray || decl->baseType == Type::tFixedArray) { if (decl->firstType) { resT->firstType = inferAlias(decl->firstType, fptr, aliases, options, autoToAlias); if (!resT->firstType) @@ -529,8 +555,8 @@ namespace das { if (decl->firstType) { resT->firstType = inferPartialAliases(decl->firstType, passT->firstType, fptr, aliases); } - } else if (decl->baseType == Type::tArray) { - if (decl->firstType) { + } else if (decl->baseType == Type::tArray || decl->baseType == Type::tFixedArray) { + if (decl->firstType && passT->firstType) { resT->firstType = inferPartialAliases(decl->firstType, passT->firstType, fptr, aliases); } } else if (decl->baseType == Type::tTable) { @@ -644,6 +670,32 @@ namespace das { } bool InferTypes::inferTypeExpr(TypeDeclPtr &type) { bool any = false; + if (type->baseType == Type::tFixedArray && type->fixedDim == TypeDecl::dimConst) { + if (type->fixedDimExpr) { + if (auto constExpr = getConstExpr(type->fixedDimExpr)) { + if (constExpr->type->isIndex()) { + auto cI = static_cast(constExpr); + auto dI = cI->getValue(); + if (dI > 0) { + type->fixedDim = dI; + any = true; + } else { + error("array dimension can't be 0 or less", "", "", + type->at, CompilationError::invalid_array_dimension); + } + } else { + error("array dimension must be int32 or uint32", "", "", + type->at, CompilationError::invalid_array_dimension_type); + } + } else { + error("array dimension must be constant", "", "", + type->at, CompilationError::invalid_array_dimension); + } + } else { + error("can't deduce array dimension", "", "", + type->at, CompilationError::invalid_array_dimension); + } + } if (type->baseType != Type::typeDecl && type->baseType != Type::typeMacro) { for (size_t i = 0, is = type->dim.size(); i != is; ++i) { if (type->dim[i] == TypeDecl::dimConst) { diff --git a/src/ast/ast_infer_type_make.cpp b/src/ast/ast_infer_type_make.cpp index b692666673..cc3704b812 100644 --- a/src/ast/ast_infer_type_make.cpp +++ b/src/ast/ast_infer_type_make.cpp @@ -358,16 +358,18 @@ namespace das { return; } verifyType(expr->makeType); - if (expr->makeType->baseType != Type::tVariant) { + auto mkBaseT = expr->makeType; + while (mkBaseT->baseType==Type::tFixedArray && mkBaseT->firstType) mkBaseT = mkBaseT->firstType; + if (mkBaseT->baseType != Type::tVariant) { error("[[variant" + describeType(expr->makeType) + "]] with non-variant type", "", "", expr->at, CompilationError::invalid_variant_type); } - if (expr->makeType->dim.size() > 1) { + if (expr->makeType->baseType==Type::tFixedArray && expr->makeType->firstType->baseType==Type::tFixedArray) { error("[[" + describeType(expr->makeType) + "]] variant can only initialize single dimension arrays", "", "", expr->at, CompilationError::invalid_variant_array); - } else if (expr->makeType->dim.size() == 1 && expr->makeType->dim[0] != int32_t(expr->variants.size())) { + } else if (expr->makeType->baseType==Type::tFixedArray && expr->makeType->fixedDim != int32_t(expr->variants.size())) { error("[[" + describeType(expr->makeType) + "]] variant dimension mismatch, provided " + - to_string(expr->variants.size()) + " elements, expecting " + to_string(expr->makeType->dim[0]), + to_string(expr->variants.size()) + " elements, expecting " + to_string(expr->makeType->fixedDim), "", "", expr->at, CompilationError::mismatching_variant_dimension); } else if (expr->makeType->ref) { @@ -379,9 +381,11 @@ namespace das { if (!decl->value->type) { return Visitor::visitMakeVariantField(expr, index, decl, last); } - auto fieldVariant = expr->makeType->findArgumentIndex(decl->name); + auto mkBaseT = expr->makeType; + while (mkBaseT->baseType==Type::tFixedArray && mkBaseT->firstType) mkBaseT = mkBaseT->firstType; + auto fieldVariant = mkBaseT->findArgumentIndex(decl->name); if (fieldVariant != -1) { - auto fieldType = expr->makeType->argTypes[fieldVariant]; + auto fieldType = mkBaseT->argTypes[fieldVariant]; { bool rangeError = false; if (auto promoted = tryPromoteConstInt(decl->value, fieldType, rangeError)) { @@ -420,8 +424,10 @@ namespace das { if (expr->makeType && expr->makeType->isExprType()) { return Visitor::visit(expr); } - // result type - auto resT = new TypeDecl(*expr->makeType); + // result type - element view; the literal's own count replaces any declared dim + auto mkBaseT = expr->makeType; + while (mkBaseT->baseType==Type::tFixedArray && mkBaseT->firstType) mkBaseT = mkBaseT->firstType; + auto resT = new TypeDecl(*mkBaseT); if ( resT->isAlias() ) { auto aT = inferAlias(resT); if (aT) { @@ -443,13 +449,9 @@ namespace das { return Visitor::visit(expr); } uint32_t resDim = uint32_t(expr->variants.size()); - if (resDim == 0) { - resT->dim.clear(); - } else if (resDim != 1) { - resT->dim.resize(1); - resT->dim[0] = resDim; - } else { - resT->dim.clear(); + resT->dim.clear(); + if (resDim > 1) { + resT = makeFixedArrayTypeDecl(int32_t(resDim), resT); } expr->type = resT; verifyType(expr->type); @@ -524,18 +526,20 @@ namespace das { } expr->constructor = nullptr; verifyType(expr->makeType); - if (expr->makeType->baseType != Type::tStructure && expr->makeType->baseType != Type::tHandle) { + auto mkBaseT = expr->makeType; + while (mkBaseT->baseType==Type::tFixedArray && mkBaseT->firstType) mkBaseT = mkBaseT->firstType; + if (mkBaseT->baseType != Type::tStructure && mkBaseT->baseType != Type::tHandle) { if (expr->structs.size()) { error("[[" + describeType(expr->makeType) + "]] with non-structure type", "", "", expr->at, CompilationError::invalid_structure_type); } } - if (expr->makeType->dim.size() > 1) { + if (expr->makeType->baseType==Type::tFixedArray && expr->makeType->firstType->baseType==Type::tFixedArray) { error("[[" + describeType(expr->makeType) + "]] struct can only initialize single dimension arrays", "", "", expr->at, CompilationError::invalid_structure_array); - } else if (expr->makeType->dim.size() == 1 && expr->makeType->dim[0] != int32_t(expr->structs.size())) { + } else if (expr->makeType->baseType==Type::tFixedArray && expr->makeType->fixedDim != int32_t(expr->structs.size())) { error("[[" + describeType(expr->makeType) + "]] struct dimension mismatch, provided " + - to_string(expr->structs.size()) + " elements, expecting " + to_string(expr->makeType->dim[0]), + to_string(expr->structs.size()) + " elements, expecting " + to_string(expr->makeType->fixedDim), "", "", expr->at, CompilationError::mismatching_structure_dimension); } else if (expr->makeType->ref) { @@ -554,7 +558,7 @@ namespace das { describeLocalType(expr->makeType), "", expr->at, CompilationError::invalid_structure_local); } - } else if (expr->makeType->baseType == Type::tHandle && expr->isNewHandle && !expr->useInitializer) { + } else if (mkBaseT->baseType == Type::tHandle && expr->isNewHandle && !expr->useInitializer) { error("'new [[" + describeType(expr->makeType) + "]]' struct requires initializer syntax", "", "use 'new [[" + describeType(expr->makeType) + "()]]' instead", expr->at, CompilationError::invalid_structure_initializer_required); @@ -563,9 +567,9 @@ namespace das { error("Constructing class on stack is unsafe. Allocate it on the heap via new [[...]] or new " + expr->makeType->structType->name + "() instead.", "", "", expr->at, CompilationError::unsafe_class_local); } - } else if (noUnsafeUninitializedStructs && !(expr->useInitializer || expr->usedInitializer) && expr->makeType->structType && !expr->makeType->structType->safeWhenUninitialized && !expr->makeType->structType->isLambda && expr->makeType->structType->hasInitFields) { + } else if (noUnsafeUninitializedStructs && !(expr->useInitializer || expr->usedInitializer) && mkBaseT->structType && !mkBaseT->structType->safeWhenUninitialized && !mkBaseT->structType->isLambda && mkBaseT->structType->hasInitFields) { if (!safeExpression(expr)) { - error("Uninitialized structure " + expr->makeType->structType->name + " is unsafe. Use initializer syntax or [safe_when_uninitialized] when intended.", "", "", + error("Uninitialized structure " + mkBaseT->structType->name + " is unsafe. Use initializer syntax or [safe_when_uninitialized] when intended.", "", "", expr->at, CompilationError::unsafe_structure_uninitialized); } } @@ -586,8 +590,10 @@ namespace das { } auto blk = static_cast(mkb->block); bool ignoreCapturedConstant = false; - if (expr->makeType->baseType == Type::tStructure) { - if (auto field = expr->makeType->structType->findField(decl->name)) { + auto mkBaseT = expr->makeType; + while (mkBaseT->baseType==Type::tFixedArray && mkBaseT->firstType) mkBaseT = mkBaseT->firstType; + if (mkBaseT->baseType == Type::tStructure) { + if (auto field = mkBaseT->structType->findField(decl->name)) { if (field->capturedConstant) { ignoreCapturedConstant = true; } @@ -609,8 +615,10 @@ namespace das { return Visitor::visitMakeStructureField(expr, index, decl, last); } } - if (expr->makeType->baseType == Type::tStructure) { - if (auto field = expr->makeType->structType->findField(decl->name)) { + auto mkBaseT = expr->makeType; + while (mkBaseT->baseType==Type::tFixedArray && mkBaseT->firstType) mkBaseT = mkBaseT->firstType; + if (mkBaseT->baseType == Type::tStructure) { + if (auto field = mkBaseT->structType->findField(decl->name)) { auto copyFieldType = field->type; if (field->capturedConstant) { copyFieldType = new TypeDecl(*field->type); @@ -630,7 +638,7 @@ namespace das { if (!canCopyOrMoveType(copyFieldType, decl->value->type, TemporaryMatters::yes, decl->value, "can't initialize field " + decl->name, CompilationError::cant_copy, decl->value->at)) { } else if (decl->value->type->isTemp(true, false)) { - if (expr->makeType->structType->isLambda) { + if (mkBaseT->structType->isLambda) { error("can't capture temporary lambda variable " + decl->name, "", "", decl->value->at, CompilationError::cant_capture_variable); } else { @@ -659,7 +667,7 @@ namespace das { } else { TextWriter extra; vector args; - args.push_back(expr->makeType); + args.push_back(mkBaseT); args.push_back(decl->value->type); auto compareName = ".`" + decl->name + "`clone"; auto opName = "_::" + compareName; @@ -672,7 +680,7 @@ namespace das { if (verbose) { extra << "since there is operator ." << decl->name << " := (" - << expr->makeType->structType->name << "," << decl->value->type->describe() << ") , try " + << mkBaseT->structType->name << "," << decl->value->type->describe() << ") , try " << decl->name << " := " << *(decl->value); } } else { @@ -703,8 +711,8 @@ namespace das { error("field not found, " + decl->name, extra.str(), "", decl->at, CompilationError::lookup_structure_field); } - } else if (expr->makeType->baseType == Type::tHandle) { - if (auto fldt = expr->makeType->annotation->makeFieldType(decl->name, false)) { + } else if (mkBaseT->baseType == Type::tHandle) { + if (auto fldt = mkBaseT->annotation->makeFieldType(decl->name, false)) { if (!fldt->isRef()) { error("field is a property, not a value; " + decl->name, "", "", decl->at, CompilationError::invalid_annotation_field); @@ -744,12 +752,14 @@ namespace das { } return Visitor::visitMakeStructureField(expr, index, decl, last); } - ExpressionPtr InferTypes::structToTuple(const TypeDeclPtr &makeType, const MakeStructPtr &st, const LineInfo &at) { - if (makeType->isAutoOrAlias()) { // not fully inferred? - error("can't infer tuple type " + describeType(makeType), "", "", + ExpressionPtr InferTypes::structToTuple(const TypeDeclPtr &mkT, const MakeStructPtr &st, const LineInfo &at) { + if (mkT->isAutoOrAlias()) { // not fully inferred? + error("can't infer tuple type " + describeType(mkT), "", "", at, CompilationError::not_resolved_yet_tuple_type); return nullptr; } + auto makeType = mkT; // element view - promote-to-tuple passes the (possibly fixed-array) make type + while (makeType->baseType==Type::tFixedArray && makeType->firstType) makeType = makeType->firstType; auto mkt = new ExprMakeTuple(at); mkt->recordType = new TypeDecl(*makeType); mkt->values.resize(makeType->argTypes.size()); @@ -795,6 +805,8 @@ namespace das { return Visitor::visit(expr); } } + auto mkBaseT = expr->makeType; + while (mkBaseT && mkBaseT->baseType==Type::tFixedArray && mkBaseT->firstType) mkBaseT = mkBaseT->firstType; auto isClassCtor = !expr->nativeClassInitializer && (expr->useInitializer || expr->usedInitializer) && expr->makeType && (expr->makeType->isClass() || (expr->alwaysUseInitializer && expr->makeType->isStructure() && !expr->makeType->structType->noGenCtor)); @@ -843,10 +855,9 @@ namespace das { int32_t rec = 0; if (expr->structs.size() > 1) rec = int32_t(expr->structs.size()); - auto passT = new TypeDecl(*expr->makeType); - passT->dim.clear(); + TypeDeclPtr passT = new TypeDecl(*mkBaseT); if (rec) - passT->dim.push_back(rec); + passT = makeFixedArrayTypeDecl(rec, passT); if (arg->type->isAuto()) { auto nargT = TypeDecl::inferGenericType(passT, arg->type, false, false, nullptr); if (nargT) { @@ -872,7 +883,7 @@ namespace das { } } // promote to make variant - if (expr->makeType->baseType == Type::tVariant) { + if (mkBaseT->baseType == Type::tVariant) { if (expr->forceClass) { error(expr->makeType->describe() + " is not a class, but a variant", "", "", expr->at, CompilationError::invalid_class_variant); @@ -909,7 +920,7 @@ namespace das { } } // promote to make tuple - if (expr->makeType->baseType == Type::tTuple && expr->structs.size()) { + if (mkBaseT->baseType == Type::tTuple && expr->structs.size()) { if (expr->forceClass) { error(expr->makeType->describe() + " is not a class, but a tuple", "", "", expr->at, CompilationError::invalid_class_tuple); @@ -950,13 +961,13 @@ namespace das { } // see if there are any duplicate fields - if (expr->makeType->baseType == Type::tStructure) { - if (expr->makeType->structType->isTemplate) { + if (mkBaseT->baseType == Type::tStructure) { + if (mkBaseT->structType->isTemplate) { string extraError; if (func) { extraError = "while compiling function " + func->describe(); } - error("can't initialize template structure " + expr->makeType->structType->name, extraError, "", + error("can't initialize template structure " + mkBaseT->structType->name, extraError, "", expr->at, CompilationError::invalid_structure_template); return Visitor::visit(expr); } @@ -976,11 +987,11 @@ namespace das { if (anyDuplicates) return Visitor::visit(expr); // see if we need to fill in missing fields - if (expr->useInitializer && expr->makeType->structType) { - for (auto &stf : expr->makeType->structType->fields) { + if (expr->useInitializer && mkBaseT->structType) { + for (auto &stf : mkBaseT->structType->fields) { if (stf.init) { if (!stf.init->type || stf.init->type->isAuto()) { - error("structure '" + expr->makeType->structType->name + "' is not fully resolved yet", "", "", expr->at, CompilationError::not_resolved_yet_structure); + error("structure '" + mkBaseT->structType->name + "' is not fully resolved yet", "", "", expr->at, CompilationError::not_resolved_yet_structure); return Visitor::visit(expr); } } @@ -991,7 +1002,7 @@ namespace das { } if (!isClassCtor) { for (auto &st : expr->structs) { - for (auto &fi : expr->makeType->structType->fields) { + for (auto &fi : mkBaseT->structType->fields) { if (fi.init) { auto it = find_if(st->begin(), st->end(), [&](const MakeFieldDeclPtr &fd) { return fd->name == fi.name; }); if (it == st->end()) { @@ -1007,10 +1018,10 @@ namespace das { expr->usedInitializer = true; } // see if we need to init fields - if (expr->makeType->structType) { + if (mkBaseT->structType) { expr->initAllFields = !expr->structs.empty(); for (auto &st : expr->structs) { - if (st->size() == expr->makeType->structType->fields.size()) { + if (st->size() == mkBaseT->structType->fields.size()) { for (auto &va : *st) { if (va->value->rtti_isMakeLocal()) { auto mkl = static_cast(va->value); @@ -1026,7 +1037,7 @@ namespace das { expr->initAllFields = false; } } else { - if (expr->makeType->baseType == Type::tTuple && expr->structs.size() == 0) { + if (mkBaseT->baseType == Type::tTuple && expr->structs.size() == 0) { expr->initAllFields = true; } } @@ -1037,24 +1048,23 @@ namespace das { } // if unresolved - but we still return. cause sometimes we pass [], and then we want it resolved bool isAutoOrAlias = expr->makeType->isAutoOrAlias(); - // result type - auto resT = new TypeDecl(*expr->makeType); + // result type - element view, re-wrapped per the literal's element count + TypeDeclPtr resT; uint32_t resDim = uint32_t(expr->structs.size()); + bool mkSingleDim = expr->makeType->baseType==Type::tFixedArray && expr->makeType->firstType->baseType!=Type::tFixedArray; if (resDim == 0) { - // resT->dim.clear(); + resT = new TypeDecl(*expr->makeType); // keep declared shape } else if (resDim != 1) { - resT->dim.resize(1); - resT->dim[0] = resDim; + resT = makeFixedArrayTypeDecl(int32_t(resDim), new TypeDecl(*mkBaseT)); } else { - if (expr->makeType->dim.size() == 1 && expr->makeType->dim[0] == 1) { - // do nothing - } else if (expr->makeType->dim.size() == 1 && expr->makeType->dim[0] == TypeDecl::dimAuto) { - resT->dim.resize(1); - resT->dim[0] = 1; - expr->makeType->dim[0] = 1; + if (mkSingleDim && expr->makeType->fixedDim == 1) { + resT = new TypeDecl(*expr->makeType); // keep [1] + } else if (mkSingleDim && expr->makeType->fixedDim == TypeDecl::dimAuto) { + resT = makeFixedArrayTypeDecl(1, new TypeDecl(*mkBaseT)); + expr->makeType->fixedDim = 1; reportAstChanged(); } else { - resT->dim.clear(); + resT = new TypeDecl(*mkBaseT); } } expr->type = resT; @@ -1106,19 +1116,19 @@ namespace das { error("skipping initializer for class initialization requires unsafe", "", "", expr->at, CompilationError::unsafe_class_initializer); } - if (expr->forceClass && !(expr->makeType->baseType == Type::tStructure && expr->makeType->structType && expr->makeType->structType->isClass)) { + if (expr->forceClass && !(mkBaseT->baseType == Type::tStructure && mkBaseT->structType && mkBaseT->structType->isClass)) { error(expr->type->describe() + " is not a class", "", "", expr->at, CompilationError::invalid_class); } - if (expr->forceStruct && !(expr->makeType->baseType == Type::tStructure && expr->makeType->structType && !expr->makeType->structType->isClass)) { + if (expr->forceStruct && !(mkBaseT->baseType == Type::tStructure && mkBaseT->structType && !mkBaseT->structType->isClass)) { error(expr->type->describe() + " is not a struct", "", "", expr->at, CompilationError::invalid_structure); } - if (expr->forceVariant && !(expr->makeType->baseType == Type::tVariant)) { + if (expr->forceVariant && !(mkBaseT->baseType == Type::tVariant)) { error(expr->type->describe() + " is not a variant", "", "", expr->at, CompilationError::invalid_variant); } - if (expr->forceTuple && !(expr->makeType->baseType == Type::tTuple)) { + if (expr->forceTuple && !(mkBaseT->baseType == Type::tTuple)) { error(expr->type->describe() + " is not a tuple", "", "", expr->at, CompilationError::invalid_tuple); } @@ -1275,20 +1285,21 @@ namespace das { } TypeDecl::clone(expr->recordType, expr->makeType); } else { - if (expr->makeType->dim.size() > 1) { + if (expr->makeType->baseType==Type::tFixedArray && expr->makeType->firstType->baseType==Type::tFixedArray) { error("[[" + describeType(expr->makeType) + "]] array can only initialize single dimension arrays", "", "", expr->at, CompilationError::invalid_array_dimension); - } else if (expr->makeType->dim.size() == 1 && expr->makeType->dim[0] != int32_t(expr->values.size())) { + } else if (expr->makeType->baseType==Type::tFixedArray && expr->makeType->fixedDim != int32_t(expr->values.size())) { error("[[" + describeType(expr->makeType) + "]] array dimension mismatch, provided " + - to_string(expr->values.size()) + " elements, expecting " + to_string(expr->makeType->dim[0]), + to_string(expr->values.size()) + " elements, expecting " + to_string(expr->makeType->fixedDim), "", "", expr->at, CompilationError::mismatching_array_dimension); } else if (expr->makeType->ref) { error("[[" + describeType(expr->makeType) + "]] array can't be reference", "", "", expr->at, CompilationError::invalid_array); } - TypeDecl::clone(expr->recordType, expr->makeType); - expr->recordType->dim.clear(); + auto mkBaseT = expr->makeType; + while (mkBaseT->baseType==Type::tFixedArray && mkBaseT->firstType) mkBaseT = mkBaseT->firstType; + TypeDecl::clone(expr->recordType, mkBaseT); } expr->initAllFields = true; } @@ -1301,14 +1312,12 @@ namespace das { if (init->type && !init->type->isAutoOrAlias()) { // blah[] vs blah TypeDeclPtr mkt = nullptr; - if (!expr->gen2 && expr->makeType->dim.size() && !init->type->dim.size()) { - if (expr->makeType->dim.size() == 1 && expr->makeType->dim[0] == TypeDecl::dimAuto) { - auto infT = new TypeDecl(*expr->makeType); - infT->dim.clear(); + if (!expr->gen2 && expr->makeType->baseType==Type::tFixedArray && init->type->baseType!=Type::tFixedArray) { + if (expr->makeType->firstType->baseType!=Type::tFixedArray && expr->makeType->fixedDim == TypeDecl::dimAuto) { + auto infT = new TypeDecl(*expr->makeType->firstType); mkt = TypeDecl::inferGenericType(infT, init->type, false, false, nullptr); if (mkt) { - mkt->dim.resize(1); - mkt->dim[0] = int32_t(expr->values.size()); + mkt = makeFixedArrayTypeDecl(int32_t(expr->values.size()), mkt); } } } else { @@ -1457,13 +1466,15 @@ namespace das { error("array element has to be copyable or moveable", "", "", expr->at, CompilationError::invalid_array_element_type); } - auto resT = new TypeDecl(*expr->makeType); uint32_t resDim = uint32_t(expr->values.size()); + TypeDeclPtr resT = nullptr; if (expr->gen2) { - resT->dim.push_back(resDim); - } else if (resDim != 1 || expr->makeType->dim.size()) { - resT->dim.resize(1); - resT->dim[0] = resDim; + // wrap outermost - element count is the outer dimension (old dim.push_back was inner-first, latent order bug) + resT = makeFixedArrayTypeDecl(int32_t(resDim), new TypeDecl(*expr->makeType)); + } else if (resDim != 1 || expr->makeType->baseType==Type::tFixedArray) { + auto mkBaseT = expr->makeType; + while (mkBaseT->baseType==Type::tFixedArray && mkBaseT->firstType) mkBaseT = mkBaseT->firstType; + resT = makeFixedArrayTypeDecl(int32_t(resDim), new TypeDecl(*mkBaseT)); } else { DAS_ASSERT(expr->values.size() == 1); auto eval = expr->values[0]; diff --git a/src/ast/ast_infer_type_op.cpp b/src/ast/ast_infer_type_op.cpp index cbf7877047..ccfbfc8e56 100644 --- a/src/ast/ast_infer_type_op.cpp +++ b/src/ast/ast_infer_type_op.cpp @@ -30,7 +30,7 @@ namespace das { rangeError = false; if (!expr) return nullptr; if (!targetType) return nullptr; - if (targetType->dim.size()) return nullptr; + if (targetType->baseType==Type::tFixedArray) return nullptr; // Source: ExprConstInt/ExprConstUInt directly, OR ExprOp1("-", such const) // (when const-folding is disabled, the parser leaves -N as ExprOp1). Expression *constExpr = expr; @@ -997,7 +997,7 @@ namespace das { } else { return Visitor::visit(expr); } - } else if (cloneType->dim.size()) { + } else if (cloneType->baseType==Type::tFixedArray) { reportAstChanged(); auto cloneFn = new ExprCall(expr->at, "clone_dim"); cloneFn->arguments.push_back(expr->left->clone()); diff --git a/src/ast/ast_lint.cpp b/src/ast/ast_lint.cpp index 7e1f120b86..cfd0998941 100644 --- a/src/ast/ast_lint.cpp +++ b/src/ast/ast_lint.cpp @@ -347,10 +347,14 @@ namespace das { if ( !type ) { return false; } - if ( !allowDim && type->dim.size() ) { - return false; + auto t = type; + if ( t->baseType==Type::tFixedArray ) { + if ( !allowDim ) { + return false; + } + while ( t->baseType==Type::tFixedArray && t->firstType ) t = t->firstType; } - if ( auto * ann = (TypeAnnotation *) type->isPointerToAnnotation() ) { + if ( auto * ann = (TypeAnnotation *) t->isPointerToAnnotation() ) { if ( ann->avoidNullPtr() ) { return true; } diff --git a/src/ast/ast_typedecl.cpp b/src/ast/ast_typedecl.cpp index 32c810ec91..d7b133cbc6 100644 --- a/src/ast/ast_typedecl.cpp +++ b/src/ast/ast_typedecl.cpp @@ -244,6 +244,10 @@ namespace das if ( TT->firstType ) { applyAutoContracts(TT->firstType, autoT->firstType); } + } else if ( autoT->baseType==Type::tFixedArray ) { + if ( TT->firstType && autoT->firstType ) { + applyAutoContracts(TT->firstType, autoT->firstType); + } } else if ( autoT->baseType==Type::tIterator ) { applyAutoContracts(TT->firstType, autoT->firstType); } else if ( autoT->baseType==Type::tArray ) { @@ -402,6 +406,13 @@ namespace das } } } + // auto[] has to match a fixed array of the same outer size (dimAuto = wildcard) + if ( autoT->baseType==Type::tFixedArray ) { + if ( initT->baseType!=Type::tFixedArray || !initT->firstType ) + return nullptr; + if ( autoT->fixedDim!=TypeDecl::dimAuto && autoT->fixedDim!=initT->fixedDim ) + return nullptr; + } // non-implicit temp can't be inferred from non-temp, and non-temp from temp if ( autoT->baseType!=autoinfer && !autoT->implicit && !initT->implicit ) { if ( autoT->temporary != initT->temporary ) @@ -449,6 +460,10 @@ namespace das // if it's a pointer, infer pointer-to separately TT->firstType = inferGenericType(autoT->firstType, initT->firstType, false, false, options); if ( !TT->firstType ) return nullptr; + } else if ( autoT->baseType==Type::tFixedArray ) { + // fixed array peels one level; elements are bare per the canonical form + TT->firstType = inferGenericType(autoT->firstType, initT->firstType, false, false, options); + if ( !TT->firstType ) return nullptr; } else if ( autoT->baseType==Type::tIterator ) { // if it's a iterator, infer iterator-ofo separately TT->firstType = inferGenericType(autoT->firstType, initT->firstType, false, false, options); From bc0029b186aec45f67720669baccf6586ab03299 Mon Sep 17 00:00:00 2001 From: Boris Batkin Date: Wed, 10 Jun 2026 10:23:52 -0700 Subject: [PATCH 07/23] fixed-array 1e: simulate lowers tFixedArray; the world boots again ast_simulate.cpp: make-variant/make-struct lowering get element views (the findArgumentIndex/getVariantFieldOffset/structType/annotation reads went through the wrapper; getStride needs NO walk - FA head getStride equals the old dim'd getStride by construction); fakeVariable flatten hack wraps via makeFixedArrayTypeDecl (ref hoists to the head); ExprAscend/ExprNew dispatch walks to the element (persistent flag was silently missed through the wrapper) and ExprNew reads the pointee size via a pointer-node walk; sv_trySimulate_At and both ExprSafeAt branches take range from fixedDim; for-loop fixedSize + the FixedArrayIterator arm flip. ExprDelete untouched: infer routes FA delete to finalize_dim, and the total==1 assert proves a dim'd type never reached it in the old world either. Boot-gate fallout fixed en route: - src/builtin: expect_dim contract and the sort transformCall read fixedDim (innermost node = old dim.back() semantics). - 1a-gap FA arms in ast_typedecl: isAliasOrA2A (isAlias() returned false on FA-of-alias, so ExprCast never resolved `TT -const[N]` - the to_array_move body), applyRefToRef, collectAliasList, isAotAlias. - inferAlias FA arm now applies+clears the head's hoisted remove* contracts, the way the dim'd alias leaf used to. - AST-GC discipline (new failure class, cdb-proven): a fresh node held only in a C++ local across inferFunctionCall gets collected - ExprNew now re-derives the pointer node from the rooted expr->type after the call. Related: gc_local frees ONLY the head while TypeDecl's copy ctor deep-clones children, so a guarded deep clone leaks its subtree - the makeTypeInfo FA-flatten arm now uses a shallow borrow scratch, and the ManagedVector walk scratch guards its element node (master parity). - daslib/ast_boost: walk_and_convert_array/table built dim-vector types via the das-side .dim push (made my make-array infer double-wrap); they now wrap via the new public make_fixed_array_type helper (canonical qualifier hoist), walk_and_convert dispatches on tFixedArray, and fixedDim / fixedDimExpr are exposed on the das-side TypeDecl (1f slice pulled forward by necessity - dastest -> clargs -> $v hits this path at boot). Gates (train tip): tests-cpp 56/56 - the int[5] known-red flipped; daslang boots and runs fixed arrays end-to-end; tests/fixed_array characterization 86/86; zero GC leaks; full-tree dastest 10632 tests, 10621 passed, 11 file-level compile errors = the 1f burndown (dasPUGIXML/test_serial_dim, decs/dim_test, json/types, language/invalid_types, match x2, stbimage x4, typemacro/test_basic). Co-Authored-By: Claude Fable 5 --- FIXED_ARRAY_REWORK.md | 28 +++++++ daslib/ast_boost.das | 26 ++++++- include/daScript/ast/ast_handle.h | 4 +- src/ast/ast_debug_info_helper.cpp | 18 ++++- src/ast/ast_infer_type.cpp | 6 +- src/ast/ast_infer_type_helper.cpp | 10 +++ src/ast/ast_simulate.cpp | 78 +++++++++++-------- src/ast/ast_typedecl.cpp | 8 +- .../module_builtin_ast_annotations_1.cpp | 2 + src/builtin/module_builtin_runtime.cpp | 2 +- src/builtin/module_builtin_runtime_sort.cpp | 9 ++- 11 files changed, 142 insertions(+), 49 deletions(-) diff --git a/FIXED_ARRAY_REWORK.md b/FIXED_ARRAY_REWORK.md index d65c7219b0..5a5b78ebec 100644 --- a/FIXED_ARRAY_REWORK.md +++ b/FIXED_ARRAY_REWORK.md @@ -208,6 +208,34 @@ The indivisible piece, sub-staged for review: 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 resolve `TT -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 it `TT -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 bare `auto(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 `.dim` push — they now wrap via the new + public ast_boost helper `make_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→`$v` hits 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`/ `.dimExpr` compat properties with the flattened-array view (payload side landed at 1b-i). diff --git a/daslib/ast_boost.das b/daslib/ast_boost.das index 10fb7c79d6..125f3a062d 100644 --- a/daslib/ast_boost.das +++ b/daslib/ast_boost.das @@ -660,6 +660,21 @@ def operator as StructureAnnotation(foo : Annotation?) { return unsafe(reinterpret(foo)); } +def make_fixed_array_type(total : int; var element : TypeDecl?) : TypeDecl? { + //! Wraps `element` into a fixed-array type node of size `total` (canonical form: + //! ref/const/temporary qualifiers live on the chain head, never on the element). + var fa = new TypeDecl(at = element.at, baseType = Type.tFixedArray) + fa.fixedDim = total + fa.flags.ref = element.flags.ref + element.flags.ref = false + fa.flags.constant = element.flags.constant + element.flags.constant = false + fa.flags.temporary = element.flags.temporary + element.flags.temporary = false + fa.firstType = element + return fa +} + def private walk_and_convert_array(data : uint8 const?; info : TypeDeclPtr; at : LineInfo) : Expression? { let total = any_array_size(data) if (total != 0) { @@ -668,7 +683,7 @@ def private walk_and_convert_array(data : uint8 const?; info : TypeDeclPtr; at : any_array_foreach(data, stride) $(value) { emplace_new(mkArr.values) <| walk_and_convert(value, info.firstType, at) } - push(mkArr.makeType.dim, total) + mkArr.makeType = make_fixed_array_type(total, mkArr.makeType) var mkToArrayMove = new ExprCall(at = at, name := "to_array_move") emplace(mkToArrayMove.arguments, mkArr) return mkToArrayMove @@ -681,7 +696,10 @@ def private walk_and_convert_dim(data : uint8 const?; info : TypeDeclPtr; at : L let stride = info.baseSizeOf let total = info.countOf var einfo = clone_type(info) - clear(einfo.dim) + while (einfo.baseType == Type.tFixedArray && einfo.firstType != null) { + einfo = clone_type(einfo.firstType) + } + clear(einfo.dim) // legacy dim-vector view; nothing produces it post-rework var mkArr = new ExprMakeArray(at = at, makeType = clone_type(info)) for (x in range(total)) { unsafe { @@ -766,7 +784,7 @@ def private walk_and_convert_table(data : uint8 const?; info : TypeDeclPtr; at : emplace(mkTup.values, value) emplace(mkArr.values, mkTup) } - push(mkArr.makeType.dim, total) + mkArr.makeType = make_fixed_array_type(total, mkArr.makeType) var mkToTableMove = new ExprCall(at = at, name := "to_table_move") emplace(mkToTableMove.arguments, mkArr) return mkToTableMove @@ -845,7 +863,7 @@ def private walk_and_convert_enumeration(data : uint8 const?; info : TypeDeclPtr def walk_and_convert(data : uint8 const?; info : TypeDeclPtr; at : LineInfo) : Expression? { //! Recursively converts raw data to an AST expression based on type information. // print("0x{intptr(data)} {describe(info)}\n") - if (!empty(info.dim)) { + if (info.baseType == Type.tFixedArray || !empty(info.dim)) { return walk_and_convert_dim(data, info, at) } elif (info.baseType == Type.tArray) { return walk_and_convert_array(data, info, at) diff --git a/include/daScript/ast/ast_handle.h b/include/daScript/ast/ast_handle.h index 5d391c5330..0a8ba3a01d 100644 --- a/include/daScript/ast/ast_handle.h +++ b/include/daScript/ast/ast_handle.h @@ -502,10 +502,12 @@ namespace das { lock_guard guard(walkMutex); if ( !ati ) { - // gc_local: scratch TypeDecl just to drive makeTypeInfo — + // gc_local: scratch TypeDecls just to drive makeTypeInfo — // read once, never stored on the result. RAII unlinks from // the thread gc_root immediately and deletes at scope exit. + // BOTH nodes need a guard - the fixed-array head does not own its element. auto elemType = new TypeDecl(*vecType); + gc_local elemGuard(elemType); elemType->ref = 0; gc_local dimType(makeFixedArrayTypeDecl(1, elemType)); ati = helpA.makeTypeInfo(nullptr, dimType); diff --git a/src/ast/ast_debug_info_helper.cpp b/src/ast/ast_debug_info_helper.cpp index 60a57b679a..c16d8c9dcb 100644 --- a/src/ast/ast_debug_info_helper.cpp +++ b/src/ast/ast_debug_info_helper.cpp @@ -210,7 +210,7 @@ namespace das { TypeInfo * DebugInfoHelper::makeTypeInfo ( TypeInfo * info, const TypeDeclPtr & type ) { if ( type->baseType==Type::tFixedArray ) { // runtime TypeInfo stays flattened forever (FIXED_ARRAY_REWORK.md): collect the - // chain dims onto a scratch element clone; canonical head qualifiers ride along. + // chain dims onto a scratch element view; canonical head qualifiers ride along. // The mangled-name cache key is unaffected — chain and flattened text are identical. const TypeDecl * t = type; vector dims; @@ -219,7 +219,21 @@ namespace das { DAS_ASSERTF(t->firstType, "tFixedArray chain without an element"); t = t->firstType; } - gc_local flat(new TypeDecl(*t)); + // SHALLOW scratch borrowing the element's children: the TypeDecl copy ctor + // deep-clones sub-nodes and gc_local frees only the head, so a full clone + // here leaks the subtree on the thread root (one per FA-typed debug info) + gc_local flat(new TypeDecl(t->baseType)); + flat->structType = t->structType; + flat->enumType = t->enumType; + flat->annotation = t->annotation; + flat->firstType = t->firstType; + flat->secondType = t->secondType; + flat->argTypes = t->argTypes; + flat->argNames = t->argNames; + flat->alias = t->alias; + flat->module = t->module; + flat->at = t->at; + flat->flags = t->flags; flat->dim.insert(flat->dim.begin(), dims.begin(), dims.end()); flat->ref = type->ref; flat->constant = type->constant; diff --git a/src/ast/ast_infer_type.cpp b/src/ast/ast_infer_type.cpp index 1f001a8d41..e262e118a9 100644 --- a/src/ast/ast_infer_type.cpp +++ b/src/ast/ast_infer_type.cpp @@ -3092,9 +3092,7 @@ namespace das { // pointer-to-element ('array of pointers', as the old flat dim copy did) TypeDecl * baseT = expr->typeexpr; while (baseT->baseType == Type::tFixedArray && baseT->firstType) baseT = baseT->firstType; - TypeDeclPtr ptrType = nullptr; auto wrapNewDimChain = [&](TypeDeclPtr pt) -> TypeDeclPtr { - ptrType = pt; if (expr->typeexpr->baseType != Type::tFixedArray) return pt; auto chain = new TypeDecl(*expr->typeexpr); // deep clone of the chain auto inner = chain; @@ -3164,6 +3162,10 @@ namespace das { expr->name = saveName; } swap(resultType, expr->type); + // pointer node re-derived from the rooted expr->type AFTER inferFunctionCall - + // a raw local held across it can be collected by the AST gc (proven on `new Class()`) + TypeDecl * ptrType = expr->type; + while (ptrType->baseType == Type::tFixedArray && ptrType->firstType) ptrType = ptrType->firstType; if (expr->func) { if (!ptrType->firstType->isSameType(*resultType, RefMatters::yes, ConstMatters::yes, TemporaryMatters::yes)) { error("initializer returns '" + describeType(resultType) + "' vs '" + describeType(ptrType->firstType) + "'", "", "", diff --git a/src/ast/ast_infer_type_helper.cpp b/src/ast/ast_infer_type_helper.cpp index 1ae57cfa3c..c77fd1a10d 100644 --- a/src/ast/ast_infer_type_helper.cpp +++ b/src/ast/ast_infer_type_helper.cpp @@ -439,6 +439,16 @@ namespace das { if (!resT->firstType) return nullptr; } + if (decl->baseType == Type::tFixedArray) { + // the chain head carries the hoisted qualifiers+contracts the dim'd alias leaf used to; + // apply and clear them here the way the alias-leaf case does + resT->ref = resT->ref && !decl->removeRef; + resT->constant = resT->constant && !decl->removeConstant; + resT->temporary = resT->temporary && !decl->removeTemporary; + resT->removeRef = false; + resT->removeConstant = false; + resT->removeTemporary = false; + } } else if (decl->baseType == Type::tTable) { if (decl->firstType) { resT->firstType = inferAlias(decl->firstType, fptr, aliases, options, autoToAlias); diff --git a/src/ast/ast_simulate.cpp b/src/ast/ast_simulate.cpp index fcfcc6ec0d..9d02138526 100644 --- a/src/ast/ast_simulate.cpp +++ b/src/ast/ast_simulate.cpp @@ -695,15 +695,17 @@ namespace das void ExprMakeVariant::setRefSp ( bool ref, bool cmres, uint32_t sp, uint32_t off ) { ExprMakeLocal::setRefSp(ref, cmres, sp, off); + auto mkBaseT = makeType; // element view - makeType may be a fixed-array chain + while ( mkBaseT->baseType==Type::tFixedArray && mkBaseT->firstType ) mkBaseT = mkBaseT->firstType; int stride = makeType->getStride(); // we go through all fields, and if its [[ ]] field // we tell it to piggy-back on our current sp, with appropriate offset int index = 0; for ( const auto & decl : variants ) { - auto fieldVariant = makeType->findArgumentIndex(decl->name); + auto fieldVariant = mkBaseT->findArgumentIndex(decl->name); DAS_ASSERT(fieldVariant!=-1 && "should have failed in type infer otherwise"); if ( decl->value->rtti_isMakeLocal() ) { - auto fieldOffset = makeType->getVariantFieldOffset(fieldVariant); + auto fieldOffset = mkBaseT->getVariantFieldOffset(fieldVariant); uint32_t offset = extraOffset + index*stride + fieldOffset; auto mkl = static_cast(decl->value); mkl->setRefSp(ref, cmres, sp, offset); @@ -727,6 +729,8 @@ namespace das gc_guard gc_scope; vector simlist; int index = 0; + auto mkBaseT = mkv->makeType; // element view - makeType may be a fixed-array chain + while ( mkBaseT->baseType==Type::tFixedArray && mkBaseT->firstType ) mkBaseT = mkBaseT->firstType; int stride = mkv->makeType->getStride(); // init with 0 it its 'default' initialization if ( stride && mkv->variants.empty() ) { @@ -749,7 +753,7 @@ namespace das } // now fields for ( const auto & decl : mkv->variants ) { - auto fieldVariant = mkv->makeType->findArgumentIndex(decl->name); + auto fieldVariant = mkBaseT->findArgumentIndex(decl->name); DAS_ASSERT(fieldVariant!=-1 && "should have failed in type infer otherwise"); // lets set variant index uint32_t voffset = mkv->extraOffset + index*stride; @@ -766,7 +770,7 @@ namespace das } simlist.push_back(svi); // field itself - auto fieldOffset = mkv->makeType->getVariantFieldOffset(fieldVariant); + auto fieldOffset = mkBaseT->getVariantFieldOffset(fieldVariant); uint32_t offset = voffset + fieldOffset; SimNode * cpy = nullptr; if ( decl->value->rtti_isMakeLocal() ) { @@ -820,8 +824,10 @@ namespace das void ExprMakeStruct::setRefSp ( bool ref, bool cmres, uint32_t sp, uint32_t off ) { ExprMakeLocal::setRefSp(ref, cmres, sp, off); + auto mkBaseT = makeType; // element view - makeType may be a fixed-array chain + while ( mkBaseT->baseType==Type::tFixedArray && mkBaseT->firstType ) mkBaseT = mkBaseT->firstType; // if it's a handle type, we can't reuse the make-local chain - if ( makeType->baseType == Type::tHandle ) return; + if ( mkBaseT->baseType == Type::tHandle ) return; // we go through all fields, and if its [[ ]] field // we tell it to piggy-back on our current sp, with appropriate offset int total = int(structs.size()); @@ -829,7 +835,7 @@ namespace das for ( int index=0; index != total; ++index ) { auto & fields = structs[index]; for ( const auto & decl : *fields ) { - auto field = makeType->structType->findField(decl->name); + auto field = mkBaseT->structType->findField(decl->name); DAS_ASSERT(field && "should have failed in type infer otherwise"); if ( decl->value->rtti_isMakeLocal() ) { uint32_t offset = extraOffset + index*stride + field->offset; @@ -855,9 +861,11 @@ namespace das vector simlist; // init with 0 int total = int(mks->structs.size()); + auto mkBaseT = mks->makeType; // element view - makeType may be a fixed-array chain + while ( mkBaseT->baseType==Type::tFixedArray && mkBaseT->firstType ) mkBaseT = mkBaseT->firstType; int stride = mks->makeType->getStride(); // note: if its an empty tuple init, like [[tuple]] and its embedded - we need to zero it out - bool emptyEmbeddedTuple = ( mks->makeType->baseType==Type::tTuple && total==0); + bool emptyEmbeddedTuple = ( mkBaseT->baseType==Type::tTuple && total==0); bool partialyInitStruct = !mks->doesNotNeedInit && !mks->initAllFields; if ( (emptyEmbeddedTuple || partialyInitStruct) && stride ) { int bytes = das::max(total,1) * stride; @@ -877,7 +885,7 @@ namespace das } if (init0) simlist.push_back(init0); } - if ( mks->makeType->baseType == Type::tStructure ) { + if ( mkBaseT->baseType == Type::tStructure ) { for ( int index=0; index != total; ++index ) { if ( mks->constructor ) { uint32_t offset = mks->extraOffset + index*stride; @@ -895,7 +903,7 @@ namespace das } auto & fields = mks->structs[index]; for ( const auto & decl : *fields ) { - auto field = mks->makeType->structType->findField(decl->name); + auto field = mkBaseT->structType->findField(decl->name); DAS_ASSERT(field && "should have failed in type infer otherwise"); uint32_t offset = mks->extraOffset + index*stride + field->offset; SimNode * cpy; @@ -931,7 +939,7 @@ namespace das } } } else { - auto ann = mks->makeType->annotation; + auto ann = mkBaseT->annotation; // making fake variable, which points to out field string fakeName = "__makelocal"; auto fakeVariable = new Variable(); @@ -946,7 +954,8 @@ namespace das fakeVariable->extraLocalOffset = mks->extraOffset; fakeVariable->type->ref = true; if ( total != 1 ) { - fakeVariable->type->dim.push_back(total); + // wrap hoists ref onto the fixed-array head (canonical form) + fakeVariable->type = makeFixedArrayTypeDecl(total, fakeVariable->type); } } fakeVariable->generated = true; @@ -1808,14 +1817,16 @@ namespace das typeInfo = context.thisHelper->makeTypeInfo(nullptr, expr->subexpr->type); } SimNode * result; - if ( expr->subexpr->type->baseType==Type::tHandle ) { + auto ascBaseT = expr->subexpr->type; // element view - may be a fixed-array chain + while ( ascBaseT->baseType==Type::tFixedArray && ascBaseT->firstType ) ascBaseT = ascBaseT->firstType; + if ( ascBaseT->baseType==Type::tHandle ) { DAS_ASSERTF(expr->useStackRef,"new of handled type should always be over stackref"); - auto ne = expr->subexpr->type->annotation->simulateGetNew(context, at); + auto ne = ascBaseT->annotation->simulateGetNew(context, at); result = context.code->makeNode(at, se, ne, bytes, expr->stackTop); } else { bool persistent = false; - if ( expr->subexpr->type->baseType==Type::tStructure ) { - persistent = expr->subexpr->type->structType->persistent; + if ( ascBaseT->baseType==Type::tStructure ) { + persistent = ascBaseT->structType->persistent; } if ( expr->useStackRef ) { result = context.code->makeNode(at, se, bytes, expr->stackTop, typeInfo, persistent); @@ -1830,12 +1841,14 @@ namespace das ExpressionPtr SimulateVisitor::visit(ExprNew * expr) { const auto &at = expr->at; SimNode * newNode; - if ( expr->typeexpr->baseType == Type::tHandle ) { - DAS_ASSERT(expr->typeexpr->annotation->canNew() && "how???"); + auto newBaseT = expr->typeexpr; // element view - may be a fixed-array chain + while ( newBaseT->baseType==Type::tFixedArray && newBaseT->firstType ) newBaseT = newBaseT->firstType; + if ( newBaseT->baseType == Type::tHandle ) { + DAS_ASSERT(newBaseT->annotation->canNew() && "how???"); if ( expr->initializer ) { auto pCall = static_cast(expr->func->makeSimNode(context, expr->arguments)); sv_simulateCall(expr->func, expr, pCall); - pCall->cmresEval = expr->typeexpr->annotation->simulateGetNew(context, at); + pCall->cmresEval = newBaseT->annotation->simulateGetNew(context, at); if ( !pCall->cmresEval ) { context.thisProgram->error("integration error, simulateGetNew returned null", "", "", at, CompilationError::internal_expression ); @@ -1843,7 +1856,7 @@ namespace das setE(expr, pCall); return expr; } else { - newNode = expr->typeexpr->annotation->simulateGetNew(context, at); + newNode = newBaseT->annotation->simulateGetNew(context, at); if ( !newNode ) { context.thisProgram->error("integration error, simulateGetNew returned null", "", "", at, CompilationError::internal_expression ); @@ -1851,10 +1864,13 @@ namespace das } } else { bool persistent = false; - if ( expr->typeexpr->baseType == Type::tStructure ) { - persistent = expr->typeexpr->structType->persistent; + if ( newBaseT->baseType == Type::tStructure ) { + persistent = newBaseT->structType->persistent; } - int32_t bytes = expr->type->firstType->getBaseSizeOf(); + // expr->type is FA(...,ptr) for `new T[N]` - walk to the pointer node for the pointee size + auto ptrT = expr->type; + while ( ptrT->baseType==Type::tFixedArray && ptrT->firstType ) ptrT = ptrT->firstType; + int32_t bytes = ptrT->firstType->getBaseSizeOf(); if ( expr->initializer ) { auto pCall = (SimNode_CallBase *) context.code->makeNodeUnrollAny( int(expr->arguments.size()), at, bytes, persistent); @@ -1867,7 +1883,7 @@ namespace das newNode = context.code->makeNode(at, bytes, persistent); } } - if ( expr->type->dim.size() ) { + if ( expr->type->baseType==Type::tFixedArray ) { uint32_t count = expr->type->getCountOf(); setE(expr, context.code->makeNode(at, newNode, expr->stackTop, count)); } else { @@ -1941,7 +1957,7 @@ namespace das }; } } else { - uint32_t range = expr->subexpr->type->dim[0]; + uint32_t range = uint32_t(expr->subexpr->type->fixedDim); uint32_t stride = expr->subexpr->type->getStride(); if ( expr->index->rtti_isConstant() ) { // if its constant index, like a[3]..., we try to let node bellow simulate @@ -2092,8 +2108,8 @@ namespace das } setE(expr, context.code->makeValueNode(valueType, at, prv, pidx, valueTypeSize, 0)); } - } else if ( seT->dim.size() ) { - uint32_t range = seT->dim[0]; + } else if ( seT->baseType==Type::tFixedArray ) { + uint32_t range = uint32_t(seT->fixedDim); uint32_t stride = seT->getStride(); auto prv = getE(expr->subexpr); auto pidx = getE(expr->index); @@ -2148,8 +2164,8 @@ namespace das } setE(expr, context.code->makeValueNode(valueType, at, prv, pidx, valueTypeSize, 0)); } - } else if ( seT->dim.size() ) { - uint32_t range = seT->dim[0]; + } else if ( seT->baseType==Type::tFixedArray ) { + uint32_t range = uint32_t(seT->fixedDim); uint32_t stride = seT->getStride(); auto prv = getE(expr->subexpr); auto pidx = getE(expr->index); @@ -3081,7 +3097,7 @@ namespace das for ( auto & src : expr->sources ) { if ( !src->type ) continue; if ( src->type->isArray() ) { - fixedSize = das::min(fixedSize, src->type->dim[0]); + fixedSize = das::min(fixedSize, src->type->fixedDim); fixedArrays = true; } else if ( src->type->isGoodArrayType() ) { dynamicArrays = true; @@ -3159,11 +3175,11 @@ namespace das expr->sources[t] ); } - } else if ( expr->sources[t]->type->dim.size() ) { + } else if ( expr->sources[t]->type->baseType==Type::tFixedArray ) { result->source_iterators[t] = context.code->makeNode( expr->sources[t]->at, getE(expr->sources[t]), - expr->sources[t]->type->dim[0], + uint32_t(expr->sources[t]->type->fixedDim), expr->sources[t]->type->getStride()); } else { DAS_ASSERTF(0, "we should not be here. we are doing iterator for on an unsupported type."); diff --git a/src/ast/ast_typedecl.cpp b/src/ast/ast_typedecl.cpp index d7b133cbc6..42df0996c3 100644 --- a/src/ast/ast_typedecl.cpp +++ b/src/ast/ast_typedecl.cpp @@ -210,7 +210,7 @@ namespace das } } else if ( TT->baseType==Type::tIterator ) { applyRefToRef(TT->firstType); - } else if ( TT->baseType==Type::tArray ) { + } else if ( TT->baseType==Type::tArray || TT->baseType==Type::tFixedArray ) { applyRefToRef(TT->firstType); } else if ( TT->baseType==Type::tTable ) { applyRefToRef(TT->firstType); @@ -2226,7 +2226,7 @@ namespace das } else if ( baseType==Type::tIterator ) { if ( firstType ) firstType->collectAliasList(aliases); - } else if ( baseType==Type::tArray ) { + } else if ( baseType==Type::tArray || baseType==Type::tFixedArray ) { if ( firstType ) firstType->collectAliasList(aliases); } else if ( baseType==Type::tTable ) { @@ -2253,7 +2253,7 @@ namespace das } else if ( baseType==Type::tIterator ) { if ( firstType ) return firstType->isAotAlias(); - } else if ( baseType==Type::tArray ) { + } else if ( baseType==Type::tArray || baseType==Type::tFixedArray ) { if ( firstType ) return firstType->isAotAlias(); } else if ( baseType==Type::tTable ) { @@ -2297,7 +2297,7 @@ namespace das } else if ( baseType==Type::tIterator ) { if ( firstType ) return firstType->isAliasOrA2A(a2a); - } else if ( baseType==Type::tArray ) { + } else if ( baseType==Type::tArray || baseType==Type::tFixedArray ) { if ( firstType ) return firstType->isAliasOrA2A(a2a); } else if ( baseType==Type::tTable ) { diff --git a/src/builtin/module_builtin_ast_annotations_1.cpp b/src/builtin/module_builtin_ast_annotations_1.cpp index 9b40919ffb..f5b7598863 100644 --- a/src/builtin/module_builtin_ast_annotations_1.cpp +++ b/src/builtin/module_builtin_ast_annotations_1.cpp @@ -36,6 +36,8 @@ namespace das { addField("argTypes"); addField("argNames"); addField("dim"); + addField("fixedDim"); + addField("fixedDimExpr"); // compat view (FIXED_ARRAY_REWORK.md, 1b): the typeMacro/typeDecl/tag payload // moved to typeMacroExpr; daslib keeps reading it under the .dimExpr name addPropertyExtConst< diff --git a/src/builtin/module_builtin_runtime.cpp b/src/builtin/module_builtin_runtime.cpp index f1b636e592..4caa0f04c3 100644 --- a/src/builtin/module_builtin_runtime.cpp +++ b/src/builtin/module_builtin_runtime.cpp @@ -407,7 +407,7 @@ namespace das for ( size_t ai=0; ai!=maxIndex; ++ ai) { if ( decl.arguments.find(fn->arguments[ai]->name, Type::tBool) ) { const auto & argT = types[ai]; - if ( argT->dim.size() == 0 ) { + if ( argT->baseType != Type::tFixedArray ) { err = "argument " + fn->arguments[ai]->name + " is expected to be a dim []"; return false; } diff --git a/src/builtin/module_builtin_runtime_sort.cpp b/src/builtin/module_builtin_runtime_sort.cpp index 6789ddec53..e335d095a2 100644 --- a/src/builtin/module_builtin_runtime_sort.cpp +++ b/src/builtin/module_builtin_runtime_sort.cpp @@ -490,15 +490,16 @@ namespace das } } const auto & arg = call->arguments[0]; - if ( arg->type->dim.size() ) { - auto arrType = new TypeDecl(*arg->type); - arrType->dim.clear(); + if ( arg->type->baseType==Type::tFixedArray ) { + auto faT = arg->type; // innermost fixed-array node (matches old dim.back() semantics) + while ( faT->firstType && faT->firstType->baseType==Type::tFixedArray ) faT = faT->firstType; + auto arrType = new TypeDecl(*faT->firstType); auto newCall = static_cast(call->clone()); auto stride = arrType->getSizeOf(); auto strideArg = new ExprConstInt(call->at, stride); strideArg->generated = true; newCall->arguments.insert(newCall->arguments.begin()+1,strideArg); - auto length = arg->type->dim.back(); + auto length = faT->fixedDim; auto lengthArg = new ExprConstInt(call->at, length); lengthArg->generated = true; newCall->arguments.insert(newCall->arguments.begin()+2,lengthArg); From bab18fffe0173171db4ce18437ac79eaa0d1a5ed Mon Sep 17 00:00:00 2001 From: Boris Batkin Date: Wed, 10 Jun 2026 12:35:03 -0700 Subject: [PATCH 08/23] fixed-array 1f: alias peel + .dim flattened view; emitter/JIT ride compat Stage 1f of FIXED_ARRAY_REWORK.md. The 11-file burndown collapsed into four classes: 1) Generic alias dim semantics = MASTER-COMPAT PEEL (settled). Master's rule, pinned empirically against a saved pre-flip build: a generic alias use-site's own dims REPLACE the bound type's dims - bare TT bound from int[3] is the ELEMENT int; TT[4] is int[4]; the strip applies even through array. Module typedefs append naturally (both worlds already agreed). peelFixedArrayAliasBinding at the alias leaf of inferAlias + inferPartialAliases (head qualifiers transfer to the element; the partial-alias arm keeps the binding's label). The FA recursion arm now HOISTS the resolved element's ref/const/temporary to the chain head (canonical form) - without it TT[4] kept const on the element and var-decl const-stripping missed it. daslib's `TT[typeinfo dim(x)]` reconstruct pattern (json_boost/PUGIXML_boost/decs) works unmodified. 2) das-side `.dim` is now a READ-ONLY flattened compat property (TypeDecl::dimCompat -> per-node transient dimCompatCache; NOT the legacy dim field - warming that would nondeterministically revive dead C++ dim-vector loops on FA nodes). 59 daslib read sites ride it unmodified. das writers break at compile by design; in-tree writers ported here: match.das (pop -> firstType), typemacro test fixture + tutorial twin (-> make_fixed_array_type), dasGlsl produce_zero, dasLLVM llvm_jit x7 (clear/erase/shift-resize -> element walks; the `new Handle[N]` tHandle gate now walks the chain; LLVM_JIT_CODEGEN_VERSION 0x22 -> 0x23). 3) aot_cpp.das had four FA gaps (the 1d+1e train's AOT lane was red from these): describeCppTypeEx panicked on FA nodes ("Missed type tFixedArray") and emission swallows macro panics, so struct fields and local declarations silently vanished from generated C++; dim'd make-variant dropped its das_get_variant_field::set wrapper; dim'd make-struct tHandle gate + tuple/variant field helpers, same class; dim'd ExprNew emitted the POINTER type where das_new_dim<> wants the POINTEE (TDim conversion error) and mis-branched handle news past das_new_dim_handle. All fixed via the fa_element clone-walk helper (clone-walk keeps the result non-const whatever flavor the caller holds); dims still emit from the head's .dim view. 4) verifyType's FA too-big check now multiplies element COUNTS across the chain (master parity: 32767^2 passes infer; lint owns byte-size with the site-specific exceeds_* codes). decs get_ro declared its dim'd return as TT[typeinfo sizeof(value)] - bytes - and worked on master only because an unresolvable result dim was silently discarded; fixed to typeinfo 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: alias probes match master 100%; all 11 burndown files green; tests-cpp 56/56; tests/fixed_array 86/86; full tree 10784/10784 interpreted and 10123/10123 under test_aot -use-aot; zero GC leaks in both modes; lint 0 errors on every changed file. Co-Authored-By: Claude Fable 5 --- FIXED_ARRAY_REWORK.md | 51 +++++++ daslib/aot_cpp.das | 126 ++++++++++-------- daslib/ast_boost.das | 1 - daslib/decs.das | 2 +- daslib/match.das | 3 +- include/daScript/ast/ast_typedecl.h | 6 + modules/dasGlsl/glsl/glsl_internal.das | 5 +- modules/dasLLVM/daslib/llvm_jit.das | 33 ++--- modules/dasLLVM/daslib/llvm_jit_common.das | 8 +- modules/dasLLVM/daslib/llvm_jit_run.das | 2 +- src/ast/ast_infer_type_helper.cpp | 55 ++++++-- src/ast/ast_typedecl.cpp | 13 ++ .../module_builtin_ast_annotations_1.cpp | 7 +- tests/typemacro/_typemacro_mod.das | 10 +- tutorials/macros/type_macro_mod.das | 4 +- 15 files changed, 222 insertions(+), 104 deletions(-) diff --git a/FIXED_ARRAY_REWORK.md b/FIXED_ARRAY_REWORK.md index 5a5b78ebec..1167375bf0 100644 --- a/FIXED_ARRAY_REWORK.md +++ b/FIXED_ARRAY_REWORK.md @@ -241,6 +241,57 @@ The indivisible piece, sub-staged for review: `.dimExpr` compat 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 via AskUserQuestion: + MASTER-COMPAT PEEL. Master's rule (empirically pinned with the saved 1b-i exe): a + generic alias use-site's own dims REPLACE the bound type's dims — bare `TT` bound from + `int[3]` is the ELEMENT `int`, `TT[4]` is `int[4]`, and the strip applies even through + `array`; module typedefs append naturally (`foo[2]` where `foo=int[3]` is + `int[2][3]`) in both worlds. Implementation: `peelFixedArrayAliasBinding` at the alias + leaf in inferAlias + inferPartialAliases (head qualifiers transfer to the element on + peel; arm 2 keeps the binding's alias label — clearing breaks typemacro aliases), plus + the FA-recursion arm now HOISTS the resolved element's ref/const/temporary to the chain + head (canonical form; without it `TT[4]` kept const on the element and var-decl + const-stripping missed it). daslib's `TT[typeinfo dim(x)]` reconstruct pattern works + unmodified. Probe matrix lives in D:/Work/fa_scratch/alias_dim_probe*.das. + (2) DASLIB `.dim` READERS (match tests, stbimage→is_local, aot_cpp): the das `.dim` + binding 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 legacy `dim` field: 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 (+ the `new 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 its `das_get_variant_field:: + set` wrapper (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 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 `.dim` view, all structural dispatch walks to the chain element via a + new `fa_element` clone-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,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_aot` pass 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 as + `TT[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 to `typeinfo 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: probes match master 100%, all 11 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. ### Stage 2 — AOT emitter `daslib/aot_cpp.das` ports to the structural API; nested `TDim` emission becomes natural diff --git a/daslib/aot_cpp.das b/daslib/aot_cpp.das index 74e960dde5..493bc7e5be 100644 --- a/daslib/aot_cpp.das +++ b/daslib/aot_cpp.das @@ -279,6 +279,17 @@ def aotEnumValueName(name : das_string) { } +def fa_element(t : TypeDeclPtr) { + // element of a tFixedArray chain (a clone of t itself when not dim'd) - structural + // reads (argTypes/annotation/variant offsets) live on the element, not the chain + // head. Clone-walks so the result is non-const whatever the caller's view is. + var e = clone_type(t) + while (e.baseType == Type.tFixedArray && e.firstType != null) { + e = clone_type(e.firstType) + } + return e +} + def describeCppTypeEx(typeDecl : TypeDeclPtr; in_cfg : DescribeConfig; useAlias : CpptUseAlias) { @@ -292,7 +303,13 @@ def describeCppTypeEx(typeDecl : TypeDeclPtr; cfg.skip_const = true; } } - let baseType = typeDecl.baseType; + // dims live on the tFixedArray chain head (the .dim compat view flattens them); + // element kind, sub-types and annotations dispatch on the chain ELEMENT + var elemDecl = typeDecl + while (elemDecl.baseType == Type.tFixedArray && elemDecl.firstType != null) { + elemDecl = elemDecl.firstType + } + let baseType = elemDecl.baseType; return build_string() $(writer) { for (_ in range(length(typeDecl.dim))) { @@ -309,104 +326,104 @@ def describeCppTypeEx(typeDecl : TypeDeclPtr; } write(writer, "DAS_COMMENT(auto{alias})") } elif (baseType == Type.tHandle) { - let handle_name = string(typeDecl.annotation.cppName.empty() ? typeDecl.annotation.name : typeDecl.annotation.cppName) + let handle_name = string(elemDecl.annotation.cppName.empty() ? elemDecl.annotation.name : elemDecl.annotation.cppName) write(writer, "{handle_name}") } elif (baseType == Type.tArray) { - if (typeDecl.firstType != null) { - let cpp_typeDecl = describeCppTypeEx(typeDecl.firstType, DescribeConfig(cross_platform = cfg.cross_platform), useAlias); + if (elemDecl.firstType != null) { + let cpp_typeDecl = describeCppTypeEx(elemDecl.firstType, DescribeConfig(cross_platform = cfg.cross_platform), useAlias); write(writer, "TArray<{cpp_typeDecl}>") } else { write(writer, "Array") } } elif (baseType == Type.tTable) { - if (typeDecl.firstType != null && typeDecl.secondType != null) { - let first_type = describeCppTypeEx(typeDecl.firstType, DescribeConfig(skip_const = true, cross_platform = cfg.cross_platform), useAlias); - let second_type = describeCppTypeEx(typeDecl.secondType, DescribeConfig(cross_platform = cfg.cross_platform), useAlias); + if (elemDecl.firstType != null && elemDecl.secondType != null) { + let first_type = describeCppTypeEx(elemDecl.firstType, DescribeConfig(skip_const = true, cross_platform = cfg.cross_platform), useAlias); + let second_type = describeCppTypeEx(elemDecl.secondType, DescribeConfig(cross_platform = cfg.cross_platform), useAlias); write(writer, "TTable<{first_type},{second_type}>"); } else { write(writer, "Table"); } } elif (baseType == Type.tTuple) { - let types = (each(typeDecl.argTypes) + let types = (each(elemDecl.argTypes) ._select(describeCppTypeEx(_, DescribeConfig(cross_platform = cfg.cross_platform), useAlias)) .to_array() ._fold()) |> join(",") if (cfg.cross_platform) { write(writer, "AutoTuple<{types}>") } else { - write(writer, "TTuple<{typeDecl.tupleSize},{types}>") + write(writer, "TTuple<{elemDecl.tupleSize},{types}>") } } elif (baseType == Type.tVariant) { - let types = (each(typeDecl.argTypes) + let types = (each(elemDecl.argTypes) ._select(describeCppTypeEx(_, DescribeConfig(cross_platform = cfg.cross_platform), useAlias)) .to_array() ._fold()) |> join(",") if (cfg.cross_platform) { write(writer, "AutoVariant<{types}>"); } else { - write(writer, "TVariant<{typeDecl.variantSize},{typeDecl.variantAlign},{types}>") + write(writer, "TVariant<{elemDecl.variantSize},{elemDecl.variantAlign},{types}>") } } elif (baseType == Type.tStructure) { - if (typeDecl.structType != null) { + if (elemDecl.structType != null) { // Elaborated-type-specifier (`struct X`) keeps GCC's -Wchanges-meaning // happy when a field name shadows the struct name in the same scope — // e.g. `struct User { TTuple<...,struct Profile> Profile; }`. Valid in // every C++ context where a bare struct name is, including template args // and qualified names. - if (typeDecl.structType._module.name.empty()) { - write(writer, "struct {aotStructName(typeDecl.structType)}") + if (elemDecl.structType._module.name.empty()) { + write(writer, "struct {aotStructName(elemDecl.structType)}") } else { - write(writer, "struct {aotModuleName(typeDecl.structType._module)}::{aotStructName(typeDecl.structType)}") + write(writer, "struct {aotModuleName(elemDecl.structType._module)}::{aotStructName(elemDecl.structType)}") } } else { write(writer, "DAS_COMMENT(unspecified structure) "); } } elif (baseType == Type.tPointer) { - if (!typeDecl.flags.smartPtr) { - if (typeDecl.firstType != null) { - write(writer, "{describeCppTypeEx(typeDecl.firstType,DescribeConfig(redundant_const=false,cross_platform=cfg.cross_platform),useAlias)} *"); + if (!elemDecl.flags.smartPtr) { + if (elemDecl.firstType != null) { + write(writer, "{describeCppTypeEx(elemDecl.firstType,DescribeConfig(redundant_const=false,cross_platform=cfg.cross_platform),useAlias)} *"); } else { write(writer, "void *"); } } else { - let ptr_name = typeDecl.flags.smartPtrNative && cfg.use_smart_ptr ? "smart_ptr" : "smart_ptr_raw" + let ptr_name = elemDecl.flags.smartPtrNative && cfg.use_smart_ptr ? "smart_ptr" : "smart_ptr_raw" var type_decl_name = "void" - if (typeDecl.firstType != null) { - type_decl_name = describeCppTypeEx(typeDecl.firstType, DescribeConfig(redundant_const = false, cross_platform = cfg.cross_platform), useAlias); + if (elemDecl.firstType != null) { + type_decl_name = describeCppTypeEx(elemDecl.firstType, DescribeConfig(redundant_const = false, cross_platform = cfg.cross_platform), useAlias); } write(writer, "{ptr_name}<{type_decl_name}>"); } - } elif (typeDecl.isEnumT) { - if (typeDecl.enumType != null) { - if (typeDecl.enumType.external) { - write(writer, "DAS_COMMENT(bound_enum) {typeDecl.enumType.cppName}"); - } elif (typeDecl.enumType._module.name.empty()) { - write(writer, "DAS_COMMENT(enum) {aotEnumName(typeDecl.enumType)}"); + } elif (elemDecl.isEnumT) { + if (elemDecl.enumType != null) { + if (elemDecl.enumType.external) { + write(writer, "DAS_COMMENT(bound_enum) {elemDecl.enumType.cppName}"); + } elif (elemDecl.enumType._module.name.empty()) { + write(writer, "DAS_COMMENT(enum) {aotEnumName(elemDecl.enumType)}"); } else { - write(writer, "DAS_COMMENT(enum) {aotModuleName(typeDecl.enumType._module)}::{aotEnumName(typeDecl.enumType)}") + write(writer, "DAS_COMMENT(enum) {aotModuleName(elemDecl.enumType._module)}::{aotEnumName(elemDecl.enumType)}") } } else { write(writer, "DAS_COMMENT(unspecified enumeration)") } } elif (baseType == Type.tIterator) { - if (typeDecl.firstType != null) { + if (elemDecl.firstType != null) { let new_cfg = DescribeConfig(substitute_ref = cfg.substitute_ref, skip_ref = cfg.skip_ref, skip_const = cfg.skip_const, redundant_const = true, cross_platform = cfg.cross_platform) - write(writer, "Sequence DAS_COMMENT(({describeCppTypeEx(typeDecl.firstType,new_cfg,useAlias)}))") + write(writer, "Sequence DAS_COMMENT(({describeCppTypeEx(elemDecl.firstType,new_cfg,useAlias)}))") } else { write(writer, "Sequence") } } elif (baseType == Type.tBlock || baseType == Type.tFunction || baseType == Type.tLambda) { - let maybe_const = !typeDecl.flags.constant && typeDecl.baseType == Type.tBlock ? "const " : "" + let maybe_const = !typeDecl.flags.constant && baseType == Type.tBlock ? "const " : "" var type_name = "void" - if (typeDecl.firstType != null) { - type_name = describeCppTypeEx(typeDecl.firstType, DescribeConfig(redundant_const = true, cross_platform = cfg.cross_platform), useAlias) + if (elemDecl.firstType != null) { + type_name = describeCppTypeEx(elemDecl.firstType, DescribeConfig(redundant_const = true, cross_platform = cfg.cross_platform), useAlias) } - let extra_comma = !empty(typeDecl.argTypes) ? "," : ""; - let arg_types = (each(typeDecl.argTypes) + let extra_comma = !empty(elemDecl.argTypes) ? "," : ""; + let arg_types = (each(elemDecl.argTypes) ._select(describeCppTypeEx(_, DescribeConfig(redundant_const = true, cross_platform = cfg.cross_platform), useAlias)) .to_array() ._fold()) |> join(",") @@ -2078,7 +2095,8 @@ class public CppAot : AstVisitor { ._fold()) |> join(",") } - def get_variant_field(field_type : TypeDeclPtr; index : int; is_pointer : bool = false) { + def get_variant_field(in_field_type : TypeDeclPtr; index : int; is_pointer : bool = false) { + let field_type = fa_element(in_field_type) let type_str = describeCppType(field_type.argTypes[index], DescribeConfig(cross_platform = cross_platform)); let maybe_ptr = is_pointer ? "_ptr" : ""; if (cross_platform) { @@ -2182,7 +2200,8 @@ class public CppAot : AstVisitor { return field; } // field - def get_tuple_field(field_type : TypeDeclPtr; index : int; is_pointer : bool = false) { + def get_tuple_field(in_field_type : TypeDeclPtr; index : int; is_pointer : bool = false) { + let field_type = fa_element(in_field_type) let field_tp = describeCppType(field_type.argTypes[index], DescribeConfig(cross_platform = cross_platform)); let maybe_ptr = is_pointer ? "_ptr" : ""; if (cross_platform) { @@ -2897,20 +2916,20 @@ class public CppAot : AstVisitor { return } if (!enew._type.dim |> empty()) { - if (enew._type.firstType.isHandle) { - let type_str = describeCppType(enew._type.firstType, DescribeConfig(skip_ref = true, skip_const = true, cross_platform = cross_platform)); - write(*ss, "das_new_dim_handle<{type_str},{enew._type.dim[0]},{enew._type.flags.smartPtr}"); + let ptrType = fa_element(enew._type) // the fixed-array chain wraps the pointer node; pointee is its firstType + if (ptrType.firstType.isHandle) { + let type_str = describeCppType(ptrType.firstType, DescribeConfig(skip_ref = true, skip_const = true, cross_platform = cross_platform)); + write(*ss, "das_new_dim_handle<{type_str},{enew._type.dim[0]},{ptrType.flags.smartPtr}"); if (enew.initializer) { panic("internal error. initializer for enew is not supported"); } else { write(*ss, ">::make(__context__"); } } else { - let type_str = describeCppType(enew._type.firstType, DescribeConfig(skip_ref = true, skip_const = true, cross_platform = cross_platform)); + let type_str = describeCppType(ptrType.firstType, DescribeConfig(skip_ref = true, skip_const = true, cross_platform = cross_platform)); write(*ss, "das_new_dim<{type_str},{enew._type.dim[0]}"); if (enew.initializer) { write(*ss, ">::make_and_init(__context__,[&]() \{ return "); - // << " . " << describeCppType(enew._type.firstType,DescribeConfig(skip_ref=true,skip_const=true,cross_platform=cross_platform)) CallFunc_preVisit(enew); } else { write(*ss, ">::make(__context__"); @@ -2990,22 +3009,24 @@ class public CppAot : AstVisitor { } } def override preVisitExprMakeVariantField(expr : ExprMakeVariant?; index : int; decl : MakeFieldDeclPtr; last : bool) { - let variantIndex = find_argument_index(expr._type, string(decl.name)); + let variantType = fa_element(expr._type) // dim'd make-variant carries the variant on the chain element + let variantIndex = find_argument_index(variantType, string(decl.name)); if (variantIndex == -1) { panic("should not infer otherwise"); } - let type_str = describeCppType(expr._type.argTypes[variantIndex], DescribeConfig(cross_platform = cross_platform)); - write(*ss, "{tabs()}{get_variant_field(expr._type, variantIndex)}::set("); + let type_str = describeCppType(variantType.argTypes[variantIndex], DescribeConfig(cross_platform = cross_platform)); + write(*ss, "{tabs()}{get_variant_field(variantType, variantIndex)}::set("); write(*ss, "{mkvName(expr)}"); if (length(expr.variants) != 1) write(*ss, "({index},__context__)"); write(*ss, ") = "); - if (expr._type.argTypes[variantIndex].isPointer && decl.value._type.isPointer && expr._type.argTypes[variantIndex].firstType != null && decl.value._type.firstType != null && !is_same_type(expr._type.argTypes[variantIndex].firstType, decl.value._type.firstType, false, false, false, false)) { + if (variantType.argTypes[variantIndex].isPointer && decl.value._type.isPointer && variantType.argTypes[variantIndex].firstType != null && decl.value._type.firstType != null && !is_same_type(variantType.argTypes[variantIndex].firstType, decl.value._type.firstType, false, false, false, false)) { write(*ss, "das_auto_cast<{type_str}>::cast("); } } def override visitExprMakeVariantField(expr : ExprMakeVariant?; index : int; decl : MakeFieldDeclPtr; last : bool) { - let variantIndex = find_argument_index(expr._type, string(decl.name)) - if (expr._type.argTypes[variantIndex].isPointer && decl.value._type.isPointer && expr._type.argTypes[variantIndex].firstType != null && decl.value._type.firstType != null && !is_same_type(expr._type.argTypes[variantIndex].firstType, decl.value._type.firstType, false, false, false, false)) { + let variantType = fa_element(expr._type) + let variantIndex = find_argument_index(variantType, string(decl.name)) + if (variantType.argTypes[variantIndex].isPointer && decl.value._type.isPointer && variantType.argTypes[variantIndex].firstType != null && decl.value._type.firstType != null && !is_same_type(variantType.argTypes[variantIndex].firstType, decl.value._type.firstType, false, false, false, false)) { write(*ss, ")"); } write(*ss, ";\n"); @@ -3080,15 +3101,16 @@ class public CppAot : AstVisitor { } } def override preVisitExprMakeStructField(expr : ExprMakeStruct?; index : int; decl : MakeFieldDeclPtr; last : bool) { + let mkType = fa_element(expr.makeType) // dim'd make-struct carries the record on the chain element write(*ss, "{tabs()}"); write(*ss, "{decl.flags.moveSemantics ? "das_move((" : "das_copy(("}"); - if (expr.makeType.baseType == Type.tHandle) { - aot_previsit_get_field(expr.makeType.annotation, ss, string(decl.name)); + if (mkType.baseType == Type.tHandle) { + aot_previsit_get_field(mkType.annotation, ss, string(decl.name)); } write(*ss, "{mksName(expr)}"); if (length(expr.structs) != 1) write(*ss, "({index},__context__)"); - if (expr.makeType.baseType == Type.tHandle) { - aot_visit_get_field(expr.makeType.annotation, ss, string(decl.name)); + if (mkType.baseType == Type.tHandle) { + aot_visit_get_field(mkType.annotation, ss, string(decl.name)); write(*ss, " /*{decl.name}*/"); } else { write(*ss, ".{aotFieldName(string(decl.name))}"); diff --git a/daslib/ast_boost.das b/daslib/ast_boost.das index 125f3a062d..7894598e24 100644 --- a/daslib/ast_boost.das +++ b/daslib/ast_boost.das @@ -699,7 +699,6 @@ def private walk_and_convert_dim(data : uint8 const?; info : TypeDeclPtr; at : L while (einfo.baseType == Type.tFixedArray && einfo.firstType != null) { einfo = clone_type(einfo.firstType) } - clear(einfo.dim) // legacy dim-vector view; nothing produces it post-rework var mkArr = new ExprMakeArray(at = at, makeType = clone_type(info)) for (x in range(total)) { unsafe { diff --git a/daslib/decs.das b/daslib/decs.das index eaaf6cfc8d..c490962913 100644 --- a/daslib/decs.das +++ b/daslib/decs.das @@ -792,7 +792,7 @@ def public get(arch : Archetype; name : string; value : auto(TT)) { } [expect_dim(value), unsafe_outside_of_for] -def public get_ro(arch : Archetype; name : string; value : auto(TT)[]) : array const { +def public get_ro(arch : Archetype; name : string; value : auto(TT)[]) : array const { //! Returns const temporary array of component given specific name and type of component for array components. unsafe { return <- get(arch, name, value) diff --git a/daslib/match.das b/daslib/match.das index 4ab03af396..4ab0fbd26b 100644 --- a/daslib/match.das +++ b/daslib/match.das @@ -262,8 +262,7 @@ def match_array(what : TypeDeclPtr; wths : ExprMakeArray?; dynamic : bool; acces return false } } - recT = clone_type(what) - recT.dim |> pop() + recT = clone_type(what.firstType) // element of the fixed-array chain (the .dim compat view is read-only) if (!wths.makeType.isAuto && !is_same_type(recT, wths.makeType, RefMatters.no, ConstMatters.no, TemporaryMatters.no)) { to.errors |> match_error("{describe(what)} type does not match [[{describe(wths.recordType)}[{wthsLength}] ... ]] array", wths.at) return false diff --git a/include/daScript/ast/ast_typedecl.h b/include/daScript/ast/ast_typedecl.h index 4853728494..3a2d31560b 100644 --- a/include/daScript/ast/ast_typedecl.h +++ b/include/daScript/ast/ast_typedecl.h @@ -276,6 +276,12 @@ namespace das { __forceinline const vector & dimExprCompat() const { return typeMacroExpr.empty() ? dimExpr : typeMacroExpr; } + // das-binding compat view for `.dim` — the flattened (outermost-first) sizes of the + // tFixedArray chain, recomputed on every read into per-node transient storage. + // Read-only by design: das writers use ast_boost`make_fixed_array_type. Dies with + // dim/dimExpr at the end of Stage 1. + const vector & dimCompat() const; + mutable vector dimCompatCache; // dimCompat scratch — transient, never serialized union { struct { bool ref : 1 ; diff --git a/modules/dasGlsl/glsl/glsl_internal.das b/modules/dasGlsl/glsl/glsl_internal.das index 9856ce4fde..947f1e8709 100644 --- a/modules/dasGlsl/glsl/glsl_internal.das +++ b/modules/dasGlsl/glsl/glsl_internal.das @@ -418,9 +418,8 @@ class GlslExport : AstVisitor { def produce_zero(t : TypeDeclPtr) { if (!empty(t.dim)) { *writer |> write("{self->describe_glsl_type(t)}(") - var ct <- clone_type(t) - ct.dim |> pop() - for (d in range(t.dim[length(t.dim) - 1])) { + var ct <- clone_type(t.firstType) // element of the fixed-array chain (the .dim compat view is read-only) + for (d in range(t.dim[0])) { if (d != 0) { *writer |> write(",") } diff --git a/modules/dasLLVM/daslib/llvm_jit.das b/modules/dasLLVM/daslib/llvm_jit.das index 7d0727b08e..48e1ef9596 100644 --- a/modules/dasLLVM/daslib/llvm_jit.das +++ b/modules/dasLLVM/daslib/llvm_jit.das @@ -1299,9 +1299,11 @@ class public LlvmJitVisitor : AstVisitor { var elem_type = dim_element_type_to_llvm_type(expr._type) var elem = LLVMBuildGEP2(g_builder, elem_type, arr, index, "") var new_ptr : LLVMOpaqueValue?; - if (expr.typeexpr.baseType == Type.tHandle) { - var elemT = clone_type(expr.typeexpr) - elemT.dim |> clear() // hack to get correct ptr to ctor + var elemT = clone_type(expr.typeexpr) // element of the fixed-array chain - the ctor lives on it + while (elemT.baseType == Type.tFixedArray && elemT.firstType != null) { + elemT = clone_type(elemT.firstType) + } + if (elemT.baseType == Type.tHandle) { new_ptr = alloc_handle_type(elemT, uid.get_new(expr.typeexpr)) check_ptr_zero(new_ptr, expr.at, "new returned null") new_ptr = LLVMBuildPointerCast(g_builder, new_ptr, LLVMPointerType(type_to_llvm_type(elemT), 0u), "") @@ -2593,13 +2595,8 @@ class public LlvmJitVisitor : AstVisitor { setE(expr, ptr_idx) } } elif (!empty(subExprT.dim)) { - var etype = clone_type(subExprT) - assume vec = etype.dim - let maxIndex = vec[0] - for (i in range(1, length(vec))) { - vec[i - 1] = vec[i] - } - vec |> resize(vec |> length - 1) + let maxIndex = subExprT.dim[0] + var etype = clone_type(subExprT.firstType) // element of the fixed-array chain (the .dim compat view is read-only) check_range(tidx, types.ConstI32(uint64(maxIndex)), expr.at, "dim index out of range", is_signed_index_type(expr.index._type)) var ptr_idx = build_array_index(tsrc, tidx, etype, "dim_at") if (expr.atFlags.r2v) { @@ -2666,13 +2663,9 @@ class public LlvmJitVisitor : AstVisitor { assume subExprT = expr.subexpr._type var res : LLVMOpaqueValue?; if (!empty(subExprT.dim) || (subExprT.isPointer && !empty(subExprT.firstType.dim))) { - var etype = subExprT.isPointer ? clone_type(subExprT.firstType) : clone_type(subExprT) - assume vec = etype.dim - let maxIndex = vec[0] - for (i in range(1, length(vec))) { - vec[i - 1] = vec[i] - } - vec |> resize(vec |> length - 1) + // element of the fixed-array chain, behind one pointer level when present + let maxIndex = subExprT.isPointer ? subExprT.firstType.dim[0] : subExprT.dim[0] + var etype = subExprT.isPointer ? clone_type(subExprT.firstType.firstType) : clone_type(subExprT.firstType) var resType = LLVMPointerType(type_to_llvm_type(etype), 0u) var if_not_null = empty(subExprT.dim) ? LLVMBuildIsNotNull(g_builder, tsrc, "") : LLVMConstInt(types.t_int1, 1ul, 0) res = build_select(if_not_null, resType) { @@ -3701,8 +3694,7 @@ class public LlvmJitVisitor : AstVisitor { range2[ssrc] = sto LLVMBuildStore(g_builder, sfrom, getV(svar)) } elif (!empty(ssrc._type.dim)) {// []->first() - var etype = clone_type(ssrc._type) - etype.dim |> erase(0) + var etype = clone_type(ssrc._type.firstType) // element of the fixed-array chain (the .dim compat view is read-only) var element_type = type_to_llvm_type(svar._type) var sdim = getE(ssrc) var s0 = LLVMBuildGEP2(g_builder, element_type, sdim, types.ConstI32(0ul), "dim_0") @@ -3826,8 +3818,7 @@ class public LlvmJitVisitor : AstVisitor { LLVMBuildCondBr(g_builder, rcond, lblk.loop_end, nextOk) LLVMPositionBuilderAtEnd(g_builder, nextOk) } elif (!empty(ssrc._type.dim)) {// []->next() - var etype = clone_type(ssrc._type) - etype.dim |> erase(0) + var etype = clone_type(ssrc._type.firstType) // element of the fixed-array chain (the .dim compat view is read-only) var svar_v = LLVMBuildLoad2(g_builder, LLVMPointerType(type_to_llvm_type(svar._type), 0u), getV(svar), "") var svar_i = build_array_index(svar_v, types.ConstI32(1ul), etype, "next_element") LLVMBuildStore(g_builder, svar_i, getV(svar)) diff --git a/modules/dasLLVM/daslib/llvm_jit_common.das b/modules/dasLLVM/daslib/llvm_jit_common.das index b56de10823..090cf3f404 100644 --- a/modules/dasLLVM/daslib/llvm_jit_common.das +++ b/modules/dasLLVM/daslib/llvm_jit_common.das @@ -1121,7 +1121,9 @@ def public type_to_llvm_type(t : TypeDeclPtr) { var res : LLVMOpaqueType? if (!empty(t.dim)) { var ndt = clone_type(t) - ndt.dim |> clear() + while (ndt.baseType == Type.tFixedArray && ndt.firstType != null) { // element of the chain (the .dim compat view is read-only) + ndt = clone_type(ndt.firstType) + } res = type_to_llvm_type(ndt) let ndim = length(t.dim) for (i in range(ndim)) { @@ -1212,7 +1214,9 @@ def public dim_element_type_to_llvm_type(typ : TypeDeclPtr) { return type_to_llvm_type(typ) } else { var ndt = clone_type(typ) - ndt.dim |> clear() + while (ndt.baseType == Type.tFixedArray && ndt.firstType != null) { // scalar element of the chain (the .dim compat view is read-only) + ndt = clone_type(ndt.firstType) + } return type_to_llvm_type(ndt) } } diff --git a/modules/dasLLVM/daslib/llvm_jit_run.das b/modules/dasLLVM/daslib/llvm_jit_run.das index 162ec1649d..9c928c026a 100644 --- a/modules/dasLLVM/daslib/llvm_jit_run.das +++ b/modules/dasLLVM/daslib/llvm_jit_run.das @@ -33,7 +33,7 @@ var LINK_WHOLE_LIB = false // when true, standalone exe links against the whole // invalidates cached DLLs (e.g. edits to llvm_jit.das, llvm_macro.das, llvm_jit_common.das, // runtime helper ABI, default target triple). Cache filenames fold this in, so a bump // makes every previously written DLL miss the cache on the next run and get GC'd. -let LLVM_JIT_CODEGEN_VERSION : uint64 = 0x22ul +let LLVM_JIT_CODEGEN_VERSION : uint64 = 0x23ul let JIT_FNV_PRIME : uint64 = 1099511628211ul diff --git a/src/ast/ast_infer_type_helper.cpp b/src/ast/ast_infer_type_helper.cpp index c77fd1a10d..895caf7ce4 100644 --- a/src/ast/ast_infer_type_helper.cpp +++ b/src/ast/ast_infer_type_helper.cpp @@ -157,10 +157,16 @@ namespace das { error("array dimension can't be 0 or less: '" + describeType(decl) + "'", "", "", decl->at, CompilationError::invalid_array_dimension); } else { - bool failed = false; - if (decl->getSizeOf64(failed) > 0x7fffffff && !failed) { - error("array is too big: '" + describeType(decl) + "'", "", "", - decl->at, CompilationError::exceeds_array); + // master parity: the infer-time limit is the flattened ELEMENT COUNT, not bytes; + // byte-size limits are enforced later by lint with site-specific errors + uint64_t count = 1; + for (auto t = decl; t->baseType == Type::tFixedArray && t->firstType; t = t->firstType) { + if (t->fixedDim > 0) count *= uint64_t(t->fixedDim); + if (count > 0x7fffffff) { + error("array is too big: '" + describeType(decl) + "'", "", "", + decl->at, CompilationError::exceeds_array); + break; + } } } if (auto elemType = decl->firstType) { @@ -375,6 +381,23 @@ namespace das { return false; } + // master semantics: a generic alias use-site's own dims REPLACE the bound type's dims + // (bare use strips them) - resolve the binding to the bound chain's ELEMENT, carrying the + // head's qualifiers; use-site FA wraps then re-dim it naturally + static TypeDecl * peelFixedArrayAliasBinding ( TypeDecl * aT ) { + auto elemT = aT; + while ( elemT->baseType==Type::tFixedArray && elemT->firstType ) { + elemT = elemT->firstType; + } + auto resT = new TypeDecl(*elemT); + if ( elemT != aT ) { + resT->ref = resT->ref || aT->ref; + resT->constant = resT->constant || aT->constant; + resT->temporary = resT->temporary || aT->temporary; + } + return resT; + } + TypeDeclPtr InferTypes::inferAlias(const TypeDeclPtr &decl, const FunctionPtr &fptr, AliasMap *aliases, OptionsMap *options, bool autoToAlias) const { autoToAlias |= decl->autoToAlias; if (decl->baseType == Type::typeDecl || decl->baseType == Type::typeMacro) { @@ -403,18 +426,14 @@ namespace das { } } if (aT) { - auto resT = new TypeDecl(*aT); + auto resT = peelFixedArrayAliasBinding(aT); resT->at = decl->at; resT->ref = (resT->ref || decl->ref) && !decl->removeRef; resT->constant = (resT->constant || decl->constant) && !decl->removeConstant; resT->temporary = (resT->temporary || decl->temporary) && !decl->removeTemporary; resT->dim = decl->dim; resT->aotAlias = false; - if (resT->baseType == Type::tFixedArray) { - resT->alias = decl->alias; // typedef name survives as a display label on the chain head (M4 design) - } else { - resT->alias.clear(); - } + resT->alias.clear(); return resT; } else { return nullptr; @@ -440,8 +459,18 @@ namespace das { return nullptr; } if (decl->baseType == Type::tFixedArray) { + if (resT->firstType) { + // canonical form: qualifiers live on the chain head only - hoist whatever + // the resolved element brought in (e.g. the constness of an alias binding) + resT->ref = resT->ref || resT->firstType->ref; + resT->constant = resT->constant || resT->firstType->constant; + resT->temporary = resT->temporary || resT->firstType->temporary; + resT->firstType->ref = false; + resT->firstType->constant = false; + resT->firstType->temporary = false; + } // the chain head carries the hoisted qualifiers+contracts the dim'd alias leaf used to; - // apply and clear them here the way the alias-leaf case does + // apply and clear them here the way the dim'd alias leaf case does resT->ref = resT->ref && !decl->removeRef; resT->constant = resT->constant && !decl->removeConstant; resT->temporary = resT->temporary && !decl->removeTemporary; @@ -529,7 +558,7 @@ namespace das { aT = fptr ? findFuncAlias(fptr, decl->alias) : findAlias(decl->alias); } if (aT) { - auto resT = new TypeDecl(*aT); + auto resT = peelFixedArrayAliasBinding(aT); resT->at = decl->at; resT->ref = (resT->ref || decl->ref) && !decl->removeRef; resT->constant = (resT->constant || decl->constant) && !decl->removeConstant; @@ -538,7 +567,7 @@ namespace das { resT->explicitConst = (resT->explicitConst || decl->explicitConst); resT->dim = decl->dim; resT->aotAlias = false; - // resT->alias.clear(); // this may speed things up, but it breaks typemacro-based aliases + resT->alias = aT->alias; // keep the binding's label (a peel drops the chain head that carried it); clearing breaks typemacro-based aliases return resT; } else { return decl; diff --git a/src/ast/ast_typedecl.cpp b/src/ast/ast_typedecl.cpp index 42df0996c3..3e344edf61 100644 --- a/src/ast/ast_typedecl.cpp +++ b/src/ast/ast_typedecl.cpp @@ -1097,6 +1097,19 @@ namespace das aliasCacheHasAlias = hasAny; return hasAny; } + const vector & TypeDecl::dimCompat() const { + if ( baseType==Type::tFixedArray ) { + vector flat; + for ( auto t = this; t->baseType==Type::tFixedArray && t->firstType; t = t->firstType ) { + flat.push_back(t->fixedDim); + } + // refill only when stale, so a nested read of the same node can't invalidate + // an iterator held over a previous read + if ( dimCompatCache != flat ) dimCompatCache = das::move(flat); + return dimCompatCache; + } + return dim; + } TypeDecl * TypeDecl::findAlias ( const string & name, bool allowAuto ) { if (!aliasCacheValid) computeAliasCache(); if (!aliasCacheHasAlias) return nullptr; // proven no aliases anywhere diff --git a/src/builtin/module_builtin_ast_annotations_1.cpp b/src/builtin/module_builtin_ast_annotations_1.cpp index f5b7598863..d3e4c7f85d 100644 --- a/src/builtin/module_builtin_ast_annotations_1.cpp +++ b/src/builtin/module_builtin_ast_annotations_1.cpp @@ -35,7 +35,12 @@ namespace das { addField("secondType"); addField("argTypes"); addField("argNames"); - addField("dim"); + // compat view (FIXED_ARRAY_REWORK.md, 1f): flattened (outermost-first) sizes of + // the tFixedArray chain under the legacy .dim name; READ-ONLY - das writers use + // ast_boost`make_fixed_array_type + addProperty< + const vector & (TypeDecl::*)() const, &TypeDecl::dimCompat + >("dim","dimCompat"); addField("fixedDim"); addField("fixedDimExpr"); // compat view (FIXED_ARRAY_REWORK.md, 1b): the typeMacro/typeDecl/tag payload diff --git a/tests/typemacro/_typemacro_mod.das b/tests/typemacro/_typemacro_mod.das index 1d3e52d32a..e7137b9395 100644 --- a/tests/typemacro/_typemacro_mod.das +++ b/tests/typemacro/_typemacro_mod.das @@ -6,9 +6,9 @@ // dimExpr[2] — ExprConstInt (size) // dimExpr[3] — ExprConstBool (wrap into an array or not) // dimExpr[4] — ExprConstString (mode tag, "arr" expected when wrapping) -// NOTE: this module reads .dimExpr and writes .dim directly — it is itself a -// consumer the rework migrates (kept on the old API until the compat property -// lands in Stage 1, ported to typeMacroExpr in Stage 5). +// NOTE: this module reads .dimExpr through the compat property (ported to +// typeMacroExpr in Stage 5); array results are built with make_fixed_array_type +// since Stage 1f (the .dim compat view is read-only). options gen2 options no_aot @@ -52,13 +52,13 @@ class TmMakeTypeMacro : AstTypeMacro { auto_type = new TypeDecl(baseType = Type.autoinfer) } if (wrap) { - auto_type.dim |> push(count) + auto_type = make_fixed_array_type(count, auto_type) } return <- auto_type } var final_type = clone_type(td.dimExpr[1]._type) if (wrap) { - final_type.dim |> push(count) + final_type = make_fixed_array_type(count, final_type) } return <- final_type } diff --git a/tutorials/macros/type_macro_mod.das b/tutorials/macros/type_macro_mod.das index 3c8c63689f..1ed66284a7 100644 --- a/tutorials/macros/type_macro_mod.das +++ b/tutorials/macros/type_macro_mod.das @@ -56,7 +56,7 @@ class PaddedTypeMacro : AstTypeMacro { // Fallback: pure auto-infer. auto_type = new TypeDecl(baseType = Type.autoinfer) } - auto_type.dim |> push(count) + auto_type = make_fixed_array_type(count, auto_type) return <- auto_type } @@ -64,7 +64,7 @@ class PaddedTypeMacro : AstTypeMacro { // The type argument is fully inferred. Clone it and add the // dimension to produce the final array type (e.g. float[4]). var final_type = clone_type(td.dimExpr[1]._type) - final_type.dim |> push(count) + final_type = make_fixed_array_type(count, final_type) return <- final_type } } From 0251ea861dca5e4f4a6e7ff50f10925c85358db2 Mon Sep 17 00:00:00 2001 From: Boris Batkin Date: Wed, 10 Jun 2026 12:48:38 -0700 Subject: [PATCH 09/23] lint ride-along: STYLE030 generic-body false positive + glsl self-> sweep Pre-push lint on the 1f-touched files surfaced two pre-existing issues: - STYLE030 false-flagged `require daslib/functional` in decs.das as unused: repeat_ref is called only inside an UNINSTANCED generic body, where calls stay unresolved (func==null) and leave no used_modules trace - dropping the require breaks instantiation in every decs dim test. STYLE030 now collects unresolved generic-body call names (qualification stripped) and treats a require as used when it exports a function or generic matching one of them. Genuinely-unused requires still flag (probed). - modules/dasGlsl/glsl_internal.das: 148 pre-existing STYLE028 hits (self-> receivers on class method calls); mechanical sweep, since touching the file for the .dim writer port put it under the hook's changed-file lint. Gates: lint 0 issues on all touched files; full tree 10784/10784, zero GC leaks. Co-Authored-By: Claude Fable 5 --- daslib/style_lint.das | 46 +++- modules/dasGlsl/glsl/glsl_internal.das | 292 ++++++++++++------------- 2 files changed, 190 insertions(+), 148 deletions(-) diff --git a/daslib/style_lint.das b/daslib/style_lint.das index f76f13f451..74832e1937 100644 --- a/daslib/style_lint.das +++ b/daslib/style_lint.das @@ -35,7 +35,7 @@ module style_lint shared private //! STYLE027 — var array / table with empty default-init followed by a for-loop that only push/insert into it; use a comprehension //! STYLE028 — 'self->method(...)' inside a class method is optional — drop 'self->' and call 'method(...)' directly (compiler auto-promotes to the same invoke) //! STYLE029 — non-public 'require X' used only for modules X re-exports (transitive-only) — require those directly and drop X (skipped when X provides macros or [init]) -//! STYLE030 — non-public 'require X' that is entirely unused — no symbol from X (or anything it re-exports) is referenced; drop it (skipped when X provides any macro or an [init], or only re-exports builtins used through it). Suppress a deliberate keep with '// nolint:STYLE030' +//! STYLE030 — non-public 'require X' that is entirely unused — no symbol from X (or anything it re-exports) is referenced; drop it (skipped when X provides any macro or an [init], or only re-exports builtins used through it, or exports a function matching an unresolved call inside an uninstanced generic body). Suppress a deliberate keep with '// nolint:STYLE030' require daslib/ast_boost require daslib/is_local @@ -82,6 +82,9 @@ class StyleLintVisitor : AstVisitor { analyze_requires : bool = false @do_not_delete this_module : Module? used_modules : table + // Calls inside UNINSTANCED generic bodies stay unresolved at lint time (func==null) + // and leave no used_modules trace; their names gate STYLE030 instead. + unresolved_call_names : table def StyleLintVisitor() { pass @@ -254,6 +257,7 @@ class StyleLintVisitor : AstVisitor { def override preVisitExprCall(var expr : ExprCall?) : void { st029_record_func(expr.func) + st029_record_unresolved_call(expr) st029_record_module_getter(expr) check_block_pipe(expr.at, expr.arguments) check_style019_clamp(expr) @@ -1496,6 +1500,43 @@ class StyleLintVisitor : AstVisitor { else st029_record_mod(fun._module) } + def st029_record_unresolved_call(expr : ExprCall?) : void { + // Calls inside generic bodies that never instantiate stay unresolved + // (func==null); keep their unqualified names so STYLE030 can match them + // against a required module's exports instead of flagging it unused. + if (expr.func != null || !st029_expr_in_scope()) return + var nm = "{expr.name}" + let sep = nm |> rfind("::") // strip any module qualification, e.g. `_::foo` + if (sep >= 0) { + nm = nm |> slice(sep + 2) + } + if (!empty(nm) && !key_exists(unresolved_call_names, nm)) { + unresolved_call_names |> insert(nm) + } + } + + def st030_exports_unresolved_name(mod : Module?) : bool { + // True when `mod` exports a function or generic whose name matches an + // unresolved generic-body call — the require is (potentially) load-bearing + // for instantiations the lint pass never sees. + if (empty(unresolved_call_names)) return false + var found = false + for_each_function(mod, "") $(fn) { + if (found) return + if (key_exists(unresolved_call_names, "{fn.name}")) { + found = true + } + } + if (found) return true + for_each_generic(mod, "") $(fn) { + if (found) return + if (key_exists(unresolved_call_names, "{fn.name}")) { + found = true + } + } + return found + } + def override preVisitTypeDecl(typ : TypeDeclPtr) : void { // Type references — st029_record_mod self-gates on expr_in_scope (global // scope or this module's code; never a foreign function body). @@ -1622,7 +1663,8 @@ class StyleLintVisitor : AstVisitor { if (mname == "" || mname == this_name || mname == "$" || mname == "builtin" || key_exists(used_modules, mname) || st029_module_provides_macro(mod) - || st029_module_has_init(mod)) return + || st029_module_has_init(mod) + || st030_exports_unresolved_name(mod)) return var closure : table st029_collect_public_deps(mod, closure) var via : array diff --git a/modules/dasGlsl/glsl/glsl_internal.das b/modules/dasGlsl/glsl/glsl_internal.das index 947f1e8709..f97428780d 100644 --- a/modules/dasGlsl/glsl/glsl_internal.das +++ b/modules/dasGlsl/glsl/glsl_internal.das @@ -87,14 +87,14 @@ class GlslShader : AstFunctionAnnotation { return false } } - self->generate_bind_uniform_dummy(func) + generate_bind_uniform_dummy(func) return true } def override patch(var func : FunctionPtr; var group : ModuleGroup; args, progArgs : AnnotationArgumentList; var errors : das_string; var astChanged : bool&) : bool { for_each_function(compiling_module(), bind_uniform_function_name(func)) $(bfun) { if (bfun.flags.exports) { bfun.flags.exports = false - self->generate_bind_uniform(func, bfun) + generate_bind_uniform(func, bfun) astChanged = true } } @@ -169,9 +169,9 @@ class GlslShader : AstFunctionAnnotation { var glob <- find_variable(compiling_module(), argName) if (glob != null && glob.init == null) { if (glob.name == argName) { - let caps = self->buildCaps() + let caps = buildCaps() let gres = generate_glsl(func, err, shaderType, version, compute_local_size, caps) - let shader_text = "{gres.inout_stub}{self->get_glsl_prefix()}{gres.body}" + let shader_text = "{gres.inout_stub}{get_glsl_prefix()}{gres.body}" glob.init = new ExprConstString(at = func.at, value := shader_text, _type = new TypeDecl(at = func.at, baseType = Type.tString) @@ -356,7 +356,7 @@ class GlslExport : AstVisitor { } elif (glsl_types |> key_exists(t.baseType)) { tw |> write(glsl_types?[t.baseType] ?? "error") } else { - self->error("unsupported type {t.baseType}", t.at) + error("unsupported type {t.baseType}", t.at) tw |> write("error") } if (write_dim) { @@ -368,7 +368,7 @@ class GlslExport : AstVisitor { return st } def describe_glsl_type(t : TypeDeclPtr) { - return self->describe_glsl_type_ex(t, true) + return describe_glsl_type_ex(t, true) } def glsl_function_name(fun : FunctionPtr) { var funname = build_string() $(var tw) { @@ -398,8 +398,8 @@ class GlslExport : AstVisitor { } def describe_glsl_function(fun : FunctionPtr) { let st = build_string() $(var tw) { - tw |> write <| self->describe_glsl_type(fun.result) - let funname = self->glsl_function_name(fun) + tw |> write <| describe_glsl_type(fun.result) + let funname = glsl_function_name(fun) tw |> write(" {funname} ( ") for (arg, i in fun.arguments, count()) { if (i != 0) { @@ -408,7 +408,7 @@ class GlslExport : AstVisitor { if (arg._type.isRef && !arg._type.flags.constant) { tw |> write("inout ") } - tw |> write <| self->describe_glsl_type(arg._type) + tw |> write <| describe_glsl_type(arg._type) tw |> write(" {arg.name}") } tw |> write(" )") @@ -417,13 +417,13 @@ class GlslExport : AstVisitor { } def produce_zero(t : TypeDeclPtr) { if (!empty(t.dim)) { - *writer |> write("{self->describe_glsl_type(t)}(") + *writer |> write("{describe_glsl_type(t)}(") var ct <- clone_type(t.firstType) // element of the fixed-array chain (the .dim compat view is read-only) for (d in range(t.dim[0])) { if (d != 0) { *writer |> write(",") } - self->produce_zero(ct) + produce_zero(ct) } ct := null *writer |> write(")") @@ -437,7 +437,7 @@ class GlslExport : AstVisitor { if (i != 0) { *writer |> write(",") } - self->produce_zero(fld._type) + produce_zero(fld._type) } *writer |> write(")") } elif (t.baseType == Type.tHandle) { @@ -454,7 +454,7 @@ class GlslExport : AstVisitor { *writer |> write("mat4(vec4(0),vec4(0),vec4(0),vec4(0))") } } else { - self->error("unsupported zero for handled type {describe(t)}", t.at) + error("unsupported zero for handled type {describe(t)}", t.at) } } elif (t.baseType == Type.tInt) { *writer |> write("0") @@ -485,7 +485,7 @@ class GlslExport : AstVisitor { } elif (t.baseType == Type.tEnumeration) { *writer |> write("0") } else { - self->error("unsupported zero for type {describe(t)}", t.at) + error("unsupported zero for type {describe(t)}", t.at) } } // any subexpression to string in current context @@ -517,18 +517,18 @@ class GlslExport : AstVisitor { var any = false for_each_function(mod, "") $(fun) { if (depFun |> key_exists(intptr(fun))) { - if (!self->isMainFunction(fun)) { + if (!isMainFunction(fun)) { if (fun._module.name != "glsl_common") { - let ss = self->describe_glsl_function(fun) + let ss = describe_glsl_function(fun) *writer |> write("{ss};") - self->newLine(1) + newLine(1) any = true } } } } if (any) { - self->newLine(1) + newLine(1) } } def override preVisitProgram(prog : ProgramPtr) : void { @@ -695,22 +695,22 @@ class GlslExport : AstVisitor { } def override preVisitStructure(str : StructurePtr) { if (str.flags.isClass) { - self->error("classes are not supported", str.at) + error("classes are not supported", str.at) } - self->line(str.at) + line(str.at) *writer |> write("struct {str.name}") - self->newLine(1) + newLine(1) *writer |> write("\{") - self->newLine(1) + newLine(1) } def override preVisitStructureField(str : StructurePtr; decl : FieldDeclaration; last : bool) { - self->line(decl.at) + line(decl.at) *writer |> write("\t") //if decl.annotation |> length != 0 // *writer |> write("[{describe(decl.annotation)}] ") - *writer |> write("{self->describe_glsl_type_ex(decl._type,false)} {decl.name}") + *writer |> write("{describe_glsl_type_ex(decl._type,false)} {decl.name}") if (decl.init != null) { - self->error("structure initialization is not supported", decl.init.at) + error("structure initialization is not supported", decl.init.at) } } def override visitStructureField(str : StructurePtr; decl : FieldDeclaration; last : bool) { @@ -718,11 +718,11 @@ class GlslExport : AstVisitor { *writer |> write("[{d}]") } *writer |> write(";") - self->newLine(1) + newLine(1) } def override visitStructure(str : StructurePtr) : StructurePtr { *writer |> write("};") - self->newLine(2) + newLine(2) if (caps.output_structure_initializers) { *writer |> write("{str.name} {str.name}_STRUCTURE_INITIALIZER ( ") var first = true @@ -732,7 +732,7 @@ class GlslExport : AstVisitor { } else { *writer |> write(", ") } - *writer |> write(self->describe_glsl_type(fl._type)) + *writer |> write(describe_glsl_type(fl._type)) *writer |> write(" _{fl.name}") } *writer |> write(" )\n") @@ -754,10 +754,10 @@ class GlslExport : AstVisitor { def override preVisitFunction(fun : FunctionPtr) { if (caps.restrict_array_as_result) { if (!empty(fun.result.dim)) { - self->error("returning arrays is prohibited in this shader model", fun.at) + error("returning arrays is prohibited in this shader model", fun.at) } } - let isMain = self->isMainFunction(fun) + let isMain = isMainFunction(fun) if (isMain && shaderType == ShaderType.compute) { if (caps.output_hlsl_style_compute_layout) { if (compute_local_size.z != 0) { @@ -769,9 +769,9 @@ class GlslExport : AstVisitor { } } } - self->line(fun.at) - *writer |> write("{self->describe_glsl_type(fun.result)} ") - let fnName = isMain ? "main" : self->glsl_function_name(fun) + line(fun.at) + *writer |> write("{describe_glsl_type(fun.result)} ") + let fnName = isMain ? "main" : glsl_function_name(fun) *writer |> write("{fnName}(") } def override preVisitFunctionBody(fun : FunctionPtr; expr : ExpressionPtr) { @@ -784,17 +784,17 @@ class GlslExport : AstVisitor { } } } - self->newLine(1) + newLine(1) } def override visitFunction(fun : FunctionPtr) : FunctionPtr { - self->newLine(1) + newLine(1) return fun } def override preVisitFunctionArgument(fun : FunctionPtr; arg : VariablePtr; last : bool) { if (arg._type.isRef && !arg._type.flags.constant) { *writer |> write("inout ") } - *writer |> write("{self->describe_glsl_type(arg._type)} {arg.name} ") + *writer |> write("{describe_glsl_type(arg._type)} {arg.name} ") } def override visitFunctionArgument(fun : FunctionPtr; arg : VariablePtr; last : bool) { if (!last) { @@ -808,37 +808,37 @@ class GlslExport : AstVisitor { // block def override preVisitExprBlock(blk : ExprBlock?) { if (blk.blockFlags.isClosure) { - self->error("closure is not supported", blk.at) + error("closure is not supported", blk.at) } *writer |> write("{repeat("\t",tab)}\{") - self->newLine(1) + newLine(1) tab ++ } def override visitExprBlock(blk : ExprBlock?) : ExpressionPtr { tab -- *writer |> write("{repeat("\t",tab)}\}") - self->newLine(1) + newLine(1) return blk } def override preVisitExprBlockExpression(blk : ExprBlock?; expr : ExpressionPtr) { - self->line(expr.at) + line(expr.at) *writer |> write("{repeat("\t",tab)}") } def override visitExprBlockExpression(blk : ExprBlock?; expr : ExpressionPtr) { *writer |> write(";") - self->newLine(1) + newLine(1) return expr } def override visitExprBlockFinal(blk : ExprBlock?) { - self->error("finally is not supported", blk.at) + error("finally is not supported", blk.at) } // let def override preVisitExprLetVariable(expr : ExprLet?; arg : VariablePtr; lastArg : bool) { if (arg._type.flags.ref) { - self->error("local references are not supported by GLSL", arg.at) + error("local references are not supported by GLSL", arg.at) } - self->line(arg.at) - *writer |> write("{self->describe_glsl_type_ex(arg._type,false)} {arg.name}") + line(arg.at) + *writer |> write("{describe_glsl_type_ex(arg._type,false)} {arg.name}") for (d in arg._type.dim) { *writer |> write("[{d}]") } @@ -855,7 +855,7 @@ class GlslExport : AstVisitor { } if (needZero) { *writer |> write(" = ") - self->produce_zero(arg._type) + produce_zero(arg._type) } } if (!lastArg) { @@ -882,7 +882,7 @@ class GlslExport : AstVisitor { if ("{typ.structType.name}" |> find("sampler") != -1 || "{typ.structType.name}" |> find("image") != -1) return } - self->error("unsupported uniform type {describe(typ)}", typ.at) + error("unsupported uniform type {describe(typ)}", typ.at) } def getSamplerType(typ : TypeDeclPtr) : string { if (typ.baseType == Type.tStructure) { @@ -898,7 +898,7 @@ class GlslExport : AstVisitor { } def outputSamplerMacro(arg : VariablePtr) : bool { if (!caps.output_unfirom_macro_declarations) return false - let st = self->getSamplerType(arg._type) + let st = getSamplerType(arg._type) if (st == "") return false let stage = arg.annotation |> find_arg("stage") ?as tInt ?? 0 let sfunc = to_upper(st) @@ -908,7 +908,7 @@ class GlslExport : AstVisitor { } def outputImageMacro(arg : VariablePtr) : bool { if (!caps.output_unfirom_macro_declarations) return false - let st = self->getImageType(arg._type) + let st = getImageType(arg._type) if (st == "") return false let format = arg.annotation |> find_arg("format") ?as tString ?? "rgba8" let binding = arg.annotation |> find_arg("stage") ?as tInt ?? 0 @@ -924,13 +924,13 @@ class GlslExport : AstVisitor { return true } def override preVisitGlobalLetVariable(arg : VariablePtr; lastArg : bool) { - self->line(arg.at) + line(arg.at) if (caps.restrict_array_as_result) { if (!empty(arg._type.dim)) { let need_init = arg.annotation |> find_arg("need_init") ?as tBool ?? true let group_shared = arg.annotation |> find_arg("groupshared") ?as tBool ?? false if (need_init && !group_shared) { - self->error("returning arrays is prohibited in this shader model", arg.at) + error("returning arrays is prohibited in this shader model", arg.at) } } } @@ -944,11 +944,11 @@ class GlslExport : AstVisitor { if (arg.annotation |> find_arg("ssbo") is tBool) { let binding = arg.annotation |> find_arg("stage") ?as tInt ?? 0 *writer |> write("layout(std430, binding={binding}) buffer {arg.name}_ssbo") - self->newLine(1) + newLine(1) *writer |> write("\{") - self->newLine(1) - *writer |> write(" {self->describe_glsl_type(arg._type)} {arg.name};") - self->newLine(1) + newLine(1) + *writer |> write(" {describe_glsl_type(arg._type)} {arg.name};") + newLine(1) *writer |> write("\}") } else { var needInit = arg.annotation |> find_arg("need_init") ?as tBool ?? true @@ -957,8 +957,8 @@ class GlslExport : AstVisitor { } if (shaderType == ShaderType.vertex) { if (arg.annotation |> find_arg("uniform") is tBool) { - self->checkUniformType(arg._type) - if (self->outputSamplerMacro(arg)) return + checkUniformType(arg._type) + if (outputSamplerMacro(arg)) return *writer |> write("uniform "); needInit = false } let argLoc = arg.annotation |> find_arg("location") @@ -973,8 +973,8 @@ class GlslExport : AstVisitor { } } elif (shaderType == ShaderType.fragment) { if (arg.annotation |> find_arg("uniform") is tBool) { - self->checkUniformType(arg._type) - if (self->outputSamplerMacro(arg)) return + checkUniformType(arg._type) + if (outputSamplerMacro(arg)) return *writer |> write("uniform "); needInit = false } if (arg.annotation |> find_arg("in") is tBool) { @@ -995,8 +995,8 @@ class GlslExport : AstVisitor { } } if (arg.annotation |> find_arg("uniform") is tBool) { - self->checkUniformType(arg._type) - if (self->outputSamplerMacro(arg) || self->outputImageMacro(arg)) return + checkUniformType(arg._type) + if (outputSamplerMacro(arg) || outputImageMacro(arg)) return *writer |> write("uniform "); needInit = false } if (arg.annotation |> find_arg("in") is tBool) { @@ -1032,13 +1032,13 @@ class GlslExport : AstVisitor { } } } - *writer |> write("{self->describe_glsl_type_ex(arg._type,false)} {arg.name}") + *writer |> write("{describe_glsl_type_ex(arg._type,false)} {arg.name}") for (d in arg._type.dim) { *writer |> write("[{d}]") } if (arg.init == null && needInit) { *writer |> write(" = ") - self->produce_zero(arg._type) + produce_zero(arg._type) } } } @@ -1048,7 +1048,7 @@ class GlslExport : AstVisitor { *writer |> write(": register({reg as tString})") } *writer |> write(";") - self->newLine(2) + newLine(2) var is_inout = false if (caps.output_inout_stub) { is_inout = true @@ -1064,7 +1064,7 @@ class GlslExport : AstVisitor { if (!glsl_restricted_vertex_attribute_names |> key_exists("{arg.name}")) { var names <- [for (n in keys(glsl_restricted_vertex_attribute_names)); n] let allowed_names = join(names, ",") - self->error("invalid vertex attribute name {arg.name}, supported names are {allowed_names}", arg.at) + error("invalid vertex attribute name {arg.name}, supported names are {allowed_names}", arg.at) delete names } } @@ -1096,10 +1096,10 @@ class GlslExport : AstVisitor { } } if (suffix == "") { - self->error("missing semantics for {arg.name}", arg.at) + error("missing semantics for {arg.name}", arg.at) } } - inout_decl += "{self->describe_glsl_type(arg._type)} {arg.name}{suffix};\n" + inout_decl += "{describe_glsl_type(arg._type)} {arg.name}{suffix};\n" } return arg } @@ -1110,22 +1110,22 @@ class GlslExport : AstVisitor { } // string builder def override preVisitExprStringBuilder(expr : ExprStringBuilder?) { - self->error("string builder is not supported", expr.at) + error("string builder is not supported", expr.at) } // new def override preVisitExprNew(expr : ExprNew?) { - self->error("new is not supported", expr.at) + error("new is not supported", expr.at) } // named call def override preVisitExprNamedCall(expr : ExprNamedCall?) { - self->error("named call is not supported", expr.at) + error("named call is not supported", expr.at) } // looks like call (debug,assert,verify,erase,find,key_exists,keys,values,invoke,memzero etc) def isInvokeMethod(expr : ExprLooksLikeCall?) { return (expr ?as ExprInvoke)?.isInvokeMethod ?? false } def override preVisitExprLooksLikeCall(expr : ExprLooksLikeCall?) { - if (self->isInvokeMethod(expr)) { + if (isInvokeMethod(expr)) { pass } else { *writer |> write("{expr.name}(") @@ -1136,13 +1136,13 @@ class GlslExport : AstVisitor { return expr } def override preVisitExprLooksLikeCallArgument(expr : ExprLooksLikeCall?; arg : ExpressionPtr; last : bool) { - let isInvoke = self->isInvokeMethod(expr) + let isInvoke = isInvokeMethod(expr) if (isInvoke && arg == expr.arguments[1]) { *writer |> write("/*") } } def override visitExprLooksLikeCallArgument(expr : ExprLooksLikeCall?; arg : ExpressionPtr; last : bool) { - let isInvoke = self->isInvokeMethod(expr) + let isInvoke = isInvokeMethod(expr) if (isInvoke && arg == expr.arguments[0]) { *writer |> write("(") } elif (isInvoke && arg == expr.arguments[1]) { @@ -1156,7 +1156,7 @@ class GlslExport : AstVisitor { def override preVisitExprCall(expr : ExprCall?) { var fnName : string unsafe { - fnName = self->glsl_function_name(reinterpret expr.func) + fnName = glsl_function_name(reinterpret expr.func) } if (caps.output_compatibility_constructors) { glsl_compatibility_constructors |> get(fnName) $(newName) { @@ -1180,7 +1180,7 @@ class GlslExport : AstVisitor { } // null coaelescing def override preVisitExprNullCoalescing(expr : ExprNullCoalescing?) : void { - self->error("null coaelescing is not supported", expr.at) + error("null coaelescing is not supported", expr.at) } // at def override visitExprAt(expr : ExprAt?) : ExpressionPtr { @@ -1192,11 +1192,11 @@ class GlslExport : AstVisitor { } // safe at def override preVisitExprSafeAt(expr : ExprSafeAt?) : void { - self->error("safe index is not supported", expr.at) + error("safe index is not supported", expr.at) } // is def override preVisitExprIsType(expr : ExprIs?; typeDecl : TypeDeclPtr) : void { - self->error("is type is not supported", expr.at) + error("is type is not supported", expr.at) } // op2 def getReplaceOp2(expr : ExprOp2?) : tuple { @@ -1212,7 +1212,7 @@ class GlslExport : AstVisitor { return (fun = "", replOp = false) } def override preVisitExprOp2(expr : ExprOp2?) { - let repl = self->getReplaceOp2(expr) + let repl = getReplaceOp2(expr) *writer |> write("{repl.fun}(") } def override visitExprOp2(expr : ExprOp2?) : ExpressionPtr { @@ -1220,7 +1220,7 @@ class GlslExport : AstVisitor { return expr } def override preVisitExprOp2Right(expr : ExprOp2?; right : ExpressionPtr) { - let repl = self->getReplaceOp2(expr) + let repl = getReplaceOp2(expr) if (repl.replOp) { *writer |> write(", ") } else { @@ -1247,15 +1247,15 @@ class GlslExport : AstVisitor { } // move def override preVisitExprMoveRight(expr : ExprMove?; right : ExpressionPtr) { - self->error("move is not supported", expr.at) + error("move is not supported", expr.at) } // clone def override preVisitExprCloneRight(expr : ExprClone?; right : ExpressionPtr) { - self->error("clone is not supported", expr.at) + error("clone is not supported", expr.at) } // with def override preVisitExprWith(expr : ExprWith?) { - self->error("with is not supported", expr.at) + error("with is not supported", expr.at) } // while def override preVisitExprWhile(expr : ExprWhile?) { @@ -1263,11 +1263,11 @@ class GlslExport : AstVisitor { } def override preVisitExprWhileBody(expr : ExprWhile?; right : ExpressionPtr) { *writer |> write(") ") - self->newLine(1) + newLine(1) } // try-catch def override preVisitExprTryCatch(expr : ExprTryCatch?) { - self->error("try-recover is not supported", expr.at) + error("try-recover is not supported", expr.at) } // if-then-else def override preVisitExprIfThenElse(expr : ExprIfThenElse?) { @@ -1275,7 +1275,7 @@ class GlslExport : AstVisitor { } def override preVisitExprIfThenElseIfBlock(expr : ExprIfThenElse?; ifBlock : ExpressionPtr) { *writer |> write(")") - self->newLine(1) + newLine(1) } def override preVisitExprIfThenElseElseBlock(expr : ExprIfThenElse?; elseBlock : ExpressionPtr) { *writer |> write("{repeat("\t",tab)}") @@ -1283,7 +1283,7 @@ class GlslExport : AstVisitor { *writer |> write("else ") } else { *writer |> write("else") - self->newLine(1) + newLine(1) } } // for @@ -1296,75 +1296,75 @@ class GlslExport : AstVisitor { } def override preVisitExprFor(expr : ExprFor?) { for (source in expr.sources) { - if (!self->isRangeFor(source) && !self->isDimFor(source)) { - self->error("only range for or dim for are supported for now {describe(source._type)}", source.at) + if (!isRangeFor(source) && !isDimFor(source)) { + error("only range for or dim for are supported for now {describe(source._type)}", source.at) return } } *writer |> write("\{") } def override preVisitExprForSource(expr : ExprFor?; source : ExpressionPtr; last : bool) : void { - self->newLine(1) + newLine(1) let index = get_for_source_index(expr, source) - if (self->isDimFor(source)) { + if (isDimFor(source)) { *writer |> write("{repeat("\t",tab)}int {expr.iteratorVariables[index].name} = 0; //") - } elif (self->isRangeFor(source)) { - let svtype = "{self->describe_glsl_type(source._type)}" + } elif (isRangeFor(source)) { + let svtype = "{describe_glsl_type(source._type)}" *writer |> write("{repeat("\t",tab)}{svtype} _for_range_v{expr.iteratorVariables[index].name} = ") } } def override visitExprForSource(expr : ExprFor?; source : ExpressionPtr; last : bool) : ExpressionPtr { let index = get_for_source_index(expr, source) - if (self->isDimFor(source)) { + if (isDimFor(source)) { pass - } elif (self->isRangeFor(source)) { + } elif (isRangeFor(source)) { *writer |> write(";") - self->newLine(1) + newLine(1) let sname = "{expr.iteratorVariables[index].name}" - let stype = "{self->describe_glsl_type(expr.iteratorVariables[index]._type)}" + let stype = "{describe_glsl_type(expr.iteratorVariables[index]._type)}" *writer |> write("{repeat("\t",tab)}{stype} {sname} = _for_range_v{sname}.x;") } return source } def override preVisitExprForBody(expr : ExprFor?) { - self->newLine(1) + newLine(1) *writer |> write("{repeat("\t",tab)}while ( ") for (source, svar, iter in expr.sources, expr.iteratorVariables, expr.iterators) { if (iter != expr.iterators[0]) { *writer |> write(" && ") } - if (self->isDimFor(source)) { + if (isDimFor(source)) { let sdim = source._type.dim[length(source._type.dim) - 1] *writer |> write("{svar.name}!={sdim}") - let newsrc = "{self->describe_subexpression(source)}[{svar.name}]" + let newsrc = "{describe_subexpression(source)}[{svar.name}]" renames |> push((name = "{iter}", repl = newsrc)) - } elif (self->isRangeFor(source)) { + } elif (isRangeFor(source)) { *writer |> write("{svar.name}!=_for_range_v{svar.name}.y") } } *writer |> write(" )") - self->newLine(1) + newLine(1) *writer |> write("{repeat("\t",tab)}\{") - self->newLine(1) + newLine(1) *writer |> write("{repeat("\t",tab-1)}") } def override visitExprFor(expr : ExprFor?) : ExpressionPtr { for (svar, source in expr.iteratorVariables, expr.sources) { *writer |> write("{repeat("\t",tab)}") - if (self->isDimFor(source)) { + if (isDimFor(source)) { renames |> pop() *writer |> write("{svar.name}++;") - } elif (self->isRangeFor(source)) { + } elif (isRangeFor(source)) { *writer |> write("{svar.name}++;") } - self->newLine(1) + newLine(1) } *writer |> write("{repeat("\t",tab)}\}\}") return expr } // make variant def override preVisitExprMakeVariant(expr : ExprMakeVariant?) { - self->error("variant is not supported", expr.at) + error("variant is not supported", expr.at) } // make structure def outputMakeStruct(expr : ExprMakeStruct?; index : int) { @@ -1373,7 +1373,7 @@ class GlslExport : AstVisitor { for (sf, i in expr._type.structType.fields, count()) { if (sf.name == st.name) { if (prev > i) { - self->error("out of order initialization is not supported by GLSL yet", st.at) + error("out of order initialization is not supported by GLSL yet", st.at) } if (!st.value.flags.noSideEffects) { prev = i @@ -1393,13 +1393,13 @@ class GlslExport : AstVisitor { } } if (!has) { - self->produce_zero(sf._type) + produce_zero(sf._type) } } } def override preVisitExprMakeStruct(expr : ExprMakeStruct?) { if (expr._block != null) { - self->error("structure initialization with the block is not supported", expr.at) + error("structure initialization with the block is not supported", expr.at) } *writer |> write("{expr.makeType.structType.name}") if (caps.output_structure_initializers) { @@ -1416,11 +1416,11 @@ class GlslExport : AstVisitor { *writer |> write(",") } *writer |> write("{expr.makeType.structType.name}(") - self->outputMakeStruct(expr, n) + outputMakeStruct(expr, n) *writer |> write(")") } } else { - self->outputMakeStruct(expr, 0) + outputMakeStruct(expr, 0) } *writer |> write(")") } @@ -1429,7 +1429,7 @@ class GlslExport : AstVisitor { } // make array def override preVisitExprMakeArray(expr : ExprMakeArray?) { - let atype = self->describe_glsl_type(expr._type) + let atype = describe_glsl_type(expr._type) *writer |> write("({atype}(") } def override visitExprMakeArray(expr : ExprMakeArray?) : ExpressionPtr { @@ -1450,31 +1450,31 @@ class GlslExport : AstVisitor { // make tuple def override preVisitExprMakeTuple(expr : ExprMakeTuple?) { // TODO: support nameless structure - self->error("tuple is not supported YET", expr.at) + error("tuple is not supported YET", expr.at) } // array comprehension def override preVisitExprArrayComprehension(expr : ExprArrayComprehension?) { - self->error("array comprehension is not supported", expr.at) + error("array comprehension is not supported", expr.at) } // type info def override preVisitExprTypeInfo(expr : ExprTypeInfo?) { - self->error("typeinfo is not supported", expr.at) + error("typeinfo is not supported", expr.at) } // ptr to ref def override preVisitExprPtr2Ref(expr : ExprPtr2Ref?) { - self->error("deref is not supported", expr.at) + error("deref is not supported", expr.at) } // label def override preVisitExprLabel(expr : ExprLabel?) { - self->error("label is not supported", expr.at) + error("label is not supported", expr.at) } // goto def override preVisitExprGoto(expr : ExprGoto?) { - self->error("goto is not supported", expr.at) + error("goto is not supported", expr.at) } // ref 2 ptr def override preVisitExprRef2Ptr(expr : ExprRef2Ptr?) { - self->error("addr is not supported", expr.at) + error("addr is not supported", expr.at) } // ref to value def override preVisitExprRef2Value(expr : ExprRef2Value?) { @@ -1486,55 +1486,55 @@ class GlslExport : AstVisitor { } // @@ def override preVisitExprAddr(expr : ExprAddr?) { - self->error("function addr is not supported", expr.at) + error("function addr is not supported", expr.at) } // assert / verify def override preVisitExprAssert(expr : ExprAssert?) : void { - self->error("assert is not supported", expr.at) + error("assert is not supported", expr.at) } // static_assert def override preVisitExprStaticAssert(expr : ExprStaticAssert?) : void { - self->error("static_assert is not supported", expr.at) + error("static_assert is not supported", expr.at) } // quote def override preVisitExprQuote(expr : ExprQuote?) : void { - self->error("quote is not supported", expr.at) + error("quote is not supported", expr.at) } // debug def override preVisitExprDebug(expr : ExprDebug?) : void { - self->error("debug is not supported", expr.at) + error("debug is not supported", expr.at) } // invoke def override preVisitExprInvoke(expr : ExprInvoke?) : void { - self->error("invoke is not supported", expr.at) + error("invoke is not supported", expr.at) } // erase def override preVisitExprErase(expr : ExprErase?) : void { - self->error("erase is not supported", expr.at) + error("erase is not supported", expr.at) } // set insert def override preVisitExprSetInsert(expr : ExprSetInsert?) : void { - self->error("insert is not supported", expr.at) + error("insert is not supported", expr.at) } // find def override preVisitExprFind(expr : ExprFind?) : void { - self->error("find is not supported", expr.at) + error("find is not supported", expr.at) } // key exists def override preVisitExprKeyExists(expr : ExprKeyExists?) : void { - self->error("keyexists is not supported", expr.at) + error("keyexists is not supported", expr.at) } // ascend def override preVisitExprAscend(expr : ExprAscend?) { - self->error("new is not supported", expr.at) + error("new is not supported", expr.at) } // cast def override preVisitExprCast(expr : ExprCast?) { - self->error("cast is not supported", expr.at) + error("cast is not supported", expr.at) } // delete def override preVisitExprDelete(expr : ExprDelete?) { - self->error("delete is not supported", expr.at) + error("delete is not supported", expr.at) } // var def override preVisitExprVar(expr : ExprVar?) { @@ -1556,7 +1556,7 @@ class GlslExport : AstVisitor { } // safe field def override preVisitExprSafeField(expr : ExprSafeField?) : void { - self->error("'?.' is not supported", expr.at) + error("'?.' is not supported", expr.at) } // swizzle def override visitExprSwizzle(expr : ExprSwizzle?) : ExpressionPtr { @@ -1570,15 +1570,15 @@ class GlslExport : AstVisitor { } // is variant def override preVisitExprIsVariant(expr : ExprIsVariant?) : void { - self->error("'is' is not supported", expr.at) + error("'is' is not supported", expr.at) } // as variant def override preVisitExprAsVariant(expr : ExprAsVariant?) : void { - self->error("'as' is not supported", expr.at) + error("'as' is not supported", expr.at) } // safe as variant def override preVisitExprSafeAsVariant(expr : ExprSafeAsVariant?) : void { - self->error("'?as' is not supported", expr.at) + error("'?as' is not supported", expr.at) } // op1 def override preVisitExprOp1(expr : ExprOp1?) : void { @@ -1602,7 +1602,7 @@ class GlslExport : AstVisitor { } // yield def override preVisitExprYield(expr : ExprYield?) : void { - self->error("yield is not supported", expr.at) + error("yield is not supported", expr.at) } // break def override preVisitExprBreak(expr : ExprBreak?) : void { @@ -1614,19 +1614,19 @@ class GlslExport : AstVisitor { } // make block def override preVisitExprMakeBlock(expr : ExprMakeBlock?) : void { - self->error("'make block' is not supported", expr.at) + error("'make block' is not supported", expr.at) } // make generator def override preVisitExprMakeGenerator(expr : ExprMakeGenerator?) : void { - self->error("'make generator' is not supported", expr.at) + error("'make generator' is not supported", expr.at) } // memzero def override preVisitExprMemZero(expr : ExprMemZero?) : void { - self->error("memzero is not supported", expr.at) + error("memzero is not supported", expr.at) } // const ptr def override preVisitExprConstPtr(expr : ExprConstPtr?) : void { - self->error("pointer is not supported", expr.at) + error("pointer is not supported", expr.at) } // const enumeraiton def override preVisitExprConstEnumeration(expr : ExprConstEnumeration?) : void { @@ -1785,7 +1785,7 @@ class GlslExport : AstVisitor { } // string def override preVisitExprConstString(expr : ExprConstString?) : void { - self->error("string constant is not supported", expr.at) + error("string constant is not supported", expr.at) } // const double def override preVisitExprConstDouble(expr : ExprConstDouble?) : void { @@ -1793,19 +1793,19 @@ class GlslExport : AstVisitor { } // fake context def override preVisitExprFakeContext(expr : ExprFakeContext?) : void { - self->error("__context__ is not supported", expr.at) + error("__context__ is not supported", expr.at) } // fake line info def override preVisitExprFakeLineInfo(expr : ExprFakeLineInfo?) : void { - self->error("__lineinfo__ is not supported", expr.at) + error("__lineinfo__ is not supported", expr.at) } // reader def override preVisitExprReader(expr : ExprReader?) : void { - self->error("call macro is not supported", expr.at) + error("call macro is not supported", expr.at) } // call macro def override preVisitExprCallMacro(expr : ExprCallMacro?) : void { - self->error("call macro is not supported", expr.at) + error("call macro is not supported", expr.at) } } From 792413b7c7cfa3db68881c5fac479ab7659e00ef Mon Sep 17 00:00:00 2001 From: Boris Batkin Date: Wed, 10 Jun 2026 14:30:33 -0700 Subject: [PATCH 10/23] fixed-array: generic aliases bind the WHOLE matched type Revises the alias decision in the previous commit, per review: auto(TT) matched against int[4] binds TT = int[4]; TT[2] is int[2][4] (an array of 2 TT, natural nesting); array against array binds TT = int[3] - inexpressible on master; def two(a : auto(TT); b : TT) accepts (int[3], int[3]). The flattened world's strip/replace rule (bare TT was the ELEMENT; use-site dims REPLACED the bound ones) is deliberately dead - it made generics clumsy and unsupportable, and existed only because a single node carried both element and dims. The master-compat peel is reverted. The invariant that keeps most of daslib unchanged: auto(TT)[] - the explicit [] suffix - still binds TT to the ELEMENT (the [] eats one dim level during matching), so the builtin clone/to_array/table-[] families and decs get_ro's TT[typeinfo dim(value)] reconstruct remain correct as written. Ported the bare-auto(TT) dim arms to the new binding: json_boost from_JV and PUGIXML_boost from_XML (`var ret : TT -const -&` - TT IS the array type), and seven decs functions (decs_array / get / get_default_ro / get_component / ComponentMap get / make_callbacks / make_component) where every `static_if (typeinfo is_dim(...))` arm collapsed into its else arm - decs.das alone nets -160 lines, which is the supportability win this flip buys. External code using the TT[typeinfo dim(x)] reconstruct on bare auto(TT) params breaks loudly at compile; bodies using bare TT as the element type change meaning silently - test suites are the net. Gates: probe matrix = natural binding everywhere; tests-cpp 56/56; tests/fixed_array 86/86; json/PUGIXML/decs green; full tree 10784/10784 interpreted + 10123/10123 under test_aot -use-aot; zero GC leaks in both modes; lint clean. Co-Authored-By: Claude Fable 5 --- FIXED_ARRAY_REWORK.md | 40 +-- daslib/decs.das | 260 +++++--------------- daslib/json_boost.das | 2 +- modules/dasPUGIXML/daslib/PUGIXML_boost.das | 2 +- src/ast/ast_infer_type_helper.cpp | 26 +- 5 files changed, 100 insertions(+), 230 deletions(-) diff --git a/FIXED_ARRAY_REWORK.md b/FIXED_ARRAY_REWORK.md index 1167375bf0..6fde4a68d9 100644 --- a/FIXED_ARRAY_REWORK.md +++ b/FIXED_ARRAY_REWORK.md @@ -242,18 +242,29 @@ The indivisible piece, sub-staged for review: 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 via AskUserQuestion: - MASTER-COMPAT PEEL. Master's rule (empirically pinned with the saved 1b-i exe): a - generic alias use-site's own dims REPLACE the bound type's dims — bare `TT` bound from - `int[3]` is the ELEMENT `int`, `TT[4]` is `int[4]`, and the strip applies even through - `array`; module typedefs append naturally (`foo[2]` where `foo=int[3]` is - `int[2][3]`) in both worlds. Implementation: `peelFixedArrayAliasBinding` at the alias - leaf in inferAlias + inferPartialAliases (head qualifiers transfer to the element on - peel; arm 2 keeps the binding's alias label — clearing breaks typemacro aliases), plus - the FA-recursion arm now HOISTS the resolved element's ref/const/temporary to the chain - head (canonical form; without it `TT[4]` kept const on the element and var-decl - const-stripping missed it). daslib's `TT[typeinfo dim(x)]` reconstruct pattern works - unmodified. Probe matrix lives in D:/Work/fa_scratch/alias_dim_probe*.das. + (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]` makes `TT = int[4]`; `TT[2]` = `int[2][4]` (array of 2 TT, natural nesting); + `array` ← `array` makes `TT = 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 (bare `TT` ← `int[3]` was the ELEMENT `int`; `TT[4]` + REPLACED to `int[4]`; strip applied even through `array`) — 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's + `TT[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 (every `static_if is_dim` arm 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 the `TT[typeinfo dim(x)]` reconstruct on bare + `auto(TT)` params breaks loudly at compile; bodies using bare `TT` as the element type + change meaning silently — test suites are the net. (2) DASLIB `.dim` READERS (match tests, stbimage→is_local, aot_cpp): the das `.dim` binding 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 @@ -289,8 +300,9 @@ Exit: full CI green with aot_cpp.das / llvm_jit.das / macro daslib UNMODIFIED (r type; fixed to `typeinfo 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: probes match master 100%, all 11 files green, tests-cpp 56/56, tests/fixed_array - 86/86, full-tree dastest 10784/10784 interpreted + 10123/10123 under test_aot -use-aot, + 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. ### Stage 2 — AOT emitter diff --git a/daslib/decs.das b/daslib/decs.das index c490962913..c2b14d86dc 100644 --- a/daslib/decs.das +++ b/daslib/decs.das @@ -740,18 +740,12 @@ def decs_array(atype : auto(TT); src : array; capacity : int64) { //! Low level function returns temporary array of component given specific type of component. //! capacity is int64 — archetypes can hold >INT_MAX entities (Phase 8c widening). assert(!empty(src)) - static_if (typeinfo is_dim(atype)) { - var dest : array - unsafe { - _builtin_make_temp_array_i64(dest, addr(src[0]), capacity) - return <- dest - } - } else { - var dest : array - unsafe { - _builtin_make_temp_array_i64(dest, addr(src[0]), capacity) - return <- dest - } + // TT is the full component type either way - a generic alias binds the whole + // matched type, fixed arrays included + var dest : array + unsafe { + _builtin_make_temp_array_i64(dest, addr(src[0]), capacity) + return <- dest } } @@ -764,30 +758,17 @@ def public get(arch : Archetype; name : string; value : auto(TT)) { let comp & = unsafe(arch.components[idx]) if (comp.name == name) { unsafe { - var cvinfo : TypeInfo const? - static_if (typeinfo is_dim(value)) { - cvinfo = addr(typeinfo rtti_typeinfo(type)) - } else { - cvinfo = addr(typeinfo rtti_typeinfo(type)) - } + let cvinfo = addr(typeinfo rtti_typeinfo(type)) if (comp.info.hash != cvinfo.hash) { panic("decs: component array {name} type mismatch, expecting {describe(comp.info)} vs {describe(cvinfo)} MNH={get_mangled_name(cvinfo)} hash={cvinfo.hash} size={cvinfo.size}") } - static_if (typeinfo is_dim(value)) { - return <- decs_array(type, comp.data, arch.size) - } else { - return <- decs_array(type, comp.data, arch.size) - } + return <- decs_array(type, comp.data, arch.size) } } } panic("decs: component array {name} not found") unsafe { - static_if (typeinfo is_dim(value)) { - return <- array() - } else { - return <- array() - } + return <- array() } } @@ -815,24 +796,13 @@ def public get_default_ro(arch : Archetype; name : string; value : auto(TT)) : i let comp & = unsafe(arch.components[idx]) if (comp.name == name) { unsafe { - var cvinfo : TypeInfo const? - static_if (typeinfo is_dim(value)) { - cvinfo = addr(typeinfo rtti_typeinfo(type)) - } else { - cvinfo = addr(typeinfo rtti_typeinfo(type)) - } + let cvinfo = addr(typeinfo rtti_typeinfo(type)) if (comp.info.hash != cvinfo.hash) { panic("decs: component array {name} type mismatch, expecting {describe(comp.info)} vs {describe(cvinfo)} MNH={get_mangled_name(cvinfo)} hash={cvinfo.hash} size={cvinfo.size}") } - static_if (typeinfo is_dim(value)) { - var it : iterator - _builtin_make_fixed_array_iterator(it, addr(comp.data[0]), arch.size, comp.stride) - return <- it - } else { - var it : iterator - _builtin_make_fixed_array_iterator(it, addr(comp.data[0]), arch.size, comp.stride) - return <- it - } + var it : iterator + _builtin_make_fixed_array_iterator(it, addr(comp.data[0]), arch.size, comp.stride) + return <- it } } } @@ -1138,12 +1108,7 @@ def public get_component(eid : EntityId; name : string; defval : auto(TT)) : TT var comp & = unsafe(arch.components[cidx]) if (comp.name == name) { unsafe { - var cvinfo : TypeInfo const? - static_if (typeinfo is_dim(defval)) { - cvinfo = addr(typeinfo rtti_typeinfo(type)) - } else { - cvinfo = addr(typeinfo rtti_typeinfo(type)) - } + let cvinfo = addr(typeinfo rtti_typeinfo(type)) if (comp.info.hash != cvinfo.hash) { panic("decs: get_component {name} type mismatch, expecting {describe(comp.info)} vs {describe(cvinfo)} MNH={get_mangled_name(cvinfo)} hash={cvinfo.hash} size={cvinfo.size}") } @@ -1162,12 +1127,7 @@ def public get(var cmp : ComponentMap; name : string; var value : auto(TT)) { let idx = lower_bound(cmp, ComponentValue(name = name)) <| $(x, y) => x.name < y.name if (idx < length(cmp) && cmp[idx].name == name) { unsafe { - var cvinfo : TypeInfo const? - static_if (typeinfo is_dim(value)) { - cvinfo = addr(typeinfo rtti_typeinfo(type)) - } else { - cvinfo = addr(typeinfo rtti_typeinfo(type)) - } + let cvinfo = addr(typeinfo rtti_typeinfo(type)) if (cmp[idx].info.hash != cvinfo.hash) { panic("decs: get component {name} type mismatch, expecting {describe(cmp[idx].info)} vs {describe(cvinfo)} MNH={get_mangled_name(cvinfo)} hash={cvinfo.hash} size={cvinfo.size}") } @@ -1179,164 +1139,83 @@ def public get(var cmp : ComponentMap; name : string; var value : auto(TT)) { def private make_callbacks(var cv : ComponentValue; value : auto(TT)) { cv.info.mkTypeInfo = @@() : TypeInfo const? { - static_if (typeinfo is_dim(value)) { - return unsafe(addr(typeinfo rtti_typeinfo(type>))) - } else { - return unsafe(addr(typeinfo rtti_typeinfo(type>))) - } + return unsafe(addr(typeinfo rtti_typeinfo(type>))) } cv.info.dumper = @@(elem : void?) { if (elem == null) return "" - unsafe { - static_if (typeinfo is_dim(value)) { - var pTT = reinterpret elem - return "{*pTT}" - } else { - var pTT = reinterpret elem - return "{*pTT}" - } - } + var pTT = unsafe(reinterpret elem) + return "{*pTT}" } cv.info.gc = @@(var src : array) : lambda { // the idea with this one is that we make a lambda // which would capture an array of the right size and type // which points to the right data // and when lambda is deleted - we memzero the temp array - static_if (typeinfo is_dim(value)) { - var gc_dummy : array - let stride = typeinfo sizeof(type) - let size = length(src) / stride - unsafe { - _builtin_make_temp_array(gc_dummy, addr(src[0]), size) - } - return <- @ capture(<- gc_dummy) { - pass - } finally { - memzero(gc_dummy) - } - } else { - var gc_dummy : array - let stride = typeinfo sizeof(type) - let size = length(src) / stride - unsafe { - _builtin_make_temp_array(gc_dummy, addr(src[0]), size) - } - return <- @ capture(<- gc_dummy) { - pass - } finally { - memzero(gc_dummy) - } + var gc_dummy : array + let stride = typeinfo sizeof(type) + let size = length(src) / stride + unsafe { + _builtin_make_temp_array(gc_dummy, addr(src[0]), size) + } + return <- @ capture(<- gc_dummy) { + pass + } finally { + memzero(gc_dummy) } } cv.info.clonner = @@(var dst : array; src : array) { if (empty(src)) return - static_if (typeinfo is_dim(value)) { - let stride = typeinfo sizeof(type) - dst |> resize(length(src)) - let size = length(src) / stride - var dsrc, ssrc : array - unsafe { - _builtin_make_temp_array(dsrc, addr(dst[0]), size) - _builtin_make_temp_array(ssrc, addr(src[0]), size) - } - for (d, s in dsrc, ssrc) { - d := s - } - } else { - let stride = typeinfo sizeof(type) - dst |> resize(length(src)) - let size = length(src) / stride - var dsrc, ssrc : array - unsafe { - _builtin_make_temp_array(dsrc, addr(dst[0]), size) - _builtin_make_temp_array(ssrc, addr(src[0]), size) - } - for (d, s in dsrc, ssrc) { - d := s - } + let stride = typeinfo sizeof(type) + dst |> resize(length(src)) + let size = length(src) / stride + var dsrc, ssrc : array + unsafe { + _builtin_make_temp_array(dsrc, addr(dst[0]), size) + _builtin_make_temp_array(ssrc, addr(src[0]), size) + } + for (d, s in dsrc, ssrc) { + d := s } } cv.info.serializer = @@(var arch : Archive; var src : array; name : string) { - static_if (typeinfo is_dim(value)) { - var stride = typeinfo sizeof(type) - if (arch.reading) { - var wasStride : int - arch |> _::serialize(wasStride) - if (wasStride != stride) { - panic("decs: component '{name}' stride mismatch, expecting {stride}, got {wasStride}") - } else { - var temp : array - arch |> _::serialize(temp) - let size = length(temp) - src |> resize(size * stride) - if (size > 0) { - unsafe { - memcpy(addr(src[0]), addr(temp[0]), size * stride) - } - } - } + var stride : int = typeinfo sizeof(type) + if (arch.reading) { + var wasStride : int + arch |> _::serialize(wasStride) + if (wasStride != stride) { + panic("decs: component '{name}' stride mismatch, expecting {stride}, got {wasStride}") } else { - arch |> _::serialize(stride) - var ssrc : array - let size = length(src) / stride - unsafe { - if (!empty(src)) { - _builtin_make_temp_array(ssrc, addr(src[0]), size) + var temp : array + arch |> _::serialize(temp) + let size = length(temp) + src |> resize(size * stride) + if (size > 0) { + unsafe { + memcpy(addr(src[0]), addr(temp[0]), size * stride) } } - arch |> serialize(ssrc) } } else { - var stride : int = typeinfo sizeof(type) - if (arch.reading) { - var wasStride : int - arch |> _::serialize(wasStride) - if (wasStride != stride) { - panic("decs: component '{name}' stride mismatch, expecting {stride}, got {wasStride}") - } else { - var temp : array - arch |> _::serialize(temp) - let size = length(temp) - src |> resize(size * stride) - if (size > 0) { - unsafe { - memcpy(addr(src[0]), addr(temp[0]), size * stride) - } - } - } - } else { - arch |> _::serialize(stride) - var ssrc : array - let size = length(src) / stride - unsafe { - if (!empty(src)) { - _builtin_make_temp_array(ssrc, addr(src[0]), size) - } + arch |> _::serialize(stride) + var ssrc : array + let size = length(src) / stride + unsafe { + if (!empty(src)) { + _builtin_make_temp_array(ssrc, addr(src[0]), size) } - arch |> serialize(ssrc) } + arch |> serialize(ssrc) } } static_if (typeinfo can_delete(type)) { cv.info.eraser = @@(var arr : array) { if (empty(arr)) return - static_if (typeinfo is_dim(value)) { - let stride = typeinfo sizeof(type) - for (i in range(length(arr) / stride)) { - let offset = i * stride - unsafe { - var adel = reinterpret addr(arr[offset]) - delete * adel - } - } - } else { - let stride = typeinfo sizeof(type) - for (i in range(length(arr) / stride)) { - let offset = i * stride - unsafe { - var adel = reinterpret addr(arr[offset]) - delete * adel - } + let stride = typeinfo sizeof(type) + for (i in range(length(arr) / stride)) { + let offset = i * stride + unsafe { + var adel = reinterpret addr(arr[offset]) + delete * adel } } } @@ -1346,15 +1225,8 @@ def private make_callbacks(var cv : ComponentValue; value : auto(TT)) { def private make_component(name : string; value : auto(TT)) { var cv = ComponentValue(name = name) unsafe { - var tinfo : TypeInfo const? - var tname : string - static_if (typeinfo is_dim(value)) { - tinfo = addr(typeinfo rtti_typeinfo(type)) - tname = typeinfo fulltypename(type) - } else { - tinfo = addr(typeinfo rtti_typeinfo(type)) - tname = typeinfo fulltypename(type) - } + let tinfo = addr(typeinfo rtti_typeinfo(type)) + let tname = typeinfo fulltypename(type) cv.info = CTypeInfo(basicType = tinfo.basicType, mangledName = get_mangled_name(tinfo), fullName = tname, hash = tinfo.hash, size = tinfo.size) make_callbacks(cv, value) diff --git a/daslib/json_boost.das b/daslib/json_boost.das index f0bec242c6..078c295ea5 100644 --- a/daslib/json_boost.das +++ b/daslib/json_boost.das @@ -453,7 +453,7 @@ def from_JV(v : JsonValue const explicit?; anything : auto(TT)) { //! Parse a JSON value and return the corresponding value of any type. //! This is the main dispatch function that handles various types. static_if (typeinfo is_dim(anything)) { - var ret : TT[typeinfo dim(anything)] + var ret : TT -const -& // TT IS the matched fixed-array type if (v == null || !(v.value is _array)) return <- ret unsafe { let arr & = v.value as _array diff --git a/modules/dasPUGIXML/daslib/PUGIXML_boost.das b/modules/dasPUGIXML/daslib/PUGIXML_boost.das index b5f502e6a9..e43f2a0772 100644 --- a/modules/dasPUGIXML/daslib/PUGIXML_boost.das +++ b/modules/dasPUGIXML/daslib/PUGIXML_boost.das @@ -765,7 +765,7 @@ def from_XML(node : xml_node; anything : auto(TT)) { //! Struct fields become child elements; arrays become sequences of //! child elements. Missing elements yield default values. static_if (typeinfo is_dim(anything)) { - var ret : TT[typeinfo dim(anything)] + var ret : TT -const -& // TT IS the matched fixed-array type var ch = node.first_child for (i in 0..typeinfo dim(anything)) { if (ch.ok) { diff --git a/src/ast/ast_infer_type_helper.cpp b/src/ast/ast_infer_type_helper.cpp index 895caf7ce4..203a4e261d 100644 --- a/src/ast/ast_infer_type_helper.cpp +++ b/src/ast/ast_infer_type_helper.cpp @@ -381,23 +381,6 @@ namespace das { return false; } - // master semantics: a generic alias use-site's own dims REPLACE the bound type's dims - // (bare use strips them) - resolve the binding to the bound chain's ELEMENT, carrying the - // head's qualifiers; use-site FA wraps then re-dim it naturally - static TypeDecl * peelFixedArrayAliasBinding ( TypeDecl * aT ) { - auto elemT = aT; - while ( elemT->baseType==Type::tFixedArray && elemT->firstType ) { - elemT = elemT->firstType; - } - auto resT = new TypeDecl(*elemT); - if ( elemT != aT ) { - resT->ref = resT->ref || aT->ref; - resT->constant = resT->constant || aT->constant; - resT->temporary = resT->temporary || aT->temporary; - } - return resT; - } - TypeDeclPtr InferTypes::inferAlias(const TypeDeclPtr &decl, const FunctionPtr &fptr, AliasMap *aliases, OptionsMap *options, bool autoToAlias) const { autoToAlias |= decl->autoToAlias; if (decl->baseType == Type::typeDecl || decl->baseType == Type::typeMacro) { @@ -426,7 +409,10 @@ namespace das { } } if (aT) { - auto resT = peelFixedArrayAliasBinding(aT); + // a generic alias binds the WHOLE matched type - int[4] passed to auto(TT) + // makes TT = int[4]; use-site FA wraps then nest naturally (TT[2] = int[2][4]). + // (The flattened world stripped the bound dims here; that wart is gone.) + auto resT = new TypeDecl(*aT); resT->at = decl->at; resT->ref = (resT->ref || decl->ref) && !decl->removeRef; resT->constant = (resT->constant || decl->constant) && !decl->removeConstant; @@ -558,7 +544,7 @@ namespace das { aT = fptr ? findFuncAlias(fptr, decl->alias) : findAlias(decl->alias); } if (aT) { - auto resT = peelFixedArrayAliasBinding(aT); + auto resT = new TypeDecl(*aT); resT->at = decl->at; resT->ref = (resT->ref || decl->ref) && !decl->removeRef; resT->constant = (resT->constant || decl->constant) && !decl->removeConstant; @@ -567,7 +553,7 @@ namespace das { resT->explicitConst = (resT->explicitConst || decl->explicitConst); resT->dim = decl->dim; resT->aotAlias = false; - resT->alias = aT->alias; // keep the binding's label (a peel drops the chain head that carried it); clearing breaks typemacro-based aliases + // resT->alias.clear(); // this may speed things up, but it breaks typemacro-based aliases return resT; } else { return decl; From b7098da10edbb03366f3353bec337166e3be5a8f Mon Sep 17 00:00:00 2001 From: Boris Batkin Date: Wed, 10 Jun 2026 15:53:58 -0700 Subject: [PATCH 11/23] fixed-array 1g: delete container-FA [] overload families; natural archive recursion; default Stage 4's builtin.das half, pulled forward per review: natural alias binding makes the base generics cover every dedicated container-FA overload with identical bodies. Deleted 22 defs (array push x2/emplace/push_clone x2; table get x4/get_value/insert x2/insert_clone x2/emplace/values x2/ get_key/clone x2; one-arg clone x2). Capability gains: push_clone of clone-only rows (old gate was can_copy; the const-src body was un-instantiable rot), at-index push of FA rows, size mismatch is now a type error. archive.das: 6 stacked [][]..[] serialize overloads -> ONE natural recursion (auto(TT)[] peels a level per call); bulk-memcpy gate is_raw && (is_workhorse || is_dim) keeps exact wire parity; >6-D now works. default fixed (broken-but-unreachable on master): 0-element FA make-struct now zero-inits the whole declared shape - infer tolerates 0 provided elements, simulate zero-fills getSizeOf() instead of one element's stride. Target spec enabled: _target_inference.das -> test_target_inference.das incl. the get_key subtest (Stage 4 gate). Settled decoration: auto(TT) <- int[4] binds "int const[4]"; auto(TT)[] <- float[4][4] binds "float[4]" (the [] ate the qualifier-bearing chain head). style_lint STYLE030: unresolved generic-body call names now match against the require's PUBLIC RE-EXPORT CLOSURE too (daslib/rtti re-exporting builtin rtti - archive.das serializes function<> by mangled-name hash in a generic body). Gates: fixed_array 95/95, full tree 10793/10793 interpreted, 10132/10132 under test_aot -use-aot (two-pass build), lint 0 issues, formatter clean. Net -313 LOC. Co-Authored-By: Claude Fable 5 --- FIXED_ARRAY_REWORK.md | 49 +++- daslib/archive.das | 115 +------- daslib/builtin.das | 254 ------------------ daslib/style_lint.das | 24 +- src/ast/ast_infer_type_make.cpp | 5 +- src/ast/ast_simulate.cpp | 4 +- ...nference.das => test_target_inference.das} | 46 ++-- 7 files changed, 88 insertions(+), 409 deletions(-) rename tests/fixed_array/{_target_inference.das => test_target_inference.das} (54%) diff --git a/FIXED_ARRAY_REWORK.md b/FIXED_ARRAY_REWORK.md index 6fde4a68d9..cb9a54f963 100644 --- a/FIXED_ARRAY_REWORK.md +++ b/FIXED_ARRAY_REWORK.md @@ -305,6 +305,39 @@ Exit: full CI green with aot_cpp.das / llvm_jit.das / macro daslib UNMODIFIED (r 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 push x2 / emplace / + push_clone x2; table 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` 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). Settled decoration: plain + `auto(TT)` ← int[4] binds `int const[4]` (param constness rides TT); `auto(TT)[]` ← + float[4][4] binds `float[4]` UNQUALIFIED — the `[]` consumed the chain head where + qualifiers live. Asymmetric but harmless (generics strip with -const anyway). + 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. + ### Stage 2 — AOT emitter `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 @@ -318,15 +351,13 @@ nesting; TypeInfo globals unchanged — flattened); bump `LLVM_JIT_CODEGEN_VERSI Exit: JIT tests green. ### Stage 4 — daslib payoff -Delete the `[]` workaround families in builtin.das (table get x4 / get_value / insert x2 / -insert_clone x2 / emplace / values x2 / get_key; array push/emplace/push_clone siblings); -verify base generics cover them. Generalize `each`/`sort`/`find_index`/`subarray`/`clone`/ -`finalize_dim`. Un-reject fixed arrays in apply.das / contracts.das where they now just work. -The broken-on-master get_key(table) overload is NOT fixed in place (it gets deleted -here) — the get_key target subtest in tests/fixed_array stays disabled until THIS stage and -its enablement is part of this stage's exit gate. -Exit: new multi-dim/typedef'd-array tests pass through the generic stdlib (incl. get_key on -FA values); LOC visibly negative. +~~Delete the `[]` workaround families in builtin.das~~ + ~~get_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. ### Stage 5 — Macro/tooling ports ast_match.das (structural FA matching), match.das, typemacro_boost (field rename), lints diff --git a/daslib/archive.das b/daslib/archive.das index a1c7d6123e..979bc7d62e 100644 --- a/daslib/archive.das +++ b/daslib/archive.das @@ -192,8 +192,9 @@ def public serialize(var arch : Archive; var value : auto(TT)&) { } def public serialize(var arch : Archive; var value : auto(TT)[]) { - //! Serializes fixed-size array. Bulk memcpy for raw workhorse types, element-wise otherwise. - static_if (typeinfo is_workhorse(type) && typeinfo is_raw(type)) { + //! Serializes fixed-size array of any depth. Bulk memcpy when the whole block is raw + //! (workhorse leaf, or nested fixed array of raw), element-wise recursion otherwise. + static_if (typeinfo is_raw(type) && (typeinfo is_workhorse(type) || typeinfo is_dim(type))) { let sz = typeinfo sizeof(value) if (arch.reading) { arch.stream->read(unsafe(addr(value)), sz) @@ -207,116 +208,6 @@ def public serialize(var arch : Archive; var value : auto(TT)[]) { } } -def public serialize(var arch : Archive; var value : auto(TT)[][]) { - //! Serializes 2D fixed-size array. Bulk memcpy for raw POD types, element-wise otherwise. - static_if (typeinfo is_raw(type)) { - let sz = typeinfo sizeof(value) - if (arch.reading) { - arch.stream->read(unsafe(addr(value)), sz) - } else { - arch.stream->write(unsafe(addr(value)), sz) - } - } else { - for (a in value) { - for (element in a) { - arch |> _::serialize(element) - } - } - } -} - -def public serialize(var arch : Archive; var value : auto(TT)[][][]) { - //! Serializes 3D fixed-size array. Bulk memcpy for raw POD types, element-wise otherwise. - static_if (typeinfo is_raw(type)) { - let sz = typeinfo sizeof(value) - if (arch.reading) { - arch.stream->read(unsafe(addr(value)), sz) - } else { - arch.stream->write(unsafe(addr(value)), sz) - } - } else { - for (a in value) { - for (b in a) { - for (element in b) { - arch |> _::serialize(element) - } - } - } - } -} - -def public serialize(var arch : Archive; var value : auto(TT)[][][][]) { - //! Serializes 4D fixed-size array. Bulk memcpy for raw POD types, element-wise otherwise. - static_if (typeinfo is_raw(type)) { - let sz = typeinfo sizeof(value) - if (arch.reading) { - arch.stream->read(unsafe(addr(value)), sz) - } else { - arch.stream->write(unsafe(addr(value)), sz) - } - } else { - for (a in value) { - for (b in a) { - for (c in b) { - for (element in c) { - arch |> _::serialize(element) - } - } - } - } - } -} - -def public serialize(var arch : Archive; var value : auto(TT)[][][][][]) { - //! Serializes 5D fixed-size array. Bulk memcpy for raw POD types, element-wise otherwise. - static_if (typeinfo is_raw(type)) { - let sz = typeinfo sizeof(value) - if (arch.reading) { - arch.stream->read(unsafe(addr(value)), sz) - } else { - arch.stream->write(unsafe(addr(value)), sz) - } - } else { - for (a in value) { - for (b in a) { - for (c in b) { - for (d in c) { - for (element in d) { - arch |> _::serialize(element) - } - } - } - } - } - } -} - -def public serialize(var arch : Archive; var value : auto(TT)[][][][][][]) { - //! Serializes 6D fixed-size array. Bulk memcpy for raw POD types, element-wise otherwise. - static_if (typeinfo is_raw(type)) { - let sz = typeinfo sizeof(value) - if (arch.reading) { - arch.stream->read(unsafe(addr(value)), sz) - } else { - arch.stream->write(unsafe(addr(value)), sz) - } - } else { - for (a in value) { - for (b in a) { - for (c in b) { - for (d in c) { - for (e in d) { - for (element in e) { - arch |> _::serialize(element) - } - } - } - } - } - } - } -} - def public serialize(var arch : Archive; var value : array) { //! Serializes array. Bulk memcpy for raw workhorse types, element-wise otherwise. if (arch.reading) { diff --git a/daslib/builtin.das b/daslib/builtin.das index 0ae5b349cf..2385124f3e 100644 --- a/daslib/builtin.das +++ b/daslib/builtin.das @@ -229,36 +229,6 @@ def push(var Arr : array; varr : numT[] -#) { } } -[unused_argument(Arr, value)] -def push(var Arr : array; varr : numT[] -# ==const) { - static_if (typeinfo can_copy(type)) { - static_if (typeinfo sizeof(Arr[0]) == typeinfo sizeof(varr)) { - static_if (typeinfo can_clone_from_const(varr)) { - Arr[__builtin_array_push_back(Arr, typeinfo sizeof(Arr[0]))] = varr - } else { - concept_assert(false, "can't push array of different size") - } - } else { - concept_assert(false, "can't push array of different size") - } - } else { - concept_assert(false, "can't push value, which can't be copied") - } -} - -[unused_argument(Arr, value)] -def push(var Arr : array; var varr : numT[] -# ==const) { - static_if (typeinfo can_copy(type)) { - static_if (typeinfo sizeof(Arr[0]) == typeinfo sizeof(varr)) { - Arr[__builtin_array_push_back(Arr, typeinfo sizeof(Arr[0]))] = varr - } else { - concept_assert(false, "can't push array of different size") - } - } else { - concept_assert(false, "can't push value, which can't be copied") - } -} - [unused_argument(Arr, value)] def emplace(var Arr : array; var value : numT -#&; at : int | int64) { static_if (typeinfo can_move(value)) { @@ -301,21 +271,6 @@ def emplace(var Arr : array; var value : numT[] -#) { } } -[unused_argument(Arr, value)] -def emplace(var Arr : array; var value : numT[] -#) { - static_if (typeinfo can_move(value)) { - static_if (typeinfo sizeof(Arr[0]) == typeinfo sizeof(value)) { - unsafe { - Arr[__builtin_array_push_back(Arr, typeinfo sizeof(Arr[0]))] <- value - } - } else { - concept_assert(false, "can't emplace array of different size") - } - } else { - concept_assert(false, "can't emplace value, which can't be moved") - } -} - [unused_argument(Arr, value, at)] def push_clone(var Arr : array; value : numT ==const | #; at : int | int64) { static_if (typeinfo can_clone(value)) { @@ -396,38 +351,6 @@ def push_clone(var Arr : array; var varr : numT[] ==const) { } } -[unused_argument(Arr, value)] -def push_clone(var Arr : array; varr : numT[] ==const) { - static_if (typeinfo can_copy(type)) { - static_if (typeinfo sizeof(Arr[0]) == typeinfo sizeof(varr)) { - static_if (typeinfo can_clone_from_const(varr)) { - for (t in varr) { - Arr[__builtin_array_push_back_zero(Arr, typeinfo sizeof(Arr[0]))] := t - } - } else { - concept_assert(false, "can't push-clone value, which can't be cloned from const") - } - } else { - concept_assert(false, "can't push_clone array of different size") - } - } else { - concept_assert(false, "can't push value, which can't be cloned") - } -} - -[unused_argument(Arr, value)] -def push_clone(var Arr : array; var varr : numT[]) { - static_if (typeinfo can_copy(type)) { - static_if (typeinfo sizeof(Arr[0]) == typeinfo sizeof(varr)) { - Arr[__builtin_array_push_back_zero(Arr, typeinfo sizeof(Arr[0]))] := varr - } else { - concept_assert(false, "can't push_clone array of different size") - } - } else { - concept_assert(false, "can't push value, which can't be cloned") - } -} - [unused_argument(A, b)] def push_clone(var A : auto(CT) -# -const; b : auto(TT) | #) { static_if (!typeinfo can_clone(type)) { @@ -755,56 +678,6 @@ def get(var Tab : table ==const; at : keyT | #; blk : bl } } -// same get with [] - -def get(Tab : table ==const#; at : keyT | #; blk : block<(p : valT[typeinfo dim_table_value(Tab)]#&) : void>) { - var val = __builtin_table_find(Tab, at) - if (val != null) { - __builtin_table_lock_mutable(Tab) - invoke(blk, *(unsafe(reinterpret val))) - __builtin_table_unlock_mutable(Tab) - return true - } else { - return false - } -} - -def get(Tab : table ==const; at : keyT | #; blk : block<(p : valT[typeinfo dim_table_value(Tab)]&) : void>) { - var val = __builtin_table_find(Tab, at) - if (val != null) { - __builtin_table_lock_mutable(Tab) - invoke(blk, *(unsafe(reinterpret val))) - __builtin_table_unlock_mutable(Tab) - return true - } else { - return false - } -} - -def get(var Tab : table ==const#; at : keyT | #; blk : block<(var p : valT[typeinfo dim_table_value(Tab)]#&) : void>) { - var val = __builtin_table_find(Tab, at) - if (val != null) { - __builtin_table_lock(Tab) - invoke(blk, *(unsafe(reinterpret val))) - __builtin_table_unlock(Tab) - return true - } else { - return false - } -} - -def get(var Tab : table ==const; at : keyT | #; blk : block<(var p : valT[typeinfo dim_table_value(Tab)]&) : void>) { - var val = __builtin_table_find(Tab, at) - if (val != null) { - __builtin_table_lock(Tab) - invoke(blk, *(unsafe(reinterpret val))) - __builtin_table_unlock(Tab) - return true - } else { - return false - } -} - [unused_argument(Tab, at)] def get(Tab : table; at : keyT | #; blk : block<(var p : void?) : void>) { concept_assert(false, "get(table, ...) is not supported; use 'key_exists' instead") @@ -836,19 +709,6 @@ def get_value(var Tab : table ==const; at : keyT | #) : } } -[unused_argument(Tab, at)] -def get_value(var Tab : table; at : keyT | #) : valT[typeinfo dim_table_value(Tab)] { - static_if (typeinfo is_void(type)) { - concept_assert(false, "can't get value, which is void") - return type - } static_elif (typeinfo can_copy(type)) { - return unsafe(Tab[at]) - } else { - concept_assert(false, "can't get value, which can't be copied") - return type - } -} - def clone_value(var Tab : table>; at : keyT | #) : smart_ptr { unsafe { var t := Tab[at] @@ -891,28 +751,6 @@ def insert_clone(var Tab : table; at : keyT | #; val : v } } -[unused_argument(Tab, at, val)] -def insert_clone(var Tab : table; at : keyT | #; var val : valT[] ==const | #) { - static_if (typeinfo can_clone(val)) { - unsafe(Tab[at]) := val - } else { - concept_assert(false, "can't insert value, which can't be cloned") - } -} - -[unused_argument(Tab, at, val)] -def insert_clone(var Tab : table; at : keyT | #; val : valT[] ==const | #) { - static_if (typeinfo can_clone(val)) { - static_if (typeinfo can_clone_from_const(val)) { - unsafe(Tab[at]) := val - } else { - concept_assert(false, "can't insert value, which can't be cloned from const") - } - } else { - concept_assert(false, "can't insert value, which can't be cloned") - } -} - [unused_argument(Tab, at, val)] def insert(var Tab : table; at : keyT | #; var val : valT ==const | #) { static_if (typeinfo can_copy(type)) { @@ -935,28 +773,6 @@ def insert(var Tab : table; at : keyT | #; val : valT == } } -[unused_argument(Tab, at, val)] -def insert(var Tab : table; at : keyT | #; var val : valT[] ==const | #) { - static_if (typeinfo can_copy(type)) { - unsafe(Tab[at]) = val - } else { - concept_assert(false, "can't insert value, which can't be copied") - } -} - -[unused_argument(Tab, at, val)] -def insert(var Tab : table; at : keyT | #; val : valT[] ==const | #) { - static_if (typeinfo can_copy(type)) { - static_if (typeinfo can_clone_from_const(val)) { - unsafe(Tab[at]) = val - } else { - concept_assert(false, "can't insert value, which can't be cloned from const") - } - } else { - concept_assert(false, "can't insert value, which can't be copied") - } -} - [unused_argument(Tab, at, val)] def emplace(var Tab : table; at : keyT | #; var val : valT -#&) { static_if (typeinfo can_move(val)) { @@ -975,15 +791,6 @@ def emplace(var Tab : table>; at : keyT | #; v } } -[unused_argument(Tab, at, val)] -def emplace(var Tab : table; at : keyT | #; var val : valT[] -#&) { - static_if (typeinfo can_move(val)) { - unsafe(Tab[at]) <- val - } else { - concept_assert(false, "can't emplace value, which can't be moved") - } -} - def emplace_new(var tab : table>; key : kT; var value : smart_ptr) { unsafe(tab[key]) |> move_new <| value } @@ -1138,22 +945,6 @@ def clone(var clone_src : auto(TT) ==const | #) : TT -const -# { } } -def clone(clone_src : auto(TT)[] ==const | #) : TT[typeinfo dim(clone_src)] -const -# { - unsafe { // nolint:STYLE025 var-decl of TT[N] -# is sometimes unsafe (is_unsafe_when_uninitialized types) - var clone_dest : TT[typeinfo dim(clone_src)] -# - clone_dest := clone_src - return <- clone_dest - } -} - -def clone(var clone_src : auto(TT)[] ==const | #) : TT[typeinfo dim(clone_src)] -const -# { - unsafe { // nolint:STYLE025 var-decl of TT[N] -# is sometimes unsafe (is_unsafe_when_uninitialized types) - var clone_dest : TT[typeinfo dim(clone_src)] -# - clone_dest := clone_src - return <- clone_dest - } -} - def clone_to_move(clone_src : auto(TT) ==const | #) : TT -const -# { unsafe { // nolint:STYLE025 var-decl of TT -# is sometimes unsafe (is_unsafe_when_uninitialized types) var clone_dest : TT -# @@ -1261,30 +1052,6 @@ def clone(var a : table; var b : table ==const | # } } - -def clone(var a : table; b : table | #) { - static_if (typeinfo dim_table_value(a) != typeinfo dim_table_value(b)) { - concept_assert(false, "table clone: source and destination tables must have the same value type") - } else { - clear(a) - for (k, v in keys(b), values(b)) { - unsafe(a[k]) := v - } - } -} - -def clone(var a : table; b : table | #) { - static_if (typeinfo dim_table_value(a) != typeinfo dim_table_value(b)) { - concept_assert(false, "table clone: source and destination tables must have the same value type") - } else { - clear(a) - for (k, v in keys(b), values(b)) { - let kk := k - unsafe(a[kk]) := v - } - } -} - [expect_any_vector(args)] def clone(var args; var nargs : array) { let tot = _::length(nargs) @@ -1378,20 +1145,6 @@ def values(var a : table ==const | #) : iterator return <- it } -[unsafe_outside_of_for, nodiscard] -def values(a : table ==const | #) : iterator { - var it : iterator - __builtin_table_values(it, a, typeinfo sizeof(type)) - return <- it -} - -[unsafe_outside_of_for, nodiscard] -def values(var a : table ==const | #) : iterator { - var it : iterator - __builtin_table_values(it, a, typeinfo sizeof(type)) - return <- it -} - def get_key(a : table ==const; value) { concept_assert(false, "can't get_key of a table<...; void>") } @@ -1403,13 +1156,6 @@ def get_key(a : table; value : valT&) : keyT -const { return result } -[nodiscard] -def get_key(a : table; value : valT[typeinfo dim_table_value(a)]) : keyT -const { - var result : keyT -const - __builtin_table_get_key(unsafe(addr(result)), a, unsafe(addr(value)), typeinfo sizeof(type), typeinfo sizeof(type)) - return result -} - def finalize_dim(var a : auto(TT)[]) { static_if (typeinfo can_delete(type)) { for (aV in a) { diff --git a/daslib/style_lint.das b/daslib/style_lint.das index 74832e1937..9031fb14d9 100644 --- a/daslib/style_lint.das +++ b/daslib/style_lint.das @@ -35,7 +35,7 @@ module style_lint shared private //! STYLE027 — var array / table with empty default-init followed by a for-loop that only push/insert into it; use a comprehension //! STYLE028 — 'self->method(...)' inside a class method is optional — drop 'self->' and call 'method(...)' directly (compiler auto-promotes to the same invoke) //! STYLE029 — non-public 'require X' used only for modules X re-exports (transitive-only) — require those directly and drop X (skipped when X provides macros or [init]) -//! STYLE030 — non-public 'require X' that is entirely unused — no symbol from X (or anything it re-exports) is referenced; drop it (skipped when X provides any macro or an [init], or only re-exports builtins used through it, or exports a function matching an unresolved call inside an uninstanced generic body). Suppress a deliberate keep with '// nolint:STYLE030' +//! STYLE030 — non-public 'require X' that is entirely unused — no symbol from X (or anything it re-exports) is referenced; drop it (skipped when X provides any macro or an [init], or only re-exports builtins used through it, or X or its public re-export closure exports a function matching an unresolved call inside an uninstanced generic body). Suppress a deliberate keep with '// nolint:STYLE030' require daslib/ast_boost require daslib/is_local @@ -1516,10 +1516,16 @@ class StyleLintVisitor : AstVisitor { } def st030_exports_unresolved_name(mod : Module?) : bool { - // True when `mod` exports a function or generic whose name matches an - // unresolved generic-body call — the require is (potentially) load-bearing - // for instantiations the lint pass never sees. + // True when `mod` — or a module it publicly re-exports (e.g. daslib/rtti + // re-exporting the builtin rtti module) — exports a function or generic whose + // name matches an unresolved generic-body call; the require is (potentially) + // load-bearing for instantiations the lint pass never sees. if (empty(unresolved_call_names)) return false + var seen : table + return st030_exports_unresolved_rec(mod, seen) + } + + def st030_exports_unresolved_rec(mod : Module?; var seen : table) : bool { var found = false for_each_function(mod, "") $(fn) { if (found) return @@ -1534,6 +1540,16 @@ class StyleLintVisitor : AstVisitor { found = true } } + if (found) return true + module_for_each_dependency(mod) $(dep, is_pub) { + if (found || !is_pub || dep == null) return + let dn = "{dep.name}" + return if (dn == "" || dn == "$" || dn == "builtin" || key_exists(seen, dn)) + seen |> insert(dn) + if (st030_exports_unresolved_rec(dep, seen)) { + found = true + } + } return found } diff --git a/src/ast/ast_infer_type_make.cpp b/src/ast/ast_infer_type_make.cpp index cc3704b812..fc5811107f 100644 --- a/src/ast/ast_infer_type_make.cpp +++ b/src/ast/ast_infer_type_make.cpp @@ -534,10 +534,11 @@ namespace das { expr->at, CompilationError::invalid_structure_type); } } - if (expr->makeType->baseType==Type::tFixedArray && expr->makeType->firstType->baseType==Type::tFixedArray) { + // zero provided elements (default, [[T[N]]]) means "zero/default-init the whole array" - any depth + if (expr->makeType->baseType==Type::tFixedArray && expr->makeType->firstType->baseType==Type::tFixedArray && expr->structs.size()) { error("[[" + describeType(expr->makeType) + "]] struct can only initialize single dimension arrays", "", "", expr->at, CompilationError::invalid_structure_array); - } else if (expr->makeType->baseType==Type::tFixedArray && expr->makeType->fixedDim != int32_t(expr->structs.size())) { + } else if (expr->makeType->baseType==Type::tFixedArray && expr->structs.size() && expr->makeType->fixedDim != int32_t(expr->structs.size())) { error("[[" + describeType(expr->makeType) + "]] struct dimension mismatch, provided " + to_string(expr->structs.size()) + " elements, expecting " + to_string(expr->makeType->fixedDim), "", "", diff --git a/src/ast/ast_simulate.cpp b/src/ast/ast_simulate.cpp index 9d02138526..823d0b9b31 100644 --- a/src/ast/ast_simulate.cpp +++ b/src/ast/ast_simulate.cpp @@ -868,7 +868,9 @@ namespace das bool emptyEmbeddedTuple = ( mkBaseT->baseType==Type::tTuple && total==0); bool partialyInitStruct = !mks->doesNotNeedInit && !mks->initAllFields; if ( (emptyEmbeddedTuple || partialyInitStruct) && stride ) { - int bytes = das::max(total,1) * stride; + // zero provided elements means zero-init the WHOLE declared shape (default) - + // stride is one outer element, which under-counts fixed-array makeTypes + int bytes = total ? total * stride : int(mks->makeType->getSizeOf()); SimNode * init0; if ( mks->useCMRES ) { if ( bytes==0 ) { diff --git a/tests/fixed_array/_target_inference.das b/tests/fixed_array/test_target_inference.das similarity index 54% rename from tests/fixed_array/_target_inference.das rename to tests/fixed_array/test_target_inference.das index 54f71c58b8..c5c83d2388 100644 --- a/tests/fixed_array/_target_inference.das +++ b/tests/fixed_array/test_target_inference.das @@ -1,23 +1,10 @@ // TARGET SPEC for the tFixedArray rework (FIXED_ARRAY_REWORK.md) — Part B. -// Leading underscore: dastest SKIPS this file. Stage 1 renames it to -// test_target_inference.das when the new inference semantics land. Until then this -// file is the reviewed acceptance criteria; it does NOT compile/pass on master. -// -// New semantics (agreed, no compat shim): +// Natural generic-alias semantics: // auto(TT) <- int[4] => TT = int[4] (whole array, like array) // auto(TT)[] <- float[4][4] => TT = float[4] (peel one level) // auto(TT)[] <- M4[10] => TT = M4 (alias preserved) // default after FA binding => zeroed FA -// Exact const decoration in the TT strings ("int[4]" vs "int const[4]") to be -// settled at the Stage 1 review — adjust the expected strings then. -// -// The `expect` line below documents how master FAILS this spec today (and keeps -// lint/CI off the file until then; dastest already skips it via the `_` prefix): -// 30192 — auto(TT) <- int[4] binds TT=int, so `for (x in default)` iterates an int -// 30341 x2 — auto(TT)[] can't take float[4][4]; get_key(table) can't resolve -// Stage 1: delete the `expect` line and rename the file to test_target_inference.das. options gen2 -expect 30192, 30341:2 require dastest/testing_boost public typedef M4 = float[4][4] @@ -33,7 +20,7 @@ def dim_name(a : auto(TT)[]) : string { def zeroed_count(a : auto(TT)) : int { var z = default var n = 0 - for (x in z) { + for (_x in z) { n ++ } return n @@ -50,11 +37,11 @@ def pick(a : auto(TT)[]) : string { [test] def test_whole_array_binding(t : T?) { t |> run("auto(TT) binds the whole fixed array") @(t : T?) { - var i4 : int[4] + let i4 : int[4] t |> equal(plain_name(i4), "int const[4]") } t |> run("default is a zeroed FA") @(t : T?) { - var i4 : int[4] + let i4 : int[4] t |> equal(zeroed_count(i4), 4) } } @@ -62,36 +49,41 @@ def test_whole_array_binding(t : T?) { [test] def test_peel_one_level(t : T?) { t |> run("auto(TT)[] peels one level off multi-dim") @(t : T?) { - var m : float[4][4] - t |> equal(dim_name(m), "float const[4]") + // note the asymmetry vs whole-binding: plain auto(TT) keeps the param's constness + // on TT ("int const[4]"); the [] form consumed the chain head where qualifiers + // live, so the peeled element binds unqualified + let m : float[4][4] + t |> equal(dim_name(m), "float[4]") } t |> run("multi-dim now prefers the dim overload") @(t : T?) { // characterized on master as "plain" (test_generics_current.das documents // the old behavior); the rework makes auto[] match and win - var m : float[2][3] + let m : float[2][3] t |> equal(pick(m), "dim") } t |> run("1D keeps preferring the dim overload") @(t : T?) { - var i4 : int[4] + let i4 : int[4] t |> equal(pick(i4), "dim") } } -// NOTE: unlike the rest of this file (enabled at Stage 1), this subtest is gated on -// STAGE 4 — the broken builtin.das:1407 overload is deleted (not fixed) there, and only -// then does the base get_key generic cover FA values. If Stage 1 enablement trips on it, -// split it out and keep it disabled until Stage 4. [test] def test_get_key_fixed(t : T?) { t |> run("get_key on table resolves") @(t : T?) { - // broken on master: builtin.das:1407 fails to resolve valT const[-2] + // master's dedicated FA overload (old builtin.das:1407) failed to resolve; + // deleted in 1g — the base get_key generic covers FA values naturally. + // get_key is address-based: the value must be a reference INTO the table slot var tab : table var v : int[4] for (i in range(4)) { v[i] = i + 1 } tab |> insert("a", v) - t |> equal(get_key(tab, v), "a") + var found = "" + for (vv in values(tab)) { + found = get_key(tab, vv) + } + t |> equal(found, "a") delete tab } } From 75ad9e6f57853313b12dcde5ced298575de2c5b3 Mon Sep 17 00:00:00 2001 From: Boris Batkin Date: Wed, 10 Jun 2026 16:40:08 -0700 Subject: [PATCH 12/23] fixed-array stage 2: aot_cpp structural port (natural TDim recursion); fix #3077 native-dim field pun Co-Authored-By: Claude Fable 5 --- FIXED_ARRAY_REWORK.md | 22 ++++++ daslib/aot_cpp.das | 120 ++++++++++++++--------------- tests/aot/CMakeLists.txt | 3 - tests/fixed_array/test_interop.das | 27 +++++-- 4 files changed, 102 insertions(+), 70 deletions(-) diff --git a/FIXED_ARRAY_REWORK.md b/FIXED_ARRAY_REWORK.md index cb9a54f963..ad3e4b27ff 100644 --- a/FIXED_ARRAY_REWORK.md +++ b/FIXED_ARRAY_REWORK.md @@ -344,6 +344,28 @@ recursion; AOT version bump. Also fixes issue #3077 (iterate/pass/copy of a NATI 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` 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>::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. ### Stage 3 — JIT `modules/dasLLVM/daslib/llvm_jit.das` port (element type, bounds checks, loop ranges from diff --git a/daslib/aot_cpp.das b/daslib/aot_cpp.das index 493bc7e5be..fc7e86a832 100644 --- a/daslib/aot_cpp.das +++ b/daslib/aot_cpp.das @@ -149,7 +149,7 @@ def das_to_cppCTypeString(t : Type) { def isConstRedundantForCpp(typeDecl : TypeDeclPtr) { - if (!empty(typeDecl.dim)) return false; + if (typeDecl.baseType == Type.tFixedArray) return false; if (typeDecl.isVectorType) return true; match (typeDecl.baseType) { if (Type.tBool || Type.tInt8 || Type.tUInt8 || Type.tInt16 || Type.tUInt16 || @@ -303,19 +303,13 @@ def describeCppTypeEx(typeDecl : TypeDeclPtr; cfg.skip_const = true; } } - // dims live on the tFixedArray chain head (the .dim compat view flattens them); - // element kind, sub-types and annotations dispatch on the chain ELEMENT - var elemDecl = typeDecl - while (elemDecl.baseType == Type.tFixedArray && elemDecl.firstType != null) { - elemDecl = elemDecl.firstType - } - let baseType = elemDecl.baseType; + let baseType = typeDecl.baseType; return build_string() $(writer) { - for (_ in range(length(typeDecl.dim))) { - write(writer, "TDim<"); - } - if (useAlias == CpptUseAlias.yes && typeDecl.flags.aotAlias && !typeDecl.alias.empty()) { + if (baseType == Type.tFixedArray && typeDecl.firstType != null) { + // natural recursion; inner chain nodes are canonically bare (qualifiers live on the head) + write(writer, "TDim<{describeCppTypeEx(typeDecl.firstType,DescribeConfig(cross_platform=cfg.cross_platform),useAlias)},{typeDecl.fixedDim}>") + } elif (useAlias == CpptUseAlias.yes && typeDecl.flags.aotAlias && !typeDecl.alias.empty()) { write(writer, "{typeDecl.alias}"); } elif (baseType == Type.alias) { write(writer, "DAS_COMMENT(alias)"); @@ -326,104 +320,104 @@ def describeCppTypeEx(typeDecl : TypeDeclPtr; } write(writer, "DAS_COMMENT(auto{alias})") } elif (baseType == Type.tHandle) { - let handle_name = string(elemDecl.annotation.cppName.empty() ? elemDecl.annotation.name : elemDecl.annotation.cppName) + let handle_name = string(typeDecl.annotation.cppName.empty() ? typeDecl.annotation.name : typeDecl.annotation.cppName) write(writer, "{handle_name}") } elif (baseType == Type.tArray) { - if (elemDecl.firstType != null) { - let cpp_typeDecl = describeCppTypeEx(elemDecl.firstType, DescribeConfig(cross_platform = cfg.cross_platform), useAlias); + if (typeDecl.firstType != null) { + let cpp_typeDecl = describeCppTypeEx(typeDecl.firstType, DescribeConfig(cross_platform = cfg.cross_platform), useAlias); write(writer, "TArray<{cpp_typeDecl}>") } else { write(writer, "Array") } } elif (baseType == Type.tTable) { - if (elemDecl.firstType != null && elemDecl.secondType != null) { - let first_type = describeCppTypeEx(elemDecl.firstType, DescribeConfig(skip_const = true, cross_platform = cfg.cross_platform), useAlias); - let second_type = describeCppTypeEx(elemDecl.secondType, DescribeConfig(cross_platform = cfg.cross_platform), useAlias); + if (typeDecl.firstType != null && typeDecl.secondType != null) { + let first_type = describeCppTypeEx(typeDecl.firstType, DescribeConfig(skip_const = true, cross_platform = cfg.cross_platform), useAlias); + let second_type = describeCppTypeEx(typeDecl.secondType, DescribeConfig(cross_platform = cfg.cross_platform), useAlias); write(writer, "TTable<{first_type},{second_type}>"); } else { write(writer, "Table"); } } elif (baseType == Type.tTuple) { - let types = (each(elemDecl.argTypes) + let types = (each(typeDecl.argTypes) ._select(describeCppTypeEx(_, DescribeConfig(cross_platform = cfg.cross_platform), useAlias)) .to_array() ._fold()) |> join(",") if (cfg.cross_platform) { write(writer, "AutoTuple<{types}>") } else { - write(writer, "TTuple<{elemDecl.tupleSize},{types}>") + write(writer, "TTuple<{typeDecl.tupleSize},{types}>") } } elif (baseType == Type.tVariant) { - let types = (each(elemDecl.argTypes) + let types = (each(typeDecl.argTypes) ._select(describeCppTypeEx(_, DescribeConfig(cross_platform = cfg.cross_platform), useAlias)) .to_array() ._fold()) |> join(",") if (cfg.cross_platform) { write(writer, "AutoVariant<{types}>"); } else { - write(writer, "TVariant<{elemDecl.variantSize},{elemDecl.variantAlign},{types}>") + write(writer, "TVariant<{typeDecl.variantSize},{typeDecl.variantAlign},{types}>") } } elif (baseType == Type.tStructure) { - if (elemDecl.structType != null) { + if (typeDecl.structType != null) { // Elaborated-type-specifier (`struct X`) keeps GCC's -Wchanges-meaning // happy when a field name shadows the struct name in the same scope — // e.g. `struct User { TTuple<...,struct Profile> Profile; }`. Valid in // every C++ context where a bare struct name is, including template args // and qualified names. - if (elemDecl.structType._module.name.empty()) { - write(writer, "struct {aotStructName(elemDecl.structType)}") + if (typeDecl.structType._module.name.empty()) { + write(writer, "struct {aotStructName(typeDecl.structType)}") } else { - write(writer, "struct {aotModuleName(elemDecl.structType._module)}::{aotStructName(elemDecl.structType)}") + write(writer, "struct {aotModuleName(typeDecl.structType._module)}::{aotStructName(typeDecl.structType)}") } } else { write(writer, "DAS_COMMENT(unspecified structure) "); } } elif (baseType == Type.tPointer) { - if (!elemDecl.flags.smartPtr) { - if (elemDecl.firstType != null) { - write(writer, "{describeCppTypeEx(elemDecl.firstType,DescribeConfig(redundant_const=false,cross_platform=cfg.cross_platform),useAlias)} *"); + if (!typeDecl.flags.smartPtr) { + if (typeDecl.firstType != null) { + write(writer, "{describeCppTypeEx(typeDecl.firstType,DescribeConfig(redundant_const=false,cross_platform=cfg.cross_platform),useAlias)} *"); } else { write(writer, "void *"); } } else { - let ptr_name = elemDecl.flags.smartPtrNative && cfg.use_smart_ptr ? "smart_ptr" : "smart_ptr_raw" + let ptr_name = typeDecl.flags.smartPtrNative && cfg.use_smart_ptr ? "smart_ptr" : "smart_ptr_raw" var type_decl_name = "void" - if (elemDecl.firstType != null) { - type_decl_name = describeCppTypeEx(elemDecl.firstType, DescribeConfig(redundant_const = false, cross_platform = cfg.cross_platform), useAlias); + if (typeDecl.firstType != null) { + type_decl_name = describeCppTypeEx(typeDecl.firstType, DescribeConfig(redundant_const = false, cross_platform = cfg.cross_platform), useAlias); } write(writer, "{ptr_name}<{type_decl_name}>"); } - } elif (elemDecl.isEnumT) { - if (elemDecl.enumType != null) { - if (elemDecl.enumType.external) { - write(writer, "DAS_COMMENT(bound_enum) {elemDecl.enumType.cppName}"); - } elif (elemDecl.enumType._module.name.empty()) { - write(writer, "DAS_COMMENT(enum) {aotEnumName(elemDecl.enumType)}"); + } elif (typeDecl.isEnumT) { + if (typeDecl.enumType != null) { + if (typeDecl.enumType.external) { + write(writer, "DAS_COMMENT(bound_enum) {typeDecl.enumType.cppName}"); + } elif (typeDecl.enumType._module.name.empty()) { + write(writer, "DAS_COMMENT(enum) {aotEnumName(typeDecl.enumType)}"); } else { - write(writer, "DAS_COMMENT(enum) {aotModuleName(elemDecl.enumType._module)}::{aotEnumName(elemDecl.enumType)}") + write(writer, "DAS_COMMENT(enum) {aotModuleName(typeDecl.enumType._module)}::{aotEnumName(typeDecl.enumType)}") } } else { write(writer, "DAS_COMMENT(unspecified enumeration)") } } elif (baseType == Type.tIterator) { - if (elemDecl.firstType != null) { + if (typeDecl.firstType != null) { let new_cfg = DescribeConfig(substitute_ref = cfg.substitute_ref, skip_ref = cfg.skip_ref, skip_const = cfg.skip_const, redundant_const = true, cross_platform = cfg.cross_platform) - write(writer, "Sequence DAS_COMMENT(({describeCppTypeEx(elemDecl.firstType,new_cfg,useAlias)}))") + write(writer, "Sequence DAS_COMMENT(({describeCppTypeEx(typeDecl.firstType,new_cfg,useAlias)}))") } else { write(writer, "Sequence") } } elif (baseType == Type.tBlock || baseType == Type.tFunction || baseType == Type.tLambda) { let maybe_const = !typeDecl.flags.constant && baseType == Type.tBlock ? "const " : "" var type_name = "void" - if (elemDecl.firstType != null) { - type_name = describeCppTypeEx(elemDecl.firstType, DescribeConfig(redundant_const = true, cross_platform = cfg.cross_platform), useAlias) + if (typeDecl.firstType != null) { + type_name = describeCppTypeEx(typeDecl.firstType, DescribeConfig(redundant_const = true, cross_platform = cfg.cross_platform), useAlias) } - let extra_comma = !empty(elemDecl.argTypes) ? "," : ""; - let arg_types = (each(elemDecl.argTypes) + let extra_comma = !empty(typeDecl.argTypes) ? "," : ""; + let arg_types = (each(typeDecl.argTypes) ._select(describeCppTypeEx(_, DescribeConfig(redundant_const = true, cross_platform = cfg.cross_platform), useAlias)) .to_array() ._fold()) |> join(",") @@ -431,13 +425,6 @@ def describeCppTypeEx(typeDecl : TypeDeclPtr; } else { write(writer, das_to_cppString(baseType)); } - // dim is stored outer-first; C++ TDim,outer> emits dims - // inner-first, so walk dim in reverse. - let dim_count = length(typeDecl.dim) - for (i in 0..dim_count) { - write(writer, ",{typeDecl.dim[dim_count - 1 - i]}>") - } - if (!cfg.skip_const && typeDecl.flags.constant) { write(writer, " const "); } @@ -968,8 +955,8 @@ class public AotDebugInfoHelper { }; def public isLocalVec(vtype : TypeDeclPtr) { - //! Returns true if the type is a non-reference vector type without dimensions. - return empty(vtype.dim) && vtype.isVectorType && !vtype.flags.ref; + //! Returns true if the type is a non-reference vector type. + return vtype.isVectorType && !vtype.flags.ref; } def public describeLocalCppType(var writer : StringBuilderWriter?; vtype : TypeDeclPtr; cross_platform : bool; substituteRef : CpptSubstitureRef = CpptSubstitureRef.yes; skipConst : CpptSkipConst = CpptSkipConst.no) { @@ -2232,6 +2219,10 @@ class public CppAot : AstVisitor { write(*ss, "(({describeCppType(field._type, DescribeConfig(cross_platform=cross_platform))})("); } } + if (field._type.flags.isNativeDim) { + // native C-array member: pun to the TDim view so iterate/pass/copy/safe-at consumers compile (issue #3077) + write(*ss, "das_reinterpret<{describeCppType(field._type, DescribeConfig(skip_ref=true,cross_platform=cross_platform))}>::pass("); + } aot_previsit_get_field(field_type.annotation, ss, string(field.name)); } elif (field_type.baseType == Type.tPointer) { if (field_type.firstType.isHandle) { @@ -2243,6 +2234,9 @@ class public CppAot : AstVisitor { write(*ss, "(({describeCppType(field._type, DescribeConfig(cross_platform=cross_platform))})("); } } + if (field._type.flags.isNativeDim) { + write(*ss, "das_reinterpret<{describeCppType(field._type, DescribeConfig(skip_ref=true,cross_platform=cross_platform))}>::pass("); + } aot_previsit_get_field_ptr(field_type.firstType.annotation, ss, string(field.name)); } elif (field.value._type.firstType.isTuple) { write(*ss, "{get_tuple_field(field_type.firstType, field.fieldIndex, true)}::get(") @@ -2264,6 +2258,9 @@ class public CppAot : AstVisitor { if (field._type.isString) { write(*ss, "))"); } + if (field._type.flags.isNativeDim) { + write(*ss, ")"); + } } elif (field.value._type.baseType == Type.tPointer) { if (field.value._type.firstType.isHandle) { aot_type_ann_get_field_ptr(field.value._type.firstType.annotation, ss, string(field.name)); @@ -2271,6 +2268,9 @@ class public CppAot : AstVisitor { if (field._type.isString) { write(*ss, "))") } + if (field._type.flags.isNativeDim) { + write(*ss, ")"); + } } elif (field.value._type.firstType.isTuple) { write(*ss, ")"); } elif (field.value._type.firstType.isVariant) { @@ -2291,13 +2291,13 @@ class public CppAot : AstVisitor { if (expr._type.flags.aotAlias) { write(*ss, "das_alias<{expr._type.alias}>::from("); } - if (!(!expr.subexpr._type.dim |> empty() || expr.subexpr._type.isGoodArrayType || expr.subexpr._type.isGoodTableType)) { + if (!(expr.subexpr._type.baseType == Type.tFixedArray || expr.subexpr._type.isGoodArrayType || expr.subexpr._type.isGoodTableType)) { let type_str = describeCppType(expr.subexpr._type, DescribeConfig(skip_ref = true, cross_platform = cross_platform)); write(*ss, "das_index<{type_str}>::at("); } } def override preVisitExprAtIndex(var expr : ExprAt?; index : ExpressionPtr) { - if (!expr.subexpr._type.dim |> empty() || expr.subexpr._type.isGoodArrayType || expr.subexpr._type.isGoodTableType) { + if (expr.subexpr._type.baseType == Type.tFixedArray || expr.subexpr._type.isGoodArrayType || expr.subexpr._type.isGoodTableType) { if (expr.subexpr._type.flags.isNativeDim) { write(*ss, "["); } else { @@ -2323,7 +2323,7 @@ class public CppAot : AstVisitor { def override preVisitExprSafeAt(expr : ExprSafeAt?) { let isPtr : bool = expr.subexpr._type.isPointer; assume seT = isPtr ? expr.subexpr._type.firstType : expr.subexpr._type; - if ((!seT.dim |> empty() || seT.isGoodArrayType || seT.isGoodTableType)) { + if ((seT.baseType == Type.tFixedArray || seT.isGoodArrayType || seT.isGoodTableType)) { let type_str = describeCppType(seT, DescribeConfig(skip_ref = true, skip_const = true, cross_platform = cross_platform)) write(*ss, "{type_str}::safe_index("); } elif (isPtr && !seT.isVectorType) { @@ -2915,11 +2915,11 @@ class public CppAot : AstVisitor { write(*ss, "(memset((void*)&{nm},0,sizeof({nm})), &{nm}"); return } - if (!enew._type.dim |> empty()) { + if (enew._type.baseType == Type.tFixedArray) { let ptrType = fa_element(enew._type) // the fixed-array chain wraps the pointer node; pointee is its firstType if (ptrType.firstType.isHandle) { let type_str = describeCppType(ptrType.firstType, DescribeConfig(skip_ref = true, skip_const = true, cross_platform = cross_platform)); - write(*ss, "das_new_dim_handle<{type_str},{enew._type.dim[0]},{ptrType.flags.smartPtr}"); + write(*ss, "das_new_dim_handle<{type_str},{enew._type.fixedDim},{ptrType.flags.smartPtr}"); if (enew.initializer) { panic("internal error. initializer for enew is not supported"); } else { @@ -2927,7 +2927,7 @@ class public CppAot : AstVisitor { } } else { let type_str = describeCppType(ptrType.firstType, DescribeConfig(skip_ref = true, skip_const = true, cross_platform = cross_platform)); - write(*ss, "das_new_dim<{type_str},{enew._type.dim[0]}"); + write(*ss, "das_new_dim<{type_str},{enew._type.fixedDim}"); if (enew.initializer) { write(*ss, ">::make_and_init(__context__,[&]() \{ return "); CallFunc_preVisit(enew); diff --git a/tests/aot/CMakeLists.txt b/tests/aot/CMakeLists.txt index 39ef358018..c395c6ee37 100644 --- a/tests/aot/CMakeLists.txt +++ b/tests/aot/CMakeLists.txt @@ -423,9 +423,6 @@ FILE(GLOB AOT_TABLE_PACKED_FILES RELATIVE ${PROJECT_SOURCE_DIR} CONFIGURE_DEPEND FILE(GLOB AOT_FIXED_ARRAY_FILES RELATIVE ${PROJECT_SOURCE_DIR} CONFIGURE_DEPENDS "tests/fixed_array/*.das") # Exclude failed_* expect-fixtures and _-prefixed target-spec files (enabled at Stage 1) list(FILTER AOT_FIXED_ARRAY_FILES EXCLUDE REGEX "failed_|/_") -# test_interop: AOT emitter produces non-compiling C++ for iterate/pass/copy of a native -# C-array field (issue #3077) — interpreted-only until fixed; re-include to verify the fix -list(FILTER AOT_FIXED_ARRAY_FILES EXCLUDE REGEX "test_interop") # AOT for typemacro test files FILE(GLOB AOT_TYPEMACRO_FILES RELATIVE ${PROJECT_SOURCE_DIR} CONFIGURE_DEPENDS "tests/typemacro/*.das") diff --git a/tests/fixed_array/test_interop.das b/tests/fixed_array/test_interop.das index 1299106a1e..7a152d0829 100644 --- a/tests/fixed_array/test_interop.das +++ b/tests/fixed_array/test_interop.das @@ -1,14 +1,10 @@ // Stage 0 characterization for the tFixedArray rework (FIXED_ARRAY_REWORK.md). // Pins C++ interop: a handled type's native C-array field (TestObjectFoo.fooArray, // bound via typeFactory with isNativeDim) must keep reading/writing and -// keep its type identity through the representation flip. -// INTERPRETED-ONLY (options no_aot + excluded from the AOT glob in -// tests/aot/CMakeLists.txt): iterate/pass/copy of a native dim field emits -// non-compiling C++ on master — issue #3077. Both markers go together: the glob -// exclusion skips stub generation, no_aot keeps test_aot's fail_on_no_aot off -// this file at runtime. Remove BOTH to verify the fix. +// keep its type identity through the representation flip. AOT-included: iterate/ +// pass/copy/safe-at of a native dim field is the issue #3077 surface, fixed at +// Stage 2 by punning the member access to its TDim view. options gen2 -options no_aot require UnitTest require dastest/testing_boost public @@ -54,6 +50,23 @@ def test_native_dim_field(t : T?) { foo.fooArray[1] = 0 t |> equal(local[1], 42) } + t |> run("native field copies in from das FA") @(t : T?) { + var foo : TestObjectFoo + var local : int[3] + for (i in range(3)) { + local[i] = (i + 1) * 7 + } + foo.fooArray = local + t |> equal(foo.fooArray[0], 7) + t |> equal(foo.fooArray[1], 14) + t |> equal(foo.fooArray[2], 21) + } + t |> run("native field safe-at") @(t : T?) { + var foo : TestObjectFoo + foo.fooArray[1] = 55 + t |> equal(foo.fooArray?[1] ?? -1, 55) + t |> equal(foo.fooArray?[7] ?? -1, -1) + } } def sum_unsized(a : int[]) : int { From b9ffb2a06621c3da7a772f5005abd7090e08bf83 Mon Sep 17 00:00:00 2001 From: Boris Batkin Date: Wed, 10 Jun 2026 17:49:24 -0700 Subject: [PATCH 13/23] fixed-array: auto(TT)[] binding inherits param constness (match plain auto, master parity) Co-Authored-By: Claude Fable 5 --- FIXED_ARRAY_REWORK.md | 31 ++++++++++++++++++--- include/daScript/ast/ast_infer_type.h | 4 +-- include/daScript/ast/ast_typedecl.h | 2 +- src/ast/ast_infer_type_helper.cpp | 24 ++++++++-------- src/ast/ast_typedecl.cpp | 14 ++++++++-- tests/fixed_array/test_target_inference.das | 14 +++++++--- 6 files changed, 65 insertions(+), 24 deletions(-) diff --git a/FIXED_ARRAY_REWORK.md b/FIXED_ARRAY_REWORK.md index ad3e4b27ff..64e67b5c84 100644 --- a/FIXED_ARRAY_REWORK.md +++ b/FIXED_ARRAY_REWORK.md @@ -331,10 +331,10 @@ Exit: full CI green with aot_cpp.das / llvm_jit.das / macro daslib UNMODIFIED (r (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). Settled decoration: plain - `auto(TT)` ← int[4] binds `int const[4]` (param constness rides TT); `auto(TT)[]` ← - float[4][4] binds `float[4]` UNQUALIFIED — the `[]` consumed the chain head where - qualifiers live. Asymmetric but harmless (generics strip with -const anyway). + line dropped; picked up by the AOT glob automatically). Decoration as landed in 1g: + plain `auto(TT)` ← int[4] bound `int const[4]`, but `auto(TT)[]` ← float[4][4] bound + `float[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. @@ -366,6 +366,25 @@ vs pre-port snapshot = ZERO content changes (28 files differ reorder-only — kn 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) — +Boris's call, not landed here; the test exercises its var param instead (mutable row +copy through the peeled binding, a real assertion). ### Stage 3 — JIT `modules/dasLLVM/daslib/llvm_jit.das` port (element type, bounds checks, loop ranges from @@ -405,6 +424,10 @@ Stage 1 breaks its COMPILE, so the field-deletion commit carries the minimal mec 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. +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. + ## Standing risk ledger (checked at every stage gate) 1. **const/ref/temporary propagation through FA levels** — the subtle-bug budget; audited diff --git a/include/daScript/ast/ast_infer_type.h b/include/daScript/ast/ast_infer_type.h index 99791d8d08..e24c938275 100644 --- a/include/daScript/ast/ast_infer_type.h +++ b/include/daScript/ast/ast_infer_type.h @@ -119,11 +119,11 @@ namespace das { // find type alias name, and resolve it to type // without one generic function - TypeDeclPtr findFuncAlias(const FunctionPtr &fptr, const string &name) const; + TypeDeclPtr findFuncAlias(const FunctionPtr &fptr, const string &name, bool *constUnderDim = nullptr) const; // find type alias name, and resolve it to type // within current context - TypeDeclPtr findAlias(const string &name) const; + TypeDeclPtr findAlias(const string &name, bool *constUnderDim = nullptr) const; // infer alias type TypeDeclPtr inferAlias(const TypeDeclPtr &decl, const FunctionPtr &fptr = nullptr, AliasMap *aliases = nullptr, OptionsMap *options = nullptr, bool autoToAlias = false) const; diff --git a/include/daScript/ast/ast_typedecl.h b/include/daScript/ast/ast_typedecl.h index 3a2d31560b..7f5217151e 100644 --- a/include/daScript/ast/ast_typedecl.h +++ b/include/daScript/ast/ast_typedecl.h @@ -225,7 +225,7 @@ namespace das { static void applyRefToRef ( const TypeDeclPtr & TT, bool topLevel = false ); static void updateAliasMap ( const TypeDeclPtr & decl, const TypeDeclPtr & pass, AliasMap & aliases, OptionsMap & options ); Type getRangeBaseType() const; - TypeDecl * findAlias ( const string & name, bool allowAuto = false ); + TypeDecl * findAlias ( const string & name, bool allowAuto = false, bool * constUnderDim = nullptr ); bool computeAliasCache(); // eager full walk, populates aliasCacheValid/aliasCacheHasAlias on every visited node; returns true if subtree contains any alias int findArgumentIndex(const string & name) const; int tupleFieldIndex( const string & name ) const; diff --git a/src/ast/ast_infer_type_helper.cpp b/src/ast/ast_infer_type_helper.cpp index 203a4e261d..74ad5d77c6 100644 --- a/src/ast/ast_infer_type_helper.cpp +++ b/src/ast/ast_infer_type_helper.cpp @@ -291,13 +291,13 @@ namespace das { subexprType->temporary = false; // array -> int } } - TypeDeclPtr InferTypes::findFuncAlias(const FunctionPtr &fptr, const string &name) const { + TypeDeclPtr InferTypes::findFuncAlias(const FunctionPtr &fptr, const string &name, bool *constUnderDim) const { for (auto &arg : fptr->arguments) { - if (auto aT = arg->type->findAlias(name, true)) { + if (auto aT = arg->type->findAlias(name, true, constUnderDim)) { return aT; } } - if (auto rT = fptr->result->findAlias(name, true)) { + if (auto rT = fptr->result->findAlias(name, true, constUnderDim)) { return rT; } TypeDeclPtr rT = nullptr; @@ -312,7 +312,7 @@ namespace das { TypeDeclPtr mtd = program->makeTypeDeclaration(LineInfo(), name); return (!mtd || mtd->isAlias()) ? nullptr : mtd; } - TypeDeclPtr InferTypes::findAlias(const string &name) const { + TypeDeclPtr InferTypes::findAlias(const string &name, bool *constUnderDim) const { if (func) { for (auto &ast : assumeType) { if (ast->alias == name) { @@ -321,16 +321,16 @@ namespace das { } for (auto it = local.rbegin(), its = local.rend(); it != its; ++it) { auto &var = *it; - if (auto vT = var->type->findAlias(name)) { + if (auto vT = var->type->findAlias(name, false, constUnderDim)) { return vT; } } for (auto &arg : func->arguments) { - if (auto aT = arg->type->findAlias(name)) { + if (auto aT = arg->type->findAlias(name, false, constUnderDim)) { return aT; } } - if (auto rT = func->result->findAlias(name, true)) { + if (auto rT = func->result->findAlias(name, true, constUnderDim)) { return rT; } } @@ -393,6 +393,7 @@ namespace das { if (decl->isTag) return nullptr; // we can never infer a tag type TypeDeclPtr aT = nullptr; + bool constUnderDim = false; if (aliases) { auto it = aliases->find(decl->alias); if (it != aliases->end()) { @@ -400,7 +401,7 @@ namespace das { } } if (!aT) { - aT = fptr ? findFuncAlias(fptr, decl->alias) : findAlias(decl->alias); + aT = fptr ? findFuncAlias(fptr, decl->alias, &constUnderDim) : findAlias(decl->alias, &constUnderDim); } if (!aT) { auto bT = nameToBasicType(decl->alias); @@ -415,7 +416,7 @@ namespace das { auto resT = new TypeDecl(*aT); resT->at = decl->at; resT->ref = (resT->ref || decl->ref) && !decl->removeRef; - resT->constant = (resT->constant || decl->constant) && !decl->removeConstant; + resT->constant = (resT->constant || constUnderDim || decl->constant) && !decl->removeConstant; resT->temporary = (resT->temporary || decl->temporary) && !decl->removeTemporary; resT->dim = decl->dim; resT->aotAlias = false; @@ -534,6 +535,7 @@ namespace das { } if (decl->baseType == Type::alias) { TypeDeclPtr aT = nullptr; + bool constUnderDim = false; if (aliases) { auto it = aliases->find(decl->alias); if (it != aliases->end()) { @@ -541,13 +543,13 @@ namespace das { } } if (!aT) { - aT = fptr ? findFuncAlias(fptr, decl->alias) : findAlias(decl->alias); + aT = fptr ? findFuncAlias(fptr, decl->alias, &constUnderDim) : findAlias(decl->alias, &constUnderDim); } if (aT) { auto resT = new TypeDecl(*aT); resT->at = decl->at; resT->ref = (resT->ref || decl->ref) && !decl->removeRef; - resT->constant = (resT->constant || decl->constant) && !decl->removeConstant; + resT->constant = (resT->constant || constUnderDim || decl->constant) && !decl->removeConstant; resT->temporary = (resT->temporary || decl->temporary) && !decl->removeTemporary; resT->implicit = (resT->implicit || decl->implicit); resT->explicitConst = (resT->explicitConst || decl->explicitConst); diff --git a/src/ast/ast_typedecl.cpp b/src/ast/ast_typedecl.cpp index 3e344edf61..ef7e537c9a 100644 --- a/src/ast/ast_typedecl.cpp +++ b/src/ast/ast_typedecl.cpp @@ -1110,7 +1110,7 @@ namespace das } return dim; } - TypeDecl * TypeDecl::findAlias ( const string & name, bool allowAuto ) { + TypeDecl * TypeDecl::findAlias ( const string & name, bool allowAuto, bool * constUnderDim ) { if (!aliasCacheValid) computeAliasCache(); if (!aliasCacheHasAlias) return nullptr; // proven no aliases anywhere if (baseType == Type::alias) { @@ -1121,7 +1121,17 @@ namespace das return this; } if ( firstType ) { - if ( auto k = firstType->findAlias(name,allowAuto) ) { + if ( auto k = firstType->findAlias(name,allowAuto,constUnderDim) ) { + // a binding peeled off a fixed-array chain (auto(TT)[]) inherits the head's + // constness - canonical form keeps inner nodes bare, but the flattened world + // kept label+const+dims on one node. only the pure FA chain transfers; + // a container boundary (array etc) binds bare, as always. + if ( constUnderDim && constant && baseType==Type::tFixedArray ) { + for ( auto t = firstType; t; t = t->firstType ) { + if ( t == k ) { *constUnderDim = true; break; } + if ( t->baseType != Type::tFixedArray ) break; + } + } return k; } } diff --git a/tests/fixed_array/test_target_inference.das b/tests/fixed_array/test_target_inference.das index c5c83d2388..91ac4cd962 100644 --- a/tests/fixed_array/test_target_inference.das +++ b/tests/fixed_array/test_target_inference.das @@ -17,6 +17,11 @@ def dim_name(a : auto(TT)[]) : string { return typeinfo typename(type) } +def dim_name_var(var a : auto(TT)[]) : string { + a[0] = a[1] // exercise the var-ness: mutable row copy through the peeled binding + return typeinfo typename(type) +} + def zeroed_count(a : auto(TT)) : int { var z = default var n = 0 @@ -49,11 +54,12 @@ def test_whole_array_binding(t : T?) { [test] def test_peel_one_level(t : T?) { t |> run("auto(TT)[] peels one level off multi-dim") @(t : T?) { - // note the asymmetry vs whole-binding: plain auto(TT) keeps the param's constness - // on TT ("int const[4]"); the [] form consumed the chain head where qualifiers - // live, so the peeled element binds unqualified + // decoration matches the plain form (settled at Stage 2 review): the peeled + // binding inherits the param's constness — non-var binds const, var binds bare let m : float[4][4] - t |> equal(dim_name(m), "float[4]") + t |> equal(dim_name(m), "float const[4]") + var mv : float[4][4] + t |> equal(dim_name_var(mv), "float[4]") } t |> run("multi-dim now prefers the dim overload") @(t : T?) { // characterized on master as "plain" (test_generics_current.das documents From 37ead955a384f6b1335db64a5b5e2681f981a0dd Mon Sep 17 00:00:00 2001 From: Boris Batkin Date: Wed, 10 Jun 2026 18:21:30 -0700 Subject: [PATCH 14/23] fixed-array stage 3: llvm_jit structural port; new Handle[N] registration-pass fix; JIT codegen version 0x24 Co-Authored-By: Claude Fable 5 --- FIXED_ARRAY_REWORK.md | 23 ++++++++++-- modules/dasLLVM/daslib/llvm_exe.das | 12 +++++-- modules/dasLLVM/daslib/llvm_jit.das | 42 ++++++++++++---------- modules/dasLLVM/daslib/llvm_jit_common.das | 17 +++------ modules/dasLLVM/daslib/llvm_jit_run.das | 2 +- 5 files changed, 60 insertions(+), 36 deletions(-) diff --git a/FIXED_ARRAY_REWORK.md b/FIXED_ARRAY_REWORK.md index 64e67b5c84..2f8cfadb84 100644 --- a/FIXED_ARRAY_REWORK.md +++ b/FIXED_ARRAY_REWORK.md @@ -382,14 +382,33 @@ 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) — -Boris's call, not landed here; the test exercises its var param instead (mutable row +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). ### Stage 3 — JIT `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 new`handle 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 ` 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. ### Stage 4 — daslib payoff ~~Delete the `[]` workaround families in builtin.das~~ + ~~get_key subtest enablement~~ — diff --git a/modules/dasLLVM/daslib/llvm_exe.das b/modules/dasLLVM/daslib/llvm_exe.das index 46f9537047..a643752138 100644 --- a/modules/dasLLVM/daslib/llvm_exe.das +++ b/modules/dasLLVM/daslib/llvm_exe.das @@ -374,11 +374,17 @@ class CollectExternVisitor : AstVisitor { register_new_from_annotation(name, expr.typeexpr.annotation) } } else { - if (!empty(expr.typeexpr.dim)) { - if (expr.typeexpr.baseType == Type.tHandle) { + if (expr.typeexpr.baseType == Type.tFixedArray) { + // the handle gate lives on the chain element (the head is tFixedArray); + // borrow-walk — this pass runs outside ast_gc_guard, clones would leak + var elemT = expr.typeexpr + while (elemT.baseType == Type.tFixedArray && elemT.firstType != null) { + elemT = elemT.firstType + } + if (elemT.baseType == Type.tHandle) { let name = uid.get_new(expr.typeexpr) if (initialize(name)) { - register_new_from_annotation(name, expr.typeexpr.annotation) + register_new_from_annotation(name, elemT.annotation) } } else { if (expr.initializer) { diff --git a/modules/dasLLVM/daslib/llvm_jit.das b/modules/dasLLVM/daslib/llvm_jit.das index 48e1ef9596..ac16dfc40d 100644 --- a/modules/dasLLVM/daslib/llvm_jit.das +++ b/modules/dasLLVM/daslib/llvm_jit.das @@ -1289,7 +1289,7 @@ class public LlvmJitVisitor : AstVisitor { new_ptr = LLVMBuildPointerCast(g_builder, new_ptr, LLVMPointerType(type_to_llvm_type(expr.typeexpr), 0u), "") setE(expr, new_ptr) } else { - if (!empty(expr.typeexpr.dim)) { + if (expr.typeexpr.baseType == Type.tFixedArray) { let count = expr.typeexpr.countOf var arr : LLVMOpaqueValue? at_function_entry() { @@ -1491,7 +1491,7 @@ class public LlvmJitVisitor : AstVisitor { var ptrType = type_to_llvm_abi_type(funcArg._type) var ptrValue = LLVMBuildPointerCast(g_builder, src, ptrType, "arg_{funcArg.name}_struct") params |> push(ptrValue) - } elif ((a._type.baseType == Type.tArray || a._type.baseType == Type.tTable || !empty(a._type.dim)) && (extern_func != null)) { + } elif ((a._type.baseType == Type.tArray || a._type.baseType == Type.tTable || a._type.baseType == Type.tFixedArray) && (extern_func != null)) { var ptrType = type_to_llvm_abi_type(funcArg._type) var arg = LLVMBuildPointerCast(g_builder, src, ptrType, "any_array_{funcArg.name}_ptr") params |> push(arg) @@ -2594,9 +2594,9 @@ class public LlvmJitVisitor : AstVisitor { } else { setE(expr, ptr_idx) } - } elif (!empty(subExprT.dim)) { - let maxIndex = subExprT.dim[0] - var etype = clone_type(subExprT.firstType) // element of the fixed-array chain (the .dim compat view is read-only) + } elif (subExprT.baseType == Type.tFixedArray) { + let maxIndex = subExprT.fixedDim + var etype = clone_type(subExprT.firstType) // element of the fixed-array chain check_range(tidx, types.ConstI32(uint64(maxIndex)), expr.at, "dim index out of range", is_signed_index_type(expr.index._type)) var ptr_idx = build_array_index(tsrc, tidx, etype, "dim_at") if (expr.atFlags.r2v) { @@ -2662,12 +2662,12 @@ class public LlvmJitVisitor : AstVisitor { var tidx = getE(expr.index) assume subExprT = expr.subexpr._type var res : LLVMOpaqueValue?; - if (!empty(subExprT.dim) || (subExprT.isPointer && !empty(subExprT.firstType.dim))) { + if (subExprT.baseType == Type.tFixedArray || (subExprT.isPointer && subExprT.firstType.baseType == Type.tFixedArray)) { // element of the fixed-array chain, behind one pointer level when present - let maxIndex = subExprT.isPointer ? subExprT.firstType.dim[0] : subExprT.dim[0] + let maxIndex = subExprT.isPointer ? subExprT.firstType.fixedDim : subExprT.fixedDim var etype = subExprT.isPointer ? clone_type(subExprT.firstType.firstType) : clone_type(subExprT.firstType) var resType = LLVMPointerType(type_to_llvm_type(etype), 0u) - var if_not_null = empty(subExprT.dim) ? LLVMBuildIsNotNull(g_builder, tsrc, "") : LLVMConstInt(types.t_int1, 1ul, 0) + var if_not_null = subExprT.baseType != Type.tFixedArray ? LLVMBuildIsNotNull(g_builder, tsrc, "") : LLVMConstInt(types.t_int1, 1ul, 0) res = build_select(if_not_null, resType) { var maxIdx = types.ConstI32(uint64(maxIndex)) let (nidx, nmax) = normalize_int_widths(tidx, maxIdx) @@ -3693,13 +3693,13 @@ class public LlvmJitVisitor : AstVisitor { LLVMPositionBuilderAtEnd(g_builder, okay) range2[ssrc] = sto LLVMBuildStore(g_builder, sfrom, getV(svar)) - } elif (!empty(ssrc._type.dim)) {// []->first() - var etype = clone_type(ssrc._type.firstType) // element of the fixed-array chain (the .dim compat view is read-only) + } elif (ssrc._type.baseType == Type.tFixedArray) {// []->first() + var etype = clone_type(ssrc._type.firstType) // element of the fixed-array chain var element_type = type_to_llvm_type(svar._type) var sdim = getE(ssrc) var s0 = LLVMBuildGEP2(g_builder, element_type, sdim, types.ConstI32(0ul), "dim_0") LLVMBuildStore(g_builder, s0, getV(svar)) - let dimSize = ssrc._type.dim[0] + let dimSize = ssrc._type.fixedDim var tail = types.ConstI32(uint64(dimSize - 1)) // Stash end-element pointer as-is; compare directly in next-step. // Previous code ptrtoint'd to LLVMIntPtrType which is statically i64 @@ -3817,8 +3817,8 @@ class public LlvmJitVisitor : AstVisitor { var nextOk = append_basic_block("for_{svar.name}_next_ok") LLVMBuildCondBr(g_builder, rcond, lblk.loop_end, nextOk) LLVMPositionBuilderAtEnd(g_builder, nextOk) - } elif (!empty(ssrc._type.dim)) {// []->next() - var etype = clone_type(ssrc._type.firstType) // element of the fixed-array chain (the .dim compat view is read-only) + } elif (ssrc._type.baseType == Type.tFixedArray) {// []->next() + var etype = clone_type(ssrc._type.firstType) // element of the fixed-array chain var svar_v = LLVMBuildLoad2(g_builder, LLVMPointerType(type_to_llvm_type(svar._type), 0u), getV(svar), "") var svar_i = build_array_index(svar_v, types.ConstI32(1ul), etype, "next_element") LLVMBuildStore(g_builder, svar_i, getV(svar)) @@ -5145,7 +5145,7 @@ class public LlvmJitVisitor : AstVisitor { res = LLVMBuildPointerCast(g_builder, subE, LLVMPointerType(type_to_llvm_type(ect), 0u), "") } elif (ect.isPointer && (subT.isPointer || subT.isRefType)) { res = LLVMBuildPointerCast(g_builder, subE, type_to_llvm_type(ect), "cast_p") - } elif (!empty(ect.dim) && subT.isPointer) { + } elif (ect.baseType == Type.tFixedArray && subT.isPointer) { res = LLVMBuildPointerCast(g_builder, subE, LLVMPointerType(type_to_llvm_type(ect), 0u), "") } elif (ect.isInteger && (ect.baseType == Type.tInt64 || ect.baseType == Type.tUInt64) && subT.isPointer) { res = LLVMBuildPtrToInt(g_builder, subE, type_to_llvm_type(ect), "cast_p_i") @@ -7046,9 +7046,15 @@ class public ResolveExternVisitor : AstVisitor { } dll.set_glob_address(uid.get_new(expr.typeexpr), new_handle_ptr) } else { - if (!empty(expr.typeexpr.dim)) { - if (expr.typeexpr.baseType == Type.tHandle) { - var new_handle_ptr = get_jit_new(expr.typeexpr.annotation) + if (expr.typeexpr.baseType == Type.tFixedArray) { + // the handle gate lives on the chain element (the head is tFixedArray); + // borrow-walk — this pass runs outside ast_gc_guard, clones would leak + var elemT = expr.typeexpr + while (elemT.baseType == Type.tFixedArray && elemT.firstType != null) { + elemT = elemT.firstType + } + if (elemT.baseType == Type.tHandle) { + var new_handle_ptr = get_jit_new(elemT.annotation) if (new_handle_ptr == null) { failed_T(expr.typeexpr, "missing new`handle function pointer for {describe(expr.typeexpr)}") return @@ -7484,7 +7490,7 @@ class public DisableJitVisitor : AstVisitor { def override preVisitExprForBody(expr : ExprFor?) : void { for (_, ssrc in expr.iteratorVariables, expr.sources) { assume ssrc_t = ssrc._type - if (ssrc_t.isRange || !empty(ssrc_t.dim) || + if (ssrc_t.isRange || ssrc_t.baseType == Type.tFixedArray || ssrc_t.isGoodArrayType || ssrc_t.isIterator || ssrc_t.isString) { } elif (!disable) { diff --git a/modules/dasLLVM/daslib/llvm_jit_common.das b/modules/dasLLVM/daslib/llvm_jit_common.das index 090cf3f404..e6913b1b2b 100644 --- a/modules/dasLLVM/daslib/llvm_jit_common.das +++ b/modules/dasLLVM/daslib/llvm_jit_common.das @@ -1119,16 +1119,9 @@ def public type_to_llvm_abi_type(t : TypeDeclPtr) { def public type_to_llvm_type(t : TypeDeclPtr) { var res : LLVMOpaqueType? - if (!empty(t.dim)) { - var ndt = clone_type(t) - while (ndt.baseType == Type.tFixedArray && ndt.firstType != null) { // element of the chain (the .dim compat view is read-only) - ndt = clone_type(ndt.firstType) - } - res = type_to_llvm_type(ndt) - let ndim = length(t.dim) - for (i in range(ndim)) { - res = LLVMArrayType(res, uint(t.dim[ndim - 1 - i])) - } + if (t.baseType == Type.tFixedArray && t.firstType != null) { + // natural recursion: [outer x [inner x elem]] — same nesting the reversed flat walk built + res = LLVMArrayType(type_to_llvm_type(t.firstType), uint(t.fixedDim)) } elif (t.isPointer) { res = get_type_pointer(t.firstType) } elif (t.isWorkhorseType) { @@ -1210,11 +1203,11 @@ def public type_to_llvm_type(t : TypeDeclPtr) { } def public dim_element_type_to_llvm_type(typ : TypeDeclPtr) { - if (empty(typ.dim)) { + if (typ.baseType != Type.tFixedArray) { return type_to_llvm_type(typ) } else { var ndt = clone_type(typ) - while (ndt.baseType == Type.tFixedArray && ndt.firstType != null) { // scalar element of the chain (the .dim compat view is read-only) + while (ndt.baseType == Type.tFixedArray && ndt.firstType != null) { // scalar element at the chain leaf ndt = clone_type(ndt.firstType) } return type_to_llvm_type(ndt) diff --git a/modules/dasLLVM/daslib/llvm_jit_run.das b/modules/dasLLVM/daslib/llvm_jit_run.das index 9c928c026a..39df918657 100644 --- a/modules/dasLLVM/daslib/llvm_jit_run.das +++ b/modules/dasLLVM/daslib/llvm_jit_run.das @@ -33,7 +33,7 @@ var LINK_WHOLE_LIB = false // when true, standalone exe links against the whole // invalidates cached DLLs (e.g. edits to llvm_jit.das, llvm_macro.das, llvm_jit_common.das, // runtime helper ABI, default target triple). Cache filenames fold this in, so a bump // makes every previously written DLL miss the cache on the next run and get GC'd. -let LLVM_JIT_CODEGEN_VERSION : uint64 = 0x23ul +let LLVM_JIT_CODEGEN_VERSION : uint64 = 0x24ul let JIT_FNV_PRIME : uint64 = 1099511628211ul From 9c3670015dd808edef439c23a638c1ab3e2a6249 Mon Sep 17 00:00:00 2001 From: Boris Batkin Date: Wed, 10 Jun 2026 19:38:57 -0700 Subject: [PATCH 15/23] fixed-array stage 4: row-sort transform one-peel fix; dead dim-rejects deleted; stdlib generics pinned multi-dim Co-Authored-By: Claude Fable 5 --- FIXED_ARRAY_REWORK.md | 22 +++++ daslib/apply.das | 5 - daslib/contracts.das | 6 +- src/builtin/module_builtin_runtime_sort.cpp | 3 +- tests/fixed_array/test_stdlib_generics.das | 103 ++++++++++++++++++++ 5 files changed, 129 insertions(+), 10 deletions(-) create mode 100644 tests/fixed_array/test_stdlib_generics.das diff --git a/FIXED_ARRAY_REWORK.md b/FIXED_ARRAY_REWORK.md index 2f8cfadb84..e5ddcf6c1e 100644 --- a/FIXED_ARRAY_REWORK.md +++ b/FIXED_ARRAY_REWORK.md @@ -418,6 +418,28 @@ 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, 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. ### Stage 5 — Macro/tooling ports ast_match.das (structural FA matching), match.das, typemacro_boost (field rename), lints diff --git a/daslib/apply.das b/daslib/apply.das index bf4f7e6881..cf5555ee87 100644 --- a/daslib/apply.das +++ b/daslib/apply.das @@ -110,7 +110,6 @@ def private inline_apply_field(nameStr : string; var access : ExpressionPtr; fie [macro_function] def generateApplyVisitStruct(stype : TypeDeclPtr; frange : range; fnname : string; at : LineInfo; var names : array; hasExtraArg : bool) { assert(stype.baseType == Type.tStructure) - assert(empty(stype.dim)) let nfields = frange.y - frange.x var selfT = clone_type(stype) selfT.flags |= TypeDeclFlags.isExplicit | TypeDeclFlags.explicitConst @@ -164,7 +163,6 @@ def generateApplyVisitStruct(stype : TypeDeclPtr; fnname : string; at : LineInfo [macro_function] def generateApplyVisitTuple(stype : TypeDeclPtr; frange : range; fnname : string; at : LineInfo; var names : array) { assert(stype.baseType == Type.tTuple) - assert(empty(stype.dim)) let nfields = frange.y - frange.x var selfT = clone_type(stype) selfT.flags |= TypeDeclFlags.isExplicit | TypeDeclFlags.explicitConst @@ -215,7 +213,6 @@ def generateApplyVisitTuple(stype : TypeDeclPtr; fnname : string; at : LineInfo) [macro_function] def generateApplyVisitVariant(stype : TypeDeclPtr; frange : range; fnname : string; at : LineInfo; var names : array) { assert(stype.baseType == Type.tVariant) - assert(empty(stype.dim)) let nfields = frange.y - frange.x var selfT = clone_type(stype) selfT.flags |= TypeDeclFlags.isExplicit | TypeDeclFlags.explicitConst @@ -348,7 +345,6 @@ class ApplyMacro : AstCallMacro { return null } var argT = clone_type(expr.arguments[0]._type) - macro_verify(empty(argT.dim), prog, expr.at, "can't apply to dim") macro_verify(argT.baseType == Type.tStructure || argT.baseType == Type.tTuple || argT.baseType == Type.tVariant, prog, expr.at, "can only apply to {describe(expr.arguments[0]._type)}") macro_verify(expr.arguments[1] is ExprMakeBlock, prog, expr.at, "expecting make block, i.e. $(..)") @@ -531,7 +527,6 @@ class ApplyImmMacro : AstCallMacro { return null } var argT = clone_type(expr.arguments[0]._type) - macro_verify(empty(argT.dim), prog, expr.at, "can't apply_imm to dim") macro_verify(argT.baseType == Type.tStructure, prog, expr.at, "apply_imm supports struct only") macro_verify(expr.arguments[1] is ExprMakeBlock, prog, expr.at, "expecting make block, i.e. $(..)") macro_verify(apply_block_inline_safe(expr.arguments[1]), prog, expr.at, diff --git a/daslib/contracts.das b/daslib/contracts.das index 09a48e6f9c..a46be9b33d 100644 --- a/daslib/contracts.das +++ b/daslib/contracts.das @@ -70,7 +70,7 @@ class IsAnyArrayMacro : IsAnyType { for (fna, typ in func.arguments, types) {// note: N^2 for (argv in decl.arguments) { if (fna.name == argv.name) { - if (!(typ.baseType == Type.tArray || !empty(typ.dim) || isYetAnotherVectorTemplate(typ))) { + if (!(typ.baseType == Type.tArray || typ.baseType == Type.tFixedArray || isYetAnotherVectorTemplate(typ))) { errors := "argument {argv.name} is not a vector, array, or [], it is {describe(typ)}" return false } @@ -88,7 +88,7 @@ class IsAnyEnumMacro : IsAnyType { for (fna, typ in func.arguments, types) {// note: N^2 for (argv in decl.arguments) { if (fna.name == argv.name) { - if (!empty(typ.dim) || (typ.baseType != Type.tEnumeration && typ.baseType != Type.tEnumeration8 && typ.baseType != Type.tEnumeration16 && typ.baseType != Type.tEnumeration64)) { + if (typ.baseType != Type.tEnumeration && typ.baseType != Type.tEnumeration8 && typ.baseType != Type.tEnumeration16 && typ.baseType != Type.tEnumeration64) { errors := "argument {argv.name} is not an enumeration" return false } @@ -125,7 +125,7 @@ class IsAnyVectorType : IsAnyType { for (fna, typ in func.arguments, types) {// note: N^2 for (argv in decl.arguments) { if (fna.name == argv.name) { - if (!empty(typ.dim) || !isVectorType(typ.baseType)) { + if (!isVectorType(typ.baseType)) { errors := "argument {argv.name} is not a vector type (int2, float3, range, etc)" return false } diff --git a/src/builtin/module_builtin_runtime_sort.cpp b/src/builtin/module_builtin_runtime_sort.cpp index e335d095a2..107e240903 100644 --- a/src/builtin/module_builtin_runtime_sort.cpp +++ b/src/builtin/module_builtin_runtime_sort.cpp @@ -491,8 +491,7 @@ namespace das } const auto & arg = call->arguments[0]; if ( arg->type->baseType==Type::tFixedArray ) { - auto faT = arg->type; // innermost fixed-array node (matches old dim.back() semantics) - while ( faT->firstType && faT->firstType->baseType==Type::tFixedArray ) faT = faT->firstType; + auto faT = arg->type; // chain head: sort the outermost level (rows of a multi-dim array) auto arrType = new TypeDecl(*faT->firstType); auto newCall = static_cast(call->clone()); auto stride = arrType->getSizeOf(); diff --git a/tests/fixed_array/test_stdlib_generics.das b/tests/fixed_array/test_stdlib_generics.das new file mode 100644 index 0000000000..40c7e40ccb --- /dev/null +++ b/tests/fixed_array/test_stdlib_generics.das @@ -0,0 +1,103 @@ +// Stage 4 of the tFixedArray rework (FIXED_ARRAY_REWORK.md): multi-dim and +// typedef'd fixed arrays flow through the kept C-array-subject generics +// (each/sort/find_index/subarray/clone_dim/finalize_dim) via natural nesting. +options gen2 +require dastest/testing_boost public + +typedef M4 = float[4][4] + +[test] +def test_each_multi_dim(t : T?) { + t |> run("each over multi-dim yields rows") @(t : T?) { + var m : float[4][4] + for (i in range(4)) { + m[i][0] = float(i * 10) + } + var rowsum = 0.0 + for (row in m) { + rowsum += row[0] + } + t |> equal(rowsum, 60.0) + } + t |> run("each over typedef'd M4[N] yields M4 elements") @(t : T?) { + var mm : M4[2] + mm[1][2][3] = 42.0 + var found = 0 + for (one_m in mm) { + if (one_m[2][3] == 42.0) { + found ++ + } + } + t |> equal(found, 1) + } +} + +[test] +def test_sort_rows(t : T?) { + t |> run("sort with cmp sorts whole rows") @(t : T?) { + var s : int[3][2] + s[0][0] = 30 + s[0][1] = 31 + s[1][0] = 10 + s[1][1] = 11 + s[2][0] = 20 + s[2][1] = 21 + s |> sort() $(x, y : int[2]) { + return x[0] < y[0] + } + // rows move as units - the second column rides along + t |> equal(s[0][0], 10) + t |> equal(s[0][1], 11) + t |> equal(s[1][0], 20) + t |> equal(s[1][1], 21) + t |> equal(s[2][0], 30) + t |> equal(s[2][1], 31) + } +} + +[test] +def test_find_subarray_rows(t : T?) { + t |> run("find_index 1-D") @(t : T?) { + var f1 : int[4] + f1[2] = 7 + t |> equal(find_index(f1, 7), 2) + } + t |> run("find_index_if over rows") @(t : T?) { + var s : int[3][2] + s[2][0] = 30 + let fi = find_index_if(s) $(row : int[2]) { + return row[0] == 30 + } + t |> equal(fi, 2) + } + t |> run("subarray over rows keeps the row type") @(t : T?) { + var s : int[3][2] + s[1][0] = 15 + s[2][0] = 25 + var sub <- subarray(s, range(1, 3)) + t |> equal(length(sub), 2) + t |> equal(sub[0][0], 15) + t |> equal(sub[1][0], 25) + t |> equal(typeinfo typename(sub), "array") + delete sub + } +} + +[test] +def test_clone_finalize_multi_dim(t : T?) { + t |> run("clone_dim multi-dim") @(t : T?) { + var src : float[4][4] + src[3][0] = 30.0 + var dst : float[4][4] + clone_dim(dst, src) + t |> equal(dst[3][0], 30.0) + } + t |> run("finalize_dim over array rows") @(t : T?) { + var rows : array[2] + rows[0] |> push(1) + rows[1] |> push(2) + finalize_dim(rows) + t |> equal(length(rows[0]), 0) + t |> equal(length(rows[1]), 0) + } +} From c5191ec8a788ebc3ac4afd2a80b9cbf6b7e2b4ef Mon Sep 17 00:00:00 2001 From: Boris Batkin Date: Wed, 10 Jun 2026 20:38:41 -0700 Subject: [PATCH 16/23] fixed-array stage 5: macro/tooling structural ports; typeMacroExpr exposed+renamed; -[] peel; rst/glsl FA render fixes Co-Authored-By: Claude Fable 5 --- FIXED_ARRAY_REWORK.md | 28 ++++++++ daslib/ast_boost.das | 8 +-- daslib/ast_match.das | 33 ++++------ daslib/clargs.das | 8 +-- daslib/decs_boost.das | 4 +- daslib/is_local.das | 8 +-- daslib/match.das | 14 ++-- daslib/perf_lint.das | 13 ++-- daslib/rst.das | 20 ++++-- daslib/sort_boost.das | 2 +- daslib/style_lint.das | 15 ++--- daslib/templates_boost.das | 4 +- daslib/typemacro_boost.das | 12 ++-- examples/flatten/backend/shader_dsl_boost.das | 4 +- modules/dasGlsl/glsl/glsl_internal.das | 66 +++++++++++-------- modules/dasSQLITE/daslib/sqlite_boost.das | 52 +++++++-------- modules/dasSQLITE/daslib/sqlite_linq.das | 2 +- src/ast/ast_infer_type_helper.cpp | 18 +++++ src/ast/ast_typedecl.cpp | 1 - .../module_builtin_ast_annotations_1.cpp | 3 +- tests/fixed_array/test_target_inference.das | 8 +++ tests/typemacro/_typemacro_mod.das | 43 ++++++------ tests/typemacro/test_basic.das | 4 +- tutorials/macros/type_macro_mod.das | 26 ++++---- utils/fix-lint-errors/main.das | 2 +- 25 files changed, 228 insertions(+), 170 deletions(-) diff --git a/FIXED_ARRAY_REWORK.md b/FIXED_ARRAY_REWORK.md index e5ddcf6c1e..7fe6a453a8 100644 --- a/FIXED_ARRAY_REWORK.md +++ b/FIXED_ARRAY_REWORK.md @@ -446,6 +446,34 @@ ast_match.das (structural FA matching), match.das, typemacro_boost (field rename (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` ← 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. ### Stage 6 — Externals + compat removal Sweep D:\DASPKG modules (dasImgui, dasSQLITE, ...); delete `.dim`/`.dimExpr` compat diff --git a/daslib/ast_boost.das b/daslib/ast_boost.das index 7894598e24..1a850e800a 100644 --- a/daslib/ast_boost.das +++ b/daslib/ast_boost.das @@ -77,7 +77,7 @@ def describe_function_short(func : FunctionPtr) { def isExpression(t : TypeDeclPtr; top : bool = true) : bool { //! Returns true if the type declaration refers to an AST expression node type. - if (t == null || !empty(t.dim)) { + if (t == null) { return false } elif (t.baseType == Type.tHandle) { if (t.annotation._module.name == "ast_core") { @@ -105,10 +105,8 @@ def is_same_or_inherited(parent, child : Structure const?) { def is_class_method(cinfo : StructurePtr; finfo : TypeDeclPtr) { //! Returns true if the function type declaration is a method of the given class structure. if (finfo.baseType != Type.tFunction - || !empty(finfo.dim) || empty(finfo.argTypes) - || finfo.argTypes[0].baseType != Type.tStructure - || !empty(finfo.argTypes[0].dim)) return false + || finfo.argTypes[0].baseType != Type.tStructure) return false return !!is_same_or_inherited(finfo.argTypes[0].structType, cinfo) } @@ -862,7 +860,7 @@ def private walk_and_convert_enumeration(data : uint8 const?; info : TypeDeclPtr def walk_and_convert(data : uint8 const?; info : TypeDeclPtr; at : LineInfo) : Expression? { //! Recursively converts raw data to an AST expression based on type information. // print("0x{intptr(data)} {describe(info)}\n") - if (info.baseType == Type.tFixedArray || !empty(info.dim)) { + if (info.baseType == Type.tFixedArray) { return walk_and_convert_dim(data, info, at) } elif (info.baseType == Type.tArray) { return walk_and_convert_array(data, info, at) diff --git a/daslib/ast_match.das b/daslib/ast_match.das index aaf9b1e67c..d1aefc921f 100644 --- a/daslib/ast_match.das +++ b/daslib/ast_match.das @@ -587,8 +587,8 @@ def private generate_type_match(pat_type : TypeDecl?; td_var : string; expr_var // ── $t tag — capture TypeDeclPtr ──────────────────────── if (pat_type.baseType == Type.alias && pat_type.flags.isTag) { - if (pat_type.firstType != null && length(pat_type.firstType.dimExpr) == 1) { - let tag_expr = pat_type.firstType.dimExpr[0] + if (pat_type.firstType != null && length(pat_type.firstType.typeMacroExpr) == 1) { + let tag_expr = pat_type.firstType.typeMacroExpr[0] if (tag_expr != null && tag_expr is ExprVar) { let tmp = next_var(index) body |> push <| qmacro_expr(${ var $i(tmp) = clone_type($i(td_var)); }) // nolint:LINT004 @@ -685,29 +685,22 @@ def private generate_type_match(pat_type : TypeDecl?; td_var : string; expr_var } } - // ── dim — compare fixed dimensions ────────────────────── - let n_dim = length(pat_type.dim) - if (n_dim > 0) { - var dc = qmacro(length($i(td_var).dim) != $v(n_dim)) - body |> push <| qm_type_guard(at, expr_var, dc) - for (d, i in pat_type.dim, range(n_dim)) { - { - var c = qmacro($i(td_var).dim[$v(i)] != $v(d)) - body |> push <| qm_type_guard(at, expr_var, c) - } - } + // ── fixedDim — chain depth/shape rides the firstType recursion ── + if (pat_type.baseType == Type.tFixedArray) { + var c = qmacro($i(td_var).fixedDim != $v(pat_type.fixedDim)) + body |> push <| qm_type_guard(at, expr_var, c) } - // ── dimExpr — these are Expressions, use generate_match ── - let n_dimexpr = length(pat_type.dimExpr) + // ── typeMacroExpr — these are Expressions, use generate_match ── + let n_dimexpr = length(pat_type.typeMacroExpr) if (n_dimexpr > 0) { - var dc = qmacro(length($i(td_var).dimExpr) != $v(n_dimexpr)) + var dc = qmacro(length($i(td_var).typeMacroExpr) != $v(n_dimexpr)) body |> push <| qm_type_guard(at, expr_var, dc) - for (dexpr, i in pat_type.dimExpr, range(n_dimexpr)) { + for (dexpr, i in pat_type.typeMacroExpr, range(n_dimexpr)) { let pat_expr = dexpr if (pat_expr != null) { let child_var = next_var(index) - body |> push <| qmacro_expr(${ var $i(child_var) = clone_expression($i(td_var).dimExpr[$v(i)]); }) + body |> push <| qmacro_expr(${ var $i(child_var) = clone_expression($i(td_var).typeMacroExpr[$v(i)]); }) generate_match(pat_expr, child_var, body, index, at) } } @@ -1312,7 +1305,7 @@ def private generate_match(pattern : Expression?; actual_var : string; var body || name == "iteratorsTags" || name == "iteratorsAt" || name == "iteratorVariables" || name == "iteratorsTupleExpansion" || name == "iteratorsAka" || name == "visibility" || name == "allowIteratorOptimization" || name == "canShadow" - || !empty(xtype.dim)) + || xtype.baseType == Type.tFixedArray) var p8 : int8? unsafe { p8 = (reinterpret pattern) + int(offset) @@ -1588,7 +1581,7 @@ def private generate_match(pattern : Expression?; actual_var : string; var body // Field-by-field matching via annotation reflection for_each_field(*ann) $(name, _cppName, xtype, offset) { return if (offset == 0xFFFFFFFFu || qm_skip_field(rtti, name)) - if (!empty(xtype.dim)) { + if (xtype.baseType == Type.tFixedArray) { panic("qmatch: fixed-size array field '{name}' not supported") } var p8 : int8? diff --git a/daslib/clargs.das b/daslib/clargs.das index 69a255424e..a6e9aafcab 100644 --- a/daslib/clargs.das +++ b/daslib/clargs.das @@ -611,10 +611,10 @@ def public print_help(info : CommandInfo; prog_name : string) : void { def option_inner_type(t : TypeDeclPtr) : TypeDeclPtr { if (t.baseType == Type.typeMacro) { - if (length(t.dimExpr) < 2 || t.dimExpr[0] == null || !(t.dimExpr[0] is ExprConstString) - || (t.dimExpr[0] as ExprConstString).value != "Option" - || t.dimExpr[1] == null || !(t.dimExpr[1] is ExprTypeDecl)) return null - return clone_type((t.dimExpr[1] as ExprTypeDecl).typeexpr) + if (length(t.typeMacroExpr) < 2 || t.typeMacroExpr[0] == null || !(t.typeMacroExpr[0] is ExprConstString) + || (t.typeMacroExpr[0] as ExprConstString).value != "Option" + || t.typeMacroExpr[1] == null || !(t.typeMacroExpr[1] is ExprTypeDecl)) return null + return clone_type((t.typeMacroExpr[1] as ExprTypeDecl).typeexpr) } if (t.baseType != Type.tStructure || t.structType == null) return null var has_flag = false diff --git a/daslib/decs_boost.das b/daslib/decs_boost.das index 7665a40052..282f27b712 100644 --- a/daslib/decs_boost.das +++ b/daslib/decs_boost.das @@ -284,10 +284,10 @@ def private append_index_lookup(arch_name : string; var qblock : ExprBlock?; a; vtype.flags.constant = true } elif (getter == "get_ro") { vtype.flags.constant = true - // if length(a._type.dim)==0 + // if a._type.baseType != Type.tFixedArray vtype.flags.ref = true } else { - // if length(a._type.dim)==0 + // if a._type.baseType != Type.tFixedArray vtype.flags.ref = true } // else diff --git a/daslib/is_local.das b/daslib/is_local.das index 2d0253bcad..3a660ccaed 100644 --- a/daslib/is_local.das +++ b/daslib/is_local.das @@ -22,7 +22,7 @@ def is_temp_safe(expr : ExpressionPtr) : bool { return (expr as ExprVar).varFlags.local // local variables are ok } elif (expr is ExprAt) { let ea = expr as ExprAt - if (ea.subexpr._type != null && !empty(ea.subexpr._type.dim)) return is_temp_safe(ea.subexpr) + if (ea.subexpr._type != null && ea.subexpr._type.baseType == Type.tFixedArray) return is_temp_safe(ea.subexpr) } elif (expr is ExprField) { let ef = expr as ExprField if (!(ef.value._type.baseType == Type.tHandle) || (ef.value._type.isLocal)) return is_temp_safe(ef.value) @@ -66,7 +66,7 @@ def is_local_expr(expr : ExpressionPtr) { return (expr as ExprVar).varFlags.local } elif (expr is ExprAt) { let ea = expr as ExprAt - if (ea.subexpr._type != null && !empty(ea.subexpr._type.dim)) return is_local_expr(ea.subexpr) + if (ea.subexpr._type != null && ea.subexpr._type.baseType == Type.tFixedArray) return is_local_expr(ea.subexpr) } elif (expr is ExprField) { let ef = expr as ExprField if (!(ef.value._type.baseType == Type.tHandle) || (ef.value._type.isLocal)) return is_local_expr(ef.value) @@ -81,7 +81,7 @@ def is_local_or_global_expr(expr : ExpressionPtr) { return ev.varFlags.local || !(ev.varFlags.argument || ev.varFlags._block) } elif (expr is ExprAt) { let ea = expr as ExprAt - if (ea.subexpr._type != null && !empty(ea.subexpr._type.dim)) return is_local_or_global_expr(ea.subexpr) + if (ea.subexpr._type != null && ea.subexpr._type.baseType == Type.tFixedArray) return is_local_or_global_expr(ea.subexpr) } elif (expr is ExprField) { let ef = expr as ExprField if (!(ef.value._type.baseType == Type.tHandle) || (ef.value._type.isLocal)) return is_local_or_global_expr(ef.value) @@ -98,7 +98,7 @@ def is_scope_expr(expr : ExpressionPtr) { return true } elif (expr is ExprAt) { let ea = expr as ExprAt - if (ea.subexpr._type != null && !empty(ea.subexpr._type.dim)) return is_scope_expr(ea.subexpr) + if (ea.subexpr._type != null && ea.subexpr._type.baseType == Type.tFixedArray) return is_scope_expr(ea.subexpr) } elif (expr is ExprField) { let ef = expr as ExprField if (!(ef.value._type.baseType == Type.tHandle) || (ef.value._type.isLocal)) return is_scope_expr(ef.value) diff --git a/daslib/match.das b/daslib/match.das index 4ab0fbd26b..31ba2ad1da 100644 --- a/daslib/match.das +++ b/daslib/match.das @@ -144,9 +144,8 @@ def match_struct(what : TypeDeclPtr; wths : ExprMakeStruct?; access : Expression to.errors |> match_error("{describe(what)} type does not match [[{describe(wths.makeType)} ... ]] structure", wths.at) return false } - let wdl = length(what.dim) let wthsLength = length(wths.structs) - let whatDim = wdl > 0 ? what.dim[wdl - 1] : 1 + let whatDim = what.baseType == Type.tFixedArray ? what.fixedDim : 1 if (wthsLength != whatDim) { to.errors |> match_error("{describe(what)} number of elements does not match [[describe(wths.makeType)}[{wthsLength}] ... ]]", wths.at) return false @@ -230,7 +229,7 @@ def match_tuple(what : TypeDeclPtr; wths : ExprMakeTuple?; access : Expression?; [macro_function] def match_array(what : TypeDeclPtr; wths : ExprMakeArray?; dynamic : bool; access : Expression?; var to : MatchTo) { - let wdl = length(what.dim) + let isFA = what.baseType == Type.tFixedArray var recT : TypeDeclPtr // find if there is ..., get its index var tagIndex = INT_MAX @@ -245,12 +244,12 @@ def match_array(what : TypeDeclPtr; wths : ExprMakeArray?; dynamic : bool; acces } // check lengths let wthsLength = length(wths.values) - if (wdl != 0) {// check if static array + if (isFA) {// check if static array if (dynamic) { to.errors |> match_error("{describe(what)} is not a dynamic array", wths.at) return false } - let whatDim = what.dim[wdl - 1] + let whatDim = what.fixedDim if (tagIndex == INT_MAX) { if (whatDim != wthsLength) { to.errors |> match_error("{describe(what)} number of elements does not match [[describe(wths.makeType)}[{wthsLength}] ... ]] array", wths.at) @@ -262,7 +261,7 @@ def match_array(what : TypeDeclPtr; wths : ExprMakeArray?; dynamic : bool; acces return false } } - recT = clone_type(what.firstType) // element of the fixed-array chain (the .dim compat view is read-only) + recT = clone_type(what.firstType) // element of the fixed-array chain if (!wths.makeType.isAuto && !is_same_type(recT, wths.makeType, RefMatters.no, ConstMatters.no, TemporaryMatters.no)) { to.errors |> match_error("{describe(what)} type does not match [[{describe(wths.recordType)}[{wthsLength}] ... ]] array", wths.at) return false @@ -308,9 +307,8 @@ def match_variant(what : TypeDeclPtr; wths : ExprMakeVariant?; access : Expressi to.errors |> match_error("{describe(what)} type does not match [[{describe(wths.makeType)} ... ]] variant", wths.at) return false } - let wdl = length(what.dim) let wthsLength = length(wths.variants) - let whatDim = wdl > 0 ? what.dim[wdl - 1] : 1 + let whatDim = what.baseType == Type.tFixedArray ? what.fixedDim : 1 if (wthsLength != whatDim) { to.errors |> match_error("{describe(what)} number of variant elements does not match [[describe(wths.makeType)}[{wthsLength}] ... ]]", wths.at) return false diff --git a/daslib/perf_lint.das b/daslib/perf_lint.das index 551562ee07..690164ceb7 100644 --- a/daslib/perf_lint.das +++ b/daslib/perf_lint.das @@ -438,7 +438,7 @@ class PerfLintVisitor : AstVisitor { def is_workhorse_numeric(t : TypeDecl?) : bool { //! True if the type is one of the six numeric workhorse scalars (int, uint, //! int64, uint64, float, double). Vectors/bitfields/enums do NOT qualify. - if (t == null || !empty(t.dim)) return false + if (t == null) return false let bt = t.baseType return (bt == Type.tInt || bt == Type.tUInt || bt == Type.tInt64 || bt == Type.tUInt64 @@ -507,7 +507,7 @@ class PerfLintVisitor : AstVisitor { //! `operator |` overload exists. Vectors/ints/etc. don't get here — //! the caller restricts to int-cast inputs whose underlying type is //! either bitfield or enum. - if (t == null || !empty(t.dim)) return false + if (t == null) return false if (t.baseType == Type.tBitfield) return true if (t.baseType == Type.tEnumeration) return enum_has_or_operator(t) return false @@ -584,7 +584,7 @@ class PerfLintVisitor : AstVisitor { //! skip — not flaggable: value-shape-changing conversions //! (array, uri, text_range, other handled types) //! and anything else. - if (arg == null || arg._type == null || !empty(arg._type.dim)) return Perf025Kind.skip + if (arg == null || arg._type == null) return Perf025Kind.skip let bt = arg._type.baseType if (bt == Type.tInt || bt == Type.tInt8 || bt == Type.tInt16 || bt == Type.tInt64 || bt == Type.tFloat || bt == Type.tDouble || bt == Type.tString @@ -771,12 +771,11 @@ class PerfLintVisitor : AstVisitor { def is_known_length_source(src : Expression?) : bool { if (src == null || src._type == null) return false let bt = src._type.baseType - // fixed arrays have dim > 0; otherwise we accept any baseType whose - // length is determined at the start of the loop. + // any source whose length is determined at the start of the loop return (bt == Type.tArray || bt == Type.tString || bt == Type.tRange || bt == Type.tRange64 || bt == Type.tURange || bt == Type.tURange64 - || !empty(src._type.dim)) + || bt == Type.tFixedArray) } def override preVisitExprForSource(expr : ExprFor?; src : ExpressionPtr; last : bool) : void { @@ -1036,7 +1035,7 @@ class PerfLintVisitor : AstVisitor { state.disqualified = true } else { let bt = src._type.baseType - let is_array_src = bt == Type.tArray || !empty(src._type.dim) + let is_array_src = bt == Type.tArray || bt == Type.tFixedArray if (!is_array_src) { state.disqualified = true } diff --git a/daslib/rst.das b/daslib/rst.das index b410e12c08..ecd371d2d1 100644 --- a/daslib/rst.das +++ b/daslib/rst.das @@ -403,6 +403,21 @@ def describe_type(td : TypeDecl?) { if (!empty(td.alias)) { write(writer, "({td.alias})") } + } elif (baseType == Type.tFixedArray) { + // element text first, then one [d] per chain level (outermost first); + // clone-walks so the recursion gets a plain non-const pointer + var elemT = clone_type(td) + while (elemT.baseType == Type.tFixedArray && elemT.firstType != null) { + elemT = clone_type(elemT.firstType) + } + write(writer, describe_type(elemT)) + var lvl = clone_type(td) + while (lvl.baseType == Type.tFixedArray && lvl.firstType != null) { + write(writer, "\\ [") + write(writer, lvl.fixedDim) + write(writer, "]") + lvl = clone_type(lvl.firstType) + } } elif (baseType == Type.tHandle) { let hname = string(td.annotation.name) if (hname |> starts_with("dasvector`smart_ptr`")) {// 20 characters @@ -562,11 +577,6 @@ def describe_type(td : TypeDecl?) { } // if td.flags.constant // write(writer," const") - for (d in td.dim) { - write(writer, "\\ [") - write(writer, d) - write(writer, "]") - } if (td.flags.ref) { write(writer, "\\ &") } diff --git a/daslib/sort_boost.das b/daslib/sort_boost.das index 1aa7261b6c..125b51e6b2 100644 --- a/daslib/sort_boost.das +++ b/daslib/sort_boost.das @@ -295,7 +295,7 @@ class QsortMacro : AstCallMacro { if (expr.arguments[0]._type == null || expr.arguments[1]._type == null) return null // todo: verify correct block type? macro_verify(expr.arguments[1]._type.baseType == Type.tBlock, prog, expr.at, "expecting 2nd argument to be block(:bool))") - if (!empty(expr.arguments[0]._type.dim) || expr.arguments[0]._type.baseType == Type.tArray) { + if (expr.arguments[0]._type.baseType == Type.tFixedArray || expr.arguments[0]._type.baseType == Type.tArray) { var call = new ExprCall(name := "sort", at = expr.at) emplace_new(call.arguments) <| clone_expression(expr.arguments[0]) emplace_new(call.arguments) <| clone_expression(expr.arguments[1]) diff --git a/daslib/style_lint.das b/daslib/style_lint.das index 9031fb14d9..7651748bd6 100644 --- a/daslib/style_lint.das +++ b/daslib/style_lint.das @@ -281,7 +281,7 @@ class StyleLintVisitor : AstVisitor { //! True for the scalar types that have a matching `operator ??` overload in //! daslib/json_boost — see lines 85-140 of json_boost.das. Bitfield and enum //! `from_JV` overloads exist but have no `??` counterpart, so they stay silent. - if (t == null || !empty(t.dim)) return false + if (t == null) return false let bt = t.baseType return (bt == Type.tInt || bt == Type.tUInt || bt == Type.tInt8 || bt == Type.tUInt8 @@ -525,7 +525,7 @@ class StyleLintVisitor : AstVisitor { def is_bitfield_value_type(t : TypeDecl?) : bool { //! True for plain bitfield-typed expressions (not arrays of bitfield). - return t != null && t.baseType == Type.tBitfield && empty(t.dim) + return t != null && t.baseType == Type.tBitfield } def extract_single_bit_name(expr : Expression? const; bf_type : TypeDecl?) : string { @@ -894,8 +894,7 @@ class StyleLintVisitor : AstVisitor { // host instantiations and compiler-synthesized vars). if (v.init != null || v.flags.generated || v.flags.inScope - || v._type == null || v._type.baseType != Type.tArray - || !empty(v._type.dim)) continue + || v._type == null || v._type.baseType != Type.tArray) continue var count = 0 for (j in range(idx + 1, n)) { let stmt = blk.list[j] @@ -1182,10 +1181,10 @@ class StyleLintVisitor : AstVisitor { def is_struct_or_struct_ptr(t : TypeDecl?) : bool { //! Match struct values AND pointers-to-struct, both flat (not array). - if (t == null || !empty(t.dim)) return false + if (t == null) return false let is_struct_val = t.baseType == Type.tStructure let is_struct_ptr = (t.baseType == Type.tPointer && t.firstType != null - && t.firstType.baseType == Type.tStructure && empty(t.firstType.dim)) + && t.firstType.baseType == Type.tStructure) return is_struct_val || is_struct_ptr } @@ -1268,7 +1267,7 @@ class StyleLintVisitor : AstVisitor { def is_jv_table_type(t : TypeDecl?) : bool { //! Matches `table` (or any pointer-to-JsonValue value type). - if (t == null || t.baseType != Type.tTable || !empty(t.dim) + if (t == null || t.baseType != Type.tTable || t.firstType == null || t.firstType.baseType != Type.tString) return false return is_jv_pointer_type(t.secondType) @@ -1342,7 +1341,7 @@ class StyleLintVisitor : AstVisitor { for (v in elet.variables) { if (v.init != null || v.flags.generated || v.flags.inScope - || v._type == null || !empty(v._type.dim)) continue + || v._type == null) continue let isArray = v._type.baseType == Type.tArray let isTable = v._type.baseType == Type.tTable if ((!isArray && !isTable) diff --git a/daslib/templates_boost.das b/daslib/templates_boost.das index 3226c0f81b..81aba77966 100644 --- a/daslib/templates_boost.das +++ b/daslib/templates_boost.das @@ -739,7 +739,7 @@ class private QRulesVisitor : AstVisitor { def override visitTypeDecl(var typ : TypeDeclPtr) : TypeDeclPtr { //! Visits type declaration and processes type tags. if ((typ.baseType == Type.alias) && (typ.flags.isTag)) { - if (typ.firstType == null || typ.firstType.dimExpr |> length != 1) { + if (typ.firstType == null || typ.firstType.typeMacroExpr |> length != 1) { compiling_program() |> macro_error(typ.at, "unsupported type tag") failed = true return <- typ @@ -757,7 +757,7 @@ class private QRulesVisitor : AstVisitor { new ExprConstString(at = typ.at, value := new_tag_name) |> generatedExpr(), new ExprCall(at = typ.at, name := "add_type_ptr_ref", arguments := array( - clone_expression(typ.firstType.dimExpr[0]), + clone_expression(typ.firstType.typeMacroExpr[0]), new ExprConstBitfield(at = typ.at, value := ntyp.flags) ) ) diff --git a/daslib/typemacro_boost.das b/daslib/typemacro_boost.das index 275c48a840..a39109d9dd 100644 --- a/daslib/typemacro_boost.das +++ b/daslib/typemacro_boost.das @@ -175,13 +175,13 @@ class TypeMacroMacro : AstFunctionAnnotation { let macroArgIndex = ai - 1 assume argType = func.arguments[ai]._type assume argDefaultValue = func.arguments[ai].init - if (!empty(argType.dim)) { + if (argType.baseType == Type.tFixedArray) { errors := "expecting non-array arguments" return false } if (argDefaultValue == null) { var checkE = qmacro_block() { - if ($v(macroArgIndex) >= length(td.dimExpr)) { + if ($v(macroArgIndex) >= length(td.typeMacroExpr)) { macro_error(compiling_program(), td.at, "expecting {$v(string(func.arguments[ai].name))} argument") return null } @@ -190,20 +190,20 @@ class TypeMacroMacro : AstFunctionAnnotation { } if (argType.baseType == Type.tInt) { var defaultValue = argDefaultValue != null ? clone_expression(argDefaultValue) : quote(0) - (retCall as ExprCall).arguments |> emplace_new <| qmacro(typemacro_argument(td.dimExpr, $v(macroArgIndex), type, $e(defaultValue))) + (retCall as ExprCall).arguments |> emplace_new <| qmacro(typemacro_argument(td.typeMacroExpr, $v(macroArgIndex), type, $e(defaultValue))) } elif (argType.baseType == Type.tBool) { var defaultValue = argDefaultValue != null ? clone_expression(argDefaultValue) : quote(false) - (retCall as ExprCall).arguments |> emplace_new <| qmacro(typemacro_argument(td.dimExpr, $v(macroArgIndex), type, $e(defaultValue))) + (retCall as ExprCall).arguments |> emplace_new <| qmacro(typemacro_argument(td.typeMacroExpr, $v(macroArgIndex), type, $e(defaultValue))) } elif (argType.baseType == Type.tString) { var defaultValue = argDefaultValue != null ? clone_expression(argDefaultValue) : quote("") - (retCall as ExprCall).arguments |> emplace_new <| qmacro(typemacro_argument(td.dimExpr, $v(macroArgIndex), type, $e(defaultValue))) + (retCall as ExprCall).arguments |> emplace_new <| qmacro(typemacro_argument(td.typeMacroExpr, $v(macroArgIndex), type, $e(defaultValue))) } elif (is_typedecl_ptr(argType)) { if (argDefaultValue != null) { errors := "TypeDeclPtr argument cannot have default value" return false } (retCall as ExprCall).arguments |> emplace_new <| qmacro( - (td.dimExpr[$v(macroArgIndex)] is ExprTypeDecl) ? (td.dimExpr[$v(macroArgIndex)] as ExprTypeDecl).typeexpr : td.dimExpr[$v(macroArgIndex)]._type) + (td.typeMacroExpr[$v(macroArgIndex)] is ExprTypeDecl) ? (td.typeMacroExpr[$v(macroArgIndex)] as ExprTypeDecl).typeexpr : td.typeMacroExpr[$v(macroArgIndex)]._type) } else { errors := "unsupported argument type {describe(argType)}" return false diff --git a/examples/flatten/backend/shader_dsl_boost.das b/examples/flatten/backend/shader_dsl_boost.das index fb7afd4ce4..085d2f8179 100644 --- a/examples/flatten/backend/shader_dsl_boost.das +++ b/examples/flatten/backend/shader_dsl_boost.das @@ -766,7 +766,7 @@ class ValidateShaderVisitor : AstVisitor { if (v._type.isPointer) { errors.push(("shader code doesn't support pointer types", unsafe(addr(v.at)))) } - if (!empty(v._type.dim)) { + if (v._type.baseType == Type.tFixedArray) { errors.push(("shader code doesn't support vector literals; use float2/3/4 types instead", unsafe(addr(v.at)))) } // A mutable `var` is an accumulator: it may be reassigned later via @@ -1026,7 +1026,7 @@ class ValidateShaderVisitor : AstVisitor { if (arg._type.isPointer) { errors.push(("shader properties can't be pointers", unsafe(addr(arg.at)))) } - if (!empty(arg._type.dim)) { + if (arg._type.baseType == Type.tFixedArray) { errors.push(("shader properties can't be vectors; use float2/3/4 types instead", unsafe(addr(arg.at)))) } // if (!arg._type.isConst) { diff --git a/modules/dasGlsl/glsl/glsl_internal.das b/modules/dasGlsl/glsl/glsl_internal.das index f97428780d..2dcf08b977 100644 --- a/modules/dasGlsl/glsl/glsl_internal.das +++ b/modules/dasGlsl/glsl/glsl_internal.das @@ -347,21 +347,28 @@ class GlslExport : AstVisitor { let st = build_string() $(var tw) { // if t.flags.ref // tw |> write("& ") // inout? - if (t.baseType == Type.tStructure) { - tw |> write("{t.structType.name}") - } elif (t.baseType == Type.tHandle) { - peek(t.annotation.name) $(aname) { + // element kind lives at the chain leaf; [d] suffixes come from the chain + var et = t + while (et.baseType == Type.tFixedArray && et.firstType != null) { + et = et.firstType + } + if (et.baseType == Type.tStructure) { + tw |> write("{et.structType.name}") + } elif (et.baseType == Type.tHandle) { + peek(et.annotation.name) $(aname) { tw |> write(glsl_handle_types?[aname] ?? "error") } - } elif (glsl_types |> key_exists(t.baseType)) { - tw |> write(glsl_types?[t.baseType] ?? "error") + } elif (glsl_types |> key_exists(et.baseType)) { + tw |> write(glsl_types?[et.baseType] ?? "error") } else { - error("unsupported type {t.baseType}", t.at) + error("unsupported type {et.baseType}", t.at) tw |> write("error") } if (write_dim) { - for (d in t.dim) { - tw |> write("[{d}]") + var lvl = t + while (lvl.baseType == Type.tFixedArray && lvl.firstType != null) { + tw |> write("[{lvl.fixedDim}]") + lvl = lvl.firstType } } } @@ -370,6 +377,14 @@ class GlslExport : AstVisitor { def describe_glsl_type(t : TypeDeclPtr) { return describe_glsl_type_ex(t, true) } + def write_dim_suffix(t : TypeDeclPtr) { + // GLSL declarator suffix: one [d] per chain level, outermost first + var lvl = t + while (lvl.baseType == Type.tFixedArray && lvl.firstType != null) { + *writer |> write("[{lvl.fixedDim}]") + lvl = lvl.firstType + } + } def glsl_function_name(fun : FunctionPtr) { var funname = build_string() $(var tw) { for (Ch in string(fun.name)) { @@ -416,10 +431,10 @@ class GlslExport : AstVisitor { return st } def produce_zero(t : TypeDeclPtr) { - if (!empty(t.dim)) { + if (t.baseType == Type.tFixedArray) { *writer |> write("{describe_glsl_type(t)}(") - var ct <- clone_type(t.firstType) // element of the fixed-array chain (the .dim compat view is read-only) - for (d in range(t.dim[0])) { + var ct <- clone_type(t.firstType) // element of the fixed-array chain + for (d in range(t.fixedDim)) { if (d != 0) { *writer |> write(",") } @@ -714,9 +729,7 @@ class GlslExport : AstVisitor { } } def override visitStructureField(str : StructurePtr; decl : FieldDeclaration; last : bool) { - for (d in decl._type.dim) { - *writer |> write("[{d}]") - } + write_dim_suffix(decl._type) *writer |> write(";") newLine(1) } @@ -753,7 +766,7 @@ class GlslExport : AstVisitor { } def override preVisitFunction(fun : FunctionPtr) { if (caps.restrict_array_as_result) { - if (!empty(fun.result.dim)) { + if (fun.result.baseType == Type.tFixedArray) { error("returning arrays is prohibited in this shader model", fun.at) } } @@ -839,16 +852,14 @@ class GlslExport : AstVisitor { } line(arg.at) *writer |> write("{describe_glsl_type_ex(arg._type,false)} {arg.name}") - for (d in arg._type.dim) { - *writer |> write("[{d}]") - } + write_dim_suffix(arg._type) } def override visitExprLetVariable(expr : ExprLet?; arg : VariablePtr; lastArg : bool) { if (arg.init == null) { var needZero = true if (caps.restrict_array_as_result) { if (caps.allow_unitialized_local_arrays) { - if (!empty(arg._type.dim)) { + if (arg._type.baseType == Type.tFixedArray) { needZero = false } } @@ -926,7 +937,7 @@ class GlslExport : AstVisitor { def override preVisitGlobalLetVariable(arg : VariablePtr; lastArg : bool) { line(arg.at) if (caps.restrict_array_as_result) { - if (!empty(arg._type.dim)) { + if (arg._type.baseType == Type.tFixedArray) { let need_init = arg.annotation |> find_arg("need_init") ?as tBool ?? true let group_shared = arg.annotation |> find_arg("groupshared") ?as tBool ?? false if (need_init && !group_shared) { @@ -1033,9 +1044,7 @@ class GlslExport : AstVisitor { } } *writer |> write("{describe_glsl_type_ex(arg._type,false)} {arg.name}") - for (d in arg._type.dim) { - *writer |> write("[{d}]") - } + write_dim_suffix(arg._type) if (arg.init == null && needInit) { *writer |> write(" = ") produce_zero(arg._type) @@ -1288,7 +1297,7 @@ class GlslExport : AstVisitor { } // for def isDimFor(svar : ExpressionPtr) { - return !empty(svar._type.dim) + return svar._type.baseType == Type.tFixedArray } def isRangeFor(svar : ExpressionPtr) { let bt = svar._type.baseType @@ -1334,7 +1343,8 @@ class GlslExport : AstVisitor { *writer |> write(" && ") } if (isDimFor(source)) { - let sdim = source._type.dim[length(source._type.dim) - 1] + // the loop indexes ONE level - bound is the outer (head) count + let sdim = source._type.fixedDim *writer |> write("{svar.name}!={sdim}") let newsrc = "{describe_subexpression(source)}[{svar.name}]" renames |> push((name = "{iter}", repl = newsrc)) @@ -1856,7 +1866,7 @@ def bind_uniform_function_name(fnMain : FunctionPtr) { [macro_function] def is_glsl_structure(arg; name : string) { - if (arg._type.baseType == Type.tStructure && empty(arg._type.dim)) { + if (arg._type.baseType == Type.tStructure) { if (arg._type.structType._module.name == "glsl_common" && arg._type.structType.name == name) return true } return false @@ -1891,7 +1901,7 @@ def get_decl_type_from_name(name : string) { [macro_function] def get_decl_info(typ : TypeDeclPtr; arguments : AnnotationArgumentList) : tuple { - if (!empty(typ.dim) || !(typ.isNumeric || typ.isVectorType)) return (ok = false, info = FieldDeclInfo()) + if (typ.baseType == Type.tFixedArray || !(typ.isNumeric || typ.isVectorType)) return (ok = false, info = FieldDeclInfo()) var fdi = FieldDeclInfo( sizei = arguments |> find_arg("size") ?as tInt ?? -1, typet = get_decl_type_from_name(arguments |> find_arg("type") ?as tString ?? ""), diff --git a/modules/dasSQLITE/daslib/sqlite_boost.das b/modules/dasSQLITE/daslib/sqlite_boost.das index 6820d42e2c..827d54bbea 100644 --- a/modules/dasSQLITE/daslib/sqlite_boost.das +++ b/modules/dasSQLITE/daslib/sqlite_boost.das @@ -113,7 +113,7 @@ def finalize(var db : SqlRunner) { def sqlite_version() : string { //! Returns the linked libsqlite3 library version (e.g. "3.45.0"). No DB connection needed. - return string(sqlite3_libversion()) + return sqlite3_libversion() } def try_open_sqlite(path : string) : Result { @@ -1524,7 +1524,7 @@ def with_transaction(db : SqlRunner; mode : SqliteTxnMode; blk : block<() : void //! ROLLBACK fires on panic / early return via ``finally`` — best effort. let was_in_txn = db |> in_transaction() db |> exec(txn_begin_sql(was_in_txn, mode)) - var success = false + var success : bool { invoke(blk) success = true @@ -1554,7 +1554,7 @@ def try_transaction(db : SqlRunner; mode : SqliteTxnMode; blk : block<() : void> let was_in_txn = db |> in_transaction() let begin_err = db |> try_exec(txn_begin_sql(was_in_txn, mode)) if (begin_err |> is_some) return begin_err - var success = false + var success : bool { invoke(blk) success = true @@ -1703,7 +1703,7 @@ def optimize(db : SqlRunner) : void { def private try_check_impl(db : SqlRunner; sql : string) : Result, string> { return <- db |> try_run_select(sql, - $(var stmt : sqlite3_stmt?) {}, + $(var _stmt : sqlite3_stmt?) {}, $(stmt : sqlite3_stmt?) { return read_via_adapter(stmt, 0, type) }) @@ -2072,7 +2072,7 @@ def private resolve_schema_from_path(p : string) : string { def is_option_field_type(td : TypeDeclPtr) : bool { if (td == null) return false // Pre-instantiation form: typeMacro `$Option` — what [sql_table] sees during structure_macro apply(). - if (td.baseType == Type.typeMacro && !empty(td.dimExpr) && (td.dimExpr[0] is ExprConstString)) return (td.dimExpr[0] as ExprConstString).value == "Option" + if (td.baseType == Type.typeMacro && !empty(td.typeMacroExpr) && (td.typeMacroExpr[0] is ExprConstString)) return (td.typeMacroExpr[0] as ExprConstString).value == "Option" // Post-instantiation form: structural named tuple ``tuple<_has_value:bool; _value:T>``. // Option is built by daslib/option's typemacro_function as a structural tuple — no // module home, no Structure clone. Match the canonical shape. @@ -2085,14 +2085,14 @@ def is_option_field_type(td : TypeDeclPtr) : bool { } def unwrap_option_payload_type(var td : TypeDeclPtr) : TypeDeclPtr { - // Unwrap Option → T (or td unchanged); typeMacro carries T in dimExpr[1], the + // Unwrap Option → T (or td unchanged); typeMacro carries T in typeMacroExpr[1], the // structural tuple form carries T in argTypes[1] (the `_value` slot). if (td == null) return td if (td.baseType == Type.typeMacro) { - if (length(td.dimExpr) < 2 || td.dimExpr[0] == null || !(td.dimExpr[0] is ExprConstString) - || (td.dimExpr[0] as ExprConstString).value != "Option" - || td.dimExpr[1] == null || !(td.dimExpr[1] is ExprTypeDecl)) return td - return clone_type((td.dimExpr[1] as ExprTypeDecl).typeexpr) + if (length(td.typeMacroExpr) < 2 || td.typeMacroExpr[0] == null || !(td.typeMacroExpr[0] is ExprConstString) + || (td.typeMacroExpr[0] as ExprConstString).value != "Option" + || td.typeMacroExpr[1] == null || !(td.typeMacroExpr[1] is ExprTypeDecl)) return td + return clone_type((td.typeMacroExpr[1] as ExprTypeDecl).typeexpr) } // Post-instantiation form: structural named tuple ``tuple<_has_value:bool; _value:T>``. if (td.baseType == Type.tTuple @@ -2228,7 +2228,7 @@ class SqlIndexMacro : AstStructureAnnotation { has_field = true let ca = find_arg(field.annotation, "sql_column") if (ca is tString) { - resolved = string(ca as tString) + resolved = ca as tString } } } @@ -2308,7 +2308,7 @@ def private find_parent_table_and_pk(var st : StructurePtr; parent_name : string var pk_col = string(pf.name) let pk_anno = find_arg(pf.annotation, "sql_column") if (pk_anno is tString) { - pk_col = string(pk_anno as tString) + pk_col = pk_anno as tString } return (tab_name = tab, pk_col = pk_col, found = true) } @@ -2326,7 +2326,7 @@ class SqlTableMacro : AstStructureAnnotation { var schema_assert_exprs : array let schema_from_arg = find_arg(args, "schema_from") if (schema_from_arg is tString) { - let raw_path = string(schema_from_arg as tString) + let raw_path = schema_from_arg as tString if (empty(raw_path)) { errors := "[sql_table]: schema_from=\"\" is invalid — provide a non-empty path to a SQLite database file." return false @@ -2365,7 +2365,7 @@ class SqlTableMacro : AstStructureAnnotation { var col_name = fname let col_anno = find_arg(field.annotation, "sql_column") if (col_anno is tString) { - let cv = string(col_anno as tString) + let cv = col_anno as tString if (empty(cv)) { errors := "[sql_table]: field '{fname}' @sql_column = \"\" is invalid — provide a non-empty SQL column name." return false @@ -3411,7 +3411,7 @@ class private SqlViewMacro : AstStructureAnnotation { var col_name = fname let col_anno = find_arg(field.annotation, "sql_column") if (col_anno is tString) { - let cv = string(col_anno as tString) + let cv = col_anno as tString if (empty(cv)) { errors := "[sql_view]: field '{fname}' @sql_column = \"\" is invalid — provide a non-empty SQL column name." return false @@ -3709,7 +3709,7 @@ class private SqlFts5Macro : AstStructureAnnotation { errors := "[sql_fts5]: field '{fname}' has both @sql_fts_rank and @sql_column — rank is hardcoded to FTS5's hidden `rank` column. Drop the @sql_column rename." return false } - let cv = string(col_anno as tString) + let cv = col_anno as tString if (empty(cv)) { errors := "[sql_fts5]: field '{fname}' @sql_column = \"\" is invalid — provide a non-empty SQL column name." return false @@ -3961,7 +3961,7 @@ enum SqlFnTag { [macro_function] def private sql_fn_tag_for_type(td : TypeDeclPtr) : tuple { - if (td == null || !empty(td.dim)) return (tag = uint8(0), ok = false) + if (td == null || td.baseType == Type.tFixedArray) return (tag = uint8(0), ok = false) if (td.baseType == Type.tInt) return (tag = uint8(SqlFnTag.Int), ok = true) if (td.baseType == Type.tInt64) return (tag = uint8(SqlFnTag.Int64), ok = true) if (td.baseType == Type.tFloat) return (tag = uint8(SqlFnTag.Float), ok = true) @@ -4010,13 +4010,12 @@ class private RegisterFunctionMacro : AstCallMacro { "register_function: return type '{describe(fnExpr._type.firstType)}' is unsupported — supported: int, int64, float, double, bool, string") return null } - var dbExpr = clone_expression(call.arguments[0]) - var nameExpr = clone_expression(call.arguments[1]) - var fnClone = clone_expression(fnExpr) + var dbExpr = call.arguments[0] + var nameExpr = call.arguments[1] // Optional positional flags. Default both to false. var detExpr : ExpressionPtr = nCallArgs >= 4 ? clone_expression(call.arguments[3]) : qmacro(false) var donExpr : ExpressionPtr = nCallArgs >= 5 ? clone_expression(call.arguments[4]) : qmacro(false) - let nArgsLit = int(nArgs) + let nArgsLit = nArgs let tag0 = tags[0] let tag1 = tags[1] let tag2 = tags[2] @@ -4024,7 +4023,7 @@ class private RegisterFunctionMacro : AstCallMacro { let retTag = retR.tag // Emit: helper checks rc and panics on failure with the libsqlite3 errmsg. var res = qmacro(_::_register_function_check_rc( - $e(dbExpr), $e(nameExpr), $e(fnClone), $v(nArgsLit), + $e(dbExpr), $e(nameExpr), $e(fnExpr), $v(nArgsLit), $v(tag0), $v(tag1), $v(tag2), $v(tag3), $v(retTag), $e(detExpr), $e(donExpr))) res.force_at(call.at) @@ -4180,7 +4179,7 @@ class SqlFunctionMacro : AstFunctionAnnotation { return false } var reg <- setup_call_list("register`sqlite`functions", func.at, true/*isInit*/, true/*isPrivate*/) - let nArgsLit = int(nArgs) + let nArgsLit = nArgs let tag0 = tags[0] let tag1 = tags[1] let tag2 = tags[2] @@ -4249,7 +4248,7 @@ def private resolve_field_index(var st : StructurePtr; field_name : string) : in def private field_sql_name_at(var st : StructurePtr; idx : int) : string { // @sql_column("X") rename → X; otherwise daslang field name. idx must be in range. let ca = find_arg(st.fields[idx].annotation, "sql_column") - if (ca is tString) return string(ca as tString) + if (ca is tString) return ca as tString return string(st.fields[idx].name) } @@ -4347,7 +4346,7 @@ class private AddColumnMacro : AstCallMacro { let null_clause = is_optional ? "" : " NOT NULL" let prefix = "ALTER TABLE \"{table_name}\" ADD COLUMN \"{col_name}\" " let suffix = "{null_clause}{default_clause}" - var dbExpr = clone_expression(call.arguments[0]) + var dbExpr = call.arguments[0] var res : ExpressionPtr if (is_json) { let full = "{prefix}TEXT{suffix}" @@ -4458,8 +4457,7 @@ def private emit_create_index(prog : ProgramPtr; var call : ExprCallMacro?; is_u } w |> write(")") } - var dbExpr = clone_expression(call.arguments[0]) - var res = qmacro(_::exec($e(dbExpr), $v(ddl))) + var res = qmacro(_::exec($e(call.arguments[0]), $v(ddl))) res.force_at(call.at) return res } diff --git a/modules/dasSQLITE/daslib/sqlite_linq.das b/modules/dasSQLITE/daslib/sqlite_linq.das index cbab0add84..fc14b49b2f 100644 --- a/modules/dasSQLITE/daslib/sqlite_linq.das +++ b/modules/dasSQLITE/daslib/sqlite_linq.das @@ -2229,7 +2229,7 @@ def private try_recognize_sql_function_proj(val : ExpressionPtr; var q : SqlQuer // a whole-row carry var (transparent-identifier `(c=c,b=b)`) falls through to the clean row-object reject // instead of reaching pred_to_sql (which deref-crashes on a bare struct receiver). def private is_sql_renderable_scalar(td : TypeDecl?) : bool { - if (td == null || td.isAutoOrAlias || !empty(td.dim)) return false + if (td == null || td.isAutoOrAlias || td.baseType == Type.tFixedArray) return false let bt = td.baseType return (bt == Type.tInt || bt == Type.tInt8 || bt == Type.tInt16 || bt == Type.tInt64 || bt == Type.tUInt || bt == Type.tUInt8 || bt == Type.tUInt16 || bt == Type.tUInt64 diff --git a/src/ast/ast_infer_type_helper.cpp b/src/ast/ast_infer_type_helper.cpp index 74ad5d77c6..afc52f82ff 100644 --- a/src/ast/ast_infer_type_helper.cpp +++ b/src/ast/ast_infer_type_helper.cpp @@ -421,6 +421,15 @@ namespace das { resT->dim = decl->dim; resT->aotAlias = false; resT->alias.clear(); + if ( decl->removeDim && resT->baseType==Type::tFixedArray && resT->firstType ) { + // TT -[] unwraps one fixed-array level; qualifiers ride to the new head + auto peeled = new TypeDecl(*resT->firstType); + peeled->at = resT->at; + peeled->ref = resT->ref; + peeled->constant = resT->constant; + peeled->temporary = resT->temporary; + resT = peeled; + } return resT; } else { return nullptr; @@ -556,6 +565,15 @@ namespace das { resT->dim = decl->dim; resT->aotAlias = false; // resT->alias.clear(); // this may speed things up, but it breaks typemacro-based aliases + if ( decl->removeDim && resT->baseType==Type::tFixedArray && resT->firstType ) { + // TT -[] unwraps one fixed-array level; qualifiers ride to the new head + auto peeled = new TypeDecl(*resT->firstType); + peeled->at = resT->at; + peeled->ref = resT->ref; + peeled->constant = resT->constant; + peeled->temporary = resT->temporary; + resT = peeled; + } return resT; } else { return decl; diff --git a/src/ast/ast_typedecl.cpp b/src/ast/ast_typedecl.cpp index ef7e537c9a..bab3dc5b51 100644 --- a/src/ast/ast_typedecl.cpp +++ b/src/ast/ast_typedecl.cpp @@ -236,7 +236,6 @@ namespace das TT->constant = (TT->constant || autoT->constant) && !autoT->removeConstant && !TT->removeConstant; TT->temporary = (TT->temporary || autoT->temporary) && !autoT->removeTemporary && !TT->removeTemporary; TT->safeWhenUninitialized = TT->safeWhenUninitialized || autoT->safeWhenUninitialized; - if ( (autoT->removeDim || TT->removeDim) && TT->dim.size() ) TT->dim.erase(TT->dim.begin()); TT->removeConstant = false; TT->removeDim = false; TT->removeRef = false; diff --git a/src/builtin/module_builtin_ast_annotations_1.cpp b/src/builtin/module_builtin_ast_annotations_1.cpp index d3e4c7f85d..2c6ff3e41a 100644 --- a/src/builtin/module_builtin_ast_annotations_1.cpp +++ b/src/builtin/module_builtin_ast_annotations_1.cpp @@ -43,8 +43,9 @@ namespace das { >("dim","dimCompat"); addField("fixedDim"); addField("fixedDimExpr"); + addField("typeMacroExpr"); // compat view (FIXED_ARRAY_REWORK.md, 1b): the typeMacro/typeDecl/tag payload - // moved to typeMacroExpr; daslib keeps reading it under the .dimExpr name + // moved to typeMacroExpr; legacy reads still resolve under the .dimExpr name addPropertyExtConst< vector & (TypeDecl::*)(), &TypeDecl::dimExprCompat, const vector & (TypeDecl::*)() const, &TypeDecl::dimExprCompat diff --git a/tests/fixed_array/test_target_inference.das b/tests/fixed_array/test_target_inference.das index 91ac4cd962..ac64a1e03b 100644 --- a/tests/fixed_array/test_target_inference.das +++ b/tests/fixed_array/test_target_inference.das @@ -22,6 +22,10 @@ def dim_name_var(var a : auto(TT)[]) : string { return typeinfo typename(type) } +def peel_name(a : auto(TT)) : string { + return typeinfo typename(type) +} + def zeroed_count(a : auto(TT)) : int { var z = default var n = 0 @@ -71,6 +75,10 @@ def test_peel_one_level(t : T?) { let i4 : int[4] t |> equal(pick(i4), "dim") } + t |> run("TT -[] unwraps one level") @(t : T?) { + let i24 : int[2][4] + t |> equal(peel_name(i24), "int const[4]") + } } [test] diff --git a/tests/typemacro/_typemacro_mod.das b/tests/typemacro/_typemacro_mod.das index e7137b9395..0b91cb6729 100644 --- a/tests/typemacro/_typemacro_mod.das +++ b/tests/typemacro/_typemacro_mod.das @@ -1,14 +1,13 @@ // Stage 0 coverage for the tFixedArray rework (FIXED_ARRAY_REWORK.md). -// Raw AstTypeMacro exercising the typeMacro argument payload that Stage 1b moves -// from TypeDecl.dimExpr to the dedicated typeMacroExpr field: -// dimExpr[0] — ExprConstString with the macro name -// dimExpr[1] — type argument (ExprTypeDecl) -// dimExpr[2] — ExprConstInt (size) -// dimExpr[3] — ExprConstBool (wrap into an array or not) -// dimExpr[4] — ExprConstString (mode tag, "arr" expected when wrapping) -// NOTE: this module reads .dimExpr through the compat property (ported to -// typeMacroExpr in Stage 5); array results are built with make_fixed_array_type -// since Stage 1f (the .dim compat view is read-only). +// Raw AstTypeMacro exercising the typeMacro argument payload in the dedicated +// TypeDecl.typeMacroExpr field: +// typeMacroExpr[0] — ExprConstString with the macro name +// typeMacroExpr[1] — type argument (ExprTypeDecl) +// typeMacroExpr[2] — ExprConstInt (size) +// typeMacroExpr[3] — ExprConstBool (wrap into an array or not) +// typeMacroExpr[4] — ExprConstString (mode tag, "arr" expected when wrapping) +// NOTE: this module reads the typeMacroExpr field directly; array results are +// built with make_fixed_array_type (the .dim compat view is read-only). options gen2 options no_aot @@ -21,33 +20,33 @@ require daslib/ast_boost [type_macro(name="tm_make")] class TmMakeTypeMacro : AstTypeMacro { def override visit(prog : ProgramPtr; mod : Module?; td : TypeDeclPtr; passT : TypeDeclPtr) : TypeDeclPtr { - if (length(td.dimExpr) != 5) { + if (length(td.typeMacroExpr) != 5) { macro_error(compiling_program(), td.at, "tm_make expects 4 arguments: type, size, wrap, tag") return <- TypeDeclPtr() } - if (!(td.dimExpr[2] is ExprConstInt)) { + if (!(td.typeMacroExpr[2] is ExprConstInt)) { macro_error(compiling_program(), td.at, "tm_make: size must be a constant integer") return <- TypeDeclPtr() } - if (!(td.dimExpr[3] is ExprConstBool)) { + if (!(td.typeMacroExpr[3] is ExprConstBool)) { macro_error(compiling_program(), td.at, "tm_make: wrap must be a constant bool") return <- TypeDeclPtr() } - if (!(td.dimExpr[4] is ExprConstString)) { + if (!(td.typeMacroExpr[4] is ExprConstString)) { macro_error(compiling_program(), td.at, "tm_make: tag must be a constant string") return <- TypeDeclPtr() } - let count = (td.dimExpr[2] as ExprConstInt).value - let wrap = (td.dimExpr[3] as ExprConstBool).value - if (wrap && (td.dimExpr[4] as ExprConstString).value != "arr") { - macro_error(compiling_program(), td.at, "tm_make: wrapping requires the 'arr' tag, got '{(td.dimExpr[4] as ExprConstString).value}'") + let count = (td.typeMacroExpr[2] as ExprConstInt).value + let wrap = (td.typeMacroExpr[3] as ExprConstBool).value + if (wrap && (td.typeMacroExpr[4] as ExprConstString).value != "arr") { + macro_error(compiling_program(), td.at, "tm_make: wrapping requires the 'arr' tag, got '{(td.typeMacroExpr[4] as ExprConstString).value}'") return <- TypeDeclPtr() } // generic path: type argument not inferred yet — return auto so the compiler retries - if (td.dimExpr[1]._type == null) { + if (td.typeMacroExpr[1]._type == null) { var auto_type : TypeDeclPtr - if (td.dimExpr[1] is ExprTypeDecl) { - auto_type = clone_type((td.dimExpr[1] as ExprTypeDecl).typeexpr) + if (td.typeMacroExpr[1] is ExprTypeDecl) { + auto_type = clone_type((td.typeMacroExpr[1] as ExprTypeDecl).typeexpr) } else { auto_type = new TypeDecl(baseType = Type.autoinfer) } @@ -56,7 +55,7 @@ class TmMakeTypeMacro : AstTypeMacro { } return <- auto_type } - var final_type = clone_type(td.dimExpr[1]._type) + var final_type = clone_type(td.typeMacroExpr[1]._type) if (wrap) { final_type = make_fixed_array_type(count, final_type) } diff --git a/tests/typemacro/test_basic.das b/tests/typemacro/test_basic.das index 05e2fd6ad8..2ee89cb5fc 100644 --- a/tests/typemacro/test_basic.das +++ b/tests/typemacro/test_basic.das @@ -1,6 +1,6 @@ // Stage 0 coverage for the tFixedArray rework (FIXED_ARRAY_REWORK.md). -// Direct tests for the typeMacro payload surface that Stage 1b migrates off -// TypeDecl.dimExpr: all four grammar forms, const-argument extraction +// Direct tests for the typeMacro payload surface that Stage 1b migrated to +// TypeDecl.typeMacroExpr: all four grammar forms, const-argument extraction // (int/bool/string), type arguments, and typedecl(expr). // Deep indirect coverage lives in tests/option + tests/hash_map + tests/delegate // (typemacro_boost-based); this file covers the raw forms those don't touch. diff --git a/tutorials/macros/type_macro_mod.das b/tutorials/macros/type_macro_mod.das index 1ed66284a7..2ad57ee501 100644 --- a/tutorials/macros/type_macro_mod.das +++ b/tutorials/macros/type_macro_mod.das @@ -9,16 +9,16 @@ options no_aot // // When the compiler sees `padded(type, 4)` in a type position it: // 1. Parses it into a TypeDecl with baseType = Type.typeMacro -// 2. Stores the arguments in `td.dimExpr`: -// td.dimExpr[0] — ExprConstString with the macro name ("padded") -// td.dimExpr[1] — the type argument (ExprTypeDecl wrapping type) -// td.dimExpr[2] — the size argument (ExprConstInt with value 4) +// 2. Stores the arguments in `td.typeMacroExpr`: +// td.typeMacroExpr[0] — ExprConstString with the macro name ("padded") +// td.typeMacroExpr[1] — the type argument (ExprTypeDecl wrapping type) +// td.typeMacroExpr[2] — the size argument (ExprConstInt with value 4) // 3. Calls visit() so the macro can return the resolved type // // The `passT` parameter is non-null only in a generic context — it // carries the actual argument type being matched against the parameter. // In a concrete declaration (`var x : padded(type, 4)`) passT -// is null and td.dimExpr[1]._type is already inferred. +// is null and td.typeMacroExpr[1]._type is already inferred. module type_macro_mod @@ -31,27 +31,27 @@ class PaddedTypeMacro : AstTypeMacro { //! Type macro that resolves `padded(type, N)` to `T[N]`. def override visit(prog : ProgramPtr; mod : Module?; td : TypeDeclPtr; passT : TypeDeclPtr) : TypeDeclPtr { // --- argument validation --- - // dimExpr must have exactly 3 entries: name + type + size. - if (length(td.dimExpr) != 3) { + // typeMacroExpr must have exactly 3 entries: name + type + size. + if (length(td.typeMacroExpr) != 3) { macro_error(compiling_program(), td.at, "padded expects 2 arguments: type and size") return <- TypeDeclPtr() } // The size argument must be a constant integer. - if (!(td.dimExpr[2] is ExprConstInt)) { + if (!(td.typeMacroExpr[2] is ExprConstInt)) { macro_error(compiling_program(), td.at, "padded: second argument must be a constant integer") return <- TypeDeclPtr() } - let count = (td.dimExpr[2] as ExprConstInt).value + let count = (td.typeMacroExpr[2] as ExprConstInt).value // --- generic path --- // When the type argument is not yet inferred (e.g. in a generic // parameter like `padded(type, 4)`) we return a type // with autoinfer so the compiler can match and deduce TT. - if (td.dimExpr[1]._type == null) { + if (td.typeMacroExpr[1]._type == null) { var auto_type : TypeDeclPtr - if (td.dimExpr[1] is ExprTypeDecl) { + if (td.typeMacroExpr[1] is ExprTypeDecl) { // Clone the unresolved type expression (e.g. auto(TT)). - auto_type = clone_type((td.dimExpr[1] as ExprTypeDecl).typeexpr) + auto_type = clone_type((td.typeMacroExpr[1] as ExprTypeDecl).typeexpr) } else { // Fallback: pure auto-infer. auto_type = new TypeDecl(baseType = Type.autoinfer) @@ -63,7 +63,7 @@ class PaddedTypeMacro : AstTypeMacro { // --- concrete path --- // The type argument is fully inferred. Clone it and add the // dimension to produce the final array type (e.g. float[4]). - var final_type = clone_type(td.dimExpr[1]._type) + var final_type = clone_type(td.typeMacroExpr[1]._type) final_type = make_fixed_array_type(count, final_type) return <- final_type } diff --git a/utils/fix-lint-errors/main.das b/utils/fix-lint-errors/main.das index dbf2f5200c..caeec30941 100644 --- a/utils/fix-lint-errors/main.das +++ b/utils/fix-lint-errors/main.das @@ -669,7 +669,7 @@ class FixerVisitor : AstVisitor { def is_workhorse_numeric(t : TypeDecl? const) : bool { //! Six numeric workhorse scalars (int, uint, int64, uint64, float, double). //! Mirrors perf_lint's gate so the fixer matches what the lint flags. - return false if (t == null || !empty(t.dim)) + return false if (t == null || t.baseType == Type.tFixedArray) let bt = t.baseType return (bt == Type.tInt || bt == Type.tUInt || bt == Type.tInt64 || bt == Type.tUInt64 From 8d0dd7fc71a90afb79b69750448abe27031dd14b Mon Sep 17 00:00:00 2001 From: Boris Batkin Date: Wed, 10 Jun 2026 21:52:31 -0700 Subject: [PATCH 17/23] fixed-array stage 6: delete TypeDecl dim/dimExpr + compat properties; makeTypeInfo chain-native; dasFormatter grammar flip; serialization v91 Co-Authored-By: Claude Fable 5 --- FIXED_ARRAY_REWORK.md | 43 ++++ include/daScript/ast/ast_typedecl.h | 78 +++---- modules/dasSQLITE/src/dasSQLITE.main.cpp | 2 +- src/ast/ast_debug_info_helper.cpp | 85 +++----- src/ast/ast_escape_analysis.cpp | 1 - src/ast/ast_infer_type.cpp | 84 ++----- src/ast/ast_infer_type_helper.cpp | 56 +---- src/ast/ast_infer_type_make.cpp | 1 - src/ast/ast_program.cpp | 2 +- src/ast/ast_typedecl.cpp | 205 +----------------- src/ast/ast_validate.cpp | 10 - .../module_builtin_ast_annotations_1.cpp | 12 - src/builtin/module_builtin_ast_serialize.cpp | 28 +-- src/parser/parser_impl.cpp | 21 -- src/parser/parser_impl.h | 3 - tests-cpp/small/test_fixed_array_interop.cpp | 31 +-- tests-cpp/small/test_fixed_array_typedecl.cpp | 119 +++------- tests/typemacro/_typemacro_mod.das | 2 +- utils/dasFormatter/ds_parser.cpp | 77 +++---- utils/dasFormatter/ds_parser.ypp | 37 ++-- 20 files changed, 237 insertions(+), 660 deletions(-) diff --git a/FIXED_ARRAY_REWORK.md b/FIXED_ARRAY_REWORK.md index 7fe6a453a8..b05faac8d7 100644 --- a/FIXED_ARRAY_REWORK.md +++ b/FIXED_ARRAY_REWORK.md @@ -479,6 +479,49 @@ 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). ### Stage 7 — Docs + merge RST (type system, generic programming), CHANGELOG, version bump; final full-matrix CI + diff --git a/include/daScript/ast/ast_typedecl.h b/include/daScript/ast/ast_typedecl.h index 7f5217151e..2c1fbb8585 100644 --- a/include/daScript/ast/ast_typedecl.h +++ b/include/daScript/ast/ast_typedecl.h @@ -257,31 +257,13 @@ namespace das { TypeDeclPtr secondType = nullptr; // map.second vector argTypes; // block arguments vector argNames; - vector dim; - vector dimExpr; - // tFixedArray rework (FIXED_ARRAY_REWORK.md), Stage 1a. fixedDim/fixedDimExpr are - // meaningful only on baseType==tFixedArray nodes (one size per node, element in - // firstType, dimAuto/dimConst sentinels apply). typeMacroExpr takes over dimExpr's - // typeMacro/typeDecl/tag payload duty in Stage 1b. dim/dimExpr above are deleted - // at the end of Stage 1. + // tFixedArray rework (FIXED_ARRAY_REWORK.md): fixedDim/fixedDimExpr are meaningful + // only on baseType==tFixedArray nodes (one size per node, element in firstType, + // dimAuto/dimConst sentinels apply). typeMacroExpr carries the typeMacro/typeDecl/tag + // payload (dimExpr's old duty). int32_t fixedDim = 0; ExpressionPtr fixedDimExpr = nullptr; vector typeMacroExpr; - // das-binding compat view for `.dimExpr` — "whatever dimExpr used to hold for this - // node" (the typeMacro/typeDecl/tag payload lives in typeMacroExpr since Stage 1b). - // Dies with dim/dimExpr at the end of Stage 1, replaced by the flattened-array view. - __forceinline vector & dimExprCompat() { - return typeMacroExpr.empty() ? dimExpr : typeMacroExpr; - } - __forceinline const vector & dimExprCompat() const { - return typeMacroExpr.empty() ? dimExpr : typeMacroExpr; - } - // das-binding compat view for `.dim` — the flattened (outermost-first) sizes of the - // tFixedArray chain, recomputed on every read into per-node transient storage. - // Read-only by design: das writers use ast_boost`make_fixed_array_type. Dies with - // dim/dimExpr at the end of Stage 1. - const vector & dimCompat() const; - mutable vector dimCompatCache; // dimCompat scratch — transient, never serialized union { struct { bool ref : 1 ; @@ -818,12 +800,12 @@ namespace das { } __forceinline bool TypeDecl::isRange() const { - return (baseType==Type::tRange || baseType==Type::tURange || - baseType==Type::tRange64 || baseType==Type::tURange64) && dim.size()==0; + return baseType==Type::tRange || baseType==Type::tURange || + baseType==Type::tRange64 || baseType==Type::tURange64; } __forceinline bool TypeDecl::isString() const { - return (baseType==Type::tString) && dim.size()==0; + return baseType==Type::tString; } __forceinline bool TypeDecl::isSimpleType(Type typ) const { @@ -831,7 +813,7 @@ namespace das { } __forceinline bool TypeDecl::isArray() const { - return dim.size()!=0 || baseType==Type::tFixedArray; + return baseType==Type::tFixedArray; } __forceinline bool TypeDecl::isFixedArray() const { @@ -847,23 +829,23 @@ namespace das { } __forceinline bool TypeDecl::isHandle() const { - return (baseType==Type::tHandle) && (dim.size()==0); + return baseType==Type::tHandle; } __forceinline bool TypeDecl::isStructure() const { - return (baseType==Type::tStructure) && (dim.size()==0); + return baseType==Type::tStructure; } __forceinline bool TypeDecl::isTuple() const { - return (baseType==Type::tTuple) && (dim.size()==0); + return baseType==Type::tTuple; } __forceinline bool TypeDecl::isFunction() const { - return (baseType==Type::tFunction) && (dim.size()==0); + return baseType==Type::tFunction; } __forceinline bool TypeDecl::isVariant() const { - return (baseType==Type::tVariant) && (dim.size()==0); + return baseType==Type::tVariant; } __forceinline bool TypeDecl::isMoveableValue() const { @@ -878,47 +860,47 @@ namespace das { } __forceinline bool TypeDecl::isGoodIteratorType() const { - return baseType==Type::tIterator && dim.size()==0 && firstType; + return baseType==Type::tIterator && firstType; } __forceinline bool TypeDecl::isGoodBlockType() const { - return baseType==Type::tBlock && dim.size()==0; + return baseType==Type::tBlock; } __forceinline bool TypeDecl::isGoodFunctionType() const { - return baseType==Type::tFunction && dim.size()==0; + return baseType==Type::tFunction; } __forceinline bool TypeDecl::isGoodLambdaType() const { - return baseType==Type::tLambda && dim.size()==0; + return baseType==Type::tLambda; } __forceinline bool TypeDecl::isGoodArrayType() const { - return baseType==Type::tArray && dim.size()==0 && firstType; + return baseType==Type::tArray && firstType; } __forceinline bool TypeDecl::isGoodTupleType() const { - return baseType==Type::tTuple && dim.size()==0; + return baseType==Type::tTuple; } __forceinline bool TypeDecl::isGoodVariantType() const { - return baseType==Type::tVariant && dim.size()==0; + return baseType==Type::tVariant; } __forceinline bool TypeDecl::isGoodTableType() const { - return baseType==Type::tTable && dim.size()==0 && firstType && secondType; + return baseType==Type::tTable && firstType && secondType; } __forceinline bool TypeDecl::isVoid() const { - return (baseType==Type::tVoid) && (dim.size()==0); + return baseType==Type::tVoid; } __forceinline bool TypeDecl::isPointer() const { - return (baseType==Type::tPointer) && (dim.size()==0); + return baseType==Type::tPointer; } __forceinline bool TypeDecl::isSmartPointer() const { - return (baseType==Type::tPointer) && (smartPtr) && (dim.size()==0); + return (baseType==Type::tPointer) && smartPtr; } __forceinline bool TypeDecl::isVoidPointer() const { @@ -926,17 +908,16 @@ namespace das { } __forceinline bool TypeDecl::isBitfield() const { - return ((baseType==Type::tBitfield) || (baseType==Type::tBitfield8) || - (baseType==Type::tBitfield16) || (baseType==Type::tBitfield64)) - && (dim.size()==0); + return (baseType==Type::tBitfield) || (baseType==Type::tBitfield8) || + (baseType==Type::tBitfield16) || (baseType==Type::tBitfield64); } __forceinline bool TypeDecl::isIterator() const { - return (baseType==Type::tIterator) && (dim.size()==0); + return baseType==Type::tIterator; } __forceinline bool TypeDecl::isLambda() const { - return (baseType==Type::tLambda) && (dim.size()==0); + return baseType==Type::tLambda; } __forceinline bool TypeDecl::isEnumT() const { @@ -945,11 +926,10 @@ namespace das { } __forceinline bool TypeDecl::isEnum() const { - return isEnumT() && (dim.size()==0); + return isEnumT(); } __forceinline bool TypeDecl::isVectorType() const { - if ( dim.size() ) return false; return isBaseVectorType(); } diff --git a/modules/dasSQLITE/src/dasSQLITE.main.cpp b/modules/dasSQLITE/src/dasSQLITE.main.cpp index dd033f79eb..020844d167 100644 --- a/modules/dasSQLITE/src/dasSQLITE.main.cpp +++ b/modules/dasSQLITE/src/dasSQLITE.main.cpp @@ -112,7 +112,7 @@ void Module_dasSQLITE::initMain() { for ( auto & pfn : this->functions.each() ) { // ok, lets fix up everything returning uint8? into returning string# and make it unsafe operation if ( pfn->result->isPointer() && pfn->result->firstType && - pfn->result->firstType->baseType==Type::tUInt8 && pfn->result->firstType->dim.size()==0 ) { + pfn->result->firstType->baseType==Type::tUInt8 ) { pfn->result = new TypeDecl(Type::tString); pfn->result->constant = true; pfn->result->temporary = true; diff --git a/src/ast/ast_debug_info_helper.cpp b/src/ast/ast_debug_info_helper.cpp index c16d8c9dcb..5def3ca6da 100644 --- a/src/ast/ast_debug_info_helper.cpp +++ b/src/ast/ast_debug_info_helper.cpp @@ -208,38 +208,15 @@ namespace das { } TypeInfo * DebugInfoHelper::makeTypeInfo ( TypeInfo * info, const TypeDeclPtr & type ) { - if ( type->baseType==Type::tFixedArray ) { - // runtime TypeInfo stays flattened forever (FIXED_ARRAY_REWORK.md): collect the - // chain dims onto a scratch element view; canonical head qualifiers ride along. - // The mangled-name cache key is unaffected — chain and flattened text are identical. - const TypeDecl * t = type; - vector dims; - while ( t->baseType==Type::tFixedArray ) { - dims.push_back(t->fixedDim); - DAS_ASSERTF(t->firstType, "tFixedArray chain without an element"); - t = t->firstType; - } - // SHALLOW scratch borrowing the element's children: the TypeDecl copy ctor - // deep-clones sub-nodes and gc_local frees only the head, so a full clone - // here leaks the subtree on the thread root (one per FA-typed debug info) - gc_local flat(new TypeDecl(t->baseType)); - flat->structType = t->structType; - flat->enumType = t->enumType; - flat->annotation = t->annotation; - flat->firstType = t->firstType; - flat->secondType = t->secondType; - flat->argTypes = t->argTypes; - flat->argNames = t->argNames; - flat->alias = t->alias; - flat->module = t->module; - flat->at = t->at; - flat->flags = t->flags; - flat->dim.insert(flat->dim.begin(), dims.begin(), dims.end()); - flat->ref = type->ref; - flat->constant = type->constant; - flat->temporary = type->temporary; - flat->implicit = type->implicit; - return makeTypeInfo(info, flat); + // runtime TypeInfo stays flattened forever (FIXED_ARRAY_REWORK.md): chain dims emit + // straight into info->dim; element fields read through elemType, whole-type semantics + // (size, quals, gc, names) stay on the chain head + vector dims; + const TypeDecl * elemType = type; + while ( elemType->baseType==Type::tFixedArray ) { + dims.push_back(elemType->fixedDim); + DAS_ASSERTF(elemType->firstType, "tFixedArray chain without an element"); + elemType = elemType->firstType; } if ( info==nullptr ) { string mangledName = type->getMangledName(); @@ -248,24 +225,24 @@ namespace das { info = debugInfo->makeNode(); tmn2t[mangledName] = info; } - info->type = type->baseType; - info->dimSize = (uint32_t) type->dim.size(); + info->type = elemType->baseType; + info->dimSize = (uint32_t) dims.size(); if ( info->dimSize ) { info->dim = (uint32_t *) debugInfo->allocate(sizeof(uint32_t) * info->dimSize ); for ( uint32_t i=0, is=info->dimSize; i!=is; ++i ) { - info->dim[i] = type->dim[i]; + info->dim[i] = dims[i]; } } - if ( type->baseType==Type::tStructure ) { - info->structType = makeStructureDebugInfo(*type->structType); - } else if ( type->isEnumT() ) { - info->enumType = type->enumType ? makeEnumDebugInfo(*type->enumType) : nullptr; - } else if ( type->annotation ) { + if ( elemType->baseType==Type::tStructure ) { + info->structType = makeStructureDebugInfo(*elemType->structType); + } else if ( elemType->isEnumT() ) { + info->enumType = elemType->enumType ? makeEnumDebugInfo(*elemType->enumType) : nullptr; + } else if ( elemType->annotation ) { #if DAS_THREAD_SAFE_ANNOTATIONS - auto annName = debugInfo->allocateCachedName("~" + type->annotation->module->name + "::" + type->annotation->name); + auto annName = debugInfo->allocateCachedName("~" + elemType->annotation->module->name + "::" + elemType->annotation->name); info->annotation_or_name = ((TypeAnnotation*)(intptr_t(annName)|1)); #else - info->annotation_or_name = type->annotation; + info->annotation_or_name = elemType->annotation; #endif } else { info->annotation_or_name = nullptr; @@ -291,9 +268,9 @@ namespace das { info->flags |= TypeInfo::flag_isImplicit; if (type->isRawPod()) info->flags |= TypeInfo::flag_isRawPod; - if (type->smartPtr) { + if (elemType->smartPtr) { info->flags |= TypeInfo::flag_isSmartPtr; - if ( type->smartPtrNative ) + if ( elemType->smartPtrNative ) info->flags |= TypeInfo::flag_isSmartPtrNative; } auto gcf = type->gcFlags(); @@ -301,36 +278,36 @@ namespace das { info->flags |= TypeInfo::flag_heapGC; if ( gcf & TypeDecl::gcFlag_stringHeap ) info->flags |= TypeInfo::flag_stringHeapGC; - if ( type->firstType ) { - info->firstType = makeTypeInfo(nullptr, type->firstType); - } else if ( type->baseType==Type::tStructure && type->structType->parent!=nullptr ) { - gc_local tdecl(new TypeDecl(type->structType->parent)); + if ( elemType->firstType ) { + info->firstType = makeTypeInfo(nullptr, elemType->firstType); + } else if ( elemType->baseType==Type::tStructure && elemType->structType->parent!=nullptr ) { + gc_local tdecl(new TypeDecl(elemType->structType->parent)); info->firstType = makeTypeInfo(nullptr, tdecl); } else { info->firstType = nullptr; } - if ( type->secondType ) { - info->secondType = makeTypeInfo(nullptr, type->secondType); + if ( elemType->secondType ) { + info->secondType = makeTypeInfo(nullptr, elemType->secondType); } else { info->secondType = nullptr; } info->argTypes = nullptr; - info->argCount = uint32_t(type->argTypes.size()); + info->argCount = uint32_t(elemType->argTypes.size()); if ( info->argCount ) { info->argTypes = (TypeInfo **) debugInfo->allocate(sizeof(TypeInfo *) * info->argCount ); for ( uint32_t i=0, is=info->argCount; i!=is; ++i ) { - info->argTypes[i] = makeTypeInfo(nullptr, type->argTypes[i]); + info->argTypes[i] = makeTypeInfo(nullptr, elemType->argTypes[i]); } } info->argNames = nullptr; - auto argNamesCount = uint32_t(type->argNames.size()); + auto argNamesCount = uint32_t(elemType->argNames.size()); t2cppTypeName[info] = describeCppType(type, CpptSubstitureRef::no,CpptSkipRef::yes, CpptSkipConst::yes); if ( argNamesCount ) { DAS_ASSERT(info->argCount == 0 || info->argCount == argNamesCount); info->argCount = argNamesCount; info->argNames = (const char **) debugInfo->allocate(sizeof(char *) * info->argCount ); for ( uint32_t i=0, is=info->argCount; i!=is; ++i ) { - info->argNames[i] = debugInfo->allocateCachedName(type->argNames[i]); + info->argNames[i] = debugInfo->allocateCachedName(elemType->argNames[i]); } } auto mangledName = type->getMangledName(); diff --git a/src/ast/ast_escape_analysis.cpp b/src/ast/ast_escape_analysis.cpp index 9f5c7dc9e8..574ee44157 100644 --- a/src/ast/ast_escape_analysis.cpp +++ b/src/ast/ast_escape_analysis.cpp @@ -30,7 +30,6 @@ namespace das { static bool isEscapeFreePtr ( const TypeDeclPtr & typ ) { if ( !typ ) return false; if ( typ->ref || typ->constant ) return false; - if ( !typ->dim.empty() ) return false; if ( typ->baseType != Type::tPointer ) return false; if ( typ->smartPtr ) return false; if ( !typ->firstType || typ->firstType->baseType != Type::tStructure ) return false; diff --git a/src/ast/ast_infer_type.cpp b/src/ast/ast_infer_type.cpp index e262e118a9..7b4b6bcff9 100644 --- a/src/ast/ast_infer_type.cpp +++ b/src/ast/ast_infer_type.cpp @@ -1780,7 +1780,7 @@ namespace das { } } else { auto stf = sttf->type; - if (stf && stf->dim.size() == 0 && (stf->baseType == Type::tBlock || stf->baseType == Type::tFunction || stf->baseType == Type::tLambda)) { + if (stf && (stf->baseType == Type::tBlock || stf->baseType == Type::tFunction || stf->baseType == Type::tLambda)) { reportAstChanged(); expr->isInvokeMethod = false; // we replace invoke(foo.GetValue,cast foo,...) with invoke(foo.GetValue,...) @@ -2202,7 +2202,7 @@ namespace das { return new ExprConstInt(expr->at, align); } else if (expr->trait == "is_dim") { reportAstChanged(); - return new ExprConstBool(expr->at, expr->typeexpr->dim.size() != 0 || expr->typeexpr->baseType == Type::tFixedArray); + return new ExprConstBool(expr->at, expr->typeexpr->baseType == Type::tFixedArray); } else if (expr->trait == "dim") { if (expr->typeexpr->isExprTypeAnywhere()) { error("typeinfo(dim " + describeType(expr->typeexpr) + ") is not fully inferred, expecting resolved dim", "", "", @@ -2212,9 +2212,6 @@ namespace das { if (expr->typeexpr->baseType == Type::tFixedArray) { reportAstChanged(); return new ExprConstInt(expr->at, expr->typeexpr->fixedDim); - } else if (expr->typeexpr->dim.size()) { - reportAstChanged(); - return new ExprConstInt(expr->at, expr->typeexpr->dim[0]); } else { error("typeinfo(dim non_array) is prohibited, " + describeType(expr->typeexpr), "", "", expr->at, CompilationError::invalid_typeinfo_dim); @@ -2238,9 +2235,6 @@ namespace das { if (expr->typeexpr->secondType->baseType == Type::tFixedArray) { reportAstChanged(); return new ExprConstInt(expr->at, expr->typeexpr->secondType->fixedDim); - } else if (expr->typeexpr->secondType->dim.size()) { - reportAstChanged(); - return new ExprConstInt(expr->at, expr->typeexpr->secondType->dim[0]); } else { error("typeinfo(dim_table_value table<...,non_array>) is prohibited, " + describeType(expr->typeexpr), "", "", expr->at, CompilationError::invalid_typeinfo_dim_table); @@ -2402,7 +2396,7 @@ namespace das { } else if (expr->trait == "is_iterable") { reportAstChanged(); bool iterable = false; - if (expr->typeexpr->dim.size() || expr->typeexpr->baseType == Type::tFixedArray) { + if (expr->typeexpr->baseType == Type::tFixedArray) { iterable = true; } else if (expr->typeexpr->isGoodIteratorType()) { iterable = true; @@ -2433,16 +2427,16 @@ namespace das { return new ExprConstBool(expr->at, expr->typeexpr->isNumeric()); } else if (expr->trait == "is_int") { reportAstChanged(); - return new ExprConstBool(expr->at, expr->typeexpr->baseType == Type::tInt && expr->typeexpr->dim.size() == 0); + return new ExprConstBool(expr->at, expr->typeexpr->baseType == Type::tInt); } else if (expr->trait == "is_int64") { reportAstChanged(); - return new ExprConstBool(expr->at, expr->typeexpr->baseType == Type::tInt64 && expr->typeexpr->dim.size() == 0); + return new ExprConstBool(expr->at, expr->typeexpr->baseType == Type::tInt64); } else if (expr->trait == "is_float") { reportAstChanged(); - return new ExprConstBool(expr->at, expr->typeexpr->baseType == Type::tFloat && expr->typeexpr->dim.size() == 0); + return new ExprConstBool(expr->at, expr->typeexpr->baseType == Type::tFloat); } else if (expr->trait == "is_double") { reportAstChanged(); - return new ExprConstBool(expr->at, expr->typeexpr->baseType == Type::tDouble && expr->typeexpr->dim.size() == 0); + return new ExprConstBool(expr->at, expr->typeexpr->baseType == Type::tDouble); } else if (expr->trait == "is_numeric_comparable") { reportAstChanged(); return new ExprConstBool(expr->at, expr->typeexpr->isNumericComparable()); @@ -2920,7 +2914,7 @@ namespace das { reportMissingFinalizer("finalizer mismatch ", expr->at, expr->subexpr->type); return Visitor::visit(expr); } - } else if (finalizeType->dim.size() || finalizeType->baseType == Type::tFixedArray) { + } else if (finalizeType->baseType == Type::tFixedArray) { reportAstChanged(); auto cloneFn = new ExprCall(expr->at, "finalize_dim"); cloneFn->arguments.push_back(expr->subexpr->clone()); @@ -3004,10 +2998,6 @@ namespace das { expr->at, CompilationError::cant_ascend); } else if (expr->subexpr->type->baseType == Type::tHandle) { const auto &subt = expr->subexpr->type; - if (!subt->dim.empty()) { - error("array of handled type cannot be allocated on the heap: '" + describeType(subt) + "'", "", "", - expr->at, CompilationError::invalid_ascend_array_handle_type); - } if (!subt->annotation->canNew()) { error("cannot allocate on the heap this handled type at all: '" + describeType(subt) + "'", "", "", expr->at, CompilationError::invalid_ascend_handle_type); @@ -3277,7 +3267,7 @@ namespace das { expr->type->constant |= seT->constant; } } else { - if (ixT->isRange() && (seT->isGoodArrayType() || seT->dim.size() || seT->baseType==Type::tFixedArray)) { // a[range(b)] into subset(a,range(b)) + if (ixT->isRange() && (seT->isGoodArrayType() || seT->baseType==Type::tFixedArray)) { // a[range(b)] into subset(a,range(b)) auto subset = new ExprCall(expr->at, "subarray"); subset->arguments.push_back(expr->subexpr->clone()); subset->arguments.push_back(expr->index->clone()); @@ -3303,7 +3293,7 @@ namespace das { expr->type = new TypeDecl(seT->getVectorBaseType()); expr->type->ref = seT->ref; expr->type->constant = seT->constant; - } else if (!seT->dim.size() && seT->baseType != Type::tFixedArray) { + } else if (seT->baseType != Type::tFixedArray) { error("type can't be indexed: '" + describeType(seT) + "'", "", "", expr->subexpr->at, CompilationError::cant_index); return Visitor::visit(expr); @@ -3311,19 +3301,11 @@ namespace das { error("type dimensions are not resolved yet: '" + describeType(seT) + "'", "", "", expr->subexpr->at, CompilationError::not_resolved_yet_array_dimension); return Visitor::visit(expr); - } else if (seT->baseType == Type::tFixedArray) { + } else { // peel one level - same element-access pattern as tArray TypeDecl::clone(expr->type, seT->firstType); expr->type->ref = true; expr->type->constant |= seT->constant; - } else { - TypeDecl::clone(expr->type, seT); - expr->type->ref = true; - expr->type->dim.erase(expr->type->dim.begin()); - if (!expr->type->dimExpr.empty()) { - expr->type->dimExpr.erase(expr->type->dimExpr.begin()); - } - expr->type->constant |= seT->constant; } } propagateTempType(expr->subexpr->type, expr->type); // foo#[a] = a# @@ -3377,7 +3359,7 @@ namespace das { // } // expr->type = seT->annotation->makeIndexType(expr->subexpr, expr->index); // expr->type->constant |= seT->constant; - } else if (seT->isVectorType() || seT->isGoodArrayType() || seT->dim.size() || seT->baseType==Type::tFixedArray) { + } else if (seT->isVectorType() || seT->isGoodArrayType() || seT->baseType==Type::tFixedArray) { // arrays accept int/int64/uint/uint64; vector and fixed_array — int/uint only if (seT->isGoodArrayType() ? !ixT->isIndexExt() : !ixT->isIndex()) { expr->type = nullptr; @@ -3408,20 +3390,6 @@ namespace das { expr->type->firstType = new TypeDecl(*seT->firstType); expr->type->firstType->constant |= seT->constant; } - } else if (seT->dim.size()) { - if (!seT->isAutoArrayResolved()) { - error("type dimensions are not resolved yet '" + describeType(seT) + "'", "", "", - expr->subexpr->at, CompilationError::not_resolved_yet_array_dimension); - return Visitor::visit(expr); - } else { - expr->type = new TypeDecl(Type::tPointer); - expr->type->firstType = new TypeDecl(*seT); - expr->type->firstType->dim.erase(expr->type->firstType->dim.begin()); - if (!expr->type->firstType->dimExpr.empty()) { - expr->type->firstType->dimExpr.erase(expr->type->firstType->dimExpr.begin()); - } - expr->type->firstType->constant |= seT->constant; - } } } else { // pointer safe-at: a?[index] where a : T* @@ -3472,7 +3440,7 @@ namespace das { expr->type = new TypeDecl(Type::tPointer); expr->type->firstType = new TypeDecl(*seT->secondType); expr->type->constant |= seT->constant; - } else if (expr->subexpr->type->dim.size() || expr->subexpr->type->baseType==Type::tFixedArray) { + } else if (expr->subexpr->type->baseType==Type::tFixedArray) { if (!safeExpression(expr)) { error("safe-index of fixed_array<> must be inside the 'unsafe' block", "", "", expr->at, CompilationError::unsafe_fixed_array_safe_index); @@ -3488,15 +3456,7 @@ namespace das { expr->subexpr->at, CompilationError::not_resolved_yet_array_dimension); } expr->type = new TypeDecl(Type::tPointer); - if (seT->baseType==Type::tFixedArray) { - expr->type->firstType = new TypeDecl(*seT->firstType); - } else { - expr->type->firstType = new TypeDecl(*seT); - expr->type->firstType->dim.erase(expr->type->firstType->dim.begin()); - if (!expr->type->firstType->dimExpr.empty()) { - expr->type->firstType->dimExpr.erase(expr->type->firstType->dimExpr.begin()); - } - } + expr->type->firstType = new TypeDecl(*seT->firstType); expr->type->firstType->constant |= seT->constant; } else if (expr->subexpr->type->isVectorType() && expr->subexpr->type->isRef()) { if (!ixT->isIndex()) { @@ -4869,7 +4829,7 @@ namespace das { ExpressionPtr InferTypes::visit(ExprWith *expr) { if (auto wT = expr->with->type) { StructurePtr pSt = nullptr; - if (wT->dim.size() || wT->baseType == Type::tFixedArray) { + if (wT->baseType == Type::tFixedArray) { error("with array in undefined, " + describeType(wT), "", "", expr->at, CompilationError::invalid_with_array_type); } else if (wT->isStructure()) { @@ -4965,13 +4925,6 @@ namespace das { pVar->type = new TypeDecl(*src->type->firstType); pVar->type->ref = true; pVar->type->constant |= src->type->constant; - } else if (src->type->dim.size()) { - pVar->type = new TypeDecl(*src->type); - pVar->type->ref = true; - pVar->type->dim.erase(pVar->type->dim.begin()); - if (!pVar->type->dimExpr.empty()) { - pVar->type->dimExpr.erase(pVar->type->dimExpr.begin()); - } } else if (src->type->isGoodIteratorType()) { if (src->type->isConst()) { error("can't iterate over const iterator", "", "", @@ -5067,8 +5020,7 @@ namespace das { } // now, for the one where we did not find anything if (that->type) { - if (!that->type->dim.size() && - that->type->baseType != Type::tFixedArray && + if (that->type->baseType != Type::tFixedArray && !that->type->isGoodIteratorType() && !that->type->isGoodArrayType() && !that->type->isRange() && @@ -5851,7 +5803,7 @@ namespace das { } if (!expr->func) { auto var = findMatchingBlockOrLambdaVariable(expr->name); // if this is lambda_var(args...) or such - if (var && var->type && var->type->dim.size() == 0) { // we promote to vname(args...) to invoke(vname,args...) + if (var && var->type) { // we promote to vname(args...) to invoke(vname,args...) auto bt = var->type->baseType; if (bt == Type::tBlock || bt == Type::tLambda || bt == Type::tFunction) { reportAstChanged(); @@ -5871,7 +5823,7 @@ namespace das { if (expr->name.find("::") == string::npos) { // we only promote to Struct`call() or self->call() if its not blah::call, _::call, or __::call auto memFn = bt->structType->findField(expr->name); if (memFn && memFn->type) { - if (memFn->type->dim.size() == 0 && (memFn->type->baseType == Type::tBlock || memFn->type->baseType == Type::tLambda || memFn->type->baseType == Type::tFunction)) { + if (memFn->type->baseType == Type::tBlock || memFn->type->baseType == Type::tLambda || memFn->type->baseType == Type::tFunction) { reportAstChanged(); if (memFn->classMethod) { auto self = new ExprVar(expr->at, "self"); diff --git a/src/ast/ast_infer_type_helper.cpp b/src/ast/ast_infer_type_helper.cpp index afc52f82ff..ab47e330c0 100644 --- a/src/ast/ast_infer_type_helper.cpp +++ b/src/ast/ast_infer_type_helper.cpp @@ -87,25 +87,6 @@ namespace das { decl->at,CompilationError::invalid_type); */ } - /* - if ( decl->dim.size() && decl->ref ) { - error("can't declare an array of references, " + describeType(decl), "", "", - decl->at,CompilationError::invalid_type); - } - */ - uint64_t size = 1; - for (auto di : decl->dim) { - if (di <= 0) { - error("array dimension can't be 0 or less: '" + describeType(decl) + "'", "", "", - decl->at, CompilationError::invalid_array_dimension); - } - size *= di; - if (size > 0x7fffffff) { - error("array is too big: '" + describeType(decl) + "'", "", "", - decl->at, CompilationError::exceeds_array); - break; - } - } if (decl->baseType == Type::tFunction || decl->baseType == Type::tLambda || decl->baseType == Type::tBlock || decl->baseType == Type::tVariant || decl->baseType == Type::tTuple) { if (decl->argNames.size() && decl->argNames.size() != decl->argTypes.size()) { @@ -118,10 +99,6 @@ namespace das { } } if (decl->baseType == Type::tVoid) { - if (decl->dim.size()) { - error("can't declare an array of void: '" + describeType(decl) + "'", "", "", - decl->at, CompilationError::invalid_array); - } if (decl->ref) { error("can't declare a void reference: '" + describeType(decl) + "'", "", "", decl->at, CompilationError::invalid_type); @@ -418,7 +395,6 @@ namespace das { resT->ref = (resT->ref || decl->ref) && !decl->removeRef; resT->constant = (resT->constant || constUnderDim || decl->constant) && !decl->removeConstant; resT->temporary = (resT->temporary || decl->temporary) && !decl->removeTemporary; - resT->dim = decl->dim; resT->aotAlias = false; resT->alias.clear(); if ( decl->removeDim && resT->baseType==Type::tFixedArray && resT->firstType ) { @@ -562,7 +538,6 @@ namespace das { resT->temporary = (resT->temporary || decl->temporary) && !decl->removeTemporary; resT->implicit = (resT->implicit || decl->implicit); resT->explicitConst = (resT->explicitConst || decl->explicitConst); - resT->dim = decl->dim; resT->aotAlias = false; // resT->alias.clear(); // this may speed things up, but it breaks typemacro-based aliases if ( decl->removeDim && resT->baseType==Type::tFixedArray && resT->firstType ) { @@ -741,36 +716,7 @@ namespace das { type->at, CompilationError::invalid_array_dimension); } } - if (type->baseType != Type::typeDecl && type->baseType != Type::typeMacro) { - for (size_t i = 0, is = type->dim.size(); i != is; ++i) { - if (type->dim[i] == TypeDecl::dimConst) { - if (idimExpr.size() && type->dimExpr[i]) { - if (auto constExpr = getConstExpr(type->dimExpr[i])) { - if (constExpr->type->isIndex()) { - auto cI = static_cast(constExpr); - auto dI = cI->getValue(); - if (dI > 0) { - type->dim[i] = dI; - any = true; - } else { - error("array dimension can't be 0 or less", "", "", - type->at, CompilationError::invalid_array_dimension); - } - } else { - error("array dimension must be int32 or uint32", "", "", - type->at, CompilationError::invalid_array_dimension_type); - } - } else { - error("array dimension must be constant", "", "", - type->at, CompilationError::invalid_array_dimension); - } - } else { - error("can't deduce array dimension", "", "", - type->at, CompilationError::invalid_array_dimension); - } - } - } - } else if (type->baseType == Type::typeDecl) { + if (type->baseType == Type::typeDecl) { if (type->typeMacroExpr.size() != 1) { error("typeDecl must have exactly one dimension", "", "", type->at, CompilationError::invalid_type_dimension); diff --git a/src/ast/ast_infer_type_make.cpp b/src/ast/ast_infer_type_make.cpp index fc5811107f..825932113d 100644 --- a/src/ast/ast_infer_type_make.cpp +++ b/src/ast/ast_infer_type_make.cpp @@ -449,7 +449,6 @@ namespace das { return Visitor::visit(expr); } uint32_t resDim = uint32_t(expr->variants.size()); - resT->dim.clear(); if (resDim > 1) { resT = makeFixedArrayTypeDecl(int32_t(resDim), resT); } diff --git a/src/ast/ast_program.cpp b/src/ast/ast_program.cpp index e1fc657f90..e509168b8b 100644 --- a/src/ast/ast_program.cpp +++ b/src/ast/ast_program.cpp @@ -327,7 +327,7 @@ namespace das { } ExpressionPtr Program::makeConst ( const LineInfo & at, const TypeDeclPtr & type, vec4f value ) { - if ( type->dim.size() || type->ref ) return nullptr; + if ( type->ref ) return nullptr; switch ( type->baseType ) { case Type::tBool: return new ExprConstBool(at, cast::to(value)); case Type::tInt8: return new ExprConstInt8(at, cast::to(value)); diff --git a/src/ast/ast_typedecl.cpp b/src/ast/ast_typedecl.cpp index bab3dc5b51..c27ea2f558 100644 --- a/src/ast/ast_typedecl.cpp +++ b/src/ast/ast_typedecl.cpp @@ -116,11 +116,6 @@ namespace das } bool TypeDecl::isExprTypeAnywhere(das_set & dep) const { - for ( auto di : dim ) { - if ( di==TypeDecl::dimConst ) { - return true; - } - } if ( baseType==Type::tFixedArray && fixedDim==TypeDecl::dimConst ) { return true; } @@ -142,11 +137,6 @@ namespace das } bool TypeDecl::isExprType() const { - for ( auto di : dim ) { - if ( di==TypeDecl::dimConst ) { - return true; - } - } if ( baseType==Type::tFixedArray && fixedDim==TypeDecl::dimConst ) { return true; } @@ -162,11 +152,6 @@ namespace das TypeDeclPtr TypeDecl::visit ( Visitor & vis ) { if ( baseType==Type::typeDecl || baseType==Type::typeMacro ) { - for ( size_t i=0, is=dimExpr.size(); i!=is; ++i ) { - if ( dimExpr[i] ) { - dimExpr[i] = dimExpr[i]->visit(vis); - } - } for ( size_t i=0, is=typeMacroExpr.size(); i!=is; ++i ) { if ( typeMacroExpr[i] ) { typeMacroExpr[i] = typeMacroExpr[i]->visit(vis); @@ -176,14 +161,6 @@ namespace das if ( fixedDim==TypeDecl::dimConst && fixedDimExpr ) { fixedDimExpr = fixedDimExpr->visit(vis); } - } else { - for ( size_t i=0, is=dim.size(); i!=is; ++i ) { - if ( dim[i]==TypeDecl::dimConst ) { - if ( ivisit(vis); - } - } - } } if ( firstType ) { vis.preVisit(firstType); @@ -277,7 +254,6 @@ namespace das if ( !decl->alias.empty() && aliases.find(decl->alias)==aliases.end() ) { auto TT = new TypeDecl(*pass); TT->alias = decl->alias; - TT->dim.clear(); TT->constant = false; TT->temporary = false; TT->ref = false; @@ -288,7 +264,6 @@ namespace das if ( !pass->alias.empty() && aliases.find(pass->alias)==aliases.end() ) { auto TT = new TypeDecl(*decl); TT->alias = pass->alias; - TT->dim.clear(); TT->constant = false; TT->temporary = false; TT->ref = false; @@ -393,18 +368,6 @@ namespace das // auto & can't be inferred from non-ref if ( autoT->ref && !initT->isRef() ) return nullptr; - // auto[][][] can't be inferred from non-array - if ( autoT->dim.size() ) { - if ( autoT->dim.size()!=initT->dim.size() ) - return nullptr; - for ( size_t di=0, dis=autoT->dim.size(); di!=dis; ++di ) { - int32_t aDI = autoT->dim[di]; - int32_t iDI = initT->dim[di]; - if ( aDI!=TypeDecl::dimAuto && aDI!=iDI ) { - return nullptr; - } - } - } // auto[] has to match a fixed array of the same outer size (dimAuto = wildcard) if ( autoT->baseType==Type::tFixedArray ) { if ( initT->baseType!=Type::tFixedArray || !initT->firstType ) @@ -527,9 +490,6 @@ namespace das void TypeDecl::getLookupHash(uint64_t & hash) const { hash = hashmix(hash, baseType); hash = hashmix(hash, flagsWithoutAliasCache(this)); - for ( auto d : dim ) { - hash = hashmix(hash, d); - } if ( baseType==Type::tFixedArray ) { hash = hashmix(hash, fixedDim); } @@ -593,15 +553,6 @@ namespace das for ( auto & argN : argNames ) { hb.updateString(argN); } - for ( auto & d : dim ) { - hb.update(d); - } - for ( auto & de : dimExpr ) { - hb.update(de != nullptr); - if ( de ) { - wr << *(de); - } - } if ( baseType==Type::tFixedArray ) { hb.update(fixedDim); hb.update(fixedDimExpr != nullptr); @@ -660,15 +611,6 @@ namespace das for ( auto & argN : argNames ) { hb.updateString(argN); } - for ( auto & d : dim ) { - hb.update(d); - } - for ( auto & de : dimExpr ) { - hb.update(de != nullptr); - if ( de ) { - wr << *(de); - } - } if ( baseType==Type::tFixedArray ) { hb.update(fixedDim); hb.update(fixedDimExpr != nullptr); @@ -902,13 +844,6 @@ namespace das if ( constant ) { stream << " const"; } - for ( auto d : dim ) { - if ( d==-1 ) { - stream << "[]"; - } else { - stream << "[" << d << "]"; - } - } if ( baseType==Type::tFixedArray ) { for ( const TypeDecl * t = this; t && t->baseType==Type::tFixedArray; t = t->firstType ) { if ( t->fixedDim==TypeDecl::dimAuto ) { @@ -969,15 +904,6 @@ namespace das structType = decl.structType; enumType = decl.enumType; annotation = decl.annotation; - dim = decl.dim; - dimExpr.reserve(decl.dimExpr.size()); - for ( auto & de : decl.dimExpr ) { - if ( de ) { - dimExpr.push_back(de->clone()); - } else { - dimExpr.push_back(nullptr); - } - } fixedDim = decl.fixedDim; if ( decl.fixedDimExpr ) { fixedDimExpr = decl.fixedDimExpr->clone(); @@ -1025,15 +951,6 @@ namespace das dest->structType = src->structType; dest->enumType = src->enumType; dest->annotation = src->annotation; - dest->dim = src->dim; - dest->dimExpr.resize(src->dimExpr.size()); - for ( size_t i=0; i!=src->dimExpr.size(); ++i ) { - if ( src->dimExpr[i] ) { - dest->dimExpr[i] = src->dimExpr[i]->clone(); - } else { - dest->dimExpr[i] = nullptr; - } - } dest->fixedDim = src->fixedDim; dest->fixedDimExpr = src->fixedDimExpr ? src->fixedDimExpr->clone() : nullptr; dest->typeMacroExpr.resize(src->typeMacroExpr.size()); @@ -1068,7 +985,6 @@ namespace das if ( firstType ) firstType->gc_collect(target, from); if ( secondType ) secondType->gc_collect(target, from); for ( auto t : argTypes ) if ( t ) t->gc_collect(target, from); - for ( auto & de : dimExpr ) if ( de ) de->gc_collect(target, from); if ( fixedDimExpr ) fixedDimExpr->gc_collect(target, from); for ( auto & de : typeMacroExpr ) if ( de ) de->gc_collect(target, from); // this?? @@ -1096,19 +1012,6 @@ namespace das aliasCacheHasAlias = hasAny; return hasAny; } - const vector & TypeDecl::dimCompat() const { - if ( baseType==Type::tFixedArray ) { - vector flat; - for ( auto t = this; t->baseType==Type::tFixedArray && t->firstType; t = t->firstType ) { - flat.push_back(t->fixedDim); - } - // refill only when stale, so a nested read of the same node can't invalidate - // an iterator held over a previous read - if ( dimCompatCache != flat ) dimCompatCache = das::move(flat); - return dimCompatCache; - } - return dim; - } TypeDecl * TypeDecl::findAlias ( const string & name, bool allowAuto, bool * constUnderDim ) { if (!aliasCacheValid) computeAliasCache(); if (!aliasCacheHasAlias) return nullptr; // proven no aliases anywhere @@ -1805,12 +1708,6 @@ namespace das return false; } if ( fixedDim != decl.fixedDim ) return false; - if ( dim.size() != decl.dim.size() ) return false; - for ( size_t i=0, is=dim.size(); i!=is; ++i ) { - if ( dim[i] != decl.dim[i] ) { - return false; - } - } if ( !isSameExactNullType(firstType,decl.firstType) ) return false; if ( !isSameExactNullType(secondType,decl.secondType) ) return false; if ( argTypes.size() != decl.argTypes.size() ) return false; @@ -1859,9 +1756,6 @@ namespace das return false; } } - if ( dim!=decl.dim ) { - return false; - } switch ( baseType ) { case Type::tHandle: if ( annotation!=decl.annotation ) { @@ -2343,12 +2237,6 @@ namespace das } bool TypeDecl::isAliasOrExpr() const { - // if its dim[expr] - for ( auto di : dim ) { - if ( di==TypeDecl::dimConst ) { - return true; - } - } // auto is auto.... or auto....? if ( baseType==Type::typeMacro ) { return true; @@ -2391,11 +2279,6 @@ namespace das } bool TypeDecl::isAutoArrayResolved() const { - for ( auto di : dim ) { - if ( di==TypeDecl::dimAuto || di==TypeDecl::dimConst ) { - return false; - } - } for ( const TypeDecl * t = this; t && t->baseType==Type::tFixedArray; t = t->firstType ) { if ( t->fixedDim==TypeDecl::dimAuto || t->fixedDim==TypeDecl::dimConst ) { return false; @@ -2406,12 +2289,6 @@ namespace das bool TypeDecl::isAuto() const { // auto is auto.... or auto....?5 - // also dim[] is aito - for (auto di : dim) { - if (di == TypeDecl::dimAuto) { - return true; - } - } switch ( baseType ) { case Type::typeMacro: case Type::typeDecl: @@ -2453,12 +2330,6 @@ namespace das bool TypeDecl::isAutoWithoutOptions(bool & hasOptions) const { // auto is auto.... or auto....? - // also dim[] is aito - for ( auto di : dim ) { - if ( di==TypeDecl::dimAuto ) { - return true; - } - } if ( baseType==Type::typeMacro ) { return true; } else if ( baseType==Type::typeDecl ) { @@ -2503,12 +2374,6 @@ namespace das bool TypeDecl::isAutoOrAlias() const { // auto is auto.... or auto....? - // also dim[] is aito - for (auto di : dim) { - if (di == TypeDecl::dimAuto) { - return true; - } - } switch ( baseType ) { case Type::typeMacro: case Type::typeDecl: @@ -2550,7 +2415,7 @@ namespace das } bool TypeDecl::isFoldable() const { - if ( dim.size() || ref ) + if ( ref ) return false; switch ( baseType ) { case Type::tBool: @@ -2593,8 +2458,6 @@ namespace das } bool TypeDecl::isCtorType() const { - if ( dim.size() ) - return false; switch ( baseType ) { // case Type::tBool: case Type::tInt8: @@ -2633,9 +2496,7 @@ namespace das } bool TypeDecl::isTableKeyType() const { - if ( dim.size() ) { - return false; - } else if ( isWorkhorseType() ) { + if ( isWorkhorseType() ) { return true; } else if ( baseType==Type::tHandle && annotation->isRefType()==false ) { return true; @@ -2645,8 +2506,6 @@ namespace das } bool TypeDecl::isWorkhorseType() const { - if ( dim.size() ) - return false; switch ( baseType ) { case Type::tBool: case Type::tInt8: @@ -2690,8 +2549,6 @@ namespace das } bool TypeDecl::canInitWithZero() const { - if ( dim.size() ) - return false; if ( isVectorType() ) return true; switch ( baseType ) { @@ -2720,8 +2577,6 @@ namespace das } bool TypeDecl::isPolicyType() const { - if ( dim.size() ) - return true; switch ( baseType ) { case Type::tVoid: case Type::tEnumeration: @@ -2755,7 +2610,7 @@ namespace das } bool TypeDecl::isVecPolicyType() const { - if ( dim.size() || baseType==Type::tFixedArray ) + if ( baseType==Type::tFixedArray ) return false; if ( baseType==Type::tString ) { return false; @@ -2784,7 +2639,6 @@ namespace das } bool TypeDecl::isRefType() const { - if ( dim.size() ) return true; if ( baseType==Type::tHandle ) return annotation->isRefType(); return baseType==Type::tTuple || baseType==Type::tVariant || baseType==Type::tStructure || baseType==Type::tArray || @@ -2838,11 +2692,6 @@ namespace das // type chain is fully resolved, and not aliased \ auto bool TypeDecl::isFullySealed(das_set & all ) const { if (baseType==Type::autoinfer || baseType==Type::alias) return false; - for (auto di : dim) { - if (di == TypeDecl::dimAuto) { - return false; - } - } if ( baseType==Type::tFixedArray && fixedDim==TypeDecl::dimAuto ) { return false; } @@ -2930,7 +2779,6 @@ namespace das } bool TypeDecl::isFloatOrDouble() const { - if (dim.size() != 0) return false; switch (baseType) { case Type::tFloat: case Type::tDouble: @@ -2941,12 +2789,10 @@ namespace das } bool TypeDecl::isBool() const { - if (dim.size() != 0) return false; return baseType==Type::tBool; } bool TypeDecl::isInteger() const { - if (dim.size() != 0) return false; switch (baseType) { case Type::tInt: case Type::tUInt: @@ -2967,7 +2813,6 @@ namespace das } bool TypeDecl::isSignedInteger() const { - if (dim.size() != 0) return false; switch (baseType) { case Type::tInt: case Type::tInt8: @@ -2980,7 +2825,6 @@ namespace das } bool TypeDecl::isUnsignedInteger() const { - if (dim.size() != 0) return false; switch (baseType) { case Type::tUInt: case Type::tBitfield: @@ -2997,7 +2841,6 @@ namespace das } bool TypeDecl::isSignedIntegerOrIntVec() const { - if (dim.size() != 0) return false; switch (baseType) { case Type::tInt: case Type::tInt8: @@ -3013,7 +2856,6 @@ namespace das } bool TypeDecl::isUnsignedIntegerOrIntVec() const { - if (dim.size() != 0) return false; switch (baseType) { case Type::tUInt: case Type::tBitfield: @@ -3034,7 +2876,6 @@ namespace das bool TypeDecl::isNumeric() const { - if (dim.size() != 0) return false; switch (baseType) { case Type::tInt: case Type::tUInt: @@ -3057,7 +2898,6 @@ namespace das } bool TypeDecl::isNumericStorage() const { - if (dim.size() != 0) return false; switch (baseType) { case Type::tInt8: case Type::tUInt8: @@ -3070,7 +2910,6 @@ namespace das } bool TypeDecl::isNumericComparable() const { - if (dim.size() != 0) return false; switch (baseType) { case Type::tInt: case Type::tUInt: @@ -3089,11 +2928,11 @@ namespace das } bool TypeDecl::isIndex() const { - return (baseType==Type::tInt || baseType==Type::tUInt) && dim.size()==0; + return baseType==Type::tInt || baseType==Type::tUInt; } bool TypeDecl::isIndexExt() const { - return (baseType==Type::tInt || baseType==Type::tUInt || baseType==Type::tInt64 || baseType==Type::tUInt64) && dim.size()==0; + return baseType==Type::tInt || baseType==Type::tUInt || baseType==Type::tInt64 || baseType==Type::tUInt64; } int TypeDecl::getTupleFieldOffset ( int index ) const { @@ -3368,9 +3207,6 @@ namespace das uint64_t TypeDecl::getCountOf64() const { uint64_t size = 1; - for ( auto i : dim ) { - size *= i; - } for ( const TypeDecl * t = this; t && t->baseType==Type::tFixedArray; t = t->firstType ) { size *= uint64_t(t->fixedDim); } @@ -3388,12 +3224,6 @@ namespace das } uint64_t TypeDecl::getSizeOf64(bool & failed) const { - for ( auto di : dim ) { - if ( di==TypeDecl::dimAuto || di==TypeDecl::dimConst ) { - failed = true; - return 0; - } - } for ( const TypeDecl * t = this; t && t->baseType==Type::tFixedArray; t = t->firstType ) { if ( t->fixedDim==TypeDecl::dimAuto || t->fixedDim==TypeDecl::dimConst ) { failed = true; @@ -3414,13 +3244,7 @@ namespace das // size of one outer-level element, same meaning the dim-vector form had return firstType ? firstType->getSizeOf64() : 0; } - uint64_t size = 1; - if ( dim.size() > 1 ) { - for ( size_t i=1, is=dim.size(); i!=is; ++i ) { - size *= dim[i]; - } - } - return getBaseSizeOf64() * size; + return getBaseSizeOf64(); } int TypeDecl::findArgumentIndex( const string & name ) const { @@ -3515,11 +3339,6 @@ namespace das } return; } - if ( dim.size() ) { - for ( auto d : dim ) { - ss << "[" << d << "]"; - } - } if ( fullName ) { if ( removeDim ) ss << "-[]"; if ( removeConstant ) ss << "-C"; @@ -3879,7 +3698,7 @@ namespace das } bool isConstRedundantForCpp ( const TypeDecl * type ) { - if ( type->dim.size() ) return false; + if ( type->baseType==Type::tFixedArray ) return false; if ( type->isVectorType() ) return true; switch ( type->baseType ) { case Type::tBool: @@ -3975,11 +3794,6 @@ namespace das skipConst = CpptSkipConst::yes; } } - if ( type->dim.size() ) { - for ( size_t d=0, ds=type->dim.size(); d!=ds; ++d ) { - stream << "TDim<"; - } - } if ( baseType==Type::alias ) { stream << "DAS_COMMENT(alias)"; } else if ( baseType==Type::autoinfer ) { @@ -4092,11 +3906,6 @@ namespace das } else { stream << das_to_cppString(baseType); } - if ( type->dim.size() ) { - for ( auto itd=type->dim.rbegin(), itds=type->dim.rend(); itd!=itds; ++itd ) { - stream << "," << *itd << ">"; - } - } if ( skipConst==CpptSkipConst::no ) { if ( type->constant ) { stream << " const "; diff --git a/src/ast/ast_validate.cpp b/src/ast/ast_validate.cpp index 325af26e0d..23616efd69 100644 --- a/src/ast/ast_validate.cpp +++ b/src/ast/ast_validate.cpp @@ -95,9 +95,6 @@ namespace das { for ( auto & argType : td->argTypes ) { trackTypeDeclTree(argType, "argTypes[]"); } - for ( auto de : td->dimExpr ) { - trackExpression(de); - } for ( auto de : td->typeMacroExpr ) { trackExpression(de); } @@ -157,14 +154,7 @@ namespace das { if ( !trackNode(td, td->at) ) { reportDuplicateTypeDecl(td); } - // dimExpr: standard visitor only visits dimExpr when dim==dimConst or baseType is typeDecl/typeMacro. - // After inference resolves dimensions, resolved dimExpr stays on gc_root but is skipped. if ( td->baseType != Type::typeDecl && td->baseType != Type::typeMacro ) { - for ( size_t i=0, is=td->dimExpr.size(); i!=is; ++i ) { - if ( td->dimExpr[i] && (i >= td->dim.size() || td->dim[i] != TypeDecl::dimConst) ) { - trackExpression(td->dimExpr[i]); - } - } // tag payload sits in typeMacroExpr of an autoinfer node — never visited by // the standard visitor (it only walks typeMacroExpr on typeDecl/typeMacro) for ( auto de : td->typeMacroExpr ) { diff --git a/src/builtin/module_builtin_ast_annotations_1.cpp b/src/builtin/module_builtin_ast_annotations_1.cpp index 2c6ff3e41a..022fbcf9dc 100644 --- a/src/builtin/module_builtin_ast_annotations_1.cpp +++ b/src/builtin/module_builtin_ast_annotations_1.cpp @@ -35,21 +35,9 @@ namespace das { addField("secondType"); addField("argTypes"); addField("argNames"); - // compat view (FIXED_ARRAY_REWORK.md, 1f): flattened (outermost-first) sizes of - // the tFixedArray chain under the legacy .dim name; READ-ONLY - das writers use - // ast_boost`make_fixed_array_type - addProperty< - const vector & (TypeDecl::*)() const, &TypeDecl::dimCompat - >("dim","dimCompat"); addField("fixedDim"); addField("fixedDimExpr"); addField("typeMacroExpr"); - // compat view (FIXED_ARRAY_REWORK.md, 1b): the typeMacro/typeDecl/tag payload - // moved to typeMacroExpr; legacy reads still resolve under the .dimExpr name - addPropertyExtConst< - vector & (TypeDecl::*)(), &TypeDecl::dimExprCompat, - const vector & (TypeDecl::*)() const, &TypeDecl::dimExprCompat - >("dimExpr","dimExprCompat"); addFieldEx ( "flags", "flags", offsetof(TypeDecl, flags), makeTypeDeclFlags() ); addField("alias"); addField("at"); diff --git a/src/builtin/module_builtin_ast_serialize.cpp b/src/builtin/module_builtin_ast_serialize.cpp index 81878de6f9..34d193015a 100644 --- a/src/builtin/module_builtin_ast_serialize.cpp +++ b/src/builtin/module_builtin_ast_serialize.cpp @@ -1145,17 +1145,17 @@ namespace das { argTypes.empty(), argNames.empty()); break; case Type::alias: - ser << alias << firstType << dim << dimExpr; + ser << alias << firstType; DAS_VERIFYF_MULTI(!annotation, !structType, !enumType, !secondType, !alias.empty(), argTypes.empty(), argNames.empty()); break; case option: - ser << argTypes << dim << dimExpr; + ser << argTypes; DAS_VERIFYF_MULTI(!annotation, !structType, !enumType, !firstType, !secondType, alias.empty(), !argTypes.empty(), argNames.empty()); break; case autoinfer: - ser << dim << dimExpr << alias; + ser << alias; DAS_VERIFYF_MULTI(!annotation, !structType, !enumType, !firstType, !secondType, argTypes.empty(), argNames.empty()); break; @@ -1185,7 +1185,7 @@ namespace das { case tFloat4: case tDouble: case tString: - ser << alias << dim << dimExpr; + ser << alias; DAS_VERIFYF_MULTI(!annotation, !structType, !enumType, !firstType, !secondType, argTypes.empty(), argNames.empty()); break; @@ -1193,17 +1193,17 @@ namespace das { case tURange: case tRange64: case tURange64: // blow up! - ser << alias << dim << dimExpr; + ser << alias; DAS_VERIFYF_MULTI(!annotation, !structType, !enumType, !firstType, !secondType, argTypes.empty(), argNames.empty()); break; case tStructure: - ser << alias << structType << dim << dimExpr; + ser << alias << structType; DAS_VERIFYF_MULTI(!annotation, !!structType, !enumType, !firstType, !secondType, argTypes.empty(), argNames.empty()); break; case tHandle: - ser << alias << annotation << dim << dimExpr; + ser << alias << annotation; DAS_VERIFYF_MULTI(!!annotation, !structType, !enumType, !firstType, !secondType, argTypes.empty(), argNames.empty()); break; @@ -1211,7 +1211,7 @@ namespace das { case tEnumeration8: case tEnumeration16: case tEnumeration64: - ser << alias << enumType << dim << dimExpr; + ser << alias << enumType; DAS_VERIFYF_MULTI(!annotation, !structType, !!enumType, !firstType, !secondType, argTypes.empty(), argNames.empty()); break; @@ -1219,31 +1219,31 @@ namespace das { case tBitfield8: case tBitfield16: case tBitfield64: - ser << alias << argNames << dim << dimExpr; + ser << alias << argNames; DAS_VERIFYF_MULTI(!annotation, !structType, !enumType, !firstType, !secondType, argTypes.empty()); break; case tIterator: case tPointer: case tArray: // blow up! - ser << alias << firstType << dim << dimExpr; + ser << alias << firstType; DAS_VERIFYF_MULTI(!annotation, !structType, !enumType, !secondType, argTypes.empty(), argNames.empty()); break; case tFunction: case tLambda: case tBlock: - ser << alias << firstType << argTypes << argNames << dim << dimExpr; + ser << alias << firstType << argTypes << argNames; DAS_VERIFYF_MULTI(!annotation, !structType, !enumType, !secondType); break; case tTable: - ser << alias << firstType << secondType << dim << dimExpr; + ser << alias << firstType << secondType; DAS_VERIFYF_MULTI(!annotation, !structType, !enumType, !!firstType, argTypes.empty(), argNames.empty()); break; case tTuple: case tVariant: - ser << alias << argTypes << argNames << dim << dimExpr; + ser << alias << argTypes << argNames; DAS_VERIFYF_MULTI(!annotation, !structType, !enumType, !firstType, !secondType, !argTypes.empty()); break; @@ -2707,7 +2707,7 @@ namespace das { } uint32_t AstSerializer::getVersion () { - static constexpr uint32_t currentVersion = 90; // 90: typeMacroExpr/fixedDim/fixedDimExpr (FIXED_ARRAY_REWORK.md, 1b) + static constexpr uint32_t currentVersion = 91; // 91: TypeDecl dim/dimExpr fields deleted (FIXED_ARRAY_REWORK.md, stage 6) return currentVersion; } diff --git a/src/parser/parser_impl.cpp b/src/parser/parser_impl.cpp index af6ccaf258..a9404140c3 100644 --- a/src/parser/parser_impl.cpp +++ b/src/parser/parser_impl.cpp @@ -92,27 +92,6 @@ namespace das { return attachDimChain(fa, typeDecl); } - // THE THIRD PARSER (FIXED_ARRAY_REWORK.md): utils/dasFormatter still builds the old - // dim/dimExpr world; this overload dies with those fields at the end of Stage 1 - void appendDimExpr ( TypeDecl * typeDecl, Expression * dimExpr ) { - if ( dimExpr ) { - int32_t dI = TypeDecl::dimConst; - if ( dimExpr->rtti_isConstant() ) { // note: this shortcut is here so we don`t get extra infer pass on every array - auto cI = (ExprConst *) dimExpr; - auto bt = cI->baseType; - if ( bt==Type::tInt || bt==Type::tUInt ) { - dI = cast::to(cI->value); - } - } - typeDecl->dim.push_back(dI); - typeDecl->dimExpr.push_back(dimExpr); - } else { - typeDecl->dim.push_back(TypeDecl::dimAuto); - typeDecl->dimExpr.push_back(nullptr); - } - typeDecl->removeDim = false; - } - vector sequenceToList ( Expression * arguments ) { vector argList; if (arguments == nullptr) { diff --git a/src/parser/parser_impl.h b/src/parser/parser_impl.h index fa7dcac59e..a4bc49d7c0 100644 --- a/src/parser/parser_impl.h +++ b/src/parser/parser_impl.h @@ -83,9 +83,6 @@ namespace das { TypeDecl * appendDimExpr ( TypeDecl * chain, Expression * dimExpr, const LineInfo & at ); TypeDecl * attachDimChain ( TypeDecl * chain, TypeDecl * element ); TypeDecl * appendAutoDim ( TypeDecl * typeDecl, const LineInfo & at ); - // THE THIRD PARSER (FIXED_ARRAY_REWORK.md): utils/dasFormatter still builds the old - // dim/dimExpr world; this overload dies with those fields at the end of Stage 1 - void appendDimExpr ( TypeDecl * typeDecl, Expression * dimExpr ); void implAddGenericFunction ( yyscan_t scanner, Function * func ); Expression * ast_arrayComprehension (yyscan_t scanner, const LineInfo & loc, vector * iters, Expression * srcs, Expression * subexpr, Expression * where, const LineInfo & forend, bool genSyntax, bool tableSyntax ); diff --git a/tests-cpp/small/test_fixed_array_interop.cpp b/tests-cpp/small/test_fixed_array_interop.cpp index cc39df4d73..95ed820272 100644 --- a/tests-cpp/small/test_fixed_array_interop.cpp +++ b/tests-cpp/small/test_fixed_array_interop.cpp @@ -8,16 +8,6 @@ #include "daScript/ast/ast.h" using namespace das; -namespace { - -TypeDecl * makeOldDim ( Type bt, std::initializer_list dims ) { - auto t = new TypeDecl(bt); - for ( auto d : dims ) t->dim.push_back(d); - return t; -} - -} - TEST_CASE("typeFactory produces tFixedArray chains") { gc_guard guard; ModuleLibrary lib; @@ -70,36 +60,27 @@ TEST_CASE("typeFactory produces tFixedArray chains") { TEST_CASE("makeTypeInfo flattens tFixedArray chains byte-equal to dim-vector input") { gc_guard guard; ModuleLibrary lib; - SUBCASE("flattened TypeInfo parity") { - DebugInfoHelper faHelp, oldHelp; + SUBCASE("flattened TypeInfo") { + DebugInfoHelper faHelp; auto fa = typeFactory::make(lib); auto faInfo = faHelp.makeTypeInfo(nullptr, fa); - auto oldInfo = oldHelp.makeTypeInfo(nullptr, makeOldDim(Type::tInt,{3,4})); CHECK_EQ(faInfo->type, Type::tInt); - REQUIRE_EQ(faInfo->dimSize, oldInfo->dimSize); REQUIRE_EQ(faInfo->dimSize, 2u); CHECK_EQ(faInfo->dim[0], 3u); CHECK_EQ(faInfo->dim[1], 4u); - CHECK_EQ(faInfo->size, oldInfo->size); - CHECK_EQ(faInfo->flags, oldInfo->flags); - CHECK_EQ(faInfo->hash, oldInfo->hash); + CHECK_EQ(faInfo->size, 48u); } SUBCASE("head qualifiers ride into the flattened flags") { - DebugInfoHelper faHelp, oldHelp; + DebugInfoHelper faHelp; auto fa = makeFixedArrayTypeDecl(4, new TypeDecl(Type::tInt)); fa->constant = true; auto faInfo = faHelp.makeTypeInfo(nullptr, fa); - auto old = makeOldDim(Type::tInt,{4}); - old->constant = true; - auto oldInfo = oldHelp.makeTypeInfo(nullptr, old); CHECK((faInfo->flags & TypeInfo::flag_isConst) != 0u); - CHECK_EQ(faInfo->flags, oldInfo->flags); - CHECK_EQ(faInfo->hash, oldInfo->hash); } - SUBCASE("cache key is shared - chain and flattened mangled text are identical") { + SUBCASE("cache key is the chain mangled name") { DebugInfoHelper help; auto a = help.makeTypeInfo(nullptr, typeFactory::make(lib)); - auto b = help.makeTypeInfo(nullptr, makeOldDim(Type::tInt,{4})); + auto b = help.makeTypeInfo(nullptr, typeFactory::make(lib)); CHECK_EQ(a, b); // same helper, same mangled key, same TypeInfo node } } diff --git a/tests-cpp/small/test_fixed_array_typedecl.cpp b/tests-cpp/small/test_fixed_array_typedecl.cpp index 21a3f9e383..25228d3144 100644 --- a/tests-cpp/small/test_fixed_array_typedecl.cpp +++ b/tests-cpp/small/test_fixed_array_typedecl.cpp @@ -1,8 +1,7 @@ -// Stage 1a of the tFixedArray rework (FIXED_ARRAY_REWORK.md): the structural -// Type::tFixedArray machinery is additive and nothing in the language produces FA nodes -// yet, so this suite hand-builds them and proves the new arms against equivalent -// old-style dim-vector nodes — text (describe/mangled/cpp), size family, identity, -// lifecycle, hashes, and classification parity. +// Structural Type::tFixedArray machinery (FIXED_ARRAY_REWORK.md): text +// (describe/mangled/cpp), size family, identity, lifecycle, hashes, and +// classification. Originally a parity suite against equivalent dim-vector nodes; +// those fields are gone, so the expected values are pinned as literals. #include #include "daScript/daScript.h" #include "daScript/ast/ast.h" @@ -18,12 +17,6 @@ TypeDecl * makeFA ( int32_t d, TypeDecl * elem ) { return fa; } -TypeDecl * makeOldDim ( Type bt, std::initializer_list dims ) { - auto t = new TypeDecl(bt); - for ( auto d : dims ) t->dim.push_back(d); - return t; -} - TypeDecl * makeFAChain ( Type bt, std::initializer_list dims ) { auto t = new TypeDecl(bt); TypeDecl * result = t; @@ -35,42 +28,23 @@ TypeDecl * makeFAChain ( Type bt, std::initializer_list dims ) { } -TEST_CASE("tFixedArray text matches dim-vector nodes") { +TEST_CASE("tFixedArray text") { gc_guard guard; SUBCASE("describe") { - CHECK_EQ(makeFAChain(Type::tInt,{4})->describe(), - makeOldDim(Type::tInt,{4})->describe()); - CHECK_EQ(makeFAChain(Type::tFloat,{3,4,4})->describe(), - makeOldDim(Type::tFloat,{3,4,4})->describe()); + CHECK_EQ(makeFAChain(Type::tInt,{4})->describe(), "int[4]"); CHECK_EQ(makeFAChain(Type::tFloat,{3,4,4})->describe(), "float[3][4][4]"); - auto faAuto = makeFAChain(Type::tInt,{TypeDecl::dimAuto}); - auto oldAuto = makeOldDim(Type::tInt,{TypeDecl::dimAuto}); - CHECK_EQ(faAuto->describe(), oldAuto->describe()); - CHECK_EQ(faAuto->describe(), "int[]"); + CHECK_EQ(makeFAChain(Type::tInt,{TypeDecl::dimAuto})->describe(), "int[]"); auto faConst = makeFAChain(Type::tInt,{4}); faConst->constant = true; - auto oldConst = makeOldDim(Type::tInt,{4}); - oldConst->constant = true; - CHECK_EQ(faConst->describe(), oldConst->describe()); CHECK_EQ(faConst->describe(), "int const[4]"); } SUBCASE("mangled name") { - CHECK_EQ(makeFAChain(Type::tInt,{4})->getMangledName(), - makeOldDim(Type::tInt,{4})->getMangledName()); CHECK_EQ(makeFAChain(Type::tInt,{4})->getMangledName(), "[4]i"); - CHECK_EQ(makeFAChain(Type::tFloat,{3,4,4})->getMangledName(), - makeOldDim(Type::tFloat,{3,4,4})->getMangledName()); CHECK_EQ(makeFAChain(Type::tFloat,{3,4,4})->getMangledName(), "[3][4][4]f"); auto faQ = makeFAChain(Type::tInt,{4}); faQ->constant = true; faQ->ref = true; - auto oldQ = makeOldDim(Type::tInt,{4}); - oldQ->constant = true; - oldQ->ref = true; - CHECK_EQ(faQ->getMangledName(), oldQ->getMangledName()); CHECK_EQ(faQ->getMangledName(), "C&[4]i"); - CHECK_EQ(makeFAChain(Type::tInt,{4})->getMangledNameHash(), - makeOldDim(Type::tInt,{4})->getMangledNameHash()); } SUBCASE("mid-chain alias mangles per level (settled: natural recursion)") { auto m4 = makeFAChain(Type::tFloat,{4,4}); @@ -79,38 +53,27 @@ TEST_CASE("tFixedArray text matches dim-vector nodes") { CHECK_EQ(m4x3->getMangledName(), "[3][4]Y[4]f"); } SUBCASE("describeCppType") { - CHECK_EQ(describeCppType(makeFAChain(Type::tInt,{3,4})), - describeCppType(makeOldDim(Type::tInt,{3,4}))); CHECK_EQ(describeCppType(makeFAChain(Type::tInt,{3,4})), "TDim,3>"); } } -TEST_CASE("tFixedArray size family matches dim-vector nodes") { +TEST_CASE("tFixedArray size family") { gc_guard guard; auto fa = makeFAChain(Type::tFloat,{3,4,4}); - auto old = makeOldDim(Type::tFloat,{3,4,4}); - CHECK_EQ(fa->getSizeOf64(), old->getSizeOf64()); CHECK_EQ(fa->getSizeOf64(), uint64_t(192)); - CHECK_EQ(fa->getCountOf64(), old->getCountOf64()); CHECK_EQ(fa->getCountOf64(), uint64_t(48)); - CHECK_EQ(fa->getStride64(), old->getStride64()); CHECK_EQ(fa->getStride64(), uint64_t(64)); - CHECK_EQ(fa->getBaseSizeOf64(), old->getBaseSizeOf64()); CHECK_EQ(fa->getBaseSizeOf64(), uint64_t(4)); - CHECK_EQ(fa->getAlignOf(), old->getAlignOf()); + CHECK_EQ(fa->getAlignOf(), 4); SUBCASE("float3[4] stays 48 bytes (memory matters)") { auto fa3 = makeFAChain(Type::tFloat3,{4}); - auto old3 = makeOldDim(Type::tFloat3,{4}); - CHECK_EQ(fa3->getSizeOf64(), old3->getSizeOf64()); CHECK_EQ(fa3->getSizeOf64(), uint64_t(48)); - CHECK_EQ(fa3->getAlignOf(), old3->getAlignOf()); + CHECK_EQ(fa3->getAlignOf(), 4); } SUBCASE("unresolved dims fail sizeof") { - bool faFailed = false, oldFailed = false; + bool faFailed = false; makeFAChain(Type::tInt,{TypeDecl::dimAuto})->getSizeOf64(faFailed); - makeOldDim(Type::tInt,{TypeDecl::dimAuto})->getSizeOf64(oldFailed); CHECK(faFailed); - CHECK_EQ(faFailed, oldFailed); } } @@ -123,7 +86,6 @@ TEST_CASE("tFixedArray identity") { auto f44b = makeFAChain(Type::tFloat,{4,4}); auto f34 = makeFAChain(Type::tFloat,{3,4}); auto justInt = new TypeDecl(Type::tInt); - auto oldI4 = makeOldDim(Type::tInt,{4}); auto arrInt = new TypeDecl(Type::tArray); arrInt->firstType = new TypeDecl(Type::tInt); auto same = [](TypeDecl * a, TypeDecl * b) { @@ -135,8 +97,6 @@ TEST_CASE("tFixedArray identity") { CHECK_FALSE(same(f44a,f34)); CHECK_FALSE(same(i4a,justInt)); CHECK_FALSE(same(i4a,arrInt)); - // the two representations never meet within one build; structural inequality is by design - CHECK_FALSE(same(i4a,oldI4)); CHECK(i4a->isSameExactType(*i4b)); CHECK_FALSE(i4a->isSameExactType(*i3)); SUBCASE("outer const participates per constMatters") { @@ -186,56 +146,47 @@ TEST_CASE("tFixedArray lifecycle and hashes") { } } -TEST_CASE("tFixedArray classification parity with dim-vector nodes") { +TEST_CASE("tFixedArray classification") { gc_guard guard; auto fa = makeFAChain(Type::tInt,{4}); - auto old = makeOldDim(Type::tInt,{4}); CHECK(fa->isArray()); - CHECK_EQ(fa->isArray(), old->isArray()); CHECK(fa->isFixedArray()); - CHECK_EQ(fa->isRefType(), old->isRefType()); - CHECK_EQ(fa->canCopy(), old->canCopy()); - CHECK_EQ(fa->canMove(), old->canMove()); - CHECK_EQ(fa->canClone(), old->canClone()); - CHECK_EQ(fa->isPod(), old->isPod()); - CHECK_EQ(fa->isRawPod(), old->isRawPod()); - CHECK_EQ(fa->isNoHeapType(), old->isNoHeapType()); - CHECK_EQ(fa->isLocal(), old->isLocal()); - CHECK_EQ(fa->isShareable(), old->isShareable()); - CHECK_EQ(fa->isWorkhorseType(), old->isWorkhorseType()); - CHECK_EQ(fa->isFoldable(), old->isFoldable()); - CHECK_EQ(fa->canInitWithZero(), old->canInitWithZero()); - CHECK_EQ(fa->isPolicyType(), old->isPolicyType()); - CHECK_EQ(fa->isVecPolicyType(), old->isVecPolicyType()); - CHECK_EQ(fa->unsafeInit(), old->unsafeInit()); - CHECK_EQ(fa->needInScope(), old->needInScope()); - CHECK_EQ(fa->gcFlags(), old->gcFlags()); - CHECK_EQ(fa->isAuto(), old->isAuto()); - CHECK_EQ(fa->isAutoOrAlias(), old->isAutoOrAlias()); - CHECK_EQ(fa->isAutoArrayResolved(), old->isAutoArrayResolved()); - CHECK_EQ(fa->isExprType(), old->isExprType()); + CHECK(fa->isRefType()); + CHECK(fa->canCopy()); + CHECK(fa->canMove()); + CHECK(fa->canClone()); + CHECK(fa->isPod()); + CHECK(fa->isRawPod()); + CHECK(fa->isNoHeapType()); + CHECK(fa->isLocal()); + CHECK(fa->isShareable()); + CHECK_FALSE(fa->isWorkhorseType()); + CHECK_FALSE(fa->isFoldable()); + CHECK_FALSE(fa->canInitWithZero()); + CHECK(fa->isPolicyType()); + CHECK_FALSE(fa->isVecPolicyType()); + CHECK_FALSE(fa->unsafeInit()); + CHECK_FALSE(fa->needInScope()); + CHECK_EQ(fa->gcFlags(), 0u); + CHECK_FALSE(fa->isAuto()); + CHECK_FALSE(fa->isAutoOrAlias()); + CHECK(fa->isAutoArrayResolved()); + CHECK_FALSE(fa->isExprType()); SUBCASE("string element carries string gc flags through the FA level") { auto faS = makeFAChain(Type::tString,{4}); - auto oldS = makeOldDim(Type::tString,{4}); - CHECK_EQ(faS->gcFlags(), oldS->gcFlags()); - CHECK_EQ(faS->hasStringData(), oldS->hasStringData()); + CHECK((faS->gcFlags() & TypeDecl::gcFlag_stringHeap) != 0u); CHECK(faS->hasStringData()); } SUBCASE("auto dims register as auto") { auto faA = makeFAChain(Type::tInt,{TypeDecl::dimAuto}); - auto oldA = makeOldDim(Type::tInt,{TypeDecl::dimAuto}); - CHECK_EQ(faA->isAuto(), oldA->isAuto()); CHECK(faA->isAuto()); - CHECK_EQ(faA->isAutoArrayResolved(), oldA->isAutoArrayResolved()); CHECK_FALSE(faA->isAutoArrayResolved()); auto faElemAuto = makeFA(4, new TypeDecl(Type::autoinfer)); CHECK(faElemAuto->isAuto()); } SUBCASE("dimConst registers as expression type") { auto faC = makeFAChain(Type::tInt,{TypeDecl::dimConst}); - auto oldC = makeOldDim(Type::tInt,{TypeDecl::dimConst}); - CHECK_EQ(faC->isExprType(), oldC->isExprType()); CHECK(faC->isExprType()); - CHECK_EQ(faC->isAliasOrExpr(), oldC->isAliasOrExpr()); + CHECK(faC->isAliasOrExpr()); } } diff --git a/tests/typemacro/_typemacro_mod.das b/tests/typemacro/_typemacro_mod.das index 0b91cb6729..7595cb4fd9 100644 --- a/tests/typemacro/_typemacro_mod.das +++ b/tests/typemacro/_typemacro_mod.das @@ -7,7 +7,7 @@ // typeMacroExpr[3] — ExprConstBool (wrap into an array or not) // typeMacroExpr[4] — ExprConstString (mode tag, "arr" expected when wrapping) // NOTE: this module reads the typeMacroExpr field directly; array results are -// built with make_fixed_array_type (the .dim compat view is read-only). +// built with make_fixed_array_type. options gen2 options no_aot diff --git a/utils/dasFormatter/ds_parser.cpp b/utils/dasFormatter/ds_parser.cpp index 23ab8716ce..8282936ac7 100644 --- a/utils/dasFormatter/ds_parser.cpp +++ b/utils/dasFormatter/ds_parser.cpp @@ -1153,26 +1153,26 @@ static const yytype_int16 yyrline[] = 3225, 3226, 3227, 3228, 3229, 3230, 3234, 3245, 3249, 3256, 3268, 3275, 3281, 3291, 3292, 3297, 3302, 3316, 3326, 3336, 3346, 3356, 3369, 3370, 3371, 3372, 3373, 3377, 3381, 3381, - 3381, 3395, 3396, 3400, 3404, 3411, 3415, 3422, 3423, 3424, - 3425, 3426, 3441, 3447, 3447, 3447, 3451, 3456, 3463, 3463, - 3470, 3474, 3478, 3483, 3488, 3493, 3498, 3502, 3506, 3511, - 3515, 3519, 3524, 3524, 3524, 3530, 3537, 3537, 3537, 3542, - 3542, 3542, 3548, 3548, 3548, 3553, 3558, 3558, 3558, 3563, - 3563, 3563, 3572, 3577, 3577, 3577, 3582, 3582, 3582, 3591, - 3596, 3596, 3596, 3601, 3601, 3601, 3610, 3610, 3610, 3616, - 3616, 3616, 3625, 3628, 3639, 3655, 3655, 3660, 3669, 3655, - 3698, 3698, 3703, 3713, 3698, 3742, 3742, 3742, 3795, 3796, - 3797, 3798, 3799, 3803, 3810, 3817, 3823, 3829, 3836, 3843, - 3849, 3858, 3861, 3867, 3875, 3880, 3887, 3892, 3899, 3904, - 3910, 3911, 3915, 3916, 3921, 3922, 3926, 3927, 3931, 3932, - 3936, 3937, 3938, 3942, 3943, 3944, 3948, 3949, 3953, 3986, - 4025, 4044, 4064, 4084, 4105, 4105, 4105, 4113, 4113, 4113, - 4120, 4120, 4120, 4131, 4131, 4131, 4142, 4146, 4152, 4168, - 4174, 4180, 4186, 4186, 4186, 4200, 4205, 4212, 4232, 4260, - 4284, 4284, 4284, 4294, 4294, 4294, 4308, 4308, 4308, 4322, - 4331, 4331, 4331, 4351, 4358, 4358, 4358, 4368, 4373, 4380, - 4383, 4389, 4409, 4428, 4436, 4456, 4481, 4482, 4486, 4487, - 4492, 4502, 4505, 4508, 4511, 4519, 4528, 4540, 4550 + 3381, 3395, 3396, 3400, 3404, 3411, 3414, 3420, 3421, 3422, + 3423, 3424, 3434, 3437, 3437, 3437, 3441, 3446, 3453, 3453, + 3460, 3464, 3468, 3473, 3478, 3483, 3488, 3492, 3496, 3501, + 3505, 3509, 3514, 3514, 3514, 3520, 3527, 3527, 3527, 3532, + 3532, 3532, 3538, 3538, 3538, 3543, 3548, 3548, 3548, 3553, + 3553, 3553, 3562, 3567, 3567, 3567, 3572, 3572, 3572, 3581, + 3586, 3586, 3586, 3591, 3591, 3591, 3600, 3600, 3600, 3606, + 3606, 3606, 3615, 3618, 3629, 3645, 3645, 3650, 3659, 3645, + 3688, 3688, 3693, 3703, 3688, 3732, 3732, 3732, 3785, 3786, + 3787, 3788, 3789, 3793, 3800, 3807, 3813, 3819, 3826, 3833, + 3839, 3848, 3851, 3857, 3865, 3870, 3877, 3882, 3889, 3894, + 3900, 3901, 3905, 3906, 3911, 3912, 3916, 3917, 3921, 3922, + 3926, 3927, 3928, 3932, 3933, 3934, 3938, 3939, 3943, 3976, + 4015, 4034, 4054, 4074, 4095, 4095, 4095, 4103, 4103, 4103, + 4110, 4110, 4110, 4121, 4121, 4121, 4132, 4136, 4142, 4158, + 4164, 4170, 4176, 4176, 4176, 4190, 4195, 4202, 4222, 4250, + 4274, 4274, 4274, 4284, 4284, 4284, 4298, 4298, 4298, 4312, + 4321, 4321, 4321, 4341, 4348, 4348, 4348, 4358, 4363, 4370, + 4373, 4379, 4399, 4421, 4429, 4449, 4474, 4475, 4479, 4480, + 4485, 4495, 4498, 4501, 4504, 4512, 4521, 4533, 4543 }; #endif @@ -10913,7 +10913,7 @@ YYLTYPE yylloc = yyloc_default; (yyval.pTypeDecl)->isTag = true; (yyval.pTypeDecl)->firstType = new TypeDecl(Type::autoinfer); (yyval.pTypeDecl)->firstType->at = tokAt(scanner, (yylsp[-1])); - (yyval.pTypeDecl)->firstType->dimExpr.push_back((yyvsp[-1].pExpression)); + (yyval.pTypeDecl)->firstType->typeMacroExpr.push_back((yyvsp[-1].pExpression)); } break; @@ -11100,15 +11100,13 @@ YYLTYPE yylloc = yyloc_default; case 775: /* dim_list: '[' expr2 ']' */ { - (yyval.pTypeDecl) = new TypeDecl(Type::autoinfer); - appendDimExpr((yyval.pTypeDecl), (yyvsp[-1].pExpression)); + (yyval.pTypeDecl) = appendDimExpr(nullptr, (yyvsp[-1].pExpression), tokAt(scanner,(yylsp[-1]))); } break; case 776: /* dim_list: dim_list '[' expr2 ']' */ { - (yyval.pTypeDecl) = (yyvsp[-3].pTypeDecl); - appendDimExpr((yyval.pTypeDecl), (yyvsp[-1].pExpression)); + (yyval.pTypeDecl) = appendDimExpr((yyvsp[-3].pTypeDecl), (yyvsp[-1].pExpression), tokAt(scanner,(yylsp[-1]))); } break; @@ -11137,21 +11135,13 @@ YYLTYPE yylloc = yyloc_default; das_yyerror(scanner,"macro can`t be used as array base type",tokAt(scanner,(yylsp[-1])), CompilationError::invalid_type); } - (yyvsp[-1].pTypeDecl)->dim.insert((yyvsp[-1].pTypeDecl)->dim.begin(), (yyvsp[0].pTypeDecl)->dim.begin(), (yyvsp[0].pTypeDecl)->dim.end()); - (yyvsp[-1].pTypeDecl)->dimExpr.insert((yyvsp[-1].pTypeDecl)->dimExpr.begin(), (yyvsp[0].pTypeDecl)->dimExpr.begin(), (yyvsp[0].pTypeDecl)->dimExpr.end()); - (yyvsp[-1].pTypeDecl)->removeDim = false; - (yyval.pTypeDecl) = (yyvsp[-1].pTypeDecl); - (yyvsp[0].pTypeDecl)->dimExpr.clear(); - delete (yyvsp[0].pTypeDecl); + (yyval.pTypeDecl) = attachDimChain((yyvsp[0].pTypeDecl), (yyvsp[-1].pTypeDecl)); } break; case 782: /* type_declaration_no_options: type_declaration_no_options '[' ']' */ { - (yyvsp[-2].pTypeDecl)->dim.push_back(TypeDecl::dimAuto); - (yyvsp[-2].pTypeDecl)->dimExpr.push_back(nullptr); - (yyvsp[-2].pTypeDecl)->removeDim = false; - (yyval.pTypeDecl) = (yyvsp[-2].pTypeDecl); + (yyval.pTypeDecl) = appendAutoDim((yyvsp[-2].pTypeDecl), tokAt(scanner,(yylsp[-1]))); } break; @@ -11174,7 +11164,7 @@ YYLTYPE yylloc = yyloc_default; { (yyval.pTypeDecl) = new TypeDecl(Type::typeDecl); (yyval.pTypeDecl)->at = tokRangeAt(scanner,(yylsp[-3]),(yylsp[-1])); - (yyval.pTypeDecl)->dimExpr.push_back((yyvsp[-1].pExpression)); + (yyval.pTypeDecl)->typeMacroExpr.push_back((yyvsp[-1].pExpression)); } break; @@ -11182,8 +11172,8 @@ YYLTYPE yylloc = yyloc_default; { (yyval.pTypeDecl) = new TypeDecl(Type::typeMacro); (yyval.pTypeDecl)->at = tokRangeAt(scanner,(yylsp[-1]), (yylsp[0])); - (yyval.pTypeDecl)->dimExpr = sequenceToList((yyvsp[0].pExpression)); - (yyval.pTypeDecl)->dimExpr.insert((yyval.pTypeDecl)->dimExpr.begin(), new ExprConstString(tokAt(scanner,(yylsp[-1])), *(yyvsp[-1].s))); + (yyval.pTypeDecl)->typeMacroExpr = sequenceToList((yyvsp[0].pExpression)); + (yyval.pTypeDecl)->typeMacroExpr.insert((yyval.pTypeDecl)->typeMacroExpr.begin(), new ExprConstString(tokAt(scanner,(yylsp[-1])), *(yyvsp[-1].s))); delete (yyvsp[-1].s); } break; @@ -11196,8 +11186,8 @@ YYLTYPE yylloc = yyloc_default; { (yyval.pTypeDecl) = new TypeDecl(Type::typeMacro); (yyval.pTypeDecl)->at = tokRangeAt(scanner,(yylsp[-5]), (yylsp[0])); - (yyval.pTypeDecl)->dimExpr = typesAndSequenceToList((yyvsp[-2].pTypeDeclList),(yyvsp[0].pExpression)); - (yyval.pTypeDecl)->dimExpr.insert((yyval.pTypeDecl)->dimExpr.begin(), new ExprConstString(tokAt(scanner,(yylsp[-5])), *(yyvsp[-5].s))); + (yyval.pTypeDecl)->typeMacroExpr = typesAndSequenceToList((yyvsp[-2].pTypeDeclList),(yyvsp[0].pExpression)); + (yyval.pTypeDecl)->typeMacroExpr.insert((yyval.pTypeDecl)->typeMacroExpr.begin(), new ExprConstString(tokAt(scanner,(yylsp[-5])), *(yyvsp[-5].s))); delete (yyvsp[-5].s); } break; @@ -12559,8 +12549,11 @@ YYLTYPE yylloc = yyloc_default; format::finish_rule(format::Pos::from_last(tokAt(scanner,(yylsp[0])))); } - auto mkt = new TypeDecl(Type::autoinfer); - mkt->dim.push_back(TypeDecl::dimAuto); + auto mkt = new TypeDecl(Type::tFixedArray); + mkt->fixedDim = TypeDecl::dimAuto; + mkt->at = tokAt(scanner,(yylsp[-2])); + mkt->firstType = new TypeDecl(Type::autoinfer); + mkt->firstType->at = mkt->at; ((ExprMakeArray *)(yyvsp[-1].pExpression))->makeType = mkt; (yyvsp[-1].pExpression)->at = tokAt(scanner,(yylsp[-2])); auto ttm = yyextra->g_Program->makeCall(tokAt(scanner,(yylsp[-2])),"to_table_move"); diff --git a/utils/dasFormatter/ds_parser.ypp b/utils/dasFormatter/ds_parser.ypp index 1e51a8f80a..6dd20be7ca 100644 --- a/utils/dasFormatter/ds_parser.ypp +++ b/utils/dasFormatter/ds_parser.ypp @@ -3260,7 +3260,7 @@ auto_type_declaration $$->isTag = true; $$->firstType = new TypeDecl(Type::autoinfer); $$->firstType->at = tokAt(scanner, @subexpr); - $$->firstType->dimExpr.push_back($subexpr); + $$->firstType->typeMacroExpr.push_back($subexpr); } ; @@ -3409,12 +3409,10 @@ table_type_pair dim_list : '[' expr2[dimExpr] ']' { - $$ = new TypeDecl(Type::autoinfer); - appendDimExpr($$, $dimExpr); + $$ = appendDimExpr(nullptr, $dimExpr, tokAt(scanner,@dimExpr)); } | dim_list[list] '[' expr2[dimExpr] ']' { - $$ = $list; - appendDimExpr($$, $dimExpr); + $$ = appendDimExpr($list, $dimExpr, tokAt(scanner,@dimExpr)); } ; @@ -3431,18 +3429,10 @@ type_declaration_no_options das_yyerror(scanner,"macro can`t be used as array base type",tokAt(scanner,@typeDecl), CompilationError::invalid_type); } - $typeDecl->dim.insert($typeDecl->dim.begin(), $dimlist->dim.begin(), $dimlist->dim.end()); - $typeDecl->dimExpr.insert($typeDecl->dimExpr.begin(), $dimlist->dimExpr.begin(), $dimlist->dimExpr.end()); - $typeDecl->removeDim = false; - $$ = $typeDecl; - $dimlist->dimExpr.clear(); - delete $dimlist; + $$ = attachDimChain($dimlist, $typeDecl); } | type_declaration_no_options[typeDecl] '[' ']' { - $typeDecl->dim.push_back(TypeDecl::dimAuto); - $typeDecl->dimExpr.push_back(nullptr); - $typeDecl->removeDim = false; - $$ = $typeDecl; + $$ = appendAutoDim($typeDecl, tokAt(scanner,@2)); } | DAS_TYPE '<' { yyextra->das_arrow_depth ++; } type_declaration[typeDecl] '>' { yyextra->das_arrow_depth --; } { $typeDecl->autoToAlias = true; @@ -3451,20 +3441,20 @@ type_declaration_no_options | DAS_TYPEDECL[td] '(' expr2[subexpr] ')' { $$ = new TypeDecl(Type::typeDecl); $$->at = tokRangeAt(scanner,@td,@subexpr); - $$->dimExpr.push_back($subexpr); + $$->typeMacroExpr.push_back($subexpr); } | '$' name_in_namespace[name] optional_expr_list_in_braces[arguments] { $$ = new TypeDecl(Type::typeMacro); $$->at = tokRangeAt(scanner,@name, @arguments); - $$->dimExpr = sequenceToList($arguments); - $$->dimExpr.insert($$->dimExpr.begin(), new ExprConstString(tokAt(scanner,@name), *$name)); + $$->typeMacroExpr = sequenceToList($arguments); + $$->typeMacroExpr.insert($$->typeMacroExpr.begin(), new ExprConstString(tokAt(scanner,@name), *$name)); delete $name; } | '$' name_in_namespace[name] '<' { yyextra->das_arrow_depth ++; } type_declaration_no_options_list[declL] '>' optional_expr_list_in_braces[arguments] { $$ = new TypeDecl(Type::typeMacro); $$->at = tokRangeAt(scanner,@name, @arguments); - $$->dimExpr = typesAndSequenceToList($declL,$arguments); - $$->dimExpr.insert($$->dimExpr.begin(), new ExprConstString(tokAt(scanner,@name), *$name)); + $$->typeMacroExpr = typesAndSequenceToList($declL,$arguments); + $$->typeMacroExpr.insert($$->typeMacroExpr.begin(), new ExprConstString(tokAt(scanner,@name), *$name)); delete $name; } | type_declaration_no_options[typeDecl] '-' '[' ']' { @@ -4417,8 +4407,11 @@ make_table_decl format::finish_rule(format::Pos::from_last(tokAt(scanner,@end))); } - auto mkt = new TypeDecl(Type::autoinfer); - mkt->dim.push_back(TypeDecl::dimAuto); + auto mkt = new TypeDecl(Type::tFixedArray); + mkt->fixedDim = TypeDecl::dimAuto; + mkt->at = tokAt(scanner,@loc); + mkt->firstType = new TypeDecl(Type::autoinfer); + mkt->firstType->at = mkt->at; ((ExprMakeArray *)$mka)->makeType = mkt; $mka->at = tokAt(scanner,@loc); auto ttm = yyextra->g_Program->makeCall(tokAt(scanner,@loc),"to_table_move"); From 68f9a8221652677538d83cba286d03dd8e598489 Mon Sep 17 00:00:00 2001 From: Boris Batkin Date: Wed, 10 Jun 2026 23:04:43 -0700 Subject: [PATCH 18/23] fixed-array stage 7: RST docs - fixed-array section in arrays.rst; natural generic binding + one-level -[] peel in generic_programming.rst Co-Authored-By: Claude Fable 5 --- FIXED_ARRAY_REWORK.md | 6 ++++ doc/source/reference/language/arrays.rst | 35 +++++++++++++++++++ .../language/generic_programming.rst | 32 ++++++++++++++--- 3 files changed, 68 insertions(+), 5 deletions(-) diff --git a/FIXED_ARRAY_REWORK.md b/FIXED_ARRAY_REWORK.md index b05faac8d7..1ce33e19f6 100644 --- a/FIXED_ARRAY_REWORK.md +++ b/FIXED_ARRAY_REWORK.md @@ -535,6 +535,12 @@ site-shapes as the in-tree gen1 parser. Caveat: deleting dim/dimExpr at the end 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 diff --git a/doc/source/reference/language/arrays.rst b/doc/source/reference/language/arrays.rst index e5641ed5fb..b6a03b84db 100644 --- a/doc/source/reference/language/arrays.rst +++ b/doc/source/reference/language/arrays.rst @@ -76,6 +76,41 @@ This expands to: let arr : float[4] = fixed_array(1.,2.,3.,4.5) +Fixed-size arrays can be multi-dimensional. Dimensions read outermost first — +``float[4][4]`` is 4 rows of ``float[4]`` — and indexing peels one level at a time: + +.. code-block:: das + + var m : float[4][4] + m[1][2] = 5.0 // m[1] is a float[4] row; m[1][2] is a float + var total = 0.0 + for ( row in m ) { // iterates the 4 rows + total += row[2] + } + +Fixed-size array types compose with type aliases, including arrays of aliased arrays: + +.. code-block:: das + + typedef M4 = float[4][4] + + var stack : M4[10] // 10 matrices + stack[0][1][2] = 3.0 + +Unlike dynamic arrays, fixed-size arrays of copyable elements copy with plain +assignment: + +.. code-block:: das + + var a = fixed_array(1, 2, 3, 4) + var b = a // b is a copy + b[0] = 99 // a[0] is still 1 + +``typeinfo dim`` returns the outermost dimension of a fixed-size array. See +:ref:`Generic Programming ` for how fixed-size arrays bind to +generic arguments (``auto(TT)`` binds the whole array, ``auto(TT)[]`` peels one +level, ``-[]`` removes one level). + Dynamic arrays can also be constructed inline: .. code-block:: das diff --git a/doc/source/reference/language/generic_programming.rst b/doc/source/reference/language/generic_programming.rst index 27314b098f..d3defa1b7a 100644 --- a/doc/source/reference/language/generic_programming.rst +++ b/doc/source/reference/language/generic_programming.rst @@ -292,21 +292,43 @@ Generic function arguments, result, and inferred type aliases can be operated on var temp : TT -& = a // TT -& is not a local reference } -``[]`` specifies that the argument is a static array of arbitrary dimension: +``[]`` specifies that the argument is a fixed-size array: .. code-block:: das - def foo ( a : auto[] ) // accepts static array of any type of any size + def foo ( a : auto[] ) // accepts a fixed-size array of any type and size -``-[]`` will remove static array dimension from the matching type: +A named alias under ``[]`` binds the element of the *outermost* level — one level is +peeled. The binding inherits the argument's constness: a non-``var`` parameter binds +the alias const, a ``var`` parameter binds it mutable: + +.. code-block:: das + + def rows ( a : auto(TT)[] ) { // for a : float[4][4], TT is float const[4] + print(typeinfo typename(type)) + } + def rows_rw ( var a : auto(TT)[] ) { // for a : float[4][4], TT is float[4] + a[0] = a[1] // rows are mutable here + } + +A plain named alias (no ``[]``) binds the *whole* matched type, fixed-array +dimensions included: + +.. code-block:: das + + def whole ( a : auto(TT) ) { // for a : int[4], TT is int const[4] + var z = default // a zero-initialized int[4] + } + +``-[]`` will remove one fixed-array level from the matching type: .. code-block:: das def take_dim( a : auto(TT) ) { - var temp : TT -[] // temp is type of element of a + var temp : TT -[] // temp is one level shallower than a } // if a is int[10] temp is int - // if a is int[10][20][30] temp is still int + // if a is int[10][20][30] temp is int[20][30] ``implicit`` specifies that both temporary and regular types can be matched, but the type will be treated as specified. ``implicit`` is _UNSAFE_: From 91399174fdb3effa4a38ac6188fa09acd4269100 Mon Sep 17 00:00:00 2001 From: Boris Batkin Date: Wed, 10 Jun 2026 23:13:03 -0700 Subject: [PATCH 19/23] fixed-array: port style_lint rules added on master to structural reads (post-rebase) Co-Authored-By: Claude Fable 5 --- daslib/style_lint.das | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/daslib/style_lint.das b/daslib/style_lint.das index 7651748bd6..6ccb976ff2 100644 --- a/daslib/style_lint.das +++ b/daslib/style_lint.das @@ -944,7 +944,7 @@ class StyleLintVisitor : AstVisitor { || length(call.arguments) != 2) return null let recv = call.arguments[0] if (recv == null || recv._type == null - || recv._type.baseType != Type.tArray || !empty(recv._type.dim)) return null + || recv._type.baseType != Type.tArray) return null return is_pure_chain(recv) ? recv : null } @@ -1066,7 +1066,7 @@ class StyleLintVisitor : AstVisitor { //! struct-field initializer → pushes are a deliberate append); 0 = unknown //! (pointer root, non-MakeStruct init, where-block). var st : Structure const? = null - if (v._type != null && v._type.baseType == Type.tStructure && empty(v._type.dim)) { + if (v._type != null && v._type.baseType == Type.tStructure) { st = v._type.structType } if (st == null) return 0 From 18c120a7568a4f6be76fc487f8c34afe615342f5 Mon Sep 17 00:00:00 2001 From: Boris Batkin Date: Wed, 10 Jun 2026 23:50:13 -0700 Subject: [PATCH 20/23] fixed-array docs: update handmade RST for das2rst (docs CI) das2rst validates handmade docs positionally against reflection: - enumeration-rtti-Type.rst: add line for the new tFixedArray value - structure_annotation-ast-TypeDecl.rst: dim/dimExpr lines replaced by fixedDim/fixedDimExpr/typeMacroExpr; firstType line mentions the fixed-array element - new handmade doc for ast_boost::make_fixed_array_type (das2rst generated a // stub, which the docs workflow rejects) Verified locally: das2rst clean, no stubs, no untracked generated files (generated/ is gitignored), sphinx-build -W html succeeds. Co-Authored-By: Claude Fable 5 --- doc/source/stdlib/handmade/enumeration-rtti-Type.rst | 3 ++- ...-ast_boost-make_fixed_array_type-0x5d7461e04861d693.rst | 1 + .../stdlib/handmade/structure_annotation-ast-TypeDecl.rst | 7 ++++--- 3 files changed, 7 insertions(+), 4 deletions(-) create mode 100644 doc/source/stdlib/handmade/function-ast_boost-make_fixed_array_type-0x5d7461e04861d693.rst diff --git a/doc/source/stdlib/handmade/enumeration-rtti-Type.rst b/doc/source/stdlib/handmade/enumeration-rtti-Type.rst index 27f355b0c5..8bff844cfa 100644 --- a/doc/source/stdlib/handmade/enumeration-rtti-Type.rst +++ b/doc/source/stdlib/handmade/enumeration-rtti-Type.rst @@ -52,4 +52,5 @@ Array type. Table type. Block type. Tuple type. -Variant type. \ No newline at end of file +Variant type. +Fixed size array type (e.g. int[10]); element type in firstType, size in fixedDim. \ No newline at end of file diff --git a/doc/source/stdlib/handmade/function-ast_boost-make_fixed_array_type-0x5d7461e04861d693.rst b/doc/source/stdlib/handmade/function-ast_boost-make_fixed_array_type-0x5d7461e04861d693.rst new file mode 100644 index 0000000000..782b24b155 --- /dev/null +++ b/doc/source/stdlib/handmade/function-ast_boost-make_fixed_array_type-0x5d7461e04861d693.rst @@ -0,0 +1 @@ +Wraps ``element`` into a fixed-array type node (``Type tFixedArray``) of size ``total``. The ref, const, and temporary qualifiers are moved from the element onto the new chain head, which is their canonical position. diff --git a/doc/source/stdlib/handmade/structure_annotation-ast-TypeDecl.rst b/doc/source/stdlib/handmade/structure_annotation-ast-TypeDecl.rst index 49fb7a19f3..6f959a901a 100644 --- a/doc/source/stdlib/handmade/structure_annotation-ast-TypeDecl.rst +++ b/doc/source/stdlib/handmade/structure_annotation-ast-TypeDecl.rst @@ -3,12 +3,13 @@ Basic declaration type Structure type if baseType is Type::tStructure Enumeration type if baseType is Type::tEnumeration Handled type if baseType is Type::tHandle -First type for compound types (like array or table) +First type for compound types (like array, table, or the element of a fixed array) Second type for compound types (like table) Argument types for function types, tuples, variants, etc Argument names for function types -Dimensions for fixed array types -Dimension expressions for fixed array types, when dimension is specified by expression +Dimension of a fixed array type (Type::tFixedArray); dimAuto when inferred, dimConst while a constant expression is pending resolution +Dimension expression for fixed array types, when dimension is specified by expression +Argument expressions of a type macro (Type::typeMacro) Type declaration flags Alias name for typedefs, i.e. 'int aka MyInt' or 'MyInt' Location of the type declaration in the source code From ba9f5d9b59e2f4616f16b80ac016de652970eb74 Mon Sep 17 00:00:00 2001 From: Boris Batkin Date: Thu, 11 Jun 2026 00:35:19 -0700 Subject: [PATCH 21/23] fixed-array: review fixes + clang/gcc tests-cpp compile fix - makeConst: restore the fixed-array early-return dropped in stage 6 (Copilot review; unreachable today but master returned nullptr gracefully, and the structural-world spelling of the old dim guard is the baseType check) - expect_dim: compatibility error message says "fixed size array" instead of the legacy "dim []" terminology (Copilot review) - tests-cpp: wrap bit-field operands in bool() inside CHECK macros. doctest's expression decomposition binds operands by reference, which C++ forbids for bit-fields; MSVC tolerated it, every clang/gcc CI lane failed to compile. Noted in skills/writing_cpp_tests.md. Co-Authored-By: Claude Fable 5 --- skills/writing_cpp_tests.md | 2 ++ src/ast/ast_program.cpp | 2 +- src/builtin/module_builtin_runtime.cpp | 2 +- tests-cpp/small/test_fixed_array_interop.cpp | 14 +++++------ tests-cpp/small/test_fixed_array_parser.cpp | 26 ++++++++++---------- 5 files changed, 24 insertions(+), 22 deletions(-) diff --git a/skills/writing_cpp_tests.md b/skills/writing_cpp_tests.md index e2d599ac9d..cb8e236958 100644 --- a/skills/writing_cpp_tests.md +++ b/skills/writing_cpp_tests.md @@ -78,6 +78,8 @@ Big tests are **not in CI yet** (V1) — local-only via `ninja test-big`. | `CHECK_THROWS(expr)` / `CHECK_NOTHROW(expr)` | Exception assertions | | `MESSAGE("...")` / `INFO("...")` | Log without checking | +**Bit-fields must be wrapped in `bool(...)`** — doctest's expression decomposition binds operands by reference, and C++ forbids binding a reference to a bit-field. `CHECK(t->ref)` on a `TypeDecl` flag compiles on MSVC but fails on clang/gcc ("non-const reference cannot bind to bit-field") — Release CI on Windows won't catch it, every other lane goes red. Write `CHECK(bool(t->ref))` / `CHECK_FALSE(bool(t->constant))`. + ## TEST_CASE / SUBCASE pattern One `TEST_CASE` per logical scenario. `SUBCASE`s for variations sharing setup. Each SUBCASE re-runs the TEST_CASE body from the top — so per-subcase fresh `Context`/state is automatic. diff --git a/src/ast/ast_program.cpp b/src/ast/ast_program.cpp index e509168b8b..e7764bb719 100644 --- a/src/ast/ast_program.cpp +++ b/src/ast/ast_program.cpp @@ -327,7 +327,7 @@ namespace das { } ExpressionPtr Program::makeConst ( const LineInfo & at, const TypeDeclPtr & type, vec4f value ) { - if ( type->ref ) return nullptr; + if ( type->ref || type->baseType==Type::tFixedArray ) return nullptr; switch ( type->baseType ) { case Type::tBool: return new ExprConstBool(at, cast::to(value)); case Type::tInt8: return new ExprConstInt8(at, cast::to(value)); diff --git a/src/builtin/module_builtin_runtime.cpp b/src/builtin/module_builtin_runtime.cpp index 4caa0f04c3..78db1c37c2 100644 --- a/src/builtin/module_builtin_runtime.cpp +++ b/src/builtin/module_builtin_runtime.cpp @@ -408,7 +408,7 @@ namespace das if ( decl.arguments.find(fn->arguments[ai]->name, Type::tBool) ) { const auto & argT = types[ai]; if ( argT->baseType != Type::tFixedArray ) { - err = "argument " + fn->arguments[ai]->name + " is expected to be a dim []"; + err = "argument " + fn->arguments[ai]->name + " is expected to be a fixed size array"; return false; } } diff --git a/tests-cpp/small/test_fixed_array_interop.cpp b/tests-cpp/small/test_fixed_array_interop.cpp index 95ed820272..79e6d92bbf 100644 --- a/tests-cpp/small/test_fixed_array_interop.cpp +++ b/tests-cpp/small/test_fixed_array_interop.cpp @@ -15,8 +15,8 @@ TEST_CASE("typeFactory produces tFixedArray chains") { auto t = typeFactory::make(lib); REQUIRE(t->baseType == Type::tFixedArray); CHECK_EQ(t->fixedDim, 4); - CHECK(t->isNativeDim); - CHECK_FALSE(t->ref); + CHECK(bool(t->isNativeDim)); // bool() — doctest binds operands by reference, bit-fields can't + CHECK_FALSE(bool(t->ref)); REQUIRE(t->firstType != nullptr); CHECK_EQ(t->firstType->baseType, Type::tInt); CHECK_EQ(t->describe(), "int[4]"); @@ -37,7 +37,7 @@ TEST_CASE("typeFactory produces tFixedArray chains") { auto t = typeFactory>::make(lib); REQUIRE(t->baseType == Type::tFixedArray); CHECK_EQ(t->fixedDim, 4); - CHECK_FALSE(t->isNativeDim); + CHECK_FALSE(bool(t->isNativeDim)); CHECK_EQ(t->firstType->baseType, Type::tInt); auto nested = typeFactory,3>>::make(lib); CHECK_EQ(nested->describe(), "int[3][4]"); @@ -46,14 +46,14 @@ TEST_CASE("typeFactory produces tFixedArray chains") { auto elem = new TypeDecl(Type::tInt); elem->constant = true; auto t = makeFixedArrayTypeDecl(4, elem); - CHECK(t->constant); - CHECK_FALSE(t->firstType->constant); + CHECK(bool(t->constant)); + CHECK_FALSE(bool(t->firstType->constant)); } SUBCASE("makeArgumentType composes through the FA-aware classifiers") { auto t = makeArgumentType(lib); REQUIRE(t->baseType == Type::tFixedArray); - CHECK_FALSE(t->ref); // isRefType() branch clears ref, skips constant - CHECK_FALSE(t->constant); + CHECK_FALSE(bool(t->ref)); // isRefType() branch clears ref, skips constant + CHECK_FALSE(bool(t->constant)); } } diff --git a/tests-cpp/small/test_fixed_array_parser.cpp b/tests-cpp/small/test_fixed_array_parser.cpp index 7b4460e841..d1710ae9db 100644 --- a/tests-cpp/small/test_fixed_array_parser.cpp +++ b/tests-cpp/small/test_fixed_array_parser.cpp @@ -105,10 +105,10 @@ TEST_CASE("gen2 grammar builds tFixedArray chains") { SUBCASE("const hoists to the chain head, element stays bare") { auto h3 = fieldType(p,"Foo","f3"); auto e3 = checkChain(h3,{4},Type::tInt); - CHECK(h3->constant); - CHECK_FALSE(e3->constant); + CHECK(bool(h3->constant)); // bool() — doctest binds operands by reference, bit-fields can't + CHECK_FALSE(bool(e3->constant)); auto h4 = fieldType(p,"Foo","f4"); - CHECK(h4->constant); + CHECK(bool(h4->constant)); CHECK(h3->isSameType(*h4, RefMatters::yes, ConstMatters::yes, TemporaryMatters::yes)); } SUBCASE("[] is the dimAuto sentinel, no dim expr") { @@ -130,8 +130,8 @@ TEST_CASE("gen2 grammar builds tFixedArray chains") { SUBCASE("late dim splices in FRONT (gen1 quirk preserved): int[3] const [4] is [4][3]") { auto t = fieldType(p,"Foo","f8"); checkChain(t,{4,3},Type::tInt); - CHECK(t->constant); // hoisted from the inner chain head - CHECK_FALSE(t->firstType->constant); + CHECK(bool(t->constant)); // hoisted from the inner chain head + CHECK_FALSE(bool(t->firstType->constant)); } SUBCASE("mixed dim_list [3][] keeps text order (gen2-only - error in gen1)") { checkChain(fieldType(p,"Foo","f9"),{3,TypeDecl::dimAuto},Type::tInt); @@ -173,14 +173,14 @@ TEST_CASE("gen1 grammar builds tFixedArray chains") { SUBCASE("const hoists to the chain head") { auto t = fieldType(p,"Foo","f5"); auto e = checkChain(t,{3},Type::tInt); - CHECK(t->constant); - CHECK_FALSE(e->constant); + CHECK(bool(t->constant)); + CHECK_FALSE(bool(e->constant)); } SUBCASE("late dim splices in FRONT: int[3] const [4] is [4][3], const on head") { auto t = fieldType(p,"Foo","f6"); checkChain(t,{4,3},Type::tInt); - CHECK(t->constant); - CHECK_FALSE(t->firstType->constant); + CHECK(bool(t->constant)); + CHECK_FALSE(bool(t->firstType->constant)); } } @@ -242,8 +242,8 @@ TEST_CASE("mangled name parse builds tFixedArray and round-trips the emit") { SUBCASE("qualifier prefixes land on the FA head") { auto t = reparse("C&[4]i"); checkChain(t,{4},Type::tInt); - CHECK(t->constant); - CHECK(t->ref); + CHECK(bool(t->constant)); + CHECK(bool(t->ref)); CHECK_EQ(t->getMangledName(), "C&[4]i"); } SUBCASE("Y<> immediately after [d] labels THAT node - the [3][4]Y[4]f golden") { @@ -270,8 +270,8 @@ TEST_CASE("mangled name parse builds tFixedArray and round-trips the emit") { auto full = built->getMangledName(true); CHECK_EQ(full, "[3]-[]-Ci"); auto t = reparse(full); - CHECK(t->removeDim); - CHECK(t->removeConstant); + CHECK(bool(t->removeDim)); + CHECK(bool(t->removeConstant)); CHECK_EQ(t->getMangledName(true), full); } SUBCASE("FA nested in containers round-trips") { From 26c8e4b77470c149014d67eb6539fc81c3e73803 Mon Sep 17 00:00:00 2001 From: Boris Batkin Date: Thu, 11 Jun 2026 00:35:43 -0700 Subject: [PATCH 22/23] fixed-array: safe_addr overloads for fixed arrays (C-style decay) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In the flattened world, plain auto(T) binding did not carry dims, so safe_addr(arr) where arr : bool[4] bound T=bool and produced bool?# — an element pointer that matched C-interop params like `bool? const implicit` (glGetBooleanv et al). The structural world binds T to the whole array, producing bool[4]?#, which correctly no longer matches — breaking dasOpenGL in extended_checks (the module is GLFW-gated, so local suites never compile it). Restore the rail where it belongs: safe_addr(x : auto(T)[]&) : T -&?# returns a temporary pointer to the FIRST ELEMENT, mirroring C array decay. Multi-dim peels one level (int[2][3] -> int[3]?#), exactly like C's T[2][3] -> T(*)[3]. Overload resolution prefers the [] form for fixed arrays and keeps the base form for everything else; call sites (opengl_state.das and downstream repos) need no edits. Co-Authored-By: Claude Fable 5 --- daslib/safe_addr.das | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/daslib/safe_addr.das b/daslib/safe_addr.das index f821aff488..e29b250e7d 100644 --- a/daslib/safe_addr.das +++ b/daslib/safe_addr.das @@ -32,6 +32,22 @@ def public safe_addr(x : auto(T)& ==const) : T -&? const# { } } +[tag_function(safe_addr_tag)] +def public safe_addr(var x : auto(T)[]& ==const) : T -&?# { + //! returns temporary pointer to the first element of a fixed array (C-style array decay) + unsafe { + return reinterpret(addr(x)) + } +} + +[tag_function(safe_addr_tag)] +def public safe_addr(x : auto(T)[]& ==const) : T -&? const# { + //! returns temporary pointer to the first element of a fixed array (C-style array decay) + unsafe { + return reinterpret(addr(x)) + } +} + [tag_function_macro(tag="safe_addr_tag")] class SafeAddrMacro : AstFunctionAnnotation { //! This macro reports an error if safe_addr is attempted on the object, which is not local to the scope. From 33b54e9925266bbb4103bbff50fd46c2963273cc Mon Sep 17 00:00:00 2001 From: Boris Batkin Date: Thu, 11 Jun 2026 00:46:15 -0700 Subject: [PATCH 23/23] fixed-array docs: group make_fixed_array_type under Type generation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Uncategorized-section grep in doc.yml rejects any public function not assigned to a group_by_regex in das2rst.das — a separate gate from the das2rst run itself, which passes locally without it. Co-Authored-By: Claude Fable 5 --- doc/reflections/das2rst.das | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/reflections/das2rst.das b/doc/reflections/das2rst.das index 8c2e949753..a129a6a982 100644 --- a/doc/reflections/das2rst.das +++ b/doc/reflections/das2rst.das @@ -374,7 +374,7 @@ def document_module_ast_boost(_root : string) { group_by_regex("Annotations", mod, %regex~(append.*|add.*)%%), group_by_regex("Expression generation", mod, %regex~(override_method|panic_expr_as|make_static_assert_false|convert_to_expression)%%), group_by_regex("Visitors", mod, %regex~(visit.*)%%), - group_by_regex("Type generation", mod, %regex~(function_to_type)%%), + group_by_regex("Type generation", mod, %regex~(function_to_type|make_fixed_array_type)%%), group_by_regex("Type casts", mod, %regex~(`is`|`as`|walk_and_convert)%%), hide_group(group_by_regex("Clonning", mod, %regex~(clone)%%)), group_by_regex("Setup", mod, %regex~setup.*$%%)