aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock39
-rw-r--r--crates/assists/src/handlers/remove_dbg.rs12
-rw-r--r--crates/completion/src/completions/attribute.rs21
-rw-r--r--crates/completion/src/completions/postfix.rs26
-rw-r--r--crates/completion/src/completions/trait_impl.rs6
-rw-r--r--crates/completion/src/patterns.rs10
-rw-r--r--crates/flycheck/Cargo.toml2
-rw-r--r--crates/hir/src/code_model.rs18
-rw-r--r--crates/hir/src/from_id.rs2
-rw-r--r--crates/hir_def/src/body/lower.rs4
-rw-r--r--crates/hir_def/src/expr.rs2
-rw-r--r--crates/hir_def/src/nameres/collector.rs8
-rw-r--r--crates/hir_def/src/resolver.rs2
-rw-r--r--crates/hir_expand/src/db.rs2
-rw-r--r--crates/hir_ty/src/diagnostics/expr.rs4
-rw-r--r--crates/hir_ty/src/diagnostics/match_check.rs4
-rw-r--r--crates/hir_ty/src/lower.rs6
-rw-r--r--crates/ide/src/call_hierarchy.rs30
-rw-r--r--crates/ide/src/display/navigation_target.rs14
-rw-r--r--crates/ide/src/doc_links.rs34
-rw-r--r--crates/ide/src/goto_definition.rs93
-rw-r--r--crates/ide/src/hover.rs47
-rw-r--r--crates/ide/src/inlay_hints.rs25
-rw-r--r--crates/ide/src/lib.rs8
-rw-r--r--crates/ide/src/references.rs16
-rw-r--r--crates/ide/src/references/rename.rs244
-rw-r--r--crates/ide/src/runnables.rs110
-rw-r--r--crates/ide/src/syntax_highlighting.rs821
-rw-r--r--crates/ide/src/syntax_highlighting/format.rs94
-rw-r--r--crates/ide/src/syntax_highlighting/highlight.rs530
-rw-r--r--crates/ide/src/syntax_highlighting/highlights.rs102
-rw-r--r--crates/ide/src/syntax_highlighting/html.rs28
-rw-r--r--crates/ide/src/syntax_highlighting/inject.rs158
-rw-r--r--crates/ide/src/syntax_highlighting/injection.rs240
-rw-r--r--crates/ide/src/syntax_highlighting/injector.rs80
-rw-r--r--crates/ide/src/syntax_highlighting/macro_rules.rs10
-rw-r--r--crates/ide/src/syntax_highlighting/tags.rs173
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlight_assoc_functions.html28
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html71
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlight_extern_crate.html4
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlight_injection.html20
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlight_strings.html106
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html88
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlighting.html260
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/injection.html16
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/rainbow_highlighting.html20
-rw-r--r--crates/ide/src/syntax_highlighting/tests.rs5
-rw-r--r--crates/ide_db/src/defs.rs36
-rw-r--r--crates/ide_db/src/imports_locator.rs2
-rw-r--r--crates/ide_db/src/search.rs2
-rw-r--r--crates/mbe/src/lib.rs4
-rw-r--r--crates/mbe/src/mbe_expander/matcher.rs2
-rw-r--r--crates/mbe/src/mbe_expander/transcriber.rs8
-rw-r--r--crates/mbe/src/syntax_bridge.rs12
-rw-r--r--crates/mbe/src/tests.rs2
-rw-r--r--crates/parser/src/grammar/expressions/atom.rs12
-rw-r--r--crates/parser/src/grammar/items.rs26
-rw-r--r--crates/parser/src/grammar/items/traits.rs2
-rw-r--r--crates/parser/src/grammar/items/use_item.rs2
-rw-r--r--crates/parser/src/grammar/patterns.rs2
-rw-r--r--crates/parser/src/grammar/type_args.rs6
-rw-r--r--crates/parser/src/grammar/type_params.rs4
-rw-r--r--crates/proc_macro_api/src/msg.rs2
-rw-r--r--crates/proc_macro_srv/Cargo.toml2
-rw-r--r--crates/proc_macro_srv/src/proc_macro/bridge/rpc.rs2
-rw-r--r--crates/proc_macro_srv/src/rustc_server.rs2
-rw-r--r--crates/project_model/Cargo.toml2
-rw-r--r--crates/rust-analyzer/src/cli/analysis_stats.rs2
-rw-r--r--crates/rust-analyzer/src/config.rs12
-rw-r--r--crates/rust-analyzer/src/global_state.rs3
-rw-r--r--crates/rust-analyzer/src/lib.rs1
-rw-r--r--crates/rust-analyzer/src/lsp_utils.rs10
-rw-r--r--crates/rust-analyzer/src/main_loop.rs82
-rw-r--r--crates/rust-analyzer/src/markdown.rs2
-rw-r--r--crates/rust-analyzer/src/op_queue.rs25
-rw-r--r--crates/rust-analyzer/src/reload.rs69
-rw-r--r--crates/rust-analyzer/src/semantic_tokens.rs8
-rw-r--r--crates/rust-analyzer/src/to_proto.rs80
-rw-r--r--crates/stdx/src/lib.rs18
-rw-r--r--crates/syntax/src/algo.rs4
-rw-r--r--crates/syntax/src/ast/make.rs2
-rw-r--r--crates/syntax/src/ast/node_ext.rs2
-rw-r--r--crates/syntax/src/parsing/lexer.rs8
-rw-r--r--crates/syntax/src/validation.rs6
-rw-r--r--crates/syntax/test_data/parser/inline/ok/0002_use_tree_list.rast98
-rw-r--r--crates/syntax/test_data/parser/inline/ok/0002_use_tree_list.rs2
-rw-r--r--crates/syntax/test_data/parser/inline/ok/0039_type_arg.rast28
-rw-r--r--crates/syntax/test_data/parser/inline/ok/0039_type_arg.rs2
-rw-r--r--crates/test_utils/src/lib.rs2
-rw-r--r--docs/dev/style.md23
90 files changed, 2311 insertions, 1953 deletions
<
diff --git a/Cargo.lock b/Cargo.lock
index 57383491a..5563fdeb2 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -127,12 +127,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
127checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" 127checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"
128 128
129[[package]] 129[[package]]
130name = "cargo-platform"
131version = "0.1.1"
132source = "registry+https://github.com/rust-lang/crates.io-index"
133checksum = "0226944a63d1bf35a3b5f948dd7c59e263db83695c9e8bffc4037de02e30f1d7"
134dependencies = [
135 "serde",
136]
137
138[[package]]
130name = "cargo_metadata" 139name = "cargo_metadata"
131version = "0.12.0" 140version = "0.12.2"
132source = "registry+https://github.com/rust-lang/crates.io-index" 141source = "registry+https://github.com/rust-lang/crates.io-index"
133checksum = "d5a5f7b42f606b7f23674f6f4d877628350682bc40687d3fae65679a58d55345" 142checksum = "11a47b6286279a9998588ef7050d1ebc2500c69892a557c90fe5d071c64415dc"
134dependencies = [ 143dependencies = [
144 "cargo-platform",
135 "semver", 145 "semver",
146 "semver-parser",
136 "serde", 147 "serde",
137 "serde_json", 148 "serde_json",
138] 149]
@@ -262,9 +273,9 @@ dependencies = [
262 273
263[[package]] 274[[package]]
264name = "const_fn" 275name = "const_fn"
265version = "0.4.4" 276version = "0.4.5"
266source = "registry+https://github.com/rust-lang/crates.io-index" 277source = "registry+https://github.com/rust-lang/crates.io-index"
267checksum = "cd51eab21ab4fd6a3bf889e2d0958c0a6e3a61ad04260325e919e652a2a62826" 278checksum = "28b9d6de7f49e22cf97ad17fc4036ece69300032f45f78f30b4a4482cdc3f4a6"
268 279
269[[package]] 280[[package]]
270name = "crc32fast" 281name = "crc32fast"
@@ -774,9 +785,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
774 785
775[[package]] 786[[package]]
776name = "libc" 787name = "libc"
777version = "0.2.81" 788version = "0.2.82"
778source = "registry+https://github.com/rust-lang/crates.io-index" 789source = "registry+https://github.com/rust-lang/crates.io-index"
779checksum = "1482821306169ec4d07f6aca392a4681f66c75c9918aa49641a2595db64053cb" 790checksum = "89203f3fba0a3795506acaad8ebce3c80c0af93f994d5a1d7a0b1eeb23271929"
780 791
781[[package]] 792[[package]]
782name = "libloading" 793name = "libloading"
@@ -1134,9 +1145,9 @@ checksum = "28b9b4df73455c861d7cbf8be42f01d3b373ed7f02e378d55fa84eafc6f638b1"
1134 1145
1135[[package]] 1146[[package]]
1136name = "pin-project-lite" 1147name = "pin-project-lite"
1137version = "0.2.0" 1148version = "0.2.1"
1138source = "registry+https://github.com/rust-lang/crates.io-index" 1149source = "registry+https://github.com/rust-lang/crates.io-index"
1139checksum = "6b063f57ec186e6140e2b8b6921e5f1bd89c7356dda5b33acc5401203ca6131c" 1150checksum = "e36743d754ccdf9954c2e352ce2d4b106e024c814f6499c2dadff80da9a442d8"
1140 1151
1141[[package]] 1152[[package]]
1142name = "proc-macro2" 1153name = "proc-macro2"
@@ -1449,9 +1460,9 @@ dependencies = [
1449 1460
1450[[package]] 1461[[package]]
1451name = "semver-parser" 1462name = "semver-parser"
1452version = "0.10.1" 1463version = "0.10.2"
1453source = "registry+https://github.com/rust-lang/crates.io-index" 1464source = "registry+https://github.com/rust-lang/crates.io-index"
1454checksum = "42ef146c2ad5e5f4b037cd6ce2ebb775401729b19a82040c1beac9d36c7d1428" 1465checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7"
1455dependencies = [ 1466dependencies = [
1456 "pest", 1467 "pest",
1457] 1468]
@@ -1558,9 +1569,9 @@ version = "0.0.0"
1558 1569
1559[[package]] 1570[[package]]
1560name = "syn" 1571name = "syn"
1561version = "1.0.57" 1572version = "1.0.58"
1562source = "registry+https://github.com/rust-lang/crates.io-index" 1573source = "registry+https://github.com/rust-lang/crates.io-index"
1563checksum = "4211ce9909eb971f111059df92c45640aad50a619cf55cd76476be803c4c68e6" 1574checksum = "cc60a3d73ea6594cd712d830cc1f0390fd71542d8c8cd24e70cc54cdfd5e05d5"
1564dependencies = [ 1575dependencies = [
1565 "proc-macro2", 1576 "proc-macro2",
1566 "quote", 1577 "quote",
@@ -1643,9 +1654,9 @@ checksum = "db3c46be180f1af9673ebb27bc1235396f61ef6965b3fe0dbb2e624deb604f0e"
1643 1654
1644[[package]] 1655[[package]]
1645name = "thread_local" 1656name = "thread_local"
1646version = "1.0.1" 1657version = "1.1.0"
1647source = "registry+https://github.com/rust-lang/crates.io-index" 1658source = "registry+https://github.com/rust-lang/crates.io-index"
1648checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" 1659checksum = "bb9bc092d0d51e76b2b19d9d85534ffc9ec2db959a2523cdae0697e2972cd447"
1649dependencies = [ 1660dependencies = [
1650 "lazy_static", 1661 "lazy_static",
1651] 1662]
diff --git a/crates/assists/src/handlers/remove_dbg.rs b/crates/assists/src/handlers/remove_dbg.rs
index 0320c2f12..6114091f2 100644
--- a/crates/assists/src/handlers/remove_dbg.rs
+++ b/crates/assists/src/handlers/remove_dbg.rs
@@ -1,6 +1,6 @@
1use syntax::{ 1use syntax::{
2 ast::{self, AstNode}, 2 ast::{self, AstNode},
3 match_ast, SyntaxElement, SyntaxKind, TextRange, TextSize, T, 3 match_ast, SyntaxElement, TextRange, TextSize, T,
4}; 4};
5 5
6use crate::{AssistContext, AssistId, AssistKind, Assists}; 6use crate::{AssistContext, AssistId, AssistKind, Assists};
@@ -136,14 +136,14 @@ fn needs_parentheses_around_macro_contents(macro_contents: Vec<SyntaxElement>) -
136 symbol_kind => { 136 symbol_kind => {
137 let symbol_not_in_bracket = unpaired_brackets_in_contents.is_empty(); 137 let symbol_not_in_bracket = unpaired_brackets_in_contents.is_empty();
138 if symbol_not_in_bracket 138 if symbol_not_in_bracket
139 && symbol_kind != SyntaxKind::COLON // paths 139 && symbol_kind != T![:] // paths
140 && (symbol_kind != SyntaxKind::DOT // field/method access 140 && (symbol_kind != T![.] // field/method access
141 || macro_contents // range expressions consist of two SyntaxKind::Dot in macro invocations 141 || macro_contents // range expressions consist of two SyntaxKind::Dot in macro invocations
142 .peek() 142 .peek()
143 .map(|element| element.kind() == SyntaxKind::DOT) 143 .map(|element| element.kind() == T![.])
144 .unwrap_or(false)) 144 .unwrap_or(false))
145 && symbol_kind != SyntaxKind::QUESTION // try operator 145 && symbol_kind != T![?] // try operator
146 && (symbol_kind.is_punct() || symbol_kind == SyntaxKind::AS_KW) 146 && (symbol_kind.is_punct() || symbol_kind == T![as])
147 { 147 {
148 return true; 148 return true;
149 } 149 }
diff --git a/crates/completion/src/completions/attribute.rs b/crates/completion/src/completions/attribute.rs
index 3a29b5203..e5522980d 100644
--- a/crates/completion/src/completions/attribute.rs
+++ b/crates/completion/src/completions/attribute.rs
@@ -5,7 +5,7 @@
5 5
6use itertools::Itertools; 6use itertools::Itertools;
7use rustc_hash::FxHashSet; 7use rustc_hash::FxHashSet;
8use syntax::{ast, AstNode, SyntaxKind}; 8use syntax::{ast, AstNode, T};
9 9
10use crate::{ 10use crate::{
11 context::CompletionContext, 11 context::CompletionContext,
@@ -21,15 +21,17 @@ pub(crate) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext)
21 21
22 let attribute = ctx.attribute_under_caret.as_ref()?; 22 let attribute = ctx.attribute_under_caret.as_ref()?;
23 match (attribute.path(), attribute.token_tree()) { 23 match (attribute.path(), attribute.token_tree()) {
24 (Some(path), Some(token_tree)) => match path.to_string().as_str() { 24 (Some(path), Some(token_tree)) => {
25 "derive" => complete_derive(acc, ctx, token_tree), 25 let path = path.syntax().text();
26 "feature" => complete_lint(acc, ctx, token_tree, FEATURES), 26 if path == "derive" {
27 "allow" | "warn" | "deny" | "forbid" => { 27 complete_derive(acc, ctx, token_tree)
28 } else if path == "feature" {
29 complete_lint(acc, ctx, token_tree, FEATURES)
30 } else if path == "allow" || path == "warn" || path == "deny" || path == "forbid" {
28 complete_lint(acc, ctx, token_tree.clone(), DEFAULT_LINT_COMPLETIONS); 31 complete_lint(acc, ctx, token_tree.clone(), DEFAULT_LINT_COMPLETIONS);
29 complete_lint(acc, ctx, token_tree, CLIPPY_LINTS); 32 complete_lint(acc, ctx, token_tree, CLIPPY_LINTS);
30 } 33 }
31 _ => {} 34 }
32 },
33 (_, Some(_token_tree)) => {} 35 (_, Some(_token_tree)) => {}
34 _ => complete_attribute_start(acc, ctx, attribute), 36 _ => complete_attribute_start(acc, ctx, attribute),
35 } 37 }
@@ -203,8 +205,7 @@ fn complete_lint(
203fn parse_comma_sep_input(derive_input: ast::TokenTree) -> Result<FxHashSet<String>, ()> { 205fn parse_comma_sep_input(derive_input: ast::TokenTree) -> Result<FxHashSet<String>, ()> {
204 match (derive_input.left_delimiter_token(), derive_input.right_delimiter_token()) { 206 match (derive_input.left_delimiter_token(), derive_input.right_delimiter_token()) {
205 (Some(left_paren), Some(right_paren)) 207 (Some(left_paren), Some(right_paren))
206 if left_paren.kind() == SyntaxKind::L_PAREN 208 if left_paren.kind() == T!['('] && right_paren.kind() == T![')'] =>
207 && right_paren.kind() == SyntaxKind::R_PAREN =>
208 { 209 {
209 let mut input_derives = FxHashSet::default(); 210 let mut input_derives = FxHashSet::default();
210 let mut current_derive = String::new(); 211 let mut current_derive = String::new();
@@ -216,7 +217,7 @@ fn parse_comma_sep_input(derive_input: ast::TokenTree) -> Result<FxHashSet<Strin
216 .skip(1) 217 .skip(1)
217 .take_while(|token| token != &right_paren) 218 .take_while(|token| token != &right_paren)
218 { 219 {
219 if SyntaxKind::COMMA == token.kind() { 220 if T![,] == token.kind() {
220 if !current_derive.is_empty() { 221 if !current_derive.is_empty() {
221 input_derives.insert(current_derive); 222 input_derives.insert(current_derive);
222 current_derive = String::new(); 223 current_derive = String::new();
diff --git a/crates/completion/src/completions/postfix.rs b/crates/completion/src/completions/postfix.rs
index 87f0c0b2a..9c34ed0b6 100644
--- a/crates/completion/src/completions/postfix.rs
+++ b/crates/completion/src/completions/postfix.rs
@@ -35,11 +35,14 @@ pub(crate) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) {
35 None => return, 35 None => return,
36 }; 36 };
37 37
38 let ref_removed_ty =
39 std::iter::successors(Some(receiver_ty.clone()), |ty| ty.remove_ref()).last().unwrap();
40
38 let cap = match ctx.config.snippet_cap { 41 let cap = match ctx.config.snippet_cap {
39 Some(it) => it, 42 Some(it) => it,
40 None => return, 43 None => return,
41 }; 44 };
42 let try_enum = TryEnum::from_ty(&ctx.sema, &receiver_ty); 45 let try_enum = TryEnum::from_ty(&ctx.sema, &ref_removed_ty);
43 if let Some(try_enum) = &try_enum { 46 if let Some(try_enum) = &try_enum {
44 match try_enum { 47 match try_enum {
45 TryEnum::Result => { 48 TryEnum::Result => {
@@ -496,6 +499,27 @@ fn main() {
496 fn postfix_completion_for_references() { 499 fn postfix_completion_for_references() {
497 check_edit("dbg", r#"fn main() { &&42.$0 }"#, r#"fn main() { dbg!(&&42) }"#); 500 check_edit("dbg", r#"fn main() { &&42.$0 }"#, r#"fn main() { dbg!(&&42) }"#);
498 check_edit("refm", r#"fn main() { &&42.$0 }"#, r#"fn main() { &&&mut 42 }"#); 501 check_edit("refm", r#"fn main() { &&42.$0 }"#, r#"fn main() { &&&mut 42 }"#);
502 check_edit(
503 "ifl",
504 r#"
505enum Option<T> { Some(T), None }
506
507fn main() {
508 let bar = &Option::Some(true);
509 bar.$0
510}
511"#,
512 r#"
513enum Option<T> { Some(T), None }
514
515fn main() {
516 let bar = &Option::Some(true);
517 if let Some($1) = bar {
518 $0
519}
520}
521"#,
522 )
499 } 523 }
500 524
501 #[test] 525 #[test]
diff --git a/crates/completion/src/completions/trait_impl.rs b/crates/completion/src/completions/trait_impl.rs
index aa9c845da..135ae49dc 100644
--- a/crates/completion/src/completions/trait_impl.rs
+++ b/crates/completion/src/completions/trait_impl.rs
@@ -93,11 +93,11 @@ fn completion_match(ctx: &CompletionContext) -> Option<(ImplCompletionKind, Synt
93 // `impl .. { const $0 }` 93 // `impl .. { const $0 }`
94 // ERROR 0 94 // ERROR 0
95 // CONST_KW <- * 95 // CONST_KW <- *
96 SyntaxKind::CONST_KW => 0, 96 T![const] => 0,
97 // `impl .. { fn/type $0 }` 97 // `impl .. { fn/type $0 }`
98 // FN/TYPE_ALIAS 0 98 // FN/TYPE_ALIAS 0
99 // FN_KW <- * 99 // FN_KW <- *
100 SyntaxKind::FN_KW | SyntaxKind::TYPE_KW => 0, 100 T![fn] | T![type] => 0,
101 // `impl .. { fn/type/const foo$0 }` 101 // `impl .. { fn/type/const foo$0 }`
102 // FN/TYPE_ALIAS/CONST 1 102 // FN/TYPE_ALIAS/CONST 1
103 // NAME 0 103 // NAME 0
@@ -121,7 +121,7 @@ fn completion_match(ctx: &CompletionContext) -> Option<(ImplCompletionKind, Synt
121 let impl_def = ast::Impl::cast(impl_item.parent()?.parent()?)?; 121 let impl_def = ast::Impl::cast(impl_item.parent()?.parent()?)?;
122 let kind = match impl_item.kind() { 122 let kind = match impl_item.kind() {
123 // `impl ... { const $0 fn/type/const }` 123 // `impl ... { const $0 fn/type/const }`
124 _ if token.kind() == SyntaxKind::CONST_KW => ImplCompletionKind::Const, 124 _ if token.kind() == T![const] => ImplCompletionKind::Const,
125 SyntaxKind::CONST | SyntaxKind::ERROR => ImplCompletionKind::Const, 125 SyntaxKind::CONST | SyntaxKind::ERROR => ImplCompletionKind::Const,
126 SyntaxKind::TYPE_ALIAS => ImplCompletionKind::TypeAlias, 126 SyntaxKind::TYPE_ALIAS => ImplCompletionKind::TypeAlias,
127 SyntaxKind::FN => ImplCompletionKind::Fn, 127 SyntaxKind::FN => ImplCompletionKind::Fn,
diff --git a/crates/completion/src/patterns.rs b/crates/completion/src/patterns.rs
index f148b9402..f3ce91dd1 100644
--- a/crates/completion/src/patterns.rs
+++ b/crates/completion/src/patterns.rs
@@ -5,7 +5,7 @@ use syntax::{
5 ast::{self, LoopBodyOwner}, 5 ast::{self, LoopBodyOwner},
6 match_ast, AstNode, Direction, NodeOrToken, SyntaxElement, 6 match_ast, AstNode, Direction, NodeOrToken, SyntaxElement,
7 SyntaxKind::*, 7 SyntaxKind::*,
8 SyntaxNode, SyntaxToken, 8 SyntaxNode, SyntaxToken, T,
9}; 9};
10 10
11#[cfg(test)] 11#[cfg(test)]
@@ -119,7 +119,7 @@ pub(crate) fn unsafe_is_prev(element: SyntaxElement) -> bool {
119 element 119 element
120 .into_token() 120 .into_token()
121 .and_then(|it| previous_non_trivia_token(it)) 121 .and_then(|it| previous_non_trivia_token(it))
122 .filter(|it| it.kind() == UNSAFE_KW) 122 .filter(|it| it.kind() == T![unsafe])
123 .is_some() 123 .is_some()
124} 124}
125#[test] 125#[test]
@@ -131,7 +131,7 @@ pub(crate) fn if_is_prev(element: SyntaxElement) -> bool {
131 element 131 element
132 .into_token() 132 .into_token()
133 .and_then(|it| previous_non_trivia_token(it)) 133 .and_then(|it| previous_non_trivia_token(it))
134 .filter(|it| it.kind() == IF_KW) 134 .filter(|it| it.kind() == T![if])
135 .is_some() 135 .is_some()
136} 136}
137 137
@@ -139,7 +139,7 @@ pub(crate) fn fn_is_prev(element: SyntaxElement) -> bool {
139 element 139 element
140 .into_token() 140 .into_token()
141 .and_then(|it| previous_non_trivia_token(it)) 141 .and_then(|it| previous_non_trivia_token(it))
142 .filter(|it| it.kind() == FN_KW) 142 .filter(|it| it.kind() == T![fn])
143 .is_some() 143 .is_some()
144} 144}
145#[test] 145#[test]
@@ -154,7 +154,7 @@ pub(crate) fn for_is_prev2(element: SyntaxElement) -> bool {
154 .into_token() 154 .into_token()
155 .and_then(|it| previous_non_trivia_token(it)) 155 .and_then(|it| previous_non_trivia_token(it))
156 .and_then(|it| previous_non_trivia_token(it)) 156 .and_then(|it| previous_non_trivia_token(it))
157 .filter(|it| it.kind() == FOR_KW) 157 .filter(|it| it.kind() == T![for])
158 .is_some() 158 .is_some()
159} 159}
160#[test] 160#[test]
diff --git a/crates/flycheck/Cargo.toml b/crates/flycheck/Cargo.toml
index 1bad64a1b..1d19c7886 100644
--- a/crates/flycheck/Cargo.toml
+++ b/crates/flycheck/Cargo.toml
@@ -12,7 +12,7 @@ doctest = false
12[dependencies] 12[dependencies]
13crossbeam-channel = "0.5.0" 13crossbeam-channel = "0.5.0"
14log = "0.4.8" 14log = "0.4.8"
15cargo_metadata = "=0.12.0" 15cargo_metadata = "0.12.2"
16serde_json = "1.0.48" 16serde_json = "1.0.48"
17jod-thread = "0.1.1" 17jod-thread = "0.1.1"
18 18
diff --git a/crates/hir/src/code_model.rs b/crates/hir/src/code_model.rs
index cc1938333..6cbf5cecf 100644
--- a/crates/hir/src/code_model.rs
+++ b/crates/hir/src/code_model.rs
@@ -1263,6 +1263,24 @@ pub enum GenericParam {
1263} 1263}
1264impl_from!(TypeParam, LifetimeParam, ConstParam for GenericParam); 1264impl_from!(TypeParam, LifetimeParam, ConstParam for GenericParam);
1265 1265
1266impl GenericParam {
1267 pub fn module(self, db: &dyn HirDatabase) -> Module {
1268 match self {
1269 GenericParam::TypeParam(it) => it.module(db),
1270 GenericParam::LifetimeParam(it) => it.module(db),
1271 GenericParam::ConstParam(it) => it.module(db),
1272 }
1273 }
1274
1275 pub fn name(self, db: &dyn HirDatabase) -> Name {
1276 match self {
1277 GenericParam::TypeParam(it) => it.name(db),
1278 GenericParam::LifetimeParam(it) => it.name(db),
1279 GenericParam::ConstParam(it) => it.name(db),
1280 }
1281 }
1282}
1283
1266#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] 1284#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1267pub struct TypeParam { 1285pub struct TypeParam {
1268 pub(crate) id: TypeParamId, 1286 pub(crate) id: TypeParamId,
diff --git a/crates/hir/src/from_id.rs b/crates/hir/src/from_id.rs
index 3e47a5e9d..c8c5fecd7 100644
--- a/crates/hir/src/from_id.rs
+++ b/crates/hir/src/from_id.rs
@@ -1,6 +1,6 @@
1//! Utility module for converting between hir_def ids and code_model wrappers. 1//! Utility module for converting between hir_def ids and code_model wrappers.
2//! 2//!
3//! It's unclear if we need this long-term, but it's definitelly useful while we 3//! It's unclear if we need this long-term, but it's definitely useful while we
4//! are splitting the hir. 4//! are splitting the hir.
5 5
6use hir_def::{ 6use hir_def::{
diff --git a/crates/hir_def/src/body/lower.rs b/crates/hir_def/src/body/lower.rs
index 6be1eaade..3dc33f248 100644
--- a/crates/hir_def/src/body/lower.rs
+++ b/crates/hir_def/src/body/lower.rs
@@ -581,7 +581,7 @@ impl ExprCollector<'_> {
581 match res.value { 581 match res.value {
582 Some((mark, expansion)) => { 582 Some((mark, expansion)) => {
583 // FIXME: Statements are too complicated to recover from error for now. 583 // FIXME: Statements are too complicated to recover from error for now.
584 // It is because we don't have any hygenine for local variable expansion right now. 584 // It is because we don't have any hygiene for local variable expansion right now.
585 if T::can_cast(syntax::SyntaxKind::MACRO_STMTS) && res.err.is_some() { 585 if T::can_cast(syntax::SyntaxKind::MACRO_STMTS) && res.err.is_some() {
586 self.expander.exit(self.db, mark); 586 self.expander.exit(self.db, mark);
587 collector(self, None); 587 collector(self, None);
@@ -959,7 +959,7 @@ impl ExprCollector<'_> {
959 959
960 fn collect_tuple_pat(&mut self, args: AstChildren<ast::Pat>) -> (Vec<PatId>, Option<usize>) { 960 fn collect_tuple_pat(&mut self, args: AstChildren<ast::Pat>) -> (Vec<PatId>, Option<usize>) {
961 // Find the location of the `..`, if there is one. Note that we do not 961 // Find the location of the `..`, if there is one. Note that we do not
962 // consider the possiblity of there being multiple `..` here. 962 // consider the possibility of there being multiple `..` here.
963 let ellipsis = args.clone().position(|p| matches!(p, ast::Pat::RestPat(_))); 963 let ellipsis = args.clone().position(|p| matches!(p, ast::Pat::RestPat(_)));
964 // We want to skip the `..` pattern here, since we account for it above. 964 // We want to skip the `..` pattern here, since we account for it above.
965 let args = args 965 let args = args
diff --git a/crates/hir_def/src/expr.rs b/crates/hir_def/src/expr.rs
index 6a481769d..76f5721e5 100644
--- a/crates/hir_def/src/expr.rs
+++ b/crates/hir_def/src/expr.rs
@@ -1,6 +1,6 @@
1//! This module describes hir-level representation of expressions. 1//! This module describes hir-level representation of expressions.
2//! 2//!
3//! This representaion is: 3//! This representation is:
4//! 4//!
5//! 1. Identity-based. Each expression has an `id`, so we can distinguish 5//! 1. Identity-based. Each expression has an `id`, so we can distinguish
6//! between different `1` in `1 + 1`. 6//! between different `1` in `1 + 1`.
diff --git a/crates/hir_def/src/nameres/collector.rs b/crates/hir_def/src/nameres/collector.rs
index 77017e4ea..f027fd48d 100644
--- a/crates/hir_def/src/nameres/collector.rs
+++ b/crates/hir_def/src/nameres/collector.rs
@@ -267,7 +267,7 @@ impl DefCollector<'_> {
267 267
268 // Resolve all indeterminate resolved imports again 268 // Resolve all indeterminate resolved imports again
269 // As some of the macros will expand newly import shadowing partial resolved imports 269 // As some of the macros will expand newly import shadowing partial resolved imports
270 // FIXME: We maybe could skip this, if we handle the Indetermine imports in `resolve_imports` 270 // FIXME: We maybe could skip this, if we handle the indeterminate imports in `resolve_imports`
271 // correctly 271 // correctly
272 let partial_resolved = self.resolved_imports.iter().filter_map(|directive| { 272 let partial_resolved = self.resolved_imports.iter().filter_map(|directive| {
273 if let PartialResolvedImport::Indeterminate(_) = directive.status { 273 if let PartialResolvedImport::Indeterminate(_) = directive.status {
@@ -402,7 +402,7 @@ impl DefCollector<'_> {
402 402
403 /// Define a proc macro 403 /// Define a proc macro
404 /// 404 ///
405 /// A proc macro is similar to normal macro scope, but it would not visiable in legacy textual scoped. 405 /// A proc macro is similar to normal macro scope, but it would not visible in legacy textual scoped.
406 /// And unconditionally exported. 406 /// And unconditionally exported.
407 fn define_proc_macro(&mut self, name: Name, macro_: MacroDefId) { 407 fn define_proc_macro(&mut self, name: Name, macro_: MacroDefId) {
408 self.update( 408 self.update(
@@ -592,7 +592,7 @@ impl DefCollector<'_> {
592 // XXX: urgh, so this works by accident! Here, we look at 592 // XXX: urgh, so this works by accident! Here, we look at
593 // the enum data, and, in theory, this might require us to 593 // the enum data, and, in theory, this might require us to
594 // look back at the crate_def_map, creating a cycle. For 594 // look back at the crate_def_map, creating a cycle. For
595 // example, `enum E { crate::some_macro!(); }`. Luckely, the 595 // example, `enum E { crate::some_macro!(); }`. Luckily, the
596 // only kind of macro that is allowed inside enum is a 596 // only kind of macro that is allowed inside enum is a
597 // `cfg_macro`, and we don't need to run name resolution for 597 // `cfg_macro`, and we don't need to run name resolution for
598 // it, but this is sheer luck! 598 // it, but this is sheer luck!
@@ -655,7 +655,7 @@ impl DefCollector<'_> {
655 &mut self, 655 &mut self,
656 module_id: LocalModuleId, 656 module_id: LocalModuleId,
657 resolutions: &[(Option<Name>, PerNs)], 657 resolutions: &[(Option<Name>, PerNs)],
658 // All resolutions are imported with this visibility; the visibilies in 658 // All resolutions are imported with this visibility; the visibilities in
659 // the `PerNs` values are ignored and overwritten 659 // the `PerNs` values are ignored and overwritten
660 vis: Visibility, 660 vis: Visibility,
661 import_type: ImportType, 661 import_type: ImportType,
diff --git a/crates/hir_def/src/resolver.rs b/crates/hir_def/src/resolver.rs
index 129f1dbac..e4152a0be 100644
--- a/crates/hir_def/src/resolver.rs
+++ b/crates/hir_def/src/resolver.rs
@@ -27,7 +27,7 @@ use crate::{
27 27
28#[derive(Debug, Clone, Default)] 28#[derive(Debug, Clone, Default)]
29pub struct Resolver { 29pub struct Resolver {
30 // FIXME: all usages generally call `.rev`, so maybe reverse once in consturciton? 30 // FIXME: all usages generally call `.rev`, so maybe reverse once in construction?
31 scopes: Vec<Scope>, 31 scopes: Vec<Scope>,
32} 32}
33 33
diff --git a/crates/hir_expand/src/db.rs b/crates/hir_expand/src/db.rs
index ab2637b8c..c62086390 100644
--- a/crates/hir_expand/src/db.rs
+++ b/crates/hir_expand/src/db.rs
@@ -40,7 +40,7 @@ impl TokenExpander {
40 // FIXME switch these to ExpandResult as well 40 // FIXME switch these to ExpandResult as well
41 TokenExpander::BuiltinDerive(it) => it.expand(db, id, tt).into(), 41 TokenExpander::BuiltinDerive(it) => it.expand(db, id, tt).into(),
42 TokenExpander::ProcMacro(_) => { 42 TokenExpander::ProcMacro(_) => {
43 // We store the result in salsa db to prevent non-determinisc behavior in 43 // We store the result in salsa db to prevent non-deterministic behavior in
44 // some proc-macro implementation 44 // some proc-macro implementation
45 // See #4315 for details 45 // See #4315 for details
46 db.expand_proc_macro(id.into()).into() 46 db.expand_proc_macro(id.into()).into()
diff --git a/crates/hir_ty/src/diagnostics/expr.rs b/crates/hir_ty/src/diagnostics/expr.rs
index a1c484fdf..107417c27 100644
--- a/crates/hir_ty/src/diagnostics/expr.rs
+++ b/crates/hir_ty/src/diagnostics/expr.rs
@@ -379,7 +379,7 @@ pub fn record_literal_missing_fields(
379 id: ExprId, 379 id: ExprId,
380 expr: &Expr, 380 expr: &Expr,
381) -> Option<(VariantId, Vec<LocalFieldId>, /*exhaustive*/ bool)> { 381) -> Option<(VariantId, Vec<LocalFieldId>, /*exhaustive*/ bool)> {
382 let (fields, exhausitve) = match expr { 382 let (fields, exhaustive) = match expr {
383 Expr::RecordLit { path: _, fields, spread } => (fields, spread.is_none()), 383 Expr::RecordLit { path: _, fields, spread } => (fields, spread.is_none()),
384 _ => return None, 384 _ => return None,
385 }; 385 };
@@ -400,7 +400,7 @@ pub fn record_literal_missing_fields(
400 if missed_fields.is_empty() { 400 if missed_fields.is_empty() {
401 return None; 401 return None;
402 } 402 }
403 Some((variant_def, missed_fields, exhausitve)) 403 Some((variant_def, missed_fields, exhaustive))
404} 404}
405 405
406pub fn record_pattern_missing_fields( 406pub fn record_pattern_missing_fields(
diff --git a/crates/hir_ty/src/diagnostics/match_check.rs b/crates/hir_ty/src/diagnostics/match_check.rs
index 62c329731..61c47eec8 100644
--- a/crates/hir_ty/src/diagnostics/match_check.rs
+++ b/crates/hir_ty/src/diagnostics/match_check.rs
@@ -14,7 +14,7 @@
14//! The algorithm implemented here is a modified version of the one described in 14//! The algorithm implemented here is a modified version of the one described in
15//! <http://moscova.inria.fr/~maranget/papers/warn/index.html>. 15//! <http://moscova.inria.fr/~maranget/papers/warn/index.html>.
16//! However, to save future implementors from reading the original paper, we 16//! However, to save future implementors from reading the original paper, we
17//! summarise the algorithm here to hopefully save time and be a little clearer 17//! summarize the algorithm here to hopefully save time and be a little clearer
18//! (without being so rigorous). 18//! (without being so rigorous).
19//! 19//!
20//! The core of the algorithm revolves about a "usefulness" check. In particular, we 20//! The core of the algorithm revolves about a "usefulness" check. In particular, we
@@ -132,7 +132,7 @@
132//! The algorithm is inductive (on the number of columns: i.e., components of tuple patterns). 132//! The algorithm is inductive (on the number of columns: i.e., components of tuple patterns).
133//! That means we're going to check the components from left-to-right, so the algorithm 133//! That means we're going to check the components from left-to-right, so the algorithm
134//! operates principally on the first component of the matrix and new pattern-stack `p`. 134//! operates principally on the first component of the matrix and new pattern-stack `p`.
135//! This algorithm is realised in the `is_useful` function. 135//! This algorithm is realized in the `is_useful` function.
136//! 136//!
137//! Base case (`n = 0`, i.e., an empty tuple pattern): 137//! Base case (`n = 0`, i.e., an empty tuple pattern):
138//! - If `P` already contains an empty pattern (i.e., if the number of patterns `m > 0`), then 138//! - If `P` already contains an empty pattern (i.e., if the number of patterns `m > 0`), then
diff --git a/crates/hir_ty/src/lower.rs b/crates/hir_ty/src/lower.rs
index 222f61a11..9594cce8b 100644
--- a/crates/hir_ty/src/lower.rs
+++ b/crates/hir_ty/src/lower.rs
@@ -491,16 +491,16 @@ impl Ty {
491 fn from_hir_path_inner( 491 fn from_hir_path_inner(
492 ctx: &TyLoweringContext<'_>, 492 ctx: &TyLoweringContext<'_>,
493 segment: PathSegment<'_>, 493 segment: PathSegment<'_>,
494 typable: TyDefId, 494 typeable: TyDefId,
495 infer_args: bool, 495 infer_args: bool,
496 ) -> Ty { 496 ) -> Ty {
497 let generic_def = match typable { 497 let generic_def = match typeable {
498 TyDefId::BuiltinType(_) => None, 498 TyDefId::BuiltinType(_) => None,
499 TyDefId::AdtId(it) => Some(it.into()), 499 TyDefId::AdtId(it) => Some(it.into()),
500 TyDefId::TypeAliasId(it) => Some(it.into()), 500 TyDefId::TypeAliasId(it) => Some(it.into()),
501 }; 501 };
502 let substs = substs_from_path_segment(ctx, segment, generic_def, infer_args); 502 let substs = substs_from_path_segment(ctx, segment, generic_def, infer_args);
503 ctx.db.ty(typable).subst(&substs) 503 ctx.db.ty(typeable).subst(&substs)
504 } 504 }
505 505
506 /// Collect generic arguments from a path into a `Substs`. See also 506 /// Collect generic arguments from a path into a `Substs`. See also
diff --git a/crates/ide/src/call_hierarchy.rs b/crates/ide/src/call_hierarchy.rs
index 4d8983cb2..b29d1fef9 100644
--- a/crates/ide/src/call_hierarchy.rs
+++ b/crates/ide/src/call_hierarchy.rs
@@ -5,7 +5,7 @@ use indexmap::IndexMap;
5use hir::Semantics; 5use hir::Semantics;
6use ide_db::call_info::FnCallNode; 6use ide_db::call_info::FnCallNode;
7use ide_db::RootDatabase; 7use ide_db::RootDatabase;
8use syntax::{ast, match_ast, AstNode, TextRange}; 8use syntax::{ast, AstNode, TextRange};
9 9
10use crate::{ 10use crate::{
11 display::TryToNav, goto_definition, references, FilePosition, NavigationTarget, RangeInfo, 11 display::TryToNav, goto_definition, references, FilePosition, NavigationTarget, RangeInfo,
@@ -57,15 +57,9 @@ pub(crate) fn incoming_calls(db: &RootDatabase, position: FilePosition) -> Optio
57 57
58 // This target is the containing function 58 // This target is the containing function
59 if let Some(nav) = syntax.ancestors().find_map(|node| { 59 if let Some(nav) = syntax.ancestors().find_map(|node| {
60 match_ast! { 60 let fn_ = ast::Fn::cast(node)?;
61 match node { 61 let def = sema.to_def(&fn_)?;
62 ast::Fn(it) => { 62 def.try_to_nav(sema.db)
63 let def = sema.to_def(&it)?;
64 def.try_to_nav(sema.db)
65 },
66 _ => None,
67 }
68 }
69 }) { 63 }) {
70 let relative_range = reference.file_range.range; 64 let relative_range = reference.file_range.range;
71 calls.add(&nav, relative_range); 65 calls.add(&nav, relative_range);
@@ -91,17 +85,12 @@ pub(crate) fn outgoing_calls(db: &RootDatabase, position: FilePosition) -> Optio
91 .filter_map(|node| FnCallNode::with_node_exact(&node)) 85 .filter_map(|node| FnCallNode::with_node_exact(&node))
92 .filter_map(|call_node| { 86 .filter_map(|call_node| {
93 let name_ref = call_node.name_ref()?; 87 let name_ref = call_node.name_ref()?;
94 88 let func_target = match call_node {
95 if let Some(func_target) = match &call_node {
96 FnCallNode::CallExpr(expr) => { 89 FnCallNode::CallExpr(expr) => {
97 //FIXME: Type::as_callable is broken 90 //FIXME: Type::as_callable is broken
98 let callable = sema.type_of_expr(&expr.expr()?)?.as_callable(db)?; 91 let callable = sema.type_of_expr(&expr.expr()?)?.as_callable(db)?;
99 match callable.kind() { 92 match callable.kind() {
100 hir::CallableKind::Function(it) => { 93 hir::CallableKind::Function(it) => it.try_to_nav(db),
101 let fn_def: hir::Function = it.into();
102 let nav = fn_def.try_to_nav(db)?;
103 Some(nav)
104 }
105 _ => None, 94 _ => None,
106 } 95 }
107 } 96 }
@@ -109,11 +98,8 @@ pub(crate) fn outgoing_calls(db: &RootDatabase, position: FilePosition) -> Optio
109 let function = sema.resolve_method_call(&expr)?; 98 let function = sema.resolve_method_call(&expr)?;
110 function.try_to_nav(db) 99 function.try_to_nav(db)
111 } 100 }
112 } { 101 }?;
113 Some((func_target, name_ref.syntax().text_range())) 102 Some((func_target, name_ref.syntax().text_range()))
114 } else {
115 None
116 }
117 }) 103 })
118 .for_each(|(nav, range)| calls.add(&nav, range)); 104 .for_each(|(nav, range)| calls.add(&nav, range));
119 105
diff --git a/crates/ide/src/display/navigation_target.rs b/crates/ide/src/display/navigation_target.rs
index e24c78301..4eecae697 100644
--- a/crates/ide/src/display/navigation_target.rs
+++ b/crates/ide/src/display/navigation_target.rs
@@ -215,10 +215,8 @@ impl TryToNav for Definition {
215 Definition::ModuleDef(it) => it.try_to_nav(db), 215 Definition::ModuleDef(it) => it.try_to_nav(db),
216 Definition::SelfType(it) => it.try_to_nav(db), 216 Definition::SelfType(it) => it.try_to_nav(db),
217 Definition::Local(it) => Some(it.to_nav(db)), 217 Definition::Local(it) => Some(it.to_nav(db)),
218 Definition::TypeParam(it) => it.try_to_nav(db), 218 Definition::GenericParam(it) => it.try_to_nav(db),
219 Definition::LifetimeParam(it) => it.try_to_nav(db),
220 Definition::Label(it) => Some(it.to_nav(db)), 219 Definition::Label(it) => Some(it.to_nav(db)),
221 Definition::ConstParam(it) => it.try_to_nav(db),
222 } 220 }
223 } 221 }
224} 222}
@@ -389,6 +387,16 @@ impl TryToNav for hir::AssocItem {
389 } 387 }
390} 388}
391 389
390impl TryToNav for hir::GenericParam {
391 fn try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget> {
392 match self {
393 hir::GenericParam::TypeParam(it) => it.try_to_nav(db),
394 hir::GenericParam::ConstParam(it) => it.try_to_nav(db),
395 hir::GenericParam::LifetimeParam(it) => it.try_to_nav(db),
396 }
397 }
398}
399
392impl ToNav for hir::Local { 400impl ToNav for hir::Local {
393 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget { 401 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
394 let src = self.source(db); 402 let src = self.source(db);
diff --git a/crates/ide/src/doc_links.rs b/crates/ide/src/doc_links.rs
index 1ff818de2..de10406bc 100644
--- a/crates/ide/src/doc_links.rs
+++ b/crates/ide/src/doc_links.rs
@@ -1,6 +1,6 @@
1//! Resolves and rewrites links in markdown documentation. 1//! Resolves and rewrites links in markdown documentation.
2 2
3use std::{convert::TryFrom, iter::once}; 3use std::{convert::TryFrom, iter::once, ops::Range};
4 4
5use itertools::Itertools; 5use itertools::Itertools;
6use pulldown_cmark::{BrokenLink, CowStr, Event, InlineStr, LinkType, Options, Parser, Tag}; 6use pulldown_cmark::{BrokenLink, CowStr, Event, InlineStr, LinkType, Options, Parser, Tag};
@@ -39,7 +39,7 @@ pub(crate) fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Defi
39 if target.contains("://") { 39 if target.contains("://") {
40 (target.to_string(), title.to_string()) 40 (target.to_string(), title.to_string())
41 } else { 41 } else {
42 // Two posibilities: 42 // Two possibilities:
43 // * path-based links: `../../module/struct.MyStruct.html` 43 // * path-based links: `../../module/struct.MyStruct.html`
44 // * module-based links (AKA intra-doc links): `super::super::module::MyStruct` 44 // * module-based links (AKA intra-doc links): `super::super::module::MyStruct`
45 if let Some(rewritten) = rewrite_intra_doc_link(db, *definition, target, title) { 45 if let Some(rewritten) = rewrite_intra_doc_link(db, *definition, target, title) {
@@ -61,6 +61,30 @@ pub(crate) fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Defi
61 out 61 out
62} 62}
63 63
64pub(crate) fn extract_definitions_from_markdown(
65 markdown: &str,
66) -> Vec<(String, Option<hir::Namespace>, Range<usize>)> {
67 let mut res = vec![];
68 let mut cb = |link: BrokenLink| {
69 Some((
70 /*url*/ link.reference.to_owned().into(),
71 /*title*/ link.reference.to_owned().into(),
72 ))
73 };
74 let doc = Parser::new_with_broken_link_callback(markdown, Options::empty(), Some(&mut cb));
75 for (event, range) in doc.into_offset_iter() {
76 match event {
77 Event::Start(Tag::Link(_link_type, ref target, ref title)) => {
78 let link = if target.is_empty() { title } else { target };
79 let (link, ns) = parse_link(link);
80 res.push((link.to_string(), ns, range));
81 }
82 _ => {}
83 }
84 }
85 res
86}
87
64/// Remove all links in markdown documentation. 88/// Remove all links in markdown documentation.
65pub(crate) fn remove_links(markdown: &str) -> String { 89pub(crate) fn remove_links(markdown: &str) -> String {
66 let mut drop_link = false; 90 let mut drop_link = false;
@@ -192,9 +216,7 @@ fn rewrite_intra_doc_link(
192 Definition::Field(it) => it.resolve_doc_path(db, link, ns), 216 Definition::Field(it) => it.resolve_doc_path(db, link, ns),
193 Definition::SelfType(_) 217 Definition::SelfType(_)
194 | Definition::Local(_) 218 | Definition::Local(_)
195 | Definition::TypeParam(_) 219 | Definition::GenericParam(_)
196 | Definition::ConstParam(_)
197 | Definition::LifetimeParam(_)
198 | Definition::Label(_) => return None, 220 | Definition::Label(_) => return None,
199 }?; 221 }?;
200 let krate = resolved.module(db)?.krate(); 222 let krate = resolved.module(db)?.krate();
@@ -420,7 +442,7 @@ fn get_symbol_fragment(db: &dyn HirDatabase, field_or_assoc: &FieldOrAssocItem)
420 function.as_assoc_item(db).map(|assoc| assoc.container(db)), 442 function.as_assoc_item(db).map(|assoc| assoc.container(db)),
421 Some(AssocItemContainer::Trait(..)) 443 Some(AssocItemContainer::Trait(..))
422 ); 444 );
423 // This distinction may get more complicated when specialisation is available. 445 // This distinction may get more complicated when specialization is available.
424 // Rustdoc makes this decision based on whether a method 'has defaultness'. 446 // Rustdoc makes this decision based on whether a method 'has defaultness'.
425 // Currently this is only the case for provided trait methods. 447 // Currently this is only the case for provided trait methods.
426 if is_trait_method && !function.has_body(db) { 448 if is_trait_method && !function.has_body(db) {
diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs
index 95b4cb9e3..cd4afc804 100644
--- a/crates/ide/src/goto_definition.rs
+++ b/crates/ide/src/goto_definition.rs
@@ -1,14 +1,18 @@
1use either::Either; 1use either::Either;
2use hir::Semantics; 2use hir::{HasAttrs, ModuleDef, Semantics};
3use ide_db::{ 3use ide_db::{
4 base_db::FileId, 4 base_db::FileId,
5 defs::{NameClass, NameRefClass}, 5 defs::{Definition, NameClass, NameRefClass},
6 symbol_index, RootDatabase, 6 symbol_index, RootDatabase,
7}; 7};
8use syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset, T}; 8use syntax::{
9 ast, match_ast, AstNode, AstToken, SyntaxKind::*, SyntaxToken, TextSize, TokenAtOffset, T,
10};
9 11
10use crate::{ 12use crate::{
11 display::{ToNav, TryToNav}, 13 display::{ToNav, TryToNav},
14 doc_links::extract_definitions_from_markdown,
15 runnables::doc_owner_to_def,
12 FilePosition, NavigationTarget, RangeInfo, SymbolKind, 16 FilePosition, NavigationTarget, RangeInfo, SymbolKind,
13}; 17};
14 18
@@ -30,6 +34,10 @@ pub(crate) fn goto_definition(
30 let original_token = pick_best(file.token_at_offset(position.offset))?; 34 let original_token = pick_best(file.token_at_offset(position.offset))?;
31 let token = sema.descend_into_macros(original_token.clone()); 35 let token = sema.descend_into_macros(original_token.clone());
32 let parent = token.parent(); 36 let parent = token.parent();
37 if let Some(comment) = ast::Comment::cast(token.clone()) {
38 let nav = def_for_doc_comment(&sema, position, &comment)?.try_to_nav(db)?;
39 return Some(RangeInfo::new(original_token.text_range(), vec![nav]));
40 }
33 41
34 let nav_targets = match_ast! { 42 let nav_targets = match_ast! {
35 match parent { 43 match parent {
@@ -68,11 +76,58 @@ pub(crate) fn goto_definition(
68 Some(RangeInfo::new(original_token.text_range(), nav_targets)) 76 Some(RangeInfo::new(original_token.text_range(), nav_targets))
69} 77}
70 78
79fn def_for_doc_comment(
80 sema: &Semantics<RootDatabase>,
81 position: FilePosition,
82 doc_comment: &ast::Comment,
83) -> Option<hir::ModuleDef> {
84 let parent = doc_comment.syntax().parent();
85 let (link, ns) = extract_positioned_link_from_comment(position, doc_comment)?;
86
87 let def = doc_owner_to_def(sema, parent)?;
88 match def {
89 Definition::ModuleDef(def) => match def {
90 ModuleDef::Module(it) => it.resolve_doc_path(sema.db, &link, ns),
91 ModuleDef::Function(it) => it.resolve_doc_path(sema.db, &link, ns),
92 ModuleDef::Adt(it) => it.resolve_doc_path(sema.db, &link, ns),
93 ModuleDef::Variant(it) => it.resolve_doc_path(sema.db, &link, ns),
94 ModuleDef::Const(it) => it.resolve_doc_path(sema.db, &link, ns),
95 ModuleDef::Static(it) => it.resolve_doc_path(sema.db, &link, ns),
96 ModuleDef::Trait(it) => it.resolve_doc_path(sema.db, &link, ns),
97 ModuleDef::TypeAlias(it) => it.resolve_doc_path(sema.db, &link, ns),
98 ModuleDef::BuiltinType(_) => return None,
99 },
100 Definition::Macro(it) => it.resolve_doc_path(sema.db, &link, ns),
101 Definition::Field(it) => it.resolve_doc_path(sema.db, &link, ns),
102 Definition::SelfType(_)
103 | Definition::Local(_)
104 | Definition::GenericParam(_)
105 | Definition::Label(_) => return None,
106 }
107}
108
109fn extract_positioned_link_from_comment(
110 position: FilePosition,
111 comment: &ast::Comment,
112) -> Option<(String, Option<hir::Namespace>)> {
113 let comment_range = comment.syntax().text_range();
114 let doc_comment = comment.doc_comment()?;
115 let def_links = extract_definitions_from_markdown(doc_comment);
116 let (def_link, ns, _) = def_links.iter().min_by_key(|(_, _, def_link_range)| {
117 let matched_position = comment_range.start() + TextSize::from(def_link_range.start as u32);
118 match position.offset.checked_sub(matched_position) {
119 Some(distance) => distance,
120 None => comment_range.end(),
121 }
122 })?;
123 Some((def_link.to_string(), ns.clone()))
124}
125
71fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> { 126fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> {
72 return tokens.max_by_key(priority); 127 return tokens.max_by_key(priority);
73 fn priority(n: &SyntaxToken) -> usize { 128 fn priority(n: &SyntaxToken) -> usize {
74 match n.kind() { 129 match n.kind() {
75 IDENT | INT_NUMBER | LIFETIME_IDENT | T![self] => 2, 130 IDENT | INT_NUMBER | LIFETIME_IDENT | T![self] | COMMENT => 2,
76 kind if kind.is_trivia() => 0, 131 kind if kind.is_trivia() => 0,
77 _ => 1, 132 _ => 1,
78 } 133 }
@@ -1145,4 +1200,34 @@ fn foo<'foo>(_: &'foo ()) {
1145}"#, 1200}"#,
1146 ) 1201 )
1147 } 1202 }
1203
1204 #[test]
1205 fn goto_def_for_intra_doc_link_same_file() {
1206 check(
1207 r#"
1208/// Blah, [`bar`](bar) .. [`foo`](foo)$0 has [`bar`](bar)
1209pub fn bar() { }
1210
1211/// You might want to see [`std::fs::read()`] too.
1212pub fn foo() { }
1213 //^^^
1214
1215}"#,
1216 )
1217 }
1218
1219 #[test]
1220 fn goto_def_for_intra_doc_link_inner() {
1221 check(
1222 r#"
1223//- /main.rs
1224mod m;
1225struct S;
1226 //^
1227
1228//- /m.rs
1229//! [`super::S$0`]
1230"#,
1231 )
1232 }
1148} 1233}
diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs
index 8cb4a51d8..317b6f011 100644
--- a/crates/ide/src/hover.rs
+++ b/crates/ide/src/hover.rs
@@ -1,6 +1,6 @@
1use hir::{ 1use hir::{
2 Adt, AsAssocItem, AssocItemContainer, FieldSource, HasAttrs, HasSource, HirDisplay, Module, 2 Adt, AsAssocItem, AssocItemContainer, FieldSource, GenericParam, HasAttrs, HasSource,
3 ModuleDef, ModuleSource, Semantics, 3 HirDisplay, Module, ModuleDef, ModuleSource, Semantics,
4}; 4};
5use ide_db::base_db::SourceDatabase; 5use ide_db::base_db::SourceDatabase;
6use ide_db::{ 6use ide_db::{
@@ -17,7 +17,7 @@ use crate::{
17 doc_links::{remove_links, rewrite_links}, 17 doc_links::{remove_links, rewrite_links},
18 markdown_remove::remove_markdown, 18 markdown_remove::remove_markdown,
19 markup::Markup, 19 markup::Markup,
20 runnables::{runnable, runnable_fn}, 20 runnables::{runnable_fn, runnable_mod},
21 FileId, FilePosition, NavigationTarget, RangeInfo, Runnable, 21 FileId, FilePosition, NavigationTarget, RangeInfo, Runnable,
22}; 22};
23 23
@@ -175,12 +175,7 @@ fn show_implementations_action(db: &RootDatabase, def: Definition) -> Option<Hov
175 Definition::SelfType(it) => it.target_ty(db).as_adt(), 175 Definition::SelfType(it) => it.target_ty(db).as_adt(),
176 _ => None, 176 _ => None,
177 }?; 177 }?;
178 match adt { 178 adt.try_to_nav(db).map(to_action)
179 Adt::Struct(it) => it.try_to_nav(db),
180 Adt::Union(it) => it.try_to_nav(db),
181 Adt::Enum(it) => it.try_to_nav(db),
182 }
183 .map(to_action)
184} 179}
185 180
186fn runnable_action( 181fn runnable_action(
@@ -192,7 +187,7 @@ fn runnable_action(
192 Definition::ModuleDef(it) => match it { 187 Definition::ModuleDef(it) => match it {
193 ModuleDef::Module(it) => match it.definition_source(sema.db).value { 188 ModuleDef::Module(it) => match it.definition_source(sema.db).value {
194 ModuleSource::Module(it) => { 189 ModuleSource::Module(it) => {
195 runnable(&sema, it.syntax().clone()).map(|it| HoverAction::Runnable(it)) 190 runnable_mod(&sema, it).map(|it| HoverAction::Runnable(it))
196 } 191 }
197 _ => None, 192 _ => None,
198 }, 193 },
@@ -220,12 +215,12 @@ fn goto_type_action(db: &RootDatabase, def: Definition) -> Option<HoverAction> {
220 } 215 }
221 }; 216 };
222 217
223 if let Definition::TypeParam(it) = def { 218 if let Definition::GenericParam(GenericParam::TypeParam(it)) = def {
224 it.trait_bounds(db).into_iter().for_each(|it| push_new_def(it.into())); 219 it.trait_bounds(db).into_iter().for_each(|it| push_new_def(it.into()));
225 } else { 220 } else {
226 let ty = match def { 221 let ty = match def {
227 Definition::Local(it) => it.ty(db), 222 Definition::Local(it) => it.ty(db),
228 Definition::ConstParam(it) => it.ty(db), 223 Definition::GenericParam(GenericParam::ConstParam(it)) => it.ty(db),
229 _ => return None, 224 _ => return None,
230 }; 225 };
231 226
@@ -357,9 +352,11 @@ fn hover_for_definition(db: &RootDatabase, def: Definition) -> Option<Markup> {
357 }) 352 })
358 } 353 }
359 Definition::Label(it) => Some(Markup::fenced_block(&it.name(db))), 354 Definition::Label(it) => Some(Markup::fenced_block(&it.name(db))),
360 Definition::LifetimeParam(it) => Some(Markup::fenced_block(&it.name(db))), 355 Definition::GenericParam(it) => match it {
361 Definition::TypeParam(type_param) => Some(Markup::fenced_block(&type_param.display(db))), 356 GenericParam::TypeParam(it) => Some(Markup::fenced_block(&it.display(db))),
362 Definition::ConstParam(it) => from_def_source(db, it, None), 357 GenericParam::LifetimeParam(it) => Some(Markup::fenced_block(&it.name(db))),
358 GenericParam::ConstParam(it) => from_def_source(db, it, None),
359 },
363 }; 360 };
364 361
365 fn from_def_source<A, D>(db: &RootDatabase, def: D, mod_path: Option<String>) -> Option<Markup> 362 fn from_def_source<A, D>(db: &RootDatabase, def: D, mod_path: Option<String>) -> Option<Markup>
@@ -1951,16 +1948,16 @@ struct S {
1951/// Test cases: 1948/// Test cases:
1952/// case 1. bare URL: https://www.example.com/ 1949/// case 1. bare URL: https://www.example.com/
1953/// case 2. inline URL with title: [example](https://www.example.com/) 1950/// case 2. inline URL with title: [example](https://www.example.com/)
1954/// case 3. code refrence: [`Result`] 1951/// case 3. code reference: [`Result`]
1955/// case 4. code refrence but miss footnote: [`String`] 1952/// case 4. code reference but miss footnote: [`String`]
1956/// case 5. autolink: <http://www.example.com/> 1953/// case 5. autolink: <http://www.example.com/>
1957/// case 6. email address: <[email protected]> 1954/// case 6. email address: <[email protected]>
1958/// case 7. refrence: [example][example] 1955/// case 7. reference: [example][example]
1959/// case 8. collapsed link: [example][] 1956/// case 8. collapsed link: [example][]
1960/// case 9. shortcut link: [example] 1957/// case 9. shortcut link: [example]
1961/// case 10. inline without URL: [example]() 1958/// case 10. inline without URL: [example]()
1962/// case 11. refrence: [foo][foo] 1959/// case 11. reference: [foo][foo]
1963/// case 12. refrence: [foo][bar] 1960/// case 12. reference: [foo][bar]
1964/// case 13. collapsed link: [foo][] 1961/// case 13. collapsed link: [foo][]
1965/// case 14. shortcut link: [foo] 1962/// case 14. shortcut link: [foo]
1966/// case 15. inline without URL: [foo]() 1963/// case 15. inline without URL: [foo]()
@@ -1987,16 +1984,16 @@ pub fn fo$0o() {}
1987 Test cases: 1984 Test cases:
1988 case 1. bare URL: https://www.example.com/ 1985 case 1. bare URL: https://www.example.com/
1989 case 2. inline URL with title: [example](https://www.example.com/) 1986 case 2. inline URL with title: [example](https://www.example.com/)
1990 case 3. code refrence: `Result` 1987 case 3. code reference: `Result`
1991 case 4. code refrence but miss footnote: `String` 1988 case 4. code reference but miss footnote: `String`
1992 case 5. autolink: http://www.example.com/ 1989 case 5. autolink: http://www.example.com/
1993 case 6. email address: [email protected] 1990 case 6. email address: [email protected]
1994 case 7. refrence: example 1991 case 7. reference: example
1995 case 8. collapsed link: example 1992 case 8. collapsed link: example
1996 case 9. shortcut link: example 1993 case 9. shortcut link: example
1997 case 10. inline without URL: example 1994 case 10. inline without URL: example
1998 case 11. refrence: foo 1995 case 11. reference: foo
1999 case 12. refrence: foo 1996 case 12. reference: foo
2000 case 13. collapsed link: foo 1997 case 13. collapsed link: foo
2001 case 14. shortcut link: foo 1998 case 14. shortcut link: foo
2002 case 15. inline without URL: foo 1999 case 15. inline without URL: foo
diff --git a/crates/ide/src/inlay_hints.rs b/crates/ide/src/inlay_hints.rs
index fe60abfc8..3e9a65d9c 100644
--- a/crates/ide/src/inlay_hints.rs
+++ b/crates/ide/src/inlay_hints.rs
@@ -353,9 +353,25 @@ fn is_argument_similar_to_param_name(
353 } 353 }
354 match get_string_representation(argument) { 354 match get_string_representation(argument) {
355 None => false, 355 None => false,
356 Some(repr) => { 356 Some(argument_string) => {
357 let argument_string = repr.trim_start_matches('_'); 357 let num_leading_underscores =
358 argument_string.starts_with(param_name) || argument_string.ends_with(param_name) 358 argument_string.bytes().take_while(|&c| c == b'_').count();
359
360 // Does the argument name begin with the parameter name? Ignore leading underscores.
361 let mut arg_bytes = argument_string.bytes().skip(num_leading_underscores);
362 let starts_with_pattern = param_name.bytes().all(
363 |expected| matches!(arg_bytes.next(), Some(actual) if expected.eq_ignore_ascii_case(&actual)),
364 );
365
366 if starts_with_pattern {
367 return true;
368 }
369
370 // Does the argument name end with the parameter name?
371 let mut arg_bytes = argument_string.bytes().skip(num_leading_underscores);
372 param_name.bytes().rev().all(
373 |expected| matches!(arg_bytes.next_back(), Some(actual) if expected.eq_ignore_ascii_case(&actual)),
374 )
359 } 375 }
360 } 376 }
361} 377}
@@ -901,6 +917,9 @@ fn main() {
901 twiddle(true); 917 twiddle(true);
902 doo(true); 918 doo(true);
903 919
920 const TWIDDLE_UPPERCASE: bool = true;
921 twiddle(TWIDDLE_UPPERCASE);
922
904 let mut param_begin: Param = Param {}; 923 let mut param_begin: Param = Param {};
905 different_order(&param_begin); 924 different_order(&param_begin);
906 different_order(&mut param_begin); 925 different_order(&mut param_begin);
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs
index cea2a13c8..1f368cbd0 100644
--- a/crates/ide/src/lib.rs
+++ b/crates/ide/src/lib.rs
@@ -76,8 +76,8 @@ pub use crate::{
76 references::{rename::RenameError, Declaration, ReferenceSearchResult}, 76 references::{rename::RenameError, Declaration, ReferenceSearchResult},
77 runnables::{Runnable, RunnableKind, TestId}, 77 runnables::{Runnable, RunnableKind, TestId},
78 syntax_highlighting::{ 78 syntax_highlighting::{
79 tags::{Highlight, HighlightModifier, HighlightModifiers, HighlightTag}, 79 tags::{Highlight, HlMod, HlMods, HlPunct, HlTag},
80 HighlightedRange, 80 HlRange,
81 }, 81 },
82}; 82};
83pub use assists::{Assist, AssistConfig, AssistId, AssistKind, InsertUseConfig}; 83pub use assists::{Assist, AssistConfig, AssistId, AssistKind, InsertUseConfig};
@@ -449,12 +449,12 @@ impl Analysis {
449 } 449 }
450 450
451 /// Computes syntax highlighting for the given file 451 /// Computes syntax highlighting for the given file
452 pub fn highlight(&self, file_id: FileId) -> Cancelable<Vec<HighlightedRange>> { 452 pub fn highlight(&self, file_id: FileId) -> Cancelable<Vec<HlRange>> {
453 self.with_db(|db| syntax_highlighting::highlight(db, file_id, None, false)) 453 self.with_db(|db| syntax_highlighting::highlight(db, file_id, None, false))
454 } 454 }
455 455
456 /// Computes syntax highlighting for the given file range. 456 /// Computes syntax highlighting for the given file range.
457 pub fn highlight_range(&self, frange: FileRange) -> Cancelable<Vec<HighlightedRange>> { 457 pub fn highlight_range(&self, frange: FileRange) -> Cancelable<Vec<HlRange>> {
458 self.with_db(|db| { 458 self.with_db(|db| {
459 syntax_highlighting::highlight(db, frange.file_id, Some(frange.range), false) 459 syntax_highlighting::highlight(db, frange.file_id, Some(frange.range), false)
460 }) 460 })
diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs
index c95ed669c..b774a2be1 100644
--- a/crates/ide/src/references.rs
+++ b/crates/ide/src/references.rs
@@ -3,7 +3,7 @@
3//! or `ast::NameRef`. If it's a `ast::NameRef`, at the classification step we 3//! or `ast::NameRef`. If it's a `ast::NameRef`, at the classification step we
4//! try to resolve the direct tree parent of this element, otherwise we 4//! try to resolve the direct tree parent of this element, otherwise we
5//! already have a definition and just need to get its HIR together with 5//! already have a definition and just need to get its HIR together with
6//! some information that is needed for futher steps of searching. 6//! some information that is needed for further steps of searching.
7//! After that, we collect files that might contain references and look 7//! After that, we collect files that might contain references and look
8//! for text occurrences of the identifier. If there's an `ast::NameRef` 8//! for text occurrences of the identifier. If there's an `ast::NameRef`
9//! at the index that the match starts at and its tree parent is 9//! at the index that the match starts at and its tree parent is
@@ -21,7 +21,7 @@ use ide_db::{
21use syntax::{ 21use syntax::{
22 algo::find_node_at_offset, 22 algo::find_node_at_offset,
23 ast::{self, NameOwner}, 23 ast::{self, NameOwner},
24 match_ast, AstNode, SyntaxKind, SyntaxNode, TextRange, TokenAtOffset, 24 match_ast, AstNode, SyntaxNode, TextRange, TokenAtOffset, T,
25}; 25};
26 26
27use crate::{display::TryToNav, FilePosition, FileRange, NavigationTarget, RangeInfo, SymbolKind}; 27use crate::{display::TryToNav, FilePosition, FileRange, NavigationTarget, RangeInfo, SymbolKind};
@@ -130,7 +130,10 @@ pub(crate) fn find_all_refs(
130 kind = ReferenceKind::FieldShorthandForLocal; 130 kind = ReferenceKind::FieldShorthandForLocal;
131 } 131 }
132 } 132 }
133 } else if matches!(def, Definition::LifetimeParam(_) | Definition::Label(_)) { 133 } else if matches!(
134 def,
135 Definition::GenericParam(hir::GenericParam::LifetimeParam(_)) | Definition::Label(_)
136 ) {
134 kind = ReferenceKind::Lifetime; 137 kind = ReferenceKind::Lifetime;
135 }; 138 };
136 139
@@ -200,7 +203,7 @@ fn get_struct_def_name_for_struct_literal_search(
200 position: FilePosition, 203 position: FilePosition,
201) -> Option<ast::Name> { 204) -> Option<ast::Name> {
202 if let TokenAtOffset::Between(ref left, ref right) = syntax.token_at_offset(position.offset) { 205 if let TokenAtOffset::Between(ref left, ref right) = syntax.token_at_offset(position.offset) {
203 if right.kind() != SyntaxKind::L_CURLY && right.kind() != SyntaxKind::L_PAREN { 206 if right.kind() != T!['{'] && right.kind() != T!['('] {
204 return None; 207 return None;
205 } 208 }
206 if let Some(name) = 209 if let Some(name) =
@@ -227,7 +230,7 @@ fn get_enum_def_name_for_struct_literal_search(
227 position: FilePosition, 230 position: FilePosition,
228) -> Option<ast::Name> { 231) -> Option<ast::Name> {
229 if let TokenAtOffset::Between(ref left, ref right) = syntax.token_at_offset(position.offset) { 232 if let TokenAtOffset::Between(ref left, ref right) = syntax.token_at_offset(position.offset) {
230 if right.kind() != SyntaxKind::L_CURLY && right.kind() != SyntaxKind::L_PAREN { 233 if right.kind() != T!['{'] && right.kind() != T!['('] {
231 return None; 234 return None;
232 } 235 }
233 if let Some(name) = 236 if let Some(name) =
@@ -252,8 +255,7 @@ fn try_find_self_references(
252 syntax: &SyntaxNode, 255 syntax: &SyntaxNode,
253 position: FilePosition, 256 position: FilePosition,
254) -> Option<RangeInfo<ReferenceSearchResult>> { 257) -> Option<RangeInfo<ReferenceSearchResult>> {
255 let self_token = 258 let self_token = syntax.token_at_offset(position.offset).find(|t| t.kind() == T![self])?;
256 syntax.token_at_offset(position.offset).find(|t| t.kind() == SyntaxKind::SELF_KW)?;
257 let parent = self_token.parent(); 259 let parent = self_token.parent();
258 match_ast! { 260 match_ast! {
259 match parent { 261 match parent {
diff --git a/crates/ide/src/references/rename.rs b/crates/ide/src/references/rename.rs
index 53d79333c..3edc43e08 100644
--- a/crates/ide/src/references/rename.rs
+++ b/crates/ide/src/references/rename.rs
@@ -14,16 +14,17 @@ use ide_db::{
14use syntax::{ 14use syntax::{
15 algo::find_node_at_offset, 15 algo::find_node_at_offset,
16 ast::{self, NameOwner}, 16 ast::{self, NameOwner},
17 lex_single_syntax_kind, match_ast, AstNode, SyntaxKind, SyntaxNode, SyntaxToken, 17 lex_single_syntax_kind, match_ast, AstNode, SyntaxKind, SyntaxNode, SyntaxToken, T,
18}; 18};
19use test_utils::mark; 19use test_utils::mark;
20use text_edit::TextEdit; 20use text_edit::TextEdit;
21 21
22use crate::{ 22use crate::{
23 references::find_all_refs, FilePosition, FileSystemEdit, RangeInfo, Reference, ReferenceKind, 23 FilePosition, FileSystemEdit, RangeInfo, Reference, ReferenceKind, ReferenceSearchResult,
24 SourceChange, SourceFileEdit, TextRange, TextSize, 24 SourceChange, SourceFileEdit, TextRange, TextSize,
25}; 25};
26 26
27type RenameResult<T> = Result<T, RenameError>;
27#[derive(Debug)] 28#[derive(Debug)]
28pub struct RenameError(pub(crate) String); 29pub struct RenameError(pub(crate) String);
29 30
@@ -35,24 +36,30 @@ impl fmt::Display for RenameError {
35 36
36impl Error for RenameError {} 37impl Error for RenameError {}
37 38
39macro_rules! format_err {
40 ($fmt:expr) => {RenameError(format!($fmt))};
41 ($fmt:expr, $($arg:tt)+) => {RenameError(format!($fmt, $($arg)+))}
42}
43
44macro_rules! bail {
45 ($($tokens:tt)*) => {return Err(format_err!($($tokens)*))}
46}
47
38pub(crate) fn prepare_rename( 48pub(crate) fn prepare_rename(
39 db: &RootDatabase, 49 db: &RootDatabase,
40 position: FilePosition, 50 position: FilePosition,
41) -> Result<RangeInfo<()>, RenameError> { 51) -> RenameResult<RangeInfo<()>> {
42 let sema = Semantics::new(db); 52 let sema = Semantics::new(db);
43 let source_file = sema.parse(position.file_id); 53 let source_file = sema.parse(position.file_id);
44 let syntax = source_file.syntax(); 54 let syntax = source_file.syntax();
45 if let Some(module) = find_module_at_offset(&sema, position, syntax) { 55 if let Some(module) = find_module_at_offset(&sema, position, syntax) {
46 rename_mod(&sema, position, module, "dummy") 56 rename_mod(&sema, position, module, "dummy")
47 } else if let Some(self_token) = 57 } else if let Some(self_token) =
48 syntax.token_at_offset(position.offset).find(|t| t.kind() == SyntaxKind::SELF_KW) 58 syntax.token_at_offset(position.offset).find(|t| t.kind() == T![self])
49 { 59 {
50 rename_self_to_param(&sema, position, self_token, "dummy") 60 rename_self_to_param(&sema, position, self_token, "dummy")
51 } else { 61 } else {
52 let range = match find_all_refs(&sema, position, None) { 62 let RangeInfo { range, .. } = find_all_refs(&sema, position)?;
53 Some(RangeInfo { range, .. }) => range,
54 None => return Err(RenameError("No references found at position".to_string())),
55 };
56 Ok(RangeInfo::new(range, SourceChange::from(vec![]))) 63 Ok(RangeInfo::new(range, SourceChange::from(vec![])))
57 } 64 }
58 .map(|info| RangeInfo::new(info.range, ())) 65 .map(|info| RangeInfo::new(info.range, ()))
@@ -62,7 +69,7 @@ pub(crate) fn rename(
62 db: &RootDatabase, 69 db: &RootDatabase,
63 position: FilePosition, 70 position: FilePosition,
64 new_name: &str, 71 new_name: &str,
65) -> Result<RangeInfo<SourceChange>, RenameError> { 72) -> RenameResult<RangeInfo<SourceChange>> {
66 let sema = Semantics::new(db); 73 let sema = Semantics::new(db);
67 rename_with_semantics(&sema, position, new_name) 74 rename_with_semantics(&sema, position, new_name)
68} 75}
@@ -71,42 +78,18 @@ pub(crate) fn rename_with_semantics(
71 sema: &Semantics<RootDatabase>, 78 sema: &Semantics<RootDatabase>,
72 position: FilePosition, 79 position: FilePosition,
73 new_name: &str, 80 new_name: &str,
74) -> Result<RangeInfo<SourceChange>, RenameError> { 81) -> RenameResult<RangeInfo<SourceChange>> {
75 let is_lifetime_name = match lex_single_syntax_kind(new_name) {
76 Some(res) => match res {
77 (SyntaxKind::IDENT, _) => false,
78 (SyntaxKind::UNDERSCORE, _) => false,
79 (SyntaxKind::SELF_KW, _) => return rename_to_self(&sema, position),
80 (SyntaxKind::LIFETIME_IDENT, _) if new_name != "'static" && new_name != "'_" => true,
81 (SyntaxKind::LIFETIME_IDENT, _) => {
82 return Err(RenameError(format!(
83 "Invalid name `{0}`: Cannot rename lifetime to {0}",
84 new_name
85 )))
86 }
87 (_, Some(syntax_error)) => {
88 return Err(RenameError(format!("Invalid name `{}`: {}", new_name, syntax_error)))
89 }
90 (_, None) => {
91 return Err(RenameError(format!("Invalid name `{}`: not an identifier", new_name)))
92 }
93 },
94 None => return Err(RenameError(format!("Invalid name `{}`: not an identifier", new_name))),
95 };
96
97 let source_file = sema.parse(position.file_id); 82 let source_file = sema.parse(position.file_id);
98 let syntax = source_file.syntax(); 83 let syntax = source_file.syntax();
99 // this is here to prevent lifetime renames from happening on modules and self 84
100 if is_lifetime_name { 85 if let Some(module) = find_module_at_offset(&sema, position, syntax) {
101 rename_reference(&sema, position, new_name, is_lifetime_name)
102 } else if let Some(module) = find_module_at_offset(&sema, position, syntax) {
103 rename_mod(&sema, position, module, new_name) 86 rename_mod(&sema, position, module, new_name)
104 } else if let Some(self_token) = 87 } else if let Some(self_token) =
105 syntax.token_at_offset(position.offset).find(|t| t.kind() == SyntaxKind::SELF_KW) 88 syntax.token_at_offset(position.offset).find(|t| t.kind() == T![self])
106 { 89 {
107 rename_self_to_param(&sema, position, self_token, new_name) 90 rename_self_to_param(&sema, position, self_token, new_name)
108 } else { 91 } else {
109 rename_reference(&sema, position, new_name, is_lifetime_name) 92 rename_reference(&sema, position, new_name)
110 } 93 }
111} 94}
112 95
@@ -127,6 +110,33 @@ pub(crate) fn will_rename_file(
127 Some(change) 110 Some(change)
128} 111}
129 112
113#[derive(Debug, PartialEq)]
114enum IdentifierKind {
115 Ident,
116 Lifetime,
117 ToSelf,
118 Underscore,
119}
120
121fn check_identifier(new_name: &str) -> RenameResult<IdentifierKind> {
122 match lex_single_syntax_kind(new_name) {
123 Some(res) => match res {
124 (SyntaxKind::IDENT, _) => Ok(IdentifierKind::Ident),
125 (T![_], _) => Ok(IdentifierKind::Underscore),
126 (T![self], _) => Ok(IdentifierKind::ToSelf),
127 (SyntaxKind::LIFETIME_IDENT, _) if new_name != "'static" && new_name != "'_" => {
128 Ok(IdentifierKind::Lifetime)
129 }
130 (SyntaxKind::LIFETIME_IDENT, _) => {
131 bail!("Invalid name `{0}`: Cannot rename lifetime to {0}", new_name)
132 }
133 (_, Some(syntax_error)) => bail!("Invalid name `{}`: {}", new_name, syntax_error),
134 (_, None) => bail!("Invalid name `{}`: not an identifier", new_name),
135 },
136 None => bail!("Invalid name `{}`: not an identifier", new_name),
137 }
138}
139
130fn find_module_at_offset( 140fn find_module_at_offset(
131 sema: &Semantics<RootDatabase>, 141 sema: &Semantics<RootDatabase>,
132 position: FilePosition, 142 position: FilePosition,
@@ -155,6 +165,14 @@ fn find_module_at_offset(
155 Some(module) 165 Some(module)
156} 166}
157 167
168fn find_all_refs(
169 sema: &Semantics<RootDatabase>,
170 position: FilePosition,
171) -> RenameResult<RangeInfo<ReferenceSearchResult>> {
172 crate::references::find_all_refs(sema, position, None)
173 .ok_or_else(|| format_err!("No references found at position"))
174}
175
158fn source_edit_from_reference( 176fn source_edit_from_reference(
159 sema: &Semantics<RootDatabase>, 177 sema: &Semantics<RootDatabase>,
160 reference: Reference, 178 reference: Reference,
@@ -223,7 +241,10 @@ fn rename_mod(
223 position: FilePosition, 241 position: FilePosition,
224 module: Module, 242 module: Module,
225 new_name: &str, 243 new_name: &str,
226) -> Result<RangeInfo<SourceChange>, RenameError> { 244) -> RenameResult<RangeInfo<SourceChange>> {
245 if IdentifierKind::Ident != check_identifier(new_name)? {
246 bail!("Invalid name `{0}`: cannot rename module to {0}", new_name);
247 }
227 let mut source_file_edits = Vec::new(); 248 let mut source_file_edits = Vec::new();
228 let mut file_system_edits = Vec::new(); 249 let mut file_system_edits = Vec::new();
229 250
@@ -254,8 +275,7 @@ fn rename_mod(
254 source_file_edits.push(edit); 275 source_file_edits.push(edit);
255 } 276 }
256 277
257 let RangeInfo { range, info: refs } = find_all_refs(sema, position, None) 278 let RangeInfo { range, info: refs } = find_all_refs(sema, position)?;
258 .ok_or_else(|| RenameError("No references found at position".to_string()))?;
259 let ref_edits = refs 279 let ref_edits = refs
260 .references 280 .references
261 .into_iter() 281 .into_iter()
@@ -274,27 +294,26 @@ fn rename_to_self(
274 294
275 let (fn_def, fn_ast) = find_node_at_offset::<ast::Fn>(syn, position.offset) 295 let (fn_def, fn_ast) = find_node_at_offset::<ast::Fn>(syn, position.offset)
276 .and_then(|fn_ast| sema.to_def(&fn_ast).zip(Some(fn_ast))) 296 .and_then(|fn_ast| sema.to_def(&fn_ast).zip(Some(fn_ast)))
277 .ok_or_else(|| RenameError("No surrounding method declaration found".to_string()))?; 297 .ok_or_else(|| format_err!("No surrounding method declaration found"))?;
278 let param_range = fn_ast 298 let param_range = fn_ast
279 .param_list() 299 .param_list()
280 .and_then(|p| p.params().next()) 300 .and_then(|p| p.params().next())
281 .ok_or_else(|| RenameError("Method has no parameters".to_string()))? 301 .ok_or_else(|| format_err!("Method has no parameters"))?
282 .syntax() 302 .syntax()
283 .text_range(); 303 .text_range();
284 if !param_range.contains(position.offset) { 304 if !param_range.contains(position.offset) {
285 return Err(RenameError("Only the first parameter can be self".to_string())); 305 bail!("Only the first parameter can be self");
286 } 306 }
287 307
288 let impl_block = find_node_at_offset::<ast::Impl>(syn, position.offset) 308 let impl_block = find_node_at_offset::<ast::Impl>(syn, position.offset)
289 .and_then(|def| sema.to_def(&def)) 309 .and_then(|def| sema.to_def(&def))
290 .ok_or_else(|| RenameError("No impl block found for function".to_string()))?; 310 .ok_or_else(|| format_err!("No impl block found for function"))?;
291 if fn_def.self_param(sema.db).is_some() { 311 if fn_def.self_param(sema.db).is_some() {
292 return Err(RenameError("Method already has a self parameter".to_string())); 312 bail!("Method already has a self parameter");
293 } 313 }
294 314
295 let params = fn_def.assoc_fn_params(sema.db); 315 let params = fn_def.assoc_fn_params(sema.db);
296 let first_param = 316 let first_param = params.first().ok_or_else(|| format_err!("Method has no parameters"))?;
297 params.first().ok_or_else(|| RenameError("Method has no parameters".into()))?;
298 let first_param_ty = first_param.ty(); 317 let first_param_ty = first_param.ty();
299 let impl_ty = impl_block.target_ty(sema.db); 318 let impl_ty = impl_block.target_ty(sema.db);
300 let (ty, self_param) = if impl_ty.remove_ref().is_some() { 319 let (ty, self_param) = if impl_ty.remove_ref().is_some() {
@@ -307,18 +326,17 @@ fn rename_to_self(
307 }; 326 };
308 327
309 if ty != impl_ty { 328 if ty != impl_ty {
310 return Err(RenameError("Parameter type differs from impl block type".to_string())); 329 bail!("Parameter type differs from impl block type");
311 } 330 }
312 331
313 let RangeInfo { range, info: refs } = find_all_refs(sema, position, None) 332 let RangeInfo { range, info: refs } = find_all_refs(sema, position)?;
314 .ok_or_else(|| RenameError("No reference found at position".to_string()))?;
315 333
316 let (param_ref, usages): (Vec<Reference>, Vec<Reference>) = refs 334 let (param_ref, usages): (Vec<Reference>, Vec<Reference>) = refs
317 .into_iter() 335 .into_iter()
318 .partition(|reference| param_range.intersect(reference.file_range.range).is_some()); 336 .partition(|reference| param_range.intersect(reference.file_range.range).is_some());
319 337
320 if param_ref.is_empty() { 338 if param_ref.is_empty() {
321 return Err(RenameError("Parameter to rename not found".to_string())); 339 bail!("Parameter to rename not found");
322 } 340 }
323 341
324 let mut edits = usages 342 let mut edits = usages
@@ -367,12 +385,22 @@ fn rename_self_to_param(
367 self_token: SyntaxToken, 385 self_token: SyntaxToken,
368 new_name: &str, 386 new_name: &str,
369) -> Result<RangeInfo<SourceChange>, RenameError> { 387) -> Result<RangeInfo<SourceChange>, RenameError> {
388 let ident_kind = check_identifier(new_name)?;
389 match ident_kind {
390 IdentifierKind::Lifetime => bail!("Invalid name `{}`: not an identifier", new_name),
391 IdentifierKind::ToSelf => {
392 // no-op
393 mark::hit!(rename_self_to_self);
394 return Ok(RangeInfo::new(self_token.text_range(), SourceChange::default()));
395 }
396 _ => (),
397 }
370 let source_file = sema.parse(position.file_id); 398 let source_file = sema.parse(position.file_id);
371 let syn = source_file.syntax(); 399 let syn = source_file.syntax();
372 400
373 let text = sema.db.file_text(position.file_id); 401 let text = sema.db.file_text(position.file_id);
374 let fn_def = find_node_at_offset::<ast::Fn>(syn, position.offset) 402 let fn_def = find_node_at_offset::<ast::Fn>(syn, position.offset)
375 .ok_or_else(|| RenameError("No surrounding method declaration found".to_string()))?; 403 .ok_or_else(|| format_err!("No surrounding method declaration found"))?;
376 let search_range = fn_def.syntax().text_range(); 404 let search_range = fn_def.syntax().text_range();
377 405
378 let mut edits: Vec<SourceFileEdit> = vec![]; 406 let mut edits: Vec<SourceFileEdit> = vec![];
@@ -382,12 +410,10 @@ fn rename_self_to_param(
382 if !search_range.contains_inclusive(offset) { 410 if !search_range.contains_inclusive(offset) {
383 continue; 411 continue;
384 } 412 }
385 if let Some(ref usage) = 413 if let Some(ref usage) = syn.token_at_offset(offset).find(|t| t.kind() == T![self]) {
386 syn.token_at_offset(offset).find(|t| t.kind() == SyntaxKind::SELF_KW)
387 {
388 let edit = if let Some(ref self_param) = ast::SelfParam::cast(usage.parent()) { 414 let edit = if let Some(ref self_param) = ast::SelfParam::cast(usage.parent()) {
389 text_edit_from_self_param(syn, self_param, new_name) 415 text_edit_from_self_param(syn, self_param, new_name)
390 .ok_or_else(|| RenameError("No target type found".to_string()))? 416 .ok_or_else(|| format_err!("No target type found"))?
391 } else { 417 } else {
392 TextEdit::replace(usage.text_range(), String::from(new_name)) 418 TextEdit::replace(usage.text_range(), String::from(new_name))
393 }; 419 };
@@ -395,6 +421,10 @@ fn rename_self_to_param(
395 } 421 }
396 } 422 }
397 423
424 if edits.len() > 1 && ident_kind == IdentifierKind::Underscore {
425 bail!("Cannot rename reference to `_` as it is being referenced multiple times");
426 }
427
398 let range = ast::SelfParam::cast(self_token.parent()) 428 let range = ast::SelfParam::cast(self_token.parent())
399 .map_or(self_token.text_range(), |p| p.syntax().text_range()); 429 .map_or(self_token.text_range(), |p| p.syntax().text_range());
400 430
@@ -405,24 +435,34 @@ fn rename_reference(
405 sema: &Semantics<RootDatabase>, 435 sema: &Semantics<RootDatabase>,
406 position: FilePosition, 436 position: FilePosition,
407 new_name: &str, 437 new_name: &str,
408 is_lifetime_name: bool,
409) -> Result<RangeInfo<SourceChange>, RenameError> { 438) -> Result<RangeInfo<SourceChange>, RenameError> {
410 let RangeInfo { range, info: refs } = match find_all_refs(sema, position, None) { 439 let ident_kind = check_identifier(new_name)?;
411 Some(range_info) => range_info, 440 let RangeInfo { range, info: refs } = find_all_refs(sema, position)?;
412 None => return Err(RenameError("No references found at position".to_string())), 441
413 }; 442 match (ident_kind, &refs.declaration.kind) {
414 443 (IdentifierKind::ToSelf, ReferenceKind::Lifetime)
415 match (refs.declaration.kind == ReferenceKind::Lifetime, is_lifetime_name) { 444 | (IdentifierKind::Underscore, ReferenceKind::Lifetime)
416 (true, false) => { 445 | (IdentifierKind::Ident, ReferenceKind::Lifetime) => {
417 return Err(RenameError(format!( 446 mark::hit!(rename_not_a_lifetime_ident_ref);
418 "Invalid name `{}`: not a lifetime identifier", 447 bail!("Invalid name `{}`: not a lifetime identifier", new_name)
419 new_name
420 )))
421 } 448 }
422 (false, true) => { 449 (IdentifierKind::Lifetime, ReferenceKind::Lifetime) => mark::hit!(rename_lifetime),
423 return Err(RenameError(format!("Invalid name `{}`: not an identifier", new_name))) 450 (IdentifierKind::Lifetime, _) => {
451 mark::hit!(rename_not_an_ident_ref);
452 bail!("Invalid name `{}`: not an identifier", new_name)
424 } 453 }
425 _ => (), 454 (IdentifierKind::ToSelf, ReferenceKind::SelfKw) => {
455 unreachable!("rename_self_to_param should've been called instead")
456 }
457 (IdentifierKind::ToSelf, _) => {
458 mark::hit!(rename_to_self);
459 return rename_to_self(sema, position);
460 }
461 (IdentifierKind::Underscore, _) if !refs.references.is_empty() => {
462 mark::hit!(rename_underscore_multiple);
463 bail!("Cannot rename reference to `_` as it is being referenced multiple times")
464 }
465 (IdentifierKind::Ident, _) | (IdentifierKind::Underscore, _) => mark::hit!(rename_ident),
426 } 466 }
427 467
428 let edit = refs 468 let edit = refs
@@ -430,10 +470,6 @@ fn rename_reference(
430 .map(|reference| source_edit_from_reference(sema, reference, new_name)) 470 .map(|reference| source_edit_from_reference(sema, reference, new_name))
431 .collect::<Vec<_>>(); 471 .collect::<Vec<_>>();
432 472
433 if edit.is_empty() {
434 return Err(RenameError("No references found at position".to_string()));
435 }
436
437 Ok(RangeInfo::new(range, SourceChange::from(edit))) 473 Ok(RangeInfo::new(range, SourceChange::from(edit)))
438} 474}
439 475
@@ -462,9 +498,11 @@ mod tests {
462 text_edit_builder.replace(indel.delete, indel.insert); 498 text_edit_builder.replace(indel.delete, indel.insert);
463 } 499 }
464 } 500 }
465 let mut result = analysis.file_text(file_id.unwrap()).unwrap().to_string(); 501 if let Some(file_id) = file_id {
466 text_edit_builder.finish().apply(&mut result); 502 let mut result = analysis.file_text(file_id).unwrap().to_string();
467 assert_eq_text!(ra_fixture_after, &*result); 503 text_edit_builder.finish().apply(&mut result);
504 assert_eq_text!(ra_fixture_after, &*result);
505 }
468 } 506 }
469 Err(err) => { 507 Err(err) => {
470 if ra_fixture_after.starts_with("error:") { 508 if ra_fixture_after.starts_with("error:") {
@@ -530,6 +568,7 @@ mod tests {
530 568
531 #[test] 569 #[test]
532 fn test_rename_to_invalid_identifier_lifetime() { 570 fn test_rename_to_invalid_identifier_lifetime() {
571 mark::check!(rename_not_an_ident_ref);
533 check( 572 check(
534 "'foo", 573 "'foo",
535 r#"fn main() { let i$0 = 1; }"#, 574 r#"fn main() { let i$0 = 1; }"#,
@@ -539,6 +578,7 @@ mod tests {
539 578
540 #[test] 579 #[test]
541 fn test_rename_to_invalid_identifier_lifetime2() { 580 fn test_rename_to_invalid_identifier_lifetime2() {
581 mark::check!(rename_not_a_lifetime_ident_ref);
542 check( 582 check(
543 "foo", 583 "foo",
544 r#"fn main<'a>(_: &'a$0 ()) {}"#, 584 r#"fn main<'a>(_: &'a$0 ()) {}"#,
@@ -547,7 +587,27 @@ mod tests {
547 } 587 }
548 588
549 #[test] 589 #[test]
590 fn test_rename_to_underscore_invalid() {
591 mark::check!(rename_underscore_multiple);
592 check(
593 "_",
594 r#"fn main(foo$0: ()) {foo;}"#,
595 "error: Cannot rename reference to `_` as it is being referenced multiple times",
596 );
597 }
598
599 #[test]
600 fn test_rename_mod_invalid() {
601 check(
602 "'foo",
603 r#"mod foo$0 {}"#,
604 "error: Invalid name `'foo`: cannot rename module to 'foo",
605 );
606 }
607
608 #[test]
550 fn test_rename_for_local() { 609 fn test_rename_for_local() {
610 mark::check!(rename_ident);
551 check( 611 check(
552 "k", 612 "k",
553 r#" 613 r#"
@@ -945,7 +1005,7 @@ use crate::foo$0::FooContent;
945//- /lib.rs 1005//- /lib.rs
946mod fo$0o; 1006mod fo$0o;
947//- /foo/mod.rs 1007//- /foo/mod.rs
948// emtpy 1008// empty
949"#, 1009"#,
950 expect![[r#" 1010 expect![[r#"
951 RangeInfo { 1011 RangeInfo {
@@ -995,7 +1055,7 @@ mod fo$0o;
995mod outer { mod fo$0o; } 1055mod outer { mod fo$0o; }
996 1056
997//- /outer/foo.rs 1057//- /outer/foo.rs
998// emtpy 1058// empty
999"#, 1059"#,
1000 expect![[r#" 1060 expect![[r#"
1001 RangeInfo { 1061 RangeInfo {
@@ -1178,6 +1238,7 @@ fn foo(f: foo::Foo) {
1178 1238
1179 #[test] 1239 #[test]
1180 fn test_parameter_to_self() { 1240 fn test_parameter_to_self() {
1241 mark::check!(rename_to_self);
1181 check( 1242 check(
1182 "self", 1243 "self",
1183 r#" 1244 r#"
@@ -1481,6 +1542,7 @@ fn foo(Foo { i: bar }: foo) -> i32 {
1481 1542
1482 #[test] 1543 #[test]
1483 fn test_rename_lifetimes() { 1544 fn test_rename_lifetimes() {
1545 mark::check!(rename_lifetime);
1484 check( 1546 check(
1485 "'yeeee", 1547 "'yeeee",
1486 r#" 1548 r#"
@@ -1565,4 +1627,24 @@ fn foo<'a>() -> &'a () {
1565"#, 1627"#,
1566 ) 1628 )
1567 } 1629 }
1630
1631 #[test]
1632 fn test_self_to_self() {
1633 mark::check!(rename_self_to_self);
1634 check(
1635 "self",
1636 r#"
1637struct Foo;
1638impl Foo {
1639 fn foo(self$0) {}
1640}
1641"#,
1642 r#"
1643struct Foo;
1644impl Foo {
1645 fn foo(self) {}
1646}
1647"#,
1648 )
1649 }
1568} 1650}
diff --git a/crates/ide/src/runnables.rs b/crates/ide/src/runnables.rs
index 557563d7e..f5ee7de86 100644
--- a/crates/ide/src/runnables.rs
+++ b/crates/ide/src/runnables.rs
@@ -3,7 +3,7 @@ use std::fmt;
3use assists::utils::test_related_attribute; 3use assists::utils::test_related_attribute;
4use cfg::CfgExpr; 4use cfg::CfgExpr;
5use hir::{AsAssocItem, HasAttrs, HasSource, Semantics}; 5use hir::{AsAssocItem, HasAttrs, HasSource, Semantics};
6use ide_db::RootDatabase; 6use ide_db::{defs::Definition, RootDatabase};
7use itertools::Itertools; 7use itertools::Itertools;
8use syntax::{ 8use syntax::{
9 ast::{self, AstNode, AttrsOwner, ModuleItemOwner}, 9 ast::{self, AstNode, AttrsOwner, ModuleItemOwner},
@@ -96,21 +96,26 @@ impl Runnable {
96pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> { 96pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> {
97 let sema = Semantics::new(db); 97 let sema = Semantics::new(db);
98 let source_file = sema.parse(file_id); 98 let source_file = sema.parse(file_id);
99 source_file.syntax().descendants().filter_map(|i| runnable(&sema, i)).collect() 99 source_file
100} 100 .syntax()
101 101 .descendants()
102pub(crate) fn runnable(sema: &Semantics<RootDatabase>, item: SyntaxNode) -> Option<Runnable> { 102 .filter_map(|item| {
103 let runnable_item = match_ast! { 103 let runnable = match_ast! {
104 match (item.clone()) { 104 match item {
105 ast::Fn(func) => { 105 ast::Fn(func) => {
106 let def = sema.to_def(&func)?; 106 let def = sema.to_def(&func)?;
107 runnable_fn(sema, def) 107 runnable_fn(&sema, def)
108 }, 108 },
109 ast::Module(it) => runnable_mod(sema, it), 109 ast::Module(it) => runnable_mod(&sema, it),
110 _ => None, 110 _ => None,
111 } 111 }
112 }; 112 };
113 runnable_item.or_else(|| runnable_doctest(sema, item)) 113 runnable.or_else(|| match doc_owner_to_def(&sema, item)? {
114 Definition::ModuleDef(def) => module_def_doctest(&sema, def),
115 _ => None,
116 })
117 })
118 .collect()
114} 119}
115 120
116pub(crate) fn runnable_fn(sema: &Semantics<RootDatabase>, def: hir::Function) -> Option<Runnable> { 121pub(crate) fn runnable_fn(sema: &Semantics<RootDatabase>, def: hir::Function) -> Option<Runnable> {
@@ -145,20 +150,49 @@ pub(crate) fn runnable_fn(sema: &Semantics<RootDatabase>, def: hir::Function) ->
145 Some(Runnable { nav, kind, cfg }) 150 Some(Runnable { nav, kind, cfg })
146} 151}
147 152
148fn runnable_doctest(sema: &Semantics<RootDatabase>, item: SyntaxNode) -> Option<Runnable> { 153pub(crate) fn runnable_mod(
149 match_ast! { 154 sema: &Semantics<RootDatabase>,
155 module: ast::Module,
156) -> Option<Runnable> {
157 if !has_test_function_or_multiple_test_submodules(&module) {
158 return None;
159 }
160 let module_def = sema.to_def(&module)?;
161
162 let path = module_def
163 .path_to_root(sema.db)
164 .into_iter()
165 .rev()
166 .filter_map(|it| it.name(sema.db))
167 .join("::");
168
169 let def = sema.to_def(&module)?;
170 let attrs = def.attrs(sema.db);
171 let cfg = attrs.cfg();
172 let nav = module_def.to_nav(sema.db);
173 Some(Runnable { nav, kind: RunnableKind::TestMod { path }, cfg })
174}
175
176// FIXME: figure out a proper API here.
177pub(crate) fn doc_owner_to_def(
178 sema: &Semantics<RootDatabase>,
179 item: SyntaxNode,
180) -> Option<Definition> {
181 let res: hir::ModuleDef = match_ast! {
150 match item { 182 match item {
151 ast::Fn(it) => module_def_doctest(sema, sema.to_def(&it)?.into()), 183 ast::SourceFile(it) => sema.scope(&item).module()?.into(),
152 ast::Struct(it) => module_def_doctest(sema, sema.to_def(&it)?.into()), 184 ast::Fn(it) => sema.to_def(&it)?.into(),
153 ast::Enum(it) => module_def_doctest(sema, sema.to_def(&it)?.into()), 185 ast::Struct(it) => sema.to_def(&it)?.into(),
154 ast::Union(it) => module_def_doctest(sema, sema.to_def(&it)?.into()), 186 ast::Enum(it) => sema.to_def(&it)?.into(),
155 ast::Trait(it) => module_def_doctest(sema, sema.to_def(&it)?.into()), 187 ast::Union(it) => sema.to_def(&it)?.into(),
156 ast::Const(it) => module_def_doctest(sema, sema.to_def(&it)?.into()), 188 ast::Trait(it) => sema.to_def(&it)?.into(),
157 ast::Static(it) => module_def_doctest(sema, sema.to_def(&it)?.into()), 189 ast::Const(it) => sema.to_def(&it)?.into(),
158 ast::TypeAlias(it) => module_def_doctest(sema, sema.to_def(&it)?.into()), 190 ast::Static(it) => sema.to_def(&it)?.into(),
159 _ => None, 191 ast::TypeAlias(it) => sema.to_def(&it)?.into(),
192 _ => return None,
160 } 193 }
161 } 194 };
195 Some(Definition::ModuleDef(res))
162} 196}
163 197
164fn module_def_doctest(sema: &Semantics<RootDatabase>, def: hir::ModuleDef) -> Option<Runnable> { 198fn module_def_doctest(sema: &Semantics<RootDatabase>, def: hir::ModuleDef) -> Option<Runnable> {
@@ -253,26 +287,6 @@ fn has_runnable_doc_test(attrs: &hir::Attrs) -> bool {
253 }) 287 })
254} 288}
255 289
256fn runnable_mod(sema: &Semantics<RootDatabase>, module: ast::Module) -> Option<Runnable> {
257 if !has_test_function_or_multiple_test_submodules(&module) {
258 return None;
259 }
260 let module_def = sema.to_def(&module)?;
261
262 let path = module_def
263 .path_to_root(sema.db)
264 .into_iter()
265 .rev()
266 .filter_map(|it| it.name(sema.db))
267 .join("::");
268
269 let def = sema.to_def(&module)?;
270 let attrs = def.attrs(sema.db);
271 let cfg = attrs.cfg();
272 let nav = module_def.to_nav(sema.db);
273 Some(Runnable { nav, kind: RunnableKind::TestMod { path }, cfg })
274}
275
276// We could create runnables for modules with number_of_test_submodules > 0, 290// We could create runnables for modules with number_of_test_submodules > 0,
277// but that bloats the runnables for no real benefit, since all tests can be run by the submodule already 291// but that bloats the runnables for no real benefit, since all tests can be run by the submodule already
278fn has_test_function_or_multiple_test_submodules(module: &ast::Module) -> bool { 292fn has_test_function_or_multiple_test_submodules(module: &ast::Module) -> bool {
diff --git a/crates/ide/src/syntax_highlighting.rs b/crates/ide/src/syntax_highlighting.rs
index ba0085244..f2d4da78d 100644
--- a/crates/ide/src/syntax_highlighting.rs
+++ b/crates/ide/src/syntax_highlighting.rs
@@ -1,35 +1,39 @@
1pub(crate) mod tags;
2
3mod highlights;
4mod injector;
5
6mod highlight;
1mod format; 7mod format;
2mod html;
3mod injection;
4mod macro_rules; 8mod macro_rules;
5pub(crate) mod tags; 9mod inject;
10
11mod html;
6#[cfg(test)] 12#[cfg(test)]
7mod tests; 13mod tests;
8 14
9use hir::{AsAssocItem, Local, Name, Semantics, VariantDef}; 15use hir::{Name, Semantics};
10use ide_db::{ 16use ide_db::RootDatabase;
11 defs::{Definition, NameClass, NameRefClass},
12 RootDatabase,
13};
14use rustc_hash::FxHashMap; 17use rustc_hash::FxHashMap;
15use syntax::{ 18use syntax::{
16 ast::{self, HasFormatSpecifier}, 19 ast::{self, HasFormatSpecifier},
17 AstNode, AstToken, Direction, NodeOrToken, SyntaxElement, 20 AstNode, AstToken, Direction, NodeOrToken,
18 SyntaxKind::{self, *}, 21 SyntaxKind::*,
19 SyntaxNode, SyntaxToken, TextRange, WalkEvent, T, 22 SyntaxNode, TextRange, WalkEvent, T,
20}; 23};
21 24
22use crate::{ 25use crate::{
23 syntax_highlighting::{ 26 syntax_highlighting::{
24 format::FormatStringHighlighter, macro_rules::MacroRulesHighlighter, tags::Highlight, 27 format::highlight_format_string, highlights::Highlights,
28 macro_rules::MacroRulesHighlighter, tags::Highlight,
25 }, 29 },
26 FileId, HighlightModifier, HighlightTag, SymbolKind, 30 FileId, HlMod, HlTag, SymbolKind,
27}; 31};
28 32
29pub(crate) use html::highlight_as_html; 33pub(crate) use html::highlight_as_html;
30 34
31#[derive(Debug, Clone)] 35#[derive(Debug, Clone, Copy)]
32pub struct HighlightedRange { 36pub struct HlRange {
33 pub range: TextRange, 37 pub range: TextRange,
34 pub highlight: Highlight, 38 pub highlight: Highlight,
35 pub binding_hash: Option<u64>, 39 pub binding_hash: Option<u64>,
@@ -49,7 +53,7 @@ pub(crate) fn highlight(
49 file_id: FileId, 53 file_id: FileId,
50 range_to_highlight: Option<TextRange>, 54 range_to_highlight: Option<TextRange>,
51 syntactic_name_ref_highlighting: bool, 55 syntactic_name_ref_highlighting: bool,
52) -> Vec<HighlightedRange> { 56) -> Vec<HlRange> {
53 let _p = profile::span("highlight"); 57 let _p = profile::span("highlight");
54 let sema = Semantics::new(db); 58 let sema = Semantics::new(db);
55 59
@@ -68,28 +72,30 @@ pub(crate) fn highlight(
68 } 72 }
69 }; 73 };
70 74
75 let mut hl = highlights::Highlights::new(range_to_highlight);
76 traverse(&mut hl, &sema, &root, range_to_highlight, syntactic_name_ref_highlighting);
77 hl.to_vec()
78}
79
80fn traverse(
81 hl: &mut Highlights,
82 sema: &Semantics<RootDatabase>,
83 root: &SyntaxNode,
84 range_to_highlight: TextRange,
85 syntactic_name_ref_highlighting: bool,
86) {
71 let mut bindings_shadow_count: FxHashMap<Name, u32> = FxHashMap::default(); 87 let mut bindings_shadow_count: FxHashMap<Name, u32> = FxHashMap::default();
72 // We use a stack for the DFS traversal below.
73 // When we leave a node, the we use it to flatten the highlighted ranges.
74 let mut stack = HighlightedRangeStack::new();
75 88
76 let mut current_macro_call: Option<ast::MacroCall> = None; 89 let mut current_macro_call: Option<ast::MacroCall> = None;
77 let mut current_macro_rules: Option<ast::MacroRules> = None; 90 let mut current_macro_rules: Option<ast::MacroRules> = None;
78 let mut format_string_highlighter = FormatStringHighlighter::default();
79 let mut macro_rules_highlighter = MacroRulesHighlighter::default(); 91 let mut macro_rules_highlighter = MacroRulesHighlighter::default();
80 let mut inside_attribute = false; 92 let mut inside_attribute = false;
81 93
82 // Walk all nodes, keeping track of whether we are inside a macro or not. 94 // Walk all nodes, keeping track of whether we are inside a macro or not.
83 // If in macro, expand it first and highlight the expanded code. 95 // If in macro, expand it first and highlight the expanded code.
84 for event in root.preorder_with_tokens() { 96 for event in root.preorder_with_tokens() {
85 match &event {
86 WalkEvent::Enter(_) => stack.push(),
87 WalkEvent::Leave(_) => stack.pop(),
88 };
89
90 let event_range = match &event { 97 let event_range = match &event {
91 WalkEvent::Enter(it) => it.text_range(), 98 WalkEvent::Enter(it) | WalkEvent::Leave(it) => it.text_range(),
92 WalkEvent::Leave(it) => it.text_range(),
93 }; 99 };
94 100
95 // Element outside of the viewport, no need to highlight 101 // Element outside of the viewport, no need to highlight
@@ -101,9 +107,9 @@ pub(crate) fn highlight(
101 match event.clone().map(|it| it.into_node().and_then(ast::MacroCall::cast)) { 107 match event.clone().map(|it| it.into_node().and_then(ast::MacroCall::cast)) {
102 WalkEvent::Enter(Some(mc)) => { 108 WalkEvent::Enter(Some(mc)) => {
103 if let Some(range) = macro_call_range(&mc) { 109 if let Some(range) = macro_call_range(&mc) {
104 stack.add(HighlightedRange { 110 hl.add(HlRange {
105 range, 111 range,
106 highlight: HighlightTag::Symbol(SymbolKind::Macro).into(), 112 highlight: HlTag::Symbol(SymbolKind::Macro).into(),
107 binding_hash: None, 113 binding_hash: None,
108 }); 114 });
109 } 115 }
@@ -113,7 +119,6 @@ pub(crate) fn highlight(
113 WalkEvent::Leave(Some(mc)) => { 119 WalkEvent::Leave(Some(mc)) => {
114 assert_eq!(current_macro_call, Some(mc)); 120 assert_eq!(current_macro_call, Some(mc));
115 current_macro_call = None; 121 current_macro_call = None;
116 format_string_highlighter = FormatStringHighlighter::default();
117 } 122 }
118 _ => (), 123 _ => (),
119 } 124 }
@@ -131,33 +136,24 @@ pub(crate) fn highlight(
131 } 136 }
132 _ => (), 137 _ => (),
133 } 138 }
134
135 match &event { 139 match &event {
136 // Check for Rust code in documentation
137 WalkEvent::Leave(NodeOrToken::Node(node)) => {
138 if ast::Attr::can_cast(node.kind()) {
139 inside_attribute = false
140 }
141 if let Some((doctest, range_mapping, new_comments)) =
142 injection::extract_doc_comments(node)
143 {
144 injection::highlight_doc_comment(
145 doctest,
146 range_mapping,
147 new_comments,
148 &mut stack,
149 );
150 }
151 }
152 WalkEvent::Enter(NodeOrToken::Node(node)) if ast::Attr::can_cast(node.kind()) => { 140 WalkEvent::Enter(NodeOrToken::Node(node)) if ast::Attr::can_cast(node.kind()) => {
153 inside_attribute = true 141 inside_attribute = true
154 } 142 }
143 WalkEvent::Leave(NodeOrToken::Node(node)) if ast::Attr::can_cast(node.kind()) => {
144 inside_attribute = false
145 }
155 _ => (), 146 _ => (),
156 } 147 }
157 148
158 let element = match event { 149 let element = match event {
159 WalkEvent::Enter(it) => it, 150 WalkEvent::Enter(it) => it,
160 WalkEvent::Leave(_) => continue, 151 WalkEvent::Leave(it) => {
152 if let Some(node) = it.as_node() {
153 inject::doc_comment(hl, node);
154 }
155 continue;
156 }
161 }; 157 };
162 158
163 let range = element.text_range(); 159 let range = element.text_range();
@@ -177,8 +173,6 @@ pub(crate) fn highlight(
177 let token = sema.descend_into_macros(token.clone()); 173 let token = sema.descend_into_macros(token.clone());
178 let parent = token.parent(); 174 let parent = token.parent();
179 175
180 format_string_highlighter.check_for_format_string(&parent);
181
182 // We only care Name and Name_ref 176 // We only care Name and Name_ref
183 match (token.kind(), parent.kind()) { 177 match (token.kind(), parent.kind()) {
184 (IDENT, NAME) | (IDENT, NAME_REF) => parent.into(), 178 (IDENT, NAME) | (IDENT, NAME_REF) => parent.into(),
@@ -191,213 +185,45 @@ pub(crate) fn highlight(
191 if let Some(token) = element.as_token().cloned().and_then(ast::String::cast) { 185 if let Some(token) = element.as_token().cloned().and_then(ast::String::cast) {
192 if token.is_raw() { 186 if token.is_raw() {
193 let expanded = element_to_highlight.as_token().unwrap().clone(); 187 let expanded = element_to_highlight.as_token().unwrap().clone();
194 if injection::highlight_injection(&mut stack, &sema, token, expanded).is_some() { 188 if inject::ra_fixture(hl, &sema, token, expanded).is_some() {
195 continue; 189 continue;
196 } 190 }
197 } 191 }
198 } 192 }
199 193
200 if let Some((mut highlight, binding_hash)) = highlight_element( 194 if let Some(_) = macro_rules_highlighter.highlight(element_to_highlight.clone()) {
195 continue;
196 }
197
198 if let Some((mut highlight, binding_hash)) = highlight::element(
201 &sema, 199 &sema,
202 &mut bindings_shadow_count, 200 &mut bindings_shadow_count,
203 syntactic_name_ref_highlighting, 201 syntactic_name_ref_highlighting,
204 element_to_highlight.clone(), 202 element_to_highlight.clone(),
205 ) { 203 ) {
206 if inside_attribute { 204 if inside_attribute {
207 highlight = highlight | HighlightModifier::Attribute; 205 highlight = highlight | HlMod::Attribute;
208 }
209
210 if macro_rules_highlighter.highlight(element_to_highlight.clone()).is_none() {
211 stack.add(HighlightedRange { range, highlight, binding_hash });
212 } 206 }
213 207
214 if let Some(string) = 208 hl.add(HlRange { range, highlight, binding_hash });
215 element_to_highlight.as_token().cloned().and_then(ast::String::cast)
216 {
217 format_string_highlighter.highlight_format_string(&mut stack, &string, range);
218 // Highlight escape sequences
219 if let Some(char_ranges) = string.char_ranges() {
220 stack.push();
221 for (piece_range, _) in char_ranges.iter().filter(|(_, char)| char.is_ok()) {
222 if string.text()[piece_range.start().into()..].starts_with('\\') {
223 stack.add(HighlightedRange {
224 range: piece_range + range.start(),
225 highlight: HighlightTag::EscapeSequence.into(),
226 binding_hash: None,
227 });
228 }
229 }
230 stack.pop_and_inject(None);
231 }
232 }
233 } 209 }
234 }
235
236 stack.flattened()
237}
238
239#[derive(Debug)]
240struct HighlightedRangeStack {
241 stack: Vec<Vec<HighlightedRange>>,
242}
243
244/// We use a stack to implement the flattening logic for the highlighted
245/// syntax ranges.
246impl HighlightedRangeStack {
247 fn new() -> Self {
248 Self { stack: vec![Vec::new()] }
249 }
250
251 fn push(&mut self) {
252 self.stack.push(Vec::new());
253 }
254
255 /// Flattens the highlighted ranges.
256 ///
257 /// For example `#[cfg(feature = "foo")]` contains the nested ranges:
258 /// 1) parent-range: Attribute [0, 23)
259 /// 2) child-range: String [16, 21)
260 ///
261 /// The following code implements the flattening, for our example this results to:
262 /// `[Attribute [0, 16), String [16, 21), Attribute [21, 23)]`
263 fn pop(&mut self) {
264 let children = self.stack.pop().unwrap();
265 let prev = self.stack.last_mut().unwrap();
266 let needs_flattening = !children.is_empty()
267 && !prev.is_empty()
268 && prev.last().unwrap().range.contains_range(children.first().unwrap().range);
269 if !needs_flattening {
270 prev.extend(children);
271 } else {
272 let mut parent = prev.pop().unwrap();
273 for ele in children {
274 assert!(parent.range.contains_range(ele.range));
275
276 let cloned = Self::intersect(&mut parent, &ele);
277 if !parent.range.is_empty() {
278 prev.push(parent);
279 }
280 prev.push(ele);
281 parent = cloned;
282 }
283 if !parent.range.is_empty() {
284 prev.push(parent);
285 }
286 }
287 }
288
289 /// Intersects the `HighlightedRange` `parent` with `child`.
290 /// `parent` is mutated in place, becoming the range before `child`.
291 /// Returns the range (of the same type as `parent`) *after* `child`.
292 fn intersect(parent: &mut HighlightedRange, child: &HighlightedRange) -> HighlightedRange {
293 assert!(parent.range.contains_range(child.range));
294
295 let mut cloned = parent.clone();
296 parent.range = TextRange::new(parent.range.start(), child.range.start());
297 cloned.range = TextRange::new(child.range.end(), cloned.range.end());
298 210
299 cloned 211 if let Some(string) = element_to_highlight.as_token().cloned().and_then(ast::String::cast) {
300 } 212 highlight_format_string(hl, &string, range);
301 213 // Highlight escape sequences
302 /// Remove the `HighlightRange` of `parent` that's currently covered by `child`. 214 if let Some(char_ranges) = string.char_ranges() {
303 fn intersect_partial(parent: &mut HighlightedRange, child: &HighlightedRange) { 215 for (piece_range, _) in char_ranges.iter().filter(|(_, char)| char.is_ok()) {
304 assert!( 216 if string.text()[piece_range.start().into()..].starts_with('\\') {
305 parent.range.start() <= child.range.start() 217 hl.add(HlRange {
306 && parent.range.end() >= child.range.start() 218 range: piece_range + range.start(),
307 && child.range.end() > parent.range.end() 219 highlight: HlTag::EscapeSequence.into(),
308 ); 220 binding_hash: None,
309 221 });
310 parent.range = TextRange::new(parent.range.start(), child.range.start());
311 }
312
313 /// Similar to `pop`, but can modify arbitrary prior ranges (where `pop`)
314 /// can only modify the last range currently on the stack.
315 /// Can be used to do injections that span multiple ranges, like the
316 /// doctest injection below.
317 /// If `overwrite_parent` is non-optional, the highlighting of the parent range
318 /// is overwritten with the argument.
319 ///
320 /// Note that `pop` can be simulated by `pop_and_inject(false)` but the
321 /// latter is computationally more expensive.
322 fn pop_and_inject(&mut self, overwrite_parent: Option<Highlight>) {
323 let mut children = self.stack.pop().unwrap();
324 let prev = self.stack.last_mut().unwrap();
325 children.sort_by_key(|range| range.range.start());
326 prev.sort_by_key(|range| range.range.start());
327
328 for child in children {
329 if let Some(idx) =
330 prev.iter().position(|parent| parent.range.contains_range(child.range))
331 {
332 if let Some(tag) = overwrite_parent {
333 prev[idx].highlight = tag;
334 }
335
336 let cloned = Self::intersect(&mut prev[idx], &child);
337 let insert_idx = if prev[idx].range.is_empty() {
338 prev.remove(idx);
339 idx
340 } else {
341 idx + 1
342 };
343 prev.insert(insert_idx, child);
344 if !cloned.range.is_empty() {
345 prev.insert(insert_idx + 1, cloned);
346 }
347 } else {
348 let maybe_idx =
349 prev.iter().position(|parent| parent.range.contains(child.range.start()));
350 match (overwrite_parent, maybe_idx) {
351 (Some(_), Some(idx)) => {
352 Self::intersect_partial(&mut prev[idx], &child);
353 let insert_idx = if prev[idx].range.is_empty() {
354 prev.remove(idx);
355 idx
356 } else {
357 idx + 1
358 };
359 prev.insert(insert_idx, child);
360 }
361 (_, None) => {
362 let idx = prev
363 .binary_search_by_key(&child.range.start(), |range| range.range.start())
364 .unwrap_or_else(|x| x);
365 prev.insert(idx, child);
366 }
367 _ => {
368 unreachable!("child range should be completely contained in parent range");
369 } 222 }
370 } 223 }
371 } 224 }
372 } 225 }
373 } 226 }
374
375 fn add(&mut self, range: HighlightedRange) {
376 self.stack
377 .last_mut()
378 .expect("during DFS traversal, the stack must not be empty")
379 .push(range)
380 }
381
382 fn flattened(mut self) -> Vec<HighlightedRange> {
383 assert_eq!(
384 self.stack.len(),
385 1,
386 "after DFS traversal, the stack should only contain a single element"
387 );
388 let mut res = self.stack.pop().unwrap();
389 res.sort_by_key(|range| range.range.start());
390 // Check that ranges are sorted and disjoint
391 for (left, right) in res.iter().zip(res.iter().skip(1)) {
392 assert!(
393 left.range.end() <= right.range.start(),
394 "left: {:#?}, right: {:#?}",
395 left,
396 right
397 );
398 }
399 res
400 }
401} 227}
402 228
403fn macro_call_range(macro_call: &ast::MacroCall) -> Option<TextRange> { 229fn macro_call_range(macro_call: &ast::MacroCall) -> Option<TextRange> {
@@ -415,524 +241,3 @@ fn macro_call_range(macro_call: &ast::MacroCall) -> Option<TextRange> {
415 241
416 Some(TextRange::new(range_start, range_end)) 242 Some(TextRange::new(range_start, range_end))
417} 243}
418
419/// Returns true if the parent nodes of `node` all match the `SyntaxKind`s in `kinds` exactly.
420fn parents_match(mut node: NodeOrToken<SyntaxNode, SyntaxToken>, mut kinds: &[SyntaxKind]) -> bool {
421 while let (Some(parent), [kind, rest @ ..]) = (&node.parent(), kinds) {
422 if parent.kind() != *kind {
423 return false;
424 }
425
426 // FIXME: Would be nice to get parent out of the match, but binding by-move and by-value
427 // in the same pattern is unstable: rust-lang/rust#68354.
428 node = node.parent().unwrap().into();
429 kinds = rest;
430 }
431
432 // Only true if we matched all expected kinds
433 kinds.len() == 0
434}
435
436fn is_consumed_lvalue(
437 node: NodeOrToken<SyntaxNode, SyntaxToken>,
438 local: &Local,
439 db: &RootDatabase,
440) -> bool {
441 // When lvalues are passed as arguments and they're not Copy, then mark them as Consuming.
442 parents_match(node, &[PATH_SEGMENT, PATH, PATH_EXPR, ARG_LIST]) && !local.ty(db).is_copy(db)
443}
444
445fn highlight_element(
446 sema: &Semantics<RootDatabase>,
447 bindings_shadow_count: &mut FxHashMap<Name, u32>,
448 syntactic_name_ref_highlighting: bool,
449 element: SyntaxElement,
450) -> Option<(Highlight, Option<u64>)> {
451 let db = sema.db;
452 let mut binding_hash = None;
453 let highlight: Highlight = match element.kind() {
454 FN => {
455 bindings_shadow_count.clear();
456 return None;
457 }
458
459 // Highlight definitions depending on the "type" of the definition.
460 NAME => {
461 let name = element.into_node().and_then(ast::Name::cast).unwrap();
462 let name_kind = NameClass::classify(sema, &name);
463
464 if let Some(NameClass::Definition(Definition::Local(local))) = &name_kind {
465 if let Some(name) = local.name(db) {
466 let shadow_count = bindings_shadow_count.entry(name.clone()).or_default();
467 *shadow_count += 1;
468 binding_hash = Some(calc_binding_hash(&name, *shadow_count))
469 }
470 };
471
472 match name_kind {
473 Some(NameClass::ExternCrate(_)) => HighlightTag::Symbol(SymbolKind::Module).into(),
474 Some(NameClass::Definition(def)) => {
475 highlight_def(db, def) | HighlightModifier::Definition
476 }
477 Some(NameClass::ConstReference(def)) => highlight_def(db, def),
478 Some(NameClass::PatFieldShorthand { field_ref, .. }) => {
479 let mut h = HighlightTag::Symbol(SymbolKind::Field).into();
480 if let Definition::Field(field) = field_ref {
481 if let VariantDef::Union(_) = field.parent_def(db) {
482 h |= HighlightModifier::Unsafe;
483 }
484 }
485
486 h
487 }
488 None => highlight_name_by_syntax(name) | HighlightModifier::Definition,
489 }
490 }
491
492 // Highlight references like the definitions they resolve to
493 NAME_REF if element.ancestors().any(|it| it.kind() == ATTR) => {
494 // even though we track whether we are in an attribute or not we still need this special case
495 // as otherwise we would emit unresolved references for name refs inside attributes
496 Highlight::from(HighlightTag::Symbol(SymbolKind::Function))
497 }
498 NAME_REF => {
499 let name_ref = element.into_node().and_then(ast::NameRef::cast).unwrap();
500 highlight_func_by_name_ref(sema, &name_ref).unwrap_or_else(|| {
501 match NameRefClass::classify(sema, &name_ref) {
502 Some(name_kind) => match name_kind {
503 NameRefClass::ExternCrate(_) => {
504 HighlightTag::Symbol(SymbolKind::Module).into()
505 }
506 NameRefClass::Definition(def) => {
507 if let Definition::Local(local) = &def {
508 if let Some(name) = local.name(db) {
509 let shadow_count =
510 bindings_shadow_count.entry(name.clone()).or_default();
511 binding_hash = Some(calc_binding_hash(&name, *shadow_count))
512 }
513 };
514
515 let mut h = highlight_def(db, def);
516
517 if let Definition::Local(local) = &def {
518 if is_consumed_lvalue(name_ref.syntax().clone().into(), local, db) {
519 h |= HighlightModifier::Consuming;
520 }
521 }
522
523 if let Some(parent) = name_ref.syntax().parent() {
524 if matches!(parent.kind(), FIELD_EXPR | RECORD_PAT_FIELD) {
525 if let Definition::Field(field) = def {
526 if let VariantDef::Union(_) = field.parent_def(db) {
527 h |= HighlightModifier::Unsafe;
528 }
529 }
530 }
531 }
532
533 h
534 }
535 NameRefClass::FieldShorthand { .. } => {
536 HighlightTag::Symbol(SymbolKind::Field).into()
537 }
538 },
539 None if syntactic_name_ref_highlighting => {
540 highlight_name_ref_by_syntax(name_ref, sema)
541 }
542 None => HighlightTag::UnresolvedReference.into(),
543 }
544 })
545 }
546
547 // Simple token-based highlighting
548 COMMENT => {
549 let comment = element.into_token().and_then(ast::Comment::cast)?;
550 let h = HighlightTag::Comment;
551 match comment.kind().doc {
552 Some(_) => h | HighlightModifier::Documentation,
553 None => h.into(),
554 }
555 }
556 STRING | BYTE_STRING => HighlightTag::StringLiteral.into(),
557 ATTR => HighlightTag::Attribute.into(),
558 INT_NUMBER | FLOAT_NUMBER => HighlightTag::NumericLiteral.into(),
559 BYTE => HighlightTag::ByteLiteral.into(),
560 CHAR => HighlightTag::CharLiteral.into(),
561 QUESTION => Highlight::new(HighlightTag::Operator) | HighlightModifier::ControlFlow,
562 LIFETIME => {
563 let lifetime = element.into_node().and_then(ast::Lifetime::cast).unwrap();
564
565 match NameClass::classify_lifetime(sema, &lifetime) {
566 Some(NameClass::Definition(def)) => {
567 highlight_def(db, def) | HighlightModifier::Definition
568 }
569 None => match NameRefClass::classify_lifetime(sema, &lifetime) {
570 Some(NameRefClass::Definition(def)) => highlight_def(db, def),
571 _ => Highlight::new(HighlightTag::Symbol(SymbolKind::LifetimeParam)),
572 },
573 _ => {
574 Highlight::new(HighlightTag::Symbol(SymbolKind::LifetimeParam))
575 | HighlightModifier::Definition
576 }
577 }
578 }
579 p if p.is_punct() => match p {
580 T![&] => {
581 let h = HighlightTag::Operator.into();
582 let is_unsafe = element
583 .parent()
584 .and_then(ast::RefExpr::cast)
585 .map(|ref_expr| sema.is_unsafe_ref_expr(&ref_expr))
586 .unwrap_or(false);
587 if is_unsafe {
588 h | HighlightModifier::Unsafe
589 } else {
590 h
591 }
592 }
593 T![::] | T![->] | T![=>] | T![..] | T![=] | T![@] | T![.] => {
594 HighlightTag::Operator.into()
595 }
596 T![!] if element.parent().and_then(ast::MacroCall::cast).is_some() => {
597 HighlightTag::Symbol(SymbolKind::Macro).into()
598 }
599 T![!] if element.parent().and_then(ast::NeverType::cast).is_some() => {
600 HighlightTag::BuiltinType.into()
601 }
602 T![*] if element.parent().and_then(ast::PtrType::cast).is_some() => {
603 HighlightTag::Keyword.into()
604 }
605 T![*] if element.parent().and_then(ast::PrefixExpr::cast).is_some() => {
606 let prefix_expr = element.parent().and_then(ast::PrefixExpr::cast)?;
607
608 let expr = prefix_expr.expr()?;
609 let ty = sema.type_of_expr(&expr)?;
610 if ty.is_raw_ptr() {
611 HighlightTag::Operator | HighlightModifier::Unsafe
612 } else if let Some(ast::PrefixOp::Deref) = prefix_expr.op_kind() {
613 HighlightTag::Operator.into()
614 } else {
615 HighlightTag::Punctuation.into()
616 }
617 }
618 T![-] if element.parent().and_then(ast::PrefixExpr::cast).is_some() => {
619 let prefix_expr = element.parent().and_then(ast::PrefixExpr::cast)?;
620
621 let expr = prefix_expr.expr()?;
622 match expr {
623 ast::Expr::Literal(_) => HighlightTag::NumericLiteral,
624 _ => HighlightTag::Operator,
625 }
626 .into()
627 }
628 _ if element.parent().and_then(ast::PrefixExpr::cast).is_some() => {
629 HighlightTag::Operator.into()
630 }
631 _ if element.parent().and_then(ast::BinExpr::cast).is_some() => {
632 HighlightTag::Operator.into()
633 }
634 _ if element.parent().and_then(ast::RangeExpr::cast).is_some() => {
635 HighlightTag::Operator.into()
636 }
637 _ if element.parent().and_then(ast::RangePat::cast).is_some() => {
638 HighlightTag::Operator.into()
639 }
640 _ if element.parent().and_then(ast::RestPat::cast).is_some() => {
641 HighlightTag::Operator.into()
642 }
643 _ if element.parent().and_then(ast::Attr::cast).is_some() => {
644 HighlightTag::Attribute.into()
645 }
646 _ => HighlightTag::Punctuation.into(),
647 },
648
649 k if k.is_keyword() => {
650 let h = Highlight::new(HighlightTag::Keyword);
651 match k {
652 T![break]
653 | T![continue]
654 | T![else]
655 | T![if]
656 | T![loop]
657 | T![match]
658 | T![return]
659 | T![while]
660 | T![in] => h | HighlightModifier::ControlFlow,
661 T![for] if !is_child_of_impl(&element) => h | HighlightModifier::ControlFlow,
662 T![unsafe] => h | HighlightModifier::Unsafe,
663 T![true] | T![false] => HighlightTag::BoolLiteral.into(),
664 T![self] => {
665 let self_param_is_mut = element
666 .parent()
667 .and_then(ast::SelfParam::cast)
668 .and_then(|p| p.mut_token())
669 .is_some();
670 let self_path = &element
671 .parent()
672 .as_ref()
673 .and_then(SyntaxNode::parent)
674 .and_then(ast::Path::cast)
675 .and_then(|p| sema.resolve_path(&p));
676 let mut h = HighlightTag::Symbol(SymbolKind::SelfParam).into();
677 if self_param_is_mut
678 || matches!(self_path,
679 Some(hir::PathResolution::Local(local))
680 if local.is_self(db)
681 && (local.is_mut(db) || local.ty(db).is_mutable_reference())
682 )
683 {
684 h |= HighlightModifier::Mutable
685 }
686
687 if let Some(hir::PathResolution::Local(local)) = self_path {
688 if is_consumed_lvalue(element, &local, db) {
689 h |= HighlightModifier::Consuming;
690 }
691 }
692
693 h
694 }
695 T![ref] => element
696 .parent()
697 .and_then(ast::IdentPat::cast)
698 .and_then(|ident_pat| {
699 if sema.is_unsafe_ident_pat(&ident_pat) {
700 Some(HighlightModifier::Unsafe)
701 } else {
702 None
703 }
704 })
705 .map(|modifier| h | modifier)
706 .unwrap_or(h),
707 _ => h,
708 }
709 }
710
711 _ => return None,
712 };
713
714 return Some((highlight, binding_hash));
715
716 fn calc_binding_hash(name: &Name, shadow_count: u32) -> u64 {
717 fn hash<T: std::hash::Hash + std::fmt::Debug>(x: T) -> u64 {
718 use std::{collections::hash_map::DefaultHasher, hash::Hasher};
719
720 let mut hasher = DefaultHasher::new();
721 x.hash(&mut hasher);
722 hasher.finish()
723 }
724
725 hash((name, shadow_count))
726 }
727}
728
729fn is_child_of_impl(element: &SyntaxElement) -> bool {
730 match element.parent() {
731 Some(e) => e.kind() == IMPL,
732 _ => false,
733 }
734}
735
736fn highlight_func_by_name_ref(
737 sema: &Semantics<RootDatabase>,
738 name_ref: &ast::NameRef,
739) -> Option<Highlight> {
740 let method_call = name_ref.syntax().parent().and_then(ast::MethodCallExpr::cast)?;
741 highlight_method_call(sema, &method_call)
742}
743
744fn highlight_method_call(
745 sema: &Semantics<RootDatabase>,
746 method_call: &ast::MethodCallExpr,
747) -> Option<Highlight> {
748 let func = sema.resolve_method_call(&method_call)?;
749 let mut h = HighlightTag::Symbol(SymbolKind::Function).into();
750 h |= HighlightModifier::Associated;
751 if func.is_unsafe(sema.db) || sema.is_unsafe_method_call(&method_call) {
752 h |= HighlightModifier::Unsafe;
753 }
754 if let Some(self_param) = func.self_param(sema.db) {
755 match self_param.access(sema.db) {
756 hir::Access::Shared => (),
757 hir::Access::Exclusive => h |= HighlightModifier::Mutable,
758 hir::Access::Owned => {
759 if let Some(receiver_ty) =
760 method_call.receiver().and_then(|it| sema.type_of_expr(&it))
761 {
762 if !receiver_ty.is_copy(sema.db) {
763 h |= HighlightModifier::Consuming
764 }
765 }
766 }
767 }
768 }
769 Some(h)
770}
771
772fn highlight_def(db: &RootDatabase, def: Definition) -> Highlight {
773 match def {
774 Definition::Macro(_) => HighlightTag::Symbol(SymbolKind::Macro),
775 Definition::Field(_) => HighlightTag::Symbol(SymbolKind::Field),
776 Definition::ModuleDef(def) => match def {
777 hir::ModuleDef::Module(_) => HighlightTag::Symbol(SymbolKind::Module),
778 hir::ModuleDef::Function(func) => {
779 let mut h = Highlight::new(HighlightTag::Symbol(SymbolKind::Function));
780 if func.as_assoc_item(db).is_some() {
781 h |= HighlightModifier::Associated;
782 if func.self_param(db).is_none() {
783 h |= HighlightModifier::Static
784 }
785 }
786 if func.is_unsafe(db) {
787 h |= HighlightModifier::Unsafe;
788 }
789 return h;
790 }
791 hir::ModuleDef::Adt(hir::Adt::Struct(_)) => HighlightTag::Symbol(SymbolKind::Struct),
792 hir::ModuleDef::Adt(hir::Adt::Enum(_)) => HighlightTag::Symbol(SymbolKind::Enum),
793 hir::ModuleDef::Adt(hir::Adt::Union(_)) => HighlightTag::Symbol(SymbolKind::Union),
794 hir::ModuleDef::Variant(_) => HighlightTag::Symbol(SymbolKind::Variant),
795 hir::ModuleDef::Const(konst) => {
796 let mut h = Highlight::new(HighlightTag::Symbol(SymbolKind::Const));
797 if konst.as_assoc_item(db).is_some() {
798 h |= HighlightModifier::Associated
799 }
800 return h;
801 }
802 hir::ModuleDef::Trait(_) => HighlightTag::Symbol(SymbolKind::Trait),
803 hir::ModuleDef::TypeAlias(type_) => {
804 let mut h = Highlight::new(HighlightTag::Symbol(SymbolKind::TypeAlias));
805 if type_.as_assoc_item(db).is_some() {
806 h |= HighlightModifier::Associated
807 }
808 return h;
809 }
810 hir::ModuleDef::BuiltinType(_) => HighlightTag::BuiltinType,
811 hir::ModuleDef::Static(s) => {
812 let mut h = Highlight::new(HighlightTag::Symbol(SymbolKind::Static));
813 if s.is_mut(db) {
814 h |= HighlightModifier::Mutable;
815 h |= HighlightModifier::Unsafe;
816 }
817 return h;
818 }
819 },
820 Definition::SelfType(_) => HighlightTag::Symbol(SymbolKind::Impl),
821 Definition::TypeParam(_) => HighlightTag::Symbol(SymbolKind::TypeParam),
822 Definition::ConstParam(_) => HighlightTag::Symbol(SymbolKind::ConstParam),
823 Definition::Local(local) => {
824 let tag = if local.is_param(db) {
825 HighlightTag::Symbol(SymbolKind::ValueParam)
826 } else {
827 HighlightTag::Symbol(SymbolKind::Local)
828 };
829 let mut h = Highlight::new(tag);
830 if local.is_mut(db) || local.ty(db).is_mutable_reference() {
831 h |= HighlightModifier::Mutable;
832 }
833 if local.ty(db).as_callable(db).is_some() || local.ty(db).impls_fnonce(db) {
834 h |= HighlightModifier::Callable;
835 }
836 return h;
837 }
838 Definition::LifetimeParam(_) => HighlightTag::Symbol(SymbolKind::LifetimeParam),
839 Definition::Label(_) => HighlightTag::Symbol(SymbolKind::Label),
840 }
841 .into()
842}
843
844fn highlight_name_by_syntax(name: ast::Name) -> Highlight {
845 let default = HighlightTag::UnresolvedReference;
846
847 let parent = match name.syntax().parent() {
848 Some(it) => it,
849 _ => return default.into(),
850 };
851
852 let tag = match parent.kind() {
853 STRUCT => HighlightTag::Symbol(SymbolKind::Struct),
854 ENUM => HighlightTag::Symbol(SymbolKind::Enum),
855 VARIANT => HighlightTag::Symbol(SymbolKind::Variant),
856 UNION => HighlightTag::Symbol(SymbolKind::Union),
857 TRAIT => HighlightTag::Symbol(SymbolKind::Trait),
858 TYPE_ALIAS => HighlightTag::Symbol(SymbolKind::TypeAlias),
859 TYPE_PARAM => HighlightTag::Symbol(SymbolKind::TypeParam),
860 RECORD_FIELD => HighlightTag::Symbol(SymbolKind::Field),
861 MODULE => HighlightTag::Symbol(SymbolKind::Module),
862 FN => HighlightTag::Symbol(SymbolKind::Function),
863 CONST => HighlightTag::Symbol(SymbolKind::Const),
864 STATIC => HighlightTag::Symbol(SymbolKind::Static),
865 IDENT_PAT => HighlightTag::Symbol(SymbolKind::Local),
866 _ => default,
867 };
868
869 tag.into()
870}
871
872fn highlight_name_ref_by_syntax(name: ast::NameRef, sema: &Semantics<RootDatabase>) -> Highlight {
873 let default = HighlightTag::UnresolvedReference;
874
875 let parent = match name.syntax().parent() {
876 Some(it) => it,
877 _ => return default.into(),
878 };
879
880 match parent.kind() {
881 METHOD_CALL_EXPR => {
882 return ast::MethodCallExpr::cast(parent)
883 .and_then(|method_call| highlight_method_call(sema, &method_call))
884 .unwrap_or_else(|| HighlightTag::Symbol(SymbolKind::Function).into());
885 }
886 FIELD_EXPR => {
887 let h = HighlightTag::Symbol(SymbolKind::Field);
888 let is_union = ast::FieldExpr::cast(parent)
889 .and_then(|field_expr| {
890 let field = sema.resolve_field(&field_expr)?;
891 Some(if let VariantDef::Union(_) = field.parent_def(sema.db) {
892 true
893 } else {
894 false
895 })
896 })
897 .unwrap_or(false);
898 if is_union {
899 h | HighlightModifier::Unsafe
900 } else {
901 h.into()
902 }
903 }
904 PATH_SEGMENT => {
905 let path = match parent.parent().and_then(ast::Path::cast) {
906 Some(it) => it,
907 _ => return default.into(),
908 };
909 let expr = match path.syntax().parent().and_then(ast::PathExpr::cast) {
910 Some(it) => it,
911 _ => {
912 // within path, decide whether it is module or adt by checking for uppercase name
913 return if name.text().chars().next().unwrap_or_default().is_uppercase() {
914 HighlightTag::Symbol(SymbolKind::Struct)
915 } else {
916 HighlightTag::Symbol(SymbolKind::Module)
917 }
918 .into();
919 }
920 };
921 let parent = match expr.syntax().parent() {
922 Some(it) => it,
923 None => return default.into(),
924 };
925
926 match parent.kind() {
927 CALL_EXPR => HighlightTag::Symbol(SymbolKind::Function).into(),
928 _ => if name.text().chars().next().unwrap_or_default().is_uppercase() {
929 HighlightTag::Symbol(SymbolKind::Struct)
930 } else {
931 HighlightTag::Symbol(SymbolKind::Const)
932 }
933 .into(),
934 }
935 }
936 _ => default.into(),
937 }
938}
diff --git a/crates/ide/src/syntax_highlighting/format.rs b/crates/ide/src/syntax_highlighting/format.rs
index 26416022b..a74ca844b 100644
--- a/crates/ide/src/syntax_highlighting/format.rs
+++ b/crates/ide/src/syntax_highlighting/format.rs
@@ -1,65 +1,51 @@
1//! Syntax highlighting for format macro strings. 1//! Syntax highlighting for format macro strings.
2use syntax::{ 2use syntax::{
3 ast::{self, FormatSpecifier, HasFormatSpecifier}, 3 ast::{self, FormatSpecifier, HasFormatSpecifier},
4 AstNode, AstToken, SyntaxElement, SyntaxKind, SyntaxNode, TextRange, 4 AstNode, AstToken, TextRange,
5}; 5};
6 6
7use crate::{ 7use crate::{syntax_highlighting::highlights::Highlights, HlRange, HlTag, SymbolKind};
8 syntax_highlighting::HighlightedRangeStack, HighlightTag, HighlightedRange, SymbolKind,
9};
10
11#[derive(Default)]
12pub(super) struct FormatStringHighlighter {
13 format_string: Option<SyntaxElement>,
14}
15 8
16impl FormatStringHighlighter { 9pub(super) fn highlight_format_string(
17 pub(super) fn check_for_format_string(&mut self, parent: &SyntaxNode) { 10 stack: &mut Highlights,
18 // Check if macro takes a format string and remember it for highlighting later. 11 string: &ast::String,
19 // The macros that accept a format string expand to a compiler builtin macros 12 range: TextRange,
20 // `format_args` and `format_args_nl`. 13) {
21 if let Some(name) = parent 14 if is_format_string(string).is_none() {
22 .parent() 15 return;
23 .and_then(ast::MacroCall::cast)
24 .and_then(|mc| mc.path())
25 .and_then(|p| p.segment())
26 .and_then(|s| s.name_ref())
27 {
28 match name.text().as_str() {
29 "format_args" | "format_args_nl" => {
30 self.format_string = parent
31 .children_with_tokens()
32 .filter(|t| t.kind() != SyntaxKind::WHITESPACE)
33 .nth(1)
34 .filter(|e| ast::String::can_cast(e.kind()))
35 }
36 _ => {}
37 }
38 }
39 } 16 }
40 pub(super) fn highlight_format_string( 17
41 &self, 18 string.lex_format_specifier(|piece_range, kind| {
42 range_stack: &mut HighlightedRangeStack, 19 if let Some(highlight) = highlight_format_specifier(kind) {
43 string: &impl HasFormatSpecifier, 20 stack.add(HlRange {
44 range: TextRange, 21 range: piece_range + range.start(),
45 ) { 22 highlight: highlight.into(),
46 if self.format_string.as_ref() == Some(&SyntaxElement::from(string.syntax().clone())) { 23 binding_hash: None,
47 range_stack.push();
48 string.lex_format_specifier(|piece_range, kind| {
49 if let Some(highlight) = highlight_format_specifier(kind) {
50 range_stack.add(HighlightedRange {
51 range: piece_range + range.start(),
52 highlight: highlight.into(),
53 binding_hash: None,
54 });
55 }
56 }); 24 });
57 range_stack.pop();
58 } 25 }
26 });
27}
28
29fn is_format_string(string: &ast::String) -> Option<()> {
30 let parent = string.syntax().parent();
31
32 let name = parent.parent().and_then(ast::MacroCall::cast)?.path()?.segment()?.name_ref()?;
33 if !matches!(name.text().as_str(), "format_args" | "format_args_nl") {
34 return None;
35 }
36
37 let first_literal = parent
38 .children_with_tokens()
39 .filter_map(|it| it.as_token().cloned().and_then(ast::String::cast))
40 .next()?;
41 if &first_literal != string {
42 return None;
59 } 43 }
44
45 Some(())
60} 46}
61 47
62fn highlight_format_specifier(kind: FormatSpecifier) -> Option<HighlightTag> { 48fn highlight_format_specifier(kind: FormatSpecifier) -> Option<HlTag> {
63 Some(match kind { 49 Some(match kind {
64 FormatSpecifier::Open 50 FormatSpecifier::Open
65 | FormatSpecifier::Close 51 | FormatSpecifier::Close
@@ -71,8 +57,10 @@ fn highlight_format_specifier(kind: FormatSpecifier) -> Option<HighlightTag> {
71 | FormatSpecifier::DollarSign 57 | FormatSpecifier::DollarSign
72 | FormatSpecifier::Dot 58 | FormatSpecifier::Dot
73 | FormatSpecifier::Asterisk 59 | FormatSpecifier::Asterisk
74 | FormatSpecifier::QuestionMark => HighlightTag::FormatSpecifier, 60 | FormatSpecifier::QuestionMark => HlTag::FormatSpecifier,
75 FormatSpecifier::Integer | FormatSpecifier::Zero => HighlightTag::NumericLiteral, 61
76 FormatSpecifier::Identifier => HighlightTag::Symbol(SymbolKind::Local), 62 FormatSpecifier::Integer | FormatSpecifier::Zero => HlTag::NumericLiteral,
63
64 FormatSpecifier::Identifier => HlTag::Symbol(SymbolKind::Local),
77 }) 65 })
78} 66}
diff --git a/crates/ide/src/syntax_highlighting/highlight.rs b/crates/ide/src/syntax_highlighting/highlight.rs
new file mode 100644
index 000000000..34bae49a8
--- /dev/null
+++ b/crates/ide/src/syntax_highlighting/highlight.rs
@@ -0,0 +1,530 @@
1//! Computes color for a single element.
2
3