From 76b21c00eb1be6cc656f2539e82f3bccab6c2ecf Mon Sep 17 00:00:00 2001 From: Brummel Date: Tue, 2 Jun 2026 00:03:46 +0200 Subject: [PATCH] =?UTF-8?q?feat(lang):=20eliminate=20the=20Implicit=20owne?= =?UTF-8?q?rship=20default=20=E2=80=94=20totality=20+=20the=20drop-soundne?= =?UTF-8?q?ss=20it=20demasks=20(#55)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Deletes `ParamMode::Implicit`. `ParamMode` is now `{Own, Borrow}`: every fn-type slot on every signature carries an explicit `own` or `borrow`, no defaulted position survives anywhere (model 0008 §2, spec 0062). The parser rejects a bare fn-type slot; `borrow-return` and `borrow-over-value` reject at the signature; the corpus is migrated to minimal-ownership modes (consumed ⇒ own, read-only-heap ⇒ borrow, value ⇒ trivial-own). The documented `Implicit`-ret-mode leak is fixed: an owned heap return now drops exactly once (live=0, acceptance criterion 5). This was the easy half. Removing the default ACTIVATED a family of drop paths that `Implicit` had silently skipped — the pre-cutover language was leaking (and in places mis-dropping) here rather than crashing, because an Implicit scrutinee turned the drop off. Making the modes explicit (Own) turned those paths on and exposed two latent-bug clusters, all fixed RED-first as part of this cutover: Drop-soundness family (four legs): A. lit-sub-pattern double-free — the desugar re-matched the same owned scrutinee in the lit fall-through; fixed by grouping consecutive same-ctor arms into one match (bind fields once), in ailang-core desugar. B. Cons-husk leak on non-tail arm bodies — the lit-sub-pattern desugar rebound the owned scrutinee via `Let $mp = xs`, which bumped consume_count and suppressed the existing fn-return partial_drop. Fixed by not rebinding a bare-Var scrutinee (one husk-freeing mechanism, not two). C. polymorphic `drop_` rc_dec'd monomorphised value fields — the per-ADT drop fn was emitted once from the polymorphic TypeDef, defaulting type-var fields to ptr and rc_dec'ing inline Ints (segfault). Fixed with per-monomorph drop functions (new ailang-codegen::dropmono): the drop set is collected from the lowered MIR, value-type fields are skipped, heap fields still freed once; monomorphic-concrete ADTs keep their byte-identical un-suffixed drop symbol. D. static Str literal passed to an `(own Str)` param — the literal lowers to a header-less rodata constant; the callee's now-active rc_dec read its length field as a refcount and freed a static address (segfault). Fixed with the missing fourth StrRep::Static→Heap promotion in lower_to_mir's App arm, gated on Own mode (borrow args stay static, no regression). over-strict-mode lint over-fired: it suggested `(borrow V)` for value-typed params (which `borrow-over-value` rejects — own is the only legal mode there) and fired on `(intrinsic)` bodies (whose consumption the linearity walk cannot observe). Tightened to skip both; contract 0008 updated to the narrowed firing scope. Irreversible step — canonical-form hash reset (model 0008 §6, acceptance criterion 6). Every signature now carries explicit modes, so the hashable canonical JSON changed for every module. RATIFY: the corpus-wide hash-pin reset (hash_pin, prelude_module_hash_pin, mono_hash_stability, eq_ord_e2e, embed_export_hash_stable, the ct4/iter*/loop_recur schema-extension pins) and the list ir_snapshot golden were regenerated once, deliberately, as the intended one-time consequence of removing the mode elision from the canonical form — not a regression. Each regenerated hash verified deterministic across two runs. Also fixes a pre-existing latent failure surfaced by the verification gate, unrelated to this cutover: the `every_contract_names_a_resolvable_ ratifying_test` resolver (design_index_pin) could not resolve the " + " dual-link ratifying-test form (`uniqueness.rs + linearity.rs`) that the #57 audit-close (dfdc65f) introduced — it shipped red on that commit. Resolver taught the dual-link form, mirroring its sibling. Verification: cargo test --workspace = 731 passed, 0 failed (twice, stable); e2e 102 passed, no binary exits non-zero (corpus crash-free); grep-clean for Implicit/fn_implicit/mode_eq across crates; every drop fix confirmed via emitted IR + AILANG_RC_STATS balance on the head==K, head!=K, and Nil paths. Three BLOCKEDs en route (the unsound first husk-dec attempt, the over-strict derivation premise, the leg-B fix direction) were each treated as a real design/spec gap and rediagnosed, not patched over. Supersedes #54 (return-position-only leak patch). Precondition #57 (linearity hardening) was already met. Spec docs/specs/0062, plan docs/plans/0121. closes #55 --- ...2026-06-01-iter-0121-implicit-cutover.json | 17 + crates/ail/src/main.rs | 20 - .../cli_diag_human_workspace_load_error.rs | 2 +- .../tests/drop_value_field_no_segfault_pin.rs | 122 +++++ crates/ail/tests/e2e.rs | 206 +++++++-- crates/ail/tests/eq_ord_e2e.rs | 2 +- crates/ail/tests/migrate_canonical_types.rs | 4 +- crates/ail/tests/migrate_modes.rs | 27 -- crates/ail/tests/mode_signature_rejects.rs | 20 + crates/ail/tests/mono_hash_stability.rs | 12 +- crates/ail/tests/print_no_leak_pin.rs | 38 +- crates/ail/tests/roundtrip_cli.rs | 21 +- crates/ail/tests/snapshots/list.ll | 1 + crates/ail/tests/typeclass_22b3.rs | 4 +- crates/ail/tests/user_adt_new_mono_pin.rs | 4 +- crates/ailang-check/src/builtins.rs | 14 +- crates/ailang-check/src/lib.rs | 213 +++++---- crates/ailang-check/src/lift.rs | 4 +- crates/ailang-check/src/linearity.rs | 199 ++++++-- crates/ailang-check/src/lower_to_mir.rs | 30 +- crates/ailang-check/src/mono.rs | 9 + crates/ailang-check/src/reuse_shape.rs | 17 +- crates/ailang-check/src/suppress_filter.rs | 2 +- crates/ailang-check/src/uniqueness.rs | 4 +- crates/ailang-check/tests/workspace.rs | 14 +- crates/ailang-codegen/src/drop.rs | 274 ++++++++--- crates/ailang-codegen/src/dropmono.rs | 437 ++++++++++++++++++ crates/ailang-codegen/src/lambda.rs | 10 +- crates/ailang-codegen/src/lib.rs | 122 +++-- crates/ailang-codegen/src/match_lower.rs | 29 +- crates/ailang-codegen/src/subst.rs | 38 +- crates/ailang-core/src/ast.rs | 123 +---- crates/ailang-core/src/desugar.rs | 203 ++++++-- crates/ailang-core/src/pretty.rs | 2 +- crates/ailang-core/src/workspace.rs | 30 +- crates/ailang-core/tests/design_index_pin.rs | 36 +- .../ailang-core/tests/design_schema_drift.rs | 14 +- .../tests/embed_export_hash_stable.rs | 4 +- crates/ailang-core/tests/hash_pin.rs | 20 +- crates/ailang-core/tests/schema_coverage.rs | 18 +- crates/ailang-core/tests/spec_drift.rs | 6 +- crates/ailang-core/tests/workspace_kernel.rs | 10 +- crates/ailang-kernel/src/raw_buf/source.ail | 8 +- crates/ailang-prose/src/lib.rs | 39 +- crates/ailang-surface/src/parse.rs | 75 ++- crates/ailang-surface/src/print.rs | 22 +- .../ailang-surface/tests/mut_removed_pin.rs | 4 +- .../tests/prelude_module_hash_pin.rs | 2 +- .../tests/reserved_dollar_pin.rs | 6 +- crates/ailang-surface/tests/round_trip.rs | 15 +- design/contracts/0002-data-model.md | 17 +- design/contracts/0008-memory-model.md | 53 ++- examples/bare_slot_reject.ail | 10 + examples/bench_closure_chain.ail | 16 +- examples/bench_compute_collatz.ail | 12 +- examples/bench_compute_intsum.ail | 8 +- examples/bench_hof_pipeline.ail | 20 +- examples/bench_latency_explicit.ail | 24 +- examples/bench_latency_implicit.ail | 38 +- examples/bench_list_sum.ail | 20 +- examples/bench_list_sum.prose.txt | 12 +- examples/bench_list_sum_explicit.ail | 14 +- examples/bench_mono_dispatch.ail | 6 +- examples/bench_tree_walk.ail | 12 +- examples/bool_to_str_drop_rc.ail | 2 +- examples/bool_to_str_smoke_false.ail | 2 +- examples/borrow_own_demo.ail | 6 +- examples/borrow_value_reject.ail | 10 + examples/box.ail | 6 +- examples/broken_unbound.ail.json | 12 +- examples/bug_unbound_in_instance_method.ail | 4 +- examples/classify_pin.ail | 4 +- examples/clone_demo.ail | 2 +- examples/closure.ail | 4 +- examples/cmp_max_smoke.ail | 4 +- examples/compare_float_noinstance.ail | 2 +- examples/compare_primitives_smoke.ail | 2 +- examples/ct_1_ordering_signum.ail | 6 +- examples/ct_2_bare_cross_module.ail | 6 +- examples/ct_3_bad_qualified.ail | 6 +- examples/ct_3b_bad_qualified_known_module.ail | 6 +- examples/ctt2_collision_cls.ail | 2 +- examples/drop_value_field_no_segfault_pin.ail | 44 ++ examples/embed_backtest_step.ail | 8 +- examples/embed_backtest_step_record.ail | 4 +- .../embed_backtest_step_record_borrow.ail | 4 +- examples/embed_backtest_step_tick.ail | 2 +- examples/embed_backtest_step_tick_borrow.ail | 2 +- examples/embed_export_adt_ret_rejected.ail | 4 +- examples/embed_export_effectful_rejected.ail | 4 +- examples/embed_export_float_ok.ail | 4 +- examples/embed_export_io_rejected.ail | 4 +- ...mbed_export_list_field_record_rejected.ail | 2 +- examples/embed_export_multictor_rejected.ail | 2 +- examples/embed_export_record_ok.ail | 4 +- ...embed_export_str_field_record_rejected.ail | 2 +- examples/embed_export_str_param_rejected.ail | 4 +- examples/embed_noentry_baseline.ail | 4 +- examples/embed_record_layout_carrier.ail | 10 +- examples/eq_demo.ail | 4 +- examples/eq_float_must_fail.ail | 2 +- examples/eq_float_noinstance.ail | 2 +- examples/eq_ord_polymorphic.ail | 4 +- examples/eq_ord_user_adt.ail | 2 +- examples/eq_primitives_smoke.ail | 4 +- examples/eq_user_adt_smoke.ail | 2 +- examples/escape_local_demo.ail | 6 +- examples/fieldtest/embabi1_1_ema_step.ail | 4 +- .../fieldtest/embabi1_2_leaky_integrator.ail | 4 +- .../embabi1_3_export_list_rejected.ail | 2 +- ...babi1_4_export_logged_counter_rejected.ail | 4 +- examples/fieldtest/eqord_1_fizzbuzz.ail | 10 +- examples/fieldtest/eqord_2_rational_eq.ail | 2 +- examples/fieldtest/eqord_3_newton_sqrt.ail | 8 +- .../fieldtest/eqord_4_float_ord_must_fail.ail | 2 +- .../fieldtest/eqord_5_float_eq_must_fail.ail | 2 +- examples/fieldtest/floats_1_newton_sqrt.ail | 8 +- .../fieldtest/floats_2_average_int_list.ail | 12 +- examples/fieldtest/floats_3_safe_division.ail | 4 +- .../fieldtest/floats_4_float_to_str_reach.ail | 2 +- examples/fieldtest/forma_1_factorial.ail | 10 +- examples/fieldtest/forma_2_show_color.ail | 2 +- .../fieldtest/forma_3_user_class_describe.ail | 6 +- examples/fieldtest/forma_4_intmath_lib.ail | 6 +- examples/fieldtest/forma_4_intmath_main.ail | 2 +- examples/fieldtest/kem_1_list_running_sum.ail | 4 +- examples/fieldtest/kem_2_counter_new.ail | 4 +- examples/fieldtest/kem_2b_min_repro.ail | 4 +- .../fieldtest/kem_4_paramin_box_green.ail | 4 +- examples/fieldtest/kem_4_paramin_box_red.ail | 4 +- .../fieldtest/loop_recur_1_isqrt_newton.ail | 4 +- examples/fieldtest/loop_recur_2_collatz.ail | 4 +- .../loop_recur_3a_recur_outside_loop.ail | 4 +- .../fieldtest/loop_recur_3b_recur_arity.ail | 4 +- .../fieldtest/loop_recur_3c_recur_type.ail | 4 +- .../loop_recur_3d_recur_not_tail.ail | 4 +- .../loop_recur_3e_binder_captured.ail | 6 +- .../fieldtest/loop_recur_4_gcd_value_pos.ail | 4 +- .../loop_recur_5_event_loop_noterm.ail | 4 +- .../loop_recur_5b_event_loop_noterm.ail | 4 +- examples/fieldtest/mut-local_1_factorial.ail | 2 +- .../fieldtest/mut-local_2_classify_temp.ail | 4 +- examples/fieldtest/mut-local_3_horner.ail | 2 +- .../mut-local_4_has_small_factor.ail | 4 +- examples/fieldtest/rawbuf_1_score_table.ail | 6 +- examples/fieldtest/rawbuf_2_running_max.ail | 2 +- examples/fieldtest/rawbuf_3_sensor_pair.ail | 2 +- .../fieldtest/rawbuf_4_paramin_reject.ail | 2 +- .../fieldtest/rbx_1_float_window_stats.ail | 8 +- examples/fieldtest/rbx_2_bool_sieve.ail | 6 +- .../fieldtest/rbx_3_unit_element_reject.ail | 4 +- .../rbx_4_int_two_borrow_helpers.ail | 8 +- .../fieldtest/remove-mut_1_sum_of_squares.ail | 4 +- .../fieldtest/remove-mut_2_grade_cascade.ail | 4 +- .../fieldtest/remove-mut_3_horner_poly.ail | 4 +- .../remove-mut_4_bracket_scanner.ail | 8 +- examples/flat_pat_shadow_control.ail | 6 +- examples/flat_pat_shadow_leak.ail | 6 +- examples/float_compare_smoke.ail | 2 +- examples/float_to_str_smoke.ail | 2 +- examples/floats.ail | 2 +- examples/ge_at_int_smoke.ail | 2 +- examples/gt_at_bool_smoke.ail | 2 +- examples/heap_str_repeated_print_borrow.ail | 2 +- examples/hello.ail | 2 +- examples/hof.ail | 6 +- examples/int_to_print_int_borrow.ail | 2 +- examples/int_to_str_drop_rc.ail | 2 +- examples/int_to_str_smoke.ail | 2 +- examples/kernel_intrinsic_smoke.ail | 2 +- examples/le_at_str_smoke.ail | 2 +- examples/list.ail | 4 +- examples/list_map.ail | 6 +- examples/list_map_poly.ail | 14 +- examples/lit_pat.ail | 6 +- examples/lit_pat_ctor_tail_drop.ail | 47 ++ examples/lit_pat_nil_scrutinee_drop.ail | 52 +++ examples/local_rec_as_value.ail | 6 +- examples/local_rec_as_value_capture.ail | 8 +- examples/local_rec_capture.ail | 6 +- examples/local_rec_demo.ail | 4 +- examples/local_rec_let_capture.ail | 6 +- examples/local_rec_match_capture.ail | 6 +- examples/loop_forever.ail | 2 +- examples/loop_forever_build.ail | 4 +- examples/loop_forever_build.prose.txt | 4 +- examples/loop_let_bound_binder_ref.ail | 2 +- .../loop_recur_heap_binder_no_leak_pin.ail | 2 +- .../loop_recur_str_binder_no_leak_pin.ail | 2 +- .../loop_str_recur_literal_no_leak_pin.ail | 2 +- examples/loop_str_static_exit_no_leak_pin.ail | 2 +- examples/loop_sum_to.ail | 2 +- examples/loop_sum_to_deep.ail | 4 +- examples/loop_sum_to_run.ail | 4 +- examples/loop_sum_to_run.prose.txt | 4 +- examples/lt_at_int_smoke.ail | 2 +- examples/max3.ail | 6 +- examples/maybe_int.ail | 4 +- examples/mir2_callee_kinds.ail | 4 +- examples/mono_hash_pin_smoke.ail | 4 +- examples/mq1_xmod_constraint_class.ail | 2 +- examples/mq1_xmod_constraint_class_dep.ail | 2 +- examples/mq3_class_eq_vs_fn_eq.ail | 2 +- examples/mq3_class_eq_vs_fn_eq_classmod.ail | 2 +- examples/mq3_class_eq_vs_fn_eq_fnmod.ail | 2 +- examples/mq3_two_show_ambiguous.ail | 2 +- examples/mq3_two_show_ambiguous_a.ail | 2 +- examples/mq3_two_show_ambiguous_b.ail | 2 +- examples/mq3_two_show_qualified.ail | 2 +- examples/mut.ail | 12 +- examples/mut_counter.ail | 4 +- examples/mut_sum_floats.ail | 4 +- examples/ne_at_int_smoke.ail | 2 +- examples/nested_let_rec.ail | 8 +- examples/nested_pat.ail | 6 +- examples/new_counter_user_adt.ail | 4 +- examples/new_rawbuf_size_only.ail | 2 +- examples/nullary_app_smoke.ail | 4 +- examples/operator_unbound_check.ail | 2 +- examples/ord_int_intercept_smoke.ail | 4 +- examples/ordering_match.ail | 2 +- examples/ordering_match.prose.txt | 2 +- examples/own_return_provenance_ctor.ail | 18 + examples/own_return_provenance_let.ail | 14 + examples/own_return_provenance_reject.ail | 14 + examples/own_str_arg_drop.ail | 29 ++ examples/ownership_total.ail | 46 ++ examples/pat_extract_partial_drop.ail | 6 +- examples/poly_apply.ail | 6 +- examples/poly_id.ail | 4 +- examples/poly_rec_capture.ail | 12 +- examples/prelude.ail | 28 +- examples/print_eq_arg_repro.ail | 2 +- examples/print_float_whole_smoke.ail | 2 +- examples/print_int_no_leak_pin.ail | 2 +- ...print_str_embedded_newline_no_doubling.ail | 2 +- examples/print_str_no_auto_newline.ail | 2 +- examples/raw_buf_adt_field_bare.ail | 4 +- examples/raw_buf_adt_field_qualified.ail | 4 +- examples/raw_buf_bool.ail | 2 +- examples/raw_buf_borrow_read.ail | 4 +- examples/raw_buf_drop_min.ail | 2 +- examples/raw_buf_float.ail | 2 +- examples/raw_buf_int.ail | 2 +- examples/raw_buf_loop_no_leak_pin.ail | 2 +- examples/raw_buf_new_type_arg_intsize.ail | 2 +- examples/raw_buf_new_type_arg_unit_reject.ail | 2 +- examples/raw_buf_reject_str.ail | 2 +- examples/rc_app_let_partial_drop_leak.ail | 6 +- .../rc_app_let_partial_drop_leak.prose.txt | 6 +- examples/rc_box_drop.ail | 2 +- examples/rc_drop_iterative_long_list.ail | 8 +- examples/rc_let_alias_implicit_param.ail | 12 +- examples/rc_let_implicit_returning_app.ail | 44 +- examples/rc_let_owned_app_leak.ail | 6 +- examples/rc_list_drop.ail | 4 +- examples/rc_list_drop_borrow.ail | 2 +- examples/rc_match_arm_partial_drop_leak.ail | 6 +- .../rc_match_arm_partial_drop_leak.prose.txt | 6 +- examples/rc_own_param_drop.ail | 4 +- examples/rc_own_param_drop.prose.txt | 4 +- examples/rc_own_param_partial_drop_leak.ail | 6 +- examples/rc_pin_recurse_implicit.ail | 8 +- examples/rc_tail_sum_explicit_leak.ail | 12 +- examples/reuse_as_demo.ail | 4 +- examples/show_mono_pin_smoke.ail | 2 +- examples/show_no_instance.ail | 2 +- examples/show_print_smoke.ail | 2 +- examples/show_user_adt.ail | 2 +- examples/show_user_adt_with_label.ail | 2 +- examples/sort.ail | 8 +- examples/std_either.ail | 26 +- examples/std_either_demo.ail | 4 +- examples/std_either_list.ail | 12 +- examples/std_either_list_demo.ail | 2 +- examples/std_list.ail | 60 +-- examples/std_list_demo.ail | 10 +- examples/std_list_more_demo.ail | 2 +- examples/std_list_stress.ail | 10 +- examples/std_maybe.ail | 18 +- examples/std_maybe_demo.ail | 4 +- examples/std_pair.ail | 24 +- examples/std_pair_demo.ail | 6 +- examples/str_clone_cross_realisation.ail | 2 +- examples/str_clone_drop_rc.ail | 2 +- examples/str_field_in_adt_heap.ail | 2 +- examples/sum.ail | 4 +- examples/test_22b1_dup_classmod.ail | 2 +- examples/test_22b1_dup_same_module.ail | 2 +- examples/test_22b1_missing_method.ail | 4 +- examples/test_22b1_orphan_class.ail | 2 +- examples/test_22b1_orphan_third_classmod.ail | 2 +- examples/test_22b2_class_method_lookup.ail | 2 +- examples/test_22b2_constraint_declared.ail | 4 +- ...2b2_constraint_declared_via_superclass.ail | 6 +- ...nstraint_declared_via_superclass.prose.txt | 6 +- examples/test_22b2_instance_present.ail | 4 +- examples/test_22b2_instance_present.prose.txt | 4 +- ...est_22b2_invalid_superclass_param.ail.json | 53 ++- examples/test_22b2_kind_mismatch.ail.json | 49 +- ...22b2_method_name_collision_class_class.ail | 4 +- ...st_22b2_method_name_collision_class_fn.ail | 4 +- examples/test_22b2_missing_constraint.ail | 4 +- .../test_22b2_missing_constraint_subclass.ail | 6 +- .../test_22b2_missing_superclass_instance.ail | 4 +- examples/test_22b2_no_instance.ail | 4 +- examples/test_22b2_overriding_nonexistent.ail | 2 +- .../test_22b2_two_fns_missing_constraint.ail | 6 +- .../test_22b2_unbound_constraint_var.ail.json | 28 +- examples/test_22b2_xmod_classmod.ail | 2 +- examples/test_22b2_xmod_classmod_noinst.ail | 2 +- examples/test_22b2_xmod_instance_present.ail | 2 +- .../test_22b2_xmod_missing_constraint.ail | 2 +- examples/test_22b2_xmod_no_instance.ail | 2 +- examples/test_22b3_chained_calls.ail | 6 +- .../test_22b3_coherence_two_instances.ail | 4 +- examples/test_22b3_default_e2e.ail | 4 +- examples/test_22b3_default_e2e.prose.txt | 4 +- examples/test_22b3_dup_call_sites.ail | 4 +- examples/test_22b3_mono_synthetic.ail | 4 +- examples/test_22b3_no_call_sites.ail | 4 +- examples/test_22b3_shadow_class_method.ail | 6 +- examples/test_22b3_xmod_e2e_classmod.ail | 2 +- examples/test_22b3_xmod_e2e_main.ail | 2 +- examples/test_22c_user_class_e2e.ail | 4 +- examples/test_ct1_bad_qualifier.ail.json | 14 +- examples/test_ct1_bare_xmod_rejected.ail.json | 11 +- ...test_ct1_qualified_class_rejected.ail.json | 5 +- ...st_loop_binder_captured_by_lambda.ail.json | 39 +- examples/test_mono_ctor_main.ail | 6 +- examples/test_mono_imports_classmod.ail | 4 +- examples/test_mono_imports_main.ail | 2 +- examples/test_mono_recursive_fn_bug.ail | 6 +- examples/test_recur_arity_mismatch.ail.json | 52 ++- .../test_recur_not_in_tail_position.ail.json | 74 ++- examples/test_recur_outside_loop.ail.json | 25 +- examples/test_recur_type_mismatch.ail.json | 40 +- examples/unreachable_demo.ail | 4 +- examples/ws_broken.ail | 2 +- examples/ws_lib.ail | 2 +- examples/ws_main.ail | 2 +- examples/ws_unknown_module.ail | 2 +- 342 files changed, 3196 insertions(+), 1503 deletions(-) create mode 100644 bench/orchestrator-stats/2026-06-01-iter-0121-implicit-cutover.json create mode 100644 crates/ail/tests/drop_value_field_no_segfault_pin.rs delete mode 100644 crates/ail/tests/migrate_modes.rs create mode 100644 crates/ailang-codegen/src/dropmono.rs create mode 100644 examples/bare_slot_reject.ail create mode 100644 examples/borrow_value_reject.ail create mode 100644 examples/drop_value_field_no_segfault_pin.ail create mode 100644 examples/lit_pat_ctor_tail_drop.ail create mode 100644 examples/lit_pat_nil_scrutinee_drop.ail create mode 100644 examples/own_return_provenance_ctor.ail create mode 100644 examples/own_return_provenance_let.ail create mode 100644 examples/own_return_provenance_reject.ail create mode 100644 examples/own_str_arg_drop.ail create mode 100644 examples/ownership_total.ail diff --git a/bench/orchestrator-stats/2026-06-01-iter-0121-implicit-cutover.json b/bench/orchestrator-stats/2026-06-01-iter-0121-implicit-cutover.json new file mode 100644 index 0000000..ffb95f0 --- /dev/null +++ b/bench/orchestrator-stats/2026-06-01-iter-0121-implicit-cutover.json @@ -0,0 +1,17 @@ +{ + "iter_id": "0121-implicit-cutover", + "date": "2026-06-01", + "mode": "standard", + "outcome": "PARTIAL", + "tasks_total": 1, + "tasks_completed": 0, + "reloops_per_task": { "4": 0 }, + "review_loops_spec": 0, + "review_loops_quality": 0, + "blocked_reason": "worker-blocked", + "corpus_files_mode_flipped_this_session": 6, + "corpus_files_migrated_total": 280, + "test_failures_remaining": 82, + "test_failures_at_session_start": 96, + "notes": "Continuation. Phase A Step 3 FINISHED: corpus linearity-clean (6 files own->borrow for read-only-heap/HOF params: std_list filter+fold predicate, bench_hof_pipeline, bench_latency_implicit loop/one_op/pin_root, rc_pin_recurse_implicit pin, rc_let_alias pin_aliased, floats_2_average sum_acc/count_acc; std_*_demo import std_list so its fix propagates). Must-fail fixtures (real_consume, c4_double_consume, 3 own_return_provenance) correctly stay RED (criterion 4). Fixed a real codegen index-panic (codegen lib.rs:1341/1494 + surface print.rs:406: param_modes[i] -> .get(i).unwrap_or(Own)) for synthesised fn-defs with empty param_modes (lambda thunks / local-rec lifts) — unblocked local_rec/nested_let_rec/poly_rec demos. Rewrote 2 obsolete linearity in-source tests to binary-model semantics. REMAINING BLOCKER: a real codegen double-free (segfault, exit 139) from the lit-sub-pattern desugar (core/desugar.rs:1221) x arm-close Iter-A drop (codegen/match_lower.rs:788): an (own ADT) param matched with a (Cons (pat-lit K) _) arm double-drops the tail because the lit `else`/fall_k re-matches the same scrutinee and each re-match arm-close drop fires on the same children. Latent pre-cutover (Implicit scrutinees skipped the drop, leaking); the cutover's Own scrutinees activate it. Gates ~7 list demos + cascading mono/rewrite tests. Needs RED-first codegen debugging (debug-skill class), NOT mechanical fixture work. Hash pins deliberately NOT reset (ordering: pins last, only after codegen correct, so the irreversible reset is not baked over a buggy state)." +} diff --git a/crates/ail/src/main.rs b/crates/ail/src/main.rs index b9ec576..db33386 100644 --- a/crates/ail/src/main.rs +++ b/crates/ail/src/main.rs @@ -76,10 +76,6 @@ enum Cmd { /// `render | parse` reproduces the input's canonical bytes /// (gated by `crates/ailang-surface/tests/round_trip.rs`). Render { path: PathBuf }, - /// THROWAWAY (spec 0062): rewrite every bare fn-type slot in a - /// `.ail` file as `(own …)`, preserving explicit `(own)`/`(borrow)`. - /// Removed in the Implicit-deletion cutover. - MigrateModes { path: PathBuf }, /// Prints a single definition as JSON or pretty text. Describe { path: PathBuf, @@ -444,22 +440,6 @@ fn main() -> Result<()> { let m = ailang_surface::load_module(&path)?; print!("{}", ailang_surface::print(&m)); } - Cmd::MigrateModes { path } => { - let src = std::fs::read_to_string(&path)?; - let mut m = ailang_surface::parse(&src)?; - ailang_core::ast::for_each_fn_type_mut(&mut m, &mut |n, pm, rm| { - pm.resize(n, ailang_core::ast::ParamMode::Own); - for x in pm.iter_mut() { - if matches!(x, ailang_core::ast::ParamMode::Implicit) { - *x = ailang_core::ast::ParamMode::Own; - } - } - if matches!(rm, ailang_core::ast::ParamMode::Implicit) { - *rm = ailang_core::ast::ParamMode::Own; - } - }); - std::fs::write(&path, ailang_surface::print(&m))?; - } Cmd::Prose { path } => { // load via `load_module` (single-module mode, // matching how `render` / `parse` work). Workspace-wide diff --git a/crates/ail/tests/cli_diag_human_workspace_load_error.rs b/crates/ail/tests/cli_diag_human_workspace_load_error.rs index 4b7d078..4f8d0ca 100644 --- a/crates/ail/tests/cli_diag_human_workspace_load_error.rs +++ b/crates/ail/tests/cli_diag_human_workspace_load_error.rs @@ -39,7 +39,7 @@ fn tempdir_with_missing_import() -> PathBuf { let mut f = std::fs::File::create(&entry).expect("create entry .ail"); writeln!( f, - "(module test_missing_import\n (import this_module_does_not_exist)\n (fn main\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (do io/print_str \"hello\"))))" + "(module test_missing_import\n (import this_module_does_not_exist)\n (fn main\n (type (fn-type (params) (ret (own (con Unit))) (effects IO)))\n (params)\n (body (do io/print_str \"hello\"))))" ).expect("write fixture"); entry } diff --git a/crates/ail/tests/drop_value_field_no_segfault_pin.rs b/crates/ail/tests/drop_value_field_no_segfault_pin.rs new file mode 100644 index 0000000..c64601c --- /dev/null +++ b/crates/ail/tests/drop_value_field_no_segfault_pin.rs @@ -0,0 +1,122 @@ +//! RED-pin for leg (C) of the drop-soundness bug family the +//! Implicit-cutover (#55) surfaced: a polymorphic ctor's per-type +//! drop fn rc_dec's a type-parameter field even when the field is +//! monomorphised at a value type (Int/Bool/Float/Unit). +//! +//! Property protected: under `--alloc=rc`, dropping a value of a +//! polymorphic ADT whose ctor field is monomorphised at a value type +//! must NOT emit `ailang_rc_dec` on that field. The field is stored +//! inline as a raw scalar (an i64 `7`, not a heap pointer), so an +//! `rc_dec` dereferences the scalar-as-pointer and crashes. The +//! binary must run to completion, print the inner value, and the +//! runtime RC stats line must report `live == 0` (the single ADT +//! slab is freed exactly once, with no spurious dec of the inline +//! value field). +//! +//! Root cause: `crates/ailang-codegen/src/drop.rs` +//! `emit_drop_fn_for_type` (mirrored in `emit_iterative_drop_fn_for_type` +//! and `emit_partial_drop_fn_for_type`) emits the per-type drop fn ONCE +//! from the polymorphic `Def::Type` declaration — never per-monomorph. +//! For each ctor field it computes `llvm_type(fty)` where `fty` is the +//! declared field type. For a type-parameter field that type is a +//! `Type::Var`, which `llvm_type` rejects (synth.rs returns `Err`); the +//! `.unwrap_or_else(|_| "ptr".into())` at the field loop then silently +//! treats the var-typed field as a boxed pointer and emits `rc_dec`. +//! At the `Box Int` monomorph the field is an inline `i64`, so the +//! emitted `rc_dec` dereferences the integer value -> SIGSEGV. +//! +//! As of HEAD (the in-flight Implicit cutover) this test fails: the +//! binary exits 139 (SIGSEGV) before printing anything. Pre-cutover +//! the bug was latent — Implicit scrutinees skipped the drop path +//! entirely, so the mistyped drop never ran (it leaked instead of +//! crashing). +//! +//! A single-field `Box a` at `Int` is the minimal trigger. The fix +//! must still dec genuinely-heap fields (e.g. a `Box Str` field must +//! free its slab exactly once) and must hold for the partial-drop and +//! nested-monomorph variants — see the debugger handoff for the fix +//! locus and soundness reasoning. + +use std::path::Path; +use std::process::Command; + +fn ail_bin() -> &'static str { + env!("CARGO_BIN_EXE_ail") +} + +#[test] +fn alloc_rc_value_type_field_is_not_rc_dec_dropped() { + let manifest_dir = env!("CARGO_MANIFEST_DIR"); + let workspace = Path::new(manifest_dir).parent().unwrap().parent().unwrap(); + let src = workspace + .join("examples") + .join("drop_value_field_no_segfault_pin.ail"); + let tmp = std::env::temp_dir().join(format!( + "ailang_drop_value_field_no_segfault_pin_{}", + std::process::id() + )); + std::fs::create_dir_all(&tmp).unwrap(); + let out = tmp.join("bin"); + let status = Command::new(ail_bin()) + .args(["build", src.to_str().unwrap(), "--alloc=rc", "-o"]) + .arg(&out) + .status() + .expect("ail build failed to run"); + assert!( + status.success(), + "ail build --alloc=rc failed for drop_value_field_no_segfault_pin.ail" + ); + let output = Command::new(&out) + .env("AILANG_RC_STATS", "1") + .output() + .expect("execute binary"); + assert!( + output.status.success(), + "binary exited non-zero (status {:?}): the polymorphic drop fn \ + drop__Box rc_dec's the type-parameter field, which at the \ + Box Int monomorph is the inline i64 `7` (not a heap pointer), \ + so rc_dec(7) dereferences the scalar-as-pointer and SIGSEGVs. \ + Fix in crates/ailang-codegen/src/drop.rs: do not dec a field \ + whose monomorphised type is a value type.", + output.status + ); + let stdout = String::from_utf8(output.stdout).expect("stdout utf8"); + assert_eq!( + stdout.trim_end(), + "7", + "unboxing MkBox 7 at Int must print 7; got {stdout:?}" + ); + let stderr = String::from_utf8(output.stderr).expect("stderr utf8"); + let stats_line = stderr + .lines() + .find(|l| l.starts_with("ailang_rc_stats:")) + .unwrap_or_else(|| { + panic!("missing ailang_rc_stats line in stderr; stderr was:\n{stderr}") + }); + let mut allocs: Option = None; + let mut frees: Option = None; + let mut live: Option = None; + for tok in stats_line.split_whitespace() { + if let Some(v) = tok.strip_prefix("allocs=") { + allocs = v.parse().ok(); + } else if let Some(v) = tok.strip_prefix("frees=") { + frees = v.parse().ok(); + } else if let Some(v) = tok.strip_prefix("live=") { + live = v.parse().ok(); + } + } + let allocs = allocs.expect("missing allocs= field"); + let frees = frees.expect("missing frees= field"); + let live = live.expect("missing live= field"); + assert_eq!( + live, 0, + "RC imbalance after dropping a value-type-field ADT \ + (allocs={allocs} frees={frees} live={live}); the Box slab \ + must be freed exactly once and the inline Int field must \ + never be dec'd." + ); + assert_eq!( + allocs, frees, + "alloc/free mismatch (allocs={allocs} frees={frees} live={live})" + ); +} diff --git a/crates/ail/tests/e2e.rs b/crates/ail/tests/e2e.rs index 4420917..11e8ae5 100644 --- a/crates/ail/tests/e2e.rs +++ b/crates/ail/tests/e2e.rs @@ -82,6 +82,80 @@ fn sum_1_to_10_is_55() { assert_eq!(stdout.trim(), "55"); } +/// Property: a static `Str` literal passed by value into an `(own Str)` +/// parameter that the callee drops must be RC-sound — dropped exactly +/// once, no double-free, no leak. +/// +/// RED for the #55-cutover drop-soundness crash. The literal lowers to +/// `StrRep::Static` (a header-less rodata constant), but the cutover +/// activates the Own-param drop path, which emits `ailang_rc_dec` on +/// the unused param. `rc_dec` reads `payload - 8` as a refcount and +/// `free()`s a static address -> SIGSEGV. `lower_to_mir`'s `Term::App` +/// arm must flip such an argument to `StrRep::Heap` (str_clone into an +/// owned slab), the same promotion mir.4 applies to loop-carried Str +/// literals. Distinct root from legs A/B (the lit-pattern desugar); the +/// match in eq_demo merely happens to drop its owned Str scrutinee too. +/// +/// The assertion is robust against the stats-path masking leg A showed: +/// it runs the binary on the NON-stats path (catching the SIGSEGV via +/// the exit status) AND on the stats path asserting `live=0` / +/// `frees==allocs`. +#[test] +fn own_str_literal_arg_is_dropped_exactly_once() { + let manifest_dir = env!("CARGO_MANIFEST_DIR"); + let workspace = Path::new(manifest_dir).parent().unwrap().parent().unwrap(); + let src = workspace.join("examples").join("own_str_arg_drop.ail"); + let tmp = std::env::temp_dir().join(format!( + "ailang_e2e_own_str_arg_drop_{}", + std::process::id() + )); + std::fs::create_dir_all(&tmp).unwrap(); + let out = tmp.join("bin"); + + let status = Command::new(ail_bin()) + .args(["build", src.to_str().unwrap(), "-o"]) + .arg(&out) + .status() + .expect("ail build failed to run"); + assert!(status.success(), "ail build failed for own_str_arg_drop.ail"); + + // NON-stats path: this is the crashing path (SIGSEGV / double-free). + let plain = Command::new(&out).output().expect("execute binary"); + assert!( + plain.status.success(), + "binary {} exited non-zero (expected success; got {:?}) — \ + the owned Str literal was dropped on a header-less static", + out.display(), + plain.status.code() + ); + assert_eq!( + String::from_utf8(plain.stdout).expect("stdout utf8").trim(), + "0" + ); + + // Stats path: RC balance must close — no leaked or double-freed Str. + let stats = Command::new(&out) + .env("AILANG_RC_STATS", "1") + .output() + .expect("execute binary with RC stats"); + assert!( + stats.status.success(), + "binary {} (RC stats) exited non-zero (expected success; got {:?})", + out.display(), + stats.status.code() + ); + let stderr = String::from_utf8(stats.stderr).expect("stderr utf8"); + let line = stderr + .lines() + .find(|l| l.starts_with("ailang_rc_stats:")) + .unwrap_or_else(|| panic!("no ailang_rc_stats line in stderr: {stderr:?}")); + // Format: `ailang_rc_stats: allocs=N frees=M live=K` + assert!( + line.contains("live=0"), + "RC balance not closed: {line:?} (live != 0 means leak or double-free)" + ); +} + #[test] fn loop_recur_sum_to_runs_to_value() { let stdout = build_and_run("loop_sum_to_run.ail"); @@ -1294,7 +1368,7 @@ fn intrinsic_in_user_module_is_rejected() { let src = tmp.join("intr_user.ail"); std::fs::write( &src, - "(module intr_user (fn f (type (fn-type (params (con Int)) (ret (con Int)))) (params n) (intrinsic)))", + "(module intr_user (fn f (type (fn-type (params (own (con Int))) (ret (own (con Int))))) (params n) (intrinsic)))", ) .unwrap(); @@ -1344,6 +1418,88 @@ fn lit_pat_demo() { assert_eq!(lines, vec!["100", "200", "999", "-1", "0", "7"]); } +/// RED (refs #55, Implicit-cutover double-drop). Property protected: +/// a ctor arm with a literal sub-pattern in a non-tail field — +/// `(Cons (pat-lit K) _)` — drops the matched value's owned children +/// EXACTLY ONCE, on both the head==K and head!=K paths. +/// +/// The 16c lit-sub-pattern desugar lowers the arm into an outer +/// Cons-match (binding the fields) wrapping `if (== head K) body +/// else fall_k`; `fall_k` re-matches the same owned scrutinee, re- +/// binding its children. Post-#55 the param mode is `Own`, so the +/// match_lower.rs arm-close Iter-A drop (gated on `scrutinee_is_owned`) +/// fires on the tail child in BOTH the re-match arm AND the enclosing +/// lit-arm's join — `arg_xs+16` is dropped twice → double-free → +/// SIGSEGV on the head!=K path. +/// +/// Two assertions, so the symptom is pinned independently of the +/// stats-drop masking divergence: +/// 1. `build_and_run` (no AILANG_RC_STATS): asserts the binary exits +/// 0 AND prints `0` then `7` — surfaces the segfault directly. +/// 2. rc-stats: `live == 0` and `frees == allocs` — pins single-drop +/// (a double-free would over-count frees / corrupt the heap) +/// rather than mere exit-0. +#[test] +fn lit_pat_ctor_tail_drop_single_drop() { + let stdout = build_and_run("lit_pat_ctor_tail_drop.ail"); + let lines: Vec<&str> = stdout.lines().collect(); + assert_eq!(lines, vec!["0", "7"]); + + let (stats_stdout, allocs, frees, live) = + build_and_run_with_rc_stats("lit_pat_ctor_tail_drop.ail"); + assert_eq!( + stats_stdout.lines().collect::>(), + vec!["0", "7"], + "stdout under rc-stats must match the non-stats run" + ); + assert_eq!(live, 0, "owned IntList children must all be freed (no leak)"); + assert_eq!( + frees, allocs, + "every alloc freed exactly once — a double-free over-counts frees" + ); +} + +/// RED (refs #55, Implicit-cutover husk leak — leg B, Nil sub-case). +/// Property protected: an owned scrutinee threaded through the +/// lit-sub-pattern chain desugar has its OUTER cell freed exactly once +/// on the Nil return path — the path that matches the Nil arm and +/// touches neither inner Cons re-match. +/// +/// The 16c desugar takes `first_or_default` off the `is_flat` +/// fast-path (it has a lit sub-pattern arm), let-binds the owned +/// scrutinee into a fresh `$mp` var, and chains single-level matches. +/// Post-#55 the `Let $mp = xs` consumes `xs` (consume_count := 1), +/// which trips the codegen fn-return gate (lib.rs ~1522, `if +/// consume_count != 0 { continue }`) and suppresses the husk free a +/// single-match owned fn would emit as `partial_drop_(arg_xs, _)`. +/// The husk obligation also moves to the internal `$mp` binder the +/// gate never inspects. The Nil cell leaks -> live=1. +/// +/// Distinct from `lit_pat_ctor_tail_drop_single_drop`, which only +/// drives the Cons path: a fix that frees the husk on the Cons path +/// but forgets the Nil path passes that test while still leaking here. +#[test] +fn lit_pat_nil_scrutinee_single_drop() { + let stdout = build_and_run("lit_pat_nil_scrutinee_drop.ail"); + assert_eq!(stdout.lines().collect::>(), vec!["-1"]); + + let (stats_stdout, allocs, frees, live) = + build_and_run_with_rc_stats("lit_pat_nil_scrutinee_drop.ail"); + assert_eq!( + stats_stdout.lines().collect::>(), + vec!["-1"], + "stdout under rc-stats must match the non-stats run" + ); + assert_eq!( + live, 0, + "the Nil scrutinee's outer cell must be freed (no husk leak)" + ); + assert_eq!( + frees, allocs, + "every alloc freed exactly once — a double-free over-counts frees" + ); +} + /// `__unreachable__` as an explicit user-callable /// primitive. Property protected: a polymorphic `forall a. a` value /// reference (a) typechecks against any expected type at the use @@ -2395,43 +2551,41 @@ fn alloc_rc_let_binder_for_owned_returning_app_drops_at_scope_close() { ); } -/// a let-binder whose value is the -/// result of an `Implicit`-ret-mode (default, unannotated) call must -/// NOT be trackable. Implicit is the back-compat lane that 18c.3 -/// documented as "params don't get any dec — leak rather than mis- -/// dec"; the symmetric carve-out for ret-modes is "Implicit-returning -/// calls do not flow ownership to the caller, the caller must NOT -/// dec". +/// a let-binder whose value is the result of an `(own T)`-ret-mode +/// call IS trackable for scope-close drop. ParamMode is binary +/// `{Own, Borrow}`: an unannotated return defaults to `Own`, the +/// callee's `(own T)` return signs the static contract that the +/// caller now owns the returned cell, and the let-scope close emits +/// the caller dec so the cell frees exactly once. /// -/// If `is_rc_heap_allocated` were mistakenly to fire on this shape, -/// the let-scope close would emit an unjustified dec — refcount -/// underflow at runtime (the cell's RC reaches zero before any other -/// owner had a chance to dec). The fixture under `--alloc=rc` must: +/// The fixture under `--alloc=rc` must: /// /// (1) Build cleanly (no consume-while-borrowed false positive, /// parse error, or unimplemented). /// (2) Exit cleanly with stdout `7` — the unboxed Int from the cell. -/// (3) Report `live = 1` — the one MkT cell leaks. The leak is the -/// intentional Implicit-mode behaviour; if codegen ever started -/// dec'ing this shape it would crash with refcount underflow -/// before the leak number could even be observed. +/// (3) Report `live = 0` — the one MkT cell frees. The own-return +/// contract flows ownership to the caller, which dec's the +/// binder at scope close. This is the typed-MIR leak fix spec +/// 0062 delivers (acceptance criterion 5): the legacy `Implicit` +/// back-compat lane that left this shape leaked (`live = 1`) is +/// gone, so the owned-ret binder is dropped exactly once. /// -/// This test is the negative-side companion to +/// This test is the positive companion to /// `alloc_rc_let_binder_for_owned_returning_app_drops_at_scope_close`: -/// the asymmetry between (own)-ret-mode (live=0) and Implicit-ret-mode -/// (live=1) is the documented contract. +/// both `(own)`-ret-mode shapes free at scope close (`live = 0`). #[test] -fn alloc_rc_let_binder_for_implicit_returning_app_does_not_drop() { +fn alloc_rc_let_binder_for_owned_returning_app_drops_exactly_once() { let (stdout, allocs, frees, live) = build_and_run_with_rc_stats("rc_let_implicit_returning_app.ail"); assert_eq!(stdout.trim(), "7", "alloc(7) -> unbox should print 7"); assert_eq!( - live, 1, - "Implicit-ret-mode App must leak (not crash with refcount \ - underflow); allocs={allocs} frees={frees} live={live}. \ - A live count of 0 means is_rc_heap_allocated mistakenly \ - marked Implicit-ret-mode App as trackable; a non-zero exit \ - means the unjustified dec triggered the underflow guard." + live, 0, + "owned-ret-mode App binder must drop exactly once at scope \ + close (post-cutover: no Implicit leak lane); allocs={allocs} \ + frees={frees} live={live}. A live count of 1 would mean \ + is_rc_heap_allocated failed to mark the owned-ret App as \ + trackable; a non-zero exit means an unjustified dec triggered \ + the underflow guard." ); } diff --git a/crates/ail/tests/eq_ord_e2e.rs b/crates/ail/tests/eq_ord_e2e.rs index 0bedba2..4cb24dd 100644 --- a/crates/ail/tests/eq_ord_e2e.rs +++ b/crates/ail/tests/eq_ord_e2e.rs @@ -136,7 +136,7 @@ fn eq_ord_user_adt_eq_intbox_hash_stable() { // sweep — the call site symbol name shifts, body hash follows. let h = def_hash(&Def::Fn(eq_intbox.clone())); assert_eq!( - h, "ceb466964df0e8d1", + h, "e639d41fc49b1524", "eq__IntBox body hash drifted — see mono_hash_stability.rs for the resolution pattern; captured: {h}" ); } diff --git a/crates/ail/tests/migrate_canonical_types.rs b/crates/ail/tests/migrate_canonical_types.rs index 9348076..014e1f1 100644 --- a/crates/ail/tests/migrate_canonical_types.rs +++ b/crates/ail/tests/migrate_canonical_types.rs @@ -50,7 +50,7 @@ fn migrate_canonical_types_rewrites_bare_xmod_term_ctor() { "imports": [{ "module": "other" }], "defs": [{ "kind": "fn", "name": "f", - "type": { "k": "fn", "params": [], "ret": { "k": "con", "name": "Unit" }, "effects": [] }, + "type": { "k": "fn", "params": [], "param_modes": [], "ret": { "k": "con", "name": "Unit" }, "ret_mode": "own", "effects": [] }, "params": [], "body": { "t": "ctor", "type": "Foo", "ctor": "MkFoo", "args": [] } }], @@ -111,7 +111,7 @@ fn migrate_canonical_types_rewrites_bare_xmod_type_con() { "imports": [{ "module": "other" }], "defs": [{ "kind": "fn", "name": "f", - "type": { "k": "fn", "params": [], "ret": { "k": "con", "name": "Foo" }, "effects": [] }, + "type": { "k": "fn", "params": [], "param_modes": [], "ret": { "k": "con", "name": "Foo" }, "ret_mode": "own", "effects": [] }, "params": [], "body": { "t": "lit", "lit": { "kind": "unit" } } }], diff --git a/crates/ail/tests/migrate_modes.rs b/crates/ail/tests/migrate_modes.rs deleted file mode 100644 index 65ef6a3..0000000 --- a/crates/ail/tests/migrate_modes.rs +++ /dev/null @@ -1,27 +0,0 @@ -//! Throwaway migration-tool test (spec 0062). Asserts the -//! parse→Implicit↦Own→print pass materialises bare slots as `(own …)` -//! and leaves explicit `(borrow …)` untouched. Deleted in the cutover. -use ailang_surface::{parse, print}; -use ailang_core::ast::ParamMode; - -/// Re-implements the tool's core transform for the unit test so the -/// assertion does not depend on the CLI I/O wrapper. -fn migrate_text(src: &str) -> String { - let mut m = parse(src).expect("parse"); - ailang_core::ast::for_each_fn_type_mut(&mut m, &mut |params_len, pm, rm| { - pm.resize(params_len, ParamMode::Own); - for x in pm.iter_mut() { - if matches!(x, ParamMode::Implicit) { *x = ParamMode::Own; } - } - if matches!(rm, ParamMode::Implicit) { *rm = ParamMode::Own; } - }); - print(&m) -} - -#[test] -fn bare_slot_becomes_own_borrow_preserved() { - let src = "(module m\n (fn f\n (doc \"d\")\n (type (fn-type (params (con Int) (borrow (con Int))) (ret (con Int))))\n (params x y)\n (body x)))\n"; - let out = migrate_text(src); - assert!(out.contains("(params (own (con Int)) (borrow (con Int)))"), "got: {out}"); - assert!(out.contains("(ret (own (con Int)))"), "got: {out}"); -} diff --git a/crates/ail/tests/mode_signature_rejects.rs b/crates/ail/tests/mode_signature_rejects.rs index abf4d2d..4785a7a 100644 --- a/crates/ail/tests/mode_signature_rejects.rs +++ b/crates/ail/tests/mode_signature_rejects.rs @@ -25,3 +25,23 @@ fn check_rejects_with(fixture: &str, code: &str) { fn borrow_return_is_rejected() { check_rejects_with("borrow_return_reject.ail", "borrow-return-not-permitted"); } + +/// `(borrow value-type)` is rejected with code `borrow-over-value`. +#[test] +fn borrow_over_value_is_rejected() { + check_rejects_with("borrow_value_reject.ail", "borrow-over-value"); +} + +/// The own-return-provenance / regime-A shapes stay rejected by +/// `consume-while-borrowed` after the schema deletion (spec 0062 +/// already-green pins — these are NOT new checks). +#[test] +fn own_return_provenance_still_rejected() { + for f in [ + "own_return_provenance_reject.ail", + "own_return_provenance_let.ail", + "own_return_provenance_ctor.ail", + ] { + check_rejects_with(f, "consume-while-borrowed"); + } +} diff --git a/crates/ail/tests/mono_hash_stability.rs b/crates/ail/tests/mono_hash_stability.rs index f213964..c0fea9a 100644 --- a/crates/ail/tests/mono_hash_stability.rs +++ b/crates/ail/tests/mono_hash_stability.rs @@ -60,12 +60,12 @@ fn primitive_eq_ord_mono_symbol_hashes_stay_bit_identical() { // by name; the body is signature-only. The four show__* pins // below do NOT move (Show instance bodies are real, unchanged). let pins: &[(&str, &str)] = &[ - ("eq__Int", "cc7b99b63d1e44ae"), - ("eq__Bool", "fd0412f127986512"), - ("eq__Str", "fa269285754a52da"), - ("compare__Int", "0a02bd9effc9746c"), - ("compare__Bool", "d0dc108dacf4e543"), - ("compare__Str", "d1419595dc52a456"), + ("eq__Int", "85be5c2de568b58d"), + ("eq__Bool", "851b24fb1b513bf9"), + ("eq__Str", "ead456ddde7c0cbd"), + ("compare__Int", "f99b792409ee8417"), + ("compare__Bool", "bdf90cb462351564"), + ("compare__Str", "752b08fdd12c6a67"), ]; for (sym, pin) in pins { diff --git a/crates/ail/tests/print_no_leak_pin.rs b/crates/ail/tests/print_no_leak_pin.rs index 62fa822..732aa41 100644 --- a/crates/ail/tests/print_no_leak_pin.rs +++ b/crates/ail/tests/print_no_leak_pin.rs @@ -12,29 +12,14 @@ //! `ailang_rc_dec(s)`. The runtime stats line at exit must report //! `live == 0` (equivalently `allocs == frees`). //! -//! As of commit 301cbc3 this test fails: stderr reports -//! `ailang_rc_stats: allocs=1 frees=0 live=1`. The IR for the post- -//! mono `ail_prelude_print__Int` body contains a `call ptr -//! @ail_prelude_show__Int(...)` followed by `getelementptr +8` + -//! `@fputs` + `ret i8 0` — there is no `ailang_rc_dec` on the -//! show-result before return. Root cause sits at codegen's -//! `is_rc_heap_allocated` App-arm: it reads the callee's -//! `Type::Fn.ret_mode` via `synth_callee_ret_mode` and requires -//! `Own`. The mono'ed `show__Int` inherits its `Type::Fn` from the -//! `Show` class method declaration in `examples/prelude.ail.json`, -//! whose `methods[0].type` omits the `ret_mode` key — `serde` defaults -//! the missing field to `ParamMode::Implicit`. So -//! `synth_callee_ret_mode(show__Int) == Implicit`, the let-binder is -//! not flagged trackable, and `drop_symbol_for_binder` is never -//! called for it. -//! -//! Once the underlying issue is fixed (the canonical move is to -//! recognise that any class-method whose declared `ret` is `Str` / -//! any pointer-typed user type should round-trip as Own through -//! mono, OR to install the Show class declaration with explicit -//! `ret_mode: "own"`, OR to widen `is_rc_heap_allocated`'s App-arm -//! to also consult the callee's ret type-via-Str-carve-out), this -//! test must flip GREEN without weakening the assertion. +//! Codegen's `is_rc_heap_allocated` App-arm reads the callee's +//! `Type::Fn.ret_mode` via `synth_callee_ret_mode` and tracks the +//! let-binder iff it is `Own`. The mono'ed `show__Int` inherits its +//! `Type::Fn` from the `Show` class method declaration in +//! `examples/prelude.ail.json`, whose `show` carries `ret_mode: own` +//! (every fn-type slot is moded — spec 0062). So `show__Int` returns +//! `Own`, the let-binder for the show-result is trackable, and +//! `drop_symbol_for_binder` emits the scope-close dec — `live == 0`. use std::path::Path; use std::process::Command; @@ -107,10 +92,9 @@ fn alloc_rc_print_int_does_not_leak_show_result_str() { live, 0, "`(app print 42)` under --alloc=rc leaks {live} heap-Str cell(s) \ (allocs={allocs} frees={frees}); print's let-binder for the \ - show-result must drop at let-scope-close. Show class method \ - `show` in prelude.ail.json omits `ret_mode`, so mono-synthesised \ - `show__Int` inherits ret_mode=Implicit and `is_rc_heap_allocated` \ - declines to track the let-binder." + show-result must drop at let-scope-close. The Show class method \ + `show` carries `ret_mode: own`, so mono-synthesised `show__Int` \ + is `Own`-returning and `is_rc_heap_allocated` tracks the binder." ); assert_eq!( allocs, frees, diff --git a/crates/ail/tests/roundtrip_cli.rs b/crates/ail/tests/roundtrip_cli.rs index 85b67c6..4a7b597 100644 --- a/crates/ail/tests/roundtrip_cli.rs +++ b/crates/ail/tests/roundtrip_cli.rs @@ -36,6 +36,23 @@ fn examples_dir() -> PathBuf { workspace_root().join("examples") } +/// Fixtures that intentionally do NOT parse — the `#55` cutover +/// reject corpus. These are negative fixtures whose whole point is +/// that the parser rejects them, so `parse∘render∘parse` is not a +/// meaningful property for them and the harness must skip them. +/// +/// Scope is deliberately minimal: only fixtures that fail at the +/// PARSE stage belong here. The other `*reject*.ail` cutover +/// fixtures (`borrow_return_reject`, `borrow_value_reject`, +/// `own_return_provenance_reject`, the `embed_export_*_rejected` +/// set, …) parse cleanly and are rejected only later at CHECK time, +/// so they round-trip fine and are NOT excluded. +/// +/// - `bare_slot_reject.ail`: a bare fn-type slot with no mode. The +/// binary-ParamMode parser rejects it ("fn-type slot requires a +/// mode: write (own T) or (borrow T)"). +const NON_PARSEABLE_FIXTURES: &[&str] = &["bare_slot_reject.ail"]; + fn list_ail_fixtures() -> Vec { let dir = examples_dir(); let mut paths: Vec = std::fs::read_dir(&dir) @@ -45,7 +62,9 @@ fn list_ail_fixtures() -> Vec { .filter(|p| { p.file_name() .and_then(|n| n.to_str()) - .map(|n| n.ends_with(".ail")) + .map(|n| { + n.ends_with(".ail") && !NON_PARSEABLE_FIXTURES.contains(&n) + }) .unwrap_or(false) }) .collect(); diff --git a/crates/ail/tests/snapshots/list.ll b/crates/ail/tests/snapshots/list.ll index be03927..ed403a5 100644 --- a/crates/ail/tests/snapshots/list.ll +++ b/crates/ail/tests/snapshots/list.ll @@ -53,6 +53,7 @@ mdefault.2: unreachable mjoin.2: %v9 = phi i64 [ 0, %marm.2.0 ], [ %v8, %marm.2.1 ] + call void @partial_drop_list_IntList(ptr %arg_xs, i64 2) ret i64 %v9 } diff --git a/crates/ail/tests/typeclass_22b3.rs b/crates/ail/tests/typeclass_22b3.rs index a929a60..26539ac 100644 --- a/crates/ail/tests/typeclass_22b3.rs +++ b/crates/ail/tests/typeclass_22b3.rs @@ -208,7 +208,7 @@ fn synthesise_mono_fn_uses_instance_body_lam() { ret: Box::new(Type::int()), effects: vec![], param_modes: vec![], - ret_mode: ailang_core::ast::ParamMode::Implicit, + ret_mode: ailang_core::ast::ParamMode::Own, }, default: None, }], @@ -277,7 +277,7 @@ fn synthesise_mono_fn_falls_back_to_class_default() { ret: Box::new(Type::str_()), effects: vec![], param_modes: vec![], - ret_mode: ailang_core::ast::ParamMode::Implicit, + ret_mode: ailang_core::ast::ParamMode::Own, }, default: Some(Term::Lam { params: vec!["_x".into()], diff --git a/crates/ail/tests/user_adt_new_mono_pin.rs b/crates/ail/tests/user_adt_new_mono_pin.rs index 1b39aa0..c89beaa 100644 --- a/crates/ail/tests/user_adt_new_mono_pin.rs +++ b/crates/ail/tests/user_adt_new_mono_pin.rs @@ -54,12 +54,12 @@ const PROG: &str = r#"(module user_adt_new_mono (fn new (doc "Build a Counter initialised to the given value.") - (type (fn-type (params (con Int)) (ret (con Counter)))) + (type (fn-type (params (own (con Int))) (ret (own (con Counter))))) (params n) (body (term-ctor Counter MkCounter n))) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (let c (new Counter 42) diff --git a/crates/ailang-check/src/builtins.rs b/crates/ailang-check/src/builtins.rs index 3ee45fb..ec8b0b6 100644 --- a/crates/ailang-check/src/builtins.rs +++ b/crates/ailang-check/src/builtins.rs @@ -65,7 +65,7 @@ pub fn install(env: &mut crate::Env) { ret: Box::new(Type::Var { name: "a".into() }), effects: vec![], param_modes: vec![], - ret_mode: ailang_core::ast::ParamMode::Implicit, + ret_mode: ailang_core::ast::ParamMode::Own, }), }; // 2026-05-21 operator-routing-eq-ord: the `poly_a_a_to_bool` @@ -78,7 +78,7 @@ pub fn install(env: &mut crate::Env) { ret: Box::new(Type::int()), effects: vec![], param_modes: vec![], - ret_mode: ailang_core::ast::ParamMode::Implicit, + ret_mode: ailang_core::ast::ParamMode::Own, }; for op in ["+", "-", "*", "/"] { env.globals.insert(op.into(), poly_a_a_to_a()); @@ -98,7 +98,7 @@ pub fn install(env: &mut crate::Env) { ret: Box::new(Type::bool_()), effects: vec![], param_modes: vec![], - ret_mode: ailang_core::ast::ParamMode::Implicit, + ret_mode: ailang_core::ast::ParamMode::Own, }, ); @@ -134,7 +134,7 @@ pub fn install(env: &mut crate::Env) { ret: Box::new(Type::Var { name: "a".into() }), effects: vec![], param_modes: vec![], - ret_mode: ailang_core::ast::ParamMode::Implicit, + ret_mode: ailang_core::ast::ParamMode::Own, }), }, ); @@ -145,7 +145,7 @@ pub fn install(env: &mut crate::Env) { ret: Box::new(Type::float()), effects: vec![], param_modes: vec![], - ret_mode: ailang_core::ast::ParamMode::Implicit, + ret_mode: ailang_core::ast::ParamMode::Own, }, ); env.globals.insert( @@ -155,7 +155,7 @@ pub fn install(env: &mut crate::Env) { ret: Box::new(Type::int()), effects: vec![], param_modes: vec![], - ret_mode: ailang_core::ast::ParamMode::Implicit, + ret_mode: ailang_core::ast::ParamMode::Own, }, ); env.globals.insert( @@ -218,7 +218,7 @@ pub fn install(env: &mut crate::Env) { ret: Box::new(Type::bool_()), effects: vec![], param_modes: vec![], - ret_mode: ailang_core::ast::ParamMode::Implicit, + ret_mode: ailang_core::ast::ParamMode::Own, }, ); diff --git a/crates/ailang-check/src/lib.rs b/crates/ailang-check/src/lib.rs index 2179649..643b1a0 100644 --- a/crates/ailang-check/src/lib.rs +++ b/crates/ailang-check/src/lib.rs @@ -782,6 +782,13 @@ pub enum CheckError { #[error("borrow-return not permitted for `{0}`: a (borrow …) return is refcount-invisible and can outlive its source; this language version forbids it (the escape/liveness axis that would make it sound is not yet built)")] BorrowReturnNotPermitted(String), + /// A fn-type slot is `(borrow V)` where `V` is an unboxed value + /// type (`Int`/`Bool`/`Float`/`Unit`). Borrow is meaningless over + /// a value type — it has no refcount and is copied by value + /// (model §3.2). Code: `borrow-over-value`. + #[error("borrow over value type in `{def}`: `{ty}` is an unboxed value type and cannot be borrowed; use `(own {ty})`")] + BorrowOverValueType { def: String, ty: String }, + /// an internal invariant in the typechecker / mono pass /// was violated — surfaced as an error so callers can propagate /// rather than abort, but in well-formed inputs (typecheck has @@ -844,6 +851,7 @@ impl CheckError { CheckError::ParamNotInRestrictedSet { .. } => "param-not-in-restricted-set", CheckError::IntrinsicOutsideKernelTier { .. } => "intrinsic-outside-kernel-tier", CheckError::BorrowReturnNotPermitted(_) => "borrow-return-not-permitted", + CheckError::BorrowOverValueType { .. } => "borrow-over-value", CheckError::Internal(_) => "internal", } } @@ -2109,7 +2117,7 @@ fn check_instance( .collect(); let subst_ret_ty = substitute_rigids(ret_ty, &subst_map); let subst_body = substitute_rigids_in_term(body, &subst_map); - let fn_ty = Type::fn_implicit( + let fn_ty = Type::fn_owned( subst_param_tys, subst_ret_ty, effects.clone(), @@ -2279,10 +2287,14 @@ fn check_fn( other => (vec![], other.clone()), }; - let (param_tys, ret_ty, ret_mode, declared_effs) = match &inner_ty { - Type::Fn { params, ret, ret_mode, effects, .. } => { - (params.clone(), (**ret).clone(), *ret_mode, effects.clone()) - } + let (param_tys, param_modes, ret_ty, ret_mode, declared_effs) = match &inner_ty { + Type::Fn { params, param_modes, ret, ret_mode, effects } => ( + params.clone(), + param_modes.clone(), + (**ret).clone(), + *ret_mode, + effects.clone(), + ), other => { return Err(CheckError::FnTypeRequired( f.name.clone(), @@ -2321,6 +2333,23 @@ fn check_fn( return Err(CheckError::BorrowReturnNotPermitted(f.name.clone())); } + // spec 0062: borrow over an unboxed value type is meaningless. + // Fired on the signature, before body dataflow. The mono-coercion + // (subst.rs) guarantees no generated value-type-borrow instance + // reaches here. + for (ty, mode) in param_tys.iter().zip(param_modes.iter()) { + if matches!(mode, ParamMode::Borrow) { + if let Type::Con { name, args } = ty { + if args.is_empty() && ailang_core::primitives::is_value_type(name) { + return Err(CheckError::BorrowOverValueType { + def: f.name.clone(), + ty: name.clone(), + }); + } + } + } + } + // An `(intrinsic)` body is signature-only: the signature is already // validated above; codegen supplies the implementation via the // intercept registry. The body is `Term::Intrinsic` directly (a @@ -4168,7 +4197,7 @@ pub(crate) fn synth( ret: Box::new((**ret_ty).clone()), effects: lam_effects.clone(), param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }) } Term::LetRec { name, ty, params, body, in_term } => { @@ -5485,7 +5514,7 @@ mod tests { ret: Box::new(Type::int()), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, vec!["a", "b"], Term::App { @@ -5515,7 +5544,7 @@ mod tests { ret: Box::new(Type::int()), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, vec![], Term::Lit { @@ -5542,7 +5571,7 @@ mod tests { ret: Box::new(Type::unit()), effects: vec![], // !IO missing, param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, vec![], Term::Do { @@ -5575,7 +5604,7 @@ mod tests { ret: Box::new(Type::int()), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, vec![], Term::Let { @@ -5620,7 +5649,7 @@ mod tests { ret: Box::new(Type::int()), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, vec!["m"], Term::Match { @@ -5673,7 +5702,7 @@ mod tests { ret: Box::new(Type::int()), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, vec!["m"], Term::Match { @@ -5716,7 +5745,7 @@ mod tests { ret: Box::new(Type::int()), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, vec![], Term::If { @@ -5754,7 +5783,7 @@ mod tests { ret: Box::new(Type::Var { name: "a".into() }), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }), }, vec!["x"], @@ -5780,7 +5809,7 @@ mod tests { ret: Box::new(Type::Var { name: "a".into() }), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }), }, vec!["x"], @@ -5794,7 +5823,7 @@ mod tests { ret: Box::new(Type::int()), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, vec![], Term::App { @@ -5811,7 +5840,7 @@ mod tests { ret: Box::new(Type::bool_()), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, vec![], Term::App { @@ -5845,7 +5874,7 @@ mod tests { ret: Box::new(Type::Var { name: "a".into() }), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }), }, vec!["x"], @@ -5859,7 +5888,7 @@ mod tests { ret: Box::new(Type::bool_()), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, vec![], Term::App { @@ -5897,14 +5926,14 @@ mod tests { ret: Box::new(Type::Var { name: "b".into() }), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, Type::Var { name: "a".into() }, ], ret: Box::new(Type::Var { name: "b".into() }), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }), }, vec!["f", "x"], @@ -5922,7 +5951,7 @@ mod tests { ret: Box::new(Type::int()), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, vec!["n"], Term::App { @@ -5941,7 +5970,7 @@ mod tests { ret: Box::new(Type::int()), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, vec![], Term::App { @@ -5989,7 +6018,7 @@ mod tests { }), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, vec![], Term::Ctor { @@ -6036,7 +6065,7 @@ mod tests { ret: Box::new(Type::Var { name: "a".into() }), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }), }, params: vec!["b".into()], @@ -6063,7 +6092,7 @@ mod tests { ret: Box::new(Type::int()), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, vec![], Term::App { @@ -6083,7 +6112,7 @@ mod tests { ret: Box::new(Type::bool_()), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, vec![], Term::App { @@ -6131,7 +6160,7 @@ mod tests { ret: Box::new(Type::int()), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, vec!["b"], Term::Lit { lit: Literal::Int { value: 0 } }, @@ -6165,7 +6194,7 @@ mod tests { ret: Box::new(Type::int()), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, vec![], Term::Seq { @@ -6219,7 +6248,7 @@ mod tests { ret: Box::new(Type::Con { name: "L".into(), args: vec![] }), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, vec!["xs"], // Body: C(0, tail-app loop xs) — the recursion is @@ -6295,7 +6324,7 @@ mod tests { ret: Box::new(Type::int()), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, vec!["b"], Term::Lit { lit: Literal::Int { value: 0 } }, @@ -6325,7 +6354,7 @@ mod tests { }), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, vec![], Term::Ctor { @@ -6362,7 +6391,7 @@ mod tests { ret: Box::new(Type::int()), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, vec!["b"], Term::Match { @@ -6449,7 +6478,7 @@ mod tests { ret: Box::new(Type::int()), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, vec![], Term::Match { @@ -6541,7 +6570,7 @@ mod tests { }), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }), }, default: None, @@ -6590,7 +6619,7 @@ mod tests { ret: Box::new(Type::int()), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, vec![], Term::Match { @@ -6681,7 +6710,7 @@ mod tests { ret: Box::new(Type::int()), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }), }; let out = qualify_local_types(&input, "p", &local_types); @@ -6735,7 +6764,7 @@ mod tests { ret: Box::new(Type::unit()), effects: vec!["IO".into()], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, vec!["xs"], Term::Match { @@ -6807,7 +6836,7 @@ mod tests { ret: Box::new(Type::int()), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, params: vec!["x".into()], body: Box::new(Term::Lit { lit: Literal::Bool { value: true } }), @@ -6831,7 +6860,7 @@ mod tests { ret: Box::new(Type::int()), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, params: vec!["x".into()], body: Box::new(Term::Seq { @@ -6867,7 +6896,7 @@ mod tests { ret: Box::new(Type::int()), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, vec![], body, @@ -6903,7 +6932,7 @@ mod tests { ret: Box::new(Type::int()), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, vec![], Term::Let { @@ -6916,7 +6945,7 @@ mod tests { ret: Box::new(Type::int()), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, params: vec!["x".into()], body: Box::new(Term::App { @@ -6960,7 +6989,7 @@ mod tests { ret: Box::new(Type::int()), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, "lifted ty should have capture appended; got {:?}", synth.ty @@ -7031,7 +7060,7 @@ mod tests { ret: Box::new(Type::int()), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, params: vec!["z".into()], body: Box::new(Term::App { @@ -7078,7 +7107,7 @@ mod tests { ret: Box::new(Type::int()), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, vec!["p"], outer_body, @@ -7110,7 +7139,7 @@ mod tests { ret: Box::new(Type::int()), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, "lifted ty should have the match-arm-capture type Int appended; got {:?}", synth.ty @@ -7166,7 +7195,7 @@ mod tests { ret: Box::new(Type::int()), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, vec!["x"], body, @@ -7228,7 +7257,7 @@ mod tests { ret: Box::new(Type::int()), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, vec!["x"], Term::Match { @@ -7296,7 +7325,7 @@ mod tests { ret: Box::new(Type::int()), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, vec!["b"], Term::Match { @@ -7346,7 +7375,7 @@ mod tests { ret: Box::new(Type::int()), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, vec!["x"], Term::Match { @@ -7403,7 +7432,7 @@ mod tests { }), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, vec![], Term::Ctor { @@ -7469,7 +7498,7 @@ mod tests { }), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, vec![], Term::Ctor { @@ -7534,7 +7563,7 @@ mod tests { }), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, vec![], Term::Ctor { @@ -7578,7 +7607,7 @@ mod tests { ret: Box::new(Type::int()), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, vec!["n"], Term::Var { name: "n".into() }, @@ -7596,7 +7625,7 @@ mod tests { ret: Box::new(Type::int()), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, vec![], Term::App { @@ -7846,7 +7875,7 @@ mod tests { param_modes: vec![ParamMode::Borrow], ret: Box::new(Type::Con { name: "Str".to_string(), args: vec![] }), effects: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, default: None, }], @@ -7913,7 +7942,7 @@ mod tests { param_modes: vec![ParamMode::Borrow], ret: Box::new(Type::str_()), effects: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, default: None, }], @@ -7925,7 +7954,7 @@ mod tests { param_modes: vec![ParamMode::Borrow], ret: Box::new(Type::str_()), effects: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }; let fnmod = Module { schema: SCHEMA.into(), @@ -8032,7 +8061,7 @@ mod tests { param_modes: vec![ParamMode::Borrow], ret: Box::new(Type::Con { name: "Str".to_string(), args: vec![] }), effects: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, default: None, }], @@ -8172,8 +8201,8 @@ mod tests { {"kind": "fn", "name": "from_maybe", "type": {"k": "forall", "vars": ["a"], "constraints": [], "body": {"k": "fn", "params": [{"k": "var", "name": "a"}, - {"k": "con", "name": "Maybe", "args": [{"k": "var", "name": "a"}]}], - "ret": {"k": "var", "name": "a"}, "effects": []}}, + {"k": "con", "name": "Maybe", "args": [{"k": "var", "name": "a"}]}], "param_modes": ["own", "borrow"], + "ret": {"k": "var", "name": "a"}, "ret_mode": "own", "effects": []}}, "params": ["default", "m"], "body": {"t": "var", "name": "default"}} ] @@ -8191,8 +8220,8 @@ mod tests { "imports": [{"module": "std_maybe"}], "defs": [ {"kind": "fn", "name": "f", - "type": {"k": "fn", "params": [{"k": "con", "name": "Int"}], - "ret": {"k": "con", "name": "Int"}, "effects": []}, + "type": {"k": "fn", "params": [{"k": "con", "name": "Int"}], "param_modes": ["own"], + "ret": {"k": "con", "name": "Int"}, "ret_mode": "own", "effects": []}, "params": ["x"], "body": {"t": "app", "fn": {"t": "var", "name": "Maybe.from_maybe"}, @@ -8240,8 +8269,8 @@ mod tests { "imports": [{"module": "std_maybe"}], "defs": [ {"kind": "fn", "name": "f", - "type": {"k": "fn", "params": [], - "ret": {"k": "con", "name": "Int"}, "effects": []}, + "type": {"k": "fn", "params": [], "param_modes": [], + "ret": {"k": "con", "name": "Int"}, "ret_mode": "own", "effects": []}, "params": [], "body": {"t": "var", "name": "Maybe.bogus"}} ] @@ -8273,8 +8302,8 @@ mod tests { "imports": [], "defs": [ {"kind": "fn", "name": "f", - "type": {"k": "fn", "params": [], - "ret": {"k": "con", "name": "Int"}, "effects": []}, + "type": {"k": "fn", "params": [], "param_modes": [], + "ret": {"k": "con", "name": "Int"}, "ret_mode": "own", "effects": []}, "params": [], "body": {"t": "var", "name": "SomethingRandom.x"}} ] @@ -8314,15 +8343,15 @@ mod tests { ]}, {"kind": "fn", "name": "new", "type": {"k": "fn", - "params": [{"k": "con", "name": "Int"}], - "ret": {"k": "con", "name": "Counter"}, + "params": [{"k": "con", "name": "Int"}], "param_modes": ["own"], + "ret": {"k": "con", "name": "Counter"}, "ret_mode": "own", "effects": []}, "params": ["n"], "body": {"t": "ctor", "type": "Counter", "ctor": "MkCounter", "args": [{"t": "var", "name": "n"}]}}, {"kind": "fn", "name": "main", - "type": {"k": "fn", "params": [], - "ret": {"k": "con", "name": "Counter"}, + "type": {"k": "fn", "params": [], "param_modes": [], + "ret": {"k": "con", "name": "Counter"}, "ret_mode": "own", "effects": []}, "params": [], "body": {"t": "new", "type": "Counter", @@ -8369,8 +8398,8 @@ mod tests { ]}, // No `new` def for T. {"kind": "fn", "name": "main", - "type": {"k": "fn", "params": [], - "ret": {"k": "con", "name": "T"}, + "type": {"k": "fn", "params": [], "param_modes": [], + "ret": {"k": "con", "name": "T"}, "ret_mode": "own", "effects": []}, "params": [], "body": {"t": "new", "type": "T", @@ -8423,17 +8452,17 @@ mod tests { {"kind": "fn", "name": "new", "type": {"k": "forall", "vars": ["a"], "constraints": [], "body": {"k": "fn", - "params": [{"k": "var", "name": "a"}], + "params": [{"k": "var", "name": "a"}], "param_modes": ["own"], "ret": {"k": "con", "name": "T", - "args": [{"k": "var", "name": "a"}]}, + "args": [{"k": "var", "name": "a"}]}, "ret_mode": "own", "effects": []}}, "params": ["x"], "body": {"t": "ctor", "type": "T", "ctor": "MkT", "args": [{"t": "var", "name": "x"}]}}, {"kind": "fn", "name": "main", - "type": {"k": "fn", "params": [], + "type": {"k": "fn", "params": [], "param_modes": [], "ret": {"k": "con", "name": "T", - "args": [{"k": "con", "name": "Int"}]}, + "args": [{"k": "con", "name": "Int"}]}, "ret_mode": "own", "effects": []}, "params": [], // Wrong: only one NewArg::Value supplied for a sig @@ -8478,9 +8507,9 @@ mod tests { // Reference `(con StubT (con Str))` in a fn return // type — well-formedness check walks the return type. {"kind": "fn", "name": "bad", - "type": {"k": "fn", "params": [], + "type": {"k": "fn", "params": [], "param_modes": [], "ret": {"k": "con", "name": "StubT", - "args": [{"k": "con", "name": "Str"}]}, + "args": [{"k": "con", "name": "Str"}]}, "ret_mode": "own", "effects": []}, "params": [], "body": {"t": "lit", "lit": {"kind": "unit"}}} @@ -8518,9 +8547,9 @@ mod tests { "ctors": [{"name": "Stub", "fields": [{"k": "var", "name": "a"}]}], "param-in": {"a": ["Int", "Float"]}}, {"kind": "fn", "name": "bad", - "type": {"k": "fn", "params": [], + "type": {"k": "fn", "params": [], "param_modes": [], "ret": {"k": "con", "name": "StubT", - "args": [{"k": "con", "name": "Str"}]}, + "args": [{"k": "con", "name": "Str"}]}, "ret_mode": "own", "effects": []}, "params": [], "body": {"t": "lit", "lit": {"kind": "unit"}}} @@ -8563,9 +8592,9 @@ mod tests { "ctors": [{"name": "Stub", "fields": [{"k": "var", "name": "a"}]}], "param-in": {"a": ["Int", "Float"]}}, {"kind": "fn", "name": "bad", - "type": {"k": "fn", "params": [], + "type": {"k": "fn", "params": [], "param_modes": [], "ret": {"k": "con", "name": "StubT", - "args": [{"k": "con", "name": "Str"}]}, + "args": [{"k": "con", "name": "Str"}]}, "ret_mode": "own", "effects": []}, "params": [], "body": {"t": "lit", "lit": {"kind": "unit"}}} @@ -8610,9 +8639,9 @@ mod tests { "ctors": [{"name": "Stub", "fields": [{"k": "var", "name": "a"}]}], "param-in": {"a": ["Int", "Float"]}}, {"kind": "fn", "name": "good", - "type": {"k": "fn", "params": [], + "type": {"k": "fn", "params": [], "param_modes": [], "ret": {"k": "con", "name": "StubT", - "args": [{"k": "con", "name": "Int"}]}, + "args": [{"k": "con", "name": "Int"}]}, "ret_mode": "own", "effects": []}, "params": [], "body": {"t": "lit", "lit": {"kind": "unit"}}} @@ -8667,8 +8696,8 @@ mod tests { // shape is irrelevant. {"kind": "fn", "name": "value", "type": {"k": "fn", - "params": [{"k": "con", "name": "Counter"}], - "ret": {"k": "con", "name": "Int"}, + "params": [{"k": "con", "name": "Counter"}], "param_modes": ["borrow"], + "ret": {"k": "con", "name": "Int"}, "ret_mode": "own", "effects": []}, "params": ["c"], "body": {"t": "lit", "lit": {"kind": "int", "value": 0}}}, @@ -8677,8 +8706,8 @@ mod tests { // Per spec § prep.1 "Canonical form decision" this // is the canonical form and must check. {"kind": "fn", "name": "main", - "type": {"k": "fn", "params": [], - "ret": {"k": "con", "name": "Int"}, + "type": {"k": "fn", "params": [], "param_modes": [], + "ret": {"k": "con", "name": "Int"}, "ret_mode": "own", "effects": []}, "params": [], "body": {"t": "let", "name": "c", @@ -8750,8 +8779,8 @@ mod tests { {"kind": "fn", "name": "unwrap_int", "type": {"k": "fn", "params": [{"k": "con", "name": "StubT", - "args": [{"k": "con", "name": "Int"}]}], - "ret": {"k": "con", "name": "Int"}, + "args": [{"k": "con", "name": "Int"}]}], "param_modes": ["borrow"], + "ret": {"k": "con", "name": "Int"}, "ret_mode": "own", "effects": []}, "params": ["s"], "body": {"t": "lit", "lit": {"kind": "int", "value": 0}}} diff --git a/crates/ailang-check/src/lift.rs b/crates/ailang-check/src/lift.rs index b0e1c80..ea67c9b 100644 --- a/crates/ailang-check/src/lift.rs +++ b/crates/ailang-check/src/lift.rs @@ -561,7 +561,7 @@ impl<'a> Lifter<'a> { ret: ret.clone(), effects: effects.clone(), param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, } } Type::Forall { .. } => panic!( @@ -933,7 +933,7 @@ fn substitute_rigids_local(t: &Type, mapping: &BTreeMap) -> Type { ret: Box::new(substitute_rigids_local(ret, mapping)), effects: effects.clone(), param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, Type::Forall { vars, constraints, body } => { let inner: BTreeMap = mapping diff --git a/crates/ailang-check/src/linearity.rs b/crates/ailang-check/src/linearity.rs index dbea736..c2c48f7 100644 --- a/crates/ailang-check/src/linearity.rs +++ b/crates/ailang-check/src/linearity.rs @@ -1,13 +1,11 @@ -//! Linearity check for fns whose every parameter mode is -//! explicit (`Borrow` or `Own`). +//! Linearity check for fns. Every parameter mode is explicit +//! (`Borrow` or `Own`) — ownership has no default (spec 0062). //! //! ## Scope //! //! - **Active only** on `Def::Fn` whose `Type::Fn.param_modes` is -//! non-empty AND contains no [`ParamMode::Implicit`]. Any -//! `Implicit`-mode param skips the entire fn — that is the back-compat -//! lane that keeps every existing fixture green (only -//! `borrow_own_demo` ships an all-explicit signature today). +//! non-empty. A nullary fn has no binders to track, so the check +//! skips it. //! - **Pure diagnostic.** Emits [`Diagnostic`]s with codes //! `use-after-consume` / `consume-while-borrowed`. No IR change, no //! codegen change, no runtime change. The check runs after @@ -34,8 +32,7 @@ //! application. //! - `Term::App.args[i]` — Borrow if the callee is a `Var` whose //! resolved type is a `Type::Fn` with `param_modes[i] == Borrow`; -//! otherwise Consume (Own / Implicit / unknown all default to -//! Consume). +//! otherwise Consume (Own / unknown default to Consume). //! - `Term::Clone.value` — Borrow (clone reads + bumps RC, doesn't //! move). //! - `Term::Match.scrutinee` — Borrow (matching is a read; for an @@ -364,8 +361,8 @@ fn fn_modes_of(t: &Type) -> Option> { } } -/// Per-fn check. Skips fns whose signature has any `Implicit` param — -/// see module-level scope rules. +/// Per-fn check. Skips nullary fns (no binders to track) — see +/// module-level scope rules. fn check_fn( f: &FnDef, globals: &HashMap, @@ -380,17 +377,17 @@ fn check_fn( _ => return, }; - // Activation gate: every param mode must be explicit (Borrow / Own). - // No params at all also disables the check — there are no binders - // to track in the param-list, so the check has nothing to enforce. - if param_modes.is_empty() || param_modes.iter().any(|m| matches!(m, ParamMode::Implicit)) { + // Activation gate: no params at all disables the check — there are + // no binders to track in the param-list, so the check has nothing + // to enforce. Every mode is now explicitly `Own`/`Borrow` + // (spec 0062), so there is no unmoded disjunct left. + if param_modes.is_empty() { return; } // Sanity: param-count has been verified by `check_fn` upstream, but - // double-defend in case the type vec is shorter than the binder list - // — the linearity walk treats over-long binder lists as `Implicit` - // and would silently mis-skip the check otherwise. + // double-defend in case the type vec is shorter than the binder + // list, which would silently mis-skip the check otherwise. if param_modes.len() != f.params.len() || param_tys.len() != f.params.len() { return; } @@ -414,7 +411,7 @@ fn check_fn( consumed: false, borrow_count: match mode { ParamMode::Borrow => 1, - ParamMode::Own | ParamMode::Implicit => 0, + ParamMode::Own => 0, }, is_value: param_tys.get(i).map(type_is_value).unwrap_or(false), fn_param_modes: param_tys.get(i).and_then(|t| fn_modes_of(t)), @@ -436,10 +433,26 @@ fn check_fn( // is fine as long as none of the arms moves a sub-binder out. // The uniqueness pass already populates `consume_count` for // pattern-binders, so the check is a deterministic walk. + // + // Two guards keep the lint coherent post-cutover: + // - An `(intrinsic)`-bodied fn is skipped wholesale: the walk does + // nothing for `Term::Intrinsic`, so `consume_count == 0` is a + // guaranteed false positive. An intrinsic's param modes are + // hand-authored contracts, not lint-derivable from a body. + // - A value-typed param (`Int`/`Bool`/`Float`/`Unit`) is skipped: + // `(borrow V)` is rejected by `borrow-over-value` (lib.rs), so + // `(own V)` is the only legal mode and a relax-to-borrow + // suggestion would be incoherent. + if matches!(f.body, Term::Intrinsic) { + return; + } for (i, mode) in param_modes.iter().enumerate() { if !matches!(mode, ParamMode::Own) { continue; } + if param_tys.get(i).map(type_is_value).unwrap_or(false) { + continue; + } let pname = &f.params[i]; let consume_count = uniq .get(&(f.name.clone(), pname.clone())) @@ -518,7 +531,7 @@ impl<'a> Checker<'a> { for (i, arg) in args.iter().enumerate() { let arg_pos = match arg_modes.get(i) { Some(ParamMode::Borrow) => Position::Borrow, - // Own / Implicit / unknown → Consume. + // Own / unknown → Consume. _ => Position::Consume, }; self.walk(arg, arg_pos); @@ -903,7 +916,7 @@ impl<'a> Checker<'a> { if param_modes.is_empty() { // All-implicit case (legacy): no mode info → all // Consume, by the rule above. - vec![ParamMode::Implicit; n_args] + vec![ParamMode::Own; n_args] } else { param_modes.clone() } @@ -1444,14 +1457,10 @@ fn relax_param_to_borrow(inner: &Type, i: usize) -> Type { ret_mode, effects, } => { - // Materialise param_modes to full length so the relaxed - // mode is positioned correctly even when the original - // vec was elided to "all-implicit empty" (not the case - // here — we only reach this for an `Own` slot — but - // defensive). - let mut new_modes: Vec = (0..params.len()) - .map(|j| param_modes.get(j).copied().unwrap_or(ParamMode::Implicit)) - .collect(); + // `param_modes.len() == params.len()` (spec 0062: every + // slot is moded), so a direct clone places the relaxed + // mode correctly. + let mut new_modes: Vec = param_modes.clone(); if i < new_modes.len() { new_modes[i] = ParamMode::Borrow; } @@ -1503,7 +1512,7 @@ mod tests { .collect(), param_modes: modes.clone(), ret: Box::new(Type::int()), - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, effects: vec![], }, params: (0..modes.len()).map(|i| format!("p{i}")).collect(), @@ -1529,7 +1538,7 @@ mod tests { }], param_modes: vec![mode], ret: Box::new(Type::int()), - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, effects: vec![], }, params: vec!["p0".into()], @@ -1684,7 +1693,7 @@ mod tests { params: vec![Type::Con { name: "Int".into(), args: vec![] }], param_modes: vec![ParamMode::Own], ret: Box::new(Type::int()), - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, effects: vec![], }, params: vec!["p0".into()], @@ -1874,17 +1883,18 @@ mod tests { imports: vec![], defs: vec![fn_with_modes( "f", - vec![ParamMode::Implicit], + vec![ParamMode::Own], Term::Var { name: "p0".into() }, )], }; assert!(check_module(&m).is_empty()); } - /// Mixed Implicit + Borrow → still skipped (any Implicit disables - /// the check entirely, per the activation gate). + /// Post-cutover (spec 0062): every param is explicitly `Own`/`Borrow`, + /// so a mixed `[Borrow, Own]` fn is checked (no Implicit exemption + /// remains). The unused `Own` param trips `over-strict-mode`. #[test] - fn mixed_implicit_explicit_is_exempt() { + fn mixed_borrow_own_unused_own_fires_over_strict() { let m = Module { schema: ailang_core::SCHEMA.into(), name: "t".into(), @@ -1892,11 +1902,15 @@ mod tests { imports: vec![], defs: vec![fn_with_modes( "f", - vec![ParamMode::Borrow, ParamMode::Implicit], + vec![ParamMode::Borrow, ParamMode::Own], Term::Lit { lit: Literal::Int { value: 0 } }, )], }; - assert!(check_module(&m).is_empty()); + let diags = check_module(&m); + assert!( + diags.iter().any(|d| d.code == "over-strict-mode"), + "the unused `Own` param must fire over-strict-mode; got: {diags:#?}" + ); } /// All-explicit fn that NEVER uses its params, annotated `Borrow`: @@ -2011,7 +2025,7 @@ mod tests { params: vec![Type::Con { name: "List".into(), args: vec![] }], param_modes: vec![ParamMode::Borrow], ret: Box::new(Type::int()), - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, effects: vec![], }, params: vec!["xs".into()], @@ -2052,17 +2066,14 @@ mod tests { !d.suggested_rewrites.is_empty(), "diagnostic should carry a suggested rewrite" ); - // The replacement is a fn-type, so it does not have to round-trip - // through `parse_term` (which parses terms). We only assert it's - // a non-empty string that mentions the relaxed `(borrow ...)`. + // The replacement is a fn-type. Post-cutover every slot is moded, + // so the rewrite legitimately carries `(own ...)` on the return + // slot; the property under test is that the over-strict *param* + // slot is relaxed to `(borrow ...)`. let rep = &d.suggested_rewrites[0].replacement; assert!( - rep.contains("(borrow"), - "rewrite should mention `(borrow`; got {rep}" - ); - assert!( - !rep.contains("(own"), - "rewrite should NOT contain the original `(own`; got {rep}" + rep.contains("(params (borrow (con List)))"), + "rewrite should relax the param to `(borrow (con List))`; got {rep}" ); } @@ -2183,7 +2194,7 @@ mod tests { params: vec![Type::Con { name: "IntList".into(), args: vec![] }], param_modes: vec![ParamMode::Own], ret: Box::new(Type::int()), - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, effects: vec![], }, params: vec!["xs".into()], @@ -2257,7 +2268,7 @@ mod tests { params: vec![Type::Con { name: "State".into(), args: vec![] }], param_modes: vec![ParamMode::Own], ret: Box::new(Type::Con { name: "State".into(), args: vec![] }), - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, effects: vec![], }, params: vec!["st".into()], @@ -2386,6 +2397,92 @@ mod tests { ); } + /// `over-strict-mode` does NOT fire on a never-consumed `(own V)` + /// param whose type is value-typed (`Int`/`Bool`/`Float`/`Unit`). + /// Property: for a value-typed param the lint's own suggested + /// rewrite — `(borrow V)` — is itself a `borrow-over-value` error + /// (lib.rs), so `(own V)` is the only legal mode and a suggestion + /// to relax it is incoherent. The fn body is real (a literal), so + /// the only reason the param looks "over-strict" is its value type; + /// the lint must stay silent regardless. + #[test] + fn over_strict_mode_silent_when_param_is_value_typed() { + // Body: (lit 0) — `p0 : (own (con Int))` is never used at all. + let body = Term::Lit { lit: Literal::Int { value: 0 } }; + let f = Def::Fn(FnDef { + name: "f".into(), + ty: Type::Fn { + params: vec![Type::int()], + param_modes: vec![ParamMode::Own], + ret: Box::new(Type::int()), + ret_mode: ParamMode::Own, + effects: vec![], + }, + params: vec!["p0".into()], + body, + suppress: vec![], + doc: None, + export: None, + }); + let m = Module { + schema: ailang_core::SCHEMA.into(), + name: "t".into(), + kernel: false, + imports: vec![], + defs: vec![f], + }; + let diags = check_module(&m); + assert!( + !diags.iter().any(|d| d.code == "over-strict-mode"), + "no over-strict-mode expected for a value-typed `(own Int)` \ + param: `(borrow Int)` is a borrow-over-value error, so `own` \ + is the only legal mode; got {diags:?}" + ); + } + + /// `over-strict-mode` does NOT fire on a fn whose body is + /// `Term::Intrinsic`, even when an `(own HeapType)` param looks + /// never-consumed. Property: the linearity walk does nothing for an + /// intrinsic body (`Term::Intrinsic => {}`), so `consume_count == 0` + /// is a guaranteed false positive — the param modes of an intrinsic + /// are hand-authored contracts, not lint-derivable from a walkable + /// body. + #[test] + fn over_strict_mode_silent_for_intrinsic_body() { + // `(own IntList)` param, body = (intrinsic). IntList is heap-typed, + // so the only reason the lint would stay silent post-fix is the + // intrinsic-body guard, not the value-type guard. + let f = Def::Fn(FnDef { + name: "f".into(), + ty: Type::Fn { + params: vec![Type::Con { name: "IntList".into(), args: vec![] }], + param_modes: vec![ParamMode::Own], + ret: Box::new(Type::int()), + ret_mode: ParamMode::Own, + effects: vec![], + }, + params: vec!["p0".into()], + body: Term::Intrinsic, + suppress: vec![], + doc: None, + export: None, + }); + let m = Module { + schema: ailang_core::SCHEMA.into(), + name: "t".into(), + kernel: false, + imports: vec![], + defs: vec![intlist_typedef(), f], + }; + let diags = check_module(&m); + assert!( + !diags.iter().any(|d| d.code == "over-strict-mode"), + "no over-strict-mode expected for an intrinsic-bodied fn: the \ + walk cannot observe consumption, so consume_count==0 is a \ + false positive; got {diags:?}" + ); + } + /// a borrow-mode fn that forwards its borrow /// params into a class-method call must not fire /// `consume-while-borrowed` false positives. The pre-fix behaviour @@ -2410,7 +2507,7 @@ mod tests { ], param_modes: vec![ParamMode::Borrow, ParamMode::Borrow], ret: Box::new(Type::bool_()), - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, effects: vec![], }, default: None, @@ -2427,7 +2524,7 @@ mod tests { ], param_modes: vec![ParamMode::Borrow, ParamMode::Borrow], ret: Box::new(Type::bool_()), - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, effects: vec![], }, params: vec!["x".into(), "y".into()], diff --git a/crates/ailang-check/src/lower_to_mir.rs b/crates/ailang-check/src/lower_to_mir.rs index f18f706..72f653c 100644 --- a/crates/ailang-check/src/lower_to_mir.rs +++ b/crates/ailang-check/src/lower_to_mir.rs @@ -260,11 +260,31 @@ fn lower_term(ctx: &mut Ctx, t: &Term) -> Result { .iter() .enumerate() .map(|(i, a)| { - Ok(MArg { - term: lower_term(ctx, a)?, - mode: param_mode_at(&sig, i), - consume_count: 1, - }) + let mode = param_mode_at(&sig, i); + let mut term = lower_term(ctx, a)?; + // mir.4 leg D: a `Str` literal passed BY VALUE into an + // owned (`Own`) param the callee drops must be an owned + // heap slab, not a header-less static rodata constant. + // The cutover's now-active Own-param drop path emits + // `ailang_rc_dec` on the argument; dec'ing a + // `StrRep::Static` reads `payload-8` (the length field) + // as a fake refcount and `free()`s a static address -> + // SIGSEGV. Flip the rep to Heap so codegen emits + // `ailang_str_clone` (rc-headered, refcount 1) and the + // callee's single dec is sound. Gated STRICTLY on `Own`: + // a `Borrow` param fires no `rc_dec`, so borrow-arg + // literals MUST stay `StrRep::Static` (no spurious + // clone). This is the fourth Static->Heap promotion, + // siblings to the seed/tail/recur loop-carried ones. + if matches!(mode, Mode::Owned) { + let arg_ty = ctx.synth_pure(a)?; + if is_str_ty(&arg_ty) { + if let MTerm::Str { rep, .. } = &mut term { + *rep = StrRep::Heap; + } + } + } + Ok(MArg { term, mode, consume_count: 1 }) }) .collect::>>()?; let m_callee = match class { diff --git a/crates/ailang-check/src/mono.rs b/crates/ailang-check/src/mono.rs index cc4cdca..45e1933 100644 --- a/crates/ailang-check/src/mono.rs +++ b/crates/ailang-check/src/mono.rs @@ -588,6 +588,15 @@ pub fn scoped_base(scope: &Option, name: &str) -> String { } } +/// Public view of the per-type-arg mono suffix mangler. Codegen's +/// per-monomorph drop-fn naming (`drop__Pair__Int_Int`) reuses this +/// exact mangling so a drop symbol's suffix matches the fn-symbol +/// scheme byte-for-byte. Delegates to the same private helper +/// `mono_symbol_n` uses, so its fn-symbol output is unperturbed. +pub fn type_mono_suffix(ty: &Type) -> String { + type_to_mono_suffix(ty) +} + fn type_to_mono_suffix(ty: &Type) -> String { match primitive_surface_name(ty) { Some(p) => p.to_string(), diff --git a/crates/ailang-check/src/reuse_shape.rs b/crates/ailang-check/src/reuse_shape.rs index 664ebbf..e83d87a 100644 --- a/crates/ailang-check/src/reuse_shape.rs +++ b/crates/ailang-check/src/reuse_shape.rs @@ -29,10 +29,8 @@ //! ## Activation gate //! //! Same as `linearity::check_module`: only fns whose `Type::Fn.param_modes` -//! is non-empty AND contains no [`ParamMode::Implicit`]. Reuse-as is -//! part of the explicit-mode discipline; firing the check on legacy -//! all-`Implicit` fns would risk false positives on workloads that -//! never opted into the explicit-mode lane. +//! is non-empty (a nullary fn has no binders to track). Every mode is +//! explicit `Own`/`Borrow` (spec 0062). //! //! ## Path-ctor resolution //! @@ -96,19 +94,14 @@ pub(crate) fn check_module(m: &Module) -> Vec { diags } -/// Per-fn check. Skips fns whose signature has any `Implicit` param -/// (legacy back-compat lane) or whose param list is empty (no binders +/// Per-fn check. Skips fns whose param list is empty (no binders /// to track). fn check_fn(f: &FnDef, types: &HashMap, diags: &mut Vec) { let param_modes: &[ParamMode] = match strip_forall(&f.ty) { Type::Fn { param_modes, .. } => param_modes.as_slice(), _ => return, }; - if param_modes.is_empty() - || param_modes - .iter() - .any(|m| matches!(m, ParamMode::Implicit)) - { + if param_modes.is_empty() { return; } @@ -607,7 +600,7 @@ mod tests { .collect(), param_modes: modes.clone(), ret: Box::new(Type::Con { name: "List".into(), args: vec![] }), - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, effects: vec![], }, params: (0..modes.len()).map(|i| format!("p{i}")).collect(), diff --git a/crates/ailang-check/src/suppress_filter.rs b/crates/ailang-check/src/suppress_filter.rs index 422dd4c..6a3ee59 100644 --- a/crates/ailang-check/src/suppress_filter.rs +++ b/crates/ailang-check/src/suppress_filter.rs @@ -120,7 +120,7 @@ mod tests { params: vec![], param_modes: vec![], ret: Box::new(Type::int()), - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, effects: vec![], }, params: vec![], diff --git a/crates/ailang-check/src/uniqueness.rs b/crates/ailang-check/src/uniqueness.rs index 7d113dd..afc5922 100644 --- a/crates/ailang-check/src/uniqueness.rs +++ b/crates/ailang-check/src/uniqueness.rs @@ -449,7 +449,7 @@ impl<'a> Walker<'a> { match inner { Type::Fn { param_modes, .. } => { if param_modes.is_empty() { - vec![ParamMode::Implicit; n_args] + vec![ParamMode::Own; n_args] } else { param_modes.clone() } @@ -510,7 +510,7 @@ mod tests { params: params.iter().map(|_| Type::int()).collect(), param_modes: vec![], ret: Box::new(Type::int()), - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, effects: vec![], }, params: params.into_iter().map(|s| s.to_string()).collect(), diff --git a/crates/ailang-check/tests/workspace.rs b/crates/ailang-check/tests/workspace.rs index e3d86eb..27942f6 100644 --- a/crates/ailang-check/tests/workspace.rs +++ b/crates/ailang-check/tests/workspace.rs @@ -146,7 +146,7 @@ fn body_errors_accumulate_across_defs() { ret: Box::new(Type::int()), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, params: vec![], body: Term::App { @@ -169,7 +169,7 @@ fn body_errors_accumulate_across_defs() { ret: Box::new(Type::int()), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, params: vec![], body: Term::Var { @@ -229,7 +229,7 @@ fn use_after_consume_on_own_param_is_reported() { params: vec![Type::Con { name: "List".into(), args: vec![] }], param_modes: vec![ParamMode::Own], ret: Box::new(Type::int()), - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, effects: vec![], }, params: vec!["ys".into()], @@ -240,7 +240,7 @@ fn use_after_consume_on_own_param_is_reported() { }); // The offending fn. Body is `(+ (sum_list xs) (sum_list xs))` — both - // arg slots of `+` are Implicit/Consume, so `xs` is consumed twice + // arg slots of `+` are Own/Consume, so `xs` is consumed twice // without intervening clone. The second occurrence triggers // `use-after-consume`. (Using `+` rather than `Seq` keeps the // function's return-type Int, which matches its declared signature.) @@ -250,7 +250,7 @@ fn use_after_consume_on_own_param_is_reported() { params: vec![Type::Con { name: "List".into(), args: vec![] }], param_modes: vec![ParamMode::Own], ret: Box::new(Type::int()), - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, effects: vec![], }, params: vec!["xs".into()], @@ -362,7 +362,7 @@ fn consume_while_borrowed_in_sibling_arg_is_reported() { ], param_modes: vec![ParamMode::Borrow, ParamMode::Own], ret: Box::new(Type::int()), - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, effects: vec![], }, params: vec!["a".into(), "b".into()], @@ -378,7 +378,7 @@ fn consume_while_borrowed_in_sibling_arg_is_reported() { params: vec![Type::Con { name: "List".into(), args: vec![] }], param_modes: vec![ParamMode::Own], ret: Box::new(Type::int()), - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, effects: vec![], }, params: vec!["xs".into()], diff --git a/crates/ailang-codegen/src/drop.rs b/crates/ailang-codegen/src/drop.rs index ffcb20d..518e3bc 100644 --- a/crates/ailang-codegen/src/drop.rs +++ b/crates/ailang-codegen/src/drop.rs @@ -27,7 +27,7 @@ use ailang_core::ast::{ParamMode, Type, TypeDef}; use ailang_mir::{Callee, MTerm}; -use std::collections::BTreeSet; +use std::collections::{BTreeMap, BTreeSet}; use super::synth::llvm_type; use super::{AllocStrategy, Emitter, FnSig, Result}; @@ -76,10 +76,44 @@ impl<'a> Emitter<'a> { /// is known to grow, recursive cascade everywhere else (cheaper /// IR, no worklist allocation). pub(crate) fn emit_drop_fn_for_type(&mut self, td: &TypeDef) { + // Monomorphic / intrinsic ADTs: one drop fn, empty subst, no + // suffix — byte-identical to the pre-leg-C emission. + let key = (self.module_name.to_string(), td.name.clone()); + if !self.drop_monos.is_suffixed(&key) { + self.emit_drop_fn_for_instantiation(td, &BTreeMap::new(), ""); + return; + } + // Polymorphic ADTs: one drop fn per concrete instantiation + // collected workspace-wide (leg C). Each fn substitutes the + // declared type-vars to the instantiation's concrete args so + // the dec-vs-skip decision is made on the *monomorph* field + // type (a value-type field is an inline scalar → skipped; a + // heap field is dec'd via its own per-monomorph drop symbol). + for args in self.drop_monos.instantiations(&key) { + let subst: BTreeMap = + td.vars.iter().cloned().zip(args.iter().cloned()).collect(); + let suffix = self + .drop_monos + .suffix_for(&key, &args) + .unwrap_or_default(); + self.emit_drop_fn_for_instantiation(td, &subst, &suffix); + } + } + + /// emit one recursive `drop__` body. `subst` maps the + /// declared type-vars to the concrete instantiation args (empty for + /// monomorphic ADTs); `sym_suffix` is the `__<...>` mono suffix + /// (empty for monomorphic ADTs). + fn emit_drop_fn_for_instantiation( + &mut self, + td: &TypeDef, + subst: &BTreeMap, + sym_suffix: &str, + ) { let m = self.module_name; let tname = &td.name; let mut out = String::new(); - out.push_str(&format!("define void @drop_{m}_{tname}(ptr %p) {{\n")); + out.push_str(&format!("define void @drop_{m}_{tname}{sym_suffix}(ptr %p) {{\n")); out.push_str("entry:\n"); // Null guard: a null payload is a no-op (matches // `runtime/rc.c::ailang_rc_dec`'s null guard). @@ -101,14 +135,16 @@ impl<'a> Emitter<'a> { let mut local = 0u64; for (i, ctor) in td.ctors.iter().enumerate() { out.push_str(&format!("arm_{i}:\n")); - for (j, fty) in ctor.fields.iter().enumerate() { - // Decide what to call for this field. If the field - // lowers to `ptr` (boxed), we issue a `dec` call. - // For known ADT field types we route through that - // ADT's own drop fn so the recursion cascades; for - // anything else that lowers to `ptr` (Str, fn-typed, - // unresolved Var), fall back to plain `ailang_rc_dec`. - let lty = llvm_type(fty).unwrap_or_else(|_| "ptr".into()); + for (j, fty_decl) in ctor.fields.iter().enumerate() { + // Substitute the declared field type to its monomorph. + // A value-type field (`Int`/`Bool`/`Float`/`Unit`) + // lowers to a non-`ptr` scalar → skip the dec (it is + // stored inline, NOT a heap pointer — `rc_dec` on it + // would SIGSEGV). A heap field lowers to `ptr` → dec via + // `field_drop_call` on the *concrete* type, so the + // cascade targets the field's own per-monomorph symbol. + let fty = super::subst::apply_subst_to_type(fty_decl, subst); + let lty = llvm_type(&fty).unwrap_or_else(|_| "ptr".into()); if lty != "ptr" { continue; } @@ -124,7 +160,7 @@ impl<'a> Emitter<'a> { out.push_str(&format!( " %v{val_id} = load ptr, ptr %a{addr_id}, align 8\n" )); - let drop_call = self.field_drop_call(fty); + let drop_call = self.field_drop_call(&fty); // Recursive call into the field's drop fn. If the // field's type is itself `(drop-iterative)`, that drop // fn is the worklist variant — recursion stops at one @@ -227,10 +263,36 @@ impl<'a> Emitter<'a> { /// worklist entry shape. The mono-typed version captures the /// stack-overflow-on-long-self-chains problem fully. pub(crate) fn emit_iterative_drop_fn_for_type(&mut self, td: &TypeDef) { + let key = (self.module_name.to_string(), td.name.clone()); + if !self.drop_monos.is_suffixed(&key) { + self.emit_iterative_drop_fn_for_instantiation(td, &BTreeMap::new(), ""); + return; + } + for args in self.drop_monos.instantiations(&key) { + let subst: BTreeMap = + td.vars.iter().cloned().zip(args.iter().cloned()).collect(); + let suffix = self + .drop_monos + .suffix_for(&key, &args) + .unwrap_or_default(); + self.emit_iterative_drop_fn_for_instantiation(td, &subst, &suffix); + } + } + + /// emit one iterative `drop__` body (worklist variant). + /// `subst` / `sym_suffix` carry the leg-C per-monomorph + /// instantiation, identical in role to + /// [`Self::emit_drop_fn_for_instantiation`]. + fn emit_iterative_drop_fn_for_instantiation( + &mut self, + td: &TypeDef, + subst: &BTreeMap, + sym_suffix: &str, + ) { let m = self.module_name; let tname = &td.name; let mut out = String::new(); - out.push_str(&format!("define void @drop_{m}_{tname}(ptr %p) {{\n")); + out.push_str(&format!("define void @drop_{m}_{tname}{sym_suffix}(ptr %p) {{\n")); out.push_str("entry:\n"); // Null guard — symmetric with the recursive variant. A null // payload skips worklist allocation entirely. @@ -265,8 +327,12 @@ impl<'a> Emitter<'a> { let mut local = 0u64; for (i, ctor) in td.ctors.iter().enumerate() { out.push_str(&format!("arm_{i}:\n")); - for (j, fty) in ctor.fields.iter().enumerate() { - let lty = llvm_type(fty).unwrap_or_else(|_| "ptr".into()); + for (j, fty_decl) in ctor.fields.iter().enumerate() { + // Substitute to the monomorph (same reasoning as the + // recursive variant): a value-type field is inline and + // must not be dec'd/pushed. + let fty = super::subst::apply_subst_to_type(fty_decl, subst); + let lty = llvm_type(&fty).unwrap_or_else(|_| "ptr".into()); if lty != "ptr" { continue; } @@ -281,7 +347,7 @@ impl<'a> Emitter<'a> { out.push_str(&format!( " %v{val_id} = load ptr, ptr %a{addr_id}, align 8\n" )); - if self.field_is_same_type(fty, &td.name) { + if self.field_is_same_type(&fty, &td.name) { // Same-type field: push onto the worklist — // continues the iterative cascade. Null-guarding // is handled inside `ailang_drop_worklist_push` @@ -295,7 +361,7 @@ impl<'a> Emitter<'a> { // iterative). `field_drop_call` resolves the // symbol; its null-guard semantics are the same // as the recursive variant. - let drop_call = self.field_drop_call(fty); + let drop_call = self.field_drop_call(&fty); out.push_str(&format!( " call void @{drop_call}(ptr %v{val_id})\n" )); @@ -374,7 +440,7 @@ impl<'a> Emitter<'a> { /// those are not user-defined ADTs and have no per-type drop fn. pub(crate) fn field_drop_call(&self, fty: &Type) -> String { match fty { - Type::Con { name, .. } => { + Type::Con { name, args } => { // Built-in pointer-typed cons: Str. No drop fn — // shallow `ailang_rc_dec` is the right answer. // Str has two realisations sharing the consumer @@ -395,19 +461,13 @@ impl<'a> Emitter<'a> { if matches!(name.as_str(), "Str") { return "ailang_rc_dec".to_string(); } - // Qualified `module.T` → drop fn lives in `module`. - if name.matches('.').count() == 1 { - let (prefix, suffix) = name.split_once('.').expect("checked"); - if let Some(target) = self.import_map.get(prefix) { - return format!("drop_{target}_{suffix}"); - } - // Fallback: treat the prefix itself as the owner - // module (typechecker would have rejected an - // unimported prefix earlier). - return format!("drop_{prefix}_{suffix}"); - } - // Bare name: declared in the current module. - format!("drop_{m}_{name}", m = self.module_name) + // Resolve owner + per-monomorph suffix in one place + // (leg C). A polymorphic non-intrinsic ADT instantiation + // (`Box Int`, `Pair Int Int`) gets the same `__` + // the emission loop minted for that instantiation; + // monomorphic / intrinsic ADTs keep the un-suffixed + // symbol byte-for-byte. + self.adt_drop_symbol(name, args) } // Type::Fn (closure-typed field) → no per-type drop fn // exists for closures (each closure has its own per-pair @@ -431,6 +491,46 @@ impl<'a> Emitter<'a> { } } + /// resolve the `drop__` symbol for an ADT `Type::Con`, + /// applying the leg-C per-monomorph suffix when the resolved ADT is + /// polymorphic + non-intrinsic. The caller has already excluded + /// `Str` and non-`Con` shapes. `name` is the (possibly qualified) + /// type name; `args` are its concrete instantiation arguments. + /// + /// The suffix decision lives in [`dropmono::DropAdtMeta`], shared + /// with the emission loop, so a call symbol matches its definition + /// byte-for-byte (a mismatch is an IR link error). + pub(crate) fn adt_drop_symbol(&self, name: &str, args: &[Type]) -> String { + let key = super::dropmono::resolve_adt_key( + name, + self.module_name, + &self.import_map, + ); + let base = format!("drop_{owner}_{bare}", owner = key.0, bare = key.1); + match self.drop_monos.suffix_for(&key, args) { + Some(suffix) => format!("{base}{suffix}"), + None => base, + } + } + + /// resolve the `partial_drop__` symbol for an ADT + /// `Type::Con`, applying the leg-C per-monomorph suffix. Parallel + /// to [`Self::adt_drop_symbol`]. The caller has already excluded + /// `Str` / non-`Con` shapes. + pub(crate) fn adt_partial_drop_symbol(&self, name: &str, args: &[Type]) -> String { + let key = super::dropmono::resolve_adt_key( + name, + self.module_name, + &self.import_map, + ); + let base = + format!("partial_drop_{owner}_{bare}", owner = key.0, bare = key.1); + match self.drop_monos.suffix_for(&key, args) { + Some(suffix) => format!("{base}{suffix}"), + None => base, + } + } + /// predicate the `Term::Let` lowering uses to decide /// whether a let-binder owns a fresh RC-heap allocation that /// codegen should `dec` at scope close. @@ -448,8 +548,8 @@ impl<'a> Emitter<'a> { /// fn-type carries `ret_mode == Own`. The mode contract states that /// the callee hands the returned cell's ownership to the caller's /// frame; the let-scope close is the right place for the caller's - /// dec. Calls whose callee is `Borrow`/`Implicit`-returning are - /// still not trackable — they don't carry that signal. + /// dec. Calls whose callee is `Borrow`-returning are still not + /// trackable — a borrow-return is a view, not an owned ref. /// /// Other value shapes (vars, literals, matches, …) return `false` /// here. A `Term::Var` returning an RC-allocated box would already @@ -467,11 +567,10 @@ impl<'a> Emitter<'a> { MTerm::App { callee, .. } => { // a call whose callee carries // `ret_mode == Own` hands a fresh heap allocation to - // the caller's frame. Trackable. `Borrow` and - // `Implicit` ret-modes do not carry that signal — - // returning by Borrow is a view into the callee's - // owned data (caller does not own it), and Implicit - // is the back-compat lane that 18c.3's debt covers. + // the caller's frame. Trackable. A `Borrow` ret-mode + // does not carry that signal — returning by Borrow is a + // view into the callee's owned data (caller does not + // own it). self.synth_callee_ret_mode(callee) .map(|m| matches!(m, ParamMode::Own)) .unwrap_or(false) @@ -538,15 +637,16 @@ impl<'a> Emitter<'a> { pub(crate) fn drop_symbol_for_binder(&self, value: &MTerm, val_ssa: &str) -> String { match value { MTerm::Ctor { type_name, .. } => { - if type_name.matches('.').count() == 1 { - let (prefix, suffix) = - type_name.split_once('.').expect("checked"); - if let Some(target) = self.import_map.get(prefix) { - return format!("drop_{target}_{suffix}"); - } - return format!("drop_{prefix}_{suffix}"); - } - format!("drop_{m}_{type_name}", m = self.module_name) + // The binder's instantiated result type carries the + // concrete args (`Box Int` → args `[Int]`); read them off + // `value.ty()` so the leg-C per-monomorph suffix matches + // the emitted `drop__Box__Int`. `type_name` alone + // would lose the instantiation. + let args = match value.ty() { + Type::Con { args, .. } => args, + _ => Vec::new(), + }; + self.adt_drop_symbol(type_name, &args) } MTerm::Lam { .. } => self .closure_drops @@ -563,7 +663,7 @@ impl<'a> Emitter<'a> { // var on an as-yet-unmonomorphised polymorphic call — // the monomorphised copies will resolve correctly). MTerm::App { .. } | MTerm::Loop { .. } => { - if let Type::Con { name, .. } = value.ty() { + if let Type::Con { name, args } = value.ty() { // Symmetric to `field_drop_call`'s Str arm: Str is a // built-in pointer type with no per-type drop fn. Both // heap-Str (rc_header at payload-8) and static-Str @@ -572,15 +672,7 @@ impl<'a> Emitter<'a> { if name == "Str" { return "ailang_rc_dec".to_string(); } - if name.matches('.').count() == 1 { - let (prefix, suffix) = - name.split_once('.').expect("checked"); - if let Some(target) = self.import_map.get(prefix) { - return format!("drop_{target}_{suffix}"); - } - return format!("drop_{prefix}_{suffix}"); - } - return format!("drop_{m}_{name}", m = self.module_name); + return self.adt_drop_symbol(&name, &args); } "ailang_rc_dec".to_string() } @@ -649,11 +741,36 @@ impl<'a> Emitter<'a> { /// cascade points — the unmoved fields go through their own /// `drop__` which itself decides recursive vs iterative). pub(crate) fn emit_partial_drop_fn_for_type(&mut self, td: &TypeDef) { + let key = (self.module_name.to_string(), td.name.clone()); + if !self.drop_monos.is_suffixed(&key) { + self.emit_partial_drop_fn_for_instantiation(td, &BTreeMap::new(), ""); + return; + } + for args in self.drop_monos.instantiations(&key) { + let subst: BTreeMap = + td.vars.iter().cloned().zip(args.iter().cloned()).collect(); + let suffix = self + .drop_monos + .suffix_for(&key, &args) + .unwrap_or_default(); + self.emit_partial_drop_fn_for_instantiation(td, &subst, &suffix); + } + } + + /// emit one `partial_drop__` body. `subst` / + /// `sym_suffix` carry the leg-C per-monomorph instantiation, + /// identical in role to [`Self::emit_drop_fn_for_instantiation`]. + fn emit_partial_drop_fn_for_instantiation( + &mut self, + td: &TypeDef, + subst: &BTreeMap, + sym_suffix: &str, + ) { let m = self.module_name; let tname = &td.name; let mut out = String::new(); out.push_str(&format!( - "define void @partial_drop_{m}_{tname}(ptr %p, i64 %mask) {{\n" + "define void @partial_drop_{m}_{tname}{sym_suffix}(ptr %p, i64 %mask) {{\n" )); out.push_str("entry:\n"); out.push_str(" %is_null = icmp eq ptr %p, null\n"); @@ -671,8 +788,9 @@ impl<'a> Emitter<'a> { let mut local = 0u64; for (i, ctor) in td.ctors.iter().enumerate() { out.push_str(&format!("arm_{i}:\n")); - for (j, fty) in ctor.fields.iter().enumerate() { - let lty = llvm_type(fty).unwrap_or_else(|_| "ptr".into()); + for (j, fty_decl) in ctor.fields.iter().enumerate() { + let fty = super::subst::apply_subst_to_type(fty_decl, subst); + let lty = llvm_type(&fty).unwrap_or_else(|_| "ptr".into()); if lty != "ptr" { continue; } @@ -702,7 +820,7 @@ impl<'a> Emitter<'a> { out.push_str(&format!( " %v{val_id} = load ptr, ptr %a{addr_id}, align 8\n" )); - let drop_call = self.field_drop_call(fty); + let drop_call = self.field_drop_call(&fty); out.push_str(&format!( " call void @{drop_call}(ptr %v{val_id})\n" )); @@ -788,21 +906,11 @@ impl<'a> Emitter<'a> { /// populated for those shapes anyway). pub(crate) fn partial_drop_symbol_for_type(&self, ty: &Type) -> Option { match ty { - Type::Con { name, .. } => { + Type::Con { name, args } => { if name == "Str" { return None; } - if name.matches('.').count() == 1 { - let (prefix, suffix) = name.split_once('.').expect("checked"); - if let Some(target) = self.import_map.get(prefix) { - return Some(format!("partial_drop_{target}_{suffix}")); - } - return Some(format!("partial_drop_{prefix}_{suffix}")); - } - Some(format!( - "partial_drop_{m}_{name}", - m = self.module_name - )) + Some(self.adt_partial_drop_symbol(name, args)) } _ => None, } @@ -879,12 +987,28 @@ impl<'a> Emitter<'a> { } }; let cref = self.lookup_ctor_by_type(type_name, ctor_name)?; + // leg C: substitute the declared field types to the binder's + // concrete instantiation (read off `value.ty()`'s args against + // the ADT's declared type-vars), so a value-type field is + // recognised as inline (non-`ptr`, skipped) and a heap field + // routes through its own per-monomorph drop symbol. For a + // monomorphic ADT the subst is empty and this is a no-op. + let inst_subst: BTreeMap = + match (&cref.type_vars, value.ty()) { + (vars, Type::Con { args, .. }) + if !vars.is_empty() && vars.len() == args.len() => + { + vars.iter().cloned().zip(args.into_iter()).collect() + } + _ => BTreeMap::new(), + }; // Per-field dec for non-moved pointer-typed slots. ail_fields // are the AILang-level field types; field_drop_call resolves // them to either `drop__` (ADTs cascade) or // `ailang_rc_dec` (Str / closures / vars). - for (idx, fty_ail) in cref.ail_fields.iter().enumerate() { - let lty = llvm_type(fty_ail).unwrap_or_else(|_| "ptr".into()); + for (idx, fty_decl) in cref.ail_fields.iter().enumerate() { + let fty_ail = super::subst::apply_subst_to_type(fty_decl, &inst_subst); + let lty = llvm_type(&fty_ail).unwrap_or_else(|_| "ptr".into()); if lty != "ptr" { continue; } @@ -900,7 +1024,7 @@ impl<'a> Emitter<'a> { self.body.push_str(&format!( " {v} = load ptr, ptr {addr}, align 8\n" )); - let drop_call = self.field_drop_call(fty_ail); + let drop_call = self.field_drop_call(&fty_ail); self.body.push_str(&format!( " call void @{drop_call}(ptr {v})\n" )); diff --git a/crates/ailang-codegen/src/dropmono.rs b/crates/ailang-codegen/src/dropmono.rs new file mode 100644 index 0000000..9591483 --- /dev/null +++ b/crates/ailang-codegen/src/dropmono.rs @@ -0,0 +1,437 @@ +//! Per-monomorph drop-fn collection for polymorphic ADTs (leg C of +//! the #55-cutover drop-soundness family). +//! +//! Background. Pre-cutover, the per-ADT drop fn was emitted exactly +//! once per `Def::Type`, keyed by the *declared* field types. For a +//! polymorphic ADT (`Box a`, `Pair a b`) a ctor field typed at a +//! type-var `a` failed `synth::llvm_type` and fell back to `ptr`, +//! emitting an `ailang_rc_dec` on it. At a value-type monomorph +//! (`Box Int`) that field is an inline `i64`, so `rc_dec(7)` +//! dereferences the scalar and SIGSEGVs. +//! +//! Fix. Emit one drop fn per (polymorphic-ADT, concrete instantiation) +//! and decide dec-vs-skip on the *substituted* field type. A +//! value-type field (`Int`/`Bool`/`Float`/`Unit`) is skipped (inline +//! scalar, no RC); a heap field is dec'd through its own per-monomorph +//! drop symbol so the cascade composes. +//! +//! Symbol naming. A concrete-monomorphic ADT (`vars` empty: IntList, +//! Ordering) keeps its un-suffixed `drop__` symbol byte-for-byte +//! (the `ir_snapshot` goldens pin these). A polymorphic ADT +//! instantiation gets a mono suffix mangled the same way +//! `ailang_check::mono::mono_symbol_n` mangles fn symbols +//! (`drop__Pair__Int_Int`; compound args hash-route to stay +//! bounded). Intrinsic-storage types (RawBuf) are polymorphic but use +//! the flat intrinsic drop path — they are NOT suffixed (their drop is +//! element-type independent and the golden pins `drop__RawBuf`). +//! +//! This module owns the workspace-global collection +//! ([`collect_drop_monos`]) and the shared suffix decision +//! ([`DropAdtMeta`]); the emission and call-site manglers in `drop.rs` +//! / `match_lower.rs` consult the same [`DropAdtMeta`] so a definition +//! and its callers always agree on the symbol (a mismatch is a link +//! error). + +use ailang_core::ast::{Def, Module, Term, Type, TypeDef}; +use ailang_mir::{Callee, MArg, MNewArg, MTerm, MirWorkspace}; +use std::collections::{BTreeMap, BTreeSet}; + +use super::subst::{apply_subst_to_type, qualify_local_types_codegen}; + +/// A canonical key for an ADT: `(owner_module, bare_type_name)`. +pub(crate) type AdtKey = (String, String); + +/// Workspace-global drop-monomorphisation metadata, built once before +/// the per-module codegen loop and shared (by reference) with every +/// `Emitter`. +#[derive(Debug, Default)] +pub(crate) struct DropAdtMeta { + /// ADTs that take a per-monomorph drop-symbol suffix: declared + /// `vars` non-empty AND not intrinsic-storage. A `Type::Con` whose + /// resolved key is in this set, and whose `args` are non-empty, + /// gets a `__` appended to its `drop_`/`partial_drop_` + /// symbol. + suffixed: BTreeSet, + /// For each suffixed ADT, the concrete arg-tuples it is + /// instantiated at across the whole workspace, keyed by a canonical + /// hash of the arg-tuple (`Type` is not `Ord`, so the tuple itself + /// cannot key a `BTreeSet`; the hash gives a deterministic, + /// dedup-stable key). The emission loop reads the values to emit one + /// drop fn per instantiation in the owning module. + monos: BTreeMap>>, +} + +/// Canonical, deterministic key for an arg-tuple (used to dedup +/// instantiations that `Type`'s non-`Ord`-ness blocks from a set key). +fn args_key(args: &[Type]) -> String { + args.iter() + .map(ailang_core::canonical::type_hash) + .collect::>() + .join("|") +} + +impl DropAdtMeta { + /// Is this ADT (resolved to `key`) suffixed (poly + non-intrinsic)? + pub(crate) fn is_suffixed(&self, key: &AdtKey) -> bool { + self.suffixed.contains(key) + } + + /// The concrete instantiations of `key` to emit in its owner + /// module. Empty for monomorphic / intrinsic / never-instantiated + /// ADTs. + pub(crate) fn instantiations(&self, key: &AdtKey) -> Vec> { + self.monos + .get(key) + .map(|s| s.values().cloned().collect()) + .unwrap_or_default() + } + + /// The mono symbol suffix for an instantiation `args`, mangled the + /// same way `mono_symbol_n` mangles fn type-args. `None` when the + /// type is not suffixed (monomorphic / intrinsic) or has no args — + /// caller keeps the un-suffixed base symbol. The leading `__` + /// joiner is included so callers concatenate directly. + pub(crate) fn suffix_for(&self, key: &AdtKey, args: &[Type]) -> Option { + if args.is_empty() || !self.is_suffixed(key) { + return None; + } + // Reuse the fn-symbol mangler: `mono_symbol_n("", &args)` would + // prepend an empty base + joiner. We want only the per-arg + // suffix joined by `__`, prefixed with the `__` that separates + // it from the drop base. Mirror `mono_symbol_n`'s join exactly. + let parts: Vec = args + .iter() + .map(ailang_check::mono::type_mono_suffix) + .collect(); + Some(format!("__{}", parts.join("__"))) + } +} + +/// Resolve a (possibly qualified) `Type::Con` name to its canonical +/// `(owner_module, bare_name)` key, using `import_map` for the +/// qualified case and `current_module` for the bare case. +pub(crate) fn resolve_adt_key( + name: &str, + current_module: &str, + import_map: &BTreeMap, +) -> AdtKey { + if name.matches('.').count() == 1 { + let (prefix, suffix) = name.split_once('.').expect("checked"); + let owner = import_map + .get(prefix) + .map(|s| s.as_str()) + .unwrap_or(prefix); + (owner.to_string(), suffix.to_string()) + } else { + (current_module.to_string(), name.to_string()) + } +} + +/// Does this `Def::Type` use the flat intrinsic-storage drop path +/// (its `new` op is an `(intrinsic)`, not a real term-ctor body)? +/// Mirror of the gate in `lib.rs`'s drop-fn emission loop. +fn is_intrinsic_storage(module: &Module, td: &TypeDef) -> bool { + module.defs.iter().any(|d| { + matches!(d, Def::Fn(f) + if f.name == "new" + && matches!(f.body, Term::Intrinsic) + && fn_returns_type_name(f, &td.name)) + }) +} + +/// Does fn `f` return a `Type::Con` named `tname` (outermost)? Mirror +/// of `lib.rs::fn_returns_type` (kept local to avoid widening that +/// fn's visibility). +fn fn_returns_type_name(f: &ailang_core::ast::FnDef, tname: &str) -> bool { + fn outer(t: &Type, tname: &str) -> bool { + match t { + Type::Con { name, .. } => name == tname, + Type::Forall { body, .. } => outer(body, tname), + _ => false, + } + } + if let Type::Fn { ret, .. } = &f.ty { + outer(ret, tname) + } else if let Type::Forall { body, .. } = &f.ty { + if let Type::Fn { ret, .. } = body.as_ref() { + outer(ret, tname) + } else { + false + } + } else { + false + } +} + +/// Build the workspace-global drop-monomorphisation table: which ADTs +/// are suffixed, and which concrete instantiations each is used at. +pub(crate) fn collect_drop_monos(mir: &MirWorkspace) -> DropAdtMeta { + let mut meta = DropAdtMeta::default(); + + // Per-module set of locally-declared ADT names. Used to qualify a + // bare type-con reference (`List` in `std_list`'s body) to its + // canonical `module.Type` form *at the module where it appears*, so + // a stored arg / field resolves to the same owner regardless of + // which module later emits a drop fn referencing it. Without this, + // a `Maybe (List Int)` collected in `std_list` would store a bare + // `List`, and the drop fn emitted in `std_maybe` (Maybe's owner) + // would resolve that bare `List` to `drop_std_maybe_List` (wrong + // owner) — an undefined-symbol link error. + let mut local_types: BTreeMap> = BTreeMap::new(); + + // 1. Determine the suffixed set + record each ADT's declared + // type-vars and ctor field types (under their owner module), + // so the fixpoint below can expand nested field instantiations. + // `adt_decl[(m, T)] = (vars, ctor_field_types_flattened)`. + let mut adt_decl: BTreeMap, Vec)> = BTreeMap::new(); + for (mname, mir_module) in &mir.modules { + let m = &mir_module.ast; + for def in &m.defs { + if let Def::Type(td) = def { + let key = (mname.clone(), td.name.clone()); + let fields: Vec = + td.ctors.iter().flat_map(|c| c.fields.clone()).collect(); + adt_decl.insert(key.clone(), (td.vars.clone(), fields)); + local_types + .entry(mname.clone()) + .or_default() + .insert(td.name.clone()); + if !td.vars.is_empty() && !is_intrinsic_storage(m, td) { + meta.suffixed.insert(key); + } + } + } + } + + // 2. Seed the instantiation set by walking every MIR body / const + // and collecting every `Type` that appears. Each walked type is + // qualified against its module's local types before collection, + // so stored args carry canonical `module.Type` names. + let mut seeds: Vec<(AdtKey, Vec)> = Vec::new(); + for (mname, mir_module) in &mir.modules { + let m = &mir_module.ast; + let import_map = build_import_map(m); + let owner_local = local_types.get(mname).cloned().unwrap_or_default(); + let mut sink = |t: &Type| { + let q = qualify_local_types_codegen(t, mname, &owner_local); + collect_instantiations(&q, mname, &import_map, &meta.suffixed, &mut seeds); + }; + for d in &mir_module.defs { + walk_term_types(&d.body, &mut sink); + } + for c in &mir_module.consts { + walk_term_types(&c.body, &mut sink); + } + // Also walk every fn signature type appearing in the AST defs — + // an Own-returned polymorphic ADT whose ctor is built in a + // callee still needs its drop emitted in the caller's module + // when the let-close decs it. The body walk catches the ctor + // site; the signature walk is belt-and-braces for ret types + // that surface only through a call. + for def in &m.defs { + if let Def::Fn(f) = def { + walk_type(&f.ty, &mut sink); + } + } + } + + // 3. Fixpoint: an instantiation `T` needs each of its ctor + // fields, substituted by {var -> arg}, to also be emitted (a + // `Pair (Box Int) Int` field-drops `Box Int`). Expand until no + // new (key, args) pairs appear. Dedup on `(AdtKey, args-hash)` + // since `Type` is not `Ord`. + let mut worklist: Vec<(AdtKey, Vec)> = seeds; + let mut seen: BTreeSet<(AdtKey, String)> = BTreeSet::new(); + while let Some((key, args)) = worklist.pop() { + let ak = args_key(&args); + if !seen.insert((key.clone(), ak.clone())) { + continue; + } + meta.monos + .entry(key.clone()) + .or_default() + .insert(ak, args.clone()); + let Some((vars, fields)) = adt_decl.get(&key) else { + continue; + }; + if vars.len() != args.len() { + // Defensive: arity mismatch means our resolution missed; + // skip rather than mis-substitute. + continue; + } + let subst: BTreeMap = + vars.iter().cloned().zip(args.iter().cloned()).collect(); + // Field types are written in the owner module's local + // namespace; resolve against the owner's import map and qualify + // the owner's own bare type-cons (e.g. a `List a` self-field, or + // a sibling ADT) so the nested instantiation keys on the + // canonical owner. The substituted-in args are already + // qualified from the seed pass. + let owner = key.0.clone(); + let import_map = mir + .modules + .get(&owner) + .map(|mm| build_import_map(&mm.ast)) + .unwrap_or_default(); + let owner_local = local_types.get(&owner).cloned().unwrap_or_default(); + for f in fields { + let concrete = apply_subst_to_type(f, &subst); + let concrete = qualify_local_types_codegen(&concrete, &owner, &owner_local); + collect_instantiations( + &concrete, + &owner, + &import_map, + &meta.suffixed, + &mut worklist, + ); + } + } + + meta +} + +/// Build a module's import map (alias|name -> actual module), mirroring +/// the per-module loop in `lower_workspace_inner`. +fn build_import_map(m: &Module) -> BTreeMap { + let mut import_map: BTreeMap = BTreeMap::new(); + for imp in &m.imports { + let key = imp.alias.clone().unwrap_or_else(|| imp.module.clone()); + import_map.insert(key, imp.module.clone()); + } + if m.name != "prelude" { + import_map + .entry("prelude".to_string()) + .or_insert_with(|| "prelude".to_string()); + } + import_map +} + +/// Collect, from a single `Type`, every suffixed-ADT instantiation +/// (recursing into args). +fn collect_instantiations( + t: &Type, + current_module: &str, + import_map: &BTreeMap, + suffixed: &BTreeSet, + out: &mut Vec<(AdtKey, Vec)>, +) { + match t { + Type::Con { name, args } => { + if !args.is_empty() { + let key = resolve_adt_key(name, current_module, import_map); + if suffixed.contains(&key) { + out.push((key, args.clone())); + } + } + for a in args { + collect_instantiations(a, current_module, import_map, suffixed, out); + } + } + Type::Fn { params, ret, .. } => { + for p in params { + collect_instantiations(p, current_module, import_map, suffixed, out); + } + collect_instantiations(ret, current_module, import_map, suffixed, out); + } + Type::Forall { body, .. } => { + collect_instantiations(body, current_module, import_map, suffixed, out) + } + Type::Var { .. } => {} + } +} + +/// Apply `sink` to every `Type` reachable from a `Type`. +fn walk_type(t: &Type, sink: &mut F) { + sink(t); +} + +/// Apply `sink` to every `Type` carried by `term` and its sub-terms. +/// `sink` itself recurses into the type structure +/// ([`collect_instantiations`]), so this walker only has to surface +/// every type-bearing node once. +fn walk_term_types(term: &MTerm, sink: &mut F) { + // The node's own static type. + let ty = term.ty(); + sink(&ty); + match term { + MTerm::Lit { .. } + | MTerm::Var { .. } + | MTerm::Str { .. } + | MTerm::Intrinsic { .. } + | MTerm::Recur { .. } => {} + MTerm::App { callee, args, .. } => { + walk_callee_types(callee, sink); + walk_args(args, sink); + } + MTerm::Do { args, .. } => walk_args(args, sink), + MTerm::Ctor { args, .. } => walk_args(args, sink), + MTerm::New { elem, args, .. } => { + if let Some(e) = elem { + sink(e); + } + for a in args { + if let MNewArg::Value(v) = a { + walk_term_types(v, sink); + } else if let MNewArg::Type(t) = a { + sink(t); + } + } + } + MTerm::Let { init, body, .. } => { + walk_term_types(init, sink); + walk_term_types(body, sink); + } + MTerm::LetRec { sig, body, in_term, .. } => { + sink(sig); + walk_term_types(body, sink); + walk_term_types(in_term, sink); + } + MTerm::If { cond, then, else_, .. } => { + walk_term_types(cond, sink); + walk_term_types(then, sink); + walk_term_types(else_, sink); + } + MTerm::Match { scrutinee, arms, .. } => { + walk_term_types(scrutinee, sink); + for arm in arms { + walk_term_types(&arm.body, sink); + } + } + MTerm::Lam { param_tys, ret_ty, body, .. } => { + for p in param_tys { + sink(p); + } + sink(ret_ty); + walk_term_types(body, sink); + } + MTerm::Seq { lhs, rhs, .. } => { + walk_term_types(lhs, sink); + walk_term_types(rhs, sink); + } + MTerm::Clone { value, .. } => walk_term_types(value, sink), + MTerm::ReuseAs { source, body, .. } => { + walk_term_types(source, sink); + walk_term_types(body, sink); + } + MTerm::Loop { binders, body, .. } => { + for b in binders { + sink(&b.ty); + walk_term_types(&b.init, sink); + } + walk_term_types(body, sink); + } + } +} + +fn walk_args(args: &[MArg], sink: &mut F) { + for a in args { + walk_term_types(&a.term, sink); + } +} + +fn walk_callee_types(callee: &Callee, sink: &mut F) { + match callee { + Callee::Static { sig, .. } | Callee::Builtin { sig, .. } => sink(sig), + Callee::Indirect(inner) => walk_term_types(inner, sink), + } +} diff --git a/crates/ailang-codegen/src/lambda.rs b/crates/ailang-codegen/src/lambda.rs index 7236061..2a87c38 100644 --- a/crates/ailang-codegen/src/lambda.rs +++ b/crates/ailang-codegen/src/lambda.rs @@ -153,15 +153,13 @@ impl<'a> Emitter<'a> { let saved_entry_marker = self.entry_block_end_marker.take(); // A lambda thunk is its own fn frame for // param-mode lookup. The outer fn's params are not in scope - // inside the thunk; the thunk's own params are pushed below - // and (currently) carry no mode annotation, so they default - // to `Implicit` — `lower_match`'s Iter A gate will skip arm- - // close pattern-binder dec for matches on lambda params, - // mirroring the fn-level Implicit-param treatment. + // inside the thunk; the thunk's own params carry no surface + // annotation, so they are synthesised `Own` — the consume-side + // default that owns its argument for the thunk body. let saved_param_modes = std::mem::take(&mut self.current_param_modes); for pname in lam_params.iter() { self.current_param_modes - .insert(pname.clone(), ParamMode::Implicit); + .insert(pname.clone(), ParamMode::Own); } // Lambdas inside lambdas are fine: they get their own counter // namespace within the enclosing thunk. They share the diff --git a/crates/ailang-codegen/src/lib.rs b/crates/ailang-codegen/src/lib.rs index efdab19..2a212ea 100644 --- a/crates/ailang-codegen/src/lib.rs +++ b/crates/ailang-codegen/src/lib.rs @@ -40,6 +40,7 @@ use ailang_mir::{Callee, MArg, MTerm, MirDef, MirWorkspace, Mode, StrRep}; use std::collections::{BTreeMap, BTreeSet}; mod drop; +mod dropmono; mod escape; mod intercepts; mod lambda; @@ -396,6 +397,14 @@ fn lower_workspace_inner(mir: &MirWorkspace, alloc: AllocStrategy, target: Targe module_consts.insert(mname.clone(), consts); } + // Leg C: workspace-global per-monomorph drop metadata. Built once + // here (after the symbol-table pass, before lowering) and shared by + // reference with every `Emitter`. Computes which polymorphic ADTs + // take a per-instantiation drop fn and the concrete arg-tuples each + // is used at, so the emission loop and the call-site manglers agree + // on every `drop____` symbol. + let drop_monos = dropmono::collect_drop_monos(mir); + // Pass 2: lower per module. Globals/strings are accumulated per module, // because they are mangled per module. for (mname, mir_module) in &mir.modules { @@ -432,6 +441,7 @@ fn lower_workspace_inner(mir: &MirWorkspace, alloc: AllocStrategy, target: Targe &module_ctor_index, &module_consts, import_map, + &drop_monos, alloc, ); emitter @@ -770,6 +780,13 @@ struct Emitter<'a> { module_user_fns: &'a BTreeMap>, /// Import map of the current module (alias/module name → actual module name). import_map: BTreeMap, + /// workspace-global per-monomorph drop metadata (leg C). Tells the + /// drop-fn emission loop which polymorphic ADTs to emit one drop fn + /// per instantiation for, and tells the call-site manglers whether + /// a `Type::Con` takes a per-monomorph `__` on its + /// `drop_`/`partial_drop_` symbol. Shared by reference with every + /// `Emitter`. + drop_monos: &'a dropmono::DropAdtMeta, /// ADT table: type_name -> list of ctors in definition order. /// Tag of a ctor = index in this list. /// Kept around for future tools (pretty-printer for ADT values, @@ -874,15 +891,15 @@ struct Emitter<'a> { /// entry) from the fn type's `param_modes`. Consulted by /// `lower_match`'s arm-close pattern-binder dec emission (Iter A) to /// decide whether the scrutinee was statically owned: if the - /// scrutinee resolves to a fn-param whose mode is `Borrow` or - /// `Implicit`, the pattern-binder dec must NOT fire — the caller - /// still holds a reference and dec'ing the pattern-binder would - /// fragment the caller's structure. + /// scrutinee resolves to a fn-param whose mode is `Borrow`, the + /// pattern-binder dec must NOT fire — the caller still holds a + /// reference and dec'ing the pattern-binder would fragment the + /// caller's structure. /// /// Symmetric with the Iter B gate at fn return (`emit_fn`'s Own- - /// param dec): both sites must check the param-mode signal before - /// dec'ing, because Implicit and Borrow do not carry the "caller - /// handed off ownership" signal that makes the dec safe. + /// param dec): both sites check the param-mode signal before + /// dec'ing, because `Borrow` does not carry the "caller handed off + /// ownership" signal that makes the dec safe. current_param_modes: BTreeMap, /// Per-fn map of name → (alloca SSA name, AIL element type) for /// alloca-resident loop binders. Populated on entry to a @@ -973,6 +990,7 @@ impl<'a> Emitter<'a> { module_ctor_index: &'a BTreeMap>, module_consts: &'a BTreeMap>, import_map: BTreeMap, + drop_monos: &'a dropmono::DropAdtMeta, alloc: AllocStrategy, ) -> Self { let mut types: BTreeMap> = BTreeMap::new(); @@ -1030,6 +1048,7 @@ impl<'a> Emitter<'a> { str_counter: 0, module_user_fns, import_map, + drop_monos, types, module_ctor_index, module_consts, @@ -1293,10 +1312,9 @@ impl<'a> Emitter<'a> { fn emit_fn(&mut self, f: &FnDef, body: Option<&MTerm>) -> Result<()> { // also lift `param_modes` out of the fn type. The // fn-return Own-param dec emission below consults it to decide - // which params get a drop call before `ret`. `Implicit` - // entries (legacy / unannotated) and `Borrow` entries are - // skipped — only `Own` carries the static "caller handed off - // ownership" signal. + // which params get a drop call before `ret`. `Borrow` entries + // are skipped — only `Own` carries the static "caller handed + // off ownership" signal. let (param_tys, ret_ty, param_modes) = match &f.ty { Type::Fn { params, @@ -1339,7 +1357,10 @@ impl<'a> Emitter<'a> { self.pending_entry_allocas.clear(); self.entry_block_end_marker = None; for (i, pname) in f.params.iter().enumerate() { - let mode = param_modes.get(i).copied().unwrap_or(ParamMode::Implicit); + // Codegen-synthesised fn-defs (lambda thunks, local-rec + // lifts) may carry an empty `param_modes`; fall back to + // `Own` (the synthesis default) rather than index-panic. + let mode = param_modes.get(i).copied().unwrap_or(ParamMode::Own); self.current_param_modes.insert(pname.clone(), mode); } // run escape analysis over the fn body. The result @@ -1469,11 +1490,10 @@ impl<'a> Emitter<'a> { // the caller's frame; caller dec's, not us), // - the current block is still open. // - // `Implicit`-mode params do NOT get this dec: they have - // no static "caller handed off ownership" signal — - // emitting a dec here might double-dec a value the caller - // also dec's. `Borrow`-mode params definitely don't get - // dec'd (the caller still owns them). + // `Borrow`-mode params do NOT get dec'd: the caller still + // owns them, so there is no caller-handed-off-ownership + // signal and a dec here would fragment the caller's + // structure. // // Closes the 18c.3/18c.4 carve-out: "fn parameters still // don't get dec'd at fn return — the caller-handed-off- @@ -1490,7 +1510,7 @@ impl<'a> Emitter<'a> { if plty != "ptr" { continue; } - let mode = param_modes.get(i).copied().unwrap_or(ParamMode::Implicit); + let mode = param_modes.get(i).copied().unwrap_or(ParamMode::Own); if !matches!(mode, ParamMode::Own) { continue; } @@ -1812,8 +1832,8 @@ impl<'a> Emitter<'a> { // If `value` is a `Term::Var` referencing a name in // `current_param_modes`, the let-binder inherits that // mode for the duration of the body. Without this, - // `(let a t (match a ...))` where `t` is an Implicit - // / Borrow-mode param defeats the + // `(let a t (match a ...))` where `t` is a + // `Borrow`-mode param defeats the // `scrutinee_is_owned` gate in `lower_match` (the // gate looks up `a` in `current_param_modes`, misses, // and defaults to "owned" — Iter A then dec's @@ -2690,8 +2710,7 @@ impl<'a> Emitter<'a> { // alias whose owner is some other binder and is dropped // there, never here; // - the matching callee param mode is `Borrow` — `Own` slots - // consume the arg (the callee dec's it), `Implicit` is the - // back-compat lane that carries no transfer signal; + // consume the arg (the callee dec's it); // - the dropped SSA is never the call result `dst` (an input // argument SSA is always distinct from the freshly-minted // result SSA), so this can never dec a value that flows out @@ -2702,7 +2721,7 @@ impl<'a> Emitter<'a> { // `param_modes`), so the borrow-slot test reads it directly // — no re-lookup of the callee's signature from a codegen // sig table. `Borrow` slots borrow the arg, so an Own-ret - // heap temp landing in one is dropped here; `Own`/`Implicit` + // heap temp landing in one is dropped here; `Own` // slots consume the arg (the callee dec's it). for (arg, (arg_ssa, arg_ty)) in args.iter().zip(compiled_args.iter()) { let is_borrow_slot = matches!(arg.mode, Mode::Borrow); @@ -3168,7 +3187,7 @@ mod tests { ret: Box::new(Type::int()), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, params: vec!["a".into(), "b".into()], body: Term::App { @@ -3192,7 +3211,7 @@ mod tests { ret: Box::new(Type::unit()), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, params: vec![], body: Term::Lit { lit: Literal::Unit }, @@ -3238,7 +3257,7 @@ mod tests { ret: Box::new(Type::int()), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, params: vec![], body: Term::Lit { @@ -3313,7 +3332,7 @@ mod tests { ret: Box::new(Type::unit()), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, params: vec![], body: Term::Lit { lit: Literal::Unit }, @@ -3386,7 +3405,7 @@ mod tests { ret: Box::new(Type::unit()), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, params: vec![], body: Term::Lit { lit: Literal::Unit }, @@ -3418,7 +3437,7 @@ mod tests { ret: Box::new(ret_ty), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, params: vec![], body, @@ -3491,7 +3510,7 @@ mod tests { name: name.into(), ty: Type::Fn { params: vec![], ret: Box::new(ret_ty), effects: vec![], - param_modes: vec![], ret_mode: ParamMode::Implicit, + param_modes: vec![], ret_mode: ParamMode::Own, }, params: vec![], body, suppress: vec![], doc: None, export: None, @@ -3543,7 +3562,7 @@ mod tests { name: name.into(), ty: Type::Fn { params: vec![], ret: Box::new(Type::float()), effects: vec![], - param_modes: vec![], ret_mode: ParamMode::Implicit, + param_modes: vec![], ret_mode: ParamMode::Own, }, params: vec![], body, suppress: vec![], doc: None, export: None, @@ -3553,7 +3572,7 @@ mod tests { name: "main".into(), ty: Type::Fn { params: vec![], ret: Box::new(Type::unit()), effects: vec![], - param_modes: vec![], ret_mode: ParamMode::Implicit, + param_modes: vec![], ret_mode: ParamMode::Own, }, params: vec![], body: Term::Lit { lit: Literal::Unit }, suppress: vec![], doc: None, @@ -3598,7 +3617,7 @@ mod tests { ret: Box::new(Type::bool_()), effects: vec![], param_modes: vec![ParamMode::Borrow, ParamMode::Borrow], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, params: vec!["x".into(), "y".into()], // Body is a placeholder — the intercept must @@ -3616,7 +3635,7 @@ mod tests { ret: Box::new(Type::unit()), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, params: vec![], body: Term::Lit { lit: Literal::Unit }, @@ -3661,7 +3680,7 @@ mod tests { ret: Box::new(Type::bool_()), effects: vec![], param_modes: vec![ParamMode::Borrow, ParamMode::Borrow], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, params: vec!["x".into(), "y".into()], body: Term::Lit { lit: Literal::Bool { value: false } }, @@ -3676,7 +3695,7 @@ mod tests { ret: Box::new(Type::unit()), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, params: vec![], body: Term::Lit { lit: Literal::Unit }, @@ -3725,7 +3744,7 @@ mod tests { ret: Box::new(Type::unit()), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, params: vec![], body: Term::Lit { lit: Literal::Unit }, @@ -3824,8 +3843,11 @@ mod tests { params: vec![param_ail_ty.clone(), param_ail_ty.clone()], ret: Box::new(Type::Con { name: "Ordering".into(), args: vec![] }), effects: vec![], - param_modes: vec![ParamMode::Borrow, ParamMode::Borrow], - ret_mode: ParamMode::Implicit, + // value-typed params (Int/Bool) cannot be `(borrow V)` — + // the cutover's `borrow-over-value` reject forbids it; a + // value type is copied, so `own` is the only legal mode. + param_modes: vec![ParamMode::Own, ParamMode::Own], + ret_mode: ParamMode::Own, }, params: vec!["x".into(), "y".into()], // placeholder body; the `compare__` intercept overrides @@ -3849,7 +3871,7 @@ mod tests { ret: Box::new(Type::unit()), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, params: vec![], body: Term::Lit { lit: Literal::Unit }, @@ -3894,7 +3916,7 @@ mod tests { ret: Box::new(Type::unit()), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, params: vec![], body: Term::Lit { lit: Literal::Unit }, @@ -3930,7 +3952,7 @@ mod tests { ret: Box::new(Type::unit()), effects: vec!["IO".into()], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, params: vec![], body: Term::Do { @@ -3972,7 +3994,7 @@ mod tests { ret: Box::new(Type::unit()), effects: vec!["IO".into()], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, params: vec![], body: Term::Do { @@ -4033,7 +4055,7 @@ mod tests { ret: Box::new(Type::bool_()), effects: vec![], param_modes: vec![ParamMode::Borrow, ParamMode::Borrow], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, params: vec!["x".into(), "y".into()], body: Term::Lit { lit: Literal::Bool { value: false } }, @@ -4048,7 +4070,7 @@ mod tests { ret: Box::new(Type::unit()), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, params: vec![], body: Term::Lit { lit: Literal::Unit }, @@ -4129,7 +4151,7 @@ mod tests { ret: Box::new(Type::unit()), effects: vec!["IO".into()], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, params: vec![], body: Term::Do { @@ -4170,7 +4192,7 @@ mod tests { ret: Box::new(Type::unit()), effects: vec!["IO".into()], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, params: vec![], body: Term::Do { @@ -4212,7 +4234,7 @@ mod tests { ret: Box::new(Type::unit()), effects: vec!["IO".into()], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, params: vec![], body: Term::Do { @@ -4254,7 +4276,7 @@ mod tests { ret: Box::new(Type::unit()), effects: vec!["IO".into()], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, params: vec![], body: Term::Do { @@ -4296,7 +4318,7 @@ mod tests { ret: Box::new(Type::unit()), effects: vec!["IO".into()], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, params: vec![], body: Term::Do { diff --git a/crates/ailang-codegen/src/match_lower.rs b/crates/ailang-codegen/src/match_lower.rs index d3519f7..e872138 100644 --- a/crates/ailang-codegen/src/match_lower.rs +++ b/crates/ailang-codegen/src/match_lower.rs @@ -256,8 +256,16 @@ impl<'a> Emitter<'a> { } else { cref.ail_fields.clone() }; - let expected_llvm_tys: Vec = if cref.type_vars.is_empty() { - cref.fields.clone() + // leg C: the body-ctor field substitution (var -> concrete + // arg). Empty for monomorphic ADTs; for a polymorphic body ctor + // it pins each declared field var to the instantiation arg + // derived from the body args' types. Used both for the expected + // LLVM field types AND (further down) for the reuse-arm per-field + // dec, so a value-type field is recognised as inline (skipped) + // and a heap field routes through its own per-monomorph drop + // symbol. + let field_subst: BTreeMap = if cref.type_vars.is_empty() { + BTreeMap::new() } else { let arg_ail_tys: Vec = body_args .iter() @@ -269,9 +277,14 @@ impl<'a> Emitter<'a> { for (exp, actual) in qualified_ail_fields.iter().zip(arg_ail_tys.iter()) { unify_for_subst(exp, actual, &var_set, &mut subst)?; } + subst + }; + let expected_llvm_tys: Vec = if cref.type_vars.is_empty() { + cref.fields.clone() + } else { qualified_ail_fields .iter() - .map(|f| llvm_type(&apply_subst_to_type(f, &subst))) + .map(|f| llvm_type(&apply_subst_to_type(f, &field_subst))) .collect::>()? }; // (18d.2 currently does not dec old fields in the reuse @@ -353,8 +366,12 @@ impl<'a> Emitter<'a> { .as_ref() .and_then(|sb| self.moved_slots.get(sb).cloned()) .unwrap_or_default(); - for (idx, fty_ail) in qualified_ail_fields.iter().enumerate() { - let lty = llvm_type(fty_ail).unwrap_or_else(|_| "ptr".into()); + for (idx, fty_decl) in qualified_ail_fields.iter().enumerate() { + // leg C: substitute to the body-ctor's monomorph so a + // value-type slot is skipped (inline scalar) and a heap slot + // dec's through its own per-monomorph drop symbol. + let fty_ail = apply_subst_to_type(fty_decl, &field_subst); + let lty = llvm_type(&fty_ail).unwrap_or_else(|_| "ptr".into()); if lty != "ptr" { continue; } @@ -370,7 +387,7 @@ impl<'a> Emitter<'a> { self.body.push_str(&format!( " {v} = load ptr, ptr {addr}, align 8\n" )); - let drop_call = self.field_drop_call(fty_ail); + let drop_call = self.field_drop_call(&fty_ail); self.body.push_str(&format!( " call void @{drop_call}(ptr {v})\n" )); diff --git a/crates/ailang-codegen/src/subst.rs b/crates/ailang-codegen/src/subst.rs index 9ac9395..72476d1 100644 --- a/crates/ailang-codegen/src/subst.rs +++ b/crates/ailang-codegen/src/subst.rs @@ -170,13 +170,37 @@ pub(crate) fn apply_subst_to_type(t: &Type, subst: &BTreeMap) -> T name: name.clone(), args: args.iter().map(|a| apply_subst_to_type(a, subst)).collect(), }, - Type::Fn { params, ret, effects, param_modes, ret_mode } => Type::Fn { - params: params.iter().map(|p| apply_subst_to_type(p, subst)).collect(), - ret: Box::new(apply_subst_to_type(ret, subst)), - effects: effects.clone(), - param_modes: param_modes.clone(), - ret_mode: *ret_mode, - }, + Type::Fn { params, ret, effects, param_modes, ret_mode } => { + let new_params: Vec = + params.iter().map(|p| apply_subst_to_type(p, subst)).collect(); + let new_ret = apply_subst_to_type(ret, subst); + // spec 0062: a polymorphic (borrow a) specialised onto a + // value type becomes (own value-type) — borrow-over-value + // is forbidden and is a no-op for unboxed types (no RC). + let coerce = |ty: &Type, m: &ParamMode| -> ParamMode { + if matches!(m, ParamMode::Borrow) { + if let Type::Con { name, args } = ty { + if args.is_empty() && ailang_core::primitives::is_value_type(name) { + return ParamMode::Own; + } + } + } + *m + }; + let new_param_modes: Vec = new_params + .iter() + .zip(param_modes.iter()) + .map(|(t, m)| coerce(t, m)) + .collect(); + let new_ret_mode = coerce(&new_ret, ret_mode); + Type::Fn { + params: new_params, + ret: Box::new(new_ret), + effects: effects.clone(), + param_modes: new_param_modes, + ret_mode: new_ret_mode, + } + } Type::Forall { vars, constraints, body } => { // Inner forall shadows: don't substitute re-bound names. let inner: BTreeMap = subst diff --git a/crates/ailang-core/src/ast.rs b/crates/ailang-core/src/ast.rs index ab2c419..6215518 100644 --- a/crates/ailang-core/src/ast.rs +++ b/crates/ailang-core/src/ast.rs @@ -770,17 +770,14 @@ pub enum Type { /// `(own T)` wrappers from the surface form. They are metadata /// on `Type::Fn`, not new `Type` variants — so unification, /// occurs, apply, and every other `Type` match-arm keeps working - /// unchanged. `param_modes` is omitted from canonical JSON when - /// every entry is `Implicit`; `ret_mode` is omitted when it is - /// `Implicit`, so pre-mode-annotation fixtures hash - /// bit-identically. Full contract in + /// unchanged. Both are always present (one mode per slot, + /// `param_modes.len() == params.len()`); ownership has no default + /// (spec 0062). Full contract in /// `design/contracts/0008-memory-model.md`. Fn { params: Vec, - #[serde(default, skip_serializing_if = "all_implicit")] param_modes: Vec, ret: Box, - #[serde(default, skip_serializing_if = "ParamMode::is_implicit")] ret_mode: ParamMode, #[serde(default)] effects: Vec, @@ -829,123 +826,37 @@ impl Type { Type::Con { name: "Float".into(), args: vec![] } } - /// Build a `Type::Fn` with all parameter modes set to - /// `ParamMode::Implicit` and `ret_mode` set to `Implicit`. This - /// is the form every typechecker / desugar / codegen site that - /// synthesises a fn-type should use, so that newly inferred - /// fn-types retain pre-mode-annotation canonical-JSON bytes. - pub fn fn_implicit(params: Vec, ret: Type, effects: Vec) -> Type { + /// Build a `Type::Fn` with every parameter mode and the return + /// mode set to `ParamMode::Own`. The synthesis form for every + /// typechecker / desugar / codegen site that builds a fn-type; + /// `Own` is correct by construction (spec 0062 Data flow: the old + /// typechecker made `Implicit ≡ Own`, so synthesised fn-types were + /// already semantically `Own`). + pub fn fn_owned(params: Vec, ret: Type, effects: Vec) -> Type { let n = params.len(); Type::Fn { params, - param_modes: vec![ParamMode::Implicit; n], + param_modes: vec![ParamMode::Own; n], ret: Box::new(ret), - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, effects, } } } -/// Visit every `Type::Fn` in `m`, letting `f` rewrite its modes. -/// `f(params_len, param_modes, ret_mode)`. Used by the throwaway -/// `migrate-modes` tool (spec 0062); has no other caller and is -/// removed if the migration machinery is retired. -pub fn for_each_fn_type_mut( - m: &mut Module, - f: &mut impl FnMut(usize, &mut Vec, &mut ParamMode), -) { - fn walk_ty(t: &mut Type, f: &mut impl FnMut(usize, &mut Vec, &mut ParamMode)) { - match t { - Type::Fn { params, param_modes, ret, ret_mode, .. } => { - let n = params.len(); - for p in params.iter_mut() { walk_ty(p, f); } - walk_ty(ret, f); - f(n, param_modes, ret_mode); - } - Type::Con { args, .. } => { for a in args.iter_mut() { walk_ty(a, f); } } - Type::Forall { body, .. } => walk_ty(body, f), - Type::Var { .. } => {} - } - } - for def in m.defs.iter_mut() { - if let Def::Fn(fd) = def { - walk_ty(&mut fd.ty, f); - } - } -} - /// Per-parameter / return mode marker on a [`Type::Fn`]. Full /// contract lives in `design/contracts/0008-memory-model.md`. -/// -/// `Implicit` is the legacy state for fn-types that were constructed -/// before the borrow/own surface annotations existed. Semantically, -/// `Implicit ≡ Own`; the distinction exists only so pre-annotation -/// JSON fixtures continue to serialize without a `"mode"` wrapper -/// and therefore keep their canonical-JSON hash. -/// -/// `Own` and `Borrow` are author-asserted: the surface form -/// `(own T)` / `(borrow T)` round-trips through this enum. -#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +/// Ownership has no default: every fn-type slot carries an explicit +/// `Own` or `Borrow` (spec 0062). +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "lowercase")] pub enum ParamMode { - /// Unannotated / back-compat. Treated as `Own` by the typechecker. - #[default] - Implicit, /// `(own T)` — caller transfers ownership; callee consumes. Own, /// `(borrow T)` — caller retains ownership; callee may not consume. Borrow, } -impl ParamMode { - /// Used by the `skip_serializing_if` predicate on - /// [`Type::Fn::ret_mode`]. - pub fn is_implicit(&self) -> bool { - matches!(self, ParamMode::Implicit) - } -} - -/// Serde helper for [`Type::Fn::param_modes`]. Returns `true` when -/// every entry is [`ParamMode::Implicit`] (or when the list is -/// empty), so canonical JSON omits the field for any fn-type without -/// explicit `(borrow)` / `(own)` annotations and pre-annotation -/// fixtures keep bit-identical hashes. -fn all_implicit(modes: &[ParamMode]) -> bool { - modes.iter().all(|m| m.is_implicit()) -} - -/// Equality of [`ParamMode`] for the purposes of `Type` equality. -/// `Implicit` and `Own` are treated as the same mode; `Borrow` is -/// distinct. This keeps pre-annotation fixtures (whose fn-types -/// serialize `Implicit`) compatible with newly-written fixtures -/// that mark the same fn-type explicitly with `(own T)`. -fn mode_eq(a: &ParamMode, b: &ParamMode) -> bool { - match (a, b) { - (ParamMode::Borrow, ParamMode::Borrow) => true, - (ParamMode::Borrow, _) | (_, ParamMode::Borrow) => false, - // Implicit and Own are interchangeable. - _ => true, - } -} - -/// Equality of two `param_modes` slices, robust to the -/// "elided when all-implicit" representation used by typechecker / -/// desugar / codegen sites that construct fn-types with -/// `param_modes: vec![]`. Both slices are normalised to "implicit -/// padding to match the longer one"; equality then proceeds -/// element-wise via [`mode_eq`]. -fn mode_slices_eq(a: &[ParamMode], b: &[ParamMode]) -> bool { - let n = a.len().max(b.len()); - for i in 0..n { - let x = a.get(i).copied().unwrap_or(ParamMode::Implicit); - let y = b.get(i).copied().unwrap_or(ParamMode::Implicit); - if !mode_eq(&x, &y) { - return false; - } - } - true -} - impl PartialEq for Type { fn eq(&self, other: &Self) -> bool { match (self, other) { @@ -971,8 +882,8 @@ impl PartialEq for Type { ) => { ap == bp && ar == br - && mode_slices_eq(apm, bpm) - && mode_eq(arm, brm) + && apm == bpm + && arm == brm && { let mut a = ae.clone(); let mut b = be.clone(); diff --git a/crates/ailang-core/src/desugar.rs b/crates/ailang-core/src/desugar.rs index 32b3c06..fdb1c91 100644 --- a/crates/ailang-core/src/desugar.rs +++ b/crates/ailang-core/src/desugar.rs @@ -933,7 +933,7 @@ impl Desugarer { ret: ret.clone(), effects: effects.clone(), param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, } } Type::Forall { .. } => panic!( @@ -1115,8 +1115,6 @@ impl Desugarer { arms, }; } - let s = self.fresh(); - let s_var = Term::Var { name: s.clone() }; // `default` is unreachable for valid programs (the // typechecker requires either a catch-all arm or exhaustive // ctor coverage). Use the polymorphic bottom builtin @@ -1125,6 +1123,28 @@ impl Desugarer { // `Unit`-typed `_` arm to dominate it. Codegen lowers the // var to LLVM `unreachable`. let default = Term::Var { name: "__unreachable__".into() }; + // When the scrutinee is ALREADY a bare `Term::Var` (a function + // param or an existing let-binder), do NOT introduce a fresh + // `Let $mp = ` rebind: reuse the binder directly as + // the chain scrutinee. The rebind is spurious here and, post-#55, + // harmful — walking the `Let` value in `Position::Consume` bumps + // the param's `consume_count` to 1, which trips the fn-return + // husk-free gate (`consume_count != 0` ⇒ skip) and relocates the + // outer-cell ownership onto the internal `$mp` binder the gate + // never inspects, leaking the moved-from outer cell. Reusing the + // binder keeps `consume_count == 0` and populates `moved_slots` + // exactly as the single-match case, so the existing gate fires + // unchanged (refs #55, examples/lit_pat_ctor_tail_drop.ail, + // examples/lit_pat_nil_scrutinee_drop.ail). + // + // A COMPOUND-expression scrutinee (not a bare Var) still needs + // the `$mp` rebind so it is evaluated once and shared across all + // chain arms rather than re-evaluated per arm. + if matches!(scrutinee, Term::Var { .. }) { + return self.build_chain(&scrutinee, &arms, &default); + } + let s = self.fresh(); + let s_var = Term::Var { name: s.clone() }; let chain = self.build_chain(&s_var, &arms, &default); Term::Let { name: s, @@ -1136,16 +1156,109 @@ impl Desugarer { /// Recursively builds a chain of single-arm matches with a shared /// fall-through. Empty arms ⇒ `default`; otherwise the first arm /// is desugared with the rest of the chain as its fall-through. + /// + /// Consecutive arms whose head pattern is the *same* outer ctor + /// (same name + arity) are grouped into ONE [`Term::Match`] arm + /// that binds the ctor fields exactly once, then branches the + /// per-arm sub-patterns over those bound field vars. This is the + /// single-ownership-scope invariant the arm-close drop accounting + /// in `match_lower` relies on: a ctor's owned children are bound + /// (and therefore dropped) under one match scope per ctor, never + /// re-matched on the same scrutinee. Re-matching the same owned + /// scrutinee under a second ctor scope (the pre-fix lowering of a + /// lit sub-pattern's `else` branch) bound — and the post-#55 + /// `Own`-param arm-close dropped — the same heap child twice + /// (double-free; refs #55, examples/lit_pat_ctor_tail_drop.ail). fn build_chain(&mut self, s_var: &Term, arms: &[Arm], default: &Term) -> Term { if arms.is_empty() { return default.clone(); } let head = &arms[0]; + // Detect a maximal run of consecutive arms sharing the head + // arm's outer ctor + arity. A run of length ≥ 1 of ctor arms + // is lowered as a single bind-once ctor-match; everything else + // (Wild / Var / Lit head) falls through to the per-arm path. + if let Pattern::Ctor { ctor, fields } = &head.pat { + let arity = fields.len(); + let group_len = arms + .iter() + .take_while(|a| match &a.pat { + Pattern::Ctor { + ctor: c, + fields: f, + } => c == ctor && f.len() == arity, + _ => false, + }) + .count(); + let group = &arms[..group_len]; + let rest = &arms[group_len..]; + let rest_chain = self.build_chain(s_var, rest, default); + return self.build_ctor_group(s_var, ctor, arity, group, rest_chain); + } let rest = &arms[1..]; let fall_k = self.build_chain(s_var, rest, default); self.desugar_one_arm(s_var, head, fall_k) } + /// Lowers a run of consecutive arms that all match the same outer + /// ctor `ctor`/`arity` into a single [`Term::Match`] that binds the + /// ctor fields once. The bound field vars are threaded through each + /// arm's sub-patterns (via [`wrap_sub`](Self::wrap_sub)); the arms' + /// internal fall-throughs chain left-to-right, ending in + /// `rest_chain` (the arms after the group, which by construction + /// match a *different* ctor or are catch-alls). The single match's + /// wildcard arm also routes to `rest_chain`. No arm in the group + /// re-matches `s_var`, so the ctor's owned children live under + /// exactly one match scope. + fn build_ctor_group( + &mut self, + s_var: &Term, + ctor: &str, + arity: usize, + group: &[Arm], + rest_chain: Term, + ) -> Term { + let fresh_vars: Vec = (0..arity).map(|_| self.fresh()).collect(); + let flat_fields: Vec = fresh_vars + .iter() + .map(|n| Pattern::Var { name: n.clone() }) + .collect(); + // Build the group body inside-out: the last arm falls to + // `rest_chain`; each earlier arm falls to the next arm's body. + // Within an arm, fold the field sub-patterns right-to-left so + // the deepest field is matched first (matches the single-arm + // `desugar_one_arm` ordering). + let mut inner = rest_chain.clone(); + for arm in group.iter().rev() { + let sub_fields = match &arm.pat { + Pattern::Ctor { fields, .. } => fields, + // `build_chain` only calls this with ctor arms. + _ => unreachable!("build_ctor_group requires ctor arms"), + }; + let mut arm_body = arm.body.clone(); + for (sub, fv) in sub_fields.iter().zip(fresh_vars.iter()).rev() { + arm_body = self.wrap_sub(fv, sub, arm_body, &inner); + } + inner = arm_body; + } + Term::Match { + scrutinee: Box::new(s_var.clone()), + arms: vec![ + Arm { + pat: Pattern::Ctor { + ctor: ctor.to_string(), + fields: flat_fields, + }, + body: inner, + }, + Arm { + pat: Pattern::Wild, + body: rest_chain, + }, + ], + } + } + /// Lowers one arm into a term. Wild/Var arms drop the chain (the /// arm matches everything); Lit and Ctor arms emit a `Term::Match` /// with the desugared head pattern as the first arm and a @@ -1988,7 +2101,7 @@ mod tests { ret: Box::new(Type::int()), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, params: vec!["xs".into()], body: body_match, @@ -2046,7 +2159,7 @@ mod tests { ret: Box::new(Type::int()), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, params: vec!["xs".into()], body: original.clone(), @@ -2138,7 +2251,7 @@ mod tests { ret: Box::new(Type::int()), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, params: vec!["n".into()], body: Box::new(Term::Var { name: "n".into() }), @@ -2164,7 +2277,7 @@ mod tests { ret: Box::new(Type::int()), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, params: vec![], body: fact_letrec_term(), @@ -2228,7 +2341,7 @@ mod tests { ret: Box::new(Type::int()), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, params: vec!["n".into()], // (let-rec helper (params x) (type Int -> Int) @@ -2241,7 +2354,7 @@ mod tests { ret: Box::new(Type::int()), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, params: vec!["x".into()], body: Box::new(Term::App { @@ -2278,7 +2391,7 @@ mod tests { ret: Box::new(Type::int()), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, "lifted fn type should have capture appended; got {:?}", lifted.ty @@ -2348,7 +2461,7 @@ mod tests { ret: Box::new(Type::int()), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, params: vec!["x".into()], body: Box::new(Term::App { @@ -2377,7 +2490,7 @@ mod tests { ret: Box::new(Type::int()), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, params: vec![], body: Term::Let { @@ -2451,7 +2564,7 @@ mod tests { ret: Box::new(Type::int()), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, params: vec!["z".into()], body: Box::new(Term::App { @@ -2498,7 +2611,7 @@ mod tests { ret: Box::new(Type::int()), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, params: vec!["p".into()], body: outer_body, @@ -2548,7 +2661,7 @@ mod tests { ret: Box::new(Type::int()), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, params: vec!["x".into()], body: Box::new(Term::Let { @@ -2578,7 +2691,7 @@ mod tests { ret: Box::new(Type::int()), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, params: vec![], body: letrec, @@ -2611,7 +2724,7 @@ mod tests { ret: Box::new(Type::int()), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, params: vec!["x".into()], body: Box::new(Term::App { @@ -2641,7 +2754,7 @@ mod tests { ret: Box::new(Type::int()), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, params: vec![], body: letrec, @@ -2733,7 +2846,7 @@ mod tests { ret: Box::new(Type::Var { name: "a".into() }), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, params: vec!["k".into()], // 2026-05-21 operator-routing-eq-ord: keep `==` here (vs @@ -2786,7 +2899,7 @@ mod tests { ret: Box::new(Type::Var { name: "a".into() }), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }), }, params: vec!["x".into()], @@ -2862,7 +2975,7 @@ mod tests { ret: Box::new(Type::Var { name: "a".into() }), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, params: vec!["k".into()], body: Box::new(Term::Var { name: "x".into() }), @@ -2891,7 +3004,7 @@ mod tests { ret: Box::new(Type::Var { name: "a".into() }), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }), }, params: vec!["x".into()], @@ -2932,7 +3045,7 @@ mod tests { ret: Box::new(Type::int()), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, params: vec!["j".into()], body: Box::new(Term::App { @@ -2953,7 +3066,7 @@ mod tests { ret: Box::new(Type::int()), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, params: vec!["i".into()], body: Box::new(inner), @@ -2975,7 +3088,7 @@ mod tests { ret: Box::new(Type::int()), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, params: vec![], body: outer, @@ -3011,7 +3124,7 @@ mod tests { ret: Box::new(Type::int()), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, params: vec!["j".into()], body: Box::new(Term::App { @@ -3035,7 +3148,7 @@ mod tests { ret: Box::new(Type::int()), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, params: vec!["i".into()], body: Box::new(inner), @@ -3057,7 +3170,7 @@ mod tests { ret: Box::new(Type::int()), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, params: vec![], body: outer, @@ -3189,7 +3302,7 @@ mod tests { ret: Box::new(Type::int()), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, params: vec!["n".into()], body: body_match, @@ -3207,15 +3320,15 @@ mod tests { !any_lit_pattern(body), "desugarer must remove every Pattern::Lit from the term tree; got: {body:#?}" ); - // The desugar wraps the match in `let $mp_N = scrutinee in `, - // and the chain head must be a `Term::If` (the lit arm). - let chain = match body { - Term::Let { body, .. } => body.as_ref(), - other => panic!("expected outer Let from chain machinery, got {other:?}"), - }; + // The scrutinee is a bare `Term::Var` (the param `n`), so the + // chain machinery reuses the binder directly without a spurious + // `let $mp_N = n` rebind (the rebind would bump the param's + // `consume_count` and break the fn-return husk-free gate; refs + // #55). The chain head is therefore the `Term::If` (the lit arm) + // sitting directly at the function body. assert!( - matches!(chain, Term::If { .. }), - "expected Term::If at the chain head, got {chain:?}" + matches!(body, Term::If { .. }), + "expected Term::If at the chain head, got {body:?}" ); } @@ -3264,7 +3377,7 @@ mod tests { ret: Box::new(Type::int()), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, params: vec!["xs".into()], body: body_match, @@ -3347,7 +3460,7 @@ mod tests { ret: Box::new(Type::int()), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, params: vec!["n".into()], body: body_match, @@ -3361,13 +3474,11 @@ mod tests { Def::Fn(f) => &f.body, _ => unreachable!(), }; - // Outer is `let $mp_N = scrutinee in `. The chain is - // `if (== sv 0) then 100 else if (== sv 1) then 200 else __unreachable__`. - let chain = match body { - Term::Let { body, .. } => body.as_ref(), - other => panic!("expected outer Let from chain machinery, got {other:?}"), - }; - let inner_else = match chain { + // The scrutinee is a bare `Term::Var` (the param `n`), so there + // is no `let $mp_N = n` rebind (it would break the fn-return + // husk-free gate; refs #55). The body IS the chain directly: + // `if (== n 0) then 100 else if (== n 1) then 200 else __unreachable__`. + let inner_else = match body { Term::If { else_, .. } => else_.as_ref(), other => panic!("expected outer If, got {other:?}"), }; diff --git a/crates/ailang-core/src/pretty.rs b/crates/ailang-core/src/pretty.rs index c44195c..d911fd5 100644 --- a/crates/ailang-core/src/pretty.rs +++ b/crates/ailang-core/src/pretty.rs @@ -179,7 +179,7 @@ mod tests { ret: Box::new(Type::int()), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, params: vec!["a".into(), "b".into()], body: Term::App { diff --git a/crates/ailang-core/src/workspace.rs b/crates/ailang-core/src/workspace.rs index e33832a..906eff0 100644 --- a/crates/ailang-core/src/workspace.rs +++ b/crates/ailang-core/src/workspace.rs @@ -1811,7 +1811,7 @@ mod tests { "defs": [{ "kind": "fn", "name": "f", - "type": { "k": "fn", "params": [], "ret": { "k": "con", "name": type_con }, "effects": [] }, + "type": { "k": "fn", "params": [], "param_modes": [], "ret": { "k": "con", "name": type_con }, "ret_mode": "own", "effects": [] }, "params": [], "body": { "t": "lit", "lit": { "kind": "unit" } } }], @@ -1829,7 +1829,7 @@ mod tests { "defs": [ { "kind": "type", "name": type_name, "ctors": [] }, { "kind": "fn", "name": "f", - "type": { "k": "fn", "params": [], "ret": { "k": "con", "name": type_name }, "effects": [] }, + "type": { "k": "fn", "params": [], "param_modes": [], "ret": { "k": "con", "name": type_name }, "ret_mode": "own", "effects": [] }, "params": [], "body": { "t": "lit", "lit": { "kind": "unit" } } } ], @@ -1847,7 +1847,7 @@ mod tests { "defs": [{ "kind": "fn", "name": "f", - "type": { "k": "fn", "params": [], "ret": { "k": "con", "name": type_con }, "effects": [] }, + "type": { "k": "fn", "params": [], "param_modes": [], "ret": { "k": "con", "name": type_con }, "ret_mode": "own", "effects": [] }, "params": [], "body": { "t": "lit", "lit": { "kind": "unit" } } }], @@ -1982,7 +1982,7 @@ mod tests { "defs": [{ "kind": "fn", "name": "f", - "type": { "k": "fn", "params": [], "ret": { "k": "con", "name": "Unit" }, "effects": [] }, + "type": { "k": "fn", "params": [], "param_modes": [], "ret": { "k": "con", "name": "Unit" }, "ret_mode": "own", "effects": [] }, "params": [], "body": { "t": "lam", @@ -2026,7 +2026,7 @@ mod tests { "defs": [{ "kind": "fn", "name": "f", - "type": { "k": "fn", "params": [], "ret": { "k": "con", "name": "Unit" }, "effects": [] }, + "type": { "k": "fn", "params": [], "param_modes": [], "ret": { "k": "con", "name": "Unit" }, "ret_mode": "own", "effects": [] }, "params": [], "body": { "t": "ctor", @@ -2173,8 +2173,8 @@ mod tests { { "class": "other.MyEq", "type": { "k": "var", "name": "a" } } ], "body": { - "k": "fn", "params": [{ "k": "var", "name": "a" }], - "ret": { "k": "con", "name": "Unit" }, "effects": [] + "k": "fn", "params": [{ "k": "var", "name": "a" }], "param_modes": ["own"], + "ret": { "k": "con", "name": "Unit" }, "ret_mode": "own", "effects": [] } }, "params": ["x"], @@ -2226,8 +2226,8 @@ mod tests { "methods": [ { "name": "tshow", "type": { "k": "fn", - "params": [{ "k": "var", "name": "a" }], - "ret": { "k": "con", "name": "Str" }, + "params": [{ "k": "var", "name": "a" }], "param_modes": ["own"], + "ret": { "k": "con", "name": "Str" }, "ret_mode": "own", "effects": [] } } ] }, @@ -2326,9 +2326,9 @@ mod tests { }], body: Box::new(Type::Fn { params: vec![Type::Var { name: "a".to_string() }], - param_modes: vec![], + param_modes: vec![crate::ast::ParamMode::Own], ret: Box::new(Type::Var { name: "a".to_string() }), - ret_mode: Default::default(), + ret_mode: crate::ast::ParamMode::Own, effects: vec![], }), }; @@ -2392,8 +2392,8 @@ mod tests { ], "body": { "k": "fn", - "params": [{ "k": "var", "name": "b" }], - "ret": { "k": "con", "name": "Unit" }, + "params": [{ "k": "var", "name": "b" }], "param_modes": ["own"], + "ret": { "k": "con", "name": "Unit" }, "ret_mode": "own", "effects": [] } } @@ -2597,8 +2597,8 @@ mod tests { ], "body": { "k": "fn", - "params": [{ "k": "var", "name": "a" }], - "ret": { "k": "con", "name": "Unit" }, + "params": [{ "k": "var", "name": "a" }], "param_modes": ["own"], + "ret": { "k": "con", "name": "Unit" }, "ret_mode": "own", "effects": [] } }, diff --git a/crates/ailang-core/tests/design_index_pin.rs b/crates/ailang-core/tests/design_index_pin.rs index a39ca39..b6b6bdd 100644 --- a/crates/ailang-core/tests/design_index_pin.rs +++ b/crates/ailang-core/tests/design_index_pin.rs @@ -119,14 +119,42 @@ fn every_contract_names_a_resolvable_ratifying_test() { for row in &contracts { // columns: id | consumer/lifetime | ratifying-test | link let rt = &row[2]; - // take the path token (before any " (" note) - let path = rt.split(" (").next().unwrap_or(rt).trim(); + // strip a trailing " (...)" note, then resolve every + // " + "-separated path segment. A dual ratifier such as + // "uniqueness.rs + linearity.rs (in-source mod tests)" names + // two real files — both must resolve, mirroring the + // dual-link handling in `link_target_exists` (clause-1). + // The second segment is a bare leafname relative to the + // first segment's directory. + let body = rt.split(" (").next().unwrap_or(rt).trim(); + let segments: Vec<&str> = body.split(" + ").map(str::trim).collect(); + let first = segments[0]; assert!( - root().join(path).exists(), + root().join(first).exists(), "ratifying-test does not resolve to a real file: {:?} (contract {:?})", - path, + first, row[0] ); + let base_dir = std::path::Path::new(first) + .parent() + .map(|p| p.to_path_buf()) + .unwrap_or_default(); + for seg in &segments[1..] { + // a later segment may be a full repo-relative path or a + // bare leafname rooted at the first segment's directory. + let resolved = if root().join(seg).exists() { + root().join(seg) + } else { + root().join(base_dir.join(seg)) + }; + assert!( + resolved.exists(), + "ratifying-test dual segment does not resolve to a real \ + file: {:?} (contract {:?})", + seg, + row[0] + ); + } } } diff --git a/crates/ailang-core/tests/design_schema_drift.rs b/crates/ailang-core/tests/design_schema_drift.rs index 7af95b7..4eb687f 100644 --- a/crates/ailang-core/tests/design_schema_drift.rs +++ b/crates/ailang-core/tests/design_schema_drift.rs @@ -82,7 +82,7 @@ fn design_md_anchors_every_term_variant() { r#""t": "letrec""#, Term::LetRec { name: "f".into(), - ty: Type::fn_implicit(vec![], Type::int(), vec![]), + ty: Type::fn_owned(vec![], Type::int(), vec![]), params: vec![], body: Box::new(Term::Lit { lit: Literal::Int { value: 0 } }), in_term: Box::new(Term::Var { name: "f".into() }), @@ -248,7 +248,7 @@ fn design_md_anchors_every_pattern_variant() { fn design_md_anchors_every_type_variant() { let exemplars: Vec<(&str, Type)> = vec![ (r#""k": "con""#, Type::int()), - (r#""k": "fn""#, Type::fn_implicit(vec![], Type::unit(), vec![])), + (r#""k": "fn""#, Type::fn_owned(vec![], Type::unit(), vec![])), (r#""k": "var""#, Type::Var { name: "a".into() }), ( r#""k": "forall""#, @@ -313,7 +313,7 @@ fn design_md_anchors_every_def_kind() { name: "f".into(), doc: None, suppress: vec![], - ty: Type::fn_implicit(vec![], Type::int(), vec![]), + ty: Type::fn_owned(vec![], Type::int(), vec![]), params: vec![], body: Term::Lit { lit: Literal::Int { value: 0 } }, export: None, @@ -338,7 +338,7 @@ fn design_md_anchors_every_def_kind() { superclass: None, methods: vec![ClassMethod { name: "show".into(), - ty: Type::fn_implicit(vec![Type::Var { name: "a".into() }], Type::str_(), vec![]), + ty: Type::fn_owned(vec![Type::Var { name: "a".into() }], Type::str_(), vec![]), default: None, }], doc: None, @@ -378,18 +378,16 @@ fn design_md_anchors_every_def_kind() { /// Every `ParamMode` variant must have its serialized string form present /// in design/contracts/0002-data-model.md. The mode annotations are load-bearing for -/// ownership checking; an LLM author must know all three forms. +/// ownership checking; an LLM author must know both forms. #[test] fn design_md_anchors_every_parammode_variant() { let exemplars: Vec<(&str, ParamMode)> = vec![ - (r#""implicit""#, ParamMode::Implicit), (r#""own""#, ParamMode::Own), (r#""borrow""#, ParamMode::Borrow), ]; for (anchor, mode) in exemplars { let _: &'static str = match mode { - ParamMode::Implicit => "implicit", ParamMode::Own => "own", ParamMode::Borrow => "borrow", }; @@ -525,7 +523,7 @@ fn design_md_anchors_nested_struct_keys() { let _ = Suppress { code: "x".into(), because: "y".into() }; let _ = ClassMethod { name: "m".into(), - ty: Type::fn_implicit(vec![], Type::int(), vec![]), + ty: Type::fn_owned(vec![], Type::int(), vec![]), default: None, }; let _ = InstanceMethod { diff --git a/crates/ailang-core/tests/embed_export_hash_stable.rs b/crates/ailang-core/tests/embed_export_hash_stable.rs index 69265bc..f53918f 100644 --- a/crates/ailang-core/tests/embed_export_hash_stable.rs +++ b/crates/ailang-core/tests/embed_export_hash_stable.rs @@ -21,7 +21,7 @@ fn fn_without_export_hash_is_unchanged() { // equal the value captured before the field existed. let def = Def::Fn(FnDef { name: "f".into(), - ty: Type::fn_implicit(vec![Type::int()], Type::int(), vec![]), + ty: Type::fn_owned(vec![Type::int()], Type::int(), vec![]), params: vec!["x".into()], body: Term::Var { name: "x".into() }, doc: None, @@ -29,7 +29,7 @@ fn fn_without_export_hash_is_unchanged() { export: None, }); // GOLDEN: captured pre-field (Step 2) from `def_hash` on this shape. - let golden = "b4662aa70839f60b"; + let golden = "8ce080ee897b3f80"; assert_eq!(def_hash(&def), golden, "pre-M1 fn hash drifted — additive-field invariant violated"); } diff --git a/crates/ailang-core/tests/hash_pin.rs b/crates/ailang-core/tests/hash_pin.rs index 14b4e31..e609c14 100644 --- a/crates/ailang-core/tests/hash_pin.rs +++ b/crates/ailang-core/tests/hash_pin.rs @@ -31,8 +31,8 @@ fn sample_fn() -> Def { params: vec![Type::int(), Type::int()], ret: Box::new(Type::int()), effects: vec![], - param_modes: vec![], - ret_mode: ParamMode::Implicit, + param_modes: vec![ParamMode::Own, ParamMode::Own], + ret_mode: ParamMode::Own, }, params: vec!["a".into(), "b".into()], body: Term::App { @@ -86,7 +86,7 @@ fn iter13a_schema_extension_preserves_pre_13a_hashes() { let sum_mod = ailang_surface::load_module(&examples.join("sum.ail")) .expect("examples/sum.ail loads"); let sum_def = sum_mod.defs.iter().find(|d| d.name() == "sum").unwrap(); - assert_eq!(def_hash(sum_def), "25343a2e5927a257"); + assert_eq!(def_hash(sum_def), "19920ec4123d35d6"); let list_mod = ailang_surface::load_module(&examples.join("list.ail")) .expect("examples/list.ail loads"); @@ -108,7 +108,7 @@ fn loop_recur_schema_extension_preserves_pre_loop_recur_hashes() { let sum_mod = ailang_surface::load_module(&examples.join("sum.ail")) .expect("examples/sum.ail loads"); let sum_def = sum_mod.defs.iter().find(|d| d.name() == "sum").unwrap(); - assert_eq!(def_hash(sum_def), "25343a2e5927a257"); + assert_eq!(def_hash(sum_def), "19920ec4123d35d6"); let list_mod = ailang_surface::load_module(&examples.join("list.ail")) .expect("examples/list.ail loads"); @@ -162,7 +162,7 @@ fn iter19b_schema_extension_preserves_pre_19b_hashes() { let sum_mod = ailang_surface::load_module(&examples.join("sum.ail")) .expect("examples/sum.ail loads"); let sum_def = sum_mod.defs.iter().find(|d| d.name() == "sum").unwrap(); - assert_eq!(def_hash(sum_def), "25343a2e5927a257"); + assert_eq!(def_hash(sum_def), "19920ec4123d35d6"); } /// adding `Def::Class` and `Def::Instance` must @@ -177,7 +177,7 @@ fn iter22b1_schema_extension_preserves_pre_22b_hashes() { let sum_mod = ailang_surface::load_module(&examples.join("sum.ail")) .expect("examples/sum.ail loads"); let sum_def = sum_mod.defs.iter().find(|d| d.name() == "sum").unwrap(); - assert_eq!(def_hash(sum_def), "25343a2e5927a257"); + assert_eq!(def_hash(sum_def), "19920ec4123d35d6"); let list_mod = ailang_surface::load_module(&examples.join("list.ail")) .expect("examples/list.ail loads"); @@ -220,7 +220,7 @@ fn forall_without_constraints_hashes_bit_identical_to_pre_22b2() { let t = Type::Forall { vars: vec!["a".into()], constraints: vec![], - body: Box::new(Type::fn_implicit( + body: Box::new(Type::fn_owned( vec![Type::Var { name: "a".into() }], Type::Var { name: "a".into() }, vec![], @@ -254,7 +254,7 @@ fn ct4_migrated_fixtures_have_canonical_form_hashes() { // io/print_str no longer adds a trailing newline. assert_eq!( def_hash(main_def), - "8ed47b4062ce00f5", + "602d7a6d6ba72bc4", "ordering_match::main canonical hash must match captured post-fputs-swap value" ); @@ -272,7 +272,7 @@ fn ct4_migrated_fixtures_have_canonical_form_hashes() { assert_eq!(dup_classmod_mod.defs.len(), 1, "test_22b1_dup_classmod expected to have exactly 1 def (class TShow)"); assert_eq!( def_hash(&dup_classmod_mod.defs[0]), - "b8bca96c2d09ed93", + "4ca71c7d8212c96a", "test_22b1_dup_classmod class TShow canonical hash; post-24.2 captured value" ); } @@ -286,7 +286,7 @@ fn ct4_unmigrated_fixtures_remain_bit_identical() { let sum_mod = ailang_surface::load_module(&examples.join("sum.ail")) .expect("examples/sum.ail loads"); let sum_def = sum_mod.defs.iter().find(|d| d.name() == "sum").unwrap(); - assert_eq!(def_hash(sum_def), "25343a2e5927a257", + assert_eq!(def_hash(sum_def), "19920ec4123d35d6", "sum.sum hash drifted across canonical-form tightening — unexpected"); let list_mod = ailang_surface::load_module(&examples.join("list.ail")) diff --git a/crates/ailang-core/tests/schema_coverage.rs b/crates/ailang-core/tests/schema_coverage.rs index 6ac314d..8d45266 100644 --- a/crates/ailang-core/tests/schema_coverage.rs +++ b/crates/ailang-core/tests/schema_coverage.rs @@ -67,7 +67,6 @@ enum VariantTag { TypeVar, TypeForall, // ParamMode - ParamModeImplicit, ParamModeOwn, ParamModeBorrow, } @@ -111,7 +110,6 @@ const EXPECTED_VARIANTS: &[VariantTag] = &[ VariantTag::TypeFn, VariantTag::TypeVar, VariantTag::TypeForall, - VariantTag::ParamModeImplicit, VariantTag::ParamModeOwn, VariantTag::ParamModeBorrow, ]; @@ -341,9 +339,6 @@ fn visit_type(t: &Type, observed: &mut HashSet) { fn visit_param_mode(m: &ParamMode, observed: &mut HashSet) { match m { - ParamMode::Implicit => { - observed.insert(VariantTag::ParamModeImplicit); - } ParamMode::Own => { observed.insert(VariantTag::ParamModeOwn); } @@ -364,6 +359,17 @@ fn examples_dir() -> PathBuf { crate_dir.parent().unwrap().parent().unwrap().join("examples") } +/// Fixtures that intentionally do NOT parse — the `#55` cutover +/// reject corpus. Negative fixtures whose whole point is that the +/// parser rejects them, so they have no AST to scan for variant +/// coverage and the scan must skip them. Mirrors the same list in +/// `crates/ail/tests/roundtrip_cli.rs`. +/// +/// - `bare_slot_reject.ail`: a bare fn-type slot with no mode. The +/// binary-ParamMode parser rejects it ("fn-type slot requires a +/// mode: write (own T) or (borrow T)"). +const NON_PARSEABLE_FIXTURES: &[&str] = &["bare_slot_reject.ail"]; + fn list_ail_fixtures() -> Vec { let dir = examples_dir(); let mut paths: Vec = std::fs::read_dir(&dir) @@ -373,7 +379,7 @@ fn list_ail_fixtures() -> Vec { .filter(|p| { p.file_name() .and_then(|n| n.to_str()) - .map(|n| n.ends_with(".ail")) + .map(|n| n.ends_with(".ail") && !NON_PARSEABLE_FIXTURES.contains(&n)) .unwrap_or(false) }) .collect(); diff --git a/crates/ailang-core/tests/spec_drift.rs b/crates/ailang-core/tests/spec_drift.rs index fbe0085..fb700ab 100644 --- a/crates/ailang-core/tests/spec_drift.rs +++ b/crates/ailang-core/tests/spec_drift.rs @@ -49,7 +49,7 @@ fn spec_mentions_every_term_variant() { "(let-rec", Term::LetRec { name: "f".into(), - ty: Type::fn_implicit(vec![], Type::int(), vec![]), + ty: Type::fn_owned(vec![], Type::int(), vec![]), params: vec![], body: Box::new(Term::Lit { lit: Literal::Int { value: 0 } }), in_term: Box::new(Term::Var { name: "f".into() }), @@ -207,7 +207,7 @@ fn spec_mentions_every_type_variant() { ("(con ", Type::int()), ( "(fn-type", - Type::fn_implicit(vec![], Type::unit(), vec![]), + Type::fn_owned(vec![], Type::unit(), vec![]), ), ( "TYVAR-NAME", @@ -271,7 +271,7 @@ fn spec_mentions_every_def_kind() { name: "f".into(), doc: None, suppress: vec![], - ty: Type::fn_implicit(vec![], Type::int(), vec![]), + ty: Type::fn_owned(vec![], Type::int(), vec![]), params: vec![], body: Term::Lit { lit: Literal::Int { value: 0 } }, export: None, diff --git a/crates/ailang-core/tests/workspace_kernel.rs b/crates/ailang-core/tests/workspace_kernel.rs index 7dfadfd..2262f17 100644 --- a/crates/ailang-core/tests/workspace_kernel.rs +++ b/crates/ailang-core/tests/workspace_kernel.rs @@ -48,7 +48,7 @@ fn kernel_tier_module_auto_imports_without_explicit_import() { "(module bridge\n", " (import k_mod)\n", " (fn anchor\n", - " (type (forall (vars a) (fn-type (params (con KT a)) (ret (con Unit)))))\n", + " (type (forall (vars a) (fn-type (params (own (con KT a))) (ret (own (con Unit))))))\n", " (params k) (body unit)))\n", ), ); @@ -61,7 +61,7 @@ fn kernel_tier_module_auto_imports_without_explicit_import() { "(module consumer\n", " (import bridge)\n", " (fn use_kt\n", - " (type (forall (vars a) (fn-type (params (con KT a)) (ret (con Unit)))))\n", + " (type (forall (vars a) (fn-type (params (own (con KT a))) (ret (own (con Unit))))))\n", " (params k) (body unit)))\n", ), ); @@ -106,7 +106,7 @@ fn two_kernel_tier_modules_coload() { " (import k_a)\n", " (import k_b)\n", " (fn anchor\n", - " (type (fn-type (params (con A) (con B)) (ret (con Unit))))\n", + " (type (fn-type (params (own (con A)) (own (con B))) (ret (own (con Unit)))))\n", " (params a b) (body unit)))\n", ), ); @@ -117,7 +117,7 @@ fn two_kernel_tier_modules_coload() { "(module consumer\n", " (import bridge)\n", " (fn use_ab\n", - " (type (fn-type (params (con A) (con B)) (ret (con Unit))))\n", + " (type (fn-type (params (own (con A)) (own (con B))) (ret (own (con Unit)))))\n", " (params a b) (body unit)))\n", ), ); @@ -151,7 +151,7 @@ fn explicit_import_takes_precedence_over_auto_import() { "(module consumer\n", " (import k_mod)\n", " (fn use_kt\n", - " (type (fn-type (params (con KT)) (ret (con Unit))))\n", + " (type (fn-type (params (own (con KT))) (ret (own (con Unit)))))\n", " (params k) (body unit)))\n", ), ); diff --git a/crates/ailang-kernel/src/raw_buf/source.ail b/crates/ailang-kernel/src/raw_buf/source.ail index 61f37ea..98d9bf3 100644 --- a/crates/ailang-kernel/src/raw_buf/source.ail +++ b/crates/ailang-kernel/src/raw_buf/source.ail @@ -6,21 +6,21 @@ (param-in (a Int Float Bool))) (fn new (doc "Allocate an uninitialised RawBuf of capacity n. Compiler-supplied (intrinsic) body; codegen intercept RawBuf_new__ emits @ailang_rc_alloc plus the i64 size header. Element bytes are uninitialised; caller must set before get.") - (type (forall (vars a) (fn-type (params (con Int)) (ret (own (con RawBuf a)))))) + (type (forall (vars a) (fn-type (params (own (con Int))) (ret (own (con RawBuf a)))))) (params n) (intrinsic)) (fn get (doc "Indexed read. UB if i >= (size b); caller checks bounds via size. Compiler-supplied (intrinsic) body; codegen intercept RawBuf_get__ emits getelementptr plus load.") - (type (forall (vars a) (fn-type (params (borrow (con RawBuf a)) (con Int)) (ret a)))) + (type (forall (vars a) (fn-type (params (borrow (con RawBuf a)) (own (con Int))) (ret (own a))))) (params b i) (intrinsic)) (fn set (doc "Indexed write. Linear: own in, own out. Under uniqueness in-place; under shared copy-on-write (Issue #22). Compiler-supplied (intrinsic) body; codegen intercept RawBuf_set__ emits getelementptr plus store.") - (type (forall (vars a) (fn-type (params (own (con RawBuf a)) (con Int) a) (ret (own (con RawBuf a)))))) + (type (forall (vars a) (fn-type (params (own (con RawBuf a)) (own (con Int)) (own a)) (ret (own (con RawBuf a)))))) (params b i v) (intrinsic)) (fn size (doc "Element count. Compiler-supplied (intrinsic) body; codegen intercept RawBuf_size__ emits a single i64 load from the slab header.") - (type (forall (vars a) (fn-type (params (borrow (con RawBuf a))) (ret (con Int))))) + (type (forall (vars a) (fn-type (params (borrow (con RawBuf a))) (ret (own (con Int)))))) (params b) (intrinsic))) diff --git a/crates/ailang-prose/src/lib.rs b/crates/ailang-prose/src/lib.rs index 2c3f19a..6ca2ca1 100644 --- a/crates/ailang-prose/src/lib.rs +++ b/crates/ailang-prose/src/lib.rs @@ -298,7 +298,7 @@ fn write_fn_def(out: &mut String, fd: &FnDef, level: usize, owning_module: &str) let mode = param_modes .get(i) .copied() - .unwrap_or(ParamMode::Implicit); + .unwrap_or(ParamMode::Own); write_mode_type(out, ty, mode, owning_module); } out.push_str(") -> "); @@ -384,7 +384,7 @@ fn write_class_method(out: &mut String, m: &ClassMethod, level: usize, owning_mo out.push_str(&format!("x{i}")); } out.push_str(": "); - let mode = param_modes.get(i).copied().unwrap_or(ParamMode::Implicit); + let mode = param_modes.get(i).copied().unwrap_or(ParamMode::Own); write_mode_type(out, p, mode, owning_module); } out.push_str(") -> "); @@ -453,7 +453,6 @@ fn write_instance_method(out: &mut String, m: &InstanceMethod, level: usize, own fn write_mode_type(out: &mut String, t: &Type, mode: ParamMode, owning_module: &str) { match mode { - ParamMode::Implicit => write_type(out, t, owning_module), ParamMode::Own => { out.push_str("own "); write_type(out, t, owning_module); @@ -505,7 +504,7 @@ fn write_type(out: &mut String, t: &Type, owning_module: &str) { let mode = param_modes .get(i) .copied() - .unwrap_or(ParamMode::Implicit); + .unwrap_or(ParamMode::Own); // In a bare `Type::Fn` outside a fn signature we have no // parameter names, so render `mode T` only (no `name:`). write_mode_type(out, p, mode, owning_module); @@ -738,7 +737,7 @@ fn write_term_prec(out: &mut String, t: &Term, level: usize, parent_prec: u8, ow let mode = param_modes .get(i) .copied() - .unwrap_or(ParamMode::Implicit); + .unwrap_or(ParamMode::Own); write_mode_type(out, pty, mode, owning_module); } out.push_str(") -> "); @@ -1749,14 +1748,14 @@ mod tests { #[test] fn type_fn_no_modes_no_effects() { - let t = Type::fn_implicit(vec![Type::int()], Type::int(), vec![]); - assert_eq!(render_type(&t), "(Int) -> Int"); + let t = Type::fn_owned(vec![Type::int()], Type::int(), vec![]); + assert_eq!(render_type(&t), "(own Int) -> own Int"); } #[test] fn type_fn_with_effects() { - let t = Type::fn_implicit(vec![], Type::unit(), vec!["IO".into()]); - assert_eq!(render_type(&t), "() -> Unit with IO"); + let t = Type::fn_owned(vec![], Type::unit(), vec!["IO".into()]); + assert_eq!(render_type(&t), "() -> own Unit with IO"); } #[test] @@ -1794,7 +1793,7 @@ mod tests { }], param_modes: vec![ParamMode::Own], ret: Box::new(Type::int()), - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, effects: vec![], }, params: vec!["xs".into()], @@ -1806,14 +1805,14 @@ mod tests { let mut out = String::new(); write_fn_def(&mut out, &fd, 0, ""); assert!(out.contains("xs: own IntList"), "got:\n{out}"); - assert!(out.contains(") -> Int "), "got:\n{out}"); + assert!(out.contains(") -> own Int "), "got:\n{out}"); } #[test] fn fn_def_with_effects_appends_with_clause() { let fd = FnDef { name: "main".into(), - ty: Type::fn_implicit(vec![], Type::unit(), vec!["IO".into()]), + ty: Type::fn_owned(vec![], Type::unit(), vec!["IO".into()]), params: vec![], body: Term::Lit { lit: Literal::Unit }, suppress: vec![], @@ -1822,7 +1821,7 @@ mod tests { }; let mut out = String::new(); write_fn_def(&mut out, &fd, 0, ""); - assert!(out.contains("() -> Unit with IO {"), "got:\n{out}"); + assert!(out.contains("() -> own Unit with IO {"), "got:\n{out}"); } /// a FnDef with a single `Suppress` entry renders the @@ -1841,7 +1840,7 @@ mod tests { }], param_modes: vec![ailang_core::ast::ParamMode::Own], ret: Box::new(Type::int()), - ret_mode: ailang_core::ast::ParamMode::Implicit, + ret_mode: ailang_core::ast::ParamMode::Own, effects: vec![], }, params: vec!["xs".into()], @@ -1866,7 +1865,7 @@ mod tests { assert_eq!(lines[1], "/// Take ownership.", "doc line should follow; got:\n{out}"); // Fn signature follows the doc. assert!( - out.contains("fn head_or_zero(xs: own IntList) -> Int"), + out.contains("fn head_or_zero(xs: own IntList) -> own Int"), "fn signature should follow; got:\n{out}" ); } @@ -1878,7 +1877,7 @@ mod tests { fn fn_def_renders_multiple_suppress_in_order() { let fd = FnDef { name: "f".into(), - ty: Type::fn_implicit(vec![], Type::int(), vec![]), + ty: Type::fn_owned(vec![], Type::int(), vec![]), params: vec![], body: Term::Lit { lit: Literal::Int { value: 0 } }, suppress: vec![ @@ -1910,7 +1909,7 @@ mod tests { fn fn_def_with_empty_suppress_emits_no_suppress_lines() { let fd = FnDef { name: "f".into(), - ty: Type::fn_implicit(vec![], Type::int(), vec![]), + ty: Type::fn_owned(vec![], Type::int(), vec![]), params: vec![], body: Term::Lit { lit: Literal::Int { value: 0 } }, suppress: vec![], @@ -2345,7 +2344,7 @@ mod tests { }), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, params: vec![], body: Term::Ctor { @@ -2361,7 +2360,7 @@ mod tests { let prose = module_to_prose(&m); assert!( - prose.contains("-> Ordering"), + prose.contains("-> own Ordering"), "expected bare `Ordering` in fn return type (owner == file's \ module); got prose:\n{}", prose @@ -2394,7 +2393,7 @@ mod tests { }), effects: vec![], param_modes: vec![], - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, }, params: vec![], body: Term::Ctor { diff --git a/crates/ailang-surface/src/parse.rs b/crates/ailang-surface/src/parse.rs index cebe091..508d7cf 100644 --- a/crates/ailang-surface/src/parse.rs +++ b/crates/ailang-surface/src/parse.rs @@ -1177,26 +1177,18 @@ impl<'a> Parser<'a> { effects = self.parse_effects_clause()?; } self.expect_rparen("fn-type")?; - // If every entry is Implicit, store as `vec![]` so canonical - // JSON serialisation omits the field — preserves pre-18a - // hashes for any fixture that still uses bare types. - let stored_modes = if param_modes.iter().all(|m| matches!(m, ParamMode::Implicit)) { - Vec::new() - } else { - param_modes - }; Ok(Type::Fn { params, ret: Box::new(ret), effects, - param_modes: stored_modes, + param_modes, ret_mode, }) } - /// parse one fn-type slot — a type, optionally wrapped - /// in `(borrow T)` or `(own T)`. The mode is `Implicit` for a - /// bare type, `Borrow` for `(borrow T)`, `Own` for `(own T)`. + /// parse one fn-type slot — a type wrapped in `(borrow T)` or + /// `(own T)`. A bare type carries no mode and is rejected + /// (spec 0062): every slot must declare its ownership. fn parse_param_with_mode(&mut self) -> Result<(Type, ParamMode), ParseError> { if let Some(head) = self.peek_head_ident() { match head { @@ -1217,8 +1209,12 @@ impl<'a> Parser<'a> { _ => {} } } - let t = self.parse_type()?; - Ok((t, ParamMode::Implicit)) + let pos = self.peek().map(|t| t.span.start).unwrap_or(0); + Err(ParseError::Production { + production: "fn-type-slot", + message: "fn-type slot requires a mode: write (own T) or (borrow T)".into(), + pos, + }) } fn parse_effects_clause(&mut self) -> Result, ParseError> { @@ -1942,7 +1938,7 @@ mod tests { r#" (module hello (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (do io/print_str "Hello, AILang.")))) "#, @@ -1958,7 +1954,7 @@ mod tests { r#" (module m (fn id - (type (fn-type (params (con Int)) (ret (con Int)))) + (type (fn-type (params (own (con Int))) (ret (own (con Int))))) (params x) (body x))) "#, @@ -1969,9 +1965,8 @@ mod tests { /// `(borrow T)` and `(own T)` wrappers in fn-type /// param/ret slots round-trip into [`ParamMode::Borrow`] / - /// [`ParamMode::Own`] on `Type::Fn`. A bare type stays - /// [`ParamMode::Implicit`] (and its mode is elided from the - /// canonical form). + /// [`ParamMode::Own`] on `Type::Fn`. A bare slot is a parse + /// error (spec 0062): every slot carries an explicit mode. #[test] fn parses_borrow_and_own_modes_on_fn_type_slots() { let m = parse( @@ -1979,7 +1974,7 @@ mod tests { (module m (fn f (type (fn-type - (params (borrow (con Int)) (con Bool)) + (params (borrow (con Int)) (own (con Bool))) (ret (own (con Int))))) (params x y) (body x))) @@ -1994,8 +1989,8 @@ mod tests { Type::Fn { param_modes, ret_mode, .. } => { assert_eq!( param_modes, - &vec![ParamMode::Borrow, ParamMode::Implicit], - "first param parsed as `(borrow ...)`, second as bare" + &vec![ParamMode::Borrow, ParamMode::Own], + "first param parsed as `(borrow ...)`, second as `(own ...)`" ); assert_eq!(*ret_mode, ParamMode::Own, "ret parsed as `(own ...)`"); } @@ -2032,7 +2027,7 @@ mod tests { r#" (module m (fn id - (type (fn-type (params (con Int)) (ret (con Int)))) + (type (fn-type (params (own (con Int))) (ret (own (con Int))))) (params x) (body (clone x)))) "#, @@ -2059,7 +2054,7 @@ mod tests { r#" (module m (fn id - (type (fn-type (params (con Int)) (ret (con Int)))) + (type (fn-type (params (own (con Int))) (ret (own (con Int))))) (params x) (body (clone)))) "#, @@ -2116,7 +2111,7 @@ mod tests { r#" (module m (fn f - (type (fn-type (params (con Int)) (ret (con Int)))) + (type (fn-type (params (own (con Int))) (ret (own (con Int))))) (params x) (body (reuse-as)))) "#, @@ -2136,7 +2131,7 @@ mod tests { r#" (module m (fn f - (type (fn-type (params (con Int)) (ret (con Int)))) + (type (fn-type (params (own (con Int))) (ret (own (con Int))))) (params x) (body (reuse-as x)))) "#, @@ -2258,12 +2253,12 @@ mod tests { r#" (module m (fn main - (type (fn-type (params) (ret (con Int)))) + (type (fn-type (params) (ret (own (con Int))))) (params) (body (let-rec f (params x) - (type (fn-type (params (con Int)) (ret (con Int)))) + (type (fn-type (params (own (con Int))) (ret (own (con Int))))) (body x) (in (app f 1)))))) "#, @@ -2300,7 +2295,7 @@ mod tests { (module t (fn f (suppress (code "over-strict-mode") (because "test reason")) - (type (fn-type (params) (ret (con Int)))) + (type (fn-type (params) (ret (own (con Int))))) (params) (body 0))) "#, @@ -2327,7 +2322,7 @@ mod tests { (fn f (suppress (code "over-strict-mode") (because "first")) (suppress (code "other-code") (because "second")) - (type (fn-type (params) (ret (con Int)))) + (type (fn-type (params) (ret (own (con Int))))) (params) (body 0))) "#, @@ -2354,7 +2349,7 @@ mod tests { r#" (module t (fn f - (type (fn-type (params) (ret (con Int)))) + (type (fn-type (params) (ret (own (con Int))))) (params) (body 0))) "#, @@ -2381,7 +2376,7 @@ mod tests { (module t (fn f (suppress (code "over-strict-mode") (because "")) - (type (fn-type (params) (ret (con Int)))) + (type (fn-type (params) (ret (own (con Int))))) (params) (body 0))) "#, @@ -2406,7 +2401,7 @@ mod tests { (module t (fn f (suppress (code "over-strict-mode") (because "ok") (oops "x")) - (type (fn-type (params) (ret (con Int)))) + (type (fn-type (params) (ret (own (con Int))))) (params) (body 0))) "#, @@ -2425,7 +2420,7 @@ mod tests { (class Foo (param a) (method m - (type (fn-type (params (con a)) (ret (con Int)))))))"#; + (type (fn-type (params (own (con a))) (ret (own (con Int))))))))"#; let m = parse(src).expect("parse ok"); assert_eq!(m.defs.len(), 1, "one def"); match &m.defs[0] { @@ -2451,7 +2446,7 @@ mod tests { (superclass (class Foo) (type a)) (doc "bar extends foo") (method m - (type (fn-type (params (con a)) (ret (con Int)))) + (type (fn-type (params (own (con a))) (ret (own (con Int))))) (default 0))))"#; let m = parse(src).expect("parse ok"); let c = match &m.defs[0] { @@ -2517,7 +2512,7 @@ mod tests { (fn f (doc "first") (doc "second") - (type (fn-type (params) (ret (con Unit)))) + (type (fn-type (params) (ret (own (con Unit))))) (params) (body (do io/print_str "x")))) "#, @@ -2536,8 +2531,8 @@ mod tests { r#" (module m (fn f - (type (fn-type (params) (ret (con Unit)))) - (type (fn-type (params) (ret (con Unit)))) + (type (fn-type (params) (ret (own (con Unit))))) + (type (fn-type (params) (ret (own (con Unit))))) (params) (body (do io/print_str "x")))) "#, @@ -2556,7 +2551,7 @@ mod tests { r#" (module m (fn f - (type (fn-type (params) (ret (con Unit)))) + (type (fn-type (params) (ret (own (con Unit))))) (params) (params) (body (do io/print_str "x")))) @@ -2576,7 +2571,7 @@ mod tests { r#" (module m (fn f - (type (fn-type (params) (ret (con Unit)))) + (type (fn-type (params) (ret (own (con Unit))))) (params) (body (do io/print_str "x")) (body (do io/print_str "y")))) diff --git a/crates/ailang-surface/src/print.rs b/crates/ailang-surface/src/print.rs index 0c57fa9..473ef2c 100644 --- a/crates/ailang-surface/src/print.rs +++ b/crates/ailang-surface/src/print.rs @@ -351,13 +351,11 @@ fn write_instance_method(out: &mut String, m: &InstanceMethod, level: usize) { // ---- types ---------------------------------------------------------------- -/// print one fn-type param/ret slot, wrapping with -/// `(borrow ...)` or `(own ...)` when the slot has an explicit -/// mode. `Implicit` is printed bare so pre-18a fixtures round-trip -/// unchanged. +/// print one fn-type param/ret slot, always wrapping with +/// `(own ...)` or `(borrow ...)` — every slot carries an explicit +/// mode (spec 0062). fn write_fn_type_slot(out: &mut String, t: &Type, mode: ParamMode) { match mode { - ParamMode::Implicit => write_type(out, t), ParamMode::Own => { out.push_str("(own "); write_type(out, t); @@ -405,7 +403,7 @@ fn write_type(out: &mut String, t: &Type) { out.push_str("(fn-type (params"); for (i, p) in params.iter().enumerate() { out.push(' '); - let mode = param_modes.get(i).copied().unwrap_or(ParamMode::Implicit); + let mode = param_modes.get(i).copied().unwrap_or(ParamMode::Own); write_fn_type_slot(out, p, mode); } out.push_str(") (ret "); @@ -771,9 +769,9 @@ mod tests { name: "m".into(), ty: Type::Fn { params: vec![Type::Var { name: "a".into() }], - param_modes: vec![ParamMode::Implicit], + param_modes: vec![ParamMode::Own], ret: Box::new(Type::int()), - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, effects: vec![], }, default: None, @@ -802,9 +800,9 @@ mod tests { name: "m".into(), ty: Type::Fn { params: vec![Type::Var { name: "a".into() }], - param_modes: vec![ParamMode::Implicit], + param_modes: vec![ParamMode::Own], ret: Box::new(Type::int()), - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, effects: vec![], }, default: Some(Term::Lit { lit: Literal::Int { value: 0 } }), @@ -832,12 +830,12 @@ mod tests { }], body: Box::new(Type::Fn { params: vec![Type::Var { name: "a".into() }], - param_modes: vec![ParamMode::Implicit], + param_modes: vec![ParamMode::Own], ret: Box::new(Type::Con { name: "Str".into(), args: vec![], }), - ret_mode: ParamMode::Implicit, + ret_mode: ParamMode::Own, effects: vec![], }), }, diff --git a/crates/ailang-surface/tests/mut_removed_pin.rs b/crates/ailang-surface/tests/mut_removed_pin.rs index 7f63517..98fd699 100644 --- a/crates/ailang-surface/tests/mut_removed_pin.rs +++ b/crates/ailang-surface/tests/mut_removed_pin.rs @@ -5,14 +5,14 @@ use ailang_surface::parse; #[test] fn mut_keyword_is_rejected_by_form_a_parse() { - let src = "(module m (fn f (type (fn-type (params) (ret (con Int)))) (params) (body (mut (var x (con Int) 0) (assign x (app + x 1)) x))))"; + let src = "(module m (fn f (type (fn-type (params) (ret (own (con Int))))) (params) (body (mut (var x (con Int) 0) (assign x (app + x 1)) x))))"; let r = parse(src); assert!(r.is_err(), "mut keyword must no longer parse, got Ok"); } #[test] fn assign_keyword_is_rejected_by_form_a_parse() { - let src = "(module m (fn f (type (fn-type (params) (ret (con Int)))) (params) (body (assign x 1))))"; + let src = "(module m (fn f (type (fn-type (params) (ret (own (con Int))))) (params) (body (assign x 1))))"; assert!(parse(src).is_err(), "assign keyword must no longer parse, got Ok"); } diff --git a/crates/ailang-surface/tests/prelude_module_hash_pin.rs b/crates/ailang-surface/tests/prelude_module_hash_pin.rs index 8c3d0d3..c40479d 100644 --- a/crates/ailang-surface/tests/prelude_module_hash_pin.rs +++ b/crates/ailang-surface/tests/prelude_module_hash_pin.rs @@ -47,7 +47,7 @@ fn prelude_parse_yields_canonical_hash() { // the synthesised mono def-hashes do NOT (synthesise_mono_fn // sets doc: None). assert_eq!( - h, "b1373a2c69e70a3f", + h, "62764e07c1bc9bbb", "prelude module hash drifted; if intentional, capture the new \ hex below + record the why in the commit body." ); diff --git a/crates/ailang-surface/tests/reserved_dollar_pin.rs b/crates/ailang-surface/tests/reserved_dollar_pin.rs index 1cf6725..b6dbc2c 100644 --- a/crates/ailang-surface/tests/reserved_dollar_pin.rs +++ b/crates/ailang-surface/tests/reserved_dollar_pin.rs @@ -12,8 +12,8 @@ use ailang_surface::{parse, ParseError}; #[test] fn dollar_binder_in_module_is_rejected_at_parse() { - // (module bad (fn main (type (fn-type (params) (ret (con Int)))) (params) (body (let x$1 7 x$1)))) - let src = "(module bad\n (fn main\n (type (fn-type (params) (ret (con Int))))\n (params)\n (body\n (let x$1 7 x$1))))\n"; + // (module bad (fn main (type (fn-type (params) (ret (own (con Int))))) (params) (body (let x$1 7 x$1)))) + let src = "(module bad\n (fn main\n (type (fn-type (params) (ret (own (con Int)))))\n (params)\n (body\n (let x$1 7 x$1))))\n"; let err = parse(src).expect_err("authored `$` binder must be rejected at parse"); assert!( matches!(err, ParseError::Lex(LexError::ReservedDollar { ref token, .. }) if token == "x$1"), @@ -25,6 +25,6 @@ fn dollar_binder_in_module_is_rejected_at_parse() { fn dollar_in_string_in_module_parses_clean() { // The exemption survives at the `parse` level too: `$` inside a // string literal in a real module is fine. - let src = "(module dollar_ok\n (fn main\n (type (fn-type (params) (ret (con Str))))\n (params)\n (body \"price: $5\")))\n"; + let src = "(module dollar_ok\n (fn main\n (type (fn-type (params) (ret (own (con Str)))))\n (params)\n (body \"price: $5\")))\n"; parse(src).expect("string-internal `$` must parse clean"); } diff --git a/crates/ailang-surface/tests/round_trip.rs b/crates/ailang-surface/tests/round_trip.rs index 258afb5..9e71a2d 100644 --- a/crates/ailang-surface/tests/round_trip.rs +++ b/crates/ailang-surface/tests/round_trip.rs @@ -51,6 +51,17 @@ fn examples_dir() -> PathBuf { // only one form is hand-authored post-iter; counterparts are derived in-process // via `ail parse`. +/// Fixtures that intentionally do NOT parse — the `#55` cutover +/// reject corpus. Negative fixtures whose whole point is that the +/// parser rejects them, so the parse/round-trip properties below are +/// not meaningful for them and the corpus scan must skip them. +/// Mirrors the same list in `crates/ail/tests/roundtrip_cli.rs`. +/// +/// - `bare_slot_reject.ail`: a bare fn-type slot with no mode. The +/// binary-ParamMode parser rejects it ("fn-type slot requires a +/// mode: write (own T) or (borrow T)"). +const NON_PARSEABLE_FIXTURES: &[&str] = &["bare_slot_reject.ail"]; + fn list_ail_fixtures() -> Vec { let dir = examples_dir(); let mut paths: Vec = std::fs::read_dir(&dir) @@ -60,7 +71,7 @@ fn list_ail_fixtures() -> Vec { .filter(|p| { p.file_name() .and_then(|n| n.to_str()) - .map(|n| n.ends_with(".ail")) + .map(|n| n.ends_with(".ail") && !NON_PARSEABLE_FIXTURES.contains(&n)) .unwrap_or(false) }) .collect(); @@ -155,7 +166,7 @@ fn round_trip_term_new_mixed_args() { // (new Series (con Float) 3) — a Type-positional arg followed by // a Value-positional arg. The body lives inside an otherwise // minimal module shell that the printer emits in canonical form. - let form_a = "(module new_demo\n (data Series\n (ctor MkSeries (con Int)))\n (fn new\n (type (fn-type (params (con Int)) (ret (con Series))))\n (params n)\n (body (term-ctor Series MkSeries n)))\n (fn main\n (type (fn-type (params) (ret (con Series))))\n (params)\n (body (new Series (con Float) 3))))"; + let form_a = "(module new_demo\n (data Series\n (ctor MkSeries (con Int)))\n (fn new\n (type (fn-type (params (own (con Int))) (ret (own (con Series)))))\n (params n)\n (body (term-ctor Series MkSeries n)))\n (fn main\n (type (fn-type (params) (ret (own (con Series)))))\n (params)\n (body (new Series (con Float) 3))))"; let parsed_once = ailang_surface::parse(form_a) .expect("Form-A round-trip fixture must parse"); let printed = ailang_surface::print(&parsed_once); diff --git a/design/contracts/0002-data-model.md b/design/contracts/0002-data-model.md index 2e3083e..46cbd7b 100644 --- a/design/contracts/0002-data-model.md +++ b/design/contracts/0002-data-model.md @@ -269,9 +269,8 @@ metadata is defined and gated there as well. // Function type. paramModes/retMode are metadata on Type::Fn — // they are NOT separate Type variants, so every existing match-arm // in the typechecker (unify, occurs, apply) keeps working. -// `paramModes` omitted when every entry is "implicit"; `retMode` -// omitted when "implicit" (hash-stable when omitted). Full mode -// contract lives in contracts/0008-memory-model.md. +// `paramModes` and `retMode` are always present (one mode per slot). +// Full mode contract lives in contracts/0008-memory-model.md. { "k": "fn", "params": [Type...], "paramModes": [ParamMode...], @@ -294,17 +293,15 @@ metadata is defined and gated there as well. [memory model](0008-memory-model.md)): ``` -"implicit" — unannotated / back-compat. Treated as `own` by the typechecker. "own" — (own T) — caller transfers ownership; callee consumes. "borrow" — (borrow T) — caller retains ownership; callee may not consume. ``` -`implicit ≡ own` semantically; the distinction exists so existing -unannotated fixtures continue to serialize without the mode wrapper and keep their -canonical-JSON hash. The full mode contract (codegen consequences, -the over-strict-mode lint, the `Suppress` mechanism) lives in -[memory model](0008-memory-model.md); the four language-design -preconditions that make RC sound live in +Every fn-type slot carries `own` or `borrow`; ownership has no +default — there is no bare/unannotated mode. The full mode +contract (codegen consequences, the over-strict-mode lint, the +`Suppress` mechanism) lives in [memory model](0008-memory-model.md); +the four language-design preconditions that make RC sound live in [language constraints](0015-language-constraints.md). Ratified by: `crates/ailang-core/tests/design_schema_drift.rs`. diff --git a/design/contracts/0008-memory-model.md b/design/contracts/0008-memory-model.md index 1ea5129..22bdf46 100644 --- a/design/contracts/0008-memory-model.md +++ b/design/contracts/0008-memory-model.md @@ -54,14 +54,13 @@ schema). The substantive reasons for per-position metadata over a ordering that don't exist when modes live in a flat metadata vector. -`Implicit` is the legacy / back-compat state — semantically -equivalent to `Own` but printed bare (`(con T)`, no wrapper). -`Own` and `Borrow` are explicitly annotated. +`Own` and `Borrow` are the two modes; every fn-type slot carries +one explicitly. Ownership has no default — there is no +bare/unannotated mode. -JSON canonical hash for every existing fixture stays bit- -identical: `param_modes` is skipped when every entry is -`Implicit`, `ret_mode` is skipped when `Implicit`. Existing -modules emit the same bytes as before. +JSON canonical form: `param_modes` and `ret_mode` are always +present, one mode per slot, with no elision — the mode vectors +are never omitted from the canonical bytes. **Type::Con name scoping (canonical form).** Within a `.ail.json`, a `Type::Con.name` is interpreted relative to the @@ -189,6 +188,9 @@ fn-param `p` annotated `(own T)` when: never consumes `p` as a whole). 2. For every match arm whose scrutinee is `p`, no **heap-typed** pattern-binder has `consume_count > 0`. + 3. `p`'s type `T` is **not** a value type + (`Int`/`Bool`/`Float`/`Unit`). + 4. The enclosing fn's body is **not** `(intrinsic)`. The heap-type filter is load-bearing for soundness: `match xs { Cons(h, t) => h }` records `consume_count(h) == 1`, @@ -198,6 +200,22 @@ binders is what lets the lint correctly identify `head_or_zero` as over-strict (could be `borrow`) while staying silent on `sum_list` where `t: List` *is* moved out. +Conditions 3 and 4 keep the lint coherent under universal mode +activation: + +- **Value-typed params never fire.** `(borrow V)` for a value + type `V` is itself rejected by the `borrow-over-value` check, + so `(own V)` is the only legal mode for a value-typed param. + A suggestion to relax `(own Int)` to `(borrow Int)` would point + at an illegal rewrite, so the lint stays silent on value-typed + params regardless of whether they are consumed. +- **`(intrinsic)`-bodied fns never fire.** The linearity walk + does nothing for a `Term::Intrinsic` body, so every param of an + intrinsic has `consume_count == 0` — a guaranteed false positive. + An intrinsic's param modes are hand-authored contracts + (`new`/`get`/`set`/`float_*`), not lint-derivable from a walkable + body, so the whole fn is skipped. + Severity: `Warning`. `ail check`, `ail build`, `ail emit-ir` exit 1 only on at least one `Error`; warnings print but do not abort. @@ -246,10 +264,9 @@ metadata's role is explicit: fall-throughs to a `ret` (no tail-call), every parameter with `param_modes[i] == Own` is dec'd before the `ret` iff its uniqueness `consume_count == 0` and the ret value is not the - param itself. `Borrow` and `Implicit` parameters are skipped: - `Borrow` retains the caller's ownership by contract; - `Implicit` carries no static caller-handed-off-ownership - signal (it's the back-compat lane). + param itself. `Borrow` parameters are skipped: `Borrow` retains + the caller's ownership by contract. (There is no `Implicit` + parameter any longer — every param is `Own` or `Borrow`.) - **Iter A: arm-close pattern-binder dec.** When a match-arm's body terminates without a tail-call, every ptr-typed @@ -257,8 +274,8 @@ metadata's role is explicit: iff its `consume_count == 0` and it is not the arm's tail value, **gated on the scrutinee's static ownership**. If the scrutinee is a fn-param, only `Own`-mode scrutinees enable - the dec — `Borrow` and `Implicit` scrutinees would let the - arm dec memory the caller still references. + the dec — a `Borrow` scrutinee would let the arm dec memory + the caller still references. - **Pre-tail-call shallow-dec.** When a match-arm's body IS a tail call, both Iter A and Iter B are skipped (the @@ -292,10 +309,12 @@ suppressing or doubling a drop. Ratified by trackable for scope-close drop iff the callee's `ret_mode == Own`. The signal is the callee's static contract that ownership of the freshly heap-allocated cell - flows to the caller. `Borrow`-returning calls remain - non-trackable (the callee retains ownership; the caller - holds a view, not an own ref). `Implicit`-returning calls - remain non-trackable (back-compat lane). + flows to the caller. Every `Term::App` callee now carries an + explicit `Own`/`Borrow` `ret_mode`; an `Own`-returning call is + trackable for scope-close drop, a `Borrow`-returning call is + not (the callee retains ownership; the caller holds a view, + not an own ref — and a borrow-return is in any case rejected + at the signature, spec 0062). The drop fn's symbol resolution for an Own-returning App: synthesise the call's return type, resolve `Type::Con { name }` diff --git a/examples/bare_slot_reject.ail b/examples/bare_slot_reject.ail new file mode 100644 index 0000000..15ef0c4 --- /dev/null +++ b/examples/bare_slot_reject.ail @@ -0,0 +1,10 @@ +(module bare_slot_reject + + (fn id + (doc "MUST FAIL post-cutover: bare `(con Int)` slot carries no mode.") + (type + (fn-type + (params (con Int)) + (ret (con Int)))) + (params x) + (body x))) diff --git a/examples/bench_closure_chain.ail b/examples/bench_closure_chain.ail index b51a177..c97e3e8 100644 --- a/examples/bench_closure_chain.ail +++ b/examples/bench_closure_chain.ail @@ -28,9 +28,9 @@ (doc "Higher-order: apply f three times to seed.") (type (fn-type - (params (fn-type (params (con Int)) (ret (con Int))) - (con Int)) - (ret (con Int)))) + (params (own (fn-type (params (own (con Int))) (ret (own (con Int))))) + (own (con Int))) + (ret (own (con Int))))) (params f seed) (body (app f (app f (app f seed))))) @@ -39,15 +39,15 @@ (doc "For each i in [n-1, n-2, ..., 0], build a closure capturing i, pass to apply_thrice, accumulate. Tail-recursive on i and acc.") (type (fn-type - (params (con Int) (con Int)) - (ret (con Int)))) + (params (own (con Int)) (own (con Int))) + (ret (own (con Int))))) (params i acc) (body (if (app lt i 0) acc (let-rec helper (params x) - (type (fn-type (params (con Int)) (ret (con Int)))) + (type (fn-type (params (own (con Int))) (ret (own (con Int))))) (body i) (in (let r (app apply_thrice helper i) @@ -55,12 +55,12 @@ (fn run (doc "Drive run_loop from i=n-1 down to 0.") - (type (fn-type (params (con Int)) (ret (con Int)))) + (type (fn-type (params (own (con Int))) (ret (own (con Int))))) (params n) (body (app run_loop (app - n 1) 0))) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (seq (app print (app run 10000)) diff --git a/examples/bench_compute_collatz.ail b/examples/bench_compute_collatz.ail index 1b1b6bf..cf8d283 100644 --- a/examples/bench_compute_collatz.ail +++ b/examples/bench_compute_collatz.ail @@ -27,8 +27,8 @@ (doc "Tail-recursive: count Collatz steps from n to 1, accumulating in acc.") (type (fn-type - (params (con Int) (con Int)) - (ret (con Int)))) + (params (own (con Int)) (own (con Int))) + (ret (own (con Int))))) (params n acc) (body (if (app eq n 1) @@ -41,8 +41,8 @@ (doc "Tail-recursive: sum collatz_steps(i) for i in [n, n-1, ..., 1].") (type (fn-type - (params (con Int) (con Int)) - (ret (con Int)))) + (params (own (con Int)) (own (con Int))) + (ret (own (con Int))))) (params i total) (body (if (app eq i 0) @@ -52,12 +52,12 @@ (app + total (app collatz_steps i 0)))))) (fn run_one - (type (fn-type (params (con Int)) (ret (con Unit)) (effects IO))) + (type (fn-type (params (own (con Int))) (ret (own (con Unit))) (effects IO))) (params n) (body (app print (app sum_steps_loop n 0)))) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (seq (app run_one 10000) diff --git a/examples/bench_compute_intsum.ail b/examples/bench_compute_intsum.ail index dc43ee9..1ebd1f7 100644 --- a/examples/bench_compute_intsum.ail +++ b/examples/bench_compute_intsum.ail @@ -24,8 +24,8 @@ (doc "Tail-recursive: acc += i*7 for i in [n, n-1, ..., 1]. Returns final acc.") (type (fn-type - (params (con Int) (con Int)) - (ret (con Int)))) + (params (own (con Int)) (own (con Int))) + (ret (own (con Int))))) (params i acc) (body (if (app eq i 0) @@ -35,12 +35,12 @@ (app + acc (app * i 7)))))) (fn run_one - (type (fn-type (params (con Int)) (ret (con Unit)) (effects IO))) + (type (fn-type (params (own (con Int))) (ret (own (con Unit))) (effects IO))) (params n) (body (app print (app intsum_loop n 0)))) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (seq (app run_one 1000000) diff --git a/examples/bench_hof_pipeline.ail b/examples/bench_hof_pipeline.ail index 32a61a8..8032581 100644 --- a/examples/bench_hof_pipeline.ail +++ b/examples/bench_hof_pipeline.ail @@ -8,7 +8,7 @@ ; layout is the same width as bench_list_sum's IntList ICons, ; but the type-arg substitution is the additional code path. ; 2. The traversal is via `fold_with_fn` — an HOF taking -; `(fn-type (params a) (ret (con Int)))` as its first param. +; `(fn-type (params (own a)) (ret (own (con Int))))` as its first param. ; Each step calls `(app f h)` — an indirect call through the ; fn-arg. This is the only fixture that exercises tight-loop ; indirect dispatch. @@ -36,8 +36,8 @@ (doc "Build [0, 1, ..., n-1] :: List Int. Tail-recursive accumulator form.") (type (fn-type - (params (con Int) (con List (con Int))) - (ret (con List (con Int))))) + (params (own (con Int)) (own (con List (con Int)))) + (ret (own (con List (con Int)))))) (params n acc) (body (if (app eq n 0) @@ -48,7 +48,7 @@ (fn inc (doc "Add 1 to an Int. Used as the HOF argument to fold_with_fn.") - (type (fn-type (params (con Int)) (ret (con Int)))) + (type (fn-type (params (own (con Int))) (ret (own (con Int))))) (params x) (body (app + x 1))) @@ -57,10 +57,10 @@ (type (forall (vars a) (fn-type - (params (fn-type (params a) (ret (con Int))) - (con List a) - (con Int)) - (ret (con Int))))) + (params (borrow (fn-type (params (own a)) (ret (own (con Int))))) + (own (con List a)) + (own (con Int))) + (ret (own (con Int)))))) (params f xs acc) (body (match xs @@ -70,7 +70,7 @@ (fn run_one (doc "Build [0..n-1], fold with inc, print sum of (inc x).") - (type (fn-type (params (con Int)) (ret (con Unit)) (effects IO))) + (type (fn-type (params (own (con Int))) (ret (own (con Unit))) (effects IO))) (params n) (body (app print @@ -79,7 +79,7 @@ 0)))) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (seq (app run_one 100000) diff --git a/examples/bench_latency_explicit.ail b/examples/bench_latency_explicit.ail index 10e71b2..a1d8690 100644 --- a/examples/bench_latency_explicit.ail +++ b/examples/bench_latency_explicit.ail @@ -45,7 +45,7 @@ (doc "Build a balanced tree of given depth, every value = 1. Returns owned Tree; main holds it across the loop and the final drop fires at main's scope close.") (type (fn-type - (params (con Int)) + (params (own (con Int))) (ret (own (con Tree))))) (params depth) (body @@ -61,7 +61,7 @@ (type (fn-type (params (borrow (con Tree))) - (ret (con Int)))) + (ret (own (con Int))))) (params t) (body (match t @@ -74,7 +74,7 @@ (doc "Tail-recursive list builder. Returns owned chain; caller is sum_list which owns and drops it.") (type (fn-type - (params (con Int) (own (con IntList))) + (params (own (con Int)) (own (con IntList))) (ret (own (con IntList))))) (params n acc) (body @@ -88,7 +88,7 @@ (doc "Build [0,1,...,n-1] :: IntList. Returns owned chain.") (type (fn-type - (params (con Int)) + (params (own (con Int))) (ret (own (con IntList))))) (params n) (body @@ -98,8 +98,8 @@ (doc "Tail-recursive sum. Owns xs; consumes it via the LCons arm's t binder (move-into-tail-call).") (type (fn-type - (params (own (con IntList)) (con Int)) - (ret (con Int)))) + (params (own (con IntList)) (own (con Int))) + (ret (own (con Int))))) (params xs acc) (body (match xs @@ -112,7 +112,7 @@ (type (fn-type (params (own (con IntList))) - (ret (con Int)))) + (ret (own (con Int))))) (params xs) (body (app sum_list_acc xs 0))) @@ -121,8 +121,8 @@ (doc "One bench operation: build+sum a fresh CHUNK_LEN-cell list, pin the tree's root, return their sum so the value chain stays observable. Tree is borrowed; no inc/dec on the hot path against the persistent cache.") (type (fn-type - (params (con Int) (borrow (con Tree))) - (ret (con Int)))) + (params (own (con Int)) (borrow (con Tree))) + (ret (own (con Int))))) (params chunk_len t) (body (app + (app sum_list (app cons_n chunk_len)) (app pin_root t)))) @@ -133,8 +133,8 @@ (doc "Tail-recursive bench loop. Tree is borrowed across all iterations.") (type (fn-type - (params (con Int) (con Int) (con Int) (con Int) (borrow (con Tree))) - (ret (con Unit)) + (params (own (con Int)) (own (con Int)) (own (con Int)) (own (con Int)) (borrow (con Tree))) + (ret (own (con Unit))) (effects IO))) (params remaining print_countdown chunk_len print_k t) (body @@ -159,7 +159,7 @@ (fn main (doc "Top-level: build tree, signal READY (8888), run loop, signal DONE (9999 emitted by loop).") - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (let t (app build_tree 19) diff --git a/examples/bench_latency_implicit.ail b/examples/bench_latency_implicit.ail index c62edd9..0dbc28b 100644 --- a/examples/bench_latency_implicit.ail +++ b/examples/bench_latency_implicit.ail @@ -69,8 +69,8 @@ (doc "Build a balanced tree of given depth, every value = 1. Constructor-blocked — recursion depth = `depth`, fits 8MB stack at depth 19.") (type (fn-type - (params (con Int)) - (ret (con Tree)))) + (params (own (con Int))) + (ret (own (con Tree))))) (params depth) (body (if (app eq depth 0) @@ -84,8 +84,8 @@ (doc "Touch every node of the tree (ensures liveness across the loop).") (type (fn-type - (params (con Tree)) - (ret (con Int)))) + (params (own (con Tree))) + (ret (own (con Int))))) (params t) (body (match t @@ -99,8 +99,8 @@ (doc "Tail-recursive list builder. Result = [n-1, n-2, ..., 0] :: IntList.") (type (fn-type - (params (con Int) (con IntList)) - (ret (con IntList)))) + (params (own (con Int)) (own (con IntList))) + (ret (own (con IntList))))) (params n acc) (body (if (app eq n 0) @@ -113,8 +113,8 @@ (doc "Build [0,1,...,n-1] :: IntList.") (type (fn-type - (params (con Int)) - (ret (con IntList)))) + (params (own (con Int))) + (ret (own (con IntList))))) (params n) (body (app cons_n_acc n (term-ctor IntList LNil)))) @@ -123,8 +123,8 @@ (doc "Tail-recursive sum.") (type (fn-type - (params (con IntList) (con Int)) - (ret (con Int)))) + (params (own (con IntList)) (own (con Int))) + (ret (own (con Int))))) (params xs acc) (body (match xs @@ -136,8 +136,8 @@ (doc "Sum every element. Calls sum_list_acc with seed 0.") (type (fn-type - (params (con IntList)) - (ret (con Int)))) + (params (own (con IntList))) + (ret (own (con Int))))) (params xs) (body (app sum_list_acc xs 0))) @@ -158,8 +158,8 @@ (doc "Constant-time tree liveness pin — read root tag, return 1 (TNode) or 0 (TLeaf).") (type (fn-type - (params (con Tree)) - (ret (con Int)))) + (params (borrow (con Tree))) + (ret (own (con Int))))) (params t) (body (match t @@ -170,8 +170,8 @@ (doc "One bench operation: build+sum a fresh CHUNK_LEN-cell list, pin the tree's root, return their sum so the value chain stays observable.") (type (fn-type - (params (con Int) (con Tree)) - (ret (con Int)))) + (params (own (con Int)) (borrow (con Tree))) + (ret (own (con Int))))) (params chunk_len t) (body (app + (app sum_list (app cons_n chunk_len)) (app pin_root t)))) @@ -192,8 +192,8 @@ (doc "Tail-recursive bench loop. Ops countdown in `remaining`; print marker every time `print_countdown` hits 0.") (type (fn-type - (params (con Int) (con Int) (con Int) (con Int) (con Tree)) - (ret (con Unit)) + (params (own (con Int)) (own (con Int)) (own (con Int)) (own (con Int)) (borrow (con Tree))) + (ret (own (con Unit))) (effects IO))) (params remaining print_countdown chunk_len print_k t) (body @@ -218,7 +218,7 @@ (fn main (doc "Top-level: build tree, signal READY (8888), run loop, signal DONE (9999 emitted by loop).") - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (let t (app build_tree 19) diff --git a/examples/bench_list_sum.ail b/examples/bench_list_sum.ail index d0ab4d8..ac774ff 100644 --- a/examples/bench_list_sum.ail +++ b/examples/bench_list_sum.ail @@ -40,8 +40,8 @@ (doc "Tail-recursive list builder. Result = accumulator-prepended list.") (type (fn-type - (params (con Int) (con IntList)) - (ret (con IntList)))) + (params (own (con Int)) (own (con IntList))) + (ret (own (con IntList))))) (params n acc) (body (if (app eq n 0) @@ -54,8 +54,8 @@ (doc "Build [0, 1, ..., n-1] :: IntList. Order doesn't matter for sum.") (type (fn-type - (params (con Int)) - (ret (con IntList)))) + (params (own (con Int))) + (ret (own (con IntList))))) (params n) (body (app cons_n_acc n (term-ctor IntList INil)))) @@ -64,8 +64,8 @@ (doc "Tail-recursive sum.") (type (fn-type - (params (con IntList) (con Int)) - (ret (con Int)))) + (params (own (con IntList)) (own (con Int))) + (ret (own (con Int))))) (params xs acc) (body (match xs @@ -77,21 +77,21 @@ (doc "Sum every element. Calls sum_acc with seed 0.") (type (fn-type - (params (con IntList)) - (ret (con Int)))) + (params (own (con IntList))) + (ret (own (con Int))))) (params xs) (body (app sum_acc xs 0))) (fn run_one (doc "Build a list of length n, sum it, print the sum.") - (type (fn-type (params (con Int)) (ret (con Unit)) (effects IO))) + (type (fn-type (params (own (con Int))) (ret (own (con Unit))) (effects IO))) (params n) (body (app print (app sum_list (app cons_n n))))) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (seq (app run_one 100000) diff --git a/examples/bench_list_sum.prose.txt b/examples/bench_list_sum.prose.txt index 4644290..3ab21b5 100644 --- a/examples/bench_list_sum.prose.txt +++ b/examples/bench_list_sum.prose.txt @@ -3,7 +3,7 @@ data IntList = INil | ICons(Int, IntList) /// Tail-recursive list builder. Result = accumulator-prepended list. -fn cons_n_acc(n: Int, acc: IntList) -> IntList { +fn cons_n_acc(n: own Int, acc: own IntList) -> own IntList { if eq(n, 0) { acc } else { @@ -12,12 +12,12 @@ fn cons_n_acc(n: Int, acc: IntList) -> IntList { } /// Build [0, 1, ..., n-1] :: IntList. Order doesn't matter for sum. -fn cons_n(n: Int) -> IntList { +fn cons_n(n: own Int) -> own IntList { cons_n_acc(n, INil) } /// Tail-recursive sum. -fn sum_acc(xs: IntList, acc: Int) -> Int { +fn sum_acc(xs: own IntList, acc: own Int) -> own Int { match xs { INil => acc, ICons(h, t) => tail sum_acc(t, acc + h) @@ -25,16 +25,16 @@ fn sum_acc(xs: IntList, acc: Int) -> Int { } /// Sum every element. Calls sum_acc with seed 0. -fn sum_list(xs: IntList) -> Int { +fn sum_list(xs: own IntList) -> own Int { sum_acc(xs, 0) } /// Build a list of length n, sum it, print the sum. -fn run_one(n: Int) -> Unit with IO { +fn run_one(n: own Int) -> own Unit with IO { print(sum_list(cons_n(n))) } -fn main() -> Unit with IO { +fn main() -> own Unit with IO { run_one(100000); run_one(1000000); run_one(3000000) diff --git a/examples/bench_list_sum_explicit.ail b/examples/bench_list_sum_explicit.ail index 770b742..13a29d0 100644 --- a/examples/bench_list_sum_explicit.ail +++ b/examples/bench_list_sum_explicit.ail @@ -37,7 +37,7 @@ (doc "Tail-recursive: prepend (n-1, n-2, ..., 0) onto acc. Returns owned chain.") (type (fn-type - (params (con Int) (own (con IntList))) + (params (own (con Int)) (own (con IntList))) (ret (own (con IntList))))) (params n acc) (body @@ -51,7 +51,7 @@ (doc "Build [0..n-1] :: IntList. Returns owned chain; caller owns and consumes.") (type (fn-type - (params (con Int)) + (params (own (con Int))) (ret (own (con IntList))))) (params n) (body (app cons_n_acc n (term-ctor IntList INil)))) @@ -60,8 +60,8 @@ (doc "Tail-recursive sum. Owns xs; consumes via LCons-arm move-into-tail-call.") (type (fn-type - (params (own (con IntList)) (con Int)) - (ret (con Int)))) + (params (own (con IntList)) (own (con Int))) + (ret (own (con Int))))) (params xs acc) (body (match xs @@ -74,19 +74,19 @@ (type (fn-type (params (own (con IntList))) - (ret (con Int)))) + (ret (own (con Int))))) (params xs) (body (app sum_acc xs 0))) (fn run_one (doc "Build, sum, print. The owned list flows from cons_n into sum_list and is fully consumed.") - (type (fn-type (params (con Int)) (ret (con Unit)) (effects IO))) + (type (fn-type (params (own (con Int))) (ret (own (con Unit))) (effects IO))) (params n) (body (app print (app sum_list (app cons_n n))))) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (seq (app run_one 100000) diff --git a/examples/bench_mono_dispatch.ail b/examples/bench_mono_dispatch.ail index 3afa33b..b99c58f 100644 --- a/examples/bench_mono_dispatch.ail +++ b/examples/bench_mono_dispatch.ail @@ -2,7 +2,7 @@ (class Foo (param a) (method foo - (type (fn-type (params a) (ret (con Int)))))) + (type (fn-type (params (own a)) (ret (own (con Int))))))) (instance (class Foo) (type (con Int)) @@ -11,13 +11,13 @@ (class Looper (param a) (method loop_call - (type (fn-type (params a (con Int)) (ret (con Int)))))) + (type (fn-type (params (own a) (own (con Int))) (ret (own (con Int))))))) (instance (class Looper) (type (con Int)) (method loop_call (body (lam (params (typed i a) (typed acc (con Int))) (ret (con Int)) (body (if (app eq i 0) acc (tail-app loop_call (app - i 1) (app + acc (app foo (app + acc i)))))))))) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (app print (app loop_call 100000000 0))))) diff --git a/examples/bench_tree_walk.ail b/examples/bench_tree_walk.ail index 790460b..4d06395 100644 --- a/examples/bench_tree_walk.ail +++ b/examples/bench_tree_walk.ail @@ -38,8 +38,8 @@ (doc "Balanced binary tree of given depth, every value = 1.") (type (fn-type - (params (con Int)) - (ret (con Tree)))) + (params (own (con Int))) + (ret (own (con Tree))))) (params depth) (body (if (app eq depth 0) @@ -53,8 +53,8 @@ (doc "Sum every Node value via match recursion. Constructor-blocked: not tail-recursive, but recursion depth = tree depth so fits.") (type (fn-type - (params (con Tree)) - (ret (con Int)))) + (params (own (con Tree))) + (ret (own (con Int))))) (params t) (body (match t @@ -64,13 +64,13 @@ (fn run_one (doc "Build a tree of given depth, sum it, print the sum.") - (type (fn-type (params (con Int)) (ret (con Unit)) (effects IO))) + (type (fn-type (params (own (con Int))) (ret (own (con Unit))) (effects IO))) (params depth) (body (app print (app sum_tree (app build_tree depth))))) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (seq (app run_one 16) diff --git a/examples/bool_to_str_drop_rc.ail b/examples/bool_to_str_drop_rc.ail index b26c10b..e478157 100644 --- a/examples/bool_to_str_drop_rc.ail +++ b/examples/bool_to_str_drop_rc.ail @@ -1,6 +1,6 @@ (module bool_to_str_drop_rc (fn main (doc "Iter 24.1: pin that `bool_to_str` participates in RC discipline. Bind the heap-Str result to `s`, consume `s` once in `io/print_str`, let the binder drop at scope close. Under --alloc=rc with AILANG_RC_STATS=1 the atexit summary must report allocs == 1 && frees == 1 && live == 0 — the heap-Str slab allocated by str_alloc inside ailang_bool_to_str rides the same rc_header + ailang_rc_dec path as int_to_str.") - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (let s (app bool_to_str true) (seq (do io/print_str s) (do io/print_str "\n")))))) diff --git a/examples/bool_to_str_smoke_false.ail b/examples/bool_to_str_smoke_false.ail index 980057e..afa7983 100644 --- a/examples/bool_to_str_smoke_false.ail +++ b/examples/bool_to_str_smoke_false.ail @@ -1,6 +1,6 @@ (module bool_to_str_smoke_false (fn main (doc "Iter 24.1: stdout-smoke for the false branch of bool_to_str. Stdout must be `false\\n` (puts adds the newline). Companion of bool_to_str_drop_rc.ail.json (which covers the true branch and the RC-stats invariant).") - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (let s (app bool_to_str false) (seq (do io/print_str s) (do io/print_str "\n")))))) diff --git a/examples/borrow_own_demo.ail b/examples/borrow_own_demo.ail index 4b8dbbf..78cf590 100644 --- a/examples/borrow_own_demo.ail +++ b/examples/borrow_own_demo.ail @@ -28,7 +28,7 @@ (type (fn-type (params (borrow (con List))) - (ret (con Int)))) + (ret (own (con Int))))) (params xs) (body (match xs @@ -41,7 +41,7 @@ (type (fn-type (params (own (con List))) - (ret (con Int)))) + (ret (own (con Int))))) (params xs) (body (match xs @@ -51,7 +51,7 @@ (fn main (doc "Build [1,2,3]; print list_length (3) then sum_list (6).") - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (let xs diff --git a/examples/borrow_value_reject.ail b/examples/borrow_value_reject.ail new file mode 100644 index 0000000..8c57be7 --- /dev/null +++ b/examples/borrow_value_reject.ail @@ -0,0 +1,10 @@ +(module borrow_value_reject + + (fn ignore + (doc "MUST FAIL post-cutover: borrow over a value type is meaningless.") + (type + (fn-type + (params (borrow (con Int))) + (ret (own (con Int))))) + (params n) + (body 0))) diff --git a/examples/box.ail b/examples/box.ail index 5d83ab8..354da08 100644 --- a/examples/box.ail +++ b/examples/box.ail @@ -14,8 +14,8 @@ (type (forall (vars a) (fn-type - (params (con Box a)) - (ret a)))) + (params (own (con Box a))) + (ret (own a))))) (params b) (body (match b @@ -27,7 +27,7 @@ (type (fn-type (params) - (ret (con Unit)) + (ret (own (con Unit))) (effects IO))) (params) (body diff --git a/examples/broken_unbound.ail.json b/examples/broken_unbound.ail.json index 7cb6ec2..982612f 100644 --- a/examples/broken_unbound.ail.json +++ b/examples/broken_unbound.ail.json @@ -9,12 +9,20 @@ "type": { "k": "fn", "params": [], - "ret": { "k": "con", "name": "Int" }, + "param_modes": [], + "ret": { + "k": "con", + "name": "Int" + }, + "ret_mode": "own", "effects": [] }, "params": [], "doc": "Referenziert eine nicht-existente Variable -> unbound-var.", - "body": { "t": "var", "name": "does_not_exist" } + "body": { + "t": "var", + "name": "does_not_exist" + } } ] } diff --git a/examples/bug_unbound_in_instance_method.ail b/examples/bug_unbound_in_instance_method.ail index 52ef154..aefdc13 100644 --- a/examples/bug_unbound_in_instance_method.ail +++ b/examples/bug_unbound_in_instance_method.ail @@ -2,7 +2,7 @@ (class Describe (param a) (method describe - (type (fn-type (params (borrow a)) (ret (con Str)))))) + (type (fn-type (params (borrow a)) (ret (own (con Str))))))) (instance (class Describe) (type (con Int)) @@ -13,6 +13,6 @@ (ret (con Str)) (body (app format_label "n=" (app int_to_str n))))))) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (do io/print_str (app describe 5))))) diff --git a/examples/classify_pin.ail b/examples/classify_pin.ail index 835a41b..55aba90 100644 --- a/examples/classify_pin.ail +++ b/examples/classify_pin.ail @@ -1,5 +1,5 @@ (module classify_pin - (fn bump (type (fn-type (params (con Int)) (ret (con Int)))) (params n) + (fn bump (type (fn-type (params (own (con Int))) (ret (own (con Int))))) (params n) (body (app + n 1))) - (fn main (type (fn-type (params) (ret (con Int)))) (params) + (fn main (type (fn-type (params) (ret (own (con Int))))) (params) (body (app bump 41)))) diff --git a/examples/clone_demo.ail b/examples/clone_demo.ail index 23efba3..febc17a 100644 --- a/examples/clone_demo.ail +++ b/examples/clone_demo.ail @@ -19,7 +19,7 @@ (fn main (doc "Use `(clone x)` on a let-bound Int; print it.") - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (let x 42 diff --git a/examples/closure.ail b/examples/closure.ail index 9452b27..a664919 100644 --- a/examples/closure.ail +++ b/examples/closure.ail @@ -1,11 +1,11 @@ (module closure (fn apply (doc "apply a fn-of-Int to an Int") - (type (fn-type (params (fn-type (params (con Int)) (ret (con Int))) (con Int)) (ret (con Int)))) + (type (fn-type (params (own (fn-type (params (own (con Int))) (ret (own (con Int))))) (own (con Int))) (ret (own (con Int))))) (params f x) (body (app f x))) (fn main (doc "captures `n` from the enclosing let, returns 42") - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (let n 3 (app print (app apply (lam (params (typed x (con Int))) (ret (con Int)) (body (app + x n))) 39)))))) diff --git a/examples/cmp_max_smoke.ail b/examples/cmp_max_smoke.ail index 6adb4fb..69ecb3f 100644 --- a/examples/cmp_max_smoke.ail +++ b/examples/cmp_max_smoke.ail @@ -1,11 +1,11 @@ (module cmp_max_smoke (fn cmp_max - (type (forall (vars a) (constraints (constraint prelude.Ord a)) (fn-type (params a a) (ret a)))) + (type (forall (vars a) (constraints (constraint prelude.Ord a)) (fn-type (params (own a) (own a)) (ret (own a))))) (params x y) (body (match (app compare x y) (case (pat-ctor LT) y) (case _ x)))) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (app print (app cmp_max 3 7))))) diff --git a/examples/compare_float_noinstance.ail b/examples/compare_float_noinstance.ail index 7bd4ae7..4fa5d03 100644 --- a/examples/compare_float_noinstance.ail +++ b/examples/compare_float_noinstance.ail @@ -1,5 +1,5 @@ (module compare_float_noinstance (fn cmp - (type (fn-type (params) (ret (con Ordering)))) + (type (fn-type (params) (ret (own (con Ordering))))) (params) (body (app compare 1.0 2.0)))) diff --git a/examples/compare_primitives_smoke.ail b/examples/compare_primitives_smoke.ail index 7fb9852..86c20fb 100644 --- a/examples/compare_primitives_smoke.ail +++ b/examples/compare_primitives_smoke.ail @@ -1,6 +1,6 @@ (module compare_primitives_smoke (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (seq (seq (app print (match (app compare 1 2) (case (pat-ctor LT) 1) diff --git a/examples/ct_1_ordering_signum.ail b/examples/ct_1_ordering_signum.ail index 64db681..3c7158e 100644 --- a/examples/ct_1_ordering_signum.ail +++ b/examples/ct_1_ordering_signum.ail @@ -23,7 +23,7 @@ (fn signum (doc "Map an Int to -1/0/+1 via prelude.compare. Returns the sign as an Int.") - (type (fn-type (params (con Int)) (ret (con Int)))) + (type (fn-type (params (own (con Int))) (ret (own (con Int))))) (params n) (body (match (app compare n 0) @@ -33,7 +33,7 @@ (fn drain (doc "Print signum of every element of xs, in list order.") - (type (fn-type (params (con IntList)) (ret (con Unit)) (effects IO))) + (type (fn-type (params (own (con IntList))) (ret (own (con Unit))) (effects IO))) (params xs) (body (match xs @@ -43,7 +43,7 @@ (tail-app drain t)))))) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (app drain diff --git a/examples/ct_2_bare_cross_module.ail b/examples/ct_2_bare_cross_module.ail index 32daa4e..3d353d9 100644 --- a/examples/ct_2_bare_cross_module.ail +++ b/examples/ct_2_bare_cross_module.ail @@ -22,13 +22,13 @@ (doc "Identity over Maybe; signature uses bare `Maybe` with no import.") (type (fn-type - (params (con Maybe (con Int))) - (ret (con Maybe (con Int))))) + (params (own (con Maybe (con Int)))) + (ret (own (con Maybe (con Int)))))) (params m) (body m)) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (app classify (term-ctor Maybe Just 7))))) diff --git a/examples/ct_3_bad_qualified.ail b/examples/ct_3_bad_qualified.ail index 27084ce..b6f53dd 100644 --- a/examples/ct_3_bad_qualified.ail +++ b/examples/ct_3_bad_qualified.ail @@ -11,12 +11,12 @@ (doc "Identity at mystery.Widget — but `mystery` is not a workspace module.") (type (fn-type - (params (con mystery.Widget)) - (ret (con mystery.Widget)))) + (params (own (con mystery.Widget))) + (ret (own (con mystery.Widget))))) (params w) (body w)) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (do io/print_str "unreachable")))) diff --git a/examples/ct_3b_bad_qualified_known_module.ail b/examples/ct_3b_bad_qualified_known_module.ail index 24dcd3f..dbbe498 100644 --- a/examples/ct_3b_bad_qualified_known_module.ail +++ b/examples/ct_3b_bad_qualified_known_module.ail @@ -15,12 +15,12 @@ (doc "Identity at std_maybe.Widget — `std_maybe` is real, but `Widget` is not in it.") (type (fn-type - (params (con std_maybe.Widget)) - (ret (con std_maybe.Widget)))) + (params (own (con std_maybe.Widget))) + (ret (own (con std_maybe.Widget))))) (params w) (body w)) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (do io/print_str "unreachable")))) diff --git a/examples/ctt2_collision_cls.ail b/examples/ctt2_collision_cls.ail index b703ef5..afc5f99 100644 --- a/examples/ctt2_collision_cls.ail +++ b/examples/ctt2_collision_cls.ail @@ -2,4 +2,4 @@ (class MyC (param a) (method op - (type (fn-type (params a) (ret (con Int))))))) + (type (fn-type (params (own a)) (ret (own (con Int)))))))) diff --git a/examples/drop_value_field_no_segfault_pin.ail b/examples/drop_value_field_no_segfault_pin.ail new file mode 100644 index 0000000..d51397e --- /dev/null +++ b/examples/drop_value_field_no_segfault_pin.ail @@ -0,0 +1,44 @@ +; RED-pin fixture for leg (C) of the drop-soundness bug family the +; Implicit-cutover (#55) surfaced: a polymorphic ctor's drop fn +; rc_dec's a type-parameter field even when that field is +; monomorphised at a value type (Int). +; +; `Box a` is the minimal single-field box. `unbox` takes the box by +; `own` and matches it, extracting the inner value `x` and returning +; it. The match consumes the box husk, so codegen emits the +; scope-close drop `drop_drop_value_field_no_segfault_pin_Box(box)`. +; That drop fn is emitted ONCE from the polymorphic declaration; the +; field type is the type-var `a`, which `llvm_type` cannot lower, so +; drop.rs's `.unwrap_or_else(|_| "ptr")` treats it as a boxed pointer +; and emits `ailang_rc_dec` on it. +; +; At the `Box Int` monomorph the field holds the raw i64 `7` stored +; inline (offset 8), NOT a heap pointer. `ailang_rc_dec(7)` +; dereferences address 7 -> SIGSEGV. Expected post-fix: prints `7`, +; exits 0, AILANG_RC_STATS reports `allocs == frees && live == 0` +; (the one Box slab is freed exactly once; no spurious dec of the +; inline value field). + +(module drop_value_field_no_segfault_pin + + (data Box (vars a) + (doc "Single-field polymorphic box.") + (ctor MkBox a)) + + (fn unbox + (doc "Consume the box by own, return its inner value. The match drops the box husk.") + (type + (forall (vars a) + (fn-type + (params (own (con Box a))) + (ret (own a))))) + (params b) + (body + (match b + (case (pat-ctor MkBox x) x)))) + + (fn main + (doc "Build MkBox 7 at Int, unbox it (drops the Box husk), print the result.") + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) + (params) + (body (app print (app unbox (term-ctor Box MkBox 7)))))) diff --git a/examples/embed_backtest_step.ail b/examples/embed_backtest_step.ail index 5e82834..338deb8 100644 --- a/examples/embed_backtest_step.ail +++ b/examples/embed_backtest_step.ail @@ -4,8 +4,8 @@ (export "backtest_step") (type (fn-type - (params (con Int) (con Int)) - (ret (con Int)))) + (params (own (con Int)) (own (con Int))) + (ret (own (con Int))))) (params state sample) (body (app + state (app * sample sample)))) @@ -13,8 +13,8 @@ (fn helper (type (fn-type - (params (con Int)) - (ret (con Int)))) + (params (own (con Int))) + (ret (own (con Int))))) (params x) (body (app * x x)))) diff --git a/examples/embed_backtest_step_record.ail b/examples/embed_backtest_step_record.ail index 922528d..c44646d 100644 --- a/examples/embed_backtest_step_record.ail +++ b/examples/embed_backtest_step_record.ail @@ -7,8 +7,8 @@ (export "backtest_step") (type (fn-type - (params (own (con State)) (con Float)) - (ret (con State)))) + (params (own (con State)) (own (con Float))) + (ret (own (con State))))) (params st sample) (body (match st diff --git a/examples/embed_backtest_step_record_borrow.ail b/examples/embed_backtest_step_record_borrow.ail index 7d44a5d..d2e6599 100644 --- a/examples/embed_backtest_step_record_borrow.ail +++ b/examples/embed_backtest_step_record_borrow.ail @@ -7,8 +7,8 @@ (export "backtest_step") (type (fn-type - (params (borrow (con State)) (con Float)) - (ret (con State)))) + (params (borrow (con State)) (own (con Float))) + (ret (own (con State))))) (params st sample) (body (match st diff --git a/examples/embed_backtest_step_tick.ail b/examples/embed_backtest_step_tick.ail index 1248a24..8ed2d51 100644 --- a/examples/embed_backtest_step_tick.ail +++ b/examples/embed_backtest_step_tick.ail @@ -11,7 +11,7 @@ (type (fn-type (params (own (con State)) (own (con Tick))) - (ret (con State)))) + (ret (own (con State))))) (params st tick) (body (match st diff --git a/examples/embed_backtest_step_tick_borrow.ail b/examples/embed_backtest_step_tick_borrow.ail index 174fa2d..d146570 100644 --- a/examples/embed_backtest_step_tick_borrow.ail +++ b/examples/embed_backtest_step_tick_borrow.ail @@ -11,7 +11,7 @@ (type (fn-type (params (own (con State)) (borrow (con Tick))) - (ret (con State)))) + (ret (own (con State))))) (params st tick) (body (match st diff --git a/examples/embed_export_adt_ret_rejected.ail b/examples/embed_export_adt_ret_rejected.ail index 3d59b13..1f13c51 100644 --- a/examples/embed_export_adt_ret_rejected.ail +++ b/examples/embed_export_adt_ret_rejected.ail @@ -6,7 +6,7 @@ (export "f") (type (fn-type - (params (con Int)) - (ret (con Reading)))) + (params (own (con Int))) + (ret (own (con Reading))))) (params n) (body (term-ctor Reading Sample n "tick")))) diff --git a/examples/embed_export_effectful_rejected.ail b/examples/embed_export_effectful_rejected.ail index ec05be6..ee0329a 100644 --- a/examples/embed_export_effectful_rejected.ail +++ b/examples/embed_export_effectful_rejected.ail @@ -3,8 +3,8 @@ (export "log_step") (type (fn-type - (params (con Int) (con Str)) - (ret (con Int)) + (params (own (con Int)) (own (con Str))) + (ret (own (con Int))) (effects IO))) (params state label) (body diff --git a/examples/embed_export_float_ok.ail b/examples/embed_export_float_ok.ail index ebf4cf3..b4ac1c6 100644 --- a/examples/embed_export_float_ok.ail +++ b/examples/embed_export_float_ok.ail @@ -3,8 +3,8 @@ (export "embed_scale") (type (fn-type - (params (con Float) (con Float)) - (ret (con Float)))) + (params (own (con Float)) (own (con Float))) + (ret (own (con Float))))) (params a b) (body (app * a b)))) diff --git a/examples/embed_export_io_rejected.ail b/examples/embed_export_io_rejected.ail index 78265a4..2b234fc 100644 --- a/examples/embed_export_io_rejected.ail +++ b/examples/embed_export_io_rejected.ail @@ -3,8 +3,8 @@ (export "f") (type (fn-type - (params (con Int)) - (ret (con Int)) + (params (own (con Int))) + (ret (own (con Int))) (effects IO))) (params n) (body diff --git a/examples/embed_export_list_field_record_rejected.ail b/examples/embed_export_list_field_record_rejected.ail index bcb661c..dba9d65 100644 --- a/examples/embed_export_list_field_record_rejected.ail +++ b/examples/embed_export_list_field_record_rejected.ail @@ -6,6 +6,6 @@ (ctor Boxed (con Int) (con List))) (fn f (export "f") - (type (fn-type (params (con Int)) (ret (con Boxed)))) + (type (fn-type (params (own (con Int))) (ret (own (con Boxed))))) (params n) (body (term-ctor Boxed Boxed n (term-ctor List Nil))))) diff --git a/examples/embed_export_multictor_rejected.ail b/examples/embed_export_multictor_rejected.ail index f208d2a..0d030f4 100644 --- a/examples/embed_export_multictor_rejected.ail +++ b/examples/embed_export_multictor_rejected.ail @@ -4,6 +4,6 @@ (ctor R (con Float))) (fn f (export "f") - (type (fn-type (params (con Int)) (ret (con Either2)))) + (type (fn-type (params (own (con Int))) (ret (own (con Either2))))) (params n) (body (term-ctor Either2 L n)))) diff --git a/examples/embed_export_record_ok.ail b/examples/embed_export_record_ok.ail index 3083b8f..896acc0 100644 --- a/examples/embed_export_record_ok.ail +++ b/examples/embed_export_record_ok.ail @@ -5,8 +5,8 @@ (export "step") (type (fn-type - (params (own (con State)) (con Float)) - (ret (con State)))) + (params (own (con State)) (own (con Float))) + (ret (own (con State))))) (params st sample) (body (match st diff --git a/examples/embed_export_str_field_record_rejected.ail b/examples/embed_export_str_field_record_rejected.ail index 81ce068..3acc98f 100644 --- a/examples/embed_export_str_field_record_rejected.ail +++ b/examples/embed_export_str_field_record_rejected.ail @@ -3,6 +3,6 @@ (ctor Tagged (con Int) (con Str))) (fn f (export "f") - (type (fn-type (params (con Int)) (ret (con Tagged)))) + (type (fn-type (params (own (con Int))) (ret (own (con Tagged))))) (params n) (body (term-ctor Tagged Tagged n "x")))) diff --git a/examples/embed_export_str_param_rejected.ail b/examples/embed_export_str_param_rejected.ail index 8d3333a..0f7fb94 100644 --- a/examples/embed_export_str_param_rejected.ail +++ b/examples/embed_export_str_param_rejected.ail @@ -3,7 +3,7 @@ (export "f") (type (fn-type - (params (con Int) (con Str)) - (ret (con Int)))) + (params (own (con Int)) (own (con Str))) + (ret (own (con Int))))) (params n label) (body n))) diff --git a/examples/embed_noentry_baseline.ail b/examples/embed_noentry_baseline.ail index 2e36c2d..40ecf0f 100644 --- a/examples/embed_noentry_baseline.ail +++ b/examples/embed_noentry_baseline.ail @@ -2,8 +2,8 @@ (fn helper (type (fn-type - (params (con Int)) - (ret (con Int)))) + (params (own (con Int))) + (ret (own (con Int))))) (params x) (body (app * x x)))) diff --git a/examples/embed_record_layout_carrier.ail b/examples/embed_record_layout_carrier.ail index 625ce31..a199a08 100644 --- a/examples/embed_record_layout_carrier.ail +++ b/examples/embed_record_layout_carrier.ail @@ -6,16 +6,16 @@ (fn mk (type (fn-type - (params (con Int) (con Int)) - (ret (con Pt)))) + (params (own (con Int)) (own (con Int))) + (ret (own (con Pt))))) (params a b) (body (term-ctor Pt Pt a b))) (fn sum_pt (type (fn-type - (params (own (con Pt))) - (ret (con Int)))) + (params (borrow (con Pt))) + (ret (own (con Int))))) (params p) (body (match p @@ -23,7 +23,7 @@ (app + x y))))) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (let s (app int_to_str (app sum_pt (app mk 3 4))) diff --git a/examples/eq_demo.ail b/examples/eq_demo.ail index 91b82da..502ac0c 100644 --- a/examples/eq_demo.ail +++ b/examples/eq_demo.ail @@ -34,7 +34,7 @@ (fn classify_str (doc "Lit-pattern over Str; exercises build_eq's class-dispatch lowering for non-Int.") - (type (fn-type (params (con Str)) (ret (con Int)))) + (type (fn-type (params (own (con Str))) (ret (own (con Int))))) (params s) (body (match s @@ -44,7 +44,7 @@ (fn main (doc "Drive eq at Int/Bool/Str/Unit; then drive classify_str.") - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (seq (seq (app print (app eq 5 5)) (do io/print_str "\n")) diff --git a/examples/eq_float_must_fail.ail b/examples/eq_float_must_fail.ail index 8673096..20383bc 100644 --- a/examples/eq_float_must_fail.ail +++ b/examples/eq_float_must_fail.ail @@ -1,6 +1,6 @@ (module eq_float_must_fail (fn main (doc "Klausel-3 discriminator. After operator-routing-eq-ord, `(app eq 1.5 1.5)` must fire `NoInstance Eq Float` with a follow-up sentence naming `float_eq` as the explicit IEEE-aware alternative. Today the diagnostic already fires (no Eq Float instance) but does not name `float_eq` — the milestone adds that hint.") - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (app print (app eq 1.5 1.5))))) diff --git a/examples/eq_float_noinstance.ail b/examples/eq_float_noinstance.ail index 5085292..3a6c6dd 100644 --- a/examples/eq_float_noinstance.ail +++ b/examples/eq_float_noinstance.ail @@ -1,5 +1,5 @@ (module eq_float_noinstance (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (app print (if (app eq 1.0 2.0) 1 0))))) diff --git a/examples/eq_ord_polymorphic.ail b/examples/eq_ord_polymorphic.ail index 75201b8..2e6f96d 100644 --- a/examples/eq_ord_polymorphic.ail +++ b/examples/eq_ord_polymorphic.ail @@ -1,10 +1,10 @@ (module eq_ord_polymorphic (fn at_most (doc "Composition of prelude `gt` with `not` — semantically equivalent to `le`. Used to exercise polymorphic-helper-over-prelude-fns composition through the unified mono pass.") - (type (forall (vars a) (constraints (constraint prelude.Ord a)) (fn-type (params (borrow a) (borrow a)) (ret (con Bool))))) + (type (forall (vars a) (constraints (constraint prelude.Ord a)) (fn-type (params (borrow a) (borrow a)) (ret (own (con Bool)))))) (params x y) (body (app not (app gt x y)))) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (seq (seq (app print (if (app at_most 3 5) 1 0)) (do io/print_str "\n")) (seq (seq (app print (if (app at_most true false) 1 0)) (do io/print_str "\n")) (seq (app print (if (app at_most "abc" "abd") 1 0)) (do io/print_str "\n"))))))) diff --git a/examples/eq_ord_user_adt.ail b/examples/eq_ord_user_adt.ail index ee14a6a..ffa6407 100644 --- a/examples/eq_ord_user_adt.ail +++ b/examples/eq_ord_user_adt.ail @@ -19,6 +19,6 @@ (case (pat-ctor MkIntBox ai) (match b (case (pat-ctor MkIntBox bi) (app compare ai bi)))))))))) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (seq (seq (app print (if (app eq (term-ctor IntBox MkIntBox 3) (term-ctor IntBox MkIntBox 3)) 1 0)) (do io/print_str "\n")) (seq (app print (if (app eq (term-ctor IntBox MkIntBox 3) (term-ctor IntBox MkIntBox 5)) 1 0)) (do io/print_str "\n")))))) diff --git a/examples/eq_primitives_smoke.ail b/examples/eq_primitives_smoke.ail index b176594..9c2de2e 100644 --- a/examples/eq_primitives_smoke.ail +++ b/examples/eq_primitives_smoke.ail @@ -1,10 +1,10 @@ (module eq_primitives_smoke (fn to_int (doc "1 if the bool is true, 0 otherwise. Used to render Eq results as printable Ints.") - (type (fn-type (params (con Bool)) (ret (con Int)))) + (type (fn-type (params (own (con Bool))) (ret (own (con Int))))) (params b) (body (if b 1 0))) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (seq (seq (app print (app to_int (app eq 7 7))) (do io/print_str "\n")) (seq (seq (app print (app to_int (app eq 7 8))) (do io/print_str "\n")) (seq (seq (app print (app to_int (app eq true true))) (do io/print_str "\n")) (seq (seq (app print (app to_int (app eq true false))) (do io/print_str "\n")) (seq (seq (app print (app to_int (app eq "hello" "hello"))) (do io/print_str "\n")) (seq (app print (app to_int (app eq "hello" "world"))) (do io/print_str "\n")))))))))) diff --git a/examples/eq_user_adt_smoke.ail b/examples/eq_user_adt_smoke.ail index 67bf5ee..5caee1c 100644 --- a/examples/eq_user_adt_smoke.ail +++ b/examples/eq_user_adt_smoke.ail @@ -17,7 +17,7 @@ (case (pat-ctor Point a2 b2) (if (app eq a1 a2) (app eq b1 b2) false)))))))))) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (let p1 (term-ctor Point Point 1 2) diff --git a/examples/escape_local_demo.ail b/examples/escape_local_demo.ail index a766135..f910f55 100644 --- a/examples/escape_local_demo.ail +++ b/examples/escape_local_demo.ail @@ -29,7 +29,7 @@ (fn peek (doc "Build a Box(n), discard the payload, return 42. The Box is non-escaping.") - (type (fn-type (params (con Int)) (ret (con Int)))) + (type (fn-type (params (own (con Int))) (ret (own (con Int))))) (params n) (body (let b (term-ctor Box MkBox n) @@ -38,7 +38,7 @@ (fn count (doc "Recurse n times; each call builds a temporary Box, discards its payload via _, and returns 0 if n<=0, else 1 + count(n-1).") - (type (fn-type (params (con Int)) (ret (con Int)))) + (type (fn-type (params (own (con Int))) (ret (own (con Int))))) (params n) (body (if (app le n 0) @@ -48,7 +48,7 @@ (case (pat-ctor MkBox _) (app + 1 (app count (app - n 1))))))))) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (seq (seq (app print (app peek 0)) (do io/print_str "\n")) diff --git a/examples/fieldtest/embabi1_1_ema_step.ail b/examples/fieldtest/embabi1_1_ema_step.ail index 2af04be..974df81 100644 --- a/examples/fieldtest/embabi1_1_ema_step.ail +++ b/examples/fieldtest/embabi1_1_ema_step.ail @@ -9,8 +9,8 @@ (export "ema_step") (type (fn-type - (params (con Int) (con Int)) - (ret (con Int)))) + (params (own (con Int)) (own (con Int))) + (ret (own (con Int))))) (params state sample) (body (app + state diff --git a/examples/fieldtest/embabi1_2_leaky_integrator.ail b/examples/fieldtest/embabi1_2_leaky_integrator.ail index fdf2acc..e82d5ad 100644 --- a/examples/fieldtest/embabi1_2_leaky_integrator.ail +++ b/examples/fieldtest/embabi1_2_leaky_integrator.ail @@ -9,8 +9,8 @@ (export "leaky_step") (type (fn-type - (params (con Float) (con Float)) - (ret (con Float)))) + (params (own (con Float)) (own (con Float))) + (ret (own (con Float))))) (params state sample) (body (app + (app * state 0.5) sample)))) diff --git a/examples/fieldtest/embabi1_3_export_list_rejected.ail b/examples/fieldtest/embabi1_3_export_list_rejected.ail index 8f3c368..e247d04 100644 --- a/examples/fieldtest/embabi1_3_export_list_rejected.ail +++ b/examples/fieldtest/embabi1_3_export_list_rejected.ail @@ -16,7 +16,7 @@ (type (fn-type (params (own (con IntList))) - (ret (con Int)))) + (ret (own (con Int))))) (params xs) (body (match xs diff --git a/examples/fieldtest/embabi1_4_export_logged_counter_rejected.ail b/examples/fieldtest/embabi1_4_export_logged_counter_rejected.ail index 6d1ba2a..93affe2 100644 --- a/examples/fieldtest/embabi1_4_export_logged_counter_rejected.ail +++ b/examples/fieldtest/embabi1_4_export_logged_counter_rejected.ail @@ -13,8 +13,8 @@ (export "tick") (type (fn-type - (params (con Int)) - (ret (con Int)) + (params (own (con Int))) + (ret (own (con Int))) (effects IO))) (params n) (body diff --git a/examples/fieldtest/eqord_1_fizzbuzz.ail b/examples/fieldtest/eqord_1_fizzbuzz.ail index 73e1e33..8ff4fe9 100644 --- a/examples/fieldtest/eqord_1_fizzbuzz.ail +++ b/examples/fieldtest/eqord_1_fizzbuzz.ail @@ -34,8 +34,8 @@ (doc "Return the FizzBuzz label for n, or the empty string if n is a plain number.") (type (fn-type - (params (con Int)) - (ret (con Str)))) + (params (own (con Int))) + (ret (own (con Str))))) (params n) (body (if (app eq (app % n 15) 0) @@ -48,7 +48,7 @@ (fn emit_one (doc "Print the FizzBuzz label or the number itself.") - (type (fn-type (params (con Int)) (ret (con Unit)) (effects IO))) + (type (fn-type (params (own (con Int))) (ret (own (con Unit))) (effects IO))) (params n) (body (let label (app classify n) @@ -58,7 +58,7 @@ (fn loop (doc "Tail-recursive driver over [i..stop].") - (type (fn-type (params (con Int) (con Int)) (ret (con Unit)) (effects IO))) + (type (fn-type (params (own (con Int)) (own (con Int))) (ret (own (con Unit))) (effects IO))) (params i stop) (body (if (app gt i stop) @@ -68,6 +68,6 @@ (tail-app loop (app + i 1) stop))))) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (app loop 1 15)))) diff --git a/examples/fieldtest/eqord_2_rational_eq.ail b/examples/fieldtest/eqord_2_rational_eq.ail index 94254a1..67ee05a 100644 --- a/examples/fieldtest/eqord_2_rational_eq.ail +++ b/examples/fieldtest/eqord_2_rational_eq.ail @@ -40,7 +40,7 @@ (app eq (app * a d) (app * b c))))))))))) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (let r_1_2 (term-ctor Rational MkRational 1 2) diff --git a/examples/fieldtest/eqord_3_newton_sqrt.ail b/examples/fieldtest/eqord_3_newton_sqrt.ail index 62bdb4a..b6c9f59 100644 --- a/examples/fieldtest/eqord_3_newton_sqrt.ail +++ b/examples/fieldtest/eqord_3_newton_sqrt.ail @@ -25,7 +25,7 @@ (fn fabs (doc "Float absolute value via the named-fn comparison surface.") - (type (fn-type (params (con Float)) (ret (con Float)))) + (type (fn-type (params (own (con Float))) (ret (own (con Float))))) (params x) (body (if (app float_lt x 0.0) @@ -34,7 +34,7 @@ (fn iterate (doc "Tail-recursive Newton iteration. Stops when |xnew - x| < tol.") - (type (fn-type (params (con Float) (con Float) (con Float)) (ret (con Float)))) + (type (fn-type (params (own (con Float)) (own (con Float)) (own (con Float))) (ret (own (con Float))))) (params n x tol) (body (let xnew (app / (app + x (app / n x)) 2.0) @@ -44,7 +44,7 @@ (fn sqrt (doc "sqrt n via Newton with initial guess 1.0 and tolerance 1e-10. Returns 0.0 for n == 0.0; assumes n >= 0.") - (type (fn-type (params (con Float)) (ret (con Float)))) + (type (fn-type (params (own (con Float))) (ret (own (con Float))))) (params n) (body (if (app float_eq n 0.0) @@ -52,7 +52,7 @@ (app iterate n 1.0 0.0000000001)))) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (seq (app print (app sqrt 4.0)) diff --git a/examples/fieldtest/eqord_4_float_ord_must_fail.ail b/examples/fieldtest/eqord_4_float_ord_must_fail.ail index 0f7a634..edd73f8 100644 --- a/examples/fieldtest/eqord_4_float_ord_must_fail.ail +++ b/examples/fieldtest/eqord_4_float_ord_must_fail.ail @@ -18,6 +18,6 @@ (module eqord_4_float_ord_must_fail (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (app print (if (app lt 1.5 2.5) 1 0))))) diff --git a/examples/fieldtest/eqord_5_float_eq_must_fail.ail b/examples/fieldtest/eqord_5_float_eq_must_fail.ail index b00d2b3..d691d2d 100644 --- a/examples/fieldtest/eqord_5_float_eq_must_fail.ail +++ b/examples/fieldtest/eqord_5_float_eq_must_fail.ail @@ -11,6 +11,6 @@ (module eqord_5_float_eq_must_fail (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (app print (if (app eq 1.5 1.5) 1 0))))) diff --git a/examples/fieldtest/floats_1_newton_sqrt.ail b/examples/fieldtest/floats_1_newton_sqrt.ail index 21fc85c..d4a5e34 100644 --- a/examples/fieldtest/floats_1_newton_sqrt.ail +++ b/examples/fieldtest/floats_1_newton_sqrt.ail @@ -14,8 +14,8 @@ (doc "Recurse k times applying x' = 0.5 * (x + n/x).") (type (fn-type - (params (con Float) (con Float) (con Int)) - (ret (con Float)))) + (params (own (con Float)) (own (con Float)) (own (con Int))) + (ret (own (con Float))))) (params n x k) (body (if (app eq k 0) @@ -27,12 +27,12 @@ (fn sqrt (doc "20-step Newton iteration starting from x0 = n.") - (type (fn-type (params (con Float)) (ret (con Float)))) + (type (fn-type (params (own (con Float))) (ret (own (con Float))))) (params n) (body (app newton_iter n n 20))) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (app print (app sqrt 2.0))))) diff --git a/examples/fieldtest/floats_2_average_int_list.ail b/examples/fieldtest/floats_2_average_int_list.ail index cf533bc..fae8d1a 100644 --- a/examples/fieldtest/floats_2_average_int_list.ail +++ b/examples/fieldtest/floats_2_average_int_list.ail @@ -22,8 +22,8 @@ (doc "Tail-recursive sum with accumulator.") (type (fn-type - (params (con IntList) (con Int)) - (ret (con Int)))) + (params (borrow (con IntList)) (own (con Int))) + (ret (own (con Int))))) (params xs acc) (body (match xs @@ -35,8 +35,8 @@ (doc "Tail-recursive length with accumulator.") (type (fn-type - (params (con IntList) (con Int)) - (ret (con Int)))) + (params (borrow (con IntList)) (own (con Int))) + (ret (own (con Int))))) (params xs acc) (body (match xs @@ -46,7 +46,7 @@ (fn mean (doc "Mean as Float = sum / count, both promoted via int_to_float.") - (type (fn-type (params (con IntList)) (ret (con Float)))) + (type (fn-type (params (borrow (con IntList))) (ret (own (con Float))))) (params xs) (body (app / @@ -54,7 +54,7 @@ (app int_to_float (app count_acc xs 0))))) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (app print diff --git a/examples/fieldtest/floats_3_safe_division.ail b/examples/fieldtest/floats_3_safe_division.ail index a335639..183b934 100644 --- a/examples/fieldtest/floats_3_safe_division.ail +++ b/examples/fieldtest/floats_3_safe_division.ail @@ -32,7 +32,7 @@ (fn classify (doc "-1=NaN, 0=infinite, 1=finite. Uses is_nan + abs > huge.") - (type (fn-type (params (con Float)) (ret (con Int)))) + (type (fn-type (params (own (con Float))) (ret (own (con Int))))) (params x) (body (if (app is_nan x) @@ -44,7 +44,7 @@ 1))))) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (seq (app print (app / 6.0 3.0)) diff --git a/examples/fieldtest/floats_4_float_to_str_reach.ail b/examples/fieldtest/floats_4_float_to_str_reach.ail index 2305dd3..bf85b5b 100644 --- a/examples/fieldtest/floats_4_float_to_str_reach.ail +++ b/examples/fieldtest/floats_4_float_to_str_reach.ail @@ -20,7 +20,7 @@ (module floats_4_float_to_str_reach (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (do io/print_str (app float_to_str 3.14))))) diff --git a/examples/fieldtest/forma_1_factorial.ail b/examples/fieldtest/forma_1_factorial.ail index 52a0bb7..3e717d2 100644 --- a/examples/fieldtest/forma_1_factorial.ail +++ b/examples/fieldtest/forma_1_factorial.ail @@ -4,8 +4,8 @@ (doc "Tail-recursive factorial accumulator: fact_acc(n, acc) = n!*acc.") (type (fn-type - (params (con Int) (con Int)) - (ret (con Int)))) + (params (own (con Int)) (own (con Int))) + (ret (own (con Int))))) (params n acc) (body (if (app eq n 0) @@ -16,13 +16,13 @@ (doc "Factorial of a non-negative Int.") (type (fn-type - (params (con Int)) - (ret (con Int)))) + (params (own (con Int))) + (ret (own (con Int))))) (params n) (body (app fact_acc n 1))) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (seq diff --git a/examples/fieldtest/forma_2_show_color.ail b/examples/fieldtest/forma_2_show_color.ail index 8c6e662..7bb43b6 100644 --- a/examples/fieldtest/forma_2_show_color.ail +++ b/examples/fieldtest/forma_2_show_color.ail @@ -21,7 +21,7 @@ (case (pat-ctor Blue) "Blue"))))))) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (seq diff --git a/examples/fieldtest/forma_3_user_class_describe.ail b/examples/fieldtest/forma_3_user_class_describe.ail index e15d1d4..28378af 100644 --- a/examples/fieldtest/forma_3_user_class_describe.ail +++ b/examples/fieldtest/forma_3_user_class_describe.ail @@ -4,7 +4,7 @@ (doc "A user-defined typeclass: produce a short Str describing a value.") (param a) (method describe - (type (fn-type (params (borrow a)) (ret (con Str)))))) + (type (fn-type (params (borrow a)) (ret (own (con Str))))))) (data Shape (ctor Circle (con Int)) @@ -42,13 +42,13 @@ (constraints (constraint Describe a)) (fn-type (params (borrow a)) - (ret (con Unit)) + (ret (own (con Unit))) (effects IO)))) (params x) (body (do io/print_str (app describe x)))) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (seq diff --git a/examples/fieldtest/forma_4_intmath_lib.ail b/examples/fieldtest/forma_4_intmath_lib.ail index bef4cf0..77c5474 100644 --- a/examples/fieldtest/forma_4_intmath_lib.ail +++ b/examples/fieldtest/forma_4_intmath_lib.ail @@ -2,19 +2,19 @@ (fn square (doc "Multiply an Int by itself.") - (type (fn-type (params (con Int)) (ret (con Int)))) + (type (fn-type (params (own (con Int))) (ret (own (con Int))))) (params x) (body (app * x x))) (fn cube (doc "Raise an Int to the third power.") - (type (fn-type (params (con Int)) (ret (con Int)))) + (type (fn-type (params (own (con Int))) (ret (own (con Int))))) (params x) (body (app * x (app square x)))) (fn abs_int (doc "Absolute value of an Int.") - (type (fn-type (params (con Int)) (ret (con Int)))) + (type (fn-type (params (own (con Int))) (ret (own (con Int))))) (params x) (body (if (app lt x 0) diff --git a/examples/fieldtest/forma_4_intmath_main.ail b/examples/fieldtest/forma_4_intmath_main.ail index 8ab5af2..0fa8ac0 100644 --- a/examples/fieldtest/forma_4_intmath_main.ail +++ b/examples/fieldtest/forma_4_intmath_main.ail @@ -3,7 +3,7 @@ (import forma_4_intmath_lib) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (seq diff --git a/examples/fieldtest/kem_1_list_running_sum.ail b/examples/fieldtest/kem_1_list_running_sum.ail index 5ad8901..9bbc07b 100644 --- a/examples/fieldtest/kem_1_list_running_sum.ail +++ b/examples/fieldtest/kem_1_list_running_sum.ail @@ -16,13 +16,13 @@ (fn add (doc "Plain Int addition. Used as the (b, a) -> b arg to List.fold_left.") - (type (fn-type (params (con Int) (con Int)) (ret (con Int)))) + (type (fn-type (params (own (con Int)) (own (con Int))) (ret (own (con Int))))) (params acc x) (body (app + acc x))) (fn main (doc "Build a List of [1,2,3,4,5], print its length (5), then its sum (15).") - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (let xs diff --git a/examples/fieldtest/kem_2_counter_new.ail b/examples/fieldtest/kem_2_counter_new.ail index e3c13b7..342eb42 100644 --- a/examples/fieldtest/kem_2_counter_new.ail +++ b/examples/fieldtest/kem_2_counter_new.ail @@ -20,12 +20,12 @@ (fn new (doc "Build a Counter initialised to the given value.") - (type (fn-type (params (con Int)) (ret (con Counter)))) + (type (fn-type (params (own (con Int))) (ret (own (con Counter))))) (params n) (body (term-ctor Counter MkCounter n))) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (let c (new Counter 42) diff --git a/examples/fieldtest/kem_2b_min_repro.ail b/examples/fieldtest/kem_2b_min_repro.ail index 97b0cf3..663e500 100644 --- a/examples/fieldtest/kem_2b_min_repro.ail +++ b/examples/fieldtest/kem_2b_min_repro.ail @@ -10,12 +10,12 @@ (ctor MkCounter (con Int))) (fn value - (type (fn-type (params (con Counter)) (ret (con Int)))) + (type (fn-type (params (borrow (con Counter))) (ret (own (con Int))))) (params c) (body (match c (case (pat-ctor MkCounter x) x)))) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (let c (term-ctor Counter MkCounter 41) diff --git a/examples/fieldtest/kem_4_paramin_box_green.ail b/examples/fieldtest/kem_4_paramin_box_green.ail index a513601..580541d 100644 --- a/examples/fieldtest/kem_4_paramin_box_green.ail +++ b/examples/fieldtest/kem_4_paramin_box_green.ail @@ -17,12 +17,12 @@ (fn unwrap (doc "Project the contained Int out of a NumBox.") - (type (fn-type (params (con NumBox (con Int))) (ret (con Int)))) + (type (fn-type (params (own (con NumBox (con Int)))) (ret (own (con Int))))) (params b) (body (match b (case (pat-ctor MkNumBox x) x)))) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (let b (term-ctor NumBox MkNumBox 17) diff --git a/examples/fieldtest/kem_4_paramin_box_red.ail b/examples/fieldtest/kem_4_paramin_box_red.ail index 5e69250..68a92c1 100644 --- a/examples/fieldtest/kem_4_paramin_box_red.ail +++ b/examples/fieldtest/kem_4_paramin_box_red.ail @@ -13,11 +13,11 @@ (fn unwrap_str (doc "BAD: NumBox is restricted to {Int, Float}, but we ask for Str. Expected: ParamNotInRestrictedSet on the type signature.") - (type (fn-type (params (con NumBox (con Str))) (ret (con Str)))) + (type (fn-type (params (own (con NumBox (con Str)))) (ret (own (con Str))))) (params b) (body (match b (case (pat-ctor MkNumBox x) x)))) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (do io/print_str "")))) diff --git a/examples/fieldtest/loop_recur_1_isqrt_newton.ail b/examples/fieldtest/loop_recur_1_isqrt_newton.ail index 1d42b5a..276cb21 100644 --- a/examples/fieldtest/loop_recur_1_isqrt_newton.ail +++ b/examples/fieldtest/loop_recur_1_isqrt_newton.ail @@ -1,7 +1,7 @@ (module loop_recur_1_isqrt_newton (fn main (doc "Integer square root of 152399025 (= 12345^2) via Newton's method, then a sanity-difference. Expected stdout: 12345 then 0.") - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (let r (app isqrt 152399025) @@ -9,7 +9,7 @@ (app print (app - r 12345)))))) (fn isqrt (doc "Newton's method for floor(sqrt(n)). Two binders: x (current guess) and prev (previous guess, to detect the fixpoint/2-cycle). The recur is buried inside a nested if, not at body toplevel.") - (type (fn-type (params (con Int)) (ret (con Int)))) + (type (fn-type (params (own (con Int))) (ret (own (con Int))))) (params n) (body (if (app lt n 2) diff --git a/examples/fieldtest/loop_recur_2_collatz.ail b/examples/fieldtest/loop_recur_2_collatz.ail index d9202d8..8de46a4 100644 --- a/examples/fieldtest/loop_recur_2_collatz.ail +++ b/examples/fieldtest/loop_recur_2_collatz.ail @@ -1,7 +1,7 @@ (module loop_recur_2_collatz (fn main (doc "Collatz step counts. collatz_len(27)=111, collatz_len(97)=118, collatz_len(1)=0. Expected stdout (per line): 111, 118, 0.") - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (seq (app print (app collatz_len 27)) @@ -9,7 +9,7 @@ (app print (app collatz_len 1)))))) (fn collatz_len (doc "Number of Collatz steps to reach 1. Two binders: n (current value) and steps (accumulator). Parity dispatch is a `match` on n%2; the recur lives in the tail of each match arm, not at body toplevel.") - (type (fn-type (params (con Int)) (ret (con Int)))) + (type (fn-type (params (own (con Int))) (ret (own (con Int))))) (params start) (body (loop (n (con Int) start) (steps (con Int) 0) diff --git a/examples/fieldtest/loop_recur_3a_recur_outside_loop.ail b/examples/fieldtest/loop_recur_3a_recur_outside_loop.ail index 8cdb5f0..fb3bb8d 100644 --- a/examples/fieldtest/loop_recur_3a_recur_outside_loop.ail +++ b/examples/fieldtest/loop_recur_3a_recur_outside_loop.ail @@ -4,13 +4,13 @@ ; Expected: `ail check` exits 1 with code recur-outside-loop. (module loop_recur_3a_recur_outside_loop (fn countdown - (type (fn-type (params (con Int)) (ret (con Int)))) + (type (fn-type (params (own (con Int))) (ret (own (con Int))))) (params n) (body (if (app eq n 0) 0 (recur (app - n 1))))) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (app print (app countdown 5))))) diff --git a/examples/fieldtest/loop_recur_3b_recur_arity.ail b/examples/fieldtest/loop_recur_3b_recur_arity.ail index 25b7369..6f24b3c 100644 --- a/examples/fieldtest/loop_recur_3b_recur_arity.ail +++ b/examples/fieldtest/loop_recur_3b_recur_arity.ail @@ -4,7 +4,7 @@ ; Expected: `ail check` exits 1 with code recur-arity-mismatch. (module loop_recur_3b_recur_arity (fn sum_to - (type (fn-type (params (con Int)) (ret (con Int)))) + (type (fn-type (params (own (con Int))) (ret (own (con Int))))) (params n) (body (loop (acc (con Int) 0) (i (con Int) 1) @@ -12,6 +12,6 @@ acc (recur (app + acc i)))))) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (app print (app sum_to 10))))) diff --git a/examples/fieldtest/loop_recur_3c_recur_type.ail b/examples/fieldtest/loop_recur_3c_recur_type.ail index d7f8f73..1a70218 100644 --- a/examples/fieldtest/loop_recur_3c_recur_type.ail +++ b/examples/fieldtest/loop_recur_3c_recur_type.ail @@ -4,7 +4,7 @@ ; Expected: `ail check` exits 1 with code recur-type-mismatch. (module loop_recur_3c_recur_type (fn count_down - (type (fn-type (params (con Int)) (ret (con Int)))) + (type (fn-type (params (own (con Int))) (ret (own (con Int))))) (params start) (body (loop (i (con Int) start) @@ -12,6 +12,6 @@ i (recur (app gt i 0)))))) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (app print (app count_down 5))))) diff --git a/examples/fieldtest/loop_recur_3d_recur_not_tail.ail b/examples/fieldtest/loop_recur_3d_recur_not_tail.ail index 1594290..1eb0cc5 100644 --- a/examples/fieldtest/loop_recur_3d_recur_not_tail.ail +++ b/examples/fieldtest/loop_recur_3d_recur_not_tail.ail @@ -5,7 +5,7 @@ ; Expected: `ail check` exits 1 with code recur-not-in-tail-position. (module loop_recur_3d_recur_not_tail (fn factorial - (type (fn-type (params (con Int)) (ret (con Int)))) + (type (fn-type (params (own (con Int))) (ret (own (con Int))))) (params n) (body (loop (i (con Int) n) @@ -13,6 +13,6 @@ 1 (app * i (recur (app - i 1))))))) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (app print (app factorial 5))))) diff --git a/examples/fieldtest/loop_recur_3e_binder_captured.ail b/examples/fieldtest/loop_recur_3e_binder_captured.ail index 6a8d46c..7ca940e 100644 --- a/examples/fieldtest/loop_recur_3e_binder_captured.ail +++ b/examples/fieldtest/loop_recur_3e_binder_captured.ail @@ -5,11 +5,11 @@ ; Expected: `ail check` exits 1 with code loop-binder-captured-by-lambda. (module loop_recur_3e_binder_captured (fn apply_int - (type (fn-type (params (fn-type (params (con Int)) (ret (con Int))) (con Int)) (ret (con Int)))) + (type (fn-type (params (own (fn-type (params (own (con Int))) (ret (own (con Int))))) (own (con Int))) (ret (own (con Int))))) (params f x) (body (app f x))) (fn sum_to - (type (fn-type (params (con Int)) (ret (con Int)))) + (type (fn-type (params (own (con Int))) (ret (own (con Int))))) (params n) (body (loop (acc (con Int) 0) (i (con Int) 1) @@ -18,6 +18,6 @@ (recur (app apply_int (lam (params (typed d (con Int))) (ret (con Int)) (body (app + acc d))) i) (app + i 1)))))) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (app print (app sum_to 10))))) diff --git a/examples/fieldtest/loop_recur_4_gcd_value_pos.ail b/examples/fieldtest/loop_recur_4_gcd_value_pos.ail index dbae052..54312e1 100644 --- a/examples/fieldtest/loop_recur_4_gcd_value_pos.ail +++ b/examples/fieldtest/loop_recur_4_gcd_value_pos.ail @@ -10,7 +10,7 @@ (module loop_recur_4_gcd_value_pos (fn gcd (doc "Euclidean gcd via loop/recur. Two binders a,b; recur in the tail of the else branch.") - (type (fn-type (params (con Int) (con Int)) (ret (con Int)))) + (type (fn-type (params (own (con Int)) (own (con Int))) (ret (own (con Int))))) (params x y) (body (loop (a (con Int) x) (b (con Int) y) @@ -18,7 +18,7 @@ a (recur b (app % a b)))))) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (app print (app + (app gcd 48 18) (app gcd 1071 462)))))) diff --git a/examples/fieldtest/loop_recur_5_event_loop_noterm.ail b/examples/fieldtest/loop_recur_5_event_loop_noterm.ail index 830ceb2..c2d6b42 100644 --- a/examples/fieldtest/loop_recur_5_event_loop_noterm.ail +++ b/examples/fieldtest/loop_recur_5_event_loop_noterm.ail @@ -6,7 +6,7 @@ (module loop_recur_5_event_loop_noterm (fn run_forever (doc "Infinite event loop. Two binders: tick counter and a running parity flag. No branch ever exits via a non-recur term.") - (type (fn-type (params) (ret (con Int)))) + (type (fn-type (params) (ret (own (con Int))))) (params) (body (loop (tick (con Int) 0) (parity (con Int) 0) @@ -14,6 +14,6 @@ (recur (app + tick 1) 1) (recur (app + tick 1) 0))))) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (app print (app run_forever))))) diff --git a/examples/fieldtest/loop_recur_5b_event_loop_noterm.ail b/examples/fieldtest/loop_recur_5b_event_loop_noterm.ail index 8a00b3a..5a3e5ac 100644 --- a/examples/fieldtest/loop_recur_5b_event_loop_noterm.ail +++ b/examples/fieldtest/loop_recur_5b_event_loop_noterm.ail @@ -8,7 +8,7 @@ (module loop_recur_5b_event_loop_noterm (fn run_forever (doc "Infinite event loop. Two binders: tick counter and a running parity flag. No branch ever exits via a non-recur term.") - (type (fn-type (params (con Int)) (ret (con Int)))) + (type (fn-type (params (own (con Int))) (ret (own (con Int))))) (params start) (body (loop (tick (con Int) start) (parity (con Int) 0) @@ -16,6 +16,6 @@ (recur (app + tick 1) 1) (recur (app + tick 1) 0))))) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (app print (app run_forever 0))))) diff --git a/examples/fieldtest/mut-local_1_factorial.ail b/examples/fieldtest/mut-local_1_factorial.ail index f477fdc..78561b3 100644 --- a/examples/fieldtest/mut-local_1_factorial.ail +++ b/examples/fieldtest/mut-local_1_factorial.ail @@ -3,7 +3,7 @@ (module mut-local_1_factorial (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (app print (let prod 1 (let prod (app * prod 1) (let prod (app * prod 2) (let prod (app * prod 3) (let prod (app * prod 4) (let prod (app * prod 5) prod)))))))))) diff --git a/examples/fieldtest/mut-local_2_classify_temp.ail b/examples/fieldtest/mut-local_2_classify_temp.ail index 2153bec..dc4f0c6 100644 --- a/examples/fieldtest/mut-local_2_classify_temp.ail +++ b/examples/fieldtest/mut-local_2_classify_temp.ail @@ -5,12 +5,12 @@ (fn classify (doc "Return category code 0..3 for temperature t in degrees C.") - (type (fn-type (params (con Int)) (ret (con Int)))) + (type (fn-type (params (own (con Int))) (ret (own (con Int))))) (params t) (body (let code 0 (let code (if (app lt t 0) 0 (if (app lt t 15) 1 (if (app lt t 28) 2 3))) code)))) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (app print (app classify 22))))) diff --git a/examples/fieldtest/mut-local_3_horner.ail b/examples/fieldtest/mut-local_3_horner.ail index 7ceb9a5..c1b415a 100644 --- a/examples/fieldtest/mut-local_3_horner.ail +++ b/examples/fieldtest/mut-local_3_horner.ail @@ -4,7 +4,7 @@ (module mut-local_3_horner (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (app print (let acc 2.0 (let acc (app - (app * acc 2.5) 3.0) (let acc (app + (app * acc 2.5) 5.0) (let acc (app - (app * acc 2.5) 7.0) acc)))))))) diff --git a/examples/fieldtest/mut-local_4_has_small_factor.ail b/examples/fieldtest/mut-local_4_has_small_factor.ail index e9150b4..9b71ce3 100644 --- a/examples/fieldtest/mut-local_4_has_small_factor.ail +++ b/examples/fieldtest/mut-local_4_has_small_factor.ail @@ -5,12 +5,12 @@ (module mut-local_4_has_small_factor (fn has_small_factor - (type (fn-type (params (con Int)) (ret (con Bool)))) + (type (fn-type (params (own (con Int))) (ret (own (con Bool))))) (params n) (body (let found false (let found (if (app eq (app % n 2) 0) true found) (let found (if (app eq (app % n 3) 0) true found) (let found (if (app eq (app % n 5) 0) true found) (let found (if (app eq (app % n 7) 0) true found) found))))))) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (app print (app has_small_factor 91))))) diff --git a/examples/fieldtest/rawbuf_1_score_table.ail b/examples/fieldtest/rawbuf_1_score_table.ail index 87f593b..70fc2c8 100644 --- a/examples/fieldtest/rawbuf_1_score_table.ail +++ b/examples/fieldtest/rawbuf_1_score_table.ail @@ -28,7 +28,7 @@ (fn fill (doc "Fill slots 0..n of buf with (i*i + 1). Linear: own in, own out.") - (type (fn-type (params (own (con RawBuf (con Int))) (con Int)) (ret (own (con RawBuf (con Int)))))) + (type (fn-type (params (own (con RawBuf (con Int))) (own (con Int))) (ret (own (con RawBuf (con Int)))))) (params buf n) (body (loop (b (con RawBuf (con Int)) buf) (i (con Int) 0) @@ -39,7 +39,7 @@ (fn total (doc "Sum every slot of buf, driving the bound off RawBuf.size.") - (type (fn-type (params (borrow (con RawBuf (con Int)))) (ret (con Int)))) + (type (fn-type (params (borrow (con RawBuf (con Int)))) (ret (own (con Int))))) (params buf) (body (loop (acc (con Int) 0) (i (con Int) 0) @@ -49,7 +49,7 @@ (app + i 1)))))) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (let buf (new RawBuf (con Int) 5) diff --git a/examples/fieldtest/rawbuf_2_running_max.ail b/examples/fieldtest/rawbuf_2_running_max.ail index a8d999a..2082a0f 100644 --- a/examples/fieldtest/rawbuf_2_running_max.ail +++ b/examples/fieldtest/rawbuf_2_running_max.ail @@ -21,7 +21,7 @@ (module rawbuf_2_running_max (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (let buf (new RawBuf (con Int) 4) diff --git a/examples/fieldtest/rawbuf_3_sensor_pair.ail b/examples/fieldtest/rawbuf_3_sensor_pair.ail index 79725fb..5c68ee7 100644 --- a/examples/fieldtest/rawbuf_3_sensor_pair.ail +++ b/examples/fieldtest/rawbuf_3_sensor_pair.ail @@ -16,7 +16,7 @@ (module rawbuf_3_sensor_pair (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (let readings (new RawBuf (con Float) 3) diff --git a/examples/fieldtest/rawbuf_4_paramin_reject.ail b/examples/fieldtest/rawbuf_4_paramin_reject.ail index 06977a5..92d96d9 100644 --- a/examples/fieldtest/rawbuf_4_paramin_reject.ail +++ b/examples/fieldtest/rawbuf_4_paramin_reject.ail @@ -30,6 +30,6 @@ (fn first_price (doc "Read the first Tick out of a batch buffer. Should never check: RawBuf cannot hold a user ADT.") - (type (fn-type (params (borrow (con RawBuf (con Tick)))) (ret (con Tick)))) + (type (fn-type (params (borrow (con RawBuf (con Tick)))) (ret (own (con Tick))))) (params batch) (body (app RawBuf.get batch 0)))) diff --git a/examples/fieldtest/rbx_1_float_window_stats.ail b/examples/fieldtest/rbx_1_float_window_stats.ail index 03525e4..8845d6b 100644 --- a/examples/fieldtest/rbx_1_float_window_stats.ail +++ b/examples/fieldtest/rbx_1_float_window_stats.ail @@ -41,7 +41,7 @@ (fn build_window (doc "Allocate a 4-slot Float buffer, fill slot i with (2*(i+1)), wrap in a Window with count=4.") - (type (fn-type (params (con Int)) (ret (own (con Window))))) + (type (fn-type (params (own (con Int))) (ret (own (con Window))))) (params n) (body (let buf (new RawBuf (con Float) 4) @@ -55,7 +55,7 @@ (fn mean (doc "Average of the first count slots, read through a borrow of the Window.") - (type (fn-type (params (borrow (con Window))) (ret (con Float)))) + (type (fn-type (params (borrow (con Window))) (ret (own (con Float))))) (params w) (body (match w @@ -70,7 +70,7 @@ (fn wmax (doc "Maximum of the first count slots, read through a borrow of the Window.") - (type (fn-type (params (borrow (con Window))) (ret (con Float)))) + (type (fn-type (params (borrow (con Window))) (ret (own (con Float))))) (params w) (body (match w @@ -84,7 +84,7 @@ (app + i 1)))))))) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (let w (app build_window 4) diff --git a/examples/fieldtest/rbx_2_bool_sieve.ail b/examples/fieldtest/rbx_2_bool_sieve.ail index d7a95d9..0704302 100644 --- a/examples/fieldtest/rbx_2_bool_sieve.ail +++ b/examples/fieldtest/rbx_2_bool_sieve.ail @@ -28,7 +28,7 @@ (fn mark_even (doc "Set slot i to (i is even) for i in 0..n. Linear own->own.") - (type (fn-type (params (own (con RawBuf (con Bool))) (con Int)) (ret (own (con RawBuf (con Bool)))))) + (type (fn-type (params (own (con RawBuf (con Bool))) (own (con Int))) (ret (own (con RawBuf (con Bool)))))) (params buf n) (body (loop (b (con RawBuf (con Bool)) buf) (i (con Int) 0) @@ -39,7 +39,7 @@ (fn count_set (doc "Count the set flags, reading the Bool buffer through a borrow.") - (type (fn-type (params (borrow (con RawBuf (con Bool)))) (ret (con Int)))) + (type (fn-type (params (borrow (con RawBuf (con Bool)))) (ret (own (con Int))))) (params buf) (body (loop (acc (con Int) 0) (i (con Int) 0) @@ -49,7 +49,7 @@ (app + i 1)))))) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (let buf (new RawBuf (con Bool) 6) diff --git a/examples/fieldtest/rbx_3_unit_element_reject.ail b/examples/fieldtest/rbx_3_unit_element_reject.ail index a7fcf34..acaeb33 100644 --- a/examples/fieldtest/rbx_3_unit_element_reject.ail +++ b/examples/fieldtest/rbx_3_unit_element_reject.ail @@ -17,12 +17,12 @@ (fn make_presence (doc "Try to allocate a RawBuf of Unit — forbidden element type.") - (type (fn-type (params (con Int)) (ret (own (con RawBuf (con Unit)))))) + (type (fn-type (params (own (con Int))) (ret (own (con RawBuf (con Unit)))))) (params n) (body (new RawBuf (con Unit) n))) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (let buf (app make_presence 3) diff --git a/examples/fieldtest/rbx_4_int_two_borrow_helpers.ail b/examples/fieldtest/rbx_4_int_two_borrow_helpers.ail index b4f5a55..c990eca 100644 --- a/examples/fieldtest/rbx_4_int_two_borrow_helpers.ail +++ b/examples/fieldtest/rbx_4_int_two_borrow_helpers.ail @@ -28,7 +28,7 @@ (fn fill (doc "Fill slot i with fib(i) for i in 0..n. Linear own->own.") - (type (fn-type (params (own (con RawBuf (con Int))) (con Int)) (ret (own (con RawBuf (con Int)))))) + (type (fn-type (params (own (con RawBuf (con Int))) (own (con Int))) (ret (own (con RawBuf (con Int)))))) (params buf n) (body ; Seed slots 0 and 1 to 1, then each later slot = sum of prior two, @@ -45,7 +45,7 @@ (fn total (doc "Sum every slot, borrow receiver.") - (type (fn-type (params (borrow (con RawBuf (con Int)))) (ret (con Int)))) + (type (fn-type (params (borrow (con RawBuf (con Int)))) (ret (own (con Int))))) (params buf) (body (loop (acc (con Int) 0) (i (con Int) 0) @@ -56,7 +56,7 @@ (fn argmax (doc "Index of the largest slot, borrow receiver. Tracks (best_i, best_v).") - (type (fn-type (params (borrow (con RawBuf (con Int)))) (ret (con Int)))) + (type (fn-type (params (borrow (con RawBuf (con Int)))) (ret (own (con Int))))) (params buf) (body (loop (best_i (con Int) 0) (best_v (con Int) (app RawBuf.get buf 0)) (i (con Int) 0) @@ -67,7 +67,7 @@ (recur best_i best_v (app + i 1))))))) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (let buf (new RawBuf (con Int) 6) diff --git a/examples/fieldtest/remove-mut_1_sum_of_squares.ail b/examples/fieldtest/remove-mut_1_sum_of_squares.ail index 63b31f2..799ec93 100644 --- a/examples/fieldtest/remove-mut_1_sum_of_squares.ail +++ b/examples/fieldtest/remove-mut_1_sum_of_squares.ail @@ -14,7 +14,7 @@ (fn sum_sq (doc "Sum of i*i for i in 1..=n via loop/recur. Two binders: i (counter), acc (running total).") - (type (fn-type (params (con Int)) (ret (con Int)))) + (type (fn-type (params (own (con Int))) (ret (own (con Int))))) (params n) (body (loop (i (con Int) 1) (acc (con Int) 0) @@ -23,7 +23,7 @@ (recur (app + i 1) (app + acc (app * i i))))))) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (app print (app sum_sq 10))))) diff --git a/examples/fieldtest/remove-mut_2_grade_cascade.ail b/examples/fieldtest/remove-mut_2_grade_cascade.ail index d6eb0d2..2ee479f 100644 --- a/examples/fieldtest/remove-mut_2_grade_cascade.ail +++ b/examples/fieldtest/remove-mut_2_grade_cascade.ail @@ -19,7 +19,7 @@ (fn grade (doc "Letter-grade code (4=A..0=F) from a numeric score, written as the let-threaded equivalent of a mutable cascade.") - (type (fn-type (params (con Int)) (ret (con Int)))) + (type (fn-type (params (own (con Int))) (ret (own (con Int))))) (params score) (body (let g 0 @@ -30,7 +30,7 @@ g))))))) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (seq (app print (app grade 95)) diff --git a/examples/fieldtest/remove-mut_3_horner_poly.ail b/examples/fieldtest/remove-mut_3_horner_poly.ail index e2e37a3..74023ff 100644 --- a/examples/fieldtest/remove-mut_3_horner_poly.ail +++ b/examples/fieldtest/remove-mut_3_horner_poly.ail @@ -18,7 +18,7 @@ (fn poly (doc "Horner-form evaluation of 2x^4+3x^3+0x^2+5x+7 as a straight-line let chain (the faithful let/if form of a mutable acc).") - (type (fn-type (params (con Int)) (ret (con Int)))) + (type (fn-type (params (own (con Int))) (ret (own (con Int))))) (params x) (body (let acc 2 @@ -29,7 +29,7 @@ acc))))))) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (app print (app poly 2))))) diff --git a/examples/fieldtest/remove-mut_4_bracket_scanner.ail b/examples/fieldtest/remove-mut_4_bracket_scanner.ail index 8774844..7c873c7 100644 --- a/examples/fieldtest/remove-mut_4_bracket_scanner.ail +++ b/examples/fieldtest/remove-mut_4_bracket_scanner.ail @@ -39,8 +39,8 @@ (doc "Thread (rest, depth, ok). Each arm updates a subset of state; the unchanged components must be restated in every tail call.") (type (fn-type - (params (own (con TokStream)) (con Int) (con Bool)) - (ret (con Bool)))) + (params (own (con TokStream)) (own (con Int)) (own (con Bool))) + (ret (own (con Bool))))) (params rest depth ok) (body (match rest @@ -58,12 +58,12 @@ (tail-app scan more depth ok))))))) (fn balanced - (type (fn-type (params (own (con TokStream))) (ret (con Bool)))) + (type (fn-type (params (own (con TokStream))) (ret (own (con Bool))))) (params s) (body (app scan s 0 true))) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (seq diff --git a/examples/flat_pat_shadow_control.ail b/examples/flat_pat_shadow_control.ail index d09684b..41f359b 100644 --- a/examples/flat_pat_shadow_control.ail +++ b/examples/flat_pat_shadow_control.ail @@ -6,21 +6,21 @@ (ctor MkBox a)) (fn blen (doc "borrow-only length over a borrowed IntList") - (type (fn-type (params (borrow (con IntList))) (ret (con Int)))) + (type (fn-type (params (borrow (con IntList))) (ret (own (con Int))))) (params xs) (body (match xs (case (pat-ctor Nil) 0) (case (pat-ctor Cons h t) (app + 1 (app blen t)))))) (fn csum (doc "consuming sum over an IntList") - (type (fn-type (params (con IntList)) (ret (con Int)))) + (type (fn-type (params (own (con IntList))) (ret (own (con Int))))) (params xs) (body (match xs (case (pat-ctor Nil) 0) (case (pat-ctor Cons h t) (app + h (app csum t)))))) (fn main (doc "Alpha-renamed control for flat_pat_shadow_leak.ail (refs #43): structurally identical, but the inner flat-match pattern-binder is `y` (no shadow of the outer `x`). The inner payload's arm-close drop fires because (def,\"y\") does not collide with (def,\"x\"). This is the live-count baseline the shadow fixture must reach once binder names are unique per fn at desugar (C').") - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (let x (term-ctor IntList Cons 1 (term-ctor IntList Nil)) diff --git a/examples/flat_pat_shadow_leak.ail b/examples/flat_pat_shadow_leak.ail index c97fa5b..1b61c7a 100644 --- a/examples/flat_pat_shadow_leak.ail +++ b/examples/flat_pat_shadow_leak.ail @@ -6,21 +6,21 @@ (ctor MkBox a)) (fn blen (doc "borrow-only length over a borrowed IntList") - (type (fn-type (params (borrow (con IntList))) (ret (con Int)))) + (type (fn-type (params (borrow (con IntList))) (ret (own (con Int))))) (params xs) (body (match xs (case (pat-ctor Nil) 0) (case (pat-ctor Cons h t) (app + 1 (app blen t)))))) (fn csum (doc "consuming sum over an IntList") - (type (fn-type (params (con IntList)) (ret (con Int)))) + (type (fn-type (params (own (con IntList))) (ret (own (con Int))))) (params xs) (body (match xs (case (pat-ctor Nil) 0) (case (pat-ctor Cons h t) (app + h (app csum t)))))) (fn main (doc "A2b completeness fixture (refs #43): a flat-match pattern-binder `x` shadows the outer let `x`. The inner `x` (Box payload, a heap IntList) is borrow-only (consume_count 0) and must be dropped at arm close; the outer `x` is consumed once by csum (count 1). Under the (def,name) uniqueness-table collision the arm-close drop gate (match_lower.rs) reads the collapsed outer count 1 and suppresses the inner payload's drop, leaking 2 cells. Pairs with flat_pat_shadow_control.ail (same program, inner binder alpha-renamed to `y`); the live-count delta is exactly the suppressed drop. GREEN once binder names are made unique per fn at desugar.") - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (let x (term-ctor IntList Cons 1 (term-ctor IntList Nil)) diff --git a/examples/float_compare_smoke.ail b/examples/float_compare_smoke.ail index e5be927..fce3e49 100644 --- a/examples/float_compare_smoke.ail +++ b/examples/float_compare_smoke.ail @@ -1,7 +1,7 @@ (module float_compare_smoke (fn main (doc "Float comparison via the named-fn surface that replaces the deleted operator names. Each float_* call lowers to a single fcmp via try_emit_primitive_instance_body.") - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (seq (seq (app print (app float_eq 1.5 1.5)) (do io/print_str "\n")) diff --git a/examples/float_to_str_smoke.ail b/examples/float_to_str_smoke.ail index af4337c..20076a9 100644 --- a/examples/float_to_str_smoke.ail +++ b/examples/float_to_str_smoke.ail @@ -1,6 +1,6 @@ (module float_to_str_smoke (fn main (doc "Iter hs.4: smoke pin for the float_to_str heap-Str builtin. `do io/print_str(float_to_str(3.5))` builds through the whole pipeline (checker resolves float_to_str:(Float)->Str, codegen lowers to call ptr @ailang_float_to_str(double 3.5), runtime/str.c's ailang_float_to_str snprintfs into a heap-Str slab via libc %g plus a `.0`-fallback for finite whole-valued doubles whose %g output lacks `.`/`e`/`E` (Gitea #7), io/print_str's @puts prints from the bytes pointer at offset 8). The exact rendering of 3.5 is libc-target-dependent; on the dev target's clang+glibc with default C locale it is the three bytes `3.5` (the fallback does not fire because `.` is already present). Single-value smoke; NaN / +Inf / -Inf edge cases are deferred per the hs.4 plan.") - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (seq (do io/print_str (app float_to_str 3.5)) (do io/print_str "\n"))))) diff --git a/examples/floats.ail b/examples/floats.ail index 0eddfd0..5ca7d0b 100644 --- a/examples/floats.ail +++ b/examples/floats.ail @@ -1,5 +1,5 @@ (module floats (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (seq (seq (app print (app + 1.5 2.5)) (do io/print_str "\n")) (seq (seq (app print (app int_to_float 42)) (do io/print_str "\n")) (seq (app print (app neg 1.5)) (do io/print_str "\n"))))))) diff --git a/examples/ge_at_int_smoke.ail b/examples/ge_at_int_smoke.ail index 003a5fd..99638cd 100644 --- a/examples/ge_at_int_smoke.ail +++ b/examples/ge_at_int_smoke.ail @@ -1,5 +1,5 @@ (module ge_at_int_smoke (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (app print (if (app ge 3 5) 1 0))))) diff --git a/examples/gt_at_bool_smoke.ail b/examples/gt_at_bool_smoke.ail index ee0baf0..c795c90 100644 --- a/examples/gt_at_bool_smoke.ail +++ b/examples/gt_at_bool_smoke.ail @@ -1,5 +1,5 @@ (module gt_at_bool_smoke (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (app print (if (app gt true false) 1 0))))) diff --git a/examples/heap_str_repeated_print_borrow.ail b/examples/heap_str_repeated_print_borrow.ail index 789a5db..f0fb0cc 100644 --- a/examples/heap_str_repeated_print_borrow.ail +++ b/examples/heap_str_repeated_print_borrow.ail @@ -1,6 +1,6 @@ (module heap_str_repeated_print_borrow (fn main (doc "Iter eob.1: heap-Str passed to io/print_str twice in sequence. Under the new rule (Term::Do args = Borrow), the second call no longer triggers use-after-consume; the linearity check accepts the program. Under --alloc=rc + AILANG_RC_STATS=1 the slab allocates once and is freed once at scope close (allocs == 1, frees == 1, live == 0).") - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (let s (app int_to_str 42) (seq (seq (do io/print_str s) (do io/print_str "\n")) (seq (do io/print_str s) (do io/print_str "\n"))))))) diff --git a/examples/hello.ail b/examples/hello.ail index 425a55c..2b2781c 100644 --- a/examples/hello.ail +++ b/examples/hello.ail @@ -3,6 +3,6 @@ (module hello (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (do io/print_str "Hello, AILang.")))) diff --git a/examples/hof.ail b/examples/hof.ail index 0be6ba2..1ecc3fc 100644 --- a/examples/hof.ail +++ b/examples/hof.ail @@ -1,15 +1,15 @@ (module hof (fn inc (doc "increment by one") - (type (fn-type (params (con Int)) (ret (con Int)))) + (type (fn-type (params (own (con Int))) (ret (own (con Int))))) (params x) (body (app + x 1))) (fn apply (doc "apply a fn-of-Int to an Int") - (type (fn-type (params (fn-type (params (con Int)) (ret (con Int))) (con Int)) (ret (con Int)))) + (type (fn-type (params (own (fn-type (params (own (con Int))) (ret (own (con Int))))) (own (con Int))) (ret (own (con Int))))) (params f x) (body (app f x))) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (app print (app apply inc 41))))) diff --git a/examples/int_to_print_int_borrow.ail b/examples/int_to_print_int_borrow.ail index 4e5635a..a899273 100644 --- a/examples/int_to_print_int_borrow.ail +++ b/examples/int_to_print_int_borrow.ail @@ -1,6 +1,6 @@ (module int_to_print_int_borrow (fn main (doc "Iter eob.1 / rpe.1: primitive Int passed to the polymorphic `print` helper (Show Int). The rule that Term::Do args are Borrow has no observable RC effect here because Int is unboxed; the test pins that no spurious bookkeeping appears (allocs == 0, frees == 0, live == 0).") - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (let n 7 (seq (app print n) (do io/print_str "\n")))))) diff --git a/examples/int_to_str_drop_rc.ail b/examples/int_to_str_drop_rc.ail index e6eda35..bb9e703 100644 --- a/examples/int_to_str_drop_rc.ail +++ b/examples/int_to_str_drop_rc.ail @@ -1,6 +1,6 @@ (module int_to_str_drop_rc (fn main (doc "Iter hs.4: pin that `int_to_str` participates in RC discipline. Bind the heap-Str result to `s`, consume `s` once in `io/print_str`, let the binder drop at scope close. Under --alloc=rc with AILANG_RC_STATS=1 the atexit summary must report allocs == frees && live == 0 — the heap-Str slab allocated by str_alloc inside ailang_int_to_str rides the same rc_header + ailang_rc_dec path as any other RC value.") - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (let s (app int_to_str 42) (seq (do io/print_str s) (do io/print_str "\n")))))) diff --git a/examples/int_to_str_smoke.ail b/examples/int_to_str_smoke.ail index 140e41d..429f045 100644 --- a/examples/int_to_str_smoke.ail +++ b/examples/int_to_str_smoke.ail @@ -1,6 +1,6 @@ (module int_to_str_smoke (fn main (doc "Iter hs.4: smoke pin for the int_to_str heap-Str builtin. `do io/print_str(int_to_str(42))` builds through the whole pipeline (checker resolves int_to_str:(Int)->Str, codegen lowers to call ptr @ailang_int_to_str(i64 42), runtime/str.c's ailang_int_to_str snprintfs into a heap-Str slab, io/print_str's @puts prints from the bytes pointer at offset 8). Single-value smoke; the spec's broader edge-case sweep (0, -1, i64::MAX, i64::MIN) is deferred to a follow-up tidy iter per the hs.4 plan.") - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (seq (do io/print_str (app int_to_str 42)) (do io/print_str "\n"))))) diff --git a/examples/kernel_intrinsic_smoke.ail b/examples/kernel_intrinsic_smoke.ail index 608ac08..0bed55f 100644 --- a/examples/kernel_intrinsic_smoke.ail +++ b/examples/kernel_intrinsic_smoke.ail @@ -2,6 +2,6 @@ (kernel) (fn smoke (doc "Schema-coverage fixture: exercises Term::Intrinsic in a kernel-tier module.") - (type (fn-type (params) (ret (con Int)))) + (type (fn-type (params) (ret (own (con Int))))) (params) (intrinsic))) diff --git a/examples/le_at_str_smoke.ail b/examples/le_at_str_smoke.ail index 37ac1c9..d8f8b58 100644 --- a/examples/le_at_str_smoke.ail +++ b/examples/le_at_str_smoke.ail @@ -1,5 +1,5 @@ (module le_at_str_smoke (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (app print (if (app le "abc" "abd") 1 0))))) diff --git a/examples/list.ail b/examples/list.ail index c1d4b86..07d1b3c 100644 --- a/examples/list.ail +++ b/examples/list.ail @@ -5,13 +5,13 @@ (ctor Cons (con Int) (con IntList))) (fn sum_list (doc "Summiert die Elemente einer Int-Liste rekursiv.") - (type (fn-type (params (con IntList)) (ret (con Int)))) + (type (fn-type (params (own (con IntList))) (ret (own (con Int))))) (params xs) (body (match xs (case (pat-ctor Nil) 0) (case (pat-ctor Cons h t) (app + h (app sum_list t)))))) (fn main (doc "Baut [10, 20, 12] und druckt die Summe (42).") - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (let xs (term-ctor IntList Cons 10 (term-ctor IntList Cons 20 (term-ctor IntList Cons 12 (term-ctor IntList Nil)))) (app print (app sum_list xs)))))) diff --git a/examples/list_map.ail b/examples/list_map.ail index 6aa40ce..53c29c3 100644 --- a/examples/list_map.ail +++ b/examples/list_map.ail @@ -5,20 +5,20 @@ (ctor Cons (con Int) (con IntList))) (fn map_int (doc "Apply f to every element. Recursive on the tail.") - (type (fn-type (params (fn-type (params (con Int)) (ret (con Int))) (con IntList)) (ret (con IntList)))) + (type (fn-type (params (own (fn-type (params (own (con Int))) (ret (own (con Int))))) (own (con IntList))) (ret (own (con IntList))))) (params f xs) (body (match xs (case (pat-ctor Nil) (term-ctor IntList Nil)) (case (pat-ctor Cons h t) (term-ctor IntList Cons (app f h) (app map_int f t)))))) (fn print_list (doc "Print each Int on its own line.") - (type (fn-type (params (con IntList)) (ret (con Unit)) (effects IO))) + (type (fn-type (params (own (con IntList))) (ret (own (con Unit))) (effects IO))) (params xs) (body (match xs (case (pat-ctor Nil) (lit-unit)) (case (pat-ctor Cons h t) (seq (seq (app print h) (do io/print_str "\n")) (app print_list t)))))) (fn main (doc "Build [1,2,3], double each, print result.") - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (let xs (term-ctor IntList Cons 1 (term-ctor IntList Cons 2 (term-ctor IntList Cons 3 (term-ctor IntList Nil)))) (app print_list (app map_int (lam (params (typed x (con Int))) (ret (con Int)) (body (app * x 2))) xs)))))) diff --git a/examples/list_map_poly.ail b/examples/list_map_poly.ail index eb413ec..11dda69 100644 --- a/examples/list_map_poly.ail +++ b/examples/list_map_poly.ail @@ -10,7 +10,7 @@ (fn inc (doc "Add 1 to an Int.") - (type (fn-type (params (con Int)) (ret (con Int)))) + (type (fn-type (params (own (con Int))) (ret (own (con Int))))) (params x) (body (app + x 1))) @@ -19,9 +19,9 @@ (type (forall (vars a b) (fn-type - (params (fn-type (params a) (ret b)) - (con List a)) - (ret (con List b))))) + (params (own (fn-type (params (own a)) (ret (own b)))) + (own (con List a))) + (ret (own (con List b)))))) (params f xs) (body (match xs @@ -36,8 +36,8 @@ (doc "Print each Int on its own line.") (type (fn-type - (params (con List (con Int))) - (ret (con Unit)) + (params (own (con List (con Int)))) + (ret (own (con Unit))) (effects IO))) (params xs) (body @@ -51,7 +51,7 @@ (fn main (doc "Map inc over [1,2,3] via polymorphic List, then print: 2,3,4.") - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (app print_list diff --git a/examples/lit_pat.ail b/examples/lit_pat.ail index d43c6e0..492c72d 100644 --- a/examples/lit_pat.ail +++ b/examples/lit_pat.ail @@ -23,7 +23,7 @@ (fn classify (doc "Map 0->100, 1->200, anything else->999.") - (type (fn-type (params (con Int)) (ret (con Int)))) + (type (fn-type (params (own (con Int))) (ret (own (con Int))))) (params n) (body (match n @@ -33,7 +33,7 @@ (fn categorize_first (doc "Empty -> -1; first element 0 -> 0; otherwise return the head.") - (type (fn-type (params (con IntList)) (ret (con Int)))) + (type (fn-type (params (borrow (con IntList))) (ret (own (con Int))))) (params xs) (body (match xs @@ -43,7 +43,7 @@ (fn main (doc "Drive both fns. Expected (per line): 100, 200, 999, -1, 0, 7.") - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (seq (seq (app print (app classify 0)) (do io/print_str "\n")) diff --git a/examples/lit_pat_ctor_tail_drop.ail b/examples/lit_pat_ctor_tail_drop.ail new file mode 100644 index 0000000..f3df8a5 --- /dev/null +++ b/examples/lit_pat_ctor_tail_drop.ail @@ -0,0 +1,47 @@ +; RED fixture (refs #55, Implicit-cutover double-drop). +; +; Property under test: a ctor arm with a literal sub-pattern in a +; non-tail field — `(Cons (pat-lit K) _)` — must drop the matched +; value's owned children EXACTLY ONCE on the head!=K path. +; +; The 16c lit-sub-pattern desugar (crates/ailang-core/src/desugar.rs +; `desugar_one_arm` / `wrap_sub`) lowers `(Cons (pat-lit K) _)` into an +; outer Cons-match that binds the Cons fields, then an +; `if (== head K) body else fall_k`. `fall_k` is the rest of the arm +; chain, which RE-MATCHES the same owned scrutinee (re-binding its +; children). Once the param mode is `Own` (post-#55 cutover, was +; `Implicit`), the arm-close Iter-A drop in +; crates/ailang-codegen/src/match_lower.rs (gated on +; `scrutinee_is_owned`) fires on the tail child in BOTH the re-match arm +; AND the enclosing lit-arm's join — the same heap pointer (`arg_xs+16`) +; is dropped twice → double-free → SIGSEGV on the head!=K path. +; +; Pre-cutover this leaked instead of crashing (Implicit suppressed the +; drop entirely). The fixture pins single-drop, not merely exit-0: +; `cat` consumes its owned IntList arg, so a leak-free run frees every +; cell exactly once. `main` drives both the head==K (0) and head!=K (7) +; paths so the regression is visible on whichever path the fix touches. + +(module lit_pat_ctor_tail_drop + + (data IntList + (ctor Nil) + (ctor Cons (con Int) (con IntList))) + + (fn cat + (doc "Empty -> -1; first element 0 -> 0; otherwise the head.") + (type (fn-type (params (borrow (con IntList))) (ret (own (con Int))))) + (params xs) + (body + (match xs + (case (pat-ctor Nil) -1) + (case (pat-ctor Cons (pat-lit 0) _) 0) + (case (pat-ctor Cons h _) h)))) + + (fn main + (doc "Drive head==0 then head!=0. Expected (per line): 0, 7.") + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) + (params) + (body + (seq (seq (app print (app cat (term-ctor IntList Cons 0 (term-ctor IntList Nil)))) (do io/print_str "\n")) + (seq (app print (app cat (term-ctor IntList Cons 7 (term-ctor IntList Nil)))) (do io/print_str "\n")))))) diff --git a/examples/lit_pat_nil_scrutinee_drop.ail b/examples/lit_pat_nil_scrutinee_drop.ail new file mode 100644 index 0000000..c1b7491 --- /dev/null +++ b/examples/lit_pat_nil_scrutinee_drop.ail @@ -0,0 +1,52 @@ +; RED fixture (refs #55, Implicit-cutover husk leak — leg B, Nil sub-case). +; +; Property under test: an owned scrutinee threaded through the +; lit-sub-pattern chain desugar must have its OUTER cell freed +; exactly once on EVERY return path — including the path that +; matches the Nil arm and touches neither inner re-match. +; +; `first_or_default` has a lit sub-pattern arm `(Cons (pat-lit 0) _)`, +; so the 16c desugar (crates/ailang-core/src/desugar.rs `desugar_match`) +; is forced off the `is_flat` fast-path: it let-binds the owned +; scrutinee `xs` into a fresh `$mp` var and lowers the arms to a chain +; of single-level matches over `$mp`. Two breakages follow once the +; param mode is `Own` (post-#55 cutover): +; 1. the `Let $mp = xs` consumes `xs` (consume_count := 1), which +; trips the codegen fn-return gate `if consume_count != 0 { +; continue }` (crates/ailang-codegen/src/lib.rs ~1522) and +; SUPPRESSES the husk free that the equivalent single-match fn +; (e.g. std_list List_map) emits as `partial_drop_(arg_xs, 2)`; +; 2. the husk obligation moves to the internal `$mp` binder, which the +; gate (it iterates `f.params` only) never inspects. +; Net effect: the outer cell of `xs` is never freed. +; +; This fixture isolates the Nil path specifically: `main` calls +; `first_or_default` with a bare `(IntList Nil)`. That path returns -1 +; and flows entry -> Nil-arm -> fn-return join, touching neither inner +; Cons re-match. The Nil cell (one 8-byte alloc) leaks -> live=1. +; The companion fixture lit_pat_ctor_tail_drop.ail covers the Cons +; path; this one pins the distinct Nil path so a Cons-only fix cannot +; pass while still leaking Nil husks. + +(module lit_pat_nil_scrutinee_drop + + (data IntList + (ctor Nil) + (ctor Cons (con Int) (con IntList))) + + (fn first_or_default + (doc "Empty -> -1; first element 0 -> 0; otherwise the head.") + (type (fn-type (params (borrow (con IntList))) (ret (own (con Int))))) + (params xs) + (body + (match xs + (case (pat-ctor Nil) -1) + (case (pat-ctor Cons (pat-lit 0) _) 0) + (case (pat-ctor Cons h _) h)))) + + (fn main + (doc "Drive the Nil scrutinee only. Expected output: -1.") + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) + (params) + (body + (seq (app print (app first_or_default (term-ctor IntList Nil))) (do io/print_str "\n"))))) diff --git a/examples/local_rec_as_value.ail b/examples/local_rec_as_value.ail index 5a9e030..2e8c843 100644 --- a/examples/local_rec_as_value.ail +++ b/examples/local_rec_as_value.ail @@ -14,18 +14,18 @@ (fn apply5 (doc "Higher-order: applies its fn argument to 5.") - (type (fn-type (params (fn-type (params (con Int)) (ret (con Int)))) (ret (con Int)))) + (type (fn-type (params (own (fn-type (params (own (con Int))) (ret (own (con Int)))))) (ret (own (con Int))))) (params f) (body (app f 5))) (fn main (doc "Iter 16b.5: pass a let-rec'd factorial as a value to apply5. Expected stdout: 120.") - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (let-rec factorial (params n) - (type (fn-type (params (con Int)) (ret (con Int)))) + (type (fn-type (params (own (con Int))) (ret (own (con Int))))) (body (if (app le n 1) 1 diff --git a/examples/local_rec_as_value_capture.ail b/examples/local_rec_as_value_capture.ail index f4087a1..fc12e65 100644 --- a/examples/local_rec_as_value_capture.ail +++ b/examples/local_rec_as_value_capture.ail @@ -16,18 +16,18 @@ (fn apply5 (doc "Higher-order: applies its fn argument to 5.") - (type (fn-type (params (fn-type (params (con Int)) (ret (con Int)))) (ret (con Int)))) + (type (fn-type (params (own (fn-type (params (own (con Int))) (ret (own (con Int)))))) (ret (own (con Int))))) (params f) (body (app f 5))) (fn run_with_base (doc "Build a recursive helper that captures `base` and pass it as a value to apply5.") - (type (fn-type (params (con Int)) (ret (con Int)))) + (type (fn-type (params (own (con Int))) (ret (own (con Int))))) (params base) (body (let-rec factorial_plus (params n) - (type (fn-type (params (con Int)) (ret (con Int)))) + (type (fn-type (params (own (con Int))) (ret (own (con Int))))) (body (if (app le n 1) (app + 1 base) @@ -36,7 +36,7 @@ (fn main (doc "Iter 16b.5: pass a capturing let-rec helper as a value. Expected stdout: 1320, 12120.") - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (seq (seq (app print (app run_with_base 10)) (do io/print_str "\n")) diff --git a/examples/local_rec_capture.ail b/examples/local_rec_capture.ail index cf4aa4f..7bc3e0e 100644 --- a/examples/local_rec_capture.ail +++ b/examples/local_rec_capture.ail @@ -13,12 +13,12 @@ (fn sum_below (doc "Sum 1 + 2 + ... + (n-1). Helper captures n from the enclosing scope.") - (type (fn-type (params (con Int)) (ret (con Int)))) + (type (fn-type (params (own (con Int))) (ret (own (con Int))))) (params n) (body (let-rec loop (params i) - (type (fn-type (params (con Int)) (ret (con Int)))) + (type (fn-type (params (own (con Int))) (ret (own (con Int))))) (body (if (app ge i n) 0 @@ -27,7 +27,7 @@ (fn main (doc "Drive sum_below at 1, 5, 10. Expected (per line): 0, 10, 45.") - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (seq (seq (app print (app sum_below 1)) (do io/print_str "\n")) diff --git a/examples/local_rec_demo.ail b/examples/local_rec_demo.ail index d9ec084..0269b49 100644 --- a/examples/local_rec_demo.ail +++ b/examples/local_rec_demo.ail @@ -10,12 +10,12 @@ (fn main (doc "Iter 16b.1: drive a let-rec'd factorial helper at three inputs. Expected stdout (per line): 1, 6, 120.") - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (let-rec fact (params n) - (type (fn-type (params (con Int)) (ret (con Int)))) + (type (fn-type (params (own (con Int))) (ret (own (con Int))))) (body (if (app le n 1) 1 diff --git a/examples/local_rec_let_capture.ail b/examples/local_rec_let_capture.ail index 91f40b5..384056b 100644 --- a/examples/local_rec_let_capture.ail +++ b/examples/local_rec_let_capture.ail @@ -23,13 +23,13 @@ (fn count_below (doc "Count i in 1..=n with i < threshold, where threshold = 5+5.") - (type (fn-type (params (con Int)) (ret (con Int)))) + (type (fn-type (params (own (con Int))) (ret (own (con Int))))) (params n) (body (let threshold (app + 5 5) (let-rec loop (params i) - (type (fn-type (params (con Int)) (ret (con Int)))) + (type (fn-type (params (own (con Int))) (ret (own (con Int))))) (body (if (app gt i n) 0 @@ -40,7 +40,7 @@ (fn main (doc "Drive count_below at 0, 5, 15. Expected (per line): 0, 5, 9.") - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (seq (seq (app print (app count_below 0)) (do io/print_str "\n")) diff --git a/examples/local_rec_match_capture.ail b/examples/local_rec_match_capture.ail index 7dfe100..c5d871c 100644 --- a/examples/local_rec_match_capture.ail +++ b/examples/local_rec_match_capture.ail @@ -33,14 +33,14 @@ (fn count_below (doc "Match-arm captures `threshold` and `n`; inner LetRec captures both.") - (type (fn-type (params (con Pair (con Int) (con Int))) (ret (con Int)))) + (type (fn-type (params (own (con Pair (con Int) (con Int)))) (ret (own (con Int))))) (params p) (body (match p (case (pat-ctor MkPair threshold n) (let-rec loop (params i) - (type (fn-type (params (con Int)) (ret (con Int)))) + (type (fn-type (params (own (con Int))) (ret (own (con Int))))) (body (if (app gt i n) 0 @@ -51,7 +51,7 @@ (fn main (doc "Drive count_below at MkPair 10 {0,5,15}. Expected: 0, 5, 9.") - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (seq (seq (app print (app count_below (term-ctor Pair MkPair 10 0))) (do io/print_str "\n")) diff --git a/examples/loop_forever.ail b/examples/loop_forever.ail index ebb1eb9..f8c4cc3 100644 --- a/examples/loop_forever.ail +++ b/examples/loop_forever.ail @@ -1,6 +1,6 @@ (module loop_forever (fn spin - (type (fn-type (params (con Int)) (ret (con Int)))) + (type (fn-type (params (own (con Int))) (ret (own (con Int))))) (params n) (body (loop (i (con Int) 0) diff --git a/examples/loop_forever_build.ail b/examples/loop_forever_build.ail index 0299257..7f6dbbb 100644 --- a/examples/loop_forever_build.ail +++ b/examples/loop_forever_build.ail @@ -1,10 +1,10 @@ (module loop_forever_build (fn main (doc "loop-recur iter 3 — an infinite loop (no non-recur exit) must COMPILE (typechecks AND compiles; no termination claim). Build-only; by design never returns, so the binary is never executed.") - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (app print (app spin 0)))) (fn spin - (type (fn-type (params (con Int)) (ret (con Int)))) + (type (fn-type (params (own (con Int))) (ret (own (con Int))))) (params n) (body (loop (i (con Int) 0) (recur (app + i 1)))))) diff --git a/examples/loop_forever_build.prose.txt b/examples/loop_forever_build.prose.txt index b89fd0d..c0bb7b9 100644 --- a/examples/loop_forever_build.prose.txt +++ b/examples/loop_forever_build.prose.txt @@ -3,11 +3,11 @@ /// loop-recur iter 3 — an infinite loop (no non-recur exit) must COMPILE /// (typechecks AND compiles; no termination claim). Build-only; by design never /// returns, so the binary is never executed. -fn main() -> Unit with IO { +fn main() -> own Unit with IO { print(spin(0)) } -fn spin(n: Int) -> Int { +fn spin(n: own Int) -> own Int { loop(i = 0) { recur(i + 1) } diff --git a/examples/loop_let_bound_binder_ref.ail b/examples/loop_let_bound_binder_ref.ail index e877dfb..d3bc1b7 100644 --- a/examples/loop_let_bound_binder_ref.ail +++ b/examples/loop_let_bound_binder_ref.ail @@ -19,7 +19,7 @@ ; Expected stdout: 3 (loop counts i 0,1,2 and returns binder b == i). (module loop_let_bound_binder_ref (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (let r diff --git a/examples/loop_recur_heap_binder_no_leak_pin.ail b/examples/loop_recur_heap_binder_no_leak_pin.ail index 42b5051..1c5d40d 100644 --- a/examples/loop_recur_heap_binder_no_leak_pin.ail +++ b/examples/loop_recur_heap_binder_no_leak_pin.ail @@ -19,7 +19,7 @@ ; Cell@i=1) must drop at the recur store. Pre-fix: live=3. Post-fix: ; live=0. Expected stdout: 2. (data Cell (ctor Cell (con Int))) - (fn main (type (fn-type (params) (ret (con Unit)) (effects IO))) (params) + (fn main (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (let final (loop (acc (con Cell) (term-ctor Cell Cell 0)) (i (con Int) 0) diff --git a/examples/loop_recur_str_binder_no_leak_pin.ail b/examples/loop_recur_str_binder_no_leak_pin.ail index e432ad9..edf0b22 100644 --- a/examples/loop_recur_str_binder_no_leak_pin.ail +++ b/examples/loop_recur_str_binder_no_leak_pin.ail @@ -23,7 +23,7 @@ ; The two superseded heap results ("xy", "xyy") plus the final "xyyy" ; after print must all be freed. Pre-fix: live=3. Post-fix: live=0. ; Expected stdout: xyyy. - (fn main (type (fn-type (params) (ret (con Unit)) (effects IO))) (params) + (fn main (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (let s (loop (acc (con Str) "x") (i (con Int) 0) diff --git a/examples/loop_str_recur_literal_no_leak_pin.ail b/examples/loop_str_recur_literal_no_leak_pin.ail index 20fc21f..2222edf 100644 --- a/examples/loop_str_recur_literal_no_leak_pin.ail +++ b/examples/loop_str_recur_literal_no_leak_pin.ail @@ -9,7 +9,7 @@ ; Trace: seed slab1("x"); recur0 slab2("reset") decs slab1; recur1 ; slab3 decs slab2; recur2 slab4 decs slab3; exit acc=slab4, print ; consumes/frees it. allocs=4 frees=4 live=0. Expected stdout: reset. - (fn main (type (fn-type (params) (ret (con Unit)) (effects IO))) (params) + (fn main (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (let s (loop (acc (con Str) "x") (i (con Int) 0) diff --git a/examples/loop_str_static_exit_no_leak_pin.ail b/examples/loop_str_static_exit_no_leak_pin.ail index e8bb16e..37b229e 100644 --- a/examples/loop_str_static_exit_no_leak_pin.ail +++ b/examples/loop_str_static_exit_no_leak_pin.ail @@ -9,7 +9,7 @@ ; str_clones it into an owned heap slab; scope-close then frees it. ; Single Int binder (no Str binder to leak). allocs=1 frees=1 live=0. ; Expected stdout: result. - (fn main (type (fn-type (params) (ret (con Unit)) (effects IO))) (params) + (fn main (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (let s (loop (i (con Int) 0) diff --git a/examples/loop_sum_to.ail b/examples/loop_sum_to.ail index 5f6d116..52987ad 100644 --- a/examples/loop_sum_to.ail +++ b/examples/loop_sum_to.ail @@ -1,6 +1,6 @@ (module loop_sum_to (fn sum_to - (type (fn-type (params (con Int)) (ret (con Int)))) + (type (fn-type (params (own (con Int))) (ret (own (con Int))))) (params n) (body (loop (acc (con Int) 0) (i (con Int) 1) diff --git a/examples/loop_sum_to_deep.ail b/examples/loop_sum_to_deep.ail index 68d9790..0baf367 100644 --- a/examples/loop_sum_to_deep.ail +++ b/examples/loop_sum_to_deep.ail @@ -1,11 +1,11 @@ (module loop_sum_to_deep (fn main (doc "loop-recur iter 3 — sum 1..1000000 iteratively. A non-tail hand-recursion to this depth would overflow; loop/recur is safe by construction. Expected stdout: 500000500000.") - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (app print (app sum_to 1000000)))) (fn sum_to - (type (fn-type (params (con Int)) (ret (con Int)))) + (type (fn-type (params (own (con Int))) (ret (own (con Int))))) (params n) (body (loop (acc (con Int) 0) (i (con Int) 1) diff --git a/examples/loop_sum_to_run.ail b/examples/loop_sum_to_run.ail index 0132fad..43f71c1 100644 --- a/examples/loop_sum_to_run.ail +++ b/examples/loop_sum_to_run.ail @@ -1,11 +1,11 @@ (module loop_sum_to_run (fn main (doc "loop-recur iter 3 — sum 1..10 via loop/recur. Expected stdout: 55.") - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (app print (app sum_to 10)))) (fn sum_to - (type (fn-type (params (con Int)) (ret (con Int)))) + (type (fn-type (params (own (con Int))) (ret (own (con Int))))) (params n) (body (loop (acc (con Int) 0) (i (con Int) 1) diff --git a/examples/loop_sum_to_run.prose.txt b/examples/loop_sum_to_run.prose.txt index 85d3af0..ed83ed9 100644 --- a/examples/loop_sum_to_run.prose.txt +++ b/examples/loop_sum_to_run.prose.txt @@ -1,11 +1,11 @@ // module loop_sum_to_run /// loop-recur iter 3 — sum 1..10 via loop/recur. Expected stdout: 55. -fn main() -> Unit with IO { +fn main() -> own Unit with IO { print(sum_to(10)) } -fn sum_to(n: Int) -> Int { +fn sum_to(n: own Int) -> own Int { loop(acc = 0, i = 1) { if gt(i, n) { acc diff --git a/examples/lt_at_int_smoke.ail b/examples/lt_at_int_smoke.ail index 73354c1..d8e1636 100644 --- a/examples/lt_at_int_smoke.ail +++ b/examples/lt_at_int_smoke.ail @@ -1,5 +1,5 @@ (module lt_at_int_smoke (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (app print (if (app lt 3 5) 1 0))))) diff --git a/examples/max3.ail b/examples/max3.ail index f9fbb49..8f40b43 100644 --- a/examples/max3.ail +++ b/examples/max3.ail @@ -1,14 +1,14 @@ (module max3 (fn max - (type (fn-type (params (con Int) (con Int)) (ret (con Int)))) + (type (fn-type (params (own (con Int)) (own (con Int))) (ret (own (con Int))))) (params a b) (body (if (app gt a b) a b))) (fn max3 (doc "Demonstriert verschachteltes if (statt max-call) zum Test des Block-Trackings.") - (type (fn-type (params (con Int) (con Int) (con Int)) (ret (con Int)))) + (type (fn-type (params (own (con Int)) (own (con Int)) (own (con Int))) (ret (own (con Int))))) (params a b c) (body (if (app gt a b) (if (app gt a c) a c) (if (app gt b c) b c)))) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (app print (app max3 3 17 9))))) diff --git a/examples/maybe_int.ail b/examples/maybe_int.ail index bcf1ca7..a757659 100644 --- a/examples/maybe_int.ail +++ b/examples/maybe_int.ail @@ -5,13 +5,13 @@ (ctor Some a)) (fn or_else (doc "Returns the wrapped Int for Some(x) and the default d for None.") - (type (fn-type (params (con Maybe (con Int)) (con Int)) (ret (con Int)))) + (type (fn-type (params (own (con Maybe (con Int))) (own (con Int))) (ret (own (con Int))))) (params m d) (body (match m (case (pat-ctor None) d) (case (pat-ctor Some x) x)))) (fn main (doc "Print or_else for both arms; expected 7 then 99.") - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (seq (seq (app print (app or_else (term-ctor Maybe Some 7) 99)) (do io/print_str "\n")) (seq (app print (app or_else (term-ctor Maybe None) 99)) (do io/print_str "\n")))))) diff --git a/examples/mir2_callee_kinds.ail b/examples/mir2_callee_kinds.ail index 57add90..e735143 100644 --- a/examples/mir2_callee_kinds.ail +++ b/examples/mir2_callee_kinds.ail @@ -1,6 +1,6 @@ (module mir2_callee_kinds - (fn bump (type (fn-type (params (con Int)) (ret (con Int)))) (params n) + (fn bump (type (fn-type (params (own (con Int))) (ret (own (con Int))))) (params n) (body (app + n 1))) - (fn main (type (fn-type (params) (ret (con Unit)) (effects IO))) (params) + (fn main (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (let r (app bump 41) (do io/print_str (app int_to_str r)))))) diff --git a/examples/mono_hash_pin_smoke.ail b/examples/mono_hash_pin_smoke.ail index f9a9304..815c4c4 100644 --- a/examples/mono_hash_pin_smoke.ail +++ b/examples/mono_hash_pin_smoke.ail @@ -1,12 +1,12 @@ (module mono_hash_pin_smoke (fn ord_to_int - (type (fn-type (params (con prelude.Ordering)) (ret (con Int)))) + (type (fn-type (params (borrow (con prelude.Ordering))) (ret (own (con Int))))) (params o) (body (match o (case (pat-ctor LT) -1) (case (pat-ctor EQ) 0) (case (pat-ctor GT) 1)))) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (seq (app print (app eq 1 1)) (seq (app print (app eq true true)) (seq (app print (app eq "a" "a")) (seq (app print (app ord_to_int (app compare 1 2))) (seq (app print (app ord_to_int (app compare false true))) (app print (app ord_to_int (app compare "a" "b"))))))))))) diff --git a/examples/mq1_xmod_constraint_class.ail b/examples/mq1_xmod_constraint_class.ail index 7e6ba91..b48500d 100644 --- a/examples/mq1_xmod_constraint_class.ail +++ b/examples/mq1_xmod_constraint_class.ail @@ -2,6 +2,6 @@ (import mq1_xmod_constraint_class_dep) (fn useShow (doc "Positive mq.1 fixture: a polymorphic fn that takes a single value and a Show constraint, ignores the value, and returns Unit. Exercises the qualified `Constraint.class` shape end-to-end (loader + validator + check_workspace).") - (type (forall (vars a) (constraints (constraint mq1_xmod_constraint_class_dep.Show a)) (fn-type (params (borrow a)) (ret (con Unit))))) + (type (forall (vars a) (constraints (constraint mq1_xmod_constraint_class_dep.Show a)) (fn-type (params (borrow a)) (ret (own (con Unit)))))) (params _x) (body (lit-unit)))) diff --git a/examples/mq1_xmod_constraint_class_dep.ail b/examples/mq1_xmod_constraint_class_dep.ail index 0b03c43..a1f2ef0 100644 --- a/examples/mq1_xmod_constraint_class_dep.ail +++ b/examples/mq1_xmod_constraint_class_dep.ail @@ -2,4 +2,4 @@ (class Show (param a) (method show - (type (fn-type (params a) (ret (con Str))))))) + (type (fn-type (params (own a)) (ret (own (con Str)))))))) diff --git a/examples/mq3_class_eq_vs_fn_eq.ail b/examples/mq3_class_eq_vs_fn_eq.ail index 06dd14c..d5dc7a8 100644 --- a/examples/mq3_class_eq_vs_fn_eq.ail +++ b/examples/mq3_class_eq_vs_fn_eq.ail @@ -3,6 +3,6 @@ (import mq3_class_eq_vs_fn_eq_fnmod) (fn main (doc "mq.3.6 fixture (c): bare `myeq 1 2` in a workspace with `class MyEq` (in classmod) and `fn myeq` (in fnmod) both imported. Fn wins per lookup precedence; the typechecker emits `class-method-shadowed-by-fn` warning so the LLM-author sees the shadow.") - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (app print (app myeq 1 2))))) diff --git a/examples/mq3_class_eq_vs_fn_eq_classmod.ail b/examples/mq3_class_eq_vs_fn_eq_classmod.ail index ba9da46..543f47d 100644 --- a/examples/mq3_class_eq_vs_fn_eq_classmod.ail +++ b/examples/mq3_class_eq_vs_fn_eq_classmod.ail @@ -2,7 +2,7 @@ (class MyEq (param a) (method myeq - (type (fn-type (params (borrow a) (borrow a)) (ret (con Bool)))))) + (type (fn-type (params (borrow a) (borrow a)) (ret (own (con Bool))))))) (instance (class MyEq) (type (con Int)) diff --git a/examples/mq3_class_eq_vs_fn_eq_fnmod.ail b/examples/mq3_class_eq_vs_fn_eq_fnmod.ail index a6b573f..ab99637 100644 --- a/examples/mq3_class_eq_vs_fn_eq_fnmod.ail +++ b/examples/mq3_class_eq_vs_fn_eq_fnmod.ail @@ -1,6 +1,6 @@ (module mq3_class_eq_vs_fn_eq_fnmod (fn myeq (doc "mq.3.6 fixture (c) helper: free fn `myeq` that shadows the class method `myeq` declared in `mq3_class_eq_vs_fn_eq_classmod`. Always returns false so the fn-wins precedence is observable at runtime if the e2e test ever runs the binary.") - (type (fn-type (params (borrow (con Int)) (borrow (con Int))) (ret (con Bool)))) + (type (fn-type (params (own (con Int)) (own (con Int))) (ret (own (con Bool))))) (params x y) (body false))) diff --git a/examples/mq3_two_show_ambiguous.ail b/examples/mq3_two_show_ambiguous.ail index 0fcd468..3c75a99 100644 --- a/examples/mq3_two_show_ambiguous.ail +++ b/examples/mq3_two_show_ambiguous.ail @@ -3,6 +3,6 @@ (import mq3_two_show_ambiguous_b) (fn main (doc "mq.3.6 fixture (a): bare `show 42` in a workspace where two Show classes (in modA and modB) each ship Show Int. Without an explicit qualifier the dispatch is genuinely ambiguous; the typechecker must fire `ambiguous-method-resolution` naming both candidate classes.") - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (do io/print_str (app show 42))))) diff --git a/examples/mq3_two_show_ambiguous_a.ail b/examples/mq3_two_show_ambiguous_a.ail index 2fed436..21e0757 100644 --- a/examples/mq3_two_show_ambiguous_a.ail +++ b/examples/mq3_two_show_ambiguous_a.ail @@ -2,7 +2,7 @@ (class Show (param a) (method show - (type (fn-type (params (borrow a)) (ret (con Str)))))) + (type (fn-type (params (borrow a)) (ret (own (con Str))))))) (instance (class Show) (type (con Int)) diff --git a/examples/mq3_two_show_ambiguous_b.ail b/examples/mq3_two_show_ambiguous_b.ail index 16f1950..54eddf7 100644 --- a/examples/mq3_two_show_ambiguous_b.ail +++ b/examples/mq3_two_show_ambiguous_b.ail @@ -2,7 +2,7 @@ (class Show (param a) (method show - (type (fn-type (params (borrow a)) (ret (con Str)))))) + (type (fn-type (params (borrow a)) (ret (own (con Str))))))) (instance (class Show) (type (con Int)) diff --git a/examples/mq3_two_show_qualified.ail b/examples/mq3_two_show_qualified.ail index 754b1b4..c7e315f 100644 --- a/examples/mq3_two_show_qualified.ail +++ b/examples/mq3_two_show_qualified.ail @@ -3,6 +3,6 @@ (import mq3_two_show_ambiguous_b) (fn main (doc "mq.3.6 fixture (b): explicit qualifier `mq3_two_show_ambiguous_a.Show.show 42` disambiguates the same workspace as fixture (a). Resolves cleanly to modA's class; typecheck passes.") - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (do io/print_str (app mq3_two_show_ambiguous_a.Show.show 42))))) diff --git a/examples/mut.ail b/examples/mut.ail index fcad4fd..2bcc6a6 100644 --- a/examples/mut.ail +++ b/examples/mut.ail @@ -2,36 +2,36 @@ (fn mut_empty (doc "Empty block; body is a single Int literal.") - (type (fn-type (params) (ret (con Int)))) + (type (fn-type (params) (ret (own (con Int))))) (params) (body 0)) (fn mut_single_var (doc "let-rebind: x starts at 0, the block value is x + 1.") - (type (fn-type (params) (ret (con Int)))) + (type (fn-type (params) (ret (own (con Int))))) (params) (body (let x 0 (let x (app + x 1) x)))) (fn mut_two_vars (doc "Two let-threaded scalars combined into the block value.") - (type (fn-type (params) (ret (con Float)))) + (type (fn-type (params) (ret (own (con Float))))) (params) (body (let sum 0.0 (let count 0 (let sum (app + sum 1.0) (let count (app + count 1) (app + sum (app int_to_float count)))))))) (fn mut_nested_shadow (doc "Nested let shadowing; inner binding is the block value.") - (type (fn-type (params) (ret (con Int)))) + (type (fn-type (params) (ret (own (con Int))))) (params) (body (let x 10 (let x (app + x 1) (let x 100 (let x (app + x 1) x)))))) (fn mut_returns_bool (doc "Bool scalar threaded through let; block value is the latest binding.") - (type (fn-type (params) (ret (con Bool)))) + (type (fn-type (params) (ret (own (con Bool))))) (params) (body (let flag false (let flag true flag)))) (fn mut_returns_unit (doc "Unit scalar threaded through let; block value is the latest binding.") - (type (fn-type (params) (ret (con Unit)))) + (type (fn-type (params) (ret (own (con Unit))))) (params) (body (let u (lit-unit) (let u (lit-unit) u))))) diff --git a/examples/mut_counter.ail b/examples/mut_counter.ail index b9d44b0..50cdf94 100644 --- a/examples/mut_counter.ail +++ b/examples/mut_counter.ail @@ -2,14 +2,14 @@ (fn main (doc "Sum 1..10 via a tail-recursive helper. Expected stdout: 55.") - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (app print (app sum_helper 1 10 0)))) (fn sum_helper (doc "Accumulator-style tail-recursive helper — sum from lo through hi inclusive.") - (type (fn-type (params (con Int) (con Int) (con Int)) (ret (con Int)))) + (type (fn-type (params (own (con Int)) (own (con Int)) (own (con Int))) (ret (own (con Int))))) (params lo hi acc) (body (if (app gt lo hi) diff --git a/examples/mut_sum_floats.ail b/examples/mut_sum_floats.ail index 7ab1c80..1bccc23 100644 --- a/examples/mut_sum_floats.ail +++ b/examples/mut_sum_floats.ail @@ -2,14 +2,14 @@ (fn main (doc "Float twin of mut_counter via a tail-recursive helper. Expected stdout: 55.0 (Float 55.0 via %g plus the runtime `.0`-fallback for finite whole-valued doubles, Gitea #7).") - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (app print (app sum_helper 1.0 10.0 0.0)))) (fn sum_helper (doc "Accumulator-style tail-recursive Float helper — sum from lo through hi inclusive.") - (type (fn-type (params (con Float) (con Float) (con Float)) (ret (con Float)))) + (type (fn-type (params (own (con Float)) (own (con Float)) (own (con Float))) (ret (own (con Float))))) (params lo hi acc) (body (if (app float_gt lo hi) diff --git a/examples/ne_at_int_smoke.ail b/examples/ne_at_int_smoke.ail index 8411ee8..121aa34 100644 --- a/examples/ne_at_int_smoke.ail +++ b/examples/ne_at_int_smoke.ail @@ -1,5 +1,5 @@ (module ne_at_int_smoke (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (app print (if (app ne 3 5) 1 0))))) diff --git a/examples/nested_let_rec.ail b/examples/nested_let_rec.ail index 473c51a..cbee322 100644 --- a/examples/nested_let_rec.ail +++ b/examples/nested_let_rec.ail @@ -22,19 +22,19 @@ (fn nested_sum (doc "Sum 1 + 2 + ... + n by nested LetRec helpers; inner captures outer's param i.") - (type (fn-type (params (con Int)) (ret (con Int)))) + (type (fn-type (params (own (con Int))) (ret (own (con Int))))) (params n) (body (let-rec outer (params i) - (type (fn-type (params (con Int)) (ret (con Int)))) + (type (fn-type (params (own (con Int))) (ret (own (con Int))))) (body (if (app gt i n) 0 (app + (let-rec inner (params j) - (type (fn-type (params (con Int)) (ret (con Int)))) + (type (fn-type (params (own (con Int))) (ret (own (con Int))))) (body (if (app gt j i) 0 @@ -45,7 +45,7 @@ (fn main (doc "Drive nested_sum at 1, 3, 5. Expected (per line): 1, 6, 15.") - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (seq (seq (app print (app nested_sum 1)) (do io/print_str "\n")) diff --git a/examples/nested_pat.ail b/examples/nested_pat.ail index 93934b0..c5d57de 100644 --- a/examples/nested_pat.ail +++ b/examples/nested_pat.ail @@ -13,8 +13,8 @@ (doc "Sum of the first two elements of an Int list, or 0 if shorter than two.") (type (fn-type - (params (con List (con Int))) - (ret (con Int)))) + (params (own (con List (con Int)))) + (ret (own (con Int))))) (params xs) (body (match xs @@ -23,7 +23,7 @@ (case _ 0)))) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (app print (app first_two_sum diff --git a/examples/new_counter_user_adt.ail b/examples/new_counter_user_adt.ail index 5013529..a1b4f10 100644 --- a/examples/new_counter_user_adt.ail +++ b/examples/new_counter_user_adt.ail @@ -1,6 +1,6 @@ (module new_counter_user_adt (data Counter (ctor MkCounter (con Int))) - (fn new (type (fn-type (params (con Int)) (ret (con Counter)))) (params n) + (fn new (type (fn-type (params (own (con Int))) (ret (own (con Counter))))) (params n) (body (term-ctor Counter MkCounter n))) - (fn main (type (fn-type (params) (ret (con Unit)) (effects IO))) (params) + (fn main (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (let c (new Counter 42) (do io/print_str "ok\n"))))) diff --git a/examples/new_rawbuf_size_only.ail b/examples/new_rawbuf_size_only.ail index 8c3b6de..7721097 100644 --- a/examples/new_rawbuf_size_only.ail +++ b/examples/new_rawbuf_size_only.ail @@ -1,6 +1,6 @@ (module new_rawbuf_size_only (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (let b (new RawBuf (con Int) 3) diff --git a/examples/nullary_app_smoke.ail b/examples/nullary_app_smoke.ail index a7d9231..eab10df 100644 --- a/examples/nullary_app_smoke.ail +++ b/examples/nullary_app_smoke.ail @@ -1,12 +1,12 @@ (module nullary_app_smoke (fn greet (doc "Nullary user function — no params, prints a fixed line.") - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (seq (do io/print_str "hello") (do io/print_str "\n")))) (fn main (doc "Calls the nullary `greet` as `(app greet)` — the zero-arg surface form accepted under Gitea #12.") - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (app greet)))) diff --git a/examples/operator_unbound_check.ail b/examples/operator_unbound_check.ail index 140d4b0..2ef1bf0 100644 --- a/examples/operator_unbound_check.ail +++ b/examples/operator_unbound_check.ail @@ -1,6 +1,6 @@ (module operator_unbound_check (fn main (doc "Pin fixture. After operator-routing-eq-ord, `(app == 5 5)` must fail typecheck with `unknown variable: ==` — the operator names are no longer in the language. Today this typechecks (== is polymorphic over Int) — RED-first.") - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (app print (app == 5 5))))) diff --git a/examples/ord_int_intercept_smoke.ail b/examples/ord_int_intercept_smoke.ail index 410e296..98cfa32 100644 --- a/examples/ord_int_intercept_smoke.ail +++ b/examples/ord_int_intercept_smoke.ail @@ -11,7 +11,7 @@ (fn drive (doc "Touches each of lt/le/gt/ge/ne at Int once. Returns 0 unconditionally; we care only about which mono symbols the call graph forces the unified mono pass to emit.") - (type (fn-type (params (con Int) (con Int)) (ret (con Int)))) + (type (fn-type (params (own (con Int)) (own (con Int))) (ret (own (con Int))))) (params a b) (body (let r1 (app lt a b) @@ -22,6 +22,6 @@ 0))))))) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (app print (app drive 1 2))))) diff --git a/examples/ordering_match.ail b/examples/ordering_match.ail index 74569a8..ebf23a2 100644 --- a/examples/ordering_match.ail +++ b/examples/ordering_match.ail @@ -1,7 +1,7 @@ (module ordering_match (fn main (doc "Iter 23.1 fixture: pattern-match on prelude Ordering ctor without explicit prelude import.") - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (seq (app print (match (term-ctor prelude.Ordering LT) (case (pat-ctor LT) 1) diff --git a/examples/ordering_match.prose.txt b/examples/ordering_match.prose.txt index 2366d50..c68009c 100644 --- a/examples/ordering_match.prose.txt +++ b/examples/ordering_match.prose.txt @@ -2,7 +2,7 @@ /// Iter 23.1 fixture: pattern-match on prelude Ordering ctor without explicit /// prelude import. -fn main() -> Unit with IO { +fn main() -> own Unit with IO { print(match LT { LT => 1, EQ => 2, diff --git a/examples/own_return_provenance_ctor.ail b/examples/own_return_provenance_ctor.ail new file mode 100644 index 0000000..1bbaaf4 --- /dev/null +++ b/examples/own_return_provenance_ctor.ail @@ -0,0 +1,18 @@ +(module own_return_provenance_ctor + + (data Box + (doc "Heap cell holding one Int.") + (ctor Box (con Int))) + + (data Wrap + (doc "Wraps a Box.") + (ctor Wrap (con Box))) + + (fn escape + (doc "Already rejected today (consume-while-borrowed, regime A): a borrowed Box escapes into a returned constructor.") + (type + (fn-type + (params (borrow (con Box))) + (ret (own (con Wrap))))) + (params b) + (body (term-ctor Wrap Wrap b)))) diff --git a/examples/own_return_provenance_let.ail b/examples/own_return_provenance_let.ail new file mode 100644 index 0000000..d5e05f0 --- /dev/null +++ b/examples/own_return_provenance_let.ail @@ -0,0 +1,14 @@ +(module own_return_provenance_let + + (data Box + (doc "Heap cell holding one Int.") + (ctor Box (con Int))) + + (fn passthrough + (doc "Already rejected today (consume-while-borrowed): own-return aliases a borrowed binder through a let.") + (type + (fn-type + (params (borrow (con Box))) + (ret (own (con Box))))) + (params b) + (body (let y b y)))) diff --git a/examples/own_return_provenance_reject.ail b/examples/own_return_provenance_reject.ail new file mode 100644 index 0000000..0851a5c --- /dev/null +++ b/examples/own_return_provenance_reject.ail @@ -0,0 +1,14 @@ +(module own_return_provenance_reject + + (data Box + (doc "Heap cell holding one Int.") + (ctor Box (con Int))) + + (fn passthrough + (doc "Already rejected today (consume-while-borrowed): own-return aliases a borrowed binder.") + (type + (fn-type + (params (borrow (con Box))) + (ret (own (con Box))))) + (params b) + (body b))) diff --git a/examples/own_str_arg_drop.ail b/examples/own_str_arg_drop.ail new file mode 100644 index 0000000..7e6fbbf --- /dev/null +++ b/examples/own_str_arg_drop.ail @@ -0,0 +1,29 @@ +; Minimal repro for the #55-cutover drop-soundness crash: a string +; LITERAL passed by value into an `(own (con Str))` parameter that the +; callee drops (the param is not consumed/returned, so the Own-param +; drop path emits `ailang_rc_dec` on it). +; +; The literal is a `StrRep::Static` rodata constant with NO rc_header, +; so `ailang_rc_dec` reads the 8 bytes before the constant as a fake +; refcount and `free()`s a static address -> SIGSEGV. +; +; `usestr` (returns `s`) does NOT crash because the param is consumed, +; not dropped. `take_int` (own Int) does NOT crash because Int is a +; value type. The crash is specific to a static Str literal flowing +; into a dropped owned Str parameter. +; +; Expected stdout: `0` + +(module own_str_arg_drop + + (fn take + (doc "Owned Str param, ignored (dropped in callee), returns 0.") + (type (fn-type (params (own (con Str))) (ret (own (con Int))))) + (params s) + (body 0)) + + (fn main + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) + (params) + (body + (seq (app print (app take "hi")) (do io/print_str "\n"))))) diff --git a/examples/ownership_total.ail b/examples/ownership_total.ail new file mode 100644 index 0000000..efb7845 --- /dev/null +++ b/examples/ownership_total.ail @@ -0,0 +1,46 @@ +(module ownership_total + + (data List + (doc "Monomorphic singly-linked Int list — boxed, recursive.") + (ctor Nil) + (ctor Cons (con Int) (con List))) + + (fn list_length + (doc "Borrow the list, count its elements.") + (type + (fn-type + (params (borrow (con List))) + (ret (own (con Int))))) + (params xs) + (body + (match xs + (case (pat-ctor Nil) 0) + (case (pat-ctor Cons h t) + (app + 1 (app list_length t)))))) + + (fn sum_list + (doc "Consume the list, sum its elements.") + (type + (fn-type + (params (own (con List))) + (ret (own (con Int))))) + (params xs) + (body + (match xs + (case (pat-ctor Nil) 0) + (case (pat-ctor Cons h t) + (app + h (app sum_list t)))))) + + (fn main + (doc "Build [1,2,3]; print length (3) then sum (6).") + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) + (params) + (body + (let xs + (term-ctor List Cons 1 + (term-ctor List Cons 2 + (term-ctor List Cons 3 + (term-ctor List Nil)))) + (seq + (seq (app print (app list_length xs)) (do io/print_str "\n")) + (seq (app print (app sum_list xs)) (do io/print_str "\n"))))))) diff --git a/examples/pat_extract_partial_drop.ail b/examples/pat_extract_partial_drop.ail index 7c8c8df..a8e1045 100644 --- a/examples/pat_extract_partial_drop.ail +++ b/examples/pat_extract_partial_drop.ail @@ -36,8 +36,8 @@ (doc "Consume xs, sum its elements.") (type (fn-type - (params (con IntList)) - (ret (con Int)))) + (params (own (con IntList))) + (ret (own (con Int))))) (params xs) (body (match xs @@ -47,7 +47,7 @@ (fn main (doc "Build Pair([1,2,3], [4,5,6]); pattern-bind first slot only; sum first list. The wildcarded second slot's IntList lives only inside the Pair box; closing the Pair's let scope must dec it (non-moved) while skipping the moved first slot.") - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (let p diff --git a/examples/poly_apply.ail b/examples/poly_apply.ail index 76942aa..d7ad4b0 100644 --- a/examples/poly_apply.ail +++ b/examples/poly_apply.ail @@ -1,13 +1,13 @@ (module poly_apply (fn apply - (type (forall (vars a b) (fn-type (params (fn-type (params a) (ret b)) a) (ret b)))) + (type (forall (vars a b) (fn-type (params (own (fn-type (params (own a)) (ret (own b)))) (own a)) (ret (own b))))) (params f x) (body (app f x))) (fn succ - (type (fn-type (params (con Int)) (ret (con Int)))) + (type (fn-type (params (own (con Int))) (ret (own (con Int))))) (params n) (body (app + n 1))) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (app print (app apply succ 41))))) diff --git a/examples/poly_id.ail b/examples/poly_id.ail index 5fa3dbf..01d4a29 100644 --- a/examples/poly_id.ail +++ b/examples/poly_id.ail @@ -1,9 +1,9 @@ (module poly_id (fn id - (type (forall (vars a) (fn-type (params a) (ret a)))) + (type (forall (vars a) (fn-type (params (own a)) (ret (own a))))) (params x) (body x)) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (seq (seq (app print (app id 42)) (do io/print_str "\n")) (seq (app print (app id true)) (do io/print_str "\n")))))) diff --git a/examples/poly_rec_capture.ail b/examples/poly_rec_capture.ail index 0b0c37c..ebbd3a2 100644 --- a/examples/poly_rec_capture.ail +++ b/examples/poly_rec_capture.ail @@ -20,13 +20,13 @@ (fn succ (doc "Helper: increment Int. Used as the Fn(Int) -> Int instance.") - (type (fn-type (params (con Int)) (ret (con Int)))) + (type (fn-type (params (own (con Int))) (ret (own (con Int))))) (params x) (body (app + x 1))) (fn flip (doc "Helper: boolean negation as a top-level fn (so it has an adapter for value-position).") - (type (fn-type (params (con Bool)) (ret (con Bool)))) + (type (fn-type (params (own (con Bool))) (ret (own (con Bool))))) (params b) (body (app not b))) @@ -35,13 +35,13 @@ (type (forall (vars a) (fn-type - (params (con Int) a (fn-type (params a) (ret a))) - (ret a)))) + (params (own (con Int)) (own a) (own (fn-type (params (own a)) (ret (own a))))) + (ret (own a))))) (params n x f) (body (let-rec loop (params k acc) - (type (fn-type (params (con Int) a) (ret a))) + (type (fn-type (params (own (con Int)) (own a)) (ret (own a)))) (body (if (app eq k 0) acc @@ -50,7 +50,7 @@ (fn main (doc "Drive apply_n_times at Int (succ 5 times from 0) and Bool (not 4 times from false).") - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (seq (seq (app print (app apply_n_times 5 0 succ)) (do io/print_str "\n")) diff --git a/examples/prelude.ail b/examples/prelude.ail index 0d3aa16..6303f11 100644 --- a/examples/prelude.ail +++ b/examples/prelude.ail @@ -9,7 +9,7 @@ (param a) (doc "Structural equality. The class-method `eq` is the surface-level comparator; `==` as a surface name is not part of the language. Primitive instances Eq Int / Bool / Str / Unit are lowered via try_emit_primitive_instance_body in the codegen.") (method eq - (type (fn-type (params (borrow a) (borrow a)) (ret (con Bool)))))) + (type (fn-type (params (borrow a) (borrow a)) (ret (own (con Bool))))))) (instance (class Eq) (type (con Int)) @@ -39,7 +39,7 @@ (superclass (class Eq) (type a)) (doc "Total ordering. Ships in milestone 23 alongside Eq. `compare x y` returns LT, EQ, or GT (the three-ctor Ordering ADT also in the prelude). Decision 11's single-superclass closure requires `instance Eq T` for every `instance Ord T` — the three Ord instances below pair with the three Eq instances shipped in iter 23.2.3.") (method compare - (type (fn-type (params (borrow a) (borrow a)) (ret (con Ordering)))))) + (type (fn-type (params (borrow a) (borrow a)) (ret (own (con Ordering))))))) (instance (class Ord) (type (con Int)) @@ -85,69 +85,69 @@ (body (lam (params (typed x (con Float))) (ret (con Str)) (body (app float_to_str x)))))) (fn ne (doc "Polymorphic disequality. `ne x y` ≡ not (eq x y). Ships in milestone 23 as the Eq-class free helper.") - (type (forall (vars a) (constraints (constraint Eq a)) (fn-type (params (borrow a) (borrow a)) (ret (con Bool))))) + (type (forall (vars a) (constraints (constraint Eq a)) (fn-type (params (borrow a) (borrow a)) (ret (own (con Bool)))))) (params x y) (body (app not (app eq x y)))) (fn lt (doc "Polymorphic strict-less-than. `lt x y` ≡ case compare x y of LT -> True; _ -> False. Ships in milestone 23 as the Ord-class free helper.") - (type (forall (vars a) (constraints (constraint Ord a)) (fn-type (params (borrow a) (borrow a)) (ret (con Bool))))) + (type (forall (vars a) (constraints (constraint Ord a)) (fn-type (params (borrow a) (borrow a)) (ret (own (con Bool)))))) (params x y) (body (match (app compare x y) (case (pat-ctor LT) true) (case _ false)))) (fn le (doc "Polymorphic less-than-or-equal. `le x y` ≡ case compare x y of GT -> False; _ -> True. Ships in milestone 23 as the Ord-class free helper.") - (type (forall (vars a) (constraints (constraint Ord a)) (fn-type (params (borrow a) (borrow a)) (ret (con Bool))))) + (type (forall (vars a) (constraints (constraint Ord a)) (fn-type (params (borrow a) (borrow a)) (ret (own (con Bool)))))) (params x y) (body (match (app compare x y) (case (pat-ctor GT) false) (case _ true)))) (fn gt (doc "Polymorphic strict-greater-than. `gt x y` ≡ case compare x y of GT -> True; _ -> False. Ships in milestone 23 as the Ord-class free helper.") - (type (forall (vars a) (constraints (constraint Ord a)) (fn-type (params (borrow a) (borrow a)) (ret (con Bool))))) + (type (forall (vars a) (constraints (constraint Ord a)) (fn-type (params (borrow a) (borrow a)) (ret (own (con Bool)))))) (params x y) (body (match (app compare x y) (case (pat-ctor GT) true) (case _ false)))) (fn ge (doc "Polymorphic greater-than-or-equal. `ge x y` ≡ case compare x y of LT -> False; _ -> True. Ships in milestone 23 as the Ord-class free helper.") - (type (forall (vars a) (constraints (constraint Ord a)) (fn-type (params (borrow a) (borrow a)) (ret (con Bool))))) + (type (forall (vars a) (constraints (constraint Ord a)) (fn-type (params (borrow a) (borrow a)) (ret (own (con Bool)))))) (params x y) (body (match (app compare x y) (case (pat-ctor LT) false) (case _ true)))) (fn print (doc "Polymorphic console-print helper. `print x` ≡ `do io/print_str (show x)` with an explicit let-binder around `show x` for heap-Str RC discipline per eob.1 Str carve-out. Ships in milestone 24 as the second half of the Show prelude.") - (type (forall (vars a) (constraints (constraint Show a)) (fn-type (params (borrow a)) (ret (con Unit)) (effects IO)))) + (type (forall (vars a) (constraints (constraint Show a)) (fn-type (params (borrow a)) (ret (own (con Unit))) (effects IO)))) (params x) (body (let s (app show x) (do io/print_str s)))) (fn float_eq (doc "IEEE Float equality. `float_eq x y` returns true iff both operands are non-NaN and bit-equal. Compiler-supplied (intrinsic) body; codegen emits `fcmp oeq double` with alwaysinline via the intercept registry. Replaces the milestone-deleted polymorphic `==` on Float.") - (type (fn-type (params (con Float) (con Float)) (ret (con Bool)))) + (type (fn-type (params (own (con Float)) (own (con Float))) (ret (own (con Bool))))) (params x y) (intrinsic)) (fn float_ne (doc "IEEE Float disequality. `float_ne nan nan` returns true (unordered-or-not-equal per IEEE-754). Compiler-supplied (intrinsic) body; codegen emits `fcmp une double`.") - (type (fn-type (params (con Float) (con Float)) (ret (con Bool)))) + (type (fn-type (params (own (con Float)) (own (con Float))) (ret (own (con Bool))))) (params x y) (intrinsic)) (fn float_lt (doc "IEEE Float strict less-than. `float_lt nan x` returns false for any x (unordered). Compiler-supplied (intrinsic) body; codegen emits `fcmp olt double`.") - (type (fn-type (params (con Float) (con Float)) (ret (con Bool)))) + (type (fn-type (params (own (con Float)) (own (con Float))) (ret (own (con Bool))))) (params x y) (intrinsic)) (fn float_le (doc "IEEE Float less-than-or-equal. Compiler-supplied (intrinsic) body; codegen emits `fcmp ole double`.") - (type (fn-type (params (con Float) (con Float)) (ret (con Bool)))) + (type (fn-type (params (own (con Float)) (own (con Float))) (ret (own (con Bool))))) (params x y) (intrinsic)) (fn float_gt (doc "IEEE Float strict greater-than. Compiler-supplied (intrinsic) body; codegen emits `fcmp ogt double`.") - (type (fn-type (params (con Float) (con Float)) (ret (con Bool)))) + (type (fn-type (params (own (con Float)) (own (con Float))) (ret (own (con Bool))))) (params x y) (intrinsic)) (fn float_ge (doc "IEEE Float greater-than-or-equal. Compiler-supplied (intrinsic) body; codegen emits `fcmp oge double`.") - (type (fn-type (params (con Float) (con Float)) (ret (con Bool)))) + (type (fn-type (params (own (con Float)) (own (con Float))) (ret (own (con Bool))))) (params x y) (intrinsic))) diff --git a/examples/print_eq_arg_repro.ail b/examples/print_eq_arg_repro.ail index 74acfad..36b30db 100644 --- a/examples/print_eq_arg_repro.ail +++ b/examples/print_eq_arg_repro.ail @@ -1,6 +1,6 @@ (module print_eq_arg_repro (fn main (doc "RED-pin fixture for the iter 24.3 cursor-misalignment bug. `(app print )` where `` itself contains a class-method call (here `eq`) causes the mono-rewrite cursor to consume the `Show T` residual pushed by `print`'s poly-free-fn observation as if it were the slot for the inner class method. Result: the inner `eq` Var is rewritten to `prelude.show__Bool` instead of `prelude.eq__Int`, and codegen reports `call prelude.show__Bool arg type mismatch: expected i1, got i64`. Bug surfaced 2026-05-14 during the rpe.1 BLOCKED orchestrator run; root cause is `crates/ailang-check/src/mono.rs::interleave_slots` not advancing the class-residual cursor when visiting a poly-free-fn Var whose source `Type::Forall` carries a class constraint. Fixture pinned by `print_with_class_method_arg_does_not_misalign_mono_cursor` in crates/ail/tests/show_print_e2e.rs.") - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (app print (app eq 1 2))))) diff --git a/examples/print_float_whole_smoke.ail b/examples/print_float_whole_smoke.ail index 7d7da1c..cb6d991 100644 --- a/examples/print_float_whole_smoke.ail +++ b/examples/print_float_whole_smoke.ail @@ -1,6 +1,6 @@ (module print_float_whole_smoke (fn main (doc "RED pin for Gitea #7: a whole-valued Float (here, the literal 2.0) routed through polymorphic `print` (which monomorphises to Show Float.show -> float_to_str -> runtime/str.c::ailang_float_to_str -> libc snprintf %g) must render with a `.0` suffix, matching the surface printer's write_float_lit fallback (crates/ailang-surface/src/print.rs lines 624-637). The desired stdout for this fixture is the three bytes `2.0\\n`. Pre-fix, libc %g strips trailing zeros and renders `2\\n` — Int-shaped output for a Float value — which violates the surface-runtime round-trip-on-lex symmetry the language relies on.") - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (seq (app print 2.0) (do io/print_str "\n"))))) diff --git a/examples/print_int_no_leak_pin.ail b/examples/print_int_no_leak_pin.ail index 3ac2a31..4347e34 100644 --- a/examples/print_int_no_leak_pin.ail +++ b/examples/print_int_no_leak_pin.ail @@ -1,6 +1,6 @@ (module print_int_no_leak_pin (fn main (doc "RED-pin fixture for the 2026-05-14 rpe.1 Cat-A heap-Str leak. Under --alloc=rc, the prelude `print` function's body `let s = (app show x) in (do io/print_str s)` allocates a heap-Str via show, then passes it (borrow) to io/print_str. The let-binder `s` is `ret_mode: Own` for `show __Int`, so codegen should emit ailang_rc_dec(s) at let-scope-close. As of commit 301cbc3 the slab leaks: AILANG_RC_STATS=1 reports `allocs=1 frees=0 live=1` for the trivial `(body (app print 42))` program. Expected post-fix: `allocs == frees && live == 0`.") - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (app print 42)))) diff --git a/examples/print_str_embedded_newline_no_doubling.ail b/examples/print_str_embedded_newline_no_doubling.ail index 66a29de..59c6b59 100644 --- a/examples/print_str_embedded_newline_no_doubling.ail +++ b/examples/print_str_embedded_newline_no_doubling.ail @@ -1,6 +1,6 @@ (module print_str_embedded_newline_no_doubling (fn main (doc "RED fixture for Gitea #29 (companion to print_str_no_auto_newline.ail): when the author embeds a single `\\n` in the argument, exactly one newline must reach stdout — not two. Today the codegen lowers io/print_str to `@puts(ptr)` which appends its own newline, so this program emits \"x\\n\\n\" (two newlines) instead of the author-intended \"x\\n\". Pins the no-doubling property as a separate axis from the no-auto-newline property: even authors who want a newline must not get a doubled one.") - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (do io/print_str "x\n")))) diff --git a/examples/print_str_no_auto_newline.ail b/examples/print_str_no_auto_newline.ail index 95c8ced..772274e 100644 --- a/examples/print_str_no_auto_newline.ail +++ b/examples/print_str_no_auto_newline.ail @@ -1,6 +1,6 @@ (module print_str_no_auto_newline (fn main (doc "RED fixture for Gitea #29: `io/print_str` must print exactly the bytes of its argument with NO auto-newline. Today the codegen lowers this op to a C `@puts(ptr)` call, which writes the string PLUS a trailing newline — so two consecutive prints of single-character strings emit \"a\\nb\\n\" instead of the expected \"ab\". This is the de-facto-println behaviour LLM authors (Qwen3-Coder, naming-A/B run 2026-05-21) trip over when they write explicit `\\n` and get doubled newlines. Property protected once the codegen swaps `@puts` for `fputs(s, stdout)`: io/print_str is a byte-faithful print, the author chooses whether to embed a newline.") - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (seq (do io/print_str "a") (do io/print_str "b"))))) diff --git a/examples/raw_buf_adt_field_bare.ail b/examples/raw_buf_adt_field_bare.ail index 31a8c3e..682e23c 100644 --- a/examples/raw_buf_adt_field_bare.ail +++ b/examples/raw_buf_adt_field_bare.ail @@ -2,11 +2,11 @@ (data Box (ctor Box (con RawBuf (con Int)))) (fn slot1 (doc "Reads a RawBuf stored in a user ADT field through a borrow. The Box field is typed with the bare kernel name `(con RawBuf (con Int))`; the kernel-tier auto-import must resolve it to raw_buf.RawBuf exactly as it does in op/value positions.") - (type (fn-type (params (borrow (con Box))) (ret (con Int)))) + (type (fn-type (params (borrow (con Box))) (ret (own (con Int))))) (params bx) (body (match bx (case (pat-ctor Box buf) (app RawBuf.get buf 1))))) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (let buf (new RawBuf (con Int) 3) (let buf (app RawBuf.set buf 1 42) diff --git a/examples/raw_buf_adt_field_qualified.ail b/examples/raw_buf_adt_field_qualified.ail index 574dcbc..5bff096 100644 --- a/examples/raw_buf_adt_field_qualified.ail +++ b/examples/raw_buf_adt_field_qualified.ail @@ -2,11 +2,11 @@ (data Box (ctor Box (con raw_buf.RawBuf (con Int)))) (fn slot1 (doc "Control twin of raw_buf_adt_field_bare: identical module but the Box field is typed with the fully-qualified `(con raw_buf.RawBuf (con Int))`. This checks clean today, isolating the bug to bare-name resolution in the type-constructor position.") - (type (fn-type (params (borrow (con Box))) (ret (con Int)))) + (type (fn-type (params (borrow (con Box))) (ret (own (con Int))))) (params bx) (body (match bx (case (pat-ctor Box buf) (app RawBuf.get buf 1))))) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (let buf (new RawBuf (con Int) 3) (let buf (app RawBuf.set buf 1 42) diff --git a/examples/raw_buf_bool.ail b/examples/raw_buf_bool.ail index abb5cdf..d71d0c8 100644 --- a/examples/raw_buf_bool.ail +++ b/examples/raw_buf_bool.ail @@ -1,7 +1,7 @@ (module raw_buf_bool (fn main (doc "raw-buf.4 Bool variant: store true in a 1-slot Bool RawBuf, read it back, discriminate to an Int and print (42). Exercises RawBuf.new/.set/.get @ Bool (i1 load/store, width-1 offsets).") - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (app print diff --git a/examples/raw_buf_borrow_read.ail b/examples/raw_buf_borrow_read.ail index 4db6e78..6c9b12b 100644 --- a/examples/raw_buf_borrow_read.ail +++ b/examples/raw_buf_borrow_read.ail @@ -1,11 +1,11 @@ (module raw_buf_borrow_read (fn read_one (doc "Reads its borrow-mode RawBuf receiver via RawBuf.get once. get is a borrow-receiver op (kernel raw_buf: get : (borrow (RawBuf a)) Int -> a), so reading the receiver through a borrow must check clean — the receiver is read, not consumed.") - (type (fn-type (params (borrow (con RawBuf (con Int)))) (ret (con Int)))) + (type (fn-type (params (borrow (con RawBuf (con Int)))) (ret (own (con Int))))) (params buf) (body (app RawBuf.get buf 0))) (fn count (doc "Reads its borrow-mode RawBuf receiver via RawBuf.size once. size is a borrow-receiver op (kernel raw_buf: size : (borrow (RawBuf a)) -> Int), so it must check clean for the same reason.") - (type (fn-type (params (borrow (con RawBuf (con Int)))) (ret (con Int)))) + (type (fn-type (params (borrow (con RawBuf (con Int)))) (ret (own (con Int))))) (params buf) (body (app RawBuf.size buf)))) diff --git a/examples/raw_buf_drop_min.ail b/examples/raw_buf_drop_min.ail index 716e69e..57e6f13 100644 --- a/examples/raw_buf_drop_min.ail +++ b/examples/raw_buf_drop_min.ail @@ -1,7 +1,7 @@ (module raw_buf_drop_min (fn main (doc "Minimal RawBuf drop-leak reproducer (refs #42): a single owned 1-slot Int RawBuf, written once and read once, then dropped at the end of main. Prints 10. Under AILANG_RC_STATS the owned buffer's drop call must fire, so allocs are balanced by frees (live == 0). The leak is independent of binder names — the fully inline form (app RawBuf.get (app RawBuf.set (new RawBuf (con Int) 1) 0 10) 0) with no let-binding at all leaks identically.") - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (app print diff --git a/examples/raw_buf_float.ail b/examples/raw_buf_float.ail index 5ab6bc6..a306020 100644 --- a/examples/raw_buf_float.ail +++ b/examples/raw_buf_float.ail @@ -1,7 +1,7 @@ (module raw_buf_float (fn main (doc "raw-buf.4 Float variant: fill a 2-slot Float RawBuf with 1.5/2.5 and print their sum (4.0). Exercises RawBuf.new/.set/.get @ Float (double load/store).") - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (app print diff --git a/examples/raw_buf_int.ail b/examples/raw_buf_int.ail index e9def69..7ec5f29 100644 --- a/examples/raw_buf_int.ail +++ b/examples/raw_buf_int.ail @@ -1,7 +1,7 @@ (module raw_buf_int (fn main (doc "raw-buf.4 worked consumer: fill a 3-slot Int RawBuf with 10/20/30 and print their sum (60). Exercises RawBuf.new/.set/.get @ Int end-to-end.") - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (app print diff --git a/examples/raw_buf_loop_no_leak_pin.ail b/examples/raw_buf_loop_no_leak_pin.ail index df6c58c..3319889 100644 --- a/examples/raw_buf_loop_no_leak_pin.ail +++ b/examples/raw_buf_loop_no_leak_pin.ail @@ -1,7 +1,7 @@ (module raw_buf_loop_no_leak_pin (fn main (doc "RED-pin fixture for the loop-valued let-binder scope-close drop gap. Under --alloc=rc, an owned `(RawBuf Int)` whose let-value is a `(loop ...)` result and which is afterwards only borrow-read (RawBuf.get) is NEVER dropped at scope close. `is_rc_heap_allocated` (crates/ailang-codegen/src/drop.rs) has no `Term::Loop` arm, so the `Term::Let` lowering never flags the binder `filled` as trackable and emits no drop. AILANG_RC_STATS=1 reports `allocs=2 frees=1 live=1` (the RawBuf slab leaks). Compare examples/raw_buf_drop_min.ail (direct `(new ...)` value, no loop) which is dropped correctly. Expected post-fix: `allocs == frees && live == 0`.") - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (let buf (new RawBuf (con Int) 4) diff --git a/examples/raw_buf_new_type_arg_intsize.ail b/examples/raw_buf_new_type_arg_intsize.ail index 3b6177d..4614c33 100644 --- a/examples/raw_buf_new_type_arg_intsize.ail +++ b/examples/raw_buf_new_type_arg_intsize.ail @@ -1,7 +1,7 @@ (module raw_buf_new_type_arg_intsize (fn main (doc "RED-pin fixture for #51 leg 1 (raw-buf F7). A `(new RawBuf (con Int) 3)` buffer used ONLY via `RawBuf.size` — its element type is never observed by a later get/set, so the only carrier of the element type is the author's explicit `(con Int)` type-arg on `new`. Today `(new T )` desugars to `(app T.new )` and DROPS the leading NewArg::Type (crates/ailang-core/src/desugar.rs:1053), so the result's forall var `a` is unpinned and monomorphisation defaults it to Unit (crates/ailang-check/src/mono.rs free-fn-call arm; mirror crates/ailang-codegen/src/subst.rs). Codegen then mints `RawBuf_new__Unit`, which has no registered intercept (only Int/Float/Bool are in crates/ailang-codegen/src/intercepts.rs) and `ail build --alloc=rc` aborts: `intrinsic fn RawBuf_new__Unit has no registered intercept`. Expected post-fix: the explicit `(con Int)` is carried through monomorphisation so the buffer specialises to `RawBuf_new__Int`; build succeeds and the program prints 3 (the size).") - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (let b (new RawBuf (con Int) 3) diff --git a/examples/raw_buf_new_type_arg_unit_reject.ail b/examples/raw_buf_new_type_arg_unit_reject.ail index 53bb792..47dbd5a 100644 --- a/examples/raw_buf_new_type_arg_unit_reject.ail +++ b/examples/raw_buf_new_type_arg_unit_reject.ail @@ -1,7 +1,7 @@ (module raw_buf_new_type_arg_unit_reject (fn main (doc "RED-pin fixture for #51 leg 2 (raw-buf F7). A `(new RawBuf (con Unit) 3)` names Unit as the element type — but Unit is NOT in RawBuf's param-in restricted set {Int, Float, Bool}, so this must be REJECTED at `ail check` with `param-not-in-restricted-set` naming Unit. Today the `(new T )` desugar (crates/ailang-core/src/desugar.rs:1053) drops the `NewArg::Type` BEFORE check, so the `(con Unit)` element annotation never reaches the param-in enforcement in check_type_well_formed (crates/ailang-check/src/lib.rs:2084). `ail check` therefore passes (exit 0) and the program only crashes later at `ail build` with the same unregistered `RawBuf_new__Unit` intercept as leg 1. Expected post-fix: a pre-desugar reject on the Term::New shape (per the CLAUDE.md Term::New/desugar lockstep, crates/ailang-check/src/pre_desugar_validation.rs) fails `ail check` with `param-not-in-restricted-set` naming Unit; codegen is never reached.") - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (let b (new RawBuf (con Unit) 3) diff --git a/examples/raw_buf_reject_str.ail b/examples/raw_buf_reject_str.ail index 2010b81..98ee80a 100644 --- a/examples/raw_buf_reject_str.ail +++ b/examples/raw_buf_reject_str.ail @@ -1,5 +1,5 @@ (module raw_buf_reject_str (fn cant_str - (type (fn-type (params (borrow (con RawBuf (con Str)))) (ret (con Str)))) + (type (fn-type (params (borrow (con RawBuf (con Str)))) (ret (own (con Str))))) (params buf) (body (app RawBuf.get buf 0)))) diff --git a/examples/rc_app_let_partial_drop_leak.ail b/examples/rc_app_let_partial_drop_leak.ail index 35ccdb7..e09968e 100644 --- a/examples/rc_app_let_partial_drop_leak.ail +++ b/examples/rc_app_let_partial_drop_leak.ail @@ -6,17 +6,17 @@ (data Pair (ctor MkPair (con Cell) (con Cell))) (fn build_pair - (type (fn-type (params (con Int)) (ret (own (con Pair))))) + (type (fn-type (params (own (con Int))) (ret (own (con Pair))))) (params n) (body (term-ctor Pair MkPair (term-ctor Cell MkCell (term-ctor Wrap MkWrap n) (term-ctor Wrap MkWrap 2)) (term-ctor Cell MkCell (term-ctor Wrap MkWrap 3) (term-ctor Wrap MkWrap 4))))) (fn use_cell (suppress (code "over-strict-mode") (because "RC codegen test: shape used to feed App-bound let-close partial-drop into use_cell")) - (type (fn-type (params (own (con Cell))) (ret (con Int)))) + (type (fn-type (params (own (con Cell))) (ret (own (con Int))))) (params c) (body (match c (case (pat-ctor MkCell w1 w2) 1)))) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (let p (app build_pair 1) (app print (match p (case (pat-ctor MkPair a _) (app use_cell a)))))))) diff --git a/examples/rc_app_let_partial_drop_leak.prose.txt b/examples/rc_app_let_partial_drop_leak.prose.txt index 962c919..69420ed 100644 --- a/examples/rc_app_let_partial_drop_leak.prose.txt +++ b/examples/rc_app_let_partial_drop_leak.prose.txt @@ -6,18 +6,18 @@ data Cell = MkCell(Wrap, Wrap) data Pair = MkPair(Cell, Cell) -fn build_pair(n: Int) -> own Pair { +fn build_pair(n: own Int) -> own Pair { MkPair(MkCell(MkWrap(n), MkWrap(2)), MkCell(MkWrap(3), MkWrap(4))) } // @suppress over-strict-mode: RC codegen test: shape used to feed App-bound let-close partial-drop into use_cell -fn use_cell(c: own Cell) -> Int { +fn use_cell(c: own Cell) -> own Int { match c { MkCell(w1, w2) => 1 } } -fn main() -> Unit with IO { +fn main() -> own Unit with IO { print(match build_pair(1) { MkPair(a, _) => use_cell(a) }) diff --git a/examples/rc_box_drop.ail b/examples/rc_box_drop.ail index cc361a0..92357ed 100644 --- a/examples/rc_box_drop.ail +++ b/examples/rc_box_drop.ail @@ -4,7 +4,7 @@ (ctor MkBox (con Int))) (fn main (doc "Bind a heap-allocated MkBox to `b`, read its Int via match, print it. Under --alloc=rc, codegen emits `ailang_rc_dec(b)` at the let scope close: `b` is unique, has zero consume uses (only the match scrutinee, a borrow), and the body's tail value is the printed Unit, not `b` itself. The Box has no boxed children so shallow `free()` is correct.") - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (let b (term-ctor Box MkBox 42) (match b (case (pat-ctor MkBox x) (app print x))))))) diff --git a/examples/rc_drop_iterative_long_list.ail b/examples/rc_drop_iterative_long_list.ail index 77e540b..2b3770c 100644 --- a/examples/rc_drop_iterative_long_list.ail +++ b/examples/rc_drop_iterative_long_list.ail @@ -5,23 +5,23 @@ (drop-iterative)) (fn cons_n_acc (doc "Tail-recursive list builder. acc-prepended.") - (type (fn-type (params (con Int) (con IntList)) (ret (con IntList)))) + (type (fn-type (params (own (con Int)) (own (con IntList))) (ret (own (con IntList))))) (params n acc) (body (if (app eq n 0) acc (tail-app cons_n_acc (app - n 1) (term-ctor IntList ICons n acc))))) (fn cons_n (doc "Build [1, 2, ..., n] :: IntList. Order is reverse of build but immaterial for head/sum.") - (type (fn-type (params (con Int)) (ret (con IntList)))) + (type (fn-type (params (own (con Int))) (ret (own (con IntList))))) (params n) (body (app cons_n_acc n (term-ctor IntList INil)))) (fn head_or_zero (doc "Take ownership of an IntList; return its head if ICons, else 0. The (own ...) signature signals 18d.4 to emit an Own-param drop at fn return — which under the (drop-iterative) annotation is the iterative-worklist body, freeing the entire chain via the heap-allocated worklist instead of recursive cascade.") (suppress (code "over-strict-mode") (because "Iter 18e test: forces (drop-iterative) cascade via Own-param drop at fn return")) - (type (fn-type (params (own (con IntList))) (ret (con Int)))) + (type (fn-type (params (own (con IntList))) (ret (own (con Int))))) (params xs) (body (match xs (case (pat-ctor INil) 0) (case (pat-ctor ICons h t) h)))) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (app print (app head_or_zero (app cons_n 1000000)))))) diff --git a/examples/rc_let_alias_implicit_param.ail b/examples/rc_let_alias_implicit_param.ail index b6b9b05..c84e8a0 100644 --- a/examples/rc_let_alias_implicit_param.ail +++ b/examples/rc_let_alias_implicit_param.ail @@ -32,7 +32,7 @@ (fn build (type (fn-type - (params (con Int)) + (params (own (con Int))) (ret (own (con Tree))))) (params d) (body @@ -50,8 +50,8 @@ (fn pin_aliased (type (fn-type - (params (con Tree)) - (ret (con Int)))) + (params (borrow (con Tree))) + (ret (own (con Int))))) (params t) (body (let a t @@ -62,8 +62,8 @@ (fn loop (type (fn-type - (params (con Int) (con Tree)) - (ret (con Int)))) + (params (own (con Int)) (borrow (con Tree))) + (ret (own (con Int))))) (params n t) (body (if (app eq n 0) @@ -72,7 +72,7 @@ (app loop (app - n 1) t))))) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (let t (app build 2) diff --git a/examples/rc_let_implicit_returning_app.ail b/examples/rc_let_implicit_returning_app.ail index 34be6b6..e342d0c 100644 --- a/examples/rc_let_implicit_returning_app.ail +++ b/examples/rc_let_implicit_returning_app.ail @@ -1,32 +1,22 @@ -; Iter 18g tidy negative-coverage fixture: a let-binder whose -; value is the result of an `Implicit`-ret-mode fn call must NOT -; be trackable for scope-close drop. Implicit is the back-compat -; lane (default for unannotated fns); 18c.3 documented "Implicit- -; mode params do not get any dec — they leak rather than mis-dec", -; and 18g.2's symmetric carve-out is "Implicit-returning calls do -; not flow ownership to the caller, the caller must NOT dec". +; Positive-coverage fixture (spec 0062): a let-binder whose value +; is the result of an `(own T)`-ret-mode fn call IS trackable for +; scope-close drop. The callee's `(own T)` return signs the static +; contract that the caller now owns the returned cell, so the +; let-scope close emits the caller dec and the cell frees cleanly. ; -; The asymmetry to `(own T)`-returning calls is real and -; intentional: an Own-returning fn signs a static contract that -; the caller now owns the cell and must dec it; an Implicit- -; returning fn does not. If `is_rc_heap_allocated` were -; mistakenly to fire on this shape, the let-scope close would -; emit a dec for a cell whose ownership is not (statically) the -; caller's. The cell may be a fresh allocation the callee -; happens to return without explicit annotation, OR a static -; constant in BSS, OR a returned-borrow indistinguishable from -; an own at the callee's signature. Dec'ing it would be -; refcount underflow in the first two cases and use-after-free -; in the third. +; Pre-0062 this fn's return was the legacy `Implicit` lane, which +; codegen treated as "do not dec — leak rather than mis-dec", +; giving `live = 1`. Deleting `Implicit` (binary {Own, Borrow}) +; makes the return `Own`, the dec fires, and the cell frees — +; this is the typed-MIR leak fix the cutover delivers. ; ; Properties guarded: ; (1) Build succeeds (no consume-while-borrowed false positive). ; (2) Binary exits cleanly with stdout `7` (no crash, no ; refcount underflow). -; (3) `live = 1` at program exit — the one MkT cell leaks. The -; leak is intentional under the Implicit back-compat lane; -; it documents the asymmetry from the (own)-ret case (which -; would have `live = 0`). +; (3) `live = 0` at program exit — the one MkT cell frees. The +; own-return contract flows ownership to the caller, which +; dec's it at scope close. (module rc_let_implicit_returning_app @@ -38,8 +28,8 @@ (fn alloc (type (fn-type - (params (con Int)) - (ret (con T)))) + (params (own (con Int))) + (ret (own (con T))))) (params n) (body (term-ctor T MkT n))) @@ -48,14 +38,14 @@ (type (fn-type (params (borrow (con T))) - (ret (con Int)))) + (ret (own (con Int))))) (params x) (body (match x (case (pat-ctor MkT v) v)))) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (let x (app alloc 7) diff --git a/examples/rc_let_owned_app_leak.ail b/examples/rc_let_owned_app_leak.ail index c408223..2e1946e 100644 --- a/examples/rc_let_owned_app_leak.ail +++ b/examples/rc_let_owned_app_leak.ail @@ -24,7 +24,7 @@ (fn build (type (fn-type - (params (con Int)) + (params (own (con Int))) (ret (own (con Tree))))) (params d) (body @@ -38,7 +38,7 @@ (type (fn-type (params (borrow (con Tree))) - (ret (con Int)))) + (ret (own (con Int))))) (params t) (body (match t @@ -46,7 +46,7 @@ (case (pat-ctor TNode v l r) 1)))) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (let t (app build 1) diff --git a/examples/rc_list_drop.ail b/examples/rc_list_drop.ail index 981d651..e5b837b 100644 --- a/examples/rc_list_drop.ail +++ b/examples/rc_list_drop.ail @@ -5,13 +5,13 @@ (ctor Cons (con Int) (con IntList))) (fn sum_list (doc "Recursive fold over IntList.") - (type (fn-type (params (con IntList)) (ret (con Int)))) + (type (fn-type (params (own (con IntList))) (ret (own (con Int))))) (params xs) (body (match xs (case (pat-ctor Nil) 0) (case (pat-ctor Cons h t) (app + h (app sum_list t)))))) (fn main (doc "Build a 5-element IntList [1,2,3,4,5] and print its sum (15). Under --alloc=rc the recursive drop_rc_list_drop_IntList fn cascades through the tail at process exit when sum_list returns ownership-implicit (the consume_count of `xs` is 1 so no dec at the outer let; the test's correctness invariant is the byte-identical stdout — leak quantification is 18f's bench).") - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (let xs (term-ctor IntList Cons 1 (term-ctor IntList Cons 2 (term-ctor IntList Cons 3 (term-ctor IntList Cons 4 (term-ctor IntList Cons 5 (term-ctor IntList Nil)))))) (app print (app sum_list xs)))))) diff --git a/examples/rc_list_drop_borrow.ail b/examples/rc_list_drop_borrow.ail index 855eeee..6fea3d5 100644 --- a/examples/rc_list_drop_borrow.ail +++ b/examples/rc_list_drop_borrow.ail @@ -5,7 +5,7 @@ (ctor Cons (con Int) (con IntList))) (fn main (doc "Build a 5-element IntList and print only its head via match. xs has consume_count == 0 (only the match scrutinee, a borrow), so under --alloc=rc the let scope close emits `drop_rc_list_drop_borrow_IntList(xs)` which recursively dec-cascades over the whole 5-cell chain.") - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (let xs (term-ctor IntList Cons 11 (term-ctor IntList Cons 22 (term-ctor IntList Cons 33 (term-ctor IntList Cons 44 (term-ctor IntList Cons 55 (term-ctor IntList Nil)))))) (match xs (case (pat-ctor Nil) (app print 0)) diff --git a/examples/rc_match_arm_partial_drop_leak.ail b/examples/rc_match_arm_partial_drop_leak.ail index 06851a4..edf4fe8 100644 --- a/examples/rc_match_arm_partial_drop_leak.ail +++ b/examples/rc_match_arm_partial_drop_leak.ail @@ -6,17 +6,17 @@ (data Pair (ctor MkPair (con Cell) (con Cell))) (fn build_pair - (type (fn-type (params (con Int)) (ret (own (con Pair))))) + (type (fn-type (params (own (con Int))) (ret (own (con Pair))))) (params n) (body (term-ctor Pair MkPair (term-ctor Cell MkCell (term-ctor Wrap MkWrap n) (term-ctor Wrap MkWrap 2)) (term-ctor Cell MkCell (term-ctor Wrap MkWrap 3) (term-ctor Wrap MkWrap 4))))) (fn use_first (suppress (code "over-strict-mode") (because "RC codegen test: exercises Iter A outer-arm-close partial-drop")) - (type (fn-type (params (own (con Pair))) (ret (con Int)))) + (type (fn-type (params (own (con Pair))) (ret (own (con Int))))) (params p) (body (match p (case (pat-ctor MkPair a b) (match a (case (pat-ctor MkCell w1 _) 1)))))) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (app print (app use_first (app build_pair 1)))))) diff --git a/examples/rc_match_arm_partial_drop_leak.prose.txt b/examples/rc_match_arm_partial_drop_leak.prose.txt index e7fbd03..2b9f7b7 100644 --- a/examples/rc_match_arm_partial_drop_leak.prose.txt +++ b/examples/rc_match_arm_partial_drop_leak.prose.txt @@ -6,12 +6,12 @@ data Cell = MkCell(Wrap, Wrap) data Pair = MkPair(Cell, Cell) -fn build_pair(n: Int) -> own Pair { +fn build_pair(n: own Int) -> own Pair { MkPair(MkCell(MkWrap(n), MkWrap(2)), MkCell(MkWrap(3), MkWrap(4))) } // @suppress over-strict-mode: RC codegen test: exercises Iter A outer-arm-close partial-drop -fn use_first(p: own Pair) -> Int { +fn use_first(p: own Pair) -> own Int { match p { MkPair(a, b) => match a { MkCell(w1, _) => 1 @@ -19,6 +19,6 @@ fn use_first(p: own Pair) -> Int { } } -fn main() -> Unit with IO { +fn main() -> own Unit with IO { print(use_first(build_pair(1))) } diff --git a/examples/rc_own_param_drop.ail b/examples/rc_own_param_drop.ail index 5a9df42..d8db5bc 100644 --- a/examples/rc_own_param_drop.ail +++ b/examples/rc_own_param_drop.ail @@ -6,13 +6,13 @@ (fn head_or_zero (doc "Take ownership of an IntList; return its head if Cons, else 0. The tail is loaded as a pattern binder but never consumed — iter A dec's it at arm close. The outer cell is dec'd at fn return via iter B's Own-param emission.") (suppress (code "over-strict-mode") (because "RC codegen test: exercises Iter B Own-param dec at fn return")) - (type (fn-type (params (own (con IntList))) (ret (con Int)))) + (type (fn-type (params (own (con IntList))) (ret (own (con Int))))) (params xs) (body (match xs (case (pat-ctor Nil) 0) (case (pat-ctor Cons h t) h)))) (fn main (doc "Build a 5-element IntList; pass to head_or_zero (transferring ownership); print the result.") - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (let xs (term-ctor IntList Cons 11 (term-ctor IntList Cons 22 (term-ctor IntList Cons 33 (term-ctor IntList Cons 44 (term-ctor IntList Cons 55 (term-ctor IntList Nil)))))) (app print (app head_or_zero xs)))))) diff --git a/examples/rc_own_param_drop.prose.txt b/examples/rc_own_param_drop.prose.txt index 67afcf8..91c6e61 100644 --- a/examples/rc_own_param_drop.prose.txt +++ b/examples/rc_own_param_drop.prose.txt @@ -7,7 +7,7 @@ data IntList = Nil | Cons(Int, IntList) /// Take ownership of an IntList; return its head if Cons, else 0. The tail is /// loaded as a pattern binder but never consumed — iter A dec's it at arm /// close. The outer cell is dec'd at fn return via iter B's Own-param emission. -fn head_or_zero(xs: own IntList) -> Int { +fn head_or_zero(xs: own IntList) -> own Int { match xs { Nil => 0, Cons(h, t) => h @@ -16,7 +16,7 @@ fn head_or_zero(xs: own IntList) -> Int { /// Build a 5-element IntList; pass to head_or_zero (transferring ownership); /// print the result. -fn main() -> Unit with IO { +fn main() -> own Unit with IO { let xs = Cons(11, Cons(22, Cons(33, Cons(44, Cons(55, Nil))))); print(head_or_zero(xs)) } diff --git a/examples/rc_own_param_partial_drop_leak.ail b/examples/rc_own_param_partial_drop_leak.ail index 5deffdc..950dda5 100644 --- a/examples/rc_own_param_partial_drop_leak.ail +++ b/examples/rc_own_param_partial_drop_leak.ail @@ -6,16 +6,16 @@ (data Pair (ctor MkPair (con Cell) (con Cell))) (fn build_pair - (type (fn-type (params (con Int)) (ret (own (con Pair))))) + (type (fn-type (params (own (con Int))) (ret (own (con Pair))))) (params n) (body (term-ctor Pair MkPair (term-ctor Cell MkCell (term-ctor Wrap MkWrap n) (term-ctor Wrap MkWrap 2)) (term-ctor Cell MkCell (term-ctor Wrap MkWrap 3) (term-ctor Wrap MkWrap 4))))) (fn use_first (suppress (code "over-strict-mode") (because "RC codegen test: exercises Iter B fn-return dynamic-tag partial-drop")) - (type (fn-type (params (own (con Pair))) (ret (con Int)))) + (type (fn-type (params (own (con Pair))) (ret (own (con Int))))) (params p) (body (match p (case (pat-ctor MkPair a _) 1)))) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (app print (app use_first (app build_pair 1)))))) diff --git a/examples/rc_pin_recurse_implicit.ail b/examples/rc_pin_recurse_implicit.ail index e69746e..bb66f0d 100644 --- a/examples/rc_pin_recurse_implicit.ail +++ b/examples/rc_pin_recurse_implicit.ail @@ -21,7 +21,7 @@ (ctor TNode (con Int) (con Tree) (con Tree))) (fn build - (type (fn-type (params (con Int)) (ret (con Tree)))) + (type (fn-type (params (own (con Int))) (ret (own (con Tree))))) (params d) (body (if (app eq d 0) @@ -31,7 +31,7 @@ (app build (app - d 1)))))) (fn pin - (type (fn-type (params (con Tree)) (ret (con Int)))) + (type (fn-type (params (borrow (con Tree))) (ret (own (con Int))))) (params t) (body (match t @@ -39,7 +39,7 @@ (case (pat-ctor TNode v l r) 1)))) (fn loop - (type (fn-type (params (con Int) (con Tree)) (ret (con Int)))) + (type (fn-type (params (own (con Int)) (borrow (con Tree))) (ret (own (con Int))))) (params n t) (body (if (app eq n 0) @@ -48,7 +48,7 @@ (app loop (app - n 1) t))))) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (let t (app build 2) diff --git a/examples/rc_tail_sum_explicit_leak.ail b/examples/rc_tail_sum_explicit_leak.ail index 1ed29f8..5392e37 100644 --- a/examples/rc_tail_sum_explicit_leak.ail +++ b/examples/rc_tail_sum_explicit_leak.ail @@ -22,7 +22,7 @@ (fn cons_n_acc (type (fn-type - (params (con Int) (own (con IntList))) + (params (own (con Int)) (own (con IntList))) (ret (own (con IntList))))) (params n acc) (body @@ -35,7 +35,7 @@ (fn cons_n (type (fn-type - (params (con Int)) + (params (own (con Int))) (ret (own (con IntList))))) (params n) (body @@ -44,8 +44,8 @@ (fn sum_acc (type (fn-type - (params (own (con IntList)) (con Int)) - (ret (con Int)))) + (params (own (con IntList)) (own (con Int))) + (ret (own (con Int))))) (params xs acc) (body (match xs @@ -57,13 +57,13 @@ (type (fn-type (params (own (con IntList))) - (ret (con Int)))) + (ret (own (con Int))))) (params xs) (body (app sum_acc xs 0))) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (app print (app sum_list (app cons_n 100)))))) diff --git a/examples/reuse_as_demo.ail b/examples/reuse_as_demo.ail index fadcd34..4855591 100644 --- a/examples/reuse_as_demo.ail +++ b/examples/reuse_as_demo.ail @@ -43,7 +43,7 @@ (type (fn-type (params (own (con List))) - (ret (con Int)))) + (ret (own (con Int))))) (params xs) (body (match xs @@ -53,7 +53,7 @@ (fn main (doc "Build [1,2,3]; map_inc → [2,3,4]; sum_list → 9. Print 9.") - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (let xs diff --git a/examples/show_mono_pin_smoke.ail b/examples/show_mono_pin_smoke.ail index 8a71d03..4840022 100644 --- a/examples/show_mono_pin_smoke.ail +++ b/examples/show_mono_pin_smoke.ail @@ -1,5 +1,5 @@ (module show_mono_pin_smoke (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (let s1 (app show 42) (let s2 (app show true) (let s3 (app show "x") (let s4 (app show 1.5) (do io/print_str s1)))))))) diff --git a/examples/show_no_instance.ail b/examples/show_no_instance.ail index 45cd53a..3a434e6 100644 --- a/examples/show_no_instance.ail +++ b/examples/show_no_instance.ail @@ -1,5 +1,5 @@ (module show_no_instance (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (let f (lam (params (typed x (con Int))) (ret (con Int)) (body x)) (app print f))))) diff --git a/examples/show_print_smoke.ail b/examples/show_print_smoke.ail index 9db3d0f..ab879af 100644 --- a/examples/show_print_smoke.ail +++ b/examples/show_print_smoke.ail @@ -1,5 +1,5 @@ (module show_print_smoke (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (seq (seq (seq (seq (app print 42) (do io/print_str "\n")) (seq (app print true) (do io/print_str "\n"))) (seq (app print "hello") (do io/print_str "\n"))) (app print 3.14))))) diff --git a/examples/show_user_adt.ail b/examples/show_user_adt.ail index 8943e99..de645eb 100644 --- a/examples/show_user_adt.ail +++ b/examples/show_user_adt.ail @@ -8,6 +8,6 @@ (body (lam (params (typed x (con IntBox))) (ret (con Str)) (body (match x (case (pat-ctor MkIntBox n) (app int_to_str n)))))))) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (app print (term-ctor IntBox MkIntBox 7))))) diff --git a/examples/show_user_adt_with_label.ail b/examples/show_user_adt_with_label.ail index add6da2..abb787d 100644 --- a/examples/show_user_adt_with_label.ail +++ b/examples/show_user_adt_with_label.ail @@ -8,6 +8,6 @@ (body (lam (params (typed it (con Item))) (ret (con Str)) (body (match it (case (pat-ctor MkItem n) (app str_concat "Item " (app int_to_str n))))))))) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (seq (app print (term-ctor Item MkItem 42)) (do io/print_str "\n"))))) diff --git a/examples/sort.ail b/examples/sort.ail index 2a01855..113e4ae 100644 --- a/examples/sort.ail +++ b/examples/sort.ail @@ -5,26 +5,26 @@ (ctor Cons (con Int) (con IntList))) (fn insert (doc "Insert y into a sorted list xs, preserving order.") - (type (fn-type (params (con Int) (con IntList)) (ret (con IntList)))) + (type (fn-type (params (own (con Int)) (own (con IntList))) (ret (own (con IntList))))) (params y xs) (body (match xs (case (pat-ctor Nil) (term-ctor IntList Cons y (term-ctor IntList Nil))) (case (pat-ctor Cons h t) (if (app le y h) (term-ctor IntList Cons y (term-ctor IntList Cons h t)) (term-ctor IntList Cons h (app insert y t))))))) (fn sort (doc "Insertion sort: build the result by inserting each head into the sorted tail.") - (type (fn-type (params (con IntList)) (ret (con IntList)))) + (type (fn-type (params (own (con IntList))) (ret (own (con IntList))))) (params xs) (body (match xs (case (pat-ctor Nil) (term-ctor IntList Nil)) (case (pat-ctor Cons h t) (app insert h (app sort t)))))) (fn print_list - (type (fn-type (params (con IntList)) (ret (con Unit)) (effects IO))) + (type (fn-type (params (own (con IntList))) (ret (own (con Unit))) (effects IO))) (params xs) (body (match xs (case (pat-ctor Nil) (lit-unit)) (case (pat-ctor Cons h t) (seq (seq (app print h) (do io/print_str "\n")) (tail-app print_list t)))))) (fn main (doc "Sort and print [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5].") - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (let xs (term-ctor IntList Cons 3 (term-ctor IntList Cons 1 (term-ctor IntList Cons 4 (term-ctor IntList Cons 1 (term-ctor IntList Cons 5 (term-ctor IntList Cons 9 (term-ctor IntList Cons 2 (term-ctor IntList Cons 6 (term-ctor IntList Cons 5 (term-ctor IntList Cons 3 (term-ctor IntList Cons 5 (term-ctor IntList Nil)))))))))))) (app print_list (app sort xs)))))) diff --git a/examples/std_either.ail b/examples/std_either.ail index 02f8eee..f06ad0b 100644 --- a/examples/std_either.ail +++ b/examples/std_either.ail @@ -15,8 +15,8 @@ (type (forall (vars e a) (fn-type - (params a (con Either e a)) - (ret a)))) + (params (own a) (own (con Either e a))) + (ret (own a))))) (params default x) (body (match x @@ -28,8 +28,8 @@ (type (forall (vars e a) (fn-type - (params (con Either e a)) - (ret (con Bool))))) + (params (borrow (con Either e a))) + (ret (own (con Bool)))))) (params x) (body (match x @@ -41,8 +41,8 @@ (type (forall (vars e a) (fn-type - (params (con Either e a)) - (ret (con Bool))))) + (params (borrow (con Either e a))) + (ret (own (con Bool)))))) (params x) (body (match x @@ -54,9 +54,9 @@ (type (forall (vars e a b) (fn-type - (params (fn-type (params a) (ret b)) - (con Either e a)) - (ret (con Either e b))))) + (params (own (fn-type (params (own a)) (ret (own b)))) + (own (con Either e a))) + (ret (own (con Either e b)))))) (params f x) (body (match x @@ -70,10 +70,10 @@ (type (forall (vars e a c) (fn-type - (params (fn-type (params e) (ret c)) - (fn-type (params a) (ret c)) - (con Either e a)) - (ret c)))) + (params (own (fn-type (params (own e)) (ret (own c)))) + (own (fn-type (params (own a)) (ret (own c)))) + (own (con Either e a))) + (ret (own c))))) (params on_left on_right x) (body (match x diff --git a/examples/std_either_demo.ail b/examples/std_either_demo.ail index 0fa4d8b..1adb315 100644 --- a/examples/std_either_demo.ail +++ b/examples/std_either_demo.ail @@ -11,13 +11,13 @@ (fn inc (doc "Add 1 to an Int. Used as the function arg to map_right and either.") - (type (fn-type (params (con Int)) (ret (con Int)))) + (type (fn-type (params (own (con Int))) (ret (own (con Int))))) (params x) (body (app + x 1))) (fn main (doc "Drive each std_either combinator. Expected outputs (per line): 42, 99, true, true, 42, 6, 101.") - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (seq (seq (app print (app Either.from_right 0 (term-ctor Either Right 42))) (do io/print_str "\n")) diff --git a/examples/std_either_list.ail b/examples/std_either_list.ail index b883584..c022934 100644 --- a/examples/std_either_list.ail +++ b/examples/std_either_list.ail @@ -17,8 +17,8 @@ (type (forall (vars e a) (fn-type - (params (con List (con Either e a))) - (ret (con List e))))) + (params (own (con List (con Either e a)))) + (ret (own (con List e)))))) (params xs) (body (match xs @@ -33,8 +33,8 @@ (type (forall (vars e a) (fn-type - (params (con List (con Either e a))) - (ret (con List a))))) + (params (own (con List (con Either e a)))) + (ret (own (con List a)))))) (params xs) (body (match xs @@ -49,8 +49,8 @@ (type (forall (vars e a) (fn-type - (params (con List (con Either e a))) - (ret (con Pair (con List e) (con List a)))))) + (params (own (con List (con Either e a)))) + (ret (own (con Pair (con List e) (con List a))))))) (params xs) (body (match xs diff --git a/examples/std_either_list_demo.ail b/examples/std_either_list_demo.ail index 6692540..ae76d34 100644 --- a/examples/std_either_list_demo.ail +++ b/examples/std_either_list_demo.ail @@ -21,7 +21,7 @@ (fn main (doc "Drive lefts, rights, and partition_eithers on the same five-element List> (Left 1, Right 10, Left 2, Right 20, Right 30). Expected output (one per line): 2, 3, 2, 3.") - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (seq (seq (app print (app List.length (app std_either_list.lefts (term-ctor List Cons (term-ctor Either Left 1) (term-ctor List Cons (term-ctor Either Right 10) (term-ctor List Cons (term-ctor Either Left 2) (term-ctor List Cons (term-ctor Either Right 20) (term-ctor List Cons (term-ctor Either Right 30) (term-ctor List Nil))))))))) (do io/print_str "\n")) diff --git a/examples/std_list.ail b/examples/std_list.ail index 5603913..d3cc0a7 100644 --- a/examples/std_list.ail +++ b/examples/std_list.ail @@ -19,10 +19,10 @@ (type (forall (vars a b) (fn-type - (params (fn-type (params b a) (ret b)) - b - (con List a)) - (ret b)))) + (params (borrow (fn-type (params (own b) (own a)) (ret (own b)))) + (own b) + (own (con List a))) + (ret (own b))))) (params f acc xs) (body (match xs @@ -35,10 +35,10 @@ (type (forall (vars a b) (fn-type - (params (fn-type (params a b) (ret b)) - b - (con List a)) - (ret b)))) + (params (own (fn-type (params (own a) (own b)) (ret (own b)))) + (own b) + (own (con List a))) + (ret (own b))))) (params f acc xs) (body (match xs @@ -51,8 +51,8 @@ (type (forall (vars a) (fn-type - (params (con List a)) - (ret (con Int))))) + (params (own (con List a))) + (ret (own (con Int)))))) (params xs) (body (app fold_left @@ -67,8 +67,8 @@ (type (forall (vars a) (fn-type - (params (con List a)) - (ret (con List a))))) + (params (own (con List a))) + (ret (own (con List a)))))) (params xs) (body (app fold_left @@ -83,8 +83,8 @@ (type (forall (vars a) (fn-type - (params (con List a)) - (ret (con Bool))))) + (params (borrow (con List a))) + (ret (own (con Bool)))))) (params xs) (body (match xs @@ -96,8 +96,8 @@ (type (forall (vars a) (fn-type - (params (con List a)) - (ret (con Maybe a))))) + (params (own (con List a))) + (ret (own (con Maybe a)))))) (params xs) (body (match xs @@ -109,8 +109,8 @@ (type (forall (vars a) (fn-type - (params (con List a)) - (ret (con Maybe (con List a)))))) + (params (own (con List a))) + (ret (own (con Maybe (con List a))))))) (params xs) (body (match xs @@ -122,8 +122,8 @@ (type (forall (vars a) (fn-type - (params (con List a) (con List a)) - (ret (con List a))))) + (params (own (con List a)) (own (con List a))) + (ret (own (con List a)))))) (params xs ys) (body (match xs @@ -136,9 +136,9 @@ (type (forall (vars a b) (fn-type - (params (fn-type (params a) (ret b)) - (con List a)) - (ret (con List b))))) + (params (own (fn-type (params (own a)) (ret (own b)))) + (own (con List a))) + (ret (own (con List b)))))) (params f xs) (body (match xs @@ -151,9 +151,9 @@ (type (forall (vars a) (fn-type - (params (fn-type (params a) (ret (con Bool))) - (con List a)) - (ret (con List a))))) + (params (borrow (fn-type (params (borrow a)) (ret (own (con Bool))))) + (own (con List a))) + (ret (own (con List a)))))) (params p xs) (body (match xs @@ -168,8 +168,8 @@ (type (forall (vars a) (fn-type - (params (con Int) (con List a)) - (ret (con List a))))) + (params (own (con Int)) (own (con List a))) + (ret (own (con List a)))))) (params n xs) (body (if (app le n 0) @@ -184,8 +184,8 @@ (type (forall (vars a) (fn-type - (params (con Int) (con List a)) - (ret (con List a))))) + (params (own (con Int)) (own (con List a))) + (ret (own (con List a)))))) (params n xs) (body (if (app le n 0) diff --git a/examples/std_list_demo.ail b/examples/std_list_demo.ail index 72503f3..d46dfcf 100644 --- a/examples/std_list_demo.ail +++ b/examples/std_list_demo.ail @@ -12,25 +12,25 @@ (fn inc (doc "Add 1 to an Int. Used as the (a -> b) arg to map.") - (type (fn-type (params (con Int)) (ret (con Int)))) + (type (fn-type (params (own (con Int))) (ret (own (con Int))))) (params x) (body (app + x 1))) (fn add (doc "Binary +. Used as the fold accumulator op.") - (type (fn-type (params (con Int) (con Int)) (ret (con Int)))) + (type (fn-type (params (own (con Int)) (own (con Int))) (ret (own (con Int))))) (params a b) (body (app + a b))) (fn is_even (doc "Predicate: x mod 2 == 0.") - (type (fn-type (params (con Int)) (ret (con Bool)))) + (type (fn-type (params (own (con Int))) (ret (own (con Bool))))) (params x) (body (app eq (app % x 2) 0))) (fn double (doc "Multiply by 2. Used as the map arg.") - (type (fn-type (params (con Int)) (ret (con Int)))) + (type (fn-type (params (own (con Int))) (ret (own (con Int))))) (params x) (body (app * x 2))) @@ -47,7 +47,7 @@ (fn main (doc "Drive each std_list combinator once. Expected outputs (per line): 5, false, true, 1, 4, 10, 5, 2, 2, 15, 15.") - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (seq (seq (app print (app List.length xs)) (do io/print_str "\n")) diff --git a/examples/std_list_more_demo.ail b/examples/std_list_more_demo.ail index d51c419..d0ba1db 100644 --- a/examples/std_list_more_demo.ail +++ b/examples/std_list_more_demo.ail @@ -21,7 +21,7 @@ (fn main (doc "Expected output (one per line): 0, 3, 5, 5, 3, 0.") - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (seq (seq (app print (app List.length (app List.take 0 xs))) (do io/print_str "\n")) diff --git a/examples/std_list_stress.ail b/examples/std_list_stress.ail index 64f2a2d..94e24fb 100644 --- a/examples/std_list_stress.ail +++ b/examples/std_list_stress.ail @@ -13,8 +13,8 @@ (doc "Build [n, n-1, ..., 1] :: List Int via Cons recursion. Constructor-blocked; not tail.") (type (fn-type - (params (con Int)) - (ret (con List (con Int))))) + (params (own (con Int))) + (ret (own (con List (con Int)))))) (params n) (body (if (app eq n 0) @@ -27,14 +27,14 @@ (doc "Binary +. Used as the fold accumulator op for both fold_left and fold_right.") (type (fn-type - (params (con Int) (con Int)) - (ret (con Int)))) + (params (own (con Int)) (own (con Int))) + (ret (own (con Int))))) (params a b) (body (app + a b))) (fn main (doc "Build a 1000-element list, fold it both ways, print both sums (each 500500).") - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (seq (seq (app print (app List.fold_left add 0 (app build 1000))) (do io/print_str "\n")) diff --git a/examples/std_maybe.ail b/examples/std_maybe.ail index 08879fb..8f52a4d 100644 --- a/examples/std_maybe.ail +++ b/examples/std_maybe.ail @@ -13,8 +13,8 @@ (type (forall (vars a) (fn-type - (params a (con Maybe a)) - (ret a)))) + (params (own a) (own (con Maybe a))) + (ret (own a))))) (params default m) (body (match m @@ -26,8 +26,8 @@ (type (forall (vars a) (fn-type - (params (con Maybe a)) - (ret (con Bool))))) + (params (borrow (con Maybe a))) + (ret (own (con Bool)))))) (params m) (body (match m @@ -39,8 +39,8 @@ (type (forall (vars a) (fn-type - (params (con Maybe a)) - (ret (con Bool))))) + (params (borrow (con Maybe a))) + (ret (own (con Bool)))))) (params m) (body (match m @@ -52,9 +52,9 @@ (type (forall (vars a b) (fn-type - (params (fn-type (params a) (ret b)) - (con Maybe a)) - (ret (con Maybe b))))) + (params (own (fn-type (params (own a)) (ret (own b)))) + (own (con Maybe a))) + (ret (own (con Maybe b)))))) (params f m) (body (match m diff --git a/examples/std_maybe_demo.ail b/examples/std_maybe_demo.ail index 94d87bf..4bdeee1 100644 --- a/examples/std_maybe_demo.ail +++ b/examples/std_maybe_demo.ail @@ -12,13 +12,13 @@ (fn inc (doc "Add 1 to an Int. Used as the (a -> b) arg to map_maybe.") - (type (fn-type (params (con Int)) (ret (con Int)))) + (type (fn-type (params (own (con Int))) (ret (own (con Int))))) (params x) (body (app + x 1))) (fn main (doc "Drive each combinator once and print 7, 99, true, true, 42.") - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (seq (seq (app print (app Maybe.from_maybe 99 (term-ctor Maybe Just 7))) (do io/print_str "\n")) diff --git a/examples/std_pair.ail b/examples/std_pair.ail index b57f0f4..baf5d63 100644 --- a/examples/std_pair.ail +++ b/examples/std_pair.ail @@ -16,8 +16,8 @@ (type (forall (vars a b) (fn-type - (params (con Pair a b)) - (ret a)))) + (params (own (con Pair a b))) + (ret (own a))))) (params p) (body (match p @@ -28,8 +28,8 @@ (type (forall (vars a b) (fn-type - (params (con Pair a b)) - (ret b)))) + (params (own (con Pair a b))) + (ret (own b))))) (params p) (body (match p @@ -40,8 +40,8 @@ (type (forall (vars a b) (fn-type - (params (con Pair a b)) - (ret (con Pair b a))))) + (params (own (con Pair a b))) + (ret (own (con Pair b a)))))) (params p) (body (match p @@ -53,9 +53,9 @@ (type (forall (vars a b c) (fn-type - (params (fn-type (params a) (ret c)) - (con Pair a b)) - (ret (con Pair c b))))) + (params (own (fn-type (params (own a)) (ret (own c)))) + (own (con Pair a b))) + (ret (own (con Pair c b)))))) (params f p) (body (match p @@ -67,9 +67,9 @@ (type (forall (vars a b c) (fn-type - (params (fn-type (params b) (ret c)) - (con Pair a b)) - (ret (con Pair a c))))) + (params (own (fn-type (params (own b)) (ret (own c)))) + (own (con Pair a b))) + (ret (own (con Pair a c)))))) (params f p) (body (match p diff --git a/examples/std_pair_demo.ail b/examples/std_pair_demo.ail index 53a5f83..95e4f7f 100644 --- a/examples/std_pair_demo.ail +++ b/examples/std_pair_demo.ail @@ -10,19 +10,19 @@ (fn inc (doc "Add 1. Used as the (a -> c) arg.") - (type (fn-type (params (con Int)) (ret (con Int)))) + (type (fn-type (params (own (con Int))) (ret (own (con Int))))) (params x) (body (app + x 1))) (fn double (doc "Multiply by 2. Used as the (b -> c) arg.") - (type (fn-type (params (con Int)) (ret (con Int)))) + (type (fn-type (params (own (con Int))) (ret (own (con Int))))) (params x) (body (app * x 2))) (fn main (doc "Drive every combinator. Expected (per line): 7, 9, 9, 7, 8, 18.") - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (seq (seq (app print (app Pair.fst (term-ctor Pair MkPair 7 9))) (do io/print_str "\n")) diff --git a/examples/str_clone_cross_realisation.ail b/examples/str_clone_cross_realisation.ail index 33c454e..b54c350 100644 --- a/examples/str_clone_cross_realisation.ail +++ b/examples/str_clone_cross_realisation.ail @@ -1,6 +1,6 @@ (module str_clone_cross_realisation (fn main (doc "Iter 24.1: cross-realisation invariant for str_clone. Clones a heap-Str (produced by int_to_str 42) AND a static-Str (literal `abc`); both clones produce fresh heap-Str slabs that print their input bytes. Pins the uniform-consumer-ABI claim — str_clone only reads the len-field at offset 0 plus the bytes plus the NUL; it never consults the (absent for static-Str) rc_header. RC stats under --alloc=rc: allocs == 3 (int_to_str output + two str_clone outputs), frees == 3, live == 0.") - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (let src_heap (app int_to_str 42) (let c1 (app str_clone src_heap) (let c2 (app str_clone "abc") (let _a (seq (do io/print_str c1) (do io/print_str "\n")) (seq (do io/print_str c2) (do io/print_str "\n"))))))))) diff --git a/examples/str_clone_drop_rc.ail b/examples/str_clone_drop_rc.ail index fb0fb21..26f8896 100644 --- a/examples/str_clone_drop_rc.ail +++ b/examples/str_clone_drop_rc.ail @@ -1,6 +1,6 @@ (module str_clone_drop_rc (fn main (doc "Iter 24.1: pin that `str_clone` participates in RC discipline AND emits correct bytes. Input is a static-Str literal; the clone produces a fresh heap-Str slab whose payload is byte-equal to the input. RC stats under --alloc=rc with AILANG_RC_STATS=1: allocs == 1 (the clone), frees == 1 (let-binder drop at scope close), live == 0.") - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (let s (app str_clone "hello") (seq (do io/print_str s) (do io/print_str "\n")))))) diff --git a/examples/str_field_in_adt_heap.ail b/examples/str_field_in_adt_heap.ail index 9788885..5e0c43d 100644 --- a/examples/str_field_in_adt_heap.ail +++ b/examples/str_field_in_adt_heap.ail @@ -4,7 +4,7 @@ (ctor Box (con Str))) (fn main (doc "Iter hs.4: pin that `field_drop_call` for `Str` correctly dispatches to `ailang_rc_dec` when the Str payload is heap-Str. Construct `Box(int_to_str(42))`, bind, pattern-match to read the Str, print it. Under --alloc=rc with AILANG_RC_STATS=1: allocs >= 2 (ADT cell + heap-Str slab), allocs == frees, live == 0. drop.rs's Str-arm comment names the codegen-level elision that protects static-Str from this same path; here the Str is heap-allocated so rc_dec is the right (and only) consumer.") - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (let b (term-ctor Boxed Box (app int_to_str 42)) (match b (case (pat-ctor Box s) (seq (do io/print_str s) (do io/print_str "\n")))))))) diff --git a/examples/sum.ail b/examples/sum.ail index 79f2e55..13e0567 100644 --- a/examples/sum.ail +++ b/examples/sum.ail @@ -1,10 +1,10 @@ (module sum (fn sum (doc "rekursive Summe 0..=n") - (type (fn-type (params (con Int)) (ret (con Int)))) + (type (fn-type (params (own (con Int))) (ret (own (con Int))))) (params n) (body (if (app eq n 0) 0 (app + n (app sum (app - n 1)))))) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (app print (app sum 10))))) diff --git a/examples/test_22b1_dup_classmod.ail b/examples/test_22b1_dup_classmod.ail index b1e87b2..feb0ef3 100644 --- a/examples/test_22b1_dup_classmod.ail +++ b/examples/test_22b1_dup_classmod.ail @@ -2,4 +2,4 @@ (class TShow (param a) (method tshow - (type (fn-type (params a) (ret (con Str))))))) + (type (fn-type (params (own a)) (ret (own (con Str)))))))) diff --git a/examples/test_22b1_dup_same_module.ail b/examples/test_22b1_dup_same_module.ail index 67b8487..b6e01e6 100644 --- a/examples/test_22b1_dup_same_module.ail +++ b/examples/test_22b1_dup_same_module.ail @@ -2,7 +2,7 @@ (class TShow (param a) (method tshow - (type (fn-type (params a) (ret (con Str)))))) + (type (fn-type (params (own a)) (ret (own (con Str))))))) (instance (class TShow) (type (con Int)) diff --git a/examples/test_22b1_missing_method.ail b/examples/test_22b1_missing_method.ail index 33e2723..fb2d1b6 100644 --- a/examples/test_22b1_missing_method.ail +++ b/examples/test_22b1_missing_method.ail @@ -2,9 +2,9 @@ (class TEq (param a) (method teq - (type (fn-type (params a a) (ret (con Bool))))) + (type (fn-type (params (own a) (own a)) (ret (own (con Bool)))))) (method tne - (type (fn-type (params a a) (ret (con Bool)))))) + (type (fn-type (params (own a) (own a)) (ret (own (con Bool))))))) (instance (class TEq) (type (con Int)) diff --git a/examples/test_22b1_orphan_class.ail b/examples/test_22b1_orphan_class.ail index 9ac6e43..3509a27 100644 --- a/examples/test_22b1_orphan_class.ail +++ b/examples/test_22b1_orphan_class.ail @@ -2,7 +2,7 @@ (class TShow (param a) (method tshow - (type (fn-type (params a) (ret (con Str)))))) + (type (fn-type (params (own a)) (ret (own (con Str))))))) (instance (class TShow) (type (con Int)) diff --git a/examples/test_22b1_orphan_third_classmod.ail b/examples/test_22b1_orphan_third_classmod.ail index 8f5f6bd..ff4dd33 100644 --- a/examples/test_22b1_orphan_third_classmod.ail +++ b/examples/test_22b1_orphan_third_classmod.ail @@ -2,4 +2,4 @@ (class TShow (param a) (method tshow - (type (fn-type (params a) (ret (con Str))))))) + (type (fn-type (params (own a)) (ret (own (con Str)))))))) diff --git a/examples/test_22b2_class_method_lookup.ail b/examples/test_22b2_class_method_lookup.ail index 701a34e..0c7a379 100644 --- a/examples/test_22b2_class_method_lookup.ail +++ b/examples/test_22b2_class_method_lookup.ail @@ -2,7 +2,7 @@ (class TShow (param a) (method tshow - (type (fn-type (params a) (ret (con Str)))))) + (type (fn-type (params (own a)) (ret (own (con Str))))))) (instance (class TShow) (type (con Int)) diff --git a/examples/test_22b2_constraint_declared.ail b/examples/test_22b2_constraint_declared.ail index 5374144..08d75fe 100644 --- a/examples/test_22b2_constraint_declared.ail +++ b/examples/test_22b2_constraint_declared.ail @@ -2,8 +2,8 @@ (class TShow (param a) (method tshow - (type (fn-type (params a) (ret (con Str)))))) + (type (fn-type (params (own a)) (ret (own (con Str))))))) (fn describe - (type (forall (vars a) (constraints (constraint TShow a)) (fn-type (params a) (ret (con Str))))) + (type (forall (vars a) (constraints (constraint TShow a)) (fn-type (params (own a)) (ret (own (con Str)))))) (params x) (body (app tshow x)))) diff --git a/examples/test_22b2_constraint_declared_via_superclass.ail b/examples/test_22b2_constraint_declared_via_superclass.ail index 07b73a2..58a1f99 100644 --- a/examples/test_22b2_constraint_declared_via_superclass.ail +++ b/examples/test_22b2_constraint_declared_via_superclass.ail @@ -2,13 +2,13 @@ (class TEq (param a) (method teq - (type (fn-type (params a a) (ret (con Bool)))))) + (type (fn-type (params (own a) (own a)) (ret (own (con Bool))))))) (class TOrd (param a) (superclass (class TEq) (type a)) (method tlt - (type (fn-type (params a a) (ret (con Bool)))))) + (type (fn-type (params (own a) (own a)) (ret (own (con Bool))))))) (fn uses_eq_via_ord - (type (forall (vars a) (constraints (constraint TOrd a)) (fn-type (params a a) (ret (con Bool))))) + (type (forall (vars a) (constraints (constraint TOrd a)) (fn-type (params (own a) (own a)) (ret (own (con Bool)))))) (params x y) (body (app teq x y)))) diff --git a/examples/test_22b2_constraint_declared_via_superclass.prose.txt b/examples/test_22b2_constraint_declared_via_superclass.prose.txt index d47280b..7b263dd 100644 --- a/examples/test_22b2_constraint_declared_via_superclass.prose.txt +++ b/examples/test_22b2_constraint_declared_via_superclass.prose.txt @@ -1,14 +1,14 @@ // module test_22b2_constraint_declared_via_superclass class TEq a { - fn teq(x: a, x1: a) -> Bool + fn teq(x: own a, x1: own a) -> own Bool } class TOrd a extends TEq { - fn tlt(x: a, x1: a) -> Bool + fn tlt(x: own a, x1: own a) -> own Bool } forall where TOrd a -fn uses_eq_via_ord(x: a, y: a) -> Bool { +fn uses_eq_via_ord(x: own a, y: own a) -> own Bool { teq(x, y) } diff --git a/examples/test_22b2_instance_present.ail b/examples/test_22b2_instance_present.ail index 5dfbe55..22d83a1 100644 --- a/examples/test_22b2_instance_present.ail +++ b/examples/test_22b2_instance_present.ail @@ -2,13 +2,13 @@ (class TShow (param a) (method tshow - (type (fn-type (params a) (ret (con Str)))))) + (type (fn-type (params (own a)) (ret (own (con Str))))))) (instance (class TShow) (type (con Int)) (method tshow (body (lam (params (typed x a)) (ret (con Str)) (body "n"))))) (fn main - (type (fn-type (params) (ret (con Str)))) + (type (fn-type (params) (ret (own (con Str))))) (params) (body (app tshow 42)))) diff --git a/examples/test_22b2_instance_present.prose.txt b/examples/test_22b2_instance_present.prose.txt index 806f65a..33b6ff6 100644 --- a/examples/test_22b2_instance_present.prose.txt +++ b/examples/test_22b2_instance_present.prose.txt @@ -1,7 +1,7 @@ // module test_22b2_instance_present class TShow a { - fn tshow(x: a) -> Str + fn tshow(x: own a) -> own Str } instance TShow Int { @@ -12,6 +12,6 @@ instance TShow Int { } } -fn main() -> Str { +fn main() -> own Str { tshow(42) } diff --git a/examples/test_22b2_invalid_superclass_param.ail.json b/examples/test_22b2_invalid_superclass_param.ail.json index 9aacdb5..0365556 100644 --- a/examples/test_22b2_invalid_superclass_param.ail.json +++ b/examples/test_22b2_invalid_superclass_param.ail.json @@ -4,35 +4,70 @@ "imports": [], "defs": [ { - "kind": "class", "name": "Eq", "param": "a", + "kind": "class", + "name": "Eq", + "param": "a", "methods": [ { "name": "eq", "type": { "k": "fn", "params": [ - { "k": "var", "name": "a" }, - { "k": "var", "name": "a" } + { + "k": "var", + "name": "a" + }, + { + "k": "var", + "name": "a" + } ], - "ret": { "k": "con", "name": "Bool" }, + "param_modes": [ + "own", + "own" + ], + "ret": { + "k": "con", + "name": "Bool" + }, + "ret_mode": "own", "effects": [] } } ] }, { - "kind": "class", "name": "Ord", "param": "a", - "superclass": { "class": "Eq", "type": "b" }, + "kind": "class", + "name": "Ord", + "param": "a", + "superclass": { + "class": "Eq", + "type": "b" + }, "methods": [ { "name": "lt", "type": { "k": "fn", "params": [ - { "k": "var", "name": "a" }, - { "k": "var", "name": "a" } + { + "k": "var", + "name": "a" + }, + { + "k": "var", + "name": "a" + } ], - "ret": { "k": "con", "name": "Bool" }, + "param_modes": [ + "own", + "own" + ], + "ret": { + "k": "con", + "name": "Bool" + }, + "ret_mode": "own", "effects": [] } } diff --git a/examples/test_22b2_kind_mismatch.ail.json b/examples/test_22b2_kind_mismatch.ail.json index 0328761..c4bcf5a 100644 --- a/examples/test_22b2_kind_mismatch.ail.json +++ b/examples/test_22b2_kind_mismatch.ail.json @@ -13,13 +13,50 @@ "type": { "k": "fn", "params": [ - { "k": "fn", "params": [{ "k": "var", "name": "a" }], - "ret": { "k": "var", "name": "b" }, "effects": [] }, - { "k": "con", "name": "f", - "args": [{ "k": "var", "name": "a" }] } + { + "k": "fn", + "params": [ + { + "k": "var", + "name": "a" + } + ], + "param_modes": [ + "own" + ], + "ret": { + "k": "var", + "name": "b" + }, + "ret_mode": "own", + "effects": [] + }, + { + "k": "con", + "name": "f", + "args": [ + { + "k": "var", + "name": "a" + } + ] + } ], - "ret": { "k": "con", "name": "f", - "args": [{ "k": "var", "name": "b" }] }, + "param_modes": [ + "own", + "own" + ], + "ret": { + "k": "con", + "name": "f", + "args": [ + { + "k": "var", + "name": "b" + } + ] + }, + "ret_mode": "own", "effects": [] } } diff --git a/examples/test_22b2_method_name_collision_class_class.ail b/examples/test_22b2_method_name_collision_class_class.ail index be26c0a..75c6c4c 100644 --- a/examples/test_22b2_method_name_collision_class_class.ail +++ b/examples/test_22b2_method_name_collision_class_class.ail @@ -2,8 +2,8 @@ (class A (param a) (method foo - (type (fn-type (params a) (ret (con Unit)))))) + (type (fn-type (params (own a)) (ret (own (con Unit))))))) (class B (param b) (method foo - (type (fn-type (params b) (ret (con Unit))))))) + (type (fn-type (params (own b)) (ret (own (con Unit)))))))) diff --git a/examples/test_22b2_method_name_collision_class_fn.ail b/examples/test_22b2_method_name_collision_class_fn.ail index 0debe67..c9c4b50 100644 --- a/examples/test_22b2_method_name_collision_class_fn.ail +++ b/examples/test_22b2_method_name_collision_class_fn.ail @@ -2,8 +2,8 @@ (class Greet (param a) (method greet - (type (fn-type (params a) (ret (con Str)))))) + (type (fn-type (params (own a)) (ret (own (con Str))))))) (fn greet - (type (fn-type (params (con Int)) (ret (con Str)))) + (type (fn-type (params (own (con Int))) (ret (own (con Str))))) (params x) (body "hi"))) diff --git a/examples/test_22b2_missing_constraint.ail b/examples/test_22b2_missing_constraint.ail index 53b3792..b08dbf9 100644 --- a/examples/test_22b2_missing_constraint.ail +++ b/examples/test_22b2_missing_constraint.ail @@ -2,8 +2,8 @@ (class TShow (param a) (method tshow - (type (fn-type (params a) (ret (con Str)))))) + (type (fn-type (params (own a)) (ret (own (con Str))))))) (fn describe - (type (forall (vars a) (fn-type (params a) (ret (con Str))))) + (type (forall (vars a) (fn-type (params (own a)) (ret (own (con Str)))))) (params x) (body (app tshow x)))) diff --git a/examples/test_22b2_missing_constraint_subclass.ail b/examples/test_22b2_missing_constraint_subclass.ail index 72dc343..cc01332 100644 --- a/examples/test_22b2_missing_constraint_subclass.ail +++ b/examples/test_22b2_missing_constraint_subclass.ail @@ -2,13 +2,13 @@ (class TEq (param a) (method teq - (type (fn-type (params a a) (ret (con Bool)))))) + (type (fn-type (params (own a) (own a)) (ret (own (con Bool))))))) (class TOrd (param a) (superclass (class TEq) (type a)) (method tlt - (type (fn-type (params a a) (ret (con Bool)))))) + (type (fn-type (params (own a) (own a)) (ret (own (con Bool))))))) (fn needs_ord_but_only_has_eq - (type (forall (vars a) (constraints (constraint TEq a)) (fn-type (params a a) (ret (con Bool))))) + (type (forall (vars a) (constraints (constraint TEq a)) (fn-type (params (own a) (own a)) (ret (own (con Bool)))))) (params x y) (body (app tlt x y)))) diff --git a/examples/test_22b2_missing_superclass_instance.ail b/examples/test_22b2_missing_superclass_instance.ail index 3b051a1..21a5d1b 100644 --- a/examples/test_22b2_missing_superclass_instance.ail +++ b/examples/test_22b2_missing_superclass_instance.ail @@ -2,12 +2,12 @@ (class TEq (param a) (method teq - (type (fn-type (params a a) (ret (con Bool)))))) + (type (fn-type (params (own a) (own a)) (ret (own (con Bool))))))) (class TOrd (param a) (superclass (class TEq) (type a)) (method tlt - (type (fn-type (params a a) (ret (con Bool)))))) + (type (fn-type (params (own a) (own a)) (ret (own (con Bool))))))) (instance (class TOrd) (type (con Int)) diff --git a/examples/test_22b2_no_instance.ail b/examples/test_22b2_no_instance.ail index 487184d..9c1419f 100644 --- a/examples/test_22b2_no_instance.ail +++ b/examples/test_22b2_no_instance.ail @@ -2,8 +2,8 @@ (class TShow (param a) (method tshow - (type (fn-type (params a) (ret (con Str)))))) + (type (fn-type (params (own a)) (ret (own (con Str))))))) (fn main - (type (fn-type (params) (ret (con Str)))) + (type (fn-type (params) (ret (own (con Str))))) (params) (body (app tshow 42)))) diff --git a/examples/test_22b2_overriding_nonexistent.ail b/examples/test_22b2_overriding_nonexistent.ail index d0d8309..f173c6a 100644 --- a/examples/test_22b2_overriding_nonexistent.ail +++ b/examples/test_22b2_overriding_nonexistent.ail @@ -2,7 +2,7 @@ (class TEq (param a) (method teq - (type (fn-type (params a a) (ret (con Bool)))))) + (type (fn-type (params (own a) (own a)) (ret (own (con Bool))))))) (instance (class TEq) (type (con Int)) diff --git a/examples/test_22b2_two_fns_missing_constraint.ail b/examples/test_22b2_two_fns_missing_constraint.ail index 79414f1..59b2773 100644 --- a/examples/test_22b2_two_fns_missing_constraint.ail +++ b/examples/test_22b2_two_fns_missing_constraint.ail @@ -2,12 +2,12 @@ (class TShow (param a) (method tshow - (type (fn-type (params a) (ret (con Str)))))) + (type (fn-type (params (own a)) (ret (own (con Str))))))) (fn describe_first - (type (forall (vars a) (fn-type (params a) (ret (con Str))))) + (type (forall (vars a) (fn-type (params (own a)) (ret (own (con Str)))))) (params x) (body (app tshow x))) (fn describe_second - (type (forall (vars b) (fn-type (params b) (ret (con Str))))) + (type (forall (vars b) (fn-type (params (own b)) (ret (own (con Str)))))) (params y) (body (app tshow y)))) diff --git a/examples/test_22b2_unbound_constraint_var.ail.json b/examples/test_22b2_unbound_constraint_var.ail.json index 8742375..e6f8f45 100644 --- a/examples/test_22b2_unbound_constraint_var.ail.json +++ b/examples/test_22b2_unbound_constraint_var.ail.json @@ -18,14 +18,34 @@ "name": "foo", "type": { "k": "forall", - "vars": ["a"], + "vars": [ + "a" + ], "constraints": [ - { "class": "Bar", "type": { "k": "var", "name": "z" } } + { + "class": "Bar", + "type": { + "k": "var", + "name": "z" + } + } ], "body": { "k": "fn", - "params": [{ "k": "var", "name": "a" }], - "ret": { "k": "con", "name": "Unit" }, + "params": [ + { + "k": "var", + "name": "a" + } + ], + "param_modes": [ + "own" + ], + "ret": { + "k": "con", + "name": "Unit" + }, + "ret_mode": "own", "effects": [] } } diff --git a/examples/test_22b2_xmod_classmod.ail b/examples/test_22b2_xmod_classmod.ail index 2a2ccb9..7842a0c 100644 --- a/examples/test_22b2_xmod_classmod.ail +++ b/examples/test_22b2_xmod_classmod.ail @@ -2,7 +2,7 @@ (class TShow (param a) (method tshow - (type (fn-type (params a) (ret (con Str)))))) + (type (fn-type (params (own a)) (ret (own (con Str))))))) (instance (class TShow) (type (con Int)) diff --git a/examples/test_22b2_xmod_classmod_noinst.ail b/examples/test_22b2_xmod_classmod_noinst.ail index f6ecc8a..76763c2 100644 --- a/examples/test_22b2_xmod_classmod_noinst.ail +++ b/examples/test_22b2_xmod_classmod_noinst.ail @@ -2,4 +2,4 @@ (class TShow (param a) (method tshow - (type (fn-type (params a) (ret (con Str))))))) + (type (fn-type (params (own a)) (ret (own (con Str)))))))) diff --git a/examples/test_22b2_xmod_instance_present.ail b/examples/test_22b2_xmod_instance_present.ail index e7cc00d..10148a4 100644 --- a/examples/test_22b2_xmod_instance_present.ail +++ b/examples/test_22b2_xmod_instance_present.ail @@ -1,6 +1,6 @@ (module test_22b2_xmod_instance_present (import test_22b2_xmod_classmod) (fn main - (type (fn-type (params) (ret (con Str)))) + (type (fn-type (params) (ret (own (con Str))))) (params) (body (app tshow 42)))) diff --git a/examples/test_22b2_xmod_missing_constraint.ail b/examples/test_22b2_xmod_missing_constraint.ail index 4c2c237..f30cdf0 100644 --- a/examples/test_22b2_xmod_missing_constraint.ail +++ b/examples/test_22b2_xmod_missing_constraint.ail @@ -1,6 +1,6 @@ (module test_22b2_xmod_missing_constraint (import test_22b2_xmod_classmod_noinst) (fn describe - (type (forall (vars a) (fn-type (params a) (ret (con Str))))) + (type (forall (vars a) (fn-type (params (own a)) (ret (own (con Str)))))) (params x) (body (app tshow x)))) diff --git a/examples/test_22b2_xmod_no_instance.ail b/examples/test_22b2_xmod_no_instance.ail index bfa451d..da879e8 100644 --- a/examples/test_22b2_xmod_no_instance.ail +++ b/examples/test_22b2_xmod_no_instance.ail @@ -1,6 +1,6 @@ (module test_22b2_xmod_no_instance (import test_22b2_xmod_classmod_noinst) (fn main - (type (fn-type (params) (ret (con Str)))) + (type (fn-type (params) (ret (own (con Str))))) (params) (body (app tshow 42)))) diff --git a/examples/test_22b3_chained_calls.ail b/examples/test_22b3_chained_calls.ail index bbd10d8..1ccf06f 100644 --- a/examples/test_22b3_chained_calls.ail +++ b/examples/test_22b3_chained_calls.ail @@ -2,11 +2,11 @@ (class B (param a) (method b_method - (type (fn-type (params a) (ret (con Int)))))) + (type (fn-type (params (own a)) (ret (own (con Int))))))) (class A (param a) (method a_method - (type (fn-type (params a) (ret (con Int)))) + (type (fn-type (params (own a)) (ret (own (con Int))))) (default (lam (params (typed x a)) (ret (con Int)) (body (app b_method x)))))) (instance (class B) @@ -17,6 +17,6 @@ (class A) (type (con Int))) (fn main - (type (fn-type (params) (ret (con Int)))) + (type (fn-type (params) (ret (own (con Int))))) (params) (body (app a_method 5)))) diff --git a/examples/test_22b3_coherence_two_instances.ail b/examples/test_22b3_coherence_two_instances.ail index 6765e83..993bd05 100644 --- a/examples/test_22b3_coherence_two_instances.ail +++ b/examples/test_22b3_coherence_two_instances.ail @@ -2,7 +2,7 @@ (class R (param a) (method r - (type (fn-type (params a) (ret (con Int)))))) + (type (fn-type (params (own a)) (ret (own (con Int))))))) (instance (class R) (type (con Int)) @@ -14,6 +14,6 @@ (method r (body (lam (params (typed b a)) (ret (con Int)) (body 22))))) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (seq (seq (app print (app r 0)) (do io/print_str "\n")) (seq (app print (app r true)) (do io/print_str "\n")))))) diff --git a/examples/test_22b3_default_e2e.ail b/examples/test_22b3_default_e2e.ail index 6cfcae6..46739e9 100644 --- a/examples/test_22b3_default_e2e.ail +++ b/examples/test_22b3_default_e2e.ail @@ -2,12 +2,12 @@ (class D (param a) (method dval - (type (fn-type (params a) (ret (con Int)))) + (type (fn-type (params (own a)) (ret (own (con Int))))) (default (lam (params (typed x a)) (ret (con Int)) (body 99))))) (instance (class D) (type (con Int))) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (app print (app dval 0))))) diff --git a/examples/test_22b3_default_e2e.prose.txt b/examples/test_22b3_default_e2e.prose.txt index 6e4a33e..b3cd9f2 100644 --- a/examples/test_22b3_default_e2e.prose.txt +++ b/examples/test_22b3_default_e2e.prose.txt @@ -1,7 +1,7 @@ // module test_22b3_default_e2e class D a { - fn dval(x: a) -> Int default { + fn dval(x: own a) -> own Int default { |x: a| -> Int { 99 } @@ -11,6 +11,6 @@ class D a { instance D Int { } -fn main() -> Unit with IO { +fn main() -> own Unit with IO { print(dval(0)) } diff --git a/examples/test_22b3_dup_call_sites.ail b/examples/test_22b3_dup_call_sites.ail index f48da0a..c66a9e5 100644 --- a/examples/test_22b3_dup_call_sites.ail +++ b/examples/test_22b3_dup_call_sites.ail @@ -2,13 +2,13 @@ (class TShow (param a) (method tshow - (type (fn-type (params a) (ret (con Str)))))) + (type (fn-type (params (own a)) (ret (own (con Str))))))) (instance (class TShow) (type (con Int)) (method tshow (body (lam (params (typed x a)) (ret (con Str)) (body "n"))))) (fn main - (type (fn-type (params) (ret (con Str)))) + (type (fn-type (params) (ret (own (con Str))))) (params) (body (let _a (app tshow 5) (app tshow 7))))) diff --git a/examples/test_22b3_mono_synthetic.ail b/examples/test_22b3_mono_synthetic.ail index 8867871..449c7d4 100644 --- a/examples/test_22b3_mono_synthetic.ail +++ b/examples/test_22b3_mono_synthetic.ail @@ -2,13 +2,13 @@ (class Foo (param a) (method foo - (type (fn-type (params a) (ret (con Int)))))) + (type (fn-type (params (own a)) (ret (own (con Int))))))) (instance (class Foo) (type (con Int)) (method foo (body (lam (params (typed i a)) (ret (con Int)) (body i))))) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (app print (app foo 5))))) diff --git a/examples/test_22b3_no_call_sites.ail b/examples/test_22b3_no_call_sites.ail index 912fccd..1a02f4b 100644 --- a/examples/test_22b3_no_call_sites.ail +++ b/examples/test_22b3_no_call_sites.ail @@ -2,13 +2,13 @@ (class TShow (param a) (method tshow - (type (fn-type (params a) (ret (con Str)))))) + (type (fn-type (params (own a)) (ret (own (con Str))))))) (instance (class TShow) (type (con Int)) (method tshow (body (lam (params (typed x a)) (ret (con Str)) (body "n"))))) (fn main - (type (fn-type (params) (ret (con Str)))) + (type (fn-type (params) (ret (own (con Str))))) (params) (body "no-call"))) diff --git a/examples/test_22b3_shadow_class_method.ail b/examples/test_22b3_shadow_class_method.ail index c654146..cec3df0 100644 --- a/examples/test_22b3_shadow_class_method.ail +++ b/examples/test_22b3_shadow_class_method.ail @@ -2,11 +2,11 @@ (class TShow (param a) (method tshow - (type (fn-type (params a) (ret (con Str)))))) + (type (fn-type (params (own a)) (ret (own (con Str))))))) (class Foo (param a) (method foo - (type (fn-type (params a) (ret (con Str)))))) + (type (fn-type (params (own a)) (ret (own (con Str))))))) (instance (class TShow) (type (con Int)) @@ -18,6 +18,6 @@ (method foo (body (lam (params (typed x a)) (ret (con Str)) (body "f"))))) (fn main - (type (fn-type (params) (ret (con Str)))) + (type (fn-type (params) (ret (own (con Str))))) (params) (body (let _t (app tshow 5) (let tshow "shadow" (let _u tshow (app foo 7))))))) diff --git a/examples/test_22b3_xmod_e2e_classmod.ail b/examples/test_22b3_xmod_e2e_classmod.ail index 03119de..2980bbd 100644 --- a/examples/test_22b3_xmod_e2e_classmod.ail +++ b/examples/test_22b3_xmod_e2e_classmod.ail @@ -2,7 +2,7 @@ (class XR (param a) (method xr - (type (fn-type (params a) (ret (con Int)))))) + (type (fn-type (params (own a)) (ret (own (con Int))))))) (instance (class XR) (type (con Int)) diff --git a/examples/test_22b3_xmod_e2e_main.ail b/examples/test_22b3_xmod_e2e_main.ail index f8f5cc4..8cd7369 100644 --- a/examples/test_22b3_xmod_e2e_main.ail +++ b/examples/test_22b3_xmod_e2e_main.ail @@ -1,6 +1,6 @@ (module test_22b3_xmod_e2e_main (import test_22b3_xmod_e2e_classmod) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (app print (app xr 0))))) diff --git a/examples/test_22c_user_class_e2e.ail b/examples/test_22c_user_class_e2e.ail index 3d43399..45e4c3f 100644 --- a/examples/test_22c_user_class_e2e.ail +++ b/examples/test_22c_user_class_e2e.ail @@ -4,7 +4,7 @@ (class Foo (param a) (method foo - (type (fn-type (params (borrow a)) (ret (con Int)))))) + (type (fn-type (params (borrow a)) (ret (own (con Int))))))) (instance (class Foo) (type (con IntBox)) @@ -12,6 +12,6 @@ (body (lam (params (typed b a)) (ret (con Int)) (body (match b (case (pat-ctor MkIntBox v) v))))))) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (app print (app foo (term-ctor IntBox MkIntBox 42)))))) diff --git a/examples/test_ct1_bad_qualifier.ail.json b/examples/test_ct1_bad_qualifier.ail.json index a02d4a1..c6dcbfb 100644 --- a/examples/test_ct1_bad_qualifier.ail.json +++ b/examples/test_ct1_bad_qualifier.ail.json @@ -9,11 +9,21 @@ "type": { "k": "fn", "params": [], - "ret": { "k": "con", "name": "Mystery.Type" }, + "param_modes": [], + "ret": { + "k": "con", + "name": "Mystery.Type" + }, + "ret_mode": "own", "effects": [] }, "params": [], - "body": { "t": "lit", "lit": { "kind": "unit" } } + "body": { + "t": "lit", + "lit": { + "kind": "unit" + } + } } ] } diff --git a/examples/test_ct1_bare_xmod_rejected.ail.json b/examples/test_ct1_bare_xmod_rejected.ail.json index 46d36b1..468fb5a 100644 --- a/examples/test_ct1_bare_xmod_rejected.ail.json +++ b/examples/test_ct1_bare_xmod_rejected.ail.json @@ -9,8 +9,15 @@ "type": { "k": "fn", "params": [], - "ret": { "k": "con", "name": "Mystery_Type" }, - "effects": ["IO"] + "param_modes": [], + "ret": { + "k": "con", + "name": "Mystery_Type" + }, + "ret_mode": "own", + "effects": [ + "IO" + ] }, "params": [], "body": { diff --git a/examples/test_ct1_qualified_class_rejected.ail.json b/examples/test_ct1_qualified_class_rejected.ail.json index 929f769..a684aae 100644 --- a/examples/test_ct1_qualified_class_rejected.ail.json +++ b/examples/test_ct1_qualified_class_rejected.ail.json @@ -6,7 +6,10 @@ { "kind": "instance", "class": "prelude.Eq", - "type": { "k": "con", "name": "Int" }, + "type": { + "k": "con", + "name": "Int" + }, "methods": [] } ] diff --git a/examples/test_loop_binder_captured_by_lambda.ail.json b/examples/test_loop_binder_captured_by_lambda.ail.json index aad2e6c..c604a42 100644 --- a/examples/test_loop_binder_captured_by_lambda.ail.json +++ b/examples/test_loop_binder_captured_by_lambda.ail.json @@ -9,7 +9,12 @@ "type": { "k": "fn", "params": [], - "ret": { "k": "con", "name": "Int" }, + "param_modes": [], + "ret": { + "k": "con", + "name": "Int" + }, + "ret_mode": "own", "effects": [] }, "params": [], @@ -19,8 +24,17 @@ "binders": [ { "name": "acc", - "type": { "k": "con", "name": "Int" }, - "init": { "t": "lit", "lit": { "kind": "int", "value": 0 } } + "type": { + "k": "con", + "name": "Int" + }, + "init": { + "t": "lit", + "lit": { + "kind": "int", + "value": 0 + } + } } ], "body": { @@ -30,11 +44,24 @@ "t": "lam", "params": [], "param-types": [], - "ret-type": { "k": "con", "name": "Int" }, + "ret-type": { + "k": "con", + "name": "Int" + }, "effects": [], - "body": { "t": "var", "name": "acc" } + "body": { + "t": "var", + "name": "acc" + } }, - "body": { "t": "app", "fn": { "t": "var", "name": "f" }, "args": [] } + "body": { + "t": "app", + "fn": { + "t": "var", + "name": "f" + }, + "args": [] + } } } } diff --git a/examples/test_mono_ctor_main.ail b/examples/test_mono_ctor_main.ail index a6f1bed..1900bf5 100644 --- a/examples/test_mono_ctor_main.ail +++ b/examples/test_mono_ctor_main.ail @@ -3,7 +3,7 @@ (class Trivial (param a) (method trivial - (type (fn-type (params a) (ret (con Int)))))) + (type (fn-type (params (own a)) (ret (own (con Int))))))) (instance (class Trivial) (type (con Int)) @@ -11,12 +11,12 @@ (body (lam (params (typed x (con Int))) (ret (con Int)) (body x))))) (fn head_or_zero (doc "Pattern-matches Cons from an imported module's List. Bug repro: with a class+instance present in the workspace, the mono pass walks this body and synth's local-flat ctor_index resolves Cons to bare \"List\", which mismatches the qualified scrutinee type \"test_mono_ctor_listmod.List\".") - (type (fn-type (params (con test_mono_ctor_listmod.List (con Int))) (ret (con Int)))) + (type (fn-type (params (own (con test_mono_ctor_listmod.List (con Int)))) (ret (own (con Int))))) (params xs) (body (match xs (case (pat-ctor Cons h _) h) (case (pat-ctor Nil) 0)))) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (app print (app head_or_zero (term-ctor test_mono_ctor_listmod.List Cons 7 (term-ctor test_mono_ctor_listmod.List Nil))))))) diff --git a/examples/test_mono_imports_classmod.ail b/examples/test_mono_imports_classmod.ail index 8738dba..e5956a3 100644 --- a/examples/test_mono_imports_classmod.ail +++ b/examples/test_mono_imports_classmod.ail @@ -2,13 +2,13 @@ (class Foo (param a) (method foo - (type (fn-type (params a) (ret (con Int)))))) + (type (fn-type (params (own a)) (ret (own (con Int))))))) (instance (class Foo) (type (con Int)) (method foo (body (lam (params (typed x a)) (ret (con Int)) (body x))))) (fn helper - (type (fn-type (params (con Int)) (ret (con Int)))) + (type (fn-type (params (own (con Int))) (ret (own (con Int))))) (params n) (body (app + n 100)))) diff --git a/examples/test_mono_imports_main.ail b/examples/test_mono_imports_main.ail index df66f9c..2c47854 100644 --- a/examples/test_mono_imports_main.ail +++ b/examples/test_mono_imports_main.ail @@ -1,6 +1,6 @@ (module test_mono_imports_main (import test_mono_imports_classmod) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (app print (app test_mono_imports_classmod.helper 42))))) diff --git a/examples/test_mono_recursive_fn_bug.ail b/examples/test_mono_recursive_fn_bug.ail index af23010..ac74022 100644 --- a/examples/test_mono_recursive_fn_bug.ail +++ b/examples/test_mono_recursive_fn_bug.ail @@ -2,17 +2,17 @@ (class Foo (param a) (method foo - (type (fn-type (params a) (ret (con Int)))))) + (type (fn-type (params (own a)) (ret (own (con Int))))))) (instance (class Foo) (type (con Int)) (method foo (body (lam (params (typed x a)) (ret (con Int)) (body x))))) (fn loop - (type (fn-type (params (con Int) (con Int)) (ret (con Int)))) + (type (fn-type (params (own (con Int)) (own (con Int))) (ret (own (con Int))))) (params acc i) (body (if (app eq i 0) acc (tail-app loop (app + acc i) (app - i 1))))) (fn main - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (app print (app loop 0 5))))) diff --git a/examples/test_recur_arity_mismatch.ail.json b/examples/test_recur_arity_mismatch.ail.json index 220c1d8..0efc3e2 100644 --- a/examples/test_recur_arity_mismatch.ail.json +++ b/examples/test_recur_arity_mismatch.ail.json @@ -6,16 +6,60 @@ { "kind": "fn", "name": "bad_arity", - "type": { "k": "fn", "params": [], "ret": { "k": "con", "name": "Int" }, "effects": [] }, + "type": { + "k": "fn", + "params": [], + "param_modes": [], + "ret": { + "k": "con", + "name": "Int" + }, + "ret_mode": "own", + "effects": [] + }, "params": [], "doc": "loop-recur iter 2: 2 binders, recur passes 1 arg -> recur-arity-mismatch.", "body": { "t": "loop", "binders": [ - { "name": "a", "type": { "k": "con", "name": "Int" }, "init": { "t": "lit", "lit": { "kind": "int", "value": 0 } } }, - { "name": "b", "type": { "k": "con", "name": "Int" }, "init": { "t": "lit", "lit": { "kind": "int", "value": 1 } } } + { + "name": "a", + "type": { + "k": "con", + "name": "Int" + }, + "init": { + "t": "lit", + "lit": { + "kind": "int", + "value": 0 + } + } + }, + { + "name": "b", + "type": { + "k": "con", + "name": "Int" + }, + "init": { + "t": "lit", + "lit": { + "kind": "int", + "value": 1 + } + } + } ], - "body": { "t": "recur", "args": [ { "t": "var", "name": "a" } ] } + "body": { + "t": "recur", + "args": [ + { + "t": "var", + "name": "a" + } + ] + } } } ] diff --git a/examples/test_recur_not_in_tail_position.ail.json b/examples/test_recur_not_in_tail_position.ail.json index 9221132..2ad193d 100644 --- a/examples/test_recur_not_in_tail_position.ail.json +++ b/examples/test_recur_not_in_tail_position.ail.json @@ -8,23 +8,83 @@ "name": "bad_recur", "type": { "k": "fn", - "params": [ { "k": "con", "name": "Int" } ], - "ret": { "k": "con", "name": "Int" }, + "params": [ + { + "k": "con", + "name": "Int" + } + ], + "param_modes": [ + "own" + ], + "ret": { + "k": "con", + "name": "Int" + }, + "ret_mode": "own", "effects": [] }, - "params": [ "n" ], + "params": [ + "n" + ], "doc": "loop-recur iter 2: recur as an argument to + is NOT in tail position -> recur-not-in-tail-position.", "body": { "t": "loop", "binders": [ - { "name": "i", "type": { "k": "con", "name": "Int" }, "init": { "t": "lit", "lit": { "kind": "int", "value": 0 } } } + { + "name": "i", + "type": { + "k": "con", + "name": "Int" + }, + "init": { + "t": "lit", + "lit": { + "kind": "int", + "value": 0 + } + } + } ], "body": { "t": "app", - "fn": { "t": "var", "name": "+" }, + "fn": { + "t": "var", + "name": "+" + }, "args": [ - { "t": "lit", "lit": { "kind": "int", "value": 1 } }, - { "t": "recur", "args": [ { "t": "app", "fn": { "t": "var", "name": "+" }, "args": [ { "t": "var", "name": "i" }, { "t": "lit", "lit": { "kind": "int", "value": 1 } } ] } ] } + { + "t": "lit", + "lit": { + "kind": "int", + "value": 1 + } + }, + { + "t": "recur", + "args": [ + { + "t": "app", + "fn": { + "t": "var", + "name": "+" + }, + "args": [ + { + "t": "var", + "name": "i" + }, + { + "t": "lit", + "lit": { + "kind": "int", + "value": 1 + } + } + ] + } + ] + } ] } } diff --git a/examples/test_recur_outside_loop.ail.json b/examples/test_recur_outside_loop.ail.json index ef19edb..fcfa20b 100644 --- a/examples/test_recur_outside_loop.ail.json +++ b/examples/test_recur_outside_loop.ail.json @@ -6,10 +6,31 @@ { "kind": "fn", "name": "main", - "type": { "k": "fn", "params": [], "ret": { "k": "con", "name": "Int" }, "effects": [] }, + "type": { + "k": "fn", + "params": [], + "param_modes": [], + "ret": { + "k": "con", + "name": "Int" + }, + "ret_mode": "own", + "effects": [] + }, "params": [], "doc": "loop-recur iter 2: recur with no enclosing loop -> recur-outside-loop.", - "body": { "t": "recur", "args": [ { "t": "lit", "lit": { "kind": "int", "value": 1 } } ] } + "body": { + "t": "recur", + "args": [ + { + "t": "lit", + "lit": { + "kind": "int", + "value": 1 + } + } + ] + } } ] } diff --git a/examples/test_recur_type_mismatch.ail.json b/examples/test_recur_type_mismatch.ail.json index a073bb4..e375ae3 100644 --- a/examples/test_recur_type_mismatch.ail.json +++ b/examples/test_recur_type_mismatch.ail.json @@ -6,15 +6,49 @@ { "kind": "fn", "name": "bad_type", - "type": { "k": "fn", "params": [], "ret": { "k": "con", "name": "Int" }, "effects": [] }, + "type": { + "k": "fn", + "params": [], + "param_modes": [], + "ret": { + "k": "con", + "name": "Int" + }, + "ret_mode": "own", + "effects": [] + }, "params": [], "doc": "loop-recur iter 2: binder is Int, recur arg is Bool -> recur-type-mismatch.", "body": { "t": "loop", "binders": [ - { "name": "i", "type": { "k": "con", "name": "Int" }, "init": { "t": "lit", "lit": { "kind": "int", "value": 0 } } } + { + "name": "i", + "type": { + "k": "con", + "name": "Int" + }, + "init": { + "t": "lit", + "lit": { + "kind": "int", + "value": 0 + } + } + } ], - "body": { "t": "recur", "args": [ { "t": "lit", "lit": { "kind": "bool", "value": true } } ] } + "body": { + "t": "recur", + "args": [ + { + "t": "lit", + "lit": { + "kind": "bool", + "value": true + } + } + ] + } } } ] diff --git a/examples/unreachable_demo.ail b/examples/unreachable_demo.ail index f99457f..adf16c8 100644 --- a/examples/unreachable_demo.ail +++ b/examples/unreachable_demo.ail @@ -19,7 +19,7 @@ (fn safe_div (doc "a/b for b != 0; otherwise panic via __unreachable__.") - (type (fn-type (params (con Int) (con Int)) (ret (con Int)))) + (type (fn-type (params (own (con Int)) (own (con Int))) (ret (own (con Int))))) (params a b) (body (if (app eq b 0) @@ -28,7 +28,7 @@ (fn main (doc "Drive safe_div with non-zero divisors. Expected: 4, 5.") - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (seq (seq (app print (app safe_div 8 2)) (do io/print_str "\n")) diff --git a/examples/ws_broken.ail b/examples/ws_broken.ail index 92ede35..f987a58 100644 --- a/examples/ws_broken.ail +++ b/examples/ws_broken.ail @@ -2,6 +2,6 @@ (import ws_lib) (fn main (doc "Iter 5b negative fixture: ruft ws_lib.bogus auf — Modul existiert, Def nicht. Triggert `unknown-import`.") - (type (fn-type (params) (ret (con Int)))) + (type (fn-type (params) (ret (own (con Int))))) (params) (body (app ws_lib.bogus 1 2)))) diff --git a/examples/ws_lib.ail b/examples/ws_lib.ail index c262c32..c6eeed0 100644 --- a/examples/ws_lib.ail +++ b/examples/ws_lib.ail @@ -1,6 +1,6 @@ (module ws_lib (fn add (doc "Iter 5a fixture: einfache Add-Funktion, die ws_main importiert.") - (type (fn-type (params (con Int) (con Int)) (ret (con Int)))) + (type (fn-type (params (own (con Int)) (own (con Int))) (ret (own (con Int))))) (params a b) (body (app + a b)))) diff --git a/examples/ws_main.ail b/examples/ws_main.ail index 0913498..e25b971 100644 --- a/examples/ws_main.ail +++ b/examples/ws_main.ail @@ -2,6 +2,6 @@ (import ws_lib) (fn main (doc "Iter 5b fixture: ruft ws_lib.add auf, druckt das Ergebnis. So ist Cross-Module-Typcheck am Beispiel beobachtbar.") - (type (fn-type (params) (ret (con Unit)) (effects IO))) + (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (app print (app ws_lib.add 2 3))))) diff --git a/examples/ws_unknown_module.ail b/examples/ws_unknown_module.ail index 3c33a5c..5f2f63b 100644 --- a/examples/ws_unknown_module.ail +++ b/examples/ws_unknown_module.ail @@ -1,6 +1,6 @@ (module ws_unknown_module (fn main (doc "Iter 5b negative fixture: kein Import, aber qualifizierter Verweis `nope.x`. Triggert `unknown-module`.") - (type (fn-type (params) (ret (con Int)))) + (type (fn-type (params) (ret (own (con Int))))) (params) (body nope.x)))