diff options
Diffstat (limited to 'crates')
-rw-r--r-- | crates/hir/src/lib.rs | 1 | ||||
-rw-r--r-- | crates/hir/src/semantics.rs | 4 | ||||
-rw-r--r-- | crates/hir_def/src/body/scope.rs | 57 | ||||
-rw-r--r-- | crates/hir_def/src/resolver.rs | 6 | ||||
-rw-r--r-- | crates/ide/src/syntax_highlighting/inject.rs | 2 | ||||
-rw-r--r-- | crates/ide_completion/src/completions.rs | 29 | ||||
-rw-r--r-- | crates/ide_completion/src/completions/lifetime.rs | 285 | ||||
-rw-r--r-- | crates/ide_completion/src/completions/pattern.rs | 2 | ||||
-rw-r--r-- | crates/ide_completion/src/context.rs | 86 | ||||
-rw-r--r-- | crates/ide_completion/src/lib.rs | 2 | ||||
-rw-r--r-- | crates/ide_completion/src/render.rs | 6 | ||||
-rw-r--r-- | crates/ide_db/src/call_info.rs | 14 | ||||
-rw-r--r-- | crates/mbe/src/expander/matcher.rs | 4 | ||||
-rw-r--r-- | crates/mbe/src/parser.rs | 12 | ||||
-rw-r--r-- | crates/mbe/src/subtree_source.rs | 1 | ||||
-rw-r--r-- | crates/mbe/src/syntax_bridge.rs | 7 | ||||
-rw-r--r-- | crates/mbe/src/tests/expand.rs | 6 | ||||
-rw-r--r-- | crates/mbe/src/tests/rule.rs | 4 | ||||
-rw-r--r-- | crates/mbe/src/tt_iter.rs | 7 | ||||
-rw-r--r-- | crates/proc_macro_srv/src/rustc_server.rs | 9 | ||||
-rw-r--r-- | crates/syntax/src/ast/node_ext.rs | 9 |
21 files changed, 490 insertions, 63 deletions
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index 30e577671..e34be7e42 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs | |||
@@ -2199,6 +2199,7 @@ pub enum ScopeDef { | |||
2199 | ImplSelfType(Impl), | 2199 | ImplSelfType(Impl), |
2200 | AdtSelfType(Adt), | 2200 | AdtSelfType(Adt), |
2201 | Local(Local), | 2201 | Local(Local), |
2202 | Label(Label), | ||
2202 | Unknown, | 2203 | Unknown, |
2203 | } | 2204 | } |
2204 | 2205 | ||
diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs index 15651bb22..1198e3f0b 100644 --- a/crates/hir/src/semantics.rs +++ b/crates/hir/src/semantics.rs | |||
@@ -839,6 +839,10 @@ impl<'a> SemanticsScope<'a> { | |||
839 | let parent = resolver.body_owner().unwrap(); | 839 | let parent = resolver.body_owner().unwrap(); |
840 | ScopeDef::Local(Local { parent, pat_id }) | 840 | ScopeDef::Local(Local { parent, pat_id }) |
841 | } | 841 | } |
842 | resolver::ScopeDef::Label(label_id) => { | ||
843 | let parent = resolver.body_owner().unwrap(); | ||
844 | ScopeDef::Label(Label { parent, label_id }) | ||
845 | } | ||
842 | }; | 846 | }; |
843 | f(name, def) | 847 | f(name, def) |
844 | }) | 848 | }) |
diff --git a/crates/hir_def/src/body/scope.rs b/crates/hir_def/src/body/scope.rs index 1bbb54fc6..bd7005ca6 100644 --- a/crates/hir_def/src/body/scope.rs +++ b/crates/hir_def/src/body/scope.rs | |||
@@ -8,7 +8,7 @@ use rustc_hash::FxHashMap; | |||
8 | use crate::{ | 8 | use crate::{ |
9 | body::Body, | 9 | body::Body, |
10 | db::DefDatabase, | 10 | db::DefDatabase, |
11 | expr::{Expr, ExprId, Pat, PatId, Statement}, | 11 | expr::{Expr, ExprId, LabelId, Pat, PatId, Statement}, |
12 | BlockId, DefWithBodyId, | 12 | BlockId, DefWithBodyId, |
13 | }; | 13 | }; |
14 | 14 | ||
@@ -40,6 +40,7 @@ impl ScopeEntry { | |||
40 | pub struct ScopeData { | 40 | pub struct ScopeData { |
41 | parent: Option<ScopeId>, | 41 | parent: Option<ScopeId>, |
42 | block: Option<BlockId>, | 42 | block: Option<BlockId>, |
43 | label: Option<(LabelId, Name)>, | ||
43 | entries: Vec<ScopeEntry>, | 44 | entries: Vec<ScopeEntry>, |
44 | } | 45 | } |
45 | 46 | ||
@@ -67,6 +68,11 @@ impl ExprScopes { | |||
67 | self.scopes[scope].block | 68 | self.scopes[scope].block |
68 | } | 69 | } |
69 | 70 | ||
71 | /// If `scope` refers to a labeled expression scope, returns the corresponding `Label`. | ||
72 | pub fn label(&self, scope: ScopeId) -> Option<(LabelId, Name)> { | ||
73 | self.scopes[scope].label.clone() | ||
74 | } | ||
75 | |||
70 | pub fn scope_chain(&self, scope: Option<ScopeId>) -> impl Iterator<Item = ScopeId> + '_ { | 76 | pub fn scope_chain(&self, scope: Option<ScopeId>) -> impl Iterator<Item = ScopeId> + '_ { |
71 | std::iter::successors(scope, move |&scope| self.scopes[scope].parent) | 77 | std::iter::successors(scope, move |&scope| self.scopes[scope].parent) |
72 | } | 78 | } |
@@ -85,15 +91,34 @@ impl ExprScopes { | |||
85 | } | 91 | } |
86 | 92 | ||
87 | fn root_scope(&mut self) -> ScopeId { | 93 | fn root_scope(&mut self) -> ScopeId { |
88 | self.scopes.alloc(ScopeData { parent: None, block: None, entries: vec![] }) | 94 | self.scopes.alloc(ScopeData { parent: None, block: None, label: None, entries: vec![] }) |
89 | } | 95 | } |
90 | 96 | ||
91 | fn new_scope(&mut self, parent: ScopeId) -> ScopeId { | 97 | fn new_scope(&mut self, parent: ScopeId) -> ScopeId { |
92 | self.scopes.alloc(ScopeData { parent: Some(parent), block: None, entries: vec![] }) | 98 | self.scopes.alloc(ScopeData { |
99 | parent: Some(parent), | ||
100 | block: None, | ||
101 | label: None, | ||
102 | entries: vec![], | ||
103 | }) | ||
93 | } | 104 | } |
94 | 105 | ||
95 | fn new_block_scope(&mut self, parent: ScopeId, block: BlockId) -> ScopeId { | 106 | fn new_labeled_scope(&mut self, parent: ScopeId, label: Option<(LabelId, Name)>) -> ScopeId { |
96 | self.scopes.alloc(ScopeData { parent: Some(parent), block: Some(block), entries: vec![] }) | 107 | self.scopes.alloc(ScopeData { parent: Some(parent), block: None, label, entries: vec![] }) |
108 | } | ||
109 | |||
110 | fn new_block_scope( | ||
111 | &mut self, | ||
112 | parent: ScopeId, | ||
113 | block: BlockId, | ||
114 | label: Option<(LabelId, Name)>, | ||
115 | ) -> ScopeId { | ||
116 | self.scopes.alloc(ScopeData { | ||
117 | parent: Some(parent), | ||
118 | block: Some(block), | ||
119 | label, | ||
120 | entries: vec![], | ||
121 | }) | ||
97 | } | 122 | } |
98 | 123 | ||
99 | fn add_bindings(&mut self, body: &Body, scope: ScopeId, pat: PatId) { | 124 | fn add_bindings(&mut self, body: &Body, scope: ScopeId, pat: PatId) { |
@@ -144,21 +169,33 @@ fn compute_block_scopes( | |||
144 | } | 169 | } |
145 | 170 | ||
146 | fn compute_expr_scopes(expr: ExprId, body: &Body, scopes: &mut ExprScopes, scope: ScopeId) { | 171 | fn compute_expr_scopes(expr: ExprId, body: &Body, scopes: &mut ExprScopes, scope: ScopeId) { |
172 | let make_label = | ||
173 | |label: &Option<_>| label.map(|label| (label, body.labels[label].name.clone())); | ||
174 | |||
147 | scopes.set_scope(expr, scope); | 175 | scopes.set_scope(expr, scope); |
148 | match &body[expr] { | 176 | match &body[expr] { |
149 | Expr::Block { statements, tail, id, .. } => { | 177 | Expr::Block { statements, tail, id, label } => { |
150 | let scope = scopes.new_block_scope(scope, *id); | 178 | let scope = scopes.new_block_scope(scope, *id, make_label(label)); |
151 | // Overwrite the old scope for the block expr, so that every block scope can be found | 179 | // Overwrite the old scope for the block expr, so that every block scope can be found |
152 | // via the block itself (important for blocks that only contain items, no expressions). | 180 | // via the block itself (important for blocks that only contain items, no expressions). |
153 | scopes.set_scope(expr, scope); | 181 | scopes.set_scope(expr, scope); |
154 | compute_block_scopes(&statements, *tail, body, scopes, scope); | 182 | compute_block_scopes(statements, *tail, body, scopes, scope); |
155 | } | 183 | } |
156 | Expr::For { iterable, pat, body: body_expr, .. } => { | 184 | Expr::For { iterable, pat, body: body_expr, label } => { |
157 | compute_expr_scopes(*iterable, body, scopes, scope); | 185 | compute_expr_scopes(*iterable, body, scopes, scope); |
158 | let scope = scopes.new_scope(scope); | 186 | let scope = scopes.new_labeled_scope(scope, make_label(label)); |
159 | scopes.add_bindings(body, scope, *pat); | 187 | scopes.add_bindings(body, scope, *pat); |
160 | compute_expr_scopes(*body_expr, body, scopes, scope); | 188 | compute_expr_scopes(*body_expr, body, scopes, scope); |
161 | } | 189 | } |
190 | Expr::While { condition, body: body_expr, label } => { | ||
191 | let scope = scopes.new_labeled_scope(scope, make_label(label)); | ||
192 | compute_expr_scopes(*condition, body, scopes, scope); | ||
193 | compute_expr_scopes(*body_expr, body, scopes, scope); | ||
194 | } | ||
195 | Expr::Loop { body: body_expr, label } => { | ||
196 | let scope = scopes.new_labeled_scope(scope, make_label(label)); | ||
197 | compute_expr_scopes(*body_expr, body, scopes, scope); | ||
198 | } | ||
162 | Expr::Lambda { args, body: body_expr, .. } => { | 199 | Expr::Lambda { args, body: body_expr, .. } => { |
163 | let scope = scopes.new_scope(scope); | 200 | let scope = scopes.new_scope(scope); |
164 | scopes.add_params_bindings(body, scope, &args); | 201 | scopes.add_params_bindings(body, scope, &args); |
diff --git a/crates/hir_def/src/resolver.rs b/crates/hir_def/src/resolver.rs index 42736171e..4a2d1c087 100644 --- a/crates/hir_def/src/resolver.rs +++ b/crates/hir_def/src/resolver.rs | |||
@@ -12,7 +12,7 @@ use crate::{ | |||
12 | body::scope::{ExprScopes, ScopeId}, | 12 | body::scope::{ExprScopes, ScopeId}, |
13 | builtin_type::BuiltinType, | 13 | builtin_type::BuiltinType, |
14 | db::DefDatabase, | 14 | db::DefDatabase, |
15 | expr::{ExprId, PatId}, | 15 | expr::{ExprId, LabelId, PatId}, |
16 | generics::GenericParams, | 16 | generics::GenericParams, |
17 | item_scope::{BuiltinShadowMode, BUILTIN_SCOPE}, | 17 | item_scope::{BuiltinShadowMode, BUILTIN_SCOPE}, |
18 | nameres::DefMap, | 18 | nameres::DefMap, |
@@ -409,6 +409,7 @@ pub enum ScopeDef { | |||
409 | AdtSelfType(AdtId), | 409 | AdtSelfType(AdtId), |
410 | GenericParam(GenericParamId), | 410 | GenericParam(GenericParamId), |
411 | Local(PatId), | 411 | Local(PatId), |
412 | Label(LabelId), | ||
412 | } | 413 | } |
413 | 414 | ||
414 | impl Scope { | 415 | impl Scope { |
@@ -470,6 +471,9 @@ impl Scope { | |||
470 | f(name![Self], ScopeDef::AdtSelfType(*i)); | 471 | f(name![Self], ScopeDef::AdtSelfType(*i)); |
471 | } | 472 | } |
472 | Scope::ExprScope(scope) => { | 473 | Scope::ExprScope(scope) => { |
474 | if let Some((label, name)) = scope.expr_scopes.label(scope.scope_id) { | ||
475 | f(name.clone(), ScopeDef::Label(label)) | ||
476 | } | ||
473 | scope.expr_scopes.entries(scope.scope_id).iter().for_each(|e| { | 477 | scope.expr_scopes.entries(scope.scope_id).iter().for_each(|e| { |
474 | f(e.name().clone(), ScopeDef::Local(e.pat())); | 478 | f(e.name().clone(), ScopeDef::Local(e.pat())); |
475 | }); | 479 | }); |
diff --git a/crates/ide/src/syntax_highlighting/inject.rs b/crates/ide/src/syntax_highlighting/inject.rs index 00493a6b5..8e0940184 100644 --- a/crates/ide/src/syntax_highlighting/inject.rs +++ b/crates/ide/src/syntax_highlighting/inject.rs | |||
@@ -23,7 +23,7 @@ pub(super) fn ra_fixture( | |||
23 | expanded: SyntaxToken, | 23 | expanded: SyntaxToken, |
24 | ) -> Option<()> { | 24 | ) -> Option<()> { |
25 | let active_parameter = ActiveParameter::at_token(&sema, expanded)?; | 25 | let active_parameter = ActiveParameter::at_token(&sema, expanded)?; |
26 | if !active_parameter.name.starts_with("ra_fixture") { | 26 | if !active_parameter.ident().map_or(false, |name| name.text().starts_with("ra_fixture")) { |
27 | return None; | 27 | return None; |
28 | } | 28 | } |
29 | let value = literal.value()?; | 29 | let value = literal.value()?; |
diff --git a/crates/ide_completion/src/completions.rs b/crates/ide_completion/src/completions.rs index 09882c4f3..cdac4e41a 100644 --- a/crates/ide_completion/src/completions.rs +++ b/crates/ide_completion/src/completions.rs | |||
@@ -2,25 +2,27 @@ | |||
2 | 2 | ||
3 | pub(crate) mod attribute; | 3 | pub(crate) mod attribute; |
4 | pub(crate) mod dot; | 4 | pub(crate) mod dot; |
5 | pub(crate) mod record; | 5 | pub(crate) mod flyimport; |
6 | pub(crate) mod pattern; | ||
7 | pub(crate) mod fn_param; | 6 | pub(crate) mod fn_param; |
8 | pub(crate) mod keyword; | 7 | pub(crate) mod keyword; |
9 | pub(crate) mod snippet; | 8 | pub(crate) mod lifetime; |
10 | pub(crate) mod qualified_path; | ||
11 | pub(crate) mod unqualified_path; | ||
12 | pub(crate) mod postfix; | ||
13 | pub(crate) mod macro_in_item_position; | 9 | pub(crate) mod macro_in_item_position; |
14 | pub(crate) mod trait_impl; | ||
15 | pub(crate) mod mod_; | 10 | pub(crate) mod mod_; |
16 | pub(crate) mod flyimport; | 11 | pub(crate) mod pattern; |
12 | pub(crate) mod postfix; | ||
13 | pub(crate) mod qualified_path; | ||
14 | pub(crate) mod record; | ||
15 | pub(crate) mod snippet; | ||
16 | pub(crate) mod trait_impl; | ||
17 | pub(crate) mod unqualified_path; | ||
17 | 18 | ||
18 | use std::iter; | 19 | use std::iter; |
19 | 20 | ||
20 | use hir::{known, ModPath, ScopeDef, Type}; | 21 | use hir::{known, ModPath, ScopeDef, Type}; |
22 | use ide_db::SymbolKind; | ||
21 | 23 | ||
22 | use crate::{ | 24 | use crate::{ |
23 | item::Builder, | 25 | item::{Builder, CompletionKind}, |
24 | render::{ | 26 | render::{ |
25 | const_::render_const, | 27 | const_::render_const, |
26 | enum_variant::render_variant, | 28 | enum_variant::render_variant, |
@@ -31,7 +33,7 @@ use crate::{ | |||
31 | type_alias::render_type_alias, | 33 | type_alias::render_type_alias, |
32 | RenderContext, | 34 | RenderContext, |
33 | }, | 35 | }, |
34 | CompletionContext, CompletionItem, | 36 | CompletionContext, CompletionItem, CompletionItemKind, |
35 | }; | 37 | }; |
36 | 38 | ||
37 | /// Represents an in-progress set of completions being built. | 39 | /// Represents an in-progress set of completions being built. |
@@ -77,6 +79,13 @@ impl Completions { | |||
77 | self.add(item); | 79 | self.add(item); |
78 | } | 80 | } |
79 | 81 | ||
82 | pub(crate) fn add_static_lifetime(&mut self, ctx: &CompletionContext) { | ||
83 | let mut item = | ||
84 | CompletionItem::new(CompletionKind::Reference, ctx.source_range(), "'static"); | ||
85 | item.kind(CompletionItemKind::SymbolKind(SymbolKind::LifetimeParam)); | ||
86 | self.add(item.build()); | ||
87 | } | ||
88 | |||
80 | pub(crate) fn add_resolution( | 89 | pub(crate) fn add_resolution( |
81 | &mut self, | 90 | &mut self, |
82 | ctx: &CompletionContext, | 91 | ctx: &CompletionContext, |
diff --git a/crates/ide_completion/src/completions/lifetime.rs b/crates/ide_completion/src/completions/lifetime.rs new file mode 100644 index 000000000..628c1fb9b --- /dev/null +++ b/crates/ide_completion/src/completions/lifetime.rs | |||
@@ -0,0 +1,285 @@ | |||
1 | //! Completes lifetimes and labels. | ||
2 | use hir::ScopeDef; | ||
3 | |||
4 | use crate::{completions::Completions, context::CompletionContext}; | ||
5 | |||
6 | /// Completes lifetimes. | ||
7 | pub(crate) fn complete_lifetime(acc: &mut Completions, ctx: &CompletionContext) { | ||
8 | if !ctx.lifetime_allowed { | ||
9 | return; | ||
10 | } | ||
11 | let param_lifetime = match ( | ||
12 | &ctx.lifetime_syntax, | ||
13 | ctx.lifetime_param_syntax.as_ref().and_then(|lp| lp.lifetime()), | ||
14 | ) { | ||
15 | (Some(lt), Some(lp)) if lp == lt.clone() => return, | ||
16 | (Some(_), Some(lp)) => Some(lp.to_string()), | ||
17 | _ => None, | ||
18 | }; | ||
19 | |||
20 | ctx.scope.process_all_names(&mut |name, res| { | ||
21 | if let ScopeDef::GenericParam(hir::GenericParam::LifetimeParam(_)) = res { | ||
22 | if param_lifetime != Some(name.to_string()) { | ||
23 | acc.add_resolution(ctx, name.to_string(), &res); | ||
24 | } | ||
25 | } | ||
26 | }); | ||
27 | if param_lifetime.is_none() { | ||
28 | acc.add_static_lifetime(ctx); | ||
29 | } | ||
30 | } | ||
31 | |||
32 | /// Completes labels. | ||
33 | pub(crate) fn complete_label(acc: &mut Completions, ctx: &CompletionContext) { | ||
34 | if !ctx.is_label_ref { | ||
35 | return; | ||
36 | } | ||
37 | ctx.scope.process_all_names(&mut |name, res| { | ||
38 | if let ScopeDef::Label(_) = res { | ||
39 | acc.add_resolution(ctx, name.to_string(), &res); | ||
40 | } | ||
41 | }); | ||
42 | } | ||
43 | |||
44 | #[cfg(test)] | ||
45 | mod tests { | ||
46 | use expect_test::{expect, Expect}; | ||
47 | |||
48 | use crate::{ | ||
49 | test_utils::{check_edit, completion_list_with_config, TEST_CONFIG}, | ||
50 | CompletionConfig, CompletionKind, | ||
51 | }; | ||
52 | |||
53 | fn check(ra_fixture: &str, expect: Expect) { | ||
54 | check_with_config(TEST_CONFIG, ra_fixture, expect); | ||
55 | } | ||
56 | |||
57 | fn check_with_config(config: CompletionConfig, ra_fixture: &str, expect: Expect) { | ||
58 | let actual = completion_list_with_config(config, ra_fixture, CompletionKind::Reference); | ||
59 | expect.assert_eq(&actual) | ||
60 | } | ||
61 | |||
62 | #[test] | ||
63 | fn check_lifetime_edit() { | ||
64 | check_edit( | ||
65 | "'lifetime", | ||
66 | r#" | ||
67 | fn func<'lifetime>(foo: &'li$0) {} | ||
68 | "#, | ||
69 | r#" | ||
70 | fn func<'lifetime>(foo: &'lifetime) {} | ||
71 | "#, | ||
72 | ); | ||
73 | } | ||
74 | |||
75 | #[test] | ||
76 | fn complete_lifetime_in_ref() { | ||
77 | check( | ||
78 | r#" | ||
79 | fn foo<'lifetime>(foo: &'a$0 usize) {} | ||
80 | "#, | ||
81 | expect![[r#" | ||
82 | lt 'lifetime | ||
83 | lt 'static | ||
84 | "#]], | ||
85 | ); | ||
86 | } | ||
87 | |||
88 | #[test] | ||
89 | fn complete_lifetime_in_ref_missing_ty() { | ||
90 | check( | ||
91 | r#" | ||
92 | fn foo<'lifetime>(foo: &'a$0) {} | ||
93 | "#, | ||
94 | expect![[r#" | ||
95 | lt 'lifetime | ||
96 | lt 'static | ||
97 | "#]], | ||
98 | ); | ||
99 | } | ||
100 | #[test] | ||
101 | fn complete_lifetime_in_self_ref() { | ||
102 | check( | ||
103 | r#" | ||
104 | struct Foo; | ||
105 | impl<'impl> Foo { | ||
106 | fn foo<'func>(&'a$0 self) {} | ||
107 | } | ||
108 | "#, | ||
109 | expect![[r#" | ||
110 | lt 'func | ||
111 | lt 'impl | ||
112 | lt 'static | ||
113 | "#]], | ||
114 | ); | ||
115 | } | ||
116 | |||
117 | #[test] | ||
118 | fn complete_lifetime_in_arg_list() { | ||
119 | check( | ||
120 | r#" | ||
121 | struct Foo<'lt>; | ||
122 | fn foo<'lifetime>(_: Foo<'a$0>) {} | ||
123 | "#, | ||
124 | expect![[r#" | ||
125 | lt 'lifetime | ||
126 | lt 'static | ||
127 | "#]], | ||
128 | ); | ||
129 | } | ||
130 | |||
131 | #[test] | ||
132 | fn complete_lifetime_in_where_pred() { | ||
133 | check( | ||
134 | r#" | ||
135 | fn foo2<'lifetime, T>() where 'a$0 {} | ||
136 | "#, | ||
137 | expect![[r#" | ||
138 | lt 'lifetime | ||
139 | lt 'static | ||
140 | "#]], | ||
141 | ); | ||
142 | } | ||
143 | |||
144 | #[test] | ||
145 | fn complete_lifetime_in_ty_bound() { | ||
146 | check( | ||
147 | r#" | ||
148 | fn foo2<'lifetime, T>() where T: 'a$0 {} | ||
149 | "#, | ||
150 | expect![[r#" | ||
151 | lt 'lifetime | ||
152 | lt 'static | ||
153 | "#]], | ||
154 | ); | ||
155 | check( | ||
156 | r#" | ||
157 | fn foo2<'lifetime, T>() where T: Trait<'a$0> {} | ||
158 | "#, | ||
159 | expect![[r#" | ||
160 | lt 'lifetime | ||
161 | lt 'static | ||
162 | "#]], | ||
163 | ); | ||
164 | } | ||
165 | |||
166 | #[test] | ||
167 | fn dont_complete_lifetime_in_assoc_ty_bound() { | ||
168 | check( | ||
169 | r#" | ||
170 | fn foo2<'lifetime, T>() where T: Trait<Item = 'a$0> {} | ||
171 | "#, | ||
172 | expect![[r#""#]], | ||
173 | ); | ||
174 | } | ||
175 | |||
176 | #[test] | ||
177 | fn complete_lifetime_in_param_list() { | ||
178 | check( | ||
179 | r#" | ||
180 | fn foo<'a$0>() {} | ||
181 | "#, | ||
182 | expect![[r#""#]], | ||
183 | ); | ||
184 | check( | ||
185 | r#" | ||
186 | fn foo<'footime, 'lifetime: 'a$0>() {} | ||
187 | "#, | ||
188 | expect![[r#" | ||
189 | lt 'footime | ||
190 | "#]], | ||
191 | ); | ||
192 | } | ||
193 | |||
194 | #[test] | ||
195 | fn complete_label_in_loop() { | ||
196 | check( | ||
197 | r#" | ||
198 | fn foo() { | ||
199 | 'foop: loop { | ||
200 | break '$0 | ||
201 | } | ||
202 | } | ||
203 | "#, | ||
204 | expect![[r#" | ||
205 | lb 'foop | ||
206 | "#]], | ||
207 | ); | ||
208 | check( | ||
209 | r#" | ||
210 | fn foo() { | ||
211 | 'foop: loop { | ||
212 | continue '$0 | ||
213 | } | ||
214 | } | ||
215 | "#, | ||
216 | expect![[r#" | ||
217 | lb 'foop | ||
218 | "#]], | ||
219 | ); | ||
220 | } | ||
221 | |||
222 | #[test] | ||
223 | fn complete_label_in_block_nested() { | ||
224 | check( | ||
225 | r#" | ||
226 | fn foo() { | ||
227 | 'foop: { | ||
228 | 'baap: { | ||
229 | break '$0 | ||
230 | } | ||
231 | } | ||
232 | } | ||
233 | "#, | ||
234 | expect![[r#" | ||
235 | lb 'baap | ||
236 | lb 'foop | ||
237 | "#]], | ||
238 | ); | ||
239 | } | ||
240 | |||
241 | #[test] | ||
242 | fn complete_label_in_loop_with_value() { | ||
243 | check( | ||
244 | r#" | ||
245 | fn foo() { | ||
246 | 'foop: loop { | ||
247 | break '$0 i32; | ||
248 | } | ||
249 | } | ||
250 | "#, | ||
251 | expect![[r#" | ||
252 | lb 'foop | ||
253 | "#]], | ||
254 | ); | ||
255 | } | ||
256 | |||
257 | #[test] | ||
258 | fn complete_label_in_while_cond() { | ||
259 | check( | ||
260 | r#" | ||
261 | fn foo() { | ||
262 | 'outer: while { 'inner: loop { break '$0 } } {} | ||
263 | } | ||
264 | "#, | ||
265 | expect![[r#" | ||
266 | lb 'inner | ||
267 | lb 'outer | ||
268 | "#]], | ||
269 | ); | ||
270 | } | ||
271 | |||
272 | #[test] | ||
273 | fn complete_label_in_for_iterable() { | ||
274 | check( | ||
275 | r#" | ||
276 | fn foo() { | ||
277 | 'outer: for _ in [{ 'inner: loop { break '$0 } }] {} | ||
278 | } | ||
279 | "#, | ||
280 | expect![[r#" | ||
281 | lb 'inner | ||
282 | "#]], | ||
283 | ); | ||
284 | } | ||
285 | } | ||
diff --git a/crates/ide_completion/src/completions/pattern.rs b/crates/ide_completion/src/completions/pattern.rs index 476eecff0..b06498e6d 100644 --- a/crates/ide_completion/src/completions/pattern.rs +++ b/crates/ide_completion/src/completions/pattern.rs | |||
@@ -1,4 +1,4 @@ | |||
1 | //! Completes constats and paths in patterns. | 1 | //! Completes constants and paths in patterns. |
2 | 2 | ||
3 | use crate::{CompletionContext, Completions}; | 3 | use crate::{CompletionContext, Completions}; |
4 | 4 | ||
diff --git a/crates/ide_completion/src/context.rs b/crates/ide_completion/src/context.rs index 6d57da06a..67e2d6f6c 100644 --- a/crates/ide_completion/src/context.rs +++ b/crates/ide_completion/src/context.rs | |||
@@ -4,8 +4,11 @@ use hir::{Local, ScopeDef, Semantics, SemanticsScope, Type}; | |||
4 | use ide_db::base_db::{FilePosition, SourceDatabase}; | 4 | use ide_db::base_db::{FilePosition, SourceDatabase}; |
5 | use ide_db::{call_info::ActiveParameter, RootDatabase}; | 5 | use ide_db::{call_info::ActiveParameter, RootDatabase}; |
6 | use syntax::{ | 6 | use syntax::{ |
7 | algo::find_node_at_offset, ast, match_ast, AstNode, NodeOrToken, SyntaxKind::*, SyntaxNode, | 7 | algo::find_node_at_offset, |
8 | SyntaxToken, TextRange, TextSize, | 8 | ast::{self, NameOrNameRef, NameOwner}, |
9 | match_ast, AstNode, NodeOrToken, | ||
10 | SyntaxKind::*, | ||
11 | SyntaxNode, SyntaxToken, TextRange, TextSize, | ||
9 | }; | 12 | }; |
10 | 13 | ||
11 | use text_edit::Indel; | 14 | use text_edit::Indel; |
@@ -35,18 +38,22 @@ pub(crate) struct CompletionContext<'a> { | |||
35 | /// The token before the cursor, in the macro-expanded file. | 38 | /// The token before the cursor, in the macro-expanded file. |
36 | pub(super) token: SyntaxToken, | 39 | pub(super) token: SyntaxToken, |
37 | pub(super) krate: Option<hir::Crate>, | 40 | pub(super) krate: Option<hir::Crate>, |
38 | pub(super) expected_name: Option<String>, | 41 | pub(super) expected_name: Option<NameOrNameRef>, |
39 | pub(super) expected_type: Option<Type>, | 42 | pub(super) expected_type: Option<Type>, |
40 | pub(super) name_ref_syntax: Option<ast::NameRef>, | 43 | pub(super) name_ref_syntax: Option<ast::NameRef>, |
44 | pub(super) lifetime_syntax: Option<ast::Lifetime>, | ||
45 | pub(super) lifetime_param_syntax: Option<ast::LifetimeParam>, | ||
41 | pub(super) function_syntax: Option<ast::Fn>, | 46 | pub(super) function_syntax: Option<ast::Fn>, |
42 | pub(super) use_item_syntax: Option<ast::Use>, | 47 | pub(super) use_item_syntax: Option<ast::Use>, |
43 | pub(super) record_lit_syntax: Option<ast::RecordExpr>, | 48 | pub(super) record_lit_syntax: Option<ast::RecordExpr>, |
44 | pub(super) record_pat_syntax: Option<ast::RecordPat>, | 49 | pub(super) record_pat_syntax: Option<ast::RecordPat>, |
45 | pub(super) record_field_syntax: Option<ast::RecordExprField>, | 50 | pub(super) record_field_syntax: Option<ast::RecordExprField>, |
46 | pub(super) impl_def: Option<ast::Impl>, | 51 | pub(super) impl_def: Option<ast::Impl>, |
52 | pub(super) lifetime_allowed: bool, | ||
47 | /// FIXME: `ActiveParameter` is string-based, which is very very wrong | 53 | /// FIXME: `ActiveParameter` is string-based, which is very very wrong |
48 | pub(super) active_parameter: Option<ActiveParameter>, | 54 | pub(super) active_parameter: Option<ActiveParameter>, |
49 | pub(super) is_param: bool, | 55 | pub(super) is_param: bool, |
56 | pub(super) is_label_ref: bool, | ||
50 | /// If a name-binding or reference to a const in a pattern. | 57 | /// If a name-binding or reference to a const in a pattern. |
51 | /// Irrefutable patterns (like let) are excluded. | 58 | /// Irrefutable patterns (like let) are excluded. |
52 | pub(super) is_pat_binding_or_const: bool, | 59 | pub(super) is_pat_binding_or_const: bool, |
@@ -136,9 +143,12 @@ impl<'a> CompletionContext<'a> { | |||
136 | original_token, | 143 | original_token, |
137 | token, | 144 | token, |
138 | krate, | 145 | krate, |
146 | lifetime_allowed: false, | ||
139 | expected_name: None, | 147 | expected_name: None, |
140 | expected_type: None, | 148 | expected_type: None, |
141 | name_ref_syntax: None, | 149 | name_ref_syntax: None, |
150 | lifetime_syntax: None, | ||
151 | lifetime_param_syntax: None, | ||
142 | function_syntax: None, | 152 | function_syntax: None, |
143 | use_item_syntax: None, | 153 | use_item_syntax: None, |
144 | record_lit_syntax: None, | 154 | record_lit_syntax: None, |
@@ -146,6 +156,7 @@ impl<'a> CompletionContext<'a> { | |||
146 | record_field_syntax: None, | 156 | record_field_syntax: None, |
147 | impl_def: None, | 157 | impl_def: None, |
148 | active_parameter: ActiveParameter::at(db, position), | 158 | active_parameter: ActiveParameter::at(db, position), |
159 | is_label_ref: false, | ||
149 | is_param: false, | 160 | is_param: false, |
150 | is_pat_binding_or_const: false, | 161 | is_pat_binding_or_const: false, |
151 | is_irrefutable_pat_binding: false, | 162 | is_irrefutable_pat_binding: false, |
@@ -241,7 +252,7 @@ impl<'a> CompletionContext<'a> { | |||
241 | pub(crate) fn source_range(&self) -> TextRange { | 252 | pub(crate) fn source_range(&self) -> TextRange { |
242 | // check kind of macro-expanded token, but use range of original token | 253 | // check kind of macro-expanded token, but use range of original token |
243 | let kind = self.token.kind(); | 254 | let kind = self.token.kind(); |
244 | if kind == IDENT || kind == UNDERSCORE || kind.is_keyword() { | 255 | if kind == IDENT || kind == LIFETIME_IDENT || kind == UNDERSCORE || kind.is_keyword() { |
245 | cov_mark::hit!(completes_if_prefix_is_keyword); | 256 | cov_mark::hit!(completes_if_prefix_is_keyword); |
246 | self.original_token.text_range() | 257 | self.original_token.text_range() |
247 | } else { | 258 | } else { |
@@ -292,13 +303,13 @@ impl<'a> CompletionContext<'a> { | |||
292 | file_with_fake_ident: SyntaxNode, | 303 | file_with_fake_ident: SyntaxNode, |
293 | offset: TextSize, | 304 | offset: TextSize, |
294 | ) { | 305 | ) { |
295 | let expected = { | 306 | let (expected_type, expected_name) = { |
296 | let mut node = match self.token.parent() { | 307 | let mut node = match self.token.parent() { |
297 | Some(it) => it, | 308 | Some(it) => it, |
298 | None => return, | 309 | None => return, |
299 | }; | 310 | }; |
300 | loop { | 311 | loop { |
301 | let ret = match_ast! { | 312 | break match_ast! { |
302 | match node { | 313 | match node { |
303 | ast::LetStmt(it) => { | 314 | ast::LetStmt(it) => { |
304 | cov_mark::hit!(expected_type_let_with_leading_char); | 315 | cov_mark::hit!(expected_type_let_with_leading_char); |
@@ -306,7 +317,7 @@ impl<'a> CompletionContext<'a> { | |||
306 | let ty = it.pat() | 317 | let ty = it.pat() |
307 | .and_then(|pat| self.sema.type_of_pat(&pat)); | 318 | .and_then(|pat| self.sema.type_of_pat(&pat)); |
308 | let name = if let Some(ast::Pat::IdentPat(ident)) = it.pat() { | 319 | let name = if let Some(ast::Pat::IdentPat(ident)) = it.pat() { |
309 | Some(ident.syntax().text().to_string()) | 320 | ident.name().map(NameOrNameRef::Name) |
310 | } else { | 321 | } else { |
311 | None | 322 | None |
312 | }; | 323 | }; |
@@ -319,7 +330,10 @@ impl<'a> CompletionContext<'a> { | |||
319 | ActiveParameter::at_token( | 330 | ActiveParameter::at_token( |
320 | &self.sema, | 331 | &self.sema, |
321 | self.token.clone(), | 332 | self.token.clone(), |
322 | ).map(|ap| (Some(ap.ty), Some(ap.name))) | 333 | ).map(|ap| { |
334 | let name = ap.ident().map(NameOrNameRef::Name); | ||
335 | (Some(ap.ty), name) | ||
336 | }) | ||
323 | .unwrap_or((None, None)) | 337 | .unwrap_or((None, None)) |
324 | }, | 338 | }, |
325 | ast::RecordExprFieldList(_it) => { | 339 | ast::RecordExprFieldList(_it) => { |
@@ -327,10 +341,10 @@ impl<'a> CompletionContext<'a> { | |||
327 | self.token.prev_sibling_or_token() | 341 | self.token.prev_sibling_or_token() |
328 | .and_then(|se| se.into_node()) | 342 | .and_then(|se| se.into_node()) |
329 | .and_then(|node| ast::RecordExprField::cast(node)) | 343 | .and_then(|node| ast::RecordExprField::cast(node)) |
330 | .and_then(|rf| self.sema.resolve_record_field(&rf)) | 344 | .and_then(|rf| self.sema.resolve_record_field(&rf).zip(Some(rf))) |
331 | .map(|f|( | 345 | .map(|(f, rf)|( |
332 | Some(f.0.signature_ty(self.db)), | 346 | Some(f.0.signature_ty(self.db)), |
333 | Some(f.0.name(self.db).to_string()), | 347 | rf.field_name().map(NameOrNameRef::NameRef), |
334 | )) | 348 | )) |
335 | .unwrap_or((None, None)) | 349 | .unwrap_or((None, None)) |
336 | }, | 350 | }, |
@@ -340,7 +354,7 @@ impl<'a> CompletionContext<'a> { | |||
340 | .resolve_record_field(&it) | 354 | .resolve_record_field(&it) |
341 | .map(|f|( | 355 | .map(|f|( |
342 | Some(f.0.signature_ty(self.db)), | 356 | Some(f.0.signature_ty(self.db)), |
343 | Some(f.0.name(self.db).to_string()), | 357 | it.field_name().map(NameOrNameRef::NameRef), |
344 | )) | 358 | )) |
345 | .unwrap_or((None, None)) | 359 | .unwrap_or((None, None)) |
346 | }, | 360 | }, |
@@ -378,14 +392,17 @@ impl<'a> CompletionContext<'a> { | |||
378 | }, | 392 | }, |
379 | } | 393 | } |
380 | }; | 394 | }; |
381 | |||
382 | break ret; | ||
383 | } | 395 | } |
384 | }; | 396 | }; |
385 | self.expected_type = expected.0; | 397 | self.expected_type = expected_type; |
386 | self.expected_name = expected.1; | 398 | self.expected_name = expected_name; |
387 | self.attribute_under_caret = find_node_at_offset(&file_with_fake_ident, offset); | 399 | self.attribute_under_caret = find_node_at_offset(&file_with_fake_ident, offset); |
388 | 400 | ||
401 | if let Some(lifetime) = find_node_at_offset::<ast::Lifetime>(&file_with_fake_ident, offset) | ||
402 | { | ||
403 | self.classify_lifetime(original_file, lifetime, offset); | ||
404 | } | ||
405 | |||
389 | // First, let's try to complete a reference to some declaration. | 406 | // First, let's try to complete a reference to some declaration. |
390 | if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(&file_with_fake_ident, offset) { | 407 | if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(&file_with_fake_ident, offset) { |
391 | // Special case, `trait T { fn foo(i_am_a_name_ref) {} }`. | 408 | // Special case, `trait T { fn foo(i_am_a_name_ref) {} }`. |
@@ -445,6 +462,35 @@ impl<'a> CompletionContext<'a> { | |||
445 | } | 462 | } |
446 | } | 463 | } |
447 | 464 | ||
465 | fn classify_lifetime( | ||
466 | &mut self, | ||
467 | original_file: &SyntaxNode, | ||
468 | lifetime: ast::Lifetime, | ||
469 | offset: TextSize, | ||
470 | ) { | ||
471 | self.lifetime_syntax = | ||
472 | find_node_at_offset(original_file, lifetime.syntax().text_range().start()); | ||
473 | if let Some(parent) = lifetime.syntax().parent() { | ||
474 | if parent.kind() == syntax::SyntaxKind::ERROR { | ||
475 | return; | ||
476 | } | ||
477 | |||
478 | match_ast! { | ||
479 | match parent { | ||
480 | ast::LifetimeParam(_it) => { | ||
481 | self.lifetime_allowed = true; | ||
482 | self.lifetime_param_syntax = | ||
483 | self.sema.find_node_at_offset_with_macros(original_file, offset); | ||
484 | }, | ||
485 | ast::BreakExpr(_it) => self.is_label_ref = true, | ||
486 | ast::ContinueExpr(_it) => self.is_label_ref = true, | ||
487 | ast::Label(_it) => (), | ||
488 | _ => self.lifetime_allowed = true, | ||
489 | } | ||
490 | } | ||
491 | } | ||
492 | } | ||
493 | |||
448 | fn classify_name_ref( | 494 | fn classify_name_ref( |
449 | &mut self, | 495 | &mut self, |
450 | original_file: &SyntaxNode, | 496 | original_file: &SyntaxNode, |
@@ -452,11 +498,11 @@ impl<'a> CompletionContext<'a> { | |||
452 | offset: TextSize, | 498 | offset: TextSize, |
453 | ) { | 499 | ) { |
454 | self.name_ref_syntax = | 500 | self.name_ref_syntax = |
455 | find_node_at_offset(&original_file, name_ref.syntax().text_range().start()); | 501 | find_node_at_offset(original_file, name_ref.syntax().text_range().start()); |
456 | let name_range = name_ref.syntax().text_range(); | 502 | let name_range = name_ref.syntax().text_range(); |
457 | if ast::RecordExprField::for_field_name(&name_ref).is_some() { | 503 | if ast::RecordExprField::for_field_name(&name_ref).is_some() { |
458 | self.record_lit_syntax = | 504 | self.record_lit_syntax = |
459 | self.sema.find_node_at_offset_with_macros(&original_file, offset); | 505 | self.sema.find_node_at_offset_with_macros(original_file, offset); |
460 | } | 506 | } |
461 | 507 | ||
462 | self.fill_impl_def(); | 508 | self.fill_impl_def(); |
@@ -631,7 +677,9 @@ mod tests { | |||
631 | .map(|t| t.display_test(&db).to_string()) | 677 | .map(|t| t.display_test(&db).to_string()) |
632 | .unwrap_or("?".to_owned()); | 678 | .unwrap_or("?".to_owned()); |
633 | 679 | ||
634 | let name = completion_context.expected_name.unwrap_or("?".to_owned()); | 680 | let name = completion_context |
681 | .expected_name | ||
682 | .map_or_else(|| "?".to_owned(), |name| name.to_string()); | ||
635 | 683 | ||
636 | expect.assert_eq(&format!("ty: {}, name: {}", ty, name)); | 684 | expect.assert_eq(&format!("ty: {}, name: {}", ty, name)); |
637 | } | 685 | } |
diff --git a/crates/ide_completion/src/lib.rs b/crates/ide_completion/src/lib.rs index 9ecd82b06..87cddb98e 100644 --- a/crates/ide_completion/src/lib.rs +++ b/crates/ide_completion/src/lib.rs | |||
@@ -133,6 +133,8 @@ pub fn completions( | |||
133 | completions::trait_impl::complete_trait_impl(&mut acc, &ctx); | 133 | completions::trait_impl::complete_trait_impl(&mut acc, &ctx); |
134 | completions::mod_::complete_mod(&mut acc, &ctx); | 134 | completions::mod_::complete_mod(&mut acc, &ctx); |
135 | completions::flyimport::import_on_the_fly(&mut acc, &ctx); | 135 | completions::flyimport::import_on_the_fly(&mut acc, &ctx); |
136 | completions::lifetime::complete_lifetime(&mut acc, &ctx); | ||
137 | completions::lifetime::complete_label(&mut acc, &ctx); | ||
136 | 138 | ||
137 | Some(acc) | 139 | Some(acc) |
138 | } | 140 | } |
diff --git a/crates/ide_completion/src/render.rs b/crates/ide_completion/src/render.rs index 12921e12b..23e00aa47 100644 --- a/crates/ide_completion/src/render.rs +++ b/crates/ide_completion/src/render.rs | |||
@@ -219,6 +219,7 @@ impl<'a> Render<'a> { | |||
219 | hir::GenericParam::ConstParam(_) => SymbolKind::ConstParam, | 219 | hir::GenericParam::ConstParam(_) => SymbolKind::ConstParam, |
220 | }), | 220 | }), |
221 | ScopeDef::Local(..) => CompletionItemKind::SymbolKind(SymbolKind::Local), | 221 | ScopeDef::Local(..) => CompletionItemKind::SymbolKind(SymbolKind::Local), |
222 | ScopeDef::Label(..) => CompletionItemKind::SymbolKind(SymbolKind::Label), | ||
222 | ScopeDef::AdtSelfType(..) | ScopeDef::ImplSelfType(..) => { | 223 | ScopeDef::AdtSelfType(..) | ScopeDef::ImplSelfType(..) => { |
223 | CompletionItemKind::SymbolKind(SymbolKind::SelfParam) | 224 | CompletionItemKind::SymbolKind(SymbolKind::SelfParam) |
224 | } | 225 | } |
@@ -243,7 +244,7 @@ impl<'a> Render<'a> { | |||
243 | 244 | ||
244 | item.set_relevance(CompletionRelevance { | 245 | item.set_relevance(CompletionRelevance { |
245 | exact_type_match: compute_exact_type_match(self.ctx.completion, &ty), | 246 | exact_type_match: compute_exact_type_match(self.ctx.completion, &ty), |
246 | exact_name_match: compute_exact_name_match(self.ctx.completion, local_name.clone()), | 247 | exact_name_match: compute_exact_name_match(self.ctx.completion, &local_name), |
247 | is_local: true, | 248 | is_local: true, |
248 | ..CompletionRelevance::default() | 249 | ..CompletionRelevance::default() |
249 | }); | 250 | }); |
@@ -319,8 +320,7 @@ fn compute_exact_type_match(ctx: &CompletionContext, completion_ty: &hir::Type) | |||
319 | 320 | ||
320 | fn compute_exact_name_match(ctx: &CompletionContext, completion_name: impl Into<String>) -> bool { | 321 | fn compute_exact_name_match(ctx: &CompletionContext, completion_name: impl Into<String>) -> bool { |
321 | let completion_name = completion_name.into(); | 322 | let completion_name = completion_name.into(); |
322 | 323 | ctx.expected_name.as_ref().map_or(false, |name| name.text() == completion_name) | |
323 | Some(&completion_name) == ctx.expected_name.as_ref() | ||
324 | } | 324 | } |
325 | 325 | ||
326 | fn compute_ref_match(ctx: &CompletionContext, completion_ty: &hir::Type) -> Option<Mutability> { | 326 | fn compute_ref_match(ctx: &CompletionContext, completion_ty: &hir::Type) -> Option<Mutability> { |
diff --git a/crates/ide_db/src/call_info.rs b/crates/ide_db/src/call_info.rs index 7e26c3ccf..e583a52f4 100644 --- a/crates/ide_db/src/call_info.rs +++ b/crates/ide_db/src/call_info.rs | |||
@@ -4,7 +4,7 @@ use either::Either; | |||
4 | use hir::{HasAttrs, HirDisplay, Semantics, Type}; | 4 | use hir::{HasAttrs, HirDisplay, Semantics, Type}; |
5 | use stdx::format_to; | 5 | use stdx::format_to; |
6 | use syntax::{ | 6 | use syntax::{ |
7 | ast::{self, ArgListOwner}, | 7 | ast::{self, ArgListOwner, NameOwner}, |
8 | match_ast, AstNode, SyntaxNode, SyntaxToken, TextRange, TextSize, | 8 | match_ast, AstNode, SyntaxNode, SyntaxToken, TextRange, TextSize, |
9 | }; | 9 | }; |
10 | 10 | ||
@@ -142,7 +142,7 @@ fn call_info_impl( | |||
142 | #[derive(Debug)] | 142 | #[derive(Debug)] |
143 | pub struct ActiveParameter { | 143 | pub struct ActiveParameter { |
144 | pub ty: Type, | 144 | pub ty: Type, |
145 | pub name: String, | 145 | pub pat: Either<ast::SelfParam, ast::Pat>, |
146 | } | 146 | } |
147 | 147 | ||
148 | impl ActiveParameter { | 148 | impl ActiveParameter { |
@@ -165,8 +165,14 @@ impl ActiveParameter { | |||
165 | return None; | 165 | return None; |
166 | } | 166 | } |
167 | let (pat, ty) = params.swap_remove(idx); | 167 | let (pat, ty) = params.swap_remove(idx); |
168 | let name = pat?.to_string(); | 168 | pat.map(|pat| ActiveParameter { ty, pat }) |
169 | Some(ActiveParameter { ty, name }) | 169 | } |
170 | |||
171 | pub fn ident(&self) -> Option<ast::Name> { | ||
172 | self.pat.as_ref().right().and_then(|param| match param { | ||
173 | ast::Pat::IdentPat(ident) => ident.name(), | ||
174 | _ => None, | ||
175 | }) | ||
170 | } | 176 | } |
171 | } | 177 | } |
172 | 178 | ||
diff --git a/crates/mbe/src/expander/matcher.rs b/crates/mbe/src/expander/matcher.rs index b6782b4ba..1682b21b0 100644 --- a/crates/mbe/src/expander/matcher.rs +++ b/crates/mbe/src/expander/matcher.rs | |||
@@ -762,7 +762,7 @@ impl<'a> TtIter<'a> { | |||
762 | fn expect_separator(&mut self, separator: &Separator, idx: usize) -> bool { | 762 | fn expect_separator(&mut self, separator: &Separator, idx: usize) -> bool { |
763 | let mut fork = self.clone(); | 763 | let mut fork = self.clone(); |
764 | let ok = match separator { | 764 | let ok = match separator { |
765 | Separator::Ident(lhs) if idx == 0 => match fork.expect_ident() { | 765 | Separator::Ident(lhs) if idx == 0 => match fork.expect_ident_or_underscore() { |
766 | Ok(rhs) => rhs.text == lhs.text, | 766 | Ok(rhs) => rhs.text == lhs.text, |
767 | _ => false, | 767 | _ => false, |
768 | }, | 768 | }, |
@@ -852,7 +852,7 @@ impl<'a> TtIter<'a> { | |||
852 | if punct.char != '\'' { | 852 | if punct.char != '\'' { |
853 | return Err(()); | 853 | return Err(()); |
854 | } | 854 | } |
855 | let ident = self.expect_ident()?; | 855 | let ident = self.expect_ident_or_underscore()?; |
856 | 856 | ||
857 | Ok(tt::Subtree { | 857 | Ok(tt::Subtree { |
858 | delimiter: None, | 858 | delimiter: None, |
diff --git a/crates/mbe/src/parser.rs b/crates/mbe/src/parser.rs index 7b5b8ec16..c88387653 100644 --- a/crates/mbe/src/parser.rs +++ b/crates/mbe/src/parser.rs | |||
@@ -177,16 +177,8 @@ fn next_op<'a>(first: &tt::TokenTree, src: &mut TtIter<'a>, mode: Mode) -> Resul | |||
177 | Op::Repeat { tokens: MetaTemplate(tokens), separator, kind } | 177 | Op::Repeat { tokens: MetaTemplate(tokens), separator, kind } |
178 | } | 178 | } |
179 | tt::TokenTree::Leaf(leaf) => match leaf { | 179 | tt::TokenTree::Leaf(leaf) => match leaf { |
180 | tt::Leaf::Punct(punct) => { | 180 | tt::Leaf::Punct(_) => { |
181 | static UNDERSCORE: SmolStr = SmolStr::new_inline("_"); | 181 | return Err(ParseError::Expected("ident".to_string())); |
182 | |||
183 | if punct.char != '_' { | ||
184 | return Err(ParseError::Expected("_".to_string())); | ||
185 | } | ||
186 | let name = UNDERSCORE.clone(); | ||
187 | let kind = eat_fragment_kind(src, mode)?; | ||
188 | let id = punct.id; | ||
189 | Op::Var { name, kind, id } | ||
190 | } | 182 | } |
191 | tt::Leaf::Ident(ident) if ident.text == "crate" => { | 183 | tt::Leaf::Ident(ident) if ident.text == "crate" => { |
192 | // We simply produce identifier `$crate` here. And it will be resolved when lowering ast to Path. | 184 | // We simply produce identifier `$crate` here. And it will be resolved when lowering ast to Path. |
diff --git a/crates/mbe/src/subtree_source.rs b/crates/mbe/src/subtree_source.rs index d7433bd35..a05cab0f3 100644 --- a/crates/mbe/src/subtree_source.rs +++ b/crates/mbe/src/subtree_source.rs | |||
@@ -150,6 +150,7 @@ fn convert_ident(ident: &tt::Ident) -> TtToken { | |||
150 | let kind = match ident.text.as_ref() { | 150 | let kind = match ident.text.as_ref() { |
151 | "true" => T![true], | 151 | "true" => T![true], |
152 | "false" => T![false], | 152 | "false" => T![false], |
153 | "_" => UNDERSCORE, | ||
153 | i if i.starts_with('\'') => LIFETIME_IDENT, | 154 | i if i.starts_with('\'') => LIFETIME_IDENT, |
154 | _ => SyntaxKind::from_keyword(ident.text.as_str()).unwrap_or(IDENT), | 155 | _ => SyntaxKind::from_keyword(ident.text.as_str()).unwrap_or(IDENT), |
155 | }; | 156 | }; |
diff --git a/crates/mbe/src/syntax_bridge.rs b/crates/mbe/src/syntax_bridge.rs index 85163c4b3..8bba3d3d5 100644 --- a/crates/mbe/src/syntax_bridge.rs +++ b/crates/mbe/src/syntax_bridge.rs | |||
@@ -350,7 +350,7 @@ trait TokenConvertor { | |||
350 | return; | 350 | return; |
351 | } | 351 | } |
352 | 352 | ||
353 | result.push(if k.is_punct() { | 353 | result.push(if k.is_punct() && k != UNDERSCORE { |
354 | assert_eq!(range.len(), TextSize::of('.')); | 354 | assert_eq!(range.len(), TextSize::of('.')); |
355 | let delim = match k { | 355 | let delim = match k { |
356 | T!['('] => Some((tt::DelimiterKind::Parenthesis, T![')'])), | 356 | T!['('] => Some((tt::DelimiterKind::Parenthesis, T![')'])), |
@@ -395,7 +395,9 @@ trait TokenConvertor { | |||
395 | { | 395 | { |
396 | tt::Spacing::Alone | 396 | tt::Spacing::Alone |
397 | } | 397 | } |
398 | Some(next) if next.kind().is_punct() => tt::Spacing::Joint, | 398 | Some(next) if next.kind().is_punct() && next.kind() != UNDERSCORE => { |
399 | tt::Spacing::Joint | ||
400 | } | ||
399 | _ => tt::Spacing::Alone, | 401 | _ => tt::Spacing::Alone, |
400 | }; | 402 | }; |
401 | let char = match token.to_char() { | 403 | let char = match token.to_char() { |
@@ -415,6 +417,7 @@ trait TokenConvertor { | |||
415 | let leaf: tt::Leaf = match k { | 417 | let leaf: tt::Leaf = match k { |
416 | T![true] | T![false] => make_leaf!(Ident), | 418 | T![true] | T![false] => make_leaf!(Ident), |
417 | IDENT => make_leaf!(Ident), | 419 | IDENT => make_leaf!(Ident), |
420 | UNDERSCORE => make_leaf!(Ident), | ||
418 | k if k.is_keyword() => make_leaf!(Ident), | 421 | k if k.is_keyword() => make_leaf!(Ident), |
419 | k if k.is_literal() => make_leaf!(Literal), | 422 | k if k.is_literal() => make_leaf!(Literal), |
420 | LIFETIME_IDENT => { | 423 | LIFETIME_IDENT => { |
diff --git a/crates/mbe/src/tests/expand.rs b/crates/mbe/src/tests/expand.rs index 9dd8ff75b..2cce62781 100644 --- a/crates/mbe/src/tests/expand.rs +++ b/crates/mbe/src/tests/expand.rs | |||
@@ -1080,6 +1080,12 @@ macro_rules! q { | |||
1080 | } | 1080 | } |
1081 | 1081 | ||
1082 | #[test] | 1082 | #[test] |
1083 | fn test_underscore_lifetime() { | ||
1084 | parse_macro(r#"macro_rules! q { ($a:lifetime) => {0}; }"#) | ||
1085 | .assert_expand_items(r#"q!['_]"#, r#"0"#); | ||
1086 | } | ||
1087 | |||
1088 | #[test] | ||
1083 | fn test_vertical_bar_with_pat() { | 1089 | fn test_vertical_bar_with_pat() { |
1084 | parse_macro( | 1090 | parse_macro( |
1085 | r#" | 1091 | r#" |
diff --git a/crates/mbe/src/tests/rule.rs b/crates/mbe/src/tests/rule.rs index 07277966d..bf48112b3 100644 --- a/crates/mbe/src/tests/rule.rs +++ b/crates/mbe/src/tests/rule.rs | |||
@@ -12,6 +12,9 @@ fn test_valid_arms() { | |||
12 | } | 12 | } |
13 | 13 | ||
14 | check("($i:ident) => ()"); | 14 | check("($i:ident) => ()"); |
15 | check("($(x),*) => ()"); | ||
16 | check("($(x)_*) => ()"); | ||
17 | check("($(x)i*) => ()"); | ||
15 | check("($($i:ident)*) => ($_)"); | 18 | check("($($i:ident)*) => ($_)"); |
16 | check("($($true:ident)*) => ($true)"); | 19 | check("($($true:ident)*) => ($true)"); |
17 | check("($($false:ident)*) => ($false)"); | 20 | check("($($false:ident)*) => ($false)"); |
@@ -32,6 +35,7 @@ fn test_invalid_arms() { | |||
32 | 35 | ||
33 | check("($i) => ($i)", ParseError::UnexpectedToken("bad fragment specifier 1".into())); | 36 | check("($i) => ($i)", ParseError::UnexpectedToken("bad fragment specifier 1".into())); |
34 | check("($i:) => ($i)", ParseError::UnexpectedToken("bad fragment specifier 1".into())); | 37 | check("($i:) => ($i)", ParseError::UnexpectedToken("bad fragment specifier 1".into())); |
38 | check("($i:_) => ()", ParseError::UnexpectedToken("bad fragment specifier 1".into())); | ||
35 | } | 39 | } |
36 | 40 | ||
37 | fn parse_macro_arm(arm_definition: &str) -> Result<crate::MacroRules, ParseError> { | 41 | fn parse_macro_arm(arm_definition: &str) -> Result<crate::MacroRules, ParseError> { |
diff --git a/crates/mbe/src/tt_iter.rs b/crates/mbe/src/tt_iter.rs index a362d31fc..319a40f2a 100644 --- a/crates/mbe/src/tt_iter.rs +++ b/crates/mbe/src/tt_iter.rs | |||
@@ -50,6 +50,13 @@ impl<'a> TtIter<'a> { | |||
50 | 50 | ||
51 | pub(crate) fn expect_ident(&mut self) -> Result<&'a tt::Ident, ()> { | 51 | pub(crate) fn expect_ident(&mut self) -> Result<&'a tt::Ident, ()> { |
52 | match self.expect_leaf()? { | 52 | match self.expect_leaf()? { |
53 | tt::Leaf::Ident(it) if it.text != "_" => Ok(it), | ||
54 | _ => Err(()), | ||
55 | } | ||
56 | } | ||
57 | |||
58 | pub(crate) fn expect_ident_or_underscore(&mut self) -> Result<&'a tt::Ident, ()> { | ||
59 | match self.expect_leaf()? { | ||
53 | tt::Leaf::Ident(it) => Ok(it), | 60 | tt::Leaf::Ident(it) => Ok(it), |
54 | _ => Err(()), | 61 | _ => Err(()), |
55 | } | 62 | } |
diff --git a/crates/proc_macro_srv/src/rustc_server.rs b/crates/proc_macro_srv/src/rustc_server.rs index ceefd187d..c147484c0 100644 --- a/crates/proc_macro_srv/src/rustc_server.rs +++ b/crates/proc_macro_srv/src/rustc_server.rs | |||
@@ -805,5 +805,14 @@ mod tests { | |||
805 | let t2 = TokenStream::from_str("(a);").unwrap(); | 805 | let t2 = TokenStream::from_str("(a);").unwrap(); |
806 | assert_eq!(t2.token_trees.len(), 2); | 806 | assert_eq!(t2.token_trees.len(), 2); |
807 | assert_eq!(t2.token_trees[0], subtree_paren_a); | 807 | assert_eq!(t2.token_trees[0], subtree_paren_a); |
808 | |||
809 | let underscore = TokenStream::from_str("_").unwrap(); | ||
810 | assert_eq!( | ||
811 | underscore.token_trees[0], | ||
812 | tt::TokenTree::Leaf(tt::Leaf::Ident(tt::Ident { | ||
813 | text: "_".into(), | ||
814 | id: tt::TokenId::unspecified(), | ||
815 | })) | ||
816 | ); | ||
808 | } | 817 | } |
809 | } | 818 | } |
diff --git a/crates/syntax/src/ast/node_ext.rs b/crates/syntax/src/ast/node_ext.rs index 01f580a40..42a7b9c2a 100644 --- a/crates/syntax/src/ast/node_ext.rs +++ b/crates/syntax/src/ast/node_ext.rs | |||
@@ -380,6 +380,15 @@ impl fmt::Display for NameOrNameRef { | |||
380 | } | 380 | } |
381 | } | 381 | } |
382 | 382 | ||
383 | impl NameOrNameRef { | ||
384 | pub fn text(&self) -> &str { | ||
385 | match self { | ||
386 | NameOrNameRef::Name(name) => name.text(), | ||
387 | NameOrNameRef::NameRef(name_ref) => name_ref.text(), | ||
388 | } | ||
389 | } | ||
390 | } | ||
391 | |||
383 | impl ast::RecordPatField { | 392 | impl ast::RecordPatField { |
384 | pub fn for_field_name_ref(field_name: &ast::NameRef) -> Option<ast::RecordPatField> { | 393 | pub fn for_field_name_ref(field_name: &ast::NameRef) -> Option<ast::RecordPatField> { |
385 | let candidate = field_name.syntax().parent().and_then(ast::RecordPatField::cast)?; | 394 | let candidate = field_name.syntax().parent().and_then(ast::RecordPatField::cast)?; |