diff options
Diffstat (limited to 'crates')
102 files changed, 2540 insertions, 729 deletions
diff --git a/crates/assists/Cargo.toml b/crates/assists/Cargo.toml index 264125651..108f656e9 100644 --- a/crates/assists/Cargo.toml +++ b/crates/assists/Cargo.toml | |||
@@ -18,7 +18,6 @@ stdx = { path = "../stdx", version = "0.0.0" } | |||
18 | syntax = { path = "../syntax", version = "0.0.0" } | 18 | syntax = { path = "../syntax", version = "0.0.0" } |
19 | text_edit = { path = "../text_edit", version = "0.0.0" } | 19 | text_edit = { path = "../text_edit", version = "0.0.0" } |
20 | profile = { path = "../profile", version = "0.0.0" } | 20 | profile = { path = "../profile", version = "0.0.0" } |
21 | base_db = { path = "../base_db", version = "0.0.0" } | ||
22 | ide_db = { path = "../ide_db", version = "0.0.0" } | 21 | ide_db = { path = "../ide_db", version = "0.0.0" } |
23 | hir = { path = "../hir", version = "0.0.0" } | 22 | hir = { path = "../hir", version = "0.0.0" } |
24 | test_utils = { path = "../test_utils", version = "0.0.0" } | 23 | test_utils = { path = "../test_utils", version = "0.0.0" } |
diff --git a/crates/assists/src/assist_context.rs b/crates/assists/src/assist_context.rs index bf520069e..d11fee196 100644 --- a/crates/assists/src/assist_context.rs +++ b/crates/assists/src/assist_context.rs | |||
@@ -3,8 +3,8 @@ | |||
3 | use std::mem; | 3 | use std::mem; |
4 | 4 | ||
5 | use algo::find_covering_element; | 5 | use algo::find_covering_element; |
6 | use base_db::{FileId, FileRange}; | ||
7 | use hir::Semantics; | 6 | use hir::Semantics; |
7 | use ide_db::base_db::{FileId, FileRange}; | ||
8 | use ide_db::{ | 8 | use ide_db::{ |
9 | label::Label, | 9 | label::Label, |
10 | source_change::{SourceChange, SourceFileEdit}, | 10 | source_change::{SourceChange, SourceFileEdit}, |
diff --git a/crates/assists/src/handlers/add_missing_impl_members.rs b/crates/assists/src/handlers/add_missing_impl_members.rs index 4c400f287..b82fb30ad 100644 --- a/crates/assists/src/handlers/add_missing_impl_members.rs +++ b/crates/assists/src/handlers/add_missing_impl_members.rs | |||
@@ -1,4 +1,5 @@ | |||
1 | use hir::HasSource; | 1 | use hir::HasSource; |
2 | use ide_db::traits::{get_missing_assoc_items, resolve_target_trait}; | ||
2 | use syntax::{ | 3 | use syntax::{ |
3 | ast::{ | 4 | ast::{ |
4 | self, | 5 | self, |
@@ -11,7 +12,7 @@ use syntax::{ | |||
11 | use crate::{ | 12 | use crate::{ |
12 | assist_context::{AssistContext, Assists}, | 13 | assist_context::{AssistContext, Assists}, |
13 | ast_transform::{self, AstTransform, QualifyPaths, SubstituteTypeParams}, | 14 | ast_transform::{self, AstTransform, QualifyPaths, SubstituteTypeParams}, |
14 | utils::{get_missing_assoc_items, render_snippet, resolve_target_trait, Cursor}, | 15 | utils::{render_snippet, Cursor}, |
15 | AssistId, AssistKind, | 16 | AssistId, AssistKind, |
16 | }; | 17 | }; |
17 | 18 | ||
diff --git a/crates/assists/src/handlers/extract_struct_from_enum_variant.rs b/crates/assists/src/handlers/extract_struct_from_enum_variant.rs index 7f4f80b23..48433feb9 100644 --- a/crates/assists/src/handlers/extract_struct_from_enum_variant.rs +++ b/crates/assists/src/handlers/extract_struct_from_enum_variant.rs | |||
@@ -1,5 +1,5 @@ | |||
1 | use base_db::FileId; | ||
2 | use hir::{EnumVariant, Module, ModuleDef, Name}; | 1 | use hir::{EnumVariant, Module, ModuleDef, Name}; |
2 | use ide_db::base_db::FileId; | ||
3 | use ide_db::{defs::Definition, search::Reference, RootDatabase}; | 3 | use ide_db::{defs::Definition, search::Reference, RootDatabase}; |
4 | use itertools::Itertools; | 4 | use itertools::Itertools; |
5 | use rustc_hash::FxHashSet; | 5 | use rustc_hash::FxHashSet; |
diff --git a/crates/assists/src/handlers/fix_visibility.rs b/crates/assists/src/handlers/fix_visibility.rs index 66f74150c..c86720787 100644 --- a/crates/assists/src/handlers/fix_visibility.rs +++ b/crates/assists/src/handlers/fix_visibility.rs | |||
@@ -1,5 +1,5 @@ | |||
1 | use base_db::FileId; | ||
2 | use hir::{db::HirDatabase, HasSource, HasVisibility, PathResolution}; | 1 | use hir::{db::HirDatabase, HasSource, HasVisibility, PathResolution}; |
2 | use ide_db::base_db::FileId; | ||
3 | use syntax::{ | 3 | use syntax::{ |
4 | ast::{self, VisibilityOwner}, | 4 | ast::{self, VisibilityOwner}, |
5 | AstNode, TextRange, TextSize, | 5 | AstNode, TextRange, TextSize, |
diff --git a/crates/assists/src/handlers/generate_function.rs b/crates/assists/src/handlers/generate_function.rs index d23f4293b..758188a42 100644 --- a/crates/assists/src/handlers/generate_function.rs +++ b/crates/assists/src/handlers/generate_function.rs | |||
@@ -1,5 +1,5 @@ | |||
1 | use base_db::FileId; | ||
2 | use hir::HirDisplay; | 1 | use hir::HirDisplay; |
2 | use ide_db::base_db::FileId; | ||
3 | use rustc_hash::{FxHashMap, FxHashSet}; | 3 | use rustc_hash::{FxHashMap, FxHashSet}; |
4 | use syntax::{ | 4 | use syntax::{ |
5 | ast::{ | 5 | ast::{ |
diff --git a/crates/assists/src/handlers/remove_dbg.rs b/crates/assists/src/handlers/remove_dbg.rs index e10616779..9731344b8 100644 --- a/crates/assists/src/handlers/remove_dbg.rs +++ b/crates/assists/src/handlers/remove_dbg.rs | |||
@@ -93,8 +93,9 @@ fn needs_parentheses_around_macro_contents(macro_contents: Vec<SyntaxElement>) - | |||
93 | if macro_contents.len() < 2 { | 93 | if macro_contents.len() < 2 { |
94 | return false; | 94 | return false; |
95 | } | 95 | } |
96 | let mut macro_contents = macro_contents.into_iter().peekable(); | ||
96 | let mut unpaired_brackets_in_contents = Vec::new(); | 97 | let mut unpaired_brackets_in_contents = Vec::new(); |
97 | for element in macro_contents { | 98 | while let Some(element) = macro_contents.next() { |
98 | match element.kind() { | 99 | match element.kind() { |
99 | T!['('] | T!['['] | T!['{'] => unpaired_brackets_in_contents.push(element), | 100 | T!['('] | T!['['] | T!['{'] => unpaired_brackets_in_contents.push(element), |
100 | T![')'] => { | 101 | T![')'] => { |
@@ -118,8 +119,14 @@ fn needs_parentheses_around_macro_contents(macro_contents: Vec<SyntaxElement>) - | |||
118 | symbol_kind => { | 119 | symbol_kind => { |
119 | let symbol_not_in_bracket = unpaired_brackets_in_contents.is_empty(); | 120 | let symbol_not_in_bracket = unpaired_brackets_in_contents.is_empty(); |
120 | if symbol_not_in_bracket | 121 | if symbol_not_in_bracket |
121 | && symbol_kind != SyntaxKind::COLON | 122 | && symbol_kind != SyntaxKind::COLON // paths |
122 | && symbol_kind.is_punct() | 123 | && (symbol_kind != SyntaxKind::DOT // field/method access |
124 | || macro_contents // range expressions consist of two SyntaxKind::Dot in macro invocations | ||
125 | .peek() | ||
126 | .map(|element| element.kind() == SyntaxKind::DOT) | ||
127 | .unwrap_or(false)) | ||
128 | && symbol_kind != SyntaxKind::QUESTION // try operator | ||
129 | && (symbol_kind.is_punct() || symbol_kind == SyntaxKind::AS_KW) | ||
123 | { | 130 | { |
124 | return true; | 131 | return true; |
125 | } | 132 | } |
@@ -243,6 +250,25 @@ fn main() { | |||
243 | } | 250 | } |
244 | 251 | ||
245 | #[test] | 252 | #[test] |
253 | fn test_remove_dbg_method_chaining() { | ||
254 | check_assist( | ||
255 | remove_dbg, | ||
256 | r#"let res = <|>dbg!(foo().bar()).baz();"#, | ||
257 | r#"let res = foo().bar().baz();"#, | ||
258 | ); | ||
259 | check_assist( | ||
260 | remove_dbg, | ||
261 | r#"let res = <|>dbg!(foo.bar()).baz();"#, | ||
262 | r#"let res = foo.bar().baz();"#, | ||
263 | ); | ||
264 | } | ||
265 | |||
266 | #[test] | ||
267 | fn test_remove_dbg_field_chaining() { | ||
268 | check_assist(remove_dbg, r#"let res = <|>dbg!(foo.bar).baz;"#, r#"let res = foo.bar.baz;"#); | ||
269 | } | ||
270 | |||
271 | #[test] | ||
246 | fn test_remove_dbg_from_inside_fn() { | 272 | fn test_remove_dbg_from_inside_fn() { |
247 | check_assist_target( | 273 | check_assist_target( |
248 | remove_dbg, | 274 | remove_dbg, |
@@ -280,4 +306,59 @@ fn main() { | |||
280 | }"#, | 306 | }"#, |
281 | ); | 307 | ); |
282 | } | 308 | } |
309 | |||
310 | #[test] | ||
311 | fn test_remove_dbg_try_expr() { | ||
312 | check_assist( | ||
313 | remove_dbg, | ||
314 | r#"let res = <|>dbg!(result?).foo();"#, | ||
315 | r#"let res = result?.foo();"#, | ||
316 | ); | ||
317 | } | ||
318 | |||
319 | #[test] | ||
320 | fn test_remove_dbg_await_expr() { | ||
321 | check_assist( | ||
322 | remove_dbg, | ||
323 | r#"let res = <|>dbg!(fut.await).foo();"#, | ||
324 | r#"let res = fut.await.foo();"#, | ||
325 | ); | ||
326 | } | ||
327 | |||
328 | #[test] | ||
329 | fn test_remove_dbg_as_cast() { | ||
330 | check_assist( | ||
331 | remove_dbg, | ||
332 | r#"let res = <|>dbg!(3 as usize).foo();"#, | ||
333 | r#"let res = (3 as usize).foo();"#, | ||
334 | ); | ||
335 | } | ||
336 | |||
337 | #[test] | ||
338 | fn test_remove_dbg_index_expr() { | ||
339 | check_assist( | ||
340 | remove_dbg, | ||
341 | r#"let res = <|>dbg!(array[3]).foo();"#, | ||
342 | r#"let res = array[3].foo();"#, | ||
343 | ); | ||
344 | check_assist( | ||
345 | remove_dbg, | ||
346 | r#"let res = <|>dbg!(tuple.3).foo();"#, | ||
347 | r#"let res = tuple.3.foo();"#, | ||
348 | ); | ||
349 | } | ||
350 | |||
351 | #[test] | ||
352 | fn test_remove_dbg_range_expr() { | ||
353 | check_assist( | ||
354 | remove_dbg, | ||
355 | r#"let res = <|>dbg!(foo..bar).foo();"#, | ||
356 | r#"let res = (foo..bar).foo();"#, | ||
357 | ); | ||
358 | check_assist( | ||
359 | remove_dbg, | ||
360 | r#"let res = <|>dbg!(foo..=bar).foo();"#, | ||
361 | r#"let res = (foo..=bar).foo();"#, | ||
362 | ); | ||
363 | } | ||
283 | } | 364 | } |
diff --git a/crates/assists/src/handlers/replace_if_let_with_match.rs b/crates/assists/src/handlers/replace_if_let_with_match.rs index 79097621e..9a49c48c1 100644 --- a/crates/assists/src/handlers/replace_if_let_with_match.rs +++ b/crates/assists/src/handlers/replace_if_let_with_match.rs | |||
@@ -7,10 +7,8 @@ use syntax::{ | |||
7 | AstNode, | 7 | AstNode, |
8 | }; | 8 | }; |
9 | 9 | ||
10 | use crate::{ | 10 | use crate::{utils::unwrap_trivial_block, AssistContext, AssistId, AssistKind, Assists}; |
11 | utils::{unwrap_trivial_block, TryEnum}, | 11 | use ide_db::ty_filter::TryEnum; |
12 | AssistContext, AssistId, AssistKind, Assists, | ||
13 | }; | ||
14 | 12 | ||
15 | // Assist: replace_if_let_with_match | 13 | // Assist: replace_if_let_with_match |
16 | // | 14 | // |
diff --git a/crates/assists/src/handlers/replace_let_with_if_let.rs b/crates/assists/src/handlers/replace_let_with_if_let.rs index ed6d0c29b..a5bcbda24 100644 --- a/crates/assists/src/handlers/replace_let_with_if_let.rs +++ b/crates/assists/src/handlers/replace_let_with_if_let.rs | |||
@@ -9,7 +9,8 @@ use syntax::{ | |||
9 | AstNode, T, | 9 | AstNode, T, |
10 | }; | 10 | }; |
11 | 11 | ||
12 | use crate::{utils::TryEnum, AssistContext, AssistId, AssistKind, Assists}; | 12 | use crate::{AssistContext, AssistId, AssistKind, Assists}; |
13 | use ide_db::ty_filter::TryEnum; | ||
13 | 14 | ||
14 | // Assist: replace_let_with_if_let | 15 | // Assist: replace_let_with_if_let |
15 | // | 16 | // |
diff --git a/crates/assists/src/handlers/replace_unwrap_with_match.rs b/crates/assists/src/handlers/replace_unwrap_with_match.rs index 4043c219c..f547066f0 100644 --- a/crates/assists/src/handlers/replace_unwrap_with_match.rs +++ b/crates/assists/src/handlers/replace_unwrap_with_match.rs | |||
@@ -10,9 +10,10 @@ use syntax::{ | |||
10 | }; | 10 | }; |
11 | 11 | ||
12 | use crate::{ | 12 | use crate::{ |
13 | utils::{render_snippet, Cursor, TryEnum}, | 13 | utils::{render_snippet, Cursor}, |
14 | AssistContext, AssistId, AssistKind, Assists, | 14 | AssistContext, AssistId, AssistKind, Assists, |
15 | }; | 15 | }; |
16 | use ide_db::ty_filter::TryEnum; | ||
16 | 17 | ||
17 | // Assist: replace_unwrap_with_match | 18 | // Assist: replace_unwrap_with_match |
18 | // | 19 | // |
diff --git a/crates/assists/src/lib.rs b/crates/assists/src/lib.rs index 8a664f654..70a651e10 100644 --- a/crates/assists/src/lib.rs +++ b/crates/assists/src/lib.rs | |||
@@ -17,8 +17,8 @@ mod tests; | |||
17 | pub mod utils; | 17 | pub mod utils; |
18 | pub mod ast_transform; | 18 | pub mod ast_transform; |
19 | 19 | ||
20 | use base_db::FileRange; | ||
21 | use hir::Semantics; | 20 | use hir::Semantics; |
21 | use ide_db::base_db::FileRange; | ||
22 | use ide_db::{label::Label, source_change::SourceChange, RootDatabase}; | 22 | use ide_db::{label::Label, source_change::SourceChange, RootDatabase}; |
23 | use syntax::TextRange; | 23 | use syntax::TextRange; |
24 | 24 | ||
diff --git a/crates/assists/src/tests.rs b/crates/assists/src/tests.rs index 2b687decf..849d85e76 100644 --- a/crates/assists/src/tests.rs +++ b/crates/assists/src/tests.rs | |||
@@ -1,7 +1,7 @@ | |||
1 | mod generated; | 1 | mod generated; |
2 | 2 | ||
3 | use base_db::{fixture::WithFixture, FileId, FileRange, SourceDatabaseExt}; | ||
4 | use hir::Semantics; | 3 | use hir::Semantics; |
4 | use ide_db::base_db::{fixture::WithFixture, FileId, FileRange, SourceDatabaseExt}; | ||
5 | use ide_db::RootDatabase; | 5 | use ide_db::RootDatabase; |
6 | use syntax::TextRange; | 6 | use syntax::TextRange; |
7 | use test_utils::{assert_eq_text, extract_offset, extract_range}; | 7 | use test_utils::{assert_eq_text, extract_offset, extract_range}; |
diff --git a/crates/assists/src/utils.rs b/crates/assists/src/utils.rs index 1a6b48b45..56f925ee6 100644 --- a/crates/assists/src/utils.rs +++ b/crates/assists/src/utils.rs | |||
@@ -2,14 +2,13 @@ | |||
2 | pub(crate) mod insert_use; | 2 | pub(crate) mod insert_use; |
3 | pub(crate) mod import_assets; | 3 | pub(crate) mod import_assets; |
4 | 4 | ||
5 | use std::{iter, ops}; | 5 | use std::ops; |
6 | 6 | ||
7 | use hir::{Adt, Crate, Enum, Module, ScopeDef, Semantics, Trait, Type}; | 7 | use hir::{Crate, Enum, Module, ScopeDef, Semantics, Trait}; |
8 | use ide_db::RootDatabase; | 8 | use ide_db::RootDatabase; |
9 | use itertools::Itertools; | 9 | use itertools::Itertools; |
10 | use rustc_hash::FxHashSet; | ||
11 | use syntax::{ | 10 | use syntax::{ |
12 | ast::{self, make, ArgListOwner, NameOwner}, | 11 | ast::{self, make, ArgListOwner}, |
13 | AstNode, Direction, | 12 | AstNode, Direction, |
14 | SyntaxKind::*, | 13 | SyntaxKind::*, |
15 | SyntaxNode, TextSize, T, | 14 | SyntaxNode, TextSize, T, |
@@ -115,72 +114,6 @@ pub(crate) fn render_snippet(_cap: SnippetCap, node: &SyntaxNode, cursor: Cursor | |||
115 | } | 114 | } |
116 | } | 115 | } |
117 | 116 | ||
118 | pub fn get_missing_assoc_items( | ||
119 | sema: &Semantics<RootDatabase>, | ||
120 | impl_def: &ast::Impl, | ||
121 | ) -> Vec<hir::AssocItem> { | ||
122 | // Names must be unique between constants and functions. However, type aliases | ||
123 | // may share the same name as a function or constant. | ||
124 | let mut impl_fns_consts = FxHashSet::default(); | ||
125 | let mut impl_type = FxHashSet::default(); | ||
126 | |||
127 | if let Some(item_list) = impl_def.assoc_item_list() { | ||
128 | for item in item_list.assoc_items() { | ||
129 | match item { | ||
130 | ast::AssocItem::Fn(f) => { | ||
131 | if let Some(n) = f.name() { | ||
132 | impl_fns_consts.insert(n.syntax().to_string()); | ||
133 | } | ||
134 | } | ||
135 | |||
136 | ast::AssocItem::TypeAlias(t) => { | ||
137 | if let Some(n) = t.name() { | ||
138 | impl_type.insert(n.syntax().to_string()); | ||
139 | } | ||
140 | } | ||
141 | |||
142 | ast::AssocItem::Const(c) => { | ||
143 | if let Some(n) = c.name() { | ||
144 | impl_fns_consts.insert(n.syntax().to_string()); | ||
145 | } | ||
146 | } | ||
147 | ast::AssocItem::MacroCall(_) => (), | ||
148 | } | ||
149 | } | ||
150 | } | ||
151 | |||
152 | resolve_target_trait(sema, impl_def).map_or(vec![], |target_trait| { | ||
153 | target_trait | ||
154 | .items(sema.db) | ||
155 | .iter() | ||
156 | .filter(|i| match i { | ||
157 | hir::AssocItem::Function(f) => { | ||
158 | !impl_fns_consts.contains(&f.name(sema.db).to_string()) | ||
159 | } | ||
160 | hir::AssocItem::TypeAlias(t) => !impl_type.contains(&t.name(sema.db).to_string()), | ||
161 | hir::AssocItem::Const(c) => c | ||
162 | .name(sema.db) | ||
163 | .map(|n| !impl_fns_consts.contains(&n.to_string())) | ||
164 | .unwrap_or_default(), | ||
165 | }) | ||
166 | .cloned() | ||
167 | .collect() | ||
168 | }) | ||
169 | } | ||
170 | |||
171 | pub(crate) fn resolve_target_trait( | ||
172 | sema: &Semantics<RootDatabase>, | ||
173 | impl_def: &ast::Impl, | ||
174 | ) -> Option<hir::Trait> { | ||
175 | let ast_path = | ||
176 | impl_def.trait_().map(|it| it.syntax().clone()).and_then(ast::PathType::cast)?.path()?; | ||
177 | |||
178 | match sema.resolve_path(&ast_path) { | ||
179 | Some(hir::PathResolution::Def(hir::ModuleDef::Trait(def))) => Some(def), | ||
180 | _ => None, | ||
181 | } | ||
182 | } | ||
183 | |||
184 | pub(crate) fn vis_offset(node: &SyntaxNode) -> TextSize { | 117 | pub(crate) fn vis_offset(node: &SyntaxNode) -> TextSize { |
185 | node.children_with_tokens() | 118 | node.children_with_tokens() |
186 | .find(|it| !matches!(it.kind(), WHITESPACE | COMMENT | ATTR)) | 119 | .find(|it| !matches!(it.kind(), WHITESPACE | COMMENT | ATTR)) |
@@ -223,54 +156,6 @@ fn invert_special_case(expr: &ast::Expr) -> Option<ast::Expr> { | |||
223 | } | 156 | } |
224 | } | 157 | } |
225 | 158 | ||
226 | #[derive(Clone, Copy)] | ||
227 | pub enum TryEnum { | ||
228 | Result, | ||
229 | Option, | ||
230 | } | ||
231 | |||
232 | impl TryEnum { | ||
233 | const ALL: [TryEnum; 2] = [TryEnum::Option, TryEnum::Result]; | ||
234 | |||
235 | pub fn from_ty(sema: &Semantics<RootDatabase>, ty: &Type) -> Option<TryEnum> { | ||
236 | let enum_ = match ty.as_adt() { | ||
237 | Some(Adt::Enum(it)) => it, | ||
238 | _ => return None, | ||
239 | }; | ||
240 | TryEnum::ALL.iter().find_map(|&var| { | ||
241 | if &enum_.name(sema.db).to_string() == var.type_name() { | ||
242 | return Some(var); | ||
243 | } | ||
244 | None | ||
245 | }) | ||
246 | } | ||
247 | |||
248 | pub(crate) fn happy_case(self) -> &'static str { | ||
249 | match self { | ||
250 | TryEnum::Result => "Ok", | ||
251 | TryEnum::Option => "Some", | ||
252 | } | ||
253 | } | ||
254 | |||
255 | pub(crate) fn sad_pattern(self) -> ast::Pat { | ||
256 | match self { | ||
257 | TryEnum::Result => make::tuple_struct_pat( | ||
258 | make::path_unqualified(make::path_segment(make::name_ref("Err"))), | ||
259 | iter::once(make::wildcard_pat().into()), | ||
260 | ) | ||
261 | .into(), | ||
262 | TryEnum::Option => make::ident_pat(make::name("None")).into(), | ||
263 | } | ||
264 | } | ||
265 | |||
266 | fn type_name(self) -> &'static str { | ||
267 | match self { | ||
268 | TryEnum::Result => "Result", | ||
269 | TryEnum::Option => "Option", | ||
270 | } | ||
271 | } | ||
272 | } | ||
273 | |||
274 | /// Helps with finding well-know things inside the standard library. This is | 159 | /// Helps with finding well-know things inside the standard library. This is |
275 | /// somewhat similar to the known paths infra inside hir, but it different; We | 160 | /// 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 | 161 | /// want to make sure that IDE specific paths don't become interesting inside |
diff --git a/crates/assists/src/utils/insert_use.rs b/crates/assists/src/utils/insert_use.rs index 409985b3b..033fbcedc 100644 --- a/crates/assists/src/utils/insert_use.rs +++ b/crates/assists/src/utils/insert_use.rs | |||
@@ -14,6 +14,7 @@ use syntax::{ | |||
14 | }, | 14 | }, |
15 | InsertPosition, SyntaxElement, SyntaxNode, | 15 | InsertPosition, SyntaxElement, SyntaxNode, |
16 | }; | 16 | }; |
17 | use test_utils::mark; | ||
17 | 18 | ||
18 | #[derive(Debug)] | 19 | #[derive(Debug)] |
19 | pub enum ImportScope { | 20 | pub enum ImportScope { |
@@ -109,6 +110,12 @@ pub(crate) fn insert_use( | |||
109 | // so look for the place we have to insert to | 110 | // so look for the place we have to insert to |
110 | let (insert_position, add_blank) = find_insert_position(scope, path); | 111 | let (insert_position, add_blank) = find_insert_position(scope, path); |
111 | 112 | ||
113 | let indent = if let ident_level @ 1..=usize::MAX = scope.indent_level().0 as usize { | ||
114 | Some(make::tokens::whitespace(&" ".repeat(4 * ident_level)).into()) | ||
115 | } else { | ||
116 | None | ||
117 | }; | ||
118 | |||
112 | let to_insert: Vec<SyntaxElement> = { | 119 | let to_insert: Vec<SyntaxElement> = { |
113 | let mut buf = Vec::new(); | 120 | let mut buf = Vec::new(); |
114 | 121 | ||
@@ -120,9 +127,13 @@ pub(crate) fn insert_use( | |||
120 | _ => (), | 127 | _ => (), |
121 | } | 128 | } |
122 | 129 | ||
123 | if let ident_level @ 1..=usize::MAX = scope.indent_level().0 as usize { | 130 | if add_blank.has_before() { |
124 | buf.push(make::tokens::whitespace(&" ".repeat(4 * ident_level)).into()); | 131 | if let Some(indent) = indent.clone() { |
132 | mark::hit!(insert_use_indent_before); | ||
133 | buf.push(indent); | ||
134 | } | ||
125 | } | 135 | } |
136 | |||
126 | buf.push(use_item.syntax().clone().into()); | 137 | buf.push(use_item.syntax().clone().into()); |
127 | 138 | ||
128 | match add_blank { | 139 | match add_blank { |
@@ -133,6 +144,16 @@ pub(crate) fn insert_use( | |||
133 | _ => (), | 144 | _ => (), |
134 | } | 145 | } |
135 | 146 | ||
147 | // only add indentation *after* our stuff if there's another node directly after it | ||
148 | if add_blank.has_after() && matches!(insert_position, InsertPosition::Before(_)) { | ||
149 | if let Some(indent) = indent { | ||
150 | mark::hit!(insert_use_indent_after); | ||
151 | buf.push(indent); | ||
152 | } | ||
153 | } else if add_blank.has_after() && matches!(insert_position, InsertPosition::After(_)) { | ||
154 | mark::hit!(insert_use_no_indent_after); | ||
155 | } | ||
156 | |||
136 | buf | 157 | buf |
137 | }; | 158 | }; |
138 | 159 | ||
@@ -470,6 +491,15 @@ enum AddBlankLine { | |||
470 | AfterTwice, | 491 | AfterTwice, |
471 | } | 492 | } |
472 | 493 | ||
494 | impl AddBlankLine { | ||
495 | fn has_before(&self) -> bool { | ||
496 | matches!(self, AddBlankLine::Before | AddBlankLine::BeforeTwice | AddBlankLine::Around) | ||
497 | } | ||
498 | fn has_after(&self) -> bool { | ||
499 | matches!(self, AddBlankLine::After | AddBlankLine::AfterTwice | AddBlankLine::Around) | ||
500 | } | ||
501 | } | ||
502 | |||
473 | fn find_insert_position( | 503 | fn find_insert_position( |
474 | scope: &ImportScope, | 504 | scope: &ImportScope, |
475 | insert_path: ast::Path, | 505 | insert_path: ast::Path, |
@@ -562,6 +592,21 @@ use std::bar::G;", | |||
562 | } | 592 | } |
563 | 593 | ||
564 | #[test] | 594 | #[test] |
595 | fn insert_start_indent() { | ||
596 | mark::check!(insert_use_indent_after); | ||
597 | check_none( | ||
598 | "std::bar::AA", | ||
599 | r" | ||
600 | use std::bar::B; | ||
601 | use std::bar::D;", | ||
602 | r" | ||
603 | use std::bar::AA; | ||
604 | use std::bar::B; | ||
605 | use std::bar::D;", | ||
606 | ) | ||
607 | } | ||
608 | |||
609 | #[test] | ||
565 | fn insert_middle() { | 610 | fn insert_middle() { |
566 | check_none( | 611 | check_none( |
567 | "std::bar::EE", | 612 | "std::bar::EE", |
@@ -580,6 +625,24 @@ use std::bar::G;", | |||
580 | } | 625 | } |
581 | 626 | ||
582 | #[test] | 627 | #[test] |
628 | fn insert_middle_indent() { | ||
629 | check_none( | ||
630 | "std::bar::EE", | ||
631 | r" | ||
632 | use std::bar::A; | ||
633 | use std::bar::D; | ||
634 | use std::bar::F; | ||
635 | use std::bar::G;", | ||
636 | r" | ||
637 | use std::bar::A; | ||
638 | use std::bar::D; | ||
639 | use std::bar::EE; | ||
640 | use std::bar::F; | ||
641 | use std::bar::G;", | ||
642 | ) | ||
643 | } | ||
644 | |||
645 | #[test] | ||
583 | fn insert_end() { | 646 | fn insert_end() { |
584 | check_none( | 647 | check_none( |
585 | "std::bar::ZZ", | 648 | "std::bar::ZZ", |
@@ -598,6 +661,25 @@ use std::bar::ZZ;", | |||
598 | } | 661 | } |
599 | 662 | ||
600 | #[test] | 663 | #[test] |
664 | fn insert_end_indent() { | ||
665 | mark::check!(insert_use_indent_before); | ||
666 | check_none( | ||
667 | "std::bar::ZZ", | ||
668 | r" | ||
669 | use std::bar::A; | ||
670 | use std::bar::D; | ||
671 | use std::bar::F; | ||
672 | use std::bar::G;", | ||
673 | r" | ||
674 | use std::bar::A; | ||
675 | use std::bar::D; | ||
676 | use std::bar::F; | ||
677 | use std::bar::G; | ||
678 | use std::bar::ZZ;", | ||
679 | ) | ||
680 | } | ||
681 | |||
682 | #[test] | ||
601 | fn insert_middle_nested() { | 683 | fn insert_middle_nested() { |
602 | check_none( | 684 | check_none( |
603 | "std::bar::EE", | 685 | "std::bar::EE", |
@@ -620,18 +702,18 @@ use std::bar::G;", | |||
620 | check_none( | 702 | check_none( |
621 | "foo::bar::GG", | 703 | "foo::bar::GG", |
622 | r" | 704 | r" |
623 | use std::bar::A; | 705 | use std::bar::A; |
624 | use std::bar::D; | 706 | use std::bar::D; |
625 | 707 | ||
626 | use foo::bar::F; | 708 | use foo::bar::F; |
627 | use foo::bar::H;", | 709 | use foo::bar::H;", |
628 | r" | 710 | r" |
629 | use std::bar::A; | 711 | use std::bar::A; |
630 | use std::bar::D; | 712 | use std::bar::D; |
631 | 713 | ||
632 | use foo::bar::F; | 714 | use foo::bar::F; |
633 | use foo::bar::GG; | 715 | use foo::bar::GG; |
634 | use foo::bar::H;", | 716 | use foo::bar::H;", |
635 | ) | 717 | ) |
636 | } | 718 | } |
637 | 719 | ||
@@ -640,22 +722,22 @@ use foo::bar::H;", | |||
640 | check_none( | 722 | check_none( |
641 | "foo::bar::GG", | 723 | "foo::bar::GG", |
642 | r" | 724 | r" |
643 | use foo::bar::A; | 725 | use foo::bar::A; |
644 | use foo::bar::D; | 726 | use foo::bar::D; |
645 | 727 | ||
646 | use std; | 728 | use std; |
647 | 729 | ||
648 | use foo::bar::F; | 730 | use foo::bar::F; |
649 | use foo::bar::H;", | 731 | use foo::bar::H;", |
650 | r" | 732 | r" |
651 | use foo::bar::A; | 733 | use foo::bar::A; |
652 | use foo::bar::D; | 734 | use foo::bar::D; |
653 | use foo::bar::GG; | 735 | use foo::bar::GG; |
654 | 736 | ||
655 | use std; | 737 | use std; |
656 | 738 | ||
657 | use foo::bar::F; | 739 | use foo::bar::F; |
658 | use foo::bar::H;", | 740 | use foo::bar::H;", |
659 | ) | 741 | ) |
660 | } | 742 | } |
661 | 743 | ||
@@ -664,13 +746,13 @@ use foo::bar::H;", | |||
664 | check_none( | 746 | check_none( |
665 | "std::fmt", | 747 | "std::fmt", |
666 | r" | 748 | r" |
667 | use foo::bar::A; | 749 | use foo::bar::A; |
668 | use foo::bar::D;", | 750 | use foo::bar::D;", |
669 | r" | 751 | r" |
670 | use std::fmt; | 752 | use std::fmt; |
671 | 753 | ||
672 | use foo::bar::A; | 754 | use foo::bar::A; |
673 | use foo::bar::D;", | 755 | use foo::bar::D;", |
674 | ) | 756 | ) |
675 | } | 757 | } |
676 | 758 | ||
@@ -714,6 +796,20 @@ fn main() {}", | |||
714 | } | 796 | } |
715 | 797 | ||
716 | #[test] | 798 | #[test] |
799 | fn insert_empty_module() { | ||
800 | mark::check!(insert_use_no_indent_after); | ||
801 | check( | ||
802 | "foo::bar", | ||
803 | "mod x {}", | ||
804 | r"{ | ||
805 | use foo::bar; | ||
806 | }", | ||
807 | None, | ||
808 | true, | ||
809 | ) | ||
810 | } | ||
811 | |||
812 | #[test] | ||
717 | fn insert_after_inner_attr() { | 813 | fn insert_after_inner_attr() { |
718 | check_full( | 814 | check_full( |
719 | "foo::bar", | 815 | "foo::bar", |
@@ -991,11 +1087,13 @@ use foo::bar::baz::Qux;", | |||
991 | ra_fixture_before: &str, | 1087 | ra_fixture_before: &str, |
992 | ra_fixture_after: &str, | 1088 | ra_fixture_after: &str, |
993 | mb: Option<MergeBehaviour>, | 1089 | mb: Option<MergeBehaviour>, |
1090 | module: bool, | ||
994 | ) { | 1091 | ) { |
995 | let file = super::ImportScope::from( | 1092 | let mut syntax = ast::SourceFile::parse(ra_fixture_before).tree().syntax().clone(); |
996 | ast::SourceFile::parse(ra_fixture_before).tree().syntax().clone(), | 1093 | if module { |
997 | ) | 1094 | syntax = syntax.descendants().find_map(ast::Module::cast).unwrap().syntax().clone(); |
998 | .unwrap(); | 1095 | } |
1096 | let file = super::ImportScope::from(syntax).unwrap(); | ||
999 | let path = ast::SourceFile::parse(&format!("use {};", path)) | 1097 | let path = ast::SourceFile::parse(&format!("use {};", path)) |
1000 | .tree() | 1098 | .tree() |
1001 | .syntax() | 1099 | .syntax() |
@@ -1008,15 +1106,15 @@ use foo::bar::baz::Qux;", | |||
1008 | } | 1106 | } |
1009 | 1107 | ||
1010 | fn check_full(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) { | 1108 | fn check_full(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) { |
1011 | check(path, ra_fixture_before, ra_fixture_after, Some(MergeBehaviour::Full)) | 1109 | check(path, ra_fixture_before, ra_fixture_after, Some(MergeBehaviour::Full), false) |
1012 | } | 1110 | } |
1013 | 1111 | ||
1014 | fn check_last(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) { | 1112 | fn check_last(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) { |
1015 | check(path, ra_fixture_before, ra_fixture_after, Some(MergeBehaviour::Last)) | 1113 | check(path, ra_fixture_before, ra_fixture_after, Some(MergeBehaviour::Last), false) |
1016 | } | 1114 | } |
1017 | 1115 | ||
1018 | fn check_none(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) { | 1116 | fn check_none(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) { |
1019 | check(path, ra_fixture_before, ra_fixture_after, None) | 1117 | check(path, ra_fixture_before, ra_fixture_after, None, false) |
1020 | } | 1118 | } |
1021 | 1119 | ||
1022 | fn check_merge_only_fail(ra_fixture0: &str, ra_fixture1: &str, mb: MergeBehaviour) { | 1120 | fn check_merge_only_fail(ra_fixture0: &str, ra_fixture1: &str, mb: MergeBehaviour) { |
diff --git a/crates/call_info/Cargo.toml b/crates/call_info/Cargo.toml deleted file mode 100644 index 98c0bd6db..000000000 --- a/crates/call_info/Cargo.toml +++ /dev/null | |||
@@ -1,26 +0,0 @@ | |||
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/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 index 25192456a..b79ee33f7 100644 --- a/crates/completion/Cargo.toml +++ b/crates/completion/Cargo.toml | |||
@@ -21,8 +21,6 @@ base_db = { path = "../base_db", version = "0.0.0" } | |||
21 | ide_db = { path = "../ide_db", version = "0.0.0" } | 21 | ide_db = { path = "../ide_db", version = "0.0.0" } |
22 | profile = { path = "../profile", version = "0.0.0" } | 22 | profile = { path = "../profile", version = "0.0.0" } |
23 | test_utils = { path = "../test_utils", 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 | 24 | ||
27 | # completions crate should depend only on the top-level `hir` package. if you need | 25 | # 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`. | 26 | # something from some `hir_xxx` subpackage, reexport the API via `hir`. |
diff --git a/crates/completion/src/complete_mod.rs b/crates/completion/src/complete_mod.rs index 35a57aba3..385911afa 100644 --- a/crates/completion/src/complete_mod.rs +++ b/crates/completion/src/complete_mod.rs | |||
@@ -1,7 +1,7 @@ | |||
1 | //! Completes mod declarations. | 1 | //! Completes mod declarations. |
2 | 2 | ||
3 | use base_db::{SourceDatabaseExt, VfsPath}; | ||
4 | use hir::{Module, ModuleSource}; | 3 | use hir::{Module, ModuleSource}; |
4 | use ide_db::base_db::{SourceDatabaseExt, VfsPath}; | ||
5 | use ide_db::RootDatabase; | 5 | use ide_db::RootDatabase; |
6 | use rustc_hash::FxHashSet; | 6 | use rustc_hash::FxHashSet; |
7 | 7 | ||
diff --git a/crates/completion/src/complete_postfix.rs b/crates/completion/src/complete_postfix.rs index 700573cf2..2622f12ab 100644 --- a/crates/completion/src/complete_postfix.rs +++ b/crates/completion/src/complete_postfix.rs | |||
@@ -2,7 +2,7 @@ | |||
2 | 2 | ||
3 | mod format_like; | 3 | mod format_like; |
4 | 4 | ||
5 | use assists::utils::TryEnum; | 5 | use ide_db::ty_filter::TryEnum; |
6 | use syntax::{ | 6 | use syntax::{ |
7 | ast::{self, AstNode, AstToken}, | 7 | ast::{self, AstNode, AstToken}, |
8 | TextRange, TextSize, | 8 | TextRange, TextSize, |
diff --git a/crates/completion/src/complete_trait_impl.rs b/crates/completion/src/complete_trait_impl.rs index c06af99e2..a14be9c73 100644 --- a/crates/completion/src/complete_trait_impl.rs +++ b/crates/completion/src/complete_trait_impl.rs | |||
@@ -31,8 +31,8 @@ | |||
31 | //! } | 31 | //! } |
32 | //! ``` | 32 | //! ``` |
33 | 33 | ||
34 | use assists::utils::get_missing_assoc_items; | ||
35 | use hir::{self, HasAttrs, HasSource}; | 34 | use hir::{self, HasAttrs, HasSource}; |
35 | use ide_db::traits::get_missing_assoc_items; | ||
36 | use syntax::{ | 36 | use syntax::{ |
37 | ast::{self, edit, Impl}, | 37 | ast::{self, edit, Impl}, |
38 | display::function_declaration, | 38 | display::function_declaration, |
diff --git a/crates/completion/src/completion_context.rs b/crates/completion/src/completion_context.rs index e4f86d0e0..dca304a8f 100644 --- a/crates/completion/src/completion_context.rs +++ b/crates/completion/src/completion_context.rs | |||
@@ -1,9 +1,8 @@ | |||
1 | //! See `CompletionContext` structure. | 1 | //! See `CompletionContext` structure. |
2 | 2 | ||
3 | use base_db::{FilePosition, SourceDatabase}; | ||
4 | use call_info::ActiveParameter; | ||
5 | use hir::{Local, ScopeDef, Semantics, SemanticsScope, Type}; | 3 | use hir::{Local, ScopeDef, Semantics, SemanticsScope, Type}; |
6 | use ide_db::RootDatabase; | 4 | use ide_db::base_db::{FilePosition, SourceDatabase}; |
5 | use ide_db::{call_info::ActiveParameter, RootDatabase}; | ||
7 | use syntax::{ | 6 | use syntax::{ |
8 | algo::{find_covering_element, find_node_at_offset}, | 7 | algo::{find_covering_element, find_node_at_offset}, |
9 | ast, match_ast, AstNode, NodeOrToken, | 8 | ast, match_ast, AstNode, NodeOrToken, |
diff --git a/crates/completion/src/lib.rs b/crates/completion/src/lib.rs index 0a60ea7f2..b72fd249d 100644 --- a/crates/completion/src/lib.rs +++ b/crates/completion/src/lib.rs | |||
@@ -23,7 +23,7 @@ mod complete_macro_in_item_position; | |||
23 | mod complete_trait_impl; | 23 | mod complete_trait_impl; |
24 | mod complete_mod; | 24 | mod complete_mod; |
25 | 25 | ||
26 | use base_db::FilePosition; | 26 | use ide_db::base_db::FilePosition; |
27 | use ide_db::RootDatabase; | 27 | use ide_db::RootDatabase; |
28 | 28 | ||
29 | use crate::{ | 29 | use crate::{ |
diff --git a/crates/completion/src/presentation.rs b/crates/completion/src/presentation.rs index 2a19281cf..0a6f5a1ea 100644 --- a/crates/completion/src/presentation.rs +++ b/crates/completion/src/presentation.rs | |||
@@ -304,9 +304,14 @@ impl Completions { | |||
304 | ) { | 304 | ) { |
305 | let is_deprecated = is_deprecated(variant, ctx.db); | 305 | let is_deprecated = is_deprecated(variant, ctx.db); |
306 | let name = local_name.unwrap_or_else(|| variant.name(ctx.db).to_string()); | 306 | let name = local_name.unwrap_or_else(|| variant.name(ctx.db).to_string()); |
307 | let qualified_name = match &path { | 307 | let (qualified_name, short_qualified_name) = match &path { |
308 | Some(it) => it.to_string(), | 308 | Some(path) => { |
309 | None => name.to_string(), | 309 | let full = path.to_string(); |
310 | let short = | ||
311 | path.segments[path.segments.len().saturating_sub(2)..].iter().join("::"); | ||
312 | (full, short) | ||
313 | } | ||
314 | None => (name.to_string(), name.to_string()), | ||
310 | }; | 315 | }; |
311 | let detail_types = variant | 316 | let detail_types = variant |
312 | .fields(ctx.db) | 317 | .fields(ctx.db) |
@@ -335,14 +340,12 @@ impl Completions { | |||
335 | .set_deprecated(is_deprecated) | 340 | .set_deprecated(is_deprecated) |
336 | .detail(detail); | 341 | .detail(detail); |
337 | 342 | ||
338 | if path.is_some() { | ||
339 | res = res.lookup_by(name); | ||
340 | } | ||
341 | |||
342 | if variant_kind == StructKind::Tuple { | 343 | if variant_kind == StructKind::Tuple { |
343 | mark::hit!(inserts_parens_for_tuple_enums); | 344 | mark::hit!(inserts_parens_for_tuple_enums); |
344 | let params = Params::Anonymous(variant.fields(ctx.db).len()); | 345 | let params = Params::Anonymous(variant.fields(ctx.db).len()); |
345 | res = res.add_call_parens(ctx, qualified_name, params) | 346 | res = res.add_call_parens(ctx, short_qualified_name, params) |
347 | } else if path.is_some() { | ||
348 | res = res.lookup_by(short_qualified_name); | ||
346 | } | 349 | } |
347 | 350 | ||
348 | res.add_to(self); | 351 | res.add_to(self); |
@@ -607,6 +610,57 @@ fn main() { Foo::Fo<|> } | |||
607 | } | 610 | } |
608 | 611 | ||
609 | #[test] | 612 | #[test] |
613 | fn lookup_enums_by_two_qualifiers() { | ||
614 | check( | ||
615 | r#" | ||
616 | mod m { | ||
617 | pub enum Spam { Foo, Bar(i32) } | ||
618 | } | ||
619 | fn main() { let _: m::Spam = S<|> } | ||
620 | "#, | ||
621 | expect![[r#" | ||
622 | [ | ||
623 | CompletionItem { | ||
624 | label: "Spam::Bar(…)", | ||
625 | source_range: 75..76, | ||
626 | delete: 75..76, | ||
627 | insert: "Spam::Bar($0)", | ||
628 | kind: EnumVariant, | ||
629 | lookup: "Spam::Bar", | ||
630 | detail: "(i32)", | ||
631 | trigger_call_info: true, | ||
632 | }, | ||
633 | CompletionItem { | ||
634 | label: "m", | ||
635 | source_range: 75..76, | ||
636 | delete: 75..76, | ||
637 | insert: "m", | ||
638 | kind: Module, | ||
639 | }, | ||
640 | CompletionItem { | ||
641 | label: "m::Spam::Foo", | ||
642 | source_range: 75..76, | ||
643 | delete: 75..76, | ||
644 | insert: "m::Spam::Foo", | ||
645 | kind: EnumVariant, | ||
646 | lookup: "Spam::Foo", | ||
647 | detail: "()", | ||
648 | }, | ||
649 | CompletionItem { | ||
650 | label: "main()", | ||
651 | source_range: 75..76, | ||
652 | delete: 75..76, | ||
653 | insert: "main()$0", | ||
654 | kind: Function, | ||
655 | lookup: "main", | ||
656 | detail: "fn main()", | ||
657 | }, | ||
658 | ] | ||
659 | "#]], | ||
660 | ) | ||
661 | } | ||
662 | |||
663 | #[test] | ||
610 | fn sets_deprecated_flag_in_completion_items() { | 664 | fn sets_deprecated_flag_in_completion_items() { |
611 | check( | 665 | check( |
612 | r#" | 666 | r#" |
diff --git a/crates/completion/src/test_utils.rs b/crates/completion/src/test_utils.rs index f2cf2561f..b02556797 100644 --- a/crates/completion/src/test_utils.rs +++ b/crates/completion/src/test_utils.rs | |||
@@ -1,7 +1,7 @@ | |||
1 | //! Runs completion for testing purposes. | 1 | //! Runs completion for testing purposes. |
2 | 2 | ||
3 | use base_db::{fixture::ChangeFixture, FileLoader, FilePosition}; | ||
4 | use hir::Semantics; | 3 | use hir::Semantics; |
4 | use ide_db::base_db::{fixture::ChangeFixture, FileLoader, FilePosition}; | ||
5 | use ide_db::RootDatabase; | 5 | use ide_db::RootDatabase; |
6 | use itertools::Itertools; | 6 | use itertools::Itertools; |
7 | use stdx::{format_to, trim_indent}; | 7 | use stdx::{format_to, trim_indent}; |
diff --git a/crates/hir/src/code_model.rs b/crates/hir/src/code_model.rs index 7f169ccd2..63c1a8ebf 100644 --- a/crates/hir/src/code_model.rs +++ b/crates/hir/src/code_model.rs | |||
@@ -31,8 +31,7 @@ use hir_ty::{ | |||
31 | autoderef, | 31 | autoderef, |
32 | display::{HirDisplayError, HirFormatter}, | 32 | display::{HirDisplayError, HirFormatter}, |
33 | method_resolution, | 33 | method_resolution, |
34 | traits::Solution, | 34 | traits::{FnTrait, Solution, SolutionVariables}, |
35 | traits::SolutionVariables, | ||
36 | ApplicationTy, BoundVar, CallableDefId, Canonical, DebruijnIndex, FnSig, GenericPredicate, | 35 | ApplicationTy, BoundVar, CallableDefId, Canonical, DebruijnIndex, FnSig, GenericPredicate, |
37 | InEnvironment, Obligation, ProjectionPredicate, ProjectionTy, Substs, TraitEnvironment, Ty, | 36 | InEnvironment, Obligation, ProjectionPredicate, ProjectionTy, Substs, TraitEnvironment, Ty, |
38 | TyDefId, TyKind, TypeCtor, | 37 | TyDefId, TyKind, TypeCtor, |
@@ -781,6 +780,7 @@ impl Function { | |||
781 | } | 780 | } |
782 | 781 | ||
783 | pub fn diagnostics(self, db: &dyn HirDatabase, sink: &mut DiagnosticSink) { | 782 | pub fn diagnostics(self, db: &dyn HirDatabase, sink: &mut DiagnosticSink) { |
783 | hir_def::diagnostics::validate_body(db.upcast(), self.id.into(), sink); | ||
784 | hir_ty::diagnostics::validate_module_item(db, self.id.into(), sink); | 784 | hir_ty::diagnostics::validate_module_item(db, self.id.into(), sink); |
785 | hir_ty::diagnostics::validate_body(db, self.id.into(), sink); | 785 | hir_ty::diagnostics::validate_body(db, self.id.into(), sink); |
786 | } | 786 | } |
@@ -1385,6 +1385,28 @@ impl Type { | |||
1385 | ) | 1385 | ) |
1386 | } | 1386 | } |
1387 | 1387 | ||
1388 | /// Checks that particular type `ty` implements `std::ops::FnOnce`. | ||
1389 | /// | ||
1390 | /// This function can be used to check if a particular type is callable, since FnOnce is a | ||
1391 | /// supertrait of Fn and FnMut, so all callable types implements at least FnOnce. | ||
1392 | pub fn impls_fnonce(&self, db: &dyn HirDatabase) -> bool { | ||
1393 | let krate = self.krate; | ||
1394 | |||
1395 | let fnonce_trait = match FnTrait::FnOnce.get_id(db, krate) { | ||
1396 | Some(it) => it, | ||
1397 | None => return false, | ||
1398 | }; | ||
1399 | |||
1400 | let canonical_ty = Canonical { value: self.ty.value.clone(), kinds: Arc::new([]) }; | ||
1401 | method_resolution::implements_trait( | ||
1402 | &canonical_ty, | ||
1403 | db, | ||
1404 | self.ty.environment.clone(), | ||
1405 | krate, | ||
1406 | fnonce_trait, | ||
1407 | ) | ||
1408 | } | ||
1409 | |||
1388 | pub fn impls_trait(&self, db: &dyn HirDatabase, trait_: Trait, args: &[Type]) -> bool { | 1410 | pub fn impls_trait(&self, db: &dyn HirDatabase, trait_: Trait, args: &[Type]) -> bool { |
1389 | let trait_ref = hir_ty::TraitRef { | 1411 | let trait_ref = hir_ty::TraitRef { |
1390 | trait_: trait_.id, | 1412 | trait_: trait_.id, |
diff --git a/crates/hir_def/src/attr.rs b/crates/hir_def/src/attr.rs index dea552a60..b2ce7ca3c 100644 --- a/crates/hir_def/src/attr.rs +++ b/crates/hir_def/src/attr.rs | |||
@@ -125,12 +125,20 @@ impl Attrs { | |||
125 | AttrQuery { attrs: self, key } | 125 | AttrQuery { attrs: self, key } |
126 | } | 126 | } |
127 | 127 | ||
128 | pub fn cfg(&self) -> impl Iterator<Item = CfgExpr> + '_ { | 128 | pub fn cfg(&self) -> Option<CfgExpr> { |
129 | // FIXME: handle cfg_attr :-) | 129 | // FIXME: handle cfg_attr :-) |
130 | self.by_key("cfg").tt_values().map(CfgExpr::parse) | 130 | let mut cfgs = self.by_key("cfg").tt_values().map(CfgExpr::parse).collect::<Vec<_>>(); |
131 | match cfgs.len() { | ||
132 | 0 => None, | ||
133 | 1 => Some(cfgs.pop().unwrap()), | ||
134 | _ => Some(CfgExpr::All(cfgs)), | ||
135 | } | ||
131 | } | 136 | } |
132 | pub(crate) fn is_cfg_enabled(&self, cfg_options: &CfgOptions) -> bool { | 137 | pub(crate) fn is_cfg_enabled(&self, cfg_options: &CfgOptions) -> bool { |
133 | self.cfg().all(|cfg| cfg_options.check(&cfg) != Some(false)) | 138 | match self.cfg() { |
139 | None => true, | ||
140 | Some(cfg) => cfg_options.check(&cfg) != Some(false), | ||
141 | } | ||
134 | } | 142 | } |
135 | } | 143 | } |
136 | 144 | ||
diff --git a/crates/hir_def/src/body.rs b/crates/hir_def/src/body.rs index d51036e4f..d10b1af01 100644 --- a/crates/hir_def/src/body.rs +++ b/crates/hir_def/src/body.rs | |||
@@ -1,6 +1,9 @@ | |||
1 | //! Defines `Body`: a lowered representation of bodies of functions, statics and | 1 | //! Defines `Body`: a lowered representation of bodies of functions, statics and |
2 | //! consts. | 2 | //! consts. |
3 | mod lower; | 3 | mod lower; |
4 | mod diagnostics; | ||
5 | #[cfg(test)] | ||
6 | mod tests; | ||
4 | pub mod scope; | 7 | pub mod scope; |
5 | 8 | ||
6 | use std::{mem, ops::Index, sync::Arc}; | 9 | use std::{mem, ops::Index, sync::Arc}; |
@@ -10,7 +13,10 @@ use base_db::CrateId; | |||
10 | use cfg::CfgOptions; | 13 | use cfg::CfgOptions; |
11 | use drop_bomb::DropBomb; | 14 | use drop_bomb::DropBomb; |
12 | use either::Either; | 15 | use either::Either; |
13 | use hir_expand::{ast_id_map::AstIdMap, hygiene::Hygiene, AstId, HirFileId, InFile, MacroDefId}; | 16 | use hir_expand::{ |
17 | ast_id_map::AstIdMap, diagnostics::DiagnosticSink, hygiene::Hygiene, AstId, HirFileId, InFile, | ||
18 | MacroDefId, | ||
19 | }; | ||
14 | use rustc_hash::FxHashMap; | 20 | use rustc_hash::FxHashMap; |
15 | use syntax::{ast, AstNode, AstPtr}; | 21 | use syntax::{ast, AstNode, AstPtr}; |
16 | use test_utils::mark; | 22 | use test_utils::mark; |
@@ -150,8 +156,12 @@ impl Expander { | |||
150 | InFile { file_id: self.current_file_id, value } | 156 | InFile { file_id: self.current_file_id, value } |
151 | } | 157 | } |
152 | 158 | ||
153 | pub(crate) fn is_cfg_enabled(&self, owner: &dyn ast::AttrsOwner) -> bool { | 159 | pub(crate) fn parse_attrs(&self, owner: &dyn ast::AttrsOwner) -> Attrs { |
154 | self.cfg_expander.is_cfg_enabled(owner) | 160 | self.cfg_expander.parse_attrs(owner) |
161 | } | ||
162 | |||
163 | pub(crate) fn cfg_options(&self) -> &CfgOptions { | ||
164 | &self.cfg_expander.cfg_options | ||
155 | } | 165 | } |
156 | 166 | ||
157 | fn parse_path(&mut self, path: ast::Path) -> Option<Path> { | 167 | fn parse_path(&mut self, path: ast::Path) -> Option<Path> { |
@@ -219,6 +229,10 @@ pub struct BodySourceMap { | |||
219 | pat_map_back: ArenaMap<PatId, Result<PatSource, SyntheticSyntax>>, | 229 | pat_map_back: ArenaMap<PatId, Result<PatSource, SyntheticSyntax>>, |
220 | field_map: FxHashMap<(ExprId, usize), InFile<AstPtr<ast::RecordExprField>>>, | 230 | field_map: FxHashMap<(ExprId, usize), InFile<AstPtr<ast::RecordExprField>>>, |
221 | expansions: FxHashMap<InFile<AstPtr<ast::MacroCall>>, HirFileId>, | 231 | expansions: FxHashMap<InFile<AstPtr<ast::MacroCall>>, HirFileId>, |
232 | |||
233 | /// Diagnostics accumulated during body lowering. These contain `AstPtr`s and so are stored in | ||
234 | /// the source map (since they're just as volatile). | ||
235 | diagnostics: Vec<diagnostics::BodyDiagnostic>, | ||
222 | } | 236 | } |
223 | 237 | ||
224 | #[derive(Default, Debug, Eq, PartialEq, Clone, Copy)] | 238 | #[derive(Default, Debug, Eq, PartialEq, Clone, Copy)] |
@@ -318,45 +332,10 @@ impl BodySourceMap { | |||
318 | pub fn field_syntax(&self, expr: ExprId, field: usize) -> InFile<AstPtr<ast::RecordExprField>> { | 332 | pub fn field_syntax(&self, expr: ExprId, field: usize) -> InFile<AstPtr<ast::RecordExprField>> { |
319 | self.field_map[&(expr, field)].clone() | 333 | self.field_map[&(expr, field)].clone() |
320 | } | 334 | } |
321 | } | ||
322 | |||
323 | #[cfg(test)] | ||
324 | mod tests { | ||
325 | use base_db::{fixture::WithFixture, SourceDatabase}; | ||
326 | use test_utils::mark; | ||
327 | 335 | ||
328 | use crate::ModuleDefId; | 336 | pub(crate) fn add_diagnostics(&self, _db: &dyn DefDatabase, sink: &mut DiagnosticSink<'_>) { |
329 | 337 | for diag in &self.diagnostics { | |
330 | use super::*; | 338 | diag.add_to(sink); |
331 | 339 | } | |
332 | fn lower(ra_fixture: &str) -> Arc<Body> { | ||
333 | let (db, file_id) = crate::test_db::TestDB::with_single_file(ra_fixture); | ||
334 | |||
335 | let krate = db.crate_graph().iter().next().unwrap(); | ||
336 | let def_map = db.crate_def_map(krate); | ||
337 | let module = def_map.modules_for_file(file_id).next().unwrap(); | ||
338 | let module = &def_map[module]; | ||
339 | let fn_def = match module.scope.declarations().next().unwrap() { | ||
340 | ModuleDefId::FunctionId(it) => it, | ||
341 | _ => panic!(), | ||
342 | }; | ||
343 | |||
344 | db.body(fn_def.into()) | ||
345 | } | ||
346 | |||
347 | #[test] | ||
348 | fn your_stack_belongs_to_me() { | ||
349 | mark::check!(your_stack_belongs_to_me); | ||
350 | lower( | ||
351 | " | ||
352 | macro_rules! n_nuple { | ||
353 | ($e:tt) => (); | ||
354 | ($($rest:tt)*) => {{ | ||
355 | (n_nuple!($($rest)*)None,) | ||
356 | }}; | ||
357 | } | ||
358 | fn main() { n_nuple!(1,2,3); } | ||
359 | ", | ||
360 | ); | ||
361 | } | 340 | } |
362 | } | 341 | } |
diff --git a/crates/hir_def/src/body/diagnostics.rs b/crates/hir_def/src/body/diagnostics.rs new file mode 100644 index 000000000..cfa47d189 --- /dev/null +++ b/crates/hir_def/src/body/diagnostics.rs | |||
@@ -0,0 +1,20 @@ | |||
1 | //! Diagnostics emitted during body lowering. | ||
2 | |||
3 | use hir_expand::diagnostics::DiagnosticSink; | ||
4 | |||
5 | use crate::diagnostics::InactiveCode; | ||
6 | |||
7 | #[derive(Debug, Eq, PartialEq)] | ||
8 | pub enum BodyDiagnostic { | ||
9 | InactiveCode(InactiveCode), | ||
10 | } | ||
11 | |||
12 | impl BodyDiagnostic { | ||
13 | pub fn add_to(&self, sink: &mut DiagnosticSink<'_>) { | ||
14 | match self { | ||
15 | BodyDiagnostic::InactiveCode(diag) => { | ||
16 | sink.push(diag.clone()); | ||
17 | } | ||
18 | } | ||
19 | } | ||
20 | } | ||
diff --git a/crates/hir_def/src/body/lower.rs b/crates/hir_def/src/body/lower.rs index 01e72690a..ddc267b83 100644 --- a/crates/hir_def/src/body/lower.rs +++ b/crates/hir_def/src/body/lower.rs | |||
@@ -16,7 +16,7 @@ use syntax::{ | |||
16 | self, ArgListOwner, ArrayExprKind, AstChildren, LiteralKind, LoopBodyOwner, NameOwner, | 16 | self, ArgListOwner, ArrayExprKind, AstChildren, LiteralKind, LoopBodyOwner, NameOwner, |
17 | SlicePatComponents, | 17 | SlicePatComponents, |
18 | }, | 18 | }, |
19 | AstNode, AstPtr, | 19 | AstNode, AstPtr, SyntaxNodePtr, |
20 | }; | 20 | }; |
21 | use test_utils::mark; | 21 | use test_utils::mark; |
22 | 22 | ||
@@ -25,6 +25,7 @@ use crate::{ | |||
25 | body::{Body, BodySourceMap, Expander, PatPtr, SyntheticSyntax}, | 25 | body::{Body, BodySourceMap, Expander, PatPtr, SyntheticSyntax}, |
26 | builtin_type::{BuiltinFloat, BuiltinInt}, | 26 | builtin_type::{BuiltinFloat, BuiltinInt}, |
27 | db::DefDatabase, | 27 | db::DefDatabase, |
28 | diagnostics::InactiveCode, | ||
28 | expr::{ | 29 | expr::{ |
29 | dummy_expr_id, ArithOp, Array, BinaryOp, BindingAnnotation, CmpOp, Expr, ExprId, Literal, | 30 | dummy_expr_id, ArithOp, Array, BinaryOp, BindingAnnotation, CmpOp, Expr, ExprId, Literal, |
30 | LogicOp, MatchArm, Ordering, Pat, PatId, RecordFieldPat, RecordLitField, Statement, | 31 | LogicOp, MatchArm, Ordering, Pat, PatId, RecordFieldPat, RecordLitField, Statement, |
@@ -37,7 +38,7 @@ use crate::{ | |||
37 | StaticLoc, StructLoc, TraitLoc, TypeAliasLoc, UnionLoc, | 38 | StaticLoc, StructLoc, TraitLoc, TypeAliasLoc, UnionLoc, |
38 | }; | 39 | }; |
39 | 40 | ||
40 | use super::{ExprSource, PatSource}; | 41 | use super::{diagnostics::BodyDiagnostic, ExprSource, PatSource}; |
41 | 42 | ||
42 | pub(crate) struct LowerCtx { | 43 | pub(crate) struct LowerCtx { |
43 | hygiene: Hygiene, | 44 | hygiene: Hygiene, |
@@ -176,7 +177,7 @@ impl ExprCollector<'_> { | |||
176 | 177 | ||
177 | fn collect_expr(&mut self, expr: ast::Expr) -> ExprId { | 178 | fn collect_expr(&mut self, expr: ast::Expr) -> ExprId { |
178 | let syntax_ptr = AstPtr::new(&expr); | 179 | let syntax_ptr = AstPtr::new(&expr); |
179 | if !self.expander.is_cfg_enabled(&expr) { | 180 | if self.check_cfg(&expr).is_none() { |
180 | return self.missing_expr(); | 181 | return self.missing_expr(); |
181 | } | 182 | } |
182 | 183 | ||
@@ -354,13 +355,15 @@ impl ExprCollector<'_> { | |||
354 | let arms = if let Some(match_arm_list) = e.match_arm_list() { | 355 | let arms = if let Some(match_arm_list) = e.match_arm_list() { |
355 | match_arm_list | 356 | match_arm_list |
356 | .arms() | 357 | .arms() |
357 | .map(|arm| MatchArm { | 358 | .filter_map(|arm| { |
358 | pat: self.collect_pat_opt(arm.pat()), | 359 | self.check_cfg(&arm).map(|()| MatchArm { |
359 | expr: self.collect_expr_opt(arm.expr()), | 360 | pat: self.collect_pat_opt(arm.pat()), |
360 | guard: arm | 361 | expr: self.collect_expr_opt(arm.expr()), |
361 | .guard() | 362 | guard: arm |
362 | .and_then(|guard| guard.expr()) | 363 | .guard() |
363 | .map(|e| self.collect_expr(e)), | 364 | .and_then(|guard| guard.expr()) |
365 | .map(|e| self.collect_expr(e)), | ||
366 | }) | ||
364 | }) | 367 | }) |
365 | .collect() | 368 | .collect() |
366 | } else { | 369 | } else { |
@@ -406,9 +409,8 @@ impl ExprCollector<'_> { | |||
406 | .fields() | 409 | .fields() |
407 | .inspect(|field| field_ptrs.push(AstPtr::new(field))) | 410 | .inspect(|field| field_ptrs.push(AstPtr::new(field))) |
408 | .filter_map(|field| { | 411 | .filter_map(|field| { |
409 | if !self.expander.is_cfg_enabled(&field) { | 412 | self.check_cfg(&field)?; |
410 | return None; | 413 | |
411 | } | ||
412 | let name = field.field_name()?.as_name(); | 414 | let name = field.field_name()?.as_name(); |
413 | 415 | ||
414 | Some(RecordLitField { | 416 | Some(RecordLitField { |
@@ -620,15 +622,23 @@ impl ExprCollector<'_> { | |||
620 | .filter_map(|s| { | 622 | .filter_map(|s| { |
621 | let stmt = match s { | 623 | let stmt = match s { |
622 | ast::Stmt::LetStmt(stmt) => { | 624 | ast::Stmt::LetStmt(stmt) => { |
625 | self.check_cfg(&stmt)?; | ||
626 | |||
623 | let pat = self.collect_pat_opt(stmt.pat()); | 627 | let pat = self.collect_pat_opt(stmt.pat()); |
624 | let type_ref = stmt.ty().map(|it| TypeRef::from_ast(&self.ctx(), it)); | 628 | let type_ref = stmt.ty().map(|it| TypeRef::from_ast(&self.ctx(), it)); |
625 | let initializer = stmt.initializer().map(|e| self.collect_expr(e)); | 629 | let initializer = stmt.initializer().map(|e| self.collect_expr(e)); |
626 | Statement::Let { pat, type_ref, initializer } | 630 | Statement::Let { pat, type_ref, initializer } |
627 | } | 631 | } |
628 | ast::Stmt::ExprStmt(stmt) => { | 632 | ast::Stmt::ExprStmt(stmt) => { |
633 | self.check_cfg(&stmt)?; | ||
634 | |||
629 | Statement::Expr(self.collect_expr_opt(stmt.expr())) | 635 | Statement::Expr(self.collect_expr_opt(stmt.expr())) |
630 | } | 636 | } |
631 | ast::Stmt::Item(_) => return None, | 637 | ast::Stmt::Item(item) => { |
638 | self.check_cfg(&item)?; | ||
639 | |||
640 | return None; | ||
641 | } | ||
632 | }; | 642 | }; |
633 | Some(stmt) | 643 | Some(stmt) |
634 | }) | 644 | }) |
@@ -872,6 +882,28 @@ impl ExprCollector<'_> { | |||
872 | 882 | ||
873 | (args, ellipsis) | 883 | (args, ellipsis) |
874 | } | 884 | } |
885 | |||
886 | /// Returns `None` (and emits diagnostics) when `owner` if `#[cfg]`d out, and `Some(())` when | ||
887 | /// not. | ||
888 | fn check_cfg(&mut self, owner: &dyn ast::AttrsOwner) -> Option<()> { | ||
889 | match self.expander.parse_attrs(owner).cfg() { | ||
890 | Some(cfg) => { | ||
891 | if self.expander.cfg_options().check(&cfg) != Some(false) { | ||
892 | return Some(()); | ||
893 | } | ||
894 | |||
895 | self.source_map.diagnostics.push(BodyDiagnostic::InactiveCode(InactiveCode { | ||
896 | file: self.expander.current_file_id, | ||
897 | node: SyntaxNodePtr::new(owner.syntax()), | ||
898 | cfg, | ||
899 | opts: self.expander.cfg_options().clone(), | ||
900 | })); | ||
901 | |||
902 | None | ||
903 | } | ||
904 | None => Some(()), | ||
905 | } | ||
906 | } | ||
875 | } | 907 | } |
876 | 908 | ||
877 | impl From<ast::BinOp> for BinaryOp { | 909 | impl From<ast::BinOp> for BinaryOp { |
diff --git a/crates/hir_def/src/body/tests.rs b/crates/hir_def/src/body/tests.rs new file mode 100644 index 000000000..f07df5cee --- /dev/null +++ b/crates/hir_def/src/body/tests.rs | |||
@@ -0,0 +1,75 @@ | |||
1 | use base_db::{fixture::WithFixture, SourceDatabase}; | ||
2 | use test_utils::mark; | ||
3 | |||
4 | use crate::{test_db::TestDB, ModuleDefId}; | ||
5 | |||
6 | use super::*; | ||
7 | |||
8 | fn lower(ra_fixture: &str) -> Arc<Body> { | ||
9 | let (db, file_id) = crate::test_db::TestDB::with_single_file(ra_fixture); | ||
10 | |||
11 | let krate = db.crate_graph().iter().next().unwrap(); | ||
12 | let def_map = db.crate_def_map(krate); | ||
13 | let module = def_map.modules_for_file(file_id).next().unwrap(); | ||
14 | let module = &def_map[module]; | ||
15 | let fn_def = match module.scope.declarations().next().unwrap() { | ||
16 | ModuleDefId::FunctionId(it) => it, | ||
17 | _ => panic!(), | ||
18 | }; | ||
19 | |||
20 | db.body(fn_def.into()) | ||
21 | } | ||
22 | |||
23 | fn check_diagnostics(ra_fixture: &str) { | ||
24 | let db: TestDB = TestDB::with_files(ra_fixture); | ||
25 | db.check_diagnostics(); | ||
26 | } | ||
27 | |||
28 | #[test] | ||
29 | fn your_stack_belongs_to_me() { | ||
30 | mark::check!(your_stack_belongs_to_me); | ||
31 | lower( | ||
32 | " | ||
33 | macro_rules! n_nuple { | ||
34 | ($e:tt) => (); | ||
35 | ($($rest:tt)*) => {{ | ||
36 | (n_nuple!($($rest)*)None,) | ||
37 | }}; | ||
38 | } | ||
39 | fn main() { n_nuple!(1,2,3); } | ||
40 | ", | ||
41 | ); | ||
42 | } | ||
43 | |||
44 | #[test] | ||
45 | fn cfg_diagnostics() { | ||
46 | check_diagnostics( | ||
47 | r" | ||
48 | fn f() { | ||
49 | // The three g̶e̶n̶d̶e̶r̶s̶ statements: | ||
50 | |||
51 | #[cfg(a)] fn f() {} // Item statement | ||
52 | //^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled | ||
53 | #[cfg(a)] {} // Expression statement | ||
54 | //^^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled | ||
55 | #[cfg(a)] let x = 0; // let statement | ||
56 | //^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled | ||
57 | |||
58 | abc(#[cfg(a)] 0); | ||
59 | //^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled | ||
60 | let x = Struct { | ||
61 | #[cfg(a)] f: 0, | ||
62 | //^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled | ||
63 | }; | ||
64 | match () { | ||
65 | () => (), | ||
66 | #[cfg(a)] () => (), | ||
67 | //^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled | ||
68 | } | ||
69 | |||
70 | #[cfg(a)] 0 // Trailing expression of block | ||
71 | //^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled | ||
72 | } | ||
73 | ", | ||
74 | ); | ||
75 | } | ||
diff --git a/crates/hir_def/src/diagnostics.rs b/crates/hir_def/src/diagnostics.rs index c9c08b01f..b221b290c 100644 --- a/crates/hir_def/src/diagnostics.rs +++ b/crates/hir_def/src/diagnostics.rs | |||
@@ -1,11 +1,19 @@ | |||
1 | //! Diagnostics produced by `hir_def`. | 1 | //! Diagnostics produced by `hir_def`. |
2 | 2 | ||
3 | use std::any::Any; | 3 | use std::any::Any; |
4 | use stdx::format_to; | ||
4 | 5 | ||
5 | use hir_expand::diagnostics::{Diagnostic, DiagnosticCode}; | 6 | use cfg::{CfgExpr, CfgOptions, DnfExpr}; |
7 | use hir_expand::diagnostics::{Diagnostic, DiagnosticCode, DiagnosticSink}; | ||
8 | use hir_expand::{HirFileId, InFile}; | ||
6 | use syntax::{ast, AstPtr, SyntaxNodePtr}; | 9 | use syntax::{ast, AstPtr, SyntaxNodePtr}; |
7 | 10 | ||
8 | use hir_expand::{HirFileId, InFile}; | 11 | use crate::{db::DefDatabase, DefWithBodyId}; |
12 | |||
13 | pub fn validate_body(db: &dyn DefDatabase, owner: DefWithBodyId, sink: &mut DiagnosticSink<'_>) { | ||
14 | let source_map = db.body_with_source_map(owner).1; | ||
15 | source_map.add_diagnostics(db, sink); | ||
16 | } | ||
9 | 17 | ||
10 | // Diagnostic: unresolved-module | 18 | // Diagnostic: unresolved-module |
11 | // | 19 | // |
@@ -87,13 +95,15 @@ impl Diagnostic for UnresolvedImport { | |||
87 | } | 95 | } |
88 | } | 96 | } |
89 | 97 | ||
90 | // Diagnostic: unconfigured-code | 98 | // Diagnostic: inactive-code |
91 | // | 99 | // |
92 | // This diagnostic is shown for code with inactive `#[cfg]` attributes. | 100 | // This diagnostic is shown for code with inactive `#[cfg]` attributes. |
93 | #[derive(Debug)] | 101 | #[derive(Debug, Clone, Eq, PartialEq)] |
94 | pub struct InactiveCode { | 102 | pub struct InactiveCode { |
95 | pub file: HirFileId, | 103 | pub file: HirFileId, |
96 | pub node: SyntaxNodePtr, | 104 | pub node: SyntaxNodePtr, |
105 | pub cfg: CfgExpr, | ||
106 | pub opts: CfgOptions, | ||
97 | } | 107 | } |
98 | 108 | ||
99 | impl Diagnostic for InactiveCode { | 109 | impl Diagnostic for InactiveCode { |
@@ -101,8 +111,14 @@ impl Diagnostic for InactiveCode { | |||
101 | DiagnosticCode("inactive-code") | 111 | DiagnosticCode("inactive-code") |
102 | } | 112 | } |
103 | fn message(&self) -> String { | 113 | fn message(&self) -> String { |
104 | // FIXME: say *why* it is configured out | 114 | let inactive = DnfExpr::new(self.cfg.clone()).why_inactive(&self.opts); |
105 | "code is inactive due to #[cfg] directives".to_string() | 115 | let mut buf = "code is inactive due to #[cfg] directives".to_string(); |
116 | |||
117 | if let Some(inactive) = inactive { | ||
118 | format_to!(buf, ": {}", inactive); | ||
119 | } | ||
120 | |||
121 | buf | ||
106 | } | 122 | } |
107 | fn display_source(&self) -> InFile<SyntaxNodePtr> { | 123 | fn display_source(&self) -> InFile<SyntaxNodePtr> { |
108 | InFile::new(self.file, self.node.clone()) | 124 | InFile::new(self.file, self.node.clone()) |
diff --git a/crates/hir_def/src/nameres.rs b/crates/hir_def/src/nameres.rs index 01a28aeeb..eb41d324e 100644 --- a/crates/hir_def/src/nameres.rs +++ b/crates/hir_def/src/nameres.rs | |||
@@ -283,6 +283,7 @@ pub enum ModuleSource { | |||
283 | } | 283 | } |
284 | 284 | ||
285 | mod diagnostics { | 285 | mod diagnostics { |
286 | use cfg::{CfgExpr, CfgOptions}; | ||
286 | use hir_expand::diagnostics::DiagnosticSink; | 287 | use hir_expand::diagnostics::DiagnosticSink; |
287 | use hir_expand::hygiene::Hygiene; | 288 | use hir_expand::hygiene::Hygiene; |
288 | use hir_expand::InFile; | 289 | use hir_expand::InFile; |
@@ -299,7 +300,7 @@ mod diagnostics { | |||
299 | 300 | ||
300 | UnresolvedImport { ast: AstId<ast::Use>, index: usize }, | 301 | UnresolvedImport { ast: AstId<ast::Use>, index: usize }, |
301 | 302 | ||
302 | UnconfiguredCode { ast: InFile<SyntaxNodePtr> }, | 303 | UnconfiguredCode { ast: InFile<SyntaxNodePtr>, cfg: CfgExpr, opts: CfgOptions }, |
303 | } | 304 | } |
304 | 305 | ||
305 | #[derive(Debug, PartialEq, Eq)] | 306 | #[derive(Debug, PartialEq, Eq)] |
@@ -341,8 +342,10 @@ mod diagnostics { | |||
341 | pub(super) fn unconfigured_code( | 342 | pub(super) fn unconfigured_code( |
342 | container: LocalModuleId, | 343 | container: LocalModuleId, |
343 | ast: InFile<SyntaxNodePtr>, | 344 | ast: InFile<SyntaxNodePtr>, |
345 | cfg: CfgExpr, | ||
346 | opts: CfgOptions, | ||
344 | ) -> Self { | 347 | ) -> Self { |
345 | Self { in_module: container, kind: DiagnosticKind::UnconfiguredCode { ast } } | 348 | Self { in_module: container, kind: DiagnosticKind::UnconfiguredCode { ast, cfg, opts } } |
346 | } | 349 | } |
347 | 350 | ||
348 | pub(super) fn add_to( | 351 | pub(super) fn add_to( |
@@ -395,8 +398,13 @@ mod diagnostics { | |||
395 | } | 398 | } |
396 | } | 399 | } |
397 | 400 | ||
398 | DiagnosticKind::UnconfiguredCode { ast } => { | 401 | DiagnosticKind::UnconfiguredCode { ast, cfg, opts } => { |
399 | sink.push(InactiveCode { file: ast.file_id, node: ast.value.clone() }); | 402 | sink.push(InactiveCode { |
403 | file: ast.file_id, | ||
404 | node: ast.value.clone(), | ||
405 | cfg: cfg.clone(), | ||
406 | opts: opts.clone(), | ||
407 | }); | ||
400 | } | 408 | } |
401 | } | 409 | } |
402 | } | 410 | } |
diff --git a/crates/hir_def/src/nameres/collector.rs b/crates/hir_def/src/nameres/collector.rs index bff8edb62..f30172d90 100644 --- a/crates/hir_def/src/nameres/collector.rs +++ b/crates/hir_def/src/nameres/collector.rs | |||
@@ -6,7 +6,7 @@ | |||
6 | use std::iter; | 6 | use std::iter; |
7 | 7 | ||
8 | use base_db::{CrateId, FileId, ProcMacroId}; | 8 | use base_db::{CrateId, FileId, ProcMacroId}; |
9 | use cfg::CfgOptions; | 9 | use cfg::{CfgExpr, CfgOptions}; |
10 | use hir_expand::InFile; | 10 | use hir_expand::InFile; |
11 | use hir_expand::{ | 11 | use hir_expand::{ |
12 | ast_id_map::FileAstId, | 12 | ast_id_map::FileAstId, |
@@ -900,7 +900,8 @@ impl ModCollector<'_, '_> { | |||
900 | // `#[macro_use] extern crate` is hoisted to imports macros before collecting | 900 | // `#[macro_use] extern crate` is hoisted to imports macros before collecting |
901 | // any other items. | 901 | // any other items. |
902 | for item in items { | 902 | for item in items { |
903 | if self.is_cfg_enabled(self.item_tree.attrs((*item).into())) { | 903 | let attrs = self.item_tree.attrs((*item).into()); |
904 | if attrs.cfg().map_or(true, |cfg| self.is_cfg_enabled(&cfg)) { | ||
904 | if let ModItem::ExternCrate(id) = item { | 905 | if let ModItem::ExternCrate(id) = item { |
905 | let import = self.item_tree[*id].clone(); | 906 | let import = self.item_tree[*id].clone(); |
906 | if import.is_macro_use { | 907 | if import.is_macro_use { |
@@ -912,9 +913,11 @@ impl ModCollector<'_, '_> { | |||
912 | 913 | ||
913 | for &item in items { | 914 | for &item in items { |
914 | let attrs = self.item_tree.attrs(item.into()); | 915 | let attrs = self.item_tree.attrs(item.into()); |
915 | if !self.is_cfg_enabled(attrs) { | 916 | if let Some(cfg) = attrs.cfg() { |
916 | self.emit_unconfigured_diagnostic(item); | 917 | if !self.is_cfg_enabled(&cfg) { |
917 | continue; | 918 | self.emit_unconfigured_diagnostic(item, &cfg); |
919 | continue; | ||
920 | } | ||
918 | } | 921 | } |
919 | let module = | 922 | let module = |
920 | ModuleId { krate: self.def_collector.def_map.krate, local_id: self.module_id }; | 923 | ModuleId { krate: self.def_collector.def_map.krate, local_id: self.module_id }; |
@@ -1321,20 +1324,22 @@ impl ModCollector<'_, '_> { | |||
1321 | } | 1324 | } |
1322 | } | 1325 | } |
1323 | 1326 | ||
1324 | fn is_cfg_enabled(&self, attrs: &Attrs) -> bool { | 1327 | fn is_cfg_enabled(&self, cfg: &CfgExpr) -> bool { |
1325 | attrs.is_cfg_enabled(self.def_collector.cfg_options) | 1328 | self.def_collector.cfg_options.check(cfg) != Some(false) |
1326 | } | 1329 | } |
1327 | 1330 | ||
1328 | fn emit_unconfigured_diagnostic(&mut self, item: ModItem) { | 1331 | fn emit_unconfigured_diagnostic(&mut self, item: ModItem, cfg: &CfgExpr) { |
1329 | let ast_id = item.ast_id(self.item_tree); | 1332 | let ast_id = item.ast_id(self.item_tree); |
1330 | let id_map = self.def_collector.db.ast_id_map(self.file_id); | 1333 | let id_map = self.def_collector.db.ast_id_map(self.file_id); |
1331 | let syntax_ptr = id_map.get(ast_id).syntax_node_ptr(); | 1334 | let syntax_ptr = id_map.get(ast_id).syntax_node_ptr(); |
1332 | 1335 | ||
1333 | let ast_node = InFile::new(self.file_id, syntax_ptr); | 1336 | let ast_node = InFile::new(self.file_id, syntax_ptr); |
1334 | self.def_collector | 1337 | self.def_collector.def_map.diagnostics.push(DefDiagnostic::unconfigured_code( |
1335 | .def_map | 1338 | self.module_id, |
1336 | .diagnostics | 1339 | ast_node, |
1337 | .push(DefDiagnostic::unconfigured_code(self.module_id, ast_node)); | 1340 | cfg.clone(), |
1341 | self.def_collector.cfg_options.clone(), | ||
1342 | )); | ||
1338 | } | 1343 | } |
1339 | } | 1344 | } |
1340 | 1345 | ||
diff --git a/crates/hir_def/src/nameres/tests/diagnostics.rs b/crates/hir_def/src/nameres/tests/diagnostics.rs index 576b813d2..1a7b98831 100644 --- a/crates/hir_def/src/nameres/tests/diagnostics.rs +++ b/crates/hir_def/src/nameres/tests/diagnostics.rs | |||
@@ -1,42 +1,10 @@ | |||
1 | use base_db::fixture::WithFixture; | 1 | use base_db::fixture::WithFixture; |
2 | use base_db::FileId; | ||
3 | use base_db::SourceDatabaseExt; | ||
4 | use hir_expand::db::AstDatabase; | ||
5 | use rustc_hash::FxHashMap; | ||
6 | use syntax::TextRange; | ||
7 | use syntax::TextSize; | ||
8 | 2 | ||
9 | use crate::test_db::TestDB; | 3 | use crate::test_db::TestDB; |
10 | 4 | ||
11 | fn check_diagnostics(ra_fixture: &str) { | 5 | fn check_diagnostics(ra_fixture: &str) { |
12 | let db: TestDB = TestDB::with_files(ra_fixture); | 6 | let db: TestDB = TestDB::with_files(ra_fixture); |
13 | let annotations = db.extract_annotations(); | 7 | db.check_diagnostics(); |
14 | assert!(!annotations.is_empty()); | ||
15 | |||
16 | let mut actual: FxHashMap<FileId, Vec<(TextRange, String)>> = FxHashMap::default(); | ||
17 | db.diagnostics(|d| { | ||
18 | let src = d.display_source(); | ||
19 | let root = db.parse_or_expand(src.file_id).unwrap(); | ||
20 | // FIXME: macros... | ||
21 | let file_id = src.file_id.original_file(&db); | ||
22 | let range = src.value.to_node(&root).text_range(); | ||
23 | let message = d.message().to_owned(); | ||
24 | actual.entry(file_id).or_default().push((range, message)); | ||
25 | }); | ||
26 | |||
27 | for (file_id, diags) in actual.iter_mut() { | ||
28 | diags.sort_by_key(|it| it.0.start()); | ||
29 | let text = db.file_text(*file_id); | ||
30 | // For multiline spans, place them on line start | ||
31 | for (range, content) in diags { | ||
32 | if text[*range].contains('\n') { | ||
33 | *range = TextRange::new(range.start(), range.start() + TextSize::from(1)); | ||
34 | *content = format!("... {}", content); | ||
35 | } | ||
36 | } | ||
37 | } | ||
38 | |||
39 | assert_eq!(annotations, actual); | ||
40 | } | 8 | } |
41 | 9 | ||
42 | #[test] | 10 | #[test] |
@@ -129,3 +97,25 @@ fn unresolved_module() { | |||
129 | ", | 97 | ", |
130 | ); | 98 | ); |
131 | } | 99 | } |
100 | |||
101 | #[test] | ||
102 | fn inactive_item() { | ||
103 | // Additional tests in `cfg` crate. This only tests disabled cfgs. | ||
104 | |||
105 | check_diagnostics( | ||
106 | r#" | ||
107 | //- /lib.rs | ||
108 | #[cfg(no)] pub fn f() {} | ||
109 | //^^^^^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: no is disabled | ||
110 | |||
111 | #[cfg(no)] #[cfg(no2)] mod m; | ||
112 | //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: no and no2 are disabled | ||
113 | |||
114 | #[cfg(all(not(a), b))] enum E {} | ||
115 | //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: b is disabled | ||
116 | |||
117 | #[cfg(feature = "std")] use std; | ||
118 | //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: feature = "std" is disabled | ||
119 | "#, | ||
120 | ); | ||
121 | } | ||
diff --git a/crates/hir_def/src/test_db.rs b/crates/hir_def/src/test_db.rs index fb1d3c974..2b36c824a 100644 --- a/crates/hir_def/src/test_db.rs +++ b/crates/hir_def/src/test_db.rs | |||
@@ -12,10 +12,10 @@ use hir_expand::diagnostics::Diagnostic; | |||
12 | use hir_expand::diagnostics::DiagnosticSinkBuilder; | 12 | use hir_expand::diagnostics::DiagnosticSinkBuilder; |
13 | use rustc_hash::FxHashMap; | 13 | use rustc_hash::FxHashMap; |
14 | use rustc_hash::FxHashSet; | 14 | use rustc_hash::FxHashSet; |
15 | use syntax::TextRange; | 15 | use syntax::{TextRange, TextSize}; |
16 | use test_utils::extract_annotations; | 16 | use test_utils::extract_annotations; |
17 | 17 | ||
18 | use crate::db::DefDatabase; | 18 | use crate::{db::DefDatabase, ModuleDefId}; |
19 | 19 | ||
20 | #[salsa::database( | 20 | #[salsa::database( |
21 | base_db::SourceDatabaseExtStorage, | 21 | base_db::SourceDatabaseExtStorage, |
@@ -135,9 +135,47 @@ impl TestDB { | |||
135 | let crate_def_map = self.crate_def_map(krate); | 135 | let crate_def_map = self.crate_def_map(krate); |
136 | 136 | ||
137 | let mut sink = DiagnosticSinkBuilder::new().build(&mut cb); | 137 | let mut sink = DiagnosticSinkBuilder::new().build(&mut cb); |
138 | for (module_id, _) in crate_def_map.modules.iter() { | 138 | for (module_id, module) in crate_def_map.modules.iter() { |
139 | crate_def_map.add_diagnostics(self, module_id, &mut sink); | 139 | crate_def_map.add_diagnostics(self, module_id, &mut sink); |
140 | |||
141 | for decl in module.scope.declarations() { | ||
142 | if let ModuleDefId::FunctionId(it) = decl { | ||
143 | let source_map = self.body_with_source_map(it.into()).1; | ||
144 | source_map.add_diagnostics(self, &mut sink); | ||
145 | } | ||
146 | } | ||
140 | } | 147 | } |
141 | } | 148 | } |
142 | } | 149 | } |
150 | |||
151 | pub fn check_diagnostics(&self) { | ||
152 | let db: &TestDB = self; | ||
153 | let annotations = db.extract_annotations(); | ||
154 | assert!(!annotations.is_empty()); | ||
155 | |||
156 | let mut actual: FxHashMap<FileId, Vec<(TextRange, String)>> = FxHashMap::default(); | ||
157 | db.diagnostics(|d| { | ||
158 | let src = d.display_source(); | ||
159 | let root = db.parse_or_expand(src.file_id).unwrap(); | ||
160 | // FIXME: macros... | ||
161 | let file_id = src.file_id.original_file(db); | ||
162 | let range = src.value.to_node(&root).text_range(); | ||
163 | let message = d.message().to_owned(); | ||
164 | actual.entry(file_id).or_default().push((range, message)); | ||
165 | }); | ||
166 | |||
167 | for (file_id, diags) in actual.iter_mut() { | ||
168 | diags.sort_by_key(|it| it.0.start()); | ||
169 | let text = db.file_text(*file_id); | ||
170 | // For multiline spans, place them on line start | ||
171 | for (range, content) in diags { | ||
172 | if text[*range].contains('\n') { | ||
173 | *range = TextRange::new(range.start(), range.start() + TextSize::from(1)); | ||
174 | *content = format!("... {}", content); | ||
175 | } | ||
176 | } | ||
177 | } | ||
178 | |||
179 | assert_eq!(annotations, actual); | ||
180 | } | ||
143 | } | 181 | } |
diff --git a/crates/hir_ty/Cargo.toml b/crates/hir_ty/Cargo.toml index e9c62c6aa..367a1b98d 100644 --- a/crates/hir_ty/Cargo.toml +++ b/crates/hir_ty/Cargo.toml | |||
@@ -17,9 +17,9 @@ ena = "0.14.0" | |||
17 | log = "0.4.8" | 17 | log = "0.4.8" |
18 | rustc-hash = "1.1.0" | 18 | rustc-hash = "1.1.0" |
19 | scoped-tls = "1" | 19 | scoped-tls = "1" |
20 | chalk-solve = "0.33" | 20 | chalk-solve = { version = "0.34", default-features = false } |
21 | chalk-ir = "0.33" | 21 | chalk-ir = "0.34" |
22 | chalk-recursive = "0.33" | 22 | chalk-recursive = "0.34" |
23 | 23 | ||
24 | stdx = { path = "../stdx", version = "0.0.0" } | 24 | stdx = { path = "../stdx", version = "0.0.0" } |
25 | hir_def = { path = "../hir_def", version = "0.0.0" } | 25 | hir_def = { path = "../hir_def", version = "0.0.0" } |
diff --git a/crates/hir_ty/src/diagnostics/decl_check/case_conv.rs b/crates/hir_ty/src/diagnostics/decl_check/case_conv.rs index 324d60765..b0144a289 100644 --- a/crates/hir_ty/src/diagnostics/decl_check/case_conv.rs +++ b/crates/hir_ty/src/diagnostics/decl_check/case_conv.rs | |||
@@ -1,156 +1,145 @@ | |||
1 | //! Functions for string case manipulation, such as detecting the identifier case, | 1 | //! Functions for string case manipulation, such as detecting the identifier case, |
2 | //! and converting it into appropriate form. | 2 | //! and converting it into appropriate form. |
3 | 3 | ||
4 | #[derive(Debug)] | 4 | // Code that was taken from rustc was taken at commit 89fdb30, |
5 | enum DetectedCase { | 5 | // from file /compiler/rustc_lint/src/nonstandard_style.rs |
6 | LowerCamelCase, | ||
7 | UpperCamelCase, | ||
8 | LowerSnakeCase, | ||
9 | UpperSnakeCase, | ||
10 | Unknown, | ||
11 | } | ||
12 | |||
13 | fn detect_case(ident: &str) -> DetectedCase { | ||
14 | let trimmed_ident = ident.trim_matches('_'); | ||
15 | let first_lowercase = trimmed_ident.starts_with(|chr: char| chr.is_ascii_lowercase()); | ||
16 | let mut has_lowercase = first_lowercase; | ||
17 | let mut has_uppercase = false; | ||
18 | let mut has_underscore = false; | ||
19 | |||
20 | for chr in trimmed_ident.chars() { | ||
21 | if chr == '_' { | ||
22 | has_underscore = true; | ||
23 | } else if chr.is_ascii_uppercase() { | ||
24 | has_uppercase = true; | ||
25 | } else if chr.is_ascii_lowercase() { | ||
26 | has_lowercase = true; | ||
27 | } | ||
28 | } | ||
29 | |||
30 | if has_uppercase { | ||
31 | if !has_lowercase { | ||
32 | if has_underscore { | ||
33 | DetectedCase::UpperSnakeCase | ||
34 | } else { | ||
35 | // It has uppercase only and no underscores. Ex: "AABB" | ||
36 | // This is a camel cased acronym. | ||
37 | DetectedCase::UpperCamelCase | ||
38 | } | ||
39 | } else if !has_underscore { | ||
40 | if first_lowercase { | ||
41 | DetectedCase::LowerCamelCase | ||
42 | } else { | ||
43 | DetectedCase::UpperCamelCase | ||
44 | } | ||
45 | } else { | ||
46 | // It has uppercase, it has lowercase, it has underscore. | ||
47 | // No assumptions here | ||
48 | DetectedCase::Unknown | ||
49 | } | ||
50 | } else { | ||
51 | DetectedCase::LowerSnakeCase | ||
52 | } | ||
53 | } | ||
54 | 6 | ||
55 | /// Converts an identifier to an UpperCamelCase form. | 7 | /// Converts an identifier to an UpperCamelCase form. |
56 | /// Returns `None` if the string is already is UpperCamelCase. | 8 | /// Returns `None` if the string is already is UpperCamelCase. |
57 | pub fn to_camel_case(ident: &str) -> Option<String> { | 9 | pub fn to_camel_case(ident: &str) -> Option<String> { |
58 | let detected_case = detect_case(ident); | 10 | if is_camel_case(ident) { |
59 | 11 | return None; | |
60 | match detected_case { | ||
61 | DetectedCase::UpperCamelCase => return None, | ||
62 | DetectedCase::LowerCamelCase => { | ||
63 | let mut first_capitalized = false; | ||
64 | let output = ident | ||
65 | .chars() | ||
66 | .map(|chr| { | ||
67 | if !first_capitalized && chr.is_ascii_lowercase() { | ||
68 | first_capitalized = true; | ||
69 | chr.to_ascii_uppercase() | ||
70 | } else { | ||
71 | chr | ||
72 | } | ||
73 | }) | ||
74 | .collect(); | ||
75 | return Some(output); | ||
76 | } | ||
77 | _ => {} | ||
78 | } | 12 | } |
79 | 13 | ||
80 | let mut output = String::with_capacity(ident.len()); | 14 | // Taken from rustc. |
81 | 15 | let ret = ident | |
82 | let mut capital_added = false; | 16 | .trim_matches('_') |
83 | for chr in ident.chars() { | 17 | .split('_') |
84 | if chr.is_alphabetic() { | 18 | .filter(|component| !component.is_empty()) |
85 | if !capital_added { | 19 | .map(|component| { |
86 | output.push(chr.to_ascii_uppercase()); | 20 | let mut camel_cased_component = String::new(); |
87 | capital_added = true; | 21 | |
88 | } else { | 22 | let mut new_word = true; |
89 | output.push(chr.to_ascii_lowercase()); | 23 | let mut prev_is_lower_case = true; |
24 | |||
25 | for c in component.chars() { | ||
26 | // Preserve the case if an uppercase letter follows a lowercase letter, so that | ||
27 | // `camelCase` is converted to `CamelCase`. | ||
28 | if prev_is_lower_case && c.is_uppercase() { | ||
29 | new_word = true; | ||
30 | } | ||
31 | |||
32 | if new_word { | ||
33 | camel_cased_component.push_str(&c.to_uppercase().to_string()); | ||
34 | } else { | ||
35 | camel_cased_component.push_str(&c.to_lowercase().to_string()); | ||
36 | } | ||
37 | |||
38 | prev_is_lower_case = c.is_lowercase(); | ||
39 | new_word = false; | ||
90 | } | 40 | } |
91 | } else if chr == '_' { | ||
92 | // Skip this character and make the next one capital. | ||
93 | capital_added = false; | ||
94 | } else { | ||
95 | // Put the characted as-is. | ||
96 | output.push(chr); | ||
97 | } | ||
98 | } | ||
99 | 41 | ||
100 | if output == ident { | 42 | camel_cased_component |
101 | // While we didn't detect the correct case at the beginning, there | 43 | }) |
102 | // may be special cases: e.g. `A` is both valid CamelCase and UPPER_SNAKE_CASE. | 44 | .fold((String::new(), None), |(acc, prev): (String, Option<String>), next| { |
103 | None | 45 | // separate two components with an underscore if their boundary cannot |
104 | } else { | 46 | // be distinguished using a uppercase/lowercase case distinction |
105 | Some(output) | 47 | let join = if let Some(prev) = prev { |
106 | } | 48 | let l = prev.chars().last().unwrap(); |
49 | let f = next.chars().next().unwrap(); | ||
50 | !char_has_case(l) && !char_has_case(f) | ||
51 | } else { | ||
52 | false | ||
53 | }; | ||
54 | (acc + if join { "_" } else { "" } + &next, Some(next)) | ||
55 | }) | ||
56 | .0; | ||
57 | Some(ret) | ||
107 | } | 58 | } |
108 | 59 | ||
109 | /// Converts an identifier to a lower_snake_case form. | 60 | /// Converts an identifier to a lower_snake_case form. |
110 | /// Returns `None` if the string is already in lower_snake_case. | 61 | /// Returns `None` if the string is already in lower_snake_case. |
111 | pub fn to_lower_snake_case(ident: &str) -> Option<String> { | 62 | pub fn to_lower_snake_case(ident: &str) -> Option<String> { |
112 | // First, assume that it's UPPER_SNAKE_CASE. | 63 | if is_lower_snake_case(ident) { |
113 | match detect_case(ident) { | 64 | return None; |
114 | DetectedCase::LowerSnakeCase => return None, | 65 | } else if is_upper_snake_case(ident) { |
115 | DetectedCase::UpperSnakeCase => { | 66 | return Some(ident.to_lowercase()); |
116 | return Some(ident.chars().map(|chr| chr.to_ascii_lowercase()).collect()) | ||
117 | } | ||
118 | _ => {} | ||
119 | } | 67 | } |
120 | 68 | ||
121 | // Otherwise, assume that it's CamelCase. | 69 | Some(stdx::to_lower_snake_case(ident)) |
122 | let lower_snake_case = stdx::to_lower_snake_case(ident); | ||
123 | |||
124 | if lower_snake_case == ident { | ||
125 | // While we didn't detect the correct case at the beginning, there | ||
126 | // may be special cases: e.g. `a` is both valid camelCase and snake_case. | ||
127 | None | ||
128 | } else { | ||
129 | Some(lower_snake_case) | ||
130 | } | ||
131 | } | 70 | } |
132 | 71 | ||
133 | /// Converts an identifier to an UPPER_SNAKE_CASE form. | 72 | /// Converts an identifier to an UPPER_SNAKE_CASE form. |
134 | /// Returns `None` if the string is already is UPPER_SNAKE_CASE. | 73 | /// Returns `None` if the string is already is UPPER_SNAKE_CASE. |
135 | pub fn to_upper_snake_case(ident: &str) -> Option<String> { | 74 | pub fn to_upper_snake_case(ident: &str) -> Option<String> { |
136 | match detect_case(ident) { | 75 | if is_upper_snake_case(ident) { |
137 | DetectedCase::UpperSnakeCase => return None, | 76 | return None; |
138 | DetectedCase::LowerSnakeCase => { | 77 | } else if is_lower_snake_case(ident) { |
139 | return Some(ident.chars().map(|chr| chr.to_ascii_uppercase()).collect()) | 78 | return Some(ident.to_uppercase()); |
140 | } | 79 | } |
141 | _ => {} | 80 | |
81 | Some(stdx::to_upper_snake_case(ident)) | ||
82 | } | ||
83 | |||
84 | // Taken from rustc. | ||
85 | // Modified by replacing the use of unstable feature `array_windows`. | ||
86 | fn is_camel_case(name: &str) -> bool { | ||
87 | let name = name.trim_matches('_'); | ||
88 | if name.is_empty() { | ||
89 | return true; | ||
142 | } | 90 | } |
143 | 91 | ||
144 | // Normalize the string from whatever form it's in currently, and then just make it uppercase. | 92 | let mut fst = None; |
145 | let upper_snake_case = stdx::to_upper_snake_case(ident); | 93 | // start with a non-lowercase letter rather than non-uppercase |
94 | // ones (some scripts don't have a concept of upper/lowercase) | ||
95 | !name.chars().next().unwrap().is_lowercase() | ||
96 | && !name.contains("__") | ||
97 | && !name.chars().any(|snd| { | ||
98 | let ret = match (fst, snd) { | ||
99 | (None, _) => false, | ||
100 | (Some(fst), snd) => { | ||
101 | char_has_case(fst) && snd == '_' || char_has_case(snd) && fst == '_' | ||
102 | } | ||
103 | }; | ||
104 | fst = Some(snd); | ||
105 | |||
106 | ret | ||
107 | }) | ||
108 | } | ||
109 | |||
110 | fn is_lower_snake_case(ident: &str) -> bool { | ||
111 | is_snake_case(ident, char::is_uppercase) | ||
112 | } | ||
146 | 113 | ||
147 | if upper_snake_case == ident { | 114 | fn is_upper_snake_case(ident: &str) -> bool { |
148 | // While we didn't detect the correct case at the beginning, there | 115 | is_snake_case(ident, char::is_lowercase) |
149 | // may be special cases: e.g. `A` is both valid CamelCase and UPPER_SNAKE_CASE. | 116 | } |
150 | None | 117 | |
151 | } else { | 118 | // Taken from rustc. |
152 | Some(upper_snake_case) | 119 | // Modified to allow checking for both upper and lower snake case. |
120 | fn is_snake_case<F: Fn(char) -> bool>(ident: &str, wrong_case: F) -> bool { | ||
121 | if ident.is_empty() { | ||
122 | return true; | ||
153 | } | 123 | } |
124 | let ident = ident.trim_matches('_'); | ||
125 | |||
126 | let mut allow_underscore = true; | ||
127 | ident.chars().all(|c| { | ||
128 | allow_underscore = match c { | ||
129 | '_' if !allow_underscore => return false, | ||
130 | '_' => false, | ||
131 | // It would be more obvious to check for the correct case, | ||
132 | // but some characters do not have a case. | ||
133 | c if !wrong_case(c) => true, | ||
134 | _ => return false, | ||
135 | }; | ||
136 | true | ||
137 | }) | ||
138 | } | ||
139 | |||
140 | // Taken from rustc. | ||
141 | fn char_has_case(c: char) -> bool { | ||
142 | c.is_lowercase() || c.is_uppercase() | ||
154 | } | 143 | } |
155 | 144 | ||
156 | #[cfg(test)] | 145 | #[cfg(test)] |
@@ -173,6 +162,7 @@ mod tests { | |||
173 | check(to_lower_snake_case, "CamelCase", expect![["camel_case"]]); | 162 | check(to_lower_snake_case, "CamelCase", expect![["camel_case"]]); |
174 | check(to_lower_snake_case, "lowerCamelCase", expect![["lower_camel_case"]]); | 163 | check(to_lower_snake_case, "lowerCamelCase", expect![["lower_camel_case"]]); |
175 | check(to_lower_snake_case, "a", expect![[""]]); | 164 | check(to_lower_snake_case, "a", expect![[""]]); |
165 | check(to_lower_snake_case, "abc", expect![[""]]); | ||
176 | } | 166 | } |
177 | 167 | ||
178 | #[test] | 168 | #[test] |
@@ -187,6 +177,11 @@ mod tests { | |||
187 | check(to_camel_case, "name", expect![["Name"]]); | 177 | check(to_camel_case, "name", expect![["Name"]]); |
188 | check(to_camel_case, "A", expect![[""]]); | 178 | check(to_camel_case, "A", expect![[""]]); |
189 | check(to_camel_case, "AABB", expect![[""]]); | 179 | check(to_camel_case, "AABB", expect![[""]]); |
180 | // Taken from rustc: /compiler/rustc_lint/src/nonstandard_style/tests.rs | ||
181 | check(to_camel_case, "X86_64", expect![[""]]); | ||
182 | check(to_camel_case, "x86__64", expect![["X86_64"]]); | ||
183 | check(to_camel_case, "Abc_123", expect![["Abc123"]]); | ||
184 | check(to_camel_case, "A1_b2_c3", expect![["A1B2C3"]]); | ||
190 | } | 185 | } |
191 | 186 | ||
192 | #[test] | 187 | #[test] |
@@ -197,5 +192,7 @@ mod tests { | |||
197 | check(to_upper_snake_case, "CamelCase", expect![["CAMEL_CASE"]]); | 192 | check(to_upper_snake_case, "CamelCase", expect![["CAMEL_CASE"]]); |
198 | check(to_upper_snake_case, "lowerCamelCase", expect![["LOWER_CAMEL_CASE"]]); | 193 | check(to_upper_snake_case, "lowerCamelCase", expect![["LOWER_CAMEL_CASE"]]); |
199 | check(to_upper_snake_case, "A", expect![[""]]); | 194 | check(to_upper_snake_case, "A", expect![[""]]); |
195 | check(to_upper_snake_case, "ABC", expect![[""]]); | ||
196 | check(to_upper_snake_case, "X86_64", expect![[""]]); | ||
200 | } | 197 | } |
201 | } | 198 | } |
diff --git a/crates/hir_ty/src/traits.rs b/crates/hir_ty/src/traits.rs index 14cd3a2b4..ce1174cbe 100644 --- a/crates/hir_ty/src/traits.rs +++ b/crates/hir_ty/src/traits.rs | |||
@@ -5,6 +5,7 @@ use base_db::CrateId; | |||
5 | use chalk_ir::cast::Cast; | 5 | use chalk_ir::cast::Cast; |
6 | use chalk_solve::{logging_db::LoggingRustIrDatabase, Solver}; | 6 | use chalk_solve::{logging_db::LoggingRustIrDatabase, Solver}; |
7 | use hir_def::{lang_item::LangItemTarget, TraitId}; | 7 | use hir_def::{lang_item::LangItemTarget, TraitId}; |
8 | use stdx::panic_context; | ||
8 | 9 | ||
9 | use crate::{db::HirDatabase, DebruijnIndex, Substs}; | 10 | use crate::{db::HirDatabase, DebruijnIndex, Substs}; |
10 | 11 | ||
@@ -168,14 +169,23 @@ fn solve( | |||
168 | }; | 169 | }; |
169 | 170 | ||
170 | let mut solve = || { | 171 | let mut solve = || { |
171 | if is_chalk_print() { | 172 | let _ctx = if is_chalk_debug() || is_chalk_print() { |
172 | let logging_db = LoggingRustIrDatabase::new(context); | 173 | Some(panic_context::enter(format!("solving {:?}", goal))) |
173 | let solution = solver.solve_limited(&logging_db, goal, &should_continue); | 174 | } else { |
174 | log::debug!("chalk program:\n{}", logging_db); | 175 | None |
176 | }; | ||
177 | let solution = if is_chalk_print() { | ||
178 | let logging_db = | ||
179 | LoggingRustIrDatabaseLoggingOnDrop(LoggingRustIrDatabase::new(context)); | ||
180 | let solution = solver.solve_limited(&logging_db.0, goal, &should_continue); | ||
175 | solution | 181 | solution |
176 | } else { | 182 | } else { |
177 | solver.solve_limited(&context, goal, &should_continue) | 183 | solver.solve_limited(&context, goal, &should_continue) |
178 | } | 184 | }; |
185 | |||
186 | log::debug!("solve({:?}) => {:?}", goal, solution); | ||
187 | |||
188 | solution | ||
179 | }; | 189 | }; |
180 | 190 | ||
181 | // don't set the TLS for Chalk unless Chalk debugging is active, to make | 191 | // don't set the TLS for Chalk unless Chalk debugging is active, to make |
@@ -183,11 +193,17 @@ fn solve( | |||
183 | let solution = | 193 | let solution = |
184 | if is_chalk_debug() { chalk::tls::set_current_program(db, solve) } else { solve() }; | 194 | if is_chalk_debug() { chalk::tls::set_current_program(db, solve) } else { solve() }; |
185 | 195 | ||
186 | log::debug!("solve({:?}) => {:?}", goal, solution); | ||
187 | |||
188 | solution | 196 | solution |
189 | } | 197 | } |
190 | 198 | ||
199 | struct LoggingRustIrDatabaseLoggingOnDrop<'a>(LoggingRustIrDatabase<Interner, ChalkContext<'a>>); | ||
200 | |||
201 | impl<'a> Drop for LoggingRustIrDatabaseLoggingOnDrop<'a> { | ||
202 | fn drop(&mut self) { | ||
203 | eprintln!("chalk program:\n{}", self.0); | ||
204 | } | ||
205 | } | ||
206 | |||
191 | fn is_chalk_debug() -> bool { | 207 | fn is_chalk_debug() -> bool { |
192 | std::env::var("CHALK_DEBUG").is_ok() | 208 | std::env::var("CHALK_DEBUG").is_ok() |
193 | } | 209 | } |
diff --git a/crates/hir_ty/src/traits/chalk/mapping.rs b/crates/hir_ty/src/traits/chalk/mapping.rs index be3301313..dd7affcec 100644 --- a/crates/hir_ty/src/traits/chalk/mapping.rs +++ b/crates/hir_ty/src/traits/chalk/mapping.rs | |||
@@ -4,8 +4,8 @@ | |||
4 | //! conversions. | 4 | //! conversions. |
5 | 5 | ||
6 | use chalk_ir::{ | 6 | use chalk_ir::{ |
7 | cast::Cast, fold::shift::Shift, interner::HasInterner, PlaceholderIndex, Scalar, TypeName, | 7 | cast::Cast, fold::shift::Shift, interner::HasInterner, LifetimeData, PlaceholderIndex, Scalar, |
8 | UniverseIndex, | 8 | TypeName, UniverseIndex, |
9 | }; | 9 | }; |
10 | use chalk_solve::rust_ir; | 10 | use chalk_solve::rust_ir; |
11 | 11 | ||
@@ -76,7 +76,7 @@ impl ToChalk for Ty { | |||
76 | ); | 76 | ); |
77 | let bounded_ty = chalk_ir::DynTy { | 77 | let bounded_ty = chalk_ir::DynTy { |
78 | bounds: make_binders(where_clauses, 1), | 78 | bounds: make_binders(where_clauses, 1), |
79 | lifetime: FAKE_PLACEHOLDER.to_lifetime(&Interner), | 79 | lifetime: LifetimeData::Static.intern(&Interner), |
80 | }; | 80 | }; |
81 | chalk_ir::TyData::Dyn(bounded_ty).intern(&Interner) | 81 | chalk_ir::TyData::Dyn(bounded_ty).intern(&Interner) |
82 | } | 82 | } |
@@ -161,9 +161,6 @@ impl ToChalk for Ty { | |||
161 | } | 161 | } |
162 | } | 162 | } |
163 | 163 | ||
164 | const FAKE_PLACEHOLDER: PlaceholderIndex = | ||
165 | PlaceholderIndex { ui: UniverseIndex::ROOT, idx: usize::MAX }; | ||
166 | |||
167 | /// We currently don't model lifetimes, but Chalk does. So, we have to insert a | 164 | /// We currently don't model lifetimes, but Chalk does. So, we have to insert a |
168 | /// fake lifetime here, because Chalks built-in logic may expect it to be there. | 165 | /// fake lifetime here, because Chalks built-in logic may expect it to be there. |
169 | fn ref_to_chalk( | 166 | fn ref_to_chalk( |
@@ -172,7 +169,7 @@ fn ref_to_chalk( | |||
172 | subst: Substs, | 169 | subst: Substs, |
173 | ) -> chalk_ir::Ty<Interner> { | 170 | ) -> chalk_ir::Ty<Interner> { |
174 | let arg = subst[0].clone().to_chalk(db); | 171 | let arg = subst[0].clone().to_chalk(db); |
175 | let lifetime = FAKE_PLACEHOLDER.to_lifetime(&Interner); | 172 | let lifetime = LifetimeData::Static.intern(&Interner); |
176 | chalk_ir::ApplicationTy { | 173 | chalk_ir::ApplicationTy { |
177 | name: TypeName::Ref(mutability.to_chalk(db)), | 174 | name: TypeName::Ref(mutability.to_chalk(db)), |
178 | substitution: chalk_ir::Substitution::from_iter( | 175 | substitution: chalk_ir::Substitution::from_iter( |
@@ -205,7 +202,11 @@ fn array_to_chalk(db: &dyn HirDatabase, subst: Substs) -> chalk_ir::Ty<Interner> | |||
205 | substitution: chalk_ir::Substitution::empty(&Interner), | 202 | substitution: chalk_ir::Substitution::empty(&Interner), |
206 | } | 203 | } |
207 | .intern(&Interner); | 204 | .intern(&Interner); |
208 | let const_ = FAKE_PLACEHOLDER.to_const(&Interner, usize_ty); | 205 | let const_ = chalk_ir::ConstData { |
206 | ty: usize_ty, | ||
207 | value: chalk_ir::ConstValue::Concrete(chalk_ir::ConcreteConst { interned: () }), | ||
208 | } | ||
209 | .intern(&Interner); | ||
209 | chalk_ir::ApplicationTy { | 210 | chalk_ir::ApplicationTy { |
210 | name: TypeName::Array, | 211 | name: TypeName::Array, |
211 | substitution: chalk_ir::Substitution::from_iter( | 212 | substitution: chalk_ir::Substitution::from_iter( |
diff --git a/crates/ide/Cargo.toml b/crates/ide/Cargo.toml index 63299dc31..4d483580d 100644 --- a/crates/ide/Cargo.toml +++ b/crates/ide/Cargo.toml | |||
@@ -11,7 +11,7 @@ doctest = false | |||
11 | 11 | ||
12 | [dependencies] | 12 | [dependencies] |
13 | either = "1.5.3" | 13 | either = "1.5.3" |
14 | indexmap = "1.3.2" | 14 | indexmap = "1.4.0" |
15 | itertools = "0.9.0" | 15 | itertools = "0.9.0" |
16 | log = "0.4.8" | 16 | log = "0.4.8" |
17 | rustc-hash = "1.1.0" | 17 | rustc-hash = "1.1.0" |
@@ -23,14 +23,12 @@ url = "2.1.1" | |||
23 | stdx = { path = "../stdx", version = "0.0.0" } | 23 | stdx = { path = "../stdx", version = "0.0.0" } |
24 | syntax = { path = "../syntax", version = "0.0.0" } | 24 | syntax = { path = "../syntax", version = "0.0.0" } |
25 | text_edit = { path = "../text_edit", version = "0.0.0" } | 25 | text_edit = { path = "../text_edit", version = "0.0.0" } |
26 | base_db = { path = "../base_db", version = "0.0.0" } | ||
27 | ide_db = { path = "../ide_db", version = "0.0.0" } | 26 | ide_db = { path = "../ide_db", version = "0.0.0" } |
28 | cfg = { path = "../cfg", version = "0.0.0" } | 27 | cfg = { path = "../cfg", version = "0.0.0" } |
29 | profile = { path = "../profile", version = "0.0.0" } | 28 | profile = { path = "../profile", version = "0.0.0" } |
30 | test_utils = { path = "../test_utils", version = "0.0.0" } | 29 | test_utils = { path = "../test_utils", version = "0.0.0" } |
31 | assists = { path = "../assists", version = "0.0.0" } | 30 | assists = { path = "../assists", version = "0.0.0" } |
32 | ssr = { path = "../ssr", version = "0.0.0" } | 31 | ssr = { path = "../ssr", version = "0.0.0" } |
33 | call_info = { path = "../call_info", version = "0.0.0" } | ||
34 | completion = { path = "../completion", version = "0.0.0" } | 32 | completion = { path = "../completion", version = "0.0.0" } |
35 | 33 | ||
36 | # ide should depend only on the top-level `hir` package. if you need | 34 | # ide should depend only on the top-level `hir` package. if you need |
diff --git a/crates/ide/src/call_hierarchy.rs b/crates/ide/src/call_hierarchy.rs index 9d6433fe0..8ad50a2ee 100644 --- a/crates/ide/src/call_hierarchy.rs +++ b/crates/ide/src/call_hierarchy.rs | |||
@@ -2,8 +2,8 @@ | |||
2 | 2 | ||
3 | use indexmap::IndexMap; | 3 | use indexmap::IndexMap; |
4 | 4 | ||
5 | use call_info::FnCallNode; | ||
6 | use hir::Semantics; | 5 | use hir::Semantics; |
6 | use ide_db::call_info::FnCallNode; | ||
7 | use ide_db::RootDatabase; | 7 | use ide_db::RootDatabase; |
8 | use syntax::{ast, match_ast, AstNode, TextRange}; | 8 | use syntax::{ast, match_ast, AstNode, TextRange}; |
9 | 9 | ||
@@ -137,7 +137,7 @@ impl CallLocations { | |||
137 | 137 | ||
138 | #[cfg(test)] | 138 | #[cfg(test)] |
139 | mod tests { | 139 | mod tests { |
140 | use base_db::FilePosition; | 140 | use ide_db::base_db::FilePosition; |
141 | 141 | ||
142 | use crate::fixture; | 142 | use crate::fixture; |
143 | 143 | ||
diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs index 90574cb35..d0ee58858 100644 --- a/crates/ide/src/diagnostics.rs +++ b/crates/ide/src/diagnostics.rs | |||
@@ -9,11 +9,11 @@ mod field_shorthand; | |||
9 | 9 | ||
10 | use std::cell::RefCell; | 10 | use std::cell::RefCell; |
11 | 11 | ||
12 | use base_db::SourceDatabase; | ||
13 | use hir::{ | 12 | use hir::{ |
14 | diagnostics::{Diagnostic as _, DiagnosticSinkBuilder}, | 13 | diagnostics::{Diagnostic as _, DiagnosticSinkBuilder}, |
15 | Semantics, | 14 | Semantics, |
16 | }; | 15 | }; |
16 | use ide_db::base_db::SourceDatabase; | ||
17 | use ide_db::RootDatabase; | 17 | use ide_db::RootDatabase; |
18 | use itertools::Itertools; | 18 | use itertools::Itertools; |
19 | use rustc_hash::FxHashSet; | 19 | use rustc_hash::FxHashSet; |
@@ -613,7 +613,7 @@ fn main() { | |||
613 | pub struct Foo { pub a: i32, pub b: i32 } | 613 | pub struct Foo { pub a: i32, pub b: i32 } |
614 | "#, | 614 | "#, |
615 | r#" | 615 | r#" |
616 | fn {a:42, b: ()} {} | 616 | fn some(, b: ()} {} |
617 | fn items() {} | 617 | fn items() {} |
618 | fn here() {} | 618 | fn here() {} |
619 | 619 | ||
diff --git a/crates/ide/src/diagnostics/field_shorthand.rs b/crates/ide/src/diagnostics/field_shorthand.rs index 54e9fce9e..f41bcd619 100644 --- a/crates/ide/src/diagnostics/field_shorthand.rs +++ b/crates/ide/src/diagnostics/field_shorthand.rs | |||
@@ -1,7 +1,7 @@ | |||
1 | //! Suggests shortening `Foo { field: field }` to `Foo { field }` in both | 1 | //! Suggests shortening `Foo { field: field }` to `Foo { field }` in both |
2 | //! expressions and patterns. | 2 | //! expressions and patterns. |
3 | 3 | ||
4 | use base_db::FileId; | 4 | use ide_db::base_db::FileId; |
5 | use ide_db::source_change::SourceFileEdit; | 5 | use ide_db::source_change::SourceFileEdit; |
6 | use syntax::{ast, match_ast, AstNode, SyntaxNode}; | 6 | use syntax::{ast, match_ast, AstNode, SyntaxNode}; |
7 | use text_edit::TextEdit; | 7 | use text_edit::TextEdit; |
diff --git a/crates/ide/src/diagnostics/fixes.rs b/crates/ide/src/diagnostics/fixes.rs index 0c75e50b0..0c950003e 100644 --- a/crates/ide/src/diagnostics/fixes.rs +++ b/crates/ide/src/diagnostics/fixes.rs | |||
@@ -1,6 +1,5 @@ | |||
1 | //! Provides a way to attach fixes to the diagnostics. | 1 | //! Provides a way to attach fixes to the diagnostics. |
2 | //! The same module also has all curret custom fixes for the diagnostics implemented. | 2 | //! The same module also has all curret custom fixes for the diagnostics implemented. |
3 | use base_db::FileId; | ||
4 | use hir::{ | 3 | use hir::{ |
5 | db::AstDatabase, | 4 | db::AstDatabase, |
6 | diagnostics::{ | 5 | diagnostics::{ |
@@ -9,6 +8,7 @@ use hir::{ | |||
9 | }, | 8 | }, |
10 | HasSource, HirDisplay, Semantics, VariantDef, | 9 | HasSource, HirDisplay, Semantics, VariantDef, |
11 | }; | 10 | }; |
11 | use ide_db::base_db::FileId; | ||
12 | use ide_db::{ | 12 | use ide_db::{ |
13 | source_change::{FileSystemEdit, SourceFileEdit}, | 13 | source_change::{FileSystemEdit, SourceFileEdit}, |
14 | RootDatabase, | 14 | RootDatabase, |
diff --git a/crates/ide/src/display/navigation_target.rs b/crates/ide/src/display/navigation_target.rs index cf9d617dc..0c429a262 100644 --- a/crates/ide/src/display/navigation_target.rs +++ b/crates/ide/src/display/navigation_target.rs | |||
@@ -1,8 +1,8 @@ | |||
1 | //! FIXME: write short doc here | 1 | //! FIXME: write short doc here |
2 |