aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/hir_def/src/data.rs25
-rw-r--r--crates/hir_def/src/item_tree.rs24
-rw-r--r--crates/hir_def/src/item_tree/lower.rs41
-rw-r--r--crates/hir_ty/src/diagnostics/expr.rs34
-rw-r--r--crates/parser/src/grammar/params.rs29
-rw-r--r--crates/syntax/test_data/parser/inline/ok/0139_param_outer_arg.rast20
-rw-r--r--crates/syntax/test_data/parser/ok/0051_parameter_attrs.rast78
7 files changed, 172 insertions, 79 deletions
diff --git a/crates/hir_def/src/data.rs b/crates/hir_def/src/data.rs
index 1a27f7bf2..e976e419e 100644
--- a/crates/hir_def/src/data.rs
+++ b/crates/hir_def/src/data.rs
@@ -9,7 +9,7 @@ use crate::{
9 attr::Attrs, 9 attr::Attrs,
10 body::Expander, 10 body::Expander,
11 db::DefDatabase, 11 db::DefDatabase,
12 item_tree::{AssocItem, FunctionQualifier, ItemTreeId, ModItem}, 12 item_tree::{AssocItem, FunctionQualifier, ItemTreeId, ModItem, Param},
13 type_ref::{TypeBound, TypeRef}, 13 type_ref::{TypeBound, TypeRef},
14 visibility::RawVisibility, 14 visibility::RawVisibility,
15 AssocContainerId, AssocItemId, ConstId, ConstLoc, FunctionId, FunctionLoc, HasModule, ImplId, 15 AssocContainerId, AssocItemId, ConstId, ConstLoc, FunctionId, FunctionLoc, HasModule, ImplId,
@@ -36,19 +36,38 @@ impl FunctionData {
36 pub(crate) fn fn_data_query(db: &dyn DefDatabase, func: FunctionId) -> Arc<FunctionData> { 36 pub(crate) fn fn_data_query(db: &dyn DefDatabase, func: FunctionId) -> Arc<FunctionData> {
37 let loc = func.lookup(db); 37 let loc = func.lookup(db);
38 let krate = loc.container.module(db).krate; 38 let krate = loc.container.module(db).krate;
39 let crate_graph = db.crate_graph();
40 let cfg_options = &crate_graph[krate].cfg_options;
39 let item_tree = db.item_tree(loc.id.file_id); 41 let item_tree = db.item_tree(loc.id.file_id);
40 let func = &item_tree[loc.id.value]; 42 let func = &item_tree[loc.id.value];
41 43
44 let enabled_params = func
45 .params
46 .clone()
47 .filter(|&param| item_tree.attrs(db, krate, param.into()).is_cfg_enabled(cfg_options));
48
49 // If last cfg-enabled param is a `...` param, it's a varargs function.
50 let is_varargs = enabled_params
51 .clone()
52 .next_back()
53 .map_or(false, |param| matches!(item_tree[param], Param::Varargs));
54
42 Arc::new(FunctionData { 55 Arc::new(FunctionData {
43 name: func.name.clone(), 56 name: func.name.clone(),
44 params: func.params.iter().map(|id| item_tree[*id].clone()).collect(), 57 params: enabled_params
58 .clone()
59 .filter_map(|id| match &item_tree[id] {
60 Param::Normal(ty) => Some(item_tree[*ty].clone()),
61 Param::Varargs => None,
62 })
63 .collect(),
45 ret_type: item_tree[func.ret_type].clone(), 64 ret_type: item_tree[func.ret_type].clone(),
46 attrs: item_tree.attrs(db, krate, ModItem::from(loc.id.value).into()), 65 attrs: item_tree.attrs(db, krate, ModItem::from(loc.id.value).into()),
47 has_self_param: func.has_self_param, 66 has_self_param: func.has_self_param,
48 has_body: func.has_body, 67 has_body: func.has_body,
49 qualifier: func.qualifier.clone(), 68 qualifier: func.qualifier.clone(),
50 is_in_extern_block: func.is_in_extern_block, 69 is_in_extern_block: func.is_in_extern_block,
51 is_varargs: func.is_varargs, 70 is_varargs,
52 visibility: item_tree[func.visibility].clone(), 71 visibility: item_tree[func.visibility].clone(),
53 }) 72 })
54 } 73 }
diff --git a/crates/hir_def/src/item_tree.rs b/crates/hir_def/src/item_tree.rs
index 7bb22c4c4..90df3d929 100644
--- a/crates/hir_def/src/item_tree.rs
+++ b/crates/hir_def/src/item_tree.rs
@@ -134,6 +134,7 @@ impl ItemTree {
134 imports, 134 imports,
135 extern_crates, 135 extern_crates,
136 functions, 136 functions,
137 params,
137 structs, 138 structs,
138 fields, 139 fields,
139 unions, 140 unions,
@@ -157,6 +158,7 @@ impl ItemTree {
157 imports.shrink_to_fit(); 158 imports.shrink_to_fit();
158 extern_crates.shrink_to_fit(); 159 extern_crates.shrink_to_fit();
159 functions.shrink_to_fit(); 160 functions.shrink_to_fit();
161 params.shrink_to_fit();
160 structs.shrink_to_fit(); 162 structs.shrink_to_fit();
161 fields.shrink_to_fit(); 163 fields.shrink_to_fit();
162 unions.shrink_to_fit(); 164 unions.shrink_to_fit();
@@ -303,6 +305,7 @@ struct ItemTreeData {
303 imports: Arena<Import>, 305 imports: Arena<Import>,
304 extern_crates: Arena<ExternCrate>, 306 extern_crates: Arena<ExternCrate>,
305 functions: Arena<Function>, 307 functions: Arena<Function>,
308 params: Arena<Param>,
306 structs: Arena<Struct>, 309 structs: Arena<Struct>,
307 fields: Arena<Field>, 310 fields: Arena<Field>,
308 unions: Arena<Union>, 311 unions: Arena<Union>,
@@ -334,6 +337,7 @@ pub enum AttrOwner {
334 337
335 Variant(Idx<Variant>), 338 Variant(Idx<Variant>),
336 Field(Idx<Field>), 339 Field(Idx<Field>),
340 Param(Idx<Param>),
337} 341}
338 342
339macro_rules! from_attrs { 343macro_rules! from_attrs {
@@ -348,7 +352,7 @@ macro_rules! from_attrs {
348 }; 352 };
349} 353}
350 354
351from_attrs!(ModItem(ModItem), Variant(Idx<Variant>), Field(Idx<Field>)); 355from_attrs!(ModItem(ModItem), Variant(Idx<Variant>), Field(Idx<Field>), Param(Idx<Param>));
352 356
353/// Trait implemented by all item nodes in the item tree. 357/// Trait implemented by all item nodes in the item tree.
354pub trait ItemTreeNode: Clone { 358pub trait ItemTreeNode: Clone {
@@ -484,7 +488,7 @@ macro_rules! impl_index {
484 }; 488 };
485} 489}
486 490
487impl_index!(fields: Field, variants: Variant); 491impl_index!(fields: Field, variants: Variant, params: Param);
488 492
489impl Index<RawVisibilityId> for ItemTree { 493impl Index<RawVisibilityId> for ItemTree {
490 type Output = RawVisibility; 494 type Output = RawVisibility;
@@ -560,12 +564,17 @@ pub struct Function {
560 /// Whether the function is located in an `extern` block (*not* whether it is an 564 /// Whether the function is located in an `extern` block (*not* whether it is an
561 /// `extern "abi" fn`). 565 /// `extern "abi" fn`).
562 pub is_in_extern_block: bool, 566 pub is_in_extern_block: bool,
563 pub params: Box<[Idx<TypeRef>]>, 567 pub params: IdRange<Param>,
564 pub is_varargs: bool,
565 pub ret_type: Idx<TypeRef>, 568 pub ret_type: Idx<TypeRef>,
566 pub ast_id: FileAstId<ast::Fn>, 569 pub ast_id: FileAstId<ast::Fn>,
567} 570}
568 571
572#[derive(Debug, Clone, Eq, PartialEq)]
573pub enum Param {
574 Normal(Idx<TypeRef>),
575 Varargs,
576}
577
569#[derive(Debug, Clone, PartialEq, Eq)] 578#[derive(Debug, Clone, PartialEq, Eq)]
570pub struct FunctionQualifier { 579pub struct FunctionQualifier {
571 pub is_default: bool, 580 pub is_default: bool,
@@ -796,6 +805,7 @@ pub struct Variant {
796 pub fields: Fields, 805 pub fields: Fields,
797} 806}
798 807
808/// A range of densely allocated ItemTree IDs.
799pub struct IdRange<T> { 809pub struct IdRange<T> {
800 range: Range<u32>, 810 range: Range<u32>,
801 _p: PhantomData<T>, 811 _p: PhantomData<T>,
@@ -814,6 +824,12 @@ impl<T> Iterator for IdRange<T> {
814 } 824 }
815} 825}
816 826
827impl<T> DoubleEndedIterator for IdRange<T> {
828 fn next_back(&mut self) -> Option<Self::Item> {
829 self.range.next_back().map(|raw| Idx::from_raw(raw.into()))
830 }
831}
832
817impl<T> fmt::Debug for IdRange<T> { 833impl<T> fmt::Debug for IdRange<T> {
818 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 834 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
819 f.debug_tuple(&format!("IdRange::<{}>", type_name::<T>())).field(&self.range).finish() 835 f.debug_tuple(&format!("IdRange::<{}>", type_name::<T>())).field(&self.range).finish()
diff --git a/crates/hir_def/src/item_tree/lower.rs b/crates/hir_def/src/item_tree/lower.rs
index 7e91b991d..3f558edd8 100644
--- a/crates/hir_def/src/item_tree/lower.rs
+++ b/crates/hir_def/src/item_tree/lower.rs
@@ -333,8 +333,8 @@ impl Ctx {
333 let visibility = self.lower_visibility(func); 333 let visibility = self.lower_visibility(func);
334 let name = func.name()?.as_name(); 334 let name = func.name()?.as_name();
335 335
336 let mut params = Vec::new();
337 let mut has_self_param = false; 336 let mut has_self_param = false;
337 let start_param = self.next_param_idx();
338 if let Some(param_list) = func.param_list() { 338 if let Some(param_list) = func.param_list() {
339 if let Some(self_param) = param_list.self_param() { 339 if let Some(self_param) = param_list.self_param() {
340 let self_type = match self_param.ty() { 340 let self_type = match self_param.ty() {
@@ -356,22 +356,25 @@ impl Ctx {
356 } 356 }
357 } 357 }
358 }; 358 };
359 params.push(self_type); 359 let ty = self.data().type_refs.intern(self_type);
360 let idx = self.data().params.alloc(Param::Normal(ty));
361 self.add_attrs(idx.into(), RawAttrs::new(&self_param, &self.hygiene));
360 has_self_param = true; 362 has_self_param = true;
361 } 363 }
362 for param in param_list.params() { 364 for param in param_list.params() {
363 let type_ref = TypeRef::from_ast_opt(&self.body_ctx, param.ty()); 365 let idx = match param.dotdotdot_token() {
364 params.push(type_ref); 366 Some(_) => self.data().params.alloc(Param::Varargs),
365 } 367 None => {
366 } 368 let type_ref = TypeRef::from_ast_opt(&self.body_ctx, param.ty());
367 let params = params.into_iter().map(|param| self.data().type_refs.intern(param)).collect(); 369 let ty = self.data().type_refs.intern(type_ref);
368 370 self.data().params.alloc(Param::Normal(ty))
369 let mut is_varargs = false; 371 }
370 if let Some(params) = func.param_list() { 372 };
371 if let Some(last) = params.params().last() { 373 self.add_attrs(idx.into(), RawAttrs::new(&param, &self.hygiene));
372 is_varargs = last.dotdotdot_token().is_some();
373 } 374 }
374 } 375 }
376 let end_param = self.next_param_idx();
377 let params = IdRange::new(start_param..end_param);
375 378
376 let ret_type = match func.ret_type().and_then(|rt| rt.ty()) { 379 let ret_type = match func.ret_type().and_then(|rt| rt.ty()) {
377 Some(type_ref) => TypeRef::from_ast(&self.body_ctx, type_ref), 380 Some(type_ref) => TypeRef::from_ast(&self.body_ctx, type_ref),
@@ -419,7 +422,6 @@ impl Ctx {
419 qualifier, 422 qualifier,
420 is_in_extern_block: false, 423 is_in_extern_block: false,
421 params, 424 params,
422 is_varargs,
423 ret_type, 425 ret_type,
424 ast_id, 426 ast_id,
425 }; 427 };
@@ -682,9 +684,11 @@ impl Ctx {
682 GenericsOwner::Function(func) => { 684 GenericsOwner::Function(func) => {
683 generics.fill(&self.body_ctx, sm, node); 685 generics.fill(&self.body_ctx, sm, node);
684 // lower `impl Trait` in arguments 686 // lower `impl Trait` in arguments
685 for param in &*func.params { 687 for id in func.params.clone() {
686 let param = self.data().type_refs.lookup(*param); 688 if let Param::Normal(ty) = self.data().params[id] {
687 generics.fill_implicit_impl_trait_args(param); 689 let ty = self.data().type_refs.lookup(ty);
690 generics.fill_implicit_impl_trait_args(ty);
691 }
688 } 692 }
689 } 693 }
690 GenericsOwner::Struct 694 GenericsOwner::Struct
@@ -769,6 +773,11 @@ impl Ctx {
769 self.tree.data.as_ref().map_or(0, |data| data.variants.len() as u32), 773 self.tree.data.as_ref().map_or(0, |data| data.variants.len() as u32),
770 )) 774 ))
771 } 775 }
776 fn next_param_idx(&self) -> Idx<Param> {
777 Idx::from_raw(RawIdx::from(
778 self.tree.data.as_ref().map_or(0, |data| data.params.len() as u32),
779 ))
780 }
772} 781}
773 782
774fn desugar_future_path(orig: TypeRef) -> Path { 783fn desugar_future_path(orig: TypeRef) -> Path {
diff --git a/crates/hir_ty/src/diagnostics/expr.rs b/crates/hir_ty/src/diagnostics/expr.rs
index 50dc40335..3909ad354 100644
--- a/crates/hir_ty/src/diagnostics/expr.rs
+++ b/crates/hir_ty/src/diagnostics/expr.rs
@@ -713,4 +713,38 @@ fn main() {
713 "#, 713 "#,
714 ); 714 );
715 } 715 }
716
717 #[test]
718 fn cfgd_out_fn_params() {
719 check_diagnostics(
720 r#"
721fn foo(#[cfg(NEVER)] x: ()) {}
722
723struct S;
724
725impl S {
726 fn method(#[cfg(NEVER)] self) {}
727 fn method2(#[cfg(NEVER)] self, arg: u8) {}
728 fn method3(self, #[cfg(NEVER)] arg: u8) {}
729}
730
731extern "C" {
732 fn fixed(fixed: u8, #[cfg(NEVER)] ...);
733 fn varargs(#[cfg(not(NEVER))] ...);
734}
735
736fn main() {
737 foo();
738 S::method();
739 S::method2(0);
740 S::method3(S);
741 S.method3();
742 unsafe {
743 fixed(0);
744 varargs(1, 2, 3);
745 }
746}
747 "#,
748 )
749 }
716} 750}
diff --git a/crates/parser/src/grammar/params.rs b/crates/parser/src/grammar/params.rs
index e313f6fb7..9e2f02d43 100644
--- a/crates/parser/src/grammar/params.rs
+++ b/crates/parser/src/grammar/params.rs
@@ -41,22 +41,32 @@ fn list_(p: &mut Parser, flavor: Flavor) {
41 FnDef | FnTrait | FnPointer => (T!['('], T![')']), 41 FnDef | FnTrait | FnPointer => (T!['('], T![')']),
42 }; 42 };
43 43
44 let m = p.start(); 44 let list_marker = p.start();
45 p.bump(bra); 45 p.bump(bra);
46 46
47 let mut param_marker = None;
47 if let FnDef = flavor { 48 if let FnDef = flavor {
48 // test self_param_outer_attr 49 // test self_param_outer_attr
49 // fn f(#[must_use] self) {} 50 // fn f(#[must_use] self) {}
50 let m = p.start(); 51 let m = p.start();
51 attributes::outer_attrs(p); 52 attributes::outer_attrs(p);
52 opt_self_param(p, m); 53 match opt_self_param(p, m) {
54 Ok(()) => {}
55 Err(m) => param_marker = Some(m),
56 }
53 } 57 }
54 58
55 while !p.at(EOF) && !p.at(ket) { 59 while !p.at(EOF) && !p.at(ket) {
56 // test param_outer_arg 60 // test param_outer_arg
57 // fn f(#[attr1] pat: Type) {} 61 // fn f(#[attr1] pat: Type) {}
58 let m = p.start(); 62 let m = match param_marker.take() {
59 attributes::outer_attrs(p); 63 Some(m) => m,
64 None => {
65 let m = p.start();
66 attributes::outer_attrs(p);
67 m
68 }
69 };
60 70
61 if !p.at_ts(PARAM_FIRST) { 71 if !p.at_ts(PARAM_FIRST) {
62 p.error("expected value parameter"); 72 p.error("expected value parameter");
@@ -72,8 +82,12 @@ fn list_(p: &mut Parser, flavor: Flavor) {
72 } 82 }
73 } 83 }
74 84
85 if let Some(m) = param_marker {
86 m.abandon(p);
87 }
88
75 p.expect(ket); 89 p.expect(ket);
76 m.complete(p, PARAM_LIST); 90 list_marker.complete(p, PARAM_LIST);
77} 91}
78 92
79const PARAM_FIRST: TokenSet = patterns::PATTERN_FIRST.union(types::TYPE_FIRST); 93const PARAM_FIRST: TokenSet = patterns::PATTERN_FIRST.union(types::TYPE_FIRST);
@@ -153,7 +167,7 @@ fn variadic_param(p: &mut Parser) -> bool {
153// fn d(&'a mut self, x: i32) {} 167// fn d(&'a mut self, x: i32) {}
154// fn e(mut self) {} 168// fn e(mut self) {}
155// } 169// }
156fn opt_self_param(p: &mut Parser, m: Marker) { 170fn opt_self_param(p: &mut Parser, m: Marker) -> Result<(), Marker> {
157 if p.at(T![self]) || p.at(T![mut]) && p.nth(1) == T![self] { 171 if p.at(T![self]) || p.at(T![mut]) && p.nth(1) == T![self] {
158 p.eat(T![mut]); 172 p.eat(T![mut]);
159 self_as_name(p); 173 self_as_name(p);
@@ -176,7 +190,7 @@ fn opt_self_param(p: &mut Parser, m: Marker) {
176 | (T![&], LIFETIME_IDENT, T![self], _) 190 | (T![&], LIFETIME_IDENT, T![self], _)
177 | (T![&], LIFETIME_IDENT, T![mut], T![self]) 191 | (T![&], LIFETIME_IDENT, T![mut], T![self])
178 ) { 192 ) {
179 return m.abandon(p); 193 return Err(m);
180 } 194 }
181 p.bump(T![&]); 195 p.bump(T![&]);
182 if p.at(LIFETIME_IDENT) { 196 if p.at(LIFETIME_IDENT) {
@@ -189,6 +203,7 @@ fn opt_self_param(p: &mut Parser, m: Marker) {
189 if !p.at(T![')']) { 203 if !p.at(T![')']) {
190 p.expect(T![,]); 204 p.expect(T![,]);
191 } 205 }
206 Ok(())
192} 207}
193 208
194fn self_as_name(p: &mut Parser) { 209fn self_as_name(p: &mut Parser) {
diff --git a/crates/syntax/test_data/parser/inline/ok/0139_param_outer_arg.rast b/crates/syntax/test_data/parser/inline/ok/0139_param_outer_arg.rast
index 495e4c51b..a84088bf3 100644
--- a/crates/syntax/test_data/parser/inline/ok/0139_param_outer_arg.rast
+++ b/crates/syntax/test_data/parser/inline/ok/0139_param_outer_arg.rast
@@ -6,16 +6,16 @@ [email protected]
6 [email protected] "f" 6 [email protected] "f"
7 [email protected] 7 [email protected]
8 [email protected] "(" 8 [email protected] "("
9 ATTR@5..13 9 PARAM@5..23
10 POUND@5..6 "#" 10 ATTR@5..13
11 L_BRACK@6..7 "[" 11 POUND@5..6 "#"
12 PATH@7..12 12 L_BRACK@6..7 "["
13 PATH_SEGMENT@7..12 13 [email protected]
14 NAME_REF@7..12 14 PATH_SEGMENT@7..12
15 IDENT@7..12 "attr1" 15 NAME_REF@7..12
16 R_BRACK@12..13 "]" 16 IDENT@7..12 "attr1"
17 WHITESPACE@13..14 " " 17 R_BRACK@12..13 "]"
18 PARAM@14..23 18 WHITESPACE@13..14 " "
19 [email protected] 19 [email protected]
20 [email protected] 20 [email protected]
21 [email protected] "pat" 21 [email protected] "pat"
diff --git a/crates/syntax/test_data/parser/ok/0051_parameter_attrs.rast b/crates/syntax/test_data/parser/ok/0051_parameter_attrs.rast
index e10521d85..88470c41c 100644
--- a/crates/syntax/test_data/parser/ok/0051_parameter_attrs.rast
+++ b/crates/syntax/test_data/parser/ok/0051_parameter_attrs.rast
@@ -6,25 +6,25 @@ [email protected]
6 [email protected] "g1" 6 [email protected] "g1"
7 [email protected] 7 [email protected]
8 [email protected] "(" 8 [email protected] "("
9 ATTR@6..14 9 PARAM@6..33
10 POUND@6..7 "#" 10 ATTR@6..14
11 L_BRACK@7..8 "[" 11 POUND@6..7 "#"
12 PATH@8..13 12 L_BRACK@7..8 "["
13 PATH_SEGMENT@8..13 13 [email protected]
14 NAME_REF@8..13 14 PATH_SEGMENT@8..13
15 IDENT@8..13 "attr1" 15 NAME_REF@8..13
16 R_BRACK@13..14 "]" 16 IDENT@8..13 "attr1"
17 WHITESPACE@14..15 " " 17 R_BRACK@13..14 "]"
18 ATTR@15..23 18 WHITESPACE@14..15 " "
19 POUND@15..16 "#" 19 ATTR@15..23
20 L_BRACK@16..17 "[" 20 POUND@15..16 "#"
21 PATH@17..22 21 L_BRACK@16..17 "["
22 PATH_SEGMENT@17..22 22 [email protected]
23 NAME_REF@17..22 23 PATH_SEGMENT@17..22
24 IDENT@17..22 "attr2" 24 NAME_REF@17..22
25 R_BRACK@22..23 "]" 25 IDENT@17..22 "attr2"
26 WHITESPACE@23..24 " " 26 R_BRACK@22..23 "]"
27 PARAM@24..33 27 WHITESPACE@23..24 " "
28 [email protected] 28 [email protected]
29 [email protected] 29 [email protected]
30 [email protected] "pat" 30 [email protected] "pat"
@@ -48,16 +48,16 @@ [email protected]
48 [email protected] "g2" 48 [email protected] "g2"
49 [email protected] 49 [email protected]
50 [email protected] "(" 50 [email protected] "("
51 ATT[email protected]2 51 PARAM@44..58
52 POUND@44..45 "#" 52 ATTR@44..52
53 L_BRACK@45..46 "[" 53 POUND@44..45 "#"
54 PATH@46..51 54 L_BRACK@45..46 "["
55 PATH_SEGMENT@46..51 55 [email protected]
56 NAME_REF@46..51 56 PATH_SEGMENT@46..51
57 IDENT@46..51 "attr1" 57 NAME_REF@46..51
58 R_BRACK@51..52 "]" 58 IDENT@46..51 "attr1"
59 WHITESPACE@52..53 " " 59 R_BRACK@51..52 "]"
60 PARAM@53..58 60 WHITESPACE@52..53 " "
61 [email protected] 61 [email protected]
62 [email protected] 62 [email protected]
63 [email protected] "x" 63 [email protected] "x"
@@ -203,16 +203,16 @@ [email protected]
203 [email protected] "bar" 203 [email protected] "bar"
204 [email protected] 204 [email protected]
205 [email protected] "(" 205 [email protected] "("
206 ATT[email protected]04 206 PARAM@197..211
207 POUND@197..198 "#" 207 ATTR@197..204
208 L_BRACK@198..199 "[" 208 POUND@197..198 "#"
209 PATH@199..203 209 L_BRACK@198..199 "["
210 PATH_SEGMENT@199..203 210 [email protected]
211 NAME_REF@199..203 211 PATH_SEGMENT@199..203
212 IDENT@199..203 "attr" 212 NAME_REF@199..203
213 R_BRACK@203..204 "]" 213 IDENT@199..203 "attr"
214 WHITESPACE@204..205 " " 214 R_BRACK@203..204 "]"
215 PARAM@205..211 215 WHITESPACE@204..205 " "
216 [email protected] 216 [email protected]
217 [email protected] "_" 217 [email protected] "_"
218 [email protected] ":" 218 [email protected] ":"