Skip to content

Ownership pass, fused Enum pipelines, std method types, and a restructured lowering - #20

Merged
dannote merged 11 commits into
masterfrom
ownership-pass
Oct 26, 2026
Merged

dannote merged 11 commits into
masterfrom
ownership-pass

Conversation

@dannote

@dannote dannote commented Oct 25, 2026

Copy link
Copy Markdown
Collaborator

The lowering restructure discussed after rc.17. Behavior changes only where generated Rust gets more precise; Figler's generated crate is byte-identical except one collect::<Vec<_>>() that now names Vec<Occurrence>.

Ownership (RustQ.Meta.Ownership). A backward liveness pass annotates every variable occurrence with what is still in use after it, following Elixir's scoping: sequence and argument order, alternative branches (case, if, cond, with), closures and comprehension bodies as repeated regions (what they capture stays live; their own variables follow the body), rebinding ending a value, and values built from another keeping it live while they may borrow (by type). Lowering asks one question, "is this the last use?", replacing the per-statement analysis, the others lists threaded through Enum/Map/MapSet, and the in-place update list in ast.ex; parameter mut now comes from the lowered Rust. Fixes the sibling gap: {Map.put(scores, …), scores} clones.

Fused pipelines. An Enum call over another Enum stage (map, filter, reject, flat_map, with_index, take_while, drop_while, zip_with, slice, take, drop, reverse, chunk_every) continues its iterator instead of collecting: .map(f).find(p), .filter(p).count().

Std method types. mix rustq.std derives 179 return types from std's source for methods on str, String, char, Vec, slices, HashMap, HashSet, and Option whose results don't depend on the receiver's type parameters; Typing uses them, so name.len() is usize and predicates get |&len|.

Desugaring (RustQ.Meta.Desugar). Map.new, MapSet.new, Enum.into into an empty collection, group_by, uniq, and with_index generators are rewritten into other Rusty-Elixir before ownership and lowering, leaving __rustq_items__/__rustq_owned__ where lowering must decide by type or ownership. Lowering accepts multi-expression blocks as expressions, and Typing types comprehensions.

Split lower.ex (4,536 → 2,907 lines) along call-graph seams into Lower.Pattern, Lower.Comprehension, Lower.Paths, and Lower.Mutability, which call back through a few documented Lower functions.

Checked: mix ci (885 tests, every fixture cargo-checked); Figler mix ci and cargo clippy -D warnings on this branch.

Lowering wrote Rust nodes as struct literals and kept five copies of a
method-call constructor. It now builds them with the Builder the Rustler
code generation already uses, and Builder.expr/1 accepts every node in
the AST registry instead of a hand-kept list.
Each Elixir module's lowering implements RustQ.Meta.Lower.Stdlib.Lowering.
Using it imports the helpers they share (lowered/2, type_of/2, closure/3,
in_use?/2, and the rest), which call Lower and Typing directly instead
of through a context of closures. Stdlib dispatches a call to the module
named after its Elixir module when that module implements the behaviour,
in place of a clause per module for lowering and another for typing.
Path calls, unary and binary operations, closures, tuples, Some/Ok/Err,
matches, assignments, and named types now use Builder constructors too.
Lowering behaviour, shared helpers, and Builder-built Rust
@dannote
dannote merged commit 4cf8342 into master Oct 26, 2026
2 checks passed
@dannote dannote mentioned this pull request Oct 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant