11use crate :: attribute:: ExtendedAttributeList ;
22use crate :: common:: { Default , Identifier } ;
33use crate :: types:: Type ;
4+ use crate :: Parse ;
45
56/// Parses dictionary members
67pub 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