diff options
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" | |||
127 | checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" | 127 | checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" |
128 | 128 | ||
129 | [[package]] | 129 | [[package]] |
130 | name = "cargo-platform" | ||
131 | version = "0.1.1" | ||
132 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
133 | checksum = "0226944a63d1bf35a3b5f948dd7c59e263db83695c9e8bffc4037de02e30f1d7" | ||
134 | dependencies = [ | ||
135 | "serde", | ||
136 | ] | ||
137 | |||
138 | [[package]] | ||
130 | name = "cargo_metadata" | 139 | name = "cargo_metadata" |
131 | version = "0.12.0" | 140 | version = "0.12.2" |
132 | source = "registry+https://github.com/rust-lang/crates.io-index" | 141 | source = "registry+https://github.com/rust-lang/crates.io-index" |
133 | checksum = "d5a5f7b42f606b7f23674f6f4d877628350682bc40687d3fae65679a58d55345" | 142 | checksum = "11a47b6286279a9998588ef7050d1ebc2500c69892a557c90fe5d071c64415dc" |
134 | dependencies = [ | 143 | dependencies = [ |
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]] |
264 | name = "const_fn" | 275 | name = "const_fn" |
265 | version = "0.4.4" | 276 | version = "0.4.5" |
266 | source = "registry+https://github.com/rust-lang/crates.io-index" | 277 | source = "registry+https://github.com/rust-lang/crates.io-index" |
267 | checksum = "cd51eab21ab4fd6a3bf889e2d0958c0a6e3a61ad04260325e919e652a2a62826" | 278 | checksum = "28b9d6de7f49e22cf97ad17fc4036ece69300032f45f78f30b4a4482cdc3f4a6" |
268 | 279 | ||
269 | [[package]] | 280 | [[package]] |
270 | name = "crc32fast" | 281 | name = "crc32fast" |
@@ -774,9 +785,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" | |||
774 | 785 | ||
775 | [[package]] | 786 | [[package]] |
776 | name = "libc" | 787 | name = "libc" |
777 | version = "0.2.81" | 788 | version = "0.2.82" |
778 | source = "registry+https://github.com/rust-lang/crates.io-index" | 789 | source = "registry+https://github.com/rust-lang/crates.io-index" |
779 | checksum = "1482821306169ec4d07f6aca392a4681f66c75c9918aa49641a2595db64053cb" | 790 | checksum = "89203f3fba0a3795506acaad8ebce3c80c0af93f994d5a1d7a0b1eeb23271929" |
780 | 791 | ||
781 | [[package]] | 792 | [[package]] |
782 | name = "libloading" | 793 | name = "libloading" |
@@ -1134,9 +1145,9 @@ checksum = "28b9b4df73455c861d7cbf8be42f01d3b373ed7f02e378d55fa84eafc6f638b1" | |||
1134 | 1145 | ||
1135 | [[package]] | 1146 | [[package]] |
1136 | name = "pin-project-lite" | 1147 | name = "pin-project-lite" |
1137 | version = "0.2.0" | 1148 | version = "0.2.1" |
1138 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1149 | source = "registry+https://github.com/rust-lang/crates.io-index" |
1139 | checksum = "6b063f57ec186e6140e2b8b6921e5f1bd89c7356dda5b33acc5401203ca6131c" | 1150 | checksum = "e36743d754ccdf9954c2e352ce2d4b106e024c814f6499c2dadff80da9a442d8" |
1140 | 1151 | ||
1141 | [[package]] | 1152 | [[package]] |
1142 | name = "proc-macro2" | 1153 | name = "proc-macro2" |
@@ -1449,9 +1460,9 @@ dependencies = [ | |||
1449 | 1460 | ||
1450 | [[package]] | 1461 | [[package]] |
1451 | name = "semver-parser" | 1462 | name = "semver-parser" |
1452 | version = "0.10.1" | 1463 | version = "0.10.2" |
1453 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1464 | source = "registry+https://github.com/rust-lang/crates.io-index" |
1454 | checksum = "42ef146c2ad5e5f4b037cd6ce2ebb775401729b19a82040c1beac9d36c7d1428" | 1465 | checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" |
1455 | dependencies = [ | 1466 | dependencies = [ |
1456 | "pest", | 1467 | "pest", |
1457 | ] | 1468 | ] |
@@ -1558,9 +1569,9 @@ version = "0.0.0" | |||
1558 | 1569 | ||
1559 | [[package]] | 1570 | [[package]] |
1560 | name = "syn" | 1571 | name = "syn" |
1561 | version = "1.0.57" | 1572 | version = "1.0.58" |
1562 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1573 | source = "registry+https://github.com/rust-lang/crates.io-index" |
1563 | checksum = "4211ce9909eb971f111059df92c45640aad50a619cf55cd76476be803c4c68e6" | 1574 | checksum = "cc60a3d73ea6594cd712d830cc1f0390fd71542d8c8cd24e70cc54cdfd5e05d5" |
1564 | dependencies = [ | 1575 | dependencies = [ |
1565 | "proc-macro2", | 1576 | "proc-macro2", |
1566 | "quote", | 1577 | "quote", |
@@ -1643,9 +1654,9 @@ checksum = "db3c46be180f1af9673ebb27bc1235396f61ef6965b3fe0dbb2e624deb604f0e" | |||
1643 | 1654 | ||
1644 | [[package]] | 1655 | [[package]] |
1645 | name = "thread_local" | 1656 | name = "thread_local" |
1646 | version = "1.0.1" | 1657 | version = "1.1.0" |
1647 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1658 | source = "registry+https://github.com/rust-lang/crates.io-index" |
1648 | checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" | 1659 | checksum = "bb9bc092d0d51e76b2b19d9d85534ffc9ec2db959a2523cdae0697e2972cd447" |
1649 | dependencies = [ | 1660 | dependencies = [ |
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 @@ | |||
1 | use syntax::{ | 1 | use 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 | ||
6 | use crate::{AssistContext, AssistId, AssistKind, Assists}; | 6 | use 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 | ||
6 | use itertools::Itertools; | 6 | use itertools::Itertools; |
7 | use rustc_hash::FxHashSet; | 7 | use rustc_hash::FxHashSet; |
8 | use syntax::{ast, AstNode, SyntaxKind}; | 8 | use syntax::{ast, AstNode, T}; |
9 | 9 | ||
10 | use crate::{ | 10 | use 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( | |||
203 | fn parse_comma_sep_input(derive_input: ast::TokenTree) -> Result<FxHashSet<String>, ()> { | 205 | fn 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#" | ||
505 | enum Option<T> { Some(T), None } | ||
506 | |||
507 | fn main() { | ||
508 | let bar = &Option::Some(true); | ||
509 | bar.$0 | ||
510 | } | ||
511 | "#, | ||
512 | r#" | ||
513 | enum Option<T> { Some(T), None } | ||
514 | |||
515 | fn 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] |
13 | crossbeam-channel = "0.5.0" | 13 | crossbeam-channel = "0.5.0" |
14 | log = "0.4.8" | 14 | log = "0.4.8" |
15 | cargo_metadata = "=0.12.0" | 15 | cargo_metadata = "0.12.2" |
16 | serde_json = "1.0.48" | 16 | serde_json = "1.0.48" |
17 | jod-thread = "0.1.1" | 17 | jod-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 | } |
1264 | impl_from!(TypeParam, LifetimeParam, ConstParam for GenericParam); | 1264 | impl_from!(TypeParam, LifetimeParam, ConstParam for GenericParam); |
1265 | 1265 | ||
1266 | impl 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)] |
1267 | pub struct TypeParam { | 1285 | pub 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 | ||
6 | use hir_def::{ | 6 | use 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)] |
29 | pub struct Resolver { | 29 | pub 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 | ||
406 | pub fn record_pattern_missing_fields( | 406 | pub 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; | |||
5 | use hir::Semantics; | 5 | use hir::Semantics; |
6 | use ide_db::call_info::FnCallNode; | 6 | use ide_db::call_info::FnCallNode; |
7 | use ide_db::RootDatabase; | 7 | use ide_db::RootDatabase; |
8 | use syntax::{ast, match_ast, AstNode, TextRange}; | 8 | use syntax::{ast, AstNode, TextRange}; |
9 | 9 | ||
10 | use crate::{ | 10 | use 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 | ||
390 | impl 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 | |||
392 | impl ToNav for hir::Local { | 400 | impl 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 | ||
3 | use std::{convert::TryFrom, iter::once}; | 3 | use std::{convert::TryFrom, iter::once, ops::Range}; |
4 | 4 | ||
5 | use itertools::Itertools; | 5 | use itertools::Itertools; |
6 | use pulldown_cmark::{BrokenLink, CowStr, Event, InlineStr, LinkType, Options, Parser, Tag}; | 6 | use 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 | ||
64 | pub(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. |
65 | pub(crate) fn remove_links(markdown: &str) -> String { | 89 | pub(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 @@ | |||
1 | use either::Either; | 1 | use either::Either; |
2 | use hir::Semantics; | 2 | use hir::{HasAttrs, ModuleDef, Semantics}; |
3 | use ide_db::{ | 3 | use 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 | }; |
8 | use syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset, T}; | 8 | use syntax::{ |
9 | ast, match_ast, AstNode, AstToken, SyntaxKind::*, SyntaxToken, TextSize, TokenAtOffset, T, | ||
10 | }; | ||
9 | 11 | ||
10 | use crate::{ | 12 | use 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 | ||
79 | fn 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 | |||
109 | fn 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 | |||
71 | fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> { | 126 | fn 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) | ||
1209 | pub fn bar() { } | ||
1210 | |||
1211 | /// You might want to see [`std::fs::read()`] too. | ||
1212 | pub 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 | ||
1224 | mod m; | ||
1225 | struct 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 @@ | |||
1 | use hir::{ | 1 | use 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 | }; |
5 | use ide_db::base_db::SourceDatabase; | 5 | use ide_db::base_db::SourceDatabase; |
6 | use ide_db::{ | 6 | use 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 | ||
186 | fn runnable_action( | 181 | fn 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(¶m_begin); | 924 | different_order(¶m_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 | }; |
83 | pub use assists::{Assist, AssistConfig, AssistId, AssistKind, InsertUseConfig}; | 83 | pub 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::{ | |||
21 | use syntax::{ | 21 | use 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 | ||
27 | use crate::{display::TryToNav, FilePosition, FileRange, NavigationTarget, RangeInfo, SymbolKind}; | 27 | use 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::{ | |||
14 | use syntax::{ | 14 | use 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 | }; |
19 | use test_utils::mark; | 19 | use test_utils::mark; |
20 | use text_edit::TextEdit; | 20 | use text_edit::TextEdit; |
21 | 21 | ||
22 | use crate::{ | 22 | use 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 | ||
27 | type RenameResult<T> = Result<T, RenameError>; | ||
27 | #[derive(Debug)] | 28 | #[derive(Debug)] |
28 | pub struct RenameError(pub(crate) String); | 29 | pub struct RenameError(pub(crate) String); |
29 | 30 | ||
@@ -35,24 +36,30 @@ impl fmt::Display for RenameError { | |||
35 | 36 | ||
36 | impl Error for RenameError {} | 37 | impl Error for RenameError {} |
37 | 38 | ||
39 | macro_rules! format_err { | ||
40 | ($fmt:expr) => {RenameError(format!($fmt))}; | ||
41 | ($fmt:expr, $($arg:tt)+) => {RenameError(format!($fmt, $($arg)+))} | ||
42 | } | ||
43 | |||
44 | macro_rules! bail { | ||
45 | ($($tokens:tt)*) => {return Err(format_err!($($tokens)*))} | ||
46 | } | ||
47 | |||
38 | pub(crate) fn prepare_rename( | 48 | pub(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)] | ||
114 | enum IdentifierKind { | ||
115 | Ident, | ||
116 | Lifetime, | ||
117 | ToSelf, | ||
118 | Underscore, | ||
119 | } | ||
120 | |||
121 | fn 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 | |||
130 | fn find_module_at_offset( | 140 | fn 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 | ||
168 | fn 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 | |||
158 | fn source_edit_from_reference( | 176 | fn 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 |
946 | mod fo$0o; | 1006 | mod 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; | |||
995 | mod outer { mod fo$0o; } | 1055 | mod 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#" | ||
1637 | struct Foo; | ||
1638 | impl Foo { | ||
1639 | fn foo(self$0) {} | ||
1640 | } | ||
1641 | "#, | ||
1642 | r#" | ||
1643 | struct Foo; | ||
1644 | impl 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; | |||
3 | use assists::utils::test_related_attribute; | 3 | use assists::utils::test_related_attribute; |
4 | use cfg::CfgExpr; | 4 | use cfg::CfgExpr; |
5 | use hir::{AsAssocItem, HasAttrs, HasSource, Semantics}; | 5 | use hir::{AsAssocItem, HasAttrs, HasSource, Semantics}; |
6 | use ide_db::RootDatabase; | 6 | use ide_db::{defs::Definition, RootDatabase}; |
7 | use itertools::Itertools; | 7 | use itertools::Itertools; |
8 | use syntax::{ | 8 | use syntax::{ |
9 | ast::{self, AstNode, AttrsOwner, ModuleItemOwner}, | 9 | ast::{self, AstNode, AttrsOwner, ModuleItemOwner}, |
@@ -96,21 +96,26 @@ impl Runnable { | |||
96 | pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> { | 96 | pub(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() | |
102 | pub(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 | ||
116 | pub(crate) fn runnable_fn(sema: &Semantics<RootDatabase>, def: hir::Function) -> Option<Runnable> { | 121 | pub(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 | ||
148 | fn runnable_doctest(sema: &Semantics<RootDatabase>, item: SyntaxNode) -> Option<Runnable> { | 153 | pub(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. | ||
177 | pub(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 | ||
164 | fn module_def_doctest(sema: &Semantics<RootDatabase>, def: hir::ModuleDef) -> Option<Runnable> { | 198 | fn 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 | ||
256 | fn 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 |
278 | fn has_test_function_or_multiple_test_submodules(module: &ast::Module) -> bool { | 292 | fn 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 @@ | |||
1 | pub(crate) mod tags; | ||
2 | |||
3 | mod highlights; | ||
4 | mod injector; | ||
5 | |||
6 | mod highlight; | ||
1 | mod format; | 7 | mod format; |
2 | mod html; | ||
3 | mod injection; | ||
4 | mod macro_rules; | 8 | mod macro_rules; |
5 | pub(crate) mod tags; | 9 | mod inject; |
10 | |||
11 | mod html; | ||
6 | #[cfg(test)] | 12 | #[cfg(test)] |
7 | mod tests; | 13 | mod tests; |
8 | 14 | ||
9 | use hir::{AsAssocItem, Local, Name, Semantics, VariantDef}; | 15 | use hir::{Name, Semantics}; |
10 | use ide_db::{ | 16 | use ide_db::RootDatabase; |
11 | defs::{Definition, NameClass, NameRefClass}, | ||
12 | RootDatabase, | ||
13 | }; | ||
14 | use rustc_hash::FxHashMap; | 17 | use rustc_hash::FxHashMap; |
15 | use syntax::{ | 18 | use 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 | ||
22 | use crate::{ | 25 | use 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 | ||
29 | pub(crate) use html::highlight_as_html; | 33 | pub(crate) use html::highlight_as_html; |
30 | 34 | ||
31 | #[derive(Debug, Clone)] | 35 | #[derive(Debug, Clone, Copy)] |
32 | pub struct HighlightedRange { | 36 | pub 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 | |||
80 | fn 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)] | ||
240 | struct HighlightedRangeStack { | ||
241 | stack: Vec<Vec<HighlightedRange>>, | ||
242 | } | ||
243 | |||
244 | /// We use a stack to implement the flattening logic for the highlighted | ||
245 | /// syntax ranges. | ||
246 | impl 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 | ||
403 | fn macro_call_range(macro_call: &ast::MacroCall) -> Option<TextRange> { | 229 | fn 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. | ||
420 | fn 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 | |||
436 | fn 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 | |||
445 | fn 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 | |||
729 | fn is_child_of_impl(element: &SyntaxElement) -> bool { | ||
730 | match element.parent() { | ||
731 | Some(e) => e.kind() == IMPL, | ||
732 | _ => false, | ||
733 | } | ||
734 | } | ||
735 | |||
736 | fn 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 | |||
744 | fn 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 | |||
772 | fn 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 | |||
844 | fn 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 | |||
872 | fn 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. |
2 | use syntax::{ | 2 | use 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 | ||
7 | use crate::{ | 7 | use crate::{syntax_highlighting::highlights::Highlights, HlRange, HlTag, SymbolKind}; |
8 | syntax_highlighting::HighlightedRangeStack, HighlightTag, HighlightedRange, SymbolKind, | ||
9 | }; | ||
10 | |||
11 | #[derive(Default)] | ||
12 | pub(super) struct FormatStringHighlighter { | ||
13 | format_string: Option<SyntaxElement>, | ||
14 | } | ||
15 | 8 | ||
16 | impl FormatStringHighlighter { | 9 | pub(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 | |||
29 | fn 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 | ||
62 | fn highlight_format_specifier(kind: FormatSpecifier) -> Option<HighlightTag> { | 48 | fn 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 | <