aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to 'crates')
-rw-r--r--crates/assists/src/handlers/auto_import.rs4
-rw-r--r--crates/assists/src/handlers/change_visibility.rs18
-rw-r--r--crates/assists/src/handlers/fill_match_arms.rs2
-rw-r--r--crates/assists/src/handlers/fix_visibility.rs6
-rw-r--r--crates/assists/src/handlers/generate_from_impl_for_enum.rs2
-rw-r--r--crates/assists/src/handlers/generate_impl.rs42
-rw-r--r--crates/assists/src/handlers/qualify_path.rs1048
-rw-r--r--crates/assists/src/handlers/replace_string_with_char.rs141
-rw-r--r--crates/assists/src/lib.rs4
-rw-r--r--crates/assists/src/tests/generated.rs36
-rw-r--r--crates/assists/src/utils.rs25
-rw-r--r--crates/assists/src/utils/import_assets.rs40
-rw-r--r--crates/base_db/src/fixture.rs4
-rw-r--r--crates/base_db/src/input.rs76
-rw-r--r--crates/base_db/src/lib.rs4
-rw-r--r--crates/call_info/Cargo.toml26
-rw-r--r--crates/call_info/src/lib.rs (renamed from crates/ide/src/call_info.rs)47
-rw-r--r--crates/cfg/Cargo.toml1
-rw-r--r--crates/cfg/src/cfg_expr.rs113
-rw-r--r--crates/cfg/src/dnf.rs320
-rw-r--r--crates/cfg/src/lib.rs130
-rw-r--r--crates/cfg/src/tests.rs193
-rw-r--r--crates/completion/Cargo.toml32
-rw-r--r--crates/completion/src/complete_attribute.rs (renamed from crates/ide/src/completion/complete_attribute.rs)135
-rw-r--r--crates/completion/src/complete_dot.rs (renamed from crates/ide/src/completion/complete_dot.rs)19
-rw-r--r--crates/completion/src/complete_fn_param.rs (renamed from crates/ide/src/completion/complete_fn_param.rs)4
-rw-r--r--crates/completion/src/complete_keyword.rs (renamed from crates/ide/src/completion/complete_keyword.rs)8
-rw-r--r--crates/completion/src/complete_macro_in_item_position.rs (renamed from crates/ide/src/completion/complete_macro_in_item_position.rs)6
-rw-r--r--crates/completion/src/complete_mod.rs (renamed from crates/ide/src/completion/complete_mod.rs)2
-rw-r--r--crates/completion/src/complete_pattern.rs (renamed from crates/ide/src/completion/complete_pattern.rs)6
-rw-r--r--crates/completion/src/complete_postfix.rs (renamed from crates/ide/src/completion/complete_postfix.rs)12
-rw-r--r--crates/completion/src/complete_postfix/format_like.rs (renamed from crates/ide/src/completion/complete_postfix/format_like.rs)2
-rw-r--r--crates/completion/src/complete_qualified_path.rs (renamed from crates/ide/src/completion/complete_qualified_path.rs)4
-rw-r--r--crates/completion/src/complete_record.rs (renamed from crates/ide/src/completion/complete_record.rs)4
-rw-r--r--crates/completion/src/complete_snippet.rs (renamed from crates/ide/src/completion/complete_snippet.rs)6
-rw-r--r--crates/completion/src/complete_trait_impl.rs (renamed from crates/ide/src/completion/complete_trait_impl.rs)13
-rw-r--r--crates/completion/src/complete_unqualified_path.rs (renamed from crates/ide/src/completion/complete_unqualified_path.rs)4
-rw-r--r--crates/completion/src/completion_config.rs (renamed from crates/ide/src/completion/completion_config.rs)0
-rw-r--r--crates/completion/src/completion_context.rs (renamed from crates/ide/src/completion/completion_context.rs)53
-rw-r--r--crates/completion/src/completion_item.rs (renamed from crates/ide/src/completion/completion_item.rs)31
-rw-r--r--crates/completion/src/generated_lint_completions.rs5
-rw-r--r--crates/completion/src/lib.rs (renamed from crates/ide/src/completion.rs)77
-rw-r--r--crates/completion/src/patterns.rs (renamed from crates/ide/src/completion/patterns.rs)48
-rw-r--r--crates/completion/src/presentation.rs (renamed from crates/ide/src/completion/presentation.rs)140
-rw-r--r--crates/completion/src/test_utils.rs (renamed from crates/ide/src/completion/test_utils.rs)55
-rw-r--r--crates/flycheck/Cargo.toml2
-rw-r--r--crates/hir/src/code_model.rs7
-rw-r--r--crates/hir/src/diagnostics.rs2
-rw-r--r--crates/hir_def/src/attr.rs14
-rw-r--r--crates/hir_def/src/body.rs69
-rw-r--r--crates/hir_def/src/body/diagnostics.rs20
-rw-r--r--crates/hir_def/src/body/lower.rs60
-rw-r--r--crates/hir_def/src/body/tests.rs75
-rw-r--r--crates/hir_def/src/diagnostics.rs54
-rw-r--r--crates/hir_def/src/import_map.rs6
-rw-r--r--crates/hir_def/src/item_tree.rs18
-rw-r--r--crates/hir_def/src/item_tree/lower.rs47
-rw-r--r--crates/hir_def/src/nameres.rs29
-rw-r--r--crates/hir_def/src/nameres/collector.rs30
-rw-r--r--crates/hir_def/src/nameres/tests/diagnostics.rs56
-rw-r--r--crates/hir_def/src/test_db.rs44
-rw-r--r--crates/hir_expand/src/db.rs1
-rw-r--r--crates/hir_expand/src/name.rs36
-rw-r--r--crates/hir_ty/src/diagnostics.rs55
-rw-r--r--crates/hir_ty/src/diagnostics/decl_check.rs28
-rw-r--r--crates/hir_ty/src/diagnostics/decl_check/case_conv.rs9
-rw-r--r--crates/hir_ty/src/diagnostics/unsafe_check.rs18
-rw-r--r--crates/hir_ty/src/traits.rs30
-rw-r--r--crates/ide/Cargo.toml4
-rw-r--r--crates/ide/src/call_hierarchy.rs4
-rw-r--r--crates/ide/src/completion/generated_features.rs4
-rw-r--r--crates/ide/src/diagnostics.rs91
-rw-r--r--crates/ide/src/diagnostics/field_shorthand.rs32
-rw-r--r--crates/ide/src/display.rs81
-rw-r--r--crates/ide/src/doc_links.rs36
-rw-r--r--crates/ide/src/hover.rs6
-rw-r--r--crates/ide/src/inlay_hints.rs53
-rw-r--r--crates/ide/src/lib.rs10
-rw-r--r--crates/ide/src/prime_caches.rs3
-rw-r--r--crates/ide/src/runnables.rs84
-rw-r--r--crates/ide/src/status.rs2
-rw-r--r--crates/ide/src/syntax_highlighting/injection.rs6
-rw-r--r--crates/proc_macro_srv/Cargo.toml2
-rw-r--r--crates/project_model/Cargo.toml2
-rw-r--r--crates/project_model/src/lib.rs16
-rw-r--r--crates/project_model/src/project_json.rs7
-rw-r--r--crates/rust-analyzer/Cargo.toml2
-rw-r--r--crates/rust-analyzer/src/cargo_target_spec.rs10
-rw-r--r--crates/rust-analyzer/src/cli/diagnostics.rs8
-rw-r--r--crates/rust-analyzer/src/dispatch.rs6
-rw-r--r--crates/rust-analyzer/src/handlers.rs16
-rw-r--r--crates/rust-analyzer/src/to_proto.rs73
-rw-r--r--crates/stdx/src/panic_context.rs4
-rw-r--r--crates/syntax/Cargo.toml4
-rw-r--r--crates/syntax/src/algo.rs417
-rw-r--r--crates/syntax/src/ast/make.rs3
-rw-r--r--crates/syntax/src/display.rs83
-rw-r--r--crates/syntax/src/lib.rs1
-rw-r--r--crates/vfs-notify/src/lib.rs17
99 files changed, 3920 insertions, 825 deletions
diff --git a/crates/assists/src/handlers/auto_import.rs b/crates/assists/src/handlers/auto_import.rs
index 4a7059c83..e49e641b3 100644
--- a/crates/assists/src/handlers/auto_import.rs
+++ b/crates/assists/src/handlers/auto_import.rs
@@ -6,7 +6,7 @@ use crate::{
6 AssistContext, AssistId, AssistKind, Assists, GroupLabel, 6 AssistContext, AssistId, AssistKind, Assists, GroupLabel,
7}; 7};
8 8
9// Feature: Import Insertion 9// Feature: Auto Import
10// 10//
11// Using the `auto-import` assist it is possible to insert missing imports for unresolved items. 11// Using the `auto-import` assist it is possible to insert missing imports for unresolved items.
12// When inserting an import it will do so in a structured manner by keeping imports grouped, 12// When inserting an import it will do so in a structured manner by keeping imports grouped,
@@ -100,7 +100,7 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
100 let group = import_group_message(import_assets.import_candidate()); 100 let group = import_group_message(import_assets.import_candidate());
101 let scope = ImportScope::find_insert_use_container(import_assets.syntax_under_caret(), ctx)?; 101 let scope = ImportScope::find_insert_use_container(import_assets.syntax_under_caret(), ctx)?;
102 let syntax = scope.as_syntax_node(); 102 let syntax = scope.as_syntax_node();
103 for import in proposed_imports { 103 for (import, _) in proposed_imports {
104 acc.add_group( 104 acc.add_group(
105 &group, 105 &group,
106 AssistId("auto_import", AssistKind::QuickFix), 106 AssistId("auto_import", AssistKind::QuickFix),
diff --git a/crates/assists/src/handlers/change_visibility.rs b/crates/assists/src/handlers/change_visibility.rs
index 32dc05378..22d7c95d9 100644
--- a/crates/assists/src/handlers/change_visibility.rs
+++ b/crates/assists/src/handlers/change_visibility.rs
@@ -1,7 +1,7 @@
1use syntax::{ 1use syntax::{
2 ast::{self, NameOwner, VisibilityOwner}, 2 ast::{self, NameOwner, VisibilityOwner},
3 AstNode, 3 AstNode,
4 SyntaxKind::{CONST, ENUM, FN, MODULE, STATIC, STRUCT, TRAIT, VISIBILITY}, 4 SyntaxKind::{CONST, ENUM, FN, MODULE, STATIC, STRUCT, TRAIT, TYPE_ALIAS, VISIBILITY},
5 T, 5 T,
6}; 6};
7use test_utils::mark; 7use test_utils::mark;
@@ -30,13 +30,20 @@ fn add_vis(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
30 let item_keyword = ctx.token_at_offset().find(|leaf| { 30 let item_keyword = ctx.token_at_offset().find(|leaf| {
31 matches!( 31 matches!(
32 leaf.kind(), 32 leaf.kind(),
33 T![const] | T![static] | T![fn] | T![mod] | T![struct] | T![enum] | T![trait] 33 T![const]
34 | T![static]
35 | T![fn]
36 | T![mod]
37 | T![struct]
38 | T![enum]
39 | T![trait]
40 | T![type]
34 ) 41 )
35 }); 42 });
36 43
37 let (offset, target) = if let Some(keyword) = item_keyword { 44 let (offset, target) = if let Some(keyword) = item_keyword {
38 let parent = keyword.parent(); 45 let parent = keyword.parent();
39 let def_kws = vec![CONST, STATIC, FN, MODULE, STRUCT, ENUM, TRAIT]; 46 let def_kws = vec![CONST, STATIC, TYPE_ALIAS, FN, MODULE, STRUCT, ENUM, TRAIT];
40 // Parent is not a definition, can't add visibility 47 // Parent is not a definition, can't add visibility
41 if !def_kws.iter().any(|&def_kw| def_kw == parent.kind()) { 48 if !def_kws.iter().any(|&def_kw| def_kw == parent.kind()) {
42 return None; 49 return None;
@@ -160,6 +167,11 @@ mod tests {
160 } 167 }
161 168
162 #[test] 169 #[test]
170 fn change_visibility_type_alias() {
171 check_assist(change_visibility, "<|>type T = ();", "pub(crate) type T = ();");
172 }
173
174 #[test]
163 fn change_visibility_handles_comment_attrs() { 175 fn change_visibility_handles_comment_attrs() {
164 check_assist( 176 check_assist(
165 change_visibility, 177 change_visibility,
diff --git a/crates/assists/src/handlers/fill_match_arms.rs b/crates/assists/src/handlers/fill_match_arms.rs
index 676f5ad92..eda45f5b3 100644
--- a/crates/assists/src/handlers/fill_match_arms.rs
+++ b/crates/assists/src/handlers/fill_match_arms.rs
@@ -59,7 +59,7 @@ pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option<
59 .filter(|variant_pat| is_variant_missing(&mut arms, variant_pat)) 59 .filter(|variant_pat| is_variant_missing(&mut arms, variant_pat))
60 .map(|pat| make::match_arm(iter::once(pat), make::expr_empty_block())) 60 .map(|pat| make::match_arm(iter::once(pat), make::expr_empty_block()))
61 .collect::<Vec<_>>(); 61 .collect::<Vec<_>>();
62 if Some(enum_def) == FamousDefs(&ctx.sema, module.krate()).core_option_Option() { 62 if Some(enum_def) == FamousDefs(&ctx.sema, Some(module.krate())).core_option_Option() {
63 // Match `Some` variant first. 63 // Match `Some` variant first.
64 mark::hit!(option_order); 64 mark::hit!(option_order);
65 variants.reverse() 65 variants.reverse()
diff --git a/crates/assists/src/handlers/fix_visibility.rs b/crates/assists/src/handlers/fix_visibility.rs
index d505e9444..66f74150c 100644
--- a/crates/assists/src/handlers/fix_visibility.rs
+++ b/crates/assists/src/handlers/fix_visibility.rs
@@ -1,9 +1,11 @@
1use base_db::FileId; 1use base_db::FileId;
2use hir::{db::HirDatabase, HasSource, HasVisibility, PathResolution}; 2use hir::{db::HirDatabase, HasSource, HasVisibility, PathResolution};
3use syntax::{ast, AstNode, TextRange, TextSize}; 3use syntax::{
4 ast::{self, VisibilityOwner},
5 AstNode, TextRange, TextSize,
6};
4 7
5use crate::{utils::vis_offset, AssistContext, AssistId, AssistKind, Assists}; 8use crate::{utils::vis_offset, AssistContext, AssistId, AssistKind, Assists};
6use ast::VisibilityOwner;
7 9
8// FIXME: this really should be a fix for diagnostic, rather than an assist. 10// FIXME: this really should be a fix for diagnostic, rather than an assist.
9 11
diff --git a/crates/assists/src/handlers/generate_from_impl_for_enum.rs b/crates/assists/src/handlers/generate_from_impl_for_enum.rs
index 7f04b9572..674e5a175 100644
--- a/crates/assists/src/handlers/generate_from_impl_for_enum.rs
+++ b/crates/assists/src/handlers/generate_from_impl_for_enum.rs
@@ -75,7 +75,7 @@ fn existing_from_impl(
75 let enum_ = variant.parent_enum(sema.db); 75 let enum_ = variant.parent_enum(sema.db);
76 let krate = enum_.module(sema.db).krate(); 76 let krate = enum_.module(sema.db).krate();
77 77
78 let from_trait = FamousDefs(sema, krate).core_convert_From()?; 78 let from_trait = FamousDefs(sema, Some(krate)).core_convert_From()?;
79 79
80 let enum_type = enum_.ty(sema.db); 80 let enum_type = enum_.ty(sema.db);
81 81
diff --git a/crates/assists/src/handlers/generate_impl.rs b/crates/assists/src/handlers/generate_impl.rs
index 9989109b5..114974465 100644
--- a/crates/assists/src/handlers/generate_impl.rs
+++ b/crates/assists/src/handlers/generate_impl.rs
@@ -1,6 +1,6 @@
1use itertools::Itertools; 1use itertools::Itertools;
2use stdx::format_to; 2use stdx::format_to;
3use syntax::ast::{self, AstNode, GenericParamsOwner, NameOwner}; 3use syntax::ast::{self, AstNode, AttrsOwner, GenericParamsOwner, NameOwner};
4 4
5use crate::{AssistContext, AssistId, AssistKind, Assists}; 5use crate::{AssistContext, AssistId, AssistKind, Assists};
6 6
@@ -27,6 +27,7 @@ pub(crate) fn generate_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()
27 let nominal = ctx.find_node_at_offset::<ast::AdtDef>()?; 27 let nominal = ctx.find_node_at_offset::<ast::AdtDef>()?;
28 let name = nominal.name()?; 28 let name = nominal.name()?;
29 let target = nominal.syntax().text_range(); 29 let target = nominal.syntax().text_range();
30
30 acc.add( 31 acc.add(
31 AssistId("generate_impl", AssistKind::Generate), 32 AssistId("generate_impl", AssistKind::Generate),
32 format!("Generate impl for `{}`", name), 33 format!("Generate impl for `{}`", name),
@@ -35,7 +36,15 @@ pub(crate) fn generate_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()
35 let type_params = nominal.generic_param_list(); 36 let type_params = nominal.generic_param_list();
36 let start_offset = nominal.syntax().text_range().end(); 37 let start_offset = nominal.syntax().text_range().end();
37 let mut buf = String::new(); 38 let mut buf = String::new();
38 buf.push_str("\n\nimpl"); 39 buf.push_str("\n\n");
40 nominal
41 .attrs()
42 .filter(|attr| {
43 attr.as_simple_call().map(|(name, _arg)| name == "cfg").unwrap_or(false)
44 })
45 .for_each(|attr| buf.push_str(format!("{}\n", attr.to_string()).as_str()));
46
47 buf.push_str("impl");
39 if let Some(type_params) = &type_params { 48 if let Some(type_params) = &type_params {
40 format_to!(buf, "{}", type_params.syntax()); 49 format_to!(buf, "{}", type_params.syntax());
41 } 50 }
@@ -91,6 +100,35 @@ mod tests {
91 "struct Foo<'a, T: Foo<'a>> {<|>}", 100 "struct Foo<'a, T: Foo<'a>> {<|>}",
92 "struct Foo<'a, T: Foo<'a>> {}\n\nimpl<'a, T: Foo<'a>> Foo<'a, T> {\n $0\n}", 101 "struct Foo<'a, T: Foo<'a>> {}\n\nimpl<'a, T: Foo<'a>> Foo<'a, T> {\n $0\n}",
93 ); 102 );
103 check_assist(
104 generate_impl,
105 r#"
106 #[cfg(feature = "foo")]
107 struct Foo<'a, T: Foo<'a>> {<|>}"#,
108 r#"
109 #[cfg(feature = "foo")]
110 struct Foo<'a, T: Foo<'a>> {}
111
112 #[cfg(feature = "foo")]
113 impl<'a, T: Foo<'a>> Foo<'a, T> {
114 $0
115 }"#,
116 );
117
118 check_assist(
119 generate_impl,
120 r#"
121 #[cfg(not(feature = "foo"))]
122 struct Foo<'a, T: Foo<'a>> {<|>}"#,
123 r#"
124 #[cfg(not(feature = "foo"))]
125 struct Foo<'a, T: Foo<'a>> {}
126
127 #[cfg(not(feature = "foo"))]
128 impl<'a, T: Foo<'a>> Foo<'a, T> {
129 $0
130 }"#,
131 );
94 } 132 }
95 133
96 #[test] 134 #[test]
diff --git a/crates/assists/src/handlers/qualify_path.rs b/crates/assists/src/handlers/qualify_path.rs
new file mode 100644
index 000000000..f436bdbbf
--- /dev/null
+++ b/crates/assists/src/handlers/qualify_path.rs
@@ -0,0 +1,1048 @@
1use std::iter;
2
3use hir::AsName;
4use ide_db::RootDatabase;
5use syntax::{
6 ast,
7 ast::{make, ArgListOwner},
8 AstNode,
9};
10use test_utils::mark;
11
12use crate::{
13 assist_context::{AssistContext, Assists},
14 utils::import_assets::{ImportAssets, ImportCandidate},
15 utils::mod_path_to_ast,
16 AssistId, AssistKind, GroupLabel,
17};
18
19// Assist: qualify_path
20//
21// If the name is unresolved, provides all possible qualified paths for it.
22//
23// ```
24// fn main() {
25// let map = HashMap<|>::new();
26// }
27// # pub mod std { pub mod collections { pub struct HashMap { } } }
28// ```
29// ->
30// ```
31// fn main() {
32// let map = std::collections::HashMap::new();
33// }
34// # pub mod std { pub mod collections { pub struct HashMap { } } }
35// ```
36pub(crate) fn qualify_path(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
37 let import_assets =
38 if let Some(path_under_caret) = ctx.find_node_at_offset_with_descend::<ast::Path>() {
39 ImportAssets::for_regular_path(path_under_caret, &ctx.sema)
40 } else if let Some(method_under_caret) =
41 ctx.find_node_at_offset_with_descend::<ast::MethodCallExpr>()
42 {
43 ImportAssets::for_method_call(method_under_caret, &ctx.sema)
44 } else {
45 None
46 }?;
47 let proposed_imports = import_assets.search_for_relative_paths(&ctx.sema);
48 if proposed_imports.is_empty() {
49 return None;
50 }
51
52 let candidate = import_assets.import_candidate();
53 let range = ctx.sema.original_range(import_assets.syntax_under_caret()).range;
54
55 let qualify_candidate = match candidate {
56 ImportCandidate::QualifierStart(_) => {
57 mark::hit!(qualify_path_qualifier_start);
58 let path = ast::Path::cast(import_assets.syntax_under_caret().clone())?;
59 let segment = path.segment()?;
60 QualifyCandidate::QualifierStart(segment)
61 }
62 ImportCandidate::UnqualifiedName(_) => {
63 mark::hit!(qualify_path_unqualified_name);
64 QualifyCandidate::UnqualifiedName
65 }
66 ImportCandidate::TraitAssocItem(_) => {
67 mark::hit!(qualify_path_trait_assoc_item);
68 let path = ast::Path::cast(import_assets.syntax_under_caret().clone())?;
69 let (qualifier, segment) = (path.qualifier()?, path.segment()?);
70 QualifyCandidate::TraitAssocItem(qualifier, segment)
71 }
72 ImportCandidate::TraitMethod(_) => {
73 mark::hit!(qualify_path_trait_method);
74 let mcall_expr = ast::MethodCallExpr::cast(import_assets.syntax_under_caret().clone())?;
75 QualifyCandidate::TraitMethod(ctx.sema.db, mcall_expr)
76 }
77 };
78
79 let group_label = group_label(candidate);
80 for (import, item) in proposed_imports {
81 acc.add_group(
82 &group_label,
83 AssistId("qualify_path", AssistKind::QuickFix),
84 label(candidate, &import),
85 range,
86 |builder| {
87 qualify_candidate.qualify(
88 |replace_with: String| builder.replace(range, replace_with),
89 import,
90 item,
91 )
92 },
93 );
94 }
95 Some(())
96}
97
98enum QualifyCandidate<'db> {
99 QualifierStart(ast::PathSegment),
100 UnqualifiedName,
101 TraitAssocItem(ast::Path, ast::PathSegment),
102 TraitMethod(&'db RootDatabase, ast::MethodCallExpr),
103}
104
105impl QualifyCandidate<'_> {
106 fn qualify(&self, mut replacer: impl FnMut(String), import: hir::ModPath, item: hir::ItemInNs) {
107 match self {
108 QualifyCandidate::QualifierStart(segment) => {
109 let import = mod_path_to_ast(&import);
110 replacer(format!("{}::{}", import, segment));
111 }
112 QualifyCandidate::UnqualifiedName => replacer(mod_path_to_ast(&import).to_string()),
113 QualifyCandidate::TraitAssocItem(qualifier, segment) => {
114 let import = mod_path_to_ast(&import);
115 replacer(format!("<{} as {}>::{}", qualifier, import, segment));
116 }
117 &QualifyCandidate::TraitMethod(db, ref mcall_expr) => {
118 Self::qualify_trait_method(db, mcall_expr, replacer, import, item);
119 }
120 }
121 }
122
123 fn qualify_trait_method(
124 db: &RootDatabase,
125 mcall_expr: &ast::MethodCallExpr,
126 mut replacer: impl FnMut(String),
127 import: hir::ModPath,
128 item: hir::ItemInNs,
129 ) -> Option<()> {
130 let receiver = mcall_expr.receiver()?;
131 let trait_method_name = mcall_expr.name_ref()?;
132 let arg_list = mcall_expr.arg_list().map(|arg_list| arg_list.args());
133 let trait_ = item_as_trait(item)?;
134 let method = find_trait_method(db, trait_, &trait_method_name)?;
135 if let Some(self_access) = method.self_param(db).map(|sp| sp.access(db)) {
136 let import = mod_path_to_ast(&import);
137 let receiver = match self_access {
138 hir::Access::Shared => make::expr_ref(receiver, false),
139 hir::Access::Exclusive => make::expr_ref(receiver, true),
140 hir::Access::Owned => receiver,
141 };
142 replacer(format!(
143 "{}::{}{}",
144 import,
145 trait_method_name,
146 match arg_list.clone() {
147 Some(args) => make::arg_list(iter::once(receiver).chain(args)),
148 None => make::arg_list(iter::once(receiver)),
149 }
150 ));
151 }
152 Some(())
153 }
154}
155
156fn find_trait_method(
157 db: &RootDatabase,
158 trait_: hir::Trait,
159 trait_method_name: &ast::NameRef,
160) -> Option<hir::Function> {
161 if let Some(hir::AssocItem::Function(method)) =
162 trait_.items(db).into_iter().find(|item: &hir::AssocItem| {
163 item.name(db).map(|name| name == trait_method_name.as_name()).unwrap_or(false)
164 })
165 {
166 Some(method)
167 } else {
168 None
169 }
170}
171
172fn item_as_trait(item: hir::ItemInNs) -> Option<hir::Trait> {
173 if let hir::ModuleDef::Trait(trait_) = hir::ModuleDef::from(item.as_module_def_id()?) {
174 Some(trait_)
175 } else {
176 None
177 }
178}
179
180fn group_label(candidate: &ImportCandidate) -> GroupLabel {
181 let name = match candidate {
182 ImportCandidate::UnqualifiedName(it) | ImportCandidate::QualifierStart(it) => &it.name,
183 ImportCandidate::TraitAssocItem(it) | ImportCandidate::TraitMethod(it) => &it.name,
184 };
185 GroupLabel(format!("Qualify {}", name))
186}
187
188fn label(candidate: &ImportCandidate, import: &hir::ModPath) -> String {
189 match candidate {
190 ImportCandidate::UnqualifiedName(_) => format!("Qualify as `{}`", &import),
191 ImportCandidate::QualifierStart(_) => format!("Qualify with `{}`", &import),
192 ImportCandidate::TraitAssocItem(_) => format!("Qualify `{}`", &import),
193 ImportCandidate::TraitMethod(_) => format!("Qualify with cast as `{}`", &import),
194 }
195}
196
197#[cfg(test)]
198mod tests {
199 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
200
201 use super::*;
202
203 #[test]
204 fn applicable_when_found_an_import_partial() {
205 mark::check!(qualify_path_unqualified_name);
206 check_assist(
207 qualify_path,
208 r"
209 mod std {
210 pub mod fmt {
211 pub struct Formatter;
212 }
213 }
214
215 use std::fmt;
216
217 <|>Formatter
218 ",
219 r"
220 mod std {
221 pub mod fmt {
222 pub struct Formatter;
223 }
224 }
225
226 use std::fmt;
227
228 fmt::Formatter
229 ",
230 );
231 }
232
233 #[test]
234 fn applicable_when_found_an_import() {
235 check_assist(
236 qualify_path,
237 r"
238 <|>PubStruct
239
240 pub mod PubMod {
241 pub struct PubStruct;
242 }
243 ",
244 r"
245 PubMod::PubStruct
246
247 pub mod PubMod {
248 pub struct PubStruct;
249 }
250 ",
251 );
252 }
253
254 #[test]
255 fn applicable_in_macros() {
256 check_assist(
257 qualify_path,
258 r"
259 macro_rules! foo {
260 ($i:ident) => { fn foo(a: $i) {} }
261 }
262 foo!(Pub<|>Struct);
263
264 pub mod PubMod {
265 pub struct PubStruct;
266 }
267 ",
268 r"
269 macro_rules! foo {
270 ($i:ident) => { fn foo(a: $i) {} }
271 }
272 foo!(PubMod::PubStruct);
273
274 pub mod PubMod {
275 pub struct PubStruct;
276 }
277 ",
278 );
279 }
280
281 #[test]
282 fn applicable_when_found_multiple_imports() {
283 check_assist(
284 qualify_path,
285 r"
286 PubSt<|>ruct
287
288 pub mod PubMod1 {
289 pub struct PubStruct;
290 }
291 pub mod PubMod2 {
292 pub struct PubStruct;
293 }
294 pub mod PubMod3 {
295 pub struct PubStruct;
296 }
297 ",
298 r"
299 PubMod3::PubStruct
300
301 pub mod PubMod1 {
302 pub struct PubStruct;
303 }
304 pub mod PubMod2 {
305 pub struct PubStruct;
306 }
307 pub mod PubMod3 {
308 pub struct PubStruct;
309 }
310 ",
311 );
312 }
313
314 #[test]
315 fn not_applicable_for_already_imported_types() {
316 check_assist_not_applicable(
317 qualify_path,
318 r"
319 use PubMod::PubStruct;
320
321 PubStruct<|>
322
323 pub mod PubMod {
324 pub struct PubStruct;
325 }
326 ",
327 );
328 }
329
330 #[test]
331 fn not_applicable_for_types_with_private_paths() {
332 check_assist_not_applicable(
333 qualify_path,
334 r"
335 PrivateStruct<|>
336
337 pub mod PubMod {
338 struct PrivateStruct;
339 }
340 ",
341 );
342 }
343
344 #[test]
345 fn not_applicable_when_no_imports_found() {
346 check_assist_not_applicable(
347 qualify_path,
348 "
349 PubStruct<|>",
350 );
351 }
352
353 #[test]
354 fn not_applicable_in_import_statements() {
355 check_assist_not_applicable(
356 qualify_path,
357 r"
358 use PubStruct<|>;
359
360 pub mod PubMod {
361 pub struct PubStruct;
362 }",
363 );
364 }
365
366 #[test]
367 fn qualify_function() {
368 check_assist(
369 qualify_path,
370 r"
371 test_function<|>
372
373 pub mod PubMod {
374 pub fn test_function() {};
375 }
376 ",
377 r"
378 PubMod::test_function
379
380 pub mod PubMod {
381 pub fn test_function() {};
382 }
383 ",
384 );
385 }
386
387 #[test]
388 fn qualify_macro() {
389 check_assist(
390 qualify_path,
391 r"
392//- /lib.rs crate:crate_with_macro
393#[macro_export]
394macro_rules! foo {
395 () => ()
396}
397
398//- /main.rs crate:main deps:crate_with_macro
399fn main() {
400 foo<|>
401}
402",
403 r"
404fn main() {
405 crate_with_macro::foo
406}
407",
408 );
409 }
410
411 #[test]
412 fn qualify_path_target() {
413 check_assist_target(
414 qualify_path,
415 r"
416 struct AssistInfo {
417 group_label: Option<<|>GroupLabel>,
418 }
419
420 mod m { pub struct GroupLabel; }
421 ",
422 "GroupLabel",
423 )
424 }
425
426 #[test]
427 fn not_applicable_when_path_start_is_imported() {
428 check_assist_not_applicable(
429 qualify_path,
430 r"
431 pub mod mod1 {
432 pub mod mod2 {
433 pub mod mod3 {
434 pub struct TestStruct;
435 }
436 }
437 }
438
439 use mod1::mod2;
440 fn main() {
441 mod2::mod3::TestStruct<|>
442 }
443 ",
444 );
445 }
446
447 #[test]
448 fn not_applicable_for_imported_function() {
449 check_assist_not_applicable(
450 qualify_path,
451 r"
452 pub mod test_mod {
453 pub fn test_function() {}
454 }
455
456 use test_mod::test_function;
457 fn main() {
458 test_function<|>
459 }
460 ",
461 );
462 }
463
464 #[test]
465 fn associated_struct_function() {
466 check_assist(
467 qualify_path,
468 r"
469 mod test_mod {
470 pub struct TestStruct {}
471 impl TestStruct {
472 pub fn test_function() {}
473 }
474 }
475
476 fn main() {
477 TestStruct::test_function<|>
478 }
479 ",
480 r"
481 mod test_mod {
482 pub struct TestStruct {}
483 impl TestStruct {
484 pub fn test_function() {}
485 }
486 }
487
488 fn main() {
489 test_mod::TestStruct::test_function
490 }
491 ",
492 );
493 }
494
495 #[test]
496 fn associated_struct_const() {
497 mark::check!(qualify_path_qualifier_start);
498 check_assist(
499 qualify_path,
500 r"
501 mod test_mod {
502 pub struct TestStruct {}
503 impl TestStruct {
504 const TEST_CONST: u8 = 42;
505 }
506 }
507
508 fn main() {
509 TestStruct::TEST_CONST<|>
510 }
511 ",
512 r"
513 mod test_mod {
514 pub struct TestStruct {}
515 impl TestStruct {
516 const TEST_CONST: u8 = 42;
517 }
518 }
519
520 fn main() {
521 test_mod::TestStruct::TEST_CONST
522 }
523 ",
524 );
525 }
526
527 #[test]
528 fn associated_trait_function() {
529 check_assist(
530 qualify_path,
531 r"
532 mod test_mod {
533 pub trait TestTrait {
534 fn test_function();
535 }
536 pub struct TestStruct {}
537 impl TestTrait for TestStruct {
538 fn test_function() {}
539 }
540 }
541
542 fn main() {
543 test_mod::TestStruct::test_function<|>
544 }
545 ",
546 r"
547 mod test_mod {
548 pub trait TestTrait {
549 fn test_function();
550 }
551 pub struct TestStruct {}
552 impl TestTrait for TestStruct {
553 fn test_function() {}
554 }
555 }
556
557 fn main() {
558 <test_mod::TestStruct as test_mod::TestTrait>::test_function
559 }
560 ",
561 );
562 }
563
564 #[test]
565 fn not_applicable_for_imported_trait_for_function() {
566 check_assist_not_applicable(
567 qualify_path,
568 r"
569 mod test_mod {
570 pub trait TestTrait {
571 fn test_function();
572 }
573 pub trait TestTrait2 {
574 fn test_function();
575 }
576 pub enum TestEnum {
577 One,
578 Two,
579 }
580 impl TestTrait2 for TestEnum {
581 fn test_function() {}
582 }
583 impl TestTrait for TestEnum {
584 fn test_function() {}
585 }
586 }
587
588 use test_mod::TestTrait2;
589 fn main() {
590 test_mod::TestEnum::test_function<|>;
591 }
592 ",
593 )
594 }
595
596 #[test]
597 fn associated_trait_const() {
598 mark::check!(qualify_path_trait_assoc_item);
599 check_assist(
600 qualify_path,
601 r"
602 mod test_mod {
603 pub trait TestTrait {
604 const TEST_CONST: u8;
605 }
606 pub struct TestStruct {}
607 impl TestTrait for TestStruct {
608 const TEST_CONST: u8 = 42;
609 }
610 }
611
612 fn main() {
613 test_mod::TestStruct::TEST_CONST<|>
614 }
615 ",
616 r"
617 mod test_mod {
618 pub trait TestTrait {
619 const TEST_CONST: u8;
620 }
621 pub struct TestStruct {}
622 impl TestTrait for TestStruct {
623 const TEST_CONST: u8 = 42;
624 }
625 }
626
627 fn main() {
628 <test_mod::TestStruct as test_mod::TestTrait>::TEST_CONST
629 }
630 ",
631 );
632 }
633
634 #[test]
635 fn not_applicable_for_imported_trait_for_const() {
636 check_assist_not_applicable(
637 qualify_path,
638 r"
639 mod test_mod {
640 pub trait TestTrait {
641 const TEST_CONST: u8;
642 }
643 pub trait TestTrait2 {
644 const TEST_CONST: f64;
645 }
646 pub enum TestEnum {
647 One,
648 Two,
649 }
650 impl TestTrait2 for TestEnum {
651 const TEST_CONST: f64 = 42.0;
652 }
653 impl TestTrait for TestEnum {
654 const TEST_CONST: u8 = 42;
655 }
656 }
657
658 use test_mod::TestTrait2;
659 fn main() {
660 test_mod::TestEnum::TEST_CONST<|>;
661 }
662 ",
663 )
664 }
665
666 #[test]
667 fn trait_method() {
668 mark::check!(qualify_path_trait_method);
669 check_assist(
670 qualify_path,
671 r"
672 mod test_mod {
673 pub trait TestTrait {
674 fn test_method(&self);
675 }
676 pub struct TestStruct {}
677 impl TestTrait for TestStruct {
678 fn test_method(&self) {}
679 }
680 }
681
682 fn main() {
683 let test_struct = test_mod::TestStruct {};
684 test_struct.test_meth<|>od()
685 }
686 ",
687 r"
688 mod test_mod {
689 pub trait TestTrait {
690 fn test_method(&self);
691 }
692 pub struct TestStruct {}
693 impl TestTrait for TestStruct {
694 fn test_method(&self) {}
695 }
696 }
697
698 fn main() {
699 let test_struct = test_mod::TestStruct {};
700 test_mod::TestTrait::test_method(&test_struct)
701 }
702 ",
703 );
704 }
705
706 #[test]
707 fn trait_method_multi_params() {
708 check_assist(
709 qualify_path,
710 r"
711 mod test_mod {
712 pub trait TestTrait {
713 fn test_method(&self, test: i32);
714 }
715 pub struct TestStruct {}
716 impl TestTrait for TestStruct {
717 fn test_method(&self, test: i32) {}
718 }
719 }
720
721 fn main() {
722 let test_struct = test_mod::TestStruct {};
723 test_struct.test_meth<|>od(42)
724 }
725 ",
726 r"
727 mod test_mod {
728 pub trait TestTrait {
729 fn test_method(&self, test: i32);
730 }
731 pub struct TestStruct {}
732 impl TestTrait for TestStruct {
733 fn test_method(&self, test: i32) {}
734 }
735 }
736
737 fn main() {
738 let test_struct = test_mod::TestStruct {};
739 test_mod::TestTrait::test_method(&test_struct, 42)
740 }
741 ",
742 );
743 }
744
745 #[test]
746 fn trait_method_consume() {
747 check_assist(
748 qualify_path,
749 r"
750 mod test_mod {
751 pub trait TestTrait {
752 fn test_method(self);
753 }
754 pub struct TestStruct {}
755 impl TestTrait for TestStruct {
756 fn test_method(self) {}
757 }
758 }
759
760 fn main() {
761 let test_struct = test_mod::TestStruct {};
762 test_struct.test_meth<|>od()
763 }
764 ",
765 r"
766 mod test_mod {
767 pub trait TestTrait {
768 fn test_method(self);
769 }
770 pub struct TestStruct {}
771 impl TestTrait for TestStruct {
772 fn test_method(self) {}
773 }
774 }
775
776 fn main() {
777 let test_struct = test_mod::TestStruct {};
778 test_mod::TestTrait::test_method(test_struct)
779 }
780 ",
781 );
782 }
783
784 #[test]
785 fn trait_method_cross_crate() {
786 check_assist(
787 qualify_path,
788 r"
789 //- /main.rs crate:main deps:dep
790 fn main() {
791 let test_struct = dep::test_mod::TestStruct {};
792 test_struct.test_meth<|>od()
793 }
794 //- /dep.rs crate:dep
795 pub mod test_mod {
796 pub trait TestTrait {
797 fn test_method(&self);
798 }
799 pub struct TestStruct {}
800 impl TestTrait for TestStruct {
801 fn test_method(&self) {}
802 }
803 }
804 ",
805 r"
806 fn main() {
807 let test_struct = dep::test_mod::TestStruct {};
808 dep::test_mod::TestTrait::test_method(&test_struct)
809 }
810 ",
811 );
812 }
813
814 #[test]
815 fn assoc_fn_cross_crate() {
816 check_assist(
817 qualify_path,
818 r"
819 //- /main.rs crate:main deps:dep
820 fn main() {
821 dep::test_mod::TestStruct::test_func<|>tion
822 }
823 //- /dep.rs crate:dep
824 pub mod test_mod {
825 pub trait TestTrait {
826 fn test_function();
827 }
828 pub struct TestStruct {}
829 impl TestTrait for TestStruct {
830 fn test_function() {}
831 }
832 }
833 ",
834 r"
835 fn main() {
836 <dep::test_mod::TestStruct as dep::test_mod::TestTrait>::test_function
837 }
838 ",
839 );
840 }
841
842 #[test]
843 fn assoc_const_cross_crate() {
844 check_assist(
845 qualify_path,
846 r"
847 //- /main.rs crate:main deps:dep
848 fn main() {
849 dep::test_mod::TestStruct::CONST<|>
850 }
851 //- /dep.rs crate:dep
852 pub mod test_mod {
853 pub trait TestTrait {
854 const CONST: bool;
855 }
856 pub struct TestStruct {}
857 impl TestTrait for TestStruct {
858 const CONST: bool = true;
859 }
860 }
861 ",
862 r"
863 fn main() {
864 <dep::test_mod::TestStruct as dep::test_mod::TestTrait>::CONST
865 }
866 ",
867 );
868 }
869
870 #[test]
871 fn assoc_fn_as_method_cross_crate() {
872 check_assist_not_applicable(
873 qualify_path,
874 r"
875 //- /main.rs crate:main deps:dep
876 fn main() {
877 let test_struct = dep::test_mod::TestStruct {};
878 test_struct.test_func<|>tion()
879 }
880 //- /dep.rs crate:dep
881 pub mod test_mod {
882 pub trait TestTrait {
883 fn test_function();
884 }
885 pub struct TestStruct {}
886 impl TestTrait for TestStruct {
887 fn test_function() {}
888 }
889 }
890 ",
891 );
892 }
893
894 #[test]
895 fn private_trait_cross_crate() {
896 check_assist_not_applicable(
897 qualify_path,
898 r"
899 //- /main.rs crate:main deps:dep
900 fn main() {
901 let test_struct = dep::test_mod::TestStruct {};
902 test_struct.test_meth<|>od()
903 }
904 //- /dep.rs crate:dep
905 pub mod test_mod {
906 trait TestTrait {
907 fn test_method(&self);
908 }
909 pub struct TestStruct {}
910 impl TestTrait for TestStruct {
911 fn test_method(&self) {}
912 }
913 }
914 ",
915 );
916 }
917
918 #[test]
919 fn not_applicable_for_imported_trait_for_method() {
920 check_assist_not_applicable(
921 qualify_path,
922 r"
923 mod test_mod {
924 pub trait TestTrait {
925 fn test_method(&self);
926 }
927 pub trait TestTrait2 {
928 fn test_method(&self);
929 }
930 pub enum TestEnum {
931 One,
932 Two,
933 }
934 impl TestTrait2 for TestEnum {
935 fn test_method(&self) {}
936 }
937 impl TestTrait for TestEnum {
938 fn test_method(&self) {}
939 }
940 }
941
942 use test_mod::TestTrait2;
943 fn main() {
944 let one = test_mod::TestEnum::One;
945 one.test<|>_method();
946 }
947 ",
948 )
949 }
950
951 #[test]
952 fn dep_import() {
953 check_assist(
954 qualify_path,
955 r"
956//- /lib.rs crate:dep
957pub struct Struct;
958
959//- /main.rs crate:main deps:dep
960fn main() {
961 Struct<|>
962}
963",
964 r"
965fn main() {
966 dep::Struct
967}
968",
969 );
970 }
971
972 #[test]
973 fn whole_segment() {
974 // Tests that only imports whose last segment matches the identifier get suggested.
975 check_assist(
976 qualify_path,
977 r"
978//- /lib.rs crate:dep
979pub mod fmt {
980 pub trait Display {}
981}
982
983pub fn panic_fmt() {}
984
985//- /main.rs crate:main deps:dep
986struct S;
987
988impl f<|>mt::Display for S {}
989",
990 r"
991struct S;
992
993impl dep::fmt::Display for S {}
994",
995 );
996 }
997
998 #[test]
999 fn macro_generated() {
1000 // Tests that macro-generated items are suggested from external crates.
1001 check_assist(
1002 qualify_path,
1003 r"
1004//- /lib.rs crate:dep
1005macro_rules! mac {
1006 () => {
1007 pub struct Cheese;
1008 };
1009}
1010
1011mac!();
1012
1013//- /main.rs crate:main deps:dep
1014fn main() {
1015 Cheese<|>;
1016}
1017",
1018 r"
1019fn main() {
1020 dep::Cheese;
1021}
1022",
1023 );
1024 }
1025
1026 #[test]
1027 fn casing() {
1028 // Tests that differently cased names don't interfere and we only suggest the matching one.
1029 check_assist(
1030 qualify_path,
1031 r"
1032//- /lib.rs crate:dep
1033pub struct FMT;
1034pub struct fmt;
1035
1036//- /main.rs crate:main deps:dep
1037fn main() {
1038 FMT<|>;
1039}
1040",
1041 r"
1042fn main() {
1043 dep::FMT;
1044}
1045",
1046 );
1047 }
1048}
diff --git a/crates/assists/src/handlers/replace_string_with_char.rs b/crates/assists/src/handlers/replace_string_with_char.rs
new file mode 100644
index 000000000..4ca87a8ec
--- /dev/null
+++ b/crates/assists/src/handlers/replace_string_with_char.rs
@@ -0,0 +1,141 @@
1use syntax::{
2 ast::{self, HasStringValue},
3 AstToken,
4 SyntaxKind::STRING,
5};
6
7use crate::{AssistContext, AssistId, AssistKind, Assists};
8
9// Assist: replace_string_with_char
10//
11// Replace string with char.
12//
13// ```
14// fn main() {
15// find("{<|>");
16// }
17// ```
18// ->
19// ```
20// fn main() {
21// find('{');
22// }
23// ```
24pub(crate) fn replace_string_with_char(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
25 let token = ctx.find_token_at_offset(STRING).and_then(ast::String::cast)?;
26 let value = token.value()?;
27 let target = token.syntax().text_range();
28
29 if value.chars().take(2).count() != 1 {
30 return None;
31 }
32
33 acc.add(
34 AssistId("replace_string_with_char", AssistKind::RefactorRewrite),
35 "Replace string with char",
36 target,
37 |edit| {
38 edit.replace(token.syntax().text_range(), format!("'{}'", value));
39 },
40 )
41}
42
43#[cfg(test)]
44mod tests {
45 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
46
47 use super::*;
48
49 #[test]
50 fn replace_string_with_char_target() {
51 check_assist_target(
52 replace_string_with_char,
53 r#"
54 fn f() {
55 let s = "<|>c";
56 }
57 "#,
58 r#""c""#,
59 );
60 }
61
62 #[test]
63 fn replace_string_with_char_assist() {
64 check_assist(
65 replace_string_with_char,
66 r#"
67 fn f() {
68 let s = "<|>c";
69 }
70 "#,
71 r##"
72 fn f() {
73 let s = 'c';
74 }
75 "##,
76 )
77 }
78
79 #[test]
80 fn replace_string_with_char_assist_with_emoji() {
81 check_assist(
82 replace_string_with_char,
83 r#"
84 fn f() {
85 let s = "<|>😀";
86 }
87 "#,
88 r##"
89 fn f() {
90 let s = '😀';
91 }
92 "##,
93 )
94 }
95
96 #[test]
97 fn replace_string_with_char_assist_not_applicable() {
98 check_assist_not_applicable(
99 replace_string_with_char,
100 r#"
101 fn f() {
102 let s = "<|>test";
103 }
104 "#,
105 )
106 }
107
108 #[test]
109 fn replace_string_with_char_works_inside_macros() {
110 check_assist(
111 replace_string_with_char,
112 r#"
113 fn f() {
114 format!(<|>"x", 92)
115 }
116 "#,
117 r##"
118 fn f() {
119 format!('x', 92)
120 }
121 "##,
122 )
123 }
124
125 #[test]
126 fn replace_string_with_char_works_func_args() {
127 check_assist(
128 replace_string_with_char,
129 r#"
130 fn f() {
131 find(<|>"x");
132 }
133 "#,
134 r##"
135 fn f() {
136 find('x');
137 }
138 "##,
139 )
140 }
141}
diff --git a/crates/assists/src/lib.rs b/crates/assists/src/lib.rs
index a2bec818c..8a664f654 100644
--- a/crates/assists/src/lib.rs
+++ b/crates/assists/src/lib.rs
@@ -150,6 +150,7 @@ mod handlers {
150 mod merge_match_arms; 150 mod merge_match_arms;
151 mod move_bounds; 151 mod move_bounds;
152 mod move_guard; 152 mod move_guard;
153 mod qualify_path;
153 mod raw_string; 154 mod raw_string;
154 mod remove_dbg; 155 mod remove_dbg;
155 mod remove_mut; 156 mod remove_mut;
@@ -159,6 +160,7 @@ mod handlers {
159 mod replace_impl_trait_with_generic; 160 mod replace_impl_trait_with_generic;
160 mod replace_let_with_if_let; 161 mod replace_let_with_if_let;
161 mod replace_qualified_name_with_use; 162 mod replace_qualified_name_with_use;
163 mod replace_string_with_char;
162 mod replace_unwrap_with_match; 164 mod replace_unwrap_with_match;
163 mod split_import; 165 mod split_import;
164 mod unwrap_block; 166 mod unwrap_block;
@@ -196,6 +198,7 @@ mod handlers {
196 move_bounds::move_bounds_to_where_clause, 198 move_bounds::move_bounds_to_where_clause,
197 move_guard::move_arm_cond_to_match_guard, 199 move_guard::move_arm_cond_to_match_guard,
198 move_guard::move_guard_to_arm_body, 200 move_guard::move_guard_to_arm_body,
201 qualify_path::qualify_path,
199 raw_string::add_hash, 202 raw_string::add_hash,
200 raw_string::make_raw_string, 203 raw_string::make_raw_string,
201 raw_string::make_usual_string, 204 raw_string::make_usual_string,
@@ -208,6 +211,7 @@ mod handlers {
208 replace_impl_trait_with_generic::replace_impl_trait_with_generic, 211 replace_impl_trait_with_generic::replace_impl_trait_with_generic,
209 replace_let_with_if_let::replace_let_with_if_let, 212 replace_let_with_if_let::replace_let_with_if_let,
210 replace_qualified_name_with_use::replace_qualified_name_with_use, 213 replace_qualified_name_with_use::replace_qualified_name_with_use,
214 replace_string_with_char::replace_string_with_char,
211 replace_unwrap_with_match::replace_unwrap_with_match, 215 replace_unwrap_with_match::replace_unwrap_with_match,
212 split_import::split_import, 216 split_import::split_import,
213 unwrap_block::unwrap_block, 217 unwrap_block::unwrap_block,
diff --git a/crates/assists/src/tests/generated.rs b/crates/assists/src/tests/generated.rs
index 41f536574..acbf5b652 100644
--- a/crates/assists/src/tests/generated.rs
+++ b/crates/assists/src/tests/generated.rs
@@ -713,6 +713,25 @@ fn handle(action: Action) {
713} 713}
714 714
715#[test] 715#[test]
716fn doctest_qualify_path() {
717 check_doc_test(
718 "qualify_path",
719 r#####"
720fn main() {
721 let map = HashMap<|>::new();
722}
723pub mod std { pub mod collections { pub struct HashMap { } } }
724"#####,
725 r#####"
726fn main() {
727 let map = std::collections::HashMap::new();
728}
729pub mod std { pub mod collections { pub struct HashMap { } } }
730"#####,
731 )
732}
733
734#[test]
716fn doctest_remove_dbg() { 735fn doctest_remove_dbg() {
717 check_doc_test( 736 check_doc_test(
718 "remove_dbg", 737 "remove_dbg",
@@ -882,6 +901,23 @@ fn process(map: HashMap<String, String>) {}
882} 901}
883 902
884#[test] 903#[test]
904fn doctest_replace_string_with_char() {
905 check_doc_test(
906 "replace_string_with_char",
907 r#####"
908fn main() {
909 find("{<|>");
910}
911"#####,
912 r#####"
913fn main() {
914 find('{');
915}
916"#####,
917 )
918}
919
920#[test]
885fn doctest_replace_unwrap_with_match() { 921fn doctest_replace_unwrap_with_match() {
886 check_doc_test( 922 check_doc_test(
887 "replace_unwrap_with_match", 923 "replace_unwrap_with_match",
diff --git a/crates/assists/src/utils.rs b/crates/assists/src/utils.rs
index b37b0d2b6..1a6b48b45 100644
--- a/crates/assists/src/utils.rs
+++ b/crates/assists/src/utils.rs
@@ -275,7 +275,7 @@ impl TryEnum {
275/// somewhat similar to the known paths infra inside hir, but it different; We 275/// somewhat similar to the known paths infra inside hir, but it different; We
276/// want to make sure that IDE specific paths don't become interesting inside 276/// want to make sure that IDE specific paths don't become interesting inside
277/// the compiler itself as well. 277/// the compiler itself as well.
278pub struct FamousDefs<'a, 'b>(pub &'a Semantics<'b, RootDatabase>, pub Crate); 278pub struct FamousDefs<'a, 'b>(pub &'a Semantics<'b, RootDatabase>, pub Option<Crate>);
279 279
280#[allow(non_snake_case)] 280#[allow(non_snake_case)]
281impl FamousDefs<'_, '_> { 281impl FamousDefs<'_, '_> {
@@ -362,6 +362,10 @@ pub mod prelude {
362pub use prelude::*; 362pub use prelude::*;
363"#; 363"#;
364 364
365 pub fn core(&self) -> Option<Crate> {
366 self.find_crate("core")
367 }
368
365 pub(crate) fn core_convert_From(&self) -> Option<Trait> { 369 pub(crate) fn core_convert_From(&self) -> Option<Trait> {
366 self.find_trait("core:convert:From") 370 self.find_trait("core:convert:From")
367 } 371 }
@@ -399,21 +403,20 @@ pub use prelude::*;
399 } 403 }
400 } 404 }
401 405
406 fn find_crate(&self, name: &str) -> Option<Crate> {
407 let krate = self.1?;
408 let db = self.0.db;
409 let res =
410 krate.dependencies(db).into_iter().find(|dep| dep.name.to_string() == name)?.krate;
411 Some(res)
412 }
413
402 fn find_def(&self, path: &str) -> Option<ScopeDef> { 414 fn find_def(&self, path: &str) -> Option<ScopeDef> {
403 let db = self.0.db; 415 let db = self.0.db;
404 let mut path = path.split(':'); 416 let mut path = path.split(':');
405 let trait_ = path.next_back()?; 417 let trait_ = path.next_back()?;
406 let std_crate = path.next()?; 418 let std_crate = path.next()?;
407 let std_crate = if self 419 let std_crate = self.find_crate(std_crate)?;
408 .1
409 .declaration_name(db)
410 .map(|name| name.to_string() == std_crate)
411 .unwrap_or(false)
412 {
413 self.1
414 } else {
415 self.1.dependencies(db).into_iter().find(|dep| dep.name.to_string() == std_crate)?.krate
416 };
417 let mut module = std_crate.root_module(db); 420 let mut module = std_crate.root_module(db);
418 for segment in path { 421 for segment in path {
419 module = module.children(db).find_map(|child| { 422 module = module.children(db).find_map(|child| {
diff --git a/crates/assists/src/utils/import_assets.rs b/crates/assists/src/utils/import_assets.rs
index 601f51098..23db3a74b 100644
--- a/crates/assists/src/utils/import_assets.rs
+++ b/crates/assists/src/utils/import_assets.rs
@@ -1,6 +1,4 @@
1//! Look up accessible paths for items. 1//! Look up accessible paths for items.
2use std::collections::BTreeSet;
3
4use either::Either; 2use either::Either;
5use hir::{AsAssocItem, AssocItemContainer, ModuleDef, Semantics}; 3use hir::{AsAssocItem, AssocItemContainer, ModuleDef, Semantics};
6use ide_db::{imports_locator, RootDatabase}; 4use ide_db::{imports_locator, RootDatabase};
@@ -29,12 +27,12 @@ pub(crate) enum ImportCandidate {
29#[derive(Debug)] 27#[derive(Debug)]
30pub(crate) struct TraitImportCandidate { 28pub(crate) struct TraitImportCandidate {
31 pub ty: hir::Type, 29 pub ty: hir::Type,
32 pub name: String, 30 pub name: ast::NameRef,
33} 31}
34 32
35#[derive(Debug)] 33#[derive(Debug)]
36pub(crate) struct PathImportCandidate { 34pub(crate) struct PathImportCandidate {
37 pub name: String, 35 pub name: ast::NameRef,
38} 36}
39 37
40#[derive(Debug)] 38#[derive(Debug)]
@@ -86,9 +84,9 @@ impl ImportAssets {
86 fn get_search_query(&self) -> &str { 84 fn get_search_query(&self) -> &str {
87 match &self.import_candidate { 85 match &self.import_candidate {
88 ImportCandidate::UnqualifiedName(candidate) 86 ImportCandidate::UnqualifiedName(candidate)
89 | ImportCandidate::QualifierStart(candidate) => &candidate.name, 87 | ImportCandidate::QualifierStart(candidate) => candidate.name.text(),
90 ImportCandidate::TraitAssocItem(candidate) 88 ImportCandidate::TraitAssocItem(candidate)
91 | ImportCandidate::TraitMethod(candidate) => &candidate.name, 89 | ImportCandidate::TraitMethod(candidate) => candidate.name.text(),
92 } 90 }
93 } 91 }
94 92
@@ -96,7 +94,7 @@ impl ImportAssets {
96 &self, 94 &self,
97 sema: &Semantics<RootDatabase>, 95 sema: &Semantics<RootDatabase>,
98 config: &InsertUseConfig, 96 config: &InsertUseConfig,
99 ) -> BTreeSet<hir::ModPath> { 97 ) -> Vec<(hir::ModPath, hir::ItemInNs)> {
100 let _p = profile::span("import_assists::search_for_imports"); 98 let _p = profile::span("import_assists::search_for_imports");
101 self.search_for(sema, Some(config.prefix_kind)) 99 self.search_for(sema, Some(config.prefix_kind))
102 } 100 }
@@ -106,7 +104,7 @@ impl ImportAssets {
106 pub(crate) fn search_for_relative_paths( 104 pub(crate) fn search_for_relative_paths(
107 &self, 105 &self,
108 sema: &Semantics<RootDatabase>, 106 sema: &Semantics<RootDatabase>,
109 ) -> BTreeSet<hir::ModPath> { 107 ) -> Vec<(hir::ModPath, hir::ItemInNs)> {
110 let _p = profile::span("import_assists::search_for_relative_paths"); 108 let _p = profile::span("import_assists::search_for_relative_paths");
111 self.search_for(sema, None) 109 self.search_for(sema, None)
112 } 110 }
@@ -115,7 +113,7 @@ impl ImportAssets {
115 &self, 113 &self,
116 sema: &Semantics<RootDatabase>, 114 sema: &Semantics<RootDatabase>,
117 prefixed: Option<hir::PrefixKind>, 115 prefixed: Option<hir::PrefixKind>,
118 ) -> BTreeSet<hir::ModPath> { 116 ) -> Vec<(hir::ModPath, hir::ItemInNs)> {
119 let db = sema.db; 117 let db = sema.db;
120 let mut trait_candidates = FxHashSet::default(); 118 let mut trait_candidates = FxHashSet::default();
121 let current_crate = self.module_with_name_to_import.krate(); 119 let current_crate = self.module_with_name_to_import.krate();
@@ -181,7 +179,7 @@ impl ImportAssets {
181 } 179 }
182 }; 180 };
183 181
184 imports_locator::find_imports(sema, current_crate, &self.get_search_query()) 182 let mut res = imports_locator::find_imports(sema, current_crate, &self.get_search_query())
185 .into_iter() 183 .into_iter()
186 .filter_map(filter) 184 .filter_map(filter)
187 .filter_map(|candidate| { 185 .filter_map(|candidate| {
@@ -191,10 +189,13 @@ impl ImportAssets {
191 } else { 189 } else {
192 self.module_with_name_to_import.find_use_path(db, item) 190 self.module_with_name_to_import.find_use_path(db, item)
193 } 191 }
192 .map(|path| (path, item))
194 }) 193 })
195 .filter(|use_path| !use_path.segments.is_empty()) 194 .filter(|(use_path, _)| !use_path.segments.is_empty())
196 .take(20) 195 .take(20)
197 .collect::<BTreeSet<_>>() 196 .collect::<Vec<_>>();
197 res.sort_by_key(|(path, _)| path.clone());
198 res
198 } 199 }
199 200
200 fn assoc_to_trait(assoc: AssocItemContainer) -> Option<hir::Trait> { 201 fn assoc_to_trait(assoc: AssocItemContainer) -> Option<hir::Trait> {
@@ -215,7 +216,7 @@ impl ImportCandidate {
215 Some(_) => None, 216 Some(_) => None,
216 None => Some(Self::TraitMethod(TraitImportCandidate { 217 None => Some(Self::TraitMethod(TraitImportCandidate {
217 ty: sema.type_of_expr(&method_call.receiver()?)?, 218 ty: sema.type_of_expr(&method_call.receiver()?)?,
218 name: method_call.name_ref()?.syntax().to_string(), 219 name: method_call.name_ref()?,
219 })), 220 })),
220 } 221 }
221 } 222 }
@@ -243,24 +244,17 @@ impl ImportCandidate {
243 hir::PathResolution::Def(hir::ModuleDef::Adt(assoc_item_path)) => { 244 hir::PathResolution::Def(hir::ModuleDef::Adt(assoc_item_path)) => {
244 ImportCandidate::TraitAssocItem(TraitImportCandidate { 245 ImportCandidate::TraitAssocItem(TraitImportCandidate {
245 ty: assoc_item_path.ty(sema.db), 246 ty: assoc_item_path.ty(sema.db),
246 name: segment.syntax().to_string(), 247 name: segment.name_ref()?,
247 }) 248 })
248 } 249 }
249 _ => return None, 250 _ => return None,
250 } 251 }
251 } else { 252 } else {
252 ImportCandidate::QualifierStart(PathImportCandidate { 253 ImportCandidate::QualifierStart(PathImportCandidate { name: qualifier_start })
253 name: qualifier_start.syntax().to_string(),
254 })
255 } 254 }
256 } else { 255 } else {
257 ImportCandidate::UnqualifiedName(PathImportCandidate { 256 ImportCandidate::UnqualifiedName(PathImportCandidate {
258 name: segment 257 name: segment.syntax().descendants().find_map(ast::NameRef::cast)?,
259 .syntax()
260 .descendants()
261 .find_map(ast::NameRef::cast)?
262 .syntax()
263 .to_string(),
264 }) 258 })
265 }; 259 };
266 Some(candidate) 260 Some(candidate)
diff --git a/crates/base_db/src/fixture.rs b/crates/base_db/src/fixture.rs
index 72f1fd667..66e6443cb 100644
--- a/crates/base_db/src/fixture.rs
+++ b/crates/base_db/src/fixture.rs
@@ -158,7 +158,7 @@ impl ChangeFixture {
158 let crate_id = crate_graph.add_crate_root( 158 let crate_id = crate_graph.add_crate_root(
159 file_id, 159 file_id,
160 meta.edition, 160 meta.edition,
161 Some(crate_name.clone()), 161 Some(crate_name.clone().into()),
162 meta.cfg, 162 meta.cfg,
163 meta.env, 163 meta.env,
164 Default::default(), 164 Default::default(),
@@ -187,7 +187,7 @@ impl ChangeFixture {
187 crate_graph.add_crate_root( 187 crate_graph.add_crate_root(
188 crate_root, 188 crate_root,
189 Edition::Edition2018, 189 Edition::Edition2018,
190 Some(CrateName::new("test").unwrap()), 190 Some(CrateName::new("test").unwrap().into()),
191 default_cfg, 191 default_cfg,
192 Env::default(), 192 Env::default(),
193 Default::default(), 193 Default::default(),
diff --git a/crates/base_db/src/input.rs b/crates/base_db/src/input.rs
index 215ac4b41..87f0a0ce5 100644
--- a/crates/base_db/src/input.rs
+++ b/crates/base_db/src/input.rs
@@ -102,11 +102,46 @@ impl fmt::Display for CrateName {
102 102
103impl ops::Deref for CrateName { 103impl ops::Deref for CrateName {
104 type Target = str; 104 type Target = str;
105 fn deref(&self) -> &Self::Target { 105 fn deref(&self) -> &str {
106 &*self.0 106 &*self.0
107 } 107 }
108} 108}
109 109
110#[derive(Debug, Clone, PartialEq, Eq, Hash)]
111pub struct CrateDisplayName {
112 // The name we use to display various paths (with `_`).
113 crate_name: CrateName,
114 // The name as specified in Cargo.toml (with `-`).
115 canonical_name: String,
116}
117
118impl From<CrateName> for CrateDisplayName {
119 fn from(crate_name: CrateName) -> CrateDisplayName {
120 let canonical_name = crate_name.to_string();
121 CrateDisplayName { crate_name, canonical_name }
122 }
123}
124
125impl fmt::Display for CrateDisplayName {
126 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
127 write!(f, "{}", self.crate_name)
128 }
129}
130
131impl ops::Deref for CrateDisplayName {
132 type Target = str;
133 fn deref(&self) -> &str {
134 &*self.crate_name
135 }
136}
137
138impl CrateDisplayName {
139 pub fn from_canonical_name(canonical_name: String) -> CrateDisplayName {
140 let crate_name = CrateName::normalize_dashes(&canonical_name);
141 CrateDisplayName { crate_name, canonical_name }
142 }
143}
144
110#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] 145#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
111pub struct ProcMacroId(pub u32); 146pub struct ProcMacroId(pub u32);
112 147
@@ -127,11 +162,13 @@ impl PartialEq for ProcMacro {
127pub struct CrateData { 162pub struct CrateData {
128 pub root_file_id: FileId, 163 pub root_file_id: FileId,
129 pub edition: Edition, 164 pub edition: Edition,
130 /// A name used in the package's project declaration: for Cargo projects, it's [package].name, 165 /// A name used in the package's project declaration: for Cargo projects,
131 /// can be different for other project types or even absent (a dummy crate for the code snippet, for example). 166 /// it's [package].name, can be different for other project types or even
132 /// NOTE: The crate can be referenced as a dependency under a different name, 167 /// absent (a dummy crate for the code snippet, for example).
133 /// this one should be used when working with crate hierarchies. 168 ///
134 pub declaration_name: Option<CrateName>, 169 /// For purposes of analysis, crates are anonymous (only names in
170 /// `Dependency` matters), this name should only be used for UI.
171 pub display_name: Option<CrateDisplayName>,
135 pub cfg_options: CfgOptions, 172 pub cfg_options: CfgOptions,
136 pub env: Env, 173 pub env: Env,
137 pub dependencies: Vec<Dependency>, 174 pub dependencies: Vec<Dependency>,
@@ -160,7 +197,7 @@ impl CrateGraph {
160 &mut self, 197 &mut self,
161 file_id: FileId, 198 file_id: FileId,
162 edition: Edition, 199 edition: Edition,
163 declaration_name: Option<CrateName>, 200 display_name: Option<CrateDisplayName>,
164 cfg_options: CfgOptions, 201 cfg_options: CfgOptions,
165 env: Env, 202 env: Env,
166 proc_macro: Vec<(SmolStr, Arc<dyn tt::TokenExpander>)>, 203 proc_macro: Vec<(SmolStr, Arc<dyn tt::TokenExpander>)>,
@@ -171,7 +208,7 @@ impl CrateGraph {
171 let data = CrateData { 208 let data = CrateData {
172 root_file_id: file_id, 209 root_file_id: file_id,
173 edition, 210 edition,
174 declaration_name, 211 display_name,
175 cfg_options, 212 cfg_options,
176 env, 213 env,
177 proc_macro, 214 proc_macro,
@@ -290,6 +327,29 @@ impl CrateGraph {
290 } 327 }
291 false 328 false
292 } 329 }
330
331 // Work around for https://github.com/rust-analyzer/rust-analyzer/issues/6038.
332 // As hacky as it gets.
333 pub fn patch_cfg_if(&mut self) -> bool {
334 let cfg_if = self.hacky_find_crate("cfg_if");
335 let std = self.hacky_find_crate("std");
336 match (cfg_if, std) {
337 (Some(cfg_if), Some(std)) => {
338 self.arena.get_mut(&cfg_if).unwrap().dependencies.clear();
339 self.arena
340 .get_mut(&std)
341 .unwrap()
342 .dependencies
343 .push(Dependency { crate_id: cfg_if, name: CrateName::new("cfg_if").unwrap() });
344 true
345 }
346 _ => false,
347 }
348 }
349
350 fn hacky_find_crate(&self, display_name: &str) -> Option<CrateId> {
351 self.iter().find(|it| self[*it].display_name.as_deref() == Some(display_name))
352 }
293} 353}
294 354
295impl ops::Index<CrateId> for CrateGraph { 355impl ops::Index<CrateId> for CrateGraph {
diff --git a/crates/base_db/src/lib.rs b/crates/base_db/src/lib.rs
index e38aa7257..0804202d6 100644
--- a/crates/base_db/src/lib.rs
+++ b/crates/base_db/src/lib.rs
@@ -13,8 +13,8 @@ pub use crate::{
13 cancellation::Canceled, 13 cancellation::Canceled,
14 change::Change, 14 change::Change,
15 input::{ 15 input::{
16 CrateData, CrateGraph, CrateId, CrateName, Dependency, Edition, Env, FileId, ProcMacroId, 16 CrateData, CrateDisplayName, CrateGraph, CrateId, CrateName, Dependency, Edition, Env,
17 SourceRoot, SourceRootId, 17 FileId, ProcMacroId, SourceRoot, SourceRootId,
18 }, 18 },
19}; 19};
20pub use salsa; 20pub use salsa;
diff --git a/crates/call_info/Cargo.toml b/crates/call_info/Cargo.toml
new file mode 100644
index 000000000..98c0bd6db
--- /dev/null
+++ b/crates/call_info/Cargo.toml
@@ -0,0 +1,26 @@
1[package]
2name = "call_info"
3version = "0.0.0"
4description = "TBD"
5license = "MIT OR Apache-2.0"
6authors = ["rust-analyzer developers"]
7edition = "2018"
8
9[lib]
10doctest = false
11
12[dependencies]
13either = "1.5.3"
14
15stdx = { path = "../stdx", version = "0.0.0" }
16syntax = { path = "../syntax", version = "0.0.0" }
17base_db = { path = "../base_db", version = "0.0.0" }
18ide_db = { path = "../ide_db", version = "0.0.0" }
19test_utils = { path = "../test_utils", version = "0.0.0" }
20
21# call_info crate should depend only on the top-level `hir` package. if you need
22# something from some `hir_xxx` subpackage, reexport the API via `hir`.
23hir = { path = "../hir", version = "0.0.0" }
24
25[dev-dependencies]
26expect-test = "1.0"
diff --git a/crates/ide/src/call_info.rs b/crates/call_info/src/lib.rs
index d7b2b926e..c45406c25 100644
--- a/crates/ide/src/call_info.rs
+++ b/crates/call_info/src/lib.rs
@@ -1,4 +1,5 @@
1//! FIXME: write short doc here 1//! This crate provides primitives for tracking the information about a call site.
2use base_db::FilePosition;
2use either::Either; 3use either::Either;
3use hir::{HasAttrs, HirDisplay, Semantics, Type}; 4use hir::{HasAttrs, HirDisplay, Semantics, Type};
4use ide_db::RootDatabase; 5use ide_db::RootDatabase;
@@ -9,8 +10,6 @@ use syntax::{
9}; 10};
10use test_utils::mark; 11use test_utils::mark;
11 12
12use crate::FilePosition;
13
14/// Contains information about a call site. Specifically the 13/// Contains information about a call site. Specifically the
15/// `FunctionSignature`and current parameter. 14/// `FunctionSignature`and current parameter.
16#[derive(Debug)] 15#[derive(Debug)]
@@ -40,7 +39,7 @@ impl CallInfo {
40} 39}
41 40
42/// Computes parameter information for the given call expression. 41/// Computes parameter information for the given call expression.
43pub(crate) fn call_info(db: &RootDatabase, position: FilePosition) -> Option<CallInfo> { 42pub fn call_info(db: &RootDatabase, position: FilePosition) -> Option<CallInfo> {
44 let sema = Semantics::new(db); 43 let sema = Semantics::new(db);
45 let file = sema.parse(position.file_id); 44 let file = sema.parse(position.file_id);
46 let file = file.syntax(); 45 let file = file.syntax();
@@ -141,13 +140,13 @@ fn call_info_impl(
141} 140}
142 141
143#[derive(Debug)] 142#[derive(Debug)]
144pub(crate) struct ActiveParameter { 143pub struct ActiveParameter {
145 pub(crate) ty: Type, 144 pub ty: Type,
146 pub(crate) name: String, 145 pub name: String,
147} 146}
148 147
149impl ActiveParameter { 148impl ActiveParameter {
150 pub(crate) fn at(db: &RootDatabase, position: FilePosition) -> Option<Self> { 149 pub fn at(db: &RootDatabase, position: FilePosition) -> Option<Self> {
151 let sema = Semantics::new(db); 150 let sema = Semantics::new(db);
152 let file = sema.parse(position.file_id); 151 let file = sema.parse(position.file_id);
153 let file = file.syntax(); 152 let file = file.syntax();
@@ -156,7 +155,7 @@ impl ActiveParameter {
156 Self::at_token(&sema, token) 155 Self::at_token(&sema, token)
157 } 156 }
158 157
159 pub(crate) fn at_token(sema: &Semantics<RootDatabase>, token: SyntaxToken) -> Option<Self> { 158 pub fn at_token(sema: &Semantics<RootDatabase>, token: SyntaxToken) -> Option<Self> {
160 let (signature, active_parameter) = call_info_impl(&sema, token)?; 159 let (signature, active_parameter) = call_info_impl(&sema, token)?;
161 160
162 let idx = active_parameter?; 161 let idx = active_parameter?;
@@ -172,7 +171,7 @@ impl ActiveParameter {
172} 171}
173 172
174#[derive(Debug)] 173#[derive(Debug)]
175pub(crate) enum FnCallNode { 174pub enum FnCallNode {
176 CallExpr(ast::CallExpr), 175 CallExpr(ast::CallExpr),
177 MethodCallExpr(ast::MethodCallExpr), 176 MethodCallExpr(ast::MethodCallExpr),
178} 177}
@@ -196,7 +195,7 @@ impl FnCallNode {
196 }) 195 })
197 } 196 }
198 197
199 pub(crate) fn with_node_exact(node: &SyntaxNode) -> Option<FnCallNode> { 198 pub fn with_node_exact(node: &SyntaxNode) -> Option<FnCallNode> {
200 match_ast! { 199 match_ast! {
201 match node { 200 match node {
202 ast::CallExpr(it) => Some(FnCallNode::CallExpr(it)), 201 ast::CallExpr(it) => Some(FnCallNode::CallExpr(it)),
@@ -206,7 +205,7 @@ impl FnCallNode {
206 } 205 }
207 } 206 }
208 207
209 pub(crate) fn name_ref(&self) -> Option<ast::NameRef> { 208 pub fn name_ref(&self) -> Option<ast::NameRef> {
210 match self { 209 match self {
211 FnCallNode::CallExpr(call_expr) => Some(match call_expr.expr()? { 210 FnCallNode::CallExpr(call_expr) => Some(match call_expr.expr()? {
212 ast::Expr::PathExpr(path_expr) => path_expr.path()?.segment()?.name_ref()?, 211 ast::Expr::PathExpr(path_expr) => path_expr.path()?.segment()?.name_ref()?,
@@ -229,14 +228,28 @@ impl FnCallNode {
229 228
230#[cfg(test)] 229#[cfg(test)]
231mod tests { 230mod tests {
231 use base_db::{fixture::ChangeFixture, FilePosition};
232 use expect_test::{expect, Expect}; 232 use expect_test::{expect, Expect};
233 use test_utils::mark; 233 use ide_db::RootDatabase;
234 234 use test_utils::{mark, RangeOrOffset};
235 use crate::fixture; 235
236 /// Creates analysis from a multi-file fixture, returns positions marked with <|>.
237 pub(crate) fn position(ra_fixture: &str) -> (RootDatabase, FilePosition) {
238 let change_fixture = ChangeFixture::parse(ra_fixture);
239 let mut database = RootDatabase::default();
240 database.apply_change(change_fixture.change);
241 let (file_id, range_or_offset) =
242 change_fixture.file_position.expect("expected a marker (<|>)");
243 let offset = match range_or_offset {
244 RangeOrOffset::Range(_) => panic!(),
245 RangeOrOffset::Offset(it) => it,
246 };
247 (database, FilePosition { file_id, offset })
248 }
236 249
237 fn check(ra_fixture: &str, expect: Expect) { 250 fn check(ra_fixture: &str, expect: Expect) {
238 let (analysis, position) = fixture::position(ra_fixture); 251 let (db, position) = position(ra_fixture);
239 let call_info = analysis.call_info(position).unwrap(); 252 let call_info = crate::call_info(&db, position);
240 let actual = match call_info { 253 let actual = match call_info {
241 Some(call_info) => { 254 Some(call_info) => {
242 let docs = match &call_info.doc { 255 let docs = match &call_info.doc {
diff --git a/crates/cfg/Cargo.toml b/crates/cfg/Cargo.toml
index a6785ee8e..c68e391c1 100644
--- a/crates/cfg/Cargo.toml
+++ b/crates/cfg/Cargo.toml
@@ -17,3 +17,4 @@ tt = { path = "../tt", version = "0.0.0" }
17[dev-dependencies] 17[dev-dependencies]
18mbe = { path = "../mbe" } 18mbe = { path = "../mbe" }
19syntax = { path = "../syntax" } 19syntax = { path = "../syntax" }
20expect-test = "1.0"
diff --git a/crates/cfg/src/cfg_expr.rs b/crates/cfg/src/cfg_expr.rs
index 336fe25bc..42327f1e1 100644
--- a/crates/cfg/src/cfg_expr.rs
+++ b/crates/cfg/src/cfg_expr.rs
@@ -2,30 +2,77 @@
2//! 2//!
3//! See: https://doc.rust-lang.org/reference/conditional-compilation.html#conditional-compilation 3//! See: https://doc.rust-lang.org/reference/conditional-compilation.html#conditional-compilation
4 4
5use std::slice::Iter as SliceIter; 5use std::{fmt, slice::Iter as SliceIter};
6 6
7use tt::SmolStr; 7use tt::SmolStr;
8 8
9/// A simple configuration value passed in from the outside.
10#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
11pub enum CfgAtom {
12 /// eg. `#[cfg(test)]`
13 Flag(SmolStr),
14 /// eg. `#[cfg(target_os = "linux")]`
15 ///
16 /// Note that a key can have multiple values that are all considered "active" at the same time.
17 /// For example, `#[cfg(target_feature = "sse")]` and `#[cfg(target_feature = "sse2")]`.
18 KeyValue { key: SmolStr, value: SmolStr },
19}
20
21impl CfgAtom {
22 /// Returns `true` when the atom comes from the target specification.
23 ///
24 /// If this returns `true`, then changing this atom requires changing the compilation target. If
25 /// it returns `false`, the atom might come from a build script or the build system.
26 pub fn is_target_defined(&self) -> bool {
27 match self {
28 CfgAtom::Flag(flag) => matches!(&**flag, "unix" | "windows"),
29 CfgAtom::KeyValue { key, value: _ } => matches!(
30 &**key,
31 "target_arch"
32 | "target_os"
33 | "target_env"
34 | "target_family"
35 | "target_endian"
36 | "target_pointer_width"
37 | "target_vendor" // NOTE: `target_feature` is left out since it can be configured via `-Ctarget-feature`
38 ),
39 }
40 }
41}
42
43impl fmt::Display for CfgAtom {
44 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45 match self {
46 CfgAtom::Flag(name) => write!(f, "{}", name),
47 CfgAtom::KeyValue { key, value } => write!(f, "{} = {:?}", key, value),
48 }
49 }
50}
51
9#[derive(Debug, Clone, PartialEq, Eq)] 52#[derive(Debug, Clone, PartialEq, Eq)]
10pub enum CfgExpr { 53pub enum CfgExpr {
11 Invalid, 54 Invalid,
12 Atom(SmolStr), 55 Atom(CfgAtom),
13 KeyValue { key: SmolStr, value: SmolStr },
14 All(Vec<CfgExpr>), 56 All(Vec<CfgExpr>),
15 Any(Vec<CfgExpr>), 57 Any(Vec<CfgExpr>),
16 Not(Box<CfgExpr>), 58 Not(Box<CfgExpr>),
17} 59}
18 60
61impl From<CfgAtom> for CfgExpr {
62 fn from(atom: CfgAtom) -> Self {
63 CfgExpr::Atom(atom)
64 }
65}
66
19impl CfgExpr { 67impl CfgExpr {
20 pub fn parse(tt: &tt::Subtree) -> CfgExpr { 68 pub fn parse(tt: &tt::Subtree) -> CfgExpr {
21 next_cfg_expr(&mut tt.token_trees.iter()).unwrap_or(CfgExpr::Invalid) 69 next_cfg_expr(&mut tt.token_trees.iter()).unwrap_or(CfgExpr::Invalid)
22 } 70 }
23 /// Fold the cfg by querying all basic `Atom` and `KeyValue` predicates. 71 /// Fold the cfg by querying all basic `Atom` and `KeyValue` predicates.
24 pub fn fold(&self, query: &dyn Fn(&SmolStr, Option<&SmolStr>) -> bool) -> Option<bool> { 72 pub fn fold(&self, query: &dyn Fn(&CfgAtom) -> bool) -> Option<bool> {
25 match self { 73 match self {
26 CfgExpr::Invalid => None, 74 CfgExpr::Invalid => None,
27 CfgExpr::Atom(name) => Some(query(name, None)), 75 CfgExpr::Atom(atom) => Some(query(atom)),
28 CfgExpr::KeyValue { key, value } => Some(query(key, Some(value))),
29 CfgExpr::All(preds) => { 76 CfgExpr::All(preds) => {
30 preds.iter().try_fold(true, |s, pred| Some(s && pred.fold(query)?)) 77 preds.iter().try_fold(true, |s, pred| Some(s && pred.fold(query)?))
31 } 78 }
@@ -54,7 +101,7 @@ fn next_cfg_expr(it: &mut SliceIter<tt::TokenTree>) -> Option<CfgExpr> {
54 // FIXME: escape? raw string? 101 // FIXME: escape? raw string?
55 let value = 102 let value =
56 SmolStr::new(literal.text.trim_start_matches('"').trim_end_matches('"')); 103 SmolStr::new(literal.text.trim_start_matches('"').trim_end_matches('"'));
57 CfgExpr::KeyValue { key: name, value } 104 CfgAtom::KeyValue { key: name, value }.into()
58 } 105 }
59 _ => return Some(CfgExpr::Invalid), 106 _ => return Some(CfgExpr::Invalid),
60 } 107 }
@@ -70,7 +117,7 @@ fn next_cfg_expr(it: &mut SliceIter<tt::TokenTree>) -> Option<CfgExpr> {
70 _ => CfgExpr::Invalid, 117 _ => CfgExpr::Invalid,
71 } 118 }
72 } 119 }
73 _ => CfgExpr::Atom(name), 120 _ => CfgAtom::Flag(name).into(),
74 }; 121 };
75 122
76 // Eat comma separator 123 // Eat comma separator
@@ -81,53 +128,3 @@ fn next_cfg_expr(it: &mut SliceIter<tt::TokenTree>) -> Option<CfgExpr> {
81 } 128 }
82 Some(ret) 129 Some(ret)
83} 130}
84
85#[cfg(test)]
86mod tests {
87 use super::*;
88
89 use mbe::ast_to_token_tree;
90 use syntax::ast::{self, AstNode};
91
92 fn assert_parse_result(input: &str, expected: CfgExpr) {
93 let (tt, _) = {
94 let source_file = ast::SourceFile::parse(input).ok().unwrap();
95 let tt = source_file.syntax().descendants().find_map(ast::TokenTree::cast).unwrap();
96 ast_to_token_tree(&tt).unwrap()
97 };
98 let cfg = CfgExpr::parse(&tt);
99 assert_eq!(cfg, expected);
100 }
101
102 #[test]
103 fn test_cfg_expr_parser() {
104 assert_parse_result("#![cfg(foo)]", CfgExpr::Atom("foo".into()));
105 assert_parse_result("#![cfg(foo,)]", CfgExpr::Atom("foo".into()));
106 assert_parse_result(
107 "#![cfg(not(foo))]",
108 CfgExpr::Not(Box::new(CfgExpr::Atom("foo".into()))),
109 );
110 assert_parse_result("#![cfg(foo(bar))]", CfgExpr::Invalid);
111
112 // Only take the first
113 assert_parse_result(r#"#![cfg(foo, bar = "baz")]"#, CfgExpr::Atom("foo".into()));
114
115 assert_parse_result(
116 r#"#![cfg(all(foo, bar = "baz"))]"#,
117 CfgExpr::All(vec![
118 CfgExpr::Atom("foo".into()),
119 CfgExpr::KeyValue { key: "bar".into(), value: "baz".into() },
120 ]),
121 );
122
123 assert_parse_result(
124 r#"#![cfg(any(not(), all(), , bar = "baz",))]"#,
125 CfgExpr::Any(vec![
126 CfgExpr::Not(Box::new(CfgExpr::Invalid)),
127 CfgExpr::All(vec![]),
128 CfgExpr::Invalid,
129 CfgExpr::KeyValue { key: "bar".into(), value: "baz".into() },
130 ]),
131 );
132 }
133}
diff --git a/crates/cfg/src/dnf.rs b/crates/cfg/src/dnf.rs
new file mode 100644
index 000000000..580c9a9a2
--- /dev/null
+++ b/crates/cfg/src/dnf.rs
@@ -0,0 +1,320 @@
1//! Disjunctive Normal Form construction.
2//!
3//! Algorithm from <https://www.cs.drexel.edu/~jjohnson/2015-16/fall/CS270/Lectures/3/dnf.pdf>,
4//! which would have been much easier to read if it used pattern matching. It's also missing the
5//! entire "distribute ANDs over ORs" part, which is not trivial. Oh well.
6//!
7//! This is currently both messy and inefficient. Feel free to improve, there are unit tests.
8
9use std::fmt;
10
11use rustc_hash::FxHashSet;
12
13use crate::{CfgAtom, CfgDiff, CfgExpr, CfgOptions, InactiveReason};
14
15/// A `#[cfg]` directive in Disjunctive Normal Form (DNF).
16pub struct DnfExpr {
17 conjunctions: Vec<Conjunction>,
18}
19
20struct Conjunction {
21 literals: Vec<Literal>,
22}
23
24struct Literal {
25 negate: bool,
26 var: Option<CfgAtom>, // None = Invalid
27}
28
29impl DnfExpr {
30 pub fn new(expr: CfgExpr) -> Self {
31 let builder = Builder { expr: DnfExpr { conjunctions: Vec::new() } };
32
33 builder.lower(expr.clone())
34 }
35
36 /// Computes a list of present or absent atoms in `opts` that cause this expression to evaluate
37 /// to `false`.
38 ///
39 /// Note that flipping a subset of these atoms might be sufficient to make the whole expression
40 /// evaluate to `true`. For that, see `compute_enable_hints`.
41 ///
42 /// Returns `None` when `self` is already true, or contains errors.
43 pub fn why_inactive(&self, opts: &CfgOptions) -> Option<InactiveReason> {
44 let mut res = InactiveReason { enabled: Vec::new(), disabled: Vec::new() };
45
46 for conj in &self.conjunctions {
47 let mut conj_is_true = true;
48 for lit in &conj.literals {
49 let atom = lit.var.as_ref()?;
50 let enabled = opts.enabled.contains(atom);
51 if lit.negate == enabled {
52 // Literal is false, but needs to be true for this conjunction.
53 conj_is_true = false;
54
55 if enabled {
56 res.enabled.push(atom.clone());
57 } else {
58 res.disabled.push(atom.clone());
59 }
60 }
61 }
62
63 if conj_is_true {
64 // This expression is not actually inactive.
65 return None;
66 }
67 }
68
69 res.enabled.sort_unstable();
70 res.enabled.dedup();
71 res.disabled.sort_unstable();
72 res.disabled.dedup();
73 Some(res)
74 }
75
76 /// Returns `CfgDiff` objects that would enable this directive if applied to `opts`.
77 pub fn compute_enable_hints<'a>(
78 &'a self,
79 opts: &'a CfgOptions,
80 ) -> impl Iterator<Item = CfgDiff> + 'a {
81 // A cfg is enabled if any of `self.conjunctions` evaluate to `true`.
82
83 self.conjunctions.iter().filter_map(move |conj| {
84 let mut enable = FxHashSet::default();
85 let mut disable = FxHashSet::default();
86 for lit in &conj.literals {
87 let atom = lit.var.as_ref()?;
88 let enabled = opts.enabled.contains(atom);
89 if lit.negate && enabled {
90 disable.insert(atom.clone());
91 }
92 if !lit.negate && !enabled {
93 enable.insert(atom.clone());
94 }
95 }
96
97 // Check that this actually makes `conj` true.
98 for lit in &conj.literals {
99 let atom = lit.var.as_ref()?;
100 let enabled = enable.contains(atom)
101 || (opts.enabled.contains(atom) && !disable.contains(atom));
102 if enabled == lit.negate {
103 return None;
104 }
105 }
106
107 if enable.is_empty() && disable.is_empty() {
108 return None;
109 }
110
111 let mut diff = CfgDiff {
112 enable: enable.into_iter().collect(),
113 disable: disable.into_iter().collect(),
114 };
115
116 // Undo the FxHashMap randomization for consistent output.
117 diff.enable.sort_unstable();
118 diff.disable.sort_unstable();
119
120 Some(diff)
121 })
122 }
123}
124
125impl fmt::Display for DnfExpr {
126 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
127 if self.conjunctions.len() != 1 {
128 write!(f, "any(")?;
129 }
130 for (i, conj) in self.conjunctions.iter().enumerate() {
131 if i != 0 {
132 f.write_str(", ")?;
133 }
134
135 write!(f, "{}", conj)?;
136 }
137 if self.conjunctions.len() != 1 {
138 write!(f, ")")?;
139 }
140
141 Ok(())
142 }
143}
144
145impl Conjunction {
146 fn new(parts: Vec<CfgExpr>) -> Self {
147 let mut literals = Vec::new();
148 for part in parts {
149 match part {
150 CfgExpr::Invalid | CfgExpr::Atom(_) | CfgExpr::Not(_) => {
151 literals.push(Literal::new(part));
152 }
153 CfgExpr::All(conj) => {
154 // Flatten.
155 literals.extend(Conjunction::new(conj).literals);
156 }
157 CfgExpr::Any(_) => unreachable!("disjunction in conjunction"),
158 }
159 }
160
161 Self { literals }
162 }
163}
164
165impl fmt::Display for Conjunction {
166 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
167 if self.literals.len() != 1 {
168 write!(f, "all(")?;
169 }
170 for (i, lit) in self.literals.iter().enumerate() {
171 if i != 0 {
172 f.write_str(", ")?;
173 }
174
175 write!(f, "{}", lit)?;
176 }
177 if self.literals.len() != 1 {
178 write!(f, ")")?;
179 }
180
181 Ok(())
182 }
183}
184
185impl Literal {
186 fn new(expr: CfgExpr) -> Self {
187 match expr {
188 CfgExpr::Invalid => Self { negate: false, var: None },
189 CfgExpr::Atom(atom) => Self { negate: false, var: Some(atom) },
190 CfgExpr::Not(expr) => match *expr {
191 CfgExpr::Invalid => Self { negate: true, var: None },
192 CfgExpr::Atom(atom) => Self { negate: true, var: Some(atom) },
193 _ => unreachable!("non-atom {:?}", expr),
194 },
195 CfgExpr::Any(_) | CfgExpr::All(_) => unreachable!("non-literal {:?}", expr),
196 }
197 }
198}
199
200impl fmt::Display for Literal {
201 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
202 if self.negate {
203 write!(f, "not(")?;
204 }
205
206 match &self.var {
207 Some(var) => write!(f, "{}", var)?,
208 None => f.write_str("<invalid>")?,
209 }
210
211 if self.negate {
212 write!(f, ")")?;
213 }
214
215 Ok(())
216 }
217}
218
219struct Builder {
220 expr: DnfExpr,
221}
222
223impl Builder {
224 fn lower(mut self, expr: CfgExpr) -> DnfExpr {
225 let expr = make_nnf(expr);
226 let expr = make_dnf(expr);
227
228 match expr {
229 CfgExpr::Invalid | CfgExpr::Atom(_) | CfgExpr::Not(_) => {
230 self.expr.conjunctions.push(Conjunction::new(vec![expr]));
231 }
232 CfgExpr::All(conj) => {
233 self.expr.conjunctions.push(Conjunction::new(conj));
234 }
235 CfgExpr::Any(mut disj) => {
236 disj.reverse();
237 while let Some(conj) = disj.pop() {
238 match conj {
239 CfgExpr::Invalid | CfgExpr::Atom(_) | CfgExpr::All(_) | CfgExpr::Not(_) => {
240 self.expr.conjunctions.push(Conjunction::new(vec![conj]));
241 }
242 CfgExpr::Any(inner_disj) => {
243 // Flatten.
244 disj.extend(inner_disj.into_iter().rev());
245 }
246 }
247 }
248 }
249 }
250
251 self.expr
252 }
253}
254
255fn make_dnf(expr: CfgExpr) -> CfgExpr {
256 match expr {
257 CfgExpr::Invalid | CfgExpr::Atom(_) | CfgExpr::Not(_) => expr,
258 CfgExpr::Any(e) => CfgExpr::Any(e.into_iter().map(|expr| make_dnf(expr)).collect()),
259 CfgExpr::All(e) => {
260 let e = e.into_iter().map(|expr| make_nnf(expr)).collect::<Vec<_>>();
261
262 CfgExpr::Any(distribute_conj(&e))
263 }
264 }
265}
266
267/// Turns a conjunction of expressions into a disjunction of expressions.
268fn distribute_conj(conj: &[CfgExpr]) -> Vec<CfgExpr> {
269 fn go(out: &mut Vec<CfgExpr>, with: &mut Vec<CfgExpr>, rest: &[CfgExpr]) {
270 match rest {
271 [head, tail @ ..] => match head {
272 CfgExpr::Any(disj) => {
273 for part in disj {
274 with.push(part.clone());
275 go(out, with, tail);
276 with.pop();
277 }
278 }
279 _ => {
280 with.push(head.clone());
281 go(out, with, tail);
282 with.pop();
283 }
284 },
285 _ => {
286 // Turn accumulated parts into a new conjunction.
287 out.push(CfgExpr::All(with.clone()));
288 }
289 }
290 }
291
292 let mut out = Vec::new();
293 let mut with = Vec::new();
294
295 go(&mut out, &mut with, conj);
296
297 out
298}
299
300fn make_nnf(expr: CfgExpr) -> CfgExpr {
301 match expr {
302 CfgExpr::Invalid | CfgExpr::Atom(_) => expr,
303 CfgExpr::Any(expr) => CfgExpr::Any(expr.into_iter().map(|expr| make_nnf(expr)).collect()),
304 CfgExpr::All(expr) => CfgExpr::All(expr.into_iter().map(|expr| make_nnf(expr)).collect()),
305 CfgExpr::Not(operand) => match *operand {
306 CfgExpr::Invalid | CfgExpr::Atom(_) => CfgExpr::Not(operand.clone()), // Original negated expr
307 CfgExpr::Not(expr) => {
308 // Remove double negation.
309 make_nnf(*expr)
310 }
311 // Convert negated conjunction/disjunction using DeMorgan's Law.
312 CfgExpr::Any(inner) => CfgExpr::All(
313 inner.into_iter().map(|expr| make_nnf(CfgExpr::Not(Box::new(expr)))).collect(),
314 ),
315 CfgExpr::All(inner) => CfgExpr::Any(
316 inner.into_iter().map(|expr| make_nnf(CfgExpr::Not(Box::new(expr)))).collect(),
317 ),
318 },
319 }
320}
diff --git a/crates/cfg/src/lib.rs b/crates/cfg/src/lib.rs
index a9d50e698..d0e08cf5f 100644
--- a/crates/cfg/src/lib.rs
+++ b/crates/cfg/src/lib.rs
@@ -1,11 +1,17 @@
1//! cfg defines conditional compiling options, `cfg` attibute parser and evaluator 1//! cfg defines conditional compiling options, `cfg` attibute parser and evaluator
2 2
3mod cfg_expr; 3mod cfg_expr;
4mod dnf;
5#[cfg(test)]
6mod tests;
7
8use std::fmt;
4 9
5use rustc_hash::FxHashSet; 10use rustc_hash::FxHashSet;
6use tt::SmolStr; 11use tt::SmolStr;
7 12
8pub use cfg_expr::CfgExpr; 13pub use cfg_expr::{CfgAtom, CfgExpr};
14pub use dnf::DnfExpr;
9 15
10/// Configuration options used for conditional compilition on items with `cfg` attributes. 16/// Configuration options used for conditional compilition on items with `cfg` attributes.
11/// 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
@@ -19,33 +25,131 @@ pub use cfg_expr::CfgExpr;
19/// See: https://doc.rust-lang.org/reference/conditional-compilation.html#set-configuration-options 25/// See: https://doc.rust-lang.org/reference/conditional-compilation.html#set-configuration-options
20#[derive(Debug, Clone, PartialEq, Eq, Default)] 26#[derive(Debug, Clone, PartialEq, Eq, Default)]
21pub struct CfgOptions { 27pub struct CfgOptions {
22 atoms: FxHashSet<SmolStr>, 28 enabled: FxHashSet<CfgAtom>,
23 key_values: FxHashSet<(SmolStr, SmolStr)>,
24} 29}
25 30
26impl CfgOptions { 31impl CfgOptions {
27 pub fn check(&self, cfg: &CfgExpr) -> Option<bool> { 32 pub fn check(&self, cfg: &CfgExpr) -> Option<bool> {
28 cfg.fold(&|key, value| match value { 33 cfg.fold(&|atom| self.enabled.contains(atom))
29 None => self.atoms.contains(key),
30 Some(value) => self.key_values.contains(&(key.clone(), value.clone())),
31 })
32 } 34 }
33 35
34 pub fn insert_atom(&mut self, key: SmolStr) { 36 pub fn insert_atom(&mut self, key: SmolStr) {
35 self.atoms.insert(key); 37 self.enabled.insert(CfgAtom::Flag(key));
36 } 38 }
37 39
38 pub fn insert_key_value(&mut self, key: SmolStr, value: SmolStr) { 40 pub fn insert_key_value(&mut self, key: SmolStr, value: SmolStr) {
39 self.key_values.insert((key, value)); 41 self.enabled.insert(CfgAtom::KeyValue { key, value });
40 } 42 }
41 43
42 pub fn append(&mut self, other: &CfgOptions) { 44 pub fn append(&mut self, other: &CfgOptions) {
43 for atom in &other.atoms { 45 for atom in &other.enabled {
44 self.atoms.insert(atom.clone()); 46 self.enabled.insert(atom.clone());
47 }
48 }
49
50 pub fn apply_diff(&mut self, diff: CfgDiff) {
51 for atom in diff.enable {
52 self.enabled.insert(atom);
45 } 53 }
46 54
47 for (key, value) in &other.key_values { 55 for atom in diff.disable {
48 self.key_values.insert((key.clone(), value.clone())); 56 self.enabled.remove(&atom);
57 }
58 }
59}
60
61pub struct CfgDiff {
62 // Invariants: No duplicates, no atom that's both in `enable` and `disable`.
63 enable: Vec<CfgAtom>,
64 disable: Vec<CfgAtom>,
65}
66
67impl CfgDiff {
68 /// Returns the total number of atoms changed by this diff.
69 pub fn len(&self) -> usize {
70 self.enable.len() + self.disable.len()
71 }
72}
73
74impl fmt::Display for CfgDiff {
75 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76 if !self.enable.is_empty() {
77 f.write_str("enable ")?;
78 for (i, atom) in self.enable.iter().enumerate() {
79 let sep = match i {
80 0 => "",
81 _ if i == self.enable.len() - 1 => " and ",
82 _ => ", ",
83 };
84 f.write_str(sep)?;
85
86 write!(f, "{}", atom)?;
87 }
88
89 if !self.disable.is_empty() {
90 f.write_str("; ")?;
91 }
49 } 92 }
93
94 if !self.disable.is_empty() {
95 f.write_str("disable ")?;
96 for (i, atom) in self.disable.iter().enumerate() {
97 let sep = match i {
98 0 => "",
99 _ if i == self.enable.len() - 1 => " and ",
100 _ => ", ",
101 };
102 f.write_str(sep)?;
103
104 write!(f, "{}", atom)?;
105 }
106 }
107
108 Ok(())
109 }
110}
111
112pub struct InactiveReason {
113 enabled: Vec<CfgAtom>,
114 disabled: Vec<CfgAtom>,
115}
116
117impl fmt::Display for InactiveReason {
118 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
119 if !self.enabled.is_empty() {
120 for (i, atom) in self.enabled.iter().enumerate() {
121 let sep = match i {
122 0 => "",
123 _ if i == self.enabled.len() - 1 => " and ",
124 _ => ", ",
125 };
126 f.write_str(sep)?;
127
128 write!(f, "{}", atom)?;
129 }
130 let is_are = if self.enabled.len() == 1 { "is" } else { "are" };
131 write!(f, " {} enabled", is_are)?;
132
133 if !self.disabled.is_empty() {
134 f.write_str(" and ")?;
135 }
136 }
137
138 if !self.disabled.is_empty() {
139 for (i, atom) in self.disabled.iter().enumerate() {
140 let sep = match i {
141 0 => "",
142 _ if i == self.disabled.len() - 1 => " and ",
143 _ => ", ",
144 };
145 f.write_str(sep)?;
146
147 write!(f, "{}", atom)?;
148 }
149 let is_are = if self.disabled.len() == 1 { "is" } else { "are" };
150 write!(f, " {} disabled", is_are)?;
151 }
152
153 Ok(())
50 } 154 }
51} 155}
diff --git a/crates/cfg/src/tests.rs b/crates/cfg/src/tests.rs
new file mode 100644
index 000000000..bd0f9ec48
--- /dev/null
+++ b/crates/cfg/src/tests.rs
@@ -0,0 +1,193 @@
1use expect_test::{expect, Expect};
2use mbe::ast_to_token_tree;
3use syntax::{ast, AstNode};
4
5use crate::{CfgAtom, CfgExpr, CfgOptions, DnfExpr};
6
7fn assert_parse_result(input: &str, expected: CfgExpr) {
8 let (tt, _) = {
9 let source_file = ast::SourceFile::parse(input).ok().unwrap();
10 let tt = source_file.syntax().descendants().find_map(ast::TokenTree::cast).unwrap();
11 ast_to_token_tree(&tt).unwrap()
12 };
13 let cfg = CfgExpr::parse(&tt);
14 assert_eq!(cfg, expected);
15}
16
17fn check_dnf(input: &str, expect: Expect) {
18 let (tt, _) = {
19 let source_file = ast::SourceFile::parse(input).ok().unwrap();
20 let tt = source_file.syntax().descendants().find_map(ast::TokenTree::cast).unwrap();
21 ast_to_token_tree(&tt).unwrap()
22 };
23 let cfg = CfgExpr::parse(&tt);
24 let actual = format!("#![cfg({})]", DnfExpr::new(cfg));
25 expect.assert_eq(&actual);
26}
27
28fn check_why_inactive(input: &str, opts: &CfgOptions, expect: Expect) {
29 let (tt, _) = {
30 let source_file = ast::SourceFile::parse(input).ok().unwrap();
31 let tt = source_file.syntax().descendants().find_map(ast::TokenTree::cast).unwrap();
32 ast_to_token_tree(&tt).unwrap()
33 };
34 let cfg = CfgExpr::parse(&tt);
35 let dnf = DnfExpr::new(cfg);
36 let why_inactive = dnf.why_inactive(opts).unwrap().to_string();
37 expect.assert_eq(&why_inactive);
38}
39
40#[track_caller]
41fn check_enable_hints(input: &str, opts: &CfgOptions, expected_hints: &[&str]) {
42 let (tt, _) = {
43 let source_file = ast::SourceFile::parse(input).ok().unwrap();
44 let tt = source_file.syntax().descendants().find_map(ast::TokenTree::cast).unwrap();
45 ast_to_token_tree(&tt).unwrap()
46 };
47 let cfg = CfgExpr::parse(&tt);
48 let dnf = DnfExpr::new(cfg);
49 let hints = dnf.compute_enable_hints(opts).map(|diff| diff.to_string()).collect::<Vec<_>>();
50 assert_eq!(hints, expected_hints);
51}
52
53#[test]
54fn test_cfg_expr_parser() {
55 assert_parse_result("#![cfg(foo)]", CfgAtom::Flag("foo".into()).into());
56 assert_parse_result("#![cfg(foo,)]", CfgAtom::Flag("foo".into()).into());
57 assert_parse_result(
58 "#![cfg(not(foo))]",
59 CfgExpr::Not(Box::new(CfgAtom::Flag("foo".into()).into())),
60 );
61 assert_parse_result("#![cfg(foo(bar))]", CfgExpr::Invalid);
62
63 // Only take the first
64 assert_parse_result(r#"#![cfg(foo, bar = "baz")]"#, CfgAtom::Flag("foo".into()).into());
65
66 assert_parse_result(
67 r#"#![cfg(all(foo, bar = "baz"))]"#,
68 CfgExpr::All(vec![
69 CfgAtom::Flag("foo".into()).into(),
70 CfgAtom::KeyValue { key: "bar".into(), value: "baz".into() }.into(),
71 ]),
72 );
73
74 assert_parse_result(
75 r#"#![cfg(any(not(), all(), , bar = "baz",))]"#,
76 CfgExpr::Any(vec![
77 CfgExpr::Not(Box::new(CfgExpr::Invalid)),
78 CfgExpr::All(vec![]),
79 CfgExpr::Invalid,
80 CfgAtom::KeyValue { key: "bar".into(), value: "baz".into() }.into(),
81 ]),
82 );
83}
84
85#[test]
86fn smoke() {
87 check_dnf("#![cfg(test)]", expect![[r#"#![cfg(test)]"#]]);
88 check_dnf("#![cfg(not(test))]", expect![[r#"#![cfg(not(test))]"#]]);
89 check_dnf("#![cfg(not(not(test)))]", expect![[r#"#![cfg(test)]"#]]);
90
91 check_dnf("#![cfg(all(a, b))]", expect![[r#"#![cfg(all(a, b))]"#]]);
92 check_dnf("#![cfg(any(a, b))]", expect![[r#"#![cfg(any(a, b))]"#]]);
93
94 check_dnf("#![cfg(not(a))]", expect![[r#"#![cfg(not(a))]"#]]);
95}
96
97#[test]
98fn distribute() {
99 check_dnf("#![cfg(all(any(a, b), c))]", expect![[r#"#![cfg(any(all(a, c), all(b, c)))]"#]]);
100 check_dnf("#![cfg(all(c, any(a, b)))]", expect![[r#"#![cfg(any(all(c, a), all(c, b)))]"#]]);
101 check_dnf(
102 "#![cfg(all(any(a, b), any(c, d)))]",
103 expect![[r#"#![cfg(any(all(a, c), all(a, d), all(b, c), all(b, d)))]"#]],
104 );
105
106 check_dnf(
107 "#![cfg(all(any(a, b, c), any(d, e, f), g))]",
108 expect![[
109 r#"#![cfg(any(all(a, d, g), all(a, e, g), all(a, f, g), all(b, d, g), all(b, e, g), all(b, f, g), all(c, d, g), all(c, e, g), all(c, f, g)))]"#
110 ]],
111 );
112}
113
114#[test]
115fn demorgan() {
116 check_dnf("#![cfg(not(all(a, b)))]", expect![[r#"#![cfg(any(not(a), not(b)))]"#]]);
117 check_dnf("#![cfg(not(any(a, b)))]", expect![[r#"#![cfg(all(not(a), not(b)))]"#]]);
118
119 check_dnf("#![cfg(not(all(not(a), b)))]", expect![[r#"#![cfg(any(a, not(b)))]"#]]);
120 check_dnf("#![cfg(not(any(a, not(b))))]", expect![[r#"#![cfg(all(not(a), b))]"#]]);
121}
122
123#[test]
124fn nested() {
125 check_dnf("#![cfg(all(any(a), not(all(any(b)))))]", expect![[r#"#![cfg(all(a, not(b)))]"#]]);
126
127 check_dnf("#![cfg(any(any(a, b)))]", expect![[r#"#![cfg(any(a, b))]"#]]);
128 check_dnf("#![cfg(not(any(any(a, b))))]", expect![[r#"#![cfg(all(not(a), not(b)))]"#]]);
129 check_dnf("#![cfg(all(all(a, b)))]", expect![[r#"#![cfg(all(a, b))]"#]]);
130 check_dnf("#![cfg(not(all(all(a, b))))]", expect![[r#"#![cfg(any(not(a), not(b)))]"#]]);
131}
132
133#[test]
134fn hints() {
135 let mut opts = CfgOptions::default();
136
137 check_enable_hints("#![cfg(test)]", &opts, &["enable test"]);
138 check_enable_hints("#![cfg(not(test))]", &opts, &[]);
139
140 check_enable_hints("#![cfg(any(a, b))]", &opts, &["enable a", "enable b"]);
141 check_enable_hints("#![cfg(any(b, a))]", &opts, &["enable b", "enable a"]);
142
143 check_enable_hints("#![cfg(all(a, b))]", &opts, &["enable a and b"]);
144
145 opts.insert_atom("test".into());
146
147 check_enable_hints("#![cfg(test)]", &opts, &[]);
148 check_enable_hints("#![cfg(not(test))]", &opts, &["disable test"]);
149}
150
151/// Tests that we don't suggest hints for cfgs that express an inconsistent formula.
152#[test]
153fn hints_impossible() {
154 let mut opts = CfgOptions::default();
155
156 check_enable_hints("#![cfg(all(test, not(test)))]", &opts, &[]);
157
158 opts.insert_atom("test".into());
159
160 check_enable_hints("#![cfg(all(test, not(test)))]", &opts, &[]);
161}
162
163#[test]
164fn why_inactive() {
165 let mut opts = CfgOptions::default();
166 opts.insert_atom("test".into());
167 opts.insert_atom("test2".into());
168
169 check_why_inactive("#![cfg(a)]", &opts, expect![["a is disabled"]]);
170 check_why_inactive("#![cfg(not(test))]", &opts, expect![["test is enabled"]]);
171
172 check_why_inactive(
173 "#![cfg(all(not(test), not(test2)))]",
174 &opts,
175 expect![["test and test2 are enabled"]],
176 );
177 check_why_inactive("#![cfg(all(a, b))]", &opts, expect![["a and b are disabled"]]);
178 check_why_inactive(
179 "#![cfg(all(not(test), a))]",
180 &opts,
181 expect![["test is enabled and a is disabled"]],
182 );
183 check_why_inactive(
184 "#![cfg(all(not(test), test2, a))]",
185 &opts,
186 expect![["test is enabled and a is disabled"]],
187 );
188 check_why_inactive(
189 "#![cfg(all(not(test), not(test2), a))]",
190 &opts,
191 expect![["test and test2 are enabled and a is disabled"]],
192 );
193}
diff --git a/crates/completion/Cargo.toml b/crates/completion/Cargo.toml
new file mode 100644
index 000000000..25192456a
--- /dev/null
+++ b/crates/completion/Cargo.toml
@@ -0,0 +1,32 @@
1[package]
2name = "completion"
3version = "0.0.0"
4description = "TBD"
5license = "MIT OR Apache-2.0"
6authors = ["rust-analyzer developers"]
7edition = "2018"
8
9[lib]
10doctest = false
11
12[dependencies]
13itertools = "0.9.0"
14log = "0.4.8"
15rustc-hash = "1.1.0"
16
17stdx = { path = "../stdx", version = "0.0.0" }
18syntax = { path = "../syntax", version = "0.0.0" }
19text_edit = { path = "../text_edit", version = "0.0.0" }
20base_db = { path = "../base_db", version = "0.0.0" }
21ide_db = { path = "../ide_db", version = "0.0.0" }
22profile = { path = "../profile", version = "0.0.0" }
23test_utils = { path = "../test_utils", version = "0.0.0" }
24assists = { path = "../assists", version = "0.0.0" }
25call_info = { path = "../call_info", version = "0.0.0" }
26
27# completions crate should depend only on the top-level `hir` package. if you need
28# something from some `hir_xxx` subpackage, reexport the API via `hir`.
29hir = { path = "../hir", version = "0.0.0" }
30
31[dev-dependencies]
32expect-test = "1.0"
diff --git a/crates/ide/src/completion/complete_attribute.rs b/crates/completion/src/complete_attribute.rs
index f4a9864d1..f97ab7dd0 100644
--- a/crates/ide/src/completion/complete_attribute.rs
+++ b/crates/completion/src/complete_attribute.rs
@@ -6,10 +6,10 @@
6use rustc_hash::FxHashSet; 6use rustc_hash::FxHashSet;
7use syntax::{ast, AstNode, SyntaxKind}; 7use syntax::{ast, AstNode, SyntaxKind};
8 8
9use crate::completion::{ 9use crate::{
10 completion_context::CompletionContext, 10 completion_context::CompletionContext,
11 completion_item::{CompletionItem, CompletionItemKind, CompletionKind, Completions}, 11 completion_item::{CompletionItem, CompletionItemKind, CompletionKind, Completions},
12 generated_features::FEATURES, 12 generated_lint_completions::{CLIPPY_LINTS, FEATURES},
13}; 13};
14 14
15pub(super) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { 15pub(super) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> {
@@ -23,14 +23,15 @@ pub(super) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext)
23 complete_derive(acc, ctx, token_tree) 23 complete_derive(acc, ctx, token_tree)
24 } 24 }
25 (Some(path), Some(token_tree)) if path.to_string() == "feature" => { 25 (Some(path), Some(token_tree)) if path.to_string() == "feature" => {
26 complete_lint(acc, ctx, token_tree, FEATURES) 26 complete_lint(acc, ctx, token_tree, FEATURES);
27 } 27 }
28 (Some(path), Some(token_tree)) 28 (Some(path), Some(token_tree))
29 if ["allow", "warn", "deny", "forbid"] 29 if ["allow", "warn", "deny", "forbid"]
30 .iter() 30 .iter()
31 .any(|lint_level| lint_level == &path.to_string()) => 31 .any(|lint_level| lint_level == &path.to_string()) =>
32 { 32 {
33 complete_lint(acc, ctx, token_tree, DEFAULT_LINT_COMPLETIONS) 33 complete_lint(acc, ctx, token_tree.clone(), DEFAULT_LINT_COMPLETIONS);
34 complete_lint(acc, ctx, token_tree, CLIPPY_LINTS);
34 } 35 }
35 (_, Some(_token_tree)) => {} 36 (_, Some(_token_tree)) => {}
36 _ => complete_attribute_start(acc, ctx, attribute), 37 _ => complete_attribute_start(acc, ctx, attribute),
@@ -389,7 +390,7 @@ const DEFAULT_LINT_COMPLETIONS: &[LintCompletion] = &[
389mod tests { 390mod tests {
390 use expect_test::{expect, Expect}; 391 use expect_test::{expect, Expect};
391 392
392 use crate::completion::{test_utils::completion_list, CompletionKind}; 393 use crate::{test_utils::completion_list, CompletionKind};
393 394
394 fn check(ra_fixture: &str, expect: Expect) { 395 fn check(ra_fixture: &str, expect: Expect) {
395 let actual = completion_list(ra_fixture, CompletionKind::Attribute); 396 let actual = completion_list(ra_fixture, CompletionKind::Attribute);
@@ -418,130 +419,6 @@ struct Test {}
418 } 419 }
419 420
420 #[test] 421 #[test]
421 fn empty_lint_completion() {
422 check(
423 r#"#[allow(<|>)]"#,
424 expect![[r#"
425 at absolute_paths_not_starting_with_crate fully qualified paths that start with a module name instead of `crate`, `self`, or an extern crate name
426 at ambiguous_associated_items ambiguous associated items
427 at anonymous_parameters detects anonymous parameters
428 at arithmetic_overflow arithmetic operation overflows
429 at array_into_iter detects calling `into_iter` on arrays
430 at asm_sub_register using only a subset of a register for inline asm inputs
431 at bare_trait_objects suggest using `dyn Trait` for trait objects
432 at bindings_with_variant_name detects pattern bindings with the same name as one of the matched variants
433 at box_pointers use of owned (Box type) heap memory
434 at cenum_impl_drop_cast a C-like enum implementing Drop is cast
435 at clashing_extern_declarations detects when an extern fn has been declared with the same name but different types
436 at coherence_leak_check distinct impls distinguished only by the leak-check code
437 at conflicting_repr_hints conflicts between `#[repr(..)]` hints that were previously accepted and used in practice
438 at confusable_idents detects visually confusable pairs between identifiers
439 at const_err constant evaluation detected erroneous expression
440 at dead_code detect unused, unexported items
441 at deprecated detects use of deprecated items
442 at deprecated_in_future detects use of items that will be deprecated in a future version
443 at elided_lifetimes_in_paths hidden lifetime parameters in types are deprecated
444 at ellipsis_inclusive_range_patterns `...` range patterns are deprecated
445 at explicit_outlives_requirements outlives requirements can be inferred
446 at exported_private_dependencies public interface leaks type from a private dependency
447 at ill_formed_attribute_input ill-formed attribute inputs that were previously accepted and used in practice
448 at illegal_floating_point_literal_pattern floating-point literals cannot be used in patterns
449 at improper_ctypes proper use of libc types in foreign modules
450 at improper_ctypes_definitions proper use of libc types in foreign item definitions
451 at incomplete_features incomplete features that may function improperly in some or all cases
452 at incomplete_include trailing content in included file
453 at indirect_structural_match pattern with const indirectly referencing non-structural-match type
454 at inline_no_sanitize detects incompatible use of `#[inline(always)]` and `#[no_sanitize(...)]`
455 at intra_doc_link_resolution_failure failures in resolving intra-doc link targets
456 at invalid_codeblock_attributes codeblock attribute looks a lot like a known one
457 at invalid_type_param_default type parameter default erroneously allowed in invalid location
458 at invalid_value an invalid value is being created (such as a NULL reference)
459 at irrefutable_let_patterns detects irrefutable patterns in if-let and while-let statements
460 at keyword_idents detects edition keywords being used as an identifier
461 at late_bound_lifetime_arguments detects generic lifetime arguments in path segments with late bound lifetime parameters
462 at macro_expanded_macro_exports_accessed_by_absolute_paths macro-expanded `macro_export` macros from the current crate cannot be referred to by absolute paths
463 at macro_use_extern_crate the `#[macro_use]` attribute is now deprecated in favor of using macros via the module system
464 at meta_variable_misuse possible meta-variable misuse at macro definition
465 at missing_copy_implementations detects potentially-forgotten implementations of `Copy`
466 at missing_crate_level_docs detects crates with no crate-level documentation
467 at missing_debug_implementations detects missing implementations of Debug
468 at missing_doc_code_examples detects publicly-exported items without code samples in their documentation
469 at missing_docs detects missing documentation for public members
470 at missing_fragment_specifier detects missing fragment specifiers in unused `macro_rules!` patterns
471 at mixed_script_confusables detects Unicode scripts whose mixed script confusables codepoints are solely used
472 at mutable_borrow_reservation_conflict reservation of a two-phased borrow conflicts with other shared borrows
473 at mutable_transmutes mutating transmuted &mut T from &T may cause undefined behavior
474 at no_mangle_const_items const items will not have their symbols exported
475 at no_mangle_generic_items generic items must be mangled
476 at non_ascii_idents detects non-ASCII identifiers
477 at non_camel_case_types types, variants, traits and type parameters should have camel case names
478 at non_shorthand_field_patterns using `Struct { x: x }` instead of `Struct { x }` in a pattern
479 at non_snake_case variables, methods, functions, lifetime parameters and modules should have snake case names
480 at non_upper_case_globals static constants should have uppercase identifiers
481 at order_dependent_trait_objects trait-object types were treated as different depending on marker-trait order
482 at overflowing_literals literal out of range for its type
483 at overlapping_patterns detects overlapping patterns
484 at path_statements path statements with no effect
485 at patterns_in_fns_without_body patterns in functions without body were erroneously allowed
486 at private_doc_tests detects code samples in docs of private items not documented by rustdoc
487 at private_in_public detect private items in public interfaces not caught by the old implementation
488 at proc_macro_derive_resolution_fallback detects proc macro derives using inaccessible names from parent modules
489 at pub_use_of_private_extern_crate detect public re-exports of private extern crates
490 at redundant_semicolons detects unnecessary trailing semicolons
491 at renamed_and_removed_lints lints that have been renamed or removed
492 at safe_packed_borrows safe borrows of fields of packed structs were erroneously allowed
493 at single_use_lifetimes detects lifetime parameters that are only used once
494 at soft_unstable a feature gate that doesn't break dependent crates
495 at stable_features stable features found in `#[feature]` directive
496 at trivial_bounds these bounds don't depend on an type parameters
497 at trivial_casts detects trivial casts which could be removed
498 at trivial_numeric_casts detects trivial casts of numeric types which could be removed
499 at type_alias_bounds bounds in type aliases are not enforced
500 at tyvar_behind_raw_pointer raw pointer to an inference variable
501 at unaligned_references detects unaligned references to fields of packed structs
502 at uncommon_codepoints detects uncommon Unicode codepoints in identifiers
503 at unconditional_panic operation will cause a panic at runtime
504 at unconditional_recursion functions that cannot return without calling themselves
505 at unknown_crate_types unknown crate type found in `#[crate_type]` directive
506 at unknown_lints unrecognized lint attribute
507 at unnameable_test_items detects an item that cannot be named being marked as `#[test_case]`
508 at unreachable_code detects unreachable code paths
509 at unreachable_patterns detects unreachable patterns
510 at unreachable_pub `pub` items not reachable from crate root
511 at unsafe_code usage of `unsafe` code
512 at unsafe_op_in_unsafe_fn unsafe operations in unsafe functions without an explicit unsafe block are deprecated
513 at unstable_features enabling unstable features (deprecated. do not use)
514 at unstable_name_collisions detects name collision with an existing but unstable method
515 at unused_allocation detects unnecessary allocations that can be eliminated
516 at unused_assignments detect assignments that will never be read
517 at unused_attributes detects attributes that were not used by the compiler
518 at unused_braces unnecessary braces around an expression
519 at unused_comparisons comparisons made useless by limits of the types involved
520 at unused_crate_dependencies crate dependencies that are never used
521 at unused_doc_comments detects doc comments that aren't used by rustdoc
522 at unused_extern_crates extern crates that are never used
523 at unused_features unused features found in crate-level `#[feature]` directives
524 at unused_import_braces unnecessary braces around an imported item
525 at unused_imports imports that are never used
526 at unused_labels detects labels that are never used
527 at unused_lifetimes detects lifetime parameters that are never used
528 at unused_macros detects macros that were not used
529 at unused_must_use unused result of a type flagged as `#[must_use]`
530 at unused_mut detect mut variables which don't need to be mutable
531 at unused_parens `if`, `match`, `while` and `return` do not need parentheses
532 at unused_qualifications detects unnecessarily qualified names
533 at unused_results unused result of an expression in a statement
534 at unused_unsafe unnecessary use of an `unsafe` block
535 at unused_variables detect variables which are not used in any way
536 at variant_size_differences detects enums with widely varying variant sizes
537 at warnings mass-change the level for lints which produce warnings
538 at where_clauses_object_safety checks the object safety of where clauses
539 at while_true suggest using `loop { }` instead of `while true { }`
540 "#]],
541 )
542 }
543
544 #[test]
545 fn no_completion_for_incorrect_derive() { 422 fn no_completion_for_incorrect_derive() {
546 check( 423 check(
547 r#" 424 r#"
diff --git a/crates/ide/src/completion/complete_dot.rs b/crates/completion/src/complete_dot.rs
index 0b9f1798a..0eabb48ae 100644
--- a/crates/ide/src/completion/complete_dot.rs
+++ b/crates/completion/src/complete_dot.rs
@@ -4,7 +4,7 @@ use hir::{HasVisibility, Type};
4use rustc_hash::FxHashSet; 4use rustc_hash::FxHashSet;
5use test_utils::mark; 5use test_utils::mark;
6 6
7use crate::completion::{completion_context::CompletionContext, completion_item::Completions}; 7use crate::{completion_context::CompletionContext, completion_item::Completions};
8 8
9/// Complete dot accesses, i.e. fields or methods. 9/// Complete dot accesses, i.e. fields or methods.
10pub(super) fn complete_dot(acc: &mut Completions, ctx: &CompletionContext) { 10pub(super) fn complete_dot(acc: &mut Completions, ctx: &CompletionContext) {
@@ -64,7 +64,7 @@ mod tests {
64 use expect_test::{expect, Expect}; 64 use expect_test::{expect, Expect};
65 use test_utils::mark; 65 use test_utils::mark;
66 66
67 use crate::completion::{test_utils::completion_list, CompletionKind}; 67 use crate::{test_utils::completion_list, CompletionKind};
68 68
69 fn check(ra_fixture: &str, expect: Expect) { 69 fn check(ra_fixture: &str, expect: Expect) {
70 let actual = completion_list(ra_fixture, CompletionKind::Reference); 70 let actual = completion_list(ra_fixture, CompletionKind::Reference);
@@ -413,4 +413,19 @@ fn foo() {
413 "#]], 413 "#]],
414 ); 414 );
415 } 415 }
416
417 #[test]
418 fn completes_method_call_when_receiver_is_a_macro_call() {
419 check(
420 r#"
421struct S;
422impl S { fn foo(&self) {} }
423macro_rules! make_s { () => { S }; }
424fn main() { make_s!().f<|>; }
425"#,
426 expect![[r#"
427 me foo() fn foo(&self)
428 "#]],
429 )
430 }
416} 431}
diff --git a/crates/ide/src/completion/complete_fn_param.rs b/crates/completion/src/complete_fn_param.rs
index 9efe25461..918996727 100644
--- a/crates/ide/src/completion/complete_fn_param.rs
+++ b/crates/completion/src/complete_fn_param.rs
@@ -6,7 +6,7 @@ use syntax::{
6 match_ast, AstNode, 6 match_ast, AstNode,
7}; 7};
8 8
9use crate::completion::{CompletionContext, CompletionItem, CompletionKind, Completions}; 9use crate::{CompletionContext, CompletionItem, CompletionKind, Completions};
10 10
11/// Complete repeated parameters, both name and type. For example, if all 11/// Complete repeated parameters, both name and type. For example, if all
12/// functions in a file have a `spam: &mut Spam` parameter, a completion with 12/// functions in a file have a `spam: &mut Spam` parameter, a completion with
@@ -68,7 +68,7 @@ pub(super) fn complete_fn_param(acc: &mut Completions, ctx: &CompletionContext)
68mod tests { 68mod tests {
69 use expect_test::{expect, Expect}; 69 use expect_test::{expect, Expect};
70 70
71 use crate::completion::{test_utils::completion_list, CompletionKind}; 71 use crate::{test_utils::completion_list, CompletionKind};
72 72
73 fn check(ra_fixture: &str, expect: Expect) { 73 fn check(ra_fixture: &str, expect: Expect) {
74 let actual = completion_list(ra_fixture, CompletionKind::Magic); 74 let actual = completion_list(ra_fixture, CompletionKind::Magic);
diff --git a/crates/ide/src/completion/complete_keyword.rs b/crates/completion/src/complete_keyword.rs
index e59747095..ace914f3f 100644
--- a/crates/ide/src/completion/complete_keyword.rs
+++ b/crates/completion/src/complete_keyword.rs
@@ -1,11 +1,9 @@
1//! FIXME: write short doc here 1//! Completes keywords.
2 2
3use syntax::{ast, SyntaxKind}; 3use syntax::{ast, SyntaxKind};
4use test_utils::mark; 4use test_utils::mark;
5 5
6use crate::completion::{ 6use crate::{CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, Completions};
7 CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, Completions,
8};
9 7
10pub(super) fn complete_use_tree_keyword(acc: &mut Completions, ctx: &CompletionContext) { 8pub(super) fn complete_use_tree_keyword(acc: &mut Completions, ctx: &CompletionContext) {
11 // complete keyword "crate" in use stmt 9 // complete keyword "crate" in use stmt
@@ -177,7 +175,7 @@ fn complete_return(
177mod tests { 175mod tests {
178 use expect_test::{expect, Expect}; 176 use expect_test::{expect, Expect};
179 177
180 use crate::completion::{ 178 use crate::{
181 test_utils::{check_edit, completion_list}, 179 test_utils::{check_edit, completion_list},
182 CompletionKind, 180 CompletionKind,
183 }; 181 };
diff --git a/crates/ide/src/completion/complete_macro_in_item_position.rs b/crates/completion/src/complete_macro_in_item_position.rs
index fc8625d8e..d1d8c23d2 100644
--- a/crates/ide/src/completion/complete_macro_in_item_position.rs
+++ b/crates/completion/src/complete_macro_in_item_position.rs
@@ -1,6 +1,6 @@
1//! FIXME: write short doc here 1//! Completes macro invocations used in item position.
2 2
3use crate::completion::{CompletionContext, Completions}; 3use crate::{CompletionContext, Completions};
4 4
5pub(super) fn complete_macro_in_item_position(acc: &mut Completions, ctx: &CompletionContext) { 5pub(super) fn complete_macro_in_item_position(acc: &mut Completions, ctx: &CompletionContext) {
6 // Show only macros in top level. 6 // Show only macros in top level.
@@ -17,7 +17,7 @@ pub(super) fn complete_macro_in_item_position(acc: &mut Completions, ctx: &Compl
17mod tests { 17mod tests {
18 use expect_test::{expect, Expect}; 18 use expect_test::{expect, Expect};
19 19
20 use crate::completion::{test_utils::completion_list, CompletionKind}; 20 use crate::{test_utils::completion_list, CompletionKind};
21 21
22 fn check(ra_fixture: &str, expect: Expect) { 22 fn check(ra_fixture: &str, expect: Expect) {
23 let actual = completion_list(ra_fixture, CompletionKind::Reference); 23 let actual = completion_list(ra_fixture, CompletionKind::Reference);
diff --git a/crates/ide/src/completion/complete_mod.rs b/crates/completion/src/complete_mod.rs
index c7a99bdc3..35a57aba3 100644
--- a/crates/ide/src/completion/complete_mod.rs
+++ b/crates/completion/src/complete_mod.rs
@@ -150,7 +150,7 @@ fn module_chain_to_containing_module_file(
150 150
151#[cfg(test)] 151#[cfg(test)]
152mod tests { 152mod tests {
153 use crate::completion::{test_utils::completion_list, CompletionKind}; 153 use crate::{test_utils::completion_list, CompletionKind};
154 use expect_test::{expect, Expect}; 154 use expect_test::{expect, Expect};
155 155
156 fn check(ra_fixture: &str, expect: Expect) { 156 fn check(ra_fixture: &str, expect: Expect) {
diff --git a/crates/ide/src/completion/complete_pattern.rs b/crates/completion/src/complete_pattern.rs
index 5a13574d4..5606dcdd9 100644
--- a/crates/ide/src/completion/complete_pattern.rs
+++ b/crates/completion/src/complete_pattern.rs
@@ -1,6 +1,6 @@
1//! FIXME: write short doc here 1//! Completes constats and paths in patterns.
2 2
3use crate::completion::{CompletionContext, Completions}; 3use crate::{CompletionContext, Completions};
4 4
5/// Completes constats and paths in patterns. 5/// Completes constats and paths in patterns.
6pub(super) fn complete_pattern(acc: &mut Completions, ctx: &CompletionContext) { 6pub(super) fn complete_pattern(acc: &mut Completions, ctx: &CompletionContext) {
@@ -35,7 +35,7 @@ pub(super) fn complete_pattern(acc: &mut Completions, ctx: &CompletionContext) {
35mod tests { 35mod tests {
36 use expect_test::{expect, Expect}; 36 use expect_test::{expect, Expect};
37 37
38 use crate::completion::{test_utils::completion_list, CompletionKind}; 38 use crate::{test_utils::completion_list, CompletionKind};
39 39
40 fn check(ra_fixture: &str, expect: Expect) { 40 fn check(ra_fixture: &str, expect: Expect) {
41 let actual = completion_list(ra_fixture, CompletionKind::Reference); 41 let actual = completion_list(ra_fixture, CompletionKind::Reference);
diff --git a/crates/ide/src/completion/complete_postfix.rs b/crates/completion/src/complete_postfix.rs
index db5319618..700573cf2 100644
--- a/crates/ide/src/completion/complete_postfix.rs
+++ b/crates/completion/src/complete_postfix.rs
@@ -1,4 +1,4 @@
1//! FIXME: write short doc here 1//! Postfix completions, like `Ok(10).ifl<|>` => `if let Ok() = Ok(10) { <|> }`.
2 2
3mod format_like; 3mod format_like;
4 4
@@ -11,11 +11,9 @@ use text_edit::TextEdit;
11 11
12use self::format_like::add_format_like_completions; 12use self::format_like::add_format_like_completions;
13use crate::{ 13use crate::{
14 completion::{ 14 completion_config::SnippetCap,
15 completion_config::SnippetCap, 15 completion_context::CompletionContext,
16 completion_context::CompletionContext, 16 completion_item::{Builder, CompletionKind, Completions},
17 completion_item::{Builder, CompletionKind, Completions},
18 },
19 CompletionItem, CompletionItemKind, 17 CompletionItem, CompletionItemKind,
20}; 18};
21 19
@@ -263,7 +261,7 @@ fn postfix_snippet(
263mod tests { 261mod tests {
264 use expect_test::{expect, Expect}; 262 use expect_test::{expect, Expect};
265 263
266 use crate::completion::{ 264 use crate::{
267 test_utils::{check_edit, completion_list}, 265 test_utils::{check_edit, completion_list},
268 CompletionKind, 266 CompletionKind,
269 }; 267 };
diff --git a/crates/ide/src/completion/complete_postfix/format_like.rs b/crates/completion/src/complete_postfix/format_like.rs
index 50d1e5c81..205c384e2 100644
--- a/crates/ide/src/completion/complete_postfix/format_like.rs
+++ b/crates/completion/src/complete_postfix/format_like.rs
@@ -14,7 +14,7 @@
14// + `logw` -> `log::warn!(...)` 14// + `logw` -> `log::warn!(...)`
15// + `loge` -> `log::error!(...)` 15// + `loge` -> `log::error!(...)`
16 16
17use crate::completion::{ 17use crate::{
18 complete_postfix::postfix_snippet, completion_config::SnippetCap, 18 complete_postfix::postfix_snippet, completion_config::SnippetCap,
19 completion_context::CompletionContext, completion_item::Completions, 19 completion_context::CompletionContext, completion_item::Completions,
20}; 20};
diff --git a/crates/ide/src/completion/complete_qualified_path.rs b/crates/completion/src/complete_qualified_path.rs
index 2fafedd47..80b271fdf 100644
--- a/crates/ide/src/completion/complete_qualified_path.rs
+++ b/crates/completion/src/complete_qualified_path.rs
@@ -5,7 +5,7 @@ use rustc_hash::FxHashSet;
5use syntax::AstNode; 5use syntax::AstNode;
6use test_utils::mark; 6use test_utils::mark;
7 7
8use crate::completion::{CompletionContext, Completions}; 8use crate::{CompletionContext, Completions};
9 9
10pub(super) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionContext) { 10pub(super) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionContext) {
11 let path = match &ctx.path_qual { 11 let path = match &ctx.path_qual {
@@ -149,7 +149,7 @@ mod tests {
149 use expect_test::{expect, Expect}; 149 use expect_test::{expect, Expect};
150 use test_utils::mark; 150 use test_utils::mark;
151 151
152 use crate::completion::{ 152 use crate::{
153 test_utils::{check_edit, completion_list}, 153 test_utils::{check_edit, completion_list},
154 CompletionKind, 154 CompletionKind,
155 }; 155 };
diff --git a/crates/ide/src/completion/complete_record.rs b/crates/completion/src/complete_record.rs
index ceb8d16c1..129ddc055 100644
--- a/crates/ide/src/completion/complete_record.rs
+++ b/crates/completion/src/complete_record.rs
@@ -1,5 +1,5 @@
1//! Complete fields in record literals and patterns. 1//! Complete fields in record literals and patterns.
2use crate::completion::{CompletionContext, Completions}; 2use crate::{CompletionContext, Completions};
3 3
4pub(super) fn complete_record(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { 4pub(super) fn complete_record(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> {
5 let missing_fields = match (ctx.record_pat_syntax.as_ref(), ctx.record_lit_syntax.as_ref()) { 5 let missing_fields = match (ctx.record_pat_syntax.as_ref(), ctx.record_lit_syntax.as_ref()) {
@@ -20,7 +20,7 @@ pub(super) fn complete_record(acc: &mut Completions, ctx: &CompletionContext) ->
20mod tests { 20mod tests {
21 use expect_test::{expect, Expect}; 21 use expect_test::{expect, Expect};
22 22
23 use crate::completion::{test_utils::completion_list, CompletionKind}; 23 use crate::{test_utils::completion_list, CompletionKind};
24 24
25 fn check(ra_fixture: &str, expect: Expect) { 25 fn check(ra_fixture: &str, expect: Expect) {
26 let actual = completion_list(ra_fixture, CompletionKind::Reference); 26 let actual = completion_list(ra_fixture, CompletionKind::Reference);
diff --git a/crates/ide/src/completion/complete_snippet.rs b/crates/completion/src/complete_snippet.rs
index 4837d2910..06096722b 100644
--- a/crates/ide/src/completion/complete_snippet.rs
+++ b/crates/completion/src/complete_snippet.rs
@@ -1,6 +1,6 @@
1//! FIXME: write short doc here 1//! This file provides snippet completions, like `pd` => `eprintln!(...)`.
2 2
3use crate::completion::{ 3use crate::{
4 completion_config::SnippetCap, completion_item::Builder, CompletionContext, CompletionItem, 4 completion_config::SnippetCap, completion_item::Builder, CompletionContext, CompletionItem,
5 CompletionItemKind, CompletionKind, Completions, 5 CompletionItemKind, CompletionKind, Completions,
6}; 6};
@@ -71,7 +71,7 @@ fn ${1:feature}() {
71mod tests { 71mod tests {
72 use expect_test::{expect, Expect}; 72 use expect_test::{expect, Expect};
73 73
74 use crate::completion::{test_utils::completion_list, CompletionKind}; 74 use crate::{test_utils::completion_list, CompletionKind};
75 75
76 fn check(ra_fixture: &str, expect: Expect) { 76 fn check(ra_fixture: &str, expect: Expect) {
77 let actual = completion_list(ra_fixture, CompletionKind::Snippet); 77 let actual = completion_list(ra_fixture, CompletionKind::Snippet);
diff --git a/crates/ide/src/completion/complete_trait_impl.rs b/crates/completion/src/complete_trait_impl.rs
index ff115df92..c06af99e2 100644
--- a/crates/ide/src/completion/complete_trait_impl.rs
+++ b/crates/completion/src/complete_trait_impl.rs
@@ -35,15 +35,18 @@ use assists::utils::get_missing_assoc_items;
35use hir::{self, HasAttrs, HasSource}; 35use hir::{self, HasAttrs, HasSource};
36use syntax::{ 36use syntax::{
37 ast::{self, edit, Impl}, 37 ast::{self, edit, Impl},
38 display::function_declaration,
38 AstNode, SyntaxKind, SyntaxNode, TextRange, T, 39 AstNode, SyntaxKind, SyntaxNode, TextRange, T,
39}; 40};
40use text_edit::TextEdit; 41use text_edit::TextEdit;
41 42
42use crate::{ 43use crate::{
43 completion::{ 44 CompletionContext,
44 CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, Completions, 45 CompletionItem,
45 }, 46 CompletionItemKind,
46 display::function_declaration, 47 CompletionKind,
48 Completions,
49 // display::function_declaration,
47}; 50};
48 51
49#[derive(Debug, PartialEq, Eq)] 52#[derive(Debug, PartialEq, Eq)]
@@ -237,7 +240,7 @@ fn make_const_compl_syntax(const_: &ast::Const) -> String {
237mod tests { 240mod tests {
238 use expect_test::{expect, Expect}; 241 use expect_test::{expect, Expect};
239 242
240 use crate::completion::{ 243 use crate::{
241 test_utils::{check_edit, completion_list}, 244 test_utils::{check_edit, completion_list},
242 CompletionKind, 245 CompletionKind,
243 }; 246 };
diff --git a/crates/ide/src/completion/complete_unqualified_path.rs b/crates/completion/src/complete_unqualified_path.rs
index 8b6757195..5464a160d 100644
--- a/crates/ide/src/completion/complete_unqualified_path.rs
+++ b/crates/completion/src/complete_unqualified_path.rs
@@ -4,7 +4,7 @@ use hir::{Adt, ModuleDef, ScopeDef, Type};
4use syntax::AstNode; 4use syntax::AstNode;
5use test_utils::mark; 5use test_utils::mark;
6 6
7use crate::completion::{CompletionContext, Completions}; 7use crate::{CompletionContext, Completions};
8 8
9pub(super) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionContext) { 9pub(super) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionContext) {
10 if !(ctx.is_trivial_path || ctx.is_pat_binding_or_const) { 10 if !(ctx.is_trivial_path || ctx.is_pat_binding_or_const) {
@@ -68,7 +68,7 @@ mod tests {
68 use expect_test::{expect, Expect}; 68 use expect_test::{expect, Expect};
69 use test_utils::mark; 69 use test_utils::mark;
70 70
71 use crate::completion::{ 71 use crate::{
72 test_utils::{check_edit, completion_list}, 72 test_utils::{check_edit, completion_list},
73 CompletionKind, 73 CompletionKind,
74 }; 74 };
diff --git a/crates/ide/src/completion/completion_config.rs b/crates/completion/src/completion_config.rs
index 71b49ace8..71b49ace8 100644
--- a/crates/ide/src/completion/completion_config.rs
+++ b/crates/completion/src/completion_config.rs
diff --git a/crates/ide/src/completion/completion_context.rs b/crates/completion/src/completion_context.rs
index 8dea8a4bf..e4f86d0e0 100644
--- a/crates/ide/src/completion/completion_context.rs
+++ b/crates/completion/src/completion_context.rs
@@ -1,6 +1,7 @@
1//! FIXME: write short doc here 1//! See `CompletionContext` structure.
2 2
3use base_db::SourceDatabase; 3use base_db::{FilePosition, SourceDatabase};
4use call_info::ActiveParameter;
4use hir::{Local, ScopeDef, Semantics, SemanticsScope, Type}; 5use hir::{Local, ScopeDef, Semantics, SemanticsScope, Type};
5use ide_db::RootDatabase; 6use ide_db::RootDatabase;
6use syntax::{ 7use syntax::{
@@ -13,17 +14,14 @@ use test_utils::mark;
13use text_edit::Indel; 14use text_edit::Indel;
14 15
15use crate::{ 16use crate::{
16 call_info::ActiveParameter, 17 patterns::{
17 completion::{ 18 fn_is_prev, for_is_prev2, has_bind_pat_parent, has_block_expr_parent,
18 patterns::{ 19 has_field_list_parent, has_impl_as_prev_sibling, has_impl_parent,
19 has_bind_pat_parent, has_block_expr_parent, has_field_list_parent, 20 has_item_list_or_source_file_parent, has_ref_parent, has_trait_as_prev_sibling,
20 has_impl_as_prev_sibling, has_impl_parent, has_item_list_or_source_file_parent, 21 has_trait_parent, if_is_prev, inside_impl_trait_block, is_in_loop_body, is_match_arm,
21 has_ref_parent, has_trait_as_prev_sibling, has_trait_parent, if_is_prev, 22 unsafe_is_prev,
22 is_in_loop_body, is_match_arm, unsafe_is_prev,
23 },
24 CompletionConfig,
25 }, 23 },
26</