aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock24
-rw-r--r--crates/completion/src/completions/attribute.rs30
-rw-r--r--crates/completion/src/completions/mod_.rs69
-rw-r--r--crates/hir/src/db.rs4
-rw-r--r--crates/hir/src/diagnostics.rs2
-rw-r--r--crates/hir_def/src/path.rs1
-rw-r--r--crates/hir_def/src/path/lower.rs2
-rw-r--r--crates/hir_expand/src/db.rs12
-rw-r--r--crates/hir_expand/src/hygiene.rs210
-rw-r--r--crates/hir_expand/src/name.rs2
-rw-r--r--crates/hir_ty/src/diagnostics.rs15
-rw-r--r--crates/hir_ty/src/diagnostics/expr.rs27
-rw-r--r--crates/hir_ty/src/tests/macros.rs31
-rw-r--r--crates/ide/src/diagnostics.rs53
-rw-r--r--crates/ide/src/diagnostics/fixes.rs10
-rw-r--r--crates/ide_db/src/apply_change.rs1
-rw-r--r--crates/mbe/src/mbe_expander/matcher.rs2
-rw-r--r--crates/mbe/src/mbe_expander/transcriber.rs27
-rw-r--r--crates/mbe/src/parser.rs15
-rw-r--r--crates/mbe/src/tests.rs13
20 files changed, 399 insertions, 151 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 57383491a..bc38c5a37 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -262,9 +262,9 @@ dependencies = [
262 262
263[[package]] 263[[package]]
264name = "const_fn" 264name = "const_fn"
265version = "0.4.4" 265version = "0.4.5"
266source = "registry+https://github.com/rust-lang/crates.io-index" 266source = "registry+https://github.com/rust-lang/crates.io-index"
267checksum = "cd51eab21ab4fd6a3bf889e2d0958c0a6e3a61ad04260325e919e652a2a62826" 267checksum = "28b9d6de7f49e22cf97ad17fc4036ece69300032f45f78f30b4a4482cdc3f4a6"
268 268
269[[package]] 269[[package]]
270name = "crc32fast" 270name = "crc32fast"
@@ -774,9 +774,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
774 774
775[[package]] 775[[package]]
776name = "libc" 776name = "libc"
777version = "0.2.81" 777version = "0.2.82"
778source = "registry+https://github.com/rust-lang/crates.io-index" 778source = "registry+https://github.com/rust-lang/crates.io-index"
779checksum = "1482821306169ec4d07f6aca392a4681f66c75c9918aa49641a2595db64053cb" 779checksum = "89203f3fba0a3795506acaad8ebce3c80c0af93f994d5a1d7a0b1eeb23271929"
780 780
781[[package]] 781[[package]]
782name = "libloading" 782name = "libloading"
@@ -1134,9 +1134,9 @@ checksum = "28b9b4df73455c861d7cbf8be42f01d3b373ed7f02e378d55fa84eafc6f638b1"
1134 1134
1135[[package]] 1135[[package]]
1136name = "pin-project-lite" 1136name = "pin-project-lite"
1137version = "0.2.0" 1137version = "0.2.1"
1138source = "registry+https://github.com/rust-lang/crates.io-index" 1138source = "registry+https://github.com/rust-lang/crates.io-index"
1139checksum = "6b063f57ec186e6140e2b8b6921e5f1bd89c7356dda5b33acc5401203ca6131c" 1139checksum = "e36743d754ccdf9954c2e352ce2d4b106e024c814f6499c2dadff80da9a442d8"
1140 1140
1141[[package]] 1141[[package]]
1142name = "proc-macro2" 1142name = "proc-macro2"
@@ -1449,9 +1449,9 @@ dependencies = [
1449 1449
1450[[package]] 1450[[package]]
1451name = "semver-parser" 1451name = "semver-parser"
1452version = "0.10.1" 1452version = "0.10.2"
1453source = "registry+https://github.com/rust-lang/crates.io-index" 1453source = "registry+https://github.com/rust-lang/crates.io-index"
1454checksum = "42ef146c2ad5e5f4b037cd6ce2ebb775401729b19a82040c1beac9d36c7d1428" 1454checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7"
1455dependencies = [ 1455dependencies = [
1456 "pest", 1456 "pest",
1457] 1457]
@@ -1558,9 +1558,9 @@ version = "0.0.0"
1558 1558
1559[[package]] 1559[[package]]
1560name = "syn" 1560name = "syn"
1561version = "1.0.57" 1561version = "1.0.58"
1562source = "registry+https://github.com/rust-lang/crates.io-index" 1562source = "registry+https://github.com/rust-lang/crates.io-index"
1563checksum = "4211ce9909eb971f111059df92c45640aad50a619cf55cd76476be803c4c68e6" 1563checksum = "cc60a3d73ea6594cd712d830cc1f0390fd71542d8c8cd24e70cc54cdfd5e05d5"
1564dependencies = [ 1564dependencies = [
1565 "proc-macro2", 1565 "proc-macro2",
1566 "quote", 1566 "quote",
@@ -1643,9 +1643,9 @@ checksum = "db3c46be180f1af9673ebb27bc1235396f61ef6965b3fe0dbb2e624deb604f0e"
1643 1643
1644[[package]] 1644[[package]]
1645name = "thread_local" 1645name = "thread_local"
1646version = "1.0.1" 1646version = "1.1.0"
1647source = "registry+https://github.com/rust-lang/crates.io-index" 1647source = "registry+https://github.com/rust-lang/crates.io-index"
1648checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" 1648checksum = "bb9bc092d0d51e76b2b19d9d85534ffc9ec2db959a2523cdae0697e2972cd447"
1649dependencies = [ 1649dependencies = [
1650 "lazy_static", 1650 "lazy_static",
1651] 1651]
diff --git a/crates/completion/src/completions/attribute.rs b/crates/completion/src/completions/attribute.rs
index 10739750c..3a29b5203 100644
--- a/crates/completion/src/completions/attribute.rs
+++ b/crates/completion/src/completions/attribute.rs
@@ -21,20 +21,15 @@ pub(crate) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext)
21 21
22 let attribute = ctx.attribute_under_caret.as_ref()?; 22 let attribute = ctx.attribute_under_caret.as_ref()?;
23 match (attribute.path(), attribute.token_tree()) { 23 match (attribute.path(), attribute.token_tree()) {
24 (Some(path), Some(token_tree)) if path.to_string() == "derive" => { 24 (Some(path), Some(token_tree)) => match path.to_string().as_str() {
25 complete_derive(acc, ctx, token_tree) 25 "derive" => complete_derive(acc, ctx, token_tree),
26 } 26 "feature" => complete_lint(acc, ctx, token_tree, FEATURES),
27 (Some(path), Some(token_tree)) if path.to_string() == "feature" => { 27 "allow" | "warn" | "deny" | "forbid" => {
28 complete_lint(acc, ctx, token_tree, FEATURES); 28 complete_lint(acc, ctx, token_tree.clone(), DEFAULT_LINT_COMPLETIONS);
29 } 29 complete_lint(acc, ctx, token_tree, CLIPPY_LINTS);
30 (Some(path), Some(token_tree)) 30 }
31 if ["allow", "warn", "deny", "forbid"] 31 _ => {}
32 .iter() 32 },
33 .any(|lint_level| lint_level == &path.to_string()) =>
34 {
35 complete_lint(acc, ctx, token_tree.clone(), DEFAULT_LINT_COMPLETIONS);
36 complete_lint(acc, ctx, token_tree, CLIPPY_LINTS);
37 }
38 (_, Some(_token_tree)) => {} 33 (_, Some(_token_tree)) => {}
39 _ => complete_attribute_start(acc, ctx, attribute), 34 _ => complete_attribute_start(acc, ctx, attribute),
40 } 35 }
@@ -54,11 +49,8 @@ fn complete_attribute_start(acc: &mut Completions, ctx: &CompletionContext, attr
54 item = item.lookup_by(lookup); 49 item = item.lookup_by(lookup);
55 } 50 }
56 51
57 match (attr_completion.snippet, ctx.config.snippet_cap) { 52 if let Some((snippet, cap)) = attr_completion.snippet.zip(ctx.config.snippet_cap) {
58 (Some(snippet), Some(cap)) => { 53 item = item.insert_snippet(cap, snippet);
59 item = item.insert_snippet(cap, snippet);
60 }
61 _ => {}
62 } 54 }
63 55
64 if attribute.kind() == ast::AttrKind::Inner || !attr_completion.prefer_inner { 56 if attribute.kind() == ast::AttrKind::Inner || !attr_completion.prefer_inner {
diff --git a/crates/completion/src/completions/mod_.rs b/crates/completion/src/completions/mod_.rs
index 8c8eaeaa6..00e951ca9 100644
--- a/crates/completion/src/completions/mod_.rs
+++ b/crates/completion/src/completions/mod_.rs
@@ -1,5 +1,7 @@
1//! Completes mod declarations. 1//! Completes mod declarations.
2 2
3use std::iter;
4
3use hir::{Module, ModuleSource}; 5use hir::{Module, ModuleSource};
4use ide_db::base_db::{SourceDatabaseExt, VfsPath}; 6use ide_db::base_db::{SourceDatabaseExt, VfsPath};
5use ide_db::RootDatabase; 7use ide_db::RootDatabase;
@@ -12,9 +14,8 @@ use crate::{context::CompletionContext, item::CompletionKind, Completions};
12/// Complete mod declaration, i.e. `mod $0 ;` 14/// Complete mod declaration, i.e. `mod $0 ;`
13pub(crate) fn complete_mod(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { 15pub(crate) fn complete_mod(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> {
14 let mod_under_caret = match &ctx.mod_declaration_under_caret { 16 let mod_under_caret = match &ctx.mod_declaration_under_caret {
15 Some(mod_under_caret) if mod_under_caret.item_list().is_some() => return None, 17 Some(mod_under_caret) if mod_under_caret.item_list().is_none() => mod_under_caret,
16 Some(mod_under_caret) => mod_under_caret, 18 _ => return None,
17 None => return None,
18 }; 19 };
19 20
20 let _p = profile::span("completion::complete_mod"); 21 let _p = profile::span("completion::complete_mod");
@@ -49,9 +50,13 @@ pub(crate) fn complete_mod(acc: &mut Completions, ctx: &CompletionContext) -> Op
49 .filter_map(|submodule_file| { 50 .filter_map(|submodule_file| {
50 let submodule_path = source_root.path_for_file(&submodule_file)?; 51 let submodule_path = source_root.path_for_file(&submodule_file)?;
51 let directory_with_submodule = submodule_path.parent()?; 52 let directory_with_submodule = submodule_path.parent()?;
52 match submodule_path.name_and_extension()? { 53 let (name, ext) = submodule_path.name_and_extension()?;
53 ("lib", Some("rs")) | ("main", Some("rs")) => None, 54 if ext != Some("rs") {
54 ("mod", Some("rs")) => { 55 return None;
56 }
57 match name {
58 "lib" | "main" => None,
59 "mod" => {
55 if directory_with_submodule.parent()? == directory_to_look_for_submodules { 60 if directory_with_submodule.parent()? == directory_to_look_for_submodules {
56 match directory_with_submodule.name_and_extension()? { 61 match directory_with_submodule.name_and_extension()? {
57 (directory_name, None) => Some(directory_name.to_owned()), 62 (directory_name, None) => Some(directory_name.to_owned()),
@@ -61,9 +66,7 @@ pub(crate) fn complete_mod(acc: &mut Completions, ctx: &CompletionContext) -> Op
61 None 66 None
62 } 67 }
63 } 68 }
64 (file_name, Some("rs")) 69 file_name if directory_with_submodule == directory_to_look_for_submodules => {
65 if directory_with_submodule == directory_to_look_for_submodules =>
66 {
67 Some(file_name.to_owned()) 70 Some(file_name.to_owned())
68 } 71 }
69 _ => None, 72 _ => None,
@@ -73,7 +76,7 @@ pub(crate) fn complete_mod(acc: &mut Completions, ctx: &CompletionContext) -> Op
73 .for_each(|submodule_name| { 76 .for_each(|submodule_name| {
74 let mut label = submodule_name; 77 let mut label = submodule_name;
75 if mod_under_caret.semicolon_token().is_none() { 78 if mod_under_caret.semicolon_token().is_none() {
76 label.push(';') 79 label.push(';');
77 } 80 }
78 CompletionItem::new(CompletionKind::Magic, ctx.source_range(), &label) 81 CompletionItem::new(CompletionKind::Magic, ctx.source_range(), &label)
79 .kind(CompletionItemKind::Module) 82 .kind(CompletionItemKind::Module)
@@ -89,11 +92,13 @@ fn directory_to_look_for_submodules(
89 module_file_path: &VfsPath, 92 module_file_path: &VfsPath,
90) -> Option<VfsPath> { 93) -> Option<VfsPath> {
91 let directory_with_module_path = module_file_path.parent()?; 94 let directory_with_module_path = module_file_path.parent()?;
92 let base_directory = match module_file_path.name_and_extension()? { 95 let (name, ext) = module_file_path.name_and_extension()?;
93 ("mod", Some("rs")) | ("lib", Some("rs")) | ("main", Some("rs")) => { 96 if ext != Some("rs") {
94 Some(directory_with_module_path) 97 return None;
95 } 98 }
96 (regular_rust_file_name, Some("rs")) => { 99 let base_directory = match name {
100 "mod" | "lib" | "main" => Some(directory_with_module_path),
101 regular_rust_file_name => {
97 if matches!( 102 if matches!(
98 ( 103 (
99 directory_with_module_path 104 directory_with_module_path
@@ -110,37 +115,25 @@ fn directory_to_look_for_submodules(
110 directory_with_module_path.join(regular_rust_file_name) 115 directory_with_module_path.join(regular_rust_file_name)
111 } 116 }
112 } 117 }
113 _ => None,
114 }?; 118 }?;
115 119
116 let mut resulting_path = base_directory; 120 module_chain_to_containing_module_file(module, db)
117 for module in module_chain_to_containing_module_file(module, db) { 121 .into_iter()
118 if let Some(name) = module.name(db) { 122 .filter_map(|module| module.name(db))
119 resulting_path = resulting_path.join(&name.to_string())?; 123 .try_fold(base_directory, |path, name| path.join(&name.to_string()))
120 }
121 }
122
123 Some(resulting_path)
124} 124}
125 125
126fn module_chain_to_containing_module_file( 126fn module_chain_to_containing_module_file(
127 current_module: Module, 127 current_module: Module,
128 db: &RootDatabase, 128 db: &RootDatabase,
129) -> Vec<Module> { 129) -> Vec<Module> {
130 let mut path = Vec::new(); 130 let mut path =
131 131 iter::successors(Some(current_module), |current_module| current_module.parent(db))
132 let mut current_module = Some(current_module); 132 .take_while(|current_module| {
133 while let Some(ModuleSource::Module(_)) = 133 matches!(current_module.definition_source(db).value, ModuleSource::Module(_))
134 current_module.map(|module| module.definition_source(db).value) 134 })
135 { 135 .collect::<Vec<_>>();
136 if let Some(module) = current_module { 136 path.reverse();
137 path.insert(0, module);
138 current_module = module.parent(db);
139 } else {
140 current_module = None;
141 }
142 }
143
144 path 137 path
145} 138}
146 139
diff --git a/crates/hir/src/db.rs b/crates/hir/src/db.rs
index d01e1b33d..d5d4cf5b6 100644
--- a/crates/hir/src/db.rs
+++ b/crates/hir/src/db.rs
@@ -10,8 +10,8 @@ pub use hir_def::db::{
10 TypeAliasDataQuery, UnionDataQuery, 10 TypeAliasDataQuery, UnionDataQuery,
11}; 11};
12pub use hir_expand::db::{ 12pub use hir_expand::db::{
13 AstDatabase, AstDatabaseStorage, AstIdMapQuery, InternEagerExpansionQuery, InternMacroQuery, 13 AstDatabase, AstDatabaseStorage, AstIdMapQuery, HygieneFrameQuery, InternEagerExpansionQuery,
14 MacroArgTextQuery, MacroDefQuery, MacroExpandQuery, ParseMacroExpansionQuery, 14 InternMacroQuery, MacroArgTextQuery, MacroDefQuery, MacroExpandQuery, ParseMacroExpansionQuery,
15}; 15};
16pub use hir_ty::db::*; 16pub use hir_ty::db::*;
17 17
diff --git a/crates/hir/src/diagnostics.rs b/crates/hir/src/diagnostics.rs
index b1c924167..447faa04f 100644
--- a/crates/hir/src/diagnostics.rs
+++ b/crates/hir/src/diagnostics.rs
@@ -4,6 +4,6 @@ pub use hir_expand::diagnostics::{
4 Diagnostic, DiagnosticCode, DiagnosticSink, DiagnosticSinkBuilder, 4 Diagnostic, DiagnosticCode, DiagnosticSink, DiagnosticSinkBuilder,
5}; 5};
6pub use hir_ty::diagnostics::{ 6pub use hir_ty::diagnostics::{
7 IncorrectCase, MismatchedArgCount, MissingFields, MissingMatchArms, MissingOkInTailExpr, 7 IncorrectCase, MismatchedArgCount, MissingFields, MissingMatchArms, MissingOkOrSomeInTailExpr,
8 NoSuchField, RemoveThisSemicolon, 8 NoSuchField, RemoveThisSemicolon,
9}; 9};
diff --git a/crates/hir_def/src/path.rs b/crates/hir_def/src/path.rs
index e2bf85bbc..3dd7c3cbb 100644
--- a/crates/hir_def/src/path.rs
+++ b/crates/hir_def/src/path.rs
@@ -305,6 +305,7 @@ pub use hir_expand::name as __name;
305macro_rules! __known_path { 305macro_rules! __known_path {
306 (core::iter::IntoIterator) => {}; 306 (core::iter::IntoIterator) => {};
307 (core::result::Result) => {}; 307 (core::result::Result) => {};
308 (core::option::Option) => {};
308 (core::ops::Range) => {}; 309 (core::ops::Range) => {};
309 (core::ops::RangeFrom) => {}; 310 (core::ops::RangeFrom) => {};
310 (core::ops::RangeFull) => {}; 311 (core::ops::RangeFull) => {};
diff --git a/crates/hir_def/src/path/lower.rs b/crates/hir_def/src/path/lower.rs
index 8a01e6eea..9518ac109 100644
--- a/crates/hir_def/src/path/lower.rs
+++ b/crates/hir_def/src/path/lower.rs
@@ -123,7 +123,7 @@ pub(super) fn lower_path(mut path: ast::Path, hygiene: &Hygiene) -> Option<Path>
123 // We follow what it did anyway :) 123 // We follow what it did anyway :)
124 if segments.len() == 1 && kind == PathKind::Plain { 124 if segments.len() == 1 && kind == PathKind::Plain {
125 if let Some(_macro_call) = path.syntax().parent().and_then(ast::MacroCall::cast) { 125 if let Some(_macro_call) = path.syntax().parent().and_then(ast::MacroCall::cast) {
126 if let Some(crate_id) = hygiene.local_inner_macros() { 126 if let Some(crate_id) = hygiene.local_inner_macros(path) {
127 kind = PathKind::DollarCrate(crate_id); 127 kind = PathKind::DollarCrate(crate_id);
128 } 128 }
129 } 129 }
diff --git a/crates/hir_expand/src/db.rs b/crates/hir_expand/src/db.rs
index 0a0d021e0..ab2637b8c 100644
--- a/crates/hir_expand/src/db.rs
+++ b/crates/hir_expand/src/db.rs
@@ -8,9 +8,9 @@ use parser::FragmentKind;
8use syntax::{algo::diff, ast::NameOwner, AstNode, GreenNode, Parse, SyntaxKind::*, SyntaxNode}; 8use syntax::{algo::diff, ast::NameOwner, AstNode, GreenNode, Parse, SyntaxKind::*, SyntaxNode};
9 9
10use crate::{ 10use crate::{
11 ast_id_map::AstIdMap, BuiltinDeriveExpander, BuiltinFnLikeExpander, EagerCallLoc, EagerMacroId, 11 ast_id_map::AstIdMap, hygiene::HygieneFrame, BuiltinDeriveExpander, BuiltinFnLikeExpander,
12 HirFileId, HirFileIdRepr, LazyMacroId, MacroCallId, MacroCallLoc, MacroDefId, MacroDefKind, 12 EagerCallLoc, EagerMacroId, HirFileId, HirFileIdRepr, LazyMacroId, MacroCallId, MacroCallLoc,
13 MacroFile, ProcMacroExpander, 13 MacroDefId, MacroDefKind, MacroFile, ProcMacroExpander,
14}; 14};
15 15
16/// Total limit on the number of tokens produced by any macro invocation. 16/// Total limit on the number of tokens produced by any macro invocation.
@@ -94,6 +94,8 @@ pub trait AstDatabase: SourceDatabase {
94 fn intern_eager_expansion(&self, eager: EagerCallLoc) -> EagerMacroId; 94 fn intern_eager_expansion(&self, eager: EagerCallLoc) -> EagerMacroId;
95 95
96 fn expand_proc_macro(&self, call: MacroCallId) -> Result<tt::Subtree, mbe::ExpandError>; 96 fn expand_proc_macro(&self, call: MacroCallId) -> Result<tt::Subtree, mbe::ExpandError>;
97
98 fn hygiene_frame(&self, file_id: HirFileId) -> Arc<HygieneFrame>;
97} 99}
98 100
99/// This expands the given macro call, but with different arguments. This is 101/// This expands the given macro call, but with different arguments. This is
@@ -369,6 +371,10 @@ fn parse_macro_with_arg(
369 } 371 }
370} 372}
371 373
374fn hygiene_frame(db: &dyn AstDatabase, file_id: HirFileId) -> Arc<HygieneFrame> {
375 Arc::new(HygieneFrame::new(db, file_id))
376}
377
372/// Given a `MacroCallId`, return what `FragmentKind` it belongs to. 378/// Given a `MacroCallId`, return what `FragmentKind` it belongs to.
373/// FIXME: Not completed 379/// FIXME: Not completed
374fn to_fragment_kind(db: &dyn AstDatabase, id: MacroCallId) -> FragmentKind { 380fn to_fragment_kind(db: &dyn AstDatabase, id: MacroCallId) -> FragmentKind {
diff --git a/crates/hir_expand/src/hygiene.rs b/crates/hir_expand/src/hygiene.rs
index 7ab0a5e52..8db581b77 100644
--- a/crates/hir_expand/src/hygiene.rs
+++ b/crates/hir_expand/src/hygiene.rs
@@ -2,65 +2,209 @@
2//! 2//!
3//! Specifically, `ast` + `Hygiene` allows you to create a `Name`. Note that, at 3//! Specifically, `ast` + `Hygiene` allows you to create a `Name`. Note that, at
4//! this moment, this is horribly incomplete and handles only `$crate`. 4//! this moment, this is horribly incomplete and handles only `$crate`.
5use std::sync::Arc;
6
5use base_db::CrateId; 7use base_db::CrateId;
6use either::Either; 8use either::Either;
7use syntax::ast; 9use mbe::Origin;
10use parser::SyntaxKind;
11use syntax::{ast, AstNode, SyntaxNode, TextRange, TextSize};
8 12
9use crate::{ 13use crate::{
10 db::AstDatabase, 14 db::{self, AstDatabase},
11 name::{AsName, Name}, 15 name::{AsName, Name},
12 HirFileId, HirFileIdRepr, MacroCallId, MacroDefKind, 16 HirFileId, HirFileIdRepr, InFile, MacroCallId, MacroCallLoc, MacroDefKind, MacroFile,
13}; 17};
14 18
15#[derive(Clone, Debug)] 19#[derive(Clone, Debug)]
16pub struct Hygiene { 20pub struct Hygiene {
17 // This is what `$crate` expands to 21 frames: Option<HygieneFrames>,
18 def_crate: Option<CrateId>,
19
20 // Indicate this is a local inner macro
21 local_inner: bool,
22} 22}
23 23
24impl Hygiene { 24impl Hygiene {
25 pub fn new(db: &dyn AstDatabase, file_id: HirFileId) -> Hygiene { 25 pub fn new(db: &dyn AstDatabase, file_id: HirFileId) -> Hygiene {
26 let (def_crate, local_inner) = match file_id.0 { 26 Hygiene { frames: Some(HygieneFrames::new(db, file_id.clone())) }
27 HirFileIdRepr::FileId(_) => (None, false),
28 HirFileIdRepr::MacroFile(macro_file) => match macro_file.macro_call_id {
29 MacroCallId::LazyMacro(id) => {
30 let loc = db.lookup_intern_macro(id);
31 match loc.def.kind {
32 MacroDefKind::Declarative => (Some(loc.def.krate), loc.def.local_inner),
33 MacroDefKind::BuiltIn(_) => (Some(loc.def.krate), false),
34 MacroDefKind::BuiltInDerive(_) => (None, false),
35 MacroDefKind::BuiltInEager(_) => (None, false),
36 MacroDefKind::ProcMacro(_) => (None, false),
37 }
38 }
39 MacroCallId::EagerMacro(_id) => (None, false),
40 },
41 };
42 Hygiene { def_crate, local_inner }
43 } 27 }
44 28
45 pub fn new_unhygienic() -> Hygiene { 29 pub fn new_unhygienic() -> Hygiene {
46 Hygiene { def_crate: None, local_inner: false } 30 Hygiene { frames: None }
47 } 31 }
48 32
49 // FIXME: this should just return name 33 // FIXME: this should just return name
50 pub fn name_ref_to_name(&self, name_ref: ast::NameRef) -> Either<Name, CrateId> { 34 pub fn name_ref_to_name(&self, name_ref: ast::NameRef) -> Either<Name, CrateId> {
51 if let Some(def_crate) = self.def_crate { 35 if let Some(frames) = &self.frames {
52 if name_ref.text() == "$crate" { 36 if name_ref.text() == "$crate" {
53 return Either::Right(def_crate); 37 if let Some(krate) = frames.root_crate(name_ref.syntax()) {
38 return Either::Right(krate);
39 }
54 } 40 }
55 } 41 }
42
56 Either::Left(name_ref.as_name()) 43 Either::Left(name_ref.as_name())
57 } 44 }
58 45
59 pub fn local_inner_macros(&self) -> Option<CrateId> { 46 pub fn local_inner_macros(&self, path: ast::Path) -> Option<CrateId> {
60 if self.local_inner { 47 let mut token = path.syntax().first_token()?.text_range();
61 self.def_crate 48 let frames = self.frames.as_ref()?;
62 } else { 49 let mut current = frames.0.clone();
63 None 50
51 loop {
52 let (mapped, origin) = current.expansion.as_ref()?.map_ident_up(token)?;
53 if origin == Origin::Def {
54 return if current.local_inner { frames.root_crate(path.syntax()) } else { None };
55 }
56 current = current.call_site.as_ref()?.clone();
57 token = mapped.value;
58 }
59 }
60}
61
62#[derive(Clone, Debug)]
63struct HygieneFrames(Arc<HygieneFrame>);
64
65#[derive(Clone, Debug, Eq, PartialEq)]
66pub struct HygieneFrame {
67 expansion: Option<HygieneInfo>,
68
69 // Indicate this is a local inner macro
70 local_inner: bool,
71 krate: Option<CrateId>,
72
73 call_site: Option<Arc<HygieneFrame>>,
74 def_site: Option<Arc<HygieneFrame>>,
75}
76
77impl HygieneFrames {
78 fn new(db: &dyn AstDatabase, file_id: HirFileId) -> Self {
79 HygieneFrames(Arc::new(HygieneFrame::new(db, file_id)))
80 }
81
82 fn root_crate(&self, node: &SyntaxNode) -> Option<CrateId> {
83 let mut token = node.first_token()?.text_range();
84 let mut result = self.0.krate;
85 let mut current = self.0.clone();
86
87 while let Some((mapped, origin)) =
88 current.expansion.as_ref().and_then(|it| it.map_ident_up(token))
89 {
90 result = current.krate;
91
92 let site = match origin {
93 Origin::Def => &current.def_site,
94 Origin::Call => &current.call_site,
95 };
96
97 let site = match site {
98 None => break,
99 Some(it) => it,
100 };
101
102 current = site.clone();
103 token = mapped.value;
64 } 104 }
105
106 result
107 }
108}
109
110#[derive(Debug, Clone, PartialEq, Eq)]
111struct HygieneInfo {
112 arg_start: InFile<TextSize>,
113 /// The `macro_rules!` arguments.
114 def_start: Option<InFile<TextSize>>,
115
116 macro_def: Arc<(db::TokenExpander, mbe::TokenMap)>,
117 macro_arg: Arc<(tt::Subtree, mbe::TokenMap)>,
118 exp_map: Arc<mbe::TokenMap>,
119}
120
121impl HygieneInfo {
122 fn map_ident_up(&self, token: TextRange) -> Option<(InFile<TextRange>, Origin)> {
123 let token_id = self.exp_map.token_by_range(token)?;
124
125 let (token_id, origin) = self.macro_def.0.map_id_up(token_id);
126 let (token_map, tt) = match origin {
127 mbe::Origin::Call => (&self.macro_arg.1, self.arg_start),
128 mbe::Origin::Def => (
129 &self.macro_def.1,
130 self.def_start
131 .as_ref()
132 .expect("`Origin::Def` used with non-`macro_rules!` macro")
133 .clone(),
134 ),
135 };
136
137 let range = token_map.range_by_token(token_id)?.by_kind(SyntaxKind::IDENT)?;
138 Some((tt.with_value(range + tt.value), origin))
139 }
140}
141
142fn make_hygiene_info(
143 db: &dyn AstDatabase,
144 macro_file: MacroFile,
145 loc: &MacroCallLoc,
146) -> Option<HygieneInfo> {
147 let arg_tt = loc.kind.arg(db)?;
148
149 let def_offset = loc.def.ast_id.and_then(|id| {
150 let def_tt = match id.to_node(db) {
151 ast::Macro::MacroRules(mac) => mac.token_tree()?.syntax().text_range().start(),
152 ast::Macro::MacroDef(_) => return None,
153 };
154 Some(InFile::new(id.file_id, def_tt))
155 });
156
157 let macro_def = db.macro_def(loc.def)?;
158 let (_, exp_map) = db.parse_macro_expansion(macro_file).value?;
159 let macro_arg = db.macro_arg(macro_file.macro_call_id)?;
160
161 Some(HygieneInfo {
162 arg_start: InFile::new(loc.kind.file_id(), arg_tt.text_range().start()),
163 def_start: def_offset,
164 macro_arg,
165 macro_def,
166 exp_map,
167 })
168}
169
170impl HygieneFrame {
171 pub(crate) fn new(db: &dyn AstDatabase, file_id: HirFileId) -> HygieneFrame {
172 let (info, krate, local_inner) = match file_id.0 {
173 HirFileIdRepr::FileId(_) => (None, None, false),
174 HirFileIdRepr::MacroFile(macro_file) => match macro_file.macro_call_id {
175 MacroCallId::EagerMacro(_id) => (None, None, false),
176 MacroCallId::LazyMacro(id) => {
177 let loc = db.lookup_intern_macro(id);
178 let info = make_hygiene_info(db, macro_file, &loc);
179 match loc.def.kind {
180 MacroDefKind::Declarative => {
181 (info, Some(loc.def.krate), loc.def.local_inner)
182 }
183 MacroDefKind::BuiltIn(_) => (info, Some(loc.def.krate), false),
184 MacroDefKind::BuiltInDerive(_) => (info, None, false),
185 MacroDefKind::BuiltInEager(_) => (info, None, false),
186 MacroDefKind::ProcMacro(_) => (info, None, false),
187 }
188 }
189 },
190 };
191
192 let info = match info {
193 None => {
194 return HygieneFrame {
195 expansion: None,
196 local_inner,
197 krate,
198 call_site: None,
199 def_site: None,
200 };
201 }
202 Some(it) => it,
203 };
204
205 let def_site = info.def_start.map(|it| db.hygiene_frame(it.file_id));
206 let call_site = Some(db.hygiene_frame(info.arg_start.file_id));
207
208 HygieneFrame { expansion: Some(info), local_inner, krate, call_site, def_site }
65 } 209 }
66} 210}
diff --git a/crates/hir_expand/src/name.rs b/crates/hir_expand/src/name.rs
index 2f44876a8..95d853b6d 100644
--- a/crates/hir_expand/src/name.rs
+++ b/crates/hir_expand/src/name.rs
@@ -164,6 +164,7 @@ pub mod known {
164 future, 164 future,
165 result, 165 result,
166 boxed, 166 boxed,
167 option,
167 // Components of known path (type name) 168 // Components of known path (type name)
168 Iterator, 169 Iterator,
169 IntoIterator, 170 IntoIterator,
@@ -172,6 +173,7 @@ pub mod known {
172 Ok, 173 Ok,
173 Future, 174 Future,
174 Result, 175 Result,
176 Option,
175 Output, 177 Output,
176 Target, 178 Target,
177 Box, 179 Box,
diff --git a/crates/hir_ty/src/diagnostics.rs b/crates/hir_ty/src/diagnostics.rs
index 14e18f5a1..c67a289f2 100644
--- a/crates/hir_ty/src/diagnostics.rs
+++ b/crates/hir_ty/src/diagnostics.rs
@@ -186,9 +186,10 @@ impl Diagnostic for MissingMatchArms {
186 } 186 }
187} 187}
188 188
189// Diagnostic: missing-ok-in-tail-expr 189// Diagnostic: missing-ok-or-some-in-tail-expr
190// 190//
191// This diagnostic is triggered if block that should return `Result` returns a value not wrapped in `Ok`. 191// This diagnostic is triggered if a block that should return `Result` returns a value not wrapped in `Ok`,
192// or if a block that should return `Option` returns a value not wrapped in `Some`.
192// 193//
193// Example: 194// Example:
194// 195//
@@ -198,17 +199,19 @@ impl Diagnostic for MissingMatchArms {
198// } 199// }
199// ``` 200// ```
200#[derive(Debug)] 201#[derive(Debug)]
201pub struct MissingOkInTailExpr { 202pub struct MissingOkOrSomeInTailExpr {
202 pub file: HirFileId, 203 pub file: HirFileId,
203 pub expr: AstPtr<ast::Expr>, 204 pub expr: AstPtr<ast::Expr>,
205 // `Some` or `Ok` depending on whether the return type is Result or Option
206 pub required: String,
204} 207}
205 208
206impl Diagnostic for MissingOkInTailExpr { 209impl Diagnostic for MissingOkOrSomeInTailExpr {
207 fn code(&self) -> DiagnosticCode { 210 fn code(&self) -> DiagnosticCode {
208 DiagnosticCode("missing-ok-in-tail-expr") 211 DiagnosticCode("missing-ok-or-some-in-tail-expr")
209 } 212 }
210 fn message(&self) -> String { 213 fn message(&self) -> String {
211 "wrap return expression in Ok".to_string() 214 format!("wrap return expression in {}", self.required)
212 } 215 }
213 fn display_source(&self) -> InFile<SyntaxNodePtr> { 216 fn display_source(&self) -> InFile<SyntaxNodePtr> {
214 InFile { file_id: self.file, value: self.expr.clone().into() } 217 InFile { file_id: self.file, value: self.expr.clone().into() }
diff --git a/crates/hir_ty/src/diagnostics/expr.rs b/crates/hir_ty/src/diagnostics/expr.rs
index b4e453411..a1c484fdf 100644
--- a/crates/hir_ty/src/diagnostics/expr.rs
+++ b/crates/hir_ty/src/diagnostics/expr.rs
@@ -11,8 +11,8 @@ use crate::{
11 db::HirDatabase, 11 db::HirDatabase,
12 diagnostics::{ 12 diagnostics::{
13 match_check::{is_useful, MatchCheckCtx, Matrix, PatStack, Usefulness}, 13 match_check::{is_useful, MatchCheckCtx, Matrix, PatStack, Usefulness},
14 MismatchedArgCount, MissingFields, MissingMatchArms, MissingOkInTailExpr, MissingPatFields, 14 MismatchedArgCount, MissingFields, MissingMatchArms, MissingOkOrSomeInTailExpr,
15 RemoveThisSemicolon, 15 MissingPatFields, RemoveThisSemicolon,
16 }, 16 },
17 utils::variant_data, 17 utils::variant_data,
18 ApplicationTy, InferenceResult, Ty, TypeCtor, 18 ApplicationTy, InferenceResult, Ty, TypeCtor,
@@ -306,27 +306,40 @@ impl<'a, 'b> ExprValidator<'a, 'b> {
306 }; 306 };
307 307
308 let core_result_path = path![core::result::Result]; 308 let core_result_path = path![core::result::Result];
309 let core_option_path = path![core::option::Option];
309 310
310 let resolver = self.owner.resolver(db.upcast()); 311 let resolver = self.owner.resolver(db.upcast());
311 let core_result_enum = match resolver.resolve_known_enum(db.upcast(), &core_result_path) { 312 let core_result_enum = match resolver.resolve_known_enum(db.upcast(), &core_result_path) {
312 Some(it) => it, 313 Some(it) => it,
313 _ => return, 314 _ => return,
314 }; 315 };
316 let core_option_enum = match resolver.resolve_known_enum(db.upcast(), &core_option_path) {
317 Some(it) => it,
318 _ => return,
319 };
315 320
316 let core_result_ctor = TypeCtor::Adt(AdtId::EnumId(core_result_enum)); 321 let core_result_ctor = TypeCtor::Adt(AdtId::EnumId(core_result_enum));
317 let params = match &mismatch.expected { 322 let core_option_ctor = TypeCtor::Adt(AdtId::EnumId(core_option_enum));
323
324 let (params, required) = match &mismatch.expected {
318 Ty::Apply(ApplicationTy { ctor, parameters }) if ctor == &core_result_ctor => { 325 Ty::Apply(ApplicationTy { ctor, parameters }) if ctor == &core_result_ctor => {
319 parameters 326 (parameters, "Ok".to_string())
327 }
328 Ty::Apply(ApplicationTy { ctor, parameters }) if ctor == &core_option_ctor => {
329 (parameters, "Some".to_string())
320 } 330 }
321 _ => return, 331 _ => return,
322 }; 332 };
323 333
324 if params.len() == 2 && params[0] == mismatch.actual { 334 if params.len() > 0 && params[0] == mismatch.actual {
325 let (_, source_map) = db.body_with_source_map(self.owner.into()); 335 let (_, source_map) = db.body_with_source_map(self.owner.into());
326 336
327 if let Ok(source_ptr) = source_map.expr_syntax(id) { 337 if let Ok(source_ptr) = source_map.expr_syntax(id) {
328 self.sink 338 self.sink.push(MissingOkOrSomeInTailExpr {
329 .push(MissingOkInTailExpr { file: source_ptr.file_id, expr: source_ptr.value }); 339 file: source_ptr.file_id,
340 expr: source_ptr.value,
341 required,
342 });
330 } 343 }
331 } 344 }
332 } 345 }
diff --git a/crates/hir_ty/src/tests/macros.rs b/crates/hir_ty/src/tests/macros.rs
index 1953da7be..c64f0b5b5 100644
--- a/crates/hir_ty/src/tests/macros.rs
+++ b/crates/hir_ty/src/tests/macros.rs
@@ -371,6 +371,37 @@ expand!();
371} 371}
372 372
373#[test] 373#[test]
374fn infer_macro_with_dollar_crate_in_def_site() {
375 check_types(
376 r#"
377//- /main.rs crate:main deps:foo
378use foo::expand;
379
380macro_rules! list {
381 ($($tt:tt)*) => { $($tt)* }
382}
383
384fn test() {
385 let r = expand!();
386 r;
387 //^ u128
388}
389
390//- /lib.rs crate:foo
391#[macro_export]
392macro_rules! expand {
393 () => { list!($crate::m!()) };
394}
395
396#[macro_export]
397macro_rules! m {
398 () => { 0u128 };
399}
400"#,
401 );
402}
403
404#[test]
374fn infer_type_value_non_legacy_macro_use_as() { 405fn infer_type_value_non_legacy_macro_use_as() {
375 check_infer( 406 check_infer(
376 r#" 407 r#"
diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs
index 6931a6190..055c0a79c 100644
--- a/crates/ide/src/diagnostics.rs
+++ b/crates/ide/src/diagnostics.rs
@@ -125,7 +125,7 @@ pub(crate) fn diagnostics(
125 .on::<hir::diagnostics::MissingFields, _>(|d| { 125 .on::<hir::diagnostics::MissingFields, _>(|d| {
126 res.borrow_mut().push(diagnostic_with_fix(d, &sema)); 126 res.borrow_mut().push(diagnostic_with_fix(d, &sema));
127 }) 127 })
128 .on::<hir::diagnostics::MissingOkInTailExpr, _>(|d| { 128 .on::<hir::diagnostics::MissingOkOrSomeInTailExpr, _>(|d| {
129 res.borrow_mut().push(diagnostic_with_fix(d, &sema)); 129 res.borrow_mut().push(diagnostic_with_fix(d, &sema));
130 }) 130 })
131 .on::<hir::diagnostics::NoSuchField, _>(|d| { 131 .on::<hir::diagnostics::NoSuchField, _>(|d| {
@@ -305,6 +305,40 @@ mod tests {
305 } 305 }
306 306
307 #[test] 307 #[test]
308 fn test_wrap_return_type_option() {
309 check_fix(
310 r#"
311//- /main.rs crate:main deps:core
312use core::option::Option::{self, Some, None};
313
314fn div(x: i32, y: i32) -> Option<i32> {
315 if y == 0 {
316 return None;
317 }
318 x / y$0
319}
320//- /core/lib.rs crate:core
321pub mod result {
322 pub enum Result<T, E> { Ok(T), Err(E) }
323}
324pub mod option {
325 pub enum Option<T> { Some(T), None }
326}
327"#,
328 r#"
329use core::option::Option::{self, Some, None};
330
331fn div(x: i32, y: i32) -> Option<i32> {
332 if y == 0 {
333 return None;
334 }
335 Some(x / y)
336}
337"#,
338 );
339 }
340
341 #[test]
308 fn test_wrap_return_type() { 342 fn test_wrap_return_type() {
309 check_fix( 343 check_fix(
310 r#" 344 r#"
@@ -321,6 +355,9 @@ fn div(x: i32, y: i32) -> Result<i32, ()> {
321pub mod result { 355pub mod result {
322 pub enum Result<T, E> { Ok(T), Err(E) } 356 pub enum Result<T, E> { Ok(T), Err(E) }
323} 357}
358pub mod option {
359 pub enum Option<T> { Some(T), None }
360}
324"#, 361"#,
325 r#" 362 r#"
326use core::result::Result::{self, Ok, Err}; 363use core::result::Result::{self, Ok, Err};
@@ -352,6 +389,9 @@ fn div<T>(x: T) -> Result<T, i32> {
352pub mod result { 389pub mod result {
353 pub enum Result<T, E> { Ok(T), Err(E) } 390 pub enum Result<T, E> { Ok(T), Err(E) }
354} 391}
392pub mod option {
393 pub enum Option<T> { Some(T), None }
394}
355"#, 395"#,
356 r#" 396 r#"
357use core::result::Result::{self, Ok, Err}; 397use core::result::Result::{self, Ok, Err};
@@ -385,6 +425,9 @@ fn div(x: i32, y: i32) -> MyResult<i32> {
385pub mod result { 425pub mod result {
386 pub enum Result<T, E> { Ok(T), Err(E) } 426 pub enum Result<T, E> { Ok(T), Err(E) }
387} 427}
428pub mod option {
429 pub enum Option<T> { Some(T), None }
430}
388"#, 431"#,
389 r#" 432 r#"
390use core::result::Result::{self, Ok, Err}; 433use core::result::Result::{self, Ok, Err};
@@ -414,12 +457,15 @@ fn foo() -> Result<(), i32> { 0 }
414pub mod result { 457pub mod result {
415 pub enum Result<T, E> { Ok(T), Err(E) } 458 pub enum Result<T, E> { Ok(T), Err(E) }
416} 459}
460pub mod option {
461 pub enum Option<T> { Some(T), None }
462}
417"#, 463"#,
418 ); 464 );
419 } 465 }
420 466
421 #[test] 467 #[test]
422 fn test_wrap_return_type_not_applicable_when_return_type_is_not_result() { 468 fn test_wrap_return_type_not_applicable_when_return_type_is_not_result_or_option() {
423 check_no_diagnostics( 469 check_no_diagnostics(
424 r#" 470 r#"
425//- /main.rs crate:main deps:core 471//- /main.rs crate:main deps:core
@@ -433,6 +479,9 @@ fn foo() -> SomeOtherEnum { 0 }
433pub mod result { 479pub mod result {
434 pub enum Result<T, E> { Ok(T), Err(E) } 480 pub enum Result<T, E> { Ok(T), Err(E) }
435} 481}
482pub mod option {
483 pub enum Option<T> { Some(T), None }
484}
436"#, 485"#,
437 ); 486 );
438 } 487 }
diff --git a/crates/ide/src/diagnostics/fixes.rs b/crates/ide/src/diagnostics/fixes.rs
index 71ec4df92..d7ad88ed5 100644
--- a/crates/ide/src/diagnostics/fixes.rs
+++ b/crates/ide/src/diagnostics/fixes.rs
@@ -3,7 +3,7 @@
3use hir::{ 3use hir::{
4 db::AstDatabase, 4 db::AstDatabase,
5 diagnostics::{ 5 diagnostics::{
6 Diagnostic, IncorrectCase, MissingFields, MissingOkInTailExpr, NoSuchField, 6 Diagnostic, IncorrectCase, MissingFields, MissingOkOrSomeInTailExpr, NoSuchField,
7 RemoveThisSemicolon, UnresolvedModule, 7 RemoveThisSemicolon, UnresolvedModule,
8 }, 8 },
9 HasSource, HirDisplay, InFile, Semantics, VariantDef, 9 HasSource, HirDisplay, InFile, Semantics, VariantDef,
@@ -94,15 +94,17 @@ impl DiagnosticWithFix for MissingFields {
94 } 94 }
95} 95}
96 96
97impl DiagnosticWithFix for MissingOkInTailExpr { 97impl DiagnosticWithFix for MissingOkOrSomeInTailExpr {
98 fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix> { 98 fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix> {
99 let root = sema.db.parse_or_expand(self.file)?; 99 let root = sema.db.parse_or_expand(self.file)?;
100 let tail_expr = self.expr.to_node(&root); 100 let tail_expr = self.expr.to_node(&root);
101 let tail_expr_range = tail_expr.syntax().text_range(); 101 let tail_expr_range = tail_expr.syntax().text_range();
102 let edit = TextEdit::replace(tail_expr_range, format!("Ok({})", tail_expr.syntax())); 102 let replacement = format!("{}({})", self.required, tail_expr.syntax());
103 let edit = TextEdit::replace(tail_expr_range, replacement);
103 let source_change = 104 let source_change =
104 SourceFileEdit { file_id: self.file.original_file(sema.db), edit }.into(); 105 SourceFileEdit { file_id: self.file.original_file(sema.db), edit }.into();
105 Some(Fix::new("Wrap with ok", source_change, tail_expr_range)) 106 let name = if self.required == "Ok" { "Wrap with Ok" } else { "Wrap with Some" };
107 Some(Fix::new(name, source_change, tail_expr_range))
106 } 108 }
107} 109}
108 110
diff --git a/crates/ide_db/src/apply_change.rs b/crates/ide_db/src/apply_change.rs
index 71c19ed13..c770a236b 100644
--- a/crates/ide_db/src/apply_change.rs
+++ b/crates/ide_db/src/apply_change.rs
@@ -145,6 +145,7 @@ impl RootDatabase {
145 hir::db::MacroDefQuery 145 hir::db::MacroDefQuery
146 hir::db::ParseMacroExpansionQuery 146 hir::db::ParseMacroExpansionQuery
147 hir::db::MacroExpandQuery 147 hir::db::MacroExpandQuery
148 hir::db::HygieneFrameQuery
148 149
149 // DefDatabase 150 // DefDatabase
150 hir::db::ItemTreeQuery 151 hir::db::ItemTreeQuery
diff --git a/crates/mbe/src/mbe_expander/matcher.rs b/crates/mbe/src/mbe_expander/matcher.rs
index fdc8844ce..c6d615c81 100644
--- a/crates/mbe/src/mbe_expander/matcher.rs
+++ b/crates/mbe/src/mbe_expander/matcher.rs
@@ -150,7 +150,7 @@ fn match_subtree(
150 res.add_err(err!("leftover tokens")); 150 res.add_err(err!("leftover tokens"));
151 } 151 }
152 } 152 }
153 Op::Var { name, kind } => { 153 Op::Var { name, kind, .. } => {
154 let kind = match kind { 154 let kind = match kind {
155 Some(k) => k, 155 Some(k) => k,
156 None => { 156 None => {
diff --git a/crates/mbe/src/mbe_expander/transcriber.rs b/crates/mbe/src/mbe_expander/transcriber.rs
index 720531237..27b2ac777 100644
--- a/crates/mbe/src/mbe_expander/transcriber.rs
+++ b/crates/mbe/src/mbe_expander/transcriber.rs
@@ -100,8 +100,8 @@ fn expand_subtree(
100 err = err.or(e); 100 err = err.or(e);
101 arena.push(tt.into()); 101 arena.push(tt.into());
102 } 102 }
103 Op::Var { name, .. } => { 103 Op::Var { name, id, .. } => {
104 let ExpandResult { value: fragment, err: e } = expand_var(ctx, &name); 104 let ExpandResult { value: fragment, err: e } = expand_var(ctx, &name, *id);
105 err = err.or(e); 105 err = err.or(e);
106 push_fragment(arena, fragment); 106 push_fragment(arena, fragment);
107 } 107 }
@@ -118,14 +118,11 @@ fn expand_subtree(
118 ExpandResult { value: tt::Subtree { delimiter: template.delimiter, token_trees: tts }, err } 118 ExpandResult { value: tt::Subtree { delimiter: template.delimiter, token_trees: tts }, err }
119} 119}
120 120
121fn expand_var(ctx: &mut ExpandCtx, v: &SmolStr) -> ExpandResult<Fragment> { 121fn expand_var(ctx: &mut ExpandCtx, v: &SmolStr, id: tt::TokenId) -> ExpandResult<Fragment> {
122 if v == "crate" { 122 // We already handle $crate case in mbe parser
123 // We simply produce identifier `$crate` here. And it will be resolved when lowering ast to Path. 123 debug_assert!(v != "crate");
124 let tt = 124
125 tt::Leaf::from(tt::Ident { text: "$crate".into(), id: tt::TokenId::unspecified() }) 125 if !ctx.bindings.contains(v) {
126 .into();
127 ExpandResult::ok(Fragment::Tokens(tt))
128 } else if !ctx.bindings.contains(v) {
129 // Note that it is possible to have a `$var` inside a macro which is not bound. 126 // Note that it is possible to have a `$var` inside a macro which is not bound.
130 // For example: 127 // For example:
131 // ``` 128 // ```
@@ -142,14 +139,8 @@ fn expand_var(ctx: &mut ExpandCtx, v: &SmolStr) -> ExpandResult<Fragment> {
142 let tt = tt::Subtree { 139 let tt = tt::Subtree {
143 delimiter: None, 140 delimiter: None,
144 token_trees: vec![ 141 token_trees: vec![
145 tt::Leaf::from(tt::Punct { 142 tt::Leaf::from(tt::Punct { char: '$', spacing: tt::Spacing::Alone, id }).into(),
146 char: '$', 143 tt::Leaf::from(tt::Ident { text: v.clone(), id }).into(),
147 spacing: tt::Spacing::Alone,
148 id: tt::TokenId::unspecified(),
149 })
150 .into(),
151 tt::Leaf::from(tt::Ident { text: v.clone(), id: tt::TokenId::unspecified() })
152 .into(),
153 ], 144 ],
154 } 145 }
155 .into(); 146 .into();
diff --git a/crates/mbe/src/parser.rs b/crates/mbe/src/parser.rs
index 2f3ebc831..f3047972d 100644
--- a/crates/mbe/src/parser.rs
+++ b/crates/mbe/src/parser.rs
@@ -8,7 +8,7 @@ use crate::{tt_iter::TtIter, ExpandError, MetaTemplate};
8 8
9#[derive(Clone, Debug, PartialEq, Eq)] 9#[derive(Clone, Debug, PartialEq, Eq)]
10pub(crate) enum Op { 10pub(crate) enum Op {
11 Var { name: SmolStr, kind: Option<SmolStr> }, 11 Var { name: SmolStr, kind: Option<SmolStr>, id: tt::TokenId },
12 Repeat { subtree: MetaTemplate, kind: RepeatKind, separator: Option<Separator> }, 12 Repeat { subtree: MetaTemplate, kind: RepeatKind, separator: Option<Separator> },
13 Leaf(tt::Leaf), 13 Leaf(tt::Leaf),
14 Subtree(MetaTemplate), 14 Subtree(MetaTemplate),
@@ -106,18 +106,25 @@ fn next_op<'a>(first: &tt::TokenTree, src: &mut TtIter<'a>, mode: Mode) -> Resul
106 } 106 }
107 let name = UNDERSCORE.clone(); 107 let name = UNDERSCORE.clone();
108 let kind = eat_fragment_kind(src, mode)?; 108 let kind = eat_fragment_kind(src, mode)?;
109 Op::Var { name, kind } 109 let id = punct.id;
110 Op::Var { name, kind, id }
111 }
112 tt::Leaf::Ident(ident) if ident.text == "crate" => {
113 // We simply produce identifier `$crate` here. And it will be resolved when lowering ast to Path.
114 Op::Leaf(tt::Leaf::from(tt::Ident { text: "$crate".into(), id: ident.id }))
110 } 115 }
111 tt::Leaf::Ident(ident) => { 116 tt::Leaf::Ident(ident) => {
112 let name = ident.text.clone(); 117 let name = ident.text.clone();
113 let kind = eat_fragment_kind(src, mode)?; 118 let kind = eat_fragment_kind(src, mode)?;
114 Op::Var { name, kind } 119 let id = ident.id;
120 Op::Var { name, kind, id }
115 } 121 }
116 tt::Leaf::Literal(lit) => { 122 tt::Leaf::Literal(lit) => {
117 if is_boolean_literal(&lit) { 123 if is_boolean_literal(&lit) {
118 let name = lit.text.clone(); 124 let name = lit.text.clone();
119 let kind = eat_fragment_kind(src, mode)?; 125 let kind = eat_fragment_kind(src, mode)?;
120 Op::Var { name, kind } 126 let id = lit.id;
127 Op::Var { name, kind, id }
121 } else { 128 } else {
122 bail!("bad var 2"); 129 bail!("bad var 2");
123 } 130 }
diff --git a/crates/mbe/src/tests.rs b/crates/mbe/src/tests.rs
index 1d9afb4fb..d854985c5 100644
--- a/crates/mbe/src/tests.rs
+++ b/crates/mbe/src/tests.rs
@@ -1080,6 +1080,19 @@ fn test_vertical_bar_with_pat() {
1080} 1080}
1081 1081
1082#[test] 1082#[test]
1083fn test_dollar_crate_lhs_is_not_meta() {
1084 parse_macro(
1085 r#"
1086macro_rules! foo {
1087 ($crate) => {};
1088 () => {0};
1089}
1090 "#,
1091 )
1092 .assert_expand_items(r#"foo!{}"#, r#"0"#);
1093}
1094
1095#[test]
1083fn test_lifetime() { 1096fn test_lifetime() {
1084 parse_macro( 1097 parse_macro(
1085 r#" 1098 r#"