aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/assists/src/handlers/add_turbo_fish.rs4
-rw-r--r--crates/assists/src/handlers/auto_import.rs55
-rw-r--r--crates/assists/src/handlers/expand_glob_import.rs4
-rw-r--r--crates/hir_ty/src/infer.rs24
-rw-r--r--crates/hir_ty/src/infer/expr.rs18
-rw-r--r--crates/hir_ty/src/tests/simple.rs89
-rw-r--r--crates/ide/src/diagnostics.rs95
-rw-r--r--crates/ide/src/diagnostics/field_shorthand.rs206
-rw-r--r--crates/ide/src/doc_links.rs6
-rw-r--r--crates/ide/src/goto_definition.rs8
-rw-r--r--crates/ide/src/hover.rs29
-rw-r--r--crates/ide/src/references.rs6
-rw-r--r--crates/ide/src/references/rename.rs6
-rw-r--r--crates/ide/src/syntax_highlighting.rs224
-rw-r--r--crates/ide/src/syntax_highlighting/format.rs78
-rw-r--r--crates/ide/src/syntax_highlighting/macro_rules.rs129
-rw-r--r--crates/ide_db/src/defs.rs377
-rw-r--r--crates/ide_db/src/imports_locator.rs4
-rw-r--r--crates/ide_db/src/search.rs12
-rw-r--r--docs/dev/style.md105
-rw-r--r--editors/code/src/inlay_hints.ts6
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 @@
1use ide_db::defs::{classify_name_ref, Definition, NameRefClass}; 1use ide_db::defs::{Definition, NameRefClass};
2use syntax::{ast, AstNode, SyntaxKind, T}; 2use syntax::{ast, AstNode, SyntaxKind, T};
3use test_utils::mark; 3use 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 @@
1use either::Either; 1use either::Either;
2use hir::{AssocItem, MacroDef, Module, ModuleDef, Name, PathResolution, ScopeDef}; 2use hir::{AssocItem, MacroDef, Module, ModuleDef, Name, PathResolution, ScopeDef};
3use ide_db::{ 3use ide_db::{
4 defs::{classify_name_ref, Definition, NameRefClass}, 4 defs::{Definition, NameRefClass},
5 search::SearchScope, 5 search::SearchScope,
6}; 6};
7use syntax::{ 7use 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;
22use hir_def::{ 22use 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};
13use hir_expand::name::{name, Name}; 13use hir_expand::name::{name, Name};
14use syntax::ast::RangeOp; 14use syntax::ast::RangeOp;
15use test_utils::mark;
15 16
16use crate::{ 17use 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 @@
1use expect_test::expect; 1use expect_test::expect;
2use test_utils::mark;
2 3
3use super::{check_infer, check_types}; 4use 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]
2231fn 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
7mod fixes; 7mod fixes;
8mod field_shorthand;
8 9
9use std::cell::RefCell; 10use 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
191fn 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)]
228mod tests { 193mod 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#"
726struct A { a: &'static str }
727fn main() { A { a: "hello" } }
728"#,
729 );
730 check_no_diagnostics(
731 r#"
732struct A(usize);
733fn main() { A { 0: 0 } }
734"#,
735 );
736
737 check_fix(
738 r#"
739struct A { a: &'static str }
740fn main() {
741 let a = "haha";
742 A { a<|>: a }
743}
744"#,
745 r#"
746struct A { a: &'static str }
747fn main() {
748 let a = "haha";
749 A { a }
750}
751"#,
752 );
753
754 check_fix(
755 r#"
756struct A { a: &'static str, b: &'static str }
757fn main() {
758 let a = "haha";
759 let b = "bb";
760 A { a<|>: a, b }
761}
762"#,
763 r#"
764struct A { a: &'static str, b: &'static str }
765fn 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
4use base_db::FileId;
5use ide_db::source_change::SourceFileEdit;
6use syntax::{ast, match_ast, AstNode, SyntaxNode};
7use text_edit::TextEdit;
8
9use crate::{Diagnostic, Fix, Severity};
10
11pub(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
21fn 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
63fn 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)]
106mod 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#"
113struct A { a: &'static str }
114fn main() { A { a: "hello" } }
115"#,
116 );
117 check_no_diagnostics(
118 r#"
119struct A(usize);
120fn main() { A { 0: 0 } }
121"#,
122 );
123
124 check_fix(
125 r#"
126struct A { a: &'static str }
127fn main() {
128 let a = "haha";
129 A { a<|>: a }
130}
131"#,
132 r#"
133struct A { a: &'static str }
134fn main() {
135 let a = "haha";
136 A { a }
137}
138"#,
139 );
140
141 check_fix(
142 r#"
143struct A { a: &'static str, b: &'static str }
144fn main() {
145 let a = "haha";
146 let b = "bb";
147 A { a<|>: a, b }
148}
149"#,
150 r#"
151struct A { a: &'static str, b: &'static str }
152fn 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#"
165struct A { a: &'static str }
166fn f(a: A) { let A { a: hello } = a; }
167"#,
168 );
169 check_no_diagnostics(
170 r#"
171struct A(usize);
172fn f(a: A) { let A { 0: 0 } = a; }
173"#,
174 );
175
176 check_fix(
177 r#"
178struct A { a: &'static str }
179fn f(a: A) {
180 let A { a<|>: a } = a;
181}
182"#,
183 r#"
184struct A { a: &'static str }
185fn f(a: A) {
186 let A { a } = a;
187}
188"#,
189 );
190
191 check_fix(
192 r#"
193struct A { a: &'static str, b: &'static str }
194fn f(a: A) {
195 let A { a<|>: a, b } = a;
196}
197"#,
198 r#"
199struct A { a: &'static str, b: &'static str }
200fn 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};
16use ide_db::{ 16use ide_db::{
17 defs::{classify_name, classify_name_ref, Definition}, 17 defs::{Definition, NameClass, NameRefClass},
18 RootDatabase, 18 RootDatabase,
19}; 19};
20use syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset, T}; 20use 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 @@
1use hir::Semantics; 1use hir::Semantics;
2use ide_db::{ 2use ide_db::{
3 defs::{classify_name, classify_name_ref}, 3 defs::{NameClass, NameRefClass},
4 symbol_index, RootDatabase, 4 symbol_index, RootDatabase,
5}; 5};
6use syntax::{ 6use 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};
6use ide_db::{ 6use ide_db::{
7 defs::{classify_name, classify_name_ref, Definition}, 7 defs::{Definition, NameClass, NameRefClass},
8 RootDatabase, 8 RootDatabase,
9}; 9};
10use itertools::Itertools; 10use 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#"
3240struct S {
3241 f: i32,
3242}
3243
3244fn 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
14use hir::Semantics; 14use hir::Semantics;
15use ide_db::{ 15use 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 @@
3use base_db::SourceDatabaseExt; 3use base_db::SourceDatabaseExt;
4use hir::{Module, ModuleDef, ModuleSource, Semantics}; 4use hir::{Module, ModuleDef, ModuleSource, Semantics};
5use ide_db::{ 5use 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 @@
1mod tags; 1mod format;
2mod html; 2mod html;
3mod injection; 3mod injection;
4mod macro_rules;
5mod tags;
4#[cfg(test)] 6#[cfg(test)]
5mod tests; 7mod tests;
6 8
7use hir::{Local, Name, Semantics, VariantDef}; 9use hir::{Local, Name, Semantics, VariantDef};
8use ide_db::{ 10use ide_db::{
9 defs::{classify_name, classify_name_ref, Definition, NameClass, NameRefClass}, 11 defs::{Definition, NameClass, NameRefClass},
10 RootDatabase, 12 RootDatabase,
11}; 13};
12use rustc_hash::FxHashMap; 14use 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
20use crate::FileId; 22use crate::{
23 syntax_highlighting::{format::FormatStringHighlighter, macro_rules::MacroRulesHighlighter},
24 FileId,
25};
21 26
22use ast::FormatSpecifier;
23pub(crate) use html::highlight_as_html; 27pub(crate) use html::highlight_as_html;
24pub use tags::{Highlight, HighlightModifier, HighlightModifiers, HighlightTag}; 28pub 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
439fn 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
457fn macro_call_range(macro_call: &ast::MacroCall) -> Option<TextRange> { 387fn 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
938struct 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
947impl 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)]
959enum RuleState {
960 Matcher,
961 Expander,
962 Between,
963 None,
964}
965
966impl 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
977fn 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
1021fn 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.
2use syntax::{
3 ast::{self, FormatSpecifier, HasFormatSpecifier},
4 AstNode, AstToken, SyntaxElement, SyntaxKind, SyntaxNode, TextRange,
5};
6
7use crate::{syntax_highlighting::HighlightedRangeStack, HighlightTag, HighlightedRange};
8
9#[derive(Default)]
10pub(super) struct FormatStringHighlighter {
11 format_string: Option<SyntaxElement>,
12}
13
14impl 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
62fn 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!.
2use syntax::{SyntaxElement, SyntaxKind, SyntaxToken, TextRange, T};
3
4use crate::{HighlightTag, HighlightedRange};
5
6#[derive(Default)]
7pub(super) struct MacroRulesHighlighter {
8 state: Option<MacroMatcherParseState>,
9}
10
11impl 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
38struct 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
47impl 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)]
59enum RuleState {
60 Matcher,
61 Expander,
62 Between,
63 None,
64}
65
66impl 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
77fn 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
118fn 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 {
81pub enum NameClass { 81pub 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
92impl NameClass { 93impl 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
111pub 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
229pub enum NameRefClass { 235pub 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
235impl NameRefClass { 241impl 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`.
247pub 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(&macro_call) { 324 if let Some(macro_def) = sema.resolve_macro_call(&macro_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
330impl From<PathResolution> for Definition { 343impl 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};
5use syntax::{ast, AstNode, SyntaxKind::NAME}; 5use syntax::{ast, AstNode, SyntaxKind::NAME};
6 6
7use crate::{ 7use 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
15use crate::defs::NameClass; 15use crate::defs::NameClass;
16use crate::{ 16use 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
191Prefer `Default` to zero-argument `new` function
192
193```rust
194// Good
195#[derive(Default)]
196struct Foo {
197 bar: Option<Bar>
198}
199
200// Not as good
201struct Foo {
202 bar: Option<Bar>
203}
204
205impl Foo {
206 fn new() -> Foo {
207 Foo { bar: None }
208 }
209}
210```
211
212Prefer `Default` even it has to be implemented manually.
213
189## Avoid Monomorphization 214## Avoid Monomorphization
190 215
191Rust 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*. 216Rust 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
226Avoid writing code which is slower than it needs to be. 253Avoid writing code which is slower than it needs to be.
227Don't allocate a `Vec` where an iterator would do, don't allocate strings needlessly. 254Don'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
245If allocation is inevitable, let the caller allocate the resource: 274If 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
289This is better because it reveals the costs.
290It is also more efficient when the caller already owns the allocation.
291
260## Collection types 292## Collection types
261 293
262Prefer `rustc_hash::FxHashMap` and `rustc_hash::FxHashSet` instead of the ones in `std::collections`. 294Prefer `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
335Specifically, if all items except one are private, always put the non-private item on top. 367Specifically, if all items except one are private, always put the non-private item on top.
336 368
337Put `struct`s and `enum`s first, functions and impls last.
338
339Do
340
341```rust 369```rust
342// Good 370// Good
343struct Foo { 371pub(crate) fn frobnicate() {
344 bars: Vec<Bar> 372 Helper::act()
345} 373}
346 374
347struct Bar; 375#[derive(Default)]
376struct Helper { stuff: i32 }
377
378impl Helper {
379 fn act(&self) {
380
381 }
382}
383
384// Not as good
385#[derive(Default)]
386struct Helper { stuff: i32 }
387
388pub(crate) fn frobnicate() {
389 Helper::act()
390}
391
392impl Helper {
393 fn act(&self) {
394
395 }
396}
348``` 397```
349 398
350rather than 399If there's a mixture of private and public items, put public items first.
400If function bodies are folded in the editor, the source code should read as documentation for the public API.
401
402Put `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
406struct Parent {
407 children: Vec<Child>
408}
409
410struct Child;
411
412impl Parent {
413}
414
415impl Child {
416}
417
353// Not as good 418// Not as good
354struct Bar; 419struct Child;
355 420
356struct Foo { 421impl Child {
357 bars: Vec<Bar> 422}
423
424struct Parent {
425 children: Vec<Child>
426}
427
428impl 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
445Many names in rust-analyzer conflict with keywords.
446We use mangled names instead of `r#ident` syntax:
447
448```
449struct -> strukt
450crate -> krate
451impl -> imp
452trait -> trait_
453fn -> func
454enum -> enum_
455mod -> 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");
44const chainingHints = createHintStyle("chaining"); 44const chainingHints = createHintStyle("chaining");
45 45
46function createHintStyle(hintKind: "type" | "parameter" | "chaining") { 46function 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`);