]>
Commit | Line | Data |
---|---|---|
2b03887a | 1 | use crate::errors::{InvalidMetaItem, SuffixedLiteralInAttribute}; |
9ffffee4 | 2 | use crate::fluent_generated as fluent; |
2b03887a | 3 | |
a2a8927a | 4 | use super::{AttrWrapper, Capturing, FnParseMode, ForceCollect, Parser, PathStyle}; |
3dfed10e | 5 | use rustc_ast as ast; |
74b04a01 | 6 | use rustc_ast::attr; |
04454e1e | 7 | use rustc_ast::token::{self, Delimiter, Nonterminal}; |
9ffffee4 | 8 | use rustc_errors::{error_code, Diagnostic, IntoDiagnostic, PResult}; |
c295e0f8 | 9 | use rustc_span::{sym, BytePos, Span}; |
9ffffee4 FG |
10 | use std::convert::TryInto; |
11 | use thin_vec::ThinVec; | |
12 | use tracing::debug; | |
9fa01778 | 13 | |
fc512014 | 14 | // Public for rustfmt usage |
8faf50e0 | 15 | #[derive(Debug)] |
2b03887a | 16 | pub enum InnerAttrPolicy { |
5bcae85e | 17 | Permitted, |
2b03887a | 18 | Forbidden(Option<InnerAttrForbiddenReason>), |
5bcae85e SL |
19 | } |
20 | ||
2b03887a FG |
21 | #[derive(Clone, Copy, Debug)] |
22 | pub enum InnerAttrForbiddenReason { | |
23 | InCodeBlock, | |
24 | AfterOuterDocComment { prev_doc_comment_span: Span }, | |
25 | AfterOuterAttribute { prev_outer_attr_sp: Span }, | |
26 | } | |
ba9703b0 | 27 | |
c295e0f8 XL |
28 | enum OuterAttributeType { |
29 | DocComment, | |
30 | DocBlockComment, | |
31 | Attribute, | |
32 | } | |
33 | ||
92a42be0 | 34 | impl<'a> Parser<'a> { |
e1599b0c | 35 | /// Parses attributes that appear before an item. |
6a06907d | 36 | pub(super) fn parse_outer_attributes(&mut self) -> PResult<'a, AttrWrapper> { |
f2b60f7d | 37 | let mut outer_attrs = ast::AttrVec::new(); |
5bcae85e | 38 | let mut just_parsed_doc_comment = false; |
cdc7bbd5 | 39 | let start_pos = self.token_cursor.num_next_calls; |
223e47cc | 40 | loop { |
29967ef6 | 41 | let attr = if self.check(&token::Pound) { |
04454e1e FG |
42 | let prev_outer_attr_sp = outer_attrs.last().map(|attr| attr.span); |
43 | ||
ba9703b0 | 44 | let inner_error_reason = if just_parsed_doc_comment { |
2b03887a FG |
45 | Some(InnerAttrForbiddenReason::AfterOuterDocComment { |
46 | prev_doc_comment_span: prev_outer_attr_sp.unwrap(), | |
47 | }) | |
ba9703b0 | 48 | } else { |
49aad941 FG |
49 | prev_outer_attr_sp.map(|prev_outer_attr_sp| { |
50 | InnerAttrForbiddenReason::AfterOuterAttribute { prev_outer_attr_sp } | |
51 | }) | |
ba9703b0 | 52 | }; |
2b03887a | 53 | let inner_parse_policy = InnerAttrPolicy::Forbidden(inner_error_reason); |
ba9703b0 | 54 | just_parsed_doc_comment = false; |
29967ef6 | 55 | Some(self.parse_attribute(inner_parse_policy)?) |
3dfed10e | 56 | } else if let token::DocComment(comment_kind, attr_style, data) = self.token.kind { |
29967ef6 | 57 | if attr_style != ast::AttrStyle::Outer { |
c295e0f8 XL |
58 | let span = self.token.span; |
59 | let mut err = self.sess.span_diagnostic.struct_span_err_with_code( | |
60 | span, | |
487cf647 | 61 | fluent::parse_inner_doc_comment_not_permitted, |
c295e0f8 XL |
62 | error_code!(E0753), |
63 | ); | |
64 | if let Some(replacement_span) = self.annotate_following_item_if_applicable( | |
65 | &mut err, | |
66 | span, | |
67 | match comment_kind { | |
68 | token::CommentKind::Line => OuterAttributeType::DocComment, | |
69 | token::CommentKind::Block => OuterAttributeType::DocBlockComment, | |
70 | }, | |
71 | ) { | |
9ffffee4 | 72 | err.note(fluent::parse_note); |
c295e0f8 XL |
73 | err.span_suggestion_verbose( |
74 | replacement_span, | |
9ffffee4 | 75 | fluent::parse_suggestion, |
923072b8 | 76 | "", |
c295e0f8 XL |
77 | rustc_errors::Applicability::MachineApplicable, |
78 | ); | |
79 | } | |
80 | err.emit(); | |
223e47cc | 81 | } |
ba9703b0 XL |
82 | self.bump(); |
83 | just_parsed_doc_comment = true; | |
136023e0 XL |
84 | // Always make an outer attribute - this allows us to recover from a misplaced |
85 | // inner attribute. | |
86 | Some(attr::mk_doc_comment( | |
f2b60f7d | 87 | &self.sess.attr_id_generator, |
136023e0 XL |
88 | comment_kind, |
89 | ast::AttrStyle::Outer, | |
90 | data, | |
91 | self.prev_token.span, | |
92 | )) | |
29967ef6 XL |
93 | } else { |
94 | None | |
95 | }; | |
96 | ||
97 | if let Some(attr) = attr { | |
04454e1e FG |
98 | if attr.style == ast::AttrStyle::Outer { |
99 | outer_attrs.push(attr); | |
100 | } | |
ba9703b0 XL |
101 | } else { |
102 | break; | |
223e47cc LB |
103 | } |
104 | } | |
f2b60f7d | 105 | Ok(AttrWrapper::new(outer_attrs, start_pos)) |
223e47cc LB |
106 | } |
107 | ||
e1599b0c | 108 | /// Matches `attribute = # ! [ meta_item ]`. |
29967ef6 | 109 | /// `inner_parse_policy` prescribes how to handle inner attributes. |
fc512014 XL |
110 | // Public for rustfmt usage. |
111 | pub fn parse_attribute( | |
e74abb32 | 112 | &mut self, |
2b03887a | 113 | inner_parse_policy: InnerAttrPolicy, |
e74abb32 | 114 | ) -> PResult<'a, ast::Attribute> { |
dfeec247 | 115 | debug!( |
29967ef6 | 116 | "parse_attribute: inner_parse_policy={:?} self.token={:?}", |
dfeec247 XL |
117 | inner_parse_policy, self.token |
118 | ); | |
ba9703b0 | 119 | let lo = self.token.span; |
c295e0f8 | 120 | // Attributes can't have attributes of their own [Editor's note: not with that attitude] |
6a06907d | 121 | self.collect_tokens_no_attrs(|this| { |
2b03887a | 122 | assert!(this.eat(&token::Pound), "parse_attribute called in non-attribute position"); |
1a4d82fc | 123 | |
2b03887a FG |
124 | let style = |
125 | if this.eat(&token::Not) { ast::AttrStyle::Inner } else { ast::AttrStyle::Outer }; | |
29967ef6 | 126 | |
2b03887a FG |
127 | this.expect(&token::OpenDelim(Delimiter::Bracket))?; |
128 | let item = this.parse_attr_item(false)?; | |
129 | this.expect(&token::CloseDelim(Delimiter::Bracket))?; | |
130 | let attr_sp = lo.to(this.prev_token.span); | |
29967ef6 | 131 | |
2b03887a FG |
132 | // Emit error if inner attribute is encountered and forbidden. |
133 | if style == ast::AttrStyle::Inner { | |
134 | this.error_on_forbidden_inner_attr(attr_sp, inner_parse_policy); | |
29967ef6 | 135 | } |
2b03887a FG |
136 | |
137 | Ok(attr::mk_attr_from_item(&self.sess.attr_id_generator, item, None, style, attr_sp)) | |
5869c6ff | 138 | }) |
ba9703b0 | 139 | } |
1a4d82fc | 140 | |
c295e0f8 XL |
141 | fn annotate_following_item_if_applicable( |
142 | &self, | |
5e7ed085 | 143 | err: &mut Diagnostic, |
c295e0f8 XL |
144 | span: Span, |
145 | attr_type: OuterAttributeType, | |
146 | ) -> Option<Span> { | |
5e7ed085 | 147 | let mut snapshot = self.create_snapshot_for_diagnostic(); |
c295e0f8 XL |
148 | let lo = span.lo() |
149 | + BytePos(match attr_type { | |
150 | OuterAttributeType::Attribute => 1, | |
151 | _ => 2, | |
152 | }); | |
153 | let hi = lo + BytePos(1); | |
154 | let replacement_span = span.with_lo(lo).with_hi(hi); | |
155 | if let OuterAttributeType::DocBlockComment | OuterAttributeType::DocComment = attr_type { | |
156 | snapshot.bump(); | |
157 | } | |
158 | loop { | |
159 | // skip any other attributes, we want the item | |
160 | if snapshot.token.kind == token::Pound { | |
5e7ed085 | 161 | if let Err(err) = snapshot.parse_attribute(InnerAttrPolicy::Permitted) { |
c295e0f8 XL |
162 | err.cancel(); |
163 | return Some(replacement_span); | |
164 | } | |
165 | } else { | |
166 | break; | |
167 | } | |
168 | } | |
169 | match snapshot.parse_item_common( | |
170 | AttrWrapper::empty(), | |
171 | true, | |
172 | false, | |
a2a8927a | 173 | FnParseMode { req_name: |_| true, req_body: true }, |
c295e0f8 XL |
174 | ForceCollect::No, |
175 | ) { | |
176 | Ok(Some(item)) => { | |
2b03887a FG |
177 | // FIXME(#100717) |
178 | err.set_arg("item", item.kind.descr()); | |
9ffffee4 | 179 | err.span_label(item.span, fluent::parse_label_does_not_annotate_this); |
c295e0f8 XL |
180 | err.span_suggestion_verbose( |
181 | replacement_span, | |
9ffffee4 | 182 | fluent::parse_sugg_change_inner_to_outer, |
923072b8 | 183 | match attr_type { |
c295e0f8 XL |
184 | OuterAttributeType::Attribute => "", |
185 | OuterAttributeType::DocBlockComment => "*", | |
186 | OuterAttributeType::DocComment => "/", | |
923072b8 | 187 | }, |
c295e0f8 XL |
188 | rustc_errors::Applicability::MachineApplicable, |
189 | ); | |
190 | return None; | |
191 | } | |
5e7ed085 | 192 | Err(item_err) => { |
c295e0f8 XL |
193 | item_err.cancel(); |
194 | } | |
195 | Ok(None) => {} | |
196 | } | |
197 | Some(replacement_span) | |
198 | } | |
199 | ||
2b03887a FG |
200 | pub(super) fn error_on_forbidden_inner_attr(&self, attr_sp: Span, policy: InnerAttrPolicy) { |
201 | if let InnerAttrPolicy::Forbidden(reason) = policy { | |
202 | let mut diag = match reason.as_ref().copied() { | |
203 | Some(InnerAttrForbiddenReason::AfterOuterDocComment { prev_doc_comment_span }) => { | |
204 | let mut diag = self.struct_span_err( | |
205 | attr_sp, | |
487cf647 | 206 | fluent::parse_inner_attr_not_permitted_after_outer_doc_comment, |
2b03887a | 207 | ); |
9ffffee4 FG |
208 | diag.span_label(attr_sp, fluent::parse_label_attr) |
209 | .span_label(prev_doc_comment_span, fluent::parse_label_prev_doc_comment); | |
2b03887a FG |
210 | diag |
211 | } | |
212 | Some(InnerAttrForbiddenReason::AfterOuterAttribute { prev_outer_attr_sp }) => { | |
213 | let mut diag = self.struct_span_err( | |
214 | attr_sp, | |
487cf647 | 215 | fluent::parse_inner_attr_not_permitted_after_outer_attr, |
2b03887a | 216 | ); |
9ffffee4 FG |
217 | diag.span_label(attr_sp, fluent::parse_label_attr) |
218 | .span_label(prev_outer_attr_sp, fluent::parse_label_prev_attr); | |
2b03887a FG |
219 | diag |
220 | } | |
221 | Some(InnerAttrForbiddenReason::InCodeBlock) | None => { | |
487cf647 | 222 | self.struct_span_err(attr_sp, fluent::parse_inner_attr_not_permitted) |
2b03887a FG |
223 | } |
224 | }; | |
1a4d82fc | 225 | |
487cf647 | 226 | diag.note(fluent::parse_inner_attr_explanation); |
c295e0f8 XL |
227 | if self |
228 | .annotate_following_item_if_applicable( | |
229 | &mut diag, | |
230 | attr_sp, | |
231 | OuterAttributeType::Attribute, | |
232 | ) | |
233 | .is_some() | |
234 | { | |
487cf647 | 235 | diag.note(fluent::parse_outer_attr_explanation); |
c295e0f8 XL |
236 | }; |
237 | diag.emit(); | |
ba9703b0 | 238 | } |
223e47cc LB |
239 | } |
240 | ||
e1599b0c | 241 | /// Parses an inner part of an attribute (the path and following tokens). |
b7449926 XL |
242 | /// The tokens must be either a delimited token stream, or empty token stream, |
243 | /// or the "legacy" key-value form. | |
e1599b0c XL |
244 | /// PATH `(` TOKEN_STREAM `)` |
245 | /// PATH `[` TOKEN_STREAM `]` | |
246 | /// PATH `{` TOKEN_STREAM `}` | |
247 | /// PATH | |
e74abb32 | 248 | /// PATH `=` UNSUFFIXED_LIT |
b7449926 | 249 | /// The delimiters or `=` are still put into the resulting token stream. |
29967ef6 | 250 | pub fn parse_attr_item(&mut self, capture_tokens: bool) -> PResult<'a, ast::AttrItem> { |
487cf647 FG |
251 | let item = match &self.token.kind { |
252 | token::Interpolated(nt) => match &**nt { | |
253 | Nonterminal::NtMeta(item) => Some(item.clone().into_inner()), | |
cc61c64b XL |
254 | _ => None, |
255 | }, | |
256 | _ => None, | |
257 | }; | |
e74abb32 | 258 | Ok(if let Some(item) = item { |
cc61c64b | 259 | self.bump(); |
e74abb32 | 260 | item |
cc61c64b | 261 | } else { |
29967ef6 XL |
262 | let do_parse = |this: &mut Self| { |
263 | let path = this.parse_path(PathStyle::Mod)?; | |
264 | let args = this.parse_attr_args()?; | |
265 | Ok(ast::AttrItem { path, args, tokens: None }) | |
266 | }; | |
6a06907d XL |
267 | // Attr items don't have attributes |
268 | if capture_tokens { self.collect_tokens_no_attrs(do_parse) } else { do_parse(self) }? | |
cc61c64b XL |
269 | }) |
270 | } | |
271 | ||
e1599b0c | 272 | /// Parses attributes that appear after the opening of an item. These should |
1a4d82fc | 273 | /// be preceded by an exclamation mark, but we accept and warn about one |
c34b1796 | 274 | /// terminated by a semicolon. |
e1599b0c XL |
275 | /// |
276 | /// Matches `inner_attrs*`. | |
f2b60f7d FG |
277 | pub(crate) fn parse_inner_attributes(&mut self) -> PResult<'a, ast::AttrVec> { |
278 | let mut attrs = ast::AttrVec::new(); | |
223e47cc | 279 | loop { |
cdc7bbd5 | 280 | let start_pos: u32 = self.token_cursor.num_next_calls.try_into().unwrap(); |
ba9703b0 | 281 | // Only try to parse if it is an inner attribute (has `!`). |
29967ef6 XL |
282 | let attr = if self.check(&token::Pound) && self.look_ahead(1, |t| t == &token::Not) { |
283 | Some(self.parse_attribute(InnerAttrPolicy::Permitted)?) | |
3dfed10e | 284 | } else if let token::DocComment(comment_kind, attr_style, data) = self.token.kind { |
29967ef6 | 285 | if attr_style == ast::AttrStyle::Inner { |
ba9703b0 | 286 | self.bump(); |
f2b60f7d FG |
287 | Some(attr::mk_doc_comment( |
288 | &self.sess.attr_id_generator, | |
289 | comment_kind, | |
290 | attr_style, | |
291 | data, | |
292 | self.prev_token.span, | |
293 | )) | |
ba9703b0 | 294 | } else { |
29967ef6 | 295 | None |
223e47cc | 296 | } |
29967ef6 XL |
297 | } else { |
298 | None | |
299 | }; | |
300 | if let Some(attr) = attr { | |
cdc7bbd5 XL |
301 | let end_pos: u32 = self.token_cursor.num_next_calls.try_into().unwrap(); |
302 | // If we are currently capturing tokens, mark the location of this inner attribute. | |
f2b60f7d | 303 | // If capturing ends up creating a `LazyAttrTokenStream`, we will include |
cdc7bbd5 | 304 | // this replace range with it, removing the inner attribute from the final |
f2b60f7d | 305 | // `AttrTokenStream`. Inner attributes are stored in the parsed AST note. |
cdc7bbd5 | 306 | // During macro expansion, they are selectively inserted back into the |
5e7ed085 | 307 | // token stream (the first inner attribute is removed each time we invoke the |
cdc7bbd5 XL |
308 | // corresponding macro). |
309 | let range = start_pos..end_pos; | |
310 | if let Capturing::Yes = self.capture_state.capturing { | |
311 | self.capture_state.inner_attr_ranges.insert(attr.id, (range, vec![])); | |
312 | } | |
29967ef6 | 313 | attrs.push(attr); |
ba9703b0 XL |
314 | } else { |
315 | break; | |
223e47cc LB |
316 | } |
317 | } | |
92a42be0 | 318 | Ok(attrs) |
223e47cc LB |
319 | } |
320 | ||
487cf647 FG |
321 | // Note: must be unsuffixed. |
322 | pub(crate) fn parse_unsuffixed_meta_item_lit(&mut self) -> PResult<'a, ast::MetaItemLit> { | |
323 | let lit = self.parse_meta_item_lit()?; | |
324 | debug!("checking if {:?} is unsuffixed", lit); | |
9e0c209e | 325 | |
e74abb32 | 326 | if !lit.kind.is_unsuffixed() { |
2b03887a | 327 | self.sess.emit_err(SuffixedLiteralInAttribute { span: lit.span }); |
9e0c209e SL |
328 | } |
329 | ||
330 | Ok(lit) | |
331 | } | |
332 | ||
e74abb32 | 333 | /// Parses `cfg_attr(pred, attr_item_list)` where `attr_item_list` is comma-delimited. |
60c5eb7d | 334 | pub fn parse_cfg_attr(&mut self) -> PResult<'a, (ast::MetaItem, Vec<(ast::AttrItem, Span)>)> { |
e74abb32 XL |
335 | let cfg_predicate = self.parse_meta_item()?; |
336 | self.expect(&token::Comma)?; | |
337 | ||
338 | // Presumably, the majority of the time there will only be one attr. | |
339 | let mut expanded_attrs = Vec::with_capacity(1); | |
60c5eb7d XL |
340 | while self.token.kind != token::Eof { |
341 | let lo = self.token.span; | |
29967ef6 | 342 | let item = self.parse_attr_item(true)?; |
74b04a01 | 343 | expanded_attrs.push((item, lo.to(self.prev_token.span))); |
60c5eb7d XL |
344 | if !self.eat(&token::Comma) { |
345 | break; | |
346 | } | |
e74abb32 XL |
347 | } |
348 | ||
e74abb32 XL |
349 | Ok((cfg_predicate, expanded_attrs)) |
350 | } | |
351 | ||
60c5eb7d | 352 | /// Matches `COMMASEP(meta_item_inner)`. |
9ffffee4 | 353 | pub(crate) fn parse_meta_seq_top(&mut self) -> PResult<'a, ThinVec<ast::NestedMetaItem>> { |
60c5eb7d | 354 | // Presumably, the majority of the time there will only be one attr. |
9ffffee4 | 355 | let mut nmis = ThinVec::with_capacity(1); |
60c5eb7d XL |
356 | while self.token.kind != token::Eof { |
357 | nmis.push(self.parse_meta_item_inner()?); | |
358 | if !self.eat(&token::Comma) { | |
359 | break; | |
360 | } | |
361 | } | |
362 | Ok(nmis) | |
363 | } | |
364 | ||
e1599b0c | 365 | /// Matches the following grammar (per RFC 1559). |
04454e1e FG |
366 | /// ```ebnf |
367 | /// meta_item : PATH ( '=' UNSUFFIXED_LIT | '(' meta_item_inner? ')' )? ; | |
368 | /// meta_item_inner : (meta_item | UNSUFFIXED_LIT) (',' meta_item_inner)? ; | |
369 | /// ``` | |
476ff2be | 370 | pub fn parse_meta_item(&mut self) -> PResult<'a, ast::MetaItem> { |
487cf647 FG |
371 | let nt_meta = match &self.token.kind { |
372 | token::Interpolated(nt) => match &**nt { | |
373 | token::NtMeta(e) => Some(e.clone()), | |
c30ab7b3 SL |
374 | _ => None, |
375 | }, | |
7453a54e | 376 | _ => None, |
1a4d82fc JJ |
377 | }; |
378 | ||
e74abb32 XL |
379 | if let Some(item) = nt_meta { |
380 | return match item.meta(item.path.span) { | |
381 | Some(meta) => { | |
382 | self.bump(); | |
383 | Ok(meta) | |
384 | } | |
385 | None => self.unexpected(), | |
dfeec247 | 386 | }; |
1a4d82fc JJ |
387 | } |
388 | ||
dc9dc135 | 389 | let lo = self.token.span; |
532ac7d7 | 390 | let path = self.parse_path(PathStyle::Mod)?; |
e74abb32 | 391 | let kind = self.parse_meta_item_kind()?; |
74b04a01 | 392 | let span = lo.to(self.prev_token.span); |
e74abb32 | 393 | Ok(ast::MetaItem { path, kind, span }) |
cc61c64b XL |
394 | } |
395 | ||
923072b8 | 396 | pub(crate) fn parse_meta_item_kind(&mut self) -> PResult<'a, ast::MetaItemKind> { |
cc61c64b | 397 | Ok(if self.eat(&token::Eq) { |
487cf647 | 398 | ast::MetaItemKind::NameValue(self.parse_unsuffixed_meta_item_lit()?) |
04454e1e | 399 | } else if self.check(&token::OpenDelim(Delimiter::Parenthesis)) { |
dfeec247 XL |
400 | // Matches `meta_seq = ( COMMASEP(meta_item_inner) )`. |
401 | let (list, _) = self.parse_paren_comma_seq(|p| p.parse_meta_item_inner())?; | |
402 | ast::MetaItemKind::List(list) | |
476ff2be SL |
403 | } else { |
404 | ast::MetaItemKind::Word | |
cc61c64b | 405 | }) |
223e47cc LB |
406 | } |
407 | ||
e1599b0c | 408 | /// Matches `meta_item_inner : (meta_item | UNSUFFIXED_LIT) ;`. |
9e0c209e | 409 | fn parse_meta_item_inner(&mut self) -> PResult<'a, ast::NestedMetaItem> { |
487cf647 FG |
410 | match self.parse_unsuffixed_meta_item_lit() { |
411 | Ok(lit) => return Ok(ast::NestedMetaItem::Lit(lit)), | |
5e7ed085 | 412 | Err(err) => err.cancel(), |
9e0c209e SL |
413 | } |
414 | ||
415 | match self.parse_meta_item() { | |
dfeec247 | 416 | Ok(mi) => return Ok(ast::NestedMetaItem::MetaItem(mi)), |
5e7ed085 | 417 | Err(err) => err.cancel(), |
9e0c209e SL |
418 | } |
419 | ||
2b03887a FG |
420 | Err(InvalidMetaItem { span: self.token.span, token: self.token.clone() } |
421 | .into_diagnostic(&self.sess.span_diagnostic)) | |
223e47cc LB |
422 | } |
423 | } | |
29967ef6 XL |
424 | |
425 | pub fn maybe_needs_tokens(attrs: &[ast::Attribute]) -> bool { | |
6a06907d | 426 | // One of the attributes may either itself be a macro, |
fc512014 | 427 | // or expand to macro attributes (`cfg_attr`). |
29967ef6 | 428 | attrs.iter().any(|attr| { |
cdc7bbd5 XL |
429 | if attr.is_doc_comment() { |
430 | return false; | |
431 | } | |
fc512014 | 432 | attr.ident().map_or(true, |ident| { |
6a06907d | 433 | ident.name == sym::cfg_attr || !rustc_feature::is_builtin_attr_name(ident.name) |
fc512014 | 434 | }) |
29967ef6 XL |
435 | }) |
436 | } |