aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/hir_def/src/find_path.rs2
-rw-r--r--crates/ide/src/hover.rs51
-rw-r--r--crates/ide/src/lib.rs4
-rw-r--r--crates/ide/src/markdown_remove.rs23
-rw-r--r--crates/ide/src/typing/on_enter.rs29
-rw-r--r--crates/mbe/src/subtree_source.rs30
-rw-r--r--crates/rust-analyzer/src/config.rs6
-rw-r--r--crates/rust-analyzer/src/handlers.rs6
-rw-r--r--crates/rust-analyzer/src/lsp_utils.rs11
-rw-r--r--crates/rust-analyzer/src/main_loop.rs2
-rw-r--r--crates/syntax/src/parsing/lexer.rs23
-rw-r--r--crates/syntax/src/ptr.rs2
-rw-r--r--crates/syntax/src/validation.rs45
-rw-r--r--crates/syntax/test_data/parser/err/0046_ambiguous_trait_object.rast192
-rw-r--r--crates/syntax/test_data/parser/err/0046_ambiguous_trait_object.rs6
-rw-r--r--crates/syntax/test_data/parser/ok/0069_multi_trait_object.rast200
-rw-r--r--crates/syntax/test_data/parser/ok/0069_multi_trait_object.rs6
-rw-r--r--docs/dev/style.md388
-rw-r--r--docs/user/manual.adoc17
19 files changed, 835 insertions, 208 deletions
diff --git a/crates/hir_def/src/find_path.rs b/crates/hir_def/src/find_path.rs
index 9106ed45f..02613c4c4 100644
--- a/crates/hir_def/src/find_path.rs
+++ b/crates/hir_def/src/find_path.rs
@@ -222,6 +222,7 @@ fn find_path_inner(
222 best_path_len - 1, 222 best_path_len - 1,
223 prefixed, 223 prefixed,
224 )?; 224 )?;
225 mark::hit!(partially_imported);
225 path.segments.push(info.path.segments.last().unwrap().clone()); 226 path.segments.push(info.path.segments.last().unwrap().clone());
226 Some(path) 227 Some(path)
227 }) 228 })
@@ -515,6 +516,7 @@ mod tests {
515 516
516 #[test] 517 #[test]
517 fn partially_imported() { 518 fn partially_imported() {
519 mark::check!(partially_imported);
518 // Tests that short paths are used even for external items, when parts of the path are 520 // Tests that short paths are used even for external items, when parts of the path are
519 // already in scope. 521 // already in scope.
520 let code = r#" 522 let code = r#"
diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs
index 4521d72cc..53265488e 100644
--- a/crates/ide/src/hover.rs
+++ b/crates/ide/src/hover.rs
@@ -15,6 +15,7 @@ use test_utils::mark;
15use crate::{ 15use crate::{
16 display::{macro_label, ShortLabel, ToNav, TryToNav}, 16 display::{macro_label, ShortLabel, ToNav, TryToNav},
17 link_rewrite::{remove_links, rewrite_links}, 17 link_rewrite::{remove_links, rewrite_links},
18 markdown_remove::remove_markdown,
18 markup::Markup, 19 markup::Markup,
19 runnables::runnable, 20 runnables::runnable,
20 FileId, FilePosition, NavigationTarget, RangeInfo, Runnable, 21 FileId, FilePosition, NavigationTarget, RangeInfo, Runnable,
@@ -27,6 +28,7 @@ pub struct HoverConfig {
27 pub debug: bool, 28 pub debug: bool,
28 pub goto_type_def: bool, 29 pub goto_type_def: bool,
29 pub links_in_hover: bool, 30 pub links_in_hover: bool,
31 pub markdown: bool,
30} 32}
31 33
32impl Default for HoverConfig { 34impl Default for HoverConfig {
@@ -37,6 +39,7 @@ impl Default for HoverConfig {
37 debug: true, 39 debug: true,
38 goto_type_def: true, 40 goto_type_def: true,
39 links_in_hover: true, 41 links_in_hover: true,
42 markdown: true,
40 } 43 }
41 } 44 }
42} 45}
@@ -48,6 +51,7 @@ impl HoverConfig {
48 debug: false, 51 debug: false,
49 goto_type_def: false, 52 goto_type_def: false,
50 links_in_hover: true, 53 links_in_hover: true,
54 markdown: true,
51 }; 55 };
52 56
53 pub fn any(&self) -> bool { 57 pub fn any(&self) -> bool {
@@ -91,6 +95,7 @@ pub(crate) fn hover(
91 db: &RootDatabase, 95 db: &RootDatabase,
92 position: FilePosition, 96 position: FilePosition,
93 links_in_hover: bool, 97 links_in_hover: bool,
98 markdown: bool,
94) -> Option<RangeInfo<HoverResult>> { 99) -> Option<RangeInfo<HoverResult>> {
95 let sema = Semantics::new(db); 100 let sema = Semantics::new(db);
96 let file = sema.parse(position.file_id).syntax().clone(); 101 let file = sema.parse(position.file_id).syntax().clone();
@@ -109,7 +114,9 @@ pub(crate) fn hover(
109 }; 114 };
110 if let Some(definition) = definition { 115 if let Some(definition) = definition {
111 if let Some(markup) = hover_for_definition(db, definition) { 116 if let Some(markup) = hover_for_definition(db, definition) {
112 let markup = if links_in_hover { 117 let markup = if !markdown {
118 remove_markdown(&markup.as_str())
119 } else if links_in_hover {
113 rewrite_links(db, &markup.as_str(), &definition) 120 rewrite_links(db, &markup.as_str(), &definition)
114 } else { 121 } else {
115 remove_links(&markup.as_str()) 122 remove_links(&markup.as_str())
@@ -147,7 +154,11 @@ pub(crate) fn hover(
147 } 154 }
148 }; 155 };
149 156
150 res.markup = Markup::fenced_block(&ty.display(db)); 157 res.markup = if markdown {
158 Markup::fenced_block(&ty.display(db))
159 } else {
160 ty.display(db).to_string().into()
161 };
151 let range = sema.original_range(&node).range; 162 let range = sema.original_range(&node).range;
152 Some(RangeInfo::new(range, res)) 163 Some(RangeInfo::new(range, res))
153} 164}
@@ -383,12 +394,12 @@ mod tests {
383 394
384 fn check_hover_no_result(ra_fixture: &str) { 395 fn check_hover_no_result(ra_fixture: &str) {
385 let (analysis, position) = fixture::position(ra_fixture); 396 let (analysis, position) = fixture::position(ra_fixture);
386 assert!(analysis.hover(position, true).unwrap().is_none()); 397 assert!(analysis.hover(position, true, true).unwrap().is_none());
387 } 398 }
388 399
389 fn check(ra_fixture: &str, expect: Expect) { 400 fn check(ra_fixture: &str, expect: Expect) {
390 let (analysis, position) = fixture::position(ra_fixture); 401 let (analysis, position) = fixture::position(ra_fixture);
391 let hover = analysis.hover(position, true).unwrap().unwrap(); 402 let hover = analysis.hover(position, true, true).unwrap().unwrap();
392 403
393 let content = analysis.db.file_text(position.file_id); 404 let content = analysis.db.file_text(position.file_id);
394 let hovered_element = &content[hover.range]; 405 let hovered_element = &content[hover.range];
@@ -399,7 +410,18 @@ mod tests {
399 410
400 fn check_hover_no_links(ra_fixture: &str, expect: Expect) { 411 fn check_hover_no_links(ra_fixture: &str, expect: Expect) {
401 let (analysis, position) = fixture::position(ra_fixture); 412 let (analysis, position) = fixture::position(ra_fixture);
402 let hover = analysis.hover(position, false).unwrap().unwrap(); 413 let hover = analysis.hover(position, false, true).unwrap().unwrap();
414
415 let content = analysis.db.file_text(position.file_id);
416 let hovered_element = &content[hover.range];
417
418 let actual = format!("*{}*\n{}\n", hovered_element, hover.info.markup);
419 expect.assert_eq(&actual)
420 }
421
422 fn check_hover_no_markdown(ra_fixture: &str, expect: Expect) {
423 let (analysis, position) = fixture::position(ra_fixture);
424 let hover = analysis.hover(position, true, false).unwrap().unwrap();
403 425
404 let content = analysis.db.file_text(position.file_id); 426 let content = analysis.db.file_text(position.file_id);
405 let hovered_element = &content[hover.range]; 427 let hovered_element = &content[hover.range];
@@ -410,7 +432,7 @@ mod tests {
410 432
411 fn check_actions(ra_fixture: &str, expect: Expect) { 433 fn check_actions(ra_fixture: &str, expect: Expect) {
412 let (analysis, position) = fixture::position(ra_fixture); 434 let (analysis, position) = fixture::position(ra_fixture);
413 let hover = analysis.hover(position, true).unwrap().unwrap(); 435 let hover = analysis.hover(position, true, true).unwrap().unwrap();
414 expect.assert_debug_eq(&hover.info.actions) 436 expect.assert_debug_eq(&hover.info.actions)
415 } 437 }
416 438
@@ -434,6 +456,23 @@ fn main() {
434 } 456 }
435 457
436 #[test] 458 #[test]
459 fn hover_remove_markdown_if_configured() {
460 check_hover_no_markdown(
461 r#"
462pub fn foo() -> u32 { 1 }
463
464fn main() {
465 let foo_test = foo()<|>;
466}
467"#,
468 expect![[r#"
469 *foo()*
470 u32
471 "#]],
472 );
473 }
474
475 #[test]
437 fn hover_shows_long_type_of_an_expression() { 476 fn hover_shows_long_type_of_an_expression() {
438 check( 477 check(
439 r#" 478 r#"
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs
index 1aa673cf8..57f3581b6 100644
--- a/crates/ide/src/lib.rs
+++ b/crates/ide/src/lib.rs
@@ -46,6 +46,7 @@ mod syntax_highlighting;
46mod syntax_tree; 46mod syntax_tree;
47mod typing; 47mod typing;
48mod link_rewrite; 48mod link_rewrite;
49mod markdown_remove;
49 50
50use std::sync::Arc; 51use std::sync::Arc;
51 52
@@ -376,8 +377,9 @@ impl Analysis {
376 &self, 377 &self,
377 position: FilePosition, 378 position: FilePosition,
378 links_in_hover: bool, 379 links_in_hover: bool,
380 markdown: bool,
379 ) -> Cancelable<Option<RangeInfo<HoverResult>>> { 381 ) -> Cancelable<Option<RangeInfo<HoverResult>>> {
380 self.with_db(|db| hover::hover(db, position, links_in_hover)) 382 self.with_db(|db| hover::hover(db, position, links_in_hover, markdown))
381 } 383 }
382 384
383 /// Computes parameter information for the given call expression. 385 /// Computes parameter information for the given call expression.
diff --git a/crates/ide/src/markdown_remove.rs b/crates/ide/src/markdown_remove.rs
new file mode 100644
index 000000000..02ad39dfb
--- /dev/null
+++ b/crates/ide/src/markdown_remove.rs
@@ -0,0 +1,23 @@
1//! Removes markdown from strings.
2
3use pulldown_cmark::{Event, Parser, Tag};
4
5/// Removes all markdown, keeping the text and code blocks
6///
7/// Currently limited in styling, i.e. no ascii tables or lists
8pub fn remove_markdown(markdown: &str) -> String {
9 let mut out = String::new();
10 let parser = Parser::new(markdown);
11
12 for event in parser {
13 match event {
14 Event::Text(text) | Event::Code(text) => out.push_str(&text),
15 Event::SoftBreak | Event::HardBreak | Event::Rule | Event::End(Tag::CodeBlock(_)) => {
16 out.push('\n')
17 }
18 _ => {}
19 }
20 }
21
22 out
23}
diff --git a/crates/ide/src/typing/on_enter.rs b/crates/ide/src/typing/on_enter.rs
index a0dc4b9df..98adef1d6 100644
--- a/crates/ide/src/typing/on_enter.rs
+++ b/crates/ide/src/typing/on_enter.rs
@@ -51,12 +51,12 @@ pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option<Text
51 return None; 51 return None;
52 } 52 }
53 53
54 let mut remove_last_space = false; 54 let mut remove_trailing_whitespace = false;
55 // Continuing single-line non-doc comments (like this one :) ) is annoying 55 // Continuing single-line non-doc comments (like this one :) ) is annoying
56 if prefix == "//" && comment_range.end() == position.offset { 56 if prefix == "//" && comment_range.end() == position.offset {
57 if comment.text().ends_with(' ') { 57 if comment.text().ends_with(' ') {
58 mark::hit!(continues_end_of_line_comment_with_space); 58 mark::hit!(continues_end_of_line_comment_with_space);
59 remove_last_space = true; 59 remove_trailing_whitespace = true;
60 } else if !followed_by_comment(&comment) { 60 } else if !followed_by_comment(&comment) {
61 return None; 61 return None;
62 } 62 }
@@ -64,8 +64,10 @@ pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option<Text
64 64
65 let indent = node_indent(&file, comment.syntax())?; 65 let indent = node_indent(&file, comment.syntax())?;
66 let inserted = format!("\n{}{} $0", indent, prefix); 66 let inserted = format!("\n{}{} $0", indent, prefix);
67 let delete = if remove_last_space { 67 let delete = if remove_trailing_whitespace {
68 TextRange::new(position.offset - TextSize::of(' '), position.offset) 68 let trimmed_len = comment.text().trim_end().len() as u32;
69 let trailing_whitespace_len = comment.text().len() as u32 - trimmed_len;
70 TextRange::new(position.offset - TextSize::from(trailing_whitespace_len), position.offset)
69 } else { 71 } else {
70 TextRange::empty(position.offset) 72 TextRange::empty(position.offset)
71 }; 73 };
@@ -253,4 +255,23 @@ fn main() {
253"#, 255"#,
254 ); 256 );
255 } 257 }
258
259 #[test]
260 fn trims_all_trailing_whitespace() {
261 do_check(
262 "
263fn main() {
264 // Fix me \t\t <|>
265 let x = 1 + 1;
266}
267",
268 "
269fn main() {
270 // Fix me
271 // $0
272 let x = 1 + 1;
273}
274",
275 );
276 }
256} 277}
diff --git a/crates/mbe/src/subtree_source.rs b/crates/mbe/src/subtree_source.rs
index 41461b315..396ce8b16 100644
--- a/crates/mbe/src/subtree_source.rs
+++ b/crates/mbe/src/subtree_source.rs
@@ -155,9 +155,14 @@ fn convert_delim(d: Option<tt::DelimiterKind>, closing: bool) -> TtToken {
155} 155}
156 156
157fn convert_literal(l: &tt::Literal) -> TtToken { 157fn convert_literal(l: &tt::Literal) -> TtToken {
158 let kind = lex_single_syntax_kind(&l.text) 158 let is_negated = l.text.starts_with('-');
159 let inner_text = &l.text[if is_negated { 1 } else { 0 }..];
160
161 let kind = lex_single_syntax_kind(inner_text)
159 .map(|(kind, _error)| kind) 162 .map(|(kind, _error)| kind)
160 .filter(|kind| kind.is_literal()) 163 .filter(|kind| {
164 kind.is_literal() && (!is_negated || matches!(kind, FLOAT_NUMBER | INT_NUMBER))
165 })
161 .unwrap_or_else(|| panic!("Fail to convert given literal {:#?}", &l)); 166 .unwrap_or_else(|| panic!("Fail to convert given literal {:#?}", &l));
162 167
163 TtToken { kind, is_joint_to_next: false, text: l.text.clone() } 168 TtToken { kind, is_joint_to_next: false, text: l.text.clone() }
@@ -195,3 +200,24 @@ fn convert_leaf(leaf: &tt::Leaf) -> TtToken {
195 tt::Leaf::Punct(punct) => convert_punct(*punct), 200 tt::Leaf::Punct(punct) => convert_punct(*punct),
196 } 201 }
197} 202}
203
204#[cfg(test)]
205mod tests {
206 use super::{convert_literal, TtToken};
207 use syntax::{SmolStr, SyntaxKind};
208
209 #[test]
210 fn test_negative_literal() {
211 assert_eq!(
212 convert_literal(&tt::Literal {
213 id: tt::TokenId::unspecified(),
214 text: SmolStr::new("-42.0")
215 }),
216 TtToken {
217 kind: SyntaxKind::FLOAT_NUMBER,
218 is_joint_to_next: false,
219 text: SmolStr::new("-42.0")
220 }
221 );
222 }
223}
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index dcbc11c14..1b9b24698 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -15,7 +15,7 @@ use ide::{
15 AssistConfig, CompletionConfig, DiagnosticsConfig, HoverConfig, InlayHintsConfig, 15 AssistConfig, CompletionConfig, DiagnosticsConfig, HoverConfig, InlayHintsConfig,
16 MergeBehaviour, 16 MergeBehaviour,
17}; 17};
18use lsp_types::ClientCapabilities; 18use lsp_types::{ClientCapabilities, MarkupKind};
19use project_model::{CargoConfig, ProjectJson, ProjectJsonData, ProjectManifest}; 19use project_model::{CargoConfig, ProjectJson, ProjectJsonData, ProjectManifest};
20use rustc_hash::FxHashSet; 20use rustc_hash::FxHashSet;
21use serde::Deserialize; 21use serde::Deserialize;
@@ -333,6 +333,7 @@ impl Config {
333 debug: data.hoverActions_enable && data.hoverActions_debug, 333 debug: data.hoverActions_enable && data.hoverActions_debug,
334 goto_type_def: data.hoverActions_enable && data.hoverActions_gotoTypeDef, 334 goto_type_def: data.hoverActions_enable && data.hoverActions_gotoTypeDef,
335 links_in_hover: data.hoverActions_linksInHover, 335 links_in_hover: data.hoverActions_linksInHover,
336 markdown: true,
336 }; 337 };
337 338
338 log::info!("Config::update() = {:#?}", self); 339 log::info!("Config::update() = {:#?}", self);
@@ -340,6 +341,9 @@ impl Config {
340 341
341 pub fn update_caps(&mut self, caps: &ClientCapabilities) { 342 pub fn update_caps(&mut self, caps: &ClientCapabilities) {
342 if let Some(doc_caps) = caps.text_document.as_ref() { 343 if let Some(doc_caps) = caps.text_document.as_ref() {
344 if let Some(value) = doc_caps.hover.as_ref().and_then(|it| it.content_format.as_ref()) {
345 self.hover.markdown = value.contains(&MarkupKind::Markdown)
346 }
343 if let Some(value) = doc_caps.definition.as_ref().and_then(|it| it.link_support) { 347 if let Some(value) = doc_caps.definition.as_ref().and_then(|it| it.link_support) {
344 self.client_caps.location_link = value; 348 self.client_caps.location_link = value;
345 } 349 }
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs
index e970abb7c..468655f9c 100644
--- a/crates/rust-analyzer/src/handlers.rs
+++ b/crates/rust-analyzer/src/handlers.rs
@@ -618,7 +618,11 @@ pub(crate) fn handle_hover(
618) -> Result<Option<lsp_ext::Hover>> { 618) -> Result<Option<lsp_ext::Hover>> {
619 let _p = profile::span("handle_hover"); 619 let _p = profile::span("handle_hover");
620 let position = from_proto::file_position(&snap, params.text_document_position_params)?; 620 let position = from_proto::file_position(&snap, params.text_document_position_params)?;
621 let info = match snap.analysis.hover(position, snap.config.hover.links_in_hover)? { 621 let info = match snap.analysis.hover(
622 position,
623 snap.config.hover.links_in_hover,
624 snap.config.hover.markdown,
625 )? {
622 None => return Ok(None), 626 None => return Ok(None),
623 Some(info) => info, 627 Some(info) => info,
624 }; 628 };
diff --git a/crates/rust-analyzer/src/lsp_utils.rs b/crates/rust-analyzer/src/lsp_utils.rs
index 85c661571..bd888f634 100644
--- a/crates/rust-analyzer/src/lsp_utils.rs
+++ b/crates/rust-analyzer/src/lsp_utils.rs
@@ -25,8 +25,9 @@ pub(crate) enum Progress {
25} 25}
26 26
27impl Progress { 27impl Progress {
28 pub(crate) fn percentage(done: usize, total: usize) -> f64 { 28 pub(crate) fn fraction(done: usize, total: usize) -> f64 {
29 (done as f64 / total.max(1) as f64) * 100.0 29 assert!(done <= total);
30 done as f64 / total.max(1) as f64
30 } 31 }
31} 32}
32 33
@@ -43,11 +44,15 @@ impl GlobalState {
43 title: &str, 44 title: &str,
44 state: Progress, 45 state: Progress,
45 message: Option<String>, 46 message: Option<String>,
46 percentage: Option<f64>, 47 fraction: Option<f64>,
47 ) { 48 ) {
48 if !self.config.client_caps.work_done_progress { 49 if !self.config.client_caps.work_done_progress {
49 return; 50 return;
50 } 51 }
52 let percentage = fraction.map(|f| {
53 assert!(0.0 <= f && f <= 1.0);
54 f * 100.0
55 });
51 let token = lsp_types::ProgressToken::String(format!("rustAnalyzer/{}", title)); 56 let token = lsp_types::ProgressToken::String(format!("rustAnalyzer/{}", title));
52 let work_done_progress = match state { 57 let work_done_progress = match state {
53 Progress::Begin => { 58 Progress::Begin => {
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs
index c2d0ac791..4b7ac8224 100644
--- a/crates/rust-analyzer/src/main_loop.rs
+++ b/crates/rust-analyzer/src/main_loop.rs
@@ -230,7 +230,7 @@ impl GlobalState {
230 "roots scanned", 230 "roots scanned",
231 state, 231 state,
232 Some(format!("{}/{}", n_done, n_total)), 232 Some(format!("{}/{}", n_done, n_total)),
233 Some(Progress::percentage(n_done, n_total)), 233 Some(Progress::fraction(n_done, n_total)),
234 ) 234 )
235 } 235 }
236 } 236 }
diff --git a/crates/syntax/src/parsing/lexer.rs b/crates/syntax/src/parsing/lexer.rs
index f1202113b..7e38c32cc 100644
--- a/crates/syntax/src/parsing/lexer.rs
+++ b/crates/syntax/src/parsing/lexer.rs
@@ -1,10 +1,10 @@
1//! Lexer analyzes raw input string and produces lexemes (tokens). 1//! Lexer analyzes raw input string and produces lexemes (tokens).
2//! It is just a bridge to `rustc_lexer`. 2//! It is just a bridge to `rustc_lexer`.
3 3
4use rustc_lexer::{LiteralKind as LK, RawStrError};
5
6use std::convert::TryInto; 4use std::convert::TryInto;
7 5
6use rustc_lexer::{LiteralKind as LK, RawStrError};
7
8use crate::{ 8use crate::{
9 SyntaxError, 9 SyntaxError,
10 SyntaxKind::{self, *}, 10 SyntaxKind::{self, *},
@@ -61,17 +61,18 @@ pub fn tokenize(text: &str) -> (Vec<Token>, Vec<SyntaxError>) {
61 (tokens, errors) 61 (tokens, errors)
62} 62}
63 63
64/// Returns `SyntaxKind` and `Option<SyntaxError>` of the first token 64/// Returns `SyntaxKind` and `Option<SyntaxError>` if `text` parses as a single token.
65/// encountered at the beginning of the string.
66/// 65///
67/// Returns `None` if the string contains zero *or two or more* tokens. 66/// Returns `None` if the string contains zero *or two or more* tokens.
68/// The token is malformed if the returned error is not `None`. 67/// The token is malformed if the returned error is not `None`.
69/// 68///
70/// Beware that unescape errors are not checked at tokenization time. 69/// Beware that unescape errors are not checked at tokenization time.
71pub fn lex_single_syntax_kind(text: &str) -> Option<(SyntaxKind, Option<SyntaxError>)> { 70pub fn lex_single_syntax_kind(text: &str) -> Option<(SyntaxKind, Option<SyntaxError>)> {
72 lex_first_token(text) 71 let (first_token, err) = lex_first_token(text)?;
73 .filter(|(token, _)| token.len == TextSize::of(text)) 72 if first_token.len != TextSize::of(text) {
74 .map(|(token, error)| (token.kind, error)) 73 return None;
74 }
75 Some((first_token.kind, err))
75} 76}
76 77
77/// The same as `lex_single_syntax_kind()` but returns only `SyntaxKind` and 78/// The same as `lex_single_syntax_kind()` but returns only `SyntaxKind` and
@@ -79,9 +80,11 @@ pub fn lex_single_syntax_kind(text: &str) -> Option<(SyntaxKind, Option<SyntaxEr
79/// 80///
80/// Beware that unescape errors are not checked at tokenization time. 81/// Beware that unescape errors are not checked at tokenization time.
81pub fn lex_single_valid_syntax_kind(text: &str) -> Option<SyntaxKind> { 82pub fn lex_single_valid_syntax_kind(text: &str) -> Option<SyntaxKind> {
82 lex_first_token(text) 83 let (single_token, err) = lex_single_syntax_kind(text)?;
83 .filter(|(token, error)| !error.is_some() && token.len == TextSize::of(text)) 84 if err.is_some() {
84 .map(|(token, _error)| token.kind) 85 return None;
86 }
87 Some(single_token)
85} 88}
86 89
87/// Returns `SyntaxKind` and `Option<SyntaxError>` of the first token 90/// Returns `SyntaxKind` and `Option<SyntaxError>` of the first token
diff --git a/crates/syntax/src/ptr.rs b/crates/syntax/src/ptr.rs
index ca7957747..d3fb7a5d9 100644
--- a/crates/syntax/src/ptr.rs
+++ b/crates/syntax/src/ptr.rs
@@ -12,6 +12,8 @@ use crate::{AstNode, SyntaxKind, SyntaxNode, TextRange};
12/// specific node across reparses of the same file. 12/// specific node across reparses of the same file.
13#[derive(Debug, Clone, PartialEq, Eq, Hash)] 13#[derive(Debug, Clone, PartialEq, Eq, Hash)]
14pub struct SyntaxNodePtr { 14pub struct SyntaxNodePtr {
15 // Don't expose this field further. At some point, we might want to replace
16 // range with node id.
15 pub(crate) range: TextRange, 17 pub(crate) range: TextRange,
16 kind: SyntaxKind, 18 kind: SyntaxKind,
17} 19}
diff --git a/crates/syntax/src/validation.rs b/crates/syntax/src/validation.rs
index 2dddaf09a..0f9a5e8ae 100644
--- a/crates/syntax/src/validation.rs
+++ b/crates/syntax/src/validation.rs
@@ -3,10 +3,11 @@
3mod block; 3mod block;
4 4
5use crate::{ 5use crate::{
6 ast, match_ast, AstNode, SyntaxError, 6 algo, ast, match_ast, AstNode, SyntaxError,
7 SyntaxKind::{BYTE, BYTE_STRING, CHAR, CONST, FN, INT_NUMBER, STRING, TYPE_ALIAS}, 7 SyntaxKind::{BYTE, BYTE_STRING, CHAR, CONST, FN, INT_NUMBER, STRING, TYPE_ALIAS},
8 SyntaxNode, SyntaxToken, TextSize, T, 8 SyntaxNode, SyntaxToken, TextSize, T,
9}; 9};
10use rowan::Direction;
10use rustc_lexer::unescape::{ 11use rustc_lexer::unescape::{
11 self, unescape_byte, unescape_byte_literal, unescape_char, unescape_literal, Mode, 12 self, unescape_byte, unescape_byte_literal, unescape_char, unescape_literal, Mode,
12}; 13};
@@ -95,6 +96,9 @@ pub(crate) fn validate(root: &SyntaxNode) -> Vec<SyntaxError> {
95 ast::Visibility(it) => validate_visibility(it, &mut errors), 96 ast::Visibility(it) => validate_visibility(it, &mut errors),
96 ast::RangeExpr(it) => validate_range_expr(it, &mut errors), 97 ast::RangeExpr(it) => validate_range_expr(it, &mut errors),
97 ast::PathSegment(it) => validate_path_keywords(it, &mut errors), 98 ast::PathSegment(it) => validate_path_keywords(it, &mut errors),
99 ast::RefType(it) => validate_trait_object_ref_ty(it, &mut errors),
100 ast::PtrType(it) => validate_trait_object_ptr_ty(it, &mut errors),
101 ast::FnPtrType(it) => validate_trait_object_fn_ptr_ret_ty(it, &mut errors),
98 _ => (), 102 _ => (),
99 } 103 }
100 } 104 }
@@ -301,3 +305,42 @@ fn validate_path_keywords(segment: ast::PathSegment, errors: &mut Vec<SyntaxErro
301 return true; 305 return true;
302 } 306 }
303} 307}
308
309fn validate_trait_object_ref_ty(ty: ast::RefType, errors: &mut Vec<SyntaxError>) {
310 if let Some(ast::Type::DynTraitType(ty)) = ty.ty() {
311 if let Some(err) = validate_trait_object_ty(ty) {
312 errors.push(err);
313 }
314 }
315}
316
317fn validate_trait_object_ptr_ty(ty: ast::PtrType, errors: &mut Vec<SyntaxError>) {
318 if let Some(ast::Type::DynTraitType(ty)) = ty.ty() {
319 if let Some(err) = validate_trait_object_ty(ty) {
320 errors.push(err);
321 }
322 }
323}
324
325fn validate_trait_object_fn_ptr_ret_ty(ty: ast::FnPtrType, errors: &mut Vec<SyntaxError>) {
326 if let Some(ast::Type::DynTraitType(ty)) = ty.ret_type().and_then(|ty| ty.ty()) {
327 if let Some(err) = validate_trait_object_ty(ty) {
328 errors.push(err);
329 }
330 }
331}
332
333fn validate_trait_object_ty(ty: ast::DynTraitType) -> Option<SyntaxError> {
334 let tbl = ty.type_bound_list()?;
335
336 if tbl.bounds().count() > 1 {
337 let dyn_token = ty.dyn_token()?;
338 let potential_parentheses =
339 algo::skip_trivia_token(dyn_token.prev_token()?, Direction::Prev)?;
340 let kind = potential_parentheses.kind();
341 if !matches!(kind, T!['('] | T![<] | T![=]) {
342 return Some(SyntaxError::new("ambiguous `+` in a type", ty.syntax().text_range()));
343 }
344 }
345 None
346}
diff --git a/crates/syntax/test_data/parser/err/0046_ambiguous_trait_object.rast b/crates/syntax/test_data/parser/err/0046_ambiguous_trait_object.rast
new file mode 100644
index 000000000..592741cdb
--- /dev/null
+++ b/crates/syntax/test_data/parser/err/0046_ambiguous_trait_object.rast
@@ -0,0 +1,192 @@
1[email protected]
2 [email protected]
3 [email protected] "type"
4 [email protected] " "
5 [email protected]
6 [email protected] "Foo"
7 [email protected]
8 [email protected] "<"
9 [email protected]
10 [email protected] "\'a"
11 [email protected] ">"
12 [email protected] " "
13 [email protected] "="
14 [email protected] " "
15 [email protected]
16 [email protected] "&"
17 [email protected] "\'a"
18 [email protected] " "
19 [email protected]
20 [email protected] "dyn"
21 [email protected] " "
22 [email protected]
23 [email protected]
24 [email protected]
25 [email protected]
26 [email protected]
27 [email protected]
28 [email protected] "Send"
29 [email protected] " "
30 [email protected] "+"
31 [email protected] " "
32 [email protected]
33 [email protected]
34 [email protected]
35 [email protected]
36 [email protected]
37 [email protected] "Sync"
38 [email protected] ";"
39 [email protected] "\n"
40 [email protected]
41 [email protected] "type"
42 [email protected] " "
43 [email protected]
44 [email protected] "Foo"
45 [email protected] " "
46 [email protected] "="
47 [email protected] " "
48 [email protected]
49 [email protected] "*"
50 [email protected] "const"
51 [email protected] " "
52 [email protected]
53 [email protected] "dyn"
54 [email protected] " "
55 [email protected]
56 [email protected]
57 [email protected]
58 [email protected]
59 [email protected]
60 [email protected]
61 [email protected] "Send"
62 [email protected] " "
63 [email protected] "+"
64 [email protected] " "
65 [email protected]
66 [email protected]
67 [email protected]
68 [email protected]
69 [email protected]
70 [email protected] "Sync"
71 [email protected] ";"
72 [email protected] "\n"
73 [email protected]
74 [email protected] "type"
75 [email protected] " "
76 [email protected]
77 [email protected] "Foo"
78 [email protected] " "
79 [email protected] "="
80 [email protected] " "
81 [email protected]
82 [email protected] "fn"
83 [email protected]
84 [email protected] "("
85 [email protected] ")"
86 [email protected] " "
87 [email protected]
88 [email protected] "->"
89 [email protected] " "
90 [email protected]
91 [email protected] "dyn"
92 [email protected] " "
93 [email protected]
94 [email protected]
95 [email protected]
96 [email protected]
97 [email protected]
98 [email protected]
99 [email protected] "Send"
100 [email protected] " "
101 [email protected] "+"
102 [email protected] " "
103 [email protected]
104 [email protected] "\'static"
105 [email protected] ";"
106 [email protected] "\n"
107 [email protected]
108 [email protected] "fn"
109 [email protected] " "
110 [email protected]
111 [email protected] "main"
112 [email protected]
113 [email protected] "("
114 [email protected] ")"
115 [email protected] " "
116 [email protected]
117 [email protected] "{"
118 [email protected] "\n "
119 [email protected]
120 [email protected] "let"
121 [email protected] " "
122 [email protected]
123 [email protected]
124 [email protected] "b"
125 [email protected] " "
126 [email protected] "="
127 [email protected] " "
128 [email protected]
129 [email protected]
130 [email protected] "("
131 [email protected]
132 [email protected] "&"
133 [email protected]
134 [email protected]
135 [email protected]
136 [email protected]
137 [email protected] "a"
138 [email protected] ")"
139 [email protected] " "
140 [email protected] "as"
141 [email protected] " "
142 [email protected]
143 [email protected] "&"
144 [email protected]
145 [email protected] "dyn"
146 [email protected] " "
147 [email protected]
148 [email protected]
149 [email protected]
150 [email protected]
151 [email protected]
152 [email protected]
153 [email protected] "Add"
154 [email protected]
155 [email protected] "<"
156 [email protected]
157 [email protected]
158 [email protected]
159 [email protected]
160 [email protected]
161 [email protected] "Other"
162 [email protected] ","
163 [email protected] " "
164 [email protected]
165 [email protected]
166 [email protected] "Output"
167 [email protected] " "
168 [email protected] "="
169 [email protected] " "
170 [email protected]
171 [email protected]
172 [email protected]
173 [email protected]
174 [email protected] "Addable"
175 [email protected] ">"
176 [email protected] " "
177 [email protected] "+"
178 [email protected] " "
179 [email protected]
180 [email protected]
181 [email protected]
182 [email protected]
183 [email protected]
184 [email protected] "Other"
185 [email protected] ";"
186 [email protected] "\n"
187 [email protected] "}"
188 [email protected] "\n"
189error 19..34: ambiguous `+` in a type
190error 54..69: ambiguous `+` in a type
191error 90..108: ambiguous `+` in a type
192error 143..183: ambiguous `+` in a type
diff --git a/crates/syntax/test_data/parser/err/0046_ambiguous_trait_object.rs b/crates/syntax/test_data/parser/err/0046_ambiguous_trait_object.rs
new file mode 100644
index 000000000..3a73d81bb
--- /dev/null
+++ b/crates/syntax/test_data/parser/err/0046_ambiguous_trait_object.rs
@@ -0,0 +1,6 @@
1type Foo<'a> = &'a dyn Send + Sync;
2type Foo = *const dyn Send + Sync;
3type Foo = fn() -> dyn Send + 'static;
4fn main() {
5 let b = (&a) as &dyn Add<Other, Output = Addable> + Other;
6}
diff --git a/crates/syntax/test_data/parser/ok/0069_multi_trait_object.rast b/crates/syntax/test_data/parser/ok/0069_multi_trait_object.rast
new file mode 100644
index 000000000..0cd868a83
--- /dev/null
+++ b/crates/syntax/test_data/parser/ok/0069_multi_trait_object.rast
@@ -0,0 +1,200 @@
1[email protected]
2 [email protected]
3 [email protected] "type"
4 [email protected] " "
5 [email protected]
6 [email protected] "Foo"
7 [email protected]
8 [email protected] "<"
9 [email protected]
10 [email protected] "\'a"
11 [email protected] ">"
12 [email protected] " "
13 [email protected] "="
14 [email protected] " "
15 [email protected]
16 [email protected] "&"
17 [email protected] "\'a"
18 [email protected] " "
19 [email protected]
20 [email protected] "("
21 [email protected]
22 [email protected] "dyn"
23 [email protected] " "
24 [email protected]
25 [email protected]
26 [email protected]
27 [email protected]
28 [email protected]
29 [email protected]
30 [email protected] "Send"
31 [email protected] " "
32 [email protected] "+"
33 [email protected] " "
34 [email protected]
35 [email protected]
36 [email protected]
37 [email protected]
38 [email protected]
39 [email protected] "Sync"
40 [email protected] ")"
41 [email protected] ";"
42 [email protected] "\n"
43 [email protected]
44 [email protected] "type"
45 [email protected] " "
46 [email protected]
47 [email protected] "Foo"
48 [email protected] " "
49 [email protected] "="
50 [email protected] " "
51 [email protected]
52 [email protected] "*"
53 [email protected] "const"
54 [email protected] " "
55 [email protected]
56 [email protected] "("
57 [email protected]
58 [email protected] "dyn"
59 [email protected] " "
60 [email protected]
61 [email protected]
62 [email protected]
63 [email protected]
64 [email protected]
65 [email protected]
66 [email protected] "Send"
67 [email protected] " "
68 [email protected] "+"
69 [email protected] " "
70 [email protected]
71 [email protected]
72 [email protected]
73 [email protected]
74 [email protected]
75 [email protected] "Sync"
76 [email protected] ")"
77 [email protected] ";"
78 [email protected] "\n"
79 [email protected]
80 [email protected] "type"
81 [email protected] " "
82 [email protected]
83 [email protected] "Foo"
84 [email protected] " "
85 [email protected] "="
86 [email protected] " "
87 [email protected]
88 [email protected] "fn"
89 [email protected]
90 [email protected] "("
91 [email protected] ")"
92 [email protected] " "
93 [email protected]
94 [email protected] "->"
95 [email protected] " "
96 [email protected]
97 [email protected] "("
98 [email protected]
99 [email protected] "dyn"
100 [email protected] " "
101 [email protected]
102 [email protected]
103 [email protected]
104 [email protected]
105 [email protected]
106 [email protected]
107 [email protected] "Send"
108 [email protected] " "
109 [email protected] "+"
110 [email protected] " "
111 [email protected]
112 [email protected] "\'static"
113 [email protected] ")"
114 [email protected] ";"
115 [email protected] "\n"
116 [email protected]
117 [email protected] "fn"
118 [email protected] " "
119 [email protected]
120 [email protected] "main"
121 [email protected]
122 [email protected] "("
123 [email protected] ")"
124 [email protected] " "
125 [email protected]
126 [email protected] "{"
127 [email protected] "\n "
128 [email protected]
129 [email protected] "let"
130 [email protected] " "
131 [email protected]
132 [email protected]
133 [email protected] "b"
134 [email protected] " "
135 [email protected] "="
136 [email protected] " "
137 [email protected]
138 [email protected]
139 [email protected] "("
140 [email protected]
141 [email protected] "&"
142 [email protected]
143 [email protected]
144 [email protected]
145 [email protected]
146 [email protected] "a"
147 [email protected] ")"
148 [email protected] " "
149 [email protected] "as"
150 [email protected] " "
151 [email protected]
152 [email protected] "&"
153 [email protected]
154 [email protected] "("
155 [email protected]
156 [email protected] "dyn"
157 [email protected] " "
158 [email protected]
159 [email protected]
160 [email protected]
161 [email protected]
162 [email protected]
163 [email protected]
164 [email protected] "Add"
165 [email protected]
166 [email protected] "<"
167 [email protected]
168 [email protected]
169 [email protected]
170 [email protected]
171 [email protected]
172 [email protected] "Other"
173 [email protected] ","
174 [email protected] " "
175 [email protected]
176 [email protected]
177 [email protected] "Output"
178 [email protected] " "
179 [email protected] "="
180 [email protected] " "
181 [email protected]
182 [email protected]
183 [email protected]
184 [email protected]
185 [email protected] "Addable"
186 [email protected] ">"
187 [email protected] " "
188 [email protected] "+"
189 [email protected] " "
190 [email protected]
191 [email protected]
192 [email protected]
193 [email protected]
194 [email protected]
195 [email protected] "Other"
196 [email protected] ")"
197 [email protected] ";"
198 [email protected] "\n"
199 [email protected] "}"
200 [email protected] "\n"
diff --git a/crates/syntax/test_data/parser/ok/0069_multi_trait_object.rs b/crates/syntax/test_data/parser/ok/0069_multi_trait_object.rs
new file mode 100644
index 000000000..97eb79c48
--- /dev/null
+++ b/crates/syntax/test_data/parser/ok/0069_multi_trait_object.rs
@@ -0,0 +1,6 @@
1type Foo<'a> = &'a (dyn Send + Sync);
2type Foo = *const (dyn Send + Sync);
3type Foo = fn() -> (dyn Send + 'static);
4fn main() {
5 let b = (&a) as &(dyn Add<Other, Output = Addable> + Other);
6}
diff --git a/docs/dev/style.md b/docs/dev/style.md
index fb407afcd..59067d234 100644
--- a/docs/dev/style.md
+++ b/docs/dev/style.md
@@ -6,7 +6,9 @@ Our approach to "clean code" is two-fold:
6It is explicitly OK for a reviewer to flag only some nits in the PR, and then send a follow-up cleanup PR for things which are easier to explain by example, cc-ing the original author. 6It is explicitly OK for a reviewer to flag only some nits in the PR, and then send a follow-up cleanup PR for things which are easier to explain by example, cc-ing the original author.
7Sending small cleanup PRs (like renaming a single local variable) is encouraged. 7Sending small cleanup PRs (like renaming a single local variable) is encouraged.
8 8
9# Scale of Changes 9# General
10
11## Scale of Changes
10 12
11Everyone knows that it's better to send small & focused pull requests. 13Everyone knows that it's better to send small & focused pull requests.
12The problem is, sometimes you *have* to, eg, rewrite the whole compiler, and that just doesn't fit into a set of isolated PRs. 14The problem is, sometimes you *have* to, eg, rewrite the whole compiler, and that just doesn't fit into a set of isolated PRs.
@@ -45,13 +47,35 @@ That said, adding an innocent-looking `pub use` is a very simple way to break en
45Note: if you enjoyed this abstract hand-waving about boundaries, you might appreciate 47Note: if you enjoyed this abstract hand-waving about boundaries, you might appreciate
46https://www.tedinski.com/2018/02/06/system-boundaries.html 48https://www.tedinski.com/2018/02/06/system-boundaries.html
47 49
48# Crates.io Dependencies 50## Crates.io Dependencies
49 51
50We try to be very conservative with usage of crates.io dependencies. 52We try to be very conservative with usage of crates.io dependencies.
51Don't use small "helper" crates (exception: `itertools` is allowed). 53Don't use small "helper" crates (exception: `itertools` is allowed).
52If there's some general reusable bit of code you need, consider adding it to the `stdx` crate. 54If there's some general reusable bit of code you need, consider adding it to the `stdx` crate.
53 55
54# Minimal Tests 56## Commit Style
57
58We don't have specific rules around git history hygiene.
59Maintaining clean git history is strongly encouraged, but not enforced.
60Use rebase workflow, it's OK to rewrite history during PR review process.
61After you are happy with the state of the code, please use [interactive rebase](https://git-scm.com/book/en/v2/Git-Tools-Rewriting-History) to squash fixup commits.
62
63Avoid @mentioning people in commit messages and pull request descriptions(they are added to commit message by bors).
64Such messages create a lot of duplicate notification traffic during rebases.
65
66## Clippy
67
68We don't enforce Clippy.
69A number of default lints have high false positive rate.
70Selectively patching false-positives with `allow(clippy)` is considered worse than not using Clippy at all.
71There's `cargo xtask lint` command which runs a subset of low-FPR lints.
72Careful tweaking of `xtask lint` is welcome.
73See also [rust-lang/clippy#5537](https://github.com/rust-lang/rust-clippy/issues/5537).
74Of course, applying Clippy suggestions is welcome as long as they indeed improve the code.
75
76# Code
77
78## Minimal Tests
55 79
56Most tests in rust-analyzer start with a snippet of Rust code. 80Most tests in rust-analyzer start with a snippet of Rust code.
57This snippets should be minimal -- if you copy-paste a snippet of real code into the tests, make sure to remove everything which could be removed. 81This snippets should be minimal -- if you copy-paste a snippet of real code into the tests, make sure to remove everything which could be removed.
@@ -65,119 +89,7 @@ There are many benefits to this:
65It also makes sense to format snippets more compactly (for example, by placing enum definitions like `enum E { Foo, Bar }` on a single line), 89It also makes sense to format snippets more compactly (for example, by placing enum definitions like `enum E { Foo, Bar }` on a single line),
66as long as they are still readable. 90as long as they are still readable.
67 91
68# Order of Imports 92## Preconditions
69
70Separate import groups with blank lines.
71Use one `use` per crate.
72
73```rust
74mod x;
75mod y;
76
77// First std.
78use std::{ ... }
79
80// Second, external crates (both crates.io crates and other rust-analyzer crates).
81use crate_foo::{ ... }
82use crate_bar::{ ... }
83
84// Then current crate.
85use crate::{}
86
87// Finally, parent and child modules, but prefer `use crate::`.
88use super::{}
89```
90
91Module declarations come before the imports.
92Order them in "suggested reading order" for a person new to the code base.
93
94# Import Style
95
96Qualify items from `hir` and `ast`.
97
98```rust
99// Good
100use syntax::ast;
101
102fn frobnicate(func: hir::Function, strukt: ast::StructDef) {}
103
104// Not as good
105use hir::Function;
106use syntax::ast::StructDef;
107
108fn frobnicate(func: Function, strukt: StructDef) {}
109```
110
111Avoid local `use MyEnum::*` imports.
112
113Prefer `use crate::foo::bar` to `use super::bar`.
114
115When implementing `Debug` or `Display`, import `std::fmt`:
116
117```rust
118// Good
119use std::fmt;
120
121impl fmt::Display for RenameError {
122 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { .. }
123}
124
125// Not as good
126impl std::fmt::Display for RenameError {
127 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { .. }
128}
129```
130
131# Order of Items
132
133Optimize for the reader who sees the file for the first time, and wants to get a general idea about what's going on.
134People read things from top to bottom, so place most important things first.
135
136Specifically, if all items except one are private, always put the non-private item on top.
137
138Put `struct`s and `enum`s first, functions and impls last.
139
140Do
141
142```rust
143// Good
144struct Foo {
145 bars: Vec<Bar>
146}
147
148struct Bar;
149```
150
151rather than
152
153```rust
154// Not as good
155struct Bar;
156
157struct Foo {
158 bars: Vec<Bar>
159}
160```
161
162# Variable Naming
163
164Use boring and long names for local variables ([yay code completion](https://github.com/rust-analyzer/rust-analyzer/pull/4162#discussion_r417130973)).
165The default name is a lowercased name of the type: `global_state: GlobalState`.
166Avoid ad-hoc acronyms and contractions, but use the ones that exist consistently (`db`, `ctx`, `acc`).
167
168Default names:
169
170* `res` -- "result of the function" local variable
171* `it` -- I don't really care about the name
172* `n_foo` -- number of foos
173* `foo_idx` -- index of `foo`
174
175# Collection types
176
177Prefer `rustc_hash::FxHashMap` and `rustc_hash::FxHashSet` instead of the ones in `std::collections`.
178They use a hasher that's slightly faster and using them consistently will reduce code size by some small amount.
179
180# Preconditions
181 93
182Express function preconditions in types and force the caller to provide them (rather than checking in callee): 94Express function preconditions in types and force the caller to provide them (rather than checking in callee):
183 95
@@ -199,9 +111,15 @@ fn frobnicate(walrus: Option<Walrus>) {
199 111
200Avoid preconditions that span across function boundaries: 112Avoid preconditions that span across function boundaries:
201 113
202
203```rust 114```rust
204// Good 115// Good
116fn main() {
117 let s: &str = ...;
118 if let Some(contents) = string_literal_contents(s) {
119
120 }
121}
122
205fn string_literal_contents(s: &str) -> Option<&str> { 123fn string_literal_contents(s: &str) -> Option<&str> {
206 if s.starts_with('"') && s.ends_with('"') { 124 if s.starts_with('"') && s.ends_with('"') {
207 Some(&s[1..s.len() - 1]) 125 Some(&s[1..s.len() - 1])
@@ -210,54 +128,37 @@ fn string_literal_contents(s: &str) -> Option<&str> {
210 } 128 }
211} 129}
212 130
213fn foo() { 131// Not as good
132fn main() {
214 let s: &str = ...; 133 let s: &str = ...;
215 if let Some(contents) = string_literal_contents(s) { 134 if is_string_literal(s) {
216 135 let contents = &s[1..s.len() - 1];
217 } 136 }
218} 137}
219 138
220// Not as good
221fn is_string_literal(s: &str) -> bool { 139fn is_string_literal(s: &str) -> bool {
222 s.starts_with('"') && s.ends_with('"') 140 s.starts_with('"') && s.ends_with('"')
223} 141}
224
225fn foo() {
226 let s: &str = ...;
227 if is_string_literal(s) {
228 let contents = &s[1..s.len() - 1];
229 }
230}
231``` 142```
232 143
233In the "Not as good" version, the precondition that `1` is a valid char boundary is checked in `is_string_literal` and used in `foo`. 144In the "Not as good" version, the precondition that `1` is a valid char boundary is checked in `is_string_literal` and used in `foo`.
234In the "Good" version, the precondition check and usage are checked in the same block, and then encoded in the types. 145In the "Good" version, the precondition check and usage are checked in the same block, and then encoded in the types.
235 146
236# Early Returns 147When checking a boolean precondition, prefer `if !invariant` to `if negated_invariant`:
237
238Do use early returns
239 148
240```rust 149```rust
241// Good 150// Good
242fn foo() -> Option<Bar> { 151if !(idx < len) {
243 if !condition() { 152 return None;
244 return None;
245 }
246
247 Some(...)
248} 153}
249 154
250// Not as good 155// Not as good
251fn foo() -> Option<Bar> { 156if idx >= len {
252 if condition() { 157 return None;
253 Some(...)
254 } else {
255 None
256 }
257} 158}
258``` 159```
259 160
260# Getters & Setters 161## Getters & Setters
261 162
262If a field can have any value without breaking invariants, make the field public. 163If a field can have any value without breaking invariants, make the field public.
263Conversely, if there is an invariant, document it, enforce it in the "constructor" function, make the field private, and provide a getter. 164Conversely, if there is an invariant, document it, enforce it in the "constructor" function, make the field private, and provide a getter.
@@ -285,6 +186,40 @@ impl Person {
285} 186}
286``` 187```
287 188
189## Avoid Monomorphization
190
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*.
192This allows for exceptionally good performance, but leads to increased compile times.
193Runtime performance obeys 80%/20% rule -- only a small fraction of code is hot.
194Compile time **does not** obey this rule -- all code has to be compiled.
195For this reason, avoid making a lot of code type parametric, *especially* on the boundaries between crates.
196
197```rust
198// Good
199fn frbonicate(f: impl FnMut()) {
200 frobnicate_impl(&mut f)
201}
202fn frobnicate_impl(f: &mut dyn FnMut()) {
203 // lots of code
204}
205
206// Not as good
207fn frbonicate(f: impl FnMut()) {
208 // lots of code
209}
210```
211
212Avoid `AsRef` polymorphism, it pays back only for widely used libraries:
213
214```rust
215// Good
216fn frbonicate(f: &Path) {
217}
218
219// Not as good
220fn frbonicate(f: impl AsRef<Path>) {
221}
222```
288 223
289# Premature Pessimization 224# Premature Pessimization
290 225
@@ -322,62 +257,159 @@ fn frobnicate(s: &str) {
322} 257}
323``` 258```
324 259
325# Avoid Monomorphization 260## Collection types
326 261
327Rust 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*. 262Prefer `rustc_hash::FxHashMap` and `rustc_hash::FxHashSet` instead of the ones in `std::collections`.
328This allows for exceptionally good performance, but leads to increased compile times. 263They use a hasher that's slightly faster and using them consistently will reduce code size by some small amount.
329Runtime performance obeys 80%/20% rule -- only a small fraction of code is hot. 264
330Compile time **does not** obey this rule -- all code has to be compiled. 265# Style
331For this reason, avoid making a lot of code type parametric, *especially* on the boundaries between crates. 266
267## Order of Imports
268
269Separate import groups with blank lines.
270Use one `use` per crate.
271
272```rust
273mod x;
274mod y;
275
276// First std.
277use std::{ ... }
278
279// Second, external crates (both crates.io crates and other rust-analyzer crates).
280use crate_foo::{ ... }
281use crate_bar::{ ... }
282
283// Then current crate.
284use crate::{}
285
286// Finally, parent and child modules, but prefer `use crate::`.
287use super::{}
288```
289
290Module declarations come before the imports.
291Order them in "suggested reading order" for a person new to the code base.
292
293## Import Style
294
295Qualify items from `hir` and `ast`.
332 296
333```rust 297```rust
334// Good 298// Good
335fn frbonicate(f: impl FnMut()) { 299use syntax::ast;
336 frobnicate_impl(&mut f) 300
337} 301fn frobnicate(func: hir::Function, strukt: ast::StructDef) {}
338fn frobnicate_impl(f: &mut dyn FnMut()) { 302
339 // lots of code 303// Not as good
304use hir::Function;
305use syntax::ast::StructDef;
306
307fn frobnicate(func: Function, strukt: StructDef) {}
308```
309
310Avoid local `use MyEnum::*` imports.
311
312Prefer `use crate::foo::bar` to `use super::bar`.
313
314When implementing `Debug` or `Display`, import `std::fmt`:
315
316```rust
317// Good
318use std::fmt;
319
320impl fmt::Display for RenameError {
321 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { .. }
340} 322}
341 323
342// Not as good 324// Not as good
343fn frbonicate(f: impl FnMut()) { 325impl std::fmt::Display for RenameError {
344 // lots of code 326 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { .. }
345} 327}
346``` 328```
347 329
348Avoid `AsRef` polymorphism, it pays back only for widely used libraries: 330## Order of Items
331
332Optimize for the reader who sees the file for the first time, and wants to get a general idea about what's going on.
333People read things from top to bottom, so place most important things first.
334
335Specifically, if all items except one are private, always put the non-private item on top.
336
337Put `struct`s and `enum`s first, functions and impls last.
338
339Do
349 340
350```rust 341```rust
351// Good 342// Good
352fn frbonicate(f: &Path) { 343struct Foo {
344 bars: Vec<Bar>
353} 345}
354 346
347struct Bar;
348```
349
350rather than
351
352```rust
355// Not as good 353// Not as good
356fn frbonicate(f: impl AsRef<Path>) { 354struct Bar;
355
356struct Foo {
357 bars: Vec<Bar>
357} 358}
358``` 359```
359 360
360# Documentation 361## Variable Naming
361 362
362For `.md` and `.adoc` files, prefer a sentence-per-line format, don't wrap lines. 363Use boring and long names for local variables ([yay code completion](https://github.com/rust-analyzer/rust-analyzer/pull/4162#discussion_r417130973)).
363If the line is too long, you want to split the sentence in two :-) 364The default name is a lowercased name of the type: `global_state: GlobalState`.
365Avoid ad-hoc acronyms and contractions, but use the ones that exist consistently (`db`, `ctx`, `acc`).
364 366
365# Commit Style 367Default names:
366 368
367We don't have specific rules around git history hygiene. 369* `res` -- "result of the function" local variable
368Maintaining clean git history is strongly encouraged, but not enforced. 370* `it` -- I don't really care about the name
369Use rebase workflow, it's OK to rewrite history during PR review process. 371* `n_foo` -- number of foos
370After you are happy with the state of the code, please use [interactive rebase](https://git-scm.com/book/en/v2/Git-Tools-Rewriting-History) to squash fixup commits. 372* `foo_idx` -- index of `foo`
371 373
372Avoid @mentioning people in commit messages and pull request descriptions(they are added to commit message by bors).
373Such messages create a lot of duplicate notification traffic during rebases.
374 374
375# Clippy 375## Early Returns
376 376
377We don't enforce Clippy. 377Do use early returns
378A number of default lints have high false positive rate. 378
379Selectively patching false-positives with `allow(clippy)` is considered worse than not using Clippy at all. 379```rust
380There's `cargo xtask lint` command which runs a subset of low-FPR lints. 380// Good
381Careful tweaking of `xtask lint` is welcome. 381fn foo() -> Option<Bar> {
382See also [rust-lang/clippy#5537](https://github.com/rust-lang/rust-clippy/issues/5537). 382 if !condition() {
383Of course, applying Clippy suggestions is welcome as long as they indeed improve the code. 383 return None;
384 }
385
386 Some(...)
387}
388
389// Not as good
390fn foo() -> Option<Bar> {
391 if condition() {
392 Some(...)
393 } else {
394 None
395 }
396}
397```
398
399## Comparisons
400
401Use `<`/`<=`, avoid `>`/`>=`.
402Less-then comparisons are more intuitive, they correspond spatially to [real line](https://en.wikipedia.org/wiki/Real_line)
403
404```rust
405// Good
406assert!(lo <= x && x <= hi);
407
408// Not as good
409assert!(x >= lo && x <= hi>);
410```
411
412## Documentation
413
414For `.md` and `.adoc` files, prefer a sentence-per-line format, don't wrap lines.
415If the line is too long, you want to split the sentence in two :-)
diff --git a/docs/user/manual.adoc b/docs/user/manual.adoc
index c1a778852..46e7bd091 100644
--- a/docs/user/manual.adoc
+++ b/docs/user/manual.adoc
@@ -397,6 +397,23 @@ It is possible to change the foreground/background color of inlay hints. Just ad
397} 397}
398---- 398----
399 399
400==== Semantic style customizations
401
402You can customize the look of different semantic elements in the source code. For example, mutable bindings are underlined by default and you can override this behavior by adding the following section to your `settings.json`:
403
404[source,jsonc]
405----
406{
407 "editor.semanticTokenColorCustomizations": {
408 "rules": {
409 "*.mutable": {
410 "fontStyle": "", // underline is the default
411 },
412 }
413 },
414}
415----
416
400==== Special `when` clause context for keybindings. 417==== Special `when` clause context for keybindings.
401You may use `inRustProject` context to configure keybindings for rust projects only. For example: 418You may use `inRustProject` context to configure keybindings for rust projects only. For example:
402[source,json] 419[source,json]