Option<T> / Result<T,E>: structural named-tuple via [template_tuple]#2601
Merged
Option<T> / Result<T,E>: structural named-tuple via [template_tuple]#2601
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
This PR addresses daslang generic candidate ambiguity when Option<T>/Result<T,E> templates are publicly re-exported through multiple module chains (issue #2598) by switching them from module-owned [template_structure] instances to structural named tuples produced by a new [template_tuple] structure macro. It also adds compiler/runtime support so tuple elements can carry @safe_when_uninitialized behavior, and updates dependent tooling (dasSQLITE + AOT) plus regression tests.
Changes:
- Add
[template_tuple]indaslib/typemacro_boost.dasto synthesize typemacro-based named tuple types (structural identity, no per-module cloning). - Convert
daslib/option.dasanddaslib/result.dasto structural tuple forms, with per-element@safe_when_uninitializedpropagation via a newTypeDecl::safeWhenUninitializedflag. - Update compiler/AOT + dasSQLITE Option-shape handling and add regression tests for cross-module Option usage and unsafe-uninitialized payloads.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/option/test_option_user_struct.das | New regression test ensuring Option<UserStruct> works across module boundaries and remains unambiguous. |
| tests/option/test_option_unsafe_uninitialized.das | New regression test validating Option<T> payload field safety for “unsafe when uninitialized” payload structs. |
| tests/option/_test_option_user_struct_mod.das | Test helper module that publicly re-exports daslib/option and returns Option<Foo> to exercise cross-module typing. |
| src/builtin/module_builtin_ast_flags.cpp | Exposes new TypeDeclFlags.safeWhenUninitialized bit to daslang code. |
| src/ast/ast_typedecl.cpp | Propagates safeWhenUninitialized through auto contracts; short-circuits unsafeInit when the flag is set. |
| src/ast/ast_infer_type.cpp | Allows silent dedupe of type-only assume X : T when the same alias already resolves to the same type. |
| modules/dasSQLITE/daslib/sqlite_boost.das | Updates Option detection/unwrapping logic to recognize the new structural tuple shape. |
| include/daScript/ast/ast_typedecl.h | Adds TypeDecl::safeWhenUninitialized flag bit. |
| daslib/typemacro_boost.das | Implements the new [template_tuple] structure macro and per-field safe-when-uninitialized propagation. |
| daslib/result.das | Converts Result<T,E> to [template_tuple] + updates constructors/move helpers for tuple construction. |
| daslib/option.das | Converts Option<T> to [template_tuple] + updates move_some to construct tuples directly. |
| daslib/aot_cpp.das | Adjusts AOT local make-struct destination type emission to skip const to satisfy aggregate initialization constraints. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
e33db52 to
4a8332f
Compare
4a8332f to
23f8d59
Compare
Fixes #2598. Combining daslib/clargs and modules/dasSQLITE/daslib/sqlite_boost in one module (directly or via require ... public chains) made every Option<string> ambiguous: [template_structure] cloned an instance struct into each consuming module via qmacro_template_class, producing N module-aliased views of the same source decl. Candidate collection treated them as distinct and emitted "error[30341]: too many matching functions or generics _::Option<string>" with three identical-looking candidates pointing at daslib/option.das. Rather than a typer-side dedupe of candidates by source decl (broader scope, not gated on this PR), Option and Result now resolve to STRUCTURAL named tuples. Tuples have no module home; multiple transitive re-exports collapse to one canonical tTuple TypeDecl regardless of which alias the user lands on. [template_structure(T), safe_when_uninitialized] struct template Option ... -> [template_tuple(T)] struct template Option { _has_value : bool = false @safe_when_uninitialized _value : T } Option<T> -> tuple<_has_value : bool; _value : T> Result<T,E> -> tuple<_is_ok : bool; _value : T; _error : E> * daslib/typemacro_boost.das: new [template_tuple(T...)] structure_macro. At apply time it generates a [typemacro_function]; at typemacro-execution time the body has two paths: - generic: shape-matches a tTuple by argNames + literal-field baseType, and feeds template-arg fields into infer_template_types via passArgument.argTypes[i]. - concrete: looks up the source struct via ``typeinfo ast_typedecl(type<X>)`` (X = the struct's module-scoped alias) and applies templates_boost's existing TypeDecl visitor (apply_template + replaceTypeWithTypeDecl) to each field. The visitor walks ``T``, ``array<T>``, ``tuple<int; T; E>``, etc. uniformly; no apply-time recursion over field shapes is needed. Field-level @safe_when_uninitialized propagates: when a source field carries the annotation, the corresponding tuple element's TypeDecl gets TypeDeclFlags.safeWhenUninitialized. The tuple's recursive unsafeInit walks its argTypes, so per-element marking gives the same effect the legacy struct-level [safe_when_uninitialized] used to. * daslib/option.das, daslib/result.das: collapse to near-original struct declarations with annotation swap + move_some / move_ok / move_err rewrites that drop the typedef RT shorthand in favor of anonymous named-tuple construction (new pattern allowed by gen2 grammar). Compiler-side support for safe-when-uninitialized at TypeDecl granularity: * TypeDecl::safeWhenUninitialized flag bit + unsafeInit short-circuit. Lets a typemacro mark a tuple component (e.g. Option's _value field) safe to leave uninitialized even when the underlying T isn't, restoring the property the struct-level [safe_when_uninitialized] previously gave Option/Result. * TypeDecl::applyAutoContracts OR-merges the new flag the same way it merges ref/constant/temporary, so generic resolution preserves it across auto. * TypeDeclFlags exposes the bit to daslang code. * InferTypes::preVisit(ExprAssume*) silently dedupes ``assume X : T`` when an outer scope already binds X to the same type. Handles the auto- injected assume placed at the start of a generic instance body when a typemacro-built result type carries an alias the body also references. * AOT codegen: emit local make-struct destinations without ``const``. Aggregate types holding TTuple<> need to be value-init'd at declaration; the const qualifier was making C++ reject ``T const x;`` for those shapes. dasSQLITE adapter rail: is_option_field_type / unwrap_option_payload_type detect the typeMacro form (during structure_macro apply) and the post-instantiation tTuple form. The legacy struct-shaped detection drops out — no in-tree code emits a struct-shaped Option after this PR. Tests: * tests/option/test_option_user_struct.das: cross-module Option<UserStruct>. * tests/option/test_option_unsafe_uninitialized.das: Option<Brittle> where Brittle is unsafe-when-uninitialized; covers default<>, none(), some(), unwrap_or_default round-trips, and the bare ``var x : Option<Brittle>`` case which is the only form that hits ast_infer_type.cpp:5040's "Uninitialized variable is unsafe" check (verified by removing the field annotation locally and observing error[31016]). Verified: tests/option 205/205 + tests/dasSQLITE 771/771 (interpreted & AOT); full tests/ tree 7961/7961 interpreted, 7355/7355 AOT. Issue #2598 repro (clargs + sqlite_boost + Option<string>) compiles cleanly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
23f8d59 to
b32186a
Compare
3 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #2598. Combining
daslib/clargsandmodules/dasSQLITE/daslib/sqlite_boost(directly or viarequire ... publicchains) made everyOption<string>ambiguous —[template_structure]cloned an instance struct into each consuming module viaqmacro_template_class, producing N module-aliased views of the same source decl. Candidate collection treated them as distinct and emittederror[30341]: too many matching functions or generics _::Option<string>with three identical-looking candidates pointing at the same line.Rather than a typer-side dedupe of candidates by source decl (broader scope, not gated on this PR),
OptionandResultnow resolve to structural named tuples. Tuples have no module home; multiple transitive re-exports collapse to one canonicaltTupleTypeDecl regardless of which alias the user lands on.Option<T>→tuple<_has_value : bool; _value : T>Result<T,E>→tuple<_is_ok : bool; _value : T; _error : E>Pieces
daslib/typemacro_boost.das— new[template_tuple(T...)][structure_macro]. At apply time it generates a[typemacro_function]; at typemacro-execution time the body has two paths:tTuplebyargNames+ literal-fieldbaseType, and feeds template-arg fields intoinfer_template_typesviapassArgument.argTypes[i].typeinfo ast_typedecl(type<X>)(X = the struct's module-scoped alias) and appliestemplates_boost's existing TypeDecl visitor (apply_template+replaceTypeWithTypeDecl) to each field. The visitor walksT,array<T>,tuple<int; T; E>, etc. uniformly — no apply-time recursion over field shapes is needed.Field-level
@safe_when_uninitializedpropagates: when a source field carries the annotation, the corresponding tuple element's TypeDecl getsTypeDeclFlags.safeWhenUninitialized. The tuple's recursiveunsafeInitwalks itsargTypes, so per-element marking gives the same effect the legacy struct-level[safe_when_uninitialized]used to.daslib/option.das,daslib/result.das— collapse to near-original struct declarations with annotation swap +move_some/move_ok/move_errrewrites that drop thetypedef RTshorthand in favor of anonymous named-tuple construction.Compiler-side support for safe-when-uninitialized at TypeDecl granularity:
TypeDecl::safeWhenUninitializedflag bit +unsafeInitshort-circuit. Lets a typemacro mark a tuple component (e.g.Option's_valuefield) safe to leave uninitialized even when the underlyingTisn't.TypeDecl::applyAutoContractsOR-merges the new flag the same way it mergesref/constant/temporary, so generic resolution preserves it acrossauto.TypeDeclFlagsexposes the bit to daslang code.InferTypes::preVisit(ExprAssume*)silently dedupesassume X : Twhen an outer scope already bindsXto the same type. Handles the auto-injected assume placed at the start of a generic instance body when a typemacro-built result type carries an alias the body also references.InferTypes::visit(ExprAssume*)mirrors the same predicate so a deduped re-assume is also kept out ofassumeType, leavingfindAliaslookups clean.const. Aggregate types holdingTTuple<>need to be value-init'd at declaration; the const qualifier was making C++ rejectT const x;for those shapes.dasSQLITE adapter rail —
is_option_field_type/unwrap_option_payload_typedetect the typeMacro form (during[sql_table]apply) and the post-instantiationtTupleform. The legacy struct-shape detection drops out — no in-tree code emits a struct-shapedOptionafter this PR.Test plan
daslang dastest/dastest.das -- --test tests/option/→ 205/205 passdaslang dastest/dastest.das -- --test tests/dasSQLITE/→ 771/771 passdaslang dastest/dastest.das -- --test tests/→ 7961/7961 interpretedtest_aot.exe -use-aot dastest/dastest.das -- --use-aot --test tests/→ 7355/7355 AOTOption<string>ambiguous when bothdaslib/clargsandsqlite/sqlite_boostare in scope #2598 repro (require daslib/clargs+require sqlite/sqlite_boost+Option<string>field) compiles cleanly@safe_when_uninitializedfrom_value : Tand recompilingtests/option/test_option_unsafe_uninitialized.dasproduceserror[31016]on the barevar x : Option<Brittle>line — the per-field annotation is doing real workarray<T>,tuple<int; T; E>) substitute correctly via the visitor — verified with a hand-rolled[template_tuple]probe carryingarray<T>andtuple<int; T; E>field shapes.dasfilesformat_fileclean on all changed.dasfiles🤖 Generated with Claude Code