Skip to content

fix: E501 messages render the precondition in call-site terms#731

Merged
aallan merged 3 commits into
mainfrom
fix/e501-concrete-fix-text
Jun 12, 2026
Merged

fix: E501 messages render the precondition in call-site terms#731
aallan merged 3 commits into
mainfrom
fix/e501-concrete-fix-text

Conversation

@aallan

@aallan aallan commented Jun 12, 2026

Copy link
Copy Markdown
Owner

Summary

Follow-up to the PR that closed issues 727/728, prompted by review of the shipped hover: the E501 message was accurate but generic. It now speaks in call-site terms.

Description gains an At this call site: line — the callee's precondition rendered with the actual arguments substituted for its parameter slots:

Call to 'classify_sentiment' in function 'main' may violate the callee's precondition.
  Precondition: requires(string_length(@String.0) > 0)
  At this call site: string_length("") > 0
  Counterexample:
      @result = 0

Fix: becomes concrete code instead of the generic two-option advice:

Guard the call so the precondition holds, e.g. if string_length("") > 0 then
{ classify_sentiment("") } else { ... } — or strengthen 'main' with
requires(string_length("") > 0).

Mechanics

  • Substitution resolves callee parameter slots through slot_table's De Bruijn most-recent-first logic and rebuilds the precondition AST with the call's argument expressions (generic frozen-dataclass rebuild), rendered via format_expr.
  • Module-qualified callees and unmappable slots (unknown type, index out of range, arity mismatch) keep the generic wording — the helper returns None and nothing guesses.
  • The LSP hover inherits everything through the instruction-contract plumbing shipped for issue 728.

Tests (3 new; 4,321 total)

The substituted rendering pinned exactly (string_length("") > 0 plus the guard and requires in the fix), the De Bruijn shuffle (requires(@Int.1 > @Int.0) at cmp(1, 2) renders 1 > 2 — parameter 1 then parameter 2), the unmappable-slot fallback returning None, and the LSP instruction-contract assertions upgraded to pin the concrete content.

Release handling — folds into v0.0.170

No version bump: the bullet is added to the existing CHANGELOG [0.0.170] section (the changelog gate accepts the amendment — verified by pre-commit). On merge, the v0.0.170 tag moves to the merge commit and the GitHub release notes get refreshed from the section.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • E501 diagnostics now include call-site-specific preconditions with actual-argument substitution and concrete guard suggestions.
  • Tests

    • Added and strengthened tests covering E501 call-site messages and slot-substitution behaviour (project total now 4,322 tests).
  • Documentation

    • Updated CHANGELOG, README, ROADMAP and TESTING to reflect refreshed test counts and CI/test corpus metrics.
  • Chores

    • Improved changelog-check to accept certain recent-release bullet additions as valid new-entry evidence.

The description gains an 'At this call site:' line with the callee's
precondition rendered against the actual arguments (callee parameter
slots substituted via slot_table's De Bruijn resolution, formatted
with format_expr), and the Fix: text becomes concrete code — the
guard with the rendered call and the exact requires(...) to add —
instead of the generic two-option advice. Module-qualified callees
and unmappable slots keep the generic wording.

Tests pin the substituted rendering (string_length("") > 0), the
De Bruijn shuffle (@Int.1 > @Int.0 at cmp(1, 2) renders 1 > 2), the
unmappable-slot fallback, and the upgraded LSP instruction-contract
assertions. 4,321 tests total.

Folds into the v0.0.170 release (no version bump; the existing
CHANGELOG [0.0.170] section gains the bullet; the tag moves to the
merge commit on merge).

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

coderabbitai Bot commented Jun 12, 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: 08a3805d-170d-4d8b-85f3-86cbeb8a53c0

📥 Commits

Reviewing files that changed from the base of the PR and between 5f17514 and f70b9c1.

📒 Files selected for processing (2)
  • scripts/check_changelog_updated.py
  • tests/test_lsp.py

📝 Walkthrough

Walkthrough

E501 precondition-violation diagnostics now render call-site-specific preconditions by substituting callee parameter slots with actual argument expressions; diagnostics include concrete guard code and strengthened requires(...) suggestions. A helper performs substitution; tests and changelog/doc metrics updated.

Changes

E501 call-site precondition rendering

Layer / File(s) Summary
Precondition substitution helper
vera/verifier.py
Adds slot_table import and implements _pre_at_call_site(...) to rewrite callee requires(...) expressions by substituting parameter slot references with caller argument expressions; returns formatted string or None when substitution fails.
Call-violation diagnostic integration
vera/verifier.py
_report_call_violation computes optional site_pre from the helper for resolved callees with parameter types. When available, diagnostic description appends "At this call site:" rendering and fix suggestion is tailored to the specific precondition; otherwise falls back to generic advice.
Test coverage for substitution and edge cases
tests/test_lsp.py, tests/test_obligations.py
test_lsp.py updates existing assertion to check concrete guard code and substituted precondition. test_obligations.py adds three unit tests covering call-site substitution with argument values, De Bruijn slot ordering for multi-parameter callees, and the case where _pre_at_call_site returns None for unmappable slots.
Changelog and test metrics
CHANGELOG.md, README.md, ROADMAP.md, TESTING.md
Changelog entry documents E501 call-site-specific precondition rendering and concrete fix guidance. Test count incremented from 4,318 to 4,322 across version/status documents and per-module test tables.
Changelog diff parser and tests
scripts/check_changelog_updated.py, tests/test_check_changelog_updated.py
Extend _changelog_has_new_entry to treat added bullets under the latest released section as valid new-entry evidence; add tests for older-release vs latest-release bullet cases and capture latest_release while scanning diffs.

Sequence Diagram

sequenceDiagram
  participant Caller
  participant Verifier as _report_call_violation
  participant PreHelper as _pre_at_call_site
  participant SlotTable as slot_table
  participant Diagnostic

  Caller->>Verifier: call node + callee + args -> violation detected
  Verifier->>PreHelper: resolve callee (non-ModuleCall), pass callee.requires and call node
  PreHelper->>SlotTable: map callee parameter slots -> caller argument expressions
  alt Substitution succeeds
    SlotTable-->>PreHelper: substituted expression
    PreHelper-->>Verifier: site_pre string
    Verifier->>Diagnostic: append "At this call site:" + rendered precondition
    Verifier->>Diagnostic: set E501 fix to concrete guard or requires(site_pre)
  else Substitution fails
    SlotTable-->>PreHelper: None
    PreHelper-->>Verifier: site_pre = None
    Verifier->>Diagnostic: keep generic fix wording
  end
  Diagnostic-->>Caller: diagnostic payload (description + fix)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • aallan/vera#729: Introduced LSP hover diagnostics carrying rationale and Fix; related to E501 fix expectations and LSP formatting.
  • aallan/vera#482: Also modified scripts/check_changelog_updated.py logic and tests around interpreting git-diff context for CHANGELOG bullets.

Suggested labels

compiler, tests, ci, docs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and precisely summarises the primary change: E501 diagnostic messages now render the callee's precondition with call-site arguments substituted for parameter slots.
Docstring Coverage ✅ Passed Docstring coverage is 86.67% which is sufficient. The required threshold is 80.00%.
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 fix/e501-concrete-fix-text

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

@codecov

codecov Bot commented Jun 12, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.56098% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 90.95%. Comparing base (2a7238b) to head (f70b9c1).

Files with missing lines Patch % Lines
vera/verifier.py 97.56% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #731      +/-   ##
==========================================
+ Coverage   90.94%   90.95%   +0.01%     
==========================================
  Files          70       70              
  Lines       24861    24902      +41     
  Branches      292      292              
==========================================
+ Hits        22609    22649      +40     
- Misses       2245     2246       +1     
  Partials        7        7              
Flag Coverage Δ
javascript 61.40% <ø> (ø)
python 94.58% <97.56%> (+<0.01%) ⬆️

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: 2

🤖 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 1184-1189: Add an assertion that the diagnostic description
includes the literal "At this call site:" so the rendered call-site line is
explicitly checked; in the test (tests/test_lsp.py) augment the existing
assertions on the message variable to include something like assert "At this
call site:" in message so the description-side clause cannot regress (keep
existing asserts for the guard and requires text).

In `@vera/verifier.py`:
- Around line 1594-1607: The code currently applies call-site substitution
unconditionally after resolving callee_info via
self.env.lookup_function(callee_name), which wrongly substitutes
module-qualified calls; update the logic in the block that computes site_pre so
that you only call _pre_at_call_site when call_node is not an instance of
ast.ModuleCall (i.e., add an explicit guard checking call_node against
ast.ModuleCall before invoking _pre_at_call_site with
callee_info.param_type_exprs, preserving the generic fallback wording for
module-qualified callees).
🪄 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: fcaa23f2-180e-4bad-b525-1ddcc1a445e3

📥 Commits

Reviewing files that changed from the base of the PR and between 2a7238b and 5b06392.

📒 Files selected for processing (7)
  • CHANGELOG.md
  • README.md
  • ROADMAP.md
  • TESTING.md
  • tests/test_lsp.py
  • tests/test_obligations.py
  • vera/verifier.py

Comment thread tests/test_lsp.py
Comment thread vera/verifier.py
…-in (#731 round 1)

The substitution now short-circuits for module-qualified callees
before the bare-name lookup (a local function sharing the callee's
name would supply the wrong parameter table) — enforcing the generic
fallback the docstring already promised. The LSP instruction-contract
test pins the 'At this call site:' description line. The changelog
gate's own test suite is reworked for the fold-in rule: the
frozen-history pin moves to an older-than-latest section (still
rejected) and the latest-section fold-in case gains its own
accepting pin.

Skip-changelog: amends behaviour shipped in this PR's own fold-in bullet

@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

♻️ Duplicate comments (1)
tests/test_lsp.py (1)

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

Pin the concrete description payload, not just the label.

At Line 1183, asserting only "At this call site:" still permits regressions where the rendered expression is missing or altered in the description. Please assert the full expected description fragment (e.g. "At this call site: 0 > 0"), so call-site substitution in the description is genuinely locked.

Suggested test tightening
-        assert "At this call site:" in message
+        assert "At this call site: 0 > 0" in message

As per coding guidelines, diagnostics must preserve full instruction-contract detail across surfaces, including concrete call-site rationale in human-readable output.

🤖 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 `@tests/test_lsp.py` at line 1183, The test currently only asserts that "At
this call site:" appears in the diagnostic message, allowing the concrete
substitution to regress; update the assertion for the variable/message used in
tests/test_lsp.py (the assertion around message at Line 1183) to assert the full
expected description fragment (for example "At this call site: 0 > 0") so the
rendered expression is pinned rather than just the label.

Source: Coding guidelines

🤖 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 `@scripts/check_changelog_updated.py`:
- Around line 175-178: The docstring currently claims that a bare "+##
[Unreleased]" heading counts as a new entry but the implementation explicitly
rejects a bare Unreleased heading and only treats Unreleased as a new entry when
there is actual content beneath it; update the docstring near the "A \"new
entry\" is any of:" section to state that "+## [Unreleased]" only counts if it
contains content (i.e., lines under the Unreleased heading), matching the logic
in the script that ignores an empty Unreleased heading.

---

Duplicate comments:
In `@tests/test_lsp.py`:
- Line 1183: The test currently only asserts that "At this call site:" appears
in the diagnostic message, allowing the concrete substitution to regress; update
the assertion for the variable/message used in tests/test_lsp.py (the assertion
around message at Line 1183) to assert the full expected description fragment
(for example "At this call site: 0 > 0") so the rendered expression is pinned
rather than just the label.
🪄 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: c3894529-79c3-411f-9783-4ca1216b28b5

📥 Commits

Reviewing files that changed from the base of the PR and between 5b06392 and 5f17514.

📒 Files selected for processing (7)
  • README.md
  • ROADMAP.md
  • TESTING.md
  • scripts/check_changelog_updated.py
  • tests/test_check_changelog_updated.py
  • tests/test_lsp.py
  • vera/verifier.py

Comment thread scripts/check_changelog_updated.py Outdated
… rendered substitution at the LSP layer (#731 round 2)

The docstring's claim that an added bare [Unreleased] heading counts
was a pre-existing inaccuracy (the implementation has always required
content beneath it); item 1 now describes versioned headings only.
The LSP instruction-contract test pins 'At this call site: 0 > 0' —
the rendered expression, not just the label.

Skip-changelog: docstring + test-assertion amendments within this PR
@aallan aallan merged commit b1f2831 into main Jun 12, 2026
26 checks passed
@aallan aallan deleted the fix/e501-concrete-fix-text branch June 12, 2026 13:18
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