aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock17
-rw-r--r--crates/flycheck/src/lib.rs1
-rw-r--r--crates/hir/src/lib.rs9
-rw-r--r--crates/hir_def/src/body.rs4
-rw-r--r--crates/hir_def/src/body/lower.rs10
-rw-r--r--crates/hir_def/src/expr.rs4
-rw-r--r--crates/hir_def/src/find_path.rs13
-rw-r--r--crates/hir_def/src/nameres/collector.rs5
-rw-r--r--crates/hir_def/src/nameres/tests.rs16
-rw-r--r--crates/hir_def/src/type_ref.rs73
-rw-r--r--crates/hir_expand/src/builtin_macro.rs35
-rw-r--r--crates/hir_expand/src/name.rs1
-rw-r--r--crates/hir_ty/src/consteval.rs56
-rw-r--r--crates/hir_ty/src/consts.rs21
-rw-r--r--crates/hir_ty/src/diagnostics/match_check.rs1
-rw-r--r--crates/hir_ty/src/display.rs5
-rw-r--r--crates/hir_ty/src/infer/expr.rs32
-rw-r--r--crates/hir_ty/src/infer/pat.rs9
-rw-r--r--crates/hir_ty/src/interner.rs3
-rw-r--r--crates/hir_ty/src/lib.rs10
-rw-r--r--crates/hir_ty/src/lower.rs11
-rw-r--r--crates/hir_ty/src/tests/coercion.rs46
-rw-r--r--crates/hir_ty/src/tests/patterns.rs10
-rw-r--r--crates/hir_ty/src/tests/simple.rs65
-rw-r--r--crates/hir_ty/src/tests/traits.rs97
-rw-r--r--crates/ide/src/diagnostics.rs596
-rw-r--r--crates/ide/src/diagnostics/field_shorthand.rs13
-rw-r--r--crates/ide/src/diagnostics/fixes.rs296
-rw-r--r--crates/ide/src/diagnostics/fixes/change_case.rs155
-rw-r--r--crates/ide/src/diagnostics/fixes/create_field.rs156
-rw-r--r--crates/ide/src/diagnostics/fixes/fill_missing_fields.rs217
-rw-r--r--crates/ide/src/diagnostics/fixes/remove_semicolon.rs41
-rw-r--r--crates/ide/src/diagnostics/fixes/replace_with_find_map.rs84
-rw-r--r--crates/ide/src/diagnostics/fixes/unresolved_module.rs89
-rw-r--r--crates/ide/src/diagnostics/fixes/wrap_tail_expr.rs211
-rw-r--r--crates/ide/src/diagnostics/unlinked_file.rs54
-rw-r--r--crates/ide/src/doc_links.rs8
-rw-r--r--crates/ide/src/join_lines.rs2
-rw-r--r--crates/ide/src/lib.rs2
-rw-r--r--crates/ide/src/matching_brace.rs12
-rw-r--r--crates/ide/src/parent_module.rs6
-rw-r--r--crates/ide/src/syntax_highlighting/highlight.rs11
-rw-r--r--crates/ide/src/syntax_highlighting/inject.rs2
-rw-r--r--crates/ide/src/syntax_highlighting/tags.rs4
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html14
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlighting.html15
-rw-r--r--crates/ide/src/syntax_highlighting/tests.rs27
-rw-r--r--crates/ide_assists/src/assist_context.rs6
-rw-r--r--crates/ide_assists/src/handlers/add_explicit_type.rs28
-rw-r--r--crates/ide_assists/src/handlers/add_missing_impl_members.rs3
-rw-r--r--crates/ide_assists/src/handlers/auto_import.rs30
-rw-r--r--crates/ide_assists/src/handlers/expand_glob_import.rs2
-rw-r--r--crates/ide_assists/src/handlers/extract_function.rs65
-rw-r--r--crates/ide_assists/src/handlers/extract_struct_from_enum_variant.rs6
-rw-r--r--crates/ide_assists/src/handlers/fill_match_arms.rs633
-rw-r--r--crates/ide_assists/src/handlers/generate_default_from_new.rs287
-rw-r--r--crates/ide_assists/src/handlers/generate_new.rs153
-rw-r--r--crates/ide_assists/src/handlers/introduce_named_lifetime.rs12
-rw-r--r--crates/ide_assists/src/handlers/merge_imports.rs8
-rw-r--r--crates/ide_assists/src/handlers/move_bounds.rs4
-rw-r--r--crates/ide_assists/src/handlers/move_module_to_file.rs38
-rw-r--r--crates/ide_assists/src/handlers/pull_assignment_up.rs4
-rw-r--r--crates/ide_assists/src/handlers/raw_string.rs2
-rw-r--r--crates/ide_assists/src/handlers/reorder_fields.rs4
-rw-r--r--crates/ide_assists/src/handlers/reorder_impl.rs3
-rw-r--r--crates/ide_assists/src/handlers/replace_impl_trait_with_generic.rs4
-rw-r--r--crates/ide_assists/src/handlers/replace_qualified_name_with_use.rs2
-rw-r--r--crates/ide_assists/src/handlers/replace_string_with_char.rs2
-rw-r--r--crates/ide_assists/src/handlers/replace_unwrap_with_match.rs2
-rw-r--r--crates/ide_assists/src/tests/generated.rs1
-rw-r--r--crates/ide_assists/src/utils.rs48
-rw-r--r--crates/ide_completion/src/completions/keyword.rs22
-rw-r--r--crates/ide_db/src/helpers/merge_imports.rs38
-rw-r--r--crates/project_model/src/build_data.rs1
-rw-r--r--crates/rust-analyzer/src/config.rs10
-rw-r--r--crates/rust-analyzer/src/handlers.rs13
-rw-r--r--crates/rust-analyzer/src/integrated_benchmarks.rs4
-rw-r--r--crates/rust-analyzer/src/semantic_tokens.rs5
-rw-r--r--crates/rust-analyzer/src/to_proto.rs11
-rw-r--r--crates/rust-analyzer/tests/slow-tests/main.rs (renamed from crates/rust-analyzer/tests/rust-analyzer/main.rs)0
-rw-r--r--crates/rust-analyzer/tests/slow-tests/support.rs (renamed from crates/rust-analyzer/tests/rust-analyzer/support.rs)0
-rw-r--r--crates/rust-analyzer/tests/slow-tests/testdir.rs (renamed from crates/rust-analyzer/tests/rust-analyzer/testdir.rs)0
-rw-r--r--crates/syntax/Cargo.toml2
-rw-r--r--crates/syntax/src/algo.rs133
-rw-r--r--crates/syntax/src/ast.rs6
-rw-r--r--crates/syntax/src/ast/edit.rs493
-rw-r--r--crates/syntax/src/ast/edit_in_place.rs182
-rw-r--r--crates/syntax/src/ast/make.rs12
-rw-r--r--crates/syntax/src/ast/token_ext.rs81
-rw-r--r--crates/syntax/src/parsing.rs7
-rw-r--r--crates/syntax/src/parsing/text_tree_sink.rs4
-rw-r--r--crates/syntax/test_data/parser/ok/0045_block_attrs.rast6
-rw-r--r--docs/dev/style.md38
-rw-r--r--docs/user/generated_config.adoc9
-rw-r--r--docs/user/manual.adoc1
-rw-r--r--editors/code/package.json5
-rw-r--r--xtask/src/tidy.rs5
97 files changed, 2870 insertions, 2134 deletions
diff --git a/Cargo.lock b/Cargo.lock
index f9c34547e..76a26ea4e 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -437,18 +437,18 @@ dependencies = [
437 437
438[[package]] 438[[package]]
439name = "fsevent-sys" 439name = "fsevent-sys"
440version = "3.0.2" 440version = "3.1.0"
441source = "registry+https://github.com/rust-lang/crates.io-index" 441source = "registry+https://github.com/rust-lang/crates.io-index"
442checksum = "77a29c77f1ca394c3e73a9a5d24cfcabb734682d9634fc398f2204a63c994120" 442checksum = "ca6f5e6817058771c10f0eb0f05ddf1e35844266f972004fe8e4b21fda295bd5"
443dependencies = [ 443dependencies = [
444 "libc", 444 "libc",
445] 445]
446 446
447[[package]] 447[[package]]
448name = "fst" 448name = "fst"
449version = "0.4.5" 449version = "0.4.6"
450source = "registry+https://github.com/rust-lang/crates.io-index" 450source = "registry+https://github.com/rust-lang/crates.io-index"
451checksum = "d79238883cf0307100b90aba4a755d8051a3182305dfe7f649a1e9dc0517006f" 451checksum = "e398fae362f4124bbe630d99519fb2d68a03e2e3a23b441028cdcdc4f4895687"
452 452
453[[package]] 453[[package]]
454name = "gimli" 454name = "gimli"
@@ -939,11 +939,10 @@ dependencies = [
939 939
940[[package]] 940[[package]]
941name = "notify" 941name = "notify"
942version = "5.0.0-pre.7" 942version = "5.0.0-pre.8"
943source = "registry+https://github.com/rust-lang/crates.io-index" 943source = "registry+https://github.com/rust-lang/crates.io-index"
944checksum = "1ebe7699a0f8c5759450716ee03d231685c22b4fe8f406c42c22e0ad94d40ce7" 944checksum = "46bbbcd078f1f00ddb7a9abe70b96e91229b44b0b3afdec610f8e5137f8f014b"
945dependencies = [ 945dependencies = [
946 "anymap",
947 "bitflags", 946 "bitflags",
948 "crossbeam-channel", 947 "crossbeam-channel",
949 "filetime", 948 "filetime",
@@ -1295,9 +1294,9 @@ checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
1295 1294
1296[[package]] 1295[[package]]
1297name = "rowan" 1296name = "rowan"
1298version = "0.13.0-pre.5" 1297version = "0.13.0-pre.6"
1299source = "registry+https://github.com/rust-lang/crates.io-index" 1298source = "registry+https://github.com/rust-lang/crates.io-index"
1300checksum = "32a5fc82ed0b7e7fba157331f0d8f64abd73bced6e7ac2a4dfa0c4cf0ab584e8" 1299checksum = "82ccc04e145e9a5ab51b9c12a81d77c4a8250d87a407ab02ac650451141ff00d"
1301dependencies = [ 1300dependencies = [
1302 "countme", 1301 "countme",
1303 "hashbrown", 1302 "hashbrown",
diff --git a/crates/flycheck/src/lib.rs b/crates/flycheck/src/lib.rs
index 1682d8bde..93cf6a3d6 100644
--- a/crates/flycheck/src/lib.rs
+++ b/crates/flycheck/src/lib.rs
@@ -215,6 +215,7 @@ impl FlycheckActor {
215 } => { 215 } => {
216 let mut cmd = Command::new(toolchain::cargo()); 216 let mut cmd = Command::new(toolchain::cargo());
217 cmd.arg(command); 217 cmd.arg(command);
218 cmd.current_dir(&self.workspace_root);
218 cmd.args(&["--workspace", "--message-format=json", "--manifest-path"]) 219 cmd.args(&["--workspace", "--message-format=json", "--manifest-path"])
219 .arg(self.workspace_root.join("Cargo.toml")); 220 .arg(self.workspace_root.join("Cargo.toml"));
220 221
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index c9ef4b420..d443b124c 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -52,7 +52,9 @@ use hir_def::{
52}; 52};
53use hir_expand::{diagnostics::DiagnosticSink, name::name, MacroDefKind}; 53use hir_expand::{diagnostics::DiagnosticSink, name::name, MacroDefKind};
54use hir_ty::{ 54use hir_ty::{
55 autoderef, could_unify, 55 autoderef,
56 consteval::ConstExt,
57 could_unify,
56 method_resolution::{self, def_crates, TyFingerprint}, 58 method_resolution::{self, def_crates, TyFingerprint},
57 primitive::UintTy, 59 primitive::UintTy,
58 subst_prefix, 60 subst_prefix,
@@ -873,6 +875,10 @@ impl Function {
873 db.function_data(self.id).is_unsafe() 875 db.function_data(self.id).is_unsafe()
874 } 876 }
875 877
878 pub fn is_async(self, db: &dyn HirDatabase) -> bool {
879 db.function_data(self.id).is_async()
880 }
881
876 pub fn diagnostics(self, db: &dyn HirDatabase, sink: &mut DiagnosticSink) { 882 pub fn diagnostics(self, db: &dyn HirDatabase, sink: &mut DiagnosticSink) {
877 let krate = self.module(db).id.krate(); 883 let krate = self.module(db).id.krate();
878 hir_def::diagnostics::validate_body(db.upcast(), self.id.into(), sink); 884 hir_def::diagnostics::validate_body(db.upcast(), self.id.into(), sink);
@@ -1910,6 +1916,7 @@ impl Type {
1910 substs.iter(&Interner).filter_map(|a| a.ty(&Interner)).any(go) 1916 substs.iter(&Interner).filter_map(|a| a.ty(&Interner)).any(go)
1911 } 1917 }
1912 1918
1919 TyKind::Array(_ty, len) if len.is_unknown() => true,
1913 TyKind::Array(ty, _) 1920 TyKind::Array(ty, _)
1914 | TyKind::Slice(ty) 1921 | TyKind::Slice(ty)
1915 | TyKind::Raw(_, ty) 1922 | TyKind::Raw(_, ty)
diff --git a/crates/hir_def/src/body.rs b/crates/hir_def/src/body.rs
index 8360426f1..98b485b60 100644
--- a/crates/hir_def/src/body.rs
+++ b/crates/hir_def/src/body.rs
@@ -21,8 +21,6 @@ use profile::Count;
21use rustc_hash::FxHashMap; 21use rustc_hash::FxHashMap;
22use syntax::{ast, AstNode, AstPtr}; 22use syntax::{ast, AstNode, AstPtr};
23 23
24pub use lower::LowerCtx;
25
26use crate::{ 24use crate::{
27 attr::{Attrs, RawAttrs}, 25 attr::{Attrs, RawAttrs},
28 db::DefDatabase, 26 db::DefDatabase,
@@ -35,6 +33,8 @@ use crate::{
35 UnresolvedMacro, 33 UnresolvedMacro,
36}; 34};
37 35
36pub use lower::LowerCtx;
37
38/// A subset of Expander that only deals with cfg attributes. We only need it to 38/// A subset of Expander that only deals with cfg attributes. We only need it to
39/// avoid cyclic queries in crate def map during enum processing. 39/// avoid cyclic queries in crate def map during enum processing.
40#[derive(Debug)] 40#[derive(Debug)]
diff --git a/crates/hir_def/src/body/lower.rs b/crates/hir_def/src/body/lower.rs
index 9f278d35b..2a7e0205f 100644
--- a/crates/hir_def/src/body/lower.rs
+++ b/crates/hir_def/src/body/lower.rs
@@ -1006,23 +1006,27 @@ impl From<ast::BinOp> for BinaryOp {
1006impl From<ast::LiteralKind> for Literal { 1006impl From<ast::LiteralKind> for Literal {
1007 fn from(ast_lit_kind: ast::LiteralKind) -> Self { 1007 fn from(ast_lit_kind: ast::LiteralKind) -> Self {
1008 match ast_lit_kind { 1008 match ast_lit_kind {
1009 // FIXME: these should have actual values filled in, but unsure on perf impact
1009 LiteralKind::IntNumber(lit) => { 1010 LiteralKind::IntNumber(lit) => {
1010 if let builtin @ Some(_) = lit.suffix().and_then(BuiltinFloat::from_suffix) { 1011 if let builtin @ Some(_) = lit.suffix().and_then(BuiltinFloat::from_suffix) {
1011 return Literal::Float(Default::default(), builtin); 1012 return Literal::Float(Default::default(), builtin);
1012 } else if let builtin @ Some(_) = 1013 } else if let builtin @ Some(_) =
1013 lit.suffix().and_then(|it| BuiltinInt::from_suffix(&it)) 1014 lit.suffix().and_then(|it| BuiltinInt::from_suffix(&it))
1014 { 1015 {
1015 Literal::Int(Default::default(), builtin) 1016 Literal::Int(lit.value().unwrap_or(0) as i128, builtin)
1016 } else { 1017 } else {
1017 let builtin = lit.suffix().and_then(|it| BuiltinUint::from_suffix(&it)); 1018 let builtin = lit.suffix().and_then(|it| BuiltinUint::from_suffix(&it));
1018 Literal::Uint(Default::default(), builtin) 1019 Literal::Uint(lit.value().unwrap_or(0), builtin)
1019 } 1020 }
1020 } 1021 }
1021 LiteralKind::FloatNumber(lit) => { 1022 LiteralKind::FloatNumber(lit) => {
1022 let ty = lit.suffix().and_then(|it| BuiltinFloat::from_suffix(&it)); 1023 let ty = lit.suffix().and_then(|it| BuiltinFloat::from_suffix(&it));
1023 Literal::Float(Default::default(), ty) 1024 Literal::Float(Default::default(), ty)
1024 } 1025 }
1025 LiteralKind::ByteString(_) => Literal::ByteString(Default::default()), 1026 LiteralKind::ByteString(bs) => {
1027 let text = bs.value().map(Vec::from).unwrap_or_else(Default::default);
1028 Literal::ByteString(text)
1029 }
1026 LiteralKind::String(_) => Literal::String(Default::default()), 1030 LiteralKind::String(_) => Literal::String(Default::default()),
1027 LiteralKind::Byte => Literal::Uint(Default::default(), Some(BuiltinUint::U8)), 1031 LiteralKind::Byte => Literal::Uint(Default::default(), Some(BuiltinUint::U8)),
1028 LiteralKind::Bool(val) => Literal::Bool(val), 1032 LiteralKind::Bool(val) => Literal::Bool(val),
diff --git a/crates/hir_def/src/expr.rs b/crates/hir_def/src/expr.rs
index 0c3b41080..2ba619d23 100644
--- a/crates/hir_def/src/expr.rs
+++ b/crates/hir_def/src/expr.rs
@@ -43,8 +43,8 @@ pub enum Literal {
43 ByteString(Vec<u8>), 43 ByteString(Vec<u8>),
44 Char(char), 44 Char(char),
45 Bool(bool), 45 Bool(bool),
46 Int(u64, Option<BuiltinInt>), 46 Int(i128, Option<BuiltinInt>),
47 Uint(u64, Option<BuiltinUint>), 47 Uint(u128, Option<BuiltinUint>),
48 Float(u64, Option<BuiltinFloat>), // FIXME: f64 is not Eq 48 Float(u64, Option<BuiltinFloat>), // FIXME: f64 is not Eq
49} 49}
50 50
diff --git a/crates/hir_def/src/find_path.rs b/crates/hir_def/src/find_path.rs
index 858e88038..ee52794aa 100644
--- a/crates/hir_def/src/find_path.rs
+++ b/crates/hir_def/src/find_path.rs
@@ -5,10 +5,10 @@ use std::iter;
5use hir_expand::name::{known, AsName, Name}; 5use hir_expand::name::{known, AsName, Name};
6use rustc_hash::FxHashSet; 6use rustc_hash::FxHashSet;
7 7
8use crate::nameres::DefMap;
9use crate::{ 8use crate::{
10 db::DefDatabase, 9 db::DefDatabase,
11 item_scope::ItemInNs, 10 item_scope::ItemInNs,
11 nameres::DefMap,
12 path::{ModPath, PathKind}, 12 path::{ModPath, PathKind},
13 visibility::Visibility, 13 visibility::Visibility,
14 ModuleDefId, ModuleId, 14 ModuleDefId, ModuleId,
@@ -134,7 +134,16 @@ fn find_path_inner(
134 for (name, def_id) in root_def_map.extern_prelude() { 134 for (name, def_id) in root_def_map.extern_prelude() {
135 if item == ItemInNs::Types(*def_id) { 135 if item == ItemInNs::Types(*def_id) {
136 let name = scope_name.unwrap_or_else(|| name.clone()); 136 let name = scope_name.unwrap_or_else(|| name.clone());
137 return Some(ModPath::from_segments(PathKind::Plain, vec![name])); 137
138 let name_already_occupied_in_type_ns = def_map
139 .with_ancestor_maps(db, from.local_id, &mut |def_map, local_id| {
140 def_map[local_id].scope.get(&name).take_types().filter(|&id| id != *def_id)
141 })
142 .is_some();
143 return Some(ModPath::from_segments(
144 if name_already_occupied_in_type_ns { PathKind::Abs } else { PathKind::Plain },
145 vec![name],
146 ));
138 } 147 }
139 } 148 }
140 149
diff --git a/crates/hir_def/src/nameres/collector.rs b/crates/hir_def/src/nameres/collector.rs
index a68674c10..d4840be2f 100644
--- a/crates/hir_def/src/nameres/collector.rs
+++ b/crates/hir_def/src/nameres/collector.rs
@@ -481,6 +481,11 @@ impl DefCollector<'_> {
481 let res = self.def_map.resolve_name_in_extern_prelude(self.db, &extern_crate.name); 481 let res = self.def_map.resolve_name_in_extern_prelude(self.db, &extern_crate.name);
482 482
483 if let Some(ModuleDefId::ModuleId(m)) = res.take_types() { 483 if let Some(ModuleDefId::ModuleId(m)) = res.take_types() {
484 if m == self.def_map.module_id(current_module_id) {
485 cov_mark::hit!(ignore_macro_use_extern_crate_self);
486 return;
487 }
488
484 cov_mark::hit!(macro_rules_from_other_crates_are_visible_with_macro_use); 489 cov_mark::hit!(macro_rules_from_other_crates_are_visible_with_macro_use);
485 self.import_all_macros_exported(current_module_id, m.krate); 490 self.import_all_macros_exported(current_module_id, m.krate);
486 } 491 }
diff --git a/crates/hir_def/src/nameres/tests.rs b/crates/hir_def/src/nameres/tests.rs
index 4f2e7a2f9..9f652731d 100644
--- a/crates/hir_def/src/nameres/tests.rs
+++ b/crates/hir_def/src/nameres/tests.rs
@@ -411,6 +411,22 @@ struct Arc;
411} 411}
412 412
413#[test] 413#[test]
414fn macro_use_extern_crate_self() {
415 cov_mark::check!(ignore_macro_use_extern_crate_self);
416 check(
417 r#"
418//- /main.rs crate:main
419#[macro_use]
420extern crate self as bla;
421"#,
422 expect![[r#"
423 crate
424 bla: t
425 "#]],
426 );
427}
428
429#[test]
414fn reexport_across_crates() { 430fn reexport_across_crates() {
415 check( 431 check(
416 r#" 432 r#"
diff --git a/crates/hir_def/src/type_ref.rs b/crates/hir_def/src/type_ref.rs
index ea29da5da..9e44547cb 100644
--- a/crates/hir_def/src/type_ref.rs
+++ b/crates/hir_def/src/type_ref.rs
@@ -2,6 +2,7 @@
2//! be directly created from an ast::TypeRef, without further queries. 2//! be directly created from an ast::TypeRef, without further queries.
3 3
4use hir_expand::{name::Name, AstId, InFile}; 4use hir_expand::{name::Name, AstId, InFile};
5use std::convert::TryInto;
5use syntax::ast; 6use syntax::ast;
6 7
7use crate::{body::LowerCtx, path::Path}; 8use crate::{body::LowerCtx, path::Path};
@@ -79,7 +80,9 @@ pub enum TypeRef {
79 Path(Path), 80 Path(Path),
80 RawPtr(Box<TypeRef>, Mutability), 81 RawPtr(Box<TypeRef>, Mutability),
81 Reference(Box<TypeRef>, Option<LifetimeRef>, Mutability), 82 Reference(Box<TypeRef>, Option<LifetimeRef>, Mutability),
82 Array(Box<TypeRef> /*, Expr*/), 83 // FIXME: for full const generics, the latter element (length) here is going to have to be an
84 // expression that is further lowered later in hir_ty.
85 Array(Box<TypeRef>, ConstScalar),
83 Slice(Box<TypeRef>), 86 Slice(Box<TypeRef>),
84 /// A fn pointer. Last element of the vector is the return type. 87 /// A fn pointer. Last element of the vector is the return type.
85 Fn(Vec<TypeRef>, bool /*varargs*/), 88 Fn(Vec<TypeRef>, bool /*varargs*/),
@@ -140,7 +143,16 @@ impl TypeRef {
140 TypeRef::RawPtr(Box::new(inner_ty), mutability) 143 TypeRef::RawPtr(Box::new(inner_ty), mutability)
141 } 144 }
142 ast::Type::ArrayType(inner) => { 145 ast::Type::ArrayType(inner) => {
143 TypeRef::Array(Box::new(TypeRef::from_ast_opt(&ctx, inner.ty()))) 146 // FIXME: This is a hack. We should probably reuse the machinery of
147 // `hir_def::body::lower` to lower this into an `Expr` and then evaluate it at the
148 // `hir_ty` level, which would allow knowing the type of:
149 // let v: [u8; 2 + 2] = [0u8; 4];
150 let len = inner
151 .expr()
152 .map(ConstScalar::usize_from_literal_expr)
153 .unwrap_or(ConstScalar::Unknown);
154
155 TypeRef::Array(Box::new(TypeRef::from_ast_opt(&ctx, inner.ty())), len)
144 } 156 }
145 ast::Type::SliceType(inner) => { 157 ast::Type::SliceType(inner) => {
146 TypeRef::Slice(Box::new(TypeRef::from_ast_opt(&ctx, inner.ty()))) 158 TypeRef::Slice(Box::new(TypeRef::from_ast_opt(&ctx, inner.ty())))
@@ -212,7 +224,7 @@ impl TypeRef {
212 } 224 }
213 TypeRef::RawPtr(type_ref, _) 225 TypeRef::RawPtr(type_ref, _)
214 | TypeRef::Reference(type_ref, ..) 226 | TypeRef::Reference(type_ref, ..)
215 | TypeRef::Array(type_ref) 227 | TypeRef::Array(type_ref, _)
216 | TypeRef::Slice(type_ref) => go(&type_ref, f), 228 | TypeRef::Slice(type_ref) => go(&type_ref, f),
217 TypeRef::ImplTrait(bounds) | TypeRef::DynTrait(bounds) => { 229 TypeRef::ImplTrait(bounds) | TypeRef::DynTrait(bounds) => {
218 for bound in bounds { 230 for bound in bounds {
@@ -298,3 +310,58 @@ impl TypeBound {
298 } 310 }
299 } 311 }
300} 312}
313
314/// A concrete constant value
315#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
316pub enum ConstScalar {
317 // for now, we only support the trivial case of constant evaluating the length of an array
318 // Note that this is u64 because the target usize may be bigger than our usize
319 Usize(u64),
320
321 /// Case of an unknown value that rustc might know but we don't
322 // FIXME: this is a hack to get around chalk not being able to represent unevaluatable
323 // constants
324 // https://github.com/rust-analyzer/rust-analyzer/pull/8813#issuecomment-840679177
325 // https://rust-lang.zulipchat.com/#narrow/stream/144729-wg-traits/topic/Handling.20non.20evaluatable.20constants'.20equality/near/238386348
326 Unknown,
327}
328
329impl std::fmt::Display for ConstScalar {
330 fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
331 match self {
332 ConstScalar::Usize(us) => write!(fmt, "{}", us),
333 ConstScalar::Unknown => write!(fmt, "_"),
334 }
335 }
336}
337
338impl ConstScalar {
339 /// Gets a target usize out of the ConstScalar
340 pub fn as_usize(&self) -> Option<u64> {
341 match self {
342 &ConstScalar::Usize(us) => Some(us),
343 _ => None,
344 }
345 }
346
347 // FIXME: as per the comments on `TypeRef::Array`, this evaluation should not happen at this
348 // parse stage.
349 fn usize_from_literal_expr(expr: ast::Expr) -> ConstScalar {
350 match expr {
351 ast::Expr::Literal(lit) => {
352 let lkind = lit.kind();
353 match lkind {
354 ast::LiteralKind::IntNumber(num)
355 if num.suffix() == None || num.suffix() == Some("usize") =>
356 {
357 num.value().and_then(|v| v.try_into().ok())
358 }
359 _ => None,
360 }
361 }
362 _ => None,
363 }
364 .map(ConstScalar::Usize)
365 .unwrap_or(ConstScalar::Unknown)
366 }
367}
diff --git a/crates/hir_expand/src/builtin_macro.rs b/crates/hir_expand/src/builtin_macro.rs
index af9802144..280c25f11 100644
--- a/crates/hir_expand/src/builtin_macro.rs
+++ b/crates/hir_expand/src/builtin_macro.rs
@@ -118,6 +118,7 @@ register_builtin! {
118 EAGER: 118 EAGER:
119 (compile_error, CompileError) => compile_error_expand, 119 (compile_error, CompileError) => compile_error_expand,
120 (concat, Concat) => concat_expand, 120 (concat, Concat) => concat_expand,
121 (concat_idents, ConcatIdents) => concat_idents_expand,
121 (include, Include) => include_expand, 122 (include, Include) => include_expand,
122 (include_bytes, IncludeBytes) => include_bytes_expand, 123 (include_bytes, IncludeBytes) => include_bytes_expand,
123 (include_str, IncludeStr) => include_str_expand, 124 (include_str, IncludeStr) => include_str_expand,
@@ -373,6 +374,28 @@ fn concat_expand(
373 ExpandResult { value: Some(ExpandedEager::new(quote!(#text), FragmentKind::Expr)), err } 374 ExpandResult { value: Some(ExpandedEager::new(quote!(#text), FragmentKind::Expr)), err }
374} 375}
375 376
377fn concat_idents_expand(
378 _db: &dyn AstDatabase,
379 _arg_id: EagerMacroId,
380 tt: &tt::Subtree,
381) -> ExpandResult<Option<ExpandedEager>> {
382 let mut err = None;
383 let mut ident = String::new();
384 for (i, t) in tt.token_trees.iter().enumerate() {
385 match t {
386 tt::TokenTree::Leaf(tt::Leaf::Ident(id)) => {
387 ident.push_str(id.text.as_str());
388 }
389 tt::TokenTree::Leaf(tt::Leaf::Punct(punct)) if i % 2 == 1 && punct.char == ',' => (),
390 _ => {
391 err.get_or_insert(mbe::ExpandError::UnexpectedToken);
392 }
393 }
394 }
395 let ident = tt::Ident { text: ident.into(), id: tt::TokenId::unspecified() };
396 ExpandResult { value: Some(ExpandedEager::new(quote!(#ident), FragmentKind::Expr)), err }
397}
398
376fn relative_file( 399fn relative_file(
377 db: &dyn AstDatabase, 400 db: &dyn AstDatabase,
378 call_id: MacroCallId, 401 call_id: MacroCallId,
@@ -794,4 +817,16 @@ mod tests {
794 expect![[r#""foor0bar\nfalse""#]], 817 expect![[r#""foor0bar\nfalse""#]],
795 ); 818 );
796 } 819 }
820
821 #[test]
822 fn test_concat_idents_expand() {
823 check_expansion(
824 r##"
825 #[rustc_builtin_macro]
826 macro_rules! concat_idents {}
827 concat_idents!(foo, bar);
828 "##,
829 expect![[r#"foobar"#]],
830 );
831 }
797} 832}
diff --git a/crates/hir_expand/src/name.rs b/crates/hir_expand/src/name.rs
index bcfd3e524..5a5dc9afd 100644
--- a/crates/hir_expand/src/name.rs
+++ b/crates/hir_expand/src/name.rs
@@ -212,6 +212,7 @@ pub mod known {
212 std_panic, 212 std_panic,
213 stringify, 213 stringify,
214 concat, 214 concat,
215 concat_idents,
215 include, 216 include,
216 include_bytes, 217 include_bytes,
217 include_str, 218 include_str,
diff --git a/crates/hir_ty/src/consteval.rs b/crates/hir_ty/src/consteval.rs
new file mode 100644
index 000000000..e3ceb3d62
--- /dev/null
+++ b/crates/hir_ty/src/consteval.rs
@@ -0,0 +1,56 @@
1//! Constant evaluation details
2
3use std::convert::TryInto;
4
5use hir_def::{
6 builtin_type::BuiltinUint,
7 expr::{Expr, Literal},
8 type_ref::ConstScalar,
9};
10
11use crate::{Const, ConstData, ConstValue, Interner, TyKind};
12
13/// Extension trait for [`Const`]
14pub trait ConstExt {
15 /// Is a [`Const`] unknown?
16 fn is_unknown(&self) -> bool;
17}
18
19impl ConstExt for Const {
20 fn is_unknown(&self) -> bool {
21 match self.data(&Interner).value {
22 // interned Unknown
23 chalk_ir::ConstValue::Concrete(chalk_ir::ConcreteConst {
24 interned: ConstScalar::Unknown,
25 }) => true,
26
27 // interned concrete anything else
28 chalk_ir::ConstValue::Concrete(..) => false,
29
30 _ => {
31 log::error!("is_unknown was called on a non-concrete constant value! {:?}", self);
32 true
33 }
34 }
35 }
36}
37
38// FIXME: support more than just evaluating literals
39pub fn eval_usize(expr: &Expr) -> Option<u64> {
40 match expr {
41 Expr::Literal(Literal::Uint(v, None))
42 | Expr::Literal(Literal::Uint(v, Some(BuiltinUint::Usize))) => (*v).try_into().ok(),
43 _ => None,
44 }
45}
46
47/// Interns a possibly-unknown target usize
48pub fn usize_const(value: Option<u64>) -> Const {
49 ConstData {
50 ty: TyKind::Scalar(chalk_ir::Scalar::Uint(chalk_ir::UintTy::Usize)).intern(&Interner),
51 value: ConstValue::Concrete(chalk_ir::ConcreteConst {
52 interned: value.map(|value| ConstScalar::Usize(value)).unwrap_or(ConstScalar::Unknown),
53 }),
54 }
55 .intern(&Interner)
56}
diff --git a/crates/hir_ty/src/consts.rs b/crates/hir_ty/src/consts.rs
deleted file mode 100644
index 0044b1cff..000000000
--- a/crates/hir_ty/src/consts.rs
+++ /dev/null
@@ -1,21 +0,0 @@
1//! Handling of concrete const values
2
3/// A concrete constant value
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
5pub enum ConstScalar {
6 // for now, we only support the trivial case of constant evaluating the length of an array
7 // Note that this is u64 because the target usize may be bigger than our usize
8 Usize(u64),
9
10 /// Case of an unknown value that rustc might know but we don't
11 Unknown,
12}
13
14impl std::fmt::Display for ConstScalar {
15 fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
16 match self {
17 ConstScalar::Usize(us) => write!(fmt, "{}", us),
18 ConstScalar::Unknown => write!(fmt, "_"),
19 }
20 }
21}
diff --git a/crates/hir_ty/src/diagnostics/match_check.rs b/crates/hir_ty/src/diagnostics/match_check.rs
index e9762622f..6ee0529c6 100644
--- a/crates/hir_ty/src/diagnostics/match_check.rs
+++ b/crates/hir_ty/src/diagnostics/match_check.rs
@@ -1119,6 +1119,7 @@ fn main() {
1119 (true, false, true) => (), 1119 (true, false, true) => (),
1120 (true) => (), 1120 (true) => (),
1121 } 1121 }
1122 match (true, false) { (true,) => {} }
1122 match (0) { () => () } 1123 match (0) { () => () }
1123 match Unresolved::Bar { Unresolved::Baz => () } 1124 match Unresolved::Bar { Unresolved::Baz => () }
1124} 1125}
diff --git a/crates/hir_ty/src/display.rs b/crates/hir_ty/src/display.rs
index 8a4296697..7bbd1a1f7 100644
--- a/crates/hir_ty/src/display.rs
+++ b/crates/hir_ty/src/display.rs
@@ -962,11 +962,10 @@ impl HirDisplay for TypeRef {
962 write!(f, "{}", mutability)?; 962 write!(f, "{}", mutability)?;
963 inner.hir_fmt(f)?; 963 inner.hir_fmt(f)?;
964 } 964 }
965 TypeRef::Array(inner) => { 965 TypeRef::Array(inner, len) => {
966 write!(f, "[")?; 966 write!(f, "[")?;
967 inner.hir_fmt(f)?; 967 inner.hir_fmt(f)?;
968 // FIXME: Array length? 968 write!(f, "; {}]", len)?;
969 write!(f, "; _]")?;
970 } 969 }
971 TypeRef::Slice(inner) => { 970 TypeRef::Slice(inner) => {
972 write!(f, "[")?; 971 write!(f, "[")?;
diff --git a/crates/hir_ty/src/infer/expr.rs b/crates/hir_ty/src/infer/expr.rs
index 2178ffd07..b6b5a1b75 100644
--- a/crates/hir_ty/src/infer/expr.rs
+++ b/crates/hir_ty/src/infer/expr.rs
@@ -3,7 +3,7 @@
3use std::iter::{repeat, repeat_with}; 3use std::iter::{repeat, repeat_with};
4use std::{mem, sync::Arc}; 4use std::{mem, sync::Arc};
5 5
6use chalk_ir::{cast::Cast, fold::Shift, ConstData, Mutability, TyVariableKind}; 6use chalk_ir::{cast::Cast, fold::Shift, Mutability, TyVariableKind};
7use hir_def::{ 7use hir_def::{
8 expr::{Array, BinaryOp, Expr, ExprId, Literal, Statement, UnaryOp}, 8 expr::{Array, BinaryOp, Expr, ExprId, Literal, Statement, UnaryOp},
9 path::{GenericArg, GenericArgs}, 9 path::{GenericArg, GenericArgs},
@@ -15,9 +15,7 @@ use stdx::always;
15use syntax::ast::RangeOp; 15use syntax::ast::RangeOp;
16 16
17use crate::{ 17use crate::{
18 autoderef, 18 autoderef, consteval,
19 consts::ConstScalar,
20 dummy_usize_const,
21 lower::lower_to_chalk_mutability, 19 lower::lower_to_chalk_mutability,
22 mapping::from_chalk, 20 mapping::from_chalk,
23 method_resolution, op, 21 method_resolution, op,
@@ -25,7 +23,7 @@ use crate::{
25 static_lifetime, to_chalk_trait_id, 23 static_lifetime, to_chalk_trait_id,
26 traits::FnTrait, 24 traits::FnTrait,
27 utils::{generics, Generics}, 25 utils::{generics, Generics},
28 AdtId, Binders, CallableDefId, ConstValue, FnPointer, FnSig, FnSubst, InEnvironment, Interner, 26 AdtId, Binders, CallableDefId, FnPointer, FnSig, FnSubst, InEnvironment, Interner,
29 ProjectionTyExt, Rawness, Scalar, Substitution, TraitRef, Ty, TyBuilder, TyExt, TyKind, 27 ProjectionTyExt, Rawness, Scalar, Substitution, TraitRef, Ty, TyBuilder, TyExt, TyKind,
30}; 28};
31 29
@@ -724,7 +722,7 @@ impl<'a> InferenceContext<'a> {
724 for expr in items.iter() { 722 for expr in items.iter() {
725 self.infer_expr_coerce(*expr, &Expectation::has_type(elem_ty.clone())); 723 self.infer_expr_coerce(*expr, &Expectation::has_type(elem_ty.clone()));
726 } 724 }
727 Some(items.len()) 725 Some(items.len() as u64)
728 } 726 }
729 Array::Repeat { initializer, repeat } => { 727 Array::Repeat { initializer, repeat } => {
730 self.infer_expr_coerce( 728 self.infer_expr_coerce(
@@ -737,20 +735,13 @@ impl<'a> InferenceContext<'a> {
737 TyKind::Scalar(Scalar::Uint(UintTy::Usize)).intern(&Interner), 735 TyKind::Scalar(Scalar::Uint(UintTy::Usize)).intern(&Interner),
738 ), 736 ),
739 ); 737 );
740 // FIXME: support length for Repeat array expressions 738
741 None 739 let repeat_expr = &self.body.exprs[*repeat];
740 consteval::eval_usize(repeat_expr)
742 } 741 }
743 }; 742 };
744 743
745 let cd = ConstData { 744 TyKind::Array(elem_ty, consteval::usize_const(len)).intern(&Interner)
746 ty: TyKind::Scalar(Scalar::Uint(UintTy::Usize)).intern(&Interner),
747 value: ConstValue::Concrete(chalk_ir::ConcreteConst {
748 interned: len
749 .map(|len| ConstScalar::Usize(len as u64))
750 .unwrap_or(ConstScalar::Unknown),
751 }),
752 };
753 TyKind::Array(elem_ty, cd.intern(&Interner)).intern(&Interner)
754 } 745 }
755 Expr::Literal(lit) => match lit { 746 Expr::Literal(lit) => match lit {
756 Literal::Bool(..) => TyKind::Scalar(Scalar::Bool).intern(&Interner), 747 Literal::Bool(..) => TyKind::Scalar(Scalar::Bool).intern(&Interner),
@@ -758,11 +749,12 @@ impl<'a> InferenceContext<'a> {
758 TyKind::Ref(Mutability::Not, static_lifetime(), TyKind::Str.intern(&Interner)) 749 TyKind::Ref(Mutability::Not, static_lifetime(), TyKind::Str.intern(&Interner))
759 .intern(&Interner) 750 .intern(&Interner)
760 } 751 }
761 Literal::ByteString(..) => { 752 Literal::ByteString(bs) => {
762 let byte_type = TyKind::Scalar(Scalar::Uint(UintTy::U8)).intern(&Interner); 753 let byte_type = TyKind::Scalar(Scalar::Uint(UintTy::U8)).intern(&Interner);
763 754
764 let array_type = 755 let len = consteval::usize_const(Some(bs.len() as u64));
765 TyKind::Array(byte_type, dummy_usize_const()).intern(&Interner); 756
757 let array_type = TyKind::Array(byte_type, len).intern(&Interner);
766 TyKind::Ref(Mutability::Not, static_lifetime(), array_type).intern(&Interner) 758 TyKind::Ref(Mutability::Not, static_lifetime(), array_type).intern(&Interner)
767 } 759 }
768 Literal::Char(..) => TyKind::Scalar(Scalar::Char).intern(&Interner), 760 Literal::Char(..) => TyKind::Scalar(Scalar::Char).intern(&Interner),
diff --git a/crates/hir_ty/src/infer/pat.rs b/crates/hir_ty/src/infer/pat.rs
index aea354cde..60b94a642 100644
--- a/crates/hir_ty/src/infer/pat.rs
+++ b/crates/hir_ty/src/infer/pat.rs
@@ -126,11 +126,12 @@ impl<'a> InferenceContext<'a> {
126 _ => &[], 126 _ => &[],
127 }; 127 };
128 128
129 let (pre, post) = match ellipsis { 129 let ((pre, post), n_uncovered_patterns) = match ellipsis {
130 Some(idx) => args.split_at(idx), 130 Some(idx) => {
131 None => (&args[..], &[][..]), 131 (args.split_at(idx), expectations.len().saturating_sub(args.len()))
132 }
133 None => ((&args[..], &[][..]), 0),
132 }; 134 };
133 let n_uncovered_patterns = expectations.len().saturating_sub(args.len());
134 let err_ty = self.err_ty(); 135 let err_ty = self.err_ty();
135 let mut expectations_iter = 136 let mut expectations_iter =
136 expectations.iter().map(|a| a.assert_ty_ref(&Interner)).chain(repeat(&err_ty)); 137 expectations.iter().map(|a| a.assert_ty_ref(&Interner)).chain(repeat(&err_ty));
diff --git a/crates/hir_ty/src/interner.rs b/crates/hir_ty/src/interner.rs
index 4cbc9cd4f..7b4119747 100644
--- a/crates/hir_ty/src/interner.rs
+++ b/crates/hir_ty/src/interner.rs
@@ -1,11 +1,12 @@
1//! Implementation of the Chalk `Interner` trait, which allows customizing the 1//! Implementation of the Chalk `Interner` trait, which allows customizing the
2//! representation of the various objects Chalk deals with (types, goals etc.). 2//! representation of the various objects Chalk deals with (types, goals etc.).
3 3
4use crate::{chalk_db, consts::ConstScalar, tls, GenericArg}; 4use crate::{chalk_db, tls, GenericArg};
5use base_db::salsa::InternId; 5use base_db::salsa::InternId;
6use chalk_ir::{Goal, GoalData}; 6use chalk_ir::{Goal, GoalData};
7use hir_def::{ 7use hir_def::{
8 intern::{impl_internable, InternStorage, Internable, Interned}, 8 intern::{impl_internable, InternStorage, Internable, Interned},
9 type_ref::ConstScalar,
9 TypeAliasId, 10 TypeAliasId,
10}; 11};
11use smallvec::SmallVec; 12use smallvec::SmallVec;
diff --git a/crates/hir_ty/src/lib.rs b/crates/hir_ty/src/lib.rs
index d23eff513..15b61bedc 100644
--- a/crates/hir_ty/src/lib.rs
+++ b/crates/hir_ty/src/lib.rs
@@ -10,9 +10,9 @@ mod autoderef;
10mod builder; 10mod builder;
11mod chalk_db; 11mod chalk_db;
12mod chalk_ext; 12mod chalk_ext;
13pub mod consteval;
13mod infer; 14mod infer;
14mod interner; 15mod interner;
15mod consts;
16mod lower; 16mod lower;
17mod mapping; 17mod mapping;
18mod op; 18mod op;
@@ -38,9 +38,13 @@ use chalk_ir::{
38 interner::HasInterner, 38 interner::HasInterner,
39 UintTy, 39 UintTy,
40}; 40};
41use hir_def::{expr::ExprId, type_ref::Rawness, TypeParamId}; 41use hir_def::{
42 expr::ExprId,
43 type_ref::{ConstScalar, Rawness},
44 TypeParamId,
45};
42 46
43use crate::{consts::ConstScalar, db::HirDatabase, display::HirDisplay, utils::generics}; 47use crate::{db::HirDatabase, display::HirDisplay, utils::generics};
44 48
45pub use autoderef::autoderef; 49pub use autoderef::autoderef;
46pub use builder::TyBuilder; 50pub use builder::TyBuilder;
diff --git a/crates/hir_ty/src/lower.rs b/crates/hir_ty/src/lower.rs
index 9751b45e4..bd8bb6028 100644
--- a/crates/hir_ty/src/lower.rs
+++ b/crates/hir_ty/src/lower.rs
@@ -29,8 +29,8 @@ use stdx::impl_from;
29use syntax::ast; 29use syntax::ast;
30 30
31use crate::{ 31use crate::{
32 consteval,
32 db::HirDatabase, 33 db::HirDatabase,
33 dummy_usize_const,
34 mapping::ToChalk, 34 mapping::ToChalk,
35 static_lifetime, to_assoc_type_id, to_chalk_trait_id, to_placeholder_idx, 35 static_lifetime, to_assoc_type_id, to_chalk_trait_id, to_placeholder_idx,
36 utils::{ 36 utils::{
@@ -172,11 +172,12 @@ impl<'a> TyLoweringContext<'a> {
172 let inner_ty = self.lower_ty(inner); 172 let inner_ty = self.lower_ty(inner);
173 TyKind::Raw(lower_to_chalk_mutability(*mutability), inner_ty).intern(&Interner) 173 TyKind::Raw(lower_to_chalk_mutability(*mutability), inner_ty).intern(&Interner)
174 } 174 }
175 TypeRef::Array(inner) => { 175 TypeRef::Array(inner, len) => {
176 let inner_ty = self.lower_ty(inner); 176 let inner_ty = self.lower_ty(inner);
177 // FIXME: we don't have length info here because we don't store an expression for 177
178 // the length 178 let const_len = consteval::usize_const(len.as_usize());
179 TyKind::Array(inner_ty, dummy_usize_const()).intern(&Interner) 179
180 TyKind::Array(inner_ty, const_len).intern(&Interner)
180 } 181 }
181 TypeRef::Slice(inner) => { 182 TypeRef::Slice(inner) => {
182 let inner_ty = self.lower_ty(inner); 183 let inner_ty = self.lower_ty(inner);
diff --git a/crates/hir_ty/src/tests/coercion.rs b/crates/hir_ty/src/tests/coercion.rs
index aad3d610e..190471069 100644
--- a/crates/hir_ty/src/tests/coercion.rs
+++ b/crates/hir_ty/src/tests/coercion.rs
@@ -64,42 +64,42 @@ fn coerce_places() {
64 81..92 '{ loop {} }': T 64 81..92 '{ loop {} }': T
65 83..90 'loop {}': ! 65 83..90 'loop {}': !
66 88..90 '{}': () 66 88..90 '{}': ()
67 121..132 '{ loop {} }': *mut [T; _] 67 121..132 '{ loop {} }': *mut [T; 2]
68 123..130 'loop {}': ! 68 123..130 'loop {}': !
69 128..130 '{}': () 69 128..130 '{}': ()
70 159..172 '{ gen() }': *mut [U] 70 159..172 '{ gen() }': *mut [U]
71 165..168 'gen': fn gen<U>() -> *mut [U; _] 71 165..168 'gen': fn gen<U>() -> *mut [U; 2]
72 165..170 'gen()': *mut [U; _] 72 165..170 'gen()': *mut [U; 2]
73 185..419 '{ ...rr); }': () 73 185..419 '{ ...rr); }': ()
74 195..198 'arr': &[u8; _] 74 195..198 'arr': &[u8; 1]
75 211..215 '&[1]': &[u8; 1] 75 211..215 '&[1]': &[u8; 1]
76 212..215 '[1]': [u8; 1] 76 212..215 '[1]': [u8; 1]
77 213..214 '1': u8 77 213..214 '1': u8
78 226..227 'a': &[u8] 78 226..227 'a': &[u8]
79 236..239 'arr': &[u8; _] 79 236..239 'arr': &[u8; 1]
80 249..250 'b': u8 80 249..250 'b': u8
81 253..254 'f': fn f<u8>(&[u8]) -> u8 81 253..254 'f': fn f<u8>(&[u8]) -> u8
82 253..259 'f(arr)': u8 82 253..259 'f(arr)': u8
83 255..258 'arr': &[u8; _] 83 255..258 'arr': &[u8; 1]
84 269..270 'c': &[u8] 84 269..270 'c': &[u8]
85 279..286 '{ arr }': &[u8] 85 279..286 '{ arr }': &[u8]
86 281..284 'arr': &[u8; _] 86 281..284 'arr': &[u8; 1]
87 296..297 'd': u8 87 296..297 'd': u8
88 300..301 'g': fn g<u8>(S<&[u8]>) -> u8 88 300..301 'g': fn g<u8>(S<&[u8]>) -> u8
89 300..315 'g(S { a: arr })': u8 89 300..315 'g(S { a: arr })': u8
90 302..314 'S { a: arr }': S<&[u8]> 90 302..314 'S { a: arr }': S<&[u8]>
91 309..312 'arr': &[u8; _] 91 309..312 'arr': &[u8; 1]
92 325..326 'e': [&[u8]; _] 92 325..326 'e': [&[u8]; 1]
93 340..345 '[arr]': [&[u8]; 1] 93 340..345 '[arr]': [&[u8]; 1]
94 341..344 'arr': &[u8; _] 94 341..344 'arr': &[u8; 1]
95 355..356 'f': [&[u8]; _] 95 355..356 'f': [&[u8]; 2]
96 370..378 '[arr; 2]': [&[u8]; _] 96 370..378 '[arr; 2]': [&[u8]; 2]
97 371..374 'arr': &[u8; _] 97 371..374 'arr': &[u8; 1]
98 376..377 '2': usize 98 376..377 '2': usize
99 388..389 'g': (&[u8], &[u8]) 99 388..389 'g': (&[u8], &[u8])
100 406..416 '(arr, arr)': (&[u8], &[u8]) 100 406..416 '(arr, arr)': (&[u8], &[u8])
101 407..410 'arr': &[u8; _] 101 407..410 'arr': &[u8; 1]
102 412..415 'arr': &[u8; _] 102 412..415 'arr': &[u8; 1]
103 "#]], 103 "#]],
104 ); 104 );
105} 105}
@@ -159,7 +159,7 @@ fn infer_custom_coerce_unsized() {
159 impl<'a, 'b: 'a, T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<&'a U> for &'b T {} 159 impl<'a, 'b: 'a, T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<&'a U> for &'b T {}
160 impl<T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<*mut U> for *mut T {} 160 impl<T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<*mut U> for *mut T {}
161 "#, 161 "#,
162 expect![[r" 162 expect![[r#"
163 257..258 'x': A<[T]> 163 257..258 'x': A<[T]>
164 278..283 '{ x }': A<[T]> 164 278..283 '{ x }': A<[T]>
165 280..281 'x': A<[T]> 165 280..281 'x': A<[T]>
@@ -169,23 +169,23 @@ fn infer_custom_coerce_unsized() {
169 333..334 'x': C<[T]> 169 333..334 'x': C<[T]>
170 354..359 '{ x }': C<[T]> 170 354..359 '{ x }': C<[T]>
171 356..357 'x': C<[T]> 171 356..357 'x': C<[T]>
172 369..370 'a': A<[u8; _]> 172 369..370 'a': A<[u8; 2]>
173 384..385 'b': B<[u8; _]> 173 384..385 'b': B<[u8; 2]>
174 399..400 'c': C<[u8; _]> 174 399..400 'c': C<[u8; 2]>
175 414..480 '{ ...(c); }': () 175 414..480 '{ ...(c); }': ()
176 424..425 'd': A<[{unknown}]> 176 424..425 'd': A<[{unknown}]>
177 428..432 'foo1': fn foo1<{unknown}>(A<[{unknown}]>) -> A<[{unknown}]> 177 428..432 'foo1': fn foo1<{unknown}>(A<[{unknown}]>) -> A<[{unknown}]>
178 428..435 'foo1(a)': A<[{unknown}]> 178 428..435 'foo1(a)': A<[{unknown}]>
179 433..434 'a': A<[u8; _]> 179 433..434 'a': A<[u8; 2]>
180 445..446 'e': B<[u8]> 180 445..446 'e': B<[u8]>
181 449..453 'foo2': fn foo2<u8>(B<[u8]>) -> B<[u8]> 181 449..453 'foo2': fn foo2<u8>(B<[u8]>) -> B<[u8]>
182 449..456 'foo2(b)': B<[u8]> 182 449..456 'foo2(b)': B<[u8]>
183 454..455 'b': B<[u8; _]> 183 454..455 'b': B<[u8; 2]>
184 466..467 'f': C<[u8]> 184 466..467 'f': C<[u8]>
185 470..474 'foo3': fn foo3<u8>(C<[u8]>) -> C<[u8]> 185 470..474 'foo3': fn foo3<u8>(C<[u8]>) -> C<[u8]>
186 470..477 'foo3(c)': C<[u8]> 186 470..477 'foo3(c)': C<[u8]>
187 475..476 'c': C<[u8; _]> 187 475..476 'c': C<[u8; 2]>
188 "]], 188 "#]],
189 ); 189 );
190} 190}
191 191
diff --git a/crates/hir_ty/src/tests/patterns.rs b/crates/hir_ty/src/tests/patterns.rs
index 33305f208..787647e9f 100644
--- a/crates/hir_ty/src/tests/patterns.rs
+++ b/crates/hir_ty/src/tests/patterns.rs
@@ -345,19 +345,19 @@ fn infer_pattern_match_arr() {
345 "#, 345 "#,
346 expect![[r#" 346 expect![[r#"
347 10..179 '{ ... } }': () 347 10..179 '{ ... } }': ()
348 20..23 'arr': [f64; _] 348 20..23 'arr': [f64; 2]
349 36..46 '[0.0, 1.0]': [f64; 2] 349 36..46 '[0.0, 1.0]': [f64; 2]
350 37..40 '0.0': f64 350 37..40 '0.0': f64
351 42..45 '1.0': f64 351 42..45 '1.0': f64
352 52..177 'match ... }': () 352 52..177 'match ... }': ()
353 58..61 'arr': [f64; _] 353 58..61 'arr': [f64; 2]
354 72..80 '[1.0, a]': [f64; _] 354 72..80 '[1.0, a]': [f64; 2]
355 73..76 '1.0': f64 355 73..76 '1.0': f64
356 73..76 '1.0': f64 356 73..76 '1.0': f64
357 78..79 'a': f64 357 78..79 'a': f64
358 84..110 '{ ... }': () 358 84..110 '{ ... }': ()
359 98..99 'a': f64 359 98..99 'a': f64
360 120..126 '[b, c]': [f64; _] 360 120..126 '[b, c]': [f64; 2]
361 121..122 'b': f64 361 121..122 'b': f64
362 124..125 'c': f64 362 124..125 'c': f64
363 130..171 '{ ... }': () 363 130..171 '{ ... }': ()
@@ -732,7 +732,7 @@ fn foo(tuple: (u8, i16, f32)) {
732 111..112 'a': u8 732 111..112 'a': u8
733 114..115 'b': i16 733 114..115 'b': i16
734 124..126 '{}': () 734 124..126 '{}': ()
735 136..142 '(a, b)': (u8, i16, f32) 735 136..142 '(a, b)': (u8, i16)
736 137..138 'a': u8 736 137..138 'a': u8
737 140..141 'b': i16 737 140..141 'b': i16
738 146..161 '{/*too short*/}': () 738 146..161 '{/*too short*/}': ()
diff --git a/crates/hir_ty/src/tests/simple.rs b/crates/hir_ty/src/tests/simple.rs
index 8b09f2e4a..a9cd42186 100644
--- a/crates/hir_ty/src/tests/simple.rs
+++ b/crates/hir_ty/src/tests/simple.rs
@@ -488,23 +488,34 @@ fn infer_literals() {
488 mod foo {} 488 mod foo {}
489 "#; 489 "#;
490 br#"yolo"#; 490 br#"yolo"#;
491 let a = b"a\x20b\
492 c";
493 let b = br"g\
494h";
495 let c = br#"x"\"yb"#;
491 } 496 }
492 "##, 497 "##,
493 expect![[r##" 498 expect![[r##"
494 10..216 '{ ...o"#; }': () 499 18..478 '{ ... }': ()
495 16..20 '5i32': i32 500 32..36 '5i32': i32
496 26..30 '5f32': f32 501 50..54 '5f32': f32
497 36..40 '5f64': f64 502 68..72 '5f64': f64
498 46..53 '"hello"': &str 503 86..93 '"hello"': &str
499 59..67 'b"bytes"': &[u8; _] 504 107..115 'b"bytes"': &[u8; 5]
500 73..76 ''c'': char 505 129..132 ''c'': char
501 82..86 'b'b'': u8 506 146..150 'b'b'': u8
502 92..96 '3.14': f64 507 164..168 '3.14': f64
503 102..106 '5000': i32 508 182..186 '5000': i32
504 112..117 'false': bool 509 200..205 'false': bool
505 123..127 'true': bool 510 219..223 'true': bool
506 133..197 'r#" ... "#': &str 511 237..333 'r#" ... "#': &str
507 203..213 'br#"yolo"#': &[u8; _] 512 347..357 'br#"yolo"#': &[u8; 4]
513 375..376 'a': &[u8; 4]
514 379..403 'b"a\x2... c"': &[u8; 4]
515 421..422 'b': &[u8; 4]
516 425..433 'br"g\ h"': &[u8; 4]
517 451..452 'c': &[u8; 6]
518 455..467 'br#"x"\"yb"#': &[u8; 6]
508 "##]], 519 "##]],
509 ); 520 );
510} 521}
@@ -1260,12 +1271,14 @@ fn infer_array() {
1260 1271
1261 let b = [a, ["b"]]; 1272 let b = [a, ["b"]];
1262 let x: [u8; 0] = []; 1273 let x: [u8; 0] = [];
1274 // FIXME: requires const evaluation/taking type from rhs somehow
1275 let y: [u8; 2+2] = [1,2,3,4];
1263 } 1276 }
1264 "#, 1277 "#,
1265 expect![[r#" 1278 expect![[r#"
1266 8..9 'x': &str 1279 8..9 'x': &str
1267 17..18 'y': isize 1280 17..18 'y': isize
1268 27..292 '{ ... []; }': () 1281 27..395 '{ ...,4]; }': ()
1269 37..38 'a': [&str; 1] 1282 37..38 'a': [&str; 1]
1270 41..44 '[x]': [&str; 1] 1283 41..44 '[x]': [&str; 1]
1271 42..43 'x': &str 1284 42..43 'x': &str
@@ -1313,8 +1326,14 @@ fn infer_array() {
1313 255..256 'a': [&str; 1] 1326 255..256 'a': [&str; 1]
1314 258..263 '["b"]': [&str; 1] 1327 258..263 '["b"]': [&str; 1]
1315 259..262 '"b"': &str 1328 259..262 '"b"': &str
1316 274..275 'x': [u8; _] 1329 274..275 'x': [u8; 0]
1317 287..289 '[]': [u8; 0] 1330 287..289 '[]': [u8; 0]
1331 368..369 'y': [u8; _]
1332 383..392 '[1,2,3,4]': [u8; 4]
1333 384..385 '1': u8
1334 386..387 '2': u8
1335 388..389 '3': u8
1336 390..391 '4': u8
1318 "#]], 1337 "#]],
1319 ); 1338 );
1320} 1339}
@@ -2409,38 +2428,38 @@ fn infer_operator_overload() {
2409 320..422 '{ ... }': V2 2428 320..422 '{ ... }': V2
2410 334..335 'x': f32 2429 334..335 'x': f32
2411 338..342 'self': V2 2430 338..342 'self': V2
2412 338..344 'self.0': [f32; _] 2431 338..344 'self.0': [f32; 2]
2413 338..347 'self.0[0]': {unknown} 2432 338..347 'self.0[0]': {unknown}
2414 338..358 'self.0...s.0[0]': f32 2433 338..358 'self.0...s.0[0]': f32
2415 345..346 '0': i32 2434 345..346 '0': i32
2416 350..353 'rhs': V2 2435 350..353 'rhs': V2
2417 350..355 'rhs.0': [f32; _] 2436 350..355 'rhs.0': [f32; 2]
2418 350..358 'rhs.0[0]': {unknown} 2437 350..358 'rhs.0[0]': {unknown}
2419 356..357 '0': i32 2438 356..357 '0': i32
2420 372..373 'y': f32 2439 372..373 'y': f32
2421 376..380 'self': V2 2440 376..380 'self': V2
2422 376..382 'self.0': [f32; _] 2441 376..382 'self.0': [f32; 2]
2423 376..385 'self.0[1]': {unknown} 2442 376..385 'self.0[1]': {unknown}
2424 376..396 'self.0...s.0[1]': f32 2443 376..396 'self.0...s.0[1]': f32
2425 383..384 '1': i32 2444 383..384 '1': i32
2426 388..391 'rhs': V2 2445 388..391 'rhs': V2
2427 388..393 'rhs.0': [f32; _] 2446 388..393 'rhs.0': [f32; 2]
2428 388..396 'rhs.0[1]': {unknown} 2447 388..396 'rhs.0[1]': {unknown}
2429 394..395 '1': i32 2448 394..395 '1': i32
2430 406..408 'V2': V2([f32; _]) -> V2 2449 406..408 'V2': V2([f32; 2]) -> V2
2431 406..416 'V2([x, y])': V2 2450 406..416 'V2([x, y])': V2
2432 409..415 '[x, y]': [f32; 2] 2451 409..415 '[x, y]': [f32; 2]
2433 410..411 'x': f32 2452 410..411 'x': f32
2434 413..414 'y': f32 2453 413..414 'y': f32
2435 436..519 '{ ... vb; }': () 2454 436..519 '{ ... vb; }': ()
2436 446..448 'va': V2 2455 446..448 'va': V2
2437 451..453 'V2': V2([f32; _]) -> V2 2456 451..453 'V2': V2([f32; 2]) -> V2
2438 451..465 'V2([0.0, 1.0])': V2 2457 451..465 'V2([0.0, 1.0])': V2
2439 454..464 '[0.0, 1.0]': [f32; 2] 2458 454..464 '[0.0, 1.0]': [f32; 2]
2440 455..458 '0.0': f32 2459 455..458 '0.0': f32
2441 460..463 '1.0': f32 2460 460..463 '1.0': f32
2442 475..477 'vb': V2 2461 475..477 'vb': V2
2443 480..482 'V2': V2([f32; _]) -> V2 2462 480..482 'V2': V2([f32; 2]) -> V2
2444 480..494 'V2([0.0, 1.0])': V2 2463 480..494 'V2([0.0, 1.0])': V2
2445 483..493 '[0.0, 1.0]': [f32; 2] 2464 483..493 '[0.0, 1.0]': [f32; 2]
2446 484..487 '0.0': f32 2465 484..487 '0.0': f32
diff --git a/crates/hir_ty/src/tests/traits.rs b/crates/hir_ty/src/tests/traits.rs
index 47a1455fd..f80cf9879 100644
--- a/crates/hir_ty/src/tests/traits.rs
+++ b/crates/hir_ty/src/tests/traits.rs
@@ -3474,3 +3474,100 @@ fn main(){
3474 "#]], 3474 "#]],
3475 ) 3475 )
3476} 3476}
3477
3478#[test]
3479fn array_length() {
3480 check_infer(
3481 r#"
3482trait T {
3483 type Output;
3484 fn do_thing(&self) -> Self::Output;
3485}
3486
3487impl T for [u8; 4] {
3488 type Output = usize;
3489 fn do_thing(&self) -> Self::Output {
3490 2
3491 }
3492}
3493
3494impl T for [u8; 2] {
3495 type Output = u8;
3496 fn do_thing(&self) -> Self::Output {
3497 2
3498 }
3499}
3500
3501fn main() {
3502 let v = [0u8; 2];
3503 let v2 = v.do_thing();
3504 let v3 = [0u8; 4];
3505 let v4 = v3.do_thing();
3506}
3507"#,
3508 expect![[r#"
3509 44..48 'self': &Self
3510 133..137 'self': &[u8; 4]
3511 155..172 '{ ... }': usize
3512 165..166 '2': usize
3513 236..240 'self': &[u8; 2]
3514 258..275 '{ ... }': u8
3515 268..269 '2': u8
3516 289..392 '{ ...g(); }': ()
3517 299..300 'v': [u8; 2]
3518 303..311 '[0u8; 2]': [u8; 2]
3519 304..307 '0u8': u8
3520 309..310 '2': usize
3521 321..323 'v2': u8
3522 326..327 'v': [u8; 2]
3523 326..338 'v.do_thing()': u8
3524 348..350 'v3': [u8; 4]
3525 353..361 '[0u8; 4]': [u8; 4]
3526 354..357 '0u8': u8
3527 359..360 '4': usize
3528 371..373 'v4': usize
3529 376..378 'v3': [u8; 4]
3530 376..389 'v3.do_thing()': usize
3531 "#]],
3532 )
3533}
3534
3535// FIXME: We should infer the length of the returned array :)
3536#[test]
3537fn const_generics() {
3538 check_infer(
3539 r#"
3540trait T {
3541 type Output;
3542 fn do_thing(&self) -> Self::Output;
3543}
3544
3545impl<const L: usize> T for [u8; L] {
3546 type Output = [u8; L];
3547 fn do_thing(&self) -> Self::Output {
3548 *self
3549 }
3550}
3551
3552fn main() {
3553 let v = [0u8; 2];
3554 let v2 = v.do_thing();
3555}
3556"#,
3557 expect![[r#"
3558 44..48 'self': &Self
3559 151..155 'self': &[u8; _]
3560 173..194 '{ ... }': [u8; _]
3561 183..188 '*self': [u8; _]
3562 184..188 'self': &[u8; _]
3563 208..260 '{ ...g(); }': ()
3564 218..219 'v': [u8; 2]
3565 222..230 '[0u8; 2]': [u8; 2]
3566 223..226 '0u8': u8
3567 228..229 '2': usize
3568 240..242 'v2': [u8; _]
3569 245..246 'v': [u8; 2]
3570 245..257 'v.do_thing()': [u8; _]
3571 "#]],
3572 )
3573}
diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs
index 273d8cfbb..27d347dbd 100644
--- a/crates/ide/src/diagnostics.rs
+++ b/crates/ide/src/diagnostics.rs
@@ -28,7 +28,7 @@ use unlinked_file::UnlinkedFile;
28 28
29use crate::{Assist, AssistId, AssistKind, FileId, Label, SourceChange}; 29use crate::{Assist, AssistId, AssistKind, FileId, Label, SourceChange};
30 30
31use self::fixes::DiagnosticWithFix; 31use self::fixes::DiagnosticWithFixes;
32 32
33#[derive(Debug)] 33#[derive(Debug)]
34pub struct Diagnostic { 34pub struct Diagnostic {
@@ -36,14 +36,14 @@ pub struct Diagnostic {
36 pub message: String, 36 pub message: String,
37 pub range: TextRange, 37 pub range: TextRange,
38 pub severity: Severity, 38 pub severity: Severity,
39 pub fix: Option<Assist>, 39 pub fixes: Option<Vec<Assist>>,
40 pub unused: bool, 40 pub unused: bool,
41 pub code: Option<DiagnosticCode>, 41 pub code: Option<DiagnosticCode>,
42} 42}
43 43
44impl Diagnostic { 44impl Diagnostic {
45 fn error(range: TextRange, message: String) -> Self { 45 fn error(range: TextRange, message: String) -> Self {
46 Self { message, range, severity: Severity::Error, fix: None, unused: false, code: None } 46 Self { message, range, severity: Severity::Error, fixes: None, unused: false, code: None }
47 } 47 }
48 48
49 fn hint(range: TextRange, message: String) -> Self { 49 fn hint(range: TextRange, message: String) -> Self {
@@ -51,14 +51,14 @@ impl Diagnostic {
51 message, 51 message,
52 range, 52 range,
53 severity: Severity::WeakWarning, 53 severity: Severity::WeakWarning,
54 fix: None, 54 fixes: None,
55 unused: false, 55 unused: false,
56 code: None, 56 code: None,
57 } 57 }
58 } 58 }
59 59
60 fn with_fix(self, fix: Option<Assist>) -> Self { 60 fn with_fixes(self, fixes: Option<Vec<Assist>>) -> Self {
61 Self { fix, ..self } 61 Self { fixes, ..self }
62 } 62 }
63 63
64 fn with_unused(self, unused: bool) -> Self { 64 fn with_unused(self, unused: bool) -> Self {
@@ -154,7 +154,7 @@ pub(crate) fn diagnostics(
154 // Override severity and mark as unused. 154 // Override severity and mark as unused.
155 res.borrow_mut().push( 155 res.borrow_mut().push(
156 Diagnostic::hint(range, d.message()) 156 Diagnostic::hint(range, d.message())
157 .with_fix(d.fix(&sema, resolve)) 157 .with_fixes(d.fixes(&sema, resolve))
158 .with_code(Some(d.code())), 158 .with_code(Some(d.code())),
159 ); 159 );
160 }) 160 })
@@ -210,23 +210,23 @@ pub(crate) fn diagnostics(
210 res.into_inner() 210 res.into_inner()
211} 211}
212 212
213fn diagnostic_with_fix<D: DiagnosticWithFix>( 213fn diagnostic_with_fix<D: DiagnosticWithFixes>(
214 d: &D, 214 d: &D,
215 sema: &Semantics<RootDatabase>, 215 sema: &Semantics<RootDatabase>,
216 resolve: &AssistResolveStrategy, 216 resolve: &AssistResolveStrategy,
217) -> Diagnostic { 217) -> Diagnostic {
218 Diagnostic::error(sema.diagnostics_display_range(d.display_source()).range, d.message()) 218 Diagnostic::error(sema.diagnostics_display_range(d.display_source()).range, d.message())
219 .with_fix(d.fix(&sema, resolve)) 219 .with_fixes(d.fixes(&sema, resolve))
220 .with_code(Some(d.code())) 220 .with_code(Some(d.code()))
221} 221}
222 222
223fn warning_with_fix<D: DiagnosticWithFix>( 223fn warning_with_fix<D: DiagnosticWithFixes>(
224 d: &D, 224 d: &D,
225 sema: &Semantics<RootDatabase>, 225 sema: &Semantics<RootDatabase>,
226 resolve: &AssistResolveStrategy, 226 resolve: &AssistResolveStrategy,
227) -> Diagnostic { 227) -> Diagnostic {
228 Diagnostic::hint(sema.diagnostics_display_range(d.display_source()).range, d.message()) 228 Diagnostic::hint(sema.diagnostics_display_range(d.display_source()).range, d.message())
229 .with_fix(d.fix(&sema, resolve)) 229 .with_fixes(d.fixes(&sema, resolve))
230 .with_code(Some(d.code())) 230 .with_code(Some(d.code()))
231} 231}
232 232
@@ -256,12 +256,12 @@ fn check_unnecessary_braces_in_use_statement(
256 256
257 acc.push( 257 acc.push(
258 Diagnostic::hint(use_range, "Unnecessary braces in use statement".to_string()) 258 Diagnostic::hint(use_range, "Unnecessary braces in use statement".to_string())
259 .with_fix(Some(fix( 259 .with_fixes(Some(vec![fix(
260 "remove_braces", 260 "remove_braces",
261 "Remove unnecessary braces", 261 "Remove unnecessary braces",
262 SourceChange::from_text_edit(file_id, edit), 262 SourceChange::from_text_edit(file_id, edit),
263 use_range, 263 use_range,
264 ))), 264 )])),
265 ); 265 );
266 } 266 }
267 267
@@ -309,9 +309,23 @@ mod tests {
309 /// Takes a multi-file input fixture with annotated cursor positions, 309 /// Takes a multi-file input fixture with annotated cursor positions,
310 /// and checks that: 310 /// and checks that:
311 /// * a diagnostic is produced 311 /// * a diagnostic is produced
312 /// * this diagnostic fix trigger range touches the input cursor position 312 /// * the first diagnostic fix trigger range touches the input cursor position
313 /// * that the contents of the file containing the cursor match `after` after the diagnostic fix is applied 313 /// * that the contents of the file containing the cursor match `after` after the diagnostic fix is applied
314 pub(crate) fn check_fix(ra_fixture_before: &str, ra_fixture_after: &str) { 314 pub(crate) fn check_fix(ra_fixture_before: &str, ra_fixture_after: &str) {
315 check_nth_fix(0, ra_fixture_before, ra_fixture_after);
316 }
317 /// Takes a multi-file input fixture with annotated cursor positions,
318 /// and checks that:
319 /// * a diagnostic is produced
320 /// * every diagnostic fixes trigger range touches the input cursor position
321 /// * that the contents of the file containing the cursor match `after` after each diagnostic fix is applied
322 pub(crate) fn check_fixes(ra_fixture_before: &str, ra_fixtures_after: Vec<&str>) {
323 for (i, ra_fixture_after) in ra_fixtures_after.iter().enumerate() {
324 check_nth_fix(i, ra_fixture_before, ra_fixture_after)
325 }
326 }
327
328 fn check_nth_fix(nth: usize, ra_fixture_before: &str, ra_fixture_after: &str) {
315 let after = trim_indent(ra_fixture_after); 329 let after = trim_indent(ra_fixture_after);
316 330
317 let (analysis, file_position) = fixture::position(ra_fixture_before); 331 let (analysis, file_position) = fixture::position(ra_fixture_before);
@@ -324,9 +338,9 @@ mod tests {
324 .unwrap() 338 .unwrap()
325 .pop() 339 .pop()
326 .unwrap(); 340 .unwrap();
327 let fix = diagnostic.fix.unwrap(); 341 let fix = &diagnostic.fixes.unwrap()[nth];
328 let actual = { 342 let actual = {
329 let source_change = fix.source_change.unwrap(); 343 let source_change = fix.source_change.as_ref().unwrap();
330 let file_id = *source_change.source_file_edits.keys().next().unwrap(); 344 let file_id = *source_change.source_file_edits.keys().next().unwrap();
331 let mut actual = analysis.file_text(file_id).unwrap().to_string(); 345 let mut actual = analysis.file_text(file_id).unwrap().to_string();
332 346
@@ -344,7 +358,6 @@ mod tests {
344 file_position.offset 358 file_position.offset
345 ); 359 );
346 } 360 }
347
348 /// Checks that there's a diagnostic *without* fix at `$0`. 361 /// Checks that there's a diagnostic *without* fix at `$0`.
349 fn check_no_fix(ra_fixture: &str) { 362 fn check_no_fix(ra_fixture: &str) {
350 let (analysis, file_position) = fixture::position(ra_fixture); 363 let (analysis, file_position) = fixture::position(ra_fixture);
@@ -357,7 +370,7 @@ mod tests {
357 .unwrap() 370 .unwrap()
358 .pop() 371 .pop()
359 .unwrap(); 372 .unwrap();
360 assert!(diagnostic.fix.is_none(), "got a fix when none was expected: {:?}", diagnostic); 373 assert!(diagnostic.fixes.is_none(), "got a fix when none was expected: {:?}", diagnostic);
361 } 374 }
362 375
363 /// Takes a multi-file input fixture with annotated cursor position and checks that no diagnostics 376 /// Takes a multi-file input fixture with annotated cursor position and checks that no diagnostics
@@ -375,7 +388,7 @@ mod tests {
375 assert_eq!(diagnostics.len(), 0, "unexpected diagnostics:\n{:#?}", diagnostics); 388 assert_eq!(diagnostics.len(), 0, "unexpected diagnostics:\n{:#?}", diagnostics);
376 } 389 }
377 390
378 fn check_expect(ra_fixture: &str, expect: Expect) { 391 pub(crate) fn check_expect(ra_fixture: &str, expect: Expect) {
379 let (analysis, file_id) = fixture::file(ra_fixture); 392 let (analysis, file_id) = fixture::file(ra_fixture);
380 let diagnostics = analysis 393 let diagnostics = analysis
381 .diagnostics(&DiagnosticsConfig::default(), AssistResolveStrategy::All, file_id) 394 .diagnostics(&DiagnosticsConfig::default(), AssistResolveStrategy::All, file_id)
@@ -384,374 +397,6 @@ mod tests {
384 } 397 }
385 398
386 #[test] 399 #[test]
387 fn test_wrap_return_type_option() {
388 check_fix(
389 r#"
390//- /main.rs crate:main deps:core
391use core::option::Option::{self, Some, None};
392
393fn div(x: i32, y: i32) -> Option<i32> {
394 if y == 0 {
395 return None;
396 }
397 x / y$0
398}
399//- /core/lib.rs crate:core
400pub mod result {
401 pub enum Result<T, E> { Ok(T), Err(E) }
402}
403pub mod option {
404 pub enum Option<T> { Some(T), None }
405}
406"#,
407 r#"
408use core::option::Option::{self, Some, None};
409
410fn div(x: i32, y: i32) -> Option<i32> {
411 if y == 0 {
412 return None;
413 }
414 Some(x / y)
415}
416"#,
417 );
418 }
419
420 #[test]
421 fn test_wrap_return_type() {
422 check_fix(
423 r#"
424//- /main.rs crate:main deps:core
425use core::result::Result::{self, Ok, Err};
426
427fn div(x: i32, y: i32) -> Result<i32, ()> {
428 if y == 0 {
429 return Err(());
430 }
431 x / y$0
432}
433//- /core/lib.rs crate:core
434pub mod result {
435 pub enum Result<T, E> { Ok(T), Err(E) }
436}
437pub mod option {
438 pub enum Option<T> { Some(T), None }
439}
440"#,
441 r#"
442use core::result::Result::{self, Ok, Err};
443
444fn div(x: i32, y: i32) -> Result<i32, ()> {
445 if y == 0 {
446 return Err(());
447 }
448 Ok(x / y)
449}
450"#,
451 );
452 }
453
454 #[test]
455 fn test_wrap_return_type_handles_generic_functions() {
456 check_fix(
457 r#"
458//- /main.rs crate:main deps:core
459use core::result::Result::{self, Ok, Err};
460
461fn div<T>(x: T) -> Result<T, i32> {
462 if x == 0 {
463 return Err(7);
464 }
465 $0x
466}
467//- /core/lib.rs crate:core
468pub mod result {
469 pub enum Result<T, E> { Ok(T), Err(E) }
470}
471pub mod option {
472 pub enum Option<T> { Some(T), None }
473}
474"#,
475 r#"
476use core::result::Result::{self, Ok, Err};
477
478fn div<T>(x: T) -> Result<T, i32> {
479 if x == 0 {
480 return Err(7);
481 }
482 Ok(x)
483}
484"#,
485 );
486 }
487
488 #[test]
489 fn test_wrap_return_type_handles_type_aliases() {
490 check_fix(
491 r#"
492//- /main.rs crate:main deps:core
493use core::result::Result::{self, Ok, Err};
494
495type MyResult<T> = Result<T, ()>;
496
497fn div(x: i32, y: i32) -> MyResult<i32> {
498 if y == 0 {
499 return Err(());
500 }
501 x $0/ y
502}
503//- /core/lib.rs crate:core
504pub mod result {
505 pub enum Result<T, E> { Ok(T), Err(E) }
506}
507pub mod option {
508 pub enum Option<T> { Some(T), None }
509}
510"#,
511 r#"
512use core::result::Result::{self, Ok, Err};
513
514type MyResult<T> = Result<T, ()>;
515
516fn div(x: i32, y: i32) -> MyResult<i32> {
517 if y == 0 {
518 return Err(());
519 }
520 Ok(x / y)
521}
522"#,
523 );
524 }
525
526 #[test]
527 fn test_wrap_return_type_not_applicable_when_expr_type_does_not_match_ok_type() {
528 check_no_diagnostics(
529 r#"
530//- /main.rs crate:main deps:core
531use core::result::Result::{self, Ok, Err};
532
533fn foo() -> Result<(), i32> { 0 }
534
535//- /core/lib.rs crate:core
536pub mod result {
537 pub enum Result<T, E> { Ok(T), Err(E) }
538}
539pub mod option {
540 pub enum Option<T> { Some(T), None }
541}
542"#,
543 );
544 }
545
546 #[test]
547 fn test_wrap_return_type_not_applicable_when_return_type_is_not_result_or_option() {
548 check_no_diagnostics(
549 r#"
550//- /main.rs crate:main deps:core
551use core::result::Result::{self, Ok, Err};
552
553enum SomeOtherEnum { Ok(i32), Err(String) }
554
555fn foo() -> SomeOtherEnum { 0 }
556
557//- /core/lib.rs crate:core
558pub mod result {
559 pub enum Result<T, E> { Ok(T), Err(E) }
560}
561pub mod option {
562 pub enum Option<T> { Some(T), None }
563}
564"#,
565 );
566 }
567
568 #[test]
569 fn test_fill_struct_fields_empty() {
570 check_fix(
571 r#"
572struct TestStruct { one: i32, two: i64 }
573
574fn test_fn() {
575 let s = TestStruct {$0};
576}
577"#,
578 r#"
579struct TestStruct { one: i32, two: i64 }
580
581fn test_fn() {
582 let s = TestStruct { one: (), two: ()};
583}
584"#,
585 );
586 }
587
588 #[test]
589 fn test_fill_struct_fields_self() {
590 check_fix(
591 r#"
592struct TestStruct { one: i32 }
593
594impl TestStruct {
595 fn test_fn() { let s = Self {$0}; }
596}
597"#,
598 r#"
599struct TestStruct { one: i32 }
600
601impl TestStruct {
602 fn test_fn() { let s = Self { one: ()}; }
603}
604"#,
605 );
606 }
607
608 #[test]
609 fn test_fill_struct_fields_enum() {
610 check_fix(
611 r#"
612enum Expr {
613 Bin { lhs: Box<Expr>, rhs: Box<Expr> }
614}
615
616impl Expr {
617 fn new_bin(lhs: Box<Expr>, rhs: Box<Expr>) -> Expr {
618 Expr::Bin {$0 }
619 }
620}
621"#,
622 r#"
623enum Expr {
624 Bin { lhs: Box<Expr>, rhs: Box<Expr> }
625}
626
627impl Expr {
628 fn new_bin(lhs: Box<Expr>, rhs: Box<Expr>) -> Expr {
629 Expr::Bin { lhs: (), rhs: () }
630 }
631}
632"#,
633 );
634 }
635
636 #[test]
637 fn test_fill_struct_fields_partial() {
638 check_fix(
639 r#"
640struct TestStruct { one: i32, two: i64 }
641
642fn test_fn() {
643 let s = TestStruct{ two: 2$0 };
644}
645"#,
646 r"
647struct TestStruct { one: i32, two: i64 }
648
649fn test_fn() {
650 let s = TestStruct{ two: 2, one: () };
651}
652",
653 );
654 }
655
656 #[test]
657 fn test_fill_struct_fields_raw_ident() {
658 check_fix(
659 r#"
660struct TestStruct { r#type: u8 }
661
662fn test_fn() {
663 TestStruct { $0 };
664}
665"#,
666 r"
667struct TestStruct { r#type: u8 }
668
669fn test_fn() {
670 TestStruct { r#type: () };
671}
672",
673 );
674 }
675
676 #[test]
677 fn test_fill_struct_fields_no_diagnostic() {
678 check_no_diagnostics(
679 r"
680 struct TestStruct { one: i32, two: i64 }
681
682 fn test_fn() {
683 let one = 1;
684 let s = TestStruct{ one, two: 2 };
685 }
686 ",
687 );
688 }
689
690 #[test]
691 fn test_fill_struct_fields_no_diagnostic_on_spread() {
692 check_no_diagnostics(
693 r"
694 struct TestStruct { one: i32, two: i64 }
695
696 fn test_fn() {
697 let one = 1;
698 let s = TestStruct{ ..a };
699 }
700 ",
701 );
702 }
703
704 #[test]
705 fn test_unresolved_module_diagnostic() {
706 check_expect(
707 r#"mod foo;"#,
708 expect![[r#"
709 [
710 Diagnostic {
711 message: "unresolved module",
712 range: 0..8,
713 severity: Error,
714 fix: Some(
715 Assist {
716 id: AssistId(
717 "create_module",
718 QuickFix,
719 ),
720 label: "Create module",
721 group: None,
722 target: 0..8,
723 source_change: Some(
724 SourceChange {
725 source_file_edits: {},
726 file_system_edits: [
727 CreateFile {
728 dst: AnchoredPathBuf {
729 anchor: FileId(
730 0,
731 ),
732 path: "foo.rs",
733 },
734 initial_contents: "",
735 },
736 ],
737 is_snippet: false,
738 },
739 ),
740 },
741 ),
742 unused: false,
743 code: Some(
744 DiagnosticCode(
745 "unresolved-module",
746 ),
747 ),
748 },
749 ]
750 "#]],
751 );
752 }
753
754 #[test]
755 fn test_unresolved_macro_range() { 400 fn test_unresolved_macro_range() {
756 check_expect( 401 check_expect(
757 r#"foo::bar!(92);"#, 402 r#"foo::bar!(92);"#,
@@ -761,7 +406,7 @@ fn test_fn() {
761 message: "unresolved macro `foo::bar!`", 406 message: "unresolved macro `foo::bar!`",
762 range: 5..8, 407 range: 5..8,
763 severity: Error, 408 severity: Error,
764 fix: None, 409 fixes: None,
765 unused: false, 410 unused: false,
766 code: Some( 411 code: Some(
767 DiagnosticCode( 412 DiagnosticCode(
@@ -792,7 +437,7 @@ fn main() {
792pub struct Foo { pub a: i32, pub b: i32 } 437pub struct Foo { pub a: i32, pub b: i32 }
793"#, 438"#,
794 r#" 439 r#"
795fn some(, b: ()) {} 440fn some(, b: () ) {}
796fn items() {} 441fn items() {}
797fn here() {} 442fn here() {}
798 443
@@ -891,53 +536,6 @@ mod a {
891 } 536 }
892 537
893 #[test] 538 #[test]
894 fn test_add_field_from_usage() {
895 check_fix(
896 r"
897fn main() {
898 Foo { bar: 3, baz$0: false};
899}
900struct Foo {
901 bar: i32
902}
903",
904 r"
905fn main() {
906 Foo { bar: 3, baz: false};
907}
908struct Foo {
909 bar: i32,
910 baz: bool
911}
912",
913 )
914 }
915
916 #[test]
917 fn test_add_field_in_other_file_from_usage() {
918 check_fix(
919 r#"
920//- /main.rs
921mod foo;
922
923fn main() {
924 foo::Foo { bar: 3, $0baz: false};
925}
926//- /foo.rs
927struct Foo {
928 bar: i32
929}
930"#,
931 r#"
932struct Foo {
933 bar: i32,
934 pub(crate) baz: bool
935}
936"#,
937 )
938 }
939
940 #[test]
941 fn test_disabled_diagnostics() { 539 fn test_disabled_diagnostics() {
942 let mut config = DiagnosticsConfig::default(); 540 let mut config = DiagnosticsConfig::default();
943 config.disabled.insert("unresolved-module".into()); 541 config.disabled.insert("unresolved-module".into());
@@ -955,134 +553,28 @@ struct Foo {
955 } 553 }
956 554
957 #[test] 555 #[test]
958 fn test_rename_incorrect_case() {
959 check_fix(
960 r#"
961pub struct test_struct$0 { one: i32 }
962
963pub fn some_fn(val: test_struct) -> test_struct {
964 test_struct { one: val.one + 1 }
965}
966"#,
967 r#"
968pub struct TestStruct { one: i32 }
969
970pub fn some_fn(val: TestStruct) -> TestStruct {
971 TestStruct { one: val.one + 1 }
972}
973"#,
974 );
975
976 check_fix(
977 r#"
978pub fn some_fn(NonSnakeCase$0: u8) -> u8 {
979 NonSnakeCase
980}
981"#,
982 r#"
983pub fn some_fn(non_snake_case: u8) -> u8 {
984 non_snake_case
985}
986"#,
987 );
988
989 check_fix(
990 r#"
991pub fn SomeFn$0(val: u8) -> u8 {
992 if val != 0 { SomeFn(val - 1) } else { val }
993}
994"#,
995 r#"
996pub fn some_fn(val: u8) -> u8 {
997 if val != 0 { some_fn(val - 1) } else { val }
998}
999"#,
1000 );
1001
1002 check_fix(
1003 r#"
1004fn some_fn() {
1005 let whatAWeird_Formatting$0 = 10;
1006 another_func(whatAWeird_Formatting);
1007}
1008"#,
1009 r#"
1010fn some_fn() {
1011 let what_a_weird_formatting = 10;
1012 another_func(what_a_weird_formatting);
1013}
1014"#,
1015 );
1016 }
1017
1018 #[test]
1019 fn test_uppercase_const_no_diagnostics() {
1020 check_no_diagnostics(
1021 r#"
1022fn foo() {
1023 const ANOTHER_ITEM$0: &str = "some_item";
1024}
1025"#,
1026 );
1027 }
1028
1029 #[test]
1030 fn test_rename_incorrect_case_struct_method() {
1031 check_fix(
1032 r#"
1033pub struct TestStruct;
1034
1035impl TestStruct {
1036 pub fn SomeFn$0() -> TestStruct {
1037 TestStruct
1038 }
1039}
1040"#,
1041 r#"
1042pub struct TestStruct;
1043
1044impl TestStruct {
1045 pub fn some_fn() -> TestStruct {
1046 TestStruct
1047 }
1048}
1049"#,
1050 );
1051 }
1052
1053 #[test]
1054 fn test_single_incorrect_case_diagnostic_in_function_name_issue_6970() {
1055 let input = r#"fn FOO$0() {}"#;
1056 let expected = r#"fn foo() {}"#;
1057
1058 let (analysis, file_position) = fixture::position(input);
1059 let diagnostics = analysis
1060 .diagnostics(
1061 &DiagnosticsConfig::default(),
1062 AssistResolveStrategy::All,
1063 file_position.file_id,
1064 )
1065 .unwrap();
1066 assert_eq!(diagnostics.len(), 1);
1067
1068 check_fix(input, expected);
1069 }
1070
1071 #[test]
1072 fn unlinked_file_prepend_first_item() { 556 fn unlinked_file_prepend_first_item() {
1073 cov_mark::check!(unlinked_file_prepend_before_first_item); 557 cov_mark::check!(unlinked_file_prepend_before_first_item);
1074 check_fix( 558 // Only tests the first one for `pub mod` since the rest are the same
559 check_fixes(
1075 r#" 560 r#"
1076//- /main.rs 561//- /main.rs
1077fn f() {} 562fn f() {}
1078//- /foo.rs 563//- /foo.rs
1079$0 564$0
1080"#, 565"#,
1081 r#" 566 vec![
567 r#"
1082mod foo; 568mod foo;
1083 569
1084fn f() {} 570fn f() {}
1085"#, 571"#,
572 r#"
573pub mod foo;
574
575fn f() {}
576"#,
577 ],
1086 ); 578 );
1087 } 579 }
1088 580
diff --git a/crates/ide/src/diagnostics/field_shorthand.rs b/crates/ide/src/diagnostics/field_shorthand.rs
index 2b1787f9b..01bd2dba6 100644
--- a/crates/ide/src/diagnostics/field_shorthand.rs
+++ b/crates/ide/src/diagnostics/field_shorthand.rs
@@ -46,14 +46,13 @@ fn check_expr_field_shorthand(
46 46
47 let field_range = record_field.syntax().text_range(); 47 let field_range = record_field.syntax().text_range();
48 acc.push( 48 acc.push(
49 Diagnostic::hint(field_range, "Shorthand struct initialization".to_string()).with_fix( 49 Diagnostic::hint(field_range, "Shorthand struct initialization".to_string())
50 Some(fix( 50 .with_fixes(Some(vec![fix(
51 "use_expr_field_shorthand", 51 "use_expr_field_shorthand",
52 "Use struct shorthand initialization", 52 "Use struct shorthand initialization",
53 SourceChange::from_text_edit(file_id, edit), 53 SourceChange::from_text_edit(file_id, edit),
54 field_range, 54 field_range,
55 )), 55 )])),
56 ),
57 ); 56 );
58 } 57 }
59} 58}
@@ -86,13 +85,13 @@ fn check_pat_field_shorthand(
86 let edit = edit_builder.finish(); 85 let edit = edit_builder.finish();
87 86
88 let field_range = record_pat_field.syntax().text_range(); 87 let field_range = record_pat_field.syntax().text_range();
89 acc.push(Diagnostic::hint(field_range, "Shorthand struct pattern".to_string()).with_fix( 88 acc.push(Diagnostic::hint(field_range, "Shorthand struct pattern".to_string()).with_fixes(
90 Some(fix( 89 Some(vec![fix(
91 "use_pat_field_shorthand", 90 "use_pat_field_shorthand",
92 "Use struct field shorthand", 91 "Use struct field shorthand",
93 SourceChange::from_text_edit(file_id, edit), 92 SourceChange::from_text_edit(file_id, edit),
94 field_range, 93 field_range,
95 )), 94 )]),
96 )); 95 ));
97 } 96 }
98} 97}
diff --git a/crates/ide/src/diagnostics/fixes.rs b/crates/ide/src/diagnostics/fixes.rs
index 15821500f..258ac6974 100644
--- a/crates/ide/src/diagnostics/fixes.rs
+++ b/crates/ide/src/diagnostics/fixes.rs
@@ -1,297 +1,31 @@
1//! Provides a way to attach fixes to the diagnostics. 1//! Provides a way to attach fixes to the diagnostics.
2//! The same module also has all curret custom fixes for the diagnostics implemented. 2//! The same module also has all curret custom fixes for the diagnostics implemented.
3use hir::{ 3mod change_case;
4 db::AstDatabase, 4mod create_field;
5 diagnostics::{ 5mod fill_missing_fields;
6 Diagnostic, IncorrectCase, MissingFields, MissingOkOrSomeInTailExpr, NoSuchField, 6mod remove_semicolon;
7 RemoveThisSemicolon, ReplaceFilterMapNextWithFindMap, UnresolvedModule, 7mod replace_with_find_map;
8 }, 8mod unresolved_module;
9 HasSource, HirDisplay, InFile, Semantics, VariantDef, 9mod wrap_tail_expr;
10}; 10
11use hir::{diagnostics::Diagnostic, Semantics};
11use ide_assists::AssistResolveStrategy; 12use ide_assists::AssistResolveStrategy;
12use ide_db::{ 13use ide_db::RootDatabase;
13 base_db::{AnchoredPathBuf, FileId},
14 source_change::{FileSystemEdit, SourceChange},
15 RootDatabase,
16};
17use syntax::{
18 algo,
19 ast::{self, edit::IndentLevel, make, ArgListOwner},
20 AstNode, TextRange,
21};
22use text_edit::TextEdit;
23 14
24use crate::{ 15use crate::Assist;
25 diagnostics::{fix, unresolved_fix},
26 references::rename::rename_with_semantics,
27 Assist, FilePosition,
28};
29 16
30/// A [Diagnostic] that potentially has a fix available. 17/// A [Diagnostic] that potentially has some fixes available.
31/// 18///
32/// [Diagnostic]: hir::diagnostics::Diagnostic 19/// [Diagnostic]: hir::diagnostics::Diagnostic
33pub(crate) trait DiagnosticWithFix: Diagnostic { 20pub(crate) trait DiagnosticWithFixes: Diagnostic {
34 /// `resolve` determines if the diagnostic should fill in the `edit` field 21 /// `resolve` determines if the diagnostic should fill in the `edit` field
35 /// of the assist. 22 /// of the assist.
36 /// 23 ///
37 /// If `resolve` is false, the edit will be computed later, on demand, and 24 /// If `resolve` is false, the edit will be computed later, on demand, and
38 /// can be omitted. 25 /// can be omitted.
39 fn fix( 26 fn fixes(
40 &self, 27 &self,
41 sema: &Semantics<RootDatabase>, 28 sema: &Semantics<RootDatabase>,
42 _resolve: &AssistResolveStrategy, 29 _resolve: &AssistResolveStrategy,
43 ) -> Option<Assist>; 30 ) -> Option<Vec<Assist>>;
44}
45
46impl DiagnosticWithFix for UnresolvedModule {
47 fn fix(
48 &self,
49 sema: &Semantics<RootDatabase>,
50 _resolve: &AssistResolveStrategy,
51 ) -> Option<Assist> {
52 let root = sema.db.parse_or_expand(self.file)?;
53 let unresolved_module = self.decl.to_node(&root);
54 Some(fix(
55 "create_module",
56 "Create module",
57 FileSystemEdit::CreateFile {
58 dst: AnchoredPathBuf {
59 anchor: self.file.original_file(sema.db),
60 path: self.candidate.clone(),
61 },
62 initial_contents: "".to_string(),
63 }
64 .into(),
65 unresolved_module.syntax().text_range(),
66 ))
67 }
68}
69
70impl DiagnosticWithFix for NoSuchField {
71 fn fix(
72 &self,
73 sema: &Semantics<RootDatabase>,
74 _resolve: &AssistResolveStrategy,
75 ) -> Option<Assist> {
76 let root = sema.db.parse_or_expand(self.file)?;
77 missing_record_expr_field_fix(
78 &sema,
79 self.file.original_file(sema.db),
80 &self.field.to_node(&root),
81 )
82 }
83}
84
85impl DiagnosticWithFix for MissingFields {
86 fn fix(
87 &self,
88 sema: &Semantics<RootDatabase>,
89 _resolve: &AssistResolveStrategy,
90 ) -> Option<Assist> {
91 // Note that although we could add a diagnostics to
92 // fill the missing tuple field, e.g :
93 // `struct A(usize);`
94 // `let a = A { 0: () }`
95 // but it is uncommon usage and it should not be encouraged.
96 if self.missed_fields.iter().any(|it| it.as_tuple_index().is_some()) {
97 return None;
98 }
99
100 let root = sema.db.parse_or_expand(self.file)?;
101 let field_list_parent = self.field_list_parent.to_node(&root);
102 let old_field_list = field_list_parent.record_expr_field_list()?;
103 let mut new_field_list = old_field_list.clone();
104 for f in self.missed_fields.iter() {
105 let field =
106 make::record_expr_field(make::name_ref(&f.to_string()), Some(make::expr_unit()));
107 new_field_list = new_field_list.append_field(&field);
108 }
109
110 let edit = {
111 let mut builder = TextEdit::builder();
112 algo::diff(&old_field_list.syntax(), &new_field_list.syntax())
113 .into_text_edit(&mut builder);
114 builder.finish()
115 };
116 Some(fix(
117 "fill_missing_fields",
118 "Fill struct fields",
119 SourceChange::from_text_edit(self.file.original_file(sema.db), edit),
120 sema.original_range(&field_list_parent.syntax()).range,
121 ))
122 }
123}
124
125impl DiagnosticWithFix for MissingOkOrSomeInTailExpr {
126 fn fix(
127 &self,
128 sema: &Semantics<RootDatabase>,
129 _resolve: &AssistResolveStrategy,
130 ) -> Option<Assist> {
131 let root = sema.db.parse_or_expand(self.file)?;
132 let tail_expr = self.expr.to_node(&root);
133 let tail_expr_range = tail_expr.syntax().text_range();
134 let replacement = format!("{}({})", self.required, tail_expr.syntax());
135 let edit = TextEdit::replace(tail_expr_range, replacement);
136 let source_change = SourceChange::from_text_edit(self.file.original_file(sema.db), edit);
137 let name = if self.required == "Ok" { "Wrap with Ok" } else { "Wrap with Some" };
138 Some(fix("wrap_tail_expr", name, source_change, tail_expr_range))
139 }
140}
141
142impl DiagnosticWithFix for RemoveThisSemicolon {
143 fn fix(
144 &self,
145 sema: &Semantics<RootDatabase>,
146 _resolve: &AssistResolveStrategy,
147 ) -> Option<Assist> {
148 let root = sema.db.parse_or_expand(self.file)?;
149
150 let semicolon = self
151 .expr
152 .to_node(&root)
153 .syntax()
154 .parent()
155 .and_then(ast::ExprStmt::cast)
156 .and_then(|expr| expr.semicolon_token())?
157 .text_range();
158
159 let edit = TextEdit::delete(semicolon);
160 let source_change = SourceChange::from_text_edit(self.file.original_file(sema.db), edit);
161
162 Some(fix("remove_semicolon", "Remove this semicolon", source_change, semicolon))
163 }
164}
165
166impl DiagnosticWithFix for IncorrectCase {
167 fn fix(
168 &self,
169 sema: &Semantics<RootDatabase>,
170 resolve: &AssistResolveStrategy,
171 ) -> Option<Assist> {
172 let root = sema.db.parse_or_expand(self.file)?;
173 let name_node = self.ident.to_node(&root);
174
175 let name_node = InFile::new(self.file, name_node.syntax());
176 let frange = name_node.original_file_range(sema.db);
177 let file_position = FilePosition { file_id: frange.file_id, offset: frange.range.start() };
178
179 let label = format!("Rename to {}", self.suggested_text);
180 let mut res = unresolved_fix("change_case", &label, frange.range);
181 if resolve.should_resolve(&res.id) {
182 let source_change = rename_with_semantics(sema, file_position, &self.suggested_text);
183 res.source_change = Some(source_change.ok().unwrap_or_default());
184 }
185
186 Some(res)
187 }
188}
189
190impl DiagnosticWithFix for ReplaceFilterMapNextWithFindMap {
191 fn fix(
192 &self,
193 sema: &Semantics<RootDatabase>,
194 _resolve: &AssistResolveStrategy,
195 ) -> Option<Assist> {
196 let root = sema.db.parse_or_expand(self.file)?;
197 let next_expr = self.next_expr.to_node(&root);
198 let next_call = ast::MethodCallExpr::cast(next_expr.syntax().clone())?;
199
200 let filter_map_call = ast::MethodCallExpr::cast(next_call.receiver()?.syntax().clone())?;
201 let filter_map_name_range = filter_map_call.name_ref()?.ident_token()?.text_range();
202 let filter_map_args = filter_map_call.arg_list()?;
203
204 let range_to_replace =
205 TextRange::new(filter_map_name_range.start(), next_expr.syntax().text_range().end());
206 let replacement = format!("find_map{}", filter_map_args.syntax().text());
207 let trigger_range = next_expr.syntax().text_range();
208
209 let edit = TextEdit::replace(range_to_replace, replacement);
210
211 let source_change = SourceChange::from_text_edit(self.file.original_file(sema.db), edit);
212
213 Some(fix(
214 "replace_with_find_map",
215 "Replace filter_map(..).next() with find_map()",
216 source_change,
217 trigger_range,
218 ))
219 }
220}
221
222fn missing_record_expr_field_fix(
223 sema: &Semantics<RootDatabase>,
224 usage_file_id: FileId,
225 record_expr_field: &ast::RecordExprField,
226) -> Option<Assist> {
227 let record_lit = ast::RecordExpr::cast(record_expr_field.syntax().parent()?.parent()?)?;
228 let def_id = sema.resolve_variant(record_lit)?;
229 let module;
230 let def_file_id;
231 let record_fields = match def_id {
232 VariantDef::Struct(s) => {
233 module = s.module(sema.db);
234 let source = s.source(sema.db)?;
235 def_file_id = source.file_id;
236 let fields = source.value.field_list()?;
237 record_field_list(fields)?
238 }
239 VariantDef::Union(u) => {
240 module = u.module(sema.db);
241 let source = u.source(sema.db)?;
242 def_file_id = source.file_id;
243 source.value.record_field_list()?
244 }
245 VariantDef::Variant(e) => {
246 module = e.module(sema.db);
247 let source = e.source(sema.db)?;
248 def_file_id = source.file_id;
249 let fields = source.value.field_list()?;
250 record_field_list(fields)?
251 }
252 };
253 let def_file_id = def_file_id.original_file(sema.db);
254
255 let new_field_type = sema.type_of_expr(&record_expr_field.expr()?)?;
256 if new_field_type.is_unknown() {
257 return None;
258 }
259 let new_field = make::record_field(
260 None,
261 make::name(&record_expr_field.field_name()?.text()),
262 make::ty(&new_field_type.display_source_code(sema.db, module.into()).ok()?),
263 );
264
265 let last_field = record_fields.fields().last()?;
266 let last_field_syntax = last_field.syntax();
267 let indent = IndentLevel::from_node(last_field_syntax);
268
269 let mut new_field = new_field.to_string();
270 if usage_file_id != def_file_id {
271 new_field = format!("pub(crate) {}", new_field);
272 }
273 new_field = format!("\n{}{}", indent, new_field);
274
275 let needs_comma = !last_field_syntax.to_string().ends_with(',');
276 if needs_comma {
277 new_field = format!(",{}", new_field);
278 }
279
280 let source_change = SourceChange::from_text_edit(
281 def_file_id,
282 TextEdit::insert(last_field_syntax.text_range().end(), new_field),
283 );
284 return Some(fix(
285 "create_field",
286 "Create field",
287 source_change,
288 record_expr_field.syntax().text_range(),
289 ));
290
291 fn record_field_list(field_def_list: ast::FieldList) -> Option<ast::RecordFieldList> {
292 match field_def_list {
293 ast::FieldList::RecordFieldList(it) => Some(it),
294 ast::FieldList::TupleFieldList(_) => None,
295 }
296 }
297} 31}
diff --git a/crates/ide/src/diagnostics/fixes/change_case.rs b/crates/ide/src/diagnostics/fixes/change_case.rs
new file mode 100644
index 000000000..42be3375f
--- /dev/null
+++ b/crates/ide/src/diagnostics/fixes/change_case.rs
@@ -0,0 +1,155 @@
1use hir::{db::AstDatabase, diagnostics::IncorrectCase, InFile, Semantics};
2use ide_assists::{Assist, AssistResolveStrategy};
3use ide_db::{base_db::FilePosition, RootDatabase};
4use syntax::AstNode;
5
6use crate::{
7 diagnostics::{unresolved_fix, DiagnosticWithFixes},
8 references::rename::rename_with_semantics,
9};
10
11impl DiagnosticWithFixes for IncorrectCase {
12 fn fixes(
13 &self,
14 sema: &Semantics<RootDatabase>,
15 resolve: &AssistResolveStrategy,
16 ) -> Option<Vec<Assist>> {
17 let root = sema.db.parse_or_expand(self.file)?;
18 let name_node = self.ident.to_node(&root);
19
20 let name_node = InFile::new(self.file, name_node.syntax());
21 let frange = name_node.original_file_range(sema.db);
22 let file_position = FilePosition { file_id: frange.file_id, offset: frange.range.start() };
23
24 let label = format!("Rename to {}", self.suggested_text);
25 let mut res = unresolved_fix("change_case", &label, frange.range);
26 if resolve.should_resolve(&res.id) {
27 let source_change = rename_with_semantics(sema, file_position, &self.suggested_text);
28 res.source_change = Some(source_change.ok().unwrap_or_default());
29 }
30
31 Some(vec![res])
32 }
33}
34
35#[cfg(test)]
36mod change_case {
37 use crate::{
38 diagnostics::tests::{check_fix, check_no_diagnostics},
39 fixture, AssistResolveStrategy, DiagnosticsConfig,
40 };
41
42 #[test]
43 fn test_rename_incorrect_case() {
44 check_fix(
45 r#"
46pub struct test_struct$0 { one: i32 }
47
48pub fn some_fn(val: test_struct) -> test_struct {
49 test_struct { one: val.one + 1 }
50}
51"#,
52 r#"
53pub struct TestStruct { one: i32 }
54
55pub fn some_fn(val: TestStruct) -> TestStruct {
56 TestStruct { one: val.one + 1 }
57}
58"#,
59 );
60
61 check_fix(
62 r#"
63pub fn some_fn(NonSnakeCase$0: u8) -> u8 {
64 NonSnakeCase
65}
66"#,
67 r#"
68pub fn some_fn(non_snake_case: u8) -> u8 {
69 non_snake_case
70}
71"#,
72 );
73
74 check_fix(
75 r#"
76pub fn SomeFn$0(val: u8) -> u8 {
77 if val != 0 { SomeFn(val - 1) } else { val }
78}
79"#,
80 r#"
81pub fn some_fn(val: u8) -> u8 {
82 if val != 0 { some_fn(val - 1) } else { val }
83}
84"#,
85 );
86
87 check_fix(
88 r#"
89fn some_fn() {
90 let whatAWeird_Formatting$0 = 10;
91 another_func(whatAWeird_Formatting);
92}
93"#,
94 r#"
95fn some_fn() {
96 let what_a_weird_formatting = 10;
97 another_func(what_a_weird_formatting);
98}
99"#,
100 );
101 }
102
103 #[test]
104 fn test_uppercase_const_no_diagnostics() {
105 check_no_diagnostics(
106 r#"
107fn foo() {
108 const ANOTHER_ITEM$0: &str = "some_item";
109}
110"#,
111 );
112 }
113
114 #[test]
115 fn test_rename_incorrect_case_struct_method() {
116 check_fix(
117 r#"
118pub struct TestStruct;
119
120impl TestStruct {
121 pub fn SomeFn$0() -> TestStruct {
122 TestStruct
123 }
124}
125"#,
126 r#"
127pub struct TestStruct;
128
129impl TestStruct {
130 pub fn some_fn() -> TestStruct {
131 TestStruct
132 }
133}
134"#,
135 );
136 }
137
138 #[test]
139 fn test_single_incorrect_case_diagnostic_in_function_name_issue_6970() {
140 let input = r#"fn FOO$0() {}"#;
141 let expected = r#"fn foo() {}"#;
142
143 let (analysis, file_position) = fixture::position(input);
144 let diagnostics = analysis
145 .diagnostics(
146 &DiagnosticsConfig::default(),
147 AssistResolveStrategy::All,
148 file_position.file_id,
149 )
150 .unwrap();
151 assert_eq!(diagnostics.len(), 1);
152
153 check_fix(input, expected);
154 }
155}
diff --git a/crates/ide/src/diagnostics/fixes/create_field.rs b/crates/ide/src/diagnostics/fixes/create_field.rs
new file mode 100644
index 000000000..a5f457dce
--- /dev/null
+++ b/crates/ide/src/diagnostics/fixes/create_field.rs
@@ -0,0 +1,156 @@
1use hir::{db::AstDatabase, diagnostics::NoSuchField, HasSource, HirDisplay, Semantics};
2use ide_db::{base_db::FileId, source_change::SourceChange, RootDatabase};
3use syntax::{
4 ast::{self, edit::IndentLevel, make},
5 AstNode,
6};
7use text_edit::TextEdit;
8
9use crate::{
10 diagnostics::{fix, DiagnosticWithFixes},
11 Assist, AssistResolveStrategy,
12};
13impl DiagnosticWithFixes for NoSuchField {
14 fn fixes(
15 &self,
16 sema: &Semantics<RootDatabase>,
17 _resolve: &AssistResolveStrategy,
18 ) -> Option<Vec<Assist>> {
19 let root = sema.db.parse_or_expand(self.file)?;
20 missing_record_expr_field_fixes(
21 &sema,
22 self.file.original_file(sema.db),
23 &self.field.to_node(&root),
24 )
25 }
26}
27
28fn missing_record_expr_field_fixes(
29 sema: &Semantics<RootDatabase>,
30 usage_file_id: FileId,
31 record_expr_field: &ast::RecordExprField,
32) -> Option<Vec<Assist>> {
33 let record_lit = ast::RecordExpr::cast(record_expr_field.syntax().parent()?.parent()?)?;
34 let def_id = sema.resolve_variant(record_lit)?;
35 let module;
36 let def_file_id;
37 let record_fields = match def_id {
38 hir::VariantDef::Struct(s) => {
39 module = s.module(sema.db);
40 let source = s.source(sema.db)?;
41 def_file_id = source.file_id;
42 let fields = source.value.field_list()?;
43 record_field_list(fields)?
44 }
45 hir::VariantDef::Union(u) => {
46 module = u.module(sema.db);
47 let source = u.source(sema.db)?;
48 def_file_id = source.file_id;
49 source.value.record_field_list()?
50 }
51 hir::VariantDef::Variant(e) => {
52 module = e.module(sema.db);
53 let source = e.source(sema.db)?;
54 def_file_id = source.file_id;
55 let fields = source.value.field_list()?;
56 record_field_list(fields)?
57 }
58 };
59 let def_file_id = def_file_id.original_file(sema.db);
60
61 let new_field_type = sema.type_of_expr(&record_expr_field.expr()?)?;
62 if new_field_type.is_unknown() {
63 return None;
64 }
65 let new_field = make::record_field(
66 None,
67 make::name(&record_expr_field.field_name()?.text()),
68 make::ty(&new_field_type.display_source_code(sema.db, module.into()).ok()?),
69 );
70
71 let last_field = record_fields.fields().last()?;
72 let last_field_syntax = last_field.syntax();
73 let indent = IndentLevel::from_node(last_field_syntax);
74
75 let mut new_field = new_field.to_string();
76 if usage_file_id != def_file_id {
77 new_field = format!("pub(crate) {}", new_field);
78 }
79 new_field = format!("\n{}{}", indent, new_field);
80
81 let needs_comma = !last_field_syntax.to_string().ends_with(',');
82 if needs_comma {
83 new_field = format!(",{}", new_field);
84 }
85
86 let source_change = SourceChange::from_text_edit(
87 def_file_id,
88 TextEdit::insert(last_field_syntax.text_range().end(), new_field),
89 );
90
91 return Some(vec![fix(
92 "create_field",
93 "Create field",
94 source_change,
95 record_expr_field.syntax().text_range(),
96 )]);
97
98 fn record_field_list(field_def_list: ast::FieldList) -> Option<ast::RecordFieldList> {
99 match field_def_list {
100 ast::FieldList::RecordFieldList(it) => Some(it),
101 ast::FieldList::TupleFieldList(_) => None,
102 }
103 }
104}
105
106#[cfg(test)]
107mod tests {
108 use crate::diagnostics::tests::check_fix;
109
110 #[test]
111 fn test_add_field_from_usage() {
112 check_fix(
113 r"
114fn main() {
115 Foo { bar: 3, baz$0: false};
116}
117struct Foo {
118 bar: i32
119}
120",
121 r"
122fn main() {
123 Foo { bar: 3, baz: false};
124}
125struct Foo {
126 bar: i32,
127 baz: bool
128}
129",
130 )
131 }
132
133 #[test]
134 fn test_add_field_in_other_file_from_usage() {
135 check_fix(
136 r#"
137//- /main.rs
138mod foo;
139
140fn main() {
141 foo::Foo { bar: 3, $0baz: false};
142}
143//- /foo.rs
144struct Foo {
145 bar: i32
146}
147"#,
148 r#"
149struct Foo {
150 bar: i32,
151 pub(crate) baz: bool
152}
153"#,
154 )
155 }
156}
diff --git a/crates/ide/src/diagnostics/fixes/fill_missing_fields.rs b/crates/ide/src/diagnostics/fixes/fill_missing_fields.rs
new file mode 100644
index 000000000..b5dd64c08
--- /dev/null
+++ b/crates/ide/src/diagnostics/fixes/fill_missing_fields.rs
@@ -0,0 +1,217 @@
1use hir::{db::AstDatabase, diagnostics::MissingFields, Semantics};
2use ide_assists::AssistResolveStrategy;
3use ide_db::{source_change::SourceChange, RootDatabase};
4use syntax::{algo, ast::make, AstNode};
5use text_edit::TextEdit;
6
7use crate::{
8 diagnostics::{fix, fixes::DiagnosticWithFixes},
9 Assist,
10};
11
12impl DiagnosticWithFixes for MissingFields {
13 fn fixes(
14 &self,
15 sema: &Semantics<RootDatabase>,
16 _resolve: &AssistResolveStrategy,
17 ) -> Option<Vec<Assist>> {
18 // Note that although we could add a diagnostics to
19 // fill the missing tuple field, e.g :
20 // `struct A(usize);`
21 // `let a = A { 0: () }`
22 // but it is uncommon usage and it should not be encouraged.
23 if self.missed_fields.iter().any(|it| it.as_tuple_index().is_some()) {
24 return None;
25 }
26
27 let root = sema.db.parse_or_expand(self.file)?;
28 let field_list_parent = self.field_list_parent.to_node(&root);
29 let old_field_list = field_list_parent.record_expr_field_list()?;
30 let new_field_list = old_field_list.clone_for_update();
31 for f in self.missed_fields.iter() {
32 let field =
33 make::record_expr_field(make::name_ref(&f.to_string()), Some(make::expr_unit()))
34 .clone_for_update();
35 new_field_list.add_field(field);
36 }
37
38 let edit = {
39 let mut builder = TextEdit::builder();
40 algo::diff(&old_field_list.syntax(), &new_field_list.syntax())
41 .into_text_edit(&mut builder);
42 builder.finish()
43 };
44 Some(vec![fix(
45 "fill_missing_fields",
46 "Fill struct fields",
47 SourceChange::from_text_edit(self.file.original_file(sema.db), edit),
48 sema.original_range(&field_list_parent.syntax()).range,
49 )])
50 }
51}
52
53#[cfg(test)]
54mod tests {
55 use crate::diagnostics::tests::{check_fix, check_no_diagnostics};
56
57 #[test]
58 fn test_fill_struct_fields_empty() {
59 check_fix(
60 r#"
61struct TestStruct { one: i32, two: i64 }
62
63fn test_fn() {
64 let s = TestStruct {$0};
65}
66"#,
67 r#"
68struct TestStruct { one: i32, two: i64 }
69
70fn test_fn() {
71 let s = TestStruct { one: (), two: () };
72}
73"#,
74 );
75 }
76
77 #[test]
78 fn test_fill_struct_fields_self() {
79 check_fix(
80 r#"
81struct TestStruct { one: i32 }
82
83impl TestStruct {
84 fn test_fn() { let s = Self {$0}; }
85}
86"#,
87 r#"
88struct TestStruct { one: i32 }
89
90impl TestStruct {
91 fn test_fn() { let s = Self { one: () }; }
92}
93"#,
94 );
95 }
96
97 #[test]
98 fn test_fill_struct_fields_enum() {
99 check_fix(
100 r#"
101enum Expr {
102 Bin { lhs: Box<Expr>, rhs: Box<Expr> }
103}
104
105impl Expr {
106 fn new_bin(lhs: Box<Expr>, rhs: Box<Expr>) -> Expr {
107 Expr::Bin {$0 }
108 }
109}
110"#,
111 r#"
112enum Expr {
113 Bin { lhs: Box<Expr>, rhs: Box<Expr> }
114}
115
116impl Expr {
117 fn new_bin(lhs: Box<Expr>, rhs: Box<Expr>) -> Expr {
118 Expr::Bin { lhs: (), rhs: () }
119 }
120}
121"#,
122 );
123 }
124
125 #[test]
126 fn test_fill_struct_fields_partial() {
127 check_fix(
128 r#"
129struct TestStruct { one: i32, two: i64 }
130
131fn test_fn() {
132 let s = TestStruct{ two: 2$0 };
133}
134"#,
135 r"
136struct TestStruct { one: i32, two: i64 }
137
138fn test_fn() {
139 let s = TestStruct{ two: 2, one: () };
140}
141",
142 );
143 }
144
145 #[test]
146 fn test_fill_struct_fields_raw_ident() {
147 check_fix(
148 r#"
149struct TestStruct { r#type: u8 }
150
151fn test_fn() {
152 TestStruct { $0 };
153}
154"#,
155 r"
156struct TestStruct { r#type: u8 }
157
158fn test_fn() {
159 TestStruct { r#type: () };
160}
161",
162 );
163 }
164
165 #[test]
166 fn test_fill_struct_fields_no_diagnostic() {
167 check_no_diagnostics(
168 r#"
169struct TestStruct { one: i32, two: i64 }
170
171fn test_fn() {
172 let one = 1;
173 let s = TestStruct{ one, two: 2 };
174}
175 "#,
176 );
177 }
178
179 #[test]
180 fn test_fill_struct_fields_no_diagnostic_on_spread() {
181 check_no_diagnostics(
182 r#"
183struct TestStruct { one: i32, two: i64 }
184
185fn test_fn() {
186 let one = 1;
187 let s = TestStruct{ ..a };
188}
189"#,
190 );
191 }
192
193 #[test]
194 fn test_fill_struct_fields_blank_line() {
195 check_fix(
196 r#"
197struct S { a: (), b: () }
198
199fn f() {
200 S {
201 $0
202 };
203}
204"#,
205 r#"
206struct S { a: (), b: () }
207
208fn f() {
209 S {
210 a: (),
211 b: (),
212 };
213}
214"#,
215 );
216 }
217}
diff --git a/crates/ide/src/diagnostics/fixes/remove_semicolon.rs b/crates/ide/src/diagnostics/fixes/remove_semicolon.rs
new file mode 100644
index 000000000..f1724d479
--- /dev/null
+++ b/crates/ide/src/diagnostics/fixes/remove_semicolon.rs
@@ -0,0 +1,41 @@
1use hir::{db::AstDatabase, diagnostics::RemoveThisSemicolon, Semantics};
2use ide_assists::{Assist, AssistResolveStrategy};
3use ide_db::{source_change::SourceChange, RootDatabase};
4use syntax::{ast, AstNode};
5use text_edit::TextEdit;
6
7use crate::diagnostics::{fix, DiagnosticWithFixes};
8
9impl DiagnosticWithFixes for RemoveThisSemicolon {
10 fn fixes(
11 &self,
12 sema: &Semantics<RootDatabase>,
13 _resolve: &AssistResolveStrategy,
14 ) -> Option<Vec<Assist>> {
15 let root = sema.db.parse_or_expand(self.file)?;
16
17 let semicolon = self
18 .expr
19 .to_node(&root)
20 .syntax()
21 .parent()
22 .and_then(ast::ExprStmt::cast)
23 .and_then(|expr| expr.semicolon_token())?
24 .text_range();
25
26 let edit = TextEdit::delete(semicolon);
27 let source_change = SourceChange::from_text_edit(self.file.original_file(sema.db), edit);
28
29 Some(vec![fix("remove_semicolon", "Remove this semicolon", source_change, semicolon)])
30 }
31}
32
33#[cfg(test)]
34mod tests {
35 use crate::diagnostics::tests::check_fix;
36
37 #[test]
38 fn remove_semicolon() {
39 check_fix(r#"fn f() -> i32 { 92$0; }"#, r#"fn f() -> i32 { 92 }"#);
40 }
41}
diff --git a/crates/ide/src/diagnostics/fixes/replace_with_find_map.rs b/crates/ide/src/diagnostics/fixes/replace_with_find_map.rs
new file mode 100644
index 000000000..444bf563b
--- /dev/null
+++ b/crates/ide/src/diagnostics/fixes/replace_with_find_map.rs
@@ -0,0 +1,84 @@
1use hir::{db::AstDatabase, diagnostics::ReplaceFilterMapNextWithFindMap, Semantics};
2use ide_assists::{Assist, AssistResolveStrategy};
3use ide_db::{source_change::SourceChange, RootDatabase};
4use syntax::{
5 ast::{self, ArgListOwner},
6 AstNode, TextRange,
7};
8use text_edit::TextEdit;
9
10use crate::diagnostics::{fix, DiagnosticWithFixes};
11
12impl DiagnosticWithFixes for ReplaceFilterMapNextWithFindMap {
13 fn fixes(
14 &self,
15 sema: &Semantics<RootDatabase>,
16 _resolve: &AssistResolveStrategy,
17 ) -> Option<Vec<Assist>> {
18 let root = sema.db.parse_or_expand(self.file)?;
19 let next_expr = self.next_expr.to_node(&root);
20 let next_call = ast::MethodCallExpr::cast(next_expr.syntax().clone())?;
21
22 let filter_map_call = ast::MethodCallExpr::cast(next_call.receiver()?.syntax().clone())?;
23 let filter_map_name_range = filter_map_call.name_ref()?.ident_token()?.text_range();
24 let filter_map_args = filter_map_call.arg_list()?;
25
26 let range_to_replace =
27 TextRange::new(filter_map_name_range.start(), next_expr.syntax().text_range().end());
28 let replacement = format!("find_map{}", filter_map_args.syntax().text());
29 let trigger_range = next_expr.syntax().text_range();
30
31 let edit = TextEdit::replace(range_to_replace, replacement);
32
33 let source_change = SourceChange::from_text_edit(self.file.original_file(sema.db), edit);
34
35 Some(vec![fix(
36 "replace_with_find_map",
37 "Replace filter_map(..).next() with find_map()",
38 source_change,
39 trigger_range,
40 )])
41 }
42}
43
44#[cfg(test)]
45mod tests {
46 use crate::diagnostics::tests::check_fix;
47
48 #[test]
49 fn replace_with_wind_map() {
50 check_fix(
51 r#"
52//- /main.rs crate:main deps:core
53use core::iter::Iterator;
54use core::option::Option::{self, Some, None};
55fn foo() {
56 let m = [1, 2, 3].iter().$0filter_map(|x| if *x == 2 { Some (4) } else { None }).next();
57}
58//- /core/lib.rs crate:core
59pub mod option {
60 pub enum Option<T> { Some(T), None }
61}
62pub mod iter {
63 pub trait Iterator {
64 type Item;
65 fn filter_map<B, F>(self, f: F) -> FilterMap where F: FnMut(Self::Item) -> Option<B> { FilterMap }
66 fn next(&mut self) -> Option<Self::Item>;
67 }
68 pub struct FilterMap {}
69 impl Iterator for FilterMap {
70 type Item = i32;
71 fn next(&mut self) -> i32 { 7 }
72 }
73}
74"#,
75 r#"
76use core::iter::Iterator;
77use core::option::Option::{self, Some, None};
78fn foo() {
79 let m = [1, 2, 3].iter().find_map(|x| if *x == 2 { Some (4) } else { None });
80}
81"#,
82 )
83 }
84}
diff --git a/crates/ide/src/diagnostics/fixes/unresolved_module.rs b/crates/ide/src/diagnostics/fixes/unresolved_module.rs
new file mode 100644
index 000000000..b3d0283bb
--- /dev/null
+++ b/crates/ide/src/diagnostics/fixes/unresolved_module.rs
@@ -0,0 +1,89 @@
1use hir::{db::AstDatabase, diagnostics::UnresolvedModule, Semantics};
2use ide_assists::{Assist, AssistResolveStrategy};
3use ide_db::{base_db::AnchoredPathBuf, source_change::FileSystemEdit, RootDatabase};
4use syntax::AstNode;
5
6use crate::diagnostics::{fix, DiagnosticWithFixes};
7
8impl DiagnosticWithFixes for UnresolvedModule {
9 fn fixes(
10 &self,
11 sema: &Semantics<RootDatabase>,
12 _resolve: &AssistResolveStrategy,
13 ) -> Option<Vec<Assist>> {
14 let root = sema.db.parse_or_expand(self.file)?;
15 let unresolved_module = self.decl.to_node(&root);
16 Some(vec![fix(
17 "create_module",
18 "Create module",
19 FileSystemEdit::CreateFile {
20 dst: AnchoredPathBuf {
21 anchor: self.file.original_file(sema.db),
22 path: self.candidate.clone(),
23 },
24 initial_contents: "".to_string(),
25 }
26 .into(),
27 unresolved_module.syntax().text_range(),
28 )])
29 }
30}
31
32#[cfg(test)]
33mod tests {
34 use expect_test::expect;
35
36 use crate::diagnostics::tests::check_expect;
37
38 #[test]
39 fn test_unresolved_module_diagnostic() {
40 check_expect(
41 r#"mod foo;"#,
42 expect![[r#"
43 [
44 Diagnostic {
45 message: "unresolved module",
46 range: 0..8,
47 severity: Error,
48 fixes: Some(
49 [
50 Assist {
51 id: AssistId(
52 "create_module",
53 QuickFix,
54 ),
55 label: "Create module",
56 group: None,
57 target: 0..8,
58 source_change: Some(
59 SourceChange {
60 source_file_edits: {},
61 file_system_edits: [
62 CreateFile {
63 dst: AnchoredPathBuf {
64 anchor: FileId(
65 0,
66 ),
67 path: "foo.rs",
68 },
69 initial_contents: "",
70 },
71 ],
72 is_snippet: false,
73 },
74 ),
75 },
76 ],
77 ),
78 unused: false,
79 code: Some(
80 DiagnosticCode(
81 "unresolved-module",
82 ),
83 ),
84 },
85 ]
86 "#]],
87 );
88 }
89}
diff --git a/crates/ide/src/diagnostics/fixes/wrap_tail_expr.rs b/crates/ide/src/diagnostics/fixes/wrap_tail_expr.rs
new file mode 100644
index 000000000..715a403b9
--- /dev/null
+++ b/crates/ide/src/diagnostics/fixes/wrap_tail_expr.rs
@@ -0,0 +1,211 @@
1use hir::{db::AstDatabase, diagnostics::MissingOkOrSomeInTailExpr, Semantics};
2use ide_assists::{Assist, AssistResolveStrategy};
3use ide_db::{source_change::SourceChange, RootDatabase};
4use syntax::AstNode;
5use text_edit::TextEdit;
6
7use crate::diagnostics::{fix, DiagnosticWithFixes};
8
9impl DiagnosticWithFixes for MissingOkOrSomeInTailExpr {
10 fn fixes(
11 &self,
12 sema: &Semantics<RootDatabase>,
13 _resolve: &AssistResolveStrategy,
14 ) -> Option<Vec<Assist>> {
15 let root = sema.db.parse_or_expand(self.file)?;
16 let tail_expr = self.expr.to_node(&root);
17 let tail_expr_range = tail_expr.syntax().text_range();
18 let replacement = format!("{}({})", self.required, tail_expr.syntax());
19 let edit = TextEdit::replace(tail_expr_range, replacement);
20 let source_change = SourceChange::from_text_edit(self.file.original_file(sema.db), edit);
21 let name = if self.required == "Ok" { "Wrap with Ok" } else { "Wrap with Some" };
22 Some(vec![fix("wrap_tail_expr", name, source_change, tail_expr_range)])
23 }
24}
25
26#[cfg(test)]
27mod tests {
28 use crate::diagnostics::tests::{check_fix, check_no_diagnostics};
29
30 #[test]
31 fn test_wrap_return_type_option() {
32 check_fix(
33 r#"
34//- /main.rs crate:main deps:core
35use core::option::Option::{self, Some, None};
36
37fn div(x: i32, y: i32) -> Option<i32> {
38 if y == 0 {
39 return None;
40 }
41 x / y$0
42}
43//- /core/lib.rs crate:core
44pub mod result {
45 pub enum Result<T, E> { Ok(T), Err(E) }
46}
47pub mod option {
48 pub enum Option<T> { Some(T), None }
49}
50"#,
51 r#"
52use core::option::Option::{self, Some, None};
53
54fn div(x: i32, y: i32) -> Option<i32> {
55 if y == 0 {
56 return None;
57 }
58 Some(x / y)
59}
60"#,
61 );
62 }
63
64 #[test]
65 fn test_wrap_return_type() {
66 check_fix(
67 r#"
68//- /main.rs crate:main deps:core
69use core::result::Result::{self, Ok, Err};
70
71fn div(x: i32, y: i32) -> Result<i32, ()> {
72 if y == 0 {
73 return Err(());
74 }
75 x / y$0
76}
77//- /core/lib.rs crate:core
78pub mod result {
79 pub enum Result<T, E> { Ok(T), Err(E) }
80}
81pub mod option {
82 pub enum Option<T> { Some(T), None }
83}
84"#,
85 r#"
86use core::result::Result::{self, Ok, Err};
87
88fn div(x: i32, y: i32) -> Result<i32, ()> {
89 if y == 0 {
90 return Err(());
91 }
92 Ok(x / y)
93}
94"#,
95 );
96 }
97
98 #[test]
99 fn test_wrap_return_type_handles_generic_functions() {
100 check_fix(
101 r#"
102//- /main.rs crate:main deps:core
103use core::result::Result::{self, Ok, Err};
104
105fn div<T>(x: T) -> Result<T, i32> {
106 if x == 0 {
107 return Err(7);
108 }
109 $0x
110}
111//- /core/lib.rs crate:core
112pub mod result {
113 pub enum Result<T, E> { Ok(T), Err(E) }
114}
115pub mod option {
116 pub enum Option<T> { Some(T), None }
117}
118"#,
119 r#"
120use core::result::Result::{self, Ok, Err};
121
122fn div<T>(x: T) -> Result<T, i32> {
123 if x == 0 {
124 return Err(7);
125 }
126 Ok(x)
127}
128"#,
129 );
130 }
131
132 #[test]
133 fn test_wrap_return_type_handles_type_aliases() {
134 check_fix(
135 r#"
136//- /main.rs crate:main deps:core
137use core::result::Result::{self, Ok, Err};
138
139type MyResult<T> = Result<T, ()>;
140
141fn div(x: i32, y: i32) -> MyResult<i32> {
142 if y == 0 {
143 return Err(());
144 }
145 x $0/ y
146}
147//- /core/lib.rs crate:core
148pub mod result {
149 pub enum Result<T, E> { Ok(T), Err(E) }
150}
151pub mod option {
152 pub enum Option<T> { Some(T), None }
153}
154"#,
155 r#"
156use core::result::Result::{self, Ok, Err};
157
158type MyResult<T> = Result<T, ()>;
159
160fn div(x: i32, y: i32) -> MyResult<i32> {
161 if y == 0 {
162 return Err(());
163 }
164 Ok(x / y)
165}
166"#,
167 );
168 }
169
170 #[test]
171 fn test_wrap_return_type_not_applicable_when_expr_type_does_not_match_ok_type() {
172 check_no_diagnostics(
173 r#"
174//- /main.rs crate:main deps:core
175use core::result::Result::{self, Ok, Err};
176
177fn foo() -> Result<(), i32> { 0 }
178
179//- /core/lib.rs crate:core
180pub mod result {
181 pub enum Result<T, E> { Ok(T), Err(E) }
182}
183pub mod option {
184 pub enum Option<T> { Some(T), None }
185}
186"#,
187 );
188 }
189
190 #[test]
191 fn test_wrap_return_type_not_applicable_when_return_type_is_not_result_or_option() {
192 check_no_diagnostics(
193 r#"
194//- /main.rs crate:main deps:core
195use core::result::Result::{self, Ok, Err};
196
197enum SomeOtherEnum { Ok(i32), Err(String) }
198
199fn foo() -> SomeOtherEnum { 0 }
200
201//- /core/lib.rs crate:core
202pub mod result {
203 pub enum Result<T, E> { Ok(T), Err(E) }
204}
205pub mod option {
206 pub enum Option<T> { Some(T), None }
207}
208"#,
209 );
210 }
211}
diff --git a/crates/ide/src/diagnostics/unlinked_file.rs b/crates/ide/src/diagnostics/unlinked_file.rs
index 93fd25dea..51fe0f360 100644
--- a/crates/ide/src/diagnostics/unlinked_file.rs
+++ b/crates/ide/src/diagnostics/unlinked_file.rs
@@ -18,7 +18,7 @@ use syntax::{
18use text_edit::TextEdit; 18use text_edit::TextEdit;
19 19
20use crate::{ 20use crate::{
21 diagnostics::{fix, fixes::DiagnosticWithFix}, 21 diagnostics::{fix, fixes::DiagnosticWithFixes},
22 Assist, 22 Assist,
23}; 23};
24 24
@@ -50,13 +50,13 @@ impl Diagnostic for UnlinkedFile {
50 } 50 }
51} 51}
52 52
53impl DiagnosticWithFix for UnlinkedFile { 53impl DiagnosticWithFixes for UnlinkedFile {
54 fn fix( 54 fn fixes(
55 &self, 55 &self,
56 sema: &hir::Semantics<RootDatabase>, 56 sema: &hir::Semantics<RootDatabase>,
57 _resolve: &AssistResolveStrategy, 57 _resolve: &AssistResolveStrategy,
58 ) -> Option<Assist> { 58 ) -> Option<Vec<Assist>> {
59 // If there's an existing module that could add a `mod` item to include the unlinked file, 59 // If there's an existing module that could add `mod` or `pub mod` items to include the unlinked file,
60 // suggest that as a fix. 60 // suggest that as a fix.
61 61
62 let source_root = sema.db.source_root(sema.db.file_source_root(self.file_id)); 62 let source_root = sema.db.source_root(sema.db.file_source_root(self.file_id));
@@ -90,7 +90,7 @@ impl DiagnosticWithFix for UnlinkedFile {
90 } 90 }
91 91
92 if module.origin.file_id() == Some(*parent_id) { 92 if module.origin.file_id() == Some(*parent_id) {
93 return make_fix(sema.db, *parent_id, module_name, self.file_id); 93 return make_fixes(sema.db, *parent_id, module_name, self.file_id);
94 } 94 }
95 } 95 }
96 } 96 }
@@ -101,20 +101,23 @@ impl DiagnosticWithFix for UnlinkedFile {
101 } 101 }
102} 102}
103 103
104fn make_fix( 104fn make_fixes(
105 db: &RootDatabase, 105 db: &RootDatabase,
106 parent_file_id: FileId, 106 parent_file_id: FileId,
107 new_mod_name: &str, 107 new_mod_name: &str,
108 added_file_id: FileId, 108 added_file_id: FileId,
109) -> Option<Assist> { 109) -> Option<Vec<Assist>> {
110 fn is_outline_mod(item: &ast::Item) -> bool { 110 fn is_outline_mod(item: &ast::Item) -> bool {
111 matches!(item, ast::Item::Module(m) if m.item_list().is_none()) 111 matches!(item, ast::Item::Module(m) if m.item_list().is_none())
112 } 112 }
113 113
114 let mod_decl = format!("mod {};", new_mod_name); 114 let mod_decl = format!("mod {};", new_mod_name);
115 let pub_mod_decl = format!("pub mod {};", new_mod_name);
116
115 let ast: ast::SourceFile = db.parse(parent_file_id).tree(); 117 let ast: ast::SourceFile = db.parse(parent_file_id).tree();
116 118
117 let mut builder = TextEdit::builder(); 119 let mut mod_decl_builder = TextEdit::builder();
120 let mut pub_mod_decl_builder = TextEdit::builder();
118 121
119 // If there's an existing `mod m;` statement matching the new one, don't emit a fix (it's 122 // If there's an existing `mod m;` statement matching the new one, don't emit a fix (it's
120 // probably `#[cfg]`d out). 123 // probably `#[cfg]`d out).
@@ -138,30 +141,43 @@ fn make_fix(
138 { 141 {
139 Some(last) => { 142 Some(last) => {
140 cov_mark::hit!(unlinked_file_append_to_existing_mods); 143 cov_mark::hit!(unlinked_file_append_to_existing_mods);
141 builder.insert(last.syntax().text_range().end(), format!("\n{}", mod_decl)); 144 let offset = last.syntax().text_range().end();
145 mod_decl_builder.insert(offset, format!("\n{}", mod_decl));
146 pub_mod_decl_builder.insert(offset, format!("\n{}", pub_mod_decl));
142 } 147 }
143 None => { 148 None => {
144 // Prepend before the first item in the file. 149 // Prepend before the first item in the file.
145 match ast.items().next() { 150 match ast.items().next() {
146 Some(item) => { 151 Some(item) => {
147 cov_mark::hit!(unlinked_file_prepend_before_first_item); 152 cov_mark::hit!(unlinked_file_prepend_before_first_item);
148 builder.insert(item.syntax().text_range().start(), format!("{}\n\n", mod_decl)); 153 let offset = item.syntax().text_range().start();
154 mod_decl_builder.insert(offset, format!("{}\n\n", mod_decl));
155 pub_mod_decl_builder.insert(offset, format!("{}\n\n", pub_mod_decl));
149 } 156 }
150 None => { 157 None => {
151 // No items in the file, so just append at the end. 158 // No items in the file, so just append at the end.
152 cov_mark::hit!(unlinked_file_empty_file); 159 cov_mark::hit!(unlinked_file_empty_file);
153 builder.insert(ast.syntax().text_range().end(), format!("{}\n", mod_decl)); 160 let offset = ast.syntax().text_range().end();
161 mod_decl_builder.insert(offset, format!("{}\n", mod_decl));
162 pub_mod_decl_builder.insert(offset, format!("{}\n", pub_mod_decl));
154 } 163 }
155 } 164 }
156 } 165 }
157 } 166 }
158 167
159 let edit = builder.finish();
160 let trigger_range = db.parse(added_file_id).tree().syntax().text_range(); 168 let trigger_range = db.parse(added_file_id).tree().syntax().text_range();
161 Some(fix( 169 Some(vec![
162 "add_mod_declaration", 170 fix(
163 &format!("Insert `{}`", mod_decl), 171 "add_mod_declaration",
164 SourceChange::from_text_edit(parent_file_id, edit), 172 &format!("Insert `{}`", mod_decl),
165 trigger_range, 173 SourceChange::from_text_edit(parent_file_id, mod_decl_builder.finish()),
166 )) 174 trigger_range,
175 ),
176 fix(
177 "add_pub_mod_declaration",
178 &format!("Insert `{}`", pub_mod_decl),
179 SourceChange::from_text_edit(parent_file_id, pub_mod_decl_builder.finish()),
180 trigger_range,
181 ),
182 ])
167} 183}
diff --git a/crates/ide/src/doc_links.rs b/crates/ide/src/doc_links.rs
index cb5a8e19a..320694a17 100644
--- a/crates/ide/src/doc_links.rs
+++ b/crates/ide/src/doc_links.rs
@@ -29,7 +29,8 @@ pub(crate) type DocumentationLink = String;
29/// Rewrite documentation links in markdown to point to an online host (e.g. docs.rs) 29/// Rewrite documentation links in markdown to point to an online host (e.g. docs.rs)
30pub(crate) fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition) -> String { 30pub(crate) fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition) -> String {
31 let mut cb = broken_link_clone_cb; 31 let mut cb = broken_link_clone_cb;
32 let doc = Parser::new_with_broken_link_callback(markdown, Options::empty(), Some(&mut cb)); 32 let doc =
33 Parser::new_with_broken_link_callback(markdown, Options::ENABLE_TASKLISTS, Some(&mut cb));
33 34
34 let doc = map_links(doc, |target, title: &str| { 35 let doc = map_links(doc, |target, title: &str| {
35 // This check is imperfect, there's some overlap between valid intra-doc links 36 // This check is imperfect, there's some overlap between valid intra-doc links
@@ -64,8 +65,7 @@ pub(crate) fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Defi
64pub(crate) fn remove_links(markdown: &str) -> String { 65pub(crate) fn remove_links(markdown: &str) -> String {
65 let mut drop_link = false; 66 let mut drop_link = false;
66 67
67 let mut opts = Options::empty(); 68 let opts = Options::ENABLE_TASKLISTS | Options::ENABLE_FOOTNOTES;
68 opts.insert(Options::ENABLE_FOOTNOTES);
69 69
70 let mut cb = |_: BrokenLink| { 70 let mut cb = |_: BrokenLink| {
71 let empty = InlineStr::try_from("").unwrap(); 71 let empty = InlineStr::try_from("").unwrap();
@@ -123,7 +123,7 @@ pub(crate) fn extract_definitions_from_markdown(
123) -> Vec<(TextRange, String, Option<hir::Namespace>)> { 123) -> Vec<(TextRange, String, Option<hir::Namespace>)> {
124 Parser::new_with_broken_link_callback( 124 Parser::new_with_broken_link_callback(
125 markdown, 125 markdown,
126 Options::empty(), 126 Options::ENABLE_TASKLISTS,
127 Some(&mut broken_link_clone_cb), 127 Some(&mut broken_link_clone_cb),
128 ) 128 )
129 .into_offset_iter() 129 .into_offset_iter()
diff --git a/crates/ide/src/join_lines.rs b/crates/ide/src/join_lines.rs
index 61dcbb399..c67ccd1a9 100644
--- a/crates/ide/src/join_lines.rs
+++ b/crates/ide/src/join_lines.rs
@@ -4,7 +4,7 @@ use ide_assists::utils::extract_trivial_expression;
4use itertools::Itertools; 4use itertools::Itertools;
5use syntax::{ 5use syntax::{
6 algo::non_trivia_sibling, 6 algo::non_trivia_sibling,
7 ast::{self, AstNode, AstToken}, 7 ast::{self, AstNode, AstToken, IsString},
8 Direction, NodeOrToken, SourceFile, 8 Direction, NodeOrToken, SourceFile,
9 SyntaxKind::{self, USE_TREE, WHITESPACE},