Skip to content

feat: LSP language features over the obligation core (#222 Phase D)#719

Merged
aallan merged 4 commits into
mainfrom
feat/222-phase-d-lsp-features
Jun 10, 2026
Merged

feat: LSP language features over the obligation core (#222 Phase D)#719
aallan merged 4 commits into
mainfrom
feat/222-phase-d-lsp-features

Conversation

@aallan

@aallan aallan commented Jun 10, 2026

Copy link
Copy Markdown
Owner

Summary

Phase D of the #222 plan: language features over the obligation core. vera lsp now serves the four standard features the issue body asked for, each a pure backing function in vera/lsp/features.py over one Analysis bundle (parse → typecheck-with-artifacts → warm incremental verify), with all verification serialised through the server's single VerificationSession under a lock (Z3 contexts are not thread-safe). This PR does not finish #222 — Phase E (vera/speculativeEdit) follows; the issue stays open.

Features

Feature Backing
publishDiagnostics (didOpen/didChange) parse/transform errors short-circuit to their single diagnostic (matching the CLI); type-check + verification diagnostics; per-function tier Hints synthesised from the obligation stream — "Tier 1 — all contracts proven by Z3" / "Tier 3 — N of M obligations fall back to runtime checks", suppressed for functions with violated obligations (decision R3: the verifier stays silent about successes; the Hint is LSP-layer presentation). didClose clears squiggles.
Hover type of the smallest recorded expression span under the cursor, from the new checker side-table (decision R4).
Definition @T.n → the parameter it names via slots.slot_table (De Bruijn most-recent-first — @Nat.0 jumps to the second @Nat parameter). References binding through let/match return None: signature-level scope by design, full binding resolution belongs to #181's refactoring engine.
Completion at a typed hole (or immediately after it): the in-scope bindings with their types, innermost first; ? registered as a trigger character.

Compiler-side plumbing (both opt-in / additive)

  • Checker artifacts (typecheck_with_artifacts): a Span → pretty-type side-table recorded by a thin _synth_expr wrapper over the renamed _synth_expr_impl dispatcher (every synthesis, including recursion, flows through it; the # WALKER_COVERAGE marker travels with the impl — check_walker_coverage.py verified green), plus structured HoleSite records via the binding-collection now factored out of _check_hole into _collect_scope_bindings() (shared by the W001 fix hint and the completion payload). Zero cost for existing callers — collection is off unless the artifact entry point is used.
  • Tier field: Diagnostic.tier: int | None (in to_dict() only when set — schema-additive for --json consumers); the verifier's six Tier-3 fallback warnings (E520–E525) carry tier=3, and the LSP diagnostic surfaces it in data.

Verification

  • tests/test_lsp.py 30 → 44: parse-error path, type-error verification short-circuit, tier=3 in E520 diagnostic data, tier-Hint synthesis + violated-suppression, hover (smallest span / off-expression / parse-error), definition (most-recent-parameter jump pinned, out-of-range None, off-slot None), hole completion (inside / immediately-after / away).
  • Behavior preservation: 978 adjacent-suite tests green unchanged (obligations + verifier + checker + errors); the differential oracle and walker-coverage gates both pass; mypy vera/ strict clean (68 files); ruff S clean; full non-stress suite green via pre-commit on both commits; doc counts consistent (4,264 tests).

Release prep (in this PR per workflow)

v0.0.164 across all tracked sites, CHANGELOG cut, HISTORY.md single-sentence row, site assets, tag on branch.

Part of #222 (Phase D of the phased plan — Phase E follows in its own PR).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • LSP: hover/type info, go-to-definition for named parameters, typed‑hole completion with in‑scope bindings, tiered diagnostics (optional tier metadata and per‑function tier hints), JSON diagnostic tier surfacing, and opt‑in artifact collection for richer IDE features; server now publishes diagnostics and responds to hover/definition/completion requests.
  • Tests

    • Added Phase D coverage for diagnostics, hover, definition resolution and hole completion.
  • Documentation

    • Updated changelog/HISTORY/README/ROADMAP/TESTING and bumped project version to v0.0.164.

aallan and others added 2 commits June 10, 2026 21:30
vera lsp now serves the four standard features, all computed by pure
backing functions in vera/lsp/features.py over one Analysis bundle
(parse -> typecheck-with-artifacts -> warm incremental verify), with
all verification serialised through the server's single
VerificationSession under a lock (Z3 contexts are not thread-safe).

- publishDiagnostics on didOpen/didChange: parse/transform errors
  (single-diagnostic short-circuit, matching the CLI), type-check
  diagnostics, verification diagnostics, plus a synthesised
  per-function verification-tier Hint computed from the obligation
  stream (decision R3: the verifier stays silent about successes; the
  Hint is LSP-layer presentation — 'fn: Tier 1 — all contracts proven
  by Z3' / 'Tier 3 — N of M obligations fall back to runtime checks';
  suppressed for functions with violated obligations). didClose
  clears squiggles.
- Hover: type of the smallest recorded expression span under the
  cursor, from the new checker side-table.
- Definition: @T.n -> the parameter it names via slots.slot_table
  (De Bruijn most-recent-first); indices binding through let/match
  return None (documented signature-level scope; #181 owns full
  binding resolution).
- Completion: at a typed hole (or immediately after it), the in-scope
  bindings with their types, innermost first; '?' added as a trigger
  character.

Checker (#222 Phase D artifacts, decision R4 — opt-in, zero cost for
existing callers):
- typecheck_with_artifacts() returns (diagnostics, CheckArtifacts)
  with a Span->pretty-type side-table and structured HoleSite records.
- _synth_expr is now a thin recording wrapper over _synth_expr_impl
  (the dispatcher; walker-coverage marker travels with it — checker
  verified green), so every synthesis including recursion is recorded.
- _check_hole's binding collection factored into
  _collect_scope_bindings(), shared by the W001 fix hint and the LSP
  completion payload.

Tier plumbing:
- Diagnostic gains tier: int | None (surfaced in to_dict() only when
  set — schema-additive); the verifier's six Tier-3 fallback warnings
  (E520-E525) now carry tier=3; the LSP diagnostic puts it in data.

Tests (tests/test_lsp.py 30 -> 44): parse-error path, type-error
short-circuit, tier in diagnostic data, tier-Hint synthesis +
violated-suppression, hover (smallest span / off-expression / parse
error), definition (most-recent-parameter jump, out-of-range None,
off-slot None), hole completion (inside/after/away).

Validation: mypy strict clean (68 files), ruff S clean, 978 adjacent
suite tests green (obligations + verifier + checker + errors), doc
counts consistent (4,264 tests).

Part of #222 (Phase D). Release prep follows before the PR.

Co-Authored-By: Claude <noreply@anthropic.invalid>
Version bump across all tracked sites, CHANGELOG cut, HISTORY row,
footer count, site assets.

Skip-changelog: the CHANGELOG cut for 0.0.164 IS this commit's changelog change.

Co-Authored-By: Claude <noreply@anthropic.invalid>
@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 58e2ec09-5d57-471d-851f-0b5c31c38d23

📥 Commits

Reviewing files that changed from the base of the PR and between be7f51e and db3d917.

📒 Files selected for processing (1)
  • tests/test_lsp.py

📝 Walkthrough

Walkthrough

Version 0.0.164 ships Phase D LSP language features: diagnostic tiering, on-demand expression-type and hole-site artifact collection during type checking, and complete LSP handlers (hover, go-to-definition for slot references, typed-hole completion) integrated into a thread-safe server with per-URI analysis caching.

Changes

Phase D LSP Language Features

Layer / File(s) Summary
Version bump and documentation updates
CHANGELOG.md, HISTORY.md, README.md, ROADMAP.md, TESTING.md, pyproject.toml, vera/__init__.py
Version incremented from 0.0.163 to 0.0.164; release notes document Phase D features; project status metrics and test counts updated across docs.
Diagnostic tiering infrastructure
vera/errors.py, vera/verifier.py
Diagnostic gains optional tier: int | None; to_dict() emits tier when present. ContractVerifier._warning() accepts tier and call sites emitting Tier‑3 fallback warnings now pass tier=3.
Type-checking artifact collection
vera/checker/core.py, vera/checker/expressions.py
Add HoleSite and CheckArtifacts dataclasses and typecheck_with_artifacts() entrypoint that initialises expr_types and hole_sites. ExpressionsMixin._synth_expr records span→type mappings; new _collect_scope_bindings() extracts in-scope bindings for hole-site completion and fix hints; _check_hole records HoleSite entries when enabled.
LSP language feature handlers
vera/lsp/features.py
New Analysis dataclass and analyze() pipeline: parse→transform→typecheck-with-artifacts→conditional verification. to_lsp_diagnostics() maps diagnostics and synthesises per-function tier-hint Hints from obligations. hover_at() returns expression type from artifacts; definition_at() resolves @T.n slot references to parameter locations; completion_at() returns typed-hole completion items from captured hole sites.
LSP server integration
vera/lsp/server.py
VeraLanguageServer adds VerificationSession, analysis_lock, and per-URI analyses cache. analyze_and_publish() runs analysis under lock, caches result, and publishes diagnostics. DID_OPEN/DID_CHANGE/DID_CLOSE lifecycle updated to run/clear analyses; hover/definition/completion handlers delegate to feature functions using the cached Analysis.
Phase D test coverage
tests/test_lsp.py
New Phase D tests: TestAnalyzeDiagnostics (diagnostic mapping, type-error short-circuit, tier metadata, synthesized tier hints, suppression on violated obligations), TestHover, TestDefinition, and TestHoleCompletion (typed-hole completions, where-scoped resolution).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • aallan/vera#436: Typed-hole completion and W001 fix-hint machinery reuse/refactor that records hole-site context for completion.
  • aallan/vera#718: Phase C LSP stdio/document-sync skeleton; Phase D replaces its transport skeleton with in-process analysis and handlers.
  • aallan/vera#716: Phase A warm VerificationSession and proof-obligation reification used by tier-hint synthesis.

Suggested labels

compiler, tests, docs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 48.89% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding LSP language features (hover, definition, completion, diagnostics) to the obligation core in Phase D of issue #222.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/222-phase-d-lsp-features

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov

codecov Bot commented Jun 10, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 92.03980% with 16 lines in your changes missing coverage. Please review.
✅ Project coverage is 90.96%. Comparing base (0509ec5) to head (db3d917).

Files with missing lines Patch % Lines
vera/lsp/server.py 64.70% 12 Missing ⚠️
vera/lsp/features.py 96.85% 4 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff            @@
##             main     #719    +/-   ##
========================================
  Coverage   90.95%   90.96%            
========================================
  Files          67       68     +1     
  Lines       24434    24630   +196     
  Branches      292      292            
========================================
+ Hits        22225    22404   +179     
- Misses       2202     2219    +17     
  Partials        7        7            
Flag Coverage Δ
javascript 61.40% <ø> (ø)
python 94.64% <92.03%> (-0.03%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@vera/lsp/features.py`:
- Around line 218-240: definition_at() currently uses the outer `decl` as
`enclosing`, causing slots inside nested `where`/inner functions to resolve
against the top-level function's params; update the logic so when you find an
`ast.SlotRef` in the `walk_nodes(decl)` loop you set `enclosing` to the
innermost `ast.FnDecl` that actually contains that node (e.g., by walking the
node's parent chain or using a helper like `find_enclosing_fn(node)`), then
break out—ensure you still break both loops only after assigning the correct
`enclosing` and keep using `slot_table(enclosing.params)` and
`positions[slot.index]` as before.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: df90fba5-f7f4-4391-8dc0-209844f545fd

📥 Commits

Reviewing files that changed from the base of the PR and between 0509ec5 and 3ebb770.

⛔ Files ignored due to path filters (5)
  • docs/index.html is excluded by !docs/**
  • docs/index.md is excluded by !docs/**
  • docs/llms-full.txt is excluded by !docs/**
  • docs/llms.txt is excluded by !docs/**
  • uv.lock is excluded by !**/*.lock, !uv.lock
📒 Files selected for processing (14)
  • CHANGELOG.md
  • HISTORY.md
  • README.md
  • ROADMAP.md
  • TESTING.md
  • pyproject.toml
  • tests/test_lsp.py
  • vera/__init__.py
  • vera/checker/core.py
  • vera/checker/expressions.py
  • vera/errors.py
  • vera/lsp/features.py
  • vera/lsp/server.py
  • vera/verifier.py

Comment thread vera/lsp/features.py
…und 1)

CodeRabbit round 1 (valid, Major): definition_at bound 'enclosing' to
the first top-level FnDecl whose subtree contained the cursor, so a
slot inside a where-block function jumped to the OUTER function's
parameter list. Now the innermost FnDecl containing the cursor (by
smallest span) wins, and the SlotRef is located within it. New test
pins a where-block helper: the slot jumps to helper's signature line,
not outer's.

Co-Authored-By: Claude <noreply@anthropic.invalid>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
README.md (1)

211-213: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add the Phase D LSP capabilities to the v0.0.164 feature summary.

The project-status feature list for this release omits the shipped LSP-facing items (tiered diagnostics, hover, @T.n go-to-definition, typed-hole completion), so the README is incomplete for the externally visible Phase D scope.

As per coding guidelines, README v0.0.164 should keep the top-line LSP/agent-facing feature list aligned with Phase D deliverables.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` around lines 211 - 213, Update the v0.0.164 feature summary in
README.md to include the Phase D LSP/agent-facing capabilities that were
shipped: add "tiered diagnostics", "hover", "`@T.n` go-to-definition", and
"typed-hole completion" (or similarly phrased LSP features) to the top-line
feature list alongside the existing items (e.g., the line listing typed De
Bruijn indices, algebraic effects, built-ins, etc.) so the externally visible
release notes accurately reflect the Phase D deliverables.

Source: Coding guidelines

vera/lsp/features.py (1)

93-96: ⚠️ Potential issue | 🟠 Major

Fix LSP diagnostic loss for import-resolution failures

vera/lsp/features.py::analyze appends only result.verify_diagnostics from session.verify_source(...). However, VerificationSession.verify_source places import-resolution/typecheck errors into result.check_diagnostics and returns early with ok=False when any error-severity diagnostics exist—so resolver errors (e.g. “Cannot resolve import …”) can be silently dropped (common when an unused import points at a missing module and typecheck_with_artifacts only emits warnings). Fix by also appending result.check_diagnostics (or result.diagnostics) when publishing LSP diagnostics.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@vera/lsp/features.py` around lines 93 - 96, The analyze logic currently only
appends result.verify_diagnostics from session.verify_source, which can drop
import-resolution/typecheck errors placed in result.check_diagnostics; update
the code in vera/lsp/features.py inside analyze (the block using check_diags and
calling session.verify_source) to also append result.check_diagnostics (or
simply result.diagnostics) to analysis.diagnostics so resolver/typecheck errors
are published, i.e. extend analysis.diagnostics with both verify and check
diagnostics returned by session.verify_source.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/test_lsp.py`:
- Around line 444-468: Move the test function
test_slot_in_where_block_resolves_to_inner_params out of the TestHoleCompletion
class and into the TestDefinition class (after the existing
test_position_not_on_slot_is_none); this test exercises definition_at resolution
(not completion_at), so cut the whole def
test_slot_in_where_block_resolves_to_inner_params(...) and paste it into
TestDefinition to keep tests grouped by the symbol under test.

---

Outside diff comments:
In `@README.md`:
- Around line 211-213: Update the v0.0.164 feature summary in README.md to
include the Phase D LSP/agent-facing capabilities that were shipped: add "tiered
diagnostics", "hover", "`@T.n` go-to-definition", and "typed-hole completion"
(or similarly phrased LSP features) to the top-line feature list alongside the
existing items (e.g., the line listing typed De Bruijn indices, algebraic
effects, built-ins, etc.) so the externally visible release notes accurately
reflect the Phase D deliverables.

In `@vera/lsp/features.py`:
- Around line 93-96: The analyze logic currently only appends
result.verify_diagnostics from session.verify_source, which can drop
import-resolution/typecheck errors placed in result.check_diagnostics; update
the code in vera/lsp/features.py inside analyze (the block using check_diags and
calling session.verify_source) to also append result.check_diagnostics (or
simply result.diagnostics) to analysis.diagnostics so resolver/typecheck errors
are published, i.e. extend analysis.diagnostics with both verify and check
diagnostics returned by session.verify_source.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 22d6fb59-4ace-4192-9b81-8b930a446f0b

📥 Commits

Reviewing files that changed from the base of the PR and between 3ebb770 and be7f51e.

📒 Files selected for processing (5)
  • README.md
  • ROADMAP.md
  • TESTING.md
  • tests/test_lsp.py
  • vera/lsp/features.py

Comment thread tests/test_lsp.py
CodeRabbit round 2 (valid): the test was appended at the file tail,
which placed it inside TestHoleCompletion. It validates definition_at
and now sits with the other definition tests. No logic changes.

Co-Authored-By: Claude <noreply@anthropic.invalid>
@aallan aallan merged commit 5324e8c into main Jun 10, 2026
28 checks passed
@aallan aallan deleted the feat/222-phase-d-lsp-features branch June 10, 2026 23:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant