aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/hir_ty/src/infer.rs20
-rw-r--r--crates/hir_ty/src/infer/expr.rs6
-rw-r--r--crates/hir_ty/src/infer/path.rs2
-rw-r--r--crates/hir_ty/src/infer/unify.rs8
-rw-r--r--crates/rust-analyzer/src/diagnostics/test_data/macro_compiler_error.txt138
-rw-r--r--crates/rust-analyzer/src/diagnostics/to_proto.rs101
-rw-r--r--docs/dev/style.md45
7 files changed, 262 insertions, 58 deletions
diff --git a/crates/hir_ty/src/infer.rs b/crates/hir_ty/src/infer.rs
index e4407ff50..674e9e6f9 100644
--- a/crates/hir_ty/src/infer.rs
+++ b/crates/hir_ty/src/infer.rs
@@ -210,6 +210,7 @@ struct InferenceContext<'a> {
210 table: unify::InferenceTable, 210 table: unify::InferenceTable,
211 trait_env: Arc<TraitEnvironment>, 211 trait_env: Arc<TraitEnvironment>,
212 obligations: Vec<DomainGoal>, 212 obligations: Vec<DomainGoal>,
213 last_obligations_check: Option<u32>,
213 result: InferenceResult, 214 result: InferenceResult,
214 /// The return type of the function being inferred, or the closure if we're 215 /// The return type of the function being inferred, or the closure if we're
215 /// currently within one. 216 /// currently within one.
@@ -245,6 +246,7 @@ impl<'a> InferenceContext<'a> {
245 result: InferenceResult::default(), 246 result: InferenceResult::default(),
246 table: unify::InferenceTable::new(), 247 table: unify::InferenceTable::new(),
247 obligations: Vec::default(), 248 obligations: Vec::default(),
249 last_obligations_check: None,
248 return_ty: TyKind::Unknown.intern(&Interner), // set in collect_fn_signature 250 return_ty: TyKind::Unknown.intern(&Interner), // set in collect_fn_signature
249 trait_env: owner 251 trait_env: owner
250 .as_generic_def_id() 252 .as_generic_def_id()
@@ -334,6 +336,13 @@ impl<'a> InferenceContext<'a> {
334 } 336 }
335 337
336 fn resolve_obligations_as_possible(&mut self) { 338 fn resolve_obligations_as_possible(&mut self) {
339 if self.last_obligations_check == Some(self.table.revision) {
340 // no change
341 return;
342 }
343 let _span = profile::span("resolve_obligations_as_possible");
344
345 self.last_obligations_check = Some(self.table.revision);
337 let obligations = mem::replace(&mut self.obligations, Vec::new()); 346 let obligations = mem::replace(&mut self.obligations, Vec::new());
338 for obligation in obligations { 347 for obligation in obligations {
339 let in_env = InEnvironment::new(self.trait_env.env.clone(), obligation.clone()); 348 let in_env = InEnvironment::new(self.trait_env.env.clone(), obligation.clone());
@@ -360,6 +369,11 @@ impl<'a> InferenceContext<'a> {
360 } 369 }
361 } 370 }
362 371
372 fn push_obligation(&mut self, o: DomainGoal) {
373 self.obligations.push(o);
374 self.last_obligations_check = None;
375 }
376
363 fn unify(&mut self, ty1: &Ty, ty2: &Ty) -> bool { 377 fn unify(&mut self, ty1: &Ty, ty2: &Ty) -> bool {
364 self.table.unify(ty1, ty2) 378 self.table.unify(ty1, ty2)
365 } 379 }
@@ -408,8 +422,8 @@ impl<'a> InferenceContext<'a> {
408 }), 422 }),
409 ty: ty.clone(), 423 ty: ty.clone(),
410 }; 424 };
411 self.obligations.push(trait_ref.cast(&Interner)); 425 self.push_obligation(trait_ref.cast(&Interner));
412 self.obligations.push(alias_eq.cast(&Interner)); 426 self.push_obligation(alias_eq.cast(&Interner));
413 self.resolve_ty_as_possible(ty) 427 self.resolve_ty_as_possible(ty)
414 } 428 }
415 None => self.err_ty(), 429 None => self.err_ty(),
@@ -436,7 +450,7 @@ impl<'a> InferenceContext<'a> {
436 let var = self.table.new_type_var(); 450 let var = self.table.new_type_var();
437 let alias_eq = AliasEq { alias: AliasTy::Projection(proj_ty), ty: var.clone() }; 451 let alias_eq = AliasEq { alias: AliasTy::Projection(proj_ty), ty: var.clone() };
438 let obligation = alias_eq.cast(&Interner); 452 let obligation = alias_eq.cast(&Interner);
439 self.obligations.push(obligation); 453 self.push_obligation(obligation);
440 var 454 var
441 } 455 }
442 456
diff --git a/crates/hir_ty/src/infer/expr.rs b/crates/hir_ty/src/infer/expr.rs
index 6279aa572..25ab3ea4c 100644
--- a/crates/hir_ty/src/infer/expr.rs
+++ b/crates/hir_ty/src/infer/expr.rs
@@ -99,7 +99,7 @@ impl<'a> InferenceContext<'a> {
99 environment: trait_env, 99 environment: trait_env,
100 }); 100 });
101 if self.db.trait_solve(krate, goal.value).is_some() { 101 if self.db.trait_solve(krate, goal.value).is_some() {
102 self.obligations.push(implements_fn_trait); 102 self.push_obligation(implements_fn_trait);
103 let output_proj_ty = crate::ProjectionTy { 103 let output_proj_ty = crate::ProjectionTy {
104 associated_ty_id: to_assoc_type_id(output_assoc_type), 104 associated_ty_id: to_assoc_type_id(output_assoc_type),
105 substitution: substs, 105 substitution: substs,
@@ -964,7 +964,7 @@ impl<'a> InferenceContext<'a> {
964 let (predicate, binders) = 964 let (predicate, binders) =
965 predicate.clone().subst(parameters).into_value_and_skipped_binders(); 965 predicate.clone().subst(parameters).into_value_and_skipped_binders();
966 always!(binders == 0); // quantified where clauses not yet handled 966 always!(binders == 0); // quantified where clauses not yet handled
967 self.obligations.push(predicate.cast(&Interner)); 967 self.push_obligation(predicate.cast(&Interner));
968 } 968 }
969 // add obligation for trait implementation, if this is a trait method 969 // add obligation for trait implementation, if this is a trait method
970 match def { 970 match def {
@@ -974,7 +974,7 @@ impl<'a> InferenceContext<'a> {
974 // construct a TraitRef 974 // construct a TraitRef
975 let substs = 975 let substs =
976 parameters.prefix(generics(self.db.upcast(), trait_.into()).len()); 976 parameters.prefix(generics(self.db.upcast(), trait_.into()).len());
977 self.obligations.push( 977 self.push_obligation(
978 TraitRef { trait_id: to_chalk_trait_id(trait_), substitution: substs } 978 TraitRef { trait_id: to_chalk_trait_id(trait_), substitution: substs }
979 .cast(&Interner), 979 .cast(&Interner),
980 ); 980 );
diff --git a/crates/hir_ty/src/infer/path.rs b/crates/hir_ty/src/infer/path.rs
index cefa38509..717738789 100644
--- a/crates/hir_ty/src/infer/path.rs
+++ b/crates/hir_ty/src/infer/path.rs
@@ -258,7 +258,7 @@ impl<'a> InferenceContext<'a> {
258 .push(ty.clone()) 258 .push(ty.clone())
259 .fill(std::iter::repeat_with(|| self.table.new_type_var())) 259 .fill(std::iter::repeat_with(|| self.table.new_type_var()))
260 .build(); 260 .build();
261 self.obligations.push( 261 self.push_obligation(
262 TraitRef { 262 TraitRef {
263 trait_id: to_chalk_trait_id(trait_), 263 trait_id: to_chalk_trait_id(trait_),
264 substitution: trait_substs.clone(), 264 substitution: trait_substs.clone(),
diff --git a/crates/hir_ty/src/infer/unify.rs b/crates/hir_ty/src/infer/unify.rs
index 6e7b0f5a6..5ea4b7481 100644
--- a/crates/hir_ty/src/infer/unify.rs
+++ b/crates/hir_ty/src/infer/unify.rs
@@ -231,6 +231,7 @@ pub(crate) struct TypeVariableData {
231pub(crate) struct InferenceTable { 231pub(crate) struct InferenceTable {
232 pub(super) var_unification_table: InPlaceUnificationTable<TypeVarId>, 232 pub(super) var_unification_table: InPlaceUnificationTable<TypeVarId>,
233 pub(super) type_variable_table: TypeVariableTable, 233 pub(super) type_variable_table: TypeVariableTable,
234 pub(super) revision: u32,
234} 235}
235 236
236impl InferenceTable { 237impl InferenceTable {
@@ -238,6 +239,7 @@ impl InferenceTable {
238 InferenceTable { 239 InferenceTable {
239 var_unification_table: InPlaceUnificationTable::new(), 240 var_unification_table: InPlaceUnificationTable::new(),
240 type_variable_table: TypeVariableTable { inner: Vec::new() }, 241 type_variable_table: TypeVariableTable { inner: Vec::new() },
242 revision: 0,
241 } 243 }
242 } 244 }
243 245
@@ -360,7 +362,10 @@ impl InferenceTable {
360 == self.type_variable_table.is_diverging(*tv2) => 362 == self.type_variable_table.is_diverging(*tv2) =>
361 { 363 {
362 // both type vars are unknown since we tried to resolve them 364 // both type vars are unknown since we tried to resolve them
363 self.var_unification_table.union(tv1.to_inner(), tv2.to_inner()); 365 if !self.var_unification_table.unioned(tv1.to_inner(), tv2.to_inner()) {
366 self.var_unification_table.union(tv1.to_inner(), tv2.to_inner());
367 self.revision += 1;
368 }
364 true 369 true
365 } 370 }
366 371
@@ -398,6 +403,7 @@ impl InferenceTable {
398 tv.to_inner(), 403 tv.to_inner(),
399 TypeVarValue::Known(other.clone().intern(&Interner)), 404 TypeVarValue::Known(other.clone().intern(&Interner)),
400 ); 405 );
406 self.revision += 1;
401 true 407 true
402 } 408 }
403 409
diff --git a/crates/rust-analyzer/src/diagnostics/test_data/macro_compiler_error.txt b/crates/rust-analyzer/src/diagnostics/test_data/macro_compiler_error.txt
index f999848a7..c847bbb35 100644
--- a/crates/rust-analyzer/src/diagnostics/test_data/macro_compiler_error.txt
+++ b/crates/rust-analyzer/src/diagnostics/test_data/macro_compiler_error.txt
@@ -13,16 +13,16 @@
13 diagnostic: Diagnostic { 13 diagnostic: Diagnostic {
14 range: Range { 14 range: Range {
15 start: Position { 15 start: Position {
16 line: 264, 16 line: 271,
17 character: 8, 17 character: 8,
18 }, 18 },
19 end: Position { 19 end: Position {
20 line: 264, 20 line: 271,
21 character: 76, 21 character: 50,
22 }, 22 },
23 }, 23 },
24 severity: Some( 24 severity: Some(
25 Error, 25 Hint,
26 ), 26 ),
27 code: None, 27 code: None,
28 code_description: None, 28 code_description: None,
@@ -40,18 +40,18 @@
40 password: None, 40 password: None,
41 host: None, 41 host: None,
42 port: None, 42 port: None,
43 path: "/test/crates/hir_def/src/data.rs", 43 path: "/test/crates/hir_def/src/path.rs",
44 query: None, 44 query: None,
45 fragment: None, 45 fragment: None,
46 }, 46 },
47 range: Range { 47 range: Range {
48 start: Position { 48 start: Position {
49 line: 79, 49 line: 264,
50 character: 15, 50 character: 8,
51 }, 51 },
52 end: Position { 52 end: Position {
53 line: 79, 53 line: 264,
54 character: 41, 54 character: 76,
55 }, 55 },
56 }, 56 },
57 }, 57 },
@@ -87,6 +87,71 @@
87 }, 87 },
88 }, 88 },
89 severity: Some( 89 severity: Some(
90 Hint,
91 ),
92 code: None,
93 code_description: None,
94 source: Some(
95 "rustc",
96 ),
97 message: "Please register your known path in the path module",
98 related_information: Some(
99 [
100 DiagnosticRelatedInformation {
101 location: Location {
102 uri: Url {
103 scheme: "file",
104 username: "",
105 password: None,
106 host: None,
107 port: None,
108 path: "/test/crates/hir_def/src/path.rs",
109 query: None,
110 fragment: None,
111 },
112 range: Range {
113 start: Position {
114 line: 264,
115 character: 8,
116 },
117 end: Position {
118 line: 264,
119 character: 76,
120 },
121 },
122 },
123 message: "Exact error occurred here",
124 },
125 ],
126 ),
127 tags: None,
128 data: None,
129 },
130 fixes: [],
131 },
132 MappedRustDiagnostic {
133 url: Url {
134 scheme: "file",
135 username: "",
136 password: None,
137 host: None,
138 port: None,
139 path: "/test/crates/hir_def/src/path.rs",
140 query: None,
141 fragment: None,
142 },
143 diagnostic: Diagnostic {
144 range: Range {
145 start: Position {
146 line: 264,
147 character: 8,
148 },
149 end: Position {
150 line: 264,
151 character: 76,
152 },
153 },
154 severity: Some(
90 Error, 155 Error,
91 ), 156 ),
92 code: None, 157 code: None,
@@ -95,7 +160,60 @@
95 "rustc", 160 "rustc",
96 ), 161 ),
97 message: "Please register your known path in the path module", 162 message: "Please register your known path in the path module",
98 related_information: None, 163 related_information: Some(
164 [
165 DiagnosticRelatedInformation {
166 location: Location {
167 uri: Url {
168 scheme: "file",
169 username: "",
170 password: None,
171 host: None,
172 port: None,
173 path: "/test/crates/hir_def/src/path.rs",
174 query: None,
175 fragment: None,
176 },
177 range: Range {
178 start: Position {
179 line: 271,
180 character: 8,
181 },
182 end: Position {
183 line: 271,
184 character: 50,
185 },
186 },
187 },
188 message: "Error originated from macro call here",
189 },
190 DiagnosticRelatedInformation {
191 location: Location {
192 uri: Url {
193 scheme: "file",
194 username: "",
195 password: None,
196 host: None,
197 port: None,
198 path: "/test/crates/hir_def/src/data.rs",
199 query: None,
200 fragment: None,
201 },
202 range: Range {
203 start: Position {
204 line: 79,
205 character: 15,
206 },
207 end: Position {
208 line: 79,
209 character: 41,
210 },
211 },
212 },
213 message: "Error originated from macro call here",
214 },
215 ],
216 ),
99 tags: None, 217 tags: None,
100 data: None, 218 data: None,
101 }, 219 },
diff --git a/crates/rust-analyzer/src/diagnostics/to_proto.rs b/crates/rust-analyzer/src/diagnostics/to_proto.rs
index 76994de71..e2f319f6b 100644
--- a/crates/rust-analyzer/src/diagnostics/to_proto.rs
+++ b/crates/rust-analyzer/src/diagnostics/to_proto.rs
@@ -34,22 +34,14 @@ fn diagnostic_severity(
34 Some(res) 34 Some(res)
35} 35}
36 36
37/// Check whether a file name is from macro invocation 37/// Checks whether a file name is from macro invocation and does not refer to an actual file.
38fn is_from_macro(file_name: &str) -> bool { 38fn is_dummy_macro_file(file_name: &str) -> bool {
39 // FIXME: current rustc does not seem to emit `<macro file>` files anymore?
39 file_name.starts_with('<') && file_name.ends_with('>') 40 file_name.starts_with('<') && file_name.ends_with('>')
40} 41}
41 42
42/// Converts a Rust span to a LSP location, resolving macro expansion site if neccesary
43fn location(workspace_root: &Path, span: &DiagnosticSpan) -> lsp_types::Location {
44 let mut span = span.clone();
45 while let Some(expansion) = span.expansion {
46 span = expansion.span;
47 }
48 return location_naive(workspace_root, &span);
49}
50
51/// Converts a Rust span to a LSP location 43/// Converts a Rust span to a LSP location
52fn location_naive(workspace_root: &Path, span: &DiagnosticSpan) -> lsp_types::Location { 44fn location(workspace_root: &Path, span: &DiagnosticSpan) -> lsp_types::Location {
53 let file_name = workspace_root.join(&span.file_name); 45 let file_name = workspace_root.join(&span.file_name);
54 let uri = url_from_abs_path(&file_name); 46 let uri = url_from_abs_path(&file_name);
55 47
@@ -62,7 +54,25 @@ fn location_naive(workspace_root: &Path, span: &DiagnosticSpan) -> lsp_types::Lo
62 lsp_types::Location { uri, range } 54 lsp_types::Location { uri, range }
63} 55}
64 56
65/// Converts a secondary Rust span to a LSP related inflocation(ormation 57/// Extracts a suitable "primary" location from a rustc diagnostic.
58///
59/// This takes locations pointing into the standard library, or generally outside the current
60/// workspace into account and tries to avoid those, in case macros are involved.
61fn primary_location(workspace_root: &Path, span: &DiagnosticSpan) -> lsp_types::Location {
62 let span_stack = std::iter::successors(Some(span), |span| Some(&span.expansion.as_ref()?.span));
63 for span in span_stack.clone() {
64 let abs_path = workspace_root.join(&span.file_name);
65 if !is_dummy_macro_file(&span.file_name) && abs_path.starts_with(workspace_root) {
66 return location(workspace_root, span);
67 }
68 }
69
70 // Fall back to the outermost macro invocation if no suitable span comes up.
71 let last_span = span_stack.last().unwrap();
72 location(workspace_root, last_span)
73}
74
75/// Converts a secondary Rust span to a LSP related information
66/// 76///
67/// If the span is unlabelled this will return `None`. 77/// If the span is unlabelled this will return `None`.
68fn diagnostic_related_information( 78fn diagnostic_related_information(
@@ -231,7 +241,7 @@ pub(crate) fn map_rust_diagnostic_to_lsp(
231 primary_spans 241 primary_spans
232 .iter() 242 .iter()
233 .flat_map(|primary_span| { 243 .flat_map(|primary_span| {
234 let location = location(workspace_root, &primary_span); 244 let primary_location = primary_location(workspace_root, &primary_span);
235 245
236 let mut message = message.clone(); 246 let mut message = message.clone();
237 if needs_primary_span_label { 247 if needs_primary_span_label {
@@ -243,31 +253,47 @@ pub(crate) fn map_rust_diagnostic_to_lsp(
243 // Each primary diagnostic span may result in multiple LSP diagnostics. 253 // Each primary diagnostic span may result in multiple LSP diagnostics.
244 let mut diagnostics = Vec::new(); 254 let mut diagnostics = Vec::new();
245 255
246 let mut related_macro_info = None; 256 let mut related_info_macro_calls = vec![];
247 257
248 // If error occurs from macro expansion, add related info pointing to 258 // If error occurs from macro expansion, add related info pointing to
249 // where the error originated 259 // where the error originated
250 // Also, we would generate an additional diagnostic, so that exact place of macro 260 // Also, we would generate an additional diagnostic, so that exact place of macro
251 // will be highlighted in the error origin place. 261 // will be highlighted in the error origin place.
252 if !is_from_macro(&primary_span.file_name) && primary_span.expansion.is_some() { 262 let span_stack = std::iter::successors(Some(*primary_span), |span| {
253 let in_macro_location = location_naive(workspace_root, &primary_span); 263 Some(&span.expansion.as_ref()?.span)
264 });
265 for (i, span) in span_stack.enumerate() {
266 if is_dummy_macro_file(&span.file_name) {
267 continue;
268 }
254 269
255 // Add related information for the main disagnostic. 270 // First span is the original diagnostic, others are macro call locations that
256 related_macro_info = Some(lsp_types::DiagnosticRelatedInformation { 271 // generated that code.
257 location: in_macro_location.clone(), 272 let is_in_macro_call = i != 0;
258 message: "Error originated from macro here".to_string(),
259 });
260 273
274 let secondary_location = location(workspace_root, &span);
275 if secondary_location == primary_location {
276 continue;
277 }
278 related_info_macro_calls.push(lsp_types::DiagnosticRelatedInformation {
279 location: secondary_location.clone(),
280 message: if is_in_macro_call {
281 "Error originated from macro call here".to_string()
282 } else {
283 "Actual error occurred here".to_string()
284 },
285 });
261 // For the additional in-macro diagnostic we add the inverse message pointing to the error location in code. 286 // For the additional in-macro diagnostic we add the inverse message pointing to the error location in code.
262 let information_for_additional_diagnostic = 287 let information_for_additional_diagnostic =
263 vec![lsp_types::DiagnosticRelatedInformation { 288 vec![lsp_types::DiagnosticRelatedInformation {
264 location: location.clone(), 289 location: primary_location.clone(),
265 message: "Exact error occurred here".to_string(), 290 message: "Exact error occurred here".to_string(),
266 }]; 291 }];
267 292
268 let diagnostic = lsp_types::Diagnostic { 293 let diagnostic = lsp_types::Diagnostic {
269 range: in_macro_location.range, 294 range: secondary_location.range,
270 severity, 295 // downgrade to hint if we're pointing at the macro
296 severity: Some(lsp_types::DiagnosticSeverity::Hint),
271 code: code.clone().map(lsp_types::NumberOrString::String), 297 code: code.clone().map(lsp_types::NumberOrString::String),
272 code_description: code_description.clone(), 298 code_description: code_description.clone(),
273 source: Some(source.clone()), 299 source: Some(source.clone()),
@@ -276,9 +302,8 @@ pub(crate) fn map_rust_diagnostic_to_lsp(
276 tags: if tags.is_empty() { None } else { Some(tags.clone()) }, 302 tags: if tags.is_empty() { None } else { Some(tags.clone()) },
277 data: None, 303 data: None,
278 }; 304 };
279
280 diagnostics.push(MappedRustDiagnostic { 305 diagnostics.push(MappedRustDiagnostic {
281 url: in_macro_location.uri, 306 url: secondary_location.uri,
282 diagnostic, 307 diagnostic,
283 fixes: Vec::new(), 308 fixes: Vec::new(),
284 }); 309 });
@@ -286,23 +311,25 @@ pub(crate) fn map_rust_diagnostic_to_lsp(
286 311
287 // Emit the primary diagnostic. 312 // Emit the primary diagnostic.
288 diagnostics.push(MappedRustDiagnostic { 313 diagnostics.push(MappedRustDiagnostic {
289 url: location.uri.clone(), 314 url: primary_location.uri.clone(),
290 diagnostic: lsp_types::Diagnostic { 315 diagnostic: lsp_types::Diagnostic {
291 range: location.range, 316 range: primary_location.range,
292 severity, 317 severity,
293 code: code.clone().map(lsp_types::NumberOrString::String), 318 code: code.clone().map(lsp_types::NumberOrString::String),
294 code_description: code_description.clone(), 319 code_description: code_description.clone(),
295 source: Some(source.clone()), 320 source: Some(source.clone()),
296 message, 321 message,
297 related_information: if subdiagnostics.is_empty() { 322 related_information: {
298 None 323 let info = related_info_macro_calls
299 } else {
300 let mut related = subdiagnostics
301 .iter() 324 .iter()
302 .map(|sub| sub.related.clone()) 325 .cloned()
326 .chain(subdiagnostics.iter().map(|sub| sub.related.clone()))
303 .collect::<Vec<_>>(); 327 .collect::<Vec<_>>();
304 related.extend(related_macro_info); 328 if info.is_empty() {
305 Some(related) 329 None
330 } else {
331 Some(info)
332 }
306 }, 333 },
307 tags: if tags.is_empty() { None } else { Some(tags.clone()) }, 334 tags: if tags.is_empty() { None } else { Some(tags.clone()) },
308 data: None, 335 data: None,
@@ -314,7 +341,7 @@ pub(crate) fn map_rust_diagnostic_to_lsp(
314 // This is useful because they will show up in the user's editor, unlike 341 // This is useful because they will show up in the user's editor, unlike
315 // `related_information`, which just produces hard-to-read links, at least in VS Code. 342 // `related_information`, which just produces hard-to-read links, at least in VS Code.
316 let back_ref = lsp_types::DiagnosticRelatedInformation { 343 let back_ref = lsp_types::DiagnosticRelatedInformation {
317 location, 344 location: primary_location,
318 message: "original diagnostic".to_string(), 345 message: "original diagnostic".to_string(),
319 }; 346 };
320 for sub in &subdiagnostics { 347 for sub in &subdiagnostics {
diff --git a/docs/dev/style.md b/docs/dev/style.md
index e4a1672ca..c594946be 100644
--- a/docs/dev/style.md
+++ b/docs/dev/style.md
@@ -55,9 +55,9 @@ https://www.tedinski.com/2018/02/06/system-boundaries.html
55We try to be very conservative with usage of crates.io dependencies. 55We try to be very conservative with usage of crates.io dependencies.
56Don't use small "helper" crates (exception: `itertools` is allowed). 56Don't use small "helper" crates (exception: `itertools` is allowed).
57If there's some general reusable bit of code you need, consider adding it to the `stdx` crate. 57If there's some general reusable bit of code you need, consider adding it to the `stdx` crate.
58A useful exercise is to read Cargo.lock and see if some of the *transitive* dependencies do not make sense for rust-analyzer.
58 59
59**Rationale:** keep compile times low, create ecosystem pressure for faster 60**Rationale:** keep compile times low, create ecosystem pressure for faster compiles, reduce the number of things which might break.
60compiles, reduce the number of things which might break.
61 61
62## Commit Style 62## Commit Style
63 63
@@ -806,9 +806,48 @@ if let Some(expected_type) = ctx.expected_type.as_ref() {
806} 806}
807``` 807```
808 808
809**Rational:** `match` is almost always more compact. 809**Rationale:** `match` is almost always more compact.
810The `else` branch can get a more precise pattern: `None` or `Err(_)` instead of `_`. 810The `else` branch can get a more precise pattern: `None` or `Err(_)` instead of `_`.
811 811
812## Helper Functions
813
814Avoid creating singe-use helper functions:
815
816```rust
817// GOOD
818let buf = {
819 let mut buf = get_empty_buf(&mut arena);
820 buf.add_item(item);
821 buf
822};
823
824// BAD
825
826let buf = prepare_buf(&mut arena, item);
827
828...
829
830fn prepare_buf(arena: &mut Arena, item: Item) -> ItemBuf {
831 let mut res = get_empty_buf(&mut arena);
832 res.add_item(item);
833 res
834}
835```
836
837Exception: if you want to make use of `return` or `?`.
838
839**Rationale:** single-use functions change frequently, adding or removing parameters adds churn.
840A block serves just as well to delineate a bit of logic, but has access to all the context.
841Re-using originally single-purpose function often leads to bad coupling.
842
843## Helper Variables
844
845Introduce helper variables freely, especially for multiline conditions.
846
847**Rationale:** like blocks, single-use variables are a cognitively cheap abstraction, as they have access to all the context.
848Extra variables help during debugging, they make it easy to print/view important intermediate results.
849Giving a name to a condition in `if` expression often improves clarity and leads to a nicer formatted code.
850
812## Token names 851## Token names
813 852
814Use `T![foo]` instead of `SyntaxKind::FOO_KW`. 853Use `T![foo]` instead of `SyntaxKind::FOO_KW`.