Skip to content

Commit 6aa432d

Browse files
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]>
2 parents cf4ae9a + 6c20d7e commit 6aa432d

File tree

14 files changed

+471
-181
lines changed

14 files changed

+471
-181
lines changed

crates/ra_hir_expand/src/db.rs

Lines changed: 53 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
33
use std::sync::Arc;
44

5-
use mbe::MacroRules;
5+
use mbe::{ExpandResult, MacroRules};
66
use ra_db::{salsa, SourceDatabase};
77
use ra_parser::FragmentKind;
88
use ra_prof::profile;
@@ -27,11 +27,12 @@ impl TokenExpander {
2727
db: &dyn AstDatabase,
2828
id: LazyMacroId,
2929
tt: &tt::Subtree,
30-
) -> Result<tt::Subtree, mbe::ExpandError> {
30+
) -> mbe::ExpandResult<tt::Subtree> {
3131
match self {
3232
TokenExpander::MacroRules(it) => it.expand(tt),
33-
TokenExpander::Builtin(it) => it.expand(db, id, tt),
34-
TokenExpander::BuiltinDerive(it) => it.expand(db, id, tt),
33+
// FIXME switch these to ExpandResult as well
34+
TokenExpander::Builtin(it) => it.expand(db, id, tt).into(),
35+
TokenExpander::BuiltinDerive(it) => it.expand(db, id, tt).into(),
3536
}
3637
}
3738

@@ -66,7 +67,7 @@ pub trait AstDatabase: SourceDatabase {
6667
fn macro_def(&self, id: MacroDefId) -> Option<Arc<(TokenExpander, mbe::TokenMap)>>;
6768
fn parse_macro(&self, macro_file: MacroFile)
6869
-> 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>);
7071

7172
#[salsa::interned]
7273
fn intern_eager_expansion(&self, eager: EagerCallLoc) -> EagerMacroId;
@@ -153,7 +154,7 @@ pub(crate) fn macro_arg(
153154
pub(crate) fn macro_expand(
154155
db: &dyn AstDatabase,
155156
id: MacroCallId,
156-
) -> Result<Arc<tt::Subtree>, String> {
157+
) -> (Option<Arc<tt::Subtree>>, Option<String>) {
157158
macro_expand_with_arg(db, id, None)
158159
}
159160

@@ -174,31 +175,38 @@ fn macro_expand_with_arg(
174175
db: &dyn AstDatabase,
175176
id: MacroCallId,
176177
arg: Option<Arc<(tt::Subtree, mbe::TokenMap)>>,
177-
) -> Result<Arc<tt::Subtree>, String> {
178+
) -> (Option<Arc<tt::Subtree>>, Option<String>) {
178179
let lazy_id = match id {
179180
MacroCallId::LazyMacro(id) => id,
180181
MacroCallId::EagerMacro(id) => {
181182
if arg.is_some() {
182-
return Err(
183-
"hypothetical macro expansion not implemented for eager macro".to_owned()
183+
return (
184+
None,
185+
Some("hypothetical macro expansion not implemented for eager macro".to_owned()),
184186
);
185187
} else {
186-
return Ok(db.lookup_intern_eager_expansion(id).subtree);
188+
return (Some(db.lookup_intern_eager_expansion(id).subtree), None);
187189
}
188190
}
189191
};
190192

191193
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+
};
193198

194-
let macro_rules = db.macro_def(loc.def).ok_or("Fail to find macro definition")?;
195-
let tt = macro_rules.0.expand(db, lazy_id, &macro_arg.0).map_err(|err| format!("{:?}", err))?;
199+
let macro_rules = match db.macro_def(loc.def) {
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, &macro_arg.0);
196204
// Set a hard limit for the expanded tt
197205
let count = tt.count();
198206
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)));
200208
}
201-
Ok(Arc::new(tt))
209+
(Some(Arc::new(tt)), err.map(|e| format!("{:?}", e)))
202210
}
203211

204212
pub(crate) fn parse_or_expand(db: &dyn AstDatabase, file_id: HirFileId) -> Option<SyntaxNode> {
@@ -225,42 +233,41 @@ pub fn parse_macro_with_arg(
225233
let _p = profile("parse_macro_query");
226234

227235
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 {
229237
macro_expand_with_arg(db, macro_call_id, Some(arg))
230238
} else {
231239
db.macro_expand(macro_call_id)
232240
};
233-
let tt = expansion
234-
.map_err(|err| {
235-
// Note:
236-
// The final goal we would like to make all parse_macro success,
237-
// such that the following log will not call anyway.
238-
match macro_call_id {
239-
MacroCallId::LazyMacro(id) => {
240-
let loc: MacroCallLoc = db.lookup_intern_macro(id);
241-
let node = loc.kind.node(db);
242-
243-
// collect parent information for warning log
244-
let parents = std::iter::successors(loc.kind.file_id().call_node(db), |it| {
245-
it.file_id.call_node(db)
246-
})
247-
.map(|n| format!("{:#}", n.value))
248-
.collect::<Vec<_>>()
249-
.join("\n");
250-
251-
log::warn!(
252-
"fail on macro_parse: (reason: {} macro_call: {:#}) parents: {}",
253-
err,
254-
node.value,
255-
parents
256-
);
257-
}
258-
_ => {
259-
log::warn!("fail on macro_parse: (reason: {})", err);
260-
}
241+
if let Some(err) = err {
242+
// Note:
243+
// The final goal we would like to make all parse_macro success,
244+
// such that the following log will not call anyway.
245+
match macro_call_id {
246+
MacroCallId::LazyMacro(id) => {
247+
let loc: MacroCallLoc = db.lookup_intern_macro(id);
248+
let node = loc.kind.node(db);
249+
250+
// collect parent information for warning log
251+
let parents = std::iter::successors(loc.kind.file_id().call_node(db), |it| {
252+
it.file_id.call_node(db)
253+
})
254+
.map(|n| format!("{:#}", n.value))
255+
.collect::<Vec<_>>()
256+
.join("\n");
257+
258+
log::warn!(
259+
"fail on macro_parse: (reason: {} macro_call: {:#}) parents: {}",
260+
err,
261+
node.value,
262+
parents
263+
);
264+
}
265+
_ => {
266+
log::warn!("fail on macro_parse: (reason: {})", err);
261267
}
262-
})
263-
.ok()?;
268+
}
269+
};
270+
let tt = tt?;
264271

265272
let fragment_kind = to_fragment_kind(db, macro_call_id);
266273

crates/ra_hir_ty/src/tests/macros.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -462,7 +462,7 @@ fn main() {
462462
fn infer_builtin_macros_include() {
463463
let (db, pos) = TestDB::with_position(
464464
r#"
465-
//- /main.rs
465+
//- /main.rs
466466
#[rustc_builtin_macro]
467467
macro_rules! include {() => {}}
468468
@@ -483,7 +483,7 @@ fn bar() -> u32 {0}
483483
fn infer_builtin_macros_include_concat() {
484484
let (db, pos) = TestDB::with_position(
485485
r#"
486-
//- /main.rs
486+
//- /main.rs
487487
#[rustc_builtin_macro]
488488
macro_rules! include {() => {}}
489489
@@ -507,7 +507,7 @@ fn bar() -> u32 {0}
507507
fn infer_builtin_macros_include_concat_with_bad_env_should_failed() {
508508
let (db, pos) = TestDB::with_position(
509509
r#"
510-
//- /main.rs
510+
//- /main.rs
511511
#[rustc_builtin_macro]
512512
macro_rules! include {() => {}}
513513
@@ -534,7 +534,7 @@ fn bar() -> u32 {0}
534534
fn infer_builtin_macros_include_itself_should_failed() {
535535
let (db, pos) = TestDB::with_position(
536536
r#"
537-
//- /main.rs
537+
//- /main.rs
538538
#[rustc_builtin_macro]
539539
macro_rules! include {() => {}}
540540

crates/ra_ide/src/completion/complete_dot.rs

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -720,7 +720,18 @@ mod tests {
720720
}
721721
",
722722
),
723-
@r###"[]"###
723+
@r###"
724+
[
725+
CompletionItem {
726+
label: "the_field",
727+
source_range: [156; 156),
728+
delete: [156; 156),
729+
insert: "the_field",
730+
kind: Field,
731+
detail: "u32",
732+
},
733+
]
734+
"###
724735
);
725736
}
726737

@@ -751,6 +762,43 @@ mod tests {
751762
);
752763
}
753764

765+
#[test]
766+
fn macro_expansion_resilient() {
767+
assert_debug_snapshot!(
768+
do_ref_completion(
769+
r"
770+
macro_rules! dbg {
771+
() => {};
772+
($val:expr) => {
773+
match $val { tmp => { tmp } }
774+
};
775+
// Trailing comma with single argument is ignored
776+
($val:expr,) => { $crate::dbg!($val) };
777+
($($val:expr),+ $(,)?) => {
778+
($($crate::dbg!($val)),+,)
779+
};
780+
}
781+
struct A { the_field: u32 }
782+
fn foo(a: A) {
783+
dbg!(a.<|>)
784+
}
785+
",
786+
),
787+
@r###"
788+
[
789+
CompletionItem {
790+
label: "the_field",
791+
source_range: [552; 552),
792+
delete: [552; 552),
793+
insert: "the_field",
794+
kind: Field,
795+
detail: "u32",
796+
},
797+
]
798+
"###
799+
);
800+
}
801+
754802
#[test]
755803
fn test_method_completion_3547() {
756804
assert_debug_snapshot!(

crates/ra_ide/src/completion/complete_pattern.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,6 @@ mod tests {
8989

9090
#[test]
9191
fn completes_in_simple_macro_call() {
92-
// FIXME: doesn't work yet because of missing error recovery in macro expansion
9392
let completions = complete(
9493
r"
9594
macro_rules! m { ($e:expr) => { $e } }
@@ -102,6 +101,16 @@ mod tests {
102101
}
103102
",
104103
);
105-
assert_debug_snapshot!(completions, @r###"[]"###);
104+
assert_debug_snapshot!(completions, @r###"
105+
[
106+
CompletionItem {
107+
label: "E",
108+
source_range: [151; 151),
109+
delete: [151; 151),
110+
insert: "E",
111+
kind: Enum,
112+
},
113+
]
114+
"###);
106115
}
107116
}

crates/ra_ide/src/completion/complete_scope.rs

Lines changed: 91 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -811,7 +811,44 @@ mod tests {
811811
}
812812
"
813813
),
814-
@"[]"
814+
@r###"
815+
[
816+
CompletionItem {
817+
label: "m!",
818+
source_range: [145; 145),
819+
delete: [145; 145),
820+
insert: "m!($0)",
821+
kind: Macro,
822+
detail: "macro_rules! m",
823+
},
824+
CompletionItem {
825+
label: "quux(…)",
826+
source_range: [145; 145),
827+
delete: [145; 145),
828+
insert: "quux(${1:x})$0",
829+
kind: Function,
830+
lookup: "quux",
831+
detail: "fn quux(x: i32)",
832+
trigger_call_info: true,
833+
},
834+
CompletionItem {
835+
label: "x",
836+
source_range: [145; 145),
837+
delete: [145; 145),
838+
insert: "x",
839+
kind: Binding,
840+
detail: "i32",
841+
},
842+
CompletionItem {
843+
label: "y",
844+
source_range: [145; 145),
845+
delete: [145; 145),
846+
insert: "y",
847+
kind: Binding,
848+
detail: "i32",
849+
},
850+
]
851+
"###
815852
);
816853
}
817854

@@ -868,6 +905,59 @@ mod tests {
868905
);
869906
}
870907

908+
#[test]
909+
fn completes_in_simple_macro_without_closing_parens() {
910+
assert_debug_snapshot!(
911+
do_reference_completion(
912+
r"
913+
macro_rules! m { ($e:expr) => { $e } }
914+
fn quux(x: i32) {
915+
let y = 92;
916+
m!(x<|>
917+
}
918+
"
919+
),
920+
@r###"
921+
[
922+
CompletionItem {
923+
label: "m!",
924+
source_range: [145; 146),
925+
delete: [145; 146),
926+
insert: "m!($0)",
927+
kind: Macro,
928+
detail: "macro_rules! m",
929+
},
930+
CompletionItem {
931+
label: "quux(…)",
932+
source_range: [145; 146),
933+
delete: [145; 146),
934+
insert: "quux(${1:x})$0",
935+
kind: Function,
936+
lookup: "quux",
937+
detail: "fn quux(x: i32)",
938+
trigger_call_info: true,
939+
},
940+
CompletionItem {
941+
label: "x",
942+
source_range: [145; 146),
943+
delete: [145; 146),
944+
insert: "x",
945+
kind: Binding,
946+
detail: "i32",
947+
},
948+
CompletionItem {
949+
label: "y",
950+
source_range: [145; 146),
951+
delete: [145; 146),
952+
insert: "y",
953+
kind: Binding,
954+
detail: "i32",
955+
},
956+
]
957+
"###
958+
);
959+
}
960+
871961
#[test]
872962
fn completes_unresolved_uses() {
873963
assert_debug_snapshot!(

0 commit comments

Comments
 (0)