diff options
author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2020-03-17 09:41:30 +0000 |
---|---|---|
committer | GitHub <[email protected]> | 2020-03-17 09:41:30 +0000 |
commit | 6aa432d86b7e4fb691600032ebdf6f2301152447 (patch) | |
tree | aa012212c11e43a95547f553b5116922631f9896 /crates/ra_hir_expand | |
parent | cf4ae9aa591729dde25a7df3fa5c22ca0fd94145 (diff) | |
parent | 6c20d7e979b967eb20207414c0a0bf875bbcb98d (diff) |
Merge #3580
3580: More error-resilient MBE expansion r=matklad a=flodiebold
This is the beginning of an attempt to make macro-by-example expansion more resilient, so that we still get an expansion even if no rule exactly matches, with the goal to make completion work better in macro calls.
The general idea is to make everything return `(T, Option<ExpandError>)` instead of `Result<T, ExpandError>`; and then to try each macro arm in turn, and somehow choose the 'best' matching rule if none matches without errors. Finding that 'best' match isn't done yet; I'm currently counting how many tokens were consumed from the args before an error, but it also needs to take into account whether there were further patterns that had nothing to match.
I'll continue this later, but I'm interested whether you think this is the right path, @matklad & @edwin0cheng.
Co-authored-by: Florian Diebold <[email protected]>
Co-authored-by: Florian Diebold <[email protected]>
Diffstat (limited to 'crates/ra_hir_expand')
-rw-r--r-- | crates/ra_hir_expand/src/db.rs | 99 |
1 files changed, 53 insertions, 46 deletions
diff --git a/crates/ra_hir_expand/src/db.rs b/crates/ra_hir_expand/src/db.rs index c3e1c68b7..d171d2dfd 100644 --- a/crates/ra_hir_expand/src/db.rs +++ b/crates/ra_hir_expand/src/db.rs | |||
@@ -2,7 +2,7 @@ | |||
2 | 2 | ||
3 | use std::sync::Arc; | 3 | use std::sync::Arc; |
4 | 4 | ||
5 | use mbe::MacroRules; | 5 | use mbe::{ExpandResult, MacroRules}; |
6 | use ra_db::{salsa, SourceDatabase}; | 6 | use ra_db::{salsa, SourceDatabase}; |
7 | use ra_parser::FragmentKind; | 7 | use ra_parser::FragmentKind; |
8 | use ra_prof::profile; | 8 | use ra_prof::profile; |
@@ -27,11 +27,12 @@ impl TokenExpander { | |||
27 | db: &dyn AstDatabase, | 27 | db: &dyn AstDatabase, |
28 | id: LazyMacroId, | 28 | id: LazyMacroId, |
29 | tt: &tt::Subtree, | 29 | tt: &tt::Subtree, |
30 | ) -> Result<tt::Subtree, mbe::ExpandError> { | 30 | ) -> mbe::ExpandResult<tt::Subtree> { |
31 | match self { | 31 | match self { |
32 | TokenExpander::MacroRules(it) => it.expand(tt), | 32 | TokenExpander::MacroRules(it) => it.expand(tt), |
33 | TokenExpander::Builtin(it) => it.expand(db, id, tt), | 33 | // FIXME switch these to ExpandResult as well |
34 | TokenExpander::BuiltinDerive(it) => it.expand(db, id, tt), | 34 | TokenExpander::Builtin(it) => it.expand(db, id, tt).into(), |
35 | TokenExpander::BuiltinDerive(it) => it.expand(db, id, tt).into(), | ||
35 | } | 36 | } |
36 | } | 37 | } |
37 | 38 | ||
@@ -66,7 +67,7 @@ pub trait AstDatabase: SourceDatabase { | |||
66 | fn macro_def(&self, id: MacroDefId) -> Option<Arc<(TokenExpander, mbe::TokenMap)>>; | 67 | fn macro_def(&self, id: MacroDefId) -> Option<Arc<(TokenExpander, mbe::TokenMap)>>; |
67 | fn parse_macro(&self, macro_file: MacroFile) | 68 | fn parse_macro(&self, macro_file: MacroFile) |
68 | -> Option<(Parse<SyntaxNode>, Arc<mbe::TokenMap>)>; | 69 | -> Option<(Parse<SyntaxNode>, Arc<mbe::TokenMap>)>; |
69 | fn macro_expand(&self, macro_call: MacroCallId) -> Result<Arc<tt::Subtree>, String>; | 70 | fn macro_expand(&self, macro_call: MacroCallId) -> (Option<Arc<tt::Subtree>>, Option<String>); |
70 | 71 | ||
71 | #[salsa::interned] | 72 | #[salsa::interned] |
72 | fn intern_eager_expansion(&self, eager: EagerCallLoc) -> EagerMacroId; | 73 | fn intern_eager_expansion(&self, eager: EagerCallLoc) -> EagerMacroId; |
@@ -153,7 +154,7 @@ pub(crate) fn macro_arg( | |||
153 | pub(crate) fn macro_expand( | 154 | pub(crate) fn macro_expand( |
154 | db: &dyn AstDatabase, | 155 | db: &dyn AstDatabase, |
155 | id: MacroCallId, | 156 | id: MacroCallId, |
156 | ) -> Result<Arc<tt::Subtree>, String> { | 157 | ) -> (Option<Arc<tt::Subtree>>, Option<String>) { |
157 | macro_expand_with_arg(db, id, None) | 158 | macro_expand_with_arg(db, id, None) |
158 | } | 159 | } |
159 | 160 | ||
@@ -174,31 +175,38 @@ fn macro_expand_with_arg( | |||
174 | db: &dyn AstDatabase, | 175 | db: &dyn AstDatabase, |
175 | id: MacroCallId, | 176 | id: MacroCallId, |
176 | arg: Option<Arc<(tt::Subtree, mbe::TokenMap)>>, | 177 | arg: Option<Arc<(tt::Subtree, mbe::TokenMap)>>, |
177 | ) -> Result<Arc<tt::Subtree>, String> { | 178 | ) -> (Option<Arc<tt::Subtree>>, Option<String>) { |
178 | let lazy_id = match id { | 179 | let lazy_id = match id { |
179 | MacroCallId::LazyMacro(id) => id, | 180 | MacroCallId::LazyMacro(id) => id, |
180 | MacroCallId::EagerMacro(id) => { | 181 | MacroCallId::EagerMacro(id) => { |
181 | if arg.is_some() { | 182 | if arg.is_some() { |
182 | return Err( | 183 | return ( |
183 | "hypothetical macro expansion not implemented for eager macro".to_owned() | 184 | None, |
185 | Some("hypothetical macro expansion not implemented for eager macro".to_owned()), | ||
184 | ); | 186 | ); |
185 | } else { | 187 | } else { |
186 | return Ok(db.lookup_intern_eager_expansion(id).subtree); | 188 | return (Some(db.lookup_intern_eager_expansion(id).subtree), None); |
187 | } | 189 | } |
188 | } | 190 | } |
189 | }; | 191 | }; |
190 | 192 | ||
191 | let loc = db.lookup_intern_macro(lazy_id); | 193 | let loc = db.lookup_intern_macro(lazy_id); |
192 | let macro_arg = arg.or_else(|| db.macro_arg(id)).ok_or("Fail to args in to tt::TokenTree")?; | 194 | let macro_arg = match arg.or_else(|| db.macro_arg(id)) { |
195 | Some(it) => it, | ||
196 | None => return (None, Some("Fail to args in to tt::TokenTree".into())), | ||
197 | }; | ||
193 | 198 | ||
194 | let macro_rules = db.macro_def(loc.def).ok_or("Fail to find macro definition")?; | 199 | let macro_rules = match db.macro_def(loc.def) { |
195 | let tt = macro_rules.0.expand(db, lazy_id, ¯o_arg.0).map_err(|err| format!("{:?}", err))?; | 200 | Some(it) => it, |
201 | None => return (None, Some("Fail to find macro definition".into())), | ||
202 | }; | ||
203 | let ExpandResult(tt, err) = macro_rules.0.expand(db, lazy_id, ¯o_arg.0); | ||
196 | // Set a hard limit for the expanded tt | 204 | // Set a hard limit for the expanded tt |
197 | let count = tt.count(); | 205 | let count = tt.count(); |
198 | if count > 65536 { | 206 | if count > 65536 { |
199 | return Err(format!("Total tokens count exceed limit : count = {}", count)); | 207 | return (None, Some(format!("Total tokens count exceed limit : count = {}", count))); |
200 | } | 208 | } |
201 | Ok(Arc::new(tt)) | 209 | (Some(Arc::new(tt)), err.map(|e| format!("{:?}", e))) |
202 | } | 210 | } |
203 | 211 | ||
204 | pub(crate) fn parse_or_expand(db: &dyn AstDatabase, file_id: HirFileId) -> Option<SyntaxNode> { | 212 | pub(crate) fn parse_or_expand(db: &dyn AstDatabase, file_id: HirFileId) -> Option<SyntaxNode> { |
@@ -225,42 +233,41 @@ pub fn parse_macro_with_arg( | |||
225 | let _p = profile("parse_macro_query"); | 233 | let _p = profile("parse_macro_query"); |
226 | 234 | ||
227 | let macro_call_id = macro_file.macro_call_id; | 235 | let macro_call_id = macro_file.macro_call_id; |
228 | let expansion = if let Some(arg) = arg { | 236 | let (tt, err) = if let Some(arg) = arg { |
229 | macro_expand_with_arg(db, macro_call_id, Some(arg)) | 237 | macro_expand_with_arg(db, macro_call_id, Some(arg)) |
230 | } else { | 238 | } else { |
231 | db.macro_expand(macro_call_id) | 239 | db.macro_expand(macro_call_id) |
232 | }; | 240 | }; |
233 | let tt = expansion | 241 | if let Some(err) = err { |
234 | .map_err(|err| { | 242 | // Note: |
235 | // Note: | 243 | // The final goal we would like to make all parse_macro success, |
236 | // The final goal we would like to make all parse_macro success, | 244 | // such that the following log will not call anyway. |
237 | // such that the following log will not call anyway. | 245 | match macro_call_id { |
238 | match macro_call_id { | 246 | MacroCallId::LazyMacro(id) => { |
239 | MacroCallId::LazyMacro(id) => { | 247 | let loc: MacroCallLoc = db.lookup_intern_macro(id); |
240 | let loc: MacroCallLoc = db.lookup_intern_macro(id); | 248 | let node = loc.kind.node(db); |
241 | let node = loc.kind.node(db); | 249 | |
242 | 250 | // collect parent information for warning log | |
243 | // collect parent information for warning log | 251 | let parents = std::iter::successors(loc.kind.file_id().call_node(db), |it| { |
244 | let parents = std::iter::successors(loc.kind.file_id().call_node(db), |it| { | 252 | it.file_id.call_node(db) |
245 | it.file_id.call_node(db) | 253 | }) |
246 | }) | 254 | .map(|n| format!("{:#}", n.value)) |
247 | .map(|n| format!("{:#}", n.value)) | 255 | .collect::<Vec<_>>() |
248 | .collect::<Vec<_>>() | 256 | .join("\n"); |
249 | .join("\n"); | 257 | |
250 | 258 | log::warn!( | |
251 | log::warn!( | 259 | "fail on macro_parse: (reason: {} macro_call: {:#}) parents: {}", |
252 | "fail on macro_parse: (reason: {} macro_call: {:#}) parents: {}", | 260 | err, |
253 | err, | 261 | node.value, |
254 | node.value, | 262 | parents |
255 | parents | 263 | ); |
256 | ); | 264 | } |
257 | } | 265 | _ => { |
258 | _ => { | 266 | log::warn!("fail on macro_parse: (reason: {})", err); |
259 | log::warn!("fail on macro_parse: (reason: {})", err); | ||
260 | } | ||
261 | } | 267 | } |
262 | }) | 268 | } |
263 | .ok()?; | 269 | }; |
270 | let tt = tt?; | ||
264 | 271 | ||
265 | let fragment_kind = to_fragment_kind(db, macro_call_id); | 272 | let fragment_kind = to_fragment_kind(db, macro_call_id); |
266 | 273 | ||