aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/assists/src/handlers/auto_import.rs55
-rw-r--r--crates/ide/src/syntax_highlighting.rs214
-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/project_model/src/sysroot.rs4
-rw-r--r--docs/dev/style.md32
-rw-r--r--editors/code/src/inlay_hints.ts6
7 files changed, 326 insertions, 192 deletions
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/ide/src/syntax_highlighting.rs b/crates/ide/src/syntax_highlighting.rs
index 6aafd6fd5..527888306 100644
--- a/crates/ide/src/syntax_highlighting.rs
+++ b/crates/ide/src/syntax_highlighting.rs
@@ -1,6 +1,8 @@
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
@@ -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()?;
@@ -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/project_model/src/sysroot.rs b/crates/project_model/src/sysroot.rs
index e529e07b0..b2ff98a15 100644
--- a/crates/project_model/src/sysroot.rs
+++ b/crates/project_model/src/sysroot.rs
@@ -49,6 +49,7 @@ impl Sysroot {
49 } 49 }
50 50
51 pub fn discover(cargo_toml: &AbsPath) -> Result<Sysroot> { 51 pub fn discover(cargo_toml: &AbsPath) -> Result<Sysroot> {
52 log::debug!("Discovering sysroot for {}", cargo_toml.display());
52 let current_dir = cargo_toml.parent().unwrap(); 53 let current_dir = cargo_toml.parent().unwrap();
53 let sysroot_src_dir = discover_sysroot_src_dir(current_dir)?; 54 let sysroot_src_dir = discover_sysroot_src_dir(current_dir)?;
54 let res = Sysroot::load(&sysroot_src_dir)?; 55 let res = Sysroot::load(&sysroot_src_dir)?;
@@ -115,12 +116,14 @@ fn discover_sysroot_src_dir(current_dir: &AbsPath) -> Result<AbsPathBuf> {
115 if let Ok(path) = env::var("RUST_SRC_PATH") { 116 if let Ok(path) = env::var("RUST_SRC_PATH") {
116 let path = AbsPathBuf::try_from(path.as_str()) 117 let path = AbsPathBuf::try_from(path.as_str())
117 .map_err(|path| format_err!("RUST_SRC_PATH must be absolute: {}", path.display()))?; 118 .map_err(|path| format_err!("RUST_SRC_PATH must be absolute: {}", path.display()))?;
119 log::debug!("Discovered sysroot by RUST_SRC_PATH: {}", path.display());
118 return Ok(path); 120 return Ok(path);
119 } 121 }
120 122
121 let sysroot_path = { 123 let sysroot_path = {
122 let mut rustc = Command::new(toolchain::rustc()); 124 let mut rustc = Command::new(toolchain::rustc());
123 rustc.current_dir(current_dir).args(&["--print", "sysroot"]); 125 rustc.current_dir(current_dir).args(&["--print", "sysroot"]);
126 log::debug!("Discovering sysroot by {:?}", rustc);
124 let stdout = utf8_stdout(rustc)?; 127 let stdout = utf8_stdout(rustc)?;
125 AbsPathBuf::assert(PathBuf::from(stdout)) 128 AbsPathBuf::assert(PathBuf::from(stdout))
126 }; 129 };
@@ -150,6 +153,7 @@ fn get_rust_src(sysroot_path: &AbsPath) -> Option<AbsPathBuf> {
150 // FIXME: remove `src` when 1.47 comes out 153 // FIXME: remove `src` when 1.47 comes out
151 // https://github.com/rust-lang/rust/pull/73265 154 // https://github.com/rust-lang/rust/pull/73265
152 let rust_src = sysroot_path.join("lib/rustlib/src/rust"); 155 let rust_src = sysroot_path.join("lib/rustlib/src/rust");
156 log::debug!("Checking sysroot (looking for `library` and `src` dirs): {}", rust_src.display());
153 ["library", "src"].iter().map(|it| rust_src.join(it)).find(|it| it.exists()) 157 ["library", "src"].iter().map(|it| rust_src.join(it)).find(|it| it.exists())
154} 158}
155 159
diff --git a/docs/dev/style.md b/docs/dev/style.md
index 59067d234..20f1b6253 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`.
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`);