diff options
-rw-r--r-- | Cargo.lock | 8 | ||||
-rw-r--r-- | crates/hir_def/src/find_path.rs | 24 | ||||
-rw-r--r-- | crates/ide/src/references/rename.rs | 378 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/apply_demorgan.rs | 45 | ||||
-rw-r--r-- | crates/ide_completion/src/completions/attribute.rs | 3 | ||||
-rw-r--r-- | crates/ide_completion/src/completions/fn_param.rs | 27 | ||||
-rw-r--r-- | crates/ide_completion/src/completions/keyword.rs | 35 | ||||
-rw-r--r-- | crates/ide_completion/src/completions/qualified_path.rs | 12 | ||||
-rw-r--r-- | crates/syntax/Cargo.toml | 2 | ||||
-rw-r--r-- | docs/dev/style.md | 8 |
10 files changed, 289 insertions, 253 deletions
diff --git a/Cargo.lock b/Cargo.lock index ec5aecfa0..799127891 100644 --- a/Cargo.lock +++ b/Cargo.lock | |||
@@ -1016,9 +1016,9 @@ checksum = "a9a7ab5d64814df0fe4a4b5ead45ed6c5f181ee3ff04ba344313a6c80446c5d4" | |||
1016 | 1016 | ||
1017 | [[package]] | 1017 | [[package]] |
1018 | name = "once_cell" | 1018 | name = "once_cell" |
1019 | version = "1.7.1" | 1019 | version = "1.7.2" |
1020 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1020 | source = "registry+https://github.com/rust-lang/crates.io-index" |
1021 | checksum = "ea78b9742c52ac729753c1590e9adc5248ea9bdaf974597efd46c74cfaa5fb54" | 1021 | checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3" |
1022 | 1022 | ||
1023 | [[package]] | 1023 | [[package]] |
1024 | name = "oorandom" | 1024 | name = "oorandom" |
@@ -1370,9 +1370,9 @@ dependencies = [ | |||
1370 | 1370 | ||
1371 | [[package]] | 1371 | [[package]] |
1372 | name = "rustc-ap-rustc_lexer" | 1372 | name = "rustc-ap-rustc_lexer" |
1373 | version = "708.0.0" | 1373 | version = "709.0.0" |
1374 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1374 | source = "registry+https://github.com/rust-lang/crates.io-index" |
1375 | checksum = "2706fc7106c75eaea49efe9f35f719a6fdfdb95212122ec2b543659406bae7ea" | 1375 | checksum = "f69f83314702aaccf29c7401cc63bb0d9fa7869a185a23b8379f08c91514b3f3" |
1376 | dependencies = [ | 1376 | dependencies = [ |
1377 | "unicode-xid", | 1377 | "unicode-xid", |
1378 | ] | 1378 | ] |
diff --git a/crates/hir_def/src/find_path.rs b/crates/hir_def/src/find_path.rs index 3e19a7702..3a98ffbaa 100644 --- a/crates/hir_def/src/find_path.rs +++ b/crates/hir_def/src/find_path.rs | |||
@@ -862,30 +862,6 @@ mod tests { | |||
862 | } | 862 | } |
863 | 863 | ||
864 | #[test] | 864 | #[test] |
865 | #[ignore] | ||
866 | fn inner_items_from_parent_module() { | ||
867 | // FIXME: ItemTree currently associates all inner items with `main`. Luckily, this sort of | ||
868 | // code is very rare, so this isn't terrible. | ||
869 | // To fix it, we should probably build dedicated `ItemTree`s for inner items, and not store | ||
870 | // them in the file's main ItemTree. This would also allow us to stop parsing function | ||
871 | // bodies when we only want to compute the crate's main DefMap. | ||
872 | check_found_path( | ||
873 | r#" | ||
874 | fn main() { | ||
875 | struct Struct {} | ||
876 | mod module { | ||
877 | $0 | ||
878 | } | ||
879 | } | ||
880 | "#, | ||
881 | "super::Struct", | ||
882 | "super::Struct", | ||
883 | "super::Struct", | ||
884 | "super::Struct", | ||
885 | ); | ||
886 | } | ||
887 | |||
888 | #[test] | ||
889 | fn outer_items_with_inner_items_present() { | 865 | fn outer_items_with_inner_items_present() { |
890 | check_found_path( | 866 | check_found_path( |
891 | r#" | 867 | r#" |
diff --git a/crates/ide/src/references/rename.rs b/crates/ide/src/references/rename.rs index 22ddeeae3..1919639a3 100644 --- a/crates/ide/src/references/rename.rs +++ b/crates/ide/src/references/rename.rs | |||
@@ -88,6 +88,8 @@ pub(crate) fn rename_with_semantics( | |||
88 | let def = find_definition(sema, syntax, position)?; | 88 | let def = find_definition(sema, syntax, position)?; |
89 | match def { | 89 | match def { |
90 | Definition::ModuleDef(ModuleDef::Module(module)) => rename_mod(&sema, module, new_name), | 90 | Definition::ModuleDef(ModuleDef::Module(module)) => rename_mod(&sema, module, new_name), |
91 | Definition::SelfType(_) => bail!("Cannot rename `Self`"), | ||
92 | Definition::ModuleDef(ModuleDef::BuiltinType(_)) => bail!("Cannot rename builtin type"), | ||
91 | def => rename_reference(sema, def, new_name), | 93 | def => rename_reference(sema, def, new_name), |
92 | } | 94 | } |
93 | } | 95 | } |
@@ -122,7 +124,7 @@ fn check_identifier(new_name: &str) -> RenameResult<IdentifierKind> { | |||
122 | Ok(IdentifierKind::Lifetime) | 124 | Ok(IdentifierKind::Lifetime) |
123 | } | 125 | } |
124 | (SyntaxKind::LIFETIME_IDENT, _) => { | 126 | (SyntaxKind::LIFETIME_IDENT, _) => { |
125 | bail!("Invalid name `{0}`: Cannot rename lifetime to {0}", new_name) | 127 | bail!("Invalid name `{}`: not a lifetime identifier", new_name) |
126 | } | 128 | } |
127 | (_, Some(syntax_error)) => bail!("Invalid name `{}`: {}", new_name, syntax_error), | 129 | (_, Some(syntax_error)) => bail!("Invalid name `{}`: {}", new_name, syntax_error), |
128 | (_, None) => bail!("Invalid name `{}`: not an identifier", new_name), | 130 | (_, None) => bail!("Invalid name `{}`: not an identifier", new_name), |
@@ -162,119 +164,6 @@ fn find_definition( | |||
162 | .ok_or_else(|| format_err!("No references found at position")) | 164 | .ok_or_else(|| format_err!("No references found at position")) |
163 | } | 165 | } |
164 | 166 | ||
165 | fn source_edit_from_references( | ||
166 | _sema: &Semantics<RootDatabase>, | ||
167 | file_id: FileId, | ||
168 | references: &[FileReference], | ||
169 | def: Definition, | ||
170 | new_name: &str, | ||
171 | ) -> (FileId, TextEdit) { | ||
172 | let mut edit = TextEdit::builder(); | ||
173 | for reference in references { | ||
174 | let (range, replacement) = match &reference.name { | ||
175 | // if the ranges differ then the node is inside a macro call, we can't really attempt | ||
176 | // to make special rewrites like shorthand syntax and such, so just rename the node in | ||
177 | // the macro input | ||
178 | ast::NameLike::NameRef(name_ref) | ||
179 | if name_ref.syntax().text_range() == reference.range => | ||
180 | { | ||
181 | source_edit_from_name_ref(name_ref, new_name, def) | ||
182 | } | ||
183 | ast::NameLike::Name(name) if name.syntax().text_range() == reference.range => { | ||
184 | source_edit_from_name(name, new_name) | ||
185 | } | ||
186 | _ => None, | ||
187 | } | ||
188 | .unwrap_or_else(|| (reference.range, new_name.to_string())); | ||
189 | edit.replace(range, replacement); | ||
190 | } | ||
191 | (file_id, edit.finish()) | ||
192 | } | ||
193 | |||
194 | fn source_edit_from_name(name: &ast::Name, new_name: &str) -> Option<(TextRange, String)> { | ||
195 | if let Some(_) = ast::RecordPatField::for_field_name(name) { | ||
196 | if let Some(ident_pat) = name.syntax().parent().and_then(ast::IdentPat::cast) { | ||
197 | return Some(( | ||
198 | TextRange::empty(ident_pat.syntax().text_range().start()), | ||
199 | format!("{}: ", new_name), | ||
200 | )); | ||
201 | } | ||
202 | } | ||
203 | None | ||
204 | } | ||
205 | |||
206 | fn source_edit_from_name_ref( | ||
207 | name_ref: &ast::NameRef, | ||
208 | new_name: &str, | ||
209 | def: Definition, | ||
210 | ) -> Option<(TextRange, String)> { | ||
211 | if let Some(record_field) = ast::RecordExprField::for_name_ref(name_ref) { | ||
212 | let rcf_name_ref = record_field.name_ref(); | ||
213 | let rcf_expr = record_field.expr(); | ||
214 | match (rcf_name_ref, rcf_expr.and_then(|it| it.name_ref())) { | ||
215 | // field: init-expr, check if we can use a field init shorthand | ||
216 | (Some(field_name), Some(init)) => { | ||
217 | if field_name == *name_ref { | ||
218 | if init.text() == new_name { | ||
219 | mark::hit!(test_rename_field_put_init_shorthand); | ||
220 | // same names, we can use a shorthand here instead. | ||
221 | // we do not want to erase attributes hence this range start | ||
222 | let s = field_name.syntax().text_range().start(); | ||
223 | let e = record_field.syntax().text_range().end(); | ||
224 | return Some((TextRange::new(s, e), new_name.to_owned())); | ||
225 | } | ||
226 | } else if init == *name_ref { | ||
227 | if field_name.text() == new_name { | ||
228 | mark::hit!(test_rename_local_put_init_shorthand); | ||
229 | // same names, we can use a shorthand here instead. | ||
230 | // we do not want to erase attributes hence this range start | ||
231 | let s = field_name.syntax().text_range().start(); | ||
232 | let e = record_field.syntax().text_range().end(); | ||
233 | return Some((TextRange::new(s, e), new_name.to_owned())); | ||
234 | } | ||
235 | } | ||
236 | None | ||
237 | } | ||
238 | // init shorthand | ||
239 | // FIXME: instead of splitting the shorthand, recursively trigger a rename of the | ||
240 | // other name https://github.com/rust-analyzer/rust-analyzer/issues/6547 | ||
241 | (None, Some(_)) if matches!(def, Definition::Field(_)) => { | ||
242 | mark::hit!(test_rename_field_in_field_shorthand); | ||
243 | let s = name_ref.syntax().text_range().start(); | ||
244 | Some((TextRange::empty(s), format!("{}: ", new_name))) | ||
245 | } | ||
246 | (None, Some(_)) if matches!(def, Definition::Local(_)) => { | ||
247 | mark::hit!(test_rename_local_in_field_shorthand); | ||
248 | let s = name_ref.syntax().text_range().end(); | ||
249 | Some((TextRange::empty(s), format!(": {}", new_name))) | ||
250 | } | ||
251 | _ => None, | ||
252 | } | ||
253 | } else if let Some(record_field) = ast::RecordPatField::for_field_name_ref(name_ref) { | ||
254 | let rcf_name_ref = record_field.name_ref(); | ||
255 | let rcf_pat = record_field.pat(); | ||
256 | match (rcf_name_ref, rcf_pat) { | ||
257 | // field: rename | ||
258 | (Some(field_name), Some(ast::Pat::IdentPat(pat))) if field_name == *name_ref => { | ||
259 | // field name is being renamed | ||
260 | if pat.name().map_or(false, |it| it.text() == new_name) { | ||
261 | mark::hit!(test_rename_field_put_init_shorthand_pat); | ||
262 | // same names, we can use a shorthand here instead/ | ||
263 | // we do not want to erase attributes hence this range start | ||
264 | let s = field_name.syntax().text_range().start(); | ||
265 | let e = record_field.syntax().text_range().end(); | ||
266 | Some((TextRange::new(s, e), pat.to_string())) | ||
267 | } else { | ||
268 | None | ||
269 | } | ||
270 | } | ||
271 | _ => None, | ||
272 | } | ||
273 | } else { | ||
274 | None | ||
275 | } | ||
276 | } | ||
277 | |||
278 | fn rename_mod( | 167 | fn rename_mod( |
279 | sema: &Semantics<RootDatabase>, | 168 | sema: &Semantics<RootDatabase>, |
280 | module: Module, | 169 | module: Module, |
@@ -308,18 +197,75 @@ fn rename_mod( | |||
308 | TextEdit::replace(name.syntax().text_range(), new_name.to_string()), | 197 | TextEdit::replace(name.syntax().text_range(), new_name.to_string()), |
309 | ), | 198 | ), |
310 | _ => unreachable!(), | 199 | _ => unreachable!(), |
311 | }; | 200 | } |
312 | } | 201 | } |
313 | let def = Definition::ModuleDef(ModuleDef::Module(module)); | 202 | let def = Definition::ModuleDef(ModuleDef::Module(module)); |
314 | let usages = def.usages(sema).all(); | 203 | let usages = def.usages(sema).all(); |
315 | let ref_edits = usages.iter().map(|(&file_id, references)| { | 204 | let ref_edits = usages.iter().map(|(&file_id, references)| { |
316 | source_edit_from_references(sema, file_id, references, def, new_name) | 205 | (file_id, source_edit_from_references(references, def, new_name)) |
317 | }); | 206 | }); |
318 | source_change.extend(ref_edits); | 207 | source_change.extend(ref_edits); |
319 | 208 | ||
320 | Ok(source_change) | 209 | Ok(source_change) |
321 | } | 210 | } |
322 | 211 | ||
212 | fn rename_reference( | ||
213 | sema: &Semantics<RootDatabase>, | ||
214 | def: Definition, | ||
215 | new_name: &str, | ||
216 | ) -> RenameResult<SourceChange> { | ||
217 | let ident_kind = check_identifier(new_name)?; | ||
218 | |||
219 | let def_is_lbl_or_lt = matches!( | ||
220 | def, | ||
221 | Definition::GenericParam(hir::GenericParam::LifetimeParam(_)) | Definition::Label(_) | ||
222 | ); | ||
223 | match (ident_kind, def) { | ||
224 | (IdentifierKind::ToSelf, _) | ||
225 | | (IdentifierKind::Underscore, _) | ||
226 | | (IdentifierKind::Ident, _) | ||
227 | if def_is_lbl_or_lt => | ||
228 | { | ||
229 | mark::hit!(rename_not_a_lifetime_ident_ref); | ||
230 | bail!("Invalid name `{}`: not a lifetime identifier", new_name) | ||
231 | } | ||
232 | (IdentifierKind::Lifetime, _) if def_is_lbl_or_lt => mark::hit!(rename_lifetime), | ||
233 | (IdentifierKind::Lifetime, _) => { | ||
234 | mark::hit!(rename_not_an_ident_ref); | ||
235 | bail!("Invalid name `{}`: not an identifier", new_name) | ||
236 | } | ||
237 | (IdentifierKind::ToSelf, Definition::Local(local)) if local.is_self(sema.db) => { | ||
238 | // no-op | ||
239 | mark::hit!(rename_self_to_self); | ||
240 | return Ok(SourceChange::default()); | ||
241 | } | ||
242 | (ident_kind, Definition::Local(local)) if local.is_self(sema.db) => { | ||
243 | mark::hit!(rename_self_to_param); | ||
244 | return rename_self_to_param(sema, local, new_name, ident_kind); | ||
245 | } | ||
246 | (IdentifierKind::ToSelf, Definition::Local(local)) => { | ||
247 | mark::hit!(rename_to_self); | ||
248 | return rename_to_self(sema, local); | ||
249 | } | ||
250 | (IdentifierKind::ToSelf, _) => bail!("Invalid name `{}`: not an identifier", new_name), | ||
251 | (IdentifierKind::Ident, _) | (IdentifierKind::Underscore, _) => mark::hit!(rename_ident), | ||
252 | } | ||
253 | |||
254 | let usages = def.usages(sema).all(); | ||
255 | if !usages.is_empty() && ident_kind == IdentifierKind::Underscore { | ||
256 | mark::hit!(rename_underscore_multiple); | ||
257 | bail!("Cannot rename reference to `_` as it is being referenced multiple times"); | ||
258 | } | ||
259 | let mut source_change = SourceChange::default(); | ||
260 | source_change.extend(usages.iter().map(|(&file_id, references)| { | ||
261 | (file_id, source_edit_from_references(&references, def, new_name)) | ||
262 | })); | ||
263 | |||
264 | let (file_id, edit) = source_edit_from_def(sema, def, new_name)?; | ||
265 | source_change.insert_source_edit(file_id, edit); | ||
266 | Ok(source_change) | ||
267 | } | ||
268 | |||
323 | fn rename_to_self(sema: &Semantics<RootDatabase>, local: hir::Local) -> RenameResult<SourceChange> { | 269 | fn rename_to_self(sema: &Semantics<RootDatabase>, local: hir::Local) -> RenameResult<SourceChange> { |
324 | if never!(local.is_self(sema.db)) { | 270 | if never!(local.is_self(sema.db)) { |
325 | bail!("rename_to_self invoked on self"); | 271 | bail!("rename_to_self invoked on self"); |
@@ -384,7 +330,7 @@ fn rename_to_self(sema: &Semantics<RootDatabase>, local: hir::Local) -> RenameRe | |||
384 | let usages = def.usages(sema).all(); | 330 | let usages = def.usages(sema).all(); |
385 | let mut source_change = SourceChange::default(); | 331 | let mut source_change = SourceChange::default(); |
386 | source_change.extend(usages.iter().map(|(&file_id, references)| { | 332 | source_change.extend(usages.iter().map(|(&file_id, references)| { |
387 | source_edit_from_references(sema, file_id, references, def, "self") | 333 | (file_id, source_edit_from_references(references, def, "self")) |
388 | })); | 334 | })); |
389 | source_change.insert_source_edit( | 335 | source_change.insert_source_edit( |
390 | file_id.original_file(sema.db), | 336 | file_id.original_file(sema.db), |
@@ -394,29 +340,6 @@ fn rename_to_self(sema: &Semantics<RootDatabase>, local: hir::Local) -> RenameRe | |||
394 | Ok(source_change) | 340 | Ok(source_change) |
395 | } | 341 | } |
396 | 342 | ||
397 | fn text_edit_from_self_param(self_param: &ast::SelfParam, new_name: &str) -> Option<TextEdit> { | ||
398 | fn target_type_name(impl_def: &ast::Impl) -> Option<String> { | ||
399 | if let Some(ast::Type::PathType(p)) = impl_def.self_ty() { | ||
400 | return Some(p.path()?.segment()?.name_ref()?.text().to_string()); | ||
401 | } | ||
402 | None | ||
403 | } | ||
404 | |||
405 | let impl_def = self_param.syntax().ancestors().find_map(|it| ast::Impl::cast(it))?; | ||
406 | let type_name = target_type_name(&impl_def)?; | ||
407 | |||
408 | let mut replacement_text = String::from(new_name); | ||
409 | replacement_text.push_str(": "); | ||
410 | match (self_param.amp_token(), self_param.mut_token()) { | ||
411 | (None, None) => (), | ||
412 | (Some(_), None) => replacement_text.push('&'), | ||
413 | (_, Some(_)) => replacement_text.push_str("&mut "), | ||
414 | }; | ||
415 | replacement_text.push_str(type_name.as_str()); | ||
416 | |||
417 | Some(TextEdit::replace(self_param.syntax().text_range(), replacement_text)) | ||
418 | } | ||
419 | |||
420 | fn rename_self_to_param( | 343 | fn rename_self_to_param( |
421 | sema: &Semantics<RootDatabase>, | 344 | sema: &Semantics<RootDatabase>, |
422 | local: hir::Local, | 345 | local: hir::Local, |
@@ -441,66 +364,143 @@ fn rename_self_to_param( | |||
441 | let mut source_change = SourceChange::default(); | 364 | let mut source_change = SourceChange::default(); |
442 | source_change.insert_source_edit(file_id.original_file(sema.db), edit); | 365 | source_change.insert_source_edit(file_id.original_file(sema.db), edit); |
443 | source_change.extend(usages.iter().map(|(&file_id, references)| { | 366 | source_change.extend(usages.iter().map(|(&file_id, references)| { |
444 | source_edit_from_references(sema, file_id, &references, def, new_name) | 367 | (file_id, source_edit_from_references(&references, def, new_name)) |
445 | })); | 368 | })); |
446 | Ok(source_change) | 369 | Ok(source_change) |
447 | } | 370 | } |
448 | 371 | ||
449 | fn rename_reference( | 372 | fn text_edit_from_self_param(self_param: &ast::SelfParam, new_name: &str) -> Option<TextEdit> { |
450 | sema: &Semantics<RootDatabase>, | 373 | fn target_type_name(impl_def: &ast::Impl) -> Option<String> { |
374 | if let Some(ast::Type::PathType(p)) = impl_def.self_ty() { | ||
375 | return Some(p.path()?.segment()?.name_ref()?.text().to_string()); | ||
376 | } | ||
377 | None | ||
378 | } | ||
379 | |||
380 | let impl_def = self_param.syntax().ancestors().find_map(|it| ast::Impl::cast(it))?; | ||
381 | let type_name = target_type_name(&impl_def)?; | ||
382 | |||
383 | let mut replacement_text = String::from(new_name); | ||
384 | replacement_text.push_str(": "); | ||
385 | match (self_param.amp_token(), self_param.mut_token()) { | ||
386 | (Some(_), None) => replacement_text.push('&'), | ||
387 | (Some(_), Some(_)) => replacement_text.push_str("&mut "), | ||
388 | (_, _) => (), | ||
389 | }; | ||
390 | replacement_text.push_str(type_name.as_str()); | ||
391 | |||
392 | Some(TextEdit::replace(self_param.syntax().text_range(), replacement_text)) | ||
393 | } | ||
394 | |||
395 | fn source_edit_from_references( | ||
396 | references: &[FileReference], | ||
451 | def: Definition, | 397 | def: Definition, |
452 | new_name: &str, | 398 | new_name: &str, |
453 | ) -> RenameResult<SourceChange> { | 399 | ) -> TextEdit { |
454 | let ident_kind = check_identifier(new_name)?; | 400 | let mut edit = TextEdit::builder(); |
455 | 401 | for reference in references { | |
456 | let def_is_lbl_or_lt = matches!( | 402 | let (range, replacement) = match &reference.name { |
457 | def, | 403 | // if the ranges differ then the node is inside a macro call, we can't really attempt |
458 | Definition::GenericParam(hir::GenericParam::LifetimeParam(_)) | Definition::Label(_) | 404 | // to make special rewrites like shorthand syntax and such, so just rename the node in |
459 | ); | 405 | // the macro input |
460 | match (ident_kind, def) { | 406 | ast::NameLike::NameRef(name_ref) |
461 | (IdentifierKind::ToSelf, _) | 407 | if name_ref.syntax().text_range() == reference.range => |
462 | | (IdentifierKind::Underscore, _) | 408 | { |
463 | | (IdentifierKind::Ident, _) | 409 | source_edit_from_name_ref(name_ref, new_name, def) |
464 | if def_is_lbl_or_lt => | 410 | } |
465 | { | 411 | ast::NameLike::Name(name) if name.syntax().text_range() == reference.range => { |
466 | mark::hit!(rename_not_a_lifetime_ident_ref); | 412 | source_edit_from_name(name, new_name) |
467 | bail!("Invalid name `{}`: not a lifetime identifier", new_name) | 413 | } |
468 | } | 414 | _ => None, |
469 | (IdentifierKind::Lifetime, _) if def_is_lbl_or_lt => mark::hit!(rename_lifetime), | ||
470 | (IdentifierKind::Lifetime, _) => { | ||
471 | mark::hit!(rename_not_an_ident_ref); | ||
472 | bail!("Invalid name `{}`: not an identifier", new_name) | ||
473 | } | ||
474 | (IdentifierKind::ToSelf, Definition::Local(local)) if local.is_self(sema.db) => { | ||
475 | // no-op | ||
476 | mark::hit!(rename_self_to_self); | ||
477 | return Ok(SourceChange::default()); | ||
478 | } | ||
479 | (ident_kind, Definition::Local(local)) if local.is_self(sema.db) => { | ||
480 | mark::hit!(rename_self_to_param); | ||
481 | return rename_self_to_param(sema, local, new_name, ident_kind); | ||
482 | } | ||
483 | (IdentifierKind::ToSelf, Definition::Local(local)) => { | ||
484 | mark::hit!(rename_to_self); | ||
485 | return rename_to_self(sema, local); | ||
486 | } | 415 | } |
487 | (IdentifierKind::ToSelf, _) => bail!("Invalid name `{}`: not an identifier", new_name), | 416 | .unwrap_or_else(|| (reference.range, new_name.to_string())); |
488 | (IdentifierKind::Ident, _) | (IdentifierKind::Underscore, _) => mark::hit!(rename_ident), | 417 | edit.replace(range, replacement); |
489 | } | 418 | } |
419 | edit.finish() | ||
420 | } | ||
490 | 421 | ||
491 | let usages = def.usages(sema).all(); | 422 | fn source_edit_from_name(name: &ast::Name, new_name: &str) -> Option<(TextRange, String)> { |
492 | if !usages.is_empty() && ident_kind == IdentifierKind::Underscore { | 423 | if let Some(_) = ast::RecordPatField::for_field_name(name) { |
493 | mark::hit!(rename_underscore_multiple); | 424 | if let Some(ident_pat) = name.syntax().parent().and_then(ast::IdentPat::cast) { |
494 | bail!("Cannot rename reference to `_` as it is being referenced multiple times"); | 425 | return Some(( |
426 | TextRange::empty(ident_pat.syntax().text_range().start()), | ||
427 | [new_name, ": "].concat(), | ||
428 | )); | ||
429 | } | ||
495 | } | 430 | } |
496 | let mut source_change = SourceChange::default(); | 431 | None |
497 | source_change.extend(usages.iter().map(|(&file_id, references)| { | 432 | } |
498 | source_edit_from_references(sema, file_id, &references, def, new_name) | ||
499 | })); | ||
500 | 433 | ||
501 | let (file_id, edit) = source_edit_from_def(sema, def, new_name)?; | 434 | fn source_edit_from_name_ref( |
502 | source_change.insert_source_edit(file_id, edit); | 435 | name_ref: &ast::NameRef, |
503 | Ok(source_change) | 436 | new_name: &str, |
437 | def: Definition, | ||
438 | ) -> Option<(TextRange, String)> { | ||
439 | if let Some(record_field) = ast::RecordExprField::for_name_ref(name_ref) { | ||
440 | let rcf_name_ref = record_field.name_ref(); | ||
441 | let rcf_expr = record_field.expr(); | ||
442 | match (rcf_name_ref, rcf_expr.and_then(|it| it.name_ref())) { | ||
443 | // field: init-expr, check if we can use a field init shorthand | ||
444 | (Some(field_name), Some(init)) => { | ||
445 | if field_name == *name_ref { | ||
446 | if init.text() == new_name { | ||
447 | mark::hit!(test_rename_field_put_init_shorthand); | ||
448 | // same names, we can use a shorthand here instead. | ||
449 | // we do not want to erase attributes hence this range start | ||
450 | let s = field_name.syntax().text_range().start(); | ||
451 | let e = record_field.syntax().text_range().end(); | ||
452 | return Some((TextRange::new(s, e), new_name.to_owned())); | ||
453 | } | ||
454 | } else if init == *name_ref { | ||
455 | if field_name.text() == new_name { | ||
456 | mark::hit!(test_rename_local_put_init_shorthand); | ||
457 | // same names, we can use a shorthand here instead. | ||
458 | // we do not want to erase attributes hence this range start | ||
459 | let s = field_name.syntax().text_range().start(); | ||
460 | let e = record_field.syntax().text_range().end(); | ||
461 | return Some((TextRange::new(s, e), new_name.to_owned())); | ||
462 | } | ||
463 | } | ||
464 | None | ||
465 | } | ||
466 | // init shorthand | ||
467 | // FIXME: instead of splitting the shorthand, recursively trigger a rename of the | ||
468 | // other name https://github.com/rust-analyzer/rust-analyzer/issues/6547 | ||
469 | (None, Some(_)) if matches!(def, Definition::Field(_)) => { | ||
470 | mark::hit!(test_rename_field_in_field_shorthand); | ||
471 | let s = name_ref.syntax().text_range().start(); | ||
472 | Some((TextRange::empty(s), format!("{}: ", new_name))) | ||
473 | } | ||
474 | (None, Some(_)) if matches!(def, Definition::Local(_)) => { | ||
475 | mark::hit!(test_rename_local_in_field_shorthand); | ||
476 | let s = name_ref.syntax().text_range().end(); | ||
477 | Some((TextRange::empty(s), format!(": {}", new_name))) | ||
478 | } | ||
479 | _ => None, | ||
480 | } | ||
481 | } else if let Some(record_field) = ast::RecordPatField::for_field_name_ref(name_ref) { | ||
482 | let rcf_name_ref = record_field.name_ref(); | ||
483 | let rcf_pat = record_field.pat(); | ||
484 | match (rcf_name_ref, rcf_pat) { | ||
485 | // field: rename | ||
486 | (Some(field_name), Some(ast::Pat::IdentPat(pat))) if field_name == *name_ref => { | ||
487 | // field name is being renamed | ||
488 | if pat.name().map_or(false, |it| it.text() == new_name) { | ||
489 | mark::hit!(test_rename_field_put_init_shorthand_pat); | ||
490 | // same names, we can use a shorthand here instead/ | ||
491 | // we do not want to erase attributes hence this range start | ||
492 | let s = field_name.syntax().text_range().start(); | ||
493 | let e = record_field.syntax().text_range().end(); | ||
494 | Some((TextRange::new(s, e), pat.to_string())) | ||
495 | } else { | ||
496 | None | ||
497 | } | ||
498 | } | ||
499 | _ => None, | ||
500 | } | ||
501 | } else { | ||
502 | None | ||
503 | } | ||
504 | } | 504 | } |
505 | 505 | ||
506 | fn source_edit_from_def( | 506 | fn source_edit_from_def( |
diff --git a/crates/ide_assists/src/handlers/apply_demorgan.rs b/crates/ide_assists/src/handlers/apply_demorgan.rs index 6997ea048..128b1eb56 100644 --- a/crates/ide_assists/src/handlers/apply_demorgan.rs +++ b/crates/ide_assists/src/handlers/apply_demorgan.rs | |||
@@ -1,4 +1,5 @@ | |||
1 | use syntax::ast::{self, AstNode}; | 1 | use syntax::ast::{self, AstNode}; |
2 | use test_utils::mark; | ||
2 | 3 | ||
3 | use crate::{utils::invert_boolean_expression, AssistContext, AssistId, AssistKind, Assists}; | 4 | use crate::{utils::invert_boolean_expression, AssistContext, AssistId, AssistKind, Assists}; |
4 | 5 | ||
@@ -43,9 +44,36 @@ pub(crate) fn apply_demorgan(acc: &mut Assists, ctx: &AssistContext) -> Option<( | |||
43 | "Apply De Morgan's law", | 44 | "Apply De Morgan's law", |
44 | op_range, | 45 | op_range, |
45 | |edit| { | 46 | |edit| { |
47 | let paren_expr = expr.syntax().parent().and_then(|parent| ast::ParenExpr::cast(parent)); | ||
48 | |||
49 | let neg_expr = paren_expr | ||
50 | .clone() | ||
51 | .and_then(|paren_expr| paren_expr.syntax().parent()) | ||
52 | .and_then(|parent| ast::PrefixExpr::cast(parent)) | ||
53 | .and_then(|prefix_expr| { | ||
54 | if prefix_expr.op_kind().unwrap() == ast::PrefixOp::Not { | ||
55 | Some(prefix_expr) | ||
56 | } else { | ||
57 | None | ||
58 | } | ||
59 | }); | ||
60 | |||
46 | edit.replace(op_range, opposite_op); | 61 | edit.replace(op_range, opposite_op); |
47 | edit.replace(lhs_range, format!("!({}", not_lhs.syntax().text())); | 62 | |
48 | edit.replace(rhs_range, format!("{})", not_rhs.syntax().text())); | 63 | if let Some(paren_expr) = paren_expr { |
64 | edit.replace(lhs_range, not_lhs.syntax().text()); | ||
65 | edit.replace(rhs_range, not_rhs.syntax().text()); | ||
66 | if let Some(neg_expr) = neg_expr { | ||
67 | mark::hit!(demorgan_double_negation); | ||
68 | edit.replace(neg_expr.op_token().unwrap().text_range(), ""); | ||
69 | } else { | ||
70 | mark::hit!(demorgan_double_parens); | ||
71 | edit.replace(paren_expr.l_paren_token().unwrap().text_range(), "!("); | ||
72 | } | ||
73 | } else { | ||
74 | edit.replace(lhs_range, format!("!({}", not_lhs.syntax().text())); | ||
75 | edit.replace(rhs_range, format!("{})", not_rhs.syntax().text())); | ||
76 | } | ||
49 | }, | 77 | }, |
50 | ) | 78 | ) |
51 | } | 79 | } |
@@ -62,6 +90,7 @@ fn opposite_logic_op(kind: ast::BinOp) -> Option<&'static str> { | |||
62 | #[cfg(test)] | 90 | #[cfg(test)] |
63 | mod tests { | 91 | mod tests { |
64 | use ide_db::helpers::FamousDefs; | 92 | use ide_db::helpers::FamousDefs; |
93 | use test_utils::mark; | ||
65 | 94 | ||
66 | use super::*; | 95 | use super::*; |
67 | 96 | ||
@@ -156,4 +185,16 @@ fn f() { | |||
156 | fn demorgan_doesnt_apply_with_cursor_not_on_op() { | 185 | fn demorgan_doesnt_apply_with_cursor_not_on_op() { |
157 | check_assist_not_applicable(apply_demorgan, "fn f() { $0 !x || !x }") | 186 | check_assist_not_applicable(apply_demorgan, "fn f() { $0 !x || !x }") |
158 | } | 187 | } |
188 | |||
189 | #[test] | ||
190 | fn demorgan_doesnt_double_negation() { | ||
191 | mark::check!(demorgan_double_negation); | ||
192 | check_assist(apply_demorgan, "fn f() { !(x ||$0 x) }", "fn f() { (!x && !x) }") | ||
193 | } | ||
194 | |||
195 | #[test] | ||
196 | fn demorgan_doesnt_double_parens() { | ||
197 | mark::check!(demorgan_double_parens); | ||
198 | check_assist(apply_demorgan, "fn f() { (x ||$0 x) }", "fn f() { !(!x && !x) }") | ||
199 | } | ||
159 | } | 200 | } |
diff --git a/crates/ide_completion/src/completions/attribute.rs b/crates/ide_completion/src/completions/attribute.rs index 3a5bc4381..cb05e85fc 100644 --- a/crates/ide_completion/src/completions/attribute.rs +++ b/crates/ide_completion/src/completions/attribute.rs | |||
@@ -39,7 +39,8 @@ pub(crate) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext) | |||
39 | } | 39 | } |
40 | 40 | ||
41 | fn complete_attribute_start(acc: &mut Completions, ctx: &CompletionContext, attribute: &ast::Attr) { | 41 | fn complete_attribute_start(acc: &mut Completions, ctx: &CompletionContext, attribute: &ast::Attr) { |
42 | for attr_completion in ATTRIBUTES { | 42 | let is_inner = attribute.kind() == ast::AttrKind::Inner; |
43 | for attr_completion in ATTRIBUTES.iter().filter(|compl| is_inner || !compl.prefer_inner) { | ||
43 | let mut item = CompletionItem::new( | 44 | let mut item = CompletionItem::new( |
44 | CompletionKind::Attribute, | 45 | CompletionKind::Attribute, |
45 | ctx.source_range(), | 46 | ctx.source_range(), |
diff --git a/crates/ide_completion/src/completions/fn_param.rs b/crates/ide_completion/src/completions/fn_param.rs index 38e33a93e..1bcc8727f 100644 --- a/crates/ide_completion/src/completions/fn_param.rs +++ b/crates/ide_completion/src/completions/fn_param.rs | |||
@@ -25,9 +25,12 @@ pub(crate) fn complete_fn_param(acc: &mut Completions, ctx: &CompletionContext) | |||
25 | return; | 25 | return; |
26 | } | 26 | } |
27 | func.param_list().into_iter().flat_map(|it| it.params()).for_each(|param| { | 27 | func.param_list().into_iter().flat_map(|it| it.params()).for_each(|param| { |
28 | let text = param.syntax().text().to_string(); | 28 | if let Some(pat) = param.pat() { |
29 | params.entry(text).or_insert(param); | 29 | let text = param.syntax().text().to_string(); |
30 | }) | 30 | let lookup = pat.syntax().text().to_string(); |
31 | params.entry(text).or_insert(lookup); | ||
32 | } | ||
33 | }); | ||
31 | }; | 34 | }; |
32 | 35 | ||
33 | for node in ctx.token.parent().ancestors() { | 36 | for node in ctx.token.parent().ancestors() { |
@@ -50,18 +53,12 @@ pub(crate) fn complete_fn_param(acc: &mut Completions, ctx: &CompletionContext) | |||
50 | }; | 53 | }; |
51 | } | 54 | } |
52 | 55 | ||
53 | params | 56 | params.into_iter().for_each(|(label, lookup)| { |
54 | .into_iter() | 57 | CompletionItem::new(CompletionKind::Magic, ctx.source_range(), label) |
55 | .filter_map(|(label, param)| { | 58 | .kind(CompletionItemKind::Binding) |
56 | let lookup = param.pat()?.syntax().text().to_string(); | 59 | .lookup_by(lookup) |
57 | Some((label, lookup)) | 60 | .add_to(acc) |
58 | }) | 61 | }); |
59 | .for_each(|(label, lookup)| { | ||
60 | CompletionItem::new(CompletionKind::Magic, ctx.source_range(), label) | ||
61 | .kind(CompletionItemKind::Binding) | ||
62 | .lookup_by(lookup) | ||
63 | .add_to(acc) | ||
64 | }); | ||
65 | } | 62 | } |
66 | 63 | ||
67 | #[cfg(test)] | 64 | #[cfg(test)] |
diff --git a/crates/ide_completion/src/completions/keyword.rs b/crates/ide_completion/src/completions/keyword.rs index eb81f9765..03c6dd454 100644 --- a/crates/ide_completion/src/completions/keyword.rs +++ b/crates/ide_completion/src/completions/keyword.rs | |||
@@ -1,5 +1,7 @@ | |||
1 | //! Completes keywords. | 1 | //! Completes keywords. |
2 | 2 | ||
3 | use std::iter; | ||
4 | |||
3 | use syntax::SyntaxKind; | 5 | use syntax::SyntaxKind; |
4 | use test_utils::mark; | 6 | use test_utils::mark; |
5 | 7 | ||
@@ -19,10 +21,14 @@ pub(crate) fn complete_use_tree_keyword(acc: &mut Completions, ctx: &CompletionC | |||
19 | CompletionItem::new(CompletionKind::Keyword, source_range, "self") | 21 | CompletionItem::new(CompletionKind::Keyword, source_range, "self") |
20 | .kind(CompletionItemKind::Keyword) | 22 | .kind(CompletionItemKind::Keyword) |
21 | .add_to(acc); | 23 | .add_to(acc); |
22 | CompletionItem::new(CompletionKind::Keyword, source_range, "super::") | 24 | if iter::successors(ctx.path_qual.clone(), |p| p.qualifier()) |
23 | .kind(CompletionItemKind::Keyword) | 25 | .all(|p| p.segment().and_then(|s| s.super_token()).is_some()) |
24 | .insert_text("super::") | 26 | { |
25 | .add_to(acc); | 27 | CompletionItem::new(CompletionKind::Keyword, source_range, "super::") |
28 | .kind(CompletionItemKind::Keyword) | ||
29 | .insert_text("super::") | ||
30 | .add_to(acc); | ||
31 | } | ||
26 | } | 32 | } |
27 | 33 | ||
28 | // Suggest .await syntax for types that implement Future trait | 34 | // Suggest .await syntax for types that implement Future trait |
@@ -85,6 +91,7 @@ pub(crate) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionConte | |||
85 | if ctx.is_expr { | 91 | if ctx.is_expr { |
86 | add_keyword(ctx, acc, "match", "match $0 {}"); | 92 | add_keyword(ctx, acc, "match", "match $0 {}"); |
87 | add_keyword(ctx, acc, "while", "while $0 {}"); | 93 | add_keyword(ctx, acc, "while", "while $0 {}"); |
94 | add_keyword(ctx, acc, "while let", "while let $1 = $0 {}"); | ||
88 | add_keyword(ctx, acc, "loop", "loop {$0}"); | 95 | add_keyword(ctx, acc, "loop", "loop {$0}"); |
89 | add_keyword(ctx, acc, "if", "if $0 {}"); | 96 | add_keyword(ctx, acc, "if", "if $0 {}"); |
90 | add_keyword(ctx, acc, "if let", "if let $1 = $0 {}"); | 97 | add_keyword(ctx, acc, "if let", "if let $1 = $0 {}"); |
@@ -204,9 +211,17 @@ mod tests { | |||
204 | "#]], | 211 | "#]], |
205 | ); | 212 | ); |
206 | 213 | ||
214 | // FIXME: `self` shouldn't be shown here and the check below | ||
207 | check( | 215 | check( |
208 | r"use a::$0", | 216 | r"use a::$0", |
209 | expect![[r#" | 217 | expect![[r#" |
218 | kw self | ||
219 | "#]], | ||
220 | ); | ||
221 | |||
222 | check( | ||
223 | r"use super::$0", | ||
224 | expect![[r#" | ||
210 | kw self | 225 | kw self |
211 | kw super:: | 226 | kw super:: |
212 | "#]], | 227 | "#]], |
@@ -215,9 +230,8 @@ mod tests { | |||
215 | check( | 230 | check( |
216 | r"use a::{b, $0}", | 231 | r"use a::{b, $0}", |
217 | expect![[r#" | 232 | expect![[r#" |
218 | kw self | 233 | kw self |
219 | kw super:: | 234 | "#]], |
220 | "#]], | ||
221 | ); | 235 | ); |
222 | } | 236 | } |
223 | 237 | ||
@@ -256,6 +270,7 @@ mod tests { | |||
256 | kw trait | 270 | kw trait |
257 | kw match | 271 | kw match |
258 | kw while | 272 | kw while |
273 | kw while let | ||
259 | kw loop | 274 | kw loop |
260 | kw if | 275 | kw if |
261 | kw if let | 276 | kw if let |
@@ -283,6 +298,7 @@ mod tests { | |||
283 | kw trait | 298 | kw trait |
284 | kw match | 299 | kw match |
285 | kw while | 300 | kw while |
301 | kw while let | ||
286 | kw loop | 302 | kw loop |
287 | kw if | 303 | kw if |
288 | kw if let | 304 | kw if let |
@@ -310,6 +326,7 @@ mod tests { | |||
310 | kw trait | 326 | kw trait |
311 | kw match | 327 | kw match |
312 | kw while | 328 | kw while |
329 | kw while let | ||
313 | kw loop | 330 | kw loop |
314 | kw if | 331 | kw if |
315 | kw if let | 332 | kw if let |
@@ -344,6 +361,7 @@ fn quux() -> i32 { | |||
344 | expect![[r#" | 361 | expect![[r#" |
345 | kw match | 362 | kw match |
346 | kw while | 363 | kw while |
364 | kw while let | ||
347 | kw loop | 365 | kw loop |
348 | kw if | 366 | kw if |
349 | kw if let | 367 | kw if let |
@@ -393,6 +411,7 @@ fn quux() -> i32 { | |||
393 | kw trait | 411 | kw trait |
394 | kw match | 412 | kw match |
395 | kw while | 413 | kw while |
414 | kw while let | ||
396 | kw loop | 415 | kw loop |
397 | kw if | 416 | kw if |
398 | kw if let | 417 | kw if let |
@@ -552,6 +571,7 @@ pub mod future { | |||
552 | expect![[r#" | 571 | expect![[r#" |
553 | kw match | 572 | kw match |
554 | kw while | 573 | kw while |
574 | kw while let | ||
555 | kw loop | 575 | kw loop |
556 | kw if | 576 | kw if |
557 | kw if let | 577 | kw if let |
@@ -611,6 +631,7 @@ fn foo() { | |||
611 | expect![[r#" | 631 | expect![[r#" |
612 | kw match | 632 | kw match |
613 | kw while | 633 | kw while |
634 | kw while let | ||
614 | kw loop | 635 | kw loop |
615 | kw if | 636 | kw if |
616 | kw if let | 637 | kw if let |
diff --git a/crates/ide_completion/src/completions/qualified_path.rs b/crates/ide_completion/src/completions/qualified_path.rs index 2afa6979e..72fb757b1 100644 --- a/crates/ide_completion/src/completions/qualified_path.rs +++ b/crates/ide_completion/src/completions/qualified_path.rs | |||
@@ -81,9 +81,7 @@ pub(crate) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon | |||
81 | return None; | 81 | return None; |
82 | } | 82 | } |
83 | match item { | 83 | match item { |
84 | hir::AssocItem::Function(func) => { | 84 | hir::AssocItem::Function(func) => acc.add_function(ctx, func, None), |
85 | acc.add_function(ctx, func, None); | ||
86 | } | ||
87 | hir::AssocItem::Const(ct) => acc.add_const(ctx, ct), | 85 | hir::AssocItem::Const(ct) => acc.add_const(ctx, ct), |
88 | hir::AssocItem::TypeAlias(ty) => acc.add_type_alias(ctx, ty), | 86 | hir::AssocItem::TypeAlias(ty) => acc.add_type_alias(ctx, ty), |
89 | } | 87 | } |
@@ -110,9 +108,7 @@ pub(crate) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon | |||
110 | continue; | 108 | continue; |
111 | } | 109 | } |
112 | match item { | 110 | match item { |
113 | hir::AssocItem::Function(func) => { | 111 | hir::AssocItem::Function(func) => acc.add_function(ctx, func, None), |
114 | acc.add_function(ctx, func, None); | ||
115 | } | ||
116 | hir::AssocItem::Const(ct) => acc.add_const(ctx, ct), | 112 | hir::AssocItem::Const(ct) => acc.add_const(ctx, ct), |
117 | hir::AssocItem::TypeAlias(ty) => acc.add_type_alias(ctx, ty), | 113 | hir::AssocItem::TypeAlias(ty) => acc.add_type_alias(ctx, ty), |
118 | } | 114 | } |
@@ -143,9 +139,7 @@ pub(crate) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon | |||
143 | // them. | 139 | // them. |
144 | if seen.insert(item) { | 140 | if seen.insert(item) { |
145 | match item { | 141 | match item { |
146 | hir::AssocItem::Function(func) => { | 142 | hir::AssocItem::Function(func) => acc.add_function(ctx, func, None), |
147 | acc.add_function(ctx, func, None); | ||
148 | } | ||
149 | hir::AssocItem::Const(ct) => acc.add_const(ctx, ct), | 143 | hir::AssocItem::Const(ct) => acc.add_const(ctx, ct), |
150 | hir::AssocItem::TypeAlias(ty) => acc.add_type_alias(ctx, ty), | 144 | hir::AssocItem::TypeAlias(ty) => acc.add_type_alias(ctx, ty), |
151 | } | 145 | } |
diff --git a/crates/syntax/Cargo.toml b/crates/syntax/Cargo.toml index d836c5d1a..9ee3a8586 100644 --- a/crates/syntax/Cargo.toml +++ b/crates/syntax/Cargo.toml | |||
@@ -13,7 +13,7 @@ doctest = false | |||
13 | [dependencies] | 13 | [dependencies] |
14 | itertools = "0.10.0" | 14 | itertools = "0.10.0" |
15 | rowan = "0.12.2" | 15 | rowan = "0.12.2" |
16 | rustc_lexer = { version = "708.0.0", package = "rustc-ap-rustc_lexer" } | 16 | rustc_lexer = { version = "709.0.0", package = "rustc-ap-rustc_lexer" } |
17 | rustc-hash = "1.1.0" | 17 | rustc-hash = "1.1.0" |
18 | arrayvec = "0.5.1" | 18 | arrayvec = "0.5.1" |
19 | once_cell = "1.3.1" | 19 | once_cell = "1.3.1" |
diff --git a/docs/dev/style.md b/docs/dev/style.md index dd71e3932..93ad98f20 100644 --- a/docs/dev/style.md +++ b/docs/dev/style.md | |||
@@ -769,14 +769,20 @@ fn foo() -> Option<Bar> { | |||
769 | 769 | ||
770 | ## Comparisons | 770 | ## Comparisons |
771 | 771 | ||
772 | Use `<`/`<=`, avoid `>`/`>=`. | 772 | When doing multiple comparisons use `<`/`<=`, avoid `>`/`>=`. |
773 | 773 | ||
774 | ```rust | 774 | ```rust |
775 | // GOOD | 775 | // GOOD |
776 | assert!(lo <= x && x <= hi); | 776 | assert!(lo <= x && x <= hi); |
777 | assert!(r1 < l2 || r2 < l1); | ||
778 | assert!(x < y); | ||
779 | assert!(x > 0); | ||
777 | 780 | ||
778 | // BAD | 781 | // BAD |
779 | assert!(x >= lo && x <= hi>); | 782 | assert!(x >= lo && x <= hi>); |
783 | assert!(r1 < l2 || l1 > r2); | ||
784 | assert!(y > x); | ||
785 | assert!(0 > x); | ||
780 | ``` | 786 | ``` |
781 | 787 | ||
782 | **Rationale:** Less-then comparisons are more intuitive, they correspond spatially to [real line](https://en.wikipedia.org/wiki/Real_line). | 788 | **Rationale:** Less-then comparisons are more intuitive, they correspond spatially to [real line](https://en.wikipedia.org/wiki/Real_line). |