aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to 'crates')
-rw-r--r--crates/hir_def/src/find_path.rs24
-rw-r--r--crates/ide/src/references/rename.rs378
-rw-r--r--crates/ide_assists/src/handlers/apply_demorgan.rs45
-rw-r--r--crates/ide_completion/src/completions/attribute.rs3
-rw-r--r--crates/ide_completion/src/completions/fn_param.rs27
-rw-r--r--crates/ide_completion/src/completions/keyword.rs35
-rw-r--r--crates/ide_completion/src/completions/qualified_path.rs12
-rw-r--r--crates/syntax/Cargo.toml2
8 files changed, 278 insertions, 248 deletions
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
165fn 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
194fn 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
206fn 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
278fn rename_mod( 167fn 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
212fn 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
323fn rename_to_self(sema: &Semantics<RootDatabase>, local: hir::Local) -> RenameResult<SourceChange> { 269fn 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
397fn 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
420fn rename_self_to_param( 343fn 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
449fn rename_reference( 372fn 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
395fn 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(); 422fn 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)?; 434fn 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
506fn source_edit_from_def( 506fn 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 @@
1use syntax::ast::{self, AstNode}; 1use syntax::ast::{self, AstNode};
2use test_utils::mark;
2 3
3use crate::{utils::invert_boolean_expression, AssistContext, AssistId, AssistKind, Assists}; 4use 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)]
63mod tests { 91mod 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
41fn complete_attribute_start(acc: &mut Completions, ctx: &CompletionContext, attribute: &ast::Attr) { 41fn 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
3use std::iter;
4
3use syntax::SyntaxKind; 5use syntax::SyntaxKind;
4use test_utils::mark; 6use 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]
14itertools = "0.10.0" 14itertools = "0.10.0"
15rowan = "0.12.2" 15rowan = "0.12.2"
16rustc_lexer = { version = "708.0.0", package = "rustc-ap-rustc_lexer" } 16rustc_lexer = { version = "709.0.0", package = "rustc-ap-rustc_lexer" }
17rustc-hash = "1.1.0" 17rustc-hash = "1.1.0"
18arrayvec = "0.5.1" 18arrayvec = "0.5.1"
19once_cell = "1.3.1" 19once_cell = "1.3.1"