diff options
author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2020-12-03 16:55:15 +0000 |
---|---|---|
committer | GitHub <[email protected]> | 2020-12-03 16:55:15 +0000 |
commit | d46fce88f5af1a97888edf91df2cb51ff5bfd61c (patch) | |
tree | 1f0351e895fa8b24ae4fa5a1854fb6c4285efcd2 /crates/hir_expand | |
parent | 74de29b223c2e6f01d1ed0912d72787c202bb225 (diff) | |
parent | bca1e5fcb825c6c4e09ec197513b5568fce3d985 (diff) |
Merge #6700
6700: More macro diagnostics improvements r=jonas-schievink a=jonas-schievink
This threads macro expansion errors through `eager.rs` and the `AsMacroCall` trait, improving macro diagnostics emitted during body lowering.
Co-authored-by: Jonas Schievink <[email protected]>
Diffstat (limited to 'crates/hir_expand')
-rw-r--r-- | crates/hir_expand/src/builtin_macro.rs | 74 | ||||
-rw-r--r-- | crates/hir_expand/src/db.rs | 1 | ||||
-rw-r--r-- | crates/hir_expand/src/eager.rs | 116 |
3 files changed, 145 insertions, 46 deletions
diff --git a/crates/hir_expand/src/builtin_macro.rs b/crates/hir_expand/src/builtin_macro.rs index 7f4db106d..16c3c4d69 100644 --- a/crates/hir_expand/src/builtin_macro.rs +++ b/crates/hir_expand/src/builtin_macro.rs | |||
@@ -86,7 +86,6 @@ pub fn find_builtin_macro( | |||
86 | register_builtin! { | 86 | register_builtin! { |
87 | LAZY: | 87 | LAZY: |
88 | (column, Column) => column_expand, | 88 | (column, Column) => column_expand, |
89 | (compile_error, CompileError) => compile_error_expand, | ||
90 | (file, File) => file_expand, | 89 | (file, File) => file_expand, |
91 | (line, Line) => line_expand, | 90 | (line, Line) => line_expand, |
92 | (assert, Assert) => assert_expand, | 91 | (assert, Assert) => assert_expand, |
@@ -97,6 +96,7 @@ register_builtin! { | |||
97 | (format_args_nl, FormatArgsNl) => format_args_expand, | 96 | (format_args_nl, FormatArgsNl) => format_args_expand, |
98 | 97 | ||
99 | EAGER: | 98 | EAGER: |
99 | (compile_error, CompileError) => compile_error_expand, | ||
100 | (concat, Concat) => concat_expand, | 100 | (concat, Concat) => concat_expand, |
101 | (include, Include) => include_expand, | 101 | (include, Include) => include_expand, |
102 | (include_bytes, IncludeBytes) => include_bytes_expand, | 102 | (include_bytes, IncludeBytes) => include_bytes_expand, |
@@ -213,25 +213,6 @@ fn file_expand( | |||
213 | ExpandResult::ok(expanded) | 213 | ExpandResult::ok(expanded) |
214 | } | 214 | } |
215 | 215 | ||
216 | fn compile_error_expand( | ||
217 | _db: &dyn AstDatabase, | ||
218 | _id: LazyMacroId, | ||
219 | tt: &tt::Subtree, | ||
220 | ) -> ExpandResult<tt::Subtree> { | ||
221 | if tt.count() == 1 { | ||
222 | if let tt::TokenTree::Leaf(tt::Leaf::Literal(it)) = &tt.token_trees[0] { | ||
223 | let s = it.text.as_str(); | ||
224 | if s.contains('"') { | ||
225 | return ExpandResult::ok(quote! { loop { #it }}); | ||
226 | } | ||
227 | }; | ||
228 | } | ||
229 | |||
230 | ExpandResult::only_err(mbe::ExpandError::BindingError( | ||
231 | "`compile_error!` argument be a string".into(), | ||
232 | )) | ||
233 | } | ||
234 | |||
235 | fn format_args_expand( | 216 | fn format_args_expand( |
236 | _db: &dyn AstDatabase, | 217 | _db: &dyn AstDatabase, |
237 | _id: LazyMacroId, | 218 | _id: LazyMacroId, |
@@ -280,6 +261,30 @@ fn unquote_str(lit: &tt::Literal) -> Option<String> { | |||
280 | token.value().map(|it| it.into_owned()) | 261 | token.value().map(|it| it.into_owned()) |
281 | } | 262 | } |
282 | 263 | ||
264 | fn compile_error_expand( | ||
265 | _db: &dyn AstDatabase, | ||
266 | _id: EagerMacroId, | ||
267 | tt: &tt::Subtree, | ||
268 | ) -> ExpandResult<Option<(tt::Subtree, FragmentKind)>> { | ||
269 | let err = match &*tt.token_trees { | ||
270 | [tt::TokenTree::Leaf(tt::Leaf::Literal(it))] => { | ||
271 | let text = it.text.as_str(); | ||
272 | if text.starts_with('"') && text.ends_with('"') { | ||
273 | // FIXME: does not handle raw strings | ||
274 | mbe::ExpandError::Other(format!( | ||
275 | "`compile_error!` called: {}", | ||
276 | &text[1..text.len() - 1] | ||
277 | )) | ||
278 | } else { | ||
279 | mbe::ExpandError::BindingError("`compile_error!` argument must be a string".into()) | ||
280 | } | ||
281 | } | ||
282 | _ => mbe::ExpandError::BindingError("`compile_error!` argument must be a string".into()), | ||
283 | }; | ||
284 | |||
285 | ExpandResult { value: Some((quote! {}, FragmentKind::Items)), err: Some(err) } | ||
286 | } | ||
287 | |||
283 | fn concat_expand( | 288 | fn concat_expand( |
284 | _db: &dyn AstDatabase, | 289 | _db: &dyn AstDatabase, |
285 | _arg_id: EagerMacroId, | 290 | _arg_id: EagerMacroId, |
@@ -417,17 +422,25 @@ fn env_expand( | |||
417 | Err(e) => return ExpandResult::only_err(e), | 422 | Err(e) => return ExpandResult::only_err(e), |
418 | }; | 423 | }; |
419 | 424 | ||
420 | // FIXME: | 425 | let mut err = None; |
421 | // If the environment variable is not defined int rustc, then a compilation error will be emitted. | 426 | let s = get_env_inner(db, arg_id, &key).unwrap_or_else(|| { |
422 | // We might do the same if we fully support all other stuffs. | 427 | // The only variable rust-analyzer ever sets is `OUT_DIR`, so only diagnose that to avoid |
423 | // But for now on, we should return some dummy string for better type infer purpose. | 428 | // unnecessary diagnostics for eg. `CARGO_PKG_NAME`. |
424 | // However, we cannot use an empty string here, because for | 429 | if key == "OUT_DIR" { |
425 | // `include!(concat!(env!("OUT_DIR"), "/foo.rs"))` will become | 430 | err = Some(mbe::ExpandError::Other( |
426 | // `include!("foo.rs"), which might go to infinite loop | 431 | r#"`OUT_DIR` not set, enable "load out dirs from check" to fix"#.into(), |
427 | let s = get_env_inner(db, arg_id, &key).unwrap_or_else(|| "__RA_UNIMPLEMENTED__".to_string()); | 432 | )); |
433 | } | ||
434 | |||
435 | // If the variable is unset, still return a dummy string to help type inference along. | ||
436 | // We cannot use an empty string here, because for | ||
437 | // `include!(concat!(env!("OUT_DIR"), "/foo.rs"))` will become | ||
438 | // `include!("foo.rs"), which might go to infinite loop | ||
439 | "__RA_UNIMPLEMENTED__".to_string() | ||
440 | }); | ||
428 | let expanded = quote! { #s }; | 441 | let expanded = quote! { #s }; |
429 | 442 | ||
430 | ExpandResult::ok(Some((expanded, FragmentKind::Expr))) | 443 | ExpandResult { value: Some((expanded, FragmentKind::Expr)), err } |
431 | } | 444 | } |
432 | 445 | ||
433 | fn option_env_expand( | 446 | fn option_env_expand( |
@@ -638,7 +651,8 @@ mod tests { | |||
638 | "#, | 651 | "#, |
639 | ); | 652 | ); |
640 | 653 | ||
641 | assert_eq!(expanded, r#"loop{"error!"}"#); | 654 | // This expands to nothing (since it's in item position), but emits an error. |
655 | assert_eq!(expanded, ""); | ||
642 | } | 656 | } |
643 | 657 | ||
644 | #[test] | 658 | #[test] |
diff --git a/crates/hir_expand/src/db.rs b/crates/hir_expand/src/db.rs index 4fd0ba290..842a177db 100644 --- a/crates/hir_expand/src/db.rs +++ b/crates/hir_expand/src/db.rs | |||
@@ -207,6 +207,7 @@ fn macro_expand_with_arg( | |||
207 | } else { | 207 | } else { |
208 | return ExpandResult { | 208 | return ExpandResult { |
209 | value: Some(db.lookup_intern_eager_expansion(id).subtree), | 209 | value: Some(db.lookup_intern_eager_expansion(id).subtree), |
210 | // FIXME: There could be errors here! | ||
210 | err: None, | 211 | err: None, |
211 | }; | 212 | }; |
212 | } | 213 | } |
diff --git a/crates/hir_expand/src/eager.rs b/crates/hir_expand/src/eager.rs index ab6b4477c..0229a836e 100644 --- a/crates/hir_expand/src/eager.rs +++ b/crates/hir_expand/src/eager.rs | |||
@@ -26,19 +26,89 @@ use crate::{ | |||
26 | }; | 26 | }; |
27 | 27 | ||
28 | use base_db::CrateId; | 28 | use base_db::CrateId; |
29 | use mbe::ExpandResult; | ||
29 | use parser::FragmentKind; | 30 | use parser::FragmentKind; |
30 | use std::sync::Arc; | 31 | use std::sync::Arc; |
31 | use syntax::{algo::SyntaxRewriter, SyntaxNode}; | 32 | use syntax::{algo::SyntaxRewriter, SyntaxNode}; |
32 | 33 | ||
34 | pub struct ErrorEmitted { | ||
35 | _private: (), | ||
36 | } | ||
37 | |||
38 | trait ErrorSink { | ||
39 | fn emit(&mut self, err: mbe::ExpandError); | ||
40 | |||
41 | fn option<T>( | ||
42 | &mut self, | ||
43 | opt: Option<T>, | ||
44 | error: impl FnOnce() -> mbe::ExpandError, | ||
45 | ) -> Result<T, ErrorEmitted> { | ||
46 | match opt { | ||
47 | Some(it) => Ok(it), | ||
48 | None => { | ||
49 | self.emit(error()); | ||
50 | Err(ErrorEmitted { _private: () }) | ||
51 | } | ||
52 | } | ||
53 | } | ||
54 | |||
55 | fn option_with<T>( | ||
56 | &mut self, | ||
57 | opt: impl FnOnce() -> Option<T>, | ||
58 | error: impl FnOnce() -> mbe::ExpandError, | ||
59 | ) -> Result<T, ErrorEmitted> { | ||
60 | self.option(opt(), error) | ||
61 | } | ||
62 | |||
63 | fn result<T>(&mut self, res: Result<T, mbe::ExpandError>) -> Result<T, ErrorEmitted> { | ||
64 | match res { | ||
65 | Ok(it) => Ok(it), | ||
66 | Err(e) => { | ||
67 | self.emit(e); | ||
68 | Err(ErrorEmitted { _private: () }) | ||
69 | } | ||
70 | } | ||
71 | } | ||
72 | |||
73 | fn expand_result_option<T>(&mut self, res: ExpandResult<Option<T>>) -> Result<T, ErrorEmitted> { | ||
74 | match (res.value, res.err) { | ||
75 | (None, Some(err)) => { | ||
76 | self.emit(err); | ||
77 | Err(ErrorEmitted { _private: () }) | ||
78 | } | ||
79 | (Some(value), opt_err) => { | ||
80 | if let Some(err) = opt_err { | ||
81 | self.emit(err); | ||
82 | } | ||
83 | Ok(value) | ||
84 | } | ||
85 | (None, None) => unreachable!("`ExpandResult` without value or error"), | ||
86 | } | ||
87 | } | ||
88 | } | ||
89 | |||
90 | impl ErrorSink for &'_ mut dyn FnMut(mbe::ExpandError) { | ||
91 | fn emit(&mut self, err: mbe::ExpandError) { | ||
92 | self(err); | ||
93 | } | ||
94 | } | ||
95 | |||
96 | fn err(msg: impl Into<String>) -> mbe::ExpandError { | ||
97 | mbe::ExpandError::Other(msg.into()) | ||
98 | } | ||
99 | |||
33 | pub fn expand_eager_macro( | 100 | pub fn expand_eager_macro( |
34 | db: &dyn AstDatabase, | 101 | db: &dyn AstDatabase, |
35 | krate: CrateId, | 102 | krate: CrateId, |
36 | macro_call: InFile<ast::MacroCall>, | 103 | macro_call: InFile<ast::MacroCall>, |
37 | def: MacroDefId, | 104 | def: MacroDefId, |
38 | resolver: &dyn Fn(ast::Path) -> Option<MacroDefId>, | 105 | resolver: &dyn Fn(ast::Path) -> Option<MacroDefId>, |
39 | ) -> Option<EagerMacroId> { | 106 | mut diagnostic_sink: &mut dyn FnMut(mbe::ExpandError), |
40 | let args = macro_call.value.token_tree()?; | 107 | ) -> Result<EagerMacroId, ErrorEmitted> { |
41 | let parsed_args = mbe::ast_to_token_tree(&args)?.0; | 108 | let parsed_args = diagnostic_sink.option_with( |
109 | || Some(mbe::ast_to_token_tree(¯o_call.value.token_tree()?)?.0), | ||
110 | || err("malformed macro invocation"), | ||
111 | )?; | ||
42 | 112 | ||
43 | // Note: | 113 | // Note: |
44 | // When `lazy_expand` is called, its *parent* file must be already exists. | 114 | // When `lazy_expand` is called, its *parent* file must be already exists. |
@@ -55,17 +125,22 @@ pub fn expand_eager_macro( | |||
55 | }); | 125 | }); |
56 | let arg_file_id: MacroCallId = arg_id.into(); | 126 | let arg_file_id: MacroCallId = arg_id.into(); |
57 | 127 | ||
58 | let parsed_args = mbe::token_tree_to_syntax_node(&parsed_args, FragmentKind::Expr).ok()?.0; | 128 | let parsed_args = |
129 | diagnostic_sink.result(mbe::token_tree_to_syntax_node(&parsed_args, FragmentKind::Expr))?.0; | ||
59 | let result = eager_macro_recur( | 130 | let result = eager_macro_recur( |
60 | db, | 131 | db, |
61 | InFile::new(arg_file_id.as_file(), parsed_args.syntax_node()), | 132 | InFile::new(arg_file_id.as_file(), parsed_args.syntax_node()), |
62 | krate, | 133 | krate, |
63 | resolver, | 134 | resolver, |
135 | diagnostic_sink, | ||
64 | )?; | 136 | )?; |
65 | let subtree = to_subtree(&result)?; | 137 | let subtree = |
138 | diagnostic_sink.option(to_subtree(&result), || err("failed to parse macro result"))?; | ||
66 | 139 | ||
67 | if let MacroDefKind::BuiltInEager(eager) = def.kind { | 140 | if let MacroDefKind::BuiltInEager(eager) = def.kind { |
68 | let (subtree, fragment) = eager.expand(db, arg_id, &subtree).value?; | 141 | let res = eager.expand(db, arg_id, &subtree); |
142 | |||
143 | let (subtree, fragment) = diagnostic_sink.expand_result_option(res)?; | ||
69 | let eager = EagerCallLoc { | 144 | let eager = EagerCallLoc { |
70 | def, | 145 | def, |
71 | fragment, | 146 | fragment, |
@@ -74,9 +149,9 @@ pub fn expand_eager_macro( | |||
74 | file_id: macro_call.file_id, | 149 | file_id: macro_call.file_id, |
75 | }; | 150 | }; |
76 | 151 | ||
77 | Some(db.intern_eager_expansion(eager)) | 152 | Ok(db.intern_eager_expansion(eager)) |
78 | } else { | 153 | } else { |
79 | None | 154 | panic!("called `expand_eager_macro` on non-eager macro def {:?}", def); |
80 | } | 155 | } |
81 | } | 156 | } |
82 | 157 | ||
@@ -91,13 +166,16 @@ fn lazy_expand( | |||
91 | def: &MacroDefId, | 166 | def: &MacroDefId, |
92 | macro_call: InFile<ast::MacroCall>, | 167 | macro_call: InFile<ast::MacroCall>, |
93 | krate: CrateId, | 168 | krate: CrateId, |
94 | ) -> Option<InFile<SyntaxNode>> { | 169 | ) -> ExpandResult<Option<InFile<SyntaxNode>>> { |
95 | let ast_id = db.ast_id_map(macro_call.file_id).ast_id(¯o_call.value); | 170 | let ast_id = db.ast_id_map(macro_call.file_id).ast_id(¯o_call.value); |
96 | 171 | ||
97 | let id: MacroCallId = | 172 | let id: MacroCallId = |
98 | def.as_lazy_macro(db, krate, MacroCallKind::FnLike(macro_call.with_value(ast_id))).into(); | 173 | def.as_lazy_macro(db, krate, MacroCallKind::FnLike(macro_call.with_value(ast_id))).into(); |
99 | 174 | ||
100 | db.parse_or_expand(id.as_file()).map(|node| InFile::new(id.as_file(), node)) | 175 | let err = db.macro_expand_error(id); |
176 | let value = db.parse_or_expand(id.as_file()).map(|node| InFile::new(id.as_file(), node)); | ||
177 | |||
178 | ExpandResult { value, err } | ||
101 | } | 179 | } |
102 | 180 | ||
103 | fn eager_macro_recur( | 181 | fn eager_macro_recur( |
@@ -105,7 +183,8 @@ fn eager_macro_recur( | |||
105 | curr: InFile<SyntaxNode>, | 183 | curr: InFile<SyntaxNode>, |
106 | krate: CrateId, | 184 | krate: CrateId, |
107 | macro_resolver: &dyn Fn(ast::Path) -> Option<MacroDefId>, | 185 | macro_resolver: &dyn Fn(ast::Path) -> Option<MacroDefId>, |
108 | ) -> Option<SyntaxNode> { | 186 | mut diagnostic_sink: &mut dyn FnMut(mbe::ExpandError), |
187 | ) -> Result<SyntaxNode, ErrorEmitted> { | ||
109 | let original = curr.value.clone(); | 188 | let original = curr.value.clone(); |
110 | 189 | ||
111 | let children = curr.value.descendants().filter_map(ast::MacroCall::cast); | 190 | let children = curr.value.descendants().filter_map(ast::MacroCall::cast); |
@@ -113,7 +192,8 @@ fn eager_macro_recur( | |||
113 | 192 | ||
114 | // Collect replacement | 193 | // Collect replacement |
115 | for child in children { | 194 | for child in children { |
116 | let def: MacroDefId = macro_resolver(child.path()?)?; | 195 | let def = diagnostic_sink |
196 | .option_with(|| macro_resolver(child.path()?), || err("failed to resolve macro"))?; | ||
117 | let insert = match def.kind { | 197 | let insert = match def.kind { |
118 | MacroDefKind::BuiltInEager(_) => { | 198 | MacroDefKind::BuiltInEager(_) => { |
119 | let id: MacroCallId = expand_eager_macro( | 199 | let id: MacroCallId = expand_eager_macro( |
@@ -122,17 +202,21 @@ fn eager_macro_recur( | |||
122 | curr.with_value(child.clone()), | 202 | curr.with_value(child.clone()), |
123 | def, | 203 | def, |
124 | macro_resolver, | 204 | macro_resolver, |
205 | diagnostic_sink, | ||
125 | )? | 206 | )? |
126 | .into(); | 207 | .into(); |
127 | db.parse_or_expand(id.as_file())? | 208 | db.parse_or_expand(id.as_file()) |
209 | .expect("successful macro expansion should be parseable") | ||
128 | } | 210 | } |
129 | MacroDefKind::Declarative | 211 | MacroDefKind::Declarative |
130 | | MacroDefKind::BuiltIn(_) | 212 | | MacroDefKind::BuiltIn(_) |
131 | | MacroDefKind::BuiltInDerive(_) | 213 | | MacroDefKind::BuiltInDerive(_) |
132 | | MacroDefKind::ProcMacro(_) => { | 214 | | MacroDefKind::ProcMacro(_) => { |
133 | let expanded = lazy_expand(db, &def, curr.with_value(child.clone()), krate)?; | 215 | let res = lazy_expand(db, &def, curr.with_value(child.clone()), krate); |
216 | let val = diagnostic_sink.expand_result_option(res)?; | ||
217 | |||
134 | // replace macro inside | 218 | // replace macro inside |
135 | eager_macro_recur(db, expanded, krate, macro_resolver)? | 219 | eager_macro_recur(db, val, krate, macro_resolver, diagnostic_sink)? |
136 | } | 220 | } |
137 | }; | 221 | }; |
138 | 222 | ||
@@ -140,5 +224,5 @@ fn eager_macro_recur( | |||
140 | } | 224 | } |
141 | 225 | ||
142 | let res = rewriter.rewrite(&original); | 226 | let res = rewriter.rewrite(&original); |
143 | Some(res) | 227 | Ok(res) |
144 | } | 228 | } |