feat(match): list patterns [head, ...tail] — the last production primitive#146
Merged
Conversation
…imitive Land list patterns in `match`, the final unshipped production-app primitive (the blocker for an Osprey-native JSON parser). All four forms work: `[]`, `[a, b]`, `[head, ...tail]`, `[a, b, ...rest]`, with `_` element wildcards. Grammar + AST + lowering + Hindley-Milner inference + codegen, built over the existing `osprey_list_drop` runtime. Implements [TYPE-LIST-PATTERNS]. Fixes a real grammar ambiguity uncovered by the feature: a match-arm body greedily swallowed the next arm's `[...]` as a postfix index. The index `[` is now `token.immediate`, so `=> 0 [h, ...t] => …` parses as two arms. Coverage push (example tests now count): an in-process `examples_compile` harness compiles every tested example through parse→typecheck→codegen under cargo-llvm-cov, plus assertion-loaded unit tests for cli/sandbox/freevars/ unify and the lspkit-based LSP server/wire/engine. crates 52% -> 92.9% (threshold raised to 90). vscode-extension 52% -> ~81% (threshold 75). Deslop duplication ratcheted 11.0% -> 9.5% (measured 9.20% after DRYing the LSP test harness). Removes docs/plans (completed + superseded design docs; recoverable from git history) and repoints the four spec links that referenced them.
The vscode-extension test suite reached 90.00% line coverage (35 passing tests covering activation, command handlers, the Shipwright version handshake, and the binary-resolution helpers; the key unlock was forcing the target `.osp` document active before driving the `osprey.compile`/`osprey.run` commands, which `outputChannel.show()` otherwise steals). Gate set to 89 (measured − 1) to absorb macOS↔Linux line-coverage drift in the platform branches, since `vscode-test` can't run locally to re-verify while VS Code is open. Both project gates are now at the 90% target band (crates 92.9%/90, vscode 90.0%/89).
…tched compiler The Run/Compile commands shelled out to a bare `osprey` on PATH, which resolved to a stale global install (v1.0.0) that predates list patterns — so `[head, ...tail]` examples failed with parser errors in the editor even though the freshly-built compiler handles them. - Runner now resolves the compiler exactly as the language server does (user setting -> version-matched bundled binary -> PATH), so the VSIX runs against its own bundled `osprey`, never whatever is on PATH. - `make _vsix_bundle` stages libfiber_runtime.a + libhttp_runtime.a beside the bundled compiler; find_runtime_lib locates archives next to the executable, so `--run` links without depending on /usr/local/lib. - `.vscodeignore` explicitly ships bin/** so the binaries reach the VSIX. Verified: bundled osprey runs list_basics.osp from a neutral cwd and byte-matches the golden output (exit 0).
…sholds
The previous commit accidentally swept in an uncommitted per-crate rewrite
of _coverage_check_rust while its matching per-crate coverage-thresholds.json
was never committed. CI then ran per-crate logic against the single-key
`crates: 90` config, hunting for a crate literally named `crates`, finding no
lcov lines, and failing ("crates FAIL: no lines found in lcov.info").
Revert _coverage_check_rust to the aggregate gate that matches the committed
config (crates >= 90; measured 92.9%). The VSIX runtime-archive bundling from
the prior commit is unaffected. Per-crate gating can land later as its own
change, with the JSON and the raised per-crate coverage committed together.
The vscode-extension coverage gate (89%) regressed to 85.67% after the resolveServerCommand hardening (looksLikePath + missing-path warn-fallback) added branches that the committed tests never exercised. - extension.test.ts: add unit tests for the new branches — missing configured path warns and falls back (the hover-killing ENOENT regression), bare PATH command returned verbatim, existing path returned verbatim, looksLikePath true/false; plus a Committed Dev Settings Sanity suite that locks .vscode/settings.json off the dead compiler/bin/osprey path. - .vscode/settings.json: point osprey.server.compilerPath at the make-build output (target/release/osprey) instead of compiler/bin/osprey, which is the C-runtime archive dir and never holds the compiler binary. Required by the sanity suite above.
vscode-extension line coverage sat at 87.43% (< 89%): the language client's initialization-failed and error/closed handlers, plus deactivate(), were never exercised — they fire only on real LSP transport failures or shutdown, which the integration suites can't deterministically induce. Extract the three failure callbacks into the exported, injectable makeClientFailureHandling (activate() spreads it into clientOptions — behaviour unchanged) and unit-test each: init-failed logs+errors+returns false, error Continues, closed Restarts. Add a deactivate test (runs last; stops the live client started by earlier suites). Deterministic coverage of the failure paths, no reliance on inducing a flaky LSP crash.
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
Lands list patterns (
[]/[a, b]/[head, ...tail]/[a, b, ...rest]) inmatch— the last unshipped production-app primitive — and fixes the dev Language Server, which never launched because its configured compiler path pointed at the C-runtime archive directory instead of the built binary (so hover and every LSP feature were dead).Details
Feature — list patterns ([TYPE-LIST-PATTERNS])
list_patternrule; scalar literals are split out of$.literalso[head, tail]can never collide with a two-element list literal. The index[is nowtoken.immediate, so a match-arm body (=> 0) no longer swallows the next arm's[...]as a postfix index.Pattern::List { elements, rest }.osprey-syntax), HM inference (osprey-types: unifies the scrutinee withList<E>, binds each element toEand...resttoList<E>), free-variable analysis (osprey-codegen/freevars), and match codegen (osprey-codegen/pattern: length-guard==/>=, thenosprey_list_get/osprey_list_drop). Built over the pre-existingosprey_list_dropruntime.lists/list_basics.ospgains a section covering all four forms + a recursivesumList(Result-unwrapping the overflow-checked+). Twofailscompilationcases reject mid-list rest ([a, ...m, b]) and double rest ([...a, ...b]).Fix — dev Language Server never launched (hover/def/refs/etc. all dead)
.vscode/settings.jsonsetosprey.server.compilerPathto…/compiler/bin/osprey.compiler/binis the gitignored C-runtime archive directory (lib*_runtime.a), not a binary — so the clientspawned a nonexistent file and died withENOENT, leaving the LSP instartFailed. The compiler thatmake buildproduces istarget/release/osprey. Repointed the setting there and corrected the explanatory comment block (//4–//8, now notingcompiler/binis the C-runtime archive dir, NOT the binary).vscode-extension/client/src/extension.ts): newlooksLikePath();resolveServerCommand()now detects a configured path that looks like a path but doesn't exist on disk, emits ashowWarningMessage+ output-channel warning, and falls back to the bundled compiler then bareospreyon PATH instead of silently spawning a dead path.makeClientFailureHandling()centralises the client'sinitializationFailedHandler/errorHandlerso a failed start is logged and surfaced rather than swallowed.Fix — editor Run/Compile ran a stale compiler; VSIX now self-contained
execFile'd a bareosprey, which resolved to whatever stale global build sat onPATH(an old ANTLR-era binary that rejects[head, ...tail]withno viable alternative at input). Both now route throughresolveServerCommand(context), so Run/Compile use the same version-matched compiler as the LSP.make _vsix_bundlenow stagestarget/release/ospreyplus thelibfiber_runtime.a/libhttp_runtime.aC-runtime archives intobin/<platform>/, and.vscodeignorere-includes!bin/**.find_runtime_libsearches for the archives beside the executable, so the bundled compiler can link--runfrom inside the installed extension — proven end-to-end by running the bundled~/.vscode/extensions/nimblesite.osprey-*/bin/darwin-arm64/ospreyfrom a neutral cwd against the golden output.make rebuild-install-vsixrebuilds all binaries (make build→ C archives + release compiler + extension compile) before packaging.Coverage & tests
coverage-thresholds.json(measured 89.92%, 375/417 lines). The earlier toothless integration tests (if (result) assert; else log) passed even with hover broken — exactly how this bug shipped — so they were replaced with assertion-dense live-LSP tests run against the real built compiler.osprey-ast96,osprey-cli95,osprey-codegen95,osprey-lsp97,osprey-syntax95,osprey-types98,osprey-runtime-sys99) —make _coverage_check_rustenforces every crate individually, backed by new unit tests acrossosprey-codegen,osprey-syntaxandosprey-types.Cleanup —
docs/plans/removed (completed/superseded design docs, recoverable from git history); the four spec links that pointed at it were repointed.How Do The Automated Tests Prove It Works?
List patterns
zsh crates/diff_examples.sh): PASS=47 FAIL=0 NOEXP=0, FC_OK.list_basics.ospbyte-matches its.expectedoutput—classifyover[]/[10,20]/[1,2,3]/[7]yieldsempty/two(10,20)/many head=1 rest=2/one(7);sumListyields6and55;restLenyields1/0.list_pattern_middle_restandlist_pattern_double_restare rejected (nonzero exit).examples_compile::every_tested_example_compiles_to_irand::list_pattern_negative_cases_are_rejected(osprey-cli).freevars::list_pattern_binds_prefix_and_rest_not_freeproveshead/tail/restare bound, not captured by an enclosing closure.cargo test --workspacegreen;make _coverage_check_rustpasses every per-crate gate (osprey-ast97.1,osprey-cli96.0,osprey-codegen95.6,osprey-lsp99.0,osprey-runtime-sys100,osprey-syntax95.8,osprey-types99.0).LSP + editor fix —
vscode-extensionsuite (43 passing locally and in CI under xvfb), exercising the real language server against the freshly-builttarget/release/osprey:CHUNKY: full language-intelligence sweep over one program— over a single 10-line program asserts hover (fn area(r) -> Unitat decl and call site, builtinlistLength -> int/List,print -> Unit), go-to-definition (area→line 1,perimeter→line 2), references (area3× on lines [1,4,5],perimeter2× on [2,6]), 10 document symbols with correct kinds (Shape=Class,area/perimeter=Function,radius=Variable), signature help (1 param, activeParameter 0), and completion (declared names +fn/let/match/type).CHUNKY: diagnostics lifecycle— broken source surfaces anErrordiagnostic (sourceosprey,/syntax/imessage, range), editing to valid clears it and hover returns, re-breaking re-raises it.CHUNKY: list-pattern programandCHUNKY: hover + symbols work on the ACTUAL reported list_basics.osp file— hover/def/refs/symbols/completion over the exactclassifymatch in the file from the original bug report.resolveServerCommand falls back and warns when the configured path is missing,keeps a bare command name without touching the filesystem,an existing configured path is still returned verbatim and never warns,looksLikePath distinguishes filesystem paths from bare commands.initializationFailedHandler logs, surfaces an error, and stops retrying;errorHandler.error/.closed;deactivate stops the running language client without throwing.Committed Dev Settings Sanity): reads the committed.vscode/settings.jsonand assertscompilerPathis never the deadcompiler/bin/ospreypath, and that a checkout-localcompilerPathresolves to a real, built binary ending intarget/release/osprey.CI — all 5 checks green on the head commit: Rust Compiler (fmt, clippy, test, differential), Test, Format, Build & Validate (lint +
make testper-crate coverage + differential + vscode tests + build), Website E2E (Playwright), Windows Core Build & Smoke Test, Detect changed areas.