Skip to content

feat(parser): reject a feature named in/out — reserved words (#420) - #437

Merged
avrabe merged 1 commit into
mainfrom
feat/reject-in-out-port-name-420
Aug 21, 2026
Merged

feat(parser): reject a feature named in/out — reserved words (#420)#437
avrabe merged 1 commit into
mainfrom
feat/reject-in-out-port-name-420

Conversation

@avrabe

@avrabe avrabe commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

What

Category-1 (grammar-level permissiveness) item from #420: spar accepted a feature — in practice a port — named with the AS-5506B Annex A reserved word in or out, e.g.

system S
  features
    in  : in event port;   -- `in` used as the port NAME
    out : out event port;
end S;

in/out cannot be identifiers, so this is a too-permissive gap: spar admitted invalid AADL a user's model could carry. OSATE 2.18.0 rejects it at the grammar level (no viable alternative at input 'in', recorded on #420).

How

The acceptance came through the general keyword-as-name path (Parser::at_name reads any keyword followed by : as a declaration name). feature() (crates/spar-parser/src/grammar/features.rs) now special-cases IN_KW/OUT_KW in the feature-name position, emitting

a feature cannot be named in or out; they are reserved words (AS-5506B Annex A)

while still consuming the token so the rest of the declaration parses and the tree round-trips (the reject is a diagnostic, not a rewrite). Scope is exactly the name position — in/out in their legal roles (feature direction in/out/in out, in modes, in binding) and keyword-as-name for other keywords are untouched.

Oracle & non-vacuity (executed, not asserted)

Inline in crates/spar-syntax/tests/parser_tests.rs — no external tool needed:

  • port_named_in_is_rejected, port_named_out_is_rejected — assert the diagnostic.
  • port_named_in_still_round_trips — asserts losslessness.
  • port_named_inp_outp_still_accepted — the discriminating partner: identical but for the port names inp/outp (ordinary identifiers). It parses cleanly both before and after, so the check keys on the name being a reserved word and nothing else.

Verified against the built test binary: before the fix all three rejection inputs parsed with zero errors; reverting the features.rs branch re-greens them, proving they bite.

No regression to the graded conformance floor

  • osate_agreement.rs (too-permissive floor 53, too-strict 0) and three_way_conformance.rs re-run and hold their ceilings — no first-party file flips verdict.
  • FIRST_PARTY_MODELS unchanged (120); no vendored/first-party .aadl names a feature in/out (grep -rniE '^[[:space:]]*(in|out)[[:space:]]*:' test-data/interop → 0).
  • cargo fmt --all -- --check, cargo clippy --workspace --all-targets -- -D warnings, and the full spar-syntax / corpus / roundtrip suites pass locally.

Requirement & honest scope

Adds REQ-PLUGFEST-PORTNAME-INOUT-001 (implemented, traces-to REQ-PLUGFEST-006).

Deferred, disclosed: the reverse-direction discipline pins such a rule with a test-data/negative/** fixture carrying measured OSATE and Ocarina verdicts (the two graded baselines are asserted set-equal and must cover every first-party file). This automated environment has neither a downloaded OSATE nor Ocarina, and a baseline row must be measured, never assumed, so the corpus fixture + its two baseline rows are left to a run/maintainer with both oracles. The inline tests fully pin the behaviour meanwhile.

Does not close #420 (many category-1 items remain), and does not claim in/out is the last reserved word spar admits as an identifier — other name positions (subcomponent/connection) are not audited here.

An independent clean-room subagent re-derived non-vacuity, scope, the graded invariants, the grep-0 corroboration, and the REQ's honesty — all PASS, no over-claim.

🤖 Generated with Claude Code


Generated by Claude Code

Category-1 (grammar-level permissiveness) item from #420: spar accepted a
feature — in practice a port — named with the AS-5506B Annex A reserved word
`in` or `out`, e.g. `in : in event port;`. `in`/`out` cannot be identifiers,
so this was a too-permissive gap admitting invalid AADL a user's model could
carry. OSATE 2.18.0 rejects it at the grammar level ("no viable alternative at
input 'in'", recorded on #420).

The acceptance came through the general keyword-as-name path (`at_name` reads
any keyword followed by `:` as a declaration name). `feature()` now
special-cases `IN_KW`/`OUT_KW` in the feature-name position, emitting a
reserved-word diagnostic while still consuming the token so the rest of the
declaration parses and the tree round-trips. Scope is exactly the feature-name
position; `in`/`out` in their legal roles (direction `in`/`out`/`in out`, `in
modes`, `in binding`) and keyword-as-name for other keywords are untouched.

Oracle (inline, no external tool needed): crates/spar-syntax/tests/parser_tests.rs
— port_named_in_is_rejected / port_named_out_is_rejected assert the diagnostic;
port_named_in_still_round_trips asserts losslessness. Non-vacuity is executed:
before the fix all three rejection inputs parsed with zero errors; the
discriminating partner port_named_inp_outp_still_accepted (ports `inp`/`outp`)
parses cleanly both before and after, so the check keys on the name being
reserved and nothing else.

No first-party file flips: the graded conformance suites (osate_agreement.rs
too-permissive floor 53, too-strict 0; three_way_conformance.rs) hold their
ceilings, and no vendored/first-party .aadl names a feature `in`/`out`
(grep over test-data/interop returns 0).

Adds REQ-PLUGFEST-PORTNAME-INOUT-001 (implemented, traces-to REQ-PLUGFEST-006).
Honestly deferred: the reverse-direction discipline pins such a rule with a
test-data/negative fixture carrying MEASURED OSATE and Ocarina verdicts (the two
graded baselines are asserted set-equal). This environment has neither oracle,
and a baseline row must be measured not assumed, so the corpus fixture + its two
baseline rows are left to a run/maintainer with both tools. Does not close #420.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

Rivet verification gate

20/20 passed

count
Passed 20
Failed 0
Skipped (no steps) 0

Filter: (and (= type "feature") (or (has-tag "v093") (has-tag "v0100")))

Failed artifacts

(none)

Updated automatically by tools/post_verification_comment.py. Source of truth: artifacts/verification.yaml.

@codecov

codecov Bot commented Aug 21, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@avrabe
avrabe merged commit 0acc824 into main Aug 21, 2026
22 checks passed
@avrabe
avrabe deleted the feat/reject-in-out-port-name-420 branch August 21, 2026 19:25
@avrabe avrabe mentioned this pull request Aug 21, 2026
avrabe added a commit that referenced this pull request Aug 22, 2026
Version bump for v0.42.0. Feature landed in #437 (#420 category-1): spar now
rejects a feature/port named with the AS-5506B Annex A reserved word `in` or
`out` (a too-permissive grammar gap OSATE rejects at the grammar level).
Cargo.toml / Cargo.lock (23 workspace crates) / vscode-spar/package.json
0.41.0 -> 0.42.0; REQ-PLUGFEST-PORTNAME-INOUT-001 promoted to verified
(release v0.42.0).

Co-authored-by: Claude <noreply@anthropic.com>
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.

spar accepts AADL that OSATE rejects: 53 of 120 first-party models (was 55 of 117 pre-v0.37.0)

2 participants