diff options
Diffstat (limited to 'crates')
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 @@ | |||
1 | use syntax::{ | 1 | use 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 | }; |
7 | use test_utils::mark; | 7 | use 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 @@ | |||
1 | use base_db::FileId; | 1 | use base_db::FileId; |
2 | use hir::{db::HirDatabase, HasSource, HasVisibility, PathResolution}; | 2 | use hir::{db::HirDatabase, HasSource, HasVisibility, PathResolution}; |
3 | use syntax::{ast, AstNode, TextRange, TextSize}; | 3 | use syntax::{ |
4 | ast::{self, VisibilityOwner}, | ||
5 | AstNode, TextRange, TextSize, | ||
6 | }; | ||
4 | 7 | ||
5 | use crate::{utils::vis_offset, AssistContext, AssistId, AssistKind, Assists}; | 8 | use crate::{utils::vis_offset, AssistContext, AssistId, AssistKind, Assists}; |
6 | use 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 @@ | |||
1 | use itertools::Itertools; | 1 | use itertools::Itertools; |
2 | use stdx::format_to; | 2 | use stdx::format_to; |
3 | use syntax::ast::{self, AstNode, GenericParamsOwner, NameOwner}; | 3 | use syntax::ast::{self, AstNode, AttrsOwner, GenericParamsOwner, NameOwner}; |
4 | 4 | ||
5 | use crate::{AssistContext, AssistId, AssistKind, Assists}; | 5 | use 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 @@ | |||
1 | use std::iter; | ||
2 | |||
3 | use hir::AsName; | ||
4 | use ide_db::RootDatabase; | ||
5 | use syntax::{ | ||
6 | ast, | ||
7 | ast::{make, ArgListOwner}, | ||
8 | AstNode, | ||
9 | }; | ||
10 | use test_utils::mark; | ||
11 | |||
12 | use 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 | // ``` | ||
36 | pub(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 | |||
98 | enum QualifyCandidate<'db> { | ||
99 | QualifierStart(ast::PathSegment), | ||
100 | UnqualifiedName, | ||
101 | TraitAssocItem(ast::Path, ast::PathSegment), | ||
102 | TraitMethod(&'db RootDatabase, ast::MethodCallExpr), | ||
103 | } | ||
104 | |||
105 | impl 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 | |||
156 | fn 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 | |||
172 | fn 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 | |||
180 | fn 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 | |||
188 | fn 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)] | ||
198 | mod 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] | ||
394 | macro_rules! foo { | ||
395 | () => () | ||
396 | } | ||
397 | |||
398 | //- /main.rs crate:main deps:crate_with_macro | ||
399 | fn main() { | ||
400 | foo<|> | ||
401 | } | ||
402 | ", | ||
403 | r" | ||
404 | fn 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 | ||
957 | pub struct Struct; | ||
958 | |||
959 | //- /main.rs crate:main deps:dep | ||
960 | fn main() { | ||
961 | Struct<|> | ||
962 | } | ||
963 | ", | ||
964 | r" | ||
965 | fn 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 | ||
979 | pub mod fmt { | ||
980 | pub trait Display {} | ||
981 | } | ||
982 | |||
983 | pub fn panic_fmt() {} | ||
984 | |||
985 | //- /main.rs crate:main deps:dep | ||
986 | struct S; | ||
987 | |||
988 | impl f<|>mt::Display for S {} | ||
989 | ", | ||
990 | r" | ||
991 | struct S; | ||
992 | |||
993 | impl 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 | ||
1005 | macro_rules! mac { | ||
1006 | () => { | ||
1007 | pub struct Cheese; | ||
1008 | }; | ||
1009 | } | ||
1010 | |||
1011 | mac!(); | ||
1012 | |||
1013 | //- /main.rs crate:main deps:dep | ||
1014 | fn main() { | ||
1015 | Cheese<|>; | ||
1016 | } | ||
1017 | ", | ||
1018 | r" | ||
1019 | fn 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 | ||
1033 | pub struct FMT; | ||
1034 | pub struct fmt; | ||
1035 | |||
1036 | //- /main.rs crate:main deps:dep | ||
1037 | fn main() { | ||
1038 | FMT<|>; | ||
1039 | } | ||
1040 | ", | ||
1041 | r" | ||
1042 | fn 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 @@ | |||
1 | use syntax::{ | ||
2 | ast::{self, HasStringValue}, | ||
3 | AstToken, | ||
4 | SyntaxKind::STRING, | ||
5 | }; | ||
6 | |||
7 | use 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 | // ``` | ||
24 | pub(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)] | ||
44 | mod 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] |
716 | fn doctest_qualify_path() { | ||
717 | check_doc_test( | ||
718 | "qualify_path", | ||
719 | r#####" | ||
720 | fn main() { | ||
721 | let map = HashMap<|>::new(); | ||
722 | } | ||
723 | pub mod std { pub mod collections { pub struct HashMap { } } } | ||
724 | "#####, | ||
725 | r#####" | ||
726 | fn main() { | ||
727 | let map = std::collections::HashMap::new(); | ||
728 | } | ||
729 | pub mod std { pub mod collections { pub struct HashMap { } } } | ||
730 | "#####, | ||
731 | ) | ||
732 | } | ||
733 | |||
734 | #[test] | ||
716 | fn doctest_remove_dbg() { | 735 | fn 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] |
904 | fn doctest_replace_string_with_char() { | ||
905 | check_doc_test( | ||
906 | "replace_string_with_char", | ||
907 | r#####" | ||
908 | fn main() { | ||
909 | find("{<|>"); | ||
910 | } | ||
911 | "#####, | ||
912 | r#####" | ||
913 | fn main() { | ||
914 | find('{'); | ||
915 | } | ||
916 | "#####, | ||
917 | ) | ||
918 | } | ||
919 | |||
920 | #[test] | ||
885 | fn doctest_replace_unwrap_with_match() { | 921 | fn 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. |
278 | pub struct FamousDefs<'a, 'b>(pub &'a Semantics<'b, RootDatabase>, pub Crate); | 278 | pub struct FamousDefs<'a, 'b>(pub &'a Semantics<'b, RootDatabase>, pub Option<Crate>); |
279 | 279 | ||
280 | #[allow(non_snake_case)] | 280 | #[allow(non_snake_case)] |
281 | impl FamousDefs<'_, '_> { | 281 | impl FamousDefs<'_, '_> { |
@@ -362,6 +362,10 @@ pub mod prelude { | |||
362 | pub use prelude::*; | 362 | pub 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. |
2 | use std::collections::BTreeSet; | ||
3 | |||
4 | use either::Either; | 2 | use either::Either; |
5 | use hir::{AsAssocItem, AssocItemContainer, ModuleDef, Semantics}; | 3 | use hir::{AsAssocItem, AssocItemContainer, ModuleDef, Semantics}; |
6 | use ide_db::{imports_locator, RootDatabase}; | 4 | use ide_db::{imports_locator, RootDatabase}; |
@@ -29,12 +27,12 @@ pub(crate) enum ImportCandidate { | |||
29 | #[derive(Debug)] | 27 | #[derive(Debug)] |
30 | pub(crate) struct TraitImportCandidate { | 28 | pub(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)] |
36 | pub(crate) struct PathImportCandidate { | 34 | pub(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 | ||
103 | impl ops::Deref for CrateName { | 103 | impl 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)] | ||
111 | pub 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 | |||
118 | impl 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 | |||
125 | impl fmt::Display for CrateDisplayName { | ||
126 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||
127 | write!(f, "{}", self.crate_name) | ||
128 | } | ||
129 | } | ||
130 | |||
131 | impl ops::Deref for CrateDisplayName { | ||
132 | type Target = str; | ||
133 | fn deref(&self) -> &str { | ||
134 | &*self.crate_name | ||
135 | } | ||
136 | } | ||
137 | |||
138 | impl 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)] |
111 | pub struct ProcMacroId(pub u32); | 146 | pub struct ProcMacroId(pub u32); |
112 | 147 | ||
@@ -127,11 +162,13 @@ impl PartialEq for ProcMacro { | |||
127 | pub struct CrateData { | 162 | pub 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 | ||
295 | impl ops::Index<CrateId> for CrateGraph { | 355 | impl 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 | }; |
20 | pub use salsa; | 20 | pub 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] | ||
2 | name = "call_info" | ||
3 | version = "0.0.0" | ||
4 | description = "TBD" | ||
5 | license = "MIT OR Apache-2.0" | ||
6 | authors = ["rust-analyzer developers"] | ||
7 | edition = "2018" | ||
8 | |||
9 | [lib] | ||
10 | doctest = false | ||
11 | |||
12 | [dependencies] | ||
13 | either = "1.5.3" | ||
14 | |||
15 | stdx = { path = "../stdx", version = "0.0.0" } | ||
16 | syntax = { path = "../syntax", version = "0.0.0" } | ||
17 | base_db = { path = "../base_db", version = "0.0.0" } | ||
18 | ide_db = { path = "../ide_db", version = "0.0.0" } | ||
19 | test_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`. | ||
23 | hir = { path = "../hir", version = "0.0.0" } | ||
24 | |||
25 | [dev-dependencies] | ||
26 | expect-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. |
2 | use base_db::FilePosition; | ||
2 | use either::Either; | 3 | use either::Either; |
3 | use hir::{HasAttrs, HirDisplay, Semantics, Type}; | 4 | use hir::{HasAttrs, HirDisplay, Semantics, Type}; |
4 | use ide_db::RootDatabase; | 5 | use ide_db::RootDatabase; |
@@ -9,8 +10,6 @@ use syntax::{ | |||
9 | }; | 10 | }; |
10 | use test_utils::mark; | 11 | use test_utils::mark; |
11 | 12 | ||
12 | use 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. |
43 | pub(crate) fn call_info(db: &RootDatabase, position: FilePosition) -> Option<CallInfo> { | 42 | pub 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)] |
144 | pub(crate) struct ActiveParameter { | 143 | pub struct ActiveParameter { |
145 | pub(crate) ty: Type, | 144 | pub ty: Type, |
146 | pub(crate) name: String, | 145 | pub name: String, |
147 | } | 146 | } |
148 | 147 | ||
149 | impl ActiveParameter { | 148 | impl 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)] |
175 | pub(crate) enum FnCallNode { | 174 | pub 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)] |
231 | mod tests { | 230 | mod 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] |
18 | mbe = { path = "../mbe" } | 18 | mbe = { path = "../mbe" } |
19 | syntax = { path = "../syntax" } | 19 | syntax = { path = "../syntax" } |
20 | expect-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 | ||
5 | use std::slice::Iter as SliceIter; | 5 | use std::{fmt, slice::Iter as SliceIter}; |
6 | 6 | ||
7 | use tt::SmolStr; | 7 | use tt::SmolStr; |
8 | 8 | ||
9 | /// A simple configuration value passed in from the outside. | ||
10 | #[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)] | ||
11 | pub 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 | |||
21 | impl 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 | |||
43 | impl 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)] |
10 | pub enum CfgExpr { | 53 | pub 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 | ||
61 | impl From<CfgAtom> for CfgExpr { | ||
62 | fn from(atom: CfgAtom) -> Self { | ||
63 | CfgExpr::Atom(atom) | ||
64 | } | ||
65 | } | ||
66 | |||
19 | impl CfgExpr { | 67 | impl 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)] | ||
86 | mod 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 | |||
9 | use std::fmt; | ||
10 | |||
11 | use rustc_hash::FxHashSet; | ||
12 | |||
13 | use crate::{CfgAtom, CfgDiff, CfgExpr, CfgOptions, InactiveReason}; | ||
14 | |||
15 | /// A `#[cfg]` directive in Disjunctive Normal Form (DNF). | ||
16 | pub struct DnfExpr { | ||
17 | conjunctions: Vec<Conjunction>, | ||
18 | } | ||
19 | |||
20 | struct Conjunction { | ||
21 | literals: Vec<Literal>, | ||
22 | } | ||
23 | |||
24 | struct Literal { | ||
25 | negate: bool, | ||
26 | var: Option<CfgAtom>, // None = Invalid | ||
27 | } | ||
28 | |||
29 | impl 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 | |||
125 | impl 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 | |||
145 | impl 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 | |||
165 | impl 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 | |||
185 | impl 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 | |||
200 | impl 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 | |||
219 | struct Builder { | ||
220 | expr: DnfExpr, | ||
221 | } | ||
222 | |||
223 | impl 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 | |||
255 | fn 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. | ||
268 | fn 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 | |||
300 | fn 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 | ||
3 | mod cfg_expr; | 3 | mod cfg_expr; |
4 | mod dnf; | ||
5 | #[cfg(test)] | ||
6 | mod tests; | ||
7 | |||
8 | use std::fmt; | ||
4 | 9 | ||
5 | use rustc_hash::FxHashSet; | 10 | use rustc_hash::FxHashSet; |
6 | use tt::SmolStr; | 11 | use tt::SmolStr; |
7 | 12 | ||
8 | pub use cfg_expr::CfgExpr; | 13 | pub use cfg_expr::{CfgAtom, CfgExpr}; |
14 | pub 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)] |
21 | pub struct CfgOptions { | 27 | pub struct CfgOptions { |
22 | atoms: FxHashSet<SmolStr>, | 28 | enabled: FxHashSet<CfgAtom>, |
23 | key_values: FxHashSet<(SmolStr, SmolStr)>, | ||
24 | } | 29 | } |
25 | 30 | ||
26 | impl CfgOptions { | 31 | impl 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 | |||
61 | pub 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 | |||
67 | impl 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 | |||
74 | impl 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 | |||
112 | pub struct InactiveReason { | ||
113 | enabled: Vec<CfgAtom>, | ||
114 | disabled: Vec<CfgAtom>, | ||
115 | } | ||
116 | |||
117 | impl 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 @@ | |||
1 | use expect_test::{expect, Expect}; | ||
2 | use mbe::ast_to_token_tree; | ||
3 | use syntax::{ast, AstNode}; | ||
4 | |||
5 | use crate::{CfgAtom, CfgExpr, CfgOptions, DnfExpr}; | ||
6 | |||
7 | fn 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 | |||
17 | fn 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 | |||
28 | fn 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] | ||
41 | fn 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] | ||
54 | fn 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] | ||
86 | fn 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] | ||
98 | fn 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] | ||
115 | fn 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] | ||
124 | fn 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] | ||
134 | fn 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] | ||
153 | fn 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] | ||
164 | fn 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] | ||
2 | name = "completion" | ||
3 | version = "0.0.0" | ||
4 | description = "TBD" | ||
5 | license = "MIT OR Apache-2.0" | ||
6 | authors = ["rust-analyzer developers"] | ||
7 | edition = "2018" | ||
8 | |||
9 | [lib] | ||
10 | doctest = false | ||
11 | |||
12 | [dependencies] | ||
13 | itertools = "0.9.0" | ||
14 | log = "0.4.8" | ||
15 | rustc-hash = "1.1.0" | ||
16 | |||
17 | stdx = { path = "../stdx", version = "0.0.0" } | ||
18 | syntax = { path = "../syntax", version = "0.0.0" } | ||
19 | text_edit = { path = "../text_edit", version = "0.0.0" } | ||
20 | base_db = { path = "../base_db", version = "0.0.0" } | ||
21 | ide_db = { path = "../ide_db", version = "0.0.0" } | ||
22 | profile = { path = "../profile", version = "0.0.0" } | ||
23 | test_utils = { path = "../test_utils", version = "0.0.0" } | ||
24 | assists = { path = "../assists", version = "0.0.0" } | ||
25 | call_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`. | ||
29 | hir = { path = "../hir", version = "0.0.0" } | ||
30 | |||
31 | [dev-dependencies] | ||
32 | expect-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 @@ | |||
6 | use rustc_hash::FxHashSet; | 6 | use rustc_hash::FxHashSet; |
7 | use syntax::{ast, AstNode, SyntaxKind}; | 7 | use syntax::{ast, AstNode, SyntaxKind}; |
8 | 8 | ||
9 | use crate::completion::{ | 9 | use 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 | ||
15 | pub(super) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { | 15 | pub(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] = &[ | |||
389 | mod tests { | 390 | mod 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}; | |||
4 | use rustc_hash::FxHashSet; | 4 | use rustc_hash::FxHashSet; |
5 | use test_utils::mark; | 5 | use test_utils::mark; |
6 | 6 | ||
7 | use crate::completion::{completion_context::CompletionContext, completion_item::Completions}; | 7 | use 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. |
10 | pub(super) fn complete_dot(acc: &mut Completions, ctx: &CompletionContext) { | 10 | pub(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#" | ||
421 | struct S; | ||
422 | impl S { fn foo(&self) {} } | ||
423 | macro_rules! make_s { () => { S }; } | ||
424 | fn 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 | ||
9 | use crate::completion::{CompletionContext, CompletionItem, CompletionKind, Completions}; | 9 | use 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) | |||
68 | mod tests { | 68 | mod 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 | ||
3 | use syntax::{ast, SyntaxKind}; | 3 | use syntax::{ast, SyntaxKind}; |
4 | use test_utils::mark; | 4 | use test_utils::mark; |
5 | 5 | ||
6 | use crate::completion::{ | 6 | use crate::{CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, Completions}; |
7 | CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, Completions, | ||
8 | }; | ||
9 | 7 | ||
10 | pub(super) fn complete_use_tree_keyword(acc: &mut Completions, ctx: &CompletionContext) { | 8 | pub(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( | |||
177 | mod tests { | 175 | mod 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 | ||
3 | use crate::completion::{CompletionContext, Completions}; | 3 | use crate::{CompletionContext, Completions}; |
4 | 4 | ||
5 | pub(super) fn complete_macro_in_item_position(acc: &mut Completions, ctx: &CompletionContext) { | 5 | pub(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 | |||
17 | mod tests { | 17 | mod 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)] |
152 | mod tests { | 152 | mod 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 | ||
3 | use crate::completion::{CompletionContext, Completions}; | 3 | use crate::{CompletionContext, Completions}; |
4 | 4 | ||
5 | /// Completes constats and paths in patterns. | 5 | /// Completes constats and paths in patterns. |
6 | pub(super) fn complete_pattern(acc: &mut Completions, ctx: &CompletionContext) { | 6 | pub(super) fn complete_pattern(acc: &mut Completions, ctx: &CompletionContext) { |
@@ -35,7 +35,7 @@ pub(super) fn complete_pattern(acc: &mut Completions, ctx: &CompletionContext) { | |||
35 | mod tests { | 35 | mod 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 | ||
3 | mod format_like; | 3 | mod format_like; |
4 | 4 | ||
@@ -11,11 +11,9 @@ use text_edit::TextEdit; | |||
11 | 11 | ||
12 | use self::format_like::add_format_like_completions; | 12 | use self::format_like::add_format_like_completions; |
13 | use crate::{ | 13 | use 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( | |||
263 | mod tests { | 261 | mod 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 | ||
17 | use crate::completion::{ | 17 | use 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; | |||
5 | use syntax::AstNode; | 5 | use syntax::AstNode; |
6 | use test_utils::mark; | 6 | use test_utils::mark; |
7 | 7 | ||
8 | use crate::completion::{CompletionContext, Completions}; | 8 | use crate::{CompletionContext, Completions}; |
9 | 9 | ||
10 | pub(super) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionContext) { | 10 | pub(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. |
2 | use crate::completion::{CompletionContext, Completions}; | 2 | use crate::{CompletionContext, Completions}; |
3 | 3 | ||
4 | pub(super) fn complete_record(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { | 4 | pub(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) -> | |||
20 | mod tests { | 20 | mod 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 | ||
3 | use crate::completion::{ | 3 | use 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}() { | |||
71 | mod tests { | 71 | mod 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; | |||
35 | use hir::{self, HasAttrs, HasSource}; | 35 | use hir::{self, HasAttrs, HasSource}; |
36 | use syntax::{ | 36 | use 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 | }; |
40 | use text_edit::TextEdit; | 41 | use text_edit::TextEdit; |
41 | 42 | ||
42 | use crate::{ | 43 | use 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 { | |||
237 | mod tests { | 240 | mod 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}; | |||
4 | use syntax::AstNode; | 4 | use syntax::AstNode; |
5 | use test_utils::mark; | 5 | use test_utils::mark; |
6 | 6 | ||
7 | use crate::completion::{CompletionContext, Completions}; | 7 | use crate::{CompletionContext, Completions}; |
8 | 8 | ||
9 | pub(super) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionContext) { | 9 | pub(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 | ||
3 | use base_db::SourceDatabase; | 3 | use base_db::{FilePosition, SourceDatabase}; |
4 | use call_info::ActiveParameter; | ||
4 | use hir::{Local, ScopeDef, Semantics, SemanticsScope, Type}; | 5 | use hir::{Local, ScopeDef, Semantics, SemanticsScope, Type}; |
5 | use ide_db::RootDatabase; | 6 | use ide_db::RootDatabase; |
6 | use syntax::{ | 7 | use syntax::{ |
@@ -13,17 +14,14 @@ use test_utils::mark; | |||
13 | use text_edit::Indel; | 14 | use text_edit::Indel; |
14 | 15 | ||
15 | use crate::{ | 16 | use 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 |