aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to 'crates')
-rw-r--r--crates/assists/Cargo.toml1
-rw-r--r--crates/assists/src/assist_context.rs2
-rw-r--r--crates/assists/src/handlers/add_missing_impl_members.rs3
-rw-r--r--crates/assists/src/handlers/extract_struct_from_enum_variant.rs2
-rw-r--r--crates/assists/src/handlers/fix_visibility.rs2
-rw-r--r--crates/assists/src/handlers/generate_function.rs2
-rw-r--r--crates/assists/src/handlers/remove_dbg.rs87
-rw-r--r--crates/assists/src/handlers/replace_if_let_with_match.rs6
-rw-r--r--crates/assists/src/handlers/replace_let_with_if_let.rs3
-rw-r--r--crates/assists/src/handlers/replace_unwrap_with_match.rs3
-rw-r--r--crates/assists/src/lib.rs2
-rw-r--r--crates/assists/src/tests.rs2
-rw-r--r--crates/assists/src/utils.rs121
-rw-r--r--crates/assists/src/utils/insert_use.rs166
-rw-r--r--crates/call_info/Cargo.toml26
-rw-r--r--crates/cfg/Cargo.toml1
-rw-r--r--crates/cfg/src/cfg_expr.rs113
-rw-r--r--crates/cfg/src/dnf.rs320
-rw-r--r--crates/cfg/src/lib.rs130
-rw-r--r--crates/cfg/src/tests.rs193
-rw-r--r--crates/completion/Cargo.toml2
-rw-r--r--crates/completion/src/complete_mod.rs2
-rw-r--r--crates/completion/src/complete_postfix.rs2
-rw-r--r--crates/completion/src/complete_trait_impl.rs2
-rw-r--r--crates/completion/src/completion_context.rs5
-rw-r--r--crates/completion/src/lib.rs2
-rw-r--r--crates/completion/src/presentation.rs70
-rw-r--r--crates/completion/src/test_utils.rs2
-rw-r--r--crates/hir/src/code_model.rs26
-rw-r--r--crates/hir_def/src/attr.rs14
-rw-r--r--crates/hir_def/src/body.rs63
-rw-r--r--crates/hir_def/src/body/diagnostics.rs20
-rw-r--r--crates/hir_def/src/body/lower.rs60
-rw-r--r--crates/hir_def/src/body/tests.rs75
-rw-r--r--crates/hir_def/src/diagnostics.rs28
-rw-r--r--crates/hir_def/src/nameres.rs16
-rw-r--r--crates/hir_def/src/nameres/collector.rs29
-rw-r--r--crates/hir_def/src/nameres/tests/diagnostics.rs56
-rw-r--r--crates/hir_def/src/test_db.rs44
-rw-r--r--crates/hir_ty/Cargo.toml6
-rw-r--r--crates/hir_ty/src/diagnostics/decl_check/case_conv.rs249
-rw-r--r--crates/hir_ty/src/traits.rs30
-rw-r--r--crates/hir_ty/src/traits/chalk/mapping.rs17
-rw-r--r--crates/ide/Cargo.toml4
-rw-r--r--crates/ide/src/call_hierarchy.rs4
-rw-r--r--crates/ide/src/diagnostics.rs4
-rw-r--r--crates/ide/src/diagnostics/field_shorthand.rs2
-rw-r--r--crates/ide/src/diagnostics/fixes.rs2
-rw-r--r--crates/ide/src/display/navigation_target.rs2
-rw-r--r--crates/ide/src/fixture.rs2
-rw-r--r--crates/ide/src/goto_definition.rs2
-rw-r--r--crates/ide/src/goto_implementation.rs2
-rw-r--r--crates/ide/src/goto_type_definition.rs2
-rw-r--r--crates/ide/src/hover.rs35
-rw-r--r--crates/ide/src/lib.rs12
-rw-r--r--crates/ide/src/parent_module.rs2
-rw-r--r--crates/ide/src/prime_caches.rs2
-rw-r--r--crates/ide/src/references.rs2
-rw-r--r--crates/ide/src/references/rename.rs2
-rw-r--r--crates/ide/src/runnables.rs84
-rw-r--r--crates/ide/src/status.rs6
-rw-r--r--crates/ide/src/syntax_highlighting.rs12
-rw-r--r--crates/ide/src/syntax_highlighting/html.rs2
-rw-r--r--crates/ide/src/syntax_highlighting/injection.rs2
-rw-r--r--crates/ide/src/syntax_highlighting/tags.rs3
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlighting.html22
-rw-r--r--crates/ide/src/syntax_highlighting/tests.rs22
-rw-r--r--crates/ide/src/syntax_tree.rs2
-rw-r--r--crates/ide/src/typing.rs2
-rw-r--r--crates/ide/src/typing/on_enter.rs2
-rw-r--r--crates/ide_db/Cargo.toml5
-rw-r--r--crates/ide_db/src/call_info.rs (renamed from crates/call_info/src/lib.rs)7
-rw-r--r--crates/ide_db/src/lib.rs6
-rw-r--r--crates/ide_db/src/traits.rs227
-rw-r--r--crates/ide_db/src/ty_filter.rs58
-rw-r--r--crates/rust-analyzer/Cargo.toml6
-rw-r--r--crates/rust-analyzer/src/bin/main.rs22
-rw-r--r--crates/rust-analyzer/src/cargo_target_spec.rs10
-rw-r--r--crates/rust-analyzer/src/cli/analysis_bench.rs8
-rw-r--r--crates/rust-analyzer/src/cli/analysis_stats.rs8
-rw-r--r--crates/rust-analyzer/src/cli/diagnostics.rs2
-rw-r--r--crates/rust-analyzer/src/cli/load_cargo.rs2
-rw-r--r--crates/rust-analyzer/src/cli/ssr.rs4
-rw-r--r--crates/rust-analyzer/src/dispatch.rs6
-rw-r--r--crates/rust-analyzer/src/from_proto.rs2
-rw-r--r--crates/rust-analyzer/src/global_state.rs2
-rw-r--r--crates/rust-analyzer/src/lsp_utils.rs2
-rw-r--r--crates/rust-analyzer/src/main_loop.rs2
-rw-r--r--crates/rust-analyzer/src/reload.rs2
-rw-r--r--crates/rust-analyzer/src/semantic_tokens.rs1
-rw-r--r--crates/rust-analyzer/src/to_proto.rs7
-rw-r--r--crates/ssr/Cargo.toml1
-rw-r--r--crates/ssr/src/lib.rs8
-rw-r--r--crates/ssr/src/matching.rs2
-rw-r--r--crates/ssr/src/resolving.rs2
-rw-r--r--crates/ssr/src/search.rs4
-rw-r--r--crates/ssr/src/tests.rs6
-rw-r--r--crates/stdx/src/panic_context.rs4
-rw-r--r--crates/syntax/Cargo.toml6
-rw-r--r--crates/syntax/src/algo.rs582
-rw-r--r--crates/syntax/src/ast/node_ext.rs14
-rw-r--r--crates/test_utils/src/lib.rs6
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" }
18syntax = { path = "../syntax", version = "0.0.0" } 18syntax = { path = "../syntax", version = "0.0.0" }
19text_edit = { path = "../text_edit", version = "0.0.0" } 19text_edit = { path = "../text_edit", version = "0.0.0" }
20profile = { path = "../profile", version = "0.0.0" } 20profile = { path = "../profile", version = "0.0.0" }
21base_db = { path = "../base_db", version = "0.0.0" }
22ide_db = { path = "../ide_db", version = "0.0.0" } 21ide_db = { path = "../ide_db", version = "0.0.0" }
23hir = { path = "../hir", version = "0.0.0" } 22hir = { path = "../hir", version = "0.0.0" }
24test_utils = { path = "../test_utils", version = "0.0.0" } 23test_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 @@
3use std::mem; 3use std::mem;
4 4
5use algo::find_covering_element; 5use algo::find_covering_element;
6use base_db::{FileId, FileRange};
7use hir::Semantics; 6use hir::Semantics;
7use ide_db::base_db::{FileId, FileRange};
8use ide_db::{ 8use 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 @@
1use hir::HasSource; 1use hir::HasSource;
2use ide_db::traits::{get_missing_assoc_items, resolve_target_trait};
2use syntax::{ 3use syntax::{
3 ast::{ 4 ast::{
4 self, 5 self,
@@ -11,7 +12,7 @@ use syntax::{
11use crate::{ 12use 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 @@
1use base_db::FileId;
2use hir::{EnumVariant, Module, ModuleDef, Name}; 1use hir::{EnumVariant, Module, ModuleDef, Name};
2use ide_db::base_db::FileId;
3use ide_db::{defs::Definition, search::Reference, RootDatabase}; 3use ide_db::{defs::Definition, search::Reference, RootDatabase};
4use itertools::Itertools; 4use itertools::Itertools;
5use rustc_hash::FxHashSet; 5use 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 @@
1use base_db::FileId;
2use hir::{db::HirDatabase, HasSource, HasVisibility, PathResolution}; 1use hir::{db::HirDatabase, HasSource, HasVisibility, PathResolution};
2use ide_db::base_db::FileId;
3use syntax::{ 3use 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 @@
1use base_db::FileId;
2use hir::HirDisplay; 1use hir::HirDisplay;
2use ide_db::base_db::FileId;
3use rustc_hash::{FxHashMap, FxHashSet}; 3use rustc_hash::{FxHashMap, FxHashSet};
4use syntax::{ 4use 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
10use crate::{ 10use crate::{utils::unwrap_trivial_block, AssistContext, AssistId, AssistKind, Assists};
11 utils::{unwrap_trivial_block, TryEnum}, 11use 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
12use crate::{utils::TryEnum, AssistContext, AssistId, AssistKind, Assists}; 12use crate::{AssistContext, AssistId, AssistKind, Assists};
13use 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
12use crate::{ 12use crate::{
13 utils::{render_snippet, Cursor, TryEnum}, 13 utils::{render_snippet, Cursor},
14 AssistContext, AssistId, AssistKind, Assists, 14 AssistContext, AssistId, AssistKind, Assists,
15}; 15};
16use 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;
17pub mod utils; 17pub mod utils;
18pub mod ast_transform; 18pub mod ast_transform;
19 19
20use base_db::FileRange;
21use hir::Semantics; 20use hir::Semantics;
21use ide_db::base_db::FileRange;
22use ide_db::{label::Label, source_change::SourceChange, RootDatabase}; 22use ide_db::{label::Label, source_change::SourceChange, RootDatabase};
23use syntax::TextRange; 23use 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 @@
1mod generated; 1mod generated;
2 2
3use base_db::{fixture::WithFixture, FileId, FileRange, SourceDatabaseExt};
4use hir::Semantics; 3use hir::Semantics;
4use ide_db::base_db::{fixture::WithFixture, FileId, FileRange, SourceDatabaseExt};
5use ide_db::RootDatabase; 5use ide_db::RootDatabase;
6use syntax::TextRange; 6use syntax::TextRange;
7use test_utils::{assert_eq_text, extract_offset, extract_range}; 7use 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 @@
2pub(crate) mod insert_use; 2pub(crate) mod insert_use;
3pub(crate) mod import_assets; 3pub(crate) mod import_assets;
4 4
5use std::{iter, ops}; 5use std::ops;
6 6
7use hir::{Adt, Crate, Enum, Module, ScopeDef, Semantics, Trait, Type}; 7use hir::{Crate, Enum, Module, ScopeDef, Semantics, Trait};
8use ide_db::RootDatabase; 8use ide_db::RootDatabase;
9use itertools::Itertools; 9use itertools::Itertools;
10use rustc_hash::FxHashSet;
11use syntax::{ 10use 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
118pub 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
171pub(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
184pub(crate) fn vis_offset(node: &SyntaxNode) -> TextSize { 117pub(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)]
227pub enum TryEnum {
228 Result,
229 Option,
230}
231
232impl 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};
17use test_utils::mark;
17 18
18#[derive(Debug)] 19#[derive(Debug)]
19pub enum ImportScope { 20pub 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
494impl 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
473fn find_insert_position( 503fn 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"
623use std::bar::A; 705 use std::bar::A;
624use std::bar::D; 706 use std::bar::D;
625 707
626use foo::bar::F; 708 use foo::bar::F;
627use foo::bar::H;", 709 use foo::bar::H;",
628 r" 710 r"
629use std::bar::A; 711 use std::bar::A;
630use std::bar::D; 712 use std::bar::D;
631 713
632use foo::bar::F; 714 use foo::bar::F;
633use foo::bar::GG; 715 use foo::bar::GG;
634use 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"
643use foo::bar::A; 725 use foo::bar::A;
644use foo::bar::D; 726 use foo::bar::D;
645 727
646use std; 728 use std;
647 729
648use foo::bar::F; 730 use foo::bar::F;
649use foo::bar::H;", 731 use foo::bar::H;",
650 r" 732 r"
651use foo::bar::A; 733 use foo::bar::A;
652use foo::bar::D; 734 use foo::bar::D;
653use foo::bar::GG; 735 use foo::bar::GG;
654 736
655use std; 737 use std;
656 738
657use foo::bar::F; 739 use foo::bar::F;
658use 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"
667use foo::bar::A; 749 use foo::bar::A;
668use foo::bar::D;", 750 use foo::bar::D;",
669 r" 751 r"
670use std::fmt; 752 use std::fmt;
671 753
672use foo::bar::A; 754 use foo::bar::A;
673use 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]
2name = "call_info"
3version = "0.0.0"
4description = "TBD"
5license = "MIT OR Apache-2.0"
6authors = ["rust-analyzer developers"]
7edition = "2018"
8
9[lib]
10doctest = false
11
12[dependencies]
13either = "1.5.3"
14
15stdx = { path = "../stdx", version = "0.0.0" }
16syntax = { path = "../syntax", version = "0.0.0" }
17base_db = { path = "../base_db", version = "0.0.0" }
18ide_db = { path = "../ide_db", version = "0.0.0" }
19test_utils = { path = "../test_utils", version = "0.0.0" }
20
21# call_info crate should depend only on the top-level `hir` package. if you need
22# something from some `hir_xxx` subpackage, reexport the API via `hir`.
23hir = { path = "../hir", version = "0.0.0" }
24
25[dev-dependencies]
26expect-test = "1.0"
diff --git a/crates/cfg/Cargo.toml b/crates/cfg/Cargo.toml
index a6785ee8e..c68e391c1 100644
--- a/crates/cfg/Cargo.toml
+++ b/crates/cfg/Cargo.toml
@@ -17,3 +17,4 @@ tt = { path = "../tt", version = "0.0.0" }
17[dev-dependencies] 17[dev-dependencies]
18mbe = { path = "../mbe" } 18mbe = { path = "../mbe" }
19syntax = { path = "../syntax" } 19syntax = { path = "../syntax" }
20expect-test = "1.0"
diff --git a/crates/cfg/src/cfg_expr.rs b/crates/cfg/src/cfg_expr.rs
index 336fe25bc..42327f1e1 100644
--- a/crates/cfg/src/cfg_expr.rs
+++ b/crates/cfg/src/cfg_expr.rs
@@ -2,30 +2,77 @@
2//! 2//!
3//! See: https://doc.rust-lang.org/reference/conditional-compilation.html#conditional-compilation 3//! See: https://doc.rust-lang.org/reference/conditional-compilation.html#conditional-compilation
4 4
5use std::slice::Iter as SliceIter; 5use std::{fmt, slice::Iter as SliceIter};
6 6
7use tt::SmolStr; 7use tt::SmolStr;
8 8
9/// A simple configuration value passed in from the outside.
10#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
11pub enum CfgAtom {
12 /// eg. `#[cfg(test)]`
13 Flag(SmolStr),
14 /// eg. `#[cfg(target_os = "linux")]`
15 ///
16 /// Note that a key can have multiple values that are all considered "active" at the same time.
17 /// For example, `#[cfg(target_feature = "sse")]` and `#[cfg(target_feature = "sse2")]`.
18 KeyValue { key: SmolStr, value: SmolStr },
19}
20
21impl CfgAtom {
22 /// Returns `true` when the atom comes from the target specification.
23 ///
24 /// If this returns `true`, then changing this atom requires changing the compilation target. If
25 /// it returns `false`, the atom might come from a build script or the build system.
26 pub fn is_target_defined(&self) -> bool {
27 match self {
28 CfgAtom::Flag(flag) => matches!(&**flag, "unix" | "windows"),
29 CfgAtom::KeyValue { key, value: _ } => matches!(
30 &**key,
31 "target_arch"
32 | "target_os"
33 | "target_env"
34 | "target_family"
35 | "target_endian"
36 | "target_pointer_width"
37 | "target_vendor" // NOTE: `target_feature` is left out since it can be configured via `-Ctarget-feature`
38 ),
39 }
40 }
41}
42
43impl fmt::Display for CfgAtom {
44 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45 match self {
46 CfgAtom::Flag(name) => write!(f, "{}", name),
47 CfgAtom::KeyValue { key, value } => write!(f, "{} = {:?}", key, value),
48 }
49 }
50}
51
9#[derive(Debug, Clone, PartialEq, Eq)] 52#[derive(Debug, Clone, PartialEq, Eq)]
10pub enum CfgExpr { 53pub enum CfgExpr {
11 Invalid, 54 Invalid,
12 Atom(SmolStr), 55 Atom(CfgAtom),
13 KeyValue { key: SmolStr, value: SmolStr },
14 All(Vec<CfgExpr>), 56 All(Vec<CfgExpr>),
15 Any(Vec<CfgExpr>), 57 Any(Vec<CfgExpr>),
16 Not(Box<CfgExpr>), 58 Not(Box<CfgExpr>),
17} 59}
18 60
61impl From<CfgAtom> for CfgExpr {
62 fn from(atom: CfgAtom) -> Self {
63 CfgExpr::Atom(atom)
64 }
65}
66
19impl CfgExpr { 67impl CfgExpr {
20 pub fn parse(tt: &tt::Subtree) -> CfgExpr { 68 pub fn parse(tt: &tt::Subtree) -> CfgExpr {
21 next_cfg_expr(&mut tt.token_trees.iter()).unwrap_or(CfgExpr::Invalid) 69 next_cfg_expr(&mut tt.token_trees.iter()).unwrap_or(CfgExpr::Invalid)
22 } 70 }
23 /// Fold the cfg by querying all basic `Atom` and `KeyValue` predicates. 71 /// Fold the cfg by querying all basic `Atom` and `KeyValue` predicates.
24 pub fn fold(&self, query: &dyn Fn(&SmolStr, Option<&SmolStr>) -> bool) -> Option<bool> { 72 pub fn fold(&self, query: &dyn Fn(&CfgAtom) -> bool) -> Option<bool> {
25 match self { 73 match self {
26 CfgExpr::Invalid => None, 74 CfgExpr::Invalid => None,
27 CfgExpr::Atom(name) => Some(query(name, None)), 75 CfgExpr::Atom(atom) => Some(query(atom)),
28 CfgExpr::KeyValue { key, value } => Some(query(key, Some(value))),
29 CfgExpr::All(preds) => { 76 CfgExpr::All(preds) => {
30 preds.iter().try_fold(true, |s, pred| Some(s && pred.fold(query)?)) 77 preds.iter().try_fold(true, |s, pred| Some(s && pred.fold(query)?))
31 } 78 }
@@ -54,7 +101,7 @@ fn next_cfg_expr(it: &mut SliceIter<tt::TokenTree>) -> Option<CfgExpr> {
54 // FIXME: escape? raw string? 101 // FIXME: escape? raw string?
55 let value = 102 let value =
56 SmolStr::new(literal.text.trim_start_matches('"').trim_end_matches('"')); 103 SmolStr::new(literal.text.trim_start_matches('"').trim_end_matches('"'));
57 CfgExpr::KeyValue { key: name, value } 104 CfgAtom::KeyValue { key: name, value }.into()
58 } 105 }
59 _ => return Some(CfgExpr::Invalid), 106 _ => return Some(CfgExpr::Invalid),
60 } 107 }
@@ -70,7 +117,7 @@ fn next_cfg_expr(it: &mut SliceIter<tt::TokenTree>) -> Option<CfgExpr> {
70 _ => CfgExpr::Invalid, 117 _ => CfgExpr::Invalid,
71 } 118 }
72 } 119 }
73 _ => CfgExpr::Atom(name), 120 _ => CfgAtom::Flag(name).into(),
74 }; 121 };
75 122
76 // Eat comma separator 123 // Eat comma separator
@@ -81,53 +128,3 @@ fn next_cfg_expr(it: &mut SliceIter<tt::TokenTree>) -> Option<CfgExpr> {
81 } 128 }
82 Some(ret) 129 Some(ret)
83} 130}
84
85#[cfg(test)]
86mod tests {
87 use super::*;
88
89 use mbe::ast_to_token_tree;
90 use syntax::ast::{self, AstNode};
91
92 fn assert_parse_result(input: &str, expected: CfgExpr) {
93 let (tt, _) = {
94 let source_file = ast::SourceFile::parse(input).ok().unwrap();
95 let tt = source_file.syntax().descendants().find_map(ast::TokenTree::cast).unwrap();
96 ast_to_token_tree(&tt).unwrap()
97 };
98 let cfg = CfgExpr::parse(&tt);
99 assert_eq!(cfg, expected);
100 }
101
102 #[test]
103 fn test_cfg_expr_parser() {
104 assert_parse_result("#![cfg(foo)]", CfgExpr::Atom("foo".into()));
105 assert_parse_result("#![cfg(foo,)]", CfgExpr::Atom("foo".into()));
106 assert_parse_result(
107 "#![cfg(not(foo))]",
108 CfgExpr::Not(Box::new(CfgExpr::Atom("foo".into()))),
109 );
110 assert_parse_result("#![cfg(foo(bar))]", CfgExpr::Invalid);
111
112 // Only take the first
113 assert_parse_result(r#"#![cfg(foo, bar = "baz")]"#, CfgExpr::Atom("foo".into()));
114
115 assert_parse_result(
116 r#"#![cfg(all(foo, bar = "baz"))]"#,
117 CfgExpr::All(vec![
118 CfgExpr::Atom("foo".into()),
119 CfgExpr::KeyValue { key: "bar".into(), value: "baz".into() },
120 ]),
121 );
122
123 assert_parse_result(
124 r#"#![cfg(any(not(), all(), , bar = "baz",))]"#,
125 CfgExpr::Any(vec![
126 CfgExpr::Not(Box::new(CfgExpr::Invalid)),
127 CfgExpr::All(vec![]),
128 CfgExpr::Invalid,
129 CfgExpr::KeyValue { key: "bar".into(), value: "baz".into() },
130 ]),
131 );
132 }
133}
diff --git a/crates/cfg/src/dnf.rs b/crates/cfg/src/dnf.rs
new file mode 100644
index 000000000..580c9a9a2
--- /dev/null
+++ b/crates/cfg/src/dnf.rs
@@ -0,0 +1,320 @@
1//! Disjunctive Normal Form construction.
2//!
3//! Algorithm from <https://www.cs.drexel.edu/~jjohnson/2015-16/fall/CS270/Lectures/3/dnf.pdf>,
4//! which would have been much easier to read if it used pattern matching. It's also missing the
5//! entire "distribute ANDs over ORs" part, which is not trivial. Oh well.
6//!
7//! This is currently both messy and inefficient. Feel free to improve, there are unit tests.
8
9use std::fmt;
10
11use rustc_hash::FxHashSet;
12
13use crate::{CfgAtom, CfgDiff, CfgExpr, CfgOptions, InactiveReason};
14
15/// A `#[cfg]` directive in Disjunctive Normal Form (DNF).
16pub struct DnfExpr {
17 conjunctions: Vec<Conjunction>,
18}
19
20struct Conjunction {
21 literals: Vec<Literal>,
22}
23
24struct Literal {
25 negate: bool,
26 var: Option<CfgAtom>, // None = Invalid
27}
28
29impl DnfExpr {
30 pub fn new(expr: CfgExpr) -> Self {
31 let builder = Builder { expr: DnfExpr { conjunctions: Vec::new() } };
32
33 builder.lower(expr.clone())
34 }
35
36 /// Computes a list of present or absent atoms in `opts` that cause this expression to evaluate
37 /// to `false`.
38 ///
39 /// Note that flipping a subset of these atoms might be sufficient to make the whole expression
40 /// evaluate to `true`. For that, see `compute_enable_hints`.
41 ///
42 /// Returns `None` when `self` is already true, or contains errors.
43 pub fn why_inactive(&self, opts: &CfgOptions) -> Option<InactiveReason> {
44 let mut res = InactiveReason { enabled: Vec::new(), disabled: Vec::new() };
45
46 for conj in &self.conjunctions {
47 let mut conj_is_true = true;
48 for lit in &conj.literals {
49 let atom = lit.var.as_ref()?;
50 let enabled = opts.enabled.contains(atom);
51 if lit.negate == enabled {
52 // Literal is false, but needs to be true for this conjunction.
53 conj_is_true = false;
54
55 if enabled {
56 res.enabled.push(atom.clone());
57 } else {
58 res.disabled.push(atom.clone());
59 }
60 }
61 }
62
63 if conj_is_true {
64 // This expression is not actually inactive.
65 return None;
66 }
67 }
68
69 res.enabled.sort_unstable();
70 res.enabled.dedup();
71 res.disabled.sort_unstable();
72 res.disabled.dedup();
73 Some(res)
74 }
75
76 /// Returns `CfgDiff` objects that would enable this directive if applied to `opts`.
77 pub fn compute_enable_hints<'a>(
78 &'a self,
79 opts: &'a CfgOptions,
80 ) -> impl Iterator<Item = CfgDiff> + 'a {
81 // A cfg is enabled if any of `self.conjunctions` evaluate to `true`.
82
83 self.conjunctions.iter().filter_map(move |conj| {
84 let mut enable = FxHashSet::default();
85 let mut disable = FxHashSet::default();
86 for lit in &conj.literals {
87 let atom = lit.var.as_ref()?;
88 let enabled = opts.enabled.contains(atom);
89 if lit.negate && enabled {
90 disable.insert(atom.clone());
91 }
92 if !lit.negate && !enabled {
93 enable.insert(atom.clone());
94 }
95 }
96
97 // Check that this actually makes `conj` true.
98 for lit in &conj.literals {
99 let atom = lit.var.as_ref()?;
100 let enabled = enable.contains(atom)
101 || (opts.enabled.contains(atom) && !disable.contains(atom));
102 if enabled == lit.negate {
103 return None;
104 }
105 }
106
107 if enable.is_empty() && disable.is_empty() {
108 return None;
109 }
110
111 let mut diff = CfgDiff {
112 enable: enable.into_iter().collect(),
113 disable: disable.into_iter().collect(),
114 };
115
116 // Undo the FxHashMap randomization for consistent output.
117 diff.enable.sort_unstable();
118 diff.disable.sort_unstable();
119
120 Some(diff)
121 })
122 }
123}
124
125impl fmt::Display for DnfExpr {
126 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
127 if self.conjunctions.len() != 1 {
128 write!(f, "any(")?;
129 }
130 for (i, conj) in self.conjunctions.iter().enumerate() {
131 if i != 0 {
132 f.write_str(", ")?;
133 }
134
135 write!(f, "{}", conj)?;
136 }
137 if self.conjunctions.len() != 1 {
138 write!(f, ")")?;
139 }
140
141 Ok(())
142 }
143}
144
145impl Conjunction {
146 fn new(parts: Vec<CfgExpr>) -> Self {
147 let mut literals = Vec::new();
148 for part in parts {
149 match part {
150 CfgExpr::Invalid | CfgExpr::Atom(_) | CfgExpr::Not(_) => {
151 literals.push(Literal::new(part));
152 }
153 CfgExpr::All(conj) => {
154 // Flatten.
155 literals.extend(Conjunction::new(conj).literals);
156 }
157 CfgExpr::Any(_) => unreachable!("disjunction in conjunction"),
158 }
159 }
160
161 Self { literals }
162 }
163}
164
165impl fmt::Display for Conjunction {
166 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
167 if self.literals.len() != 1 {
168 write!(f, "all(")?;
169 }
170 for (i, lit) in self.literals.iter().enumerate() {
171 if i != 0 {
172 f.write_str(", ")?;
173 }
174
175 write!(f, "{}", lit)?;
176 }
177 if self.literals.len() != 1 {
178 write!(f, ")")?;
179 }
180
181 Ok(())
182 }
183}
184
185impl Literal {
186 fn new(expr: CfgExpr) -> Self {
187 match expr {
188 CfgExpr::Invalid => Self { negate: false, var: None },
189 CfgExpr::Atom(atom) => Self { negate: false, var: Some(atom) },
190 CfgExpr::Not(expr) => match *expr {
191 CfgExpr::Invalid => Self { negate: true, var: None },
192 CfgExpr::Atom(atom) => Self { negate: true, var: Some(atom) },
193 _ => unreachable!("non-atom {:?}", expr),
194 },
195 CfgExpr::Any(_) | CfgExpr::All(_) => unreachable!("non-literal {:?}", expr),
196 }
197 }
198}
199
200impl fmt::Display for Literal {
201 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
202 if self.negate {
203 write!(f, "not(")?;
204 }
205
206 match &self.var {
207 Some(var) => write!(f, "{}", var)?,
208 None => f.write_str("<invalid>")?,
209 }
210
211 if self.negate {
212 write!(f, ")")?;
213 }
214
215 Ok(())
216 }
217}
218
219struct Builder {
220 expr: DnfExpr,
221}
222
223impl Builder {
224 fn lower(mut self, expr: CfgExpr) -> DnfExpr {
225 let expr = make_nnf(expr);
226 let expr = make_dnf(expr);
227
228 match expr {
229 CfgExpr::Invalid | CfgExpr::Atom(_) | CfgExpr::Not(_) => {
230 self.expr.conjunctions.push(Conjunction::new(vec![expr]));
231 }
232 CfgExpr::All(conj) => {
233 self.expr.conjunctions.push(Conjunction::new(conj));
234 }
235 CfgExpr::Any(mut disj) => {
236 disj.reverse();
237 while let Some(conj) = disj.pop() {
238 match conj {
239 CfgExpr::Invalid | CfgExpr::Atom(_) | CfgExpr::All(_) | CfgExpr::Not(_) => {
240 self.expr.conjunctions.push(Conjunction::new(vec![conj]));
241 }
242 CfgExpr::Any(inner_disj) => {
243 // Flatten.
244 disj.extend(inner_disj.into_iter().rev());
245 }
246 }
247 }
248 }
249 }
250
251 self.expr
252 }
253}
254
255fn make_dnf(expr: CfgExpr) -> CfgExpr {
256 match expr {
257 CfgExpr::Invalid | CfgExpr::Atom(_) | CfgExpr::Not(_) => expr,
258 CfgExpr::Any(e) => CfgExpr::Any(e.into_iter().map(|expr| make_dnf(expr)).collect()),
259 CfgExpr::All(e) => {
260 let e = e.into_iter().map(|expr| make_nnf(expr)).collect::<Vec<_>>();
261
262 CfgExpr::Any(distribute_conj(&e))
263 }
264 }
265}
266
267/// Turns a conjunction of expressions into a disjunction of expressions.
268fn distribute_conj(conj: &[CfgExpr]) -> Vec<CfgExpr> {
269 fn go(out: &mut Vec<CfgExpr>, with: &mut Vec<CfgExpr>, rest: &[CfgExpr]) {
270 match rest {
271 [head, tail @ ..] => match head {
272 CfgExpr::Any(disj) => {
273 for part in disj {
274 with.push(part.clone());
275 go(out, with, tail);
276 with.pop();
277 }
278 }
279 _ => {
280 with.push(head.clone());
281 go(out, with, tail);
282 with.pop();
283 }
284 },
285 _ => {
286 // Turn accumulated parts into a new conjunction.
287 out.push(CfgExpr::All(with.clone()));
288 }
289 }
290 }
291
292 let mut out = Vec::new();
293 let mut with = Vec::new();
294
295 go(&mut out, &mut with, conj);
296
297 out
298}
299
300fn make_nnf(expr: CfgExpr) -> CfgExpr {
301 match expr {
302 CfgExpr::Invalid | CfgExpr::Atom(_) => expr,
303 CfgExpr::Any(expr) => CfgExpr::Any(expr.into_iter().map(|expr| make_nnf(expr)).collect()),
304 CfgExpr::All(expr) => CfgExpr::All(expr.into_iter().map(|expr| make_nnf(expr)).collect()),
305 CfgExpr::Not(operand) => match *operand {
306 CfgExpr::Invalid | CfgExpr::Atom(_) => CfgExpr::Not(operand.clone()), // Original negated expr
307 CfgExpr::Not(expr) => {
308 // Remove double negation.
309 make_nnf(*expr)
310 }
311 // Convert negated conjunction/disjunction using DeMorgan's Law.
312 CfgExpr::Any(inner) => CfgExpr::All(
313 inner.into_iter().map(|expr| make_nnf(CfgExpr::Not(Box::new(expr)))).collect(),
314 ),
315 CfgExpr::All(inner) => CfgExpr::Any(
316 inner.into_iter().map(|expr| make_nnf(CfgExpr::Not(Box::new(expr)))).collect(),
317 ),
318 },
319 }
320}
diff --git a/crates/cfg/src/lib.rs b/crates/cfg/src/lib.rs
index a9d50e698..d0e08cf5f 100644
--- a/crates/cfg/src/lib.rs
+++ b/crates/cfg/src/lib.rs
@@ -1,11 +1,17 @@
1//! cfg defines conditional compiling options, `cfg` attibute parser and evaluator 1//! cfg defines conditional compiling options, `cfg` attibute parser and evaluator
2 2
3mod cfg_expr; 3mod cfg_expr;
4mod dnf;
5#[cfg(test)]
6mod tests;
7
8use std::fmt;
4 9
5use rustc_hash::FxHashSet; 10use rustc_hash::FxHashSet;
6use tt::SmolStr; 11use tt::SmolStr;
7 12
8pub use cfg_expr::CfgExpr; 13pub use cfg_expr::{CfgAtom, CfgExpr};
14pub use dnf::DnfExpr;
9 15
10/// Configuration options used for conditional compilition on items with `cfg` attributes. 16/// Configuration options used for conditional compilition on items with `cfg` attributes.
11/// We have two kind of options in different namespaces: atomic options like `unix`, and 17/// We have two kind of options in different namespaces: atomic options like `unix`, and
@@ -19,33 +25,131 @@ pub use cfg_expr::CfgExpr;
19/// See: https://doc.rust-lang.org/reference/conditional-compilation.html#set-configuration-options 25/// See: https://doc.rust-lang.org/reference/conditional-compilation.html#set-configuration-options
20#[derive(Debug, Clone, PartialEq, Eq, Default)] 26#[derive(Debug, Clone, PartialEq, Eq, Default)]
21pub struct CfgOptions { 27pub struct CfgOptions {
22 atoms: FxHashSet<SmolStr>, 28 enabled: FxHashSet<CfgAtom>,
23 key_values: FxHashSet<(SmolStr, SmolStr)>,
24} 29}
25 30
26impl CfgOptions { 31impl CfgOptions {
27 pub fn check(&self, cfg: &CfgExpr) -> Option<bool> { 32 pub fn check(&self, cfg: &CfgExpr) -> Option<bool> {
28 cfg.fold(&|key, value| match value { 33 cfg.fold(&|atom| self.enabled.contains(atom))
29 None => self.atoms.contains(key),
30 Some(value) => self.key_values.contains(&(key.clone(), value.clone())),
31 })
32 } 34 }
33 35
34 pub fn insert_atom(&mut self, key: SmolStr) { 36 pub fn insert_atom(&mut self, key: SmolStr) {
35 self.atoms.insert(key); 37 self.enabled.insert(CfgAtom::Flag(key));
36 } 38 }
37 39
38 pub fn insert_key_value(&mut self, key: SmolStr, value: SmolStr) { 40 pub fn insert_key_value(&mut self, key: SmolStr, value: SmolStr) {
39 self.key_values.insert((key, value)); 41 self.enabled.insert(CfgAtom::KeyValue { key, value });
40 } 42 }
41 43
42 pub fn append(&mut self, other: &CfgOptions) { 44 pub fn append(&mut self, other: &CfgOptions) {
43 for atom in &other.atoms { 45 for atom in &other.enabled {
44 self.atoms.insert(atom.clone()); 46 self.enabled.insert(atom.clone());
47 }
48 }
49
50 pub fn apply_diff(&mut self, diff: CfgDiff) {
51 for atom in diff.enable {
52 self.enabled.insert(atom);
45 } 53 }
46 54
47 for (key, value) in &other.key_values { 55 for atom in diff.disable {
48 self.key_values.insert((key.clone(), value.clone())); 56 self.enabled.remove(&atom);
57 }
58 }
59}
60
61pub struct CfgDiff {
62 // Invariants: No duplicates, no atom that's both in `enable` and `disable`.
63 enable: Vec<CfgAtom>,
64 disable: Vec<CfgAtom>,
65}
66
67impl CfgDiff {
68 /// Returns the total number of atoms changed by this diff.
69 pub fn len(&self) -> usize {
70 self.enable.len() + self.disable.len()
71 }
72}
73
74impl fmt::Display for CfgDiff {
75 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76 if !self.enable.is_empty() {
77 f.write_str("enable ")?;
78 for (i, atom) in self.enable.iter().enumerate() {
79 let sep = match i {
80 0 => "",
81 _ if i == self.enable.len() - 1 => " and ",
82 _ => ", ",
83 };
84 f.write_str(sep)?;
85
86 write!(f, "{}", atom)?;
87 }
88
89 if !self.disable.is_empty() {
90 f.write_str("; ")?;
91 }
49 } 92 }
93
94 if !self.disable.is_empty() {
95 f.write_str("disable ")?;
96 for (i, atom) in self.disable.iter().enumerate() {
97 let sep = match i {
98 0 => "",
99 _ if i == self.enable.len() - 1 => " and ",
100 _ => ", ",
101 };
102 f.write_str(sep)?;
103
104 write!(f, "{}", atom)?;
105 }
106 }
107
108 Ok(())
109 }
110}
111
112pub struct InactiveReason {
113 enabled: Vec<CfgAtom>,
114 disabled: Vec<CfgAtom>,
115}
116
117impl fmt::Display for InactiveReason {
118 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
119 if !self.enabled.is_empty() {
120 for (i, atom) in self.enabled.iter().enumerate() {
121 let sep = match i {
122 0 => "",
123 _ if i == self.enabled.len() - 1 => " and ",
124 _ => ", ",
125 };
126 f.write_str(sep)?;
127
128 write!(f, "{}", atom)?;
129 }
130 let is_are = if self.enabled.len() == 1 { "is" } else { "are" };
131 write!(f, " {} enabled", is_are)?;
132
133 if !self.disabled.is_empty() {
134 f.write_str(" and ")?;
135 }
136 }
137
138 if !self.disabled.is_empty() {
139 for (i, atom) in self.disabled.iter().enumerate() {
140 let sep = match i {
141 0 => "",
142 _ if i == self.disabled.len() - 1 => " and ",
143 _ => ", ",
144 };
145 f.write_str(sep)?;
146
147 write!(f, "{}", atom)?;
148 }
149 let is_are = if self.disabled.len() == 1 { "is" } else { "are" };
150 write!(f, " {} disabled", is_are)?;
151 }
152
153 Ok(())
50 } 154 }
51} 155}
diff --git a/crates/cfg/src/tests.rs b/crates/cfg/src/tests.rs
new file mode 100644
index 000000000..bd0f9ec48
--- /dev/null
+++ b/crates/cfg/src/tests.rs
@@ -0,0 +1,193 @@
1use expect_test::{expect, Expect};
2use mbe::ast_to_token_tree;
3use syntax::{ast, AstNode};
4
5use crate::{CfgAtom, CfgExpr, CfgOptions, DnfExpr};
6
7fn assert_parse_result(input: &str, expected: CfgExpr) {
8 let (tt, _) = {
9 let source_file = ast::SourceFile::parse(input).ok().unwrap();
10 let tt = source_file.syntax().descendants().find_map(ast::TokenTree::cast).unwrap();
11 ast_to_token_tree(&tt).unwrap()
12 };
13 let cfg = CfgExpr::parse(&tt);
14 assert_eq!(cfg, expected);
15}
16
17fn check_dnf(input: &str, expect: Expect) {
18 let (tt, _) = {
19 let source_file = ast::SourceFile::parse(input).ok().unwrap();
20 let tt = source_file.syntax().descendants().find_map(ast::TokenTree::cast).unwrap();
21 ast_to_token_tree(&tt).unwrap()
22 };
23 let cfg = CfgExpr::parse(&tt);
24 let actual = format!("#![cfg({})]", DnfExpr::new(cfg));
25 expect.assert_eq(&actual);
26}
27
28fn check_why_inactive(input: &str, opts: &CfgOptions, expect: Expect) {
29 let (tt, _) = {
30 let source_file = ast::SourceFile::parse(input).ok().unwrap();
31 let tt = source_file.syntax().descendants().find_map(ast::TokenTree::cast).unwrap();
32 ast_to_token_tree(&tt).unwrap()
33 };
34 let cfg = CfgExpr::parse(&tt);
35 let dnf = DnfExpr::new(cfg);
36 let why_inactive = dnf.why_inactive(opts).unwrap().to_string();
37 expect.assert_eq(&why_inactive);
38}
39
40#[track_caller]
41fn check_enable_hints(input: &str, opts: &CfgOptions, expected_hints: &[&str]) {
42 let (tt, _) = {
43 let source_file = ast::SourceFile::parse(input).ok().unwrap();
44 let tt = source_file.syntax().descendants().find_map(ast::TokenTree::cast).unwrap();
45 ast_to_token_tree(&tt).unwrap()
46 };
47 let cfg = CfgExpr::parse(&tt);
48 let dnf = DnfExpr::new(cfg);
49 let hints = dnf.compute_enable_hints(opts).map(|diff| diff.to_string()).collect::<Vec<_>>();
50 assert_eq!(hints, expected_hints);
51}
52
53#[test]
54fn test_cfg_expr_parser() {
55 assert_parse_result("#![cfg(foo)]", CfgAtom::Flag("foo".into()).into());
56 assert_parse_result("#![cfg(foo,)]", CfgAtom::Flag("foo".into()).into());
57 assert_parse_result(
58 "#![cfg(not(foo))]",
59 CfgExpr::Not(Box::new(CfgAtom::Flag("foo".into()).into())),
60 );
61 assert_parse_result("#![cfg(foo(bar))]", CfgExpr::Invalid);
62
63 // Only take the first
64 assert_parse_result(r#"#![cfg(foo, bar = "baz")]"#, CfgAtom::Flag("foo".into()).into());
65
66 assert_parse_result(
67 r#"#![cfg(all(foo, bar = "baz"))]"#,
68 CfgExpr::All(vec![
69 CfgAtom::Flag("foo".into()).into(),
70 CfgAtom::KeyValue { key: "bar".into(), value: "baz".into() }.into(),
71 ]),
72 );
73
74 assert_parse_result(
75 r#"#![cfg(any(not(), all(), , bar = "baz",))]"#,
76 CfgExpr::Any(vec![
77 CfgExpr::Not(Box::new(CfgExpr::Invalid)),
78 CfgExpr::All(vec![]),
79 CfgExpr::Invalid,
80 CfgAtom::KeyValue { key: "bar".into(), value: "baz".into() }.into(),
81 ]),
82 );
83}
84
85#[test]
86fn smoke() {
87 check_dnf("#![cfg(test)]", expect![[r#"#![cfg(test)]"#]]);
88 check_dnf("#![cfg(not(test))]", expect![[r#"#![cfg(not(test))]"#]]);
89 check_dnf("#![cfg(not(not(test)))]", expect![[r#"#![cfg(test)]"#]]);
90
91 check_dnf("#![cfg(all(a, b))]", expect![[r#"#![cfg(all(a, b))]"#]]);
92 check_dnf("#![cfg(any(a, b))]", expect![[r#"#![cfg(any(a, b))]"#]]);
93
94 check_dnf("#![cfg(not(a))]", expect![[r#"#![cfg(not(a))]"#]]);
95}
96
97#[test]
98fn distribute() {
99 check_dnf("#![cfg(all(any(a, b), c))]", expect![[r#"#![cfg(any(all(a, c), all(b, c)))]"#]]);
100 check_dnf("#![cfg(all(c, any(a, b)))]", expect![[r#"#![cfg(any(all(c, a), all(c, b)))]"#]]);
101 check_dnf(
102 "#![cfg(all(any(a, b), any(c, d)))]",
103 expect![[r#"#![cfg(any(all(a, c), all(a, d), all(b, c), all(b, d)))]"#]],
104 );
105
106 check_dnf(
107 "#![cfg(all(any(a, b, c), any(d, e, f), g))]",
108 expect![[
109 r#"#![cfg(any(all(a, d, g), all(a, e, g), all(a, f, g), all(b, d, g), all(b, e, g), all(b, f, g), all(c, d, g), all(c, e, g), all(c, f, g)))]"#
110 ]],
111 );
112}
113
114#[test]
115fn demorgan() {
116 check_dnf("#![cfg(not(all(a, b)))]", expect![[r#"#![cfg(any(not(a), not(b)))]"#]]);
117 check_dnf("#![cfg(not(any(a, b)))]", expect![[r#"#![cfg(all(not(a), not(b)))]"#]]);
118
119 check_dnf("#![cfg(not(all(not(a), b)))]", expect![[r#"#![cfg(any(a, not(b)))]"#]]);
120 check_dnf("#![cfg(not(any(a, not(b))))]", expect![[r#"#![cfg(all(not(a), b))]"#]]);
121}
122
123#[test]
124fn nested() {
125 check_dnf("#![cfg(all(any(a), not(all(any(b)))))]", expect![[r#"#![cfg(all(a, not(b)))]"#]]);
126
127 check_dnf("#![cfg(any(any(a, b)))]", expect![[r#"#![cfg(any(a, b))]"#]]);
128 check_dnf("#![cfg(not(any(any(a, b))))]", expect![[r#"#![cfg(all(not(a), not(b)))]"#]]);
129 check_dnf("#![cfg(all(all(a, b)))]", expect![[r#"#![cfg(all(a, b))]"#]]);
130 check_dnf("#![cfg(not(all(all(a, b))))]", expect![[r#"#![cfg(any(not(a), not(b)))]"#]]);
131}
132
133#[test]
134fn hints() {
135 let mut opts = CfgOptions::default();
136
137 check_enable_hints("#![cfg(test)]", &opts, &["enable test"]);
138 check_enable_hints("#![cfg(not(test))]", &opts, &[]);
139
140 check_enable_hints("#![cfg(any(a, b))]", &opts, &["enable a", "enable b"]);
141 check_enable_hints("#![cfg(any(b, a))]", &opts, &["enable b", "enable a"]);
142
143 check_enable_hints("#![cfg(all(a, b))]", &opts, &["enable a and b"]);
144
145 opts.insert_atom("test".into());
146
147 check_enable_hints("#![cfg(test)]", &opts, &[]);
148 check_enable_hints("#![cfg(not(test))]", &opts, &["disable test"]);
149}
150
151/// Tests that we don't suggest hints for cfgs that express an inconsistent formula.
152#[test]
153fn hints_impossible() {
154 let mut opts = CfgOptions::default();
155
156 check_enable_hints("#![cfg(all(test, not(test)))]", &opts, &[]);
157
158 opts.insert_atom("test".into());
159
160 check_enable_hints("#![cfg(all(test, not(test)))]", &opts, &[]);
161}
162
163#[test]
164fn why_inactive() {
165 let mut opts = CfgOptions::default();
166 opts.insert_atom("test".into());
167 opts.insert_atom("test2".into());
168
169 check_why_inactive("#![cfg(a)]", &opts, expect![["a is disabled"]]);
170 check_why_inactive("#![cfg(not(test))]", &opts, expect![["test is enabled"]]);
171
172 check_why_inactive(
173 "#![cfg(all(not(test), not(test2)))]",
174 &opts,
175 expect![["test and test2 are enabled"]],
176 );
177 check_why_inactive("#![cfg(all(a, b))]", &opts, expect![["a and b are disabled"]]);
178 check_why_inactive(
179 "#![cfg(all(not(test), a))]",
180 &opts,
181 expect![["test is enabled and a is disabled"]],
182 );
183 check_why_inactive(
184 "#![cfg(all(not(test), test2, a))]",
185 &opts,
186 expect![["test is enabled and a is disabled"]],
187 );
188 check_why_inactive(
189 "#![cfg(all(not(test), not(test2), a))]",
190 &opts,
191 expect![["test and test2 are enabled and a is disabled"]],
192 );
193}
diff --git a/crates/completion/Cargo.toml b/crates/completion/Cargo.toml
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" }
21ide_db = { path = "../ide_db", version = "0.0.0" } 21ide_db = { path = "../ide_db", version = "0.0.0" }
22profile = { path = "../profile", version = "0.0.0" } 22profile = { path = "../profile", version = "0.0.0" }
23test_utils = { path = "../test_utils", version = "0.0.0" } 23test_utils = { path = "../test_utils", version = "0.0.0" }
24assists = { path = "../assists", version = "0.0.0" }
25call_info = { path = "../call_info", version = "0.0.0" }
26 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
3use base_db::{SourceDatabaseExt, VfsPath};
4use hir::{Module, ModuleSource}; 3use hir::{Module, ModuleSource};
4use ide_db::base_db::{SourceDatabaseExt, VfsPath};
5use ide_db::RootDatabase; 5use ide_db::RootDatabase;
6use rustc_hash::FxHashSet; 6use 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
3mod format_like; 3mod format_like;
4 4
5use assists::utils::TryEnum; 5use ide_db::ty_filter::TryEnum;
6use syntax::{ 6use 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
34use assists::utils::get_missing_assoc_items;
35use hir::{self, HasAttrs, HasSource}; 34use hir::{self, HasAttrs, HasSource};
35use ide_db::traits::get_missing_assoc_items;
36use syntax::{ 36use 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
3use base_db::{FilePosition, SourceDatabase};
4use call_info::ActiveParameter;
5use hir::{Local, ScopeDef, Semantics, SemanticsScope, Type}; 3use hir::{Local, ScopeDef, Semantics, SemanticsScope, Type};
6use ide_db::RootDatabase; 4use ide_db::base_db::{FilePosition, SourceDatabase};
5use ide_db::{call_info::ActiveParameter, RootDatabase};
7use syntax::{ 6use 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;
23mod complete_trait_impl; 23mod complete_trait_impl;
24mod complete_mod; 24mod complete_mod;
25 25
26use base_db::FilePosition; 26use ide_db::base_db::FilePosition;
27use ide_db::RootDatabase; 27use ide_db::RootDatabase;
28 28
29use crate::{ 29use 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#"
616mod m {
617 pub enum Spam { Foo, Bar(i32) }
618}
619fn 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
3use base_db::{fixture::ChangeFixture, FileLoader, FilePosition};
4use hir::Semantics; 3use hir::Semantics;
4use ide_db::base_db::{fixture::ChangeFixture, FileLoader, FilePosition};
5use ide_db::RootDatabase; 5use ide_db::RootDatabase;
6use itertools::Itertools; 6use itertools::Itertools;
7use stdx::{format_to, trim_indent}; 7use 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.
3mod lower; 3mod lower;
4mod diagnostics;
5#[cfg(test)]
6mod tests;
4pub mod scope; 7pub mod scope;
5 8
6use std::{mem, ops::Index, sync::Arc}; 9use std::{mem, ops::Index, sync::Arc};
@@ -10,7 +13,10 @@ use base_db::CrateId;
10use cfg::CfgOptions; 13use cfg::CfgOptions;
11use drop_bomb::DropBomb; 14use drop_bomb::DropBomb;
12use either::Either; 15use either::Either;
13use hir_expand::{ast_id_map::AstIdMap, hygiene::Hygiene, AstId, HirFileId, InFile, MacroDefId}; 16use hir_expand::{
17 ast_id_map::AstIdMap, diagnostics::DiagnosticSink, hygiene::Hygiene, AstId, HirFileId, InFile,
18 MacroDefId,
19};
14use rustc_hash::FxHashMap; 20use rustc_hash::FxHashMap;
15use syntax::{ast, AstNode, AstPtr}; 21use syntax::{ast, AstNode, AstPtr};
16use test_utils::mark; 22use 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)]
324mod 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 "
352macro_rules! n_nuple {
353 ($e:tt) => ();
354 ($($rest:tt)*) => {{
355 (n_nuple!($($rest)*)None,)
356 }};
357}
358fn 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
3use hir_expand::diagnostics::DiagnosticSink;
4
5use crate::diagnostics::InactiveCode;
6
7#[derive(Debug, Eq, PartialEq)]
8pub enum BodyDiagnostic {
9 InactiveCode(InactiveCode),
10}
11
12impl 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};
21use test_utils::mark; 21use 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
40use super::{ExprSource, PatSource}; 41use super::{diagnostics::BodyDiagnostic, ExprSource, PatSource};
41 42
42pub(crate) struct LowerCtx { 43pub(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
877impl From<ast::BinOp> for BinaryOp { 909impl 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 @@
1use base_db::{fixture::WithFixture, SourceDatabase};
2use test_utils::mark;
3
4use crate::{test_db::TestDB, ModuleDefId};
5
6use super::*;
7
8fn 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
23fn check_diagnostics(ra_fixture: &str) {
24 let db: TestDB = TestDB::with_files(ra_fixture);
25 db.check_diagnostics();
26}
27
28#[test]
29fn your_stack_belongs_to_me() {
30 mark::check!(your_stack_belongs_to_me);
31 lower(
32 "
33macro_rules! n_nuple {
34 ($e:tt) => ();
35 ($($rest:tt)*) => {{
36 (n_nuple!($($rest)*)None,)
37 }};
38}
39fn main() { n_nuple!(1,2,3); }
40",
41 );
42}
43
44#[test]
45fn cfg_diagnostics() {
46 check_diagnostics(
47 r"
48fn 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
3use std::any::Any; 3use std::any::Any;
4use stdx::format_to;
4 5
5use hir_expand::diagnostics::{Diagnostic, DiagnosticCode}; 6use cfg::{CfgExpr, CfgOptions, DnfExpr};
7use hir_expand::diagnostics::{Diagnostic, DiagnosticCode, DiagnosticSink};
8use hir_expand::{HirFileId, InFile};
6use syntax::{ast, AstPtr, SyntaxNodePtr}; 9use syntax::{ast, AstPtr, SyntaxNodePtr};
7 10
8use hir_expand::{HirFileId, InFile}; 11use crate::{db::DefDatabase, DefWithBodyId};
12
13pub 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)]
94pub struct InactiveCode { 102pub 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
99impl Diagnostic for InactiveCode { 109impl 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
285mod diagnostics { 285mod 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 @@
6use std::iter; 6use std::iter;
7 7
8use base_db::{CrateId, FileId, ProcMacroId}; 8use base_db::{CrateId, FileId, ProcMacroId};
9use cfg::CfgOptions; 9use cfg::{CfgExpr, CfgOptions};
10use hir_expand::InFile; 10use hir_expand::InFile;
11use hir_expand::{ 11use 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 @@
1use base_db::fixture::WithFixture; 1use base_db::fixture::WithFixture;
2use base_db::FileId;
3use base_db::SourceDatabaseExt;
4use hir_expand::db::AstDatabase;
5use rustc_hash::FxHashMap;
6use syntax::TextRange;
7use syntax::TextSize;
8 2
9use crate::test_db::TestDB; 3use crate::test_db::TestDB;
10 4
11fn check_diagnostics(ra_fixture: &str) { 5fn 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]
102fn 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;
12use hir_expand::diagnostics::DiagnosticSinkBuilder; 12use hir_expand::diagnostics::DiagnosticSinkBuilder;
13use rustc_hash::FxHashMap; 13use rustc_hash::FxHashMap;
14use rustc_hash::FxHashSet; 14use rustc_hash::FxHashSet;
15use syntax::TextRange; 15use syntax::{TextRange, TextSize};
16use test_utils::extract_annotations; 16use test_utils::extract_annotations;
17 17
18use crate::db::DefDatabase; 18use 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"
17log = "0.4.8" 17log = "0.4.8"
18rustc-hash = "1.1.0" 18rustc-hash = "1.1.0"
19scoped-tls = "1" 19scoped-tls = "1"
20chalk-solve = "0.33" 20chalk-solve = { version = "0.34", default-features = false }
21chalk-ir = "0.33" 21chalk-ir = "0.34"
22chalk-recursive = "0.33" 22chalk-recursive = "0.34"
23 23
24stdx = { path = "../stdx", version = "0.0.0" } 24stdx = { path = "../stdx", version = "0.0.0" }
25hir_def = { path = "../hir_def", version = "0.0.0" } 25hir_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,
5enum DetectedCase { 5// from file /compiler/rustc_lint/src/nonstandard_style.rs
6 LowerCamelCase,
7 UpperCamelCase,
8 LowerSnakeCase,
9 UpperSnakeCase,
10 Unknown,
11}
12
13fn 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.
57pub fn to_camel_case(ident: &str) -> Option<String> { 9pub 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.
111pub fn to_lower_snake_case(ident: &str) -> Option<String> { 62pub 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.
135pub fn to_upper_snake_case(ident: &str) -> Option<String> { 74pub 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`.
86fn 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
110fn is_lower_snake_case(ident: &str) -> bool {
111 is_snake_case(ident, char::is_uppercase)
112}
146 113
147 if upper_snake_case == ident { 114fn 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.
120fn 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.
141fn 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;
5use chalk_ir::cast::Cast; 5use chalk_ir::cast::Cast;
6use chalk_solve::{logging_db::LoggingRustIrDatabase, Solver}; 6use chalk_solve::{logging_db::LoggingRustIrDatabase, Solver};
7use hir_def::{lang_item::LangItemTarget, TraitId}; 7use hir_def::{lang_item::LangItemTarget, TraitId};
8use stdx::panic_context;
8 9
9use crate::{db::HirDatabase, DebruijnIndex, Substs}; 10use 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
199struct LoggingRustIrDatabaseLoggingOnDrop<'a>(LoggingRustIrDatabase<Interner, ChalkContext<'a>>);
200
201impl<'a> Drop for LoggingRustIrDatabaseLoggingOnDrop<'a> {
202 fn drop(&mut self) {
203 eprintln!("chalk program:\n{}", self.0);
204 }
205}
206
191fn is_chalk_debug() -> bool { 207fn 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
6use chalk_ir::{ 6use 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};
10use chalk_solve::rust_ir; 10use 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
164const 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.
169fn ref_to_chalk( 166fn 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]
13either = "1.5.3" 13either = "1.5.3"
14indexmap = "1.3.2" 14indexmap = "1.4.0"
15itertools = "0.9.0" 15itertools = "0.9.0"
16log = "0.4.8" 16log = "0.4.8"
17rustc-hash = "1.1.0" 17rustc-hash = "1.1.0"
@@ -23,14 +23,12 @@ url = "2.1.1"
23stdx = { path = "../stdx", version = "0.0.0" } 23stdx = { path = "../stdx", version = "0.0.0" }
24syntax = { path = "../syntax", version = "0.0.0" } 24syntax = { path = "../syntax", version = "0.0.0" }
25text_edit = { path = "../text_edit", version = "0.0.0" } 25text_edit = { path = "../text_edit", version = "0.0.0" }
26base_db = { path = "../base_db", version = "0.0.0" }
27ide_db = { path = "../ide_db", version = "0.0.0" } 26ide_db = { path = "../ide_db", version = "0.0.0" }
28cfg = { path = "../cfg", version = "0.0.0" } 27cfg = { path = "../cfg", version = "0.0.0" }
29profile = { path = "../profile", version = "0.0.0" } 28profile = { path = "../profile", version = "0.0.0" }
30test_utils = { path = "../test_utils", version = "0.0.0" } 29test_utils = { path = "../test_utils", version = "0.0.0" }
31assists = { path = "../assists", version = "0.0.0" } 30assists = { path = "../assists", version = "0.0.0" }
32ssr = { path = "../ssr", version = "0.0.0" } 31ssr = { path = "../ssr", version = "0.0.0" }
33call_info = { path = "../call_info", version = "0.0.0" }
34completion = { path = "../completion", version = "0.0.0" } 32completion = { 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
3use indexmap::IndexMap; 3use indexmap::IndexMap;
4 4
5use call_info::FnCallNode;
6use hir::Semantics; 5use hir::Semantics;
6use ide_db::call_info::FnCallNode;
7use ide_db::RootDatabase; 7use ide_db::RootDatabase;
8use syntax::{ast, match_ast, AstNode, TextRange}; 8use syntax::{ast, match_ast, AstNode, TextRange};
9 9
@@ -137,7 +137,7 @@ impl CallLocations {
137 137
138#[cfg(test)] 138#[cfg(test)]
139mod tests { 139mod 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
10use std::cell::RefCell; 10use std::cell::RefCell;
11 11
12use base_db::SourceDatabase;
13use hir::{ 12use hir::{
14 diagnostics::{Diagnostic as _, DiagnosticSinkBuilder}, 13 diagnostics::{Diagnostic as _, DiagnosticSinkBuilder},
15 Semantics, 14 Semantics,
16}; 15};
16use ide_db::base_db::SourceDatabase;
17use ide_db::RootDatabase; 17use ide_db::RootDatabase;
18use itertools::Itertools; 18use itertools::Itertools;
19use rustc_hash::FxHashSet; 19use rustc_hash::FxHashSet;
@@ -613,7 +613,7 @@ fn main() {
613pub struct Foo { pub a: i32, pub b: i32 } 613pub struct Foo { pub a: i32, pub b: i32 }
614"#, 614"#,
615 r#" 615 r#"
616fn {a:42, b: ()} {} 616fn some(, b: ()} {}
617fn items() {} 617fn items() {}
618fn here() {} 618fn 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
4use base_db::FileId; 4use ide_db::base_db::FileId;
5use ide_db::source_change::SourceFileEdit; 5use ide_db::source_change::SourceFileEdit;
6use syntax::{ast, match_ast, AstNode, SyntaxNode}; 6use syntax::{ast, match_ast, AstNode, SyntaxNode};
7use text_edit::TextEdit; 7use 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.
3use base_db::FileId;
4use hir::{ 3use 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};
11use ide_db::base_db::FileId;
12use ide_db::{ 12use 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 2
3use base_db::{FileId, SourceDatabase};
4use either::Either; 3use either::Either;
5use hir::{original_range, AssocItem, FieldSource, HasSource, InFile, ModuleSource}; 4use hir::{original_range, AssocItem, FieldSource, HasSource, InFile, ModuleSource};
5use ide_db::base_db::{FileId, SourceDatabase};
6use ide_db::{defs::Definition, RootDatabase}; 6use ide_db::{defs::Definition, RootDatabase};
7use syntax::{ 7use syntax::{
8 ast::{self, DocCommentsOwner, NameOwner}, 8 ast::{self, DocCommentsOwner, NameOwner},
diff --git a/crates/ide/src/fixture.rs b/crates/ide/src/fixture.rs
index ed06689f0..eb57f9224 100644
--- a/crates/ide/src/fixture.rs
+++ b/crates/ide/src/fixture.rs
@@ -1,5 +1,5 @@
1//! Utilities for creating `Analysis` instances for tests. 1//! Utilities for creating `Analysis` instances for tests.
2use base_db::fixture::ChangeFixture; 2use ide_db::base_db::fixture::ChangeFixture;
3use test_utils::{extract_annotations, RangeOrOffset}; 3use test_utils::{extract_annotations, RangeOrOffset};
4 4
5use crate::{Analysis, AnalysisHost, FileId, FilePosition, FileRange}; 5use crate::{Analysis, AnalysisHost, FileId, FilePosition, FileRange};
diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs
index a87e31019..15792f947 100644
--- a/crates/ide/src/goto_definition.rs
+++ b/crates/ide/src/goto_definition.rs
@@ -100,7 +100,7 @@ pub(crate) fn reference_definition(
100 100
101#[cfg(test)] 101#[cfg(test)]
102mod tests { 102mod tests {
103 use base_db::FileRange; 103 use ide_db::base_db::FileRange;
104 use syntax::{TextRange, TextSize}; 104 use syntax::{TextRange, TextSize};
105 105
106 use crate::fixture; 106 use crate::fixture;
diff --git a/crates/ide/src/goto_implementation.rs b/crates/ide/src/goto_implementation.rs
index 6c586bbd1..529004878 100644
--- a/crates/ide/src/goto_implementation.rs
+++ b/crates/ide/src/goto_implementation.rs
@@ -74,7 +74,7 @@ fn impls_for_trait(
74 74
75#[cfg(test)] 75#[cfg(test)]
76mod tests { 76mod tests {
77 use base_db::FileRange; 77 use ide_db::base_db::FileRange;
78 78
79 use crate::fixture; 79 use crate::fixture;
80 80
diff --git a/crates/ide/src/goto_type_definition.rs b/crates/ide/src/goto_type_definition.rs
index 6d0df04dd..aba6bf5dc 100644
--- a/crates/ide/src/goto_type_definition.rs
+++ b/crates/ide/src/goto_type_definition.rs
@@ -54,7 +54,7 @@ fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> {
54 54
55#[cfg(test)] 55#[cfg(test)]
56mod tests { 56mod tests {
57 use base_db::FileRange; 57 use ide_db::base_db::FileRange;
58 58
59 use crate::fixture; 59 use crate::fixture;
60 60
diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs
index 6466422c5..832192881 100644
--- a/crates/ide/src/hover.rs
+++ b/crates/ide/src/hover.rs
@@ -1,8 +1,8 @@
1use base_db::SourceDatabase;
2use hir::{ 1use hir::{
3 Adt, AsAssocItem, AssocItemContainer, Documentation, FieldSource, HasSource, HirDisplay, 2 Adt, AsAssocItem, AssocItemContainer, Documentation, FieldSource, HasSource, HirDisplay,
4 Module, ModuleDef, ModuleSource, Semantics, 3 Module, ModuleDef, ModuleSource, Semantics,
5}; 4};
5use ide_db::base_db::SourceDatabase;
6use ide_db::{ 6use ide_db::{
7 defs::{Definition, NameClass, NameRefClass}, 7 defs::{Definition, NameClass, NameRefClass},
8 RootDatabase, 8 RootDatabase,
@@ -385,8 +385,8 @@ fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> {
385 385
386#[cfg(test)] 386#[cfg(test)]
387mod tests { 387mod tests {
388 use base_db::FileLoader;
389 use expect_test::{expect, Expect}; 388 use expect_test::{expect, Expect};
389 use ide_db::base_db::FileLoader;
390 390
391 use crate::fixture; 391 use crate::fixture;
392 392
@@ -638,6 +638,33 @@ fn main() { }
638 } 638 }
639 639
640 #[test] 640 #[test]
641 fn hover_shows_fn_doc_attr_raw_string() {
642 check(
643 r##"
644#[doc = r#"Raw string doc attr"#]
645pub fn foo<|>(_: &Path) {}
646
647fn main() { }
648"##,
649 expect![[r##"
650 *foo*
651
652 ```rust
653 test
654 ```
655
656 ```rust
657 pub fn foo(_: &Path)
658 ```
659
660 ---
661
662 Raw string doc attr
663 "##]],
664 );
665 }
666
667 #[test]
641 fn hover_shows_struct_field_info() { 668 fn hover_shows_struct_field_info() {
642 // Hovering over the field when instantiating 669 // Hovering over the field when instantiating
643 check( 670 check(
@@ -2128,7 +2155,7 @@ fn foo_<|>test() {}
2128 ignore: false, 2155 ignore: false,
2129 }, 2156 },
2130 }, 2157 },
2131 cfg_exprs: [], 2158 cfg: None,
2132 }, 2159 },
2133 ), 2160 ),
2134 ] 2161 ]
@@ -2166,7 +2193,7 @@ mod tests<|> {
2166 kind: TestMod { 2193 kind: TestMod {
2167 path: "tests", 2194 path: "tests",
2168 }, 2195 },
2169 cfg_exprs: [], 2196 cfg: None,
2170 }, 2197 },
2171 ), 2198 ),
2172 ] 2199 ]
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs
index cecfae4c7..4bc733b70 100644
--- a/crates/ide/src/lib.rs
+++ b/crates/ide/src/lib.rs
@@ -48,11 +48,11 @@ mod doc_links;
48 48
49use std::sync::Arc; 49use std::sync::Arc;
50 50
51use base_db::{ 51use cfg::CfgOptions;
52use ide_db::base_db::{
52 salsa::{self, ParallelDatabase}, 53 salsa::{self, ParallelDatabase},
53 CheckCanceled, Env, FileLoader, FileSet, SourceDatabase, VfsPath, 54 CheckCanceled, Env, FileLoader, FileSet, SourceDatabase, VfsPath,
54}; 55};
55use cfg::CfgOptions;
56use ide_db::{ 56use ide_db::{
57 symbol_index::{self, FileSymbol}, 57 symbol_index::{self, FileSymbol},
58 LineIndexDatabase, 58 LineIndexDatabase,
@@ -80,19 +80,19 @@ pub use crate::{
80 Highlight, HighlightModifier, HighlightModifiers, HighlightTag, HighlightedRange, 80 Highlight, HighlightModifier, HighlightModifiers, HighlightTag, HighlightedRange,
81 }, 81 },
82}; 82};
83pub use call_info::CallInfo;
84pub use completion::{ 83pub use completion::{
85 CompletionConfig, CompletionItem, CompletionItemKind, CompletionScore, InsertTextFormat, 84 CompletionConfig, CompletionItem, CompletionItemKind, CompletionScore, InsertTextFormat,
86}; 85};
86pub use ide_db::call_info::CallInfo;
87 87
88pub use assists::{ 88pub use assists::{
89 utils::MergeBehaviour, Assist, AssistConfig, AssistId, AssistKind, ResolvedAssist, 89 utils::MergeBehaviour, Assist, AssistConfig, AssistId, AssistKind, ResolvedAssist,
90}; 90};
91pub use base_db::{ 91pub use hir::{Documentation, Semantics};
92pub use ide_db::base_db::{
92 Canceled, Change, CrateGraph, CrateId, Edition, FileId, FilePosition, FileRange, SourceRoot, 93 Canceled, Change, CrateGraph, CrateId, Edition, FileId, FilePosition, FileRange, SourceRoot,
93 SourceRootId, 94 SourceRootId,
94}; 95};
95pub use hir::{Documentation, Semantics};
96pub use ide_db::{ 96pub use ide_db::{
97 label::Label, 97 label::Label,
98 line_index::{LineCol, LineIndex}, 98 line_index::{LineCol, LineIndex},
@@ -396,7 +396,7 @@ impl Analysis {
396 396
397 /// Computes parameter information for the given call expression. 397 /// Computes parameter information for the given call expression.
398 pub fn call_info(&self, position: FilePosition) -> Cancelable<Option<CallInfo>> { 398 pub fn call_info(&self, position: FilePosition) -> Cancelable<Option<CallInfo>> {
399 self.with_db(|db| call_info::call_info(db, position)) 399 self.with_db(|db| ide_db::call_info::call_info(db, position))
400 } 400 }
401 401
402 /// Computes call hierarchy candidates for the given file position. 402 /// Computes call hierarchy candidates for the given file position.
diff --git a/crates/ide/src/parent_module.rs b/crates/ide/src/parent_module.rs
index ef94acfec..6cc3b2991 100644
--- a/crates/ide/src/parent_module.rs
+++ b/crates/ide/src/parent_module.rs
@@ -1,5 +1,5 @@
1use base_db::{CrateId, FileId, FilePosition};
2use hir::Semantics; 1use hir::Semantics;
2use ide_db::base_db::{CrateId, FileId, FilePosition};
3use ide_db::RootDatabase; 3use ide_db::RootDatabase;
4use syntax::{ 4use syntax::{
5 algo::find_node_at_offset, 5 algo::find_node_at_offset,
diff --git a/crates/ide/src/prime_caches.rs b/crates/ide/src/prime_caches.rs
index 6944dbcd2..ea0acfaa0 100644
--- a/crates/ide/src/prime_caches.rs
+++ b/crates/ide/src/prime_caches.rs
@@ -3,8 +3,8 @@
3//! request takes longer to compute. This modules implemented prepopulating of 3//! request takes longer to compute. This modules implemented prepopulating of
4//! various caches, it's not really advanced at the moment. 4//! various caches, it's not really advanced at the moment.
5 5
6use base_db::SourceDatabase;
7use hir::db::DefDatabase; 6use hir::db::DefDatabase;
7use ide_db::base_db::SourceDatabase;
8 8
9use crate::RootDatabase; 9use crate::RootDatabase;
10 10
diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs
index 67ec257a8..a517081d5 100644
--- a/crates/ide/src/references.rs
+++ b/crates/ide/src/references.rs
@@ -191,8 +191,8 @@ fn get_struct_def_name_for_struct_literal_search(
191 191
192#[cfg(test)] 192#[cfg(test)]
193mod tests { 193mod tests {
194 use base_db::FileId;
195 use expect_test::{expect, Expect}; 194 use expect_test::{expect, Expect};
195 use ide_db::base_db::FileId;
196 use stdx::format_to; 196 use stdx::format_to;
197 197
198 use crate::{fixture, SearchScope}; 198 use crate::{fixture, SearchScope};
diff --git a/crates/ide/src/references/rename.rs b/crates/ide/src/references/rename.rs
index 35aafc49d..26ac2371a 100644
--- a/crates/ide/src/references/rename.rs
+++ b/crates/ide/src/references/rename.rs
@@ -1,7 +1,7 @@
1//! FIXME: write short doc here 1//! FIXME: write short doc here
2 2
3use base_db::SourceDatabaseExt;
4use hir::{Module, ModuleDef, ModuleSource, Semantics}; 3use hir::{Module, ModuleDef, ModuleSource, Semantics};
4use ide_db::base_db::SourceDatabaseExt;
5use ide_db::{ 5use ide_db::{
6 defs::{Definition, NameClass, NameRefClass}, 6 defs::{Definition, NameClass, NameRefClass},
7 RootDatabase, 7 RootDatabase,
diff --git a/crates/ide/src/runnables.rs b/crates/ide/src/runnables.rs
index 752ef2f21..eb82456ad 100644
--- a/crates/ide/src/runnables.rs
+++ b/crates/ide/src/runnables.rs
@@ -15,7 +15,7 @@ use crate::{display::ToNav, FileId, NavigationTarget};
15pub struct Runnable { 15pub struct Runnable {
16 pub nav: NavigationTarget, 16 pub nav: NavigationTarget,
17 pub kind: RunnableKind, 17 pub kind: RunnableKind,
18 pub cfg_exprs: Vec<CfgExpr>, 18 pub cfg: Option<CfgExpr>,
19} 19}
20 20
21#[derive(Debug, Clone)] 21#[derive(Debug, Clone)]
@@ -168,7 +168,7 @@ fn runnable_fn(
168 }; 168 };
169 169
170 let attrs = Attrs::from_attrs_owner(sema.db, InFile::new(HirFileId::from(file_id), &fn_def)); 170 let attrs = Attrs::from_attrs_owner(sema.db, InFile::new(HirFileId::from(file_id), &fn_def));
171 let cfg_exprs = attrs.cfg().collect(); 171 let cfg = attrs.cfg();
172 172
173 let nav = if let RunnableKind::DocTest { .. } = kind { 173 let nav = if let RunnableKind::DocTest { .. } = kind {
174 NavigationTarget::from_doc_commented( 174 NavigationTarget::from_doc_commented(
@@ -179,7 +179,7 @@ fn runnable_fn(
179 } else { 179 } else {
180 NavigationTarget::from_named(sema.db, InFile::new(file_id.into(), &fn_def)) 180 NavigationTarget::from_named(sema.db, InFile::new(file_id.into(), &fn_def))
181 }; 181 };
182 Some(Runnable { nav, kind, cfg_exprs }) 182 Some(Runnable { nav, kind, cfg })
183} 183}
184 184
185#[derive(Debug, Copy, Clone)] 185#[derive(Debug, Copy, Clone)]
@@ -255,9 +255,9 @@ fn runnable_mod(
255 .join("::"); 255 .join("::");
256 256
257 let attrs = Attrs::from_attrs_owner(sema.db, InFile::new(HirFileId::from(file_id), &module)); 257 let attrs = Attrs::from_attrs_owner(sema.db, InFile::new(HirFileId::from(file_id), &module));
258 let cfg_exprs = attrs.cfg().collect(); 258 let cfg = attrs.cfg();
259 let nav = module_def.to_nav(sema.db); 259 let nav = module_def.to_nav(sema.db);
260 Some(Runnable { nav, kind: RunnableKind::TestMod { path }, cfg_exprs }) 260 Some(Runnable { nav, kind: RunnableKind::TestMod { path }, cfg })
261} 261}
262 262
263// We could create runnables for modules with number_of_test_submodules > 0, 263// We could create runnables for modules with number_of_test_submodules > 0,
@@ -348,7 +348,7 @@ fn bench() {}
348 docs: None, 348 docs: None,
349 }, 349 },
350 kind: Bin, 350 kind: Bin,
351 cfg_exprs: [], 351 cfg: None,
352 }, 352 },
353 Runnable { 353 Runnable {
354 nav: NavigationTarget { 354 nav: NavigationTarget {
@@ -373,7 +373,7 @@ fn bench() {}
373 ignore: false, 373 ignore: false,
374 }, 374 },
375 }, 375 },
376 cfg_exprs: [], 376 cfg: None,
377 }, 377 },
378 Runnable { 378 Runnable {
379 nav: NavigationTarget { 379 nav: NavigationTarget {
@@ -398,7 +398,7 @@ fn bench() {}
398 ignore: true, 398 ignore: true,
399 }, 399 },
400 }, 400 },
401 cfg_exprs: [], 401 cfg: None,
402 }, 402 },
403 Runnable { 403 Runnable {
404 nav: NavigationTarget { 404 nav: NavigationTarget {
@@ -420,7 +420,7 @@ fn bench() {}
420 "bench", 420 "bench",
421 ), 421 ),
422 }, 422 },
423 cfg_exprs: [], 423 cfg: None,
424 }, 424 },
425 ] 425 ]
426 "#]], 426 "#]],
@@ -507,7 +507,7 @@ fn should_have_no_runnable_6() {}
507 docs: None, 507 docs: None,
508 }, 508 },
509 kind: Bin, 509 kind: Bin,
510 cfg_exprs: [], 510 cfg: None,
511 }, 511 },
512 Runnable { 512 Runnable {
513 nav: NavigationTarget { 513 nav: NavigationTarget {
@@ -527,7 +527,7 @@ fn should_have_no_runnable_6() {}
527 "should_have_runnable", 527 "should_have_runnable",
528 ), 528 ),
529 }, 529 },
530 cfg_exprs: [], 530 cfg: None,
531 }, 531 },
532 Runnable { 532 Runnable {
533 nav: NavigationTarget { 533 nav: NavigationTarget {
@@ -547,7 +547,7 @@ fn should_have_no_runnable_6() {}
547 "should_have_runnable_1", 547 "should_have_runnable_1",
548 ), 548 ),
549 }, 549 },
550 cfg_exprs: [], 550 cfg: None,
551 }, 551 },
552 Runnable { 552 Runnable {
553 nav: NavigationTarget { 553 nav: NavigationTarget {
@@ -567,7 +567,7 @@ fn should_have_no_runnable_6() {}
567 "should_have_runnable_2", 567 "should_have_runnable_2",
568 ), 568 ),
569 }, 569 },
570 cfg_exprs: [], 570 cfg: None,
571 }, 571 },
572 ] 572 ]
573 "#]], 573 "#]],
@@ -609,7 +609,7 @@ impl Data {
609 docs: None, 609 docs: None,
610 }, 610 },
611 kind: Bin, 611 kind: Bin,
612 cfg_exprs: [], 612 cfg: None,
613 }, 613 },
614 Runnable { 614 Runnable {
615 nav: NavigationTarget { 615 nav: NavigationTarget {
@@ -629,7 +629,7 @@ impl Data {
629 "Data::foo", 629 "Data::foo",
630 ), 630 ),
631 }, 631 },
632 cfg_exprs: [], 632 cfg: None,
633 }, 633 },
634 ] 634 ]
635 "#]], 635 "#]],
@@ -668,7 +668,7 @@ mod test_mod {
668 kind: TestMod { 668 kind: TestMod {
669 path: "test_mod", 669 path: "test_mod",
670 }, 670 },
671 cfg_exprs: [], 671 cfg: None,
672 }, 672 },
673 Runnable { 673 Runnable {
674 nav: NavigationTarget { 674 nav: NavigationTarget {
@@ -693,7 +693,7 @@ mod test_mod {
693 ignore: false, 693 ignore: false,
694 }, 694 },
695 }, 695 },
696 cfg_exprs: [], 696 cfg: None,
697 }, 697 },
698 ] 698 ]
699 "#]], 699 "#]],
@@ -748,7 +748,7 @@ mod root_tests {
748 kind: TestMod { 748 kind: TestMod {
749 path: "root_tests::nested_tests_0", 749 path: "root_tests::nested_tests_0",
750 }, 750 },
751 cfg_exprs: [], 751 cfg: None,
752 }, 752 },
753 Runnable { 753 Runnable {
754 nav: NavigationTarget { 754 nav: NavigationTarget {
@@ -768,7 +768,7 @@ mod root_tests {
768 kind: TestMod { 768 kind: TestMod {
769 path: "root_tests::nested_tests_0::nested_tests_1", 769 path: "root_tests::nested_tests_0::nested_tests_1",
770 }, 770 },
771 cfg_exprs: [], 771 cfg: None,
772 }, 772 },
773 Runnable { 773 Runnable {
774 nav: NavigationTarget { 774 nav: NavigationTarget {
@@ -793,7 +793,7 @@ mod root_tests {
793 ignore: false, 793 ignore: false,
794 }, 794 },
795 }, 795 },
796 cfg_exprs: [], 796 cfg: None,
797 }, 797 },
798 Runnable { 798 Runnable {
799 nav: NavigationTarget { 799 nav: NavigationTarget {
@@ -818,7 +818,7 @@ mod root_tests {
818 ignore: false, 818 ignore: false,
819 }, 819 },
820 }, 820 },
821 cfg_exprs: [], 821 cfg: None,
822 }, 822 },
823 Runnable { 823 Runnable {
824 nav: NavigationTarget { 824 nav: NavigationTarget {
@@ -838,7 +838,7 @@ mod root_tests {
838 kind: TestMod { 838 kind: TestMod {
839 path: "root_tests::nested_tests_0::nested_tests_2", 839 path: "root_tests::nested_tests_0::nested_tests_2",
840 }, 840 },
841 cfg_exprs: [], 841 cfg: None,
842 }, 842 },
843 Runnable { 843 Runnable {
844 nav: NavigationTarget { 844 nav: NavigationTarget {
@@ -863,7 +863,7 @@ mod root_tests {
863 ignore: false, 863 ignore: false,
864 }, 864 },
865 }, 865 },
866 cfg_exprs: [], 866 cfg: None,
867 }, 867 },
868 ] 868 ]
869 "#]], 869 "#]],
@@ -906,12 +906,14 @@ fn test_foo1() {}
906 ignore: false, 906 ignore: false,
907 }, 907 },
908 }, 908 },
909 cfg_exprs: [ 909 cfg: Some(
910 KeyValue { 910 Atom(
911 key: "feature", 911 KeyValue {
912 value: "foo", 912 key: "feature",
913 }, 913 value: "foo",
914 ], 914 },
915 ),
916 ),
915 }, 917 },
916 ] 918 ]
917 "#]], 919 "#]],
@@ -954,20 +956,24 @@ fn test_foo1() {}
954 ignore: false, 956 ignore: false,
955 }, 957 },
956 }, 958 },
957 cfg_exprs: [ 959 cfg: Some(
958 All( 960 All(
959 [ 961 [
960 KeyValue { 962 Atom(
961 key: "feature", 963 KeyValue {
962 value: "foo", 964 key: "feature",
963 }, 965 value: "foo",
964 KeyValue { 966 },
965 key: "feature", 967 ),
966 value: "bar", 968 Atom(
967 }, 969 KeyValue {
970 key: "feature",
971 value: "bar",
972 },
973 ),
968 ], 974 ],
969 ), 975 ),
970 ], 976 ),
971 }, 977 },
972 ] 978 ]
973 "#]], 979 "#]],
diff --git a/crates/ide/src/status.rs b/crates/ide/src/status.rs
index 0af84daa0..8e91c99d7 100644
--- a/crates/ide/src/status.rs
+++ b/crates/ide/src/status.rs
@@ -1,10 +1,10 @@
1use std::{fmt, iter::FromIterator, sync::Arc}; 1use std::{fmt, iter::FromIterator, sync::Arc};
2 2
3use base_db::{ 3use hir::MacroFile;
4use ide_db::base_db::{
4 salsa::debug::{DebugQueryTable, TableEntry}, 5 salsa::debug::{DebugQueryTable, TableEntry},
5 CrateId, FileId, FileTextQuery, SourceDatabase, SourceRootId, 6 CrateId, FileId, FileTextQuery, SourceDatabase, SourceRootId,
6}; 7};
7use hir::MacroFile;
8use ide_db::{ 8use ide_db::{
9 symbol_index::{LibrarySymbolsQuery, SymbolIndex}, 9 symbol_index::{LibrarySymbolsQuery, SymbolIndex},
10 RootDatabase, 10 RootDatabase,
@@ -16,7 +16,7 @@ use stdx::format_to;
16use syntax::{ast, Parse, SyntaxNode}; 16use syntax::{ast, Parse, SyntaxNode};
17 17
18fn syntax_tree_stats(db: &RootDatabase) -> SyntaxTreeStats { 18fn syntax_tree_stats(db: &RootDatabase) -> SyntaxTreeStats {
19 base_db::ParseQuery.in_db(db).entries::<SyntaxTreeStats>() 19 ide_db::base_db::ParseQuery.in_db(db).entries::<SyntaxTreeStats>()
20} 20}
21fn macro_syntax_tree_stats(db: &RootDatabase) -> SyntaxTreeStats { 21fn macro_syntax_tree_stats(db: &RootDatabase) -> SyntaxTreeStats {
22 hir::db::ParseMacroQuery.in_db(db).entries::<SyntaxTreeStats>() 22 hir::db::ParseMacroQuery.in_db(db).entries::<SyntaxTreeStats>()
diff --git a/crates/ide/src/syntax_highlighting.rs b/crates/ide/src/syntax_highlighting.rs
index b35c03162..9f864179e 100644
--- a/crates/ide/src/syntax_highlighting.rs
+++ b/crates/ide/src/syntax_highlighting.rs
@@ -579,7 +579,14 @@ fn highlight_element(
579 } 579 }
580 } 580 }
581 T![-] if element.parent().and_then(ast::PrefixExpr::cast).is_some() => { 581 T![-] if element.parent().and_then(ast::PrefixExpr::cast).is_some() => {
582 HighlightTag::NumericLiteral.into() 582 let prefix_expr = element.parent().and_then(ast::PrefixExpr::cast)?;
583
584 let expr = prefix_expr.expr()?;
585 match expr {
586 ast::Expr::Literal(_) => HighlightTag::NumericLiteral,
587 _ => HighlightTag::Operator,
588 }
589 .into()
583 } 590 }
584 _ if element.parent().and_then(ast::PrefixExpr::cast).is_some() => { 591 _ if element.parent().and_then(ast::PrefixExpr::cast).is_some() => {
585 HighlightTag::Operator.into() 592 HighlightTag::Operator.into()
@@ -763,6 +770,9 @@ fn highlight_def(db: &RootDatabase, def: Definition) -> Highlight {
763 if local.is_mut(db) || local.ty(db).is_mutable_reference() { 770 if local.is_mut(db) || local.ty(db).is_mutable_reference() {
764 h |= HighlightModifier::Mutable; 771 h |= HighlightModifier::Mutable;
765 } 772 }
773 if local.ty(db).as_callable(db).is_some() || local.ty(db).impls_fnonce(db) {
774 h |= HighlightModifier::Callable;
775 }
766 return h; 776 return h;
767 } 777 }
768 } 778 }
diff --git a/crates/ide/src/syntax_highlighting/html.rs b/crates/ide/src/syntax_highlighting/html.rs
index 57e2d2923..abcc5cccc 100644
--- a/crates/ide/src/syntax_highlighting/html.rs
+++ b/crates/ide/src/syntax_highlighting/html.rs
@@ -1,6 +1,6 @@
1//! Renders a bit of code as HTML. 1//! Renders a bit of code as HTML.
2 2
3use base_db::SourceDatabase; 3use ide_db::base_db::SourceDatabase;
4use oorandom::Rand32; 4use oorandom::Rand32;
5use stdx::format_to; 5use stdx::format_to;
6use syntax::{AstNode, TextRange, TextSize}; 6use syntax::{AstNode, TextRange, TextSize};
diff --git a/crates/ide/src/syntax_highlighting/injection.rs b/crates/ide/src/syntax_highlighting/injection.rs
index acd91b26c..59a74bc02 100644
--- a/crates/ide/src/syntax_highlighting/injection.rs
+++ b/crates/ide/src/syntax_highlighting/injection.rs
@@ -3,8 +3,8 @@
3use std::{collections::BTreeMap, convert::TryFrom}; 3use std::{collections::BTreeMap, convert::TryFrom};
4 4
5use ast::{HasQuotes, HasStringValue}; 5use ast::{HasQuotes, HasStringValue};
6use call_info::ActiveParameter;
7use hir::Semantics; 6use hir::Semantics;
7use ide_db::call_info::ActiveParameter;
8use itertools::Itertools; 8use itertools::Itertools;
9use syntax::{ast, AstToken, SyntaxNode, SyntaxToken, TextRange, TextSize}; 9use syntax::{ast, AstToken, SyntaxNode, SyntaxToken, TextRange, TextSize};
10 10
diff --git a/crates/ide/src/syntax_highlighting/tags.rs b/crates/ide/src/syntax_highlighting/tags.rs
index c1b817f06..e8f78ad52 100644
--- a/crates/ide/src/syntax_highlighting/tags.rs
+++ b/crates/ide/src/syntax_highlighting/tags.rs
@@ -64,6 +64,7 @@ pub enum HighlightModifier {
64 Mutable, 64 Mutable,
65 Consuming, 65 Consuming,
66 Unsafe, 66 Unsafe,
67 Callable,
67} 68}
68 69
69impl HighlightTag { 70impl HighlightTag {
@@ -122,6 +123,7 @@ impl HighlightModifier {
122 HighlightModifier::Mutable, 123 HighlightModifier::Mutable,
123 HighlightModifier::Consuming, 124 HighlightModifier::Consuming,
124 HighlightModifier::Unsafe, 125 HighlightModifier::Unsafe,
126 HighlightModifier::Callable,
125 ]; 127 ];
126 128
127 fn as_str(self) -> &'static str { 129 fn as_str(self) -> &'static str {
@@ -134,6 +136,7 @@ impl HighlightModifier {
134 HighlightModifier::Mutable => "mutable", 136 HighlightModifier::Mutable => "mutable",
135 HighlightModifier::Consuming => "consuming", 137 HighlightModifier::Consuming => "consuming",
136 HighlightModifier::Unsafe => "unsafe", 138 HighlightModifier::Unsafe => "unsafe",
139 HighlightModifier::Callable => "callable",
137 } 140 }
138 } 141 }
139 142
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlighting.html b/crates/ide/src/syntax_highlighting/test_data/highlighting.html
index 0bb0928e4..c6b4f5a00 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlighting.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlighting.html
@@ -44,6 +44,17 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
44 <span class="keyword">pub</span> <span class="keyword">trait</span> <span class="trait declaration">Copy</span> <span class="punctuation">{</span><span class="punctuation">}</span> 44 <span class="keyword">pub</span> <span class="keyword">trait</span> <span class="trait declaration">Copy</span> <span class="punctuation">{</span><span class="punctuation">}</span>
45<span class="punctuation">}</span> 45<span class="punctuation">}</span>
46 46
47<span class="keyword">pub</span> <span class="keyword">mod</span> <span class="module declaration">ops</span> <span class="punctuation">{</span>
48 <span class="attribute">#</span><span class="attribute">[</span><span class="function attribute">lang</span><span class="attribute"> </span><span class="operator">=</span><span class="attribute"> </span><span class="string_literal">"fn_once"</span><span class="attribute">]</span>
49 <span class="keyword">pub</span> <span class="keyword">trait</span> <span class="trait declaration">FnOnce</span><span class="punctuation">&lt;</span><span class="type_param declaration">Args</span><span class="punctuation">&gt;</span> <span class="punctuation">{</span><span class="punctuation">}</span>
50
51 <span class="attribute">#</span><span class="attribute">[</span><span class="function attribute">lang</span><span class="attribute"> </span><span class="operator">=</span><span class="attribute"> </span><span class="string_literal">"fn_mut"</span><span class="attribute">]</span>
52 <span class="keyword">pub</span> <span class="keyword">trait</span> <span class="trait declaration">FnMut</span><span class="punctuation">&lt;</span><span class="type_param declaration">Args</span><span class="punctuation">&gt;</span><span class="punctuation">:</span> <span class="trait">FnOnce</span><span class="punctuation">&lt;</span><span class="type_param">Args</span><span class="punctuation">&gt;</span> <span class="punctuation">{</span><span class="punctuation">}</span>
53
54 <span class="attribute">#</span><span class="attribute">[</span><span class="function attribute">lang</span><span class="attribute"> </span><span class="operator">=</span><span class="attribute"> </span><span class="string_literal">"fn"</span><span class="attribute">]</span>
55 <span class="keyword">pub</span> <span class="keyword">trait</span> <span class="trait declaration">Fn</span><span class="punctuation">&lt;</span><span class="type_param declaration">Args</span><span class="punctuation">&gt;</span><span class="punctuation">:</span> <span class="trait">FnMut</span><span class="punctuation">&lt;</span><span class="type_param">Args</span><span class="punctuation">&gt;</span> <span class="punctuation">{</span><span class="punctuation">}</span>
56<span class="punctuation">}</span>
57
47 58
48<span class="keyword">struct</span> <span class="struct declaration">Foo</span> <span class="punctuation">{</span> 59<span class="keyword">struct</span> <span class="struct declaration">Foo</span> <span class="punctuation">{</span>
49 <span class="keyword">pub</span> <span class="field declaration">x</span><span class="punctuation">:</span> <span class="builtin_type">i32</span><span class="punctuation">,</span> 60 <span class="keyword">pub</span> <span class="field declaration">x</span><span class="punctuation">:</span> <span class="builtin_type">i32</span><span class="punctuation">,</span>
@@ -99,6 +110,11 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
99 <span class="function">foo</span><span class="operator">::</span><span class="punctuation">&lt;</span><span class="lifetime">'a</span><span class="punctuation">,</span> <span class="builtin_type">i32</span><span class="punctuation">&gt;</span><span class="punctuation">(</span><span class="punctuation">)</span> 110 <span class="function">foo</span><span class="operator">::</span><span class="punctuation">&lt;</span><span class="lifetime">'a</span><span class="punctuation">,</span> <span class="builtin_type">i32</span><span class="punctuation">&gt;</span><span class="punctuation">(</span><span class="punctuation">)</span>
100<span class="punctuation">}</span> 111<span class="punctuation">}</span>
101 112
113<span class="keyword">use</span> <span class="module">ops</span><span class="operator">::</span><span class="trait">Fn</span><span class="punctuation">;</span>
114<span class="keyword">fn</span> <span class="function declaration">baz</span><span class="punctuation">&lt;</span><span class="type_param declaration">F</span><span class="punctuation">:</span> <span class="trait">Fn</span><span class="punctuation">(</span><span class="punctuation">)</span> <span class="operator">-&gt;</span> <span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">&gt;</span><span class="punctuation">(</span><span class="value_param declaration callable">f</span><span class="punctuation">:</span> <span class="type_param">F</span><span class="punctuation">)</span> <span class="punctuation">{</span>
115 <span class="value_param callable">f</span><span class="punctuation">(</span><span class="punctuation">)</span>
116<span class="punctuation">}</span>
117
102<span class="macro">macro_rules!</span> <span class="macro declaration">def_fn</span> <span class="punctuation">{</span> 118<span class="macro">macro_rules!</span> <span class="macro declaration">def_fn</span> <span class="punctuation">{</span>
103 <span class="punctuation">(</span><span class="punctuation">$</span><span class="punctuation">(</span><span class="punctuation">$</span>tt<span class="punctuation">:</span>tt<span class="punctuation">)</span><span class="punctuation">*</span><span class="punctuation">)</span> <span class="operator">=</span><span class="punctuation">&gt;</span> <span class="punctuation">{</span><span class="punctuation">$</span><span class="punctuation">(</span><span class="punctuation">$</span>tt<span class="punctuation">)</span><span class="punctuation">*</span><span class="punctuation">}</span> 119 <span class="punctuation">(</span><span class="punctuation">$</span><span class="punctuation">(</span><span class="punctuation">$</span>tt<span class="punctuation">:</span>tt<span class="punctuation">)</span><span class="punctuation">*</span><span class="punctuation">)</span> <span class="operator">=</span><span class="punctuation">&gt;</span> <span class="punctuation">{</span><span class="punctuation">$</span><span class="punctuation">(</span><span class="punctuation">$</span>tt<span class="punctuation">)</span><span class="punctuation">*</span><span class="punctuation">}</span>
104<span class="punctuation">}</span> 120<span class="punctuation">}</span>
@@ -157,6 +173,12 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
157 <span class="variable mutable">copy</span><span class="punctuation">.</span><span class="function">quop</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span> 173 <span class="variable mutable">copy</span><span class="punctuation">.</span><span class="function">quop</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span>
158 <span class="variable mutable">copy</span><span class="punctuation">.</span><span class="function mutable">qux</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span> 174 <span class="variable mutable">copy</span><span class="punctuation">.</span><span class="function mutable">qux</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span>
159 <span class="variable mutable">copy</span><span class="punctuation">.</span><span class="function">baz</span><span class="punctuation">(</span><span class="variable mutable">copy</span><span class="punctuation">)</span><span class="punctuation">;</span> 175 <span class="variable mutable">copy</span><span class="punctuation">.</span><span class="function">baz</span><span class="punctuation">(</span><span class="variable mutable">copy</span><span class="punctuation">)</span><span class="punctuation">;</span>
176
177 <span class="keyword">let</span> <span class="variable declaration callable">a</span> <span class="operator">=</span> <span class="punctuation">|</span><span class="value_param declaration">x</span><span class="punctuation">|</span> <span class="value_param">x</span><span class="punctuation">;</span>
178 <span class="keyword">let</span> <span class="variable declaration callable">bar</span> <span class="operator">=</span> <span class="struct">Foo</span><span class="operator">::</span><span class="function">baz</span><span class="punctuation">;</span>
179
180 <span class="keyword">let</span> <span class="variable declaration">baz</span> <span class="operator">=</span> <span class="numeric_literal">-</span><span class="numeric_literal">42</span><span class="punctuation">;</span>
181 <span class="keyword">let</span> <span class="variable declaration">baz</span> <span class="operator">=</span> <span class="operator">-</span><span class="variable">baz</span><span class="punctuation">;</span>
160<span class="punctuation">}</span> 182<span class="punctuation">}</span>
161 183
162<span class="keyword">enum</span> <span class="enum declaration">Option</span><span class="punctuation">&lt;</span><span class="type_param declaration">T</span><span class="punctuation">&gt;</span> <span class="punctuation">{</span> 184<span class="keyword">enum</span> <span class="enum declaration">Option</span><span class="punctuation">&lt;</span><span class="type_param declaration">T</span><span class="punctuation">&gt;</span> <span class="punctuation">{</span>
diff --git a/crates/ide/src/syntax_highlighting/tests.rs b/crates/ide/src/syntax_highlighting/tests.rs
index 126363b8b..dd43f9dd9 100644
--- a/crates/ide/src/syntax_highlighting/tests.rs
+++ b/crates/ide/src/syntax_highlighting/tests.rs
@@ -18,6 +18,17 @@ pub mod marker {
18 pub trait Copy {} 18 pub trait Copy {}
19} 19}
20 20
21pub mod ops {
22 #[lang = "fn_once"]
23 pub trait FnOnce<Args> {}
24
25 #[lang = "fn_mut"]
26 pub trait FnMut<Args>: FnOnce<Args> {}
27
28 #[lang = "fn"]
29 pub trait Fn<Args>: FnMut<Args> {}
30}
31
21 32
22struct Foo { 33struct Foo {
23 pub x: i32, 34 pub x: i32,
@@ -73,6 +84,11 @@ fn foo<'a, T>() -> T {
73 foo::<'a, i32>() 84 foo::<'a, i32>()
74} 85}
75 86
87use ops::Fn;
88fn baz<F: Fn() -> ()>(f: F) {
89 f()
90}
91
76macro_rules! def_fn { 92macro_rules! def_fn {
77 ($($tt:tt)*) => {$($tt)*} 93 ($($tt:tt)*) => {$($tt)*}
78} 94}
@@ -131,6 +147,12 @@ fn main() {
131 copy.quop(); 147 copy.quop();
132 copy.qux(); 148 copy.qux();
133 copy.baz(copy); 149 copy.baz(copy);
150
151 let a = |x| x;
152 let bar = Foo::baz;
153
154 let baz = -42;
155 let baz = -baz;
134} 156}
135 157
136enum Option<T> { 158enum Option<T> {
diff --git a/crates/ide/src/syntax_tree.rs b/crates/ide/src/syntax_tree.rs
index 0eed2dbd7..7941610d6 100644
--- a/crates/ide/src/syntax_tree.rs
+++ b/crates/ide/src/syntax_tree.rs
@@ -1,4 +1,4 @@
1use base_db::{FileId, SourceDatabase}; 1use ide_db::base_db::{FileId, SourceDatabase};
2use ide_db::RootDatabase; 2use ide_db::RootDatabase;
3use syntax::{ 3use syntax::{
4 algo, AstNode, NodeOrToken, SourceFile, 4 algo, AstNode, NodeOrToken, SourceFile,
diff --git a/crates/ide/src/typing.rs b/crates/ide/src/typing.rs
index 94b91f049..43458a3a2 100644
--- a/crates/ide/src/typing.rs
+++ b/crates/ide/src/typing.rs
@@ -15,7 +15,7 @@
15 15
16mod on_enter; 16mod on_enter;
17 17
18use base_db::{FilePosition, SourceDatabase}; 18use ide_db::base_db::{FilePosition, SourceDatabase};
19use ide_db::{source_change::SourceFileEdit, RootDatabase}; 19use ide_db::{source_change::SourceFileEdit, RootDatabase};
20use syntax::{ 20use syntax::{
21 algo::find_node_at_offset, 21 algo::find_node_at_offset,
diff --git a/crates/ide/src/typing/on_enter.rs b/crates/ide/src/typing/on_enter.rs
index 98adef1d6..f4ea30352 100644
--- a/crates/ide/src/typing/on_enter.rs
+++ b/crates/ide/src/typing/on_enter.rs
@@ -1,7 +1,7 @@
1//! Handles the `Enter` key press. At the momently, this only continues 1//! Handles the `Enter` key press. At the momently, this only continues
2//! comments, but should handle indent some time in the future as well. 2//! comments, but should handle indent some time in the future as well.
3 3
4use base_db::{FilePosition, SourceDatabase}; 4use ide_db::base_db::{FilePosition, SourceDatabase};
5use ide_db::RootDatabase; 5use ide_db::RootDatabase;
6use syntax::{ 6use syntax::{
7 ast::{self, AstToken}, 7 ast::{self, AstToken},
diff --git a/crates/ide_db/Cargo.toml b/crates/ide_db/Cargo.toml
index 320fb15e5..72a9212f1 100644
--- a/crates/ide_db/Cargo.toml
+++ b/crates/ide_db/Cargo.toml
@@ -14,7 +14,7 @@ wasm = []
14 14
15[dependencies] 15[dependencies]
16log = "0.4.8" 16log = "0.4.8"
17rayon = "1.3.0" 17rayon = "1.5.0"
18fst = { version = "0.4", default-features = false } 18fst = { version = "0.4", default-features = false }
19rustc-hash = "1.1.0" 19rustc-hash = "1.1.0"
20once_cell = "1.3.1" 20once_cell = "1.3.1"
@@ -29,3 +29,6 @@ test_utils = { path = "../test_utils", version = "0.0.0" }
29# ide should depend only on the top-level `hir` package. if you need 29# ide should depend only on the top-level `hir` package. if you need
30# something from some `hir_xxx` subpackage, reexport the API via `hir`. 30# something from some `hir_xxx` subpackage, reexport the API via `hir`.
31hir = { path = "../hir", version = "0.0.0" } 31hir = { path = "../hir", version = "0.0.0" }
32
33[dev-dependencies]
34expect-test = "1.0"
diff --git a/crates/call_info/src/lib.rs b/crates/ide_db/src/call_info.rs
index c45406c25..83a602b9a 100644
--- a/crates/call_info/src/lib.rs
+++ b/crates/ide_db/src/call_info.rs
@@ -2,7 +2,6 @@
2use base_db::FilePosition; 2use base_db::FilePosition;
3use either::Either; 3use either::Either;
4use hir::{HasAttrs, HirDisplay, Semantics, Type}; 4use hir::{HasAttrs, HirDisplay, Semantics, Type};
5use ide_db::RootDatabase;
6use stdx::format_to; 5use stdx::format_to;
7use syntax::{ 6use syntax::{
8 ast::{self, ArgListOwner}, 7 ast::{self, ArgListOwner},
@@ -10,6 +9,8 @@ use syntax::{
10}; 9};
11use test_utils::mark; 10use test_utils::mark;
12 11
12use crate::RootDatabase;
13
13/// Contains information about a call site. Specifically the 14/// Contains information about a call site. Specifically the
14/// `FunctionSignature`and current parameter. 15/// `FunctionSignature`and current parameter.
15#[derive(Debug)] 16#[derive(Debug)]
@@ -228,9 +229,9 @@ impl FnCallNode {
228 229
229#[cfg(test)] 230#[cfg(test)]
230mod tests { 231mod tests {
232 use crate::RootDatabase;
231 use base_db::{fixture::ChangeFixture, FilePosition}; 233 use base_db::{fixture::ChangeFixture, FilePosition};
232 use expect_test::{expect, Expect}; 234 use expect_test::{expect, Expect};
233 use ide_db::RootDatabase;
234 use test_utils::{mark, RangeOrOffset}; 235 use test_utils::{mark, RangeOrOffset};
235 236
236 /// Creates analysis from a multi-file fixture, returns positions marked with <|>. 237 /// Creates analysis from a multi-file fixture, returns positions marked with <|>.
@@ -249,7 +250,7 @@ mod tests {
249 250
250 fn check(ra_fixture: &str, expect: Expect) { 251 fn check(ra_fixture: &str, expect: Expect) {
251 let (db, position) = position(ra_fixture); 252 let (db, position) = position(ra_fixture);
252 let call_info = crate::call_info(&db, position); 253 let call_info = crate::call_info::call_info(&db, position);
253 let actual = match call_info { 254 let actual = match call_info {
254 Some(call_info) => { 255 Some(call_info) => {
255 let docs = match &call_info.doc { 256 let docs = match &call_info.doc {
diff --git a/crates/ide_db/src/lib.rs b/crates/ide_db/src/lib.rs
index 7eff247c7..38ebdbf79 100644
--- a/crates/ide_db/src/lib.rs
+++ b/crates/ide_db/src/lib.rs
@@ -10,6 +10,9 @@ pub mod defs;
10pub mod search; 10pub mod search;
11pub mod imports_locator; 11pub mod imports_locator;
12pub mod source_change; 12pub mod source_change;
13pub mod ty_filter;
14pub mod traits;
15pub mod call_info;
13 16
14use std::{fmt, sync::Arc}; 17use std::{fmt, sync::Arc};
15 18
@@ -23,6 +26,9 @@ use rustc_hash::FxHashSet;
23 26
24use crate::{line_index::LineIndex, symbol_index::SymbolsDatabase}; 27use crate::{line_index::LineIndex, symbol_index::SymbolsDatabase};
25 28
29/// `base_db` is normally also needed in places where `ide_db` is used, so this re-export is for convenience.
30pub use base_db;
31
26#[salsa::database( 32#[salsa::database(
27 base_db::SourceDatabaseStorage, 33 base_db::SourceDatabaseStorage,
28 base_db::SourceDatabaseExtStorage, 34 base_db::SourceDatabaseExtStorage,
diff --git a/crates/ide_db/src/traits.rs b/crates/ide_db/src/traits.rs
new file mode 100644
index 000000000..f57b6dd91
--- /dev/null
+++ b/crates/ide_db/src/traits.rs
@@ -0,0 +1,227 @@
1//! Functionality for obtaining data related to traits from the DB.
2
3use crate::RootDatabase;
4use hir::Semantics;
5use rustc_hash::FxHashSet;
6use syntax::{
7 ast::{self, NameOwner},
8 AstNode,
9};
10
11/// Given the `impl` block, attempts to find the trait this `impl` corresponds to.
12pub fn resolve_target_trait(
13 sema: &Semantics<RootDatabase>,
14 impl_def: &ast::Impl,
15) -> Option<hir::Trait> {
16 let ast_path =
17 impl_def.trait_().map(|it| it.syntax().clone()).and_then(ast::PathType::cast)?.path()?;
18
19 match sema.resolve_path(&ast_path) {
20 Some(hir::PathResolution::Def(hir::ModuleDef::Trait(def))) => Some(def),
21 _ => None,
22 }
23}
24
25/// Given the `impl` block, returns the list of associated items (e.g. functions or types) that are
26/// missing in this `impl` block.
27pub fn get_missing_assoc_items(
28 sema: &Semantics<RootDatabase>,
29 impl_def: &ast::Impl,
30) -> Vec<hir::AssocItem> {
31 // Names must be unique between constants and functions. However, type aliases
32 // may share the same name as a function or constant.
33 let mut impl_fns_consts = FxHashSet::default();
34 let mut impl_type = FxHashSet::default();
35
36 if let Some(item_list) = impl_def.assoc_item_list() {
37 for item in item_list.assoc_items() {
38 match item {
39 ast::AssocItem::Fn(f) => {
40 if let Some(n) = f.name() {
41 impl_fns_consts.insert(n.syntax().to_string());
42 }
43 }
44
45 ast::AssocItem::TypeAlias(t) => {
46 if let Some(n) = t.name() {
47 impl_type.insert(n.syntax().to_string());
48 }
49 }
50
51 ast::AssocItem::Const(c) => {
52 if let Some(n) = c.name() {
53 impl_fns_consts.insert(n.syntax().to_string());
54 }
55 }
56 ast::AssocItem::MacroCall(_) => (),
57 }
58 }
59 }
60
61 resolve_target_trait(sema, impl_def).map_or(vec![], |target_trait| {
62 target_trait
63 .items(sema.db)
64 .iter()
65 .filter(|i| match i {
66 hir::AssocItem::Function(f) => {
67 !impl_fns_consts.contains(&f.name(sema.db).to_string())
68 }
69 hir::AssocItem::TypeAlias(t) => !impl_type.contains(&t.name(sema.db).to_string()),
70 hir::AssocItem::Const(c) => c
71 .name(sema.db)
72 .map(|n| !impl_fns_consts.contains(&n.to_string()))
73 .unwrap_or_default(),
74 })
75 .cloned()
76 .collect()
77 })
78}
79
80#[cfg(test)]
81mod tests {
82 use crate::RootDatabase;
83 use base_db::{fixture::ChangeFixture, FilePosition};
84 use expect_test::{expect, Expect};
85 use hir::Semantics;
86 use syntax::ast::{self, AstNode};
87 use test_utils::RangeOrOffset;
88
89 /// Creates analysis from a multi-file fixture, returns positions marked with <|>.
90 pub(crate) fn position(ra_fixture: &str) -> (RootDatabase, FilePosition) {
91 let change_fixture = ChangeFixture::parse(ra_fixture);
92 let mut database = RootDatabase::default();
93 database.apply_change(change_fixture.change);
94 let (file_id, range_or_offset) =
95 change_fixture.file_position.expect("expected a marker (<|>)");
96 let offset = match range_or_offset {
97 RangeOrOffset::Range(_) => panic!(),
98 RangeOrOffset::Offset(it) => it,
99 };
100 (database, FilePosition { file_id, offset })
101 }
102
103 fn check_trait(ra_fixture: &str, expect: Expect) {
104 let (db, position) = position(ra_fixture);
105 let sema = Semantics::new(&db);
106 let file = sema.parse(position.file_id);
107 let impl_block: ast::Impl =
108 sema.find_node_at_offset_with_descend(file.syntax(), position.offset).unwrap();
109 let trait_ = crate::traits::resolve_target_trait(&sema, &impl_block);
110 let actual = match trait_ {
111 Some(trait_) => trait_.name(&db).to_string(),
112 None => String::new(),
113 };
114 expect.assert_eq(&actual);
115 }
116
117 fn check_missing_assoc(ra_fixture: &str, expect: Expect) {
118 let (db, position) = position(ra_fixture);
119 let sema = Semantics::new(&db);
120 let file = sema.parse(position.file_id);
121 let impl_block: ast::Impl =
122 sema.find_node_at_offset_with_descend(file.syntax(), position.offset).unwrap();
123 let items = crate::traits::get_missing_assoc_items(&sema, &impl_block);
124 let actual = items
125 .into_iter()
126 .map(|item| item.name(&db).unwrap().to_string())
127 .collect::<Vec<_>>()
128 .join("\n");
129 expect.assert_eq(&actual);
130 }
131
132 #[test]
133 fn resolve_trait() {
134 check_trait(
135 r#"
136pub trait Foo {
137 fn bar();
138}
139impl Foo for u8 {
140 <|>
141}
142 "#,
143 expect![["Foo"]],
144 );
145 check_trait(
146 r#"
147pub trait Foo {
148 fn bar();
149}
150impl Foo for u8 {
151 fn bar() {
152 fn baz() {
153 <|>
154 }
155 baz();
156 }
157}
158 "#,
159 expect![["Foo"]],
160 );
161 check_trait(
162 r#"
163pub trait Foo {
164 fn bar();
165}
166pub struct Bar;
167impl Bar {
168 <|>
169}
170 "#,
171 expect![[""]],
172 );
173 }
174
175 #[test]
176 fn missing_assoc_items() {
177 check_missing_assoc(
178 r#"
179pub trait Foo {
180 const FOO: u8;
181 fn bar();
182}
183impl Foo for u8 {
184 <|>
185}"#,
186 expect![[r#"
187 FOO
188 bar"#]],
189 );
190
191 check_missing_assoc(
192 r#"
193pub trait Foo {
194 const FOO: u8;
195 fn bar();
196}
197impl Foo for u8 {
198 const FOO: u8 = 10;
199 <|>
200}"#,
201 expect![[r#"
202 bar"#]],
203 );
204
205 check_missing_assoc(
206 r#"
207pub trait Foo {
208 const FOO: u8;
209 fn bar();
210}
211impl Foo for u8 {
212 const FOO: u8 = 10;
213 fn bar() {<|>}
214}"#,
215 expect![[r#""#]],
216 );
217
218 check_missing_assoc(
219 r#"
220pub struct Foo;
221impl Foo {
222 fn bar() {<|>}
223}"#,
224 expect![[r#""#]],
225 );
226 }
227}
diff --git a/crates/ide_db/src/ty_filter.rs b/crates/ide_db/src/ty_filter.rs
new file mode 100644
index 000000000..63a945282
--- /dev/null
+++ b/crates/ide_db/src/ty_filter.rs
@@ -0,0 +1,58 @@
1//! This module contains structures for filtering the expected types.
2//! Use case for structures in this module is, for example, situation when you need to process
3//! only certain `Enum`s.
4
5use crate::RootDatabase;
6use hir::{Adt, Semantics, Type};
7use std::iter;
8use syntax::ast::{self, make};
9
10/// Enum types that implement `std::ops::Try` trait.
11#[derive(Clone, Copy)]
12pub enum TryEnum {
13 Result,
14 Option,
15}
16
17impl TryEnum {
18 const ALL: [TryEnum; 2] = [TryEnum::Option, TryEnum::Result];
19
20 /// Returns `Some(..)` if the provided type is an enum that implements `std::ops::Try`.
21 pub fn from_ty(sema: &Semantics<RootDatabase>, ty: &Type) -> Option<TryEnum> {
22 let enum_ = match ty.as_adt() {
23 Some(Adt::Enum(it)) => it,
24 _ => return None,
25 };
26 TryEnum::ALL.iter().find_map(|&var| {
27 if &enum_.name(sema.db).to_string() == var.type_name() {
28 return Some(var);
29 }
30 None
31 })
32 }
33
34 pub fn happy_case(self) -> &'static str {
35 match self {
36 TryEnum::Result => "Ok",
37 TryEnum::Option => "Some",
38 }
39 }
40
41 pub fn sad_pattern(self) -> ast::Pat {
42 match self {
43 TryEnum::Result => make::tuple_struct_pat(
44 make::path_unqualified(make::path_segment(make::name_ref("Err"))),
45 iter::once(make::wildcard_pat().into()),
46 )
47 .into(),
48 TryEnum::Option => make::ident_pat(make::name("None")).into(),
49 }
50 }
51
52 fn type_name(self) -> &'static str {
53 match self {
54 TryEnum::Result => "Result",
55 TryEnum::Option => "Option",
56 }
57 }
58}
diff --git a/crates/rust-analyzer/Cargo.toml b/crates/rust-analyzer/Cargo.toml
index 66cf06e1a..975b24aaf 100644
--- a/crates/rust-analyzer/Cargo.toml
+++ b/crates/rust-analyzer/Cargo.toml
@@ -29,9 +29,12 @@ rustc-hash = "1.1.0"
29serde = { version = "1.0.106", features = ["derive"] } 29serde = { version = "1.0.106", features = ["derive"] }
30serde_json = "1.0.48" 30serde_json = "1.0.48"
31threadpool = "1.7.1" 31threadpool = "1.7.1"
32rayon = "1.3.1" 32rayon = "1.5"
33mimalloc = { version = "0.1.19", default-features = false, optional = true } 33mimalloc = { version = "0.1.19", default-features = false, optional = true }
34lsp-server = "0.4.0" 34lsp-server = "0.4.0"
35tracing = "0.1"
36tracing-subscriber = { version = "0.2", default-features = false, features = ["env-filter", "registry"] }
37tracing-tree = { version = "0.1.4" }
35 38
36stdx = { path = "../stdx", version = "0.0.0" } 39stdx = { path = "../stdx", version = "0.0.0" }
37flycheck = { path = "../flycheck", version = "0.0.0" } 40flycheck = { path = "../flycheck", version = "0.0.0" }
@@ -46,7 +49,6 @@ cfg = { path = "../cfg", version = "0.0.0" }
46toolchain = { path = "../toolchain", version = "0.0.0" } 49toolchain = { path = "../toolchain", version = "0.0.0" }
47 50
48# This should only be used in CLI 51# This should only be used in CLI
49base_db = { path = "../base_db", version = "0.0.0" }
50ide_db = { path = "../ide_db", version = "0.0.0" } 52ide_db = { path = "../ide_db", version = "0.0.0" }
51ssr = { path = "../ssr", version = "0.0.0" } 53ssr = { path = "../ssr", version = "0.0.0" }
52hir = { path = "../hir", version = "0.0.0" } 54hir = { path = "../hir", version = "0.0.0" }
diff --git a/crates/rust-analyzer/src/bin/main.rs b/crates/rust-analyzer/src/bin/main.rs
index 97b246a32..4175e569e 100644
--- a/crates/rust-analyzer/src/bin/main.rs
+++ b/crates/rust-analyzer/src/bin/main.rs
@@ -68,10 +68,32 @@ fn setup_logging(log_file: Option<PathBuf>) -> Result<()> {
68 let filter = env::var("RA_LOG").ok(); 68 let filter = env::var("RA_LOG").ok();
69 logger::Logger::new(log_file, filter.as_deref()).install(); 69 logger::Logger::new(log_file, filter.as_deref()).install();
70 70
71 tracing_setup::setup_tracing()?;
72
71 profile::init(); 73 profile::init();
72 Ok(()) 74 Ok(())
73} 75}
74 76
77mod tracing_setup {
78 use tracing::subscriber;
79 use tracing_subscriber::layer::SubscriberExt;
80 use tracing_subscriber::EnvFilter;
81 use tracing_subscriber::Registry;
82 use tracing_tree::HierarchicalLayer;
83
84 pub fn setup_tracing() -> super::Result<()> {
85 let filter = EnvFilter::from_env("CHALK_DEBUG");
86 let layer = HierarchicalLayer::default()
87 .with_indent_lines(true)
88 .with_ansi(false)
89 .with_indent_amount(2)
90 .with_writer(std::io::stderr);
91 let subscriber = Registry::default().with(filter).with(layer);
92 subscriber::set_global_default(subscriber)?;
93 Ok(())
94 }
95}
96
75fn run_server() -> Result<()> { 97fn run_server() -> Result<()> {
76 log::info!("server will start"); 98 log::info!("server will start");
77 99
diff --git a/crates/rust-analyzer/src/cargo_target_spec.rs b/crates/rust-analyzer/src/cargo_target_spec.rs
index ddc028148..1ab72bd91 100644
--- a/crates/rust-analyzer/src/cargo_target_spec.rs
+++ b/crates/rust-analyzer/src/cargo_target_spec.rs
@@ -1,6 +1,6 @@
1//! See `CargoTargetSpec` 1//! See `CargoTargetSpec`
2 2
3use cfg::CfgExpr; 3use cfg::{CfgAtom, CfgExpr};
4use ide::{FileId, RunnableKind, TestId}; 4use ide::{FileId, RunnableKind, TestId};
5use project_model::{self, TargetKind}; 5use project_model::{self, TargetKind};
6use vfs::AbsPathBuf; 6use vfs::AbsPathBuf;
@@ -24,7 +24,7 @@ impl CargoTargetSpec {
24 snap: &GlobalStateSnapshot, 24 snap: &GlobalStateSnapshot,
25 spec: Option<CargoTargetSpec>, 25 spec: Option<CargoTargetSpec>,
26 kind: &RunnableKind, 26 kind: &RunnableKind,
27 cfgs: &[CfgExpr], 27 cfg: &Option<CfgExpr>,
28 ) -> Result<(Vec<String>, Vec<String>)> { 28 ) -> Result<(Vec<String>, Vec<String>)> {
29 let mut args = Vec::new(); 29 let mut args = Vec::new();
30 let mut extra_args = Vec::new(); 30 let mut extra_args = Vec::new();
@@ -87,7 +87,7 @@ impl CargoTargetSpec {
87 args.push("--all-features".to_string()); 87 args.push("--all-features".to_string());
88 } else { 88 } else {
89 let mut features = Vec::new(); 89 let mut features = Vec::new();
90 for cfg in cfgs { 90 if let Some(cfg) = cfg.as_ref() {
91 required_features(cfg, &mut features); 91 required_features(cfg, &mut features);
92 } 92 }
93 for feature in &snap.config.cargo.features { 93 for feature in &snap.config.cargo.features {
@@ -160,7 +160,9 @@ impl CargoTargetSpec {
160/// Fill minimal features needed 160/// Fill minimal features needed
161fn required_features(cfg_expr: &CfgExpr, features: &mut Vec<String>) { 161fn required_features(cfg_expr: &CfgExpr, features: &mut Vec<String>) {
162 match cfg_expr { 162 match cfg_expr {
163 CfgExpr::KeyValue { key, value } if key == "feature" => features.push(value.to_string()), 163 CfgExpr::Atom(CfgAtom::KeyValue { key, value }) if key == "feature" => {
164 features.push(value.to_string())
165 }
164 CfgExpr::All(preds) => { 166 CfgExpr::All(preds) => {
165 preds.iter().for_each(|cfg| required_features(cfg, features)); 167 preds.iter().for_each(|cfg| required_features(cfg, features));
166 } 168 }
diff --git a/crates/rust-analyzer/src/cli/analysis_bench.rs b/crates/rust-analyzer/src/cli/analysis_bench.rs
index d1c095ba5..8e33986d5 100644
--- a/crates/rust-analyzer/src/cli/analysis_bench.rs
+++ b/crates/rust-analyzer/src/cli/analysis_bench.rs
@@ -3,13 +3,13 @@
3use std::{env, path::PathBuf, str::FromStr, sync::Arc, time::Instant}; 3use std::{env, path::PathBuf, str::FromStr, sync::Arc, time::Instant};
4 4
5use anyhow::{bail, format_err, Result}; 5use anyhow::{bail, format_err, Result};
6use base_db::{
7 salsa::{Database, Durability},
8 FileId,
9};
10use ide::{ 6use ide::{
11 Analysis, AnalysisHost, Change, CompletionConfig, DiagnosticsConfig, FilePosition, LineCol, 7 Analysis, AnalysisHost, Change, CompletionConfig, DiagnosticsConfig, FilePosition, LineCol,
12}; 8};
9use ide_db::base_db::{
10 salsa::{Database, Durability},
11 FileId,
12};
13use vfs::AbsPathBuf; 13use vfs::AbsPathBuf;
14 14
15use crate::{ 15use crate::{
diff --git a/crates/rust-analyzer/src/cli/analysis_stats.rs b/crates/rust-analyzer/src/cli/analysis_stats.rs
index fb2b2b000..98ef0cd68 100644
--- a/crates/rust-analyzer/src/cli/analysis_stats.rs
+++ b/crates/rust-analyzer/src/cli/analysis_stats.rs
@@ -6,16 +6,16 @@ use std::{
6 time::{SystemTime, UNIX_EPOCH}, 6 time::{SystemTime, UNIX_EPOCH},
7}; 7};
8 8
9use base_db::{
10 salsa::{self, ParallelDatabase},
11 SourceDatabaseExt,
12};
13use hir::{ 9use hir::{
14 db::{AstDatabase, DefDatabase, HirDatabase}, 10 db::{AstDatabase, DefDatabase, HirDatabase},
15 original_range, AssocItem, Crate, HasSource, HirDisplay, ModuleDef, 11 original_range, AssocItem, Crate, HasSource, HirDisplay, ModuleDef,
16}; 12};
17use hir_def::FunctionId; 13use hir_def::FunctionId;
18use hir_ty::{Ty, TypeWalk}; 14use hir_ty::{Ty, TypeWalk};
15use ide_db::base_db::{
16 salsa::{self, ParallelDatabase},
17 SourceDatabaseExt,
18};
19use itertools::Itertools; 19use itertools::Itertools;
20use oorandom::Rand32; 20use oorandom::Rand32;
21use rayon::prelude::*; 21use rayon::prelude::*;
diff --git a/crates/rust-analyzer/src/cli/diagnostics.rs b/crates/rust-analyzer/src/cli/diagnostics.rs
index a89993a2b..368f627ac 100644
--- a/crates/rust-analyzer/src/cli/diagnostics.rs
+++ b/crates/rust-analyzer/src/cli/diagnostics.rs
@@ -6,9 +6,9 @@ use std::path::Path;
6use anyhow::anyhow; 6use anyhow::anyhow;
7use rustc_hash::FxHashSet; 7use rustc_hash::FxHashSet;
8 8
9use base_db::SourceDatabaseExt;
10use hir::Crate; 9use hir::Crate;
11use ide::{DiagnosticsConfig, Severity}; 10use ide::{DiagnosticsConfig, Severity};
11use ide_db::base_db::SourceDatabaseExt;
12 12
13use crate::cli::{load_cargo::load_cargo, Result}; 13use crate::cli::{load_cargo::load_cargo, Result};
14 14
diff --git a/crates/rust-analyzer/src/cli/load_cargo.rs b/crates/rust-analyzer/src/cli/load_cargo.rs
index 7ae1c9055..ab1e2ab92 100644
--- a/crates/rust-analyzer/src/cli/load_cargo.rs
+++ b/crates/rust-analyzer/src/cli/load_cargo.rs
@@ -3,9 +3,9 @@
3use std::{path::Path, sync::Arc}; 3use std::{path::Path, sync::Arc};
4 4
5use anyhow::Result; 5use anyhow::Result;
6use base_db::CrateGraph;
7use crossbeam_channel::{unbounded, Receiver}; 6use crossbeam_channel::{unbounded, Receiver};
8use ide::{AnalysisHost, Change}; 7use ide::{AnalysisHost, Change};
8use ide_db::base_db::CrateGraph;
9use project_model::{CargoConfig, ProcMacroClient, ProjectManifest, ProjectWorkspace}; 9use project_model::{CargoConfig, ProcMacroClient, ProjectManifest, ProjectWorkspace};
10use vfs::{loader::Handle, AbsPath, AbsPathBuf}; 10use vfs::{loader::Handle, AbsPath, AbsPathBuf};
11 11
diff --git a/crates/rust-analyzer/src/cli/ssr.rs b/crates/rust-analyzer/src/cli/ssr.rs
index c11e10943..a06631dac 100644
--- a/crates/rust-analyzer/src/cli/ssr.rs
+++ b/crates/rust-analyzer/src/cli/ssr.rs
@@ -4,7 +4,7 @@ use crate::cli::{load_cargo::load_cargo, Result};
4use ssr::{MatchFinder, SsrPattern, SsrRule}; 4use ssr::{MatchFinder, SsrPattern, SsrRule};
5 5
6pub fn apply_ssr_rules(rules: Vec<SsrRule>) -> Result<()> { 6pub fn apply_ssr_rules(rules: Vec<SsrRule>) -> Result<()> {
7 use base_db::SourceDatabaseExt; 7 use ide_db::base_db::SourceDatabaseExt;
8 let (host, vfs) = load_cargo(&std::env::current_dir()?, true, true)?; 8 let (host, vfs) = load_cargo(&std::env::current_dir()?, true, true)?;
9 let db = host.raw_database(); 9 let db = host.raw_database();
10 let mut match_finder = MatchFinder::at_first_file(db)?; 10 let mut match_finder = MatchFinder::at_first_file(db)?;
@@ -26,7 +26,7 @@ pub fn apply_ssr_rules(rules: Vec<SsrRule>) -> Result<()> {
26/// `debug_snippet`. This is intended for debugging and probably isn't in it's current form useful 26/// `debug_snippet`. This is intended for debugging and probably isn't in it's current form useful
27/// for much else. 27/// for much else.
28pub fn search_for_patterns(patterns: Vec<SsrPattern>, debug_snippet: Option<String>) -> Result<()> { 28pub fn search_for_patterns(patterns: Vec<SsrPattern>, debug_snippet: Option<String>) -> Result<()> {
29 use base_db::SourceDatabaseExt; 29 use ide_db::base_db::SourceDatabaseExt;
30 use ide_db::symbol_index::SymbolsDatabase; 30 use ide_db::symbol_index::SymbolsDatabase;
31 let (host, _vfs) = load_cargo(&std::env::current_dir()?, true, true)?; 31 let (host, _vfs) = load_cargo(&std::env::current_dir()?, true, true)?;
32 let db = host.raw_database(); 32 let db = host.raw_database();
diff --git a/crates/rust-analyzer/src/dispatch.rs b/crates/rust-analyzer/src/dispatch.rs
index 9c8815e29..7a87515e9 100644
--- a/crates/rust-analyzer/src/dispatch.rs
+++ b/crates/rust-analyzer/src/dispatch.rs
@@ -34,7 +34,7 @@ impl<'a> RequestDispatcher<'a> {
34 }; 34 };
35 let world = panic::AssertUnwindSafe(&mut *self.global_state); 35 let world = panic::AssertUnwindSafe(&mut *self.global_state);
36 let response = panic::catch_unwind(move || { 36 let response = panic::catch_unwind(move || {
37 stdx::panic_context::enter(format!("request: {} {:#?}", R::METHOD, params)); 37 let _pctx = stdx::panic_context::enter(format!("request: {} {:#?}", R::METHOD, params));
38 let result = f(world.0, params); 38 let result = f(world.0, params);
39 result_to_response::<R>(id, result) 39 result_to_response::<R>(id, result)
40 }) 40 })
@@ -64,7 +64,7 @@ impl<'a> RequestDispatcher<'a> {
64 let world = self.global_state.snapshot(); 64 let world = self.global_state.snapshot();
65 65
66 move || { 66 move || {
67 let _ctx = 67 let _pctx =
68 stdx::panic_context::enter(format!("request: {} {:#?}", R::METHOD, params)); 68 stdx::panic_context::enter(format!("request: {} {:#?}", R::METHOD, params));
69 let result = f(world, params); 69 let result = f(world, params);
70 Task::Response(result_to_response::<R>(id, result)) 70 Task::Response(result_to_response::<R>(id, result))
@@ -160,7 +160,7 @@ impl<'a> NotificationDispatcher<'a> {
160 return Ok(self); 160 return Ok(self);
161 } 161 }
162 }; 162 };
163 stdx::panic_context::enter(format!("notification: {}", N::METHOD)); 163 let _pctx = stdx::panic_context::enter(format!("notification: {}", N::METHOD));
164 f(self.global_state, params)?; 164 f(self.global_state, params)?;
165 Ok(self) 165 Ok(self)
166 } 166 }
diff --git a/crates/rust-analyzer/src/from_proto.rs b/crates/rust-analyzer/src/from_proto.rs
index 5b9f52993..aa6b808d6 100644
--- a/crates/rust-analyzer/src/from_proto.rs
+++ b/crates/rust-analyzer/src/from_proto.rs
@@ -1,8 +1,8 @@
1//! Conversion lsp_types types to rust-analyzer specific ones. 1//! Conversion lsp_types types to rust-analyzer specific ones.
2use std::convert::TryFrom; 2use std::convert::TryFrom;
3 3
4use base_db::{FileId, FilePosition, FileRange};
5use ide::{AssistKind, LineCol, LineIndex}; 4use ide::{AssistKind, LineCol, LineIndex};
5use ide_db::base_db::{FileId, FilePosition, FileRange};
6use syntax::{TextRange, TextSize}; 6use syntax::{TextRange, TextSize};
7use vfs::AbsPathBuf; 7use vfs::AbsPathBuf;
8 8
diff --git a/crates/rust-analyzer/src/global_state.rs b/crates/rust-analyzer/src/global_state.rs
index dafab6a6a..673a2eebc 100644
--- a/crates/rust-analyzer/src/global_state.rs
+++ b/crates/rust-analyzer/src/global_state.rs
@@ -5,10 +5,10 @@
5 5
6use std::{sync::Arc, time::Instant}; 6use std::{sync::Arc, time::Instant};
7 7
8use base_db::{CrateId, VfsPath};
9use crossbeam_channel::{unbounded, Receiver, Sender}; 8use crossbeam_channel::{unbounded, Receiver, Sender};
10use flycheck::FlycheckHandle; 9use flycheck::FlycheckHandle;
11use ide::{Analysis, AnalysisHost, Change, FileId}; 10use ide::{Analysis, AnalysisHost, Change, FileId};
11use ide_db::base_db::{CrateId, VfsPath};
12use lsp_types::{SemanticTokens, Url}; 12use lsp_types::{SemanticTokens, Url};
13use parking_lot::{Mutex, RwLock}; 13use parking_lot::{Mutex, RwLock};
14use project_model::{CargoWorkspace, ProcMacroClient, ProjectWorkspace, Target}; 14use project_model::{CargoWorkspace, ProcMacroClient, ProjectWorkspace, Target};
diff --git a/crates/rust-analyzer/src/lsp_utils.rs b/crates/rust-analyzer/src/lsp_utils.rs
index bd888f634..1d271a9d8 100644
--- a/crates/rust-analyzer/src/lsp_utils.rs
+++ b/crates/rust-analyzer/src/lsp_utils.rs
@@ -1,8 +1,8 @@
1//! Utilities for LSP-related boilerplate code. 1//! Utilities for LSP-related boilerplate code.
2use std::{error::Error, ops::Range}; 2use std::{error::Error, ops::Range};
3 3
4use base_db::Canceled;
5use ide::LineIndex; 4use ide::LineIndex;
5use ide_db::base_db::Canceled;
6use lsp_server::Notification; 6use lsp_server::Notification;
7 7
8use crate::{from_proto, global_state::GlobalState}; 8use crate::{from_proto, global_state::GlobalState};
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs
index fb18f9014..ed5292733 100644
--- a/crates/rust-analyzer/src/main_loop.rs
+++ b/crates/rust-analyzer/src/main_loop.rs
@@ -5,10 +5,10 @@ use std::{
5 time::{Duration, Instant}, 5 time::{Duration, Instant},
6}; 6};
7 7
8use base_db::VfsPath;
9use crossbeam_channel::{select, Receiver}; 8use crossbeam_channel::{select, Receiver};
10use ide::PrimeCachesProgress; 9use ide::PrimeCachesProgress;
11use ide::{Canceled, FileId}; 10use ide::{Canceled, FileId};
11use ide_db::base_db::VfsPath;
12use lsp_server::{Connection, Notification, Request, Response}; 12use lsp_server::{Connection, Notification, Request, Response};
13use lsp_types::notification::Notification as _; 13use lsp_types::notification::Notification as _;
14use project_model::ProjectWorkspace; 14use project_model::ProjectWorkspace;
diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs
index f7215f129..0eabd51bd 100644
--- a/crates/rust-analyzer/src/reload.rs
+++ b/crates/rust-analyzer/src/reload.rs
@@ -1,9 +1,9 @@
1//! Project loading & configuration updates 1//! Project loading & configuration updates
2use std::{mem, sync::Arc}; 2use std::{mem, sync::Arc};
3 3
4use base_db::{CrateGraph, SourceRoot, VfsPath};
5use flycheck::{FlycheckConfig, FlycheckHandle}; 4use flycheck::{FlycheckConfig, FlycheckHandle};
6use ide::Change; 5use ide::Change;
6use ide_db::base_db::{CrateGraph, SourceRoot, VfsPath};
7use project_model::{ProcMacroClient, ProjectWorkspace}; 7use project_model::{ProcMacroClient, ProjectWorkspace};
8use vfs::{file_set::FileSetConfig, AbsPath, AbsPathBuf, ChangeKind}; 8use vfs::{file_set::FileSetConfig, AbsPath, AbsPathBuf, ChangeKind};
9 9
diff --git a/crates/rust-analyzer/src/semantic_tokens.rs b/crates/rust-analyzer/src/semantic_tokens.rs
index a6c4d6099..7df28c9dd 100644
--- a/crates/rust-analyzer/src/semantic_tokens.rs
+++ b/crates/rust-analyzer/src/semantic_tokens.rs
@@ -77,6 +77,7 @@ define_semantic_token_modifiers![
77 (CONSUMING, "consuming"), 77 (CONSUMING, "consuming"),
78 (UNSAFE, "unsafe"), 78 (UNSAFE, "unsafe"),
79 (ATTRIBUTE_MODIFIER, "attribute"), 79 (ATTRIBUTE_MODIFIER, "attribute"),
80 (CALLABLE, "callable"),
80]; 81];
81 82
82#[derive(Default)] 83#[derive(Default)]
diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs
index 24ad49206..24a658fc6 100644
--- a/crates/rust-analyzer/src/to_proto.rs
+++ b/crates/rust-analyzer/src/to_proto.rs
@@ -4,13 +4,13 @@ use std::{
4 sync::atomic::{AtomicU32, Ordering}, 4 sync::atomic::{AtomicU32, Ordering},
5}; 5};
6 6
7use base_db::{FileId, FileRange};
8use ide::{ 7use ide::{
9 Assist, AssistKind, CallInfo, CompletionItem, CompletionItemKind, Documentation, 8 Assist, AssistKind, CallInfo, CompletionItem, CompletionItemKind, Documentation,
10 FileSystemEdit, Fold, FoldKind, Highlight, HighlightModifier, HighlightTag, HighlightedRange, 9 FileSystemEdit, Fold, FoldKind, Highlight, HighlightModifier, HighlightTag, HighlightedRange,
11 Indel, InlayHint, InlayKind, InsertTextFormat, LineIndex, Markup, NavigationTarget, 10 Indel, InlayHint, InlayKind, InsertTextFormat, LineIndex, Markup, NavigationTarget,
12 ReferenceAccess, ResolvedAssist, Runnable, Severity, SourceChange, SourceFileEdit, TextEdit, 11 ReferenceAccess, ResolvedAssist, Runnable, Severity, SourceChange, SourceFileEdit, TextEdit,
13}; 12};
13use ide_db::base_db::{FileId, FileRange};
14use itertools::Itertools; 14use itertools::Itertools;
15use syntax::{SyntaxKind, TextRange, TextSize}; 15use syntax::{SyntaxKind, TextRange, TextSize};
16 16
@@ -425,6 +425,7 @@ fn semantic_token_type_and_modifiers(
425 HighlightModifier::Mutable => semantic_tokens::MUTABLE, 425 HighlightModifier::Mutable => semantic_tokens::MUTABLE,
426 HighlightModifier::Consuming => semantic_tokens::CONSUMING, 426 HighlightModifier::Consuming => semantic_tokens::CONSUMING,
427 HighlightModifier::Unsafe => semantic_tokens::UNSAFE, 427 HighlightModifier::Unsafe => semantic_tokens::UNSAFE,
428 HighlightModifier::Callable => semantic_tokens::CALLABLE,
428 }; 429 };
429 mods |= modifier; 430 mods |= modifier;
430 } 431 }
@@ -762,7 +763,7 @@ pub(crate) fn runnable(
762 let workspace_root = spec.as_ref().map(|it| it.workspace_root.clone()); 763 let workspace_root = spec.as_ref().map(|it| it.workspace_root.clone());
763 let target = spec.as_ref().map(|s| s.target.clone()); 764 let target = spec.as_ref().map(|s| s.target.clone());
764 let (cargo_args, executable_args) = 765 let (cargo_args, executable_args) =
765 CargoTargetSpec::runnable_args(snap, spec, &runnable.kind, &runnable.cfg_exprs)?; 766 CargoTargetSpec::runnable_args(snap, spec, &runnable.kind, &runnable.cfg)?;
766 let label = runnable.label(target); 767 let label = runnable.label(target);
767 let location = location_link(snap, None, runnable.nav)?; 768 let location = location_link(snap, None, runnable.nav)?;
768 769
@@ -808,7 +809,7 @@ mod tests {
808 let completions: Vec<(String, Option<String>)> = analysis 809 let completions: Vec<(String, Option<String>)> = analysis
809 .completions( 810 .completions(
810 &ide::CompletionConfig::default(), 811 &ide::CompletionConfig::default(),
811 base_db::FilePosition { file_id, offset }, 812 ide_db::base_db::FilePosition { file_id, offset },
812 ) 813 )
813 .unwrap() 814 .unwrap()
814 .unwrap() 815 .unwrap()
diff --git a/crates/ssr/Cargo.toml b/crates/ssr/Cargo.toml
index 408140014..98ed25fb6 100644
--- a/crates/ssr/Cargo.toml
+++ b/crates/ssr/Cargo.toml
@@ -16,7 +16,6 @@ itertools = "0.9.0"
16 16
17text_edit = { path = "../text_edit", version = "0.0.0" } 17text_edit = { path = "../text_edit", version = "0.0.0" }
18syntax = { path = "../syntax", version = "0.0.0" } 18syntax = { path = "../syntax", version = "0.0.0" }
19base_db = { path = "../base_db", version = "0.0.0" }
20ide_db = { path = "../ide_db", version = "0.0.0" } 19ide_db = { path = "../ide_db", version = "0.0.0" }
21hir = { path = "../hir", version = "0.0.0" } 20hir = { path = "../hir", version = "0.0.0" }
22test_utils = { path = "../test_utils", version = "0.0.0" } 21test_utils = { path = "../test_utils", version = "0.0.0" }
diff --git a/crates/ssr/src/lib.rs b/crates/ssr/src/lib.rs
index ba669fd56..747ce495d 100644
--- a/crates/ssr/src/lib.rs
+++ b/crates/ssr/src/lib.rs
@@ -73,8 +73,8 @@ use crate::errors::bail;
73pub use crate::errors::SsrError; 73pub use crate::errors::SsrError;
74pub use crate::matching::Match; 74pub use crate::matching::Match;
75use crate::matching::MatchFailureReason; 75use crate::matching::MatchFailureReason;
76use base_db::{FileId, FilePosition, FileRange};
77use hir::Semantics; 76use hir::Semantics;
77use ide_db::base_db::{FileId, FilePosition, FileRange};
78use ide_db::source_change::SourceFileEdit; 78use ide_db::source_change::SourceFileEdit;
79use resolving::ResolvedRule; 79use resolving::ResolvedRule;
80use rustc_hash::FxHashMap; 80use rustc_hash::FxHashMap;
@@ -126,7 +126,7 @@ impl<'db> MatchFinder<'db> {
126 126
127 /// Constructs an instance using the start of the first file in `db` as the lookup context. 127 /// Constructs an instance using the start of the first file in `db` as the lookup context.
128 pub fn at_first_file(db: &'db ide_db::RootDatabase) -> Result<MatchFinder<'db>, SsrError> { 128 pub fn at_first_file(db: &'db ide_db::RootDatabase) -> Result<MatchFinder<'db>, SsrError> {
129 use base_db::SourceDatabaseExt; 129 use ide_db::base_db::SourceDatabaseExt;
130 use ide_db::symbol_index::SymbolsDatabase; 130 use ide_db::symbol_index::SymbolsDatabase;
131 if let Some(first_file_id) = db 131 if let Some(first_file_id) = db
132 .local_roots() 132 .local_roots()
@@ -160,7 +160,7 @@ impl<'db> MatchFinder<'db> {
160 160
161 /// Finds matches for all added rules and returns edits for all found matches. 161 /// Finds matches for all added rules and returns edits for all found matches.
162 pub fn edits(&self) -> Vec<SourceFileEdit> { 162 pub fn edits(&self) -> Vec<SourceFileEdit> {
163 use base_db::SourceDatabaseExt; 163 use ide_db::base_db::SourceDatabaseExt;
164 let mut matches_by_file = FxHashMap::default(); 164 let mut matches_by_file = FxHashMap::default();
165 for m in self.matches().matches { 165 for m in self.matches().matches {
166 matches_by_file 166 matches_by_file
@@ -205,7 +205,7 @@ impl<'db> MatchFinder<'db> {
205 /// them, while recording reasons why they don't match. This API is useful for command 205 /// them, while recording reasons why they don't match. This API is useful for command
206 /// line-based debugging where providing a range is difficult. 206 /// line-based debugging where providing a range is difficult.
207 pub fn debug_where_text_equal(&self, file_id: FileId, snippet: &str) -> Vec<MatchDebugInfo> { 207 pub fn debug_where_text_equal(&self, file_id: FileId, snippet: &str) -> Vec<MatchDebugInfo> {
208 use base_db::SourceDatabaseExt; 208 use ide_db::base_db::SourceDatabaseExt;
209 let file = self.sema.parse(file_id); 209 let file = self.sema.parse(file_id);
210 let mut res = Vec::new(); 210 let mut res = Vec::new();
211 let file_text = self.sema.db.file_text(file_id); 211 let file_text = self.sema.db.file_text(file_id);
diff --git a/crates/ssr/src/matching.rs b/crates/ssr/src/matching.rs
index 948862a77..99b187311 100644
--- a/crates/ssr/src/matching.rs
+++ b/crates/ssr/src/matching.rs
@@ -6,8 +6,8 @@ use crate::{
6 resolving::{ResolvedPattern, ResolvedRule, UfcsCallInfo}, 6 resolving::{ResolvedPattern, ResolvedRule, UfcsCallInfo},
7 SsrMatches, 7 SsrMatches,
8}; 8};
9use base_db::FileRange;
10use hir::Semantics; 9use hir::Semantics;
10use ide_db::base_db::FileRange;
11use rustc_hash::FxHashMap; 11use rustc_hash::FxHashMap;
12use std::{cell::Cell, iter::Peekable}; 12use std::{cell::Cell, iter::Peekable};
13use syntax::ast::{AstNode, AstToken}; 13use syntax::ast::{AstNode, AstToken};
diff --git a/crates/ssr/src/resolving.rs b/crates/ssr/src/resolving.rs
index 347cc4aad..f5ceb5729 100644
--- a/crates/ssr/src/resolving.rs
+++ b/crates/ssr/src/resolving.rs
@@ -2,7 +2,7 @@
2 2
3use crate::errors::error; 3use crate::errors::error;
4use crate::{parsing, SsrError}; 4use crate::{parsing, SsrError};
5use base_db::FilePosition; 5use ide_db::base_db::FilePosition;
6use parsing::Placeholder; 6use parsing::Placeholder;
7use rustc_hash::FxHashMap; 7use rustc_hash::FxHashMap;
8use syntax::{ast, SmolStr, SyntaxKind, SyntaxNode, SyntaxToken}; 8use syntax::{ast, SmolStr, SyntaxKind, SyntaxNode, SyntaxToken};
diff --git a/crates/ssr/src/search.rs b/crates/ssr/src/search.rs
index a595fd269..44b5db029 100644
--- a/crates/ssr/src/search.rs
+++ b/crates/ssr/src/search.rs
@@ -5,7 +5,7 @@ use crate::{
5 resolving::{ResolvedPath, ResolvedPattern, ResolvedRule}, 5 resolving::{ResolvedPath, ResolvedPattern, ResolvedRule},
6 Match, MatchFinder, 6 Match, MatchFinder,
7}; 7};
8use base_db::{FileId, FileRange}; 8use ide_db::base_db::{FileId, FileRange};
9use ide_db::{ 9use ide_db::{
10 defs::Definition, 10 defs::Definition,
11 search::{Reference, SearchScope}, 11 search::{Reference, SearchScope},
@@ -145,7 +145,7 @@ impl<'db> MatchFinder<'db> {
145 fn search_files_do(&self, mut callback: impl FnMut(FileId)) { 145 fn search_files_do(&self, mut callback: impl FnMut(FileId)) {
146 if self.restrict_ranges.is_empty() { 146 if self.restrict_ranges.is_empty() {
147 // Unrestricted search. 147 // Unrestricted search.
148 use base_db::SourceDatabaseExt; 148 use ide_db::base_db::SourceDatabaseExt;
149 use ide_db::symbol_index::SymbolsDatabase; 149 use ide_db::symbol_index::SymbolsDatabase;
150 for &root in self.sema.db.local_roots().iter() { 150 for &root in self.sema.db.local_roots().iter() {
151 let sr = self.sema.db.source_root(root); 151 let sr = self.sema.db.source_root(root);
diff --git a/crates/ssr/src/tests.rs b/crates/ssr/src/tests.rs
index 20231a9bc..63131f6ca 100644
--- a/crates/ssr/src/tests.rs
+++ b/crates/ssr/src/tests.rs
@@ -1,6 +1,6 @@
1use crate::{MatchFinder, SsrRule}; 1use crate::{MatchFinder, SsrRule};
2use base_db::{salsa::Durability, FileId, FilePosition, FileRange, SourceDatabaseExt};
3use expect_test::{expect, Expect}; 2use expect_test::{expect, Expect};
3use ide_db::base_db::{salsa::Durability, FileId, FilePosition, FileRange, SourceDatabaseExt};
4use rustc_hash::FxHashSet; 4use rustc_hash::FxHashSet;
5use std::sync::Arc; 5use std::sync::Arc;
6use test_utils::{mark, RangeOrOffset}; 6use test_utils::{mark, RangeOrOffset};
@@ -62,7 +62,7 @@ fn parser_undefined_placeholder_in_replacement() {
62/// `code` may optionally contain a cursor marker `<|>`. If it doesn't, then the position will be 62/// `code` may optionally contain a cursor marker `<|>`. If it doesn't, then the position will be
63/// the start of the file. If there's a second cursor marker, then we'll return a single range. 63/// the start of the file. If there's a second cursor marker, then we'll return a single range.
64pub(crate) fn single_file(code: &str) -> (ide_db::RootDatabase, FilePosition, Vec<FileRange>) { 64pub(crate) fn single_file(code: &str) -> (ide_db::RootDatabase, FilePosition, Vec<FileRange>) {
65 use base_db::fixture::WithFixture; 65 use ide_db::base_db::fixture::WithFixture;
66 use ide_db::symbol_index::SymbolsDatabase; 66 use ide_db::symbol_index::SymbolsDatabase;
67 let (mut db, file_id, range_or_offset) = if code.contains(test_utils::CURSOR_MARKER) { 67 let (mut db, file_id, range_or_offset) = if code.contains(test_utils::CURSOR_MARKER) {
68 ide_db::RootDatabase::with_range_or_offset(code) 68 ide_db::RootDatabase::with_range_or_offset(code)
@@ -83,7 +83,7 @@ pub(crate) fn single_file(code: &str) -> (ide_db::RootDatabase, FilePosition, Ve
83 } 83 }
84 } 84 }
85 let mut local_roots = FxHashSet::default(); 85 let mut local_roots = FxHashSet::default();
86 local_roots.insert(base_db::fixture::WORKSPACE); 86 local_roots.insert(ide_db::base_db::fixture::WORKSPACE);
87 db.set_local_roots_with_durability(Arc::new(local_roots), Durability::HIGH); 87 db.set_local_roots_with_durability(Arc::new(local_roots), Durability::HIGH);
88 (db, position, selections) 88 (db, position, selections)
89} 89}
diff --git a/crates/stdx/src/panic_context.rs b/crates/stdx/src/panic_context.rs
index fd232e0cc..8d51e20d3 100644
--- a/crates/stdx/src/panic_context.rs
+++ b/crates/stdx/src/panic_context.rs
@@ -4,7 +4,7 @@
4 4
5use std::{cell::RefCell, panic, sync::Once}; 5use std::{cell::RefCell, panic, sync::Once};
6 6
7pub fn enter(context: String) -> impl Drop { 7pub fn enter(context: String) -> PanicContext {
8 static ONCE: Once = Once::new(); 8 static ONCE: Once = Once::new();
9 ONCE.call_once(PanicContext::init); 9 ONCE.call_once(PanicContext::init);
10 10
@@ -13,7 +13,7 @@ pub fn enter(context: String) -> impl Drop {
13} 13}
14 14
15#[must_use] 15#[must_use]
16struct PanicContext { 16pub struct PanicContext {
17 _priv: (), 17 _priv: (),
18} 18}
19 19
diff --git a/crates/syntax/Cargo.toml b/crates/syntax/Cargo.toml
index c343f2f70..e8de61868 100644
--- a/crates/syntax/Cargo.toml
+++ b/crates/syntax/Cargo.toml
@@ -13,10 +13,11 @@ doctest = false
13[dependencies] 13[dependencies]
14itertools = "0.9.0" 14itertools = "0.9.0"
15rowan = "0.10.0" 15rowan = "0.10.0"
16rustc_lexer = { version = "683.0.0", package = "rustc-ap-rustc_lexer" } 16rustc_lexer = { version = "685.0.0", package = "rustc-ap-rustc_lexer" }
17rustc-hash = "1.1.0" 17rustc-hash = "1.1.0"
18arrayvec = "0.5.1" 18arrayvec = "0.5.1"
19once_cell = "1.3.1" 19once_cell = "1.3.1"
20indexmap = "1.4.0"
20# This crate transitively depends on `smol_str` via `rowan`. 21# This crate transitively depends on `smol_str` via `rowan`.
21# ideally, `serde` should be enabled by `rust-analyzer`, but we enable it here 22# ideally, `serde` should be enabled by `rust-analyzer`, but we enable it here
22# to reduce number of compilations 23# to reduce number of compilations
@@ -26,10 +27,9 @@ serde = { version = "1.0.106", features = ["derive"] }
26stdx = { path = "../stdx", version = "0.0.0" } 27stdx = { path = "../stdx", version = "0.0.0" }
27text_edit = { path = "../text_edit", version = "0.0.0" } 28text_edit = { path = "../text_edit", version = "0.0.0" }
28parser = { path = "../parser", version = "0.0.0" } 29parser = { path = "../parser", version = "0.0.0" }
30test_utils = { path = "../test_utils" }
29 31
30[dev-dependencies] 32[dev-dependencies]
31walkdir = "2.3.1" 33walkdir = "2.3.1"
32rayon = "1" 34rayon = "1"
33expect-test = "1.0" 35expect-test = "1.0"
34
35test_utils = { path = "../test_utils" }
diff --git a/crates/syntax/src/algo.rs b/crates/syntax/src/algo.rs
index ea199f9b8..065035fe6 100644
--- a/crates/syntax/src/algo.rs
+++ b/crates/syntax/src/algo.rs
@@ -2,11 +2,14 @@
2 2
3use std::{ 3use std::{
4 fmt, 4 fmt,
5 hash::BuildHasherDefault,
5 ops::{self, RangeInclusive}, 6 ops::{self, RangeInclusive},
6}; 7};
7 8
9use indexmap::IndexMap;
8use itertools::Itertools; 10use itertools::Itertools;
9use rustc_hash::FxHashMap; 11use rustc_hash::FxHashMap;
12use test_utils::mark;
10use text_edit::TextEditBuilder; 13use text_edit::TextEditBuilder;
11 14
12use crate::{ 15use crate::{
@@ -106,42 +109,56 @@ pub enum InsertPosition<T> {
106 After(T), 109 After(T),
107} 110}
108 111
112type FxIndexMap<K, V> = IndexMap<K, V, BuildHasherDefault<rustc_hash::FxHasher>>;
113
114#[derive(Debug)]
109pub struct TreeDiff { 115pub struct TreeDiff {
110 replacements: FxHashMap<SyntaxElement, SyntaxElement>, 116 replacements: FxHashMap<SyntaxElement, SyntaxElement>,
117 deletions: Vec<SyntaxElement>,
118 // the vec as well as the indexmap are both here to preserve order
119 insertions: FxIndexMap<SyntaxElement, Vec<SyntaxElement>>,
111} 120}
112 121
113impl TreeDiff { 122impl TreeDiff {
114 pub fn into_text_edit(&self, builder: &mut TextEditBuilder) { 123 pub fn into_text_edit(&self, builder: &mut TextEditBuilder) {
124 for (anchor, to) in self.insertions.iter() {
125 to.iter().for_each(|to| builder.insert(anchor.text_range().end(), to.to_string()));
126 }
115 for (from, to) in self.replacements.iter() { 127 for (from, to) in self.replacements.iter() {
116 builder.replace(from.text_range(), to.to_string()) 128 builder.replace(from.text_range(), to.to_string())
117 } 129 }
130 for text_range in self.deletions.iter().map(SyntaxElement::text_range) {
131 builder.delete(text_range);
132 }
118 } 133 }
119 134
120 pub fn is_empty(&self) -> bool { 135 pub fn is_empty(&self) -> bool {
121 self.replacements.is_empty() 136 self.replacements.is_empty() && self.deletions.is_empty() && self.insertions.is_empty()
122 } 137 }
123} 138}
124 139
125/// Finds minimal the diff, which, applied to `from`, will result in `to`. 140/// Finds minimal the diff, which, applied to `from`, will result in `to`.
126/// 141///
127/// Specifically, returns a map whose keys are descendants of `from` and values 142/// Specifically, returns a structure that consists of a replacements, insertions and deletions
128/// are descendants of `to`, such that `replace_descendants(from, map) == to`. 143/// such that applying this map on `from` will result in `to`.
129/// 144///
130/// A trivial solution is a singleton map `{ from: to }`, but this function 145/// This function tries to find a fine-grained diff.
131/// tries to find a more fine-grained diff.
132pub fn diff(from: &SyntaxNode, to: &SyntaxNode) -> TreeDiff { 146pub fn diff(from: &SyntaxNode, to: &SyntaxNode) -> TreeDiff {
133 let mut buf = FxHashMap::default(); 147 let mut diff = TreeDiff {
134 // FIXME: this is both horrible inefficient and gives larger than 148 replacements: FxHashMap::default(),
135 // necessary diff. I bet there's a cool algorithm to diff trees properly. 149 insertions: FxIndexMap::default(),
136 go(&mut buf, from.clone().into(), to.clone().into()); 150 deletions: Vec::new(),
137 return TreeDiff { replacements: buf }; 151 };
138 152 let (from, to) = (from.clone().into(), to.clone().into());
139 fn go( 153
140 buf: &mut FxHashMap<SyntaxElement, SyntaxElement>, 154 // FIXME: this is horrible inefficient. I bet there's a cool algorithm to diff trees properly.
141 lhs: SyntaxElement, 155 if !syntax_element_eq(&from, &to) {
142 rhs: SyntaxElement, 156 go(&mut diff, from, to);
143 ) { 157 }
144 if lhs.kind() == rhs.kind() 158 return diff;
159
160 fn syntax_element_eq(lhs: &SyntaxElement, rhs: &SyntaxElement) -> bool {
161 lhs.kind() == rhs.kind()
145 && lhs.text_range().len() == rhs.text_range().len() 162 && lhs.text_range().len() == rhs.text_range().len()
146 && match (&lhs, &rhs) { 163 && match (&lhs, &rhs) {
147 (NodeOrToken::Node(lhs), NodeOrToken::Node(rhs)) => { 164 (NodeOrToken::Node(lhs), NodeOrToken::Node(rhs)) => {
@@ -150,18 +167,47 @@ pub fn diff(from: &SyntaxNode, to: &SyntaxNode) -> TreeDiff {
150 (NodeOrToken::Token(lhs), NodeOrToken::Token(rhs)) => lhs.text() == rhs.text(), 167 (NodeOrToken::Token(lhs), NodeOrToken::Token(rhs)) => lhs.text() == rhs.text(),
151 _ => false, 168 _ => false,
152 } 169 }
153 { 170 }
154 return; 171
155 } 172 fn go(diff: &mut TreeDiff, lhs: SyntaxElement, rhs: SyntaxElement) {
156 if let (Some(lhs), Some(rhs)) = (lhs.as_node(), rhs.as_node()) { 173 let (lhs, rhs) = match lhs.as_node().zip(rhs.as_node()) {
157 if lhs.children_with_tokens().count() == rhs.children_with_tokens().count() { 174 Some((lhs, rhs)) => (lhs, rhs),
158 for (lhs, rhs) in lhs.children_with_tokens().zip(rhs.children_with_tokens()) { 175 _ => {
159 go(buf, lhs, rhs) 176 mark::hit!(diff_node_token_replace);
160 } 177 diff.replacements.insert(lhs, rhs);
161 return; 178 return;
162 } 179 }
180 };
181
182 let mut rhs_children = rhs.children_with_tokens();
183 let mut lhs_children = lhs.children_with_tokens();
184 let mut last_lhs = None;
185 loop {
186 let lhs_child = lhs_children.next();
187 match (lhs_child.clone(), rhs_children.next()) {
188 (None, None) => break,
189 (None, Some(element)) => match last_lhs.clone() {
190 Some(prev) => {
191 mark::hit!(diff_insert);
192 diff.insertions.entry(prev).or_insert_with(Vec::new).push(element);
193 }
194 // first iteration, this means we got no anchor element to insert after
195 // therefor replace the parent node instead
196 None => {
197 mark::hit!(diff_replace_parent);
198 diff.replacements.insert(lhs.clone().into(), rhs.clone().into());
199 break;
200 }
201 },
202 (Some(element), None) => {
203 mark::hit!(diff_delete);
204 diff.deletions.push(element);
205 }
206 (Some(ref lhs_ele), Some(ref rhs_ele)) if syntax_element_eq(lhs_ele, rhs_ele) => {}
207 (Some(lhs_ele), Some(rhs_ele)) => go(diff, lhs_ele, rhs_ele),
208 }
209 last_lhs = lhs_child.or(last_lhs);
163 } 210 }
164 buf.insert(lhs, rhs);
165 } 211 }
166} 212}
167 213
@@ -243,11 +289,19 @@ fn _replace_children(
243 with_children(parent, new_children) 289 with_children(parent, new_children)
244} 290}
245 291
292#[derive(Debug, PartialEq, Eq, Hash)]
293enum InsertPos {
294 FirstChildOf(SyntaxNode),
295 // Before(SyntaxElement),
296 After(SyntaxElement),
297}
298
246#[derive(Default)] 299#[derive(Default)]
247pub struct SyntaxRewriter<'a> { 300pub struct SyntaxRewriter<'a> {
248 f: Option<Box<dyn Fn(&SyntaxElement) -> Option<SyntaxElement> + 'a>>, 301 f: Option<Box<dyn Fn(&SyntaxElement) -> Option<SyntaxElement> + 'a>>,
249 //FIXME: add debug_assertions that all elements are in fact from the same file. 302 //FIXME: add debug_assertions that all elements are in fact from the same file.
250 replacements: FxHashMap<SyntaxElement, Replacement>, 303 replacements: FxHashMap<SyntaxElement, Replacement>,
304 insertions: IndexMap<InsertPos, Vec<SyntaxElement>>,
251} 305}
252 306
253impl fmt::Debug for SyntaxRewriter<'_> { 307impl fmt::Debug for SyntaxRewriter<'_> {
@@ -258,13 +312,96 @@ impl fmt::Debug for SyntaxRewriter<'_> {
258 312
259impl<'a> SyntaxRewriter<'a> { 313impl<'a> SyntaxRewriter<'a> {
260 pub fn from_fn(f: impl Fn(&SyntaxElement) -> Option<SyntaxElement> + 'a) -> SyntaxRewriter<'a> { 314 pub fn from_fn(f: impl Fn(&SyntaxElement) -> Option<SyntaxElement> + 'a) -> SyntaxRewriter<'a> {
261 SyntaxRewriter { f: Some(Box::new(f)), replacements: FxHashMap::default() } 315 SyntaxRewriter {
316 f: Some(Box::new(f)),
317 replacements: FxHashMap::default(),
318 insertions: IndexMap::default(),
319 }
262 } 320 }
263 pub fn delete<T: Clone + Into<SyntaxElement>>(&mut self, what: &T) { 321 pub fn delete<T: Clone + Into<SyntaxElement>>(&mut self, what: &T) {
264 let what = what.clone().into(); 322 let what = what.clone().into();
265 let replacement = Replacement::Delete; 323 let replacement = Replacement::Delete;
266 self.replacements.insert(what, replacement); 324 self.replacements.insert(what, replacement);
267 } 325 }
326 pub fn insert_before<T: Clone + Into<SyntaxElement>, U: Clone + Into<SyntaxElement>>(
327 &mut self,
328 before: &T,
329 what: &U,
330 ) {
331 let before = before.clone().into();
332 let pos = match before.prev_sibling_or_token() {
333 Some(sibling) => InsertPos::After(sibling),
334 None => match before.parent() {
335 Some(parent) => InsertPos::FirstChildOf(parent),
336 None => return,
337 },
338 };
339 self.insertions.entry(pos).or_insert_with(Vec::new).push(what.clone().into());
340 }
341 pub fn insert_after<T: Clone + Into<SyntaxElement>, U: Clone + Into<SyntaxElement>>(
342 &mut self,
343 after: &T,
344 what: &U,
345 ) {
346 self.insertions
347 .entry(InsertPos::After(after.clone().into()))
348 .or_insert_with(Vec::new)
349 .push(what.clone().into());
350 }
351 pub fn insert_as_first_child<T: Clone + Into<SyntaxNode>, U: Clone + Into<SyntaxElement>>(
352 &mut self,
353 parent: &T,
354 what: &U,
355 ) {
356 self.insertions
357 .entry(InsertPos::FirstChildOf(parent.clone().into()))
358 .or_insert_with(Vec::new)
359 .push(what.clone().into());
360 }
361 pub fn insert_many_before<
362 T: Clone + Into<SyntaxElement>,
363 U: IntoIterator<Item = SyntaxElement>,
364 >(
365 &mut self,
366 before: &T,
367 what: U,
368 ) {
369 let before = before.clone().into();
370 let pos = match before.prev_sibling_or_token() {
371 Some(sibling) => InsertPos::After(sibling),
372 None => match before.parent() {
373 Some(parent) => InsertPos::FirstChildOf(parent),
374 None => return,
375 },
376 };
377 self.insertions.entry(pos).or_insert_with(Vec::new).extend(what);
378 }
379 pub fn insert_many_after<
380 T: Clone + Into<SyntaxElement>,
381 U: IntoIterator<Item = SyntaxElement>,
382 >(
383 &mut self,
384 after: &T,
385 what: U,
386 ) {
387 self.insertions
388 .entry(InsertPos::After(after.clone().into()))
389 .or_insert_with(Vec::new)
390 .extend(what);
391 }
392 pub fn insert_many_as_first_children<
393 T: Clone + Into<SyntaxNode>,
394 U: IntoIterator<Item = SyntaxElement>,
395 >(
396 &mut self,
397 parent: &T,
398 what: U,
399 ) {
400 self.insertions
401 .entry(InsertPos::FirstChildOf(parent.clone().into()))
402 .or_insert_with(Vec::new)
403 .extend(what)
404 }
268 pub fn replace<T: Clone + Into<SyntaxElement>>(&mut self, what: &T, with: &T) { 405 pub fn replace<T: Clone + Into<SyntaxElement>>(&mut self, what: &T, with: &T) {
269 let what = what.clone().into(); 406 let what = what.clone().into();
270 let replacement = Replacement::Single(with.clone().into()); 407 let replacement = Replacement::Single(with.clone().into());
@@ -284,7 +421,7 @@ impl<'a> SyntaxRewriter<'a> {
284 } 421 }
285 422
286 pub fn rewrite(&self, node: &SyntaxNode) -> SyntaxNode { 423 pub fn rewrite(&self, node: &SyntaxNode) -> SyntaxNode {
287 if self.f.is_none() && self.replacements.is_empty() { 424 if self.f.is_none() && self.replacements.is_empty() && self.insertions.is_empty() {
288 return node.clone(); 425 return node.clone();
289 } 426 }
290 self.rewrite_children(node) 427 self.rewrite_children(node)
@@ -300,14 +437,22 @@ impl<'a> SyntaxRewriter<'a> {
300 /// 437 ///
301 /// Returns `None` when there are no replacements. 438 /// Returns `None` when there are no replacements.
302 pub fn rewrite_root(&self) -> Option<SyntaxNode> { 439 pub fn rewrite_root(&self) -> Option<SyntaxNode> {
440 fn element_to_node_or_parent(element: &SyntaxElement) -> SyntaxNode {
441 match element {
442 SyntaxElement::Node(it) => it.clone(),
443 SyntaxElement::Token(it) => it.parent(),
444 }
445 }
446
303 assert!(self.f.is_none()); 447 assert!(self.f.is_none());
304 self.replacements 448 self.replacements
305 .keys() 449 .keys()
306 .map(|element| match element { 450 .map(element_to_node_or_parent)
307 SyntaxElement::Node(it) => it.clone(), 451 .chain(self.insertions.keys().map(|pos| match pos {
308 SyntaxElement::Token(it) => it.parent(), 452 InsertPos::FirstChildOf(it) => it.clone(),
309 }) 453 InsertPos::After(it) => element_to_node_or_parent(it),
310 // If we only have one replacement, we must return its parent node, since `rewrite` does 454 }))
455 // If we only have one replacement/insertion, we must return its parent node, since `rewrite` does
311 // not replace the node passed to it. 456 // not replace the node passed to it.
312 .map(|it| it.parent().unwrap_or(it)) 457 .map(|it| it.parent().unwrap_or(it))
313 .fold1(|a, b| least_common_ancestor(&a, &b).unwrap()) 458 .fold1(|a, b| least_common_ancestor(&a, &b).unwrap())
@@ -321,9 +466,16 @@ impl<'a> SyntaxRewriter<'a> {
321 self.replacements.get(element).cloned() 466 self.replacements.get(element).cloned()
322 } 467 }
323 468
469 fn insertions(&self, pos: &InsertPos) -> Option<impl Iterator<Item = SyntaxElement> + '_> {
470 self.insertions.get(pos).map(|insertions| insertions.iter().cloned())
471 }
472
324 fn rewrite_children(&self, node: &SyntaxNode) -> SyntaxNode { 473 fn rewrite_children(&self, node: &SyntaxNode) -> SyntaxNode {
325 // FIXME: this could be made much faster. 474 // FIXME: this could be made much faster.
326 let mut new_children = Vec::new(); 475 let mut new_children = Vec::new();
476 if let Some(elements) = self.insertions(&InsertPos::FirstChildOf(node.clone())) {
477 new_children.extend(elements.map(element_to_green));
478 }
327 for child in node.children_with_tokens() { 479 for child in node.children_with_tokens() {
328 self.rewrite_self(&mut new_children, &child); 480 self.rewrite_self(&mut new_children, &child);
329 } 481 }
@@ -337,34 +489,45 @@ impl<'a> SyntaxRewriter<'a> {
337 ) { 489 ) {
338 if let Some(replacement) = self.replacement(&element) { 490 if let Some(replacement) = self.replacement(&element) {
339 match replacement { 491 match replacement {
340 Replacement::Single(NodeOrToken::Node(it)) => { 492 Replacement::Single(element) => acc.push(element_to_green(element)),
341 acc.push(NodeOrToken::Node(it.green().clone()))
342 }
343 Replacement::Single(NodeOrToken::Token(it)) => {
344 acc.push(NodeOrToken::Token(it.green().clone()))
345 }
346 Replacement::Many(replacements) => { 493 Replacement::Many(replacements) => {
347 acc.extend(replacements.iter().map(|it| match it { 494 acc.extend(replacements.into_iter().map(element_to_green))
348 NodeOrToken::Node(it) => NodeOrToken::Node(it.green().clone()),
349 NodeOrToken::Token(it) => NodeOrToken::Token(it.green().clone()),
350 }))
351 } 495 }
352 Replacement::Delete => (), 496 Replacement::Delete => (),
353 }; 497 };
354 return; 498 } else {
499 match element {
500 NodeOrToken::Token(it) => acc.push(NodeOrToken::Token(it.green().clone())),
501 NodeOrToken::Node(it) => {
502 acc.push(NodeOrToken::Node(self.rewrite_children(it).green().clone()));
503 }
504 }
505 }
506 if let Some(elements) = self.insertions(&InsertPos::After(element.clone())) {
507 acc.extend(elements.map(element_to_green));
355 } 508 }
356 let res = match element { 509 }
357 NodeOrToken::Token(it) => NodeOrToken::Token(it.green().clone()), 510}
358 NodeOrToken::Node(it) => NodeOrToken::Node(self.rewrite_children(it).green().clone()), 511
359 }; 512fn element_to_green(element: SyntaxElement) -> NodeOrToken<rowan::GreenNode, rowan::GreenToken> {
360 acc.push(res) 513 match element {
514 NodeOrToken::Node(it) => NodeOrToken::Node(it.green().clone()),
515 NodeOrToken::Token(it) => NodeOrToken::Token(it.green().clone()),
361 } 516 }
362} 517}
363 518
364impl ops::AddAssign for SyntaxRewriter<'_> { 519impl ops::AddAssign for SyntaxRewriter<'_> {
365 fn add_assign(&mut self, rhs: SyntaxRewriter) { 520 fn add_assign(&mut self, rhs: SyntaxRewriter) {
366 assert!(rhs.f.is_none()); 521 assert!(rhs.f.is_none());
367 self.replacements.extend(rhs.replacements) 522 self.replacements.extend(rhs.replacements);
523 for (pos, insertions) in rhs.insertions.into_iter() {
524 match self.insertions.entry(pos) {
525 indexmap::map::Entry::Occupied(mut occupied) => {
526 occupied.get_mut().extend(insertions)
527 }
528 indexmap::map::Entry::Vacant(vacant) => drop(vacant.insert(insertions)),
529 }
530 }
368 } 531 }
369} 532}
370 533
@@ -404,3 +567,322 @@ fn to_green_element(element: SyntaxElement) -> NodeOrToken<rowan::GreenNode, row
404 NodeOrToken::Token(it) => it.green().clone().into(), 567 NodeOrToken::Token(it) => it.green().clone().into(),
405 } 568 }
406} 569}
570
571#[cfg(test)]
572mod tests {
573 use expect_test::{expect, Expect};
574 use itertools::Itertools;
575 use parser::SyntaxKind;
576 use test_utils::mark;
577 use text_edit::TextEdit;
578
579 use crate::{AstNode, SyntaxElement};
580
581 #[test]
582 fn replace_node_token() {
583 mark::check!(diff_node_token_replace);
584 check_diff(
585 r#"use node;"#,
586 r#"ident"#,
587 expect![[r#"
588 insertions:
589
590
591
592 replacements:
593
594 Line 0: Token([email protected] "use") -> ident
595
596 deletions:
597
598 Line 1: " "
599 Line 1: node
600 Line 1: ;
601 "#]],
602 );
603 }
604
605 #[test]
606 fn insert() {
607 mark::check!(diff_insert);
608 check_diff(
609 r#"use foo;"#,
610 r#"use foo;
611use bar;"#,
612 expect![[r#"
613 insertions:
614
615 Line 0: Node([email protected])
616 -> "\n"
617 -> use bar;
618
619 replacements:
620
621
622
623 deletions:
624
625
626 "#]],
627 );
628 }
629
630 #[test]
631 fn replace_parent() {
632 mark::check!(diff_replace_parent);
633 check_diff(
634 r#""#,
635 r#"use foo::bar;"#,
636 expect![[r#"
637 insertions:
638
639
640
641 replacements:
642
643 Line 0: Node([email protected]) -> use foo::bar;
644
645 deletions:
646
647
648 "#]],
649 );
650 }
651
652 #[test]
653 fn delete() {
654 mark::check!(diff_delete);
655 check_diff(
656 r#"use foo;
657 use bar;"#,
658 r#"use foo;"#,
659 expect![[r#"
660 insertions:
661
662
663
664 replacements:
665
666
667
668 deletions:
669
670 Line 1: "\n "
671 Line 2: use bar;
672 "#]],
673 );
674 }
675
676 #[test]
677 fn insert_use() {
678 check_diff(
679 r#"
680use expect_test::{expect, Expect};
681
682use crate::AstNode;
683"#,
684 r#"
685use expect_test::{expect, Expect};
686use text_edit::TextEdit;
687
688use crate::AstNode;
689"#,
690 expect![[r#"
691 insertions:
692
693 Line 4: Token([email protected] "\n")
694 -> use crate::AstNode;
695 -> "\n"
696
697 replacements:
698
699 Line 2: Token([email protected] "\n\n") -> "\n"
700 Line 4: Token([email protected] "crate") -> text_edit
701 Line 4: Token([email protected] "AstNode") -> TextEdit
702 Line 4: Token([email protected] "\n") -> "\n\n"
703
704 deletions:
705
706
707 "#]],
708 )
709 }
710
711 #[test]
712 fn remove_use() {
713 check_diff(
714 r#"
715use expect_test::{expect, Expect};
716use text_edit::TextEdit;
717
718use crate::AstNode;
719"#,
720 r#"
721use expect_test::{expect, Expect};
722
723use crate::AstNode;
724"#,
725 expect![[r#"
726 insertions:
727
728
729
730 replacements:
731
732 Line 2: Token([email protected] "\n") -> "\n\n"
733 Line 3: Node([email protected]) -> crate
734 Line 3: Token([email protected] "TextEdit") -> AstNode
735 Line 3: Token([email protected] "\n\n") -> "\n"
736
737 deletions:
738
739 Line 4: use crate::AstNode;
740 Line 5: "\n"
741 "#]],
742 )
743 }
744
745 #[test]
746 fn merge_use() {
747 check_diff(
748 r#"
749use std::{
750 fmt,
751 hash::BuildHasherDefault,
752 ops::{self, RangeInclusive},
753};
754"#,
755 r#"
756use std::fmt;
757use std::hash::BuildHasherDefault;
758use std::ops::{self, RangeInclusive};
759"#,
760 expect![[r#"
761 insertions:
762
763 Line 2: Node([email protected])
764 -> ::
765 -> fmt
766 Line 6: Token([email protected] "\n")
767 -> use std::hash::BuildHasherDefault;
768 -> "\n"
769 -> use std::ops::{self, RangeInclusive};
770 -> "\n"
771
772 replacements:
773
774 Line 2: Token([email protected] "std") -> std
775
776 deletions:
777
778 Line 2: ::
779 Line 2: {
780 fmt,
781 hash::BuildHasherDefault,
782 ops::{self, RangeInclusive},
783 }
784 "#]],
785 )
786 }
787
788 #[test]
789 fn early_return_assist() {
790 check_diff(
791 r#"
792fn main() {
793 if let Ok(x) = Err(92) {
794 foo(x);
795 }
796}
797 "#,
798 r#"
799fn main() {
800 let x = match Err(92) {
801 Ok(it) => it,
802 _ => return,
803 };
804 foo(x);
805}
806 "#,
807 expect![[r#"
808 insertions:
809
810 Line 3: Node([email protected])
811 -> " "
812 -> match Err(92) {
813 Ok(it) => it,
814 _ => return,
815 }
816 -> ;
817 Line 5: Token([email protected] "}")
818 -> "\n"
819 -> }
820
821 replacements:
822
823 Line 3: Token([email protected] "if") -> let
824 Line 3: Token([email protected] "let") -> x
825 Line 3: Node([email protected]) -> =
826 Line 5: Token([email protected] "\n") -> "\n "
827 Line 5: Token([email protected] "}") -> foo(x);
828
829 deletions:
830
831 Line 3: " "
832 Line 3: Ok(x)
833 Line 3: " "
834 Line 3: =
835 Line 3: " "
836 Line 3: Err(92)
837 "#]],
838 )
839 }
840
841 fn check_diff(from: &str, to: &str, expected_diff: Expect) {
842 let from_node = crate::SourceFile::parse(from).tree().syntax().clone();
843 let to_node = crate::SourceFile::parse(to).tree().syntax().clone();
844 let diff = super::diff(&from_node, &to_node);
845
846 let line_number =
847 |syn: &SyntaxElement| from[..syn.text_range().start().into()].lines().count();
848
849 let fmt_syntax = |syn: &SyntaxElement| match syn.kind() {
850 SyntaxKind::WHITESPACE => format!("{:?}", syn.to_string()),
851 _ => format!("{}", syn),
852 };
853
854 let insertions = diff.insertions.iter().format_with("\n", |(k, v), f| {
855 f(&format!(
856 "Line {}: {:?}\n-> {}",
857 line_number(k),
858 k,
859 v.iter().format_with("\n-> ", |v, f| f(&fmt_syntax(v)))
860 ))
861 });
862
863 let replacements = diff
864 .replacements
865 .iter()
866 .sorted_by_key(|(syntax, _)| syntax.text_range().start())
867 .format_with("\n", |(k, v), f| {
868 f(&format!("Line {}: {:?} -> {}", line_number(k), k, fmt_syntax(v)))
869 });
870
871 let deletions = diff
872 .deletions
873 .iter()
874 .format_with("\n", |v, f| f(&format!("Line {}: {}", line_number(v), &fmt_syntax(v))));
875
876 let actual = format!(
877 "insertions:\n\n{}\n\nreplacements:\n\n{}\n\ndeletions:\n\n{}\n",
878 insertions, replacements, deletions
879 );
880 expected_diff.assert_eq(&actual);
881
882 let mut from = from.to_owned();
883 let mut text_edit = TextEdit::builder();
884 diff.into_text_edit(&mut text_edit);
885 text_edit.finish().apply(&mut from);
886 assert_eq!(&*from, to, "diff did not turn `from` to `to`");
887 }
888}
diff --git a/crates/syntax/src/ast/node_ext.rs b/crates/syntax/src/ast/node_ext.rs
index 50c1c157d..c5cd1c504 100644
--- a/crates/syntax/src/ast/node_ext.rs
+++ b/crates/syntax/src/ast/node_ext.rs
@@ -7,7 +7,7 @@ use itertools::Itertools;
7use parser::SyntaxKind; 7use parser::SyntaxKind;
8 8
9use crate::{ 9use crate::{
10 ast::{self, support, AstNode, NameOwner, SyntaxNode}, 10 ast::{self, support, token_ext::HasStringValue, AstNode, AstToken, NameOwner, SyntaxNode},
11 SmolStr, SyntaxElement, SyntaxToken, T, 11 SmolStr, SyntaxElement, SyntaxToken, T,
12}; 12};
13 13
@@ -53,8 +53,16 @@ impl ast::Attr {
53 pub fn as_simple_key_value(&self) -> Option<(SmolStr, SmolStr)> { 53 pub fn as_simple_key_value(&self) -> Option<(SmolStr, SmolStr)> {
54 let lit = self.literal()?; 54 let lit = self.literal()?;
55 let key = self.simple_name()?; 55 let key = self.simple_name()?;
56 // FIXME: escape? raw string? 56 let value_token = lit.syntax().first_token()?;
57 let value = lit.syntax().first_token()?.text().trim_matches('"').into(); 57
58 let value: SmolStr = if let Some(s) = ast::String::cast(value_token.clone()) {
59 s.value()?.into()
60 } else if let Some(s) = ast::RawString::cast(value_token) {
61 s.value()?.into()
62 } else {
63 return None;
64 };
65
58 Some((key, value)) 66 Some((key, value))
59 } 67 }
60 68
diff --git a/crates/test_utils/src/lib.rs b/crates/test_utils/src/lib.rs
index ad586c882..a49be4602 100644
--- a/crates/test_utils/src/lib.rs
+++ b/crates/test_utils/src/lib.rs
@@ -43,12 +43,12 @@ macro_rules! assert_eq_text {
43 let right = $right; 43 let right = $right;
44 if left != right { 44 if left != right {
45 if left.trim() == right.trim() { 45 if left.trim() == right.trim() {
46 eprintln!("Left:\n{:?}\n\nRight:\n{:?}\n\nWhitespace difference\n", left, right); 46 std::eprintln!("Left:\n{:?}\n\nRight:\n{:?}\n\nWhitespace difference\n", left, right);
47 } else { 47 } else {
48 let changeset = $crate::__Changeset::new(left, right, "\n"); 48 let changeset = $crate::__Changeset::new(left, right, "\n");
49 eprintln!("Left:\n{}\n\nRight:\n{}\n\nDiff:\n{}\n", left, right, changeset); 49 std::eprintln!("Left:\n{}\n\nRight:\n{}\n\nDiff:\n{}\n", left, right, changeset);
50 } 50 }
51 eprintln!($($tt)*); 51 std::eprintln!($($tt)*);
52 panic!("text differs"); 52 panic!("text differs");
53 } 53 }
54 }}; 54 }};