aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock16
-rw-r--r--PRIVACY.md17
-rw-r--r--crates/assists/src/assist_config.rs5
-rw-r--r--crates/assists/src/ast_transform.rs17
-rw-r--r--crates/assists/src/handlers/add_missing_impl_members.rs25
-rw-r--r--crates/assists/src/handlers/auto_import.rs23
-rw-r--r--crates/assists/src/handlers/convert_integer_literal.rs701
-rw-r--r--crates/assists/src/handlers/extract_struct_from_enum_variant.rs8
-rw-r--r--crates/assists/src/handlers/fill_match_arms.rs4
-rw-r--r--crates/assists/src/handlers/remove_dbg.rs9
-rw-r--r--crates/assists/src/lib.rs2
-rw-r--r--crates/assists/src/tests.rs27
-rw-r--r--crates/assists/src/tests/generated.rs13
-rw-r--r--crates/assists/src/utils.rs131
-rw-r--r--crates/base_db/src/fixture.rs8
-rw-r--r--crates/base_db/src/input.rs13
-rw-r--r--crates/hir/src/code_model.rs47
-rw-r--r--crates/hir/src/lib.rs10
-rw-r--r--crates/hir_def/src/find_path.rs249
-rw-r--r--crates/hir_def/src/import_map.rs46
-rw-r--r--crates/hir_def/src/nameres.rs2
-rw-r--r--crates/hir_def/src/nameres/collector.rs3
-rw-r--r--crates/hir_def/src/nameres/tests/macros.rs7
-rw-r--r--crates/hir_def/src/path.rs22
-rw-r--r--crates/hir_expand/src/name.rs1
-rw-r--r--crates/hir_ty/Cargo.toml6
-rw-r--r--crates/hir_ty/src/display.rs29
-rw-r--r--crates/hir_ty/src/traits/chalk.rs14
-rw-r--r--crates/hir_ty/src/traits/chalk/mapping.rs2
-rw-r--r--crates/ide/src/completion/complete_postfix/format_like.rs2
-rw-r--r--crates/ide/src/completion/complete_unqualified_path.rs20
-rw-r--r--crates/ide/src/completion/completion_context.rs5
-rw-r--r--crates/ide/src/hover.rs83
-rw-r--r--crates/ide/src/inlay_hints.rs233
-rw-r--r--crates/ide/src/lib.rs4
-rw-r--r--crates/ide/src/link_rewrite.rs6
-rw-r--r--crates/ide/src/markdown_remove.rs23
-rw-r--r--crates/ide/src/status.rs2
-rw-r--r--crates/ide/src/typing/on_enter.rs29
-rw-r--r--crates/mbe/src/subtree_source.rs30
-rw-r--r--crates/project_model/src/lib.rs5
-rw-r--r--crates/rust-analyzer/src/cli/diagnostics.rs11
-rw-r--r--crates/rust-analyzer/src/config.rs23
-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
-rw-r--r--editors/code/package.json15
-rw-r--r--xtask/src/codegen/gen_feature_docs.rs12
-rw-r--r--xtask/tests/tidy.rs7
58 files changed, 2364 insertions, 501 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 49022502d..a1786d201 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -162,9 +162,9 @@ checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
162 162
163[[package]] 163[[package]]
164name = "chalk-derive" 164name = "chalk-derive"
165version = "0.30.0" 165version = "0.32.0"
166source = "registry+https://github.com/rust-lang/crates.io-index" 166source = "registry+https://github.com/rust-lang/crates.io-index"
167checksum = "a6696d18587b7470c1e357a3fa120a2b7e6ac95e91d5c408f087455f7dc31f8b" 167checksum = "2d072b2ba723f0bada7c515d8b3725224bc4f5052d2a92dcbeb0b118ff37084a"
168dependencies = [ 168dependencies = [
169 "proc-macro2", 169 "proc-macro2",
170 "quote", 170 "quote",
@@ -174,9 +174,9 @@ dependencies = [
174 174
175[[package]] 175[[package]]
176name = "chalk-ir" 176name = "chalk-ir"
177version = "0.30.0" 177version = "0.32.0"
178source = "registry+https://github.com/rust-lang/crates.io-index" 178source = "registry+https://github.com/rust-lang/crates.io-index"
179checksum = "8c9538918d3e1fd6edda042d717c969a4099af67a40372dfb0a00b45d3a5a946" 179checksum = "f60cdb0e18c5455cb6a85e8464aad3622b70476018edfa8845691df66f7e9a05"
180dependencies = [ 180dependencies = [
181 "chalk-derive", 181 "chalk-derive",
182 "lazy_static", 182 "lazy_static",
@@ -184,9 +184,9 @@ dependencies = [
184 184
185[[package]] 185[[package]]
186name = "chalk-recursive" 186name = "chalk-recursive"
187version = "0.30.0" 187version = "0.32.0"
188source = "registry+https://github.com/rust-lang/crates.io-index" 188source = "registry+https://github.com/rust-lang/crates.io-index"
189checksum = "97ec8d95c808f2b540c39da889536e1ae0d15182107f61fe80000ec3a5c3959a" 189checksum = "b14f40242102e7c0e2791a2cc86dbbc213a1d7b7acc0e22b7da329f4957d1722"
190dependencies = [ 190dependencies = [
191 "chalk-derive", 191 "chalk-derive",
192 "chalk-ir", 192 "chalk-ir",
@@ -197,9 +197,9 @@ dependencies = [
197 197
198[[package]] 198[[package]]
199name = "chalk-solve" 199name = "chalk-solve"
200version = "0.30.0" 200version = "0.32.0"
201source = "registry+https://github.com/rust-lang/crates.io-index" 201source = "registry+https://github.com/rust-lang/crates.io-index"
202checksum = "f373dff4bcff66004424b72bcc56ae62889c21887c1cac875f083f69a7da4448" 202checksum = "981534d499a8476ecc0b520be4d3864757f96211826a75360fbf2cb6fae362ab"
203dependencies = [ 203dependencies = [
204 "chalk-derive", 204 "chalk-derive",
205 "chalk-ir", 205 "chalk-ir",
diff --git a/PRIVACY.md b/PRIVACY.md
new file mode 100644
index 000000000..dd165c0e2
--- /dev/null
+++ b/PRIVACY.md
@@ -0,0 +1,17 @@
1# Privacy Notes
2
3## LSP server binary
4
5The LSP server performs no network access in itself, but runs `cargo metadata` which will update or download the crate registry and the source code of the project dependencies.
6
7## Visual Studio Code extension
8
9The Code extension connects to GitHub to download updated LSP binaries and, if the nightly channel is selected, to perform update checks.
10
11## Other editor plugins
12
13Any other editor plugins that integrate with `rust-analyzer` are not under the control of the `rust-analyzer` developers. For any privacy concerns, you should check with their respective developers.
14
15## Others
16
17If `cargo check` is enabled (the default), any build scripts or procedural macros used by the project or its dependencies will be executed. This is also the case when `cargo check` is disabled, but build script or procedural macro support is enabled in `rust-analyzer` (off by default).
diff --git a/crates/assists/src/assist_config.rs b/crates/assists/src/assist_config.rs
index adf02edab..b24527ec4 100644
--- a/crates/assists/src/assist_config.rs
+++ b/crates/assists/src/assist_config.rs
@@ -4,6 +4,8 @@
4//! module, and we use to statically check that we only produce snippet 4//! module, and we use to statically check that we only produce snippet
5//! assists if we are allowed to. 5//! assists if we are allowed to.
6 6
7use hir::PrefixKind;
8
7use crate::{utils::MergeBehaviour, AssistKind}; 9use crate::{utils::MergeBehaviour, AssistKind};
8 10
9#[derive(Clone, Debug, PartialEq, Eq)] 11#[derive(Clone, Debug, PartialEq, Eq)]
@@ -37,10 +39,11 @@ impl Default for AssistConfig {
37#[derive(Clone, Copy, Debug, PartialEq, Eq)] 39#[derive(Clone, Copy, Debug, PartialEq, Eq)]
38pub struct InsertUseConfig { 40pub struct InsertUseConfig {
39 pub merge: Option<MergeBehaviour>, 41 pub merge: Option<MergeBehaviour>,
42 pub prefix_kind: PrefixKind,
40} 43}
41 44
42impl Default for InsertUseConfig { 45impl Default for InsertUseConfig {
43 fn default() -> Self { 46 fn default() -> Self {
44 InsertUseConfig { merge: Some(MergeBehaviour::Full) } 47 InsertUseConfig { merge: Some(MergeBehaviour::Full), prefix_kind: PrefixKind::Plain }
45 } 48 }
46} 49}
diff --git a/crates/assists/src/ast_transform.rs b/crates/assists/src/ast_transform.rs
index 4307e0191..ac72f3f02 100644
--- a/crates/assists/src/ast_transform.rs
+++ b/crates/assists/src/ast_transform.rs
@@ -1,13 +1,14 @@
1//! `AstTransformer`s are functions that replace nodes in an AST and can be easily combined. 1//! `AstTransformer`s are functions that replace nodes in an AST and can be easily combined.
2use rustc_hash::FxHashMap;
3
4use hir::{HirDisplay, PathResolution, SemanticsScope}; 2use hir::{HirDisplay, PathResolution, SemanticsScope};
3use rustc_hash::FxHashMap;
5use syntax::{ 4use syntax::{
6 algo::SyntaxRewriter, 5 algo::SyntaxRewriter,
7 ast::{self, AstNode}, 6 ast::{self, AstNode},
8 SyntaxNode, 7 SyntaxNode,
9}; 8};
10 9
10use crate::utils::mod_path_to_ast;
11
11pub fn apply<'a, N: AstNode>(transformer: &dyn AstTransform<'a>, node: N) -> N { 12pub fn apply<'a, N: AstNode>(transformer: &dyn AstTransform<'a>, node: N) -> N {
12 SyntaxRewriter::from_fn(|element| match element { 13 SyntaxRewriter::from_fn(|element| match element {
13 syntax::SyntaxElement::Node(n) => { 14 syntax::SyntaxElement::Node(n) => {
@@ -189,7 +190,7 @@ impl<'a> AstTransform<'a> for QualifyPaths<'a> {
189 match resolution { 190 match resolution {
190 PathResolution::Def(def) => { 191 PathResolution::Def(def) => {
191 let found_path = from.find_use_path(self.source_scope.db.upcast(), def)?; 192 let found_path = from.find_use_path(self.source_scope.db.upcast(), def)?;
192 let mut path = path_to_ast(found_path); 193 let mut path = mod_path_to_ast(&found_path);
193 194
194 let type_args = p 195 let type_args = p
195 .segment() 196 .segment()
@@ -210,13 +211,3 @@ impl<'a> AstTransform<'a> for QualifyPaths<'a> {
210 } 211 }
211 } 212 }
212} 213}
213
214pub(crate) fn path_to_ast(path: hir::ModPath) -> ast::Path {
215 let parse = ast::SourceFile::parse(&path.to_string());
216 parse
217 .tree()
218 .syntax()
219 .descendants()
220 .find_map(ast::Path::cast)
221 .unwrap_or_else(|| panic!("failed to parse path {:?}, `{}`", path, path))
222}
diff --git a/crates/assists/src/handlers/add_missing_impl_members.rs b/crates/assists/src/handlers/add_missing_impl_members.rs
index 51b5a2eb0..4c400f287 100644
--- a/crates/assists/src/handlers/add_missing_impl_members.rs
+++ b/crates/assists/src/handlers/add_missing_impl_members.rs
@@ -820,4 +820,29 @@ impl Tr for () {
820}"#, 820}"#,
821 ) 821 )
822 } 822 }
823
824 #[test]
825 fn weird_path() {
826 check_assist(
827 add_missing_impl_members,
828 r#"
829trait Test {
830 fn foo(&self, x: crate)
831}
832impl Test for () {
833 <|>
834}
835"#,
836 r#"
837trait Test {
838 fn foo(&self, x: crate)
839}
840impl Test for () {
841 fn foo(&self, x: crate) {
842 ${0:todo!()}
843 }
844}
845"#,
846 )
847 }
823} 848}
diff --git a/crates/assists/src/handlers/auto_import.rs b/crates/assists/src/handlers/auto_import.rs
index fa524ffd9..d3ee98e5f 100644
--- a/crates/assists/src/handlers/auto_import.rs
+++ b/crates/assists/src/handlers/auto_import.rs
@@ -13,7 +13,10 @@ use syntax::{
13 SyntaxNode, 13 SyntaxNode,
14}; 14};
15 15
16use crate::{utils::insert_use, AssistContext, AssistId, AssistKind, Assists, GroupLabel}; 16use crate::{
17 utils::insert_use, utils::mod_path_to_ast, AssistContext, AssistId, AssistKind, Assists,
18 GroupLabel,
19};
17 20
18// Assist: auto_import 21// Assist: auto_import
19// 22//
@@ -54,7 +57,7 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
54 range, 57 range,
55 |builder| { 58 |builder| {
56 let new_syntax = 59 let new_syntax =
57 insert_use(&scope, import.to_ast_path(), ctx.config.insert_use.merge); 60 insert_use(&scope, mod_path_to_ast(&import), ctx.config.insert_use.merge);
58 builder.replace(syntax.text_range(), new_syntax.to_string()) 61 builder.replace(syntax.text_range(), new_syntax.to_string())
59 }, 62 },
60 ); 63 );
@@ -191,12 +194,16 @@ impl AutoImportAssets {
191 _ => Some(candidate), 194 _ => Some(candidate),
192 }) 195 })
193 .filter_map(|candidate| match candidate { 196 .filter_map(|candidate| match candidate {
194 Either::Left(module_def) => { 197 Either::Left(module_def) => self.module_with_name_to_import.find_use_path_prefixed(
195 self.module_with_name_to_import.find_use_path_prefixed(db, module_def) 198 db,
196 } 199 module_def,
197 Either::Right(macro_def) => { 200 ctx.config.insert_use.prefix_kind,
198 self.module_with_name_to_import.find_use_path_prefixed(db, macro_def) 201 ),
199 } 202 Either::Right(macro_def) => self.module_with_name_to_import.find_use_path_prefixed(
203 db,
204 macro_def,
205 ctx.config.insert_use.prefix_kind,
206 ),
200 }) 207 })
201 .filter(|use_path| !use_path.segments.is_empty()) 208 .filter(|use_path| !use_path.segments.is_empty())
202 .take(20) 209 .take(20)
diff --git a/crates/assists/src/handlers/convert_integer_literal.rs b/crates/assists/src/handlers/convert_integer_literal.rs
new file mode 100644
index 000000000..ea35e833a
--- /dev/null
+++ b/crates/assists/src/handlers/convert_integer_literal.rs
@@ -0,0 +1,701 @@
1use syntax::{ast, AstNode, SmolStr};
2
3use crate::{AssistContext, AssistId, AssistKind, Assists, GroupLabel};
4
5// Assist: convert_integer_literal
6//
7// Converts the base of integer literals to other bases.
8//
9// ```
10// const _: i32 = 10<|>;
11// ```
12// ->
13// ```
14// const _: i32 = 0b1010;
15// ```
16pub(crate) fn convert_integer_literal(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
17 let literal = ctx.find_node_at_offset::<ast::Literal>()?;
18 let range = literal.syntax().text_range();
19 let group_id = GroupLabel("Convert integer base".into());
20
21 let suffix = match literal.kind() {
22 ast::LiteralKind::IntNumber { suffix } => suffix,
23 _ => return None,
24 };
25 let suffix_len = suffix.as_ref().map(|s| s.len()).unwrap_or(0);
26 let raw_literal_text = literal.syntax().to_string();
27
28 // Gets the literal's text without the type suffix and without underscores.
29 let literal_text = raw_literal_text
30 .chars()
31 .take(raw_literal_text.len() - suffix_len)
32 .filter(|c| *c != '_')
33 .collect::<SmolStr>();
34 let literal_base = IntegerLiteralBase::identify(&literal_text)?;
35
36 for base in IntegerLiteralBase::bases() {
37 if *base == literal_base {
38 continue;
39 }
40
41 let mut converted = literal_base.convert(&literal_text, base);
42
43 let label = if let Some(suffix) = &suffix {
44 format!("Convert {} ({}) to {}", &literal_text, suffix, &converted)
45 } else {
46 format!("Convert {} to {}", &literal_text, &converted)
47 };
48
49 // Appends the type suffix back into the new literal if it exists.
50 if let Some(suffix) = &suffix {
51 converted.push_str(&suffix);
52 }
53
54 acc.add_group(
55 &group_id,
56 AssistId("convert_integer_literal", AssistKind::RefactorInline),
57 label,
58 range,
59 |builder| builder.replace(range, converted),
60 );
61 }
62
63 Some(())
64}
65
66#[derive(Debug, PartialEq, Eq)]
67enum IntegerLiteralBase {
68 Binary,
69 Octal,
70 Decimal,
71 Hexadecimal,
72}
73
74impl IntegerLiteralBase {
75 fn identify(literal_text: &str) -> Option<Self> {
76 // We cannot express a literal in anything other than decimal in under 3 characters, so we return here if possible.
77 if literal_text.len() < 3 && literal_text.chars().all(|c| c.is_digit(10)) {
78 return Some(Self::Decimal);
79 }
80
81 let base = match &literal_text[..2] {
82 "0b" => Self::Binary,
83 "0o" => Self::Octal,
84 "0x" => Self::Hexadecimal,
85 _ => Self::Decimal,
86 };
87
88 // Checks that all characters after the base prefix are all valid digits for that base.
89 if literal_text[base.prefix_len()..].chars().all(|c| c.is_digit(base.base())) {
90 Some(base)
91 } else {
92 None
93 }
94 }
95
96 fn convert(&self, literal_text: &str, to: &IntegerLiteralBase) -> String {
97 let digits = &literal_text[self.prefix_len()..];
98 let value = u128::from_str_radix(digits, self.base()).unwrap();
99
100 match to {
101 Self::Binary => format!("0b{:b}", value),
102 Self::Octal => format!("0o{:o}", value),
103 Self::Decimal => value.to_string(),
104 Self::Hexadecimal => format!("0x{:X}", value),
105 }
106 }
107
108 const fn base(&self) -> u32 {
109 match self {
110 Self::Binary => 2,
111 Self::Octal => 8,
112 Self::Decimal => 10,
113 Self::Hexadecimal => 16,
114 }
115 }
116
117 const fn prefix_len(&self) -> usize {
118 match self {
119 Self::Decimal => 0,
120 _ => 2,
121 }
122 }
123
124 const fn bases() -> &'static [IntegerLiteralBase] {
125 &[
126 IntegerLiteralBase::Binary,
127 IntegerLiteralBase::Octal,
128 IntegerLiteralBase::Decimal,
129 IntegerLiteralBase::Hexadecimal,
130 ]
131 }
132}
133
134#[cfg(test)]
135mod tests {
136
137 use super::*;
138 use crate::tests::{check_assist_by_label, check_assist_target};
139
140 #[test]
141 fn binary_target() {
142 check_assist_target(convert_integer_literal, "const _: i32 = 0b1010<|>;", "0b1010");
143 }
144
145 #[test]
146 fn octal_target() {
147 check_assist_target(convert_integer_literal, "const _: i32 = 0o12<|>;", "0o12");
148 }
149
150 #[test]
151 fn decimal_target() {
152 check_assist_target(convert_integer_literal, "const _: i32 = 10<|>;", "10");
153 }
154
155 #[test]
156 fn hexadecimal_target() {
157 check_assist_target(convert_integer_literal, "const _: i32 = 0xA<|>;", "0xA");
158 }
159
160 #[test]
161 fn binary_target_with_underscores() {
162 check_assist_target(convert_integer_literal, "const _: i32 = 0b10_10<|>;", "0b10_10");
163 }
164
165 #[test]
166 fn octal_target_with_underscores() {
167 check_assist_target(convert_integer_literal, "const _: i32 = 0o1_2<|>;", "0o1_2");
168 }
169
170 #[test]
171 fn decimal_target_with_underscores() {
172 check_assist_target(convert_integer_literal, "const _: i32 = 1_0<|>;", "1_0");
173 }
174
175 #[test]
176 fn hexadecimal_target_with_underscores() {
177 check_assist_target(convert_integer_literal, "const _: i32 = 0x_A<|>;", "0x_A");
178 }
179
180 #[test]
181 fn convert_decimal_integer() {
182 let before = "const _: i32 = 1000<|>;";
183
184 check_assist_by_label(
185 convert_integer_literal,
186 before,
187 "const _: i32 = 0b1111101000;",
188 "Convert 1000 to 0b1111101000",
189 );
190
191 check_assist_by_label(
192 convert_integer_literal,
193 before,
194 "const _: i32 = 0o1750;",
195 "Convert 1000 to 0o1750",
196 );
197
198 check_assist_by_label(
199 convert_integer_literal,
200 before,
201 "const _: i32 = 0x3E8;",
202 "Convert 1000 to 0x3E8",
203 );
204 }
205
206 // Decimal numbers under 3 digits have a special case where they return early because we can't fit a
207 // other base's prefix, so we have a separate test for that.
208 #[test]
209 fn convert_small_decimal_integer() {
210 let before = "const _: i32 = 10<|>;";
211
212 check_assist_by_label(
213 convert_integer_literal,
214 before,
215 "const _: i32 = 0b1010;",
216 "Convert 10 to 0b1010",
217 );
218
219 check_assist_by_label(
220 convert_integer_literal,
221 before,
222 "const _: i32 = 0o12;",
223 "Convert 10 to 0o12",
224 );
225
226 check_assist_by_label(
227 convert_integer_literal,
228 before,
229 "const _: i32 = 0xA;",
230 "Convert 10 to 0xA",
231 );
232 }
233
234 #[test]
235 fn convert_hexadecimal_integer() {
236 let before = "const _: i32 = 0xFF<|>;";
237
238 check_assist_by_label(
239 convert_integer_literal,
240 before,
241 "const _: i32 = 0b11111111;",
242 "Convert 0xFF to 0b11111111",
243 );
244
245 check_assist_by_label(
246 convert_integer_literal,
247 before,
248 "const _: i32 = 0o377;",
249 "Convert 0xFF to 0o377",
250 );
251
252 check_assist_by_label(
253 convert_integer_literal,
254 before,
255 "const _: i32 = 255;",
256 "Convert 0xFF to 255",
257 );
258 }
259
260 #[test]
261 fn convert_binary_integer() {
262 let before = "const _: i32 = 0b11111111<|>;";
263
264 check_assist_by_label(
265 convert_integer_literal,
266 before,
267 "const _: i32 = 0o377;",
268 "Convert 0b11111111 to 0o377",
269 );
270
271 check_assist_by_label(
272 convert_integer_literal,
273 before,
274 "const _: i32 = 255;",
275 "Convert 0b11111111 to 255",
276 );
277
278 check_assist_by_label(
279 convert_integer_literal,
280 before,
281 "const _: i32 = 0xFF;",
282 "Convert 0b11111111 to 0xFF",
283 );
284 }
285
286 #[test]
287 fn convert_octal_integer() {
288 let before = "const _: i32 = 0o377<|>;";
289
290 check_assist_by_label(
291 convert_integer_literal,
292 before,
293 "const _: i32 = 0b11111111;",
294 "Convert 0o377 to 0b11111111",
295 );
296
297 check_assist_by_label(
298 convert_integer_literal,
299 before,
300 "const _: i32 = 255;",
301 "Convert 0o377 to 255",
302 );
303
304 check_assist_by_label(
305 convert_integer_literal,
306 before,
307 "const _: i32 = 0xFF;",
308 "Convert 0o377 to 0xFF",
309 );
310 }
311
312 #[test]
313 fn convert_decimal_integer_with_underscores() {
314 let before = "const _: i32 = 1_00_0<|>;";
315
316 check_assist_by_label(
317 convert_integer_literal,
318 before,
319 "const _: i32 = 0b1111101000;",
320 "Convert 1000 to 0b1111101000",
321 );
322
323 check_assist_by_label(
324 convert_integer_literal,
325 before,
326 "const _: i32 = 0o1750;",
327 "Convert 1000 to 0o1750",
328 );
329
330 check_assist_by_label(
331 convert_integer_literal,
332 before,
333 "const _: i32 = 0x3E8;",
334 "Convert 1000 to 0x3E8",
335 );
336 }
337
338 #[test]
339 fn convert_small_decimal_integer_with_underscores() {
340 let before = "const _: i32 = 1_0<|>;";
341
342 check_assist_by_label(
343 convert_integer_literal,
344 before,
345 "const _: i32 = 0b1010;",
346 "Convert 10 to 0b1010",
347 );
348
349 check_assist_by_label(
350 convert_integer_literal,
351 before,
352 "const _: i32 = 0o12;",
353 "Convert 10 to 0o12",
354 );
355
356 check_assist_by_label(
357 convert_integer_literal,
358 before,
359 "const _: i32 = 0xA;",
360 "Convert 10 to 0xA",
361 );
362 }
363
364 #[test]
365 fn convert_hexadecimal_integer_with_underscores() {
366 let before = "const _: i32 = 0x_F_F<|>;";
367
368 check_assist_by_label(
369 convert_integer_literal,
370 before,
371 "const _: i32 = 0b11111111;",
372 "Convert 0xFF to 0b11111111",
373 );
374
375 check_assist_by_label(
376 convert_integer_literal,
377 before,
378 "const _: i32 = 0o377;",
379 "Convert 0xFF to 0o377",
380 );
381
382 check_assist_by_label(
383 convert_integer_literal,
384 before,
385 "const _: i32 = 255;",
386 "Convert 0xFF to 255",
387 );
388 }
389
390 #[test]
391 fn convert_binary_integer_with_underscores() {
392 let before = "const _: i32 = 0b1111_1111<|>;";
393
394 check_assist_by_label(
395 convert_integer_literal,
396 before,
397 "const _: i32 = 0o377;",
398 "Convert 0b11111111 to 0o377",
399 );
400
401 check_assist_by_label(
402 convert_integer_literal,
403 before,
404 "const _: i32 = 255;",
405 "Convert 0b11111111 to 255",
406 );
407
408 check_assist_by_label(
409 convert_integer_literal,
410 before,
411 "const _: i32 = 0xFF;",
412 "Convert 0b11111111 to 0xFF",
413 );
414 }
415
416 #[test]
417 fn convert_octal_integer_with_underscores() {
418 let before = "const _: i32 = 0o3_77<|>;";
419
420 check_assist_by_label(
421 convert_integer_literal,
422 before,
423 "const _: i32 = 0b11111111;",
424 "Convert 0o377 to 0b11111111",
425 );
426
427 check_assist_by_label(
428 convert_integer_literal,
429 before,
430 "const _: i32 = 255;",
431 "Convert 0o377 to 255",
432 );
433
434 check_assist_by_label(
435 convert_integer_literal,
436 before,
437 "const _: i32 = 0xFF;",
438 "Convert 0o377 to 0xFF",
439 );
440 }
441
442 #[test]
443 fn convert_decimal_integer_with_suffix() {
444 let before = "const _: i32 = 1000i32<|>;";
445
446 check_assist_by_label(
447 convert_integer_literal,
448 before,
449 "const _: i32 = 0b1111101000i32;",
450 "Convert 1000 (i32) to 0b1111101000",
451 );
452
453 check_assist_by_label(
454 convert_integer_literal,
455 before,
456 "const _: i32 = 0o1750i32;",
457 "Convert 1000 (i32) to 0o1750",
458 );
459
460 check_assist_by_label(
461 convert_integer_literal,
462 before,
463 "const _: i32 = 0x3E8i32;",
464 "Convert 1000 (i32) to 0x3E8",
465 );
466 }
467
468 #[test]
469 fn convert_small_decimal_integer_with_suffix() {
470 let before = "const _: i32 = 10i32<|>;";
471
472 check_assist_by_label(
473 convert_integer_literal,
474 before,
475 "const _: i32 = 0b1010i32;",
476 "Convert 10 (i32) to 0b1010",
477 );
478
479 check_assist_by_label(
480 convert_integer_literal,
481 before,
482 "const _: i32 = 0o12i32;",
483 "Convert 10 (i32) to 0o12",
484 );
485
486 check_assist_by_label(
487 convert_integer_literal,
488 before,
489 "const _: i32 = 0xAi32;",
490 "Convert 10 (i32) to 0xA",
491 );
492 }
493
494 #[test]
495 fn convert_hexadecimal_integer_with_suffix() {
496 let before = "const _: i32 = 0xFFi32<|>;";
497
498 check_assist_by_label(
499 convert_integer_literal,
500 before,
501 "const _: i32 = 0b11111111i32;",
502 "Convert 0xFF (i32) to 0b11111111",
503 );
504
505 check_assist_by_label(
506 convert_integer_literal,
507 before,
508 "const _: i32 = 0o377i32;",
509 "Convert 0xFF (i32) to 0o377",
510 );
511
512 check_assist_by_label(
513 convert_integer_literal,
514 before,
515 "const _: i32 = 255i32;",
516 "Convert 0xFF (i32) to 255",
517 );
518 }
519
520 #[test]
521 fn convert_binary_integer_with_suffix() {
522 let before = "const _: i32 = 0b11111111i32<|>;";
523
524 check_assist_by_label(
525 convert_integer_literal,
526 before,
527 "const _: i32 = 0o377i32;",
528 "Convert 0b11111111 (i32) to 0o377",
529 );
530
531 check_assist_by_label(
532 convert_integer_literal,
533 before,
534 "const _: i32 = 255i32;",
535 "Convert 0b11111111 (i32) to 255",
536 );
537
538 check_assist_by_label(
539 convert_integer_literal,
540 before,
541 "const _: i32 = 0xFFi32;",
542 "Convert 0b11111111 (i32) to 0xFF",
543 );
544 }
545
546 #[test]
547 fn convert_octal_integer_with_suffix() {
548 let before = "const _: i32 = 0o377i32<|>;";
549
550 check_assist_by_label(
551 convert_integer_literal,
552 before,
553 "const _: i32 = 0b11111111i32;",
554 "Convert 0o377 (i32) to 0b11111111",
555 );
556
557 check_assist_by_label(
558 convert_integer_literal,
559 before,
560 "const _: i32 = 255i32;",
561 "Convert 0o377 (i32) to 255",
562 );
563
564 check_assist_by_label(
565 convert_integer_literal,
566 before,
567 "const _: i32 = 0xFFi32;",
568 "Convert 0o377 (i32) to 0xFF",
569 );
570 }
571
572 #[test]
573 fn convert_decimal_integer_with_underscores_and_suffix() {
574 let before = "const _: i32 = 1_00_0i32<|>;";
575
576 check_assist_by_label(
577 convert_integer_literal,
578 before,
579 "const _: i32 = 0b1111101000i32;",
580 "Convert 1000 (i32) to 0b1111101000",
581 );
582
583 check_assist_by_label(
584 convert_integer_literal,
585 before,
586 "const _: i32 = 0o1750i32;",
587 "Convert 1000 (i32) to 0o1750",
588 );
589
590 check_assist_by_label(
591 convert_integer_literal,
592 before,
593 "const _: i32 = 0x3E8i32;",
594 "Convert 1000 (i32) to 0x3E8",
595 );
596 }
597
598 #[test]
599 fn convert_small_decimal_integer_with_underscores_and_suffix() {
600 let before = "const _: i32 = 1_0i32<|>;";
601
602 check_assist_by_label(
603 convert_integer_literal,
604 before,
605 "const _: i32 = 0b1010i32;",
606 "Convert 10 (i32) to 0b1010",
607 );
608
609 check_assist_by_label(
610 convert_integer_literal,
611 before,
612 "const _: i32 = 0o12i32;",
613 "Convert 10 (i32) to 0o12",
614 );
615
616 check_assist_by_label(
617 convert_integer_literal,
618 before,
619 "const _: i32 = 0xAi32;",
620 "Convert 10 (i32) to 0xA",
621 );
622 }
623
624 #[test]
625 fn convert_hexadecimal_integer_with_underscores_and_suffix() {
626 let before = "const _: i32 = 0x_F_Fi32<|>;";
627
628 check_assist_by_label(
629 convert_integer_literal,
630 before,
631 "const _: i32 = 0b11111111i32;",
632 "Convert 0xFF (i32) to 0b11111111",
633 );
634
635 check_assist_by_label(
636 convert_integer_literal,
637 before,
638 "const _: i32 = 0o377i32;",
639 "Convert 0xFF (i32) to 0o377",
640 );
641
642 check_assist_by_label(
643 convert_integer_literal,
644 before,
645 "const _: i32 = 255i32;",
646 "Convert 0xFF (i32) to 255",
647 );
648 }
649
650 #[test]
651 fn convert_binary_integer_with_underscores_and_suffix() {
652 let before = "const _: i32 = 0b1111_1111i32<|>;";
653
654 check_assist_by_label(
655 convert_integer_literal,
656 before,
657 "const _: i32 = 0o377i32;",
658 "Convert 0b11111111 (i32) to 0o377",
659 );
660
661 check_assist_by_label(
662 convert_integer_literal,
663 before,
664 "const _: i32 = 255i32;",
665 "Convert 0b11111111 (i32) to 255",
666 );
667
668 check_assist_by_label(
669 convert_integer_literal,
670 before,
671 "const _: i32 = 0xFFi32;",
672 "Convert 0b11111111 (i32) to 0xFF",
673 );
674 }
675
676 #[test]
677 fn convert_octal_integer_with_underscores_and_suffix() {
678 let before = "const _: i32 = 0o3_77i32<|>;";
679
680 check_assist_by_label(
681 convert_integer_literal,
682 before,
683 "const _: i32 = 0b11111111i32;",
684 "Convert 0o377 (i32) to 0b11111111",
685 );
686
687 check_assist_by_label(
688 convert_integer_literal,
689 before,
690 "const _: i32 = 255i32;",
691 "Convert 0o377 (i32) to 255",
692 );
693
694 check_assist_by_label(
695 convert_integer_literal,
696 before,
697 "const _: i32 = 0xFFi32;",
698 "Convert 0o377 (i32) to 0xFF",
699 );
700 }
701}
diff --git a/crates/assists/src/handlers/extract_struct_from_enum_variant.rs b/crates/assists/src/handlers/extract_struct_from_enum_variant.rs
index f5f03ef36..7f4f80b23 100644
--- a/crates/assists/src/handlers/extract_struct_from_enum_variant.rs
+++ b/crates/assists/src/handlers/extract_struct_from_enum_variant.rs
@@ -10,9 +10,10 @@ use syntax::{
10}; 10};
11 11
12use crate::{ 12use crate::{
13 assist_context::AssistBuilder, utils::insert_use, AssistContext, AssistId, AssistKind, Assists, 13 assist_context::AssistBuilder,
14 utils::{insert_use, mod_path_to_ast, ImportScope},
15 AssistContext, AssistId, AssistKind, Assists,
14}; 16};
15use insert_use::ImportScope;
16 17
17// Assist: extract_struct_from_enum_variant 18// Assist: extract_struct_from_enum_variant
18// 19//
@@ -111,7 +112,8 @@ fn insert_import(
111 let scope = ImportScope::find_insert_use_container(path.syntax(), ctx)?; 112 let scope = ImportScope::find_insert_use_container(path.syntax(), ctx)?;
112 let syntax = scope.as_syntax_node(); 113 let syntax = scope.as_syntax_node();
113 114
114 let new_syntax = insert_use(&scope, mod_path.to_ast_path(), ctx.config.insert_use.merge); 115 let new_syntax =
116 insert_use(&scope, mod_path_to_ast(&mod_path), ctx.config.insert_use.merge);
115 // FIXME: this will currently panic as multiple imports will have overlapping text ranges 117 // FIXME: this will currently panic as multiple imports will have overlapping text ranges
116 builder.replace(syntax.text_range(), new_syntax.to_string()) 118 builder.replace(syntax.text_range(), new_syntax.to_string())
117 } 119 }
diff --git a/crates/assists/src/handlers/fill_match_arms.rs b/crates/assists/src/handlers/fill_match_arms.rs
index 3d9bdb2bf..676f5ad92 100644
--- a/crates/assists/src/handlers/fill_match_arms.rs
+++ b/crates/assists/src/handlers/fill_match_arms.rs
@@ -7,7 +7,7 @@ use syntax::ast::{self, make, AstNode, MatchArm, NameOwner, Pat};
7use test_utils::mark; 7use test_utils::mark;
8 8
9use crate::{ 9use crate::{
10 utils::{render_snippet, Cursor, FamousDefs}, 10 utils::{mod_path_to_ast, render_snippet, Cursor, FamousDefs},
11 AssistContext, AssistId, AssistKind, Assists, 11 AssistContext, AssistId, AssistKind, Assists,
12}; 12};
13 13
@@ -192,7 +192,7 @@ fn resolve_tuple_of_enum_def(
192} 192}
193 193
194fn build_pat(db: &RootDatabase, module: hir::Module, var: hir::EnumVariant) -> Option<ast::Pat> { 194fn build_pat(db: &RootDatabase, module: hir::Module, var: hir::EnumVariant) -> Option<ast::Pat> {
195 let path = crate::ast_transform::path_to_ast(module.find_use_path(db, ModuleDef::from(var))?); 195 let path = mod_path_to_ast(&module.find_use_path(db, ModuleDef::from(var))?);
196 196
197 // FIXME: use HIR for this; it doesn't currently expose struct vs. tuple vs. unit variants though 197 // FIXME: use HIR for this; it doesn't currently expose struct vs. tuple vs. unit variants though
198 let pat: ast::Pat = match var.source(db).value.kind() { 198 let pat: ast::Pat = match var.source(db).value.kind() {
diff --git a/crates/assists/src/handlers/remove_dbg.rs b/crates/assists/src/handlers/remove_dbg.rs
index a8ab2aecc..e10616779 100644
--- a/crates/assists/src/handlers/remove_dbg.rs
+++ b/crates/assists/src/handlers/remove_dbg.rs
@@ -1,6 +1,6 @@
1use syntax::{ 1use syntax::{
2 ast::{self, AstNode}, 2 ast::{self, AstNode},
3 SyntaxElement, TextRange, TextSize, T, 3 SyntaxElement, SyntaxKind, TextRange, TextSize, T,
4}; 4};
5 5
6use crate::{AssistContext, AssistId, AssistKind, Assists}; 6use crate::{AssistContext, AssistId, AssistKind, Assists};
@@ -117,7 +117,10 @@ fn needs_parentheses_around_macro_contents(macro_contents: Vec<SyntaxElement>) -
117 } 117 }
118 symbol_kind => { 118 symbol_kind => {
119 let symbol_not_in_bracket = unpaired_brackets_in_contents.is_empty(); 119 let symbol_not_in_bracket = unpaired_brackets_in_contents.is_empty();
120 if symbol_not_in_bracket && symbol_kind.is_punct() { 120 if symbol_not_in_bracket
121 && symbol_kind != SyntaxKind::COLON
122 && symbol_kind.is_punct()
123 {
121 return true; 124 return true;
122 } 125 }
123 } 126 }
@@ -159,6 +162,8 @@ fn foo(n: usize) {
159} 162}
160", 163",
161 ); 164 );
165
166 check_assist(remove_dbg, "<|>dbg!(Foo::foo_test()).bar()", "Foo::foo_test().bar()");
162 } 167 }
163 168
164 #[test] 169 #[test]
diff --git a/crates/assists/src/lib.rs b/crates/assists/src/lib.rs
index cbac53e71..a2bec818c 100644
--- a/crates/assists/src/lib.rs
+++ b/crates/assists/src/lib.rs
@@ -128,6 +128,7 @@ mod handlers {
128 mod auto_import; 128 mod auto_import;
129 mod change_return_type_to_result; 129 mod change_return_type_to_result;
130 mod change_visibility; 130 mod change_visibility;
131 mod convert_integer_literal;
131 mod early_return; 132 mod early_return;
132 mod expand_glob_import; 133 mod expand_glob_import;
133 mod extract_struct_from_enum_variant; 134 mod extract_struct_from_enum_variant;
@@ -172,6 +173,7 @@ mod handlers {
172 auto_import::auto_import, 173 auto_import::auto_import,
173 change_return_type_to_result::change_return_type_to_result, 174 change_return_type_to_result::change_return_type_to_result,
174 change_visibility::change_visibility, 175 change_visibility::change_visibility,
176 convert_integer_literal::convert_integer_literal,
175 early_return::convert_to_guarded_return, 177 early_return::convert_to_guarded_return,
176 expand_glob_import::expand_glob_import, 178 expand_glob_import::expand_glob_import,
177 extract_struct_from_enum_variant::extract_struct_from_enum_variant, 179 extract_struct_from_enum_variant::extract_struct_from_enum_variant,
diff --git a/crates/assists/src/tests.rs b/crates/assists/src/tests.rs
index ba1fb543b..2b687decf 100644
--- a/crates/assists/src/tests.rs
+++ b/crates/assists/src/tests.rs
@@ -15,18 +15,30 @@ pub(crate) fn with_single_file(text: &str) -> (RootDatabase, FileId) {
15 15
16pub(crate) fn check_assist(assist: Handler, ra_fixture_before: &str, ra_fixture_after: &str) { 16pub(crate) fn check_assist(assist: Handler, ra_fixture_before: &str, ra_fixture_after: &str) {
17 let ra_fixture_after = trim_indent(ra_fixture_after); 17 let ra_fixture_after = trim_indent(ra_fixture_after);
18 check(assist, ra_fixture_before, ExpectedResult::After(&ra_fixture_after)); 18 check(assist, ra_fixture_before, ExpectedResult::After(&ra_fixture_after), None);
19}
20
21// There is no way to choose what assist within a group you want to test against,
22// so this is here to allow you choose.
23pub(crate) fn check_assist_by_label(
24 assist: Handler,
25 ra_fixture_before: &str,
26 ra_fixture_after: &str,
27 label: &str,
28) {
29 let ra_fixture_after = trim_indent(ra_fixture_after);
30 check(assist, ra_fixture_before, ExpectedResult::After(&ra_fixture_after), Some(label));
19} 31}
20 32
21// FIXME: instead of having a separate function here, maybe use 33// FIXME: instead of having a separate function here, maybe use
22// `extract_ranges` and mark the target as `<target> </target>` in the 34// `extract_ranges` and mark the target as `<target> </target>` in the
23// fixture? 35// fixture?
24pub(crate) fn check_assist_target(assist: Handler, ra_fixture: &str, target: &str) { 36pub(crate) fn check_assist_target(assist: Handler, ra_fixture: &str, target: &str) {
25 check(assist, ra_fixture, ExpectedResult::Target(target)); 37 check(assist, ra_fixture, ExpectedResult::Target(target), None);
26} 38}
27 39
28pub(crate) fn check_assist_not_applicable(assist: Handler, ra_fixture: &str) { 40pub(crate) fn check_assist_not_applicable(assist: Handler, ra_fixture: &str) {
29 check(assist, ra_fixture, ExpectedResult::NotApplicable); 41 check(assist, ra_fixture, ExpectedResult::NotApplicable, None);
30} 42}
31 43
32fn check_doc_test(assist_id: &str, before: &str, after: &str) { 44fn check_doc_test(assist_id: &str, before: &str, after: &str) {
@@ -65,7 +77,7 @@ enum ExpectedResult<'a> {
65 Target(&'a str), 77 Target(&'a str),
66} 78}
67 79
68fn check(handler: Handler, before: &str, expected: ExpectedResult) { 80fn check(handler: Handler, before: &str, expected: ExpectedResult, assist_label: Option<&str>) {
69 let (db, file_with_caret_id, range_or_offset) = RootDatabase::with_range_or_offset(before); 81 let (db, file_with_caret_id, range_or_offset) = RootDatabase::with_range_or_offset(before);
70 let text_without_caret = db.file_text(file_with_caret_id).to_string(); 82 let text_without_caret = db.file_text(file_with_caret_id).to_string();
71 83
@@ -77,7 +89,12 @@ fn check(handler: Handler, before: &str, expected: ExpectedResult) {
77 let mut acc = Assists::new_resolved(&ctx); 89 let mut acc = Assists::new_resolved(&ctx);
78 handler(&mut acc, &ctx); 90 handler(&mut acc, &ctx);
79 let mut res = acc.finish_resolved(); 91 let mut res = acc.finish_resolved();
80 let assist = res.pop(); 92
93 let assist = match assist_label {
94 Some(label) => res.into_iter().find(|resolved| resolved.assist.label == label),
95 None => res.pop(),
96 };
97
81 match (assist, expected) { 98 match (assist, expected) {
82 (Some(assist), ExpectedResult::After(after)) => { 99 (Some(assist), ExpectedResult::After(after)) => {
83 let mut source_change = assist.source_change; 100 let mut source_change = assist.source_change;
diff --git a/crates/assists/src/tests/generated.rs b/crates/assists/src/tests/generated.rs
index 27d15adb0..7f6e98a54 100644
--- a/crates/assists/src/tests/generated.rs
+++ b/crates/assists/src/tests/generated.rs
@@ -204,6 +204,19 @@ pub(crate) fn frobnicate() {}
204} 204}
205 205
206#[test] 206#[test]
207fn doctest_convert_integer_literal() {
208 check_doc_test(
209 "convert_integer_literal",
210 r#####"
211const _: i32 = 10<|>;
212"#####,
213 r#####"
214const _: i32 = 0b1010;
215"#####,
216 )
217}
218
219#[test]
207fn doctest_convert_to_guarded_return() { 220fn doctest_convert_to_guarded_return() {
208 check_doc_test( 221 check_doc_test(
209 "convert_to_guarded_return", 222 "convert_to_guarded_return",
diff --git a/crates/assists/src/utils.rs b/crates/assists/src/utils.rs
index b0511ceb6..c1847f601 100644
--- a/crates/assists/src/utils.rs
+++ b/crates/assists/src/utils.rs
@@ -3,7 +3,7 @@ pub(crate) mod insert_use;
3 3
4use std::{iter, ops}; 4use std::{iter, ops};
5 5
6use hir::{Adt, Crate, Enum, ScopeDef, Semantics, Trait, Type}; 6use hir::{Adt, Crate, Enum, Module, ScopeDef, Semantics, Trait, Type};
7use ide_db::RootDatabase; 7use ide_db::RootDatabase;
8use itertools::Itertools; 8use itertools::Itertools;
9use rustc_hash::FxHashSet; 9use rustc_hash::FxHashSet;
@@ -19,6 +19,27 @@ use crate::assist_config::SnippetCap;
19pub use insert_use::MergeBehaviour; 19pub use insert_use::MergeBehaviour;
20pub(crate) use insert_use::{insert_use, ImportScope}; 20pub(crate) use insert_use::{insert_use, ImportScope};
21 21
22pub fn mod_path_to_ast(path: &hir::ModPath) -> ast::Path {
23 let mut segments = Vec::new();
24 let mut is_abs = false;
25 match path.kind {
26 hir::PathKind::Plain => {}
27 hir::PathKind::Super(0) => segments.push(make::path_segment_self()),
28 hir::PathKind::Super(n) => segments.extend((0..n).map(|_| make::path_segment_super())),
29 hir::PathKind::DollarCrate(_) | hir::PathKind::Crate => {
30 segments.push(make::path_segment_crate())
31 }
32 hir::PathKind::Abs => is_abs = true,
33 }
34
35 segments.extend(
36 path.segments
37 .iter()
38 .map(|segment| make::path_segment(make::name_ref(&segment.to_string()))),
39 );
40 make::path_from_segments(segments, is_abs)
41}
42
22pub(crate) fn unwrap_trivial_block(block: ast::BlockExpr) -> ast::Expr { 43pub(crate) fn unwrap_trivial_block(block: ast::BlockExpr) -> ast::Expr {
23 extract_trivial_expression(&block) 44 extract_trivial_expression(&block)
24 .filter(|expr| !expr.syntax().text().contains_char('\n')) 45 .filter(|expr| !expr.syntax().text().contains_char('\n'))
@@ -253,15 +274,79 @@ impl TryEnum {
253/// somewhat similar to the known paths infra inside hir, but it different; We 274/// somewhat similar to the known paths infra inside hir, but it different; We
254/// want to make sure that IDE specific paths don't become interesting inside 275/// want to make sure that IDE specific paths don't become interesting inside
255/// the compiler itself as well. 276/// the compiler itself as well.
256pub(crate) struct FamousDefs<'a, 'b>(pub(crate) &'a Semantics<'b, RootDatabase>, pub(crate) Crate); 277pub struct FamousDefs<'a, 'b>(pub &'a Semantics<'b, RootDatabase>, pub Crate);
257 278
258#[allow(non_snake_case)] 279#[allow(non_snake_case)]
259impl FamousDefs<'_, '_> { 280impl FamousDefs<'_, '_> {
260 #[cfg(test)] 281 pub const FIXTURE: &'static str = r#"//- /libcore.rs crate:core
261 pub(crate) const FIXTURE: &'static str = r#"//- /libcore.rs crate:core
262pub mod convert { 282pub mod convert {
263 pub trait From<T> { 283 pub trait From<T> {
264 fn from(T) -> Self; 284 fn from(t: T) -> Self;
285 }
286}
287
288pub mod iter {
289 pub use self::traits::{collect::IntoIterator, iterator::Iterator};
290 mod traits {
291 pub(crate) mod iterator {
292 use crate::option::Option;
293 pub trait Iterator {
294 type Item;
295 fn next(&mut self) -> Option<Self::Item>;
296 fn by_ref(&mut self) -> &mut Self {
297 self
298 }
299 fn take(self, n: usize) -> crate::iter::Take<Self> {
300 crate::iter::Take { inner: self }
301 }
302 }
303
304 impl<I: Iterator> Iterator for &mut I {
305 type Item = I::Item;
306 fn next(&mut self) -> Option<I::Item> {
307 (**self).next()
308 }
309 }
310 }
311 pub(crate) mod collect {
312 pub trait IntoIterator {
313 type Item;
314 }
315 }
316 }
317
318 pub use self::sources::*;
319 pub(crate) mod sources {
320 use super::Iterator;
321 use crate::option::Option::{self, *};
322 pub struct Repeat<A> {
323 element: A,
324 }
325
326 pub fn repeat<T>(elt: T) -> Repeat<T> {
327 Repeat { element: elt }
328 }
329
330 impl<A> Iterator for Repeat<A> {
331 type Item = A;
332
333 fn next(&mut self) -> Option<A> {
334 None
335 }
336 }
337 }
338
339 pub use self::adapters::*;
340 pub(crate) mod adapters {
341 use super::Iterator;
342 use crate::option::Option::{self, *};
343 pub struct Take<I> { pub(crate) inner: I }
344 impl<I> Iterator for Take<I> where I: Iterator {
345 type Item = <I as Iterator>::Item;
346 fn next(&mut self) -> Option<<I as Iterator>::Item> {
347 None
348 }
349 }
265 } 350 }
266} 351}
267 352
@@ -270,7 +355,7 @@ pub mod option {
270} 355}
271 356
272pub mod prelude { 357pub mod prelude {
273 pub use crate::{convert::From, option::Option::{self, *}}; 358 pub use crate::{convert::From, iter::{IntoIterator, Iterator}, option::Option::{self, *}};
274} 359}
275#[prelude_import] 360#[prelude_import]
276pub use prelude::*; 361pub use prelude::*;
@@ -284,6 +369,14 @@ pub use prelude::*;
284 self.find_enum("core:option:Option") 369 self.find_enum("core:option:Option")
285 } 370 }
286 371
372 pub fn core_iter_Iterator(&self) -> Option<Trait> {
373 self.find_trait("core:iter:traits:iterator:Iterator")
374 }
375
376 pub fn core_iter(&self) -> Option<Module> {
377 self.find_module("core:iter")
378 }
379
287 fn find_trait(&self, path: &str) -> Option<Trait> { 380 fn find_trait(&self, path: &str) -> Option<Trait> {
288 match self.find_def(path)? { 381 match self.find_def(path)? {
289 hir::ScopeDef::ModuleDef(hir::ModuleDef::Trait(it)) => Some(it), 382 hir::ScopeDef::ModuleDef(hir::ModuleDef::Trait(it)) => Some(it),
@@ -298,23 +391,33 @@ pub use prelude::*;
298 } 391 }
299 } 392 }
300 393
394 fn find_module(&self, path: &str) -> Option<Module> {
395 match self.find_def(path)? {
396 hir::ScopeDef::ModuleDef(hir::ModuleDef::Module(it)) => Some(it),
397 _ => None,
398 }
399 }
400
301 fn find_def(&self, path: &str) -> Option<ScopeDef> { 401 fn find_def(&self, path: &str) -> Option<ScopeDef> {
302 let db = self.0.db; 402 let db = self.0.db;
303 let mut path = path.split(':'); 403 let mut path = path.split(':');
304 let trait_ = path.next_back()?; 404 let trait_ = path.next_back()?;
305 let std_crate = path.next()?; 405 let std_crate = path.next()?;
306 let std_crate = self 406 let std_crate = if self
307 .1 407 .1
308 .dependencies(db) 408 .declaration_name(db)
309 .into_iter() 409 .map(|name| name.to_string() == std_crate)
310 .find(|dep| &dep.name.to_string() == std_crate)? 410 .unwrap_or(false)
311 .krate; 411 {
312 412 self.1
413 } else {
414 self.1.dependencies(db).into_iter().find(|dep| dep.name.to_string() == std_crate)?.krate
415 };
313 let mut module = std_crate.root_module(db); 416 let mut module = std_crate.root_module(db);
314 for segment in path { 417 for segment in path {
315 module = module.children(db).find_map(|child| { 418 module = module.children(db).find_map(|child| {
316 let name = child.name(db)?; 419 let name = child.name(db)?;
317 if &name.to_string() == segment { 420 if name.to_string() == segment {
318 Some(child) 421 Some(child)
319 } else { 422 } else {
320 None 423 None
@@ -322,7 +425,7 @@ pub use prelude::*;
322 })?; 425 })?;
323 } 426 }
324 let def = 427 let def =
325 module.scope(db, None).into_iter().find(|(name, _def)| &name.to_string() == trait_)?.1; 428 module.scope(db, None).into_iter().find(|(name, _def)| name.to_string() == trait_)?.1;
326 Some(def) 429 Some(def)
327 } 430 }
328} 431}
diff --git a/crates/base_db/src/fixture.rs b/crates/base_db/src/fixture.rs
index b7286fc7d..72f1fd667 100644
--- a/crates/base_db/src/fixture.rs
+++ b/crates/base_db/src/fixture.rs
@@ -154,19 +154,19 @@ impl ChangeFixture {
154 assert!(meta.path.starts_with(&source_root_prefix)); 154 assert!(meta.path.starts_with(&source_root_prefix));
155 155
156 if let Some(krate) = meta.krate { 156 if let Some(krate) = meta.krate {
157 let crate_name = CrateName::normalize_dashes(&krate);
157 let crate_id = crate_graph.add_crate_root( 158 let crate_id = crate_graph.add_crate_root(
158 file_id, 159 file_id,
159 meta.edition, 160 meta.edition,
160 Some(krate.clone()), 161 Some(crate_name.clone()),
161 meta.cfg, 162 meta.cfg,
162 meta.env, 163 meta.env,
163 Default::default(), 164 Default::default(),
164 ); 165 );
165 let crate_name = CrateName::new(&krate).unwrap();
166 let prev = crates.insert(crate_name.clone(), crate_id); 166 let prev = crates.insert(crate_name.clone(), crate_id);
167 assert!(prev.is_none()); 167 assert!(prev.is_none());
168 for dep in meta.deps { 168 for dep in meta.deps {
169 let dep = CrateName::new(&dep).unwrap(); 169 let dep = CrateName::normalize_dashes(&dep);
170 crate_deps.push((crate_name.clone(), dep)) 170 crate_deps.push((crate_name.clone(), dep))
171 } 171 }
172 } else if meta.path == "/main.rs" || meta.path == "/lib.rs" { 172 } else if meta.path == "/main.rs" || meta.path == "/lib.rs" {
@@ -187,7 +187,7 @@ impl ChangeFixture {
187 crate_graph.add_crate_root( 187 crate_graph.add_crate_root(
188 crate_root, 188 crate_root,
189 Edition::Edition2018, 189 Edition::Edition2018,
190 Some("test".to_string()), 190 Some(CrateName::new("test").unwrap()),
191 default_cfg, 191 default_cfg,
192 Env::default(), 192 Env::default(),
193 Default::default(), 193 Default::default(),
diff --git a/crates/base_db/src/input.rs b/crates/base_db/src/input.rs
index 9a61f1d56..c330314d4 100644
--- a/crates/base_db/src/input.rs
+++ b/crates/base_db/src/input.rs
@@ -127,10 +127,11 @@ impl PartialEq for ProcMacro {
127pub struct CrateData { 127pub struct CrateData {
128 pub root_file_id: FileId, 128 pub root_file_id: FileId,
129 pub edition: Edition, 129 pub edition: Edition,
130 /// The name to display to the end user. 130 /// A name used in the package's project declaration: for Cargo projects, it's [package].name,
131 /// This actual crate name can be different in a particular dependent crate 131 /// can be different for other project types or even absent (a dummy crate for the code snippet, for example).
132 /// or may even be missing for some cases, such as a dummy crate for the code snippet. 132 /// NOTE: The crate can be referenced as a dependency under a different name,
133 pub display_name: Option<String>, 133 /// this one should be used when working with crate hierarchies.
134 pub declaration_name: Option<CrateName>,
134 pub cfg_options: CfgOptions, 135 pub cfg_options: CfgOptions,
135 pub env: Env, 136 pub env: Env,
136 pub dependencies: Vec<Dependency>, 137 pub dependencies: Vec<Dependency>,
@@ -159,7 +160,7 @@ impl CrateGraph {
159 &mut self, 160 &mut self,
160 file_id: FileId, 161 file_id: FileId,
161 edition: Edition, 162 edition: Edition,
162 display_name: Option<String>, 163 declaration_name: Option<CrateName>,
163 cfg_options: CfgOptions, 164 cfg_options: CfgOptions,
164 env: Env, 165 env: Env,
165 proc_macro: Vec<(SmolStr, Arc<dyn tt::TokenExpander>)>, 166 proc_macro: Vec<(SmolStr, Arc<dyn tt::TokenExpander>)>,
@@ -170,7 +171,7 @@ impl CrateGraph {
170 let data = CrateData { 171 let data = CrateData {
171 root_file_id: file_id, 172 root_file_id: file_id,
172 edition, 173 edition,
173 display_name, 174 declaration_name,
174 cfg_options, 175 cfg_options,
175 env, 176 env,
176 proc_macro, 177 proc_macro,
diff --git a/crates/hir/src/code_model.rs b/crates/hir/src/code_model.rs
index 5721a66c4..031c91ccf 100644
--- a/crates/hir/src/code_model.rs
+++ b/crates/hir/src/code_model.rs
@@ -2,8 +2,9 @@
2use std::{iter, sync::Arc}; 2use std::{iter, sync::Arc};
3 3
4use arrayvec::ArrayVec; 4use arrayvec::ArrayVec;
5use base_db::{CrateId, Edition, FileId}; 5use base_db::{CrateId, CrateName, Edition, FileId};
6use either::Either; 6use either::Either;
7use hir_def::find_path::PrefixKind;
7use hir_def::{ 8use hir_def::{
8 adt::ReprKind, 9 adt::ReprKind,
9 adt::StructKind, 10 adt::StructKind,
@@ -29,8 +30,12 @@ use hir_expand::{
29use hir_ty::{ 30use hir_ty::{
30 autoderef, 31 autoderef,
31 display::{HirDisplayError, HirFormatter}, 32 display::{HirDisplayError, HirFormatter},
32 method_resolution, ApplicationTy, CallableDefId, Canonical, FnSig, GenericPredicate, 33 method_resolution,
33 InEnvironment, Substs, TraitEnvironment, Ty, TyDefId, TypeCtor, 34 traits::Solution,
35 traits::SolutionVariables,
36 ApplicationTy, BoundVar, CallableDefId, Canonical, DebruijnIndex, FnSig, GenericPredicate,
37 InEnvironment, Obligation, ProjectionPredicate, ProjectionTy, Substs, TraitEnvironment, Ty,
38 TyDefId, TyKind, TypeCtor,
34}; 39};
35use rustc_hash::FxHashSet; 40use rustc_hash::FxHashSet;
36use stdx::impl_from; 41use stdx::impl_from;
@@ -98,8 +103,8 @@ impl Crate {
98 db.crate_graph()[self.id].edition 103 db.crate_graph()[self.id].edition
99 } 104 }
100 105
101 pub fn display_name(self, db: &dyn HirDatabase) -> Option<String> { 106 pub fn declaration_name(self, db: &dyn HirDatabase) -> Option<CrateName> {
102 db.crate_graph()[self.id].display_name.clone() 107 db.crate_graph()[self.id].declaration_name.clone()
103 } 108 }
104 109
105 pub fn query_external_importables( 110 pub fn query_external_importables(
@@ -390,8 +395,9 @@ impl Module {
390 self, 395 self,
391 db: &dyn DefDatabase, 396 db: &dyn DefDatabase,
392 item: impl Into<ItemInNs>, 397 item: impl Into<ItemInNs>,
398 prefix_kind: PrefixKind,
393 ) -> Option<ModPath> { 399 ) -> Option<ModPath> {
394 hir_def::find_path::find_path_prefixed(db, item.into(), self.into()) 400 hir_def::find_path::find_path_prefixed(db, item.into(), self.into(), prefix_kind)
395 } 401 }
396} 402}
397 403
@@ -1360,6 +1366,35 @@ impl Type {
1360 db.trait_solve(self.krate, goal).is_some() 1366 db.trait_solve(self.krate, goal).is_some()
1361 } 1367 }
1362 1368
1369 pub fn normalize_trait_assoc_type(
1370 &self,
1371 db: &dyn HirDatabase,
1372 r#trait: Trait,
1373 args: &[Type],
1374 alias: TypeAlias,
1375 ) -> Option<Ty> {
1376 let subst = Substs::build_for_def(db, r#trait.id)
1377 .push(self.ty.value.clone())
1378 .fill(args.iter().map(|t| t.ty.value.clone()))
1379 .build();
1380 let predicate = ProjectionPredicate {
1381 projection_ty: ProjectionTy { associated_ty: alias.id, parameters: subst },
1382 ty: Ty::Bound(BoundVar::new(DebruijnIndex::INNERMOST, 0)),
1383 };
1384 let goal = Canonical {
1385 value: InEnvironment::new(
1386 self.ty.environment.clone(),
1387 Obligation::Projection(predicate),
1388 ),
1389 kinds: Arc::new([TyKind::General]),
1390 };
1391
1392 match db.trait_solve(self.krate, goal)? {
1393 Solution::Unique(SolutionVariables(subst)) => subst.value.first().cloned(),
1394 Solution::Ambig(_) => None,
1395 }
1396 }
1397
1363 pub fn is_copy(&self, db: &dyn HirDatabase) -> bool { 1398 pub fn is_copy(&self, db: &dyn HirDatabase) -> bool {
1364 let lang_item = db.lang_item(self.krate, SmolStr::new("copy")); 1399 let lang_item = db.lang_item(self.krate, SmolStr::new("copy"));
1365 let copy_trait = match lang_item { 1400 let copy_trait = match lang_item {
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index b9d9c7e25..4094a76cb 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -48,13 +48,14 @@ pub use hir_def::{
48 body::scope::ExprScopes, 48 body::scope::ExprScopes,
49 builtin_type::BuiltinType, 49 builtin_type::BuiltinType,
50 docs::Documentation, 50 docs::Documentation,
51 find_path::PrefixKind,
51 item_scope::ItemInNs, 52 item_scope::ItemInNs,
52 nameres::ModuleSource, 53 nameres::ModuleSource,
53 path::ModPath, 54 path::{ModPath, PathKind},
54 type_ref::{Mutability, TypeRef}, 55 type_ref::{Mutability, TypeRef},
55}; 56};
56pub use hir_expand::{ 57pub use hir_expand::{
57 name::AsName, name::Name, HirFileId, InFile, MacroCallId, MacroCallLoc, 58 name::known, name::AsName, name::Name, HirFileId, InFile, MacroCallId, MacroCallLoc,
58 /* FIXME */ MacroDefId, MacroFile, Origin, 59 /* FIXME */ MacroDefId, MacroFile, Origin,
59}; 60};
60pub use hir_ty::display::HirDisplay; 61pub use hir_ty::display::HirDisplay;
@@ -62,7 +63,4 @@ pub use hir_ty::display::HirDisplay;
62// These are negative re-exports: pub using these names is forbidden, they 63// These are negative re-exports: pub using these names is forbidden, they
63// should remain private to hir internals. 64// should remain private to hir internals.
64#[allow(unused)] 65#[allow(unused)]
65use { 66use {hir_def::path::Path, hir_expand::hygiene::Hygiene};
66 hir_def::path::{Path, PathKind},
67 hir_expand::hygiene::Hygiene,
68};
diff --git a/crates/hir_def/src/find_path.rs b/crates/hir_def/src/find_path.rs
index baf374144..02613c4c4 100644
--- a/crates/hir_def/src/find_path.rs
+++ b/crates/hir_def/src/find_path.rs
@@ -19,12 +19,17 @@ use crate::{
19/// *from where* you're referring to the item, hence the `from` parameter. 19/// *from where* you're referring to the item, hence the `from` parameter.
20pub fn find_path(db: &dyn DefDatabase, item: ItemInNs, from: ModuleId) -> Option<ModPath> { 20pub fn find_path(db: &dyn DefDatabase, item: ItemInNs, from: ModuleId) -> Option<ModPath> {
21 let _p = profile::span("find_path"); 21 let _p = profile::span("find_path");
22 find_path_inner(db, item, from, MAX_PATH_LEN, Prefixed::Not) 22 find_path_inner(db, item, from, MAX_PATH_LEN, None)
23} 23}
24 24
25pub fn find_path_prefixed(db: &dyn DefDatabase, item: ItemInNs, from: ModuleId) -> Option<ModPath> { 25pub fn find_path_prefixed(
26 let _p = profile::span("find_path_absolute"); 26 db: &dyn DefDatabase,
27 find_path_inner(db, item, from, MAX_PATH_LEN, Prefixed::Plain) 27 item: ItemInNs,
28 from: ModuleId,
29 prefix_kind: PrefixKind,
30) -> Option<ModPath> {
31 let _p = profile::span("find_path_prefixed");
32 find_path_inner(db, item, from, MAX_PATH_LEN, Some(prefix_kind))
28} 33}
29 34
30const MAX_PATH_LEN: usize = 15; 35const MAX_PATH_LEN: usize = 15;
@@ -42,58 +47,52 @@ impl ModPath {
42 } 47 }
43} 48}
44 49
45fn check_crate_self_super( 50fn check_self_super(def_map: &CrateDefMap, item: ItemInNs, from: ModuleId) -> Option<ModPath> {
46 def_map: &CrateDefMap, 51 if item == ItemInNs::Types(from.into()) {
47 item: ItemInNs,
48 from: ModuleId,
49) -> Option<ModPath> {
50 // - if the item is the crate root, return `crate`
51 if item
52 == ItemInNs::Types(ModuleDefId::ModuleId(ModuleId {
53 krate: from.krate,
54 local_id: def_map.root,
55 }))
56 {
57 Some(ModPath::from_segments(PathKind::Crate, Vec::new()))
58 } else if item == ItemInNs::Types(from.into()) {
59 // - if the item is the module we're in, use `self` 52 // - if the item is the module we're in, use `self`
60 Some(ModPath::from_segments(PathKind::Super(0), Vec::new())) 53 Some(ModPath::from_segments(PathKind::Super(0), Vec::new()))
61 } else { 54 } else if let Some(parent_id) = def_map.modules[from.local_id].parent {
62 if let Some(parent_id) = def_map.modules[from.local_id].parent { 55 // - if the item is the parent module, use `super` (this is not used recursively, since `super::super` is ugly)
63 // - if the item is the parent module, use `super` (this is not used recursively, since `super::super` is ugly) 56 if item
64 if item 57 == ItemInNs::Types(ModuleDefId::ModuleId(ModuleId {
65 == ItemInNs::Types(ModuleDefId::ModuleId(ModuleId { 58 krate: from.krate,
66 krate: from.krate, 59 local_id: parent_id,
67 local_id: parent_id, 60 }))
68 })) 61 {
69 { 62 Some(ModPath::from_segments(PathKind::Super(1), Vec::new()))
70 return Some(ModPath::from_segments(PathKind::Super(1), Vec::new())); 63 } else {
71 } 64 None
72 } 65 }
66 } else {
73 None 67 None
74 } 68 }
75} 69}
76 70
77#[derive(Copy, Clone, PartialEq, Eq)] 71#[derive(Copy, Clone, Debug, PartialEq, Eq)]
78pub enum Prefixed { 72pub enum PrefixKind {
79 Not, 73 /// Causes paths to always start with either `self`, `super`, `crate` or a crate-name.
74 /// This is the same as plain, just that paths will start with `self` iprepended f the path
75 /// starts with an identifier that is not a crate.
80 BySelf, 76 BySelf,
77 /// Causes paths to ignore imports in the local module.
81 Plain, 78 Plain,
79 /// Causes paths to start with `crate` where applicable, effectively forcing paths to be absolute.
80 ByCrate,
82} 81}
83 82
84impl Prefixed { 83impl PrefixKind {
85 #[inline] 84 #[inline]
86 fn prefix(self) -> Option<PathKind> { 85 fn prefix(self) -> PathKind {
87 match self { 86 match self {
88 Prefixed::Not => None, 87 PrefixKind::BySelf => PathKind::Super(0),
89 Prefixed::BySelf => Some(PathKind::Super(0)), 88 PrefixKind::Plain => PathKind::Plain,
90 Prefixed::Plain => Some(PathKind::Plain), 89 PrefixKind::ByCrate => PathKind::Crate,
91 } 90 }
92 } 91 }
93 92
94 #[inline] 93 #[inline]
95 fn prefixed(self) -> bool { 94 fn is_absolute(&self) -> bool {
96 self != Prefixed::Not 95 self == &PrefixKind::ByCrate
97 } 96 }
98} 97}
99 98
@@ -102,7 +101,7 @@ fn find_path_inner(
102 item: ItemInNs, 101 item: ItemInNs,
103 from: ModuleId, 102 from: ModuleId,
104 max_len: usize, 103 max_len: usize,
105 prefixed: Prefixed, 104 prefixed: Option<PrefixKind>,
106) -> Option<ModPath> { 105) -> Option<ModPath> {
107 if max_len == 0 { 106 if max_len == 0 {
108 return None; 107 return None;
@@ -115,13 +114,25 @@ fn find_path_inner(
115 let from_scope: &crate::item_scope::ItemScope = &def_map.modules[from.local_id].scope; 114 let from_scope: &crate::item_scope::ItemScope = &def_map.modules[from.local_id].scope;
116 let scope_name = 115 let scope_name =
117 if let Some((name, _)) = from_scope.name_of(item) { Some(name.clone()) } else { None }; 116 if let Some((name, _)) = from_scope.name_of(item) { Some(name.clone()) } else { None };
118 if !prefixed.prefixed() && scope_name.is_some() { 117 if prefixed.is_none() && scope_name.is_some() {
119 return scope_name 118 return scope_name
120 .map(|scope_name| ModPath::from_segments(PathKind::Plain, vec![scope_name])); 119 .map(|scope_name| ModPath::from_segments(PathKind::Plain, vec![scope_name]));
121 } 120 }
122 121
123 if let modpath @ Some(_) = check_crate_self_super(&def_map, item, from) { 122 // - if the item is the crate root, return `crate`
124 return modpath; 123 if item
124 == ItemInNs::Types(ModuleDefId::ModuleId(ModuleId {
125 krate: from.krate,
126 local_id: def_map.root,
127 }))
128 {
129 return Some(ModPath::from_segments(PathKind::Crate, Vec::new()));
130 }
131
132 if prefixed.filter(PrefixKind::is_absolute).is_none() {
133 if let modpath @ Some(_) = check_self_super(&def_map, item, from) {
134 return modpath;
135 }
125 } 136 }
126 137
127 // - if the item is the crate root of a dependency crate, return the name from the extern prelude 138 // - if the item is the crate root of a dependency crate, return the name from the extern prelude
@@ -211,6 +222,7 @@ fn find_path_inner(
211 best_path_len - 1, 222 best_path_len - 1,
212 prefixed, 223 prefixed,
213 )?; 224 )?;
225 mark::hit!(partially_imported);
214 path.segments.push(info.path.segments.last().unwrap().clone()); 226 path.segments.push(info.path.segments.last().unwrap().clone());
215 Some(path) 227 Some(path)
216 }) 228 })
@@ -226,7 +238,7 @@ fn find_path_inner(
226 } 238 }
227 } 239 }
228 240
229 if let Some(prefix) = prefixed.prefix() { 241 if let Some(prefix) = prefixed.map(PrefixKind::prefix) {
230 best_path.or_else(|| { 242 best_path.or_else(|| {
231 scope_name.map(|scope_name| ModPath::from_segments(prefix, vec![scope_name])) 243 scope_name.map(|scope_name| ModPath::from_segments(prefix, vec![scope_name]))
232 }) 244 })
@@ -355,7 +367,7 @@ mod tests {
355 /// `code` needs to contain a cursor marker; checks that `find_path` for the 367 /// `code` needs to contain a cursor marker; checks that `find_path` for the
356 /// item the `path` refers to returns that same path when called from the 368 /// item the `path` refers to returns that same path when called from the
357 /// module the cursor is in. 369 /// module the cursor is in.
358 fn check_found_path_(ra_fixture: &str, path: &str, absolute: bool) { 370 fn check_found_path_(ra_fixture: &str, path: &str, prefix_kind: Option<PrefixKind>) {
359 let (db, pos) = TestDB::with_position(ra_fixture); 371 let (db, pos) = TestDB::with_position(ra_fixture);
360 let module = db.module_for_file(pos.file_id); 372 let module = db.module_for_file(pos.file_id);
361 let parsed_path_file = syntax::SourceFile::parse(&format!("use {};", path)); 373 let parsed_path_file = syntax::SourceFile::parse(&format!("use {};", path));
@@ -375,20 +387,22 @@ mod tests {
375 .take_types() 387 .take_types()
376 .unwrap(); 388 .unwrap();
377 389
378 let found_path = if absolute { find_path_prefixed } else { find_path }( 390 let found_path =
379 &db, 391 find_path_inner(&db, ItemInNs::Types(resolved), module, MAX_PATH_LEN, prefix_kind);
380 ItemInNs::Types(resolved), 392 assert_eq!(found_path, Some(mod_path), "{:?}", prefix_kind);
381 module,
382 );
383 assert_eq!(found_path, Some(mod_path), "absolute {}", absolute);
384 } 393 }
385 394
386 fn check_found_path(ra_fixture: &str, path: &str) { 395 fn check_found_path(
387 check_found_path_(ra_fixture, path, false); 396 ra_fixture: &str,
388 } 397 unprefixed: &str,
389 398 prefixed: &str,
390 fn check_found_path_abs(ra_fixture: &str, path: &str) { 399 absolute: &str,
391 check_found_path_(ra_fixture, path, true); 400 self_prefixed: &str,
401 ) {
402 check_found_path_(ra_fixture, unprefixed, None);
403 check_found_path_(ra_fixture, prefixed, Some(PrefixKind::Plain));
404 check_found_path_(ra_fixture, absolute, Some(PrefixKind::ByCrate));
405 check_found_path_(ra_fixture, self_prefixed, Some(PrefixKind::BySelf));
392 } 406 }
393 407
394 #[test] 408 #[test]
@@ -398,8 +412,7 @@ mod tests {
398 struct S; 412 struct S;
399 <|> 413 <|>
400 "#; 414 "#;
401 check_found_path(code, "S"); 415 check_found_path(code, "S", "S", "crate::S", "self::S");
402 check_found_path_abs(code, "S");
403 } 416 }
404 417
405 #[test] 418 #[test]
@@ -409,8 +422,7 @@ mod tests {
409 enum E { A } 422 enum E { A }
410 <|> 423 <|>
411 "#; 424 "#;
412 check_found_path(code, "E::A"); 425 check_found_path(code, "E::A", "E::A", "E::A", "E::A");
413 check_found_path_abs(code, "E::A");
414 } 426 }
415 427
416 #[test] 428 #[test]
@@ -422,8 +434,7 @@ mod tests {
422 } 434 }
423 <|> 435 <|>
424 "#; 436 "#;
425 check_found_path(code, "foo::S"); 437 check_found_path(code, "foo::S", "foo::S", "crate::foo::S", "self::foo::S");
426 check_found_path_abs(code, "foo::S");
427 } 438 }
428 439
429 #[test] 440 #[test]
@@ -437,8 +448,7 @@ mod tests {
437 //- /foo/bar.rs 448 //- /foo/bar.rs
438 <|> 449 <|>
439 "#; 450 "#;
440 check_found_path(code, "super::S"); 451 check_found_path(code, "super::S", "super::S", "crate::foo::S", "super::S");
441 check_found_path_abs(code, "super::S");
442 } 452 }
443 453
444 #[test] 454 #[test]
@@ -449,8 +459,7 @@ mod tests {
449 //- /foo.rs 459 //- /foo.rs
450 <|> 460 <|>
451 "#; 461 "#;
452 check_found_path(code, "self"); 462 check_found_path(code, "self", "self", "crate::foo", "self");
453 check_found_path_abs(code, "self");
454 } 463 }
455 464
456 #[test] 465 #[test]
@@ -461,8 +470,7 @@ mod tests {
461 //- /foo.rs 470 //- /foo.rs
462 <|> 471 <|>
463 "#; 472 "#;
464 check_found_path(code, "crate"); 473 check_found_path(code, "crate", "crate", "crate", "crate");
465 check_found_path_abs(code, "crate");
466 } 474 }
467 475
468 #[test] 476 #[test]
@@ -474,8 +482,7 @@ mod tests {
474 //- /foo.rs 482 //- /foo.rs
475 <|> 483 <|>
476 "#; 484 "#;
477 check_found_path(code, "crate::S"); 485 check_found_path(code, "crate::S", "crate::S", "crate::S", "crate::S");
478 check_found_path_abs(code, "crate::S");
479 } 486 }
480 487
481 #[test] 488 #[test]
@@ -486,8 +493,7 @@ mod tests {
486 //- /std.rs crate:std 493 //- /std.rs crate:std
487 pub struct S; 494 pub struct S;
488 "#; 495 "#;
489 check_found_path(code, "std::S"); 496 check_found_path(code, "std::S", "std::S", "std::S", "std::S");
490 check_found_path_abs(code, "std::S");
491 } 497 }
492 498
493 #[test] 499 #[test]
@@ -499,12 +505,18 @@ mod tests {
499 //- /std.rs crate:std 505 //- /std.rs crate:std
500 pub struct S; 506 pub struct S;
501 "#; 507 "#;
502 check_found_path(code, "std_renamed::S"); 508 check_found_path(
503 check_found_path_abs(code, "std_renamed::S"); 509 code,
510 "std_renamed::S",
511 "std_renamed::S",
512 "std_renamed::S",
513 "std_renamed::S",
514 );
504 } 515 }
505 516
506 #[test] 517 #[test]
507 fn partially_imported() { 518 fn partially_imported() {
519 mark::check!(partially_imported);
508 // 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
509 // already in scope. 521 // already in scope.
510 let code = r#" 522 let code = r#"
@@ -520,8 +532,13 @@ mod tests {
520 } 532 }
521 } 533 }
522 "#; 534 "#;
523 check_found_path(code, "ast::ModuleItem"); 535 check_found_path(
524 check_found_path_abs(code, "syntax::ast::ModuleItem"); 536 code,
537 "ast::ModuleItem",
538 "syntax::ast::ModuleItem",
539 "syntax::ast::ModuleItem",
540 "syntax::ast::ModuleItem",
541 );
525 542
526 let code = r#" 543 let code = r#"
527 //- /main.rs crate:main deps:syntax 544 //- /main.rs crate:main deps:syntax
@@ -535,8 +552,13 @@ mod tests {
535 } 552 }
536 } 553 }
537 "#; 554 "#;
538 check_found_path(code, "syntax::ast::ModuleItem"); 555 check_found_path(
539 check_found_path_abs(code, "syntax::ast::ModuleItem"); 556 code,
557 "syntax::ast::ModuleItem",
558 "syntax::ast::ModuleItem",
559 "syntax::ast::ModuleItem",
560 "syntax::ast::ModuleItem",
561 );
540 } 562 }
541 563
542 #[test] 564 #[test]
@@ -549,8 +571,7 @@ mod tests {
549 } 571 }
550 <|> 572 <|>
551 "#; 573 "#;
552 check_found_path(code, "bar::S"); 574 check_found_path(code, "bar::S", "bar::S", "crate::bar::S", "self::bar::S");
553 check_found_path_abs(code, "bar::S");
554 } 575 }
555 576
556 #[test] 577 #[test]
@@ -563,8 +584,7 @@ mod tests {
563 } 584 }
564 <|> 585 <|>
565 "#; 586 "#;
566 check_found_path(code, "bar::U"); 587 check_found_path(code, "bar::U", "bar::U", "crate::bar::U", "self::bar::U");
567 check_found_path_abs(code, "bar::U");
568 } 588 }
569 589
570 #[test] 590 #[test]
@@ -577,8 +597,7 @@ mod tests {
577 //- /core.rs crate:core 597 //- /core.rs crate:core
578 pub struct S; 598 pub struct S;
579 "#; 599 "#;
580 check_found_path(code, "std::S"); 600 check_found_path(code, "std::S", "std::S", "std::S", "std::S");
581 check_found_path_abs(code, "std::S");
582 } 601 }
583 602
584 #[test] 603 #[test]
@@ -591,8 +610,7 @@ mod tests {
591 #[prelude_import] 610 #[prelude_import]
592 pub use prelude::*; 611 pub use prelude::*;
593 "#; 612 "#;
594 check_found_path(code, "S"); 613 check_found_path(code, "S", "S", "S", "S");
595 check_found_path_abs(code, "S");
596 } 614 }
597 615
598 #[test] 616 #[test]
@@ -608,10 +626,8 @@ mod tests {
608 #[prelude_import] 626 #[prelude_import]
609 pub use prelude::*; 627 pub use prelude::*;
610 "#; 628 "#;
611 check_found_path(code, "None"); 629 check_found_path(code, "None", "None", "None", "None");
612 check_found_path(code, "Some"); 630 check_found_path(code, "Some", "Some", "Some", "Some");
613 check_found_path_abs(code, "None");
614 check_found_path_abs(code, "Some");
615 } 631 }
616 632
617 #[test] 633 #[test]
@@ -627,8 +643,7 @@ mod tests {
627 //- /baz.rs 643 //- /baz.rs
628 pub use crate::foo::bar::S; 644 pub use crate::foo::bar::S;
629 "#; 645 "#;
630 check_found_path(code, "baz::S"); 646 check_found_path(code, "baz::S", "baz::S", "crate::baz::S", "self::baz::S");
631 check_found_path_abs(code, "baz::S");
632 } 647 }
633 648
634 #[test] 649 #[test]
@@ -642,8 +657,7 @@ mod tests {
642 <|> 657 <|>
643 "#; 658 "#;
644 // crate::S would be shorter, but using private imports seems wrong 659 // crate::S would be shorter, but using private imports seems wrong
645 check_found_path(code, "crate::bar::S"); 660 check_found_path(code, "crate::bar::S", "crate::bar::S", "crate::bar::S", "crate::bar::S");
646 check_found_path_abs(code, "crate::bar::S");
647 } 661 }
648 662
649 #[test] 663 #[test]
@@ -661,8 +675,7 @@ mod tests {
661 //- /baz.rs 675 //- /baz.rs
662 pub use super::foo; 676 pub use super::foo;
663 "#; 677 "#;
664 check_found_path(code, "crate::foo::S"); 678 check_found_path(code, "crate::foo::S", "crate::foo::S", "crate::foo::S", "crate::foo::S");
665 check_found_path_abs(code, "crate::foo::S");
666 } 679 }
667 680
668 #[test] 681 #[test]
@@ -682,8 +695,13 @@ mod tests {
682 pub struct Arc; 695 pub struct Arc;
683 } 696 }
684 "#; 697 "#;
685 check_found_path(code, "std::sync::Arc"); 698 check_found_path(
686 check_found_path_abs(code, "std::sync::Arc"); 699 code,
700 "std::sync::Arc",
701 "std::sync::Arc",
702 "std::sync::Arc",
703 "std::sync::Arc",
704 );
687 } 705 }
688 706
689 #[test] 707 #[test]
@@ -707,8 +725,13 @@ mod tests {
707 pub struct Error; 725 pub struct Error;
708 } 726 }
709 "#; 727 "#;
710 check_found_path(code, "core::fmt::Error"); 728 check_found_path(
711 check_found_path_abs(code, "core::fmt::Error"); 729 code,
730 "core::fmt::Error",
731 "core::fmt::Error",
732 "core::fmt::Error",
733 "core::fmt::Error",
734 );
712 } 735 }
713 736
714 #[test] 737 #[test]
@@ -731,8 +754,13 @@ mod tests {
731 pub struct Arc; 754 pub struct Arc;
732 } 755 }
733 "#; 756 "#;
734 check_found_path(code, "alloc::sync::Arc"); 757 check_found_path(
735 check_found_path_abs(code, "alloc::sync::Arc"); 758 code,
759 "alloc::sync::Arc",
760 "alloc::sync::Arc",
761 "alloc::sync::Arc",
762 "alloc::sync::Arc",
763 );
736 } 764 }
737 765
738 #[test] 766 #[test]
@@ -749,8 +777,13 @@ mod tests {
749 //- /zzz.rs crate:megaalloc 777 //- /zzz.rs crate:megaalloc
750 pub struct Arc; 778 pub struct Arc;
751 "#; 779 "#;
752 check_found_path(code, "megaalloc::Arc"); 780 check_found_path(
753 check_found_path_abs(code, "megaalloc::Arc"); 781 code,
782 "megaalloc::Arc",
783 "megaalloc::Arc",
784 "megaalloc::Arc",
785 "megaalloc::Arc",
786 );
754 } 787 }
755 788
756 #[test] 789 #[test]
@@ -763,9 +796,7 @@ mod tests {
763 pub use u8; 796 pub use u8;
764 } 797 }
765 "#; 798 "#;
766 check_found_path(code, "u8"); 799 check_found_path(code, "u8", "u8", "u8", "u8");
767 check_found_path(code, "u16"); 800 check_found_path(code, "u16", "u16", "u16", "u16");
768 check_found_path_abs(code, "u8");
769 check_found_path_abs(code, "u16");
770 } 801 }
771} 802}
diff --git a/crates/hir_def/src/import_map.rs b/crates/hir_def/src/import_map.rs
index a442fb63a..028cae2e7 100644
--- a/crates/hir_def/src/import_map.rs
+++ b/crates/hir_def/src/import_map.rs
@@ -4,17 +4,16 @@ use std::{cmp::Ordering, fmt, hash::BuildHasherDefault, sync::Arc};
4 4
5use base_db::CrateId; 5use base_db::CrateId;
6use fst::{self, Streamer}; 6use fst::{self, Streamer};
7use hir_expand::name::Name;
7use indexmap::{map::Entry, IndexMap}; 8use indexmap::{map::Entry, IndexMap};
9use itertools::Itertools;
8use rustc_hash::{FxHashMap, FxHasher}; 10use rustc_hash::{FxHashMap, FxHasher};
9use smallvec::SmallVec; 11use smallvec::SmallVec;
10use syntax::SmolStr; 12use syntax::SmolStr;
11 13
12use crate::{ 14use crate::{
13 db::DefDatabase, 15 db::DefDatabase, item_scope::ItemInNs, visibility::Visibility, AssocItemId, ModuleDefId,
14 item_scope::ItemInNs, 16 ModuleId, TraitId,
15 path::{ModPath, PathKind},
16 visibility::Visibility,
17 AssocItemId, ModuleDefId, ModuleId, TraitId,
18}; 17};
19 18
20type FxIndexMap<K, V> = IndexMap<K, V, BuildHasherDefault<FxHasher>>; 19type FxIndexMap<K, V> = IndexMap<K, V, BuildHasherDefault<FxHasher>>;
@@ -23,11 +22,28 @@ type FxIndexMap<K, V> = IndexMap<K, V, BuildHasherDefault<FxHasher>>;
23#[derive(Debug, Clone, Eq, PartialEq)] 22#[derive(Debug, Clone, Eq, PartialEq)]
24pub struct ImportInfo { 23pub struct ImportInfo {
25 /// A path that can be used to import the item, relative to the crate's root. 24 /// A path that can be used to import the item, relative to the crate's root.
26 pub path: ModPath, 25 pub path: ImportPath,
27 /// The module containing this item. 26 /// The module containing this item.
28 pub container: ModuleId, 27 pub container: ModuleId,
29} 28}
30 29
30#[derive(Debug, Clone, Eq, PartialEq)]
31pub struct ImportPath {
32 pub segments: Vec<Name>,
33}
34
35impl fmt::Display for ImportPath {
36 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37 fmt::Display::fmt(&self.segments.iter().format("::"), f)
38 }
39}
40
41impl ImportPath {
42 fn len(&self) -> usize {
43 self.segments.len()
44 }
45}
46
31/// A map from publicly exported items to the path needed to import/name them from a downstream 47/// A map from publicly exported items to the path needed to import/name them from a downstream
32/// crate. 48/// crate.
33/// 49///
@@ -61,7 +77,7 @@ impl ImportMap {
61 let mut import_map = Self::default(); 77 let mut import_map = Self::default();
62 78
63 // We look only into modules that are public(ly reexported), starting with the crate root. 79 // We look only into modules that are public(ly reexported), starting with the crate root.
64 let empty = ModPath { kind: PathKind::Plain, segments: vec![] }; 80 let empty = ImportPath { segments: vec![] };
65 let root = ModuleId { krate, local_id: def_map.root }; 81 let root = ModuleId { krate, local_id: def_map.root };
66 let mut worklist = vec![(root, empty)]; 82 let mut worklist = vec![(root, empty)];
67 while let Some((module, mod_path)) = worklist.pop() { 83 while let Some((module, mod_path)) = worklist.pop() {
@@ -152,8 +168,8 @@ impl ImportMap {
152 } 168 }
153 169
154 /// Returns the `ModPath` needed to import/mention `item`, relative to this crate's root. 170 /// Returns the `ModPath` needed to import/mention `item`, relative to this crate's root.
155 pub fn path_of(&self, item: ItemInNs) -> Option<&ModPath> { 171 pub fn path_of(&self, item: ItemInNs) -> Option<&ImportPath> {
156 Some(&self.map.get(&item)?.path) 172 self.import_info_for(item).map(|it| &it.path)
157 } 173 }
158 174
159 pub fn import_info_for(&self, item: ItemInNs) -> Option<&ImportInfo> { 175 pub fn import_info_for(&self, item: ItemInNs) -> Option<&ImportInfo> {
@@ -197,7 +213,7 @@ impl fmt::Debug for ImportMap {
197 } 213 }
198} 214}
199 215
200fn fst_path(path: &ModPath) -> String { 216fn fst_path(path: &ImportPath) -> String {
201 let mut s = path.to_string(); 217 let mut s = path.to_string();
202 s.make_ascii_lowercase(); 218 s.make_ascii_lowercase();
203 s 219 s
@@ -334,14 +350,14 @@ mod tests {
334 350
335 use super::*; 351 use super::*;
336 352
337 fn check_search(ra_fixture: &str, krate_name: &str, query: Query, expect: Expect) { 353 fn check_search(ra_fixture: &str, crate_name: &str, query: Query, expect: Expect) {
338 let db = TestDB::with_files(ra_fixture); 354 let db = TestDB::with_files(ra_fixture);
339 let crate_graph = db.crate_graph(); 355 let crate_graph = db.crate_graph();
340 let krate = crate_graph 356 let krate = crate_graph
341 .iter() 357 .iter()
342 .find(|krate| { 358 .find(|krate| {
343 crate_graph[*krate].display_name.as_ref().map(|n| n.to_string()) 359 crate_graph[*krate].declaration_name.as_ref().map(|n| n.to_string())
344 == Some(krate_name.to_string()) 360 == Some(crate_name.to_string())
345 }) 361 })
346 .unwrap(); 362 .unwrap();
347 363
@@ -359,7 +375,7 @@ mod tests {
359 let path = map.path_of(item).unwrap(); 375 let path = map.path_of(item).unwrap();
360 format!( 376 format!(
361 "{}::{} ({})\n", 377 "{}::{} ({})\n",
362 crate_graph[krate].display_name.as_ref().unwrap(), 378 crate_graph[krate].declaration_name.as_ref().unwrap(),
363 path, 379 path,
364 mark 380 mark
365 ) 381 )
@@ -400,7 +416,7 @@ mod tests {
400 .iter() 416 .iter()
401 .filter_map(|krate| { 417 .filter_map(|krate| {
402 let cdata = &crate_graph[krate]; 418 let cdata = &crate_graph[krate];
403 let name = cdata.display_name.as_ref()?; 419 let name = cdata.declaration_name.as_ref()?;
404 420
405 let map = db.import_map(krate); 421 let map = db.import_map(krate);
406 422
diff --git a/crates/hir_def/src/nameres.rs b/crates/hir_def/src/nameres.rs
index 5e4d73c1f..464ffef21 100644
--- a/crates/hir_def/src/nameres.rs
+++ b/crates/hir_def/src/nameres.rs
@@ -173,7 +173,7 @@ impl CrateDefMap {
173 pub(crate) fn crate_def_map_query(db: &dyn DefDatabase, krate: CrateId) -> Arc<CrateDefMap> { 173 pub(crate) fn crate_def_map_query(db: &dyn DefDatabase, krate: CrateId) -> Arc<CrateDefMap> {
174 let _p = profile::span("crate_def_map_query").detail(|| { 174 let _p = profile::span("crate_def_map_query").detail(|| {
175 db.crate_graph()[krate] 175 db.crate_graph()[krate]
176 .display_name 176 .declaration_name
177 .as_ref() 177 .as_ref()
178 .map(ToString::to_string) 178 .map(ToString::to_string)
179 .unwrap_or_default() 179 .unwrap_or_default()
diff --git a/crates/hir_def/src/nameres/collector.rs b/crates/hir_def/src/nameres/collector.rs
index 100e25ffc..c8cd04264 100644
--- a/crates/hir_def/src/nameres/collector.rs
+++ b/crates/hir_def/src/nameres/collector.rs
@@ -1229,9 +1229,10 @@ impl ModCollector<'_, '_> {
1229 } else { 1229 } else {
1230 let derive = attrs.by_key("proc_macro_derive"); 1230 let derive = attrs.by_key("proc_macro_derive");
1231 if let Some(arg) = derive.tt_values().next() { 1231 if let Some(arg) = derive.tt_values().next() {
1232 if let [TokenTree::Leaf(Leaf::Ident(trait_name))] = &*arg.token_trees { 1232 if let [TokenTree::Leaf(Leaf::Ident(trait_name)), ..] = &*arg.token_trees {
1233 trait_name.as_name() 1233 trait_name.as_name()
1234 } else { 1234 } else {
1235 log::trace!("malformed `#[proc_macro_derive]`: {}", arg);
1235 return; 1236 return;
1236 } 1237 }
1237 } else { 1238 } else {
diff --git a/crates/hir_def/src/nameres/tests/macros.rs b/crates/hir_def/src/nameres/tests/macros.rs
index 0851c3b7d..305fca0f9 100644
--- a/crates/hir_def/src/nameres/tests/macros.rs
+++ b/crates/hir_def/src/nameres/tests/macros.rs
@@ -688,13 +688,20 @@ fn resolves_proc_macros() {
688 pub fn derive_macro(_item: TokenStream) -> TokenStream { 688 pub fn derive_macro(_item: TokenStream) -> TokenStream {
689 TokenStream 689 TokenStream
690 } 690 }
691
692 #[proc_macro_derive(AnotherTrait, attributes(helper_attr))]
693 pub fn derive_macro_2(_item: TokenStream) -> TokenStream {
694 TokenStream
695 }
691 ", 696 ",
692 expect![[r#" 697 expect![[r#"
693 crate 698 crate
699 AnotherTrait: m
694 DummyTrait: m 700 DummyTrait: m
695 TokenStream: t v 701 TokenStream: t v
696 attribute_macro: v m 702 attribute_macro: v m
697 derive_macro: v 703 derive_macro: v
704 derive_macro_2: v
698 function_like_macro: v m 705 function_like_macro: v m
699 "#]], 706 "#]],
700 ); 707 );
diff --git a/crates/hir_def/src/path.rs b/crates/hir_def/src/path.rs
index 209b18e78..5b8c1e449 100644
--- a/crates/hir_def/src/path.rs
+++ b/crates/hir_def/src/path.rs
@@ -13,7 +13,7 @@ use hir_expand::{
13 hygiene::Hygiene, 13 hygiene::Hygiene,
14 name::{AsName, Name}, 14 name::{AsName, Name},
15}; 15};
16use syntax::ast::{self, make}; 16use syntax::ast::{self};
17 17
18use crate::{ 18use crate::{
19 type_ref::{TypeBound, TypeRef}, 19 type_ref::{TypeBound, TypeRef},
@@ -100,26 +100,6 @@ impl ModPath {
100 } 100 }
101 self.segments.first() 101 self.segments.first()
102 } 102 }
103
104 pub fn to_ast_path(&self) -> ast::Path {
105 let mut segments = Vec::new();
106 let mut is_abs = false;
107 match self.kind {
108 PathKind::Plain => {}
109 PathKind::Super(0) => segments.push(make::path_segment_self()),
110 PathKind::Super(n) => segments.extend((0..n).map(|_| make::path_segment_super())),
111 PathKind::Crate => segments.push(make::path_segment_crate()),
112 PathKind::Abs => is_abs = true,
113 PathKind::DollarCrate(_) => (),
114 }
115
116 segments.extend(
117 self.segments
118 .iter()
119 .map(|segment| make::path_segment(make::name_ref(&segment.to_string()))),
120 );
121 make::path_from_segments(segments, is_abs)
122 }
123} 103}
124 104
125#[derive(Debug, Clone, PartialEq, Eq, Hash)] 105#[derive(Debug, Clone, PartialEq, Eq, Hash)]
diff --git a/crates/hir_expand/src/name.rs b/crates/hir_expand/src/name.rs
index a5750d829..63f828707 100644
--- a/crates/hir_expand/src/name.rs
+++ b/crates/hir_expand/src/name.rs
@@ -164,6 +164,7 @@ pub mod known {
164 result, 164 result,
165 boxed, 165 boxed,
166 // Components of known path (type name) 166 // Components of known path (type name)
167 Iterator,
167 IntoIterator, 168 IntoIterator,
168 Item, 169 Item,
169 Try, 170 Try,
diff --git a/crates/hir_ty/Cargo.toml b/crates/hir_ty/Cargo.toml
index ed1c911c2..0f3c85926 100644
--- a/crates/hir_ty/Cargo.toml
+++ b/crates/hir_ty/Cargo.toml
@@ -17,9 +17,9 @@ ena = "0.14.0"
17log = "0.4.8" 17log = "0.4.8"
18rustc-hash = "1.1.0" 18rustc-hash = "1.1.0"
19scoped-tls = "1" 19scoped-tls = "1"
20chalk-solve = { version = "0.30.0" } 20chalk-solve = "0.32"
21chalk-ir = { version = "0.30.0" } 21chalk-ir = "0.32"
22chalk-recursive = { version = "0.30.0" } 22chalk-recursive = "0.32"
23 23
24stdx = { path = "../stdx", version = "0.0.0" } 24stdx = { path = "../stdx", version = "0.0.0" }
25hir_def = { path = "../hir_def", version = "0.0.0" } 25hir_def = { path = "../hir_def", version = "0.0.0" }
diff --git a/crates/hir_ty/src/display.rs b/crates/hir_ty/src/display.rs
index f389c5a4b..d2e151f25 100644
--- a/crates/hir_ty/src/display.rs
+++ b/crates/hir_ty/src/display.rs
@@ -221,7 +221,16 @@ impl HirDisplay for ApplicationTy {
221 } 221 }
222 TypeCtor::RawPtr(m) => { 222 TypeCtor::RawPtr(m) => {
223 let t = self.parameters.as_single(); 223 let t = self.parameters.as_single();
224 write!(f, "*{}{}", m.as_keyword_for_ptr(), t.display(f.db))?; 224 let ty_display = t.display(f.db);
225
226 write!(f, "*{}", m.as_keyword_for_ptr())?;
227 if matches!(t, Ty::Dyn(predicates) if predicates.len() > 1) {
228 write!(f, "(")?;
229 write!(f, "{}", ty_display)?;
230 write!(f, ")")?;
231 } else {
232 write!(f, "{}", ty_display)?;
233 }
225 } 234 }
226 TypeCtor::Ref(m) => { 235 TypeCtor::Ref(m) => {
227 let t = self.parameters.as_single(); 236 let t = self.parameters.as_single();
@@ -230,7 +239,15 @@ impl HirDisplay for ApplicationTy {
230 } else { 239 } else {
231 t.display(f.db) 240 t.display(f.db)
232 }; 241 };
233 write!(f, "&{}{}", m.as_keyword_for_ref(), ty_display)?; 242
243 write!(f, "&{}", m.as_keyword_for_ref())?;
244 if matches!(t, Ty::Dyn(predicates) if predicates.len() > 1) {
245 write!(f, "(")?;
246 write!(f, "{}", ty_display)?;
247 write!(f, ")")?;
248 } else {
249 write!(f, "{}", ty_display)?;
250 }
234 } 251 }
235 TypeCtor::Never => write!(f, "!")?, 252 TypeCtor::Never => write!(f, "!")?,
236 TypeCtor::Tuple { .. } => { 253 TypeCtor::Tuple { .. } => {
@@ -636,14 +653,14 @@ impl HirDisplay for GenericPredicate {
636 653
637impl HirDisplay for Obligation { 654impl HirDisplay for Obligation {
638 fn hir_fmt(&self, f: &mut HirFormatter) -> Result<(), HirDisplayError> { 655 fn hir_fmt(&self, f: &mut HirFormatter) -> Result<(), HirDisplayError> {
639 Ok(match self { 656 match self {
640 Obligation::Trait(tr) => write!(f, "Implements({})", tr.display(f.db))?, 657 Obligation::Trait(tr) => write!(f, "Implements({})", tr.display(f.db)),
641 Obligation::Projection(proj) => write!( 658 Obligation::Projection(proj) => write!(
642 f, 659 f,
643 "Normalize({} => {})", 660 "Normalize({} => {})",
644 proj.projection_ty.display(f.db), 661 proj.projection_ty.display(f.db),
645 proj.ty.display(f.db) 662 proj.ty.display(f.db)
646 )?, 663 ),
647 }) 664 }
648 } 665 }
649} 666}
diff --git a/crates/hir_ty/src/traits/chalk.rs b/crates/hir_ty/src/traits/chalk.rs
index 009b17a7f..cbe5cd7dd 100644
--- a/crates/hir_ty/src/traits/chalk.rs
+++ b/crates/hir_ty/src/traits/chalk.rs
@@ -330,6 +330,20 @@ impl<'a> chalk_solve::RustIrDatabase<Interner> for ChalkContext<'a> {
330 fn fn_def_name(&self, fn_def_id: chalk_ir::FnDefId<Interner>) -> String { 330 fn fn_def_name(&self, fn_def_id: chalk_ir::FnDefId<Interner>) -> String {
331 format!("fn_{}", fn_def_id.0) 331 format!("fn_{}", fn_def_id.0)
332 } 332 }
333 fn generator_datum(
334 &self,
335 _: chalk_ir::GeneratorId<Interner>,
336 ) -> std::sync::Arc<chalk_solve::rust_ir::GeneratorDatum<Interner>> {
337 // FIXME
338 unimplemented!()
339 }
340 fn generator_witness_datum(
341 &self,
342 _: chalk_ir::GeneratorId<Interner>,
343 ) -> std::sync::Arc<chalk_solve::rust_ir::GeneratorWitnessDatum<Interner>> {
344 // FIXME
345 unimplemented!()
346 }
333} 347}
334 348
335pub(crate) fn program_clauses_for_chalk_env_query( 349pub(crate) fn program_clauses_for_chalk_env_query(
diff --git a/crates/hir_ty/src/traits/chalk/mapping.rs b/crates/hir_ty/src/traits/chalk/mapping.rs
index d42f4bba9..be3301313 100644
--- a/crates/hir_ty/src/traits/chalk/mapping.rs
+++ b/crates/hir_ty/src/traits/chalk/mapping.rs
@@ -399,6 +399,8 @@ impl ToChalk for TypeCtor {
399 // this should not be reached, since we don't represent TypeName::Error with TypeCtor 399 // this should not be reached, since we don't represent TypeName::Error with TypeCtor
400 unreachable!() 400 unreachable!()
401 } 401 }
402 TypeName::Generator(_) => unimplemented!(), // FIXME
403 TypeName::GeneratorWitness(_) => unimplemented!(), // FIXME
402 } 404 }
403 } 405 }
404} 406}
diff --git a/crates/ide/src/completion/complete_postfix/format_like.rs b/crates/ide/src/completion/complete_postfix/format_like.rs
index 0287fc803..81c33bf3a 100644
--- a/crates/ide/src/completion/complete_postfix/format_like.rs
+++ b/crates/ide/src/completion/complete_postfix/format_like.rs
@@ -1,4 +1,4 @@
1// Feature: Postfix completion for `format`-like strings. 1// Feature: Format String Completion.
2// 2//
3// `"Result {result} is {2 + 2}"` is expanded to the `"Result {} is {}", result, 2 + 2`. 3// `"Result {result} is {2 + 2}"` is expanded to the `"Result {} is {}", result, 2 + 2`.
4// 4//
diff --git a/crates/ide/src/completion/complete_unqualified_path.rs b/crates/ide/src/completion/complete_unqualified_path.rs
index 2010d9a2f..8b6757195 100644
--- a/crates/ide/src/completion/complete_unqualified_path.rs
+++ b/crates/ide/src/completion/complete_unqualified_path.rs
@@ -267,6 +267,26 @@ fn quux() { <|> }
267 ); 267 );
268 } 268 }
269 269
270 /// Regression test for issue #6091.
271 #[test]
272 fn correctly_completes_module_items_prefixed_with_underscore() {
273 check_edit(
274 "_alpha",
275 r#"
276fn main() {
277 _<|>
278}
279fn _alpha() {}
280"#,
281 r#"
282fn main() {
283 _alpha()$0
284}
285fn _alpha() {}
286"#,
287 )
288 }
289
270 #[test] 290 #[test]
271 fn completes_extern_prelude() { 291 fn completes_extern_prelude() {
272 check( 292 check(
diff --git a/crates/ide/src/completion/completion_context.rs b/crates/ide/src/completion/completion_context.rs
index 101be8eb5..8dea8a4bf 100644
--- a/crates/ide/src/completion/completion_context.rs
+++ b/crates/ide/src/completion/completion_context.rs
@@ -221,10 +221,11 @@ impl<'a> CompletionContext<'a> {
221 Some(ctx) 221 Some(ctx)
222 } 222 }
223 223
224 // The range of the identifier that is being completed. 224 /// The range of the identifier that is being completed.
225 pub(crate) fn source_range(&self) -> TextRange { 225 pub(crate) fn source_range(&self) -> TextRange {
226 // check kind of macro-expanded token, but use range of original token 226 // check kind of macro-expanded token, but use range of original token
227 if self.token.kind() == IDENT || self.token.kind().is_keyword() { 227 let kind = self.token.kind();
228 if kind == IDENT || kind == UNDERSCORE || kind.is_keyword() {
228 mark::hit!(completes_if_prefix_is_keyword); 229 mark::hit!(completes_if_prefix_is_keyword);
229 self.original_token.text_range() 230 self.original_token.text_range()
230 } else { 231 } else {
diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs
index 9cf02f0a3..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}
@@ -289,7 +300,7 @@ fn definition_owner_name(db: &RootDatabase, def: &Definition) -> Option<String>
289 300
290fn render_path(db: &RootDatabase, module: Module, item_name: Option<String>) -> String { 301fn render_path(db: &RootDatabase, module: Module, item_name: Option<String>) -> String {
291 let crate_name = 302 let crate_name =
292 db.crate_graph()[module.krate().into()].display_name.as_ref().map(ToString::to_string); 303 db.crate_graph()[module.krate().into()].declaration_name.as_ref().map(ToString::to_string);
293 let module_path = module 304 let module_path = module
294 .path_to_root(db) 305 .path_to_root(db)
295 .into_iter() 306 .into_iter()
@@ -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#"
@@ -3163,4 +3202,34 @@ fn main() { let s<|>t = test().get(); }
3163 "#]], 3202 "#]],
3164 ); 3203 );
3165 } 3204 }
3205
3206 #[test]
3207 fn hover_displays_normalized_crate_names() {
3208 check(
3209 r#"
3210//- /lib.rs crate:name-with-dashes
3211pub mod wrapper {
3212 pub struct Thing { x: u32 }
3213
3214 impl Thing {
3215 pub fn new() -> Thing { Thing { x: 0 } }
3216 }
3217}
3218
3219//- /main.rs crate:main deps:name-with-dashes
3220fn main() { let foo_test = name_with_dashes::wrapper::Thing::new<|>(); }
3221"#,
3222 expect![[r#"
3223 *new*
3224
3225 ```rust
3226 name_with_dashes::wrapper::Thing
3227 ```
3228
3229 ```rust
3230 pub fn new() -> Thing
3231 ```
3232 "#]],
3233 )
3234 }
3166} 3235}
diff --git a/crates/ide/src/inlay_hints.rs b/crates/ide/src/inlay_hints.rs
index 1d7e8de56..7d716577e 100644
--- a/crates/ide/src/inlay_hints.rs
+++ b/crates/ide/src/inlay_hints.rs
@@ -1,4 +1,5 @@
1use hir::{Adt, Callable, HirDisplay, Semantics, Type}; 1use assists::utils::FamousDefs;
2use hir::{known, HirDisplay, Semantics};
2use ide_db::RootDatabase; 3use ide_db::RootDatabase;
3use stdx::to_lower_snake_case; 4use stdx::to_lower_snake_case;
4use syntax::{ 5use syntax::{
@@ -119,17 +120,18 @@ fn get_chaining_hints(
119 return None; 120 return None;
120 } 121 }
121 if matches!(expr, ast::Expr::PathExpr(_)) { 122 if matches!(expr, ast::Expr::PathExpr(_)) {
122 if let Some(Adt::Struct(st)) = ty.as_adt() { 123 if let Some(hir::Adt::Struct(st)) = ty.as_adt() {
123 if st.fields(sema.db).is_empty() { 124 if st.fields(sema.db).is_empty() {
124 return None; 125 return None;
125 } 126 }
126 } 127 }
127 } 128 }
128 let label = ty.display_truncated(sema.db, config.max_length).to_string();
129 acc.push(InlayHint { 129 acc.push(InlayHint {
130 range: expr.syntax().text_range(), 130 range: expr.syntax().text_range(),
131 kind: InlayKind::ChainingHint, 131 kind: InlayKind::ChainingHint,
132 label: label.into(), 132 label: hint_iterator(sema, config, &ty).unwrap_or_else(|| {
133 ty.display_truncated(sema.db, config.max_length).to_string().into()
134 }),
133 }); 135 });
134 } 136 }
135 Some(()) 137 Some(())
@@ -192,17 +194,58 @@ fn get_bind_pat_hints(
192 if should_not_display_type_hint(sema, &pat, &ty) { 194 if should_not_display_type_hint(sema, &pat, &ty) {
193 return None; 195 return None;
194 } 196 }
195
196 acc.push(InlayHint { 197 acc.push(InlayHint {
197 range: pat.syntax().text_range(), 198 range: pat.syntax().text_range(),
198 kind: InlayKind::TypeHint, 199 kind: InlayKind::TypeHint,
199 label: ty.display_truncated(sema.db, config.max_length).to_string().into(), 200 label: hint_iterator(sema, config, &ty)
201 .unwrap_or_else(|| ty.display_truncated(sema.db, config.max_length).to_string().into()),
200 }); 202 });
203
201 Some(()) 204 Some(())
202} 205}
203 206
204fn pat_is_enum_variant(db: &RootDatabase, bind_pat: &ast::IdentPat, pat_ty: &Type) -> bool { 207/// Checks if the type is an Iterator from std::iter and replaces its hint with an `impl Iterator<Item = Ty>`.
205 if let Some(Adt::Enum(enum_data)) = pat_ty.as_adt() { 208fn hint_iterator(
209 sema: &Semantics<RootDatabase>,
210 config: &InlayHintsConfig,
211 ty: &hir::Type,
212) -> Option<SmolStr> {
213 let db = sema.db;
214 let strukt = std::iter::successors(Some(ty.clone()), |ty| ty.remove_ref())
215 .last()
216 .and_then(|strukt| strukt.as_adt())?;
217 let krate = strukt.krate(db)?;
218 if krate.declaration_name(db).as_deref() != Some("core") {
219 return None;
220 }
221 let iter_trait = FamousDefs(sema, krate).core_iter_Iterator()?;
222 let iter_mod = FamousDefs(sema, krate).core_iter()?;
223 // assert this type comes from `core::iter`
224 iter_mod.visibility_of(db, &iter_trait.into()).filter(|&vis| vis == hir::Visibility::Public)?;
225 if ty.impls_trait(db, iter_trait, &[]) {
226 let assoc_type_item = iter_trait.items(db).into_iter().find_map(|item| match item {
227 hir::AssocItem::TypeAlias(alias) if alias.name(db) == known::Item => Some(alias),
228 _ => None,
229 })?;
230 if let Some(ty) = ty.normalize_trait_assoc_type(db, iter_trait, &[], assoc_type_item) {
231 const LABEL_START: &str = "impl Iterator<Item = ";
232 const LABEL_END: &str = ">";
233
234 let ty_display = ty.display_truncated(
235 db,
236 config
237 .max_length
238 .map(|len| len.saturating_sub(LABEL_START.len() + LABEL_END.len())),
239 );
240 return Some(format!("{}{}{}", LABEL_START, ty_display, LABEL_END).into());
241 }
242 }
243
244 None
245}
246
247fn pat_is_enum_variant(db: &RootDatabase, bind_pat: &ast::IdentPat, pat_ty: &hir::Type) -> bool {
248 if let Some(hir::Adt::Enum(enum_data)) = pat_ty.as_adt() {
206 let pat_text = bind_pat.to_string(); 249 let pat_text = bind_pat.to_string();
207 enum_data 250 enum_data
208 .variants(db) 251 .variants(db)
@@ -217,7 +260,7 @@ fn pat_is_enum_variant(db: &RootDatabase, bind_pat: &ast::IdentPat, pat_ty: &Typ
217fn should_not_display_type_hint( 260fn should_not_display_type_hint(
218 sema: &Semantics<RootDatabase>, 261 sema: &Semantics<RootDatabase>,
219 bind_pat: &ast::IdentPat, 262 bind_pat: &ast::IdentPat,
220 pat_ty: &Type, 263 pat_ty: &hir::Type,
221) -> bool { 264) -> bool {
222 let db = sema.db; 265 let db = sema.db;
223 266
@@ -225,7 +268,7 @@ fn should_not_display_type_hint(
225 return true; 268 return true;
226 } 269 }
227 270
228 if let Some(Adt::Struct(s)) = pat_ty.as_adt() { 271 if let Some(hir::Adt::Struct(s)) = pat_ty.as_adt() {
229 if s.fields(db).is_empty() && s.name(db).to_string() == bind_pat.to_string() { 272 if s.fields(db).is_empty() && s.name(db).to_string() == bind_pat.to_string() {
230 return true; 273 return true;
231 } 274 }
@@ -269,7 +312,7 @@ fn should_not_display_type_hint(
269 312
270fn should_show_param_name_hint( 313fn should_show_param_name_hint(
271 sema: &Semantics<RootDatabase>, 314 sema: &Semantics<RootDatabase>,
272 callable: &Callable, 315 callable: &hir::Callable,
273 param_name: &str, 316 param_name: &str,
274 argument: &ast::Expr, 317 argument: &ast::Expr,
275) -> bool { 318) -> bool {
@@ -316,7 +359,7 @@ fn is_enum_name_similar_to_param_name(
316 param_name: &str, 359 param_name: &str,
317) -> bool { 360) -> bool {
318 match sema.type_of_expr(argument).and_then(|t| t.as_adt()) { 361 match sema.type_of_expr(argument).and_then(|t| t.as_adt()) {
319 Some(Adt::Enum(e)) => to_lower_snake_case(&e.name(sema.db).to_string()) == param_name, 362 Some(hir::Adt::Enum(e)) => to_lower_snake_case(&e.name(sema.db).to_string()) == param_name,
320 _ => false, 363 _ => false,
321 } 364 }
322} 365}
@@ -337,7 +380,7 @@ fn is_obvious_param(param_name: &str) -> bool {
337 param_name.len() == 1 || is_obvious_param_name 380 param_name.len() == 1 || is_obvious_param_name
338} 381}
339 382
340fn get_callable(sema: &Semantics<RootDatabase>, expr: &ast::Expr) -> Option<Callable> { 383fn get_callable(sema: &Semantics<RootDatabase>, expr: &ast::Expr) -> Option<hir::Callable> {
341 match expr { 384 match expr {
342 ast::Expr::CallExpr(expr) => sema.type_of_expr(&expr.expr()?)?.as_callable(sema.db), 385 ast::Expr::CallExpr(expr) => sema.type_of_expr(&expr.expr()?)?.as_callable(sema.db),
343 ast::Expr::MethodCallExpr(expr) => sema.resolve_method_call_as_callable(expr), 386 ast::Expr::MethodCallExpr(expr) => sema.resolve_method_call_as_callable(expr),
@@ -347,6 +390,7 @@ fn get_callable(sema: &Semantics<RootDatabase>, expr: &ast::Expr) -> Option<Call
347 390
348#[cfg(test)] 391#[cfg(test)]
349mod tests { 392mod tests {
393 use assists::utils::FamousDefs;
350 use expect_test::{expect, Expect}; 394 use expect_test::{expect, Expect};
351 use test_utils::extract_annotations; 395 use test_utils::extract_annotations;
352 396
@@ -357,7 +401,9 @@ mod tests {
357 } 401 }
358 402
359 fn check_with_config(config: InlayHintsConfig, ra_fixture: &str) { 403 fn check_with_config(config: InlayHintsConfig, ra_fixture: &str) {
360 let (analysis, file_id) = fixture::file(ra_fixture); 404 let ra_fixture =
405 format!("//- /main.rs crate:main deps:core\n{}\n{}", ra_fixture, FamousDefs::FIXTURE);
406 let (analysis, file_id) = fixture::file(&ra_fixture);
361 let expected = extract_annotations(&*analysis.file_text(file_id).unwrap()); 407 let expected = extract_annotations(&*analysis.file_text(file_id).unwrap());
362 let inlay_hints = analysis.inlay_hints(file_id, &config).unwrap(); 408 let inlay_hints = analysis.inlay_hints(file_id, &config).unwrap();
363 let actual = 409 let actual =
@@ -366,7 +412,9 @@ mod tests {
366 } 412 }
367 413
368 fn check_expect(config: InlayHintsConfig, ra_fixture: &str, expect: Expect) { 414 fn check_expect(config: InlayHintsConfig, ra_fixture: &str, expect: Expect) {
369 let (analysis, file_id) = fixture::file(ra_fixture); 415 let ra_fixture =
416 format!("//- /main.rs crate:main deps:core\n{}\n{}", ra_fixture, FamousDefs::FIXTURE);
417 let (analysis, file_id) = fixture::file(&ra_fixture);
370 let inlay_hints = analysis.inlay_hints(file_id, &config).unwrap(); 418 let inlay_hints = analysis.inlay_hints(file_id, &config).unwrap();
371 expect.assert_debug_eq(&inlay_hints) 419 expect.assert_debug_eq(&inlay_hints)
372 } 420 }
@@ -798,12 +846,12 @@ fn main() {
798 expect![[r#" 846 expect![[r#"
799 [ 847 [
800 InlayHint { 848 InlayHint {
801 range: 147..172, 849 range: 148..173,
802 kind: ChainingHint, 850 kind: ChainingHint,
803 label: "B", 851 label: "B",
804 }, 852 },
805 InlayHint { 853 InlayHint {
806 range: 147..154, 854 range: 148..155,
807 kind: ChainingHint, 855 kind: ChainingHint,
808 label: "A", 856 label: "A",
809 }, 857 },
@@ -864,12 +912,12 @@ fn main() {
864 expect![[r#" 912 expect![[r#"
865 [ 913 [
866 InlayHint { 914 InlayHint {
867 range: 143..190, 915 range: 144..191,
868 kind: ChainingHint, 916 kind: ChainingHint,
869 label: "C", 917 label: "C",
870 }, 918 },
871 InlayHint { 919 InlayHint {
872 range: 143..179, 920 range: 144..180,
873 kind: ChainingHint, 921 kind: ChainingHint,
874 label: "B", 922 label: "B",
875 }, 923 },
@@ -909,12 +957,12 @@ fn main() {
909 expect![[r#" 957 expect![[r#"
910 [ 958 [
911 InlayHint { 959 InlayHint {
912 range: 246..283, 960 range: 247..284,
913 kind: ChainingHint, 961 kind: ChainingHint,
914 label: "B<X<i32, bool>>", 962 label: "B<X<i32, bool>>",
915 }, 963 },
916 InlayHint { 964 InlayHint {
917 range: 246..265, 965 range: 247..266,
918 kind: ChainingHint, 966 kind: ChainingHint,
919 label: "A<X<i32, bool>>", 967 label: "A<X<i32, bool>>",
920 }, 968 },
@@ -935,7 +983,6 @@ fn main() {
935 ); 983 );
936 check( 984 check(
937 r#" 985 r#"
938//- /main.rs crate:main deps:core
939pub struct Vec<T> {} 986pub struct Vec<T> {}
940 987
941impl<T> Vec<T> { 988impl<T> Vec<T> {
@@ -956,13 +1003,6 @@ fn main() {
956 println!("Unit expr"); 1003 println!("Unit expr");
957} 1004}
958 1005
959//- /core.rs crate:core
960#[prelude_import] use iter::*;
961mod iter {
962 trait IntoIterator {
963 type Item;
964 }
965}
966//- /alloc.rs crate:alloc deps:core 1006//- /alloc.rs crate:alloc deps:core
967mod collections { 1007mod collections {
968 struct Vec<T> {} 1008 struct Vec<T> {}
@@ -982,7 +1022,6 @@ mod collections {
982 fn complete_for_hint() { 1022 fn complete_for_hint() {
983 check( 1023 check(
984 r#" 1024 r#"
985//- /main.rs crate:main deps:core
986pub struct Vec<T> {} 1025pub struct Vec<T> {}
987 1026
988impl<T> Vec<T> { 1027impl<T> Vec<T> {
@@ -1004,14 +1043,6 @@ fn main() {
1004 //^ &str 1043 //^ &str
1005 } 1044 }
1006} 1045}
1007
1008//- /core.rs crate:core
1009#[prelude_import] use iter::*;
1010mod iter {
1011 trait IntoIterator {
1012 type Item;
1013 }
1014}
1015//- /alloc.rs crate:alloc deps:core 1046//- /alloc.rs crate:alloc deps:core
1016mod collections { 1047mod collections {
1017 struct Vec<T> {} 1048 struct Vec<T> {}
@@ -1026,4 +1057,130 @@ mod collections {
1026"#, 1057"#,
1027 ); 1058 );
1028 } 1059 }
1060
1061 #[test]
1062 fn multi_dyn_trait_bounds() {
1063 check_with_config(
1064 InlayHintsConfig {
1065 type_hints: true,
1066 parameter_hints: false,
1067 chaining_hints: false,
1068 max_length: None,
1069 },
1070 r#"
1071pub struct Vec<T> {}
1072
1073impl<T> Vec<T> {
1074 pub fn new() -> Self { Vec {} }
1075}
1076
1077pub struct Box<T> {}
1078
1079trait Display {}
1080trait Sync {}
1081
1082fn main() {
1083 let _v = Vec::<Box<&(dyn Display + Sync)>>::new();
1084 //^^ Vec<Box<&(dyn Display + Sync)>>
1085 let _v = Vec::<Box<*const (dyn Display + Sync)>>::new();
1086 //^^ Vec<Box<*const (dyn Display + Sync)>>
1087 let _v = Vec::<Box<dyn Display + Sync>>::new();
1088 //^^ Vec<Box<dyn Display + Sync>>
1089}
1090"#,
1091 );
1092 }
1093
1094 #[test]
1095 fn shorten_iterator_hints() {
1096 check_with_config(
1097 InlayHintsConfig {
1098 parameter_hints: false,
1099 type_hints: true,
1100 chaining_hints: false,
1101 max_length: None,
1102 },
1103 r#"
1104use core::iter;
1105
1106struct MyIter;
1107
1108impl Iterator for MyIter {
1109 type Item = ();
1110 fn next(&mut self) -> Option<Self::Item> {
1111 None
1112 }
1113}
1114
1115fn main() {
1116 let _x = MyIter;
1117 //^^ MyIter
1118 let _x = iter::repeat(0);
1119 //^^ impl Iterator<Item = i32>
1120 fn generic<T: Clone>(t: T) {
1121 let _x = iter::repeat(t);
1122 //^^ impl Iterator<Item = T>
1123 let _chained = iter::repeat(t).take(10);
1124 //^^^^^^^^ impl Iterator<Item = T>
1125 }
1126}
1127"#,
1128 );
1129 }
1130
1131 #[test]
1132 fn shorten_iterator_chaining_hints() {
1133 check_expect(
1134 InlayHintsConfig {
1135 parameter_hints: false,
1136 type_hints: false,
1137 chaining_hints: true,
1138 max_length: None,
1139 },
1140 r#"
1141use core::iter;
1142
1143struct MyIter;
1144
1145impl Iterator for MyIter {
1146 type Item = ();
1147 fn next(&mut self) -> Option<Self::Item> {
1148 None
1149 }
1150}
1151
1152fn main() {
1153 let _x = MyIter.by_ref()
1154 .take(5)
1155 .by_ref()
1156 .take(5)
1157 .by_ref();
1158}
1159"#,
1160 expect![[r#"
1161 [
1162 InlayHint {
1163 range: 175..242,
1164 kind: ChainingHint,
1165 label: "impl Iterator<Item = ()>",
1166 },
1167 InlayHint {
1168 range: 175..225,
1169 kind: ChainingHint,
1170 label: "impl Iterator<Item = ()>",
1171 },
1172 InlayHint {
1173 range: 175..207,
1174 kind: ChainingHint,
1175 label: "impl Iterator<Item = ()>",
1176 },
1177 InlayHint {
1178 range: 175..190,
1179 kind: ChainingHint,
1180 label: "&mut MyIter",
1181 },
1182 ]
1183 "#]],
1184 );
1185 }
1029} 1186}
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/link_rewrite.rs b/crates/ide/src/link_rewrite.rs
index a16f90e17..c317a2379 100644
--- a/crates/ide/src/link_rewrite.rs
+++ b/crates/ide/src/link_rewrite.rs
@@ -107,7 +107,7 @@ fn rewrite_intra_doc_link(
107 let krate = resolved.module(db)?.krate(); 107 let krate = resolved.module(db)?.krate();
108 let canonical_path = resolved.canonical_path(db)?; 108 let canonical_path = resolved.canonical_path(db)?;
109 let new_target = get_doc_url(db, &krate)? 109 let new_target = get_doc_url(db, &krate)?
110 .join(&format!("{}/", krate.display_name(db)?)) 110 .join(&format!("{}/", krate.declaration_name(db)?))
111 .ok()? 111 .ok()?
112 .join(&canonical_path.replace("::", "/")) 112 .join(&canonical_path.replace("::", "/"))
113 .ok()? 113 .ok()?
@@ -127,7 +127,7 @@ fn rewrite_url_link(db: &RootDatabase, def: ModuleDef, target: &str) -> Option<S
127 let module = def.module(db)?; 127 let module = def.module(db)?;
128 let krate = module.krate(); 128 let krate = module.krate();
129 let canonical_path = def.canonical_path(db)?; 129 let canonical_path = def.canonical_path(db)?;
130 let base = format!("{}/{}", krate.display_name(db)?, canonical_path.replace("::", "/")); 130 let base = format!("{}/{}", krate.declaration_name(db)?, canonical_path.replace("::", "/"));
131 131
132 get_doc_url(db, &krate) 132 get_doc_url(db, &krate)
133 .and_then(|url| url.join(&base).ok()) 133 .and_then(|url| url.join(&base).ok())
@@ -248,7 +248,7 @@ fn get_doc_url(db: &RootDatabase, krate: &Crate) -> Option<Url> {
248 // 248 //
249 // FIXME: clicking on the link should just open the file in the editor, 249 // FIXME: clicking on the link should just open the file in the editor,
250 // instead of falling back to external urls. 250 // instead of falling back to external urls.
251 Some(format!("https://docs.rs/{}/*/", krate.display_name(db)?)) 251 Some(format!("https://docs.rs/{}/*/", krate.declaration_name(db)?))
252 }) 252 })
253 .and_then(|s| Url::parse(&s).ok()) 253 .and_then(|s| Url::parse(&s).ok())
254} 254}
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/status.rs b/crates/ide/src/status.rs
index 0af84daa0..f67f10491 100644
--- a/crates/ide/src/status.rs
+++ b/crates/ide/src/status.rs
@@ -45,7 +45,7 @@ pub(crate) fn status(db: &RootDatabase, file_id: Option<FileId>) -> String {
45 match krate { 45 match krate {
46 Some(krate) => { 46 Some(krate) => {
47 let crate_graph = db.crate_graph(); 47 let crate_graph = db.crate_graph();
48 let display_crate = |krate: CrateId| match &crate_graph[krate].display_name { 48 let display_crate = |krate: CrateId| match &crate_graph[krate].declaration_name {
49 Some(it) => format!("{}({:?})", it, krate), 49 Some(it) => format!("{}({:?})", it, krate),
50 None => format!("{:?}", krate), 50 None => format!("{:?}", krate),
51 }; 51 };
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/project_model/src/lib.rs b/crates/project_model/src/lib.rs
index 258f60e28..d1e7602fc 100644
--- a/crates/project_model/src/lib.rs
+++ b/crates/project_model/src/lib.rs
@@ -411,7 +411,7 @@ impl ProjectWorkspace {
411 let crate_id = crate_graph.add_crate_root( 411 let crate_id = crate_graph.add_crate_root(
412 file_id, 412 file_id,
413 edition, 413 edition,
414 Some(cargo[pkg].name.clone()), 414 Some(CrateName::normalize_dashes(&cargo[pkg].name)),
415 cfg_options, 415 cfg_options,
416 env, 416 env,
417 proc_macro.clone(), 417 proc_macro.clone(),
@@ -546,7 +546,8 @@ fn sysroot_to_crate_graph(
546 546
547 let env = Env::default(); 547 let env = Env::default();
548 let proc_macro = vec![]; 548 let proc_macro = vec![];
549 let name = sysroot[krate].name.clone(); 549 let name = CrateName::new(&sysroot[krate].name)
550 .expect("Sysroot crates' names do not contain dashes");
550 let crate_id = crate_graph.add_crate_root( 551 let crate_id = crate_graph.add_crate_root(
551 file_id, 552 file_id,
552 Edition::Edition2018, 553 Edition::Edition2018,
diff --git a/crates/rust-analyzer/src/cli/diagnostics.rs b/crates/rust-analyzer/src/cli/diagnostics.rs
index f3b6c900e..d1d3b12f8 100644
--- a/crates/rust-analyzer/src/cli/diagnostics.rs
+++ b/crates/rust-analyzer/src/cli/diagnostics.rs
@@ -36,11 +36,12 @@ pub fn diagnostics(path: &Path, load_output_dirs: bool, with_proc_macro: bool) -
36 for module in work { 36 for module in work {
37 let file_id = module.definition_source(db).file_id.original_file(db); 37 let file_id = module.definition_source(db).file_id.original_file(db);
38 if !visited_files.contains(&file_id) { 38 if !visited_files.contains(&file_id) {
39 let crate_name = if let Some(name) = module.krate().display_name(db) { 39 let crate_name = module
40 format!("{}", name) 40 .krate()
41 } else { 41 .declaration_name(db)
42 String::from("unknown") 42 .as_ref()
43 }; 43 .map(ToString::to_string)
44 .unwrap_or_else(|| "unknown".to_string());
44 println!("processing crate: {}, module: {}", crate_name, _vfs.file_path(file_id)); 45 println!("processing crate: {}, module: {}", crate_name, _vfs.file_path(file_id));
45 for diagnostic in analysis.diagnostics(&DiagnosticsConfig::default(), file_id).unwrap() 46 for diagnostic in analysis.diagnostics(&DiagnosticsConfig::default(), file_id).unwrap()
46 { 47 {
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index 0ab4c37bf..1b9b24698 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -10,11 +10,12 @@
10use std::{ffi::OsString, path::PathBuf}; 10use std::{ffi::OsString, path::PathBuf};
11 11
12use flycheck::FlycheckConfig; 12use flycheck::FlycheckConfig;
13use hir::PrefixKind;
13use ide::{ 14use ide::{
14 AssistConfig, CompletionConfig, DiagnosticsConfig, HoverConfig, InlayHintsConfig, 15 AssistConfig, CompletionConfig, DiagnosticsConfig, HoverConfig, InlayHintsConfig,
15 MergeBehaviour, 16 MergeBehaviour,
16}; 17};
17use lsp_types::ClientCapabilities; 18use lsp_types::{ClientCapabilities, MarkupKind};
18use project_model::{CargoConfig, ProjectJson, ProjectJsonData, ProjectManifest}; 19use project_model::{CargoConfig, ProjectJson, ProjectJsonData, ProjectManifest};
19use rustc_hash::FxHashSet; 20use rustc_hash::FxHashSet;
20use serde::Deserialize; 21use serde::Deserialize;
@@ -289,6 +290,11 @@ impl Config {
289 MergeBehaviourDef::Full => Some(MergeBehaviour::Full), 290 MergeBehaviourDef::Full => Some(MergeBehaviour::Full),
290 MergeBehaviourDef::Last => Some(MergeBehaviour::Last), 291 MergeBehaviourDef::Last => Some(MergeBehaviour::Last),
291 }; 292 };
293 self.assist.insert_use.prefix_kind = match data.assist_importPrefix {
294 ImportPrefixDef::Plain => PrefixKind::Plain,
295 ImportPrefixDef::ByCrate => PrefixKind::ByCrate,
296 ImportPrefixDef::BySelf => PrefixKind::BySelf,
297 };
292 298
293 self.call_info_full = data.callInfo_full; 299 self.call_info_full = data.callInfo_full;
294 300
@@ -327,6 +333,7 @@ impl Config {
327 debug: data.hoverActions_enable && data.hoverActions_debug, 333 debug: data.hoverActions_enable && data.hoverActions_debug,
328 goto_type_def: data.hoverActions_enable && data.hoverActions_gotoTypeDef, 334 goto_type_def: data.hoverActions_enable && data.hoverActions_gotoTypeDef,
329 links_in_hover: data.hoverActions_linksInHover, 335 links_in_hover: data.hoverActions_linksInHover,
336 markdown: true,
330 }; 337 };
331 338
332 log::info!("Config::update() = {:#?}", self); 339 log::info!("Config::update() = {:#?}", self);
@@ -334,6 +341,9 @@ impl Config {
334 341
335 pub fn update_caps(&mut self, caps: &ClientCapabilities) { 342 pub fn update_caps(&mut self, caps: &ClientCapabilities) {
336 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 }
337 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) {
338 self.client_caps.location_link = value; 348 self.client_caps.location_link = value;
339 } 349 }
@@ -403,13 +413,21 @@ enum ManifestOrProjectJson {
403} 413}
404 414
405#[derive(Deserialize)] 415#[derive(Deserialize)]
406#[serde(rename_all = "lowercase")] 416#[serde(rename_all = "snake_case")]
407enum MergeBehaviourDef { 417enum MergeBehaviourDef {
408 None, 418 None,
409 Full, 419 Full,
410 Last, 420 Last,
411} 421}
412 422
423#[derive(Deserialize)]
424#[serde(rename_all = "snake_case")]
425enum ImportPrefixDef {
426 Plain,
427 BySelf,
428 ByCrate,
429}
430
413macro_rules! config_data { 431macro_rules! config_data {
414 (struct $name:ident { $($field:ident: $ty:ty = $default:expr,)*}) => { 432 (struct $name:ident { $($field:ident: $ty:ty = $default:expr,)*}) => {
415 #[allow(non_snake_case)] 433 #[allow(non_snake_case)]
@@ -434,6 +452,7 @@ macro_rules! config_data {
434config_data! { 452config_data! {
435 struct ConfigData { 453 struct ConfigData {
436 assist_importMergeBehaviour: MergeBehaviourDef = MergeBehaviourDef::None, 454 assist_importMergeBehaviour: MergeBehaviourDef = MergeBehaviourDef::None,
455 assist_importPrefix: ImportPrefixDef = ImportPrefixDef::Plain,
437 456
438 callInfo_full: bool = true, 457 callInfo_full: bool = true,
439 458
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]
diff --git a/editors/code/package.json b/editors/code/package.json
index cc2ac3bd2..6a712a8a8 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -652,6 +652,21 @@
652 "default": "full", 652 "default": "full",
653 "description": "The strategy to use when inserting new imports or merging imports." 653 "description": "The strategy to use when inserting new imports or merging imports."
654 }, 654 },
655 "rust-analyzer.assist.importPrefix": {
656 "type": "string",
657 "enum": [
658 "plain",
659 "by_self",
660 "by_crate"
661 ],
662 "enumDescriptions": [
663 "Insert import paths relative to the current module, using up to one `super` prefix if the parent module contains the requested item.",
664 "Prefix all import paths with `self` if they don't begin with `self`, `super`, `crate` or a crate name",
665 "Force import paths to be absolute by always starting them with `crate` or the crate name they refer to."
666 ],
667 "default": "plain",
668 "description": "The path structure for newly inserted paths to use."
669 },
655 "rust-analyzer.runnables.overrideCargo": { 670 "rust-analyzer.runnables.overrideCargo": {
656 "type": [ 671 "type": [
657 "null", 672 "null",
diff --git a/xtask/src/codegen/gen_feature_docs.rs b/xtask/src/codegen/gen_feature_docs.rs
index 3f0013e82..341e67c73 100644
--- a/xtask/src/codegen/gen_feature_docs.rs
+++ b/xtask/src/codegen/gen_feature_docs.rs
@@ -38,7 +38,9 @@ impl Feature {
38 38
39 for block in comment_blocks { 39 for block in comment_blocks {
40 let id = block.id; 40 let id = block.id;
41 assert!(is_valid_feature_name(&id), "invalid feature name: {:?}", id); 41 if let Err(msg) = is_valid_feature_name(&id) {
42 panic!("invalid feature name: {:?}:\n {}", id, msg)
43 }
42 let doc = block.contents.join("\n"); 44 let doc = block.contents.join("\n");
43 let location = Location::new(path.clone(), block.line); 45 let location = Location::new(path.clone(), block.line);
44 acc.push(Feature { id, location, doc }) 46 acc.push(Feature { id, location, doc })
@@ -49,7 +51,7 @@ impl Feature {
49 } 51 }
50} 52}
51 53
52fn is_valid_feature_name(feature: &str) -> bool { 54fn is_valid_feature_name(feature: &str) -> Result<(), String> {
53 'word: for word in feature.split_whitespace() { 55 'word: for word in feature.split_whitespace() {
54 for &short in ["to", "and"].iter() { 56 for &short in ["to", "and"].iter() {
55 if word == short { 57 if word == short {
@@ -58,14 +60,14 @@ fn is_valid_feature_name(feature: &str) -> bool {
58 } 60 }
59 for &short in ["To", "And"].iter() { 61 for &short in ["To", "And"].iter() {
60 if word == short { 62 if word == short {
61 return false; 63 return Err(format!("Don't capitalize {:?}", word));
62 } 64 }
63 } 65 }
64 if !word.starts_with(char::is_uppercase) { 66 if !word.starts_with(char::is_uppercase) {
65 return false; 67 return Err(format!("Capitalize {:?}", word));
66 } 68 }
67 } 69 }
68 true 70 Ok(())
69} 71}
70 72
71impl fmt::Display for Feature { 73impl fmt::Display for Feature {
diff --git a/xtask/tests/tidy.rs b/xtask/tests/tidy.rs
index 02b3afc96..01f04a17c 100644
--- a/xtask/tests/tidy.rs
+++ b/xtask/tests/tidy.rs
@@ -38,6 +38,13 @@ fn check_code_formatting() {
38} 38}
39 39
40#[test] 40#[test]
41fn smoke_test_docs_generation() {
42 // We don't commit docs to the repo, so we can just overwrite in tests.
43 codegen::generate_assists_docs(Mode::Overwrite).unwrap();
44 codegen::generate_feature_docs(Mode::Overwrite).unwrap();
45}
46
47#[test]
41fn rust_files_are_tidy() { 48fn rust_files_are_tidy() {
42 let mut tidy_docs = TidyDocs::default(); 49 let mut tidy_docs = TidyDocs::default();
43 for path in rust_files(&project_root().join("crates")) { 50 for path in rust_files(&project_root().join("crates")) {