Commit Graph

126 Commits

Author SHA1 Message Date
Brummel da1746229d iter 22b.4a.2: form-a parser arm for InstanceDef 2026-05-09 22:05:49 +02:00
Brummel a1b4cd0063 iter 22b.4a.1.fix: drop test comments narrating plan deviations 2026-05-09 22:03:27 +02:00
Brummel fefaa03c76 iter 22b.4a.1: form-a parser arm for ClassDef 2026-05-09 21:59:29 +02:00
Brummel 090f082215 iter 22b.3.tester: e2e for coherence, default-keyword, cross-module mono
Adds three end-to-end tests for the 22b.3 mono pass, each with a
matching one-property fixture under examples/. Defends mono-pass
invariants 3, 4, and 6 from docs/specs/2026-05-09-22-typeclasses.md
at the binary level (stdout-asserting, not AST-asserting):

- coherence_two_instances_pick_correct_body_at_runtime — class R
  with instances at Int and Bool, both reachable from one main.
  Stdout `11\n22` proves both mono fns are synthesised AND each
  call site picks the matching body (no collapse).

- class_default_method_runs_and_prints_default_value — empty
  instance body + class-level `default` clause must lower, link,
  and print the default value (`99`). Promotes the existing
  synthesise_mono_fn unit test to a binary-level guarantee.

- cross_module_class_method_runs_and_prints_correct_value —
  class+instance in module A, called from module B's main. The
  qualified-name rewrite + cross-module synth-def placement was
  AST-tested in 22b.3.6; this pins down the emit-and-link path.

All 18 typeclass_22b3 tests pass; full workspace green; 3 bench
gates 0/0/0.
2026-05-09 21:36:15 +02:00
Brummel b59820b737 iter 22b.3.7: synthetic fixture — class+instance compiles, runs, prints 5 2026-05-09 21:28:41 +02:00
Brummel 7f260b82ad iter 22b.3.6: fix — quality-review (shadow-aware walker, cross-mod test, Option-typed targets, const helper) 2026-05-09 21:21:33 +02:00
Brummel d1b590ceab iter 22b.3.6: call-site rewrite — same/cross module qualified names 2026-05-09 21:07:58 +02:00
Brummel e8018d48b6 iter 22b.3.5: fix — quality-review (chained-call test, hoist class_index, drop noise comments) 2026-05-09 20:59:43 +02:00
Brummel dd87b36e00 iter 22b.3.5: workspace fixpoint loop — dedup + synth-append 2026-05-09 20:52:07 +02:00
Brummel d2954010ea iter 22b.3.4: fix — quality-review (zero-arg test, drop what-comments + param-name pin) 2026-05-09 20:45:56 +02:00
Brummel d6084a71fb iter 22b.3.4: synthesise_mono_fn — instance body + class default fallback 2026-05-09 20:41:45 +02:00
Brummel 9f86c072dc iter 22b.3.3: fix — revert dead pub(crate), reuse build_module_types, drop defensive unwrap
Quality-review follow-up to 89311ee:

- Revert `substitute_rigids` to private. Task 3 does not call it; the
  visibility bump was forward-looking for Task 4 (synthesise_mono_fn).
  Task 4 will re-bump it in the same diff that uses it.
- Promote `build_module_types` to pub(crate); drop the inline
  reimplementation in `mono::build_workspace_env`. Removes drift risk
  between the two paths.
- Replace defensive `unwrap_or_default()` on `build_module_globals`
  with `expect(...)` naming the pre-condition. Silent default violated
  CLAUDE.md "no defensive validation in internal code paths".
- Add a one-line drift-risk reminder at the top of `build_workspace_env`
  flagging that it and `check_in_workspace` both populate `Env`.

Build clean; all 5 typeclass_22b3 tests green; no workspace regressions.
2026-05-09 20:36:52 +02:00
Brummel 89311ee20b iter 22b.3.3: collect_mono_targets — synth-replay residual gathering 2026-05-09 20:31:12 +02:00
Brummel c77fd1b85e iter 22b.3.2: fix — drop speculative Float (plan-text overreach)
Float is referenced nowhere else in the codebase; the plan-text
listed it aspirationally. Restored parity with the four other
primitive enumerations across the crate. When Float lands as a
real type (post-22b.4 Prelude work, if at all), the arm gets
re-added in the same iter that introduces Type::float().

Deferred (not part of this fix):
- Shared-constant extraction across the four primitive enumeration
  sites (linearity.rs:143, lib.rs:1232/2214, mono.rs) — workspace-
  level cleanup belonging to the milestone-22 audit phase.
- pub mod mono vs sibling pub use lift::lift_letrecs style — plan
  prescribed pub mod for 22b.3 because Tasks 3-7 add multiple pub
  items reached by integration tests; per-item pub use would
  proliferate.
2026-05-09 20:25:51 +02:00
Brummel 028227587b iter 22b.3.2: mono_symbol — primitive surface forms + hash for compound 2026-05-09 20:21:24 +02:00
Brummel 108115f7dd iter 22b.3.1: fix — quality-review (doc reframe + class-present test) 2026-05-09 20:18:20 +02:00
Brummel 209074ee5d iter 22b.3.1: mono pass skeleton — identity for class-free workspaces 2026-05-09 20:12:36 +02:00
Brummel 73965a6cbd iter 22b.2.e2e: cross-module class resolution + multi-fn aggregation
Four new E2E tests in typeclass_22b2.rs covering invariants the
per-task fixtures (all single-module, single-fn) did not exercise:

- cross_module_class_method_resolves_and_fires_no_instance:
  module B's call to `show` (declared in module A) reaches the
  no-instance arm, proving Env.class_methods is merged workspace-
  wide and not restricted to the local module.
- cross_module_polymorphic_fn_without_constraint_fires_missing_constraint:
  same merge guarantees the missing-constraint arm sees the residual
  produced by a call to A's class method from inside B.
- cross_module_instance_satisfies_concrete_call: the workspace
  registry built at load time over all modules must populate keys
  visible from B; a concrete call resolves clean cross-module.
- two_fns_each_missing_constraint_produce_two_diagnostics: pins
  check_in_workspace's per-def aggregation — two ill-formed fns
  produce two diagnostics (one per fn), not one.

Six new fixtures (xmod_* trio + two_fns_*).
2026-05-09 19:19:44 +02:00
Brummel 47bec74b26 iter 22b.2.10: fix — positive green-path test, drop dead let _ = ws
Adds the missing positive counterpart to the no-instance test:
`show 42` with `instance Show Int` PRESENT must typecheck green.
This guards against registry-keying or threading bugs that would
otherwise sneak past the negative-only test (an empty registry at
typecheck time, or a key shape mismatch, would silently fire
no-instance even when the instance exists).

Also drops the dead `let _ = ws;` and its stale comment in
`check_in_workspace` — the line above already consumes
`ws.registry`, so the explicit drop is redundant. The remaining
comment is folded into the registry-threading comment so the
"registry is the only thing threaded" rationale is preserved.
2026-05-09 19:13:32 +02:00
Brummel 3600e82ec2 iter 22b.2.10: no-instance per-fn diagnostic 2026-05-09 19:07:46 +02:00
Brummel b741a8ba05 iter 22b.2.9: fix — superclass-walk tests, simplify class_superclasses map shape 2026-05-09 18:59:15 +02:00
Brummel 7cff741632 iter 22b.2.9: missing-constraint per-fn diagnostic 2026-05-09 18:51:39 +02:00
Brummel afa06a3f3d iter 22b.2.8: fix — fold double-match, IndexMap parity, broader contract test 2026-05-09 18:37:49 +02:00
Brummel 17605a3327 iter 22b.2.8: register class methods in module globals 2026-05-09 18:33:01 +02:00
Brummel 6869bd5d0c iter 22b.2.7: fix — cycle termination guard, hoist type_repr 2026-05-09 18:23:52 +02:00
Brummel ae7281ac41 iter 22b.2.7: fix — use examples_dir() helper in superclass-instance test 2026-05-09 18:20:18 +02:00
Brummel a546c5851c iter 22b.2.7: missing-superclass-instance diagnostic 2026-05-09 18:18:12 +02:00
Brummel b252f837af iter 22b.2.6: fix — structural Origin enum, fn-fn collision delegation, test path helper
Address quality review of a9aa248:

- Replace string-prefix discriminator (`prior.starts_with("class ")`)
  with a structural `Origin { Class | Fn }` enum; `kind` is now a
  `match` on variants, formatting is reserved for error construction.
- Fix fn-fn collision misreport: in the `Def::Fn` arm, only fire
  `MethodNameCollision` when the prior origin is a class. Two
  `Def::Fn` with the same name are `CheckError::DuplicateDef`'s job
  in `ailang-check`, not this pre-pass.
- Tests use `examples_dir()` like the rest of the module; path
  construction is no longer ad-hoc.
- Tests assert `first_origin` / `second_origin` so an origin-format
  regression cannot silently break the `kind` discriminator.
- Drop forward-looking 22b.4 prelude reservation note from the
  variant doc — keep only what's load-bearing for 22b.2.
2026-05-09 18:13:49 +02:00
Brummel a9aa248910 iter 22b.2.6: method-name-collision diagnostic 2026-05-09 18:07:52 +02:00
Brummel 1f6d2322c3 iter 22b.2.5: fix — move OverridingNonExistentMethod after UnboundConstraintTypeVar 2026-05-09 18:01:51 +02:00
Brummel 634bd5eca3 iter 22b.2.5: overriding-non-existent-method diagnostic 2026-05-09 17:59:10 +02:00
Brummel 257b47625d iter 22b.2.4: constraint-references-unbound-type-var diagnostic 2026-05-09 17:54:20 +02:00
Brummel 4b84e781d4 iter 22b.2.3: invalid-superclass-param class-schema diagnostic 2026-05-09 17:50:29 +02:00
Brummel 928deb9d74 iter 22b.2.2: kind-mismatch class-schema diagnostic 2026-05-09 17:45:27 +02:00
Brummel 04bde14a9e iter 22b.2.1: fix — revert PartialEq semantic change, drop unused derives 2026-05-09 17:38:20 +02:00
Brummel b636b883a3 iter 22b.2.1: add Constraint struct, Type::Forall.constraints 2026-05-09 17:34:39 +02:00
Brummel 057784934a iter 22b.1.3: registry coherence-check fixtures + tests
Adds seven fixtures under examples/test_22b1_* exercising the
workspace-load coherence checks plus four positive/negative tests in
ailang-core/src/workspace.rs:

- iter22b1_instance_in_class_module_loads_clean: positive case;
  test_22b1_orphan_class.ail.json declares Show + instance Show Int
  in the same module (the class's). Registry has one entry.
- iter22b1_orphan_instance_fires_diagnostic: declares Show in
  test_22b1_orphan_third_classmod, declares instance Show Int in
  test_22b1_orphan_third (a third module). Fires OrphanInstance.
- iter22b1_duplicate_instance_fires_diagnostic: per the JOURNAL hint,
  module A defines class Show + instance Show MyInt (legal, A is
  class's module), module B defines type MyInt + instance Show MyInt
  (legal, B is type's module). Entry imports both. Fires
  DuplicateInstance with first/second_module distinct.
- iter22b1_missing_method_fires_diagnostic: class Eq with two
  non-default methods (eq, ne), instance Eq Int specifies only ne.
  Fires MissingMethod { method: "eq" }.

The surface round-trip gate (ailang-surface/tests/round_trip.rs)
filters out test_22b1_* fixtures because the Form-B parser arms for
ClassDef/InstanceDef are deferred to 22b.4. Without the filter, every
fixture's printed text would re-parse-fail on the unknown `class` /
`instance` head.

Test count: 291 → 295 (4 new workspace tests, all green).
2026-05-09 12:40:06 +02:00
Brummel eb4db9dafc iter 22b.1.2: workspace registry skeleton + coherence checks
Adds the workspace-global typeclass instance registry per Decision 11
"Resolution and monomorphisation". Built at the end of load_workspace
after the DFS over imports completes; keyed by (class-name, type-hash)
where type-hash uses the new canonical::type_hash (16-hex prefix,
parallel to def_hash and module_hash).

Three coherence checks fire from build_registry, each surfacing as a
distinct WorkspaceLoadError variant:

- OrphanInstance: `instance C T` not in C's or T's defining module.
- DuplicateInstance: two entries share the same (class, type-hash) key.
- MissingMethod: instance omits a required (non-default) method.

The CLI's workspace_error_to_diagnostic is extended with the three new
codes (orphan-instance / duplicate-instance / missing-method).

All existing Workspace { ... } construction sites get the new
`registry` field, defaulting to Registry::default() at synthetic /
test-only paths and threading through ws.registry.clone() at codepath
sites that already hold a real workspace.

Includes hash-stability regression tests (iter22b1_schema_extension_*
and iter22b1_classdef_empty_optionals_hash_stable) and the empty-
registry positive test against examples/sum.ail.json. Test count:
288 → 291 (3 new tests, all pass).
2026-05-09 12:37:00 +02:00
Brummel f25d7b6cd6 iter 22b.1.1: ClassDef/InstanceDef AST + downstream match arms
Adds Def::Class(ClassDef) and Def::Instance(InstanceDef) variants per
Decision 11 §"Form-A schema". All optional fields (superclass, doc,
default body) carry skip_serializing_if so canonical-JSON bytes of
pre-22b fixtures stay bit-identical.

Downstream match-on-Def sites are extended with explicit Class/Instance
arms. Behaviour for 22b.1 is placeholder (skip in typecheck/codegen,
one-line summary in pretty/manifest, placeholder marker in prose/print)
with TODO comments naming the deferred sub-iters (22b.2 typecheck,
22b.3 codegen, 22b.4 prose-projection arms).
2026-05-09 12:31:23 +02:00
Brummel 7e5c95f6c6 runtime: half-retirement of Boehm — flip CLI default to --alloc=rc
The dual-allocator policy from 2026-05-08 made Boehm the CLI default
"for general workloads", with rc selected explicitly for real-time-
sensitive code. That asymmetry contradicted Decision 10: rc + uniqueness
inference is the canonical memory model AILang's typechecker enforces,
and the CLI was teaching the opposite mental model to users and prompt-
fed LLMs.

This iter flips the asymmetry without removing Boehm:

  - main.rs: default_value gc -> rc for build/run; help text rewritten
    so rc is the canonical path and gc is the parity oracle.
  - DESIGN.md Decision 9 retitled "RC canonical, Boehm parity oracle"
    and rewritten end-to-end. Migration plan step 6 updated to a
    two-step retirement (default-flip done, full removal gated).
  - DESIGN.md pipeline diagram: rc-first, gc-as-oracle.
  - JOURNAL: 2026-05-09 entry recording the call, what shipped,
    the bug the flip surfaced, the gating condition for full
    Boehm removal.

Bug surfaced by the flip and fixed in the same iter:
codegen/drop.rs build_pair_drop_fn emitted `getelementptr inbounds
{{ ptr, ptr }}, ...` via push_str (not format!), so the doubled
braces leaked verbatim into the IR. LLVM parsed it as a struct-of-
struct, the GEP referenced a non-existent field, and clang failed.
Invisible under the old gc default (no per-type drop fns) and
invisible to the 8 explicit _with_alloc("rc") tests (none of them
escaped a closure-with-captures). Caught immediately by two corpus
tests once rc became the baseline: closure_captures_let_n,
local_rec_as_value_capture_demo. Fix: single braces. Tests stay
as the regression guard.

The episode is the canonical justification for keeping the GC arm
as a parity oracle for now — a months-old codegen bug found by
flipping the baseline column.

288 passed / 0 failed / 3 ignored, unchanged.
2026-05-09 00:14:56 +02:00
Brummel 72626fa94f ail: embed Form-A spec in merge-prose prompt (iter 20f)
Closes a design hole shipped in 20d: the merge-prose prompt instructed
the LLM to emit JSON-AST and gave a 12-line schema-essentials reminder.
JSON-AST is the canonical hashable artefact, not a writing surface; the
reminder was not a language spec. Foreign LLMs had no realistic shot.

20f makes two coupled changes:

1) The LLM now emits Form-A (the canonical authoring surface fixed by
   Decision 6). merge-prose loads the original via ailang_core::load_module
   and re-renders via ailang_surface::print before embedding (round-trip
   is a gating contract on the surface crate, so this is lossless). The
   user runs `ail parse foo.new.ailx` to recover JSON, then `ail check`.

2) crates/ailang-core/specs/form_a.md is the complete LLM-targeted
   Form-A specification — grammar, every term/pattern/type/def keyword,
   schema invariants, pitfall catalogue, four few-shot modules from
   examples/*.ailx. Exported as ailang_core::FORM_A_SPEC via include_str!
   and embedded verbatim in every merge-prose prompt.

Drift detection in crates/ailang-core/tests/spec_drift.rs: every variant
of Term, Pattern, Type, Def, Literal is reached via exhaustive `match`.
The arms are not the assertion — adding a new variant without updating
the match is a compile error before the test runs. Once matched, an
anchor string is asserted to appear in FORM_A_SPEC. 8 tests, all green.

The hand-written + mechanical-drift-test combo addresses the user's
"distance to code is too big" concern about a docs-only spec. Generator
overkill rejected: structural drift is mechanically caught, but prose
explanation, schema-invariant catalogue, pitfall list, and few-shot
corpus cannot be emitted from AST shape alone.

Tests:
  - ailang-core: +8 spec_drift tests; existing 12 unchanged
  - ail unit (3): rewritten in lockstep — assert (own T)/(effects IO)
    landmarks, FORM-A SPECIFICATION header, FORM_A_SPEC body verbatim
  - ail e2e merge_prose_prints_framed_prompt: rewritten — assert
    `(module foo` + `FORM-A SPECIFICATION` instead of `ailang/v0`
  - Workspace: all green

Richer integration paths (LLM tool-use, MCP server, LSP) were named
in the design discussion and deferred per "kiss". All three layer
additively on the static-prompt path; static prompt remains the
lowest-common-denominator fallback.
2026-05-08 23:31:27 +02:00
Brummel 8375eb81ed prose: split reuse-as keyword + inline single-use lets (iter 20b polish)
reuse-as: render `(reuse-as src body)` as `reuse src as body` with
adaptive braces — inline for single-line bodies (the 99% Ctor case),
braced for multi-line (Let, Seq, nested Match). Reads as English
subject-verb-object instead of compound keyword + always-block.

let-inlining: `let x = rhs; <body using x once>` collapses to
`<body[x := rhs]>` when (a) rhs is not a `Term::Do` (keep effect
sequencing visible), (b) x is used exactly once (no work duplication),
(c) rhs renders single-line AND ≤ 40 chars (no line smearing). Lossy
projection — round-trip mediator sees the original .ail.json and can
re-introduce a let when the mode model demands it.

Helpers `count_free_var` and `subst_var_with_term` are render-local;
both respect inner shadowing (let, lam, match arm pattern binders,
let rec). Pre-render value at current level + check for newline + len
budget; on inline, write_term_prec gets passed parent_prec so the
substituted term still wraps correctly inside binops.

Snapshot drift: rc_app_let_partial_drop_leak.prose.txt — boilerplate
`let p = build_pair(1)` collapsed into the match scrutinee. Improves
readability. Cons-tower in rc_own_param_drop kept its let thanks to
the length budget (52 chars > 40).

Tests: 59 unit (+ 9 new) + 4 snapshot, all green. Coverage:
inline-single-use, two-uses-keeps, zero-uses-keeps, Do-rhs-keeps,
multiline-rhs-keeps, length-budget-keeps, shadowing-not-counted,
reuse-as inline + braces.
2026-05-08 23:03:10 +02:00
Brummel 50b68267fe check: mode-strict-because suppression (iter 19b)
Closes the 19a/19a.1/19b arc. Corpus signal from 19a.1 (5/65
fixtures fire over-strict-mode, all deliberate RC codegen-test
fixtures) justified shipping the suppress mechanism end-to-end.

Schema: Suppress { code, because } on FnDef. Pre-19b hashes
bit-identical via skip_serializing_if. Typechecker drops matching
diagnostics; empty 'because' is Error severity; wrong codes are
silent no-ops (open-set registry).

Form-A: (suppress (code "...") (because "...")) clause, parser
+ printer round-trip clean. Form-B: '// @suppress <code>: <reason>'
above the doc string, lossless contract metadata.

5 RC fixtures migrated (.ail.json + .ailx + .prose.txt). Corpus
signal: 5/65 -> 0/65. Lint still fires on any future fn that's
accidentally over-strict without an authored reason.

Test counts: ailang-check 55->61, ailang-core 26->28, ailang-surface
21->26, ailang-prose 49->52, e2e 70 unchanged.

Known debt: .ailx comment headers lost on regen (ail render's
contract excludes comments); parse_suppress_attr accepts
duplicate code/because attrs without diagnose (bounded by canonical
print order).
2026-05-08 19:36:04 +02:00
Brummel 9f3f10ce6e check: precise sub-binder analysis for over-strict-mode (iter 19a.1)
Corpus-signal upgrade: orchestrator ran 19a's lint across 65
fixtures, zero warnings fired. The conservative match-scrutinee
carve-out filtered every (own T) fixture because every such body
destructures the param via match. Real-corpus signal warranted
the precise variant.

Precise rule: for an Own param with consume_count == 0, walk each
match arm whose scrutinee is the param; if any HEAP-TYPED
pattern-binder has consume_count > 0, stay silent (sub-consume
forces ownership). Otherwise fire.

Heap-type filter is the implementer's design call (not in the
brief): reading a primitive (h: Int) is load-by-value, no RC, no
heap-data move; reading heap-typed t: IntList IS a pointer move
requiring outer-cell ownership. Documented in module-doc.

Corpus signal after upgrade: 5/65 fixtures fire — all RC
codegen-test fixtures with deliberate over-strictness for the
codegen path. Justifies 19b (mode-strict-because suppression).

ailang-check test count: 53 → 55. e2e + everything else green.
Known debt: deeply-nested-match-on-sub-binder (no fixture hits).
2026-05-08 19:19:31 +02:00
Brummel b9e942b99d prose: doc-wrap widow control for 1-word orphans
When greedy word-wrap leaves a 1-word last line, pull the
penultimate line's tail word down so the orphan has at least
2 words. Skipped if the combined length would exceed budget
(invariant takes priority over cosmetics).

Visible on nested_let_rec.prose.txt:
  before: '...captures outer's param\n/// i.'
  after:  '...captures outer's\n/// param i.'

Two new unit tests; existing 4 snapshots unaffected (none had
a 1-word orphan).
2026-05-08 19:18:37 +02:00
Brummel 9c09dfbc8d ail: merge-prose subcommand + PROSE_ROUNDTRIP.md (iter 20d)
Closes family 20: prose-edit cycle is end-to-end. ail merge-prose
<original.ail.json> <edited.prose.txt> composes a shaped LLM prompt
to stdout. User pipes to their LLM (Claude/etc.), gets new .ail.json,
runs ail check, saves.

No built-in API client — AILang stays a compiler + tooling, the LLM
mediator is supplied externally. PROSE_ROUNDTRIP.md documents the
6-step cycle, the prompt contract (preserve modes/effects/tail
flags from original), failure modes, and the design choice to omit
an API client.

Family 20 now closes: 20a renderer + 20b polish + 20d mediator
shipped; 20c rolled into 20a. Three deferred polishes (let-inlining,
print sugar, doc-wrap widow control) wait for corpus signal.
2026-05-08 18:22:46 +02:00
Brummel 9cf0e3e81c prose: infix + paren elision + doc-wrap + nested-match (iter 20b)
Eleven binary builtins (+ - * / % == != < <= > >=) render as
lhs op rhs when called with two args (tail-flagged keeps prefix).
Standard Rust-aligned 4-level precedence table elides parens.
not(x) renders as !x. Long /// doc strings wrap at 80 cols on
word boundaries.

bench_list_sum: 'if ==(n, 0)' becomes 'if n == 0', '+(acc, h)'
becomes 'acc + h', tail keyword still visible on tail calls.

19 new unit tests (47 total). 4 snapshots (3 re-rendered + 1 new
bench fixture). Public API unchanged.
2026-05-08 18:13:43 +02:00
Brummel a9d57c5c81 prose: human-readable projection renderer (iter 20a)
New crate ailang-prose with one public fn module_to_prose(&Module)
-> String. Rust-flavour with braces, =>-match-arms, mode keywords
(own/borrow), effects as trailing 'with IO', Cons(1, Nil) ctor
form, /// doc strings.

Lossy projection where the LLM can re-derive: (con T) wrap,
(fn-type ...) wrap, (term-ctor ...) wrap. Load-bearing semantic
detail stays visible (modes, effects, clone, reuse-as, doc strings,
type annotations, tail flag).

CLI: 'ail prose <file.ail.json>' prints the projection.

3 snapshot fixtures + 28 unit tests. 215-line .ail.json reduces
to ~18 lines of legible source.

20b queued: infix arithmetic, paren elision, let-inlining, do
prettify.
2026-05-08 18:06:16 +02:00
Brummel 41804a39fb check: over-strict-mode lint (iter 19a)
When a fn-param is annotated (own T) but the body never consumes
it (consume_count == 0) and never destructures it via match, the
linearity pass now emits a Severity::Warning diagnostic with a
form-A-rendered (borrow T) rewrite as suggested_rewrite. Pure
advisory, Decision 10 unchanged.

First Warning-severity diagnostic the typechecker emits; three
CLI exit paths gated on Error-only.

JOURNAL: design entry + iter 19a shipping entry.
2026-05-08 17:56:58 +02:00
Brummel caefcf996a codegen rc: tag-conditional partial-drop helper closes 3 carve-outs
Three sibling sites previously fell back to shallow `ailang_rc_dec`
when a binder had non-empty `moved_slots` and a dynamic runtime ctor
tag: Iter B Own-param dec at fn-return (lib.rs), Iter A arm-close
pattern-binder dec (match_lower.rs), and emit_inlined_partial_drop's
non-Ctor branch (drop.rs). Shallow freed the outer cell but did not
walk the unmoved ptr fields — silent leak.

Adds `emit_partial_drop_fn_for_type`: a per-ADT helper structurally
parallel to `emit_drop_fn_for_type` but taking an i64 moved-slots
bitmask. Each ptr-field's dec is gated on the corresponding mask
bit; the outer box is dec'd at join. Emitted alongside the per-type
drop fn for every ADT under --alloc=rc.

Three call sites rewritten to resolve the partial_drop symbol from
the binder's static type and build the mask from `moved_slots`.

Three RED-then-GREEN fixtures (rc_own_param_/rc_match_arm_/
rc_app_let_partial_drop_leak) pin the carve-outs in regression
coverage; live cell counts go from 3/1/3 (pre-fix) to 0/0/0.
e2e count: 66 → 69.

The pre-existing `alloc_rc_own_param_dec_at_fn_return` IR-shape
assertion is widened to accept `partial_drop_<m>_<T>(%arg_xs,
i64 mask)` as a third valid drop shape (the canonical fu2 case).

Closes the JOURNAL queue's only remaining iter; the "wait for
organic fixture" stance from the 18g tidy is retracted — known
leaks are bugs, CLAUDE.md's TDD rule covers them autonomously.
2026-05-08 16:25:40 +02:00