Skip to content

Fix unreachableTrigger false positives for cross-spec and conditional trigger emissions (#19)#35

Merged
yavorpanayotov merged 1 commit into
mainfrom
fix/issue-19-cross-spec-triggers
Jun 11, 2026
Merged

Fix unreachableTrigger false positives for cross-spec and conditional trigger emissions (#19)#35
yavorpanayotov merged 1 commit into
mainfrom
fix/issue-19-cross-spec-triggers

Conversation

@yavorpanayotov

Copy link
Copy Markdown
Collaborator

Fixes #19.

Three fixes to the unreachable-trigger analysis, applied to both the Rust analyzer (crates/allium-parser/src/analysis.rs) and the TypeScript extension analyzer (extensions/allium/src/language-tools/analyzer.ts).

1. Qualified trigger calls are a valid trigger form

when: alias/Trigger(...) is documented in the language reference ("Responding to external triggers"), and the TS isValidTriggerShape already accepted it — but the Rust is_valid_trigger rejected it with an allium.rule.invalidTrigger error, making allium check exit non-zero on the issue's minimal repro (this was actually worse than the info-level diagnostics the issue reports). Expr::Call with a QualifiedName function is now a valid trigger.

2. Cross-spec trigger reachability across use imports

The CLI's CrossModuleContext now carries imported_triggers: per file, a use-alias → set of trigger names the aliased module provides (surfaces) or emits (rule ensures), computed by the new allium_parser::collect_trigger_outputs. The unreachable-trigger check (both the diagnostics lane and the analyse findings lane) resolves:

  • alias/Trigger against the aliased module — flagged only when the module is in the check set and determinately lacks the trigger, with a message naming the module: ... but imported module 'emitter' does not provide or emit it.
  • aliases whose targets are outside the check set (e.g. github.com/... coordinates) — never flagged, reachability is unknowable
  • unqualified triggers — local provides/emits first, then any imported module's triggers

The single-file TS analyzer cannot see other modules, so it now skips qualified subscriptions entirely (call name preceded by /).

3. Emissions inside if/else if/else branches and for iterations

A trigger emitted as the leading call of a conditional branch or for body inside an ensures: value now registers as an emission. (Contrary to the issue's grammar-gap theory, the Rust parser already parsed these fine — the collector just didn't recurse.) Rust: collect_leading_ensures_call recurses into Conditional/For; TS: a branch-call lane scoped to ensures clause extents via the new ensuresClauseRegions helper, so branch calls in other clauses (e.g. a let conditional) don't register. for is included on both sides so the two implementations can't diverge on nesting.

Verification

  • Issue's minimal repro: allium check emitter.allium listener.allium exits 0 with no invalidTrigger/unreachableTrigger for emitter/Pinged; analyse likewise
  • Issue 2 repro: the else-branch SensorAdvertDecoded emission satisfies its listener
  • Genuinely unreachable qualified triggers are still flagged (new message names the module)
  • 357 Rust parser tests (10 new), 29 CLI integration tests (5 new), 320 extension tests (4 new) — all green; npm run lint clean

Specs updated in docs/project/specs/ and the parity notes in docs/project/rust-checker-parity.md.

🤖 Generated with Claude Code

… trigger emissions (#19)

Three fixes to the unreachable-trigger analysis, applied to both the Rust
analyzer and the TypeScript extension analyzer:

- Accept qualified trigger calls (`when: alias/Trigger(...)`) as a valid
  trigger form in the Rust checker. The language reference documents this
  under "Responding to external triggers"; previously the Rust CLI rejected
  it with an `allium.rule.invalidTrigger` error.

- Resolve trigger reachability across `use` imports in the multi-file CLI.
  The cross-module context now carries a per-alias map of the trigger names
  each module provides or emits; `alias/Trigger` subscriptions are checked
  against the aliased module and flagged only when it determinately lacks
  the trigger. Aliases pointing outside the check set are never flagged,
  and unqualified triggers emitted by an imported module count as
  reachable. The single-file TS analyzer skips qualified subscriptions
  entirely since it cannot see the emitting module.

- Register trigger emissions made on `if`/`else if`/`else` branches and
  inside `for` iterations of `ensures:` values. Rust recurses into
  Conditional/For expressions; TS adds a branch-call lane scoped to
  ensures clause extents. Both keep the leading-call-only convention.

Fixes #19

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@yavorpanayotov yavorpanayotov merged commit 6035041 into main Jun 11, 2026
2 checks passed
@yavorpanayotov yavorpanayotov deleted the fix/issue-19-cross-spec-triggers branch June 11, 2026 17:17
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.

analyse: unreachableTrigger false positives for cross-spec trigger subscriptions via use imports

1 participant