diff options
-rw-r--r-- | crates/ra_hir_def/src/body/lower.rs | 3 | ||||
-rw-r--r-- | crates/ra_hir_def/src/expr.rs | 1 | ||||
-rw-r--r-- | crates/ra_hir_ty/src/infer.rs | 9 | ||||
-rw-r--r-- | crates/ra_hir_ty/src/infer/coerce.rs | 4 | ||||
-rw-r--r-- | crates/ra_hir_ty/src/infer/expr.rs | 19 | ||||
-rw-r--r-- | crates/ra_hir_ty/src/tests/coercion.rs | 86 | ||||
-rw-r--r-- | crates/ra_hir_ty/src/tests/simple.rs | 55 | ||||
-rw-r--r-- | crates/ra_hir_ty/src/tests/traits.rs | 10 | ||||
-rw-r--r-- | crates/ra_ide/src/goto_definition.rs | 36 | ||||
-rw-r--r-- | crates/ra_parser/src/grammar/patterns.rs | 2 | ||||
-rw-r--r-- | crates/ra_syntax/src/ast/generated.rs | 3 | ||||
-rw-r--r-- | crates/ra_syntax/src/grammar.ron | 2 | ||||
-rw-r--r-- | crates/ra_syntax/test_data/parser/inline/ok/0129_marco_pat.txt | 9 |
13 files changed, 220 insertions, 19 deletions
diff --git a/crates/ra_hir_def/src/body/lower.rs b/crates/ra_hir_def/src/body/lower.rs index 853e17bae..be5d17d85 100644 --- a/crates/ra_hir_def/src/body/lower.rs +++ b/crates/ra_hir_def/src/body/lower.rs | |||
@@ -372,8 +372,9 @@ where | |||
372 | arg_types.push(type_ref); | 372 | arg_types.push(type_ref); |
373 | } | 373 | } |
374 | } | 374 | } |
375 | let ret_type = e.ret_type().and_then(|r| r.type_ref()).map(TypeRef::from_ast); | ||
375 | let body = self.collect_expr_opt(e.body()); | 376 | let body = self.collect_expr_opt(e.body()); |
376 | self.alloc_expr(Expr::Lambda { args, arg_types, body }, syntax_ptr) | 377 | self.alloc_expr(Expr::Lambda { args, arg_types, ret_type, body }, syntax_ptr) |
377 | } | 378 | } |
378 | ast::Expr::BinExpr(e) => { | 379 | ast::Expr::BinExpr(e) => { |
379 | let lhs = self.collect_expr_opt(e.lhs()); | 380 | let lhs = self.collect_expr_opt(e.lhs()); |
diff --git a/crates/ra_hir_def/src/expr.rs b/crates/ra_hir_def/src/expr.rs index 6fad80a8d..a75ef9970 100644 --- a/crates/ra_hir_def/src/expr.rs +++ b/crates/ra_hir_def/src/expr.rs | |||
@@ -143,6 +143,7 @@ pub enum Expr { | |||
143 | Lambda { | 143 | Lambda { |
144 | args: Vec<PatId>, | 144 | args: Vec<PatId>, |
145 | arg_types: Vec<Option<TypeRef>>, | 145 | arg_types: Vec<Option<TypeRef>>, |
146 | ret_type: Option<TypeRef>, | ||
146 | body: ExprId, | 147 | body: ExprId, |
147 | }, | 148 | }, |
148 | Tuple { | 149 | Tuple { |
diff --git a/crates/ra_hir_ty/src/infer.rs b/crates/ra_hir_ty/src/infer.rs index bbbc391c4..e97b81473 100644 --- a/crates/ra_hir_ty/src/infer.rs +++ b/crates/ra_hir_ty/src/infer.rs | |||
@@ -196,7 +196,12 @@ struct InferenceContext<'a, D: HirDatabase> { | |||
196 | trait_env: Arc<TraitEnvironment>, | 196 | trait_env: Arc<TraitEnvironment>, |
197 | obligations: Vec<Obligation>, | 197 | obligations: Vec<Obligation>, |
198 | result: InferenceResult, | 198 | result: InferenceResult, |
199 | /// The return type of the function being inferred. | 199 | /// The return type of the function being inferred, or the closure if we're |
200 | /// currently within one. | ||
201 | /// | ||
202 | /// We might consider using a nested inference context for checking | ||
203 | /// closures, but currently this is the only field that will change there, | ||
204 | /// so it doesn't make sense. | ||
200 | return_ty: Ty, | 205 | return_ty: Ty, |
201 | 206 | ||
202 | /// Impls of `CoerceUnsized` used in coercion. | 207 | /// Impls of `CoerceUnsized` used in coercion. |
@@ -455,7 +460,7 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> { | |||
455 | } | 460 | } |
456 | 461 | ||
457 | fn infer_body(&mut self) { | 462 | fn infer_body(&mut self) { |
458 | self.infer_expr(self.body.body_expr, &Expectation::has_type(self.return_ty.clone())); | 463 | self.infer_expr_coerce(self.body.body_expr, &Expectation::has_type(self.return_ty.clone())); |
459 | } | 464 | } |
460 | 465 | ||
461 | fn resolve_into_iter_item(&self) -> Option<TypeAliasId> { | 466 | fn resolve_into_iter_item(&self) -> Option<TypeAliasId> { |
diff --git a/crates/ra_hir_ty/src/infer/coerce.rs b/crates/ra_hir_ty/src/infer/coerce.rs index 0f4dac45e..83c0c2c3f 100644 --- a/crates/ra_hir_ty/src/infer/coerce.rs +++ b/crates/ra_hir_ty/src/infer/coerce.rs | |||
@@ -134,6 +134,10 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> { | |||
134 | } | 134 | } |
135 | } | 135 | } |
136 | 136 | ||
137 | (ty_app!(TypeCtor::Closure { .. }, params), ty_app!(TypeCtor::FnPtr { .. })) => { | ||
138 | from_ty = params[0].clone(); | ||
139 | } | ||
140 | |||
137 | _ => {} | 141 | _ => {} |
138 | } | 142 | } |
139 | 143 | ||
diff --git a/crates/ra_hir_ty/src/infer/expr.rs b/crates/ra_hir_ty/src/infer/expr.rs index 8be567917..3af05394c 100644 --- a/crates/ra_hir_ty/src/infer/expr.rs +++ b/crates/ra_hir_ty/src/infer/expr.rs | |||
@@ -41,7 +41,7 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> { | |||
41 | 41 | ||
42 | /// Infer type of expression with possibly implicit coerce to the expected type. | 42 | /// Infer type of expression with possibly implicit coerce to the expected type. |
43 | /// Return the type after possible coercion. | 43 | /// Return the type after possible coercion. |
44 | fn infer_expr_coerce(&mut self, expr: ExprId, expected: &Expectation) -> Ty { | 44 | pub(super) fn infer_expr_coerce(&mut self, expr: ExprId, expected: &Expectation) -> Ty { |
45 | let ty = self.infer_expr_inner(expr, &expected); | 45 | let ty = self.infer_expr_inner(expr, &expected); |
46 | let ty = if !self.coerce(&ty, &expected.ty) { | 46 | let ty = if !self.coerce(&ty, &expected.ty) { |
47 | self.result | 47 | self.result |
@@ -102,7 +102,7 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> { | |||
102 | self.infer_expr(*body, &Expectation::has_type(Ty::unit())); | 102 | self.infer_expr(*body, &Expectation::has_type(Ty::unit())); |
103 | Ty::unit() | 103 | Ty::unit() |
104 | } | 104 | } |
105 | Expr::Lambda { body, args, arg_types } => { | 105 | Expr::Lambda { body, args, ret_type, arg_types } => { |
106 | assert_eq!(args.len(), arg_types.len()); | 106 | assert_eq!(args.len(), arg_types.len()); |
107 | 107 | ||
108 | let mut sig_tys = Vec::new(); | 108 | let mut sig_tys = Vec::new(); |
@@ -118,7 +118,10 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> { | |||
118 | } | 118 | } |
119 | 119 | ||
120 | // add return type | 120 | // add return type |
121 | let ret_ty = self.table.new_type_var(); | 121 | let ret_ty = match ret_type { |
122 | Some(type_ref) => self.make_ty(type_ref), | ||
123 | None => self.table.new_type_var(), | ||
124 | }; | ||
122 | sig_tys.push(ret_ty.clone()); | 125 | sig_tys.push(ret_ty.clone()); |
123 | let sig_ty = Ty::apply( | 126 | let sig_ty = Ty::apply( |
124 | TypeCtor::FnPtr { num_args: sig_tys.len() as u16 - 1 }, | 127 | TypeCtor::FnPtr { num_args: sig_tys.len() as u16 - 1 }, |
@@ -134,7 +137,12 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> { | |||
134 | // infer the body. | 137 | // infer the body. |
135 | self.coerce(&closure_ty, &expected.ty); | 138 | self.coerce(&closure_ty, &expected.ty); |
136 | 139 | ||
137 | self.infer_expr(*body, &Expectation::has_type(ret_ty)); | 140 | let prev_ret_ty = std::mem::replace(&mut self.return_ty, ret_ty.clone()); |
141 | |||
142 | self.infer_expr_coerce(*body, &Expectation::has_type(ret_ty)); | ||
143 | |||
144 | self.return_ty = prev_ret_ty; | ||
145 | |||
138 | closure_ty | 146 | closure_ty |
139 | } | 147 | } |
140 | Expr::Call { callee, args } => { | 148 | Expr::Call { callee, args } => { |
@@ -192,6 +200,9 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> { | |||
192 | Expr::Return { expr } => { | 200 | Expr::Return { expr } => { |
193 | if let Some(expr) = expr { | 201 | if let Some(expr) = expr { |
194 | self.infer_expr_coerce(*expr, &Expectation::has_type(self.return_ty.clone())); | 202 | self.infer_expr_coerce(*expr, &Expectation::has_type(self.return_ty.clone())); |
203 | } else { | ||
204 | let unit = Ty::unit(); | ||
205 | self.coerce(&unit, &self.return_ty.clone()); | ||
195 | } | 206 | } |
196 | Ty::simple(TypeCtor::Never) | 207 | Ty::simple(TypeCtor::Never) |
197 | } | 208 | } |
diff --git a/crates/ra_hir_ty/src/tests/coercion.rs b/crates/ra_hir_ty/src/tests/coercion.rs index ac9e3872a..7e99a42ed 100644 --- a/crates/ra_hir_ty/src/tests/coercion.rs +++ b/crates/ra_hir_ty/src/tests/coercion.rs | |||
@@ -370,6 +370,22 @@ fn test() { | |||
370 | } | 370 | } |
371 | 371 | ||
372 | #[test] | 372 | #[test] |
373 | fn return_coerce_unknown() { | ||
374 | assert_snapshot!( | ||
375 | infer_with_mismatches(r#" | ||
376 | fn foo() -> u32 { | ||
377 | return unknown; | ||
378 | } | ||
379 | "#, true), | ||
380 | @r###" | ||
381 | [17; 40) '{ ...own; }': ! | ||
382 | [23; 37) 'return unknown': ! | ||
383 | [30; 37) 'unknown': u32 | ||
384 | "### | ||
385 | ); | ||
386 | } | ||
387 | |||
388 | #[test] | ||
373 | fn coerce_autoderef() { | 389 | fn coerce_autoderef() { |
374 | assert_snapshot!( | 390 | assert_snapshot!( |
375 | infer_with_mismatches(r#" | 391 | infer_with_mismatches(r#" |
@@ -440,3 +456,73 @@ fn test() { | |||
440 | "### | 456 | "### |
441 | ); | 457 | ); |
442 | } | 458 | } |
459 | |||
460 | #[test] | ||
461 | fn closure_return_coerce() { | ||
462 | assert_snapshot!( | ||
463 | infer_with_mismatches(r#" | ||
464 | fn foo() { | ||
465 | let x = || { | ||
466 | if true { | ||
467 | return &1u32; | ||
468 | } | ||
469 | &&1u32 | ||
470 | }; | ||
471 | } | ||
472 | "#, true), | ||
473 | @r###" | ||
474 | [10; 106) '{ ... }; }': () | ||
475 | [20; 21) 'x': || -> &u32 | ||
476 | [24; 103) '|| { ... }': || -> &u32 | ||
477 | [27; 103) '{ ... }': &u32 | ||
478 | [37; 82) 'if tru... }': () | ||
479 | [40; 44) 'true': bool | ||
480 | [45; 82) '{ ... }': ! | ||
481 | [59; 71) 'return &1u32': ! | ||
482 | [66; 71) '&1u32': &u32 | ||
483 | [67; 71) '1u32': u32 | ||
484 | [91; 97) '&&1u32': &&u32 | ||
485 | [92; 97) '&1u32': &u32 | ||
486 | [93; 97) '1u32': u32 | ||
487 | "### | ||
488 | ); | ||
489 | } | ||
490 | |||
491 | #[test] | ||
492 | fn coerce_fn_item_to_fn_ptr() { | ||
493 | assert_snapshot!( | ||
494 | infer_with_mismatches(r#" | ||
495 | fn foo(x: u32) -> isize { 1 } | ||
496 | fn test() { | ||
497 | let f: fn(u32) -> isize = foo; | ||
498 | } | ||
499 | "#, true), | ||
500 | @r###" | ||
501 | [8; 9) 'x': u32 | ||
502 | [25; 30) '{ 1 }': isize | ||
503 | [27; 28) '1': isize | ||
504 | [41; 79) '{ ...foo; }': () | ||
505 | [51; 52) 'f': fn(u32) -> isize | ||
506 | [73; 76) 'foo': fn foo(u32) -> isize | ||
507 | "### | ||
508 | ); | ||
509 | } | ||
510 | |||
511 | #[test] | ||
512 | fn coerce_closure_to_fn_ptr() { | ||
513 | assert_snapshot!( | ||
514 | infer_with_mismatches(r#" | ||
515 | fn test() { | ||
516 | let f: fn(u32) -> isize = |x| { 1 }; | ||
517 | } | ||
518 | "#, true), | ||
519 | @r###" | ||
520 | [11; 55) '{ ...1 }; }': () | ||
521 | [21; 22) 'f': fn(u32) -> isize | ||
522 | [43; 52) '|x| { 1 }': |u32| -> isize | ||
523 | [44; 45) 'x': u32 | ||
524 | [47; 52) '{ 1 }': isize | ||
525 | [49; 50) '1': isize | ||
526 | "### | ||
527 | ); | ||
528 | } | ||
diff --git a/crates/ra_hir_ty/src/tests/simple.rs b/crates/ra_hir_ty/src/tests/simple.rs index 18976c9ae..6fe647a5e 100644 --- a/crates/ra_hir_ty/src/tests/simple.rs +++ b/crates/ra_hir_ty/src/tests/simple.rs | |||
@@ -1606,3 +1606,58 @@ fn main() { | |||
1606 | ); | 1606 | ); |
1607 | assert_eq!(t, "u32"); | 1607 | assert_eq!(t, "u32"); |
1608 | } | 1608 | } |
1609 | |||
1610 | #[test] | ||
1611 | fn closure_return() { | ||
1612 | assert_snapshot!( | ||
1613 | infer(r#" | ||
1614 | fn foo() -> u32 { | ||
1615 | let x = || -> usize { return 1; }; | ||
1616 | } | ||
1617 | "#), | ||
1618 | @r###" | ||
1619 | [17; 59) '{ ...; }; }': () | ||
1620 | [27; 28) 'x': || -> usize | ||
1621 | [31; 56) '|| -> ...n 1; }': || -> usize | ||
1622 | [43; 56) '{ return 1; }': ! | ||
1623 | [45; 53) 'return 1': ! | ||
1624 | [52; 53) '1': usize | ||
1625 | "### | ||
1626 | ); | ||
1627 | } | ||
1628 | |||
1629 | #[test] | ||
1630 | fn closure_return_unit() { | ||
1631 | assert_snapshot!( | ||
1632 | infer(r#" | ||
1633 | fn foo() -> u32 { | ||
1634 | let x = || { return; }; | ||
1635 | } | ||
1636 | "#), | ||
1637 | @r###" | ||
1638 | [17; 48) '{ ...; }; }': () | ||
1639 | [27; 28) 'x': || -> () | ||
1640 | [31; 45) '|| { return; }': || -> () | ||
1641 | [34; 45) '{ return; }': ! | ||
1642 | [36; 42) 'return': ! | ||
1643 | "### | ||
1644 | ); | ||
1645 | } | ||
1646 | |||
1647 | #[test] | ||
1648 | fn closure_return_inferred() { | ||
1649 | assert_snapshot!( | ||
1650 | infer(r#" | ||
1651 | fn foo() -> u32 { | ||
1652 | let x = || { "test" }; | ||
1653 | } | ||
1654 | "#), | ||
1655 | @r###" | ||
1656 | [17; 47) '{ ..." }; }': () | ||
1657 | [27; 28) 'x': || -> &str | ||
1658 | [31; 44) '|| { "test" }': || -> &str | ||
1659 | [34; 44) '{ "test" }': &str | ||
1660 | [36; 42) '"test"': &str | ||
1661 | "### | ||
1662 | ); | ||
1663 | } | ||
diff --git a/crates/ra_hir_ty/src/tests/traits.rs b/crates/ra_hir_ty/src/tests/traits.rs index 2d92a5eec..76e2198b6 100644 --- a/crates/ra_hir_ty/src/tests/traits.rs +++ b/crates/ra_hir_ty/src/tests/traits.rs | |||
@@ -393,11 +393,11 @@ fn test() -> u64 { | |||
393 | [54; 55) 'a': S | 393 | [54; 55) 'a': S |
394 | [58; 59) 'S': S(fn(u32) -> u64) -> S | 394 | [58; 59) 'S': S(fn(u32) -> u64) -> S |
395 | [58; 68) 'S(|i| 2*i)': S | 395 | [58; 68) 'S(|i| 2*i)': S |
396 | [60; 67) '|i| 2*i': |i32| -> i32 | 396 | [60; 67) '|i| 2*i': |u32| -> u64 |
397 | [61; 62) 'i': i32 | 397 | [61; 62) 'i': u32 |
398 | [64; 65) '2': i32 | 398 | [64; 65) '2': u32 |
399 | [64; 67) '2*i': i32 | 399 | [64; 67) '2*i': u32 |
400 | [66; 67) 'i': i32 | 400 | [66; 67) 'i': u32 |
401 | [78; 79) 'b': u64 | 401 | [78; 79) 'b': u64 |
402 | [82; 83) 'a': S | 402 | [82; 83) 'a': S |
403 | [82; 85) 'a.0': fn(u32) -> u64 | 403 | [82; 85) 'a.0': fn(u32) -> u64 |
diff --git a/crates/ra_ide/src/goto_definition.rs b/crates/ra_ide/src/goto_definition.rs index 9b5744789..79d332e8c 100644 --- a/crates/ra_ide/src/goto_definition.rs +++ b/crates/ra_ide/src/goto_definition.rs | |||
@@ -425,6 +425,42 @@ mod tests { | |||
425 | } | 425 | } |
426 | 426 | ||
427 | #[test] | 427 | #[test] |
428 | fn goto_definition_works_for_macro_inside_pattern() { | ||
429 | check_goto( | ||
430 | " | ||
431 | //- /lib.rs | ||
432 | macro_rules! foo {() => {0}} | ||
433 | |||
434 | fn bar() { | ||
435 | match (0,1) { | ||
436 | (<|>foo!(), _) => {} | ||
437 | } | ||
438 | } | ||
439 | ", | ||
440 | "foo MACRO_CALL FileId(1) [0; 28) [13; 16)", | ||
441 | "macro_rules! foo {() => {0}}|foo", | ||
442 | ); | ||
443 | } | ||
444 | |||
445 | #[test] | ||
446 | fn goto_definition_works_for_macro_inside_match_arm_lhs() { | ||
447 | check_goto( | ||
448 | " | ||
449 | //- /lib.rs | ||
450 | macro_rules! foo {() => {0}} | ||
451 | |||
452 | fn bar() { | ||
453 | match 0 { | ||
454 | <|>foo!() => {} | ||
455 | } | ||
456 | } | ||
457 | ", | ||
458 | "foo MACRO_CALL FileId(1) [0; 28) [13; 16)", | ||
459 | "macro_rules! foo {() => {0}}|foo", | ||
460 | ); | ||
461 | } | ||
462 | |||
463 | #[test] | ||
428 | fn goto_def_for_methods() { | 464 | fn goto_def_for_methods() { |
429 | covers!(goto_def_for_methods); | 465 | covers!(goto_def_for_methods); |
430 | check_goto( | 466 | check_goto( |
diff --git a/crates/ra_parser/src/grammar/patterns.rs b/crates/ra_parser/src/grammar/patterns.rs index f5d12278c..422a4e3dc 100644 --- a/crates/ra_parser/src/grammar/patterns.rs +++ b/crates/ra_parser/src/grammar/patterns.rs | |||
@@ -50,7 +50,7 @@ pub(super) fn pattern_r(p: &mut Parser, recovery_set: TokenSet) { | |||
50 | // let m!(x) = 0; | 50 | // let m!(x) = 0; |
51 | // } | 51 | // } |
52 | if lhs.kind() == PATH_PAT && p.at(T![!]) { | 52 | if lhs.kind() == PATH_PAT && p.at(T![!]) { |
53 | let m = lhs.precede(p); | 53 | let m = lhs.undo_completion(p); |
54 | items::macro_call_after_excl(p); | 54 | items::macro_call_after_excl(p); |
55 | m.complete(p, MACRO_CALL); | 55 | m.complete(p, MACRO_CALL); |
56 | } | 56 | } |
diff --git a/crates/ra_syntax/src/ast/generated.rs b/crates/ra_syntax/src/ast/generated.rs index 9dd6bd3ea..8d65e2e08 100644 --- a/crates/ra_syntax/src/ast/generated.rs +++ b/crates/ra_syntax/src/ast/generated.rs | |||
@@ -1426,6 +1426,9 @@ impl LambdaExpr { | |||
1426 | pub fn param_list(&self) -> Option<ParamList> { | 1426 | pub fn param_list(&self) -> Option<ParamList> { |
1427 | AstChildren::new(&self.syntax).next() | 1427 | AstChildren::new(&self.syntax).next() |
1428 | } | 1428 | } |
1429 | pub fn ret_type(&self) -> Option<RetType> { | ||
1430 | AstChildren::new(&self.syntax).next() | ||
1431 | } | ||
1429 | pub fn body(&self) -> Option<Expr> { | 1432 | pub fn body(&self) -> Option<Expr> { |
1430 | AstChildren::new(&self.syntax).next() | 1433 | AstChildren::new(&self.syntax).next() |
1431 | } | 1434 | } |
diff --git a/crates/ra_syntax/src/grammar.ron b/crates/ra_syntax/src/grammar.ron index 9ffa9095b..a228fa9d6 100644 --- a/crates/ra_syntax/src/grammar.ron +++ b/crates/ra_syntax/src/grammar.ron | |||
@@ -426,7 +426,7 @@ Grammar( | |||
426 | "PathExpr": (options: ["Path"]), | 426 | "PathExpr": (options: ["Path"]), |
427 | "LambdaExpr": ( | 427 | "LambdaExpr": ( |
428 | options: [ | 428 | options: [ |
429 | "ParamList", | 429 | "ParamList", "RetType", |
430 | ["body", "Expr"], | 430 | ["body", "Expr"], |
431 | ] | 431 | ] |
432 | ), | 432 | ), |
diff --git a/crates/ra_syntax/test_data/parser/inline/ok/0129_marco_pat.txt b/crates/ra_syntax/test_data/parser/inline/ok/0129_marco_pat.txt index 4a714ad6b..b05ccc0ed 100644 --- a/crates/ra_syntax/test_data/parser/inline/ok/0129_marco_pat.txt +++ b/crates/ra_syntax/test_data/parser/inline/ok/0129_marco_pat.txt | |||
@@ -16,11 +16,10 @@ SOURCE_FILE@[0; 33) | |||
16 | LET_KW@[16; 19) "let" | 16 | LET_KW@[16; 19) "let" |
17 | WHITESPACE@[19; 20) " " | 17 | WHITESPACE@[19; 20) " " |
18 | MACRO_CALL@[20; 25) | 18 | MACRO_CALL@[20; 25) |
19 | PATH_PAT@[20; 21) | 19 | PATH@[20; 21) |
20 | PATH@[20; 21) | 20 | PATH_SEGMENT@[20; 21) |
21 | PATH_SEGMENT@[20; 21) | 21 | NAME_REF@[20; 21) |
22 | NAME_REF@[20; 21) | 22 | IDENT@[20; 21) "m" |
23 | IDENT@[20; 21) "m" | ||
24 | EXCL@[21; 22) "!" | 23 | EXCL@[21; 22) "!" |
25 | TOKEN_TREE@[22; 25) | 24 | TOKEN_TREE@[22; 25) |
26 | L_PAREN@[22; 23) "(" | 25 | L_PAREN@[22; 23) "(" |