aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to 'crates')
-rw-r--r--crates/cfg/src/lib.rs2
-rw-r--r--crates/flycheck/src/lib.rs1
-rw-r--r--crates/hir/Cargo.toml1
-rw-r--r--crates/hir/src/lib.rs18
-rw-r--r--crates/hir_def/src/attr.rs144
-rw-r--r--crates/hir_def/src/body.rs4
-rw-r--r--crates/hir_def/src/body/lower.rs34
-rw-r--r--crates/hir_def/src/body/scope.rs2
-rw-r--r--crates/hir_def/src/expr.rs8
-rw-r--r--crates/hir_def/src/find_path.rs13
-rw-r--r--crates/hir_def/src/item_tree.rs3
-rw-r--r--crates/hir_def/src/item_tree/lower.rs3
-rw-r--r--crates/hir_def/src/lib.rs14
-rw-r--r--crates/hir_def/src/nameres.rs2
-rw-r--r--crates/hir_def/src/nameres/collector.rs27
-rw-r--r--crates/hir_def/src/nameres/tests.rs16
-rw-r--r--crates/hir_def/src/nameres/tests/incremental.rs6
-rw-r--r--crates/hir_def/src/type_ref.rs73
-rw-r--r--crates/hir_expand/src/builtin_derive.rs4
-rw-r--r--crates/hir_expand/src/builtin_macro.rs40
-rw-r--r--crates/hir_expand/src/db.rs89
-rw-r--r--crates/hir_expand/src/eager.rs7
-rw-r--r--crates/hir_expand/src/input.rs94
-rw-r--r--crates/hir_expand/src/lib.rs89
-rw-r--r--crates/hir_expand/src/name.rs1
-rw-r--r--crates/hir_expand/src/proc_macro.rs102
-rw-r--r--crates/hir_expand/src/quote.rs4
-rw-r--r--crates/hir_ty/src/chalk_ext.rs36
-rw-r--r--crates/hir_ty/src/consteval.rs56
-rw-r--r--crates/hir_ty/src/diagnostics/expr.rs2
-rw-r--r--crates/hir_ty/src/diagnostics/match_check.rs1
-rw-r--r--crates/hir_ty/src/display.rs7
-rw-r--r--crates/hir_ty/src/infer/expr.rs22
-rw-r--r--crates/hir_ty/src/infer/pat.rs9
-rw-r--r--crates/hir_ty/src/interner.rs15
-rw-r--r--crates/hir_ty/src/lib.rs11
-rw-r--r--crates/hir_ty/src/lower.rs9
-rw-r--r--crates/hir_ty/src/tests/coercion.rs136
-rw-r--r--crates/hir_ty/src/tests/patterns.rs16
-rw-r--r--crates/hir_ty/src/tests/regression.rs61
-rw-r--r--crates/hir_ty/src/tests/simple.rs145
-rw-r--r--crates/hir_ty/src/tests/traits.rs99
-rw-r--r--crates/ide/Cargo.toml1
-rw-r--r--crates/ide/src/diagnostics.rs533
-rw-r--r--crates/ide/src/diagnostics/fixes.rs288
-rw-r--r--crates/ide/src/diagnostics/fixes/change_case.rs155
-rw-r--r--crates/ide/src/diagnostics/fixes/create_field.rs157
-rw-r--r--crates/ide/src/diagnostics/fixes/fill_missing_fields.rs217
-rw-r--r--crates/ide/src/diagnostics/fixes/remove_semicolon.rs41
-rw-r--r--crates/ide/src/diagnostics/fixes/replace_with_find_map.rs84
-rw-r--r--crates/ide/src/diagnostics/fixes/unresolved_module.rs87
-rw-r--r--crates/ide/src/diagnostics/fixes/wrap_tail_expr.rs211
-rw-r--r--crates/ide/src/doc_links.rs8
-rw-r--r--crates/ide/src/inlay_hints.rs2
-rw-r--r--crates/ide/src/join_lines.rs71
-rw-r--r--crates/ide/src/lib.rs6
-rw-r--r--crates/ide/src/matching_brace.rs12
-rw-r--r--crates/ide/src/parent_module.rs6
-rw-r--r--crates/ide/src/references.rs64
-rw-r--r--crates/ide/src/references/rename.rs17
-rw-r--r--crates/ide/src/syntax_highlighting/highlight.rs11
-rw-r--r--crates/ide/src/syntax_highlighting/inject.rs2
-rw-r--r--crates/ide/src/syntax_highlighting/tags.rs4
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html14
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlighting.html15
-rw-r--r--crates/ide/src/syntax_highlighting/tests.rs27
-rw-r--r--crates/ide/src/typing.rs150
-rw-r--r--crates/ide/src/typing/on_enter.rs115
-rw-r--r--crates/ide/src/view_crate_graph.rs90
-rw-r--r--crates/ide_assists/src/assist_context.rs14
-rw-r--r--crates/ide_assists/src/handlers/add_explicit_type.rs28
-rw-r--r--crates/ide_assists/src/handlers/add_missing_impl_members.rs3
-rw-r--r--crates/ide_assists/src/handlers/auto_import.rs30
-rw-r--r--crates/ide_assists/src/handlers/convert_tuple_struct_to_named_struct.rs2
-rw-r--r--crates/ide_assists/src/handlers/early_return.rs4
-rw-r--r--crates/ide_assists/src/handlers/expand_glob_import.rs89
-rw-r--r--crates/ide_assists/src/handlers/extract_function.rs164
-rw-r--r--crates/ide_assists/src/handlers/extract_struct_from_enum_variant.rs141
-rw-r--r--crates/ide_assists/src/handlers/extract_variable.rs2
-rw-r--r--crates/ide_assists/src/handlers/fill_match_arms.rs633
-rw-r--r--crates/ide_assists/src/handlers/generate_default_from_new.rs287
-rw-r--r--crates/ide_assists/src/handlers/generate_function.rs2
-rw-r--r--crates/ide_assists/src/handlers/generate_new.rs153
-rw-r--r--crates/ide_assists/src/handlers/introduce_named_lifetime.rs58
-rw-r--r--crates/ide_assists/src/handlers/merge_imports.rs12
-rw-r--r--crates/ide_assists/src/handlers/move_bounds.rs10
-rw-r--r--crates/ide_assists/src/handlers/move_module_to_file.rs38
-rw-r--r--crates/ide_assists/src/handlers/pull_assignment_up.rs212
-rw-r--r--crates/ide_assists/src/handlers/raw_string.rs2
-rw-r--r--crates/ide_assists/src/handlers/reorder_fields.rs4
-rw-r--r--crates/ide_assists/src/handlers/reorder_impl.rs3
-rw-r--r--crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs8
-rw-r--r--crates/ide_assists/src/handlers/replace_impl_trait_with_generic.rs120
-rw-r--r--crates/ide_assists/src/handlers/replace_let_with_if_let.rs21
-rw-r--r--crates/ide_assists/src/handlers/replace_qualified_name_with_use.rs2
-rw-r--r--crates/ide_assists/src/handlers/replace_string_with_char.rs2
-rw-r--r--crates/ide_assists/src/handlers/replace_unwrap_with_match.rs23
-rw-r--r--crates/ide_assists/src/handlers/wrap_return_type_in_result.rs4
-rw-r--r--crates/ide_assists/src/tests.rs2
-rw-r--r--crates/ide_assists/src/tests/generated.rs3
-rw-r--r--crates/ide_assists/src/utils.rs52
-rw-r--r--crates/ide_assists/src/utils/suggest_name.rs15
-rw-r--r--crates/ide_completion/src/completions/keyword.rs22
-rw-r--r--crates/ide_completion/src/context.rs23
-rw-r--r--crates/ide_completion/src/lib.rs2
-rw-r--r--crates/ide_completion/src/test_utils.rs2
-rw-r--r--crates/ide_db/src/helpers/insert_use/tests.rs84
-rw-r--r--crates/ide_db/src/helpers/merge_imports.rs54
-rw-r--r--crates/ide_db/src/search.rs177
-rw-r--r--crates/ide_db/src/ty_filter.rs6
-rw-r--r--crates/parser/src/grammar/patterns.rs2
-rw-r--r--crates/project_model/src/build_data.rs1
-rw-r--r--crates/project_model/src/cargo_workspace.rs101
-rw-r--r--crates/project_model/src/rustc_cfg.rs45
-rw-r--r--crates/project_model/src/workspace.rs46
-rw-r--r--crates/rust-analyzer/src/cargo_target_spec.rs2
-rw-r--r--crates/rust-analyzer/src/config.rs20
-rw-r--r--crates/rust-analyzer/src/dispatch.rs21
-rw-r--r--crates/rust-analyzer/src/handlers.rs22
-rw-r--r--crates/rust-analyzer/src/integrated_benchmarks.rs8
-rw-r--r--crates/rust-analyzer/src/lsp_ext.rs8
-rw-r--r--crates/rust-analyzer/src/main_loop.rs1
-rw-r--r--crates/rust-analyzer/src/semantic_tokens.rs1
-rw-r--r--crates/rust-analyzer/src/to_proto.rs3
-rw-r--r--crates/syntax/Cargo.toml2
-rw-r--r--crates/syntax/src/algo.rs253
-rw-r--r--crates/syntax/src/ast.rs6
-rw-r--r--crates/syntax/src/ast/edit.rs503
-rw-r--r--crates/syntax/src/ast/edit_in_place.rs201
-rw-r--r--crates/syntax/src/ast/make.rs122
-rw-r--r--crates/syntax/src/ast/token_ext.rs81
-rw-r--r--crates/syntax/src/parsing.rs7
-rw-r--r--crates/syntax/src/parsing/text_tree_sink.rs4
-rw-r--r--crates/syntax/src/ted.rs5
-rw-r--r--crates/syntax/test_data/parser/ok/0045_block_attrs.rast6
135 files changed, 4620 insertions, 3178 deletions
diff --git a/crates/cfg/src/lib.rs b/crates/cfg/src/lib.rs
index d88ecf8b0..59fd38880 100644
--- a/crates/cfg/src/lib.rs
+++ b/crates/cfg/src/lib.rs
@@ -13,7 +13,7 @@ use tt::SmolStr;
13pub use cfg_expr::{CfgAtom, CfgExpr}; 13pub use cfg_expr::{CfgAtom, CfgExpr};
14pub use dnf::DnfExpr; 14pub use dnf::DnfExpr;
15 15
16/// Configuration options used for conditional compilition on items with `cfg` attributes. 16/// Configuration options used for conditional compilation on items with `cfg` attributes.
17/// We have two kind of options in different namespaces: atomic options like `unix`, and 17/// We have two kind of options in different namespaces: atomic options like `unix`, and
18/// key-value options like `target_arch="x86"`. 18/// key-value options like `target_arch="x86"`.
19/// 19///
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/Cargo.toml b/crates/hir/Cargo.toml
index 9e329656f..560b15238 100644
--- a/crates/hir/Cargo.toml
+++ b/crates/hir/Cargo.toml
@@ -25,3 +25,4 @@ hir_expand = { path = "../hir_expand", version = "0.0.0" }
25hir_def = { path = "../hir_def", version = "0.0.0" } 25hir_def = { path = "../hir_def", version = "0.0.0" }
26hir_ty = { path = "../hir_ty", version = "0.0.0" } 26hir_ty = { path = "../hir_ty", version = "0.0.0" }
27tt = { path = "../tt", version = "0.0.0" } 27tt = { path = "../tt", version = "0.0.0" }
28cfg = { path = "../cfg", version = "0.0.0" }
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index f876339de..d443b124c 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -52,7 +52,9 @@ use hir_def::{
52}; 52};
53use hir_expand::{diagnostics::DiagnosticSink, name::name, MacroDefKind}; 53use hir_expand::{diagnostics::DiagnosticSink, name::name, MacroDefKind};
54use hir_ty::{ 54use hir_ty::{
55 autoderef, could_unify, 55 autoderef,
56 consteval::ConstExt,
57 could_unify,
56 method_resolution::{self, def_crates, TyFingerprint}, 58 method_resolution::{self, def_crates, TyFingerprint},
57 primitive::UintTy, 59 primitive::UintTy,
58 subst_prefix, 60 subst_prefix,
@@ -89,6 +91,7 @@ pub use crate::{
89// Generally, a refactoring which *removes* a name from this list is a good 91// Generally, a refactoring which *removes* a name from this list is a good
90// idea! 92// idea!
91pub use { 93pub use {
94 cfg::{CfgAtom, CfgExpr, CfgOptions},
92 hir_def::{ 95 hir_def::{
93 adt::StructKind, 96 adt::StructKind,
94 attr::{Attr, Attrs, AttrsWithOwner, Documentation}, 97 attr::{Attr, Attrs, AttrsWithOwner, Documentation},
@@ -215,6 +218,10 @@ impl Crate {
215 218
216 doc_url.map(|s| s.trim_matches('"').trim_end_matches('/').to_owned() + "/") 219 doc_url.map(|s| s.trim_matches('"').trim_end_matches('/').to_owned() + "/")
217 } 220 }
221
222 pub fn cfg(&self, db: &dyn HirDatabase) -> CfgOptions {
223 db.crate_graph()[self.id].cfg_options.clone()
224 }
218} 225}
219 226
220#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 227#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
@@ -868,6 +875,10 @@ impl Function {
868 db.function_data(self.id).is_unsafe() 875 db.function_data(self.id).is_unsafe()
869 } 876 }
870 877
878 pub fn is_async(self, db: &dyn HirDatabase) -> bool {
879 db.function_data(self.id).is_async()
880 }
881
871 pub fn diagnostics(self, db: &dyn HirDatabase, sink: &mut DiagnosticSink) { 882 pub fn diagnostics(self, db: &dyn HirDatabase, sink: &mut DiagnosticSink) {
872 let krate = self.module(db).id.krate(); 883 let krate = self.module(db).id.krate();
873 hir_def::diagnostics::validate_body(db.upcast(), self.id.into(), sink); 884 hir_def::diagnostics::validate_body(db.upcast(), self.id.into(), sink);
@@ -1905,6 +1916,7 @@ impl Type {
1905 substs.iter(&Interner).filter_map(|a| a.ty(&Interner)).any(go) 1916 substs.iter(&Interner).filter_map(|a| a.ty(&Interner)).any(go)
1906 } 1917 }
1907 1918
1919 TyKind::Array(_ty, len) if len.is_unknown() => true,
1908 TyKind::Array(ty, _) 1920 TyKind::Array(ty, _)
1909 | TyKind::Slice(ty) 1921 | TyKind::Slice(ty)
1910 | TyKind::Raw(_, ty) 1922 | TyKind::Raw(_, ty)
@@ -2066,6 +2078,10 @@ impl Type {
2066 Some(adt.into()) 2078 Some(adt.into())
2067 } 2079 }
2068 2080
2081 pub fn as_builtin(&self) -> Option<BuiltinType> {
2082 self.ty.as_builtin().map(|inner| BuiltinType { inner })
2083 }
2084
2069 pub fn as_dyn_trait(&self) -> Option<Trait> { 2085 pub fn as_dyn_trait(&self) -> Option<Trait> {
2070 self.ty.dyn_trait().map(Into::into) 2086 self.ty.dyn_trait().map(Into::into)
2071 } 2087 }
diff --git a/crates/hir_def/src/attr.rs b/crates/hir_def/src/attr.rs
index a2479016e..aadd4e44a 100644
--- a/crates/hir_def/src/attr.rs
+++ b/crates/hir_def/src/attr.rs
@@ -9,7 +9,7 @@ use std::{
9use base_db::CrateId; 9use base_db::CrateId;
10use cfg::{CfgExpr, CfgOptions}; 10use cfg::{CfgExpr, CfgOptions};
11use either::Either; 11use either::Either;
12use hir_expand::{hygiene::Hygiene, name::AsName, AstId, AttrId, InFile}; 12use hir_expand::{hygiene::Hygiene, name::AsName, AstId, InFile};
13use itertools::Itertools; 13use itertools::Itertools;
14use la_arena::ArenaMap; 14use la_arena::ArenaMap;
15use mbe::ast_to_token_tree; 15use mbe::ast_to_token_tree;
@@ -101,17 +101,13 @@ impl RawAttrs {
101 hygiene: &Hygiene, 101 hygiene: &Hygiene,
102 ) -> Self { 102 ) -> Self {
103 let entries = collect_attrs(owner) 103 let entries = collect_attrs(owner)
104 .enumerate() 104 .flat_map(|(id, attr)| match attr {
105 .flat_map(|(i, attr)| { 105 Either::Left(attr) => Attr::from_src(db, attr, hygiene, id),
106 let index = AttrId(i as u32); 106 Either::Right(comment) => comment.doc_comment().map(|doc| Attr {
107 match attr { 107 id,
108 Either::Left(attr) => Attr::from_src(db, attr, hygiene, index), 108 input: Some(AttrInput::Literal(SmolStr::new(doc))),
109 Either::Right(comment) => comment.doc_comment().map(|doc| Attr { 109 path: Interned::new(ModPath::from(hir_expand::name!(doc))),
110 id: index, 110 }),
111 input: Some(AttrInput::Literal(SmolStr::new(doc))),
112 path: Interned::new(ModPath::from(hir_expand::name!(doc))),
113 }),
114 }
115 }) 111 })
116 .collect::<Arc<_>>(); 112 .collect::<Arc<_>>();
117 113
@@ -124,6 +120,7 @@ impl RawAttrs {
124 } 120 }
125 121
126 pub(crate) fn merge(&self, other: Self) -> Self { 122 pub(crate) fn merge(&self, other: Self) -> Self {
123 // FIXME: This needs to fixup `AttrId`s
127 match (&self.entries, &other.entries) { 124 match (&self.entries, &other.entries) {
128 (None, None) => Self::EMPTY, 125 (None, None) => Self::EMPTY,
129 (Some(entries), None) | (None, Some(entries)) => { 126 (Some(entries), None) | (None, Some(entries)) => {
@@ -375,39 +372,26 @@ impl AttrsWithOwner {
375 372
376 let def_map = module.def_map(db); 373 let def_map = module.def_map(db);
377 let mod_data = &def_map[module.local_id]; 374 let mod_data = &def_map[module.local_id];
378 let attrs = match mod_data.declaration_source(db) { 375 match mod_data.declaration_source(db) {
379 Some(it) => { 376 Some(it) => {
380 let mut attrs: Vec<_> = collect_attrs(&it.value as &dyn ast::AttrsOwner) 377 let mut map = AttrSourceMap::new(InFile::new(it.file_id, &it.value));
381 .map(|attr| InFile::new(it.file_id, attr))
382 .collect();
383 if let InFile { file_id, value: ModuleSource::SourceFile(file) } = 378 if let InFile { file_id, value: ModuleSource::SourceFile(file) } =
384 mod_data.definition_source(db) 379 mod_data.definition_source(db)
385 { 380 {
386 attrs.extend( 381 map.merge(AttrSourceMap::new(InFile::new(file_id, &file)));
387 collect_attrs(&file as &dyn ast::AttrsOwner)
388 .map(|attr| InFile::new(file_id, attr)),
389 )
390 } 382 }
391 attrs 383 return map;
392 } 384 }
393 None => { 385 None => {
394 let InFile { file_id, value } = mod_data.definition_source(db); 386 let InFile { file_id, value } = mod_data.definition_source(db);
395 match &value { 387 let attrs_owner = match &value {
396 ModuleSource::SourceFile(file) => { 388 ModuleSource::SourceFile(file) => file as &dyn ast::AttrsOwner,
397 collect_attrs(file as &dyn ast::AttrsOwner) 389 ModuleSource::Module(module) => module as &dyn ast::AttrsOwner,
398 } 390 ModuleSource::BlockExpr(block) => block as &dyn ast::AttrsOwner,
399 ModuleSource::Module(module) => { 391 };
400 collect_attrs(module as &dyn ast::AttrsOwner) 392 return AttrSourceMap::new(InFile::new(file_id, attrs_owner));
401 }
402 ModuleSource::BlockExpr(block) => {
403 collect_attrs(block as &dyn ast::AttrsOwner)
404 }
405 }
406 .map(|attr| InFile::new(file_id, attr))
407 .collect()
408 } 393 }
409 }; 394 }
410 return AttrSourceMap { attrs };
411 } 395 }
412 AttrDefId::FieldId(id) => { 396 AttrDefId::FieldId(id) => {
413 let map = db.fields_attrs_source_map(id.parent); 397 let map = db.fields_attrs_source_map(id.parent);
@@ -462,11 +446,7 @@ impl AttrsWithOwner {
462 }, 446 },
463 }; 447 };
464 448
465 AttrSourceMap { 449 AttrSourceMap::new(owner.as_ref().map(|node| node as &dyn AttrsOwner))
466 attrs: collect_attrs(&owner.value)
467 .map(|attr| InFile::new(owner.file_id, attr))
468 .collect(),
469 }
470 } 450 }
471 451
472 pub fn docs_with_rangemap( 452 pub fn docs_with_rangemap(
@@ -518,7 +498,7 @@ impl AttrsWithOwner {
518 if buf.is_empty() { 498 if buf.is_empty() {
519 None 499 None
520 } else { 500 } else {
521 Some((Documentation(buf), DocsRangeMap { mapping, source: self.source_map(db).attrs })) 501 Some((Documentation(buf), DocsRangeMap { mapping, source_map: self.source_map(db) }))
522 } 502 }
523 } 503 }
524} 504}
@@ -559,27 +539,59 @@ fn inner_attributes(
559} 539}
560 540
561pub struct AttrSourceMap { 541pub struct AttrSourceMap {
562 attrs: Vec<InFile<Either<ast::Attr, ast::Comment>>>, 542 attrs: Vec<InFile<ast::Attr>>,
543 doc_comments: Vec<InFile<ast::Comment>>,
563} 544}
564 545
565impl AttrSourceMap { 546impl AttrSourceMap {
547 fn new(owner: InFile<&dyn ast::AttrsOwner>) -> Self {
548 let mut attrs = Vec::new();
549 let mut doc_comments = Vec::new();
550 for (_, attr) in collect_attrs(owner.value) {
551 match attr {
552 Either::Left(attr) => attrs.push(owner.with_value(attr)),
553 Either::Right(comment) => doc_comments.push(owner.with_value(comment)),
554 }
555 }
556
557 Self { attrs, doc_comments }
558 }
559
560 fn merge(&mut self, other: Self) {
561 self.attrs.extend(other.attrs);
562 self.doc_comments.extend(other.doc_comments);
563 }
564
566 /// Maps the lowered `Attr` back to its original syntax node. 565 /// Maps the lowered `Attr` back to its original syntax node.
567 /// 566 ///
568 /// `attr` must come from the `owner` used for AttrSourceMap 567 /// `attr` must come from the `owner` used for AttrSourceMap
569 /// 568 ///
570 /// Note that the returned syntax node might be a `#[cfg_attr]`, or a doc comment, instead of 569 /// Note that the returned syntax node might be a `#[cfg_attr]`, or a doc comment, instead of
571 /// the attribute represented by `Attr`. 570 /// the attribute represented by `Attr`.
572 pub fn source_of(&self, attr: &Attr) -> InFile<&Either<ast::Attr, ast::Comment>> { 571 pub fn source_of(&self, attr: &Attr) -> InFile<Either<ast::Attr, ast::Comment>> {
573 self.attrs 572 self.source_of_id(attr.id)
574 .get(attr.id.0 as usize) 573 }
575 .unwrap_or_else(|| panic!("cannot find `Attr` at index {:?}", attr.id)) 574
576 .as_ref() 575 fn source_of_id(&self, id: AttrId) -> InFile<Either<ast::Attr, ast::Comment>> {
576 if id.is_doc_comment {
577 self.doc_comments
578 .get(id.ast_index as usize)
579 .unwrap_or_else(|| panic!("cannot find doc comment at index {:?}", id))
580 .clone()
581 .map(|attr| Either::Right(attr))
582 } else {
583 self.attrs
584 .get(id.ast_index as usize)
585 .unwrap_or_else(|| panic!("cannot find `Attr` at index {:?}", id))
586 .clone()
587 .map(|attr| Either::Left(attr))
588 }
577 } 589 }
578} 590}
579 591
580/// A struct to map text ranges from [`Documentation`] back to TextRanges in the syntax tree. 592/// A struct to map text ranges from [`Documentation`] back to TextRanges in the syntax tree.
581pub struct DocsRangeMap { 593pub struct DocsRangeMap {
582 source: Vec<InFile<Either<ast::Attr, ast::Comment>>>, 594 source_map: AttrSourceMap,
583 // (docstring-line-range, attr_index, attr-string-range) 595 // (docstring-line-range, attr_index, attr-string-range)
584 // a mapping from the text range of a line of the [`Documentation`] to the attribute index and 596 // a mapping from the text range of a line of the [`Documentation`] to the attribute index and
585 // the original (untrimmed) syntax doc line 597 // the original (untrimmed) syntax doc line
@@ -596,7 +608,7 @@ impl DocsRangeMap {
596 608
597 let relative_range = range - line_docs_range.start(); 609 let relative_range = range - line_docs_range.start();
598 610
599 let &InFile { file_id, value: ref source } = &self.source[idx.0 as usize]; 611 let &InFile { file_id, value: ref source } = &self.source_map.source_of_id(idx);
600 match source { 612 match source {
601 Either::Left(_) => None, // FIXME, figure out a nice way to handle doc attributes here 613 Either::Left(_) => None, // FIXME, figure out a nice way to handle doc attributes here
602 // as well as for whats done in syntax highlight doc injection 614 // as well as for whats done in syntax highlight doc injection
@@ -615,6 +627,12 @@ impl DocsRangeMap {
615 } 627 }
616} 628}
617 629
630#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
631pub(crate) struct AttrId {
632 is_doc_comment: bool,
633 pub(crate) ast_index: u32,
634}
635
618#[derive(Debug, Clone, PartialEq, Eq)] 636#[derive(Debug, Clone, PartialEq, Eq)]
619pub struct Attr { 637pub struct Attr {
620 pub(crate) id: AttrId, 638 pub(crate) id: AttrId,
@@ -749,22 +767,32 @@ fn attrs_from_item_tree<N: ItemTreeNode>(id: ItemTreeId<N>, db: &dyn DefDatabase
749 767
750fn collect_attrs( 768fn collect_attrs(
751 owner: &dyn ast::AttrsOwner, 769 owner: &dyn ast::AttrsOwner,
752) -> impl Iterator<Item = Either<ast::Attr, ast::Comment>> { 770) -> impl Iterator<Item = (AttrId, Either<ast::Attr, ast::Comment>)> {
753 let (inner_attrs, inner_docs) = inner_attributes(owner.syntax()) 771 let (inner_attrs, inner_docs) = inner_attributes(owner.syntax())
754 .map_or((None, None), |(attrs, docs)| (Some(attrs), Some(docs))); 772 .map_or((None, None), |(attrs, docs)| (Some(attrs), Some(docs)));
755 773
756 let outer_attrs = owner.attrs().filter(|attr| attr.kind().is_outer()); 774 let outer_attrs = owner.attrs().filter(|attr| attr.kind().is_outer());
757 let attrs = outer_attrs 775 let attrs =
758 .chain(inner_attrs.into_iter().flatten()) 776 outer_attrs.chain(inner_attrs.into_iter().flatten()).enumerate().map(|(idx, attr)| {
759 .map(|attr| (attr.syntax().text_range().start(), Either::Left(attr))); 777 (
778 AttrId { ast_index: idx as u32, is_doc_comment: false },
779 attr.syntax().text_range().start(),
780 Either::Left(attr),
781 )
782 });
760 783
761 let outer_docs = 784 let outer_docs =
762 ast::CommentIter::from_syntax_node(owner.syntax()).filter(ast::Comment::is_outer); 785 ast::CommentIter::from_syntax_node(owner.syntax()).filter(ast::Comment::is_outer);
763 let docs = outer_docs 786 let docs =
764 .chain(inner_docs.into_iter().flatten()) 787 outer_docs.chain(inner_docs.into_iter().flatten()).enumerate().map(|(idx, docs_text)| {
765 .map(|docs_text| (docs_text.syntax().text_range().start(), Either::Right(docs_text))); 788 (
789 AttrId { ast_index: idx as u32, is_doc_comment: true },
790 docs_text.syntax().text_range().start(),
791 Either::Right(docs_text),
792 )
793 });
766 // sort here by syntax node offset because the source can have doc attributes and doc strings be interleaved 794 // sort here by syntax node offset because the source can have doc attributes and doc strings be interleaved
767 docs.chain(attrs).sorted_by_key(|&(offset, _)| offset).map(|(_, attr)| attr) 795 docs.chain(attrs).sorted_by_key(|&(_, offset, _)| offset).map(|(id, _, attr)| (id, attr))
768} 796}
769 797
770pub(crate) fn variants_attrs_source_map( 798pub(crate) fn variants_attrs_source_map(
diff --git a/crates/hir_def/src/body.rs b/crates/hir_def/src/body.rs
index 8360426f1..98b485b60 100644
--- a/crates/hir_def/src/body.rs
+++ b/crates/hir_def/src/body.rs
@@ -21,8 +21,6 @@ use profile::Count;
21use rustc_hash::FxHashMap; 21use rustc_hash::FxHashMap;
22use syntax::{ast, AstNode, AstPtr}; 22use syntax::{ast, AstNode, AstPtr};
23 23
24pub use lower::LowerCtx;
25
26use crate::{ 24use crate::{
27 attr::{Attrs, RawAttrs}, 25 attr::{Attrs, RawAttrs},
28 db::DefDatabase, 26 db::DefDatabase,
@@ -35,6 +33,8 @@ use crate::{
35 UnresolvedMacro, 33 UnresolvedMacro,
36}; 34};
37 35
36pub use lower::LowerCtx;
37
38/// A subset of Expander that only deals with cfg attributes. We only need it to 38/// A subset of Expander that only deals with cfg attributes. We only need it to
39/// avoid cyclic queries in crate def map during enum processing. 39/// avoid cyclic queries in crate def map during enum processing.
40#[derive(Debug)] 40#[derive(Debug)]
diff --git a/crates/hir_def/src/body/lower.rs b/crates/hir_def/src/body/lower.rs
index 75dc19c11..2a7e0205f 100644
--- a/crates/hir_def/src/body/lower.rs
+++ b/crates/hir_def/src/body/lower.rs
@@ -205,7 +205,7 @@ impl ExprCollector<'_> {
205 self.maybe_collect_expr(expr).unwrap_or_else(|| self.missing_expr()) 205 self.maybe_collect_expr(expr).unwrap_or_else(|| self.missing_expr())
206 } 206 }
207 207
208 /// Returns `None` if the expression is `#[cfg]`d out. 208 /// Returns `None` if and only if the expression is `#[cfg]`d out.
209 fn maybe_collect_expr(&mut self, expr: ast::Expr) -> Option<ExprId> { 209 fn maybe_collect_expr(&mut self, expr: ast::Expr) -> Option<ExprId> {
210 let syntax_ptr = AstPtr::new(&expr); 210 let syntax_ptr = AstPtr::new(&expr);
211 self.check_cfg(&expr)?; 211 self.check_cfg(&expr)?;
@@ -668,7 +668,7 @@ impl ExprCollector<'_> {
668 if self.check_cfg(&stmt).is_none() { 668 if self.check_cfg(&stmt).is_none() {
669 return; 669 return;
670 } 670 }
671 671 let has_semi = stmt.semicolon_token().is_some();
672 // Note that macro could be expended to multiple statements 672 // Note that macro could be expended to multiple statements
673 if let Some(ast::Expr::MacroCall(m)) = stmt.expr() { 673 if let Some(ast::Expr::MacroCall(m)) = stmt.expr() {
674 let macro_ptr = AstPtr::new(&m); 674 let macro_ptr = AstPtr::new(&m);
@@ -685,18 +685,19 @@ impl ExprCollector<'_> {
685 statements.statements().for_each(|stmt| this.collect_stmt(stmt)); 685 statements.statements().for_each(|stmt| this.collect_stmt(stmt));
686 if let Some(expr) = statements.expr() { 686 if let Some(expr) = statements.expr() {
687 let expr = this.collect_expr(expr); 687 let expr = this.collect_expr(expr);
688 this.statements_in_scope.push(Statement::Expr(expr)); 688 this.statements_in_scope
689 .push(Statement::Expr { expr, has_semi });
689 } 690 }
690 } 691 }
691 None => { 692 None => {
692 let expr = this.alloc_expr(Expr::Missing, syntax_ptr.clone()); 693 let expr = this.alloc_expr(Expr::Missing, syntax_ptr.clone());
693 this.statements_in_scope.push(Statement::Expr(expr)); 694 this.statements_in_scope.push(Statement::Expr { expr, has_semi });
694 } 695 }
695 }, 696 },
696 ); 697 );
697 } else { 698 } else {
698 let expr = self.collect_expr_opt(stmt.expr()); 699 let expr = self.collect_expr_opt(stmt.expr());
699 self.statements_in_scope.push(Statement::Expr(expr)); 700 self.statements_in_scope.push(Statement::Expr { expr, has_semi });
700 } 701 }
701 } 702 }
702 ast::Stmt::Item(item) => { 703 ast::Stmt::Item(item) => {
@@ -725,8 +726,17 @@ impl ExprCollector<'_> {
725 let prev_statements = std::mem::take(&mut self.statements_in_scope); 726 let prev_statements = std::mem::take(&mut self.statements_in_scope);
726 727
727 block.statements().for_each(|s| self.collect_stmt(s)); 728 block.statements().for_each(|s| self.collect_stmt(s));
728 729 block.tail_expr().and_then(|e| {
729 let tail = block.tail_expr().map(|e| self.collect_expr(e)); 730 let expr = self.maybe_collect_expr(e)?;
731 Some(self.statements_in_scope.push(Statement::Expr { expr, has_semi: false }))
732 });
733
734 let mut tail = None;
735 if let Some(Statement::Expr { expr, has_semi: false }) = self.statements_in_scope.last() {
736 tail = Some(*expr);
737 self.statements_in_scope.pop();
738 }
739 let tail = tail;
730 let statements = std::mem::replace(&mut self.statements_in_scope, prev_statements); 740 let statements = std::mem::replace(&mut self.statements_in_scope, prev_statements);
731 let syntax_node_ptr = AstPtr::new(&block.into()); 741 let syntax_node_ptr = AstPtr::new(&block.into());
732 let expr_id = self.alloc_expr( 742 let expr_id = self.alloc_expr(
@@ -996,23 +1006,27 @@ impl From<ast::BinOp> for BinaryOp {
996impl From<ast::LiteralKind> for Literal { 1006impl From<ast::LiteralKind> for Literal {
997 fn from(ast_lit_kind: ast::LiteralKind) -> Self { 1007 fn from(ast_lit_kind: ast::LiteralKind) -> Self {
998 match ast_lit_kind { 1008 match ast_lit_kind {
1009 // FIXME: these should have actual values filled in, but unsure on perf impact
999 LiteralKind::IntNumber(lit) => { 1010 LiteralKind::IntNumber(lit) => {
1000 if let builtin @ Some(_) = lit.suffix().and_then(BuiltinFloat::from_suffix) { 1011 if let builtin @ Some(_) = lit.suffix().and_then(BuiltinFloat::from_suffix) {
1001 return Literal::Float(Default::default(), builtin); 1012 return Literal::Float(Default::default(), builtin);
1002 } else if let builtin @ Some(_) = 1013 } else if let builtin @ Some(_) =
1003 lit.suffix().and_then(|it| BuiltinInt::from_suffix(&it)) 1014 lit.suffix().and_then(|it| BuiltinInt::from_suffix(&it))
1004 { 1015 {
1005 Literal::Int(Default::default(), builtin) 1016 Literal::Int(lit.value().unwrap_or(0) as i128, builtin)
1006 } else { 1017 } else {
1007 let builtin = lit.suffix().and_then(|it| BuiltinUint::from_suffix(&it)); 1018 let builtin = lit.suffix().and_then(|it| BuiltinUint::from_suffix(&it));
1008 Literal::Uint(Default::default(), builtin) 1019 Literal::Uint(lit.value().unwrap_or(0), builtin)
1009 } 1020 }
1010 } 1021 }
1011 LiteralKind::FloatNumber(lit) => { 1022 LiteralKind::FloatNumber(lit) => {
1012 let ty = lit.suffix().and_then(|it| BuiltinFloat::from_suffix(&it)); 1023 let ty = lit.suffix().and_then(|it| BuiltinFloat::from_suffix(&it));
1013 Literal::Float(Default::default(), ty) 1024 Literal::Float(Default::default(), ty)
1014 } 1025 }
1015 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 }
1016 LiteralKind::String(_) => Literal::String(Default::default()), 1030 LiteralKind::String(_) => Literal::String(Default::default()),
1017 LiteralKind::Byte => Literal::Uint(Default::default(), Some(BuiltinUint::U8)), 1031 LiteralKind::Byte => Literal::Uint(Default::default(), Some(BuiltinUint::U8)),
1018 LiteralKind::Bool(val) => Literal::Bool(val), 1032 LiteralKind::Bool(val) => Literal::Bool(val),
diff --git a/crates/hir_def/src/body/scope.rs b/crates/hir_def/src/body/scope.rs
index bd7005ca6..6764de3a7 100644
--- a/crates/hir_def/src/body/scope.rs
+++ b/crates/hir_def/src/body/scope.rs
@@ -157,7 +157,7 @@ fn compute_block_scopes(
157 scope = scopes.new_scope(scope); 157 scope = scopes.new_scope(scope);
158 scopes.add_bindings(body, scope, *pat); 158 scopes.add_bindings(body, scope, *pat);
159 } 159 }
160 Statement::Expr(expr) => { 160 Statement::Expr { expr, .. } => {
161 scopes.set_scope(*expr, scope); 161 scopes.set_scope(*expr, scope);
162 compute_expr_scopes(*expr, body, scopes, scope); 162 compute_expr_scopes(*expr, body, scopes, scope);
163 } 163 }
diff --git a/crates/hir_def/src/expr.rs b/crates/hir_def/src/expr.rs
index b4ad984bd..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
@@ -242,7 +242,7 @@ pub struct RecordLitField {
242#[derive(Debug, Clone, Eq, PartialEq)] 242#[derive(Debug, Clone, Eq, PartialEq)]
243pub enum Statement { 243pub enum Statement {
244 Let { pat: PatId, type_ref: Option<Interned<TypeRef>>, initializer: Option<ExprId> }, 244 Let { pat: PatId, type_ref: Option<Interned<TypeRef>>, initializer: Option<ExprId> },
245 Expr(ExprId), 245 Expr { expr: ExprId, has_semi: bool },
246} 246}
247 247
248impl Expr { 248impl Expr {
@@ -265,7 +265,7 @@ impl Expr {
265 f(*expr); 265 f(*expr);
266 } 266 }
267 } 267 }
268 Statement::Expr(e) => f(*e), 268 Statement::Expr { expr: expression, .. } => f(*expression),
269 } 269 }
270 } 270 }
271 if let Some(expr) = tail { 271 if let Some(expr) = tail {
diff --git a/crates/hir_def/src/find_path.rs b/crates/hir_def/src/find_path.rs
index 858e88038..ee52794aa 100644
--- a/crates/hir_def/src/find_path.rs
+++ b/crates/hir_def/src/find_path.rs
@@ -5,10 +5,10 @@ use std::iter;
5use hir_expand::name::{known, AsName, Name}; 5use hir_expand::name::{known, AsName, Name};
6use rustc_hash::FxHashSet; 6use rustc_hash::FxHashSet;
7 7
8use crate::nameres::DefMap;
9use crate::{ 8use crate::{
10 db::DefDatabase, 9 db::DefDatabase,
11 item_scope::ItemInNs, 10 item_scope::ItemInNs,
11 nameres::DefMap,
12 path::{ModPath, PathKind}, 12 path::{ModPath, PathKind},
13 visibility::Visibility, 13 visibility::Visibility,
14 ModuleDefId, ModuleId, 14 ModuleDefId, ModuleId,
@@ -134,7 +134,16 @@ fn find_path_inner(
134 for (name, def_id) in root_def_map.extern_prelude() { 134 for (name, def_id) in root_def_map.extern_prelude() {
135 if item == ItemInNs::Types(*def_id) { 135 if item == ItemInNs::Types(*def_id) {
136 let name = scope_name.unwrap_or_else(|| name.clone()); 136 let name = scope_name.unwrap_or_else(|| name.clone());
137 return Some(ModPath::from_segments(PathKind::Plain, vec![name])); 137
138 let name_already_occupied_in_type_ns = def_map
139 .with_ancestor_maps(db, from.local_id, &mut |def_map, local_id| {
140 def_map[local_id].scope.get(&name).take_types().filter(|&id| id != *def_id)
141 })
142 .is_some();
143 return Some(ModPath::from_segments(
144 if name_already_occupied_in_type_ns { PathKind::Abs } else { PathKind::Plain },
145 vec![name],
146 ));
138 } 147 }
139 } 148 }
140 149
diff --git a/crates/hir_def/src/item_tree.rs b/crates/hir_def/src/item_tree.rs
index 8d13c7e04..cad8a7479 100644
--- a/crates/hir_def/src/item_tree.rs
+++ b/crates/hir_def/src/item_tree.rs
@@ -18,7 +18,7 @@ use hir_expand::{
18 ast_id_map::FileAstId, 18 ast_id_map::FileAstId,
19 hygiene::Hygiene, 19 hygiene::Hygiene,
20 name::{name, AsName, Name}, 20 name::{name, AsName, Name},
21 HirFileId, InFile, 21 FragmentKind, HirFileId, InFile,
22}; 22};
23use la_arena::{Arena, Idx, RawIdx}; 23use la_arena::{Arena, Idx, RawIdx};
24use profile::Count; 24use profile::Count;
@@ -656,6 +656,7 @@ pub struct MacroCall {
656 /// Path to the called macro. 656 /// Path to the called macro.
657 pub path: Interned<ModPath>, 657 pub path: Interned<ModPath>,
658 pub ast_id: FileAstId<ast::MacroCall>, 658 pub ast_id: FileAstId<ast::MacroCall>,
659 pub fragment: FragmentKind,
659} 660}
660 661
661#[derive(Debug, Clone, Eq, PartialEq)] 662#[derive(Debug, Clone, Eq, PartialEq)]
diff --git a/crates/hir_def/src/item_tree/lower.rs b/crates/hir_def/src/item_tree/lower.rs
index 5743b3386..fe348091d 100644
--- a/crates/hir_def/src/item_tree/lower.rs
+++ b/crates/hir_def/src/item_tree/lower.rs
@@ -624,7 +624,8 @@ impl<'a> Ctx<'a> {
624 fn lower_macro_call(&mut self, m: &ast::MacroCall) -> Option<FileItemTreeId<MacroCall>> { 624 fn lower_macro_call(&mut self, m: &ast::MacroCall) -> Option<FileItemTreeId<MacroCall>> {
625 let path = Interned::new(ModPath::from_src(self.db, m.path()?, &self.hygiene)?); 625 let path = Interned::new(ModPath::from_src(self.db, m.path()?, &self.hygiene)?);
626 let ast_id = self.source_ast_id_map.ast_id(m); 626 let ast_id = self.source_ast_id_map.ast_id(m);
627 let res = MacroCall { path, ast_id }; 627 let fragment = hir_expand::to_fragment_kind(m);
628 let res = MacroCall { path, ast_id, fragment };
628 Some(id(self.data().macro_calls.alloc(res))) 629 Some(id(self.data().macro_calls.alloc(res)))
629 } 630 }
630 631
diff --git a/crates/hir_def/src/lib.rs b/crates/hir_def/src/lib.rs
index da46f16f7..a82ea5957 100644
--- a/crates/hir_def/src/lib.rs
+++ b/crates/hir_def/src/lib.rs
@@ -62,13 +62,14 @@ use hir_expand::{
62 ast_id_map::FileAstId, 62 ast_id_map::FileAstId,
63 eager::{expand_eager_macro, ErrorEmitted, ErrorSink}, 63 eager::{expand_eager_macro, ErrorEmitted, ErrorSink},
64 hygiene::Hygiene, 64 hygiene::Hygiene,
65 AstId, AttrId, HirFileId, InFile, MacroCallId, MacroCallKind, MacroDefId, MacroDefKind, 65 AstId, FragmentKind, HirFileId, InFile, MacroCallId, MacroCallKind, MacroDefId, MacroDefKind,
66}; 66};
67use la_arena::Idx; 67use la_arena::Idx;
68use nameres::DefMap; 68use nameres::DefMap;
69use path::ModPath; 69use path::ModPath;
70use syntax::ast; 70use syntax::ast;
71 71
72use crate::attr::AttrId;
72use crate::builtin_type::BuiltinType; 73use crate::builtin_type::BuiltinType;
73use item_tree::{ 74use item_tree::{
74 Const, Enum, Function, Impl, ItemTreeId, ItemTreeNode, ModItem, Static, Struct, Trait, 75 Const, Enum, Function, Impl, ItemTreeId, ItemTreeNode, ModItem, Static, Struct, Trait,
@@ -652,6 +653,7 @@ impl AsMacroCall for InFile<&ast::MacroCall> {
652 resolver: impl Fn(path::ModPath) -> Option<MacroDefId>, 653 resolver: impl Fn(path::ModPath) -> Option<MacroDefId>,
653 mut error_sink: &mut dyn FnMut(mbe::ExpandError), 654 mut error_sink: &mut dyn FnMut(mbe::ExpandError),
654 ) -> Result<Result<MacroCallId, ErrorEmitted>, UnresolvedMacro> { 655 ) -> Result<Result<MacroCallId, ErrorEmitted>, UnresolvedMacro> {
656 let fragment = hir_expand::to_fragment_kind(self.value);
655 let ast_id = AstId::new(self.file_id, db.ast_id_map(self.file_id).ast_id(self.value)); 657 let ast_id = AstId::new(self.file_id, db.ast_id_map(self.file_id).ast_id(self.value));
656 let h = Hygiene::new(db.upcast(), self.file_id); 658 let h = Hygiene::new(db.upcast(), self.file_id);
657 let path = self.value.path().and_then(|path| path::ModPath::from_src(db, path, &h)); 659 let path = self.value.path().and_then(|path| path::ModPath::from_src(db, path, &h));
@@ -667,6 +669,7 @@ impl AsMacroCall for InFile<&ast::MacroCall> {
667 669
668 macro_call_as_call_id( 670 macro_call_as_call_id(
669 &AstIdWithPath::new(ast_id.file_id, ast_id.value, path), 671 &AstIdWithPath::new(ast_id.file_id, ast_id.value, path),
672 fragment,
670 db, 673 db,
671 krate, 674 krate,
672 resolver, 675 resolver,
@@ -695,6 +698,7 @@ pub struct UnresolvedMacro {
695 698
696fn macro_call_as_call_id( 699fn macro_call_as_call_id(
697 call: &AstIdWithPath<ast::MacroCall>, 700 call: &AstIdWithPath<ast::MacroCall>,
701 fragment: FragmentKind,
698 db: &dyn db::DefDatabase, 702 db: &dyn db::DefDatabase,
699 krate: CrateId, 703 krate: CrateId,
700 resolver: impl Fn(path::ModPath) -> Option<MacroDefId>, 704 resolver: impl Fn(path::ModPath) -> Option<MacroDefId>,
@@ -718,7 +722,11 @@ fn macro_call_as_call_id(
718 .map(MacroCallId::from) 722 .map(MacroCallId::from)
719 } else { 723 } else {
720 Ok(def 724 Ok(def
721 .as_lazy_macro(db.upcast(), krate, MacroCallKind::FnLike { ast_id: call.ast_id }) 725 .as_lazy_macro(
726 db.upcast(),
727 krate,
728 MacroCallKind::FnLike { ast_id: call.ast_id, fragment },
729 )
722 .into()) 730 .into())
723 }; 731 };
724 Ok(res) 732 Ok(res)
@@ -745,7 +753,7 @@ fn derive_macro_as_call_id(
745 MacroCallKind::Derive { 753 MacroCallKind::Derive {
746 ast_id: item_attr.ast_id, 754 ast_id: item_attr.ast_id,
747 derive_name: last_segment.to_string(), 755 derive_name: last_segment.to_string(),
748 derive_attr, 756 derive_attr_index: derive_attr.ast_index,
749 }, 757 },
750 ) 758 )
751 .into(); 759 .into();
diff --git a/crates/hir_def/src/nameres.rs b/crates/hir_def/src/nameres.rs
index 1bc72ec1f..249af6fc8 100644
--- a/crates/hir_def/src/nameres.rs
+++ b/crates/hir_def/src/nameres.rs
@@ -629,7 +629,7 @@ mod diagnostics {
629 DiagnosticKind::UnresolvedProcMacro { ast } => { 629 DiagnosticKind::UnresolvedProcMacro { ast } => {
630 let mut precise_location = None; 630 let mut precise_location = None;
631 let (file, ast, name) = match ast { 631 let (file, ast, name) = match ast {
632 MacroCallKind::FnLike { ast_id } => { 632 MacroCallKind::FnLike { ast_id, .. } => {
633 let node = ast_id.to_node(db.upcast()); 633 let node = ast_id.to_node(db.upcast());
634 (ast_id.file_id, SyntaxNodePtr::from(AstPtr::new(&node)), None) 634 (ast_id.file_id, SyntaxNodePtr::from(AstPtr::new(&node)), None)
635 } 635 }
diff --git a/crates/hir_def/src/nameres/collector.rs b/crates/hir_def/src/nameres/collector.rs
index 05ceb1efb..19db6cc59 100644
--- a/crates/hir_def/src/nameres/collector.rs
+++ b/crates/hir_def/src/nameres/collector.rs
@@ -13,14 +13,14 @@ use hir_expand::{
13 builtin_macro::find_builtin_macro, 13 builtin_macro::find_builtin_macro,
14 name::{AsName, Name}, 14 name::{AsName, Name},
15 proc_macro::ProcMacroExpander, 15 proc_macro::ProcMacroExpander,
16 AttrId, HirFileId, MacroCallId, MacroCallKind, MacroDefId, MacroDefKind, 16 FragmentKind, HirFileId, MacroCallId, MacroCallKind, MacroDefId, MacroDefKind,
17}; 17};
18use hir_expand::{InFile, MacroCallLoc}; 18use hir_expand::{InFile, MacroCallLoc};
19use rustc_hash::{FxHashMap, FxHashSet}; 19use rustc_hash::{FxHashMap, FxHashSet};
20use syntax::ast; 20use syntax::ast;
21 21
22use crate::{ 22use crate::{
23 attr::Attrs, 23 attr::{AttrId, Attrs},
24 db::DefDatabase, 24 db::DefDatabase,
25 derive_macro_as_call_id, 25 derive_macro_as_call_id,
26 intern::Interned, 26 intern::Interned,
@@ -215,7 +215,7 @@ struct MacroDirective {
215 215
216#[derive(Clone, Debug, Eq, PartialEq)] 216#[derive(Clone, Debug, Eq, PartialEq)]
217enum MacroDirectiveKind { 217enum MacroDirectiveKind {
218 FnLike { ast_id: AstIdWithPath<ast::MacroCall> }, 218 FnLike { ast_id: AstIdWithPath<ast::MacroCall>, fragment: FragmentKind },
219 Derive { ast_id: AstIdWithPath<ast::Item>, derive_attr: AttrId }, 219 Derive { ast_id: AstIdWithPath<ast::Item>, derive_attr: AttrId },
220} 220}
221 221
@@ -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 }
@@ -807,9 +812,10 @@ impl DefCollector<'_> {
807 let mut res = ReachedFixedPoint::Yes; 812 let mut res = ReachedFixedPoint::Yes;
808 macros.retain(|directive| { 813 macros.retain(|directive| {
809 match &directive.kind { 814 match &directive.kind {
810 MacroDirectiveKind::FnLike { ast_id } => { 815 MacroDirectiveKind::FnLike { ast_id, fragment } => {
811 match macro_call_as_call_id( 816 match macro_call_as_call_id(
812 ast_id, 817 ast_id,
818 *fragment,
813 self.db, 819 self.db,
814 self.def_map.krate, 820 self.def_map.krate,
815 |path| { 821 |path| {
@@ -926,8 +932,9 @@ impl DefCollector<'_> {
926 932
927 for directive in &self.unexpanded_macros { 933 for directive in &self.unexpanded_macros {
928 match &directive.kind { 934 match &directive.kind {
929 MacroDirectiveKind::FnLike { ast_id, .. } => match macro_call_as_call_id( 935 MacroDirectiveKind::FnLike { ast_id, fragment } => match macro_call_as_call_id(
930 ast_id, 936 ast_id,
937 *fragment,
931 self.db, 938 self.db,
932 self.def_map.krate, 939 self.def_map.krate,
933 |path| { 940 |path| {
@@ -1496,6 +1503,7 @@ impl ModCollector<'_, '_> {
1496 let mut error = None; 1503 let mut error = None;
1497 match macro_call_as_call_id( 1504 match macro_call_as_call_id(
1498 &ast_id, 1505 &ast_id,
1506 mac.fragment,
1499 self.def_collector.db, 1507 self.def_collector.db,
1500 self.def_collector.def_map.krate, 1508 self.def_collector.def_map.krate,
1501 |path| { 1509 |path| {
@@ -1524,9 +1532,14 @@ impl ModCollector<'_, '_> {
1524 } 1532 }
1525 Ok(Err(_)) => { 1533 Ok(Err(_)) => {
1526 // Built-in macro failed eager expansion. 1534 // Built-in macro failed eager expansion.
1535
1536 // FIXME: don't parse the file here
1537 let fragment = hir_expand::to_fragment_kind(
1538 &ast_id.ast_id.to_node(self.def_collector.db.upcast()),
1539 );
1527 self.def_collector.def_map.diagnostics.push(DefDiagnostic::macro_error( 1540 self.def_collector.def_map.diagnostics.push(DefDiagnostic::macro_error(
1528 self.module_id, 1541 self.module_id,
1529 MacroCallKind::FnLike { ast_id: ast_id.ast_id }, 1542 MacroCallKind::FnLike { ast_id: ast_id.ast_id, fragment },
1530 error.unwrap().to_string(), 1543 error.unwrap().to_string(),
1531 )); 1544 ));
1532 return; 1545 return;
@@ -1543,7 +1556,7 @@ impl ModCollector<'_, '_> {
1543 self.def_collector.unexpanded_macros.push(MacroDirective { 1556 self.def_collector.unexpanded_macros.push(MacroDirective {
1544 module_id: self.module_id, 1557 module_id: self.module_id,
1545 depth: self.macro_depth + 1, 1558 depth: self.macro_depth + 1,
1546 kind: MacroDirectiveKind::FnLike { ast_id }, 1559 kind: MacroDirectiveKind::FnLike { ast_id, fragment: mac.fragment },
1547 }); 1560 });
1548 } 1561 }
1549 1562
diff --git a/crates/hir_def/src/nameres/tests.rs b/crates/hir_def/src/nameres/tests.rs
index 4f2e7a2f9..9f652731d 100644
--- a/crates/hir_def/src/nameres/tests.rs
+++ b/crates/hir_def/src/nameres/tests.rs
@@ -411,6 +411,22 @@ struct Arc;
411} 411}
412 412
413#[test] 413#[test]
414fn macro_use_extern_crate_self() {
415 cov_mark::check!(ignore_macro_use_extern_crate_self);
416 check(
417 r#"
418//- /main.rs crate:main
419#[macro_use]
420extern crate self as bla;
421"#,
422 expect![[r#"
423 crate
424 bla: t
425 "#]],
426 );
427}
428
429#[test]
414fn reexport_across_crates() { 430fn reexport_across_crates() {
415 check( 431 check(
416 r#" 432 r#"
diff --git a/crates/hir_def/src/nameres/tests/incremental.rs b/crates/hir_def/src/nameres/tests/incremental.rs
index 227ecd162..d884a6eb4 100644
--- a/crates/hir_def/src/nameres/tests/incremental.rs
+++ b/crates/hir_def/src/nameres/tests/incremental.rs
@@ -137,6 +137,9 @@ m!(Z);
137 }); 137 });
138 let n_recalculated_item_trees = events.iter().filter(|it| it.contains("item_tree")).count(); 138 let n_recalculated_item_trees = events.iter().filter(|it| it.contains("item_tree")).count();
139 assert_eq!(n_recalculated_item_trees, 6); 139 assert_eq!(n_recalculated_item_trees, 6);
140 let n_reparsed_macros =
141 events.iter().filter(|it| it.contains("parse_macro_expansion")).count();
142 assert_eq!(n_reparsed_macros, 3);
140 } 143 }
141 144
142 let new_text = r#" 145 let new_text = r#"
@@ -155,5 +158,8 @@ m!(Z);
155 }); 158 });
156 let n_recalculated_item_trees = events.iter().filter(|it| it.contains("item_tree")).count(); 159 let n_recalculated_item_trees = events.iter().filter(|it| it.contains("item_tree")).count();
157 assert_eq!(n_recalculated_item_trees, 1); 160 assert_eq!(n_recalculated_item_trees, 1);
161 let n_reparsed_macros =
162 events.iter().filter(|it| it.contains("parse_macro_expansion")).count();
163 assert_eq!(n_reparsed_macros, 0);
158 } 164 }
159} 165}
diff --git a/crates/hir_def/src/type_ref.rs b/crates/hir_def/src/type_ref.rs
index ea29da5da..9e44547cb 100644
--- a/crates/hir_def/src/type_ref.rs
+++ b/crates/hir_def/src/type_ref.rs
@@ -2,6 +2,7 @@
2//! be directly created from an ast::TypeRef, without further queries. 2//! be directly created from an ast::TypeRef, without further queries.
3 3
4use hir_expand::{name::Name, AstId, InFile}; 4use hir_expand::{name::Name, AstId, InFile};
5use std::convert::TryInto;
5use syntax::ast; 6use syntax::ast;
6 7
7use crate::{body::LowerCtx, path::Path}; 8use crate::{body::LowerCtx, path::Path};
@@ -79,7 +80,9 @@ pub enum TypeRef {
79 Path(Path), 80 Path(Path),
80 RawPtr(Box<TypeRef>, Mutability), 81 RawPtr(Box<TypeRef>, Mutability),
81 Reference(Box<TypeRef>, Option<LifetimeRef>, Mutability), 82 Reference(Box<TypeRef>, Option<LifetimeRef>, Mutability),
82 Array(Box<TypeRef> /*, Expr*/), 83 // FIXME: for full const generics, the latter element (length) here is going to have to be an
84 // expression that is further lowered later in hir_ty.
85 Array(Box<TypeRef>, ConstScalar),
83 Slice(Box<TypeRef>), 86 Slice(Box<TypeRef>),
84 /// A fn pointer. Last element of the vector is the return type. 87 /// A fn pointer. Last element of the vector is the return type.
85 Fn(Vec<TypeRef>, bool /*varargs*/), 88 Fn(Vec<TypeRef>, bool /*varargs*/),
@@ -140,7 +143,16 @@ impl TypeRef {
140 TypeRef::RawPtr(Box::new(inner_ty), mutability) 143 TypeRef::RawPtr(Box::new(inner_ty), mutability)
141 } 144 }
142 ast::Type::ArrayType(inner) => { 145 ast::Type::ArrayType(inner) => {
143 TypeRef::Array(Box::new(TypeRef::from_ast_opt(&ctx, inner.ty()))) 146 // FIXME: This is a hack. We should probably reuse the machinery of
147 // `hir_def::body::lower` to lower this into an `Expr` and then evaluate it at the
148 // `hir_ty` level, which would allow knowing the type of:
149 // let v: [u8; 2 + 2] = [0u8; 4];
150 let len = inner
151 .expr()
152 .map(ConstScalar::usize_from_literal_expr)
153 .unwrap_or(ConstScalar::Unknown);
154
155 TypeRef::Array(Box::new(TypeRef::from_ast_opt(&ctx, inner.ty())), len)
144 } 156 }
145 ast::Type::SliceType(inner) => { 157 ast::Type::SliceType(inner) => {
146 TypeRef::Slice(Box::new(TypeRef::from_ast_opt(&ctx, inner.ty()))) 158 TypeRef::Slice(Box::new(TypeRef::from_ast_opt(&ctx, inner.ty())))
@@ -212,7 +224,7 @@ impl TypeRef {
212 } 224 }
213 TypeRef::RawPtr(type_ref, _) 225 TypeRef::RawPtr(type_ref, _)
214 | TypeRef::Reference(type_ref, ..) 226 | TypeRef::Reference(type_ref, ..)
215 | TypeRef::Array(type_ref) 227 | TypeRef::Array(type_ref, _)
216 | TypeRef::Slice(type_ref) => go(&type_ref, f), 228 | TypeRef::Slice(type_ref) => go(&type_ref, f),
217 TypeRef::ImplTrait(bounds) | TypeRef::DynTrait(bounds) => { 229 TypeRef::ImplTrait(bounds) | TypeRef::DynTrait(bounds) => {
218 for bound in bounds { 230 for bound in bounds {
@@ -298,3 +310,58 @@ impl TypeBound {
298 } 310 }
299 } 311 }
300} 312}
313
314/// A concrete constant value
315#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
316pub enum ConstScalar {
317 // for now, we only support the trivial case of constant evaluating the length of an array
318 // Note that this is u64 because the target usize may be bigger than our usize
319 Usize(u64),
320
321 /// Case of an unknown value that rustc might know but we don't
322 // FIXME: this is a hack to get around chalk not being able to represent unevaluatable
323 // constants
324 // https://github.com/rust-analyzer/rust-analyzer/pull/8813#issuecomment-840679177
325 // https://rust-lang.zulipchat.com/#narrow/stream/144729-wg-traits/topic/Handling.20non.20evaluatable.20constants'.20equality/near/238386348
326 Unknown,
327}
328
329impl std::fmt::Display for ConstScalar {
330 fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
331 match self {
332 ConstScalar::Usize(us) => write!(fmt, "{}", us),
333 ConstScalar::Unknown => write!(fmt, "_"),
334 }
335 }
336}
337
338impl ConstScalar {
339 /// Gets a target usize out of the ConstScalar
340 pub fn as_usize(&self) -> Option<u64> {
341 match self {
342 &ConstScalar::Usize(us) => Some(us),
343 _ => None,
344 }
345 }
346
347 // FIXME: as per the comments on `TypeRef::Array`, this evaluation should not happen at this
348 // parse stage.
349 fn usize_from_literal_expr(expr: ast::Expr) -> ConstScalar {
350 match expr {
351 ast::Expr::Literal(lit) => {
352 let lkind = lit.kind();
353 match lkind {
354 ast::LiteralKind::IntNumber(num)
355 if num.suffix() == None || num.suffix() == Some("usize") =>
356 {
357 num.value().and_then(|v| v.try_into().ok())
358 }
359 _ => None,
360 }
361 }
362 _ => None,
363 }
364 .map(ConstScalar::Usize)
365 .unwrap_or(ConstScalar::Unknown)
366 }
367}
diff --git a/crates/hir_expand/src/builtin_derive.rs b/crates/hir_expand/src/builtin_derive.rs
index 537c03028..b6a6d602f 100644
--- a/crates/hir_expand/src/builtin_derive.rs
+++ b/crates/hir_expand/src/builtin_derive.rs
@@ -269,7 +269,7 @@ mod tests {
269 use expect_test::{expect, Expect}; 269 use expect_test::{expect, Expect};
270 use name::AsName; 270 use name::AsName;
271 271
272 use crate::{test_db::TestDB, AstId, AttrId, MacroCallId, MacroCallKind, MacroCallLoc}; 272 use crate::{test_db::TestDB, AstId, MacroCallId, MacroCallKind, MacroCallLoc};
273 273
274 use super::*; 274 use super::*;
275 275
@@ -320,7 +320,7 @@ $0
320 kind: MacroCallKind::Derive { 320 kind: MacroCallKind::Derive {
321 ast_id, 321 ast_id,
322 derive_name: name.to_string(), 322 derive_name: name.to_string(),
323 derive_attr: AttrId(0), 323 derive_attr_index: 0,
324 }, 324 },
325 }; 325 };
326 326
diff --git a/crates/hir_expand/src/builtin_macro.rs b/crates/hir_expand/src/builtin_macro.rs
index 179de61f9..280c25f11 100644
--- a/crates/hir_expand/src/builtin_macro.rs
+++ b/crates/hir_expand/src/builtin_macro.rs
@@ -118,6 +118,7 @@ register_builtin! {
118 EAGER: 118 EAGER:
119 (compile_error, CompileError) => compile_error_expand, 119 (compile_error, CompileError) => compile_error_expand,
120 (concat, Concat) => concat_expand, 120 (concat, Concat) => concat_expand,
121 (concat_idents, ConcatIdents) => concat_idents_expand,
121 (include, Include) => include_expand, 122 (include, Include) => include_expand,
122 (include_bytes, IncludeBytes) => include_bytes_expand, 123 (include_bytes, IncludeBytes) => include_bytes_expand,
123 (include_str, IncludeStr) => include_str_expand, 124 (include_str, IncludeStr) => include_str_expand,
@@ -373,6 +374,28 @@ fn concat_expand(
373 ExpandResult { value: Some(ExpandedEager::new(quote!(#text), FragmentKind::Expr)), err } 374 ExpandResult { value: Some(ExpandedEager::new(quote!(#text), FragmentKind::Expr)), err }
374} 375}
375 376
377fn concat_idents_expand(
378 _db: &dyn AstDatabase,
379 _arg_id: EagerMacroId,
380 tt: &tt::Subtree,
381) -> ExpandResult<Option<ExpandedEager>> {
382 let mut err = None;
383 let mut ident = String::new();
384 for (i, t) in tt.token_trees.iter().enumerate() {
385 match t {
386 tt::TokenTree::Leaf(tt::Leaf::Ident(id)) => {
387 ident.push_str(id.text.as_str());
388 }
389 tt::TokenTree::Leaf(tt::Leaf::Punct(punct)) if i % 2 == 1 && punct.char == ',' => (),
390 _ => {
391 err.get_or_insert(mbe::ExpandError::UnexpectedToken);
392 }
393 }
394 }
395 let ident = tt::Ident { text: ident.into(), id: tt::TokenId::unspecified() };
396 ExpandResult { value: Some(ExpandedEager::new(quote!(#ident), FragmentKind::Expr)), err }
397}
398
376fn relative_file( 399fn relative_file(
377 db: &dyn AstDatabase, 400 db: &dyn AstDatabase,
378 call_id: MacroCallId, 401 call_id: MacroCallId,
@@ -578,6 +601,7 @@ mod tests {
578 krate, 601 krate,
579 kind: MacroCallKind::FnLike { 602 kind: MacroCallKind::FnLike {
580 ast_id: AstId::new(file_id.into(), ast_id_map.ast_id(&macro_call)), 603 ast_id: AstId::new(file_id.into(), ast_id_map.ast_id(&macro_call)),
604 fragment: FragmentKind::Expr,
581 }, 605 },
582 }; 606 };
583 607
@@ -788,9 +812,21 @@ mod tests {
788 r##" 812 r##"
789 #[rustc_builtin_macro] 813 #[rustc_builtin_macro]
790 macro_rules! concat {} 814 macro_rules! concat {}
791 concat!("foo", "r", 0, r#"bar"#, false); 815 concat!("foo", "r", 0, r#"bar"#, "\n", false);
816 "##,
817 expect![[r#""foor0bar\nfalse""#]],
818 );
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);
792 "##, 828 "##,
793 expect![[r#""foor0barfalse""#]], 829 expect![[r#"foobar"#]],
794 ); 830 );
795 } 831 }
796} 832}
diff --git a/crates/hir_expand/src/db.rs b/crates/hir_expand/src/db.rs
index d61f4b31a..c43d382ad 100644
--- a/crates/hir_expand/src/db.rs
+++ b/crates/hir_expand/src/db.rs
@@ -8,15 +8,13 @@ use parser::FragmentKind;
8use syntax::{ 8use syntax::{
9 algo::diff, 9 algo::diff,
10 ast::{self, NameOwner}, 10 ast::{self, NameOwner},
11 AstNode, GreenNode, Parse, 11 AstNode, GreenNode, Parse, SyntaxNode, SyntaxToken,
12 SyntaxKind::*,
13 SyntaxNode, SyntaxToken,
14}; 12};
15 13
16use crate::{ 14use crate::{
17 ast_id_map::AstIdMap, hygiene::HygieneFrame, BuiltinDeriveExpander, BuiltinFnLikeExpander, 15 ast_id_map::AstIdMap, hygiene::HygieneFrame, input::process_macro_input, BuiltinDeriveExpander,
18 EagerCallLoc, EagerMacroId, HirFileId, HirFileIdRepr, LazyMacroId, MacroCallId, MacroCallLoc, 16 BuiltinFnLikeExpander, EagerCallLoc, EagerMacroId, HirFileId, HirFileIdRepr, LazyMacroId,
19 MacroDefId, MacroDefKind, MacroFile, ProcMacroExpander, 17 MacroCallId, MacroCallLoc, MacroDefId, MacroDefKind, MacroFile, ProcMacroExpander,
20}; 18};
21 19
22/// Total limit on the number of tokens produced by any macro invocation. 20/// Total limit on the number of tokens produced by any macro invocation.
@@ -160,7 +158,7 @@ pub fn expand_hypothetical(
160 158
161 let hypothetical_expansion = macro_def.expand(db, lazy_id, &tt); 159 let hypothetical_expansion = macro_def.expand(db, lazy_id, &tt);
162 160
163 let fragment_kind = to_fragment_kind(db, actual_macro_call); 161 let fragment_kind = macro_fragment_kind(db, actual_macro_call);
164 162
165 let (node, tmap_2) = 163 let (node, tmap_2) =
166 mbe::token_tree_to_syntax_node(&hypothetical_expansion.value, fragment_kind).ok()?; 164 mbe::token_tree_to_syntax_node(&hypothetical_expansion.value, fragment_kind).ok()?;
@@ -226,7 +224,7 @@ fn parse_macro_expansion(
226 None => return ExpandResult { value: None, err: result.err }, 224 None => return ExpandResult { value: None, err: result.err },
227 }; 225 };
228 226
229 let fragment_kind = to_fragment_kind(db, macro_file.macro_call_id); 227 let fragment_kind = macro_fragment_kind(db, macro_file.macro_call_id);
230 228
231 log::debug!("expanded = {}", tt.as_debug_string()); 229 log::debug!("expanded = {}", tt.as_debug_string());
232 log::debug!("kind = {:?}", fragment_kind); 230 log::debug!("kind = {:?}", fragment_kind);
@@ -269,7 +267,16 @@ fn parse_macro_expansion(
269 267
270fn macro_arg(db: &dyn AstDatabase, id: MacroCallId) -> Option<Arc<(tt::Subtree, mbe::TokenMap)>> { 268fn macro_arg(db: &dyn AstDatabase, id: MacroCallId) -> Option<Arc<(tt::Subtree, mbe::TokenMap)>> {
271 let arg = db.macro_arg_text(id)?; 269 let arg = db.macro_arg_text(id)?;
272 let (tt, tmap) = mbe::syntax_node_to_token_tree(&SyntaxNode::new_root(arg)); 270 let (mut tt, tmap) = mbe::syntax_node_to_token_tree(&SyntaxNode::new_root(arg));
271
272 if let MacroCallId::LazyMacro(id) = id {
273 let loc: MacroCallLoc = db.lookup_intern_macro(id);
274 if loc.def.is_proc_macro() {
275 // proc macros expect their inputs without parentheses, MBEs expect it with them included
276 tt.delimiter = None;
277 }
278 }
279
273 Some(Arc::new((tt, tmap))) 280 Some(Arc::new((tt, tmap)))
274} 281}
275 282
@@ -283,6 +290,7 @@ fn macro_arg_text(db: &dyn AstDatabase, id: MacroCallId) -> Option<GreenNode> {
283 }; 290 };
284 let loc = db.lookup_intern_macro(id); 291 let loc = db.lookup_intern_macro(id);
285 let arg = loc.kind.arg(db)?; 292 let arg = loc.kind.arg(db)?;
293 let arg = process_macro_input(db, arg, id);
286 Some(arg.green().into()) 294 Some(arg.green().into())
287} 295}
288 296
@@ -427,62 +435,15 @@ fn hygiene_frame(db: &dyn AstDatabase, file_id: HirFileId) -> Arc<HygieneFrame>
427 Arc::new(HygieneFrame::new(db, file_id)) 435 Arc::new(HygieneFrame::new(db, file_id))
428} 436}
429 437
430/// Given a `MacroCallId`, return what `FragmentKind` it belongs to. 438fn macro_fragment_kind(db: &dyn AstDatabase, id: MacroCallId) -> FragmentKind {
431/// FIXME: Not completed 439 match id {
432fn to_fragment_kind(db: &dyn AstDatabase, id: MacroCallId) -> FragmentKind { 440 MacroCallId::LazyMacro(id) => {
433 let lazy_id = match id { 441 let loc: MacroCallLoc = db.lookup_intern_macro(id);
434 MacroCallId::LazyMacro(id) => id, 442 loc.kind.fragment_kind()
435 MacroCallId::EagerMacro(id) => {
436 return db.lookup_intern_eager_expansion(id).fragment;
437 }
438 };
439 let syn = db.lookup_intern_macro(lazy_id).kind.node(db).value;
440
441 let parent = match syn.parent() {
442 Some(it) => it,
443 None => return FragmentKind::Statements,
444 };
445
446 match parent.kind() {
447 MACRO_ITEMS | SOURCE_FILE => FragmentKind::Items,
448 MACRO_STMTS => FragmentKind::Statements,
449 MACRO_PAT => FragmentKind::Pattern,
450 MACRO_TYPE => FragmentKind::Type,
451 ITEM_LIST => FragmentKind::Items,
452 LET_STMT => {
453 // FIXME: Handle LHS Pattern
454 FragmentKind::Expr
455 } 443 }
456 EXPR_STMT => FragmentKind::Statements, 444 MacroCallId::EagerMacro(id) => {
457 BLOCK_EXPR => FragmentKind::Statements, 445 let loc: EagerCallLoc = db.lookup_intern_eager_expansion(id);
458 ARG_LIST => FragmentKind::Expr, 446 loc.fragment
459 TRY_EXPR => FragmentKind::Expr,
460 TUPLE_EXPR => FragmentKind::Expr,
461 PAREN_EXPR => FragmentKind::Expr,
462 ARRAY_EXPR => FragmentKind::Expr,
463 FOR_EXPR => FragmentKind::Expr,
464 PATH_EXPR => FragmentKind::Expr,
465 CLOSURE_EXPR => FragmentKind::Expr,
466 CONDITION => FragmentKind::Expr,
467 BREAK_EXPR => FragmentKind::Expr,
468 RETURN_EXPR => FragmentKind::Expr,
469 MATCH_EXPR => FragmentKind::Expr,
470 MATCH_ARM => FragmentKind::Expr,
471 MATCH_GUARD => FragmentKind::Expr,
472 RECORD_EXPR_FIELD => FragmentKind::Expr,
473 CALL_EXPR => FragmentKind::Expr,
474 INDEX_EXPR => FragmentKind::Expr,
475 METHOD_CALL_EXPR => FragmentKind::Expr,
476 FIELD_EXPR => FragmentKind::Expr,
477 AWAIT_EXPR => FragmentKind::Expr,
478 CAST_EXPR => FragmentKind::Expr,
479 REF_EXPR => FragmentKind::Expr,
480 PREFIX_EXPR => FragmentKind::Expr,
481 RANGE_EXPR => FragmentKind::Expr,
482 BIN_EXPR => FragmentKind::Expr,
483 _ => {
484 // Unknown , Just guess it is `Items`
485 FragmentKind::Items
486 } 447 }
487 } 448 }
488} 449}
diff --git a/crates/hir_expand/src/eager.rs b/crates/hir_expand/src/eager.rs
index f12132f84..85491fe8b 100644
--- a/crates/hir_expand/src/eager.rs
+++ b/crates/hir_expand/src/eager.rs
@@ -175,8 +175,13 @@ fn lazy_expand(
175) -> ExpandResult<Option<InFile<SyntaxNode>>> { 175) -> ExpandResult<Option<InFile<SyntaxNode>>> {
176 let ast_id = db.ast_id_map(macro_call.file_id).ast_id(&macro_call.value); 176 let ast_id = db.ast_id_map(macro_call.file_id).ast_id(&macro_call.value);
177 177
178 let fragment = crate::to_fragment_kind(&macro_call.value);
178 let id: MacroCallId = def 179 let id: MacroCallId = def
179 .as_lazy_macro(db, krate, MacroCallKind::FnLike { ast_id: macro_call.with_value(ast_id) }) 180 .as_lazy_macro(
181 db,
182 krate,
183 MacroCallKind::FnLike { ast_id: macro_call.with_value(ast_id), fragment },
184 )
180 .into(); 185 .into();
181 186
182 let err = db.macro_expand_error(id); 187 let err = db.macro_expand_error(id);
diff --git a/crates/hir_expand/src/input.rs b/crates/hir_expand/src/input.rs
new file mode 100644
index 000000000..112216859
--- /dev/null
+++ b/crates/hir_expand/src/input.rs
@@ -0,0 +1,94 @@
1//! Macro input conditioning.
2
3use syntax::{
4 ast::{self, AttrsOwner},
5 AstNode, SyntaxNode,
6};
7
8use crate::{
9 db::AstDatabase,
10 name::{name, AsName},
11 LazyMacroId, MacroCallKind, MacroCallLoc,
12};
13
14pub(crate) fn process_macro_input(
15 db: &dyn AstDatabase,
16 node: SyntaxNode,
17 id: LazyMacroId,
18) -> SyntaxNode {
19 let loc: MacroCallLoc = db.lookup_intern_macro(id);
20
21 match loc.kind {
22 MacroCallKind::FnLike { .. } => node,
23 MacroCallKind::Derive { derive_attr_index, .. } => {
24 let item = match ast::Item::cast(node.clone()) {
25 Some(item) => item,
26 None => return node,
27 };
28
29 remove_derives_up_to(item, derive_attr_index as usize).syntax().clone()
30 }
31 }
32}
33
34/// Removes `#[derive]` attributes from `item`, up to `attr_index`.
35fn remove_derives_up_to(item: ast::Item, attr_index: usize) -> ast::Item {
36 let item = item.clone_for_update();
37 for attr in item.attrs().take(attr_index + 1) {
38 if let Some(name) =
39 attr.path().and_then(|path| path.as_single_segment()).and_then(|seg| seg.name_ref())
40 {
41 if name.as_name() == name![derive] {
42 attr.syntax().detach();
43 }
44 }
45 }
46 item
47}
48
49#[cfg(test)]
50mod tests {
51 use base_db::fixture::WithFixture;
52 use base_db::SourceDatabase;
53 use expect_test::{expect, Expect};
54
55 use crate::test_db::TestDB;
56
57 use super::*;
58
59 fn test_remove_derives_up_to(attr: usize, ra_fixture: &str, expect: Expect) {
60 let (db, file_id) = TestDB::with_single_file(&ra_fixture);
61 let parsed = db.parse(file_id);
62
63 let mut items: Vec<_> =
64 parsed.syntax_node().descendants().filter_map(ast::Item::cast).collect();
65 assert_eq!(items.len(), 1);
66
67 let item = remove_derives_up_to(items.pop().unwrap(), attr);
68 expect.assert_eq(&item.to_string());
69 }
70
71 #[test]
72 fn remove_derive() {
73 test_remove_derives_up_to(
74 2,
75 r#"
76#[allow(unused)]
77#[derive(Copy)]
78#[derive(Hello)]
79#[derive(Clone)]
80struct A {
81 bar: u32
82}
83 "#,
84 expect![[r#"
85#[allow(unused)]
86
87
88#[derive(Clone)]
89struct A {
90 bar: u32
91}"#]],
92 );
93 }
94}
diff --git a/crates/hir_expand/src/lib.rs b/crates/hir_expand/src/lib.rs
index 0402640de..88cb16ca4 100644
--- a/crates/hir_expand/src/lib.rs
+++ b/crates/hir_expand/src/lib.rs
@@ -14,9 +14,12 @@ pub mod builtin_macro;
14pub mod proc_macro; 14pub mod proc_macro;
15pub mod quote; 15pub mod quote;
16pub mod eager; 16pub mod eager;
17mod input;
17 18
18use either::Either; 19use either::Either;
20
19pub use mbe::{ExpandError, ExpandResult}; 21pub use mbe::{ExpandError, ExpandResult};
22pub use parser::FragmentKind;
20 23
21use std::hash::Hash; 24use std::hash::Hash;
22use std::sync::Arc; 25use std::sync::Arc;
@@ -269,6 +272,10 @@ impl MacroDefId {
269 }; 272 };
270 Either::Left(*id) 273 Either::Left(*id)
271 } 274 }
275
276 pub fn is_proc_macro(&self) -> bool {
277 matches!(self.kind, MacroDefKind::ProcMacro(..))
278 }
272} 279}
273 280
274#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 281#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
@@ -290,13 +297,21 @@ pub struct MacroCallLoc {
290 297
291#[derive(Debug, Clone, PartialEq, Eq, Hash)] 298#[derive(Debug, Clone, PartialEq, Eq, Hash)]
292pub enum MacroCallKind { 299pub enum MacroCallKind {
293 FnLike { ast_id: AstId<ast::MacroCall> }, 300 FnLike {
294 Derive { ast_id: AstId<ast::Item>, derive_name: String, derive_attr: AttrId }, 301 ast_id: AstId<ast::MacroCall>,
302 fragment: FragmentKind,
303 },
304 Derive {
305 ast_id: AstId<ast::Item>,
306 derive_name: String,
307 /// Syntactical index of the invoking `#[derive]` attribute.
308 ///
309 /// Outer attributes are counted first, then inner attributes. This does not support
310 /// out-of-line modules, which may have attributes spread across 2 files!
311 derive_attr_index: u32,
312 },
295} 313}
296 314
297#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
298pub struct AttrId(pub u32);
299
300impl MacroCallKind { 315impl MacroCallKind {
301 fn file_id(&self) -> HirFileId { 316 fn file_id(&self) -> HirFileId {
302 match self { 317 match self {
@@ -324,6 +339,13 @@ impl MacroCallKind {
324 MacroCallKind::Derive { ast_id, .. } => Some(ast_id.to_node(db).syntax().clone()), 339 MacroCallKind::Derive { ast_id, .. } => Some(ast_id.to_node(db).syntax().clone()),
325 } 340 }
326 } 341 }
342
343 fn fragment_kind(&self) -> FragmentKind {
344 match self {
345 MacroCallKind::FnLike { fragment, .. } => *fragment,
346 MacroCallKind::Derive { .. } => FragmentKind::Items,
347 }
348 }
327} 349}
328 350
329impl MacroCallId { 351impl MacroCallId {
@@ -357,7 +379,6 @@ pub struct ExpansionInfo {
357} 379}
358 380
359pub use mbe::Origin; 381pub use mbe::Origin;
360use parser::FragmentKind;
361 382
362impl ExpansionInfo { 383impl ExpansionInfo {
363 pub fn call_node(&self) -> Option<InFile<SyntaxNode>> { 384 pub fn call_node(&self) -> Option<InFile<SyntaxNode>> {
@@ -562,3 +583,59 @@ impl<N: AstNode> InFile<N> {
562 self.with_value(self.value.syntax()) 583 self.with_value(self.value.syntax())
563 } 584 }
564} 585}
586
587/// Given a `MacroCallId`, return what `FragmentKind` it belongs to.
588/// FIXME: Not completed
589pub fn to_fragment_kind(call: &ast::MacroCall) -> FragmentKind {
590 use syntax::SyntaxKind::*;
591
592 let syn = call.syntax();
593
594 let parent = match syn.parent() {
595 Some(it) => it,
596 None => return FragmentKind::Statements,
597 };
598
599 match parent.kind() {
600 MACRO_ITEMS | SOURCE_FILE => FragmentKind::Items,
601 MACRO_STMTS => FragmentKind::Statements,
602 MACRO_PAT => FragmentKind::Pattern,
603 MACRO_TYPE => FragmentKind::Type,
604 ITEM_LIST => FragmentKind::Items,
605 LET_STMT => {
606 // FIXME: Handle LHS Pattern
607 FragmentKind::Expr
608 }
609 EXPR_STMT => FragmentKind::Statements,
610 BLOCK_EXPR => FragmentKind::Statements,
611 ARG_LIST => FragmentKind::Expr,
612 TRY_EXPR => FragmentKind::Expr,
613 TUPLE_EXPR => FragmentKind::Expr,
614 PAREN_EXPR => FragmentKind::Expr,
615 ARRAY_EXPR => FragmentKind::Expr,
616 FOR_EXPR => FragmentKind::Expr,
617 PATH_EXPR => FragmentKind::Expr,
618 CLOSURE_EXPR => FragmentKind::Expr,
619 CONDITION => FragmentKind::Expr,
620 BREAK_EXPR => FragmentKind::Expr,
621 RETURN_EXPR => FragmentKind::Expr,
622 MATCH_EXPR => FragmentKind::Expr,
623 MATCH_ARM => FragmentKind::Expr,
624 MATCH_GUARD => FragmentKind::Expr,
625 RECORD_EXPR_FIELD => FragmentKind::Expr,
626 CALL_EXPR => FragmentKind::Expr,
627 INDEX_EXPR => FragmentKind::Expr,
628 METHOD_CALL_EXPR => FragmentKind::Expr,
629 FIELD_EXPR => FragmentKind::Expr,
630 AWAIT_EXPR => FragmentKind::Expr,
631 CAST_EXPR => FragmentKind::Expr,
632 REF_EXPR => FragmentKind::Expr,
633 PREFIX_EXPR => FragmentKind::Expr,
634 RANGE_EXPR => FragmentKind::Expr,
635 BIN_EXPR => FragmentKind::Expr,
636 _ => {
637 // Unknown , Just guess it is `Items`
638 FragmentKind::Items
639 }
640 }
641}
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_expand/src/proc_macro.rs b/crates/hir_expand/src/proc_macro.rs
index 75e950816..d5643393a 100644
--- a/crates/hir_expand/src/proc_macro.rs
+++ b/crates/hir_expand/src/proc_macro.rs
@@ -2,7 +2,6 @@
2 2
3use crate::db::AstDatabase; 3use crate::db::AstDatabase;
4use base_db::{CrateId, ProcMacroId}; 4use base_db::{CrateId, ProcMacroId};
5use tt::buffer::{Cursor, TokenBuffer};
6 5
7#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] 6#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
8pub struct ProcMacroExpander { 7pub struct ProcMacroExpander {
@@ -44,9 +43,6 @@ impl ProcMacroExpander {
44 .clone() 43 .clone()
45 .ok_or_else(|| err!("No derive macro found."))?; 44 .ok_or_else(|| err!("No derive macro found."))?;
46 45
47 let tt = remove_derive_attrs(tt)
48 .ok_or_else(|| err!("Fail to remove derive for custom derive"))?;
49
50 // Proc macros have access to the environment variables of the invoking crate. 46 // Proc macros have access to the environment variables of the invoking crate.
51 let env = &krate_graph[calling_crate].env; 47 let env = &krate_graph[calling_crate].env;
52 48
@@ -56,101 +52,3 @@ impl ProcMacroExpander {
56 } 52 }
57 } 53 }
58} 54}
59
60fn eat_punct(cursor: &mut Cursor, c: char) -> bool {
61 if let Some(tt::buffer::TokenTreeRef::Leaf(tt::Leaf::Punct(punct), _)) = cursor.token_tree() {
62 if punct.char == c {
63 *cursor = cursor.bump();
64 return true;
65 }
66 }
67 false
68}
69
70fn eat_subtree(cursor: &mut Cursor, kind: tt::DelimiterKind) -> bool {
71 if let Some(tt::buffer::TokenTreeRef::Subtree(subtree, _)) = cursor.token_tree() {
72 if Some(kind) == subtree.delimiter_kind() {
73 *cursor = cursor.bump_subtree();
74 return true;
75 }
76 }
77 false
78}
79
80fn eat_ident(cursor: &mut Cursor, t: &str) -> bool {
81 if let Some(tt::buffer::TokenTreeRef::Leaf(tt::Leaf::Ident(ident), _)) = cursor.token_tree() {
82 if t == ident.text.as_str() {
83 *cursor = cursor.bump();
84 return true;
85 }
86 }
87 false
88}
89
90fn remove_derive_attrs(tt: &tt::Subtree) -> Option<tt::Subtree> {
91 let buffer = TokenBuffer::from_tokens(&tt.token_trees);
92 let mut p = buffer.begin();
93 let mut result = tt::Subtree::default();
94
95 while !p.eof() {
96 let curr = p;
97
98 if eat_punct(&mut p, '#') {
99 eat_punct(&mut p, '!');
100 let parent = p;
101 if eat_subtree(&mut p, tt::DelimiterKind::Bracket) {
102 if eat_ident(&mut p, "derive") {
103 p = parent.bump();
104 continue;
105 }
106 }
107 }
108
109 result.token_trees.push(curr.token_tree()?.cloned());
110 p = curr.bump();
111 }
112
113 Some(result)
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119 use test_utils::assert_eq_text;
120
121 #[test]
122 fn test_remove_derive_attrs() {
123 let tt = mbe::parse_to_token_tree(
124 r#"
125 #[allow(unused)]
126 #[derive(Copy)]
127 #[derive(Hello)]
128 struct A {
129 bar: u32
130 }
131"#,
132 )
133 .unwrap()
134 .0;
135 let result = format!("{:#?}", remove_derive_attrs(&tt).unwrap());
136
137 assert_eq_text!(
138 r#"
139SUBTREE $
140 PUNCH # [alone] 0
141 SUBTREE [] 1
142 IDENT allow 2
143 SUBTREE () 3
144 IDENT unused 4
145 IDENT struct 15
146 IDENT A 16
147 SUBTREE {} 17
148 IDENT bar 18
149 PUNCH : [alone] 19
150 IDENT u32 20
151"#
152 .trim(),
153 &result
154 );
155 }
156}
diff --git a/crates/hir_expand/src/quote.rs b/crates/hir_expand/src/quote.rs
index c82487ef0..230a59964 100644
--- a/crates/hir_expand/src/quote.rs
+++ b/crates/hir_expand/src/quote.rs
@@ -196,8 +196,8 @@ impl_to_to_tokentrees! {
196 tt::Literal => self { self }; 196 tt::Literal => self { self };
197 tt::Ident => self { self }; 197 tt::Ident => self { self };
198 tt::Punct => self { self }; 198 tt::Punct => self { self };
199 &str => self { tt::Literal{text: format!("{:?}", self.escape_default().to_string()).into(), id: tt::TokenId::unspecified()}}; 199 &str => self { tt::Literal{text: format!("\"{}\"", self.escape_debug()).into(), id: tt::TokenId::unspecified()}};
200 String => self { tt::Literal{text: format!("{:?}", self.escape_default().to_string()).into(), id: tt::TokenId::unspecified()}} 200 String => self { tt::Literal{text: format!("\"{}\"", self.escape_debug()).into(), id: tt::TokenId::unspecified()}}
201} 201}
202 202
203#[cfg(test)] 203#[cfg(test)]
diff --git a/crates/hir_ty/src/chalk_ext.rs b/crates/hir_ty/src/chalk_ext.rs
index 8c4542956..5232a7d80 100644
--- a/crates/hir_ty/src/chalk_ext.rs
+++ b/crates/hir_ty/src/chalk_ext.rs
@@ -1,8 +1,10 @@
1//! Various extensions traits for Chalk types. 1//! Various extensions traits for Chalk types.
2 2
3use chalk_ir::Mutability; 3use chalk_ir::{FloatTy, IntTy, Mutability, Scalar, UintTy};
4use hir_def::{ 4use hir_def::{
5 type_ref::Rawness, AssocContainerId, FunctionId, GenericDefId, HasModule, Lookup, TraitId, 5 builtin_type::{BuiltinFloat, BuiltinInt, BuiltinType, BuiltinUint},
6 type_ref::Rawness,
7 AssocContainerId, FunctionId, GenericDefId, HasModule, Lookup, TraitId,
6}; 8};
7 9
8use crate::{ 10use crate::{
@@ -18,6 +20,7 @@ pub trait TyExt {
18 fn is_unknown(&self) -> bool; 20 fn is_unknown(&self) -> bool;
19 21
20 fn as_adt(&self) -> Option<(hir_def::AdtId, &Substitution)>; 22 fn as_adt(&self) -> Option<(hir_def::AdtId, &Substitution)>;
23 fn as_builtin(&self) -> Option<BuiltinType>;
21 fn as_tuple(&self) -> Option<&Substitution>; 24 fn as_tuple(&self) -> Option<&Substitution>;
22 fn as_fn_def(&self, db: &dyn HirDatabase) -> Option<FunctionId>; 25 fn as_fn_def(&self, db: &dyn HirDatabase) -> Option<FunctionId>;
23 fn as_reference(&self) -> Option<(&Ty, Lifetime, Mutability)>; 26 fn as_reference(&self) -> Option<(&Ty, Lifetime, Mutability)>;
@@ -59,6 +62,35 @@ impl TyExt for Ty {
59 } 62 }
60 } 63 }
61 64
65 fn as_builtin(&self) -> Option<BuiltinType> {
66 match self.kind(&Interner) {
67 TyKind::Str => Some(BuiltinType::Str),
68 TyKind::Scalar(Scalar::Bool) => Some(BuiltinType::Bool),
69 TyKind::Scalar(Scalar::Char) => Some(BuiltinType::Char),
70 TyKind::Scalar(Scalar::Float(fty)) => Some(BuiltinType::Float(match fty {
71 FloatTy::F64 => BuiltinFloat::F64,
72 FloatTy::F32 => BuiltinFloat::F32,
73 })),
74 TyKind::Scalar(Scalar::Int(ity)) => Some(BuiltinType::Int(match ity {
75 IntTy::Isize => BuiltinInt::Isize,
76 IntTy::I8 => BuiltinInt::I8,
77 IntTy::I16 => BuiltinInt::I16,
78 IntTy::I32 => BuiltinInt::I32,
79 IntTy::I64 => BuiltinInt::I64,
80 IntTy::I128 => BuiltinInt::I128,
81 })),
82 TyKind::Scalar(Scalar::Uint(ity)) => Some(BuiltinType::Uint(match ity {
83 UintTy::Usize => BuiltinUint::Usize,
84 UintTy::U8 => BuiltinUint::U8,
85 UintTy::U16 => BuiltinUint::U16,
86 UintTy::U32 => BuiltinUint::U32,
87 UintTy::U64 => BuiltinUint::U64,
88 UintTy::U128 => BuiltinUint::U128,
89 })),
90 _ => None,
91 }
92 }
93
62 fn as_tuple(&self) -> Option<&Substitution> { 94 fn as_tuple(&self) -> Option<&Substitution> {
63 match self.kind(&Interner) { 95 match self.kind(&Interner) {
64 TyKind::Tuple(_, substs) => Some(substs), 96 TyKind::Tuple(_, substs) => Some(substs),
diff --git a/crates/hir_ty/src/consteval.rs b/crates/hir_ty/src/consteval.rs
new file mode 100644
index 000000000..e3ceb3d62
--- /dev/null
+++ b/crates/hir_ty/src/consteval.rs
@@ -0,0 +1,56 @@
1//! Constant evaluation details
2
3use std::convert::TryInto;
4
5use hir_def::{
6 builtin_type::BuiltinUint,
7 expr::{Expr, Literal},
8 type_ref::ConstScalar,
9};
10
11use crate::{Const, ConstData, ConstValue, Interner, TyKind};
12
13/// Extension trait for [`Const`]
14pub trait ConstExt {
15 /// Is a [`Const`] unknown?
16 fn is_unknown(&self) -> bool;
17}
18
19impl ConstExt for Const {
20 fn is_unknown(&self) -> bool {
21 match self.data(&Interner).value {
22 // interned Unknown
23 chalk_ir::ConstValue::Concrete(chalk_ir::ConcreteConst {
24 interned: ConstScalar::Unknown,
25 }) => true,
26
27 // interned concrete anything else
28 chalk_ir::ConstValue::Concrete(..) => false,
29
30 _ => {
31 log::error!("is_unknown was called on a non-concrete constant value! {:?}", self);
32 true
33 }
34 }
35 }
36}
37
38// FIXME: support more than just evaluating literals
39pub fn eval_usize(expr: &Expr) -> Option<u64> {
40 match expr {
41 Expr::Literal(Literal::Uint(v, None))
42 | Expr::Literal(Literal::Uint(v, Some(BuiltinUint::Usize))) => (*v).try_into().ok(),
43 _ => None,
44 }
45}
46
47/// Interns a possibly-unknown target usize
48pub fn usize_const(value: Option<u64>) -> Const {
49 ConstData {
50 ty: TyKind::Scalar(chalk_ir::Scalar::Uint(chalk_ir::UintTy::Usize)).intern(&Interner),
51 value: ConstValue::Concrete(chalk_ir::ConcreteConst {
52 interned: value.map(|value| ConstScalar::Usize(value)).unwrap_or(ConstScalar::Unknown),
53 }),
54 }
55 .intern(&Interner)
56}
diff --git a/crates/hir_ty/src/diagnostics/expr.rs b/crates/hir_ty/src/diagnostics/expr.rs
index 79602c3dd..47709c1e8 100644
--- a/crates/hir_ty/src/diagnostics/expr.rs
+++ b/crates/hir_ty/src/diagnostics/expr.rs
@@ -83,7 +83,7 @@ impl<'a, 'b> ExprValidator<'a, 'b> {
83 if let Expr::Block { statements, tail, .. } = body_expr { 83 if let Expr::Block { statements, tail, .. } = body_expr {
84 if let Some(t) = tail { 84 if let Some(t) = tail {
85 self.validate_results_in_tail_expr(body.body_expr, *t, db); 85 self.validate_results_in_tail_expr(body.body_expr, *t, db);
86 } else if let Some(Statement::Expr(id)) = statements.last() { 86 } else if let Some(Statement::Expr { expr: id, .. }) = statements.last() {
87 self.validate_missing_tail_expr(body.body_expr, *id, db); 87 self.validate_missing_tail_expr(body.body_expr, *id, db);
88 } 88 }
89 } 89 }
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 1f6edf7a2..7bbd1a1f7 100644
--- a/crates/hir_ty/src/display.rs
+++ b/crates/hir_ty/src/display.rs
@@ -308,7 +308,7 @@ impl HirDisplay for Const {
308 let param_data = &generics.params.consts[id.local_id]; 308 let param_data = &generics.params.consts[id.local_id];
309 write!(f, "{}", param_data.name) 309 write!(f, "{}", param_data.name)
310 } 310 }
311 ConstValue::Concrete(_) => write!(f, "_"), 311 ConstValue::Concrete(c) => write!(f, "{}", c.interned),
312 } 312 }
313 } 313 }
314} 314}
@@ -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 50497eecb..b6b5a1b75 100644
--- a/crates/hir_ty/src/infer/expr.rs
+++ b/crates/hir_ty/src/infer/expr.rs
@@ -15,7 +15,7 @@ use stdx::always;
15use syntax::ast::RangeOp; 15use syntax::ast::RangeOp;
16 16
17use crate::{ 17use crate::{
18 autoderef, dummy_usize_const, 18 autoderef, consteval,
19 lower::lower_to_chalk_mutability, 19 lower::lower_to_chalk_mutability,
20 mapping::from_chalk, 20 mapping::from_chalk,
21 method_resolution, op, 21 method_resolution, op,
@@ -717,11 +717,12 @@ impl<'a> InferenceContext<'a> {
717 _ => self.table.new_type_var(), 717 _ => self.table.new_type_var(),
718 }; 718 };
719 719
720 match array { 720 let len = match array {
721 Array::ElementList(items) => { 721 Array::ElementList(items) => {
722 for expr in items.iter() { 722 for expr in items.iter() {
723 self.infer_expr_coerce(*expr, &Expectation::has_type(elem_ty.clone())); 723 self.infer_expr_coerce(*expr, &Expectation::has_type(elem_ty.clone()));
724 } 724 }
725 Some(items.len() as u64)
725 } 726 }
726 Array::Repeat { initializer, repeat } => { 727 Array::Repeat { initializer, repeat } => {
727 self.infer_expr_coerce( 728 self.infer_expr_coerce(
@@ -734,10 +735,13 @@ impl<'a> InferenceContext<'a> {
734 TyKind::Scalar(Scalar::Uint(UintTy::Usize)).intern(&Interner), 735 TyKind::Scalar(Scalar::Uint(UintTy::Usize)).intern(&Interner),
735 ), 736 ),
736 ); 737 );
738
739 let repeat_expr = &self.body.exprs[*repeat];
740 consteval::eval_usize(repeat_expr)
737 } 741 }
738 } 742 };
739 743
740 TyKind::Array(elem_ty, dummy_usize_const()).intern(&Interner) 744 TyKind::Array(elem_ty, consteval::usize_const(len)).intern(&Interner)
741 } 745 }
742 Expr::Literal(lit) => match lit { 746 Expr::Literal(lit) => match lit {
743 Literal::Bool(..) => TyKind::Scalar(Scalar::Bool).intern(&Interner), 747 Literal::Bool(..) => TyKind::Scalar(Scalar::Bool).intern(&Interner),
@@ -745,10 +749,12 @@ impl<'a> InferenceContext<'a> {
745 TyKind::Ref(Mutability::Not, static_lifetime(), TyKind::Str.intern(&Interner)) 749 TyKind::Ref(Mutability::Not, static_lifetime(), TyKind::Str.intern(&Interner))
746 .intern(&Interner) 750 .intern(&Interner)
747 } 751 }
748 Literal::ByteString(..) => { 752 Literal::ByteString(bs) => {
749 let byte_type = TyKind::Scalar(Scalar::Uint(UintTy::U8)).intern(&Interner); 753 let byte_type = TyKind::Scalar(Scalar::Uint(UintTy::U8)).intern(&Interner);
750 let array_type = 754
751 TyKind::Array(byte_type, dummy_usize_const()).intern(&Interner); 755 let len = consteval::usize_const(Some(bs.len() as u64));
756
757 let array_type = TyKind::Array(byte_type, len).intern(&Interner);
752 TyKind::Ref(Mutability::Not, static_lifetime(), array_type).intern(&Interner) 758 TyKind::Ref(Mutability::Not, static_lifetime(), array_type).intern(&Interner)
753 } 759 }
754 Literal::Char(..) => TyKind::Scalar(Scalar::Char).intern(&Interner), 760 Literal::Char(..) => TyKind::Scalar(Scalar::Char).intern(&Interner),
@@ -809,7 +815,7 @@ impl<'a> InferenceContext<'a> {
809 let ty = self.resolve_ty_as_possible(ty); 815 let ty = self.resolve_ty_as_possible(ty);
810 self.infer_pat(*pat, &ty, BindingMode::default()); 816 self.infer_pat(*pat, &ty, BindingMode::default());
811 } 817 }
812 Statement::Expr(expr) => { 818 Statement::Expr { expr, .. } => {
813 self.infer_expr(*expr, &Expectation::none()); 819 self.infer_expr(*expr, &Expectation::none());
814 } 820 }
815 } 821 }
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 a1656115d..7b4119747 100644
--- a/crates/hir_ty/src/interner.rs
+++ b/crates/hir_ty/src/interner.rs
@@ -6,6 +6,7 @@ use base_db::salsa::InternId;
6use chalk_ir::{Goal, GoalData}; 6use chalk_ir::{Goal, GoalData};
7use hir_def::{ 7use hir_def::{
8 intern::{impl_internable, InternStorage, Internable, Interned}, 8 intern::{impl_internable, InternStorage, Internable, Interned},
9 type_ref::ConstScalar,
9 TypeAliasId, 10 TypeAliasId,
10}; 11};
11use smallvec::SmallVec; 12use smallvec::SmallVec;
@@ -31,6 +32,7 @@ impl_internable!(
31 InternedWrapper<chalk_ir::TyData<Interner>>, 32 InternedWrapper<chalk_ir::TyData<Interner>>,
32 InternedWrapper<chalk_ir::LifetimeData<Interner>>, 33 InternedWrapper<chalk_ir::LifetimeData<Interner>>,
33 InternedWrapper<chalk_ir::ConstData<Interner>>, 34 InternedWrapper<chalk_ir::ConstData<Interner>>,
35 InternedWrapper<ConstScalar>,
34 InternedWrapper<Vec<chalk_ir::CanonicalVarKind<Interner>>>, 36 InternedWrapper<Vec<chalk_ir::CanonicalVarKind<Interner>>>,
35 InternedWrapper<Vec<chalk_ir::ProgramClause<Interner>>>, 37 InternedWrapper<Vec<chalk_ir::ProgramClause<Interner>>>,
36 InternedWrapper<Vec<chalk_ir::QuantifiedWhereClause<Interner>>>, 38 InternedWrapper<Vec<chalk_ir::QuantifiedWhereClause<Interner>>>,
@@ -41,7 +43,7 @@ impl chalk_ir::interner::Interner for Interner {
41 type InternedType = Interned<InternedWrapper<chalk_ir::TyData<Interner>>>; 43 type InternedType = Interned<InternedWrapper<chalk_ir::TyData<Interner>>>;
42 type InternedLifetime = Interned<InternedWrapper<chalk_ir::LifetimeData<Self>>>; 44 type InternedLifetime = Interned<InternedWrapper<chalk_ir::LifetimeData<Self>>>;
43 type InternedConst = Interned<InternedWrapper<chalk_ir::ConstData<Self>>>; 45 type InternedConst = Interned<InternedWrapper<chalk_ir::ConstData<Self>>>;
44 type InternedConcreteConst = (); 46 type InternedConcreteConst = ConstScalar;
45 type InternedGenericArg = chalk_ir::GenericArgData<Self>; 47 type InternedGenericArg = chalk_ir::GenericArgData<Self>;
46 type InternedGoal = Arc<GoalData<Self>>; 48 type InternedGoal = Arc<GoalData<Self>>;
47 type InternedGoals = Vec<Goal<Self>>; 49 type InternedGoals = Vec<Goal<Self>>;
@@ -245,10 +247,15 @@ impl chalk_ir::interner::Interner for Interner {
245 fn const_eq( 247 fn const_eq(
246 &self, 248 &self,
247 _ty: &Self::InternedType, 249 _ty: &Self::InternedType,
248 _c1: &Self::InternedConcreteConst, 250 c1: &Self::InternedConcreteConst,
249 _c2: &Self::InternedConcreteConst, 251 c2: &Self::InternedConcreteConst,
250 ) -> bool { 252 ) -> bool {
251 true 253 match (c1, c2) {
254 (&ConstScalar::Usize(a), &ConstScalar::Usize(b)) => a == b,
255 // we were previously assuming this to be true, I'm not whether true or false on
256 // unknown values is safer.
257 (_, _) => true,
258 }
252 } 259 }
253 260
254 fn intern_generic_arg( 261 fn intern_generic_arg(
diff --git a/crates/hir_ty/src/lib.rs b/crates/hir_ty/src/lib.rs
index 0505fa4ae..15b61bedc 100644
--- a/crates/hir_ty/src/lib.rs
+++ b/crates/hir_ty/src/lib.rs
@@ -10,6 +10,7 @@ mod autoderef;
10mod builder; 10mod builder;
11mod chalk_db; 11mod chalk_db;
12mod chalk_ext; 12mod chalk_ext;
13pub mod consteval;
13mod infer; 14mod infer;
14mod interner; 15mod interner;
15mod lower; 16mod lower;
@@ -37,7 +38,11 @@ use chalk_ir::{
37 interner::HasInterner, 38 interner::HasInterner,
38 UintTy, 39 UintTy,
39}; 40};
40use hir_def::{expr::ExprId, type_ref::Rawness, TypeParamId}; 41use hir_def::{
42 expr::ExprId,
43 type_ref::{ConstScalar, Rawness},
44 TypeParamId,
45};
41 46
42use crate::{db::HirDatabase, display::HirDisplay, utils::generics}; 47use crate::{db::HirDatabase, display::HirDisplay, utils::generics};
43 48
@@ -250,7 +255,9 @@ pub fn dummy_usize_const() -> Const {
250 let usize_ty = chalk_ir::TyKind::Scalar(Scalar::Uint(UintTy::Usize)).intern(&Interner); 255 let usize_ty = chalk_ir::TyKind::Scalar(Scalar::Uint(UintTy::Usize)).intern(&Interner);
251 chalk_ir::ConstData { 256 chalk_ir::ConstData {
252 ty: usize_ty, 257 ty: usize_ty,
253 value: chalk_ir::ConstValue::Concrete(chalk_ir::ConcreteConst { interned: () }), 258 value: chalk_ir::ConstValue::Concrete(chalk_ir::ConcreteConst {
259 interned: ConstScalar::Unknown,
260 }),
254 } 261 }
255 .intern(&Interner) 262 .intern(&Interner)
256} 263}
diff --git a/crates/hir_ty/src/lower.rs b/crates/hir_ty/src/lower.rs
index c99dd8d0a..bd8bb6028 100644
--- a/crates/hir_ty/src/lower.rs
+++ b/crates/hir_ty/src/lower.rs
@@ -29,8 +29,8 @@ use stdx::impl_from;
29use syntax::ast; 29use syntax::ast;
30 30
31use crate::{ 31use crate::{
32 consteval,
32 db::HirDatabase, 33 db::HirDatabase,
33 dummy_usize_const,
34 mapping::ToChalk, 34 mapping::ToChalk,
35 static_lifetime, to_assoc_type_id, to_chalk_trait_id, to_placeholder_idx, 35 static_lifetime, to_assoc_type_id, to_chalk_trait_id, to_placeholder_idx,
36 utils::{ 36 utils::{
@@ -172,9 +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 TyKind::Array(inner_ty, dummy_usize_const()).intern(&Interner) 177
178 let const_len = consteval::usize_const(len.as_usize());
179
180 TyKind::Array(inner_ty, const_len).intern(&Interner)
178 } 181 }
179 TypeRef::Slice(inner) => { 182 TypeRef::Slice(inner) => {
180 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 63d9d4e0b..190471069 100644
--- a/crates/hir_ty/src/tests/coercion.rs
+++ b/crates/hir_ty/src/tests/coercion.rs
@@ -55,7 +55,7 @@ fn coerce_places() {
55 impl<'a, 'b: 'a, T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<&'a U> for &'b T {} 55 impl<'a, 'b: 'a, T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<&'a U> for &'b T {}
56 impl<T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<*mut U> for *mut T {} 56 impl<T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<*mut U> for *mut T {}
57 "#, 57 "#,
58 expect![[r" 58 expect![[r#"
59 30..31 '_': &[T] 59 30..31 '_': &[T]
60 44..55 '{ loop {} }': T 60 44..55 '{ loop {} }': T
61 46..53 'loop {}': ! 61 46..53 'loop {}': !
@@ -64,43 +64,43 @@ 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; _] 75 211..215 '&[1]': &[u8; 1]
76 212..215 '[1]': [u8; _] 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]; _] 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}
106 106
@@ -113,17 +113,17 @@ fn infer_let_stmt_coerce() {
113 let x: *const [isize] = &[1]; 113 let x: *const [isize] = &[1];
114 } 114 }
115 ", 115 ",
116 expect![[r" 116 expect![[r#"
117 10..75 '{ ...[1]; }': () 117 10..75 '{ ...[1]; }': ()
118 20..21 'x': &[isize] 118 20..21 'x': &[isize]
119 34..38 '&[1]': &[isize; _] 119 34..38 '&[1]': &[isize; 1]
120 35..38 '[1]': [isize; _] 120 35..38 '[1]': [isize; 1]
121 36..37 '1': isize 121 36..37 '1': isize
122 48..49 'x': *const [isize] 122 48..49 'x': *const [isize]
123 68..72 '&[1]': &[isize; _] 123 68..72 '&[1]': &[isize; 1]
124 69..72 '[1]': [isize; _] 124 69..72 '[1]': [isize; 1]
125 70..71 '1': isize 125 70..71 '1': isize
126 "]], 126 "#]],
127 ); 127 );
128} 128}
129 129
@@ -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
@@ -208,7 +208,7 @@ fn infer_if_coerce() {
208 #[lang = "unsize"] 208 #[lang = "unsize"]
209 pub trait Unsize<T: ?Sized> {} 209 pub trait Unsize<T: ?Sized> {}
210 "#, 210 "#,
211 expect![[r" 211 expect![[r#"
212 10..11 'x': &[T] 212 10..11 'x': &[T]
213 27..38 '{ loop {} }': &[T] 213 27..38 '{ loop {} }': &[T]
214 29..36 'loop {}': ! 214 29..36 'loop {}': !
@@ -220,14 +220,14 @@ fn infer_if_coerce() {
220 71..96 '{ ... }': &[i32] 220 71..96 '{ ... }': &[i32]
221 81..84 'foo': fn foo<i32>(&[i32]) -> &[i32] 221 81..84 'foo': fn foo<i32>(&[i32]) -> &[i32]
222 81..90 'foo(&[1])': &[i32] 222 81..90 'foo(&[1])': &[i32]
223 85..89 '&[1]': &[i32; _] 223 85..89 '&[1]': &[i32; 1]
224 86..89 '[1]': [i32; _] 224 86..89 '[1]': [i32; 1]
225 87..88 '1': i32 225 87..88 '1': i32
226 102..122 '{ ... }': &[i32; _] 226 102..122 '{ ... }': &[i32; 1]
227 112..116 '&[1]': &[i32; _] 227 112..116 '&[1]': &[i32; 1]
228 113..116 '[1]': [i32; _] 228 113..116 '[1]': [i32; 1]
229 114..115 '1': i32 229 114..115 '1': i32
230 "]], 230 "#]],
231 ); 231 );
232} 232}
233 233
@@ -254,7 +254,7 @@ fn infer_if_else_coerce() {
254 impl<'a, 'b: 'a, T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<&'a U> for &'b T {} 254 impl<'a, 'b: 'a, T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<&'a U> for &'b T {}
255 impl<T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<*mut U> for *mut T {} 255 impl<T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<*mut U> for *mut T {}
256 "#, 256 "#,
257 expect![[r" 257 expect![[r#"
258 10..11 'x': &[T] 258 10..11 'x': &[T]
259 27..38 '{ loop {} }': &[T] 259 27..38 '{ loop {} }': &[T]
260 29..36 'loop {}': ! 260 29..36 'loop {}': !
@@ -263,17 +263,17 @@ fn infer_if_else_coerce() {
263 59..60 'x': &[i32] 263 59..60 'x': &[i32]
264 63..122 'if tru... }': &[i32] 264 63..122 'if tru... }': &[i32]
265 66..70 'true': bool 265 66..70 'true': bool
266 71..91 '{ ... }': &[i32; _] 266 71..91 '{ ... }': &[i32; 1]
267 81..85 '&[1]': &[i32; _] 267 81..85 '&[1]': &[i32; 1]
268 82..85 '[1]': [i32; _] 268 82..85 '[1]': [i32; 1]
269 83..84 '1': i32 269 83..84 '1': i32
270 97..122 '{ ... }': &[i32] 270 97..122 '{ ... }': &[i32]
271 107..110 'foo': fn foo<i32>(&[i32]) -> &[i32] 271 107..110 'foo': fn foo<i32>(&[i32]) -> &[i32]
272 107..116 'foo(&[1])': &[i32] 272 107..116 'foo(&[1])': &[i32]
273 111..115 '&[1]': &[i32; _] 273 111..115 '&[1]': &[i32; 1]
274 112..115 '[1]': [i32; _] 274 112..115 '[1]': [i32; 1]
275 113..114 '1': i32 275 113..114 '1': i32
276 "]], 276 "#]],
277 ) 277 )
278} 278}
279 279
@@ -295,7 +295,7 @@ fn infer_match_first_coerce() {
295 #[lang = "unsize"] 295 #[lang = "unsize"]
296 pub trait Unsize<T: ?Sized> {} 296 pub trait Unsize<T: ?Sized> {}
297 "#, 297 "#,
298 expect![[r" 298 expect![[r#"
299 10..11 'x': &[T] 299 10..11 'x': &[T]
300 27..38 '{ loop {} }': &[T] 300 27..38 '{ loop {} }': &[T]
301 29..36 'loop {}': ! 301 29..36 'loop {}': !
@@ -309,19 +309,19 @@ fn infer_match_first_coerce() {
309 87..88 '2': i32 309 87..88 '2': i32
310 92..95 'foo': fn foo<i32>(&[i32]) -> &[i32] 310 92..95 'foo': fn foo<i32>(&[i32]) -> &[i32]
311 92..101 'foo(&[2])': &[i32] 311 92..101 'foo(&[2])': &[i32]
312 96..100 '&[2]': &[i32; _] 312 96..100 '&[2]': &[i32; 1]
313 97..100 '[2]': [i32; _] 313 97..100 '[2]': [i32; 1]
314 98..99 '2': i32 314 98..99 '2': i32
315 111..112 '1': i32 315 111..112 '1': i32
316 111..112 '1': i32 316 111..112 '1': i32
317 116..120 '&[1]': &[i32; _] 317 116..120 '&[1]': &[i32; 1]
318 117..120 '[1]': [i32; _] 318 117..120 '[1]': [i32; 1]
319 118..119 '1': i32 319 118..119 '1': i32
320 130..131 '_': i32 320 130..131 '_': i32
321 135..139 '&[3]': &[i32; _] 321 135..139 '&[3]': &[i32; 1]
322 136..139 '[3]': [i32; _] 322 136..139 '[3]': [i32; 1]
323 137..138 '3': i32 323 137..138 '3': i32
324 "]], 324 "#]],
325 ); 325 );
326} 326}
327 327
@@ -348,7 +348,7 @@ fn infer_match_second_coerce() {
348 impl<'a, 'b: 'a, T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<&'a U> for &'b T {} 348 impl<'a, 'b: 'a, T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<&'a U> for &'b T {}
349 impl<T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<*mut U> for *mut T {} 349 impl<T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<*mut U> for *mut T {}
350 "#, 350 "#,
351 expect![[r" 351 expect![[r#"
352 10..11 'x': &[T] 352 10..11 'x': &[T]
353 27..38 '{ loop {} }': &[T] 353 27..38 '{ loop {} }': &[T]
354 29..36 'loop {}': ! 354 29..36 'loop {}': !
@@ -360,21 +360,21 @@ fn infer_match_second_coerce() {
360 75..76 'i': i32 360 75..76 'i': i32
361 87..88 '1': i32 361 87..88 '1': i32
362 87..88 '1': i32 362 87..88 '1': i32
363 92..96 '&[1]': &[i32; _] 363 92..96 '&[1]': &[i32; 1]
364 93..96 '[1]': [i32; _] 364 93..96 '[1]': [i32; 1]
365 94..95 '1': i32 365 94..95 '1': i32
366 106..107 '2': i32 366 106..107 '2': i32
367 106..107 '2': i32 367 106..107 '2': i32
368 111..114 'foo': fn foo<i32>(&[i32]) -> &[i32] 368 111..114 'foo': fn foo<i32>(&[i32]) -> &[i32]
369 111..120 'foo(&[2])': &[i32] 369 111..120 'foo(&[2])': &[i32]
370 115..119 '&[2]': &[i32; _] 370 115..119 '&[2]': &[i32; 1]
371 116..119 '[2]': [i32; _] 371 116..119 '[2]': [i32; 1]
372 117..118 '2': i32 372 117..118 '2': i32
373 130..131 '_': i32 373 130..131 '_': i32
374 135..139 '&[3]': &[i32; _] 374 135..139 '&[3]': &[i32; 1]
375 136..139 '[3]': [i32; _] 375 136..139 '[3]': [i32; 1]
376 137..138 '3': i32 376 137..138 '3': i32
377 "]], 377 "#]],
378 ); 378 );
379} 379}
380 380
@@ -685,15 +685,15 @@ fn coerce_unsize_array() {
685 let f: &[usize] = &[1, 2, 3]; 685 let f: &[usize] = &[1, 2, 3];
686 } 686 }
687 "#, 687 "#,
688 expect![[r" 688 expect![[r#"
689 161..198 '{ ... 3]; }': () 689 161..198 '{ ... 3]; }': ()
690 171..172 'f': &[usize] 690 171..172 'f': &[usize]
691 185..195 '&[1, 2, 3]': &[usize; _] 691 185..195 '&[1, 2, 3]': &[usize; 3]
692 186..195 '[1, 2, 3]': [usize; _] 692 186..195 '[1, 2, 3]': [usize; 3]
693 187..188 '1': usize 693 187..188 '1': usize
694 190..191 '2': usize 694 190..191 '2': usize
695 193..194 '3': usize 695 193..194 '3': usize
696 "]], 696 "#]],
697 ); 697 );
698} 698}
699 699
diff --git a/crates/hir_ty/src/tests/patterns.rs b/crates/hir_ty/src/tests/patterns.rs
index f514b3efe..787647e9f 100644
--- a/crates/hir_ty/src/tests/patterns.rs
+++ b/crates/hir_ty/src/tests/patterns.rs
@@ -243,8 +243,8 @@ fn infer_pattern_match_slice() {
243 expect![[r#" 243 expect![[r#"
244 10..209 '{ ... } }': () 244 10..209 '{ ... } }': ()
245 20..25 'slice': &[f64] 245 20..25 'slice': &[f64]
246 36..42 '&[0.0]': &[f64; _] 246 36..42 '&[0.0]': &[f64; 1]
247 37..42 '[0.0]': [f64; _] 247 37..42 '[0.0]': [f64; 1]
248 38..41 '0.0': f64 248 38..41 '0.0': f64
249 48..207 'match ... }': () 249 48..207 'match ... }': ()
250 54..59 'slice': &[f64] 250 54..59 'slice': &[f64]
@@ -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; _] 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/regression.rs b/crates/hir_ty/src/tests/regression.rs
index d14f5c9bb..431861712 100644
--- a/crates/hir_ty/src/tests/regression.rs
+++ b/crates/hir_ty/src/tests/regression.rs
@@ -99,7 +99,7 @@ fn recursive_vars() {
99 10..47 '{ ...&y]; }': () 99 10..47 '{ ...&y]; }': ()
100 20..21 'y': &{unknown} 100 20..21 'y': &{unknown}
101 24..31 'unknown': &{unknown} 101 24..31 'unknown': &{unknown}
102 37..44 '[y, &y]': [&&{unknown}; _] 102 37..44 '[y, &y]': [&&{unknown}; 2]
103 38..39 'y': &{unknown} 103 38..39 'y': &{unknown}
104 41..43 '&y': &&{unknown} 104 41..43 '&y': &&{unknown}
105 42..43 'y': &{unknown} 105 42..43 'y': &{unknown}
@@ -123,7 +123,7 @@ fn recursive_vars_2() {
123 24..31 'unknown': &&{unknown} 123 24..31 'unknown': &&{unknown}
124 41..42 'y': &&{unknown} 124 41..42 'y': &&{unknown}
125 45..52 'unknown': &&{unknown} 125 45..52 'unknown': &&{unknown}
126 58..76 '[(x, y..., &x)]': [(&&&{unknown}, &&&{unknown}); _] 126 58..76 '[(x, y..., &x)]': [(&&&{unknown}, &&&{unknown}); 2]
127 59..65 '(x, y)': (&&&{unknown}, &&&{unknown}) 127 59..65 '(x, y)': (&&&{unknown}, &&&{unknown})
128 60..61 'x': &&{unknown} 128 60..61 'x': &&{unknown}
129 63..64 'y': &&{unknown} 129 63..64 'y': &&{unknown}
@@ -175,8 +175,8 @@ fn infer_std_crash_2() {
175 "#, 175 "#,
176 expect![[r#" 176 expect![[r#"
177 22..52 '{ ...n']; }': () 177 22..52 '{ ...n']; }': ()
178 28..49 '&[0, b...b'\n']': &[u8; _] 178 28..49 '&[0, b...b'\n']': &[u8; 4]
179 29..49 '[0, b'...b'\n']': [u8; _] 179 29..49 '[0, b'...b'\n']': [u8; 4]
180 30..31 '0': u8 180 30..31 '0': u8
181 33..38 'b'\n'': u8 181 33..38 'b'\n'': u8
182 40..41 '1': u8 182 40..41 '1': u8
@@ -336,8 +336,8 @@ fn infer_array_macro_call() {
336 expect![[r#" 336 expect![[r#"
337 !0..4 '0u32': u32 337 !0..4 '0u32': u32
338 44..69 '{ ...()]; }': () 338 44..69 '{ ...()]; }': ()
339 54..55 'a': [u32; _] 339 54..55 'a': [u32; 1]
340 58..66 '[bar!()]': [u32; _] 340 58..66 '[bar!()]': [u32; 1]
341 "#]], 341 "#]],
342 ); 342 );
343} 343}
@@ -1050,3 +1050,52 @@ fn test() {
1050 "#]], 1050 "#]],
1051 ); 1051 );
1052} 1052}
1053
1054#[test]
1055fn cfg_tail() {
1056 // https://github.com/rust-analyzer/rust-analyzer/issues/8378
1057 check_infer(
1058 r#"
1059 fn fake_tail(){
1060 { "first" }
1061 #[cfg(never)] 9
1062 }
1063 fn multiple_fake(){
1064 { "fake" }
1065 { "fake" }
1066 { "second" }
1067 #[cfg(never)] { 11 }
1068 #[cfg(never)] 12;
1069 #[cfg(never)] 13
1070 }
1071 fn no_normal_tail(){
1072 { "third" }
1073 #[cfg(never)] 14;
1074 #[cfg(never)] 15;
1075 }
1076 fn no_actual_tail(){
1077 { "fourth" };
1078 #[cfg(never)] 14;
1079 #[cfg(never)] 15
1080 }
1081 "#,
1082 expect![[r#"
1083 14..53 '{ ...)] 9 }': &str
1084 20..31 '{ "first" }': &str
1085 22..29 '"first"': &str
1086 72..190 '{ ...] 13 }': &str
1087 78..88 '{ "fake" }': &str
1088 80..86 '"fake"': &str
1089 93..103 '{ "fake" }': &str
1090 95..101 '"fake"': &str
1091 108..120 '{ "second" }': &str
1092 110..118 '"second"': &str
1093 210..273 '{ ... 15; }': &str
1094 216..227 '{ "third" }': &str
1095 218..225 '"third"': &str
1096 293..357 '{ ...] 15 }': ()
1097 299..311 '{ "fourth" }': &str
1098 301..309 '"fourth"': &str
1099 "#]],
1100 )
1101}
diff --git a/crates/hir_ty/src/tests/simple.rs b/crates/hir_ty/src/tests/simple.rs
index 0eefd70f2..a9cd42186 100644
--- a/crates/hir_ty/src/tests/simple.rs
+++ b/crates/hir_ty/src/tests/simple.rs
@@ -11,7 +11,7 @@ fn test() {
11 let x = box 1; 11 let x = box 1;
12 let t = (x, box x, box &1, box [1]); 12 let t = (x, box x, box &1, box [1]);
13 t; 13 t;
14} //^ (Box<i32>, Box<Box<i32>>, Box<&i32>, Box<[i32; _]>) 14} //^ (Box<i32>, Box<Box<i32>>, Box<&i32>, Box<[i32; 1]>)
15 15
16//- /std.rs crate:std 16//- /std.rs crate:std
17#[prelude_import] use prelude::*; 17#[prelude_import] use prelude::*;
@@ -36,7 +36,7 @@ fn test() {
36 let x = box 1; 36 let x = box 1;
37 let t = (x, box x, box &1, box [1]); 37 let t = (x, box x, box &1, box [1]);
38 t; 38 t;
39} //^ (Box<i32, {unknown}>, Box<Box<i32, {unknown}>, {unknown}>, Box<&i32, {unknown}>, Box<[i32; _], {unknown}>) 39} //^ (Box<i32, {unknown}>, Box<Box<i32, {unknown}>, {unknown}>, Box<&i32, {unknown}>, Box<[i32; 1], {unknown}>)
40 40
41//- /std.rs crate:std 41//- /std.rs crate:std
42#[prelude_import] use prelude::*; 42#[prelude_import] use prelude::*;
@@ -488,23 +488,34 @@ fn infer_literals() {
488 mod foo {} 488 mod foo {}
489 "#; 489 "#;
490 br#"yolo"#; 490 br#"yolo"#;
491 let a = b"a\x20b\
492 c";
493 let b = br"g\
494h";
495 let c = br#"x"\"yb"#;
491 } 496 }
492 "##, 497 "##,
493 expect![[r##" 498 expect![[r##"
494 10..216 '{ ...o"#; }': () 499 18..478 '{ ... }': ()
495 16..20 '5i32': i32 500 32..36 '5i32': i32
496 26..30 '5f32': f32 501 50..54 '5f32': f32
497 36..40 '5f64': f64 502 68..72 '5f64': f64
498 46..53 '"hello"': &str 503 86..93 '"hello"': &str
499 59..67 'b"bytes"': &[u8; _] 504 107..115 'b"bytes"': &[u8; 5]
500 73..76 ''c'': char 505 129..132 ''c'': char
501 82..86 'b'b'': u8 506 146..150 'b'b'': u8
502 92..96 '3.14': f64 507 164..168 '3.14': f64
503 102..106 '5000': i32 508 182..186 '5000': i32
504 112..117 'false': bool 509 200..205 'false': bool
505 123..127 'true': bool 510 219..223 'true': bool
506 133..197 'r#" ... "#': &str 511 237..333 'r#" ... "#': &str
507 203..213 'br#"yolo"#': &[u8; _] 512 347..357 'br#"yolo"#': &[u8; 4]
513 375..376 'a': &[u8; 4]
514 379..403 'b"a\x2... c"': &[u8; 4]
515 421..422 'b': &[u8; 4]
516 425..433 'br"g\ h"': &[u8; 4]
517 451..452 'c': &[u8; 6]
518 455..467 'br#"x"\"yb"#': &[u8; 6]
508 "##]], 519 "##]],
509 ); 520 );
510} 521}
@@ -1260,61 +1271,69 @@ 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; _] 1282 37..38 'a': [&str; 1]
1270 41..44 '[x]': [&str; _] 1283 41..44 '[x]': [&str; 1]
1271 42..43 'x': &str 1284 42..43 'x': &str
1272 54..55 'b': [[&str; _]; _] 1285 54..55 'b': [[&str; 1]; 2]
1273 58..64 '[a, a]': [[&str; _]; _] 1286 58..64 '[a, a]': [[&str; 1]; 2]
1274 59..60 'a': [&str; _] 1287 59..60 'a': [&str; 1]
1275 62..63 'a': [&str; _] 1288 62..63 'a': [&str; 1]
1276 74..75 'c': [[[&str; _]; _]; _] 1289 74..75 'c': [[[&str; 1]; 2]; 2]
1277 78..84 '[b, b]': [[[&str; _]; _]; _] 1290 78..84 '[b, b]': [[[&str; 1]; 2]; 2]
1278 79..80 'b': [[&str; _]; _] 1291 79..80 'b': [[&str; 1]; 2]
1279 82..83 'b': [[&str; _]; _] 1292 82..83 'b': [[&str; 1]; 2]
1280 95..96 'd': [isize; _] 1293 95..96 'd': [isize; 4]
1281 99..111 '[y, 1, 2, 3]': [isize; _] 1294 99..111 '[y, 1, 2, 3]': [isize; 4]
1282 100..101 'y': isize 1295 100..101 'y': isize
1283 103..104 '1': isize 1296 103..104 '1': isize
1284 106..107 '2': isize 1297 106..107 '2': isize
1285 109..110 '3': isize 1298 109..110 '3': isize
1286 121..122 'd': [isize; _] 1299 121..122 'd': [isize; 4]
1287 125..137 '[1, y, 2, 3]': [isize; _] 1300 125..137 '[1, y, 2, 3]': [isize; 4]
1288 126..127 '1': isize 1301 126..127 '1': isize
1289 129..130 'y': isize 1302 129..130 'y': isize
1290 132..133 '2': isize 1303 132..133 '2': isize
1291 135..136 '3': isize 1304 135..136 '3': isize
1292 147..148 'e': [isize; _] 1305 147..148 'e': [isize; 1]
1293 151..154 '[y]': [isize; _] 1306 151..154 '[y]': [isize; 1]
1294 152..153 'y': isize 1307 152..153 'y': isize
1295 164..165 'f': [[isize; _]; _] 1308 164..165 'f': [[isize; 4]; 2]
1296 168..174 '[d, d]': [[isize; _]; _] 1309 168..174 '[d, d]': [[isize; 4]; 2]
1297 169..170 'd': [isize; _] 1310 169..170 'd': [isize; 4]
1298 172..173 'd': [isize; _] 1311 172..173 'd': [isize; 4]
1299 184..185 'g': [[isize; _]; _] 1312 184..185 'g': [[isize; 1]; 2]
1300 188..194 '[e, e]': [[isize; _]; _] 1313 188..194 '[e, e]': [[isize; 1]; 2]
1301 189..190 'e': [isize; _] 1314 189..190 'e': [isize; 1]
1302 192..193 'e': [isize; _] 1315 192..193 'e': [isize; 1]
1303 205..206 'h': [i32; _] 1316 205..206 'h': [i32; 2]
1304 209..215 '[1, 2]': [i32; _] 1317 209..215 '[1, 2]': [i32; 2]
1305 210..211 '1': i32 1318 210..211 '1': i32
1306 213..214 '2': i32 1319 213..214 '2': i32
1307 225..226 'i': [&str; _] 1320 225..226 'i': [&str; 2]
1308 229..239 '["a", "b"]': [&str; _] 1321 229..239 '["a", "b"]': [&str; 2]
1309 230..233 '"a"': &str 1322 230..233 '"a"': &str
1310 235..238 '"b"': &str 1323 235..238 '"b"': &str
1311 250..251 'b': [[&str; _]; _] 1324 250..251 'b': [[&str; 1]; 2]
1312 254..264 '[a, ["b"]]': [[&str; _]; _] 1325 254..264 '[a, ["b"]]': [[&str; 1]; 2]
1313 255..256 'a': [&str; _] 1326 255..256 'a': [&str; 1]
1314 258..263 '["b"]': [&str; _] 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; _] 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,40 +2428,40 @@ 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; _] 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; _] 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; _] 2464 483..493 '[0.0, 1.0]': [f32; 2]
2446 484..487 '0.0': f32 2465 484..487 '0.0': f32
2447 489..492 '1.0': f32 2466 489..492 '1.0': f32
2448 505..506 'r': V2 2467 505..506 'r': V2
@@ -2593,8 +2612,8 @@ fn test() {
2593 658..661 'vec': Vec<i32, Global> 2612 658..661 'vec': Vec<i32, Global>
2594 664..679 '<[_]>::into_vec': fn into_vec<i32, Global>(Box<[i32], Global>) -> Vec<i32, Global> 2613 664..679 '<[_]>::into_vec': fn into_vec<i32, Global>(Box<[i32], Global>) -> Vec<i32, Global>
2595 664..691 '<[_]>:...1i32])': Vec<i32, Global> 2614 664..691 '<[_]>:...1i32])': Vec<i32, Global>
2596 680..690 'box [1i32]': Box<[i32; _], Global> 2615 680..690 'box [1i32]': Box<[i32; 1], Global>
2597 684..690 '[1i32]': [i32; _] 2616 684..690 '[1i32]': [i32; 1]
2598 685..689 '1i32': i32 2617 685..689 '1i32': i32
2599 "#]], 2618 "#]],
2600 ) 2619 )
diff --git a/crates/hir_ty/src/tests/traits.rs b/crates/hir_ty/src/tests/traits.rs
index ffc7c8ef4..f80cf9879 100644
--- a/crates/hir_ty/src/tests/traits.rs
+++ b/crates/hir_ty/src/tests/traits.rs
@@ -531,7 +531,7 @@ fn indexing_arrays() {
531 expect![[r#" 531 expect![[r#"
532 10..26 '{ &mut...[2]; }': () 532 10..26 '{ &mut...[2]; }': ()
533 12..23 '&mut [9][2]': &mut {unknown} 533 12..23 '&mut [9][2]': &mut {unknown}
534 17..20 '[9]': [i32; _] 534 17..20 '[9]': [i32; 1]
535 17..23 '[9][2]': {unknown} 535 17..23 '[9][2]': {unknown}
536 18..19 '9': i32 536 18..19 '9': i32
537 21..22 '2': i32 537 21..22 '2': i32
@@ -3474,3 +3474,100 @@ fn main(){
3474 "#]], 3474 "#]],
3475 ) 3475 )
3476} 3476}
3477
3478#[test]
3479fn array_length() {
3480 check_infer(
3481 r#"
3482trait T {
3483 type Output;
3484 fn do_thing(&self) -> Self::Output;
3485}
3486
3487impl T for [u8; 4] {
3488 type Output = usize;
3489 fn do_thing(&self) -> Self::Output {
3490 2
3491 }
3492}
3493
3494impl T for [u8; 2] {
3495 type Output = u8;
3496 fn do_thing(&self) -> Self::Output {
3497 2
3498 }
3499}
3500
3501fn main() {
3502 let v = [0u8; 2];
3503 let v2 = v.do_thing();
3504 let v3 = [0u8; 4];
3505 let v4 = v3.do_thing();
3506}
3507"#,
3508 expect![[r#"
3509 44..48 'self': &Self
3510 133..137 'self': &[u8; 4]
3511 155..172 '{ ... }': usize
3512 165..166 '2': usize
3513 236..240 'self': &[u8; 2]
3514 258..275 '{ ... }': u8
3515 268..269 '2': u8
3516 289..392 '{ ...g(); }': ()
3517 299..300 'v': [u8; 2]
3518 303..311 '[0u8; 2]': [u8; 2]
3519 304..307 '0u8': u8
3520 309..310 '2': usize
3521 321..323 'v2': u8
3522 326..327 'v': [u8; 2]
3523 326..338 'v.do_thing()': u8
3524 348..350 'v3': [u8; 4]
3525 353..361 '[0u8; 4]': [u8; 4]
3526 354..357 '0u8': u8
3527 359..360 '4': usize
3528 371..373 'v4': usize
3529 376..378 'v3': [u8; 4]
3530 376..389 'v3.do_thing()': usize
3531 "#]],
3532 )
3533}
3534
3535// FIXME: We should infer the length of the returned array :)
3536#[test]
3537fn const_generics() {
3538 check_infer(
3539 r#"
3540trait T {
3541 type Output;
3542 fn do_thing(&self) -> Self::Output;
3543}
3544
3545impl<const L: usize> T for [u8; L] {
3546 type Output = [u8; L];
3547 fn do_thing(&self) -> Self::Output {
3548 *self
3549 }
3550}
3551
3552fn main() {
3553 let v = [0u8; 2];
3554 let v2 = v.do_thing();
3555}
3556"#,
3557 expect![[r#"
3558 44..48 'self': &Self
3559 151..155 'self': &[u8; _]
3560 173..194 '{ ... }': [u8; _]
3561 183..188 '*self': [u8; _]
3562 184..188 'self': &[u8; _]
3563 208..260 '{ ...g(); }': ()
3564 218..219 'v': [u8; 2]
3565 222..230 '[0u8; 2]': [u8; 2]
3566 223..226 '0u8': u8
3567 228..229 '2': usize
3568 240..242 'v2': [u8; _]
3569 245..246 'v': [u8; 2]
3570 245..257 'v.do_thing()': [u8; _]
3571 "#]],
3572 )
3573}
diff --git a/crates/ide/Cargo.toml b/crates/ide/Cargo.toml
index f04bcf531..88f3d09d3 100644
--- a/crates/ide/Cargo.toml
+++ b/crates/ide/Cargo.toml
@@ -20,6 +20,7 @@ oorandom = "11.1.2"
20pulldown-cmark-to-cmark = "6.0.0" 20pulldown-cmark-to-cmark = "6.0.0"
21pulldown-cmark = { version = "0.8.0", default-features = false } 21pulldown-cmark = { version = "0.8.0", default-features = false }
22url = "2.1.1" 22url = "2.1.1"
23dot = "0.1.4"
23 24
24stdx = { path = "../stdx", version = "0.0.0" } 25stdx = { path = "../stdx", version = "0.0.0" }
25syntax = { path = "../syntax", version = "0.0.0" } 26syntax = { path = "../syntax", version = "0.0.0" }
diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs
index 273d8cfbb..4172f6cae 100644
--- a/crates/ide/src/diagnostics.rs
+++ b/crates/ide/src/diagnostics.rs
@@ -375,7 +375,7 @@ mod tests {
375 assert_eq!(diagnostics.len(), 0, "unexpected diagnostics:\n{:#?}", diagnostics); 375 assert_eq!(diagnostics.len(), 0, "unexpected diagnostics:\n{:#?}", diagnostics);
376 } 376 }
377 377
378 fn check_expect(ra_fixture: &str, expect: Expect) { 378 pub(crate) fn check_expect(ra_fixture: &str, expect: Expect) {
379 let (analysis, file_id) = fixture::file(ra_fixture); 379 let (analysis, file_id) = fixture::file(ra_fixture);
380 let diagnostics = analysis 380 let diagnostics = analysis
381 .diagnostics(&DiagnosticsConfig::default(), AssistResolveStrategy::All, file_id) 381 .diagnostics(&DiagnosticsConfig::default(), AssistResolveStrategy::All, file_id)
@@ -384,374 +384,6 @@ mod tests {
384 } 384 }
385 385
386 #[test] 386 #[test]
387 fn test_wrap_return_type_option() {
388 check_fix(
389 r#"
390//- /main.rs crate:main deps:core
391use core::option::Option::{self, Some, None};
392
393fn div(x: i32, y: i32) -> Option<i32> {
394 if y == 0 {
395 return None;
396 }
397 x / y$0
398}
399//- /core/lib.rs crate:core
400pub mod result {
401 pub enum Result<T, E> { Ok(T), Err(E) }
402}
403pub mod option {
404 pub enum Option<T> { Some(T), None }
405}
406"#,
407 r#"
408use core::option::Option::{self, Some, None};
409
410fn div(x: i32, y: i32) -> Option<i32> {
411 if y == 0 {
412 return None;
413 }
414 Some(x / y)
415}
416"#,
417 );
418 }
419
420 #[test]
421 fn test_wrap_return_type() {
422 check_fix(
423 r#"
424//- /main.rs crate:main deps:core
425use core::result::Result::{self, Ok, Err};
426
427fn div(x: i32, y: i32) -> Result<i32, ()> {
428 if y == 0 {
429 return Err(());
430 }
431 x / y$0
432}
433//- /core/lib.rs crate:core
434pub mod result {
435 pub enum Result<T, E> { Ok(T), Err(E) }
436}
437pub mod option {
438 pub enum Option<T> { Some(T), None }
439}
440"#,
441 r#"
442use core::result::Result::{self, Ok, Err};
443
444fn div(x: i32, y: i32) -> Result<i32, ()> {
445 if y == 0 {
446 return Err(());
447 }
448 Ok(x / y)
449}
450"#,
451 );
452 }
453
454 #[test]
455 fn test_wrap_return_type_handles_generic_functions() {
456 check_fix(
457 r#"
458//- /main.rs crate:main deps:core
459use core::result::Result::{self, Ok, Err};
460
461fn div<T>(x: T) -> Result<T, i32> {
462 if x == 0 {
463 return Err(7);
464 }
465 $0x
466}
467//- /core/lib.rs crate:core
468pub mod result {
469 pub enum Result<T, E> { Ok(T), Err(E) }
470}
471pub mod option {
472 pub enum Option<T> { Some(T), None }
473}
474"#,
475 r#"
476use core::result::Result::{self, Ok, Err};
477
478fn div<T>(x: T) -> Result<T, i32> {
479 if x == 0 {
480 return Err(7);
481 }
482 Ok(x)
483}
484"#,
485 );
486 }
487
488 #[test]
489 fn test_wrap_return_type_handles_type_aliases() {
490 check_fix(
491 r#"
492//- /main.rs crate:main deps:core
493use core::result::Result::{self, Ok, Err};
494
495type MyResult<T> = Result<T, ()>;
496
497fn div(x: i32, y: i32) -> MyResult<i32> {
498 if y == 0 {
499 return Err(());
500 }
501 x $0/ y
502}
503//- /core/lib.rs crate:core
504pub mod result {
505 pub enum Result<T, E> { Ok(T), Err(E) }
506}
507pub mod option {
508 pub enum Option<T> { Some(T), None }
509}
510"#,
511 r#"
512use core::result::Result::{self, Ok, Err};
513
514type MyResult<T> = Result<T, ()>;
515
516fn div(x: i32, y: i32) -> MyResult<i32> {
517 if y == 0 {
518 return Err(());
519 }
520 Ok(x / y)
521}
522"#,
523 );
524 }
525
526 #[test]
527 fn test_wrap_return_type_not_applicable_when_expr_type_does_not_match_ok_type() {
528 check_no_diagnostics(
529 r#"
530//- /main.rs crate:main deps:core
531use core::result::Result::{self, Ok, Err};
532
533fn foo() -> Result<(), i32> { 0 }
534
535//- /core/lib.rs crate:core
536pub mod result {
537 pub enum Result<T, E> { Ok(T), Err(E) }
538}
539pub mod option {
540 pub enum Option<T> { Some(T), None }
541}
542"#,
543 );
544 }
545
546 #[test]
547 fn test_wrap_return_type_not_applicable_when_return_type_is_not_result_or_option() {
548 check_no_diagnostics(
549 r#"
550//- /main.rs crate:main deps:core
551use core::result::Result::{self, Ok, Err};
552
553enum SomeOtherEnum { Ok(i32), Err(String) }
554
555fn foo() -> SomeOtherEnum { 0 }
556
557//- /core/lib.rs crate:core
558pub mod result {
559 pub enum Result<T, E> { Ok(T), Err(E) }
560}
561pub mod option {
562 pub enum Option<T> { Some(T), None }
563}
564"#,
565 );
566 }
567
568 #[test]
569 fn test_fill_struct_fields_empty() {
570 check_fix(
571 r#"
572struct TestStruct { one: i32, two: i64 }
573
574fn test_fn() {
575 let s = TestStruct {$0};
576}
577"#,
578 r#"
579struct TestStruct { one: i32, two: i64 }
580
581fn test_fn() {
582 let s = TestStruct { one: (), two: ()};
583}
584"#,
585 );
586 }
587
588 #[test]
589 fn test_fill_struct_fields_self() {
590 check_fix(
591 r#"
592struct TestStruct { one: i32 }
593
594impl TestStruct {
595 fn test_fn() { let s = Self {$0}; }
596}
597"#,
598 r#"
599struct TestStruct { one: i32 }
600
601impl TestStruct {
602 fn test_fn() { let s = Self { one: ()}; }
603}
604"#,
605 );
606 }
607
608 #[test]
609 fn test_fill_struct_fields_enum() {
610 check_fix(
611 r#"
612enum Expr {
613 Bin { lhs: Box<Expr>, rhs: Box<Expr> }
614}
615
616impl Expr {
617 fn new_bin(lhs: Box<Expr>, rhs: Box<Expr>) -> Expr {
618 Expr::Bin {$0 }
619 }
620}
621"#,
622 r#"
623enum Expr {
624 Bin { lhs: Box<Expr>, rhs: Box<Expr> }
625}
626
627impl Expr {
628 fn new_bin(lhs: Box<Expr>, rhs: Box<Expr>) -> Expr {
629 Expr::Bin { lhs: (), rhs: () }
630 }
631}
632"#,
633 );
634 }
635
636 #[test]
637 fn test_fill_struct_fields_partial() {
638 check_fix(
639 r#"
640struct TestStruct { one: i32, two: i64 }
641
642fn test_fn() {
643 let s = TestStruct{ two: 2$0 };
644}
645"#,
646 r"
647struct TestStruct { one: i32, two: i64 }
648
649fn test_fn() {
650 let s = TestStruct{ two: 2, one: () };
651}
652",
653 );
654 }
655
656 #[test]
657 fn test_fill_struct_fields_raw_ident() {
658 check_fix(
659 r#"
660struct TestStruct { r#type: u8 }
661
662fn test_fn() {
663 TestStruct { $0 };
664}
665"#,
666 r"
667struct TestStruct { r#type: u8 }
668
669fn test_fn() {
670 TestStruct { r#type: () };
671}
672",
673 );
674 }
675
676 #[test]
677 fn test_fill_struct_fields_no_diagnostic() {
678 check_no_diagnostics(
679 r"
680 struct TestStruct { one: i32, two: i64 }
681
682 fn test_fn() {
683 let one = 1;
684 let s = TestStruct{ one, two: 2 };
685 }
686 ",
687 );
688 }
689
690 #[test]
691 fn test_fill_struct_fields_no_diagnostic_on_spread() {
692 check_no_diagnostics(
693 r"
694 struct TestStruct { one: i32, two: i64 }
695
696 fn test_fn() {
697 let one = 1;
698 let s = TestStruct{ ..a };
699 }
700 ",
701 );
702 }
703
704 #[test]
705 fn test_unresolved_module_diagnostic() {
706 check_expect(
707 r#"mod foo;"#,
708 expect![[r#"
709 [
710 Diagnostic {
711 message: "unresolved module",
712 range: 0..8,
713 severity: Error,
714 fix: Some(
715 Assist {
716 id: AssistId(
717 "create_module",
718 QuickFix,
719 ),
720 label: "Create module",
721 group: None,
722 target: 0..8,
723 source_change: Some(
724 SourceChange {
725 source_file_edits: {},
726 file_system_edits: [
727 CreateFile {
728 dst: AnchoredPathBuf {
729 anchor: FileId(
730 0,
731 ),
732 path: "foo.rs",
733 },
734 initial_contents: "",
735 },
736 ],
737 is_snippet: false,
738 },
739 ),
740 },
741 ),
742 unused: false,
743 code: Some(
744 DiagnosticCode(
745 "unresolved-module",
746 ),
747 ),
748 },
749 ]
750 "#]],
751 );
752 }
753
754 #[test]
755 fn test_unresolved_macro_range() { 387 fn test_unresolved_macro_range() {
756 check_expect( 388 check_expect(
757 r#"foo::bar!(92);"#, 389 r#"foo::bar!(92);"#,
@@ -792,7 +424,7 @@ fn main() {
792pub struct Foo { pub a: i32, pub b: i32 } 424pub struct Foo { pub a: i32, pub b: i32 }
793"#, 425"#,
794 r#" 426 r#"
795fn some(, b: ()) {} 427fn some(, b: () ) {}
796fn items() {} 428fn items() {}
797fn here() {} 429fn here() {}
798 430
@@ -891,53 +523,6 @@ mod a {
891 } 523 }
892 524
893 #[test] 525 #[test]
894 fn test_add_field_from_usage() {
895 check_fix(
896 r"
897fn main() {
898 Foo { bar: 3, baz$0: false};
899}
900struct Foo {
901 bar: i32
902}
903",
904 r"
905fn main() {
906 Foo { bar: 3, baz: false};
907}
908struct Foo {
909 bar: i32,
910 baz: bool
911}
912",
913 )
914 }
915
916 #[test]
917 fn test_add_field_in_other_file_from_usage() {
918 check_fix(
919 r#"
920//- /main.rs
921mod foo;
922
923fn main() {
924 foo::Foo { bar: 3, $0baz: false};
925}
926//- /foo.rs
927struct Foo {
928 bar: i32
929}
930"#,
931 r#"
932struct Foo {
933 bar: i32,
934 pub(crate) baz: bool
935}
936"#,
937 )
938 }
939
940 #[test]
941 fn test_disabled_diagnostics() { 526 fn test_disabled_diagnostics() {
942 let mut config = DiagnosticsConfig::default(); 527 let mut config = DiagnosticsConfig::default();
943 config.disabled.insert("unresolved-module".into()); 528 config.disabled.insert("unresolved-module".into());
@@ -955,120 +540,6 @@ struct Foo {
955 } 540 }
956 541
957 #[test] 542 #[test]
958 fn test_rename_incorrect_case() {
959 check_fix(
960 r#"
961pub struct test_struct$0 { one: i32 }
962
963pub fn some_fn(val: test_struct) -> test_struct {
964 test_struct { one: val.one + 1 }
965}
966"#,
967 r#"
968pub struct TestStruct { one: i32 }
969
970pub fn some_fn(val: TestStruct) -> TestStruct {
971 TestStruct { one: val.one + 1 }
972}
973"#,
974 );
975
976 check_fix(
977 r#"
978pub fn some_fn(NonSnakeCase$0: u8) -> u8 {
979 NonSnakeCase
980}
981"#,
982 r#"
983pub fn some_fn(non_snake_case: u8) -> u8 {
984 non_snake_case
985}
986"#,
987 );
988
989 check_fix(
990 r#"
991pub fn SomeFn$0(val: u8) -> u8 {
992 if val != 0 { SomeFn(val - 1) } else { val }
993}
994"#,
995 r#"
996pub fn some_fn(val: u8) -> u8 {
997 if val != 0 { some_fn(val - 1) } else { val }
998}
999"#,
1000 );
1001
1002 check_fix(
1003 r#"
1004fn some_fn() {
1005 let whatAWeird_Formatting$0 = 10;
1006 another_func(whatAWeird_Formatting);
1007}
1008"#,
1009 r#"
1010fn some_fn() {
1011 let what_a_weird_formatting = 10;
1012 another_func(what_a_weird_formatting);
1013}
1014"#,
1015 );
1016 }
1017
1018 #[test]
1019 fn test_uppercase_const_no_diagnostics() {
1020 check_no_diagnostics(
1021 r#"
1022fn foo() {
1023 const ANOTHER_ITEM$0: &str = "some_item";
1024}
1025"#,
1026 );
1027 }
1028
1029 #[test]
1030 fn test_rename_incorrect_case_struct_method() {
1031 check_fix(
1032 r#"
1033pub struct TestStruct;
1034
1035impl TestStruct {
1036 pub fn SomeFn$0() -> TestStruct {
1037 TestStruct
1038 }
1039}
1040"#,
1041 r#"
1042pub struct TestStruct;
1043
1044impl TestStruct {
1045 pub fn some_fn() -> TestStruct {
1046 TestStruct
1047 }
1048}
1049"#,
1050 );
1051 }
1052
1053 #[test]
1054 fn test_single_incorrect_case_diagnostic_in_function_name_issue_6970() {
1055 let input = r#"fn FOO$0() {}"#;
1056 let expected = r#"fn foo() {}"#;
1057
1058 let (analysis, file_position) = fixture::position(input);
1059 let diagnostics = analysis
1060 .diagnostics(
1061 &DiagnosticsConfig::default(),
1062 AssistResolveStrategy::All,
1063 file_position.file_id,
1064 )
1065 .unwrap();
1066 assert_eq!(diagnostics.len(), 1);
1067
1068 check_fix(input, expected);
1069 }
1070
1071 #[test]
1072 fn unlinked_file_prepend_first_item() { 543 fn unlinked_file_prepend_first_item() {
1073 cov_mark::check!(unlinked_file_prepend_before_first_item); 544 cov_mark::check!(unlinked_file_prepend_before_first_item);
1074 check_fix( 545 check_fix(
diff --git a/crates/ide/src/diagnostics/fixes.rs b/crates/ide/src/diagnostics/fixes.rs
index 15821500f..92b3f5a2d 100644
--- a/crates/ide/src/diagnostics/fixes.rs
+++ b/crates/ide/src/diagnostics/fixes.rs
@@ -1,31 +1,18 @@
1//! Provides a way to attach fixes to the diagnostics. 1//! Provides a way to attach fixes to the diagnostics.
2//! The same module also has all curret custom fixes for the diagnostics implemented. 2//! The same module also has all curret custom fixes for the diagnostics implemented.
3use hir::{ 3mod change_case;
4 db::AstDatabase, 4mod create_field;
5 diagnostics::{ 5mod fill_missing_fields;
6 Diagnostic, IncorrectCase, MissingFields, MissingOkOrSomeInTailExpr, NoSuchField, 6mod remove_semicolon;
7 RemoveThisSemicolon, ReplaceFilterMapNextWithFindMap, UnresolvedModule, 7mod replace_with_find_map;
8 }, 8mod unresolved_module;
9 HasSource, HirDisplay, InFile, Semantics, VariantDef, 9mod wrap_tail_expr;
10}; 10
11use hir::{diagnostics::Diagnostic, Semantics};
11use ide_assists::AssistResolveStrategy; 12use ide_assists::AssistResolveStrategy;
12use ide_db::{ 13use ide_db::RootDatabase;
13 base_db::{AnchoredPathBuf, FileId},
14 source_change::{FileSystemEdit, SourceChange},
15 RootDatabase,
16};
17use syntax::{
18 algo,
19 ast::{self, edit::IndentLevel, make, ArgListOwner},
20 AstNode, TextRange,
21};
22use text_edit::TextEdit;
23 14
24use crate::{ 15use crate::Assist;
25 diagnostics::{fix, unresolved_fix},
26 references::rename::rename_with_semantics,
27 Assist, FilePosition,
28};
29 16
30/// A [Diagnostic] that potentially has a fix available. 17/// A [Diagnostic] that potentially has a fix available.
31/// 18///
@@ -42,256 +29,3 @@ pub(crate) trait DiagnosticWithFix: Diagnostic {
42 _resolve: &AssistResolveStrategy, 29 _resolve: &AssistResolveStrategy,
43 ) -> Option<Assist>; 30 ) -> Option<Assist>;
44} 31}
45
46impl DiagnosticWithFix for UnresolvedModule {
47 fn fix(
48 &self,
49 sema: &Semantics<RootDatabase>,
50 _resolve: &AssistResolveStrategy,
51 ) -> Option<Assist> {
52 let root = sema.db.parse_or_expand(self.file)?;
53 let unresolved_module = self.decl.to_node(&root);
54 Some(fix(
55 "create_module",
56 "Create module",
57 FileSystemEdit::CreateFile {
58 dst: AnchoredPathBuf {
59 anchor: self.file.original_file(sema.db),
60 path: self.candidate.clone(),
61 },
62 initial_contents: "".to_string(),
63 }
64 .into(),
65 unresolved_module.syntax().text_range(),
66 ))
67 }
68}
69
70impl DiagnosticWithFix for NoSuchField {
71 fn fix(
72 &self,
73 sema: &Semantics<RootDatabase>,
74 _resolve: &AssistResolveStrategy,
75 ) -> Option<Assist> {
76 let root = sema.db.parse_or_expand(self.file)?;
77 missing_record_expr_field_fix(
78 &sema,
79 self.file.original_file(sema.db),
80 &self.field.to_node(&root),
81 )
82 }
83}
84
85impl DiagnosticWithFix for MissingFields {
86 fn fix(
87 &self,
88 sema: &Semantics<RootDatabase>,
89 _resolve: &AssistResolveStrategy,
90 ) -> Option<Assist> {
91 // Note that although we could add a diagnostics to
92 // fill the missing tuple field, e.g :
93 // `struct A(usize);`
94 // `let a = A { 0: () }`
95 // but it is uncommon usage and it should not be encouraged.
96 if self.missed_fields.iter().any(|it| it.as_tuple_index().is_some()) {
97 return None;
98 }
99
100 let root = sema.db.parse_or_expand(self.file)?;
101 let field_list_parent = self.field_list_parent.to_node(&root);
102 let old_field_list = field_list_parent.record_expr_field_list()?;
103 let mut new_field_list = old_field_list.clone();
104 for f in self.missed_fields.iter() {
105 let field =
106 make::record_expr_field(make::name_ref(&f.to_string()), Some(make::expr_unit()));
107 new_field_list = new_field_list.append_field(&field);
108 }
109
110 let edit = {
111 let mut builder = TextEdit::builder();
112 algo::diff(&old_field_list.syntax(), &new_field_list.syntax())
113 .into_text_edit(&mut builder);
114 builder.finish()
115 };
116 Some(fix(
117 "fill_missing_fields",
118 "Fill struct fields",
119 SourceChange::from_text_edit(self.file.original_file(sema.db), edit),
120 sema.original_range(&field_list_parent.syntax()).range,
121 ))
122 }
123}
124
125impl DiagnosticWithFix for MissingOkOrSomeInTailExpr {
126 fn fix(
127 &self,
128 sema: &Semantics<RootDatabase>,
129 _resolve: &AssistResolveStrategy,
130 ) -> Option<Assist> {
131 let root = sema.db.parse_or_expand(self.file)?;
132 let tail_expr = self.expr.to_node(&root);
133 let tail_expr_range = tail_expr.syntax().text_range();
134 let replacement = format!("{}({})", self.required, tail_expr.syntax());
135 let edit = TextEdit::replace(tail_expr_range, replacement);
136 let source_change = SourceChange::from_text_edit(self.file.original_file(sema.db), edit);
137 let name = if self.required == "Ok" { "Wrap with Ok" } else { "Wrap with Some" };
138 Some(fix("wrap_tail_expr", name, source_change, tail_expr_range))
139 }
140}
141
142impl DiagnosticWithFix for RemoveThisSemicolon {
143 fn fix(
144 &self,
145 sema: &Semantics<RootDatabase>,
146 _resolve: &AssistResolveStrategy,
147 ) -> Option<Assist> {
148 let root = sema.db.parse_or_expand(self.file)?;
149
150 let semicolon = self
151 .expr
152 .to_node(&root)
153 .syntax()
154 .parent()
155 .and_then(ast::ExprStmt::cast)
156 .and_then(|expr| expr.semicolon_token())?
157 .text_range();
158
159 let edit = TextEdit::delete(semicolon);
160 let source_change = SourceChange::from_text_edit(self.file.original_file(sema.db), edit);
161
162 Some(fix("remove_semicolon", "Remove this semicolon", source_change, semicolon))
163 }
164}
165
166impl DiagnosticWithFix for IncorrectCase {
167 fn fix(
168 &self,
169 sema: &Semantics<RootDatabase>,
170 resolve: &AssistResolveStrategy,
171 ) -> Option<Assist> {
172 let root = sema.db.parse_or_expand(self.file)?;
173 let name_node = self.ident.to_node(&root);
174
175 let name_node = InFile::new(self.file, name_node.syntax());
176 let frange = name_node.original_file_range(sema.db);
177 let file_position = FilePosition { file_id: frange.file_id, offset: frange.range.start() };
178
179 let label = format!("Rename to {}", self.suggested_text);
180 let mut res = unresolved_fix("change_case", &label, frange.range);
181 if resolve.should_resolve(&res.id) {
182 let source_change = rename_with_semantics(sema, file_position, &self.suggested_text);
183 res.source_change = Some(source_change.ok().unwrap_or_default());
184 }
185
186 Some(res)
187 }
188}
189
190impl DiagnosticWithFix for ReplaceFilterMapNextWithFindMap {
191 fn fix(
192 &self,
193 sema: &Semantics<RootDatabase>,
194 _resolve: &AssistResolveStrategy,
195 ) -> Option<Assist> {
196 let root = sema.db.parse_or_expand(self.file)?;
197 let next_expr = self.next_expr.to_node(&root);
198 let next_call = ast::MethodCallExpr::cast(next_expr.syntax().clone())?;
199
200 let filter_map_call = ast::MethodCallExpr::cast(next_call.receiver()?.syntax().clone())?;
201 let filter_map_name_range = filter_map_call.name_ref()?.ident_token()?.text_range();
202 let filter_map_args = filter_map_call.arg_list()?;
203
204 let range_to_replace =
205 TextRange::new(filter_map_name_range.start(), next_expr.syntax().text_range().end());
206 let replacement = format!("find_map{}", filter_map_args.syntax().text());
207 let trigger_range = next_expr.syntax().text_range();
208
209 let edit = TextEdit::replace(range_to_replace, replacement);
210
211 let source_change = SourceChange::from_text_edit(self.file.original_file(sema.db), edit);
212
213 Some(fix(
214 "replace_with_find_map",
215 "Replace filter_map(..).next() with find_map()",
216 source_change,
217 trigger_range,
218 ))
219 }
220}
221
222fn missing_record_expr_field_fix(
223 sema: &Semantics<RootDatabase>,
224 usage_file_id: FileId,
225 record_expr_field: &ast::RecordExprField,
226) -> Option<Assist> {
227 let record_lit = ast::RecordExpr::cast(record_expr_field.syntax().parent()?.parent()?)?;
228 let def_id = sema.resolve_variant(record_lit)?;
229 let module;
230 let def_file_id;
231 let record_fields = match def_id {
232 VariantDef::Struct(s) => {
233 module = s.module(sema.db);
234 let source = s.source(sema.db)?;
235 def_file_id = source.file_id;
236 let fields = source.value.field_list()?;
237 record_field_list(fields)?
238 }
239 VariantDef::Union(u) => {
240 module = u.module(sema.db);
241 let source = u.source(sema.db)?;
242 def_file_id = source.file_id;
243 source.value.record_field_list()?
244 }
245 VariantDef::Variant(e) => {
246 module = e.module(sema.db);
247 let source = e.source(sema.db)?;
248 def_file_id = source.file_id;
249 let fields = source.value.field_list()?;
250 record_field_list(fields)?
251 }
252 };
253 let def_file_id = def_file_id.original_file(sema.db);
254
255 let new_field_type = sema.type_of_expr(&record_expr_field.expr()?)?;
256 if new_field_type.is_unknown() {
257 return None;
258 }
259 let new_field = make::record_field(
260 None,
261 make::name(&record_expr_field.field_name()?.text()),
262 make::ty(&new_field_type.display_source_code(sema.db, module.into()).ok()?),
263 );
264
265 let last_field = record_fields.fields().last()?;
266 let last_field_syntax = last_field.syntax();
267 let indent = IndentLevel::from_node(last_field_syntax);
268
269 let mut new_field = new_field.to_string();
270 if usage_file_id != def_file_id {
271 new_field = format!("pub(crate) {}", new_field);
272 }
273 new_field = format!("\n{}{}", indent, new_field);
274
275 let needs_comma = !last_field_syntax.to_string().ends_with(',');
276 if needs_comma {
277 new_field = format!(",{}", new_field);
278 }
279
280 let source_change = SourceChange::from_text_edit(
281 def_file_id,
282 TextEdit::insert(last_field_syntax.text_range().end(), new_field),
283 );
284 return Some(fix(
285 "create_field",
286 "Create field",
287 source_change,
288 record_expr_field.syntax().text_range(),
289 ));
290
291 fn record_field_list(field_def_list: ast::FieldList) -> Option<ast::RecordFieldList> {
292 match field_def_list {
293 ast::FieldList::RecordFieldList(it) => Some(it),
294 ast::FieldList::TupleFieldList(_) => None,
295 }
296 }
297}
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..80aca58a1
--- /dev/null
+++ b/crates/ide/src/diagnostics/fixes/change_case.rs
@@ -0,0 +1,155 @@
1use hir::{db::AstDatabase, diagnostics::IncorrectCase, InFile, Semantics};
2use ide_assists::{Assist, AssistResolveStrategy};
3use ide_db::{base_db::FilePosition, RootDatabase};
4use syntax::AstNode;
5
6use crate::{
7 diagnostics::{unresolved_fix, DiagnosticWithFix},
8 references::rename::rename_with_semantics,
9};
10
11impl DiagnosticWithFix for IncorrectCase {
12 fn fix(
13 &self,
14 sema: &Semantics<RootDatabase>,
15 resolve: &AssistResolveStrategy,
16 ) -> Option<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(res)
32 }
33}
34
35#[cfg(test)]
36mod change_case {
37 use crate::{
38 diagnostics::tests::{check_fix, check_no_diagnostics},
39 fixture, AssistResolveStrategy, DiagnosticsConfig,
40 };
41
42 #[test]
43 fn test_rename_incorrect_case() {
44 check_fix(
45 r#"
46pub struct test_struct$0 { one: i32 }
47
48pub fn some_fn(val: test_struct) -> test_struct {
49 test_struct { one: val.one + 1 }
50}
51"#,
52 r#"
53pub struct TestStruct { one: i32 }
54
55pub fn some_fn(val: TestStruct) -> TestStruct {
56 TestStruct { one: val.one + 1 }
57}
58"#,
59 );
60
61 check_fix(
62 r#"
63pub fn some_fn(NonSnakeCase$0: u8) -> u8 {
64 NonSnakeCase
65}
66"#,
67 r#"
68pub fn some_fn(non_snake_case: u8) -> u8 {
69 non_snake_case
70}
71"#,
72 );
73
74 check_fix(
75 r#"
76pub fn SomeFn$0(val: u8) -> u8 {
77 if val != 0 { SomeFn(val - 1) } else { val }
78}
79"#,
80 r#"
81pub fn some_fn(val: u8) -> u8 {
82 if val != 0 { some_fn(val - 1) } else { val }
83}
84"#,
85 );
86
87 check_fix(
88 r#"
89fn some_fn() {
90 let whatAWeird_Formatting$0 = 10;
91 another_func(whatAWeird_Formatting);
92}
93"#,
94 r#"
95fn some_fn() {
96 let what_a_weird_formatting = 10;
97 another_func(what_a_weird_formatting);
98}
99"#,
100 );
101 }
102
103 #[test]
104 fn test_uppercase_const_no_diagnostics() {
105 check_no_diagnostics(
106 r#"
107fn foo() {
108 const ANOTHER_ITEM$0: &str = "some_item";
109}
110"#,
111 );
112 }
113
114 #[test]
115 fn test_rename_incorrect_case_struct_method() {
116 check_fix(
117 r#"
118pub struct TestStruct;
119
120impl TestStruct {
121 pub fn SomeFn$0() -> TestStruct {
122 TestStruct
123 }
124}
125"#,
126 r#"
127pub struct TestStruct;
128
129impl TestStruct {
130 pub fn some_fn() -> TestStruct {
131 TestStruct
132 }
133}
134"#,
135 );
136 }
137
138 #[test]
139 fn test_single_incorrect_case_diagnostic_in_function_name_issue_6970() {
140 let input = r#"fn FOO$0() {}"#;
141 let expected = r#"fn foo() {}"#;
142
143 let (analysis, file_position) = fixture::position(input);
144 let diagnostics = analysis
145 .diagnostics(
146 &DiagnosticsConfig::default(),
147 AssistResolveStrategy::All,
148 file_position.file_id,
149 )
150 .unwrap();
151 assert_eq!(diagnostics.len(), 1);
152
153 check_fix(input, expected);
154 }
155}
diff --git a/crates/ide/src/diagnostics/fixes/create_field.rs b/crates/ide/src/diagnostics/fixes/create_field.rs
new file mode 100644
index 000000000..24e0fda52
--- /dev/null
+++ b/crates/ide/src/diagnostics/fixes/create_field.rs
@@ -0,0 +1,157 @@
1use hir::{db::AstDatabase, diagnostics::NoSuchField, HasSource, HirDisplay, Semantics};
2use ide_db::{base_db::FileId, source_change::SourceChange, RootDatabase};
3use syntax::{
4 ast::{self, edit::IndentLevel, make},
5 AstNode,
6};
7use text_edit::TextEdit;
8
9use crate::{
10 diagnostics::{fix, DiagnosticWithFix},
11 Assist, AssistResolveStrategy,
12};
13
14impl DiagnosticWithFix for NoSuchField {
15 fn fix(
16 &self,
17 sema: &Semantics<RootDatabase>,
18 _resolve: &AssistResolveStrategy,
19 ) -> Option<Assist> {
20 let root = sema.db.parse_or_expand(self.file)?;
21 missing_record_expr_field_fix(
22 &sema,
23 self.file.original_file(sema.db),
24 &self.field.to_node(&root),
25 )
26 }
27}
28
29fn missing_record_expr_field_fix(
30 sema: &Semantics<RootDatabase>,
31 usage_file_id: FileId,
32 record_expr_field: &ast::RecordExprField,
33) -> Option<Assist> {
34 let record_lit = ast::RecordExpr::cast(record_expr_field.syntax().parent()?.parent()?)?;
35 let def_id = sema.resolve_variant(record_lit)?;
36 let module;
37 let def_file_id;
38 let record_fields = match def_id {
39 hir::VariantDef::Struct(s) => {
40 module = s.module(sema.db);
41 let source = s.source(sema.db)?;
42 def_file_id = source.file_id;
43 let fields = source.value.field_list()?;
44 record_field_list(fields)?
45 }
46 hir::VariantDef::Union(u) => {
47 module = u.module(sema.db);
48 let source = u.source(sema.db)?;
49 def_file_id = source.file_id;
50 source.value.record_field_list()?
51 }
52 hir::VariantDef::Variant(e) => {
53 module = e.module(sema.db);
54 let source = e.source(sema.db)?;
55 def_file_id = source.file_id;
56 let fields = source.value.field_list()?;
57 record_field_list(fields)?
58 }
59 };
60 let def_file_id = def_file_id.original_file(sema.db);
61
62 let new_field_type = sema.type_of_expr(&record_expr_field.expr()?)?;
63 if new_field_type.is_unknown() {
64 return None;
65 }
66 let new_field = make::record_field(
67 None,
68 make::name(&record_expr_field.field_name()?.text()),
69 make::ty(&new_field_type.display_source_code(sema.db, module.into()).ok()?),
70 );
71
72 let last_field = record_fields.fields().last()?;
73 let last_field_syntax = last_field.syntax();
74 let indent = IndentLevel::from_node(last_field_syntax);
75
76 let mut new_field = new_field.to_string();
77 if usage_file_id != def_file_id {
78 new_field = format!("pub(crate) {}", new_field);
79 }
80 new_field = format!("\n{}{}", indent, new_field);
81
82 let needs_comma = !last_field_syntax.to_string().ends_with(',');
83 if needs_comma {
84 new_field = format!(",{}", new_field);
85 }
86
87 let source_change = SourceChange::from_text_edit(
88 def_file_id,
89 TextEdit::insert(last_field_syntax.text_range().end(), new_field),
90 );
91
92 return Some(fix(
93 "create_field",
94 "Create field",
95 source_change,
96 record_expr_field.syntax().text_range(),
97 ));
98
99 fn record_field_list(field_def_list: ast::FieldList) -> Option<ast::RecordFieldList> {
100 match field_def_list {
101 ast::FieldList::RecordFieldList(it) => Some(it),
102 ast::FieldList::TupleFieldList(_) => None,
103 }
104 }
105}
106
107#[cfg(test)]
108mod tests {
109 use crate::diagnostics::tests::check_fix;
110
111 #[test]
112 fn test_add_field_from_usage() {
113 check_fix(
114 r"
115fn main() {
116 Foo { bar: 3, baz$0: false};
117}
118struct Foo {
119 bar: i32
120}
121",
122 r"
123fn main() {
124 Foo { bar: 3, baz: false};
125}
126struct Foo {
127 bar: i32,
128 baz: bool
129}
130",
131 )
132 }
133
134 #[test]
135 fn test_add_field_in_other_file_from_usage() {
136 check_fix(
137 r#"
138//- /main.rs
139mod foo;
140
141fn main() {
142 foo::Foo { bar: 3, $0baz: false};
143}
144//- /foo.rs
145struct Foo {
146 bar: i32
147}
148"#,
149 r#"
150struct Foo {
151 bar: i32,
152 pub(crate) baz: bool
153}
154"#,
155 )
156 }
157}
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..37a0e37a9
--- /dev/null
+++ b/crates/ide/src/diagnostics/fixes/fill_missing_fields.rs
@@ -0,0 +1,217 @@
1use hir::{db::AstDatabase, diagnostics::MissingFields, Semantics};
2use ide_assists::AssistResolveStrategy;
3use ide_db::{source_change::SourceChange, RootDatabase};
4use syntax::{algo, ast::make, AstNode};
5use text_edit::TextEdit;
6
7use crate::{
8 diagnostics::{fix, fixes::DiagnosticWithFix},
9 Assist,
10};
11
12impl DiagnosticWithFix for MissingFields {
13 fn fix(
14 &self,
15 sema: &Semantics<RootDatabase>,
16 _resolve: &AssistResolveStrategy,
17 ) -> Option<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(fix(
45 "fill_missing_fields",
46 "Fill struct fields",
47 SourceChange::from_text_edit(self.file.original_file(sema.db), edit),
48 sema.original_range(&field_list_parent.syntax()).range,
49 ))
50 }
51}
52
53#[cfg(test)]
54mod tests {
55 use crate::diagnostics::tests::{check_fix, check_no_diagnostics};
56
57 #[test]
58 fn test_fill_struct_fields_empty() {
59 check_fix(
60 r#"
61struct TestStruct { one: i32, two: i64 }
62
63fn test_fn() {
64 let s = TestStruct {$0};
65}
66"#,
67 r#"
68struct TestStruct { one: i32, two: i64 }
69
70fn test_fn() {
71 let s = TestStruct { one: (), two: () };
72}
73"#,
74 );
75 }
76
77 #[test]
78 fn test_fill_struct_fields_self() {
79 check_fix(
80 r#"
81struct TestStruct { one: i32 }
82
83impl TestStruct {
84 fn test_fn() { let s = Self {$0}; }
85}
86"#,
87 r#"
88struct TestStruct { one: i32 }
89
90impl TestStruct {
91 fn test_fn() { let s = Self { one: () }; }
92}
93"#,
94 );
95 }
96
97 #[test]
98 fn test_fill_struct_fields_enum() {
99 check_fix(
100 r#"
101enum Expr {
102 Bin { lhs: Box<Expr>, rhs: Box<Expr> }
103}
104
105impl Expr {
106 fn new_bin(lhs: Box<Expr>, rhs: Box<Expr>) -> Expr {
107 Expr::Bin {$0 }
108 }
109}
110"#,
111 r#"
112enum Expr {
113 Bin { lhs: Box<Expr>, rhs: Box<Expr> }
114}
115
116impl Expr {
117 fn new_bin(lhs: Box<Expr>, rhs: Box<Expr>) -> Expr {
118 Expr::Bin { lhs: (), rhs: () }
119 }
120}
121"#,
122 );
123 }
124
125 #[test]
126 fn test_fill_struct_fields_partial() {
127 check_fix(
128 r#"
129struct TestStruct { one: i32, two: i64 }
130
131fn test_fn() {
132 let s = TestStruct{ two: 2$0 };
133}
134"#,
135 r"
136struct TestStruct { one: i32, two: i64 }
137
138fn test_fn() {
139 let s = TestStruct{ two: 2, one: () };
140}
141",
142 );
143 }
144
145 #[test]
146 fn test_fill_struct_fields_raw_ident() {
147 check_fix(
148 r#"
149struct TestStruct { r#type: u8 }
150
151fn test_fn() {
152 TestStruct { $0 };
153}
154"#,
155 r"
156struct TestStruct { r#type: u8 }
157
158fn test_fn() {
159 TestStruct { r#type: () };
160}
161",
162 );
163 }
164
165 #[test]
166 fn test_fill_struct_fields_no_diagnostic() {
167 check_no_diagnostics(
168 r#"
169struct TestStruct { one: i32, two: i64 }
170
171fn test_fn() {
172 let one = 1;
173 let s = TestStruct{ one, two: 2 };
174}
175 "#,
176 );
177 }
178
179 #[test]
180 fn test_fill_struct_fields_no_diagnostic_on_spread() {
181 check_no_diagnostics(
182 r#"
183struct TestStruct { one: i32, two: i64 }
184
185fn test_fn() {
186 let one = 1;
187 let s = TestStruct{ ..a };
188}
189"#,
190 );
191 }
192
193 #[test]
194 fn test_fill_struct_fields_blank_line() {
195 check_fix(
196 r#"
197struct S { a: (), b: () }
198
199fn f() {
200 S {
201 $0
202 };
203}
204"#,
205 r#"
206struct S { a: (), b: () }
207
208fn f() {
209 S {
210 a: (),
211 b: (),
212 };
213}
214"#,
215 );
216 }
217}
diff --git a/crates/ide/src/diagnostics/fixes/remove_semicolon.rs b/crates/ide/src/diagnostics/fixes/remove_semicolon.rs
new file mode 100644
index 000000000..45471da41
--- /dev/null
+++ b/crates/ide/src/diagnostics/fixes/remove_semicolon.rs
@@ -0,0 +1,41 @@
1use hir::{db::AstDatabase, diagnostics::RemoveThisSemicolon, Semantics};
2use ide_assists::{Assist, AssistResolveStrategy};
3use ide_db::{source_change::SourceChange, RootDatabase};
4use syntax::{ast, AstNode};
5use text_edit::TextEdit;
6
7use crate::diagnostics::{fix, DiagnosticWithFix};
8
9impl DiagnosticWithFix for RemoveThisSemicolon {
10 fn fix(
11 &self,
12 sema: &Semantics<RootDatabase>,
13 _resolve: &AssistResolveStrategy,
14 ) -> Option<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(fix("remove_semicolon", "Remove this semicolon", source_change, semicolon))
30 }
31}
32
33#[cfg(test)]
34mod tests {
35 use crate::diagnostics::tests::check_fix;
36
37 #[test]
38 fn remove_semicolon() {
39 check_fix(r#"fn f() -> i32 { 92$0; }"#, r#"fn f() -> i32 { 92 }"#);
40 }
41}
diff --git a/crates/ide/src/diagnostics/fixes/replace_with_find_map.rs b/crates/ide/src/diagnostics/fixes/replace_with_find_map.rs
new file mode 100644
index 000000000..b0ef7b44a
--- /dev/null
+++ b/crates/ide/src/diagnostics/fixes/replace_with_find_map.rs
@@ -0,0 +1,84 @@
1use hir::{db::AstDatabase, diagnostics::ReplaceFilterMapNextWithFindMap, Semantics};
2use ide_assists::{Assist, AssistResolveStrategy};
3use ide_db::{source_change::SourceChange, RootDatabase};
4use syntax::{
5 ast::{self, ArgListOwner},
6 AstNode, TextRange,
7};
8use text_edit::TextEdit;
9
10use crate::diagnostics::{fix, DiagnosticWithFix};
11
12impl DiagnosticWithFix for ReplaceFilterMapNextWithFindMap {
13 fn fix(
14 &self,
15 sema: &Semantics<RootDatabase>,
16 _resolve: &AssistResolveStrategy,
17 ) -> Option<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(fix(
36 "replace_with_find_map",
37 "Replace filter_map(..).next() with find_map()",
38 source_change,
39 trigger_range,
40 ))
41 }
42}
43
44#[cfg(test)]
45mod tests {
46 use crate::diagnostics::tests::check_fix;
47
48 #[test]
49 fn replace_with_wind_map() {
50 check_fix(
51 r#"
52//- /main.rs crate:main deps:core
53use core::iter::Iterator;
54use core::option::Option::{self, Some, None};
55fn foo() {
56 let m = [1, 2, 3].iter().$0filter_map(|x| if *x == 2 { Some (4) } else { None }).next();
57}
58//- /core/lib.rs crate:core
59pub mod option {
60 pub enum Option<T> { Some(T), None }
61}
62pub mod iter {
63 pub trait Iterator {
64 type Item;
65 fn filter_map<B, F>(self, f: F) -> FilterMap where F: FnMut(Self::Item) -> Option<B> { FilterMap }
66 fn next(&mut self) -> Option<Self::Item>;
67 }
68 pub struct FilterMap {}
69 impl Iterator for FilterMap {
70 type Item = i32;
71 fn next(&mut self) -> i32 { 7 }
72 }
73}
74"#,
75 r#"
76use core::iter::Iterator;
77use core::option::Option::{self, Some, None};
78fn foo() {
79 let m = [1, 2, 3].iter().find_map(|x| if *x == 2 { Some (4) } else { None });
80}
81"#,
82 )
83 }
84}
diff --git a/crates/ide/src/diagnostics/fixes/unresolved_module.rs b/crates/ide/src/diagnostics/fixes/unresolved_module.rs
new file mode 100644
index 000000000..81244b293
--- /dev/null
+++ b/crates/ide/src/diagnostics/fixes/unresolved_module.rs
@@ -0,0 +1,87 @@
1use hir::{db::AstDatabase, diagnostics::UnresolvedModule, Semantics};
2use ide_assists::{Assist, AssistResolveStrategy};
3use ide_db::{base_db::AnchoredPathBuf, source_change::FileSystemEdit, RootDatabase};
4use syntax::AstNode;
5
6use crate::diagnostics::{fix, DiagnosticWithFix};
7
8impl DiagnosticWithFix for UnresolvedModule {
9 fn fix(
10 &self,
11 sema: &Semantics<RootDatabase>,
12 _resolve: &AssistResolveStrategy,
13 ) -> Option<Assist> {
14 let root = sema.db.parse_or_expand(self.file)?;
15 let unresolved_module = self.decl.to_node(&root);
16 Some(fix(
17 "create_module",
18 "Create module",
19 FileSystemEdit::CreateFile {
20 dst: AnchoredPathBuf {
21 anchor: self.file.original_file(sema.db),
22 path: self.candidate.clone(),
23 },
24 initial_contents: "".to_string(),
25 }
26 .into(),
27 unresolved_module.syntax().text_range(),
28 ))
29 }
30}
31
32#[cfg(test)]
33mod tests {
34 use expect_test::expect;
35
36 use crate::diagnostics::tests::check_expect;
37
38 #[test]
39 fn test_unresolved_module_diagnostic() {
40 check_expect(
41 r#"mod foo;"#,
42 expect![[r#"
43 [
44 Diagnostic {
45 message: "unresolved module",
46 range: 0..8,
47 severity: Error,
48 fix: Some(
49 Assist {
50 id: AssistId(
51 "create_module",
52 QuickFix,
53 ),
54 label: "Create module",
55 group: None,
56 target: 0..8,
57 source_change: Some(
58 SourceChange {
59 source_file_edits: {},
60 file_system_edits: [
61 CreateFile {
62 dst: AnchoredPathBuf {
63 anchor: FileId(
64 0,
65 ),
66 path: "foo.rs",
67 },
68 initial_contents: "",
69 },
70 ],
71 is_snippet: false,
72 },
73 ),
74 },
75 ),
76 unused: false,
77 code: Some(
78 DiagnosticCode(
79 "unresolved-module",
80 ),
81 ),
82 },
83 ]
84 "#]],
85 );
86 }
87}
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..66676064a
--- /dev/null
+++ b/crates/ide/src/diagnostics/fixes/wrap_tail_expr.rs
@@ -0,0 +1,211 @@
1use hir::{db::AstDatabase, diagnostics::MissingOkOrSomeInTailExpr, Semantics};
2use ide_assists::{Assist, AssistResolveStrategy};
3use ide_db::{source_change::SourceChange, RootDatabase};
4use syntax::AstNode;
5use text_edit::TextEdit;
6
7use crate::diagnostics::{fix, DiagnosticWithFix};
8
9impl DiagnosticWithFix for MissingOkOrSomeInTailExpr {
10 fn fix(
11 &self,
12 sema: &Semantics<RootDatabase>,
13 _resolve: &AssistResolveStrategy,
14 ) -> Option<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(fix("wrap_tail_expr", name, source_change, tail_expr_range))
23 }
24}
25
26#[cfg(test)]
27mod tests {
28 use crate::diagnostics::tests::{check_fix, check_no_diagnostics};
29
30 #[test]
31 fn test_wrap_return_type_option() {
32 check_fix(
33 r#"
34//- /main.rs crate:main deps:core
35use core::option::Option::{self, Some, None};
36
37fn div(x: i32, y: i32) -> Option<i32> {
38 if y == 0 {
39 return None;
40 }
41 x / y$0
42}
43//- /core/lib.rs crate:core
44pub mod result {
45 pub enum Result<T, E> { Ok(T), Err(E) }
46}
47pub mod option {
48 pub enum Option<T> { Some(T), None }
49}
50"#,
51 r#"
52use core::option::Option::{self, Some, None};
53
54fn div(x: i32, y: i32) -> Option<i32> {
55 if y == 0 {
56 return None;
57 }
58 Some(x / y)
59}
60"#,
61 );
62 }
63
64 #[test]
65 fn test_wrap_return_type() {
66 check_fix(
67 r#"
68//- /main.rs crate:main deps:core
69use core::result::Result::{self, Ok, Err};
70
71fn div(x: i32, y: i32) -> Result<i32, ()> {
72 if y == 0 {
73 return Err(());
74 }
75 x / y$0
76}
77//- /core/lib.rs crate:core
78pub mod result {
79 pub enum Result<T, E> { Ok(T), Err(E) }
80}
81pub mod option {
82 pub enum Option<T> { Some(T), None }
83}
84"#,
85 r#"
86use core::result::Result::{self, Ok, Err};
87
88fn div(x: i32, y: i32) -> Result<i32, ()> {
89 if y == 0 {
90 return Err(());
91 }
92 Ok(x / y)
93}
94"#,
95 );
96 }
97
98 #[test]
99 fn test_wrap_return_type_handles_generic_functions() {
100 check_fix(
101 r#"
102//- /main.rs crate:main deps:core
103use core::result::Result::{self, Ok, Err};
104
105fn div<T>(x: T) -> Result<T, i32> {
106 if x == 0 {
107 return Err(7);
108 }
109 $0x
110}
111//- /core/lib.rs crate:core
112pub mod result {
113 pub enum Result<T, E> { Ok(T), Err(E) }
114}
115pub mod option {
116 pub enum Option<T> { Some(T), None }
117}
118"#,
119 r#"
120use core::result::Result::{self, Ok, Err};
121
122fn div<T>(x: T) -> Result<T, i32> {
123 if x == 0 {
124 return Err(7);
125 }
126 Ok(x)
127}
128"#,
129 );
130 }
131
132 #[test]
133 fn test_wrap_return_type_handles_type_aliases() {
134 check_fix(
135 r#"
136//- /main.rs crate:main deps:core
137use core::result::Result::{self, Ok, Err};
138
139type MyResult<T> = Result<T, ()>;
140
141fn div(x: i32, y: i32) -> MyResult<i32> {
142 if y == 0 {
143 return Err(());
144 }
145 x $0/ y
146}
147//- /core/lib.rs crate:core
148pub mod result {
149 pub enum Result<T, E> { Ok(T), Err(E) }
150}
151pub mod option {
152 pub enum Option<T> { Some(T), None }
153}
154"#,
155 r#"
156use core::result::Result::{self, Ok, Err};
157
158type MyResult<T> = Result<T, ()>;
159
160fn div(x: i32, y: i32) -> MyResult<i32> {
161 if y == 0 {
162 return Err(());
163 }
164 Ok(x / y)
165}
166"#,
167 );
168 }
169
170 #[test]
171 fn test_wrap_return_type_not_applicable_when_expr_type_does_not_match_ok_type() {
172 check_no_diagnostics(
173 r#"
174//- /main.rs crate:main deps:core
175use core::result::Result::{self, Ok, Err};
176
177fn foo() -> Result<(), i32> { 0 }
178
179//- /core/lib.rs crate:core
180pub mod result {
181 pub enum Result<T, E> { Ok(T), Err(E) }
182}
183pub mod option {
184 pub enum Option<T> { Some(T), None }
185}
186"#,
187 );
188 }
189
190 #[test]
191 fn test_wrap_return_type_not_applicable_when_return_type_is_not_result_or_option() {
192 check_no_diagnostics(
193 r#"
194//- /main.rs crate:main deps:core
195use core::result::Result::{self, Ok, Err};
196
197enum SomeOtherEnum { Ok(i32), Err(String) }
198
199fn foo() -> SomeOtherEnum { 0 }
200
201//- /core/lib.rs crate:core
202pub mod result {
203 pub enum Result<T, E> { Ok(T), Err(E) }
204}
205pub mod option {
206 pub enum Option<T> { Some(T), None }
207}
208"#,
209 );
210 }
211}
diff --git a/crates/ide/src/doc_links.rs b/crates/ide/src/doc_links.rs
index cb5a8e19a..320694a17 100644
--- a/crates/ide/src/doc_links.rs
+++ b/crates/ide/src/doc_links.rs
@@ -29,7 +29,8 @@ pub(crate) type DocumentationLink = String;
29/// Rewrite documentation links in markdown to point to an online host (e.g. docs.rs) 29/// Rewrite documentation links in markdown to point to an online host (e.g. docs.rs)
30pub(crate) fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition) -> String { 30pub(crate) fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition) -> String {
31 let mut cb = broken_link_clone_cb; 31 let mut cb = broken_link_clone_cb;
32 let doc = Parser::new_with_broken_link_callback(markdown, Options::empty(), Some(&mut cb)); 32 let doc =
33 Parser::new_with_broken_link_callback(markdown, Options::ENABLE_TASKLISTS, Some(&mut cb));
33 34
34 let doc = map_links(doc, |target, title: &str| { 35 let doc = map_links(doc, |target, title: &str| {
35 // This check is imperfect, there's some overlap between valid intra-doc links 36 // This check is imperfect, there's some overlap between valid intra-doc links
@@ -64,8 +65,7 @@ pub(crate) fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Defi
64pub(crate) fn remove_links(markdown: &str) -> String { 65pub(crate) fn remove_links(markdown: &str) -> String {
65 let mut drop_link = false; 66 let mut drop_link = false;
66 67
67 let mut opts = Options::empty(); 68 let opts = Options::ENABLE_TASKLISTS | Options::ENABLE_FOOTNOTES;
68 opts.insert(Options::ENABLE_FOOTNOTES);
69 69
70 let mut cb = |_: BrokenLink| { 70 let mut cb = |_: BrokenLink| {
71 let empty = InlineStr::try_from("").unwrap(); 71 let empty = InlineStr::try_from("").unwrap();
@@ -123,7 +123,7 @@ pub(crate) fn extract_definitions_from_markdown(
123) -> Vec<(TextRange, String, Option<hir::Namespace>)> { 123) -> Vec<(TextRange, String, Option<hir::Namespace>)> {
124 Parser::new_with_broken_link_callback( 124 Parser::new_with_broken_link_callback(
125 markdown, 125 markdown,
126 Options::empty(), 126 Options::ENABLE_TASKLISTS,
127 Some(&mut broken_link_clone_cb), 127 Some(&mut broken_link_clone_cb),
128 ) 128 )
129 .into_offset_iter() 129 .into_offset_iter()
diff --git a/crates/ide/src/inlay_hints.rs b/crates/ide/src/inlay_hints.rs
index e0bf660c4..960d169f4 100644
--- a/crates/ide/src/inlay_hints.rs
+++ b/crates/ide/src/inlay_hints.rs
@@ -1126,7 +1126,7 @@ fn main() {
1126 r#" 1126 r#"
1127fn main() { 1127fn main() {
1128 let data = &[1i32, 2, 3]; 1128 let data = &[1i32, 2, 3];
1129 //^^^^ &[i32; _] 1129 //^^^^ &[i32; 3]
1130 for i 1130 for i
1131}"#, 1131}"#,
1132 ); 1132 );
diff --git a/crates/ide/src/join_lines.rs b/crates/ide/src/join_lines.rs
index fe2a349e6..c67ccd1a9 100644
--- a/crates/ide/src/join_lines.rs
+++ b/crates/ide/src/join_lines.rs
@@ -1,8 +1,10 @@
1use std::convert::TryFrom;
2
1use ide_assists::utils::extract_trivial_expression; 3use ide_assists::utils::extract_trivial_expression;
2use itertools::Itertools; 4use itertools::Itertools;
3use syntax::{ 5use syntax::{
4 algo::non_trivia_sibling, 6 algo::non_trivia_sibling,
5 ast::{self, AstNode, AstToken}, 7 ast::{self, AstNode, AstToken, IsString},
6 Direction, NodeOrToken, SourceFile, 8 Direction, NodeOrToken, SourceFile,
7 SyntaxKind::{self, USE_TREE, WHITESPACE}, 9 SyntaxKind::{self, USE_TREE, WHITESPACE},
8 SyntaxNode, SyntaxToken, TextRange, TextSize, T, 10 SyntaxNode, SyntaxToken, TextRange, TextSize, T,
@@ -65,14 +67,6 @@ fn remove_newlines(edit: &mut TextEditBuilder, token: &SyntaxToken, range: TextR
65 67
66fn remove_newline(edit: &mut TextEditBuilder, token: &SyntaxToken, offset: TextSize) { 68fn remove_newline(edit: &mut TextEditBuilder, token: &SyntaxToken, offset: TextSize) {
67 if token.kind() != WHITESPACE || token.text().bytes().filter(|&b| b == b'\n').count() != 1 { 69 if token.kind() != WHITESPACE || token.text().bytes().filter(|&b| b == b'\n').count() != 1 {
68 let mut string_open_quote = false;
69 if let Some(string) = ast::String::cast(token.clone()) {
70 if let Some(range) = string.open_quote_text_range() {
71 cov_mark::hit!(join_string_literal);
72 string_open_quote = range.end() == offset;
73 }
74 }
75
76 let n_spaces_after_line_break = { 70 let n_spaces_after_line_break = {
77 let suff = &token.text()[TextRange::new( 71 let suff = &token.text()[TextRange::new(
78 offset - token.text_range().start() + TextSize::of('\n'), 72 offset - token.text_range().start() + TextSize::of('\n'),
@@ -81,8 +75,23 @@ fn remove_newline(edit: &mut TextEditBuilder, token: &SyntaxToken, offset: TextS
81 suff.bytes().take_while(|&b| b == b' ').count() 75 suff.bytes().take_while(|&b| b == b' ').count()
82 }; 76 };
83 77
78 let mut no_space = false;
79 if let Some(string) = ast::String::cast(token.clone()) {
80 if let Some(range) = string.open_quote_text_range() {
81 cov_mark::hit!(join_string_literal_open_quote);
82 no_space |= range.end() == offset;
83 }
84 if let Some(range) = string.close_quote_text_range() {
85 cov_mark::hit!(join_string_literal_close_quote);
86 no_space |= range.start()
87 == offset
88 + TextSize::of('\n')
89 + TextSize::try_from(n_spaces_after_line_break).unwrap();
90 }
91 }
92
84 let range = TextRange::at(offset, ((n_spaces_after_line_break + 1) as u32).into()); 93 let range = TextRange::at(offset, ((n_spaces_after_line_break + 1) as u32).into());
85 let replace_with = if string_open_quote { "" } else { " " }; 94 let replace_with = if no_space { "" } else { " " };
86 edit.replace(range, replace_with.to_string()); 95 edit.replace(range, replace_with.to_string());
87 return; 96 return;
88 } 97 }
@@ -797,22 +806,54 @@ fn foo() {
797 806
798 #[test] 807 #[test]
799 fn join_string_literal() { 808 fn join_string_literal() {
800 cov_mark::check!(join_string_literal); 809 {
801 check_join_lines( 810 cov_mark::check!(join_string_literal_open_quote);
802 r#" 811 check_join_lines(
812 r#"
803fn main() { 813fn main() {
804 $0" 814 $0"
805hello 815hello
806"; 816";
807} 817}
808"#, 818"#,
809 r#" 819 r#"
810fn main() { 820fn main() {
811 $0"hello 821 $0"hello
812"; 822";
813} 823}
814"#, 824"#,
815 ); 825 );
826 }
827
828 {
829 cov_mark::check!(join_string_literal_close_quote);
830 check_join_lines(
831 r#"
832fn main() {
833 $0"hello
834";
835}
836"#,
837 r#"
838fn main() {
839 $0"hello";
840}
841"#,
842 );
843 check_join_lines(
844 r#"
845fn main() {
846 $0r"hello
847 ";
848}
849"#,
850 r#"
851fn main() {
852 $0r"hello";
853}
854"#,
855 );
856 }
816 857
817 check_join_lines( 858 check_join_lines(
818 r#" 859 r#"
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs
index 8e5b72044..db08547d1 100644
--- a/crates/ide/src/lib.rs
+++ b/crates/ide/src/lib.rs
@@ -49,6 +49,7 @@ mod syntax_tree;
49mod typing; 49mod typing;
50mod markdown_remove; 50mod markdown_remove;
51mod doc_links; 51mod doc_links;
52mod view_crate_graph;
52 53
53use std::sync::Arc; 54use std::sync::Arc;
54 55
@@ -287,6 +288,11 @@ impl Analysis {
287 self.with_db(|db| view_hir::view_hir(&db, position)) 288 self.with_db(|db| view_hir::view_hir(&db, position))
288 } 289 }
289 290
291 /// Renders the crate graph to GraphViz "dot" syntax.
292 pub fn view_crate_graph(&self) -> Cancelable<Result<String, String>> {
293 self.with_db(|db| view_crate_graph::view_crate_graph(&db))
294 }
295
290 pub fn expand_macro(&self, position: FilePosition) -> Cancelable<Option<ExpandedMacro>> { 296 pub fn expand_macro(&self, position: FilePosition) -> Cancelable<Option<ExpandedMacro>> {
291 self.with_db(|db| expand_macro::expand_macro(db, position)) 297 self.with_db(|db| expand_macro::expand_macro(db, position))
292 } 298 }
diff --git a/crates/ide/src/matching_brace.rs b/crates/ide/src/matching_brace.rs
index 261dcc255..011c8cc55 100644
--- a/crates/ide/src/matching_brace.rs
+++ b/crates/ide/src/matching_brace.rs
@@ -19,14 +19,10 @@ use syntax::{
19pub(crate) fn matching_brace(file: &SourceFile, offset: TextSize) -> Option<TextSize> { 19pub(crate) fn matching_brace(file: &SourceFile, offset: TextSize) -> Option<TextSize> {
20 const BRACES: &[SyntaxKind] = 20 const BRACES: &[SyntaxKind] =
21 &[T!['{'], T!['}'], T!['['], T![']'], T!['('], T![')'], T![<], T![>], T![|], T![|]]; 21 &[T!['{'], T!['}'], T!['['], T![']'], T!['('], T![')'], T![<], T![>], T![|], T![|]];
22 let (brace_token, brace_idx) = file 22 let (brace_token, brace_idx) = file.syntax().token_at_offset(offset).find_map(|node| {
23 .syntax() 23 let idx = BRACES.iter().position(|&brace| brace == node.kind())?;
24 .token_at_offset(offset) 24 Some((node, idx))
25 .filter_map(|node| { 25 })?;
26 let idx = BRACES.iter().position(|&brace| brace == node.kind())?;
27 Some((node, idx))
28 })
29 .next()?;
30 let parent = brace_token.parent()?; 26 let parent = brace_token.parent()?;
31 if brace_token.kind() == T![|] && !ast::ParamList::can_cast(parent.kind()) { 27 if brace_token.kind() == T![|] && !ast::ParamList::can_cast(parent.kind()) {
32 cov_mark::hit!(pipes_not_braces); 28 cov_mark::hit!(pipes_not_braces);
diff --git a/crates/ide/src/parent_module.rs b/crates/ide/src/parent_module.rs
index 99365c8a7..9b1f48044 100644
--- a/crates/ide/src/parent_module.rs
+++ b/crates/ide/src/parent_module.rs
@@ -1,6 +1,8 @@
1use hir::Semantics; 1use hir::Semantics;
2use ide_db::base_db::{CrateId, FileId, FilePosition}; 2use ide_db::{
3use ide_db::RootDatabase; 3 base_db::{CrateId, FileId, FilePosition},
4 RootDatabase,
5};
4use itertools::Itertools; 6use itertools::Itertools;
5use syntax::{ 7use syntax::{
6 algo::find_node_at_offset, 8 algo::find_node_at_offset,
diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs
index 11ca7ec6b..ae492a264 100644
--- a/crates/ide/src/references.rs
+++ b/crates/ide/src/references.rs
@@ -65,7 +65,7 @@ pub(crate) fn find_all_refs(
65 (find_def(&sema, &syntax, position)?, false) 65 (find_def(&sema, &syntax, position)?, false)
66 }; 66 };
67 67
68 let mut usages = def.usages(sema).set_scope(search_scope).all(); 68 let mut usages = def.usages(sema).set_scope(search_scope).include_self_refs().all();
69 if is_literal_search { 69 if is_literal_search {
70 // filter for constructor-literals 70 // filter for constructor-literals
71 let refs = usages.references.values_mut(); 71 let refs = usages.references.values_mut();
@@ -1163,22 +1163,76 @@ fn foo<const FOO$0: usize>() -> usize {
1163 } 1163 }
1164 1164
1165 #[test] 1165 #[test]
1166 fn test_find_self_ty_in_trait_def() { 1166 fn test_trait() {
1167 check( 1167 check(
1168 r#" 1168 r#"
1169trait Foo { 1169trait Foo$0 where Self: {}
1170 fn f() -> Self$0; 1170
1171impl Foo for () {}
1172"#,
1173 expect![[r#"
1174 Foo Trait FileId(0) 0..24 6..9
1175
1176 FileId(0) 31..34
1177 "#]],
1178 );
1179 }
1180
1181 #[test]
1182 fn test_trait_self() {
1183 check(
1184 r#"
1185trait Foo where Self$0 {
1186 fn f() -> Self;
1171} 1187}
1188
1189impl Foo for () {}
1172"#, 1190"#,
1173 expect![[r#" 1191 expect![[r#"
1174 Self TypeParam FileId(0) 6..9 6..9 1192 Self TypeParam FileId(0) 6..9 6..9
1175 1193
1176 FileId(0) 26..30 1194 FileId(0) 16..20
1195 FileId(0) 37..41
1177 "#]], 1196 "#]],
1178 ); 1197 );
1179 } 1198 }
1180 1199
1181 #[test] 1200 #[test]
1201 fn test_self_ty() {
1202 check(
1203 r#"
1204 struct $0Foo;
1205
1206 impl Foo where Self: {
1207 fn f() -> Self;
1208 }
1209 "#,
1210 expect![[r#"
1211 Foo Struct FileId(0) 0..11 7..10
1212
1213 FileId(0) 18..21
1214 FileId(0) 28..32
1215 FileId(0) 50..54
1216 "#]],
1217 );
1218 check(
1219 r#"
1220struct Foo;
1221
1222impl Foo where Self: {
1223 fn f() -> Self$0;
1224}
1225"#,
1226 expect![[r#"
1227 impl Impl FileId(0) 13..57 18..21
1228
1229 FileId(0) 18..21
1230 FileId(0) 28..32
1231 FileId(0) 50..54
1232 "#]],
1233 );
1234 }
1235 #[test]
1182 fn test_self_variant_with_payload() { 1236 fn test_self_variant_with_payload() {
1183 check( 1237 check(
1184 r#" 1238 r#"
diff --git a/crates/ide/src/references/rename.rs b/crates/ide/src/references/rename.rs
index 175e7a31d..2bf953305 100644
--- a/crates/ide/src/references/rename.rs
+++ b/crates/ide/src/references/rename.rs
@@ -1888,4 +1888,21 @@ impl Foo {
1888 "error: Cannot rename `Self`", 1888 "error: Cannot rename `Self`",
1889 ); 1889 );
1890 } 1890 }
1891
1892 #[test]
1893 fn test_rename_ignores_self_ty() {
1894 check(
1895 "Fo0",
1896 r#"
1897struct $0Foo;
1898
1899impl Foo where Self: {}
1900"#,
1901 r#"
1902struct Fo0;
1903
1904impl Fo0 where Self: {}
1905"#,
1906 );
1907 }
1891} 1908}
diff --git a/crates/ide/src/syntax_highlighting/highlight.rs b/crates/ide/src/syntax_highlighting/highlight.rs
index b586dcc17..baed8e217 100644
--- a/crates/ide/src/syntax_highlighting/highlight.rs
+++ b/crates/ide/src/syntax_highlighting/highlight.rs
@@ -227,8 +227,8 @@ pub(super) fn element(
227 k if k.is_keyword() => { 227 k if k.is_keyword() => {
228 let h = Highlight::new(HlTag::Keyword); 228 let h = Highlight::new(HlTag::Keyword);
229 match k { 229 match k {
230 T![await] 230 T![await] => h | HlMod::Async | HlMod::ControlFlow,
231 | T![break] 231 T![break]
232 | T![continue] 232 | T![continue]
233 | T![else] 233 | T![else]
234 | T![if] 234 | T![if]
@@ -255,6 +255,7 @@ pub(super) fn element(
255 }) 255 })
256 .map(|modifier| h | modifier) 256 .map(|modifier| h | modifier)
257 .unwrap_or(h), 257 .unwrap_or(h),
258 T![async] => h | HlMod::Async,
258 _ => h, 259 _ => h,
259 } 260 }
260 } 261 }
@@ -310,6 +311,9 @@ fn highlight_def(db: &RootDatabase, def: Definition) -> Highlight {
310 if func.is_unsafe(db) { 311 if func.is_unsafe(db) {
311 h |= HlMod::Unsafe; 312 h |= HlMod::Unsafe;
312 } 313 }
314 if func.is_async(db) {
315 h |= HlMod::Async;
316 }
313 return h; 317 return h;
314 } 318 }
315 hir::ModuleDef::Adt(hir::Adt::Struct(_)) => HlTag::Symbol(SymbolKind::Struct), 319 hir::ModuleDef::Adt(hir::Adt::Struct(_)) => HlTag::Symbol(SymbolKind::Struct),
@@ -409,6 +413,9 @@ fn highlight_method_call(
409 if func.is_unsafe(sema.db) || sema.is_unsafe_method_call(&method_call) { 413 if func.is_unsafe(sema.db) || sema.is_unsafe_method_call(&method_call) {
410 h |= HlMod::Unsafe; 414 h |= HlMod::Unsafe;
411 } 415 }
416 if func.is_async(sema.db) {
417 h |= HlMod::Async;
418 }
412 if func.as_assoc_item(sema.db).and_then(|it| it.containing_trait(sema.db)).is_some() { 419 if func.as_assoc_item(sema.db).and_then(|it| it.containing_trait(sema.db)).is_some() {
413 h |= HlMod::Trait 420 h |= HlMod::Trait
414 } 421 }
diff --git a/crates/ide/src/syntax_highlighting/inject.rs b/crates/ide/src/syntax_highlighting/inject.rs
index bc221d599..4269d339e 100644
--- a/crates/ide/src/syntax_highlighting/inject.rs
+++ b/crates/ide/src/syntax_highlighting/inject.rs
@@ -6,7 +6,7 @@ use either::Either;
6use hir::{InFile, Semantics}; 6use hir::{InFile, Semantics};
7use ide_db::{call_info::ActiveParameter, helpers::rust_doc::is_rust_fence, SymbolKind}; 7use ide_db::{call_info::ActiveParameter, helpers::rust_doc::is_rust_fence, SymbolKind};
8use syntax::{ 8use syntax::{
9 ast::{self, AstNode}, 9 ast::{self, AstNode, IsString},
10 AstToken, NodeOrToken, SyntaxNode, SyntaxToken, TextRange, TextSize, 10 AstToken, NodeOrToken, SyntaxNode, SyntaxToken, TextRange, TextSize,
11}; 11};
12 12
diff --git a/crates/ide/src/syntax_highlighting/tags.rs b/crates/ide/src/syntax_highlighting/tags.rs
index a304b3250..f4a2e7506 100644
--- a/crates/ide/src/syntax_highlighting/tags.rs
+++ b/crates/ide/src/syntax_highlighting/tags.rs
@@ -65,6 +65,8 @@ pub enum HlMod {
65 Static, 65 Static,
66 /// Used for items in traits and trait impls. 66 /// Used for items in traits and trait impls.
67 Trait, 67 Trait,
68 /// Used with keywords like `async` and `await`.
69 Async,
68 // Keep this last! 70 // Keep this last!
69 /// Used for unsafe functions, mutable statics, union accesses and unsafe operations. 71 /// Used for unsafe functions, mutable statics, union accesses and unsafe operations.
70 Unsafe, 72 Unsafe,
@@ -186,6 +188,7 @@ impl HlMod {
186 HlMod::Mutable, 188 HlMod::Mutable,
187 HlMod::Static, 189 HlMod::Static,
188 HlMod::Trait, 190 HlMod::Trait,
191 HlMod::Async,
189 HlMod::Unsafe, 192 HlMod::Unsafe,
190 ]; 193 ];
191 194
@@ -203,6 +206,7 @@ impl HlMod {
203 HlMod::Mutable => "mutable", 206 HlMod::Mutable => "mutable",
204 HlMod::Static => "static", 207 HlMod::Static => "static",
205 HlMod::Trait => "trait", 208 HlMod::Trait => "trait",
209 HlMod::Async => "async",
206 HlMod::Unsafe => "unsafe", 210 HlMod::Unsafe => "unsafe",
207 } 211 }
208 } 212 }
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html b/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html
index 8d83ba206..921a956e6 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html
@@ -37,13 +37,25 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
37 37
38.unresolved_reference { color: #FC5555; text-decoration: wavy underline; } 38.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
39</style> 39</style>
40<pre><code><span class="comment documentation">/// ```</span> 40<pre><code><span class="comment documentation">//! This is a module to test doc injection.</span>
41<span class="comment documentation">//! ```</span>
42<span class="comment documentation">//! </span><span class="keyword injected">fn</span><span class="none injected"> </span><span class="function declaration injected">test</span><span class="parenthesis injected">(</span><span class="parenthesis injected">)</span><span class="none injected"> </span><span class="brace injected">{</span><span class="brace injected">}</span>
43<span class="comment documentation">//! ```</span>
44
45<span class="comment documentation">/// ```</span>
41<span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="none injected"> </span><span class="punctuation injected">_</span><span class="none injected"> </span><span class="operator injected">=</span><span class="none injected"> </span><span class="string_literal injected">"early doctests should not go boom"</span><span class="semicolon injected">;</span> 46<span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="none injected"> </span><span class="punctuation injected">_</span><span class="none injected"> </span><span class="operator injected">=</span><span class="none injected"> </span><span class="string_literal injected">"early doctests should not go boom"</span><span class="semicolon injected">;</span>
42<span class="comment documentation">/// ```</span> 47<span class="comment documentation">/// ```</span>
43<span class="keyword">struct</span> <span class="struct declaration">Foo</span> <span class="brace">{</span> 48<span class="keyword">struct</span> <span class="struct declaration">Foo</span> <span class="brace">{</span>
44 <span class="field declaration">bar</span><span class="colon">:</span> <span class="builtin_type">bool</span><span class="comma">,</span> 49 <span class="field declaration">bar</span><span class="colon">:</span> <span class="builtin_type">bool</span><span class="comma">,</span>
45<span class="brace">}</span> 50<span class="brace">}</span>
46 51
52<span class="comment documentation">/// This is an impl with a code block.</span>
53<span class="comment documentation">///</span>
54<span class="comment documentation">/// ```</span>
55<span class="comment documentation">/// </span><span class="keyword injected">fn</span><span class="none injected"> </span><span class="function declaration injected">foo</span><span class="parenthesis injected">(</span><span class="parenthesis injected">)</span><span class="none injected"> </span><span class="brace injected">{</span>
56<span class="comment documentation">///</span>
57<span class="comment documentation">/// </span><span class="brace injected">}</span>
58<span class="comment documentation">/// ```</span>
47<span class="keyword">impl</span> <span class="struct">Foo</span> <span class="brace">{</span> 59<span class="keyword">impl</span> <span class="struct">Foo</span> <span class="brace">{</span>
48 <span class="comment documentation">/// ```</span> 60 <span class="comment documentation">/// ```</span>
49 <span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="none injected"> </span><span class="punctuation injected">_</span><span class="none injected"> </span><span class="operator injected">=</span><span class="none injected"> </span><span class="string_literal injected">"Call me</span> 61 <span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="none injected"> </span><span class="punctuation injected">_</span><span class="none injected"> </span><span class="operator injected">=</span><span class="none injected"> </span><span class="string_literal injected">"Call me</span>
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlighting.html b/crates/ide/src/syntax_highlighting/test_data/highlighting.html
index df4192194..0d325f3f3 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlighting.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlighting.html
@@ -66,11 +66,11 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
66 <span class="keyword">pub</span> <span class="field declaration">y</span><span class="colon">:</span> <span class="builtin_type">i32</span><span class="comma">,</span> 66 <span class="keyword">pub</span> <span class="field declaration">y</span><span class="colon">:</span> <span class="builtin_type">i32</span><span class="comma">,</span>
67<span class="brace">}</span> 67<span class="brace">}</span>
68 68
69<span class="keyword">trait</span> <span class="trait declaration">Bar</span> <span class="brace">{</span> 69<span class="keyword">trait</span> <span class="trait declaration">Bar</span> <span class="keyword">where</span> <span class="type_param">Self</span><span class="colon">:</span> <span class="brace">{</span>
70 <span class="keyword">fn</span> <span class="function associated declaration trait">bar</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration">self</span><span class="parenthesis">)</span> <span class="operator">-&gt;</span> <span class="builtin_type">i32</span><span class="semicolon">;</span> 70 <span class="keyword">fn</span> <span class="function associated declaration trait">bar</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration">self</span><span class="parenthesis">)</span> <span class="operator">-&gt;</span> <span class="builtin_type">i32</span><span class="semicolon">;</span>
71<span class="brace">}</span> 71<span class="brace">}</span>
72 72
73<span class="keyword">impl</span> <span class="trait">Bar</span> <span class="keyword">for</span> <span class="struct">Foo</span> <span class="brace">{</span> 73<span class="keyword">impl</span> <span class="trait">Bar</span> <span class="keyword">for</span> <span class="struct">Foo</span> <span class="keyword">where</span> <span class="self_type">Self</span><span class="colon">:</span> <span class="brace">{</span>
74 <span class="keyword">fn</span> <span class="function associated declaration trait">bar</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration">self</span><span class="parenthesis">)</span> <span class="operator">-&gt;</span> <span class="builtin_type">i32</span> <span class="brace">{</span> 74 <span class="keyword">fn</span> <span class="function associated declaration trait">bar</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration">self</span><span class="parenthesis">)</span> <span class="operator">-&gt;</span> <span class="builtin_type">i32</span> <span class="brace">{</span>
75 <span class="self_keyword">self</span><span class="operator">.</span><span class="field">x</span> 75 <span class="self_keyword">self</span><span class="operator">.</span><span class="field">x</span>
76 <span class="brace">}</span> 76 <span class="brace">}</span>
@@ -234,4 +234,15 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
234 <span class="variable declaration">Nope</span> <span class="operator">=&gt;</span> <span class="variable">Nope</span><span class="comma">,</span> 234 <span class="variable declaration">Nope</span> <span class="operator">=&gt;</span> <span class="variable">Nope</span><span class="comma">,</span>
235 <span class="brace">}</span> 235 <span class="brace">}</span>
236 <span class="brace">}</span> 236 <span class="brace">}</span>
237<span class="brace">}</span>
238
239<span class="keyword async">async</span> <span class="keyword">fn</span> <span class="function declaration async">learn_and_sing</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span>
240 <span class="keyword">let</span> <span class="variable declaration">song</span> <span class="operator">=</span> <span class="unresolved_reference">learn_song</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="operator">.</span><span class="keyword control async">await</span><span class="semicolon">;</span>
241 <span class="unresolved_reference">sing_song</span><span class="parenthesis">(</span><span class="variable consuming">song</span><span class="parenthesis">)</span><span class="operator">.</span><span class="keyword control async">await</span><span class="semicolon">;</span>
242<span class="brace">}</span>
243
244<span class="keyword async">async</span> <span class="keyword">fn</span> <span class="function declaration async">async_main</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span>
245 <span class="keyword">let</span> <span class="variable declaration">f1</span> <span class="operator">=</span> <span class="function async">learn_and_sing</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span>
246 <span class="keyword">let</span> <span class="variable declaration">f2</span> <span class="operator">=</span> <span class="unresolved_reference">dance</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span>
247 futures::<span class="macro">join!</span><span class="parenthesis">(</span>f1<span class="comma">,</span> f2<span class="parenthesis">)</span><span class="semicolon">;</span>
237<span class="brace">}</span></code></pre> \ No newline at end of file 248<span class="brace">}</span></code></pre> \ No newline at end of file
diff --git a/crates/ide/src/syntax_highlighting/tests.rs b/crates/ide/src/syntax_highlighting/tests.rs
index b6e952b08..8c8878d36 100644
--- a/crates/ide/src/syntax_highlighting/tests.rs
+++ b/crates/ide/src/syntax_highlighting/tests.rs
@@ -39,11 +39,11 @@ struct Foo {
39 pub y: i32, 39 pub y: i32,
40} 40}
41 41
42trait Bar { 42trait Bar where Self: {
43 fn bar(&self) -> i32; 43 fn bar(&self) -> i32;
44} 44}
45 45
46impl Bar for Foo { 46impl Bar for Foo where Self: {
47 fn bar(&self) -> i32 { 47 fn bar(&self) -> i32 {
48 self.x 48 self.x
49 } 49 }
@@ -208,6 +208,17 @@ impl<T> Option<T> {
208 } 208 }
209 } 209 }
210} 210}
211
212async fn learn_and_sing() {
213 let song = learn_song().await;
214 sing_song(song).await;
215}
216
217async fn async_main() {
218 let f1 = learn_and_sing();
219 let f2 = dance();
220 futures::join!(f1, f2);
221}
211"# 222"#
212 .trim(), 223 .trim(),
213 expect_file!["./test_data/highlighting.html"], 224 expect_file!["./test_data/highlighting.html"],
@@ -513,6 +524,11 @@ fn main() {
513fn test_highlight_doc_comment() { 524fn test_highlight_doc_comment() {
514 check_highlighting( 525 check_highlighting(
515 r#" 526 r#"
527//! This is a module to test doc injection.
528//! ```
529//! fn test() {}
530//! ```
531
516/// ``` 532/// ```
517/// let _ = "early doctests should not go boom"; 533/// let _ = "early doctests should not go boom";
518/// ``` 534/// ```
@@ -520,6 +536,13 @@ struct Foo {
520 bar: bool, 536 bar: bool,
521} 537}
522 538
539/// This is an impl with a code block.
540///
541/// ```
542/// fn foo() {
543///
544/// }
545/// ```
523impl Foo { 546impl Foo {
524 /// ``` 547 /// ```
525 /// let _ = "Call me 548 /// let _ = "Call me
diff --git a/crates/ide/src/typing.rs b/crates/ide/src/typing.rs
index 82c732390..4ad49eca0 100644
--- a/crates/ide/src/typing.rs
+++ b/crates/ide/src/typing.rs
@@ -88,7 +88,7 @@ fn on_char_typed_inner(
88} 88}
89 89
90/// Inserts a closing `}` when the user types an opening `{`, wrapping an existing expression in a 90/// Inserts a closing `}` when the user types an opening `{`, wrapping an existing expression in a
91/// block. 91/// block, or a part of a `use` item.
92fn on_opening_brace_typed(file: &Parse<SourceFile>, offset: TextSize) -> Option<TextEdit> { 92fn on_opening_brace_typed(file: &Parse<SourceFile>, offset: TextSize) -> Option<TextEdit> {
93 if !stdx::always!(file.tree().syntax().text().char_at(offset) == Some('{')) { 93 if !stdx::always!(file.tree().syntax().text().char_at(offset) == Some('{')) {
94 return None; 94 return None;
@@ -99,30 +99,59 @@ fn on_opening_brace_typed(file: &Parse<SourceFile>, offset: TextSize) -> Option<
99 // Remove the `{` to get a better parse tree, and reparse 99 // Remove the `{` to get a better parse tree, and reparse
100 let file = file.reparse(&Indel::delete(brace_token.text_range())); 100 let file = file.reparse(&Indel::delete(brace_token.text_range()));
101 101
102 let mut expr: ast::Expr = find_node_at_offset(file.tree().syntax(), offset)?; 102 if let Some(edit) = brace_expr(&file.tree(), offset) {
103 if expr.syntax().text_range().start() != offset { 103 return Some(edit);
104 return None;
105 } 104 }
106 105
107 // Enclose the outermost expression starting at `offset` 106 if let Some(edit) = brace_use_path(&file.tree(), offset) {
108 while let Some(parent) = expr.syntax().parent() { 107 return Some(edit);
109 if parent.text_range().start() != expr.syntax().text_range().start() { 108 }
110 break;
111 }
112 109
113 match ast::Expr::cast(parent) { 110 return None;
114 Some(parent) => expr = parent, 111
115 None => break, 112 fn brace_use_path(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
113 let segment: ast::PathSegment = find_node_at_offset(file.syntax(), offset)?;
114 if segment.syntax().text_range().start() != offset {
115 return None;
116 } 116 }
117 }
118 117
119 // If it's a statement in a block, we don't know how many statements should be included 118 let tree: ast::UseTree = find_node_at_offset(file.syntax(), offset)?;
120 if ast::ExprStmt::can_cast(expr.syntax().parent()?.kind()) { 119
121 return None; 120 Some(TextEdit::insert(
121 tree.syntax().text_range().end() + TextSize::of("{"),
122 "}".to_string(),
123 ))
122 } 124 }
123 125
124 // Insert `}` right after the expression. 126 fn brace_expr(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
125 Some(TextEdit::insert(expr.syntax().text_range().end() + TextSize::of("{"), "}".to_string())) 127 let mut expr: ast::Expr = find_node_at_offset(file.syntax(), offset)?;
128 if expr.syntax().text_range().start() != offset {
129 return None;
130 }
131
132 // Enclose the outermost expression starting at `offset`
133 while let Some(parent) = expr.syntax().parent() {
134 if parent.text_range().start() != expr.syntax().text_range().start() {
135 break;
136 }
137
138 match ast::Expr::cast(parent) {
139 Some(parent) => expr = parent,
140 None => break,
141 }
142 }
143
144 // If it's a statement in a block, we don't know how many statements should be included
145 if ast::ExprStmt::can_cast(expr.syntax().parent()?.kind()) {
146 return None;
147 }
148
149 // Insert `}` right after the expression.
150 Some(TextEdit::insert(
151 expr.syntax().text_range().end() + TextSize::of("{"),
152 "}".to_string(),
153 ))
154 }
126} 155}
127 156
128/// Returns an edit which should be applied after `=` was typed. Primarily, 157/// Returns an edit which should be applied after `=` was typed. Primarily,
@@ -440,7 +469,7 @@ fn foo() -> { 92 }
440 } 469 }
441 470
442 #[test] 471 #[test]
443 fn adds_closing_brace() { 472 fn adds_closing_brace_for_expr() {
444 type_char( 473 type_char(
445 '{', 474 '{',
446 r#" 475 r#"
@@ -519,4 +548,87 @@ fn f() {
519 "#, 548 "#,
520 ); 549 );
521 } 550 }
551
552 #[test]
553 fn adds_closing_brace_for_use_tree() {
554 type_char(
555 '{',
556 r#"
557use some::$0Path;
558 "#,
559 r#"
560use some::{Path};
561 "#,
562 );
563 type_char(
564 '{',
565 r#"
566use some::{Path, $0Other};
567 "#,
568 r#"
569use some::{Path, {Other}};
570 "#,
571 );
572 type_char(
573 '{',
574 r#"
575use some::{$0Path, Other};
576 "#,
577 r#"
578use some::{{Path}, Other};
579 "#,
580 );
581 type_char(
582 '{',
583 r#"
584use some::path::$0to::Item;
585 "#,
586 r#"
587use some::path::{to::Item};
588 "#,
589 );
590 type_char(
591 '{',
592 r#"
593use some::$0path::to::Item;
594 "#,
595 r#"
596use some::{path::to::Item};
597 "#,
598 );
599 type_char(
600 '{',
601 r#"
602use $0some::path::to::Item;
603 "#,
604 r#"
605use {some::path::to::Item};
606 "#,
607 );
608 type_char(
609 '{',
610 r#"
611use some::path::$0to::{Item};
612 "#,
613 r#"
614use some::path::{to::{Item}};
615 "#,
616 );
617 type_char(
618 '{',
619 r#"
620use $0Thing as _;
621 "#,
622 r#"
623use {Thing as _};
624 "#,
625 );
626
627 type_char_noop(
628 '{',
629 r#"
630use some::pa$0th::to::Item;
631 "#,
632 );
633 }
522} 634}
diff --git a/crates/ide/src/typing/on_enter.rs b/crates/ide/src/typing/on_enter.rs
index 7d2db201a..81c4d95b1 100644
--- a/crates/ide/src/typing/on_enter.rs
+++ b/crates/ide/src/typing/on_enter.rs
@@ -54,6 +54,14 @@ pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option<Text
54 cov_mark::hit!(indent_block_contents); 54 cov_mark::hit!(indent_block_contents);
55 return Some(edit); 55 return Some(edit);
56 } 56 }
57
58 // Typing enter after the `{` of a use tree list.
59 if let Some(edit) = find_node_at_offset(file.syntax(), position.offset - TextSize::of('{'))
60 .and_then(|list| on_enter_in_use_tree_list(list, position))
61 {
62 cov_mark::hit!(indent_block_contents);
63 return Some(edit);
64 }
57 } 65 }
58 66
59 None 67 None
@@ -111,6 +119,21 @@ fn on_enter_in_block(block: ast::BlockExpr, position: FilePosition) -> Option<Te
111 Some(edit) 119 Some(edit)
112} 120}
113 121
122fn on_enter_in_use_tree_list(list: ast::UseTreeList, position: FilePosition) -> Option<TextEdit> {
123 if list.syntax().text().contains_char('\n') {
124 return None;
125 }
126
127 let indent = IndentLevel::from_node(list.syntax());
128 let mut edit = TextEdit::insert(position.offset, format!("\n{}$0", indent + 1));
129 edit.union(TextEdit::insert(
130 list.r_curly_token()?.text_range().start(),
131 format!("\n{}", indent),
132 ))
133 .ok()?;
134 Some(edit)
135}
136
114fn block_contents(block: &ast::BlockExpr) -> Option<SyntaxNode> { 137fn block_contents(block: &ast::BlockExpr) -> Option<SyntaxNode> {
115 let mut node = block.tail_expr().map(|e| e.syntax().clone()); 138 let mut node = block.tail_expr().map(|e| e.syntax().clone());
116 139
@@ -484,4 +507,96 @@ fn f() {$0
484 "#, 507 "#,
485 ); 508 );
486 } 509 }
510
511 #[test]
512 fn indents_use_tree_list() {
513 do_check(
514 r#"
515use crate::{$0};
516 "#,
517 r#"
518use crate::{
519 $0
520};
521 "#,
522 );
523 do_check(
524 r#"
525use crate::{$0Object, path::to::OtherThing};
526 "#,
527 r#"
528use crate::{
529 $0Object, path::to::OtherThing
530};
531 "#,
532 );
533 do_check(
534 r#"
535use {crate::{$0Object, path::to::OtherThing}};
536 "#,
537 r#"
538use {crate::{
539 $0Object, path::to::OtherThing
540}};
541 "#,
542 );
543 do_check(
544 r#"
545use {
546 crate::{$0Object, path::to::OtherThing}
547};
548 "#,
549 r#"
550use {
551 crate::{
552 $0Object, path::to::OtherThing
553 }
554};
555 "#,
556 );
557 }
558
559 #[test]
560 fn does_not_indent_use_tree_list_when_not_at_curly_brace() {
561 do_check_noop(
562 r#"
563use path::{Thing$0};
564 "#,
565 );
566 }
567
568 #[test]
569 fn does_not_indent_use_tree_list_without_curly_braces() {
570 do_check_noop(
571 r#"
572use path::Thing$0;
573 "#,
574 );
575 do_check_noop(
576 r#"
577use path::$0Thing;
578 "#,
579 );
580 do_check_noop(
581 r#"
582use path::Thing$0};
583 "#,
584 );
585 do_check_noop(
586 r#"
587use path::{$0Thing;
588 "#,
589 );
590 }
591
592 #[test]
593 fn does_not_indent_multiline_use_tree_list() {
594 do_check_noop(
595 r#"
596use path::{$0
597 Thing
598};
599 "#,
600 );
601 }
487} 602}
diff --git a/crates/ide/src/view_crate_graph.rs b/crates/ide/src/view_crate_graph.rs
new file mode 100644
index 000000000..df6cc8aed
--- /dev/null
+++ b/crates/ide/src/view_crate_graph.rs
@@ -0,0 +1,90 @@
1use std::sync::Arc;
2
3use dot::{Id, LabelText};
4use ide_db::{
5 base_db::{CrateGraph, CrateId, Dependency, SourceDatabase, SourceDatabaseExt},
6 RootDatabase,
7};
8use rustc_hash::FxHashSet;
9
10// Feature: View Crate Graph
11//
12// Renders the currently loaded crate graph as an SVG graphic. Requires the `dot` tool, which
13// is part of graphviz, to be installed.
14//
15// Only workspace crates are included, no crates.io dependencies or sysroot crates.
16//
17// |===
18// | Editor | Action Name
19//
20// | VS Code | **Rust Analyzer: View Crate Graph**
21// |===
22pub(crate) fn view_crate_graph(db: &RootDatabase) -> Result<String, String> {
23 let crate_graph = db.crate_graph();
24 let crates_to_render = crate_graph
25 .iter()
26 .filter(|krate| {
27 // Only render workspace crates
28 let root_id = db.file_source_root(crate_graph[*krate].root_file_id);
29 !db.source_root(root_id).is_library
30 })
31 .collect();
32 let graph = DotCrateGraph { graph: crate_graph, crates_to_render };
33
34 let mut dot = Vec::new();
35 dot::render(&graph, &mut dot).unwrap();
36 Ok(String::from_utf8(dot).unwrap())
37}
38
39struct DotCrateGraph {
40 graph: Arc<CrateGraph>,
41 crates_to_render: FxHashSet<CrateId>,
42}
43
44type Edge<'a> = (CrateId, &'a Dependency);
45
46impl<'a> dot::GraphWalk<'a, CrateId, Edge<'a>> for DotCrateGraph {
47 fn nodes(&'a self) -> dot::Nodes<'a, CrateId> {
48 self.crates_to_render.iter().copied().collect()
49 }
50
51 fn edges(&'a self) -> dot::Edges<'a, Edge<'a>> {
52 self.crates_to_render
53 .iter()
54 .flat_map(|krate| {
55 self.graph[*krate]
56 .dependencies
57 .iter()
58 .filter(|dep| self.crates_to_render.contains(&dep.crate_id))
59 .map(move |dep| (*krate, dep))
60 })
61 .collect()
62 }
63
64 fn source(&'a self, edge: &Edge<'a>) -> CrateId {
65 edge.0
66 }
67
68 fn target(&'a self, edge: &Edge<'a>) -> CrateId {
69 edge.1.crate_id
70 }
71}
72
73impl<'a> dot::Labeller<'a, CrateId, Edge<'a>> for DotCrateGraph {
74 fn graph_id(&'a self) -> Id<'a> {
75 Id::new("rust_analyzer_crate_graph").unwrap()
76 }
77
78 fn node_id(&'a self, n: &CrateId) -> Id<'a> {
79 Id::new(format!("_{}", n.0)).unwrap()
80 }
81
82 fn node_shape(&'a self, _node: &CrateId) -> Option<LabelText<'a>> {
83 Some(LabelText::LabelStr("box".into()))
84 }
85
86 fn node_label(&'a self, n: &CrateId) -> LabelText<'a> {
87 let name = self.graph[*n].display_name.as_ref().map_or("(unnamed crate)", |name| &*name);
88 LabelText::LabelStr(name.into())
89 }
90}
diff --git a/crates/ide_assists/src/assist_context.rs b/crates/ide_assists/src/assist_context.rs
index 112939948..20754a02a 100644
--- a/crates/ide_assists/src/assist_context.rs
+++ b/crates/ide_assists/src/assist_context.rs
@@ -13,7 +13,7 @@ use ide_db::{
13 RootDatabase, 13 RootDatabase,
14}; 14};
15use syntax::{ 15use syntax::{
16 algo::{self, find_node_at_offset, find_node_at_range, SyntaxRewriter}, 16 algo::{self, find_node_at_offset, find_node_at_range},
17 AstNode, AstToken, SourceFile, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxNodePtr, 17 AstNode, AstToken, SourceFile, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxNodePtr,
18 SyntaxToken, TextRange, TextSize, TokenAtOffset, 18 SyntaxToken, TextRange, TextSize, TokenAtOffset,
19}; 19};
@@ -238,8 +238,8 @@ impl AssistBuilder {
238 } 238 }
239 } 239 }
240 240
241 pub(crate) fn make_ast_mut<N: AstNode>(&mut self, node: N) -> N { 241 pub(crate) fn make_mut<N: AstNode>(&mut self, node: N) -> N {
242 N::cast(self.make_mut(node.syntax().clone())).unwrap() 242 self.mutated_tree.get_or_insert_with(|| TreeMutator::new(node.syntax())).make_mut(&node)
243 } 243 }
244 /// Returns a copy of the `node`, suitable for mutation. 244 /// Returns a copy of the `node`, suitable for mutation.
245 /// 245 ///
@@ -251,7 +251,7 @@ impl AssistBuilder {
251 /// The typical pattern for an assist is to find specific nodes in the read 251 /// The typical pattern for an assist is to find specific nodes in the read
252 /// phase, and then get their mutable couterparts using `make_mut` in the 252 /// phase, and then get their mutable couterparts using `make_mut` in the
253 /// mutable state. 253 /// mutable state.
254 pub(crate) fn make_mut(&mut self, node: SyntaxNode) -> SyntaxNode { 254 pub(crate) fn make_syntax_mut(&mut self, node: SyntaxNode) -> SyntaxNode {
255 self.mutated_tree.get_or_insert_with(|| TreeMutator::new(&node)).make_syntax_mut(&node) 255 self.mutated_tree.get_or_insert_with(|| TreeMutator::new(&node)).make_syntax_mut(&node)
256 } 256 }
257 257
@@ -290,12 +290,6 @@ impl AssistBuilder {
290 pub(crate) fn replace_ast<N: AstNode>(&mut self, old: N, new: N) { 290 pub(crate) fn replace_ast<N: AstNode>(&mut self, old: N, new: N) {
291 algo::diff(old.syntax(), new.syntax()).into_text_edit(&mut self.edit) 291 algo::diff(old.syntax(), new.syntax()).into_text_edit(&mut self.edit)
292 } 292 }
293 pub(crate) fn rewrite(&mut self, rewriter: SyntaxRewriter) {
294 if let Some(node) = rewriter.rewrite_root() {
295 let new = rewriter.rewrite(&node);
296 algo::diff(&node, &new).into_text_edit(&mut self.edit);
297 }
298 }
299 pub(crate) fn create_file(&mut self, dst: AnchoredPathBuf, content: impl Into<String>) { 293 pub(crate) fn create_file(&mut self, dst: AnchoredPathBuf, content: impl Into<String>) {
300 let file_system_edit = 294 let file_system_edit =
301 FileSystemEdit::CreateFile { dst: dst, initial_contents: content.into() }; 295 FileSystemEdit::CreateFile { dst: dst, initial_contents: content.into() };
diff --git a/crates/ide_assists/src/handlers/add_explicit_type.rs b/crates/ide_assists/src/handlers/add_explicit_type.rs
index 62db31952..36589203d 100644
--- a/crates/ide_assists/src/handlers/add_explicit_type.rs
+++ b/crates/ide_assists/src/handlers/add_explicit_type.rs
@@ -198,6 +198,34 @@ fn main() {
198 ) 198 )
199 } 199 }
200 200
201 /// https://github.com/rust-analyzer/rust-analyzer/issues/2922
202 #[test]
203 fn regression_issue_2922() {
204 check_assist(
205 add_explicit_type,
206 r#"
207fn main() {
208 let $0v = [0.0; 2];
209}
210"#,
211 r#"
212fn main() {
213 let v: [f64; 2] = [0.0; 2];
214}
215"#,
216 );
217 // note: this may break later if we add more consteval. it just needs to be something that our
218 // consteval engine doesn't understand
219 check_assist_not_applicable(
220 add_explicit_type,
221 r#"
222fn main() {
223 let $0l = [0.0; 2+2];
224}
225"#,
226 );
227 }
228
201 #[test] 229 #[test]
202 fn default_generics_should_not_be_added() { 230 fn default_generics_should_not_be_added() {
203 check_assist( 231 check_assist(
diff --git a/crates/ide_assists/src/handlers/add_missing_impl_members.rs b/crates/ide_assists/src/handlers/add_missing_impl_members.rs
index 0148635f9..8225ae22c 100644
--- a/crates/ide_assists/src/handlers/add_missing_impl_members.rs
+++ b/crates/ide_assists/src/handlers/add_missing_impl_members.rs
@@ -64,7 +64,6 @@ pub(crate) fn add_missing_impl_members(acc: &mut Assists, ctx: &AssistContext) -
64// impl Trait for () { 64// impl Trait for () {
65// type X = (); 65// type X = ();
66// fn foo(&self) {}$0 66// fn foo(&self) {}$0
67//
68// } 67// }
69// ``` 68// ```
70// -> 69// ->
@@ -195,6 +194,7 @@ impl Foo for S {
195 fn baz(&self) { 194 fn baz(&self) {
196 todo!() 195 todo!()
197 } 196 }
197
198}"#, 198}"#,
199 ); 199 );
200 } 200 }
@@ -231,6 +231,7 @@ impl Foo for S {
231 fn foo(&self) { 231 fn foo(&self) {
232 ${0:todo!()} 232 ${0:todo!()}
233 } 233 }
234
234}"#, 235}"#,
235 ); 236 );
236 } 237 }
diff --git a/crates/ide_assists/src/handlers/auto_import.rs b/crates/ide_assists/src/handlers/auto_import.rs
index a454a2af3..dda5a6631 100644
--- a/crates/ide_assists/src/handlers/auto_import.rs
+++ b/crates/ide_assists/src/handlers/auto_import.rs
@@ -102,8 +102,8 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
102 range, 102 range,
103 |builder| { 103 |builder| {
104 let scope = match scope.clone() { 104 let scope = match scope.clone() {
105 ImportScope::File(it) => ImportScope::File(builder.make_ast_mut(it)), 105 ImportScope::File(it) => ImportScope::File(builder.make_mut(it)),
106 ImportScope::Module(it) => ImportScope::Module(builder.make_ast_mut(it)), 106 ImportScope::Module(it) => ImportScope::Module(builder.make_mut(it)),
107 }; 107 };
108 insert_use(&scope, mod_path_to_ast(&import.import_path), ctx.config.insert_use); 108 insert_use(&scope, mod_path_to_ast(&import.import_path), ctx.config.insert_use);
109 }, 109 },
@@ -969,4 +969,30 @@ mod bar {
969"#, 969"#,
970 ); 970 );
971 } 971 }
972
973 #[test]
974 fn uses_abs_path_with_extern_crate_clash() {
975 check_assist(
976 auto_import,
977 r#"
978//- /main.rs crate:main deps:foo
979mod foo {}
980
981const _: () = {
982 Foo$0
983};
984//- /foo.rs crate:foo
985pub struct Foo
986"#,
987 r#"
988use ::foo::Foo;
989
990mod foo {}
991
992const _: () = {
993 Foo
994};
995"#,
996 );
997 }
972} 998}
diff --git a/crates/ide_assists/src/handlers/convert_tuple_struct_to_named_struct.rs b/crates/ide_assists/src/handlers/convert_tuple_struct_to_named_struct.rs
index b5b5ada5e..70949ca35 100644
--- a/crates/ide_assists/src/handlers/convert_tuple_struct_to_named_struct.rs
+++ b/crates/ide_assists/src/handlers/convert_tuple_struct_to_named_struct.rs
@@ -107,7 +107,7 @@ fn edit_struct_references(
107 names: &[ast::Name], 107 names: &[ast::Name],
108) { 108) {
109 let strukt_def = Definition::ModuleDef(hir::ModuleDef::Adt(hir::Adt::Struct(strukt))); 109 let strukt_def = Definition::ModuleDef(hir::ModuleDef::Adt(hir::Adt::Struct(strukt)));
110 let usages = strukt_def.usages(&ctx.sema).include_self_kw_refs(true).all(); 110 let usages = strukt_def.usages(&ctx.sema).include_self_refs().all();
111 111
112 let edit_node = |edit: &mut AssistBuilder, node: SyntaxNode| -> Option<()> { 112 let edit_node = |edit: &mut AssistBuilder, node: SyntaxNode| -> Option<()> {
113 match_ast! { 113 match_ast! {
diff --git a/crates/ide_assists/src/handlers/early_return.rs b/crates/ide_assists/src/handlers/early_return.rs
index c66f8c05d..5eb6a57f0 100644
--- a/crates/ide_assists/src/handlers/early_return.rs
+++ b/crates/ide_assists/src/handlers/early_return.rs
@@ -130,9 +130,7 @@ pub(crate) fn convert_to_guarded_return(acc: &mut Assists, ctx: &AssistContext)
130 once(make::ident_pat(make::name("it")).into()), 130 once(make::ident_pat(make::name("it")).into()),
131 ); 131 );
132 let expr = { 132 let expr = {
133 let name_ref = make::name_ref("it"); 133 let path = make::ext::ident_path("it");
134 let segment = make::path_segment(name_ref);
135 let path = make::path_unqualified(segment);
136 make::expr_path(path) 134 make::expr_path(path)
137 }; 135 };
138 make::match_arm(once(pat.into()), expr) 136 make::match_arm(once(pat.into()), expr)
diff --git a/crates/ide_assists/src/handlers/expand_glob_import.rs b/crates/ide_assists/src/handlers/expand_glob_import.rs
index 98389e4f7..79cb08d69 100644
--- a/crates/ide_assists/src/handlers/expand_glob_import.rs
+++ b/crates/ide_assists/src/handlers/expand_glob_import.rs
@@ -4,10 +4,10 @@ use ide_db::{
4 defs::{Definition, NameRefClass}, 4 defs::{Definition, NameRefClass},
5 search::SearchScope, 5 search::SearchScope,
6}; 6};
7use stdx::never;
7use syntax::{ 8use syntax::{
8 algo::SyntaxRewriter,
9 ast::{self, make}, 9 ast::{self, make},
10 AstNode, Direction, SyntaxNode, SyntaxToken, T, 10 ted, AstNode, Direction, SyntaxNode, SyntaxToken, T,
11}; 11};
12 12
13use crate::{ 13use crate::{
@@ -42,6 +42,7 @@ use crate::{
42// ``` 42// ```
43pub(crate) fn expand_glob_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 43pub(crate) fn expand_glob_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
44 let star = ctx.find_token_syntax_at_offset(T![*])?; 44 let star = ctx.find_token_syntax_at_offset(T![*])?;
45 let use_tree = star.parent().and_then(ast::UseTree::cast)?;
45 let (parent, mod_path) = find_parent_and_path(&star)?; 46 let (parent, mod_path) = find_parent_and_path(&star)?;
46 let target_module = match ctx.sema.resolve_path(&mod_path)? { 47 let target_module = match ctx.sema.resolve_path(&mod_path)? {
47 PathResolution::Def(ModuleDef::Module(it)) => it, 48 PathResolution::Def(ModuleDef::Module(it)) => it,
@@ -53,7 +54,6 @@ pub(crate) fn expand_glob_import(acc: &mut Assists, ctx: &AssistContext) -> Opti
53 54
54 let refs_in_target = find_refs_in_mod(ctx, target_module, Some(current_module))?; 55 let refs_in_target = find_refs_in_mod(ctx, target_module, Some(current_module))?;
55 let imported_defs = find_imported_defs(ctx, star)?; 56 let imported_defs = find_imported_defs(ctx, star)?;
56 let names_to_import = find_names_to_import(ctx, refs_in_target, imported_defs);
57 57
58 let target = parent.clone().either(|n| n.syntax().clone(), |n| n.syntax().clone()); 58 let target = parent.clone().either(|n| n.syntax().clone(), |n| n.syntax().clone());
59 acc.add( 59 acc.add(
@@ -61,9 +61,31 @@ pub(crate) fn expand_glob_import(acc: &mut Assists, ctx: &AssistContext) -> Opti
61 "Expand glob import", 61 "Expand glob import",
62 target.text_range(), 62 target.text_range(),
63 |builder| { 63 |builder| {
64 let mut rewriter = SyntaxRewriter::default(); 64 let use_tree = builder.make_mut(use_tree);
65 replace_ast(&mut rewriter, parent, mod_path, names_to_import); 65
66 builder.rewrite(rewriter); 66 let names_to_import = find_names_to_import(ctx, refs_in_target, imported_defs);
67 let expanded = make::use_tree_list(names_to_import.iter().map(|n| {
68 let path = make::ext::ident_path(&n.to_string());
69 make::use_tree(path, None, None, false)
70 }))
71 .clone_for_update();
72
73 match use_tree.star_token() {
74 Some(star) => {
75 let needs_braces = use_tree.path().is_some() && names_to_import.len() > 1;
76 if needs_braces {
77 ted::replace(star, expanded.syntax())
78 } else {
79 let without_braces = expanded
80 .syntax()
81 .children_with_tokens()
82 .filter(|child| !matches!(child.kind(), T!['{'] | T!['}']))
83 .collect();
84 ted::replace_with_many(star, without_braces)
85 }
86 }
87 None => never!(),
88 }
67 }, 89 },
68 ) 90 )
69} 91}
@@ -232,53 +254,6 @@ fn find_names_to_import(
232 used_refs.0.iter().map(|r| r.visible_name.clone()).collect() 254 used_refs.0.iter().map(|r| r.visible_name.clone()).collect()
233} 255}
234 256
235fn replace_ast(
236 rewriter: &mut SyntaxRewriter,
237 parent: Either<ast::UseTree, ast::UseTreeList>,
238 path: ast::Path,
239 names_to_import: Vec<Name>,
240) {
241 let existing_use_trees = match parent.clone() {
242 Either::Left(_) => vec![],
243 Either::Right(u) => u
244 .use_trees()
245 .filter(|n|
246 // filter out star
247 n.star_token().is_none())
248 .collect(),
249 };
250
251 let new_use_trees: Vec<ast::UseTree> = names_to_import
252 .iter()
253 .map(|n| {
254 let path = make::path_unqualified(make::path_segment(make::name_ref(&n.to_string())));
255 make::use_tree(path, None, None, false)
256 })
257 .collect();
258
259 let use_trees = [&existing_use_trees[..], &new_use_trees[..]].concat();
260
261 match use_trees.as_slice() {
262 [name] => {
263 if let Some(end_path) = name.path() {
264 rewriter.replace_ast(
265 &parent.left_or_else(|tl| tl.parent_use_tree()),
266 &make::use_tree(make::path_concat(path, end_path), None, None, false),
267 );
268 }
269 }
270 names => match &parent {
271 Either::Left(parent) => rewriter.replace_ast(
272 parent,
273 &make::use_tree(path, Some(make::use_tree_list(names.to_owned())), None, false),
274 ),
275 Either::Right(parent) => {
276 rewriter.replace_ast(parent, &make::use_tree_list(names.to_owned()))
277 }
278 },
279 };
280}
281
282#[cfg(test)] 257#[cfg(test)]
283mod tests { 258mod tests {
284 use crate::tests::{check_assist, check_assist_not_applicable}; 259 use crate::tests::{check_assist, check_assist_not_applicable};
@@ -350,7 +325,7 @@ mod foo {
350 pub fn f() {} 325 pub fn f() {}
351} 326}
352 327
353use foo::{f, Baz, Bar}; 328use foo::{Baz, Bar, f};
354 329
355fn qux(bar: Bar, baz: Baz) { 330fn qux(bar: Bar, baz: Baz) {
356 f(); 331 f();
@@ -389,7 +364,7 @@ mod foo {
389} 364}
390 365
391use foo::Bar; 366use foo::Bar;
392use foo::{f, Baz}; 367use foo::{Baz, f};
393 368
394fn qux(bar: Bar, baz: Baz) { 369fn qux(bar: Bar, baz: Baz) {
395 f(); 370 f();
@@ -439,7 +414,7 @@ mod foo {
439 } 414 }
440} 415}
441 416
442use foo::{bar::{f, Baz, Bar}, baz::*}; 417use foo::{bar::{Baz, Bar, f}, baz::*};
443 418
444fn qux(bar: Bar, baz: Baz) { 419fn qux(bar: Bar, baz: Baz) {
445 f(); 420 f();
@@ -891,7 +866,7 @@ mod foo {
891 pub struct Bar; 866 pub struct Bar;
892} 867}
893 868
894use foo::Bar; 869use foo::{Bar};
895 870
896struct Baz { 871struct Baz {
897 bar: Bar 872 bar: Bar
diff --git a/crates/ide_assists/src/handlers/extract_function.rs b/crates/ide_assists/src/handlers/extract_function.rs
index 93b28370c..6311afc1f 100644
--- a/crates/ide_assists/src/handlers/extract_function.rs
+++ b/crates/ide_assists/src/handlers/extract_function.rs
@@ -10,7 +10,6 @@ use ide_db::{
10use itertools::Itertools; 10use itertools::Itertools;
11use stdx::format_to; 11use stdx::format_to;
12use syntax::{ 12use syntax::{
13 algo::SyntaxRewriter,
14 ast::{ 13 ast::{
15 self, 14 self,
16 edit::{AstNodeEdit, IndentLevel}, 15 edit::{AstNodeEdit, IndentLevel},
@@ -957,10 +956,10 @@ fn format_replacement(ctx: &AssistContext, fun: &Function, indent: IndentLevel)
957 let args = fun.params.iter().map(|param| param.to_arg(ctx)); 956 let args = fun.params.iter().map(|param| param.to_arg(ctx));
958 let args = make::arg_list(args); 957 let args = make::arg_list(args);
959 let call_expr = if fun.self_param.is_some() { 958 let call_expr = if fun.self_param.is_some() {
960 let self_arg = make::expr_path(make_path_from_text("self")); 959 let self_arg = make::expr_path(make::ext::ident_path("self"));
961 make::expr_method_call(self_arg, &fun.name, args) 960 make::expr_method_call(self_arg, &fun.name, args)
962 } else { 961 } else {
963 let func = make::expr_path(make_path_from_text(&fun.name)); 962 let func = make::expr_path(make::ext::ident_path(&fun.name));
964 make::expr_call(func, args) 963 make::expr_call(func, args)
965 }; 964 };
966 965
@@ -1055,11 +1054,11 @@ impl FlowHandler {
1055 make::expr_if(condition, block, None) 1054 make::expr_if(condition, block, None)
1056 } 1055 }
1057 FlowHandler::IfOption { action } => { 1056 FlowHandler::IfOption { action } => {
1058 let path = make_path_from_text("Some"); 1057 let path = make::ext::ident_path("Some");
1059 let value_pat = make::ident_pat(make::name("value")); 1058 let value_pat = make::ident_pat(make::name("value"));
1060 let pattern = make::tuple_struct_pat(path, iter::once(value_pat.into())); 1059 let pattern = make::tuple_struct_pat(path, iter::once(value_pat.into()));
1061 let cond = make::condition(call_expr, Some(pattern.into())); 1060 let cond = make::condition(call_expr, Some(pattern.into()));
1062 let value = make::expr_path(make_path_from_text("value")); 1061 let value = make::expr_path(make::ext::ident_path("value"));
1063 let action_expr = action.make_result_handler(Some(value)); 1062 let action_expr = action.make_result_handler(Some(value));
1064 let action_stmt = make::expr_stmt(action_expr); 1063 let action_stmt = make::expr_stmt(action_expr);
1065 let then = make::block_expr(iter::once(action_stmt.into()), None); 1064 let then = make::block_expr(iter::once(action_stmt.into()), None);
@@ -1069,14 +1068,14 @@ impl FlowHandler {
1069 let some_name = "value"; 1068 let some_name = "value";
1070 1069
1071 let some_arm = { 1070 let some_arm = {
1072 let path = make_path_from_text("Some"); 1071 let path = make::ext::ident_path("Some");
1073 let value_pat = make::ident_pat(make::name(some_name)); 1072 let value_pat = make::ident_pat(make::name(some_name));
1074 let pat = make::tuple_struct_pat(path, iter::once(value_pat.into())); 1073 let pat = make::tuple_struct_pat(path, iter::once(value_pat.into()));
1075 let value = make::expr_path(make_path_from_text(some_name)); 1074 let value = make::expr_path(make::ext::ident_path(some_name));
1076 make::match_arm(iter::once(pat.into()), value) 1075 make::match_arm(iter::once(pat.into()), value)
1077 }; 1076 };
1078 let none_arm = { 1077 let none_arm = {
1079 let path = make_path_from_text("None"); 1078 let path = make::ext::ident_path("None");
1080 let pat = make::path_pat(path); 1079 let pat = make::path_pat(path);
1081 make::match_arm(iter::once(pat), none.make_result_handler(None)) 1080 make::match_arm(iter::once(pat), none.make_result_handler(None))
1082 }; 1081 };
@@ -1088,17 +1087,17 @@ impl FlowHandler {
1088 let err_name = "value"; 1087 let err_name = "value";
1089 1088
1090 let ok_arm = { 1089 let ok_arm = {
1091 let path = make_path_from_text("Ok"); 1090 let path = make::ext::ident_path("Ok");
1092 let value_pat = make::ident_pat(make::name(ok_name)); 1091 let value_pat = make::ident_pat(make::name(ok_name));
1093 let pat = make::tuple_struct_pat(path, iter::once(value_pat.into())); 1092 let pat = make::tuple_struct_pat(path, iter::once(value_pat.into()));
1094 let value = make::expr_path(make_path_from_text(ok_name)); 1093 let value = make::expr_path(make::ext::ident_path(ok_name));
1095 make::match_arm(iter::once(pat.into()), value) 1094 make::match_arm(iter::once(pat.into()), value)
1096 }; 1095 };
1097 let err_arm = { 1096 let err_arm = {
1098 let path = make_path_from_text("Err"); 1097 let path = make::ext::ident_path("Err");
1099 let value_pat = make::ident_pat(make::name(err_name)); 1098 let value_pat = make::ident_pat(make::name(err_name));
1100 let pat = make::tuple_struct_pat(path, iter::once(value_pat.into())); 1099 let pat = make::tuple_struct_pat(path, iter::once(value_pat.into()));
1101 let value = make::expr_path(make_path_from_text(err_name)); 1100 let value = make::expr_path(make::ext::ident_path(err_name));
1102 make::match_arm(iter::once(pat.into()), err.make_result_handler(Some(value))) 1101 make::match_arm(iter::once(pat.into()), err.make_result_handler(Some(value)))
1103 }; 1102 };
1104 let arms = make::match_arm_list(vec![ok_arm, err_arm]); 1103 let arms = make::match_arm_list(vec![ok_arm, err_arm]);
@@ -1108,13 +1107,9 @@ impl FlowHandler {
1108 } 1107 }
1109} 1108}
1110 1109
1111fn make_path_from_text(text: &str) -> ast::Path {
1112 make::path_unqualified(make::path_segment(make::name_ref(text)))
1113}
1114
1115fn path_expr_from_local(ctx: &AssistContext, var: Local) -> ast::Expr { 1110fn path_expr_from_local(ctx: &AssistContext, var: Local) -> ast::Expr {
1116 let name = var.name(ctx.db()).unwrap().to_string(); 1111 let name = var.name(ctx.db()).unwrap().to_string();
1117 make::expr_path(make_path_from_text(&name)) 1112 make::expr_path(make::ext::ident_path(&name))
1118} 1113}
1119 1114
1120fn format_function( 1115fn format_function(
@@ -1180,7 +1175,7 @@ fn make_ret_ty(ctx: &AssistContext, module: hir::Module, fun: &Function) -> Opti
1180 fun_ty.make_ty(ctx, module) 1175 fun_ty.make_ty(ctx, module)
1181 } 1176 }
1182 FlowHandler::Try { kind: TryKind::Option } => { 1177 FlowHandler::Try { kind: TryKind::Option } => {
1183 make::ty_generic(make::name_ref("Option"), iter::once(fun_ty.make_ty(ctx, module))) 1178 make::ext::ty_option(fun_ty.make_ty(ctx, module))
1184 } 1179 }
1185 FlowHandler::Try { kind: TryKind::Result { ty: parent_ret_ty } } => { 1180 FlowHandler::Try { kind: TryKind::Result { ty: parent_ret_ty } } => {
1186 let handler_ty = parent_ret_ty 1181 let handler_ty = parent_ret_ty
@@ -1188,29 +1183,21 @@ fn make_ret_ty(ctx: &AssistContext, module: hir::Module, fun: &Function) -> Opti
1188 .nth(1) 1183 .nth(1)
1189 .map(|ty| make_ty(&ty, ctx, module)) 1184 .map(|ty| make_ty(&ty, ctx, module))
1190 .unwrap_or_else(make::ty_unit); 1185 .unwrap_or_else(make::ty_unit);
1191 make::ty_generic( 1186 make::ext::ty_result(fun_ty.make_ty(ctx, module), handler_ty)
1192 make::name_ref("Result"),
1193 vec![fun_ty.make_ty(ctx, module), handler_ty],
1194 )
1195 } 1187 }
1196 FlowHandler::If { .. } => make::ty("bool"), 1188 FlowHandler::If { .. } => make::ext::ty_bool(),
1197 FlowHandler::IfOption { action } => { 1189 FlowHandler::IfOption { action } => {
1198 let handler_ty = action 1190 let handler_ty = action
1199 .expr_ty(ctx) 1191 .expr_ty(ctx)
1200 .map(|ty| make_ty(&ty, ctx, module)) 1192 .map(|ty| make_ty(&ty, ctx, module))
1201 .unwrap_or_else(make::ty_unit); 1193 .unwrap_or_else(make::ty_unit);
1202 make::ty_generic(make::name_ref("Option"), iter::once(handler_ty)) 1194 make::ext::ty_option(handler_ty)
1203 }
1204 FlowHandler::MatchOption { .. } => {
1205 make::ty_generic(make::name_ref("Option"), iter::once(fun_ty.make_ty(ctx, module)))
1206 } 1195 }
1196 FlowHandler::MatchOption { .. } => make::ext::ty_option(fun_ty.make_ty(ctx, module)),
1207 FlowHandler::MatchResult { err } => { 1197 FlowHandler::MatchResult { err } => {
1208 let handler_ty = 1198 let handler_ty =
1209 err.expr_ty(ctx).map(|ty| make_ty(&ty, ctx, module)).unwrap_or_else(make::ty_unit); 1199 err.expr_ty(ctx).map(|ty| make_ty(&ty, ctx, module)).unwrap_or_else(make::ty_unit);
1210 make::ty_generic( 1200 make::ext::ty_result(fun_ty.make_ty(ctx, module), handler_ty)
1211 make::name_ref("Result"),
1212 vec![fun_ty.make_ty(ctx, module), handler_ty],
1213 )
1214 } 1201 }
1215 }; 1202 };
1216 Some(make::ret_type(ret_ty)) 1203 Some(make::ret_type(ret_ty))
@@ -1297,7 +1284,7 @@ fn make_body(
1297 TryKind::Option => "Some", 1284 TryKind::Option => "Some",
1298 TryKind::Result { .. } => "Ok", 1285 TryKind::Result { .. } => "Ok",
1299 }; 1286 };
1300 let func = make::expr_path(make_path_from_text(constructor)); 1287 let func = make::expr_path(make::ext::ident_path(constructor));
1301 let args = make::arg_list(iter::once(tail_expr)); 1288 let args = make::arg_list(iter::once(tail_expr));
1302 make::expr_call(func, args) 1289 make::expr_call(func, args)
1303 }) 1290 })
@@ -1307,16 +1294,16 @@ fn make_body(
1307 with_tail_expr(block, lit_false.into()) 1294 with_tail_expr(block, lit_false.into())
1308 } 1295 }
1309 FlowHandler::IfOption { .. } => { 1296 FlowHandler::IfOption { .. } => {
1310 let none = make::expr_path(make_path_from_text("None")); 1297 let none = make::expr_path(make::ext::ident_path("None"));
1311 with_tail_expr(block, none) 1298 with_tail_expr(block, none)
1312 } 1299 }
1313 FlowHandler::MatchOption { .. } => map_tail_expr(block, |tail_expr| { 1300 FlowHandler::MatchOption { .. } => map_tail_expr(block, |tail_expr| {
1314 let some = make::expr_path(make_path_from_text("Some")); 1301 let some = make::expr_path(make::ext::ident_path("Some"));
1315 let args = make::arg_list(iter::once(tail_expr)); 1302 let args = make::arg_list(iter::once(tail_expr));
1316 make::expr_call(some, args) 1303 make::expr_call(some, args)
1317 }), 1304 }),
1318 FlowHandler::MatchResult { .. } => map_tail_expr(block, |tail_expr| { 1305 FlowHandler::MatchResult { .. } => map_tail_expr(block, |tail_expr| {
1319 let ok = make::expr_path(make_path_from_text("Ok")); 1306 let ok = make::expr_path(make::ext::ident_path("Ok"));
1320 let args = make::arg_list(iter::once(tail_expr)); 1307 let args = make::arg_list(iter::once(tail_expr));
1321 make::expr_call(ok, args) 1308 make::expr_call(ok, args)
1322 }), 1309 }),
@@ -1362,7 +1349,8 @@ fn rewrite_body_segment(
1362 syntax: &SyntaxNode, 1349 syntax: &SyntaxNode,
1363) -> SyntaxNode { 1350) -> SyntaxNode {
1364 let syntax = fix_param_usages(ctx, params, syntax); 1351 let syntax = fix_param_usages(ctx, params, syntax);
1365 update_external_control_flow(handler, &syntax) 1352 update_external_control_flow(handler, &syntax);
1353 syntax
1366} 1354}
1367 1355
1368/// change all usages to account for added `&`/`&mut` for some params 1356/// change all usages to account for added `&`/`&mut` for some params
@@ -1415,75 +1403,65 @@ fn fix_param_usages(ctx: &AssistContext, params: &[Param], syntax: &SyntaxNode)
1415 res 1403 res
1416} 1404}
1417 1405
1418fn update_external_control_flow(handler: &FlowHandler, syntax: &SyntaxNode) -> SyntaxNode { 1406fn update_external_control_flow(handler: &FlowHandler, syntax: &SyntaxNode) {
1419 let mut rewriter = SyntaxRewriter::default();
1420
1421 let mut nested_loop = None; 1407 let mut nested_loop = None;
1422 let mut nested_scope = None; 1408 let mut nested_scope = None;
1423 for event in syntax.preorder() { 1409 for event in syntax.preorder() {
1424 let node = match event { 1410 match event {
1425 WalkEvent::Enter(e) => { 1411 WalkEvent::Enter(e) => match e.kind() {
1426 match e.kind() { 1412 SyntaxKind::LOOP_EXPR | SyntaxKind::WHILE_EXPR | SyntaxKind::FOR_EXPR => {
1427 SyntaxKind::LOOP_EXPR | SyntaxKind::WHILE_EXPR | SyntaxKind::FOR_EXPR => { 1413 if nested_loop.is_none() {
1428 if nested_loop.is_none() { 1414 nested_loop = Some(e.clone());
1429 nested_loop = Some(e.clone());
1430 }
1431 } 1415 }
1432 SyntaxKind::FN 1416 }
1433 | SyntaxKind::CONST 1417 SyntaxKind::FN
1434 | SyntaxKind::STATIC 1418 | SyntaxKind::CONST
1435 | SyntaxKind::IMPL 1419 | SyntaxKind::STATIC
1436 | SyntaxKind::MODULE => { 1420 | SyntaxKind::IMPL
1437 if nested_scope.is_none() { 1421 | SyntaxKind::MODULE => {
1438 nested_scope = Some(e.clone()); 1422 if nested_scope.is_none() {
1439 } 1423 nested_scope = Some(e.clone());
1440 } 1424 }
1441 _ => {}
1442 } 1425 }
1443 e 1426 _ => {}
1444 } 1427 },
1445 WalkEvent::Leave(e) => { 1428 WalkEvent::Leave(e) => {
1429 if nested_scope.is_none() {
1430 if let Some(expr) = ast::Expr::cast(e.clone()) {
1431 match expr {
1432 ast::Expr::ReturnExpr(return_expr) if nested_scope.is_none() => {
1433 let expr = return_expr.expr();
1434 if let Some(replacement) = make_rewritten_flow(handler, expr) {
1435 ted::replace(return_expr.syntax(), replacement.syntax())
1436 }
1437 }
1438 ast::Expr::BreakExpr(break_expr) if nested_loop.is_none() => {
1439 let expr = break_expr.expr();
1440 if let Some(replacement) = make_rewritten_flow(handler, expr) {
1441 ted::replace(break_expr.syntax(), replacement.syntax())
1442 }
1443 }
1444 ast::Expr::ContinueExpr(continue_expr) if nested_loop.is_none() => {
1445 if let Some(replacement) = make_rewritten_flow(handler, None) {
1446 ted::replace(continue_expr.syntax(), replacement.syntax())
1447 }
1448 }
1449 _ => {
1450 // do nothing
1451 }
1452 }
1453 }
1454 }
1455
1446 if nested_loop.as_ref() == Some(&e) { 1456 if nested_loop.as_ref() == Some(&e) {
1447 nested_loop = None; 1457 nested_loop = None;
1448 } 1458 }
1449 if nested_scope.as_ref() == Some(&e) { 1459 if nested_scope.as_ref() == Some(&e) {
1450 nested_scope = None; 1460 nested_scope = None;
1451 } 1461 }
1452 continue;
1453 } 1462 }
1454 }; 1463 };
1455 if nested_scope.is_some() {
1456 continue;
1457 }
1458 let expr = match ast::Expr::cast(node) {
1459 Some(e) => e,
1460 None => continue,
1461 };
1462 match expr {
1463 ast::Expr::ReturnExpr(return_expr) if nested_scope.is_none() => {
1464 let expr = return_expr.expr();
1465 if let Some(replacement) = make_rewritten_flow(handler, expr) {
1466 rewriter.replace_ast(&return_expr.into(), &replacement);
1467 }
1468 }
1469 ast::Expr::BreakExpr(break_expr) if nested_loop.is_none() => {
1470 let expr = break_expr.expr();
1471 if let Some(replacement) = make_rewritten_flow(handler, expr) {
1472 rewriter.replace_ast(&break_expr.into(), &replacement);
1473 }
1474 }
1475 ast::Expr::ContinueExpr(continue_expr) if nested_loop.is_none() => {
1476 if let Some(replacement) = make_rewritten_flow(handler, None) {
1477 rewriter.replace_ast(&continue_expr.into(), &replacement);
1478 }
1479 }
1480 _ => {
1481 // do nothing
1482 }
1483 }
1484 } 1464 }
1485
1486 rewriter.rewrite(syntax)
1487} 1465}
1488 1466
1489fn make_rewritten_flow(handler: &FlowHandler, arg_expr: Option<ast::Expr>) -> Option<ast::Expr> { 1467fn make_rewritten_flow(handler: &FlowHandler, arg_expr: Option<ast::Expr>) -> Option<ast::Expr> {
@@ -1493,16 +1471,16 @@ fn make_rewritten_flow(handler: &FlowHandler, arg_expr: Option<ast::Expr>) -> Op
1493 FlowHandler::IfOption { .. } => { 1471 FlowHandler::IfOption { .. } => {
1494 let expr = arg_expr.unwrap_or_else(|| make::expr_tuple(Vec::new())); 1472 let expr = arg_expr.unwrap_or_else(|| make::expr_tuple(Vec::new()));
1495 let args = make::arg_list(iter::once(expr)); 1473 let args = make::arg_list(iter::once(expr));
1496 make::expr_call(make::expr_path(make_path_from_text("Some")), args) 1474 make::expr_call(make::expr_path(make::ext::ident_path("Some")), args)
1497 } 1475 }
1498 FlowHandler::MatchOption { .. } => make::expr_path(make_path_from_text("None")), 1476 FlowHandler::MatchOption { .. } => make::expr_path(make::ext::ident_path("None")),
1499 FlowHandler::MatchResult { .. } => { 1477 FlowHandler::MatchResult { .. } => {
1500 let expr = arg_expr.unwrap_or_else(|| make::expr_tuple(Vec::new())); 1478 let expr = arg_expr.unwrap_or_else(|| make::expr_tuple(Vec::new()));
1501 let args = make::arg_list(iter::once(expr)); 1479 let args = make::arg_list(iter::once(expr));
1502 make::expr_call(make::expr_path(make_path_from_text("Err")), args) 1480 make::expr_call(make::expr_path(make::ext::ident_path("Err")), args)
1503 } 1481 }
1504 }; 1482 };
1505 Some(make::expr_return(Some(value))) 1483 Some(make::expr_return(Some(value)).clone_for_update())
1506} 1484}
1507 1485
1508#[cfg(test)] 1486#[cfg(test)]
diff --git a/crates/ide_assists/src/handlers/extract_struct_from_enum_variant.rs b/crates/ide_assists/src/handlers/extract_struct_from_enum_variant.rs
index 66f274fa7..007aba23d 100644
--- a/crates/ide_assists/src/handlers/extract_struct_from_enum_variant.rs
+++ b/crates/ide_assists/src/handlers/extract_struct_from_enum_variant.rs
@@ -70,7 +70,7 @@ pub(crate) fn extract_struct_from_enum_variant(
70 continue; 70 continue;
71 } 71 }
72 builder.edit_file(file_id); 72 builder.edit_file(file_id);
73 let source_file = builder.make_ast_mut(ctx.sema.parse(file_id)); 73 let source_file = builder.make_mut(ctx.sema.parse(file_id));
74 let processed = process_references( 74 let processed = process_references(
75 ctx, 75 ctx,
76 &mut visited_modules_set, 76 &mut visited_modules_set,
@@ -84,8 +84,8 @@ pub(crate) fn extract_struct_from_enum_variant(
84 }); 84 });
85 } 85 }
86 builder.edit_file(ctx.frange.file_id); 86 builder.edit_file(ctx.frange.file_id);
87 let source_file = builder.make_ast_mut(ctx.sema.parse(ctx.frange.file_id)); 87 let source_file = builder.make_mut(ctx.sema.parse(ctx.frange.file_id));
88 let variant = builder.make_ast_mut(variant.clone()); 88 let variant = builder.make_mut(variant.clone());
89 if let Some(references) = def_file_references { 89 if let Some(references) = def_file_references {
90 let processed = process_references( 90 let processed = process_references(
91 ctx, 91 ctx,
@@ -151,20 +151,37 @@ fn create_struct_def(
151 field_list: &Either<ast::RecordFieldList, ast::TupleFieldList>, 151 field_list: &Either<ast::RecordFieldList, ast::TupleFieldList>,
152 visibility: Option<ast::Visibility>, 152 visibility: Option<ast::Visibility>,
153) -> ast::Struct { 153) -> ast::Struct {
154 let pub_vis = Some(make::visibility_pub()); 154 let pub_vis = make::visibility_pub();
155
156 let insert_pub = |node: &'_ SyntaxNode| {
157 let pub_vis = pub_vis.clone_for_update();
158 ted::insert(ted::Position::before(node), pub_vis.syntax());
159 };
160
161 // for fields without any existing visibility, use pub visibility
155 let field_list = match field_list { 162 let field_list = match field_list {
156 Either::Left(field_list) => { 163 Either::Left(field_list) => {
157 make::record_field_list(field_list.fields().flat_map(|field| { 164 let field_list = field_list.clone_for_update();
158 Some(make::record_field(pub_vis.clone(), field.name()?, field.ty()?)) 165
159 })) 166 field_list
160 .into() 167 .fields()
168 .filter(|field| field.visibility().is_none())
169 .filter_map(|field| field.name())
170 .for_each(|it| insert_pub(it.syntax()));
171
172 field_list.into()
161 } 173 }
162 Either::Right(field_list) => make::tuple_field_list( 174 Either::Right(field_list) => {
175 let field_list = field_list.clone_for_update();
176
163 field_list 177 field_list
164 .fields() 178 .fields()
165 .flat_map(|field| Some(make::tuple_field(pub_vis.clone(), field.ty()?))), 179 .filter(|field| field.visibility().is_none())
166 ) 180 .filter_map(|field| field.ty())
167 .into(), 181 .for_each(|it| insert_pub(it.syntax()));
182
183 field_list.into()
184 }
168 }; 185 };
169 186
170 make::struct_(visibility, variant_name, None, field_list).clone_for_update() 187 make::struct_(visibility, variant_name, None, field_list).clone_for_update()
@@ -295,6 +312,106 @@ enum A { One(One) }"#,
295 } 312 }
296 313
297 #[test] 314 #[test]
315 fn test_extract_struct_keep_comments_and_attrs_one_field_named() {
316 check_assist(
317 extract_struct_from_enum_variant,
318 r#"
319enum A {
320 $0One {
321 // leading comment
322 /// doc comment
323 #[an_attr]
324 foo: u32
325 // trailing comment
326 }
327}"#,
328 r#"
329struct One{
330 // leading comment
331 /// doc comment
332 #[an_attr]
333 pub foo: u32
334 // trailing comment
335 }
336
337enum A {
338 One(One)
339}"#,
340 );
341 }
342
343 #[test]
344 fn test_extract_struct_keep_comments_and_attrs_several_fields_named() {
345 check_assist(
346 extract_struct_from_enum_variant,
347 r#"
348enum A {
349 $0One {
350 // comment
351 /// doc
352 #[attr]
353 foo: u32,
354 // comment
355 #[attr]
356 /// doc
357 bar: u32
358 }
359}"#,
360 r#"
361struct One{
362 // comment
363 /// doc
364 #[attr]
365 pub foo: u32,
366 // comment
367 #[attr]
368 /// doc
369 pub bar: u32
370 }
371
372enum A {
373 One(One)
374}"#,
375 );
376 }
377
378 #[test]
379 fn test_extract_struct_keep_comments_and_attrs_several_fields_tuple() {
380 check_assist(
381 extract_struct_from_enum_variant,
382 "enum A { $0One(/* comment */ #[attr] u32, /* another */ u32 /* tail */) }",
383 r#"
384struct One(/* comment */ #[attr] pub u32, /* another */ pub u32 /* tail */);
385
386enum A { One(One) }"#,
387 );
388 }
389
390 #[test]
391 fn test_extract_struct_keep_existing_visibility_named() {
392 check_assist(
393 extract_struct_from_enum_variant,
394 "enum A { $0One{ pub a: u32, pub(crate) b: u32, pub(super) c: u32, d: u32 } }",
395 r#"
396struct One{ pub a: u32, pub(crate) b: u32, pub(super) c: u32, pub d: u32 }
397
398enum A { One(One) }"#,
399 );
400 }
401
402 #[test]
403 fn test_extract_struct_keep_existing_visibility_tuple() {
404 check_assist(
405 extract_struct_from_enum_variant,
406 "enum A { $0One(pub u32, pub(crate) u32, pub(super) u32, u32) }",
407 r#"
408struct One(pub u32, pub(crate) u32, pub(super) u32, pub u32);
409
410enum A { One(One) }"#,
411 );
412 }
413
414 #[test]
298 fn test_extract_enum_variant_name_value_namespace() { 415 fn test_extract_enum_variant_name_value_namespace() {
299 check_assist( 416 check_assist(
300 extract_struct_from_enum_variant, 417 extract_struct_from_enum_variant,
diff --git a/crates/ide_assists/src/handlers/extract_variable.rs b/crates/ide_assists/src/handlers/extract_variable.rs
index 136b9a55b..ae084c86c 100644
--- a/crates/ide_assists/src/handlers/extract_variable.rs
+++ b/crates/ide_assists/src/handlers/extract_variable.rs
@@ -54,7 +54,7 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext) -> Option
54 54
55 let var_name = match &field_shorthand { 55 let var_name = match &field_shorthand {
56 Some(it) => it.to_string(), 56 Some(it) => it.to_string(),
57 None => suggest_name::variable(&to_extract, &ctx.sema), 57 None => suggest_name::for_variable(&to_extract, &ctx.sema),
58 }; 58 };
59 let expr_range = match &field_shorthand { 59 let expr_range = match &field_shorthand {
60 Some(it) => it.syntax().text_range().cover(to_extract.syntax().text_range()), 60 Some(it) => it.syntax().text_range().cover(to_extract.syntax().text_range()),
diff --git a/crates/ide_assists/src/handlers/fill_match_arms.rs b/crates/ide_assists/src/handlers/fill_match_arms.rs
index be927cc1c..58b001050 100644
--- a/crates/ide_assists/src/handlers/fill_match_arms.rs
+++ b/crates/ide_assists/src/handlers/fill_match_arms.rs
@@ -71,6 +71,7 @@ pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option<
71 .filter_map(|variant| build_pat(ctx.db(), module, variant)) 71 .filter_map(|variant| build_pat(ctx.db(), module, variant))
72 .filter(|variant_pat| is_variant_missing(&top_lvl_pats, variant_pat)) 72 .filter(|variant_pat| is_variant_missing(&top_lvl_pats, variant_pat))
73 .map(|pat| make::match_arm(iter::once(pat), make::expr_empty_block())) 73 .map(|pat| make::match_arm(iter::once(pat), make::expr_empty_block()))
74 .map(|it| it.clone_for_update())
74 .collect::<Vec<_>>(); 75 .collect::<Vec<_>>();
75 if Some(enum_def) 76 if Some(enum_def)
76 == FamousDefs(&ctx.sema, Some(module.krate())) 77 == FamousDefs(&ctx.sema, Some(module.krate()))
@@ -99,6 +100,7 @@ pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option<
99 }) 100 })
100 .filter(|variant_pat| is_variant_missing(&top_lvl_pats, variant_pat)) 101 .filter(|variant_pat| is_variant_missing(&top_lvl_pats, variant_pat))
101 .map(|pat| make::match_arm(iter::once(pat), make::expr_empty_block())) 102 .map(|pat| make::match_arm(iter::once(pat), make::expr_empty_block()))
103 .map(|it| it.clone_for_update())
102 .collect() 104 .collect()
103 } else { 105 } else {
104 return None; 106 return None;
@@ -114,10 +116,20 @@ pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option<
114 "Fill match arms", 116 "Fill match arms",
115 target, 117 target,
116 |builder| { 118 |builder| {
117 let new_arm_list = match_arm_list.remove_placeholder(); 119 let new_match_arm_list = match_arm_list.clone_for_update();
118 let n_old_arms = new_arm_list.arms().count(); 120
119 let new_arm_list = new_arm_list.append_arms(missing_arms); 121 let catch_all_arm = new_match_arm_list
120 let first_new_arm = new_arm_list.arms().nth(n_old_arms); 122 .arms()
123 .find(|arm| matches!(arm.pat(), Some(ast::Pat::WildcardPat(_))));
124 if let Some(arm) = catch_all_arm {
125 arm.remove()
126 }
127 let mut first_new_arm = None;
128 for arm in missing_arms {
129 first_new_arm.get_or_insert_with(|| arm.clone());
130 new_match_arm_list.add_arm(arm);
131 }
132
121 let old_range = ctx.sema.original_range(match_arm_list.syntax()).range; 133 let old_range = ctx.sema.original_range(match_arm_list.syntax()).range;
122 match (first_new_arm, ctx.config.snippet_cap) { 134 match (first_new_arm, ctx.config.snippet_cap) {
123 (Some(first_new_arm), Some(cap)) => { 135 (Some(first_new_arm), Some(cap)) => {
@@ -131,10 +143,10 @@ pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option<
131 } 143 }
132 None => Cursor::Before(first_new_arm.syntax()), 144 None => Cursor::Before(first_new_arm.syntax()),
133 }; 145 };
134 let snippet = render_snippet(cap, new_arm_list.syntax(), cursor); 146 let snippet = render_snippet(cap, new_match_arm_list.syntax(), cursor);
135 builder.replace_snippet(cap, old_range, snippet); 147 builder.replace_snippet(cap, old_range, snippet);
136 } 148 }
137 _ => builder.replace(old_range, new_arm_list.to_string()), 149 _ => builder.replace(old_range, new_match_arm_list.to_string()),
138 } 150 }
139 }, 151 },
140 ) 152 )
@@ -263,18 +275,18 @@ mod tests {
263 check_assist_not_applicable( 275 check_assist_not_applicable(
264 fill_match_arms, 276 fill_match_arms,
265 r#" 277 r#"
266 enum A { 278enum A {
267 As, 279 As,
268 Bs{x:i32, y:Option<i32>}, 280 Bs{x:i32, y:Option<i32>},
269 Cs(i32, Option<i32>), 281 Cs(i32, Option<i32>),
270 } 282}
271 fn main() { 283fn main() {
272 match A::As$0 { 284 match A::As$0 {
273 A::As, 285 A::As,
274 A::Bs{x,y:Some(_)} => {} 286 A::Bs{x,y:Some(_)} => {}
275 A::Cs(_, Some(_)) => {} 287 A::Cs(_, Some(_)) => {}
276 } 288 }
277 } 289}
278 "#, 290 "#,
279 ); 291 );
280 } 292 }
@@ -284,13 +296,13 @@ mod tests {
284 check_assist_not_applicable( 296 check_assist_not_applicable(
285 fill_match_arms, 297 fill_match_arms,
286 r#" 298 r#"
287 fn foo(a: bool) { 299fn foo(a: bool) {
288 match a$0 { 300 match a$0 {
289 true => {} 301 true => {}
290 false => {} 302 false => {}
291 } 303 }
292 } 304}
293 "#, 305"#,
294 ) 306 )
295 } 307 }
296 308
@@ -301,11 +313,11 @@ mod tests {
301 check_assist_not_applicable( 313 check_assist_not_applicable(
302 fill_match_arms, 314 fill_match_arms,
303 r#" 315 r#"
304 fn main() { 316fn main() {
305 match (0, false)$0 { 317 match (0, false)$0 {
306 } 318 }
307 } 319}
308 "#, 320"#,
309 ); 321 );
310 } 322 }
311 323
@@ -314,19 +326,19 @@ mod tests {
314 check_assist( 326 check_assist(
315 fill_match_arms, 327 fill_match_arms,
316 r#" 328 r#"
317 fn foo(a: bool) { 329fn foo(a: bool) {
318 match a$0 { 330 match a$0 {
319 } 331 }
320 } 332}
321 "#, 333"#,
322 r#" 334 r#"
323 fn foo(a: bool) { 335fn foo(a: bool) {
324 match a { 336 match a {
325 $0true => {} 337 $0true => {}
326 false => {} 338 false => {}
327 } 339 }
328 } 340}
329 "#, 341"#,
330 ) 342 )
331 } 343 }
332 344
@@ -335,20 +347,20 @@ mod tests {
335 check_assist( 347 check_assist(
336 fill_match_arms, 348 fill_match_arms,
337 r#" 349 r#"
338 fn foo(a: bool) { 350fn foo(a: bool) {
339 match a$0 { 351 match a$0 {
340 true => {} 352 true => {}
341 } 353 }
342 } 354}
343 "#, 355"#,
344 r#" 356 r#"
345 fn foo(a: bool) { 357fn foo(a: bool) {
346 match a { 358 match a {
347 true => {} 359 true => {}
348 $0false => {} 360 $0false => {}
349 } 361 }
350 } 362}
351 "#, 363"#,
352 ) 364 )
353 } 365 }
354 366
@@ -357,15 +369,15 @@ mod tests {
357 check_assist_not_applicable( 369 check_assist_not_applicable(
358 fill_match_arms, 370 fill_match_arms,
359 r#" 371 r#"
360 fn foo(a: bool) { 372fn foo(a: bool) {
361 match (a, a)$0 { 373 match (a, a)$0 {
362 (true, true) => {} 374 (true, true) => {}
363 (true, false) => {} 375 (true, false) => {}
364 (false, true) => {} 376 (false, true) => {}
365 (false, false) => {} 377 (false, false) => {}
366 } 378 }
367 } 379}
368 "#, 380"#,
369 ) 381 )
370 } 382 }
371 383
@@ -374,21 +386,21 @@ mod tests {
374 check_assist( 386 check_assist(
375 fill_match_arms, 387 fill_match_arms,
376 r#" 388 r#"
377 fn foo(a: bool) { 389fn foo(a: bool) {
378 match (a, a)$0 { 390 match (a, a)$0 {
379 } 391 }
380 } 392}
381 "#, 393"#,
382 r#" 394 r#"
383 fn foo(a: bool) { 395fn foo(a: bool) {
384 match (a, a) { 396 match (a, a) {
385 $0(true, true) => {} 397 $0(true, true) => {}
386 (true, false) => {} 398 (true, false) => {}
387 (false, true) => {} 399 (false, true) => {}
388 (false, false) => {} 400 (false, false) => {}
389 } 401 }
390 } 402}
391 "#, 403"#,
392 ) 404 )
393 } 405 }
394 406
@@ -397,22 +409,22 @@ mod tests {
397 check_assist( 409 check_assist(
398 fill_match_arms, 410 fill_match_arms,
399 r#" 411 r#"
400 fn foo(a: bool) { 412fn foo(a: bool) {
401 match (a, a)$0 { 413 match (a, a)$0 {
402 (false, true) => {} 414 (false, true) => {}
403 } 415 }
404 } 416}
405 "#, 417"#,
406 r#" 418 r#"
407 fn foo(a: bool) { 419fn foo(a: bool) {
408 match (a, a) { 420 match (a, a) {
409 (false, true) => {} 421 (false, true) => {}
410 $0(true, true) => {} 422 $0(true, true) => {}
411 (true, false) => {} 423 (true, false) => {}
412 (false, false) => {} 424 (false, false) => {}
413 } 425 }
414 } 426}
415 "#, 427"#,
416 ) 428 )
417 } 429 }
418 430
@@ -421,32 +433,32 @@ mod tests {
421 check_assist( 433 check_assist(
422 fill_match_arms, 434 fill_match_arms,
423 r#" 435 r#"
424 enum A { 436enum A {
425 As, 437 As,
426 Bs { x: i32, y: Option<i32> }, 438 Bs { x: i32, y: Option<i32> },
427 Cs(i32, Option<i32>), 439 Cs(i32, Option<i32>),
428 } 440}
429 fn main() { 441fn main() {
430 match A::As$0 { 442 match A::As$0 {
431 A::Bs { x, y: Some(_) } => {} 443 A::Bs { x, y: Some(_) } => {}
432 A::Cs(_, Some(_)) => {} 444 A::Cs(_, Some(_)) => {}
433 } 445 }
434 } 446}
435 "#, 447"#,
436 r#" 448 r#"
437 enum A { 449enum A {
438 As, 450 As,
439 Bs { x: i32, y: Option<i32> }, 451 Bs { x: i32, y: Option<i32> },
440 Cs(i32, Option<i32>), 452 Cs(i32, Option<i32>),
441 } 453}
442 fn main() { 454fn main() {
443 match A::As { 455 match A::As {
444 A::Bs { x, y: Some(_) } => {} 456 A::Bs { x, y: Some(_) } => {}
445 A::Cs(_, Some(_)) => {} 457 A::Cs(_, Some(_)) => {}
446 $0A::As => {} 458 $0A::As => {}
447 } 459 }
448 } 460}
449 "#, 461"#,
450 ); 462 );
451 } 463 }
452 464
@@ -593,30 +605,30 @@ fn main() {
593 check_assist( 605 check_assist(
594 fill_match_arms, 606 fill_match_arms,
595 r#" 607 r#"
596 enum A { One, Two } 608enum A { One, Two }
597 enum B { One, Two } 609enum B { One, Two }
598 610
599 fn main() { 611fn main() {
600 let a = A::One; 612 let a = A::One;
601 let b = B::One; 613 let b = B::One;
602 match (a$0, b) {} 614 match (a$0, b) {}
603 } 615}
604 "#, 616"#,
605 r#" 617 r#"
606 enum A { One, Two } 618enum A { One, Two }
607 enum B { One, Two } 619enum B { One, Two }
608 620
609 fn main() { 621fn main() {
610 let a = A::One; 622 let a = A::One;
611 let b = B::One; 623 let b = B::One;
612 match (a, b) { 624 match (a, b) {
613 $0(A::One, B::One) => {} 625 $0(A::One, B::One) => {}
614 (A::One, B::Two) => {} 626 (A::One, B::Two) => {}
615 (A::Two, B::One) => {} 627 (A::Two, B::One) => {}
616 (A::Two, B::Two) => {} 628 (A::Two, B::Two) => {}
617 } 629 }
618 } 630}
619 "#, 631"#,
620 ); 632 );
621 } 633 }
622 634
@@ -625,30 +637,30 @@ fn main() {
625 check_assist( 637 check_assist(
626 fill_match_arms, 638 fill_match_arms,
627 r#" 639 r#"
628 enum A { One, Two } 640enum A { One, Two }
629 enum B { One, Two } 641enum B { One, Two }
630 642
631 fn main() { 643fn main() {
632 let a = A::One; 644 let a = A::One;
633 let b = B::One; 645 let b = B::One;
634 match (&a$0, &b) {} 646 match (&a$0, &b) {}
635 } 647}
636 "#, 648"#,
637 r#" 649 r#"
638 enum A { One, Two } 650enum A { One, Two }
639 enum B { One, Two } 651enum B { One, Two }
640 652
641 fn main() { 653fn main() {
642 let a = A::One; 654 let a = A::One;
643 let b = B::One; 655 let b = B::One;
644 match (&a, &b) { 656 match (&a, &b) {
645 $0(A::One, B::One) => {} 657 $0(A::One, B::One) => {}
646 (A::One, B::Two) => {} 658 (A::One, B::Two) => {}
647 (A::Two, B::One) => {} 659 (A::Two, B::One) => {}
648 (A::Two, B::Two) => {} 660 (A::Two, B::Two) => {}
649 } 661 }
650 } 662}
651 "#, 663"#,
652 ); 664 );
653 } 665 }
654 666
@@ -737,20 +749,20 @@ fn main() {
737 check_assist_not_applicable( 749 check_assist_not_applicable(
738 fill_match_arms, 750 fill_match_arms,
739 r#" 751 r#"
740 enum A { One, Two } 752enum A { One, Two }
741 enum B { One, Two } 753enum B { One, Two }
742 754
743 fn main() { 755fn main() {
744 let a = A::One; 756 let a = A::One;
745 let b = B::One; 757 let b = B::One;
746 match (a$0, b) { 758 match (a$0, b) {
747 (A::Two, B::One) => {} 759 (A::Two, B::One) => {}
748 (A::One, B::One) => {} 760 (A::One, B::One) => {}
749 (A::One, B::Two) => {} 761 (A::One, B::Two) => {}
750 (A::Two, B::Two) => {} 762 (A::Two, B::Two) => {}
751 } 763 }
752 } 764}
753 "#, 765"#,
754 ); 766 );
755 } 767 }
756 768
@@ -759,25 +771,25 @@ fn main() {
759 check_assist( 771 check_assist(
760 fill_match_arms, 772 fill_match_arms,
761 r#" 773 r#"
762 enum A { One, Two } 774enum A { One, Two }
763 775
764 fn main() { 776fn main() {
765 let a = A::One; 777 let a = A::One;
766 match (a$0, ) { 778 match (a$0, ) {
767 } 779 }
768 } 780}
769 "#, 781"#,
770 r#" 782 r#"
771 enum A { One, Two } 783enum A { One, Two }
772 784
773 fn main() { 785fn main() {
774 let a = A::One; 786 let a = A::One;
775 match (a, ) { 787 match (a, ) {
776 $0(A::One,) => {} 788 $0(A::One,) => {}
777 (A::Two,) => {} 789 (A::Two,) => {}
778 } 790 }
779 } 791}
780 "#, 792"#,
781 ); 793 );
782 } 794 }
783 795
@@ -786,47 +798,47 @@ fn main() {
786 check_assist( 798 check_assist(
787 fill_match_arms, 799 fill_match_arms,
788 r#" 800 r#"
789 enum A { As } 801enum A { As }
790 802
791 fn foo(a: &A) { 803fn foo(a: &A) {
792 match a$0 { 804 match a$0 {
793 } 805 }
794 } 806}
795 "#, 807"#,
796 r#" 808 r#"
797 enum A { As } 809enum A { As }
798 810
799 fn foo(a: &A) { 811fn foo(a: &A) {
800 match a { 812 match a {
801 $0A::As => {} 813 $0A::As => {}
802 } 814 }
803 } 815}
804 "#, 816"#,
805 ); 817 );
806 818
807 check_assist( 819 check_assist(
808 fill_match_arms, 820 fill_match_arms,
809 r#" 821 r#"
810 enum A { 822enum A {
811 Es { x: usize, y: usize } 823 Es { x: usize, y: usize }
812 } 824}
813 825
814 fn foo(a: &mut A) { 826fn foo(a: &mut A) {
815 match a$0 { 827 match a$0 {
816 } 828 }
817 } 829}
818 "#, 830"#,
819 r#" 831 r#"
820 enum A { 832enum A {
821 Es { x: usize, y: usize } 833 Es { x: usize, y: usize }
822 } 834}
823 835
824 fn foo(a: &mut A) { 836fn foo(a: &mut A) {
825 match a { 837 match a {
826 $0A::Es { x, y } => {} 838 $0A::Es { x, y } => {}
827 } 839 }
828 } 840}
829 "#, 841"#,
830 ); 842 );
831 } 843 }
832 844
@@ -835,12 +847,12 @@ fn main() {
835 check_assist_target( 847 check_assist_target(
836 fill_match_arms, 848 fill_match_arms,
837 r#" 849 r#"
838 enum E { X, Y } 850enum E { X, Y }
839 851
840 fn main() { 852fn main() {
841 match E::X$0 {} 853 match E::X$0 {}
842 } 854}
843 "#, 855"#,
844 "match E::X {}", 856 "match E::X {}",
845 ); 857 );
846 } 858 }
@@ -850,24 +862,24 @@ fn main() {
850 check_assist( 862 check_assist(
851 fill_match_arms, 863 fill_match_arms,
852 r#" 864 r#"
853 enum E { X, Y } 865enum E { X, Y }
854 866
855 fn main() { 867fn main() {
856 match E::X { 868 match E::X {
857 $0_ => {} 869 $0_ => {}
858 } 870 }
859 } 871}
860 "#, 872"#,
861 r#" 873 r#"
862 enum E { X, Y } 874enum E { X, Y }
863 875
864 fn main() { 876fn main() {
865 match E::X { 877 match E::X {
866 $0E::X => {} 878 $0E::X => {}
867 E::Y => {} 879 E::Y => {}
868 } 880 }
869 } 881}
870 "#, 882"#,
871 ); 883 );
872 } 884 }
873 885
@@ -876,26 +888,26 @@ fn main() {
876 check_assist( 888 check_assist(
877 fill_match_arms, 889 fill_match_arms,
878 r#" 890 r#"
879 mod foo { pub enum E { X, Y } } 891mod foo { pub enum E { X, Y } }
880 use foo::E::X; 892use foo::E::X;
881 893
882 fn main() { 894fn main() {
883 match X { 895 match X {
884 $0 896 $0
885 } 897 }
886 } 898}
887 "#, 899"#,
888 r#" 900 r#"
889 mod foo { pub enum E { X, Y } } 901mod foo { pub enum E { X, Y } }
890 use foo::E::X; 902use foo::E::X;
891 903
892 fn main() { 904fn main() {
893 match X { 905 match X {
894 $0X => {} 906 $0X => {}
895 foo::E::Y => {} 907 foo::E::Y => {}
896 } 908 }
897 } 909}
898 "#, 910"#,
899 ); 911 );
900 } 912 }
901 913
@@ -904,26 +916,26 @@ fn main() {
904 check_assist( 916 check_assist(
905 fill_match_arms, 917 fill_match_arms,
906 r#" 918 r#"
907 enum A { One, Two } 919enum A { One, Two }
908 fn foo(a: A) { 920fn foo(a: A) {
909 match a { 921 match a {
910 // foo bar baz$0 922 // foo bar baz$0
911 A::One => {} 923 A::One => {}
912 // This is where the rest should be 924 // This is where the rest should be
913 } 925 }
914 } 926}
915 "#, 927"#,
916 r#" 928 r#"
917 enum A { One, Two } 929enum A { One, Two }
918 fn foo(a: A) { 930fn foo(a: A) {
919 match a { 931 match a {
920 // foo bar baz 932 // foo bar baz
921 A::One => {} 933 A::One => {}
922 // This is where the rest should be 934 $0A::Two => {}
923 $0A::Two => {} 935 // This is where the rest should be
924 } 936 }
925 } 937}
926 "#, 938"#,
927 ); 939 );
928 } 940 }
929 941
@@ -932,23 +944,23 @@ fn main() {
932 check_assist( 944 check_assist(
933 fill_match_arms, 945 fill_match_arms,
934 r#" 946 r#"
935 enum A { One, Two } 947enum A { One, Two }
936 fn foo(a: A) { 948fn foo(a: A) {
937 match a { 949 match a {
938 // foo bar baz$0 950 // foo bar baz$0
939 } 951 }
940 } 952}
941 "#, 953"#,
942 r#" 954 r#"
943 enum A { One, Two } 955enum A { One, Two }
944 fn foo(a: A) { 956fn foo(a: A) {
945 match a { 957 match a {
946 // foo bar baz 958 $0A::One => {}
947 $0A::One => {} 959 A::Two => {}
948 A::Two => {} 960 // foo bar baz
949 } 961 }
950 } 962}
951 "#, 963"#,
952 ); 964 );
953 } 965 }
954 966
@@ -957,22 +969,22 @@ fn main() {
957 check_assist( 969 check_assist(
958 fill_match_arms, 970 fill_match_arms,
959 r#" 971 r#"
960 enum A { One, Two, } 972enum A { One, Two, }
961 fn foo(a: A) { 973fn foo(a: A) {
962 match a$0 { 974 match a$0 {
963 _ => (), 975 _ => (),
964 } 976 }
965 } 977}
966 "#, 978"#,
967 r#" 979 r#"
968 enum A { One, Two, } 980enum A { One, Two, }
969 fn foo(a: A) { 981fn foo(a: A) {
970 match a { 982 match a {
971 $0A::One => {} 983 $0A::One => {}
972 A::Two => {} 984 A::Two => {}
973 } 985 }
974 } 986}
975 "#, 987"#,
976 ); 988 );
977 } 989 }
978 990
@@ -1016,7 +1028,8 @@ enum Test {
1016fn foo(t: Test) { 1028fn foo(t: Test) {
1017 m!(match t$0 {}); 1029 m!(match t$0 {});
1018}"#, 1030}"#,
1019 r#"macro_rules! m { ($expr:expr) => {$expr}} 1031 r#"
1032macro_rules! m { ($expr:expr) => {$expr}}
1020enum Test { 1033enum Test {
1021 A, 1034 A,
1022 B, 1035 B,
diff --git a/crates/ide_assists/src/handlers/generate_default_from_new.rs b/crates/ide_assists/src/handlers/generate_default_from_new.rs
index dc14552d6..bad826366 100644
--- a/crates/ide_assists/src/handlers/generate_default_from_new.rs
+++ b/crates/ide_assists/src/handlers/generate_default_from_new.rs
@@ -3,8 +3,10 @@ use crate::{
3 AssistId, 3 AssistId,
4}; 4};
5use ide_db::helpers::FamousDefs; 5use ide_db::helpers::FamousDefs;
6use itertools::Itertools;
7use stdx::format_to;
6use syntax::{ 8use syntax::{
7 ast::{self, Impl, NameOwner}, 9 ast::{self, GenericParamsOwner, Impl, NameOwner, TypeBoundsOwner},
8 AstNode, 10 AstNode,
9}; 11};
10 12
@@ -65,23 +67,56 @@ pub(crate) fn generate_default_from_new(acc: &mut Assists, ctx: &AssistContext)
65 "Generate a Default impl from a new fn", 67 "Generate a Default impl from a new fn",
66 insert_location, 68 insert_location,
67 move |builder| { 69 move |builder| {
68 let code = default_fn_node_for_new(impl_); 70 let default_code = " fn default() -> Self {
71 Self::new()
72 }";
73 let code = generate_trait_impl_text_from_impl(&impl_, "Default", default_code);
69 builder.insert(insert_location.end(), code); 74 builder.insert(insert_location.end(), code);
70 }, 75 },
71 ) 76 )
72} 77}
73 78
74fn default_fn_node_for_new(impl_: Impl) -> String { 79fn generate_trait_impl_text_from_impl(impl_: &ast::Impl, trait_text: &str, code: &str) -> String {
75 format!( 80 let generic_params = impl_.generic_param_list();
76 " 81 let mut buf = String::with_capacity(code.len());
82 buf.push_str("\n\n");
83 buf.push_str("impl");
84
85 if let Some(generic_params) = &generic_params {
86 let lifetimes = generic_params.lifetime_params().map(|lt| format!("{}", lt.syntax()));
87 let type_params = generic_params.type_params().map(|type_param| {
88 let mut buf = String::new();
89 if let Some(it) = type_param.name() {
90 format_to!(buf, "{}", it.syntax());
91 }
92 if let Some(it) = type_param.colon_token() {
93 format_to!(buf, "{} ", it);
94 }
95 if let Some(it) = type_param.type_bound_list() {
96 format_to!(buf, "{}", it.syntax());
97 }
98 buf
99 });
100 let const_params = generic_params.const_params().map(|t| t.syntax().to_string());
101 let generics = lifetimes.chain(type_params).chain(const_params).format(", ");
102 format_to!(buf, "<{}>", generics);
103 }
104
105 buf.push(' ');
106 buf.push_str(trait_text);
107 buf.push_str(" for ");
108 buf.push_str(&impl_.self_ty().unwrap().syntax().text().to_string());
109
110 match impl_.where_clause() {
111 Some(where_clause) => {
112 format_to!(buf, "\n{}\n{{\n{}\n}}", where_clause, code);
113 }
114 None => {
115 format_to!(buf, " {{\n{}\n}}", code);
116 }
117 }
77 118
78impl Default for {} {{ 119 buf
79 fn default() -> Self {{
80 Self::new()
81 }}
82}}",
83 impl_.self_ty().unwrap().syntax().text()
84 )
85} 120}
86 121
87fn is_default_implemented(ctx: &AssistContext, impl_: &Impl) -> bool { 122fn is_default_implemented(ctx: &AssistContext, impl_: &Impl) -> bool {
@@ -176,6 +211,234 @@ impl Default for Test {
176 } 211 }
177 212
178 #[test] 213 #[test]
214 fn new_function_with_generic() {
215 check_pass(
216 r#"
217pub struct Foo<T> {
218 _bar: *mut T,
219}
220
221impl<T> Foo<T> {
222 pub fn ne$0w() -> Self {
223 unimplemented!()
224 }
225}
226"#,
227 r#"
228pub struct Foo<T> {
229 _bar: *mut T,
230}
231
232impl<T> Foo<T> {
233 pub fn new() -> Self {
234 unimplemented!()
235 }
236}
237
238impl<T> Default for Foo<T> {
239 fn default() -> Self {
240 Self::new()
241 }
242}
243"#,
244 );
245 }
246
247 #[test]
248 fn new_function_with_generics() {
249 check_pass(
250 r#"
251pub struct Foo<T, B> {
252 _tars: *mut T,
253 _bar: *mut B,
254}
255
256impl<T, B> Foo<T, B> {
257 pub fn ne$0w() -> Self {
258 unimplemented!()
259 }
260}
261"#,
262 r#"
263pub struct Foo<T, B> {
264 _tars: *mut T,
265 _bar: *mut B,
266}
267
268impl<T, B> Foo<T, B> {
269 pub fn new() -> Self {
270 unimplemented!()
271 }
272}
273
274impl<T, B> Default for Foo<T, B> {
275 fn default() -> Self {
276 Self::new()
277 }
278}
279"#,
280 );
281 }
282
283 #[test]
284 fn new_function_with_generic_and_bound() {
285 check_pass(
286 r#"
287pub struct Foo<T> {
288 t: T,
289}
290
291impl<T: From<i32>> Foo<T> {
292 pub fn ne$0w() -> Self {
293 Foo { t: 0.into() }
294 }
295}
296"#,
297 r#"
298pub struct Foo<T> {
299 t: T,
300}
301
302impl<T: From<i32>> Foo<T> {
303 pub fn new() -> Self {
304 Foo { t: 0.into() }
305 }
306}
307
308impl<T: From<i32>> Default for Foo<T> {
309 fn default() -> Self {
310 Self::new()
311 }
312}
313"#,
314 );
315 }
316
317 #[test]
318 fn new_function_with_generics_and_bounds() {
319 check_pass(
320 r#"
321pub struct Foo<T, B> {
322 _tars: T,
323 _bar: B,
324}
325
326impl<T: From<i32>, B: From<i64>> Foo<T, B> {
327 pub fn ne$0w() -> Self {
328 unimplemented!()
329 }
330}
331"#,
332 r#"
333pub struct Foo<T, B> {
334 _tars: T,
335 _bar: B,
336}
337
338impl<T: From<i32>, B: From<i64>> Foo<T, B> {
339 pub fn new() -> Self {
340 unimplemented!()
341 }
342}
343
344impl<T: From<i32>, B: From<i64>> Default for Foo<T, B> {
345 fn default() -> Self {
346 Self::new()
347 }
348}
349"#,
350 );
351 }
352
353 #[test]
354 fn new_function_with_generic_and_where() {
355 check_pass(
356 r#"
357pub struct Foo<T> {
358 t: T,
359}
360
361impl<T: From<i32>> Foo<T>
362where
363 Option<T>: Debug
364{
365 pub fn ne$0w() -> Self {
366 Foo { t: 0.into() }
367 }
368}
369"#,
370 r#"
371pub struct Foo<T> {
372 t: T,
373}
374
375impl<T: From<i32>> Foo<T>
376where
377 Option<T>: Debug
378{
379 pub fn new() -> Self {
380 Foo { t: 0.into() }
381 }
382}
383
384impl<T: From<i32>> Default for Foo<T>
385where
386 Option<T>: Debug
387{
388 fn default() -> Self {
389 Self::new()
390 }
391}
392"#,
393 );
394 }
395
396 #[test]
397 fn new_function_with_generics_and_wheres() {
398 check_pass(
399 r#"
400pub struct Foo<T, B> {
401 _tars: T,
402 _bar: B,
403}
404
405impl<T: From<i32>, B: From<i64>> Foo<T, B>
406where
407 Option<T>: Debug, Option<B>: Debug,
408{
409 pub fn ne$0w() -> Self {
410 unimplemented!()
411 }
412}
413"#,
414 r#"
415pub struct Foo<T, B> {
416 _tars: T,
417 _bar: B,
418}
419
420impl<T: From<i32>, B: From<i64>> Foo<T, B>
421where
422 Option<T>: Debug, Option<B>: Debug,
423{
424 pub fn new() -> Self {
425 unimplemented!()
426 }
427}
428
429impl<T: From<i32>, B: From<i64>> Default for Foo<T, B>
430where
431 Option<T>: Debug, Option<B>: Debug,
432{
433 fn default() -> Self {
434 Self::new()
435 }
436}
437"#,
438 );
439 }
440
441 #[test]
179 fn new_function_with_parameters() { 442 fn new_function_with_parameters() {
180 cov_mark::check!(new_function_with_parameters); 443 cov_mark::check!(new_function_with_parameters);
181 check_not_applicable( 444 check_not_applicable(
diff --git a/crates/ide_assists/src/handlers/generate_function.rs b/crates/ide_assists/src/handlers/generate_function.rs
index 6f95b1a07..bc9fc524b 100644
--- a/crates/ide_assists/src/handlers/generate_function.rs
+++ b/crates/ide_assists/src/handlers/generate_function.rs
@@ -175,7 +175,7 @@ impl FunctionBuilder {
175 } 175 }
176 176
177 fn render(self) -> FunctionTemplate { 177 fn render(self) -> FunctionTemplate {
178 let placeholder_expr = make::expr_todo(); 178 let placeholder_expr = make::ext::expr_todo();
179 let fn_body = make::block_expr(vec![], Some(placeholder_expr)); 179 let fn_body = make::block_expr(vec![], Some(placeholder_expr));
180 let visibility = if self.needs_pub { Some(make::visibility_pub_crate()) } else { None }; 180 let visibility = if self.needs_pub { Some(make::visibility_pub_crate()) } else { None };
181 let mut fn_def = make::fn_( 181 let mut fn_def = make::fn_(
diff --git a/crates/ide_assists/src/handlers/generate_new.rs b/crates/ide_assists/src/handlers/generate_new.rs
index 8ce5930b7..959a1f86c 100644
--- a/crates/ide_assists/src/handlers/generate_new.rs
+++ b/crates/ide_assists/src/handlers/generate_new.rs
@@ -1,4 +1,3 @@
1use ast::Adt;
2use itertools::Itertools; 1use itertools::Itertools;
3use stdx::format_to; 2use stdx::format_to;
4use syntax::ast::{self, AstNode, NameOwner, StructKind, VisibilityOwner}; 3use syntax::ast::{self, AstNode, NameOwner, StructKind, VisibilityOwner};
@@ -37,7 +36,7 @@ pub(crate) fn generate_new(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
37 }; 36 };
38 37
39 // Return early if we've found an existing new fn 38 // Return early if we've found an existing new fn
40 let impl_def = find_struct_impl(&ctx, &Adt::Struct(strukt.clone()), "new")?; 39 let impl_def = find_struct_impl(&ctx, &ast::Adt::Struct(strukt.clone()), "new")?;
41 40
42 let target = strukt.syntax().text_range(); 41 let target = strukt.syntax().text_range();
43 acc.add(AssistId("generate_new", AssistKind::Generate), "Generate `new`", target, |builder| { 42 acc.add(AssistId("generate_new", AssistKind::Generate), "Generate `new`", target, |builder| {
@@ -60,7 +59,7 @@ pub(crate) fn generate_new(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
60 let start_offset = impl_def 59 let start_offset = impl_def
61 .and_then(|impl_def| find_impl_block_start(impl_def, &mut buf)) 60 .and_then(|impl_def| find_impl_block_start(impl_def, &mut buf))
62 .unwrap_or_else(|| { 61 .unwrap_or_else(|| {
63 buf = generate_impl_text(&Adt::Struct(strukt.clone()), &buf); 62 buf = generate_impl_text(&ast::Adt::Struct(strukt.clone()), &buf);
64 strukt.syntax().text_range().end() 63 strukt.syntax().text_range().end()
65 }); 64 });
66 65
@@ -81,101 +80,132 @@ mod tests {
81 use super::*; 80 use super::*;
82 81
83 #[test] 82 #[test]
84 #[rustfmt::skip]
85 fn test_generate_new() { 83 fn test_generate_new() {
86 // Check output of generation
87 check_assist( 84 check_assist(
88 generate_new, 85 generate_new,
89"struct Foo {$0}", 86 r#"
90"struct Foo {} 87struct Foo {$0}
88"#,
89 r#"
90struct Foo {}
91 91
92impl Foo { 92impl Foo {
93 fn $0new() -> Self { Self { } } 93 fn $0new() -> Self { Self { } }
94}", 94}
95"#,
95 ); 96 );
96 check_assist( 97 check_assist(
97 generate_new, 98 generate_new,
98"struct Foo<T: Clone> {$0}", 99 r#"
99"struct Foo<T: Clone> {} 100struct Foo<T: Clone> {$0}
101"#,
102 r#"
103struct Foo<T: Clone> {}
100 104
101impl<T: Clone> Foo<T> { 105impl<T: Clone> Foo<T> {
102 fn $0new() -> Self { Self { } } 106 fn $0new() -> Self { Self { } }
103}", 107}
108"#,
104 ); 109 );
105 check_assist( 110 check_assist(
106 generate_new, 111 generate_new,
107"struct Foo<'a, T: Foo<'a>> {$0}", 112 r#"
108"struct Foo<'a, T: Foo<'a>> {} 113struct Foo<'a, T: Foo<'a>> {$0}
114"#,
115 r#"
116struct Foo<'a, T: Foo<'a>> {}
109 117
110impl<'a, T: Foo<'a>> Foo<'a, T> { 118impl<'a, T: Foo<'a>> Foo<'a, T> {
111 fn $0new() -> Self { Self { } } 119 fn $0new() -> Self { Self { } }
112}", 120}
121"#,
113 ); 122 );
114 check_assist( 123 check_assist(
115 generate_new, 124 generate_new,
116"struct Foo { baz: String $0}", 125 r#"
117"struct Foo { baz: String } 126struct Foo { baz: String $0}
127"#,
128 r#"
129struct Foo { baz: String }
118 130
119impl Foo { 131impl Foo {
120 fn $0new(baz: String) -> Self { Self { baz } } 132 fn $0new(baz: String) -> Self { Self { baz } }
121}", 133}
134"#,
122 ); 135 );
123 check_assist( 136 check_assist(
124 generate_new, 137 generate_new,
125"struct Foo { baz: String, qux: Vec<i32> $0}", 138 r#"
126"struct Foo { baz: String, qux: Vec<i32> } 139struct Foo { baz: String, qux: Vec<i32> $0}
140"#,
141 r#"
142struct Foo { baz: String, qux: Vec<i32> }
127 143
128impl Foo { 144impl Foo {
129 fn $0new(baz: String, qux: Vec<i32>) -> Self { Self { baz, qux } } 145 fn $0new(baz: String, qux: Vec<i32>) -> Self { Self { baz, qux } }
130}", 146}
147"#,
131 ); 148 );
149 }
132 150
133 // Check that visibility modifiers don't get brought in for fields 151 #[test]
152 fn check_that_visibility_modifiers_dont_get_brought_in() {
134 check_assist( 153 check_assist(
135 generate_new, 154 generate_new,
136"struct Foo { pub baz: String, pub qux: Vec<i32> $0}", 155 r#"
137"struct Foo { pub baz: String, pub qux: Vec<i32> } 156struct Foo { pub baz: String, pub qux: Vec<i32> $0}
157"#,
158 r#"
159struct Foo { pub baz: String, pub qux: Vec<i32> }
138 160
139impl Foo { 161impl Foo {
140 fn $0new(baz: String, qux: Vec<i32>) -> Self { Self { baz, qux } } 162 fn $0new(baz: String, qux: Vec<i32>) -> Self { Self { baz, qux } }
141}", 163}
164"#,
142 ); 165 );
166 }
143 167
144 // Check that it reuses existing impls 168 #[test]
169 fn check_it_reuses_existing_impls() {
145 check_assist( 170 check_assist(
146 generate_new, 171 generate_new,
147"struct Foo {$0} 172 r#"
173struct Foo {$0}
148 174
149impl Foo {} 175impl Foo {}
150", 176"#,
151"struct Foo {} 177 r#"
178struct Foo {}
152 179
153impl Foo { 180impl Foo {
154 fn $0new() -> Self { Self { } } 181 fn $0new() -> Self { Self { } }
155} 182}
156", 183"#,
157 ); 184 );
158 check_assist( 185 check_assist(
159 generate_new, 186 generate_new,
160"struct Foo {$0} 187 r#"
188struct Foo {$0}
161 189
162impl Foo { 190impl Foo {
163 fn qux(&self) {} 191 fn qux(&self) {}
164} 192}
165", 193"#,
166"struct Foo {} 194 r#"
195struct Foo {}
167 196
168impl Foo { 197impl Foo {
169 fn $0new() -> Self { Self { } } 198 fn $0new() -> Self { Self { } }
170 199
171 fn qux(&self) {} 200 fn qux(&self) {}
172} 201}
173", 202"#,
174 ); 203 );
175 204
176 check_assist( 205 check_assist(
177 generate_new, 206 generate_new,
178"struct Foo {$0} 207 r#"
208struct Foo {$0}
179 209
180impl Foo { 210impl Foo {
181 fn qux(&self) {} 211 fn qux(&self) {}
@@ -183,8 +213,9 @@ impl Foo {
183 5 213 5
184 } 214 }
185} 215}
186", 216"#,
187"struct Foo {} 217 r#"
218struct Foo {}
188 219
189impl Foo { 220impl Foo {
190 fn $0new() -> Self { Self { } } 221 fn $0new() -> Self { Self { } }
@@ -194,27 +225,37 @@ impl Foo {
194 5 225 5
195 } 226 }
196} 227}
197", 228"#,
198 ); 229 );
230 }
199 231
200 // Check visibility of new fn based on struct 232 #[test]
233 fn check_visibility_of_new_fn_based_on_struct() {
201 check_assist( 234 check_assist(
202 generate_new, 235 generate_new,
203"pub struct Foo {$0}", 236 r#"
204"pub struct Foo {} 237pub struct Foo {$0}
238"#,
239 r#"
240pub struct Foo {}
205 241
206impl Foo { 242impl Foo {
207 pub fn $0new() -> Self { Self { } } 243 pub fn $0new() -> Self { Self { } }
208}", 244}
245"#,
209 ); 246 );
210 check_assist( 247 check_assist(
211 generate_new, 248 generate_new,
212"pub(crate) struct Foo {$0}", 249 r#"
213"pub(crate) struct Foo {} 250pub(crate) struct Foo {$0}
251"#,
252 r#"
253pub(crate) struct Foo {}
214 254
215impl Foo { 255impl Foo {
216 pub(crate) fn $0new() -> Self { Self { } } 256 pub(crate) fn $0new() -> Self { Self { } }
217}", 257}
258"#,
218 ); 259 );
219 } 260 }
220 261
@@ -222,26 +263,28 @@ impl Foo {
222 fn generate_new_not_applicable_if_fn_exists() { 263 fn generate_new_not_applicable_if_fn_exists() {
223 check_assist_not_applicable( 264 check_assist_not_applicable(
224 generate_new, 265 generate_new,
225 " 266 r#"
226struct Foo {$0} 267struct Foo {$0}
227 268
228impl Foo { 269impl Foo {
229 fn new() -> Self { 270 fn new() -> Self {
230 Self 271 Self
231 } 272 }
232}", 273}
274"#,
233 ); 275 );
234 276
235 check_assist_not_applicable( 277 check_assist_not_applicable(
236 generate_new, 278 generate_new,
237 " 279 r#"
238struct Foo {$0} 280struct Foo {$0}
239 281
240impl Foo { 282impl Foo {
241 fn New() -> Self { 283 fn New() -> Self {
242 Self 284 Self
243 } 285 }
244}", 286}
287"#,
245 ); 288 );
246 } 289 }
247 290
@@ -249,12 +292,12 @@ impl Foo {
249 fn generate_new_target() { 292 fn generate_new_target() {
250 check_assist_target( 293 check_assist_target(
251 generate_new, 294 generate_new,
252 " 295 r#"
253struct SomeThingIrrelevant; 296struct SomeThingIrrelevant;
254/// Has a lifetime parameter 297/// Has a lifetime parameter
255struct Foo<'a, T: Foo<'a>> {$0} 298struct Foo<'a, T: Foo<'a>> {$0}
256struct EvenMoreIrrelevant; 299struct EvenMoreIrrelevant;
257", 300"#,
258 "/// Has a lifetime parameter 301 "/// Has a lifetime parameter
259struct Foo<'a, T: Foo<'a>> {}", 302struct Foo<'a, T: Foo<'a>> {}",
260 ); 303 );
@@ -264,7 +307,7 @@ struct Foo<'a, T: Foo<'a>> {}",
264 fn test_unrelated_new() { 307 fn test_unrelated_new() {
265 check_assist( 308 check_assist(
266 generate_new, 309 generate_new,
267 r##" 310 r#"
268pub struct AstId<N: AstNode> { 311pub struct AstId<N: AstNode> {
269 file_id: HirFileId, 312 file_id: HirFileId,
270 file_ast_id: FileAstId<N>, 313 file_ast_id: FileAstId<N>,
@@ -285,8 +328,9 @@ impl<T> Source<T> {
285 pub fn map<F: FnOnce(T) -> U, U>(self, f: F) -> Source<U> { 328 pub fn map<F: FnOnce(T) -> U, U>(self, f: F) -> Source<U> {
286 Source { file_id: self.file_id, ast: f(self.ast) } 329 Source { file_id: self.file_id, ast: f(self.ast) }
287 } 330 }
288}"##, 331}
289 r##" 332"#,
333 r#"
290pub struct AstId<N: AstNode> { 334pub struct AstId<N: AstNode> {
291 file_id: HirFileId, 335 file_id: HirFileId,
292 file_ast_id: FileAstId<N>, 336 file_ast_id: FileAstId<N>,
@@ -309,7 +353,8 @@ impl<T> Source<T> {
309 pub fn map<F: FnOnce(T) -> U, U>(self, f: F) -> Source<U> { 353 pub fn map<F: FnOnce(T) -> U, U>(self, f: F) -> Source<U> {
310 Source { file_id: self.file_id, ast: f(self.ast) } 354 Source { file_id: self.file_id, ast: f(self.ast) }
311 } 355 }
312}"##, 356}
357"#,
313 ); 358 );
314 } 359 }
315} 360}
diff --git a/crates/ide_assists/src/handlers/introduce_named_lifetime.rs b/crates/ide_assists/src/handlers/introduce_named_lifetime.rs
index 9f4f71d6c..16f8f9d70 100644
--- a/crates/ide_assists/src/handlers/introduce_named_lifetime.rs
+++ b/crates/ide_assists/src/handlers/introduce_named_lifetime.rs
@@ -84,19 +84,17 @@ fn generate_fn_def_assist(
84 } 84 }
85 }; 85 };
86 acc.add(AssistId(ASSIST_NAME, AssistKind::Refactor), ASSIST_LABEL, lifetime_loc, |builder| { 86 acc.add(AssistId(ASSIST_NAME, AssistKind::Refactor), ASSIST_LABEL, lifetime_loc, |builder| {
87 let fn_def = builder.make_ast_mut(fn_def); 87 let fn_def = builder.make_mut(fn_def);
88 let lifetime = builder.make_ast_mut(lifetime); 88 let lifetime = builder.make_mut(lifetime);
89 let loc_needing_lifetime = 89 let loc_needing_lifetime =
90 loc_needing_lifetime.and_then(|it| it.make_mut(builder).to_position()); 90 loc_needing_lifetime.and_then(|it| it.make_mut(builder).to_position());
91 91
92 add_lifetime_param(fn_def.get_or_create_generic_param_list(), new_lifetime_param); 92 fn_def.get_or_create_generic_param_list().add_generic_param(
93 ted::replace( 93 make::lifetime_param(new_lifetime_param.clone()).clone_for_update().into(),
94 lifetime.syntax(),
95 make_ast_lifetime(new_lifetime_param).clone_for_update().syntax(),
96 ); 94 );
97 loc_needing_lifetime.map(|position| { 95 ted::replace(lifetime.syntax(), new_lifetime_param.clone_for_update().syntax());
98 ted::insert(position, make_ast_lifetime(new_lifetime_param).clone_for_update().syntax()) 96 loc_needing_lifetime
99 }); 97 .map(|position| ted::insert(position, new_lifetime_param.clone_for_update().syntax()));
100 }) 98 })
101} 99}
102 100
@@ -109,14 +107,13 @@ fn generate_impl_def_assist(
109) -> Option<()> { 107) -> Option<()> {
110 let new_lifetime_param = generate_unique_lifetime_param_name(impl_def.generic_param_list())?; 108 let new_lifetime_param = generate_unique_lifetime_param_name(impl_def.generic_param_list())?;
111 acc.add(AssistId(ASSIST_NAME, AssistKind::Refactor), ASSIST_LABEL, lifetime_loc, |builder| { 109 acc.add(AssistId(ASSIST_NAME, AssistKind::Refactor), ASSIST_LABEL, lifetime_loc, |builder| {
112 let impl_def = builder.make_ast_mut(impl_def); 110 let impl_def = builder.make_mut(impl_def);
113 let lifetime = builder.make_ast_mut(lifetime); 111 let lifetime = builder.make_mut(lifetime);
114 112
115 add_lifetime_param(impl_def.get_or_create_generic_param_list(), new_lifetime_param); 113 impl_def.get_or_create_generic_param_list().add_generic_param(
116 ted::replace( 114 make::lifetime_param(new_lifetime_param.clone()).clone_for_update().into(),
117 lifetime.syntax(),
118 make_ast_lifetime(new_lifetime_param).clone_for_update().syntax(),
119 ); 115 );
116 ted::replace(lifetime.syntax(), new_lifetime_param.clone_for_update().syntax());
120 }) 117 })
121} 118}
122 119
@@ -124,31 +121,16 @@ fn generate_impl_def_assist(
124/// which is not in the list 121/// which is not in the list
125fn generate_unique_lifetime_param_name( 122fn generate_unique_lifetime_param_name(
126 existing_type_param_list: Option<ast::GenericParamList>, 123 existing_type_param_list: Option<ast::GenericParamList>,
127) -> Option<char> { 124) -> Option<ast::Lifetime> {
128 match existing_type_param_list { 125 match existing_type_param_list {
129 Some(type_params) => { 126 Some(type_params) => {
130 let used_lifetime_params: FxHashSet<_> = type_params 127 let used_lifetime_params: FxHashSet<_> =
131 .lifetime_params() 128 type_params.lifetime_params().map(|p| p.syntax().text().to_string()).collect();
132 .map(|p| p.syntax().text().to_string()[1..].to_owned()) 129 ('a'..='z').map(|it| format!("'{}", it)).find(|it| !used_lifetime_params.contains(it))
133 .collect();
134 (b'a'..=b'z').map(char::from).find(|c| !used_lifetime_params.contains(&c.to_string()))
135 } 130 }
136 None => Some('a'), 131 None => Some("'a".to_string()),
137 } 132 }
138} 133 .map(|it| make::lifetime(&it))
139
140fn add_lifetime_param(type_params: ast::GenericParamList, new_lifetime_param: char) {
141 let generic_param =
142 make::generic_param(format!("'{}", new_lifetime_param), None).clone_for_update();
143 type_params.add_generic_param(generic_param);
144}
145
146fn make_ast_lifetime(new_lifetime_param: char) -> ast::Lifetime {
147 make::generic_param(format!("'{}", new_lifetime_param), None)
148 .syntax()
149 .descendants()
150 .find_map(ast::Lifetime::cast)
151 .unwrap()
152} 134}
153 135
154enum NeedsLifetime { 136enum NeedsLifetime {
@@ -159,8 +141,8 @@ enum NeedsLifetime {
159impl NeedsLifetime { 141impl NeedsLifetime {
160 fn make_mut(self, builder: &mut AssistBuilder) -> Self { 142 fn make_mut(self, builder: &mut AssistBuilder) -> Self {
161 match self { 143 match self {
162 Self::SelfParam(it) => Self::SelfParam(builder.make_ast_mut(it)), 144 Self::SelfParam(it) => Self::SelfParam(builder.make_mut(it)),
163 Self::RefType(it) => Self::RefType(builder.make_ast_mut(it)), 145 Self::RefType(it) => Self::RefType(builder.make_mut(it)),
164 } 146 }
165 } 147 }
166 148
diff --git a/crates/ide_assists/src/handlers/merge_imports.rs b/crates/ide_assists/src/handlers/merge_imports.rs
index add7b8e37..31854840c 100644
--- a/crates/ide_assists/src/handlers/merge_imports.rs
+++ b/crates/ide_assists/src/handlers/merge_imports.rs
@@ -27,14 +27,14 @@ pub(crate) fn merge_imports(acc: &mut Assists, ctx: &AssistContext) -> Option<()
27 if let Some(use_item) = tree.syntax().parent().and_then(ast::Use::cast) { 27 if let Some(use_item) = tree.syntax().parent().and_then(ast::Use::cast) {
28 let (merged, to_remove) = 28 let (merged, to_remove) =
29 next_prev().filter_map(|dir| neighbor(&use_item, dir)).find_map(|use_item2| { 29 next_prev().filter_map(|dir| neighbor(&use_item, dir)).find_map(|use_item2| {
30 try_merge_imports(&use_item, &use_item2, MergeBehavior::Full).zip(Some(use_item2)) 30 try_merge_imports(&use_item, &use_item2, MergeBehavior::Crate).zip(Some(use_item2))
31 })?; 31 })?;
32 32
33 imports = Some((use_item, merged, to_remove)); 33 imports = Some((use_item, merged, to_remove));
34 } else { 34 } else {
35 let (merged, to_remove) = 35 let (merged, to_remove) =
36 next_prev().filter_map(|dir| neighbor(&tree, dir)).find_map(|use_tree| { 36 next_prev().filter_map(|dir| neighbor(&tree, dir)).find_map(|use_tree| {
37 try_merge_trees(&tree, &use_tree, MergeBehavior::Full).zip(Some(use_tree)) 37 try_merge_trees(&tree, &use_tree, MergeBehavior::Crate).zip(Some(use_tree))
38 })?; 38 })?;
39 39
40 uses = Some((tree.clone(), merged, to_remove)) 40 uses = Some((tree.clone(), merged, to_remove))
@@ -47,16 +47,16 @@ pub(crate) fn merge_imports(acc: &mut Assists, ctx: &AssistContext) -> Option<()
47 target, 47 target,
48 |builder| { 48 |builder| {
49 if let Some((to_replace, replacement, to_remove)) = imports { 49 if let Some((to_replace, replacement, to_remove)) = imports {
50 let to_replace = builder.make_ast_mut(to_replace); 50 let to_replace = builder.make_mut(to_replace);
51 let to_remove = builder.make_ast_mut(to_remove); 51 let to_remove = builder.make_mut(to_remove);
52 52
53 ted::replace(to_replace.syntax(), replacement.syntax()); 53 ted::replace(to_replace.syntax(), replacement.syntax());
54 to_remove.remove(); 54 to_remove.remove();
55 } 55 }
56 56
57 if let Some((to_replace, replacement, to_remove)) = uses { 57 if let Some((to_replace, replacement, to_remove)) = uses {
58 let to_replace = builder.make_ast_mut(to_replace); 58 let to_replace = builder.make_mut(to_replace);
59 let to_remove = builder.make_ast_mut(to_remove); 59 let to_remove = builder.make_mut(to_remove);
60 60
61 ted::replace(to_replace.syntax(), replacement.syntax()); 61 ted::replace(to_replace.syntax(), replacement.syntax());
62 to_remove.remove() 62 to_remove.remove()
diff --git a/crates/ide_assists/src/handlers/move_bounds.rs b/crates/ide_assists/src/handlers/move_bounds.rs
index 011a28d44..d89d11bdf 100644
--- a/crates/ide_assists/src/handlers/move_bounds.rs
+++ b/crates/ide_assists/src/handlers/move_bounds.rs
@@ -36,8 +36,8 @@ pub(crate) fn move_bounds_to_where_clause(acc: &mut Assists, ctx: &AssistContext
36 "Move to where clause", 36 "Move to where clause",
37 target, 37 target,
38 |edit| { 38 |edit| {
39 let type_param_list = edit.make_ast_mut(type_param_list); 39 let type_param_list = edit.make_mut(type_param_list);
40 let parent = edit.make_mut(parent); 40 let parent = edit.make_syntax_mut(parent);
41 41
42 let where_clause: ast::WhereClause = match_ast! { 42 let where_clause: ast::WhereClause = match_ast! {
43 match parent { 43 match parent {
@@ -63,11 +63,7 @@ pub(crate) fn move_bounds_to_where_clause(acc: &mut Assists, ctx: &AssistContext
63} 63}
64 64
65fn build_predicate(param: ast::TypeParam) -> Option<ast::WherePred> { 65fn build_predicate(param: ast::TypeParam) -> Option<ast::WherePred> {
66 let path = { 66 let path = make::ext::ident_path(&param.name()?.syntax().to_string());
67 let name_ref = make::name_ref(&param.name()?.syntax().to_string());
68 let segment = make::path_segment(name_ref);
69 make::path_unqualified(segment)
70 };
71 let predicate = make::where_pred(path, param.type_bound_list()?.bounds()); 67 let predicate = make::where_pred(path, param.type_bound_list()?.bounds());
72 Some(predicate.clone_for_update()) 68 Some(predicate.clone_for_update())
73} 69}
diff --git a/crates/ide_assists/src/handlers/move_module_to_file.rs b/crates/ide_assists/src/handlers/move_module_to_file.rs
index 6e685b4b2..93f702c55 100644
--- a/crates/ide_assists/src/handlers/move_module_to_file.rs
+++ b/crates/ide_assists/src/handlers/move_module_to_file.rs
@@ -1,4 +1,4 @@
1use ast::{edit::IndentLevel, VisibilityOwner}; 1use ast::edit::IndentLevel;
2use ide_db::base_db::AnchoredPathBuf; 2use ide_db::base_db::AnchoredPathBuf;
3use stdx::format_to; 3use stdx::format_to;
4use syntax::{ 4use syntax::{
@@ -60,12 +60,18 @@ pub(crate) fn move_module_to_file(acc: &mut Assists, ctx: &AssistContext) -> Opt
60 }; 60 };
61 61
62 let mut buf = String::new(); 62 let mut buf = String::new();
63 if let Some(v) = module_ast.visibility() {
64 format_to!(buf, "{} ", v);
65 }
66 format_to!(buf, "mod {};", module_name); 63 format_to!(buf, "mod {};", module_name);
67 64
68 builder.replace(module_ast.syntax().text_range(), buf); 65 let replacement_start = if let Some(mod_token) = module_ast.mod_token() {
66 mod_token.text_range().start()
67 } else {
68 module_ast.syntax().text_range().start()
69 };
70
71 builder.replace(
72 TextRange::new(replacement_start, module_ast.syntax().text_range().end()),
73 buf,
74 );
69 75
70 let dst = AnchoredPathBuf { anchor: ctx.frange.file_id, path }; 76 let dst = AnchoredPathBuf { anchor: ctx.frange.file_id, path };
71 builder.create_file(dst, contents); 77 builder.create_file(dst, contents);
@@ -184,4 +190,26 @@ pub(crate) mod tests;
184 cov_mark::check!(available_before_curly); 190 cov_mark::check!(available_before_curly);
185 check_assist_not_applicable(move_module_to_file, r#"mod m { $0 }"#); 191 check_assist_not_applicable(move_module_to_file, r#"mod m { $0 }"#);
186 } 192 }
193
194 #[test]
195 fn keep_outer_comments_and_attributes() {
196 check_assist(
197 move_module_to_file,
198 r#"
199/// doc comment
200#[attribute]
201mod $0tests {
202 #[test] fn t() {}
203}
204"#,
205 r#"
206//- /main.rs
207/// doc comment
208#[attribute]
209mod tests;
210//- /tests.rs
211#[test] fn t() {}
212"#,
213 );
214 }
187} 215}
diff --git a/crates/ide_assists/src/handlers/pull_assignment_up.rs b/crates/ide_assists/src/handlers/pull_assignment_up.rs
index 04bae4e58..f07b8a6c0 100644
--- a/crates/ide_assists/src/handlers/pull_assignment_up.rs
+++ b/crates/ide_assists/src/handlers/pull_assignment_up.rs
@@ -1,6 +1,6 @@
1use syntax::{ 1use syntax::{
2 ast::{self, edit::AstNodeEdit, make}, 2 ast::{self, make},
3 AstNode, 3 ted, AstNode,
4}; 4};
5 5
6use crate::{ 6use crate::{
@@ -37,103 +37,120 @@ use crate::{
37// ``` 37// ```
38pub(crate) fn pull_assignment_up(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 38pub(crate) fn pull_assignment_up(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
39 let assign_expr = ctx.find_node_at_offset::<ast::BinExpr>()?; 39 let assign_expr = ctx.find_node_at_offset::<ast::BinExpr>()?;
40 let name_expr = if assign_expr.op_kind()? == ast::BinOp::Assignment { 40
41 assign_expr.lhs()? 41 let op_kind = assign_expr.op_kind()?;
42 } else { 42 if op_kind != ast::BinOp::Assignment {
43 cov_mark::hit!(test_cant_pull_non_assignments);
43 return None; 44 return None;
45 }
46
47 let mut collector = AssignmentsCollector {
48 sema: &ctx.sema,
49 common_lhs: assign_expr.lhs()?,
50 assignments: Vec::new(),
44 }; 51 };
45 52
46 let (old_stmt, new_stmt) = if let Some(if_expr) = ctx.find_node_at_offset::<ast::IfExpr>() { 53 let tgt: ast::Expr = if let Some(if_expr) = ctx.find_node_at_offset::<ast::IfExpr>() {
47 ( 54 collector.collect_if(&if_expr)?;
48 ast::Expr::cast(if_expr.syntax().to_owned())?, 55 if_expr.into()
49 exprify_if(&if_expr, &ctx.sema, &name_expr)?.indent(if_expr.indent_level()),
50 )
51 } else if let Some(match_expr) = ctx.find_node_at_offset::<ast::MatchExpr>() { 56 } else if let Some(match_expr) = ctx.find_node_at_offset::<ast::MatchExpr>() {
52 ( 57 collector.collect_match(&match_expr)?;
53 ast::Expr::cast(match_expr.syntax().to_owned())?, 58 match_expr.into()
54 exprify_match(&match_expr, &ctx.sema, &name_expr)?,
55 )
56 } else { 59 } else {
57 return None; 60 return None;
58 }; 61 };
59 62
60 let expr_stmt = make::expr_stmt(new_stmt); 63 if let Some(parent) = tgt.syntax().parent() {
64 if matches!(parent.kind(), syntax::SyntaxKind::BIN_EXPR | syntax::SyntaxKind::LET_STMT) {
65 return None;
66 }
67 }
61 68
62 acc.add( 69 acc.add(
63 AssistId("pull_assignment_up", AssistKind::RefactorExtract), 70 AssistId("pull_assignment_up", AssistKind::RefactorExtract),
64 "Pull assignment up", 71 "Pull assignment up",
65 old_stmt.syntax().text_range(), 72 tgt.syntax().text_range(),
66 move |edit| { 73 move |edit| {
67 edit.replace(old_stmt.syntax().text_range(), format!("{} = {};", name_expr, expr_stmt)); 74 let assignments: Vec<_> = collector
75 .assignments
76 .into_iter()
77 .map(|(stmt, rhs)| (edit.make_mut(stmt), rhs.clone_for_update()))
78 .collect();
79
80 let tgt = edit.make_mut(tgt);
81
82 for (stmt, rhs) in assignments {
83 let mut stmt = stmt.syntax().clone();
84 if let Some(parent) = stmt.parent() {
85 if ast::ExprStmt::cast(parent.clone()).is_some() {
86 stmt = parent.clone();
87 }
88 }
89 ted::replace(stmt, rhs.syntax());
90 }
91 let assign_expr = make::expr_assignment(collector.common_lhs, tgt.clone());
92 let assign_stmt = make::expr_stmt(assign_expr);
93
94 ted::replace(tgt.syntax(), assign_stmt.syntax().clone_for_update());
68 }, 95 },
69 ) 96 )
70} 97}
71 98
72fn exprify_match( 99struct AssignmentsCollector<'a> {
73 match_expr: &ast::MatchExpr, 100 sema: &'a hir::Semantics<'a, ide_db::RootDatabase>,
74 sema: &hir::Semantics<ide_db::RootDatabase>, 101 common_lhs: ast::Expr,
75 name: &ast::Expr, 102 assignments: Vec<(ast::BinExpr, ast::Expr)>,
76) -> Option<ast::Expr> { 103}
77 let new_arm_list = match_expr 104
78 .match_arm_list()? 105impl<'a> AssignmentsCollector<'a> {
79 .arms() 106 fn collect_match(&mut self, match_expr: &ast::MatchExpr) -> Option<()> {
80 .map(|arm| { 107 for arm in match_expr.match_arm_list()?.arms() {
81 if let ast::Expr::BlockExpr(block) = arm.expr()? { 108 match arm.expr()? {
82 let new_block = exprify_block(&block, sema, name)?.indent(block.indent_level()); 109 ast::Expr::BlockExpr(block) => self.collect_block(&block)?,
83 Some(arm.replace_descendant(block, new_block)) 110 ast::Expr::BinExpr(expr) => self.collect_expr(&expr)?,
111 _ => return None,
112 }
113 }
114
115 Some(())
116 }
117 fn collect_if(&mut self, if_expr: &ast::IfExpr) -> Option<()> {
118 let then_branch = if_expr.then_branch()?;
119 self.collect_block(&then_branch)?;
120
121 match if_expr.else_branch()? {
122 ast::ElseBranch::Block(block) => self.collect_block(&block),
123 ast::ElseBranch::IfExpr(expr) => {
124 cov_mark::hit!(test_pull_assignment_up_chained_if);
125 self.collect_if(&expr)
126 }
127 }
128 }
129 fn collect_block(&mut self, block: &ast::BlockExpr) -> Option<()> {
130 let last_expr = block.tail_expr().or_else(|| {
131 if let ast::Stmt::ExprStmt(stmt) = block.statements().last()? {
132 stmt.expr()
84 } else { 133 } else {
85 None 134 None
86 } 135 }
87 }) 136 })?;
88 .collect::<Option<Vec<_>>>()?;
89 let new_arm_list = match_expr
90 .match_arm_list()?
91 .replace_descendants(match_expr.match_arm_list()?.arms().zip(new_arm_list));
92 Some(make::expr_match(match_expr.expr()?, new_arm_list))
93}
94 137
95fn exprify_if( 138 if let ast::Expr::BinExpr(expr) = last_expr {
96 statement: &ast::IfExpr, 139 return self.collect_expr(&expr);
97 sema: &hir::Semantics<ide_db::RootDatabase>,
98 name: &ast::Expr,
99) -> Option<ast::Expr> {
100 let then_branch = exprify_block(&statement.then_branch()?, sema, name)?;
101 let else_branch = match statement.else_branch()? {
102 ast::ElseBranch::Block(ref block) => {
103 ast::ElseBranch::Block(exprify_block(block, sema, name)?)
104 }
105 ast::ElseBranch::IfExpr(expr) => {
106 cov_mark::hit!(test_pull_assignment_up_chained_if);
107 ast::ElseBranch::IfExpr(ast::IfExpr::cast(
108 exprify_if(&expr, sema, name)?.syntax().to_owned(),
109 )?)
110 } 140 }
111 };
112 Some(make::expr_if(statement.condition()?, then_branch, Some(else_branch)))
113}
114 141
115fn exprify_block( 142 None
116 block: &ast::BlockExpr,
117 sema: &hir::Semantics<ide_db::RootDatabase>,
118 name: &ast::Expr,
119) -> Option<ast::BlockExpr> {
120 if block.tail_expr().is_some() {
121 return None;
122 } 143 }
123 144
124 let mut stmts: Vec<_> = block.statements().collect(); 145 fn collect_expr(&mut self, expr: &ast::BinExpr) -> Option<()> {
125 let stmt = stmts.pop()?; 146 if expr.op_kind()? == ast::BinOp::Assignment
126 147 && is_equivalent(self.sema, &expr.lhs()?, &self.common_lhs)
127 if let ast::Stmt::ExprStmt(stmt) = stmt { 148 {
128 if let ast::Expr::BinExpr(expr) = stmt.expr()? { 149 self.assignments.push((expr.clone(), expr.rhs()?));
129 if expr.op_kind()? == ast::BinOp::Assignment && is_equivalent(sema, &expr.lhs()?, name) 150 return Some(());
130 {
131 // The last statement in the block is an assignment to the name we want
132 return Some(make::block_expr(stmts, Some(expr.rhs()?)));
133 }
134 } 151 }
152 None
135 } 153 }
136 None
137} 154}
138 155
139fn is_equivalent( 156fn is_equivalent(
@@ -243,6 +260,37 @@ fn foo() {
243 } 260 }
244 261
245 #[test] 262 #[test]
263 fn test_pull_assignment_up_assignment_expressions() {
264 check_assist(
265 pull_assignment_up,
266 r#"
267fn foo() {
268 let mut a = 1;
269
270 match 1 {
271 1 => { $0a = 2; },
272 2 => a = 3,
273 3 => {
274 a = 4
275 }
276 }
277}"#,
278 r#"
279fn foo() {
280 let mut a = 1;
281
282 a = match 1 {
283 1 => { 2 },
284 2 => 3,
285 3 => {
286 4
287 }
288 };
289}"#,
290 );
291 }
292
293 #[test]
246 fn test_pull_assignment_up_not_last_not_applicable() { 294 fn test_pull_assignment_up_not_last_not_applicable() {
247 check_assist_not_applicable( 295 check_assist_not_applicable(
248 pull_assignment_up, 296 pull_assignment_up,
@@ -439,4 +487,24 @@ fn foo() {
439"#, 487"#,
440 ) 488 )
441 } 489 }
490
491 #[test]
492 fn test_cant_pull_non_assignments() {
493 cov_mark::check!(test_cant_pull_non_assignments);
494 check_assist_not_applicable(
495 pull_assignment_up,
496 r#"
497fn foo() {
498 let mut a = 1;
499 let b = &mut a;
500
501 if true {
502 $0*b + 2;
503 } else {
504 *b + 3;
505 }
506}
507"#,
508 )
509 }
442} 510}
diff --git a/crates/ide_assists/src/handlers/raw_string.rs b/crates/ide_assists/src/handlers/raw_string.rs
index d0f1613f3..d98a55ae4 100644
--- a/crates/ide_assists/src/handlers/raw_string.rs
+++ b/crates/ide_assists/src/handlers/raw_string.rs
@@ -1,6 +1,6 @@
1use std::borrow::Cow; 1use std::borrow::Cow;
2 2
3use syntax::{ast, AstToken, TextRange, TextSize}; 3use syntax::{ast, ast::IsString, AstToken, TextRange, TextSize};
4 4
5use crate::{AssistContext, AssistId, AssistKind, Assists}; 5use crate::{AssistContext, AssistId, AssistKind, Assists};
6 6
diff --git a/crates/ide_assists/src/handlers/reorder_fields.rs b/crates/ide_assists/src/handlers/reorder_fields.rs
index e90bbdbcf..933acead1 100644
--- a/crates/ide_assists/src/handlers/reorder_fields.rs
+++ b/crates/ide_assists/src/handlers/reorder_fields.rs
@@ -70,10 +70,10 @@ pub(crate) fn reorder_fields(acc: &mut Assists, ctx: &AssistContext) -> Option<(
70 target, 70 target,
71 |builder| match fields { 71 |builder| match fields {
72 Either::Left((sorted, field_list)) => { 72 Either::Left((sorted, field_list)) => {
73 replace(builder.make_ast_mut(field_list).fields(), sorted) 73 replace(builder.make_mut(field_list).fields(), sorted)
74 } 74 }
75 Either::Right((sorted, field_list)) => { 75 Either::Right((sorted, field_list)) => {
76 replace(builder.make_ast_mut(field_list).fields(), sorted) 76 replace(builder.make_mut(field_list).fields(), sorted)
77 } 77 }
78 }, 78 },
79 ) 79 )
diff --git a/crates/ide_assists/src/handlers/reorder_impl.rs b/crates/ide_assists/src/handlers/reorder_impl.rs
index 54a9a468e..5a6a9f158 100644
--- a/crates/ide_assists/src/handlers/reorder_impl.rs
+++ b/crates/ide_assists/src/handlers/reorder_impl.rs
@@ -79,8 +79,7 @@ pub(crate) fn reorder_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
79 "Sort methods", 79 "Sort methods",
80 target, 80 target,
81 |builder| { 81 |builder| {
82 let methods = 82 let methods = methods.into_iter().map(|fn_| builder.make_mut(fn_)).collect::<Vec<_>>();
83 methods.into_iter().map(|fn_| builder.make_ast_mut(fn_)).collect::<Vec<_>>();
84 methods 83 methods
85 .into_iter() 84 .into_iter()
86 .zip(sorted) 85 .zip(sorted)
diff --git a/crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs b/crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs
index 694d897d1..10d9cec31 100644
--- a/crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs
+++ b/crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs
@@ -84,7 +84,7 @@ pub(crate) fn replace_derive_with_manual_impl(
84 add_assist(acc, ctx, &attr, &args, &trait_path, Some(trait_), &adt)?; 84 add_assist(acc, ctx, &attr, &args, &trait_path, Some(trait_), &adt)?;
85 } 85 }
86 if no_traits_found { 86 if no_traits_found {
87 let trait_path = make::path_unqualified(make::path_segment(make::name_ref(trait_name))); 87 let trait_path = make::ext::ident_path(trait_name);
88 add_assist(acc, ctx, &attr, &args, &trait_path, None, &adt)?; 88 add_assist(acc, ctx, &attr, &args, &trait_path, None, &adt)?;
89 } 89 }
90 Some(()) 90 Some(())
@@ -159,10 +159,8 @@ fn impl_def_from_trait(
159 if trait_items.is_empty() { 159 if trait_items.is_empty() {
160 return None; 160 return None;
161 } 161 }
162 let impl_def = make::impl_trait( 162 let impl_def =
163 trait_path.clone(), 163 make::impl_trait(trait_path.clone(), make::ext::ident_path(&annotated_name.text()));
164 make::path_unqualified(make::path_segment(make::name_ref(&annotated_name.text()))),
165 );
166 let (impl_def, first_assoc_item) = 164 let (impl_def, first_assoc_item) =
167 add_trait_assoc_items_to_impl(sema, trait_items, trait_, impl_def, target_scope); 165 add_trait_assoc_items_to_impl(sema, trait_items, trait_, impl_def, target_scope);
168 Some((impl_def, first_assoc_item)) 166 Some((impl_def, first_assoc_item))
diff --git a/crates/ide_assists/src/handlers/replace_impl_trait_with_generic.rs b/crates/ide_assists/src/handlers/replace_impl_trait_with_generic.rs
index ff25b61ea..540a905cc 100644
--- a/crates/ide_assists/src/handlers/replace_impl_trait_with_generic.rs
+++ b/crates/ide_assists/src/handlers/replace_impl_trait_with_generic.rs
@@ -1,6 +1,9 @@
1use syntax::ast::{self, edit::AstNodeEdit, make, AstNode, GenericParamsOwner}; 1use syntax::{
2 ast::{self, edit_in_place::GenericParamsOwnerEdit, make, AstNode},
3 ted,
4};
2 5
3use crate::{AssistContext, AssistId, AssistKind, Assists}; 6use crate::{utils::suggest_name, AssistContext, AssistId, AssistKind, Assists};
4 7
5// Assist: replace_impl_trait_with_generic 8// Assist: replace_impl_trait_with_generic
6// 9//
@@ -17,30 +20,29 @@ pub(crate) fn replace_impl_trait_with_generic(
17 acc: &mut Assists, 20 acc: &mut Assists,
18 ctx: &AssistContext, 21 ctx: &AssistContext,
19) -> Option<()> { 22) -> Option<()> {
20 let type_impl_trait = ctx.find_node_at_offset::<ast::ImplTraitType>()?; 23 let impl_trait_type = ctx.find_node_at_offset::<ast::ImplTraitType>()?;
21 let type_param = type_impl_trait.syntax().parent().and_then(ast::Param::cast)?; 24 let param = impl_trait_type.syntax().parent().and_then(ast::Param::cast)?;
22 let type_fn = type_param.syntax().ancestors().find_map(ast::Fn::cast)?; 25 let fn_ = param.syntax().ancestors().find_map(ast::Fn::cast)?;
23 26
24 let impl_trait_ty = type_impl_trait.type_bound_list()?; 27 let type_bound_list = impl_trait_type.type_bound_list()?;
25 28
26 let target = type_fn.syntax().text_range(); 29 let target = fn_.syntax().text_range();
27 acc.add( 30 acc.add(
28 AssistId("replace_impl_trait_with_generic", AssistKind::RefactorRewrite), 31 AssistId("replace_impl_trait_with_generic", AssistKind::RefactorRewrite),
29 "Replace impl trait with generic", 32 "Replace impl trait with generic",
30 target, 33 target,
31 |edit| { 34 |edit| {
32 let generic_letter = impl_trait_ty.to_string().chars().next().unwrap().to_string(); 35 let impl_trait_type = edit.make_mut(impl_trait_type);
36 let fn_ = edit.make_mut(fn_);
33 37
34 let generic_param_list = type_fn 38 let type_param_name = suggest_name::for_generic_parameter(&impl_trait_type);
35 .generic_param_list()
36 .unwrap_or_else(|| make::generic_param_list(None))
37 .append_param(make::generic_param(generic_letter.clone(), Some(impl_trait_ty)));
38 39
39 let new_type_fn = type_fn 40 let type_param = make::type_param(make::name(&type_param_name), Some(type_bound_list))
40 .replace_descendant::<ast::Type>(type_impl_trait.into(), make::ty(&generic_letter)) 41 .clone_for_update();
41 .with_generic_param_list(generic_param_list); 42 let new_ty = make::ty(&type_param_name).clone_for_update();
42 43
43 edit.replace_ast(type_fn.clone(), new_type_fn); 44 ted::replace(impl_trait_type.syntax(), new_ty.syntax());
45 fn_.get_or_create_generic_param_list().add_generic_param(type_param.into())
44 }, 46 },
45 ) 47 )
46} 48}
@@ -55,12 +57,8 @@ mod tests {
55 fn replace_impl_trait_with_generic_params() { 57 fn replace_impl_trait_with_generic_params() {
56 check_assist( 58 check_assist(
57 replace_impl_trait_with_generic, 59 replace_impl_trait_with_generic,
58 r#" 60 r#"fn foo<G>(bar: $0impl Bar) {}"#,
59 fn foo<G>(bar: $0impl Bar) {} 61 r#"fn foo<G, B: Bar>(bar: B) {}"#,
60 "#,
61 r#"
62 fn foo<G, B: Bar>(bar: B) {}
63 "#,
64 ); 62 );
65 } 63 }
66 64
@@ -68,12 +66,8 @@ mod tests {
68 fn replace_impl_trait_without_generic_params() { 66 fn replace_impl_trait_without_generic_params() {
69 check_assist( 67 check_assist(
70 replace_impl_trait_with_generic, 68 replace_impl_trait_with_generic,
71 r#" 69 r#"fn foo(bar: $0impl Bar) {}"#,
72 fn foo(bar: $0impl Bar) {} 70 r#"fn foo<B: Bar>(bar: B) {}"#,
73 "#,
74 r#"
75 fn foo<B: Bar>(bar: B) {}
76 "#,
77 ); 71 );
78 } 72 }
79 73
@@ -81,12 +75,8 @@ mod tests {
81 fn replace_two_impl_trait_with_generic_params() { 75 fn replace_two_impl_trait_with_generic_params() {
82 check_assist( 76 check_assist(
83 replace_impl_trait_with_generic, 77 replace_impl_trait_with_generic,
84 r#" 78 r#"fn foo<G>(foo: impl Foo, bar: $0impl Bar) {}"#,
85 fn foo<G>(foo: impl Foo, bar: $0impl Bar) {} 79 r#"fn foo<G, B: Bar>(foo: impl Foo, bar: B) {}"#,
86 "#,
87 r#"
88 fn foo<G, B: Bar>(foo: impl Foo, bar: B) {}
89 "#,
90 ); 80 );
91 } 81 }
92 82
@@ -94,12 +84,8 @@ mod tests {
94 fn replace_impl_trait_with_empty_generic_params() { 84 fn replace_impl_trait_with_empty_generic_params() {
95 check_assist( 85 check_assist(
96 replace_impl_trait_with_generic, 86 replace_impl_trait_with_generic,
97 r#" 87 r#"fn foo<>(bar: $0impl Bar) {}"#,
98 fn foo<>(bar: $0impl Bar) {} 88 r#"fn foo<B: Bar>(bar: B) {}"#,
99 "#,
100 r#"
101 fn foo<B: Bar>(bar: B) {}
102 "#,
103 ); 89 );
104 } 90 }
105 91
@@ -108,13 +94,13 @@ mod tests {
108 check_assist( 94 check_assist(
109 replace_impl_trait_with_generic, 95 replace_impl_trait_with_generic,
110 r#" 96 r#"
111 fn foo< 97fn foo<
112 >(bar: $0impl Bar) {} 98>(bar: $0impl Bar) {}
113 "#, 99"#,
114 r#" 100 r#"
115 fn foo<B: Bar 101fn foo<B: Bar
116 >(bar: B) {} 102>(bar: B) {}
117 "#, 103"#,
118 ); 104 );
119 } 105 }
120 106
@@ -123,12 +109,8 @@ mod tests {
123 fn replace_impl_trait_with_exist_generic_letter() { 109 fn replace_impl_trait_with_exist_generic_letter() {
124 check_assist( 110 check_assist(
125 replace_impl_trait_with_generic, 111 replace_impl_trait_with_generic,
126 r#" 112 r#"fn foo<B>(bar: $0impl Bar) {}"#,
127 fn foo<B>(bar: $0impl Bar) {} 113 r#"fn foo<B, C: Bar>(bar: C) {}"#,
128 "#,
129 r#"
130 fn foo<B, C: Bar>(bar: C) {}
131 "#,
132 ); 114 );
133 } 115 }
134 116
@@ -137,19 +119,19 @@ mod tests {
137 check_assist( 119 check_assist(
138 replace_impl_trait_with_generic, 120 replace_impl_trait_with_generic,
139 r#" 121 r#"
140 fn foo< 122fn foo<
141 G: Foo, 123 G: Foo,
142 F, 124 F,
143 H, 125 H,
144 >(bar: $0impl Bar) {} 126>(bar: $0impl Bar) {}
145 "#, 127"#,
146 r#" 128 r#"
147 fn foo< 129fn foo<
148 G: Foo, 130 G: Foo,
149 F, 131 F,
150 H, B: Bar 132 H, B: Bar,
151 >(bar: B) {} 133>(bar: B) {}
152 "#, 134"#,
153 ); 135 );
154 } 136 }
155 137
@@ -157,12 +139,8 @@ mod tests {
157 fn replace_impl_trait_multiple() { 139 fn replace_impl_trait_multiple() {
158 check_assist( 140 check_assist(
159 replace_impl_trait_with_generic, 141 replace_impl_trait_with_generic,
160 r#" 142 r#"fn foo(bar: $0impl Foo + Bar) {}"#,
161 fn foo(bar: $0impl Foo + Bar) {} 143 r#"fn foo<F: Foo + Bar>(bar: F) {}"#,
162 "#,
163 r#"
164 fn foo<F: Foo + Bar>(bar: F) {}
165 "#,
166 ); 144 );
167 } 145 }
168} 146}
diff --git a/crates/ide_assists/src/handlers/replace_let_with_if_let.rs b/crates/ide_assists/src/handlers/replace_let_with_if_let.rs
index be7e724b5..1ad0fa816 100644
--- a/crates/ide_assists/src/handlers/replace_let_with_if_let.rs
+++ b/crates/ide_assists/src/handlers/replace_let_with_if_let.rs
@@ -50,22 +50,19 @@ pub(crate) fn replace_let_with_if_let(acc: &mut Assists, ctx: &AssistContext) ->
50 "Replace with if-let", 50 "Replace with if-let",
51 target, 51 target,
52 |edit| { 52 |edit| {
53 let with_placeholder: ast::Pat = match happy_variant { 53 let pat = match happy_variant {
54 None => make::wildcard_pat().into(), 54 None => original_pat,
55 Some(var_name) => make::tuple_struct_pat( 55 Some(var_name) => {
56 make::path_unqualified(make::path_segment(make::name_ref(var_name))), 56 make::tuple_struct_pat(make::ext::ident_path(var_name), once(original_pat))
57 once(make::wildcard_pat().into()), 57 .into()
58 ) 58 }
59 .into(),
60 }; 59 };
60
61 let block = 61 let block =
62 make::block_expr(None, None).indent(IndentLevel::from_node(let_stmt.syntax())); 62 make::ext::empty_block_expr().indent(IndentLevel::from_node(let_stmt.syntax()));
63 let if_ = make::expr_if(make::condition(init, Some(with_placeholder)), block, None); 63 let if_ = make::expr_if(make::condition(init, Some(pat)), block, None);
64 let stmt = make::expr_stmt(if_); 64 let stmt = make::expr_stmt(if_);
65 65
66 let placeholder = stmt.syntax().descendants().find_map(ast::WildcardPat::cast).unwrap();
67 let stmt = stmt.replace_descendant(placeholder.into(), original_pat);
68
69 edit.replace_ast(ast::Stmt::from(let_stmt), ast::Stmt::from(stmt)); 66 edit.replace_ast(ast::Stmt::from(let_stmt), ast::Stmt::from(stmt));
70 }, 67 },
71 ) 68 )
diff --git a/crates/ide_assists/src/handlers/replace_qualified_name_with_use.rs b/crates/ide_assists/src/handlers/replace_qualified_name_with_use.rs
index 99ba79860..39f5eb4ff 100644
--- a/crates/ide_assists/src/handlers/replace_qualified_name_with_use.rs
+++ b/crates/ide_assists/src/handlers/replace_qualified_name_with_use.rs
@@ -40,7 +40,7 @@ pub(crate) fn replace_qualified_name_with_use(
40 |builder| { 40 |builder| {
41 // Now that we've brought the name into scope, re-qualify all paths that could be 41 // Now that we've brought the name into scope, re-qualify all paths that could be
42 // affected (that is, all paths inside the node we added the `use` to). 42 // affected (that is, all paths inside the node we added the `use` to).
43 let syntax = builder.make_mut(syntax.clone()); 43 let syntax = builder.make_syntax_mut(syntax.clone());
44 if let Some(ref import_scope) = ImportScope::from(syntax.clone()) { 44 if let Some(ref import_scope) = ImportScope::from(syntax.clone()) {
45 shorten_paths(&syntax, &path.clone_for_update()); 45 shorten_paths(&syntax, &path.clone_for_update());
46 insert_use(import_scope, path, ctx.config.insert_use); 46 insert_use(import_scope, path, ctx.config.insert_use);
diff --git a/crates/ide_assists/src/handlers/replace_string_with_char.rs b/crates/ide_assists/src/handlers/replace_string_with_char.rs
index 634b9c0b7..0800d291e 100644
--- a/crates/ide_assists/src/handlers/replace_string_with_char.rs
+++ b/crates/ide_assists/src/handlers/replace_string_with_char.rs
@@ -1,4 +1,4 @@
1use syntax::{ast, AstToken, SyntaxKind::STRING}; 1use syntax::{ast, ast::IsString, AstToken, SyntaxKind::STRING};
2 2
3use crate::{AssistContext, AssistId, AssistKind, Assists}; 3use crate::{AssistContext, AssistId, AssistKind, Assists};
4 4
diff --git a/crates/ide_assists/src/handlers/replace_unwrap_with_match.rs b/crates/ide_assists/src/handlers/replace_unwrap_with_match.rs
index a986a6ae8..a3bfa221c 100644
--- a/crates/ide_assists/src/handlers/replace_unwrap_with_match.rs
+++ b/crates/ide_assists/src/handlers/replace_unwrap_with_match.rs
@@ -17,7 +17,7 @@ use ide_db::ty_filter::TryEnum;
17 17
18// Assist: replace_unwrap_with_match 18// Assist: replace_unwrap_with_match
19// 19//
20// Replaces `unwrap` a `match` expression. Works for Result and Option. 20// Replaces `unwrap` with a `match` expression. Works for Result and Option.
21// 21//
22// ``` 22// ```
23// enum Result<T, E> { Ok(T), Err(E) } 23// enum Result<T, E> { Ok(T), Err(E) }
@@ -32,7 +32,7 @@ use ide_db::ty_filter::TryEnum;
32// fn main() { 32// fn main() {
33// let x: Result<i32, i32> = Result::Ok(92); 33// let x: Result<i32, i32> = Result::Ok(92);
34// let y = match x { 34// let y = match x {
35// Ok(a) => a, 35// Ok(it) => it,
36// $0_ => unreachable!(), 36// $0_ => unreachable!(),
37// }; 37// };
38// } 38// }
@@ -52,16 +52,17 @@ pub(crate) fn replace_unwrap_with_match(acc: &mut Assists, ctx: &AssistContext)
52 "Replace unwrap with match", 52 "Replace unwrap with match",
53 target, 53 target,
54 |builder| { 54 |builder| {
55 let ok_path = make::path_unqualified(make::path_segment(make::name_ref(happy_variant))); 55 let ok_path = make::ext::ident_path(happy_variant);
56 let it = make::ident_pat(make::name("a")).into(); 56 let it = make::ident_pat(make::name("it")).into();
57 let ok_tuple = make::tuple_struct_pat(ok_path, iter::once(it)).into(); 57 let ok_tuple = make::tuple_struct_pat(ok_path, iter::once(it)).into();
58 58
59 let bind_path = make::path_unqualified(make::path_segment(make::name_ref("a"))); 59 let bind_path = make::ext::ident_path("it");
60 let ok_arm = make::match_arm(iter::once(ok_tuple), make::expr_path(bind_path)); 60 let ok_arm = make::match_arm(iter::once(ok_tuple), make::expr_path(bind_path));
61 61
62 let unreachable_call = make::expr_unreachable(); 62 let err_arm = make::match_arm(
63 let err_arm = 63 iter::once(make::wildcard_pat().into()),
64 make::match_arm(iter::once(make::wildcard_pat().into()), unreachable_call); 64 make::ext::expr_unreachable(),
65 );
65 66
66 let match_arm_list = make::match_arm_list(vec![ok_arm, err_arm]); 67 let match_arm_list = make::match_arm_list(vec![ok_arm, err_arm]);
67 let match_expr = make::expr_match(caller.clone(), match_arm_list) 68 let match_expr = make::expr_match(caller.clone(), match_arm_list)
@@ -110,7 +111,7 @@ fn i<T>(a: T) -> T { a }
110fn main() { 111fn main() {
111 let x: Result<i32, i32> = Result::Ok(92); 112 let x: Result<i32, i32> = Result::Ok(92);
112 let y = match i(x) { 113 let y = match i(x) {
113 Ok(a) => a, 114 Ok(it) => it,
114 $0_ => unreachable!(), 115 $0_ => unreachable!(),
115 }; 116 };
116} 117}
@@ -136,7 +137,7 @@ fn i<T>(a: T) -> T { a }
136fn main() { 137fn main() {
137 let x = Option::Some(92); 138 let x = Option::Some(92);
138 let y = match i(x) { 139 let y = match i(x) {
139 Some(a) => a, 140 Some(it) => it,
140 $0_ => unreachable!(), 141 $0_ => unreachable!(),
141 }; 142 };
142} 143}
@@ -162,7 +163,7 @@ fn i<T>(a: T) -> T { a }
162fn main() { 163fn main() {
163 let x: Result<i32, i32> = Result::Ok(92); 164 let x: Result<i32, i32> = Result::Ok(92);
164 let y = match i(x) { 165 let y = match i(x) {
165 Ok(a) => a, 166 Ok(it) => it,
166 $0_ => unreachable!(), 167 $0_ => unreachable!(),
167 }.count_zeroes(); 168 }.count_zeroes();
168} 169}
diff --git a/crates/ide_assists/src/handlers/wrap_return_type_in_result.rs b/crates/ide_assists/src/handlers/wrap_return_type_in_result.rs
index e838630ea..2f1da82c7 100644
--- a/crates/ide_assists/src/handlers/wrap_return_type_in_result.rs
+++ b/crates/ide_assists/src/handlers/wrap_return_type_in_result.rs
@@ -54,9 +54,7 @@ pub(crate) fn wrap_return_type_in_result(acc: &mut Assists, ctx: &AssistContext)
54 54
55 for ret_expr_arg in tail_return_expr_collector.exprs_to_wrap { 55 for ret_expr_arg in tail_return_expr_collector.exprs_to_wrap {
56 let ok_wrapped = make::expr_call( 56 let ok_wrapped = make::expr_call(
57 make::expr_path(make::path_unqualified(make::path_segment(make::name_ref( 57 make::expr_path(make::ext::ident_path("Ok")),
58 "Ok",
59 )))),
60 make::arg_list(iter::once(ret_expr_arg.clone())), 58 make::arg_list(iter::once(ret_expr_arg.clone())),
61 ); 59 );
62 builder.replace_ast(ret_expr_arg, ok_wrapped); 60 builder.replace_ast(ret_expr_arg, ok_wrapped);
diff --git a/crates/ide_assists/src/tests.rs b/crates/ide_assists/src/tests.rs
index 9c2847998..0d3969c36 100644
--- a/crates/ide_assists/src/tests.rs
+++ b/crates/ide_assists/src/tests.rs
@@ -21,7 +21,7 @@ pub(crate) const TEST_CONFIG: AssistConfig = AssistConfig {
21 snippet_cap: SnippetCap::new(true), 21 snippet_cap: SnippetCap::new(true),
22 allowed: None, 22 allowed: None,
23 insert_use: InsertUseConfig { 23 insert_use: InsertUseConfig {
24 merge: Some(MergeBehavior::Full), 24 merge: Some(MergeBehavior::Crate),
25 prefix_kind: hir::PrefixKind::Plain, 25 prefix_kind: hir::PrefixKind::Plain,
26 group: true, 26 group: true,
27 }, 27 },
diff --git a/crates/ide_assists/src/tests/generated.rs b/crates/ide_assists/src/tests/generated.rs
index 59bcef8fb..4406406a2 100644
--- a/crates/ide_assists/src/tests/generated.rs
+++ b/crates/ide_assists/src/tests/generated.rs
@@ -50,7 +50,6 @@ trait Trait {
50impl Trait for () { 50impl Trait for () {
51 type X = (); 51 type X = ();
52 fn foo(&self) {}$0 52 fn foo(&self) {}$0
53
54} 53}
55"#####, 54"#####,
56 r#####" 55 r#####"
@@ -1531,7 +1530,7 @@ enum Result<T, E> { Ok(T), Err(E) }
1531fn main() { 1530fn main() {
1532 let x: Result<i32, i32> = Result::Ok(92); 1531 let x: Result<i32, i32> = Result::Ok(92);
1533 let y = match x { 1532 let y = match x {
1534 Ok(a) => a, 1533 Ok(it) => it,
1535 $0_ => unreachable!(), 1534 $0_ => unreachable!(),
1536 }; 1535 };
1537} 1536}
diff --git a/crates/ide_assists/src/utils.rs b/crates/ide_assists/src/utils.rs
index 5a90ad715..fc7caee04 100644
--- a/crates/ide_assists/src/utils.rs
+++ b/crates/ide_assists/src/utils.rs
@@ -17,7 +17,7 @@ use syntax::{
17 ast::AttrsOwner, 17 ast::AttrsOwner,
18 ast::NameOwner, 18 ast::NameOwner,
19 ast::{self, edit, make, ArgListOwner, GenericParamsOwner}, 19 ast::{self, edit, make, ArgListOwner, GenericParamsOwner},
20 AstNode, Direction, SmolStr, 20 ted, AstNode, Direction, SmolStr,
21 SyntaxKind::*, 21 SyntaxKind::*,
22 SyntaxNode, TextSize, T, 22 SyntaxNode, TextSize, T,
23}; 23};
@@ -128,43 +128,43 @@ pub fn add_trait_assoc_items_to_impl(
128 sema: &hir::Semantics<ide_db::RootDatabase>, 128 sema: &hir::Semantics<ide_db::RootDatabase>,
129 items: Vec<ast::AssocItem>, 129 items: Vec<ast::AssocItem>,
130 trait_: hir::Trait, 130 trait_: hir::Trait,
131 impl_def: ast::Impl, 131 impl_: ast::Impl,
132 target_scope: hir::SemanticsScope, 132 target_scope: hir::SemanticsScope,
133) -> (ast::Impl, ast::AssocItem) { 133) -> (ast::Impl, ast::AssocItem) {
134 let impl_item_list = impl_def.assoc_item_list().unwrap_or_else(make::assoc_item_list);
135
136 let n_existing_items = impl_item_list.assoc_items().count();
137 let source_scope = sema.scope_for_def(trait_); 134 let source_scope = sema.scope_for_def(trait_);
138 let ast_transform = QualifyPaths::new(&target_scope, &source_scope) 135 let ast_transform = QualifyPaths::new(&target_scope, &source_scope)
139 .or(SubstituteTypeParams::for_trait_impl(&source_scope, trait_, impl_def.clone())); 136 .or(SubstituteTypeParams::for_trait_impl(&source_scope, trait_, impl_.clone()));
140 137
141 let items = items 138 let items = items
142 .into_iter() 139 .into_iter()
143 .map(|it| it.clone_for_update()) 140 .map(|it| it.clone_for_update())
144 .inspect(|it| ast_transform::apply(&*ast_transform, it)) 141 .inspect(|it| ast_transform::apply(&*ast_transform, it))
145 .map(|it| match it { 142 .map(|it| edit::remove_attrs_and_docs(&it).clone_subtree().clone_for_update());
146 ast::AssocItem::Fn(def) => ast::AssocItem::Fn(add_body(def)), 143
147 ast::AssocItem::TypeAlias(def) => ast::AssocItem::TypeAlias(def.remove_bounds()), 144 let res = impl_.clone_for_update();
148 _ => it, 145
149 }) 146 let assoc_item_list = res.get_or_create_assoc_item_list();
150 .map(|it| edit::remove_attrs_and_docs(&it)); 147 let mut first_item = None;
151 148 for item in items {
152 let new_impl_item_list = impl_item_list.append_items(items); 149 first_item.get_or_insert_with(|| item.clone());
153 let new_impl_def = impl_def.with_assoc_item_list(new_impl_item_list); 150 match &item {
154 let first_new_item = 151 ast::AssocItem::Fn(fn_) if fn_.body().is_none() => {
155 new_impl_def.assoc_item_list().unwrap().assoc_items().nth(n_existing_items).unwrap(); 152 let body = make::block_expr(None, Some(make::ext::expr_todo()))
156 return (new_impl_def, first_new_item); 153 .indent(edit::IndentLevel(1));
157 154 ted::replace(fn_.get_or_create_body().syntax(), body.clone_for_update().syntax())
158 fn add_body(fn_def: ast::Fn) -> ast::Fn { 155 }
159 match fn_def.body() { 156 ast::AssocItem::TypeAlias(type_alias) => {
160 Some(_) => fn_def, 157 if let Some(type_bound_list) = type_alias.type_bound_list() {
161 None => { 158 type_bound_list.remove()
162 let body = 159 }
163 make::block_expr(None, Some(make::expr_todo())).indent(edit::IndentLevel(1));
164 fn_def.with_body(body)
165 } 160 }
161 _ => {}
166 } 162 }
163
164 assoc_item_list.add_item(item)
167 } 165 }
166
167 (res, first_item.unwrap())
168} 168}
169 169
170#[derive(Clone, Copy, Debug)] 170#[derive(Clone, Copy, Debug)]
diff --git a/crates/ide_assists/src/utils/suggest_name.rs b/crates/ide_assists/src/utils/suggest_name.rs
index deafcd630..b3aabeab3 100644
--- a/crates/ide_assists/src/utils/suggest_name.rs
+++ b/crates/ide_assists/src/utils/suggest_name.rs
@@ -6,7 +6,7 @@ use itertools::Itertools;
6use stdx::to_lower_snake_case; 6use stdx::to_lower_snake_case;
7use syntax::{ 7use syntax::{
8 ast::{self, NameOwner}, 8 ast::{self, NameOwner},
9 match_ast, AstNode, 9 match_ast, AstNode, SmolStr,
10}; 10};
11 11
12/// Trait names, that will be ignored when in `impl Trait` and `dyn Trait` 12/// Trait names, that will be ignored when in `impl Trait` and `dyn Trait`
@@ -57,6 +57,14 @@ const USELESS_METHODS: &[&str] = &[
57 "iter_mut", 57 "iter_mut",
58]; 58];
59 59
60pub(crate) fn for_generic_parameter(ty: &ast::ImplTraitType) -> SmolStr {
61 let c = ty
62 .type_bound_list()
63 .and_then(|bounds| bounds.syntax().text().char_at(0.into()))
64 .unwrap_or('T');
65 c.encode_utf8(&mut [0; 4]).into()
66}
67
60/// Suggest name of variable for given expression 68/// Suggest name of variable for given expression
61/// 69///
62/// **NOTE**: it is caller's responsibility to guarantee uniqueness of the name. 70/// **NOTE**: it is caller's responsibility to guarantee uniqueness of the name.
@@ -75,7 +83,8 @@ const USELESS_METHODS: &[&str] = &[
75/// It also applies heuristics to filter out less informative names 83/// It also applies heuristics to filter out less informative names
76/// 84///
77/// Currently it sticks to the first name found. 85/// Currently it sticks to the first name found.
78pub(crate) fn variable(expr: &ast::Expr, sema: &Semantics<'_, RootDatabase>) -> String { 86// FIXME: Microoptimize and return a `SmolStr` here.
87pub(crate) fn for_variable(expr: &ast::Expr, sema: &Semantics<'_, RootDatabase>) -> String {
79 // `from_param` does not benifit from stripping 88 // `from_param` does not benifit from stripping
80 // it need the largest context possible 89 // it need the largest context possible
81 // so we check firstmost 90 // so we check firstmost
@@ -276,7 +285,7 @@ mod tests {
276 frange.range, 285 frange.range,
277 "selection is not an expression(yet contained in one)" 286 "selection is not an expression(yet contained in one)"
278 ); 287 );
279 let name = variable(&expr, &sema); 288 let name = for_variable(&expr, &sema);
280 assert_eq!(&name, expected); 289 assert_eq!(&name, expected);
281 } 290 }
282 291
diff --git a/crates/ide_completion/src/completions/keyword.rs b/crates/ide_completion/src/completions/keyword.rs
index b635e0ca3..61b667104 100644
--- a/crates/ide_completion/src/completions/keyword.rs
+++ b/crates/ide_completion/src/completions/keyword.rs
@@ -9,22 +9,21 @@ use crate::{CompletionContext, CompletionItem, CompletionItemKind, CompletionKin
9pub(crate) fn complete_use_tree_keyword(acc: &mut Completions, ctx: &CompletionContext) { 9pub(crate) fn complete_use_tree_keyword(acc: &mut Completions, ctx: &CompletionContext) {
10 // complete keyword "crate" in use stmt 10 // complete keyword "crate" in use stmt
11 let source_range = ctx.source_range(); 11 let source_range = ctx.source_range();
12 let kw_completion = move |text: &str| {
13 let mut item = CompletionItem::new(CompletionKind::Keyword, source_range, text);
14 item.kind(CompletionItemKind::Keyword).insert_text(text);
15 item
16 };
12 17
13 if ctx.use_item_syntax.is_some() { 18 if ctx.use_item_syntax.is_some() {
14 if ctx.path_qual.is_none() { 19 if ctx.path_qual.is_none() {
15 let mut item = CompletionItem::new(CompletionKind::Keyword, source_range, "crate::"); 20 kw_completion("crate::").add_to(acc);
16 item.kind(CompletionItemKind::Keyword).insert_text("crate::");
17 item.add_to(acc);
18 } 21 }
19 let mut item = CompletionItem::new(CompletionKind::Keyword, source_range, "self"); 22 kw_completion("self").add_to(acc);
20 item.kind(CompletionItemKind::Keyword);
21 item.add_to(acc);
22 if iter::successors(ctx.path_qual.clone(), |p| p.qualifier()) 23 if iter::successors(ctx.path_qual.clone(), |p| p.qualifier())
23 .all(|p| p.segment().and_then(|s| s.super_token()).is_some()) 24 .all(|p| p.segment().and_then(|s| s.super_token()).is_some())
24 { 25 {
25 let mut item = CompletionItem::new(CompletionKind::Keyword, source_range, "super::"); 26 kw_completion("super::").add_to(acc);
26 item.kind(CompletionItemKind::Keyword).insert_text("super::");
27 item.add_to(acc);
28 } 27 }
29 } 28 }
30 29
@@ -32,9 +31,8 @@ pub(crate) fn complete_use_tree_keyword(acc: &mut Completions, ctx: &CompletionC
32 if let Some(receiver) = &ctx.dot_receiver { 31 if let Some(receiver) = &ctx.dot_receiver {
33 if let Some(ty) = ctx.sema.type_of_expr(receiver) { 32 if let Some(ty) = ctx.sema.type_of_expr(receiver) {
34 if ty.impls_future(ctx.db) { 33 if ty.impls_future(ctx.db) {
35 let mut item = 34 let mut item = kw_completion("await");
36 CompletionItem::new(CompletionKind::Keyword, ctx.source_range(), "await"); 35 item.detail("expr.await");
37 item.kind(CompletionItemKind::Keyword).detail("expr.await").insert_text("await");
38 item.add_to(acc); 36 item.add_to(acc);
39 } 37 }
40 }; 38 };
diff --git a/crates/ide_completion/src/context.rs b/crates/ide_completion/src/context.rs
index 62ef40818..787eb2fd3 100644
--- a/crates/ide_completion/src/context.rs
+++ b/crates/ide_completion/src/context.rs
@@ -313,7 +313,8 @@ impl<'a> CompletionContext<'a> {
313 cov_mark::hit!(expected_type_let_with_leading_char); 313 cov_mark::hit!(expected_type_let_with_leading_char);
314 cov_mark::hit!(expected_type_let_without_leading_char); 314 cov_mark::hit!(expected_type_let_without_leading_char);
315 let ty = it.pat() 315 let ty = it.pat()
316 .and_then(|pat| self.sema.type_of_pat(&pat)); 316 .and_then(|pat| self.sema.type_of_pat(&pat))
317 .or_else(|| it.initializer().and_then(|it| self.sema.type_of_expr(&it)));
317 let name = if let Some(ast::Pat::IdentPat(ident)) = it.pat() { 318 let name = if let Some(ast::Pat::IdentPat(ident)) = it.pat() {
318 ident.name().map(NameOrNameRef::Name) 319 ident.name().map(NameOrNameRef::Name)
319 } else { 320 } else {
@@ -720,6 +721,26 @@ fn foo() {
720 } 721 }
721 722
722 #[test] 723 #[test]
724 fn expected_type_let_pat() {
725 check_expected_type_and_name(
726 r#"
727fn foo() {
728 let x$0 = 0u32;
729}
730"#,
731 expect![[r#"ty: u32, name: ?"#]],
732 );
733 check_expected_type_and_name(
734 r#"
735fn foo() {
736 let $0 = 0u32;
737}
738"#,
739 expect![[r#"ty: u32, name: ?"#]],
740 );
741 }
742
743 #[test]
723 fn expected_type_fn_param_without_leading_char() { 744 fn expected_type_fn_param_without_leading_char() {
724 cov_mark::check!(expected_type_fn_param_without_leading_char); 745 cov_mark::check!(expected_type_fn_param_without_leading_char);
725 check_expected_type_and_name( 746 check_expected_type_and_name(
diff --git a/crates/ide_completion/src/lib.rs b/crates/ide_completion/src/lib.rs
index e32633565..645349215 100644
--- a/crates/ide_completion/src/lib.rs
+++ b/crates/ide_completion/src/lib.rs
@@ -80,7 +80,7 @@ pub use crate::{
80// 80//
81// And the auto import completions, enabled with the `rust-analyzer.completion.autoimport.enable` setting and the corresponding LSP client capabilities. 81// And the auto import completions, enabled with the `rust-analyzer.completion.autoimport.enable` setting and the corresponding LSP client capabilities.
82// Those are the additional completion options with automatic `use` import and options from all project importable items, 82// Those are the additional completion options with automatic `use` import and options from all project importable items,
83// fuzzy matched agains the completion imput. 83// fuzzy matched against the completion input.
84// 84//
85// image::https://user-images.githubusercontent.com/48062697/113020667-b72ab880-917a-11eb-8778-716cf26a0eb3.gif[] 85// image::https://user-images.githubusercontent.com/48062697/113020667-b72ab880-917a-11eb-8778-716cf26a0eb3.gif[]
86 86
diff --git a/crates/ide_completion/src/test_utils.rs b/crates/ide_completion/src/test_utils.rs
index c9857ec5f..939fb2d47 100644
--- a/crates/ide_completion/src/test_utils.rs
+++ b/crates/ide_completion/src/test_utils.rs
@@ -20,7 +20,7 @@ pub(crate) const TEST_CONFIG: CompletionConfig = CompletionConfig {
20 add_call_argument_snippets: true, 20 add_call_argument_snippets: true,
21 snippet_cap: SnippetCap::new(true), 21 snippet_cap: SnippetCap::new(true),
22 insert_use: InsertUseConfig { 22 insert_use: InsertUseConfig {
23 merge: Some(MergeBehavior::Full), 23 merge: Some(MergeBehavior::Crate),
24 prefix_kind: PrefixKind::Plain, 24 prefix_kind: PrefixKind::Plain,
25 group: true, 25 group: true,
26 }, 26 },
diff --git a/crates/ide_db/src/helpers/insert_use/tests.rs b/crates/ide_db/src/helpers/insert_use/tests.rs
index 048c213e2..248227d29 100644
--- a/crates/ide_db/src/helpers/insert_use/tests.rs
+++ b/crates/ide_db/src/helpers/insert_use/tests.rs
@@ -44,7 +44,7 @@ fn insert_not_group_empty() {
44 44
45#[test] 45#[test]
46fn insert_existing() { 46fn insert_existing() {
47 check_full("std::fs", "use std::fs;", "use std::fs;") 47 check_crate("std::fs", "use std::fs;", "use std::fs;")
48} 48}
49 49
50#[test] 50#[test]
@@ -249,7 +249,7 @@ use self::fmt;",
249 249
250#[test] 250#[test]
251fn insert_no_imports() { 251fn insert_no_imports() {
252 check_full( 252 check_crate(
253 "foo::bar", 253 "foo::bar",
254 "fn main() {}", 254 "fn main() {}",
255 r"use foo::bar; 255 r"use foo::bar;
@@ -263,7 +263,7 @@ fn insert_empty_file() {
263 cov_mark::check!(insert_group_empty_file); 263 cov_mark::check!(insert_group_empty_file);
264 // empty files will get two trailing newlines 264 // empty files will get two trailing newlines
265 // this is due to the test case insert_no_imports above 265 // this is due to the test case insert_no_imports above
266 check_full( 266 check_crate(
267 "foo::bar", 267 "foo::bar",
268 "", 268 "",
269 r"use foo::bar; 269 r"use foo::bar;
@@ -290,7 +290,7 @@ fn insert_empty_module() {
290#[test] 290#[test]
291fn insert_after_inner_attr() { 291fn insert_after_inner_attr() {
292 cov_mark::check!(insert_group_empty_inner_attr); 292 cov_mark::check!(insert_group_empty_inner_attr);
293 check_full( 293 check_crate(
294 "foo::bar", 294 "foo::bar",
295 r"#![allow(unused_imports)]", 295 r"#![allow(unused_imports)]",
296 r"#![allow(unused_imports)] 296 r"#![allow(unused_imports)]
@@ -301,7 +301,7 @@ use foo::bar;",
301 301
302#[test] 302#[test]
303fn insert_after_inner_attr2() { 303fn insert_after_inner_attr2() {
304 check_full( 304 check_crate(
305 "foo::bar", 305 "foo::bar",
306 r"#![allow(unused_imports)] 306 r"#![allow(unused_imports)]
307 307
@@ -371,12 +371,12 @@ fn main() {}"#,
371 371
372#[test] 372#[test]
373fn merge_groups() { 373fn merge_groups() {
374 check_last("std::io", r"use std::fmt;", r"use std::{fmt, io};") 374 check_module("std::io", r"use std::fmt;", r"use std::{fmt, io};")
375} 375}
376 376
377#[test] 377#[test]
378fn merge_groups_last() { 378fn merge_groups_last() {
379 check_last( 379 check_module(
380 "std::io", 380 "std::io",
381 r"use std::fmt::{Result, Display};", 381 r"use std::fmt::{Result, Display};",
382 r"use std::fmt::{Result, Display}; 382 r"use std::fmt::{Result, Display};
@@ -386,12 +386,12 @@ use std::io;",
386 386
387#[test] 387#[test]
388fn merge_last_into_self() { 388fn merge_last_into_self() {
389 check_last("foo::bar::baz", r"use foo::bar;", r"use foo::bar::{self, baz};"); 389 check_module("foo::bar::baz", r"use foo::bar;", r"use foo::bar::{self, baz};");
390} 390}
391 391
392#[test] 392#[test]
393fn merge_groups_full() { 393fn merge_groups_full() {
394 check_full( 394 check_crate(
395 "std::io", 395 "std::io",
396 r"use std::fmt::{Result, Display};", 396 r"use std::fmt::{Result, Display};",
397 r"use std::{fmt::{Result, Display}, io};", 397 r"use std::{fmt::{Result, Display}, io};",
@@ -400,17 +400,21 @@ fn merge_groups_full() {
400 400
401#[test] 401#[test]
402fn merge_groups_long_full() { 402fn merge_groups_long_full() {
403 check_full("std::foo::bar::Baz", r"use std::foo::bar::Qux;", r"use std::foo::bar::{Baz, Qux};") 403 check_crate("std::foo::bar::Baz", r"use std::foo::bar::Qux;", r"use std::foo::bar::{Baz, Qux};")
404} 404}
405 405
406#[test] 406#[test]
407fn merge_groups_long_last() { 407fn merge_groups_long_last() {
408 check_last("std::foo::bar::Baz", r"use std::foo::bar::Qux;", r"use std::foo::bar::{Baz, Qux};") 408 check_module(
409 "std::foo::bar::Baz",
410 r"use std::foo::bar::Qux;",
411 r"use std::foo::bar::{Baz, Qux};",
412 )
409} 413}
410 414
411#[test] 415#[test]
412fn merge_groups_long_full_list() { 416fn merge_groups_long_full_list() {
413 check_full( 417 check_crate(
414 "std::foo::bar::Baz", 418 "std::foo::bar::Baz",
415 r"use std::foo::bar::{Qux, Quux};", 419 r"use std::foo::bar::{Qux, Quux};",
416 r"use std::foo::bar::{Baz, Quux, Qux};", 420 r"use std::foo::bar::{Baz, Quux, Qux};",
@@ -419,7 +423,7 @@ fn merge_groups_long_full_list() {
419 423
420#[test] 424#[test]
421fn merge_groups_long_last_list() { 425fn merge_groups_long_last_list() {
422 check_last( 426 check_module(
423 "std::foo::bar::Baz", 427 "std::foo::bar::Baz",
424 r"use std::foo::bar::{Qux, Quux};", 428 r"use std::foo::bar::{Qux, Quux};",
425 r"use std::foo::bar::{Baz, Quux, Qux};", 429 r"use std::foo::bar::{Baz, Quux, Qux};",
@@ -428,7 +432,7 @@ fn merge_groups_long_last_list() {
428 432
429#[test] 433#[test]
430fn merge_groups_long_full_nested() { 434fn merge_groups_long_full_nested() {
431 check_full( 435 check_crate(
432 "std::foo::bar::Baz", 436 "std::foo::bar::Baz",
433 r"use std::foo::bar::{Qux, quux::{Fez, Fizz}};", 437 r"use std::foo::bar::{Qux, quux::{Fez, Fizz}};",
434 r"use std::foo::bar::{Baz, Qux, quux::{Fez, Fizz}};", 438 r"use std::foo::bar::{Baz, Qux, quux::{Fez, Fizz}};",
@@ -437,7 +441,7 @@ fn merge_groups_long_full_nested() {
437 441
438#[test] 442#[test]
439fn merge_groups_long_last_nested() { 443fn merge_groups_long_last_nested() {
440 check_last( 444 check_module(
441 "std::foo::bar::Baz", 445 "std::foo::bar::Baz",
442 r"use std::foo::bar::{Qux, quux::{Fez, Fizz}};", 446 r"use std::foo::bar::{Qux, quux::{Fez, Fizz}};",
443 r"use std::foo::bar::Baz; 447 r"use std::foo::bar::Baz;
@@ -447,7 +451,7 @@ use std::foo::bar::{Qux, quux::{Fez, Fizz}};",
447 451
448#[test] 452#[test]
449fn merge_groups_full_nested_deep() { 453fn merge_groups_full_nested_deep() {
450 check_full( 454 check_crate(
451 "std::foo::bar::quux::Baz", 455 "std::foo::bar::quux::Baz",
452 r"use std::foo::bar::{Qux, quux::{Fez, Fizz}};", 456 r"use std::foo::bar::{Qux, quux::{Fez, Fizz}};",
453 r"use std::foo::bar::{Qux, quux::{Baz, Fez, Fizz}};", 457 r"use std::foo::bar::{Qux, quux::{Baz, Fez, Fizz}};",
@@ -456,7 +460,7 @@ fn merge_groups_full_nested_deep() {
456 460
457#[test] 461#[test]
458fn merge_groups_full_nested_long() { 462fn merge_groups_full_nested_long() {
459 check_full( 463 check_crate(
460 "std::foo::bar::Baz", 464 "std::foo::bar::Baz",
461 r"use std::{foo::bar::Qux};", 465 r"use std::{foo::bar::Qux};",
462 r"use std::{foo::bar::{Baz, Qux}};", 466 r"use std::{foo::bar::{Baz, Qux}};",
@@ -465,7 +469,7 @@ fn merge_groups_full_nested_long() {
465 469
466#[test] 470#[test]
467fn merge_groups_last_nested_long() { 471fn merge_groups_last_nested_long() {
468 check_full( 472 check_crate(
469 "std::foo::bar::Baz", 473 "std::foo::bar::Baz",
470 r"use std::{foo::bar::Qux};", 474 r"use std::{foo::bar::Qux};",
471 r"use std::{foo::bar::{Baz, Qux}};", 475 r"use std::{foo::bar::{Baz, Qux}};",
@@ -474,7 +478,7 @@ fn merge_groups_last_nested_long() {
474 478
475#[test] 479#[test]
476fn merge_groups_skip_pub() { 480fn merge_groups_skip_pub() {
477 check_full( 481 check_crate(
478 "std::io", 482 "std::io",
479 r"pub use std::fmt::{Result, Display};", 483 r"pub use std::fmt::{Result, Display};",
480 r"pub use std::fmt::{Result, Display}; 484 r"pub use std::fmt::{Result, Display};
@@ -484,7 +488,7 @@ use std::io;",
484 488
485#[test] 489#[test]
486fn merge_groups_skip_pub_crate() { 490fn merge_groups_skip_pub_crate() {
487 check_full( 491 check_crate(
488 "std::io", 492 "std::io",
489 r"pub(crate) use std::fmt::{Result, Display};", 493 r"pub(crate) use std::fmt::{Result, Display};",
490 r"pub(crate) use std::fmt::{Result, Display}; 494 r"pub(crate) use std::fmt::{Result, Display};
@@ -494,7 +498,7 @@ use std::io;",
494 498
495#[test] 499#[test]
496fn merge_groups_skip_attributed() { 500fn merge_groups_skip_attributed() {
497 check_full( 501 check_crate(
498 "std::io", 502 "std::io",
499 r#" 503 r#"
500#[cfg(feature = "gated")] use std::fmt::{Result, Display}; 504#[cfg(feature = "gated")] use std::fmt::{Result, Display};
@@ -509,7 +513,7 @@ use std::io;
509#[test] 513#[test]
510#[ignore] // FIXME: Support this 514#[ignore] // FIXME: Support this
511fn split_out_merge() { 515fn split_out_merge() {
512 check_last( 516 check_module(
513 "std::fmt::Result", 517 "std::fmt::Result",
514 r"use std::{fmt, io};", 518 r"use std::{fmt, io};",
515 r"use std::fmt::{self, Result}; 519 r"use std::fmt::{self, Result};
@@ -519,29 +523,33 @@ use std::io;",
519 523
520#[test] 524#[test]
521fn merge_into_module_import() { 525fn merge_into_module_import() {
522 check_full("std::fmt::Result", r"use std::{fmt, io};", r"use std::{fmt::{self, Result}, io};") 526 check_crate("std::fmt::Result", r"use std::{fmt, io};", r"use std::{fmt::{self, Result}, io};")
523} 527}
524 528
525#[test] 529#[test]
526fn merge_groups_self() { 530fn merge_groups_self() {
527 check_full("std::fmt::Debug", r"use std::fmt;", r"use std::fmt::{self, Debug};") 531 check_crate("std::fmt::Debug", r"use std::fmt;", r"use std::fmt::{self, Debug};")
528} 532}
529 533
530#[test] 534#[test]
531fn merge_mod_into_glob() { 535fn merge_mod_into_glob() {
532 check_full("token::TokenKind", r"use token::TokenKind::*;", r"use token::TokenKind::{*, self};") 536 check_crate(
537 "token::TokenKind",
538 r"use token::TokenKind::*;",
539 r"use token::TokenKind::{*, self};",
540 )
533 // FIXME: have it emit `use token::TokenKind::{self, *}`? 541 // FIXME: have it emit `use token::TokenKind::{self, *}`?
534} 542}
535 543
536#[test] 544#[test]
537fn merge_self_glob() { 545fn merge_self_glob() {
538 check_full("self", r"use self::*;", r"use self::{*, self};") 546 check_crate("self", r"use self::*;", r"use self::{*, self};")
539 // FIXME: have it emit `use {self, *}`? 547 // FIXME: have it emit `use {self, *}`?
540} 548}
541 549
542#[test] 550#[test]
543fn merge_glob_nested() { 551fn merge_glob_nested() {
544 check_full( 552 check_crate(
545 "foo::bar::quux::Fez", 553 "foo::bar::quux::Fez",
546 r"use foo::bar::{Baz, quux::*};", 554 r"use foo::bar::{Baz, quux::*};",
547 r"use foo::bar::{Baz, quux::{self::*, Fez}};", 555 r"use foo::bar::{Baz, quux::{self::*, Fez}};",
@@ -550,7 +558,7 @@ fn merge_glob_nested() {
550 558
551#[test] 559#[test]
552fn merge_nested_considers_first_segments() { 560fn merge_nested_considers_first_segments() {
553 check_full( 561 check_crate(
554 "hir_ty::display::write_bounds_like_dyn_trait", 562 "hir_ty::display::write_bounds_like_dyn_trait",
555 r"use hir_ty::{autoderef, display::{HirDisplayError, HirFormatter}, method_resolution};", 563 r"use hir_ty::{autoderef, display::{HirDisplayError, HirFormatter}, method_resolution};",
556 r"use hir_ty::{autoderef, display::{HirDisplayError, HirFormatter, write_bounds_like_dyn_trait}, method_resolution};", 564 r"use hir_ty::{autoderef, display::{HirDisplayError, HirFormatter, write_bounds_like_dyn_trait}, method_resolution};",
@@ -559,7 +567,7 @@ fn merge_nested_considers_first_segments() {
559 567
560#[test] 568#[test]
561fn skip_merge_last_too_long() { 569fn skip_merge_last_too_long() {
562 check_last( 570 check_module(
563 "foo::bar", 571 "foo::bar",
564 r"use foo::bar::baz::Qux;", 572 r"use foo::bar::baz::Qux;",
565 r"use foo::bar; 573 r"use foo::bar;
@@ -569,7 +577,7 @@ use foo::bar::baz::Qux;",
569 577
570#[test] 578#[test]
571fn skip_merge_last_too_long2() { 579fn skip_merge_last_too_long2() {
572 check_last( 580 check_module(
573 "foo::bar::baz::Qux", 581 "foo::bar::baz::Qux",
574 r"use foo::bar;", 582 r"use foo::bar;",
575 r"use foo::bar; 583 r"use foo::bar;
@@ -592,7 +600,7 @@ fn merge_last_fail() {
592 check_merge_only_fail( 600 check_merge_only_fail(
593 r"use foo::bar::{baz::{Qux, Fez}};", 601 r"use foo::bar::{baz::{Qux, Fez}};",
594 r"use foo::bar::{baaz::{Quux, Feez}};", 602 r"use foo::bar::{baaz::{Quux, Feez}};",
595 MergeBehavior::Last, 603 MergeBehavior::Module,
596 ); 604 );
597} 605}
598 606
@@ -601,7 +609,7 @@ fn merge_last_fail1() {
601 check_merge_only_fail( 609 check_merge_only_fail(
602 r"use foo::bar::{baz::{Qux, Fez}};", 610 r"use foo::bar::{baz::{Qux, Fez}};",
603 r"use foo::bar::baaz::{Quux, Feez};", 611 r"use foo::bar::baaz::{Quux, Feez};",
604 MergeBehavior::Last, 612 MergeBehavior::Module,
605 ); 613 );
606} 614}
607 615
@@ -610,7 +618,7 @@ fn merge_last_fail2() {
610 check_merge_only_fail( 618 check_merge_only_fail(
611 r"use foo::bar::baz::{Qux, Fez};", 619 r"use foo::bar::baz::{Qux, Fez};",
612 r"use foo::bar::{baaz::{Quux, Feez}};", 620 r"use foo::bar::{baaz::{Quux, Feez}};",
613 MergeBehavior::Last, 621 MergeBehavior::Module,
614 ); 622 );
615} 623}
616 624
@@ -619,7 +627,7 @@ fn merge_last_fail3() {
619 check_merge_only_fail( 627 check_merge_only_fail(
620 r"use foo::bar::baz::{Qux, Fez};", 628 r"use foo::bar::baz::{Qux, Fez};",
621 r"use foo::bar::baaz::{Quux, Feez};", 629 r"use foo::bar::baaz::{Quux, Feez};",
622 MergeBehavior::Last, 630 MergeBehavior::Module,
623 ); 631 );
624} 632}
625 633
@@ -648,12 +656,12 @@ fn check(
648 assert_eq_text!(ra_fixture_after, &result); 656 assert_eq_text!(ra_fixture_after, &result);
649} 657}
650 658
651fn check_full(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) { 659fn check_crate(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) {
652 check(path, ra_fixture_before, ra_fixture_after, Some(MergeBehavior::Full), false, true) 660 check(path, ra_fixture_before, ra_fixture_after, Some(MergeBehavior::Crate), false, true)
653} 661}
654 662
655fn check_last(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) { 663fn check_module(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) {
656 check(path, ra_fixture_before, ra_fixture_after, Some(MergeBehavior::Last), false, true) 664 check(path, ra_fixture_before, ra_fixture_after, Some(MergeBehavior::Module), false, true)
657} 665}
658 666
659fn check_none(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) { 667fn check_none(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) {
diff --git a/crates/ide_db/src/helpers/merge_imports.rs b/crates/ide_db/src/helpers/merge_imports.rs
index 3f5bbef7f..8fb40e837 100644
--- a/crates/ide_db/src/helpers/merge_imports.rs
+++ b/crates/ide_db/src/helpers/merge_imports.rs
@@ -2,26 +2,27 @@
2use std::cmp::Ordering; 2use std::cmp::Ordering;
3 3
4use itertools::{EitherOrBoth, Itertools}; 4use itertools::{EitherOrBoth, Itertools};
5use syntax::ast::{ 5use syntax::{
6 self, edit::AstNodeEdit, make, AstNode, AttrsOwner, PathSegmentKind, VisibilityOwner, 6 ast::{self, make, AstNode, AttrsOwner, PathSegmentKind, VisibilityOwner},
7 ted,
7}; 8};
8 9
9/// What type of merges are allowed. 10/// What type of merges are allowed.
10#[derive(Copy, Clone, Debug, PartialEq, Eq)] 11#[derive(Copy, Clone, Debug, PartialEq, Eq)]
11pub enum MergeBehavior { 12pub enum MergeBehavior {
12 /// Merge everything together creating deeply nested imports. 13 /// Merge imports from the same crate into a single use statement.
13 Full, 14 Crate,
14 /// Only merge the last import level, doesn't allow import nesting. 15 /// Merge imports from the same module into a single use statement.
15 Last, 16 Module,
16} 17}
17 18
18impl MergeBehavior { 19impl MergeBehavior {
19 #[inline] 20 #[inline]
20 fn is_tree_allowed(&self, tree: &ast::UseTree) -> bool { 21 fn is_tree_allowed(&self, tree: &ast::UseTree) -> bool {
21 match self { 22 match self {
22 MergeBehavior::Full => true, 23 MergeBehavior::Crate => true,
23 // only simple single segment paths are allowed 24 // only simple single segment paths are allowed
24 MergeBehavior::Last => { 25 MergeBehavior::Module => {
25 tree.use_tree_list().is_none() && tree.path().map(path_len) <= Some(1) 26 tree.use_tree_list().is_none() && tree.path().map(path_len) <= Some(1)
26 } 27 }
27 } 28 }
@@ -41,10 +42,12 @@ pub fn try_merge_imports(
41 return None; 42 return None;
42 } 43 }
43 44
45 let lhs = lhs.clone_subtree().clone_for_update();
44 let lhs_tree = lhs.use_tree()?; 46 let lhs_tree = lhs.use_tree()?;
45 let rhs_tree = rhs.use_tree()?; 47 let rhs_tree = rhs.use_tree()?;
46 let merged = try_merge_trees(&lhs_tree, &rhs_tree, merge_behavior)?; 48 let merged = try_merge_trees(&lhs_tree, &rhs_tree, merge_behavior)?;
47 Some(lhs.with_use_tree(merged).clone_for_update()) 49 ted::replace(lhs_tree.syntax(), merged.syntax());
50 Some(lhs)
48} 51}
49 52
50pub fn try_merge_trees( 53pub fn try_merge_trees(
@@ -65,7 +68,7 @@ pub fn try_merge_trees(
65 } else { 68 } else {
66 (lhs.split_prefix(&lhs_prefix), rhs.split_prefix(&rhs_prefix)) 69 (lhs.split_prefix(&lhs_prefix), rhs.split_prefix(&rhs_prefix))
67 }; 70 };
68 recursive_merge(&lhs, &rhs, merge) 71 recursive_merge(&lhs, &rhs, merge).map(|it| it.clone_for_update())
69} 72}
70 73
71/// Recursively "zips" together lhs and rhs. 74/// Recursively "zips" together lhs and rhs.
@@ -78,7 +81,8 @@ fn recursive_merge(
78 .use_tree_list() 81 .use_tree_list()
79 .into_iter() 82 .into_iter()
80 .flat_map(|list| list.use_trees()) 83 .flat_map(|list| list.use_trees())
81 // we use Option here to early return from this function(this is not the same as a `filter` op) 84 // We use Option here to early return from this function(this is not the
85 // same as a `filter` op).
82 .map(|tree| match merge.is_tree_allowed(&tree) { 86 .map(|tree| match merge.is_tree_allowed(&tree) {
83 true => Some(tree), 87 true => Some(tree),
84 false => None, 88 false => None,
@@ -111,8 +115,10 @@ fn recursive_merge(
111 let tree_is_self = |tree: ast::UseTree| { 115 let tree_is_self = |tree: ast::UseTree| {
112 tree.path().as_ref().map(path_is_self).unwrap_or(false) 116 tree.path().as_ref().map(path_is_self).unwrap_or(false)
113 }; 117 };
114 // check if only one of the two trees has a tree list, and whether that then contains `self` or not. 118 // Check if only one of the two trees has a tree list, and
115 // If this is the case we can skip this iteration since the path without the list is already included in the other one via `self` 119 // whether that then contains `self` or not. If this is the
120 // case we can skip this iteration since the path without
121 // the list is already included in the other one via `self`.
116 let tree_contains_self = |tree: &ast::UseTree| { 122 let tree_contains_self = |tree: &ast::UseTree| {
117 tree.use_tree_list() 123 tree.use_tree_list()
118 .map(|tree_list| tree_list.use_trees().any(tree_is_self)) 124 .map(|tree_list| tree_list.use_trees().any(tree_is_self))
@@ -127,9 +133,11 @@ fn recursive_merge(
127 _ => (), 133 _ => (),
128 } 134 }
129 135
130 // glob imports arent part of the use-tree lists so we need to special handle them here as well 136 // Glob imports aren't part of the use-tree lists so we need
131 // this special handling is only required for when we merge a module import into a glob import of said module 137 // to special handle them here as well this special handling
132 // see the `merge_self_glob` or `merge_mod_into_glob` tests 138 // is only required for when we merge a module import into a
139 // glob import of said module see the `merge_self_glob` or
140 // `merge_mod_into_glob` tests.
133 if lhs_t.star_token().is_some() || rhs_t.star_token().is_some() { 141 if lhs_t.star_token().is_some() || rhs_t.star_token().is_some() {
134 *lhs_t = make::use_tree( 142 *lhs_t = make::use_tree(
135 make::path_unqualified(make::path_segment_self()), 143 make::path_unqualified(make::path_segment_self()),
@@ -137,7 +145,7 @@ fn recursive_merge(
137 None, 145 None,
138 false, 146 false,
139 ); 147 );
140 use_trees.insert(idx, make::glob_use_tree()); 148 use_trees.insert(idx, make::use_tree_glob());
141 continue; 149 continue;
142 } 150 }
143 151
@@ -153,7 +161,7 @@ fn recursive_merge(
153 } 161 }
154 } 162 }
155 Err(_) 163 Err(_)
156 if merge == MergeBehavior::Last 164 if merge == MergeBehavior::Module
157 && use_trees.len() > 0 165 && use_trees.len() > 0
158 && rhs_t.use_tree_list().is_some() => 166 && rhs_t.use_tree_list().is_some() =>
159 { 167 {
@@ -165,11 +173,11 @@ fn recursive_merge(
165 } 173 }
166 } 174 }
167 175
168 Some(if let Some(old) = lhs.use_tree_list() { 176 let lhs = lhs.clone_subtree().clone_for_update();
169 lhs.replace_descendant(old, make::use_tree_list(use_trees)).clone_for_update() 177 if let Some(old) = lhs.use_tree_list() {
170 } else { 178 ted::replace(old.syntax(), make::use_tree_list(use_trees).syntax().clone_for_update());
171 lhs.clone() 179 }
172 }) 180 ast::UseTree::cast(lhs.syntax().clone_subtree())
173} 181}
174 182
175/// Traverses both paths until they differ, returning the common prefix of both. 183/// Traverses both paths until they differ, returning the common prefix of both.
diff --git a/crates/ide_db/src/search.rs b/crates/ide_db/src/search.rs
index 8f899ea56..67840602b 100644
--- a/crates/ide_db/src/search.rs
+++ b/crates/ide_db/src/search.rs
@@ -233,6 +233,13 @@ impl Definition {
233 }; 233 };
234 } 234 }
235 235
236 if let Definition::SelfType(impl_) = self {
237 return match impl_.source(db).map(|src| src.value.syntax().text_range()) {
238 Some(range) => SearchScope::file_range(FileRange { file_id, range }),
239 None => SearchScope::single_file(file_id),
240 };
241 }
242
236 if let Definition::GenericParam(hir::GenericParam::LifetimeParam(param)) = self { 243 if let Definition::GenericParam(hir::GenericParam::LifetimeParam(param)) = self {
237 let range = match param.parent(db) { 244 let range = match param.parent(db) {
238 hir::GenericDef::Function(it) => { 245 hir::GenericDef::Function(it) => {
@@ -297,7 +304,7 @@ impl Definition {
297 } 304 }
298 305
299 pub fn usages<'a>(&'a self, sema: &'a Semantics<RootDatabase>) -> FindUsages<'a> { 306 pub fn usages<'a>(&'a self, sema: &'a Semantics<RootDatabase>) -> FindUsages<'a> {
300 FindUsages { def: self, sema, scope: None, include_self_kw_refs: false } 307 FindUsages { def: self, sema, scope: None, include_self_kw_refs: None }
301 } 308 }
302} 309}
303 310
@@ -305,12 +312,13 @@ pub struct FindUsages<'a> {
305 def: &'a Definition, 312 def: &'a Definition,
306 sema: &'a Semantics<'a, RootDatabase>, 313 sema: &'a Semantics<'a, RootDatabase>,
307 scope: Option<SearchScope>, 314 scope: Option<SearchScope>,
308 include_self_kw_refs: bool, 315 include_self_kw_refs: Option<hir::Type>,
309} 316}
310 317
311impl<'a> FindUsages<'a> { 318impl<'a> FindUsages<'a> {
312 pub fn include_self_kw_refs(mut self, include: bool) -> FindUsages<'a> { 319 /// Enable searching for `Self` when the definition is a type.
313 self.include_self_kw_refs = include; 320 pub fn include_self_refs(mut self) -> FindUsages<'a> {
321 self.include_self_kw_refs = def_to_ty(self.sema, self.def);
314 self 322 self
315 } 323 }
316 324
@@ -354,13 +362,18 @@ impl<'a> FindUsages<'a> {
354 } 362 }
355 }; 363 };
356 364
357 let name = match self.def.name(sema.db) { 365 let name = self.def.name(sema.db).or_else(|| {
358 Some(it) => it.to_string(), 366 self.include_self_kw_refs.as_ref().and_then(|ty| {
367 ty.as_adt()
368 .map(|adt| adt.name(self.sema.db))
369 .or_else(|| ty.as_builtin().map(|builtin| builtin.name()))
370 })
371 });
372 let name = match name {
373 Some(name) => name.to_string(),
359 None => return, 374 None => return,
360 }; 375 };
361 376 let name = name.as_str();
362 let pat = name.as_str();
363 let search_for_self = self.include_self_kw_refs;
364 377
365 for (file_id, search_range) in search_scope { 378 for (file_id, search_range) in search_scope {
366 let text = sema.db.file_text(file_id); 379 let text = sema.db.file_text(file_id);
@@ -369,51 +382,63 @@ impl<'a> FindUsages<'a> {
369 382
370 let tree = Lazy::new(|| sema.parse(file_id).syntax().clone()); 383 let tree = Lazy::new(|| sema.parse(file_id).syntax().clone());
371 384
372 let mut handle_match = |idx: usize| -> bool { 385 for (idx, _) in text.match_indices(name) {
373 let offset: TextSize = idx.try_into().unwrap(); 386 let offset: TextSize = idx.try_into().unwrap();
374 if !search_range.contains_inclusive(offset) { 387 if !search_range.contains_inclusive(offset) {
375 return false; 388 continue;
376 } 389 }
377 390
378 if let Some(name) = sema.find_node_at_offset_with_descend(&tree, offset) { 391 if let Some(name) = sema.find_node_at_offset_with_descend(&tree, offset) {
379 match name { 392 if match name {
380 ast::NameLike::NameRef(name_ref) => { 393 ast::NameLike::NameRef(name_ref) => self.found_name_ref(&name_ref, sink),
381 if self.found_name_ref(&name_ref, sink) { 394 ast::NameLike::Name(name) => self.found_name(&name, sink),
382 return true; 395 ast::NameLike::Lifetime(lifetime) => self.found_lifetime(&lifetime, sink),
383 } 396 } {
384 } 397 return;
385 ast::NameLike::Name(name) => {
386 if self.found_name(&name, sink) {
387 return true;
388 }
389 }
390 ast::NameLike::Lifetime(lifetime) => {
391 if self.found_lifetime(&lifetime, sink) {
392 return true;
393 }
394 }
395 } 398 }
396 } 399 }
397
398 return false;
399 };
400
401 for (idx, _) in text.match_indices(pat) {
402 if handle_match(idx) {
403 return;
404 }
405 } 400 }
406 401 if let Some(self_ty) = &self.include_self_kw_refs {
407 if search_for_self {
408 for (idx, _) in text.match_indices("Self") { 402 for (idx, _) in text.match_indices("Self") {
409 if handle_match(idx) { 403 let offset: TextSize = idx.try_into().unwrap();
410 return; 404 if !search_range.contains_inclusive(offset) {
405 continue;
406 }
407
408 if let Some(ast::NameLike::NameRef(name_ref)) =
409 sema.find_node_at_offset_with_descend(&tree, offset)
410 {
411 if self.found_self_ty_name_ref(&self_ty, &name_ref, sink) {
412 return;
413 }
411 } 414 }
412 } 415 }
413 } 416 }
414 } 417 }
415 } 418 }
416 419
420 fn found_self_ty_name_ref(
421 &self,
422 self_ty: &hir::Type,
423 name_ref: &ast::NameRef,
424 sink: &mut dyn FnMut(FileId, FileReference) -> bool,
425 ) -> bool {
426 match NameRefClass::classify(self.sema, &name_ref) {
427 Some(NameRefClass::Definition(Definition::SelfType(impl_)))
428 if impl_.self_ty(self.sema.db) == *self_ty =>
429 {
430 let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax());
431 let reference = FileReference {
432 range,
433 name: ast::NameLike::NameRef(name_ref.clone()),
434 access: None,
435 };
436 sink(file_id, reference)
437 }
438 _ => false,
439 }
440 }
441
417 fn found_lifetime( 442 fn found_lifetime(
418 &self, 443 &self,
419 lifetime: &ast::Lifetime, 444 lifetime: &ast::Lifetime,
@@ -429,7 +454,7 @@ impl<'a> FindUsages<'a> {
429 }; 454 };
430 sink(file_id, reference) 455 sink(file_id, reference)
431 } 456 }
432 _ => false, // not a usage 457 _ => false,
433 } 458 }
434 } 459 }
435 460
@@ -448,42 +473,35 @@ impl<'a> FindUsages<'a> {
448 }; 473 };
449 sink(file_id, reference) 474 sink(file_id, reference)
450 } 475 }
451 Some(NameRefClass::Definition(Definition::SelfType(impl_))) => { 476 Some(NameRefClass::Definition(def)) if self.include_self_kw_refs.is_some() => {
452 let ty = impl_.self_ty(self.sema.db); 477 if self.include_self_kw_refs == def_to_ty(self.sema, &def) {
453 478 let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax());
454 if let Some(adt) = ty.as_adt() { 479 let reference = FileReference {
455 if &Definition::ModuleDef(ModuleDef::Adt(adt)) == self.def { 480 range,
456 let FileRange { file_id, range } = 481 name: ast::NameLike::NameRef(name_ref.clone()),
457 self.sema.original_range(name_ref.syntax()); 482 access: reference_access(&def, &name_ref),
458 let reference = FileReference { 483 };
459 range, 484 sink(file_id, reference)
460 name: ast::NameLike::NameRef(name_ref.clone()), 485 } else {
461 access: None, 486 false
462 };
463 return sink(file_id, reference);
464 }
465 } 487 }
466
467 false
468 } 488 }
469 Some(NameRefClass::FieldShorthand { local_ref: local, field_ref: field }) => { 489 Some(NameRefClass::FieldShorthand { local_ref: local, field_ref: field }) => {
470 let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax()); 490 let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax());
471 let reference = match self.def { 491 let access = match self.def {
472 Definition::Field(_) if &field == self.def => FileReference { 492 Definition::Field(_) if &field == self.def => {
473 range, 493 reference_access(&field, &name_ref)
474 name: ast::NameLike::NameRef(name_ref.clone()), 494 }
475 access: reference_access(&field, &name_ref), 495 Definition::Local(l) if &local == l => {
476 }, 496 reference_access(&Definition::Local(local), &name_ref)
477 Definition::Local(l) if &local == l => FileReference { 497 }
478 range, 498 _ => return false,
479 name: ast::NameLike::NameRef(name_ref.clone()),
480 access: reference_access(&Definition::Local(local), &name_ref),
481 },
482 _ => return false, // not a usage
483 }; 499 };
500 let reference =
501 FileReference { range, name: ast::NameLike::NameRef(name_ref.clone()), access };
484 sink(file_id, reference) 502 sink(file_id, reference)
485 } 503 }
486 _ => false, // not a usage 504 _ => false,
487 } 505 }
488 } 506 }
489 507
@@ -513,11 +531,30 @@ impl<'a> FindUsages<'a> {
513 FileReference { range, name: ast::NameLike::Name(name.clone()), access: None }; 531 FileReference { range, name: ast::NameLike::Name(name.clone()), access: None };
514 sink(file_id, reference) 532 sink(file_id, reference)
515 } 533 }
516 _ => false, // not a usage 534 _ => false,
517 } 535 }
518 } 536 }
519} 537}
520 538
539fn def_to_ty(sema: &Semantics<RootDatabase>, def: &Definition) -> Option<hir::Type> {
540 match def {
541 Definition::ModuleDef(def) => match def {
542 ModuleDef::Adt(adt) => Some(adt.ty(sema.db)),
543 ModuleDef::TypeAlias(it) => Some(it.ty(sema.db)),
544 ModuleDef::BuiltinType(it) => {
545 let graph = sema.db.crate_graph();
546 let krate = graph.iter().next()?;
547 let root_file = graph[krate].root_file_id;
548 let module = sema.to_module_def(root_file)?;
549 Some(it.ty(sema.db, module))
550 }
551 _ => None,
552 },
553 Definition::SelfType(it) => Some(it.self_ty(sema.db)),
554 _ => None,
555 }
556}
557
521fn reference_access(def: &Definition, name_ref: &ast::NameRef) -> Option<ReferenceAccess> { 558fn reference_access(def: &Definition, name_ref: &ast::NameRef) -> Option<ReferenceAccess> {
522 // Only Locals and Fields have accesses for now. 559 // Only Locals and Fields have accesses for now.
523 if !matches!(def, Definition::Local(_) | Definition::Field(_)) { 560 if !matches!(def, Definition::Local(_) | Definition::Field(_)) {
diff --git a/crates/ide_db/src/ty_filter.rs b/crates/ide_db/src/ty_filter.rs
index 988ecd060..00678bf3e 100644
--- a/crates/ide_db/src/ty_filter.rs
+++ b/crates/ide_db/src/ty_filter.rs
@@ -43,7 +43,7 @@ impl TryEnum {
43 pub fn sad_pattern(self) -> ast::Pat { 43 pub fn sad_pattern(self) -> ast::Pat {
44 match self { 44 match self {
45 TryEnum::Result => make::tuple_struct_pat( 45 TryEnum::Result => make::tuple_struct_pat(
46 make::path_unqualified(make::path_segment(make::name_ref("Err"))), 46 make::ext::ident_path("Err"),
47 iter::once(make::wildcard_pat().into()), 47 iter::once(make::wildcard_pat().into()),
48 ) 48 )
49 .into(), 49 .into(),
@@ -54,12 +54,12 @@ impl TryEnum {
54 pub fn happy_pattern(self) -> ast::Pat { 54 pub fn happy_pattern(self) -> ast::Pat {
55 match self { 55 match self {
56 TryEnum::Result => make::tuple_struct_pat( 56 TryEnum::Result => make::tuple_struct_pat(
57 make::path_unqualified(make::path_segment(make::name_ref("Ok"))), 57 make::ext::ident_path("Ok"),
58 iter::once(make::wildcard_pat().into()), 58 iter::once(make::wildcard_pat().into()),
59 ) 59 )
60 .into(), 60 .into(),
61 TryEnum::Option => make::tuple_struct_pat( 61 TryEnum::Option => make::tuple_struct_pat(
62 make::path_unqualified(make::path_segment(make::name_ref("Some"))), 62 make::ext::ident_path("Some"),
63 iter::once(make::wildcard_pat().into()), 63 iter::once(make::wildcard_pat().into()),
64 ) 64 )
65 .into(), 65 .into(),
diff --git a/crates/parser/src/grammar/patterns.rs b/crates/parser/src/grammar/patterns.rs
index 3ab347834..f1d1f9eaa 100644
--- a/crates/parser/src/grammar/patterns.rs
+++ b/crates/parser/src/grammar/patterns.rs
@@ -83,7 +83,7 @@ fn pattern_single_r(p: &mut Parser, recovery_set: TokenSet) {
83} 83}
84 84
85const PAT_RECOVERY_SET: TokenSet = 85const PAT_RECOVERY_SET: TokenSet =
86 TokenSet::new(&[T![let], T![if], T![while], T![loop], T![match], T![')'], T![,]]); 86 TokenSet::new(&[T![let], T![if], T![while], T![loop], T![match], T![')'], T![,], T![=]]);
87 87
88fn atom_pat(p: &mut Parser, recovery_set: TokenSet) -> Option<CompletedMarker> { 88fn atom_pat(p: &mut Parser, recovery_set: TokenSet) -> Option<CompletedMarker> {
89 let m = match p.nth(0) { 89 let m = match p.nth(0) {
diff --git a/crates/project_model/src/build_data.rs b/crates/project_model/src/build_data.rs
index 7b88dca63..3aa546980 100644
--- a/crates/project_model/src/build_data.rs
+++ b/crates/project_model/src/build_data.rs
@@ -143,6 +143,7 @@ impl WorkspaceBuildData {
143 cmd.env("RA_RUSTC_WRAPPER", "1"); 143 cmd.env("RA_RUSTC_WRAPPER", "1");
144 } 144 }
145 145
146 cmd.current_dir(cargo_toml.parent().unwrap());
146 cmd.args(&["check", "--quiet", "--workspace", "--message-format=json", "--manifest-path"]) 147 cmd.args(&["check", "--quiet", "--workspace", "--message-format=json", "--manifest-path"])
147 .arg(cargo_toml.as_ref()); 148 .arg(cargo_toml.as_ref());
148 149
diff --git a/crates/project_model/src/cargo_workspace.rs b/crates/project_model/src/cargo_workspace.rs
index bc6e20341..4a4996cf4 100644
--- a/crates/project_model/src/cargo_workspace.rs
+++ b/crates/project_model/src/cargo_workspace.rs
@@ -119,6 +119,32 @@ pub struct RustAnalyzerPackageMetaData {
119pub struct PackageDependency { 119pub struct PackageDependency {
120 pub pkg: Package, 120 pub pkg: Package,
121 pub name: String, 121 pub name: String,
122 pub kind: DepKind,
123}
124
125#[derive(Debug, Clone, Eq, PartialEq)]
126pub enum DepKind {
127 /// Available to the library, binary, and dev targets in the package (but not the build script).
128 Normal,
129 /// Available only to test and bench targets (and the library target, when built with `cfg(test)`).
130 Dev,
131 /// Available only to the build script target.
132 Build,
133}
134
135impl DepKind {
136 fn new(list: &[cargo_metadata::DepKindInfo]) -> Self {
137 for info in list {
138 match info.kind {
139 cargo_metadata::DependencyKind::Normal => return Self::Normal,
140 cargo_metadata::DependencyKind::Development => return Self::Dev,
141 cargo_metadata::DependencyKind::Build => return Self::Build,
142 cargo_metadata::DependencyKind::Unknown => continue,
143 }
144 }
145
146 Self::Normal
147 }
122} 148}
123 149
124/// Information associated with a package's target 150/// Information associated with a package's target
@@ -144,6 +170,7 @@ pub enum TargetKind {
144 Example, 170 Example,
145 Test, 171 Test,
146 Bench, 172 Bench,
173 BuildScript,
147 Other, 174 Other,
148} 175}
149 176
@@ -155,6 +182,7 @@ impl TargetKind {
155 "test" => TargetKind::Test, 182 "test" => TargetKind::Test,
156 "bench" => TargetKind::Bench, 183 "bench" => TargetKind::Bench,
157 "example" => TargetKind::Example, 184 "example" => TargetKind::Example,
185 "custom-build" => TargetKind::BuildScript,
158 "proc-macro" => TargetKind::Lib, 186 "proc-macro" => TargetKind::Lib,
159 _ if kind.contains("lib") => TargetKind::Lib, 187 _ if kind.contains("lib") => TargetKind::Lib,
160 _ => continue, 188 _ => continue,
@@ -201,31 +229,12 @@ impl CargoWorkspace {
201 if let Some(parent) = cargo_toml.parent() { 229 if let Some(parent) = cargo_toml.parent() {
202 meta.current_dir(parent.to_path_buf()); 230 meta.current_dir(parent.to_path_buf());
203 } 231 }
204 let target = if let Some(target) = config.target.as_ref() { 232 let target = if let Some(target) = &config.target {
205 Some(target.clone()) 233 Some(target.clone())
234 } else if let stdout @ Some(_) = cargo_config_build_target(cargo_toml) {
235 stdout
206 } else { 236 } else {
207 // cargo metadata defaults to giving information for _all_ targets. 237 rustc_discover_host_triple(cargo_toml)
208 // In the absence of a preference from the user, we use the host platform.
209 let mut rustc = Command::new(toolchain::rustc());
210 rustc.current_dir(cargo_toml.parent().unwrap()).arg("-vV");
211 log::debug!("Discovering host platform by {:?}", rustc);
212 match utf8_stdout(rustc) {
213 Ok(stdout) => {
214 let field = "host: ";
215 let target = stdout.lines().find_map(|l| l.strip_prefix(field));
216 if let Some(target) = target {
217 Some(target.to_string())
218 } else {
219 // If we fail to resolve the host platform, it's not the end of the world.
220 log::info!("rustc -vV did not report host platform, got:\n{}", stdout);
221 None
222 }
223 }
224 Err(e) => {
225 log::warn!("Failed to discover host platform: {}", e);
226 None
227 }
228 }
229 }; 238 };
230 if let Some(target) = target { 239 if let Some(target) = target {
231 meta.other_options(vec![String::from("--filter-platform"), target]); 240 meta.other_options(vec![String::from("--filter-platform"), target]);
@@ -320,7 +329,11 @@ impl CargoWorkspace {
320 continue; 329 continue;
321 } 330 }
322 }; 331 };
323 let dep = PackageDependency { name: dep_node.name, pkg }; 332 let dep = PackageDependency {
333 name: dep_node.name,
334 pkg,
335 kind: DepKind::new(&dep_node.dep_kinds),
336 };
324 packages[source].dependencies.push(dep); 337 packages[source].dependencies.push(dep);
325 } 338 }
326 packages[source].active_features.extend(node.features); 339 packages[source].active_features.extend(node.features);
@@ -368,3 +381,43 @@ impl CargoWorkspace {
368 self.packages.iter().filter(|(_, v)| v.name == name).count() == 1 381 self.packages.iter().filter(|(_, v)| v.name == name).count() == 1
369 } 382 }
370} 383}
384
385fn rustc_discover_host_triple(cargo_toml: &AbsPath) -> Option<String> {
386 let mut rustc = Command::new(toolchain::rustc());
387 rustc.current_dir(cargo_toml.parent().unwrap()).arg("-vV");
388 log::debug!("Discovering host platform by {:?}", rustc);
389 match utf8_stdout(rustc) {
390 Ok(stdout) => {
391 let field = "host: ";
392 let target = stdout.lines().find_map(|l| l.strip_prefix(field));
393 if let Some(target) = target {
394 Some(target.to_string())
395 } else {
396 // If we fail to resolve the host platform, it's not the end of the world.
397 log::info!("rustc -vV did not report host platform, got:\n{}", stdout);
398 None
399 }
400 }
401 Err(e) => {
402 log::warn!("Failed to discover host platform: {}", e);
403 None
404 }
405 }
406}
407
408fn cargo_config_build_target(cargo_toml: &AbsPath) -> Option<String> {
409 let mut cargo_config = Command::new(toolchain::cargo());
410 cargo_config
411 .current_dir(cargo_toml.parent().unwrap())
412 .args(&["-Z", "unstable-options", "config", "get", "build.target"])
413 .env("RUSTC_BOOTSTRAP", "1");
414 // if successful we receive `build.target = "target-triple"`
415 log::debug!("Discovering cargo config target by {:?}", cargo_config);
416 match utf8_stdout(cargo_config) {
417 Ok(stdout) => stdout
418 .strip_prefix("build.target = \"")
419 .and_then(|stdout| stdout.strip_suffix('"'))
420 .map(ToOwned::to_owned),
421 Err(_) => None,
422 }
423}
diff --git a/crates/project_model/src/rustc_cfg.rs b/crates/project_model/src/rustc_cfg.rs
index 312708575..012eab256 100644
--- a/crates/project_model/src/rustc_cfg.rs
+++ b/crates/project_model/src/rustc_cfg.rs
@@ -2,9 +2,12 @@
2 2
3use std::process::Command; 3use std::process::Command;
4 4
5use anyhow::Result;
6use paths::AbsPath;
7
5use crate::{cfg_flag::CfgFlag, utf8_stdout}; 8use crate::{cfg_flag::CfgFlag, utf8_stdout};
6 9
7pub(crate) fn get(target: Option<&str>) -> Vec<CfgFlag> { 10pub(crate) fn get(cargo_toml: Option<&AbsPath>, target: Option<&str>) -> Vec<CfgFlag> {
8 let _p = profile::span("rustc_cfg::get"); 11 let _p = profile::span("rustc_cfg::get");
9 let mut res = Vec::with_capacity(6 * 2 + 1); 12 let mut res = Vec::with_capacity(6 * 2 + 1);
10 13
@@ -16,19 +19,39 @@ pub(crate) fn get(target: Option<&str>) -> Vec<CfgFlag> {
16 } 19 }
17 } 20 }
18 21
19 let rustc_cfgs = { 22 match get_rust_cfgs(cargo_toml, target) {
20 let mut cmd = Command::new(toolchain::rustc());
21 cmd.args(&["--print", "cfg", "-O"]);
22 if let Some(target) = target {
23 cmd.args(&["--target", target]);
24 }
25 utf8_stdout(cmd)
26 };
27
28 match rustc_cfgs {
29 Ok(rustc_cfgs) => res.extend(rustc_cfgs.lines().map(|it| it.parse().unwrap())), 23 Ok(rustc_cfgs) => res.extend(rustc_cfgs.lines().map(|it| it.parse().unwrap())),
30 Err(e) => log::error!("failed to get rustc cfgs: {:#}", e), 24 Err(e) => log::error!("failed to get rustc cfgs: {:#}", e),
31 } 25 }
32 26
33 res 27 res
34} 28}
29
30fn get_rust_cfgs(cargo_toml: Option<&AbsPath>, target: Option<&str>) -> Result<String> {
31 let cargo_rust_cfgs = match cargo_toml {
32 Some(cargo_toml) => {
33 let mut cargo_config = Command::new(toolchain::cargo());
34 cargo_config
35 .current_dir(cargo_toml.parent().unwrap())
36 .args(&["-Z", "unstable-options", "rustc", "--print", "cfg"])
37 .env("RUSTC_BOOTSTRAP", "1");
38 if let Some(target) = target {
39 cargo_config.args(&["--target", target]);
40 }
41 utf8_stdout(cargo_config).ok()
42 }
43 None => None,
44 };
45 match cargo_rust_cfgs {
46 Some(stdout) => Ok(stdout),
47 None => {
48 // using unstable cargo features failed, fall back to using plain rustc
49 let mut cmd = Command::new(toolchain::rustc());
50 cmd.args(&["--print", "cfg", "-O"]);
51 if let Some(target) = target {
52 cmd.args(&["--target", target]);
53 }
54 utf8_stdout(cmd)
55 }
56 }
57}
diff --git a/crates/project_model/src/workspace.rs b/crates/project_model/src/workspace.rs
index 2fcd0f8fa..607e62ea5 100644
--- a/crates/project_model/src/workspace.rs
+++ b/crates/project_model/src/workspace.rs
@@ -6,6 +6,7 @@ use std::{collections::VecDeque, fmt, fs, path::Path, process::Command};
6 6
7use anyhow::{Context, Result}; 7use anyhow::{Context, Result};
8use base_db::{CrateDisplayName, CrateGraph, CrateId, CrateName, Edition, Env, FileId, ProcMacro}; 8use base_db::{CrateDisplayName, CrateGraph, CrateId, CrateName, Edition, Env, FileId, ProcMacro};
9use cargo_workspace::DepKind;
9use cfg::CfgOptions; 10use cfg::CfgOptions;
10use paths::{AbsPath, AbsPathBuf}; 11use paths::{AbsPath, AbsPathBuf};
11use proc_macro_api::ProcMacroClient; 12use proc_macro_api::ProcMacroClient;
@@ -143,7 +144,8 @@ impl ProjectWorkspace {
143 } else { 144 } else {
144 None 145 None
145 }; 146 };
146 let rustc_cfg = rustc_cfg::get(config.target.as_deref()); 147
148 let rustc_cfg = rustc_cfg::get(Some(&cargo_toml), config.target.as_deref());
147 ProjectWorkspace::Cargo { cargo, sysroot, rustc, rustc_cfg } 149 ProjectWorkspace::Cargo { cargo, sysroot, rustc, rustc_cfg }
148 } 150 }
149 }; 151 };
@@ -159,7 +161,7 @@ impl ProjectWorkspace {
159 Some(path) => Some(Sysroot::load(path)?), 161 Some(path) => Some(Sysroot::load(path)?),
160 None => None, 162 None => None,
161 }; 163 };
162 let rustc_cfg = rustc_cfg::get(target); 164 let rustc_cfg = rustc_cfg::get(None, target);
163 Ok(ProjectWorkspace::Json { project: project_json, sysroot, rustc_cfg }) 165 Ok(ProjectWorkspace::Json { project: project_json, sysroot, rustc_cfg })
164 } 166 }
165 167
@@ -310,7 +312,7 @@ fn project_json_to_crate_graph(
310 312
311 let target_cfgs = match krate.target.as_deref() { 313 let target_cfgs = match krate.target.as_deref() {
312 Some(target) => { 314 Some(target) => {
313 cfg_cache.entry(target).or_insert_with(|| rustc_cfg::get(Some(target))) 315 cfg_cache.entry(target).or_insert_with(|| rustc_cfg::get(None, Some(target)))
314 } 316 }
315 None => &rustc_cfg, 317 None => &rustc_cfg,
316 }; 318 };
@@ -389,6 +391,7 @@ fn cargo_to_crate_graph(
389 &cfg_options, 391 &cfg_options,
390 proc_macro_loader, 392 proc_macro_loader,
391 file_id, 393 file_id,
394 &cargo[tgt].name,
392 ); 395 );
393 if cargo[tgt].kind == TargetKind::Lib { 396 if cargo[tgt].kind == TargetKind::Lib {
394 lib_tgt = Some((crate_id, cargo[tgt].name.clone())); 397 lib_tgt = Some((crate_id, cargo[tgt].name.clone()));
@@ -405,23 +408,25 @@ fn cargo_to_crate_graph(
405 } 408 }
406 } 409 }
407 410
408 pkg_crates.entry(pkg).or_insert_with(Vec::new).push(crate_id); 411 pkg_crates.entry(pkg).or_insert_with(Vec::new).push((crate_id, cargo[tgt].kind));
409 } 412 }
410 } 413 }
411 414
412 // Set deps to the core, std and to the lib target of the current package 415 // Set deps to the core, std and to the lib target of the current package
413 for &from in pkg_crates.get(&pkg).into_iter().flatten() { 416 for (from, kind) in pkg_crates.get(&pkg).into_iter().flatten() {
414 if let Some((to, name)) = lib_tgt.clone() { 417 if let Some((to, name)) = lib_tgt.clone() {
415 if to != from { 418 if to != *from && *kind != TargetKind::BuildScript {
419 // (build script can not depend on its library target)
420
416 // For root projects with dashes in their name, 421 // For root projects with dashes in their name,
417 // cargo metadata does not do any normalization, 422 // cargo metadata does not do any normalization,
418 // so we do it ourselves currently 423 // so we do it ourselves currently
419 let name = CrateName::normalize_dashes(&name); 424 let name = CrateName::normalize_dashes(&name);
420 add_dep(&mut crate_graph, from, name, to); 425 add_dep(&mut crate_graph, *from, name, to);
421 } 426 }
422 } 427 }
423 for (name, krate) in public_deps.iter() { 428 for (name, krate) in public_deps.iter() {
424 add_dep(&mut crate_graph, from, name.clone(), *krate); 429 add_dep(&mut crate_graph, *from, name.clone(), *krate);
425 } 430 }
426 } 431 }
427 } 432 }
@@ -432,8 +437,17 @@ fn cargo_to_crate_graph(
432 for dep in cargo[pkg].dependencies.iter() { 437 for dep in cargo[pkg].dependencies.iter() {
433 let name = CrateName::new(&dep.name).unwrap(); 438 let name = CrateName::new(&dep.name).unwrap();
434 if let Some(&to) = pkg_to_lib_crate.get(&dep.pkg) { 439 if let Some(&to) = pkg_to_lib_crate.get(&dep.pkg) {
435 for &from in pkg_crates.get(&pkg).into_iter().flatten() { 440 for (from, kind) in pkg_crates.get(&pkg).into_iter().flatten() {
436 add_dep(&mut crate_graph, from, name.clone(), to) 441 if dep.kind == DepKind::Build && *kind != TargetKind::BuildScript {
442 // Only build scripts may depend on build dependencies.
443 continue;
444 }
445 if dep.kind != DepKind::Build && *kind == TargetKind::BuildScript {
446 // Build scripts may only depend on build dependencies.
447 continue;
448 }
449
450 add_dep(&mut crate_graph, *from, name.clone(), to)
437 } 451 }
438 } 452 }
439 } 453 }
@@ -470,7 +484,7 @@ fn handle_rustc_crates(
470 pkg_to_lib_crate: &mut FxHashMap<la_arena::Idx<crate::PackageData>, CrateId>, 484 pkg_to_lib_crate: &mut FxHashMap<la_arena::Idx<crate::PackageData>, CrateId>,
471 public_deps: &[(CrateName, CrateId)], 485 public_deps: &[(CrateName, CrateId)],
472 cargo: &CargoWorkspace, 486 cargo: &CargoWorkspace,
473 pkg_crates: &FxHashMap<la_arena::Idx<crate::PackageData>, Vec<CrateId>>, 487 pkg_crates: &FxHashMap<la_arena::Idx<crate::PackageData>, Vec<(CrateId, TargetKind)>>,
474) { 488) {
475 let mut rustc_pkg_crates = FxHashMap::default(); 489 let mut rustc_pkg_crates = FxHashMap::default();
476 // The root package of the rustc-dev component is rustc_driver, so we match that 490 // The root package of the rustc-dev component is rustc_driver, so we match that
@@ -504,6 +518,7 @@ fn handle_rustc_crates(
504 &cfg_options, 518 &cfg_options,
505 proc_macro_loader, 519 proc_macro_loader,
506 file_id, 520 file_id,
521 &rustc_workspace[tgt].name,
507 ); 522 );
508 pkg_to_lib_crate.insert(pkg, crate_id); 523 pkg_to_lib_crate.insert(pkg, crate_id);
509 // Add dependencies on core / std / alloc for this crate 524 // Add dependencies on core / std / alloc for this crate
@@ -538,13 +553,13 @@ fn handle_rustc_crates(
538 if !package.metadata.rustc_private { 553 if !package.metadata.rustc_private {
539 continue; 554 continue;
540 } 555 }
541 for &from in pkg_crates.get(&pkg).into_iter().flatten() { 556 for (from, _) in pkg_crates.get(&pkg).into_iter().flatten() {
542 // Avoid creating duplicate dependencies 557 // Avoid creating duplicate dependencies
543 // This avoids the situation where `from` depends on e.g. `arrayvec`, but 558 // This avoids the situation where `from` depends on e.g. `arrayvec`, but
544 // `rust_analyzer` thinks that it should use the one from the `rustcSource` 559 // `rust_analyzer` thinks that it should use the one from the `rustcSource`
545 // instead of the one from `crates.io` 560 // instead of the one from `crates.io`
546 if !crate_graph[from].dependencies.iter().any(|d| d.name == name) { 561 if !crate_graph[*from].dependencies.iter().any(|d| d.name == name) {
547 add_dep(crate_graph, from, name.clone(), to); 562 add_dep(crate_graph, *from, name.clone(), to);
548 } 563 }
549 } 564 }
550 } 565 }
@@ -559,6 +574,7 @@ fn add_target_crate_root(
559 cfg_options: &CfgOptions, 574 cfg_options: &CfgOptions,
560 proc_macro_loader: &dyn Fn(&Path) -> Vec<ProcMacro>, 575 proc_macro_loader: &dyn Fn(&Path) -> Vec<ProcMacro>,
561 file_id: FileId, 576 file_id: FileId,
577 cargo_name: &str,
562) -> CrateId { 578) -> CrateId {
563 let edition = pkg.edition; 579 let edition = pkg.edition;
564 let cfg_options = { 580 let cfg_options = {
@@ -585,7 +601,7 @@ fn add_target_crate_root(
585 .map(|it| proc_macro_loader(&it)) 601 .map(|it| proc_macro_loader(&it))
586 .unwrap_or_default(); 602 .unwrap_or_default();
587 603
588 let display_name = CrateDisplayName::from_canonical_name(pkg.name.clone()); 604 let display_name = CrateDisplayName::from_canonical_name(cargo_name.to_string());
589 let crate_id = crate_graph.add_crate_root( 605 let crate_id = crate_graph.add_crate_root(
590 file_id, 606 file_id,
591 edition, 607 edition,
diff --git a/crates/rust-analyzer/src/cargo_target_spec.rs b/crates/rust-analyzer/src/cargo_target_spec.rs
index 909c21532..f4cd43448 100644
--- a/crates/rust-analyzer/src/cargo_target_spec.rs
+++ b/crates/rust-analyzer/src/cargo_target_spec.rs
@@ -159,7 +159,7 @@ impl CargoTargetSpec {
159 TargetKind::Lib => { 159 TargetKind::Lib => {
160 buf.push("--lib".to_string()); 160 buf.push("--lib".to_string());
161 } 161 }
162 TargetKind::Other => (), 162 TargetKind::Other | TargetKind::BuildScript => (),
163 } 163 }
164 } 164 }
165} 165}
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index 123b63f53..905a6ee55 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -36,7 +36,7 @@ config_data! {
36 struct ConfigData { 36 struct ConfigData {
37 /// The strategy to use when inserting new imports or merging imports. 37 /// The strategy to use when inserting new imports or merging imports.
38 assist_importMergeBehavior | 38 assist_importMergeBehavior |
39 assist_importMergeBehaviour: MergeBehaviorDef = "\"full\"", 39 assist_importMergeBehaviour: MergeBehaviorDef = "\"crate\"",
40 /// The path structure for newly inserted paths to use. 40 /// The path structure for newly inserted paths to use.
41 assist_importPrefix: ImportPrefixDef = "\"plain\"", 41 assist_importPrefix: ImportPrefixDef = "\"plain\"",
42 /// Group inserted imports by the [following order](https://rust-analyzer.github.io/manual.html#auto-import). Groups are separated by newlines. 42 /// Group inserted imports by the [following order](https://rust-analyzer.github.io/manual.html#auto-import). Groups are separated by newlines.
@@ -614,8 +614,8 @@ impl Config {
614 InsertUseConfig { 614 InsertUseConfig {
615 merge: match self.data.assist_importMergeBehavior { 615 merge: match self.data.assist_importMergeBehavior {
616 MergeBehaviorDef::None => None, 616 MergeBehaviorDef::None => None,
617 MergeBehaviorDef::Full => Some(MergeBehavior::Full), 617 MergeBehaviorDef::Crate => Some(MergeBehavior::Crate),
618 MergeBehaviorDef::Last => Some(MergeBehavior::Last), 618 MergeBehaviorDef::Module => Some(MergeBehavior::Module),
619 }, 619 },
620 prefix_kind: match self.data.assist_importPrefix { 620 prefix_kind: match self.data.assist_importPrefix {
621 ImportPrefixDef::Plain => PrefixKind::Plain, 621 ImportPrefixDef::Plain => PrefixKind::Plain,
@@ -719,8 +719,10 @@ enum ManifestOrProjectJson {
719#[serde(rename_all = "snake_case")] 719#[serde(rename_all = "snake_case")]
720enum MergeBehaviorDef { 720enum MergeBehaviorDef {
721 None, 721 None,
722 Full, 722 #[serde(alias = "full")]
723 Last, 723 Crate,
724 #[serde(alias = "last")]
725 Module,
724} 726}
725 727
726#[derive(Deserialize, Debug, Clone)] 728#[derive(Deserialize, Debug, Clone)]
@@ -877,11 +879,11 @@ fn field_props(field: &str, ty: &str, doc: &[&str], default: &str) -> serde_json
877 }, 879 },
878 "MergeBehaviorDef" => set! { 880 "MergeBehaviorDef" => set! {
879 "type": "string", 881 "type": "string",
880 "enum": ["none", "full", "last"], 882 "enum": ["none", "crate", "module"],
881 "enumDescriptions": [ 883 "enumDescriptions": [
882 "No merging", 884 "Do not merge imports at all.",
883 "Merge all layers of the import trees", 885 "Merge imports from the same crate into a single `use` statement.",
884 "Only merge the last layer of the import trees" 886 "Merge imports from the same module into a single `use` statement."
885 ], 887 ],
886 }, 888 },
887 "ImportPrefixDef" => set! { 889 "ImportPrefixDef" => set! {
diff --git a/crates/rust-analyzer/src/dispatch.rs b/crates/rust-analyzer/src/dispatch.rs
index 81b85a269..baf2199d9 100644
--- a/crates/rust-analyzer/src/dispatch.rs
+++ b/crates/rust-analyzer/src/dispatch.rs
@@ -33,7 +33,12 @@ impl<'a> RequestDispatcher<'a> {
33 let world = panic::AssertUnwindSafe(&mut *self.global_state); 33 let world = panic::AssertUnwindSafe(&mut *self.global_state);
34 34
35 let response = panic::catch_unwind(move || { 35 let response = panic::catch_unwind(move || {
36 let _pctx = stdx::panic_context::enter(format!("request: {} {:#?}", R::METHOD, params)); 36 let _pctx = stdx::panic_context::enter(format!(
37 "\nversion: {}\nrequest: {} {:#?}",
38 env!("REV"),
39 R::METHOD,
40 params
41 ));
37 let result = f(world.0, params); 42 let result = f(world.0, params);
38 result_to_response::<R>(id, result) 43 result_to_response::<R>(id, result)
39 }) 44 })
@@ -61,8 +66,12 @@ impl<'a> RequestDispatcher<'a> {
61 let world = self.global_state.snapshot(); 66 let world = self.global_state.snapshot();
62 67
63 move || { 68 move || {
64 let _pctx = 69 let _pctx = stdx::panic_context::enter(format!(
65 stdx::panic_context::enter(format!("request: {} {:#?}", R::METHOD, params)); 70 "\nversion: {}\nrequest: {} {:#?}",
71 env!("REV"),
72 R::METHOD,
73 params
74 ));
66 let result = f(world, params); 75 let result = f(world, params);
67 Task::Response(result_to_response::<R>(id, result)) 76 Task::Response(result_to_response::<R>(id, result))
68 } 77 }
@@ -166,7 +175,11 @@ impl<'a> NotificationDispatcher<'a> {
166 return Ok(self); 175 return Ok(self);
167 } 176 }
168 }; 177 };
169 let _pctx = stdx::panic_context::enter(format!("notification: {}", N::METHOD)); 178 let _pctx = stdx::panic_context::enter(format!(
179 "\nversion: {}\nnotification: {}",
180 env!("REV"),
181 N::METHOD
182 ));
170 f(self.global_state, params)?; 183 f(self.global_state, params)?;
171 Ok(self) 184 Ok(self)
172 } 185 }
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs
index 78b558a21..85e45337c 100644
--- a/crates/rust-analyzer/src/handlers.rs
+++ b/crates/rust-analyzer/src/handlers.rs
@@ -3,8 +3,8 @@
3//! `ide` crate. 3//! `ide` crate.
4 4
5use std::{ 5use std::{
6 io::Write as _, 6 io::{Read, Write as _},
7 process::{self, Stdio}, 7 process::{self, Command, Stdio},
8}; 8};
9 9
10use ide::{ 10use ide::{
@@ -117,6 +117,24 @@ pub(crate) fn handle_view_hir(
117 Ok(res) 117 Ok(res)
118} 118}
119 119
120pub(crate) fn handle_view_crate_graph(snap: GlobalStateSnapshot, (): ()) -> Result<String> {
121 let _p = profile::span("handle_view_crate_graph");
122 let dot = snap.analysis.view_crate_graph()??;
123
124 // We shell out to `dot` to render to SVG, as there does not seem to be a pure-Rust renderer.
125 let child = Command::new("dot")
126 .arg("-Tsvg")
127 .stdin(Stdio::piped())
128 .stdout(Stdio::piped())
129 .spawn()
130 .map_err(|err| format!("failed to spawn `dot`: {}", err))?;
131 child.stdin.unwrap().write_all(dot.as_bytes())?;
132
133 let mut svg = String::new();
134 child.stdout.unwrap().read_to_string(&mut svg)?;
135 Ok(svg)
136}
137
120pub(crate) fn handle_expand_macro( 138pub(crate) fn handle_expand_macro(
121 snap: GlobalStateSnapshot, 139 snap: GlobalStateSnapshot,
122 params: lsp_ext::ExpandMacroParams, 140 params: lsp_ext::ExpandMacroParams,
diff --git a/crates/rust-analyzer/src/integrated_benchmarks.rs b/crates/rust-analyzer/src/integrated_benchmarks.rs
index 3dcbe397a..ba2790acb 100644
--- a/crates/rust-analyzer/src/integrated_benchmarks.rs
+++ b/crates/rust-analyzer/src/integrated_benchmarks.rs
@@ -123,7 +123,7 @@ fn integrated_completion_benchmark() {
123 }; 123 };
124 124
125 { 125 {
126 let _it = stdx::timeit("unqualified path completion"); 126 let _p = profile::span("unqualified path completion");
127 let _span = profile::cpu_span(); 127 let _span = profile::cpu_span();
128 let analysis = host.analysis(); 128 let analysis = host.analysis();
129 let config = CompletionConfig { 129 let config = CompletionConfig {
@@ -133,7 +133,7 @@ fn integrated_completion_benchmark() {
133 add_call_argument_snippets: true, 133 add_call_argument_snippets: true,
134 snippet_cap: SnippetCap::new(true), 134 snippet_cap: SnippetCap::new(true),
135 insert_use: InsertUseConfig { 135 insert_use: InsertUseConfig {
136 merge: Some(MergeBehavior::Full), 136 merge: Some(MergeBehavior::Crate),
137 prefix_kind: hir::PrefixKind::ByCrate, 137 prefix_kind: hir::PrefixKind::ByCrate,
138 group: true, 138 group: true,
139 }, 139 },
@@ -156,7 +156,7 @@ fn integrated_completion_benchmark() {
156 }; 156 };
157 157
158 { 158 {
159 let _it = stdx::timeit("dot completion"); 159 let _p = profile::span("dot completion");
160 let _span = profile::cpu_span(); 160 let _span = profile::cpu_span();
161 let analysis = host.analysis(); 161 let analysis = host.analysis();
162 let config = CompletionConfig { 162 let config = CompletionConfig {
@@ -166,7 +166,7 @@ fn integrated_completion_benchmark() {
166 add_call_argument_snippets: true, 166 add_call_argument_snippets: true,
167 snippet_cap: SnippetCap::new(true), 167 snippet_cap: SnippetCap::new(true),
168 insert_use: InsertUseConfig { 168 insert_use: InsertUseConfig {
169 merge: Some(MergeBehavior::Full), 169 merge: Some(MergeBehavior::Crate),
170 prefix_kind: hir::PrefixKind::ByCrate, 170 prefix_kind: hir::PrefixKind::ByCrate,
171 group: true, 171 group: true,
172 }, 172 },
diff --git a/crates/rust-analyzer/src/lsp_ext.rs b/crates/rust-analyzer/src/lsp_ext.rs
index b8835a534..3bd098058 100644
--- a/crates/rust-analyzer/src/lsp_ext.rs
+++ b/crates/rust-analyzer/src/lsp_ext.rs
@@ -61,6 +61,14 @@ impl Request for ViewHir {
61 const METHOD: &'static str = "rust-analyzer/viewHir"; 61 const METHOD: &'static str = "rust-analyzer/viewHir";
62} 62}
63 63
64pub enum ViewCrateGraph {}
65
66impl Request for ViewCrateGraph {
67 type Params = ();
68 type Result = String;
69 const METHOD: &'static str = "rust-analyzer/viewCrateGraph";
70}
71
64pub enum ExpandMacro {} 72pub enum ExpandMacro {}
65 73
66impl Request for ExpandMacro { 74impl Request for ExpandMacro {
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs
index ce7ece559..c7bd7eee1 100644
--- a/crates/rust-analyzer/src/main_loop.rs
+++ b/crates/rust-analyzer/src/main_loop.rs
@@ -513,6 +513,7 @@ impl GlobalState {
513 .on::<lsp_ext::AnalyzerStatus>(handlers::handle_analyzer_status) 513 .on::<lsp_ext::AnalyzerStatus>(handlers::handle_analyzer_status)
514 .on::<lsp_ext::SyntaxTree>(handlers::handle_syntax_tree) 514 .on::<lsp_ext::SyntaxTree>(handlers::handle_syntax_tree)
515 .on::<lsp_ext::ViewHir>(handlers::handle_view_hir) 515 .on::<lsp_ext::ViewHir>(handlers::handle_view_hir)
516 .on::<lsp_ext::ViewCrateGraph>(handlers::handle_view_crate_graph)
516 .on::<lsp_ext::ExpandMacro>(handlers::handle_expand_macro) 517 .on::<lsp_ext::ExpandMacro>(handlers::handle_expand_macro)
517 .on::<lsp_ext::ParentModule>(handlers::handle_parent_module) 518 .on::<lsp_ext::ParentModule>(handlers::handle_parent_module)
518 .on::<lsp_ext::Runnables>(handlers::handle_runnables) 519 .on::<lsp_ext::Runnables>(handlers::handle_runnables)
diff --git a/crates/rust-analyzer/src/semantic_tokens.rs b/crates/rust-analyzer/src/semantic_tokens.rs
index ecab89b2a..c9d38693e 100644
--- a/crates/rust-analyzer/src/semantic_tokens.rs
+++ b/crates/rust-analyzer/src/semantic_tokens.rs
@@ -91,6 +91,7 @@ define_semantic_token_modifiers![
91 (INJECTED, "injected"), 91 (INJECTED, "injected"),
92 (MUTABLE, "mutable"), 92 (MUTABLE, "mutable"),
93 (CONSUMING, "consuming"), 93 (CONSUMING, "consuming"),
94 (ASYNC, "async"),
94 (UNSAFE, "unsafe"), 95 (UNSAFE, "unsafe"),
95 (ATTRIBUTE_MODIFIER, "attribute"), 96 (ATTRIBUTE_MODIFIER, "attribute"),
96 (TRAIT_MODIFIER, "trait"), 97 (TRAIT_MODIFIER, "trait"),
diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs
index 5f2dd418f..6dc9f82ab 100644
--- a/crates/rust-analyzer/src/to_proto.rs
+++ b/crates/rust-analyzer/src/to_proto.rs
@@ -426,7 +426,7 @@ fn semantic_token_type_and_modifiers(
426 let type_ = match highlight.tag { 426 let type_ = match highlight.tag {
427 HlTag::Symbol(symbol) => match symbol { 427 HlTag::Symbol(symbol) => match symbol {
428 SymbolKind::Module => lsp_types::SemanticTokenType::NAMESPACE, 428 SymbolKind::Module => lsp_types::SemanticTokenType::NAMESPACE,
429 SymbolKind::Impl => lsp_types::SemanticTokenType::TYPE, 429 SymbolKind::Impl => semantic_tokens::TYPE_ALIAS,
430 SymbolKind::Field => lsp_types::SemanticTokenType::PROPERTY, 430 SymbolKind::Field => lsp_types::SemanticTokenType::PROPERTY,
431 SymbolKind::TypeParam => lsp_types::SemanticTokenType::TYPE_PARAMETER, 431 SymbolKind::TypeParam => lsp_types::SemanticTokenType::TYPE_PARAMETER,
432 SymbolKind::ConstParam => semantic_tokens::CONST_PARAMETER, 432 SymbolKind::ConstParam => semantic_tokens::CONST_PARAMETER,
@@ -500,6 +500,7 @@ fn semantic_token_type_and_modifiers(
500 HlMod::ControlFlow => semantic_tokens::CONTROL_FLOW, 500 HlMod::ControlFlow => semantic_tokens::CONTROL_FLOW,
501 HlMod::Mutable => semantic_tokens::MUTABLE, 501 HlMod::Mutable => semantic_tokens::MUTABLE,
502 HlMod::Consuming => semantic_tokens::CONSUMING, 502 HlMod::Consuming => semantic_tokens::CONSUMING,
503 HlMod::Async => semantic_tokens::ASYNC,
503 HlMod::Unsafe => semantic_tokens::UNSAFE, 504 HlMod::Unsafe => semantic_tokens::UNSAFE,
504 HlMod::Callable => semantic_tokens::CALLABLE, 505 HlMod::Callable => semantic_tokens::CALLABLE,
505 HlMod::Static => lsp_types::SemanticTokenModifier::STATIC, 506 HlMod::Static => lsp_types::SemanticTokenModifier::STATIC,
diff --git a/crates/syntax/Cargo.toml b/crates/syntax/Cargo.toml
index c0bc59918..747f0b9eb 100644
--- a/crates/syntax/Cargo.toml
+++ b/crates/syntax/Cargo.toml
@@ -13,7 +13,7 @@ doctest = false
13[dependencies] 13[dependencies]
14cov-mark = { version = "1.1", features = ["thread-local"] } 14cov-mark = { version = "1.1", features = ["thread-local"] }
15itertools = "0.10.0" 15itertools = "0.10.0"
16rowan = "=0.13.0-pre.5" 16rowan = "=0.13.0-pre.6"
17rustc_lexer = { version = "716.0.0", package = "rustc-ap-rustc_lexer" } 17rustc_lexer = { version = "716.0.0", package = "rustc-ap-rustc_lexer" }
18rustc-hash = "1.1.0" 18rustc-hash = "1.1.0"
19arrayvec = "0.7" 19arrayvec = "0.7"
diff --git a/crates/syntax/src/algo.rs b/crates/syntax/src/algo.rs
index ba263be0d..241713c48 100644
--- a/crates/syntax/src/algo.rs
+++ b/crates/syntax/src/algo.rs
@@ -1,10 +1,6 @@
1//! FIXME: write short doc here 1//! FIXME: write short doc here
2 2
3use std::{ 3use std::{hash::BuildHasherDefault, ops::RangeInclusive};
4 fmt,
5 hash::BuildHasherDefault,
6 ops::{self, RangeInclusive},
7};
8 4
9use indexmap::IndexMap; 5use indexmap::IndexMap;
10use itertools::Itertools; 6use itertools::Itertools;
@@ -334,253 +330,6 @@ fn _replace_children(
334 with_children(parent, new_children) 330 with_children(parent, new_children)
335} 331}
336 332
337#[derive(Debug, PartialEq, Eq, Hash)]
338enum InsertPos {
339 FirstChildOf(SyntaxNode),
340 After(SyntaxElement),
341}
342
343#[derive(Default)]
344pub struct SyntaxRewriter<'a> {
345 //FIXME: add debug_assertions that all elements are in fact from the same file.
346 replacements: FxHashMap<SyntaxElement, Replacement>,
347 insertions: IndexMap<InsertPos, Vec<SyntaxElement>>,
348 _pd: std::marker::PhantomData<&'a ()>,
349}
350
351impl fmt::Debug for SyntaxRewriter<'_> {
352 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
353 f.debug_struct("SyntaxRewriter")
354 .field("replacements", &self.replacements)
355 .field("insertions", &self.insertions)
356 .finish()
357 }
358}
359
360impl SyntaxRewriter<'_> {
361 pub fn delete<T: Clone + Into<SyntaxElement>>(&mut self, what: &T) {
362 let what = what.clone().into();
363 let replacement = Replacement::Delete;
364 self.replacements.insert(what, replacement);
365 }
366 pub fn insert_before<T: Clone + Into<SyntaxElement>, U: Clone + Into<SyntaxElement>>(
367 &mut self,
368 before: &T,
369 what: &U,
370 ) {
371 let before = before.clone().into();
372 let pos = match before.prev_sibling_or_token() {
373 Some(sibling) => InsertPos::After(sibling),
374 None => match before.parent() {
375 Some(parent) => InsertPos::FirstChildOf(parent),
376 None => return,
377 },
378 };
379 self.insertions.entry(pos).or_insert_with(Vec::new).push(what.clone().into());
380 }
381 pub fn insert_after<T: Clone + Into<SyntaxElement>, U: Clone + Into<SyntaxElement>>(
382 &mut self,
383 after: &T,
384 what: &U,
385 ) {
386 self.insertions
387 .entry(InsertPos::After(after.clone().into()))
388 .or_insert_with(Vec::new)
389 .push(what.clone().into());
390 }
391 pub fn insert_as_first_child<T: Clone + Into<SyntaxNode>, U: Clone + Into<SyntaxElement>>(
392 &mut self,
393 parent: &T,
394 what: &U,
395 ) {
396 self.insertions
397 .entry(InsertPos::FirstChildOf(parent.clone().into()))
398 .or_insert_with(Vec::new)
399 .push(what.clone().into());
400 }
401 pub fn insert_many_before<
402 T: Clone + Into<SyntaxElement>,
403 U: IntoIterator<Item = SyntaxElement>,
404 >(
405 &mut self,
406 before: &T,
407 what: U,
408 ) {
409 let before = before.clone().into();
410 let pos = match before.prev_sibling_or_token() {
411 Some(sibling) => InsertPos::After(sibling),
412 None => match before.parent() {
413 Some(parent) => InsertPos::FirstChildOf(parent),
414 None => return,
415 },
416 };
417 self.insertions.entry(pos).or_insert_with(Vec::new).extend(what);
418 }
419 pub fn insert_many_after<
420 T: Clone + Into<SyntaxElement>,
421 U: IntoIterator<Item = SyntaxElement>,
422 >(
423 &mut self,
424 after: &T,
425 what: U,
426 ) {
427 self.insertions
428 .entry(InsertPos::After(after.clone().into()))
429 .or_insert_with(Vec::new)
430 .extend(what);
431 }
432 pub fn insert_many_as_first_children<
433 T: Clone + Into<SyntaxNode>,
434 U: IntoIterator<Item = SyntaxElement>,
435 >(
436 &mut self,
437 parent: &T,
438 what: U,
439 ) {
440 self.insertions
441 .entry(InsertPos::FirstChildOf(parent.clone().into()))
442 .or_insert_with(Vec::new)
443 .extend(what)
444 }
445 pub fn replace<T: Clone + Into<SyntaxElement>>(&mut self, what: &T, with: &T) {
446 let what = what.clone().into();
447 let replacement = Replacement::Single(with.clone().into());
448 self.replacements.insert(what, replacement);
449 }
450 pub fn replace_with_many<T: Clone + Into<SyntaxElement>>(
451 &mut self,
452 what: &T,
453 with: Vec<SyntaxElement>,
454 ) {
455 let what = what.clone().into();
456 let replacement = Replacement::Many(with);
457 self.replacements.insert(what, replacement);
458 }
459 pub fn replace_ast<T: AstNode>(&mut self, what: &T, with: &T) {
460 self.replace(what.syntax(), with.syntax())
461 }
462
463 pub fn rewrite(&self, node: &SyntaxNode) -> SyntaxNode {
464 let _p = profile::span("rewrite");
465
466 if self.replacements.is_empty() && self.insertions.is_empty() {
467 return node.clone();
468 }
469 let green = self.rewrite_children(node);
470 with_green(node, green)
471 }
472
473 pub fn rewrite_ast<N: AstNode>(self, node: &N) -> N {
474 N::cast(self.rewrite(node.syntax())).unwrap()
475 }
476
477 /// Returns a node that encompasses all replacements to be done by this rewriter.
478 ///
479 /// Passing the returned node to `rewrite` will apply all replacements queued up in `self`.
480 ///
481 /// Returns `None` when there are no replacements.
482 pub fn rewrite_root(&self) -> Option<SyntaxNode> {
483 let _p = profile::span("rewrite_root");
484 fn element_to_node_or_parent(element: &SyntaxElement) -> Option<SyntaxNode> {
485 match element {
486 SyntaxElement::Node(it) => Some(it.clone()),
487 SyntaxElement::Token(it) => it.parent(),
488 }
489 }
490
491 self.replacements
492 .keys()
493 .filter_map(element_to_node_or_parent)
494 .chain(self.insertions.keys().filter_map(|pos| match pos {
495 InsertPos::FirstChildOf(it) => Some(it.clone()),
496 InsertPos::After(it) => element_to_node_or_parent(it),
497 }))
498 // If we only have one replacement/insertion, we must return its parent node, since `rewrite` does
499 // not replace the node passed to it.
500 .map(|it| it.parent().unwrap_or(it))
501 .fold1(|a, b| least_common_ancestor(&a, &b).unwrap())
502 }
503
504 fn replacement(&self, element: &SyntaxElement) -> Option<Replacement> {
505 self.replacements.get(element).cloned()
506 }
507
508 fn insertions(&self, pos: &InsertPos) -> Option<impl Iterator<Item = SyntaxElement> + '_> {
509 self.insertions.get(pos).map(|insertions| insertions.iter().cloned())
510 }
511
512 fn rewrite_children(&self, node: &SyntaxNode) -> rowan::GreenNode {
513 let _p = profile::span("rewrite_children");
514
515 // FIXME: this could be made much faster.
516 let mut new_children = Vec::new();
517 if let Some(elements) = self.insertions(&InsertPos::FirstChildOf(node.clone())) {
518 new_children.extend(elements.map(element_to_green));
519 }
520 for child in node.children_with_tokens() {
521 self.rewrite_self(&mut new_children, &child);
522 }
523
524 rowan::GreenNode::new(rowan::SyntaxKind(node.kind() as u16), new_children)
525 }
526
527 fn rewrite_self(
528 &self,
529 acc: &mut Vec<NodeOrToken<rowan::GreenNode, rowan::GreenToken>>,
530 element: &SyntaxElement,
531 ) {
532 let _p = profile::span("rewrite_self");
533
534 if let Some(replacement) = self.replacement(&element) {
535 match replacement {
536 Replacement::Single(element) => acc.push(element_to_green(element)),
537 Replacement::Many(replacements) => {
538 acc.extend(replacements.into_iter().map(element_to_green))
539 }
540 Replacement::Delete => (),
541 };
542 } else {
543 match element {
544 NodeOrToken::Token(it) => acc.push(NodeOrToken::Token(it.green().to_owned())),
545 NodeOrToken::Node(it) => {
546 acc.push(NodeOrToken::Node(self.rewrite_children(it)));
547 }
548 }
549 }
550 if let Some(elements) = self.insertions(&InsertPos::After(element.clone())) {
551 acc.extend(elements.map(element_to_green));
552 }
553 }
554}
555
556fn element_to_green(element: SyntaxElement) -> NodeOrToken<rowan::GreenNode, rowan::GreenToken> {
557 match element {
558 NodeOrToken::Node(it) => NodeOrToken::Node(it.green().into_owned()),
559 NodeOrToken::Token(it) => NodeOrToken::Token(it.green().to_owned()),
560 }
561}
562
563impl ops::AddAssign for SyntaxRewriter<'_> {
564 fn add_assign(&mut self, rhs: SyntaxRewriter) {
565 self.replacements.extend(rhs.replacements);
566 for (pos, insertions) in rhs.insertions.into_iter() {
567 match self.insertions.entry(pos) {
568 indexmap::map::Entry::Occupied(mut occupied) => {
569 occupied.get_mut().extend(insertions)
570 }
571 indexmap::map::Entry::Vacant(vacant) => drop(vacant.insert(insertions)),
572 }
573 }
574 }
575}
576
577#[derive(Clone, Debug)]
578enum Replacement {
579 Delete,
580 Single(SyntaxElement),
581 Many(Vec<SyntaxElement>),
582}
583
584fn with_children( 333fn with_children(
585 parent: &SyntaxNode, 334 parent: &SyntaxNode,
586 new_children: Vec<NodeOrToken<rowan::GreenNode, rowan::GreenToken>>, 335 new_children: Vec<NodeOrToken<rowan::GreenNode, rowan::GreenToken>>,
diff --git a/crates/syntax/src/ast.rs b/crates/syntax/src/ast.rs
index 7f472d4db..a8071b51d 100644
--- a/crates/syntax/src/ast.rs
+++ b/crates/syntax/src/ast.rs
@@ -47,6 +47,12 @@ pub trait AstNode {
47 { 47 {
48 Self::cast(self.syntax().clone_for_update()).unwrap() 48 Self::cast(self.syntax().clone_for_update()).unwrap()
49 } 49 }
50 fn clone_subtree(&self) -> Self
51 where
52 Self: Sized,
53 {
54 Self::cast(self.syntax().clone_subtree()).unwrap()
55 }
50} 56}
51 57
52/// Like `AstNode`, but wraps tokens rather than interior nodes. 58/// Like `AstNode`, but wraps tokens rather than interior nodes.
diff --git a/crates/syntax/src/ast/edit.rs b/crates/syntax/src/ast/edit.rs
index 8c60927e4..61952377f 100644
--- a/crates/syntax/src/ast/edit.rs
+++ b/crates/syntax/src/ast/edit.rs
@@ -2,20 +2,16 @@
2//! immutable, all function here return a fresh copy of the tree, instead of 2//! immutable, all function here return a fresh copy of the tree, instead of
3//! doing an in-place modification. 3//! doing an in-place modification.
4use std::{ 4use std::{
5 array, fmt, iter, 5 fmt, iter,
6 ops::{self, RangeInclusive}, 6 ops::{self, RangeInclusive},
7}; 7};
8 8
9use arrayvec::ArrayVec; 9use arrayvec::ArrayVec;
10 10
11use crate::{ 11use crate::{
12 algo::{self, SyntaxRewriter}, 12 algo,
13 ast::{ 13 ast::{self, make, AstNode},
14 self, 14 ted, AstToken, InsertPosition, NodeOrToken, SyntaxElement, SyntaxKind,
15 make::{self, tokens},
16 AstNode, GenericParamsOwner, NameOwner, TypeBoundsOwner,
17 },
18 AstToken, Direction, InsertPosition, SmolStr, SyntaxElement, SyntaxKind,
19 SyntaxKind::{ATTR, COMMENT, WHITESPACE}, 15 SyntaxKind::{ATTR, COMMENT, WHITESPACE},
20 SyntaxNode, SyntaxToken, T, 16 SyntaxNode, SyntaxToken, T,
21}; 17};
@@ -29,250 +25,6 @@ impl ast::BinExpr {
29 } 25 }
30} 26}
31 27
32impl ast::Fn {
33 #[must_use]
34 pub fn with_body(&self, body: ast::BlockExpr) -> ast::Fn {
35 let mut to_insert: ArrayVec<SyntaxElement, 2> = ArrayVec::new();
36 let old_body_or_semi: SyntaxElement = if let Some(old_body) = self.body() {
37 old_body.syntax().clone().into()
38 } else if let Some(semi) = self.semicolon_token() {
39 to_insert.push(make::tokens::single_space().into());
40 semi.into()
41 } else {
42 to_insert.push(make::tokens::single_space().into());
43 to_insert.push(body.syntax().clone().into());
44 return self.insert_children(InsertPosition::Last, to_insert);
45 };
46 to_insert.push(body.syntax().clone().into());
47 self.replace_children(single_node(old_body_or_semi), to_insert)
48 }
49
50 #[must_use]
51 pub fn with_generic_param_list(&self, generic_args: ast::GenericParamList) -> ast::Fn {
52 if let Some(old) = self.generic_param_list() {
53 return self.replace_descendant(old, generic_args);
54 }
55
56 let anchor = self.name().expect("The function must have a name").syntax().clone();
57
58 let to_insert = [generic_args.syntax().clone().into()];
59 self.insert_children(InsertPosition::After(anchor.into()), array::IntoIter::new(to_insert))
60 }
61}
62
63fn make_multiline<N>(node: N) -> N
64where
65 N: AstNode + Clone,
66{
67 let l_curly = match node.syntax().children_with_tokens().find(|it| it.kind() == T!['{']) {
68 Some(it) => it,
69 None => return node,
70 };
71 let sibling = match l_curly.next_sibling_or_token() {
72 Some(it) => it,
73 None => return node,
74 };
75 let existing_ws = match sibling.as_token() {
76 None => None,
77 Some(tok) if tok.kind() != WHITESPACE => None,
78 Some(ws) => {
79 if ws.text().contains('\n') {
80 return node;
81 }
82 Some(ws.clone())
83 }
84 };
85
86 let indent = leading_indent(node.syntax()).unwrap_or_default();
87 let ws = tokens::WsBuilder::new(&format!("\n{}", indent));
88 let to_insert = iter::once(ws.ws().into());
89 match existing_ws {
90 None => node.insert_children(InsertPosition::After(l_curly), to_insert),
91 Some(ws) => node.replace_children(single_node(ws), to_insert),
92 }
93}
94
95impl ast::Impl {
96 #[must_use]
97 pub fn with_assoc_item_list(&self, items: ast::AssocItemList) -> ast::Impl {
98 let mut to_insert: ArrayVec<SyntaxElement, 2> = ArrayVec::new();
99 if let Some(old_items) = self.assoc_item_list() {
100 let to_replace: SyntaxElement = old_items.syntax().clone().into();
101 to_insert.push(items.syntax().clone().into());
102 self.replace_children(single_node(to_replace), to_insert)
103 } else {
104 to_insert.push(make::tokens::single_space().into());
105 to_insert.push(items.syntax().clone().into());
106 self.insert_children(InsertPosition::Last, to_insert)
107 }
108 }
109}
110
111impl ast::AssocItemList {
112 #[must_use]
113 pub fn append_items(
114 &self,
115 items: impl IntoIterator<Item = ast::AssocItem>,
116 ) -> ast::AssocItemList {
117 let mut res = self.clone();
118 if !self.syntax().text().contains_char('\n') {
119 res = make_multiline(res);
120 }
121 items.into_iter().for_each(|it| res = res.append_item(it));
122 res.fixup_trailing_whitespace().unwrap_or(res)
123 }
124
125 #[must_use]
126 pub fn append_item(&self, item: ast::AssocItem) -> ast::AssocItemList {
127 let (indent, position, whitespace) = match self.assoc_items().last() {
128 Some(it) => (
129 leading_indent(it.syntax()).unwrap_or_default().to_string(),
130 InsertPosition::After(it.syntax().clone().into()),
131 "\n\n",
132 ),
133 None => match self.l_curly_token() {
134 Some(it) => (
135 " ".to_string() + &leading_indent(self.syntax()).unwrap_or_default(),
136 InsertPosition::After(it.into()),
137 "\n",
138 ),
139 None => return self.clone(),
140 },
141 };
142 let ws = tokens::WsBuilder::new(&format!("{}{}", whitespace, indent));
143 let to_insert: ArrayVec<SyntaxElement, 2> =
144 [ws.ws().into(), item.syntax().clone().into()].into();
145 self.insert_children(position, to_insert)
146 }
147
148 /// Remove extra whitespace between last item and closing curly brace.
149 fn fixup_trailing_whitespace(&self) -> Option<ast::AssocItemList> {
150 let first_token_after_items =
151 self.assoc_items().last()?.syntax().next_sibling_or_token()?;
152 let last_token_before_curly = self.r_curly_token()?.prev_sibling_or_token()?;
153 if last_token_before_curly != first_token_after_items {
154 // there is something more between last item and
155 // right curly than just whitespace - bail out
156 return None;
157 }
158 let whitespace =
159 last_token_before_curly.clone().into_token().and_then(ast::Whitespace::cast)?;
160 let text = whitespace.syntax().text();
161 let newline = text.rfind('\n')?;
162 let keep = tokens::WsBuilder::new(&text[newline..]);
163 Some(self.replace_children(
164 first_token_after_items..=last_token_before_curly,
165 std::iter::once(keep.ws().into()),
166 ))
167 }
168}
169
170impl ast::RecordExprFieldList {
171 #[must_use]
172 pub fn append_field(&self, field: &ast::RecordExprField) -> ast::RecordExprFieldList {
173 self.insert_field(InsertPosition::Last, field)
174 }
175
176 #[must_use]
177 pub fn insert_field(
178 &self,
179 position: InsertPosition<&'_ ast::RecordExprField>,
180 field: &ast::RecordExprField,
181 ) -> ast::RecordExprFieldList {
182 let is_multiline = self.syntax().text().contains_char('\n');
183 let ws;
184 let space = if is_multiline {
185 ws = tokens::WsBuilder::new(&format!(
186 "\n{} ",
187 leading_indent(self.syntax()).unwrap_or_default()
188 ));
189 ws.ws()
190 } else {
191 tokens::single_space()
192 };
193
194 let mut to_insert: ArrayVec<SyntaxElement, 4> = ArrayVec::new();
195 to_insert.push(space.into());
196 to_insert.push(field.syntax().clone().into());
197 to_insert.push(make::token(T![,]).into());
198
199 macro_rules! after_l_curly {
200 () => {{
201 let anchor = match self.l_curly_token() {
202 Some(it) => it.into(),
203 None => return self.clone(),
204 };
205 InsertPosition::After(anchor)
206 }};
207 }
208
209 macro_rules! after_field {
210 ($anchor:expr) => {
211 if let Some(comma) = $anchor
212 .syntax()
213 .siblings_with_tokens(Direction::Next)
214 .find(|it| it.kind() == T![,])
215 {
216 InsertPosition::After(comma)
217 } else {
218 to_insert.insert(0, make::token(T![,]).into());
219 InsertPosition::After($anchor.syntax().clone().into())
220 }
221 };
222 }
223
224 let position = match position {
225 InsertPosition::First => after_l_curly!(),
226 InsertPosition::Last => {
227 if !is_multiline {
228 // don't insert comma before curly
229 to_insert.pop();
230 }
231 match self.fields().last() {
232 Some(it) => after_field!(it),
233 None => after_l_curly!(),
234 }
235 }
236 InsertPosition::Before(anchor) => {
237 InsertPosition::Before(anchor.syntax().clone().into())
238 }
239 InsertPosition::After(anchor) => after_field!(anchor),
240 };
241
242 self.insert_children(position, to_insert)
243 }
244}
245
246impl ast::TypeAlias {
247 #[must_use]
248 pub fn remove_bounds(&self) -> ast::TypeAlias {
249 let colon = match self.colon_token() {
250 Some(it) => it,
251 None => return self.clone(),
252 };
253 let end = match self.type_bound_list() {
254 Some(it) => it.syntax().clone().into(),
255 None => colon.clone().into(),
256 };
257 self.replace_children(colon.into()..=end, iter::empty())
258 }
259}
260
261impl ast::TypeParam {
262 #[must_use]
263 pub fn remove_bounds(&self) -> ast::TypeParam {
264 let colon = match self.colon_token() {
265 Some(it) => it,
266 None => return self.clone(),
267 };
268 let end = match self.type_bound_list() {
269 Some(it) => it.syntax().clone().into(),
270 None => colon.clone().into(),
271 };
272 self.replace_children(colon.into()..=end, iter::empty())
273 }
274}
275
276impl ast::Path { 28impl ast::Path {
277 #[must_use] 29 #[must_use]
278 pub fn with_segment(&self, segment: ast::PathSegment) -> ast::Path { 30 pub fn with_segment(&self, segment: ast::PathSegment) -> ast::Path {
@@ -313,33 +65,7 @@ impl ast::PathSegment {
313 } 65 }
314} 66}
315 67
316impl ast::Use {
317 #[must_use]
318 pub fn with_use_tree(&self, use_tree: ast::UseTree) -> ast::Use {
319 if let Some(old) = self.use_tree() {
320 return self.replace_descendant(old, use_tree);
321 }
322 self.clone()
323 }
324}
325
326impl ast::UseTree { 68impl ast::UseTree {
327 #[must_use]
328 pub fn with_path(&self, path: ast::Path) -> ast::UseTree {
329 if let Some(old) = self.path() {
330 return self.replace_descendant(old, path);
331 }
332 self.clone()
333 }
334
335 #[must_use]
336 pub fn with_use_tree_list(&self, use_tree_list: ast::UseTreeList) -> ast::UseTree {
337 if let Some(old) = self.use_tree_list() {
338 return self.replace_descendant(old, use_tree_list);
339 }
340 self.clone()
341 }
342
343 /// Splits off the given prefix, making it the path component of the use tree, appending the rest of the path to all UseTreeList items. 69 /// Splits off the given prefix, making it the path component of the use tree, appending the rest of the path to all UseTreeList items.
344 #[must_use] 70 #[must_use]
345 pub fn split_prefix(&self, prefix: &ast::Path) -> ast::UseTree { 71 pub fn split_prefix(&self, prefix: &ast::Path) -> ast::UseTree {
@@ -376,134 +102,6 @@ impl ast::UseTree {
376 } 102 }
377} 103}
378 104
379impl ast::MatchArmList {
380 #[must_use]
381 pub fn append_arms(&self, items: impl IntoIterator<Item = ast::MatchArm>) -> ast::MatchArmList {
382 let mut res = self.clone();
383 res = res.strip_if_only_whitespace();
384 if !res.syntax().text().contains_char('\n') {
385 res = make_multiline(res);
386 }
387 items.into_iter().for_each(|it| res = res.append_arm(it));
388 res
389 }
390
391 fn strip_if_only_whitespace(&self) -> ast::MatchArmList {
392 let mut iter = self.syntax().children_with_tokens().skip_while(|it| it.kind() != T!['{']);
393 iter.next(); // Eat the curly
394 let mut inner = iter.take_while(|it| it.kind() != T!['}']);
395 if !inner.clone().all(|it| it.kind() == WHITESPACE) {
396 return self.clone();
397 }
398 let start = match inner.next() {
399 Some(s) => s,
400 None => return self.clone(),
401 };
402 let end = match inner.last() {
403 Some(s) => s,
404 None => start.clone(),
405 };
406 self.replace_children(start..=end, &mut iter::empty())
407 }
408
409 #[must_use]
410 pub fn remove_placeholder(&self) -> ast::MatchArmList {
411 let placeholder =
412 self.arms().find(|arm| matches!(arm.pat(), Some(ast::Pat::WildcardPat(_))));
413 if let Some(placeholder) = placeholder {
414 self.remove_arm(&placeholder)
415 } else {
416 self.clone()
417 }
418 }
419
420 #[must_use]
421 fn remove_arm(&self, arm: &ast::MatchArm) -> ast::MatchArmList {
422 let start = arm.syntax().clone();
423 let end = if let Some(comma) = start
424 .siblings_with_tokens(Direction::Next)
425 .skip(1)
426 .find(|it| !it.kind().is_trivia())
427 .filter(|it| it.kind() == T![,])
428 {
429 comma
430 } else {
431 start.clone().into()
432 };
433 self.replace_children(start.into()..=end, None)
434 }
435
436 #[must_use]
437 pub fn append_arm(&self, item: ast::MatchArm) -> ast::MatchArmList {
438 let r_curly = match self.syntax().children_with_tokens().find(|it| it.kind() == T!['}']) {
439 Some(t) => t,
440 None => return self.clone(),
441 };
442 let position = InsertPosition::Before(r_curly);
443 let arm_ws = tokens::WsBuilder::new(" ");
444 let match_indent = &leading_indent(self.syntax()).unwrap_or_default();
445 let match_ws = tokens::WsBuilder::new(&format!("\n{}", match_indent));
446 let to_insert: ArrayVec<SyntaxElement, 3> =
447 [arm_ws.ws().into(), item.syntax().clone().into(), match_ws.ws().into()].into();
448 self.insert_children(position, to_insert)
449 }
450}
451
452impl ast::GenericParamList {
453 #[must_use]
454 pub fn append_params(
455 &self,
456 params: impl IntoIterator<Item = ast::GenericParam>,
457 ) -> ast::GenericParamList {
458 let mut res = self.clone();
459 params.into_iter().for_each(|it| res = res.append_param(it));
460 res
461 }
462
463 #[must_use]
464 pub fn append_param(&self, item: ast::GenericParam) -> ast::GenericParamList {
465 let space = tokens::single_space();
466
467 let mut to_insert: ArrayVec<SyntaxElement, 4> = ArrayVec::new();
468 if self.generic_params().next().is_some() {
469 to_insert.push(space.into());
470 }
471 to_insert.push(item.syntax().clone().into());
472
473 macro_rules! after_l_angle {
474 () => {{
475 let anchor = match self.l_angle_token() {
476 Some(it) => it.into(),
477 None => return self.clone(),
478 };
479 InsertPosition::After(anchor)
480 }};
481 }
482
483 macro_rules! after_field {
484 ($anchor:expr) => {
485 if let Some(comma) = $anchor
486 .syntax()
487 .siblings_with_tokens(Direction::Next)
488 .find(|it| it.kind() == T![,])
489 {
490 InsertPosition::After(comma)
491 } else {
492 to_insert.insert(0, make::token(T![,]).into());
493 InsertPosition::After($anchor.syntax().clone().into())
494 }
495 };
496 }
497
498 let position = match self.generic_params().last() {
499 Some(it) => after_field!(it),
500 None => after_l_angle!(),
501 };
502
503 self.insert_children(position, to_insert)
504 }
505}
506
507#[must_use] 105#[must_use]
508pub fn remove_attrs_and_docs<N: ast::AttrsOwner>(node: &N) -> N { 106pub fn remove_attrs_and_docs<N: ast::AttrsOwner>(node: &N) -> N {
509 N::cast(remove_attrs_and_docs_inner(node.syntax().clone())).unwrap() 107 N::cast(remove_attrs_and_docs_inner(node.syntax().clone())).unwrap()
@@ -554,6 +152,12 @@ impl ops::Add<u8> for IndentLevel {
554} 152}
555 153
556impl IndentLevel { 154impl IndentLevel {
155 pub fn single() -> IndentLevel {
156 IndentLevel(0)
157 }
158 pub fn is_zero(&self) -> bool {
159 self.0 == 0
160 }
557 pub fn from_element(element: &SyntaxElement) -> IndentLevel { 161 pub fn from_element(element: &SyntaxElement) -> IndentLevel {
558 match element { 162 match element {
559 rowan::NodeOrToken::Node(it) => IndentLevel::from_node(it), 163 rowan::NodeOrToken::Node(it) => IndentLevel::from_node(it),
@@ -588,54 +192,40 @@ impl IndentLevel {
588 /// ``` 192 /// ```
589 /// if you indent the block, the `{` token would stay put. 193 /// if you indent the block, the `{` token would stay put.
590 fn increase_indent(self, node: SyntaxNode) -> SyntaxNode { 194 fn increase_indent(self, node: SyntaxNode) -> SyntaxNode {
591 let mut rewriter = SyntaxRewriter::default(); 195 let res = node.clone_subtree().clone_for_update();
592 node.descendants_with_tokens() 196 let tokens = res.preorder_with_tokens().filter_map(|event| match event {
593 .filter_map(|el| el.into_token()) 197 rowan::WalkEvent::Leave(NodeOrToken::Token(it)) => Some(it),
594 .filter_map(ast::Whitespace::cast) 198 _ => None,
595 .filter(|ws| { 199 });
596 let text = ws.syntax().text(); 200 for token in tokens {
597 text.contains('\n') 201 if let Some(ws) = ast::Whitespace::cast(token) {
598 }) 202 if ws.text().contains('\n') {
599 .for_each(|ws| { 203 let new_ws = make::tokens::whitespace(&format!("{}{}", ws.syntax(), self));
600 let new_ws = make::tokens::whitespace(&format!("{}{}", ws.syntax(), self,)); 204 ted::replace(ws.syntax(), &new_ws)
601 rewriter.replace(ws.syntax(), &new_ws) 205 }
602 }); 206 }
603 rewriter.rewrite(&node) 207 }
208 res.clone_subtree()
604 } 209 }
605 210
606 fn decrease_indent(self, node: SyntaxNode) -> SyntaxNode { 211 fn decrease_indent(self, node: SyntaxNode) -> SyntaxNode {
607 let mut rewriter = SyntaxRewriter::default(); 212 let res = node.clone_subtree().clone_for_update();
608 node.descendants_with_tokens() 213 let tokens = res.preorder_with_tokens().filter_map(|event| match event {
609 .filter_map(|el| el.into_token()) 214 rowan::WalkEvent::Leave(NodeOrToken::Token(it)) => Some(it),
610 .filter_map(ast::Whitespace::cast) 215 _ => None,
611 .filter(|ws| { 216 });
612 let text = ws.syntax().text(); 217 for token in tokens {
613 text.contains('\n') 218 if let Some(ws) = ast::Whitespace::cast(token) {
614 }) 219 if ws.text().contains('\n') {
615 .for_each(|ws| { 220 let new_ws = make::tokens::whitespace(
616 let new_ws = make::tokens::whitespace( 221 &ws.syntax().text().replace(&format!("\n{}", self), "\n"),
617 &ws.syntax().text().replace(&format!("\n{}", self), "\n"), 222 );
618 ); 223 ted::replace(ws.syntax(), &new_ws)
619 rewriter.replace(ws.syntax(), &new_ws) 224 }
620 });
621 rewriter.rewrite(&node)
622 }
623}
624
625// FIXME: replace usages with IndentLevel above
626fn leading_indent(node: &SyntaxNode) -> Option<SmolStr> {
627 for token in prev_tokens(node.first_token()?) {
628 if let Some(ws) = ast::Whitespace::cast(token.clone()) {
629 let ws_text = ws.text();
630 if let Some(pos) = ws_text.rfind('\n') {
631 return Some(ws_text[pos + 1..].into());
632 } 225 }
633 } 226 }
634 if token.text().contains('\n') { 227 res.clone_subtree()
635 break;
636 }
637 } 228 }
638 None
639} 229}
640 230
641fn prev_tokens(token: SyntaxToken) -> impl Iterator<Item = SyntaxToken> { 231fn prev_tokens(token: SyntaxToken) -> impl Iterator<Item = SyntaxToken> {
@@ -662,23 +252,6 @@ pub trait AstNodeEdit: AstNode + Clone + Sized {
662 let new_syntax = algo::replace_children(self.syntax(), to_replace, to_insert); 252 let new_syntax = algo::replace_children(self.syntax(), to_replace, to_insert);
663 Self::cast(new_syntax).unwrap() 253 Self::cast(new_syntax).unwrap()
664 } 254 }
665
666 #[must_use]
667 fn replace_descendant<D: AstNode>(&self, old: D, new: D) -> Self {
668 self.replace_descendants(iter::once((old, new)))
669 }
670
671 #[must_use]
672 fn replace_descendants<D: AstNode>(
673 &self,
674 replacement_map: impl IntoIterator<Item = (D, D)>,
675 ) -> Self {
676 let mut rewriter = SyntaxRewriter::default();
677 for (from, to) in replacement_map {
678 rewriter.replace(from.syntax(), to.syntax())
679 }
680 rewriter.rewrite_ast(self)
681 }
682 fn indent_level(&self) -> IndentLevel { 255 fn indent_level(&self) -> IndentLevel {
683 IndentLevel::from_node(self.syntax()) 256 IndentLevel::from_node(self.syntax())
684 } 257 }
diff --git a/crates/syntax/src/ast/edit_in_place.rs b/crates/syntax/src/ast/edit_in_place.rs
index 04f97f368..2676ed8c9 100644
--- a/crates/syntax/src/ast/edit_in_place.rs
+++ b/crates/syntax/src/ast/edit_in_place.rs
@@ -2,13 +2,18 @@
2 2
3use std::iter::empty; 3use std::iter::empty;
4 4
5use parser::T; 5use parser::{SyntaxKind, T};
6use rowan::SyntaxElement;
6 7
7use crate::{ 8use crate::{
8 algo::neighbor, 9 algo::neighbor,
9 ast::{self, edit::AstNodeEdit, make, GenericParamsOwner, WhereClause}, 10 ast::{
11 self,
12 edit::{AstNodeEdit, IndentLevel},
13 make, GenericParamsOwner,
14 },
10 ted::{self, Position}, 15 ted::{self, Position},
11 AstNode, AstToken, Direction, 16 AstNode, AstToken, Direction, SyntaxNode,
12}; 17};
13 18
14use super::NameOwner; 19use super::NameOwner;
@@ -37,7 +42,7 @@ impl GenericParamsOwnerEdit for ast::Fn {
37 } 42 }
38 } 43 }
39 44
40 fn get_or_create_where_clause(&self) -> WhereClause { 45 fn get_or_create_where_clause(&self) -> ast::WhereClause {
41 if self.where_clause().is_none() { 46 if self.where_clause().is_none() {
42 let position = if let Some(ty) = self.ret_type() { 47 let position = if let Some(ty) = self.ret_type() {
43 Position::after(ty.syntax()) 48 Position::after(ty.syntax())
@@ -67,7 +72,7 @@ impl GenericParamsOwnerEdit for ast::Impl {
67 } 72 }
68 } 73 }
69 74
70 fn get_or_create_where_clause(&self) -> WhereClause { 75 fn get_or_create_where_clause(&self) -> ast::WhereClause {
71 if self.where_clause().is_none() { 76 if self.where_clause().is_none() {
72 let position = if let Some(items) = self.assoc_item_list() { 77 let position = if let Some(items) = self.assoc_item_list() {
73 Position::before(items.syntax()) 78 Position::before(items.syntax())
@@ -97,7 +102,7 @@ impl GenericParamsOwnerEdit for ast::Trait {
97 } 102 }
98 } 103 }
99 104
100 fn get_or_create_where_clause(&self) -> WhereClause { 105 fn get_or_create_where_clause(&self) -> ast::WhereClause {
101 if self.where_clause().is_none() { 106 if self.where_clause().is_none() {
102 let position = if let Some(items) = self.assoc_item_list() { 107 let position = if let Some(items) = self.assoc_item_list() {
103 Position::before(items.syntax()) 108 Position::before(items.syntax())
@@ -127,7 +132,7 @@ impl GenericParamsOwnerEdit for ast::Struct {
127 } 132 }
128 } 133 }
129 134
130 fn get_or_create_where_clause(&self) -> WhereClause { 135 fn get_or_create_where_clause(&self) -> ast::WhereClause {
131 if self.where_clause().is_none() { 136 if self.where_clause().is_none() {
132 let tfl = self.field_list().and_then(|fl| match fl { 137 let tfl = self.field_list().and_then(|fl| match fl {
133 ast::FieldList::RecordFieldList(_) => None, 138 ast::FieldList::RecordFieldList(_) => None,
@@ -165,7 +170,7 @@ impl GenericParamsOwnerEdit for ast::Enum {
165 } 170 }
166 } 171 }
167 172
168 fn get_or_create_where_clause(&self) -> WhereClause { 173 fn get_or_create_where_clause(&self) -> ast::WhereClause {
169 if self.where_clause().is_none() { 174 if self.where_clause().is_none() {
170 let position = if let Some(gpl) = self.generic_param_list() { 175 let position = if let Some(gpl) = self.generic_param_list() {
171 Position::after(gpl.syntax()) 176 Position::after(gpl.syntax())
@@ -195,18 +200,13 @@ impl ast::GenericParamList {
195 pub fn add_generic_param(&self, generic_param: ast::GenericParam) { 200 pub fn add_generic_param(&self, generic_param: ast::GenericParam) {
196 match self.generic_params().last() { 201 match self.generic_params().last() {
197 Some(last_param) => { 202 Some(last_param) => {
198 let mut elems = Vec::new(); 203 let position = Position::after(last_param.syntax());
199 if !last_param 204 let elements = vec![
200 .syntax() 205 make::token(T![,]).into(),
201 .siblings_with_tokens(Direction::Next) 206 make::tokens::single_space().into(),
202 .any(|it| it.kind() == T![,]) 207 generic_param.syntax().clone().into(),
203 { 208 ];
204 elems.push(make::token(T![,]).into()); 209 ted::insert_all(position, elements);
205 elems.push(make::tokens::single_space().into());
206 };
207 elems.push(generic_param.syntax().clone().into());
208 let after_last_param = Position::after(last_param.syntax());
209 ted::insert_all(after_last_param, elems);
210 } 210 }
211 None => { 211 None => {
212 let after_l_angle = Position::after(self.l_angle_token().unwrap()); 212 let after_l_angle = Position::after(self.l_angle_token().unwrap());
@@ -277,6 +277,167 @@ impl ast::Use {
277 } 277 }
278} 278}
279 279
280impl ast::Impl {
281 pub fn get_or_create_assoc_item_list(&self) -> ast::AssocItemList {
282 if self.assoc_item_list().is_none() {
283 let assoc_item_list = make::assoc_item_list().clone_for_update();
284 ted::append_child(self.syntax(), assoc_item_list.syntax());
285 }
286 self.assoc_item_list().unwrap()
287 }
288}
289
290impl ast::AssocItemList {
291 pub fn add_item(&self, item: ast::AssocItem) {
292 let (indent, position, whitespace) = match self.assoc_items().last() {
293 Some(last_item) => (
294 IndentLevel::from_node(last_item.syntax()),
295 Position::after(last_item.syntax()),
296 "\n\n",
297 ),
298 None => match self.l_curly_token() {
299 Some(l_curly) => {
300 normalize_ws_between_braces(self.syntax());
301 (IndentLevel::from_token(&l_curly) + 1, Position::after(&l_curly), "\n")
302 }
303 None => (IndentLevel::single(), Position::last_child_of(self.syntax()), "\n"),
304 },
305 };
306 let elements: Vec<SyntaxElement<_>> = vec![
307 make::tokens::whitespace(&format!("{}{}", whitespace, indent)).into(),
308 item.syntax().clone().into(),
309 ];
310 ted::insert_all(position, elements);
311 }
312}
313
314impl ast::Fn {
315 pub fn get_or_create_body(&self) -> ast::BlockExpr {
316 if self.body().is_none() {
317 let body = make::ext::empty_block_expr().clone_for_update();
318 match self.semicolon_token() {
319 Some(semi) => {
320 ted::replace(semi, body.syntax());
321 ted::insert(Position::before(body.syntax), make::tokens::single_space());
322 }
323 None => ted::append_child(self.syntax(), body.syntax()),
324 }
325 }
326 self.body().unwrap()
327 }
328}
329
330impl ast::MatchArm {
331 pub fn remove(&self) {
332 if let Some(sibling) = self.syntax().prev_sibling_or_token() {
333 if sibling.kind() == SyntaxKind::WHITESPACE {
334 ted::remove(sibling);
335 }
336 }
337 if let Some(sibling) = self.syntax().next_sibling_or_token() {
338 if sibling.kind() == T![,] {
339 ted::remove(sibling);
340 }
341 }
342 ted::remove(self.syntax());
343 }
344}
345
346impl ast::MatchArmList {
347 pub fn add_arm(&self, arm: ast::MatchArm) {
348 normalize_ws_between_braces(self.syntax());
349 let position = match self.arms().last() {
350 Some(last_arm) => {
351 let curly = last_arm
352 .syntax()
353 .siblings_with_tokens(Direction::Next)
354 .find(|it| it.kind() == T![,]);
355 Position::after(curly.unwrap_or_else(|| last_arm.syntax().clone().into()))
356 }
357 None => match self.l_curly_token() {
358 Some(it) => Position::after(it),
359 None => Position::last_child_of(self.syntax()),
360 },
361 };
362 let indent = IndentLevel::from_node(self.syntax()) + 1;
363 let elements = vec![
364 make::tokens::whitespace(&format!("\n{}", indent)).into(),
365 arm.syntax().clone().into(),
366 ];
367 ted::insert_all(position, elements);
368 }
369}
370
371impl ast::RecordExprFieldList {
372 pub fn add_field(&self, field: ast::RecordExprField) {
373 let is_multiline = self.syntax().text().contains_char('\n');
374 let whitespace = if is_multiline {
375 let indent = IndentLevel::from_node(self.syntax()) + 1;
376 make::tokens::whitespace(&format!("\n{}", indent))
377 } else {
378 make::tokens::single_space()
379 };
380
381 if is_multiline {
382 normalize_ws_between_braces(self.syntax());
383 }
384
385 let position = match self.fields().last() {
386 Some(last_field) => {
387 let comma = match last_field
388 .syntax()
389 .siblings_with_tokens(Direction::Next)
390 .filter_map(|it| it.into_token())
391 .find(|it| it.kind() == T![,])
392 {
393 Some(it) => it,
394 None => {
395 let comma = ast::make::token(T![,]);
396 ted::insert(Position::after(last_field.syntax()), &comma);
397 comma
398 }
399 };
400 Position::after(comma)
401 }
402 None => match self.l_curly_token() {
403 Some(it) => Position::after(it),
404 None => Position::last_child_of(self.syntax()),
405 },
406 };
407
408 ted::insert_all(position, vec![whitespace.into(), field.syntax().clone().into()]);
409 if is_multiline {
410 ted::insert(Position::after(field.syntax()), ast::make::token(T![,]));
411 }
412 }
413}
414
415fn normalize_ws_between_braces(node: &SyntaxNode) -> Option<()> {
416 let l = node
417 .children_with_tokens()
418 .filter_map(|it| it.into_token())
419 .find(|it| it.kind() == T!['{'])?;
420 let r = node
421 .children_with_tokens()
422 .filter_map(|it| it.into_token())
423 .find(|it| it.kind() == T!['}'])?;
424
425 let indent = IndentLevel::from_node(node);
426
427 match l.next_sibling_or_token() {
428 Some(ws) if ws.kind() == SyntaxKind::WHITESPACE => {
429 if ws.next_sibling_or_token()?.into_token()? == r {
430 ted::replace(ws, make::tokens::whitespace(&format!("\n{}", indent)));
431 }
432 }
433 Some(ws) if ws.kind() == T!['}'] => {
434 ted::insert(Position::after(l), make::tokens::whitespace(&format!("\n{}", indent)));
435 }
436 _ => (),
437 }
438 Some(())
439}
440
280#[cfg(test)] 441#[cfg(test)]
281mod tests { 442mod tests {
282 use std::fmt; 443 use std::fmt;
diff --git a/crates/syntax/src/ast/make.rs b/crates/syntax/src/ast/make.rs
index f8b508a90..d13926ded 100644
--- a/crates/syntax/src/ast/make.rs
+++ b/crates/syntax/src/ast/make.rs
@@ -3,25 +3,55 @@
3//! 3//!
4//! Note that all functions here intended to be stupid constructors, which just 4//! Note that all functions here intended to be stupid constructors, which just
5//! assemble a finish node from immediate children. If you want to do something 5//! assemble a finish node from immediate children. If you want to do something
6//! smarter than that, it probably doesn't belong in this module. 6//! smarter than that, it belongs to the `ext` submodule.
7//! 7//!
8//! Keep in mind that `from_text` functions should be kept private. The public 8//! Keep in mind that `from_text` functions should be kept private. The public
9//! API should require to assemble every node piecewise. The trick of 9//! API should require to assemble every node piecewise. The trick of
10//! `parse(format!())` we use internally is an implementation detail -- long 10//! `parse(format!())` we use internally is an implementation detail -- long
11//! term, it will be replaced with direct tree manipulation. 11//! term, it will be replaced with direct tree manipulation.
12use itertools::Itertools; 12use itertools::Itertools;
13use stdx::format_to; 13use stdx::{format_to, never};
14 14
15use crate::{ast, AstNode, SourceFile, SyntaxKind, SyntaxNode, SyntaxToken}; 15use crate::{ast, AstNode, SourceFile, SyntaxKind, SyntaxToken};
16
17/// While the parent module defines basic atomic "constructors", the `ext`
18/// module defines shortcuts for common things.
19///
20/// It's named `ext` rather than `shortcuts` just to keep it short.
21pub mod ext {
22 use super::*;
23
24 pub fn ident_path(ident: &str) -> ast::Path {
25 path_unqualified(path_segment(name_ref(ident)))
26 }
27
28 pub fn expr_unreachable() -> ast::Expr {
29 expr_from_text("unreachable!()")
30 }
31 pub fn expr_todo() -> ast::Expr {
32 expr_from_text("todo!()")
33 }
34 pub fn empty_block_expr() -> ast::BlockExpr {
35 block_expr(None, None)
36 }
37
38 pub fn ty_bool() -> ast::Type {
39 ty_path(ident_path("bool"))
40 }
41 pub fn ty_option(t: ast::Type) -> ast::Type {
42 ty_from_text(&format!("Option<{}>", t))
43 }
44 pub fn ty_result(t: ast::Type, e: ast::Type) -> ast::Type {
45 ty_from_text(&format!("Result<{}, {}>", t, e))
46 }
47}
16 48
17pub fn name(text: &str) -> ast::Name { 49pub fn name(text: &str) -> ast::Name {
18 ast_from_text(&format!("mod {}{};", raw_ident_esc(text), text)) 50 ast_from_text(&format!("mod {}{};", raw_ident_esc(text), text))
19} 51}
20
21pub fn name_ref(text: &str) -> ast::NameRef { 52pub fn name_ref(text: &str) -> ast::NameRef {
22 ast_from_text(&format!("fn f() {{ {}{}; }}", raw_ident_esc(text), text)) 53 ast_from_text(&format!("fn f() {{ {}{}; }}", raw_ident_esc(text), text))
23} 54}
24
25fn raw_ident_esc(ident: &str) -> &'static str { 55fn raw_ident_esc(ident: &str) -> &'static str {
26 let is_keyword = parser::SyntaxKind::from_keyword(ident).is_some(); 56 let is_keyword = parser::SyntaxKind::from_keyword(ident).is_some();
27 if is_keyword && !matches!(ident, "self" | "crate" | "super" | "Self") { 57 if is_keyword && !matches!(ident, "self" | "crate" | "super" | "Self") {
@@ -31,13 +61,23 @@ fn raw_ident_esc(ident: &str) -> &'static str {
31 } 61 }
32} 62}
33 63
64pub fn lifetime(text: &str) -> ast::Lifetime {
65 let mut text = text;
66 let tmp;
67 if never!(!text.starts_with('\'')) {
68 tmp = format!("'{}", text);
69 text = &tmp;
70 }
71 ast_from_text(&format!("fn f<{}>() {{ }}", text))
72}
73
34// FIXME: replace stringly-typed constructor with a family of typed ctors, a-la 74// FIXME: replace stringly-typed constructor with a family of typed ctors, a-la
35// `expr_xxx`. 75// `expr_xxx`.
36pub fn ty(text: &str) -> ast::Type { 76pub fn ty(text: &str) -> ast::Type {
37 ast_from_text(&format!("fn f() -> {} {{}}", text)) 77 ty_from_text(text)
38} 78}
39pub fn ty_unit() -> ast::Type { 79pub fn ty_unit() -> ast::Type {
40 ty("()") 80 ty_from_text("()")
41} 81}
42pub fn ty_tuple(types: impl IntoIterator<Item = ast::Type>) -> ast::Type { 82pub fn ty_tuple(types: impl IntoIterator<Item = ast::Type>) -> ast::Type {
43 let mut count: usize = 0; 83 let mut count: usize = 0;
@@ -46,19 +86,20 @@ pub fn ty_tuple(types: impl IntoIterator<Item = ast::Type>) -> ast::Type {
46 contents.push(','); 86 contents.push(',');
47 } 87 }
48 88
49 ty(&format!("({})", contents)) 89 ty_from_text(&format!("({})", contents))
50}
51// FIXME: handle path to type
52pub fn ty_generic(name: ast::NameRef, types: impl IntoIterator<Item = ast::Type>) -> ast::Type {
53 let contents = types.into_iter().join(", ");
54 ty(&format!("{}<{}>", name, contents))
55} 90}
56pub fn ty_ref(target: ast::Type, exclusive: bool) -> ast::Type { 91pub fn ty_ref(target: ast::Type, exclusive: bool) -> ast::Type {
57 ty(&if exclusive { format!("&mut {}", target) } else { format!("&{}", target) }) 92 ty_from_text(&if exclusive { format!("&mut {}", target) } else { format!("&{}", target) })
93}
94pub fn ty_path(path: ast::Path) -> ast::Type {
95 ty_from_text(&path.to_string())
96}
97fn ty_from_text(text: &str) -> ast::Type {
98 ast_from_text(&format!("type _T = {};", text))
58} 99}
59 100
60pub fn assoc_item_list() -> ast::AssocItemList { 101pub fn assoc_item_list() -> ast::AssocItemList {
61 ast_from_text("impl C for D {};") 102 ast_from_text("impl C for D {}")
62} 103}
63 104
64pub fn impl_trait(trait_: ast::Path, ty: ast::Path) -> ast::Impl { 105pub fn impl_trait(trait_: ast::Path, ty: ast::Path) -> ast::Impl {
@@ -88,7 +129,7 @@ pub fn path_unqualified(segment: ast::PathSegment) -> ast::Path {
88pub fn path_qualified(qual: ast::Path, segment: ast::PathSegment) -> ast::Path { 129pub fn path_qualified(qual: ast::Path, segment: ast::PathSegment) -> ast::Path {
89 ast_from_text(&format!("{}::{}", qual, segment)) 130 ast_from_text(&format!("{}::{}", qual, segment))
90} 131}
91 132// FIXME: path concatenation operation doesn't make sense as AST op.
92pub fn path_concat(first: ast::Path, second: ast::Path) -> ast::Path { 133pub fn path_concat(first: ast::Path, second: ast::Path) -> ast::Path {
93 ast_from_text(&format!("{}::{}", first, second)) 134 ast_from_text(&format!("{}::{}", first, second))
94} 135}
@@ -104,15 +145,14 @@ pub fn path_from_segments(
104 format!("use {};", segments) 145 format!("use {};", segments)
105 }) 146 })
106} 147}
107 148// FIXME: should not be pub
108pub fn path_from_text(text: &str) -> ast::Path { 149pub fn path_from_text(text: &str) -> ast::Path {
109 ast_from_text(&format!("fn main() {{ let test = {}; }}", text)) 150 ast_from_text(&format!("fn main() {{ let test = {}; }}", text))
110} 151}
111 152
112pub fn glob_use_tree() -> ast::UseTree { 153pub fn use_tree_glob() -> ast::UseTree {
113 ast_from_text("use *;") 154 ast_from_text("use *;")
114} 155}
115
116pub fn use_tree( 156pub fn use_tree(
117 path: ast::Path, 157 path: ast::Path,
118 use_tree_list: Option<ast::UseTreeList>, 158 use_tree_list: Option<ast::UseTreeList>,
@@ -207,15 +247,6 @@ pub fn expr_literal(text: &str) -> ast::Literal {
207pub fn expr_empty_block() -> ast::Expr { 247pub fn expr_empty_block() -> ast::Expr {
208 expr_from_text("{}") 248 expr_from_text("{}")
209} 249}
210pub fn expr_unimplemented() -> ast::Expr {
211 expr_from_text("unimplemented!()")
212}
213pub fn expr_unreachable() -> ast::Expr {
214 expr_from_text("unreachable!()")
215}
216pub fn expr_todo() -> ast::Expr {
217 expr_from_text("todo!()")
218}
219pub fn expr_path(path: ast::Path) -> ast::Expr { 250pub fn expr_path(path: ast::Path) -> ast::Expr {
220 expr_from_text(&path.to_string()) 251 expr_from_text(&path.to_string())
221} 252}
@@ -275,6 +306,9 @@ pub fn expr_tuple(elements: impl IntoIterator<Item = ast::Expr>) -> ast::Expr {
275 let expr = elements.into_iter().format(", "); 306 let expr = elements.into_iter().format(", ");
276 expr_from_text(&format!("({})", expr)) 307 expr_from_text(&format!("({})", expr))
277} 308}
309pub fn expr_assignment(lhs: ast::Expr, rhs: ast::Expr) -> ast::Expr {
310 expr_from_text(&format!("{} = {}", lhs, rhs))
311}
278fn expr_from_text(text: &str) -> ast::Expr { 312fn expr_from_text(text: &str) -> ast::Expr {
279 ast_from_text(&format!("const C: () = {};", text)) 313 ast_from_text(&format!("const C: () = {};", text))
280} 314}
@@ -441,17 +475,6 @@ pub fn expr_stmt(expr: ast::Expr) -> ast::ExprStmt {
441 ast_from_text(&format!("fn f() {{ {}{} (); }}", expr, semi)) 475 ast_from_text(&format!("fn f() {{ {}{} (); }}", expr, semi))
442} 476}
443 477
444pub fn token(kind: SyntaxKind) -> SyntaxToken {
445 tokens::SOURCE_FILE
446 .tree()
447 .syntax()
448 .clone_for_update()
449 .descendants_with_tokens()
450 .filter_map(|it| it.into_token())
451 .find(|it| it.kind() == kind)
452 .unwrap_or_else(|| panic!("unhandled token: {:?}", kind))
453}
454
455pub fn param(pat: ast::Pat, ty: ast::Type) -> ast::Param { 478pub fn param(pat: ast::Pat, ty: ast::Type) -> ast::Param {
456 ast_from_text(&format!("fn f({}: {}) {{ }}", pat, ty)) 479 ast_from_text(&format!("fn f({}: {}) {{ }}", pat, ty))
457} 480}
@@ -473,7 +496,7 @@ pub fn param_list(
473 ast_from_text(&list) 496 ast_from_text(&list)
474} 497}
475 498
476pub fn generic_param(name: String, ty: Option<ast::TypeBoundList>) -> ast::GenericParam { 499pub fn type_param(name: ast::Name, ty: Option<ast::TypeBoundList>) -> ast::TypeParam {
477 let bound = match ty { 500 let bound = match ty {
478 Some(it) => format!(": {}", it), 501 Some(it) => format!(": {}", it),
479 None => String::new(), 502 None => String::new(),
@@ -481,6 +504,10 @@ pub fn generic_param(name: String, ty: Option<ast::TypeBoundList>) -> ast::Gener
481 ast_from_text(&format!("fn f<{}{}>() {{ }}", name, bound)) 504 ast_from_text(&format!("fn f<{}{}>() {{ }}", name, bound))
482} 505}
483 506
507pub fn lifetime_param(lifetime: ast::Lifetime) -> ast::LifetimeParam {
508 ast_from_text(&format!("fn f<{}>() {{ }}", lifetime))
509}
510
484pub fn generic_param_list( 511pub fn generic_param_list(
485 pats: impl IntoIterator<Item = ast::GenericParam>, 512 pats: impl IntoIterator<Item = ast::GenericParam>,
486) -> ast::GenericParamList { 513) -> ast::GenericParamList {
@@ -574,15 +601,20 @@ fn ast_from_text<N: AstNode>(text: &str) -> N {
574 panic!("Failed to make ast node `{}` from text {}", std::any::type_name::<N>(), text) 601 panic!("Failed to make ast node `{}` from text {}", std::any::type_name::<N>(), text)
575 } 602 }
576 }; 603 };
577 let node = node.syntax().clone(); 604 let node = node.clone_subtree();
578 let node = unroot(node);
579 let node = N::cast(node).unwrap();
580 assert_eq!(node.syntax().text_range().start(), 0.into()); 605 assert_eq!(node.syntax().text_range().start(), 0.into());
581 node 606 node
582} 607}
583 608
584fn unroot(n: SyntaxNode) -> SyntaxNode { 609pub fn token(kind: SyntaxKind) -> SyntaxToken {
585 SyntaxNode::new_root(n.green().into()) 610 tokens::SOURCE_FILE
611 .tree()
612 .syntax()
613 .clone_for_update()
614 .descendants_with_tokens()
615 .filter_map(|it| it.into_token())
616 .find(|it| it.kind() == kind)
617 .unwrap_or_else(|| panic!("unhandled token: {:?}", kind))
586} 618}
587 619
588pub mod tokens { 620pub mod tokens {
diff --git a/crates/syntax/src/ast/token_ext.rs b/crates/syntax/src/ast/token_ext.rs
index 29d25a58a..4b1e1ccee 100644
--- a/crates/syntax/src/ast/token_ext.rs
+++ b/crates/syntax/src/ast/token_ext.rs
@@ -143,6 +143,30 @@ impl QuoteOffsets {
143 } 143 }
144} 144}
145 145
146pub trait IsString: AstToken {
147 fn quote_offsets(&self) -> Option<QuoteOffsets> {
148 let text = self.text();
149 let offsets = QuoteOffsets::new(text)?;
150 let o = self.syntax().text_range().start();
151 let offsets = QuoteOffsets {
152 quotes: (offsets.quotes.0 + o, offsets.quotes.1 + o),
153 contents: offsets.contents + o,
154 };
155 Some(offsets)
156 }
157 fn text_range_between_quotes(&self) -> Option<TextRange> {
158 self.quote_offsets().map(|it| it.contents)
159 }
160 fn open_quote_text_range(&self) -> Option<TextRange> {
161 self.quote_offsets().map(|it| it.quotes.0)
162 }
163 fn close_quote_text_range(&self) -> Option<TextRange> {
164 self.quote_offsets().map(|it| it.quotes.1)
165 }
166}
167
168impl IsString for ast::String {}
169
146impl ast::String { 170impl ast::String {
147 pub fn is_raw(&self) -> bool { 171 pub fn is_raw(&self) -> bool {
148 self.text().starts_with('r') 172 self.text().starts_with('r')
@@ -187,32 +211,49 @@ impl ast::String {
187 (false, false) => Some(Cow::Owned(buf)), 211 (false, false) => Some(Cow::Owned(buf)),
188 } 212 }
189 } 213 }
190
191 pub fn quote_offsets(&self) -> Option<QuoteOffsets> {
192 let text = self.text();
193 let offsets = QuoteOffsets::new(text)?;
194 let o = self.syntax().text_range().start();
195 let offsets = QuoteOffsets {
196 quotes: (offsets.quotes.0 + o, offsets.quotes.1 + o),
197 contents: offsets.contents + o,
198 };
199 Some(offsets)
200 }
201 pub fn text_range_between_quotes(&self) -> Option<TextRange> {
202 self.quote_offsets().map(|it| it.contents)
203 }
204 pub fn open_quote_text_range(&self) -> Option<TextRange> {
205 self.quote_offsets().map(|it| it.quotes.0)
206 }
207 pub fn close_quote_text_range(&self) -> Option<TextRange> {
208 self.quote_offsets().map(|it| it.quotes.1)
209 }
210} 214}
211 215
216impl IsString for ast::ByteString {}
217
212impl ast::ByteString { 218impl ast::ByteString {
213 pub fn is_raw(&self) -> bool { 219 pub fn is_raw(&self) -> bool {
214 self.text().starts_with("br") 220 self.text().starts_with("br")
215 } 221 }
222
223 pub fn value(&self) -> Option<Cow<'_, [u8]>> {
224 if self.is_raw() {
225 let text = self.text();
226 let text =
227 &text[self.text_range_between_quotes()? - self.syntax().text_range().start()];
228 return Some(Cow::Borrowed(text.as_bytes()));
229 }
230
231 let text = self.text();
232 let text = &text[self.text_range_between_quotes()? - self.syntax().text_range().start()];
233
234 let mut buf: Vec<u8> = Vec::new();
235 let mut text_iter = text.chars();
236 let mut has_error = false;
237 unescape_literal(text, Mode::ByteStr, &mut |char_range, unescaped_char| match (
238 unescaped_char,
239 buf.capacity() == 0,
240 ) {
241 (Ok(c), false) => buf.push(c as u8),
242 (Ok(c), true) if char_range.len() == 1 && Some(c) == text_iter.next() => (),
243 (Ok(c), true) => {
244 buf.reserve_exact(text.len());
245 buf.extend_from_slice(&text[..char_range.start].as_bytes());
246 buf.push(c as u8);
247 }
248 (Err(_), _) => has_error = true,
249 });
250
251 match (has_error, buf.capacity() == 0) {
252 (true, _) => None,
253 (false, true) => Some(Cow::Borrowed(text.as_bytes())),
254 (false, false) => Some(Cow::Owned(buf)),
255 }
256 }
216} 257}
217 258
218#[derive(Debug)] 259#[derive(Debug)]
diff --git a/crates/syntax/src/parsing.rs b/crates/syntax/src/parsing.rs
index 333bde54a..431ed0699 100644
--- a/crates/syntax/src/parsing.rs
+++ b/crates/syntax/src/parsing.rs
@@ -6,14 +6,13 @@ mod text_token_source;
6mod text_tree_sink; 6mod text_tree_sink;
7mod reparsing; 7mod reparsing;
8 8
9use crate::{syntax_node::GreenNode, AstNode, SyntaxError, SyntaxNode}; 9use parser::SyntaxKind;
10use text_token_source::TextTokenSource; 10use text_token_source::TextTokenSource;
11use text_tree_sink::TextTreeSink; 11use text_tree_sink::TextTreeSink;
12 12
13pub(crate) use lexer::*; 13use crate::{syntax_node::GreenNode, AstNode, SyntaxError, SyntaxNode};
14 14
15pub(crate) use self::reparsing::incremental_reparse; 15pub(crate) use crate::parsing::{lexer::*, reparsing::incremental_reparse};
16use parser::SyntaxKind;
17 16
18pub(crate) fn parse_text(text: &str) -> (GreenNode, Vec<SyntaxError>) { 17pub(crate) fn parse_text(text: &str) -> (GreenNode, Vec<SyntaxError>) {
19 let (tokens, lexer_errors) = tokenize(&text); 18 let (tokens, lexer_errors) = tokenize(&text);
diff --git a/crates/syntax/src/parsing/text_tree_sink.rs b/crates/syntax/src/parsing/text_tree_sink.rs
index 1934204ea..d63ec080b 100644
--- a/crates/syntax/src/parsing/text_tree_sink.rs
+++ b/crates/syntax/src/parsing/text_tree_sink.rs
@@ -147,8 +147,8 @@ fn n_attached_trivias<'a>(
147 trivias: impl Iterator<Item = (SyntaxKind, &'a str)>, 147 trivias: impl Iterator<Item = (SyntaxKind, &'a str)>,
148) -> usize { 148) -> usize {
149 match kind { 149 match kind {
150 MACRO_CALL | MACRO_RULES | MACRO_DEF | CONST | TYPE_ALIAS | STRUCT | UNION | ENUM 150 CONST | ENUM | FN | IMPL | MACRO_CALL | MACRO_DEF | MACRO_RULES | MODULE | RECORD_FIELD
151 | VARIANT | FN | TRAIT | MODULE | RECORD_FIELD | STATIC | USE => { 151 | STATIC | STRUCT | TRAIT | TUPLE_FIELD | TYPE_ALIAS | UNION | USE | VARIANT => {
152 let mut res = 0; 152 let mut res = 0;
153 let mut trivias = trivias.enumerate().peekable(); 153 let mut trivias = trivias.enumerate().peekable();
154 154
diff --git a/crates/syntax/src/ted.rs b/crates/syntax/src/ted.rs
index 91a06101f..a50c0dbca 100644
--- a/crates/syntax/src/ted.rs
+++ b/crates/syntax/src/ted.rs
@@ -125,8 +125,11 @@ pub fn remove_all_iter(range: impl IntoIterator<Item = SyntaxElement>) {
125} 125}
126 126
127pub fn replace(old: impl Element, new: impl Element) { 127pub fn replace(old: impl Element, new: impl Element) {
128 replace_with_many(old, vec![new.syntax_element()])
129}
130pub fn replace_with_many(old: impl Element, new: Vec<SyntaxElement>) {
128 let old = old.syntax_element(); 131 let old = old.syntax_element();
129 replace_all(old.clone()..=old, vec![new.syntax_element()]) 132 replace_all(old.clone()..=old, new)
130} 133}
131pub fn replace_all(range: RangeInclusive<SyntaxElement>, new: Vec<SyntaxElement>) { 134pub fn replace_all(range: RangeInclusive<SyntaxElement>, new: Vec<SyntaxElement>) {
132 let start = range.start().index(); 135 let start = range.start().index();
diff --git a/crates/syntax/test_data/parser/ok/0045_block_attrs.rast b/crates/syntax/test_data/parser/ok/0045_block_attrs.rast
index 50ab52d32..5e50b4e0b 100644
--- a/crates/syntax/test_data/parser/ok/0045_block_attrs.rast
+++ b/crates/syntax/test_data/parser/ok/0045_block_attrs.rast
@@ -127,9 +127,9 @@ [email protected]
127 [email protected] "\n" 127 [email protected] "\n"
128 [email protected] "}" 128 [email protected] "}"
129 [email protected] "\n\n" 129 [email protected] "\n\n"
130 COMMENT@541..601 "// https://github.com ..." 130 IMPL@541..763
131 WHITESPACE@601..602 "\n" 131 COMMENT@541..601 "// https://github.com ..."
132 IMPL@602..763 132 WHITESPACE@601..602 "\n"
133 [email protected] "impl" 133 [email protected] "impl"
134 [email protected] " " 134 [email protected] " "
135 [email protected] 135 [email protected]