Skip to content

Commit 3e7c8cb

Browse files
authored
Fix DictionaryMember parser to match the WebIDL spec grammar (#62)
The WebIDL spec defines two productions for dictionary members: DictionaryMemberRest :: required TypeWithExtendedAttributes identifier ; Type identifier Default ; This means for required members, extended attributes like [EnforceRange] go between 'required' and the type: required [EnforceRange] unsigned long codedWidth; Previously, weedle only accepted the non-spec order: [EnforceRange] required unsigned long codedWidth; This change replaces the macro-generated parser with a manual Parse implementation that supports both orderings for backward compatibility. Fixes wasm-bindgen/wasm-bindgen#5008.
1 parent e9e1312 commit 3e7c8cb

1 file changed

Lines changed: 131 additions & 10 deletions

File tree

src/dictionary.rs

Lines changed: 131 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,95 @@
11
use crate::attribute::ExtendedAttributeList;
22
use crate::common::{Default, Identifier};
33
use crate::types::Type;
4+
use crate::Parse;
45

56
/// Parses dictionary members
67
pub type DictionaryMembers<'a> = Vec<DictionaryMember<'a>>;
78

8-
ast_types! {
9-
/// Parses dictionary member `[attributes]? required? type identifier ( = default )?;`
10-
struct DictionaryMember<'a> {
11-
attributes: Option<ExtendedAttributeList<'a>>,
12-
required: Option<term!(required)>,
13-
type_: Type<'a>,
14-
identifier: Identifier<'a>,
15-
default: Option<Default<'a>>,
16-
semi_colon: term!(;),
9+
/// Parses dictionary member per the WebIDL spec grammar:
10+
///
11+
/// ```text
12+
/// DictionaryMember ::
13+
/// ExtendedAttributeList DictionaryMemberRest
14+
///
15+
/// DictionaryMemberRest ::
16+
/// required TypeWithExtendedAttributes identifier ;
17+
/// Type identifier Default ;
18+
///
19+
/// TypeWithExtendedAttributes ::
20+
/// ExtendedAttributeList Type
21+
/// ```
22+
///
23+
/// This means:
24+
/// - Required members: `[member-attrs]? required [type-attrs]? Type identifier ;`
25+
/// - Optional members: `[member-attrs]? Type identifier Default? ;`
26+
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
27+
pub struct DictionaryMember<'a> {
28+
pub attributes: Option<ExtendedAttributeList<'a>>,
29+
pub required: Option<crate::term::Required>,
30+
pub type_: Type<'a>,
31+
pub identifier: Identifier<'a>,
32+
pub default: Option<Default<'a>>,
33+
pub semi_colon: crate::term::SemiColon,
34+
}
35+
36+
impl<'a> Parse<'a> for DictionaryMember<'a> {
37+
fn parse(input: &'a str) -> crate::IResult<&'a str, Self> {
38+
// First, try to parse optional extended attributes (member-level)
39+
let (input, attributes) = <Option<ExtendedAttributeList<'a>>>::parse(input)?;
40+
41+
// Try to parse `required`
42+
let (input, required) = <Option<crate::term::Required>>::parse(input)?;
43+
44+
if required.is_some() {
45+
// Per the spec, required members use TypeWithExtendedAttributes:
46+
// required [type-attrs]? Type identifier ;
47+
// Try to parse type-level extended attributes after `required`.
48+
// If present, merge them into the member attributes for backward
49+
// compatibility with consumers that only look at `attributes`.
50+
let (input, type_attributes) = <Option<ExtendedAttributeList<'a>>>::parse(input)?;
51+
let (input, type_) = Type::parse(input)?;
52+
let (input, identifier) = Identifier::parse(input)?;
53+
let (input, semi_colon) = <crate::term::SemiColon>::parse(input)?;
54+
55+
// Merge: if both member-level and type-level attributes are present,
56+
// prefer the type-level attributes (the spec-correct position).
57+
// In practice they shouldn't both be present on the same member.
58+
let merged_attributes = match (attributes, type_attributes) {
59+
(_, Some(ta)) => Some(ta),
60+
(ma, None) => ma,
61+
};
62+
63+
Ok((
64+
input,
65+
DictionaryMember {
66+
attributes: merged_attributes,
67+
required,
68+
type_,
69+
identifier,
70+
default: None,
71+
semi_colon,
72+
},
73+
))
74+
} else {
75+
// Optional member: [member-attrs]? Type identifier Default? ;
76+
let (input, type_) = Type::parse(input)?;
77+
let (input, identifier) = Identifier::parse(input)?;
78+
let (input, default) = <Option<Default<'a>>>::parse(input)?;
79+
let (input, semi_colon) = <crate::term::SemiColon>::parse(input)?;
80+
81+
Ok((
82+
input,
83+
DictionaryMember {
84+
attributes,
85+
required,
86+
type_,
87+
identifier,
88+
default,
89+
semi_colon,
90+
},
91+
))
92+
}
1793
}
1894
}
1995

@@ -22,12 +98,57 @@ mod test {
2298
use super::*;
2399
use crate::Parse;
24100

25-
test!(should_parse_dictionary_member { "required long num = 5;" =>
101+
test!(should_parse_dictionary_member { "required long num;" =>
26102
"";
27103
DictionaryMember;
28104
attributes.is_none();
29105
required.is_some();
30106
identifier.0 == "num";
107+
default.is_none();
108+
});
109+
110+
test!(should_parse_required_with_type_ext_attrs { "required [EnforceRange] unsigned long num;" =>
111+
"";
112+
DictionaryMember;
113+
attributes.is_some();
114+
required.is_some();
115+
identifier.0 == "num";
116+
default.is_none();
117+
});
118+
119+
test!(should_parse_member_attrs_before_required { "[EnforceRange] required unsigned long num;" =>
120+
"";
121+
DictionaryMember;
122+
attributes.is_some();
123+
required.is_some();
124+
identifier.0 == "num";
125+
default.is_none();
126+
});
127+
128+
test!(should_parse_optional_member { "long num;" =>
129+
"";
130+
DictionaryMember;
131+
attributes.is_none();
132+
required.is_none();
133+
identifier.0 == "num";
134+
default.is_none();
135+
});
136+
137+
test!(should_parse_optional_member_with_default { "long num = 5;" =>
138+
"";
139+
DictionaryMember;
140+
attributes.is_none();
141+
required.is_none();
142+
identifier.0 == "num";
31143
default.is_some();
32144
});
145+
146+
test!(should_parse_optional_member_with_attrs { "[Clamp] long num;" =>
147+
"";
148+
DictionaryMember;
149+
attributes.is_some();
150+
required.is_none();
151+
identifier.0 == "num";
152+
default.is_none();
153+
});
33154
}

0 commit comments

Comments
 (0)