Skip to content

[codex] Ship LSP forward propagation floor#491

Merged
TSavo merged 10 commits into
mainfrom
codex/main-20260508-104659
May 8, 2026
Merged

[codex] Ship LSP forward propagation floor#491
TSavo merged 10 commits into
mainfrom
codex/main-20260508-104659

Conversation

@TSavo
Copy link
Copy Markdown
Owner

@TSavo TSavo commented May 8, 2026

Summary

  • Adds the missing Rust, Go, and PHP floor fixtures under tests/lsp/floor-fixture/.
  • Wires Rust, Go, and PHP LSP parse responses to emit shared implication-failed diagnostics for the v1 forward-propagation floor.
  • Adds focused propagator tests for satisfied preconditions, failed preconditions, branch-merge partial satisfaction, and top-fallback suppression.
  • Adds checked-in CICP accepted witnesses for the stale rust, go, and ts blast radii on this PR closure.

Notes

Validation

  • cargo test -p provekit-lsp-rust
  • cargo check --workspace from implementations/rust (exit 0; existing unrelated warnings remain)
  • go test ./cmd/provekit-lsp-go/...
  • php -l implementations/php/provekit-lift/src/ForwardPropagator.php
  • php -l implementations/php/provekit-lift/src/lspd.php
  • php implementations/php/provekit-lift/tests/ForwardPropagatorTest.php
  • pnpm test
  • make prove-rust
  • make prove-go
  • make prove-ts
  • implementations/rust/target/release/provekit ci accept --repo . --all-kits --clean --check --json (existingCount: 11, missingCount: 0, verifiedCount: 11)
  • git diff --check
  • git diff --cached --check

make conformance was not run end-to-end in this pass.

Summary by CodeRabbit

  • New Features

    • LSP parse responses now include forward-propagation diagnostics that flag when callee preconditions are not met (Go, PHP, Rust).
  • Tests

    • Added unit and integration tests plus language fixtures exercising diagnostics across branches, loops, and lowering edge cases.
  • Chores

    • Added CI job result artifacts recording successful runs.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 8, 2026

Review Change Stack

Warning

Rate limit exceeded

@TSavo has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 24 minutes and 57 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0552b80d-ef03-469a-940e-b3fecc059e12

📥 Commits

Reviewing files that changed from the base of the PR and between 1d07299 and 8f9ed0e.

📒 Files selected for processing (7)
  • .provekit/ci/accepted/go/blake3-512:9352d9fd4c559ad1c56c1e297a2fb0f5ff3c2e40cf8617e4f9a365d57f041c792033e3f9ef576c0d4dfc44ae10991af3a11bca56061762333e66de157c67f48e.job-result.json
  • .provekit/ci/accepted/rust/blake3-512:abce0df5445214b71fbdc928af7b36e7519b51b82f2a8db80dbdca779cd94795fb3b5ef029ffb3a2bfbe2691192f908f0476843ad3ab4826d225c46b15327eaf.job-result.json
  • implementations/go/cmd/provekit-lsp-go/forward_propagator.go
  • implementations/go/cmd/provekit-lsp-go/forward_propagator_test.go
  • implementations/php/provekit-lift/src/ForwardPropagator.php
  • implementations/php/provekit-lift/tests/ForwardPropagatorTest.php
  • implementations/rust/provekit-lsp-rust/tests/forward_propagator.rs

Walkthrough

This PR adds cross-language forward-propagation analysis: constraint state, baseline entries with deterministic CIDs, source lowering (floor v1), diagnostic emission on callsite precondition failures, LSP wiring in Go/PHP/Rust servers, fixtures, unit and integration tests, and CI job-result artifacts.

Changes

Forward Propagation Analysis

Layer / File(s) Summary
Constraint & Statement Data Model
implementations/go/cmd/provekit-lsp-go/forward_propagator.go, implementations/php/provekit-lift/src/ForwardPropagator.php, implementations/rust/provekit-lsp-rust/src/forward_propagator.rs
Defines Post/ForwardPost constraint state (top vs known set), ForwardStmt IR (reset/assign/call/if_else/unsupported), and LSP range/position types.
Baseline & CIDs
implementations/*/forward_propagator*
BaselineEntry creation computes deterministic member/contract/attestation and pre/post identity CIDs; signer and baseline index/catalog fields populated.
Propagator & Diagnostics
implementations/*/forward_propagator*
ForwardPropagator indexes baselines by callee, checks callsite constraints against baseline preconditions, emits implication-failed LSP diagnostics with missing conjuncts and CID context, and updates propagation state on calls/branches.
Source Lowering (floor v1)
implementations/*/forward_propagator*, tests/lsp/floor-fixture/*
LowerFloorSource/lower_floor_source emits Reset at function headers, extracts checkPositive(...) calls with single-line ranges, infers assign posts from numeric literals, and marks loop-contained calls unsupported (top-fallback suppression).
LSP Handler Integration
implementations/go/cmd/provekit-lsp-go/main.go, implementations/php/provekit-lift/src/lspd.php, implementations/rust/provekit-lsp-rust/src/main.rs
parse handlers now lower source, run a floor-seeded ForwardPropagator, and include diagnostics in parse responses.
Unit Tests
implementations/go/.../forward_propagator_test.go, implementations/php/.../tests/ForwardPropagatorTest.php, implementations/rust/.../tests/forward_propagator.rs
Tests cover precondition satisfaction (no diagnostic), violation (implication-failed diagnostic with expected fields), branch-merge behavior, top-fallback suppression, multi-callee non-propagation, and lowering reset semantics.
Integration Tests & Fixtures
implementations/*/tests/*, tests/lsp/floor-fixture/go.go, tests/lsp/floor-fixture/php.php, tests/lsp/floor-fixture/rust.rs
Fixtures define checkPositive and callers (satisfy/violate/loop); integration tests spawn LSP plugins and assert parse responses include the expected implication-failed diagnostic.

CI Job Results

Layer / File(s) Summary
Job Result Documents
.provekit/ci/accepted/go/*, .provekit/ci/accepted/rust/*, .provekit/ci/accepted/ts/*
Add CI job-result JSON claims recording successful provekit/ci runs with content-addressed CIDs, timestamps, and producer metadata.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

Possibly related PRs

Poem

A rabbit hops through constraint forests bright,
Checking preconditions left and right,
When callsites stumble, diagnostics flow,
Three languages dance in implication's glow. 🐰✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.57% 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 '[codex] Ship LSP forward propagation floor' clearly and concisely describes the main objective of adding forward-propagation floor fixtures and LSP wiring for diagnostics across multiple languages.
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 unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/main-20260508-104659

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@TSavo TSavo force-pushed the codex/main-20260508-104659 branch from 5ad4bdd to c99bfe7 Compare May 8, 2026 18:12
@TSavo TSavo marked this pull request as ready for review May 8, 2026 18:39
Copilot AI review requested due to automatic review settings May 8, 2026 18:39
@TSavo
Copy link
Copy Markdown
Owner Author

TSavo commented May 8, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 8, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 65753f3823

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".


for (line_idx, line) in source.lines().enumerate() {
let trimmed = line.trim_start();
let is_function_definition = trimmed.starts_with("fn ");
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Reset state for qualified Rust function declarations

lower_floor_source only emits Stmt::Reset when a line starts with fn , but Rust functions often start with qualifiers such as pub fn, pub(crate) fn, or async fn. Those functions will be analyzed without a reset, so facts from a previous function can leak into the next one; for example, a prior checkPositive(5) can leave x > 0 in state and suppress the expected diagnostic on a later pub fn call to checkPositive(-1).

Useful? React with 👍 / 👎.


foreach (explode("\n", $source) as $lineIdx => $line) {
$trimmed = ltrim($line);
$isFunctionDefinition = preg_match('/^function\s+/', $trimmed) === 1;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Reset state when parsing PHP class method declarations

lowerFloorSource only treats lines matching ^function\s+ as function boundaries, so class methods declared as public/protected/private function ... are missed. This allows forward-propagation state to carry across methods, which can hide real precondition violations (e.g., a satisfied call in one method can make a violating call in a later method appear satisfied).

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (1)
.provekit/ci/accepted/ts/blake3-512:cf2632156ae353e1b6e263e8a5278476d15337d8fb7310297eccdce0476021db91086e63a869e08aeb3115ec2fa57d6f9b617377e7720b15493f1a2d61089f43.job-result.json (1)

13-19: 💤 Low value

Consider normalizing inputCids ordering across language artifacts.

The inputCids array ordering differs across the three job result files:

  • TS: [policyCid, blastRadiusCid, logCid, runnerIdentityCid, outputCid]
  • Go: [outputCid, blastRadiusCid, policyCid, logCid, runnerIdentityCid]
  • Rust: [policyCid, outputCid, runnerIdentityCid, logCid, blastRadiusCid]

If the ordering is semantically irrelevant to the CICP protocol, standardizing to a consistent order (e.g., sorted lexicographically or by a defined field order) would improve maintainability and make diffs clearer.

🤖 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
@.provekit/ci/accepted/ts/blake3-512:cf2632156ae353e1b6e263e8a5278476d15337d8fb7310297eccdce0476021db91086e63a869e08aeb3115ec2fa57d6f9b617377e7720b15493f1a2d61089f43.job-result.json
around lines 13 - 19, The inputCids arrays are inconsistently ordered across job
result artifacts; normalize the ordering for the symbol inputCids by choosing a
deterministic rule (e.g., lexicographic sort of CID strings or a fixed semantic
order such as [policyCid, blastRadiusCid, logCid, runnerIdentityCid, outputCid])
and apply that rule to all language artifacts (TS/Go/Rust) so the same ordered
list appears in each job-result.json; update the generation code or
post-processing step that emits inputCids to produce the chosen deterministic
order.
🤖 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 `@implementations/php/provekit-lift/src/ForwardPropagator.php`:
- Around line 397-408: The checkPositiveCalls function is scanning raw lines
with strpos and thus matches occurrences inside comments or string literals;
update checkPositiveCalls (used by lowerFloorSource) to ignore non-code text by
tokenizing the PHP source instead of raw substring search—use PHP's
token_get_all to walk tokens, detect a T_STRING token with content
"checkPositive" followed by a '(' punctuation token, then capture the argument
tokens until the matching ')' while skipping tokens inside comments (T_COMMENT,
T_DOC_COMMENT) and string tokens (T_CONSTANT_ENCAPSED_STRING); return the same
array shape ([start, arg]) but derived from token positions so false positives
in comments/strings are eliminated.
- Around line 358-384: The suppression flag topBlockDepth is being cleared when
a loop/header line has no braces, so an unbraced loop body (e.g. while ($c) \n
checkPositive($x);) loses top-fallback suppression; change the logic in
ForwardPropagator so that when startsTopFallbackBlock($trimmed) detects a header
with no '{' on the same line you set topBlockDepth to braceDepth+1 (or mark a
one-statement-applies flag) and ensure that this topBlockDepth is not nulled
until after the next statement is processed (i.e., delay clearing topBlockDepth
used by the checkPositiveCalls loop until after you consume a following unbraced
statement), referencing symbols: startsTopFallbackBlock, $topBlockDepth,
$braceDepth, checkPositiveCalls, isFunctionDefinition and the block that
currently nulls topBlockDepth at the end of the line.
- Around line 316-325: The code currently calls
$this->checkCallsite($stmt->calleeId, $currentPost, $stmt->range) and then
always merges $entry->post into $currentPost even when a diagnostic was
returned; change the logic in the 'case "call"' branch so that after calling
checkCallsite() you only merge $entry->post into $currentPost when no diagnostic
was produced (i.e., when $diagnostic is falsy) and $entry && $entry->post exist;
this prevents propagating callee postconditions after a failed precondition
(references: checkCallsite(), $diagnostic, $entry, $currentPost,
$this->index[$stmt->calleeId], $stmt->calleeId, $stmt->range).

In `@implementations/rust/provekit-lsp-rust/src/forward_propagator.rs`:
- Around line 310-314: The current function-header check (is_function_definition
= trimmed.starts_with("fn ")) misses qualified headers like "pub fn", "async
fn", "unsafe fn", "const fn" (and visibility forms like "pub(crate) fn"),
causing Stmt::Reset and top_block_depth clearing to be skipped; update the check
in forward_propagator.rs to recognize function headers with optional
qualifiers/visibility before "fn" (e.g., use a regex or token-based check that
matches start-of-line optional qualifiers/visibility/keywords followed by "fn"
and a space/paren) so that when such a header is detected you still push
Stmt::Reset and set top_block_depth = None (references: the variables trimmed,
is_function_definition, Stmt::Reset, and top_block_depth).

---

Nitpick comments:
In
@.provekit/ci/accepted/ts/blake3-512:cf2632156ae353e1b6e263e8a5278476d15337d8fb7310297eccdce0476021db91086e63a869e08aeb3115ec2fa57d6f9b617377e7720b15493f1a2d61089f43.job-result.json:
- Around line 13-19: The inputCids arrays are inconsistently ordered across job
result artifacts; normalize the ordering for the symbol inputCids by choosing a
deterministic rule (e.g., lexicographic sort of CID strings or a fixed semantic
order such as [policyCid, blastRadiusCid, logCid, runnerIdentityCid, outputCid])
and apply that rule to all language artifacts (TS/Go/Rust) so the same ordered
list appears in each job-result.json; update the generation code or
post-processing step that emits inputCids to produce the chosen deterministic
order.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 47634786-1879-44e6-b331-3bc7d0772d4f

📥 Commits

Reviewing files that changed from the base of the PR and between 88fee01 and 65753f3.

📒 Files selected for processing (15)
  • .provekit/ci/accepted/go/blake3-512:275735848a3086ec6a984040a4ab54230576aa2b643250ebe49fe7ccec811defcfb7d0d7d24997385b7c9af54c7bc92fd87f449cd96a9aff9896e13f5dee7819.job-result.json
  • .provekit/ci/accepted/rust/blake3-512:f3146fb0af9400276e30507d046038344fa7b21b0f33003799d252b41f7edeaeeedc0a3d9d48841387481d00ee81ded0896bcb31e21ce6033ae618a88253a5f8.job-result.json
  • .provekit/ci/accepted/ts/blake3-512:cf2632156ae353e1b6e263e8a5278476d15337d8fb7310297eccdce0476021db91086e63a869e08aeb3115ec2fa57d6f9b617377e7720b15493f1a2d61089f43.job-result.json
  • implementations/go/cmd/provekit-lsp-go/forward_propagator.go
  • implementations/go/cmd/provekit-lsp-go/forward_propagator_test.go
  • implementations/go/cmd/provekit-lsp-go/main.go
  • implementations/php/provekit-lift/src/ForwardPropagator.php
  • implementations/php/provekit-lift/src/lspd.php
  • implementations/php/provekit-lift/tests/ForwardPropagatorTest.php
  • implementations/rust/provekit-lsp-rust/src/forward_propagator.rs
  • implementations/rust/provekit-lsp-rust/src/main.rs
  • implementations/rust/provekit-lsp-rust/tests/round_trip.rs
  • tests/lsp/floor-fixture/go.go
  • tests/lsp/floor-fixture/php.php
  • tests/lsp/floor-fixture/rust.rs

Comment thread implementations/php/provekit-lift/src/ForwardPropagator.php
Comment thread implementations/php/provekit-lift/src/ForwardPropagator.php Outdated
Comment thread implementations/php/provekit-lift/src/ForwardPropagator.php Outdated
Comment thread implementations/rust/provekit-lsp-rust/src/forward_propagator.rs Outdated
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@TSavo
Copy link
Copy Markdown
Owner Author

TSavo commented May 8, 2026

@coderabbitai review
@codex review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 8, 2026

@TSavo: I'll trigger a review of the PR right away!

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 364f549d0d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +502 to +503
for prefix in ["async ", "unsafe ", "const "] {
if let Some(next) = rest.strip_prefix(prefix) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Recognize extern-qualified Rust function headers

lower_floor_source depends on is_rust_function_header to insert Stmt::Reset between functions, but the qualifier parser only consumes async, unsafe, and const before requiring fn. Valid headers like pub extern "C" fn ... are therefore missed, so forward facts can leak from the previous function into the extern-qualified one and skew diagnostics (false positives/false negatives) across function boundaries.

Useful? React with 👍 / 👎.

fn check_positive_calls(line: &str) -> Vec<(usize, String)> {
let mut calls = Vec::new();
let mut search_from = 0usize;
while let Some(relative_start) = line[search_from..].find("checkPositive(") {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Ignore non-code text when detecting Rust callsites

check_positive_calls scans raw source lines with find("checkPositive(") and no tokenization, so occurrences inside comments/strings (or inside longer identifiers) are lowered as real callsites. That makes parse emit implication-failed diagnostics for non-executable text such as // checkPositive(-1), creating deterministic false positives in normal Rust files.

Useful? React with 👍 / 👎.

calls := []checkPositiveCall{}
searchFrom := 0
for {
relativeStart := strings.Index(line[searchFrom:], "checkPositive(")
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Ignore non-code text when detecting Go callsites

checkPositiveCalls uses a plain substring search on each raw line, so checkPositive( inside comments/strings (or embedded in longer identifiers) is treated as a real callsite. During parse, this can produce implication-failed diagnostics even when no executable checkPositive call exists, which turns ordinary documentation/log text into user-visible false positives.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (4)
implementations/rust/provekit-lsp-rust/tests/forward_propagator.rs (1)

159-181: ⚡ Quick win

Strengthen this test beyond count-only validation.

Line 180 checks only cardinality, so semantic regressions could still pass. Assert core diagnostic fields for each emitted item.

Suggested assertion hardening
     assert_eq!(diagnostics.len(), 3, "{diagnostics:#?}");
+    for diagnostic in &diagnostics {
+        assert_eq!(diagnostic.code, "implication-failed");
+        assert_eq!(diagnostic.source, "provekit");
+        assert_eq!(diagnostic.data.callee, "checkPositive");
+        assert_eq!(diagnostic.data.missing_conjuncts, vec!["x > 0"]);
+    }
 }
🤖 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 `@implementations/rust/provekit-lsp-rust/tests/forward_propagator.rs` around
lines 159 - 181, The test floor_lowering_resets_on_qualified_function_headers
currently only asserts diagnostics.len(), so enhance it to verify each
diagnostic's core fields: iterate the diagnostics produced by
ForwardPropagator::floor_v1_seed_index().emit_diagnostics(&ForwardPropagator::lower_floor_source(source))
and assert expected values for diagnostic.message (or title),
diagnostic.severity, and diagnostic.span/range (matching the function locations
like public_violates, crate_visible_violates, async_violates) and/or any
diagnostic.code; keep the existing diagnostics variable and replace the single
count-only check with concrete assertions that the three expected diagnostics
target the correct function names and spans and have the expected
severity/messages.
implementations/go/cmd/provekit-lsp-go/forward_propagator_test.go (1)

154-169: ⚡ Quick win

Tighten the method-reset test to assert diagnostic identity, not only count.

TestLowerFloorSourceResetsOnGoMethods currently checks only len(diagnostics) == 1, which can false-pass if a different single diagnostic is emitted.

Proposed assertion hardening
 func TestLowerFloorSourceResetsOnGoMethods(t *testing.T) {
@@
 	diagnostics := FloorV1SeedForwardPropagator().EmitDiagnostics(LowerFloorSource(source))
 
 	if len(diagnostics) != 1 {
 		t.Fatalf("expected qualified method body to reset state and diagnose, got %#v", diagnostics)
 	}
+	diagnostic := diagnostics[0]
+	if diagnostic.Code != "implication-failed" {
+		t.Fatalf("code = %q, want implication-failed", diagnostic.Code)
+	}
+	if diagnostic.Data.Callee != "checkPositive" {
+		t.Fatalf("callee = %q, want checkPositive", diagnostic.Data.Callee)
+	}
 }
🤖 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 `@implementations/go/cmd/provekit-lsp-go/forward_propagator_test.go` around
lines 154 - 169, TestLowerFloorSourceResetsOnGoMethods only checks
len(diagnostics) and can false-pass; instead, after verifying
FloorV1SeedForwardPropagator().EmitDiagnostics(LowerFloorSource(source)) yields
exactly one diagnostic, assert the diagnostic's identity (e.g., diagnostic.ID or
diagnostic.Code or diagnostic.Message) matches the expected diagnostic emitted
for the qualified method reset. Update the test to first assert len(diagnostics)
== 1 and then compare diagnostics[0].(use the project’s diagnostic identity
field such as ID/Code/Message) against the expected value so the test verifies
the exact diagnostic produced.
implementations/php/provekit-lift/src/ForwardPropagator.php (1)

412-416: ⚡ Quick win

foreach and do { … } while are not recognized as top-fallback loops.

startsTopFallbackBlock matches only for/for(/while/while(. A foreach (...) { checkPositive(-1); } body or a do { checkPositive(-1); } while (...) body with a literal-negative argument therefore lowers to a real assign(x <= 0) + call, producing an implication-failed diagnostic that the for/while equivalent suppresses. For v1 floor consistency it's worth either extending the prefix list to include foreach /foreach(/do /do{/do\n or documenting the intentional gap.

Suggested extension
     private static function startsTopFallbackBlock(string $trimmed): bool
     {
         return str_starts_with($trimmed, 'for ') || str_starts_with($trimmed, 'for(')
-            || str_starts_with($trimmed, 'while ') || str_starts_with($trimmed, 'while(');
+            || str_starts_with($trimmed, 'while ') || str_starts_with($trimmed, 'while(')
+            || str_starts_with($trimmed, 'foreach ') || str_starts_with($trimmed, 'foreach(')
+            || $trimmed === 'do' || str_starts_with($trimmed, 'do ') || str_starts_with($trimmed, 'do{');
     }

Note: do { … } opens a brace on the header line, so it would land in the $topBlockDepth branch rather than the pending branch — that's the desired behavior.

🤖 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 `@implementations/php/provekit-lift/src/ForwardPropagator.php` around lines 412
- 416, The startsTopFallbackBlock function currently only checks for 'for' and
'while' prefixes, missing 'foreach' and 'do' loop headers which causes foreach
and do-while blocks to be misclassified; update startsTopFallbackBlock to also
detect 'foreach ' and 'foreach(' and the 'do' forms (e.g. 'do ', 'do{' and
'do\n') so those loop headers are treated as top-fallback blocks by the existing
logic in ForwardPropagator::startsTopFallbackBlock.
implementations/php/provekit-lift/tests/ForwardPropagatorTest.php (1)

185-208: ⚡ Quick win

Add a read timeout to the lspd subprocess test to prevent indefinite hangs.

fgets($pipes[1]) will block forever if lspd.php crashes before writing a response, fails to flush, or starts behaving differently in the future. Setting stream_set_timeout on the read pipe (and asserting via stream_get_meta_data) keeps CI from hanging on a regression.

Suggested guard
     assert_true(is_resource($proc), 'lspd process should start');
+    stream_set_timeout($pipes[1], 5);

     $request = json_encode([
@@
     $line = fgets($pipes[1]);
+    $meta = stream_get_meta_data($pipes[1]);
     fclose($pipes[1]);
     $stderr = stream_get_contents($pipes[2]);
     fclose($pipes[2]);
     $code = proc_close($proc);
+    assert_true(empty($meta['timed_out']), 'lspd response timed out');
     assert_same(0, $code, 'lspd process exit code: ' . $stderr);
     assert_true(is_string($line), 'lspd should emit one response line');

Note: the OpenGrep "command injection" hint on lines 185–189 is a false positive — proc_open with an array $cmd does not invoke a shell.

🤖 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 `@implementations/php/provekit-lift/tests/ForwardPropagatorTest.php` around
lines 185 - 208, The test currently blocks on fgets($pipes[1]) if the lspd
subprocess never writes; before calling fgets in ForwardPropagatorTest.php set a
short stream timeout on $pipes[1] via stream_set_timeout($pipes[1], <seconds>)
and after reading check stream_get_meta_data($pipes[1]) for the timed_out flag
and assert false if timed_out is true (include the captured stderr in the
assertion message). Ensure you still close pipes and proc_close($proc) in the
same test flow so the timeout path cleans up the subprocess.
🤖 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 `@implementations/php/provekit-lift/src/ForwardPropagator.php`:
- Around line 363-371: The loop-body suppression breaks when the loop header and
opening brace are on separate lines; update the logic so that if
$topSingleStatementPending is true and the current $scanLine contains a '{' (and
the line is not a new function definition), promote the pending state into a
real block by setting $topBlockDepth = $braceDepth (use the braceDepth value
computed before applying the current line's brace counts) and clear
$topSingleStatementPending so suppression persists until the matching '}' is
found; apply this change where the code handles lines with '{' (the same pattern
near the block handling around startsTopFallbackBlock and the similar block at
the other occurrence mentioned for lines 393-401), ensuring you compute the
before-update depth via $braceDepth + substr_count($scanLine, '{') -
substr_count($scanLine, '}') or equivalent and only promote when a '{' exists on
the current line and it's not a function definition.

---

Nitpick comments:
In `@implementations/go/cmd/provekit-lsp-go/forward_propagator_test.go`:
- Around line 154-169: TestLowerFloorSourceResetsOnGoMethods only checks
len(diagnostics) and can false-pass; instead, after verifying
FloorV1SeedForwardPropagator().EmitDiagnostics(LowerFloorSource(source)) yields
exactly one diagnostic, assert the diagnostic's identity (e.g., diagnostic.ID or
diagnostic.Code or diagnostic.Message) matches the expected diagnostic emitted
for the qualified method reset. Update the test to first assert len(diagnostics)
== 1 and then compare diagnostics[0].(use the project’s diagnostic identity
field such as ID/Code/Message) against the expected value so the test verifies
the exact diagnostic produced.

In `@implementations/php/provekit-lift/src/ForwardPropagator.php`:
- Around line 412-416: The startsTopFallbackBlock function currently only checks
for 'for' and 'while' prefixes, missing 'foreach' and 'do' loop headers which
causes foreach and do-while blocks to be misclassified; update
startsTopFallbackBlock to also detect 'foreach ' and 'foreach(' and the 'do'
forms (e.g. 'do ', 'do{' and 'do\n') so those loop headers are treated as
top-fallback blocks by the existing logic in
ForwardPropagator::startsTopFallbackBlock.

In `@implementations/php/provekit-lift/tests/ForwardPropagatorTest.php`:
- Around line 185-208: The test currently blocks on fgets($pipes[1]) if the lspd
subprocess never writes; before calling fgets in ForwardPropagatorTest.php set a
short stream timeout on $pipes[1] via stream_set_timeout($pipes[1], <seconds>)
and after reading check stream_get_meta_data($pipes[1]) for the timed_out flag
and assert false if timed_out is true (include the captured stderr in the
assertion message). Ensure you still close pipes and proc_close($proc) in the
same test flow so the timeout path cleans up the subprocess.

In `@implementations/rust/provekit-lsp-rust/tests/forward_propagator.rs`:
- Around line 159-181: The test
floor_lowering_resets_on_qualified_function_headers currently only asserts
diagnostics.len(), so enhance it to verify each diagnostic's core fields:
iterate the diagnostics produced by
ForwardPropagator::floor_v1_seed_index().emit_diagnostics(&ForwardPropagator::lower_floor_source(source))
and assert expected values for diagnostic.message (or title),
diagnostic.severity, and diagnostic.span/range (matching the function locations
like public_violates, crate_visible_violates, async_violates) and/or any
diagnostic.code; keep the existing diagnostics variable and replace the single
count-only check with concrete assertions that the three expected diagnostics
target the correct function names and spans and have the expected
severity/messages.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: bbf5c5a7-1895-46ed-a669-58cda31c06ba

📥 Commits

Reviewing files that changed from the base of the PR and between 65753f3 and 364f549.

📒 Files selected for processing (8)
  • .provekit/ci/accepted/go/blake3-512:6bb052cd30d79c9680ac36bff34429108c1746f3b22b63e196d5837f9462732c4b0739a46e53e82510697ef6bdefbbdf1883bef5652c8e713337a9d0d434189a.job-result.json
  • .provekit/ci/accepted/rust/blake3-512:803d931ad3484aab0eff88d76404dd1f369a0a3bbcc2be877b2c7fa71478e2c77f10e71843c5a8eb3d9170da467a3f2a2925fe622463b4d6b3fb5ab8131138d7.job-result.json
  • implementations/go/cmd/provekit-lsp-go/forward_propagator.go
  • implementations/go/cmd/provekit-lsp-go/forward_propagator_test.go
  • implementations/php/provekit-lift/src/ForwardPropagator.php
  • implementations/php/provekit-lift/tests/ForwardPropagatorTest.php
  • implementations/rust/provekit-lsp-rust/src/forward_propagator.rs
  • implementations/rust/provekit-lsp-rust/tests/forward_propagator.rs
✅ Files skipped from review due to trivial changes (2)
  • .provekit/ci/accepted/rust/blake3-512:803d931ad3484aab0eff88d76404dd1f369a0a3bbcc2be877b2c7fa71478e2c77f10e71843c5a8eb3d9170da467a3f2a2925fe622463b4d6b3fb5ab8131138d7.job-result.json
  • .provekit/ci/accepted/go/blake3-512:6bb052cd30d79c9680ac36bff34429108c1746f3b22b63e196d5837f9462732c4b0739a46e53e82510697ef6bdefbbdf1883bef5652c8e713337a9d0d434189a.job-result.json
🚧 Files skipped from review as they are similar to previous changes (2)
  • implementations/rust/provekit-lsp-rust/src/forward_propagator.rs
  • implementations/go/cmd/provekit-lsp-go/forward_propagator.go

Comment thread implementations/php/provekit-lift/src/ForwardPropagator.php
@TSavo
Copy link
Copy Markdown
Owner Author

TSavo commented May 8, 2026

@coderabbitai review
@codex review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 8, 2026

@TSavo: I'll trigger a review of PR #491 right away!

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@TSavo
Copy link
Copy Markdown
Owner Author

TSavo commented May 8, 2026

@coderabbitai review
@codex review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 8, 2026

@TSavo: I'll trigger a review of PR #491 right away!

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 22c107c54d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

{
$calls = [];
$searchFrom = 0;
while (($relativeStart = strpos($line, 'checkPositive(', $searchFrom)) !== false) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Enforce identifier boundaries for checkPositive matches

checkPositiveCalls accepts any substring match of checkPositive(, so identifiers like notcheckPositive(-1) are lowered as baseline checkPositive callsites. That produces false implication-failed diagnostics for unrelated functions and also perturbs propagated state (assign/call) based on the wrong symbol. Add identifier-boundary checks (as done in the Go/Rust scanners) before recording a match.

Useful? React with 👍 / 👎.

Comment on lines +448 to +450
while (($relativeStart = strpos($line, 'checkPositive(', $searchFrom)) !== false) {
$start = $relativeStart;
$argsStart = $start + strlen('checkPositive(');
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Parse checkPositive calls with optional whitespace

The matcher only looks for the exact token checkPositive(, so valid PHP call syntax like checkPositive (-1) is ignored and no diagnostic is emitted even when the precondition is violated. In editor buffers you cannot assume PSR formatting, so this creates real false negatives; the parser should skip optional spaces/tabs between the callee name and (.

Useful? React with 👍 / 👎.

@TSavo
Copy link
Copy Markdown
Owner Author

TSavo commented May 8, 2026

@coderabbitai review
@codex review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 8, 2026

@TSavo: I'll trigger a review of PR #491 right away!

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ac30a45128

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +582 to +583
} else if bytes[idx] == b'\'' || bytes[idx..].starts_with(b"b'") {
idx = mask_escaped_delimited(bytes, idx, b'\'', &mut out);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Skip lifetime apostrophes when masking Rust strings

The Rust masker currently treats any ' byte as the start of a quoted literal, so common lifetime syntax like fn f<'a>(...) is parsed as an unterminated char literal and masked until end-of-line. Because lower_floor_source relies on this masked text for brace depth and call lowering, lines containing lifetimes can lose {/} and other tokens, which can leak facts across functions or misclassify loop regions and diagnostics. This affects ordinary Rust code that uses lifetime annotations, not just edge cases.

Useful? React with 👍 / 👎.

@TSavo
Copy link
Copy Markdown
Owner Author

TSavo commented May 8, 2026

@coderabbitai review
@codex review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 8, 2026

@TSavo: I'll trigger a review of PR #491 right away!

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a2107cc76a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +503 to +505
for prefix in ["async ", "unsafe ", "const ", "extern "] {
if let Some(next) = rest.strip_prefix(prefix) {
rest = next.trim_start();
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Recognize extern ABI function headers before lowering

is_rust_function_header consumes the extern qualifier but never skips the ABI literal, so valid headers like pub unsafe extern "C" fn ... are not recognized as function boundaries. In lower_floor_source this prevents emitting Stmt::Reset, which leaks postconditions from the previous function into the extern function body and can suppress expected implication-failed diagnostics there.

Useful? React with 👍 / 👎.

Comment thread implementations/rust/provekit-lsp-rust/src/forward_propagator.rs
@TSavo
Copy link
Copy Markdown
Owner Author

TSavo commented May 8, 2026

@coderabbitai review
@codex review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 8, 2026

@TSavo: I'll trigger a review of PR #491 right away!

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1d07299c6e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +461 to +462
if ($start > 0 && self::isIdentifierByte($line[$start - 1])) {
$searchFrom = $start + $nameLen;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Ignore qualified member calls when matching checkPositive

The call scanner treats any standalone checkPositive( as the baseline callee, but the boundary check only excludes identifier characters, so qualified calls like $obj->checkPositive(-1) and Foo::checkPositive(-1) are still lowered as the seeded global checkPositive. In those cases we can emit implication-failed diagnostics for unrelated methods, which is a false positive in normal OO PHP code.

Useful? React with 👍 / 👎.

Comment on lines +516 to +517
func startsTopFallbackBlock(trimmed string) bool {
return strings.HasPrefix(trimmed, "for ") || strings.HasPrefix(trimmed, "for{")
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Recognize labeled for-loops for top-fallback suppression

startsTopFallbackBlock only matches lines beginning with for, so valid labeled loops like outer: for ... { ... } are not marked as top-fallback. That means callsites inside labeled loops are analyzed as precise flow and can produce implication-failed diagnostics that the loop fallback is supposed to suppress.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (2)
implementations/rust/provekit-lsp-rust/tests/forward_propagator.rs (2)

153-156: ⚡ Quick win

Make this assertion order-independent to avoid brittle tests.

This currently depends on diagnostics being emitted in a fixed index order. Assert on the callee set (and kind/code) instead of positional indices.

Suggested adjustment
-    assert_eq!(diagnostics.len(), 2, "{diagnostics:#?}");
-    assert_eq!(diagnostics[0].data.callee, "checkPositive");
-    assert_eq!(diagnostics[1].data.callee, "consumeReturn");
+    assert_eq!(diagnostics.len(), 2, "{diagnostics:#?}");
+    let actual_callees: std::collections::BTreeSet<&str> =
+        diagnostics.iter().map(|d| d.data.callee.as_str()).collect();
+    let expected_callees: std::collections::BTreeSet<&str> =
+        ["checkPositive", "consumeReturn"].into_iter().collect();
+    assert_eq!(actual_callees, expected_callees);
+    assert!(diagnostics.iter().all(|d| d.code == "implication-failed"));
🤖 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 `@implementations/rust/provekit-lsp-rust/tests/forward_propagator.rs` around
lines 153 - 156, The test currently asserts diagnostics by index
(assert_eq!(diagnostics[0].data.callee, "checkPositive") etc.), which is
brittle; instead collect the diagnostics' identifying fields (e.g.,
diagnostics.iter().map(|d| (d.data.callee.clone(), d.kind.clone(),
d.code.clone())) into a HashSet or sorted Vec) and assert that this set equals
the expected set {("checkPositive", kind_x, code_x), ("consumeReturn", kind_y,
code_y)} so order is ignored; update the assertions that reference diagnostics,
diagnostics[0], and diagnostics[1] to use this set comparison.

180-181: ⚡ Quick win

Strengthen count-only checks with semantic assertions.

len() checks alone can still pass with incorrect diagnostic content. Add at least code == "implication-failed" and expected callee assertions for these floor-lowering tests.

Example for one test (apply similarly to others)
     assert_eq!(diagnostics.len(), 3, "{diagnostics:#?}");
+    assert!(diagnostics.iter().all(|d| d.code == "implication-failed"));
+    assert!(diagnostics.iter().all(|d| d.data.callee == "checkPositive"));

Also applies to: 197-198, 226-227

🤖 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 `@implementations/rust/provekit-lsp-rust/tests/forward_propagator.rs` around
lines 180 - 181, Replace the bare count-only assertions on the test-local
diagnostics vector with semantic checks: after asserting diagnostics.len() == 3,
iterate the diagnostics and assert each diagnostic.code (or
diagnostic.code.as_deref()) equals "implication-failed" and assert the
callee/target field (the field you use to record the expected function/term in
these floor-lowering tests) matches the expected callee for each diagnostic;
update the three test blocks in tests/forward_propagator.rs that currently only
do assert_eq!(diagnostics.len(), ...) (the one at the shown location and the two
others flagged) so they validate diagnostic.code == "implication-failed" and the
expected callee identifier for each diagnostic entry rather than relying on
count-only checks.
🤖 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 `@implementations/go/cmd/provekit-lsp-go/forward_propagator.go`:
- Around line 377-390: The current match for the function name checkPositive
records selector-qualified calls like pkg.checkPositive because it only rejects
when the byte immediately before start is an identifier; update the matching
logic (around variables start, searchFrom, cursor, and function
isIdentifierByte) to also reject selector-qualified invocations by checking for
a '.' immediately before the name (e.g. if start>0 && line[start-1]=='.' then
set searchFrom = start+len(name) and continue), or more robustly skip any
whitespace backwards and if the first non-space byte before start is '.' treat
it as a selector and skip the match; apply this change where checkPositive
invocations are detected so only bare function calls are recorded.
- Around line 333-337: The current code calls SingleLineRange with raw byte
offsets (call.start and call.start+len("checkPositive")), which must be
converted to UTF-16 code unit offsets for LSP positions; modify the code that
appends the ForwardStmt so you compute UTF-16 column offsets for the start and
end by iterating the line up to the byte index, decoding runes and summing 1 for
BMP runes or 2 for supplementary runes (use utf16.EncodeRune helper) to get
startUTF16 and endUTF16, then call SingleLineRange(lineIdx, startUTF16,
endUTF16) when constructing the ForwardStmt (symbols: ForwardStmt,
ForwardStmtCall, call.start, SingleLineRange).

In `@implementations/php/provekit-lift/src/ForwardPropagator.php`:
- Around line 420-424: The startsTopFallbackBlock predicate currently only
matches for/while and should be extended to also detect foreach, switch and
do-while constructs so the propagator treats their bodies as top-fallback
regions; update startsTopFallbackBlock to return true for strings starting with
'foreach', 'switch', and 'do' (and for 'do{' / 'do {' variants) and also adjust
the logic that handles the closing brace so a bare 'while(...)' immediately
following a '}' from a do-block does not open a new topSingleStatementPending —
run/add a regression mirroring testNextLineLoopBraceUsesTopFallbackForWholeBody
to cover foreach, switch and do/while cases and ensure
topBlockDepth/topSingleStatementPending are set/cleared consistently for these
constructs.
- Around line 459-470: The identifier-boundary check in ForwardPropagator.php
misses method/static-call prefixes (-> and ::), so add an explicit check around
the existing isIdentifierByte usage in the loop that looks for $name: if the
char before $start is '>' or ':' (i.e. $line[$start-1] === '>' ||
$line[$start-1] === ':'), treat it as a non-global identifier and skip lowering
by advancing $searchFrom (same behavior as when isIdentifierByte returns true);
update the while loop that uses $relativeStart/$start/$nameLen/$cursor and
isIdentifierByte to include this condition (and similarly ensure the cursor-side
check still applies), and add a regression test like "$obj->checkPositive(-1);
Foo::checkPositive(-1);" asserting zero diagnostics.

---

Nitpick comments:
In `@implementations/rust/provekit-lsp-rust/tests/forward_propagator.rs`:
- Around line 153-156: The test currently asserts diagnostics by index
(assert_eq!(diagnostics[0].data.callee, "checkPositive") etc.), which is
brittle; instead collect the diagnostics' identifying fields (e.g.,
diagnostics.iter().map(|d| (d.data.callee.clone(), d.kind.clone(),
d.code.clone())) into a HashSet or sorted Vec) and assert that this set equals
the expected set {("checkPositive", kind_x, code_x), ("consumeReturn", kind_y,
code_y)} so order is ignored; update the assertions that reference diagnostics,
diagnostics[0], and diagnostics[1] to use this set comparison.
- Around line 180-181: Replace the bare count-only assertions on the test-local
diagnostics vector with semantic checks: after asserting diagnostics.len() == 3,
iterate the diagnostics and assert each diagnostic.code (or
diagnostic.code.as_deref()) equals "implication-failed" and assert the
callee/target field (the field you use to record the expected function/term in
these floor-lowering tests) matches the expected callee for each diagnostic;
update the three test blocks in tests/forward_propagator.rs that currently only
do assert_eq!(diagnostics.len(), ...) (the one at the shown location and the two
others flagged) so they validate diagnostic.code == "implication-failed" and the
expected callee identifier for each diagnostic entry rather than relying on
count-only checks.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: cd458bdc-7f52-47c2-b872-df983427379a

📥 Commits

Reviewing files that changed from the base of the PR and between 364f549 and 1d07299.

📒 Files selected for processing (10)
  • .provekit/ci/accepted/go/blake3-512:81c69046c282a4c4fe84051dc79e9986144a12358ec2a0e2b4d3154ed9241bb73b2a8480c75df9c3b9f39744b8dff7bc8e743f95442be329297a3698715fa3e1.job-result.json
  • .provekit/ci/accepted/rust/blake3-512:1664fd99978d6da7bca9d54a1d2d635d2d4e76ed486c07021cd8bbbc60a51d7615b030fac25ef24e2228ca0e35d4815db959b507f25eb47ad81d698f4430ff88.job-result.json
  • .provekit/ci/accepted/rust/blake3-512:2ac53de148161672d99e0302fc1d939a35a82af744300ec5f74df71b56ff77b18bca047e1556026816f2866fe55b01cf988b40079b283cf14ed27dffc7f3303c.job-result.json
  • .provekit/ci/accepted/rust/blake3-512:cd7e1e2855389cab7c28af88541e7cb1950bf3ee9b119b73fb7f03bf60d6dc939e8e4071f3e792e02520b4b9dc0adc1c2e6f663a56e6387623fea9364c8fbc28.job-result.json
  • implementations/go/cmd/provekit-lsp-go/forward_propagator.go
  • implementations/go/cmd/provekit-lsp-go/forward_propagator_test.go
  • implementations/php/provekit-lift/src/ForwardPropagator.php
  • implementations/php/provekit-lift/tests/ForwardPropagatorTest.php
  • implementations/rust/provekit-lsp-rust/src/forward_propagator.rs
  • implementations/rust/provekit-lsp-rust/tests/forward_propagator.rs
✅ Files skipped from review due to trivial changes (4)
  • .provekit/ci/accepted/rust/blake3-512:cd7e1e2855389cab7c28af88541e7cb1950bf3ee9b119b73fb7f03bf60d6dc939e8e4071f3e792e02520b4b9dc0adc1c2e6f663a56e6387623fea9364c8fbc28.job-result.json
  • .provekit/ci/accepted/rust/blake3-512:1664fd99978d6da7bca9d54a1d2d635d2d4e76ed486c07021cd8bbbc60a51d7615b030fac25ef24e2228ca0e35d4815db959b507f25eb47ad81d698f4430ff88.job-result.json
  • .provekit/ci/accepted/go/blake3-512:81c69046c282a4c4fe84051dc79e9986144a12358ec2a0e2b4d3154ed9241bb73b2a8480c75df9c3b9f39744b8dff7bc8e743f95442be329297a3698715fa3e1.job-result.json
  • .provekit/ci/accepted/rust/blake3-512:2ac53de148161672d99e0302fc1d939a35a82af744300ec5f74df71b56ff77b18bca047e1556026816f2866fe55b01cf988b40079b283cf14ed27dffc7f3303c.job-result.json
🚧 Files skipped from review as they are similar to previous changes (2)
  • implementations/go/cmd/provekit-lsp-go/forward_propagator_test.go
  • implementations/rust/provekit-lsp-rust/src/forward_propagator.rs

Comment thread implementations/go/cmd/provekit-lsp-go/forward_propagator.go
Comment thread implementations/go/cmd/provekit-lsp-go/forward_propagator.go Outdated
Comment thread implementations/php/provekit-lift/src/ForwardPropagator.php
Comment thread implementations/php/provekit-lift/src/ForwardPropagator.php
@TSavo
Copy link
Copy Markdown
Owner Author

TSavo commented May 8, 2026

@coderabbitai review
@codex review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 8, 2026

@TSavo: I'll trigger a review of PR #491 right away!

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@TSavo
Copy link
Copy Markdown
Owner Author

TSavo commented May 8, 2026

@coderabbitai review
@codex review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 8, 2026

@TSavo: I'll trigger a review of PR #491 right away!

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@TSavo TSavo merged commit 151a763 into main May 8, 2026
17 checks passed
@TSavo TSavo deleted the codex/main-20260508-104659 branch May 8, 2026 21:41
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.

2 participants