diff options
-rw-r--r-- | crates/assists/src/handlers/add_turbo_fish.rs | 4 | ||||
-rw-r--r-- | crates/assists/src/handlers/auto_import.rs | 55 | ||||
-rw-r--r-- | crates/assists/src/handlers/expand_glob_import.rs | 4 | ||||
-rw-r--r-- | crates/hir_ty/src/infer.rs | 24 | ||||
-rw-r--r-- | crates/hir_ty/src/infer/expr.rs | 18 | ||||
-rw-r--r-- | crates/hir_ty/src/tests/simple.rs | 89 | ||||
-rw-r--r-- | crates/ide/src/diagnostics.rs | 95 | ||||
-rw-r--r-- | crates/ide/src/diagnostics/field_shorthand.rs | 206 | ||||
-rw-r--r-- | crates/ide/src/doc_links.rs | 6 | ||||
-rw-r--r-- | crates/ide/src/goto_definition.rs | 8 | ||||
-rw-r--r-- | crates/ide/src/hover.rs | 29 | ||||
-rw-r--r-- | crates/ide/src/references.rs | 6 | ||||
-rw-r--r-- | crates/ide/src/references/rename.rs | 6 | ||||
-rw-r--r-- | crates/ide/src/syntax_highlighting.rs | 224 | ||||
-rw-r--r-- | crates/ide/src/syntax_highlighting/format.rs | 78 | ||||
-rw-r--r-- | crates/ide/src/syntax_highlighting/macro_rules.rs | 129 | ||||
-rw-r--r-- | crates/ide_db/src/defs.rs | 377 | ||||
-rw-r--r-- | crates/ide_db/src/imports_locator.rs | 4 | ||||
-rw-r--r-- | crates/ide_db/src/search.rs | 12 | ||||
-rw-r--r-- | docs/dev/style.md | 105 | ||||
-rw-r--r-- | editors/code/src/inlay_hints.ts | 6 |
21 files changed, 971 insertions, 514 deletions
diff --git a/crates/assists/src/handlers/add_turbo_fish.rs b/crates/assists/src/handlers/add_turbo_fish.rs index f4f997d8e..e3d84d698 100644 --- a/crates/assists/src/handlers/add_turbo_fish.rs +++ b/crates/assists/src/handlers/add_turbo_fish.rs | |||
@@ -1,4 +1,4 @@ | |||
1 | use ide_db::defs::{classify_name_ref, Definition, NameRefClass}; | 1 | use ide_db::defs::{Definition, NameRefClass}; |
2 | use syntax::{ast, AstNode, SyntaxKind, T}; | 2 | use syntax::{ast, AstNode, SyntaxKind, T}; |
3 | use test_utils::mark; | 3 | use test_utils::mark; |
4 | 4 | ||
@@ -39,7 +39,7 @@ pub(crate) fn add_turbo_fish(acc: &mut Assists, ctx: &AssistContext) -> Option<( | |||
39 | return None; | 39 | return None; |
40 | } | 40 | } |
41 | let name_ref = ast::NameRef::cast(ident.parent())?; | 41 | let name_ref = ast::NameRef::cast(ident.parent())?; |
42 | let def = match classify_name_ref(&ctx.sema, &name_ref)? { | 42 | let def = match NameRefClass::classify(&ctx.sema, &name_ref)? { |
43 | NameRefClass::Definition(def) => def, | 43 | NameRefClass::Definition(def) => def, |
44 | NameRefClass::ExternCrate(_) | NameRefClass::FieldShorthand { .. } => return None, | 44 | NameRefClass::ExternCrate(_) | NameRefClass::FieldShorthand { .. } => return None, |
45 | }; | 45 | }; |
diff --git a/crates/assists/src/handlers/auto_import.rs b/crates/assists/src/handlers/auto_import.rs index e595b5b93..4a7059c83 100644 --- a/crates/assists/src/handlers/auto_import.rs +++ b/crates/assists/src/handlers/auto_import.rs | |||
@@ -6,6 +6,61 @@ use crate::{ | |||
6 | AssistContext, AssistId, AssistKind, Assists, GroupLabel, | 6 | AssistContext, AssistId, AssistKind, Assists, GroupLabel, |
7 | }; | 7 | }; |
8 | 8 | ||
9 | // Feature: Import Insertion | ||
10 | // | ||
11 | // Using the `auto-import` assist it is possible to insert missing imports for unresolved items. | ||
12 | // When inserting an import it will do so in a structured manner by keeping imports grouped, | ||
13 | // separated by a newline in the following order: | ||
14 | // | ||
15 | // - `std` and `core` | ||
16 | // - External Crates | ||
17 | // - Current Crate, paths prefixed by `crate` | ||
18 | // - Current Module, paths prefixed by `self` | ||
19 | // - Super Module, paths prefixed by `super` | ||
20 | // | ||
21 | // Example: | ||
22 | // ```rust | ||
23 | // use std::fs::File; | ||
24 | // | ||
25 | // use itertools::Itertools; | ||
26 | // use syntax::ast; | ||
27 | // | ||
28 | // use crate::utils::insert_use; | ||
29 | // | ||
30 | // use self::auto_import; | ||
31 | // | ||
32 | // use super::AssistContext; | ||
33 | // ``` | ||
34 | // | ||
35 | // .Merge Behaviour | ||
36 | // | ||
37 | // It is possible to configure how use-trees are merged with the `importMergeBehaviour` setting. | ||
38 | // It has the following configurations: | ||
39 | // | ||
40 | // - `full`: This setting will cause auto-import to always completely merge use-trees that share the | ||
41 | // same path prefix while also merging inner trees that share the same path-prefix. This kind of | ||
42 | // nesting is only supported in Rust versions later than 1.24. | ||
43 | // - `last`: This setting will cause auto-import to merge use-trees as long as the resulting tree | ||
44 | // will only contain a nesting of single segment paths at the very end. | ||
45 | // - `none`: This setting will cause auto-import to never merge use-trees keeping them as simple | ||
46 | // paths. | ||
47 | // | ||
48 | // In `VS Code` the configuration for this is `rust-analyzer.assist.importMergeBehaviour`. | ||
49 | // | ||
50 | // .Import Prefix | ||
51 | // | ||
52 | // The style of imports in the same crate is configurable through the `importPrefix` setting. | ||
53 | // It has the following configurations: | ||
54 | // | ||
55 | // - `by_crate`: This setting will force paths to be always absolute, starting with the `crate` | ||
56 | // prefix, unless the item is defined outside of the current crate. | ||
57 | // - `by_self`: This setting will force paths that are relative to the current module to always | ||
58 | // start with `self`. This will result in paths that always start with either `crate`, `self`, | ||
59 | // `super` or an extern crate identifier. | ||
60 | // - `plain`: This setting does not impose any restrictions in imports. | ||
61 | // | ||
62 | // In `VS Code` the configuration for this is `rust-analyzer.assist.importPrefix`. | ||
63 | |||
9 | // Assist: auto_import | 64 | // Assist: auto_import |
10 | // | 65 | // |
11 | // If the name is unresolved, provides all possible imports for it. | 66 | // If the name is unresolved, provides all possible imports for it. |
diff --git a/crates/assists/src/handlers/expand_glob_import.rs b/crates/assists/src/handlers/expand_glob_import.rs index d1adff972..316a58d88 100644 --- a/crates/assists/src/handlers/expand_glob_import.rs +++ b/crates/assists/src/handlers/expand_glob_import.rs | |||
@@ -1,7 +1,7 @@ | |||
1 | use either::Either; | 1 | use either::Either; |
2 | use hir::{AssocItem, MacroDef, Module, ModuleDef, Name, PathResolution, ScopeDef}; | 2 | use hir::{AssocItem, MacroDef, Module, ModuleDef, Name, PathResolution, ScopeDef}; |
3 | use ide_db::{ | 3 | use ide_db::{ |
4 | defs::{classify_name_ref, Definition, NameRefClass}, | 4 | defs::{Definition, NameRefClass}, |
5 | search::SearchScope, | 5 | search::SearchScope, |
6 | }; | 6 | }; |
7 | use syntax::{ | 7 | use syntax::{ |
@@ -217,7 +217,7 @@ fn find_imported_defs(ctx: &AssistContext, star: SyntaxToken) -> Option<Vec<Def> | |||
217 | .flatten() | 217 | .flatten() |
218 | .filter_map(|n| Some(n.descendants().filter_map(ast::NameRef::cast))) | 218 | .filter_map(|n| Some(n.descendants().filter_map(ast::NameRef::cast))) |
219 | .flatten() | 219 | .flatten() |
220 | .filter_map(|r| match classify_name_ref(&ctx.sema, &r)? { | 220 | .filter_map(|r| match NameRefClass::classify(&ctx.sema, &r)? { |
221 | NameRefClass::Definition(Definition::ModuleDef(def)) => Some(Def::ModuleDef(def)), | 221 | NameRefClass::Definition(Definition::ModuleDef(def)) => Some(Def::ModuleDef(def)), |
222 | NameRefClass::Definition(Definition::Macro(def)) => Some(Def::MacroDef(def)), | 222 | NameRefClass::Definition(Definition::Macro(def)) => Some(Def::MacroDef(def)), |
223 | _ => None, | 223 | _ => None, |
diff --git a/crates/hir_ty/src/infer.rs b/crates/hir_ty/src/infer.rs index 9a7785c76..644ebd42d 100644 --- a/crates/hir_ty/src/infer.rs +++ b/crates/hir_ty/src/infer.rs | |||
@@ -22,7 +22,7 @@ use arena::map::ArenaMap; | |||
22 | use hir_def::{ | 22 | use hir_def::{ |
23 | body::Body, | 23 | body::Body, |
24 | data::{ConstData, FunctionData, StaticData}, | 24 | data::{ConstData, FunctionData, StaticData}, |
25 | expr::{BindingAnnotation, ExprId, PatId}, | 25 | expr::{ArithOp, BinaryOp, BindingAnnotation, ExprId, PatId}, |
26 | lang_item::LangItemTarget, | 26 | lang_item::LangItemTarget, |
27 | path::{path, Path}, | 27 | path::{path, Path}, |
28 | resolver::{HasResolver, Resolver, TypeNs}, | 28 | resolver::{HasResolver, Resolver, TypeNs}, |
@@ -586,6 +586,28 @@ impl<'a> InferenceContext<'a> { | |||
586 | self.db.trait_data(trait_).associated_type_by_name(&name![Output]) | 586 | self.db.trait_data(trait_).associated_type_by_name(&name![Output]) |
587 | } | 587 | } |
588 | 588 | ||
589 | fn resolve_binary_op_output(&self, bop: &BinaryOp) -> Option<TypeAliasId> { | ||
590 | let lang_item = match bop { | ||
591 | BinaryOp::ArithOp(aop) => match aop { | ||
592 | ArithOp::Add => "add", | ||
593 | ArithOp::Sub => "sub", | ||
594 | ArithOp::Mul => "mul", | ||
595 | ArithOp::Div => "div", | ||
596 | ArithOp::Shl => "shl", | ||
597 | ArithOp::Shr => "shr", | ||
598 | ArithOp::Rem => "rem", | ||
599 | ArithOp::BitXor => "bitxor", | ||
600 | ArithOp::BitOr => "bitor", | ||
601 | ArithOp::BitAnd => "bitand", | ||
602 | }, | ||
603 | _ => return None, | ||
604 | }; | ||
605 | |||
606 | let trait_ = self.resolve_lang_item(lang_item)?.as_trait(); | ||
607 | |||
608 | self.db.trait_data(trait_?).associated_type_by_name(&name![Output]) | ||
609 | } | ||
610 | |||
589 | fn resolve_boxed_box(&self) -> Option<AdtId> { | 611 | fn resolve_boxed_box(&self) -> Option<AdtId> { |
590 | let struct_ = self.resolve_lang_item("owned_box")?.as_struct()?; | 612 | let struct_ = self.resolve_lang_item("owned_box")?.as_struct()?; |
591 | Some(struct_.into()) | 613 | Some(struct_.into()) |
diff --git a/crates/hir_ty/src/infer/expr.rs b/crates/hir_ty/src/infer/expr.rs index 0a141b9cb..8ac4cf89a 100644 --- a/crates/hir_ty/src/infer/expr.rs +++ b/crates/hir_ty/src/infer/expr.rs | |||
@@ -12,6 +12,7 @@ use hir_def::{ | |||
12 | }; | 12 | }; |
13 | use hir_expand::name::{name, Name}; | 13 | use hir_expand::name::{name, Name}; |
14 | use syntax::ast::RangeOp; | 14 | use syntax::ast::RangeOp; |
15 | use test_utils::mark; | ||
15 | 16 | ||
16 | use crate::{ | 17 | use crate::{ |
17 | autoderef, method_resolution, op, | 18 | autoderef, method_resolution, op, |
@@ -531,13 +532,22 @@ impl<'a> InferenceContext<'a> { | |||
531 | _ => Expectation::none(), | 532 | _ => Expectation::none(), |
532 | }; | 533 | }; |
533 | let lhs_ty = self.infer_expr(*lhs, &lhs_expectation); | 534 | let lhs_ty = self.infer_expr(*lhs, &lhs_expectation); |
534 | // FIXME: find implementation of trait corresponding to operation | ||
535 | // symbol and resolve associated `Output` type | ||
536 | let rhs_expectation = op::binary_op_rhs_expectation(*op, lhs_ty.clone()); | 535 | let rhs_expectation = op::binary_op_rhs_expectation(*op, lhs_ty.clone()); |
537 | let rhs_ty = self.infer_expr(*rhs, &Expectation::has_type(rhs_expectation)); | 536 | let rhs_ty = self.infer_expr(*rhs, &Expectation::has_type(rhs_expectation)); |
538 | 537 | ||
539 | // FIXME: similar as above, return ty is often associated trait type | 538 | let ret = op::binary_op_return_ty(*op, lhs_ty.clone(), rhs_ty.clone()); |
540 | op::binary_op_return_ty(*op, lhs_ty, rhs_ty) | 539 | |
540 | if ret == Ty::Unknown { | ||
541 | mark::hit!(infer_expr_inner_binary_operator_overload); | ||
542 | |||
543 | self.resolve_associated_type_with_params( | ||
544 | lhs_ty, | ||
545 | self.resolve_binary_op_output(op), | ||
546 | &[rhs_ty], | ||
547 | ) | ||
548 | } else { | ||
549 | ret | ||
550 | } | ||
541 | } | 551 | } |
542 | _ => Ty::Unknown, | 552 | _ => Ty::Unknown, |
543 | }, | 553 | }, |
diff --git a/crates/hir_ty/src/tests/simple.rs b/crates/hir_ty/src/tests/simple.rs index 5b07948f3..4f72582b6 100644 --- a/crates/hir_ty/src/tests/simple.rs +++ b/crates/hir_ty/src/tests/simple.rs | |||
@@ -1,4 +1,5 @@ | |||
1 | use expect_test::expect; | 1 | use expect_test::expect; |
2 | use test_utils::mark; | ||
2 | 3 | ||
3 | use super::{check_infer, check_types}; | 4 | use super::{check_infer, check_types}; |
4 | 5 | ||
@@ -2225,3 +2226,91 @@ fn generic_default_depending_on_other_type_arg_forward() { | |||
2225 | "#]], | 2226 | "#]], |
2226 | ); | 2227 | ); |
2227 | } | 2228 | } |
2229 | |||
2230 | #[test] | ||
2231 | fn infer_operator_overload() { | ||
2232 | mark::check!(infer_expr_inner_binary_operator_overload); | ||
2233 | |||
2234 | check_infer( | ||
2235 | r#" | ||
2236 | struct V2([f32; 2]); | ||
2237 | |||
2238 | #[lang = "add"] | ||
2239 | pub trait Add<Rhs = Self> { | ||
2240 | /// The resulting type after applying the `+` operator. | ||
2241 | type Output; | ||
2242 | |||
2243 | /// Performs the `+` operation. | ||
2244 | #[must_use] | ||
2245 | fn add(self, rhs: Rhs) -> Self::Output; | ||
2246 | } | ||
2247 | |||
2248 | impl Add<V2> for V2 { | ||
2249 | type Output = V2; | ||
2250 | |||
2251 | fn add(self, rhs: V2) -> V2 { | ||
2252 | let x = self.0[0] + rhs.0[0]; | ||
2253 | let y = self.0[1] + rhs.0[1]; | ||
2254 | V2([x, y]) | ||
2255 | } | ||
2256 | } | ||
2257 | |||
2258 | fn test() { | ||
2259 | let va = V2([0.0, 1.0]); | ||
2260 | let vb = V2([0.0, 1.0]); | ||
2261 | |||
2262 | let r = va + vb; | ||
2263 | } | ||
2264 | |||
2265 | "#, | ||
2266 | expect![[r#" | ||
2267 | 207..211 'self': Self | ||
2268 | 213..216 'rhs': Rhs | ||
2269 | 299..303 'self': V2 | ||
2270 | 305..308 'rhs': V2 | ||
2271 | 320..422 '{ ... }': V2 | ||
2272 | 334..335 'x': f32 | ||
2273 | 338..342 'self': V2 | ||
2274 | 338..344 'self.0': [f32; _] | ||
2275 | 338..347 'self.0[0]': {unknown} | ||
2276 | 338..358 'self.0...s.0[0]': f32 | ||
2277 | 345..346 '0': i32 | ||
2278 | 350..353 'rhs': V2 | ||
2279 | 350..355 'rhs.0': [f32; _] | ||
2280 | 350..358 'rhs.0[0]': {unknown} | ||
2281 | 356..357 '0': i32 | ||
2282 | 372..373 'y': f32 | ||
2283 | 376..380 'self': V2 | ||
2284 | 376..382 'self.0': [f32; _] | ||
2285 | 376..385 'self.0[1]': {unknown} | ||
2286 | 376..396 'self.0...s.0[1]': f32 | ||
2287 | 383..384 '1': i32 | ||
2288 | 388..391 'rhs': V2 | ||
2289 | 388..393 'rhs.0': [f32; _] | ||
2290 | 388..396 'rhs.0[1]': {unknown} | ||
2291 | 394..395 '1': i32 | ||
2292 | 406..408 'V2': V2([f32; _]) -> V2 | ||
2293 | 406..416 'V2([x, y])': V2 | ||
2294 | 409..415 '[x, y]': [f32; _] | ||
2295 | 410..411 'x': f32 | ||
2296 | 413..414 'y': f32 | ||
2297 | 436..519 '{ ... vb; }': () | ||
2298 | 446..448 'va': V2 | ||
2299 | 451..453 'V2': V2([f32; _]) -> V2 | ||
2300 | 451..465 'V2([0.0, 1.0])': V2 | ||
2301 | 454..464 '[0.0, 1.0]': [f32; _] | ||
2302 | 455..458 '0.0': f32 | ||
2303 | 460..463 '1.0': f32 | ||
2304 | 475..477 'vb': V2 | ||
2305 | 480..482 'V2': V2([f32; _]) -> V2 | ||
2306 | 480..494 'V2([0.0, 1.0])': V2 | ||
2307 | 483..493 '[0.0, 1.0]': [f32; _] | ||
2308 | 484..487 '0.0': f32 | ||
2309 | 489..492 '1.0': f32 | ||
2310 | 505..506 'r': V2 | ||
2311 | 509..511 'va': V2 | ||
2312 | 509..516 'va + vb': V2 | ||
2313 | 514..516 'vb': V2 | ||
2314 | "#]], | ||
2315 | ); | ||
2316 | } | ||
diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs index b30cdb6ed..1e5ea4617 100644 --- a/crates/ide/src/diagnostics.rs +++ b/crates/ide/src/diagnostics.rs | |||
@@ -5,6 +5,7 @@ | |||
5 | //! original files. So we need to map the ranges. | 5 | //! original files. So we need to map the ranges. |
6 | 6 | ||
7 | mod fixes; | 7 | mod fixes; |
8 | mod field_shorthand; | ||
8 | 9 | ||
9 | use std::cell::RefCell; | 10 | use std::cell::RefCell; |
10 | 11 | ||
@@ -80,7 +81,7 @@ pub(crate) fn diagnostics( | |||
80 | 81 | ||
81 | for node in parse.tree().syntax().descendants() { | 82 | for node in parse.tree().syntax().descendants() { |
82 | check_unnecessary_braces_in_use_statement(&mut res, file_id, &node); | 83 | check_unnecessary_braces_in_use_statement(&mut res, file_id, &node); |
83 | check_struct_shorthand_initialization(&mut res, file_id, &node); | 84 | field_shorthand::check(&mut res, file_id, &node); |
84 | } | 85 | } |
85 | let res = RefCell::new(res); | 86 | let res = RefCell::new(res); |
86 | let sink_builder = DiagnosticSinkBuilder::new() | 87 | let sink_builder = DiagnosticSinkBuilder::new() |
@@ -188,42 +189,6 @@ fn text_edit_for_remove_unnecessary_braces_with_self_in_use_statement( | |||
188 | None | 189 | None |
189 | } | 190 | } |
190 | 191 | ||
191 | fn check_struct_shorthand_initialization( | ||
192 | acc: &mut Vec<Diagnostic>, | ||
193 | file_id: FileId, | ||
194 | node: &SyntaxNode, | ||
195 | ) -> Option<()> { | ||
196 | let record_lit = ast::RecordExpr::cast(node.clone())?; | ||
197 | let record_field_list = record_lit.record_expr_field_list()?; | ||
198 | for record_field in record_field_list.fields() { | ||
199 | if let (Some(name_ref), Some(expr)) = (record_field.name_ref(), record_field.expr()) { | ||
200 | let field_name = name_ref.syntax().text().to_string(); | ||
201 | let field_expr = expr.syntax().text().to_string(); | ||
202 | let field_name_is_tup_index = name_ref.as_tuple_field().is_some(); | ||
203 | if field_name == field_expr && !field_name_is_tup_index { | ||
204 | let mut edit_builder = TextEdit::builder(); | ||
205 | edit_builder.delete(record_field.syntax().text_range()); | ||
206 | edit_builder.insert(record_field.syntax().text_range().start(), field_name); | ||
207 | let edit = edit_builder.finish(); | ||
208 | |||
209 | let field_range = record_field.syntax().text_range(); | ||
210 | acc.push(Diagnostic { | ||
211 | // name: None, | ||
212 | range: field_range, | ||
213 | message: "Shorthand struct initialization".to_string(), | ||
214 | severity: Severity::WeakWarning, | ||
215 | fix: Some(Fix::new( | ||
216 | "Use struct shorthand initialization", | ||
217 | SourceFileEdit { file_id, edit }.into(), | ||
218 | field_range, | ||
219 | )), | ||
220 | }); | ||
221 | } | ||
222 | } | ||
223 | } | ||
224 | Some(()) | ||
225 | } | ||
226 | |||
227 | #[cfg(test)] | 192 | #[cfg(test)] |
228 | mod tests { | 193 | mod tests { |
229 | use expect_test::{expect, Expect}; | 194 | use expect_test::{expect, Expect}; |
@@ -237,7 +202,7 @@ mod tests { | |||
237 | /// * a diagnostic is produced | 202 | /// * a diagnostic is produced |
238 | /// * this diagnostic fix trigger range touches the input cursor position | 203 | /// * this diagnostic fix trigger range touches the input cursor position |
239 | /// * that the contents of the file containing the cursor match `after` after the diagnostic fix is applied | 204 | /// * that the contents of the file containing the cursor match `after` after the diagnostic fix is applied |
240 | fn check_fix(ra_fixture_before: &str, ra_fixture_after: &str) { | 205 | pub(super) fn check_fix(ra_fixture_before: &str, ra_fixture_after: &str) { |
241 | let after = trim_indent(ra_fixture_after); | 206 | let after = trim_indent(ra_fixture_after); |
242 | 207 | ||
243 | let (analysis, file_position) = fixture::position(ra_fixture_before); | 208 | let (analysis, file_position) = fixture::position(ra_fixture_before); |
@@ -319,7 +284,7 @@ mod tests { | |||
319 | 284 | ||
320 | /// Takes a multi-file input fixture with annotated cursor position and checks that no diagnostics | 285 | /// Takes a multi-file input fixture with annotated cursor position and checks that no diagnostics |
321 | /// apply to the file containing the cursor. | 286 | /// apply to the file containing the cursor. |
322 | fn check_no_diagnostics(ra_fixture: &str) { | 287 | pub(crate) fn check_no_diagnostics(ra_fixture: &str) { |
323 | let (analysis, files) = fixture::files(ra_fixture); | 288 | let (analysis, files) = fixture::files(ra_fixture); |
324 | let diagnostics = files | 289 | let diagnostics = files |
325 | .into_iter() | 290 | .into_iter() |
@@ -720,58 +685,6 @@ mod a { | |||
720 | } | 685 | } |
721 | 686 | ||
722 | #[test] | 687 | #[test] |
723 | fn test_check_struct_shorthand_initialization() { | ||
724 | check_no_diagnostics( | ||
725 | r#" | ||
726 | struct A { a: &'static str } | ||
727 | fn main() { A { a: "hello" } } | ||
728 | "#, | ||
729 | ); | ||
730 | check_no_diagnostics( | ||
731 | r#" | ||
732 | struct A(usize); | ||
733 | fn main() { A { 0: 0 } } | ||
734 | "#, | ||
735 | ); | ||
736 | |||
737 | check_fix( | ||
738 | r#" | ||
739 | struct A { a: &'static str } | ||
740 | fn main() { | ||
741 | let a = "haha"; | ||
742 | A { a<|>: a } | ||
743 | } | ||
744 | "#, | ||
745 | r#" | ||
746 | struct A { a: &'static str } | ||
747 | fn main() { | ||
748 | let a = "haha"; | ||
749 | A { a } | ||
750 | } | ||
751 | "#, | ||
752 | ); | ||
753 | |||
754 | check_fix( | ||
755 | r#" | ||
756 | struct A { a: &'static str, b: &'static str } | ||
757 | fn main() { | ||
758 | let a = "haha"; | ||
759 | let b = "bb"; | ||
760 | A { a<|>: a, b } | ||
761 | } | ||
762 | "#, | ||
763 | r#" | ||
764 | struct A { a: &'static str, b: &'static str } | ||
765 | fn main() { | ||
766 | let a = "haha"; | ||
767 | let b = "bb"; | ||
768 | A { a, b } | ||
769 | } | ||
770 | "#, | ||
771 | ); | ||
772 | } | ||
773 | |||
774 | #[test] | ||
775 | fn test_add_field_from_usage() { | 688 | fn test_add_field_from_usage() { |
776 | check_fix( | 689 | check_fix( |
777 | r" | 690 | r" |
diff --git a/crates/ide/src/diagnostics/field_shorthand.rs b/crates/ide/src/diagnostics/field_shorthand.rs new file mode 100644 index 000000000..2c4acd783 --- /dev/null +++ b/crates/ide/src/diagnostics/field_shorthand.rs | |||
@@ -0,0 +1,206 @@ | |||
1 | //! Suggests shortening `Foo { field: field }` to `Foo { field }` in both | ||
2 | //! expressions and patterns. | ||
3 | |||
4 | use base_db::FileId; | ||
5 | use ide_db::source_change::SourceFileEdit; | ||
6 | use syntax::{ast, match_ast, AstNode, SyntaxNode}; | ||
7 | use text_edit::TextEdit; | ||
8 | |||
9 | use crate::{Diagnostic, Fix, Severity}; | ||
10 | |||
11 | pub(super) fn check(acc: &mut Vec<Diagnostic>, file_id: FileId, node: &SyntaxNode) { | ||
12 | match_ast! { | ||
13 | match node { | ||
14 | ast::RecordExpr(it) => check_expr_field_shorthand(acc, file_id, it), | ||
15 | ast::RecordPat(it) => check_pat_field_shorthand(acc, file_id, it), | ||
16 | _ => () | ||
17 | } | ||
18 | }; | ||
19 | } | ||
20 | |||
21 | fn check_expr_field_shorthand( | ||
22 | acc: &mut Vec<Diagnostic>, | ||
23 | file_id: FileId, | ||
24 | record_expr: ast::RecordExpr, | ||
25 | ) { | ||
26 | let record_field_list = match record_expr.record_expr_field_list() { | ||
27 | Some(it) => it, | ||
28 | None => return, | ||
29 | }; | ||
30 | for record_field in record_field_list.fields() { | ||
31 | let (name_ref, expr) = match record_field.name_ref().zip(record_field.expr()) { | ||
32 | Some(it) => it, | ||
33 | None => continue, | ||
34 | }; | ||
35 | |||
36 | let field_name = name_ref.syntax().text().to_string(); | ||
37 | let field_expr = expr.syntax().text().to_string(); | ||
38 | let field_name_is_tup_index = name_ref.as_tuple_field().is_some(); | ||
39 | if field_name != field_expr || field_name_is_tup_index { | ||
40 | continue; | ||
41 | } | ||
42 | |||
43 | let mut edit_builder = TextEdit::builder(); | ||
44 | edit_builder.delete(record_field.syntax().text_range()); | ||
45 | edit_builder.insert(record_field.syntax().text_range().start(), field_name); | ||
46 | let edit = edit_builder.finish(); | ||
47 | |||
48 | let field_range = record_field.syntax().text_range(); | ||
49 | acc.push(Diagnostic { | ||
50 | // name: None, | ||
51 | range: field_range, | ||
52 | message: "Shorthand struct initialization".to_string(), | ||
53 | severity: Severity::WeakWarning, | ||
54 | fix: Some(Fix::new( | ||
55 | "Use struct shorthand initialization", | ||
56 | SourceFileEdit { file_id, edit }.into(), | ||
57 | field_range, | ||
58 | )), | ||
59 | }); | ||
60 | } | ||
61 | } | ||
62 | |||
63 | fn check_pat_field_shorthand( | ||
64 | acc: &mut Vec<Diagnostic>, | ||
65 | file_id: FileId, | ||
66 | record_pat: ast::RecordPat, | ||
67 | ) { | ||
68 | let record_pat_field_list = match record_pat.record_pat_field_list() { | ||
69 | Some(it) => it, | ||
70 | None => return, | ||
71 | }; | ||
72 | for record_pat_field in record_pat_field_list.fields() { | ||
73 | let (name_ref, pat) = match record_pat_field.name_ref().zip(record_pat_field.pat()) { | ||
74 | Some(it) => it, | ||
75 | None => continue, | ||
76 | }; | ||
77 | |||
78 | let field_name = name_ref.syntax().text().to_string(); | ||
79 | let field_pat = pat.syntax().text().to_string(); | ||
80 | let field_name_is_tup_index = name_ref.as_tuple_field().is_some(); | ||
81 | if field_name != field_pat || field_name_is_tup_index { | ||
82 | continue; | ||
83 | } | ||
84 | |||
85 | let mut edit_builder = TextEdit::builder(); | ||
86 | edit_builder.delete(record_pat_field.syntax().text_range()); | ||
87 | edit_builder.insert(record_pat_field.syntax().text_range().start(), field_name); | ||
88 | let edit = edit_builder.finish(); | ||
89 | |||
90 | let field_range = record_pat_field.syntax().text_range(); | ||
91 | acc.push(Diagnostic { | ||
92 | // name: None, | ||
93 | range: field_range, | ||
94 | message: "Shorthand struct pattern".to_string(), | ||
95 | severity: Severity::WeakWarning, | ||
96 | fix: Some(Fix::new( | ||
97 | "Use struct field shorthand", | ||
98 | SourceFileEdit { file_id, edit }.into(), | ||
99 | field_range, | ||
100 | )), | ||
101 | }); | ||
102 | } | ||
103 | } | ||
104 | |||
105 | #[cfg(test)] | ||
106 | mod tests { | ||
107 | use crate::diagnostics::tests::{check_fix, check_no_diagnostics}; | ||
108 | |||
109 | #[test] | ||
110 | fn test_check_expr_field_shorthand() { | ||
111 | check_no_diagnostics( | ||
112 | r#" | ||
113 | struct A { a: &'static str } | ||
114 | fn main() { A { a: "hello" } } | ||
115 | "#, | ||
116 | ); | ||
117 | check_no_diagnostics( | ||
118 | r#" | ||
119 | struct A(usize); | ||
120 | fn main() { A { 0: 0 } } | ||
121 | "#, | ||
122 | ); | ||
123 | |||
124 | check_fix( | ||
125 | r#" | ||
126 | struct A { a: &'static str } | ||
127 | fn main() { | ||
128 | let a = "haha"; | ||
129 | A { a<|>: a } | ||
130 | } | ||
131 | "#, | ||
132 | r#" | ||
133 | struct A { a: &'static str } | ||
134 | fn main() { | ||
135 | let a = "haha"; | ||
136 | A { a } | ||
137 | } | ||
138 | "#, | ||
139 | ); | ||
140 | |||
141 | check_fix( | ||
142 | r#" | ||
143 | struct A { a: &'static str, b: &'static str } | ||
144 | fn main() { | ||
145 | let a = "haha"; | ||
146 | let b = "bb"; | ||
147 | A { a<|>: a, b } | ||
148 | } | ||
149 | "#, | ||
150 | r#" | ||
151 | struct A { a: &'static str, b: &'static str } | ||
152 | fn main() { | ||
153 | let a = "haha"; | ||
154 | let b = "bb"; | ||
155 | A { a, b } | ||
156 | } | ||
157 | "#, | ||
158 | ); | ||
159 | } | ||
160 | |||
161 | #[test] | ||
162 | fn test_check_pat_field_shorthand() { | ||
163 | check_no_diagnostics( | ||
164 | r#" | ||
165 | struct A { a: &'static str } | ||
166 | fn f(a: A) { let A { a: hello } = a; } | ||
167 | "#, | ||
168 | ); | ||
169 | check_no_diagnostics( | ||
170 | r#" | ||
171 | struct A(usize); | ||
172 | fn f(a: A) { let A { 0: 0 } = a; } | ||
173 | "#, | ||
174 | ); | ||
175 | |||
176 | check_fix( | ||
177 | r#" | ||
178 | struct A { a: &'static str } | ||
179 | fn f(a: A) { | ||
180 | let A { a<|>: a } = a; | ||
181 | } | ||
182 | "#, | ||
183 | r#" | ||
184 | struct A { a: &'static str } | ||
185 | fn f(a: A) { | ||
186 | let A { a } = a; | ||
187 | } | ||
188 | "#, | ||
189 | ); | ||
190 | |||
191 | check_fix( | ||
192 | r#" | ||
193 | struct A { a: &'static str, b: &'static str } | ||
194 | fn f(a: A) { | ||
195 | let A { a<|>: a, b } = a; | ||
196 | } | ||
197 | "#, | ||
198 | r#" | ||
199 | struct A { a: &'static str, b: &'static str } | ||
200 | fn f(a: A) { | ||
201 | let A { a, b } = a; | ||
202 | } | ||
203 | "#, | ||
204 | ); | ||
205 | } | ||
206 | } | ||
diff --git a/crates/ide/src/doc_links.rs b/crates/ide/src/doc_links.rs index db3f911c8..d9dc63b33 100644 --- a/crates/ide/src/doc_links.rs +++ b/crates/ide/src/doc_links.rs | |||
@@ -14,7 +14,7 @@ use hir::{ | |||
14 | ModuleDef, | 14 | ModuleDef, |
15 | }; | 15 | }; |
16 | use ide_db::{ | 16 | use ide_db::{ |
17 | defs::{classify_name, classify_name_ref, Definition}, | 17 | defs::{Definition, NameClass, NameRefClass}, |
18 | RootDatabase, | 18 | RootDatabase, |
19 | }; | 19 | }; |
20 | use syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset, T}; | 20 | use syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset, T}; |
@@ -232,8 +232,8 @@ pub(crate) fn external_docs( | |||
232 | let node = token.parent(); | 232 | let node = token.parent(); |
233 | let definition = match_ast! { | 233 | let definition = match_ast! { |
234 | match node { | 234 | match node { |
235 | ast::NameRef(name_ref) => classify_name_ref(&sema, &name_ref).map(|d| d.definition(sema.db)), | 235 | ast::NameRef(name_ref) => NameRefClass::classify(&sema, &name_ref).map(|d| d.referenced(sema.db)), |
236 | ast::Name(name) => classify_name(&sema, &name).map(|d| d.definition(sema.db)), | 236 | ast::Name(name) => NameClass::classify(&sema, &name).map(|d| d.referenced_or_defined(sema.db)), |
237 | _ => None, | 237 | _ => None, |
238 | } | 238 | } |
239 | }; | 239 | }; |
diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs index 582bf4837..a87e31019 100644 --- a/crates/ide/src/goto_definition.rs +++ b/crates/ide/src/goto_definition.rs | |||
@@ -1,6 +1,6 @@ | |||
1 | use hir::Semantics; | 1 | use hir::Semantics; |
2 | use ide_db::{ | 2 | use ide_db::{ |
3 | defs::{classify_name, classify_name_ref}, | 3 | defs::{NameClass, NameRefClass}, |
4 | symbol_index, RootDatabase, | 4 | symbol_index, RootDatabase, |
5 | }; | 5 | }; |
6 | use syntax::{ | 6 | use syntax::{ |
@@ -40,7 +40,7 @@ pub(crate) fn goto_definition( | |||
40 | reference_definition(&sema, &name_ref).to_vec() | 40 | reference_definition(&sema, &name_ref).to_vec() |
41 | }, | 41 | }, |
42 | ast::Name(name) => { | 42 | ast::Name(name) => { |
43 | let def = classify_name(&sema, &name)?.definition(sema.db); | 43 | let def = NameClass::classify(&sema, &name)?.referenced_or_defined(sema.db); |
44 | let nav = def.try_to_nav(sema.db)?; | 44 | let nav = def.try_to_nav(sema.db)?; |
45 | vec![nav] | 45 | vec![nav] |
46 | }, | 46 | }, |
@@ -81,9 +81,9 @@ pub(crate) fn reference_definition( | |||
81 | sema: &Semantics<RootDatabase>, | 81 | sema: &Semantics<RootDatabase>, |
82 | name_ref: &ast::NameRef, | 82 | name_ref: &ast::NameRef, |
83 | ) -> ReferenceResult { | 83 | ) -> ReferenceResult { |
84 | let name_kind = classify_name_ref(sema, name_ref); | 84 | let name_kind = NameRefClass::classify(sema, name_ref); |
85 | if let Some(def) = name_kind { | 85 | if let Some(def) = name_kind { |
86 | let def = def.definition(sema.db); | 86 | let def = def.referenced(sema.db); |
87 | return match def.try_to_nav(sema.db) { | 87 | return match def.try_to_nav(sema.db) { |
88 | Some(nav) => ReferenceResult::Exact(nav), | 88 | Some(nav) => ReferenceResult::Exact(nav), |
89 | None => ReferenceResult::Approximate(Vec::new()), | 89 | None => ReferenceResult::Approximate(Vec::new()), |
diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs index 6290b35bd..845333e2a 100644 --- a/crates/ide/src/hover.rs +++ b/crates/ide/src/hover.rs | |||
@@ -4,7 +4,7 @@ use hir::{ | |||
4 | Module, ModuleDef, ModuleSource, Semantics, | 4 | Module, ModuleDef, ModuleSource, Semantics, |
5 | }; | 5 | }; |
6 | use ide_db::{ | 6 | use ide_db::{ |
7 | defs::{classify_name, classify_name_ref, Definition}, | 7 | defs::{Definition, NameClass, NameRefClass}, |
8 | RootDatabase, | 8 | RootDatabase, |
9 | }; | 9 | }; |
10 | use itertools::Itertools; | 10 | use itertools::Itertools; |
@@ -107,8 +107,8 @@ pub(crate) fn hover( | |||
107 | let node = token.parent(); | 107 | let node = token.parent(); |
108 | let definition = match_ast! { | 108 | let definition = match_ast! { |
109 | match node { | 109 | match node { |
110 | ast::NameRef(name_ref) => classify_name_ref(&sema, &name_ref).map(|d| d.definition(sema.db)), | 110 | ast::Name(name) => NameClass::classify(&sema, &name).and_then(|d| d.defined(sema.db)), |
111 | ast::Name(name) => classify_name(&sema, &name).map(|d| d.definition(sema.db)), | 111 | ast::NameRef(name_ref) => NameRefClass::classify(&sema, &name_ref).map(|d| d.referenced(sema.db)), |
112 | _ => None, | 112 | _ => None, |
113 | } | 113 | } |
114 | }; | 114 | }; |
@@ -3232,4 +3232,27 @@ fn main() { let foo_test = name_with_dashes::wrapper::Thing::new<|>(); } | |||
3232 | "#]], | 3232 | "#]], |
3233 | ) | 3233 | ) |
3234 | } | 3234 | } |
3235 | |||
3236 | #[test] | ||
3237 | fn hover_field_pat_shorthand_ref_match_ergonomics() { | ||
3238 | check( | ||
3239 | r#" | ||
3240 | struct S { | ||
3241 | f: i32, | ||
3242 | } | ||
3243 | |||
3244 | fn main() { | ||
3245 | let s = S { f: 0 }; | ||
3246 | let S { f<|> } = &s; | ||
3247 | } | ||
3248 | "#, | ||
3249 | expect![[r#" | ||
3250 | *f* | ||
3251 | |||
3252 | ```rust | ||
3253 | &i32 | ||
3254 | ``` | ||
3255 | "#]], | ||
3256 | ); | ||
3257 | } | ||
3235 | } | 3258 | } |
diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs index 88e2f2db3..67ec257a8 100644 --- a/crates/ide/src/references.rs +++ b/crates/ide/src/references.rs | |||
@@ -13,7 +13,7 @@ pub(crate) mod rename; | |||
13 | 13 | ||
14 | use hir::Semantics; | 14 | use hir::Semantics; |
15 | use ide_db::{ | 15 | use ide_db::{ |
16 | defs::{classify_name, classify_name_ref, Definition}, | 16 | defs::{Definition, NameClass, NameRefClass}, |
17 | search::SearchScope, | 17 | search::SearchScope, |
18 | RootDatabase, | 18 | RootDatabase, |
19 | }; | 19 | }; |
@@ -132,13 +132,13 @@ fn find_name( | |||
132 | opt_name: Option<ast::Name>, | 132 | opt_name: Option<ast::Name>, |
133 | ) -> Option<RangeInfo<Definition>> { | 133 | ) -> Option<RangeInfo<Definition>> { |
134 | if let Some(name) = opt_name { | 134 | if let Some(name) = opt_name { |
135 | let def = classify_name(sema, &name)?.definition(sema.db); | 135 | let def = NameClass::classify(sema, &name)?.referenced_or_defined(sema.db); |
136 | let range = name.syntax().text_range(); | 136 | let range = name.syntax().text_range(); |
137 | return Some(RangeInfo::new(range, def)); | 137 | return Some(RangeInfo::new(range, def)); |
138 | } | 138 | } |
139 | let name_ref = | 139 | let name_ref = |
140 | sema.find_node_at_offset_with_descend::<ast::NameRef>(&syntax, position.offset)?; | 140 | sema.find_node_at_offset_with_descend::<ast::NameRef>(&syntax, position.offset)?; |
141 | let def = classify_name_ref(sema, &name_ref)?.definition(sema.db); | 141 | let def = NameRefClass::classify(sema, &name_ref)?.referenced(sema.db); |
142 | let range = name_ref.syntax().text_range(); | 142 | let range = name_ref.syntax().text_range(); |
143 | Some(RangeInfo::new(range, def)) | 143 | Some(RangeInfo::new(range, def)) |
144 | } | 144 | } |
diff --git a/crates/ide/src/references/rename.rs b/crates/ide/src/references/rename.rs index f9a11e43d..35aafc49d 100644 --- a/crates/ide/src/references/rename.rs +++ b/crates/ide/src/references/rename.rs | |||
@@ -3,7 +3,7 @@ | |||
3 | use base_db::SourceDatabaseExt; | 3 | use base_db::SourceDatabaseExt; |
4 | use hir::{Module, ModuleDef, ModuleSource, Semantics}; | 4 | use hir::{Module, ModuleDef, ModuleSource, Semantics}; |
5 | use ide_db::{ | 5 | use ide_db::{ |
6 | defs::{classify_name, classify_name_ref, Definition, NameClass, NameRefClass}, | 6 | defs::{Definition, NameClass, NameRefClass}, |
7 | RootDatabase, | 7 | RootDatabase, |
8 | }; | 8 | }; |
9 | 9 | ||
@@ -88,13 +88,13 @@ fn find_module_at_offset( | |||
88 | let module = match_ast! { | 88 | let module = match_ast! { |
89 | match (ident.parent()) { | 89 | match (ident.parent()) { |
90 | ast::NameRef(name_ref) => { | 90 | ast::NameRef(name_ref) => { |
91 | match classify_name_ref(sema, &name_ref)? { | 91 | match NameRefClass::classify(sema, &name_ref)? { |
92 | NameRefClass::Definition(Definition::ModuleDef(ModuleDef::Module(module))) => module, | 92 | NameRefClass::Definition(Definition::ModuleDef(ModuleDef::Module(module))) => module, |
93 | _ => return None, | 93 | _ => return None, |
94 | } | 94 | } |
95 | }, | 95 | }, |
96 | ast::Name(name) => { | 96 | ast::Name(name) => { |
97 | match classify_name(&sema, &name)? { | 97 | match NameClass::classify(&sema, &name)? { |
98 | NameClass::Definition(Definition::ModuleDef(ModuleDef::Module(module))) => module, | 98 | NameClass::Definition(Definition::ModuleDef(ModuleDef::Module(module))) => module, |
99 | _ => return None, | 99 | _ => return None, |
100 | } | 100 | } |
diff --git a/crates/ide/src/syntax_highlighting.rs b/crates/ide/src/syntax_highlighting.rs index 6aafd6fd5..b35c03162 100644 --- a/crates/ide/src/syntax_highlighting.rs +++ b/crates/ide/src/syntax_highlighting.rs | |||
@@ -1,12 +1,14 @@ | |||
1 | mod tags; | 1 | mod format; |
2 | mod html; | 2 | mod html; |
3 | mod injection; | 3 | mod injection; |
4 | mod macro_rules; | ||
5 | mod tags; | ||
4 | #[cfg(test)] | 6 | #[cfg(test)] |
5 | mod tests; | 7 | mod tests; |
6 | 8 | ||
7 | use hir::{Local, Name, Semantics, VariantDef}; | 9 | use hir::{Local, Name, Semantics, VariantDef}; |
8 | use ide_db::{ | 10 | use ide_db::{ |
9 | defs::{classify_name, classify_name_ref, Definition, NameClass, NameRefClass}, | 11 | defs::{Definition, NameClass, NameRefClass}, |
10 | RootDatabase, | 12 | RootDatabase, |
11 | }; | 13 | }; |
12 | use rustc_hash::FxHashMap; | 14 | use rustc_hash::FxHashMap; |
@@ -17,9 +19,11 @@ use syntax::{ | |||
17 | SyntaxNode, SyntaxToken, TextRange, WalkEvent, T, | 19 | SyntaxNode, SyntaxToken, TextRange, WalkEvent, T, |
18 | }; | 20 | }; |
19 | 21 | ||
20 | use crate::FileId; | 22 | use crate::{ |
23 | syntax_highlighting::{format::FormatStringHighlighter, macro_rules::MacroRulesHighlighter}, | ||
24 | FileId, | ||
25 | }; | ||
21 | 26 | ||
22 | use ast::FormatSpecifier; | ||
23 | pub(crate) use html::highlight_as_html; | 27 | pub(crate) use html::highlight_as_html; |
24 | pub use tags::{Highlight, HighlightModifier, HighlightModifiers, HighlightTag}; | 28 | pub use tags::{Highlight, HighlightModifier, HighlightModifiers, HighlightTag}; |
25 | 29 | ||
@@ -68,8 +72,9 @@ pub(crate) fn highlight( | |||
68 | // When we leave a node, the we use it to flatten the highlighted ranges. | 72 | // When we leave a node, the we use it to flatten the highlighted ranges. |
69 | let mut stack = HighlightedRangeStack::new(); | 73 | let mut stack = HighlightedRangeStack::new(); |
70 | 74 | ||
71 | let mut current_macro_call: Option<(ast::MacroCall, Option<MacroMatcherParseState>)> = None; | 75 | let mut current_macro_call: Option<ast::MacroCall> = None; |
72 | let mut format_string: Option<SyntaxElement> = None; | 76 | let mut format_string_highlighter = FormatStringHighlighter::default(); |
77 | let mut macro_rules_highlighter = MacroRulesHighlighter::default(); | ||
73 | 78 | ||
74 | // Walk all nodes, keeping track of whether we are inside a macro or not. | 79 | // Walk all nodes, keeping track of whether we are inside a macro or not. |
75 | // If in macro, expand it first and highlight the expanded code. | 80 | // If in macro, expand it first and highlight the expanded code. |
@@ -99,9 +104,8 @@ pub(crate) fn highlight( | |||
99 | binding_hash: None, | 104 | binding_hash: None, |
100 | }); | 105 | }); |
101 | } | 106 | } |
102 | let mut is_macro_rules = None; | ||
103 | if let Some(name) = mc.is_macro_rules() { | 107 | if let Some(name) = mc.is_macro_rules() { |
104 | is_macro_rules = Some(MacroMatcherParseState::new()); | 108 | macro_rules_highlighter.init(); |
105 | if let Some((highlight, binding_hash)) = highlight_element( | 109 | if let Some((highlight, binding_hash)) = highlight_element( |
106 | &sema, | 110 | &sema, |
107 | &mut bindings_shadow_count, | 111 | &mut bindings_shadow_count, |
@@ -115,13 +119,14 @@ pub(crate) fn highlight( | |||
115 | }); | 119 | }); |
116 | } | 120 | } |
117 | } | 121 | } |
118 | current_macro_call = Some((mc.clone(), is_macro_rules)); | 122 | current_macro_call = Some(mc.clone()); |
119 | continue; | 123 | continue; |
120 | } | 124 | } |
121 | WalkEvent::Leave(Some(mc)) => { | 125 | WalkEvent::Leave(Some(mc)) => { |
122 | assert!(current_macro_call.map(|it| it.0) == Some(mc)); | 126 | assert!(current_macro_call == Some(mc)); |
123 | current_macro_call = None; | 127 | current_macro_call = None; |
124 | format_string = None; | 128 | format_string_highlighter = FormatStringHighlighter::default(); |
129 | macro_rules_highlighter = MacroRulesHighlighter::default(); | ||
125 | } | 130 | } |
126 | _ => (), | 131 | _ => (), |
127 | } | 132 | } |
@@ -148,20 +153,6 @@ pub(crate) fn highlight( | |||
148 | WalkEvent::Leave(_) => continue, | 153 | WalkEvent::Leave(_) => continue, |
149 | }; | 154 | }; |
150 | 155 | ||
151 | // check if in matcher part of a macro_rules rule | ||
152 | if let Some((_, Some(ref mut state))) = current_macro_call { | ||
153 | if let Some(tok) = element.as_token() { | ||
154 | if matches!( | ||
155 | update_macro_rules_state(tok, state), | ||
156 | RuleState::Matcher | RuleState::Expander | ||
157 | ) { | ||
158 | if skip_metavariables(element.clone()) { | ||
159 | continue; | ||
160 | } | ||
161 | } | ||
162 | } | ||
163 | } | ||
164 | |||
165 | let range = element.text_range(); | 156 | let range = element.text_range(); |
166 | 157 | ||
167 | let element_to_highlight = if current_macro_call.is_some() && element.kind() != COMMENT { | 158 | let element_to_highlight = if current_macro_call.is_some() && element.kind() != COMMENT { |
@@ -173,29 +164,9 @@ pub(crate) fn highlight( | |||
173 | let token = sema.descend_into_macros(token.clone()); | 164 | let token = sema.descend_into_macros(token.clone()); |
174 | let parent = token.parent(); | 165 | let parent = token.parent(); |
175 | 166 | ||
176 | // Check if macro takes a format string and remember it for highlighting later. | 167 | format_string_highlighter.check_for_format_string(&parent); |
177 | // The macros that accept a format string expand to a compiler builtin macros | 168 | if let Some(tok) = element.as_token() { |
178 | // `format_args` and `format_args_nl`. | 169 | macro_rules_highlighter.advance(tok); |
179 | if let Some(name) = parent | ||
180 | .parent() | ||
181 | .and_then(ast::MacroCall::cast) | ||
182 | .and_then(|mc| mc.path()) | ||
183 | .and_then(|p| p.segment()) | ||
184 | .and_then(|s| s.name_ref()) | ||
185 | { | ||
186 | match name.text().as_str() { | ||
187 | "format_args" | "format_args_nl" => { | ||
188 | format_string = parent | ||
189 | .children_with_tokens() | ||
190 | .filter(|t| t.kind() != WHITESPACE) | ||
191 | .nth(1) | ||
192 | .filter(|e| { | ||
193 | ast::String::can_cast(e.kind()) | ||
194 | || ast::RawString::can_cast(e.kind()) | ||
195 | }) | ||
196 | } | ||
197 | _ => {} | ||
198 | } | ||
199 | } | 170 | } |
200 | 171 | ||
201 | // We only care Name and Name_ref | 172 | // We only care Name and Name_ref |
@@ -214,31 +185,20 @@ pub(crate) fn highlight( | |||
214 | } | 185 | } |
215 | } | 186 | } |
216 | 187 | ||
217 | let is_format_string = format_string.as_ref() == Some(&element_to_highlight); | ||
218 | |||
219 | if let Some((highlight, binding_hash)) = highlight_element( | 188 | if let Some((highlight, binding_hash)) = highlight_element( |
220 | &sema, | 189 | &sema, |
221 | &mut bindings_shadow_count, | 190 | &mut bindings_shadow_count, |
222 | syntactic_name_ref_highlighting, | 191 | syntactic_name_ref_highlighting, |
223 | element_to_highlight.clone(), | 192 | element_to_highlight.clone(), |
224 | ) { | 193 | ) { |
225 | stack.add(HighlightedRange { range, highlight, binding_hash }); | 194 | if macro_rules_highlighter.highlight(element_to_highlight.clone()).is_none() { |
195 | stack.add(HighlightedRange { range, highlight, binding_hash }); | ||
196 | } | ||
197 | |||
226 | if let Some(string) = | 198 | if let Some(string) = |
227 | element_to_highlight.as_token().cloned().and_then(ast::String::cast) | 199 | element_to_highlight.as_token().cloned().and_then(ast::String::cast) |
228 | { | 200 | { |
229 | if is_format_string { | 201 | format_string_highlighter.highlight_format_string(&mut stack, &string, range); |
230 | stack.push(); | ||
231 | string.lex_format_specifier(|piece_range, kind| { | ||
232 | if let Some(highlight) = highlight_format_specifier(kind) { | ||
233 | stack.add(HighlightedRange { | ||
234 | range: piece_range + range.start(), | ||
235 | highlight: highlight.into(), | ||
236 | binding_hash: None, | ||
237 | }); | ||
238 | } | ||
239 | }); | ||
240 | stack.pop(); | ||
241 | } | ||
242 | // Highlight escape sequences | 202 | // Highlight escape sequences |
243 | if let Some(char_ranges) = string.char_ranges() { | 203 | if let Some(char_ranges) = string.char_ranges() { |
244 | stack.push(); | 204 | stack.push(); |
@@ -256,19 +216,7 @@ pub(crate) fn highlight( | |||
256 | } else if let Some(string) = | 216 | } else if let Some(string) = |
257 | element_to_highlight.as_token().cloned().and_then(ast::RawString::cast) | 217 | element_to_highlight.as_token().cloned().and_then(ast::RawString::cast) |
258 | { | 218 | { |
259 | if is_format_string { | 219 | format_string_highlighter.highlight_format_string(&mut stack, &string, range); |
260 | stack.push(); | ||
261 | string.lex_format_specifier(|piece_range, kind| { | ||
262 | if let Some(highlight) = highlight_format_specifier(kind) { | ||
263 | stack.add(HighlightedRange { | ||
264 | range: piece_range + range.start(), | ||
265 | highlight: highlight.into(), | ||
266 | binding_hash: None, | ||
267 | }); | ||
268 | } | ||
269 | }); | ||
270 | stack.pop(); | ||
271 | } | ||
272 | } | 220 | } |
273 | } | 221 | } |
274 | } | 222 | } |
@@ -436,24 +384,6 @@ impl HighlightedRangeStack { | |||
436 | } | 384 | } |
437 | } | 385 | } |
438 | 386 | ||
439 | fn highlight_format_specifier(kind: FormatSpecifier) -> Option<HighlightTag> { | ||
440 | Some(match kind { | ||
441 | FormatSpecifier::Open | ||
442 | | FormatSpecifier::Close | ||
443 | | FormatSpecifier::Colon | ||
444 | | FormatSpecifier::Fill | ||
445 | | FormatSpecifier::Align | ||
446 | | FormatSpecifier::Sign | ||
447 | | FormatSpecifier::NumberSign | ||
448 | | FormatSpecifier::DollarSign | ||
449 | | FormatSpecifier::Dot | ||
450 | | FormatSpecifier::Asterisk | ||
451 | | FormatSpecifier::QuestionMark => HighlightTag::FormatSpecifier, | ||
452 | FormatSpecifier::Integer | FormatSpecifier::Zero => HighlightTag::NumericLiteral, | ||
453 | FormatSpecifier::Identifier => HighlightTag::Local, | ||
454 | }) | ||
455 | } | ||
456 | |||
457 | fn macro_call_range(macro_call: &ast::MacroCall) -> Option<TextRange> { | 387 | fn macro_call_range(macro_call: &ast::MacroCall) -> Option<TextRange> { |
458 | let path = macro_call.path()?; | 388 | let path = macro_call.path()?; |
459 | let name_ref = path.segment()?.name_ref()?; | 389 | let name_ref = path.segment()?.name_ref()?; |
@@ -513,7 +443,7 @@ fn highlight_element( | |||
513 | // Highlight definitions depending on the "type" of the definition. | 443 | // Highlight definitions depending on the "type" of the definition. |
514 | NAME => { | 444 | NAME => { |
515 | let name = element.into_node().and_then(ast::Name::cast).unwrap(); | 445 | let name = element.into_node().and_then(ast::Name::cast).unwrap(); |
516 | let name_kind = classify_name(sema, &name); | 446 | let name_kind = NameClass::classify(sema, &name); |
517 | 447 | ||
518 | if let Some(NameClass::Definition(Definition::Local(local))) = &name_kind { | 448 | if let Some(NameClass::Definition(Definition::Local(local))) = &name_kind { |
519 | if let Some(name) = local.name(db) { | 449 | if let Some(name) = local.name(db) { |
@@ -529,9 +459,9 @@ fn highlight_element( | |||
529 | highlight_def(db, def) | HighlightModifier::Definition | 459 | highlight_def(db, def) | HighlightModifier::Definition |
530 | } | 460 | } |
531 | Some(NameClass::ConstReference(def)) => highlight_def(db, def), | 461 | Some(NameClass::ConstReference(def)) => highlight_def(db, def), |
532 | Some(NameClass::FieldShorthand { field, .. }) => { | 462 | Some(NameClass::PatFieldShorthand { field_ref, .. }) => { |
533 | let mut h = HighlightTag::Field.into(); | 463 | let mut h = HighlightTag::Field.into(); |
534 | if let Definition::Field(field) = field { | 464 | if let Definition::Field(field) = field_ref { |
535 | if let VariantDef::Union(_) = field.parent_def(db) { | 465 | if let VariantDef::Union(_) = field.parent_def(db) { |
536 | h |= HighlightModifier::Unsafe; | 466 | h |= HighlightModifier::Unsafe; |
537 | } | 467 | } |
@@ -550,7 +480,7 @@ fn highlight_element( | |||
550 | NAME_REF => { | 480 | NAME_REF => { |
551 | let name_ref = element.into_node().and_then(ast::NameRef::cast).unwrap(); | 481 | let name_ref = element.into_node().and_then(ast::NameRef::cast).unwrap(); |
552 | highlight_func_by_name_ref(sema, &name_ref).unwrap_or_else(|| { | 482 | highlight_func_by_name_ref(sema, &name_ref).unwrap_or_else(|| { |
553 | match classify_name_ref(sema, &name_ref) { | 483 | match NameRefClass::classify(sema, &name_ref) { |
554 | Some(name_kind) => match name_kind { | 484 | Some(name_kind) => match name_kind { |
555 | NameRefClass::ExternCrate(_) => HighlightTag::Module.into(), | 485 | NameRefClass::ExternCrate(_) => HighlightTag::Module.into(), |
556 | NameRefClass::Definition(def) => { | 486 | NameRefClass::Definition(def) => { |
@@ -934,99 +864,3 @@ fn highlight_name_ref_by_syntax(name: ast::NameRef, sema: &Semantics<RootDatabas | |||
934 | _ => default.into(), | 864 | _ => default.into(), |
935 | } | 865 | } |
936 | } | 866 | } |
937 | |||
938 | struct MacroMatcherParseState { | ||
939 | /// Opening and corresponding closing bracket of the matcher or expander of the current rule | ||
940 | paren_ty: Option<(SyntaxKind, SyntaxKind)>, | ||
941 | paren_level: usize, | ||
942 | rule_state: RuleState, | ||
943 | /// Whether we are inside the outer `{` `}` macro block that holds the rules | ||
944 | in_invoc_body: bool, | ||
945 | } | ||
946 | |||
947 | impl MacroMatcherParseState { | ||
948 | fn new() -> Self { | ||
949 | MacroMatcherParseState { | ||
950 | paren_ty: None, | ||
951 | paren_level: 0, | ||
952 | in_invoc_body: false, | ||
953 | rule_state: RuleState::None, | ||
954 | } | ||
955 | } | ||
956 | } | ||
957 | |||
958 | #[derive(Copy, Clone, PartialEq)] | ||
959 | enum RuleState { | ||
960 | Matcher, | ||
961 | Expander, | ||
962 | Between, | ||
963 | None, | ||
964 | } | ||
965 | |||
966 | impl RuleState { | ||
967 | fn transition(&mut self) { | ||
968 | *self = match self { | ||
969 | RuleState::Matcher => RuleState::Between, | ||
970 | RuleState::Expander => RuleState::None, | ||
971 | RuleState::Between => RuleState::Expander, | ||
972 | RuleState::None => RuleState::Matcher, | ||
973 | }; | ||
974 | } | ||
975 | } | ||
976 | |||
977 | fn update_macro_rules_state(tok: &SyntaxToken, state: &mut MacroMatcherParseState) -> RuleState { | ||
978 | if !state.in_invoc_body { | ||
979 | if tok.kind() == T!['{'] { | ||
980 | state.in_invoc_body = true; | ||
981 | } | ||
982 | return state.rule_state; | ||
983 | } | ||
984 | |||
985 | match state.paren_ty { | ||
986 | Some((open, close)) => { | ||
987 | if tok.kind() == open { | ||
988 | state.paren_level += 1; | ||
989 | } else if tok.kind() == close { | ||
990 | state.paren_level -= 1; | ||
991 | if state.paren_level == 0 { | ||
992 | let res = state.rule_state; | ||
993 | state.rule_state.transition(); | ||
994 | state.paren_ty = None; | ||
995 | return res; | ||
996 | } | ||
997 | } | ||
998 | } | ||
999 | None => { | ||
1000 | match tok.kind() { | ||
1001 | T!['('] => { | ||
1002 | state.paren_ty = Some((T!['('], T![')'])); | ||
1003 | } | ||
1004 | T!['{'] => { | ||
1005 | state.paren_ty = Some((T!['{'], T!['}'])); | ||
1006 | } | ||
1007 | T!['['] => { | ||
1008 | state.paren_ty = Some((T!['['], T![']'])); | ||
1009 | } | ||
1010 | _ => (), | ||
1011 | } | ||
1012 | if state.paren_ty.is_some() { | ||
1013 | state.paren_level = 1; | ||
1014 | state.rule_state.transition(); | ||
1015 | } | ||
1016 | } | ||
1017 | } | ||
1018 | state.rule_state | ||
1019 | } | ||
1020 | |||
1021 | fn skip_metavariables(element: SyntaxElement) -> bool { | ||
1022 | let tok = match element.as_token() { | ||
1023 | Some(tok) => tok, | ||
1024 | None => return false, | ||
1025 | }; | ||
1026 | let is_fragment = || tok.prev_token().map(|tok| tok.kind()) == Some(T![$]); | ||
1027 | match tok.kind() { | ||
1028 | IDENT if is_fragment() => true, | ||
1029 | kind if kind.is_keyword() && is_fragment() => true, | ||
1030 | _ => false, | ||
1031 | } | ||
1032 | } | ||
diff --git a/crates/ide/src/syntax_highlighting/format.rs b/crates/ide/src/syntax_highlighting/format.rs new file mode 100644 index 000000000..71bde24f0 --- /dev/null +++ b/crates/ide/src/syntax_highlighting/format.rs | |||
@@ -0,0 +1,78 @@ | |||
1 | //! Syntax highlighting for format macro strings. | ||
2 | use syntax::{ | ||
3 | ast::{self, FormatSpecifier, HasFormatSpecifier}, | ||
4 | AstNode, AstToken, SyntaxElement, SyntaxKind, SyntaxNode, TextRange, | ||
5 | }; | ||
6 | |||
7 | use crate::{syntax_highlighting::HighlightedRangeStack, HighlightTag, HighlightedRange}; | ||
8 | |||
9 | #[derive(Default)] | ||
10 | pub(super) struct FormatStringHighlighter { | ||
11 | format_string: Option<SyntaxElement>, | ||
12 | } | ||
13 | |||
14 | impl FormatStringHighlighter { | ||
15 | pub(super) fn check_for_format_string(&mut self, parent: &SyntaxNode) { | ||
16 | // Check if macro takes a format string and remember it for highlighting later. | ||
17 | // The macros that accept a format string expand to a compiler builtin macros | ||
18 | // `format_args` and `format_args_nl`. | ||
19 | if let Some(name) = parent | ||
20 | .parent() | ||
21 | .and_then(ast::MacroCall::cast) | ||
22 | .and_then(|mc| mc.path()) | ||
23 | .and_then(|p| p.segment()) | ||
24 | .and_then(|s| s.name_ref()) | ||
25 | { | ||
26 | match name.text().as_str() { | ||
27 | "format_args" | "format_args_nl" => { | ||
28 | self.format_string = parent | ||
29 | .children_with_tokens() | ||
30 | .filter(|t| t.kind() != SyntaxKind::WHITESPACE) | ||
31 | .nth(1) | ||
32 | .filter(|e| { | ||
33 | ast::String::can_cast(e.kind()) || ast::RawString::can_cast(e.kind()) | ||
34 | }) | ||
35 | } | ||
36 | _ => {} | ||
37 | } | ||
38 | } | ||
39 | } | ||
40 | pub(super) fn highlight_format_string( | ||
41 | &self, | ||
42 | range_stack: &mut HighlightedRangeStack, | ||
43 | string: &impl HasFormatSpecifier, | ||
44 | range: TextRange, | ||
45 | ) { | ||
46 | if self.format_string.as_ref() == Some(&SyntaxElement::from(string.syntax().clone())) { | ||
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 | }); | ||
57 | range_stack.pop(); | ||
58 | } | ||
59 | } | ||
60 | } | ||
61 | |||
62 | fn highlight_format_specifier(kind: FormatSpecifier) -> Option<HighlightTag> { | ||
63 | Some(match kind { | ||
64 | FormatSpecifier::Open | ||
65 | | FormatSpecifier::Close | ||
66 | | FormatSpecifier::Colon | ||
67 | | FormatSpecifier::Fill | ||
68 | | FormatSpecifier::Align | ||
69 | | FormatSpecifier::Sign | ||
70 | | FormatSpecifier::NumberSign | ||
71 | | FormatSpecifier::DollarSign | ||
72 | | FormatSpecifier::Dot | ||
73 | | FormatSpecifier::Asterisk | ||
74 | | FormatSpecifier::QuestionMark => HighlightTag::FormatSpecifier, | ||
75 | FormatSpecifier::Integer | FormatSpecifier::Zero => HighlightTag::NumericLiteral, | ||
76 | FormatSpecifier::Identifier => HighlightTag::Local, | ||
77 | }) | ||
78 | } | ||
diff --git a/crates/ide/src/syntax_highlighting/macro_rules.rs b/crates/ide/src/syntax_highlighting/macro_rules.rs new file mode 100644 index 000000000..4462af47e --- /dev/null +++ b/crates/ide/src/syntax_highlighting/macro_rules.rs | |||
@@ -0,0 +1,129 @@ | |||
1 | //! Syntax highlighting for macro_rules!. | ||
2 | use syntax::{SyntaxElement, SyntaxKind, SyntaxToken, TextRange, T}; | ||
3 | |||
4 | use crate::{HighlightTag, HighlightedRange}; | ||
5 | |||
6 | #[derive(Default)] | ||
7 | pub(super) struct MacroRulesHighlighter { | ||
8 | state: Option<MacroMatcherParseState>, | ||
9 | } | ||
10 | |||
11 | impl MacroRulesHighlighter { | ||
12 | pub(super) fn init(&mut self) { | ||
13 | self.state = Some(MacroMatcherParseState::default()); | ||
14 | } | ||
15 | |||
16 | pub(super) fn advance(&mut self, token: &SyntaxToken) { | ||
17 | if let Some(state) = self.state.as_mut() { | ||
18 | update_macro_rules_state(state, token); | ||
19 | } | ||
20 | } | ||
21 | |||
22 | pub(super) fn highlight(&self, element: SyntaxElement) -> Option<HighlightedRange> { | ||
23 | if let Some(state) = self.state.as_ref() { | ||
24 | if matches!(state.rule_state, RuleState::Matcher | RuleState::Expander) { | ||
25 | if let Some(range) = is_metavariable(element) { | ||
26 | return Some(HighlightedRange { | ||
27 | range, | ||
28 | highlight: HighlightTag::UnresolvedReference.into(), | ||
29 | binding_hash: None, | ||
30 | }); | ||
31 | } | ||
32 | } | ||
33 | } | ||
34 | None | ||
35 | } | ||
36 | } | ||
37 | |||
38 | struct MacroMatcherParseState { | ||
39 | /// Opening and corresponding closing bracket of the matcher or expander of the current rule | ||
40 | paren_ty: Option<(SyntaxKind, SyntaxKind)>, | ||
41 | paren_level: usize, | ||
42 | rule_state: RuleState, | ||
43 | /// Whether we are inside the outer `{` `}` macro block that holds the rules | ||
44 | in_invoc_body: bool, | ||
45 | } | ||
46 | |||
47 | impl Default for MacroMatcherParseState { | ||
48 | fn default() -> Self { | ||
49 | MacroMatcherParseState { | ||
50 | paren_ty: None, | ||
51 | paren_level: 0, | ||
52 | in_invoc_body: false, | ||
53 | rule_state: RuleState::None, | ||
54 | } | ||
55 | } | ||
56 | } | ||
57 | |||
58 | #[derive(Copy, Clone, Debug, PartialEq)] | ||
59 | enum RuleState { | ||
60 | Matcher, | ||
61 | Expander, | ||
62 | Between, | ||
63 | None, | ||
64 | } | ||
65 | |||
66 | impl RuleState { | ||
67 | fn transition(&mut self) { | ||
68 | *self = match self { | ||
69 | RuleState::Matcher => RuleState::Between, | ||
70 | RuleState::Expander => RuleState::None, | ||
71 | RuleState::Between => RuleState::Expander, | ||
72 | RuleState::None => RuleState::Matcher, | ||
73 | }; | ||
74 | } | ||
75 | } | ||
76 | |||
77 | fn update_macro_rules_state(state: &mut MacroMatcherParseState, tok: &SyntaxToken) { | ||
78 | if !state.in_invoc_body { | ||
79 | if tok.kind() == T!['{'] { | ||
80 | state.in_invoc_body = true; | ||
81 | } | ||
82 | return; | ||
83 | } | ||
84 | |||
85 | match state.paren_ty { | ||
86 | Some((open, close)) => { | ||
87 | if tok.kind() == open { | ||
88 | state.paren_level += 1; | ||
89 | } else if tok.kind() == close { | ||
90 | state.paren_level -= 1; | ||
91 | if state.paren_level == 0 { | ||
92 | state.rule_state.transition(); | ||
93 | state.paren_ty = None; | ||
94 | } | ||
95 | } | ||
96 | } | ||
97 | None => { | ||
98 | match tok.kind() { | ||
99 | T!['('] => { | ||
100 | state.paren_ty = Some((T!['('], T![')'])); | ||
101 | } | ||
102 | T!['{'] => { | ||
103 | state.paren_ty = Some((T!['{'], T!['}'])); | ||
104 | } | ||
105 | T!['['] => { | ||
106 | state.paren_ty = Some((T!['['], T![']'])); | ||
107 | } | ||
108 | _ => (), | ||
109 | } | ||
110 | if state.paren_ty.is_some() { | ||
111 | state.paren_level = 1; | ||
112 | state.rule_state.transition(); | ||
113 | } | ||
114 | } | ||
115 | } | ||
116 | } | ||
117 | |||
118 | fn is_metavariable(element: SyntaxElement) -> Option<TextRange> { | ||
119 | let tok = element.as_token()?; | ||
120 | match tok.kind() { | ||
121 | kind if kind == SyntaxKind::IDENT || kind.is_keyword() => { | ||
122 | if let Some(_dollar) = tok.prev_token().filter(|t| t.kind() == SyntaxKind::DOLLAR) { | ||
123 | return Some(tok.text_range()); | ||
124 | } | ||
125 | } | ||
126 | _ => (), | ||
127 | }; | ||
128 | None | ||
129 | } | ||
diff --git a/crates/ide_db/src/defs.rs b/crates/ide_db/src/defs.rs index f8c7aa491..201a3d6fa 100644 --- a/crates/ide_db/src/defs.rs +++ b/crates/ide_db/src/defs.rs | |||
@@ -81,146 +81,152 @@ impl Definition { | |||
81 | pub enum NameClass { | 81 | pub enum NameClass { |
82 | ExternCrate(Crate), | 82 | ExternCrate(Crate), |
83 | Definition(Definition), | 83 | Definition(Definition), |
84 | /// `None` in `if let None = Some(82) {}` | 84 | /// `None` in `if let None = Some(82) {}`. |
85 | ConstReference(Definition), | 85 | ConstReference(Definition), |
86 | FieldShorthand { | 86 | /// `field` in `if let Foo { field } = foo`. |
87 | local: Local, | 87 | PatFieldShorthand { |
88 | field: Definition, | 88 | local_def: Local, |
89 | field_ref: Definition, | ||
89 | }, | 90 | }, |
90 | } | 91 | } |
91 | 92 | ||
92 | impl NameClass { | 93 | impl NameClass { |
93 | pub fn into_definition(self, db: &dyn HirDatabase) -> Option<Definition> { | 94 | /// `Definition` defined by this name. |
94 | Some(match self { | 95 | pub fn defined(self, db: &dyn HirDatabase) -> Option<Definition> { |
96 | let res = match self { | ||
95 | NameClass::ExternCrate(krate) => Definition::ModuleDef(krate.root_module(db).into()), | 97 | NameClass::ExternCrate(krate) => Definition::ModuleDef(krate.root_module(db).into()), |
96 | NameClass::Definition(it) => it, | 98 | NameClass::Definition(it) => it, |
97 | NameClass::ConstReference(_) => return None, | 99 | NameClass::ConstReference(_) => return None, |
98 | NameClass::FieldShorthand { local, field: _ } => Definition::Local(local), | 100 | NameClass::PatFieldShorthand { local_def, field_ref: _ } => { |
99 | }) | 101 | Definition::Local(local_def) |
102 | } | ||
103 | }; | ||
104 | Some(res) | ||
100 | } | 105 | } |
101 | 106 | ||
102 | pub fn definition(self, db: &dyn HirDatabase) -> Definition { | 107 | /// `Definition` referenced or defined by this name. |
108 | pub fn referenced_or_defined(self, db: &dyn HirDatabase) -> Definition { | ||
103 | match self { | 109 | match self { |
104 | NameClass::ExternCrate(krate) => Definition::ModuleDef(krate.root_module(db).into()), | 110 | NameClass::ExternCrate(krate) => Definition::ModuleDef(krate.root_module(db).into()), |
105 | NameClass::Definition(it) | NameClass::ConstReference(it) => it, | 111 | NameClass::Definition(it) | NameClass::ConstReference(it) => it, |
106 | NameClass::FieldShorthand { local: _, field } => field, | 112 | NameClass::PatFieldShorthand { local_def: _, field_ref } => field_ref, |
107 | } | 113 | } |
108 | } | 114 | } |
109 | } | ||
110 | 115 | ||
111 | pub fn classify_name(sema: &Semantics<RootDatabase>, name: &ast::Name) -> Option<NameClass> { | 116 | pub fn classify(sema: &Semantics<RootDatabase>, name: &ast::Name) -> Option<NameClass> { |
112 | let _p = profile::span("classify_name"); | 117 | let _p = profile::span("classify_name"); |
113 | 118 | ||
114 | let parent = name.syntax().parent()?; | 119 | let parent = name.syntax().parent()?; |
115 | 120 | ||
116 | if let Some(bind_pat) = ast::IdentPat::cast(parent.clone()) { | 121 | if let Some(bind_pat) = ast::IdentPat::cast(parent.clone()) { |
117 | if let Some(def) = sema.resolve_bind_pat_to_const(&bind_pat) { | 122 | if let Some(def) = sema.resolve_bind_pat_to_const(&bind_pat) { |
118 | return Some(NameClass::ConstReference(Definition::ModuleDef(def))); | 123 | return Some(NameClass::ConstReference(Definition::ModuleDef(def))); |
124 | } | ||
119 | } | 125 | } |
120 | } | ||
121 | 126 | ||
122 | match_ast! { | 127 | match_ast! { |
123 | match parent { | 128 | match parent { |
124 | ast::Rename(it) => { | 129 | ast::Rename(it) => { |
125 | if let Some(use_tree) = it.syntax().parent().and_then(ast::UseTree::cast) { | 130 | if let Some(use_tree) = it.syntax().parent().and_then(ast::UseTree::cast) { |
126 | let path = use_tree.path()?; | 131 | let path = use_tree.path()?; |
127 | let path_segment = path.segment()?; | 132 | let path_segment = path.segment()?; |
128 | let name_ref_class = path_segment | 133 | let name_ref_class = path_segment |
129 | .name_ref() | 134 | .name_ref() |
130 | // The rename might be from a `self` token, so fallback to the name higher | 135 | // The rename might be from a `self` token, so fallback to the name higher |
131 | // in the use tree. | 136 | // in the use tree. |
132 | .or_else(||{ | 137 | .or_else(||{ |
133 | if path_segment.self_token().is_none() { | 138 | if path_segment.self_token().is_none() { |
134 | return None; | 139 | return None; |
135 | } | 140 | } |
136 | 141 | ||
137 | let use_tree = use_tree | 142 | let use_tree = use_tree |
138 | .syntax() | 143 | .syntax() |
139 | .parent() | 144 | .parent() |
140 | .as_ref() | 145 | .as_ref() |
141 | // Skip over UseTreeList | 146 | // Skip over UseTreeList |
142 | .and_then(SyntaxNode::parent) | 147 | .and_then(SyntaxNode::parent) |
143 | .and_then(ast::UseTree::cast)?; | 148 | .and_then(ast::UseTree::cast)?; |
144 | let path = use_tree.path()?; | 149 | let path = use_tree.path()?; |
145 | let path_segment = path.segment()?; | 150 | let path_segment = path.segment()?; |
146 | path_segment.name_ref() | 151 | path_segment.name_ref() |
147 | }) | 152 | }) |
148 | .and_then(|name_ref| classify_name_ref(sema, &name_ref))?; | 153 | .and_then(|name_ref| NameRefClass::classify(sema, &name_ref))?; |
149 | 154 | ||
150 | Some(NameClass::Definition(name_ref_class.definition(sema.db))) | 155 | Some(NameClass::Definition(name_ref_class.referenced(sema.db))) |
151 | } else { | 156 | } else { |
152 | let extern_crate = it.syntax().parent().and_then(ast::ExternCrate::cast)?; | 157 | let extern_crate = it.syntax().parent().and_then(ast::ExternCrate::cast)?; |
153 | let resolved = sema.resolve_extern_crate(&extern_crate)?; | 158 | let resolved = sema.resolve_extern_crate(&extern_crate)?; |
154 | Some(NameClass::ExternCrate(resolved)) | 159 | Some(NameClass::ExternCrate(resolved)) |
155 | } | 160 | } |
156 | }, | 161 | }, |
157 | ast::IdentPat(it) => { | 162 | ast::IdentPat(it) => { |
158 | let local = sema.to_def(&it)?; | 163 | let local = sema.to_def(&it)?; |
159 | 164 | ||
160 | if let Some(record_pat_field) = it.syntax().parent().and_then(ast::RecordPatField::cast) { | 165 | if let Some(record_pat_field) = it.syntax().parent().and_then(ast::RecordPatField::cast) { |
161 | if record_pat_field.name_ref().is_none() { | 166 | if record_pat_field.name_ref().is_none() { |
162 | if let Some(field) = sema.resolve_record_pat_field(&record_pat_field) { | 167 | if let Some(field) = sema.resolve_record_pat_field(&record_pat_field) { |
163 | let field = Definition::Field(field); | 168 | let field = Definition::Field(field); |
164 | return Some(NameClass::FieldShorthand { local, field }); | 169 | return Some(NameClass::PatFieldShorthand { local_def: local, field_ref: field }); |
170 | } | ||
165 | } | 171 | } |
166 | } | 172 | } |
167 | } | ||
168 | 173 | ||
169 | Some(NameClass::Definition(Definition::Local(local))) | 174 | Some(NameClass::Definition(Definition::Local(local))) |
170 | }, | 175 | }, |
171 | ast::RecordField(it) => { | 176 | ast::RecordField(it) => { |
172 | let field: hir::Field = sema.to_def(&it)?; | 177 | let field: hir::Field = sema.to_def(&it)?; |
173 | Some(NameClass::Definition(Definition::Field(field))) | 178 | Some(NameClass::Definition(Definition::Field(field))) |
174 | }, | 179 | }, |
175 | ast::Module(it) => { | 180 | ast::Module(it) => { |
176 | let def = sema.to_def(&it)?; | 181 | let def = sema.to_def(&it)?; |
177 | Some(NameClass::Definition(Definition::ModuleDef(def.into()))) | 182 | Some(NameClass::Definition(Definition::ModuleDef(def.into()))) |
178 | }, | 183 | }, |
179 | ast::Struct(it) => { | 184 | ast::Struct(it) => { |
180 | let def: hir::Struct = sema.to_def(&it)?; | 185 | let def: hir::Struct = sema.to_def(&it)?; |
181 | Some(NameClass::Definition(Definition::ModuleDef(def.into()))) | 186 | Some(NameClass::Definition(Definition::ModuleDef(def.into()))) |
182 | }, | 187 | }, |
183 | ast::Union(it) => { | 188 | ast::Union(it) => { |
184 | let def: hir::Union = sema.to_def(&it)?; | 189 | let def: hir::Union = sema.to_def(&it)?; |
185 | Some(NameClass::Definition(Definition::ModuleDef(def.into()))) | 190 | Some(NameClass::Definition(Definition::ModuleDef(def.into()))) |
186 | }, | 191 | }, |
187 | ast::Enum(it) => { | 192 | ast::Enum(it) => { |
188 | let def: hir::Enum = sema.to_def(&it)?; | 193 | let def: hir::Enum = sema.to_def(&it)?; |
189 | Some(NameClass::Definition(Definition::ModuleDef(def.into()))) | 194 | Some(NameClass::Definition(Definition::ModuleDef(def.into()))) |
190 | }, | 195 | }, |
191 | ast::Trait(it) => { | 196 | ast::Trait(it) => { |
192 | let def: hir::Trait = sema.to_def(&it)?; | 197 | let def: hir::Trait = sema.to_def(&it)?; |
193 | Some(NameClass::Definition(Definition::ModuleDef(def.into()))) | 198 | Some(NameClass::Definition(Definition::ModuleDef(def.into()))) |
194 | }, | 199 | }, |
195 | ast::Static(it) => { | 200 | ast::Static(it) => { |
196 | let def: hir::Static = sema.to_def(&it)?; | 201 | let def: hir::Static = sema.to_def(&it)?; |
197 | Some(NameClass::Definition(Definition::ModuleDef(def.into()))) | 202 | Some(NameClass::Definition(Definition::ModuleDef(def.into()))) |
198 | }, | 203 | }, |
199 | ast::Variant(it) => { | 204 | ast::Variant(it) => { |
200 | let def: hir::EnumVariant = sema.to_def(&it)?; | 205 | let def: hir::EnumVariant = sema.to_def(&it)?; |
201 | Some(NameClass::Definition(Definition::ModuleDef(def.into()))) | 206 | Some(NameClass::Definition(Definition::ModuleDef(def.into()))) |
202 | }, | 207 | }, |
203 | ast::Fn(it) => { | 208 | ast::Fn(it) => { |
204 | let def: hir::Function = sema.to_def(&it)?; | 209 | let def: hir::Function = sema.to_def(&it)?; |
205 | Some(NameClass::Definition(Definition::ModuleDef(def.into()))) | 210 | Some(NameClass::Definition(Definition::ModuleDef(def.into()))) |
206 | }, | 211 | }, |
207 | ast::Const(it) => { | 212 | ast::Const(it) => { |
208 | let def: hir::Const = sema.to_def(&it)?; | 213 | let def: hir::Const = sema.to_def(&it)?; |
209 | Some(NameClass::Definition(Definition::ModuleDef(def.into()))) | 214 | Some(NameClass::Definition(Definition::ModuleDef(def.into()))) |
210 | }, | 215 | }, |
211 | ast::TypeAlias(it) => { | 216 | ast::TypeAlias(it) => { |
212 | let def: hir::TypeAlias = sema.to_def(&it)?; | 217 | let def: hir::TypeAlias = sema.to_def(&it)?; |
213 | Some(NameClass::Definition(Definition::ModuleDef(def.into()))) | 218 | Some(NameClass::Definition(Definition::ModuleDef(def.into()))) |
214 | }, | 219 | }, |
215 | ast::MacroCall(it) => { | 220 | ast::MacroCall(it) => { |
216 | let def = sema.to_def(&it)?; | 221 | let def = sema.to_def(&it)?; |
217 | Some(NameClass::Definition(Definition::Macro(def))) | 222 | Some(NameClass::Definition(Definition::Macro(def))) |
218 | }, | 223 | }, |
219 | ast::TypeParam(it) => { | 224 | ast::TypeParam(it) => { |
220 | let def = sema.to_def(&it)?; | 225 | let def = sema.to_def(&it)?; |
221 | Some(NameClass::Definition(Definition::TypeParam(def))) | 226 | Some(NameClass::Definition(Definition::TypeParam(def))) |
222 | }, | 227 | }, |
223 | _ => None, | 228 | _ => None, |
229 | } | ||
224 | } | 230 | } |
225 | } | 231 | } |
226 | } | 232 | } |
@@ -229,102 +235,109 @@ pub fn classify_name(sema: &Semantics<RootDatabase>, name: &ast::Name) -> Option | |||
229 | pub enum NameRefClass { | 235 | pub enum NameRefClass { |
230 | ExternCrate(Crate), | 236 | ExternCrate(Crate), |
231 | Definition(Definition), | 237 | Definition(Definition), |
232 | FieldShorthand { local: Local, field: Definition }, | 238 | FieldShorthand { local_ref: Local, field_ref: Definition }, |
233 | } | 239 | } |
234 | 240 | ||
235 | impl NameRefClass { | 241 | impl NameRefClass { |
236 | pub fn definition(self, db: &dyn HirDatabase) -> Definition { | 242 | /// `Definition`, which this name refers to. |
243 | pub fn referenced(self, db: &dyn HirDatabase) -> Definition { | ||
237 | match self { | 244 | match self { |
238 | NameRefClass::ExternCrate(krate) => Definition::ModuleDef(krate.root_module(db).into()), | 245 | NameRefClass::ExternCrate(krate) => Definition::ModuleDef(krate.root_module(db).into()), |
239 | NameRefClass::Definition(def) => def, | 246 | NameRefClass::Definition(def) => def, |
240 | NameRefClass::FieldShorthand { local, field: _ } => Definition::Local(local), | 247 | NameRefClass::FieldShorthand { local_ref, field_ref: _ } => { |
248 | // FIXME: this is inherently ambiguous -- this name refers to | ||
249 | // two different defs.... | ||
250 | Definition::Local(local_ref) | ||
251 | } | ||
241 | } | 252 | } |
242 | } | 253 | } |
243 | } | ||
244 | 254 | ||
245 | // Note: we don't have unit-tests for this rather important function. | 255 | // Note: we don't have unit-tests for this rather important function. |
246 | // It is primarily exercised via goto definition tests in `ide`. | 256 | // It is primarily exercised via goto definition tests in `ide`. |
247 | pub fn classify_name_ref( | 257 | pub fn classify( |
248 | sema: &Semantics<RootDatabase>, | 258 | sema: &Semantics<RootDatabase>, |
249 | name_ref: &ast::NameRef, | 259 | name_ref: &ast::NameRef, |
250 | ) -> Option<NameRefClass> { | 260 | ) -> Option<NameRefClass> { |
251 | let _p = profile::span("classify_name_ref"); | 261 | let _p = profile::span("classify_name_ref"); |
252 | 262 | ||
253 | let parent = name_ref.syntax().parent()?; | 263 | let parent = name_ref.syntax().parent()?; |
254 | 264 | ||
255 | if let Some(method_call) = ast::MethodCallExpr::cast(parent.clone()) { | 265 | if let Some(method_call) = ast::MethodCallExpr::cast(parent.clone()) { |
256 | if let Some(func) = sema.resolve_method_call(&method_call) { | 266 | if let Some(func) = sema.resolve_method_call(&method_call) { |
257 | return Some(NameRefClass::Definition(Definition::ModuleDef(func.into()))); | 267 | return Some(NameRefClass::Definition(Definition::ModuleDef(func.into()))); |
268 | } | ||
258 | } | 269 | } |
259 | } | ||
260 | 270 | ||
261 | if let Some(field_expr) = ast::FieldExpr::cast(parent.clone()) { | 271 | if let Some(field_expr) = ast::FieldExpr::cast(parent.clone()) { |
262 | if let Some(field) = sema.resolve_field(&field_expr) { | 272 | if let Some(field) = sema.resolve_field(&field_expr) { |
263 | return Some(NameRefClass::Definition(Definition::Field(field))); | 273 | return Some(NameRefClass::Definition(Definition::Field(field))); |
274 | } | ||
264 | } | 275 | } |
265 | } | ||
266 | 276 | ||
267 | if let Some(record_field) = ast::RecordExprField::for_field_name(name_ref) { | 277 | if let Some(record_field) = ast::RecordExprField::for_field_name(name_ref) { |
268 | if let Some((field, local)) = sema.resolve_record_field(&record_field) { | 278 | if let Some((field, local)) = sema.resolve_record_field(&record_field) { |
269 | let field = Definition::Field(field); | 279 | let field = Definition::Field(field); |
270 | let res = match local { | 280 | let res = match local { |
271 | None => NameRefClass::Definition(field), | 281 | None => NameRefClass::Definition(field), |
272 | Some(local) => NameRefClass::FieldShorthand { field, local }, | 282 | Some(local) => { |
273 | }; | 283 | NameRefClass::FieldShorthand { field_ref: field, local_ref: local } |
274 | return Some(res); | 284 | } |
285 | }; | ||
286 | return Some(res); | ||
287 | } | ||
275 | } | 288 | } |
276 | } | ||
277 | 289 | ||
278 | if let Some(record_pat_field) = ast::RecordPatField::cast(parent.clone()) { | 290 | if let Some(record_pat_field) = ast::RecordPatField::cast(parent.clone()) { |
279 | if let Some(field) = sema.resolve_record_pat_field(&record_pat_field) { | 291 | if let Some(field) = sema.resolve_record_pat_field(&record_pat_field) { |
280 | let field = Definition::Field(field); | 292 | let field = Definition::Field(field); |
281 | return Some(NameRefClass::Definition(field)); | 293 | return Some(NameRefClass::Definition(field)); |
294 | } | ||
282 | } | 295 | } |
283 | } | ||
284 | 296 | ||
285 | if ast::AssocTypeArg::cast(parent.clone()).is_some() { | 297 | if ast::AssocTypeArg::cast(parent.clone()).is_some() { |
286 | // `Trait<Assoc = Ty>` | 298 | // `Trait<Assoc = Ty>` |
287 | // ^^^^^ | 299 | // ^^^^^ |
288 | let path = name_ref.syntax().ancestors().find_map(ast::Path::cast)?; | 300 | let path = name_ref.syntax().ancestors().find_map(ast::Path::cast)?; |
289 | let resolved = sema.resolve_path(&path)?; | 301 | let resolved = sema.resolve_path(&path)?; |
290 | if let PathResolution::Def(ModuleDef::Trait(tr)) = resolved { | 302 | if let PathResolution::Def(ModuleDef::Trait(tr)) = resolved { |
291 | if let Some(ty) = tr | 303 | if let Some(ty) = tr |
292 | .items(sema.db) | 304 | .items(sema.db) |
293 | .iter() | 305 | .iter() |
294 | .filter_map(|assoc| match assoc { | 306 | .filter_map(|assoc| match assoc { |
295 | hir::AssocItem::TypeAlias(it) => Some(*it), | 307 | hir::AssocItem::TypeAlias(it) => Some(*it), |
296 | _ => None, | 308 | _ => None, |
297 | }) | 309 | }) |
298 | .find(|alias| alias.name(sema.db).to_string() == **name_ref.text()) | 310 | .find(|alias| alias.name(sema.db).to_string() == **name_ref.text()) |
299 | { | 311 | { |
300 | return Some(NameRefClass::Definition(Definition::ModuleDef( | 312 | return Some(NameRefClass::Definition(Definition::ModuleDef( |
301 | ModuleDef::TypeAlias(ty), | 313 | ModuleDef::TypeAlias(ty), |
302 | ))); | 314 | ))); |
315 | } | ||
303 | } | 316 | } |
304 | } | 317 | } |
305 | } | ||
306 | 318 | ||
307 | if let Some(macro_call) = parent.ancestors().find_map(ast::MacroCall::cast) { | 319 | if let Some(macro_call) = parent.ancestors().find_map(ast::MacroCall::cast) { |
308 | if let Some(path) = macro_call.path() { | 320 | if let Some(path) = macro_call.path() { |
309 | if path.qualifier().is_none() { | 321 | if path.qualifier().is_none() { |
310 | // Only use this to resolve single-segment macro calls like `foo!()`. Multi-segment | 322 | // Only use this to resolve single-segment macro calls like `foo!()`. Multi-segment |
311 | // paths are handled below (allowing `log<|>::info!` to resolve to the log crate). | 323 | // paths are handled below (allowing `log<|>::info!` to resolve to the log crate). |
312 | if let Some(macro_def) = sema.resolve_macro_call(¯o_call) { | 324 | if let Some(macro_def) = sema.resolve_macro_call(¯o_call) { |
313 | return Some(NameRefClass::Definition(Definition::Macro(macro_def))); | 325 | return Some(NameRefClass::Definition(Definition::Macro(macro_def))); |
326 | } | ||
314 | } | 327 | } |
315 | } | 328 | } |
316 | } | 329 | } |
317 | } | ||
318 | 330 | ||
319 | if let Some(path) = name_ref.syntax().ancestors().find_map(ast::Path::cast) { | 331 | if let Some(path) = name_ref.syntax().ancestors().find_map(ast::Path::cast) { |
320 | if let Some(resolved) = sema.resolve_path(&path) { | 332 | if let Some(resolved) = sema.resolve_path(&path) { |
321 | return Some(NameRefClass::Definition(resolved.into())); | 333 | return Some(NameRefClass::Definition(resolved.into())); |
334 | } | ||
322 | } | 335 | } |
323 | } | ||
324 | 336 | ||
325 | let extern_crate = ast::ExternCrate::cast(parent)?; | 337 | let extern_crate = ast::ExternCrate::cast(parent)?; |
326 | let resolved = sema.resolve_extern_crate(&extern_crate)?; | 338 | let resolved = sema.resolve_extern_crate(&extern_crate)?; |
327 | Some(NameRefClass::ExternCrate(resolved)) | 339 | Some(NameRefClass::ExternCrate(resolved)) |
340 | } | ||
328 | } | 341 | } |
329 | 342 | ||
330 | impl From<PathResolution> for Definition { | 343 | impl From<PathResolution> for Definition { |
diff --git a/crates/ide_db/src/imports_locator.rs b/crates/ide_db/src/imports_locator.rs index ed67e3553..df74be00b 100644 --- a/crates/ide_db/src/imports_locator.rs +++ b/crates/ide_db/src/imports_locator.rs | |||
@@ -5,7 +5,7 @@ use hir::{Crate, MacroDef, ModuleDef, Semantics}; | |||
5 | use syntax::{ast, AstNode, SyntaxKind::NAME}; | 5 | use syntax::{ast, AstNode, SyntaxKind::NAME}; |
6 | 6 | ||
7 | use crate::{ | 7 | use crate::{ |
8 | defs::{classify_name, Definition}, | 8 | defs::{Definition, NameClass}, |
9 | symbol_index::{self, FileSymbol, Query}, | 9 | symbol_index::{self, FileSymbol, Query}, |
10 | RootDatabase, | 10 | RootDatabase, |
11 | }; | 11 | }; |
@@ -60,5 +60,5 @@ fn get_name_definition<'a>( | |||
60 | candidate_node | 60 | candidate_node |
61 | }; | 61 | }; |
62 | let name = ast::Name::cast(candidate_name_node)?; | 62 | let name = ast::Name::cast(candidate_name_node)?; |
63 | classify_name(sema, &name)?.into_definition(sema.db) | 63 | NameClass::classify(sema, &name)?.defined(sema.db) |
64 | } | 64 | } |
diff --git a/crates/ide_db/src/search.rs b/crates/ide_db/src/search.rs index 8e3dcd99c..a24335240 100644 --- a/crates/ide_db/src/search.rs +++ b/crates/ide_db/src/search.rs | |||
@@ -14,7 +14,7 @@ use syntax::{ast, match_ast, AstNode, TextRange, TextSize}; | |||
14 | 14 | ||
15 | use crate::defs::NameClass; | 15 | use crate::defs::NameClass; |
16 | use crate::{ | 16 | use crate::{ |
17 | defs::{classify_name, classify_name_ref, Definition, NameRefClass}, | 17 | defs::{Definition, NameRefClass}, |
18 | RootDatabase, | 18 | RootDatabase, |
19 | }; | 19 | }; |
20 | 20 | ||
@@ -276,7 +276,7 @@ impl<'a> FindUsages<'a> { | |||
276 | name_ref: &ast::NameRef, | 276 | name_ref: &ast::NameRef, |
277 | sink: &mut dyn FnMut(Reference) -> bool, | 277 | sink: &mut dyn FnMut(Reference) -> bool, |
278 | ) -> bool { | 278 | ) -> bool { |
279 | match classify_name_ref(self.sema, &name_ref) { | 279 | match NameRefClass::classify(self.sema, &name_ref) { |
280 | Some(NameRefClass::Definition(def)) if &def == self.def => { | 280 | Some(NameRefClass::Definition(def)) if &def == self.def => { |
281 | let kind = if is_record_lit_name_ref(&name_ref) || is_call_expr_name_ref(&name_ref) | 281 | let kind = if is_record_lit_name_ref(&name_ref) || is_call_expr_name_ref(&name_ref) |
282 | { | 282 | { |
@@ -292,7 +292,7 @@ impl<'a> FindUsages<'a> { | |||
292 | }; | 292 | }; |
293 | sink(reference) | 293 | sink(reference) |
294 | } | 294 | } |
295 | Some(NameRefClass::FieldShorthand { local, field }) => { | 295 | Some(NameRefClass::FieldShorthand { local_ref: local, field_ref: field }) => { |
296 | let reference = match self.def { | 296 | let reference = match self.def { |
297 | Definition::Field(_) if &field == self.def => Reference { | 297 | Definition::Field(_) if &field == self.def => Reference { |
298 | file_range: self.sema.original_range(name_ref.syntax()), | 298 | file_range: self.sema.original_range(name_ref.syntax()), |
@@ -313,10 +313,10 @@ impl<'a> FindUsages<'a> { | |||
313 | } | 313 | } |
314 | 314 | ||
315 | fn found_name(&self, name: &ast::Name, sink: &mut dyn FnMut(Reference) -> bool) -> bool { | 315 | fn found_name(&self, name: &ast::Name, sink: &mut dyn FnMut(Reference) -> bool) -> bool { |
316 | match classify_name(self.sema, name) { | 316 | match NameClass::classify(self.sema, name) { |
317 | Some(NameClass::FieldShorthand { local: _, field }) => { | 317 | Some(NameClass::PatFieldShorthand { local_def: _, field_ref }) => { |
318 | let reference = match self.def { | 318 | let reference = match self.def { |
319 | Definition::Field(_) if &field == self.def => Reference { | 319 | Definition::Field(_) if &field_ref == self.def => Reference { |
320 | file_range: self.sema.original_range(name.syntax()), | 320 | file_range: self.sema.original_range(name.syntax()), |
321 | kind: ReferenceKind::FieldShorthandForField, | 321 | kind: ReferenceKind::FieldShorthandForField, |
322 | // FIXME: mutable patterns should have `Write` access | 322 | // FIXME: mutable patterns should have `Write` access |
diff --git a/docs/dev/style.md b/docs/dev/style.md index 59067d234..7a64a0d22 100644 --- a/docs/dev/style.md +++ b/docs/dev/style.md | |||
@@ -186,6 +186,31 @@ impl Person { | |||
186 | } | 186 | } |
187 | ``` | 187 | ``` |
188 | 188 | ||
189 | ## Constructors | ||
190 | |||
191 | Prefer `Default` to zero-argument `new` function | ||
192 | |||
193 | ```rust | ||
194 | // Good | ||
195 | #[derive(Default)] | ||
196 | struct Foo { | ||
197 | bar: Option<Bar> | ||
198 | } | ||
199 | |||
200 | // Not as good | ||
201 | struct Foo { | ||
202 | bar: Option<Bar> | ||
203 | } | ||
204 | |||
205 | impl Foo { | ||
206 | fn new() -> Foo { | ||
207 | Foo { bar: None } | ||
208 | } | ||
209 | } | ||
210 | ``` | ||
211 | |||
212 | Prefer `Default` even it has to be implemented manually. | ||
213 | |||
189 | ## Avoid Monomorphization | 214 | ## Avoid Monomorphization |
190 | 215 | ||
191 | Rust uses monomorphization to compile generic code, meaning that for each instantiation of a generic functions with concrete types, the function is compiled afresh, *per crate*. | 216 | Rust uses monomorphization to compile generic code, meaning that for each instantiation of a generic functions with concrete types, the function is compiled afresh, *per crate*. |
@@ -223,6 +248,8 @@ fn frbonicate(f: impl AsRef<Path>) { | |||
223 | 248 | ||
224 | # Premature Pessimization | 249 | # Premature Pessimization |
225 | 250 | ||
251 | ## Avoid Allocations | ||
252 | |||
226 | Avoid writing code which is slower than it needs to be. | 253 | Avoid writing code which is slower than it needs to be. |
227 | Don't allocate a `Vec` where an iterator would do, don't allocate strings needlessly. | 254 | Don't allocate a `Vec` where an iterator would do, don't allocate strings needlessly. |
228 | 255 | ||
@@ -242,6 +269,8 @@ if words.len() != 2 { | |||
242 | } | 269 | } |
243 | ``` | 270 | ``` |
244 | 271 | ||
272 | ## Push Allocations to the Call Site | ||
273 | |||
245 | If allocation is inevitable, let the caller allocate the resource: | 274 | If allocation is inevitable, let the caller allocate the resource: |
246 | 275 | ||
247 | ```rust | 276 | ```rust |
@@ -257,6 +286,9 @@ fn frobnicate(s: &str) { | |||
257 | } | 286 | } |
258 | ``` | 287 | ``` |
259 | 288 | ||
289 | This is better because it reveals the costs. | ||
290 | It is also more efficient when the caller already owns the allocation. | ||
291 | |||
260 | ## Collection types | 292 | ## Collection types |
261 | 293 | ||
262 | Prefer `rustc_hash::FxHashMap` and `rustc_hash::FxHashSet` instead of the ones in `std::collections`. | 294 | Prefer `rustc_hash::FxHashMap` and `rustc_hash::FxHashSet` instead of the ones in `std::collections`. |
@@ -334,27 +366,66 @@ People read things from top to bottom, so place most important things first. | |||
334 | 366 | ||
335 | Specifically, if all items except one are private, always put the non-private item on top. | 367 | Specifically, if all items except one are private, always put the non-private item on top. |
336 | 368 | ||
337 | Put `struct`s and `enum`s first, functions and impls last. | ||
338 | |||
339 | Do | ||
340 | |||
341 | ```rust | 369 | ```rust |
342 | // Good | 370 | // Good |
343 | struct Foo { | 371 | pub(crate) fn frobnicate() { |
344 | bars: Vec<Bar> | 372 | Helper::act() |
345 | } | 373 | } |
346 | 374 | ||
347 | struct Bar; | 375 | #[derive(Default)] |
376 | struct Helper { stuff: i32 } | ||
377 | |||
378 | impl Helper { | ||
379 | fn act(&self) { | ||
380 | |||
381 | } | ||
382 | } | ||
383 | |||
384 | // Not as good | ||
385 | #[derive(Default)] | ||
386 | struct Helper { stuff: i32 } | ||
387 | |||
388 | pub(crate) fn frobnicate() { | ||
389 | Helper::act() | ||
390 | } | ||
391 | |||
392 | impl Helper { | ||
393 | fn act(&self) { | ||
394 | |||
395 | } | ||
396 | } | ||
348 | ``` | 397 | ``` |
349 | 398 | ||
350 | rather than | 399 | If there's a mixture of private and public items, put public items first. |
400 | If function bodies are folded in the editor, the source code should read as documentation for the public API. | ||
401 | |||
402 | Put `struct`s and `enum`s first, functions and impls last. Order types declarations in top-down manner. | ||
351 | 403 | ||
352 | ```rust | 404 | ```rust |
405 | // Good | ||
406 | struct Parent { | ||
407 | children: Vec<Child> | ||
408 | } | ||
409 | |||
410 | struct Child; | ||
411 | |||
412 | impl Parent { | ||
413 | } | ||
414 | |||
415 | impl Child { | ||
416 | } | ||
417 | |||
353 | // Not as good | 418 | // Not as good |
354 | struct Bar; | 419 | struct Child; |
355 | 420 | ||
356 | struct Foo { | 421 | impl Child { |
357 | bars: Vec<Bar> | 422 | } |
423 | |||
424 | struct Parent { | ||
425 | children: Vec<Child> | ||
426 | } | ||
427 | |||
428 | impl Parent { | ||
358 | } | 429 | } |
359 | ``` | 430 | ``` |
360 | 431 | ||
@@ -371,6 +442,18 @@ Default names: | |||
371 | * `n_foo` -- number of foos | 442 | * `n_foo` -- number of foos |
372 | * `foo_idx` -- index of `foo` | 443 | * `foo_idx` -- index of `foo` |
373 | 444 | ||
445 | Many names in rust-analyzer conflict with keywords. | ||
446 | We use mangled names instead of `r#ident` syntax: | ||
447 | |||
448 | ``` | ||
449 | struct -> strukt | ||
450 | crate -> krate | ||
451 | impl -> imp | ||
452 | trait -> trait_ | ||
453 | fn -> func | ||
454 | enum -> enum_ | ||
455 | mod -> module | ||
456 | ``` | ||
374 | 457 | ||
375 | ## Early Returns | 458 | ## Early Returns |
376 | 459 | ||
diff --git a/editors/code/src/inlay_hints.ts b/editors/code/src/inlay_hints.ts index 30ade9b98..ed0db2924 100644 --- a/editors/code/src/inlay_hints.ts +++ b/editors/code/src/inlay_hints.ts | |||
@@ -44,10 +44,12 @@ const paramHints = createHintStyle("parameter"); | |||
44 | const chainingHints = createHintStyle("chaining"); | 44 | const chainingHints = createHintStyle("chaining"); |
45 | 45 | ||
46 | function createHintStyle(hintKind: "type" | "parameter" | "chaining") { | 46 | function createHintStyle(hintKind: "type" | "parameter" | "chaining") { |
47 | // U+200C is a zero-width non-joiner to prevent the editor from forming a ligature | ||
48 | // between code and type hints | ||
47 | const [pos, render] = ({ | 49 | const [pos, render] = ({ |
48 | type: ["after", (label: string) => `: ${label}`], | 50 | type: ["after", (label: string) => `\u{200c}: ${label}`], |
49 | parameter: ["before", (label: string) => `${label}: `], | 51 | parameter: ["before", (label: string) => `${label}: `], |
50 | chaining: ["after", (label: string) => `: ${label}`], | 52 | chaining: ["after", (label: string) => `\u{200c}: ${label}`], |
51 | } as const)[hintKind]; | 53 | } as const)[hintKind]; |
52 | 54 | ||
53 | const fg = new vscode.ThemeColor(`rust_analyzer.inlayHints.foreground.${hintKind}Hints`); | 55 | const fg = new vscode.ThemeColor(`rust_analyzer.inlayHints.foreground.${hintKind}Hints`); |