From 0df1153277cc581533c97ccf826231898eb214ed Mon Sep 17 00:00:00 2001 From: Ali Bektas Date: Tue, 13 Jun 2023 14:42:33 +0200 Subject: [PATCH 01/59] bugfix : skip doc(hidden) default members fixes #14957 --- .../src/handlers/add_missing_impl_members.rs | 39 +++++++++++++++++++ .../replace_derive_with_manual_impl.rs | 2 +- crates/ide-assists/src/utils.rs | 36 +++++++++++++++-- 3 files changed, 72 insertions(+), 5 deletions(-) diff --git a/crates/ide-assists/src/handlers/add_missing_impl_members.rs b/crates/ide-assists/src/handlers/add_missing_impl_members.rs index d07c6372628d6..ecf1abc277d6e 100644 --- a/crates/ide-assists/src/handlers/add_missing_impl_members.rs +++ b/crates/ide-assists/src/handlers/add_missing_impl_members.rs @@ -43,6 +43,7 @@ pub(crate) fn add_missing_impl_members(acc: &mut Assists, ctx: &AssistContext<'_ acc, ctx, DefaultMethods::No, + true, "add_impl_missing_members", "Implement missing members", ) @@ -87,6 +88,7 @@ pub(crate) fn add_missing_default_members( acc, ctx, DefaultMethods::Only, + true, "add_impl_default_members", "Implement default members", ) @@ -96,6 +98,7 @@ fn add_missing_impl_members_inner( acc: &mut Assists, ctx: &AssistContext<'_>, mode: DefaultMethods, + ignore_hidden: bool, assist_id: &'static str, label: &'static str, ) -> Option<()> { @@ -119,6 +122,7 @@ fn add_missing_impl_members_inner( &ctx.sema, &ide_db::traits::get_missing_assoc_items(&ctx.sema, &impl_def), mode, + ignore_hidden, ); if missing_items.is_empty() { @@ -1966,4 +1970,39 @@ impl AnotherTrait for () { "#, ); } + + #[test] + fn doc_hidden_default_impls_ignored() { + check_assist( + add_missing_default_members, + r#" +struct Foo; +trait Trait { + #[doc(hidden)] + fn func_with_default_impl() -> u32 { + 42 + } + fn another_default_impl() -> u32 { + 43 + } +} +impl Tra$0it for Foo {}"#, + r#" +struct Foo; +trait Trait { + #[doc(hidden)] + fn func_with_default_impl() -> u32 { + 42 + } + fn another_default_impl() -> u32 { + 43 + } +} +impl Trait for Foo { + $0fn another_default_impl() -> u32 { + 43 + } +}"#, + ) + } } diff --git a/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs b/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs index c03bc2f41d50f..4fa3394233fd1 100644 --- a/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs +++ b/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs @@ -172,7 +172,7 @@ fn impl_def_from_trait( ) -> Option<(ast::Impl, ast::AssocItem)> { let trait_ = trait_?; let target_scope = sema.scope(annotated_name.syntax())?; - let trait_items = filter_assoc_items(sema, &trait_.items(sema.db), DefaultMethods::No); + let trait_items = filter_assoc_items(sema, &trait_.items(sema.db), DefaultMethods::No, true); if trait_items.is_empty() { return None; } diff --git a/crates/ide-assists/src/utils.rs b/crates/ide-assists/src/utils.rs index 03d8553506f14..e4d242815ef5a 100644 --- a/crates/ide-assists/src/utils.rs +++ b/crates/ide-assists/src/utils.rs @@ -3,7 +3,7 @@ use std::ops; pub(crate) use gen_trait_fn_body::gen_trait_fn_body; -use hir::{db::HirDatabase, HirDisplay, InFile, Semantics}; +use hir::{db::HirDatabase, HasAttrs as HirHasAttrs, HirDisplay, InFile, Semantics}; use ide_db::{ famous_defs::FamousDefs, path_transform::PathTransform, syntax_helpers::insert_whitespace_into_node::insert_ws_into, RootDatabase, SnippetCap, @@ -94,16 +94,44 @@ pub fn filter_assoc_items( sema: &Semantics<'_, RootDatabase>, items: &[hir::AssocItem], default_methods: DefaultMethods, + ignore_hidden: bool, ) -> Vec> { + // FIXME : How to use the closure below this so I can write sth like + // sema.source(hide(it)?)?... + + // let hide = |it: impl HasAttrs| { + // if default_methods == DefaultMethods::IgnoreHidden && it.attrs(sema.db).has_doc_hidden() { + // None; + // } + // Some(it) + // }; + return items .iter() // Note: This throws away items with no source. .copied() .filter_map(|assoc_item| { let item = match assoc_item { - hir::AssocItem::Function(it) => sema.source(it)?.map(ast::AssocItem::Fn), - hir::AssocItem::TypeAlias(it) => sema.source(it)?.map(ast::AssocItem::TypeAlias), - hir::AssocItem::Const(it) => sema.source(it)?.map(ast::AssocItem::Const), + hir::AssocItem::Function(it) => { + if ignore_hidden && it.attrs(sema.db).has_doc_hidden() { + return None; + } + sema.source(it)?.map(ast::AssocItem::Fn) + } + hir::AssocItem::TypeAlias(it) => { + if ignore_hidden && it.attrs(sema.db).has_doc_hidden() { + return None; + } + + sema.source(it)?.map(ast::AssocItem::TypeAlias) + } + hir::AssocItem::Const(it) => { + if ignore_hidden && it.attrs(sema.db).has_doc_hidden() { + return None; + } + + sema.source(it)?.map(ast::AssocItem::Const) + } }; Some(item) }) From 8a2c5d215bbef35688d47d851569843e61c1a0ba Mon Sep 17 00:00:00 2001 From: Ali Bektas Date: Fri, 23 Jun 2023 01:27:19 +0200 Subject: [PATCH 02/59] Still in need of more test cases --- .../src/handlers/add_missing_impl_members.rs | 44 ++++++++++++++--- .../replace_derive_with_manual_impl.rs | 14 +++++- crates/ide-assists/src/utils.rs | 47 ++++++------------- 3 files changed, 65 insertions(+), 40 deletions(-) diff --git a/crates/ide-assists/src/handlers/add_missing_impl_members.rs b/crates/ide-assists/src/handlers/add_missing_impl_members.rs index ecf1abc277d6e..818ec3de337cc 100644 --- a/crates/ide-assists/src/handlers/add_missing_impl_members.rs +++ b/crates/ide-assists/src/handlers/add_missing_impl_members.rs @@ -3,7 +3,10 @@ use syntax::ast::{self, make, AstNode}; use crate::{ assist_context::{AssistContext, Assists}, - utils::{add_trait_assoc_items_to_impl, filter_assoc_items, gen_trait_fn_body, DefaultMethods}, + utils::{ + add_trait_assoc_items_to_impl, filter_assoc_items, gen_trait_fn_body, DefaultMethods, + IgnoreAssocItems, + }, AssistId, AssistKind, }; @@ -43,7 +46,7 @@ pub(crate) fn add_missing_impl_members(acc: &mut Assists, ctx: &AssistContext<'_ acc, ctx, DefaultMethods::No, - true, + IgnoreAssocItems::HiddenDocAttrPresent, "add_impl_missing_members", "Implement missing members", ) @@ -88,7 +91,7 @@ pub(crate) fn add_missing_default_members( acc, ctx, DefaultMethods::Only, - true, + IgnoreAssocItems::HiddenDocAttrPresent, "add_impl_default_members", "Implement default members", ) @@ -98,7 +101,7 @@ fn add_missing_impl_members_inner( acc: &mut Assists, ctx: &AssistContext<'_>, mode: DefaultMethods, - ignore_hidden: bool, + ignore_items: IgnoreAssocItems, assist_id: &'static str, label: &'static str, ) -> Option<()> { @@ -118,11 +121,22 @@ fn add_missing_impl_members_inner( let trait_ref = impl_.trait_ref(ctx.db())?; let trait_ = trait_ref.trait_(); + let mut ign_item = ignore_items; + + if let IgnoreAssocItems::HiddenDocAttrPresent = ignore_items { + // Relax condition for local crates. + + let db = ctx.db(); + if trait_.module(db).krate().origin(db).is_local() { + ign_item = IgnoreAssocItems::No; + } + } + let missing_items = filter_assoc_items( &ctx.sema, &ide_db::traits::get_missing_assoc_items(&ctx.sema, &impl_def), mode, - ignore_hidden, + ign_item, ); if missing_items.is_empty() { @@ -1999,10 +2013,28 @@ trait Trait { } } impl Trait for Foo { - $0fn another_default_impl() -> u32 { + $0fn func_with_default_impl() -> u32 { + 42 + } + + fn another_default_impl() -> u32 { 43 } }"#, ) } + + #[test] + fn doc_hidden_default_impls_extern_crates() { + // Not applicable because Eq has a single method and this has a #[doc(hidden)] attr set. + check_assist_not_applicable( + add_missing_default_members, + r#" +//- minicore: eq +use core::cmp::Eq; +struct Foo; +impl E$0q for Foo { /* $0 */ } +"#, + ) + } } diff --git a/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs b/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs index 4fa3394233fd1..48a68b6bea6a5 100644 --- a/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs +++ b/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs @@ -10,7 +10,7 @@ use crate::{ assist_context::{AssistContext, Assists, SourceChangeBuilder}, utils::{ add_trait_assoc_items_to_impl, filter_assoc_items, gen_trait_fn_body, - generate_trait_impl_text, render_snippet, Cursor, DefaultMethods, + generate_trait_impl_text, render_snippet, Cursor, DefaultMethods, IgnoreAssocItems, }, AssistId, AssistKind, }; @@ -172,7 +172,17 @@ fn impl_def_from_trait( ) -> Option<(ast::Impl, ast::AssocItem)> { let trait_ = trait_?; let target_scope = sema.scope(annotated_name.syntax())?; - let trait_items = filter_assoc_items(sema, &trait_.items(sema.db), DefaultMethods::No, true); + + // Keep assoc items of local crates even if they have #[doc(hidden)] attr. + let ignore_items = if trait_.module(sema.db).krate().origin(sema.db).is_local() { + IgnoreAssocItems::No + } else { + IgnoreAssocItems::HiddenDocAttrPresent + }; + + let trait_items = + filter_assoc_items(sema, &trait_.items(sema.db), DefaultMethods::No, ignore_items); + if trait_items.is_empty() { return None; } diff --git a/crates/ide-assists/src/utils.rs b/crates/ide-assists/src/utils.rs index e4d242815ef5a..e09af5ce3024f 100644 --- a/crates/ide-assists/src/utils.rs +++ b/crates/ide-assists/src/utils.rs @@ -84,6 +84,12 @@ pub fn test_related_attribute(fn_def: &ast::Fn) -> Option { }) } +#[derive(Clone, Copy, PartialEq)] +pub enum IgnoreAssocItems { + HiddenDocAttrPresent, + No, +} + #[derive(Copy, Clone, PartialEq)] pub enum DefaultMethods { Only, @@ -94,48 +100,25 @@ pub fn filter_assoc_items( sema: &Semantics<'_, RootDatabase>, items: &[hir::AssocItem], default_methods: DefaultMethods, - ignore_hidden: bool, + ignore_items: IgnoreAssocItems, ) -> Vec> { - // FIXME : How to use the closure below this so I can write sth like - // sema.source(hide(it)?)?... - - // let hide = |it: impl HasAttrs| { - // if default_methods == DefaultMethods::IgnoreHidden && it.attrs(sema.db).has_doc_hidden() { - // None; - // } - // Some(it) - // }; - return items .iter() - // Note: This throws away items with no source. .copied() + .filter(|assoc_item| { + !(ignore_items == IgnoreAssocItems::HiddenDocAttrPresent + && assoc_item.attrs(sema.db).has_doc_hidden()) + }) .filter_map(|assoc_item| { let item = match assoc_item { - hir::AssocItem::Function(it) => { - if ignore_hidden && it.attrs(sema.db).has_doc_hidden() { - return None; - } - sema.source(it)?.map(ast::AssocItem::Fn) - } - hir::AssocItem::TypeAlias(it) => { - if ignore_hidden && it.attrs(sema.db).has_doc_hidden() { - return None; - } - - sema.source(it)?.map(ast::AssocItem::TypeAlias) - } - hir::AssocItem::Const(it) => { - if ignore_hidden && it.attrs(sema.db).has_doc_hidden() { - return None; - } - - sema.source(it)?.map(ast::AssocItem::Const) - } + hir::AssocItem::Function(it) => sema.source(it)?.map(ast::AssocItem::Fn), + hir::AssocItem::TypeAlias(it) => sema.source(it)?.map(ast::AssocItem::TypeAlias), + hir::AssocItem::Const(it) => sema.source(it)?.map(ast::AssocItem::Const), }; Some(item) }) .filter(has_def_name) + // Note: This throws away items with no source. .filter(|it| match &it.value { ast::AssocItem::Fn(def) => matches!( (default_methods, def.body()), From 18ea9245c6c1b73dabaff1fe5be286a6e39a68dc Mon Sep 17 00:00:00 2001 From: Ali Bektas Date: Fri, 23 Jun 2023 01:32:06 +0200 Subject: [PATCH 03/59] v2 --- .../src/handlers/add_missing_impl_members.rs | 115 +++++++++++++++++- crates/ide-assists/src/utils.rs | 2 +- 2 files changed, 114 insertions(+), 3 deletions(-) diff --git a/crates/ide-assists/src/handlers/add_missing_impl_members.rs b/crates/ide-assists/src/handlers/add_missing_impl_members.rs index 818ec3de337cc..e5c18f362b722 100644 --- a/crates/ide-assists/src/handlers/add_missing_impl_members.rs +++ b/crates/ide-assists/src/handlers/add_missing_impl_members.rs @@ -125,7 +125,6 @@ fn add_missing_impl_members_inner( if let IgnoreAssocItems::HiddenDocAttrPresent = ignore_items { // Relax condition for local crates. - let db = ctx.db(); if trait_.module(db).krate().origin(db).is_local() { ign_item = IgnoreAssocItems::No; @@ -1987,6 +1986,7 @@ impl AnotherTrait for () { #[test] fn doc_hidden_default_impls_ignored() { + // doc(hidden) attr is ignored trait and impl both belong to the local crate. check_assist( add_missing_default_members, r#" @@ -2025,7 +2025,7 @@ impl Trait for Foo { } #[test] - fn doc_hidden_default_impls_extern_crates() { + fn doc_hidden_default_impls_lang_crates() { // Not applicable because Eq has a single method and this has a #[doc(hidden)] attr set. check_assist_not_applicable( add_missing_default_members, @@ -2037,4 +2037,115 @@ impl E$0q for Foo { /* $0 */ } "#, ) } + + #[test] + fn doc_hidden_default_impls_lib_crates() { + check_assist( + add_missing_default_members, + r#" + //- /main.rs crate:a deps:b + struct B; + impl b::Exte$0rnTrait for B {} + //- /lib.rs crate:b new_source_root:library + pub trait ExternTrait { + #[doc(hidden)] + fn hidden_default() -> Option<()> { + todo!() + } + + fn unhidden_default() -> Option<()> { + todo!() + } + + fn unhidden_nondefault() -> Option<()>; + } + "#, + r#" + struct B; + impl b::ExternTrait for B { + $0fn unhidden_default() -> Option<()> { + todo!() + } + } + "#, + ) + } + + #[test] + fn doc_hidden_default_impls_local_crates() { + check_assist( + add_missing_default_members, + r#" +trait LocalTrait { + #[doc(hidden)] + fn no_skip_default() -> Option<()> { + todo!() + } + fn no_skip_default_2() -> Option<()> { + todo!() + } +} + +struct B; +impl Loc$0alTrait for B {} + "#, + r#" +trait LocalTrait { + #[doc(hidden)] + fn no_skip_default() -> Option<()> { + todo!() + } + fn no_skip_default_2() -> Option<()> { + todo!() + } +} + +struct B; +impl LocalTrait for B { + $0fn no_skip_default() -> Option<()> { + todo!() + } + + fn no_skip_default_2() -> Option<()> { + todo!() + } +} + "#, + ) + } + + #[test] + fn doc_hidden_default_impls_workspace_crates() { + check_assist( + add_missing_default_members, + r#" +//- /lib.rs crate:b new_source_root:local +trait LocalTrait { + #[doc(hidden)] + fn no_skip_default() -> Option<()> { + todo!() + } + fn no_skip_default_2() -> Option<()> { + todo!() + } +} + +//- /main.rs crate:a deps:b +struct B; +impl b::Loc$0alTrait for B {} + "#, + r#" +struct B; +impl b::LocalTrait for B { + $0fn no_skip_default() -> Option<()> { + todo!() + } + + fn no_skip_default_2() -> Option<()> { + todo!() + } +} + "#, + ) + } } diff --git a/crates/ide-assists/src/utils.rs b/crates/ide-assists/src/utils.rs index e09af5ce3024f..aab4f15f14126 100644 --- a/crates/ide-assists/src/utils.rs +++ b/crates/ide-assists/src/utils.rs @@ -109,6 +109,7 @@ pub fn filter_assoc_items( !(ignore_items == IgnoreAssocItems::HiddenDocAttrPresent && assoc_item.attrs(sema.db).has_doc_hidden()) }) + // Note: This throws away items with no source. .filter_map(|assoc_item| { let item = match assoc_item { hir::AssocItem::Function(it) => sema.source(it)?.map(ast::AssocItem::Fn), @@ -118,7 +119,6 @@ pub fn filter_assoc_items( Some(item) }) .filter(has_def_name) - // Note: This throws away items with no source. .filter(|it| match &it.value { ast::AssocItem::Fn(def) => matches!( (default_methods, def.body()), From 915ddb05fa78dfe1cfcda2b68c1013f218d729d4 Mon Sep 17 00:00:00 2001 From: Ali Bektas Date: Fri, 7 Jul 2023 14:15:15 +0200 Subject: [PATCH 04/59] HiddenDocAttr becomes DocHiddenAttr --- crates/ide-assists/src/handlers/add_missing_impl_members.rs | 6 +++--- .../src/handlers/replace_derive_with_manual_impl.rs | 2 +- crates/ide-assists/src/utils.rs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/ide-assists/src/handlers/add_missing_impl_members.rs b/crates/ide-assists/src/handlers/add_missing_impl_members.rs index e5c18f362b722..6aca716bb60a3 100644 --- a/crates/ide-assists/src/handlers/add_missing_impl_members.rs +++ b/crates/ide-assists/src/handlers/add_missing_impl_members.rs @@ -46,7 +46,7 @@ pub(crate) fn add_missing_impl_members(acc: &mut Assists, ctx: &AssistContext<'_ acc, ctx, DefaultMethods::No, - IgnoreAssocItems::HiddenDocAttrPresent, + IgnoreAssocItems::DocHiddenAttrPresent, "add_impl_missing_members", "Implement missing members", ) @@ -91,7 +91,7 @@ pub(crate) fn add_missing_default_members( acc, ctx, DefaultMethods::Only, - IgnoreAssocItems::HiddenDocAttrPresent, + IgnoreAssocItems::DocHiddenAttrPresent, "add_impl_default_members", "Implement default members", ) @@ -123,7 +123,7 @@ fn add_missing_impl_members_inner( let mut ign_item = ignore_items; - if let IgnoreAssocItems::HiddenDocAttrPresent = ignore_items { + if let IgnoreAssocItems::DocHiddenAttrPresent = ignore_items { // Relax condition for local crates. let db = ctx.db(); if trait_.module(db).krate().origin(db).is_local() { diff --git a/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs b/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs index 48a68b6bea6a5..ac45581b7b440 100644 --- a/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs +++ b/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs @@ -177,7 +177,7 @@ fn impl_def_from_trait( let ignore_items = if trait_.module(sema.db).krate().origin(sema.db).is_local() { IgnoreAssocItems::No } else { - IgnoreAssocItems::HiddenDocAttrPresent + IgnoreAssocItems::DocHiddenAttrPresent }; let trait_items = diff --git a/crates/ide-assists/src/utils.rs b/crates/ide-assists/src/utils.rs index aab4f15f14126..a262570d94e7d 100644 --- a/crates/ide-assists/src/utils.rs +++ b/crates/ide-assists/src/utils.rs @@ -86,7 +86,7 @@ pub fn test_related_attribute(fn_def: &ast::Fn) -> Option { #[derive(Clone, Copy, PartialEq)] pub enum IgnoreAssocItems { - HiddenDocAttrPresent, + DocHiddenAttrPresent, No, } @@ -106,7 +106,7 @@ pub fn filter_assoc_items( .iter() .copied() .filter(|assoc_item| { - !(ignore_items == IgnoreAssocItems::HiddenDocAttrPresent + !(ignore_items == IgnoreAssocItems::DocHiddenAttrPresent && assoc_item.attrs(sema.db).has_doc_hidden()) }) // Note: This throws away items with no source. From b9cef032300227dc27af9c9e24814a8f54035f03 Mon Sep 17 00:00:00 2001 From: Josiah Bills Date: Sun, 9 Jul 2023 17:20:18 -0400 Subject: [PATCH 05/59] Updated search to expose some more functions and to make search take the search scope by reference. --- .../src/handlers/destructure_tuple_binding.rs | 2 +- .../src/handlers/expand_glob_import.rs | 2 +- .../ide-assists/src/handlers/extract_function.rs | 2 +- .../ide-assists/src/handlers/extract_module.rs | 2 +- crates/ide-assists/src/handlers/inline_call.rs | 2 +- .../src/handlers/move_const_to_impl.rs | 16 +++++++++------- .../handlers/replace_named_generic_with_impl.rs | 2 +- crates/ide-db/src/search.rs | 12 ++++++------ crates/ide-ssr/src/search.rs | 2 +- crates/ide/src/highlight_related.rs | 9 +++------ crates/ide/src/references.rs | 2 +- crates/ide/src/runnables.rs | 2 +- 12 files changed, 27 insertions(+), 28 deletions(-) diff --git a/crates/ide-assists/src/handlers/destructure_tuple_binding.rs b/crates/ide-assists/src/handlers/destructure_tuple_binding.rs index ea71d165e6aa6..f30ca2552d368 100644 --- a/crates/ide-assists/src/handlers/destructure_tuple_binding.rs +++ b/crates/ide-assists/src/handlers/destructure_tuple_binding.rs @@ -114,7 +114,7 @@ fn collect_data(ident_pat: IdentPat, ctx: &AssistContext<'_>) -> Option) -> bool { let search_scope = SearchScope::single_file(ctx.file_id()); - def.usages(&ctx.sema).in_scope(search_scope).at_least_one() + def.usages(&ctx.sema).in_scope(&search_scope).at_least_one() } #[derive(Debug, Clone)] diff --git a/crates/ide-assists/src/handlers/extract_function.rs b/crates/ide-assists/src/handlers/extract_function.rs index e9db38aca0f05..b8b781ea48d45 100644 --- a/crates/ide-assists/src/handlers/extract_function.rs +++ b/crates/ide-assists/src/handlers/extract_function.rs @@ -384,7 +384,7 @@ impl LocalUsages { Self( Definition::Local(var) .usages(&ctx.sema) - .in_scope(SearchScope::single_file(ctx.file_id())) + .in_scope(&SearchScope::single_file(ctx.file_id())) .all(), ) } diff --git a/crates/ide-assists/src/handlers/extract_module.rs b/crates/ide-assists/src/handlers/extract_module.rs index de37f5f130fca..6839c5820dc99 100644 --- a/crates/ide-assists/src/handlers/extract_module.rs +++ b/crates/ide-assists/src/handlers/extract_module.rs @@ -478,7 +478,7 @@ impl Module { let selection_range = ctx.selection_trimmed(); let curr_file_id = ctx.file_id(); let search_scope = SearchScope::single_file(curr_file_id); - let usage_res = def.usages(&ctx.sema).in_scope(search_scope).all(); + let usage_res = def.usages(&ctx.sema).in_scope(&search_scope).all(); let file = ctx.sema.parse(curr_file_id); let mut exists_inside_sel = false; diff --git a/crates/ide-assists/src/handlers/inline_call.rs b/crates/ide-assists/src/handlers/inline_call.rs index 67036029f5eec..ffab58509b182 100644 --- a/crates/ide-assists/src/handlers/inline_call.rs +++ b/crates/ide-assists/src/handlers/inline_call.rs @@ -80,7 +80,7 @@ pub(crate) fn inline_into_callers(acc: &mut Assists, ctx: &AssistContext<'_>) -> let is_recursive_fn = usages .clone() - .in_scope(SearchScope::file_range(FileRange { + .in_scope(&SearchScope::file_range(FileRange { file_id: def_file, range: func_body.syntax().text_range(), })) diff --git a/crates/ide-assists/src/handlers/move_const_to_impl.rs b/crates/ide-assists/src/handlers/move_const_to_impl.rs index e1849eb71d57a..22d536b5afc36 100644 --- a/crates/ide-assists/src/handlers/move_const_to_impl.rs +++ b/crates/ide-assists/src/handlers/move_const_to_impl.rs @@ -82,17 +82,19 @@ pub(crate) fn move_const_to_impl(acc: &mut Assists, ctx: &AssistContext<'_>) -> return None; } - let usages = - Definition::Const(def).usages(&ctx.sema).in_scope(SearchScope::file_range(FileRange { - file_id: ctx.file_id(), - range: parent_fn.syntax().text_range(), - })); - acc.add( AssistId("move_const_to_impl", crate::AssistKind::RefactorRewrite), "Move const to impl block", const_.syntax().text_range(), |builder| { + let usages = Definition::Const(def) + .usages(&ctx.sema) + .in_scope(&SearchScope::file_range(FileRange { + file_id: ctx.file_id(), + range: parent_fn.syntax().text_range(), + })) + .all(); + let range_to_delete = match const_.syntax().next_sibling_or_token() { Some(s) if matches!(s.kind(), SyntaxKind::WHITESPACE) => { // Remove following whitespaces too. @@ -103,7 +105,7 @@ pub(crate) fn move_const_to_impl(acc: &mut Assists, ctx: &AssistContext<'_>) -> builder.delete(range_to_delete); let const_ref = format!("Self::{}", name.display(ctx.db())); - for range in usages.all().file_ranges().map(|it| it.range) { + for range in usages.file_ranges().map(|it| it.range) { builder.replace(range, const_ref.clone()); } diff --git a/crates/ide-assists/src/handlers/replace_named_generic_with_impl.rs b/crates/ide-assists/src/handlers/replace_named_generic_with_impl.rs index e7b62d49bb814..c7c0be4c7d4f8 100644 --- a/crates/ide-assists/src/handlers/replace_named_generic_with_impl.rs +++ b/crates/ide-assists/src/handlers/replace_named_generic_with_impl.rs @@ -157,7 +157,7 @@ fn find_usages( file_id: FileId, ) -> UsageSearchResult { let file_range = FileRange { file_id, range: fn_.syntax().text_range() }; - type_param_def.usages(sema).in_scope(SearchScope::file_range(file_range)).all() + type_param_def.usages(sema).in_scope(&SearchScope::file_range(file_range)).all() } fn check_valid_usages(usages: &UsageSearchResult, param_list_range: TextRange) -> bool { diff --git a/crates/ide-db/src/search.rs b/crates/ide-db/src/search.rs index f3c0f79c58967..d5abd09912681 100644 --- a/crates/ide-db/src/search.rs +++ b/crates/ide-db/src/search.rs @@ -127,7 +127,7 @@ impl SearchScope { } /// Build a search scope spanning the given module and all its submodules. - fn module_and_children(db: &RootDatabase, module: hir::Module) -> SearchScope { + pub fn module_and_children(db: &RootDatabase, module: hir::Module) -> SearchScope { let mut entries = IntMap::default(); let (file_id, range) = { @@ -329,7 +329,7 @@ impl Definition { pub struct FindUsages<'a> { def: Definition, sema: &'a Semantics<'a, RootDatabase>, - scope: Option, + scope: Option<&'a SearchScope>, /// The container of our definition should it be an assoc item assoc_item_container: Option, /// whether to search for the `Self` type of the definition @@ -338,7 +338,7 @@ pub struct FindUsages<'a> { search_self_mod: bool, } -impl FindUsages<'_> { +impl<'a> FindUsages<'a> { /// Enable searching for `Self` when the definition is a type or `self` for modules. pub fn include_self_refs(mut self) -> Self { self.include_self_kw_refs = def_to_ty(self.sema, &self.def); @@ -347,12 +347,12 @@ impl FindUsages<'_> { } /// Limit the search to a given [`SearchScope`]. - pub fn in_scope(self, scope: SearchScope) -> Self { + pub fn in_scope(self, scope: &'a SearchScope) -> Self { self.set_scope(Some(scope)) } /// Limit the search to a given [`SearchScope`]. - pub fn set_scope(mut self, scope: Option) -> Self { + pub fn set_scope(mut self, scope: Option<&'a SearchScope>) -> Self { assert!(self.scope.is_none()); self.scope = scope; self @@ -376,7 +376,7 @@ impl FindUsages<'_> { res } - fn search(&self, sink: &mut dyn FnMut(FileId, FileReference) -> bool) { + pub fn search(&self, sink: &mut dyn FnMut(FileId, FileReference) -> bool) { let _p = profile::span("FindUsages:search"); let sema = self.sema; diff --git a/crates/ide-ssr/src/search.rs b/crates/ide-ssr/src/search.rs index 96c193bd53903..ca76d0a87b99b 100644 --- a/crates/ide-ssr/src/search.rs +++ b/crates/ide-ssr/src/search.rs @@ -121,7 +121,7 @@ impl MatchFinder<'_> { // cache miss. This is a limitation of NLL and is fixed with Polonius. For now we do two // lookups in the case of a cache hit. if usage_cache.find(&definition).is_none() { - let usages = definition.usages(&self.sema).in_scope(self.search_scope()).all(); + let usages = definition.usages(&self.sema).in_scope(&self.search_scope()).all(); usage_cache.usages.push((definition, usages)); return &usage_cache.usages.last().unwrap().1; } diff --git a/crates/ide/src/highlight_related.rs b/crates/ide/src/highlight_related.rs index 7e545491f8e7e..43e89a334bf5b 100644 --- a/crates/ide/src/highlight_related.rs +++ b/crates/ide/src/highlight_related.rs @@ -100,10 +100,7 @@ fn highlight_closure_captures( .flat_map(|local| { let usages = Definition::Local(local) .usages(sema) - .set_scope(Some(SearchScope::file_range(FileRange { - file_id, - range: search_range, - }))) + .in_scope(&SearchScope::file_range(FileRange { file_id, range: search_range })) .include_self_refs() .all() .references @@ -139,7 +136,7 @@ fn highlight_references( .iter() .filter_map(|&d| { d.usages(sema) - .set_scope(Some(SearchScope::single_file(file_id))) + .in_scope(&SearchScope::single_file(file_id)) .include_self_refs() .all() .references @@ -183,7 +180,7 @@ fn highlight_references( .filter_map(|item| { Definition::from(item) .usages(sema) - .set_scope(Some(SearchScope::file_range(FileRange { + .set_scope(Some(&SearchScope::file_range(FileRange { file_id, range: trait_item_use_scope.text_range(), }))) diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs index fdc5261ac38b2..44073fa757f1d 100644 --- a/crates/ide/src/references.rs +++ b/crates/ide/src/references.rs @@ -74,7 +74,7 @@ pub(crate) fn find_all_refs( } }); let mut usages = - def.usages(sema).set_scope(search_scope.clone()).include_self_refs().all(); + def.usages(sema).set_scope(search_scope.as_ref()).include_self_refs().all(); if literal_search { retain_adt_literal_usages(&mut usages, def, sema); diff --git a/crates/ide/src/runnables.rs b/crates/ide/src/runnables.rs index 9fa0e6449b836..8c445ddcb226d 100644 --- a/crates/ide/src/runnables.rs +++ b/crates/ide/src/runnables.rs @@ -232,7 +232,7 @@ fn find_related_tests( for def in defs { let defs = def .usages(sema) - .set_scope(search_scope.clone()) + .set_scope(search_scope.as_ref()) .all() .references .into_values() From 7a87a35f1aaeaf8d463f200a2f3ce47d5e10bbf9 Mon Sep 17 00:00:00 2001 From: Josiah Bills Date: Sun, 9 Jul 2023 17:20:43 -0400 Subject: [PATCH 06/59] Added remove_unused_imports assist. --- .../src/handlers/remove_unused_imports.rs | 739 ++++++++++++++++++ crates/ide-assists/src/lib.rs | 2 + crates/ide-assists/src/tests/generated.rs | 18 + crates/syntax/src/ast/edit_in_place.rs | 36 + 4 files changed, 795 insertions(+) create mode 100644 crates/ide-assists/src/handlers/remove_unused_imports.rs diff --git a/crates/ide-assists/src/handlers/remove_unused_imports.rs b/crates/ide-assists/src/handlers/remove_unused_imports.rs new file mode 100644 index 0000000000000..dd4839351fb41 --- /dev/null +++ b/crates/ide-assists/src/handlers/remove_unused_imports.rs @@ -0,0 +1,739 @@ +use std::collections::{hash_map::Entry, HashMap}; + +use hir::{InFile, Module, ModuleSource}; +use ide_db::{ + base_db::FileRange, + defs::Definition, + search::{FileReference, ReferenceCategory, SearchScope}, + RootDatabase, +}; +use syntax::{ast, AstNode}; +use text_edit::TextRange; + +use crate::{AssistContext, AssistId, AssistKind, Assists}; + +// Assist: remove_unused_imports +// +// Removes any use statements in the current selection that are unused. +// +// ``` +// struct X(); +// mod foo { +// use super::X$0; +// } +// ``` +// -> +// ``` +// struct X(); +// mod foo { +// } +// ``` +pub(crate) fn remove_unused_imports(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { + // First, grab the uses that intersect with the current selection. + let selected_el = match ctx.covering_element() { + syntax::NodeOrToken::Node(n) => n, + syntax::NodeOrToken::Token(t) => t.parent()?, + }; + + // This applies to all uses that are selected, or are ancestors of our selection. + let uses_up = selected_el.ancestors().skip(1).filter_map(ast::Use::cast); + let uses_down = selected_el + .descendants() + .filter(|x| x.text_range().intersect(ctx.selection_trimmed()).is_some()) + .filter_map(ast::Use::cast); + let uses = uses_up.chain(uses_down).collect::>(); + + // Maps use nodes to the scope that we should search through to find + let mut search_scopes = HashMap::>::new(); + + // iterator over all unused use trees + let mut unused = uses + .into_iter() + .flat_map(|u| u.syntax().descendants().filter_map(ast::UseTree::cast)) + .filter(|u| u.use_tree_list().is_none()) + .filter_map(|u| { + // Find any uses trees that are unused + + let use_module = ctx.sema.scope(&u.syntax()).map(|s| s.module())?; + let scope = match search_scopes.entry(use_module) { + Entry::Occupied(o) => o.into_mut(), + Entry::Vacant(v) => v.insert(module_search_scope(ctx.db(), use_module)), + }; + + // Gets the path associated with this use tree. If there isn't one, then ignore this use tree. + let path = if let Some(path) = u.path() { + path + } else if u.star_token().is_some() { + // This case maps to the situation where the * token is braced. + // In this case, the parent use tree's path is the one we should use to resolve the glob. + match u.syntax().ancestors().skip(1).find_map(ast::UseTree::cast) { + Some(parent_u) if parent_u.path().is_some() => parent_u.path().unwrap(), + _ => return None, + } + } else { + return None; + }; + + // Get the actual definition associated with this use item. + let res = match ctx.sema.resolve_path(&path) { + Some(x) => x, + None => { + return None; + } + }; + + let def = match res { + hir::PathResolution::Def(d) => Definition::from(d), + _ => return None, + }; + + if u.star_token().is_some() { + // Check if any of the children of this module are used + let def_mod = match def { + Definition::Module(module) => module, + _ => return None, + }; + + if !def_mod + .scope(ctx.db(), Some(use_module)) + .iter() + .filter_map(|(_, x)| match x { + hir::ScopeDef::ModuleDef(d) => Some(Definition::from(*d)), + _ => None, + }) + .any(|d| used_once_in_scope(ctx, d, scope)) + { + return Some(u); + } + } else if let Definition::Trait(ref t) = def { + // If the trait or any item is used. + if !std::iter::once(def) + .chain(t.items(ctx.db()).into_iter().map(Definition::from)) + .any(|d| used_once_in_scope(ctx, d, scope)) + { + return Some(u); + } + } else { + if !used_once_in_scope(ctx, def, &scope) { + return Some(u); + } + } + + None + }) + .peekable(); + + // Peek so we terminate early if an unused use is found. Only do the rest of the work if the user selects the assist. + if unused.peek().is_some() { + acc.add( + AssistId("remove_unused_imports", AssistKind::QuickFix), + "Remove all the unused imports", + selected_el.text_range(), + |builder| { + let unused: Vec = unused.map(|x| builder.make_mut(x)).collect(); + for node in unused { + node.remove_recursive(); + } + }, + ) + } else { + None + } +} + +fn used_once_in_scope(ctx: &AssistContext<'_>, def: Definition, scopes: &Vec) -> bool { + let mut found = false; + + for scope in scopes { + let mut search_non_import = |_, r: FileReference| { + // The import itself is a use; we must skip that. + if r.category != Some(ReferenceCategory::Import) { + found = true; + true + } else { + false + } + }; + def.usages(&ctx.sema).in_scope(scope).search(&mut search_non_import); + if found { + break; + } + } + + found +} + +/// Build a search scope spanning the given module but none of its submodules. +fn module_search_scope(db: &RootDatabase, module: hir::Module) -> Vec { + let (file_id, range) = { + let InFile { file_id, value } = module.definition_source(db); + if let Some((file_id, call_source)) = file_id.original_call_node(db) { + (file_id, Some(call_source.text_range())) + } else { + ( + file_id.original_file(db), + match value { + ModuleSource::SourceFile(_) => None, + ModuleSource::Module(it) => Some(it.syntax().text_range()), + ModuleSource::BlockExpr(it) => Some(it.syntax().text_range()), + }, + ) + } + }; + + fn split_at_subrange(first: TextRange, second: TextRange) -> (TextRange, Option) { + let intersect = first.intersect(second); + if let Some(intersect) = intersect { + let start_range = TextRange::new(first.start(), intersect.start()); + + if intersect.end() < first.end() { + (start_range, Some(TextRange::new(intersect.end(), first.end()))) + } else { + (start_range, None) + } + } else { + (first, None) + } + } + + let mut scopes = Vec::new(); + if let Some(range) = range { + let mut ranges = vec![range]; + + for child in module.children(db) { + let rng = match child.definition_source(db).value { + ModuleSource::SourceFile(_) => continue, + ModuleSource::Module(it) => it.syntax().text_range(), + ModuleSource::BlockExpr(_) => continue, + }; + let mut new_ranges = Vec::new(); + for old_range in ranges.iter_mut() { + let split = split_at_subrange(old_range.clone(), rng); + *old_range = split.0; + new_ranges.extend(split.1); + } + + ranges.append(&mut new_ranges); + } + + for range in ranges { + scopes.push(SearchScope::file_range(FileRange { file_id, range })); + } + } else { + scopes.push(SearchScope::single_file(file_id)); + } + + scopes +} + +#[cfg(test)] +mod tests { + use crate::tests::{check_assist, check_assist_not_applicable}; + + use super::*; + + #[test] + fn remove_unused() { + check_assist( + remove_unused_imports, + r#" +struct X(); +struct Y(); +mod z { + $0use super::X; + use super::Y;$0 +} +"#, + r#" +struct X(); +struct Y(); +mod z { +} +"#, + ); + } + + #[test] + fn remove_unused_is_precise() { + check_assist( + remove_unused_imports, + r#" +struct X(); +mod z { +$0use super::X;$0 + +fn w() { + struct X(); + let x = X(); +} +} +"#, + r#" +struct X(); +mod z { + +fn w() { + struct X(); + let x = X(); +} +} +"#, + ); + } + + #[test] + fn trait_name_use_is_use() { + check_assist_not_applicable( + remove_unused_imports, + r#" +struct X(); +trait Y { + fn f(); +} + +impl Y for X { + fn f() {} +} +mod z { +$0use super::X; +use super::Y;$0 + +fn w() { + X::f(); +} +} +"#, + ); + } + + #[test] + fn trait_item_use_is_use() { + check_assist_not_applicable( + remove_unused_imports, + r#" +struct X(); +trait Y { + fn f(self); +} + +impl Y for X { + fn f(self) {} +} +mod z { +$0use super::X; +use super::Y;$0 + +fn w() { + let x = X(); + x.f(); +} +} +"#, + ); + } + + #[test] + fn ranamed_trait_item_use_is_use() { + check_assist_not_applicable( + remove_unused_imports, + r#" +struct X(); +trait Y { + fn f(self); +} + +impl Y for X { + fn f(self) {} +} +mod z { +$0use super::X; +use super::Y as Z;$0 + +fn w() { + let x = X(); + x.f(); +} +} +"#, + ); + } + + #[test] + fn ranamed_underscore_trait_item_use_is_use() { + check_assist_not_applicable( + remove_unused_imports, + r#" +struct X(); +trait Y { + fn f(self); +} + +impl Y for X { + fn f(self) {} +} +mod z { +$0use super::X; +use super::Y as _;$0 + +fn w() { + let x = X(); + x.f(); +} +} +"#, + ); + } + + #[test] + fn dont_remove_used() { + check_assist_not_applicable( + remove_unused_imports, + r#" +struct X(); +struct Y(); +mod z { +$0use super::X; +use super::Y;$0 + +fn w() { + let x = X(); + let y = Y(); +} +} +"#, + ); + } + + #[test] + fn remove_unused_in_braces() { + check_assist( + remove_unused_imports, + r#" +struct X(); +struct Y(); +mod z { + $0use super::{X, Y};$0 + + fn w() { + let x = X(); + } +} +"#, + r#" +struct X(); +struct Y(); +mod z { + use super::{X}; + + fn w() { + let x = X(); + } +} +"#, + ); + } + + #[test] + fn remove_unused_under_cursor() { + check_assist( + remove_unused_imports, + r#" +struct X(); +mod z { + use super::X$0; +} +"#, + r#" +struct X(); +mod z { +} +"#, + ); + } + + #[test] + fn remove_multi_use_block() { + check_assist( + remove_unused_imports, + r#" +struct X(); +$0mod y { + use super::X; +} +mod z { + use super::X; +}$0 +"#, + r#" +struct X(); +mod y { +} +mod z { +} +"#, + ); + } + + #[test] + fn remove_nested() { + check_assist( + remove_unused_imports, + r#" +struct X(); +mod y { + struct Y(); + mod z { + use crate::{X, y::Y}$0; + fn f() { + let x = X(); + } + } +} +"#, + r#" +struct X(); +mod y { + struct Y(); + mod z { + use crate::{X}; + fn f() { + let x = X(); + } + } +} +"#, + ); + } + + #[test] + fn remove_nested_first_item() { + check_assist( + remove_unused_imports, + r#" +struct X(); +mod y { + struct Y(); + mod z { + use crate::{X, y::Y}$0; + fn f() { + let y = Y(); + } + } +} +"#, + r#" +struct X(); +mod y { + struct Y(); + mod z { + use crate::{y::Y}; + fn f() { + let y = Y(); + } + } +} +"#, + ); + } + + #[test] + fn remove_nested_all_unused() { + check_assist( + remove_unused_imports, + r#" +struct X(); +mod y { + struct Y(); + mod z { + use crate::{X, y::Y}$0; + } +} +"#, + r#" +struct X(); +mod y { + struct Y(); + mod z { + } +} +"#, + ); + } + + #[test] + fn remove_unused_glob() { + check_assist( + remove_unused_imports, + r#" +struct X(); +struct Y(); +mod z { + use super::*$0; +} +"#, + r#" +struct X(); +struct Y(); +mod z { +} +"#, + ); + } + + #[test] + fn remove_unused_braced_glob() { + check_assist( + remove_unused_imports, + r#" +struct X(); +struct Y(); +mod z { + use super::{*}$0; +} +"#, + r#" +struct X(); +struct Y(); +mod z { +} +"#, + ); + } + + #[test] + fn dont_remove_used_glob() { + check_assist_not_applicable( + remove_unused_imports, + r#" +struct X(); +struct Y(); +mod z { + use super::*$0; + + fn f() { + let x = X(); + } +} +"#, + ); + } + + #[test] + fn only_remove_from_selection() { + check_assist( + remove_unused_imports, + r#" +struct X(); +struct Y(); +mod z { + $0use super::X;$0 + use super::Y; +} +mod w { + use super::Y; +} +"#, + r#" +struct X(); +struct Y(); +mod z { + use super::Y; +} +mod w { + use super::Y; +} +"#, + ); + } + + #[test] + fn test_several_files() { + check_assist( + remove_unused_imports, + r#" +//- /foo.rs +pub struct X(); +pub struct Y(); + +//- /main.rs +$0use foo::X; +use foo::Y; +$0 +mod foo; +mod z { + use crate::foo::X; +} +"#, + r#" + +mod foo; +mod z { + use crate::foo::X; +} +"#, + ); + } + + #[test] + fn use_in_submodule_doesnt_count() { + check_assist( + remove_unused_imports, + r#" +struct X(); +mod z { + use super::X$0; + + mod w { + use crate::X; + + fn f() { + let x = X(); + } + } +} +"#, + r#" +struct X(); +mod z { + + mod w { + use crate::X; + + fn f() { + let x = X(); + } + } +} +"#, + ); + } + + #[test] + fn use_in_submodule_file_doesnt_count() { + check_assist( + remove_unused_imports, + r#" +//- /z/foo.rs +use crate::X; +fn f() { + let x = X(); +} + +//- /main.rs +pub struct X(); + +mod z { + use crate::X$0; + mod foo; +} +"#, + r#" +pub struct X(); + +mod z { + mod foo; +} +"#, + ); + } +} diff --git a/crates/ide-assists/src/lib.rs b/crates/ide-assists/src/lib.rs index a82f1f9dd8bec..2ebb5ef9b1906 100644 --- a/crates/ide-assists/src/lib.rs +++ b/crates/ide-assists/src/lib.rs @@ -184,6 +184,7 @@ mod handlers { mod raw_string; mod remove_dbg; mod remove_mut; + mod remove_unused_imports; mod remove_unused_param; mod remove_parentheses; mod reorder_fields; @@ -294,6 +295,7 @@ mod handlers { raw_string::make_usual_string, raw_string::remove_hash, remove_mut::remove_mut, + remove_unused_imports::remove_unused_imports, remove_unused_param::remove_unused_param, remove_parentheses::remove_parentheses, reorder_fields::reorder_fields, diff --git a/crates/ide-assists/src/tests/generated.rs b/crates/ide-assists/src/tests/generated.rs index 4d47a199b7c21..ec3822c3d119a 100644 --- a/crates/ide-assists/src/tests/generated.rs +++ b/crates/ide-assists/src/tests/generated.rs @@ -2233,6 +2233,24 @@ fn main() { ) } +#[test] +fn doctest_remove_unused_imports() { + check_doc_test( + "remove_unused_imports", + r#####" +struct X(); +mod foo { + use super::X$0; +} +"#####, + r#####" +struct X(); +mod foo { +} +"#####, + ) +} + #[test] fn doctest_remove_unused_param() { check_doc_test( diff --git a/crates/syntax/src/ast/edit_in_place.rs b/crates/syntax/src/ast/edit_in_place.rs index 5919609d91ea1..96b438f1a0db3 100644 --- a/crates/syntax/src/ast/edit_in_place.rs +++ b/crates/syntax/src/ast/edit_in_place.rs @@ -358,6 +358,26 @@ impl Removable for ast::UseTree { } impl ast::UseTree { + /// Deletes the usetree node represented by the input. Recursively removes parents, including use nodes that become empty. + pub fn remove_recursive(self) { + let parent = self.syntax().parent(); + + self.remove(); + + if let Some(u) = parent.clone().and_then(ast::Use::cast) { + if u.use_tree().is_none() { + u.remove(); + } + } else if let Some(u) = parent.and_then(ast::UseTreeList::cast) { + if u.use_trees().next().is_none() { + let parent = u.syntax().parent().and_then(ast::UseTree::cast); + if let Some(u) = parent { + u.remove_recursive(); + } + } + } + } + pub fn get_or_create_use_tree_list(&self) -> ast::UseTreeList { match self.use_tree_list() { Some(it) => it, @@ -465,6 +485,22 @@ impl Removable for ast::Use { } } } + let prev_ws = self + .syntax() + .prev_sibling_or_token() + .and_then(|it| it.into_token()) + .and_then(ast::Whitespace::cast); + if let Some(prev_ws) = prev_ws { + let ws_text = prev_ws.syntax().text(); + let prev_newline = ws_text.rfind('\n').map(|x| x + 1).unwrap_or(0); + let rest = &ws_text[0..prev_newline]; + if rest.is_empty() { + ted::remove(prev_ws.syntax()); + } else { + ted::replace(prev_ws.syntax(), make::tokens::whitespace(rest)); + } + } + ted::remove(self.syntax()); } } From 89f7bf74112a7153b706127484f0ddeb392fc6cc Mon Sep 17 00:00:00 2001 From: DropDemBits Date: Wed, 12 Jul 2023 00:36:06 -0400 Subject: [PATCH 07/59] Add `SnippetEdit` to be alongside source changes Rendering of snippet edits is deferred to places using source change --- crates/ide-assists/src/tests.rs | 83 ++++---- crates/ide-db/src/source_change.rs | 137 +++++++++++- crates/ide-diagnostics/src/tests.rs | 5 +- crates/ide/src/rename.rs | 208 +++++++++++-------- crates/ide/src/ssr.rs | 57 ++--- crates/rust-analyzer/src/handlers/request.rs | 3 +- crates/rust-analyzer/src/to_proto.rs | 2 +- 7 files changed, 335 insertions(+), 160 deletions(-) diff --git a/crates/ide-assists/src/tests.rs b/crates/ide-assists/src/tests.rs index 344f2bfcce14a..00cea0e76c670 100644 --- a/crates/ide-assists/src/tests.rs +++ b/crates/ide-assists/src/tests.rs @@ -191,7 +191,7 @@ fn check_with_config( && source_change.file_system_edits.len() == 0; let mut buf = String::new(); - for (file_id, edit) in source_change.source_file_edits { + for (file_id, (edit, _snippet_edit)) in source_change.source_file_edits { let mut text = db.file_text(file_id).as_ref().to_owned(); edit.apply(&mut text); if !skip_header { @@ -485,18 +485,21 @@ pub fn test_some_range(a: int) -> bool { source_file_edits: { FileId( 0, - ): TextEdit { - indels: [ - Indel { - insert: "let $0var_name = 5;\n ", - delete: 45..45, - }, - Indel { - insert: "var_name", - delete: 59..60, - }, - ], - }, + ): ( + TextEdit { + indels: [ + Indel { + insert: "let $0var_name = 5;\n ", + delete: 45..45, + }, + Indel { + insert: "var_name", + delete: 59..60, + }, + ], + }, + None, + ), }, file_system_edits: [], is_snippet: true, @@ -544,18 +547,21 @@ pub fn test_some_range(a: int) -> bool { source_file_edits: { FileId( 0, - ): TextEdit { - indels: [ - Indel { - insert: "let $0var_name = 5;\n ", - delete: 45..45, - }, - Indel { - insert: "var_name", - delete: 59..60, - }, - ], - }, + ): ( + TextEdit { + indels: [ + Indel { + insert: "let $0var_name = 5;\n ", + delete: 45..45, + }, + Indel { + insert: "var_name", + delete: 59..60, + }, + ], + }, + None, + ), }, file_system_edits: [], is_snippet: true, @@ -581,18 +587,21 @@ pub fn test_some_range(a: int) -> bool { source_file_edits: { FileId( 0, - ): TextEdit { - indels: [ - Indel { - insert: "fun_name()", - delete: 59..60, - }, - Indel { - insert: "\n\nfn $0fun_name() -> i32 {\n 5\n}", - delete: 110..110, - }, - ], - }, + ): ( + TextEdit { + indels: [ + Indel { + insert: "fun_name()", + delete: 59..60, + }, + Indel { + insert: "\n\nfn $0fun_name() -> i32 {\n 5\n}", + delete: 110..110, + }, + ], + }, + None, + ), }, file_system_edits: [], is_snippet: true, diff --git a/crates/ide-db/src/source_change.rs b/crates/ide-db/src/source_change.rs index fad0ca51a0257..596f28e98167f 100644 --- a/crates/ide-db/src/source_change.rs +++ b/crates/ide-db/src/source_change.rs @@ -7,6 +7,7 @@ use std::{collections::hash_map::Entry, iter, mem}; use crate::SnippetCap; use base_db::{AnchoredPathBuf, FileId}; +use itertools::Itertools; use nohash_hasher::IntMap; use stdx::never; use syntax::{ @@ -17,7 +18,7 @@ use text_edit::{TextEdit, TextEditBuilder}; #[derive(Default, Debug, Clone)] pub struct SourceChange { - pub source_file_edits: IntMap, + pub source_file_edits: IntMap)>, pub file_system_edits: Vec, pub is_snippet: bool, } @@ -26,7 +27,7 @@ impl SourceChange { /// Creates a new SourceChange with the given label /// from the edits. pub fn from_edits( - source_file_edits: IntMap, + source_file_edits: IntMap)>, file_system_edits: Vec, ) -> Self { SourceChange { source_file_edits, file_system_edits, is_snippet: false } @@ -34,7 +35,7 @@ impl SourceChange { pub fn from_text_edit(file_id: FileId, edit: TextEdit) -> Self { SourceChange { - source_file_edits: iter::once((file_id, edit)).collect(), + source_file_edits: iter::once((file_id, (edit, None))).collect(), ..Default::default() } } @@ -42,12 +43,31 @@ impl SourceChange { /// Inserts a [`TextEdit`] for the given [`FileId`]. This properly handles merging existing /// edits for a file if some already exist. pub fn insert_source_edit(&mut self, file_id: FileId, edit: TextEdit) { + self.insert_source_and_snippet_edit(file_id, edit, None) + } + + /// Inserts a [`TextEdit`] and potentially a [`SnippetEdit`] for the given [`FileId`]. + /// This properly handles merging existing edits for a file if some already exist. + pub fn insert_source_and_snippet_edit( + &mut self, + file_id: FileId, + edit: TextEdit, + snippet_edit: Option, + ) { match self.source_file_edits.entry(file_id) { Entry::Occupied(mut entry) => { - never!(entry.get_mut().union(edit).is_err(), "overlapping edits for same file"); + let value = entry.get_mut(); + never!(value.0.union(edit).is_err(), "overlapping edits for same file"); + never!( + value.1.is_some() && snippet_edit.is_some(), + "overlapping snippet edits for same file" + ); + if value.1.is_none() { + value.1 = snippet_edit; + } } Entry::Vacant(entry) => { - entry.insert(edit); + entry.insert((edit, snippet_edit)); } } } @@ -57,7 +77,7 @@ impl SourceChange { } pub fn get_source_edit(&self, file_id: FileId) -> Option<&TextEdit> { - self.source_file_edits.get(&file_id) + self.source_file_edits.get(&file_id).map(|(edit, _)| edit) } pub fn merge(mut self, other: SourceChange) -> SourceChange { @@ -70,7 +90,18 @@ impl SourceChange { impl Extend<(FileId, TextEdit)> for SourceChange { fn extend>(&mut self, iter: T) { - iter.into_iter().for_each(|(file_id, edit)| self.insert_source_edit(file_id, edit)); + self.extend(iter.into_iter().map(|(file_id, edit)| (file_id, (edit, None)))) + } +} + +impl Extend<(FileId, (TextEdit, Option))> for SourceChange { + fn extend))>>( + &mut self, + iter: T, + ) { + iter.into_iter().for_each(|(file_id, (edit, snippet_edit))| { + self.insert_source_and_snippet_edit(file_id, edit, snippet_edit) + }); } } @@ -82,6 +113,14 @@ impl Extend for SourceChange { impl From> for SourceChange { fn from(source_file_edits: IntMap) -> SourceChange { + let source_file_edits = + source_file_edits.into_iter().map(|(file_id, edit)| (file_id, (edit, None))).collect(); + SourceChange { source_file_edits, file_system_edits: Vec::new(), is_snippet: false } + } +} + +impl From)>> for SourceChange { + fn from(source_file_edits: IntMap)>) -> SourceChange { SourceChange { source_file_edits, file_system_edits: Vec::new(), is_snippet: false } } } @@ -94,6 +133,73 @@ impl FromIterator<(FileId, TextEdit)> for SourceChange { } } +impl FromIterator<(FileId, (TextEdit, Option))> for SourceChange { + fn from_iter))>>( + iter: T, + ) -> Self { + let mut this = SourceChange::default(); + this.extend(iter); + this + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct SnippetEdit(Vec<(u32, TextRange)>); + +impl SnippetEdit { + fn new(snippets: Vec) -> Self { + let mut snippet_ranges = snippets + .into_iter() + .zip(1..) + .with_position() + .map(|pos| { + let (snippet, index) = match pos { + itertools::Position::First(it) | itertools::Position::Middle(it) => it, + // last/only snippet gets index 0 + itertools::Position::Last((snippet, _)) + | itertools::Position::Only((snippet, _)) => (snippet, 0), + }; + + let range = match snippet { + Snippet::Tabstop(pos) => TextRange::empty(pos), + Snippet::Placeholder(range) => range, + }; + (index, range) + }) + .collect_vec(); + + snippet_ranges.sort_by_key(|(_, range)| range.start()); + + // Ensure that none of the ranges overlap + let disjoint_ranges = + snippet_ranges.windows(2).all(|ranges| ranges[0].1.end() <= ranges[1].1.start()); + stdx::always!(disjoint_ranges); + + SnippetEdit(snippet_ranges) + } + + /// Inserts all of the snippets into the given text. + pub fn apply(&self, text: &mut String) { + // Start from the back so that we don't have to adjust ranges + for (index, range) in self.0.iter().rev() { + if range.is_empty() { + // is a tabstop + text.insert_str(range.start().into(), &format!("${index}")); + } else { + // is a placeholder + text.insert(range.end().into(), '}'); + text.insert_str(range.start().into(), &format!("${{{index}:")); + } + } + } + + /// Gets the underlying snippet index + text range + /// Tabstops are represented by an empty range, and placeholders use the range that they were given + pub fn into_edit_ranges(self) -> Vec<(u32, TextRange)> { + self.0 + } +} + pub struct SourceChangeBuilder { pub edit: TextEditBuilder, pub file_id: FileId, @@ -275,6 +381,16 @@ impl SourceChangeBuilder { pub fn finish(mut self) -> SourceChange { self.commit(); + + // Only one file can have snippet edits + stdx::never!(self + .source_change + .source_file_edits + .iter() + .filter(|(_, (_, snippet_edit))| snippet_edit.is_some()) + .at_most_one() + .is_err()); + mem::take(&mut self.source_change) } } @@ -296,6 +412,13 @@ impl From for SourceChange { } } +enum Snippet { + /// A tabstop snippet (e.g. `$0`). + Tabstop(TextSize), + /// A placeholder snippet (e.g. `${0:placeholder}`). + Placeholder(TextRange), +} + enum PlaceSnippet { /// Place a tabstop before an element Before(SyntaxElement), diff --git a/crates/ide-diagnostics/src/tests.rs b/crates/ide-diagnostics/src/tests.rs index 4ac9d0a9fb73c..ee0e0354906ed 100644 --- a/crates/ide-diagnostics/src/tests.rs +++ b/crates/ide-diagnostics/src/tests.rs @@ -49,8 +49,11 @@ fn check_nth_fix(nth: usize, ra_fixture_before: &str, ra_fixture_after: &str) { let file_id = *source_change.source_file_edits.keys().next().unwrap(); let mut actual = db.file_text(file_id).to_string(); - for edit in source_change.source_file_edits.values() { + for (edit, snippet_edit) in source_change.source_file_edits.values() { edit.apply(&mut actual); + if let Some(snippet_edit) = snippet_edit { + snippet_edit.apply(&mut actual); + } } actual }; diff --git a/crates/ide/src/rename.rs b/crates/ide/src/rename.rs index e10c463810220..5c4beb7dd500f 100644 --- a/crates/ide/src/rename.rs +++ b/crates/ide/src/rename.rs @@ -367,7 +367,7 @@ mod tests { let mut file_id: Option = None; for edit in source_change.source_file_edits { file_id = Some(edit.0); - for indel in edit.1.into_iter() { + for indel in edit.1 .0.into_iter() { text_edit_builder.replace(indel.delete, indel.insert); } } @@ -895,14 +895,17 @@ mod foo$0; source_file_edits: { FileId( 1, - ): TextEdit { - indels: [ - Indel { - insert: "foo2", - delete: 4..7, - }, - ], - }, + ): ( + TextEdit { + indels: [ + Indel { + insert: "foo2", + delete: 4..7, + }, + ], + }, + None, + ), }, file_system_edits: [ MoveFile { @@ -944,24 +947,30 @@ use crate::foo$0::FooContent; source_file_edits: { FileId( 0, - ): TextEdit { - indels: [ - Indel { - insert: "quux", - delete: 8..11, - }, - ], - }, + ): ( + TextEdit { + indels: [ + Indel { + insert: "quux", + delete: 8..11, + }, + ], + }, + None, + ), FileId( 2, - ): TextEdit { - indels: [ - Indel { - insert: "quux", - delete: 11..14, - }, - ], - }, + ): ( + TextEdit { + indels: [ + Indel { + insert: "quux", + delete: 11..14, + }, + ], + }, + None, + ), }, file_system_edits: [ MoveFile { @@ -997,14 +1006,17 @@ mod fo$0o; source_file_edits: { FileId( 0, - ): TextEdit { - indels: [ - Indel { - insert: "foo2", - delete: 4..7, - }, - ], - }, + ): ( + TextEdit { + indels: [ + Indel { + insert: "foo2", + delete: 4..7, + }, + ], + }, + None, + ), }, file_system_edits: [ MoveDir { @@ -1047,14 +1059,17 @@ mod outer { mod fo$0o; } source_file_edits: { FileId( 0, - ): TextEdit { - indels: [ - Indel { - insert: "bar", - delete: 16..19, - }, - ], - }, + ): ( + TextEdit { + indels: [ + Indel { + insert: "bar", + delete: 16..19, + }, + ], + }, + None, + ), }, file_system_edits: [ MoveFile { @@ -1120,24 +1135,30 @@ pub mod foo$0; source_file_edits: { FileId( 0, - ): TextEdit { - indels: [ - Indel { - insert: "foo2", - delete: 27..30, - }, - ], - }, + ): ( + TextEdit { + indels: [ + Indel { + insert: "foo2", + delete: 27..30, + }, + ], + }, + None, + ), FileId( 1, - ): TextEdit { - indels: [ - Indel { - insert: "foo2", - delete: 8..11, - }, - ], - }, + ): ( + TextEdit { + indels: [ + Indel { + insert: "foo2", + delete: 8..11, + }, + ], + }, + None, + ), }, file_system_edits: [ MoveFile { @@ -1187,14 +1208,17 @@ mod quux; source_file_edits: { FileId( 0, - ): TextEdit { - indels: [ - Indel { - insert: "foo2", - delete: 4..7, - }, - ], - }, + ): ( + TextEdit { + indels: [ + Indel { + insert: "foo2", + delete: 4..7, + }, + ], + }, + None, + ), }, file_system_edits: [ MoveFile { @@ -1325,18 +1349,21 @@ pub fn baz() {} source_file_edits: { FileId( 0, - ): TextEdit { - indels: [ - Indel { - insert: "r#fn", - delete: 4..7, - }, - Indel { - insert: "r#fn", - delete: 22..25, - }, - ], - }, + ): ( + TextEdit { + indels: [ + Indel { + insert: "r#fn", + delete: 4..7, + }, + Indel { + insert: "r#fn", + delete: 22..25, + }, + ], + }, + None, + ), }, file_system_edits: [ MoveFile { @@ -1395,18 +1422,21 @@ pub fn baz() {} source_file_edits: { FileId( 0, - ): TextEdit { - indels: [ - Indel { - insert: "foo", - delete: 4..8, - }, - Indel { - insert: "foo", - delete: 23..27, - }, - ], - }, + ): ( + TextEdit { + indels: [ + Indel { + insert: "foo", + delete: 4..8, + }, + Indel { + insert: "foo", + delete: 23..27, + }, + ], + }, + None, + ), }, file_system_edits: [ MoveFile { diff --git a/crates/ide/src/ssr.rs b/crates/ide/src/ssr.rs index deaf3c9c416b4..d8d81869a2f81 100644 --- a/crates/ide/src/ssr.rs +++ b/crates/ide/src/ssr.rs @@ -126,14 +126,17 @@ mod tests { source_file_edits: { FileId( 0, - ): TextEdit { - indels: [ - Indel { - insert: "3", - delete: 33..34, - }, - ], - }, + ): ( + TextEdit { + indels: [ + Indel { + insert: "3", + delete: 33..34, + }, + ], + }, + None, + ), }, file_system_edits: [], is_snippet: false, @@ -163,24 +166,30 @@ mod tests { source_file_edits: { FileId( 0, - ): TextEdit { - indels: [ - Indel { - insert: "3", - delete: 33..34, - }, - ], - }, + ): ( + TextEdit { + indels: [ + Indel { + insert: "3", + delete: 33..34, + }, + ], + }, + None, + ), FileId( 1, - ): TextEdit { - indels: [ - Indel { - insert: "3", - delete: 11..12, - }, - ], - }, + ): ( + TextEdit { + indels: [ + Indel { + insert: "3", + delete: 11..12, + }, + ], + }, + None, + ), }, file_system_edits: [], is_snippet: false, diff --git a/crates/rust-analyzer/src/handlers/request.rs b/crates/rust-analyzer/src/handlers/request.rs index aad74b7466a27..5f1f731cffb39 100644 --- a/crates/rust-analyzer/src/handlers/request.rs +++ b/crates/rust-analyzer/src/handlers/request.rs @@ -353,7 +353,8 @@ pub(crate) fn handle_on_type_formatting( }; // This should be a single-file edit - let (_, text_edit) = edit.source_file_edits.into_iter().next().unwrap(); + let (_, (text_edit, snippet_edit)) = edit.source_file_edits.into_iter().next().unwrap(); + stdx::never!(snippet_edit.is_none(), "on type formatting shouldn't use structured snippets"); let change = to_proto::snippet_text_edit_vec(&line_index, edit.is_snippet, text_edit); Ok(Some(change)) diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs index ba3421bf9e76a..0022b7a8674eb 100644 --- a/crates/rust-analyzer/src/to_proto.rs +++ b/crates/rust-analyzer/src/to_proto.rs @@ -973,7 +973,7 @@ pub(crate) fn snippet_workspace_edit( let ops = snippet_text_document_ops(snap, op)?; document_changes.extend_from_slice(&ops); } - for (file_id, edit) in source_change.source_file_edits { + for (file_id, (edit, _snippet_edit)) in source_change.source_file_edits { let edit = snippet_text_document_edit(snap, source_change.is_snippet, file_id, edit)?; document_changes.push(lsp_ext::SnippetDocumentChangeOperation::Edit(edit)); } From 97a6fa58cdc5ec113e49c81bf407a71ebdfabc99 Mon Sep 17 00:00:00 2001 From: DropDemBits Date: Wed, 12 Jul 2023 01:50:35 -0400 Subject: [PATCH 08/59] internal: Defer rendering of structured snippets This ensures that any assist using structured snippets won't accidentally remove bits interpreted as snippet bits. --- crates/ide-assists/src/tests.rs | 12 ++- crates/ide-db/src/source_change.rs | 85 ++++------------ crates/ide/src/lib.rs | 2 +- crates/rust-analyzer/src/to_proto.rs | 140 +++++++++++++++++++++++++-- 4 files changed, 161 insertions(+), 78 deletions(-) diff --git a/crates/ide-assists/src/tests.rs b/crates/ide-assists/src/tests.rs index 00cea0e76c670..cc3e251a8f2a6 100644 --- a/crates/ide-assists/src/tests.rs +++ b/crates/ide-assists/src/tests.rs @@ -132,8 +132,13 @@ fn check_doc_test(assist_id: &str, before: &str, after: &str) { .filter(|it| !it.source_file_edits.is_empty() || !it.file_system_edits.is_empty()) .expect("Assist did not contain any source changes"); let mut actual = before; - if let Some(source_file_edit) = source_change.get_source_edit(file_id) { + if let Some((source_file_edit, snippet_edit)) = + source_change.get_source_and_snippet_edit(file_id) + { source_file_edit.apply(&mut actual); + if let Some(snippet_edit) = snippet_edit { + snippet_edit.apply(&mut actual); + } } actual }; @@ -191,9 +196,12 @@ fn check_with_config( && source_change.file_system_edits.len() == 0; let mut buf = String::new(); - for (file_id, (edit, _snippet_edit)) in source_change.source_file_edits { + for (file_id, (edit, snippet_edit)) in source_change.source_file_edits { let mut text = db.file_text(file_id).as_ref().to_owned(); edit.apply(&mut text); + if let Some(snippet_edit) = snippet_edit { + snippet_edit.apply(&mut text); + } if !skip_header { let sr = db.file_source_root(file_id); let sr = db.source_root(sr); diff --git a/crates/ide-db/src/source_change.rs b/crates/ide-db/src/source_change.rs index 596f28e98167f..3ff56ae9027bc 100644 --- a/crates/ide-db/src/source_change.rs +++ b/crates/ide-db/src/source_change.rs @@ -11,8 +11,7 @@ use itertools::Itertools; use nohash_hasher::IntMap; use stdx::never; use syntax::{ - algo, ast, ted, AstNode, SyntaxElement, SyntaxNode, SyntaxNodePtr, SyntaxToken, TextRange, - TextSize, + algo, AstNode, SyntaxElement, SyntaxNode, SyntaxNodePtr, SyntaxToken, TextRange, TextSize, }; use text_edit::{TextEdit, TextEditBuilder}; @@ -76,8 +75,11 @@ impl SourceChange { self.file_system_edits.push(edit); } - pub fn get_source_edit(&self, file_id: FileId) -> Option<&TextEdit> { - self.source_file_edits.get(&file_id).map(|(edit, _)| edit) + pub fn get_source_and_snippet_edit( + &self, + file_id: FileId, + ) -> Option<&(TextEdit, Option)> { + self.source_file_edits.get(&file_id) } pub fn merge(mut self, other: SourceChange) -> SourceChange { @@ -258,24 +260,19 @@ impl SourceChangeBuilder { } fn commit(&mut self) { - // Render snippets first so that they get bundled into the tree diff - if let Some(mut snippets) = self.snippet_builder.take() { - // Last snippet always has stop index 0 - let last_stop = snippets.places.pop().unwrap(); - last_stop.place(0); - - for (index, stop) in snippets.places.into_iter().enumerate() { - stop.place(index + 1) - } - } + let snippet_edit = self.snippet_builder.take().map(|builder| { + SnippetEdit::new( + builder.places.into_iter().map(PlaceSnippet::finalize_position).collect_vec(), + ) + }); if let Some(tm) = self.mutated_tree.take() { - algo::diff(&tm.immutable, &tm.mutable_clone).into_text_edit(&mut self.edit) + algo::diff(&tm.immutable, &tm.mutable_clone).into_text_edit(&mut self.edit); } let edit = mem::take(&mut self.edit).finish(); - if !edit.is_empty() { - self.source_change.insert_source_edit(self.file_id, edit); + if !edit.is_empty() || snippet_edit.is_some() { + self.source_change.insert_source_and_snippet_edit(self.file_id, edit, snippet_edit); } } @@ -429,57 +426,11 @@ enum PlaceSnippet { } impl PlaceSnippet { - /// Places the snippet before or over an element with the given tab stop index - fn place(self, order: usize) { - // ensure the target element is still attached - match &self { - PlaceSnippet::Before(element) - | PlaceSnippet::After(element) - | PlaceSnippet::Over(element) => { - // element should still be in the tree, but if it isn't - // then it's okay to just ignore this place - if stdx::never!(element.parent().is_none()) { - return; - } - } - } - + fn finalize_position(self) -> Snippet { match self { - PlaceSnippet::Before(element) => { - ted::insert_raw(ted::Position::before(&element), Self::make_tab_stop(order)); - } - PlaceSnippet::After(element) => { - ted::insert_raw(ted::Position::after(&element), Self::make_tab_stop(order)); - } - PlaceSnippet::Over(element) => { - let position = ted::Position::before(&element); - element.detach(); - - let snippet = ast::SourceFile::parse(&format!("${{{order}:_}}")) - .syntax_node() - .clone_for_update(); - - let placeholder = - snippet.descendants().find_map(ast::UnderscoreExpr::cast).unwrap(); - ted::replace(placeholder.syntax(), element); - - ted::insert_raw(position, snippet); - } + PlaceSnippet::Before(it) => Snippet::Tabstop(it.text_range().start()), + PlaceSnippet::After(it) => Snippet::Tabstop(it.text_range().end()), + PlaceSnippet::Over(it) => Snippet::Placeholder(it.text_range()), } } - - fn make_tab_stop(order: usize) -> SyntaxNode { - let stop = ast::SourceFile::parse(&format!("stop!(${order})")) - .syntax_node() - .descendants() - .find_map(ast::TokenTree::cast) - .unwrap() - .syntax() - .clone_for_update(); - - stop.first_token().unwrap().detach(); - stop.last_token().unwrap().detach(); - - stop - } } diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index 0ad4c6c47e625..bf77d55d58e57 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs @@ -127,7 +127,7 @@ pub use ide_db::{ label::Label, line_index::{LineCol, LineIndex}, search::{ReferenceCategory, SearchScope}, - source_change::{FileSystemEdit, SourceChange}, + source_change::{FileSystemEdit, SnippetEdit, SourceChange}, symbol_index::Query, RootDatabase, SymbolKind, }; diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs index 0022b7a8674eb..46ca7db2e16f1 100644 --- a/crates/rust-analyzer/src/to_proto.rs +++ b/crates/rust-analyzer/src/to_proto.rs @@ -10,8 +10,8 @@ use ide::{ CompletionItemKind, CompletionRelevance, Documentation, FileId, FileRange, FileSystemEdit, Fold, FoldKind, Highlight, HlMod, HlOperator, HlPunct, HlRange, HlTag, Indel, InlayHint, InlayHintLabel, InlayHintLabelPart, InlayKind, Markup, NavigationTarget, ReferenceCategory, - RenameError, Runnable, Severity, SignatureHelp, SourceChange, StructureNodeKind, SymbolKind, - TextEdit, TextRange, TextSize, + RenameError, Runnable, Severity, SignatureHelp, SnippetEdit, SourceChange, StructureNodeKind, + SymbolKind, TextEdit, TextRange, TextSize, }; use itertools::Itertools; use serde_json::to_value; @@ -22,7 +22,7 @@ use crate::{ config::{CallInfoConfig, Config}, global_state::GlobalStateSnapshot, line_index::{LineEndings, LineIndex, PositionEncoding}, - lsp_ext, + lsp_ext::{self, SnippetTextEdit}, lsp_utils::invalid_params_error, semantic_tokens::{self, standard_fallback_type}, }; @@ -884,16 +884,135 @@ fn outside_workspace_annotation_id() -> String { String::from("OutsideWorkspace") } +fn merge_text_and_snippet_edit( + line_index: &LineIndex, + edit: TextEdit, + snippet_edit: Option, +) -> Vec { + let Some(snippet_edit) = snippet_edit else { + return edit.into_iter().map(|it| snippet_text_edit(&line_index, false, it)).collect(); + }; + + let mut edits: Vec = vec![]; + let mut snippets = snippet_edit.into_edit_ranges().into_iter().peekable(); + let mut text_edits = edit.into_iter(); + + while let Some(current_indel) = text_edits.next() { + let new_range = { + let insert_len = + TextSize::try_from(current_indel.insert.len()).unwrap_or(TextSize::from(u32::MAX)); + TextRange::at(current_indel.delete.start(), insert_len) + }; + + // insert any snippets before the text edit + let first_snippet_in_or_after_edit = loop { + let Some((snippet_index, snippet_range)) = snippets.peek() else { break None }; + + // check if we're entirely before the range + // only possible for tabstops + if snippet_range.end() < new_range.start() + && stdx::always!( + snippet_range.is_empty(), + "placeholder range is before any text edits" + ) + { + let range = range(&line_index, *snippet_range); + let new_text = format!("${snippet_index}"); + + edits.push(SnippetTextEdit { + range, + new_text, + insert_text_format: Some(lsp_types::InsertTextFormat::SNIPPET), + annotation_id: None, + }) + } else { + break Some((snippet_index, snippet_range)); + } + }; + + if first_snippet_in_or_after_edit + .is_some_and(|(_, range)| new_range.intersect(*range).is_some()) + { + // at least one snippet edit intersects this text edit, + // so gather all of the edits that intersect this text edit + let mut all_snippets = snippets + .take_while_ref(|(_, range)| new_range.intersect(*range).is_some()) + .collect_vec(); + + // ensure all of the ranges are wholly contained inside of the new range + all_snippets.retain(|(_, range)| { + stdx::always!( + new_range.contains_range(*range), + "found placeholder range {:?} which wasn't fully inside of text edit's new range {:?}", range, new_range + ) + }); + + let mut text_edit = text_edit(line_index, current_indel); + + // escape out snippet text + stdx::replace(&mut text_edit.new_text, '\\', r"\\"); + stdx::replace(&mut text_edit.new_text, '$', r"\$"); + + // ...and apply! + for (index, range) in all_snippets.iter().rev() { + let start = (range.start() - new_range.start()).into(); + let end = (range.end() - new_range.start()).into(); + + if range.is_empty() { + text_edit.new_text.insert_str(start, &format!("${index}")); + } else { + text_edit.new_text.insert(end, '}'); + text_edit.new_text.insert_str(start, &format!("${{{index}")); + } + } + + edits.push(SnippetTextEdit { + range: text_edit.range, + new_text: text_edit.new_text, + insert_text_format: Some(lsp_types::InsertTextFormat::SNIPPET), + annotation_id: None, + }) + } else { + // snippet edit was beyond the current one + // since it wasn't consumed, it's available for the next pass + edits.push(snippet_text_edit(line_index, false, current_indel)); + } + } + + // insert any remaining edits + // either one of the two or both should've run out at this point, + // so it's either a tail of text edits or tabstops + edits.extend(text_edits.map(|indel| snippet_text_edit(line_index, false, indel))); + edits.extend(snippets.map(|(snippet_index, snippet_range)| { + stdx::always!( + snippet_range.is_empty(), + "found placeholder snippet {:?} without a text edit", + snippet_range + ); + + let range = range(&line_index, snippet_range); + let new_text = format!("${snippet_index}"); + + SnippetTextEdit { + range, + new_text, + insert_text_format: Some(lsp_types::InsertTextFormat::SNIPPET), + annotation_id: None, + } + })); + + edits +} + pub(crate) fn snippet_text_document_edit( snap: &GlobalStateSnapshot, - is_snippet: bool, file_id: FileId, edit: TextEdit, + snippet_edit: Option, ) -> Cancellable { let text_document = optional_versioned_text_document_identifier(snap, file_id); let line_index = snap.file_line_index(file_id)?; - let mut edits: Vec<_> = - edit.into_iter().map(|it| snippet_text_edit(&line_index, is_snippet, it)).collect(); + let mut edits = merge_text_and_snippet_edit(&line_index, edit, snippet_edit); if snap.analysis.is_library_file(file_id)? && snap.config.change_annotation_support() { for edit in &mut edits { @@ -973,8 +1092,13 @@ pub(crate) fn snippet_workspace_edit( let ops = snippet_text_document_ops(snap, op)?; document_changes.extend_from_slice(&ops); } - for (file_id, (edit, _snippet_edit)) in source_change.source_file_edits { - let edit = snippet_text_document_edit(snap, source_change.is_snippet, file_id, edit)?; + for (file_id, (edit, snippet_edit)) in source_change.source_file_edits { + let edit = snippet_text_document_edit( + snap, + file_id, + edit, + snippet_edit.filter(|_| source_change.is_snippet), + )?; document_changes.push(lsp_ext::SnippetDocumentChangeOperation::Edit(edit)); } let mut workspace_edit = lsp_ext::SnippetWorkspaceEdit { From ae83f32ee9d8787e266d439c9a1bca2424093cfd Mon Sep 17 00:00:00 2001 From: DropDemBits Date: Wed, 12 Jul 2023 02:36:37 -0400 Subject: [PATCH 09/59] Remove unnecessary `SourceChange` trait impls --- crates/ide-db/src/source_change.rs | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/crates/ide-db/src/source_change.rs b/crates/ide-db/src/source_change.rs index 3ff56ae9027bc..bfccd6b6e19b4 100644 --- a/crates/ide-db/src/source_change.rs +++ b/crates/ide-db/src/source_change.rs @@ -121,12 +121,6 @@ impl From> for SourceChange { } } -impl From)>> for SourceChange { - fn from(source_file_edits: IntMap)>) -> SourceChange { - SourceChange { source_file_edits, file_system_edits: Vec::new(), is_snippet: false } - } -} - impl FromIterator<(FileId, TextEdit)> for SourceChange { fn from_iter>(iter: T) -> Self { let mut this = SourceChange::default(); @@ -135,16 +129,6 @@ impl FromIterator<(FileId, TextEdit)> for SourceChange { } } -impl FromIterator<(FileId, (TextEdit, Option))> for SourceChange { - fn from_iter))>>( - iter: T, - ) -> Self { - let mut this = SourceChange::default(); - this.extend(iter); - this - } -} - #[derive(Debug, Clone, PartialEq, Eq)] pub struct SnippetEdit(Vec<(u32, TextRange)>); From a3a02d01f389a5e49e57ab3a37224e4d31c71b6b Mon Sep 17 00:00:00 2001 From: DropDemBits Date: Wed, 12 Jul 2023 02:58:32 -0400 Subject: [PATCH 10/59] Simplify snippet rendering Also makes sure that stray placeholders get converted into tabstops --- crates/rust-analyzer/src/to_proto.rs | 65 +++++++++++++++------------- 1 file changed, 34 insertions(+), 31 deletions(-) diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs index 46ca7db2e16f1..6624e6c082a84 100644 --- a/crates/rust-analyzer/src/to_proto.rs +++ b/crates/rust-analyzer/src/to_proto.rs @@ -884,7 +884,7 @@ fn outside_workspace_annotation_id() -> String { String::from("OutsideWorkspace") } -fn merge_text_and_snippet_edit( +fn merge_text_and_snippet_edits( line_index: &LineIndex, edit: TextEdit, snippet_edit: Option, @@ -905,34 +905,33 @@ fn merge_text_and_snippet_edit( }; // insert any snippets before the text edit - let first_snippet_in_or_after_edit = loop { - let Some((snippet_index, snippet_range)) = snippets.peek() else { break None }; - - // check if we're entirely before the range - // only possible for tabstops - if snippet_range.end() < new_range.start() - && stdx::always!( - snippet_range.is_empty(), - "placeholder range is before any text edits" - ) - { - let range = range(&line_index, *snippet_range); - let new_text = format!("${snippet_index}"); - - edits.push(SnippetTextEdit { - range, - new_text, - insert_text_format: Some(lsp_types::InsertTextFormat::SNIPPET), - annotation_id: None, - }) + for (snippet_index, snippet_range) in + snippets.take_while_ref(|(_, range)| range.end() < new_range.start()) + { + let snippet_range = if stdx::never!( + !snippet_range.is_empty(), + "placeholder range {:?} is before current text edit range {:?}", + snippet_range, + new_range + ) { + // only possible for tabstops, so make sure it's an empty/insert range + TextRange::empty(snippet_range.start()) } else { - break Some((snippet_index, snippet_range)); - } - }; + snippet_range + }; - if first_snippet_in_or_after_edit - .is_some_and(|(_, range)| new_range.intersect(*range).is_some()) - { + let range = range(&line_index, snippet_range); + let new_text = format!("${snippet_index}"); + + edits.push(SnippetTextEdit { + range, + new_text, + insert_text_format: Some(lsp_types::InsertTextFormat::SNIPPET), + annotation_id: None, + }) + } + + if snippets.peek().is_some_and(|(_, range)| new_range.intersect(*range).is_some()) { // at least one snippet edit intersects this text edit, // so gather all of the edits that intersect this text edit let mut all_snippets = snippets @@ -984,11 +983,15 @@ fn merge_text_and_snippet_edit( // so it's either a tail of text edits or tabstops edits.extend(text_edits.map(|indel| snippet_text_edit(line_index, false, indel))); edits.extend(snippets.map(|(snippet_index, snippet_range)| { - stdx::always!( - snippet_range.is_empty(), + let snippet_range = if stdx::never!( + !snippet_range.is_empty(), "found placeholder snippet {:?} without a text edit", snippet_range - ); + ) { + TextRange::empty(snippet_range.start()) + } else { + snippet_range + }; let range = range(&line_index, snippet_range); let new_text = format!("${snippet_index}"); @@ -1012,7 +1015,7 @@ pub(crate) fn snippet_text_document_edit( ) -> Cancellable { let text_document = optional_versioned_text_document_identifier(snap, file_id); let line_index = snap.file_line_index(file_id)?; - let mut edits = merge_text_and_snippet_edit(&line_index, edit, snippet_edit); + let mut edits = merge_text_and_snippet_edits(&line_index, edit, snippet_edit); if snap.analysis.is_library_file(file_id)? && snap.config.change_annotation_support() { for edit in &mut edits { From a1877df5a5166c0b4a129a68df75c76691692ee3 Mon Sep 17 00:00:00 2001 From: DropDemBits Date: Wed, 12 Jul 2023 03:14:09 -0400 Subject: [PATCH 11/59] Passthrough `is_snippet` for non-structured snippets Structured snippets precisely track which text edits need to be marked as snippet text edits, but the cases where structured snippets aren't used but snippets are still present are for simple single text-edit changes, so it's perfectly fine to mark all one of them as being a snippet text edit --- crates/rust-analyzer/src/to_proto.rs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs index 6624e6c082a84..3848ec004a075 100644 --- a/crates/rust-analyzer/src/to_proto.rs +++ b/crates/rust-analyzer/src/to_proto.rs @@ -887,12 +887,8 @@ fn outside_workspace_annotation_id() -> String { fn merge_text_and_snippet_edits( line_index: &LineIndex, edit: TextEdit, - snippet_edit: Option, + snippet_edit: SnippetEdit, ) -> Vec { - let Some(snippet_edit) = snippet_edit else { - return edit.into_iter().map(|it| snippet_text_edit(&line_index, false, it)).collect(); - }; - let mut edits: Vec = vec![]; let mut snippets = snippet_edit.into_edit_ranges().into_iter().peekable(); let mut text_edits = edit.into_iter(); @@ -1009,13 +1005,18 @@ fn merge_text_and_snippet_edits( pub(crate) fn snippet_text_document_edit( snap: &GlobalStateSnapshot, + is_snippet: bool, file_id: FileId, edit: TextEdit, snippet_edit: Option, ) -> Cancellable { let text_document = optional_versioned_text_document_identifier(snap, file_id); let line_index = snap.file_line_index(file_id)?; - let mut edits = merge_text_and_snippet_edits(&line_index, edit, snippet_edit); + let mut edits = if let Some(snippet_edit) = snippet_edit { + merge_text_and_snippet_edits(&line_index, edit, snippet_edit) + } else { + edit.into_iter().map(|it| snippet_text_edit(&line_index, is_snippet, it)).collect() + }; if snap.analysis.is_library_file(file_id)? && snap.config.change_annotation_support() { for edit in &mut edits { @@ -1098,9 +1099,10 @@ pub(crate) fn snippet_workspace_edit( for (file_id, (edit, snippet_edit)) in source_change.source_file_edits { let edit = snippet_text_document_edit( snap, + source_change.is_snippet, file_id, edit, - snippet_edit.filter(|_| source_change.is_snippet), + snippet_edit, )?; document_changes.push(lsp_ext::SnippetDocumentChangeOperation::Edit(edit)); } From 074488b290732092d077c8271fdcc2c6a91ecede Mon Sep 17 00:00:00 2001 From: Ryo Yoshida Date: Wed, 12 Jul 2023 23:46:23 +0900 Subject: [PATCH 12/59] Properly infer types with type casts --- crates/hir-ty/src/infer.rs | 34 +++++++++++++------- crates/hir-ty/src/infer/cast.rs | 46 +++++++++++++++++++++++++++ crates/hir-ty/src/infer/expr.rs | 18 +++-------- crates/hir-ty/src/tests/regression.rs | 20 ++++++++++++ crates/hir-ty/src/tests/simple.rs | 22 ++++++++++--- 5 files changed, 112 insertions(+), 28 deletions(-) create mode 100644 crates/hir-ty/src/infer/cast.rs diff --git a/crates/hir-ty/src/infer.rs b/crates/hir-ty/src/infer.rs index 0a617dae7d433..b4915dbf0f992 100644 --- a/crates/hir-ty/src/infer.rs +++ b/crates/hir-ty/src/infer.rs @@ -13,6 +13,15 @@ //! to certain types. To record this, we use the union-find implementation from //! the `ena` crate, which is extracted from rustc. +mod cast; +pub(crate) mod closure; +mod coerce; +mod expr; +mod mutability; +mod pat; +mod path; +pub(crate) mod unify; + use std::{convert::identity, ops::Index}; use chalk_ir::{ @@ -60,15 +69,8 @@ pub use coerce::could_coerce; #[allow(unreachable_pub)] pub use unify::could_unify; -pub(crate) use self::closure::{CaptureKind, CapturedItem, CapturedItemWithoutTy}; - -pub(crate) mod unify; -mod path; -mod expr; -mod pat; -mod coerce; -pub(crate) mod closure; -mod mutability; +use cast::CastCheck; +pub(crate) use closure::{CaptureKind, CapturedItem, CapturedItemWithoutTy}; /// The entry point of type inference. pub(crate) fn infer_query(db: &dyn HirDatabase, def: DefWithBodyId) -> Arc { @@ -508,6 +510,8 @@ pub(crate) struct InferenceContext<'a> { diverges: Diverges, breakables: Vec, + deferred_cast_checks: Vec, + // fields related to closure capture current_captures: Vec, current_closure: Option, @@ -582,7 +586,8 @@ impl<'a> InferenceContext<'a> { resolver, diverges: Diverges::Maybe, breakables: Vec::new(), - current_captures: vec![], + deferred_cast_checks: Vec::new(), + current_captures: Vec::new(), current_closure: None, deferred_closures: FxHashMap::default(), closure_dependencies: FxHashMap::default(), @@ -594,7 +599,7 @@ impl<'a> InferenceContext<'a> { // used this function for another workaround, mention it here. If you really need this function and believe that // there is no problem in it being `pub(crate)`, remove this comment. pub(crate) fn resolve_all(self) -> InferenceResult { - let InferenceContext { mut table, mut result, .. } = self; + let InferenceContext { mut table, mut result, deferred_cast_checks, .. } = self; // Destructure every single field so whenever new fields are added to `InferenceResult` we // don't forget to handle them here. let InferenceResult { @@ -622,6 +627,13 @@ impl<'a> InferenceContext<'a> { table.fallback_if_possible(); + // Comment from rustc: + // Even though coercion casts provide type hints, we check casts after fallback for + // backwards compatibility. This makes fallback a stronger type hint than a cast coercion. + for cast in deferred_cast_checks { + cast.check(&mut table); + } + // FIXME resolve obligations as well (use Guidance if necessary) table.resolve_obligations_as_possible(); diff --git a/crates/hir-ty/src/infer/cast.rs b/crates/hir-ty/src/infer/cast.rs new file mode 100644 index 0000000000000..9e1c74b16fa01 --- /dev/null +++ b/crates/hir-ty/src/infer/cast.rs @@ -0,0 +1,46 @@ +//! Type cast logic. Basically coercion + additional casts. + +use crate::{infer::unify::InferenceTable, Interner, Ty, TyExt, TyKind}; + +#[derive(Clone, Debug)] +pub(super) struct CastCheck { + expr_ty: Ty, + cast_ty: Ty, +} + +impl CastCheck { + pub(super) fn new(expr_ty: Ty, cast_ty: Ty) -> Self { + Self { expr_ty, cast_ty } + } + + pub(super) fn check(self, table: &mut InferenceTable<'_>) { + // FIXME: This function currently only implements the bits that influence the type + // inference. We should return the adjustments on success and report diagnostics on error. + let expr_ty = table.resolve_ty_shallow(&self.expr_ty); + let cast_ty = table.resolve_ty_shallow(&self.cast_ty); + + if expr_ty.contains_unknown() || cast_ty.contains_unknown() { + return; + } + + if table.coerce(&expr_ty, &cast_ty).is_ok() { + return; + } + + if check_ref_to_ptr_cast(expr_ty, cast_ty, table) { + // Note that this type of cast is actually split into a coercion to a + // pointer type and a cast: + // &[T; N] -> *[T; N] -> *T + return; + } + + // FIXME: Check other kinds of non-coercion casts and report error if any? + } +} + +fn check_ref_to_ptr_cast(expr_ty: Ty, cast_ty: Ty, table: &mut InferenceTable<'_>) -> bool { + let Some((expr_inner_ty, _, _)) = expr_ty.as_reference() else { return false; }; + let Some((cast_inner_ty, _)) = cast_ty.as_raw_ptr() else { return false; }; + let TyKind::Array(expr_elt_ty, _) = expr_inner_ty.kind(Interner) else { return false; }; + table.coerce(expr_elt_ty, cast_inner_ty).is_ok() +} diff --git a/crates/hir-ty/src/infer/expr.rs b/crates/hir-ty/src/infer/expr.rs index 4b14345aa39f1..9475eed44e434 100644 --- a/crates/hir-ty/src/infer/expr.rs +++ b/crates/hir-ty/src/infer/expr.rs @@ -46,8 +46,8 @@ use crate::{ }; use super::{ - coerce::auto_deref_adjust_steps, find_breakable, BreakableContext, Diverges, Expectation, - InferenceContext, InferenceDiagnostic, TypeMismatch, + cast::CastCheck, coerce::auto_deref_adjust_steps, find_breakable, BreakableContext, Diverges, + Expectation, InferenceContext, InferenceDiagnostic, TypeMismatch, }; impl InferenceContext<'_> { @@ -574,16 +574,8 @@ impl InferenceContext<'_> { } Expr::Cast { expr, type_ref } => { let cast_ty = self.make_ty(type_ref); - // FIXME: propagate the "castable to" expectation - let inner_ty = self.infer_expr_no_expect(*expr); - match (inner_ty.kind(Interner), cast_ty.kind(Interner)) { - (TyKind::Ref(_, _, inner), TyKind::Raw(_, cast)) => { - // FIXME: record invalid cast diagnostic in case of mismatch - self.unify(inner, cast); - } - // FIXME check the other kinds of cast... - _ => (), - } + let expr_ty = self.infer_expr(*expr, &Expectation::Castable(cast_ty.clone())); + self.deferred_cast_checks.push(CastCheck::new(expr_ty, cast_ty.clone())); cast_ty } Expr::Ref { expr, rawness, mutability } => { @@ -1592,7 +1584,7 @@ impl InferenceContext<'_> { output: Ty, inputs: Vec, ) -> Vec { - if let Some(expected_ty) = expected_output.to_option(&mut self.table) { + if let Some(expected_ty) = expected_output.only_has_type(&mut self.table) { self.table.fudge_inference(|table| { if table.try_unify(&expected_ty, &output).is_ok() { table.resolve_with_fallback(inputs, &|var, kind, _, _| match kind { diff --git a/crates/hir-ty/src/tests/regression.rs b/crates/hir-ty/src/tests/regression.rs index 8b95110233fdc..375014d6c7f84 100644 --- a/crates/hir-ty/src/tests/regression.rs +++ b/crates/hir-ty/src/tests/regression.rs @@ -1978,3 +1978,23 @@ fn x(a: [i32; 4]) { "#, ); } + +#[test] +fn dont_unify_on_casts() { + // #15246 + check_types( + r#" +fn unify(_: [bool; 1]) {} +fn casted(_: *const bool) {} +fn default() -> T { loop {} } + +fn test() { + let foo = default(); + //^^^ [bool; 1] + + casted(&foo as *const _); + unify(foo); +} +"#, + ); +} diff --git a/crates/hir-ty/src/tests/simple.rs b/crates/hir-ty/src/tests/simple.rs index a0ff628435f3d..2ad7946c8ac19 100644 --- a/crates/hir-ty/src/tests/simple.rs +++ b/crates/hir-ty/src/tests/simple.rs @@ -3513,7 +3513,6 @@ fn func() { ); } -// FIXME #[test] fn castable_to() { check_infer( @@ -3538,10 +3537,10 @@ fn func() { 120..122 '{}': () 138..184 '{ ...0]>; }': () 148..149 'x': Box<[i32; 0]> - 152..160 'Box::new': fn new<[{unknown}; 0]>([{unknown}; 0]) -> Box<[{unknown}; 0]> - 152..164 'Box::new([])': Box<[{unknown}; 0]> + 152..160 'Box::new': fn new<[i32; 0]>([i32; 0]) -> Box<[i32; 0]> + 152..164 'Box::new([])': Box<[i32; 0]> 152..181 'Box::n...2; 0]>': Box<[i32; 0]> - 161..163 '[]': [{unknown}; 0] + 161..163 '[]': [i32; 0] "#]], ); } @@ -3577,6 +3576,21 @@ fn f(t: Ark) { ); } +#[test] +fn ref_to_array_to_ptr_cast() { + check_types( + r#" +fn default() -> T { loop {} } +fn foo() { + let arr = [default()]; + //^^^ [i32; 1] + let ref_to_arr = &arr; + let casted = ref_to_arr as *const i32; +} +"#, + ); +} + #[test] fn const_dependent_on_local() { check_types( From 614987ae710162f8283934fe643702b690b24fd1 Mon Sep 17 00:00:00 2001 From: DropDemBits Date: Wed, 12 Jul 2023 17:22:02 -0400 Subject: [PATCH 13/59] Test rendering of snippets Had a missing ':' between the snippet index and placeholder text --- crates/ide-db/src/source_change.rs | 10 +- crates/rust-analyzer/src/to_proto.rs | 492 ++++++++++++++++++++++++++- 2 files changed, 489 insertions(+), 13 deletions(-) diff --git a/crates/ide-db/src/source_change.rs b/crates/ide-db/src/source_change.rs index bfccd6b6e19b4..39763479c65a2 100644 --- a/crates/ide-db/src/source_change.rs +++ b/crates/ide-db/src/source_change.rs @@ -133,7 +133,7 @@ impl FromIterator<(FileId, TextEdit)> for SourceChange { pub struct SnippetEdit(Vec<(u32, TextRange)>); impl SnippetEdit { - fn new(snippets: Vec) -> Self { + pub fn new(snippets: Vec) -> Self { let mut snippet_ranges = snippets .into_iter() .zip(1..) @@ -157,8 +157,10 @@ impl SnippetEdit { snippet_ranges.sort_by_key(|(_, range)| range.start()); // Ensure that none of the ranges overlap - let disjoint_ranges = - snippet_ranges.windows(2).all(|ranges| ranges[0].1.end() <= ranges[1].1.start()); + let disjoint_ranges = snippet_ranges + .iter() + .zip(snippet_ranges.iter().skip(1)) + .all(|((_, left), (_, right))| left.end() <= right.start() || left == right); stdx::always!(disjoint_ranges); SnippetEdit(snippet_ranges) @@ -393,7 +395,7 @@ impl From for SourceChange { } } -enum Snippet { +pub enum Snippet { /// A tabstop snippet (e.g. `$0`). Tabstop(TextSize), /// A placeholder snippet (e.g. `${0:placeholder}`). diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs index 3848ec004a075..01c30fbfcb7a2 100644 --- a/crates/rust-analyzer/src/to_proto.rs +++ b/crates/rust-analyzer/src/to_proto.rs @@ -904,8 +904,8 @@ fn merge_text_and_snippet_edits( for (snippet_index, snippet_range) in snippets.take_while_ref(|(_, range)| range.end() < new_range.start()) { - let snippet_range = if stdx::never!( - !snippet_range.is_empty(), + let snippet_range = if !stdx::always!( + snippet_range.is_empty(), "placeholder range {:?} is before current text edit range {:?}", snippet_range, new_range @@ -957,7 +957,7 @@ fn merge_text_and_snippet_edits( text_edit.new_text.insert_str(start, &format!("${index}")); } else { text_edit.new_text.insert(end, '}'); - text_edit.new_text.insert_str(start, &format!("${{{index}")); + text_edit.new_text.insert_str(start, &format!("${{{index}:")); } } @@ -974,13 +974,10 @@ fn merge_text_and_snippet_edits( } } - // insert any remaining edits - // either one of the two or both should've run out at this point, - // so it's either a tail of text edits or tabstops - edits.extend(text_edits.map(|indel| snippet_text_edit(line_index, false, indel))); + // insert any remaining tabstops edits.extend(snippets.map(|(snippet_index, snippet_range)| { - let snippet_range = if stdx::never!( - !snippet_range.is_empty(), + let snippet_range = if !stdx::always!( + snippet_range.is_empty(), "found placeholder snippet {:?} without a text edit", snippet_range ) { @@ -1542,7 +1539,9 @@ pub(crate) fn rename_error(err: RenameError) -> crate::LspError { #[cfg(test)] mod tests { + use expect_test::{expect, Expect}; use ide::{Analysis, FilePosition}; + use ide_db::source_change::Snippet; use test_utils::extract_offset; use triomphe::Arc; @@ -1612,6 +1611,481 @@ fn bar(_: usize) {} assert!(!docs.contains("use crate::bar")); } + fn check_rendered_snippets(edit: TextEdit, snippets: SnippetEdit, expect: Expect) { + let text = r#"/* place to put all ranges in */"#; + let line_index = LineIndex { + index: Arc::new(ide::LineIndex::new(text)), + endings: LineEndings::Unix, + encoding: PositionEncoding::Utf8, + }; + + let res = merge_text_and_snippet_edits(&line_index, edit, snippets); + expect.assert_debug_eq(&res); + } + + #[test] + fn snippet_rendering_only_tabstops() { + let edit = TextEdit::builder().finish(); + let snippets = SnippetEdit::new(vec![ + Snippet::Tabstop(0.into()), + Snippet::Tabstop(0.into()), + Snippet::Tabstop(1.into()), + Snippet::Tabstop(1.into()), + ]); + + check_rendered_snippets( + edit, + snippets, + expect![[r#" + [ + SnippetTextEdit { + range: Range { + start: Position { + line: 0, + character: 0, + }, + end: Position { + line: 0, + character: 0, + }, + }, + new_text: "$1", + insert_text_format: Some( + Snippet, + ), + annotation_id: None, + }, + SnippetTextEdit { + range: Range { + start: Position { + line: 0, + character: 0, + }, + end: Position { + line: 0, + character: 0, + }, + }, + new_text: "$2", + insert_text_format: Some( + Snippet, + ), + annotation_id: None, + }, + SnippetTextEdit { + range: Range { + start: Position { + line: 0, + character: 1, + }, + end: Position { + line: 0, + character: 1, + }, + }, + new_text: "$3", + insert_text_format: Some( + Snippet, + ), + annotation_id: None, + }, + SnippetTextEdit { + range: Range { + start: Position { + line: 0, + character: 1, + }, + end: Position { + line: 0, + character: 1, + }, + }, + new_text: "$0", + insert_text_format: Some( + Snippet, + ), + annotation_id: None, + }, + ] + "#]], + ); + } + + #[test] + fn snippet_rendering_only_text_edits() { + let mut edit = TextEdit::builder(); + edit.insert(0.into(), "abc".to_owned()); + edit.insert(3.into(), "def".to_owned()); + let edit = edit.finish(); + let snippets = SnippetEdit::new(vec![]); + + check_rendered_snippets( + edit, + snippets, + expect![[r#" + [ + SnippetTextEdit { + range: Range { + start: Position { + line: 0, + character: 0, + }, + end: Position { + line: 0, + character: 0, + }, + }, + new_text: "abc", + insert_text_format: None, + annotation_id: None, + }, + SnippetTextEdit { + range: Range { + start: Position { + line: 0, + character: 3, + }, + end: Position { + line: 0, + character: 3, + }, + }, + new_text: "def", + insert_text_format: None, + annotation_id: None, + }, + ] + "#]], + ); + } + + #[test] + fn snippet_rendering_tabstop_after_text_edit() { + let mut edit = TextEdit::builder(); + edit.insert(0.into(), "abc".to_owned()); + let edit = edit.finish(); + let snippets = SnippetEdit::new(vec![Snippet::Tabstop(7.into())]); + + check_rendered_snippets( + edit, + snippets, + expect![[r#" + [ + SnippetTextEdit { + range: Range { + start: Position { + line: 0, + character: 0, + }, + end: Position { + line: 0, + character: 0, + }, + }, + new_text: "abc", + insert_text_format: None, + annotation_id: None, + }, + SnippetTextEdit { + range: Range { + start: Position { + line: 0, + character: 7, + }, + end: Position { + line: 0, + character: 7, + }, + }, + new_text: "$0", + insert_text_format: Some( + Snippet, + ), + annotation_id: None, + }, + ] + "#]], + ); + } + + #[test] + fn snippet_rendering_tabstops_before_text_edit() { + let mut edit = TextEdit::builder(); + edit.insert(2.into(), "abc".to_owned()); + let edit = edit.finish(); + let snippets = + SnippetEdit::new(vec![Snippet::Tabstop(0.into()), Snippet::Tabstop(0.into())]); + + check_rendered_snippets( + edit, + snippets, + expect![[r#" + [ + SnippetTextEdit { + range: Range { + start: Position { + line: 0, + character: 0, + }, + end: Position { + line: 0, + character: 0, + }, + }, + new_text: "$1", + insert_text_format: Some( + Snippet, + ), + annotation_id: None, + }, + SnippetTextEdit { + range: Range { + start: Position { + line: 0, + character: 0, + }, + end: Position { + line: 0, + character: 0, + }, + }, + new_text: "$0", + insert_text_format: Some( + Snippet, + ), + annotation_id: None, + }, + SnippetTextEdit { + range: Range { + start: Position { + line: 0, + character: 2, + }, + end: Position { + line: 0, + character: 2, + }, + }, + new_text: "abc", + insert_text_format: None, + annotation_id: None, + }, + ] + "#]], + ); + } + + #[test] + fn snippet_rendering_tabstops_between_text_edits() { + let mut edit = TextEdit::builder(); + edit.insert(0.into(), "abc".to_owned()); + edit.insert(7.into(), "abc".to_owned()); + let edit = edit.finish(); + let snippets = + SnippetEdit::new(vec![Snippet::Tabstop(4.into()), Snippet::Tabstop(4.into())]); + + check_rendered_snippets( + edit, + snippets, + expect![[r#" + [ + SnippetTextEdit { + range: Range { + start: Position { + line: 0, + character: 0, + }, + end: Position { + line: 0, + character: 0, + }, + }, + new_text: "abc", + insert_text_format: None, + annotation_id: None, + }, + SnippetTextEdit { + range: Range { + start: Position { + line: 0, + character: 4, + }, + end: Position { + line: 0, + character: 4, + }, + }, + new_text: "$1", + insert_text_format: Some( + Snippet, + ), + annotation_id: None, + }, + SnippetTextEdit { + range: Range { + start: Position { + line: 0, + character: 4, + }, + end: Position { + line: 0, + character: 4, + }, + }, + new_text: "$0", + insert_text_format: Some( + Snippet, + ), + annotation_id: None, + }, + SnippetTextEdit { + range: Range { + start: Position { + line: 0, + character: 7, + }, + end: Position { + line: 0, + character: 7, + }, + }, + new_text: "abc", + insert_text_format: None, + annotation_id: None, + }, + ] + "#]], + ); + } + + #[test] + fn snippet_rendering_multiple_tabstops_in_text_edit() { + let mut edit = TextEdit::builder(); + edit.insert(0.into(), "abcdefghijkl".to_owned()); + let edit = edit.finish(); + let snippets = SnippetEdit::new(vec![ + Snippet::Tabstop(0.into()), + Snippet::Tabstop(5.into()), + Snippet::Tabstop(12.into()), + ]); + + check_rendered_snippets( + edit, + snippets, + expect![[r#" + [ + SnippetTextEdit { + range: Range { + start: Position { + line: 0, + character: 0, + }, + end: Position { + line: 0, + character: 0, + }, + }, + new_text: "$1abcde$2fghijkl$0", + insert_text_format: Some( + Snippet, + ), + annotation_id: None, + }, + ] + "#]], + ); + } + + #[test] + fn snippet_rendering_multiple_placeholders_in_text_edit() { + let mut edit = TextEdit::builder(); + edit.insert(0.into(), "abcdefghijkl".to_owned()); + let edit = edit.finish(); + let snippets = SnippetEdit::new(vec![ + Snippet::Placeholder(TextRange::new(0.into(), 3.into())), + Snippet::Placeholder(TextRange::new(5.into(), 7.into())), + Snippet::Placeholder(TextRange::new(10.into(), 12.into())), + ]); + + check_rendered_snippets( + edit, + snippets, + expect![[r#" + [ + SnippetTextEdit { + range: Range { + start: Position { + line: 0, + character: 0, + }, + end: Position { + line: 0, + character: 0, + }, + }, + new_text: "${1:abc}de${2:fg}hij${0:kl}", + insert_text_format: Some( + Snippet, + ), + annotation_id: None, + }, + ] + "#]], + ); + } + + #[test] + fn snippet_rendering_escape_snippet_bits() { + // only needed for snippet formats + let mut edit = TextEdit::builder(); + edit.insert(0.into(), r"abc\def$".to_owned()); + edit.insert(8.into(), r"ghi\jkl$".to_owned()); + let edit = edit.finish(); + let snippets = + SnippetEdit::new(vec![Snippet::Placeholder(TextRange::new(0.into(), 3.into()))]); + + check_rendered_snippets( + edit, + snippets, + expect![[r#" + [ + SnippetTextEdit { + range: Range { + start: Position { + line: 0, + character: 0, + }, + end: Position { + line: 0, + character: 0, + }, + }, + new_text: "${0:abc}\\\\def\\$", + insert_text_format: Some( + Snippet, + ), + annotation_id: None, + }, + SnippetTextEdit { + range: Range { + start: Position { + line: 0, + character: 8, + }, + end: Position { + line: 0, + character: 8, + }, + }, + new_text: "ghi\\jkl$", + insert_text_format: None, + annotation_id: None, + }, + ] + "#]], + ); + } + // `Url` is not able to parse windows paths on unix machines. #[test] #[cfg(target_os = "windows")] From 3468b093bd01e2cebba7964b48c9cef9ceb9e70b Mon Sep 17 00:00:00 2001 From: vsrs Date: Tue, 18 Jul 2023 17:51:57 +0700 Subject: [PATCH 14/59] Platform specific runnables env --- editors/code/package.json | 9 +++++++++ editors/code/src/config.ts | 10 ++++++---- editors/code/src/run.ts | 15 +++++++++++++-- 3 files changed, 28 insertions(+), 6 deletions(-) diff --git a/editors/code/package.json b/editors/code/package.json index ffb5dd9079ad0..6913841b3f71b 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -325,6 +325,15 @@ "items": { "type": "object", "properties": { + "platform": { + "type": [ + "null", + "string", + "array" + ], + "default": null, + "markdownDescription": "Platform(s) filter like \"win32\" or [\"linux\", \"win32\"]. See [process.platform](https://nodejs.org/api/process.html#processplatform) values." + }, "mask": { "type": "string", "description": "Runnable name mask" diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts index a047f9659a96a..0e64054c11d5d 100644 --- a/editors/code/src/config.ts +++ b/editors/code/src/config.ts @@ -6,10 +6,12 @@ import type { Env } from "./client"; import { log } from "./util"; import { expectNotUndefined, unwrapUndefinable } from "./undefinable"; -export type RunnableEnvCfg = - | undefined - | Record - | { mask?: string; env: Record }[]; +export type RunnableEnvCfgItem = { + mask?: string; + env: Record; + platform?: string | string[]; +}; +export type RunnableEnvCfg = undefined | Record | RunnableEnvCfgItem[]; export class Config { readonly extensionId = "rust-lang.rust-analyzer"; diff --git a/editors/code/src/run.ts b/editors/code/src/run.ts index c893d390554d8..8d468141d53ac 100644 --- a/editors/code/src/run.ts +++ b/editors/code/src/run.ts @@ -5,8 +5,9 @@ import * as tasks from "./tasks"; import type { CtxInit } from "./ctx"; import { makeDebugConfig } from "./debug"; -import type { Config, RunnableEnvCfg } from "./config"; +import type { Config, RunnableEnvCfg, RunnableEnvCfgItem } from "./config"; import { unwrapUndefinable } from "./undefinable"; +import { string } from "vscode-languageclient/lib/common/utils/is"; const quickPickButtons = [ { iconPath: new vscode.ThemeIcon("save"), tooltip: "Save as a launch.json configuration." }, @@ -112,11 +113,21 @@ export function prepareEnv( } Object.assign(env, process.env as { [key: string]: string }); + const platform = process.platform; + + const checkPlatform = (it: RunnableEnvCfgItem) => { + if (it.platform) { + const platforms = Array.isArray(it.platform) ? it.platform : [it.platform]; + return platforms.indexOf(platform) >= 0; + } + return true; + }; if (runnableEnvCfg) { if (Array.isArray(runnableEnvCfg)) { for (const it of runnableEnvCfg) { - if (!it.mask || new RegExp(it.mask).test(runnable.label)) { + const masked = !it.mask || new RegExp(it.mask).test(runnable.label); + if (masked && checkPlatform(it)) { Object.assign(env, it.env); } } From 7f29f016f3330b12b41f313c2b724063a2bd23d4 Mon Sep 17 00:00:00 2001 From: vsrs Date: Tue, 18 Jul 2023 17:58:51 +0700 Subject: [PATCH 15/59] Add docs --- docs/user/manual.adoc | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/docs/user/manual.adoc b/docs/user/manual.adoc index 31035c4b7295c..6d9234538d14b 100644 --- a/docs/user/manual.adoc +++ b/docs/user/manual.adoc @@ -952,6 +952,29 @@ Or it is possible to specify vars more granularly: You can use any valid regular expression as a mask. Also note that a full runnable name is something like *run bin_or_example_name*, *test some::mod::test_name* or *test-mod some::mod*, so it is possible to distinguish binaries, single tests, and test modules with this masks: `"^run"`, `"^test "` (the trailing space matters!), and `"^test-mod"` respectively. +If needed, you can set different values for different platforms: +```jsonc +"rust-analyzer.runnables.extraEnv": [ + { + "platform": "win32", // windows only + env: { + "APP_DATA": "windows specific data" + } + }, + { + "platform": ["linux"], + "env": { + "APP_DATA": "linux data", + } + }, + { // for all platforms + "env": { + "APP_COMMON_DATA": "xxx", + } + } +] +``` + ==== Compiler feedback from external commands Instead of relying on the built-in `cargo check`, you can configure Code to run a command in the background and use the `$rustc-watch` problem matcher to generate inline error markers from its output. From 08b3b2a56db77d56cdf0b6a8a23b8f93f92dae4f Mon Sep 17 00:00:00 2001 From: vsrs Date: Tue, 18 Jul 2023 18:06:20 +0700 Subject: [PATCH 16/59] Fix lint --- editors/code/src/run.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/editors/code/src/run.ts b/editors/code/src/run.ts index 8d468141d53ac..57881803a6a04 100644 --- a/editors/code/src/run.ts +++ b/editors/code/src/run.ts @@ -7,7 +7,6 @@ import type { CtxInit } from "./ctx"; import { makeDebugConfig } from "./debug"; import type { Config, RunnableEnvCfg, RunnableEnvCfgItem } from "./config"; import { unwrapUndefinable } from "./undefinable"; -import { string } from "vscode-languageclient/lib/common/utils/is"; const quickPickButtons = [ { iconPath: new vscode.ThemeIcon("save"), tooltip: "Save as a launch.json configuration." }, From 0155385b576523194c06e3ec164c8c0691f331e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lauren=C8=9Biu=20Nicola?= Date: Mon, 24 Jul 2023 12:21:34 +0300 Subject: [PATCH 17/59] Merge commit '99718d0c8bc5aadd993acdcabc1778fc7b5cc572' into sync-from-ra --- .github/workflows/ci.yaml | 15 ++- crates/hir-def/src/body/pretty.rs | 2 +- crates/hir-def/src/hir.rs | 2 +- crates/hir-ty/src/consteval.rs | 17 +-- crates/hir-ty/src/consteval/tests.rs | 51 ++++++++- crates/hir-ty/src/db.rs | 16 ++- crates/hir-ty/src/diagnostics/decl_check.rs | 69 ++++++------ crates/hir-ty/src/display.rs | 31 +++--- crates/hir-ty/src/infer/expr.rs | 1 + crates/hir-ty/src/infer/unify.rs | 5 +- crates/hir-ty/src/layout.rs | 32 +++--- crates/hir-ty/src/layout/adt.rs | 12 +-- crates/hir-ty/src/layout/tests.rs | 20 ++-- crates/hir-ty/src/method_resolution.rs | 24 +++-- crates/hir-ty/src/mir.rs | 12 +-- crates/hir-ty/src/mir/eval.rs | 50 +++++---- crates/hir-ty/src/mir/eval/tests.rs | 2 +- crates/hir-ty/src/mir/lower.rs | 11 +- crates/hir-ty/src/tests/method_resolution.rs | 21 ++++ crates/hir-ty/src/tests/traits.rs | 44 ++++++++ crates/hir-ty/src/utils.rs | 10 +- crates/hir/src/lib.rs | 34 +++--- .../src/handlers/change_visibility.rs | 75 +++++++++++-- .../src/handlers/incorrect_case.rs | 96 +++++++++++++++++ crates/ide/src/syntax_highlighting.rs | 10 +- crates/ide/src/syntax_highlighting/escape.rs | 36 +++++-- crates/ide/src/syntax_highlighting/html.rs | 3 +- crates/ide/src/syntax_highlighting/tags.rs | 2 + .../test_data/highlight_assoc_functions.html | 3 +- .../test_data/highlight_attributes.html | 3 +- .../test_data/highlight_crate_root.html | 3 +- .../test_data/highlight_default_library.html | 3 +- .../test_data/highlight_doctest.html | 3 +- .../test_data/highlight_extern_crate.html | 3 +- .../test_data/highlight_general.html | 3 +- .../test_data/highlight_injection.html | 3 +- .../test_data/highlight_keywords.html | 3 +- .../test_data/highlight_lifetimes.html | 3 +- .../test_data/highlight_macros.html | 3 +- .../highlight_module_docs_inline.html | 3 +- .../highlight_module_docs_outline.html | 3 +- .../test_data/highlight_operators.html | 3 +- .../test_data/highlight_rainbow.html | 3 +- .../test_data/highlight_strings.html | 10 +- .../test_data/highlight_unsafe.html | 3 +- crates/ide/src/syntax_highlighting/tests.rs | 7 +- crates/parser/src/grammar.rs | 102 ++++++++---------- crates/parser/src/tests/prefix_entries.rs | 1 - .../inline/ok/0040_crate_keyword_vis.rast | 63 ----------- .../inline/ok/0040_crate_keyword_vis.rs | 3 - .../inline/ok/0125_crate_keyword_path.rast | 33 ------ .../inline/ok/0125_crate_keyword_path.rs | 1 - crates/project-model/src/cargo_workspace.rs | 23 ++-- .../rust-analyzer/src/cli/analysis_stats.rs | 4 +- crates/rust-analyzer/src/semantic_tokens.rs | 1 + crates/rust-analyzer/src/to_proto.rs | 1 + crates/syntax/src/ast/token_ext.rs | 61 ++++++++--- docs/user/manual.adoc | 33 +++--- editors/code/package.json | 15 ++- editors/code/src/debug.ts | 13 ++- 60 files changed, 717 insertions(+), 405 deletions(-) delete mode 100644 crates/parser/test_data/parser/inline/ok/0040_crate_keyword_vis.rast delete mode 100644 crates/parser/test_data/parser/inline/ok/0040_crate_keyword_vis.rs delete mode 100644 crates/parser/test_data/parser/inline/ok/0125_crate_keyword_path.rast delete mode 100644 crates/parser/test_data/parser/inline/ok/0125_crate_keyword_path.rs diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 31bb7eed8d73f..9f246098e767b 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -161,10 +161,21 @@ jobs: # if: runner.os == 'Linux' # working-directory: ./editors/code + # If this steps fails, your code's type integrity might be wrong at some places at TypeScript level. + - run: npm run typecheck + working-directory: ./editors/code + if: needs.changes.outputs.typescript == 'true' + + # You may fix the code automatically by running `npm run lint:fix` if this steps fails. - run: npm run lint working-directory: ./editors/code if: needs.changes.outputs.typescript == 'true' + # To fix this steps, please run `npm run format`. + - run: npm run format:check + working-directory: ./editors/code + if: needs.changes.outputs.typescript == 'true' + - name: Run VS Code tests (Linux) if: matrix.os == 'ubuntu-latest' && needs.changes.outputs.typescript == 'true' env: @@ -179,10 +190,6 @@ jobs: run: npm test working-directory: ./editors/code - - run: npm run pretest - working-directory: ./editors/code - if: needs.changes.outputs.typescript == 'true' - - run: npm run package --scripts-prepend-node-path working-directory: ./editors/code if: needs.changes.outputs.typescript == 'true' diff --git a/crates/hir-def/src/body/pretty.rs b/crates/hir-def/src/body/pretty.rs index 0c6cf0b49a266..eeaed87164dc0 100644 --- a/crates/hir-def/src/body/pretty.rs +++ b/crates/hir-def/src/body/pretty.rs @@ -634,7 +634,7 @@ impl Printer<'_> { match literal { Literal::String(it) => w!(self, "{:?}", it), Literal::ByteString(it) => w!(self, "\"{}\"", it.escape_ascii()), - Literal::CString(it) => w!(self, "\"{}\\0\"", it), + Literal::CString(it) => w!(self, "\"{}\\0\"", it.escape_ascii()), Literal::Char(it) => w!(self, "'{}'", it.escape_debug()), Literal::Bool(it) => w!(self, "{}", it), Literal::Int(i, suffix) => { diff --git a/crates/hir-def/src/hir.rs b/crates/hir-def/src/hir.rs index 500e880061ac4..8a140a1ec1819 100644 --- a/crates/hir-def/src/hir.rs +++ b/crates/hir-def/src/hir.rs @@ -85,7 +85,7 @@ impl fmt::Display for FloatTypeWrapper { pub enum Literal { String(Box), ByteString(Box<[u8]>), - CString(Box), + CString(Box<[u8]>), Char(char), Bool(bool), Int(i128, Option), diff --git a/crates/hir-ty/src/consteval.rs b/crates/hir-ty/src/consteval.rs index 4de90d40a7c72..1c0f7b08da8c0 100644 --- a/crates/hir-ty/src/consteval.rs +++ b/crates/hir-ty/src/consteval.rs @@ -16,7 +16,8 @@ use triomphe::Arc; use crate::{ db::HirDatabase, infer::InferenceContext, lower::ParamLoweringMode, mir::monomorphize_mir_body_bad, to_placeholder_idx, utils::Generics, Const, ConstData, - ConstScalar, ConstValue, GenericArg, Interner, MemoryMap, Substitution, Ty, TyBuilder, + ConstScalar, ConstValue, GenericArg, Interner, MemoryMap, Substitution, TraitEnvironment, Ty, + TyBuilder, }; use super::mir::{interpret_mir, lower_to_mir, pad16, MirEvalError, MirLowerError}; @@ -135,7 +136,7 @@ pub fn intern_const_ref( ty: Ty, krate: CrateId, ) -> Const { - let layout = db.layout_of_ty(ty.clone(), krate); + let layout = db.layout_of_ty(ty.clone(), Arc::new(TraitEnvironment::empty(krate))); let bytes = match value { LiteralConstRef::Int(i) => { // FIXME: We should handle failure of layout better. @@ -173,7 +174,7 @@ pub fn try_const_usize(db: &dyn HirDatabase, c: &Const) -> Option { chalk_ir::ConstValue::Concrete(c) => match &c.interned { ConstScalar::Bytes(it, _) => Some(u128::from_le_bytes(pad16(&it, false))), ConstScalar::UnevaluatedConst(c, subst) => { - let ec = db.const_eval(*c, subst.clone()).ok()?; + let ec = db.const_eval(*c, subst.clone(), None).ok()?; try_const_usize(db, &ec) } _ => None, @@ -186,6 +187,7 @@ pub(crate) fn const_eval_recover( _: &[String], _: &GeneralConstId, _: &Substitution, + _: &Option>, ) -> Result { Err(ConstEvalError::MirLowerError(MirLowerError::Loop)) } @@ -210,6 +212,7 @@ pub(crate) fn const_eval_query( db: &dyn HirDatabase, def: GeneralConstId, subst: Substitution, + trait_env: Option>, ) -> Result { let body = match def { GeneralConstId::ConstId(c) => { @@ -228,7 +231,7 @@ pub(crate) fn const_eval_query( } GeneralConstId::InTypeConstId(c) => db.mir_body(c.into())?, }; - let c = interpret_mir(db, body, false).0?; + let c = interpret_mir(db, body, false, trait_env).0?; Ok(c) } @@ -241,7 +244,7 @@ pub(crate) fn const_eval_static_query( Substitution::empty(Interner), db.trait_environment_for_body(def.into()), )?; - let c = interpret_mir(db, body, false).0?; + let c = interpret_mir(db, body, false, None).0?; Ok(c) } @@ -268,7 +271,7 @@ pub(crate) fn const_eval_discriminant_variant( Substitution::empty(Interner), db.trait_environment_for_body(def), )?; - let c = interpret_mir(db, mir_body, false).0?; + let c = interpret_mir(db, mir_body, false, None).0?; let c = try_const_usize(db, &c).unwrap() as i128; Ok(c) } @@ -293,7 +296,7 @@ pub(crate) fn eval_to_const( } let infer = ctx.clone().resolve_all(); if let Ok(mir_body) = lower_to_mir(ctx.db, ctx.owner, &ctx.body, &infer, expr) { - if let Ok(result) = interpret_mir(db, Arc::new(mir_body), true).0 { + if let Ok(result) = interpret_mir(db, Arc::new(mir_body), true, None).0 { return result; } } diff --git a/crates/hir-ty/src/consteval/tests.rs b/crates/hir-ty/src/consteval/tests.rs index 5bb327606d326..98ebe557245fd 100644 --- a/crates/hir-ty/src/consteval/tests.rs +++ b/crates/hir-ty/src/consteval/tests.rs @@ -114,7 +114,7 @@ fn eval_goal(db: &TestDB, file_id: FileId) -> Result { _ => None, }) .expect("No const named GOAL found in the test"); - db.const_eval(const_id.into(), Substitution::empty(Interner)) + db.const_eval(const_id.into(), Substitution::empty(Interner), None) } #[test] @@ -1941,6 +1941,33 @@ fn dyn_trait() { "#, 900, ); + check_number( + r#" + //- minicore: coerce_unsized, index, slice + trait A { + fn x(&self) -> i32; + } + + trait B: A {} + + impl A for i32 { + fn x(&self) -> i32 { + 5 + } + } + + impl B for i32 { + + } + + const fn f(x: &dyn B) -> i32 { + x.x() + } + + const GOAL: i32 = f(&2i32); + "#, + 5, + ); } #[test] @@ -2492,6 +2519,28 @@ fn const_trait_assoc() { "#, 5, ); + check_number( + r#" + //- minicore: size_of + //- /a/lib.rs crate:a + use core::mem::size_of; + pub struct S(T); + impl S { + pub const X: usize = core::mem::size_of::(); + } + //- /main.rs crate:main deps:a + use a::{S}; + trait Tr { + type Ty; + } + impl Tr for i32 { + type Ty = u64; + } + struct K(::Ty); + const GOAL: usize = S::>::X; + "#, + 8, + ); check_number( r#" struct S(*mut T); diff --git a/crates/hir-ty/src/db.rs b/crates/hir-ty/src/db.rs index 14b719ea41240..9c96b5ab8dbb6 100644 --- a/crates/hir-ty/src/db.rs +++ b/crates/hir-ty/src/db.rs @@ -77,8 +77,12 @@ pub trait HirDatabase: DefDatabase + Upcast { #[salsa::invoke(crate::consteval::const_eval_query)] #[salsa::cycle(crate::consteval::const_eval_recover)] - fn const_eval(&self, def: GeneralConstId, subst: Substitution) - -> Result; + fn const_eval( + &self, + def: GeneralConstId, + subst: Substitution, + trait_env: Option>, + ) -> Result; #[salsa::invoke(crate::consteval::const_eval_static_query)] #[salsa::cycle(crate::consteval::const_eval_static_recover)] @@ -100,12 +104,16 @@ pub trait HirDatabase: DefDatabase + Upcast { &self, def: AdtId, subst: Substitution, - krate: CrateId, + env: Arc, ) -> Result, LayoutError>; #[salsa::invoke(crate::layout::layout_of_ty_query)] #[salsa::cycle(crate::layout::layout_of_ty_recover)] - fn layout_of_ty(&self, ty: Ty, krate: CrateId) -> Result, LayoutError>; + fn layout_of_ty( + &self, + ty: Ty, + env: Arc, + ) -> Result, LayoutError>; #[salsa::invoke(crate::layout::target_data_layout_query)] fn target_data_layout(&self, krate: CrateId) -> Option>; diff --git a/crates/hir-ty/src/diagnostics/decl_check.rs b/crates/hir-ty/src/diagnostics/decl_check.rs index 73c8ad3dd5a97..5aaa2bcc7c2c3 100644 --- a/crates/hir-ty/src/diagnostics/decl_check.rs +++ b/crates/hir-ty/src/diagnostics/decl_check.rs @@ -14,13 +14,12 @@ mod case_conv; use std::fmt; -use base_db::CrateId; use hir_def::{ data::adt::VariantData, hir::{Pat, PatId}, src::HasSource, - AdtId, AttrDefId, ConstId, EnumId, FunctionId, ItemContainerId, Lookup, ModuleDefId, StaticId, - StructId, + AdtId, AttrDefId, ConstId, DefWithBodyId, EnumId, EnumVariantId, FunctionId, ItemContainerId, + Lookup, ModuleDefId, StaticId, StructId, }; use hir_expand::{ name::{AsName, Name}, @@ -44,13 +43,9 @@ mod allow { pub(super) const NON_CAMEL_CASE_TYPES: &str = "non_camel_case_types"; } -pub fn incorrect_case( - db: &dyn HirDatabase, - krate: CrateId, - owner: ModuleDefId, -) -> Vec { +pub fn incorrect_case(db: &dyn HirDatabase, owner: ModuleDefId) -> Vec { let _p = profile::span("validate_module_item"); - let mut validator = DeclValidator::new(db, krate); + let mut validator = DeclValidator::new(db); validator.validate_item(owner); validator.sink } @@ -120,7 +115,6 @@ pub struct IncorrectCase { pub(super) struct DeclValidator<'a> { db: &'a dyn HirDatabase, - krate: CrateId, pub(super) sink: Vec, } @@ -132,8 +126,8 @@ struct Replacement { } impl<'a> DeclValidator<'a> { - pub(super) fn new(db: &'a dyn HirDatabase, krate: CrateId) -> DeclValidator<'a> { - DeclValidator { db, krate, sink: Vec::new() } + pub(super) fn new(db: &'a dyn HirDatabase) -> DeclValidator<'a> { + DeclValidator { db, sink: Vec::new() } } pub(super) fn validate_item(&mut self, item: ModuleDefId) { @@ -195,8 +189,7 @@ impl<'a> DeclValidator<'a> { AttrDefId::TypeAliasId(_) => None, AttrDefId::GenericParamId(_) => None, } - .map(|mid| self.allowed(mid, allow_name, true)) - .unwrap_or(false) + .is_some_and(|mid| self.allowed(mid, allow_name, true)) } fn validate_func(&mut self, func: FunctionId) { @@ -206,17 +199,7 @@ impl<'a> DeclValidator<'a> { return; } - let body = self.db.body(func.into()); - - // Recursively validate inner scope items, such as static variables and constants. - for (_, block_def_map) in body.blocks(self.db.upcast()) { - for (_, module) in block_def_map.modules() { - for def_id in module.scope.declarations() { - let mut validator = DeclValidator::new(self.db, self.krate); - validator.validate_item(def_id); - } - } - } + self.validate_body_inner_items(func.into()); // Check whether non-snake case identifiers are allowed for this function. if self.allowed(func.into(), allow::NON_SNAKE_CASE, false) { @@ -231,6 +214,8 @@ impl<'a> DeclValidator<'a> { expected_case: CaseType::LowerSnakeCase, }); + let body = self.db.body(func.into()); + // Check the patterns inside the function body. // This includes function parameters. let pats_replacements = body @@ -496,6 +481,11 @@ impl<'a> DeclValidator<'a> { fn validate_enum(&mut self, enum_id: EnumId) { let data = self.db.enum_data(enum_id); + for (local_id, _) in data.variants.iter() { + let variant_id = EnumVariantId { parent: enum_id, local_id }; + self.validate_body_inner_items(variant_id.into()); + } + // Check whether non-camel case names are allowed for this enum. if self.allowed(enum_id.into(), allow::NON_CAMEL_CASE_TYPES, false) { return; @@ -512,13 +502,11 @@ impl<'a> DeclValidator<'a> { // Check the field names. let enum_fields_replacements = data .variants - .iter() - .filter_map(|(_, variant)| { + .values() + .filter_map(|variant| { Some(Replacement { current_name: variant.name.clone(), - suggested_text: to_camel_case( - &variant.name.display(self.db.upcast()).to_string(), - )?, + suggested_text: to_camel_case(&variant.name.to_smol_str())?, expected_case: CaseType::UpperCamelCase, }) }) @@ -622,6 +610,8 @@ impl<'a> DeclValidator<'a> { fn validate_const(&mut self, const_id: ConstId) { let data = self.db.const_data(const_id); + self.validate_body_inner_items(const_id.into()); + if self.allowed(const_id.into(), allow::NON_UPPER_CASE_GLOBAL, false) { return; } @@ -631,7 +621,7 @@ impl<'a> DeclValidator<'a> { None => return, }; - let const_name = name.display(self.db.upcast()).to_string(); + let const_name = name.to_smol_str(); let replacement = if let Some(new_name) = to_upper_snake_case(&const_name) { Replacement { current_name: name.clone(), @@ -670,13 +660,15 @@ impl<'a> DeclValidator<'a> { return; } + self.validate_body_inner_items(static_id.into()); + if self.allowed(static_id.into(), allow::NON_UPPER_CASE_GLOBAL, false) { return; } let name = &data.name; - let static_name = name.display(self.db.upcast()).to_string(); + let static_name = name.to_smol_str(); let replacement = if let Some(new_name) = to_upper_snake_case(&static_name) { Replacement { current_name: name.clone(), @@ -707,4 +699,17 @@ impl<'a> DeclValidator<'a> { self.sink.push(diagnostic); } + + // FIXME: We don't currently validate names within `DefWithBodyId::InTypeConstId`. + /// Recursively validates inner scope items, such as static variables and constants. + fn validate_body_inner_items(&mut self, body_id: DefWithBodyId) { + let body = self.db.body(body_id); + for (_, block_def_map) in body.blocks(self.db.upcast()) { + for (_, module) in block_def_map.modules() { + for def_id in module.scope.declarations() { + self.validate_item(def_id); + } + } + } + } } diff --git a/crates/hir-ty/src/display.rs b/crates/hir-ty/src/display.rs index 8cffdef289ea1..96787959e1f2c 100644 --- a/crates/hir-ty/src/display.rs +++ b/crates/hir-ty/src/display.rs @@ -29,6 +29,7 @@ use itertools::Itertools; use la_arena::ArenaMap; use smallvec::SmallVec; use stdx::never; +use triomphe::Arc; use crate::{ consteval::try_const_usize, @@ -43,7 +44,7 @@ use crate::{ AdtId, AliasEq, AliasTy, Binders, CallableDefId, CallableSig, Const, ConstScalar, ConstValue, DomainGoal, GenericArg, ImplTraitId, Interner, Lifetime, LifetimeData, LifetimeOutlives, MemoryMap, Mutability, OpaqueTy, ProjectionTy, ProjectionTyExt, QuantifiedWhereClause, Scalar, - Substitution, TraitRef, TraitRefExt, Ty, TyExt, WhereClause, + Substitution, TraitEnvironment, TraitRef, TraitRefExt, Ty, TyExt, WhereClause, }; pub trait HirWrite: fmt::Write { @@ -454,7 +455,9 @@ fn render_const_scalar( ) -> Result<(), HirDisplayError> { // FIXME: We need to get krate from the final callers of the hir display // infrastructure and have it here as a field on `f`. - let krate = *f.db.crate_graph().crates_in_topological_order().last().unwrap(); + let trait_env = Arc::new(TraitEnvironment::empty( + *f.db.crate_graph().crates_in_topological_order().last().unwrap(), + )); match ty.kind(Interner) { TyKind::Scalar(s) => match s { Scalar::Bool => write!(f, "{}", if b[0] == 0 { false } else { true }), @@ -497,7 +500,7 @@ fn render_const_scalar( TyKind::Slice(ty) => { let addr = usize::from_le_bytes(b[0..b.len() / 2].try_into().unwrap()); let count = usize::from_le_bytes(b[b.len() / 2..].try_into().unwrap()); - let Ok(layout) = f.db.layout_of_ty(ty.clone(), krate) else { + let Ok(layout) = f.db.layout_of_ty(ty.clone(), trait_env) else { return f.write_str(""); }; let size_one = layout.size.bytes_usize(); @@ -523,7 +526,7 @@ fn render_const_scalar( let Ok(t) = memory_map.vtable.ty(ty_id) else { return f.write_str(""); }; - let Ok(layout) = f.db.layout_of_ty(t.clone(), krate) else { + let Ok(layout) = f.db.layout_of_ty(t.clone(), trait_env) else { return f.write_str(""); }; let size = layout.size.bytes_usize(); @@ -555,7 +558,7 @@ fn render_const_scalar( return f.write_str(""); } }); - let Ok(layout) = f.db.layout_of_ty(t.clone(), krate) else { + let Ok(layout) = f.db.layout_of_ty(t.clone(), trait_env) else { return f.write_str(""); }; let size = layout.size.bytes_usize(); @@ -567,7 +570,7 @@ fn render_const_scalar( } }, TyKind::Tuple(_, subst) => { - let Ok(layout) = f.db.layout_of_ty(ty.clone(), krate) else { + let Ok(layout) = f.db.layout_of_ty(ty.clone(), trait_env.clone()) else { return f.write_str(""); }; f.write_str("(")?; @@ -580,7 +583,7 @@ fn render_const_scalar( } let ty = ty.assert_ty_ref(Interner); // Tuple only has type argument let offset = layout.fields.offset(id).bytes_usize(); - let Ok(layout) = f.db.layout_of_ty(ty.clone(), krate) else { + let Ok(layout) = f.db.layout_of_ty(ty.clone(), trait_env.clone()) else { f.write_str("")?; continue; }; @@ -590,7 +593,7 @@ fn render_const_scalar( f.write_str(")") } TyKind::Adt(adt, subst) => { - let Ok(layout) = f.db.layout_of_adt(adt.0, subst.clone(), krate) else { + let Ok(layout) = f.db.layout_of_adt(adt.0, subst.clone(), trait_env.clone()) else { return f.write_str(""); }; match adt.0 { @@ -602,7 +605,7 @@ fn render_const_scalar( &data.variant_data, f, &field_types, - adt.0.module(f.db.upcast()).krate(), + f.db.trait_environment(adt.0.into()), &layout, subst, b, @@ -614,7 +617,7 @@ fn render_const_scalar( } hir_def::AdtId::EnumId(e) => { let Some((var_id, var_layout)) = - detect_variant_from_bytes(&layout, f.db, krate, b, e) + detect_variant_from_bytes(&layout, f.db, trait_env.clone(), b, e) else { return f.write_str(""); }; @@ -626,7 +629,7 @@ fn render_const_scalar( &data.variant_data, f, &field_types, - adt.0.module(f.db.upcast()).krate(), + f.db.trait_environment(adt.0.into()), &var_layout, subst, b, @@ -645,7 +648,7 @@ fn render_const_scalar( let Some(len) = try_const_usize(f.db, len) else { return f.write_str(""); }; - let Ok(layout) = f.db.layout_of_ty(ty.clone(), krate) else { + let Ok(layout) = f.db.layout_of_ty(ty.clone(), trait_env) else { return f.write_str(""); }; let size_one = layout.size.bytes_usize(); @@ -684,7 +687,7 @@ fn render_variant_after_name( data: &VariantData, f: &mut HirFormatter<'_>, field_types: &ArenaMap>, - krate: CrateId, + trait_env: Arc, layout: &Layout, subst: &Substitution, b: &[u8], @@ -695,7 +698,7 @@ fn render_variant_after_name( let render_field = |f: &mut HirFormatter<'_>, id: LocalFieldId| { let offset = layout.fields.offset(u32::from(id.into_raw()) as usize).bytes_usize(); let ty = field_types[id].clone().substitute(Interner, subst); - let Ok(layout) = f.db.layout_of_ty(ty.clone(), krate) else { + let Ok(layout) = f.db.layout_of_ty(ty.clone(), trait_env.clone()) else { return f.write_str(""); }; let size = layout.size.bytes_usize(); diff --git a/crates/hir-ty/src/infer/expr.rs b/crates/hir-ty/src/infer/expr.rs index 4b14345aa39f1..72e6443beb77b 100644 --- a/crates/hir-ty/src/infer/expr.rs +++ b/crates/hir-ty/src/infer/expr.rs @@ -1665,6 +1665,7 @@ impl InferenceContext<'_> { // the parameter to coerce to the expected type (for example in // `coerce_unsize_expected_type_4`). let param_ty = self.normalize_associated_types_in(param_ty); + let expected_ty = self.normalize_associated_types_in(expected_ty); let expected = Expectation::rvalue_hint(self, expected_ty); // infer with the expected type we have... let ty = self.infer_expr_inner(arg, &expected); diff --git a/crates/hir-ty/src/infer/unify.rs b/crates/hir-ty/src/infer/unify.rs index 385f39f5374d0..0fb71135b4de2 100644 --- a/crates/hir-ty/src/infer/unify.rs +++ b/crates/hir-ty/src/infer/unify.rs @@ -252,7 +252,8 @@ impl<'a> InferenceTable<'a> { // and registering an obligation. But it needs chalk support, so we handle the most basic // case (a non associated const without generic parameters) manually. if subst.len(Interner) == 0 { - if let Ok(eval) = self.db.const_eval((*c_id).into(), subst.clone()) + if let Ok(eval) = + self.db.const_eval((*c_id).into(), subst.clone(), None) { eval } else { @@ -785,7 +786,7 @@ impl<'a> InferenceTable<'a> { crate::ConstScalar::Unknown => self.new_const_var(data.ty.clone()), // try to evaluate unevaluated const. Replace with new var if const eval failed. crate::ConstScalar::UnevaluatedConst(id, subst) => { - if let Ok(eval) = self.db.const_eval(*id, subst.clone()) { + if let Ok(eval) = self.db.const_eval(*id, subst.clone(), None) { eval } else { self.new_const_var(data.ty.clone()) diff --git a/crates/hir-ty/src/layout.rs b/crates/hir-ty/src/layout.rs index 72e2bcc55590f..ffc7a6f2ebd7c 100644 --- a/crates/hir-ty/src/layout.rs +++ b/crates/hir-ty/src/layout.rs @@ -1,6 +1,5 @@ //! Compute the binary representation of a type -use base_db::CrateId; use chalk_ir::{AdtId, FloatTy, IntTy, TyKind, UintTy}; use hir_def::{ layout::{ @@ -61,7 +60,6 @@ pub enum LayoutError { } struct LayoutCx<'a> { - krate: CrateId, target: &'a TargetDataLayout, } @@ -82,7 +80,7 @@ fn layout_of_simd_ty( db: &dyn HirDatabase, id: StructId, subst: &Substitution, - krate: CrateId, + env: Arc, dl: &TargetDataLayout, ) -> Result, LayoutError> { let fields = db.field_types(id.into()); @@ -111,7 +109,7 @@ fn layout_of_simd_ty( // * the homogeneous field type and the number of fields. let (e_ty, e_len, is_array) = if let TyKind::Array(e_ty, _) = f0_ty.kind(Interner) { // Extract the number of elements from the layout of the array field: - let FieldsShape::Array { count, .. } = db.layout_of_ty(f0_ty.clone(), krate)?.fields else { + let FieldsShape::Array { count, .. } = db.layout_of_ty(f0_ty.clone(), env.clone())?.fields else { user_error!("Array with non array layout"); }; @@ -122,7 +120,7 @@ fn layout_of_simd_ty( }; // Compute the ABI of the element type: - let e_ly = db.layout_of_ty(e_ty, krate)?; + let e_ly = db.layout_of_ty(e_ty, env.clone())?; let Abi::Scalar(e_abi) = e_ly.abi else { user_error!("simd type with inner non scalar type"); }; @@ -152,25 +150,25 @@ fn layout_of_simd_ty( pub fn layout_of_ty_query( db: &dyn HirDatabase, ty: Ty, - krate: CrateId, + trait_env: Arc, ) -> Result, LayoutError> { + let krate = trait_env.krate; let Some(target) = db.target_data_layout(krate) else { return Err(LayoutError::TargetLayoutNotAvailable); }; - let cx = LayoutCx { krate, target: &target }; + let cx = LayoutCx { target: &target }; let dl = &*cx.current_data_layout(); - let trait_env = Arc::new(TraitEnvironment::empty(krate)); - let ty = normalize(db, trait_env, ty.clone()); + let ty = normalize(db, trait_env.clone(), ty.clone()); let result = match ty.kind(Interner) { TyKind::Adt(AdtId(def), subst) => { if let hir_def::AdtId::StructId(s) = def { let data = db.struct_data(*s); let repr = data.repr.unwrap_or_default(); if repr.simd() { - return layout_of_simd_ty(db, *s, subst, krate, &target); + return layout_of_simd_ty(db, *s, subst, trait_env.clone(), &target); } }; - return db.layout_of_adt(*def, subst.clone(), krate); + return db.layout_of_adt(*def, subst.clone(), trait_env.clone()); } TyKind::Scalar(s) => match s { chalk_ir::Scalar::Bool => Layout::scalar( @@ -228,7 +226,7 @@ pub fn layout_of_ty_query( let fields = tys .iter(Interner) - .map(|k| db.layout_of_ty(k.assert_ty_ref(Interner).clone(), krate)) + .map(|k| db.layout_of_ty(k.assert_ty_ref(Interner).clone(), trait_env.clone())) .collect::, _>>()?; let fields = fields.iter().map(|it| &**it).collect::>(); let fields = fields.iter().collect::>(); @@ -238,7 +236,7 @@ pub fn layout_of_ty_query( let count = try_const_usize(db, &count).ok_or(LayoutError::UserError( "unevaluated or mistyped const generic parameter".to_string(), ))? as u64; - let element = db.layout_of_ty(element.clone(), krate)?; + let element = db.layout_of_ty(element.clone(), trait_env.clone())?; let size = element.size.checked_mul(count, dl).ok_or(LayoutError::SizeOverflow)?; let abi = if count != 0 && matches!(element.abi, Abi::Uninhabited) { @@ -259,7 +257,7 @@ pub fn layout_of_ty_query( } } TyKind::Slice(element) => { - let element = db.layout_of_ty(element.clone(), krate)?; + let element = db.layout_of_ty(element.clone(), trait_env.clone())?; Layout { variants: Variants::Single { index: struct_variant_idx() }, fields: FieldsShape::Array { stride: element.size, count: 0 }, @@ -335,7 +333,7 @@ pub fn layout_of_ty_query( match impl_trait_id { crate::ImplTraitId::ReturnTypeImplTrait(func, idx) => { let infer = db.infer(func.into()); - return db.layout_of_ty(infer.type_of_rpit[idx].clone(), krate); + return db.layout_of_ty(infer.type_of_rpit[idx].clone(), trait_env.clone()); } crate::ImplTraitId::AsyncBlockTypeImplTrait(_, _) => { return Err(LayoutError::NotImplemented) @@ -351,7 +349,7 @@ pub fn layout_of_ty_query( .map(|it| { db.layout_of_ty( it.ty.clone().substitute(Interner, ClosureSubst(subst).parent_subst()), - krate, + trait_env.clone(), ) }) .collect::, _>>()?; @@ -377,7 +375,7 @@ pub fn layout_of_ty_recover( _: &dyn HirDatabase, _: &[String], _: &Ty, - _: &CrateId, + _: &Arc, ) -> Result, LayoutError> { user_error!("infinite sized recursive type"); } diff --git a/crates/hir-ty/src/layout/adt.rs b/crates/hir-ty/src/layout/adt.rs index 19d5e98e73898..1c92e80f3355b 100644 --- a/crates/hir-ty/src/layout/adt.rs +++ b/crates/hir-ty/src/layout/adt.rs @@ -2,7 +2,6 @@ use std::{cmp, ops::Bound}; -use base_db::CrateId; use hir_def::{ data::adt::VariantData, layout::{Integer, LayoutCalculator, ReprOptions, TargetDataLayout}, @@ -16,7 +15,7 @@ use crate::{ db::HirDatabase, lang_items::is_unsafe_cell, layout::{field_ty, Layout, LayoutError, RustcEnumVariantIdx}, - Substitution, + Substitution, TraitEnvironment, }; use super::LayoutCx; @@ -29,17 +28,18 @@ pub fn layout_of_adt_query( db: &dyn HirDatabase, def: AdtId, subst: Substitution, - krate: CrateId, + trait_env: Arc, ) -> Result, LayoutError> { + let krate = trait_env.krate; let Some(target) = db.target_data_layout(krate) else { return Err(LayoutError::TargetLayoutNotAvailable); }; - let cx = LayoutCx { krate, target: &target }; + let cx = LayoutCx { target: &target }; let dl = cx.current_data_layout(); let handle_variant = |def: VariantId, var: &VariantData| { var.fields() .iter() - .map(|(fd, _)| db.layout_of_ty(field_ty(db, def, fd, &subst), cx.krate)) + .map(|(fd, _)| db.layout_of_ty(field_ty(db, def, fd, &subst), trait_env.clone())) .collect::, _>>() }; let (variants, repr) = match def { @@ -134,7 +134,7 @@ pub fn layout_of_adt_recover( _: &[String], _: &AdtId, _: &Substitution, - _: &CrateId, + _: &Arc, ) -> Result, LayoutError> { user_error!("infinite sized recursive type"); } diff --git a/crates/hir-ty/src/layout/tests.rs b/crates/hir-ty/src/layout/tests.rs index a3ced2bac0a2c..333ad473a8b2c 100644 --- a/crates/hir-ty/src/layout/tests.rs +++ b/crates/hir-ty/src/layout/tests.rs @@ -26,7 +26,7 @@ fn eval_goal(ra_fixture: &str, minicore: &str) -> Result, LayoutErro ); let (db, file_ids) = TestDB::with_many_files(&ra_fixture); - let (adt_or_type_alias_id, module_id) = file_ids + let adt_or_type_alias_id = file_ids .into_iter() .find_map(|file_id| { let module_id = db.module_for_file(file_id); @@ -47,7 +47,7 @@ fn eval_goal(ra_fixture: &str, minicore: &str) -> Result, LayoutErro } _ => None, })?; - Some((adt_or_type_alias_id, module_id)) + Some(adt_or_type_alias_id) }) .unwrap(); let goal_ty = match adt_or_type_alias_id { @@ -58,7 +58,13 @@ fn eval_goal(ra_fixture: &str, minicore: &str) -> Result, LayoutErro db.ty(ty_id.into()).substitute(Interner, &Substitution::empty(Interner)) } }; - db.layout_of_ty(goal_ty, module_id.krate()) + db.layout_of_ty( + goal_ty, + db.trait_environment(match adt_or_type_alias_id { + Either::Left(adt) => hir_def::GenericDefId::AdtId(adt), + Either::Right(ty) => hir_def::GenericDefId::TypeAliasId(ty), + }), + ) } /// A version of `eval_goal` for types that can not be expressed in ADTs, like closures and `impl Trait` @@ -72,7 +78,7 @@ fn eval_expr(ra_fixture: &str, minicore: &str) -> Result, LayoutErro let module_id = db.module_for_file(file_id); let def_map = module_id.def_map(&db); let scope = &def_map[module_id.local_id].scope; - let adt_id = scope + let function_id = scope .declarations() .find_map(|x| match x { hir_def::ModuleDefId::FunctionId(x) => { @@ -82,11 +88,11 @@ fn eval_expr(ra_fixture: &str, minicore: &str) -> Result, LayoutErro _ => None, }) .unwrap(); - let hir_body = db.body(adt_id.into()); + let hir_body = db.body(function_id.into()); let b = hir_body.bindings.iter().find(|x| x.1.name.to_smol_str() == "goal").unwrap().0; - let infer = db.infer(adt_id.into()); + let infer = db.infer(function_id.into()); let goal_ty = infer.type_of_binding[b].clone(); - db.layout_of_ty(goal_ty, module_id.krate()) + db.layout_of_ty(goal_ty, db.trait_environment(function_id.into())) } #[track_caller] diff --git a/crates/hir-ty/src/method_resolution.rs b/crates/hir-ty/src/method_resolution.rs index 5e1040bc6aac2..f3a5f69b2a63c 100644 --- a/crates/hir-ty/src/method_resolution.rs +++ b/crates/hir-ty/src/method_resolution.rs @@ -665,13 +665,21 @@ pub fn is_dyn_method( }; let self_ty = trait_ref.self_type_parameter(Interner); if let TyKind::Dyn(d) = self_ty.kind(Interner) { - let is_my_trait_in_bounds = - d.bounds.skip_binders().as_slice(Interner).iter().any(|it| match it.skip_binders() { - // rustc doesn't accept `impl Foo<2> for dyn Foo<5>`, so if the trait id is equal, no matter - // what the generics are, we are sure that the method is come from the vtable. - WhereClause::Implemented(tr) => tr.trait_id == trait_ref.trait_id, - _ => false, - }); + let is_my_trait_in_bounds = d + .bounds + .skip_binders() + .as_slice(Interner) + .iter() + .map(|it| it.skip_binders()) + .flat_map(|it| match it { + WhereClause::Implemented(tr) => { + all_super_traits(db.upcast(), from_chalk_trait_id(tr.trait_id)) + } + _ => smallvec![], + }) + // rustc doesn't accept `impl Foo<2> for dyn Foo<5>`, so if the trait id is equal, no matter + // what the generics are, we are sure that the method is come from the vtable. + .any(|x| x == trait_id); if is_my_trait_in_bounds { return Some(fn_params); } @@ -1504,7 +1512,7 @@ fn autoderef_method_receiver( ty: Ty, ) -> Vec<(Canonical, ReceiverAdjustments)> { let mut deref_chain: Vec<_> = Vec::new(); - let mut autoderef = autoderef::Autoderef::new(table, ty, true); + let mut autoderef = autoderef::Autoderef::new(table, ty, false); while let Some((ty, derefs)) = autoderef.next() { deref_chain.push(( autoderef.table.canonicalize(ty).value, diff --git a/crates/hir-ty/src/mir.rs b/crates/hir-ty/src/mir.rs index da5b496e141ee..922e49d281d03 100644 --- a/crates/hir-ty/src/mir.rs +++ b/crates/hir-ty/src/mir.rs @@ -142,7 +142,7 @@ impl ProjectionElem { closure_field: impl FnOnce(ClosureId, &Substitution, usize) -> Ty, krate: CrateId, ) -> Ty { - if matches!(base.data(Interner).kind, TyKind::Alias(_) | TyKind::AssociatedType(..)) { + if matches!(base.kind(Interner), TyKind::Alias(_) | TyKind::AssociatedType(..)) { base = normalize( db, // FIXME: we should get this from caller @@ -151,7 +151,7 @@ impl ProjectionElem { ); } match self { - ProjectionElem::Deref => match &base.data(Interner).kind { + ProjectionElem::Deref => match &base.kind(Interner) { TyKind::Raw(_, inner) | TyKind::Ref(_, _, inner) => inner.clone(), TyKind::Adt(adt, subst) if is_box(db, adt.0) => { subst.at(Interner, 0).assert_ty_ref(Interner).clone() @@ -161,7 +161,7 @@ impl ProjectionElem { return TyKind::Error.intern(Interner); } }, - ProjectionElem::Field(f) => match &base.data(Interner).kind { + ProjectionElem::Field(f) => match &base.kind(Interner) { TyKind::Adt(_, subst) => { db.field_types(f.parent)[f.local_id].clone().substitute(Interner, subst) } @@ -170,7 +170,7 @@ impl ProjectionElem { return TyKind::Error.intern(Interner); } }, - ProjectionElem::TupleOrClosureField(f) => match &base.data(Interner).kind { + ProjectionElem::TupleOrClosureField(f) => match &base.kind(Interner) { TyKind::Tuple(_, subst) => subst .as_slice(Interner) .get(*f) @@ -187,7 +187,7 @@ impl ProjectionElem { } }, ProjectionElem::ConstantIndex { .. } | ProjectionElem::Index(_) => { - match &base.data(Interner).kind { + match &base.kind(Interner) { TyKind::Array(inner, _) | TyKind::Slice(inner) => inner.clone(), _ => { never!("Overloaded index is not a projection"); @@ -195,7 +195,7 @@ impl ProjectionElem { } } } - &ProjectionElem::Subslice { from, to } => match &base.data(Interner).kind { + &ProjectionElem::Subslice { from, to } => match &base.kind(Interner) { TyKind::Array(inner, c) => { let next_c = usize_const( db, diff --git a/crates/hir-ty/src/mir/eval.rs b/crates/hir-ty/src/mir/eval.rs index d7820de629ae4..7bd2756c14f40 100644 --- a/crates/hir-ty/src/mir/eval.rs +++ b/crates/hir-ty/src/mir/eval.rs @@ -484,9 +484,10 @@ pub fn interpret_mir( // a zero size, hoping that they are all outside of our current body. Even without a fix for #7434, we can // (and probably should) do better here, for example by excluding bindings outside of the target expression. assert_placeholder_ty_is_unused: bool, + trait_env: Option>, ) -> (Result, String, String) { let ty = body.locals[return_slot()].ty.clone(); - let mut evaluator = Evaluator::new(db, body.owner, assert_placeholder_ty_is_unused); + let mut evaluator = Evaluator::new(db, body.owner, assert_placeholder_ty_is_unused, trait_env); let it: Result = (|| { if evaluator.ptr_size() != std::mem::size_of::() { not_supported!("targets with different pointer size from host"); @@ -512,9 +513,9 @@ impl Evaluator<'_> { db: &'a dyn HirDatabase, owner: DefWithBodyId, assert_placeholder_ty_is_unused: bool, + trait_env: Option>, ) -> Evaluator<'a> { let crate_id = owner.module(db.upcast()).krate(); - let trait_env = db.trait_environment_for_body(owner); Evaluator { stack: vec![0], heap: vec![0], @@ -524,7 +525,7 @@ impl Evaluator<'_> { static_locations: HashMap::default(), db, random_state: oorandom::Rand64::new(0), - trait_env, + trait_env: trait_env.unwrap_or_else(|| db.trait_environment_for_body(owner)), crate_id, stdout: vec![], stderr: vec![], @@ -634,7 +635,7 @@ impl Evaluator<'_> { addr = addr.offset(ty_size * offset); } &ProjectionElem::Subslice { from, to } => { - let inner_ty = match &ty.data(Interner).kind { + let inner_ty = match &ty.kind(Interner) { TyKind::Array(inner, _) | TyKind::Slice(inner) => inner.clone(), _ => TyKind::Error.intern(Interner), }; @@ -694,14 +695,14 @@ impl Evaluator<'_> { } let r = self .db - .layout_of_ty(ty.clone(), self.crate_id) + .layout_of_ty(ty.clone(), self.trait_env.clone()) .map_err(|e| MirEvalError::LayoutError(e, ty.clone()))?; self.layout_cache.borrow_mut().insert(ty.clone(), r.clone()); Ok(r) } fn layout_adt(&self, adt: AdtId, subst: Substitution) -> Result> { - self.db.layout_of_adt(adt, subst.clone(), self.crate_id).map_err(|e| { + self.db.layout_of_adt(adt, subst.clone(), self.trait_env.clone()).map_err(|e| { MirEvalError::LayoutError(e, TyKind::Adt(chalk_ir::AdtId(adt), subst).intern(Interner)) }) } @@ -793,7 +794,7 @@ impl Evaluator<'_> { .iter() .map(|it| self.operand_ty_and_eval(it, &mut locals)) .collect::>>()?; - let stack_frame = match &fn_ty.data(Interner).kind { + let stack_frame = match &fn_ty.kind(Interner) { TyKind::Function(_) => { let bytes = self.eval_operand(func, &mut locals)?; self.exec_fn_pointer( @@ -1255,7 +1256,7 @@ impl Evaluator<'_> { PointerCast::ReifyFnPointer | PointerCast::ClosureFnPointer(_) => { let current_ty = self.operand_ty(operand, locals)?; if let TyKind::FnDef(_, _) | TyKind::Closure(_, _) = - ¤t_ty.data(Interner).kind + ¤t_ty.kind(Interner) { let id = self.vtable_map.id(current_ty); let ptr_size = self.ptr_size(); @@ -1408,8 +1409,8 @@ impl Evaluator<'_> { addr: Interval, ) -> Result { use IntervalOrOwned::*; - Ok(match &target_ty.data(Interner).kind { - TyKind::Slice(_) => match ¤t_ty.data(Interner).kind { + Ok(match &target_ty.kind(Interner) { + TyKind::Slice(_) => match ¤t_ty.kind(Interner) { TyKind::Array(_, size) => { let len = match try_const_usize(self.db, size) { None => { @@ -1435,7 +1436,7 @@ impl Evaluator<'_> { r.extend(vtable.to_le_bytes().into_iter()); Owned(r) } - TyKind::Adt(id, target_subst) => match ¤t_ty.data(Interner).kind { + TyKind::Adt(id, target_subst) => match ¤t_ty.kind(Interner) { TyKind::Adt(current_id, current_subst) => { if id != current_id { not_supported!("unsizing struct with different type"); @@ -1582,10 +1583,13 @@ impl Evaluator<'_> { const_id = hir_def::GeneralConstId::ConstId(c); subst = s; } - result_owner = self.db.const_eval(const_id.into(), subst).map_err(|e| { - let name = const_id.name(self.db.upcast()); - MirEvalError::ConstEvalError(name, Box::new(e)) - })?; + result_owner = self + .db + .const_eval(const_id.into(), subst, Some(self.trait_env.clone())) + .map_err(|e| { + let name = const_id.name(self.db.upcast()); + MirEvalError::ConstEvalError(name, Box::new(e)) + })?; if let chalk_ir::ConstValue::Concrete(c) = &result_owner.data(Interner).value { if let ConstScalar::Bytes(v, mm) = &c.interned { break 'b (v, mm); @@ -1818,9 +1822,13 @@ impl Evaluator<'_> { } AdtId::EnumId(e) => { let layout = this.layout(ty)?; - if let Some((v, l)) = - detect_variant_from_bytes(&layout, this.db, this.crate_id, bytes, e) - { + if let Some((v, l)) = detect_variant_from_bytes( + &layout, + this.db, + this.trait_env.clone(), + bytes, + e, + ) { let data = &this.db.enum_data(e).variants[v].variant_data; let field_types = this .db @@ -1931,7 +1939,7 @@ impl Evaluator<'_> { ) -> Result> { let id = from_bytes!(usize, bytes.get(self)?); let next_ty = self.vtable_map.ty(id)?.clone(); - match &next_ty.data(Interner).kind { + match &next_ty.kind(Interner) { TyKind::FnDef(def, generic_args) => { self.exec_fn_def(*def, generic_args, destination, args, &locals, target_bb, span) } @@ -2182,7 +2190,7 @@ impl Evaluator<'_> { let size = self.size_of_sized(&func_ty, locals, "self type of fn trait")?; func_data = Interval { addr: Address::from_bytes(func_data.get(self)?)?, size }; } - match &func_ty.data(Interner).kind { + match &func_ty.kind(Interner) { TyKind::FnDef(def, subst) => { return self.exec_fn_def( *def, @@ -2409,7 +2417,7 @@ pub fn render_const_using_debug_impl( owner: ConstId, c: &Const, ) -> Result { - let mut evaluator = Evaluator::new(db, owner.into(), false); + let mut evaluator = Evaluator::new(db, owner.into(), false, None); let locals = &Locals { ptr: ArenaMap::new(), body: db diff --git a/crates/hir-ty/src/mir/eval/tests.rs b/crates/hir-ty/src/mir/eval/tests.rs index 03c083bac4227..93f4b699147fc 100644 --- a/crates/hir-ty/src/mir/eval/tests.rs +++ b/crates/hir-ty/src/mir/eval/tests.rs @@ -30,7 +30,7 @@ fn eval_main(db: &TestDB, file_id: FileId) -> Result<(String, String), MirEvalEr db.trait_environment(func_id.into()), ) .map_err(|e| MirEvalError::MirLowerError(func_id.into(), e))?; - let (result, stdout, stderr) = interpret_mir(db, body, false); + let (result, stdout, stderr) = interpret_mir(db, body, false, None); result?; Ok((stdout, stderr)) } diff --git a/crates/hir-ty/src/mir/lower.rs b/crates/hir-ty/src/mir/lower.rs index 3610858790412..9f25175a3a9f3 100644 --- a/crates/hir-ty/src/mir/lower.rs +++ b/crates/hir-ty/src/mir/lower.rs @@ -633,7 +633,7 @@ impl<'ctx> MirLowerCtx<'ctx> { ); } let callee_ty = self.expr_ty_after_adjustments(*callee); - match &callee_ty.data(Interner).kind { + match &callee_ty.kind(Interner) { chalk_ir::TyKind::FnDef(..) => { let func = Operand::from_bytes(vec![], callee_ty.clone()); self.lower_call_and_args( @@ -1229,7 +1229,7 @@ impl<'ctx> MirLowerCtx<'ctx> { } Expr::Array(l) => match l { Array::ElementList { elements, .. } => { - let elem_ty = match &self.expr_ty_without_adjust(expr_id).data(Interner).kind { + let elem_ty = match &self.expr_ty_without_adjust(expr_id).kind(Interner) { TyKind::Array(ty, _) => ty.clone(), _ => { return Err(MirLowerError::TypeError( @@ -1260,7 +1260,7 @@ impl<'ctx> MirLowerCtx<'ctx> { else { return Ok(None); }; - let len = match &self.expr_ty_without_adjust(expr_id).data(Interner).kind { + let len = match &self.expr_ty_without_adjust(expr_id).kind(Interner) { TyKind::Array(_, len) => len.clone(), _ => { return Err(MirLowerError::TypeError( @@ -1341,7 +1341,7 @@ impl<'ctx> MirLowerCtx<'ctx> { fn lower_literal_to_operand(&mut self, ty: Ty, l: &Literal) -> Result { let size = self .db - .layout_of_ty(ty.clone(), self.owner.module(self.db.upcast()).krate())? + .layout_of_ty(ty.clone(), self.db.trait_environment_for_body(self.owner))? .size .bytes_usize(); let bytes = match l { @@ -1355,7 +1355,6 @@ impl<'ctx> MirLowerCtx<'ctx> { return Ok(Operand::from_concrete_const(data, mm, ty)); } hir_def::hir::Literal::CString(b) => { - let b = b.as_bytes(); let bytes = b.iter().copied().chain(iter::once(0)).collect::>(); let mut data = Vec::with_capacity(mem::size_of::() * 2); @@ -1418,7 +1417,7 @@ impl<'ctx> MirLowerCtx<'ctx> { } else { let name = const_id.name(self.db.upcast()); self.db - .const_eval(const_id.into(), subst) + .const_eval(const_id.into(), subst, None) .map_err(|e| MirLowerError::ConstEvalError(name, Box::new(e)))? }; Ok(Operand::Constant(c)) diff --git a/crates/hir-ty/src/tests/method_resolution.rs b/crates/hir-ty/src/tests/method_resolution.rs index a8e146b096a4d..c837fae3fef4f 100644 --- a/crates/hir-ty/src/tests/method_resolution.rs +++ b/crates/hir-ty/src/tests/method_resolution.rs @@ -1236,6 +1236,27 @@ fn main() { ); } +#[test] +fn inherent_method_ref_self_deref_raw() { + check_types( + r#" +struct Val; + +impl Val { + pub fn method(&self) -> u32 { + 0 + } +} + +fn main() { + let foo: *const Val; + foo.method(); + // ^^^^^^^^^^^^ {unknown} +} +"#, + ); +} + #[test] fn trait_method_deref_raw() { check_types( diff --git a/crates/hir-ty/src/tests/traits.rs b/crates/hir-ty/src/tests/traits.rs index 5f5cd794512c6..542df8b3468fa 100644 --- a/crates/hir-ty/src/tests/traits.rs +++ b/crates/hir-ty/src/tests/traits.rs @@ -4434,3 +4434,47 @@ fn test(v: S) { "#, ); } + +#[test] +fn associated_type_in_argument() { + check( + r#" + trait A { + fn m(&self) -> i32; + } + + fn x(k: &::Ty) { + k.m(); + } + + struct X; + struct Y; + + impl A for X { + fn m(&self) -> i32 { + 8 + } + } + + impl A for Y { + fn m(&self) -> i32 { + 32 + } + } + + trait B { + type Ty: A; + } + + impl B for u16 { + type Ty = X; + } + + fn ttt() { + let inp = Y; + x::(&inp); + //^^^^ expected &X, got &Y + } + "#, + ); +} diff --git a/crates/hir-ty/src/utils.rs b/crates/hir-ty/src/utils.rs index 0c38fe5d6ab33..75b8b9afa7085 100644 --- a/crates/hir-ty/src/utils.rs +++ b/crates/hir-ty/src/utils.rs @@ -28,14 +28,15 @@ use intern::Interned; use rustc_hash::FxHashSet; use smallvec::{smallvec, SmallVec}; use stdx::never; +use triomphe::Arc; use crate::{ consteval::unknown_const, db::HirDatabase, layout::{Layout, TagEncoding}, mir::pad16, - ChalkTraitId, Const, ConstScalar, GenericArg, Interner, Substitution, TraitRef, TraitRefExt, - Ty, WhereClause, + ChalkTraitId, Const, ConstScalar, GenericArg, Interner, Substitution, TraitEnvironment, + TraitRef, TraitRefExt, Ty, WhereClause, }; pub(crate) fn fn_traits( @@ -417,7 +418,7 @@ impl FallibleTypeFolder for UnevaluatedConstEvaluatorFolder<'_> { ) -> Result { if let chalk_ir::ConstValue::Concrete(c) = &constant.data(Interner).value { if let ConstScalar::UnevaluatedConst(id, subst) = &c.interned { - if let Ok(eval) = self.db.const_eval(*id, subst.clone()) { + if let Ok(eval) = self.db.const_eval(*id, subst.clone(), None) { return Ok(eval); } else { return Ok(unknown_const(constant.data(Interner).ty.clone())); @@ -431,10 +432,11 @@ impl FallibleTypeFolder for UnevaluatedConstEvaluatorFolder<'_> { pub(crate) fn detect_variant_from_bytes<'a>( layout: &'a Layout, db: &dyn HirDatabase, - krate: CrateId, + trait_env: Arc, b: &[u8], e: EnumId, ) -> Option<(LocalEnumVariantId, &'a Layout)> { + let krate = trait_env.krate; let (var_id, var_layout) = match &layout.variants { hir_def::layout::Variants::Single { index } => (index.0, &*layout), hir_def::layout::Variants::Multiple { tag, tag_encoding, variants, .. } => { diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index f8d9398ae2c55..b094bb7a06883 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -378,11 +378,6 @@ impl ModuleDef { ModuleDef::BuiltinType(_) | ModuleDef::Macro(_) => return Vec::new(), }; - let module = match self.module(db) { - Some(it) => it, - None => return Vec::new(), - }; - let mut acc = Vec::new(); match self.as_def_with_body() { @@ -390,7 +385,7 @@ impl ModuleDef { def.diagnostics(db, &mut acc); } None => { - for diag in hir_ty::diagnostics::incorrect_case(db, module.id.krate(), id) { + for diag in hir_ty::diagnostics::incorrect_case(db, id) { acc.push(diag.into()) } } @@ -965,8 +960,15 @@ impl Field { } pub fn layout(&self, db: &dyn HirDatabase) -> Result { - db.layout_of_ty(self.ty(db).ty.clone(), self.parent.module(db).krate().into()) - .map(|layout| Layout(layout, db.target_data_layout(self.krate(db).into()).unwrap())) + db.layout_of_ty( + self.ty(db).ty.clone(), + db.trait_environment(match hir_def::VariantId::from(self.parent) { + hir_def::VariantId::EnumVariantId(id) => GenericDefId::EnumVariantId(id), + hir_def::VariantId::StructId(id) => GenericDefId::AdtId(id.into()), + hir_def::VariantId::UnionId(id) => GenericDefId::AdtId(id.into()), + }), + ) + .map(|layout| Layout(layout, db.target_data_layout(self.krate(db).into()).unwrap())) } pub fn parent_def(&self, _db: &dyn HirDatabase) -> VariantDef { @@ -1246,8 +1248,12 @@ impl Adt { return Err(LayoutError::HasPlaceholder); } let krate = self.krate(db).id; - db.layout_of_adt(self.into(), Substitution::empty(Interner), krate) - .map(|layout| Layout(layout, db.target_data_layout(krate).unwrap())) + db.layout_of_adt( + self.into(), + Substitution::empty(Interner), + db.trait_environment(self.into()), + ) + .map(|layout| Layout(layout, db.target_data_layout(krate).unwrap())) } /// Turns this ADT into a type. Any type parameters of the ADT will be @@ -1820,7 +1826,7 @@ impl DefWithBody { // FIXME: don't ignore diagnostics for in type const DefWithBody::InTypeConst(_) => return, }; - for diag in hir_ty::diagnostics::incorrect_case(db, krate, def.into()) { + for diag in hir_ty::diagnostics::incorrect_case(db, def.into()) { acc.push(diag.into()) } } @@ -1987,7 +1993,7 @@ impl Function { return r; } }; - let (result, stdout, stderr) = interpret_mir(db, body, false); + let (result, stdout, stderr) = interpret_mir(db, body, false, None); let mut text = match result { Ok(_) => "pass".to_string(), Err(e) => { @@ -2156,7 +2162,7 @@ impl Const { } pub fn render_eval(self, db: &dyn HirDatabase) -> Result { - let c = db.const_eval(self.id.into(), Substitution::empty(Interner))?; + let c = db.const_eval(self.id.into(), Substitution::empty(Interner), None)?; let data = &c.data(Interner); if let TyKind::Scalar(s) = data.ty.kind(Interner) { if matches!(s, Scalar::Int(_) | Scalar::Uint(_)) { @@ -4322,7 +4328,7 @@ impl Type { } pub fn layout(&self, db: &dyn HirDatabase) -> Result { - db.layout_of_ty(self.ty.clone(), self.env.krate) + db.layout_of_ty(self.ty.clone(), self.env.clone()) .map(|layout| Layout(layout, db.target_data_layout(self.env.krate).unwrap())) } } diff --git a/crates/ide-assists/src/handlers/change_visibility.rs b/crates/ide-assists/src/handlers/change_visibility.rs index 2b1d8f6f0132a..e6179ab8b1bac 100644 --- a/crates/ide-assists/src/handlers/change_visibility.rs +++ b/crates/ide-assists/src/handlers/change_visibility.rs @@ -2,9 +2,10 @@ use syntax::{ ast::{self, HasName, HasVisibility}, AstNode, SyntaxKind::{ - CONST, ENUM, FN, MACRO_DEF, MODULE, STATIC, STRUCT, TRAIT, TYPE_ALIAS, USE, VISIBILITY, + self, ASSOC_ITEM_LIST, CONST, ENUM, FN, MACRO_DEF, MODULE, SOURCE_FILE, STATIC, STRUCT, + TRAIT, TYPE_ALIAS, USE, VISIBILITY, }, - T, + SyntaxNode, T, }; use crate::{utils::vis_offset, AssistContext, AssistId, AssistKind, Assists}; @@ -46,13 +47,11 @@ fn add_vis(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { let (offset, target) = if let Some(keyword) = item_keyword { let parent = keyword.parent()?; - let def_kws = - vec![CONST, STATIC, TYPE_ALIAS, FN, MODULE, STRUCT, ENUM, TRAIT, USE, MACRO_DEF]; - // Parent is not a definition, can't add visibility - if !def_kws.iter().any(|&def_kw| def_kw == parent.kind()) { + + if !can_add(&parent) { return None; } - // Already have visibility, do nothing + // Already has visibility, do nothing if parent.children().any(|child| child.kind() == VISIBILITY) { return None; } @@ -86,6 +85,29 @@ fn add_vis(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { ) } +fn can_add(node: &SyntaxNode) -> bool { + const LEGAL: &[SyntaxKind] = + &[CONST, STATIC, TYPE_ALIAS, FN, MODULE, STRUCT, ENUM, TRAIT, USE, MACRO_DEF]; + + LEGAL.contains(&node.kind()) && { + let Some(p) = node.parent() else { + return false; + }; + + if p.kind() == ASSOC_ITEM_LIST { + p.parent() + .and_then(|it| ast::Impl::cast(it)) + // inherent impls i.e 'non-trait impls' have a non-local + // effect, thus can have visibility even when nested. + // so filter them out + .filter(|imp| imp.for_token().is_none()) + .is_some() + } else { + matches!(p.kind(), SOURCE_FILE | MODULE) + } + } +} + fn change_vis(acc: &mut Assists, vis: ast::Visibility) -> Option<()> { if vis.syntax().text() == "pub" { let target = vis.syntax().text_range(); @@ -129,6 +151,16 @@ mod tests { check_assist(change_visibility, "unsafe f$0n foo() {}", "pub(crate) unsafe fn foo() {}"); check_assist(change_visibility, "$0macro foo() {}", "pub(crate) macro foo() {}"); check_assist(change_visibility, "$0use foo;", "pub(crate) use foo;"); + check_assist( + change_visibility, + "impl Foo { f$0n foo() {} }", + "impl Foo { pub(crate) fn foo() {} }", + ); + check_assist( + change_visibility, + "fn bar() { impl Foo { f$0n foo() {} } }", + "fn bar() { impl Foo { pub(crate) fn foo() {} } }", + ); } #[test] @@ -213,4 +245,33 @@ mod tests { check_assist_target(change_visibility, "pub(crate)$0 fn foo() {}", "pub(crate)"); check_assist_target(change_visibility, "struct S { $0field: u32 }", "field"); } + + #[test] + fn not_applicable_for_items_within_traits() { + check_assist_not_applicable(change_visibility, "trait Foo { f$0n run() {} }"); + check_assist_not_applicable(change_visibility, "trait Foo { con$0st FOO: u8 = 69; }"); + check_assist_not_applicable(change_visibility, "impl Foo for Bar { f$0n quox() {} }"); + } + + #[test] + fn not_applicable_for_items_within_fns() { + check_assist_not_applicable(change_visibility, "fn foo() { f$0n inner() {} }"); + check_assist_not_applicable(change_visibility, "fn foo() { unsafe f$0n inner() {} }"); + check_assist_not_applicable(change_visibility, "fn foo() { const f$0n inner() {} }"); + check_assist_not_applicable(change_visibility, "fn foo() { con$0st FOO: u8 = 69; }"); + check_assist_not_applicable(change_visibility, "fn foo() { en$0um Foo {} }"); + check_assist_not_applicable(change_visibility, "fn foo() { stru$0ct Foo {} }"); + check_assist_not_applicable(change_visibility, "fn foo() { mo$0d foo {} }"); + check_assist_not_applicable(change_visibility, "fn foo() { $0use foo; }"); + check_assist_not_applicable(change_visibility, "fn foo() { $0type Foo = Bar; }"); + check_assist_not_applicable(change_visibility, "fn foo() { tr$0ait Foo {} }"); + check_assist_not_applicable( + change_visibility, + "fn foo() { impl Trait for Bar { f$0n bar() {} } }", + ); + check_assist_not_applicable( + change_visibility, + "fn foo() { impl Trait for Bar { con$0st FOO: u8 = 69; } }", + ); + } } diff --git a/crates/ide-diagnostics/src/handlers/incorrect_case.rs b/crates/ide-diagnostics/src/handlers/incorrect_case.rs index 92fd4f71ca5d1..235062bf531fe 100644 --- a/crates/ide-diagnostics/src/handlers/incorrect_case.rs +++ b/crates/ide-diagnostics/src/handlers/incorrect_case.rs @@ -545,4 +545,100 @@ pub static SomeStatic: u8 = 10; "#, ); } + + #[test] + fn fn_inner_items() { + check_diagnostics( + r#" +fn main() { + const foo: bool = true; + //^^^ 💡 warn: Constant `foo` should have UPPER_SNAKE_CASE name, e.g. `FOO` + static bar: bool = true; + //^^^ 💡 warn: Static variable `bar` should have UPPER_SNAKE_CASE name, e.g. `BAR` + fn BAZ() { + //^^^ 💡 warn: Function `BAZ` should have snake_case name, e.g. `baz` + const foo: bool = true; + //^^^ 💡 warn: Constant `foo` should have UPPER_SNAKE_CASE name, e.g. `FOO` + static bar: bool = true; + //^^^ 💡 warn: Static variable `bar` should have UPPER_SNAKE_CASE name, e.g. `BAR` + fn BAZ() { + //^^^ 💡 warn: Function `BAZ` should have snake_case name, e.g. `baz` + let INNER_INNER = 42; + //^^^^^^^^^^^ 💡 warn: Variable `INNER_INNER` should have snake_case name, e.g. `inner_inner` + } + + let INNER_LOCAL = 42; + //^^^^^^^^^^^ 💡 warn: Variable `INNER_LOCAL` should have snake_case name, e.g. `inner_local` + } +} +"#, + ); + } + + #[test] + fn const_body_inner_items() { + check_diagnostics( + r#" +const _: () = { + static bar: bool = true; + //^^^ 💡 warn: Static variable `bar` should have UPPER_SNAKE_CASE name, e.g. `BAR` + fn BAZ() {} + //^^^ 💡 warn: Function `BAZ` should have snake_case name, e.g. `baz` + + const foo: () = { + //^^^ 💡 warn: Constant `foo` should have UPPER_SNAKE_CASE name, e.g. `FOO` + const foo: bool = true; + //^^^ 💡 warn: Constant `foo` should have UPPER_SNAKE_CASE name, e.g. `FOO` + static bar: bool = true; + //^^^ 💡 warn: Static variable `bar` should have UPPER_SNAKE_CASE name, e.g. `BAR` + fn BAZ() {} + //^^^ 💡 warn: Function `BAZ` should have snake_case name, e.g. `baz` + }; +}; +"#, + ); + } + + #[test] + fn static_body_inner_items() { + check_diagnostics( + r#" +static FOO: () = { + const foo: bool = true; + //^^^ 💡 warn: Constant `foo` should have UPPER_SNAKE_CASE name, e.g. `FOO` + fn BAZ() {} + //^^^ 💡 warn: Function `BAZ` should have snake_case name, e.g. `baz` + + static bar: () = { + //^^^ 💡 warn: Static variable `bar` should have UPPER_SNAKE_CASE name, e.g. `BAR` + const foo: bool = true; + //^^^ 💡 warn: Constant `foo` should have UPPER_SNAKE_CASE name, e.g. `FOO` + static bar: bool = true; + //^^^ 💡 warn: Static variable `bar` should have UPPER_SNAKE_CASE name, e.g. `BAR` + fn BAZ() {} + //^^^ 💡 warn: Function `BAZ` should have snake_case name, e.g. `baz` + }; +}; +"#, + ); + } + + #[test] + fn enum_variant_body_inner_item() { + check_diagnostics( + r#" +enum E { + A = { + const foo: bool = true; + //^^^ 💡 warn: Constant `foo` should have UPPER_SNAKE_CASE name, e.g. `FOO` + static bar: bool = true; + //^^^ 💡 warn: Static variable `bar` should have UPPER_SNAKE_CASE name, e.g. `BAR` + fn BAZ() {} + //^^^ 💡 warn: Function `BAZ` should have snake_case name, e.g. `baz` + 42 + }, +} +"#, + ); + } } diff --git a/crates/ide/src/syntax_highlighting.rs b/crates/ide/src/syntax_highlighting.rs index 577bd2bc1f890..ae97236409ebb 100644 --- a/crates/ide/src/syntax_highlighting.rs +++ b/crates/ide/src/syntax_highlighting.rs @@ -24,7 +24,7 @@ use syntax::{ use crate::{ syntax_highlighting::{ - escape::{highlight_escape_char, highlight_escape_string}, + escape::{highlight_escape_byte, highlight_escape_char, highlight_escape_string}, format::highlight_format_string, highlights::Highlights, macro_::MacroHighlighter, @@ -471,6 +471,14 @@ fn traverse( }; highlight_escape_char(hl, &char, range.start()) + } else if ast::Byte::can_cast(token.kind()) + && ast::Byte::can_cast(descended_token.kind()) + { + let Some(byte) = ast::Byte::cast(token) else { + continue; + }; + + highlight_escape_byte(hl, &byte, range.start()) } } diff --git a/crates/ide/src/syntax_highlighting/escape.rs b/crates/ide/src/syntax_highlighting/escape.rs index 211e3588095cf..5913ca5e454c0 100644 --- a/crates/ide/src/syntax_highlighting/escape.rs +++ b/crates/ide/src/syntax_highlighting/escape.rs @@ -1,7 +1,7 @@ //! Syntax highlighting for escape sequences use crate::syntax_highlighting::highlights::Highlights; use crate::{HlRange, HlTag}; -use syntax::ast::{Char, IsString}; +use syntax::ast::{Byte, Char, IsString}; use syntax::{AstToken, TextRange, TextSize}; pub(super) fn highlight_escape_string( @@ -10,14 +10,14 @@ pub(super) fn highlight_escape_string( start: TextSize, ) { string.escaped_char_ranges(&mut |piece_range, char| { - if char.is_err() { - return; - } - if string.text()[piece_range.start().into()..].starts_with('\\') { + let highlight = match char { + Ok(_) => HlTag::EscapeSequence, + Err(_) => HlTag::InvalidEscapeSequence, + }; stack.add(HlRange { range: piece_range + start, - highlight: HlTag::EscapeSequence.into(), + highlight: highlight.into(), binding_hash: None, }); } @@ -26,6 +26,9 @@ pub(super) fn highlight_escape_string( pub(super) fn highlight_escape_char(stack: &mut Highlights, char: &Char, start: TextSize) { if char.value().is_none() { + // We do not emit invalid escapes highlighting here. The lexer would likely be in a bad + // state and this token contains junks, since `'` is not a reliable delimiter (consider + // lifetimes). Nonetheless, parser errors should already be emitted. return; } @@ -43,3 +46,24 @@ pub(super) fn highlight_escape_char(stack: &mut Highlights, char: &Char, start: TextRange::new(start + TextSize::from(1), start + TextSize::from(text.len() as u32 + 1)); stack.add(HlRange { range, highlight: HlTag::EscapeSequence.into(), binding_hash: None }) } + +pub(super) fn highlight_escape_byte(stack: &mut Highlights, byte: &Byte, start: TextSize) { + if byte.value().is_none() { + // See `highlight_escape_char` for why no error highlighting here. + return; + } + + let text = byte.text(); + if !text.starts_with("b'") || !text.ends_with('\'') { + return; + } + + let text = &text[2..text.len() - 1]; + if !text.starts_with('\\') { + return; + } + + let range = + TextRange::new(start + TextSize::from(2), start + TextSize::from(text.len() as u32 + 2)); + stack.add(HlRange { range, highlight: HlTag::EscapeSequence.into(), binding_hash: None }) +} diff --git a/crates/ide/src/syntax_highlighting/html.rs b/crates/ide/src/syntax_highlighting/html.rs index 2c7823069b3f8..bbc6b55a64222 100644 --- a/crates/ide/src/syntax_highlighting/html.rs +++ b/crates/ide/src/syntax_highlighting/html.rs @@ -109,6 +109,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd .control { font-style: italic; } .reference { font-style: italic; font-weight: bold; } -.unresolved_reference { color: #FC5555; text-decoration: wavy underline; } +.invalid_escape_sequence { color: #FC5555; text-decoration: wavy underline; } +.unresolved_reference { color: #FC5555; text-decoration: wavy underline; } "; diff --git a/crates/ide/src/syntax_highlighting/tags.rs b/crates/ide/src/syntax_highlighting/tags.rs index f983109115f6c..6d4cdd0efe212 100644 --- a/crates/ide/src/syntax_highlighting/tags.rs +++ b/crates/ide/src/syntax_highlighting/tags.rs @@ -29,6 +29,7 @@ pub enum HlTag { Comment, EscapeSequence, FormatSpecifier, + InvalidEscapeSequence, Keyword, NumericLiteral, Operator(HlOperator), @@ -166,6 +167,7 @@ impl HlTag { HlTag::CharLiteral => "char_literal", HlTag::Comment => "comment", HlTag::EscapeSequence => "escape_sequence", + HlTag::InvalidEscapeSequence => "invalid_escape_sequence", HlTag::FormatSpecifier => "format_specifier", HlTag::Keyword => "keyword", HlTag::Punctuation(punct) => match punct { diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_assoc_functions.html b/crates/ide/src/syntax_highlighting/test_data/highlight_assoc_functions.html index 9ed65fbc8548d..4dcbfe4eb62ca 100644 --- a/crates/ide/src/syntax_highlighting/test_data/highlight_assoc_functions.html +++ b/crates/ide/src/syntax_highlighting/test_data/highlight_assoc_functions.html @@ -40,7 +40,8 @@ .control { font-style: italic; } .reference { font-style: italic; font-weight: bold; } -.unresolved_reference { color: #FC5555; text-decoration: wavy underline; } +.invalid_escape_sequence { color: #FC5555; text-decoration: wavy underline; } +.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
fn not_static() {}
 
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_attributes.html b/crates/ide/src/syntax_highlighting/test_data/highlight_attributes.html
index 567ab8ccc11df..bf5505caf376d 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_attributes.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_attributes.html
@@ -40,7 +40,8 @@
 .control            { font-style: italic; }
 .reference          { font-style: italic; font-weight: bold; }
 
-.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
+.invalid_escape_sequence { color: #FC5555; text-decoration: wavy underline; }
+.unresolved_reference    { color: #FC5555; text-decoration: wavy underline; }
 
 
#[allow(dead_code)]
 #[rustfmt::skip]
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_crate_root.html b/crates/ide/src/syntax_highlighting/test_data/highlight_crate_root.html
index 1e4c06df7ea0b..0d1b3c1f18315 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_crate_root.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_crate_root.html
@@ -40,7 +40,8 @@
 .control            { font-style: italic; }
 .reference          { font-style: italic; font-weight: bold; }
 
-.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
+.invalid_escape_sequence { color: #FC5555; text-decoration: wavy underline; }
+.unresolved_reference    { color: #FC5555; text-decoration: wavy underline; }
 
 
extern crate foo;
 use core::iter;
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_default_library.html b/crates/ide/src/syntax_highlighting/test_data/highlight_default_library.html
index 5d66f832daf94..dd1528ed03f02 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_default_library.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_default_library.html
@@ -40,7 +40,8 @@
 .control            { font-style: italic; }
 .reference          { font-style: italic; font-weight: bold; }
 
-.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
+.invalid_escape_sequence { color: #FC5555; text-decoration: wavy underline; }
+.unresolved_reference    { color: #FC5555; text-decoration: wavy underline; }
 
 
use core::iter;
 
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html b/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html
index 35f240d428471..d5f92aa5d4782 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html
@@ -40,7 +40,8 @@
 .control            { font-style: italic; }
 .reference          { font-style: italic; font-weight: bold; }
 
-.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
+.invalid_escape_sequence { color: #FC5555; text-decoration: wavy underline; }
+.unresolved_reference    { color: #FC5555; text-decoration: wavy underline; }
 
 
//! This is a module to test doc injection.
 //! ```
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_extern_crate.html b/crates/ide/src/syntax_highlighting/test_data/highlight_extern_crate.html
index 87b9da46e2cc8..b15f7bca72b98 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_extern_crate.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_extern_crate.html
@@ -40,7 +40,8 @@
 .control            { font-style: italic; }
 .reference          { font-style: italic; font-weight: bold; }
 
-.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
+.invalid_escape_sequence { color: #FC5555; text-decoration: wavy underline; }
+.unresolved_reference    { color: #FC5555; text-decoration: wavy underline; }
 
 
extern crate std;
 extern crate alloc as abc;
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_general.html b/crates/ide/src/syntax_highlighting/test_data/highlight_general.html
index 6b049f379ac1d..bdeb09d2f83cb 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_general.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_general.html
@@ -40,7 +40,8 @@
 .control            { font-style: italic; }
 .reference          { font-style: italic; font-weight: bold; }
 
-.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
+.invalid_escape_sequence { color: #FC5555; text-decoration: wavy underline; }
+.unresolved_reference    { color: #FC5555; text-decoration: wavy underline; }
 
 
use inner::{self as inner_mod};
 mod inner {}
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_injection.html b/crates/ide/src/syntax_highlighting/test_data/highlight_injection.html
index d9c3db6fbb510..f9c33b8a60177 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_injection.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_injection.html
@@ -40,7 +40,8 @@
 .control            { font-style: italic; }
 .reference          { font-style: italic; font-weight: bold; }
 
-.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
+.invalid_escape_sequence { color: #FC5555; text-decoration: wavy underline; }
+.unresolved_reference    { color: #FC5555; text-decoration: wavy underline; }
 
 
fn fixture(ra_fixture: &str) {}
 
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_keywords.html b/crates/ide/src/syntax_highlighting/test_data/highlight_keywords.html
index 3900959bedf8d..fd3b39855e201 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_keywords.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_keywords.html
@@ -40,7 +40,8 @@
 .control            { font-style: italic; }
 .reference          { font-style: italic; font-weight: bold; }
 
-.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
+.invalid_escape_sequence { color: #FC5555; text-decoration: wavy underline; }
+.unresolved_reference    { color: #FC5555; text-decoration: wavy underline; }
 
 
extern crate self;
 
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_lifetimes.html b/crates/ide/src/syntax_highlighting/test_data/highlight_lifetimes.html
index f98e0b1cda6e9..ec39998de268d 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_lifetimes.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_lifetimes.html
@@ -40,7 +40,8 @@
 .control            { font-style: italic; }
 .reference          { font-style: italic; font-weight: bold; }
 
-.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
+.invalid_escape_sequence { color: #FC5555; text-decoration: wavy underline; }
+.unresolved_reference    { color: #FC5555; text-decoration: wavy underline; }
 
 

 #[derive()]
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_macros.html b/crates/ide/src/syntax_highlighting/test_data/highlight_macros.html
index 2cbbf69641525..c5fcec7568080 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_macros.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_macros.html
@@ -40,7 +40,8 @@
 .control            { font-style: italic; }
 .reference          { font-style: italic; font-weight: bold; }
 
-.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
+.invalid_escape_sequence { color: #FC5555; text-decoration: wavy underline; }
+.unresolved_reference    { color: #FC5555; text-decoration: wavy underline; }
 
 
proc_macros::mirror! {
     {
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_module_docs_inline.html b/crates/ide/src/syntax_highlighting/test_data/highlight_module_docs_inline.html
index 8a1d69816e688..4dcf8e5f01f9f 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_module_docs_inline.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_module_docs_inline.html
@@ -40,7 +40,8 @@
 .control            { font-style: italic; }
 .reference          { font-style: italic; font-weight: bold; }
 
-.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
+.invalid_escape_sequence { color: #FC5555; text-decoration: wavy underline; }
+.unresolved_reference    { color: #FC5555; text-decoration: wavy underline; }
 
 
//! [Struct]
 //! This is an intra doc injection test for modules
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_module_docs_outline.html b/crates/ide/src/syntax_highlighting/test_data/highlight_module_docs_outline.html
index c4c3e3dc2606e..084bbf2f74209 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_module_docs_outline.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_module_docs_outline.html
@@ -40,7 +40,8 @@
 .control            { font-style: italic; }
 .reference          { font-style: italic; font-weight: bold; }
 
-.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
+.invalid_escape_sequence { color: #FC5555; text-decoration: wavy underline; }
+.unresolved_reference    { color: #FC5555; text-decoration: wavy underline; }
 
 
/// [crate::foo::Struct]
 /// This is an intra doc injection test for modules
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_operators.html b/crates/ide/src/syntax_highlighting/test_data/highlight_operators.html
index 2369071ae2aae..1af4bcfbd9dd5 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_operators.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_operators.html
@@ -40,7 +40,8 @@
 .control            { font-style: italic; }
 .reference          { font-style: italic; font-weight: bold; }
 
-.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
+.invalid_escape_sequence { color: #FC5555; text-decoration: wavy underline; }
+.unresolved_reference    { color: #FC5555; text-decoration: wavy underline; }
 
 
fn main() {
     1 + 1 - 1 * 1 / 1 % 1 | 1 & 1 ! 1 ^ 1 >> 1 << 1;
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_rainbow.html b/crates/ide/src/syntax_highlighting/test_data/highlight_rainbow.html
index bff35c897e1da..ec18c3ea1f9b7 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_rainbow.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_rainbow.html
@@ -40,7 +40,8 @@
 .control            { font-style: italic; }
 .reference          { font-style: italic; font-weight: bold; }
 
-.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
+.invalid_escape_sequence { color: #FC5555; text-decoration: wavy underline; }
+.unresolved_reference    { color: #FC5555; text-decoration: wavy underline; }
 
 
fn main() {
     let hello = "hello";
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html b/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html
index f4f164aa1de6f..dcac8eb736893 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html
@@ -40,7 +40,8 @@
 .control            { font-style: italic; }
 .reference          { font-style: italic; font-weight: bold; }
 
-.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
+.invalid_escape_sequence { color: #FC5555; text-decoration: wavy underline; }
+.unresolved_reference    { color: #FC5555; text-decoration: wavy underline; }
 
 
macro_rules! println {
     ($($arg:tt)*) => ({
@@ -105,6 +106,8 @@
     let a = '\x65';
     let a = '\x00';
 
+    let a = b'\xFF';
+
     println!("Hello {{Hello}}");
     // from https://doc.rust-lang.org/std/fmt/index.html
     println!("Hello");                 // => "Hello"
@@ -159,8 +162,9 @@
     println!("Hello\nWorld");
     println!("\u{48}\x65\x6C\x6C\x6F World");
 
-    let _ = "\x28\x28\x00\x63\n";
-    let _ = b"\x28\x28\x00\x63\n";
+    let _ = "\x28\x28\x00\x63\xFF\u{FF}\n"; // invalid non-UTF8 escape sequences
+    let _ = b"\x28\x28\x00\x63\xFF\u{FF}\n"; // valid bytes, invalid unicodes
+    let _ = c"\u{FF}\xFF"; // valid bytes, valid unicodes
     let backslash = r"\\";
 
     println!("{\x41}", A = 92);
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html b/crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html
index 654d51b8a43d9..c72ea54e948e8 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html
@@ -40,7 +40,8 @@
 .control            { font-style: italic; }
 .reference          { font-style: italic; font-weight: bold; }
 
-.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
+.invalid_escape_sequence { color: #FC5555; text-decoration: wavy underline; }
+.unresolved_reference    { color: #FC5555; text-decoration: wavy underline; }
 
 
macro_rules! id {
     ($($tt:tt)*) => {
diff --git a/crates/ide/src/syntax_highlighting/tests.rs b/crates/ide/src/syntax_highlighting/tests.rs
index 1ee451a06d043..696aa59002527 100644
--- a/crates/ide/src/syntax_highlighting/tests.rs
+++ b/crates/ide/src/syntax_highlighting/tests.rs
@@ -451,6 +451,8 @@ fn main() {
     let a = '\x65';
     let a = '\x00';
 
+    let a = b'\xFF';
+
     println!("Hello {{Hello}}");
     // from https://doc.rust-lang.org/std/fmt/index.html
     println!("Hello");                 // => "Hello"
@@ -505,8 +507,9 @@ fn main() {
     println!("Hello\nWorld");
     println!("\u{48}\x65\x6C\x6C\x6F World");
 
-    let _ = "\x28\x28\x00\x63\n";
-    let _ = b"\x28\x28\x00\x63\n";
+    let _ = "\x28\x28\x00\x63\xFF\u{FF}\n"; // invalid non-UTF8 escape sequences
+    let _ = b"\x28\x28\x00\x63\xFF\u{FF}\n"; // valid bytes, invalid unicodes
+    let _ = c"\u{FF}\xFF"; // valid bytes, valid unicodes
     let backslash = r"\\";
 
     println!("{\x41}", A = 92);
diff --git a/crates/parser/src/grammar.rs b/crates/parser/src/grammar.rs
index 1814e0e54c592..a868419821d89 100644
--- a/crates/parser/src/grammar.rs
+++ b/crates/parser/src/grammar.rs
@@ -211,70 +211,54 @@ impl BlockLike {
 const VISIBILITY_FIRST: TokenSet = TokenSet::new(&[T![pub], T![crate]]);
 
 fn opt_visibility(p: &mut Parser<'_>, in_tuple_field: bool) -> bool {
-    match p.current() {
-        T![pub] => {
-            let m = p.start();
-            p.bump(T![pub]);
-            if p.at(T!['(']) {
-                match p.nth(1) {
-                    // test crate_visibility
-                    // pub(crate) struct S;
-                    // pub(self) struct S;
-                    // pub(super) struct S;
-
-                    // test_err crate_visibility_empty_recover
-                    // pub() struct S;
-
-                    // test pub_parens_typepath
-                    // struct B(pub (super::A));
-                    // struct B(pub (crate::A,));
-                    T![crate] | T![self] | T![super] | T![ident] | T![')'] if p.nth(2) != T![:] => {
-                        // If we are in a tuple struct, then the parens following `pub`
-                        // might be an tuple field, not part of the visibility. So in that
-                        // case we don't want to consume an identifier.
-
-                        // test pub_tuple_field
-                        // struct MyStruct(pub (u32, u32));
-                        // struct MyStruct(pub (u32));
-                        // struct MyStruct(pub ());
-                        if !(in_tuple_field && matches!(p.nth(1), T![ident] | T![')'])) {
-                            p.bump(T!['(']);
-                            paths::use_path(p);
-                            p.expect(T![')']);
-                        }
-                    }
-                    // test crate_visibility_in
-                    // pub(in super::A) struct S;
-                    // pub(in crate) struct S;
-                    T![in] => {
-                        p.bump(T!['(']);
-                        p.bump(T![in]);
-                        paths::use_path(p);
-                        p.expect(T![')']);
-                    }
-                    _ => {}
+    if !p.at(T![pub]) {
+        return false;
+    }
+
+    let m = p.start();
+    p.bump(T![pub]);
+    if p.at(T!['(']) {
+        match p.nth(1) {
+            // test crate_visibility
+            // pub(crate) struct S;
+            // pub(self) struct S;
+            // pub(super) struct S;
+
+            // test_err crate_visibility_empty_recover
+            // pub() struct S;
+
+            // test pub_parens_typepath
+            // struct B(pub (super::A));
+            // struct B(pub (crate::A,));
+            T![crate] | T![self] | T![super] | T![ident] | T![')'] if p.nth(2) != T![:] => {
+                // If we are in a tuple struct, then the parens following `pub`
+                // might be an tuple field, not part of the visibility. So in that
+                // case we don't want to consume an identifier.
+
+                // test pub_tuple_field
+                // struct MyStruct(pub (u32, u32));
+                // struct MyStruct(pub (u32));
+                // struct MyStruct(pub ());
+                if !(in_tuple_field && matches!(p.nth(1), T![ident] | T![')'])) {
+                    p.bump(T!['(']);
+                    paths::use_path(p);
+                    p.expect(T![')']);
                 }
             }
-            m.complete(p, VISIBILITY);
-            true
-        }
-        // test crate_keyword_vis
-        // crate fn main() { }
-        // struct S { crate field: u32 }
-        // struct T(crate u32);
-        T![crate] => {
-            if p.nth_at(1, T![::]) {
-                // test crate_keyword_path
-                // fn foo() { crate::foo(); }
-                return false;
+            // test crate_visibility_in
+            // pub(in super::A) struct S;
+            // pub(in crate) struct S;
+            T![in] => {
+                p.bump(T!['(']);
+                p.bump(T![in]);
+                paths::use_path(p);
+                p.expect(T![')']);
             }
-            let m = p.start();
-            p.bump(T![crate]);
-            m.complete(p, VISIBILITY);
-            true
+            _ => {}
         }
-        _ => false,
     }
+    m.complete(p, VISIBILITY);
+    true
 }
 
 fn opt_rename(p: &mut Parser<'_>) {
diff --git a/crates/parser/src/tests/prefix_entries.rs b/crates/parser/src/tests/prefix_entries.rs
index 11f9c34abdf1e..2f3c7febc0401 100644
--- a/crates/parser/src/tests/prefix_entries.rs
+++ b/crates/parser/src/tests/prefix_entries.rs
@@ -6,7 +6,6 @@ fn vis() {
     check(PrefixEntryPoint::Vis, "fn foo() {}", "");
     check(PrefixEntryPoint::Vis, "pub(fn foo() {}", "pub");
     check(PrefixEntryPoint::Vis, "pub(crate fn foo() {}", "pub(crate");
-    check(PrefixEntryPoint::Vis, "crate fn foo() {}", "crate");
 }
 
 #[test]
diff --git a/crates/parser/test_data/parser/inline/ok/0040_crate_keyword_vis.rast b/crates/parser/test_data/parser/inline/ok/0040_crate_keyword_vis.rast
deleted file mode 100644
index 07b0210e44d03..0000000000000
--- a/crates/parser/test_data/parser/inline/ok/0040_crate_keyword_vis.rast
+++ /dev/null
@@ -1,63 +0,0 @@
-SOURCE_FILE
-  FN
-    VISIBILITY
-      CRATE_KW "crate"
-    WHITESPACE " "
-    FN_KW "fn"
-    WHITESPACE " "
-    NAME
-      IDENT "main"
-    PARAM_LIST
-      L_PAREN "("
-      R_PAREN ")"
-    WHITESPACE " "
-    BLOCK_EXPR
-      STMT_LIST
-        L_CURLY "{"
-        WHITESPACE " "
-        R_CURLY "}"
-  WHITESPACE "\n"
-  STRUCT
-    STRUCT_KW "struct"
-    WHITESPACE " "
-    NAME
-      IDENT "S"
-    WHITESPACE " "
-    RECORD_FIELD_LIST
-      L_CURLY "{"
-      WHITESPACE " "
-      RECORD_FIELD
-        VISIBILITY
-          CRATE_KW "crate"
-        WHITESPACE " "
-        NAME
-          IDENT "field"
-        COLON ":"
-        WHITESPACE " "
-        PATH_TYPE
-          PATH
-            PATH_SEGMENT
-              NAME_REF
-                IDENT "u32"
-      WHITESPACE " "
-      R_CURLY "}"
-  WHITESPACE "\n"
-  STRUCT
-    STRUCT_KW "struct"
-    WHITESPACE " "
-    NAME
-      IDENT "T"
-    TUPLE_FIELD_LIST
-      L_PAREN "("
-      TUPLE_FIELD
-        VISIBILITY
-          CRATE_KW "crate"
-        WHITESPACE " "
-        PATH_TYPE
-          PATH
-            PATH_SEGMENT
-              NAME_REF
-                IDENT "u32"
-      R_PAREN ")"
-    SEMICOLON ";"
-  WHITESPACE "\n"
diff --git a/crates/parser/test_data/parser/inline/ok/0040_crate_keyword_vis.rs b/crates/parser/test_data/parser/inline/ok/0040_crate_keyword_vis.rs
deleted file mode 100644
index e2b5f2161dfc1..0000000000000
--- a/crates/parser/test_data/parser/inline/ok/0040_crate_keyword_vis.rs
+++ /dev/null
@@ -1,3 +0,0 @@
-crate fn main() { }
-struct S { crate field: u32 }
-struct T(crate u32);
diff --git a/crates/parser/test_data/parser/inline/ok/0125_crate_keyword_path.rast b/crates/parser/test_data/parser/inline/ok/0125_crate_keyword_path.rast
deleted file mode 100644
index 8d9b61630ae68..0000000000000
--- a/crates/parser/test_data/parser/inline/ok/0125_crate_keyword_path.rast
+++ /dev/null
@@ -1,33 +0,0 @@
-SOURCE_FILE
-  FN
-    FN_KW "fn"
-    WHITESPACE " "
-    NAME
-      IDENT "foo"
-    PARAM_LIST
-      L_PAREN "("
-      R_PAREN ")"
-    WHITESPACE " "
-    BLOCK_EXPR
-      STMT_LIST
-        L_CURLY "{"
-        WHITESPACE " "
-        EXPR_STMT
-          CALL_EXPR
-            PATH_EXPR
-              PATH
-                PATH
-                  PATH_SEGMENT
-                    NAME_REF
-                      CRATE_KW "crate"
-                COLON2 "::"
-                PATH_SEGMENT
-                  NAME_REF
-                    IDENT "foo"
-            ARG_LIST
-              L_PAREN "("
-              R_PAREN ")"
-          SEMICOLON ";"
-        WHITESPACE " "
-        R_CURLY "}"
-  WHITESPACE "\n"
diff --git a/crates/parser/test_data/parser/inline/ok/0125_crate_keyword_path.rs b/crates/parser/test_data/parser/inline/ok/0125_crate_keyword_path.rs
deleted file mode 100644
index 0f454d121d698..0000000000000
--- a/crates/parser/test_data/parser/inline/ok/0125_crate_keyword_path.rs
+++ /dev/null
@@ -1 +0,0 @@
-fn foo() { crate::foo(); }
diff --git a/crates/project-model/src/cargo_workspace.rs b/crates/project-model/src/cargo_workspace.rs
index e1117ac464bdd..e47808a2cc9fc 100644
--- a/crates/project-model/src/cargo_workspace.rs
+++ b/crates/project-model/src/cargo_workspace.rs
@@ -145,7 +145,7 @@ pub struct PackageDependency {
     pub kind: DepKind,
 }
 
-#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord)]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
 pub enum DepKind {
     /// Available to the library, binary, and dev targets in the package (but not the build script).
     Normal,
@@ -156,23 +156,20 @@ pub enum DepKind {
 }
 
 impl DepKind {
-    fn iter(list: &[cargo_metadata::DepKindInfo]) -> impl Iterator + '_ {
-        let mut dep_kinds = Vec::new();
+    fn iter(list: &[cargo_metadata::DepKindInfo]) -> impl Iterator {
+        let mut dep_kinds = [None; 3];
         if list.is_empty() {
-            dep_kinds.push(Self::Normal);
+            dep_kinds[0] = Some(Self::Normal);
         }
         for info in list {
-            let kind = match info.kind {
-                cargo_metadata::DependencyKind::Normal => Self::Normal,
-                cargo_metadata::DependencyKind::Development => Self::Dev,
-                cargo_metadata::DependencyKind::Build => Self::Build,
+            match info.kind {
+                cargo_metadata::DependencyKind::Normal => dep_kinds[0] = Some(Self::Normal),
+                cargo_metadata::DependencyKind::Development => dep_kinds[1] = Some(Self::Dev),
+                cargo_metadata::DependencyKind::Build => dep_kinds[2] = Some(Self::Build),
                 cargo_metadata::DependencyKind::Unknown => continue,
-            };
-            dep_kinds.push(kind);
+            }
         }
-        dep_kinds.sort_unstable();
-        dep_kinds.dedup();
-        dep_kinds.into_iter()
+        dep_kinds.into_iter().flatten()
     }
 }
 
diff --git a/crates/rust-analyzer/src/cli/analysis_stats.rs b/crates/rust-analyzer/src/cli/analysis_stats.rs
index 8d68bf160a2d8..33d7b5ed8789f 100644
--- a/crates/rust-analyzer/src/cli/analysis_stats.rs
+++ b/crates/rust-analyzer/src/cli/analysis_stats.rs
@@ -8,7 +8,7 @@ use std::{
 
 use hir::{
     db::{DefDatabase, ExpandDatabase, HirDatabase},
-    Adt, AssocItem, Crate, DefWithBody, HasCrate, HasSource, HirDisplay, ModuleDef, Name,
+    Adt, AssocItem, Crate, DefWithBody, HasSource, HirDisplay, ModuleDef, Name,
 };
 use hir_def::{
     body::{BodySourceMap, SyntheticSyntax},
@@ -277,7 +277,7 @@ impl flags::AnalysisStats {
             let Err(e) = db.layout_of_adt(
                 hir_def::AdtId::from(a).into(),
                 Substitution::empty(Interner),
-                a.krate(db).into(),
+                db.trait_environment(a.into()),
             ) else {
                 continue;
             };
diff --git a/crates/rust-analyzer/src/semantic_tokens.rs b/crates/rust-analyzer/src/semantic_tokens.rs
index d4bb20c8f448e..1fe02fc7ead0d 100644
--- a/crates/rust-analyzer/src/semantic_tokens.rs
+++ b/crates/rust-analyzer/src/semantic_tokens.rs
@@ -78,6 +78,7 @@ define_semantic_token_types![
         (DERIVE_HELPER, "deriveHelper") => DECORATOR,
         (DOT, "dot"),
         (ESCAPE_SEQUENCE, "escapeSequence") => STRING,
+        (INVALID_ESCAPE_SEQUENCE, "invalidEscapeSequence") => STRING,
         (FORMAT_SPECIFIER, "formatSpecifier") => STRING,
         (GENERIC, "generic") => TYPE_PARAMETER,
         (LABEL, "label"),
diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs
index ba3421bf9e76a..7a89533a5e920 100644
--- a/crates/rust-analyzer/src/to_proto.rs
+++ b/crates/rust-analyzer/src/to_proto.rs
@@ -640,6 +640,7 @@ fn semantic_token_type_and_modifiers(
         HlTag::CharLiteral => semantic_tokens::CHAR,
         HlTag::Comment => semantic_tokens::COMMENT,
         HlTag::EscapeSequence => semantic_tokens::ESCAPE_SEQUENCE,
+        HlTag::InvalidEscapeSequence => semantic_tokens::INVALID_ESCAPE_SEQUENCE,
         HlTag::FormatSpecifier => semantic_tokens::FORMAT_SPECIFIER,
         HlTag::Keyword => semantic_tokens::KEYWORD,
         HlTag::None => semantic_tokens::GENERIC,
diff --git a/crates/syntax/src/ast/token_ext.rs b/crates/syntax/src/ast/token_ext.rs
index 090eb89f47040..87fd51d703cff 100644
--- a/crates/syntax/src/ast/token_ext.rs
+++ b/crates/syntax/src/ast/token_ext.rs
@@ -2,7 +2,9 @@
 
 use std::borrow::Cow;
 
-use rustc_lexer::unescape::{unescape_byte, unescape_char, unescape_literal, Mode};
+use rustc_lexer::unescape::{
+    unescape_byte, unescape_c_string, unescape_char, unescape_literal, CStrUnit, Mode,
+};
 
 use crate::{
     ast::{self, AstToken},
@@ -146,6 +148,7 @@ impl QuoteOffsets {
 
 pub trait IsString: AstToken {
     const RAW_PREFIX: &'static str;
+    const MODE: Mode;
     fn is_raw(&self) -> bool {
         self.text().starts_with(Self::RAW_PREFIX)
     }
@@ -181,7 +184,7 @@ pub trait IsString: AstToken {
         let text = &self.text()[text_range_no_quotes - start];
         let offset = text_range_no_quotes.start() - start;
 
-        unescape_literal(text, Mode::Str, &mut |range, unescaped_char| {
+        unescape_literal(text, Self::MODE, &mut |range, unescaped_char| {
             let text_range =
                 TextRange::new(range.start.try_into().unwrap(), range.end.try_into().unwrap());
             cb(text_range + offset, unescaped_char);
@@ -196,6 +199,7 @@ pub trait IsString: AstToken {
 
 impl IsString for ast::String {
     const RAW_PREFIX: &'static str = "r";
+    const MODE: Mode = Mode::Str;
 }
 
 impl ast::String {
@@ -213,7 +217,7 @@ impl ast::String {
         let mut buf = String::new();
         let mut prev_end = 0;
         let mut has_error = false;
-        unescape_literal(text, Mode::Str, &mut |char_range, unescaped_char| match (
+        unescape_literal(text, Self::MODE, &mut |char_range, unescaped_char| match (
             unescaped_char,
             buf.capacity() == 0,
         ) {
@@ -239,6 +243,7 @@ impl ast::String {
 
 impl IsString for ast::ByteString {
     const RAW_PREFIX: &'static str = "br";
+    const MODE: Mode = Mode::ByteStr;
 }
 
 impl ast::ByteString {
@@ -256,7 +261,7 @@ impl ast::ByteString {
         let mut buf: Vec = Vec::new();
         let mut prev_end = 0;
         let mut has_error = false;
-        unescape_literal(text, Mode::ByteStr, &mut |char_range, unescaped_char| match (
+        unescape_literal(text, Self::MODE, &mut |char_range, unescaped_char| match (
             unescaped_char,
             buf.capacity() == 0,
         ) {
@@ -282,42 +287,70 @@ impl ast::ByteString {
 
 impl IsString for ast::CString {
     const RAW_PREFIX: &'static str = "cr";
+    const MODE: Mode = Mode::CStr;
+
+    fn escaped_char_ranges(
+        &self,
+        cb: &mut dyn FnMut(TextRange, Result),
+    ) {
+        let text_range_no_quotes = match self.text_range_between_quotes() {
+            Some(it) => it,
+            None => return,
+        };
+
+        let start = self.syntax().text_range().start();
+        let text = &self.text()[text_range_no_quotes - start];
+        let offset = text_range_no_quotes.start() - start;
+
+        unescape_c_string(text, Self::MODE, &mut |range, unescaped_char| {
+            let text_range =
+                TextRange::new(range.start.try_into().unwrap(), range.end.try_into().unwrap());
+            // XXX: This method should only be used for highlighting ranges. The unescaped
+            // char/byte is not used. For simplicity, we return an arbitrary placeholder char.
+            cb(text_range + offset, unescaped_char.map(|_| ' '));
+        });
+    }
 }
 
 impl ast::CString {
-    pub fn value(&self) -> Option> {
+    pub fn value(&self) -> Option> {
         if self.is_raw() {
             let text = self.text();
             let text =
                 &text[self.text_range_between_quotes()? - self.syntax().text_range().start()];
-            return Some(Cow::Borrowed(text));
+            return Some(Cow::Borrowed(text.as_bytes()));
         }
 
         let text = self.text();
         let text = &text[self.text_range_between_quotes()? - self.syntax().text_range().start()];
 
-        let mut buf = String::new();
+        let mut buf = Vec::new();
         let mut prev_end = 0;
         let mut has_error = false;
-        unescape_literal(text, Mode::Str, &mut |char_range, unescaped_char| match (
-            unescaped_char,
+        let mut char_buf = [0u8; 4];
+        let mut extend_unit = |buf: &mut Vec, unit: CStrUnit| match unit {
+            CStrUnit::Byte(b) => buf.push(b),
+            CStrUnit::Char(c) => buf.extend(c.encode_utf8(&mut char_buf).as_bytes()),
+        };
+        unescape_c_string(text, Self::MODE, &mut |char_range, unescaped| match (
+            unescaped,
             buf.capacity() == 0,
         ) {
-            (Ok(c), false) => buf.push(c),
+            (Ok(u), false) => extend_unit(&mut buf, u),
             (Ok(_), true) if char_range.len() == 1 && char_range.start == prev_end => {
                 prev_end = char_range.end
             }
-            (Ok(c), true) => {
+            (Ok(u), true) => {
                 buf.reserve_exact(text.len());
-                buf.push_str(&text[..prev_end]);
-                buf.push(c);
+                buf.extend(text[..prev_end].as_bytes());
+                extend_unit(&mut buf, u);
             }
             (Err(_), _) => has_error = true,
         });
 
         match (has_error, buf.capacity() == 0) {
             (true, _) => None,
-            (false, true) => Some(Cow::Borrowed(text)),
+            (false, true) => Some(Cow::Borrowed(text.as_bytes())),
             (false, false) => Some(Cow::Owned(buf)),
         }
     }
diff --git a/docs/user/manual.adoc b/docs/user/manual.adoc
index 31035c4b7295c..2cf985adabc75 100644
--- a/docs/user/manual.adoc
+++ b/docs/user/manual.adoc
@@ -449,27 +449,24 @@ You'll need to close and reopen all .rs and Cargo files, or to restart the IDE,
 Support for the language server protocol is built into Kate through the LSP plugin, which is included by default.
 It is preconfigured to use rust-analyzer for Rust sources since Kate 21.12.
 
-Earlier versions allow you to use rust-analyzer through a simple settings change.
-In the LSP Client settings of Kate, copy the content of the third tab "default parameters" to the second tab "server configuration".
-Then in the configuration replace:
-[source,json]
-----
-        "rust": {
-            "command": ["rls"],
-            "rootIndicationFileNames": ["Cargo.lock", "Cargo.toml"],
-            "url": "https://github.com/rust-lang/rls",
-            "highlightingModeRegex": "^Rust$"
-        },
-----
-With
+To change rust-analyzer config options, start from the following example and put it into Kate's "User Server Settings" tab (located under the LSP Client settings):
 [source,json]
 ----
+{
+    "servers": {
         "rust": {
-            "command": ["rust-analyzer"],
-            "rootIndicationFileNames": ["Cargo.lock", "Cargo.toml"],
-            "url": "https://github.com/rust-lang/rust-analyzer",
-            "highlightingModeRegex": "^Rust$"
-        },
+            "initializationOptions": {
+                "cachePriming": {
+                    "enable": false
+                },
+                "check": {
+                    "allTargets": false
+                },
+                "checkOnSave": false
+            }
+        }
+    }
+}
 ----
 Then click on apply, and restart the LSP server for your rust project.
 
diff --git a/editors/code/package.json b/editors/code/package.json
index ffb5dd9079ad0..a4897899cab14 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -35,9 +35,12 @@
         "build-base": "esbuild ./src/main.ts --bundle --outfile=out/main.js --external:vscode --format=cjs --platform=node --target=node16",
         "build": "npm run build-base -- --sourcemap",
         "watch": "npm run build-base -- --sourcemap --watch",
-        "lint": "prettier --check . && eslint -c .eslintrc.js --ext ts ./src ./tests",
-        "fix": "prettier --write . && eslint -c .eslintrc.js --ext ts ./src ./tests --fix",
-        "pretest": "tsc && npm run build",
+        "format": "prettier --write .",
+        "format:check": "prettier --check .",
+        "lint": "eslint -c .eslintrc.js --ext ts ./src ./tests",
+        "lint:fix": "npm run lint -- --fix",
+        "typecheck": "tsc",
+        "pretest": "npm run typecheck && npm run build",
         "test": "node ./out/tests/runTests.js"
     },
     "dependencies": {
@@ -1801,12 +1804,16 @@
             },
             {
                 "id": "escapeSequence",
-                "description": "Style for char escapes in strings"
+                "description": "Style for char or byte escapes in strings"
             },
             {
                 "id": "formatSpecifier",
                 "description": "Style for {} placeholders in format strings"
             },
+            {
+                "id": "invalidEscapeSequence",
+                "description": "Style for invalid char or byte escapes in strings"
+            },
             {
                 "id": "label",
                 "description": "Style for labels"
diff --git a/editors/code/src/debug.ts b/editors/code/src/debug.ts
index f3d6238d51b4d..e817d680eaeff 100644
--- a/editors/code/src/debug.ts
+++ b/editors/code/src/debug.ts
@@ -66,6 +66,12 @@ export async function startDebugSession(ctx: Ctx, runnable: ra.Runnable): Promis
     return vscode.debug.startDebugging(undefined, debugConfig);
 }
 
+function createCommandLink(extensionId: string): string {
+    // do not remove the second quotes inside
+    // encodeURIComponent or it won't work
+    return `extension.open?${encodeURIComponent(`"${extensionId}"`)}`;
+}
+
 async function getDebugConfiguration(
     ctx: Ctx,
     runnable: ra.Runnable,
@@ -90,9 +96,12 @@ async function getDebugConfiguration(
     }
 
     if (!debugEngine) {
+        const commandCodeLLDB: string = createCommandLink("vadimcn.vscode-lldb");
+        const commandCpp: string = createCommandLink("ms-vscode.cpptools");
+
         await vscode.window.showErrorMessage(
-            `Install [CodeLLDB](https://marketplace.visualstudio.com/items?itemName=vadimcn.vscode-lldb)` +
-                ` or [MS C++ tools](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools) extension for debugging.`,
+            `Install [CodeLLDB](command:${commandCodeLLDB} "Open CodeLLDB")` +
+                ` or [C/C++](command:${commandCpp} "Open C/C++") extension for debugging.`,
         );
         return;
     }

From ba722165a06b5d325f7612192efb8c5425a33253 Mon Sep 17 00:00:00 2001
From: David Barsky 
Date: Mon, 24 Jul 2023 11:19:52 -0400
Subject: [PATCH 18/59] vscode: change minimum VS Code version to 1.75 from
 1.78

---
 editors/code/package-lock.json | 10 +++++-----
 editors/code/package.json      |  4 ++--
 2 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/editors/code/package-lock.json b/editors/code/package-lock.json
index 1c94f13d74517..20fe781ae9e40 100644
--- a/editors/code/package-lock.json
+++ b/editors/code/package-lock.json
@@ -18,7 +18,7 @@
             "devDependencies": {
                 "@tsconfig/strictest": "^2.0.1",
                 "@types/node": "~16.11.7",
-                "@types/vscode": "~1.78.1",
+                "@types/vscode": "~1.75",
                 "@typescript-eslint/eslint-plugin": "^6.0.0",
                 "@typescript-eslint/parser": "^6.0.0",
                 "@vscode/test-electron": "^2.3.3",
@@ -32,7 +32,7 @@
                 "typescript": "^5.1.6"
             },
             "engines": {
-                "vscode": "^1.78.0"
+                "vscode": "^1.75.0"
             }
         },
         "node_modules/@aashutoshrathi/word-wrap": {
@@ -565,9 +565,9 @@
             "dev": true
         },
         "node_modules/@types/vscode": {
-            "version": "1.78.1",
-            "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.78.1.tgz",
-            "integrity": "sha512-wEA+54axejHu7DhcUfnFBan1IqFD1gBDxAFz8LoX06NbNDMRJv/T6OGthOs52yZccasKfN588EyffHWABkR0fg==",
+            "version": "1.75.1",
+            "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.75.1.tgz",
+            "integrity": "sha512-emg7wdsTFzdi+elvoyoA+Q8keEautdQHyY5LNmHVM4PTpY8JgOTVADrGVyXGepJ6dVW2OS5/xnLUWh+nZxvdiA==",
             "dev": true
         },
         "node_modules/@typescript-eslint/eslint-plugin": {
diff --git a/editors/code/package.json b/editors/code/package.json
index a4897899cab14..8b20011ba5010 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -26,7 +26,7 @@
         }
     },
     "engines": {
-        "vscode": "^1.78.0"
+        "vscode": "^1.75.0"
     },
     "enabledApiProposals": [],
     "scripts": {
@@ -53,7 +53,7 @@
     "devDependencies": {
         "@tsconfig/strictest": "^2.0.1",
         "@types/node": "~16.11.7",
-        "@types/vscode": "~1.78.1",
+        "@types/vscode": "~1.75",
         "@typescript-eslint/eslint-plugin": "^6.0.0",
         "@typescript-eslint/parser": "^6.0.0",
         "@vscode/test-electron": "^2.3.3",

From 10b5fd14314882633ec2162fd259246063949330 Mon Sep 17 00:00:00 2001
From: Ryo Yoshida 
Date: Thu, 27 Jul 2023 16:30:57 +0900
Subject: [PATCH 19/59] Minor refactoring

- use `str::parse()` rather than `FromStr::from_str()`
- use `iter::once()` instead of constructing `Vec` for a single element
---
 crates/proc-macro-srv/src/server.rs | 21 +++++++++++----------
 crates/tt/src/lib.rs                |  2 +-
 2 files changed, 12 insertions(+), 11 deletions(-)

diff --git a/crates/proc-macro-srv/src/server.rs b/crates/proc-macro-srv/src/server.rs
index 1980d4c78bbee..fe18451d38482 100644
--- a/crates/proc-macro-srv/src/server.rs
+++ b/crates/proc-macro-srv/src/server.rs
@@ -17,7 +17,10 @@ use token_stream::TokenStreamBuilder;
 mod symbol;
 pub use symbol::*;
 
-use std::ops::{Bound, Range};
+use std::{
+    iter,
+    ops::{Bound, Range},
+};
 
 use crate::tt;
 
@@ -80,9 +83,7 @@ impl server::TokenStream for RustAnalyzer {
         stream.is_empty()
     }
     fn from_str(&mut self, src: &str) -> Self::TokenStream {
-        use std::str::FromStr;
-
-        Self::TokenStream::from_str(src).expect("cannot parse string")
+        src.parse().expect("cannot parse string")
     }
     fn to_string(&mut self, stream: &Self::TokenStream) -> String {
         stream.to_string()
@@ -101,7 +102,7 @@ impl server::TokenStream for RustAnalyzer {
                     },
                 };
                 let tree = TokenTree::from(group);
-                Self::TokenStream::from_iter(vec![tree])
+                Self::TokenStream::from_iter(iter::once(tree))
             }
 
             bridge::TokenTree::Ident(ident) => {
@@ -111,7 +112,7 @@ impl server::TokenStream for RustAnalyzer {
                 let ident: tt::Ident = tt::Ident { text, span: ident.span };
                 let leaf = tt::Leaf::from(ident);
                 let tree = TokenTree::from(leaf);
-                Self::TokenStream::from_iter(vec![tree])
+                Self::TokenStream::from_iter(iter::once(tree))
             }
 
             bridge::TokenTree::Literal(literal) => {
@@ -123,7 +124,7 @@ impl server::TokenStream for RustAnalyzer {
                 let literal = tt::Literal { text, span: literal.0.span };
                 let leaf = tt::Leaf::from(literal);
                 let tree = TokenTree::from(leaf);
-                Self::TokenStream::from_iter(vec![tree])
+                Self::TokenStream::from_iter(iter::once(tree))
             }
 
             bridge::TokenTree::Punct(p) => {
@@ -134,7 +135,7 @@ impl server::TokenStream for RustAnalyzer {
                 };
                 let leaf = tt::Leaf::from(punct);
                 let tree = TokenTree::from(leaf);
-                Self::TokenStream::from_iter(vec![tree])
+                Self::TokenStream::from_iter(iter::once(tree))
             }
         }
     }
@@ -355,12 +356,12 @@ impl server::Server for RustAnalyzer {
     }
 
     fn intern_symbol(ident: &str) -> Self::Symbol {
-        // FIXME: should be self.interner once the proc-macro api allows is
+        // FIXME: should be `self.interner` once the proc-macro api allows it.
         Symbol::intern(&SYMBOL_INTERNER, &::tt::SmolStr::from(ident))
     }
 
     fn with_symbol_string(symbol: &Self::Symbol, f: impl FnOnce(&str)) {
-        // FIXME: should be self.interner once the proc-macro api allows is
+        // FIXME: should be `self.interner` once the proc-macro api allows it.
         f(symbol.text(&SYMBOL_INTERNER).as_str())
     }
 }
diff --git a/crates/tt/src/lib.rs b/crates/tt/src/lib.rs
index 1b8d4ba42a592..2d426f7346b78 100644
--- a/crates/tt/src/lib.rs
+++ b/crates/tt/src/lib.rs
@@ -65,7 +65,7 @@ pub mod token_id {
     }
     impl TokenTree {
         pub const fn empty() -> Self {
-            Self::Subtree(Subtree { delimiter: Delimiter::unspecified(), token_trees: vec![] })
+            Self::Subtree(Subtree::empty())
         }
     }
 

From fbec711ada52e5b0995e83697da3a5f39af8e030 Mon Sep 17 00:00:00 2001
From: Ryo Yoshida 
Date: Thu, 27 Jul 2023 16:43:01 +0900
Subject: [PATCH 20/59] Don't provide `add_missing_match_arms` assist when
 upmapping match arm list failed

---
 .../src/handlers/add_missing_match_arms.rs    | 31 +++++++++++++++----
 1 file changed, 25 insertions(+), 6 deletions(-)

diff --git a/crates/ide-assists/src/handlers/add_missing_match_arms.rs b/crates/ide-assists/src/handlers/add_missing_match_arms.rs
index ac0b74ee8e74d..3b162d7c4d82f 100644
--- a/crates/ide-assists/src/handlers/add_missing_match_arms.rs
+++ b/crates/ide-assists/src/handlers/add_missing_match_arms.rs
@@ -37,9 +37,9 @@ use crate::{utils, AssistContext, AssistId, AssistKind, Assists};
 pub(crate) fn add_missing_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
     let match_expr = ctx.find_node_at_offset_with_descend::()?;
     let match_arm_list = match_expr.match_arm_list()?;
-    let target_range = ctx.sema.original_range(match_expr.syntax()).range;
+    let arm_list_range = ctx.sema.original_range_opt(match_arm_list.syntax())?;
 
-    if let None = cursor_at_trivial_match_arm_list(ctx, &match_expr, &match_arm_list) {
+    if cursor_at_trivial_match_arm_list(ctx, &match_expr, &match_arm_list).is_none() {
         let arm_list_range = ctx.sema.original_range(match_arm_list.syntax()).range;
         let cursor_in_range = arm_list_range.contains_range(ctx.selection_trimmed());
         if cursor_in_range {
@@ -198,7 +198,7 @@ pub(crate) fn add_missing_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>)
     acc.add(
         AssistId("add_missing_match_arms", AssistKind::QuickFix),
         "Fill match arms",
-        target_range,
+        ctx.sema.original_range(match_expr.syntax()).range,
         |edit| {
             let new_match_arm_list = match_arm_list.clone_for_update();
 
@@ -262,9 +262,8 @@ pub(crate) fn add_missing_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>)
             // Just replace the element that the original range came from
             let old_place = {
                 // Find the original element
-                let old_file_range = ctx.sema.original_range(match_arm_list.syntax());
-                let file = ctx.sema.parse(old_file_range.file_id);
-                let old_place = file.syntax().covering_element(old_file_range.range);
+                let file = ctx.sema.parse(arm_list_range.file_id);
+                let old_place = file.syntax().covering_element(arm_list_range.range);
 
                 // Make `old_place` mut
                 match old_place {
@@ -1922,4 +1921,24 @@ fn foo(t: E) {
 }"#,
         );
     }
+
+    #[test]
+    fn not_applicable_when_match_arm_list_cannot_be_upmapped() {
+        check_assist_not_applicable(
+            add_missing_match_arms,
+            r#"
+macro_rules! foo {
+    ($($t:tt)*) => {
+        $($t)* {}
+    }
+}
+
+enum E { A }
+
+fn main() {
+    foo!(match E::A$0);
+}
+"#,
+        );
+    }
 }

From 17cc813e92e4770f91f9f1e03e3ceffd2fb6c0b3 Mon Sep 17 00:00:00 2001
From: hkalbasi 
Date: Thu, 27 Jul 2023 12:28:16 +0330
Subject: [PATCH 21/59] Support atomic fence intrinsic

---
 crates/hir-ty/src/consteval/tests.rs            | 8 ++++++--
 crates/hir-ty/src/consteval/tests/intrinsics.rs | 4 ++++
 crates/hir-ty/src/mir/eval.rs                   | 4 +---
 crates/hir-ty/src/mir/eval/shim.rs              | 9 ++++++++-
 crates/hir-ty/src/mir/lower.rs                  | 5 +++++
 5 files changed, 24 insertions(+), 6 deletions(-)

diff --git a/crates/hir-ty/src/consteval/tests.rs b/crates/hir-ty/src/consteval/tests.rs
index 98ebe557245fd..3d73a55a351a2 100644
--- a/crates/hir-ty/src/consteval/tests.rs
+++ b/crates/hir-ty/src/consteval/tests.rs
@@ -2521,12 +2521,16 @@ fn const_trait_assoc() {
     );
     check_number(
         r#"
-    //- minicore: size_of
+    //- minicore: size_of, fn
     //- /a/lib.rs crate:a
     use core::mem::size_of;
     pub struct S(T);
     impl S {
-        pub const X: usize = core::mem::size_of::();
+        pub const X: usize = {
+            let k: T;
+            let f = || core::mem::size_of::();
+            f()
+        };
     }
     //- /main.rs crate:main deps:a
     use a::{S};
diff --git a/crates/hir-ty/src/consteval/tests/intrinsics.rs b/crates/hir-ty/src/consteval/tests/intrinsics.rs
index 9253e31d77b67..b0682866004c5 100644
--- a/crates/hir-ty/src/consteval/tests/intrinsics.rs
+++ b/crates/hir-ty/src/consteval/tests/intrinsics.rs
@@ -438,6 +438,8 @@ fn atomic() {
             pub fn atomic_nand_seqcst(dst: *mut T, src: T) -> T;
             pub fn atomic_or_release(dst: *mut T, src: T) -> T;
             pub fn atomic_xor_seqcst(dst: *mut T, src: T) -> T;
+            pub fn atomic_fence_seqcst();
+            pub fn atomic_singlethreadfence_acqrel();
         }
 
         fn should_not_reach() {
@@ -452,6 +454,7 @@ fn atomic() {
             if (30, true) != atomic_cxchg_release_seqcst(&mut y, 30, 40) {
                 should_not_reach();
             }
+            atomic_fence_seqcst();
             if (40, false) != atomic_cxchg_release_seqcst(&mut y, 30, 50) {
                 should_not_reach();
             }
@@ -459,6 +462,7 @@ fn atomic() {
                 should_not_reach();
             }
             let mut z = atomic_xsub_seqcst(&mut x, -200);
+            atomic_singlethreadfence_acqrel();
             atomic_xor_seqcst(&mut x, 1024);
             atomic_load_seqcst(&x) + z * 3 + atomic_load_seqcst(&y) * 2
         };
diff --git a/crates/hir-ty/src/mir/eval.rs b/crates/hir-ty/src/mir/eval.rs
index 7bd2756c14f40..be66464864f90 100644
--- a/crates/hir-ty/src/mir/eval.rs
+++ b/crates/hir-ty/src/mir/eval.rs
@@ -702,9 +702,7 @@ impl Evaluator<'_> {
     }
 
     fn layout_adt(&self, adt: AdtId, subst: Substitution) -> Result> {
-        self.db.layout_of_adt(adt, subst.clone(), self.trait_env.clone()).map_err(|e| {
-            MirEvalError::LayoutError(e, TyKind::Adt(chalk_ir::AdtId(adt), subst).intern(Interner))
-        })
+        self.layout(&TyKind::Adt(chalk_ir::AdtId(adt), subst).intern(Interner))
     }
 
     fn place_ty<'a>(&'a self, p: &Place, locals: &'a Locals) -> Result {
diff --git a/crates/hir-ty/src/mir/eval/shim.rs b/crates/hir-ty/src/mir/eval/shim.rs
index 9ad6087cad9e8..d21b05c61206c 100644
--- a/crates/hir-ty/src/mir/eval/shim.rs
+++ b/crates/hir-ty/src/mir/eval/shim.rs
@@ -1057,7 +1057,14 @@ impl Evaluator<'_> {
         _span: MirSpan,
     ) -> Result<()> {
         // We are a single threaded runtime with no UB checking and no optimization, so
-        // we can implement these as normal functions.
+        // we can implement atomic intrinsics as normal functions.
+
+        if name.starts_with("singlethreadfence_") || name.starts_with("fence_") {
+            return Ok(());
+        }
+
+        // The rest of atomic intrinsics have exactly one generic arg
+
         let Some(ty) = generic_args.as_slice(Interner).get(0).and_then(|it| it.ty(Interner)) else {
             return Err(MirEvalError::TypeError("atomic intrinsic generic arg is not provided"));
         };
diff --git a/crates/hir-ty/src/mir/lower.rs b/crates/hir-ty/src/mir/lower.rs
index e443c58f22f55..2325426ff529d 100644
--- a/crates/hir-ty/src/mir/lower.rs
+++ b/crates/hir-ty/src/mir/lower.rs
@@ -660,6 +660,11 @@ impl<'ctx> MirLowerCtx<'ctx> {
                             expr_id.into(),
                         )
                     }
+                    TyKind::Closure(_, _) => {
+                        not_supported!(
+                            "method resolution not emitted for closure (Are Fn traits available?)"
+                        );
+                    }
                     TyKind::Error => {
                         return Err(MirLowerError::MissingFunctionDefinition(self.owner, expr_id))
                     }

From 9349769363624795db79a860b5a4b46fec9b0930 Mon Sep 17 00:00:00 2001
From: Max Heller 
Date: Thu, 27 Jul 2023 19:33:00 -0400
Subject: [PATCH 22/59] exclude non-identifier aliases from completion
 filtering text

---
 Cargo.lock                        |  1 +
 crates/ide-completion/Cargo.toml  |  1 +
 crates/ide-completion/src/item.rs | 34 +++++++++++++++++++++++++++++--
 3 files changed, 34 insertions(+), 2 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index f8806794979e7..f07c08a77bcc2 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -715,6 +715,7 @@ dependencies = [
  "syntax",
  "test-utils",
  "text-edit",
+ "unicode-ident",
 ]
 
 [[package]]
diff --git a/crates/ide-completion/Cargo.toml b/crates/ide-completion/Cargo.toml
index 092fb303668fe..c06ac55aae332 100644
--- a/crates/ide-completion/Cargo.toml
+++ b/crates/ide-completion/Cargo.toml
@@ -17,6 +17,7 @@ itertools = "0.10.5"
 
 once_cell = "1.17.0"
 smallvec.workspace = true
+unicode-ident = "1.0.0"
 
 
 # local deps
diff --git a/crates/ide-completion/src/item.rs b/crates/ide-completion/src/item.rs
index e850f7bfdf3f9..d5713db0a36b5 100644
--- a/crates/ide-completion/src/item.rs
+++ b/crates/ide-completion/src/item.rs
@@ -427,9 +427,21 @@ impl Builder {
         let insert_text = self.insert_text.unwrap_or_else(|| label.to_string());
 
         if !self.doc_aliases.is_empty() {
-            let doc_aliases = self.doc_aliases.into_iter().join(", ");
+            let doc_aliases = self.doc_aliases.iter().join(", ");
             label = SmolStr::from(format!("{label} (alias {doc_aliases})"));
-            lookup = SmolStr::from(format!("{lookup} {doc_aliases}"));
+            let lookup_doc_aliases = (self.doc_aliases.iter())
+                // Don't include aliases in `lookup` that aren't valid identifiers as including
+                // them results in weird completion filtering behavior e.g. `Partial>` matching
+                // `PartialOrd` because it has an alias of ">".
+                .filter(|alias| {
+                    let mut chars = alias.chars();
+                    chars.next().map(unicode_ident::is_xid_start).unwrap_or(false)
+                        && chars.all(unicode_ident::is_xid_continue)
+                })
+                .join(", ");
+            if !lookup_doc_aliases.is_empty() {
+                lookup = SmolStr::from(format!("{lookup} {lookup_doc_aliases}"));
+            }
         }
         if let [import_edit] = &*self.imports_to_add {
             // snippets can have multiple imports, but normal completions only have up to one
@@ -553,9 +565,12 @@ impl Builder {
 
 #[cfg(test)]
 mod tests {
+    use ide_db::SymbolKind;
     use itertools::Itertools;
     use test_utils::assert_eq_text;
 
+    use crate::{CompletionItem, CompletionItemKind};
+
     use super::{
         CompletionRelevance, CompletionRelevancePostfixMatch, CompletionRelevanceTypeMatch,
     };
@@ -630,4 +645,19 @@ mod tests {
 
         check_relevance_score_ordered(expected_relevance_order);
     }
+
+    #[test]
+    fn exclude_non_identifier_aliases_from_lookup() {
+        let mut item = CompletionItem::new(
+            CompletionItemKind::SymbolKind(SymbolKind::Trait),
+            Default::default(),
+            "PartialOrd",
+        );
+        let aliases = [">", "<", "<=", ">="];
+        item.doc_aliases(aliases.iter().map(|&alias| alias.into()).collect());
+        let item = item.build(&Default::default());
+        for alias in aliases {
+            assert!(!item.lookup().contains(alias));
+        }
+    }
 }

From b517aeeca5976955f8f49e8ce3de4bc63cf629c9 Mon Sep 17 00:00:00 2001
From: Ryo Yoshida 
Date: Fri, 28 Jul 2023 19:09:38 +0900
Subject: [PATCH 23/59] Show `TyKind::FnDef` as a fn pointer in source code

---
 crates/hir-ty/src/display.rs                  |  7 +++++++
 .../hir-ty/src/tests/display_source_code.rs   | 19 +++++++++++++++++++
 .../src/handlers/generate_function.rs         |  3 +--
 3 files changed, 27 insertions(+), 2 deletions(-)

diff --git a/crates/hir-ty/src/display.rs b/crates/hir-ty/src/display.rs
index 96787959e1f2c..f53e24c238d81 100644
--- a/crates/hir-ty/src/display.rs
+++ b/crates/hir-ty/src/display.rs
@@ -885,6 +885,13 @@ impl HirDisplay for Ty {
             TyKind::FnDef(def, parameters) => {
                 let def = from_chalk(db, *def);
                 let sig = db.callable_item_signature(def).substitute(Interner, parameters);
+
+                if f.display_target.is_source_code() {
+                    // `FnDef` is anonymous and there's no surface syntax for it. Show it as a
+                    // function pointer type.
+                    return sig.hir_fmt(f);
+                }
+
                 f.start_location_link(def.into());
                 match def {
                     CallableDefId::FunctionId(ff) => {
diff --git a/crates/hir-ty/src/tests/display_source_code.rs b/crates/hir-ty/src/tests/display_source_code.rs
index 425432479e815..e75b037e38d3e 100644
--- a/crates/hir-ty/src/tests/display_source_code.rs
+++ b/crates/hir-ty/src/tests/display_source_code.rs
@@ -227,3 +227,22 @@ fn f(a: impl Foo = i32>) {
         "#,
     );
 }
+
+#[test]
+fn fn_def_is_shown_as_fn_ptr() {
+    check_types_source_code(
+        r#"
+fn foo(_: i32) -> i64 { 42 }
+struct S(T);
+enum E { A(usize) }
+fn test() {
+    let f = foo;
+      //^ fn(i32) -> i64
+    let f = S::;
+      //^ fn(i8) -> S
+    let f = E::A;
+      //^ fn(usize) -> E
+}
+"#,
+    );
+}
diff --git a/crates/ide-assists/src/handlers/generate_function.rs b/crates/ide-assists/src/handlers/generate_function.rs
index 8085572497aa7..5b13e01b13305 100644
--- a/crates/ide-assists/src/handlers/generate_function.rs
+++ b/crates/ide-assists/src/handlers/generate_function.rs
@@ -1878,7 +1878,6 @@ where
 
     #[test]
     fn add_function_with_fn_arg() {
-        // FIXME: The argument in `bar` is wrong.
         check_assist(
             generate_function,
             r"
@@ -1899,7 +1898,7 @@ fn foo() {
     bar(Baz::new);
 }
 
-fn bar(new: fn) ${0:-> _} {
+fn bar(new: fn() -> Baz) ${0:-> _} {
     todo!()
 }
 ",

From 104d707d6a35b5f3f77c3a7d02a9787daf05547f Mon Sep 17 00:00:00 2001
From: Ryo Yoshida 
Date: Fri, 28 Jul 2023 19:11:55 +0900
Subject: [PATCH 24/59] Add default implementation for `HirWrite` methods

---
 crates/hir-ty/src/display.rs | 15 ++++-----------
 1 file changed, 4 insertions(+), 11 deletions(-)

diff --git a/crates/hir-ty/src/display.rs b/crates/hir-ty/src/display.rs
index f53e24c238d81..1b4ee4613d61c 100644
--- a/crates/hir-ty/src/display.rs
+++ b/crates/hir-ty/src/display.rs
@@ -48,22 +48,15 @@ use crate::{
 };
 
 pub trait HirWrite: fmt::Write {
-    fn start_location_link(&mut self, location: ModuleDefId);
-    fn end_location_link(&mut self);
+    fn start_location_link(&mut self, _location: ModuleDefId) {}
+    fn end_location_link(&mut self) {}
 }
 
 // String will ignore link metadata
-impl HirWrite for String {
-    fn start_location_link(&mut self, _: ModuleDefId) {}
-
-    fn end_location_link(&mut self) {}
-}
+impl HirWrite for String {}
 
 // `core::Formatter` will ignore metadata
-impl HirWrite for fmt::Formatter<'_> {
-    fn start_location_link(&mut self, _: ModuleDefId) {}
-    fn end_location_link(&mut self) {}
-}
+impl HirWrite for fmt::Formatter<'_> {}
 
 pub struct HirFormatter<'a> {
     pub db: &'a dyn HirDatabase,

From 047bc47ecd5f0769b5eaa4e56001b59bdfc5cdf1 Mon Sep 17 00:00:00 2001
From: Max Heller 
Date: Fri, 28 Jul 2023 09:22:22 -0400
Subject: [PATCH 25/59] Cleanup

Co-authored-by: LowR 
---
 crates/ide-completion/src/item.rs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/crates/ide-completion/src/item.rs b/crates/ide-completion/src/item.rs
index d5713db0a36b5..40e51201987b0 100644
--- a/crates/ide-completion/src/item.rs
+++ b/crates/ide-completion/src/item.rs
@@ -435,7 +435,7 @@ impl Builder {
                 // `PartialOrd` because it has an alias of ">".
                 .filter(|alias| {
                     let mut chars = alias.chars();
-                    chars.next().map(unicode_ident::is_xid_start).unwrap_or(false)
+                    chars.next().is_some_and(unicode_ident::is_xid_start)
                         && chars.all(unicode_ident::is_xid_continue)
                 })
                 .join(", ");

From bc2b70d678f0a99484b9353cf2fdb6a0e25a3db5 Mon Sep 17 00:00:00 2001
From: Max Heller 
Date: Fri, 28 Jul 2023 09:23:05 -0400
Subject: [PATCH 26/59] formatting

---
 crates/ide-completion/src/item.rs | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/crates/ide-completion/src/item.rs b/crates/ide-completion/src/item.rs
index 40e51201987b0..c7879d7bf3ad5 100644
--- a/crates/ide-completion/src/item.rs
+++ b/crates/ide-completion/src/item.rs
@@ -429,7 +429,9 @@ impl Builder {
         if !self.doc_aliases.is_empty() {
             let doc_aliases = self.doc_aliases.iter().join(", ");
             label = SmolStr::from(format!("{label} (alias {doc_aliases})"));
-            let lookup_doc_aliases = (self.doc_aliases.iter())
+            let lookup_doc_aliases = self
+                .doc_aliases
+                .iter()
                 // Don't include aliases in `lookup` that aren't valid identifiers as including
                 // them results in weird completion filtering behavior e.g. `Partial>` matching
                 // `PartialOrd` because it has an alias of ">".

From bd2a8ca507daddca13bb30e2ba93a02e238801cf Mon Sep 17 00:00:00 2001
From: hkalbasi 
Date: Fri, 28 Jul 2023 18:52:01 +0330
Subject: [PATCH 27/59] Add manual implementation of clone for tuples in mir
 interpreter

---
 crates/hir-ty/src/consteval/tests.rs | 34 +++++++++++--
 crates/hir-ty/src/layout.rs          | 24 +++++++--
 crates/hir-ty/src/mir/eval.rs        | 13 ++++-
 crates/hir-ty/src/mir/eval/shim.rs   | 76 ++++++++++++++++++++++++++++
 4 files changed, 138 insertions(+), 9 deletions(-)

diff --git a/crates/hir-ty/src/consteval/tests.rs b/crates/hir-ty/src/consteval/tests.rs
index 3d73a55a351a2..43792994988d9 100644
--- a/crates/hir-ty/src/consteval/tests.rs
+++ b/crates/hir-ty/src/consteval/tests.rs
@@ -1428,14 +1428,14 @@ fn builtin_derive_macro() {
     #[derive(Clone)]
     struct Y {
         field1: i32,
-        field2: u8,
+        field2: ((i32, u8), i64),
     }
 
     const GOAL: u8 = {
-        let x = X(2, Z::Foo(Y { field1: 4, field2: 5 }), 8);
+        let x = X(2, Z::Foo(Y { field1: 4, field2: ((32, 5), 12) }), 8);
         let x = x.clone();
         let Z::Foo(t) = x.1;
-        t.field2
+        t.field2.0 .1
     };
     "#,
         5,
@@ -1632,6 +1632,34 @@ const GOAL: i32 = {
     );
 }
 
+#[test]
+fn closure_capture_unsized_type() {
+    check_number(
+        r#"
+    //- minicore: fn, copy, slice, index, coerce_unsized
+    fn f(x: &::Ty) -> &::Ty {
+        let c = || &*x;
+        c()
+    }
+
+    trait A {
+        type Ty;
+    }
+
+    impl A for i32 {
+        type Ty = [u8];
+    }
+
+    const GOAL: u8 = {
+        let k: &[u8] = &[1, 2, 3];
+        let k = f::(k);
+        k[0] + k[1] + k[2]
+    }
+    "#,
+        6,
+    );
+}
+
 #[test]
 fn closure_and_impl_fn() {
     check_number(
diff --git a/crates/hir-ty/src/layout.rs b/crates/hir-ty/src/layout.rs
index ffc7a6f2ebd7c..b15339d4434cb 100644
--- a/crates/hir-ty/src/layout.rs
+++ b/crates/hir-ty/src/layout.rs
@@ -14,7 +14,7 @@ use triomphe::Arc;
 
 use crate::{
     consteval::try_const_usize, db::HirDatabase, infer::normalize, layout::adt::struct_variant_idx,
-    utils::ClosureSubst, Interner, Substitution, TraitEnvironment, Ty,
+    utils::ClosureSubst, Interner, ProjectionTy, Substitution, TraitEnvironment, Ty,
 };
 
 pub use self::{
@@ -279,7 +279,15 @@ pub fn layout_of_ty_query(
             //     return Ok(tcx.mk_layout(LayoutS::scalar(cx, data_ptr)));
             // }
 
-            let unsized_part = struct_tail_erasing_lifetimes(db, pointee.clone());
+            let mut unsized_part = struct_tail_erasing_lifetimes(db, pointee.clone());
+            if let TyKind::AssociatedType(id, subst) = unsized_part.kind(Interner) {
+                unsized_part = TyKind::Alias(chalk_ir::AliasTy::Projection(ProjectionTy {
+                    associated_ty_id: *id,
+                    substitution: subst.clone(),
+                }))
+                .intern(Interner);
+            }
+            unsized_part = normalize(db, trait_env.clone(), unsized_part);
             let metadata = match unsized_part.kind(Interner) {
                 TyKind::Slice(_) | TyKind::Str => {
                     scalar_unit(dl, Primitive::Int(dl.ptr_sized_integer(), false))
@@ -362,8 +370,16 @@ pub fn layout_of_ty_query(
             return Err(LayoutError::NotImplemented)
         }
         TyKind::Error => return Err(LayoutError::HasErrorType),
-        TyKind::AssociatedType(_, _)
-        | TyKind::Alias(_)
+        TyKind::AssociatedType(id, subst) => {
+            // Try again with `TyKind::Alias` to normalize the associated type.
+            let ty = TyKind::Alias(chalk_ir::AliasTy::Projection(ProjectionTy {
+                associated_ty_id: *id,
+                substitution: subst.clone(),
+            }))
+            .intern(Interner);
+            return db.layout_of_ty(ty, trait_env);
+        }
+        TyKind::Alias(_)
         | TyKind::Placeholder(_)
         | TyKind::BoundVar(_)
         | TyKind::InferenceVar(_, _) => return Err(LayoutError::HasPlaceholder),
diff --git a/crates/hir-ty/src/mir/eval.rs b/crates/hir-ty/src/mir/eval.rs
index be66464864f90..2d5d7c7b1f7a4 100644
--- a/crates/hir-ty/src/mir/eval.rs
+++ b/crates/hir-ty/src/mir/eval.rs
@@ -313,6 +313,7 @@ pub enum MirEvalError {
     InvalidVTableId(usize),
     CoerceUnsizedError(Ty),
     LangItemNotFound(LangItem),
+    BrokenLayout(Layout),
 }
 
 impl MirEvalError {
@@ -399,6 +400,7 @@ impl MirEvalError {
             | MirEvalError::TargetDataLayoutNotAvailable
             | MirEvalError::CoerceUnsizedError(_)
             | MirEvalError::LangItemNotFound(_)
+            | MirEvalError::BrokenLayout(_)
             | MirEvalError::InvalidVTableId(_) => writeln!(f, "{:?}", err)?,
         }
         Ok(())
@@ -433,6 +435,7 @@ impl std::fmt::Debug for MirEvalError {
             Self::CoerceUnsizedError(arg0) => {
                 f.debug_tuple("CoerceUnsizedError").field(arg0).finish()
             }
+            Self::BrokenLayout(arg0) => f.debug_tuple("BrokenLayout").field(arg0).finish(),
             Self::InvalidVTableId(arg0) => f.debug_tuple("InvalidVTableId").field(arg0).finish(),
             Self::NotSupported(arg0) => f.debug_tuple("NotSupported").field(arg0).finish(),
             Self::InvalidConst(arg0) => {
@@ -1541,12 +1544,18 @@ impl Evaluator<'_> {
     ) -> Result> {
         let mut result = vec![0; size];
         if let Some((offset, size, value)) = tag {
-            result[offset..offset + size].copy_from_slice(&value.to_le_bytes()[0..size]);
+            match result.get_mut(offset..offset + size) {
+                Some(it) => it.copy_from_slice(&value.to_le_bytes()[0..size]),
+                None => return Err(MirEvalError::BrokenLayout(variant_layout.clone())),
+            }
         }
         for (i, op) in values.enumerate() {
             let offset = variant_layout.fields.offset(i).bytes_usize();
             let op = op.get(&self)?;
-            result[offset..offset + op.len()].copy_from_slice(op);
+            match result.get_mut(offset..offset + op.len()) {
+                Some(it) => it.copy_from_slice(op),
+                None => return Err(MirEvalError::BrokenLayout(variant_layout.clone())),
+            }
         }
         Ok(result)
     }
diff --git a/crates/hir-ty/src/mir/eval/shim.rs b/crates/hir-ty/src/mir/eval/shim.rs
index d21b05c61206c..356bf70a5fb5a 100644
--- a/crates/hir-ty/src/mir/eval/shim.rs
+++ b/crates/hir-ty/src/mir/eval/shim.rs
@@ -124,9 +124,85 @@ impl Evaluator<'_> {
             destination.write_from_bytes(self, &result)?;
             return Ok(true);
         }
+        if let ItemContainerId::TraitId(t) = def.lookup(self.db.upcast()).container {
+            if self.db.lang_attr(t.into()) == Some(LangItem::Clone) {
+                let [self_ty] = generic_args.as_slice(Interner) else {
+                    not_supported!("wrong generic arg count for clone");
+                };
+                let Some(self_ty) = self_ty.ty(Interner) else {
+                    not_supported!("wrong generic arg kind for clone");
+                };
+                // Clone has special impls for tuples and function pointers
+                if matches!(self_ty.kind(Interner), TyKind::Function(_) | TyKind::Tuple(..)) {
+                    self.exec_clone(def, args, self_ty.clone(), locals, destination, span)?;
+                    return Ok(true);
+                }
+            }
+        }
         Ok(false)
     }
 
+    /// Clone has special impls for tuples and function pointers
+    fn exec_clone(
+        &mut self,
+        def: FunctionId,
+        args: &[IntervalAndTy],
+        self_ty: Ty,
+        locals: &Locals,
+        destination: Interval,
+        span: MirSpan,
+    ) -> Result<()> {
+        match self_ty.kind(Interner) {
+            TyKind::Function(_) => {
+                let [arg] = args else {
+                    not_supported!("wrong arg count for clone");
+                };
+                let addr = Address::from_bytes(arg.get(self)?)?;
+                return destination
+                    .write_from_interval(self, Interval { addr, size: destination.size });
+            }
+            TyKind::Tuple(_, subst) => {
+                let [arg] = args else {
+                    not_supported!("wrong arg count for clone");
+                };
+                let addr = Address::from_bytes(arg.get(self)?)?;
+                let layout = self.layout(&self_ty)?;
+                for (i, ty) in subst.iter(Interner).enumerate() {
+                    let ty = ty.assert_ty_ref(Interner);
+                    let size = self.layout(ty)?.size.bytes_usize();
+                    let tmp = self.heap_allocate(self.ptr_size(), self.ptr_size())?;
+                    let arg = IntervalAndTy {
+                        interval: Interval { addr: tmp, size: self.ptr_size() },
+                        ty: TyKind::Ref(Mutability::Not, static_lifetime(), ty.clone())
+                            .intern(Interner),
+                    };
+                    let offset = layout.fields.offset(i).bytes_usize();
+                    self.write_memory(tmp, &addr.offset(offset).to_bytes())?;
+                    self.exec_clone(
+                        def,
+                        &[arg],
+                        ty.clone(),
+                        locals,
+                        destination.slice(offset..offset + size),
+                        span,
+                    )?;
+                }
+            }
+            _ => {
+                self.exec_fn_with_args(
+                    def,
+                    args,
+                    Substitution::from1(Interner, self_ty),
+                    locals,
+                    destination,
+                    None,
+                    span,
+                )?;
+            }
+        }
+        Ok(())
+    }
+
     fn exec_alloc_fn(
         &mut self,
         alloc_fn: &str,

From 4bb7702833f9c172694579adb53c830d5ba9ca2d Mon Sep 17 00:00:00 2001
From: Max Heller 
Date: Sat, 29 Jul 2023 11:39:59 -0400
Subject: [PATCH 28/59] `check_edit` test

---
 crates/ide-completion/src/tests.rs         | 28 ++++++++++++++++++----
 crates/ide-completion/src/tests/special.rs | 25 +++++++++++++++++++
 2 files changed, 49 insertions(+), 4 deletions(-)

diff --git a/crates/ide-completion/src/tests.rs b/crates/ide-completion/src/tests.rs
index 2464e8d5f8175..63c7f789d3b6d 100644
--- a/crates/ide-completion/src/tests.rs
+++ b/crates/ide-completion/src/tests.rs
@@ -23,6 +23,8 @@ mod type_pos;
 mod use_tree;
 mod visibility;
 
+use std::ops::ControlFlow;
+
 use expect_test::Expect;
 use hir::PrefixKind;
 use ide_db::{
@@ -185,11 +187,29 @@ pub(crate) fn check_edit_with_config(
     let (db, position) = position(ra_fixture_before);
     let completions: Vec =
         crate::completions(&db, &config, position, None).unwrap();
-    let (completion,) = completions
+    let matching = completions
         .iter()
-        .filter(|it| it.lookup() == what)
-        .collect_tuple()
-        .unwrap_or_else(|| panic!("can't find {what:?} completion in {completions:#?}"));
+        // Match IDE behavior by considering completions as matching if `what` is a subsequence
+        // of the completion's lookup text.
+        .filter(|it| {
+            let mut lookup = it.lookup().chars();
+            what.chars().all(|c| lookup.contains(&c))
+        })
+        // Select the first exact match if one exists, or the first subsequence match if not
+        .try_fold(None, |first_match, completion| {
+            let exact_match = completion.lookup() == what;
+            if exact_match {
+                ControlFlow::Break(completion)
+            } else {
+                ControlFlow::Continue(first_match.or(Some(completion)))
+            }
+        });
+    let completion = match matching {
+        ControlFlow::Continue(first_match) => first_match
+            .unwrap_or_else(|| panic!("can't find {what:?} completion in {completions:#?}")),
+        ControlFlow::Break(exact_match) => exact_match,
+    };
+
     let mut actual = db.file_text(position.file_id).to_string();
 
     let mut combined_edit = completion.text_edit.clone();
diff --git a/crates/ide-completion/src/tests/special.rs b/crates/ide-completion/src/tests/special.rs
index 3824720839eb9..a50184de533cf 100644
--- a/crates/ide-completion/src/tests/special.rs
+++ b/crates/ide-completion/src/tests/special.rs
@@ -1280,3 +1280,28 @@ fn here_we_go() {
         "#]],
     );
 }
+
+#[test]
+fn completion_filtering_excludes_non_identifier_aliases() {
+    // Catch panic instead of using `#[should_panic]` as style check bans
+    // `#[should_panic]`. Making `check_edit` return a result would require
+    // a lot of test changes.
+    std::panic::catch_unwind(|| {
+        check_edit(
+            "Partial>",
+            r#"
+#[doc(alias = ">")]
+trait PartialOrd {}
+
+struct Foo
Date: Sun, 30 Jul 2023 11:41:21 +0200
Subject: [PATCH 29/59] fix: Fix bad unwrap in eager_macro_recur

---
 crates/hir-expand/src/eager.rs | 58 ++++++++++++++++++----------------
 1 file changed, 31 insertions(+), 27 deletions(-)

diff --git a/crates/hir-expand/src/eager.rs b/crates/hir-expand/src/eager.rs
index 876813eab5d5a..1d8e156241559 100644
--- a/crates/hir-expand/src/eager.rs
+++ b/crates/hir-expand/src/eager.rs
@@ -205,17 +205,19 @@ fn eager_macro_recur(
                         let ExpandResult { value, err: err2 } =
                             db.parse_macro_expansion(call_id.as_macro_file());
 
-                        let call_tt_start =
-                            call.token_tree().unwrap().syntax().text_range().start();
-                        let call_start = apply_offset(call.syntax().text_range().start(), offset);
-                        if let Some((_, arg_map, _)) = db.macro_arg(call_id).value.as_deref() {
-                            mapping.extend(arg_map.entries().filter_map(|(tid, range)| {
-                                value
-                                    .1
-                                    .first_range_by_token(tid, syntax::SyntaxKind::TOMBSTONE)
-                                    .map(|r| (r + call_start, range + call_tt_start))
-                            }));
-                        };
+                        if let Some(tt) = call.token_tree() {
+                            let call_tt_start = tt.syntax().text_range().start();
+                            let call_start =
+                                apply_offset(call.syntax().text_range().start(), offset);
+                            if let Some((_, arg_map, _)) = db.macro_arg(call_id).value.as_deref() {
+                                mapping.extend(arg_map.entries().filter_map(|(tid, range)| {
+                                    value
+                                        .1
+                                        .first_range_by_token(tid, syntax::SyntaxKind::TOMBSTONE)
+                                        .map(|r| (r + call_start, range + call_tt_start))
+                                }));
+                            }
+                        }
 
                         ExpandResult {
                             value: Some(value.0.syntax_node().clone_for_update()),
@@ -250,22 +252,24 @@ fn eager_macro_recur(
                 )?;
                 let err = err.or(error);
 
-                let call_tt_start = call.token_tree().unwrap().syntax().text_range().start();
-                let call_start = apply_offset(call.syntax().text_range().start(), offset);
-                if let Some((_tt, arg_map, _)) = parse
-                    .file_id
-                    .macro_file()
-                    .and_then(|id| db.macro_arg(id.macro_call_id).value)
-                    .as_deref()
-                {
-                    mapping.extend(arg_map.entries().filter_map(|(tid, range)| {
-                        tm.first_range_by_token(
-                            decl_mac.as_ref().map(|it| it.map_id_down(tid)).unwrap_or(tid),
-                            syntax::SyntaxKind::TOMBSTONE,
-                        )
-                        .map(|r| (r + call_start, range + call_tt_start))
-                    }));
-                };
+                if let Some(tt) = call.token_tree() {
+                    let call_tt_start = tt.syntax().text_range().start();
+                    let call_start = apply_offset(call.syntax().text_range().start(), offset);
+                    if let Some((_tt, arg_map, _)) = parse
+                        .file_id
+                        .macro_file()
+                        .and_then(|id| db.macro_arg(id.macro_call_id).value)
+                        .as_deref()
+                    {
+                        mapping.extend(arg_map.entries().filter_map(|(tid, range)| {
+                            tm.first_range_by_token(
+                                decl_mac.as_ref().map(|it| it.map_id_down(tid)).unwrap_or(tid),
+                                syntax::SyntaxKind::TOMBSTONE,
+                            )
+                            .map(|r| (r + call_start, range + call_tt_start))
+                        }));
+                    }
+                }
                 // FIXME: Do we need to re-use _m here?
                 ExpandResult { value: value.map(|(n, _m)| n), err }
             }

From df725d6b6d841730fd07d68b62b6d8c2eb68a78f Mon Sep 17 00:00:00 2001
From: Lukas Wirth 
Date: Sun, 30 Jul 2023 12:18:19 +0200
Subject: [PATCH 30/59] fix: Do not create fn macro calls with non-fn expanders

---
 crates/hir-def/src/lib.rs    | 17 +++++++++++------
 crates/hir-expand/src/lib.rs | 18 ++++++++++++++++++
 2 files changed, 29 insertions(+), 6 deletions(-)

diff --git a/crates/hir-def/src/lib.rs b/crates/hir-def/src/lib.rs
index 1e74e2dfcb4c3..9270cbfe89093 100644
--- a/crates/hir-def/src/lib.rs
+++ b/crates/hir-def/src/lib.rs
@@ -1155,18 +1155,22 @@ fn macro_call_as_call_id_(
     let def =
         resolver(call.path.clone()).ok_or_else(|| UnresolvedMacro { path: call.path.clone() })?;
 
-    let res = if let MacroDefKind::BuiltInEager(..) = def.kind {
-        let macro_call = InFile::new(call.ast_id.file_id, call.ast_id.to_node(db));
-        expand_eager_macro_input(db, krate, macro_call, def, &resolver)?
-    } else {
-        ExpandResult {
+    let res = match def.kind {
+        MacroDefKind::BuiltInEager(..) => {
+            let macro_call = InFile::new(call.ast_id.file_id, call.ast_id.to_node(db));
+            expand_eager_macro_input(db, krate, macro_call, def, &|path| {
+                resolver(path).filter(MacroDefId::is_fn_like)
+            })?
+        }
+        _ if def.is_fn_like() => ExpandResult {
             value: Some(def.as_lazy_macro(
                 db,
                 krate,
                 MacroCallKind::FnLike { ast_id: call.ast_id, expand_to },
             )),
             err: None,
-        }
+        },
+        _ => return Err(UnresolvedMacro { path: call.path.clone() }),
     };
     Ok(res)
 }
@@ -1251,6 +1255,7 @@ fn derive_macro_as_call_id(
     resolver: impl Fn(path::ModPath) -> Option<(MacroId, MacroDefId)>,
 ) -> Result<(MacroId, MacroDefId, MacroCallId), UnresolvedMacro> {
     let (macro_id, def_id) = resolver(item_attr.path.clone())
+        .filter(|(_, def_id)| def_id.is_derive())
         .ok_or_else(|| UnresolvedMacro { path: item_attr.path.clone() })?;
     let call_id = def_id.as_lazy_macro(
         db.upcast(),
diff --git a/crates/hir-expand/src/lib.rs b/crates/hir-expand/src/lib.rs
index 9ed6c31ddde56..1f1e20f49e3c4 100644
--- a/crates/hir-expand/src/lib.rs
+++ b/crates/hir-expand/src/lib.rs
@@ -415,6 +415,24 @@ impl MacroDefId {
         )
     }
 
+    pub fn is_derive(&self) -> bool {
+        matches!(
+            self.kind,
+            MacroDefKind::BuiltInDerive(..)
+                | MacroDefKind::ProcMacro(_, ProcMacroKind::CustomDerive, _)
+        )
+    }
+
+    pub fn is_fn_like(&self) -> bool {
+        matches!(
+            self.kind,
+            MacroDefKind::BuiltIn(..)
+                | MacroDefKind::ProcMacro(_, ProcMacroKind::FuncLike, _)
+                | MacroDefKind::BuiltInEager(..)
+                | MacroDefKind::Declarative(..)
+        )
+    }
+
     pub fn is_attribute_derive(&self) -> bool {
         matches!(self.kind, MacroDefKind::BuiltInAttr(expander, ..) if expander.is_derive())
     }

From 4172dcc9fd617c127b690f3017dfdda9c5cc4478 Mon Sep 17 00:00:00 2001
From: Lukas Wirth 
Date: Sun, 30 Jul 2023 14:02:03 +0200
Subject: [PATCH 31/59] Add triagebot no-merges config

---
 triagebot.toml | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/triagebot.toml b/triagebot.toml
index a910e012b7349..f0cd35399752f 100644
--- a/triagebot.toml
+++ b/triagebot.toml
@@ -9,3 +9,7 @@ allow-unauthenticated = [
 
 [autolabel."S-waiting-on-review"]
 new_pr = true
+
+[no-merges]
+exclude_labels = ["sync"]
+labels = ["has-merge-commits", "S-waiting-on-author"]

From bd6ec062373147421b8a396bb49c4cbc7cc98b3b Mon Sep 17 00:00:00 2001
From: Lukas Wirth 
Date: Sun, 30 Jul 2023 14:35:20 +0200
Subject: [PATCH 32/59] Write proc-macro server spawn errors to the status text

---
 crates/rust-analyzer/src/reload.rs | 5 +++++
 editors/code/src/ctx.ts            | 2 +-
 2 files changed, 6 insertions(+), 1 deletion(-)

diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs
index 18d9151d4aabc..0a2bb8224757c 100644
--- a/crates/rust-analyzer/src/reload.rs
+++ b/crates/rust-analyzer/src/reload.rs
@@ -114,6 +114,11 @@ impl GlobalState {
         if self.proc_macro_clients.iter().any(|it| it.is_err()) {
             status.health = lsp_ext::Health::Warning;
             message.push_str("Failed to spawn one or more proc-macro servers.\n\n");
+            for err in self.proc_macro_clients.iter() {
+                if let Err(err) = err {
+                    format_to!(message, "- {err}\n");
+                }
+            }
         }
         if !self.config.cargo_autoreload()
             && self.is_quiescent()
diff --git a/editors/code/src/ctx.ts b/editors/code/src/ctx.ts
index ac144cbebb217..72d8109bc80de 100644
--- a/editors/code/src/ctx.ts
+++ b/editors/code/src/ctx.ts
@@ -449,7 +449,7 @@ export class Ctx {
                 return;
         }
         if (statusBar.tooltip.value) {
-            statusBar.tooltip.appendText("\n\n");
+            statusBar.tooltip.appendMarkdown("\n\n---\n\n");
         }
         statusBar.tooltip.appendMarkdown("\n\n[Open logs](command:rust-analyzer.openLogs)");
         statusBar.tooltip.appendMarkdown(

From fd7435d46363d22143d4ced3bb20c27c6b85e901 Mon Sep 17 00:00:00 2001
From: Ryo Yoshida 
Date: Sun, 30 Jul 2023 23:36:42 +0900
Subject: [PATCH 33/59] Fixup path fragments upon MBE transcription

---
 .../hir-def/src/macro_expansion_tests/mbe.rs  | 31 ++++++++++++++
 crates/mbe/src/expander.rs                    | 10 +++++
 crates/mbe/src/expander/matcher.rs            | 10 +++--
 crates/mbe/src/expander/transcriber.rs        | 42 ++++++++++++++++++-
 crates/tt/src/lib.rs                          |  1 -
 5 files changed, 89 insertions(+), 5 deletions(-)

diff --git a/crates/hir-def/src/macro_expansion_tests/mbe.rs b/crates/hir-def/src/macro_expansion_tests/mbe.rs
index b26f9867580b2..a75eaf49467c3 100644
--- a/crates/hir-def/src/macro_expansion_tests/mbe.rs
+++ b/crates/hir-def/src/macro_expansion_tests/mbe.rs
@@ -848,6 +848,37 @@ fn foo() {
     );
 }
 
+#[test]
+fn test_type_path_is_transcribed_as_expr_path() {
+    check(
+        r#"
+macro_rules! m {
+    ($p:path) => { let $p; }
+}
+fn test() {
+    m!(S)
+    m!(S)
+    m!(S>)
+    m!(S<{ module::CONST < 42 }>)
+}
+"#,
+        expect![[r#"
+macro_rules! m {
+    ($p:path) => { let $p; }
+}
+fn test() {
+    let S;
+    let S::  ;
+    let S:: > ;
+    let S:: < {
+        module::CONST<42
+    }
+    > ;
+}
+"#]],
+    );
+}
+
 #[test]
 fn test_expr() {
     check(
diff --git a/crates/mbe/src/expander.rs b/crates/mbe/src/expander.rs
index 8e2181e977e1d..f2d89d3efe5ad 100644
--- a/crates/mbe/src/expander.rs
+++ b/crates/mbe/src/expander.rs
@@ -123,4 +123,14 @@ enum Fragment {
     /// proc-macro delimiter=none. As we later discovered, "none" delimiters are
     /// tricky to handle in the parser, and rustc doesn't handle those either.
     Expr(tt::TokenTree),
+    /// There are roughly two types of paths: paths in expression context, where a
+    /// separator `::` between an identifier and its following generic argument list
+    /// is mandatory, and paths in type context, where `::` can be omitted.
+    ///
+    /// Unlike rustc, we need to transform the parsed fragments back into tokens
+    /// during transcription. When the matched path fragment is a type-context path
+    /// and is trasncribed as an expression-context path, verbatim transcription
+    /// would cause a syntax error. We need to fix it up just before transcribing;
+    /// see `transcriber::fix_up_and_push_path_tt()`.
+    Path(tt::TokenTree),
 }
diff --git a/crates/mbe/src/expander/matcher.rs b/crates/mbe/src/expander/matcher.rs
index 1a7b7eed2953f..1471af98b75b8 100644
--- a/crates/mbe/src/expander/matcher.rs
+++ b/crates/mbe/src/expander/matcher.rs
@@ -742,7 +742,11 @@ fn match_meta_var(
     is_2021: bool,
 ) -> ExpandResult> {
     let fragment = match kind {
-        MetaVarKind::Path => parser::PrefixEntryPoint::Path,
+        MetaVarKind::Path => {
+            return input
+                .expect_fragment(parser::PrefixEntryPoint::Path)
+                .map(|it| it.map(Fragment::Path));
+        }
         MetaVarKind::Ty => parser::PrefixEntryPoint::Ty,
         MetaVarKind::Pat if is_2021 => parser::PrefixEntryPoint::PatTop,
         MetaVarKind::Pat => parser::PrefixEntryPoint::Pat,
@@ -771,7 +775,7 @@ fn match_meta_var(
                 .expect_fragment(parser::PrefixEntryPoint::Expr)
                 .map(|tt| tt.map(Fragment::Expr));
         }
-        _ => {
+        MetaVarKind::Ident | MetaVarKind::Tt | MetaVarKind::Lifetime | MetaVarKind::Literal => {
             let tt_result = match kind {
                 MetaVarKind::Ident => input
                     .expect_ident()
@@ -799,7 +803,7 @@ fn match_meta_var(
                         })
                         .map_err(|()| ExpandError::binding_error("expected literal"))
                 }
-                _ => Err(ExpandError::UnexpectedToken),
+                _ => unreachable!(),
             };
             return tt_result.map(|it| Some(Fragment::Tokens(it))).into();
         }
diff --git a/crates/mbe/src/expander/transcriber.rs b/crates/mbe/src/expander/transcriber.rs
index 6161af185871d..cdac2f1e3bb8c 100644
--- a/crates/mbe/src/expander/transcriber.rs
+++ b/crates/mbe/src/expander/transcriber.rs
@@ -400,7 +400,8 @@ fn push_fragment(buf: &mut Vec, fragment: Fragment) {
             }
             buf.push(tt.into())
         }
-        Fragment::Tokens(tt) | Fragment::Expr(tt) => buf.push(tt),
+        Fragment::Path(tt::TokenTree::Subtree(tt)) => fix_up_and_push_path_tt(buf, tt),
+        Fragment::Tokens(tt) | Fragment::Expr(tt) | Fragment::Path(tt) => buf.push(tt),
     }
 }
 
@@ -411,6 +412,45 @@ fn push_subtree(buf: &mut Vec, tt: tt::Subtree) {
     }
 }
 
+/// Inserts the path separator `::` between an identifier and its following generic
+/// argument list, and then pushes into the buffer. See [`Fragment::Path`] for why
+/// we need this fixup.
+fn fix_up_and_push_path_tt(buf: &mut Vec, subtree: tt::Subtree) {
+    stdx::always!(matches!(subtree.delimiter.kind, tt::DelimiterKind::Invisible));
+    let mut prev_was_ident = false;
+    // Note that we only need to fix up the top-level `TokenTree`s because the
+    // context of the paths in the descendant `Subtree`s won't be changed by the
+    // mbe transcription.
+    for tt in subtree.token_trees {
+        if prev_was_ident {
+            // Pedantically, `(T) -> U` in `FnOnce(T) -> U` is treated as a generic
+            // argument list and thus needs `::` between it and `FnOnce`. However in
+            // today's Rust this type of path *semantically* cannot appear as a
+            // top-level expression-context path, so we can safely ignore it.
+            if let tt::TokenTree::Leaf(tt::Leaf::Punct(tt::Punct { char: '<', .. })) = tt {
+                buf.push(
+                    tt::Leaf::Punct(tt::Punct {
+                        char: ':',
+                        spacing: tt::Spacing::Joint,
+                        span: tt::Span::unspecified(),
+                    })
+                    .into(),
+                );
+                buf.push(
+                    tt::Leaf::Punct(tt::Punct {
+                        char: ':',
+                        spacing: tt::Spacing::Alone,
+                        span: tt::Span::unspecified(),
+                    })
+                    .into(),
+                );
+            }
+        }
+        prev_was_ident = matches!(tt, tt::TokenTree::Leaf(tt::Leaf::Ident(_)));
+        buf.push(tt);
+    }
+}
+
 /// Handles `${count(t, depth)}`. `our_depth` is the recursion depth and `count_depth` is the depth
 /// defined by the metavar expression.
 fn count(
diff --git a/crates/tt/src/lib.rs b/crates/tt/src/lib.rs
index 1b8d4ba42a592..97866439b0024 100644
--- a/crates/tt/src/lib.rs
+++ b/crates/tt/src/lib.rs
@@ -122,7 +122,6 @@ impl_from!(Literal, Punct, Ident for Leaf);
 
 #[derive(Clone, PartialEq, Eq, Hash)]
 pub struct Subtree {
-    // FIXME, this should not be Option
     pub delimiter: Delimiter,
     pub token_trees: Vec>,
 }

From 7c765d9f9ea1a79958ea9f4d29e1d627ee87837a Mon Sep 17 00:00:00 2001
From: Lukas Wirth 
Date: Sun, 30 Jul 2023 17:03:51 +0200
Subject: [PATCH 34/59] fix: Expand eager macros to delimited comma separated
 expression list

---
 .../macro_expansion_tests/builtin_fn_macro.rs |  2 +-
 crates/hir-expand/src/db.rs                   |  5 ++-
 .../src/handlers/macro_error.rs               |  3 ++
 crates/parser/src/grammar.rs                  | 34 +++++++++++++++++++
 crates/parser/src/lib.rs                      |  3 ++
 crates/parser/src/syntax_kind/generated.rs    |  1 +
 crates/syntax/rust.ungram                     |  6 ++++
 crates/syntax/src/ast/generated/nodes.rs      | 30 ++++++++++++++++
 crates/syntax/src/lib.rs                      |  4 +--
 crates/syntax/src/tests/ast_src.rs            |  1 +
 10 files changed, 83 insertions(+), 6 deletions(-)

diff --git a/crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs b/crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs
index b232651db96ef..1250cbb742c5c 100644
--- a/crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs
+++ b/crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs
@@ -238,7 +238,7 @@ fn main() {
     /* error: expected expression */;
     /* error: expected expression, expected COMMA */;
     /* error: expected expression */::core::fmt::Arguments::new_v1(&["", ], &[::core::fmt::ArgumentV1::new(&(), ::core::fmt::Display::fmt), ]);
-    /* error: expected expression, expected R_PAREN */;
+    /* error: expected expression, expected expression */;
     ::core::fmt::Arguments::new_v1(&["", ], &[::core::fmt::ArgumentV1::new(&(5), ::core::fmt::Display::fmt), ]);
 }
 "##]],
diff --git a/crates/hir-expand/src/db.rs b/crates/hir-expand/src/db.rs
index 309c0930d1af9..5292a5fa1b161 100644
--- a/crates/hir-expand/src/db.rs
+++ b/crates/hir-expand/src/db.rs
@@ -430,14 +430,13 @@ fn macro_arg_node(
     let loc = db.lookup_intern_macro_call(id);
     let arg = if let MacroDefKind::BuiltInEager(..) = loc.def.kind {
         let res = if let Some(EagerCallInfo { arg, .. }) = loc.eager.as_deref() {
-            Some(mbe::token_tree_to_syntax_node(&arg.0, mbe::TopEntryPoint::Expr).0)
+            Some(mbe::token_tree_to_syntax_node(&arg.0, mbe::TopEntryPoint::MacroEagerInput).0)
         } else {
             loc.kind
                 .arg(db)
                 .and_then(|arg| ast::TokenTree::cast(arg.value))
-                .map(|tt| tt.reparse_as_expr().to_syntax())
+                .map(|tt| tt.reparse_as_comma_separated_expr().to_syntax())
         };
-
         match res {
             Some(res) if res.errors().is_empty() => res.syntax_node(),
             Some(res) => {
diff --git a/crates/ide-diagnostics/src/handlers/macro_error.rs b/crates/ide-diagnostics/src/handlers/macro_error.rs
index 937e2f96642ed..3af5f94eeb9ff 100644
--- a/crates/ide-diagnostics/src/handlers/macro_error.rs
+++ b/crates/ide-diagnostics/src/handlers/macro_error.rs
@@ -51,6 +51,9 @@ macro_rules! compile_error { () => {} }
 
   compile_error!("compile_error macro works");
 //^^^^^^^^^^^^^ error: compile_error macro works
+
+  compile_error! { "compile_error macro braced works" }
+//^^^^^^^^^^^^^ error: compile_error macro braced works
             "#,
         );
     }
diff --git a/crates/parser/src/grammar.rs b/crates/parser/src/grammar.rs
index a868419821d89..333318f53b7e2 100644
--- a/crates/parser/src/grammar.rs
+++ b/crates/parser/src/grammar.rs
@@ -165,6 +165,40 @@ pub(crate) mod entry {
             }
             m.complete(p, ERROR);
         }
+
+        pub(crate) fn eager_macro_input(p: &mut Parser<'_>) {
+            let m = p.start();
+
+            let closing_paren_kind = match p.current() {
+                T!['{'] => T!['}'],
+                T!['('] => T![')'],
+                T!['['] => T![']'],
+                _ => {
+                    p.error("expected `{`, `[`, `(`");
+                    while !p.at(EOF) {
+                        p.bump_any();
+                    }
+                    m.complete(p, ERROR);
+                    return;
+                }
+            };
+            p.bump_any();
+            while !p.at(EOF) && !p.at(closing_paren_kind) {
+                expressions::expr(p);
+                if !p.at(EOF) && !p.at(closing_paren_kind) {
+                    p.expect(T![,]);
+                }
+            }
+            p.expect(closing_paren_kind);
+            if p.at(EOF) {
+                m.complete(p, MACRO_EAGER_INPUT);
+                return;
+            }
+            while !p.at(EOF) {
+                p.bump_any();
+            }
+            m.complete(p, ERROR);
+        }
     }
 }
 
diff --git a/crates/parser/src/lib.rs b/crates/parser/src/lib.rs
index 1aba1f7674ffd..c155e8aaf67b3 100644
--- a/crates/parser/src/lib.rs
+++ b/crates/parser/src/lib.rs
@@ -75,6 +75,8 @@ pub enum TopEntryPoint {
     /// Edge case -- macros generally don't expand to attributes, with the
     /// exception of `cfg_attr` which does!
     MetaItem,
+    /// Edge case 2 -- eager macros expand their input to a delimited list of comma separated expressions
+    MacroEagerInput,
 }
 
 impl TopEntryPoint {
@@ -87,6 +89,7 @@ impl TopEntryPoint {
             TopEntryPoint::Type => grammar::entry::top::type_,
             TopEntryPoint::Expr => grammar::entry::top::expr,
             TopEntryPoint::MetaItem => grammar::entry::top::meta_item,
+            TopEntryPoint::MacroEagerInput => grammar::entry::top::eager_macro_input,
         };
         let mut p = parser::Parser::new(input);
         entry_point(&mut p);
diff --git a/crates/parser/src/syntax_kind/generated.rs b/crates/parser/src/syntax_kind/generated.rs
index a8fbcfacf7ece..48f407623d834 100644
--- a/crates/parser/src/syntax_kind/generated.rs
+++ b/crates/parser/src/syntax_kind/generated.rs
@@ -262,6 +262,7 @@ pub enum SyntaxKind {
     TYPE_BOUND_LIST,
     MACRO_ITEMS,
     MACRO_STMTS,
+    MACRO_EAGER_INPUT,
     #[doc(hidden)]
     __LAST,
 }
diff --git a/crates/syntax/rust.ungram b/crates/syntax/rust.ungram
index b096c9974489f..138ddd208979d 100644
--- a/crates/syntax/rust.ungram
+++ b/crates/syntax/rust.ungram
@@ -72,6 +72,12 @@ TokenTree =
 MacroItems =
   Item*
 
+MacroEagerInput =
+  '(' (Expr (',' Expr)* ','?)? ')'
+| '{' (Expr (',' Expr)* ','?)? '}'
+| '[' (Expr (',' Expr)* ','?)? ']'
+
+
 MacroStmts =
   statements:Stmt*
   Expr?
diff --git a/crates/syntax/src/ast/generated/nodes.rs b/crates/syntax/src/ast/generated/nodes.rs
index e520801ea2ed6..0b27faa535da1 100644
--- a/crates/syntax/src/ast/generated/nodes.rs
+++ b/crates/syntax/src/ast/generated/nodes.rs
@@ -197,6 +197,20 @@ pub struct MacroItems {
 impl ast::HasModuleItem for MacroItems {}
 impl MacroItems {}
 
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct MacroEagerInput {
+    pub(crate) syntax: SyntaxNode,
+}
+impl MacroEagerInput {
+    pub fn l_paren_token(&self) -> Option { support::token(&self.syntax, T!['(']) }
+    pub fn exprs(&self) -> AstChildren { support::children(&self.syntax) }
+    pub fn r_paren_token(&self) -> Option { support::token(&self.syntax, T![')']) }
+    pub fn l_curly_token(&self) -> Option { support::token(&self.syntax, T!['{']) }
+    pub fn r_curly_token(&self) -> Option { support::token(&self.syntax, T!['}']) }
+    pub fn l_brack_token(&self) -> Option { support::token(&self.syntax, T!['[']) }
+    pub fn r_brack_token(&self) -> Option { support::token(&self.syntax, T![']']) }
+}
+
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct MacroStmts {
     pub(crate) syntax: SyntaxNode,
@@ -1922,6 +1936,17 @@ impl AstNode for MacroItems {
     }
     fn syntax(&self) -> &SyntaxNode { &self.syntax }
 }
+impl AstNode for MacroEagerInput {
+    fn can_cast(kind: SyntaxKind) -> bool { kind == MACRO_EAGER_INPUT }
+    fn cast(syntax: SyntaxNode) -> Option {
+        if Self::can_cast(syntax.kind()) {
+            Some(Self { syntax })
+        } else {
+            None
+        }
+    }
+    fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
 impl AstNode for MacroStmts {
     fn can_cast(kind: SyntaxKind) -> bool { kind == MACRO_STMTS }
     fn cast(syntax: SyntaxNode) -> Option {
@@ -4360,6 +4385,11 @@ impl std::fmt::Display for MacroItems {
         std::fmt::Display::fmt(self.syntax(), f)
     }
 }
+impl std::fmt::Display for MacroEagerInput {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        std::fmt::Display::fmt(self.syntax(), f)
+    }
+}
 impl std::fmt::Display for MacroStmts {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         std::fmt::Display::fmt(self.syntax(), f)
diff --git a/crates/syntax/src/lib.rs b/crates/syntax/src/lib.rs
index bed240a6d7357..4cd668a0cd563 100644
--- a/crates/syntax/src/lib.rs
+++ b/crates/syntax/src/lib.rs
@@ -172,7 +172,7 @@ impl SourceFile {
 }
 
 impl ast::TokenTree {
-    pub fn reparse_as_expr(self) -> Parse {
+    pub fn reparse_as_comma_separated_expr(self) -> Parse {
         let tokens = self.syntax().descendants_with_tokens().filter_map(NodeOrToken::into_token);
 
         let mut parser_input = parser::Input::default();
@@ -203,7 +203,7 @@ impl ast::TokenTree {
             }
         }
 
-        let parser_output = parser::TopEntryPoint::Expr.parse(&parser_input);
+        let parser_output = parser::TopEntryPoint::MacroEagerInput.parse(&parser_input);
 
         let mut tokens =
             self.syntax().descendants_with_tokens().filter_map(NodeOrToken::into_token);
diff --git a/crates/syntax/src/tests/ast_src.rs b/crates/syntax/src/tests/ast_src.rs
index c5783b91a0fdb..e4db33f1c6921 100644
--- a/crates/syntax/src/tests/ast_src.rs
+++ b/crates/syntax/src/tests/ast_src.rs
@@ -216,6 +216,7 @@ pub(crate) const KINDS_SRC: KindsSrc<'_> = KindsSrc {
         // macro related
         "MACRO_ITEMS",
         "MACRO_STMTS",
+        "MACRO_EAGER_INPUT",
     ],
 };
 

From a9d81ae89c69defb20e3e07ce20bea7394eb0c77 Mon Sep 17 00:00:00 2001
From: hkalbasi 
Date: Sun, 30 Jul 2023 23:05:10 +0330
Subject: [PATCH 35/59] Support `Self` in mir lowering

---
 crates/hir-ty/src/mir/eval/tests.rs | 44 +++++++++++++++++++++++++++++
 crates/hir-ty/src/mir/lower.rs      |  5 +---
 2 files changed, 45 insertions(+), 4 deletions(-)

diff --git a/crates/hir-ty/src/mir/eval/tests.rs b/crates/hir-ty/src/mir/eval/tests.rs
index 93f4b699147fc..4e21a9632766c 100644
--- a/crates/hir-ty/src/mir/eval/tests.rs
+++ b/crates/hir-ty/src/mir/eval/tests.rs
@@ -613,6 +613,50 @@ fn main() {
     );
 }
 
+#[test]
+fn self_with_capital_s() {
+    check_pass(
+        r#"
+//- minicore: fn, add, copy
+
+struct S1;
+
+impl S1 {
+    fn f() {
+        Self;
+    }
+}
+
+struct S2 {
+    f1: i32,
+}
+
+impl S2 {
+    fn f() {
+        Self { f1: 5 };
+    }
+}
+
+struct S3(i32);
+
+impl S3 {
+    fn f() {
+        Self(2);
+        Self;
+        let this = Self;
+        this(2);
+    }
+}
+
+fn main() {
+    S1::f();
+    S2::f();
+    S3::f();
+}
+        "#,
+    );
+}
+
 #[test]
 fn syscalls() {
     check_pass(
diff --git a/crates/hir-ty/src/mir/lower.rs b/crates/hir-ty/src/mir/lower.rs
index 305c34ec70039..566204bb70d4e 100644
--- a/crates/hir-ty/src/mir/lower.rs
+++ b/crates/hir-ty/src/mir/lower.rs
@@ -486,13 +486,10 @@ impl<'ctx> MirLowerCtx<'ctx> {
                         );
                         Ok(Some(current))
                     }
-                    ValueNs::FunctionId(_) | ValueNs::StructId(_) => {
+                    ValueNs::FunctionId(_) | ValueNs::StructId(_) | ValueNs::ImplSelf(_) => {
                         // It's probably a unit struct or a zero sized function, so no action is needed.
                         Ok(Some(current))
                     }
-                    it => {
-                        not_supported!("unknown name {it:?} in value name space");
-                    }
                 }
             }
             Expr::If { condition, then_branch, else_branch } => {

From 91581becac9ec00e25257f469e6905c3fa169cb3 Mon Sep 17 00:00:00 2001
From: Max Heller 
Date: Sun, 30 Jul 2023 15:58:20 -0400
Subject: [PATCH 36/59] update tests

---
 crates/ide-completion/src/item.rs          | 25 ++++---------------
 crates/ide-completion/src/tests.rs         | 28 ++++------------------
 crates/ide-completion/src/tests/special.rs | 22 ++++++++---------
 3 files changed, 19 insertions(+), 56 deletions(-)

diff --git a/crates/ide-completion/src/item.rs b/crates/ide-completion/src/item.rs
index c7879d7bf3ad5..92782d1e807d2 100644
--- a/crates/ide-completion/src/item.rs
+++ b/crates/ide-completion/src/item.rs
@@ -440,9 +440,12 @@ impl Builder {
                     chars.next().is_some_and(unicode_ident::is_xid_start)
                         && chars.all(unicode_ident::is_xid_continue)
                 })
-                .join(", ");
+                // Deliberately concatenated without separators as adding separators e.g.
+                // `alias1, alias2` results in LSP clients continuing to display the completion even
+                // after typing a comma or space.
+                .join("");
             if !lookup_doc_aliases.is_empty() {
-                lookup = SmolStr::from(format!("{lookup} {lookup_doc_aliases}"));
+                lookup = SmolStr::from(format!("{lookup}{lookup_doc_aliases}"));
             }
         }
         if let [import_edit] = &*self.imports_to_add {
@@ -567,12 +570,9 @@ impl Builder {
 
 #[cfg(test)]
 mod tests {
-    use ide_db::SymbolKind;
     use itertools::Itertools;
     use test_utils::assert_eq_text;
 
-    use crate::{CompletionItem, CompletionItemKind};
-
     use super::{
         CompletionRelevance, CompletionRelevancePostfixMatch, CompletionRelevanceTypeMatch,
     };
@@ -647,19 +647,4 @@ mod tests {
 
         check_relevance_score_ordered(expected_relevance_order);
     }
-
-    #[test]
-    fn exclude_non_identifier_aliases_from_lookup() {
-        let mut item = CompletionItem::new(
-            CompletionItemKind::SymbolKind(SymbolKind::Trait),
-            Default::default(),
-            "PartialOrd",
-        );
-        let aliases = [">", "<", "<=", ">="];
-        item.doc_aliases(aliases.iter().map(|&alias| alias.into()).collect());
-        let item = item.build(&Default::default());
-        for alias in aliases {
-            assert!(!item.lookup().contains(alias));
-        }
-    }
 }
diff --git a/crates/ide-completion/src/tests.rs b/crates/ide-completion/src/tests.rs
index 63c7f789d3b6d..2464e8d5f8175 100644
--- a/crates/ide-completion/src/tests.rs
+++ b/crates/ide-completion/src/tests.rs
@@ -23,8 +23,6 @@ mod type_pos;
 mod use_tree;
 mod visibility;
 
-use std::ops::ControlFlow;
-
 use expect_test::Expect;
 use hir::PrefixKind;
 use ide_db::{
@@ -187,29 +185,11 @@ pub(crate) fn check_edit_with_config(
     let (db, position) = position(ra_fixture_before);
     let completions: Vec =
         crate::completions(&db, &config, position, None).unwrap();
-    let matching = completions
+    let (completion,) = completions
         .iter()
-        // Match IDE behavior by considering completions as matching if `what` is a subsequence
-        // of the completion's lookup text.
-        .filter(|it| {
-            let mut lookup = it.lookup().chars();
-            what.chars().all(|c| lookup.contains(&c))
-        })
-        // Select the first exact match if one exists, or the first subsequence match if not
-        .try_fold(None, |first_match, completion| {
-            let exact_match = completion.lookup() == what;
-            if exact_match {
-                ControlFlow::Break(completion)
-            } else {
-                ControlFlow::Continue(first_match.or(Some(completion)))
-            }
-        });
-    let completion = match matching {
-        ControlFlow::Continue(first_match) => first_match
-            .unwrap_or_else(|| panic!("can't find {what:?} completion in {completions:#?}")),
-        ControlFlow::Break(exact_match) => exact_match,
-    };
-
+        .filter(|it| it.lookup() == what)
+        .collect_tuple()
+        .unwrap_or_else(|| panic!("can't find {what:?} completion in {completions:#?}"));
     let mut actual = db.file_text(position.file_id).to_string();
 
     let mut combined_edit = completion.text_edit.clone();
diff --git a/crates/ide-completion/src/tests/special.rs b/crates/ide-completion/src/tests/special.rs
index a50184de533cf..e80a289049f1d 100644
--- a/crates/ide-completion/src/tests/special.rs
+++ b/crates/ide-completion/src/tests/special.rs
@@ -1282,26 +1282,24 @@ fn here_we_go() {
 }
 
 #[test]
-fn completion_filtering_excludes_non_identifier_aliases() {
-    // Catch panic instead of using `#[should_panic]` as style check bans
-    // `#[should_panic]`. Making `check_edit` return a result would require
-    // a lot of test changes.
-    std::panic::catch_unwind(|| {
-        check_edit(
-            "Partial>",
-            r#"
+fn completion_filtering_excludes_non_identifier_doc_aliases() {
+    check_edit(
+        "PartialOrdcmporder",
+        r#"
 #[doc(alias = ">")]
+#[doc(alias = "cmp")]
+#[doc(alias = "order")]
 trait PartialOrd {}
 
 struct Foo
Date: Mon, 31 Jul 2023 16:41:43 +0200
Subject: [PATCH 37/59] fix: Remove another faulty unwrap (expect)

---
 crates/hir-expand/src/eager.rs | 42 ++++++++++++++++++----------------
 1 file changed, 22 insertions(+), 20 deletions(-)

diff --git a/crates/hir-expand/src/eager.rs b/crates/hir-expand/src/eager.rs
index 1d8e156241559..91aa49d1e21ad 100644
--- a/crates/hir-expand/src/eager.rs
+++ b/crates/hir-expand/src/eager.rs
@@ -79,33 +79,35 @@ pub fn expand_eager_macro_input(
         return Ok(ExpandResult { value: None, err });
     };
 
-    let og_tmap = mbe::syntax_node_to_token_map(
-        macro_call.value.token_tree().expect("macro_arg_text succeeded").syntax(),
-    );
-
     let (mut subtree, expanded_eager_input_token_map) =
         mbe::syntax_node_to_token_tree(&expanded_eager_input);
 
-    // The tokenmap and ids of subtree point into the expanded syntax node, but that is inaccessible from the outside
-    // so we need to remap them to the original input of the eager macro.
-    subtree.visit_ids(&|id| {
-        // Note: we discard all token ids of braces and the like here, but that's not too bad and only a temporary fix
+    let og_tmap = if let Some(tt) = macro_call.value.token_tree() {
+        let og_tmap = mbe::syntax_node_to_token_map(tt.syntax());
+        // The tokenmap and ids of subtree point into the expanded syntax node, but that is inaccessible from the outside
+        // so we need to remap them to the original input of the eager macro.
+        subtree.visit_ids(&|id| {
+            // Note: we discard all token ids of braces and the like here, but that's not too bad and only a temporary fix
 
-        if let Some(range) =
-            expanded_eager_input_token_map.first_range_by_token(id, syntax::SyntaxKind::TOMBSTONE)
-        {
-            // remap from expanded eager input to eager input expansion
-            if let Some(og_range) = mapping.get(&range) {
-                // remap from eager input expansion to original eager input
-                if let Some(&og_range) = ws_mapping.get(og_range) {
-                    if let Some(og_token) = og_tmap.token_by_range(og_range) {
-                        return og_token;
+            if let Some(range) = expanded_eager_input_token_map
+                .first_range_by_token(id, syntax::SyntaxKind::TOMBSTONE)
+            {
+                // remap from expanded eager input to eager input expansion
+                if let Some(og_range) = mapping.get(&range) {
+                    // remap from eager input expansion to original eager input
+                    if let Some(&og_range) = ws_mapping.get(og_range) {
+                        if let Some(og_token) = og_tmap.token_by_range(og_range) {
+                            return og_token;
+                        }
                     }
                 }
             }
-        }
-        tt::TokenId::UNSPECIFIED
-    });
+            tt::TokenId::UNSPECIFIED
+        });
+        og_tmap
+    } else {
+        Default::default()
+    };
     subtree.delimiter = crate::tt::Delimiter::unspecified();
 
     let loc = MacroCallLoc {

From c7b34e4873183d052f277ee7dda1f9927b66e154 Mon Sep 17 00:00:00 2001
From: Lukas Wirth 
Date: Mon, 31 Jul 2023 17:12:17 +0200
Subject: [PATCH 38/59] fix: Strip unused token ids from eager macro input
 token maps

---
 crates/hir-expand/src/eager.rs                    |  9 ++++++---
 .../test_data/highlight_macros.html               | 14 ++++++++++++--
 crates/ide/src/syntax_highlighting/tests.rs       | 15 ++++++++++++++-
 crates/mbe/src/token_map.rs                       |  4 ++++
 crates/tt/src/lib.rs                              |  2 +-
 5 files changed, 37 insertions(+), 7 deletions(-)

diff --git a/crates/hir-expand/src/eager.rs b/crates/hir-expand/src/eager.rs
index 91aa49d1e21ad..3ca2e295d7f82 100644
--- a/crates/hir-expand/src/eager.rs
+++ b/crates/hir-expand/src/eager.rs
@@ -19,7 +19,7 @@
 //!
 //! See the full discussion : 
 use base_db::CrateId;
-use rustc_hash::FxHashMap;
+use rustc_hash::{FxHashMap, FxHashSet};
 use syntax::{ted, Parse, SyntaxNode, TextRange, TextSize, WalkEvent};
 use triomphe::Arc;
 
@@ -83,10 +83,11 @@ pub fn expand_eager_macro_input(
         mbe::syntax_node_to_token_tree(&expanded_eager_input);
 
     let og_tmap = if let Some(tt) = macro_call.value.token_tree() {
-        let og_tmap = mbe::syntax_node_to_token_map(tt.syntax());
+        let mut ids_used = FxHashSet::default();
+        let mut og_tmap = mbe::syntax_node_to_token_map(tt.syntax());
         // The tokenmap and ids of subtree point into the expanded syntax node, but that is inaccessible from the outside
         // so we need to remap them to the original input of the eager macro.
-        subtree.visit_ids(&|id| {
+        subtree.visit_ids(&mut |id| {
             // Note: we discard all token ids of braces and the like here, but that's not too bad and only a temporary fix
 
             if let Some(range) = expanded_eager_input_token_map
@@ -97,6 +98,7 @@ pub fn expand_eager_macro_input(
                     // remap from eager input expansion to original eager input
                     if let Some(&og_range) = ws_mapping.get(og_range) {
                         if let Some(og_token) = og_tmap.token_by_range(og_range) {
+                            ids_used.insert(id);
                             return og_token;
                         }
                     }
@@ -104,6 +106,7 @@ pub fn expand_eager_macro_input(
             }
             tt::TokenId::UNSPECIFIED
         });
+        og_tmap.filter(|id| ids_used.contains(&id));
         og_tmap
     } else {
         Default::default()
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_macros.html b/crates/ide/src/syntax_highlighting/test_data/highlight_macros.html
index c5fcec7568080..06b66b302ae02 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_macros.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_macros.html
@@ -90,8 +90,18 @@
     }
 }
 
+#[rustc_builtin_macro]
+macro_rules! concat {}
+#[rustc_builtin_macro]
+macro_rules! include {}
+#[rustc_builtin_macro]
+macro_rules! format_args {}
+
+include!(concat!("foo/", "foo.rs"));
+
 fn main() {
-    println!("Hello, {}!", 92);
+    format_args!("Hello, {}!", 92);
     dont_color_me_braces!();
     noop!(noop!(1));
-}
\ No newline at end of file +} +
\ No newline at end of file diff --git a/crates/ide/src/syntax_highlighting/tests.rs b/crates/ide/src/syntax_highlighting/tests.rs index 696aa59002527..8747071807067 100644 --- a/crates/ide/src/syntax_highlighting/tests.rs +++ b/crates/ide/src/syntax_highlighting/tests.rs @@ -48,6 +48,7 @@ fn macros() { check_highlighting( r#" //- proc_macros: mirror +//- /lib.rs crate:lib proc_macros::mirror! { { ,i32 :x pub @@ -95,11 +96,23 @@ macro without_args { } } +#[rustc_builtin_macro] +macro_rules! concat {} +#[rustc_builtin_macro] +macro_rules! include {} +#[rustc_builtin_macro] +macro_rules! format_args {} + +include!(concat!("foo/", "foo.rs")); + fn main() { - println!("Hello, {}!", 92); + format_args!("Hello, {}!", 92); dont_color_me_braces!(); noop!(noop!(1)); } +//- /foo/foo.rs crate:foo +mod foo {} +use self::foo as bar; "#, expect_file!["./test_data/highlight_macros.html"], false, diff --git a/crates/mbe/src/token_map.rs b/crates/mbe/src/token_map.rs index 9b2df89f9c71e..73a27df5dbca6 100644 --- a/crates/mbe/src/token_map.rs +++ b/crates/mbe/src/token_map.rs @@ -117,4 +117,8 @@ impl TokenMap { TokenTextRange::Delimiter(_) => None, }) } + + pub fn filter(&mut self, id: impl Fn(tt::TokenId) -> bool) { + self.entries.retain(|&(tid, _)| id(tid)); + } } diff --git a/crates/tt/src/lib.rs b/crates/tt/src/lib.rs index 1b8d4ba42a592..ab398726b870a 100644 --- a/crates/tt/src/lib.rs +++ b/crates/tt/src/lib.rs @@ -70,7 +70,7 @@ pub mod token_id { } impl Subtree { - pub fn visit_ids(&mut self, f: &impl Fn(TokenId) -> TokenId) { + pub fn visit_ids(&mut self, f: &mut impl FnMut(TokenId) -> TokenId) { self.delimiter.open = f(self.delimiter.open); self.delimiter.close = f(self.delimiter.close); self.token_trees.iter_mut().for_each(|tt| match tt { From d999d34e39ca9787f376e22d5f079a9d89267692 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Tue, 1 Aug 2023 11:20:15 +0200 Subject: [PATCH 39/59] Don't bail eager expansion when inner macros fail to resolve --- crates/hir-def/src/lib.rs | 2 +- .../hir-def/src/macro_expansion_tests/mbe.rs | 24 ++++++++++++ crates/hir-expand/src/eager.rs | 38 ++++++++++--------- 3 files changed, 46 insertions(+), 18 deletions(-) diff --git a/crates/hir-def/src/lib.rs b/crates/hir-def/src/lib.rs index 9270cbfe89093..fdab7382d3753 100644 --- a/crates/hir-def/src/lib.rs +++ b/crates/hir-def/src/lib.rs @@ -1160,7 +1160,7 @@ fn macro_call_as_call_id_( let macro_call = InFile::new(call.ast_id.file_id, call.ast_id.to_node(db)); expand_eager_macro_input(db, krate, macro_call, def, &|path| { resolver(path).filter(MacroDefId::is_fn_like) - })? + }) } _ if def.is_fn_like() => ExpandResult { value: Some(def.as_lazy_macro( diff --git a/crates/hir-def/src/macro_expansion_tests/mbe.rs b/crates/hir-def/src/macro_expansion_tests/mbe.rs index b26f9867580b2..0364904fcd8e9 100644 --- a/crates/hir-def/src/macro_expansion_tests/mbe.rs +++ b/crates/hir-def/src/macro_expansion_tests/mbe.rs @@ -99,6 +99,30 @@ fn#19 main#20(#21)#21 {#22 ); } +#[test] +fn eager_expands_with_unresolved_within() { + check( + r#" +#[rustc_builtin_macro] +#[macro_export] +macro_rules! format_args {} + +fn main(foo: ()) { + format_args!("{} {} {}", format_args!("{}", 0), foo, identity!(10), "bar") +} +"#, + expect![[r##" +#[rustc_builtin_macro] +#[macro_export] +macro_rules! format_args {} + +fn main(foo: ()) { + /* error: unresolved macro identity */::core::fmt::Arguments::new_v1(&["", " ", " ", ], &[::core::fmt::ArgumentV1::new(&(::core::fmt::Arguments::new_v1(&["", ], &[::core::fmt::ArgumentV1::new(&(0), ::core::fmt::Display::fmt), ])), ::core::fmt::Display::fmt), ::core::fmt::ArgumentV1::new(&(foo), ::core::fmt::Display::fmt), ::core::fmt::ArgumentV1::new(&(identity!(10)), ::core::fmt::Display::fmt), ]) +} +"##]], + ); +} + #[test] fn token_mapping_eager() { check( diff --git a/crates/hir-expand/src/eager.rs b/crates/hir-expand/src/eager.rs index 3ca2e295d7f82..4110f2847592d 100644 --- a/crates/hir-expand/src/eager.rs +++ b/crates/hir-expand/src/eager.rs @@ -29,7 +29,7 @@ use crate::{ hygiene::Hygiene, mod_path::ModPath, EagerCallInfo, ExpandError, ExpandResult, ExpandTo, InFile, MacroCallId, MacroCallKind, - MacroCallLoc, MacroDefId, MacroDefKind, UnresolvedMacro, + MacroCallLoc, MacroDefId, MacroDefKind, }; pub fn expand_eager_macro_input( @@ -38,7 +38,7 @@ pub fn expand_eager_macro_input( macro_call: InFile, def: MacroDefId, resolver: &dyn Fn(ModPath) -> Option, -) -> Result>, UnresolvedMacro> { +) -> ExpandResult> { let ast_map = db.ast_id_map(macro_call.file_id); // the expansion which the ast id map is built upon has no whitespace, so the offsets are wrong as macro_call is from the token tree that has whitespace! let call_id = InFile::new(macro_call.file_id, ast_map.ast_id(¯o_call.value)); @@ -71,12 +71,12 @@ pub fn expand_eager_macro_input( InFile::new(arg_id.as_file(), arg_exp.syntax_node()), krate, resolver, - )? + ) }; let err = parse_err.or(err); let Some((expanded_eager_input, mapping)) = expanded_eager_input else { - return Ok(ExpandResult { value: None, err }); + return ExpandResult { value: None, err }; }; let (mut subtree, expanded_eager_input_token_map) = @@ -98,7 +98,7 @@ pub fn expand_eager_macro_input( // remap from eager input expansion to original eager input if let Some(&og_range) = ws_mapping.get(og_range) { if let Some(og_token) = og_tmap.token_by_range(og_range) { - ids_used.insert(id); + ids_used.insert(og_token); return og_token; } } @@ -124,7 +124,7 @@ pub fn expand_eager_macro_input( kind: MacroCallKind::FnLike { ast_id: call_id, expand_to }, }; - Ok(ExpandResult { value: Some(db.intern_macro_call(loc)), err }) + ExpandResult { value: Some(db.intern_macro_call(loc)), err } } fn lazy_expand( @@ -150,13 +150,13 @@ fn eager_macro_recur( curr: InFile, krate: CrateId, macro_resolver: &dyn Fn(ModPath) -> Option, -) -> Result)>>, UnresolvedMacro> { +) -> ExpandResult)>> { let original = curr.value.clone_for_update(); let mut mapping = FxHashMap::default(); let mut replacements = Vec::new(); - // Note: We only report a single error inside of eager expansions + // FIXME: We only report a single error inside of eager expansions let mut error = None; let mut offset = 0i32; let apply_offset = |it: TextSize, offset: i32| { @@ -187,7 +187,14 @@ fn eager_macro_recur( } }; let def = match call.path().and_then(|path| ModPath::from_src(db, path, hygiene)) { - Some(path) => macro_resolver(path.clone()).ok_or(UnresolvedMacro { path })?, + Some(path) => match macro_resolver(path.clone()) { + Some(def) => def, + None => { + error = + Some(ExpandError::other(format!("unresolved macro {}", path.display(db)))); + continue; + } + }, None => { error = Some(ExpandError::other("malformed macro invocation")); continue; @@ -195,16 +202,13 @@ fn eager_macro_recur( }; let ExpandResult { value, err } = match def.kind { MacroDefKind::BuiltInEager(..) => { - let ExpandResult { value, err } = match expand_eager_macro_input( + let ExpandResult { value, err } = expand_eager_macro_input( db, krate, curr.with_value(call.clone()), def, macro_resolver, - ) { - Ok(it) => it, - Err(err) => return Err(err), - }; + ); match value { Some(call_id) => { let ExpandResult { value, err: err2 } = @@ -254,7 +258,7 @@ fn eager_macro_recur( parse.as_ref().map(|it| it.syntax_node()), krate, macro_resolver, - )?; + ); let err = err.or(error); if let Some(tt) = call.token_tree() { @@ -284,7 +288,7 @@ fn eager_macro_recur( } // check if the whole original syntax is replaced if call.syntax() == &original { - return Ok(ExpandResult { value: value.zip(Some(mapping)), err: error }); + return ExpandResult { value: value.zip(Some(mapping)), err: error }; } if let Some(insert) = value { @@ -295,5 +299,5 @@ fn eager_macro_recur( } replacements.into_iter().rev().for_each(|(old, new)| ted::replace(old.syntax(), new)); - Ok(ExpandResult { value: Some((original, mapping)), err: error }) + ExpandResult { value: Some((original, mapping)), err: error } } From 3c1c319c88fd688059eda8c3b96d37cf775dfba6 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Tue, 1 Aug 2023 12:10:38 +0200 Subject: [PATCH 40/59] Allow match to matches assist to trigger on non-literal bool arms --- ...ert_two_arm_bool_match_to_matches_macro.rs | 125 ++++++++++++------ 1 file changed, 88 insertions(+), 37 deletions(-) diff --git a/crates/ide-assists/src/handlers/convert_two_arm_bool_match_to_matches_macro.rs b/crates/ide-assists/src/handlers/convert_two_arm_bool_match_to_matches_macro.rs index b1b0f587cd33d..6a5b11f542560 100644 --- a/crates/ide-assists/src/handlers/convert_two_arm_bool_match_to_matches_macro.rs +++ b/crates/ide-assists/src/handlers/convert_two_arm_bool_match_to_matches_macro.rs @@ -1,3 +1,6 @@ +use hir::Semantics; +use ide_db::RootDatabase; +use stdx::format_to; use syntax::ast::{self, AstNode}; use crate::{AssistContext, AssistId, AssistKind, Assists}; @@ -24,6 +27,7 @@ pub(crate) fn convert_two_arm_bool_match_to_matches_macro( acc: &mut Assists, ctx: &AssistContext<'_>, ) -> Option<()> { + use ArmBodyExpression::*; let match_expr = ctx.find_node_at_offset::()?; let match_arm_list = match_expr.match_arm_list()?; let mut arms = match_arm_list.arms(); @@ -33,21 +37,20 @@ pub(crate) fn convert_two_arm_bool_match_to_matches_macro( cov_mark::hit!(non_two_arm_match); return None; } - let first_arm_expr = first_arm.expr(); - let second_arm_expr = second_arm.expr(); + let first_arm_expr = first_arm.expr()?; + let second_arm_expr = second_arm.expr()?; + let first_arm_body = is_bool_literal_expr(&ctx.sema, &first_arm_expr)?; + let second_arm_body = is_bool_literal_expr(&ctx.sema, &second_arm_expr)?; - let invert_matches = if is_bool_literal_expr(&first_arm_expr, true) - && is_bool_literal_expr(&second_arm_expr, false) - { - false - } else if is_bool_literal_expr(&first_arm_expr, false) - && is_bool_literal_expr(&second_arm_expr, true) - { - true - } else { + if !matches!( + (&first_arm_body, &second_arm_body), + (Literal(true), Literal(false)) + | (Literal(false), Literal(true)) + | (Expression(_), Literal(false)) + ) { cov_mark::hit!(non_invert_bool_literal_arms); return None; - }; + } let target_range = ctx.sema.original_range(match_expr.syntax()).range; let expr = match_expr.expr()?; @@ -59,28 +62,55 @@ pub(crate) fn convert_two_arm_bool_match_to_matches_macro( |builder| { let mut arm_str = String::new(); if let Some(pat) = &first_arm.pat() { - arm_str += &pat.to_string(); + format_to!(arm_str, "{pat}"); } if let Some(guard) = &first_arm.guard() { arm_str += &format!(" {guard}"); } - if invert_matches { - builder.replace(target_range, format!("!matches!({expr}, {arm_str})")); - } else { - builder.replace(target_range, format!("matches!({expr}, {arm_str})")); - } + + let replace_with = match (first_arm_body, second_arm_body) { + (Literal(true), Literal(false)) => { + format!("matches!({expr}, {arm_str})") + } + (Literal(false), Literal(true)) => { + format!("!matches!({expr}, {arm_str})") + } + (Expression(body_expr), Literal(false)) => { + arm_str.push_str(match &first_arm.guard() { + Some(_) => " && ", + _ => " if ", + }); + format!("matches!({expr}, {arm_str}{body_expr})") + } + _ => { + unreachable!() + } + }; + builder.replace(target_range, replace_with); }, ) } -fn is_bool_literal_expr(expr: &Option, expect_bool: bool) -> bool { - if let Some(ast::Expr::Literal(lit)) = expr { +enum ArmBodyExpression { + Literal(bool), + Expression(ast::Expr), +} + +fn is_bool_literal_expr( + sema: &Semantics<'_, RootDatabase>, + expr: &ast::Expr, +) -> Option { + if let ast::Expr::Literal(lit) = expr { if let ast::LiteralKind::Bool(b) = lit.kind() { - return b == expect_bool; + return Some(ArmBodyExpression::Literal(b)); } } - return false; + if !sema.type_of_expr(expr)?.original.is_bool() { + return None; + } + + Some(ArmBodyExpression::Expression(expr.clone())) } #[cfg(test)] @@ -121,21 +151,6 @@ fn foo(a: Option) -> bool { ); } - #[test] - fn not_applicable_non_bool_literal_arms() { - cov_mark::check!(non_invert_bool_literal_arms); - check_assist_not_applicable( - convert_two_arm_bool_match_to_matches_macro, - r#" -fn foo(a: Option) -> bool { - match a$0 { - Some(val) => val == 3, - _ => false - } -} - "#, - ); - } #[test] fn not_applicable_both_false_arms() { cov_mark::check!(non_invert_bool_literal_arms); @@ -291,4 +306,40 @@ fn main() { }", ); } + + #[test] + fn convert_non_literal_bool() { + check_assist( + convert_two_arm_bool_match_to_matches_macro, + r#" +fn main() { + match 0$0 { + a @ 0..15 => a == 0, + _ => false, + } +} +"#, + r#" +fn main() { + matches!(0, a @ 0..15 if a == 0) +} +"#, + ); + check_assist( + convert_two_arm_bool_match_to_matches_macro, + r#" +fn main() { + match 0$0 { + a @ 0..15 if thing() => a == 0, + _ => false, + } +} +"#, + r#" +fn main() { + matches!(0, a @ 0..15 if thing() && a == 0) +} +"#, + ); + } } From e14d84d0a6b532c683ca3e3e1fedbd94796c9604 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Tue, 1 Aug 2023 12:38:53 +0200 Subject: [PATCH 41/59] Skip out on single-segment immediate macro resolution when there are errors --- crates/hir-def/src/lib.rs | 21 +++++++------ crates/hir-def/src/nameres/collector.rs | 39 +++++++++++++++++-------- 2 files changed, 39 insertions(+), 21 deletions(-) diff --git a/crates/hir-def/src/lib.rs b/crates/hir-def/src/lib.rs index fdab7382d3753..e8187e05adada 100644 --- a/crates/hir-def/src/lib.rs +++ b/crates/hir-def/src/lib.rs @@ -1083,7 +1083,7 @@ pub trait AsMacroCall { &self, db: &dyn ExpandDatabase, krate: CrateId, - resolver: impl Fn(path::ModPath) -> Option, + resolver: impl Fn(path::ModPath) -> Option + Copy, ) -> Option { self.as_call_id_with_errors(db, krate, resolver).ok()?.value } @@ -1092,7 +1092,7 @@ pub trait AsMacroCall { &self, db: &dyn ExpandDatabase, krate: CrateId, - resolver: impl Fn(path::ModPath) -> Option, + resolver: impl Fn(path::ModPath) -> Option + Copy, ) -> Result>, UnresolvedMacro>; } @@ -1101,7 +1101,7 @@ impl AsMacroCall for InFile<&ast::MacroCall> { &self, db: &dyn ExpandDatabase, krate: CrateId, - resolver: impl Fn(path::ModPath) -> Option, + resolver: impl Fn(path::ModPath) -> Option + Copy, ) -> Result>, UnresolvedMacro> { let expands_to = hir_expand::ExpandTo::from_call_site(self.value); let ast_id = AstId::new(self.file_id, db.ast_id_map(self.file_id).ast_id(self.value)); @@ -1112,12 +1112,13 @@ impl AsMacroCall for InFile<&ast::MacroCall> { return Ok(ExpandResult::only_err(ExpandError::other("malformed macro invocation"))); }; - macro_call_as_call_id_( + macro_call_as_call_id_with_eager( db, &AstIdWithPath::new(ast_id.file_id, ast_id.value, path), expands_to, krate, resolver, + resolver, ) } } @@ -1140,17 +1141,19 @@ fn macro_call_as_call_id( call: &AstIdWithPath, expand_to: ExpandTo, krate: CrateId, - resolver: impl Fn(path::ModPath) -> Option, + resolver: impl Fn(path::ModPath) -> Option + Copy, ) -> Result, UnresolvedMacro> { - macro_call_as_call_id_(db, call, expand_to, krate, resolver).map(|res| res.value) + macro_call_as_call_id_with_eager(db, call, expand_to, krate, resolver, resolver) + .map(|res| res.value) } -fn macro_call_as_call_id_( +fn macro_call_as_call_id_with_eager( db: &dyn ExpandDatabase, call: &AstIdWithPath, expand_to: ExpandTo, krate: CrateId, - resolver: impl Fn(path::ModPath) -> Option, + resolver: impl FnOnce(path::ModPath) -> Option, + eager_resolver: impl Fn(path::ModPath) -> Option, ) -> Result>, UnresolvedMacro> { let def = resolver(call.path.clone()).ok_or_else(|| UnresolvedMacro { path: call.path.clone() })?; @@ -1159,7 +1162,7 @@ fn macro_call_as_call_id_( MacroDefKind::BuiltInEager(..) => { let macro_call = InFile::new(call.ast_id.file_id, call.ast_id.to_node(db)); expand_eager_macro_input(db, krate, macro_call, def, &|path| { - resolver(path).filter(MacroDefId::is_fn_like) + eager_resolver(path).filter(MacroDefId::is_fn_like) }) } _ if def.is_fn_like() => ExpandResult { diff --git a/crates/hir-def/src/nameres/collector.rs b/crates/hir-def/src/nameres/collector.rs index c048716d7403b..61915504b6c52 100644 --- a/crates/hir-def/src/nameres/collector.rs +++ b/crates/hir-def/src/nameres/collector.rs @@ -38,7 +38,7 @@ use crate::{ self, ExternCrate, Fields, FileItemTreeId, ImportKind, ItemTree, ItemTreeId, ItemTreeNode, MacroCall, MacroDef, MacroRules, Mod, ModItem, ModKind, TreeId, }, - macro_call_as_call_id, macro_id_to_def_id, + macro_call_as_call_id, macro_call_as_call_id_with_eager, macro_id_to_def_id, nameres::{ diagnostics::DefDiagnostic, mod_resolution::ModDir, @@ -2187,7 +2187,7 @@ impl ModCollector<'_, '_> { // scopes without eager expansion. // Case 1: try to resolve macro calls with single-segment name and expand macro_rules - if let Ok(res) = macro_call_as_call_id( + if let Ok(res) = macro_call_as_call_id_with_eager( db.upcast(), &ast_id, mac.expand_to, @@ -2210,19 +2210,34 @@ impl ModCollector<'_, '_> { .map(|it| macro_id_to_def_id(self.def_collector.db, it)) }) }, - ) { - // Legacy macros need to be expanded immediately, so that any macros they produce - // are in scope. - if let Some(val) = res { - self.def_collector.collect_macro_expansion( + |path| { + let resolved_res = self.def_collector.def_map.resolve_path_fp_with_macro( + db, + ResolveMode::Other, self.module_id, - val, - self.macro_depth + 1, - container, + &path, + BuiltinShadowMode::Module, + Some(MacroSubNs::Bang), ); - } + resolved_res.resolved_def.take_macros().map(|it| macro_id_to_def_id(db, it)) + }, + ) { + // FIXME: if there were errors, this mightve been in the eager expansion from an + // unresolved macro, so we need to push this into late macro resolution. see fixme above + if res.err.is_none() { + // Legacy macros need to be expanded immediately, so that any macros they produce + // are in scope. + if let Some(val) = res.value { + self.def_collector.collect_macro_expansion( + self.module_id, + val, + self.macro_depth + 1, + container, + ); + } - return; + return; + } } // Case 2: resolve in module scope, expand during name resolution. From a5059da57afd32a545e346394b1f058a6a40f244 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Tue, 1 Aug 2023 12:53:45 +0200 Subject: [PATCH 42/59] Update test fixture --- crates/ide-diagnostics/src/handlers/macro_error.rs | 2 +- .../src/syntax_highlighting/test_data/highlight_strings.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/ide-diagnostics/src/handlers/macro_error.rs b/crates/ide-diagnostics/src/handlers/macro_error.rs index 3af5f94eeb9ff..f54cdd63bbb60 100644 --- a/crates/ide-diagnostics/src/handlers/macro_error.rs +++ b/crates/ide-diagnostics/src/handlers/macro_error.rs @@ -80,7 +80,7 @@ macro_rules! m { fn f() { m!(); - //^^^^ error: unresolved macro `$crate::private::concat!` + //^^^^ error: unresolved macro $crate::private::concat } //- /core.rs crate:core diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html b/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html index dcac8eb736893..3ac8aa9cc9da2 100644 --- a/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html +++ b/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html @@ -178,5 +178,5 @@ toho!("{}fmt", 0); asm!("mov eax, {0}"); format_args!(concat!("{}"), "{}"); - format_args!("{} {} {} {} {} {}", backslash, format_args!("{}", 0), foo, "bar", toho!(), backslash); + format_args!("{} {} {} {} {} {}", backslash, format_args!("{}", 0), foo, "bar", toho!(), backslash); }
\ No newline at end of file From 75607fc34cd6da4e7081dbdb6b8cfaa50a08633e Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Tue, 1 Aug 2023 17:47:52 +0200 Subject: [PATCH 43/59] Simplify --- crates/hir-expand/src/hygiene.rs | 26 +++++++++++--------------- crates/mbe/src/lib.rs | 1 - 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/crates/hir-expand/src/hygiene.rs b/crates/hir-expand/src/hygiene.rs index 54e74d50c87e4..ade4a5928933d 100644 --- a/crates/hir-expand/src/hygiene.rs +++ b/crates/hir-expand/src/hygiene.rs @@ -173,7 +173,7 @@ fn make_hygiene_info( db: &dyn ExpandDatabase, macro_file: MacroFile, loc: &MacroCallLoc, -) -> Option { +) -> HygieneInfo { let def = loc.def.ast_id().left().and_then(|id| { let def_tt = match id.to_node(db) { ast::Macro::MacroRules(mac) => mac.token_tree()?, @@ -204,7 +204,7 @@ fn make_hygiene_info( )) }); - Some(HygieneInfo { + HygieneInfo { file: macro_file, attr_input_or_mac_def_start: attr_input_or_mac_def .map(|it| it.map(|tt| tt.syntax().text_range().start())), @@ -212,7 +212,7 @@ fn make_hygiene_info( macro_arg, macro_def, exp_map, - }) + } } impl HygieneFrame { @@ -221,8 +221,7 @@ impl HygieneFrame { None => (None, None, false), Some(macro_file) => { let loc = db.lookup_intern_macro_call(macro_file.macro_call_id); - let info = - make_hygiene_info(db, macro_file, &loc).map(|info| (loc.kind.file_id(), info)); + let info = Some((make_hygiene_info(db, macro_file, &loc), loc.kind.file_id())); match loc.def.kind { MacroDefKind::Declarative(_) => { (info, Some(loc.def.krate), loc.def.local_inner) @@ -236,17 +235,14 @@ impl HygieneFrame { } }; - let (calling_file, info) = match info { - None => { - return HygieneFrame { - expansion: None, - local_inner, - krate, - call_site: None, - def_site: None, - }; + let Some((info, calling_file)) = info else { + return HygieneFrame { + expansion: None, + local_inner, + krate, + call_site: None, + def_site: None, } - Some(it) => it, }; let def_site = info.attr_input_or_mac_def_start.map(|it| db.hygiene_frame(it.file_id)); diff --git a/crates/mbe/src/lib.rs b/crates/mbe/src/lib.rs index 665bce474a69d..9d886a1c979a9 100644 --- a/crates/mbe/src/lib.rs +++ b/crates/mbe/src/lib.rs @@ -28,7 +28,6 @@ use crate::{ tt_iter::TtIter, }; -// FIXME: we probably should re-think `token_tree_to_syntax_node` interfaces pub use self::tt::{Delimiter, DelimiterKind, Punct}; pub use ::parser::TopEntryPoint; From a743903cf06dfc8b55cd71840895b453a16cf0ae Mon Sep 17 00:00:00 2001 From: Max Heller Date: Tue, 1 Aug 2023 18:18:12 -0400 Subject: [PATCH 44/59] remove unicode-ident dependency --- Cargo.lock | 1 - crates/ide-completion/Cargo.toml | 1 - crates/ide-completion/src/item.rs | 4 ++-- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f07c08a77bcc2..f8806794979e7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -715,7 +715,6 @@ dependencies = [ "syntax", "test-utils", "text-edit", - "unicode-ident", ] [[package]] diff --git a/crates/ide-completion/Cargo.toml b/crates/ide-completion/Cargo.toml index c06ac55aae332..092fb303668fe 100644 --- a/crates/ide-completion/Cargo.toml +++ b/crates/ide-completion/Cargo.toml @@ -17,7 +17,6 @@ itertools = "0.10.5" once_cell = "1.17.0" smallvec.workspace = true -unicode-ident = "1.0.0" # local deps diff --git a/crates/ide-completion/src/item.rs b/crates/ide-completion/src/item.rs index 92782d1e807d2..0309952c29a8f 100644 --- a/crates/ide-completion/src/item.rs +++ b/crates/ide-completion/src/item.rs @@ -437,8 +437,8 @@ impl Builder { // `PartialOrd` because it has an alias of ">". .filter(|alias| { let mut chars = alias.chars(); - chars.next().is_some_and(unicode_ident::is_xid_start) - && chars.all(unicode_ident::is_xid_continue) + chars.next().is_some_and(char::is_alphabetic) + && chars.all(|c| c.is_alphanumeric() || c == '_') }) // Deliberately concatenated without separators as adding separators e.g. // `alias1, alias2` results in LSP clients continuing to display the completion even From 31bcba84f9281752b63254d9c7fdfb9c7792011f Mon Sep 17 00:00:00 2001 From: Tadeo Kondrak Date: Tue, 1 Aug 2023 19:04:36 -0600 Subject: [PATCH 45/59] hir: Desugar `while` to `loop` and `break` --- crates/hir-def/src/body/lower.rs | 11 +++++++++-- crates/hir-ty/src/tests/macros.rs | 4 ++++ crates/hir-ty/src/tests/never_type.rs | 6 ++++++ crates/hir-ty/src/tests/regression.rs | 2 ++ 4 files changed, 21 insertions(+), 2 deletions(-) diff --git a/crates/hir-def/src/body/lower.rs b/crates/hir-def/src/body/lower.rs index 152c02743f77f..c8d1ca4fa708d 100644 --- a/crates/hir-def/src/body/lower.rs +++ b/crates/hir-def/src/body/lower.rs @@ -314,11 +314,18 @@ impl ExprCollector<'_> { self.alloc_expr(Expr::Loop { body, label }, syntax_ptr) } ast::Expr::WhileExpr(e) => { + // Desugar `while { }` to + // `loop { if { } else { break } }` let label = e.label().map(|label| self.collect_label(label)); let body = self.collect_labelled_block_opt(label, e.loop_body()); let condition = self.collect_expr_opt(e.condition()); - - self.alloc_expr(Expr::While { condition, body, label }, syntax_ptr) + let break_expr = + self.alloc_expr(Expr::Break { expr: None, label: None }, syntax_ptr.clone()); + let if_expr = self.alloc_expr( + Expr::If { condition, then_branch: body, else_branch: Some(break_expr) }, + syntax_ptr.clone(), + ); + self.alloc_expr(Expr::Loop { body: if_expr, label }, syntax_ptr) } ast::Expr::ForExpr(e) => self.collect_for_loop(syntax_ptr, e), ast::Expr::CallExpr(e) => { diff --git a/crates/hir-ty/src/tests/macros.rs b/crates/hir-ty/src/tests/macros.rs index b71c457f01528..1e6e946a13f8b 100644 --- a/crates/hir-ty/src/tests/macros.rs +++ b/crates/hir-ty/src/tests/macros.rs @@ -209,6 +209,8 @@ fn expr_macro_def_expanded_in_various_places() { 104..105 '_': IntoIterator::Item 117..119 '{}': () 124..134 '|| spam!()': impl Fn() -> isize + 140..156 'while ...!() {}': ! + 140..156 'while ...!() {}': () 140..156 'while ...!() {}': () 154..156 '{}': () 161..174 'break spam!()': ! @@ -300,6 +302,8 @@ fn expr_macro_rules_expanded_in_various_places() { 118..119 '_': IntoIterator::Item 131..133 '{}': () 138..148 '|| spam!()': impl Fn() -> isize + 154..170 'while ...!() {}': ! + 154..170 'while ...!() {}': () 154..170 'while ...!() {}': () 168..170 '{}': () 175..188 'break spam!()': ! diff --git a/crates/hir-ty/src/tests/never_type.rs b/crates/hir-ty/src/tests/never_type.rs index 59046c0435a3a..5d809b823923c 100644 --- a/crates/hir-ty/src/tests/never_type.rs +++ b/crates/hir-ty/src/tests/never_type.rs @@ -412,17 +412,23 @@ fn diverging_expression_3_break() { 355..654 '{ ...; }; }': () 398..399 'x': u32 407..433 '{ whil...; }; }': u32 + 409..430 'while ...eak; }': ! + 409..430 'while ...eak; }': () 409..430 'while ...eak; }': () 415..419 'true': bool 420..430 '{ break; }': () 422..427 'break': ! 537..538 'x': u32 546..564 '{ whil... {}; }': u32 + 548..561 'while true {}': ! + 548..561 'while true {}': () 548..561 'while true {}': () 554..558 'true': bool 559..561 '{}': () 615..616 'x': u32 624..651 '{ whil...; }; }': u32 + 626..648 'while ...urn; }': ! + 626..648 'while ...urn; }': () 626..648 'while ...urn; }': () 632..636 'true': bool 637..648 '{ return; }': () diff --git a/crates/hir-ty/src/tests/regression.rs b/crates/hir-ty/src/tests/regression.rs index 375014d6c7f84..6ea059065e935 100644 --- a/crates/hir-ty/src/tests/regression.rs +++ b/crates/hir-ty/src/tests/regression.rs @@ -1267,6 +1267,8 @@ fn test() { "#, expect![[r#" 10..59 '{ ... } }': () + 16..57 'while ... }': ! + 16..57 'while ... }': () 16..57 'while ... }': () 22..30 '{ true }': bool 24..28 'true': bool From 92a97c292a95688d4d8973193e59b6443560f363 Mon Sep 17 00:00:00 2001 From: Tadeo Kondrak Date: Tue, 1 Aug 2023 19:08:16 -0600 Subject: [PATCH 46/59] hir: Remove Expr::While The previous commit desugared it to a loop. --- crates/hir-def/src/body/pretty.rs | 8 ------- crates/hir-def/src/body/scope.rs | 5 ----- crates/hir-def/src/hir.rs | 9 -------- crates/hir-ty/src/infer/closure.rs | 4 ---- crates/hir-ty/src/infer/expr.rs | 13 ------------ crates/hir-ty/src/infer/mutability.rs | 4 ---- crates/hir-ty/src/mir/lower.rs | 30 --------------------------- 7 files changed, 73 deletions(-) diff --git a/crates/hir-def/src/body/pretty.rs b/crates/hir-def/src/body/pretty.rs index eeaed87164dc0..5d71abe37cc5e 100644 --- a/crates/hir-def/src/body/pretty.rs +++ b/crates/hir-def/src/body/pretty.rs @@ -178,14 +178,6 @@ impl Printer<'_> { w!(self, "loop "); self.print_expr(*body); } - Expr::While { condition, body, label } => { - if let Some(lbl) = label { - w!(self, "{}: ", self.body[*lbl].name.display(self.db)); - } - w!(self, "while "); - self.print_expr(*condition); - self.print_expr(*body); - } Expr::Call { callee, args, is_assignee_expr: _ } => { self.print_expr(*callee); w!(self, "("); diff --git a/crates/hir-def/src/body/scope.rs b/crates/hir-def/src/body/scope.rs index 69741c445fbae..2a90a09f25e8c 100644 --- a/crates/hir-def/src/body/scope.rs +++ b/crates/hir-def/src/body/scope.rs @@ -228,11 +228,6 @@ fn compute_expr_scopes(expr: ExprId, body: &Body, scopes: &mut ExprScopes, scope scopes.set_scope(expr, scope); compute_block_scopes(statements, *tail, body, scopes, &mut scope); } - Expr::While { condition, body: body_expr, label } => { - let mut scope = scopes.new_labeled_scope(*scope, make_label(label)); - compute_expr_scopes(*condition, body, scopes, &mut scope); - compute_expr_scopes(*body_expr, body, scopes, &mut scope); - } Expr::Loop { body: body_expr, label } => { let mut scope = scopes.new_labeled_scope(*scope, make_label(label)); compute_expr_scopes(*body_expr, body, scopes, &mut scope); diff --git a/crates/hir-def/src/hir.rs b/crates/hir-def/src/hir.rs index 8a140a1ec1819..6591c92ac62d0 100644 --- a/crates/hir-def/src/hir.rs +++ b/crates/hir-def/src/hir.rs @@ -191,11 +191,6 @@ pub enum Expr { body: ExprId, label: Option, }, - While { - condition: ExprId, - body: ExprId, - label: Option, - }, Call { callee: ExprId, args: Box<[ExprId]>, @@ -379,10 +374,6 @@ impl Expr { } } Expr::Loop { body, .. } => f(*body), - Expr::While { condition, body, .. } => { - f(*condition); - f(*body); - } Expr::Call { callee, args, .. } => { f(*callee); args.iter().copied().for_each(f); diff --git a/crates/hir-ty/src/infer/closure.rs b/crates/hir-ty/src/infer/closure.rs index 972e5321a4759..1781f6c58f1c7 100644 --- a/crates/hir-ty/src/infer/closure.rs +++ b/crates/hir-ty/src/infer/closure.rs @@ -488,10 +488,6 @@ impl InferenceContext<'_> { self.consume_expr(*tail); } } - Expr::While { condition, body, label: _ } => { - self.consume_expr(*condition); - self.consume_expr(*body); - } Expr::Call { callee, args, is_assignee_expr: _ } => { self.consume_expr(*callee); self.consume_exprs(args.iter().copied()); diff --git a/crates/hir-ty/src/infer/expr.rs b/crates/hir-ty/src/infer/expr.rs index 501c0b4bd77a1..8cbdae6252672 100644 --- a/crates/hir-ty/src/infer/expr.rs +++ b/crates/hir-ty/src/infer/expr.rs @@ -198,19 +198,6 @@ impl InferenceContext<'_> { None => self.result.standard_types.never.clone(), } } - &Expr::While { condition, body, label } => { - self.with_breakable_ctx(BreakableKind::Loop, None, label, |this| { - this.infer_expr( - condition, - &Expectation::HasType(this.result.standard_types.bool_.clone()), - ); - this.infer_expr(body, &Expectation::HasType(TyBuilder::unit())); - }); - - // the body may not run, so it diverging doesn't mean we diverge - self.diverges = Diverges::Maybe; - TyBuilder::unit() - } Expr::Closure { body, args, ret_type, arg_types, closure_kind, capture_by: _ } => { assert_eq!(args.len(), arg_types.len()); diff --git a/crates/hir-ty/src/infer/mutability.rs b/crates/hir-ty/src/infer/mutability.rs index f517bc2c09f1b..396ca0044ff02 100644 --- a/crates/hir-ty/src/infer/mutability.rs +++ b/crates/hir-ty/src/infer/mutability.rs @@ -69,10 +69,6 @@ impl InferenceContext<'_> { self.infer_mut_expr(*tail, Mutability::Not); } } - &Expr::While { condition: c, body, label: _ } => { - self.infer_mut_expr(c, Mutability::Not); - self.infer_mut_expr(body, Mutability::Not); - } Expr::MethodCall { receiver: it, method_name: _, args, generic_args: _ } | Expr::Call { callee: it, args, is_assignee_expr: _ } => { self.infer_mut_not_expr_iter(args.iter().copied().chain(Some(*it))); diff --git a/crates/hir-ty/src/mir/lower.rs b/crates/hir-ty/src/mir/lower.rs index 566204bb70d4e..c406d9f773014 100644 --- a/crates/hir-ty/src/mir/lower.rs +++ b/crates/hir-ty/src/mir/lower.rs @@ -582,36 +582,6 @@ impl<'ctx> MirLowerCtx<'ctx> { Ok(()) }) } - Expr::While { condition, body, label } => { - self.lower_loop(current, place, *label, expr_id.into(), |this, begin| { - let scope = this.push_drop_scope(); - let Some((discr, to_switch)) = - this.lower_expr_to_some_operand(*condition, begin)? - else { - return Ok(()); - }; - let fail_cond = this.new_basic_block(); - let after_cond = this.new_basic_block(); - this.set_terminator( - to_switch, - TerminatorKind::SwitchInt { - discr, - targets: SwitchTargets::static_if(1, after_cond, fail_cond), - }, - expr_id.into(), - ); - let fail_cond = this.drop_until_scope(this.drop_scopes.len() - 1, fail_cond); - let end = this.current_loop_end()?; - this.set_goto(fail_cond, end, expr_id.into()); - if let Some((_, block)) = this.lower_expr_as_place(after_cond, *body, true)? { - let block = scope.pop_and_drop(this, block); - this.set_goto(block, begin, expr_id.into()); - } else { - scope.pop_assume_dropped(this); - } - Ok(()) - }) - } Expr::Call { callee, args, .. } => { if let Some((func_id, generic_args)) = self.infer.method_resolution(expr_id) { let ty = chalk_ir::TyKind::FnDef( From bcff166b3ae6b75174956561faf1adae02b593af Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 2 Aug 2023 11:52:55 +0200 Subject: [PATCH 47/59] Add ExternCrateDecl to HIR --- crates/hir-def/src/child_by_source.rs | 16 ++- crates/hir-def/src/data.rs | 16 +++ crates/hir-def/src/item_scope.rs | 6 + crates/hir-def/src/lib.rs | 6 + .../hir-def/src/nameres/tests/incremental.rs | 18 +-- crates/hir/src/attrs.rs | 39 +++++- crates/hir/src/db.rs | 5 - crates/hir/src/display.rs | 18 ++- crates/hir/src/from_id.rs | 5 +- crates/hir/src/has_source.rs | 14 ++- crates/hir/src/lib.rs | 65 ++++++++-- crates/hir/src/semantics.rs | 23 +--- crates/hir/src/semantics/source_to_def.rs | 12 +- .../src/handlers/add_turbo_fish.rs | 4 +- crates/ide-db/src/defs.rs | 53 ++++++-- crates/ide-db/src/famous_defs.rs | 2 +- crates/ide-db/src/rename.rs | 18 ++- .../src/handlers/missing_fields.rs | 2 +- crates/ide/src/doc_links.rs | 7 ++ crates/ide/src/goto_declaration.rs | 35 +++++- crates/ide/src/goto_definition.rs | 11 +- crates/ide/src/goto_implementation.rs | 88 +++++++------ crates/ide/src/hover.rs | 17 ++- crates/ide/src/hover/render.rs | 15 +-- crates/ide/src/hover/tests.rs | 54 +++++--- crates/ide/src/moniker.rs | 4 + crates/ide/src/navigation_target.rs | 27 +++- crates/ide/src/references.rs | 3 + crates/ide/src/rename.rs | 119 +++++++++++++++++- crates/ide/src/runnables.rs | 2 +- crates/ide/src/static_index.rs | 2 +- .../ide/src/syntax_highlighting/highlight.rs | 29 ++++- crates/ide/src/syntax_highlighting/inject.rs | 2 +- .../test_data/highlight_extern_crate.html | 2 +- .../test_data/highlight_keywords.html | 2 +- crates/parser/src/shortcuts.rs | 3 +- .../rust-analyzer/src/cli/analysis_stats.rs | 2 +- crates/rust-analyzer/src/cli/diagnostics.rs | 2 +- crates/rust-analyzer/src/cli/run_tests.rs | 2 +- 39 files changed, 584 insertions(+), 166 deletions(-) diff --git a/crates/hir-def/src/child_by_source.rs b/crates/hir-def/src/child_by_source.rs index bb79e28f2673a..814257745d068 100644 --- a/crates/hir-def/src/child_by_source.rs +++ b/crates/hir-def/src/child_by_source.rs @@ -14,8 +14,8 @@ use crate::{ item_scope::ItemScope, nameres::DefMap, src::{HasChildSource, HasSource}, - AdtId, AssocItemId, DefWithBodyId, EnumId, EnumVariantId, FieldId, ImplId, Lookup, MacroId, - ModuleDefId, ModuleId, TraitId, VariantId, + AdtId, AssocItemId, DefWithBodyId, EnumId, EnumVariantId, ExternCrateId, FieldId, ImplId, + Lookup, MacroId, ModuleDefId, ModuleId, TraitId, VariantId, }; pub trait ChildBySource { @@ -91,6 +91,7 @@ impl ChildBySource for ItemScope { fn child_by_source_to(&self, db: &dyn DefDatabase, res: &mut DynMap, file_id: HirFileId) { self.declarations().for_each(|item| add_module_def(db, res, file_id, item)); self.impls().for_each(|imp| add_impl(db, res, file_id, imp)); + self.extern_crate_decls().for_each(|ext| add_extern_crate(db, res, file_id, ext)); self.unnamed_consts().for_each(|konst| { let loc = konst.lookup(db); if loc.id.file_id() == file_id { @@ -167,6 +168,17 @@ impl ChildBySource for ItemScope { map[keys::IMPL].insert(loc.source(db).value, imp) } } + fn add_extern_crate( + db: &dyn DefDatabase, + map: &mut DynMap, + file_id: HirFileId, + ext: ExternCrateId, + ) { + let loc = ext.lookup(db); + if loc.id.file_id() == file_id { + map[keys::EXTERN_CRATE].insert(loc.source(db).value, ext) + } + } } } diff --git a/crates/hir-def/src/data.rs b/crates/hir-def/src/data.rs index 54fe9a2e84409..4fb4794d4a679 100644 --- a/crates/hir-def/src/data.rs +++ b/crates/hir-def/src/data.rs @@ -2,6 +2,7 @@ pub mod adt; +use base_db::CrateId; use hir_expand::{ name::Name, AstId, ExpandResult, HirFileId, InFile, MacroCallId, MacroCallKind, MacroDefKind, }; @@ -467,6 +468,7 @@ pub struct ExternCrateDeclData { pub name: Name, pub alias: Option, pub visibility: RawVisibility, + pub crate_id: CrateId, } impl ExternCrateDeclData { @@ -478,10 +480,24 @@ impl ExternCrateDeclData { let item_tree = loc.id.item_tree(db); let extern_crate = &item_tree[loc.id.value]; + let name = extern_crate.name.clone(); + let crate_id = if name == hir_expand::name![self] { + loc.container.krate() + } else { + db.crate_def_map(loc.container.krate()) + .extern_prelude() + .find(|&(prelude_name, ..)| *prelude_name == name) + // FIXME: Suspicious unwrap + .unwrap() + .1 + .krate() + }; + Arc::new(Self { name: extern_crate.name.clone(), visibility: item_tree[extern_crate.visibility].clone(), alias: extern_crate.alias.clone(), + crate_id, }) } } diff --git a/crates/hir-def/src/item_scope.rs b/crates/hir-def/src/item_scope.rs index 2ac1bcdc079f2..639b8215c84db 100644 --- a/crates/hir-def/src/item_scope.rs +++ b/crates/hir-def/src/item_scope.rs @@ -113,6 +113,12 @@ impl ItemScope { self.declarations.iter().copied() } + pub fn extern_crate_decls( + &self, + ) -> impl Iterator + ExactSizeIterator + '_ { + self.extern_crate_decls.iter().copied() + } + pub fn impls(&self) -> impl Iterator + ExactSizeIterator + '_ { self.impls.iter().copied() } diff --git a/crates/hir-def/src/lib.rs b/crates/hir-def/src/lib.rs index e8187e05adada..67fd5a3635d08 100644 --- a/crates/hir-def/src/lib.rs +++ b/crates/hir-def/src/lib.rs @@ -121,6 +121,12 @@ impl From for ModuleDefId { } } +impl From for CrateRootModuleId { + fn from(krate: CrateId) -> Self { + CrateRootModuleId { krate } + } +} + impl TryFrom for CrateRootModuleId { type Error = (); diff --git a/crates/hir-def/src/nameres/tests/incremental.rs b/crates/hir-def/src/nameres/tests/incremental.rs index 4931c36bbca92..40d3a16540dc7 100644 --- a/crates/hir-def/src/nameres/tests/incremental.rs +++ b/crates/hir-def/src/nameres/tests/incremental.rs @@ -213,17 +213,17 @@ pub type Ty = (); for (_, res) in module_data.scope.resolutions() { match res.values.or(res.types).unwrap().0 { - ModuleDefId::FunctionId(f) => drop(db.function_data(f)), + ModuleDefId::FunctionId(f) => _ = db.function_data(f), ModuleDefId::AdtId(adt) => match adt { - AdtId::StructId(it) => drop(db.struct_data(it)), - AdtId::UnionId(it) => drop(db.union_data(it)), - AdtId::EnumId(it) => drop(db.enum_data(it)), + AdtId::StructId(it) => _ = db.struct_data(it), + AdtId::UnionId(it) => _ = db.union_data(it), + AdtId::EnumId(it) => _ = db.enum_data(it), }, - ModuleDefId::ConstId(it) => drop(db.const_data(it)), - ModuleDefId::StaticId(it) => drop(db.static_data(it)), - ModuleDefId::TraitId(it) => drop(db.trait_data(it)), - ModuleDefId::TraitAliasId(it) => drop(db.trait_alias_data(it)), - ModuleDefId::TypeAliasId(it) => drop(db.type_alias_data(it)), + ModuleDefId::ConstId(it) => _ = db.const_data(it), + ModuleDefId::StaticId(it) => _ = db.static_data(it), + ModuleDefId::TraitId(it) => _ = db.trait_data(it), + ModuleDefId::TraitAliasId(it) => _ = db.trait_alias_data(it), + ModuleDefId::TypeAliasId(it) => _ = db.type_alias_data(it), ModuleDefId::EnumVariantId(_) | ModuleDefId::ModuleId(_) | ModuleDefId::MacroId(_) diff --git a/crates/hir/src/attrs.rs b/crates/hir/src/attrs.rs index cf8db2a5a24ab..b4944379dcb60 100644 --- a/crates/hir/src/attrs.rs +++ b/crates/hir/src/attrs.rs @@ -12,9 +12,9 @@ use hir_ty::db::HirDatabase; use syntax::{ast, AstNode}; use crate::{ - Adt, AssocItem, Const, ConstParam, Enum, Field, Function, GenericParam, Impl, LifetimeParam, - Macro, Module, ModuleDef, Static, Struct, Trait, TraitAlias, TypeAlias, TypeParam, Union, - Variant, + Adt, AssocItem, Const, ConstParam, Enum, ExternCrateDecl, Field, Function, GenericParam, Impl, + LifetimeParam, Macro, Module, ModuleDef, Static, Struct, Trait, TraitAlias, TypeAlias, + TypeParam, Union, Variant, }; pub trait HasAttrs { @@ -120,6 +120,39 @@ impl HasAttrs for AssocItem { } } +impl HasAttrs for ExternCrateDecl { + fn attrs(self, db: &dyn HirDatabase) -> AttrsWithOwner { + let def = AttrDefId::ExternCrateId(self.into()); + db.attrs_with_owner(def) + } + fn docs(self, db: &dyn HirDatabase) -> Option { + let crate_docs = self.resolved_crate(db).root_module().attrs(db).docs().map(String::from); + let def = AttrDefId::ExternCrateId(self.into()); + let decl_docs = db.attrs(def).docs().map(String::from); + match (decl_docs, crate_docs) { + (None, None) => None, + (Some(decl_docs), None) => Some(decl_docs), + (None, Some(crate_docs)) => Some(crate_docs), + (Some(mut decl_docs), Some(crate_docs)) => { + decl_docs.push('\n'); + decl_docs.push('\n'); + decl_docs += &crate_docs; + Some(decl_docs) + } + } + .map(Documentation::new) + } + fn resolve_doc_path( + self, + db: &dyn HirDatabase, + link: &str, + ns: Option, + ) -> Option { + let def = AttrDefId::ExternCrateId(self.into()); + resolve_doc_path(db, def, link, ns).map(ModuleDef::from) + } +} + /// Resolves the item `link` points to in the scope of `def`. fn resolve_doc_path( db: &dyn HirDatabase, diff --git a/crates/hir/src/db.rs b/crates/hir/src/db.rs index f3a0608944b65..936581bfe32c3 100644 --- a/crates/hir/src/db.rs +++ b/crates/hir/src/db.rs @@ -10,8 +10,3 @@ pub use hir_expand::db::{ MacroExpandQuery, ParseMacroExpansionErrorQuery, ParseMacroExpansionQuery, }; pub use hir_ty::db::*; - -#[test] -fn hir_database_is_object_safe() { - fn _assert_object_safe(_: &dyn HirDatabase) {} -} diff --git a/crates/hir/src/display.rs b/crates/hir/src/display.rs index 4de9c872ad6f6..9dfb98e459b9d 100644 --- a/crates/hir/src/display.rs +++ b/crates/hir/src/display.rs @@ -18,9 +18,9 @@ use hir_ty::{ }; use crate::{ - Adt, AsAssocItem, AssocItemContainer, Const, ConstParam, Enum, Field, Function, GenericParam, - HasCrate, HasVisibility, LifetimeParam, Macro, Module, Static, Struct, Trait, TraitAlias, - TyBuilder, Type, TypeAlias, TypeOrConstParam, TypeParam, Union, Variant, + Adt, AsAssocItem, AssocItemContainer, Const, ConstParam, Enum, ExternCrateDecl, Field, + Function, GenericParam, HasCrate, HasVisibility, LifetimeParam, Macro, Module, Static, Struct, + Trait, TraitAlias, TyBuilder, Type, TypeAlias, TypeOrConstParam, TypeParam, Union, Variant, }; impl HirDisplay for Function { @@ -238,6 +238,18 @@ impl HirDisplay for Type { } } +impl HirDisplay for ExternCrateDecl { + fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { + write_visibility(self.module(f.db).id, self.visibility(f.db), f)?; + f.write_str("extern crate ")?; + write!(f, "{}", self.name(f.db).display(f.db.upcast()))?; + if let Some(alias) = self.alias(f.db) { + write!(f, " as {alias}",)?; + } + Ok(()) + } +} + impl HirDisplay for GenericParam { fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { match self { diff --git a/crates/hir/src/from_id.rs b/crates/hir/src/from_id.rs index de23902199f55..fc4bbffdb8361 100644 --- a/crates/hir/src/from_id.rs +++ b/crates/hir/src/from_id.rs @@ -15,7 +15,7 @@ use crate::{ }; macro_rules! from_id { - ($(($id:path, $ty:path)),*) => {$( + ($(($id:path, $ty:path)),* $(,)?) => {$( impl From<$id> for $ty { fn from(id: $id) -> $ty { $ty { id } @@ -47,7 +47,8 @@ from_id![ (hir_def::TypeParamId, crate::TypeParam), (hir_def::ConstParamId, crate::ConstParam), (hir_def::LifetimeParamId, crate::LifetimeParam), - (hir_def::MacroId, crate::Macro) + (hir_def::MacroId, crate::Macro), + (hir_def::ExternCrateId, crate::ExternCrateDecl), ]; impl From for Adt { diff --git a/crates/hir/src/has_source.rs b/crates/hir/src/has_source.rs index b46a3856d4545..31cf8ba336434 100644 --- a/crates/hir/src/has_source.rs +++ b/crates/hir/src/has_source.rs @@ -11,9 +11,9 @@ use hir_expand::{HirFileId, InFile}; use syntax::ast; use crate::{ - db::HirDatabase, Adt, Const, Enum, Field, FieldSource, Function, Impl, LifetimeParam, - LocalSource, Macro, Module, Static, Struct, Trait, TraitAlias, TypeAlias, TypeOrConstParam, - Union, Variant, + db::HirDatabase, Adt, Const, Enum, ExternCrateDecl, Field, FieldSource, Function, Impl, + LifetimeParam, LocalSource, Macro, Module, Static, Struct, Trait, TraitAlias, TypeAlias, + TypeOrConstParam, Union, Variant, }; pub trait HasSource { @@ -207,3 +207,11 @@ impl HasSource for LocalSource { Some(self.source) } } + +impl HasSource for ExternCrateDecl { + type Ast = ast::ExternCrate; + + fn source(self, db: &dyn HirDatabase) -> Option> { + Some(self.id.lookup(db.upcast()).source(db.upcast())) + } +} diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index b094bb7a06883..1a9d3ac448e66 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -48,14 +48,15 @@ use hir_def::{ layout::{self, ReprOptions, TargetDataLayout}, macro_id_to_def_id, nameres::{self, diagnostics::DefDiagnostic}, + path::ImportAlias, per_ns::PerNs, resolver::{HasResolver, Resolver}, src::HasSource as _, - AssocItemId, AssocItemLoc, AttrDefId, ConstId, ConstParamId, DefWithBodyId, EnumId, - EnumVariantId, FunctionId, GenericDefId, HasModule, ImplId, InTypeConstId, ItemContainerId, - LifetimeParamId, LocalEnumVariantId, LocalFieldId, Lookup, MacroExpander, MacroId, ModuleId, - StaticId, StructId, TraitAliasId, TraitId, TypeAliasId, TypeOrConstParamId, TypeParamId, - UnionId, + AssocItemId, AssocItemLoc, AttrDefId, ConstId, ConstParamId, CrateRootModuleId, DefWithBodyId, + EnumId, EnumVariantId, ExternCrateId, FunctionId, GenericDefId, HasModule, ImplId, + InTypeConstId, ItemContainerId, LifetimeParamId, LocalEnumVariantId, LocalFieldId, Lookup, + MacroExpander, MacroId, ModuleId, StaticId, StructId, TraitAliasId, TraitId, TypeAliasId, + TypeOrConstParamId, TypeParamId, UnionId, }; use hir_expand::{name::name, MacroCallKind}; use hir_ty::{ @@ -200,9 +201,8 @@ impl Crate { db.crate_graph().transitive_rev_deps(self.id).map(|id| Crate { id }) } - pub fn root_module(self, db: &dyn HirDatabase) -> Module { - let def_map = db.crate_def_map(self.id); - Module { id: def_map.crate_root().into() } + pub fn root_module(self) -> Module { + Module { id: CrateRootModuleId::from(self.id).into() } } pub fn modules(self, db: &dyn HirDatabase) -> Vec { @@ -247,7 +247,7 @@ impl Crate { /// Try to get the root URL of the documentation of a crate. pub fn get_html_root_url(self: &Crate, db: &dyn HirDatabase) -> Option { // Look for #![doc(html_root_url = "...")] - let attrs = db.attrs(AttrDefId::ModuleId(self.root_module(db).into())); + let attrs = db.attrs(AttrDefId::ModuleId(self.root_module().into())); let doc_url = attrs.by_key("doc").find_string_value_in_tt("html_root_url"); doc_url.map(|s| s.trim_matches('"').trim_end_matches('/').to_owned() + "/") } @@ -2128,6 +2128,47 @@ impl HasVisibility for Function { } } +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct ExternCrateDecl { + pub(crate) id: ExternCrateId, +} + +impl ExternCrateDecl { + pub fn module(self, db: &dyn HirDatabase) -> Module { + self.id.module(db.upcast()).into() + } + + pub fn resolved_crate(self, db: &dyn HirDatabase) -> Crate { + db.extern_crate_decl_data(self.id).crate_id.into() + } + + pub fn name(self, db: &dyn HirDatabase) -> Name { + db.extern_crate_decl_data(self.id).name.clone() + } + + pub fn alias(self, db: &dyn HirDatabase) -> Option { + db.extern_crate_decl_data(self.id).alias.clone() + } + + /// Returns the name under which this crate is made accessible, taking `_` into account. + pub fn alias_or_name(self, db: &dyn HirDatabase) -> Option { + let extern_crate_decl_data = db.extern_crate_decl_data(self.id); + match &extern_crate_decl_data.alias { + Some(ImportAlias::Underscore) => None, + Some(ImportAlias::Alias(alias)) => Some(alias.clone()), + None => Some(extern_crate_decl_data.name.clone()), + } + } +} + +impl HasVisibility for ExternCrateDecl { + fn visibility(&self, db: &dyn HirDatabase) -> Visibility { + db.extern_crate_decl_data(self.id) + .visibility + .resolve(db.upcast(), &self.id.resolver(db.upcast())) + } +} + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct InTypeConst { pub(crate) id: InTypeConstId, @@ -4715,6 +4756,12 @@ pub trait HasContainer { fn container(&self, db: &dyn HirDatabase) -> ItemContainer; } +impl HasContainer for ExternCrateDecl { + fn container(&self, db: &dyn HirDatabase) -> ItemContainer { + container_id_to_hir(self.id.lookup(db.upcast()).container.into()) + } +} + impl HasContainer for Module { fn container(&self, db: &dyn HirDatabase) -> ItemContainer { // FIXME: handle block expressions as modules (their parent is in a different DefMap) diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs index 39a3e1c4489e1..e99d2984c367b 100644 --- a/crates/hir/src/semantics.rs +++ b/crates/hir/src/semantics.rs @@ -15,11 +15,7 @@ use hir_def::{ type_ref::Mutability, AsMacroCall, DefWithBodyId, FieldId, FunctionId, MacroId, TraitId, VariantId, }; -use hir_expand::{ - db::ExpandDatabase, - name::{known, AsName}, - ExpansionInfo, MacroCallId, -}; +use hir_expand::{db::ExpandDatabase, name::AsName, ExpansionInfo, MacroCallId}; use itertools::Itertools; use rustc_hash::{FxHashMap, FxHashSet}; use smallvec::{smallvec, SmallVec}; @@ -439,10 +435,6 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> { self.imp.resolve_path(path) } - pub fn resolve_extern_crate(&self, extern_crate: &ast::ExternCrate) -> Option { - self.imp.resolve_extern_crate(extern_crate) - } - pub fn resolve_variant(&self, record_lit: ast::RecordExpr) -> Option { self.imp.resolve_variant(record_lit).map(VariantDef::from) } @@ -1242,18 +1234,6 @@ impl<'db> SemanticsImpl<'db> { self.analyze(path.syntax())?.resolve_path(self.db, path) } - fn resolve_extern_crate(&self, extern_crate: &ast::ExternCrate) -> Option { - let krate = self.scope(extern_crate.syntax())?.krate(); - let name = extern_crate.name_ref()?.as_name(); - if name == known::SELF_PARAM { - return Some(krate); - } - krate - .dependencies(self.db) - .into_iter() - .find_map(|dep| (dep.name == name).then_some(dep.krate)) - } - fn resolve_variant(&self, record_lit: ast::RecordExpr) -> Option { self.analyze(record_lit.syntax())?.resolve_variant(self.db, record_lit) } @@ -1603,6 +1583,7 @@ to_def_impls![ (crate::Local, ast::SelfParam, self_param_to_def), (crate::Label, ast::Label, label_to_def), (crate::Adt, ast::Adt, adt_to_def), + (crate::ExternCrateDecl, ast::ExternCrate, extern_crate_to_def), ]; fn find_root(node: &SyntaxNode) -> SyntaxNode { diff --git a/crates/hir/src/semantics/source_to_def.rs b/crates/hir/src/semantics/source_to_def.rs index b971ca62387ef..e6eb7d7a5633e 100644 --- a/crates/hir/src/semantics/source_to_def.rs +++ b/crates/hir/src/semantics/source_to_def.rs @@ -93,9 +93,9 @@ use hir_def::{ DynMap, }, hir::{BindingId, LabelId}, - AdtId, ConstId, ConstParamId, DefWithBodyId, EnumId, EnumVariantId, FieldId, FunctionId, - GenericDefId, GenericParamId, ImplId, LifetimeParamId, MacroId, ModuleId, StaticId, StructId, - TraitAliasId, TraitId, TypeAliasId, TypeParamId, UnionId, VariantId, + AdtId, ConstId, ConstParamId, DefWithBodyId, EnumId, EnumVariantId, ExternCrateId, FieldId, + FunctionId, GenericDefId, GenericParamId, ImplId, LifetimeParamId, MacroId, ModuleId, StaticId, + StructId, TraitAliasId, TraitId, TypeAliasId, TypeParamId, UnionId, VariantId, }; use hir_expand::{attrs::AttrId, name::AsName, HirFileId, MacroCallId}; use rustc_hash::FxHashMap; @@ -203,6 +203,12 @@ impl SourceToDefCtx<'_, '_> { ) -> Option { self.to_def(src, keys::VARIANT) } + pub(super) fn extern_crate_to_def( + &mut self, + src: InFile, + ) -> Option { + self.to_def(src, keys::EXTERN_CRATE) + } pub(super) fn adt_to_def( &mut self, InFile { file_id, value }: InFile, diff --git a/crates/ide-assists/src/handlers/add_turbo_fish.rs b/crates/ide-assists/src/handlers/add_turbo_fish.rs index acf82e4b25794..36f68d1767735 100644 --- a/crates/ide-assists/src/handlers/add_turbo_fish.rs +++ b/crates/ide-assists/src/handlers/add_turbo_fish.rs @@ -42,7 +42,9 @@ pub(crate) fn add_turbo_fish(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opti let name_ref = ast::NameRef::cast(ident.parent()?)?; let def = match NameRefClass::classify(&ctx.sema, &name_ref)? { NameRefClass::Definition(def) => def, - NameRefClass::FieldShorthand { .. } => return None, + NameRefClass::FieldShorthand { .. } | NameRefClass::ExternCrateShorthand { .. } => { + return None + } }; let fun = match def { Definition::Function(it) => it, diff --git a/crates/ide-db/src/defs.rs b/crates/ide-db/src/defs.rs index 760834bfafc7b..4417a61b86654 100644 --- a/crates/ide-db/src/defs.rs +++ b/crates/ide-db/src/defs.rs @@ -7,10 +7,10 @@ use arrayvec::ArrayVec; use hir::{ - Adt, AsAssocItem, AssocItem, BuiltinAttr, BuiltinType, Const, Crate, DeriveHelper, Field, - Function, GenericParam, HasVisibility, Impl, Label, Local, Macro, Module, ModuleDef, Name, - PathResolution, Semantics, Static, ToolModule, Trait, TraitAlias, TypeAlias, Variant, - Visibility, + Adt, AsAssocItem, AssocItem, BuiltinAttr, BuiltinType, Const, Crate, DeriveHelper, + ExternCrateDecl, Field, Function, GenericParam, HasVisibility, Impl, Label, Local, Macro, + Module, ModuleDef, Name, PathResolution, Semantics, Static, ToolModule, Trait, TraitAlias, + TypeAlias, Variant, Visibility, }; use stdx::impl_from; use syntax::{ @@ -42,6 +42,7 @@ pub enum Definition { DeriveHelper(DeriveHelper), BuiltinAttr(BuiltinAttr), ToolModule(ToolModule), + ExternCrateDecl(ExternCrateDecl), } impl Definition { @@ -73,6 +74,7 @@ impl Definition { Definition::Local(it) => it.module(db), Definition::GenericParam(it) => it.module(db), Definition::Label(it) => it.module(db), + Definition::ExternCrateDecl(it) => it.module(db), Definition::DeriveHelper(it) => it.derive().module(db), Definition::BuiltinAttr(_) | Definition::BuiltinType(_) | Definition::ToolModule(_) => { return None @@ -93,6 +95,7 @@ impl Definition { Definition::TraitAlias(it) => it.visibility(db), Definition::TypeAlias(it) => it.visibility(db), Definition::Variant(it) => it.visibility(db), + Definition::ExternCrateDecl(it) => it.visibility(db), Definition::BuiltinType(_) => Visibility::Public, Definition::Macro(_) => return None, Definition::BuiltinAttr(_) @@ -127,6 +130,7 @@ impl Definition { Definition::BuiltinAttr(_) => return None, // FIXME Definition::ToolModule(_) => return None, // FIXME Definition::DeriveHelper(it) => it.name(db), + Definition::ExternCrateDecl(it) => return it.alias_or_name(db), }; Some(name) } @@ -196,6 +200,10 @@ impl IdentClass { res.push(Definition::Local(local_ref)); res.push(Definition::Field(field_ref)); } + IdentClass::NameRefClass(NameRefClass::ExternCrateShorthand { decl, krate }) => { + res.push(Definition::ExternCrateDecl(decl)); + res.push(Definition::Module(krate.root_module())); + } IdentClass::Operator( OperatorClass::Await(func) | OperatorClass::Prefix(func) @@ -222,6 +230,10 @@ impl IdentClass { res.push(Definition::Local(local_ref)); res.push(Definition::Field(field_ref)); } + IdentClass::NameRefClass(NameRefClass::ExternCrateShorthand { decl, krate }) => { + res.push(Definition::ExternCrateDecl(decl)); + res.push(Definition::Module(krate.root_module())); + } IdentClass::Operator(_) => (), } res @@ -310,6 +322,7 @@ impl NameClass { ast::Item::Enum(it) => Definition::Adt(hir::Adt::Enum(sema.to_def(&it)?)), ast::Item::Struct(it) => Definition::Adt(hir::Adt::Struct(sema.to_def(&it)?)), ast::Item::Union(it) => Definition::Adt(hir::Adt::Union(sema.to_def(&it)?)), + ast::Item::ExternCrate(it) => Definition::ExternCrateDecl(sema.to_def(&it)?), _ => return None, }; Some(definition) @@ -346,10 +359,8 @@ impl NameClass { let path = use_tree.path()?; sema.resolve_path(&path).map(Definition::from) } else { - let extern_crate = rename.syntax().parent().and_then(ast::ExternCrate::cast)?; - let krate = sema.resolve_extern_crate(&extern_crate)?; - let root_module = krate.root_module(sema.db); - Some(Definition::Module(root_module)) + sema.to_def(&rename.syntax().parent().and_then(ast::ExternCrate::cast)?) + .map(Definition::ExternCrateDecl) } } } @@ -427,7 +438,19 @@ impl OperatorClass { #[derive(Debug)] pub enum NameRefClass { Definition(Definition), - FieldShorthand { local_ref: Local, field_ref: Field }, + FieldShorthand { + local_ref: Local, + field_ref: Field, + }, + /// The specific situation where we have an extern crate decl without a rename + /// Here we have both a declaration and a reference. + /// ```rs + /// extern crate foo; + /// ``` + ExternCrateShorthand { + decl: ExternCrateDecl, + krate: Crate, + }, } impl NameRefClass { @@ -513,10 +536,14 @@ impl NameRefClass { } None }, - ast::ExternCrate(extern_crate) => { - let krate = sema.resolve_extern_crate(&extern_crate)?; - let root_module = krate.root_module(sema.db); - Some(NameRefClass::Definition(Definition::Module(root_module))) + ast::ExternCrate(extern_crate_ast) => { + let extern_crate = sema.to_def(&extern_crate_ast)?; + let krate = extern_crate.resolved_crate(sema.db); + Some(if extern_crate_ast.rename().is_some() { + NameRefClass::Definition(Definition::Module(krate.root_module())) + } else { + NameRefClass::ExternCrateShorthand { krate, decl: extern_crate } + }) }, _ => None } diff --git a/crates/ide-db/src/famous_defs.rs b/crates/ide-db/src/famous_defs.rs index c8341fed1c72c..b63dde2c21e73 100644 --- a/crates/ide-db/src/famous_defs.rs +++ b/crates/ide-db/src/famous_defs.rs @@ -167,7 +167,7 @@ impl FamousDefs<'_, '_> { lang_crate => lang_crate, }; let std_crate = self.find_lang_crate(lang_crate)?; - let mut module = std_crate.root_module(db); + let mut module = std_crate.root_module(); for segment in path { module = module.children(db).find_map(|child| { let name = child.name(db)?; diff --git a/crates/ide-db/src/rename.rs b/crates/ide-db/src/rename.rs index 52a23b4b8f3d2..aa0bb7cce6912 100644 --- a/crates/ide-db/src/rename.rs +++ b/crates/ide-db/src/rename.rs @@ -82,8 +82,9 @@ impl Definition { } /// Textual range of the identifier which will change when renaming this - /// `Definition`. Note that some definitions, like builtin types, can't be - /// renamed. + /// `Definition`. Note that builtin types can't be + /// renamed and extern crate names will report its range, though a rename will introduce + /// an alias instead. pub fn range_for_rename(self, sema: &Semantics<'_, RootDatabase>) -> Option { let res = match self { Definition::Macro(mac) => { @@ -146,6 +147,16 @@ impl Definition { let lifetime = src.value.lifetime()?; src.with_value(lifetime.syntax()).original_file_range_opt(sema.db) } + Definition::ExternCrateDecl(it) => { + let src = it.source(sema.db)?; + if let Some(rename) = src.value.rename() { + let name = rename.name()?; + src.with_value(name.syntax()).original_file_range_opt(sema.db) + } else { + let name = src.value.name_ref()?; + src.with_value(name.syntax()).original_file_range_opt(sema.db) + } + } Definition::BuiltinType(_) => return None, Definition::SelfType(_) => return None, Definition::BuiltinAttr(_) => return None, @@ -526,6 +537,9 @@ fn source_edit_from_def( TextRange::new(range.start() + syntax::TextSize::from(1), range.end()), new_name.strip_prefix('\'').unwrap_or(new_name).to_owned(), ), + Definition::ExternCrateDecl(decl) if decl.alias(sema.db).is_none() => { + (TextRange::empty(range.end()), format!(" as {new_name}")) + } _ => (range, new_name.to_owned()), }; edit.replace(range, new_name); diff --git a/crates/ide-diagnostics/src/handlers/missing_fields.rs b/crates/ide-diagnostics/src/handlers/missing_fields.rs index bb0e36ff3a1e3..acc31cd117adb 100644 --- a/crates/ide-diagnostics/src/handlers/missing_fields.rs +++ b/crates/ide-diagnostics/src/handlers/missing_fields.rs @@ -208,7 +208,7 @@ fn get_default_constructor( } let krate = ctx.sema.to_module_def(d.file.original_file(ctx.sema.db))?.krate(); - let module = krate.root_module(ctx.sema.db); + let module = krate.root_module(); // Look for a ::new() associated function let has_new_func = ty diff --git a/crates/ide/src/doc_links.rs b/crates/ide/src/doc_links.rs index c90ba21253536..d240127f3761a 100644 --- a/crates/ide/src/doc_links.rs +++ b/crates/ide/src/doc_links.rs @@ -153,6 +153,9 @@ pub(crate) fn external_docs( NameRefClass::FieldShorthand { local_ref: _, field_ref } => { Definition::Field(field_ref) } + NameRefClass::ExternCrateShorthand { decl, .. } => { + Definition::ExternCrateDecl(decl) + } }, ast::Name(name) => match NameClass::classify(sema, &name)? { NameClass::Definition(it) | NameClass::ConstReference(it) => it, @@ -209,6 +212,7 @@ pub(crate) fn resolve_doc_path_for_def( Definition::Macro(it) => it.resolve_doc_path(db, link, ns), Definition::Field(it) => it.resolve_doc_path(db, link, ns), Definition::SelfType(it) => it.resolve_doc_path(db, link, ns), + Definition::ExternCrateDecl(it) => it.resolve_doc_path(db, link, ns), Definition::BuiltinAttr(_) | Definition::ToolModule(_) | Definition::BuiltinType(_) @@ -617,6 +621,9 @@ fn filename_and_frag_for_def( // FIXME fragment numbering return Some((adt, file, Some(String::from("impl")))); } + Definition::ExternCrateDecl(it) => { + format!("{}/index.html", it.name(db).display(db.upcast())) + } Definition::Local(_) | Definition::GenericParam(_) | Definition::Label(_) diff --git a/crates/ide/src/goto_declaration.rs b/crates/ide/src/goto_declaration.rs index e70bc2ec54172..c39c696cfd9be 100644 --- a/crates/ide/src/goto_declaration.rs +++ b/crates/ide/src/goto_declaration.rs @@ -37,11 +37,15 @@ pub(crate) fn goto_declaration( match parent { ast::NameRef(name_ref) => match NameRefClass::classify(&sema, &name_ref)? { NameRefClass::Definition(it) => Some(it), - NameRefClass::FieldShorthand { field_ref, .. } => return field_ref.try_to_nav(db), + NameRefClass::FieldShorthand { field_ref, .. } => + return field_ref.try_to_nav(db), + NameRefClass::ExternCrateShorthand { decl, .. } => + return decl.try_to_nav(db), }, ast::Name(name) => match NameClass::classify(&sema, &name)? { NameClass::Definition(it) | NameClass::ConstReference(it) => Some(it), - NameClass::PatFieldShorthand { field_ref, .. } => return field_ref.try_to_nav(db), + NameClass::PatFieldShorthand { field_ref, .. } => + return field_ref.try_to_nav(db), }, _ => None } @@ -53,6 +57,7 @@ pub(crate) fn goto_declaration( Definition::Const(c) => c.as_assoc_item(db), Definition::TypeAlias(ta) => ta.as_assoc_item(db), Definition::Function(f) => f.as_assoc_item(db), + Definition::ExternCrateDecl(it) => return it.try_to_nav(db), _ => None, }?; @@ -211,4 +216,30 @@ fn main() { "#, ); } + + #[test] + fn goto_decl_for_extern_crate() { + check( + r#" +//- /main.rs crate:main deps:std +extern crate std$0; + /// ^^^ +//- /std/lib.rs crate:std +// empty +"#, + ) + } + + #[test] + fn goto_decl_for_renamed_extern_crate() { + check( + r#" +//- /main.rs crate:main deps:std +extern crate std as abc$0; + /// ^^^ +//- /std/lib.rs crate:std +// empty +"#, + ) + } } diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs index 4e641357e3728..10a26befa466f 100644 --- a/crates/ide/src/goto_definition.rs +++ b/crates/ide/src/goto_definition.rs @@ -1,6 +1,9 @@ use std::mem::discriminant; -use crate::{doc_links::token_as_doc_comment, FilePosition, NavigationTarget, RangeInfo, TryToNav}; +use crate::{ + doc_links::token_as_doc_comment, navigation_target::ToNav, FilePosition, NavigationTarget, + RangeInfo, TryToNav, +}; use hir::{AsAssocItem, AssocItem, Semantics}; use ide_db::{ base_db::{AnchoredPath, FileId, FileLoader}, @@ -73,6 +76,12 @@ pub(crate) fn goto_definition( .definitions() .into_iter() .flat_map(|def| { + if let Definition::ExternCrateDecl(crate_def) = def { + return vec![crate_def + .resolved_crate(db) + .root_module() + .to_nav(sema.db)]; + } try_filter_trait_item_definition(sema, &def) .unwrap_or_else(|| def_to_nav(sema.db, def)) }) diff --git a/crates/ide/src/goto_implementation.rs b/crates/ide/src/goto_implementation.rs index a1a119629a94e..37166bdbd0c1f 100644 --- a/crates/ide/src/goto_implementation.rs +++ b/crates/ide/src/goto_implementation.rs @@ -34,54 +34,50 @@ pub(crate) fn goto_implementation( _ => 0, })?; let range = original_token.text_range(); - let navs = sema - .descend_into_macros(original_token) - .into_iter() - .filter_map(|token| token.parent().and_then(ast::NameLike::cast)) - .filter_map(|node| match &node { - ast::NameLike::Name(name) => { - NameClass::classify(&sema, name).map(|class| match class { - NameClass::Definition(it) | NameClass::ConstReference(it) => it, - NameClass::PatFieldShorthand { local_def, field_ref: _ } => { - Definition::Local(local_def) + let navs = + sema.descend_into_macros(original_token) + .into_iter() + .filter_map(|token| token.parent().and_then(ast::NameLike::cast)) + .filter_map(|node| match &node { + ast::NameLike::Name(name) => { + NameClass::classify(&sema, name).and_then(|class| match class { + NameClass::Definition(it) | NameClass::ConstReference(it) => Some(it), + NameClass::PatFieldShorthand { .. } => None, + }) + } + ast::NameLike::NameRef(name_ref) => NameRefClass::classify(&sema, name_ref) + .and_then(|class| match class { + NameRefClass::Definition(def) => Some(def), + NameRefClass::FieldShorthand { .. } + | NameRefClass::ExternCrateShorthand { .. } => None, + }), + ast::NameLike::Lifetime(_) => None, + }) + .unique() + .filter_map(|def| { + let navs = match def { + Definition::Trait(trait_) => impls_for_trait(&sema, trait_), + Definition::Adt(adt) => impls_for_ty(&sema, adt.ty(sema.db)), + Definition::TypeAlias(alias) => impls_for_ty(&sema, alias.ty(sema.db)), + Definition::BuiltinType(builtin) => impls_for_ty(&sema, builtin.ty(sema.db)), + Definition::Function(f) => { + let assoc = f.as_assoc_item(sema.db)?; + let name = assoc.name(sema.db)?; + let trait_ = assoc.containing_trait_or_trait_impl(sema.db)?; + impls_for_trait_item(&sema, trait_, name) } - }) - } - ast::NameLike::NameRef(name_ref) => { - NameRefClass::classify(&sema, name_ref).map(|class| match class { - NameRefClass::Definition(def) => def, - NameRefClass::FieldShorthand { local_ref, field_ref: _ } => { - Definition::Local(local_ref) + Definition::Const(c) => { + let assoc = c.as_assoc_item(sema.db)?; + let name = assoc.name(sema.db)?; + let trait_ = assoc.containing_trait_or_trait_impl(sema.db)?; + impls_for_trait_item(&sema, trait_, name) } - }) - } - ast::NameLike::Lifetime(_) => None, - }) - .unique() - .filter_map(|def| { - let navs = match def { - Definition::Trait(trait_) => impls_for_trait(&sema, trait_), - Definition::Adt(adt) => impls_for_ty(&sema, adt.ty(sema.db)), - Definition::TypeAlias(alias) => impls_for_ty(&sema, alias.ty(sema.db)), - Definition::BuiltinType(builtin) => impls_for_ty(&sema, builtin.ty(sema.db)), - Definition::Function(f) => { - let assoc = f.as_assoc_item(sema.db)?; - let name = assoc.name(sema.db)?; - let trait_ = assoc.containing_trait_or_trait_impl(sema.db)?; - impls_for_trait_item(&sema, trait_, name) - } - Definition::Const(c) => { - let assoc = c.as_assoc_item(sema.db)?; - let name = assoc.name(sema.db)?; - let trait_ = assoc.containing_trait_or_trait_impl(sema.db)?; - impls_for_trait_item(&sema, trait_, name) - } - _ => return None, - }; - Some(navs) - }) - .flatten() - .collect(); + _ => return None, + }; + Some(navs) + }) + .flatten() + .collect(); Some(RangeInfo { range, info: navs }) } diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs index 5ef6ac9480721..40659e6c26317 100644 --- a/crates/ide/src/hover.rs +++ b/crates/ide/src/hover.rs @@ -9,7 +9,7 @@ use either::Either; use hir::{db::DefDatabase, HasSource, LangItem, Semantics}; use ide_db::{ base_db::FileRange, - defs::{Definition, IdentClass, OperatorClass}, + defs::{Definition, IdentClass, NameRefClass, OperatorClass}, famous_defs::FamousDefs, helpers::pick_best_token, FxIndexSet, RootDatabase, @@ -186,7 +186,20 @@ fn hover_simple( // rendering poll is very confusing return None; } - Some(class.definitions().into_iter().zip(iter::once(node).cycle())) + if let IdentClass::NameRefClass(NameRefClass::ExternCrateShorthand { + decl, + .. + }) = class + { + return Some(vec![(Definition::ExternCrateDecl(decl), node)]); + } + Some( + class + .definitions() + .into_iter() + .zip(iter::once(node).cycle()) + .collect::>(), + ) }) .flatten() .unique_by(|&(def, _)| def) diff --git a/crates/ide/src/hover/render.rs b/crates/ide/src/hover/render.rs index ef33386a7e912..a33a6ee1816cc 100644 --- a/crates/ide/src/hover/render.rs +++ b/crates/ide/src/hover/render.rs @@ -257,7 +257,7 @@ pub(super) fn keyword( let KeywordHint { description, keyword_mod, actions } = keyword_hints(sema, token, parent); let doc_owner = find_std_module(&famous_defs, &keyword_mod)?; - let docs = doc_owner.attrs(sema.db).docs()?; + let docs = doc_owner.docs(sema.db)?; let markup = process_markup( sema.db, Definition::Module(doc_owner), @@ -472,6 +472,7 @@ pub(super) fn definition( } Definition::GenericParam(it) => label_and_docs(db, it), Definition::Label(it) => return Some(Markup::fenced_block(&it.name(db).display(db))), + Definition::ExternCrateDecl(it) => label_and_docs(db, it), // FIXME: We should be able to show more info about these Definition::BuiltinAttr(it) => return render_builtin_attr(db, it), Definition::ToolModule(it) => return Some(Markup::fenced_block(&it.name(db))), @@ -620,7 +621,7 @@ where D: HasAttrs + HirDisplay, { let label = def.display(db).to_string(); - let docs = def.attrs(db).docs(); + let docs = def.docs(db); (label, docs) } @@ -645,7 +646,7 @@ where ) { format_to!(label, "{layout}"); } - let docs = def.attrs(db).docs(); + let docs = def.docs(db); (label, docs) } @@ -677,7 +678,7 @@ where ) { format_to!(label, "{layout}"); } - let docs = def.attrs(db).docs(); + let docs = def.docs(db); (label, docs) } @@ -696,7 +697,7 @@ where } else { def.display(db).to_string() }; - let docs = def.attrs(db).docs(); + let docs = def.docs(db); (label, docs) } @@ -727,14 +728,14 @@ fn builtin(famous_defs: &FamousDefs<'_, '_>, builtin: hir::BuiltinType) -> Optio // std exposes prim_{} modules with docstrings on the root to document the builtins let primitive_mod = format!("prim_{}", builtin.name().display(famous_defs.0.db)); let doc_owner = find_std_module(famous_defs, &primitive_mod)?; - let docs = doc_owner.attrs(famous_defs.0.db).docs()?; + let docs = doc_owner.docs(famous_defs.0.db)?; markup(Some(docs.into()), builtin.name().display(famous_defs.0.db).to_string(), None) } fn find_std_module(famous_defs: &FamousDefs<'_, '_>, name: &str) -> Option { let db = famous_defs.0.db; let std_crate = famous_defs.std()?; - let std_root_module = std_crate.root_module(db); + let std_root_module = std_crate.root_module(); std_root_module.children(db).find(|module| { module.name(db).map_or(false, |module| module.display(db).to_string() == name) }) diff --git a/crates/ide/src/hover/tests.rs b/crates/ide/src/hover/tests.rs index 00e21433daaec..ddc71dffa8a64 100644 --- a/crates/ide/src/hover/tests.rs +++ b/crates/ide/src/hover/tests.rs @@ -1616,6 +1616,9 @@ fn test_hover_extern_crate() { check( r#" //- /main.rs crate:main deps:std +//! Crate docs + +/// Decl docs! extern crate st$0d; //- /std/lib.rs crate:std //! Standard library for this test @@ -1624,23 +1627,32 @@ extern crate st$0d; //! abc123 "#, expect![[r#" - *std* + *std* - ```rust - extern crate std - ``` + ```rust + main + ``` - --- + ```rust + extern crate std + ``` + + --- - Standard library for this test + Decl docs! - Printed? - abc123 - "#]], + Standard library for this test + + Printed? + abc123 + "#]], ); check( r#" //- /main.rs crate:main deps:std +//! Crate docs + +/// Decl docs! extern crate std as ab$0c; //- /std/lib.rs crate:std //! Standard library for this test @@ -1649,19 +1661,25 @@ extern crate std as ab$0c; //! abc123 "#, expect![[r#" - *abc* + *abc* - ```rust - extern crate std - ``` + ```rust + main + ``` - --- + ```rust + extern crate std as abc + ``` - Standard library for this test + --- - Printed? - abc123 - "#]], + Decl docs! + + Standard library for this test + + Printed? + abc123 + "#]], ); } diff --git a/crates/ide/src/moniker.rs b/crates/ide/src/moniker.rs index d486a794e13b6..de8c5b8e83035 100644 --- a/crates/ide/src/moniker.rs +++ b/crates/ide/src/moniker.rs @@ -247,6 +247,10 @@ pub(crate) fn def_to_moniker( name: s.name(db).display(db).to_string(), desc: MonikerDescriptorKind::Meta, }, + Definition::ExternCrateDecl(m) => MonikerDescriptor { + name: m.name(db).display(db).to_string(), + desc: MonikerDescriptorKind::Namespace, + }, }; description.push(name_desc); diff --git a/crates/ide/src/navigation_target.rs b/crates/ide/src/navigation_target.rs index c7abecb4f1e34..d1479dd1e5848 100644 --- a/crates/ide/src/navigation_target.rs +++ b/crates/ide/src/navigation_target.rs @@ -102,7 +102,7 @@ impl NavigationTarget { full_range, SymbolKind::Module, ); - res.docs = module.attrs(db).docs(); + res.docs = module.docs(db); res.description = Some(module.display(db).to_string()); return res; } @@ -217,6 +217,7 @@ impl TryToNav for Definition { Definition::Trait(it) => it.try_to_nav(db), Definition::TraitAlias(it) => it.try_to_nav(db), Definition::TypeAlias(it) => it.try_to_nav(db), + Definition::ExternCrateDecl(it) => Some(it.try_to_nav(db)?), Definition::BuiltinType(_) => None, Definition::ToolModule(_) => None, Definition::BuiltinAttr(_) => None, @@ -375,6 +376,30 @@ impl TryToNav for hir::Impl { } } +impl TryToNav for hir::ExternCrateDecl { + fn try_to_nav(&self, db: &RootDatabase) -> Option { + let src = self.source(db)?; + let InFile { file_id, value } = src; + let focus = value + .rename() + .map_or_else(|| value.name_ref().map(Either::Left), |it| it.name().map(Either::Right)); + let (file_id, full_range, focus_range) = + orig_range_with_focus(db, file_id, value.syntax(), focus); + let mut res = NavigationTarget::from_syntax( + file_id, + self.alias_or_name(db).unwrap_or_else(|| self.name(db)).to_smol_str(), + focus_range, + full_range, + SymbolKind::Module, + ); + + res.docs = self.docs(db); + res.description = Some(self.display(db).to_string()); + res.container_name = container_name(db, *self); + Some(res) + } +} + impl TryToNav for hir::Field { fn try_to_nav(&self, db: &RootDatabase) -> Option { let src = self.source(db)?; diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs index 44073fa757f1d..813f9ed943f26 100644 --- a/crates/ide/src/references.rs +++ b/crates/ide/src/references.rs @@ -137,6 +137,9 @@ pub(crate) fn find_defs<'a>( NameRefClass::FieldShorthand { local_ref, field_ref: _ } => { Definition::Local(local_ref) } + NameRefClass::ExternCrateShorthand { decl, .. } => { + Definition::ExternCrateDecl(decl) + } } } ast::NameLike::Name(name) => match NameClass::classify(sema, &name)? { diff --git a/crates/ide/src/rename.rs b/crates/ide/src/rename.rs index 5c4beb7dd500f..dae8e71e8a09e 100644 --- a/crates/ide/src/rename.rs +++ b/crates/ide/src/rename.rs @@ -145,7 +145,14 @@ fn find_definitions( if name .syntax() .parent() - .map_or(false, |it| ast::Rename::can_cast(it.kind())) => + .map_or(false, |it| ast::Rename::can_cast(it.kind())) + // FIXME: uncomment this once we resolve to usages to extern crate declarations + // && name + // .syntax() + // .ancestors() + // .nth(2) + // .map_or(true, |it| !ast::ExternCrate::can_cast(it.kind())) + => { bail!("Renaming aliases is currently unsupported") } @@ -165,7 +172,12 @@ fn find_definitions( NameRefClass::FieldShorthand { local_ref, field_ref: _ } => { Definition::Local(local_ref) } + NameRefClass::ExternCrateShorthand { decl, .. } => { + Definition::ExternCrateDecl(decl) + } }) + // FIXME: uncomment this once we resolve to usages to extern crate declarations + .filter(|def| !matches!(def, Definition::ExternCrateDecl(..))) .ok_or_else(|| format_err!("No references found at position")) .and_then(|def| { // if the name differs from the definitions name it has to be an alias @@ -2517,4 +2529,109 @@ fn main() { ", ) } + + #[test] + fn extern_crate() { + check_prepare( + r" +//- /lib.rs crate:main deps:foo +extern crate foo$0; +use foo as qux; +//- /foo.rs crate:foo +", + expect![[r#"No references found at position"#]], + ); + // FIXME: replace above check_prepare with this once we resolve to usages to extern crate declarations + // check( + // "bar", + // r" + // //- /lib.rs crate:main deps:foo + // extern crate foo$0; + // use foo as qux; + // //- /foo.rs crate:foo + // ", + // r" + // extern crate foo as bar; + // use bar as qux; + // ", + // ); + } + + #[test] + fn extern_crate_rename() { + check_prepare( + r" +//- /lib.rs crate:main deps:foo +extern crate foo as qux$0; +use qux as frob; +//- /foo.rs crate:foo +", + expect!["Renaming aliases is currently unsupported"], + ); + // FIXME: replace above check_prepare with this once we resolve to usages to extern crate + // declarations + // check( + // "bar", + // r" + // //- /lib.rs crate:main deps:foo + // extern crate foo as qux$0; + // use qux as frob; + // //- /foo.rs crate:foo + // ", + // r" + // extern crate foo as bar; + // use bar as frob; + // ", + // ); + } + + #[test] + fn extern_crate_self() { + check_prepare( + r" +extern crate self$0; +use self as qux; +", + expect!["No references found at position"], + ); + // FIXME: replace above check_prepare with this once we resolve to usages to extern crate declarations + // check( + // "bar", + // r" + // extern crate self$0; + // use self as qux; + // ", + // r" + // extern crate self as bar; + // use self as qux; + // ", + // ); + } + + #[test] + fn extern_crate_self_rename() { + check_prepare( + r" +//- /lib.rs crate:main deps:foo +extern crate self as qux$0; +use qux as frob; +//- /foo.rs crate:foo +", + expect!["Renaming aliases is currently unsupported"], + ); + // FIXME: replace above check_prepare with this once we resolve to usages to extern crate declarations + // check( + // "bar", + // r" + // //- /lib.rs crate:main deps:foo + // extern crate self as qux$0; + // use qux as frob; + // //- /foo.rs crate:foo + // ", + // r" + // extern crate self as bar; + // use bar as frob; + // ", + // ); + } } diff --git a/crates/ide/src/runnables.rs b/crates/ide/src/runnables.rs index 8c445ddcb226d..5f87a78551d2d 100644 --- a/crates/ide/src/runnables.rs +++ b/crates/ide/src/runnables.rs @@ -309,7 +309,7 @@ pub(crate) fn runnable_fn( ) -> Option { let name = def.name(sema.db).to_smol_str(); - let root = def.module(sema.db).krate().root_module(sema.db); + let root = def.module(sema.db).krate().root_module(); let kind = if name == "main" && def.module(sema.db) == root { RunnableKind::Bin diff --git a/crates/ide/src/static_index.rs b/crates/ide/src/static_index.rs index 59e8300dcdbe8..d8696198d3bab 100644 --- a/crates/ide/src/static_index.rs +++ b/crates/ide/src/static_index.rs @@ -88,7 +88,7 @@ pub struct StaticIndexedFile { fn all_modules(db: &dyn HirDatabase) -> Vec { let mut worklist: Vec<_> = - Crate::all(db).into_iter().map(|krate| krate.root_module(db)).collect(); + Crate::all(db).into_iter().map(|krate| krate.root_module()).collect(); let mut modules = Vec::new(); while let Some(module) = worklist.pop() { diff --git a/crates/ide/src/syntax_highlighting/highlight.rs b/crates/ide/src/syntax_highlighting/highlight.rs index 3c40246a69d37..8e96bfa01ada5 100644 --- a/crates/ide/src/syntax_highlighting/highlight.rs +++ b/crates/ide/src/syntax_highlighting/highlight.rs @@ -269,7 +269,26 @@ fn highlight_name_ref( h } - NameRefClass::FieldShorthand { .. } => SymbolKind::Field.into(), + NameRefClass::FieldShorthand { field_ref, .. } => { + highlight_def(sema, krate, field_ref.into()) + } + NameRefClass::ExternCrateShorthand { decl, krate: resolved_krate } => { + let mut h = HlTag::Symbol(SymbolKind::Module).into(); + + if resolved_krate != krate { + h |= HlMod::Library + } + let is_public = decl.visibility(db) == hir::Visibility::Public; + if is_public { + h |= HlMod::Public + } + let is_from_builtin_crate = resolved_krate.is_builtin(db); + if is_from_builtin_crate { + h |= HlMod::DefaultLibrary; + } + h |= HlMod::CrateRoot; + h + } }; h.tag = match name_ref.token_kind() { @@ -474,6 +493,14 @@ fn highlight_def( } h } + Definition::ExternCrateDecl(extern_crate) => { + let mut highlight = + Highlight::new(HlTag::Symbol(SymbolKind::Module)) | HlMod::CrateRoot; + if extern_crate.alias(db).is_none() { + highlight |= HlMod::Library; + } + highlight + } Definition::Label(_) => Highlight::new(HlTag::Symbol(SymbolKind::Label)), Definition::BuiltinAttr(_) => Highlight::new(HlTag::Symbol(SymbolKind::BuiltinAttr)), Definition::ToolModule(_) => Highlight::new(HlTag::Symbol(SymbolKind::ToolModule)), diff --git a/crates/ide/src/syntax_highlighting/inject.rs b/crates/ide/src/syntax_highlighting/inject.rs index 901df147d32c0..2657a641482a9 100644 --- a/crates/ide/src/syntax_highlighting/inject.rs +++ b/crates/ide/src/syntax_highlighting/inject.rs @@ -288,7 +288,7 @@ fn find_doc_string_in_attr(attr: &hir::Attr, it: &ast::Attr) -> Option HlTag { let symbol = match def { - Definition::Module(_) => SymbolKind::Module, + Definition::Module(_) | Definition::ExternCrateDecl(_) => SymbolKind::Module, Definition::Function(_) => SymbolKind::Function, Definition::Adt(hir::Adt::Struct(_)) => SymbolKind::Struct, Definition::Adt(hir::Adt::Enum(_)) => SymbolKind::Enum, diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_extern_crate.html b/crates/ide/src/syntax_highlighting/test_data/highlight_extern_crate.html index b15f7bca72b98..cc9640b0b53cf 100644 --- a/crates/ide/src/syntax_highlighting/test_data/highlight_extern_crate.html +++ b/crates/ide/src/syntax_highlighting/test_data/highlight_extern_crate.html @@ -44,5 +44,5 @@ .unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
extern crate std;
-extern crate alloc as abc;
+extern crate alloc as abc;
 
\ No newline at end of file diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_keywords.html b/crates/ide/src/syntax_highlighting/test_data/highlight_keywords.html index fd3b39855e201..2043752bc746d 100644 --- a/crates/ide/src/syntax_highlighting/test_data/highlight_keywords.html +++ b/crates/ide/src/syntax_highlighting/test_data/highlight_keywords.html @@ -43,7 +43,7 @@ .invalid_escape_sequence { color: #FC5555; text-decoration: wavy underline; } .unresolved_reference { color: #FC5555; text-decoration: wavy underline; } -
extern crate self;
+
extern crate self;
 
 use crate;
 use self;
diff --git a/crates/parser/src/shortcuts.rs b/crates/parser/src/shortcuts.rs
index 6e3ae656b0257..53cdad64992cb 100644
--- a/crates/parser/src/shortcuts.rs
+++ b/crates/parser/src/shortcuts.rs
@@ -223,7 +223,8 @@ fn n_attached_trivias<'a>(
 ) -> usize {
     match kind {
         CONST | ENUM | FN | IMPL | MACRO_CALL | MACRO_DEF | MACRO_RULES | MODULE | RECORD_FIELD
-        | STATIC | STRUCT | TRAIT | TUPLE_FIELD | TYPE_ALIAS | UNION | USE | VARIANT => {
+        | STATIC | STRUCT | TRAIT | TUPLE_FIELD | TYPE_ALIAS | UNION | USE | VARIANT
+        | EXTERN_CRATE => {
             let mut res = 0;
             let mut trivias = trivias.enumerate().peekable();
 
diff --git a/crates/rust-analyzer/src/cli/analysis_stats.rs b/crates/rust-analyzer/src/cli/analysis_stats.rs
index 33d7b5ed8789f..f446a7c059665 100644
--- a/crates/rust-analyzer/src/cli/analysis_stats.rs
+++ b/crates/rust-analyzer/src/cli/analysis_stats.rs
@@ -128,7 +128,7 @@ impl flags::AnalysisStats {
         let mut visited_modules = FxHashSet::default();
         let mut visit_queue = Vec::new();
         for krate in krates {
-            let module = krate.root_module(db);
+            let module = krate.root_module();
             let file_id = module.definition_source_file_id(db);
             let file_id = file_id.original_file(db);
             let source_root = db.file_source_root(file_id);
diff --git a/crates/rust-analyzer/src/cli/diagnostics.rs b/crates/rust-analyzer/src/cli/diagnostics.rs
index 0db5fb4740ea6..8541be715a938 100644
--- a/crates/rust-analyzer/src/cli/diagnostics.rs
+++ b/crates/rust-analyzer/src/cli/diagnostics.rs
@@ -80,7 +80,7 @@ impl flags::Diagnostics {
 
 fn all_modules(db: &dyn HirDatabase) -> Vec {
     let mut worklist: Vec<_> =
-        Crate::all(db).into_iter().map(|krate| krate.root_module(db)).collect();
+        Crate::all(db).into_iter().map(|krate| krate.root_module()).collect();
     let mut modules = Vec::new();
 
     while let Some(module) = worklist.pop() {
diff --git a/crates/rust-analyzer/src/cli/run_tests.rs b/crates/rust-analyzer/src/cli/run_tests.rs
index b63a266a57ae2..e1704199151a6 100644
--- a/crates/rust-analyzer/src/cli/run_tests.rs
+++ b/crates/rust-analyzer/src/cli/run_tests.rs
@@ -76,7 +76,7 @@ fn all_modules(db: &dyn HirDatabase) -> Vec {
     let mut worklist: Vec<_> = Crate::all(db)
         .into_iter()
         .filter(|x| x.origin(db).is_local())
-        .map(|krate| krate.root_module(db))
+        .map(|krate| krate.root_module())
         .collect();
     let mut modules = Vec::new();
 

From 6e2c3f610b6ed6e0c26aacf967117cbaf06c3754 Mon Sep 17 00:00:00 2001
From: Lukas Wirth 
Date: Wed, 2 Aug 2023 12:18:10 +0200
Subject: [PATCH 48/59] Remove suspicious unwrap

---
 crates/hir-def/src/data.rs                               | 9 +++------
 crates/hir/src/attrs.rs                                  | 2 +-
 crates/hir/src/lib.rs                                    | 4 ++--
 crates/ide-db/src/defs.rs                                | 2 +-
 crates/ide/src/goto_definition.rs                        | 7 ++++---
 .../test_data/highlight_extern_crate.html                | 1 +
 crates/ide/src/syntax_highlighting/tests.rs              | 1 +
 7 files changed, 13 insertions(+), 13 deletions(-)

diff --git a/crates/hir-def/src/data.rs b/crates/hir-def/src/data.rs
index 4fb4794d4a679..91db68058b02e 100644
--- a/crates/hir-def/src/data.rs
+++ b/crates/hir-def/src/data.rs
@@ -468,7 +468,7 @@ pub struct ExternCrateDeclData {
     pub name: Name,
     pub alias: Option,
     pub visibility: RawVisibility,
-    pub crate_id: CrateId,
+    pub crate_id: Option,
 }
 
 impl ExternCrateDeclData {
@@ -482,15 +482,12 @@ impl ExternCrateDeclData {
 
         let name = extern_crate.name.clone();
         let crate_id = if name == hir_expand::name![self] {
-            loc.container.krate()
+            Some(loc.container.krate())
         } else {
             db.crate_def_map(loc.container.krate())
                 .extern_prelude()
                 .find(|&(prelude_name, ..)| *prelude_name == name)
-                // FIXME: Suspicious unwrap
-                .unwrap()
-                .1
-                .krate()
+                .map(|(_, root)| root.krate())
         };
 
         Arc::new(Self {
diff --git a/crates/hir/src/attrs.rs b/crates/hir/src/attrs.rs
index b4944379dcb60..3ed03fe7c0775 100644
--- a/crates/hir/src/attrs.rs
+++ b/crates/hir/src/attrs.rs
@@ -126,7 +126,7 @@ impl HasAttrs for ExternCrateDecl {
         db.attrs_with_owner(def)
     }
     fn docs(self, db: &dyn HirDatabase) -> Option {
-        let crate_docs = self.resolved_crate(db).root_module().attrs(db).docs().map(String::from);
+        let crate_docs = self.resolved_crate(db)?.root_module().attrs(db).docs().map(String::from);
         let def = AttrDefId::ExternCrateId(self.into());
         let decl_docs = db.attrs(def).docs().map(String::from);
         match (decl_docs, crate_docs) {
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index 1a9d3ac448e66..bf041b61f2fbe 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -2138,8 +2138,8 @@ impl ExternCrateDecl {
         self.id.module(db.upcast()).into()
     }
 
-    pub fn resolved_crate(self, db: &dyn HirDatabase) -> Crate {
-        db.extern_crate_decl_data(self.id).crate_id.into()
+    pub fn resolved_crate(self, db: &dyn HirDatabase) -> Option {
+        db.extern_crate_decl_data(self.id).crate_id.map(Into::into)
     }
 
     pub fn name(self, db: &dyn HirDatabase) -> Name {
diff --git a/crates/ide-db/src/defs.rs b/crates/ide-db/src/defs.rs
index 4417a61b86654..5e4562d9c583d 100644
--- a/crates/ide-db/src/defs.rs
+++ b/crates/ide-db/src/defs.rs
@@ -538,7 +538,7 @@ impl NameRefClass {
                 },
                 ast::ExternCrate(extern_crate_ast) => {
                     let extern_crate = sema.to_def(&extern_crate_ast)?;
-                    let krate = extern_crate.resolved_crate(sema.db);
+                    let krate = extern_crate.resolved_crate(sema.db)?;
                     Some(if extern_crate_ast.rename().is_some() {
                         NameRefClass::Definition(Definition::Module(krate.root_module()))
                     } else {
diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs
index 10a26befa466f..21471ab2a03d6 100644
--- a/crates/ide/src/goto_definition.rs
+++ b/crates/ide/src/goto_definition.rs
@@ -77,10 +77,11 @@ pub(crate) fn goto_definition(
                     .into_iter()
                     .flat_map(|def| {
                         if let Definition::ExternCrateDecl(crate_def) = def {
-                            return vec![crate_def
+                            return crate_def
                                 .resolved_crate(db)
-                                .root_module()
-                                .to_nav(sema.db)];
+                                .map(|it| it.root_module().to_nav(sema.db))
+                                .into_iter()
+                                .collect();
                         }
                         try_filter_trait_item_definition(sema, &def)
                             .unwrap_or_else(|| def_to_nav(sema.db, def))
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_extern_crate.html b/crates/ide/src/syntax_highlighting/test_data/highlight_extern_crate.html
index cc9640b0b53cf..88a008796b370 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_extern_crate.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_extern_crate.html
@@ -45,4 +45,5 @@
 
 
extern crate std;
 extern crate alloc as abc;
+extern crate unresolved as definitely_unresolved;
 
\ No newline at end of file diff --git a/crates/ide/src/syntax_highlighting/tests.rs b/crates/ide/src/syntax_highlighting/tests.rs index 8747071807067..8749d355c85d7 100644 --- a/crates/ide/src/syntax_highlighting/tests.rs +++ b/crates/ide/src/syntax_highlighting/tests.rs @@ -804,6 +804,7 @@ fn test_extern_crate() { //- /main.rs crate:main deps:std,alloc extern crate std; extern crate alloc as abc; +extern crate unresolved as definitely_unresolved; //- /std/lib.rs crate:std pub struct S; //- /alloc/lib.rs crate:alloc From f86f6a89ebb4f75efcf7106972b2feb705abfb75 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 2 Aug 2023 14:19:38 +0200 Subject: [PATCH 49/59] Change terminology, do not name use items and use trees as imports --- crates/hir-def/src/db.rs | 10 ++++----- crates/hir-def/src/item_tree.rs | 16 +++++++------- crates/hir-def/src/item_tree/lower.rs | 6 +++--- crates/hir-def/src/item_tree/pretty.rs | 4 ++-- crates/hir-def/src/lib.rs | 10 ++++----- crates/hir-def/src/nameres/collector.rs | 26 +++++++++++------------ crates/hir-def/src/nameres/diagnostics.rs | 4 ++-- 7 files changed, 37 insertions(+), 39 deletions(-) diff --git a/crates/hir-def/src/db.rs b/crates/hir-def/src/db.rs index 82e6dfb30c81f..e34a6768f2862 100644 --- a/crates/hir-def/src/db.rs +++ b/crates/hir-def/src/db.rs @@ -23,17 +23,17 @@ use crate::{ visibility::{self, Visibility}, AttrDefId, BlockId, BlockLoc, ConstBlockId, ConstBlockLoc, ConstId, ConstLoc, DefWithBodyId, EnumId, EnumLoc, ExternBlockId, ExternBlockLoc, ExternCrateId, ExternCrateLoc, FunctionId, - FunctionLoc, GenericDefId, ImplId, ImplLoc, ImportId, ImportLoc, InTypeConstId, InTypeConstLoc, - LocalEnumVariantId, LocalFieldId, Macro2Id, Macro2Loc, MacroRulesId, MacroRulesLoc, - ProcMacroId, ProcMacroLoc, StaticId, StaticLoc, StructId, StructLoc, TraitAliasId, - TraitAliasLoc, TraitId, TraitLoc, TypeAliasId, TypeAliasLoc, UnionId, UnionLoc, VariantId, + FunctionLoc, GenericDefId, ImplId, ImplLoc, InTypeConstId, InTypeConstLoc, LocalEnumVariantId, + LocalFieldId, Macro2Id, Macro2Loc, MacroRulesId, MacroRulesLoc, ProcMacroId, ProcMacroLoc, + StaticId, StaticLoc, StructId, StructLoc, TraitAliasId, TraitAliasLoc, TraitId, TraitLoc, + TypeAliasId, TypeAliasLoc, UnionId, UnionLoc, UseId, UseLoc, VariantId, }; #[salsa::query_group(InternDatabaseStorage)] pub trait InternDatabase: SourceDatabase { // region: items #[salsa::interned] - fn intern_import(&self, loc: ImportLoc) -> ImportId; + fn intern_use(&self, loc: UseLoc) -> UseId; #[salsa::interned] fn intern_extern_crate(&self, loc: ExternCrateLoc) -> ExternCrateId; #[salsa::interned] diff --git a/crates/hir-def/src/item_tree.rs b/crates/hir-def/src/item_tree.rs index 6f80bb6e07ce3..c9b0f75f1a8aa 100644 --- a/crates/hir-def/src/item_tree.rs +++ b/crates/hir-def/src/item_tree.rs @@ -188,7 +188,7 @@ impl ItemTree { fn shrink_to_fit(&mut self) { if let Some(data) = &mut self.data { let ItemTreeData { - imports, + uses, extern_crates, extern_blocks, functions, @@ -211,7 +211,7 @@ impl ItemTree { vis, } = &mut **data; - imports.shrink_to_fit(); + uses.shrink_to_fit(); extern_crates.shrink_to_fit(); extern_blocks.shrink_to_fit(); functions.shrink_to_fit(); @@ -262,7 +262,7 @@ static VIS_PUB_CRATE: RawVisibility = RawVisibility::Module(ModPath::from_kind(P #[derive(Default, Debug, Eq, PartialEq)] struct ItemTreeData { - imports: Arena, + uses: Arena, extern_crates: Arena, extern_blocks: Arena, functions: Arena, @@ -486,7 +486,7 @@ macro_rules! mod_items { } mod_items! { - Import in imports -> ast::Use, + Use in uses -> ast::Use, ExternCrate in extern_crates -> ast::ExternCrate, ExternBlock in extern_blocks -> ast::ExternBlock, Function in functions -> ast::Fn, @@ -541,7 +541,7 @@ impl Index> for ItemTree { } #[derive(Debug, Clone, Eq, PartialEq)] -pub struct Import { +pub struct Use { pub visibility: RawVisibilityId, pub ast_id: FileAstId, pub use_tree: UseTree, @@ -744,7 +744,7 @@ pub struct MacroDef { pub ast_id: FileAstId, } -impl Import { +impl Use { /// Maps a `UseTree` contained in this import back to its AST node. pub fn use_tree_to_ast( &self, @@ -870,7 +870,7 @@ macro_rules! impl_froms { impl ModItem { pub fn as_assoc_item(&self) -> Option { match self { - ModItem::Import(_) + ModItem::Use(_) | ModItem::ExternCrate(_) | ModItem::ExternBlock(_) | ModItem::Struct(_) @@ -892,7 +892,7 @@ impl ModItem { pub fn ast_id(&self, tree: &ItemTree) -> FileAstId { match self { - ModItem::Import(it) => tree[it.index].ast_id().upcast(), + ModItem::Use(it) => tree[it.index].ast_id().upcast(), ModItem::ExternCrate(it) => tree[it.index].ast_id().upcast(), ModItem::ExternBlock(it) => tree[it.index].ast_id().upcast(), ModItem::Function(it) => tree[it.index].ast_id().upcast(), diff --git a/crates/hir-def/src/item_tree/lower.rs b/crates/hir-def/src/item_tree/lower.rs index 46633667ed3e2..7b898e62dbae4 100644 --- a/crates/hir-def/src/item_tree/lower.rs +++ b/crates/hir-def/src/item_tree/lower.rs @@ -502,13 +502,13 @@ impl<'a> Ctx<'a> { Some(id(self.data().impls.alloc(res))) } - fn lower_use(&mut self, use_item: &ast::Use) -> Option> { + fn lower_use(&mut self, use_item: &ast::Use) -> Option> { let visibility = self.lower_visibility(use_item); let ast_id = self.source_ast_id_map.ast_id(use_item); let (use_tree, _) = lower_use_tree(self.db, self.hygiene(), use_item.use_tree()?)?; - let res = Import { visibility, ast_id, use_tree }; - Some(id(self.data().imports.alloc(res))) + let res = Use { visibility, ast_id, use_tree }; + Some(id(self.data().uses.alloc(res))) } fn lower_extern_crate( diff --git a/crates/hir-def/src/item_tree/pretty.rs b/crates/hir-def/src/item_tree/pretty.rs index ddf668d20b052..da30830fe4522 100644 --- a/crates/hir-def/src/item_tree/pretty.rs +++ b/crates/hir-def/src/item_tree/pretty.rs @@ -198,8 +198,8 @@ impl Printer<'_> { self.print_attrs_of(item); match item { - ModItem::Import(it) => { - let Import { visibility, use_tree, ast_id: _ } = &self.tree[it]; + ModItem::Use(it) => { + let Use { visibility, use_tree, ast_id: _ } = &self.tree[it]; self.print_visibility(*visibility); w!(self, "use "); self.print_use_tree(use_tree); diff --git a/crates/hir-def/src/lib.rs b/crates/hir-def/src/lib.rs index 67fd5a3635d08..2f14378f67adc 100644 --- a/crates/hir-def/src/lib.rs +++ b/crates/hir-def/src/lib.rs @@ -88,8 +88,8 @@ use crate::{ builtin_type::BuiltinType, data::adt::VariantData, item_tree::{ - Const, Enum, ExternCrate, Function, Impl, Import, ItemTreeId, ItemTreeNode, MacroDef, - MacroRules, Static, Struct, Trait, TraitAlias, TypeAlias, Union, + Const, Enum, ExternCrate, Function, Impl, ItemTreeId, ItemTreeNode, MacroDef, MacroRules, + Static, Struct, Trait, TraitAlias, TypeAlias, Union, Use, }, }; @@ -324,9 +324,9 @@ type ImplLoc = ItemLoc; impl_intern!(ImplId, ImplLoc, intern_impl, lookup_intern_impl); #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)] -pub struct ImportId(salsa::InternId); -type ImportLoc = ItemLoc; -impl_intern!(ImportId, ImportLoc, intern_import, lookup_intern_import); +pub struct UseId(salsa::InternId); +type UseLoc = ItemLoc; +impl_intern!(UseId, UseLoc, intern_use, lookup_intern_use); #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)] pub struct ExternCrateId(salsa::InternId); diff --git a/crates/hir-def/src/nameres/collector.rs b/crates/hir-def/src/nameres/collector.rs index 61915504b6c52..eef54fc492e92 100644 --- a/crates/hir-def/src/nameres/collector.rs +++ b/crates/hir-def/src/nameres/collector.rs @@ -52,10 +52,10 @@ use crate::{ tt, visibility::{RawVisibility, Visibility}, AdtId, AstId, AstIdWithPath, ConstLoc, CrateRootModuleId, EnumLoc, EnumVariantId, - ExternBlockLoc, ExternCrateLoc, FunctionId, FunctionLoc, ImplLoc, ImportLoc, Intern, - ItemContainerId, LocalModuleId, Macro2Id, Macro2Loc, MacroExpander, MacroId, MacroRulesId, - MacroRulesLoc, ModuleDefId, ModuleId, ProcMacroId, ProcMacroLoc, StaticLoc, StructLoc, - TraitAliasLoc, TraitLoc, TypeAliasLoc, UnionLoc, UnresolvedMacro, + ExternBlockLoc, ExternCrateLoc, FunctionId, FunctionLoc, ImplLoc, Intern, ItemContainerId, + LocalModuleId, Macro2Id, Macro2Loc, MacroExpander, MacroId, MacroRulesId, MacroRulesLoc, + ModuleDefId, ModuleId, ProcMacroId, ProcMacroLoc, StaticLoc, StructLoc, TraitAliasLoc, + TraitLoc, TypeAliasLoc, UnionLoc, UnresolvedMacro, UseLoc, }; static GLOB_RECURSION_LIMIT: Limit = Limit::new(100); @@ -146,7 +146,7 @@ impl PartialResolvedImport { #[derive(Clone, Debug, Eq, PartialEq)] enum ImportSource { - Import { id: ItemTreeId, use_tree: Idx }, + Use { id: ItemTreeId, use_tree: Idx }, ExternCrate(ItemTreeId), } @@ -166,7 +166,7 @@ impl Import { db: &dyn DefDatabase, krate: CrateId, tree: &ItemTree, - id: ItemTreeId, + id: ItemTreeId, mut cb: impl FnMut(Self), ) { let it = &tree[id.value]; @@ -181,7 +181,7 @@ impl Import { kind, is_prelude, is_macro_use: false, - source: ImportSource::Import { id, use_tree: idx }, + source: ImportSource::Use { id, use_tree: idx }, }); }); } @@ -1474,7 +1474,7 @@ impl DefCollector<'_> { } for directive in &self.unresolved_imports { - if let ImportSource::Import { id: import, use_tree } = directive.import.source { + if let ImportSource::Use { id: import, use_tree } = directive.import.source { if matches!( (directive.import.path.segments().first(), &directive.import.path.kind), (Some(krate), PathKind::Plain | PathKind::Abs) if diagnosed_extern_crates.contains(krate) @@ -1576,12 +1576,10 @@ impl ModCollector<'_, '_> { match item { ModItem::Mod(m) => self.collect_module(m, &attrs), - ModItem::Import(import_id) => { - let _import_id = ImportLoc { - container: module, - id: ItemTreeId::new(self.tree_id, import_id), - } - .intern(db); + ModItem::Use(import_id) => { + let _import_id = + UseLoc { container: module, id: ItemTreeId::new(self.tree_id, import_id) } + .intern(db); Import::from_use( db, krate, diff --git a/crates/hir-def/src/nameres/diagnostics.rs b/crates/hir-def/src/nameres/diagnostics.rs index e82e97b628e9c..9cffb3c9f37fe 100644 --- a/crates/hir-def/src/nameres/diagnostics.rs +++ b/crates/hir-def/src/nameres/diagnostics.rs @@ -19,7 +19,7 @@ pub enum DefDiagnosticKind { UnresolvedExternCrate { ast: AstId }, - UnresolvedImport { id: ItemTreeId, index: Idx }, + UnresolvedImport { id: ItemTreeId, index: Idx }, UnconfiguredCode { ast: ErasedAstId, cfg: CfgExpr, opts: CfgOptions }, @@ -70,7 +70,7 @@ impl DefDiagnostic { pub(super) fn unresolved_import( container: LocalModuleId, - id: ItemTreeId, + id: ItemTreeId, index: Idx, ) -> Self { Self { in_module: container, kind: DefDiagnosticKind::UnresolvedImport { id, index } } From ecb6d07d572ab2f0696afb596ac8b2c4ac19e57a Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 2 Aug 2023 14:53:45 +0200 Subject: [PATCH 50/59] Add currently unused UseId variants --- crates/hir-def/src/attr.rs | 2 ++ crates/hir-def/src/child_by_source.rs | 9 ++++++++- crates/hir-def/src/dyn_map/keys.rs | 3 ++- crates/hir-def/src/item_scope.rs | 6 ++++++ crates/hir-def/src/lib.rs | 2 ++ crates/hir-def/src/resolver.rs | 8 +++++++- crates/hir-ty/src/diagnostics/decl_check.rs | 1 + crates/hir/src/attrs.rs | 1 + crates/hir/src/semantics/source_to_def.rs | 6 +++++- 9 files changed, 34 insertions(+), 4 deletions(-) diff --git a/crates/hir-def/src/attr.rs b/crates/hir-def/src/attr.rs index c29446d8235af..fae07111806c0 100644 --- a/crates/hir-def/src/attr.rs +++ b/crates/hir-def/src/attr.rs @@ -485,6 +485,7 @@ impl AttrsWithOwner { }, AttrDefId::ExternBlockId(it) => attrs_from_item_tree_loc(db, it), AttrDefId::ExternCrateId(it) => attrs_from_item_tree_loc(db, it), + AttrDefId::UseId(it) => attrs_from_item_tree_loc(db, it), }; let attrs = raw_attrs.filter(db.upcast(), def.krate(db)); @@ -570,6 +571,7 @@ impl AttrsWithOwner { }, AttrDefId::ExternBlockId(id) => any_has_attrs(db, id), AttrDefId::ExternCrateId(id) => any_has_attrs(db, id), + AttrDefId::UseId(id) => any_has_attrs(db, id), }; AttrSourceMap::new(owner.as_ref().map(|node| node as &dyn HasAttrs)) diff --git a/crates/hir-def/src/child_by_source.rs b/crates/hir-def/src/child_by_source.rs index 814257745d068..4cfd318a43377 100644 --- a/crates/hir-def/src/child_by_source.rs +++ b/crates/hir-def/src/child_by_source.rs @@ -15,7 +15,7 @@ use crate::{ nameres::DefMap, src::{HasChildSource, HasSource}, AdtId, AssocItemId, DefWithBodyId, EnumId, EnumVariantId, ExternCrateId, FieldId, ImplId, - Lookup, MacroId, ModuleDefId, ModuleId, TraitId, VariantId, + Lookup, MacroId, ModuleDefId, ModuleId, TraitId, UseId, VariantId, }; pub trait ChildBySource { @@ -92,6 +92,7 @@ impl ChildBySource for ItemScope { self.declarations().for_each(|item| add_module_def(db, res, file_id, item)); self.impls().for_each(|imp| add_impl(db, res, file_id, imp)); self.extern_crate_decls().for_each(|ext| add_extern_crate(db, res, file_id, ext)); + self.use_decls().for_each(|ext| add_use(db, res, file_id, ext)); self.unnamed_consts().for_each(|konst| { let loc = konst.lookup(db); if loc.id.file_id() == file_id { @@ -179,6 +180,12 @@ impl ChildBySource for ItemScope { map[keys::EXTERN_CRATE].insert(loc.source(db).value, ext) } } + fn add_use(db: &dyn DefDatabase, map: &mut DynMap, file_id: HirFileId, ext: UseId) { + let loc = ext.lookup(db); + if loc.id.file_id() == file_id { + map[keys::USE].insert(loc.source(db).value, ext) + } + } } } diff --git a/crates/hir-def/src/dyn_map/keys.rs b/crates/hir-def/src/dyn_map/keys.rs index 4197d01060823..d0f2bfab43223 100644 --- a/crates/hir-def/src/dyn_map/keys.rs +++ b/crates/hir-def/src/dyn_map/keys.rs @@ -10,7 +10,7 @@ use crate::{ dyn_map::{DynMap, Policy}, ConstId, EnumId, EnumVariantId, ExternCrateId, FieldId, FunctionId, ImplId, LifetimeParamId, Macro2Id, MacroRulesId, ProcMacroId, StaticId, StructId, TraitAliasId, TraitId, TypeAliasId, - TypeOrConstParamId, UnionId, + TypeOrConstParamId, UnionId, UseId, }; pub type Key = crate::dyn_map::Key>; @@ -26,6 +26,7 @@ pub const STRUCT: Key = Key::new(); pub const UNION: Key = Key::new(); pub const ENUM: Key = Key::new(); pub const EXTERN_CRATE: Key = Key::new(); +pub const USE: Key = Key::new(); pub const VARIANT: Key = Key::new(); pub const TUPLE_FIELD: Key = Key::new(); diff --git a/crates/hir-def/src/item_scope.rs b/crates/hir-def/src/item_scope.rs index 639b8215c84db..873accafb43d0 100644 --- a/crates/hir-def/src/item_scope.rs +++ b/crates/hir-def/src/item_scope.rs @@ -16,6 +16,7 @@ use syntax::ast; use crate::{ db::DefDatabase, per_ns::PerNs, visibility::Visibility, AdtId, BuiltinType, ConstId, ExternCrateId, HasModule, ImplId, LocalModuleId, MacroId, ModuleDefId, ModuleId, TraitId, + UseId, }; #[derive(Copy, Clone, Debug)] @@ -119,6 +120,11 @@ impl ItemScope { self.extern_crate_decls.iter().copied() } + pub fn use_decls(&self) -> impl Iterator + ExactSizeIterator + '_ { + // FIXME: to be implemented + std::iter::empty() + } + pub fn impls(&self) -> impl Iterator + ExactSizeIterator + '_ { self.impls.iter().copied() } diff --git a/crates/hir-def/src/lib.rs b/crates/hir-def/src/lib.rs index 2f14378f67adc..1901db8a0f9b0 100644 --- a/crates/hir-def/src/lib.rs +++ b/crates/hir-def/src/lib.rs @@ -842,6 +842,7 @@ pub enum AttrDefId { GenericParamId(GenericParamId), ExternBlockId(ExternBlockId), ExternCrateId(ExternCrateId), + UseId(UseId), } impl_from!( @@ -1079,6 +1080,7 @@ impl AttrDefId { } AttrDefId::MacroId(it) => it.module(db).krate, AttrDefId::ExternCrateId(it) => it.lookup(db).container.krate, + AttrDefId::UseId(it) => it.lookup(db).container.krate, } } } diff --git a/crates/hir-def/src/resolver.rs b/crates/hir-def/src/resolver.rs index 10f5702845e20..b112c1070d49e 100644 --- a/crates/hir-def/src/resolver.rs +++ b/crates/hir-def/src/resolver.rs @@ -25,7 +25,7 @@ use crate::{ EnumVariantId, ExternBlockId, ExternCrateId, FunctionId, GenericDefId, GenericParamId, HasModule, ImplId, ItemContainerId, LifetimeParamId, LocalModuleId, Lookup, Macro2Id, MacroId, MacroRulesId, ModuleDefId, ModuleId, ProcMacroId, StaticId, StructId, TraitAliasId, TraitId, - TypeAliasId, TypeOrConstParamId, TypeOwnerId, TypeParamId, VariantId, + TypeAliasId, TypeOrConstParamId, TypeOwnerId, TypeParamId, UseId, VariantId, }; #[derive(Debug, Clone)] @@ -1024,6 +1024,12 @@ impl HasResolver for ExternCrateId { } } +impl HasResolver for UseId { + fn resolver(self, db: &dyn DefDatabase) -> Resolver { + self.lookup(db).container.resolver(db) + } +} + impl HasResolver for TypeOwnerId { fn resolver(self, db: &dyn DefDatabase) -> Resolver { match self { diff --git a/crates/hir-ty/src/diagnostics/decl_check.rs b/crates/hir-ty/src/diagnostics/decl_check.rs index 5aaa2bcc7c2c3..a94a962c1f858 100644 --- a/crates/hir-ty/src/diagnostics/decl_check.rs +++ b/crates/hir-ty/src/diagnostics/decl_check.rs @@ -176,6 +176,7 @@ impl<'a> DeclValidator<'a> { AttrDefId::ImplId(iid) => Some(iid.lookup(self.db.upcast()).container.into()), AttrDefId::ExternBlockId(id) => Some(id.lookup(self.db.upcast()).container.into()), AttrDefId::ExternCrateId(id) => Some(id.lookup(self.db.upcast()).container.into()), + AttrDefId::UseId(id) => Some(id.lookup(self.db.upcast()).container.into()), // These warnings should not explore macro definitions at all AttrDefId::MacroId(_) => None, AttrDefId::AdtId(aid) => match aid { diff --git a/crates/hir/src/attrs.rs b/crates/hir/src/attrs.rs index 3ed03fe7c0775..0f2fb2c811812 100644 --- a/crates/hir/src/attrs.rs +++ b/crates/hir/src/attrs.rs @@ -173,6 +173,7 @@ fn resolve_doc_path( AttrDefId::TypeAliasId(it) => it.resolver(db.upcast()), AttrDefId::ImplId(it) => it.resolver(db.upcast()), AttrDefId::ExternBlockId(it) => it.resolver(db.upcast()), + AttrDefId::UseId(it) => it.resolver(db.upcast()), AttrDefId::MacroId(it) => it.resolver(db.upcast()), AttrDefId::ExternCrateId(it) => it.resolver(db.upcast()), AttrDefId::GenericParamId(it) => match it { diff --git a/crates/hir/src/semantics/source_to_def.rs b/crates/hir/src/semantics/source_to_def.rs index e6eb7d7a5633e..aabda3655602a 100644 --- a/crates/hir/src/semantics/source_to_def.rs +++ b/crates/hir/src/semantics/source_to_def.rs @@ -95,7 +95,7 @@ use hir_def::{ hir::{BindingId, LabelId}, AdtId, ConstId, ConstParamId, DefWithBodyId, EnumId, EnumVariantId, ExternCrateId, FieldId, FunctionId, GenericDefId, GenericParamId, ImplId, LifetimeParamId, MacroId, ModuleId, StaticId, - StructId, TraitAliasId, TraitId, TypeAliasId, TypeParamId, UnionId, VariantId, + StructId, TraitAliasId, TraitId, TypeAliasId, TypeParamId, UnionId, UseId, VariantId, }; use hir_expand::{attrs::AttrId, name::AsName, HirFileId, MacroCallId}; use rustc_hash::FxHashMap; @@ -209,6 +209,10 @@ impl SourceToDefCtx<'_, '_> { ) -> Option { self.to_def(src, keys::EXTERN_CRATE) } + #[allow(dead_code)] + pub(super) fn use_to_def(&mut self, src: InFile) -> Option { + self.to_def(src, keys::USE) + } pub(super) fn adt_to_def( &mut self, InFile { file_id, value }: InFile, From 6990d0f26abb3709d6dcaad18044b1c22be5655d Mon Sep 17 00:00:00 2001 From: hkalbasi Date: Wed, 2 Aug 2023 17:36:11 +0330 Subject: [PATCH 51/59] Fix unsized struct problems in mir eval --- crates/hir-ty/src/consteval/tests.rs | 64 +++++++++++++++---- .../hir-ty/src/consteval/tests/intrinsics.rs | 22 +++++++ crates/hir-ty/src/mir.rs | 1 + crates/hir-ty/src/mir/eval.rs | 61 ++++++++++++++++-- crates/hir-ty/src/mir/eval/shim.rs | 26 ++++++-- crates/hir-ty/src/mir/eval/tests.rs | 44 +++++++++++++ crates/hir-ty/src/mir/lower.rs | 37 +++++++---- 7 files changed, 220 insertions(+), 35 deletions(-) diff --git a/crates/hir-ty/src/consteval/tests.rs b/crates/hir-ty/src/consteval/tests.rs index 43792994988d9..666955fa1c382 100644 --- a/crates/hir-ty/src/consteval/tests.rs +++ b/crates/hir-ty/src/consteval/tests.rs @@ -1186,6 +1186,25 @@ fn pattern_matching_ergonomics() { ); } +#[test] +fn destructing_assignment() { + check_number( + r#" + //- minicore: add + const fn f(i: &mut u8) -> &mut u8 { + *i += 1; + i + } + const GOAL: u8 = { + let mut i = 4; + _ = f(&mut i); + i + }; + "#, + 5, + ); +} + #[test] fn let_else() { check_number( @@ -1745,6 +1764,24 @@ fn function_pointer_in_constants() { ); } +#[test] +fn function_pointer_and_niche_optimization() { + check_number( + r#" + //- minicore: option + const GOAL: i32 = { + let f: fn(i32) -> i32 = |x| x + 2; + let init = Some(f); + match init { + Some(t) => t(3), + None => 222, + } + }; + "#, + 5, + ); +} + #[test] fn function_pointer() { check_number( @@ -2359,11 +2396,14 @@ fn const_loop() { fn const_transfer_memory() { check_number( r#" - const A1: &i32 = &2; - const A2: &i32 = &5; - const GOAL: i32 = *A1 + *A2; + //- minicore: slice, index, coerce_unsized + const A1: &i32 = &1; + const A2: &i32 = &10; + const A3: [&i32; 3] = [&1, &2, &100]; + const A4: (i32, &i32) = (1, &1000); + const GOAL: i32 = *A1 + *A2 + *A3[2] + *A4.1; "#, - 7, + 1111, ); } @@ -2634,9 +2674,9 @@ fn exec_limits() { } sum } - const GOAL: i32 = f(10000); + const GOAL: i32 = f(1000); "#, - 10000 * 10000, + 1000 * 1000, ); } @@ -2683,7 +2723,7 @@ fn unsized_field() { //- minicore: coerce_unsized, index, slice, transmute use core::mem::transmute; - struct Slice([u8]); + struct Slice([usize]); struct Slice2(Slice); impl Slice2 { @@ -2691,19 +2731,19 @@ fn unsized_field() { &self.0 } - fn as_bytes(&self) -> &[u8] { + fn as_bytes(&self) -> &[usize] { &self.as_inner().0 } } - const GOAL: u8 = unsafe { - let x: &[u8] = &[1, 2, 3]; + const GOAL: usize = unsafe { + let x: &[usize] = &[1, 2, 3]; let x: &Slice2 = transmute(x); let x = x.as_bytes(); - x[0] + x[1] + x[2] + x[0] + x[1] + x[2] + x.len() * 100 }; "#, - 6, + 306, ); } diff --git a/crates/hir-ty/src/consteval/tests/intrinsics.rs b/crates/hir-ty/src/consteval/tests/intrinsics.rs index b0682866004c5..2855f789001a7 100644 --- a/crates/hir-ty/src/consteval/tests/intrinsics.rs +++ b/crates/hir-ty/src/consteval/tests/intrinsics.rs @@ -251,6 +251,28 @@ fn wrapping_add() { ); } +#[test] +fn ptr_offset_from() { + check_number( + r#" + //- minicore: index, slice, coerce_unsized + extern "rust-intrinsic" { + pub fn ptr_offset_from(ptr: *const T, base: *const T) -> isize; + pub fn ptr_offset_from_unsigned(ptr: *const T, base: *const T) -> usize; + } + + const GOAL: isize = { + let x = [1, 2, 3, 4, 5i32]; + let r1 = -ptr_offset_from(&x[0], &x[4]); + let r2 = ptr_offset_from(&x[3], &x[1]); + let r3 = ptr_offset_from_unsigned(&x[3], &x[0]) as isize; + r3 * 100 + r2 * 10 + r1 + }; + "#, + 324, + ); +} + #[test] fn saturating() { check_number( diff --git a/crates/hir-ty/src/mir.rs b/crates/hir-ty/src/mir.rs index 922e49d281d03..4723c25ed0800 100644 --- a/crates/hir-ty/src/mir.rs +++ b/crates/hir-ty/src/mir.rs @@ -234,6 +234,7 @@ impl Place { self.local == child.local && child.projection.starts_with(&self.projection) } + /// The place itself is not included fn iterate_over_parents(&self) -> impl Iterator + '_ { (0..self.projection.len()) .map(|x| &self.projection[0..x]) diff --git a/crates/hir-ty/src/mir/eval.rs b/crates/hir-ty/src/mir/eval.rs index 2d5d7c7b1f7a4..177c1f7ac6d17 100644 --- a/crates/hir-ty/src/mir/eval.rs +++ b/crates/hir-ty/src/mir/eval.rs @@ -68,18 +68,22 @@ pub struct VTableMap { } impl VTableMap { + const OFFSET: usize = 1000; // We should add some offset to ids to make 0 (null) an invalid id. + fn id(&mut self, ty: Ty) -> usize { if let Some(it) = self.ty_to_id.get(&ty) { return *it; } - let id = self.id_to_ty.len(); + let id = self.id_to_ty.len() + VTableMap::OFFSET; self.id_to_ty.push(ty.clone()); self.ty_to_id.insert(ty, id); id } pub(crate) fn ty(&self, id: usize) -> Result<&Ty> { - self.id_to_ty.get(id).ok_or(MirEvalError::InvalidVTableId(id)) + id.checked_sub(VTableMap::OFFSET) + .and_then(|id| self.id_to_ty.get(id)) + .ok_or(MirEvalError::InvalidVTableId(id)) } fn ty_of_bytes(&self, bytes: &[u8]) -> Result<&Ty> { @@ -467,6 +471,10 @@ impl DropFlags { fn remove_place(&mut self, p: &Place) -> bool { // FIXME: replace parents with parts + if let Some(parent) = p.iterate_over_parents().find(|it| self.need_drop.contains(&it)) { + self.need_drop.remove(&parent); + return true; + } self.need_drop.remove(p) } } @@ -511,6 +519,11 @@ pub fn interpret_mir( ) } +#[cfg(test)] +const EXECUTION_LIMIT: usize = 100_000; +#[cfg(not(test))] +const EXECUTION_LIMIT: usize = 10_000_000; + impl Evaluator<'_> { pub fn new<'a>( db: &'a dyn HirDatabase, @@ -534,7 +547,7 @@ impl Evaluator<'_> { stderr: vec![], assert_placeholder_ty_is_unused, stack_depth_limit: 100, - execution_limit: 1000_000, + execution_limit: EXECUTION_LIMIT, memory_limit: 1000_000_000, // 2GB, 1GB for stack and 1GB for heap layout_cache: RefCell::new(HashMap::default()), } @@ -683,8 +696,10 @@ impl Evaluator<'_> { .offset(u32::from(f.local_id.into_raw()) as usize) .bytes_usize(); addr = addr.offset(offset); - // FIXME: support structs with unsized fields - metadata = None; + // Unsized field metadata is equal to the metadata of the struct + if self.size_align_of(&ty, locals)?.is_some() { + metadata = None; + } } ProjectionElem::OpaqueCast(_) => not_supported!("opaque cast"), } @@ -1803,6 +1818,17 @@ impl Evaluator<'_> { } } } + chalk_ir::TyKind::Array(inner, len) => { + let len = match try_const_usize(this.db, &len) { + Some(it) => it as usize, + None => not_supported!("non evaluatable array len in patching addresses"), + }; + let size = this.size_of_sized(inner, locals, "inner of array")?; + for i in 0..len { + let offset = i * size; + rec(this, &bytes[offset..offset + size], inner, locals, mm)?; + } + } chalk_ir::TyKind::Tuple(_, subst) => { let layout = this.layout(ty)?; for (id, ty) in subst.iter(Interner).enumerate() { @@ -1911,10 +1937,31 @@ impl Evaluator<'_> { AdtId::UnionId(_) => (), AdtId::EnumId(_) => (), }, + TyKind::Tuple(_, subst) => { + for (id, ty) in subst.iter(Interner).enumerate() { + let ty = ty.assert_ty_ref(Interner); // Tuple only has type argument + let offset = layout.fields.offset(id).bytes_usize(); + self.patch_addresses(patch_map, old_vtable, addr.offset(offset), ty, locals)?; + } + } + TyKind::Array(inner, len) => { + let len = match try_const_usize(self.db, &len) { + Some(it) => it as usize, + None => not_supported!("non evaluatable array len in patching addresses"), + }; + let size = self.size_of_sized(inner, locals, "inner of array")?; + for i in 0..len { + self.patch_addresses( + patch_map, + old_vtable, + addr.offset(i * size), + inner, + locals, + )?; + } + } TyKind::AssociatedType(_, _) | TyKind::Scalar(_) - | TyKind::Tuple(_, _) - | TyKind::Array(_, _) | TyKind::Slice(_) | TyKind::Raw(_, _) | TyKind::OpaqueType(_, _) diff --git a/crates/hir-ty/src/mir/eval/shim.rs b/crates/hir-ty/src/mir/eval/shim.rs index 356bf70a5fb5a..6c562fe3093d4 100644 --- a/crates/hir-ty/src/mir/eval/shim.rs +++ b/crates/hir-ty/src/mir/eval/shim.rs @@ -694,12 +694,15 @@ impl Evaluator<'_> { else { return Err(MirEvalError::TypeError("type_name generic arg is not provided")); }; - let Ok(ty_name) = ty.display_source_code( + let ty_name = match ty.display_source_code( self.db, locals.body.owner.module(self.db.upcast()), true, - ) else { - not_supported!("fail in generating type_name using source code display"); + ) { + Ok(ty_name) => ty_name, + // Fallback to human readable display in case of `Err`. Ideally we want to use `display_source_code` to + // render full paths. + Err(_) => ty.display(self.db).to_string(), }; let len = ty_name.len(); let addr = self.heap_allocate(len, 1)?; @@ -755,7 +758,22 @@ impl Evaluator<'_> { let ans = lhs.wrapping_add(rhs); destination.write_from_bytes(self, &ans.to_le_bytes()[0..destination.size]) } - "wrapping_sub" | "unchecked_sub" | "ptr_offset_from_unsigned" | "ptr_offset_from" => { + "ptr_offset_from_unsigned" | "ptr_offset_from" => { + let [lhs, rhs] = args else { + return Err(MirEvalError::TypeError("wrapping_sub args are not provided")); + }; + let lhs = i128::from_le_bytes(pad16(lhs.get(self)?, false)); + let rhs = i128::from_le_bytes(pad16(rhs.get(self)?, false)); + let ans = lhs.wrapping_sub(rhs); + let Some(ty) = generic_args.as_slice(Interner).get(0).and_then(|it| it.ty(Interner)) + else { + return Err(MirEvalError::TypeError("ptr_offset_from generic arg is not provided")); + }; + let size = self.size_of_sized(ty, locals, "ptr_offset_from arg")? as i128; + let ans = ans / size; + destination.write_from_bytes(self, &ans.to_le_bytes()[0..destination.size]) + } + "wrapping_sub" | "unchecked_sub" => { let [lhs, rhs] = args else { return Err(MirEvalError::TypeError("wrapping_sub args are not provided")); }; diff --git a/crates/hir-ty/src/mir/eval/tests.rs b/crates/hir-ty/src/mir/eval/tests.rs index 4e21a9632766c..46165cf3d6940 100644 --- a/crates/hir-ty/src/mir/eval/tests.rs +++ b/crates/hir-ty/src/mir/eval/tests.rs @@ -182,6 +182,50 @@ fn main() { ); } +#[test] +fn drop_struct_field() { + check_pass( + r#" +//- minicore: drop, add, option, cell, builtin_impls + +use core::cell::Cell; + +fn should_not_reach() { + _ // FIXME: replace this function with panic when that works +} + +struct X<'a>(&'a Cell); +impl<'a> Drop for X<'a> { + fn drop(&mut self) { + self.0.set(self.0.get() + 1) + } +} + +struct Tuple<'a>(X<'a>, X<'a>, X<'a>); + +fn main() { + let s = Cell::new(0); + { + let x0 = X(&s); + let xt = Tuple(x0, X(&s), X(&s)); + let x1 = xt.1; + if s.get() != 0 { + should_not_reach(); + } + drop(xt.0); + if s.get() != 1 { + should_not_reach(); + } + } + // FIXME: this should be 3 + if s.get() != 2 { + should_not_reach(); + } +} +"#, + ); +} + #[test] fn drop_in_place() { check_pass( diff --git a/crates/hir-ty/src/mir/lower.rs b/crates/hir-ty/src/mir/lower.rs index 566204bb70d4e..e5e8909481878 100644 --- a/crates/hir-ty/src/mir/lower.rs +++ b/crates/hir-ty/src/mir/lower.rs @@ -1028,18 +1028,7 @@ impl<'ctx> MirLowerCtx<'ctx> { self.push_assignment(current, lhs_place, r_value, expr_id.into()); return Ok(Some(current)); } else { - let Some((lhs_place, current)) = - self.lower_expr_as_place(current, *lhs, false)? - else { - return Ok(None); - }; - let Some((rhs_op, current)) = - self.lower_expr_to_some_operand(*rhs, current)? - else { - return Ok(None); - }; - self.push_assignment(current, lhs_place, rhs_op.into(), expr_id.into()); - return Ok(Some(current)); + return self.lower_assignment(current, *lhs, *rhs, expr_id.into()); } } let Some((lhs_op, current)) = self.lower_expr_to_some_operand(*lhs, current)? @@ -1285,6 +1274,30 @@ impl<'ctx> MirLowerCtx<'ctx> { } } + fn lower_assignment( + &mut self, + current: BasicBlockId, + lhs: ExprId, + rhs: ExprId, + span: MirSpan, + ) -> Result> { + let Some((rhs_op, current)) = + self.lower_expr_to_some_operand(rhs, current)? + else { + return Ok(None); + }; + if matches!(&self.body.exprs[lhs], Expr::Underscore) { + return Ok(Some(current)); + } + let Some((lhs_place, current)) = + self.lower_expr_as_place(current, lhs, false)? + else { + return Ok(None); + }; + self.push_assignment(current, lhs_place, rhs_op.into(), span); + Ok(Some(current)) + } + fn placeholder_subst(&mut self) -> Substitution { let placeholder_subst = match self.owner.as_generic_def_id() { Some(it) => TyBuilder::placeholder_subst(self.db, it), From 3115d6988f7c191d66378bba69ed15ac878d6f5c Mon Sep 17 00:00:00 2001 From: hkalbasi Date: Fri, 4 Aug 2023 16:04:40 +0330 Subject: [PATCH 52/59] Improve mir interpreter performance by caching --- crates/hir-ty/src/mir/eval.rs | 366 ++++++++++++++++++----------- crates/hir-ty/src/mir/eval/shim.rs | 6 + 2 files changed, 233 insertions(+), 139 deletions(-) diff --git a/crates/hir-ty/src/mir/eval.rs b/crates/hir-ty/src/mir/eval.rs index 177c1f7ac6d17..9e30eed56f3a7 100644 --- a/crates/hir-ty/src/mir/eval.rs +++ b/crates/hir-ty/src/mir/eval.rs @@ -1,6 +1,13 @@ //! This module provides a MIR interpreter, which is used in const eval. -use std::{borrow::Cow, cell::RefCell, collections::HashMap, fmt::Write, iter, mem, ops::Range}; +use std::{ + borrow::Cow, + cell::RefCell, + collections::{HashMap, HashSet}, + fmt::Write, + iter, mem, + ops::Range, +}; use base_db::{CrateId, FileId}; use chalk_ir::Mutability; @@ -39,7 +46,8 @@ use crate::{ use super::{ return_slot, AggregateKind, BasicBlockId, BinOp, CastKind, LocalId, MirBody, MirLowerError, - MirSpan, Operand, Place, ProjectionElem, Rvalue, StatementKind, TerminatorKind, UnOp, + MirSpan, Operand, Place, PlaceElem, ProjectionElem, Rvalue, StatementKind, TerminatorKind, + UnOp, }; mod shim; @@ -120,13 +128,18 @@ impl TlsData { } struct StackFrame { - body: Arc, locals: Locals, destination: Option, prev_stack_ptr: usize, span: (MirSpan, DefWithBodyId), } +#[derive(Clone)] +enum MirOrDynIndex { + Mir(Arc), + Dyn(usize), +} + pub struct Evaluator<'a> { db: &'a dyn HirDatabase, trait_env: Arc, @@ -145,6 +158,17 @@ pub struct Evaluator<'a> { stdout: Vec, stderr: Vec, layout_cache: RefCell>>, + projected_ty_cache: RefCell>, + not_special_fn_cache: RefCell>, + mir_or_dyn_index_cache: RefCell>, + /// Constantly dropping and creating `Locals` is very costly. We store + /// old locals that we normaly want to drop here, to reuse their allocations + /// later. + unused_locals_store: RefCell>>, + cached_ptr_size: usize, + cached_fn_trait_func: Option, + cached_fn_mut_trait_func: Option, + cached_fn_once_trait_func: Option, crate_id: CrateId, // FIXME: This is a workaround, see the comment on `interpret_mir` assert_placeholder_ty_is_unused: bool, @@ -477,6 +501,10 @@ impl DropFlags { } self.need_drop.remove(p) } + + fn clear(&mut self) { + self.need_drop.clear(); + } } #[derive(Debug)] @@ -550,6 +578,26 @@ impl Evaluator<'_> { execution_limit: EXECUTION_LIMIT, memory_limit: 1000_000_000, // 2GB, 1GB for stack and 1GB for heap layout_cache: RefCell::new(HashMap::default()), + projected_ty_cache: RefCell::new(HashMap::default()), + not_special_fn_cache: RefCell::new(HashSet::default()), + mir_or_dyn_index_cache: RefCell::new(HashMap::default()), + unused_locals_store: RefCell::new(HashMap::default()), + cached_ptr_size: match db.target_data_layout(crate_id) { + Some(it) => it.pointer_size.bytes_usize(), + None => 8, + }, + cached_fn_trait_func: db + .lang_item(crate_id, LangItem::Fn) + .and_then(|x| x.as_trait()) + .and_then(|x| db.trait_data(x).method_by_name(&name![call])), + cached_fn_mut_trait_func: db + .lang_item(crate_id, LangItem::FnMut) + .and_then(|x| x.as_trait()) + .and_then(|x| db.trait_data(x).method_by_name(&name![call_mut])), + cached_fn_once_trait_func: db + .lang_item(crate_id, LangItem::FnOnce) + .and_then(|x| x.as_trait()) + .and_then(|x| db.trait_data(x).method_by_name(&name![call_once])), } } @@ -570,10 +618,34 @@ impl Evaluator<'_> { } fn ptr_size(&self) -> usize { - match self.db.target_data_layout(self.crate_id) { - Some(it) => it.pointer_size.bytes_usize(), - None => 8, + self.cached_ptr_size + } + + fn projected_ty(&self, ty: Ty, proj: PlaceElem) -> Ty { + let pair = (ty, proj); + if let Some(r) = self.projected_ty_cache.borrow().get(&pair) { + return r.clone(); } + let (ty, proj) = pair; + let r = proj.projected_ty( + ty.clone(), + self.db, + |c, subst, f| { + let (def, _) = self.db.lookup_intern_closure(c.into()); + let infer = self.db.infer(def); + let (captures, _) = infer.closure_info(&c); + let parent_subst = ClosureSubst(subst).parent_subst(); + captures + .get(f) + .expect("broken closure field") + .ty + .clone() + .substitute(Interner, parent_subst) + }, + self.crate_id, + ); + self.projected_ty_cache.borrow_mut().insert((ty, proj), r.clone()); + r } fn place_addr_and_ty_and_metadata<'a>( @@ -586,23 +658,7 @@ impl Evaluator<'_> { let mut metadata: Option = None; // locals are always sized for proj in &*p.projection { let prev_ty = ty.clone(); - ty = proj.projected_ty( - ty, - self.db, - |c, subst, f| { - let (def, _) = self.db.lookup_intern_closure(c.into()); - let infer = self.db.infer(def); - let (captures, _) = infer.closure_info(&c); - let parent_subst = ClosureSubst(subst).parent_subst(); - captures - .get(f) - .expect("broken closure field") - .ty - .clone() - .substitute(Interner, parent_subst) - }, - self.crate_id, - ); + ty = self.projected_ty(ty, proj.clone()); match proj { ProjectionElem::Deref => { metadata = if self.size_align_of(&ty, locals)?.is_none() { @@ -756,18 +812,18 @@ impl Evaluator<'_> { return Err(MirEvalError::StackOverflow); } let mut current_block_idx = body.start_block; - let (mut locals, prev_stack_ptr) = self.create_locals_for_body(body.clone(), None)?; + let (mut locals, prev_stack_ptr) = self.create_locals_for_body(&body, None)?; self.fill_locals_for_body(&body, &mut locals, args)?; let prev_code_stack = mem::take(&mut self.code_stack); let span = (MirSpan::Unknown, body.owner); - self.code_stack.push(StackFrame { body, locals, destination: None, prev_stack_ptr, span }); + self.code_stack.push(StackFrame { locals, destination: None, prev_stack_ptr, span }); 'stack: loop { let Some(mut my_stack_frame) = self.code_stack.pop() else { not_supported!("missing stack frame"); }; let e = (|| { let mut locals = &mut my_stack_frame.locals; - let body = &*my_stack_frame.body; + let body = locals.body.clone(); loop { let current_block = &body.basic_blocks[current_block_idx]; if let Some(it) = self.execution_limit.checked_sub(1) { @@ -836,7 +892,7 @@ impl Evaluator<'_> { locals.drop_flags.add_place(destination.clone()); if let Some(stack_frame) = stack_frame { self.code_stack.push(my_stack_frame); - current_block_idx = stack_frame.body.start_block; + current_block_idx = stack_frame.locals.body.start_block; self.code_stack.push(stack_frame); return Ok(None); } else { @@ -877,18 +933,24 @@ impl Evaluator<'_> { let my_code_stack = mem::replace(&mut self.code_stack, prev_code_stack); let mut error_stack = vec![]; for frame in my_code_stack.into_iter().rev() { - if let DefWithBodyId::FunctionId(f) = frame.body.owner { + if let DefWithBodyId::FunctionId(f) = frame.locals.body.owner { error_stack.push((Either::Left(f), frame.span.0, frame.span.1)); } } return Err(MirEvalError::InFunction(Box::new(e), error_stack)); } }; + let return_interval = my_stack_frame.locals.ptr[return_slot()]; + self.unused_locals_store + .borrow_mut() + .entry(my_stack_frame.locals.body.owner) + .or_default() + .push(my_stack_frame.locals); match my_stack_frame.destination { None => { self.code_stack = prev_code_stack; self.stack_depth_limit += 1; - return Ok(my_stack_frame.locals.ptr[return_slot()].get(self)?.to_vec()); + return Ok(return_interval.get(self)?.to_vec()); } Some(bb) => { // We don't support const promotion, so we can't truncate the stack yet. @@ -926,39 +988,45 @@ impl Evaluator<'_> { fn create_locals_for_body( &mut self, - body: Arc, + body: &Arc, destination: Option, ) -> Result<(Locals, usize)> { let mut locals = - Locals { ptr: ArenaMap::new(), body: body.clone(), drop_flags: DropFlags::default() }; - let (locals_ptr, stack_size) = { + match self.unused_locals_store.borrow_mut().entry(body.owner).or_default().pop() { + None => Locals { + ptr: ArenaMap::new(), + body: body.clone(), + drop_flags: DropFlags::default(), + }, + Some(mut l) => { + l.drop_flags.clear(); + l.body = body.clone(); + l + } + }; + let stack_size = { let mut stack_ptr = self.stack.len(); - let addr = body - .locals - .iter() - .map(|(id, it)| { - if id == return_slot() { - if let Some(destination) = destination { - return Ok((id, destination)); - } - } - let (size, align) = self.size_align_of_sized( - &it.ty, - &locals, - "no unsized local in extending stack", - )?; - while stack_ptr % align != 0 { - stack_ptr += 1; + for (id, it) in body.locals.iter() { + if id == return_slot() { + if let Some(destination) = destination { + locals.ptr.insert(id, destination); + continue; } - let my_ptr = stack_ptr; - stack_ptr += size; - Ok((id, Interval { addr: Stack(my_ptr), size })) - }) - .collect::>>()?; - let stack_size = stack_ptr - self.stack.len(); - (addr, stack_size) + } + let (size, align) = self.size_align_of_sized( + &it.ty, + &locals, + "no unsized local in extending stack", + )?; + while stack_ptr % align != 0 { + stack_ptr += 1; + } + let my_ptr = stack_ptr; + stack_ptr += size; + locals.ptr.insert(id, Interval { addr: Stack(my_ptr), size }); + } + stack_ptr - self.stack.len() }; - locals.ptr = locals_ptr; let prev_stack_pointer = self.stack.len(); if stack_size > self.memory_limit { return Err(MirEvalError::Panic(format!( @@ -1693,6 +1761,11 @@ impl Evaluator<'_> { } fn size_align_of(&self, ty: &Ty, locals: &Locals) -> Result> { + if let Some(layout) = self.layout_cache.borrow().get(ty) { + return Ok(layout + .is_sized() + .then(|| (layout.size.bytes_usize(), layout.align.abi.bytes() as usize))); + } if let DefWithBodyId::VariantId(f) = locals.body.owner { if let Some((adt, _)) = ty.as_adt() { if AdtId::from(f.parent) == adt { @@ -1753,16 +1826,15 @@ impl Evaluator<'_> { } fn detect_fn_trait(&self, def: FunctionId) -> Option { - use LangItem::*; - let ItemContainerId::TraitId(parent) = self.db.lookup_intern_function(def).container else { - return None; - }; - let l = self.db.lang_attr(parent.into())?; - match l { - FnOnce => Some(FnTrait::FnOnce), - FnMut => Some(FnTrait::FnMut), - Fn => Some(FnTrait::Fn), - _ => None, + let def = Some(def); + if def == self.cached_fn_trait_func { + Some(FnTrait::Fn) + } else if def == self.cached_fn_mut_trait_func { + Some(FnTrait::FnMut) + } else if def == self.cached_fn_once_trait_func { + Some(FnTrait::FnOnce) + } else { + None } } @@ -2105,6 +2177,40 @@ impl Evaluator<'_> { } } + fn get_mir_or_dyn_index( + &self, + def: FunctionId, + generic_args: Substitution, + locals: &Locals, + span: MirSpan, + ) -> Result { + let pair = (def, generic_args); + if let Some(r) = self.mir_or_dyn_index_cache.borrow().get(&pair) { + return Ok(r.clone()); + } + let (def, generic_args) = pair; + let r = if let Some(self_ty_idx) = + is_dyn_method(self.db, self.trait_env.clone(), def, generic_args.clone()) + { + MirOrDynIndex::Dyn(self_ty_idx) + } else { + let (imp, generic_args) = + self.db.lookup_impl_method(self.trait_env.clone(), def, generic_args.clone()); + let mir_body = self + .db + .monomorphized_mir_body(imp.into(), generic_args, self.trait_env.clone()) + .map_err(|e| { + MirEvalError::InFunction( + Box::new(MirEvalError::MirLowerError(imp, e)), + vec![(Either::Left(imp), span, locals.body.owner)], + ) + })?; + MirOrDynIndex::Mir(mir_body) + }; + self.mir_or_dyn_index_cache.borrow_mut().insert((def, generic_args), r.clone()); + Ok(r) + } + fn exec_fn_with_args( &mut self, def: FunctionId, @@ -2126,93 +2232,76 @@ impl Evaluator<'_> { return Ok(None); } let arg_bytes = args.iter().map(|it| IntervalOrOwned::Borrowed(it.interval)); - if let Some(self_ty_idx) = - is_dyn_method(self.db, self.trait_env.clone(), def, generic_args.clone()) - { - // In the layout of current possible receiver, which at the moment of writing this code is one of - // `&T`, `&mut T`, `Box`, `Rc`, `Arc`, and `Pin

` where `P` is one of possible recievers, - // the vtable is exactly in the `[ptr_size..2*ptr_size]` bytes. So we can use it without branching on - // the type. - let first_arg = arg_bytes.clone().next().unwrap(); - let first_arg = first_arg.get(self)?; - let ty = - self.vtable_map.ty_of_bytes(&first_arg[self.ptr_size()..self.ptr_size() * 2])?; - let mut args_for_target = args.to_vec(); - args_for_target[0] = IntervalAndTy { - interval: args_for_target[0].interval.slice(0..self.ptr_size()), - ty: ty.clone(), - }; - let ty = GenericArgData::Ty(ty.clone()).intern(Interner); - let generics_for_target = Substitution::from_iter( - Interner, - generic_args.iter(Interner).enumerate().map(|(i, it)| { - if i == self_ty_idx { - &ty - } else { - it - } - }), - ); - return self.exec_fn_with_args( - def, - &args_for_target, - generics_for_target, + match self.get_mir_or_dyn_index(def, generic_args.clone(), locals, span)? { + MirOrDynIndex::Dyn(self_ty_idx) => { + // In the layout of current possible receiver, which at the moment of writing this code is one of + // `&T`, `&mut T`, `Box`, `Rc`, `Arc`, and `Pin

` where `P` is one of possible recievers, + // the vtable is exactly in the `[ptr_size..2*ptr_size]` bytes. So we can use it without branching on + // the type. + let first_arg = arg_bytes.clone().next().unwrap(); + let first_arg = first_arg.get(self)?; + let ty = self + .vtable_map + .ty_of_bytes(&first_arg[self.ptr_size()..self.ptr_size() * 2])?; + let mut args_for_target = args.to_vec(); + args_for_target[0] = IntervalAndTy { + interval: args_for_target[0].interval.slice(0..self.ptr_size()), + ty: ty.clone(), + }; + let ty = GenericArgData::Ty(ty.clone()).intern(Interner); + let generics_for_target = Substitution::from_iter( + Interner, + generic_args.iter(Interner).enumerate().map(|(i, it)| { + if i == self_ty_idx { + &ty + } else { + it + } + }), + ); + return self.exec_fn_with_args( + def, + &args_for_target, + generics_for_target, + locals, + destination, + target_bb, + span, + ); + } + MirOrDynIndex::Mir(body) => self.exec_looked_up_function( + body, locals, + def, + arg_bytes, + span, destination, target_bb, - span, - ); + ), } - let (imp, generic_args) = - self.db.lookup_impl_method(self.trait_env.clone(), def, generic_args); - self.exec_looked_up_function( - generic_args, - locals, - imp, - arg_bytes, - span, - destination, - target_bb, - ) } fn exec_looked_up_function( &mut self, - generic_args: Substitution, + mir_body: Arc, locals: &Locals, - imp: FunctionId, + def: FunctionId, arg_bytes: impl Iterator, span: MirSpan, destination: Interval, target_bb: Option, ) -> Result> { - let def = imp.into(); - let mir_body = self - .db - .monomorphized_mir_body(def, generic_args, self.trait_env.clone()) - .map_err(|e| { - MirEvalError::InFunction( - Box::new(MirEvalError::MirLowerError(imp, e)), - vec![(Either::Left(imp), span, locals.body.owner)], - ) - })?; Ok(if let Some(target_bb) = target_bb { let (mut locals, prev_stack_ptr) = - self.create_locals_for_body(mir_body.clone(), Some(destination))?; + self.create_locals_for_body(&mir_body, Some(destination))?; self.fill_locals_for_body(&mir_body, &mut locals, arg_bytes.into_iter())?; let span = (span, locals.body.owner); - Some(StackFrame { - body: mir_body, - locals, - destination: Some(target_bb), - prev_stack_ptr, - span, - }) + Some(StackFrame { locals, destination: Some(target_bb), prev_stack_ptr, span }) } else { let result = self.interpret_mir(mir_body, arg_bytes).map_err(|e| { MirEvalError::InFunction( Box::new(e), - vec![(Either::Left(imp), span, locals.body.owner)], + vec![(Either::Left(def), span, locals.body.owner)], ) })?; destination.write_from_bytes(self, &result)?; @@ -2384,16 +2473,15 @@ impl Evaluator<'_> { // we can ignore drop in them. return Ok(()); }; - let (impl_drop_candidate, subst) = self.db.lookup_impl_method( - self.trait_env.clone(), - drop_fn, - Substitution::from1(Interner, ty.clone()), - ); - if impl_drop_candidate != drop_fn { + + let generic_args = Substitution::from1(Interner, ty.clone()); + if let Ok(MirOrDynIndex::Mir(body)) = + self.get_mir_or_dyn_index(drop_fn, generic_args, locals, span) + { self.exec_looked_up_function( - subst, + body, locals, - impl_drop_candidate, + drop_fn, [IntervalOrOwned::Owned(addr.to_bytes())].into_iter(), span, Interval { addr: Address::Invalid(0), size: 0 }, diff --git a/crates/hir-ty/src/mir/eval/shim.rs b/crates/hir-ty/src/mir/eval/shim.rs index 6c562fe3093d4..b2e29fd34b5f9 100644 --- a/crates/hir-ty/src/mir/eval/shim.rs +++ b/crates/hir-ty/src/mir/eval/shim.rs @@ -36,6 +36,9 @@ impl Evaluator<'_> { destination: Interval, span: MirSpan, ) -> Result { + if self.not_special_fn_cache.borrow().contains(&def) { + return Ok(false); + } let function_data = self.db.function_data(def); let is_intrinsic = match &function_data.abi { Some(abi) => *abi == Interned::new_str("rust-intrinsic"), @@ -137,8 +140,11 @@ impl Evaluator<'_> { self.exec_clone(def, args, self_ty.clone(), locals, destination, span)?; return Ok(true); } + // Return early to prevent caching clone as non special fn. + return Ok(false); } } + self.not_special_fn_cache.borrow_mut().insert(def); Ok(false) } From cc5664c5a22f0b8fcd268f5b5866bc91dacdda6a Mon Sep 17 00:00:00 2001 From: hkalbasi Date: Fri, 4 Aug 2023 16:35:13 +0330 Subject: [PATCH 53/59] Add rustc comment into while desugaring --- crates/hir-def/src/body/lower.rs | 41 +++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/crates/hir-def/src/body/lower.rs b/crates/hir-def/src/body/lower.rs index c8d1ca4fa708d..3df4357648296 100644 --- a/crates/hir-def/src/body/lower.rs +++ b/crates/hir-def/src/body/lower.rs @@ -313,20 +313,7 @@ impl ExprCollector<'_> { let body = self.collect_labelled_block_opt(label, e.loop_body()); self.alloc_expr(Expr::Loop { body, label }, syntax_ptr) } - ast::Expr::WhileExpr(e) => { - // Desugar `while { }` to - // `loop { if { } else { break } }` - let label = e.label().map(|label| self.collect_label(label)); - let body = self.collect_labelled_block_opt(label, e.loop_body()); - let condition = self.collect_expr_opt(e.condition()); - let break_expr = - self.alloc_expr(Expr::Break { expr: None, label: None }, syntax_ptr.clone()); - let if_expr = self.alloc_expr( - Expr::If { condition, then_branch: body, else_branch: Some(break_expr) }, - syntax_ptr.clone(), - ); - self.alloc_expr(Expr::Loop { body: if_expr, label }, syntax_ptr) - } + ast::Expr::WhileExpr(e) => self.collect_while_loop(syntax_ptr, e), ast::Expr::ForExpr(e) => self.collect_for_loop(syntax_ptr, e), ast::Expr::CallExpr(e) => { let is_rustc_box = { @@ -738,6 +725,32 @@ impl ExprCollector<'_> { expr_id } + /// Desugar `ast::WhileExpr` from: `[opt_ident]: while ` into: + /// ```ignore (pseudo-rust) + /// [opt_ident]: loop { + /// if { + /// + /// } + /// else { + /// break; + /// } + /// } + /// ``` + /// FIXME: Rustc wraps the condition in a construct equivalent to `{ let _t = ; _t }` + /// to preserve drop semantics. We should probably do the same in future. + fn collect_while_loop(&mut self, syntax_ptr: AstPtr, e: ast::WhileExpr) -> ExprId { + let label = e.label().map(|label| self.collect_label(label)); + let body = self.collect_labelled_block_opt(label, e.loop_body()); + let condition = self.collect_expr_opt(e.condition()); + let break_expr = + self.alloc_expr(Expr::Break { expr: None, label: None }, syntax_ptr.clone()); + let if_expr = self.alloc_expr( + Expr::If { condition, then_branch: body, else_branch: Some(break_expr) }, + syntax_ptr.clone(), + ); + self.alloc_expr(Expr::Loop { body: if_expr, label }, syntax_ptr) + } + /// Desugar `ast::ForExpr` from: `[opt_ident]: for in ` into: /// ```ignore (pseudo-rust) /// match IntoIterator::into_iter() { From 1e76b11a2029adc4c225bd8db5fc246d120c161d Mon Sep 17 00:00:00 2001 From: Wilfred Hughes Date: Fri, 4 Aug 2023 11:03:41 -0700 Subject: [PATCH 54/59] Set the default status bar action to openLogs Previously, clicking 'rust-analyzer' would stop the server entirely. This was easy to do accidentally, and then the user has to wait for the server to start up again. --- editors/code/src/ctx.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/editors/code/src/ctx.ts b/editors/code/src/ctx.ts index 72d8109bc80de..b9a73c6c84091 100644 --- a/editors/code/src/ctx.ts +++ b/editors/code/src/ctx.ts @@ -414,7 +414,7 @@ export class Ctx { statusBar.tooltip.appendText(status.message ?? "Ready"); statusBar.color = undefined; statusBar.backgroundColor = undefined; - statusBar.command = "rust-analyzer.stopServer"; + statusBar.command = "rust-analyzer.openLogs"; this.dependencies?.refresh(); break; case "warning": From 253d68459de10a1a6f25e0fb8859a7447e67fa34 Mon Sep 17 00:00:00 2001 From: Wilfred Hughes Date: Fri, 4 Aug 2023 11:07:22 -0700 Subject: [PATCH 55/59] Use the warning color when rust-analyzer is stopped If the rust-analyzer server isn't running, we can't do much. Treat this state as a warning color, so it's more obvious. --- editors/code/src/ctx.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/editors/code/src/ctx.ts b/editors/code/src/ctx.ts index 72d8109bc80de..720c756a5fa78 100644 --- a/editors/code/src/ctx.ts +++ b/editors/code/src/ctx.ts @@ -442,8 +442,10 @@ export class Ctx { statusBar.tooltip.appendMarkdown( "\n\n[Start server](command:rust-analyzer.startServer)", ); - statusBar.color = undefined; - statusBar.backgroundColor = undefined; + statusBar.color = new vscode.ThemeColor("statusBarItem.warningForeground"); + statusBar.backgroundColor = new vscode.ThemeColor( + "statusBarItem.warningBackground", + ); statusBar.command = "rust-analyzer.startServer"; statusBar.text = `$(stop-circle) rust-analyzer`; return; From edabffbd5ae0fa16afd4c555442bce4e96386a8f Mon Sep 17 00:00:00 2001 From: Wilfred Hughes Date: Fri, 4 Aug 2023 15:28:22 -0700 Subject: [PATCH 56/59] SCIP: Qualify parameters by the containing function SCIP requires symbols to be unique, but multiple functions may have a parameter with the same name. Qualify parameters according to the containing function. --- crates/ide/src/moniker.rs | 11 ++++++++ crates/rust-analyzer/src/cli/scip.rs | 38 ++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/crates/ide/src/moniker.rs b/crates/ide/src/moniker.rs index de8c5b8e83035..17f3771b1a5a4 100644 --- a/crates/ide/src/moniker.rs +++ b/crates/ide/src/moniker.rs @@ -177,6 +177,17 @@ pub(crate) fn def_to_moniker( }); } + // Qualify locals/parameters by their parent definition name. + if let Definition::Local(it) = def { + let parent_name = it.parent(db).name(db); + if let Some(name) = parent_name { + description.push(MonikerDescriptor { + name: name.display(db).to_string(), + desc: MonikerDescriptorKind::Method, + }); + } + } + let name_desc = match def { // These are handled by top-level guard (for performance). Definition::GenericParam(_) diff --git a/crates/rust-analyzer/src/cli/scip.rs b/crates/rust-analyzer/src/cli/scip.rs index 4579aca30219f..44337f955e5c2 100644 --- a/crates/rust-analyzer/src/cli/scip.rs +++ b/crates/rust-analyzer/src/cli/scip.rs @@ -416,6 +416,44 @@ pub mod module { ); } + #[test] + fn symbol_for_param() { + check_symbol( + r#" +//- /lib.rs crate:main deps:foo +use foo::example_mod::func; +fn main() { + func(42); +} +//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library +pub mod example_mod { + pub fn func(x$0: usize) {} +} +"#, + "rust-analyzer cargo foo 0.1.0 example_mod/func().(x)", + ); + } + + #[test] + fn symbol_for_closure_param() { + check_symbol( + r#" +//- /lib.rs crate:main deps:foo +use foo::example_mod::func; +fn main() { + func(); +} +//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library +pub mod example_mod { + pub fn func() { + let f = |x$0: usize| {}; + } +} +"#, + "rust-analyzer cargo foo 0.1.0 example_mod/func().(x)", + ); + } + #[test] fn local_symbol_for_local() { check_symbol( From 622b18e579d5c28f73af9157ef753528d42b96fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lauren=C8=9Biu=20Nicola?= Date: Sat, 5 Aug 2023 19:10:05 +0300 Subject: [PATCH 57/59] Remove unwraps from Generate delegate trait --- .../src/handlers/generate_delegate_trait.rs | 70 ++++++++++--------- 1 file changed, 36 insertions(+), 34 deletions(-) diff --git a/crates/ide-assists/src/handlers/generate_delegate_trait.rs b/crates/ide-assists/src/handlers/generate_delegate_trait.rs index 185f47184d432..f4fa6a74c6b94 100644 --- a/crates/ide-assists/src/handlers/generate_delegate_trait.rs +++ b/crates/ide-assists/src/handlers/generate_delegate_trait.rs @@ -213,7 +213,9 @@ impl Struct { // continue; // } let signature = delegee.signature(db); - let delegate = generate_impl(ctx, self, &field.ty, &field.name, delegee); + let Some(delegate) = generate_impl(ctx, self, &field.ty, &field.name, delegee) else { + continue; + }; acc.add_group( &GroupLabel("Delegate trait impl for field...".to_owned()), @@ -237,7 +239,7 @@ fn generate_impl( field_ty: &ast::Type, field_name: &String, delegee: &Delegee, -) -> ast::Impl { +) -> Option { let delegate: ast::Impl; let source: ast::Impl; let genpar: Option; @@ -247,7 +249,7 @@ fn generate_impl( match delegee { Delegee::Bound(delegee) => { - let in_file = ctx.sema.source(delegee.0.to_owned()).unwrap(); + let in_file = ctx.sema.source(delegee.0.to_owned())?; let source: ast::Trait = in_file.value; delegate = make::impl_trait( @@ -293,15 +295,15 @@ fn generate_impl( None => {} }; - let target = ctx.sema.scope(strukt.strukt.syntax()).unwrap(); - let source = ctx.sema.scope(source.syntax()).unwrap(); + let target = ctx.sema.scope(strukt.strukt.syntax())?; + let source = ctx.sema.scope(source.syntax())?; let transform = PathTransform::trait_impl(&target, &source, delegee.0, delegate.clone()); transform.apply(&delegate.syntax()); } Delegee::Impls(delegee) => { - let in_file = ctx.sema.source(delegee.1.to_owned()).unwrap(); + let in_file = ctx.sema.source(delegee.1.to_owned())?; source = in_file.value; delegate = make::impl_trait( delegee.0.is_unsafe(db), @@ -341,8 +343,8 @@ fn generate_impl( } }); - let target = ctx.sema.scope(strukt.strukt.syntax()).unwrap(); - let source = ctx.sema.scope(source.syntax()).unwrap(); + let target = ctx.sema.scope(strukt.strukt.syntax())?; + let source = ctx.sema.scope(source.syntax())?; let transform = PathTransform::trait_impl(&target, &source, delegee.0, delegate.clone()); @@ -350,7 +352,7 @@ fn generate_impl( } } - delegate + Some(delegate) } fn process_assoc_item( @@ -359,19 +361,19 @@ fn process_assoc_item( base_name: &str, ) -> Option { match item { - AssocItem::Const(c) => Some(const_assoc_item(c, qual_path_ty)), - AssocItem::Fn(f) => Some(func_assoc_item(f, qual_path_ty, base_name)), + AssocItem::Const(c) => const_assoc_item(c, qual_path_ty), + AssocItem::Fn(f) => func_assoc_item(f, qual_path_ty, base_name), AssocItem::MacroCall(_) => { // FIXME : Handle MacroCall case. - // return Some(macro_assoc_item(mac, qual_path_ty)); + // macro_assoc_item(mac, qual_path_ty) None } - AssocItem::TypeAlias(ta) => Some(ty_assoc_item(ta, qual_path_ty)), + AssocItem::TypeAlias(ta) => ty_assoc_item(ta, qual_path_ty), } } -fn const_assoc_item(item: syntax::ast::Const, qual_path_ty: ast::Path) -> AssocItem { - let path_expr_segment = make::path_from_text(item.name().unwrap().to_string().as_str()); +fn const_assoc_item(item: syntax::ast::Const, qual_path_ty: ast::Path) -> Option { + let path_expr_segment = make::path_from_text(item.name()?.to_string().as_str()); // We want rhs of the const assignment to be a qualified path // The general case for const assigment can be found [here](`https://doc.rust-lang.org/reference/items/constant-items.html`) @@ -380,19 +382,19 @@ fn const_assoc_item(item: syntax::ast::Const, qual_path_ty: ast::Path) -> AssocI // FIXME : We can't rely on `make::path_qualified` for now but it would be nice to replace the following with it. // make::path_qualified(qual_path_ty, path_expr_segment.as_single_segment().unwrap()); let qualpath = qualpath(qual_path_ty, path_expr_segment); - let inner = make::item_const( - item.visibility(), - item.name().unwrap(), - item.ty().unwrap(), - make::expr_path(qualpath), - ) - .clone_for_update(); + let inner = + make::item_const(item.visibility(), item.name()?, item.ty()?, make::expr_path(qualpath)) + .clone_for_update(); - AssocItem::Const(inner) + Some(AssocItem::Const(inner)) } -fn func_assoc_item(item: syntax::ast::Fn, qual_path_ty: Path, base_name: &str) -> AssocItem { - let path_expr_segment = make::path_from_text(item.name().unwrap().to_string().as_str()); +fn func_assoc_item( + item: syntax::ast::Fn, + qual_path_ty: Path, + base_name: &str, +) -> Option { + let path_expr_segment = make::path_from_text(item.name()?.to_string().as_str()); let qualpath = qualpath(qual_path_ty, path_expr_segment); let call = match item.param_list() { @@ -415,7 +417,7 @@ fn func_assoc_item(item: syntax::ast::Fn, qual_path_ty: Path, base_name: &str) - if param_count > 0 { // Add SelfParam and a TOKEN::COMMA ted::insert_all( - Position::after(args.l_paren_token().unwrap()), + Position::after(args.l_paren_token()?), vec![ NodeOrToken::Node(tail_expr_self.syntax().clone_for_update()), NodeOrToken::Token(make::token(SyntaxKind::WHITESPACE)), @@ -425,7 +427,7 @@ fn func_assoc_item(item: syntax::ast::Fn, qual_path_ty: Path, base_name: &str) - } else { // Add SelfParam only ted::insert( - Position::after(args.l_paren_token().unwrap()), + Position::after(args.l_paren_token()?), NodeOrToken::Node(tail_expr_self.syntax().clone_for_update()), ); } @@ -444,10 +446,10 @@ fn func_assoc_item(item: syntax::ast::Fn, qual_path_ty: Path, base_name: &str) - let body = make::block_expr(vec![], Some(call)).clone_for_update(); let func = make::fn_( item.visibility(), - item.name().unwrap(), + item.name()?, item.generic_param_list(), item.where_clause(), - item.param_list().unwrap(), + item.param_list()?, body, item.ret_type(), item.async_token().is_some(), @@ -456,14 +458,14 @@ fn func_assoc_item(item: syntax::ast::Fn, qual_path_ty: Path, base_name: &str) - ) .clone_for_update(); - AssocItem::Fn(func.indent(edit::IndentLevel(1)).clone_for_update()) + Some(AssocItem::Fn(func.indent(edit::IndentLevel(1)).clone_for_update())) } -fn ty_assoc_item(item: syntax::ast::TypeAlias, qual_path_ty: Path) -> AssocItem { - let path_expr_segment = make::path_from_text(item.name().unwrap().to_string().as_str()); +fn ty_assoc_item(item: syntax::ast::TypeAlias, qual_path_ty: Path) -> Option { + let path_expr_segment = make::path_from_text(item.name()?.to_string().as_str()); let qualpath = qualpath(qual_path_ty, path_expr_segment); let ty = make::ty_path(qualpath); - let ident = item.name().unwrap().to_string(); + let ident = item.name()?.to_string(); let alias = make::ty_alias( ident.as_str(), @@ -474,7 +476,7 @@ fn ty_assoc_item(item: syntax::ast::TypeAlias, qual_path_ty: Path) -> AssocItem ) .clone_for_update(); - AssocItem::TypeAlias(alias) + Some(AssocItem::TypeAlias(alias)) } fn qualpath(qual_path_ty: ast::Path, path_expr_seg: ast::Path) -> ast::Path { From 042be329a7f09ba6b5e7901c59dee044723b9670 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Sat, 5 Aug 2023 20:00:33 +0200 Subject: [PATCH 58/59] Turn unresolved proc macro expansions into missing expressions --- crates/hir-def/src/body/lower.rs | 5 ++- crates/hir-def/src/expander.rs | 32 ++++++++++++------- .../ide-assists/src/handlers/inline_macro.rs | 3 +- crates/parser/src/grammar/items.rs | 5 ++- 4 files changed, 25 insertions(+), 20 deletions(-) diff --git a/crates/hir-def/src/body/lower.rs b/crates/hir-def/src/body/lower.rs index 3df4357648296..3853a6ab3a576 100644 --- a/crates/hir-def/src/body/lower.rs +++ b/crates/hir-def/src/body/lower.rs @@ -913,15 +913,14 @@ impl ExprCollector<'_> { self.alloc_expr(Expr::Match { expr, arms }, syntax_ptr) } - fn collect_macro_call( + fn collect_macro_call( &mut self, mcall: ast::MacroCall, syntax_ptr: AstPtr, record_diagnostics: bool, - collector: F, + collector: impl FnOnce(&mut Self, Option) -> U, ) -> U where - F: FnOnce(&mut Self, Option) -> U, T: ast::AstNode, { // File containing the macro call. Expansion errors will be attached here. diff --git a/crates/hir-def/src/expander.rs b/crates/hir-def/src/expander.rs index cc85bd893ac47..6db8398bc9867 100644 --- a/crates/hir-def/src/expander.rs +++ b/crates/hir-def/src/expander.rs @@ -164,18 +164,26 @@ impl Expander { return ExpandResult { value: None, err }; }; - Self::enter_expand_inner(db, call_id, err).map(|value| { - value.and_then(|InFile { file_id, value }| { - let parse = value.cast::()?; - - self.recursion_depth += 1; - self.hygiene = Hygiene::new(db.upcast(), file_id); - let old_file_id = std::mem::replace(&mut self.current_file_id, file_id); - let mark = - Mark { file_id: old_file_id, bomb: DropBomb::new("expansion mark dropped") }; - Some((mark, parse)) - }) - }) + let res = Self::enter_expand_inner(db, call_id, err); + match res.err { + // If proc-macro is disabled or unresolved, we want to expand to a missing expression + // instead of an empty tree which might end up in an empty block. + Some(ExpandError::UnresolvedProcMacro(_)) => res.map(|_| None), + _ => res.map(|value| { + value.and_then(|InFile { file_id, value }| { + let parse = value.cast::()?; + + self.recursion_depth += 1; + self.hygiene = Hygiene::new(db.upcast(), file_id); + let old_file_id = std::mem::replace(&mut self.current_file_id, file_id); + let mark = Mark { + file_id: old_file_id, + bomb: DropBomb::new("expansion mark dropped"), + }; + Some((mark, parse)) + }) + }), + } } } diff --git a/crates/ide-assists/src/handlers/inline_macro.rs b/crates/ide-assists/src/handlers/inline_macro.rs index 5aa8e56f5626c..5d956b1a5e870 100644 --- a/crates/ide-assists/src/handlers/inline_macro.rs +++ b/crates/ide-assists/src/handlers/inline_macro.rs @@ -37,11 +37,10 @@ use crate::{AssistContext, AssistId, AssistKind, Assists}; pub(crate) fn inline_macro(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { let unexpanded = ctx.find_node_at_offset::()?; let expanded = insert_ws_into(ctx.sema.expand(&unexpanded)?.clone_for_update()); - let text_range = unexpanded.syntax().text_range(); acc.add( - AssistId("inline_macro", AssistKind::RefactorRewrite), + AssistId("inline_macro", AssistKind::RefactorInline), format!("Inline macro"), text_range, |builder| builder.replace(text_range, expanded.to_string()), diff --git a/crates/parser/src/grammar/items.rs b/crates/parser/src/grammar/items.rs index 1c056819f4b7f..4e850b1f74df8 100644 --- a/crates/parser/src/grammar/items.rs +++ b/crates/parser/src/grammar/items.rs @@ -328,9 +328,6 @@ fn macro_rules(p: &mut Parser<'_>, m: Marker) { p.bump_remap(T![macro_rules]); p.expect(T![!]); - if p.at(IDENT) { - name(p); - } // Special-case `macro_rules! try`. // This is a hack until we do proper edition support @@ -340,6 +337,8 @@ fn macro_rules(p: &mut Parser<'_>, m: Marker) { let m = p.start(); p.bump_remap(IDENT); m.complete(p, NAME); + } else { + name(p); } match p.current() { From 582917453b0d455979a3e6571ab231e85be10d29 Mon Sep 17 00:00:00 2001 From: Ryo Yoshida Date: Mon, 7 Aug 2023 03:23:41 +0900 Subject: [PATCH 59/59] Don't provide `generate_default_from_new` when impl self ty is missing Also don't provide the assist when the `Default` trait can't be found. --- .../src/handlers/generate_default_from_new.rs | 38 ++++++++++++++++--- crates/ide-assists/src/tests/generated.rs | 1 + 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/crates/ide-assists/src/handlers/generate_default_from_new.rs b/crates/ide-assists/src/handlers/generate_default_from_new.rs index 860372941f794..7e4f140a28faa 100644 --- a/crates/ide-assists/src/handlers/generate_default_from_new.rs +++ b/crates/ide-assists/src/handlers/generate_default_from_new.rs @@ -15,6 +15,7 @@ use crate::{ // Generates default implementation from new method. // // ``` +// # //- minicore: default // struct Example { _inner: () } // // impl Example { @@ -54,6 +55,7 @@ pub(crate) fn generate_default_from_new(acc: &mut Assists, ctx: &AssistContext<' } let impl_ = fn_node.syntax().ancestors().find_map(ast::Impl::cast)?; + let self_ty = impl_.self_ty()?; if is_default_implemented(ctx, &impl_) { cov_mark::hit!(default_block_is_already_present); cov_mark::hit!(struct_in_module_with_default); @@ -70,15 +72,19 @@ pub(crate) fn generate_default_from_new(acc: &mut Assists, ctx: &AssistContext<' let default_code = " fn default() -> Self { Self::new() }"; - let code = generate_trait_impl_text_from_impl(&impl_, "Default", default_code); + let code = generate_trait_impl_text_from_impl(&impl_, self_ty, "Default", default_code); builder.insert(insert_location.end(), code); }, ) } // FIXME: based on from utils::generate_impl_text_inner -fn generate_trait_impl_text_from_impl(impl_: &ast::Impl, trait_text: &str, code: &str) -> String { - let impl_ty = impl_.self_ty().unwrap(); +fn generate_trait_impl_text_from_impl( + impl_: &ast::Impl, + self_ty: ast::Type, + trait_text: &str, + code: &str, +) -> String { let generic_params = impl_.generic_param_list().map(|generic_params| { let lifetime_params = generic_params.lifetime_params().map(ast::GenericParam::LifetimeParam); @@ -109,7 +115,7 @@ fn generate_trait_impl_text_from_impl(impl_: &ast::Impl, trait_text: &str, code: if let Some(generic_params) = &generic_params { format_to!(buf, "{generic_params}") } - format_to!(buf, " {trait_text} for {impl_ty}"); + format_to!(buf, " {trait_text} for {self_ty}"); match impl_.where_clause() { Some(where_clause) => { @@ -136,7 +142,9 @@ fn is_default_implemented(ctx: &AssistContext<'_>, impl_: &Impl) -> bool { let default = FamousDefs(&ctx.sema, krate).core_default_Default(); let default_trait = match default { Some(value) => value, - None => return false, + // Return `true` to avoid providing the assist because it makes no sense + // to impl `Default` when it's missing. + None => return true, }; ty.impls_trait(db, default_trait, &[]) @@ -480,6 +488,7 @@ impl Example { check_assist_not_applicable( generate_default_from_new, r#" +//- minicore: default struct Example { _inner: () } impl Example { @@ -655,4 +664,23 @@ mod test { "#, ); } + + #[test] + fn not_applicable_when_default_lang_item_is_missing() { + check_assist_not_applicable( + generate_default_from_new, + r#" +struct S; +impl S { + fn new$0() -> Self {} +} +"#, + ); + } + + #[test] + fn not_applicable_for_missing_self_ty() { + // Regression test for #15398. + check_assist_not_applicable(generate_default_from_new, "impl { fn new$0() -> Self {} }"); + } } diff --git a/crates/ide-assists/src/tests/generated.rs b/crates/ide-assists/src/tests/generated.rs index ec3822c3d119a..6eadc3dbcbccd 100644 --- a/crates/ide-assists/src/tests/generated.rs +++ b/crates/ide-assists/src/tests/generated.rs @@ -952,6 +952,7 @@ fn doctest_generate_default_from_new() { check_doc_test( "generate_default_from_new", r#####" +//- minicore: default struct Example { _inner: () } impl Example {