diff options
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]] |
439 | name = "fsevent-sys" | 439 | name = "fsevent-sys" |
440 | version = "3.0.2" | 440 | version = "3.1.0" |
441 | source = "registry+https://github.com/rust-lang/crates.io-index" | 441 | source = "registry+https://github.com/rust-lang/crates.io-index" |
442 | checksum = "77a29c77f1ca394c3e73a9a5d24cfcabb734682d9634fc398f2204a63c994120" | 442 | checksum = "ca6f5e6817058771c10f0eb0f05ddf1e35844266f972004fe8e4b21fda295bd5" |
443 | dependencies = [ | 443 | dependencies = [ |
444 | "libc", | 444 | "libc", |
445 | ] | 445 | ] |
446 | 446 | ||
447 | [[package]] | 447 | [[package]] |
448 | name = "fst" | 448 | name = "fst" |
449 | version = "0.4.5" | 449 | version = "0.4.6" |
450 | source = "registry+https://github.com/rust-lang/crates.io-index" | 450 | source = "registry+https://github.com/rust-lang/crates.io-index" |
451 | checksum = "d79238883cf0307100b90aba4a755d8051a3182305dfe7f649a1e9dc0517006f" | 451 | checksum = "e398fae362f4124bbe630d99519fb2d68a03e2e3a23b441028cdcdc4f4895687" |
452 | 452 | ||
453 | [[package]] | 453 | [[package]] |
454 | name = "gimli" | 454 | name = "gimli" |
@@ -939,11 +939,10 @@ dependencies = [ | |||
939 | 939 | ||
940 | [[package]] | 940 | [[package]] |
941 | name = "notify" | 941 | name = "notify" |
942 | version = "5.0.0-pre.7" | 942 | version = "5.0.0-pre.8" |
943 | source = "registry+https://github.com/rust-lang/crates.io-index" | 943 | source = "registry+https://github.com/rust-lang/crates.io-index" |
944 | checksum = "1ebe7699a0f8c5759450716ee03d231685c22b4fe8f406c42c22e0ad94d40ce7" | 944 | checksum = "46bbbcd078f1f00ddb7a9abe70b96e91229b44b0b3afdec610f8e5137f8f014b" |
945 | dependencies = [ | 945 | dependencies = [ |
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]] |
1297 | name = "rowan" | 1296 | name = "rowan" |
1298 | version = "0.13.0-pre.5" | 1297 | version = "0.13.0-pre.6" |
1299 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1298 | source = "registry+https://github.com/rust-lang/crates.io-index" |
1300 | checksum = "32a5fc82ed0b7e7fba157331f0d8f64abd73bced6e7ac2a4dfa0c4cf0ab584e8" | 1299 | checksum = "82ccc04e145e9a5ab51b9c12a81d77c4a8250d87a407ab02ac650451141ff00d" |
1301 | dependencies = [ | 1300 | dependencies = [ |
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 | }; |
53 | use hir_expand::{diagnostics::DiagnosticSink, name::name, MacroDefKind}; | 53 | use hir_expand::{diagnostics::DiagnosticSink, name::name, MacroDefKind}; |
54 | use hir_ty::{ | 54 | use 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; | |||
21 | use rustc_hash::FxHashMap; | 21 | use rustc_hash::FxHashMap; |
22 | use syntax::{ast, AstNode, AstPtr}; | 22 | use syntax::{ast, AstNode, AstPtr}; |
23 | 23 | ||
24 | pub use lower::LowerCtx; | ||
25 | |||
26 | use crate::{ | 24 | use 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 | ||
36 | pub 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 { | |||
1006 | impl From<ast::LiteralKind> for Literal { | 1006 | impl 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; | |||
5 | use hir_expand::name::{known, AsName, Name}; | 5 | use hir_expand::name::{known, AsName, Name}; |
6 | use rustc_hash::FxHashSet; | 6 | use rustc_hash::FxHashSet; |
7 | 7 | ||
8 | use crate::nameres::DefMap; | ||
9 | use crate::{ | 8 | use 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] |
414 | fn 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] | ||
420 | extern crate self as bla; | ||
421 | "#, | ||
422 | expect![[r#" | ||
423 | crate | ||
424 | bla: t | ||
425 | "#]], | ||
426 | ); | ||
427 | } | ||
428 | |||
429 | #[test] | ||
414 | fn reexport_across_crates() { | 430 | fn 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 | ||
4 | use hir_expand::{name::Name, AstId, InFile}; | 4 | use hir_expand::{name::Name, AstId, InFile}; |
5 | use std::convert::TryInto; | ||
5 | use syntax::ast; | 6 | use syntax::ast; |
6 | 7 | ||
7 | use crate::{body::LowerCtx, path::Path}; | 8 | use 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)] | ||
316 | pub 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 | |||
329 | impl 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 | |||
338 | impl 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 | ||
377 | fn 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 | |||
376 | fn relative_file( | 399 | fn 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 | |||
3 | use std::convert::TryInto; | ||
4 | |||
5 | use hir_def::{ | ||
6 | builtin_type::BuiltinUint, | ||
7 | expr::{Expr, Literal}, | ||
8 | type_ref::ConstScalar, | ||
9 | }; | ||
10 | |||
11 | use crate::{Const, ConstData, ConstValue, Interner, TyKind}; | ||
12 | |||
13 | /// Extension trait for [`Const`] | ||
14 | pub trait ConstExt { | ||
15 | /// Is a [`Const`] unknown? | ||
16 | fn is_unknown(&self) -> bool; | ||
17 | } | ||
18 | |||
19 | impl 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 | ||
39 | pub 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 | ||
48 | pub 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)] | ||
5 | pub 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 | |||
14 | impl 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 @@ | |||
3 | use std::iter::{repeat, repeat_with}; | 3 | use std::iter::{repeat, repeat_with}; |
4 | use std::{mem, sync::Arc}; | 4 | use std::{mem, sync::Arc}; |
5 | 5 | ||
6 | use chalk_ir::{cast::Cast, fold::Shift, ConstData, Mutability, TyVariableKind}; | 6 | use chalk_ir::{cast::Cast, fold::Shift, Mutability, TyVariableKind}; |
7 | use hir_def::{ | 7 | use 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; | |||
15 | use syntax::ast::RangeOp; | 15 | use syntax::ast::RangeOp; |
16 | 16 | ||
17 | use crate::{ | 17 | use 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 | ||
4 | use crate::{chalk_db, consts::ConstScalar, tls, GenericArg}; | 4 | use crate::{chalk_db, tls, GenericArg}; |
5 | use base_db::salsa::InternId; | 5 | use base_db::salsa::InternId; |
6 | use chalk_ir::{Goal, GoalData}; | 6 | use chalk_ir::{Goal, GoalData}; |
7 | use hir_def::{ | 7 | use 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 | }; |
11 | use smallvec::SmallVec; | 12 | use 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; | |||
10 | mod builder; | 10 | mod builder; |
11 | mod chalk_db; | 11 | mod chalk_db; |
12 | mod chalk_ext; | 12 | mod chalk_ext; |
13 | pub mod consteval; | ||
13 | mod infer; | 14 | mod infer; |
14 | mod interner; | 15 | mod interner; |
15 | mod consts; | ||
16 | mod lower; | 16 | mod lower; |
17 | mod mapping; | 17 | mod mapping; |
18 | mod op; | 18 | mod op; |
@@ -38,9 +38,13 @@ use chalk_ir::{ | |||
38 | interner::HasInterner, | 38 | interner::HasInterner, |
39 | UintTy, | 39 | UintTy, |
40 | }; | 40 | }; |
41 | use hir_def::{expr::ExprId, type_ref::Rawness, TypeParamId}; | 41 | use hir_def::{ |
42 | expr::ExprId, | ||
43 | type_ref::{ConstScalar, Rawness}, | ||
44 | TypeParamId, | ||
45 | }; | ||
42 | 46 | ||
43 | use crate::{consts::ConstScalar, db::HirDatabase, display::HirDisplay, utils::generics}; | 47 | use crate::{db::HirDatabase, display::HirDisplay, utils::generics}; |
44 | 48 | ||
45 | pub use autoderef::autoderef; | 49 | pub use autoderef::autoderef; |
46 | pub use builder::TyBuilder; | 50 | pub 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; | |||
29 | use syntax::ast; | 29 | use syntax::ast; |
30 | 30 | ||
31 | use crate::{ | 31 | use 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\ | ||
494 | h"; | ||
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] | ||
3479 | fn array_length() { | ||
3480 | check_infer( | ||
3481 | r#" | ||
3482 | trait T { | ||
3483 | type Output; | ||
3484 | fn do_thing(&self) -> Self::Output; | ||
3485 | } | ||
3486 | |||
3487 | impl T for [u8; 4] { | ||
3488 | type Output = usize; | ||
3489 | fn do_thing(&self) -> Self::Output { | ||
3490 | 2 | ||
3491 | } | ||
3492 | } | ||
3493 | |||
3494 | impl T for [u8; 2] { | ||
3495 | type Output = u8; | ||
3496 | fn do_thing(&self) -> Self::Output { | ||
3497 | 2 | ||
3498 | } | ||
3499 | } | ||
3500 | |||
3501 | fn 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] | ||
3537 | fn const_generics() { | ||
3538 | check_infer( | ||
3539 | r#" | ||
3540 | trait T { | ||
3541 | type Output; | ||
3542 | fn do_thing(&self) -> Self::Output; | ||
3543 | } | ||
3544 | |||
3545 | impl<const L: usize> T for [u8; L] { | ||
3546 | type Output = [u8; L]; | ||
3547 | fn do_thing(&self) -> Self::Output { | ||
3548 | *self | ||
3549 | } | ||
3550 | } | ||
3551 | |||
3552 | fn 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 | ||
29 | use crate::{Assist, AssistId, AssistKind, FileId, Label, SourceChange}; | 29 | use crate::{Assist, AssistId, AssistKind, FileId, Label, SourceChange}; |
30 | 30 | ||
31 | use self::fixes::DiagnosticWithFix; | 31 | use self::fixes::DiagnosticWithFixes; |
32 | 32 | ||
33 | #[derive(Debug)] | 33 | #[derive(Debug)] |
34 | pub struct Diagnostic { | 34 | pub 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 | ||
44 | impl Diagnostic { | 44 | impl 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 | ||
213 | fn diagnostic_with_fix<D: DiagnosticWithFix>( | 213 | fn 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 | ||
223 | fn warning_with_fix<D: DiagnosticWithFix>( | 223 | fn 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 | ||
391 | use core::option::Option::{self, Some, None}; | ||
392 | |||
393 | fn 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 | ||
400 | pub mod result { | ||
401 | pub enum Result<T, E> { Ok(T), Err(E) } | ||
402 | } | ||
403 | pub mod option { | ||
404 | pub enum Option<T> { Some(T), None } | ||
405 | } | ||
406 | "#, | ||
407 | r#" | ||
408 | use core::option::Option::{self, Some, None}; | ||
409 | |||
410 | fn 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 | ||
425 | use core::result::Result::{self, Ok, Err}; | ||
426 | |||
427 | fn 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 | ||
434 | pub mod result { | ||
435 | pub enum Result<T, E> { Ok(T), Err(E) } | ||
436 | } | ||
437 | pub mod option { | ||
438 | pub enum Option<T> { Some(T), None } | ||
439 | } | ||
440 | "#, | ||
441 | r#" | ||
442 | use core::result::Result::{self, Ok, Err}; | ||
443 | |||
444 | fn 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 | ||
459 | use core::result::Result::{self, Ok, Err}; | ||
460 | |||
461 | fn 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 | ||
468 | pub mod result { | ||
469 | pub enum Result<T, E> { Ok(T), Err(E) } | ||
470 | } | ||
471 | pub mod option { | ||
472 | pub enum Option<T> { Some(T), None } | ||
473 | } | ||
474 | "#, | ||
475 | r#" | ||
476 | use core::result::Result::{self, Ok, Err}; | ||
477 | |||
478 | fn 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 | ||
493 | use core::result::Result::{self, Ok, Err}; | ||
494 | |||
495 | type MyResult<T> = Result<T, ()>; | ||
496 | |||
497 | fn 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 | ||
504 | pub mod result { | ||
505 | pub enum Result<T, E> { Ok(T), Err(E) } | ||
506 | } | ||
507 | pub mod option { | ||
508 | pub enum Option<T> { Some(T), None } | ||
509 | } | ||
510 | "#, | ||
511 | r#" | ||
512 | use core::result::Result::{self, Ok, Err}; | ||
513 | |||
514 | type MyResult<T> = Result<T, ()>; | ||
515 | |||
516 | fn 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 | ||
531 | use core::result::Result::{self, Ok, Err}; | ||
532 | |||
533 | fn foo() -> Result<(), i32> { 0 } | ||
534 | |||
535 | //- /core/lib.rs crate:core | ||
536 | pub mod result { | ||
537 | pub enum Result<T, E> { Ok(T), Err(E) } | ||
538 | } | ||
539 | pub 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 | ||
551 | use core::result::Result::{self, Ok, Err}; | ||
552 | |||
553 | enum SomeOtherEnum { Ok(i32), Err(String) } | ||
554 | |||
555 | fn foo() -> SomeOtherEnum { 0 } | ||
556 | |||
557 | //- /core/lib.rs crate:core | ||
558 | pub mod result { | ||
559 | pub enum Result<T, E> { Ok(T), Err(E) } | ||
560 | } | ||
561 | pub 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#" | ||
572 | struct TestStruct { one: i32, two: i64 } | ||
573 | |||
574 | fn test_fn() { | ||
575 | let s = TestStruct {$0}; | ||
576 | } | ||
577 | "#, | ||
578 | r#" | ||
579 | struct TestStruct { one: i32, two: i64 } | ||
580 | |||
581 | fn 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#" | ||
592 | struct TestStruct { one: i32 } | ||
593 | |||
594 | impl TestStruct { | ||
595 | fn test_fn() { let s = Self {$0}; } | ||
596 | } | ||
597 | "#, | ||
598 | r#" | ||
599 | struct TestStruct { one: i32 } | ||
600 | |||
601 | impl 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#" | ||
612 | enum Expr { | ||
613 | Bin { lhs: Box<Expr>, rhs: Box<Expr> } | ||
614 | } | ||
615 | |||
616 | impl Expr { | ||
617 | fn new_bin(lhs: Box<Expr>, rhs: Box<Expr>) -> Expr { | ||
618 | Expr::Bin {$0 } | ||
619 | } | ||
620 | } | ||
621 | "#, | ||
622 | r#" | ||
623 | enum Expr { | ||
624 | Bin { lhs: Box<Expr>, rhs: Box<Expr> } | ||
625 | } | ||
626 | |||
627 | impl 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#" | ||
640 | struct TestStruct { one: i32, two: i64 } | ||
641 | |||
642 | fn test_fn() { | ||
643 | let s = TestStruct{ two: 2$0 }; | ||
644 | } | ||
645 | "#, | ||
646 | r" | ||
647 | struct TestStruct { one: i32, two: i64 } | ||
648 | |||
649 | fn 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#" | ||
660 | struct TestStruct { r#type: u8 } | ||
661 | |||
662 | fn test_fn() { | ||
663 | TestStruct { $0 }; | ||
664 | } | ||
665 | "#, | ||
666 | r" | ||
667 | struct TestStruct { r#type: u8 } | ||
668 | |||
669 | fn 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() { | |||
792 | pub struct Foo { pub a: i32, pub b: i32 } | 437 | pub struct Foo { pub a: i32, pub b: i32 } |
793 | "#, | 438 | "#, |
794 | r#" | 439 | r#" |
795 | fn some(, b: ()) {} | 440 | fn some(, b: () ) {} |
796 | fn items() {} | 441 | fn items() {} |
797 | fn here() {} | 442 | fn 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" | ||
897 | fn main() { | ||
898 | Foo { bar: 3, baz$0: false}; | ||
899 | } | ||
900 | struct Foo { | ||
901 | bar: i32 | ||
902 | } | ||
903 | ", | ||
904 | r" | ||
905 | fn main() { | ||
906 | Foo { bar: 3, baz: false}; | ||
907 | } | ||
908 | struct 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 | ||
921 | mod foo; | ||
922 | |||
923 | fn main() { | ||
924 | foo::Foo { bar: 3, $0baz: false}; | ||
925 | } | ||
926 | //- /foo.rs | ||
927 | struct Foo { | ||
928 | bar: i32 | ||
929 | } | ||
930 | "#, | ||
931 | r#" | ||
932 | struct 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#" | ||
961 | pub struct test_struct$0 { one: i32 } | ||
962 | |||
963 | pub fn some_fn(val: test_struct) -> test_struct { | ||
964 | test_struct { one: val.one + 1 } | ||
965 | } | ||
966 | "#, | ||
967 | r#" | ||
968 | pub struct TestStruct { one: i32 } | ||
969 | |||
970 | pub fn some_fn(val: TestStruct) -> TestStruct { | ||
971 | TestStruct { one: val.one + 1 } | ||
972 | } | ||
973 | "#, | ||
974 | ); | ||
975 | |||
976 | check_fix( | ||
977 | r#" | ||
978 | pub fn some_fn(NonSnakeCase$0: u8) -> u8 { | ||
979 | NonSnakeCase | ||
980 | } | ||
981 | "#, | ||
982 | r#" | ||
983 | pub fn some_fn(non_snake_case: u8) -> u8 { | ||
984 | non_snake_case | ||
985 | } | ||
986 | "#, | ||
987 | ); | ||
988 | |||
989 | check_fix( | ||
990 | r#" | ||
991 | pub fn SomeFn$0(val: u8) -> u8 { | ||
992 | if val != 0 { SomeFn(val - 1) } else { val } | ||
993 | } | ||
994 | "#, | ||
995 | r#" | ||
996 | pub 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#" | ||
1004 | fn some_fn() { | ||
1005 | let whatAWeird_Formatting$0 = 10; | ||
1006 | another_func(whatAWeird_Formatting); | ||
1007 | } | ||
1008 | "#, | ||
1009 | r#" | ||
1010 | fn 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#" | ||
1022 | fn 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#" | ||
1033 | pub struct TestStruct; | ||
1034 | |||
1035 | impl TestStruct { | ||
1036 | pub fn SomeFn$0() -> TestStruct { | ||
1037 | TestStruct | ||
1038 | } | ||
1039 | } | ||
1040 | "#, | ||
1041 | r#" | ||
1042 | pub struct TestStruct; | ||
1043 | |||
1044 | impl 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 |
1077 | fn f() {} | 562 | fn f() {} |
1078 | //- /foo.rs | 563 | //- /foo.rs |
1079 | $0 | 564 | $0 |
1080 | "#, | 565 | "#, |
1081 | r#" | 566 | vec![ |
567 | r#" | ||
1082 | mod foo; | 568 | mod foo; |
1083 | 569 | ||
1084 | fn f() {} | 570 | fn f() {} |
1085 | "#, | 571 | "#, |
572 | r#" | ||
573 | pub mod foo; | ||
574 | |||
575 | fn 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. |
3 | use hir::{ | 3 | mod change_case; |
4 | db::AstDatabase, | 4 | mod create_field; |
5 | diagnostics::{ | 5 | mod fill_missing_fields; |
6 | Diagnostic, IncorrectCase, MissingFields, MissingOkOrSomeInTailExpr, NoSuchField, | 6 | mod remove_semicolon; |
7 | RemoveThisSemicolon, ReplaceFilterMapNextWithFindMap, UnresolvedModule, | 7 | mod replace_with_find_map; |
8 | }, | 8 | mod unresolved_module; |
9 | HasSource, HirDisplay, InFile, Semantics, VariantDef, | 9 | mod wrap_tail_expr; |
10 | }; | 10 | |
11 | use hir::{diagnostics::Diagnostic, Semantics}; | ||
11 | use ide_assists::AssistResolveStrategy; | 12 | use ide_assists::AssistResolveStrategy; |
12 | use ide_db::{ | 13 | use ide_db::RootDatabase; |
13 | base_db::{AnchoredPathBuf, FileId}, | ||
14 | source_change::{FileSystemEdit, SourceChange}, | ||
15 | RootDatabase, | ||
16 | }; | ||
17 | use syntax::{ | ||
18 | algo, | ||
19 | ast::{self, edit::IndentLevel, make, ArgListOwner}, | ||
20 | AstNode, TextRange, | ||
21 | }; | ||
22 | use text_edit::TextEdit; | ||
23 | 14 | ||
24 | use crate::{ | 15 | use 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 |
33 | pub(crate) trait DiagnosticWithFix: Diagnostic { | 20 | pub(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 | |||
46 | impl 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 | |||
70 | impl 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 | |||
85 | impl 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 | |||
125 | impl 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 | |||
142 | impl 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 | |||
166 | impl 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 | |||
190 | impl 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 | |||
222 | fn 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 @@ | |||
1 | use hir::{db::AstDatabase, diagnostics::IncorrectCase, InFile, Semantics}; | ||
2 | use ide_assists::{Assist, AssistResolveStrategy}; | ||
3 | use ide_db::{base_db::FilePosition, RootDatabase}; | ||
4 | use syntax::AstNode; | ||
5 | |||
6 | use crate::{ | ||
7 | diagnostics::{unresolved_fix, DiagnosticWithFixes}, | ||
8 | references::rename::rename_with_semantics, | ||
9 | }; | ||
10 | |||
11 | impl 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)] | ||
36 | mod 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#" | ||
46 | pub struct test_struct$0 { one: i32 } | ||
47 | |||
48 | pub fn some_fn(val: test_struct) -> test_struct { | ||
49 | test_struct { one: val.one + 1 } | ||
50 | } | ||
51 | "#, | ||
52 | r#" | ||
53 | pub struct TestStruct { one: i32 } | ||
54 | |||
55 | pub fn some_fn(val: TestStruct) -> TestStruct { | ||
56 | TestStruct { one: val.one + 1 } | ||
57 | } | ||
58 | "#, | ||
59 | ); | ||
60 | |||
61 | check_fix( | ||
62 | r#" | ||
63 | pub fn some_fn(NonSnakeCase$0: u8) -> u8 { | ||
64 | NonSnakeCase | ||
65 | } | ||
66 | "#, | ||
67 | r#" | ||
68 | pub fn some_fn(non_snake_case: u8) -> u8 { | ||
69 | non_snake_case | ||
70 | } | ||
71 | "#, | ||
72 | ); | ||
73 | |||
74 | check_fix( | ||
75 | r#" | ||
76 | pub fn SomeFn$0(val: u8) -> u8 { | ||
77 | if val != 0 { SomeFn(val - 1) } else { val } | ||
78 | } | ||
79 | "#, | ||
80 | r#" | ||
81 | pub 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#" | ||
89 | fn some_fn() { | ||
90 | let whatAWeird_Formatting$0 = 10; | ||
91 | another_func(whatAWeird_Formatting); | ||
92 | } | ||
93 | "#, | ||
94 | r#" | ||
95 | fn 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#" | ||
107 | fn 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#" | ||
118 | pub struct TestStruct; | ||
119 | |||
120 | impl TestStruct { | ||
121 | pub fn SomeFn$0() -> TestStruct { | ||
122 | TestStruct | ||
123 | } | ||
124 | } | ||
125 | "#, | ||
126 | r#" | ||
127 | pub struct TestStruct; | ||
128 | |||
129 | impl 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 @@ | |||
1 | use hir::{db::AstDatabase, diagnostics::NoSuchField, HasSource, HirDisplay, Semantics}; | ||
2 | use ide_db::{base_db::FileId, source_change::SourceChange, RootDatabase}; | ||
3 | use syntax::{ | ||
4 | ast::{self, edit::IndentLevel, make}, | ||
5 | AstNode, | ||
6 | }; | ||
7 | use text_edit::TextEdit; | ||
8 | |||
9 | use crate::{ | ||
10 | diagnostics::{fix, DiagnosticWithFixes}, | ||
11 | Assist, AssistResolveStrategy, | ||
12 | }; | ||
13 | impl 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 | |||
28 | fn 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)] | ||
107 | mod tests { | ||
108 | use crate::diagnostics::tests::check_fix; | ||
109 | |||
110 | #[test] | ||
111 | fn test_add_field_from_usage() { | ||
112 | check_fix( | ||
113 | r" | ||
114 | fn main() { | ||
115 | Foo { bar: 3, baz$0: false}; | ||
116 | } | ||
117 | struct Foo { | ||
118 | bar: i32 | ||
119 | } | ||
120 | ", | ||
121 | r" | ||
122 | fn main() { | ||
123 | Foo { bar: 3, baz: false}; | ||
124 | } | ||
125 | struct 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 | ||
138 | mod foo; | ||
139 | |||
140 | fn main() { | ||
141 | foo::Foo { bar: 3, $0baz: false}; | ||
142 | } | ||
143 | //- /foo.rs | ||
144 | struct Foo { | ||
145 | bar: i32 | ||
146 | } | ||
147 | "#, | ||
148 | r#" | ||
149 | struct 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 @@ | |||
1 | use hir::{db::AstDatabase, diagnostics::MissingFields, Semantics}; | ||
2 | use ide_assists::AssistResolveStrategy; | ||
3 | use ide_db::{source_change::SourceChange, RootDatabase}; | ||
4 | use syntax::{algo, ast::make, AstNode}; | ||
5 | use text_edit::TextEdit; | ||
6 | |||
7 | use crate::{ | ||
8 | diagnostics::{fix, fixes::DiagnosticWithFixes}, | ||
9 | Assist, | ||
10 | }; | ||
11 | |||
12 | impl 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)] | ||
54 | mod 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#" | ||
61 | struct TestStruct { one: i32, two: i64 } | ||
62 | |||
63 | fn test_fn() { | ||
64 | let s = TestStruct {$0}; | ||
65 | } | ||
66 | "#, | ||
67 | r#" | ||
68 | struct TestStruct { one: i32, two: i64 } | ||
69 | |||
70 | fn 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#" | ||
81 | struct TestStruct { one: i32 } | ||
82 | |||
83 | impl TestStruct { | ||
84 | fn test_fn() { let s = Self {$0}; } | ||
85 | } | ||
86 | "#, | ||
87 | r#" | ||
88 | struct TestStruct { one: i32 } | ||
89 | |||
90 | impl 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#" | ||
101 | enum Expr { | ||
102 | Bin { lhs: Box<Expr>, rhs: Box<Expr> } | ||
103 | } | ||
104 | |||
105 | impl Expr { | ||
106 | fn new_bin(lhs: Box<Expr>, rhs: Box<Expr>) -> Expr { | ||
107 | Expr::Bin {$0 } | ||
108 | } | ||
109 | } | ||
110 | "#, | ||
111 | r#" | ||
112 | enum Expr { | ||
113 | Bin { lhs: Box<Expr>, rhs: Box<Expr> } | ||
114 | } | ||
115 | |||
116 | impl 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#" | ||
129 | struct TestStruct { one: i32, two: i64 } | ||
130 | |||
131 | fn test_fn() { | ||
132 | let s = TestStruct{ two: 2$0 }; | ||
133 | } | ||
134 | "#, | ||
135 | r" | ||
136 | struct TestStruct { one: i32, two: i64 } | ||
137 | |||
138 | fn 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#" | ||
149 | struct TestStruct { r#type: u8 } | ||
150 | |||
151 | fn test_fn() { | ||
152 | TestStruct { $0 }; | ||
153 | } | ||
154 | "#, | ||
155 | r" | ||
156 | struct TestStruct { r#type: u8 } | ||
157 | |||
158 | fn 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#" | ||
169 | struct TestStruct { one: i32, two: i64 } | ||
170 | |||
171 | fn 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#" | ||
183 | struct TestStruct { one: i32, two: i64 } | ||
184 | |||
185 | fn 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#" | ||
197 | struct S { a: (), b: () } | ||
198 | |||
199 | fn f() { | ||
200 | S { | ||
201 | $0 | ||
202 | }; | ||
203 | } | ||
204 | "#, | ||
205 | r#" | ||
206 | struct S { a: (), b: () } | ||
207 | |||
208 | fn 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 @@ | |||
1 | use hir::{db::AstDatabase, diagnostics::RemoveThisSemicolon, Semantics}; | ||
2 | use ide_assists::{Assist, AssistResolveStrategy}; | ||
3 | use ide_db::{source_change::SourceChange, RootDatabase}; | ||
4 | use syntax::{ast, AstNode}; | ||
5 | use text_edit::TextEdit; | ||
6 | |||
7 | use crate::diagnostics::{fix, DiagnosticWithFixes}; | ||
8 | |||
9 | impl 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)] | ||
34 | mod 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 @@ | |||
1 | use hir::{db::AstDatabase, diagnostics::ReplaceFilterMapNextWithFindMap, Semantics}; | ||
2 | use ide_assists::{Assist, AssistResolveStrategy}; | ||
3 | use ide_db::{source_change::SourceChange, RootDatabase}; | ||
4 | use syntax::{ | ||
5 | ast::{self, ArgListOwner}, | ||
6 | AstNode, TextRange, | ||
7 | }; | ||
8 | use text_edit::TextEdit; | ||
9 | |||
10 | use crate::diagnostics::{fix, DiagnosticWithFixes}; | ||
11 | |||
12 | impl 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)] | ||
45 | mod 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 | ||
53 | use core::iter::Iterator; | ||
54 | use core::option::Option::{self, Some, None}; | ||
55 | fn 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 | ||
59 | pub mod option { | ||
60 | pub enum Option<T> { Some(T), None } | ||
61 | } | ||
62 | pub 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#" | ||
76 | use core::iter::Iterator; | ||
77 | use core::option::Option::{self, Some, None}; | ||
78 | fn 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 @@ | |||
1 | use hir::{db::AstDatabase, diagnostics::UnresolvedModule, Semantics}; | ||
2 | use ide_assists::{Assist, AssistResolveStrategy}; | ||
3 | use ide_db::{base_db::AnchoredPathBuf, source_change::FileSystemEdit, RootDatabase}; | ||
4 | use syntax::AstNode; | ||
5 | |||
6 | use crate::diagnostics::{fix, DiagnosticWithFixes}; | ||
7 | |||
8 | impl 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)] | ||
33 | mod 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 @@ | |||
1 | use hir::{db::AstDatabase, diagnostics::MissingOkOrSomeInTailExpr, Semantics}; | ||
2 | use ide_assists::{Assist, AssistResolveStrategy}; | ||
3 | use ide_db::{source_change::SourceChange, RootDatabase}; | ||
4 | use syntax::AstNode; | ||
5 | use text_edit::TextEdit; | ||
6 | |||
7 | use crate::diagnostics::{fix, DiagnosticWithFixes}; | ||
8 | |||
9 | impl 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)] | ||
27 | mod 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 | ||
35 | use core::option::Option::{self, Some, None}; | ||
36 | |||
37 | fn 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 | ||
44 | pub mod result { | ||
45 | pub enum Result<T, E> { Ok(T), Err(E) } | ||
46 | } | ||
47 | pub mod option { | ||
48 | pub enum Option<T> { Some(T), None } | ||
49 | } | ||
50 | "#, | ||
51 | r#" | ||
52 | use core::option::Option::{self, Some, None}; | ||
53 | |||
54 | fn 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 | ||
69 | use core::result::Result::{self, Ok, Err}; | ||
70 | |||
71 | fn 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 | ||
78 | pub mod result { | ||
79 | pub enum Result<T, E> { Ok(T), Err(E) } | ||
80 | } | ||
81 | pub mod option { | ||
82 | pub enum Option<T> { Some(T), None } | ||
83 | } | ||
84 | "#, | ||
85 | r#" | ||
86 | use core::result::Result::{self, Ok, Err}; | ||
87 | |||
88 | fn 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 | ||
103 | use core::result::Result::{self, Ok, Err}; | ||
104 | |||
105 | fn 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 | ||
112 | pub mod result { | ||
113 | pub enum Result<T, E> { Ok(T), Err(E) } | ||
114 | } | ||
115 | pub mod option { | ||
116 | pub enum Option<T> { Some(T), None } | ||
117 | } | ||
118 | "#, | ||
119 | r#" | ||
120 | use core::result::Result::{self, Ok, Err}; | ||
121 | |||
122 | fn 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 | ||
137 | use core::result::Result::{self, Ok, Err}; | ||
138 | |||
139 | type MyResult<T> = Result<T, ()>; | ||
140 | |||
141 | fn 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 | ||
148 | pub mod result { | ||
149 | pub enum Result<T, E> { Ok(T), Err(E) } | ||
150 | } | ||
151 | pub mod option { | ||
152 | pub enum Option<T> { Some(T), None } | ||
153 | } | ||
154 | "#, | ||
155 | r#" | ||
156 | use core::result::Result::{self, Ok, Err}; | ||
157 | |||
158 | type MyResult<T> = Result<T, ()>; | ||
159 | |||
160 | fn 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 | ||
175 | use core::result::Result::{self, Ok, Err}; | ||
176 | |||
177 | fn foo() -> Result<(), i32> { 0 } | ||
178 | |||
179 | //- /core/lib.rs crate:core | ||
180 | pub mod result { | ||
181 | pub enum Result<T, E> { Ok(T), Err(E) } | ||
182 | } | ||
183 | pub 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 | ||
195 | use core::result::Result::{self, Ok, Err}; | ||
196 | |||
197 | enum SomeOtherEnum { Ok(i32), Err(String) } | ||
198 | |||
199 | fn foo() -> SomeOtherEnum { 0 } | ||
200 | |||
201 | //- /core/lib.rs crate:core | ||
202 | pub mod result { | ||
203 | pub enum Result<T, E> { Ok(T), Err(E) } | ||
204 | } | ||
205 | pub 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::{ | |||
18 | use text_edit::TextEdit; | 18 | use text_edit::TextEdit; |
19 | 19 | ||
20 | use crate::{ | 20 | use 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 | ||
53 | impl DiagnosticWithFix for UnlinkedFile { | 53 | impl 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 | ||
104 | fn make_fix( | 104 | fn 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) |
30 | pub(crate) fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition) -> String { | 30 | pub(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 | |||
64 | pub(crate) fn remove_links(markdown: &str) -> String { | 65 | pub(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; | |||
4 | use itertools::Itertools; | 4 | use itertools::Itertools; |
5 | use syntax::{ | 5 | use 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}, |