aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/release.yaml3
-rw-r--r--crates/hir/src/lib.rs1
-rw-r--r--crates/hir/src/semantics.rs4
-rw-r--r--crates/hir_def/src/body/scope.rs57
-rw-r--r--crates/hir_def/src/resolver.rs6
-rw-r--r--crates/ide/src/syntax_highlighting/inject.rs2
-rw-r--r--crates/ide_completion/src/completions.rs29
-rw-r--r--crates/ide_completion/src/completions/lifetime.rs285
-rw-r--r--crates/ide_completion/src/completions/pattern.rs2
-rw-r--r--crates/ide_completion/src/context.rs86
-rw-r--r--crates/ide_completion/src/lib.rs2
-rw-r--r--crates/ide_completion/src/render.rs6
-rw-r--r--crates/ide_db/src/call_info.rs14
-rw-r--r--crates/mbe/src/expander/matcher.rs4
-rw-r--r--crates/mbe/src/parser.rs12
-rw-r--r--crates/mbe/src/subtree_source.rs1
-rw-r--r--crates/mbe/src/syntax_bridge.rs7
-rw-r--r--crates/mbe/src/tests/expand.rs6
-rw-r--r--crates/mbe/src/tests/rule.rs4
-rw-r--r--crates/mbe/src/tt_iter.rs7
-rw-r--r--crates/proc_macro_srv/src/rustc_server.rs9
-rw-r--r--crates/syntax/src/ast/node_ext.rs9
22 files changed, 490 insertions, 66 deletions
diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml
index 7549e998b..ae9dccce9 100644
--- a/.github/workflows/release.yaml
+++ b/.github/workflows/release.yaml
@@ -108,9 +108,6 @@ jobs:
108 with: 108 with:
109 node-version: 12.x 109 node-version: 12.x
110 110
111 - name: Print current revision
112 run: git describe --tags
113
114 - name: Dist 111 - name: Dist
115 if: github.ref == 'refs/heads/release' 112 if: github.ref == 'refs/heads/release'
116 run: cargo xtask dist --client 0.2.$GITHUB_RUN_NUMBER 113 run: cargo xtask dist --client 0.2.$GITHUB_RUN_NUMBER
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;
8use crate::{ 8use 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 {
40pub struct ScopeData { 40pub 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
146fn compute_expr_scopes(expr: ExprId, body: &Body, scopes: &mut ExprScopes, scope: ScopeId) { 171fn 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
414impl Scope { 415impl 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
3pub(crate) mod attribute; 3pub(crate) mod attribute;
4pub(crate) mod dot; 4pub(crate) mod dot;
5pub(crate) mod record; 5pub(crate) mod flyimport;
6pub(crate) mod pattern;
7pub(crate) mod fn_param; 6pub(crate) mod fn_param;
8pub(crate) mod keyword; 7pub(crate) mod keyword;
9pub(crate) mod snippet; 8pub(crate) mod lifetime;
10pub(crate) mod qualified_path;
11pub(crate) mod unqualified_path;
12pub(crate) mod postfix;
13pub(crate) mod macro_in_item_position; 9pub(crate) mod macro_in_item_position;
14pub(crate) mod trait_impl;
15pub(crate) mod mod_; 10pub(crate) mod mod_;
16pub(crate) mod flyimport; 11pub(crate) mod pattern;
12pub(crate) mod postfix;
13pub(crate) mod qualified_path;
14pub(crate) mod record;
15pub(crate) mod snippet;
16pub(crate) mod trait_impl;
17pub(crate) mod unqualified_path;
17 18
18use std::iter; 19use std::iter;
19 20
20use hir::{known, ModPath, ScopeDef, Type}; 21use hir::{known, ModPath, ScopeDef, Type};
22use ide_db::SymbolKind;
21 23
22use crate::{ 24use 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.
2use hir::ScopeDef;
3
4use crate::{completions::Completions, context::CompletionContext};
5
6/// Completes lifetimes.
7pub(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.
33pub(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)]
45mod 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#"
67fn func<'lifetime>(foo: &'li$0) {}
68"#,
69 r#"
70fn func<'lifetime>(foo: &'lifetime) {}
71"#,
72 );
73 }
74
75 #[test]
76 fn complete_lifetime_in_ref() {
77 check(
78 r#"
79fn 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#"
92fn 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#"
104struct Foo;
105impl<'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#"
121struct Foo<'lt>;
122fn 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#"
135fn 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#"
148fn foo2<'lifetime, T>() where T: 'a$0 {}
149"#,
150 expect![[r#"
151 lt 'lifetime
152 lt 'static
153 "#]],
154 );
155 check(
156 r#"
157fn 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#"
170fn 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#"
180fn foo<'a$0>() {}
181"#,
182 expect![[r#""#]],
183 );
184 check(
185 r#"
186fn 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#"
198fn foo() {
199 'foop: loop {
200 break '$0
201 }
202}
203"#,
204 expect![[r#"
205 lb 'foop
206 "#]],
207 );
208 check(
209 r#"
210fn 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#"
226fn 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#"
245fn 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#"
261fn 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#"
276fn 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
3use crate::{CompletionContext, Completions}; 3use 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};
4use ide_db::base_db::{FilePosition, SourceDatabase}; 4use ide_db::base_db::{FilePosition, SourceDatabase};
5use ide_db::{call_info::ActiveParameter, RootDatabase}; 5use ide_db::{call_info::ActiveParameter, RootDatabase};
6use syntax::{ 6use 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
11use text_edit::Indel; 14use 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
320fn compute_exact_name_match(ctx: &CompletionContext, completion_name: impl Into<String>) -> bool { 321fn 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
326fn compute_ref_match(ctx: &CompletionContext, completion_ty: &hir::Type) -> Option<Mutability> { 326fn 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;
4use hir::{HasAttrs, HirDisplay, Semantics, Type}; 4use hir::{HasAttrs, HirDisplay, Semantics, Type};
5use stdx::format_to; 5use stdx::format_to;
6use syntax::{ 6use 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)]
143pub struct ActiveParameter { 143pub 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
148impl ActiveParameter { 148impl 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]
1083fn 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]
1083fn test_vertical_bar_with_pat() { 1089fn 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
37fn parse_macro_arm(arm_definition: &str) -> Result<crate::MacroRules, ParseError> { 41fn 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
383impl 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
383impl ast::RecordPatField { 392impl 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)?;