aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock16
-rw-r--r--crates/assists/src/handlers/early_return.rs2
-rw-r--r--crates/assists/src/handlers/move_guard.rs1
-rw-r--r--crates/assists/src/handlers/replace_if_let_with_match.rs277
-rw-r--r--crates/assists/src/handlers/replace_let_with_if_let.rs2
-rw-r--r--crates/assists/src/lib.rs1
-rw-r--r--crates/assists/src/tests/generated.rs28
-rw-r--r--crates/hir_ty/Cargo.toml6
-rw-r--r--crates/hir_ty/src/db.rs6
-rw-r--r--crates/hir_ty/src/tests/regression.rs43
-rw-r--r--crates/hir_ty/src/traits.rs13
-rw-r--r--crates/hir_ty/src/traits/chalk.rs49
-rw-r--r--crates/hir_ty/src/traits/chalk/interner.rs16
-rw-r--r--crates/hir_ty/src/traits/chalk/mapping.rs13
-rw-r--r--crates/rust-analyzer/src/diagnostics/test_data/clippy_pass_by_ref.txt164
-rw-r--r--crates/rust-analyzer/src/diagnostics/test_data/macro_compiler_error.txt60
-rw-r--r--crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable.txt138
-rw-r--r--crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable_as_hint.txt138
-rw-r--r--crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable_as_info.txt138
-rw-r--r--crates/rust-analyzer/src/diagnostics/test_data/rustc_wrong_number_of_parameters.txt82
-rw-r--r--crates/rust-analyzer/src/diagnostics/test_data/snap_multi_line_fix.txt303
-rw-r--r--crates/rust-analyzer/src/diagnostics/to_proto.rs189
-rw-r--r--crates/syntax/src/ast/make.rs13
23 files changed, 1549 insertions, 149 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 8dcea6ea4..f1847e769 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -168,9 +168,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
168 168
169[[package]] 169[[package]]
170name = "chalk-derive" 170name = "chalk-derive"
171version = "0.37.0" 171version = "0.43.0"
172source = "registry+https://github.com/rust-lang/crates.io-index" 172source = "registry+https://github.com/rust-lang/crates.io-index"
173checksum = "564b529b0d620da43dc7ea46fa95b5c602e783e1870aeb07e8cbb6d7ff71bee6" 173checksum = "e2d9e0c8adcced1ab0fea5cb8a38647922893d5b495e363e1814299fd380469b"
174dependencies = [ 174dependencies = [
175 "proc-macro2", 175 "proc-macro2",
176 "quote", 176 "quote",
@@ -180,9 +180,9 @@ dependencies = [
180 180
181[[package]] 181[[package]]
182name = "chalk-ir" 182name = "chalk-ir"
183version = "0.37.0" 183version = "0.43.0"
184source = "registry+https://github.com/rust-lang/crates.io-index" 184source = "registry+https://github.com/rust-lang/crates.io-index"
185checksum = "e67d29482387f4cbed6d8f1b1f7d24f00ff10612c700c65fe4af220df11e4d24" 185checksum = "c5218266a5709bc4943de997e64d3fab41c9e9f68efd54a898de53135e987bd3"
186dependencies = [ 186dependencies = [
187 "chalk-derive", 187 "chalk-derive",
188 "lazy_static", 188 "lazy_static",
@@ -190,9 +190,9 @@ dependencies = [
190 190
191[[package]] 191[[package]]
192name = "chalk-recursive" 192name = "chalk-recursive"
193version = "0.37.0" 193version = "0.43.0"
194source = "registry+https://github.com/rust-lang/crates.io-index" 194source = "registry+https://github.com/rust-lang/crates.io-index"
195checksum = "5c52032be6fbdf91b6a7df3cafba3a6683fdabeff88e7ab73eea96e28657d973" 195checksum = "ed8f34f13fd4f30251f9f6f1dc56f80363201390ecbcac2fdfc8e33036cd9c4a"
196dependencies = [ 196dependencies = [
197 "chalk-derive", 197 "chalk-derive",
198 "chalk-ir", 198 "chalk-ir",
@@ -203,9 +203,9 @@ dependencies = [
203 203
204[[package]] 204[[package]]
205name = "chalk-solve" 205name = "chalk-solve"
206version = "0.37.0" 206version = "0.43.0"
207source = "registry+https://github.com/rust-lang/crates.io-index" 207source = "registry+https://github.com/rust-lang/crates.io-index"
208checksum = "e0378bdfe1547b6fd545f518373b08c1e0c14920f7555a62d049021250a2b89b" 208checksum = "379c9f584488346044709d4c638c38d61a06fe593d4de2ac5f15fd2b0ba4cd9d"
209dependencies = [ 209dependencies = [
210 "chalk-derive", 210 "chalk-derive",
211 "chalk-ir", 211 "chalk-ir",
diff --git a/crates/assists/src/handlers/early_return.rs b/crates/assists/src/handlers/early_return.rs
index 7fd78e9d4..7bcc318a9 100644
--- a/crates/assists/src/handlers/early_return.rs
+++ b/crates/assists/src/handlers/early_return.rs
@@ -112,7 +112,7 @@ pub(crate) fn convert_to_guarded_return(acc: &mut Assists, ctx: &AssistContext)
112 let then_branch = 112 let then_branch =
113 make::block_expr(once(make::expr_stmt(early_expression).into()), None); 113 make::block_expr(once(make::expr_stmt(early_expression).into()), None);
114 let cond = invert_boolean_expression(cond_expr); 114 let cond = invert_boolean_expression(cond_expr);
115 make::expr_if(make::condition(cond, None), then_branch) 115 make::expr_if(make::condition(cond, None), then_branch, None)
116 .indent(if_indent_level) 116 .indent(if_indent_level)
117 }; 117 };
118 replace(new_expr.syntax(), &then_block, &parent_block, &if_expr) 118 replace(new_expr.syntax(), &then_block, &parent_block, &if_expr)
diff --git a/crates/assists/src/handlers/move_guard.rs b/crates/assists/src/handlers/move_guard.rs
index e1855b63d..eaffd80ce 100644
--- a/crates/assists/src/handlers/move_guard.rs
+++ b/crates/assists/src/handlers/move_guard.rs
@@ -42,6 +42,7 @@ pub(crate) fn move_guard_to_arm_body(acc: &mut Assists, ctx: &AssistContext) ->
42 let if_expr = make::expr_if( 42 let if_expr = make::expr_if(
43 make::condition(guard_condition, None), 43 make::condition(guard_condition, None),
44 make::block_expr(None, Some(arm_expr.clone())), 44 make::block_expr(None, Some(arm_expr.clone())),
45 None,
45 ) 46 )
46 .indent(arm_expr.indent_level()); 47 .indent(arm_expr.indent_level());
47 48
diff --git a/crates/assists/src/handlers/replace_if_let_with_match.rs b/crates/assists/src/handlers/replace_if_let_with_match.rs
index 9a49c48c1..4a355c66f 100644
--- a/crates/assists/src/handlers/replace_if_let_with_match.rs
+++ b/crates/assists/src/handlers/replace_if_let_with_match.rs
@@ -1,3 +1,6 @@
1use std::iter;
2
3use ide_db::{ty_filter::TryEnum, RootDatabase};
1use syntax::{ 4use syntax::{
2 ast::{ 5 ast::{
3 self, 6 self,
@@ -8,7 +11,6 @@ use syntax::{
8}; 11};
9 12
10use crate::{utils::unwrap_trivial_block, AssistContext, AssistId, AssistKind, Assists}; 13use crate::{utils::unwrap_trivial_block, AssistContext, AssistId, AssistKind, Assists};
11use ide_db::ty_filter::TryEnum;
12 14
13// Assist: replace_if_let_with_match 15// Assist: replace_if_let_with_match
14// 16//
@@ -79,6 +81,91 @@ pub(crate) fn replace_if_let_with_match(acc: &mut Assists, ctx: &AssistContext)
79 ) 81 )
80} 82}
81 83
84// Assist: replace_match_with_if_let
85//
86// Replaces a binary `match` with a wildcard pattern and no guards with an `if let` expression.
87//
88// ```
89// enum Action { Move { distance: u32 }, Stop }
90//
91// fn handle(action: Action) {
92// <|>match action {
93// Action::Move { distance } => foo(distance),
94// _ => bar(),
95// }
96// }
97// ```
98// ->
99// ```
100// enum Action { Move { distance: u32 }, Stop }
101//
102// fn handle(action: Action) {
103// if let Action::Move { distance } = action {
104// foo(distance)
105// } else {
106// bar()
107// }
108// }
109// ```
110pub(crate) fn replace_match_with_if_let(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
111 let match_expr: ast::MatchExpr = ctx.find_node_at_offset()?;
112 let mut arms = match_expr.match_arm_list()?.arms();
113 let first_arm = arms.next()?;
114 let second_arm = arms.next()?;
115 if arms.next().is_some() || first_arm.guard().is_some() || second_arm.guard().is_some() {
116 return None;
117 }
118 let condition_expr = match_expr.expr()?;
119 let (if_let_pat, then_expr, else_expr) = if is_pat_wildcard_or_sad(&ctx.sema, &first_arm.pat()?)
120 {
121 (second_arm.pat()?, second_arm.expr()?, first_arm.expr()?)
122 } else if is_pat_wildcard_or_sad(&ctx.sema, &second_arm.pat()?) {
123 (first_arm.pat()?, first_arm.expr()?, second_arm.expr()?)
124 } else {
125 return None;
126 };
127
128 let target = match_expr.syntax().text_range();
129 acc.add(
130 AssistId("replace_match_with_if_let", AssistKind::RefactorRewrite),
131 "Replace with if let",
132 target,
133 move |edit| {
134 let condition = make::condition(condition_expr, Some(if_let_pat));
135 let then_block = match then_expr.reset_indent() {
136 ast::Expr::BlockExpr(block) => block,
137 expr => make::block_expr(iter::empty(), Some(expr)),
138 };
139 let else_expr = match else_expr {
140 ast::Expr::BlockExpr(block)
141 if block.statements().count() == 0 && block.expr().is_none() =>
142 {
143 None
144 }
145 ast::Expr::TupleExpr(tuple) if tuple.fields().count() == 0 => None,
146 expr => Some(expr),
147 };
148 let if_let_expr = make::expr_if(
149 condition,
150 then_block,
151 else_expr.map(|else_expr| {
152 ast::ElseBranch::Block(make::block_expr(iter::empty(), Some(else_expr)))
153 }),
154 )
155 .indent(IndentLevel::from_node(match_expr.syntax()));
156
157 edit.replace_ast::<ast::Expr>(match_expr.into(), if_let_expr);
158 },
159 )
160}
161
162fn is_pat_wildcard_or_sad(sema: &hir::Semantics<RootDatabase>, pat: &ast::Pat) -> bool {
163 sema.type_of_pat(&pat)
164 .and_then(|ty| TryEnum::from_ty(sema, &ty))
165 .map(|it| it.sad_pattern().syntax().text() == pat.syntax().text())
166 .unwrap_or_else(|| matches!(pat, ast::Pat::WildcardPat(_)))
167}
168
82#[cfg(test)] 169#[cfg(test)]
83mod tests { 170mod tests {
84 use super::*; 171 use super::*;
@@ -252,4 +339,192 @@ fn main() {
252"#, 339"#,
253 ) 340 )
254 } 341 }
342
343 #[test]
344 fn test_replace_match_with_if_let_unwraps_simple_expressions() {
345 check_assist(
346 replace_match_with_if_let,
347 r#"
348impl VariantData {
349 pub fn is_struct(&self) -> bool {
350 <|>match *self {
351 VariantData::Struct(..) => true,
352 _ => false,
353 }
354 }
355} "#,
356 r#"
357impl VariantData {
358 pub fn is_struct(&self) -> bool {
359 if let VariantData::Struct(..) = *self {
360 true
361 } else {
362 false
363 }
364 }
365} "#,
366 )
367 }
368
369 #[test]
370 fn test_replace_match_with_if_let_doesnt_unwrap_multiline_expressions() {
371 check_assist(
372 replace_match_with_if_let,
373 r#"
374fn foo() {
375 <|>match a {
376 VariantData::Struct(..) => {
377 bar(
378 123
379 )
380 }
381 _ => false,
382 }
383} "#,
384 r#"
385fn foo() {
386 if let VariantData::Struct(..) = a {
387 bar(
388 123
389 )
390 } else {
391 false
392 }
393} "#,
394 )
395 }
396
397 #[test]
398 fn replace_match_with_if_let_target() {
399 check_assist_target(
400 replace_match_with_if_let,
401 r#"
402impl VariantData {
403 pub fn is_struct(&self) -> bool {
404 <|>match *self {
405 VariantData::Struct(..) => true,
406 _ => false,
407 }
408 }
409} "#,
410 r#"match *self {
411 VariantData::Struct(..) => true,
412 _ => false,
413 }"#,
414 );
415 }
416
417 #[test]
418 fn special_case_option_match_to_if_let() {
419 check_assist(
420 replace_match_with_if_let,
421 r#"
422enum Option<T> { Some(T), None }
423use Option::*;
424
425fn foo(x: Option<i32>) {
426 <|>match x {
427 Some(x) => println!("{}", x),
428 None => println!("none"),
429 }
430}
431 "#,
432 r#"
433enum Option<T> { Some(T), None }
434use Option::*;
435
436fn foo(x: Option<i32>) {
437 if let Some(x) = x {
438 println!("{}", x)
439 } else {
440 println!("none")
441 }
442}
443 "#,
444 );
445 }
446
447 #[test]
448 fn special_case_result_match_to_if_let() {
449 check_assist(
450 replace_match_with_if_let,
451 r#"
452enum Result<T, E> { Ok(T), Err(E) }
453use Result::*;
454
455fn foo(x: Result<i32, ()>) {
456 <|>match x {
457 Ok(x) => println!("{}", x),
458 Err(_) => println!("none"),
459 }
460}
461 "#,
462 r#"
463enum Result<T, E> { Ok(T), Err(E) }
464use Result::*;
465
466fn foo(x: Result<i32, ()>) {
467 if let Ok(x) = x {
468 println!("{}", x)
469 } else {
470 println!("none")
471 }
472}
473 "#,
474 );
475 }
476
477 #[test]
478 fn nested_indent_match_to_if_let() {
479 check_assist(
480 replace_match_with_if_let,
481 r#"
482fn main() {
483 if true {
484 <|>match path.strip_prefix(root_path) {
485 Ok(rel_path) => {
486 let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
487 Some((*id, rel_path))
488 }
489 _ => None,
490 }
491 }
492}
493"#,
494 r#"
495fn main() {
496 if true {
497 if let Ok(rel_path) = path.strip_prefix(root_path) {
498 let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
499 Some((*id, rel_path))
500 } else {
501 None
502 }
503 }
504}
505"#,
506 )
507 }
508
509 #[test]
510 fn replace_match_with_if_let_empty_wildcard_expr() {
511 check_assist(
512 replace_match_with_if_let,
513 r#"
514fn main() {
515 <|>match path.strip_prefix(root_path) {
516 Ok(rel_path) => println!("{}", rel_path),
517 _ => (),
518 }
519}
520"#,
521 r#"
522fn main() {
523 if let Ok(rel_path) = path.strip_prefix(root_path) {
524 println!("{}", rel_path)
525 }
526}
527"#,
528 )
529 }
255} 530}
diff --git a/crates/assists/src/handlers/replace_let_with_if_let.rs b/crates/assists/src/handlers/replace_let_with_if_let.rs
index 69d3b08d3..5970e283c 100644
--- a/crates/assists/src/handlers/replace_let_with_if_let.rs
+++ b/crates/assists/src/handlers/replace_let_with_if_let.rs
@@ -60,7 +60,7 @@ pub(crate) fn replace_let_with_if_let(acc: &mut Assists, ctx: &AssistContext) ->
60 }; 60 };
61 let block = 61 let block =
62 make::block_expr(None, None).indent(IndentLevel::from_node(let_stmt.syntax())); 62 make::block_expr(None, None).indent(IndentLevel::from_node(let_stmt.syntax()));
63 let if_ = make::expr_if(make::condition(init, Some(with_placeholder)), block); 63 let if_ = make::expr_if(make::condition(init, Some(with_placeholder)), block, None);
64 let stmt = make::expr_stmt(if_); 64 let stmt = make::expr_stmt(if_);
65 65
66 let placeholder = stmt.syntax().descendants().find_map(ast::WildcardPat::cast).unwrap(); 66 let placeholder = stmt.syntax().descendants().find_map(ast::WildcardPat::cast).unwrap();
diff --git a/crates/assists/src/lib.rs b/crates/assists/src/lib.rs
index dfe6c2729..b8ce7418d 100644
--- a/crates/assists/src/lib.rs
+++ b/crates/assists/src/lib.rs
@@ -209,6 +209,7 @@ mod handlers {
209 reorder_fields::reorder_fields, 209 reorder_fields::reorder_fields,
210 replace_derive_with_manual_impl::replace_derive_with_manual_impl, 210 replace_derive_with_manual_impl::replace_derive_with_manual_impl,
211 replace_if_let_with_match::replace_if_let_with_match, 211 replace_if_let_with_match::replace_if_let_with_match,
212 replace_if_let_with_match::replace_match_with_if_let,
212 replace_impl_trait_with_generic::replace_impl_trait_with_generic, 213 replace_impl_trait_with_generic::replace_impl_trait_with_generic,
213 replace_let_with_if_let::replace_let_with_if_let, 214 replace_let_with_if_let::replace_let_with_if_let,
214 replace_qualified_name_with_use::replace_qualified_name_with_use, 215 replace_qualified_name_with_use::replace_qualified_name_with_use,
diff --git a/crates/assists/src/tests/generated.rs b/crates/assists/src/tests/generated.rs
index 8d50c8791..853bde09c 100644
--- a/crates/assists/src/tests/generated.rs
+++ b/crates/assists/src/tests/generated.rs
@@ -890,6 +890,34 @@ fn compute() -> Option<i32> { None }
890} 890}
891 891
892#[test] 892#[test]
893fn doctest_replace_match_with_if_let() {
894 check_doc_test(
895 "replace_match_with_if_let",
896 r#####"
897enum Action { Move { distance: u32 }, Stop }
898
899fn handle(action: Action) {
900 <|>match action {
901 Action::Move { distance } => foo(distance),
902 _ => bar(),
903 }
904}
905"#####,
906 r#####"
907enum Action { Move { distance: u32 }, Stop }
908
909fn handle(action: Action) {
910 if let Action::Move { distance } = action {
911 foo(distance)
912 } else {
913 bar()
914 }
915}
916"#####,
917 )
918}
919
920#[test]
893fn doctest_replace_qualified_name_with_use() { 921fn doctest_replace_qualified_name_with_use() {
894 check_doc_test( 922 check_doc_test(
895 "replace_qualified_name_with_use", 923 "replace_qualified_name_with_use",
diff --git a/crates/hir_ty/Cargo.toml b/crates/hir_ty/Cargo.toml
index cf5c38a23..289e812fe 100644
--- a/crates/hir_ty/Cargo.toml
+++ b/crates/hir_ty/Cargo.toml
@@ -17,9 +17,9 @@ ena = "0.14.0"
17log = "0.4.8" 17log = "0.4.8"
18rustc-hash = "1.1.0" 18rustc-hash = "1.1.0"
19scoped-tls = "1" 19scoped-tls = "1"
20chalk-solve = { version = "0.37", default-features = false } 20chalk-solve = { version = "0.43", default-features = false }
21chalk-ir = "0.37" 21chalk-ir = "0.43"
22chalk-recursive = "0.37" 22chalk-recursive = "0.43"
23 23
24stdx = { path = "../stdx", version = "0.0.0" } 24stdx = { path = "../stdx", version = "0.0.0" }
25hir_def = { path = "../hir_def", version = "0.0.0" } 25hir_def = { path = "../hir_def", version = "0.0.0" }
diff --git a/crates/hir_ty/src/db.rs b/crates/hir_ty/src/db.rs
index 25cf9eb7f..66bdb8e88 100644
--- a/crates/hir_ty/src/db.rs
+++ b/crates/hir_ty/src/db.rs
@@ -99,6 +99,12 @@ pub trait HirDatabase: DefDatabase + Upcast<dyn DefDatabase> {
99 #[salsa::invoke(crate::traits::chalk::fn_def_datum_query)] 99 #[salsa::invoke(crate::traits::chalk::fn_def_datum_query)]
100 fn fn_def_datum(&self, krate: CrateId, fn_def_id: chalk::FnDefId) -> Arc<chalk::FnDefDatum>; 100 fn fn_def_datum(&self, krate: CrateId, fn_def_id: chalk::FnDefId) -> Arc<chalk::FnDefDatum>;
101 101
102 #[salsa::invoke(crate::traits::chalk::fn_def_variance_query)]
103 fn fn_def_variance(&self, krate: CrateId, fn_def_id: chalk::FnDefId) -> chalk::Variances;
104
105 #[salsa::invoke(crate::traits::chalk::adt_variance_query)]
106 fn adt_variance(&self, krate: CrateId, adt_id: chalk::AdtId) -> chalk::Variances;
107
102 #[salsa::invoke(crate::traits::chalk::associated_ty_value_query)] 108 #[salsa::invoke(crate::traits::chalk::associated_ty_value_query)]
103 fn associated_ty_value( 109 fn associated_ty_value(
104 &self, 110 &self,
diff --git a/crates/hir_ty/src/tests/regression.rs b/crates/hir_ty/src/tests/regression.rs
index 94d86b0d1..8cf4e7012 100644
--- a/crates/hir_ty/src/tests/regression.rs
+++ b/crates/hir_ty/src/tests/regression.rs
@@ -840,3 +840,46 @@ fn issue_4966() {
840 "#]], 840 "#]],
841 ); 841 );
842} 842}
843
844#[test]
845fn issue_6628() {
846 check_infer(
847 r#"
848 #[lang = "fn_once"]
849 pub trait FnOnce<Args> {
850 type Output;
851 }
852
853 struct S<T>();
854 impl<T> S<T> {
855 fn f(&self, _t: T) {}
856 fn g<F: FnOnce(&T)>(&self, _f: F) {}
857 }
858 fn main() {
859 let s = S();
860 s.g(|_x| {});
861 s.f(10);
862 }
863 "#,
864 expect![[r#"
865 105..109 'self': &S<T>
866 111..113 '_t': T
867 118..120 '{}': ()
868 146..150 'self': &S<T>
869 152..154 '_f': F
870 159..161 '{}': ()
871 174..225 '{ ...10); }': ()
872 184..185 's': S<i32>
873 188..189 'S': S<i32>() -> S<i32>
874 188..191 'S()': S<i32>
875 197..198 's': S<i32>
876 197..209 's.g(|_x| {})': ()
877 201..208 '|_x| {}': |&i32| -> ()
878 202..204 '_x': &i32
879 206..208 '{}': ()
880 215..216 's': S<i32>
881 215..222 's.f(10)': ()
882 219..221 '10': i32
883 "#]],
884 );
885}
diff --git a/crates/hir_ty/src/traits.rs b/crates/hir_ty/src/traits.rs
index ce1174cbe..dfa51896b 100644
--- a/crates/hir_ty/src/traits.rs
+++ b/crates/hir_ty/src/traits.rs
@@ -1,4 +1,5 @@
1//! Trait solving using Chalk. 1//! Trait solving using Chalk.
2use std::env::var;
2use std::sync::Arc; 3use std::sync::Arc;
3 4
4use base_db::CrateId; 5use base_db::CrateId;
@@ -15,12 +16,6 @@ use self::chalk::{from_chalk, Interner, ToChalk};
15 16
16pub(crate) mod chalk; 17pub(crate) mod chalk;
17 18
18// This controls the maximum size of types Chalk considers. If we set this too
19// high, we can run into slow edge cases; if we set it too low, Chalk won't
20// find some solutions.
21// FIXME this is currently hardcoded in the recursive solver
22// const CHALK_SOLVER_MAX_SIZE: usize = 10;
23
24/// This controls how much 'time' we give the Chalk solver before giving up. 19/// This controls how much 'time' we give the Chalk solver before giving up.
25const CHALK_SOLVER_FUEL: i32 = 100; 20const CHALK_SOLVER_FUEL: i32 = 100;
26 21
@@ -31,9 +26,11 @@ struct ChalkContext<'a> {
31} 26}
32 27
33fn create_chalk_solver() -> chalk_recursive::RecursiveSolver<Interner> { 28fn create_chalk_solver() -> chalk_recursive::RecursiveSolver<Interner> {
34 let overflow_depth = 100; 29 let overflow_depth =
30 var("CHALK_OVERFLOW_DEPTH").ok().and_then(|s| s.parse().ok()).unwrap_or(100);
35 let caching_enabled = true; 31 let caching_enabled = true;
36 chalk_recursive::RecursiveSolver::new(overflow_depth, caching_enabled) 32 let max_size = var("CHALK_SOLVER_MAX_SIZE").ok().and_then(|s| s.parse().ok()).unwrap_or(30);
33 chalk_recursive::RecursiveSolver::new(overflow_depth, max_size, caching_enabled)
37} 34}
38 35
39/// A set of clauses that we assume to be true. E.g. if we are inside this function: 36/// A set of clauses that we assume to be true. E.g. if we are inside this function:
diff --git a/crates/hir_ty/src/traits/chalk.rs b/crates/hir_ty/src/traits/chalk.rs
index 55e2c3a3e..69eae6f79 100644
--- a/crates/hir_ty/src/traits/chalk.rs
+++ b/crates/hir_ty/src/traits/chalk.rs
@@ -104,7 +104,7 @@ impl<'a> chalk_solve::RustIrDatabase<Interner> for ChalkContext<'a> {
104 }; 104 };
105 105
106 // Note: Since we're using impls_for_trait, only impls where the trait 106 // Note: Since we're using impls_for_trait, only impls where the trait
107 // can be resolved should ever reach Chalk. `impl_datum` relies on that 107 // can be resolved should ever reach Chalk. Symbol’s value as variable is void: impl_datum relies on that
108 // and will panic if the trait can't be resolved. 108 // and will panic if the trait can't be resolved.
109 let in_deps = self.db.trait_impls_in_deps(self.krate); 109 let in_deps = self.db.trait_impls_in_deps(self.krate);
110 let in_self = self.db.trait_impls_in_crate(self.krate); 110 let in_self = self.db.trait_impls_in_crate(self.krate);
@@ -206,7 +206,7 @@ impl<'a> chalk_solve::RustIrDatabase<Interner> for ChalkContext<'a> {
206 Some((trait_, alias)) 206 Some((trait_, alias))
207 }) 207 })
208 { 208 {
209 // Making up `AsyncBlock<T>: Future<Output = T>` 209 // Making up Symbol’s value as variable is void: AsyncBlock<T>:
210 // 210 //
211 // |--------------------OpaqueTyDatum-------------------| 211 // |--------------------OpaqueTyDatum-------------------|
212 // |-------------OpaqueTyDatumBound--------------| 212 // |-------------OpaqueTyDatumBound--------------|
@@ -242,7 +242,7 @@ impl<'a> chalk_solve::RustIrDatabase<Interner> for ChalkContext<'a> {
242 // The opaque type has 1 parameter. 242 // The opaque type has 1 parameter.
243 make_binders(bound, 1) 243 make_binders(bound, 1)
244 } else { 244 } else {
245 // If failed to find `Future::Output`, return empty bounds as fallback. 245 // If failed to find Symbol’s value as variable is void: Future::Output, return empty bounds as fallback.
246 let bound = OpaqueTyDatumBound { 246 let bound = OpaqueTyDatumBound {
247 bounds: make_binders(vec![], 0), 247 bounds: make_binders(vec![], 0),
248 where_clauses: make_binders(vec![], 0), 248 where_clauses: make_binders(vec![], 0),
@@ -343,6 +343,23 @@ impl<'a> chalk_solve::RustIrDatabase<Interner> for ChalkContext<'a> {
343 // FIXME 343 // FIXME
344 unimplemented!() 344 unimplemented!()
345 } 345 }
346
347 fn unification_database(&self) -> &dyn chalk_ir::UnificationDatabase<Interner> {
348 self
349 }
350}
351
352impl<'a> chalk_ir::UnificationDatabase<Interner> for ChalkContext<'a> {
353 fn fn_def_variance(
354 &self,
355 fn_def_id: chalk_ir::FnDefId<Interner>,
356 ) -> chalk_ir::Variances<Interner> {
357 self.db.fn_def_variance(self.krate, fn_def_id)
358 }
359
360 fn adt_variance(&self, adt_id: chalk_ir::AdtId<Interner>) -> chalk_ir::Variances<Interner> {
361 self.db.adt_variance(self.krate, adt_id)
362 }
346} 363}
347 364
348pub(crate) fn program_clauses_for_chalk_env_query( 365pub(crate) fn program_clauses_for_chalk_env_query(
@@ -644,6 +661,32 @@ pub(crate) fn fn_def_datum_query(
644 Arc::new(datum) 661 Arc::new(datum)
645} 662}
646 663
664pub(crate) fn fn_def_variance_query(
665 db: &dyn HirDatabase,
666 _krate: CrateId,
667 fn_def_id: FnDefId,
668) -> Variances {
669 let callable_def: CallableDefId = from_chalk(db, fn_def_id);
670 let generic_params = generics(db.upcast(), callable_def.into());
671 Variances::from(
672 &Interner,
673 std::iter::repeat(chalk_ir::Variance::Invariant).take(generic_params.len()),
674 )
675}
676
677pub(crate) fn adt_variance_query(
678 db: &dyn HirDatabase,
679 _krate: CrateId,
680 adt_id: AdtId,
681) -> Variances {
682 let adt: crate::AdtId = from_chalk(db, adt_id);
683 let generic_params = generics(db.upcast(), adt.into());
684 Variances::from(
685 &Interner,
686 std::iter::repeat(chalk_ir::Variance::Invariant).take(generic_params.len()),
687 )
688}
689
647impl From<FnDefId> for crate::db::InternedCallableDefId { 690impl From<FnDefId> for crate::db::InternedCallableDefId {
648 fn from(fn_def_id: FnDefId) -> Self { 691 fn from(fn_def_id: FnDefId) -> Self {
649 InternKey::from_intern_id(fn_def_id.0) 692 InternKey::from_intern_id(fn_def_id.0)
diff --git a/crates/hir_ty/src/traits/chalk/interner.rs b/crates/hir_ty/src/traits/chalk/interner.rs
index 39569e690..6a4aa8333 100644
--- a/crates/hir_ty/src/traits/chalk/interner.rs
+++ b/crates/hir_ty/src/traits/chalk/interner.rs
@@ -25,6 +25,7 @@ pub(crate) type FnDefId = chalk_ir::FnDefId<Interner>;
25pub(crate) type FnDefDatum = chalk_solve::rust_ir::FnDefDatum<Interner>; 25pub(crate) type FnDefDatum = chalk_solve::rust_ir::FnDefDatum<Interner>;
26pub(crate) type OpaqueTyId = chalk_ir::OpaqueTyId<Interner>; 26pub(crate) type OpaqueTyId = chalk_ir::OpaqueTyId<Interner>;
27pub(crate) type OpaqueTyDatum = chalk_solve::rust_ir::OpaqueTyDatum<Interner>; 27pub(crate) type OpaqueTyDatum = chalk_solve::rust_ir::OpaqueTyDatum<Interner>;
28pub(crate) type Variances = chalk_ir::Variances<Interner>;
28 29
29impl chalk_ir::interner::Interner for Interner { 30impl chalk_ir::interner::Interner for Interner {
30 type InternedType = Arc<chalk_ir::TyData<Self>>; 31 type InternedType = Arc<chalk_ir::TyData<Self>>;
@@ -41,6 +42,7 @@ impl chalk_ir::interner::Interner for Interner {
41 type InternedVariableKinds = Vec<chalk_ir::VariableKind<Self>>; 42 type InternedVariableKinds = Vec<chalk_ir::VariableKind<Self>>;
42 type InternedCanonicalVarKinds = Vec<chalk_ir::CanonicalVarKind<Self>>; 43 type InternedCanonicalVarKinds = Vec<chalk_ir::CanonicalVarKind<Self>>;
43 type InternedConstraints = Vec<chalk_ir::InEnvironment<chalk_ir::Constraint<Self>>>; 44 type InternedConstraints = Vec<chalk_ir::InEnvironment<chalk_ir::Constraint<Self>>>;
45 type InternedVariances = Arc<[chalk_ir::Variance]>;
44 type DefId = InternId; 46 type DefId = InternId;
45 type InternedAdtId = hir_def::AdtId; 47 type InternedAdtId = hir_def::AdtId;
46 type Identifier = TypeAliasId; 48 type Identifier = TypeAliasId;
@@ -370,6 +372,20 @@ impl chalk_ir::interner::Interner for Interner {
370 ) -> Option<fmt::Result> { 372 ) -> Option<fmt::Result> {
371 None 373 None
372 } 374 }
375
376 fn intern_variances<E>(
377 &self,
378 data: impl IntoIterator<Item = Result<chalk_ir::Variance, E>>,
379 ) -> Result<Self::InternedVariances, E> {
380 data.into_iter().collect()
381 }
382
383 fn variances_data<'a>(
384 &self,
385 variances: &'a Self::InternedVariances,
386 ) -> &'a [chalk_ir::Variance] {
387 &variances
388 }
373} 389}
374 390
375impl chalk_ir::interner::HasInterner for Interner { 391impl chalk_ir::interner::HasInterner for Interner {
diff --git a/crates/hir_ty/src/traits/chalk/mapping.rs b/crates/hir_ty/src/traits/chalk/mapping.rs
index 86cbc4c7e..8700d664e 100644
--- a/crates/hir_ty/src/traits/chalk/mapping.rs
+++ b/crates/hir_ty/src/traits/chalk/mapping.rs
@@ -31,7 +31,8 @@ impl ToChalk for Ty {
31 TypeCtor::Ref(m) => ref_to_chalk(db, m, apply_ty.parameters), 31 TypeCtor::Ref(m) => ref_to_chalk(db, m, apply_ty.parameters),
32 TypeCtor::Array => array_to_chalk(db, apply_ty.parameters), 32 TypeCtor::Array => array_to_chalk(db, apply_ty.parameters),
33 TypeCtor::FnPtr { num_args: _, is_varargs } => { 33 TypeCtor::FnPtr { num_args: _, is_varargs } => {
34 let substitution = apply_ty.parameters.to_chalk(db).shifted_in(&Interner); 34 let substitution =
35 chalk_ir::FnSubst(apply_ty.parameters.to_chalk(db).shifted_in(&Interner));
35 chalk_ir::TyKind::Function(chalk_ir::FnPointer { 36 chalk_ir::TyKind::Function(chalk_ir::FnPointer {
36 num_binders: 0, 37 num_binders: 0,
37 sig: chalk_ir::FnSig { 38 sig: chalk_ir::FnSig {
@@ -183,7 +184,7 @@ impl ToChalk for Ty {
183 assert_eq!(num_binders, 0); 184 assert_eq!(num_binders, 0);
184 let parameters: Substs = from_chalk( 185 let parameters: Substs = from_chalk(
185 db, 186 db,
186 substitution.shifted_out(&Interner).expect("fn ptr should have no binders"), 187 substitution.0.shifted_out(&Interner).expect("fn ptr should have no binders"),
187 ); 188 );
188 Ty::Apply(ApplicationTy { 189 Ty::Apply(ApplicationTy {
189 ctor: TypeCtor::FnPtr { 190 ctor: TypeCtor::FnPtr {
@@ -536,6 +537,7 @@ impl ToChalk for GenericPredicate {
536 // we don't produce any where clauses with binders and can't currently deal with them 537 // we don't produce any where clauses with binders and can't currently deal with them
537 match where_clause 538 match where_clause
538 .skip_binders() 539 .skip_binders()
540 .clone()
539 .shifted_out(&Interner) 541 .shifted_out(&Interner)
540 .expect("unexpected bound vars in where clause") 542 .expect("unexpected bound vars in where clause")
541 { 543 {
@@ -661,7 +663,12 @@ where
661 chalk_ir::TyVariableKind::Integer => TyKind::Integer, 663 chalk_ir::TyVariableKind::Integer => TyKind::Integer,
662 chalk_ir::TyVariableKind::Float => TyKind::Float, 664 chalk_ir::TyVariableKind::Float => TyKind::Float,
663 }, 665 },
664 chalk_ir::VariableKind::Lifetime => panic!("unexpected lifetime from Chalk"), 666 // HACK: Chalk can sometimes return new lifetime variables. We
667 // want to just skip them, but to not mess up the indices of
668 // other variables, we'll just create a new type variable in
669 // their place instead. This should not matter (we never see the
670 // actual *uses* of the lifetime variable).
671 chalk_ir::VariableKind::Lifetime => TyKind::General,
665 chalk_ir::VariableKind::Const(_) => panic!("unexpected const from Chalk"), 672 chalk_ir::VariableKind::Const(_) => panic!("unexpected const from Chalk"),
666 }) 673 })
667 .collect(); 674 .collect();
diff --git a/crates/rust-analyzer/src/diagnostics/test_data/clippy_pass_by_ref.txt b/crates/rust-analyzer/src/diagnostics/test_data/clippy_pass_by_ref.txt
index 72f6c5725..7576097b3 100644
--- a/crates/rust-analyzer/src/diagnostics/test_data/clippy_pass_by_ref.txt
+++ b/crates/rust-analyzer/src/diagnostics/test_data/clippy_pass_by_ref.txt
@@ -104,4 +104,168 @@
104 }, 104 },
105 fixes: [], 105 fixes: [],
106 }, 106 },
107 MappedRustDiagnostic {
108 url: Url {
109 scheme: "file",
110 host: None,
111 port: None,
112 path: "/test/compiler/lib.rs",
113 query: None,
114 fragment: None,
115 },
116 diagnostic: Diagnostic {
117 range: Range {
118 start: Position {
119 line: 0,
120 character: 8,
121 },
122 end: Position {
123 line: 0,
124 character: 19,
125 },
126 },
127 severity: Some(
128 Hint,
129 ),
130 code: Some(
131 String(
132 "trivially_copy_pass_by_ref",
133 ),
134 ),
135 code_description: Some(
136 CodeDescription {
137 href: Url {
138 scheme: "https",
139 host: Some(
140 Domain(
141 "rust-lang.github.io",
142 ),
143 ),
144 port: None,
145 path: "/rust-clippy/master/index.html",
146 query: None,
147 fragment: Some(
148 "trivially_copy_pass_by_ref",
149 ),
150 },
151 },
152 ),
153 source: Some(
154 "clippy",
155 ),
156 message: "lint level defined here",
157 related_information: Some(
158 [
159 DiagnosticRelatedInformation {
160 location: Location {
161 uri: Url {
162 scheme: "file",
163 host: None,
164 port: None,
165 path: "/test/compiler/mir/tagset.rs",
166 query: None,
167 fragment: None,
168 },
169 range: Range {
170 start: Position {
171 line: 41,
172 character: 23,
173 },
174 end: Position {
175 line: 41,
176 character: 28,
177 },
178 },
179 },
180 message: "original diagnostic",
181 },
182 ],
183 ),
184 tags: None,
185 data: None,
186 },
187 fixes: [],
188 },
189 MappedRustDiagnostic {
190 url: Url {
191 scheme: "file",
192 host: None,
193 port: None,
194 path: "/test/compiler/mir/tagset.rs",
195 query: None,
196 fragment: None,
197 },
198 diagnostic: Diagnostic {
199 range: Range {
200 start: Position {
201 line: 41,
202 character: 23,
203 },
204 end: Position {
205 line: 41,
206 character: 28,
207 },
208 },
209 severity: Some(
210 Hint,
211 ),
212 code: Some(
213 String(
214 "trivially_copy_pass_by_ref",
215 ),
216 ),
217 code_description: Some(
218 CodeDescription {
219 href: Url {
220 scheme: "https",
221 host: Some(
222 Domain(
223 "rust-lang.github.io",
224 ),
225 ),
226 port: None,
227 path: "/rust-clippy/master/index.html",
228 query: None,
229 fragment: Some(
230 "trivially_copy_pass_by_ref",
231 ),
232 },
233 },
234 ),
235 source: Some(
236 "clippy",
237 ),
238 message: "consider passing by value instead",
239 related_information: Some(
240 [
241 DiagnosticRelatedInformation {
242 location: Location {
243 uri: Url {
244 scheme: "file",
245 host: None,
246 port: None,
247 path: "/test/compiler/mir/tagset.rs",
248 query: None,
249 fragment: None,
250 },
251 range: Range {
252 start: Position {
253 line: 41,
254 character: 23,
255 },
256 end: Position {
257 line: 41,
258 character: 28,
259 },
260 },
261 },
262 message: "original diagnostic",
263 },
264 ],
265 ),
266 tags: None,
267 data: None,
268 },
269 fixes: [],
270 },
107] 271]
diff --git a/crates/rust-analyzer/src/diagnostics/test_data/macro_compiler_error.txt b/crates/rust-analyzer/src/diagnostics/test_data/macro_compiler_error.txt
index bbec6a796..bdcf2a38f 100644
--- a/crates/rust-analyzer/src/diagnostics/test_data/macro_compiler_error.txt
+++ b/crates/rust-analyzer/src/diagnostics/test_data/macro_compiler_error.txt
@@ -4,19 +4,19 @@
4 scheme: "file", 4 scheme: "file",
5 host: None, 5 host: None,
6 port: None, 6 port: None,
7 path: "/test/crates/hir_def/src/data.rs", 7 path: "/test/crates/hir_def/src/path.rs",
8 query: None, 8 query: None,
9 fragment: None, 9 fragment: None,
10 }, 10 },
11 diagnostic: Diagnostic { 11 diagnostic: Diagnostic {
12 range: Range { 12 range: Range {
13 start: Position { 13 start: Position {
14 line: 79, 14 line: 264,
15 character: 15, 15 character: 8,
16 }, 16 },
17 end: Position { 17 end: Position {
18 line: 79, 18 line: 264,
19 character: 41, 19 character: 76,
20 }, 20 },
21 }, 21 },
22 severity: Some( 22 severity: Some(
@@ -36,22 +36,22 @@
36 scheme: "file", 36 scheme: "file",
37 host: None, 37 host: None,
38 port: None, 38 port: None,
39 path: "/test/crates/hir_def/src/path.rs", 39 path: "/test/crates/hir_def/src/data.rs",
40 query: None, 40 query: None,
41 fragment: None, 41 fragment: None,
42 }, 42 },
43 range: Range { 43 range: Range {
44 start: Position { 44 start: Position {
45 line: 264, 45 line: 79,
46 character: 8, 46 character: 15,
47 }, 47 },
48 end: Position { 48 end: Position {
49 line: 264, 49 line: 79,
50 character: 76, 50 character: 41,
51 }, 51 },
52 }, 52 },
53 }, 53 },
54 message: "Error originated from macro here", 54 message: "Exact error occurred here",
55 }, 55 },
56 ], 56 ],
57 ), 57 ),
@@ -65,19 +65,19 @@
65 scheme: "file", 65 scheme: "file",
66 host: None, 66 host: None,
67 port: None, 67 port: None,
68 path: "/test/crates/hir_def/src/path.rs", 68 path: "/test/crates/hir_def/src/data.rs",
69 query: None, 69 query: None,
70 fragment: None, 70 fragment: None,
71 }, 71 },
72 diagnostic: Diagnostic { 72 diagnostic: Diagnostic {
73 range: Range { 73 range: Range {
74 start: Position { 74 start: Position {
75 line: 264, 75 line: 79,
76 character: 8, 76 character: 15,
77 }, 77 },
78 end: Position { 78 end: Position {
79 line: 264, 79 line: 79,
80 character: 76, 80 character: 41,
81 }, 81 },
82 }, 82 },
83 severity: Some( 83 severity: Some(
@@ -89,33 +89,7 @@
89 "rustc", 89 "rustc",
90 ), 90 ),
91 message: "Please register your known path in the path module", 91 message: "Please register your known path in the path module",
92 related_information: Some( 92 related_information: None,
93 [
94 DiagnosticRelatedInformation {
95 location: Location {
96 uri: Url {
97 scheme: "file",
98 host: None,
99 port: None,
100 path: "/test/crates/hir_def/src/data.rs",
101 query: None,
102 fragment: None,
103 },
104 range: Range {
105 start: Position {
106 line: 79,
107 character: 15,
108 },
109 end: Position {
110 line: 79,
111 character: 41,
112 },
113 },
114 },
115 message: "Exact error occured here",
116 },
117 ],
118 ),
119 tags: None, 93 tags: None,
120 data: None, 94 data: None,
121 }, 95 },
diff --git a/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable.txt b/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable.txt
index c709de95f..23d42b4d0 100644
--- a/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable.txt
+++ b/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable.txt
@@ -32,7 +32,33 @@
32 "rustc", 32 "rustc",
33 ), 33 ),
34 message: "unused variable: `foo`\n#[warn(unused_variables)] on by default", 34 message: "unused variable: `foo`\n#[warn(unused_variables)] on by default",
35 related_information: None, 35 related_information: Some(
36 [
37 DiagnosticRelatedInformation {
38 location: Location {
39 uri: Url {
40 scheme: "file",
41 host: None,
42 port: None,
43 path: "/test/driver/subcommand/repl.rs",
44 query: None,
45 fragment: None,
46 },
47 range: Range {
48 start: Position {
49 line: 290,
50 character: 8,
51 },
52 end: Position {
53 line: 290,
54 character: 11,
55 },
56 },
57 },
58 message: "consider prefixing with an underscore",
59 },
60 ],
61 ),
36 tags: Some( 62 tags: Some(
37 [ 63 [
38 Unnecessary, 64 Unnecessary,
@@ -87,4 +113,114 @@
87 }, 113 },
88 ], 114 ],
89 }, 115 },
116 MappedRustDiagnostic {
117 url: Url {
118 scheme: "file",
119 host: None,
120 port: None,
121 path: "/test/driver/subcommand/repl.rs",
122 query: None,
123 fragment: None,
124 },
125 diagnostic: Diagnostic {
126 range: Range {
127 start: Position {
128 line: 290,
129 character: 8,
130 },
131 end: Position {
132 line: 290,
133 character: 11,
134 },
135 },
136 severity: Some(
137 Hint,
138 ),
139 code: Some(
140 String(
141 "unused_variables",
142 ),
143 ),
144 code_description: None,
145 source: Some(
146 "rustc",
147 ),
148 message: "consider prefixing with an underscore",
149 related_information: Some(
150 [
151 DiagnosticRelatedInformation {
152 location: Location {
153 uri: Url {
154 scheme: "file",
155 host: None,
156 port: None,
157 path: "/test/driver/subcommand/repl.rs",
158 query: None,
159 fragment: None,
160 },
161 range: Range {
162 start: Position {
163 line: 290,
164 character: 8,
165 },
166 end: Position {
167 line: 290,
168 character: 11,
169 },
170 },
171 },
172 message: "original diagnostic",
173 },
174 ],
175 ),
176 tags: None,
177 data: None,
178 },
179 fixes: [
180 CodeAction {
181 title: "consider prefixing with an underscore",
182 group: None,
183 kind: Some(
184 CodeActionKind(
185 "quickfix",
186 ),
187 ),
188 edit: Some(
189 SnippetWorkspaceEdit {
190 changes: Some(
191 {
192 Url {
193 scheme: "file",
194 host: None,
195 port: None,
196 path: "/test/driver/subcommand/repl.rs",
197 query: None,
198 fragment: None,
199 }: [
200 TextEdit {
201 range: Range {
202 start: Position {
203 line: 290,
204 character: 8,
205 },
206 end: Position {
207 line: 290,
208 character: 11,
209 },
210 },
211 new_text: "_foo",
212 },
213 ],
214 },
215 ),
216 document_changes: None,
217 },
218 ),
219 is_preferred: Some(
220 true,
221 ),
222 data: None,
223 },
224 ],
225 },
90] 226]
diff --git a/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable_as_hint.txt b/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable_as_hint.txt
index 632f438d7..4e428bedc 100644
--- a/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable_as_hint.txt
+++ b/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable_as_hint.txt
@@ -32,7 +32,33 @@
32 "rustc", 32 "rustc",
33 ), 33 ),
34 message: "unused variable: `foo`\n#[warn(unused_variables)] on by default", 34 message: "unused variable: `foo`\n#[warn(unused_variables)] on by default",
35 related_information: None, 35 related_information: Some(
36 [
37 DiagnosticRelatedInformation {
38 location: Location {
39 uri: Url {
40 scheme: "file",
41 host: None,
42 port: None,
43 path: "/test/driver/subcommand/repl.rs",
44 query: None,
45 fragment: None,
46 },
47 range: Range {
48 start: Position {
49 line: 290,
50 character: 8,
51 },
52 end: Position {
53 line: 290,
54 character: 11,
55 },
56 },
57 },
58 message: "consider prefixing with an underscore",
59 },
60 ],
61 ),
36 tags: Some( 62 tags: Some(
37 [ 63 [
38 Unnecessary, 64 Unnecessary,
@@ -87,4 +113,114 @@
87 }, 113 },
88 ], 114 ],
89 }, 115 },
116 MappedRustDiagnostic {
117 url: Url {
118 scheme: "file",
119 host: None,
120 port: None,
121 path: "/test/driver/subcommand/repl.rs",
122 query: None,
123 fragment: None,
124 },
125 diagnostic: Diagnostic {
126 range: Range {
127 start: Position {
128 line: 290,
129 character: 8,
130 },
131 end: Position {
132 line: 290,
133 character: 11,
134 },
135 },
136 severity: Some(
137 Hint,
138 ),
139 code: Some(
140 String(
141 "unused_variables",
142 ),
143 ),
144 code_description: None,
145 source: Some(
146 "rustc",
147 ),
148 message: "consider prefixing with an underscore",
149 related_information: Some(
150 [
151 DiagnosticRelatedInformation {
152 location: Location {
153 uri: Url {
154 scheme: "file",
155 host: None,
156 port: None,
157 path: "/test/driver/subcommand/repl.rs",
158 query: None,
159 fragment: None,
160 },
161 range: Range {
162 start: Position {
163 line: 290,
164 character: 8,
165 },
166 end: Position {
167 line: 290,
168 character: 11,
169 },
170 },
171 },
172 message: "original diagnostic",
173 },
174 ],
175 ),
176 tags: None,
177 data: None,
178 },
179 fixes: [
180 CodeAction {
181 title: "consider prefixing with an underscore",
182 group: None,
183 kind: Some(
184 CodeActionKind(
185 "quickfix",
186 ),
187 ),
188 edit: Some(
189 SnippetWorkspaceEdit {
190 changes: Some(
191 {
192 Url {
193 scheme: "file",
194 host: None,
195 port: None,
196 path: "/test/driver/subcommand/repl.rs",
197 query: None,
198 fragment: None,
199 }: [
200 TextEdit {
201 range: Range {
202 start: Position {
203 line: 290,
204 character: 8,
205 },
206 end: Position {
207 line: 290,
208 character: 11,
209 },
210 },
211 new_text: "_foo",
212 },
213 ],
214 },
215 ),
216 document_changes: None,
217 },
218 ),
219 is_preferred: Some(
220 true,
221 ),
222 data: None,
223 },
224 ],
225 },
90] 226]
diff --git a/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable_as_info.txt b/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable_as_info.txt
index c0b79428d..4ddd7efae 100644
--- a/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable_as_info.txt
+++ b/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable_as_info.txt
@@ -32,7 +32,33 @@
32 "rustc", 32 "rustc",
33 ), 33 ),
34 message: "unused variable: `foo`\n#[warn(unused_variables)] on by default", 34 message: "unused variable: `foo`\n#[warn(unused_variables)] on by default",
35 related_information: None, 35 related_information: Some(
36 [
37 DiagnosticRelatedInformation {
38 location: Location {
39 uri: Url {
40 scheme: "file",
41 host: None,
42 port: None,
43 path: "/test/driver/subcommand/repl.rs",
44 query: None,
45 fragment: None,
46 },
47 range: Range {
48 start: Position {
49 line: 290,
50 character: 8,
51 },
52 end: Position {
53 line: 290,
54 character: 11,
55 },
56 },
57 },
58 message: "consider prefixing with an underscore",
59 },
60 ],
61 ),
36 tags: Some( 62 tags: Some(
37 [ 63 [
38 Unnecessary, 64 Unnecessary,
@@ -87,4 +113,114 @@
87 }, 113 },
88 ], 114 ],
89 }, 115 },
116 MappedRustDiagnostic {
117 url: Url {
118 scheme: "file",
119 host: None,
120 port: None,
121 path: "/test/driver/subcommand/repl.rs",
122 query: None,
123 fragment: None,
124 },
125 diagnostic: Diagnostic {
126 range: Range {
127 start: Position {
128 line: 290,
129 character: 8,
130 },
131 end: Position {
132 line: 290,
133 character: 11,
134 },
135 },
136 severity: Some(
137 Hint,
138 ),
139 code: Some(
140 String(
141 "unused_variables",
142 ),
143 ),
144 code_description: None,
145 source: Some(
146 "rustc",
147 ),
148 message: "consider prefixing with an underscore",
149 related_information: Some(
150 [
151 DiagnosticRelatedInformation {
152 location: Location {
153 uri: Url {
154 scheme: "file",
155 host: None,
156 port: None,
157 path: "/test/driver/subcommand/repl.rs",
158 query: None,
159 fragment: None,
160 },
161 range: Range {
162 start: Position {
163 line: 290,
164 character: 8,
165 },
166 end: Position {
167 line: 290,
168 character: 11,
169 },
170 },
171 },
172 message: "original diagnostic",
173 },
174 ],
175 ),
176 tags: None,
177 data: None,
178 },
179 fixes: [
180 CodeAction {
181 title: "consider prefixing with an underscore",
182 group: None,
183 kind: Some(
184 CodeActionKind(
185 "quickfix",
186 ),
187 ),
188 edit: Some(
189 SnippetWorkspaceEdit {
190 changes: Some(
191 {
192 Url {
193 scheme: "file",
194 host: None,
195 port: None,
196 path: "/test/driver/subcommand/repl.rs",
197 query: None,
198 fragment: None,
199 }: [
200 TextEdit {
201 range: Range {
202 start: Position {
203 line: 290,
204 character: 8,
205 },
206 end: Position {
207 line: 290,
208 character: 11,
209 },
210 },
211 new_text: "_foo",
212 },
213 ],
214 },
215 ),
216 document_changes: None,
217 },
218 ),
219 is_preferred: Some(
220 true,
221 ),
222 data: None,
223 },
224 ],
225 },
90] 226]
diff --git a/crates/rust-analyzer/src/diagnostics/test_data/rustc_wrong_number_of_parameters.txt b/crates/rust-analyzer/src/diagnostics/test_data/rustc_wrong_number_of_parameters.txt
index b9650f3e4..f455cf25e 100644
--- a/crates/rust-analyzer/src/diagnostics/test_data/rustc_wrong_number_of_parameters.txt
+++ b/crates/rust-analyzer/src/diagnostics/test_data/rustc_wrong_number_of_parameters.txt
@@ -81,4 +81,86 @@
81 }, 81 },
82 fixes: [], 82 fixes: [],
83 }, 83 },
84 MappedRustDiagnostic {
85 url: Url {
86 scheme: "file",
87 host: None,
88 port: None,
89 path: "/test/compiler/ty/select.rs",
90 query: None,
91 fragment: None,
92 },
93 diagnostic: Diagnostic {
94 range: Range {
95 start: Position {
96 line: 218,
97 character: 4,
98 },
99 end: Position {
100 line: 230,
101 character: 5,
102 },
103 },
104 severity: Some(
105 Hint,
106 ),
107 code: Some(
108 String(
109 "E0061",
110 ),
111 ),
112 code_description: Some(
113 CodeDescription {
114 href: Url {
115 scheme: "https",
116 host: Some(
117 Domain(
118 "doc.rust-lang.org",
119 ),
120 ),
121 port: None,
122 path: "/error-index.html",
123 query: None,
124 fragment: Some(
125 "E0061",
126 ),
127 },
128 },
129 ),
130 source: Some(
131 "rustc",
132 ),
133 message: "defined here",
134 related_information: Some(
135 [
136 DiagnosticRelatedInformation {
137 location: Location {
138 uri: Url {
139 scheme: "file",
140 host: None,
141 port: None,
142 path: "/test/compiler/ty/select.rs",
143 query: None,
144 fragment: None,
145 },
146 range: Range {
147 start: Position {
148 line: 103,
149 character: 17,
150 },
151 end: Position {
152 line: 103,
153 character: 29,
154 },
155 },
156 },
157 message: "original diagnostic",
158 },
159 ],
160 ),
161 tags: None,
162 data: None,
163 },
164 fixes: [],
165 },
84] 166]
diff --git a/crates/rust-analyzer/src/diagnostics/test_data/snap_multi_line_fix.txt b/crates/rust-analyzer/src/diagnostics/test_data/snap_multi_line_fix.txt
index c45f68a91..4cbdb3b92 100644
--- a/crates/rust-analyzer/src/diagnostics/test_data/snap_multi_line_fix.txt
+++ b/crates/rust-analyzer/src/diagnostics/test_data/snap_multi_line_fix.txt
@@ -74,6 +74,309 @@
74 }, 74 },
75 message: "unnecessary let binding", 75 message: "unnecessary let binding",
76 }, 76 },
77 DiagnosticRelatedInformation {
78 location: Location {
79 uri: Url {
80 scheme: "file",
81 host: None,
82 port: None,
83 path: "/test/src/main.rs",
84 query: None,
85 fragment: None,
86 },
87 range: Range {
88 start: Position {
89 line: 2,
90 character: 4,
91 },
92 end: Position {
93 line: 2,
94 character: 30,
95 },
96 },
97 },
98 message: "return the expression directly",
99 },
100 ],
101 ),
102 tags: None,
103 data: None,
104 },
105 fixes: [
106 CodeAction {
107 title: "return the expression directly",
108 group: None,
109 kind: Some(
110 CodeActionKind(
111 "quickfix",
112 ),
113 ),
114 edit: Some(
115 SnippetWorkspaceEdit {
116 changes: Some(
117 {
118 Url {
119 scheme: "file",
120 host: None,
121 port: None,
122 path: "/test/src/main.rs",
123 query: None,
124 fragment: None,
125 }: [
126 TextEdit {
127 range: Range {
128 start: Position {
129 line: 2,
130 character: 4,
131 },
132 end: Position {
133 line: 2,
134 character: 30,
135 },
136 },
137 new_text: "",
138 },
139 TextEdit {
140 range: Range {
141 start: Position {
142 line: 3,
143 character: 4,
144 },
145 end: Position {
146 line: 3,
147 character: 5,
148 },
149 },
150 new_text: "(0..10).collect()",
151 },
152 ],
153 },
154 ),
155 document_changes: None,
156 },
157 ),
158 is_preferred: Some(
159 true,
160 ),
161 data: None,
162 },
163 ],
164 },
165 MappedRustDiagnostic {
166 url: Url {
167 scheme: "file",
168 host: None,
169 port: None,
170 path: "/test/src/main.rs",
171 query: None,
172 fragment: None,
173 },
174 diagnostic: Diagnostic {
175 range: Range {
176 start: Position {
177 line: 2,
178 character: 4,
179 },
180 end: Position {
181 line: 2,
182 character: 30,
183 },
184 },
185 severity: Some(
186 Hint,
187 ),
188 code: Some(
189 String(
190 "let_and_return",
191 ),
192 ),
193 code_description: Some(
194 CodeDescription {
195 href: Url {
196 scheme: "https",
197 host: Some(
198 Domain(
199 "rust-lang.github.io",
200 ),
201 ),
202 port: None,
203 path: "/rust-clippy/master/index.html",
204 query: None,
205 fragment: Some(
206 "let_and_return",
207 ),
208 },
209 },
210 ),
211 source: Some(
212 "clippy",
213 ),
214 message: "unnecessary let binding",
215 related_information: Some(
216 [
217 DiagnosticRelatedInformation {
218 location: Location {
219 uri: Url {
220 scheme: "file",
221 host: None,
222 port: None,
223 path: "/test/src/main.rs",
224 query: None,
225 fragment: None,
226 },
227 range: Range {
228 start: Position {
229 line: 3,
230 character: 4,
231 },
232 end: Position {
233 line: 3,
234 character: 5,
235 },
236 },
237 },
238 message: "original diagnostic",
239 },
240 ],
241 ),
242 tags: None,
243 data: None,
244 },
245 fixes: [
246 CodeAction {
247 title: "return the expression directly",
248 group: None,
249 kind: Some(
250 CodeActionKind(
251 "quickfix",
252 ),
253 ),
254 edit: Some(
255 SnippetWorkspaceEdit {
256 changes: Some(
257 {
258 Url {
259 scheme: "file",
260 host: None,
261 port: None,
262 path: "/test/src/main.rs",
263 query: None,
264 fragment: None,
265 }: [
266 TextEdit {
267 range: Range {
268 start: Position {
269 line: 2,
270 character: 4,
271 },
272 end: Position {
273 line: 2,
274 character: 30,
275 },
276 },
277 new_text: "",
278 },
279 TextEdit {
280 range: Range {
281 start: Position {
282 line: 3,
283 character: 4,
284 },
285 end: Position {
286 line: 3,
287 character: 5,
288 },
289 },
290 new_text: "(0..10).collect()",
291 },
292 ],
293 },
294 ),
295 document_changes: None,
296 },
297 ),
298 is_preferred: Some(
299 true,
300 ),
301 data: None,
302 },
303 ],
304 },
305 MappedRustDiagnostic {
306 url: Url {
307 scheme: "file",
308 host: None,
309 port: None,
310 path: "/test/src/main.rs",
311 query: None,
312 fragment: None,
313 },
314 diagnostic: Diagnostic {
315 range: Range {
316 start: Position {
317 line: 2,
318 character: 4,
319 },
320 end: Position {
321 line: 2,
322 character: 30,
323 },
324 },
325 severity: Some(
326 Hint,
327 ),
328 code: Some(
329 String(
330 "let_and_return",
331 ),
332 ),
333 code_description: Some(
334 CodeDescription {
335 href: Url {
336 scheme: "https",
337 host: Some(
338 Domain(
339 "rust-lang.github.io",
340 ),
341 ),
342 port: None,
343 path: "/rust-clippy/master/index.html",
344 query: None,
345 fragment: Some(
346 "let_and_return",
347 ),
348 },
349 },
350 ),
351 source: Some(
352 "clippy",
353 ),
354 message: "return the expression directly",
355 related_information: Some(
356 [
357 DiagnosticRelatedInformation {
358 location: Location {
359 uri: Url {
360 scheme: "file",
361 host: None,
362 port: None,
363 path: "/test/src/main.rs",
364 query: None,
365 fragment: None,
366 },
367 range: Range {
368 start: Position {
369 line: 3,
370 character: 4,
371 },
372 end: Position {
373 line: 3,
374 character: 5,
375 },
376 },
377 },
378 message: "original diagnostic",
379 },
77 ], 380 ],
78 ), 381 ),
79 tags: None, 382 tags: None,
diff --git a/crates/rust-analyzer/src/diagnostics/to_proto.rs b/crates/rust-analyzer/src/diagnostics/to_proto.rs
index 324019614..f16f97131 100644
--- a/crates/rust-analyzer/src/diagnostics/to_proto.rs
+++ b/crates/rust-analyzer/src/diagnostics/to_proto.rs
@@ -75,8 +75,10 @@ fn diagnostic_related_information(
75} 75}
76 76
77enum MappedRustChildDiagnostic { 77enum MappedRustChildDiagnostic {
78 Related(lsp_types::DiagnosticRelatedInformation), 78 Related {
79 SuggestedFix(lsp_ext::CodeAction), 79 related: lsp_types::DiagnosticRelatedInformation,
80 suggested_fix: Option<lsp_ext::CodeAction>,
81 },
80 MessageLine(String), 82 MessageLine(String),
81} 83}
82 84
@@ -103,23 +105,32 @@ fn map_rust_child_diagnostic(
103 } 105 }
104 106
105 if edit_map.is_empty() { 107 if edit_map.is_empty() {
106 MappedRustChildDiagnostic::Related(lsp_types::DiagnosticRelatedInformation { 108 MappedRustChildDiagnostic::Related {
107 location: location(workspace_root, spans[0]), 109 related: lsp_types::DiagnosticRelatedInformation {
108 message: rd.message.clone(), 110 location: location(workspace_root, spans[0]),
109 }) 111 message: rd.message.clone(),
112 },
113 suggested_fix: None,
114 }
110 } else { 115 } else {
111 MappedRustChildDiagnostic::SuggestedFix(lsp_ext::CodeAction { 116 MappedRustChildDiagnostic::Related {
112 title: rd.message.clone(), 117 related: lsp_types::DiagnosticRelatedInformation {
113 group: None, 118 location: location(workspace_root, spans[0]),
114 kind: Some(lsp_types::CodeActionKind::QUICKFIX), 119 message: rd.message.clone(),
115 edit: Some(lsp_ext::SnippetWorkspaceEdit { 120 },
116 // FIXME: there's no good reason to use edit_map here.... 121 suggested_fix: Some(lsp_ext::CodeAction {
117 changes: Some(edit_map), 122 title: rd.message.clone(),
118 document_changes: None, 123 group: None,
124 kind: Some(lsp_types::CodeActionKind::QUICKFIX),
125 edit: Some(lsp_ext::SnippetWorkspaceEdit {
126 // FIXME: there's no good reason to use edit_map here....
127 changes: Some(edit_map),
128 document_changes: None,
129 }),
130 is_preferred: Some(true),
131 data: None,
119 }), 132 }),
120 is_preferred: Some(true), 133 }
121 data: None,
122 })
123 } 134 }
124} 135}
125 136
@@ -179,8 +190,12 @@ pub(crate) fn map_rust_diagnostic_to_lsp(
179 for child in &rd.children { 190 for child in &rd.children {
180 let child = map_rust_child_diagnostic(workspace_root, &child); 191 let child = map_rust_child_diagnostic(workspace_root, &child);
181 match child { 192 match child {
182 MappedRustChildDiagnostic::Related(related) => related_information.push(related), 193 MappedRustChildDiagnostic::Related { related, suggested_fix } => {
183 MappedRustChildDiagnostic::SuggestedFix(code_action) => fixes.push(code_action), 194 related_information.push(related);
195 if let Some(code_action) = suggested_fix {
196 fixes.push(code_action);
197 }
198 }
184 MappedRustChildDiagnostic::MessageLine(message_line) => { 199 MappedRustChildDiagnostic::MessageLine(message_line) => {
185 format_to!(message, "\n{}", message_line); 200 format_to!(message, "\n{}", message_line);
186 201
@@ -219,7 +234,7 @@ pub(crate) fn map_rust_diagnostic_to_lsp(
219 234
220 primary_spans 235 primary_spans
221 .iter() 236 .iter()
222 .map(|primary_span| { 237 .flat_map(|primary_span| {
223 let location = location(workspace_root, &primary_span); 238 let location = location(workspace_root, &primary_span);
224 239
225 let mut message = message.clone(); 240 let mut message = message.clone();
@@ -229,72 +244,100 @@ pub(crate) fn map_rust_diagnostic_to_lsp(
229 } 244 }
230 } 245 }
231 246
247 // Each primary diagnostic span may result in multiple LSP diagnostics.
248 let mut diagnostics = Vec::new();
249
250 let mut related_macro_info = None;
251
232 // If error occurs from macro expansion, add related info pointing to 252 // If error occurs from macro expansion, add related info pointing to
233 // where the error originated 253 // where the error originated
234 // Also, we would generate an additional diagnostic, so that exact place of macro 254 // Also, we would generate an additional diagnostic, so that exact place of macro
235 // will be highlighted in the error origin place. 255 // will be highlighted in the error origin place.
236 let additional_diagnostic = 256 if !is_from_macro(&primary_span.file_name) && primary_span.expansion.is_some() {
237 if !is_from_macro(&primary_span.file_name) && primary_span.expansion.is_some() { 257 let in_macro_location = location_naive(workspace_root, &primary_span);
238 let in_macro_location = location_naive(workspace_root, &primary_span);
239 258
240 // Add related information for the main disagnostic. 259 // Add related information for the main disagnostic.
241 related_information.push(lsp_types::DiagnosticRelatedInformation { 260 related_macro_info = Some(lsp_types::DiagnosticRelatedInformation {
242 location: in_macro_location.clone(), 261 location: in_macro_location.clone(),
243 message: "Error originated from macro here".to_string(), 262 message: "Error originated from macro here".to_string(),
244 }); 263 });
245 264
246 // For the additional in-macro diagnostic we add the inverse message pointing to the error location in code. 265 // For the additional in-macro diagnostic we add the inverse message pointing to the error location in code.
247 let information_for_additional_diagnostic = 266 let information_for_additional_diagnostic =
248 vec![lsp_types::DiagnosticRelatedInformation { 267 vec![lsp_types::DiagnosticRelatedInformation {
249 location: location.clone(), 268 location: location.clone(),
250 message: "Exact error occured here".to_string(), 269 message: "Exact error occurred here".to_string(),
251 }]; 270 }];
252 271
253 let diagnostic = lsp_types::Diagnostic { 272 let diagnostic = lsp_types::Diagnostic {
254 range: in_macro_location.range, 273 range: in_macro_location.range,
255 severity, 274 severity,
256 code: code.clone().map(lsp_types::NumberOrString::String), 275 code: code.clone().map(lsp_types::NumberOrString::String),
257 code_description: code_description.clone(), 276 code_description: code_description.clone(),
258 source: Some(source.clone()), 277 source: Some(source.clone()),
259 message: message.clone(), 278 message: message.clone(),
260 related_information: Some(information_for_additional_diagnostic), 279 related_information: Some(information_for_additional_diagnostic),
261 tags: if tags.is_empty() { None } else { Some(tags.clone()) }, 280 tags: if tags.is_empty() { None } else { Some(tags.clone()) },
262 data: None, 281 data: None,
263 };
264
265 Some(MappedRustDiagnostic {
266 url: in_macro_location.uri,
267 diagnostic,
268 fixes: fixes.clone(),
269 })
270 } else {
271 None
272 }; 282 };
273 283
274 let diagnostic = lsp_types::Diagnostic { 284 diagnostics.push(MappedRustDiagnostic {
275 range: location.range, 285 url: in_macro_location.uri,
276 severity, 286 diagnostic,
277 code: code.clone().map(lsp_types::NumberOrString::String), 287 fixes: fixes.clone(),
278 code_description: code_description.clone(), 288 });
279 source: Some(source.clone()), 289 }
280 message, 290
281 related_information: if related_information.is_empty() { 291 // Emit the primary diagnostic.
282 None 292 diagnostics.push(MappedRustDiagnostic {
283 } else { 293 url: location.uri.clone(),
284 Some(related_information.clone()) 294 diagnostic: lsp_types::Diagnostic {
295 range: location.range,
296 severity,
297 code: code.clone().map(lsp_types::NumberOrString::String),
298 code_description: code_description.clone(),
299 source: Some(source.clone()),
300 message,
301 related_information: if related_information.is_empty() {
302 None
303 } else {
304 let mut related = related_information.clone();
305 related.extend(related_macro_info);
306 Some(related)
307 },
308 tags: if tags.is_empty() { None } else { Some(tags.clone()) },
309 data: None,
285 }, 310 },
286 tags: if tags.is_empty() { None } else { Some(tags.clone()) }, 311 fixes: fixes.clone(),
287 data: None, 312 });
288 };
289 313
290 let main_diagnostic = 314 // Emit hint-level diagnostics for all `related_information` entries such as "help"s.
291 MappedRustDiagnostic { url: location.uri, diagnostic, fixes: fixes.clone() }; 315 // This is useful because they will show up in the user's editor, unlike
292 match additional_diagnostic { 316 // `related_information`, which just produces hard-to-read links, at least in VS Code.
293 None => vec![main_diagnostic], 317 let back_ref = lsp_types::DiagnosticRelatedInformation {
294 Some(additional_diagnostic) => vec![main_diagnostic, additional_diagnostic], 318 location,
319 message: "original diagnostic".to_string(),
320 };
321 for info in &related_information {
322 diagnostics.push(MappedRustDiagnostic {
323 url: info.location.uri.clone(),
324 fixes: fixes.clone(), // share fixes to make them easier to apply
325 diagnostic: lsp_types::Diagnostic {
326 range: info.location.range,
327 severity: Some(lsp_types::DiagnosticSeverity::Hint),
328 code: code.clone().map(lsp_types::NumberOrString::String),
329 code_description: code_description.clone(),
330 source: Some(source.clone()),
331 message: info.message.clone(),
332 related_information: Some(vec![back_ref.clone()]),
333 tags: None, // don't apply modifiers again
334 data: None,
335 },
336 });
295 } 337 }
338
339 diagnostics
296 }) 340 })
297 .flatten()
298 .collect() 341 .collect()
299} 342}
300 343
diff --git a/crates/syntax/src/ast/make.rs b/crates/syntax/src/ast/make.rs
index 876659a2b..cc09b77a5 100644
--- a/crates/syntax/src/ast/make.rs
+++ b/crates/syntax/src/ast/make.rs
@@ -171,8 +171,17 @@ pub fn expr_return() -> ast::Expr {
171pub fn expr_match(expr: ast::Expr, match_arm_list: ast::MatchArmList) -> ast::Expr { 171pub fn expr_match(expr: ast::Expr, match_arm_list: ast::MatchArmList) -> ast::Expr {
172 expr_from_text(&format!("match {} {}", expr, match_arm_list)) 172 expr_from_text(&format!("match {} {}", expr, match_arm_list))
173} 173}
174pub fn expr_if(condition: ast::Condition, then_branch: ast::BlockExpr) -> ast::Expr { 174pub fn expr_if(
175 expr_from_text(&format!("if {} {}", condition, then_branch)) 175 condition: ast::Condition,
176 then_branch: ast::BlockExpr,
177 else_branch: Option<ast::ElseBranch>,
178) -> ast::Expr {
179 let else_branch = match else_branch {
180 Some(ast::ElseBranch::Block(block)) => format!("else {}", block),
181 Some(ast::ElseBranch::IfExpr(if_expr)) => format!("else {}", if_expr),
182 None => String::new(),
183 };
184 expr_from_text(&format!("if {} {} {}", condition, then_branch, else_branch))
176} 185}
177pub fn expr_prefix(op: SyntaxKind, expr: ast::Expr) -> ast::Expr { 186pub fn expr_prefix(op: SyntaxKind, expr: ast::Expr) -> ast::Expr {
178 let token = token(op); 187 let token = token(op);