feat(codegen): higher-order calls through computed and field callees#148
Merged
Conversation
Calls and iterator callbacks whose callee is an arbitrary expression — a chained application (`add3(1)(2)(3)`), a function held in a record field (`cfg.processor`), or any computed call result — previously failed loudly. They now recover the callee's signature from the type table and dispatch through the existing closure cell: - builder.rs: `callee_fn_type` resolves the function `Type` of a callee expression (identifier / chained call / record-field access). - expr.rs: the non-identifier callee in `gen_call` dispatches via the recovered `FnSig` instead of bailing; still fails loudly for non-functions. - iter.rs: `callback_of` evaluates a computed/field callback once to a closure handle (`Callback::Value`) and calls it per element through its cell. Closes plan 0001. Extends function_composition_test with a chained 3-deep call, a computed iterator callback, and a record-field filter callback; adds a codegen unit test for the three paths.
MelbourneDeveloper
added a commit
that referenced
this pull request
Jun 25, 2026
…ator, handler-owned effect state + resume front-end (#149) ## TLDR Adds editor **variable hover** (declared/inferred type + `///` docs), a single-source-of-truth **built-in function docs generator**, **handler-owned mutable state** for algebraic effects, and the parse/type **front-end for explicit `resume`** — all behind a green `make ci`. ## Details **LSP / editor — variable hover (`[LSP-HOVER]`)** - `collect_all_symbols` now deep-walks every expression body (blocks, match arms, lambdas, call args, string interpolations, handler `in {…}` blocks), so a `let`/`mut` nested anywhere is discoverable — fixing "no hover bubble on a variable inside an HTTP server block". - Hover shows the declared type, or the **inferred** type for unannotated bindings, via position-keyed `ProgramTypes.lets` / `let_type` (new `crates/osprey-types/src/info.rs` + `check.rs`). - Variables are documentable with `///` like functions: grammar `doc_comment` on `let`, captured through syntax lowering into the AST `doc` field, rendered as hover prose. The binding position anchors on the name/keyword field, so a leading `///` never shifts it. - lspkit is reused, not reimplemented; the one missing text primitive is a documented shim in `text.rs` filed upstream as `Nimblesite/lspkit#2`. Spec `0020` gains `[LSP-HOVER]`, `[LSP-HOVER-VARIABLES]`, `[LSP-HOVER-DOCS]`, `[LSP-REUSE-LSPKIT]`. **Built-in docs generator — `osprey --docs --docs-dir <dir>`** - New `crates/osprey-cli/src/docs.rs` + `crates/osprey-types/src/builtin_docs{,_lang,_sys}.rs`: the compiler's type schemes are the single source of truth — one page per built-in plus an index, with stale pages pruned. Regenerates 90 `website/src/docs/functions/*` pages so the website and editor hover show identical signatures/types. **Algebraic effects** - **Handler-owned state (`[EFFECTS-HANDLER-STATE]`)**: handler arms read/write a `mut` captured from the enclosing scope (promoted to a shared heap cell; per-region env carried on the handler stack). Makes the `State` effect fully usable. Runtime in `effects_runtime.c`; codegen in `effects.rs` / `lower.rs` / `expr.rs`. Reference example `compiler/examples/tested/effects/http_state_levels.osp`. - **Explicit `resume` front-end**: grammar / AST / syntax-lowering + Hindley-Milner typing (`resume` typed against the operation result; rejected outside a handler arm). The continuation **runtime/codegen is not yet wired** — `resume` parses and type-checks but is currently rejected at code generation; spec `0017` and plan `0008` mark it specified-but-not-yet-executable. **Examples / runtime** - `compiler/examples/statefulhttp/{server,tui}.osp` + README: a stateful HTTP server and a terminal UI. Terminal/raw-mode additions in `system_runtime.c`; HTTP handler return-type alignment in `http_server_runtime.c`. **VS Code extension** - Debug-config synthesis extracted and unit-tested; server-command resolution hardening; test harness/runner updates; FEATURES/README/TESTING docs refreshed. > Scope note: this branch is named `feature/higher-order-calls`, but that feature already shipped in the merged PR #148. The changes here are the **post-#148** work that accumulated on the branch (the four areas above); they are unrelated to higher-order calls. ## How Do The Automated Tests Prove It Works? - **Variable hover (Rust, `crates/osprey-lsp`)**: `hover_on_a_let_binding_uses_the_name_and_type_form` (asserts `limit: int`), `hover_on_a_local_let_shows_inferred_type_and_docs` (asserts inferred `greeting: string` **and** the `///` doc prose), `hover_on_a_documented_function_renders_its_docs`, and `collect_all_symbols_descends_into_every_expression_form` (walks every expression container). - **Variable hover (VS Code e2e / vsix)**: `vscode-extension/test/suite/extension.test.ts` — "CHUNKY: full language-intelligence sweep…" asserts `radius: int` / `count: int` on unannotated lets, and "CHUNKY: a `///`-documented variable hovers with its type AND its docs" asserts a top-level **and** a block-nested documented `let` hover with type + doc prose through a live `executeHoverProvider` against the launched LSP. - **Docs generator (Rust, `crates/osprey-cli/src/docs.rs`)**: `generate_writes_a_page_per_builtin_plus_an_index`, `prune_deletes_stale_pages_but_keeps_live_ones_and_the_index`, `slugs_disambiguate_case_collisions_and_stay_unique`, `slug_map_falls_back_to_a_numeric_suffix_when_the_type_slug_also_collides`, `page_uses_scheme_types_and_omits_empty_sections`. - **Handler-owned state**: the differential harness runs `http_state_levels.osp` through `osprey --run` and matches `.expectedoutput` byte-for-byte (part of `PASS=48 FAIL=0`). - **Resume typing**: `osprey-types` `select_and_handler_expressions` type-checks a `handle … resume(v + 1000) …` program (auto-unwrap of the arithmetic `Result` into the operation-result slot). - **Gates**: `make ci` green — clippy `-D warnings`, `cargo fmt --check`, all 7 crate coverage thresholds (osprey-lsp 99.1%, osprey-types 98.7%, osprey-cli 96.4%, osprey-codegen 95.6%, …), vscode-extension 90.37% ≥ 89%, differential `PASS=48 FAIL=0 NOEXP=0` + `FC_OK`.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
TLDR
Calls and iterator callbacks whose callee is an arbitrary expression — a chained application
add3(1)(2)(3), a function held in a record fieldcfg.processor, or any computed call result — now compile and dispatch through the closure cell instead of failing withunsupported construct.Details
For an FP language this closes a core gap: only a plain-identifier callee (or the special
maker(args)(args)shape) lowered before; everything else bailed (indirect / higher-order call, oriterator callback must be a function name or lambda). The dispatch machinery (closure::cell_call) already existed — the missing piece was recovering the callee's signature.crates/osprey-codegen/src/builder.rs— newcallee_fn_type(expr)recovers the functionTypean expression evaluates to in callee/callback position: a named function or function-typed local, a chained call (peeling one arrow per application, soadd3(1)(2)(3)resolves), or a function-valued record field (resolving the owner from the bound value's type tag, else a unique field-name match).crates/osprey-codegen/src/expr.rs—gen_call's non-identifier callee now recovers aFnSigviacallee_fn_typeand dispatches throughcall_fn_value→cell_call; it still fails loudly when the callee is not a function value.crates/osprey-codegen/src/iter.rs—callback_ofgains aCallback::Valuevariant: a computed/field callback is evaluated once to a closure handle and called per element through its cell, somap/filter/fold/forEachacceptmakeAdder(10)andcfg.processoras callbacks.Out of scope (separately tracked): the UFCS method-call spelling
obj.field(args), which the parser desugars tofield(obj, args);function_composition_testalready documents this. The capability is reachable today vialet p = obj.fieldthenp(x), or as an iterator callback.Closes plan
0001; removes it fromdocs/plans/.How Do The Automated Tests Prove It Works?
higher_order_calls_through_computed_and_field_calleescompiles a program with all three forms (chained 3-deep call,makeAdder(10)map callback,cfg.keepfilter callback) and asserts the IR contains a closure cell-call (to { i8* }*) — i.e. each higher-order callee loads a function pointer from a cell rather than erroring.compiler/examples/tested/basics/function_composition_test.ospaddschain3: ${tri3(1)(2)(3)} computed: ${...} fieldcb: ${...}and assertschain3: 6 computed: 36 fieldcb: 23byte-for-byte (range(1,4) mapped throughmakeAdder(10)folds to 36; range(8,13) filtered by the field predicaten > 10folds to 23).PASS=47 FAIL=0 NOEXP=0,FC_OK— the change is purely additive (it only fires where codegen previously bailed), so no existing example regresses.osprey-codegen 95.6% ≥ 95%. Full-workspacecargo fmt --check,cargo clippy --workspace --all-targets -D warnings, andcargo test --workspaceare clean.