Skip to content

fix(pest_bridge): colon member key misparsed as arrow key on => in value#618

Open
SebastienGllmt wants to merge 1 commit into
anweiss:mainfrom
dcSpark:fix-bareword-member-key
Open

fix(pest_bridge): colon member key misparsed as arrow key on => in value#618
SebastienGllmt wants to merge 1 commit into
anweiss:mainfrom
dcSpark:fix-bareword-member-key

Conversation

@SebastienGllmt

Copy link
Copy Markdown
Contributor

Fix: bareword (colon) member key mis-parsed as a type-1 (arrow) key when the entry value contains =>

Problem

In the Pest bridge, a colon-form member key (name: type, RFC 8610 §3.5.1) is silently converted
into an arrow-form type-1 member key (type => type) whenever the surrounding group entry's text
contains the substring => anywhere — including inside the entry's value.

foo = { x: uint, b: "=>" }
  • Expected: b is MemberKey::Bareword — the text key "b", whose value is the string "=>".
  • Actual: b is MemberKey::Type1 — a type key named b.

There is no arrow member key anywhere in that entry; the only => is two characters inside a string
literal. The same misfire happens for any entry whose value legitimately contains an arrow, e.g. a
nested map:

foo = { b: { * uint => uint } }   ; `b` should be Bareword; the `=>` belongs to the inner map

This matters because the two forms mean different things (RFC 8610 §3.5.1): b: T matches a fixed
field whose key is the text "b", while b => T matches any key of type b. Since b is usually
not a defined type, the misparse also turns a well-formed schema into one with an undefined type
reference. Any consumer that distinguishes text keys from type keys (validators, code generators) is
affected.

Root cause and relation to the Pest migration / #536

The grammar is not at fault — it disambiguates correctly. The defect is in the AST conversion,
which re-derives the arrow-vs-colon distinction from a raw substring scan of the whole entry instead
of reading the parse tree:

// src/pest_bridge.rs, convert_group_entry (before)
let full_text = pair.as_str();                                    // spans key AND value
let has_arrow = has_member_key && (has_cut || full_text.contains("=>"));
let has_colon = has_member_key && !has_arrow && full_text.contains(':');

full_text spans the entire entry, so any => in the value corrupts the classification.

The fix

Decide arrow vs. colon structurally instead of substring checking or based on a boolean parameter to functions

Note: after #536, a bareword/typename/value child of member_key can
only occur in colon context (every arrow routes through the type1 alternative).

This means the old if is_arrow_map { …→Type1 } branches in those arms were unreachable dead code — they only ever
fired because the substring scan mis-set the flag. Therefore, the minimal correct change is therefore not to re-derive the flag, but to delete it

Conversion is now a pure function of the parse-tree shape, with no footgun left behind.

// after — the whole arrow/colon decision moves into the type-tree-driven member-key conversion
let has_cut = pair.clone().into_inner().any(|p| p.as_rule() == Rule::cut);
if has_cut {
  is_cut = true;
}

Other notes

  • Also removed _convert_member_key — an unused (no callers) function that carried the same
    full_str.contains("=>") heuristic (keeping it as reference didn't feel like it made sense if it's wrong)

Test plan

Added test_bareword_colon_key_not_misparsed_as_arrow in tests/grammar.rs. Existing member-key
tests only asserted parse success and so could not catch a wrong variant; this one asserts the
member-key variant:

CDDL key expected
foo = { b: "=>" } b Bareword (was Type1 — the bug)
foo = { b: { * uint => uint } } b Bareword (was Type1 — the bug)
foo = { b: uint } b Bareword
foo = { 1: uint } 1 Value
foo = { b => uint } b Type1
foo = { 1 => uint } 1 Type1
foo = { b ^ => uint } b Type1

cargo test --lib passes (full default feature set); cargo fmt clean.

@SebastienGllmt
SebastienGllmt force-pushed the fix-bareword-member-key branch from 5ec4363 to cad07c8 Compare June 30, 2026 20:26
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