aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
authorbors[bot] <bors[bot]@users.noreply.github.com>2019-03-19 10:36:17 +0000
committerbors[bot] <bors[bot]@users.noreply.github.com>2019-03-19 10:36:17 +0000
commit5b6ad0971c050981deb10c56ad2634293f104228 (patch)
tree7f76ce1da6ed9e245f5b4ef14257418c66f59577 /crates
parent91576afc7e64f11dde2bed14b578e4914d253a6a (diff)
parent4cf179c089aeed381cd67bcd265e76a27f11facd (diff)
Merge #996
996: Allow attributes on top level expressions r=matklad a=pcpthm This PR modifies parser to allow outer attributes on top level expression. Here, top level expression means either - Expression statement e.g. `foo();` - Last expression in a block without semicolon `bar()` in `{ foo(); bar() }`. Except for binary operation expressions and `if` expressions, which are errors (feature gated) in rustc. Attributes on inner expressions like `foo(#[a] 1)` are not implemented. I first tried to implement this by passing `Maker` to expression parsers. However, this implementation couldn't parse `#[attr] foo()` correctly as `CallExpr(Attr(..), PathExpr(..), ArgList(..))` and instead parsed incorrectly as `CallExpr(PathExpr(Attr(..), ..), ArgList(..))` due to the way left recursion is handled. In the end, I introduce `undo_completion` method. Which is not the suggested approach, but it seems not very bad. Fix #759. Co-authored-by: pcpthm <[email protected]>
Diffstat (limited to 'crates')
-rw-r--r--crates/ra_parser/src/event.rs4
-rw-r--r--crates/ra_parser/src/grammar/expressions.rs94
-rw-r--r--crates/ra_parser/src/grammar/expressions/atom.rs4
-rw-r--r--crates/ra_parser/src/parser.rs34
-rw-r--r--crates/ra_syntax/tests/data/parser/inline/err/0009_attr_on_expr_not_allowed.rs4
-rw-r--r--crates/ra_syntax/tests/data/parser/inline/err/0009_attr_on_expr_not_allowed.txt55
-rw-r--r--crates/ra_syntax/tests/data/parser/inline/ok/0126_attr_on_expr_stmt.rs6
-rw-r--r--crates/ra_syntax/tests/data/parser/inline/ok/0126_attr_on_expr_stmt.txt88
-rw-r--r--crates/ra_syntax/tests/data/parser/inline/ok/0127_attr_on_last_expr_in_block.rs4
-rw-r--r--crates/ra_syntax/tests/data/parser/inline/ok/0127_attr_on_last_expr_in_block.txt54
10 files changed, 305 insertions, 42 deletions
diff --git a/crates/ra_parser/src/event.rs b/crates/ra_parser/src/event.rs
index 6361d5d86..c1773e8e0 100644
--- a/crates/ra_parser/src/event.rs
+++ b/crates/ra_parser/src/event.rs
@@ -105,7 +105,9 @@ pub(super) fn process(sink: &mut dyn TreeSink, mut events: Vec<Event>) {
105 // append `A`'s forward_parent `B` 105 // append `A`'s forward_parent `B`
106 fp = match mem::replace(&mut events[idx], Event::tombstone()) { 106 fp = match mem::replace(&mut events[idx], Event::tombstone()) {
107 Event::Start { kind, forward_parent } => { 107 Event::Start { kind, forward_parent } => {
108 forward_parents.push(kind); 108 if kind != TOMBSTONE {
109 forward_parents.push(kind);
110 }
109 forward_parent 111 forward_parent
110 } 112 }
111 _ => unreachable!(), 113 _ => unreachable!(),
diff --git a/crates/ra_parser/src/grammar/expressions.rs b/crates/ra_parser/src/grammar/expressions.rs
index 83812e938..73e1acd5a 100644
--- a/crates/ra_parser/src/grammar/expressions.rs
+++ b/crates/ra_parser/src/grammar/expressions.rs
@@ -8,10 +8,10 @@ const EXPR_FIRST: TokenSet = LHS_FIRST;
8 8
9pub(super) fn expr(p: &mut Parser) -> BlockLike { 9pub(super) fn expr(p: &mut Parser) -> BlockLike {
10 let r = Restrictions { forbid_structs: false, prefer_stmt: false }; 10 let r = Restrictions { forbid_structs: false, prefer_stmt: false };
11 expr_bp(p, r, 1) 11 expr_bp(p, r, 1).1
12} 12}
13 13
14pub(super) fn expr_stmt(p: &mut Parser) -> BlockLike { 14pub(super) fn expr_stmt(p: &mut Parser) -> (Option<CompletedMarker>, BlockLike) {
15 let r = Restrictions { forbid_structs: false, prefer_stmt: true }; 15 let r = Restrictions { forbid_structs: false, prefer_stmt: true };
16 expr_bp(p, r, 1) 16 expr_bp(p, r, 1)
17} 17}
@@ -38,6 +38,13 @@ pub(crate) fn block(p: &mut Parser) {
38 m.complete(p, BLOCK); 38 m.complete(p, BLOCK);
39} 39}
40 40
41fn is_expr_stmt_attr_allowed(kind: SyntaxKind) -> bool {
42 match kind {
43 BIN_EXPR | RANGE_EXPR | IF_EXPR => false,
44 _ => true,
45 }
46}
47
41pub(crate) fn expr_block_contents(p: &mut Parser) { 48pub(crate) fn expr_block_contents(p: &mut Parser) {
42 // This is checked by a validator 49 // This is checked by a validator
43 attributes::inner_attributes(p); 50 attributes::inner_attributes(p);
@@ -55,6 +62,13 @@ pub(crate) fn expr_block_contents(p: &mut Parser) {
55 // test block_items 62 // test block_items
56 // fn a() { fn b() {} } 63 // fn a() { fn b() {} }
57 let m = p.start(); 64 let m = p.start();
65 // test attr_on_expr_stmt
66 // fn foo() {
67 // #[A] foo();
68 // #[B] bar!{}
69 // #[C] #[D] {}
70 // #[D] return ();
71 // }
58 let has_attrs = p.at(POUND); 72 let has_attrs = p.at(POUND);
59 attributes::outer_attributes(p); 73 attributes::outer_attributes(p);
60 if p.at(LET_KW) { 74 if p.at(LET_KW) {
@@ -67,35 +81,51 @@ pub(crate) fn expr_block_contents(p: &mut Parser) {
67 Err(m) => m, 81 Err(m) => m,
68 }; 82 };
69 83
70 if has_attrs { 84 let (cm, blocklike) = expr_stmt(p);
71 m.abandon(p); 85 let kind = cm.as_ref().map(|cm| cm.kind()).unwrap_or(ERROR);
72 p.error("expected a let statement or an item after attributes in block"); 86
73 } else { 87 if has_attrs && !is_expr_stmt_attr_allowed(kind) {
74 let is_blocklike = expressions::expr_stmt(p) == BlockLike::Block; 88 // test_err attr_on_expr_not_allowed
75 if p.at(R_CURLY) { 89 // fn foo() {
90 // #[A] 1 + 2;
91 // #[B] if true {};
92 // }
93 p.error(format!("attributes are not allowed on {:?}", kind));
94 }
95
96 if p.at(R_CURLY) {
97 // test attr_on_last_expr_in_block
98 // fn foo() {
99 // { #[A] bar!()? }
100 // #[B] &()
101 // }
102 if let Some(cm) = cm {
103 cm.undo_completion(p).abandon(p);
104 m.complete(p, kind);
105 } else {
76 m.abandon(p); 106 m.abandon(p);
107 }
108 } else {
109 // test no_semi_after_block
110 // fn foo() {
111 // if true {}
112 // loop {}
113 // match () {}
114 // while true {}
115 // for _ in () {}
116 // {}
117 // {}
118 // macro_rules! test {
119 // () => {}
120 // }
121 // test!{}
122 // }
123 if blocklike.is_block() {
124 p.eat(SEMI);
77 } else { 125 } else {
78 // test no_semi_after_block 126 p.expect(SEMI);
79 // fn foo() {
80 // if true {}
81 // loop {}
82 // match () {}
83 // while true {}
84 // for _ in () {}
85 // {}
86 // {}
87 // macro_rules! test {
88 // () => {}
89 // }
90 // test!{}
91 // }
92 if is_blocklike {
93 p.eat(SEMI);
94 } else {
95 p.expect(SEMI);
96 }
97 m.complete(p, EXPR_STMT);
98 } 127 }
128 m.complete(p, EXPR_STMT);
99 } 129 }
100 } 130 }
101 131
@@ -176,7 +206,7 @@ fn current_op(p: &Parser) -> (u8, Op) {
176} 206}
177 207
178// Parses expression with binding power of at least bp. 208// Parses expression with binding power of at least bp.
179fn expr_bp(p: &mut Parser, r: Restrictions, bp: u8) -> BlockLike { 209fn expr_bp(p: &mut Parser, r: Restrictions, bp: u8) -> (Option<CompletedMarker>, BlockLike) {
180 let mut lhs = match lhs(p, r) { 210 let mut lhs = match lhs(p, r) {
181 Some((lhs, blocklike)) => { 211 Some((lhs, blocklike)) => {
182 // test stmt_bin_expr_ambiguity 212 // test stmt_bin_expr_ambiguity
@@ -185,11 +215,11 @@ fn expr_bp(p: &mut Parser, r: Restrictions, bp: u8) -> BlockLike {
185 // {1} &2; 215 // {1} &2;
186 // } 216 // }
187 if r.prefer_stmt && blocklike.is_block() { 217 if r.prefer_stmt && blocklike.is_block() {
188 return BlockLike::Block; 218 return (Some(lhs), BlockLike::Block);
189 } 219 }
190 lhs 220 lhs
191 } 221 }
192 None => return BlockLike::NotBlock, 222 None => return (None, BlockLike::NotBlock),
193 }; 223 };
194 224
195 loop { 225 loop {
@@ -208,7 +238,7 @@ fn expr_bp(p: &mut Parser, r: Restrictions, bp: u8) -> BlockLike {
208 expr_bp(p, r, op_bp + 1); 238 expr_bp(p, r, op_bp + 1);
209 lhs = m.complete(p, if is_range { RANGE_EXPR } else { BIN_EXPR }); 239 lhs = m.complete(p, if is_range { RANGE_EXPR } else { BIN_EXPR });
210 } 240 }
211 BlockLike::NotBlock 241 (Some(lhs), BlockLike::NotBlock)
212} 242}
213 243
214const LHS_FIRST: TokenSet = 244const LHS_FIRST: TokenSet =
diff --git a/crates/ra_parser/src/grammar/expressions/atom.rs b/crates/ra_parser/src/grammar/expressions/atom.rs
index d933288cd..a23977bfb 100644
--- a/crates/ra_parser/src/grammar/expressions/atom.rs
+++ b/crates/ra_parser/src/grammar/expressions/atom.rs
@@ -392,9 +392,9 @@ fn match_arm(p: &mut Parser) -> BlockLike {
392 match_guard(p); 392 match_guard(p);
393 } 393 }
394 p.expect(FAT_ARROW); 394 p.expect(FAT_ARROW);
395 let ret = expr_stmt(p); 395 let blocklike = expr_stmt(p).1;
396 m.complete(p, MATCH_ARM); 396 m.complete(p, MATCH_ARM);
397 ret 397 blocklike
398} 398}
399 399
400// test match_guard 400// test match_guard
diff --git a/crates/ra_parser/src/parser.rs b/crates/ra_parser/src/parser.rs
index a18458148..3c326452b 100644
--- a/crates/ra_parser/src/parser.rs
+++ b/crates/ra_parser/src/parser.rs
@@ -212,8 +212,9 @@ impl Marker {
212 } 212 }
213 _ => unreachable!(), 213 _ => unreachable!(),
214 } 214 }
215 let finish_pos = p.events.len() as u32;
215 p.push_event(Event::Finish); 216 p.push_event(Event::Finish);
216 CompletedMarker::new(self.pos, kind) 217 CompletedMarker::new(self.pos, finish_pos, kind)
217 } 218 }
218 219
219 /// Abandons the syntax tree node. All its children 220 /// Abandons the syntax tree node. All its children
@@ -230,11 +231,15 @@ impl Marker {
230 } 231 }
231} 232}
232 233
233pub(crate) struct CompletedMarker(u32, SyntaxKind); 234pub(crate) struct CompletedMarker {
235 start_pos: u32,
236 finish_pos: u32,
237 kind: SyntaxKind,
238}
234 239
235impl CompletedMarker { 240impl CompletedMarker {
236 fn new(pos: u32, kind: SyntaxKind) -> Self { 241 fn new(start_pos: u32, finish_pos: u32, kind: SyntaxKind) -> Self {
237 CompletedMarker(pos, kind) 242 CompletedMarker { start_pos, finish_pos, kind }
238 } 243 }
239 244
240 /// This method allows to create a new node which starts 245 /// This method allows to create a new node which starts
@@ -251,17 +256,32 @@ impl CompletedMarker {
251 /// distance to `NEWSTART` into forward_parent(=2 in this case); 256 /// distance to `NEWSTART` into forward_parent(=2 in this case);
252 pub(crate) fn precede(self, p: &mut Parser) -> Marker { 257 pub(crate) fn precede(self, p: &mut Parser) -> Marker {
253 let new_pos = p.start(); 258 let new_pos = p.start();
254 let idx = self.0 as usize; 259 let idx = self.start_pos as usize;
255 match p.events[idx] { 260 match p.events[idx] {
256 Event::Start { ref mut forward_parent, .. } => { 261 Event::Start { ref mut forward_parent, .. } => {
257 *forward_parent = Some(new_pos.pos - self.0); 262 *forward_parent = Some(new_pos.pos - self.start_pos);
258 } 263 }
259 _ => unreachable!(), 264 _ => unreachable!(),
260 } 265 }
261 new_pos 266 new_pos
262 } 267 }
263 268
269 /// Undo this completion and turns into a `Marker`
270 pub(crate) fn undo_completion(self, p: &mut Parser) -> Marker {
271 let start_idx = self.start_pos as usize;
272 let finish_idx = self.finish_pos as usize;
273 match p.events[start_idx] {
274 Event::Start { ref mut kind, forward_parent: None } => *kind = TOMBSTONE,
275 _ => unreachable!(),
276 }
277 match p.events[finish_idx] {
278 ref mut slot @ Event::Finish => *slot = Event::tombstone(),
279 _ => unreachable!(),
280 }
281 Marker::new(self.start_pos)
282 }
283
264 pub(crate) fn kind(&self) -> SyntaxKind { 284 pub(crate) fn kind(&self) -> SyntaxKind {
265 self.1 285 self.kind
266 } 286 }
267} 287}
diff --git a/crates/ra_syntax/tests/data/parser/inline/err/0009_attr_on_expr_not_allowed.rs b/crates/ra_syntax/tests/data/parser/inline/err/0009_attr_on_expr_not_allowed.rs
new file mode 100644
index 000000000..d725a07ce
--- /dev/null
+++ b/crates/ra_syntax/tests/data/parser/inline/err/0009_attr_on_expr_not_allowed.rs
@@ -0,0 +1,4 @@
1fn foo() {
2 #[A] 1 + 2;
3 #[B] if true {};
4}
diff --git a/crates/ra_syntax/tests/data/parser/inline/err/0009_attr_on_expr_not_allowed.txt b/crates/ra_syntax/tests/data/parser/inline/err/0009_attr_on_expr_not_allowed.txt
new file mode 100644
index 000000000..fdea1ec1e
--- /dev/null
+++ b/crates/ra_syntax/tests/data/parser/inline/err/0009_attr_on_expr_not_allowed.txt
@@ -0,0 +1,55 @@
1SOURCE_FILE@[0; 48)
2 FN_DEF@[0; 47)
3 FN_KW@[0; 2)
4 WHITESPACE@[2; 3)
5 NAME@[3; 6)
6 IDENT@[3; 6) "foo"
7 PARAM_LIST@[6; 8)
8 L_PAREN@[6; 7)
9 R_PAREN@[7; 8)
10 WHITESPACE@[8; 9)
11 BLOCK@[9; 47)
12 L_CURLY@[9; 10)
13 WHITESPACE@[10; 14)
14 EXPR_STMT@[14; 25)
15 ATTR@[14; 18)
16 POUND@[14; 15)
17 TOKEN_TREE@[15; 18)
18 L_BRACK@[15; 16)
19 IDENT@[16; 17) "A"
20 R_BRACK@[17; 18)
21 WHITESPACE@[18; 19)
22 BIN_EXPR@[19; 24)
23 LITERAL@[19; 20)
24 INT_NUMBER@[19; 20) "1"
25 WHITESPACE@[20; 21)
26 PLUS@[21; 22)
27 WHITESPACE@[22; 23)
28 LITERAL@[23; 24)
29 INT_NUMBER@[23; 24) "2"
30 err: `attributes are not allowed on BIN_EXPR`
31 SEMI@[24; 25)
32 WHITESPACE@[25; 29)
33 EXPR_STMT@[29; 45)
34 ATTR@[29; 33)
35 POUND@[29; 30)
36 TOKEN_TREE@[30; 33)
37 L_BRACK@[30; 31)
38 IDENT@[31; 32) "B"
39 R_BRACK@[32; 33)
40 WHITESPACE@[33; 34)
41 IF_EXPR@[34; 44)
42 IF_KW@[34; 36)
43 WHITESPACE@[36; 37)
44 CONDITION@[37; 41)
45 LITERAL@[37; 41)
46 TRUE_KW@[37; 41)
47 WHITESPACE@[41; 42)
48 BLOCK@[42; 44)
49 L_CURLY@[42; 43)
50 R_CURLY@[43; 44)
51 err: `attributes are not allowed on IF_EXPR`
52 SEMI@[44; 45)
53 WHITESPACE@[45; 46)
54 R_CURLY@[46; 47)
55 WHITESPACE@[47; 48)
diff --git a/crates/ra_syntax/tests/data/parser/inline/ok/0126_attr_on_expr_stmt.rs b/crates/ra_syntax/tests/data/parser/inline/ok/0126_attr_on_expr_stmt.rs
new file mode 100644
index 000000000..b28c078f9
--- /dev/null
+++ b/crates/ra_syntax/tests/data/parser/inline/ok/0126_attr_on_expr_stmt.rs
@@ -0,0 +1,6 @@
1fn foo() {
2 #[A] foo();
3 #[B] bar!{}
4 #[C] #[D] {}
5 #[D] return ();
6}
diff --git a/crates/ra_syntax/tests/data/parser/inline/ok/0126_attr_on_expr_stmt.txt b/crates/ra_syntax/tests/data/parser/inline/ok/0126_attr_on_expr_stmt.txt
new file mode 100644
index 000000000..7cd525cc7
--- /dev/null
+++ b/crates/ra_syntax/tests/data/parser/inline/ok/0126_attr_on_expr_stmt.txt
@@ -0,0 +1,88 @@
1SOURCE_FILE@[0; 82)
2 FN_DEF@[0; 81)
3 FN_KW@[0; 2)
4 WHITESPACE@[2; 3)
5 NAME@[3; 6)
6 IDENT@[3; 6) "foo"
7 PARAM_LIST@[6; 8)
8 L_PAREN@[6; 7)
9 R_PAREN@[7; 8)
10 WHITESPACE@[8; 9)
11 BLOCK@[9; 81)
12 L_CURLY@[9; 10)
13 WHITESPACE@[10; 15)
14 EXPR_STMT@[15; 26)
15 ATTR@[15; 19)
16 POUND@[15; 16)
17 TOKEN_TREE@[16; 19)
18 L_BRACK@[16; 17)
19 IDENT@[17; 18) "A"
20 R_BRACK@[18; 19)
21 WHITESPACE@[19; 20)
22 CALL_EXPR@[20; 25)
23 PATH_EXPR@[20; 23)
24 PATH@[20; 23)
25 PATH_SEGMENT@[20; 23)
26 NAME_REF@[20; 23)
27 IDENT@[20; 23) "foo"
28 ARG_LIST@[23; 25)
29 L_PAREN@[23; 24)
30 R_PAREN@[24; 25)
31 SEMI@[25; 26)
32 WHITESPACE@[26; 31)
33 EXPR_STMT@[31; 42)
34 ATTR@[31; 35)
35 POUND@[31; 32)
36 TOKEN_TREE@[32; 35)
37 L_BRACK@[32; 33)
38 IDENT@[33; 34) "B"
39 R_BRACK@[34; 35)
40 WHITESPACE@[35; 36)
41 MACRO_CALL@[36; 42)
42 PATH@[36; 39)
43 PATH_SEGMENT@[36; 39)
44 NAME_REF@[36; 39)
45 IDENT@[36; 39) "bar"
46 EXCL@[39; 40)
47 TOKEN_TREE@[40; 42)
48 L_CURLY@[40; 41)
49 R_CURLY@[41; 42)
50 WHITESPACE@[42; 47)
51 EXPR_STMT@[47; 59)
52 ATTR@[47; 51)
53 POUND@[47; 48)
54 TOKEN_TREE@[48; 51)
55 L_BRACK@[48; 49)
56 IDENT@[49; 50) "C"
57 R_BRACK@[50; 51)
58 WHITESPACE@[51; 52)
59 ATTR@[52; 56)
60 POUND@[52; 53)
61 TOKEN_TREE@[53; 56)
62 L_BRACK@[53; 54)
63 IDENT@[54; 55) "D"
64 R_BRACK@[55; 56)
65 WHITESPACE@[56; 57)
66 BLOCK_EXPR@[57; 59)
67 BLOCK@[57; 59)
68 L_CURLY@[57; 58)
69 R_CURLY@[58; 59)
70 WHITESPACE@[59; 64)
71 EXPR_STMT@[64; 79)
72 ATTR@[64; 68)
73 POUND@[64; 65)
74 TOKEN_TREE@[65; 68)
75 L_BRACK@[65; 66)
76 IDENT@[66; 67) "D"
77 R_BRACK@[67; 68)
78 WHITESPACE@[68; 69)
79 RETURN_EXPR@[69; 78)
80 RETURN_KW@[69; 75)
81 WHITESPACE@[75; 76)
82 TUPLE_EXPR@[76; 78)
83 L_PAREN@[76; 77)
84 R_PAREN@[77; 78)
85 SEMI@[78; 79)
86 WHITESPACE@[79; 80)
87 R_CURLY@[80; 81)
88 WHITESPACE@[81; 82)
diff --git a/crates/ra_syntax/tests/data/parser/inline/ok/0127_attr_on_last_expr_in_block.rs b/crates/ra_syntax/tests/data/parser/inline/ok/0127_attr_on_last_expr_in_block.rs
new file mode 100644
index 000000000..9c5c8eb36
--- /dev/null
+++ b/crates/ra_syntax/tests/data/parser/inline/ok/0127_attr_on_last_expr_in_block.rs
@@ -0,0 +1,4 @@
1fn foo() {
2 { #[A] bar!()? }
3 #[B] &()
4}
diff --git a/crates/ra_syntax/tests/data/parser/inline/ok/0127_attr_on_last_expr_in_block.txt b/crates/ra_syntax/tests/data/parser/inline/ok/0127_attr_on_last_expr_in_block.txt
new file mode 100644
index 000000000..4af64559c
--- /dev/null
+++ b/crates/ra_syntax/tests/data/parser/inline/ok/0127_attr_on_last_expr_in_block.txt
@@ -0,0 +1,54 @@
1SOURCE_FILE@[0; 47)
2 FN_DEF@[0; 46)
3 FN_KW@[0; 2)
4 WHITESPACE@[2; 3)
5 NAME@[3; 6)
6 IDENT@[3; 6) "foo"
7 PARAM_LIST@[6; 8)
8 L_PAREN@[6; 7)
9 R_PAREN@[7; 8)
10 WHITESPACE@[8; 9)
11 BLOCK@[9; 46)
12 L_CURLY@[9; 10)
13 WHITESPACE@[10; 15)
14 EXPR_STMT@[15; 31)
15 BLOCK_EXPR@[15; 31)
16 BLOCK@[15; 31)
17 L_CURLY@[15; 16)
18 WHITESPACE@[16; 17)
19 TRY_EXPR@[17; 29)
20 ATTR@[17; 21)
21 POUND@[17; 18)
22 TOKEN_TREE@[18; 21)
23 L_BRACK@[18; 19)
24 IDENT@[19; 20) "A"
25 R_BRACK@[20; 21)
26 WHITESPACE@[21; 22)
27 MACRO_CALL@[22; 28)
28 PATH@[22; 25)
29 PATH_SEGMENT@[22; 25)
30 NAME_REF@[22; 25)
31 IDENT@[22; 25) "bar"
32 EXCL@[25; 26)
33 TOKEN_TREE@[26; 28)
34 L_PAREN@[26; 27)
35 R_PAREN@[27; 28)
36 QUESTION@[28; 29)
37 WHITESPACE@[29; 30)
38 R_CURLY@[30; 31)
39 WHITESPACE@[31; 36)
40 REF_EXPR@[36; 44)
41 ATTR@[36; 40)
42 POUND@[36; 37)
43 TOKEN_TREE@[37; 40)
44 L_BRACK@[37; 38)
45 IDENT@[38; 39) "B"
46 R_BRACK@[39; 40)
47 WHITESPACE@[40; 41)
48 AMP@[41; 42)
49 TUPLE_EXPR@[42; 44)
50 L_PAREN@[42; 43)
51 R_PAREN@[43; 44)
52 WHITESPACE@[44; 45)
53 R_CURLY@[45; 46)
54 WHITESPACE@[46; 47)