diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index d5951a9420..2ccdc8c042 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -7,9 +7,10 @@ # prettier format f247090558c9ba3c551566eae5882b7ca865225f -# subtree syncs -932d85b52946d917deab2c23ead552f7f713b828 +# pre-josh subtree syncs 3e358a6827d83e8d6473913a5e304734aadfed04 +932d85b52946d917deab2c23ead552f7f713b828 9d2cb42a413e51deb50b36794a2e1605381878fc -f532576ac53ddcc666bc8d59e0b6437065e2f599 +b2f6fd4f961fc7e4fbfdb80cae2e6065f8436f15 c48062fe2ab9a2d913d1985a6b0aec4bf936bfc1 +f532576ac53ddcc666bc8d59e0b6437065e2f599 diff --git a/Cargo.lock b/Cargo.lock index 41dc440509..4e9319f13a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -495,7 +495,6 @@ dependencies = [ "hir-ty", "intern", "itertools", - "once_cell", "rustc-hash", "smallvec", "span", @@ -528,7 +527,6 @@ dependencies = [ "la-arena 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "limit", "mbe", - "once_cell", "ra-ap-rustc_abi", "ra-ap-rustc_parse_format", "rustc-hash", @@ -595,7 +593,6 @@ dependencies = [ "la-arena 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "limit", "nohash-hasher", - "once_cell", "oorandom", "project-model", "ra-ap-rustc_abi", @@ -691,7 +688,6 @@ dependencies = [ "hir", "ide-db", "itertools", - "once_cell", "smallvec", "stdx", "syntax", @@ -720,7 +716,6 @@ dependencies = [ "line-index 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "memchr", "nohash-hasher", - "once_cell", "parser", "profile", "rayon", @@ -746,7 +741,6 @@ dependencies = [ "hir", "ide-db", "itertools", - "once_cell", "paths", "serde_json", "stdx", @@ -1933,7 +1927,6 @@ dependencies = [ "expect-test", "indexmap", "itertools", - "once_cell", "parser", "ra-ap-rustc_lexer", "rayon", diff --git a/Cargo.toml b/Cargo.toml index 56e80e11e7..aa7bd2dc5f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,9 +19,10 @@ rowan.opt-level = 3 rustc-hash.opt-level = 3 smol_str.opt-level = 3 text-size.opt-level = 3 +serde.opt-level = 3 +salsa.opt-level = 3 # This speeds up `cargo xtask dist`. miniz_oxide.opt-level = 3 -salsa.opt-level = 3 [profile.release] incremental = true @@ -184,6 +185,8 @@ style = { level = "warn", priority = -1 } suspicious = { level = "warn", priority = -1 } ## allow following lints +# subjective +single_match = "allow" # () makes a fine error in most cases result_unit_err = "allow" # We don't expose public APIs that matter like this diff --git a/LICENSE-APACHE b/LICENSE-APACHE index 16fe87b06e..1b5ec8b78e 100644 --- a/LICENSE-APACHE +++ b/LICENSE-APACHE @@ -174,28 +174,3 @@ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - -Copyright [yyyy] [name of copyright owner] - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/crates/hir-def/Cargo.toml b/crates/hir-def/Cargo.toml index 5b9d227e31..c8ba5da449 100644 --- a/crates/hir-def/Cargo.toml +++ b/crates/hir-def/Cargo.toml @@ -23,7 +23,6 @@ fst = { version = "0.4.7", default-features = false } indexmap.workspace = true itertools.workspace = true la-arena.workspace = true -once_cell = "1.17.0" rustc-hash.workspace = true tracing.workspace = true smallvec.workspace = true diff --git a/crates/hir-def/src/body.rs b/crates/hir-def/src/body.rs index a988317e04..f5fe8f8770 100644 --- a/crates/hir-def/src/body.rs +++ b/crates/hir-def/src/body.rs @@ -14,7 +14,7 @@ use hir_expand::{name::Name, ExpandError, InFile}; use la_arena::{Arena, ArenaMap, Idx, RawIdx}; use rustc_hash::FxHashMap; use smallvec::SmallVec; -use span::MacroFileId; +use span::{Edition, MacroFileId}; use syntax::{ast, AstPtr, SyntaxNodePtr}; use triomphe::Arc; @@ -201,8 +201,13 @@ impl Body { self.block_scopes.iter().map(move |&block| (block, db.block_def_map(block))) } - pub fn pretty_print(&self, db: &dyn DefDatabase, owner: DefWithBodyId) -> String { - pretty::print_body_hir(db, self, owner) + pub fn pretty_print( + &self, + db: &dyn DefDatabase, + owner: DefWithBodyId, + edition: Edition, + ) -> String { + pretty::print_body_hir(db, self, owner, edition) } pub fn pretty_print_expr( @@ -210,8 +215,9 @@ impl Body { db: &dyn DefDatabase, owner: DefWithBodyId, expr: ExprId, + edition: Edition, ) -> String { - pretty::print_expr_hir(db, self, owner, expr) + pretty::print_expr_hir(db, self, owner, expr, edition) } fn new( diff --git a/crates/hir-def/src/body/pretty.rs b/crates/hir-def/src/body/pretty.rs index edaee60937..55740a68ac 100644 --- a/crates/hir-def/src/body/pretty.rs +++ b/crates/hir-def/src/body/pretty.rs @@ -3,6 +3,7 @@ use std::fmt::{self, Write}; use itertools::Itertools; +use span::Edition; use crate::{ hir::{ @@ -15,20 +16,26 @@ use crate::{ use super::*; -pub(super) fn print_body_hir(db: &dyn DefDatabase, body: &Body, owner: DefWithBodyId) -> String { +pub(super) fn print_body_hir( + db: &dyn DefDatabase, + body: &Body, + owner: DefWithBodyId, + edition: Edition, +) -> String { let header = match owner { - DefWithBodyId::FunctionId(it) => { - it.lookup(db).id.resolved(db, |it| format!("fn {}", it.name.display(db.upcast()))) - } + DefWithBodyId::FunctionId(it) => it + .lookup(db) + .id + .resolved(db, |it| format!("fn {}", it.name.display(db.upcast(), edition))), DefWithBodyId::StaticId(it) => it .lookup(db) .id - .resolved(db, |it| format!("static {} = ", it.name.display(db.upcast()))), + .resolved(db, |it| format!("static {} = ", it.name.display(db.upcast(), edition))), DefWithBodyId::ConstId(it) => it.lookup(db).id.resolved(db, |it| { format!( "const {} = ", match &it.name { - Some(name) => name.display(db.upcast()).to_string(), + Some(name) => name.display(db.upcast(), edition).to_string(), None => "_".to_owned(), } ) @@ -39,13 +46,13 @@ pub(super) fn print_body_hir(db: &dyn DefDatabase, body: &Body, owner: DefWithBo let enum_loc = loc.parent.lookup(db); format!( "enum {}::{}", - enum_loc.id.item_tree(db)[enum_loc.id.value].name.display(db.upcast()), - loc.id.item_tree(db)[loc.id.value].name.display(db.upcast()), + enum_loc.id.item_tree(db)[enum_loc.id.value].name.display(db.upcast(), edition), + loc.id.item_tree(db)[loc.id.value].name.display(db.upcast(), edition), ) } }; - let mut p = Printer { db, body, buf: header, indent_level: 0, needs_indent: false }; + let mut p = Printer { db, body, buf: header, indent_level: 0, needs_indent: false, edition }; if let DefWithBodyId::FunctionId(it) = owner { p.buf.push('('); let function_data = &db.function_data(it); @@ -86,8 +93,10 @@ pub(super) fn print_expr_hir( body: &Body, _owner: DefWithBodyId, expr: ExprId, + edition: Edition, ) -> String { - let mut p = Printer { db, body, buf: String::new(), indent_level: 0, needs_indent: false }; + let mut p = + Printer { db, body, buf: String::new(), indent_level: 0, needs_indent: false, edition }; p.print_expr(expr); p.buf } @@ -113,6 +122,7 @@ struct Printer<'a> { buf: String, indent_level: usize, needs_indent: bool, + edition: Edition, } impl Write for Printer<'_> { @@ -173,13 +183,14 @@ impl Printer<'_> { Expr::OffsetOf(offset_of) => { w!(self, "builtin#offset_of("); self.print_type_ref(&offset_of.container); + let edition = self.edition; w!( self, ", {})", offset_of .fields .iter() - .format_with(".", |field, f| f(&field.display(self.db.upcast()))) + .format_with(".", |field, f| f(&field.display(self.db.upcast(), edition))) ); } Expr::Path(path) => self.print_path(path), @@ -201,7 +212,7 @@ impl Printer<'_> { } Expr::Loop { body, label } => { if let Some(lbl) = label { - w!(self, "{}: ", self.body[*lbl].name.display(self.db.upcast())); + w!(self, "{}: ", self.body[*lbl].name.display(self.db.upcast(), self.edition)); } w!(self, "loop "); self.print_expr(*body); @@ -221,10 +232,11 @@ impl Printer<'_> { } Expr::MethodCall { receiver, method_name, args, generic_args } => { self.print_expr(*receiver); - w!(self, ".{}", method_name.display(self.db.upcast())); + w!(self, ".{}", method_name.display(self.db.upcast(), self.edition)); if let Some(args) = generic_args { w!(self, "::<"); - print_generic_args(self.db, args, self).unwrap(); + let edition = self.edition; + print_generic_args(self.db, args, self, edition).unwrap(); w!(self, ">"); } w!(self, "("); @@ -259,13 +271,13 @@ impl Printer<'_> { Expr::Continue { label } => { w!(self, "continue"); if let Some(lbl) = label { - w!(self, " {}", self.body[*lbl].name.display(self.db.upcast())); + w!(self, " {}", self.body[*lbl].name.display(self.db.upcast(), self.edition)); } } Expr::Break { expr, label } => { w!(self, "break"); if let Some(lbl) = label { - w!(self, " {}", self.body[*lbl].name.display(self.db.upcast())); + w!(self, " {}", self.body[*lbl].name.display(self.db.upcast(), self.edition)); } if let Some(expr) = expr { self.whitespace(); @@ -307,9 +319,10 @@ impl Printer<'_> { } w!(self, "{{"); + let edition = self.edition; self.indented(|p| { for field in &**fields { - w!(p, "{}: ", field.name.display(self.db.upcast())); + w!(p, "{}: ", field.name.display(self.db.upcast(), edition)); p.print_expr(field.expr); wln!(p, ","); } @@ -326,7 +339,7 @@ impl Printer<'_> { } Expr::Field { expr, name } => { self.print_expr(*expr); - w!(self, ".{}", name.display(self.db.upcast())); + w!(self, ".{}", name.display(self.db.upcast(), self.edition)); } Expr::Await { expr } => { self.print_expr(*expr); @@ -464,8 +477,9 @@ impl Printer<'_> { } Expr::Literal(lit) => self.print_literal(lit), Expr::Block { id: _, statements, tail, label } => { - let label = - label.map(|lbl| format!("{}: ", self.body[lbl].name.display(self.db.upcast()))); + let label = label.map(|lbl| { + format!("{}: ", self.body[lbl].name.display(self.db.upcast(), self.edition)) + }); self.print_block(label.as_deref(), statements, tail); } Expr::Unsafe { id: _, statements, tail } => { @@ -539,9 +553,10 @@ impl Printer<'_> { } w!(self, " {{"); + let edition = self.edition; self.indented(|p| { for arg in args.iter() { - w!(p, "{}: ", arg.name.display(self.db.upcast())); + w!(p, "{}: ", arg.name.display(self.db.upcast(), edition)); p.print_pat(arg.pat); wln!(p, ","); } @@ -686,11 +701,13 @@ impl Printer<'_> { } fn print_type_ref(&mut self, ty: &TypeRef) { - print_type_ref(self.db, ty, self).unwrap(); + let edition = self.edition; + print_type_ref(self.db, ty, self, edition).unwrap(); } fn print_path(&mut self, path: &Path) { - print_path(self.db, path, self).unwrap(); + let edition = self.edition; + print_path(self.db, path, self, edition).unwrap(); } fn print_binding(&mut self, id: BindingId) { @@ -701,6 +718,6 @@ impl Printer<'_> { BindingAnnotation::Ref => "ref ", BindingAnnotation::RefMut => "ref mut ", }; - w!(self, "{}{}", mode, name.display(self.db.upcast())); + w!(self, "{}{}", mode, name.display(self.db.upcast(), self.edition)); } } diff --git a/crates/hir-def/src/body/tests.rs b/crates/hir-def/src/body/tests.rs index 0011d3a20c..38fc01d8fc 100644 --- a/crates/hir-def/src/body/tests.rs +++ b/crates/hir-def/src/body/tests.rs @@ -219,7 +219,7 @@ fn main() { }, ); }"#]] - .assert_eq(&body.pretty_print(&db, def)) + .assert_eq(&body.pretty_print(&db, def, Edition::CURRENT)) } #[test] @@ -285,7 +285,7 @@ impl SsrError { ), ); }"#]] - .assert_eq(&body.pretty_print(&db, def)) + .assert_eq(&body.pretty_print(&db, def, Edition::CURRENT)) } #[test] @@ -333,5 +333,5 @@ fn f(a: i32, b: u32) -> String { ); }; }"#]] - .assert_eq(&body.pretty_print(&db, def)) + .assert_eq(&body.pretty_print(&db, def, Edition::CURRENT)) } diff --git a/crates/hir-def/src/find_path.rs b/crates/hir-def/src/find_path.rs index 5a3a3e9189..f5e03e5281 100644 --- a/crates/hir-def/src/find_path.rs +++ b/crates/hir-def/src/find_path.rs @@ -651,6 +651,7 @@ mod tests { use expect_test::{expect, Expect}; use hir_expand::db::ExpandDatabase; use itertools::Itertools; + use span::Edition; use stdx::format_to; use syntax::ast::AstNode; use test_fixture::WithFixture; @@ -717,8 +718,10 @@ mod tests { "{:7}(imports {}): {}\n", format!("{:?}", prefix), if ignore_local_imports { '✖' } else { '✔' }, - found_path - .map_or_else(|| "".to_owned(), |it| it.display(&db).to_string()), + found_path.map_or_else( + || "".to_owned(), + |it| it.display(&db, Edition::CURRENT).to_string() + ), ); } expect.assert_eq(&res); diff --git a/crates/hir-def/src/generics.rs b/crates/hir-def/src/generics.rs index ebaaef66db..6c34ee086a 100644 --- a/crates/hir-def/src/generics.rs +++ b/crates/hir-def/src/generics.rs @@ -12,7 +12,6 @@ use hir_expand::{ }; use intern::Interned; use la_arena::{Arena, RawIdx}; -use once_cell::unsync::Lazy; use stdx::impl_from; use syntax::ast::{self, HasGenericParams, HasName, HasTypeBounds}; use triomphe::Arc; @@ -394,11 +393,16 @@ impl GenericParams { // Don't create an `Expander` if not needed since this // could cause a reparse after the `ItemTree` has been created due to the spanmap. - let mut expander = Lazy::new(|| { - (module.def_map(db), Expander::new(db, loc.id.file_id(), module)) - }); + let mut expander = None; for param in func_data.params.iter() { - generic_params.fill_implicit_impl_trait_args(db, &mut expander, param); + generic_params.fill_implicit_impl_trait_args( + db, + &mut expander, + &mut || { + (module.def_map(db), Expander::new(db, loc.id.file_id(), module)) + }, + param, + ); } Interned::new(generic_params.finish()) } @@ -597,7 +601,9 @@ impl GenericParamsCollector { fn fill_implicit_impl_trait_args( &mut self, db: &dyn DefDatabase, - exp: &mut Lazy<(Arc, Expander), impl FnOnce() -> (Arc, Expander)>, + // FIXME: Change this back to `LazyCell` if https://github.com/rust-lang/libs-team/issues/429 is accepted. + exp: &mut Option<(Arc, Expander)>, + exp_fill: &mut dyn FnMut() -> (Arc, Expander), type_ref: &TypeRef, ) { type_ref.walk(&mut |type_ref| { @@ -617,7 +623,7 @@ impl GenericParamsCollector { } if let TypeRef::Macro(mc) = type_ref { let macro_call = mc.to_node(db.upcast()); - let (def_map, expander) = &mut **exp; + let (def_map, expander) = exp.get_or_insert_with(&mut *exp_fill); let module = expander.module.local_id; let resolver = |path: &_| { @@ -637,8 +643,8 @@ impl GenericParamsCollector { { let ctx = expander.ctx(db); let type_ref = TypeRef::from_ast(&ctx, expanded.tree()); - self.fill_implicit_impl_trait_args(db, &mut *exp, &type_ref); - exp.1.exit(mark); + self.fill_implicit_impl_trait_args(db, &mut *exp, exp_fill, &type_ref); + exp.get_or_insert_with(&mut *exp_fill).1.exit(mark); } } }); diff --git a/crates/hir-def/src/hir/format_args.rs b/crates/hir-def/src/hir/format_args.rs index 390e7da677..e1c3bd25bc 100644 --- a/crates/hir-def/src/hir/format_args.rs +++ b/crates/hir-def/src/hir/format_args.rs @@ -250,7 +250,7 @@ pub(crate) fn parse( } } ArgRef::Name(name, span) => { - let name = Name::new(name, tt::IdentIsRaw::No, call_ctx); + let name = Name::new(name, call_ctx); if let Some((index, _)) = args.by_name(&name) { record_usage(name, span); // Name found in `args`, so we resolve it to its index. diff --git a/crates/hir-def/src/hir/type_ref.rs b/crates/hir-def/src/hir/type_ref.rs index 8f618b2d30..b74cd90f69 100644 --- a/crates/hir-def/src/hir/type_ref.rs +++ b/crates/hir-def/src/hir/type_ref.rs @@ -10,6 +10,7 @@ use hir_expand::{ AstId, }; use intern::{sym, Interned, Symbol}; +use span::Edition; use syntax::ast::{self, HasGenericArgs, HasName, IsString}; use crate::{ @@ -419,18 +420,22 @@ impl ConstRef { param.default_val().map(|default| Self::from_const_arg(lower_ctx, Some(default))) } - pub fn display<'a>(&'a self, db: &'a dyn ExpandDatabase) -> impl fmt::Display + 'a { - struct Display<'a>(&'a dyn ExpandDatabase, &'a ConstRef); + pub fn display<'a>( + &'a self, + db: &'a dyn ExpandDatabase, + edition: Edition, + ) -> impl fmt::Display + 'a { + struct Display<'a>(&'a dyn ExpandDatabase, &'a ConstRef, Edition); impl fmt::Display for Display<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self.1 { ConstRef::Scalar(s) => s.fmt(f), - ConstRef::Path(n) => n.display(self.0).fmt(f), + ConstRef::Path(n) => n.display(self.0, self.2).fmt(f), ConstRef::Complex(_) => f.write_str("{const}"), } } } - Display(db, self) + Display(db, self, edition) } // We special case literals and single identifiers, to speed up things. diff --git a/crates/hir-def/src/import_map.rs b/crates/hir-def/src/import_map.rs index 8cc022e4c6..9574e5d9cd 100644 --- a/crates/hir-def/src/import_map.rs +++ b/crates/hir-def/src/import_map.rs @@ -8,6 +8,7 @@ use hir_expand::name::Name; use itertools::Itertools; use rustc_hash::FxHashSet; use smallvec::SmallVec; +use span::Edition; use stdx::{format_to, TupleExt}; use syntax::ToSmolStr; use triomphe::Arc; @@ -66,7 +67,12 @@ impl ImportMap { for (k, v) in self.item_to_info_map.iter() { format_to!(out, "{:?} ({:?}) -> ", k, v.1); for v in &v.0 { - format_to!(out, "{}:{:?}, ", v.name.display(db.upcast()), v.container); + format_to!( + out, + "{}:{:?}, ", + v.name.display(db.upcast(), Edition::CURRENT), + v.container + ); } format_to!(out, "\n"); } @@ -83,7 +89,7 @@ impl ImportMap { // We've only collected items, whose name cannot be tuple field so unwrapping is fine. .flat_map(|(&item, (info, _))| { info.iter().enumerate().map(move |(idx, info)| { - (item, info.name.display(db.upcast()).to_smolstr(), idx as u32) + (item, info.name.unescaped().display(db.upcast()).to_smolstr(), idx as u32) }) }) .collect(); @@ -461,7 +467,7 @@ fn search_maps( query.search_mode.check( &query.query, query.case_sensitive, - &info.name.display(db.upcast()).to_smolstr(), + &info.name.unescaped().display(db.upcast()).to_smolstr(), ) }); res.extend(iter.map(TupleExt::head)); @@ -577,7 +583,7 @@ mod tests { Some(format!( "{}::{}", render_path(db, &trait_info[0]), - assoc_item_name.display(db.upcast()) + assoc_item_name.display(db.upcast(), Edition::CURRENT) )) } @@ -616,7 +622,7 @@ mod tests { module = parent; } - segments.iter().rev().map(|it| it.display(db.upcast())).join("::") + segments.iter().rev().map(|it| it.display(db.upcast(), Edition::CURRENT)).join("::") } #[test] diff --git a/crates/hir-def/src/item_scope.rs b/crates/hir-def/src/item_scope.rs index df6b1f55c1..a04f12cab7 100644 --- a/crates/hir-def/src/item_scope.rs +++ b/crates/hir-def/src/item_scope.rs @@ -1,14 +1,16 @@ //! Describes items defined or visible (ie, imported) in a certain scope. //! This is shared between modules and blocks. +use std::sync::LazyLock; + use base_db::CrateId; use hir_expand::{attrs::AttrId, db::ExpandDatabase, name::Name, AstId, MacroCallId}; use indexmap::map::Entry; use itertools::Itertools; use la_arena::Idx; -use once_cell::sync::Lazy; use rustc_hash::{FxHashMap, FxHashSet}; use smallvec::{smallvec, SmallVec}; +use span::Edition; use stdx::format_to; use syntax::ast; @@ -129,7 +131,7 @@ struct DeriveMacroInvocation { derive_call_ids: SmallVec<[Option; 1]>, } -pub(crate) static BUILTIN_SCOPE: Lazy> = Lazy::new(|| { +pub(crate) static BUILTIN_SCOPE: LazyLock> = LazyLock::new(|| { BuiltinType::all_builtin_types() .iter() .map(|(name, ty)| (name.clone(), PerNs::types((*ty).into(), Visibility::Public, None))) @@ -706,7 +708,7 @@ impl ItemScope { format_to!( buf, "{}:", - name.map_or("_".to_owned(), |name| name.display(db).to_string()) + name.map_or("_".to_owned(), |name| name.display(db, Edition::LATEST).to_string()) ); if let Some((.., i)) = def.types { diff --git a/crates/hir-def/src/item_tree.rs b/crates/hir-def/src/item_tree.rs index 28eebb286e..f16230e1dc 100644 --- a/crates/hir-def/src/item_tree.rs +++ b/crates/hir-def/src/item_tree.rs @@ -40,6 +40,7 @@ use std::{ fmt::{self, Debug}, hash::{Hash, Hasher}, ops::{Index, Range}, + sync::OnceLock, }; use ast::{AstNode, StructKind}; @@ -48,10 +49,9 @@ use either::Either; use hir_expand::{attrs::RawAttrs, name::Name, ExpandTo, HirFileId, InFile}; use intern::{Interned, Symbol}; use la_arena::{Arena, Idx, RawIdx}; -use once_cell::sync::OnceCell; use rustc_hash::FxHashMap; use smallvec::SmallVec; -use span::{AstIdNode, FileAstId, SyntaxContextId}; +use span::{AstIdNode, Edition, FileAstId, SyntaxContextId}; use stdx::never; use syntax::{ast, match_ast, SyntaxKind}; use triomphe::Arc; @@ -101,7 +101,7 @@ pub struct ItemTree { impl ItemTree { pub(crate) fn file_item_tree_query(db: &dyn DefDatabase, file_id: HirFileId) -> Arc { let _p = tracing::info_span!("file_item_tree_query", ?file_id).entered(); - static EMPTY: OnceCell> = OnceCell::new(); + static EMPTY: OnceLock> = OnceLock::new(); let syntax = db.parse_or_expand(file_id); @@ -152,7 +152,7 @@ impl ItemTree { pub(crate) fn block_item_tree_query(db: &dyn DefDatabase, block: BlockId) -> Arc { let _p = tracing::info_span!("block_item_tree_query", ?block).entered(); - static EMPTY: OnceCell> = OnceCell::new(); + static EMPTY: OnceLock> = OnceLock::new(); let loc = block.lookup(db); let block = loc.ast_id.to_node(db.upcast()); @@ -199,8 +199,8 @@ impl ItemTree { Attrs::filter(db, krate, self.raw_attrs(of).clone()) } - pub fn pretty_print(&self, db: &dyn DefDatabase) -> String { - pretty::print_item_tree(db, self) + pub fn pretty_print(&self, db: &dyn DefDatabase, edition: Edition) -> String { + pretty::print_item_tree(db, self, edition) } fn data(&self) -> &ItemTreeData { @@ -626,9 +626,9 @@ impl Index for ItemTree { type Output = RawVisibility; fn index(&self, index: RawVisibilityId) -> &Self::Output { static VIS_PUB: RawVisibility = RawVisibility::Public; - static VIS_PRIV_IMPLICIT: OnceCell = OnceCell::new(); - static VIS_PRIV_EXPLICIT: OnceCell = OnceCell::new(); - static VIS_PUB_CRATE: OnceCell = OnceCell::new(); + static VIS_PRIV_IMPLICIT: OnceLock = OnceLock::new(); + static VIS_PRIV_EXPLICIT: OnceLock = OnceLock::new(); + static VIS_PUB_CRATE: OnceLock = OnceLock::new(); match index { RawVisibilityId::PRIV_IMPLICIT => VIS_PRIV_IMPLICIT.get_or_init(|| { diff --git a/crates/hir-def/src/item_tree/pretty.rs b/crates/hir-def/src/item_tree/pretty.rs index 740759e6e3..b5a65abce8 100644 --- a/crates/hir-def/src/item_tree/pretty.rs +++ b/crates/hir-def/src/item_tree/pretty.rs @@ -3,7 +3,7 @@ use std::fmt::{self, Write}; use la_arena::{Idx, RawIdx}; -use span::ErasedFileAstId; +use span::{Edition, ErasedFileAstId}; use crate::{ generics::{TypeOrConstParamData, WherePredicate, WherePredicateTypeTarget}, @@ -18,8 +18,9 @@ use crate::{ visibility::RawVisibility, }; -pub(super) fn print_item_tree(db: &dyn DefDatabase, tree: &ItemTree) -> String { - let mut p = Printer { db, tree, buf: String::new(), indent_level: 0, needs_indent: true }; +pub(super) fn print_item_tree(db: &dyn DefDatabase, tree: &ItemTree, edition: Edition) -> String { + let mut p = + Printer { db, tree, buf: String::new(), indent_level: 0, needs_indent: true, edition }; if let Some(attrs) = tree.attrs.get(&AttrOwner::TopLevel) { p.print_attrs(attrs, true, "\n"); @@ -56,6 +57,7 @@ struct Printer<'a> { buf: String, indent_level: usize, needs_indent: bool, + edition: Edition, } impl Printer<'_> { @@ -97,7 +99,7 @@ impl Printer<'_> { self, "#{}[{}{}]{}", inner, - attr.path.display(self.db.upcast()), + attr.path.display(self.db.upcast(), self.edition), attr.input.as_ref().map(|it| it.to_string()).unwrap_or_default(), separated_by, ); @@ -113,13 +115,14 @@ impl Printer<'_> { fn print_visibility(&mut self, vis: RawVisibilityId) { match &self.tree[vis] { RawVisibility::Module(path, _expl) => { - w!(self, "pub({}) ", path.display(self.db.upcast())) + w!(self, "pub({}) ", path.display(self.db.upcast(), self.edition)) } RawVisibility::Public => w!(self, "pub "), }; } fn print_fields(&mut self, parent: FieldParent, kind: FieldsShape, fields: &[Field]) { + let edition = self.edition; match kind { FieldsShape::Record => { self.whitespace(); @@ -131,7 +134,7 @@ impl Printer<'_> { "\n", ); this.print_visibility(*visibility); - w!(this, "{}: ", name.display(self.db.upcast())); + w!(this, "{}: ", name.display(self.db.upcast(), edition)); this.print_type_ref(type_ref); wln!(this, ","); } @@ -147,7 +150,7 @@ impl Printer<'_> { "\n", ); this.print_visibility(*visibility); - w!(this, "{}: ", name.display(self.db.upcast())); + w!(this, "{}: ", name.display(self.db.upcast(), edition)); this.print_type_ref(type_ref); wln!(this, ","); } @@ -186,20 +189,20 @@ impl Printer<'_> { fn print_use_tree(&mut self, use_tree: &UseTree) { match &use_tree.kind { UseTreeKind::Single { path, alias } => { - w!(self, "{}", path.display(self.db.upcast())); + w!(self, "{}", path.display(self.db.upcast(), self.edition)); if let Some(alias) = alias { - w!(self, " as {}", alias); + w!(self, " as {}", alias.display(self.edition)); } } UseTreeKind::Glob { path } => { if let Some(path) = path { - w!(self, "{}::", path.display(self.db.upcast())); + w!(self, "{}::", path.display(self.db.upcast(), self.edition)); } w!(self, "*"); } UseTreeKind::Prefixed { prefix, list } => { if let Some(prefix) = prefix { - w!(self, "{}::", prefix.display(self.db.upcast())); + w!(self, "{}::", prefix.display(self.db.upcast(), self.edition)); } w!(self, "{{"); for (i, tree) in list.iter().enumerate() { @@ -229,9 +232,9 @@ impl Printer<'_> { let ExternCrate { name, alias, visibility, ast_id } = &self.tree[it]; self.print_ast_id(ast_id.erase()); self.print_visibility(*visibility); - w!(self, "extern crate {}", name.display(self.db.upcast())); + w!(self, "extern crate {}", name.display(self.db.upcast(), self.edition)); if let Some(alias) = alias { - w!(self, " as {}", alias); + w!(self, " as {}", alias.display(self.edition)); } wln!(self, ";"); } @@ -278,7 +281,7 @@ impl Printer<'_> { if let Some(abi) = abi { w!(self, "extern \"{}\" ", abi); } - w!(self, "fn {}", name.display(self.db.upcast())); + w!(self, "fn {}", name.display(self.db.upcast(), self.edition)); self.print_generic_params(explicit_generic_params, it.into()); w!(self, "("); if !params.is_empty() { @@ -314,7 +317,7 @@ impl Printer<'_> { &self.tree[it]; self.print_ast_id(ast_id.erase()); self.print_visibility(*visibility); - w!(self, "struct {}", name.display(self.db.upcast())); + w!(self, "struct {}", name.display(self.db.upcast(), self.edition)); self.print_generic_params(generic_params, it.into()); self.print_fields_and_where_clause( FieldParent::Struct(it), @@ -332,7 +335,7 @@ impl Printer<'_> { let Union { name, visibility, fields, generic_params, ast_id } = &self.tree[it]; self.print_ast_id(ast_id.erase()); self.print_visibility(*visibility); - w!(self, "union {}", name.display(self.db.upcast())); + w!(self, "union {}", name.display(self.db.upcast(), self.edition)); self.print_generic_params(generic_params, it.into()); self.print_fields_and_where_clause( FieldParent::Union(it), @@ -346,15 +349,16 @@ impl Printer<'_> { let Enum { name, visibility, variants, generic_params, ast_id } = &self.tree[it]; self.print_ast_id(ast_id.erase()); self.print_visibility(*visibility); - w!(self, "enum {}", name.display(self.db.upcast())); + w!(self, "enum {}", name.display(self.db.upcast(), self.edition)); self.print_generic_params(generic_params, it.into()); self.print_where_clause_and_opening_brace(generic_params); + let edition = self.edition; self.indented(|this| { for variant in FileItemTreeId::range_iter(variants.clone()) { let Variant { name, fields, shape: kind, ast_id } = &this.tree[variant]; this.print_ast_id(ast_id.erase()); this.print_attrs_of(variant, "\n"); - w!(this, "{}", name.display(self.db.upcast())); + w!(this, "{}", name.display(self.db.upcast(), edition)); this.print_fields(FieldParent::Variant(variant), *kind, fields); wln!(this, ","); } @@ -367,7 +371,7 @@ impl Printer<'_> { self.print_visibility(*visibility); w!(self, "const "); match name { - Some(name) => w!(self, "{}", name.display(self.db.upcast())), + Some(name) => w!(self, "{}", name.display(self.db.upcast(), self.edition)), None => w!(self, "_"), } w!(self, ": "); @@ -382,7 +386,7 @@ impl Printer<'_> { if *mutable { w!(self, "mut "); } - w!(self, "{}: ", name.display(self.db.upcast())); + w!(self, "{}: ", name.display(self.db.upcast(), self.edition)); self.print_type_ref(type_ref); w!(self, " = _;"); wln!(self); @@ -398,7 +402,7 @@ impl Printer<'_> { if *is_auto { w!(self, "auto "); } - w!(self, "trait {}", name.display(self.db.upcast())); + w!(self, "trait {}", name.display(self.db.upcast(), self.edition)); self.print_generic_params(generic_params, it.into()); self.print_where_clause_and_opening_brace(generic_params); self.indented(|this| { @@ -412,7 +416,7 @@ impl Printer<'_> { let TraitAlias { name, visibility, generic_params, ast_id } = &self.tree[it]; self.print_ast_id(ast_id.erase()); self.print_visibility(*visibility); - w!(self, "trait {}", name.display(self.db.upcast())); + w!(self, "trait {}", name.display(self.db.upcast(), self.edition)); self.print_generic_params(generic_params, it.into()); w!(self, " = "); self.print_where_clause(generic_params); @@ -457,7 +461,7 @@ impl Printer<'_> { &self.tree[it]; self.print_ast_id(ast_id.erase()); self.print_visibility(*visibility); - w!(self, "type {}", name.display(self.db.upcast())); + w!(self, "type {}", name.display(self.db.upcast(), self.edition)); self.print_generic_params(generic_params, it.into()); if !bounds.is_empty() { w!(self, ": "); @@ -475,7 +479,7 @@ impl Printer<'_> { let Mod { name, visibility, kind, ast_id } = &self.tree[it]; self.print_ast_id(ast_id.erase()); self.print_visibility(*visibility); - w!(self, "mod {}", name.display(self.db.upcast())); + w!(self, "mod {}", name.display(self.db.upcast(), self.edition)); match kind { ModKind::Inline { items } => { w!(self, " {{"); @@ -500,18 +504,22 @@ impl Printer<'_> { ctxt, expand_to ); - wln!(self, "{}!(...);", path.display(self.db.upcast())); + wln!(self, "{}!(...);", path.display(self.db.upcast(), self.edition)); } ModItem::MacroRules(it) => { let MacroRules { name, ast_id } = &self.tree[it]; self.print_ast_id(ast_id.erase()); - wln!(self, "macro_rules! {} {{ ... }}", name.display(self.db.upcast())); + wln!( + self, + "macro_rules! {} {{ ... }}", + name.display(self.db.upcast(), self.edition) + ); } ModItem::Macro2(it) => { let Macro2 { name, visibility, ast_id } = &self.tree[it]; self.print_ast_id(ast_id.erase()); self.print_visibility(*visibility); - wln!(self, "macro {} {{ ... }}", name.display(self.db.upcast())); + wln!(self, "macro {} {{ ... }}", name.display(self.db.upcast(), self.edition)); } } @@ -519,15 +527,18 @@ impl Printer<'_> { } fn print_type_ref(&mut self, type_ref: &TypeRef) { - print_type_ref(self.db, type_ref, self).unwrap(); + let edition = self.edition; + print_type_ref(self.db, type_ref, self, edition).unwrap(); } fn print_type_bounds(&mut self, bounds: &[Interned]) { - print_type_bounds(self.db, bounds, self).unwrap(); + let edition = self.edition; + print_type_bounds(self.db, bounds, self, edition).unwrap(); } fn print_path(&mut self, path: &Path) { - print_path(self.db, path, self).unwrap(); + let edition = self.edition; + print_path(self.db, path, self, edition).unwrap(); } fn print_generic_params(&mut self, params: &GenericParams, parent: GenericModItem) { @@ -543,7 +554,7 @@ impl Printer<'_> { } first = false; self.print_attrs_of(AttrOwner::LifetimeParamData(parent, idx), " "); - w!(self, "{}", lt.name.display(self.db.upcast())); + w!(self, "{}", lt.name.display(self.db.upcast(), self.edition)); } for (idx, x) in params.iter_type_or_consts() { if !first { @@ -553,11 +564,11 @@ impl Printer<'_> { self.print_attrs_of(AttrOwner::TypeOrConstParamData(parent, idx), " "); match x { TypeOrConstParamData::TypeParamData(ty) => match &ty.name { - Some(name) => w!(self, "{}", name.display(self.db.upcast())), + Some(name) => w!(self, "{}", name.display(self.db.upcast(), self.edition)), None => w!(self, "_anon_{}", idx.into_raw()), }, TypeOrConstParamData::ConstParamData(konst) => { - w!(self, "const {}: ", konst.name.display(self.db.upcast())); + w!(self, "const {}: ", konst.name.display(self.db.upcast(), self.edition)); self.print_type_ref(&konst.ty); } } @@ -580,6 +591,7 @@ impl Printer<'_> { } w!(self, "\nwhere"); + let edition = self.edition; self.indented(|this| { for (i, pred) in params.where_predicates().enumerate() { if i != 0 { @@ -592,8 +604,8 @@ impl Printer<'_> { wln!( this, "{}: {},", - target.name.display(self.db.upcast()), - bound.name.display(self.db.upcast()) + target.name.display(self.db.upcast(), edition), + bound.name.display(self.db.upcast(), edition) ); continue; } @@ -603,7 +615,7 @@ impl Printer<'_> { if i != 0 { w!(this, ", "); } - w!(this, "{}", lt.display(self.db.upcast())); + w!(this, "{}", lt.display(self.db.upcast(), edition)); } w!(this, "> "); (target, bound) @@ -613,7 +625,7 @@ impl Printer<'_> { match target { WherePredicateTypeTarget::TypeRef(ty) => this.print_type_ref(ty), WherePredicateTypeTarget::TypeOrConstParam(id) => match params[*id].name() { - Some(name) => w!(this, "{}", name.display(self.db.upcast())), + Some(name) => w!(this, "{}", name.display(self.db.upcast(), edition)), None => w!(this, "_anon_{}", id.into_raw()), }, } diff --git a/crates/hir-def/src/item_tree/tests.rs b/crates/hir-def/src/item_tree/tests.rs index c6930401a6..5c07369f4b 100644 --- a/crates/hir-def/src/item_tree/tests.rs +++ b/crates/hir-def/src/item_tree/tests.rs @@ -1,4 +1,5 @@ use expect_test::{expect, Expect}; +use span::Edition; use test_fixture::WithFixture; use crate::{db::DefDatabase, test_db::TestDB}; @@ -6,7 +7,7 @@ use crate::{db::DefDatabase, test_db::TestDB}; fn check(ra_fixture: &str, expect: Expect) { let (db, file_id) = TestDB::with_single_file(ra_fixture); let item_tree = db.file_item_tree(file_id.into()); - let pretty = item_tree.pretty_print(&db); + let pretty = item_tree.pretty_print(&db, Edition::CURRENT); expect.assert_eq(&pretty); } diff --git a/crates/hir-def/src/lang_item.rs b/crates/hir-def/src/lang_item.rs index a09fd658ae..166c965d14 100644 --- a/crates/hir-def/src/lang_item.rs +++ b/crates/hir-def/src/lang_item.rs @@ -74,6 +74,13 @@ impl LangItemTarget { _ => None, } } + + pub fn as_type_alias(self) -> Option { + match self { + LangItemTarget::TypeAlias(id) => Some(id), + _ => None, + } + } } #[derive(Default, Debug, Clone, PartialEq, Eq)] @@ -117,11 +124,19 @@ impl LangItems { match def { ModuleDefId::TraitId(trait_) => { lang_items.collect_lang_item(db, trait_, LangItemTarget::Trait); - db.trait_data(trait_).items.iter().for_each(|&(_, assoc_id)| { - if let AssocItemId::FunctionId(f) = assoc_id { - lang_items.collect_lang_item(db, f, LangItemTarget::Function); - } - }); + db.trait_data(trait_).items.iter().for_each( + |&(_, assoc_id)| match assoc_id { + AssocItemId::FunctionId(f) => { + lang_items.collect_lang_item(db, f, LangItemTarget::Function); + } + AssocItemId::TypeAliasId(alias) => lang_items.collect_lang_item( + db, + alias, + LangItemTarget::TypeAlias, + ), + AssocItemId::ConstId(_) => {} + }, + ); } ModuleDefId::AdtId(AdtId::EnumId(e)) => { lang_items.collect_lang_item(db, e, LangItemTarget::EnumId); @@ -453,6 +468,7 @@ language_item_table! { Context, sym::Context, context, Target::Struct, GenericRequirement::None; FuturePoll, sym::poll, future_poll_fn, Target::Method(MethodKind::Trait { body: false }), GenericRequirement::None; + FutureOutput, sym::future_output, future_output, Target::TypeAlias, GenericRequirement::None; Option, sym::Option, option_type, Target::Enum, GenericRequirement::None; OptionSome, sym::Some, option_some_variant, Target::Variant, GenericRequirement::None; @@ -467,6 +483,7 @@ language_item_table! { IntoFutureIntoFuture, sym::into_future, into_future_fn, Target::Method(MethodKind::Trait { body: false }), GenericRequirement::None; IntoIterIntoIter, sym::into_iter, into_iter_fn, Target::Method(MethodKind::Trait { body: false }), GenericRequirement::None; IteratorNext, sym::next, next_fn, Target::Method(MethodKind::Trait { body: false}), GenericRequirement::None; + Iterator, sym::iterator, iterator, Target::Trait, GenericRequirement::None; PinNewUnchecked, sym::new_unchecked, new_unchecked_fn, Target::Method(MethodKind::Inherent), GenericRequirement::None; diff --git a/crates/hir-def/src/lib.rs b/crates/hir-def/src/lib.rs index 4ced30c81d..0213bd904b 100644 --- a/crates/hir-def/src/lib.rs +++ b/crates/hir-def/src/lib.rs @@ -241,7 +241,7 @@ pub type StaticLoc = AssocItemLoc; impl_intern!(StaticId, StaticLoc, intern_static, lookup_intern_static); impl_loc!(StaticLoc, id: Static, container: ItemContainerId); -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct TraitId(salsa::InternId); pub type TraitLoc = ItemLoc; impl_intern!(TraitId, TraitLoc, intern_trait, lookup_intern_trait); diff --git a/crates/hir-def/src/macro_expansion_tests/mod.rs b/crates/hir-def/src/macro_expansion_tests/mod.rs index b430e2cefb..7f76119251 100644 --- a/crates/hir-def/src/macro_expansion_tests/mod.rs +++ b/crates/hir-def/src/macro_expansion_tests/mod.rs @@ -25,7 +25,7 @@ use hir_expand::{ InFile, MacroFileId, MacroFileIdExt, }; use intern::Symbol; -use span::Span; +use span::{Edition, Span}; use stdx::{format_to, format_to_acc}; use syntax::{ ast::{self, edit::IndentLevel}, @@ -257,21 +257,25 @@ fn pretty_print_macro_expansion( (T![;] | T!['{'] | T!['}'], _) => "\n", (_, T!['}']) => "\n", (IDENT | LIFETIME_IDENT, IDENT | LIFETIME_IDENT) => " ", - _ if prev_kind.is_keyword() && curr_kind.is_keyword() => " ", - (IDENT, _) if curr_kind.is_keyword() => " ", - (_, IDENT) if prev_kind.is_keyword() => " ", + _ if prev_kind.is_keyword(Edition::CURRENT) + && curr_kind.is_keyword(Edition::CURRENT) => + { + " " + } + (IDENT, _) if curr_kind.is_keyword(Edition::CURRENT) => " ", + (_, IDENT) if prev_kind.is_keyword(Edition::CURRENT) => " ", (T![>], IDENT) => " ", - (T![>], _) if curr_kind.is_keyword() => " ", + (T![>], _) if curr_kind.is_keyword(Edition::CURRENT) => " ", (T![->], _) | (_, T![->]) => " ", (T![&&], _) | (_, T![&&]) => " ", (T![,], _) => " ", (T![:], IDENT | T!['(']) => " ", - (T![:], _) if curr_kind.is_keyword() => " ", + (T![:], _) if curr_kind.is_keyword(Edition::CURRENT) => " ", (T![fn], T!['(']) => "", - (T![']'], _) if curr_kind.is_keyword() => " ", + (T![']'], _) if curr_kind.is_keyword(Edition::CURRENT) => " ", (T![']'], T![#]) => "\n", (T![Self], T![::]) => "", - _ if prev_kind.is_keyword() => " ", + _ if prev_kind.is_keyword(Edition::CURRENT) => " ", _ => "", }; diff --git a/crates/hir-def/src/nameres.rs b/crates/hir-def/src/nameres.rs index 8825e46336..11601c683e 100644 --- a/crates/hir-def/src/nameres.rs +++ b/crates/hir-def/src/nameres.rs @@ -328,6 +328,10 @@ impl DefMap { /// The module id of a crate or block root. pub const ROOT: LocalModuleId = LocalModuleId::from_raw(la_arena::RawIdx::from_u32(0)); + pub fn edition(&self) -> Edition { + self.data.edition + } + pub(crate) fn crate_def_map_query(db: &dyn DefDatabase, crate_id: CrateId) -> Arc { let crate_graph = db.crate_graph(); let krate = &crate_graph[crate_id]; @@ -550,7 +554,7 @@ impl DefMap { for (name, child) in map.modules[module].children.iter().sorted_by(|a, b| Ord::cmp(&a.0, &b.0)) { - let path = format!("{path}::{}", name.display(db.upcast())); + let path = format!("{path}::{}", name.display(db.upcast(), Edition::LATEST)); buf.push('\n'); go(buf, db, map, &path, *child); } diff --git a/crates/hir-def/src/nameres/collector.rs b/crates/hir-def/src/nameres/collector.rs index debc5a4432..96db3db8f0 100644 --- a/crates/hir-def/src/nameres/collector.rs +++ b/crates/hir-def/src/nameres/collector.rs @@ -548,7 +548,7 @@ impl DefCollector<'_> { types => { tracing::debug!( "could not resolve prelude path `{}` to module (resolved to {:?})", - path.display(self.db.upcast()), + path.display(self.db.upcast(), Edition::LATEST), types ); } @@ -768,7 +768,7 @@ impl DefCollector<'_> { } fn resolve_import(&self, module_id: LocalModuleId, import: &Import) -> PartialResolvedImport { - let _p = tracing::info_span!("resolve_import", import_path = %import.path.display(self.db.upcast())) + let _p = tracing::info_span!("resolve_import", import_path = %import.path.display(self.db.upcast(), Edition::LATEST)) .entered(); tracing::debug!("resolving import: {:?} ({:?})", import, self.def_map.data.edition); match import.source { @@ -1606,7 +1606,11 @@ impl ModCollector<'_, '_> { // Prelude module is always considered to be `#[macro_use]`. if let Some((prelude_module, _use)) = self.def_collector.def_map.prelude { - if prelude_module.krate != krate && is_crate_root { + // Don't insert macros from the prelude into blocks, as they can be shadowed by other macros. + if prelude_module.krate != krate + && is_crate_root + && self.def_collector.def_map.block.is_none() + { cov_mark::hit!(prelude_is_macro_use); self.def_collector.import_macros_from_extern_crate( prelude_module.krate, @@ -2151,7 +2155,7 @@ impl ModCollector<'_, '_> { } tracing::debug!( "non-builtin attribute {}", - attr.path.display(self.def_collector.db.upcast()) + attr.path.display(self.def_collector.db.upcast(), Edition::LATEST) ); let ast_id = AstIdWithPath::new( @@ -2286,8 +2290,8 @@ impl ModCollector<'_, '_> { stdx::always!( name == mac.name, "built-in macro {} has #[rustc_builtin_macro] which declares different name {}", - mac.name.display(self.def_collector.db.upcast()), - name.display(self.def_collector.db.upcast()) + mac.name.display(self.def_collector.db.upcast(), Edition::LATEST), + name.display(self.def_collector.db.upcast(), Edition::LATEST), ); helpers_opt = Some(helpers); } diff --git a/crates/hir-def/src/nameres/tests/macros.rs b/crates/hir-def/src/nameres/tests/macros.rs index 390c934f6d..a05c4dcf9b 100644 --- a/crates/hir-def/src/nameres/tests/macros.rs +++ b/crates/hir-def/src/nameres/tests/macros.rs @@ -1,6 +1,7 @@ use expect_test::expect; use itertools::Itertools; +use span::Edition; use super::*; @@ -1100,7 +1101,7 @@ pub fn derive_macro_2(_item: TokenStream) -> TokenStream { assert_eq!(def_map.data.exported_derives.len(), 1); match def_map.data.exported_derives.values().next() { Some(helpers) => match &**helpers { - [attr] => assert_eq!(attr.display(&db).to_string(), "helper_attr"), + [attr] => assert_eq!(attr.display(&db, Edition::CURRENT).to_string(), "helper_attr"), _ => unreachable!(), }, _ => unreachable!(), @@ -1456,7 +1457,7 @@ fn proc_attr(a: TokenStream, b: TokenStream) -> TokenStream { a } let actual = def_map .macro_use_prelude .keys() - .map(|name| name.display(&db).to_string()) + .map(|name| name.display(&db, Edition::CURRENT).to_string()) .sorted() .join("\n"); diff --git a/crates/hir-def/src/nameres/tests/mod_resolution.rs b/crates/hir-def/src/nameres/tests/mod_resolution.rs index 1327d9aa62..071b55c83d 100644 --- a/crates/hir-def/src/nameres/tests/mod_resolution.rs +++ b/crates/hir-def/src/nameres/tests/mod_resolution.rs @@ -144,14 +144,14 @@ pub struct Baz; crate::r#async Bar: t v - foo: t r#async: t - - crate::r#async::foo - Foo: t v + foo: t crate::r#async::r#async Baz: t v + + crate::r#async::foo + Foo: t v "#]], ); } diff --git a/crates/hir-def/src/path.rs b/crates/hir-def/src/path.rs index f90bc954a9..077863c0c9 100644 --- a/crates/hir-def/src/path.rs +++ b/crates/hir-def/src/path.rs @@ -13,7 +13,8 @@ use crate::{ }; use hir_expand::name::Name; use intern::Interned; -use syntax::{ast, ToSmolStr}; +use span::Edition; +use syntax::ast; pub use hir_expand::mod_path::{path, ModPath, PathKind}; @@ -25,11 +26,21 @@ pub enum ImportAlias { Alias(Name), } -impl Display for ImportAlias { +impl ImportAlias { + pub fn display(&self, edition: Edition) -> impl Display + '_ { + ImportAliasDisplay { value: self, edition } + } +} + +struct ImportAliasDisplay<'a> { + value: &'a ImportAlias, + edition: Edition, +} +impl Display for ImportAliasDisplay<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { + match self.value { ImportAlias::Underscore => f.write_str("_"), - ImportAlias::Alias(name) => f.write_str(&name.display_no_db().to_smolstr()), + ImportAlias::Alias(name) => Display::fmt(&name.display_no_db(self.edition), f), } } } diff --git a/crates/hir-def/src/pretty.rs b/crates/hir-def/src/pretty.rs index 3ee88b536f..d5ef17a91f 100644 --- a/crates/hir-def/src/pretty.rs +++ b/crates/hir-def/src/pretty.rs @@ -5,6 +5,7 @@ use std::fmt::{self, Write}; use hir_expand::mod_path::PathKind; use intern::Interned; use itertools::Itertools; +use span::Edition; use crate::{ db::DefDatabase, @@ -13,46 +14,51 @@ use crate::{ type_ref::{Mutability, TraitBoundModifier, TypeBound, TypeRef}, }; -pub(crate) fn print_path(db: &dyn DefDatabase, path: &Path, buf: &mut dyn Write) -> fmt::Result { +pub(crate) fn print_path( + db: &dyn DefDatabase, + path: &Path, + buf: &mut dyn Write, + edition: Edition, +) -> fmt::Result { if let Path::LangItem(it, s) = path { write!(buf, "builtin#lang(")?; match *it { LangItemTarget::ImplDef(it) => write!(buf, "{it:?}")?, LangItemTarget::EnumId(it) => { - write!(buf, "{}", db.enum_data(it).name.display(db.upcast()))? + write!(buf, "{}", db.enum_data(it).name.display(db.upcast(), edition))? } LangItemTarget::Function(it) => { - write!(buf, "{}", db.function_data(it).name.display(db.upcast()))? + write!(buf, "{}", db.function_data(it).name.display(db.upcast(), edition))? } LangItemTarget::Static(it) => { - write!(buf, "{}", db.static_data(it).name.display(db.upcast()))? + write!(buf, "{}", db.static_data(it).name.display(db.upcast(), edition))? } LangItemTarget::Struct(it) => { - write!(buf, "{}", db.struct_data(it).name.display(db.upcast()))? + write!(buf, "{}", db.struct_data(it).name.display(db.upcast(), edition))? } LangItemTarget::Union(it) => { - write!(buf, "{}", db.union_data(it).name.display(db.upcast()))? + write!(buf, "{}", db.union_data(it).name.display(db.upcast(), edition))? } LangItemTarget::TypeAlias(it) => { - write!(buf, "{}", db.type_alias_data(it).name.display(db.upcast()))? + write!(buf, "{}", db.type_alias_data(it).name.display(db.upcast(), edition))? } LangItemTarget::Trait(it) => { - write!(buf, "{}", db.trait_data(it).name.display(db.upcast()))? + write!(buf, "{}", db.trait_data(it).name.display(db.upcast(), edition))? } LangItemTarget::EnumVariant(it) => { - write!(buf, "{}", db.enum_variant_data(it).name.display(db.upcast()))? + write!(buf, "{}", db.enum_variant_data(it).name.display(db.upcast(), edition))? } } if let Some(s) = s { - write!(buf, "::{}", s.display(db.upcast()))?; + write!(buf, "::{}", s.display(db.upcast(), edition))?; } return write!(buf, ")"); } match path.type_anchor() { Some(anchor) => { write!(buf, "<")?; - print_type_ref(db, anchor, buf)?; + print_type_ref(db, anchor, buf, edition)?; write!(buf, ">::")?; } None => match path.kind() { @@ -78,10 +84,10 @@ pub(crate) fn print_path(db: &dyn DefDatabase, path: &Path, buf: &mut dyn Write) write!(buf, "::")?; } - write!(buf, "{}", segment.name.display(db.upcast()))?; + write!(buf, "{}", segment.name.display(db.upcast(), edition))?; if let Some(generics) = segment.args_and_bindings { write!(buf, "::<")?; - print_generic_args(db, generics, buf)?; + print_generic_args(db, generics, buf, edition)?; write!(buf, ">")?; } @@ -94,12 +100,13 @@ pub(crate) fn print_generic_args( db: &dyn DefDatabase, generics: &GenericArgs, buf: &mut dyn Write, + edition: Edition, ) -> fmt::Result { let mut first = true; let args = if generics.has_self_type { let (self_ty, args) = generics.args.split_first().unwrap(); write!(buf, "Self=")?; - print_generic_arg(db, self_ty, buf)?; + print_generic_arg(db, self_ty, buf, edition)?; first = false; args } else { @@ -110,21 +117,21 @@ pub(crate) fn print_generic_args( write!(buf, ", ")?; } first = false; - print_generic_arg(db, arg, buf)?; + print_generic_arg(db, arg, buf, edition)?; } for binding in generics.bindings.iter() { if !first { write!(buf, ", ")?; } first = false; - write!(buf, "{}", binding.name.display(db.upcast()))?; + write!(buf, "{}", binding.name.display(db.upcast(), edition))?; if !binding.bounds.is_empty() { write!(buf, ": ")?; - print_type_bounds(db, &binding.bounds, buf)?; + print_type_bounds(db, &binding.bounds, buf, edition)?; } if let Some(ty) = &binding.type_ref { write!(buf, " = ")?; - print_type_ref(db, ty, buf)?; + print_type_ref(db, ty, buf, edition)?; } } Ok(()) @@ -134,11 +141,12 @@ pub(crate) fn print_generic_arg( db: &dyn DefDatabase, arg: &GenericArg, buf: &mut dyn Write, + edition: Edition, ) -> fmt::Result { match arg { - GenericArg::Type(ty) => print_type_ref(db, ty, buf), - GenericArg::Const(c) => write!(buf, "{}", c.display(db.upcast())), - GenericArg::Lifetime(lt) => write!(buf, "{}", lt.name.display(db.upcast())), + GenericArg::Type(ty) => print_type_ref(db, ty, buf, edition), + GenericArg::Const(c) => write!(buf, "{}", c.display(db.upcast(), edition)), + GenericArg::Lifetime(lt) => write!(buf, "{}", lt.name.display(db.upcast(), edition)), } } @@ -146,6 +154,7 @@ pub(crate) fn print_type_ref( db: &dyn DefDatabase, type_ref: &TypeRef, buf: &mut dyn Write, + edition: Edition, ) -> fmt::Result { // FIXME: deduplicate with `HirDisplay` impl match type_ref { @@ -157,18 +166,18 @@ pub(crate) fn print_type_ref( if i != 0 { write!(buf, ", ")?; } - print_type_ref(db, field, buf)?; + print_type_ref(db, field, buf, edition)?; } write!(buf, ")")?; } - TypeRef::Path(path) => print_path(db, path, buf)?, + TypeRef::Path(path) => print_path(db, path, buf, edition)?, TypeRef::RawPtr(pointee, mtbl) => { let mtbl = match mtbl { Mutability::Shared => "*const", Mutability::Mut => "*mut", }; write!(buf, "{mtbl} ")?; - print_type_ref(db, pointee, buf)?; + print_type_ref(db, pointee, buf, edition)?; } TypeRef::Reference(pointee, lt, mtbl) => { let mtbl = match mtbl { @@ -177,19 +186,19 @@ pub(crate) fn print_type_ref( }; write!(buf, "&")?; if let Some(lt) = lt { - write!(buf, "{} ", lt.name.display(db.upcast()))?; + write!(buf, "{} ", lt.name.display(db.upcast(), edition))?; } write!(buf, "{mtbl}")?; - print_type_ref(db, pointee, buf)?; + print_type_ref(db, pointee, buf, edition)?; } TypeRef::Array(elem, len) => { write!(buf, "[")?; - print_type_ref(db, elem, buf)?; - write!(buf, "; {}]", len.display(db.upcast()))?; + print_type_ref(db, elem, buf, edition)?; + write!(buf, "; {}]", len.display(db.upcast(), edition))?; } TypeRef::Slice(elem) => { write!(buf, "[")?; - print_type_ref(db, elem, buf)?; + print_type_ref(db, elem, buf, edition)?; write!(buf, "]")?; } TypeRef::Fn(args_and_ret, varargs, is_unsafe, abi) => { @@ -208,7 +217,7 @@ pub(crate) fn print_type_ref( if i != 0 { write!(buf, ", ")?; } - print_type_ref(db, typeref, buf)?; + print_type_ref(db, typeref, buf, edition)?; } if *varargs { if !args.is_empty() { @@ -217,7 +226,7 @@ pub(crate) fn print_type_ref( write!(buf, "...")?; } write!(buf, ") -> ")?; - print_type_ref(db, return_type, buf)?; + print_type_ref(db, return_type, buf, edition)?; } TypeRef::Macro(_ast_id) => { write!(buf, "")?; @@ -225,11 +234,11 @@ pub(crate) fn print_type_ref( TypeRef::Error => write!(buf, "{{unknown}}")?, TypeRef::ImplTrait(bounds) => { write!(buf, "impl ")?; - print_type_bounds(db, bounds, buf)?; + print_type_bounds(db, bounds, buf, edition)?; } TypeRef::DynTrait(bounds) => { write!(buf, "dyn ")?; - print_type_bounds(db, bounds, buf)?; + print_type_bounds(db, bounds, buf, edition)?; } } @@ -240,6 +249,7 @@ pub(crate) fn print_type_bounds( db: &dyn DefDatabase, bounds: &[Interned], buf: &mut dyn Write, + edition: Edition, ) -> fmt::Result { for (i, bound) in bounds.iter().enumerate() { if i != 0 { @@ -252,17 +262,17 @@ pub(crate) fn print_type_bounds( TraitBoundModifier::None => (), TraitBoundModifier::Maybe => write!(buf, "?")?, } - print_path(db, path, buf)?; + print_path(db, path, buf, edition)?; } TypeBound::ForLifetime(lifetimes, path) => { write!( buf, "for<{}> ", - lifetimes.iter().map(|it| it.display(db.upcast())).format(", ") + lifetimes.iter().map(|it| it.display(db.upcast(), edition)).format(", ") )?; - print_path(db, path, buf)?; + print_path(db, path, buf, edition)?; } - TypeBound::Lifetime(lt) => write!(buf, "{}", lt.name.display(db.upcast()))?, + TypeBound::Lifetime(lt) => write!(buf, "{}", lt.name.display(db.upcast(), edition))?, TypeBound::Error => write!(buf, "{{unknown}}")?, } } diff --git a/crates/hir-expand/src/eager.rs b/crates/hir-expand/src/eager.rs index 3528b2dde7..1dadfe2ba9 100644 --- a/crates/hir-expand/src/eager.rs +++ b/crates/hir-expand/src/eager.rs @@ -176,9 +176,10 @@ fn eager_macro_recur( Some(path) => match macro_resolver(&path) { Some(def) => def, None => { + let edition = db.crate_graph()[krate].edition; error = Some(ExpandError::other( span_map.span_at(call.syntax().text_range().start()), - format!("unresolved macro {}", path.display(db)), + format!("unresolved macro {}", path.display(db, edition)), )); offset += call.syntax().text_range().len(); continue; diff --git a/crates/hir-expand/src/files.rs b/crates/hir-expand/src/files.rs index 20f484f672..d41f69812e 100644 --- a/crates/hir-expand/src/files.rs +++ b/crates/hir-expand/src/files.rs @@ -461,3 +461,12 @@ impl InFile { Some(InRealFile::new(file_id, value)) } } + +impl InFile { + pub fn into_real_file(self) -> Result, InFile> { + match self.file_id.repr() { + HirFileIdRepr::FileId(file_id) => Ok(InRealFile { file_id, value: self.value }), + HirFileIdRepr::MacroFile(_) => Err(self), + } + } +} diff --git a/crates/hir-expand/src/lib.rs b/crates/hir-expand/src/lib.rs index 2bea902626..19c3c9c43f 100644 --- a/crates/hir-expand/src/lib.rs +++ b/crates/hir-expand/src/lib.rs @@ -192,7 +192,7 @@ impl ExpandErrorKind { ("overflow expanding the original macro".to_owned(), true) } ExpandErrorKind::Other(e) => ((**e).to_owned(), true), - ExpandErrorKind::ProcMacroPanic(e) => ((**e).to_owned(), true), + ExpandErrorKind::ProcMacroPanic(e) => (format!("proc-macro panicked: {e}"), true), } } } @@ -279,6 +279,7 @@ pub enum MacroCallKind { } pub trait HirFileIdExt { + fn edition(self, db: &dyn ExpandDatabase) -> Edition; /// Returns the original file of this macro call hierarchy. fn original_file(self, db: &dyn ExpandDatabase) -> EditionedFileId; @@ -293,6 +294,12 @@ pub trait HirFileIdExt { } impl HirFileIdExt for HirFileId { + fn edition(self, db: &dyn ExpandDatabase) -> Edition { + match self.repr() { + HirFileIdRepr::FileId(file_id) => file_id.edition(), + HirFileIdRepr::MacroFile(m) => m.macro_call_id.lookup(db).def.edition, + } + } fn original_file(self, db: &dyn ExpandDatabase) -> EditionedFileId { let mut file_id = self; loop { diff --git a/crates/hir-expand/src/mod_path.rs b/crates/hir-expand/src/mod_path.rs index 2c26fe414d..dcf2af3997 100644 --- a/crates/hir-expand/src/mod_path.rs +++ b/crates/hir-expand/src/mod_path.rs @@ -14,7 +14,7 @@ use crate::{ use base_db::CrateId; use intern::sym; use smallvec::SmallVec; -use span::SyntaxContextId; +use span::{Edition, SyntaxContextId}; use syntax::{ast, AstNode}; #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -140,8 +140,12 @@ impl ModPath { UnescapedModPath(self) } - pub fn display<'a>(&'a self, db: &'a dyn crate::db::ExpandDatabase) -> impl fmt::Display + 'a { - Display { db, path: self } + pub fn display<'a>( + &'a self, + db: &'a dyn crate::db::ExpandDatabase, + edition: Edition, + ) -> impl fmt::Display + 'a { + Display { db, path: self, edition } } } @@ -154,11 +158,12 @@ impl Extend for ModPath { struct Display<'a> { db: &'a dyn ExpandDatabase, path: &'a ModPath, + edition: Edition, } impl fmt::Display for Display<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - display_fmt_path(self.db, self.path, f, true) + display_fmt_path(self.db, self.path, f, Escape::IfNeeded(self.edition)) } } @@ -169,7 +174,7 @@ struct UnescapedDisplay<'a> { impl fmt::Display for UnescapedDisplay<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - display_fmt_path(self.db, self.path.0, f, false) + display_fmt_path(self.db, self.path.0, f, Escape::No) } } @@ -178,11 +183,17 @@ impl From for ModPath { ModPath::from_segments(PathKind::Plain, iter::once(name)) } } + +enum Escape { + No, + IfNeeded(Edition), +} + fn display_fmt_path( db: &dyn ExpandDatabase, path: &ModPath, f: &mut fmt::Formatter<'_>, - escaped: bool, + escaped: Escape, ) -> fmt::Result { let mut first_segment = true; let mut add_segment = |s| -> fmt::Result { @@ -210,10 +221,9 @@ fn display_fmt_path( f.write_str("::")?; } first_segment = false; - if escaped { - segment.display(db).fmt(f)?; - } else { - segment.unescaped().display(db).fmt(f)?; + match escaped { + Escape::IfNeeded(edition) => segment.display(db, edition).fmt(f)?, + Escape::No => segment.unescaped().display(db).fmt(f)?, } } Ok(()) @@ -322,9 +332,11 @@ fn convert_path_tt(db: &dyn ExpandDatabase, tt: &[tt::TokenTree]) -> Option PathKind::SELF, tt::Leaf::Ident(tt::Ident { sym: text, .. }) if *text == sym::super_ => { let mut deg = 1; - while let Some(tt::Leaf::Ident(tt::Ident { sym: text, span, is_raw })) = leaves.next() { + while let Some(tt::Leaf::Ident(tt::Ident { sym: text, span, is_raw: _ })) = + leaves.next() + { if *text != sym::super_ { - segments.push(Name::new_symbol_maybe_raw(text.clone(), *is_raw, span.ctx)); + segments.push(Name::new_symbol(text.clone(), span.ctx)); break; } deg += 1; @@ -333,19 +345,13 @@ fn convert_path_tt(db: &dyn ExpandDatabase, tt: &[tt::TokenTree]) -> Option PathKind::Crate, tt::Leaf::Ident(ident) => { - segments.push(Name::new_symbol_maybe_raw( - ident.sym.clone(), - ident.is_raw, - ident.span.ctx, - )); + segments.push(Name::new_symbol(ident.sym.clone(), ident.span.ctx)); PathKind::Plain } _ => return None, }; segments.extend(leaves.filter_map(|leaf| match leaf { - ::tt::Leaf::Ident(ident) => { - Some(Name::new_symbol_maybe_raw(ident.sym.clone(), ident.is_raw, ident.span.ctx)) - } + ::tt::Leaf::Ident(ident) => Some(Name::new_symbol(ident.sym.clone(), ident.span.ctx)), _ => None, })); Some(ModPath { kind, segments }) diff --git a/crates/hir-expand/src/name.rs b/crates/hir-expand/src/name.rs index d012d272d7..54313904a7 100644 --- a/crates/hir-expand/src/name.rs +++ b/crates/hir-expand/src/name.rs @@ -3,22 +3,22 @@ use std::fmt; use intern::{sym, Symbol}; -use span::SyntaxContextId; -use syntax::{ast, utils::is_raw_identifier}; +use span::{Edition, SyntaxContextId}; +use syntax::ast; +use syntax::utils::is_raw_identifier; /// `Name` is a wrapper around string, which is used in hir for both references /// and declarations. In theory, names should also carry hygiene info, but we are /// not there yet! /// -/// Note that `Name` holds and prints escaped name i.e. prefixed with "r#" when it -/// is a raw identifier. Use [`unescaped()`][Name::unescaped] when you need the -/// name without "r#". +/// Note that the rawness (`r#`) of names does not depend on whether they are written raw. +/// This is because we want to show (in completions etc.) names as raw depending on the needs +/// of the current crate, for example if it is edition 2021 complete `gen` even if the defining +/// crate is in edition 2024 and wrote `r#gen`, and the opposite holds as well. #[derive(Clone, PartialEq, Eq, Hash)] pub struct Name { symbol: Symbol, ctx: (), - // FIXME: We should probably encode rawness as a property here instead, once we have hygiene - // in here we've got 4 bytes of padding to fill anyways } impl fmt::Debug for Name { @@ -42,6 +42,7 @@ impl PartialOrd for Name { } } +// No need to strip `r#`, all comparisons are done against well-known symbols. impl PartialEq for Name { fn eq(&self, sym: &Symbol) -> bool { self.symbol == *sym @@ -55,16 +56,16 @@ impl PartialEq for Symbol { } /// Wrapper of `Name` to print the name without "r#" even when it is a raw identifier. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct UnescapedName<'a>(&'a Name); -impl UnescapedName<'_> { - pub fn display(&self, db: &dyn crate::db::ExpandDatabase) -> impl fmt::Display + '_ { +impl<'a> UnescapedName<'a> { + pub fn display(self, db: &dyn crate::db::ExpandDatabase) -> impl fmt::Display + 'a { _ = db; UnescapedDisplay { name: self } } #[doc(hidden)] - pub fn display_no_db(&self) -> impl fmt::Display + '_ { + pub fn display_no_db(self) -> impl fmt::Display + 'a { UnescapedDisplay { name: self } } } @@ -77,16 +78,9 @@ impl Name { Name { symbol: Symbol::intern(text), ctx: () } } - pub fn new(text: &str, raw: tt::IdentIsRaw, ctx: SyntaxContextId) -> Name { + pub fn new(text: &str, ctx: SyntaxContextId) -> Name { _ = ctx; - Name { - symbol: if raw.yes() { - Symbol::intern(&format!("{}{text}", raw.as_str())) - } else { - Symbol::intern(text) - }, - ctx: (), - } + Self::new_text(text) } pub fn new_tuple_field(idx: usize) -> Name { @@ -97,23 +91,9 @@ impl Name { Name { symbol: Symbol::intern(lt.text().as_str()), ctx: () } } - /// Shortcut to create a name from a string literal. - fn new_ref(text: &str) -> Name { - Name { symbol: Symbol::intern(text), ctx: () } - } - /// Resolve a name from the text of token. fn resolve(raw_text: &str) -> Name { - match raw_text.strip_prefix("r#") { - // When `raw_text` starts with "r#" but the name does not coincide with any - // keyword, we never need the prefix so we strip it. - Some(text) if !is_raw_identifier(text) => Name::new_ref(text), - // Keywords (in the current edition) *can* be used as a name in earlier editions of - // Rust, e.g. "try" in Rust 2015. Even in such cases, we keep track of them in their - // escaped form. - None if is_raw_identifier(raw_text) => Name::new_text(&format!("r#{}", raw_text)), - _ => Name::new_text(raw_text), - } + Name::new_text(raw_text.trim_start_matches("r#")) } /// A fake name for things missing in the source code. @@ -159,19 +139,23 @@ impl Name { UnescapedName(self) } - pub fn is_escaped(&self) -> bool { - self.symbol.as_str().starts_with("r#") + pub fn is_escaped(&self, edition: Edition) -> bool { + is_raw_identifier(self.symbol.as_str(), edition) } - pub fn display<'a>(&'a self, db: &dyn crate::db::ExpandDatabase) -> impl fmt::Display + 'a { + pub fn display<'a>( + &'a self, + db: &dyn crate::db::ExpandDatabase, + edition: Edition, + ) -> impl fmt::Display + 'a { _ = db; - Display { name: self } + self.display_no_db(edition) } // FIXME: Remove this #[doc(hidden)] - pub fn display_no_db(&self) -> impl fmt::Display + '_ { - Display { name: self } + pub fn display_no_db(&self, edition: Edition) -> impl fmt::Display + '_ { + Display { name: self, needs_escaping: is_raw_identifier(self.symbol.as_str(), edition) } } pub fn symbol(&self) -> &Symbol { @@ -183,39 +167,39 @@ impl Name { Self { symbol, ctx: () } } - pub fn new_symbol_maybe_raw(sym: Symbol, raw: tt::IdentIsRaw, ctx: SyntaxContextId) -> Self { - if raw.no() { - Self { symbol: sym, ctx: () } - } else { - Name::new(sym.as_str(), raw, ctx) - } - } - // FIXME: This needs to go once we have hygiene pub const fn new_symbol_root(sym: Symbol) -> Self { Self { symbol: sym, ctx: () } } + + #[inline] + pub fn eq_ident(&self, ident: &str) -> bool { + self.as_str() == ident.trim_start_matches("r#") + } } struct Display<'a> { name: &'a Name, + needs_escaping: bool, } impl fmt::Display for Display<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.needs_escaping { + write!(f, "r#")?; + } fmt::Display::fmt(self.name.symbol.as_str(), f) } } struct UnescapedDisplay<'a> { - name: &'a UnescapedName<'a>, + name: UnescapedName<'a>, } impl fmt::Display for UnescapedDisplay<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let symbol = &self.name.0.symbol.as_str(); - let text = symbol.strip_prefix("r#").unwrap_or(symbol); - fmt::Display::fmt(&text, f) + let symbol = self.name.0.symbol.as_str(); + fmt::Display::fmt(symbol, f) } } diff --git a/crates/hir-ty/Cargo.toml b/crates/hir-ty/Cargo.toml index b079b5675b..989f0955e1 100644 --- a/crates/hir-ty/Cargo.toml +++ b/crates/hir-ty/Cargo.toml @@ -29,7 +29,6 @@ chalk-ir.workspace = true chalk-recursive.workspace = true chalk-derive.workspace = true la-arena.workspace = true -once_cell = "1.17.0" triomphe.workspace = true nohash-hasher.workspace = true typed-arena = "2.0.1" diff --git a/crates/hir-ty/src/autoderef.rs b/crates/hir-ty/src/autoderef.rs index ecfc1ff99e..7a3846df40 100644 --- a/crates/hir-ty/src/autoderef.rs +++ b/crates/hir-ty/src/autoderef.rs @@ -3,6 +3,8 @@ //! reference to a type with the field `bar`. This is an approximation of the //! logic in rustc (which lives in rustc_hir_analysis/check/autoderef.rs). +use std::mem; + use chalk_ir::cast::Cast; use hir_def::lang_item::LangItem; use hir_expand::name::Name; @@ -37,7 +39,7 @@ pub fn autoderef( ) -> impl Iterator { let mut table = InferenceTable::new(db, env); let ty = table.instantiate_canonical(ty); - let mut autoderef = Autoderef::new(&mut table, ty, false); + let mut autoderef = Autoderef::new_no_tracking(&mut table, ty, false); let mut v = Vec::new(); while let Some((ty, _steps)) = autoderef.next() { // `ty` may contain unresolved inference variables. Since there's no chance they would be @@ -58,41 +60,76 @@ pub fn autoderef( v.into_iter() } +trait TrackAutoderefSteps { + fn len(&self) -> usize; + fn push(&mut self, kind: AutoderefKind, ty: &Ty); +} + +impl TrackAutoderefSteps for usize { + fn len(&self) -> usize { + *self + } + fn push(&mut self, _: AutoderefKind, _: &Ty) { + *self += 1; + } +} +impl TrackAutoderefSteps for Vec<(AutoderefKind, Ty)> { + fn len(&self) -> usize { + self.len() + } + fn push(&mut self, kind: AutoderefKind, ty: &Ty) { + self.push((kind, ty.clone())); + } +} + #[derive(Debug)] -pub(crate) struct Autoderef<'a, 'db> { - pub(crate) table: &'a mut InferenceTable<'db>, +pub(crate) struct Autoderef<'table, 'db, T = Vec<(AutoderefKind, Ty)>> { + pub(crate) table: &'table mut InferenceTable<'db>, ty: Ty, at_start: bool, - steps: Vec<(AutoderefKind, Ty)>, + steps: T, explicit: bool, } -impl<'a, 'db> Autoderef<'a, 'db> { - pub(crate) fn new(table: &'a mut InferenceTable<'db>, ty: Ty, explicit: bool) -> Self { +impl<'table, 'db> Autoderef<'table, 'db> { + pub(crate) fn new(table: &'table mut InferenceTable<'db>, ty: Ty, explicit: bool) -> Self { let ty = table.resolve_ty_shallow(&ty); Autoderef { table, ty, at_start: true, steps: Vec::new(), explicit } } - pub(crate) fn step_count(&self) -> usize { - self.steps.len() - } - pub(crate) fn steps(&self) -> &[(AutoderefKind, Ty)] { &self.steps } +} + +impl<'table, 'db> Autoderef<'table, 'db, usize> { + pub(crate) fn new_no_tracking( + table: &'table mut InferenceTable<'db>, + ty: Ty, + explicit: bool, + ) -> Self { + let ty = table.resolve_ty_shallow(&ty); + Autoderef { table, ty, at_start: true, steps: 0, explicit } + } +} + +#[allow(private_bounds)] +impl<'table, 'db, T: TrackAutoderefSteps> Autoderef<'table, 'db, T> { + pub(crate) fn step_count(&self) -> usize { + self.steps.len() + } pub(crate) fn final_ty(&self) -> Ty { self.ty.clone() } } -impl Iterator for Autoderef<'_, '_> { +impl Iterator for Autoderef<'_, '_, T> { type Item = (Ty, usize); #[tracing::instrument(skip_all)] fn next(&mut self) -> Option { - if self.at_start { - self.at_start = false; + if mem::take(&mut self.at_start) { return Some((self.ty.clone(), 0)); } @@ -102,7 +139,7 @@ impl Iterator for Autoderef<'_, '_> { let (kind, new_ty) = autoderef_step(self.table, self.ty.clone(), self.explicit)?; - self.steps.push((kind, self.ty.clone())); + self.steps.push(kind, &self.ty); self.ty = new_ty; Some((self.ty.clone(), self.step_count())) @@ -129,12 +166,8 @@ pub(crate) fn builtin_deref<'ty>( match ty.kind(Interner) { TyKind::Ref(.., ty) => Some(ty), TyKind::Raw(.., ty) if explicit => Some(ty), - &TyKind::Adt(chalk_ir::AdtId(adt), ref substs) => { - if crate::lang_items::is_box(db, adt) { - substs.at(Interner, 0).ty(Interner) - } else { - None - } + &TyKind::Adt(chalk_ir::AdtId(adt), ref substs) if crate::lang_items::is_box(db, adt) => { + substs.at(Interner, 0).ty(Interner) } _ => None, } diff --git a/crates/hir-ty/src/chalk_db.rs b/crates/hir-ty/src/chalk_db.rs index a151ee01e6..a3e4da5d1a 100644 --- a/crates/hir-ty/src/chalk_db.rs +++ b/crates/hir-ty/src/chalk_db.rs @@ -5,6 +5,7 @@ use std::{iter, ops::ControlFlow, sync::Arc}; use hir_expand::name::Name; use intern::sym; +use span::Edition; use tracing::debug; use chalk_ir::{cast::Caster, fold::shift::Shift, CanonicalVarKinds}; @@ -424,18 +425,19 @@ impl chalk_solve::RustIrDatabase for ChalkContext<'_> { fn trait_name(&self, trait_id: chalk_ir::TraitId) -> String { let id = from_chalk_trait_id(trait_id); - self.db.trait_data(id).name.display(self.db.upcast()).to_string() + self.db.trait_data(id).name.display(self.db.upcast(), self.edition()).to_string() } fn adt_name(&self, chalk_ir::AdtId(adt_id): AdtId) -> String { + let edition = self.edition(); match adt_id { hir_def::AdtId::StructId(id) => { - self.db.struct_data(id).name.display(self.db.upcast()).to_string() + self.db.struct_data(id).name.display(self.db.upcast(), edition).to_string() } hir_def::AdtId::EnumId(id) => { - self.db.enum_data(id).name.display(self.db.upcast()).to_string() + self.db.enum_data(id).name.display(self.db.upcast(), edition).to_string() } hir_def::AdtId::UnionId(id) => { - self.db.union_data(id).name.display(self.db.upcast()).to_string() + self.db.union_data(id).name.display(self.db.upcast(), edition).to_string() } } } @@ -445,7 +447,7 @@ impl chalk_solve::RustIrDatabase for ChalkContext<'_> { } fn assoc_type_name(&self, assoc_ty_id: chalk_ir::AssocTypeId) -> String { let id = self.db.associated_ty_data(assoc_ty_id).name; - self.db.type_alias_data(id).name.display(self.db.upcast()).to_string() + self.db.type_alias_data(id).name.display(self.db.upcast(), self.edition()).to_string() } fn opaque_type_name(&self, opaque_ty_id: chalk_ir::OpaqueTyId) -> String { format!("Opaque_{}", opaque_ty_id.0) @@ -519,6 +521,10 @@ impl chalk_solve::RustIrDatabase for ChalkContext<'_> { } impl<'a> ChalkContext<'a> { + fn edition(&self) -> Edition { + self.db.crate_graph()[self.krate].edition + } + fn for_trait_impls( &self, trait_id: hir_def::TraitId, @@ -843,7 +849,7 @@ fn impl_def_datum( "impl {:?}: {}{} where {:?}", chalk_id, if negative { "!" } else { "" }, - trait_ref.display(db), + trait_ref.display(db, db.crate_graph()[krate].edition), where_clauses ); diff --git a/crates/hir-ty/src/consteval/tests.rs b/crates/hir-ty/src/consteval/tests.rs index dc3817ce3f..86228250c2 100644 --- a/crates/hir-ty/src/consteval/tests.rs +++ b/crates/hir-ty/src/consteval/tests.rs @@ -1,3 +1,4 @@ +use base_db::SourceDatabase; use chalk_ir::Substitution; use hir_def::db::DefDatabase; use rustc_apfloat::{ @@ -94,9 +95,10 @@ fn check_answer(ra_fixture: &str, check: impl FnOnce(&[u8], &MemoryMap)) { fn pretty_print_err(e: ConstEvalError, db: TestDB) -> String { let mut err = String::new(); let span_formatter = |file, range| format!("{file:?} {range:?}"); + let edition = db.crate_graph()[db.test_crate()].edition; match e { - ConstEvalError::MirLowerError(e) => e.pretty_print(&mut err, &db, span_formatter), - ConstEvalError::MirEvalError(e) => e.pretty_print(&mut err, &db, span_formatter), + ConstEvalError::MirLowerError(e) => e.pretty_print(&mut err, &db, span_formatter, edition), + ConstEvalError::MirEvalError(e) => e.pretty_print(&mut err, &db, span_formatter, edition), } .unwrap(); err @@ -110,7 +112,9 @@ fn eval_goal(db: &TestDB, file_id: EditionedFileId) -> Result { - if db.const_data(x).name.as_ref()?.display(db).to_string() == "GOAL" { + if db.const_data(x).name.as_ref()?.display(db, file_id.edition()).to_string() + == "GOAL" + { Some(x) } else { None @@ -243,6 +247,17 @@ fn casts() { check_number(r#"const GOAL: i32 = -12i8 as i32"#, -12); } +#[test] +fn floating_point_casts() { + check_number(r#"const GOAL: usize = 12i32 as f32 as usize"#, 12); + check_number(r#"const GOAL: i8 = -12i32 as f64 as i8"#, -12); + check_number(r#"const GOAL: i32 = (-1ui8 as f32 + 2u64 as f32) as i32"#, 1); + check_number(r#"const GOAL: i8 = (0./0.) as i8"#, 0); + check_number(r#"const GOAL: i8 = (1./0.) as i8"#, 127); + check_number(r#"const GOAL: i8 = (-1./0.) as i8"#, -128); + check_number(r#"const GOAL: i64 = 1e18f64 as f32 as i64"#, 999999984306749440); +} + #[test] fn raw_pointer_equality() { check_number( diff --git a/crates/hir-ty/src/diagnostics/decl_check.rs b/crates/hir-ty/src/diagnostics/decl_check.rs index b093440060..024fc32f86 100644 --- a/crates/hir-ty/src/diagnostics/decl_check.rs +++ b/crates/hir-ty/src/diagnostics/decl_check.rs @@ -17,17 +17,18 @@ use std::fmt; use hir_def::{ data::adt::VariantData, db::DefDatabase, hir::Pat, src::HasSource, AdtId, AttrDefId, ConstId, - EnumId, EnumVariantId, FunctionId, ItemContainerId, Lookup, ModuleDefId, ModuleId, StaticId, - StructId, TraitId, TypeAliasId, + EnumId, EnumVariantId, FunctionId, HasModule, ItemContainerId, Lookup, ModuleDefId, ModuleId, + StaticId, StructId, TraitId, TypeAliasId, }; use hir_expand::{ name::{AsName, Name}, - HirFileId, MacroFileIdExt, + HirFileId, HirFileIdExt, MacroFileIdExt, }; use intern::sym; use stdx::{always, never}; use syntax::{ ast::{self, HasName}, + utils::is_raw_identifier, AstNode, AstPtr, ToSmolStr, }; @@ -318,17 +319,21 @@ impl<'a> DeclValidator<'a> { /// This includes function parameters except for trait implementation associated functions. fn validate_func_body(&mut self, func: FunctionId) { let body = self.db.body(func.into()); + let edition = self.edition(func); let mut pats_replacements = body .pats .iter() .filter_map(|(pat_id, pat)| match pat { Pat::Bind { id, .. } => { let bind_name = &body.bindings[*id].name; + let mut suggested_text = + to_lower_snake_case(&bind_name.unescaped().display_no_db().to_smolstr())?; + if is_raw_identifier(&suggested_text, edition) { + suggested_text.insert_str(0, "r#"); + } let replacement = Replacement { current_name: bind_name.clone(), - suggested_text: to_lower_snake_case( - &bind_name.display_no_db().to_smolstr(), - )?, + suggested_text, expected_case: CaseType::LowerSnakeCase, }; Some((pat_id, replacement)) @@ -377,6 +382,11 @@ impl<'a> DeclValidator<'a> { } } + fn edition(&self, id: impl HasModule) -> span::Edition { + let krate = id.krate(self.db.upcast()); + self.db.crate_graph()[krate].edition + } + fn validate_struct(&mut self, struct_id: StructId) { // Check the structure name. let non_camel_case_allowed = @@ -405,16 +415,17 @@ impl<'a> DeclValidator<'a> { let VariantData::Record(fields) = data.variant_data.as_ref() else { return; }; + let edition = self.edition(struct_id); let mut struct_fields_replacements = fields .iter() .filter_map(|(_, field)| { - to_lower_snake_case(&field.name.display_no_db().to_smolstr()).map(|new_name| { - Replacement { + to_lower_snake_case(&field.name.display_no_db(edition).to_smolstr()).map( + |new_name| Replacement { current_name: field.name.clone(), suggested_text: new_name, expected_case: CaseType::LowerSnakeCase, - } - }) + }, + ) }) .peekable(); @@ -498,14 +509,17 @@ impl<'a> DeclValidator<'a> { self.validate_enum_variant_fields(*variant_id); } + let edition = self.edition(enum_id); let mut enum_variants_replacements = data .variants .iter() .filter_map(|(_, name)| { - to_camel_case(&name.display_no_db().to_smolstr()).map(|new_name| Replacement { - current_name: name.clone(), - suggested_text: new_name, - expected_case: CaseType::UpperCamelCase, + to_camel_case(&name.display_no_db(edition).to_smolstr()).map(|new_name| { + Replacement { + current_name: name.clone(), + suggested_text: new_name, + expected_case: CaseType::UpperCamelCase, + } }) }) .peekable(); @@ -566,16 +580,17 @@ impl<'a> DeclValidator<'a> { let VariantData::Record(fields) = variant_data.variant_data.as_ref() else { return; }; + let edition = self.edition(variant_id); let mut variant_field_replacements = fields .iter() .filter_map(|(_, field)| { - to_lower_snake_case(&field.name.display_no_db().to_smolstr()).map(|new_name| { - Replacement { + to_lower_snake_case(&field.name.display_no_db(edition).to_smolstr()).map( + |new_name| Replacement { current_name: field.name.clone(), suggested_text: new_name, expected_case: CaseType::LowerSnakeCase, - } - }) + }, + ) }) .peekable(); @@ -704,18 +719,22 @@ impl<'a> DeclValidator<'a> { ) where N: AstNode + HasName + fmt::Debug, S: HasSource, - L: Lookup = dyn DefDatabase + 'a>, + L: Lookup = dyn DefDatabase + 'a> + HasModule + Copy, { let to_expected_case_type = match expected_case { CaseType::LowerSnakeCase => to_lower_snake_case, CaseType::UpperSnakeCase => to_upper_snake_case, CaseType::UpperCamelCase => to_camel_case, }; - let Some(replacement) = - to_expected_case_type(&name.display(self.db.upcast()).to_smolstr()).map(|new_name| { - Replacement { current_name: name.clone(), suggested_text: new_name, expected_case } - }) - else { + let edition = self.edition(item_id); + let Some(replacement) = to_expected_case_type( + &name.display(self.db.upcast(), edition).to_smolstr(), + ) + .map(|new_name| Replacement { + current_name: name.clone(), + suggested_text: new_name, + expected_case, + }) else { return; }; @@ -748,12 +767,13 @@ impl<'a> DeclValidator<'a> { return; }; + let edition = file_id.original_file(self.db.upcast()).edition(); let diagnostic = IncorrectCase { file: file_id, ident_type, ident: AstPtr::new(&name_ast), expected_case: replacement.expected_case, - ident_text: replacement.current_name.display(self.db.upcast()).to_string(), + ident_text: replacement.current_name.display(self.db.upcast(), edition).to_string(), suggested_text: replacement.suggested_text, }; diff --git a/crates/hir-ty/src/diagnostics/expr.rs b/crates/hir-ty/src/diagnostics/expr.rs index 6e5a7cce9c..f8b5c7d0ce 100644 --- a/crates/hir-ty/src/diagnostics/expr.rs +++ b/crates/hir-ty/src/diagnostics/expr.rs @@ -4,6 +4,7 @@ use std::fmt; +use base_db::CrateId; use chalk_solve::rust_ir::AdtKind; use either::Either; use hir_def::{ @@ -15,6 +16,7 @@ use intern::sym; use itertools::Itertools; use rustc_hash::FxHashSet; use rustc_pattern_analysis::constructor::Constructor; +use span::Edition; use syntax::{ ast::{self, UnaryOp}, AstNode, @@ -258,7 +260,13 @@ impl ExprValidator { if !witnesses.is_empty() { self.diagnostics.push(BodyValidationDiagnostic::MissingMatchArms { match_expr, - uncovered_patterns: missing_match_arms(&cx, scrut_ty, witnesses, m_arms.is_empty()), + uncovered_patterns: missing_match_arms( + &cx, + scrut_ty, + witnesses, + m_arms.is_empty(), + self.owner.krate(db.upcast()), + ), }); } } @@ -345,7 +353,13 @@ impl ExprValidator { if !witnesses.is_empty() { self.diagnostics.push(BodyValidationDiagnostic::NonExhaustiveLet { pat, - uncovered_patterns: missing_match_arms(&cx, ty, witnesses, false), + uncovered_patterns: missing_match_arms( + &cx, + ty, + witnesses, + false, + self.owner.krate(db.upcast()), + ), }); } } @@ -616,24 +630,26 @@ fn missing_match_arms<'p>( scrut_ty: &Ty, witnesses: Vec>, arms_is_empty: bool, + krate: CrateId, ) -> String { - struct DisplayWitness<'a, 'p>(&'a WitnessPat<'p>, &'a MatchCheckCtx<'p>); + struct DisplayWitness<'a, 'p>(&'a WitnessPat<'p>, &'a MatchCheckCtx<'p>, Edition); impl fmt::Display for DisplayWitness<'_, '_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let DisplayWitness(witness, cx) = *self; + let DisplayWitness(witness, cx, edition) = *self; let pat = cx.hoist_witness_pat(witness); - write!(f, "{}", pat.display(cx.db)) + write!(f, "{}", pat.display(cx.db, edition)) } } + let edition = cx.db.crate_graph()[krate].edition; let non_empty_enum = match scrut_ty.as_adt() { Some((AdtId::EnumId(e), _)) => !cx.db.enum_data(e).variants.is_empty(), _ => false, }; if arms_is_empty && !non_empty_enum { - format!("type `{}` is non-empty", scrut_ty.display(cx.db)) + format!("type `{}` is non-empty", scrut_ty.display(cx.db, edition)) } else { - let pat_display = |witness| DisplayWitness(witness, cx); + let pat_display = |witness| DisplayWitness(witness, cx, edition); const LIMIT: usize = 3; match &*witnesses { [witness] => format!("`{}` not covered", pat_display(witness)), diff --git a/crates/hir-ty/src/diagnostics/match_check.rs b/crates/hir-ty/src/diagnostics/match_check.rs index a0ee7c0748..4bc07bc9ec 100644 --- a/crates/hir-ty/src/diagnostics/match_check.rs +++ b/crates/hir-ty/src/diagnostics/match_check.rs @@ -14,6 +14,7 @@ use hir_def::{ body::Body, data::adt::VariantData, hir::PatId, AdtId, EnumVariantId, LocalFieldId, VariantId, }; use hir_expand::name::Name; +use span::Edition; use stdx::{always, never}; use crate::{ @@ -151,7 +152,11 @@ impl<'a> PatCtxt<'a> { match (bm, ty.kind(Interner)) { (BindingMode::Ref(_), TyKind::Ref(.., rty)) => ty = rty, (BindingMode::Ref(_), _) => { - never!("`ref {}` has wrong type {:?}", name.display(self.db.upcast()), ty); + never!( + "`ref {}` has wrong type {:?}", + name.display(self.db.upcast(), Edition::LATEST), + ty + ); self.errors.push(PatternError::UnexpectedType); return Pat { ty: ty.clone(), kind: PatKind::Wild.into() }; } @@ -297,7 +302,7 @@ impl HirDisplay for Pat { PatKind::Wild => write!(f, "_"), PatKind::Never => write!(f, "!"), PatKind::Binding { name, subpattern } => { - write!(f, "{}", name.display(f.db.upcast()))?; + write!(f, "{}", name.display(f.db.upcast(), f.edition()))?; if let Some(subpattern) = subpattern { write!(f, " @ ")?; subpattern.hir_fmt(f)?; @@ -317,14 +322,22 @@ impl HirDisplay for Pat { if let Some(variant) = variant { match variant { VariantId::EnumVariantId(v) => { - write!(f, "{}", f.db.enum_variant_data(v).name.display(f.db.upcast()))?; - } - VariantId::StructId(s) => { - write!(f, "{}", f.db.struct_data(s).name.display(f.db.upcast()))? - } - VariantId::UnionId(u) => { - write!(f, "{}", f.db.union_data(u).name.display(f.db.upcast()))? + write!( + f, + "{}", + f.db.enum_variant_data(v).name.display(f.db.upcast(), f.edition()) + )?; } + VariantId::StructId(s) => write!( + f, + "{}", + f.db.struct_data(s).name.display(f.db.upcast(), f.edition()) + )?, + VariantId::UnionId(u) => write!( + f, + "{}", + f.db.union_data(u).name.display(f.db.upcast(), f.edition()) + )?, }; let variant_data = variant.variant_data(f.db.upcast()); @@ -341,7 +354,9 @@ impl HirDisplay for Pat { write!( f, "{}: ", - rec_fields[p.field].name.display(f.db.upcast()) + rec_fields[p.field] + .name + .display(f.db.upcast(), f.edition()) )?; p.pattern.hir_fmt(f) }) diff --git a/crates/hir-ty/src/diagnostics/match_check/pat_analysis.rs b/crates/hir-ty/src/diagnostics/match_check/pat_analysis.rs index 7b3abf501d..1066a28c3f 100644 --- a/crates/hir-ty/src/diagnostics/match_check/pat_analysis.rs +++ b/crates/hir-ty/src/diagnostics/match_check/pat_analysis.rs @@ -1,10 +1,10 @@ //! Interface with `rustc_pattern_analysis`. +use std::cell::LazyCell; use std::fmt; use hir_def::{DefWithBodyId, EnumId, EnumVariantId, HasModule, LocalFieldId, ModuleId, VariantId}; use intern::sym; -use once_cell::unsync::Lazy; use rustc_pattern_analysis::{ constructor::{Constructor, ConstructorSet, VariantVisibility}, usefulness::{compute_match_usefulness, PlaceValidity, UsefulnessReport}, @@ -384,8 +384,9 @@ impl<'db> PatCx for MatchCheckCtx<'db> { let variant = Self::variant_id_for_adt(self.db, ctor, adt).unwrap(); // Whether we must not match the fields of this variant exhaustively. - let is_non_exhaustive = Lazy::new(|| self.is_foreign_non_exhaustive(adt)); - let visibilities = Lazy::new(|| self.db.field_visibilities(variant)); + let is_non_exhaustive = + LazyCell::new(|| self.is_foreign_non_exhaustive(adt)); + let visibilities = LazyCell::new(|| self.db.field_visibilities(variant)); self.list_variant_fields(ty, variant) .map(move |(fid, ty)| { diff --git a/crates/hir-ty/src/display.rs b/crates/hir-ty/src/display.rs index f406666ae5..70c03477c4 100644 --- a/crates/hir-ty/src/display.rs +++ b/crates/hir-ty/src/display.rs @@ -33,7 +33,8 @@ use rustc_apfloat::{ Float, }; use smallvec::SmallVec; -use stdx::{never, IsNoneOr}; +use span::Edition; +use stdx::never; use triomphe::Arc; use crate::{ @@ -131,7 +132,11 @@ pub trait HirDisplay { /// Returns a `Display`able type that is human-readable. /// Use this for showing types to the user (e.g. diagnostics) - fn display<'a>(&'a self, db: &'a dyn HirDatabase) -> HirDisplayWrapper<'a, Self> + fn display<'a>( + &'a self, + db: &'a dyn HirDatabase, + edition: Edition, + ) -> HirDisplayWrapper<'a, Self> where Self: Sized, { @@ -142,7 +147,7 @@ pub trait HirDisplay { limited_size: None, omit_verbose_types: false, closure_style: ClosureStyle::ImplFn, - display_target: DisplayTarget::Diagnostics, + display_target: DisplayTarget::Diagnostics { edition }, show_container_bounds: false, } } @@ -153,6 +158,7 @@ pub trait HirDisplay { &'a self, db: &'a dyn HirDatabase, max_size: Option, + edition: Edition, ) -> HirDisplayWrapper<'a, Self> where Self: Sized, @@ -164,7 +170,7 @@ pub trait HirDisplay { limited_size: None, omit_verbose_types: true, closure_style: ClosureStyle::ImplFn, - display_target: DisplayTarget::Diagnostics, + display_target: DisplayTarget::Diagnostics { edition }, show_container_bounds: false, } } @@ -175,6 +181,7 @@ pub trait HirDisplay { &'a self, db: &'a dyn HirDatabase, limited_size: Option, + edition: Edition, ) -> HirDisplayWrapper<'a, Self> where Self: Sized, @@ -186,7 +193,7 @@ pub trait HirDisplay { limited_size, omit_verbose_types: true, closure_style: ClosureStyle::ImplFn, - display_target: DisplayTarget::Diagnostics, + display_target: DisplayTarget::Diagnostics { edition }, show_container_bounds: false, } } @@ -242,6 +249,7 @@ pub trait HirDisplay { &'a self, db: &'a dyn HirDatabase, show_container_bounds: bool, + edition: Edition, ) -> HirDisplayWrapper<'a, Self> where Self: Sized, @@ -253,13 +261,23 @@ pub trait HirDisplay { limited_size: None, omit_verbose_types: false, closure_style: ClosureStyle::ImplFn, - display_target: DisplayTarget::Diagnostics, + display_target: DisplayTarget::Diagnostics { edition }, show_container_bounds, } } } impl HirFormatter<'_> { + pub fn edition(&self) -> Edition { + match self.display_target { + DisplayTarget::Diagnostics { edition } => edition, + DisplayTarget::SourceCode { module_id, .. } => { + self.db.crate_graph()[module_id.krate()].edition + } + DisplayTarget::Test => Edition::CURRENT, + } + } + pub fn write_joined( &mut self, iter: impl IntoIterator, @@ -324,7 +342,7 @@ pub enum DisplayTarget { /// Display types for inlays, doc popups, autocompletion, etc... /// Showing `{unknown}` or not qualifying paths is fine here. /// There's no reason for this to fail. - Diagnostics, + Diagnostics { edition: Edition }, /// Display types for inserting them in source files. /// The generated code should compile, so paths need to be qualified. SourceCode { module_id: ModuleId, allow_opaque: bool }, @@ -460,7 +478,7 @@ impl HirDisplay for ProjectionTy { ">::{}", f.db.type_alias_data(from_assoc_type_id(self.associated_ty_id)) .name - .display(f.db.upcast()) + .display(f.db.upcast(), f.edition()) )?; let proj_params_count = self.substitution.len(Interner) - trait_ref.substitution.len(Interner); @@ -499,7 +517,7 @@ impl HirDisplay for Const { let id = from_placeholder_idx(f.db, *idx); let generics = generics(f.db.upcast(), id.parent); let param_data = &generics[id.local_id]; - write!(f, "{}", param_data.name().unwrap().display(f.db.upcast()))?; + write!(f, "{}", param_data.name().unwrap().display(f.db.upcast(), f.edition()))?; Ok(()) } ConstValue::Concrete(c) => match &c.interned { @@ -633,7 +651,7 @@ fn render_const_scalar( TyKind::Adt(adt, _) if b.len() == 2 * size_of::() => match adt.0 { hir_def::AdtId::StructId(s) => { let data = f.db.struct_data(s); - write!(f, "&{}", data.name.display(f.db.upcast()))?; + write!(f, "&{}", data.name.display(f.db.upcast(), f.edition()))?; Ok(()) } _ => f.write_str(""), @@ -691,7 +709,7 @@ fn render_const_scalar( match adt.0 { hir_def::AdtId::StructId(s) => { let data = f.db.struct_data(s); - write!(f, "{}", data.name.display(f.db.upcast()))?; + write!(f, "{}", data.name.display(f.db.upcast(), f.edition()))?; let field_types = f.db.field_types(s.into()); render_variant_after_name( &data.variant_data, @@ -705,7 +723,7 @@ fn render_const_scalar( ) } hir_def::AdtId::UnionId(u) => { - write!(f, "{}", f.db.union_data(u).name.display(f.db.upcast())) + write!(f, "{}", f.db.union_data(u).name.display(f.db.upcast(), f.edition())) } hir_def::AdtId::EnumId(e) => { let Ok(target_data_layout) = f.db.target_data_layout(trait_env.krate) else { @@ -717,7 +735,7 @@ fn render_const_scalar( return f.write_str(""); }; let data = f.db.enum_variant_data(var_id); - write!(f, "{}", data.name.display(f.db.upcast()))?; + write!(f, "{}", data.name.display(f.db.upcast(), f.edition()))?; let field_types = f.db.field_types(var_id.into()); render_variant_after_name( &data.variant_data, @@ -802,11 +820,11 @@ fn render_variant_after_name( if matches!(data, VariantData::Record(_)) { write!(f, " {{")?; if let Some((id, data)) = it.next() { - write!(f, " {}: ", data.name.display(f.db.upcast()))?; + write!(f, " {}: ", data.name.display(f.db.upcast(), f.edition()))?; render_field(f, id)?; } for (id, data) in it { - write!(f, ", {}: ", data.name.display(f.db.upcast()))?; + write!(f, ", {}: ", data.name.display(f.db.upcast(), f.edition()))?; render_field(f, id)?; } write!(f, " }}")?; @@ -1000,15 +1018,23 @@ impl HirDisplay for Ty { CallableDefId::FunctionId(ff) => { write!(f, "fn ")?; f.start_location_link(def.into()); - write!(f, "{}", db.function_data(ff).name.display(f.db.upcast()))? + write!( + f, + "{}", + db.function_data(ff).name.display(f.db.upcast(), f.edition()) + )? } CallableDefId::StructId(s) => { f.start_location_link(def.into()); - write!(f, "{}", db.struct_data(s).name.display(f.db.upcast()))? + write!(f, "{}", db.struct_data(s).name.display(f.db.upcast(), f.edition()))? } CallableDefId::EnumVariantId(e) => { f.start_location_link(def.into()); - write!(f, "{}", db.enum_variant_data(e).name.display(f.db.upcast()))? + write!( + f, + "{}", + db.enum_variant_data(e).name.display(f.db.upcast(), f.edition()) + )? } }; f.end_location_link(); @@ -1019,26 +1045,25 @@ impl HirDisplay for Ty { let (parent_len, self_param, type_, const_, impl_, lifetime) = generics.provenance_split(); let parameters = parameters.as_slice(Interner); + debug_assert_eq!( + parameters.len(), + parent_len + self_param as usize + type_ + const_ + impl_ + lifetime + ); // We print all params except implicit impl Trait params. Still a bit weird; should we leave out parent and self? if parameters.len() - impl_ > 0 { // `parameters` are in the order of fn's params (including impl traits), fn's lifetimes + let parameters = + generic_args_sans_defaults(f, Some(generic_def_id), parameters); let without_impl = self_param as usize + type_ + const_ + lifetime; // parent's params (those from enclosing impl or trait, if any). let (fn_params, parent_params) = parameters.split_at(without_impl + impl_); - debug_assert_eq!(parent_params.len(), parent_len); - - let parent_params = - generic_args_sans_defaults(f, Some(generic_def_id), parent_params); - let fn_params = - &generic_args_sans_defaults(f, Some(generic_def_id), fn_params) - [0..without_impl]; write!(f, "<")?; hir_fmt_generic_arguments(f, parent_params, None)?; if !parent_params.is_empty() && !fn_params.is_empty() { write!(f, ", ")?; } - hir_fmt_generic_arguments(f, fn_params, None)?; + hir_fmt_generic_arguments(f, &fn_params[0..without_impl], None)?; write!(f, ">")?; } } @@ -1054,13 +1079,13 @@ impl HirDisplay for Ty { TyKind::Adt(AdtId(def_id), parameters) => { f.start_location_link((*def_id).into()); match f.display_target { - DisplayTarget::Diagnostics | DisplayTarget::Test => { + DisplayTarget::Diagnostics { .. } | DisplayTarget::Test => { let name = match *def_id { hir_def::AdtId::StructId(it) => db.struct_data(it).name.clone(), hir_def::AdtId::UnionId(it) => db.union_data(it).name.clone(), hir_def::AdtId::EnumId(it) => db.enum_data(it).name.clone(), }; - write!(f, "{}", name.display(f.db.upcast()))?; + write!(f, "{}", name.display(f.db.upcast(), f.edition()))?; } DisplayTarget::SourceCode { module_id, allow_opaque: _ } => { if let Some(path) = find_path::find_path( @@ -1076,7 +1101,7 @@ impl HirDisplay for Ty { prefer_absolute: false, }, ) { - write!(f, "{}", path.display(f.db.upcast()))?; + write!(f, "{}", path.display(f.db.upcast(), f.edition()))?; } else { return Err(HirDisplayError::DisplaySourceCodeError( DisplaySourceCodeError::PathNotFound, @@ -1102,12 +1127,12 @@ impl HirDisplay for Ty { // Use placeholder associated types when the target is test (https://rust-lang.github.io/chalk/book/clauses/type_equality.html#placeholder-associated-types) if f.display_target.is_test() { f.start_location_link(trait_.into()); - write!(f, "{}", trait_data.name.display(f.db.upcast()))?; + write!(f, "{}", trait_data.name.display(f.db.upcast(), f.edition()))?; f.end_location_link(); write!(f, "::")?; f.start_location_link(type_alias.into()); - write!(f, "{}", type_alias_data.name.display(f.db.upcast()))?; + write!(f, "{}", type_alias_data.name.display(f.db.upcast(), f.edition()))?; f.end_location_link(); // Note that the generic args for the associated type come before those for the // trait (including the self type). @@ -1125,7 +1150,7 @@ impl HirDisplay for Ty { let alias = from_foreign_def_id(*type_alias); let type_alias = db.type_alias_data(alias); f.start_location_link(alias.into()); - write!(f, "{}", type_alias.name.display(f.db.upcast()))?; + write!(f, "{}", type_alias.name.display(f.db.upcast(), f.edition()))?; f.end_location_link(); } TyKind::OpaqueType(opaque_ty_id, parameters) => { @@ -1257,7 +1282,10 @@ impl HirDisplay for Ty { write!( f, "{}", - p.name.clone().unwrap_or_else(Name::missing).display(f.db.upcast()) + p.name + .clone() + .unwrap_or_else(Name::missing) + .display(f.db.upcast(), f.edition()) )? } TypeParamProvenance::ArgumentImplTrait => { @@ -1290,7 +1318,7 @@ impl HirDisplay for Ty { } }, TypeOrConstParamData::ConstParamData(p) => { - write!(f, "{}", p.name.display(f.db.upcast()))?; + write!(f, "{}", p.name.display(f.db.upcast(), f.edition()))?; } } } @@ -1410,17 +1438,7 @@ fn hir_fmt_generics( let parameters_to_write = generic_args_sans_defaults(f, generic_def, parameters); - // FIXME: Remote this - // most of our lifetimes will be errors as we lack elision and inference - // so don't render them for now - let only_err_lifetimes = !cfg!(test) - && parameters_to_write.iter().all(|arg| { - matches!( - arg.data(Interner), - chalk_ir::GenericArgData::Lifetime(it) if *it.data(Interner) == LifetimeData::Error - ) - }); - if !parameters_to_write.is_empty() && !only_err_lifetimes { + if !parameters_to_write.is_empty() { write!(f, "<")?; hir_fmt_generic_arguments(f, parameters_to_write, self_)?; write!(f, ">")?; @@ -1461,12 +1479,14 @@ fn generic_args_sans_defaults<'ga>( } // otherwise, if the arg is equal to the param default, hide it (unless the // default is an error which can happen for the trait Self type) - #[allow(unstable_name_collisions)] - IsNoneOr::is_none_or(default_parameters.get(i), |default_parameter| { - // !is_err(default_parameter.skip_binders()) - // && - arg != &default_parameter.clone().substitute(Interner, ¶meters) - }) + match default_parameters.get(i) { + None => true, + Some(default_parameter) => { + // !is_err(default_parameter.skip_binders()) + // && + arg != &default_parameter.clone().substitute(Interner, ¶meters) + } + } }; let mut default_from = 0; for (i, parameter) in parameters.iter().enumerate() { @@ -1495,18 +1515,6 @@ fn hir_fmt_generic_arguments( None => (parameters, &[][..]), }; for generic_arg in lifetimes.iter().chain(ty_or_const) { - // FIXME: Remove this - // most of our lifetimes will be errors as we lack elision and inference - // so don't render them for now - if !cfg!(test) - && matches!( - generic_arg.lifetime(Interner), - Some(l) if ***l.interned() == LifetimeData::Error - ) - { - continue; - } - if !mem::take(&mut first) { write!(f, ", ")?; } @@ -1633,7 +1641,7 @@ fn write_bounds_like_dyn_trait( // existential) here, which is the only thing that's // possible in actual Rust, and hence don't print it f.start_location_link(trait_.into()); - write!(f, "{}", f.db.trait_data(trait_).name.display(f.db.upcast()))?; + write!(f, "{}", f.db.trait_data(trait_).name.display(f.db.upcast(), f.edition()))?; f.end_location_link(); if is_fn_trait { if let [self_, params @ ..] = trait_ref.substitution.as_slice(Interner) { @@ -1707,7 +1715,7 @@ fn write_bounds_like_dyn_trait( let assoc_ty_id = from_assoc_type_id(proj.associated_ty_id); let type_alias = f.db.type_alias_data(assoc_ty_id); f.start_location_link(assoc_ty_id.into()); - write!(f, "{}", type_alias.name.display(f.db.upcast()))?; + write!(f, "{}", type_alias.name.display(f.db.upcast(), f.edition()))?; f.end_location_link(); let proj_arg_count = generics(f.db.upcast(), assoc_ty_id.into()).len_self(); @@ -1771,7 +1779,7 @@ fn fmt_trait_ref( } let trait_ = tr.hir_trait_id(); f.start_location_link(trait_.into()); - write!(f, "{}", f.db.trait_data(trait_).name.display(f.db.upcast()))?; + write!(f, "{}", f.db.trait_data(trait_).name.display(f.db.upcast(), f.edition()))?; f.end_location_link(); let substs = tr.substitution.as_slice(Interner); hir_fmt_generics(f, &substs[1..], None, substs[0].ty(Interner)) @@ -1797,7 +1805,11 @@ impl HirDisplay for WhereClause { write!(f, ">::",)?; let type_alias = from_assoc_type_id(projection_ty.associated_ty_id); f.start_location_link(type_alias.into()); - write!(f, "{}", f.db.type_alias_data(type_alias).name.display(f.db.upcast()),)?; + write!( + f, + "{}", + f.db.type_alias_data(type_alias).name.display(f.db.upcast(), f.edition()), + )?; f.end_location_link(); write!(f, " = ")?; ty.hir_fmt(f)?; @@ -1833,14 +1845,20 @@ impl HirDisplay for LifetimeData { let id = lt_from_placeholder_idx(f.db, *idx); let generics = generics(f.db.upcast(), id.parent); let param_data = &generics[id.local_id]; - write!(f, "{}", param_data.name.display(f.db.upcast()))?; + write!(f, "{}", param_data.name.display(f.db.upcast(), f.edition()))?; Ok(()) } _ if f.display_target.is_source_code() => write!(f, "'_"), LifetimeData::BoundVar(idx) => idx.hir_fmt(f), LifetimeData::InferenceVar(_) => write!(f, "_"), LifetimeData::Static => write!(f, "'static"), - LifetimeData::Error => write!(f, "'?"), + LifetimeData::Error => { + if cfg!(test) { + write!(f, "'?") + } else { + write!(f, "'_") + } + } LifetimeData::Erased => write!(f, "'"), LifetimeData::Phantom(void, _) => match *void {}, } @@ -1855,7 +1873,7 @@ impl HirDisplay for DomainGoal { wc.hir_fmt(f)?; write!(f, ")")?; } - _ => write!(f, "?")?, + _ => write!(f, "_")?, } Ok(()) } @@ -1914,7 +1932,7 @@ impl HirDisplay for TypeRef { }; write!(f, "&")?; if let Some(lifetime) = lifetime { - write!(f, "{} ", lifetime.name.display(f.db.upcast()))?; + write!(f, "{} ", lifetime.name.display(f.db.upcast(), f.edition()))?; } write!(f, "{mutability}")?; inner.hir_fmt(f)?; @@ -1922,7 +1940,7 @@ impl HirDisplay for TypeRef { TypeRef::Array(inner, len) => { write!(f, "[")?; inner.hir_fmt(f)?; - write!(f, "; {}]", len.display(f.db.upcast()))?; + write!(f, "; {}]", len.display(f.db.upcast(), f.edition()))?; } TypeRef::Slice(inner) => { write!(f, "[")?; @@ -1943,7 +1961,7 @@ impl HirDisplay for TypeRef { for index in 0..function_parameters.len() { let (param_name, param_type) = &function_parameters[index]; if let Some(name) = param_name { - write!(f, "{}: ", name.display(f.db.upcast()))?; + write!(f, "{}: ", name.display(f.db.upcast(), f.edition()))?; } param_type.hir_fmt(f)?; @@ -2001,12 +2019,15 @@ impl HirDisplay for TypeBound { } path.hir_fmt(f) } - TypeBound::Lifetime(lifetime) => write!(f, "{}", lifetime.name.display(f.db.upcast())), + TypeBound::Lifetime(lifetime) => { + write!(f, "{}", lifetime.name.display(f.db.upcast(), f.edition())) + } TypeBound::ForLifetime(lifetimes, path) => { + let edition = f.edition(); write!( f, "for<{}> ", - lifetimes.iter().map(|it| it.display(f.db.upcast())).format(", ") + lifetimes.iter().map(|it| it.display(f.db.upcast(), edition)).format(", ") )?; path.hir_fmt(f) } @@ -2072,7 +2093,7 @@ impl HirDisplay for Path { if !matches!(self.kind(), PathKind::Plain) || seg_idx > 0 { write!(f, "::")?; } - write!(f, "{}", segment.name.display(f.db.upcast()))?; + write!(f, "{}", segment.name.display(f.db.upcast(), f.edition()))?; if let Some(generic_args) = segment.args_and_bindings { // We should be in type context, so format as `Foo` instead of `Foo::`. // Do we actually format expressions? @@ -2117,7 +2138,7 @@ impl HirDisplay for Path { } else { write!(f, ", ")?; } - write!(f, "{}", binding.name.display(f.db.upcast()))?; + write!(f, "{}", binding.name.display(f.db.upcast(), f.edition()))?; match &binding.type_ref { Some(ty) => { write!(f, " = ")?; @@ -2151,9 +2172,11 @@ impl HirDisplay for hir_def::path::GenericArg { fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { match self { hir_def::path::GenericArg::Type(ty) => ty.hir_fmt(f), - hir_def::path::GenericArg::Const(c) => write!(f, "{}", c.display(f.db.upcast())), + hir_def::path::GenericArg::Const(c) => { + write!(f, "{}", c.display(f.db.upcast(), f.edition())) + } hir_def::path::GenericArg::Lifetime(lifetime) => { - write!(f, "{}", lifetime.name.display(f.db.upcast())) + write!(f, "{}", lifetime.name.display(f.db.upcast(), f.edition())) } } } diff --git a/crates/hir-ty/src/infer.rs b/crates/hir-ty/src/infer.rs index 45d423d03c..062ea27815 100644 --- a/crates/hir-ty/src/infer.rs +++ b/crates/hir-ty/src/infer.rs @@ -22,7 +22,7 @@ mod pat; mod path; pub(crate) mod unify; -use std::{convert::identity, iter, ops::Index}; +use std::{cell::OnceCell, convert::identity, iter, ops::Index}; use chalk_ir::{ cast::Cast, @@ -49,17 +49,17 @@ use hir_expand::name::Name; use indexmap::IndexSet; use intern::sym; use la_arena::{ArenaMap, Entry}; -use once_cell::unsync::OnceCell; use rustc_hash::{FxHashMap, FxHashSet}; use stdx::{always, never}; use triomphe::Arc; use crate::{ db::HirDatabase, - error_lifetime, fold_tys, + fold_tys, generics::Generics, infer::{coerce::CoerceMany, unify::InferenceTable}, lower::ImplTraitLoweringMode, + mir::MirSpan, to_assoc_type_id, traits::FnTrait, utils::{InTypeConstIdMetadata, UnevaluatedConstEvaluatorFolder}, @@ -328,13 +328,13 @@ pub struct Adjustment { } impl Adjustment { - pub fn borrow(m: Mutability, ty: Ty) -> Self { - let ty = TyKind::Ref(m, error_lifetime(), ty).intern(Interner); - Adjustment { kind: Adjust::Borrow(AutoBorrow::Ref(m)), target: ty } + pub fn borrow(m: Mutability, ty: Ty, lt: Lifetime) -> Self { + let ty = TyKind::Ref(m, lt.clone(), ty).intern(Interner); + Adjustment { kind: Adjust::Borrow(AutoBorrow::Ref(lt, m)), target: ty } } } -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[derive(Clone, Debug, PartialEq, Eq, Hash)] pub enum Adjust { /// Go from ! to any type. NeverToAny, @@ -354,18 +354,18 @@ pub enum Adjust { #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct OverloadedDeref(pub Option); -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[derive(Clone, Debug, PartialEq, Eq, Hash)] pub enum AutoBorrow { /// Converts from T to &T. - Ref(Mutability), + Ref(Lifetime, Mutability), /// Converts from T to *T. RawPtr(Mutability), } impl AutoBorrow { - fn mutability(self) -> Mutability { - let (AutoBorrow::Ref(m) | AutoBorrow::RawPtr(m)) = self; - m + fn mutability(&self) -> Mutability { + let (AutoBorrow::Ref(_, m) | AutoBorrow::RawPtr(m)) = self; + *m } } @@ -554,6 +554,12 @@ pub(crate) struct InferenceContext<'a> { // fields related to closure capture current_captures: Vec, + /// A stack that has an entry for each projection in the current capture. + /// + /// For example, in `a.b.c`, we capture the spans of `a`, `a.b`, and `a.b.c`. + /// We do that because sometimes we truncate projections (when a closure captures + /// both `a.b` and `a.b.c`), and we want to provide accurate spans in this case. + current_capture_span_stack: Vec, current_closure: Option, /// Stores the list of closure ids that need to be analyzed before this closure. See the /// comment on `InferenceContext::sort_closures` @@ -605,6 +611,11 @@ fn find_continuable( } } +enum ImplTraitReplacingMode { + ReturnPosition(FxHashSet), + TypeAlias, +} + impl<'a> InferenceContext<'a> { fn new( db: &'a dyn HirDatabase, @@ -630,6 +641,7 @@ impl<'a> InferenceContext<'a> { breakables: Vec::new(), deferred_cast_checks: Vec::new(), current_captures: Vec::new(), + current_capture_span_stack: Vec::new(), current_closure: None, deferred_closures: FxHashMap::default(), closure_dependencies: FxHashMap::default(), @@ -826,13 +838,19 @@ impl<'a> InferenceContext<'a> { self.write_binding_ty(self_param, ty); } } - let mut params_and_ret_tys = Vec::new(); + let mut tait_candidates = FxHashSet::default(); for (ty, pat) in param_tys.zip(&*self.body.params) { let ty = self.insert_type_vars(ty); let ty = self.normalize_associated_types_in(ty); self.infer_top_pat(*pat, &ty); - params_and_ret_tys.push(ty); + if ty + .data(Interner) + .flags + .intersects(TypeFlags::HAS_TY_OPAQUE.union(TypeFlags::HAS_TY_INFER)) + { + tait_candidates.insert(ty); + } } let return_ty = &*data.ret_type; @@ -845,7 +863,12 @@ impl<'a> InferenceContext<'a> { let return_ty = if let Some(rpits) = self.db.return_type_impl_traits(func) { // RPIT opaque types use substitution of their parent function. let fn_placeholders = TyBuilder::placeholder_subst(self.db, func); - let result = self.insert_inference_vars_for_impl_trait(return_ty, fn_placeholders); + let mut mode = ImplTraitReplacingMode::ReturnPosition(FxHashSet::default()); + let result = + self.insert_inference_vars_for_impl_trait(return_ty, fn_placeholders, &mut mode); + if let ImplTraitReplacingMode::ReturnPosition(taits) = mode { + tait_candidates.extend(taits); + } let rpits = rpits.skip_binders(); for (id, _) in rpits.impl_traits.iter() { if let Entry::Vacant(e) = self.result.type_of_rpit.entry(id) { @@ -864,11 +887,23 @@ impl<'a> InferenceContext<'a> { // Functions might be defining usage sites of TAITs. // To define an TAITs, that TAIT must appear in the function's signatures. // So, it suffices to check for params and return types. - params_and_ret_tys.push(self.return_ty.clone()); - self.make_tait_coercion_table(params_and_ret_tys.iter()); + if self + .return_ty + .data(Interner) + .flags + .intersects(TypeFlags::HAS_TY_OPAQUE.union(TypeFlags::HAS_TY_INFER)) + { + tait_candidates.insert(self.return_ty.clone()); + } + self.make_tait_coercion_table(tait_candidates.iter()); } - fn insert_inference_vars_for_impl_trait(&mut self, t: T, placeholders: Substitution) -> T + fn insert_inference_vars_for_impl_trait( + &mut self, + t: T, + placeholders: Substitution, + mode: &mut ImplTraitReplacingMode, + ) -> T where T: crate::HasInterner + crate::TypeFoldable, { @@ -881,10 +916,31 @@ impl<'a> InferenceContext<'a> { }; let (impl_traits, idx) = match self.db.lookup_intern_impl_trait_id(opaque_ty_id.into()) { + // We don't replace opaque types from other kind with inference vars + // because `insert_inference_vars_for_impl_traits` for each kinds + // and unreplaced opaque types of other kind are resolved while + // inferencing because of `tait_coercion_table`. + // Moreover, calling `insert_inference_vars_for_impl_traits` with same + // `placeholders` for other kind may cause trouble because + // the substs for the bounds of each impl traits do not match ImplTraitId::ReturnTypeImplTrait(def, idx) => { + if matches!(mode, ImplTraitReplacingMode::TypeAlias) { + // RPITs don't have `tait_coercion_table`, so use inserted inference + // vars for them. + if let Some(ty) = self.result.type_of_rpit.get(idx) { + return ty.clone(); + } + return ty; + } (self.db.return_type_impl_traits(def), idx) } ImplTraitId::TypeAliasImplTrait(def, idx) => { + if let ImplTraitReplacingMode::ReturnPosition(taits) = mode { + // Gather TAITs while replacing RPITs because TAITs inside RPITs + // may not visited while replacing TAITs + taits.insert(ty.clone()); + return ty; + } (self.db.type_alias_impl_traits(def), idx) } _ => unreachable!(), @@ -893,16 +949,20 @@ impl<'a> InferenceContext<'a> { return ty; }; let bounds = (*impl_traits) - .map_ref(|rpits| rpits.impl_traits[idx].bounds.map_ref(|it| it.iter())); + .map_ref(|its| its.impl_traits[idx].bounds.map_ref(|it| it.iter())); let var = self.table.new_type_var(); let var_subst = Substitution::from1(Interner, var.clone()); for bound in bounds { - let predicate = bound.map(|it| it.cloned()).substitute(Interner, &placeholders); + let predicate = bound.map(|it| it.cloned()); + let predicate = predicate.substitute(Interner, &placeholders); let (var_predicate, binders) = predicate.substitute(Interner, &var_subst).into_value_and_skipped_binders(); always!(binders.is_empty(Interner)); // quantified where clauses not yet handled - let var_predicate = self - .insert_inference_vars_for_impl_trait(var_predicate, placeholders.clone()); + let var_predicate = self.insert_inference_vars_for_impl_trait( + var_predicate, + placeholders.clone(), + mode, + ); self.push_obligation(var_predicate.cast(Interner)); } self.result.type_of_rpit.insert(idx, var.clone()); @@ -1039,7 +1099,11 @@ impl<'a> InferenceContext<'a> { self.db.lookup_intern_impl_trait_id(id.into()) { let subst = TyBuilder::placeholder_subst(self.db, alias_id); - let ty = self.insert_inference_vars_for_impl_trait(ty, subst); + let ty = self.insert_inference_vars_for_impl_trait( + ty, + subst, + &mut ImplTraitReplacingMode::TypeAlias, + ); Some((id, ty)) } else { None @@ -1436,7 +1500,8 @@ impl<'a> InferenceContext<'a> { let remaining = unresolved.map(|it| path.segments()[it..].len()).filter(|it| it > &0); let ty = match ty.kind(Interner) { TyKind::Alias(AliasTy::Projection(proj_ty)) => { - self.db.normalize_projection(proj_ty.clone(), self.table.trait_env.clone()) + let ty = self.table.normalize_projection_ty(proj_ty.clone()); + self.table.resolve_ty_shallow(&ty) } _ => ty, }; diff --git a/crates/hir-ty/src/infer/closure.rs b/crates/hir-ty/src/infer/closure.rs index 034ed2d691..36327d1d49 100644 --- a/crates/hir-ty/src/infer/closure.rs +++ b/crates/hir-ty/src/infer/closure.rs @@ -18,8 +18,9 @@ use hir_def::{ use hir_expand::name::Name; use intern::sym; use rustc_hash::FxHashMap; -use smallvec::SmallVec; -use stdx::never; +use smallvec::{smallvec, SmallVec}; +use stdx::{format_to, never}; +use syntax::utils::is_raw_identifier; use crate::{ db::{HirDatabase, InternedClosure}, @@ -236,7 +237,13 @@ pub enum CaptureKind { pub struct CapturedItem { pub(crate) place: HirPlace, pub(crate) kind: CaptureKind, - pub(crate) span: MirSpan, + /// The inner vec is the stacks; the outer vec is for each capture reference. + /// + /// Even though we always report only the last span (i.e. the most inclusive span), + /// we need to keep them all, since when a closure occurs inside a closure, we + /// copy all captures of the inner closure to the outer closure, and then we may + /// truncate them, and we want the correct span to be reported. + span_stacks: SmallVec<[SmallVec<[MirSpan; 3]>; 3]>, pub(crate) ty: Binders, } @@ -245,6 +252,11 @@ impl CapturedItem { self.place.local } + /// Returns whether this place has any field (aka. non-deref) projections. + pub fn has_field_projections(&self) -> bool { + self.place.projections.iter().any(|it| !matches!(it, ProjectionElem::Deref)) + } + pub fn ty(&self, subst: &Substitution) -> Ty { self.ty.clone().substitute(Interner, utils::ClosureSubst(subst).parent_subst()) } @@ -253,9 +265,106 @@ impl CapturedItem { self.kind } + pub fn spans(&self) -> SmallVec<[MirSpan; 3]> { + self.span_stacks.iter().map(|stack| *stack.last().expect("empty span stack")).collect() + } + + /// Converts the place to a name that can be inserted into source code. + pub fn place_to_name(&self, owner: DefWithBodyId, db: &dyn HirDatabase) -> String { + let body = db.body(owner); + let mut result = body[self.place.local].name.unescaped().display(db.upcast()).to_string(); + for proj in &self.place.projections { + match proj { + ProjectionElem::Deref => {} + ProjectionElem::Field(Either::Left(f)) => { + match &*f.parent.variant_data(db.upcast()) { + VariantData::Record(fields) => { + result.push('_'); + result.push_str(fields[f.local_id].name.as_str()) + } + VariantData::Tuple(fields) => { + let index = fields.iter().position(|it| it.0 == f.local_id); + if let Some(index) = index { + format_to!(result, "_{index}"); + } + } + VariantData::Unit => {} + } + } + ProjectionElem::Field(Either::Right(f)) => format_to!(result, "_{}", f.index), + &ProjectionElem::ClosureField(field) => format_to!(result, "_{field}"), + ProjectionElem::Index(_) + | ProjectionElem::ConstantIndex { .. } + | ProjectionElem::Subslice { .. } + | ProjectionElem::OpaqueCast(_) => { + never!("Not happen in closure capture"); + continue; + } + } + } + if is_raw_identifier(&result, db.crate_graph()[owner.module(db.upcast()).krate()].edition) { + result.insert_str(0, "r#"); + } + result + } + + pub fn display_place_source_code(&self, owner: DefWithBodyId, db: &dyn HirDatabase) -> String { + let body = db.body(owner); + let krate = owner.krate(db.upcast()); + let edition = db.crate_graph()[krate].edition; + let mut result = body[self.place.local].name.display(db.upcast(), edition).to_string(); + for proj in &self.place.projections { + match proj { + // In source code autoderef kicks in. + ProjectionElem::Deref => {} + ProjectionElem::Field(Either::Left(f)) => { + let variant_data = f.parent.variant_data(db.upcast()); + match &*variant_data { + VariantData::Record(fields) => format_to!( + result, + ".{}", + fields[f.local_id].name.display(db.upcast(), edition) + ), + VariantData::Tuple(fields) => format_to!( + result, + ".{}", + fields.iter().position(|it| it.0 == f.local_id).unwrap_or_default() + ), + VariantData::Unit => {} + } + } + ProjectionElem::Field(Either::Right(f)) => { + let field = f.index; + format_to!(result, ".{field}"); + } + &ProjectionElem::ClosureField(field) => { + format_to!(result, ".{field}"); + } + ProjectionElem::Index(_) + | ProjectionElem::ConstantIndex { .. } + | ProjectionElem::Subslice { .. } + | ProjectionElem::OpaqueCast(_) => { + never!("Not happen in closure capture"); + continue; + } + } + } + let final_derefs_count = self + .place + .projections + .iter() + .rev() + .take_while(|proj| matches!(proj, ProjectionElem::Deref)) + .count(); + result.insert_str(0, &"*".repeat(final_derefs_count)); + result + } + pub fn display_place(&self, owner: DefWithBodyId, db: &dyn HirDatabase) -> String { let body = db.body(owner); - let mut result = body[self.place.local].name.display(db.upcast()).to_string(); + let krate = owner.krate(db.upcast()); + let edition = db.crate_graph()[krate].edition; + let mut result = body[self.place.local].name.display(db.upcast(), edition).to_string(); let mut field_need_paren = false; for proj in &self.place.projections { match proj { @@ -312,7 +421,8 @@ impl CapturedItem { pub(crate) struct CapturedItemWithoutTy { pub(crate) place: HirPlace, pub(crate) kind: CaptureKind, - pub(crate) span: MirSpan, + /// The inner vec is the stacks; the outer vec is for each capture reference. + pub(crate) span_stacks: SmallVec<[SmallVec<[MirSpan; 3]>; 3]>, } impl CapturedItemWithoutTy { @@ -331,7 +441,7 @@ impl CapturedItemWithoutTy { return CapturedItem { place: self.place, kind: self.kind, - span: self.span, + span_stacks: self.span_stacks, ty: replace_placeholder_with_binder(ctx, ty), }; @@ -391,22 +501,26 @@ impl InferenceContext<'_> { let r = self.place_of_expr_without_adjust(tgt_expr)?; let default = vec![]; let adjustments = self.result.expr_adjustments.get(&tgt_expr).unwrap_or(&default); - apply_adjusts_to_place(r, adjustments) + apply_adjusts_to_place(&mut self.current_capture_span_stack, r, adjustments) } + /// Changes `current_capture_span_stack` to contain the stack of spans for this expr. fn place_of_expr_without_adjust(&mut self, tgt_expr: ExprId) -> Option { + self.current_capture_span_stack.clear(); match &self.body[tgt_expr] { Expr::Path(p) => { let resolver = resolver_for_expr(self.db.upcast(), self.owner, tgt_expr); if let Some(ResolveValueResult::ValueNs(ValueNs::LocalBinding(b), _)) = resolver.resolve_path_in_value_ns(self.db.upcast(), p) { + self.current_capture_span_stack.push(MirSpan::ExprId(tgt_expr)); return Some(HirPlace { local: b, projections: vec![] }); } } Expr::Field { expr, name: _ } => { let mut place = self.place_of_expr(*expr)?; let field = self.result.field_resolution(tgt_expr)?; + self.current_capture_span_stack.push(MirSpan::ExprId(tgt_expr)); place.projections.push(ProjectionElem::Field(field)); return Some(place); } @@ -416,6 +530,7 @@ impl InferenceContext<'_> { TyKind::Ref(..) | TyKind::Raw(..) ) { let mut place = self.place_of_expr(*expr)?; + self.current_capture_span_stack.push(MirSpan::ExprId(tgt_expr)); place.projections.push(ProjectionElem::Deref); return Some(place); } @@ -425,29 +540,57 @@ impl InferenceContext<'_> { None } - fn push_capture(&mut self, capture: CapturedItemWithoutTy) { - self.current_captures.push(capture); + fn push_capture(&mut self, place: HirPlace, kind: CaptureKind) { + self.current_captures.push(CapturedItemWithoutTy { + place, + kind, + span_stacks: smallvec![self.current_capture_span_stack.iter().copied().collect()], + }); } - fn ref_expr(&mut self, expr: ExprId) { - if let Some(place) = self.place_of_expr(expr) { - self.add_capture(place, CaptureKind::ByRef(BorrowKind::Shared), expr.into()); + fn truncate_capture_spans(&self, capture: &mut CapturedItemWithoutTy, mut truncate_to: usize) { + // The first span is the identifier, and it must always remain. + truncate_to += 1; + for span_stack in &mut capture.span_stacks { + let mut remained = truncate_to; + let mut actual_truncate_to = 0; + for &span in &*span_stack { + actual_truncate_to += 1; + if !span.is_ref_span(self.body) { + remained -= 1; + if remained == 0 { + break; + } + } + } + if actual_truncate_to < span_stack.len() + && span_stack[actual_truncate_to].is_ref_span(self.body) + { + // Include the ref operator if there is one, we will fix it later (in `strip_captures_ref_span()`) if it's incorrect. + actual_truncate_to += 1; + } + span_stack.truncate(actual_truncate_to); + } + } + + fn ref_expr(&mut self, expr: ExprId, place: Option) { + if let Some(place) = place { + self.add_capture(place, CaptureKind::ByRef(BorrowKind::Shared)); } self.walk_expr(expr); } - fn add_capture(&mut self, place: HirPlace, kind: CaptureKind, span: MirSpan) { + fn add_capture(&mut self, place: HirPlace, kind: CaptureKind) { if self.is_upvar(&place) { - self.push_capture(CapturedItemWithoutTy { place, kind, span }); + self.push_capture(place, kind); } } - fn mutate_expr(&mut self, expr: ExprId) { - if let Some(place) = self.place_of_expr(expr) { + fn mutate_expr(&mut self, expr: ExprId, place: Option) { + if let Some(place) = place { self.add_capture( place, CaptureKind::ByRef(BorrowKind::Mut { kind: MutBorrowKind::Default }), - expr.into(), ); } self.walk_expr(expr); @@ -455,12 +598,12 @@ impl InferenceContext<'_> { fn consume_expr(&mut self, expr: ExprId) { if let Some(place) = self.place_of_expr(expr) { - self.consume_place(place, expr.into()); + self.consume_place(place); } self.walk_expr(expr); } - fn consume_place(&mut self, place: HirPlace, span: MirSpan) { + fn consume_place(&mut self, place: HirPlace) { if self.is_upvar(&place) { let ty = place.ty(self); let kind = if self.is_ty_copy(ty) { @@ -468,13 +611,13 @@ impl InferenceContext<'_> { } else { CaptureKind::ByValue }; - self.push_capture(CapturedItemWithoutTy { place, kind, span }); + self.push_capture(place, kind); } } fn walk_expr_with_adjust(&mut self, tgt_expr: ExprId, adjustment: &[Adjustment]) { if let Some((last, rest)) = adjustment.split_last() { - match last.kind { + match &last.kind { Adjust::NeverToAny | Adjust::Deref(None) | Adjust::Pointer(_) => { self.walk_expr_with_adjust(tgt_expr, rest) } @@ -499,8 +642,10 @@ impl InferenceContext<'_> { Mutability::Not => CaptureKind::ByRef(BorrowKind::Shared), }; if let Some(place) = self.place_of_expr_without_adjust(tgt_expr) { - if let Some(place) = apply_adjusts_to_place(place, rest) { - self.add_capture(place, capture_kind, tgt_expr.into()); + if let Some(place) = + apply_adjusts_to_place(&mut self.current_capture_span_stack, place, rest) + { + self.add_capture(place, capture_kind); } } self.walk_expr_with_adjust(tgt_expr, rest); @@ -582,11 +727,7 @@ impl InferenceContext<'_> { self.walk_pat(&mut capture_mode, arm.pat); } if let Some(c) = capture_mode { - self.push_capture(CapturedItemWithoutTy { - place: discr_place, - kind: c, - span: (*expr).into(), - }) + self.push_capture(discr_place, c); } } } @@ -630,10 +771,11 @@ impl InferenceContext<'_> { } false }; + let place = self.place_of_expr(*expr); if mutability { - self.mutate_expr(*expr); + self.mutate_expr(*expr, place); } else { - self.ref_expr(*expr); + self.ref_expr(*expr, place); } } else { self.select_from_expr(*expr); @@ -648,16 +790,22 @@ impl InferenceContext<'_> { | Expr::Cast { expr, type_ref: _ } => { self.consume_expr(*expr); } - Expr::Ref { expr, rawness: _, mutability } => match mutability { - hir_def::type_ref::Mutability::Shared => self.ref_expr(*expr), - hir_def::type_ref::Mutability::Mut => self.mutate_expr(*expr), - }, + Expr::Ref { expr, rawness: _, mutability } => { + // We need to do this before we push the span so the order will be correct. + let place = self.place_of_expr(*expr); + self.current_capture_span_stack.push(MirSpan::ExprId(tgt_expr)); + match mutability { + hir_def::type_ref::Mutability::Shared => self.ref_expr(*expr, place), + hir_def::type_ref::Mutability::Mut => self.mutate_expr(*expr, place), + } + } Expr::BinaryOp { lhs, rhs, op } => { let Some(op) = op else { return; }; if matches!(op, BinaryOp::Assignment { .. }) { - self.mutate_expr(*lhs); + let place = self.place_of_expr(*lhs); + self.mutate_expr(*lhs, place); self.consume_expr(*rhs); return; } @@ -688,7 +836,11 @@ impl InferenceContext<'_> { ); let mut cc = mem::take(&mut self.current_captures); cc.extend(captures.iter().filter(|it| self.is_upvar(&it.place)).map(|it| { - CapturedItemWithoutTy { place: it.place.clone(), kind: it.kind, span: it.span } + CapturedItemWithoutTy { + place: it.place.clone(), + kind: it.kind, + span_stacks: it.span_stacks.clone(), + } })); self.current_captures = cc; } @@ -810,10 +962,13 @@ impl InferenceContext<'_> { } fn restrict_precision_for_unsafe(&mut self) { - for capture in &mut self.current_captures { + // FIXME: Borrow checker problems without this. + let mut current_captures = std::mem::take(&mut self.current_captures); + for capture in &mut current_captures { let mut ty = self.table.resolve_completely(self.result[capture.place.local].clone()); if ty.as_raw_ptr().is_some() || ty.is_union() { capture.kind = CaptureKind::ByRef(BorrowKind::Shared); + self.truncate_capture_spans(capture, 0); capture.place.projections.truncate(0); continue; } @@ -828,29 +983,35 @@ impl InferenceContext<'_> { ); if ty.as_raw_ptr().is_some() || ty.is_union() { capture.kind = CaptureKind::ByRef(BorrowKind::Shared); + self.truncate_capture_spans(capture, i + 1); capture.place.projections.truncate(i + 1); break; } } } + self.current_captures = current_captures; } fn adjust_for_move_closure(&mut self) { - for capture in &mut self.current_captures { + // FIXME: Borrow checker won't allow without this. + let mut current_captures = std::mem::take(&mut self.current_captures); + for capture in &mut current_captures { if let Some(first_deref) = capture.place.projections.iter().position(|proj| *proj == ProjectionElem::Deref) { + self.truncate_capture_spans(capture, first_deref); capture.place.projections.truncate(first_deref); } capture.kind = CaptureKind::ByValue; } + self.current_captures = current_captures; } fn minimize_captures(&mut self) { - self.current_captures.sort_by_key(|it| it.place.projections.len()); + self.current_captures.sort_unstable_by_key(|it| it.place.projections.len()); let mut hash_map = FxHashMap::::default(); let result = mem::take(&mut self.current_captures); - for item in result { + for mut item in result { let mut lookup_place = HirPlace { local: item.place.local, projections: vec![] }; let mut it = item.place.projections.iter(); let prev_index = loop { @@ -858,12 +1019,17 @@ impl InferenceContext<'_> { break Some(*k); } match it.next() { - Some(it) => lookup_place.projections.push(it.clone()), + Some(it) => { + lookup_place.projections.push(it.clone()); + } None => break None, } }; match prev_index { Some(p) => { + let prev_projections_len = self.current_captures[p].place.projections.len(); + self.truncate_capture_spans(&mut item, prev_projections_len); + self.current_captures[p].span_stacks.extend(item.span_stacks); let len = self.current_captures[p].place.projections.len(); let kind_after_truncate = item.place.capture_kind_of_truncated_place(item.kind, len); @@ -878,113 +1044,128 @@ impl InferenceContext<'_> { } } - fn consume_with_pat(&mut self, mut place: HirPlace, pat: PatId) { - let cnt = self.result.pat_adjustments.get(&pat).map(|it| it.len()).unwrap_or_default(); - place.projections = place - .projections - .iter() - .cloned() - .chain((0..cnt).map(|_| ProjectionElem::Deref)) - .collect::>(); - match &self.body[pat] { - Pat::Missing | Pat::Wild => (), - Pat::Tuple { args, ellipsis } => { - let (al, ar) = args.split_at(ellipsis.map_or(args.len(), |it| it as usize)); - let field_count = match self.result[pat].kind(Interner) { - TyKind::Tuple(_, s) => s.len(Interner), - _ => return, - }; - let fields = 0..field_count; - let it = al.iter().zip(fields.clone()).chain(ar.iter().rev().zip(fields.rev())); - for (arg, i) in it { - let mut p = place.clone(); - p.projections.push(ProjectionElem::Field(Either::Right(TupleFieldId { - tuple: TupleId(!0), // dummy this, as its unused anyways - index: i as u32, - }))); - self.consume_with_pat(p, *arg); - } - } - Pat::Or(pats) => { - for pat in pats.iter() { - self.consume_with_pat(place.clone(), *pat); - } - } - Pat::Record { args, .. } => { - let Some(variant) = self.result.variant_resolution_for_pat(pat) else { - return; - }; - match variant { - VariantId::EnumVariantId(_) | VariantId::UnionId(_) => { - self.consume_place(place, pat.into()) + fn consume_with_pat(&mut self, mut place: HirPlace, tgt_pat: PatId) { + let adjustments_count = + self.result.pat_adjustments.get(&tgt_pat).map(|it| it.len()).unwrap_or_default(); + place.projections.extend((0..adjustments_count).map(|_| ProjectionElem::Deref)); + self.current_capture_span_stack + .extend((0..adjustments_count).map(|_| MirSpan::PatId(tgt_pat))); + 'reset_span_stack: { + match &self.body[tgt_pat] { + Pat::Missing | Pat::Wild => (), + Pat::Tuple { args, ellipsis } => { + let (al, ar) = args.split_at(ellipsis.map_or(args.len(), |it| it as usize)); + let field_count = match self.result[tgt_pat].kind(Interner) { + TyKind::Tuple(_, s) => s.len(Interner), + _ => break 'reset_span_stack, + }; + let fields = 0..field_count; + let it = al.iter().zip(fields.clone()).chain(ar.iter().rev().zip(fields.rev())); + for (&arg, i) in it { + let mut p = place.clone(); + self.current_capture_span_stack.push(MirSpan::PatId(arg)); + p.projections.push(ProjectionElem::Field(Either::Right(TupleFieldId { + tuple: TupleId(!0), // dummy this, as its unused anyways + index: i as u32, + }))); + self.consume_with_pat(p, arg); + self.current_capture_span_stack.pop(); } - VariantId::StructId(s) => { - let vd = &*self.db.struct_data(s).variant_data; - for field_pat in args.iter() { - let arg = field_pat.pat; - let Some(local_id) = vd.field(&field_pat.name) else { - continue; - }; - let mut p = place.clone(); - p.projections.push(ProjectionElem::Field(Either::Left(FieldId { - parent: variant, - local_id, - }))); - self.consume_with_pat(p, arg); + } + Pat::Or(pats) => { + for pat in pats.iter() { + self.consume_with_pat(place.clone(), *pat); + } + } + Pat::Record { args, .. } => { + let Some(variant) = self.result.variant_resolution_for_pat(tgt_pat) else { + break 'reset_span_stack; + }; + match variant { + VariantId::EnumVariantId(_) | VariantId::UnionId(_) => { + self.consume_place(place) + } + VariantId::StructId(s) => { + let vd = &*self.db.struct_data(s).variant_data; + for field_pat in args.iter() { + let arg = field_pat.pat; + let Some(local_id) = vd.field(&field_pat.name) else { + continue; + }; + let mut p = place.clone(); + self.current_capture_span_stack.push(MirSpan::PatId(arg)); + p.projections.push(ProjectionElem::Field(Either::Left(FieldId { + parent: variant, + local_id, + }))); + self.consume_with_pat(p, arg); + self.current_capture_span_stack.pop(); + } } } } - } - Pat::Range { .. } - | Pat::Slice { .. } - | Pat::ConstBlock(_) - | Pat::Path(_) - | Pat::Lit(_) => self.consume_place(place, pat.into()), - Pat::Bind { id: _, subpat: _ } => { - let mode = self.result.binding_modes[pat]; - let capture_kind = match mode { - BindingMode::Move => { - self.consume_place(place, pat.into()); - return; - } - BindingMode::Ref(Mutability::Not) => BorrowKind::Shared, - BindingMode::Ref(Mutability::Mut) => { - BorrowKind::Mut { kind: MutBorrowKind::Default } - } - }; - self.add_capture(place, CaptureKind::ByRef(capture_kind), pat.into()); - } - Pat::TupleStruct { path: _, args, ellipsis } => { - let Some(variant) = self.result.variant_resolution_for_pat(pat) else { - return; - }; - match variant { - VariantId::EnumVariantId(_) | VariantId::UnionId(_) => { - self.consume_place(place, pat.into()) - } - VariantId::StructId(s) => { - let vd = &*self.db.struct_data(s).variant_data; - let (al, ar) = args.split_at(ellipsis.map_or(args.len(), |it| it as usize)); - let fields = vd.fields().iter(); - let it = - al.iter().zip(fields.clone()).chain(ar.iter().rev().zip(fields.rev())); - for (arg, (i, _)) in it { - let mut p = place.clone(); - p.projections.push(ProjectionElem::Field(Either::Left(FieldId { - parent: variant, - local_id: i, - }))); - self.consume_with_pat(p, *arg); + Pat::Range { .. } + | Pat::Slice { .. } + | Pat::ConstBlock(_) + | Pat::Path(_) + | Pat::Lit(_) => self.consume_place(place), + &Pat::Bind { id, subpat: _ } => { + let mode = self.result.binding_modes[tgt_pat]; + let capture_kind = match mode { + BindingMode::Move => { + self.consume_place(place); + break 'reset_span_stack; + } + BindingMode::Ref(Mutability::Not) => BorrowKind::Shared, + BindingMode::Ref(Mutability::Mut) => { + BorrowKind::Mut { kind: MutBorrowKind::Default } + } + }; + self.current_capture_span_stack.push(MirSpan::BindingId(id)); + self.add_capture(place, CaptureKind::ByRef(capture_kind)); + self.current_capture_span_stack.pop(); + } + Pat::TupleStruct { path: _, args, ellipsis } => { + let Some(variant) = self.result.variant_resolution_for_pat(tgt_pat) else { + break 'reset_span_stack; + }; + match variant { + VariantId::EnumVariantId(_) | VariantId::UnionId(_) => { + self.consume_place(place) + } + VariantId::StructId(s) => { + let vd = &*self.db.struct_data(s).variant_data; + let (al, ar) = + args.split_at(ellipsis.map_or(args.len(), |it| it as usize)); + let fields = vd.fields().iter(); + let it = al + .iter() + .zip(fields.clone()) + .chain(ar.iter().rev().zip(fields.rev())); + for (&arg, (i, _)) in it { + let mut p = place.clone(); + self.current_capture_span_stack.push(MirSpan::PatId(arg)); + p.projections.push(ProjectionElem::Field(Either::Left(FieldId { + parent: variant, + local_id: i, + }))); + self.consume_with_pat(p, arg); + self.current_capture_span_stack.pop(); + } } } } + Pat::Ref { pat, mutability: _ } => { + self.current_capture_span_stack.push(MirSpan::PatId(tgt_pat)); + place.projections.push(ProjectionElem::Deref); + self.consume_with_pat(place, *pat); + self.current_capture_span_stack.pop(); + } + Pat::Box { .. } => (), // not supported } - Pat::Ref { pat, mutability: _ } => { - place.projections.push(ProjectionElem::Deref); - self.consume_with_pat(place, *pat) - } - Pat::Box { .. } => (), // not supported } + self.current_capture_span_stack + .truncate(self.current_capture_span_stack.len() - adjustments_count); } fn consume_exprs(&mut self, exprs: impl Iterator) { @@ -1042,12 +1223,28 @@ impl InferenceContext<'_> { CaptureBy::Ref => (), } self.minimize_captures(); + self.strip_captures_ref_span(); let result = mem::take(&mut self.current_captures); let captures = result.into_iter().map(|it| it.with_ty(self)).collect::>(); self.result.closure_info.insert(closure, (captures, closure_kind)); closure_kind } + fn strip_captures_ref_span(&mut self) { + // FIXME: Borrow checker won't allow without this. + let mut captures = std::mem::take(&mut self.current_captures); + for capture in &mut captures { + if matches!(capture.kind, CaptureKind::ByValue) { + for span_stack in &mut capture.span_stacks { + if span_stack[span_stack.len() - 1].is_ref_span(self.body) { + span_stack.truncate(span_stack.len() - 1); + } + } + } + } + self.current_captures = captures; + } + pub(crate) fn infer_closures(&mut self) { let deferred_closures = self.sort_closures(); for (closure, exprs) in deferred_closures.into_iter().rev() { @@ -1108,10 +1305,17 @@ impl InferenceContext<'_> { } } -fn apply_adjusts_to_place(mut r: HirPlace, adjustments: &[Adjustment]) -> Option { +/// Call this only when the last span in the stack isn't a split. +fn apply_adjusts_to_place( + current_capture_span_stack: &mut Vec, + mut r: HirPlace, + adjustments: &[Adjustment], +) -> Option { + let span = *current_capture_span_stack.last().expect("empty capture span stack"); for adj in adjustments { match &adj.kind { Adjust::Deref(None) => { + current_capture_span_stack.push(span); r.projections.push(ProjectionElem::Deref); } _ => return None, diff --git a/crates/hir-ty/src/infer/coerce.rs b/crates/hir-ty/src/infer/coerce.rs index 6f85a4a424..7e758c0b51 100644 --- a/crates/hir-ty/src/infer/coerce.rs +++ b/crates/hir-ty/src/infer/coerce.rs @@ -18,14 +18,13 @@ use triomphe::Arc; use crate::{ autoderef::{Autoderef, AutoderefKind}, db::HirDatabase, - error_lifetime, infer::{ Adjust, Adjustment, AutoBorrow, InferOk, InferenceContext, OverloadedDeref, PointerCast, TypeError, TypeMismatch, }, utils::ClosureSubst, - Canonical, DomainGoal, FnAbi, FnPointer, FnSig, Guidance, InEnvironment, Interner, Solution, - Substitution, TraitEnvironment, Ty, TyBuilder, TyExt, + Canonical, DomainGoal, FnAbi, FnPointer, FnSig, Guidance, InEnvironment, Interner, Lifetime, + Solution, Substitution, TraitEnvironment, Ty, TyBuilder, TyExt, }; use super::unify::InferenceTable; @@ -301,7 +300,7 @@ impl InferenceTable<'_> { // Examine the supertype and consider auto-borrowing. match to_ty.kind(Interner) { TyKind::Raw(mt, _) => return self.coerce_ptr(from_ty, to_ty, *mt), - TyKind::Ref(mt, _, _) => return self.coerce_ref(from_ty, to_ty, *mt), + TyKind::Ref(mt, lt, _) => return self.coerce_ref(from_ty, to_ty, *mt, lt), _ => {} } @@ -377,11 +376,17 @@ impl InferenceTable<'_> { /// Reborrows `&mut A` to `&mut B` and `&(mut) A` to `&B`. /// To match `A` with `B`, autoderef will be performed, /// calling `deref`/`deref_mut` where necessary. - fn coerce_ref(&mut self, from_ty: Ty, to_ty: &Ty, to_mt: Mutability) -> CoerceResult { - let from_mt = match from_ty.kind(Interner) { - &TyKind::Ref(mt, _, _) => { - coerce_mutabilities(mt, to_mt)?; - mt + fn coerce_ref( + &mut self, + from_ty: Ty, + to_ty: &Ty, + to_mt: Mutability, + to_lt: &Lifetime, + ) -> CoerceResult { + let (_from_lt, from_mt) = match from_ty.kind(Interner) { + TyKind::Ref(mt, lt, _) => { + coerce_mutabilities(*mt, to_mt)?; + (lt.clone(), *mt) // clone is probably not good? } _ => return self.unify_and(&from_ty, to_ty, identity), }; @@ -427,8 +432,8 @@ impl InferenceTable<'_> { // compare those. Note that this means we use the target // mutability [1], since it may be that we are coercing // from `&mut T` to `&U`. - let lt = error_lifetime(); // FIXME: handle lifetimes correctly, see rustc - let derefd_from_ty = TyKind::Ref(to_mt, lt, referent_ty).intern(Interner); + let lt = to_lt; // FIXME: Involve rustc LUB and SUB flag checks + let derefd_from_ty = TyKind::Ref(to_mt, lt.clone(), referent_ty).intern(Interner); match autoderef.table.try_unify(&derefd_from_ty, to_ty) { Ok(result) => { found = Some(result.map(|()| derefd_from_ty)); @@ -472,8 +477,10 @@ impl InferenceTable<'_> { } let mut adjustments = auto_deref_adjust_steps(&autoderef); - adjustments - .push(Adjustment { kind: Adjust::Borrow(AutoBorrow::Ref(to_mt)), target: ty.clone() }); + adjustments.push(Adjustment { + kind: Adjust::Borrow(AutoBorrow::Ref(to_lt.clone(), to_mt)), + target: ty.clone(), + }); success(adjustments, ty, goals) } @@ -621,11 +628,11 @@ impl InferenceTable<'_> { (TyKind::Ref(from_mt, _, from_inner), &TyKind::Ref(to_mt, _, _)) => { coerce_mutabilities(*from_mt, to_mt)?; - let lt = error_lifetime(); + let lt = self.new_lifetime_var(); Some(( Adjustment { kind: Adjust::Deref(None), target: from_inner.clone() }, Adjustment { - kind: Adjust::Borrow(AutoBorrow::Ref(to_mt)), + kind: Adjust::Borrow(AutoBorrow::Ref(lt.clone(), to_mt)), target: TyKind::Ref(to_mt, lt, from_inner.clone()).intern(Interner), }, )) diff --git a/crates/hir-ty/src/infer/expr.rs b/crates/hir-ty/src/infer/expr.rs index f5eb37f427..89d92ea9af 100644 --- a/crates/hir-ty/src/infer/expr.rs +++ b/crates/hir-ty/src/infer/expr.rs @@ -635,7 +635,10 @@ impl InferenceContext<'_> { let inner_ty = self.infer_expr_inner(*expr, &expectation); match rawness { Rawness::RawPtr => TyKind::Raw(mutability, inner_ty), - Rawness::Ref => TyKind::Ref(mutability, error_lifetime(), inner_ty), + Rawness::Ref => { + let lt = self.table.new_lifetime_var(); + TyKind::Ref(mutability, lt, inner_ty) + } } .intern(Interner) } @@ -786,18 +789,23 @@ impl InferenceContext<'_> { adj.apply(&mut self.table, base_ty) }); // mutability will be fixed up in `InferenceContext::infer_mut`; - adj.push(Adjustment::borrow(Mutability::Not, self_ty.clone())); + adj.push(Adjustment::borrow( + Mutability::Not, + self_ty.clone(), + self.table.new_lifetime_var(), + )); self.write_expr_adj(*base, adj); if let Some(func) = self .db .trait_data(index_trait) .method_by_name(&Name::new_symbol_root(sym::index.clone())) { - let substs = TyBuilder::subst_for_def(self.db, index_trait, None) - .push(self_ty.clone()) - .push(index_ty.clone()) - .build(); - self.write_method_resolution(tgt_expr, func, substs); + let subst = TyBuilder::subst_for_def(self.db, index_trait, None); + if subst.remaining() != 2 { + return self.err_ty(); + } + let subst = subst.push(self_ty.clone()).push(index_ty.clone()).build(); + self.write_method_resolution(tgt_expr, func, subst); } let assoc = self.resolve_ops_index_output(); let res = self.resolve_associated_type_with_params( @@ -990,7 +998,7 @@ impl InferenceContext<'_> { match fn_x { FnTrait::FnOnce => (), FnTrait::FnMut => { - if let TyKind::Ref(Mutability::Mut, _, inner) = derefed_callee.kind(Interner) { + if let TyKind::Ref(Mutability::Mut, lt, inner) = derefed_callee.kind(Interner) { if adjustments .last() .map(|it| matches!(it.kind, Adjust::Borrow(_))) @@ -999,15 +1007,27 @@ impl InferenceContext<'_> { // prefer reborrow to move adjustments .push(Adjustment { kind: Adjust::Deref(None), target: inner.clone() }); - adjustments.push(Adjustment::borrow(Mutability::Mut, inner.clone())) + adjustments.push(Adjustment::borrow( + Mutability::Mut, + inner.clone(), + lt.clone(), + )) } } else { - adjustments.push(Adjustment::borrow(Mutability::Mut, derefed_callee.clone())); + adjustments.push(Adjustment::borrow( + Mutability::Mut, + derefed_callee.clone(), + self.table.new_lifetime_var(), + )); } } FnTrait::Fn => { if !matches!(derefed_callee.kind(Interner), TyKind::Ref(Mutability::Not, _, _)) { - adjustments.push(Adjustment::borrow(Mutability::Not, derefed_callee.clone())); + adjustments.push(Adjustment::borrow( + Mutability::Not, + derefed_callee.clone(), + self.table.new_lifetime_var(), + )); } } } @@ -1295,10 +1315,12 @@ impl InferenceContext<'_> { // HACK: We can use this substitution for the function because the function itself doesn't // have its own generic parameters. - let subst = TyBuilder::subst_for_def(self.db, trait_, None) - .push(lhs_ty.clone()) - .push(rhs_ty.clone()) - .build(); + let subst = TyBuilder::subst_for_def(self.db, trait_, None); + if subst.remaining() != 2 { + return Ty::new(Interner, TyKind::Error); + } + let subst = subst.push(lhs_ty.clone()).push(rhs_ty.clone()).build(); + self.write_method_resolution(tgt_expr, func, subst.clone()); let method_ty = self.db.value_ty(func.into()).unwrap().substitute(Interner, &subst); @@ -1310,11 +1332,11 @@ impl InferenceContext<'_> { Some(sig) => { let p_left = &sig.params()[0]; if matches!(op, BinaryOp::CmpOp(..) | BinaryOp::Assignment { .. }) { - if let &TyKind::Ref(mtbl, _, _) = p_left.kind(Interner) { + if let TyKind::Ref(mtbl, lt, _) = p_left.kind(Interner) { self.write_expr_adj( lhs, vec![Adjustment { - kind: Adjust::Borrow(AutoBorrow::Ref(mtbl)), + kind: Adjust::Borrow(AutoBorrow::Ref(lt.clone(), *mtbl)), target: p_left.clone(), }], ); @@ -1322,11 +1344,11 @@ impl InferenceContext<'_> { } let p_right = &sig.params()[1]; if matches!(op, BinaryOp::CmpOp(..)) { - if let &TyKind::Ref(mtbl, _, _) = p_right.kind(Interner) { + if let TyKind::Ref(mtbl, lt, _) = p_right.kind(Interner) { self.write_expr_adj( rhs, vec![Adjustment { - kind: Adjust::Borrow(AutoBorrow::Ref(mtbl)), + kind: Adjust::Borrow(AutoBorrow::Ref(lt.clone(), *mtbl)), target: p_right.clone(), }], ); diff --git a/crates/hir-ty/src/infer/mutability.rs b/crates/hir-ty/src/infer/mutability.rs index 66267e08db..7fed5f0203 100644 --- a/crates/hir-ty/src/infer/mutability.rs +++ b/crates/hir-ty/src/infer/mutability.rs @@ -28,7 +28,7 @@ impl InferenceContext<'_> { Adjust::NeverToAny | Adjust::Deref(None) | Adjust::Pointer(_) => (), Adjust::Deref(Some(d)) => *d = OverloadedDeref(Some(mutability)), Adjust::Borrow(b) => match b { - AutoBorrow::Ref(m) | AutoBorrow::RawPtr(m) => mutability = *m, + AutoBorrow::Ref(_, m) | AutoBorrow::RawPtr(m) => mutability = *m, }, } } @@ -125,7 +125,7 @@ impl InferenceContext<'_> { .get_mut(&base) .and_then(|it| it.last_mut()); if let Some(Adjustment { - kind: Adjust::Borrow(AutoBorrow::Ref(mutability)), + kind: Adjust::Borrow(AutoBorrow::Ref(_, mutability)), target, }) = base_adjustments { diff --git a/crates/hir-ty/src/infer/pat.rs b/crates/hir-ty/src/infer/pat.rs index f3c6f13a08..50542b2acd 100644 --- a/crates/hir-ty/src/infer/pat.rs +++ b/crates/hir-ty/src/infer/pat.rs @@ -12,7 +12,6 @@ use stdx::TupleExt; use crate::{ consteval::{try_const_usize, usize_const}, - error_lifetime, infer::{BindingMode, Expectation, InferenceContext, TypeMismatch}, lower::lower_to_chalk_mutability, primitive::UintTy, @@ -394,19 +393,20 @@ impl InferenceContext<'_> { expected: &Ty, default_bm: BindingMode, ) -> Ty { - let expectation = match expected.as_reference() { - Some((inner_ty, _lifetime, _exp_mut)) => inner_ty.clone(), + let (expectation_type, expectation_lt) = match expected.as_reference() { + Some((inner_ty, lifetime, _exp_mut)) => (inner_ty.clone(), lifetime.clone()), None => { let inner_ty = self.table.new_type_var(); + let inner_lt = self.table.new_lifetime_var(); let ref_ty = - TyKind::Ref(mutability, error_lifetime(), inner_ty.clone()).intern(Interner); + TyKind::Ref(mutability, inner_lt.clone(), inner_ty.clone()).intern(Interner); // Unification failure will be reported by the caller. self.unify(&ref_ty, expected); - inner_ty + (inner_ty, inner_lt) } }; - let subty = self.infer_pat(inner_pat, &expectation, default_bm); - TyKind::Ref(mutability, error_lifetime(), subty).intern(Interner) + let subty = self.infer_pat(inner_pat, &expectation_type, default_bm); + TyKind::Ref(mutability, expectation_lt, subty).intern(Interner) } fn infer_bind_pat( @@ -433,7 +433,8 @@ impl InferenceContext<'_> { let bound_ty = match mode { BindingMode::Ref(mutability) => { - TyKind::Ref(mutability, error_lifetime(), inner_ty.clone()).intern(Interner) + let inner_lt = self.table.new_lifetime_var(); + TyKind::Ref(mutability, inner_lt, inner_ty.clone()).intern(Interner) } BindingMode::Move => inner_ty.clone(), }; diff --git a/crates/hir-ty/src/infer/unify.rs b/crates/hir-ty/src/infer/unify.rs index 3e3578b9f9..c0f5ddddcb 100644 --- a/crates/hir-ty/src/infer/unify.rs +++ b/crates/hir-ty/src/infer/unify.rs @@ -17,12 +17,12 @@ use triomphe::Arc; use super::{InferOk, InferResult, InferenceContext, TypeError}; use crate::{ - consteval::unknown_const, db::HirDatabase, error_lifetime, fold_generic_args, - fold_tys_and_consts, to_chalk_trait_id, traits::FnTrait, AliasEq, AliasTy, BoundVar, Canonical, - Const, ConstValue, DebruijnIndex, DomainGoal, GenericArg, GenericArgData, Goal, GoalData, - Guidance, InEnvironment, InferenceVar, Interner, Lifetime, OpaqueTyId, ParamKind, ProjectionTy, - ProjectionTyExt, Scalar, Solution, Substitution, TraitEnvironment, Ty, TyBuilder, TyExt, - TyKind, VariableKind, WhereClause, + consteval::unknown_const, db::HirDatabase, fold_generic_args, fold_tys_and_consts, + to_chalk_trait_id, traits::FnTrait, AliasEq, AliasTy, BoundVar, Canonical, Const, ConstValue, + DebruijnIndex, DomainGoal, GenericArg, GenericArgData, Goal, GoalData, Guidance, InEnvironment, + InferenceVar, Interner, Lifetime, OpaqueTyId, ParamKind, ProjectionTy, ProjectionTyExt, Scalar, + Solution, Substitution, TraitEnvironment, Ty, TyBuilder, TyExt, TyKind, VariableKind, + WhereClause, }; impl InferenceContext<'_> { @@ -105,7 +105,7 @@ impl> Canonicalized { VariableKind::Ty(TyVariableKind::Float) => ctx.new_float_var().cast(Interner), // Chalk can sometimes return new lifetime variables. We just replace them by errors // for now. - VariableKind::Lifetime => error_lifetime().cast(Interner), + VariableKind::Lifetime => ctx.new_lifetime_var().cast(Interner), VariableKind::Const(ty) => ctx.new_const_var(ty.clone()).cast(Interner), }), ); diff --git a/crates/hir-ty/src/layout/tests.rs b/crates/hir-ty/src/layout/tests.rs index 8cb428a610..f40d508f75 100644 --- a/crates/hir-ty/src/layout/tests.rs +++ b/crates/hir-ty/src/layout/tests.rs @@ -42,19 +42,20 @@ fn eval_goal(ra_fixture: &str, minicore: &str) -> Result, LayoutErro hir_def::ModuleDefId::AdtId(x) => { let name = match x { hir_def::AdtId::StructId(x) => { - db.struct_data(x).name.display_no_db().to_smolstr() + db.struct_data(x).name.display_no_db(file_id.edition()).to_smolstr() } hir_def::AdtId::UnionId(x) => { - db.union_data(x).name.display_no_db().to_smolstr() + db.union_data(x).name.display_no_db(file_id.edition()).to_smolstr() } hir_def::AdtId::EnumId(x) => { - db.enum_data(x).name.display_no_db().to_smolstr() + db.enum_data(x).name.display_no_db(file_id.edition()).to_smolstr() } }; (name == "Goal").then_some(Either::Left(x)) } hir_def::ModuleDefId::TypeAliasId(x) => { - let name = db.type_alias_data(x).name.display_no_db().to_smolstr(); + let name = + db.type_alias_data(x).name.display_no_db(file_id.edition()).to_smolstr(); (name == "Goal").then_some(Either::Right(x)) } _ => None, @@ -94,7 +95,7 @@ fn eval_expr(ra_fixture: &str, minicore: &str) -> Result, LayoutErro .declarations() .find_map(|x| match x { hir_def::ModuleDefId::FunctionId(x) => { - let name = db.function_data(x).name.display_no_db().to_smolstr(); + let name = db.function_data(x).name.display_no_db(file_id.edition()).to_smolstr(); (name == "main").then_some(x) } _ => None, @@ -104,7 +105,7 @@ fn eval_expr(ra_fixture: &str, minicore: &str) -> Result, LayoutErro let b = hir_body .bindings .iter() - .find(|x| x.1.name.display_no_db().to_smolstr() == "goal") + .find(|x| x.1.name.display_no_db(file_id.edition()).to_smolstr() == "goal") .unwrap() .0; let infer = db.infer(function_id.into()); diff --git a/crates/hir-ty/src/lib.rs b/crates/hir-ty/src/lib.rs index 21c84511dc..2f4e764f4c 100644 --- a/crates/hir-ty/src/lib.rs +++ b/crates/hir-ty/src/lib.rs @@ -68,6 +68,7 @@ use intern::{sym, Symbol}; use la_arena::{Arena, Idx}; use mir::{MirEvalError, VTableMap}; use rustc_hash::{FxHashMap, FxHashSet}; +use span::Edition; use syntax::ast::{make, ConstArg}; use traits::FnTrait; use triomphe::Arc; @@ -1027,7 +1028,11 @@ where collector.placeholders.into_iter().collect() } -pub fn known_const_to_ast(konst: &Const, db: &dyn HirDatabase) -> Option { +pub fn known_const_to_ast( + konst: &Const, + db: &dyn HirDatabase, + edition: Edition, +) -> Option { if let ConstValue::Concrete(c) = &konst.interned().value { match c.interned { ConstScalar::UnevaluatedConst(GeneralConstId::InTypeConstId(cid), _) => { @@ -1037,5 +1042,5 @@ pub fn known_const_to_ast(konst: &Const, db: &dyn HirDatabase) -> Option (), } } - Some(make::expr_const_value(konst.display(db).to_string().as_str())) + Some(make::expr_const_value(konst.display(db, edition).to_string().as_str())) } diff --git a/crates/hir-ty/src/lower.rs b/crates/hir-ty/src/lower.rs index 67cdb99744..370d9ba99c 100644 --- a/crates/hir-ty/src/lower.rs +++ b/crates/hir-ty/src/lower.rs @@ -6,7 +6,7 @@ //! //! This usually involves resolving names, collecting generic arguments etc. use std::{ - cell::{Cell, RefCell, RefMut}, + cell::{Cell, OnceCell, RefCell, RefMut}, iter, ops::{self, Not as _}, }; @@ -43,7 +43,6 @@ use hir_def::{ use hir_expand::{name::Name, ExpandResult}; use intern::Interned; use la_arena::{Arena, ArenaMap}; -use once_cell::unsync::OnceCell; use rustc_hash::FxHashSet; use rustc_pattern_analysis::Captures; use smallvec::SmallVec; @@ -378,26 +377,25 @@ impl<'a> TyLoweringContext<'a> { // Count the number of `impl Trait` things that appear within our bounds. // Since t hose have been emitted as implicit type args already. counter.set(idx + count_impl_traits(type_ref) as u16); - let ( - _parent_params, - self_param, - type_params, - const_params, - _impl_trait_params, - lifetime_params, - ) = self + let kind = self .generics() .expect("variable impl trait lowering must be in a generic def") - .provenance_split(); - TyKind::BoundVar(BoundVar::new( - self.in_binders, - idx as usize - + self_param as usize - + type_params - + const_params - + lifetime_params, - )) - .intern(Interner) + .iter() + .enumerate() + .filter_map(|(i, (id, data))| match (id, data) { + ( + GenericParamId::TypeParamId(_), + GenericParamDataRef::TypeParamData(data), + ) if data.provenance == TypeParamProvenance::ArgumentImplTrait => { + Some(i) + } + _ => None, + }) + .nth(idx as usize) + .map_or(TyKind::Error, |id| { + TyKind::BoundVar(BoundVar { debruijn: self.in_binders, index: id }) + }); + kind.intern(Interner) } ImplTraitLoweringState::Disallowed => { // FIXME: report error @@ -1553,6 +1551,10 @@ pub(crate) fn generic_predicates_for_param_query( } }; if invalid_target { + // If this is filtered out without lowering, `?Sized` is not gathered into `ctx.unsized_types` + if let TypeBound::Path(_, TraitBoundModifier::Maybe) = &**bound { + ctx.lower_where_predicate(pred, &def, true).for_each(drop); + } return false; } @@ -1741,15 +1743,39 @@ fn implicitly_sized_clauses<'a, 'subst: 'a>( substitution: &'subst Substitution, resolver: &Resolver, ) -> Option + Captures<'a> + Captures<'subst>> { - let is_trait_def = matches!(def, GenericDefId::TraitId(..)); - let generic_args = &substitution.as_slice(Interner)[is_trait_def as usize..]; let sized_trait = db .lang_item(resolver.krate(), LangItem::Sized) - .and_then(|lang_item| lang_item.as_trait().map(to_chalk_trait_id)); + .and_then(|lang_item| lang_item.as_trait().map(to_chalk_trait_id))?; - sized_trait.map(move |sized_trait| { - generic_args - .iter() + let get_trait_self_idx = |container: ItemContainerId| { + if matches!(container, ItemContainerId::TraitId(_)) { + let generics = generics(db.upcast(), def); + Some(generics.len_self()) + } else { + None + } + }; + let trait_self_idx = match def { + GenericDefId::TraitId(_) => Some(0), + GenericDefId::FunctionId(it) => get_trait_self_idx(it.lookup(db.upcast()).container), + GenericDefId::ConstId(it) => get_trait_self_idx(it.lookup(db.upcast()).container), + GenericDefId::TypeAliasId(it) => get_trait_self_idx(it.lookup(db.upcast()).container), + _ => None, + }; + + Some( + substitution + .iter(Interner) + .enumerate() + .filter_map( + move |(idx, generic_arg)| { + if Some(idx) == trait_self_idx { + None + } else { + Some(generic_arg) + } + }, + ) .filter_map(|generic_arg| generic_arg.ty(Interner)) .filter(move |&self_ty| !explicitly_unsized_tys.contains(self_ty)) .map(move |self_ty| { @@ -1757,8 +1783,8 @@ fn implicitly_sized_clauses<'a, 'subst: 'a>( trait_id: sized_trait, substitution: Substitution::from1(Interner, self_ty.clone()), }) - }) - }) + }), + ) } #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -1978,13 +2004,13 @@ fn type_for_type_alias(db: &dyn HirDatabase, t: TypeAliasId) -> Binders { .with_impl_trait_mode(ImplTraitLoweringMode::Opaque) .with_type_param_mode(ParamLoweringMode::Variable); let type_alias_data = db.type_alias_data(t); - if type_alias_data.is_extern { - Binders::empty(Interner, TyKind::Foreign(crate::to_foreign_def_id(t)).intern(Interner)) + let inner = if type_alias_data.is_extern { + TyKind::Foreign(crate::to_foreign_def_id(t)).intern(Interner) } else { let type_ref = &type_alias_data.type_ref; - let inner = ctx.lower_ty(type_ref.as_deref().unwrap_or(&TypeRef::Error)); - make_binders(db, &generics, inner) - } + ctx.lower_ty(type_ref.as_deref().unwrap_or(&TypeRef::Error)) + }; + make_binders(db, &generics, inner) } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] diff --git a/crates/hir-ty/src/method_resolution.rs b/crates/hir-ty/src/method_resolution.rs index 8ba8071d36..5a72b97653 100644 --- a/crates/hir-ty/src/method_resolution.rs +++ b/crates/hir-ty/src/method_resolution.rs @@ -35,7 +35,7 @@ use crate::{ }; /// This is used as a key for indexing impls. -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] pub enum TyFingerprint { // These are lang item impls: Str, @@ -542,7 +542,8 @@ impl ReceiverAdjustments { } } if let Some(m) = self.autoref { - let a = Adjustment::borrow(m, ty); + let lt = table.new_lifetime_var(); + let a = Adjustment::borrow(m, ty, lt); ty = a.target.clone(); adjust.push(a); } @@ -1066,7 +1067,7 @@ fn iterate_method_candidates_by_receiver( // be found in any of the derefs of receiver_ty, so we have to go through // that, including raw derefs. table.run_in_snapshot(|table| { - let mut autoderef = autoderef::Autoderef::new(table, receiver_ty.clone(), true); + let mut autoderef = autoderef::Autoderef::new_no_tracking(table, receiver_ty.clone(), true); while let Some((self_ty, _)) = autoderef.next() { iterate_inherent_methods( &self_ty, @@ -1081,7 +1082,7 @@ fn iterate_method_candidates_by_receiver( ControlFlow::Continue(()) })?; table.run_in_snapshot(|table| { - let mut autoderef = autoderef::Autoderef::new(table, receiver_ty.clone(), true); + let mut autoderef = autoderef::Autoderef::new_no_tracking(table, receiver_ty.clone(), true); while let Some((self_ty, _)) = autoderef.next() { if matches!(self_ty.kind(Interner), TyKind::InferenceVar(_, TyVariableKind::General)) { // don't try to resolve methods on unknown types @@ -1656,7 +1657,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, false); + let mut autoderef = autoderef::Autoderef::new_no_tracking(table, ty, false); while let Some((ty, derefs)) = autoderef.next() { deref_chain.push(( autoderef.table.canonicalize(ty), diff --git a/crates/hir-ty/src/mir.rs b/crates/hir-ty/src/mir.rs index 06a4236e0a..22d4da0e75 100644 --- a/crates/hir-ty/src/mir.rs +++ b/crates/hir-ty/src/mir.rs @@ -16,7 +16,8 @@ use base_db::CrateId; use chalk_ir::Mutability; use either::Either; use hir_def::{ - hir::{BindingId, Expr, ExprId, Ordering, PatId}, + body::Body, + hir::{BindingAnnotation, BindingId, Expr, ExprId, Ordering, PatId}, DefWithBodyId, FieldId, StaticId, TupleFieldId, UnionId, VariantId, }; use la_arena::{Arena, ArenaMap, Idx, RawIdx}; @@ -158,7 +159,10 @@ impl ProjectionElem { subst.at(Interner, 0).assert_ty_ref(Interner).clone() } _ => { - never!("Overloaded deref on type {} is not a projection", base.display(db)); + never!( + "Overloaded deref on type {} is not a projection", + base.display(db, db.crate_graph()[krate].edition) + ); TyKind::Error.intern(Interner) } }, @@ -633,6 +637,7 @@ pub enum TerminatorKind { }, } +// Order of variants in this enum matter: they are used to compare borrow kinds. #[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)] pub enum BorrowKind { /// Data must be immutable and is aliasable. @@ -663,15 +668,16 @@ pub enum BorrowKind { Mut { kind: MutBorrowKind }, } +// Order of variants in this enum matter: they are used to compare borrow kinds. #[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)] pub enum MutBorrowKind { + /// Data must be immutable but not aliasable. This kind of borrow cannot currently + /// be expressed by the user and is used only in implicit closure bindings. + ClosureCapture, Default, /// This borrow arose from method-call auto-ref /// (i.e., adjustment::Adjust::Borrow). TwoPhasedBorrow, - /// Data must be immutable but not aliasable. This kind of borrow cannot currently - /// be expressed by the user and is used only in implicit closure bindings. - ClosureCapture, } impl BorrowKind { @@ -1169,6 +1175,20 @@ pub enum MirSpan { Unknown, } +impl MirSpan { + pub fn is_ref_span(&self, body: &Body) -> bool { + match *self { + MirSpan::ExprId(expr) => matches!(body[expr], Expr::Ref { .. }), + // FIXME: Figure out if this is correct wrt. match ergonomics. + MirSpan::BindingId(binding) => matches!( + body.bindings[binding].mode, + BindingAnnotation::Ref | BindingAnnotation::RefMut + ), + MirSpan::PatId(_) | MirSpan::SelfParam | MirSpan::Unknown => false, + } + } +} + impl_from!(ExprId, PatId for MirSpan); impl From<&ExprId> for MirSpan { diff --git a/crates/hir-ty/src/mir/eval.rs b/crates/hir-ty/src/mir/eval.rs index f8083e8985..1bb0c18868 100644 --- a/crates/hir-ty/src/mir/eval.rs +++ b/crates/hir-ty/src/mir/eval.rs @@ -23,7 +23,7 @@ use rustc_apfloat::{ Float, }; use rustc_hash::{FxHashMap, FxHashSet}; -use span::FileId; +use span::{Edition, FileId}; use stdx::never; use syntax::{SyntaxNodePtr, TextRange}; use triomphe::Arc; @@ -358,6 +358,7 @@ impl MirEvalError { f: &mut String, db: &dyn HirDatabase, span_formatter: impl Fn(FileId, TextRange) -> String, + edition: Edition, ) -> std::result::Result<(), std::fmt::Error> { writeln!(f, "Mir eval error:")?; let mut err = self; @@ -370,7 +371,7 @@ impl MirEvalError { writeln!( f, "In function {} ({:?})", - function_name.name.display(db.upcast()), + function_name.name.display(db.upcast(), edition), func )?; } @@ -415,7 +416,7 @@ impl MirEvalError { write!( f, "Layout for type `{}` is not available due {err:?}", - ty.display(db).with_closure_style(ClosureStyle::ClosureWithId) + ty.display(db, edition).with_closure_style(ClosureStyle::ClosureWithId) )?; } MirEvalError::MirLowerError(func, err) => { @@ -423,16 +424,17 @@ impl MirEvalError { writeln!( f, "MIR lowering for function `{}` ({:?}) failed due:", - function_name.name.display(db.upcast()), + function_name.name.display(db.upcast(), edition), func )?; - err.pretty_print(f, db, span_formatter)?; + err.pretty_print(f, db, span_formatter, edition)?; } MirEvalError::ConstEvalError(name, err) => { MirLowerError::ConstEvalError((**name).into(), err.clone()).pretty_print( f, db, span_formatter, + edition, )?; } MirEvalError::UndefinedBehavior(_) @@ -1516,9 +1518,97 @@ impl Evaluator<'_> { self.size_of_sized(target_ty, locals, "destination of int to int cast")?; Owned(current[0..dest_size].to_vec()) } - CastKind::FloatToInt => not_supported!("float to int cast"), - CastKind::FloatToFloat => not_supported!("float to float cast"), - CastKind::IntToFloat => not_supported!("float to int cast"), + CastKind::FloatToInt => { + let ty = self.operand_ty(operand, locals)?; + let TyKind::Scalar(chalk_ir::Scalar::Float(ty)) = ty.kind(Interner) else { + not_supported!("invalid float to int cast"); + }; + let value = self.eval_operand(operand, locals)?.get(self)?; + let value = match ty { + chalk_ir::FloatTy::F32 => { + let value = value.try_into().unwrap(); + f32::from_le_bytes(value) as f64 + } + chalk_ir::FloatTy::F64 => { + let value = value.try_into().unwrap(); + f64::from_le_bytes(value) + } + chalk_ir::FloatTy::F16 | chalk_ir::FloatTy::F128 => { + not_supported!("unstable floating point type f16 and f128"); + } + }; + let is_signed = matches!( + target_ty.kind(Interner), + TyKind::Scalar(chalk_ir::Scalar::Int(_)) + ); + let dest_size = + self.size_of_sized(target_ty, locals, "destination of float to int cast")?; + let dest_bits = dest_size * 8; + let (max, min) = if dest_bits == 128 { + (i128::MAX, i128::MIN) + } else if is_signed { + let max = 1i128 << (dest_bits - 1); + (max - 1, -max) + } else { + (1i128 << dest_bits, 0) + }; + let value = (value as i128).min(max).max(min); + let result = value.to_le_bytes(); + Owned(result[0..dest_size].to_vec()) + } + CastKind::FloatToFloat => { + let ty = self.operand_ty(operand, locals)?; + let TyKind::Scalar(chalk_ir::Scalar::Float(ty)) = ty.kind(Interner) else { + not_supported!("invalid float to int cast"); + }; + let value = self.eval_operand(operand, locals)?.get(self)?; + let value = match ty { + chalk_ir::FloatTy::F32 => { + let value = value.try_into().unwrap(); + f32::from_le_bytes(value) as f64 + } + chalk_ir::FloatTy::F64 => { + let value = value.try_into().unwrap(); + f64::from_le_bytes(value) + } + chalk_ir::FloatTy::F16 | chalk_ir::FloatTy::F128 => { + not_supported!("unstable floating point type f16 and f128"); + } + }; + let TyKind::Scalar(chalk_ir::Scalar::Float(target_ty)) = + target_ty.kind(Interner) + else { + not_supported!("invalid float to float cast"); + }; + match target_ty { + chalk_ir::FloatTy::F32 => Owned((value as f32).to_le_bytes().to_vec()), + chalk_ir::FloatTy::F64 => Owned((value as f64).to_le_bytes().to_vec()), + chalk_ir::FloatTy::F16 | chalk_ir::FloatTy::F128 => { + not_supported!("unstable floating point type f16 and f128"); + } + } + } + CastKind::IntToFloat => { + let current_ty = self.operand_ty(operand, locals)?; + let is_signed = matches!( + current_ty.kind(Interner), + TyKind::Scalar(chalk_ir::Scalar::Int(_)) + ); + let value = pad16(self.eval_operand(operand, locals)?.get(self)?, is_signed); + let value = i128::from_le_bytes(value); + let TyKind::Scalar(chalk_ir::Scalar::Float(target_ty)) = + target_ty.kind(Interner) + else { + not_supported!("invalid int to float cast"); + }; + match target_ty { + chalk_ir::FloatTy::F32 => Owned((value as f32).to_le_bytes().to_vec()), + chalk_ir::FloatTy::F64 => Owned((value as f64).to_le_bytes().to_vec()), + chalk_ir::FloatTy::F16 | chalk_ir::FloatTy::F128 => { + not_supported!("unstable floating point type f16 and f128"); + } + } + } CastKind::FnPtrToPtr => not_supported!("fn ptr to ptr cast"), }, }) @@ -2675,10 +2765,11 @@ impl Evaluator<'_> { let db = self.db.upcast(); let loc = variant.lookup(db); let enum_loc = loc.parent.lookup(db); + let edition = self.db.crate_graph()[self.crate_id].edition; let name = format!( "{}::{}", - enum_loc.id.item_tree(db)[enum_loc.id.value].name.display(db.upcast()), - loc.id.item_tree(db)[loc.id.value].name.display(db.upcast()), + enum_loc.id.item_tree(db)[enum_loc.id.value].name.display(db.upcast(), edition), + loc.id.item_tree(db)[loc.id.value].name.display(db.upcast(), edition), ); Err(MirEvalError::ConstEvalError(name, Box::new(e))) } diff --git a/crates/hir-ty/src/mir/eval/shim.rs b/crates/hir-ty/src/mir/eval/shim.rs index d76f538187..0cdad74a4f 100644 --- a/crates/hir-ty/src/mir/eval/shim.rs +++ b/crates/hir-ty/src/mir/eval/shim.rs @@ -856,7 +856,11 @@ impl Evaluator<'_> { 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(), + Err(_) => { + let krate = locals.body.owner.krate(self.db.upcast()); + let edition = self.db.crate_graph()[krate].edition; + ty.display(self.db, edition).to_string() + } }; let len = ty_name.len(); let addr = self.heap_allocate(len, 1)?; diff --git a/crates/hir-ty/src/mir/eval/tests.rs b/crates/hir-ty/src/mir/eval/tests.rs index b21a401fa7..371a5278dc 100644 --- a/crates/hir-ty/src/mir/eval/tests.rs +++ b/crates/hir-ty/src/mir/eval/tests.rs @@ -1,5 +1,5 @@ use hir_def::db::DefDatabase; -use span::EditionedFileId; +use span::{Edition, EditionedFileId}; use syntax::{TextRange, TextSize}; use test_fixture::WithFixture; @@ -15,7 +15,7 @@ fn eval_main(db: &TestDB, file_id: EditionedFileId) -> Result<(String, String), .declarations() .find_map(|x| match x { hir_def::ModuleDefId::FunctionId(x) => { - if db.function_data(x).name.display(db).to_string() == "main" { + if db.function_data(x).name.display(db, Edition::CURRENT).to_string() == "main" { Some(x) } else { None @@ -63,7 +63,7 @@ fn check_pass_and_stdio(ra_fixture: &str, expected_stdout: &str, expected_stderr let span_formatter = |file, range: TextRange| { format!("{:?} {:?}..{:?}", file, line_index(range.start()), line_index(range.end())) }; - e.pretty_print(&mut err, &db, span_formatter).unwrap(); + e.pretty_print(&mut err, &db, span_formatter, Edition::CURRENT).unwrap(); panic!("Error in interpreting: {err}"); } Ok((stdout, stderr)) => { diff --git a/crates/hir-ty/src/mir/lower.rs b/crates/hir-ty/src/mir/lower.rs index 9aa2eeebc1..9e23550451 100644 --- a/crates/hir-ty/src/mir/lower.rs +++ b/crates/hir-ty/src/mir/lower.rs @@ -21,7 +21,7 @@ use hir_expand::name::Name; use la_arena::ArenaMap; use rustc_apfloat::Float; use rustc_hash::FxHashMap; -use span::FileId; +use span::{Edition, FileId}; use syntax::TextRange; use triomphe::Arc; @@ -157,13 +157,18 @@ impl MirLowerError { f: &mut String, db: &dyn HirDatabase, span_formatter: impl Fn(FileId, TextRange) -> String, + edition: Edition, ) -> std::result::Result<(), std::fmt::Error> { match self { MirLowerError::ConstEvalError(name, e) => { writeln!(f, "In evaluating constant {name}")?; match &**e { - ConstEvalError::MirLowerError(e) => e.pretty_print(f, db, span_formatter)?, - ConstEvalError::MirEvalError(e) => e.pretty_print(f, db, span_formatter)?, + ConstEvalError::MirLowerError(e) => { + e.pretty_print(f, db, span_formatter, edition)? + } + ConstEvalError::MirEvalError(e) => { + e.pretty_print(f, db, span_formatter, edition)? + } } } MirLowerError::MissingFunctionDefinition(owner, it) => { @@ -171,15 +176,15 @@ impl MirLowerError { writeln!( f, "Missing function definition for {}", - body.pretty_print_expr(db.upcast(), *owner, *it) + body.pretty_print_expr(db.upcast(), *owner, *it, edition) )?; } MirLowerError::TypeMismatch(e) => match e { Some(e) => writeln!( f, "Type mismatch: Expected {}, found {}", - e.expected.display(db), - e.actual.display(db), + e.expected.display(db, edition), + e.actual.display(db, edition), )?, None => writeln!(f, "Type mismatch: types mismatch with {{unknown}}",)?, }, @@ -189,11 +194,11 @@ impl MirLowerError { writeln!( f, "Generic arg not provided for {}", - param.name().unwrap_or(&Name::missing()).display(db.upcast()) + param.name().unwrap_or(&Name::missing()).display(db.upcast(), edition) )?; writeln!(f, "Provided args: [")?; for g in subst.iter(Interner) { - write!(f, " {},", g.display(db))?; + write!(f, " {},", g.display(db, edition))?; } writeln!(f, "]")?; } @@ -242,8 +247,8 @@ impl From for MirLowerError { } impl MirLowerError { - fn unresolved_path(db: &dyn HirDatabase, p: &Path) -> Self { - Self::UnresolvedName(p.display(db).to_string()) + fn unresolved_path(db: &dyn HirDatabase, p: &Path, edition: Edition) -> Self { + Self::UnresolvedName(p.display(db, edition).to_string()) } } @@ -337,7 +342,7 @@ impl<'ctx> MirLowerCtx<'ctx> { self.push_assignment(current, place, Operand::Copy(p).into(), expr_id.into()); Ok(Some(current)) } - Adjust::Borrow(AutoBorrow::Ref(m) | AutoBorrow::RawPtr(m)) => { + Adjust::Borrow(AutoBorrow::Ref(_, m) | AutoBorrow::RawPtr(m)) => { let Some((p, current)) = self.lower_expr_as_place_with_adjust(current, expr_id, true, rest)? else { @@ -436,7 +441,8 @@ impl<'ctx> MirLowerCtx<'ctx> { VariantId::UnionId(_) => implementation_error!("Union variant as path"), } } else { - let unresolved_name = || MirLowerError::unresolved_path(self.db, p); + let unresolved_name = + || MirLowerError::unresolved_path(self.db, p, self.edition()); let resolver = resolver_for_expr(self.db.upcast(), self.owner, expr_id); resolver .resolve_path_in_value_ns_fully(self.db.upcast(), p) @@ -662,7 +668,7 @@ impl<'ctx> MirLowerCtx<'ctx> { let (func_id, generic_args) = self.infer.method_resolution(expr_id).ok_or_else(|| { MirLowerError::UnresolvedMethod( - method_name.display(self.db.upcast()).to_string(), + method_name.display(self.db.upcast(), self.edition()).to_string(), ) })?; let func = Operand::from_fn(self.db, func_id, generic_args); @@ -803,7 +809,9 @@ impl<'ctx> MirLowerCtx<'ctx> { }; let variant_id = self.infer.variant_resolution_for_expr(expr_id).ok_or_else(|| match path { - Some(p) => MirLowerError::UnresolvedName(p.display(self.db).to_string()), + Some(p) => MirLowerError::UnresolvedName( + p.display(self.db, self.edition()).to_string(), + ), None => MirLowerError::RecordLiteralWithoutPath, })?; let subst = match self.expr_ty_without_adjust(expr_id).kind(Interner) { @@ -1172,8 +1180,15 @@ impl<'ctx> MirLowerCtx<'ctx> { let placeholder_subst = self.placeholder_subst(); let tmp_ty = capture.ty.clone().substitute(Interner, &placeholder_subst); - let tmp: Place = self.temp(tmp_ty, current, capture.span)?.into(); - self.push_assignment(current, tmp, Rvalue::Ref(*bk, p), capture.span); + // FIXME: Handle more than one span. + let capture_spans = capture.spans(); + let tmp: Place = self.temp(tmp_ty, current, capture_spans[0])?.into(); + self.push_assignment( + current, + tmp, + Rvalue::Ref(*bk, p), + capture_spans[0], + ); operands.push(Operand::Move(tmp)); } CaptureKind::ByValue => operands.push(Operand::Move(p)), @@ -1378,7 +1393,9 @@ impl<'ctx> MirLowerCtx<'ctx> { "only `char` and numeric types are allowed in range patterns" ), }; - let unresolved_name = || MirLowerError::unresolved_path(self.db, c.as_ref()); + let edition = self.edition(); + let unresolved_name = + || MirLowerError::unresolved_path(self.db, c.as_ref(), edition); let resolver = self.owner.resolver(self.db.upcast()); let pr = resolver .resolve_path_in_value_ns(self.db.upcast(), c.as_ref()) @@ -1904,19 +1921,25 @@ impl<'ctx> MirLowerCtx<'ctx> { match r { Ok(r) => Ok(r), Err(e) => { + let edition = self.edition(); let db = self.db.upcast(); let loc = variant.lookup(db); let enum_loc = loc.parent.lookup(db); let name = format!( "{}::{}", - enum_loc.id.item_tree(db)[enum_loc.id.value].name.display(db.upcast()), - loc.id.item_tree(db)[loc.id.value].name.display(db.upcast()), + enum_loc.id.item_tree(db)[enum_loc.id.value].name.display(db.upcast(), edition), + loc.id.item_tree(db)[loc.id.value].name.display(db.upcast(), edition), ); Err(MirLowerError::ConstEvalError(name.into(), Box::new(e))) } } } + fn edition(&self) -> Edition { + let krate = self.owner.krate(self.db.upcast()); + self.db.crate_graph()[krate].edition + } + fn drop_until_scope( &mut self, scope_index: usize, @@ -2121,18 +2144,24 @@ pub fn mir_body_for_closure_query( } pub fn mir_body_query(db: &dyn HirDatabase, def: DefWithBodyId) -> Result> { + let krate = def.krate(db.upcast()); + let edition = db.crate_graph()[krate].edition; let detail = match def { - DefWithBodyId::FunctionId(it) => db.function_data(it).name.display(db.upcast()).to_string(), - DefWithBodyId::StaticId(it) => db.static_data(it).name.display(db.upcast()).to_string(), + DefWithBodyId::FunctionId(it) => { + db.function_data(it).name.display(db.upcast(), edition).to_string() + } + DefWithBodyId::StaticId(it) => { + db.static_data(it).name.display(db.upcast(), edition).to_string() + } DefWithBodyId::ConstId(it) => db .const_data(it) .name .clone() .unwrap_or_else(Name::missing) - .display(db.upcast()) + .display(db.upcast(), edition) .to_string(), DefWithBodyId::VariantId(it) => { - db.enum_variant_data(it).name.display(db.upcast()).to_string() + db.enum_variant_data(it).name.display(db.upcast(), edition).to_string() } DefWithBodyId::InTypeConstId(it) => format!("in type const {it:?}"), }; diff --git a/crates/hir-ty/src/mir/lower/pattern_matching.rs b/crates/hir-ty/src/mir/lower/pattern_matching.rs index 34e0f30afb..b1c0d1f2b3 100644 --- a/crates/hir-ty/src/mir/lower/pattern_matching.rs +++ b/crates/hir-ty/src/mir/lower/pattern_matching.rs @@ -347,7 +347,8 @@ impl MirLowerCtx<'_> { // A const don't bind anything. Only needs check. return Ok((current, current_else)); } - let unresolved_name = || MirLowerError::unresolved_path(self.db, p); + let unresolved_name = + || MirLowerError::unresolved_path(self.db, p, self.edition()); let resolver = self.owner.resolver(self.db.upcast()); let pr = resolver .resolve_path_in_value_ns(self.db.upcast(), p) diff --git a/crates/hir-ty/src/mir/pretty.rs b/crates/hir-ty/src/mir/pretty.rs index 0c641d7c6c..df56071aa9 100644 --- a/crates/hir-ty/src/mir/pretty.rs +++ b/crates/hir-ty/src/mir/pretty.rs @@ -9,6 +9,7 @@ use either::Either; use hir_def::{body::Body, hir::BindingId}; use hir_expand::{name::Name, Lookup}; use la_arena::ArenaMap; +use span::Edition; use crate::{ db::HirDatabase, @@ -44,18 +45,21 @@ impl MirBody { ctx.for_body(|this| match ctx.body.owner { hir_def::DefWithBodyId::FunctionId(id) => { let data = db.function_data(id); - w!(this, "fn {}() ", data.name.display(db.upcast())); + w!(this, "fn {}() ", data.name.display(db.upcast(), Edition::LATEST)); } hir_def::DefWithBodyId::StaticId(id) => { let data = db.static_data(id); - w!(this, "static {}: _ = ", data.name.display(db.upcast())); + w!(this, "static {}: _ = ", data.name.display(db.upcast(), Edition::LATEST)); } hir_def::DefWithBodyId::ConstId(id) => { let data = db.const_data(id); w!( this, "const {}: _ = ", - data.name.as_ref().unwrap_or(&Name::missing()).display(db.upcast()) + data.name + .as_ref() + .unwrap_or(&Name::missing()) + .display(db.upcast(), Edition::LATEST) ); } hir_def::DefWithBodyId::VariantId(id) => { @@ -64,8 +68,12 @@ impl MirBody { w!( this, "enum {}::{} = ", - enum_loc.id.item_tree(db.upcast())[enum_loc.id.value].name.display(db.upcast()), - loc.id.item_tree(db.upcast())[loc.id.value].name.display(db.upcast()), + enum_loc.id.item_tree(db.upcast())[enum_loc.id.value] + .name + .display(db.upcast(), Edition::LATEST), + loc.id.item_tree(db.upcast())[loc.id.value] + .name + .display(db.upcast(), Edition::LATEST), ) } hir_def::DefWithBodyId::InTypeConstId(id) => { @@ -122,7 +130,7 @@ impl HirDisplay for LocalName { match self { LocalName::Unknown(l) => write!(f, "_{}", u32::from(l.into_raw())), LocalName::Binding(n, l) => { - write!(f, "{}_{}", n.display(f.db.upcast()), u32::from(l.into_raw())) + write!(f, "{}_{}", n.display(f.db.upcast(), f.edition()), u32::from(l.into_raw())) } } } @@ -200,7 +208,7 @@ impl<'a> MirPrettyCtx<'a> { wln!( self, "let {}: {};", - self.local_name(id).display(self.db), + self.local_name(id).display_test(self.db), self.hir_display(&local.ty) ); } @@ -231,10 +239,18 @@ impl<'a> MirPrettyCtx<'a> { wln!(this, ";"); } StatementKind::StorageDead(p) => { - wln!(this, "StorageDead({})", this.local_name(*p).display(self.db)); + wln!( + this, + "StorageDead({})", + this.local_name(*p).display_test(self.db) + ); } StatementKind::StorageLive(p) => { - wln!(this, "StorageLive({})", this.local_name(*p).display(self.db)); + wln!( + this, + "StorageLive({})", + this.local_name(*p).display_test(self.db) + ); } StatementKind::Deinit(p) => { w!(this, "Deinit("); @@ -297,7 +313,7 @@ impl<'a> MirPrettyCtx<'a> { fn f(this: &mut MirPrettyCtx<'_>, local: LocalId, projections: &[PlaceElem]) { let Some((last, head)) = projections.split_last() else { // no projection - w!(this, "{}", this.local_name(local).display(this.db)); + w!(this, "{}", this.local_name(local).display_test(this.db)); return; }; match last { @@ -317,13 +333,13 @@ impl<'a> MirPrettyCtx<'a> { w!( this, " as {}).{}", - variant_name.display(this.db.upcast()), - name.display(this.db.upcast()) + variant_name.display(this.db.upcast(), Edition::LATEST), + name.display(this.db.upcast(), Edition::LATEST) ); } hir_def::VariantId::StructId(_) | hir_def::VariantId::UnionId(_) => { f(this, local, head); - w!(this, ".{}", name.display(this.db.upcast())); + w!(this, ".{}", name.display(this.db.upcast(), Edition::LATEST)); } } } @@ -337,7 +353,7 @@ impl<'a> MirPrettyCtx<'a> { } ProjectionElem::Index(l) => { f(this, local, head); - w!(this, "[{}]", this.local_name(*l).display(this.db)); + w!(this, "[{}]", this.local_name(*l).display_test(this.db)); } it => { f(this, local, head); @@ -387,7 +403,7 @@ impl<'a> MirPrettyCtx<'a> { Rvalue::Repeat(op, len) => { w!(self, "["); self.operand(op); - w!(self, "; {}]", len.display(self.db)); + w!(self, "; {}]", len.display_test(self.db)); } Rvalue::Aggregate(AggregateKind::Adt(_, _), it) => { w!(self, "Adt("); @@ -458,6 +474,6 @@ impl<'a> MirPrettyCtx<'a> { } fn hir_display(&self, ty: &'a T) -> impl Display + 'a { - ty.display(self.db).with_closure_style(ClosureStyle::ClosureWithSubst) + ty.display_test(self.db).with_closure_style(ClosureStyle::ClosureWithSubst) } } diff --git a/crates/hir-ty/src/tests.rs b/crates/hir-ty/src/tests.rs index 0fcd789f76..bcf9d5ceff 100644 --- a/crates/hir-ty/src/tests.rs +++ b/crates/hir-ty/src/tests.rs @@ -1,3 +1,4 @@ +mod closure_captures; mod coercion; mod diagnostics; mod display_source_code; @@ -12,6 +13,7 @@ mod traits; mod type_alias_impl_traits; use std::env; +use std::sync::LazyLock; use base_db::SourceDatabaseFileInputExt as _; use expect_test::Expect; @@ -25,7 +27,7 @@ use hir_def::{ AssocItemId, DefWithBodyId, HasModule, LocalModuleId, Lookup, ModuleDefId, }; use hir_expand::{db::ExpandDatabase, FileRange, InFile}; -use once_cell::race::OnceBool; +use itertools::Itertools; use rustc_hash::FxHashMap; use stdx::format_to; use syntax::{ @@ -50,8 +52,8 @@ use crate::{ // `env UPDATE_EXPECT=1 cargo test -p hir_ty` to update the snapshots. fn setup_tracing() -> Option { - static ENABLE: OnceBool = OnceBool::new(); - if !ENABLE.get_or_init(|| env::var("CHALK_DEBUG").is_ok()) { + static ENABLE: LazyLock = LazyLock::new(|| env::var("CHALK_DEBUG").is_ok()); + if !*ENABLE { return None; } @@ -94,7 +96,7 @@ fn check_impl(ra_fixture: &str, allow_none: bool, only_types: bool, display_sour let mut had_annotations = false; let mut mismatches = FxHashMap::default(); let mut types = FxHashMap::default(); - let mut adjustments = FxHashMap::<_, Vec<_>>::default(); + let mut adjustments = FxHashMap::default(); for (file_id, annotations) in db.extract_annotations() { for (range, expected) in annotations { let file_range = FileRange { file_id, range }; @@ -107,13 +109,7 @@ fn check_impl(ra_fixture: &str, allow_none: bool, only_types: bool, display_sour } else if expected.starts_with("adjustments:") { adjustments.insert( file_range, - expected - .trim_start_matches("adjustments:") - .trim() - .split(',') - .map(|it| it.trim().to_owned()) - .filter(|it| !it.is_empty()) - .collect(), + expected.trim_start_matches("adjustments:").trim().to_owned(), ); } else { panic!("unexpected annotation: {expected}"); @@ -200,7 +196,7 @@ fn check_impl(ra_fixture: &str, allow_none: bool, only_types: bool, display_sour adjustments .iter() .map(|Adjustment { kind, .. }| format!("{kind:?}")) - .collect::>() + .join(", ") ); } } diff --git a/crates/hir-ty/src/tests/closure_captures.rs b/crates/hir-ty/src/tests/closure_captures.rs new file mode 100644 index 0000000000..22cef3505b --- /dev/null +++ b/crates/hir-ty/src/tests/closure_captures.rs @@ -0,0 +1,433 @@ +use base_db::salsa::InternKey; +use expect_test::{expect, Expect}; +use hir_def::db::DefDatabase; +use hir_expand::files::InFileWrapper; +use itertools::Itertools; +use span::{HirFileId, TextRange}; +use syntax::{AstNode, AstPtr}; +use test_fixture::WithFixture; + +use crate::db::{HirDatabase, InternedClosureId}; +use crate::display::HirDisplay; +use crate::mir::MirSpan; +use crate::test_db::TestDB; + +use super::visit_module; + +fn check_closure_captures(ra_fixture: &str, expect: Expect) { + let (db, file_id) = TestDB::with_single_file(ra_fixture); + let module = db.module_for_file(file_id); + let def_map = module.def_map(&db); + + let mut defs = Vec::new(); + visit_module(&db, &def_map, module.local_id, &mut |it| defs.push(it)); + + let mut captures_info = Vec::new(); + for def in defs { + let infer = db.infer(def); + let db = &db; + captures_info.extend(infer.closure_info.iter().flat_map(|(closure_id, (captures, _))| { + let closure = db.lookup_intern_closure(InternedClosureId::from_intern_id(closure_id.0)); + let (_, source_map) = db.body_with_source_map(closure.0); + let closure_text_range = source_map + .expr_syntax(closure.1) + .expect("failed to map closure to SyntaxNode") + .value + .text_range(); + captures.iter().map(move |capture| { + fn text_range( + db: &TestDB, + syntax: InFileWrapper>, + ) -> TextRange { + let root = syntax.file_syntax(db); + syntax.value.to_node(&root).syntax().text_range() + } + + // FIXME: Deduplicate this with hir::Local::sources(). + let (body, source_map) = db.body_with_source_map(closure.0); + let local_text_range = match body.self_param.zip(source_map.self_param_syntax()) { + Some((param, source)) if param == capture.local() => { + format!("{:?}", text_range(db, source)) + } + _ => source_map + .patterns_for_binding(capture.local()) + .iter() + .map(|&definition| { + text_range(db, source_map.pat_syntax(definition).unwrap()) + }) + .map(|it| format!("{it:?}")) + .join(", "), + }; + let place = capture.display_place(closure.0, db); + let capture_ty = capture.ty.skip_binders().display_test(db).to_string(); + let spans = capture + .spans() + .iter() + .flat_map(|span| match *span { + MirSpan::ExprId(expr) => { + vec![text_range(db, source_map.expr_syntax(expr).unwrap())] + } + MirSpan::PatId(pat) => { + vec![text_range(db, source_map.pat_syntax(pat).unwrap())] + } + MirSpan::BindingId(binding) => source_map + .patterns_for_binding(binding) + .iter() + .map(|pat| text_range(db, source_map.pat_syntax(*pat).unwrap())) + .collect(), + MirSpan::SelfParam => { + vec![text_range(db, source_map.self_param_syntax().unwrap())] + } + MirSpan::Unknown => Vec::new(), + }) + .sorted_by_key(|it| it.start()) + .map(|it| format!("{it:?}")) + .join(","); + + (closure_text_range, local_text_range, spans, place, capture_ty, capture.kind()) + }) + })); + } + captures_info.sort_unstable_by_key(|(closure_text_range, local_text_range, ..)| { + (closure_text_range.start(), local_text_range.clone()) + }); + + let rendered = captures_info + .iter() + .map(|(closure_text_range, local_text_range, spans, place, capture_ty, capture_kind)| { + format!( + "{closure_text_range:?};{local_text_range};{spans} {capture_kind:?} {place} {capture_ty}" + ) + }) + .join("\n"); + + expect.assert_eq(&rendered); +} + +#[test] +fn deref_in_let() { + check_closure_captures( + r#" +//- minicore:copy +fn main() { + let a = &mut true; + let closure = || { let b = *a; }; +} +"#, + expect!["53..71;20..21;66..68 ByRef(Shared) *a &'? bool"], + ); +} + +#[test] +fn deref_then_ref_pattern() { + check_closure_captures( + r#" +//- minicore:copy +fn main() { + let a = &mut true; + let closure = || { let &mut ref b = a; }; +} +"#, + expect!["53..79;20..21;67..72 ByRef(Shared) *a &'? bool"], + ); + check_closure_captures( + r#" +//- minicore:copy +fn main() { + let a = &mut true; + let closure = || { let &mut ref mut b = a; }; +} +"#, + expect!["53..83;20..21;67..76 ByRef(Mut { kind: Default }) *a &'? mut bool"], + ); +} + +#[test] +fn unique_borrow() { + check_closure_captures( + r#" +//- minicore:copy +fn main() { + let a = &mut true; + let closure = || { *a = false; }; +} +"#, + expect!["53..71;20..21;58..60 ByRef(Mut { kind: Default }) *a &'? mut bool"], + ); +} + +#[test] +fn deref_ref_mut() { + check_closure_captures( + r#" +//- minicore:copy +fn main() { + let a = &mut true; + let closure = || { let ref mut b = *a; }; +} +"#, + expect!["53..79;20..21;62..71 ByRef(Mut { kind: Default }) *a &'? mut bool"], + ); +} + +#[test] +fn let_else_not_consuming() { + check_closure_captures( + r#" +//- minicore:copy +fn main() { + let a = &mut true; + let closure = || { let _ = *a else { return; }; }; +} +"#, + expect!["53..88;20..21;66..68 ByRef(Shared) *a &'? bool"], + ); +} + +#[test] +fn consume() { + check_closure_captures( + r#" +//- minicore:copy +struct NonCopy; +fn main() { + let a = NonCopy; + let closure = || { let b = a; }; +} +"#, + expect!["67..84;36..37;80..81 ByValue a NonCopy"], + ); +} + +#[test] +fn ref_to_upvar() { + check_closure_captures( + r#" +//- minicore:copy +struct NonCopy; +fn main() { + let mut a = NonCopy; + let closure = || { let b = &a; }; + let closure = || { let c = &mut a; }; +} +"#, + expect![[r#" + 71..89;36..41;84..86 ByRef(Shared) a &'? NonCopy + 109..131;36..41;122..128 ByRef(Mut { kind: Default }) a &'? mut NonCopy"#]], + ); +} + +#[test] +fn field() { + check_closure_captures( + r#" +//- minicore:copy +struct Foo { a: i32, b: i32 } +fn main() { + let a = Foo { a: 0, b: 0 }; + let closure = || { let b = a.a; }; +} +"#, + expect!["92..111;50..51;105..108 ByRef(Shared) a.a &'? i32"], + ); +} + +#[test] +fn fields_different_mode() { + check_closure_captures( + r#" +//- minicore:copy +struct NonCopy; +struct Foo { a: i32, b: i32, c: NonCopy, d: bool } +fn main() { + let mut a = Foo { a: 0, b: 0 }; + let closure = || { + let b = &a.a; + let c = &mut a.b; + let d = a.c; + }; +} +"#, + expect![[r#" + 133..212;87..92;154..158 ByRef(Shared) a.a &'? i32 + 133..212;87..92;176..184 ByRef(Mut { kind: Default }) a.b &'? mut i32 + 133..212;87..92;202..205 ByValue a.c NonCopy"#]], + ); +} + +#[test] +fn autoref() { + check_closure_captures( + r#" +//- minicore:copy +struct Foo; +impl Foo { + fn imm(&self) {} + fn mut_(&mut self) {} +} +fn main() { + let mut a = Foo; + let closure = || a.imm(); + let closure = || a.mut_(); +} +"#, + expect![[r#" + 123..133;92..97;126..127 ByRef(Shared) a &'? Foo + 153..164;92..97;156..157 ByRef(Mut { kind: Default }) a &'? mut Foo"#]], + ); +} + +#[test] +fn captures_priority() { + check_closure_captures( + r#" +//- minicore:copy +struct NonCopy; +fn main() { + let mut a = &mut true; + // Max ByRef(Mut { kind: Default }) + let closure = || { + *a = false; + let b = &mut a; + }; + // Max ByValue + let mut a = NonCopy; + let closure = || { + let b = a; + let c = &mut a; + let d = &a; + }; +} +"#, + expect![[r#" + 113..167;36..41;127..128,154..160 ByRef(Mut { kind: Default }) a &'? mut &'? mut bool + 231..304;196..201;252..253,276..277,296..297 ByValue a NonCopy"#]], + ); +} + +#[test] +fn let_underscore() { + check_closure_captures( + r#" +//- minicore:copy +fn main() { + let mut a = true; + let closure = || { let _ = a; }; +} +"#, + expect![""], + ); +} + +#[test] +fn match_wildcard() { + check_closure_captures( + r#" +//- minicore:copy +struct NonCopy; +fn main() { + let mut a = NonCopy; + let closure = || match a { + _ => {} + }; + let closure = || match a { + ref b => {} + }; + let closure = || match a { + ref mut b => {} + }; +} +"#, + expect![[r#" + 125..163;36..41;134..135 ByRef(Shared) a &'? NonCopy + 183..225;36..41;192..193 ByRef(Mut { kind: Default }) a &'? mut NonCopy"#]], + ); +} + +#[test] +fn multiple_bindings() { + check_closure_captures( + r#" +//- minicore:copy +fn main() { + let mut a = false; + let mut closure = || { let (b | b) = a; }; +} +"#, + expect!["57..80;20..25;76..77,76..77 ByRef(Shared) a &'? bool"], + ); +} + +#[test] +fn multiple_usages() { + check_closure_captures( + r#" +//- minicore:copy +fn main() { + let mut a = false; + let mut closure = || { + let b = &a; + let c = &a; + let d = &mut a; + a = true; + }; +} +"#, + expect!["57..149;20..25;78..80,98..100,118..124,134..135 ByRef(Mut { kind: Default }) a &'? mut bool"], + ); +} + +#[test] +fn ref_then_deref() { + check_closure_captures( + r#" +//- minicore:copy +fn main() { + let mut a = false; + let mut closure = || { let b = *&mut a; }; +} +"#, + expect!["57..80;20..25;71..77 ByRef(Mut { kind: Default }) a &'? mut bool"], + ); +} + +#[test] +fn ref_of_ref() { + check_closure_captures( + r#" +//- minicore:copy +fn main() { + let mut a = &false; + let closure = || { let b = &a; }; + let closure = || { let b = &mut a; }; + let a = &mut false; + let closure = || { let b = &a; }; + let closure = || { let b = &mut a; }; +} +"#, + expect![[r#" + 54..72;20..25;67..69 ByRef(Shared) a &'? &'? bool + 92..114;20..25;105..111 ByRef(Mut { kind: Default }) a &'? mut &'? bool + 158..176;124..125;171..173 ByRef(Shared) a &'? &'? mut bool + 196..218;124..125;209..215 ByRef(Mut { kind: Default }) a &'? mut &'? mut bool"#]], + ); +} + +#[test] +fn multiple_capture_usages() { + check_closure_captures( + r#" +//- minicore:copy +struct A { a: i32, b: bool } +fn main() { + let mut a = A { a: 123, b: false }; + let closure = |$0| { + let b = a.b; + a = A { a: 456, b: true }; + }; + closure(); +} +"#, + expect!["99..165;49..54;120..121,133..134 ByRef(Mut { kind: Default }) a &'? mut A"], + ); +} diff --git a/crates/hir-ty/src/tests/coercion.rs b/crates/hir-ty/src/tests/coercion.rs index 526db2af6d..908bbc248c 100644 --- a/crates/hir-ty/src/tests/coercion.rs +++ b/crates/hir-ty/src/tests/coercion.rs @@ -49,7 +49,7 @@ fn let_stmt_coerce() { //- minicore: coerce_unsized fn test() { let x: &[isize] = &[1]; - // ^^^^ adjustments: Deref(None), Borrow(Ref(Not)), Pointer(Unsize) + // ^^^^ adjustments: Deref(None), Borrow(Ref('?3, Not)), Pointer(Unsize) let x: *const [isize] = &[1]; // ^^^^ adjustments: Deref(None), Borrow(RawPtr(Not)), Pointer(Unsize) } @@ -96,7 +96,7 @@ fn foo(x: &[T]) -> &[T] { x } fn test() { let x = if true { foo(&[1]) - // ^^^^ adjustments: Deref(None), Borrow(Ref(Not)), Pointer(Unsize) + // ^^^^ adjustments: Deref(None), Borrow(Ref('?8, Not)), Pointer(Unsize) } else { &[1] }; @@ -148,7 +148,7 @@ fn foo(x: &[T]) -> &[T] { x } fn test(i: i32) { let x = match i { 2 => foo(&[2]), - // ^^^^ adjustments: Deref(None), Borrow(Ref(Not)), Pointer(Unsize) + // ^^^^ adjustments: Deref(None), Borrow(Ref('?10, Not)), Pointer(Unsize) 1 => &[1], _ => &[3], }; @@ -267,7 +267,7 @@ fn takes_ref_str(x: &str) {} fn returns_string() -> String { loop {} } fn test() { takes_ref_str(&{ returns_string() }); - // ^^^^^^^^^^^^^^^^^^^^^ adjustments: Deref(None), Deref(Some(OverloadedDeref(Some(Not)))), Borrow(Ref(Not)) + // ^^^^^^^^^^^^^^^^^^^^^ adjustments: Deref(None), Deref(Some(OverloadedDeref(Some(Not)))), Borrow(Ref('{error}, Not)) } "#, ); @@ -849,8 +849,8 @@ impl core::cmp::PartialEq for Struct { } fn test() { Struct == Struct; - // ^^^^^^ adjustments: Borrow(Ref(Not)) - // ^^^^^^ adjustments: Borrow(Ref(Not)) + // ^^^^^^ adjustments: Borrow(Ref('{error}, Not)) + // ^^^^^^ adjustments: Borrow(Ref('{error}, Not)) }", ); } @@ -866,7 +866,7 @@ impl core::ops::AddAssign for Struct { } fn test() { Struct += Struct; - // ^^^^^^ adjustments: Borrow(Ref(Mut)) + // ^^^^^^ adjustments: Borrow(Ref('{error}, Mut)) // ^^^^^^ adjustments: }", ); @@ -880,7 +880,7 @@ fn adjust_index() { fn test() { let x = [1, 2, 3]; x[2] = 6; - // ^ adjustments: Borrow(Ref(Mut)) + // ^ adjustments: Borrow(Ref('?8, Mut)) } ", ); @@ -905,11 +905,11 @@ impl core::ops::IndexMut for StructMut { } fn test() { Struct[0]; - // ^^^^^^ adjustments: Borrow(Ref(Not)) + // ^^^^^^ adjustments: Borrow(Ref('?2, Not)) StructMut[0]; - // ^^^^^^^^^ adjustments: Borrow(Ref(Not)) + // ^^^^^^^^^ adjustments: Borrow(Ref('?5, Not)) &mut StructMut[0]; - // ^^^^^^^^^ adjustments: Borrow(Ref(Mut)) + // ^^^^^^^^^ adjustments: Borrow(Ref('?8, Mut)) }", ); } diff --git a/crates/hir-ty/src/tests/method_resolution.rs b/crates/hir-ty/src/tests/method_resolution.rs index 14e2e74653..610fc9b6b4 100644 --- a/crates/hir-ty/src/tests/method_resolution.rs +++ b/crates/hir-ty/src/tests/method_resolution.rs @@ -1186,11 +1186,11 @@ fn test() { 89..109 '{ ... }': bool 99..103 'true': bool 123..167 '{ ...o(); }': () - 133..134 's': &'? S + 133..134 's': &'static S 137..151 'unsafe { f() }': &'static S 146..147 'f': fn f() -> &'static S 146..149 'f()': &'static S - 157..158 's': &'? S + 157..158 's': &'static S 157..164 's.foo()': bool "#]], ); @@ -1847,9 +1847,9 @@ impl Foo { } fn test() { Foo.foo(); - //^^^ adjustments: Borrow(Ref(Not)) + //^^^ adjustments: Borrow(Ref('?1, Not)) (&Foo).foo(); - // ^^^^ adjustments: Deref(None), Borrow(Ref(Not)) + // ^^^^ adjustments: Deref(None), Borrow(Ref('?3, Not)) } "#, ); @@ -1863,7 +1863,7 @@ fn receiver_adjustment_unsize_array() { fn test() { let a = [1, 2, 3]; a.len(); -} //^ adjustments: Borrow(Ref(Not)), Pointer(Unsize) +} //^ adjustments: Borrow(Ref('?7, Not)), Pointer(Unsize) "#, ); } @@ -2076,7 +2076,7 @@ impl Foo { } fn test() { Box::new(Foo).foo(); - //^^^^^^^^^^^^^ adjustments: Deref(None), Borrow(Ref(Not)) + //^^^^^^^^^^^^^ adjustments: Deref(None), Borrow(Ref('?3, Not)) } "#, ); @@ -2094,7 +2094,7 @@ impl Foo { use core::mem::ManuallyDrop; fn test() { ManuallyDrop::new(Foo).foo(); - //^^^^^^^^^^^^^^^^^^^^^^ adjustments: Deref(Some(OverloadedDeref(Some(Not)))), Borrow(Ref(Not)) + //^^^^^^^^^^^^^^^^^^^^^^ adjustments: Deref(Some(OverloadedDeref(Some(Not)))), Borrow(Ref('?4, Not)) } "#, ); diff --git a/crates/hir-ty/src/tests/regression.rs b/crates/hir-ty/src/tests/regression.rs index 2819838612..17fbe4db31 100644 --- a/crates/hir-ty/src/tests/regression.rs +++ b/crates/hir-ty/src/tests/regression.rs @@ -2141,3 +2141,90 @@ fn test() { }"#, ); } + +#[test] +fn issue_17866() { + check_infer( + r#" +trait T { + type A; +} + +type Foo = ::A; + +fn main() { + Foo {}; +} +"#, + expect![[r#" + 60..75 '{ Foo {}; }': () + 66..72 'Foo {}': {unknown} + "#]], + ); +} + +#[test] +fn issue_17711() { + check_infer( + r#" +//- minicore: deref +use core::ops::Deref; + +struct Struct<'a, T>(&'a T); + +trait Trait {} + +impl<'a, T: Deref> Struct<'a, T> { + fn foo(&self) -> &Self { self } + + fn bar(&self) { + let _ = self.foo(); + } + +} +"#, + expect![[r#" + 137..141 'self': &'? Struct<'a, T> + 152..160 '{ self }': &'? Struct<'a, T> + 154..158 'self': &'? Struct<'a, T> + 174..178 'self': &'? Struct<'a, T> + 180..215 '{ ... }': () + 194..195 '_': &'? Struct<'?, T> + 198..202 'self': &'? Struct<'a, T> + 198..208 'self.foo()': &'? Struct<'?, T> + "#]], + ); +} + +#[test] +fn issue_17767() { + check_infer( + r#" +extern "C" { + type Foo; +} + +fn f() -> Foo {} +"#, + expect![[r#" + 47..49 '{}': Foo + "#]], + ); +} + +#[test] +fn issue_17921() { + check_infer( + r#" +//- minicore: future +trait Foo {} +type Bar = impl Foo; + +async fn f() -> Bar {} +"#, + expect![[r#" + 64..66 '{}': () + 64..66 '{}': impl Future + "#]], + ); +} diff --git a/crates/hir-ty/src/tests/simple.rs b/crates/hir-ty/src/tests/simple.rs index d83a34298e..1c6fa62e30 100644 --- a/crates/hir-ty/src/tests/simple.rs +++ b/crates/hir-ty/src/tests/simple.rs @@ -1201,8 +1201,8 @@ fn infer_array() { 209..215 '[1, 2]': [i32; 2] 210..211 '1': i32 213..214 '2': i32 - 225..226 'i': [&'? str; 2] - 229..239 '["a", "b"]': [&'? str; 2] + 225..226 'i': [&'static str; 2] + 229..239 '["a", "b"]': [&'static str; 2] 230..233 '"a"': &'static str 235..238 '"b"': &'static str 250..251 'b': [[&'? str; 1]; 2] @@ -3686,3 +3686,36 @@ fn main() { "#, ); } + +#[test] +fn infer_bad_lang_item() { + check_infer( + r#" +#[lang="eq"] +pub trait Eq { + fn eq(&self, ) -> bool; + +} + +#[lang="shr"] +pub trait Shr { + fn shr(&self, rhs: &RHS) -> Result; +} + +fn test() -> bool { + 1 >> 1; + 1 == 1; +} +"#, + expect![[r#" + 39..43 'self': &'? Self + 114..118 'self': &'? Self + 120..123 'rhs': &'? RHS + 163..190 '{ ...= 1; }': bool + 169..170 '1': i32 + 169..175 '1 >> 1': {unknown} + 181..182 '1': i32 + 181..187 '1 == 1': {unknown} + "#]], + ); +} diff --git a/crates/hir-ty/src/tls.rs b/crates/hir-ty/src/tls.rs index db5fa32057..6cb59491fa 100644 --- a/crates/hir-ty/src/tls.rs +++ b/crates/hir-ty/src/tls.rs @@ -2,6 +2,7 @@ use std::fmt::{self, Display}; use itertools::Itertools; +use span::Edition; use crate::{ chalk_db, db::HirDatabase, from_assoc_type_id, from_chalk_trait_id, mapping::from_chalk, @@ -24,7 +25,7 @@ impl DebugContext<'_> { AdtId::UnionId(it) => self.0.union_data(it).name.clone(), AdtId::EnumId(it) => self.0.enum_data(it).name.clone(), }; - name.display(self.0.upcast()).fmt(f)?; + name.display(self.0.upcast(), Edition::LATEST).fmt(f)?; Ok(()) } @@ -35,7 +36,7 @@ impl DebugContext<'_> { ) -> Result<(), fmt::Error> { let trait_: hir_def::TraitId = from_chalk_trait_id(id); let trait_data = self.0.trait_data(trait_); - trait_data.name.display(self.0.upcast()).fmt(f)?; + trait_data.name.display(self.0.upcast(), Edition::LATEST).fmt(f)?; Ok(()) } @@ -54,8 +55,8 @@ impl DebugContext<'_> { write!( fmt, "{}::{}", - trait_data.name.display(self.0.upcast()), - type_alias_data.name.display(self.0.upcast()) + trait_data.name.display(self.0.upcast(), Edition::LATEST), + type_alias_data.name.display(self.0.upcast(), Edition::LATEST) )?; Ok(()) } @@ -75,7 +76,7 @@ impl DebugContext<'_> { let trait_ref = projection_ty.trait_ref(self.0); let trait_params = trait_ref.substitution.as_slice(Interner); let self_ty = trait_ref.self_type_parameter(Interner); - write!(fmt, "<{self_ty:?} as {}", trait_name.display(self.0.upcast()))?; + write!(fmt, "<{self_ty:?} as {}", trait_name.display(self.0.upcast(), Edition::LATEST))?; if trait_params.len() > 1 { write!( fmt, @@ -83,7 +84,7 @@ impl DebugContext<'_> { trait_params[1..].iter().format_with(", ", |x, f| f(&format_args!("{x:?}"))), )?; } - write!(fmt, ">::{}", type_alias_data.name.display(self.0.upcast()))?; + write!(fmt, ">::{}", type_alias_data.name.display(self.0.upcast(), Edition::LATEST))?; let proj_params_count = projection_ty.substitution.len(Interner) - trait_params.len(); let proj_params = &projection_ty.substitution.as_slice(Interner)[..proj_params_count]; @@ -110,9 +111,11 @@ impl DebugContext<'_> { CallableDefId::EnumVariantId(e) => self.0.enum_variant_data(e).name.clone(), }; match def { - CallableDefId::FunctionId(_) => write!(fmt, "{{fn {}}}", name.display(self.0.upcast())), + CallableDefId::FunctionId(_) => { + write!(fmt, "{{fn {}}}", name.display(self.0.upcast(), Edition::LATEST)) + } CallableDefId::StructId(_) | CallableDefId::EnumVariantId(_) => { - write!(fmt, "{{ctor {}}}", name.display(self.0.upcast())) + write!(fmt, "{{ctor {}}}", name.display(self.0.upcast(), Edition::LATEST)) } } } diff --git a/crates/hir-ty/src/traits.rs b/crates/hir-ty/src/traits.rs index c46382a0ea..51ccd4ef29 100644 --- a/crates/hir-ty/src/traits.rs +++ b/crates/hir-ty/src/traits.rs @@ -14,13 +14,14 @@ use hir_def::{ }; use hir_expand::name::Name; use intern::sym; -use stdx::panic_context; +use span::Edition; +use stdx::{never, panic_context}; use triomphe::Arc; use crate::{ db::HirDatabase, infer::unify::InferenceTable, utils::UnevaluatedConstEvaluatorFolder, AliasEq, AliasTy, Canonical, DomainGoal, Goal, Guidance, InEnvironment, Interner, ProjectionTy, - ProjectionTyExt, Solution, TraitRefExt, Ty, TyKind, WhereClause, + ProjectionTyExt, Solution, TraitRefExt, Ty, TyKind, TypeFlags, WhereClause, }; /// This controls how much 'time' we give the Chalk solver before giving up. @@ -90,6 +91,16 @@ pub(crate) fn normalize_projection_query( projection: ProjectionTy, env: Arc, ) -> Ty { + if projection.substitution.iter(Interner).any(|arg| { + arg.ty(Interner) + .is_some_and(|ty| ty.data(Interner).flags.intersects(TypeFlags::HAS_TY_INFER)) + }) { + never!( + "Invoking `normalize_projection_query` with a projection type containing inference var" + ); + return TyKind::Error.intern(Interner); + } + let mut table = InferenceTable::new(db, env); let ty = table.normalize_projection_ty(projection); table.resolve_completely(ty) @@ -104,7 +115,7 @@ pub(crate) fn trait_solve_query( ) -> Option { let detail = match &goal.value.goal.data(Interner) { GoalData::DomainGoal(DomainGoal::Holds(WhereClause::Implemented(it))) => { - db.trait_data(it.hir_trait_id()).name.display(db.upcast()).to_string() + db.trait_data(it.hir_trait_id()).name.display(db.upcast(), Edition::LATEST).to_string() } GoalData::DomainGoal(DomainGoal::Holds(WhereClause::AliasEq(_))) => "alias_eq".to_owned(), _ => "??".to_owned(), diff --git a/crates/hir/Cargo.toml b/crates/hir/Cargo.toml index 324fa1c6a8..26666d6feb 100644 --- a/crates/hir/Cargo.toml +++ b/crates/hir/Cargo.toml @@ -20,7 +20,6 @@ itertools.workspace = true smallvec.workspace = true tracing.workspace = true triomphe.workspace = true -once_cell = "1.17.1" # local deps base-db.workspace = true diff --git a/crates/hir/src/attrs.rs b/crates/hir/src/attrs.rs index 02d92620e0..af60c233e5 100644 --- a/crates/hir/src/attrs.rs +++ b/crates/hir/src/attrs.rs @@ -328,11 +328,9 @@ fn doc_modpath_from_str(link: &str) -> Option { }; let parts = first_segment.into_iter().chain(parts).map(|segment| match segment.parse() { Ok(idx) => Name::new_tuple_field(idx), - Err(_) => Name::new( - segment.split_once('<').map_or(segment, |it| it.0), - tt::IdentIsRaw::No, - SyntaxContextId::ROOT, - ), + Err(_) => { + Name::new(segment.split_once('<').map_or(segment, |it| it.0), SyntaxContextId::ROOT) + } }); Some(ModPath::from_segments(kind, parts)) }; diff --git a/crates/hir/src/display.rs b/crates/hir/src/display.rs index 12dd8b5bf4..923dca6466 100644 --- a/crates/hir/src/display.rs +++ b/crates/hir/src/display.rs @@ -84,7 +84,7 @@ impl HirDisplay for Function { if let Some(abi) = &data.abi { write!(f, "extern \"{}\" ", abi.as_str())?; } - write!(f, "fn {}", data.name.display(f.db.upcast()))?; + write!(f, "fn {}", data.name.display(f.db.upcast(), f.edition()))?; write_generic_params(GenericDefId::FunctionId(self.id), f)?; @@ -107,7 +107,7 @@ impl HirDisplay for Function { first = false; } match local { - Some(name) => write!(f, "{}: ", name.display(f.db.upcast()))?, + Some(name) => write!(f, "{}: ", name.display(f.db.upcast(), f.edition()))?, None => f.write_str("_: ")?, } type_ref.hir_fmt(f)?; @@ -177,7 +177,7 @@ fn write_impl_header(impl_: &Impl, f: &mut HirFormatter<'_>) -> Result<(), HirDi if let Some(trait_) = impl_.trait_(db) { let trait_data = db.trait_data(trait_.id); - write!(f, " {} for", trait_data.name.display(db.upcast()))?; + write!(f, " {} for", trait_data.name.display(db.upcast(), f.edition()))?; } f.write_char(' ')?; @@ -196,7 +196,7 @@ impl HirDisplay for SelfParam { { f.write_char('&')?; if let Some(lifetime) = lifetime { - write!(f, "{} ", lifetime.name.display(f.db.upcast()))?; + write!(f, "{} ", lifetime.name.display(f.db.upcast(), f.edition()))?; } if let hir_def::type_ref::Mutability::Mut = mut_ { f.write_str("mut ")?; @@ -227,7 +227,7 @@ impl HirDisplay for Struct { // FIXME: Render repr if its set explicitly? write_visibility(module_id, self.visibility(f.db), f)?; f.write_str("struct ")?; - write!(f, "{}", self.name(f.db).display(f.db.upcast()))?; + write!(f, "{}", self.name(f.db).display(f.db.upcast(), f.edition()))?; let def_id = GenericDefId::AdtId(AdtId::StructId(self.id)); write_generic_params(def_id, f)?; @@ -266,7 +266,7 @@ impl HirDisplay for Enum { fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { write_visibility(self.module(f.db).id, self.visibility(f.db), f)?; f.write_str("enum ")?; - write!(f, "{}", self.name(f.db).display(f.db.upcast()))?; + write!(f, "{}", self.name(f.db).display(f.db.upcast(), f.edition()))?; let def_id = GenericDefId::AdtId(AdtId::EnumId(self.id)); write_generic_params(def_id, f)?; @@ -283,7 +283,7 @@ impl HirDisplay for Union { fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { write_visibility(self.module(f.db).id, self.visibility(f.db), f)?; f.write_str("union ")?; - write!(f, "{}", self.name(f.db).display(f.db.upcast()))?; + write!(f, "{}", self.name(f.db).display(f.db.upcast(), f.edition()))?; let def_id = GenericDefId::AdtId(AdtId::UnionId(self.id)); write_generic_params(def_id, f)?; @@ -343,7 +343,7 @@ fn write_variants( } else { f.write_str("{\n")?; for variant in &variants[..count] { - write!(f, " {}", variant.name(f.db).display(f.db.upcast()))?; + write!(f, " {}", variant.name(f.db).display(f.db.upcast(), f.edition()))?; match variant.kind(f.db) { StructKind::Tuple => { let fields_str = @@ -372,21 +372,21 @@ fn write_variants( impl HirDisplay for Field { fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { write_visibility(self.parent.module(f.db).id, self.visibility(f.db), f)?; - write!(f, "{}: ", self.name(f.db).display(f.db.upcast()))?; + write!(f, "{}: ", self.name(f.db).display(f.db.upcast(), f.edition()))?; self.ty(f.db).hir_fmt(f) } } impl HirDisplay for TupleField { fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { - write!(f, "pub {}: ", self.name().display(f.db.upcast()))?; + write!(f, "pub {}: ", self.name().display(f.db.upcast(), f.edition()))?; self.ty(f.db).hir_fmt(f) } } impl HirDisplay for Variant { fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { - write!(f, "{}", self.name(f.db).display(f.db.upcast()))?; + write!(f, "{}", self.name(f.db).display(f.db.upcast(), f.edition()))?; let data = self.variant_data(f.db); match &*data { VariantData::Unit => {} @@ -424,9 +424,9 @@ 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()))?; + write!(f, "{}", self.name(f.db).display(f.db.upcast(), f.edition()))?; if let Some(alias) = self.alias(f.db) { - write!(f, " as {alias}",)?; + write!(f, " as {}", alias.display(f.edition()))?; } Ok(()) } @@ -478,7 +478,7 @@ impl HirDisplay for TypeParam { match param_data { TypeOrConstParamData::TypeParamData(p) => match p.provenance { TypeParamProvenance::TypeParamList | TypeParamProvenance::TraitSelf => { - write!(f, "{}", p.name.clone().unwrap().display(f.db.upcast()))? + write!(f, "{}", p.name.clone().unwrap().display(f.db.upcast(), f.edition()))? } TypeParamProvenance::ArgumentImplTrait => { return write_bounds_like_dyn_trait_with_prefix( @@ -491,7 +491,7 @@ impl HirDisplay for TypeParam { } }, TypeOrConstParamData::ConstParamData(p) => { - write!(f, "{}", p.name.display(f.db.upcast()))?; + write!(f, "{}", p.name.display(f.db.upcast(), f.edition()))?; } } @@ -525,13 +525,13 @@ impl HirDisplay for TypeParam { impl HirDisplay for LifetimeParam { fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { - write!(f, "{}", self.name(f.db).display(f.db.upcast())) + write!(f, "{}", self.name(f.db).display(f.db.upcast(), f.edition())) } } impl HirDisplay for ConstParam { fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { - write!(f, "const {}: ", self.name(f.db).display(f.db.upcast()))?; + write!(f, "const {}: ", self.name(f.db).display(f.db.upcast(), f.edition()))?; self.ty(f.db).hir_fmt(f) } } @@ -563,7 +563,7 @@ fn write_generic_params( }; for (_, lifetime) in params.iter_lt() { delim(f)?; - write!(f, "{}", lifetime.name.display(f.db.upcast()))?; + write!(f, "{}", lifetime.name.display(f.db.upcast(), f.edition()))?; } for (_, ty) in params.iter_type_or_consts() { if let Some(name) = &ty.name() { @@ -573,7 +573,7 @@ fn write_generic_params( continue; } delim(f)?; - write!(f, "{}", name.display(f.db.upcast()))?; + write!(f, "{}", name.display(f.db.upcast(), f.edition()))?; if let Some(default) = &ty.default { f.write_str(" = ")?; default.hir_fmt(f)?; @@ -581,12 +581,12 @@ fn write_generic_params( } TypeOrConstParamData::ConstParamData(c) => { delim(f)?; - write!(f, "const {}: ", name.display(f.db.upcast()))?; + write!(f, "const {}: ", name.display(f.db.upcast(), f.edition()))?; c.ty.hir_fmt(f)?; if let Some(default) = &c.default { f.write_str(" = ")?; - write!(f, "{}", default.display(f.db.upcast()))?; + write!(f, "{}", default.display(f.db.upcast(), f.edition()))?; } } } @@ -639,7 +639,7 @@ fn write_where_predicates( let write_target = |target: &WherePredicateTypeTarget, f: &mut HirFormatter<'_>| match target { WherePredicateTypeTarget::TypeRef(ty) => ty.hir_fmt(f), WherePredicateTypeTarget::TypeOrConstParam(id) => match params[*id].name() { - Some(name) => write!(f, "{}", name.display(f.db.upcast())), + Some(name) => write!(f, "{}", name.display(f.db.upcast(), f.edition())), None => f.write_str("{unnamed}"), }, }; @@ -668,12 +668,13 @@ fn write_where_predicates( bound.hir_fmt(f)?; } Lifetime { target, bound } => { - let target = target.name.display(f.db.upcast()); - let bound = bound.name.display(f.db.upcast()); + let target = target.name.display(f.db.upcast(), f.edition()); + let bound = bound.name.display(f.db.upcast(), f.edition()); write!(f, "{target}: {bound}")?; } ForLifetime { lifetimes, target, bound } => { - let lifetimes = lifetimes.iter().map(|it| it.display(f.db.upcast())).join(", "); + let lifetimes = + lifetimes.iter().map(|it| it.display(f.db.upcast(), f.edition())).join(", "); write!(f, "for<{lifetimes}> ")?; write_target(target, f)?; f.write_str(": ")?; @@ -685,7 +686,9 @@ fn write_where_predicates( f.write_str(" + ")?; match nxt { TypeBound { bound, .. } | ForLifetime { bound, .. } => bound.hir_fmt(f)?, - Lifetime { bound, .. } => write!(f, "{}", bound.name.display(f.db.upcast()))?, + Lifetime { bound, .. } => { + write!(f, "{}", bound.name.display(f.db.upcast(), f.edition()))? + } } } f.write_str(",")?; @@ -707,7 +710,7 @@ impl HirDisplay for Const { let data = db.const_data(self.id); f.write_str("const ")?; match &data.name { - Some(name) => write!(f, "{}: ", name.display(f.db.upcast()))?, + Some(name) => write!(f, "{}: ", name.display(f.db.upcast(), f.edition()))?, None => f.write_str("_: ")?, } data.type_ref.hir_fmt(f)?; @@ -723,7 +726,7 @@ impl HirDisplay for Static { if data.mutable { f.write_str("mut ")?; } - write!(f, "{}: ", data.name.display(f.db.upcast()))?; + write!(f, "{}: ", data.name.display(f.db.upcast(), f.edition()))?; data.type_ref.hir_fmt(f)?; Ok(()) } @@ -777,7 +780,7 @@ fn write_trait_header(trait_: &Trait, f: &mut HirFormatter<'_>) -> Result<(), Hi if data.is_auto { f.write_str("auto ")?; } - write!(f, "trait {}", data.name.display(f.db.upcast()))?; + write!(f, "trait {}", data.name.display(f.db.upcast(), f.edition()))?; write_generic_params(GenericDefId::TraitId(trait_.id), f)?; Ok(()) } @@ -786,7 +789,7 @@ impl HirDisplay for TraitAlias { fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { write_visibility(self.module(f.db).id, self.visibility(f.db), f)?; let data = f.db.trait_alias_data(self.id); - write!(f, "trait {}", data.name.display(f.db.upcast()))?; + write!(f, "trait {}", data.name.display(f.db.upcast(), f.edition()))?; let def_id = GenericDefId::TraitAliasId(self.id); write_generic_params(def_id, f)?; f.write_str(" = ")?; @@ -802,7 +805,7 @@ impl HirDisplay for TypeAlias { fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { write_visibility(self.module(f.db).id, self.visibility(f.db), f)?; let data = f.db.type_alias_data(self.id); - write!(f, "type {}", data.name.display(f.db.upcast()))?; + write!(f, "type {}", data.name.display(f.db.upcast(), f.edition()))?; let def_id = GenericDefId::TypeAliasId(self.id); write_generic_params(def_id, f)?; if !data.bounds.is_empty() { @@ -822,7 +825,7 @@ impl HirDisplay for Module { fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { // FIXME: Module doesn't have visibility saved in data. match self.name(f.db) { - Some(name) => write!(f, "mod {}", name.display(f.db.upcast())), + Some(name) => write!(f, "mod {}", name.display(f.db.upcast(), f.edition())), None if self.is_crate_root() => match self.krate(f.db).display_name(f.db) { Some(name) => write!(f, "extern crate {name}"), None => f.write_str("extern crate {unknown}"), @@ -839,6 +842,6 @@ impl HirDisplay for Macro { hir_def::MacroId::MacroRulesId(_) => f.write_str("macro_rules!"), hir_def::MacroId::ProcMacroId(_) => f.write_str("proc_macro"), }?; - write!(f, " {}", self.name(f.db).display(f.db.upcast())) + write!(f, " {}", self.name(f.db).display(f.db.upcast(), f.edition())) } } diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index 1a3becdf50..58340c74fe 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -78,7 +78,8 @@ use hir_ty::{ use itertools::Itertools; use nameres::diagnostics::DefDiagnosticKind; use rustc_hash::FxHashSet; -use span::{Edition, EditionedFileId, FileId, MacroCallId}; +use smallvec::SmallVec; +use span::{Edition, EditionedFileId, FileId, MacroCallId, SyntaxContextId}; use stdx::{impl_from, never}; use syntax::{ ast::{self, HasAttrs as _, HasGenericParams, HasName}, @@ -93,8 +94,7 @@ pub use crate::{ diagnostics::*, has_source::HasSource, semantics::{ - DescendPreference, PathResolution, Semantics, SemanticsImpl, SemanticsScope, TypeInfo, - VisibleTraits, + PathResolution, Semantics, SemanticsImpl, SemanticsScope, TypeInfo, VisibleTraits, }, }; pub use hir_ty::method_resolution::TyFingerprint; @@ -340,13 +340,13 @@ impl ModuleDef { } } - pub fn canonical_path(&self, db: &dyn HirDatabase) -> Option { + pub fn canonical_path(&self, db: &dyn HirDatabase, edition: Edition) -> Option { let mut segments = vec![self.name(db)?]; for m in self.module(db)?.path_to_root(db) { segments.extend(m.name(db)) } segments.reverse(); - Some(segments.iter().map(|it| it.display(db.upcast())).join("::")) + Some(segments.iter().map(|it| it.display(db.upcast(), edition)).join("::")) } pub fn canonical_module_path( @@ -556,13 +556,14 @@ impl Module { style_lints: bool, ) { let _p = tracing::info_span!("Module::diagnostics", name = ?self.name(db)).entered(); + let edition = db.crate_graph()[self.id.krate()].edition; let def_map = self.id.def_map(db.upcast()); for diag in def_map.diagnostics() { if diag.in_module != self.id.local_id { // FIXME: This is accidentally quadratic. continue; } - emit_def_diagnostic(db, acc, diag); + emit_def_diagnostic(db, acc, diag, edition); } if !self.id.is_block_module() { @@ -582,7 +583,7 @@ impl Module { } ModuleDef::Trait(t) => { for diag in db.trait_data_with_diagnostics(t.id).1.iter() { - emit_def_diagnostic(db, acc, diag); + emit_def_diagnostic(db, acc, diag, edition); } for item in t.items(db) { @@ -599,19 +600,19 @@ impl Module { match adt { Adt::Struct(s) => { for diag in db.struct_data_with_diagnostics(s.id).1.iter() { - emit_def_diagnostic(db, acc, diag); + emit_def_diagnostic(db, acc, diag, edition); } } Adt::Union(u) => { for diag in db.union_data_with_diagnostics(u.id).1.iter() { - emit_def_diagnostic(db, acc, diag); + emit_def_diagnostic(db, acc, diag, edition); } } Adt::Enum(e) => { for v in e.variants(db) { acc.extend(ModuleDef::Variant(v).diagnostics(db, style_lints)); for diag in db.enum_variant_data_with_diagnostics(v.id).1.iter() { - emit_def_diagnostic(db, acc, diag); + emit_def_diagnostic(db, acc, diag, edition); } } } @@ -645,7 +646,7 @@ impl Module { let ast_id_map = db.ast_id_map(file_id); for diag in db.impl_data_with_diagnostics(impl_def.id).1.iter() { - emit_def_diagnostic(db, acc, diag); + emit_def_diagnostic(db, acc, diag, edition); } if inherent_impls.invalid_impls().contains(&impl_def.id) { @@ -869,23 +870,32 @@ fn emit_macro_def_diagnostics(db: &dyn HirDatabase, acc: &mut Vec never!("declarative expander for non decl-macro: {:?}", e); return; }; + let krate = HasModule::krate(&m.id, db.upcast()); + let edition = db.crate_graph()[krate].edition; emit_def_diagnostic_( db, acc, &DefDiagnosticKind::MacroDefError { ast, message: e.to_string() }, + edition, ); } } } -fn emit_def_diagnostic(db: &dyn HirDatabase, acc: &mut Vec, diag: &DefDiagnostic) { - emit_def_diagnostic_(db, acc, &diag.kind) +fn emit_def_diagnostic( + db: &dyn HirDatabase, + acc: &mut Vec, + diag: &DefDiagnostic, + edition: Edition, +) { + emit_def_diagnostic_(db, acc, &diag.kind, edition) } fn emit_def_diagnostic_( db: &dyn HirDatabase, acc: &mut Vec, diag: &DefDiagnosticKind, + edition: Edition, ) { match diag { DefDiagnosticKind::UnresolvedModule { ast: declaration, candidates } => { @@ -910,7 +920,7 @@ fn emit_def_diagnostic_( MacroError { node: InFile::new(ast.file_id, item.syntax_node_ptr()), precise_location: None, - message: format!("{}: {message}", path.display(db.upcast())), + message: format!("{}: {message}", path.display(db.upcast(), edition)), error, } .into(), @@ -1764,7 +1774,7 @@ impl DefWithBody { /// A textual representation of the HIR of this def's body for debugging purposes. pub fn debug_hir(self, db: &dyn HirDatabase) -> String { let body = db.body(self.id()); - body.pretty_print(db.upcast(), self.id()) + body.pretty_print(db.upcast(), self.id(), Edition::CURRENT) } /// A textual representation of the MIR of this def's body for debugging purposes. @@ -2259,6 +2269,8 @@ impl Function { db: &dyn HirDatabase, span_formatter: impl Fn(FileId, TextRange) -> String, ) -> String { + let krate = HasModule::krate(&self.id, db.upcast()); + let edition = db.crate_graph()[krate].edition; let body = match db.monomorphized_mir_body( self.id.into(), Substitution::empty(Interner), @@ -2267,7 +2279,7 @@ impl Function { Ok(body) => body, Err(e) => { let mut r = String::new(); - _ = e.pretty_print(&mut r, db, &span_formatter); + _ = e.pretty_print(&mut r, db, &span_formatter, edition); return r; } }; @@ -2276,7 +2288,7 @@ impl Function { Ok(_) => "pass".to_owned(), Err(e) => { let mut r = String::new(); - _ = e.pretty_print(&mut r, db, &span_formatter); + _ = e.pretty_print(&mut r, db, &span_formatter, edition); r } }; @@ -2510,7 +2522,11 @@ impl Const { Type::from_value_def(db, self.id) } - pub fn render_eval(self, db: &dyn HirDatabase) -> Result { + pub fn render_eval( + self, + db: &dyn HirDatabase, + edition: Edition, + ) -> Result { 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) { @@ -2532,7 +2548,7 @@ impl Const { if let Ok(s) = mir::render_const_using_debug_impl(db, self.id, &c) { Ok(s) } else { - Ok(format!("{}", c.display(db))) + Ok(format!("{}", c.display(db, edition))) } } } @@ -3728,9 +3744,9 @@ impl ConstParam { Type::new(db, self.id.parent(), db.const_param_ty(self.id)) } - pub fn default(self, db: &dyn HirDatabase) -> Option { + pub fn default(self, db: &dyn HirDatabase, edition: Edition) -> Option { let arg = generic_arg_from_param(db, self.id.into())?; - known_const_to_ast(arg.constant(Interner)?, db) + known_const_to_ast(arg.constant(Interner)?, db, edition) } } @@ -4038,12 +4054,20 @@ impl Closure { TyKind::Closure(self.id, self.subst).intern(Interner) } - pub fn display_with_id(&self, db: &dyn HirDatabase) -> String { - self.clone().as_ty().display(db).with_closure_style(ClosureStyle::ClosureWithId).to_string() + pub fn display_with_id(&self, db: &dyn HirDatabase, edition: Edition) -> String { + self.clone() + .as_ty() + .display(db, edition) + .with_closure_style(ClosureStyle::ClosureWithId) + .to_string() } - pub fn display_with_impl(&self, db: &dyn HirDatabase) -> String { - self.clone().as_ty().display(db).with_closure_style(ClosureStyle::ImplFn).to_string() + pub fn display_with_impl(&self, db: &dyn HirDatabase, edition: Edition) -> String { + self.clone() + .as_ty() + .display(db, edition) + .with_closure_style(ClosureStyle::ImplFn) + .to_string() } pub fn captured_items(&self, db: &dyn HirDatabase) -> Vec { @@ -4090,6 +4114,15 @@ impl ClosureCapture { Local { parent: self.owner, binding_id: self.capture.local() } } + /// Returns whether this place has any field (aka. non-deref) projections. + pub fn has_field_projections(&self) -> bool { + self.capture.has_field_projections() + } + + pub fn usages(&self) -> CaptureUsages { + CaptureUsages { parent: self.owner, spans: self.capture.spans() } + } + pub fn kind(&self) -> CaptureKind { match self.capture.kind() { hir_ty::CaptureKind::ByRef( @@ -4105,11 +4138,21 @@ impl ClosureCapture { } } + /// Converts the place to a name that can be inserted into source code. + pub fn place_to_name(&self, db: &dyn HirDatabase) -> String { + self.capture.place_to_name(self.owner, db) + } + + pub fn display_place_source_code(&self, db: &dyn HirDatabase) -> String { + self.capture.display_place_source_code(self.owner, db) + } + pub fn display_place(&self, db: &dyn HirDatabase) -> String { self.capture.display_place(self.owner, db) } } +#[derive(Clone, Copy, PartialEq, Eq)] pub enum CaptureKind { SharedRef, UniqueSharedRef, @@ -4117,6 +4160,74 @@ pub enum CaptureKind { Move, } +#[derive(Debug, Clone)] +pub struct CaptureUsages { + parent: DefWithBodyId, + spans: SmallVec<[mir::MirSpan; 3]>, +} + +impl CaptureUsages { + pub fn sources(&self, db: &dyn HirDatabase) -> Vec { + let (body, source_map) = db.body_with_source_map(self.parent); + let mut result = Vec::with_capacity(self.spans.len()); + for &span in self.spans.iter() { + let is_ref = span.is_ref_span(&body); + match span { + mir::MirSpan::ExprId(expr) => { + if let Ok(expr) = source_map.expr_syntax(expr) { + result.push(CaptureUsageSource { + is_ref, + source: expr.map(AstPtr::wrap_left), + }) + } + } + mir::MirSpan::PatId(pat) => { + if let Ok(pat) = source_map.pat_syntax(pat) { + result.push(CaptureUsageSource { + is_ref, + source: pat.map(AstPtr::wrap_right), + }); + } + } + mir::MirSpan::BindingId(binding) => result.extend( + source_map + .patterns_for_binding(binding) + .iter() + .filter_map(|&pat| source_map.pat_syntax(pat).ok()) + .map(|pat| CaptureUsageSource { + is_ref, + source: pat.map(AstPtr::wrap_right), + }), + ), + mir::MirSpan::SelfParam | mir::MirSpan::Unknown => { + unreachable!("invalid capture usage span") + } + } + } + result + } +} + +#[derive(Debug)] +pub struct CaptureUsageSource { + is_ref: bool, + source: InFile>>, +} + +impl CaptureUsageSource { + pub fn source(&self) -> AstPtr> { + self.source.value + } + + pub fn file_id(&self) -> HirFileId { + self.source.file_id + } + + pub fn is_ref(&self) -> bool { + self.is_ref + } +} + #[derive(Clone, PartialEq, Eq, Debug, Hash)] pub struct Type { env: Arc, @@ -4355,6 +4466,22 @@ impl Type { method_resolution::implements_trait(&canonical_ty, db, &self.env, trait_) } + /// This does **not** resolve `IntoFuture`, only `Future`. + pub fn future_output(self, db: &dyn HirDatabase) -> Option { + let future_output = + db.lang_item(self.env.krate, LangItem::FutureOutput)?.as_type_alias()?; + self.normalize_trait_assoc_type(db, &[], future_output.into()) + } + + /// This does **not** resolve `IntoIterator`, only `Iterator`. + pub fn iterator_item(self, db: &dyn HirDatabase) -> Option { + let iterator_trait = db.lang_item(self.env.krate, LangItem::Iterator)?.as_trait()?; + let iterator_item = db + .trait_data(iterator_trait) + .associated_type_by_name(&Name::new_symbol(sym::Item.clone(), SyntaxContextId::ROOT))?; + self.normalize_trait_assoc_type(db, &[], iterator_item.into()) + } + /// Checks that particular type `ty` implements `std::ops::FnOnce`. /// /// This function can be used to check if a particular type is callable, since FnOnce is a @@ -4704,18 +4831,20 @@ impl Type { pub fn type_and_const_arguments<'a>( &'a self, db: &'a dyn HirDatabase, + edition: Edition, ) -> impl Iterator + 'a { self.ty .strip_references() .as_adt() .into_iter() .flat_map(|(_, substs)| substs.iter(Interner)) - .filter_map(|arg| { + .filter_map(move |arg| { // arg can be either a `Ty` or `constant` if let Some(ty) = arg.ty(Interner) { - Some(format_smolstr!("{}", ty.display(db))) + Some(format_smolstr!("{}", ty.display(db, edition))) } else { - arg.constant(Interner).map(|const_| format_smolstr!("{}", const_.display(db))) + arg.constant(Interner) + .map(|const_| format_smolstr!("{}", const_.display(db, edition))) } }) } @@ -4724,13 +4853,17 @@ impl Type { pub fn generic_parameters<'a>( &'a self, db: &'a dyn HirDatabase, + edition: Edition, ) -> impl Iterator + 'a { // iterate the lifetime self.as_adt() - .and_then(|a| a.lifetime(db).map(|lt| lt.name.display_no_db().to_smolstr())) + .and_then(|a| { + // Lifetimes do not need edition-specific handling as they cannot be escaped. + a.lifetime(db).map(|lt| lt.name.display_no_db(Edition::Edition2015).to_smolstr()) + }) .into_iter() // add the type and const parameters - .chain(self.type_and_const_arguments(db)) + .chain(self.type_and_const_arguments(db, edition)) } pub fn iterate_method_candidates_with_traits( diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs index a377163162..763f53031e 100644 --- a/crates/hir/src/semantics.rs +++ b/crates/hir/src/semantics.rs @@ -4,6 +4,7 @@ mod source_to_def; use std::{ cell::RefCell, + convert::Infallible, fmt, iter, mem, ops::{self, ControlFlow, Not}, }; @@ -22,9 +23,11 @@ use hir_expand::{ builtin::{BuiltinFnLikeExpander, EagerExpander}, db::ExpandDatabase, files::InRealFile, + inert_attr_macro::find_builtin_attr_idx, name::AsName, FileRange, InMacroFile, MacroCallId, MacroFileId, MacroFileIdExt, }; +use intern::Symbol; use itertools::Itertools; use rustc_hash::{FxHashMap, FxHashSet}; use smallvec::{smallvec, SmallVec}; @@ -48,11 +51,7 @@ use crate::{ Variant, VariantDef, }; -pub enum DescendPreference { - SameText, - SameKind, - None, -} +const CONTINUE_NO_BREAKS: ControlFlow = ControlFlow::Continue(()); #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum PathResolution { @@ -182,6 +181,7 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> { /// Find an AstNode by offset inside SyntaxNode, if it is inside *MacroCall*, /// descend it and find again + // FIXME: Rethink this API pub fn find_node_at_offset_with_descend( &self, node: &SyntaxNode, @@ -190,8 +190,9 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> { self.imp.descend_node_at_offset(node, offset).flatten().find_map(N::cast) } - /// Find an AstNode by offset inside SyntaxNode, if it is inside *MacroCall*, + /// Find an AstNode by offset inside SyntaxNode, if it is inside an attribute macro call, /// descend it and find again + // FIXME: Rethink this API pub fn find_nodes_at_offset_with_descend<'slf, N: AstNode + 'slf>( &'slf self, node: &SyntaxNode, @@ -545,51 +546,53 @@ impl<'db> SemanticsImpl<'db> { ) } + /// Retrieves all the formatting parts of the format_args! template string. pub fn as_format_args_parts( &self, string: &ast::String, ) -> Option)>> { - if let Some(quote) = string.open_quote_text_range() { - return self - .descend_into_macros(DescendPreference::SameText, string.syntax().clone()) - .into_iter() - .find_map(|token| { - let string = ast::String::cast(token)?; - let literal = - string.syntax().parent().filter(|it| it.kind() == SyntaxKind::LITERAL)?; - let format_args = ast::FormatArgsExpr::cast(literal.parent()?)?; - let source_analyzer = self.analyze_no_infer(format_args.syntax())?; - let format_args = self.wrap_node_infile(format_args); - let res = source_analyzer - .as_format_args_parts(self.db, format_args.as_ref())? - .map(|(range, res)| (range + quote.end(), res)) - .collect(); - Some(res) - }); - } - None + let quote = string.open_quote_text_range()?; + + let token = self.wrap_token_infile(string.syntax().clone()).into_real_file().ok()?; + self.descend_into_macros_breakable(token, |token| { + (|| { + let token = token.value; + let string = ast::String::cast(token)?; + let literal = + string.syntax().parent().filter(|it| it.kind() == SyntaxKind::LITERAL)?; + let format_args = ast::FormatArgsExpr::cast(literal.parent()?)?; + let source_analyzer = self.analyze_no_infer(format_args.syntax())?; + let format_args = self.wrap_node_infile(format_args); + let res = source_analyzer + .as_format_args_parts(self.db, format_args.as_ref())? + .map(|(range, res)| (range + quote.end(), res)) + .collect(); + Some(res) + })() + .map_or(ControlFlow::Continue(()), ControlFlow::Break) + }) } + /// Retrieves the formatting part of the format_args! template string at the given offset. pub fn check_for_format_args_template( &self, original_token: SyntaxToken, offset: TextSize, ) -> Option<(TextRange, Option)> { - if let Some(original_string) = ast::String::cast(original_token.clone()) { - if let Some(quote) = original_string.open_quote_text_range() { - return self - .descend_into_macros(DescendPreference::SameText, original_token) - .into_iter() - .find_map(|token| { - self.resolve_offset_in_format_args( - ast::String::cast(token)?, - offset.checked_sub(quote.end())?, - ) - }) - .map(|(range, res)| (range + quote.end(), res)); - } - } - None + let original_string = ast::String::cast(original_token.clone())?; + let original_token = self.wrap_token_infile(original_token).into_real_file().ok()?; + let quote = original_string.open_quote_text_range()?; + self.descend_into_macros_breakable(original_token, |token| { + (|| { + let token = token.value; + self.resolve_offset_in_format_args( + ast::String::cast(token)?, + offset.checked_sub(quote.end())?, + ) + .map(|(range, res)| (range + quote.end(), res)) + })() + .map_or(ControlFlow::Continue(()), ControlFlow::Break) + }) } fn resolve_offset_in_format_args( @@ -619,30 +622,37 @@ impl<'db> SemanticsImpl<'db> { Some(it) => it, None => return res, }; + let file = self.find_file(node.syntax()); + let Some(file_id) = file.file_id.file_id() else { + return res; + }; if first == last { // node is just the token, so descend the token - self.descend_into_macros_impl(first, &mut |InFile { value, .. }| { - if let Some(node) = value - .parent_ancestors() - .take_while(|it| it.text_range() == value.text_range()) - .find_map(N::cast) - { - res.push(node) - } - ControlFlow::Continue(()) - }); + self.descend_into_macros_impl( + InRealFile::new(file_id, first), + &mut |InFile { value, .. }| { + if let Some(node) = value + .parent_ancestors() + .take_while(|it| it.text_range() == value.text_range()) + .find_map(N::cast) + { + res.push(node) + } + CONTINUE_NO_BREAKS + }, + ); } else { // Descend first and last token, then zip them to look for the node they belong to let mut scratch: SmallVec<[_; 1]> = smallvec![]; - self.descend_into_macros_impl(first, &mut |token| { + self.descend_into_macros_impl(InRealFile::new(file_id, first), &mut |token| { scratch.push(token); - ControlFlow::Continue(()) + CONTINUE_NO_BREAKS }); let mut scratch = scratch.into_iter(); self.descend_into_macros_impl( - last, + InRealFile::new(file_id, last), &mut |InFile { value: last, file_id: last_fid }| { if let Some(InFile { value: first, file_id: first_fid }) = scratch.next() { if first_fid == last_fid { @@ -659,130 +669,151 @@ impl<'db> SemanticsImpl<'db> { } } } - ControlFlow::Continue(()) + CONTINUE_NO_BREAKS }, ); } res } - /// Descend the token into its macro call if it is part of one, returning the tokens in the - /// expansion that it is associated with. - pub fn descend_into_macros( + fn is_inside_macro_call(token: &SyntaxToken) -> bool { + token.parent_ancestors().any(|ancestor| { + if ast::MacroCall::can_cast(ancestor.kind()) { + return true; + } + // Check if it is an item (only items can have macro attributes) that has a non-builtin attribute. + let Some(item) = ast::Item::cast(ancestor) else { return false }; + item.attrs().any(|attr| { + let Some(meta) = attr.meta() else { return false }; + let Some(path) = meta.path() else { return false }; + let Some(attr_name) = path.as_single_name_ref() else { return true }; + let attr_name = attr_name.text(); + let attr_name = attr_name.as_str(); + attr_name == "derive" || find_builtin_attr_idx(&Symbol::intern(attr_name)).is_none() + }) + }) + } + + pub fn descend_into_macros_exact_if_in_macro( &self, - mode: DescendPreference, token: SyntaxToken, ) -> SmallVec<[SyntaxToken; 1]> { - enum Dp<'t> { - SameText(&'t str), - SameKind(SyntaxKind), - None, + if Self::is_inside_macro_call(&token) { + self.descend_into_macros_exact(token) + } else { + smallvec![token] } - let fetch_kind = |token: &SyntaxToken| match token.parent() { - Some(node) => match node.kind() { - kind @ (SyntaxKind::NAME | SyntaxKind::NAME_REF) => kind, - _ => token.kind(), - }, - None => token.kind(), - }; - let mode = match mode { - DescendPreference::SameText => Dp::SameText(token.text()), - DescendPreference::SameKind => Dp::SameKind(fetch_kind(&token)), - DescendPreference::None => Dp::None, - }; + } + + pub fn descend_into_macros_cb( + &self, + token: SyntaxToken, + mut cb: impl FnMut(InFile), + ) { + if let Ok(token) = self.wrap_token_infile(token).into_real_file() { + self.descend_into_macros_impl(token, &mut |t| { + cb(t); + CONTINUE_NO_BREAKS + }); + } + } + + pub fn descend_into_macros(&self, token: SyntaxToken) -> SmallVec<[SyntaxToken; 1]> { let mut res = smallvec![]; - self.descend_into_macros_impl(token.clone(), &mut |InFile { value, .. }| { - let is_a_match = match mode { - Dp::SameText(text) => value.text() == text, - Dp::SameKind(preferred_kind) => { - let kind = fetch_kind(&value); - kind == preferred_kind - // special case for derive macros - || (preferred_kind == SyntaxKind::IDENT && kind == SyntaxKind::NAME_REF) - } - Dp::None => true, - }; - if is_a_match { - res.push(value); - } - ControlFlow::Continue(()) - }); + if let Ok(token) = self.wrap_token_infile(token.clone()).into_real_file() { + self.descend_into_macros_impl(token, &mut |t| { + res.push(t.value); + CONTINUE_NO_BREAKS + }); + } if res.is_empty() { res.push(token); } res } - pub fn descend_into_macros_single( + pub fn descend_into_macros_breakable( &self, - mode: DescendPreference, - token: SyntaxToken, - ) -> SyntaxToken { - enum Dp<'t> { - SameText(&'t str), - SameKind(SyntaxKind), - None, - } - let fetch_kind = |token: &SyntaxToken| match token.parent() { - Some(node) => match node.kind() { - kind @ (SyntaxKind::NAME | SyntaxKind::NAME_REF) => kind, - _ => token.kind(), - }, - None => token.kind(), - }; - let mode = match mode { - DescendPreference::SameText => Dp::SameText(token.text()), - DescendPreference::SameKind => Dp::SameKind(fetch_kind(&token)), - DescendPreference::None => Dp::None, - }; - let mut res = token.clone(); - self.descend_into_macros_impl(token.clone(), &mut |InFile { value, .. }| { - let is_a_match = match mode { - Dp::SameText(text) => value.text() == text, - Dp::SameKind(preferred_kind) => { - let kind = fetch_kind(&value); - kind == preferred_kind - // special case for derive macros - || (preferred_kind == SyntaxKind::IDENT && kind == SyntaxKind::NAME_REF) - } - Dp::None => true, - }; - res = value; - if is_a_match { - ControlFlow::Break(()) - } else { - ControlFlow::Continue(()) - } - }); - res + token: InRealFile, + mut cb: impl FnMut(InFile) -> ControlFlow, + ) -> Option { + self.descend_into_macros_impl(token.clone(), &mut cb) } - fn descend_into_macros_impl( + /// Descends the token into expansions, returning the tokens that matches the input + /// token's [`SyntaxKind`] and text. + pub fn descend_into_macros_exact(&self, token: SyntaxToken) -> SmallVec<[SyntaxToken; 1]> { + let mut r = smallvec![]; + let text = token.text(); + let kind = token.kind(); + + self.descend_into_macros_cb(token.clone(), |InFile { value, file_id: _ }| { + let mapped_kind = value.kind(); + let any_ident_match = || kind.is_any_identifier() && value.kind().is_any_identifier(); + let matches = (kind == mapped_kind || any_ident_match()) && text == value.text(); + if matches { + r.push(value); + } + }); + if r.is_empty() { + r.push(token); + } + r + } + + /// Descends the token into expansions, returning the first token that matches the input + /// token's [`SyntaxKind`] and text. + pub fn descend_into_macros_single_exact(&self, token: SyntaxToken) -> SyntaxToken { + let text = token.text(); + let kind = token.kind(); + if let Ok(token) = self.wrap_token_infile(token.clone()).into_real_file() { + self.descend_into_macros_breakable(token.clone(), |InFile { value, file_id: _ }| { + let mapped_kind = value.kind(); + let any_ident_match = + || kind.is_any_identifier() && value.kind().is_any_identifier(); + let matches = (kind == mapped_kind || any_ident_match()) && text == value.text(); + if matches { + ControlFlow::Break(value) + } else { + ControlFlow::Continue(()) + } + }) + } else { + None + } + .unwrap_or(token) + } + + fn descend_into_macros_impl( &self, - token: SyntaxToken, - f: &mut dyn FnMut(InFile) -> ControlFlow<()>, - ) { + InRealFile { value: token, file_id }: InRealFile, + f: &mut dyn FnMut(InFile) -> ControlFlow, + ) -> Option { let _p = tracing::info_span!("descend_into_macros_impl").entered(); - let (sa, span, file_id) = - match token.parent().and_then(|parent| self.analyze_no_infer(&parent)) { - Some(sa) => match sa.file_id.file_id() { - Some(file_id) => ( - sa, - self.db.real_span_map(file_id).span_for_range(token.text_range()), - file_id.into(), - ), - None => { - stdx::never!(); - return; - } - }, - None => return, - }; + let (sa, span, file_id) = token + .parent() + .and_then(|parent| { + self.analyze_impl(InRealFile::new(file_id, &parent).into(), None, false) + }) + .and_then(|sa| { + let file_id = sa.file_id.file_id()?; + Some(( + sa, + self.db.real_span_map(file_id).span_for_range(token.text_range()), + HirFileId::from(file_id), + )) + })?; let mut m_cache = self.macro_call_cache.borrow_mut(); let def_map = sa.resolver.def_map(); + // A stack of tokens to process, along with the file they came from + // These are tracked to know which macro calls we still have to look into + // the tokens themselves aren't that interesting as the span that is being used to map + // things down never changes. let mut stack: Vec<(_, SmallVec<[_; 2]>)> = vec![(file_id, smallvec![token])]; + + // Process the expansion of a call, pushing all tokens with our span in the expansion back onto our stack let process_expansion_for_token = |stack: &mut Vec<_>, macro_file| { let InMacroFile { file_id, value: mapped_tokens } = self.with_ctx(|ctx| { Some( @@ -809,7 +840,13 @@ impl<'db> SemanticsImpl<'db> { res }; - while let Some((file_id, mut tokens)) = stack.pop() { + // Filters out all tokens that contain the given range (usually the macro call), any such + // token is redundant as the corresponding macro call has already been processed + let filter_duplicates = |tokens: &mut SmallVec<_>, range: TextRange| { + tokens.retain(|t: &mut SyntaxToken| !range.contains_range(t.text_range())) + }; + + while let Some((expansion, ref mut tokens)) = stack.pop() { while let Some(token) = tokens.pop() { let was_not_remapped = (|| { // First expand into attribute invocations @@ -817,7 +854,7 @@ impl<'db> SemanticsImpl<'db> { token.parent_ancestors().filter_map(ast::Item::cast).find_map(|item| { // Don't force populate the dyn cache for items that don't have an attribute anyways item.attrs().next()?; - Some((ctx.item_to_macro_call(InFile::new(file_id, &item))?, item)) + Some((ctx.item_to_macro_call(InFile::new(expansion, &item))?, item)) }) }); if let Some((call_id, item)) = containing_attribute_macro_call { @@ -849,9 +886,7 @@ impl<'db> SemanticsImpl<'db> { }) .unwrap_or_else(|| text_range.start()); let text_range = TextRange::new(start, text_range.end()); - // remove any other token in this macro input, all their mappings are the - // same as this one - tokens.retain(|t| !text_range.contains_range(t.text_range())); + filter_duplicates(tokens, text_range); return process_expansion_for_token(&mut stack, file_id); } @@ -862,6 +897,7 @@ impl<'db> SemanticsImpl<'db> { .map_while(Either::::cast) .last()?; match tt { + // function-like macro call Either::Left(tt) => { if tt.left_delimiter_token().map_or(false, |it| it == token) { return None; @@ -870,7 +906,7 @@ impl<'db> SemanticsImpl<'db> { return None; } let macro_call = tt.syntax().parent().and_then(ast::MacroCall::cast)?; - let mcall = InFile::new(file_id, macro_call); + let mcall = InFile::new(expansion, macro_call); let file_id = match m_cache.get(&mcall) { Some(&it) => it, None => { @@ -888,9 +924,7 @@ impl<'db> SemanticsImpl<'db> { } }; let text_range = tt.syntax().text_range(); - // remove any other token in this macro input, all their mappings are the - // same as this one - tokens.retain(|t| !text_range.contains_range(t.text_range())); + filter_duplicates(tokens, text_range); process_expansion_for_token(&mut stack, file_id).or(file_id .eager_arg(self.db.upcast()) @@ -899,6 +933,7 @@ impl<'db> SemanticsImpl<'db> { process_expansion_for_token(&mut stack, arg.as_macro_file()) })) } + // derive or derive helper Either::Right(meta) => { // attribute we failed expansion for earlier, this might be a derive invocation // or derive helper attribute @@ -910,8 +945,8 @@ impl<'db> SemanticsImpl<'db> { // so try downmapping the token into the pseudo derive expansion // see [hir_expand::builtin_attr_macro] for how the pseudo derive expansion works ctx.attr_to_derive_macro_call( - InFile::new(file_id, &adt), - InFile::new(file_id, attr.clone()), + InFile::new(expansion, &adt), + InFile::new(expansion, attr.clone()), ) .map(|(_, call_id, _)| call_id) }); @@ -945,28 +980,29 @@ impl<'db> SemanticsImpl<'db> { ) } }?; - if !self.with_ctx(|ctx| ctx.has_derives(InFile::new(file_id, &adt))) { + if !self.with_ctx(|ctx| ctx.has_derives(InFile::new(expansion, &adt))) { return None; } let attr_name = attr.path().and_then(|it| it.as_single_name_ref())?.as_name(); - // Not an attribute, nor a derive, so it's either a builtin or a derive helper + // Not an attribute, nor a derive, so it's either an intert attribute or a derive helper // Try to resolve to a derive helper and downmap - let id = self.db.ast_id_map(file_id).ast_id(&adt); + let id = self.db.ast_id_map(expansion).ast_id(&adt); let helpers = - def_map.derive_helpers_in_scope(InFile::new(file_id, id))?; + def_map.derive_helpers_in_scope(InFile::new(expansion, id))?; if !helpers.is_empty() { let text_range = attr.syntax().text_range(); - // remove any other token in this macro input, all their mappings are the - // same as this - tokens.retain(|t| !text_range.contains_range(t.text_range())); + filter_duplicates(tokens, text_range); } let mut res = None; for (.., derive) in helpers.iter().filter(|(helper, ..)| *helper == attr_name) { + // as there may be multiple derives registering the same helper + // name, we gotta make sure to call this for all of them! + // FIXME: We need to call `f` for all of them as well though! res = res.or(process_expansion_for_token( &mut stack, derive.as_macro_file(), @@ -978,11 +1014,14 @@ impl<'db> SemanticsImpl<'db> { })() .is_none(); - if was_not_remapped && f(InFile::new(file_id, token)).is_break() { - break; + if was_not_remapped { + if let ControlFlow::Break(b) = f(InFile::new(expansion, token)) { + return Some(b); + } } } } + None } // Note this return type is deliberate as [`find_nodes_at_offset_with_descend`] wants to stop @@ -995,7 +1034,7 @@ impl<'db> SemanticsImpl<'db> { offset: TextSize, ) -> impl Iterator + '_> + '_ { node.token_at_offset(offset) - .map(move |token| self.descend_into_macros(DescendPreference::None, token)) + .map(move |token| self.descend_into_macros_exact(token)) .map(|descendants| { descendants.into_iter().map(move |it| self.token_ancestors_with_macros(it)) }) @@ -1179,7 +1218,8 @@ impl<'db> SemanticsImpl<'db> { hir_ty::Adjust::Borrow(hir_ty::AutoBorrow::RawPtr(m)) => { Adjust::Borrow(AutoBorrow::RawPtr(mutability(m))) } - hir_ty::Adjust::Borrow(hir_ty::AutoBorrow::Ref(m)) => { + hir_ty::Adjust::Borrow(hir_ty::AutoBorrow::Ref(_, m)) => { + // FIXME: Handle lifetimes here Adjust::Borrow(AutoBorrow::Ref(mutability(m))) } hir_ty::Adjust::Pointer(pc) => Adjust::Pointer(pc), @@ -1413,11 +1453,13 @@ impl<'db> SemanticsImpl<'db> { /// Returns none if the file of the node is not part of a crate. fn analyze(&self, node: &SyntaxNode) -> Option { + let node = self.find_file(node); self.analyze_impl(node, None, true) } /// Returns none if the file of the node is not part of a crate. fn analyze_no_infer(&self, node: &SyntaxNode) -> Option { + let node = self.find_file(node); self.analyze_impl(node, None, false) } @@ -1426,17 +1468,17 @@ impl<'db> SemanticsImpl<'db> { node: &SyntaxNode, offset: TextSize, ) -> Option { + let node = self.find_file(node); self.analyze_impl(node, Some(offset), false) } fn analyze_impl( &self, - node: &SyntaxNode, + node: InFile<&SyntaxNode>, offset: Option, infer_body: bool, ) -> Option { let _p = tracing::info_span!("SemanticsImpl::analyze_impl").entered(); - let node = self.find_file(node); let container = self.with_ctx(|ctx| ctx.find_container(node))?; @@ -1481,6 +1523,11 @@ impl<'db> SemanticsImpl<'db> { InFile::new(file_id, node) } + fn wrap_token_infile(&self, token: SyntaxToken) -> InFile { + let InFile { file_id, .. } = self.find_file(&token.parent().unwrap()); + InFile::new(file_id, token) + } + /// Wraps the node in a [`InFile`] with the file id it belongs to. fn find_file<'node>(&self, node: &'node SyntaxNode) -> InFile<&'node SyntaxNode> { let root_node = find_root(node); diff --git a/crates/hir/src/symbols.rs b/crates/hir/src/symbols.rs index b1f5df681f..cabb7e3db3 100644 --- a/crates/hir/src/symbols.rs +++ b/crates/hir/src/symbols.rs @@ -9,6 +9,7 @@ use hir_def::{ }; use hir_expand::HirFileId; use hir_ty::{db::HirDatabase, display::HirDisplay}; +use span::Edition; use syntax::{ast::HasName, AstNode, AstPtr, SmolStr, SyntaxNode, SyntaxNodePtr, ToSmolStr}; use crate::{Module, ModuleDef, Semantics}; @@ -54,6 +55,7 @@ pub struct SymbolCollector<'a> { symbols: Vec, work: Vec, current_container_name: Option, + edition: Edition, } /// Given a [`ModuleId`] and a [`HirDatabase`], use the DefMap for the module's crate to collect @@ -65,10 +67,13 @@ impl<'a> SymbolCollector<'a> { symbols: Default::default(), work: Default::default(), current_container_name: None, + edition: Edition::Edition2015, } } pub fn collect(&mut self, module: Module) { + self.edition = module.krate().edition(self.db); + // The initial work is the root module we're collecting, additional work will // be populated as we traverse the module's definitions. self.work.push(SymbolCollectorWork { module_id: module.into(), parent: None }); @@ -209,7 +214,8 @@ impl<'a> SymbolCollector<'a> { fn collect_from_impl(&mut self, impl_id: ImplId) { let impl_data = self.db.impl_data(impl_id); - let impl_name = Some(SmolStr::new(impl_data.self_ty.display(self.db).to_string())); + let impl_name = + Some(SmolStr::new(impl_data.self_ty.display(self.db, self.edition).to_string())); self.with_container_name(impl_name, |s| { for &assoc_item_id in impl_data.items.iter() { s.push_assoc_item(assoc_item_id) @@ -239,16 +245,16 @@ impl<'a> SymbolCollector<'a> { fn def_with_body_id_name(&self, body_id: DefWithBodyId) -> Option { match body_id { DefWithBodyId::FunctionId(id) => { - Some(self.db.function_data(id).name.display_no_db().to_smolstr()) + Some(self.db.function_data(id).name.display_no_db(self.edition).to_smolstr()) } DefWithBodyId::StaticId(id) => { - Some(self.db.static_data(id).name.display_no_db().to_smolstr()) + Some(self.db.static_data(id).name.display_no_db(self.edition).to_smolstr()) } DefWithBodyId::ConstId(id) => { - Some(self.db.const_data(id).name.as_ref()?.display_no_db().to_smolstr()) + Some(self.db.const_data(id).name.as_ref()?.display_no_db(self.edition).to_smolstr()) } DefWithBodyId::VariantId(id) => { - Some(self.db.enum_variant_data(id).name.display_no_db().to_smolstr()) + Some(self.db.enum_variant_data(id).name.display_no_db(self.edition).to_smolstr()) } DefWithBodyId::InTypeConstId(_) => Some("in type const".into()), } diff --git a/crates/hir/src/term_search/expr.rs b/crates/hir/src/term_search/expr.rs index 0c8f6932c7..6ad074e8e5 100644 --- a/crates/hir/src/term_search/expr.rs +++ b/crates/hir/src/term_search/expr.rs @@ -7,6 +7,7 @@ use hir_ty::{ display::{DisplaySourceCodeError, HirDisplay}, }; use itertools::Itertools; +use span::Edition; use crate::{ Adt, AsAssocItem, AssocItemContainer, Const, ConstParam, Field, Function, Local, ModuleDef, @@ -29,9 +30,10 @@ fn mod_item_path_str( sema_scope: &SemanticsScope<'_>, def: &ModuleDef, cfg: ImportPathConfig, + edition: Edition, ) -> Result { let path = mod_item_path(sema_scope, def, cfg); - path.map(|it| it.display(sema_scope.db.upcast()).to_string()) + path.map(|it| it.display(sema_scope.db.upcast(), edition).to_string()) .ok_or(DisplaySourceCodeError::PathNotFound) } @@ -97,37 +99,38 @@ impl Expr { sema_scope: &SemanticsScope<'_>, many_formatter: &mut dyn FnMut(&Type) -> String, cfg: ImportPathConfig, + edition: Edition, ) -> Result { let db = sema_scope.db; - let mod_item_path_str = |s, def| mod_item_path_str(s, def, cfg); + let mod_item_path_str = |s, def| mod_item_path_str(s, def, cfg, edition); match self { Expr::Const(it) => match it.as_assoc_item(db).map(|it| it.container(db)) { Some(container) => { - let container_name = container_name(container, sema_scope, cfg)?; + let container_name = container_name(container, sema_scope, cfg, edition)?; let const_name = it .name(db) - .map(|c| c.display(db.upcast()).to_string()) + .map(|c| c.display(db.upcast(), edition).to_string()) .unwrap_or(String::new()); Ok(format!("{container_name}::{const_name}")) } None => mod_item_path_str(sema_scope, &ModuleDef::Const(*it)), }, Expr::Static(it) => mod_item_path_str(sema_scope, &ModuleDef::Static(*it)), - Expr::Local(it) => Ok(it.name(db).display(db.upcast()).to_string()), - Expr::ConstParam(it) => Ok(it.name(db).display(db.upcast()).to_string()), + Expr::Local(it) => Ok(it.name(db).display(db.upcast(), edition).to_string()), + Expr::ConstParam(it) => Ok(it.name(db).display(db.upcast(), edition).to_string()), Expr::FamousType { value, .. } => Ok(value.to_string()), Expr::Function { func, params, .. } => { let args = params .iter() - .map(|f| f.gen_source_code(sema_scope, many_formatter, cfg)) + .map(|f| f.gen_source_code(sema_scope, many_formatter, cfg, edition)) .collect::, DisplaySourceCodeError>>()? .into_iter() .join(", "); match func.as_assoc_item(db).map(|it| it.container(db)) { Some(container) => { - let container_name = container_name(container, sema_scope, cfg)?; - let fn_name = func.name(db).display(db.upcast()).to_string(); + let container_name = container_name(container, sema_scope, cfg, edition)?; + let fn_name = func.name(db).display(db.upcast(), edition).to_string(); Ok(format!("{container_name}::{fn_name}({args})")) } None => { @@ -141,12 +144,13 @@ impl Expr { return Ok(many_formatter(&target.ty(db))); } - let func_name = func.name(db).display(db.upcast()).to_string(); + let func_name = func.name(db).display(db.upcast(), edition).to_string(); let self_param = func.self_param(db).unwrap(); - let target_str = target.gen_source_code(sema_scope, many_formatter, cfg)?; + let target_str = + target.gen_source_code(sema_scope, many_formatter, cfg, edition)?; let args = params .iter() - .map(|f| f.gen_source_code(sema_scope, many_formatter, cfg)) + .map(|f| f.gen_source_code(sema_scope, many_formatter, cfg, edition)) .collect::, DisplaySourceCodeError>>()? .into_iter() .join(", "); @@ -176,7 +180,7 @@ impl Expr { StructKind::Tuple => { let args = params .iter() - .map(|f| f.gen_source_code(sema_scope, many_formatter, cfg)) + .map(|f| f.gen_source_code(sema_scope, many_formatter, cfg, edition)) .collect::, DisplaySourceCodeError>>()? .into_iter() .join(", "); @@ -190,8 +194,8 @@ impl Expr { .map(|(a, f)| { let tmp = format!( "{}: {}", - f.name(db).display(db.upcast()), - a.gen_source_code(sema_scope, many_formatter, cfg)? + f.name(db).display(db.upcast(), edition), + a.gen_source_code(sema_scope, many_formatter, cfg, edition)? ); Ok(tmp) }) @@ -211,7 +215,7 @@ impl Expr { StructKind::Tuple => { let args = params .iter() - .map(|a| a.gen_source_code(sema_scope, many_formatter, cfg)) + .map(|a| a.gen_source_code(sema_scope, many_formatter, cfg, edition)) .collect::, DisplaySourceCodeError>>()? .into_iter() .join(", "); @@ -225,8 +229,8 @@ impl Expr { .map(|(a, f)| { let tmp = format!( "{}: {}", - f.name(db).display(db.upcast()), - a.gen_source_code(sema_scope, many_formatter, cfg)? + f.name(db).display(db.upcast(), edition), + a.gen_source_code(sema_scope, many_formatter, cfg, edition)? ); Ok(tmp) }) @@ -244,7 +248,7 @@ impl Expr { Expr::Tuple { params, .. } => { let args = params .iter() - .map(|a| a.gen_source_code(sema_scope, many_formatter, cfg)) + .map(|a| a.gen_source_code(sema_scope, many_formatter, cfg, edition)) .collect::, DisplaySourceCodeError>>()? .into_iter() .join(", "); @@ -256,8 +260,8 @@ impl Expr { return Ok(many_formatter(&expr.ty(db))); } - let strukt = expr.gen_source_code(sema_scope, many_formatter, cfg)?; - let field = field.name(db).display(db.upcast()).to_string(); + let strukt = expr.gen_source_code(sema_scope, many_formatter, cfg, edition)?; + let field = field.name(db).display(db.upcast(), edition).to_string(); Ok(format!("{strukt}.{field}")) } Expr::Reference(expr) => { @@ -265,7 +269,7 @@ impl Expr { return Ok(many_formatter(&expr.ty(db))); } - let inner = expr.gen_source_code(sema_scope, many_formatter, cfg)?; + let inner = expr.gen_source_code(sema_scope, many_formatter, cfg, edition)?; Ok(format!("&{inner}")) } Expr::Many(ty) => Ok(many_formatter(ty)), @@ -353,17 +357,18 @@ fn container_name( container: AssocItemContainer, sema_scope: &SemanticsScope<'_>, cfg: ImportPathConfig, + edition: Edition, ) -> Result { let container_name = match container { crate::AssocItemContainer::Trait(trait_) => { - mod_item_path_str(sema_scope, &ModuleDef::Trait(trait_), cfg)? + mod_item_path_str(sema_scope, &ModuleDef::Trait(trait_), cfg, edition)? } crate::AssocItemContainer::Impl(imp) => { let self_ty = imp.self_ty(sema_scope.db); // Should it be guaranteed that `mod_item_path` always exists? match self_ty.as_adt().and_then(|adt| mod_item_path(sema_scope, &adt.into(), cfg)) { - Some(path) => path.display(sema_scope.db.upcast()).to_string(), - None => self_ty.display(sema_scope.db).to_string(), + Some(path) => path.display(sema_scope.db.upcast(), edition).to_string(), + None => self_ty.display(sema_scope.db, edition).to_string(), } } }; 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 4cd15f1c75..7f8ea44fb1 100644 --- a/crates/ide-assists/src/handlers/add_missing_impl_members.rs +++ b/crates/ide-assists/src/handlers/add_missing_impl_members.rs @@ -1,5 +1,8 @@ use hir::HasSource; -use syntax::ast::{self, make, AstNode}; +use syntax::{ + ast::{self, make, AstNode}, + Edition, +}; use crate::{ assist_context::{AssistContext, Assists}, @@ -150,14 +153,22 @@ fn add_missing_impl_members_inner( &missing_items, trait_, &new_impl_def, - target_scope, + &target_scope, ); if let Some(cap) = ctx.config.snippet_cap { let mut placeholder = None; if let DefaultMethods::No = mode { if let ast::AssocItem::Fn(func) = &first_new_item { - if try_gen_trait_body(ctx, func, trait_ref, &impl_def).is_none() { + if try_gen_trait_body( + ctx, + func, + trait_ref, + &impl_def, + target_scope.krate().edition(ctx.sema.db), + ) + .is_none() + { if let Some(m) = func.syntax().descendants().find_map(ast::MacroCall::cast) { if m.syntax().text() == "todo!()" { @@ -182,9 +193,11 @@ fn try_gen_trait_body( func: &ast::Fn, trait_ref: hir::TraitRef, impl_def: &ast::Impl, + edition: Edition, ) -> Option<()> { - let trait_path = - make::ext::ident_path(&trait_ref.trait_().name(ctx.db()).display(ctx.db()).to_string()); + let trait_path = make::ext::ident_path( + &trait_ref.trait_().name(ctx.db()).display(ctx.db(), edition).to_string(), + ); let hir_ty = ctx.sema.resolve_type(&impl_def.self_ty()?)?; let adt = hir_ty.as_adt()?.source(ctx.db())?; gen_trait_fn_body(func, &trait_path, &adt.value, Some(trait_ref)) 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 f4569ca848..b6abb06a2a 100644 --- a/crates/ide-assists/src/handlers/add_missing_match_arms.rs +++ b/crates/ide-assists/src/handlers/add_missing_match_arms.rs @@ -445,7 +445,8 @@ fn build_pat( ) -> Option { match var { ExtendedVariant::Variant(var) => { - let path = mod_path_to_ast(&module.find_path(db, ModuleDef::from(var), cfg)?); + let edition = module.krate().edition(db); + let path = mod_path_to_ast(&module.find_path(db, ModuleDef::from(var), cfg)?, edition); // FIXME: use HIR for this; it doesn't currently expose struct vs. tuple vs. unit variants though Some(match var.source(db)?.value.kind() { ast::StructKind::Tuple(field_list) => { diff --git a/crates/ide-assists/src/handlers/auto_import.rs b/crates/ide-assists/src/handlers/auto_import.rs index db53e49d84..d86948818b 100644 --- a/crates/ide-assists/src/handlers/auto_import.rs +++ b/crates/ide-assists/src/handlers/auto_import.rs @@ -8,7 +8,7 @@ use ide_db::{ insert_use::{insert_use, insert_use_as_alias, ImportScope}, }, }; -use syntax::{ast, AstNode, NodeOrToken, SyntaxElement}; +use syntax::{ast, AstNode, Edition, NodeOrToken, SyntaxElement}; use crate::{AssistContext, AssistId, AssistKind, Assists, GroupLabel}; @@ -120,13 +120,14 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option< // prioritize more relevant imports proposed_imports .sort_by_key(|import| Reverse(relevance_score(ctx, import, current_module.as_ref()))); + let edition = current_module.map(|it| it.krate().edition(ctx.db())).unwrap_or(Edition::CURRENT); let group_label = group_label(import_assets.import_candidate()); for import in proposed_imports { let import_path = import.import_path; let (assist_id, import_name) = - (AssistId("auto_import", AssistKind::QuickFix), import_path.display(ctx.db())); + (AssistId("auto_import", AssistKind::QuickFix), import_path.display(ctx.db(), edition)); acc.add_group( &group_label, assist_id, @@ -138,7 +139,7 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option< ImportScope::Module(it) => ImportScope::Module(builder.make_mut(it)), ImportScope::Block(it) => ImportScope::Block(builder.make_mut(it)), }; - insert_use(&scope, mod_path_to_ast(&import_path), &ctx.config.insert_use); + insert_use(&scope, mod_path_to_ast(&import_path, edition), &ctx.config.insert_use); }, ); @@ -165,7 +166,7 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option< }; insert_use_as_alias( &scope, - mod_path_to_ast(&import_path), + mod_path_to_ast(&import_path, edition), &ctx.config.insert_use, ); }, diff --git a/crates/ide-assists/src/handlers/bool_to_enum.rs b/crates/ide-assists/src/handlers/bool_to_enum.rs index 3a0754d60f..cd5fe0f862 100644 --- a/crates/ide-assists/src/handlers/bool_to_enum.rs +++ b/crates/ide-assists/src/handlers/bool_to_enum.rs @@ -339,6 +339,7 @@ fn augment_references_with_imports( let cfg = ctx.config.import_path_config(); + let edition = target_module.krate().edition(ctx.db()); references .into_iter() .filter_map(|FileReference { range, name, .. }| { @@ -361,7 +362,10 @@ fn augment_references_with_imports( cfg, ) .map(|mod_path| { - make::path_concat(mod_path_to_ast(&mod_path), make::path_from_text("Bool")) + make::path_concat( + mod_path_to_ast(&mod_path, edition), + make::path_from_text("Bool"), + ) }); import_scope.zip(path) diff --git a/crates/ide-assists/src/handlers/convert_bool_then.rs b/crates/ide-assists/src/handlers/convert_bool_then.rs index 77f9c66b35..a5c5b08d5b 100644 --- a/crates/ide-assists/src/handlers/convert_bool_then.rs +++ b/crates/ide-assists/src/handlers/convert_bool_then.rs @@ -159,7 +159,7 @@ pub(crate) fn convert_bool_then_to_if(acc: &mut Assists, ctx: &AssistContext<'_> }; // Verify this is `bool::then` that is being called. let func = ctx.sema.resolve_method_call(&mcall)?; - if func.name(ctx.sema.db).display(ctx.db()).to_string() != "then" { + if !func.name(ctx.sema.db).eq_ident("then") { return None; } let assoc = func.as_assoc_item(ctx.sema.db)?; diff --git a/crates/ide-assists/src/handlers/convert_closure_to_fn.rs b/crates/ide-assists/src/handlers/convert_closure_to_fn.rs new file mode 100644 index 0000000000..79f303b37a --- /dev/null +++ b/crates/ide-assists/src/handlers/convert_closure_to_fn.rs @@ -0,0 +1,1271 @@ +use either::Either; +use hir::{CaptureKind, ClosureCapture, FileRangeWrapper, HirDisplay}; +use ide_db::{ + assists::{AssistId, AssistKind}, + base_db::SourceDatabase, + defs::Definition, + search::FileReferenceNode, + source_change::SourceChangeBuilder, + FxHashSet, +}; +use stdx::format_to; +use syntax::{ + algo::{skip_trivia_token, skip_whitespace_token}, + ast::{ + self, + edit::{AstNodeEdit, IndentLevel}, + make, HasArgList, HasGenericParams, HasName, + }, + hacks::parse_expr_from_str, + ted, AstNode, Direction, SyntaxKind, SyntaxNode, TextSize, ToSmolStr, T, +}; + +use crate::assist_context::{AssistContext, Assists}; + +// Assist: convert_closure_to_fn +// +// This converts a closure to a freestanding function, changing all captures to parameters. +// +// ``` +// # //- minicore: copy +// # struct String; +// # impl String { +// # fn new() -> Self {} +// # fn push_str(&mut self, s: &str) {} +// # } +// fn main() { +// let mut s = String::new(); +// let closure = |$0a| s.push_str(a); +// closure("abc"); +// } +// ``` +// -> +// ``` +// # struct String; +// # impl String { +// # fn new() -> Self {} +// # fn push_str(&mut self, s: &str) {} +// # } +// fn main() { +// let mut s = String::new(); +// fn closure(a: &str, s: &mut String) { +// s.push_str(a) +// } +// closure("abc", &mut s); +// } +// ``` +pub(crate) fn convert_closure_to_fn(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { + let closure = ctx.find_node_at_offset::()?; + if ctx.find_node_at_offset::() != Some(ast::Expr::ClosureExpr(closure.clone())) { + // Not inside the parameter list. + return None; + } + let closure_name = closure.syntax().parent().and_then(|parent| { + let closure_decl = ast::LetStmt::cast(parent)?; + match closure_decl.pat()? { + ast::Pat::IdentPat(pat) => Some((closure_decl, pat.clone(), pat.name()?)), + _ => None, + } + }); + let module = ctx.sema.scope(closure.syntax())?.module(); + let closure_ty = ctx.sema.type_of_expr(&closure.clone().into())?; + let callable = closure_ty.original.as_callable(ctx.db())?; + let closure_ty = closure_ty.original.as_closure()?; + + let mut ret_ty = callable.return_type(); + let mut closure_mentioned_generic_params = ret_ty.generic_params(ctx.db()); + + let mut params = callable + .params() + .into_iter() + .map(|param| { + let node = ctx.sema.source(param.clone())?.value.right()?; + let param_ty = param.ty(); + closure_mentioned_generic_params.extend(param_ty.generic_params(ctx.db())); + match node.ty() { + Some(_) => Some(node), + None => { + let ty = param_ty + .display_source_code(ctx.db(), module.into(), true) + .unwrap_or_else(|_| "_".to_owned()); + Some(make::param(node.pat()?, make::ty(&ty))) + } + } + }) + .collect::>>()?; + + let mut body = closure.body()?.clone_for_update(); + let mut is_gen = false; + let mut is_async = closure.async_token().is_some(); + if is_async { + ret_ty = ret_ty.future_output(ctx.db())?; + } + // We defer the wrapping of the body in the block, because `make::block()` will generate a new node, + // but we need to locate `AstPtr`s inside the body. + let mut wrap_body_in_block = true; + if let ast::Expr::BlockExpr(block) = &body { + if let Some(async_token) = block.async_token() { + if !is_async { + is_async = true; + ret_ty = ret_ty.future_output(ctx.db())?; + let token_idx = async_token.index(); + let whitespace_tokens_after_count = async_token + .siblings_with_tokens(Direction::Next) + .skip(1) + .take_while(|token| token.kind() == SyntaxKind::WHITESPACE) + .count(); + body.syntax().splice_children( + token_idx..token_idx + whitespace_tokens_after_count + 1, + Vec::new(), + ); + } + } + if let Some(gen_token) = block.gen_token() { + is_gen = true; + ret_ty = ret_ty.iterator_item(ctx.db())?; + let token_idx = gen_token.index(); + let whitespace_tokens_after_count = gen_token + .siblings_with_tokens(Direction::Next) + .skip(1) + .take_while(|token| token.kind() == SyntaxKind::WHITESPACE) + .count(); + body.syntax().splice_children( + token_idx..token_idx + whitespace_tokens_after_count + 1, + Vec::new(), + ); + } + + if block.try_token().is_none() + && block.unsafe_token().is_none() + && block.label().is_none() + && block.const_token().is_none() + && block.async_token().is_none() + { + wrap_body_in_block = false; + } + }; + + acc.add( + AssistId("convert_closure_to_fn", AssistKind::RefactorRewrite), + "Convert closure to fn", + closure.param_list()?.syntax().text_range(), + |builder| { + let closure_name_or_default = closure_name + .as_ref() + .map(|(_, _, it)| it.clone()) + .unwrap_or_else(|| make::name("fun_name")); + let captures = closure_ty.captured_items(ctx.db()); + let capture_tys = closure_ty.capture_types(ctx.db()); + + let mut captures_as_args = Vec::with_capacity(captures.len()); + + let body_root = body.syntax().ancestors().last().unwrap(); + // We need to defer this work because otherwise the text range of elements is being messed up, and + // replacements for the next captures won't work. + let mut capture_usages_replacement_map = Vec::with_capacity(captures.len()); + + for (capture, capture_ty) in std::iter::zip(&captures, &capture_tys) { + // FIXME: Allow configuring the replacement of `self`. + let capture_name = + if capture.local().is_self(ctx.db()) && !capture.has_field_projections() { + make::name("this") + } else { + make::name(&capture.place_to_name(ctx.db())) + }; + + closure_mentioned_generic_params.extend(capture_ty.generic_params(ctx.db())); + + let capture_ty = capture_ty + .display_source_code(ctx.db(), module.into(), true) + .unwrap_or_else(|_| "_".to_owned()); + params.push(make::param( + ast::Pat::IdentPat(make::ident_pat(false, false, capture_name.clone_subtree())), + make::ty(&capture_ty), + )); + + for capture_usage in capture.usages().sources(ctx.db()) { + if capture_usage.file_id() != ctx.file_id() { + // This is from a macro, don't change it. + continue; + } + + let capture_usage_source = capture_usage.source(); + let capture_usage_source = capture_usage_source.to_node(&body_root); + let expr = match capture_usage_source { + Either::Left(expr) => expr, + Either::Right(pat) => { + let Some(expr) = expr_of_pat(pat) else { continue }; + expr + } + }; + let replacement = wrap_capture_in_deref_if_needed( + &expr, + &capture_name, + capture.kind(), + capture_usage.is_ref(), + ) + .clone_for_update(); + capture_usages_replacement_map.push((expr, replacement)); + } + + captures_as_args.push(capture_as_arg(ctx, capture)); + } + + let (closure_type_params, closure_where_clause) = + compute_closure_type_params(ctx, closure_mentioned_generic_params, &closure); + + for (old, new) in capture_usages_replacement_map { + if old == body { + body = new; + } else { + ted::replace(old.syntax(), new.syntax()); + } + } + + let body = if wrap_body_in_block { + make::block_expr([], Some(body)) + } else { + ast::BlockExpr::cast(body.syntax().clone()).unwrap() + }; + + let params = make::param_list(None, params); + let ret_ty = if ret_ty.is_unit() { + None + } else { + let ret_ty = ret_ty + .display_source_code(ctx.db(), module.into(), true) + .unwrap_or_else(|_| "_".to_owned()); + Some(make::ret_type(make::ty(&ret_ty))) + }; + let mut fn_ = make::fn_( + None, + closure_name_or_default.clone(), + closure_type_params, + closure_where_clause, + params, + body, + ret_ty, + is_async, + false, + false, + is_gen, + ); + fn_ = fn_.dedent(IndentLevel::from_token(&fn_.syntax().last_token().unwrap())); + + builder.edit_file(ctx.file_id()); + match &closure_name { + Some((closure_decl, _, _)) => { + fn_ = fn_.indent(closure_decl.indent_level()); + builder.replace(closure_decl.syntax().text_range(), fn_.to_string()); + } + None => { + let Some(top_stmt) = + closure.syntax().ancestors().skip(1).find_map(|ancestor| { + ast::Stmt::cast(ancestor.clone()).map(Either::Left).or_else(|| { + ast::ClosureExpr::cast(ancestor.clone()) + .map(Either::Left) + .or_else(|| ast::BlockExpr::cast(ancestor).map(Either::Right)) + .map(Either::Right) + }) + }) + else { + return; + }; + builder.replace( + closure.syntax().text_range(), + closure_name_or_default.to_string(), + ); + match top_stmt { + Either::Left(stmt) => { + let indent = stmt.indent_level(); + fn_ = fn_.indent(indent); + let range = stmt + .syntax() + .first_token() + .and_then(|token| { + skip_whitespace_token(token.prev_token()?, Direction::Prev) + }) + .map(|it| it.text_range().end()) + .unwrap_or_else(|| stmt.syntax().text_range().start()); + builder.insert(range, format!("\n{indent}{fn_}")); + } + Either::Right(Either::Left(closure_inside_closure)) => { + let Some(closure_body) = closure_inside_closure.body() else { return }; + // FIXME: Maybe we can indent this properly, adding newlines and all, but this is hard. + builder.insert( + closure_body.syntax().text_range().start(), + format!("{{ {fn_} "), + ); + builder + .insert(closure_body.syntax().text_range().end(), " }".to_owned()); + } + Either::Right(Either::Right(block_expr)) => { + let Some(tail_expr) = block_expr.tail_expr() else { return }; + let Some(insert_in) = + tail_expr.syntax().first_token().and_then(|token| { + skip_whitespace_token(token.prev_token()?, Direction::Prev) + }) + else { + return; + }; + let indent = tail_expr.indent_level(); + fn_ = fn_.indent(indent); + builder + .insert(insert_in.text_range().end(), format!("\n{indent}{fn_}")); + } + } + } + } + + handle_calls( + builder, + ctx, + closure_name.as_ref().map(|(_, it, _)| it), + &captures_as_args, + &closure, + ); + + // FIXME: Place the cursor at `fun_name`, like rename does. + }, + )?; + Some(()) +} + +fn compute_closure_type_params( + ctx: &AssistContext<'_>, + mentioned_generic_params: FxHashSet, + closure: &ast::ClosureExpr, +) -> (Option, Option) { + if mentioned_generic_params.is_empty() { + return (None, None); + } + + let mut mentioned_names = mentioned_generic_params + .iter() + .filter_map(|param| match param { + hir::GenericParam::TypeParam(param) => { + Some(param.name(ctx.db()).unescaped().display(ctx.db()).to_smolstr()) + } + hir::GenericParam::ConstParam(param) => { + Some(param.name(ctx.db()).unescaped().display(ctx.db()).to_smolstr()) + } + hir::GenericParam::LifetimeParam(_) => None, + }) + .collect::>(); + + let Some((container_params, container_where, container)) = + closure.syntax().ancestors().find_map(ast::AnyHasGenericParams::cast).and_then( + |container| { + Some((container.generic_param_list()?, container.where_clause(), container)) + }, + ) + else { + return (None, None); + }; + let containing_impl = if ast::AssocItem::can_cast(container.syntax().kind()) { + container + .syntax() + .ancestors() + .find_map(ast::Impl::cast) + .and_then(|impl_| Some((impl_.generic_param_list()?, impl_.where_clause()))) + } else { + None + }; + + let all_params = container_params + .type_or_const_params() + .chain(containing_impl.iter().flat_map(|(param_list, _)| param_list.type_or_const_params())) + .filter_map(|param| Some(param.name()?.text().to_smolstr())) + .collect::>(); + + // A fixpoint algorithm to detect (very roughly) if we need to include a generic parameter + // by checking if it is mentioned by another parameter we need to include. + let mut reached_fixpoint = false; + let mut container_where_bounds_indices = Vec::new(); + let mut impl_where_bounds_indices = Vec::new(); + while !reached_fixpoint { + reached_fixpoint = true; + + let mut insert_name = |syntax: &SyntaxNode| { + let has_name = syntax + .descendants() + .filter_map(ast::NameOrNameRef::cast) + .any(|name| mentioned_names.contains(&*name.text())); + let mut has_new_params = false; + if has_name { + syntax + .descendants() + .filter_map(ast::NameOrNameRef::cast) + .filter(|name| all_params.contains(name.text().trim_start_matches("r#"))) + .for_each(|name| { + if mentioned_names.insert(name.text().trim_start_matches("r#").to_smolstr()) + { + // We do this here so we don't do it if there are only matches that are not in `all_params`. + has_new_params = true; + reached_fixpoint = false; + } + }); + } + has_new_params + }; + + for param in container_params.type_or_const_params() { + insert_name(param.syntax()); + } + for (pred_index, pred) in container_where.iter().flat_map(|it| it.predicates()).enumerate() + { + if insert_name(pred.syntax()) { + container_where_bounds_indices.push(pred_index); + } + } + if let Some((impl_params, impl_where)) = &containing_impl { + for param in impl_params.type_or_const_params() { + insert_name(param.syntax()); + } + for (pred_index, pred) in impl_where.iter().flat_map(|it| it.predicates()).enumerate() { + if insert_name(pred.syntax()) { + impl_where_bounds_indices.push(pred_index); + } + } + } + } + + // Order matters here (for beauty). First the outer impl parameters, then the direct container's. + let include_params = containing_impl + .iter() + .flat_map(|(impl_params, _)| { + impl_params.type_or_const_params().filter(|param| { + param.name().is_some_and(|name| { + mentioned_names.contains(name.text().trim_start_matches("r#")) + }) + }) + }) + .chain(container_params.type_or_const_params().filter(|param| { + param + .name() + .is_some_and(|name| mentioned_names.contains(name.text().trim_start_matches("r#"))) + })) + .map(ast::TypeOrConstParam::into); + let include_where_bounds = containing_impl + .as_ref() + .and_then(|(_, it)| it.as_ref()) + .into_iter() + .flat_map(|where_| { + impl_where_bounds_indices.iter().filter_map(|&index| where_.predicates().nth(index)) + }) + .chain(container_where.iter().flat_map(|where_| { + container_where_bounds_indices + .iter() + .filter_map(|&index| where_.predicates().nth(index)) + })) + .collect::>(); + let where_clause = + (!include_where_bounds.is_empty()).then(|| make::where_clause(include_where_bounds)); + + // FIXME: Consider generic parameters that do not appear in params/return type/captures but + // written explicitly inside the closure. + (Some(make::generic_param_list(include_params)), where_clause) +} + +fn wrap_capture_in_deref_if_needed( + expr: &ast::Expr, + capture_name: &ast::Name, + capture_kind: CaptureKind, + is_ref: bool, +) -> ast::Expr { + fn peel_parens(mut expr: ast::Expr) -> ast::Expr { + loop { + if ast::ParenExpr::can_cast(expr.syntax().kind()) { + let Some(parent) = expr.syntax().parent().and_then(ast::Expr::cast) else { break }; + expr = parent; + } else { + break; + } + } + expr + } + + let capture_name = make::expr_path(make::path_from_text(&capture_name.text())); + if capture_kind == CaptureKind::Move || is_ref { + return capture_name; + } + let expr_parent = expr.syntax().parent().and_then(ast::Expr::cast); + let expr_parent_peeled_parens = expr_parent.map(peel_parens); + let does_autoderef = match expr_parent_peeled_parens { + Some( + ast::Expr::AwaitExpr(_) + | ast::Expr::CallExpr(_) + | ast::Expr::FieldExpr(_) + | ast::Expr::FormatArgsExpr(_) + | ast::Expr::MethodCallExpr(_), + ) => true, + Some(ast::Expr::IndexExpr(parent_expr)) if parent_expr.base().as_ref() == Some(expr) => { + true + } + _ => false, + }; + if does_autoderef { + return capture_name; + } + make::expr_prefix(T![*], capture_name) +} + +fn capture_as_arg(ctx: &AssistContext<'_>, capture: &ClosureCapture) -> ast::Expr { + let place = + parse_expr_from_str(&capture.display_place_source_code(ctx.db()), ctx.file_id().edition()) + .expect("`display_place_source_code()` produced an invalid expr"); + let needs_mut = match capture.kind() { + CaptureKind::SharedRef => false, + CaptureKind::MutableRef | CaptureKind::UniqueSharedRef => true, + CaptureKind::Move => return place, + }; + if let ast::Expr::PrefixExpr(expr) = &place { + if expr.op_kind() == Some(ast::UnaryOp::Deref) { + return expr.expr().expect("`display_place_source_code()` produced an invalid expr"); + } + } + make::expr_ref(place, needs_mut) +} + +fn handle_calls( + builder: &mut SourceChangeBuilder, + ctx: &AssistContext<'_>, + closure_name: Option<&ast::IdentPat>, + captures_as_args: &[ast::Expr], + closure: &ast::ClosureExpr, +) { + if captures_as_args.is_empty() { + return; + } + + match closure_name { + Some(closure_name) => { + let Some(closure_def) = ctx.sema.to_def(closure_name) else { return }; + let closure_usages = Definition::from(closure_def).usages(&ctx.sema).all(); + for (_, usages) in closure_usages { + for usage in usages { + let name = match usage.name { + FileReferenceNode::Name(name) => name.syntax().clone(), + FileReferenceNode::NameRef(name_ref) => name_ref.syntax().clone(), + FileReferenceNode::FormatStringEntry(..) => continue, + FileReferenceNode::Lifetime(_) => { + unreachable!("impossible usage") + } + }; + let Some(expr) = name.parent().and_then(|it| { + ast::Expr::cast( + ast::PathSegment::cast(it)?.parent_path().syntax().parent()?, + ) + }) else { + continue; + }; + handle_call(builder, ctx, expr, captures_as_args); + } + } + } + None => { + handle_call(builder, ctx, ast::Expr::ClosureExpr(closure.clone()), captures_as_args); + } + } +} + +fn handle_call( + builder: &mut SourceChangeBuilder, + ctx: &AssistContext<'_>, + closure_ref: ast::Expr, + captures_as_args: &[ast::Expr], +) -> Option<()> { + let call = + ast::CallExpr::cast(peel_blocks_and_refs_and_parens(closure_ref).syntax().parent()?)?; + let args = call.arg_list()?; + // The really last token is `)`; we need one before that. + let has_trailing_comma = args.syntax().last_token()?.prev_token().is_some_and(|token| { + skip_trivia_token(token, Direction::Prev).is_some_and(|token| token.kind() == T![,]) + }); + let has_existing_args = args.args().next().is_some(); + + let FileRangeWrapper { file_id, range } = ctx.sema.original_range_opt(args.syntax())?; + let first_arg_indent = args.args().next().map(|it| it.indent_level()); + let arg_list_indent = args.indent_level(); + let insert_newlines = + first_arg_indent.is_some_and(|first_arg_indent| first_arg_indent != arg_list_indent); + let indent = + if insert_newlines { first_arg_indent.unwrap().to_string() } else { String::new() }; + // FIXME: This text manipulation seems risky. + let text = ctx.db().file_text(file_id.file_id()); + let mut text = text[..u32::from(range.end()).try_into().unwrap()].trim_end(); + if !text.ends_with(')') { + return None; + } + text = text[..text.len() - 1].trim_end(); + let offset = TextSize::new(text.len().try_into().unwrap()); + + let mut to_insert = String::new(); + if has_existing_args && !has_trailing_comma { + to_insert.push(','); + } + if insert_newlines { + to_insert.push('\n'); + } + let (last_arg, rest_args) = + captures_as_args.split_last().expect("already checked has captures"); + if !insert_newlines && has_existing_args { + to_insert.push(' '); + } + if let Some((first_arg, rest_args)) = rest_args.split_first() { + format_to!(to_insert, "{indent}{first_arg},",); + if insert_newlines { + to_insert.push('\n'); + } + for new_arg in rest_args { + if !insert_newlines { + to_insert.push(' '); + } + format_to!(to_insert, "{indent}{new_arg},",); + if insert_newlines { + to_insert.push('\n'); + } + } + if !insert_newlines { + to_insert.push(' '); + } + } + format_to!(to_insert, "{indent}{last_arg}"); + if has_trailing_comma { + to_insert.push(','); + } + + builder.edit_file(file_id); + builder.insert(offset, to_insert); + + Some(()) +} + +fn peel_blocks_and_refs_and_parens(mut expr: ast::Expr) -> ast::Expr { + loop { + let Some(parent) = expr.syntax().parent() else { break }; + if matches!(parent.kind(), SyntaxKind::PAREN_EXPR | SyntaxKind::REF_EXPR) { + expr = ast::Expr::cast(parent).unwrap(); + continue; + } + if let Some(stmt_list) = ast::StmtList::cast(parent) { + if let Some(block) = stmt_list.syntax().parent().and_then(ast::BlockExpr::cast) { + expr = ast::Expr::BlockExpr(block); + continue; + } + } + break; + } + expr +} + +// FIXME: +// Somehow handle the case of `let Struct { field, .. } = capture`. +// Replacing `capture` with `capture_field` won't work. +fn expr_of_pat(pat: ast::Pat) -> Option { + 'find_expr: { + for ancestor in pat.syntax().ancestors() { + if let Some(let_stmt) = ast::LetStmt::cast(ancestor.clone()) { + break 'find_expr let_stmt.initializer(); + } + if ast::MatchArm::can_cast(ancestor.kind()) { + if let Some(match_) = + ancestor.parent().and_then(|it| it.parent()).and_then(ast::MatchExpr::cast) + { + break 'find_expr match_.expr(); + } + } + if ast::ExprStmt::can_cast(ancestor.kind()) { + break; + } + } + None + } +} + +#[cfg(test)] +mod tests { + use crate::tests::{check_assist, check_assist_not_applicable}; + + use super::*; + + #[test] + fn handles_unique_captures() { + check_assist( + convert_closure_to_fn, + r#" +//- minicore:copy +fn main() { + let s = &mut true; + let closure = |$0| { *s = false; }; + closure(); +} +"#, + r#" +fn main() { + let s = &mut true; + fn closure(s: &mut bool) { *s = false; } + closure(s); +} +"#, + ); + } + + #[test] + fn multiple_capture_usages() { + check_assist( + convert_closure_to_fn, + r#" +//- minicore:copy +struct A { a: i32, b: bool } +fn main() { + let mut a = A { a: 123, b: false }; + let closure = |$0| { + let b = a.b; + a = A { a: 456, b: true }; + }; + closure(); +} +"#, + r#" +struct A { a: i32, b: bool } +fn main() { + let mut a = A { a: 123, b: false }; + fn closure(a: &mut A) { + let b = a.b; + *a = A { a: 456, b: true }; + } + closure(&mut a); +} +"#, + ); + } + + #[test] + fn changes_names_of_place() { + check_assist( + convert_closure_to_fn, + r#" +//- minicore:copy +struct A { b: &'static B, c: i32 } +struct B(bool, i32); +struct C; +impl C { + fn foo(&self) { + let a = A { b: &B(false, 0), c: 123 }; + let closure = |$0| { + let b = a.b.1; + let c = &*self; + }; + closure(); + } +} +"#, + r#" +struct A { b: &'static B, c: i32 } +struct B(bool, i32); +struct C; +impl C { + fn foo(&self) { + let a = A { b: &B(false, 0), c: 123 }; + fn closure(this: &C, a_b_1: &i32) { + let b = *a_b_1; + let c = this; + } + closure(self, &a.b.1); + } +} +"#, + ); + } + + #[test] + fn self_with_fields_does_not_change_to_this() { + check_assist( + convert_closure_to_fn, + r#" +//- minicore:copy +struct A { b: &'static B, c: i32 } +struct B(bool, i32); +impl A { + fn foo(&self) { + let closure = |$0| { + let b = self.b.1; + }; + closure(); + } +} +"#, + r#" +struct A { b: &'static B, c: i32 } +struct B(bool, i32); +impl A { + fn foo(&self) { + fn closure(self_b_1: &i32) { + let b = *self_b_1; + } + closure(&self.b.1); + } +} +"#, + ); + } + + #[test] + fn replaces_async_closure_with_async_fn() { + check_assist( + convert_closure_to_fn, + r#" +//- minicore: copy, future +fn foo(&self) { + let closure = async |$0| 1; + closure(); +} +"#, + r#" +fn foo(&self) { + async fn closure() -> i32 { + 1 + } + closure(); +} +"#, + ); + } + + #[test] + fn replaces_async_block_with_async_fn() { + check_assist( + convert_closure_to_fn, + r#" +//- minicore: copy, future +fn foo() { + let closure = |$0| async { 1 }; + closure(); +} +"#, + r#" +fn foo() { + async fn closure() -> i32 { 1 } + closure(); +} +"#, + ); + } + + #[test] + #[ignore = "FIXME: we do not do type inference for gen blocks yet"] + fn replaces_gen_block_with_gen_fn() { + check_assist( + convert_closure_to_fn, + r#" +//- minicore: copy, iterator +//- /lib.rs edition:2024 +fn foo() { + let closure = |$0| gen { + yield 1; + }; + closure(); +} +"#, + r#" +fn foo() { + gen fn closure() -> i32 { + yield 1; + } + closure(); +} +"#, + ); + } + + #[test] + fn leaves_block_in_place() { + check_assist( + convert_closure_to_fn, + r#" +//- minicore: copy +fn foo() { + let closure = |$0| {}; + closure(); +} +"#, + r#" +fn foo() { + fn closure() {} + closure(); +} +"#, + ); + } + + #[test] + fn wraps_in_block_if_needed() { + check_assist( + convert_closure_to_fn, + r#" +//- minicore: copy +fn foo() { + let a = 1; + let closure = |$0| a; + closure(); +} +"#, + r#" +fn foo() { + let a = 1; + fn closure(a: &i32) -> i32 { + *a + } + closure(&a); +} +"#, + ); + check_assist( + convert_closure_to_fn, + r#" +//- minicore: copy +fn foo() { + let closure = |$0| 'label: {}; + closure(); +} +"#, + r#" +fn foo() { + fn closure() { + 'label: {} + } + closure(); +} +"#, + ); + check_assist( + convert_closure_to_fn, + r#" +//- minicore: copy +fn foo() { + let closure = |$0| { + const { () } + }; + closure(); +} +"#, + r#" +fn foo() { + fn closure() { + const { () } + } + closure(); +} +"#, + ); + check_assist( + convert_closure_to_fn, + r#" +//- minicore: copy +fn foo() { + let closure = |$0| unsafe { }; + closure(); +} +"#, + r#" +fn foo() { + fn closure() { + unsafe { } + } + closure(); +} +"#, + ); + } + + #[test] + fn closure_in_closure() { + check_assist( + convert_closure_to_fn, + r#" +//- minicore: copy +fn foo() { + let a = 1; + || |$0| { let b = &a; }; +} +"#, + r#" +fn foo() { + let a = 1; + || { fn fun_name(a: &i32) { let b = a; } fun_name }; +} +"#, + ); + } + + #[test] + fn closure_in_block() { + check_assist( + convert_closure_to_fn, + r#" +//- minicore: copy +fn foo() { + { + let a = 1; + |$0| { let b = &a; } + }; +} +"#, + r#" +fn foo() { + { + let a = 1; + fn fun_name(a: &i32) { let b = a; } + fun_name + }; +} +"#, + ); + } + + #[test] + fn finds_pat_for_expr() { + check_assist( + convert_closure_to_fn, + r#" +//- minicore: copy +struct A { b: B } +struct B(bool, i32); +fn foo() { + let mut a = A { b: B(true, 0) }; + let closure = |$0| { + let A { b: B(_, ref mut c) } = a; + }; + closure(); +} +"#, + r#" +struct A { b: B } +struct B(bool, i32); +fn foo() { + let mut a = A { b: B(true, 0) }; + fn closure(a_b_1: &mut i32) { + let A { b: B(_, ref mut c) } = a_b_1; + } + closure(&mut a.b.1); +} +"#, + ); + } + + #[test] + fn with_existing_params() { + check_assist( + convert_closure_to_fn, + r#" +//- minicore: copy +fn foo() { + let (mut a, b) = (0.1, "abc"); + let closure = |$0p1: i32, p2: &mut bool| { + a = 1.2; + let c = b; + }; + closure(0, &mut false); +} +"#, + r#" +fn foo() { + let (mut a, b) = (0.1, "abc"); + fn closure(p1: i32, p2: &mut bool, a: &mut f64, b: &&str) { + *a = 1.2; + let c = *b; + } + closure(0, &mut false, &mut a, &b); +} +"#, + ); + } + + #[test] + fn with_existing_params_newlines() { + check_assist( + convert_closure_to_fn, + r#" +//- minicore: copy +fn foo() { + let (mut a, b) = (0.1, "abc"); + let closure = |$0p1: i32, p2| { + let _: &mut bool = p2; + a = 1.2; + let c = b; + }; + closure( + 0, + &mut false + ); +} +"#, + r#" +fn foo() { + let (mut a, b) = (0.1, "abc"); + fn closure(p1: i32, p2: &mut bool, a: &mut f64, b: &&str) { + let _: &mut bool = p2; + *a = 1.2; + let c = *b; + } + closure( + 0, + &mut false, + &mut a, + &b + ); +} +"#, + ); + } + + #[test] + fn with_existing_params_trailing_comma() { + check_assist( + convert_closure_to_fn, + r#" +//- minicore: copy +fn foo() { + let (mut a, b) = (0.1, "abc"); + let closure = |$0p1: i32, p2| { + let _: &mut bool = p2; + a = 1.2; + let c = b; + }; + closure( + 0, + &mut false, + ); +} +"#, + r#" +fn foo() { + let (mut a, b) = (0.1, "abc"); + fn closure(p1: i32, p2: &mut bool, a: &mut f64, b: &&str) { + let _: &mut bool = p2; + *a = 1.2; + let c = *b; + } + closure( + 0, + &mut false, + &mut a, + &b, + ); +} +"#, + ); + } + + #[test] + fn closure_using_generic_params() { + check_assist( + convert_closure_to_fn, + r#" +//- minicore: copy +struct Foo(A, B); +impl, const C: usize> Foo { + fn foo(a: A, b: D) + where + E: From, + { + let closure = |$0c: F| { + let a = B::from(a); + let b = E::from(b); + }; + } +} +"#, + r#" +struct Foo(A, B); +impl, const C: usize> Foo { + fn foo(a: A, b: D) + where + E: From, + { + fn closure, D, E, F>(c: F, a: A, b: D) where E: From { + let a = B::from(a); + let b = E::from(b); + } + } +} +"#, + ); + } + + #[test] + fn closure_in_stmt() { + check_assist( + convert_closure_to_fn, + r#" +//- minicore: copy +fn bar(_: impl FnOnce() -> i32) {} +fn foo() { + let a = 123; + bar(|$0| a); +} +"#, + r#" +fn bar(_: impl FnOnce() -> i32) {} +fn foo() { + let a = 123; + fn fun_name(a: &i32) -> i32 { + *a + } + bar(fun_name); +} +"#, + ); + } + + #[test] + fn unique_and_imm() { + check_assist( + convert_closure_to_fn, + r#" +//- minicore:copy +fn main() { + let a = &mut true; + let closure = |$0| { + let b = &a; + *a = false; + }; + closure(); +} +"#, + r#" +fn main() { + let a = &mut true; + fn closure(a: &mut &mut bool) { + let b = a; + **a = false; + } + closure(&mut a); +} +"#, + ); + } + + #[test] + fn only_applicable_in_param_list() { + check_assist_not_applicable( + convert_closure_to_fn, + r#" +//- minicore:copy +fn main() { + let closure = || { $0 }; +} +"#, + ); + check_assist_not_applicable( + convert_closure_to_fn, + r#" +//- minicore:copy +fn main() { + let $0closure = || { }; +} +"#, + ); + } +} diff --git a/crates/ide-assists/src/handlers/convert_into_to_from.rs b/crates/ide-assists/src/handlers/convert_into_to_from.rs index 5aa94590e6..8c59ef4314 100644 --- a/crates/ide-assists/src/handlers/convert_into_to_from.rs +++ b/crates/ide-assists/src/handlers/convert_into_to_from.rs @@ -51,7 +51,10 @@ pub(crate) fn convert_into_to_from(acc: &mut Assists, ctx: &AssistContext<'_>) - Some(hir::PathResolution::Def(module_def)) => module_def, _ => return None, }; - mod_path_to_ast(&module.find_path(ctx.db(), src_type_def, cfg)?) + mod_path_to_ast( + &module.find_path(ctx.db(), src_type_def, cfg)?, + module.krate().edition(ctx.db()), + ) }; let dest_type = match &ast_trait { diff --git a/crates/ide-assists/src/handlers/convert_iter_for_each_to_for.rs b/crates/ide-assists/src/handlers/convert_iter_for_each_to_for.rs index e86ff0dbeb..3c9a917410 100644 --- a/crates/ide-assists/src/handlers/convert_iter_for_each_to_for.rs +++ b/crates/ide-assists/src/handlers/convert_iter_for_each_to_for.rs @@ -114,12 +114,16 @@ pub(crate) fn convert_for_loop_with_for_each( |builder| { let mut buf = String::new(); - if let Some((expr_behind_ref, method)) = + if let Some((expr_behind_ref, method, krate)) = is_ref_and_impls_iter_method(&ctx.sema, &iterable) { // We have either "for x in &col" and col implements a method called iter // or "for x in &mut col" and col implements a method called iter_mut - format_to!(buf, "{expr_behind_ref}.{}()", method.display(ctx.db())); + format_to!( + buf, + "{expr_behind_ref}.{}()", + method.display(ctx.db(), krate.edition(ctx.db())) + ); } else if let ast::Expr::RangeExpr(..) = iterable { // range expressions need to be parenthesized for the syntax to be correct format_to!(buf, "({iterable})"); @@ -144,7 +148,7 @@ pub(crate) fn convert_for_loop_with_for_each( fn is_ref_and_impls_iter_method( sema: &hir::Semantics<'_, ide_db::RootDatabase>, iterable: &ast::Expr, -) -> Option<(ast::Expr, hir::Name)> { +) -> Option<(ast::Expr, hir::Name, hir::Crate)> { let ref_expr = match iterable { ast::Expr::RefExpr(r) => r, _ => return None, @@ -172,7 +176,7 @@ fn is_ref_and_impls_iter_method( return None; } - Some((expr_behind_ref, wanted_method)) + Some((expr_behind_ref, wanted_method, krate)) } /// Whether iterable implements core::Iterator diff --git a/crates/ide-assists/src/handlers/convert_named_struct_to_tuple_struct.rs b/crates/ide-assists/src/handlers/convert_named_struct_to_tuple_struct.rs index 3705597927..8d4ff84084 100644 --- a/crates/ide-assists/src/handlers/convert_named_struct_to_tuple_struct.rs +++ b/crates/ide-assists/src/handlers/convert_named_struct_to_tuple_struct.rs @@ -2,8 +2,8 @@ use either::Either; use ide_db::{defs::Definition, search::FileReference}; use itertools::Itertools; use syntax::{ - ast::{self, AstNode, HasGenericParams, HasVisibility}, - match_ast, SyntaxKind, + ast::{self, AstNode, HasAttrs, HasGenericParams, HasVisibility}, + match_ast, ted, SyntaxKind, }; use crate::{assist_context::SourceChangeBuilder, AssistContext, AssistId, AssistKind, Assists}; @@ -87,9 +87,14 @@ fn edit_struct_def( ) { // Note that we don't need to consider macro files in this function because this is // currently not triggered for struct definitions inside macro calls. - let tuple_fields = record_fields - .fields() - .filter_map(|f| Some(ast::make::tuple_field(f.visibility(), f.ty()?))); + let tuple_fields = record_fields.fields().filter_map(|f| { + let field = ast::make::tuple_field(f.visibility(), f.ty()?).clone_for_update(); + ted::insert_all( + ted::Position::first_child_of(field.syntax()), + f.attrs().map(|attr| attr.syntax().clone_subtree().clone_for_update().into()).collect(), + ); + Some(field) + }); let tuple_fields = ast::make::tuple_field_list(tuple_fields); let record_fields_text_range = record_fields.syntax().text_range(); @@ -975,6 +980,22 @@ impl HasAssoc for Struct { let Self::Assoc { value } = a; } } +"#, + ); + } + + #[test] + fn fields_with_attrs() { + check_assist( + convert_named_struct_to_tuple_struct, + r#" +pub struct $0Foo { + #[my_custom_attr] + value: u32, +} +"#, + r#" +pub struct Foo(#[my_custom_attr] u32); "#, ); } diff --git a/crates/ide-assists/src/handlers/convert_tuple_return_type_to_struct.rs b/crates/ide-assists/src/handlers/convert_tuple_return_type_to_struct.rs index 0f0b4442d8..91af9b05bb 100644 --- a/crates/ide-assists/src/handlers/convert_tuple_return_type_to_struct.rs +++ b/crates/ide-assists/src/handlers/convert_tuple_return_type_to_struct.rs @@ -211,7 +211,7 @@ fn augment_references_with_imports( ) .map(|mod_path| { make::path_concat( - mod_path_to_ast(&mod_path), + mod_path_to_ast(&mod_path, target_module.krate().edition(ctx.db())), make::path_from_text(struct_name), ) }); diff --git a/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs b/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs index c72bd411d6..f01b4ea0fd 100644 --- a/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs +++ b/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs @@ -1,8 +1,8 @@ use either::Either; use ide_db::defs::{Definition, NameRefClass}; use syntax::{ - ast::{self, AstNode, HasGenericParams, HasVisibility}, - match_ast, SyntaxKind, SyntaxNode, + ast::{self, AstNode, HasAttrs, HasGenericParams, HasVisibility}, + match_ast, ted, SyntaxKind, SyntaxNode, }; use crate::{assist_context::SourceChangeBuilder, AssistContext, AssistId, AssistKind, Assists}; @@ -83,10 +83,14 @@ fn edit_struct_def( tuple_fields: ast::TupleFieldList, names: Vec, ) { - let record_fields = tuple_fields - .fields() - .zip(names) - .filter_map(|(f, name)| Some(ast::make::record_field(f.visibility(), name, f.ty()?))); + let record_fields = tuple_fields.fields().zip(names).filter_map(|(f, name)| { + let field = ast::make::record_field(f.visibility(), name, f.ty()?).clone_for_update(); + ted::insert_all( + ted::Position::first_child_of(field.syntax()), + f.attrs().map(|attr| attr.syntax().clone_subtree().clone_for_update().into()).collect(), + ); + Some(field) + }); let record_fields = ast::make::record_field_list(record_fields); let tuple_fields_text_range = tuple_fields.syntax().text_range(); @@ -904,6 +908,19 @@ where T: Foo, { pub field1: T } +"#, + ); + } + + #[test] + fn fields_with_attrs() { + check_assist( + convert_tuple_struct_to_named_struct, + r#" +pub struct $0Foo(#[my_custom_attr] u32); +"#, + r#" +pub struct Foo { #[my_custom_attr] field1: u32 } "#, ); } diff --git a/crates/ide-assists/src/handlers/convert_while_to_loop.rs b/crates/ide-assists/src/handlers/convert_while_to_loop.rs index c34b684112..434daa279c 100644 --- a/crates/ide-assists/src/handlers/convert_while_to_loop.rs +++ b/crates/ide-assists/src/handlers/convert_while_to_loop.rs @@ -1,5 +1,6 @@ -use std::iter::once; +use std::iter; +use either::Either; use ide_db::syntax_helpers::node_ext::is_pattern_cond; use syntax::{ ast::{ @@ -52,18 +53,30 @@ pub(crate) fn convert_while_to_loop(acc: &mut Assists, ctx: &AssistContext<'_>) |edit| { let while_indent_level = IndentLevel::from_node(while_expr.syntax()); - let break_block = - make::block_expr(once(make::expr_stmt(make::expr_break(None, None)).into()), None) - .indent(while_indent_level); + let break_block = make::block_expr( + iter::once(make::expr_stmt(make::expr_break(None, None)).into()), + None, + ) + .indent(while_indent_level); let block_expr = if is_pattern_cond(while_cond.clone()) { let if_expr = make::expr_if(while_cond, while_body, Some(break_block.into())); - let stmts = once(make::expr_stmt(if_expr).into()); + let stmts = iter::once(make::expr_stmt(if_expr).into()); make::block_expr(stmts, None) } else { let if_cond = invert_boolean_expression(while_cond); - let if_expr = make::expr_if(if_cond, break_block, None); - let stmts = once(make::expr_stmt(if_expr).into()).chain(while_body.statements()); - make::block_expr(stmts, while_body.tail_expr()) + let if_expr = make::expr_if(if_cond, break_block, None).syntax().clone().into(); + let elements = while_body.stmt_list().map_or_else( + || Either::Left(iter::empty()), + |stmts| { + Either::Right(stmts.syntax().children_with_tokens().filter(|node_or_tok| { + // Filter out the trailing expr + !node_or_tok + .as_node() + .is_some_and(|node| ast::Expr::can_cast(node.kind())) + })) + }, + ); + make::hacky_block_expr(iter::once(if_expr).chain(elements), while_body.tail_expr()) }; let replacement = make::expr_loop(block_expr.indent(while_indent_level)); @@ -182,6 +195,74 @@ fn main() { bar(); } } +"#, + ); + } + + #[test] + fn preserve_comments() { + check_assist( + convert_while_to_loop, + r#" +fn main() { + let mut i = 0; + + $0while i < 5 { + // comment 1 + dbg!(i); + // comment 2 + i += 1; + // comment 3 + } +} +"#, + r#" +fn main() { + let mut i = 0; + + loop { + if i >= 5 { + break; + } + // comment 1 + dbg!(i); + // comment 2 + i += 1; + // comment 3 + } +} +"#, + ); + + check_assist( + convert_while_to_loop, + r#" +fn main() { + let v = vec![1, 2, 3]; + let iter = v.iter(); + + $0while let Some(i) = iter.next() { + // comment 1 + dbg!(i); + // comment 2 + } +} +"#, + r#" +fn main() { + let v = vec![1, 2, 3]; + let iter = v.iter(); + + loop { + if let Some(i) = iter.next() { + // comment 1 + dbg!(i); + // comment 2 + } else { + break; + } + } +} "#, ); } diff --git a/crates/ide-assists/src/handlers/destructure_struct_binding.rs b/crates/ide-assists/src/handlers/destructure_struct_binding.rs index 095b8f958d..b229b750e8 100644 --- a/crates/ide-assists/src/handlers/destructure_struct_binding.rs +++ b/crates/ide-assists/src/handlers/destructure_struct_binding.rs @@ -7,7 +7,7 @@ use ide_db::{ FxHashMap, FxHashSet, }; use itertools::Itertools; -use syntax::{ast, ted, AstNode, SmolStr, SyntaxNode, ToSmolStr}; +use syntax::{ast, ted, AstNode, Edition, SmolStr, SyntaxNode, ToSmolStr}; use text_edit::TextRange; use crate::{ @@ -81,6 +81,7 @@ struct StructEditData { has_private_members: bool, is_nested: bool, is_ref: bool, + edition: Edition, } fn collect_data(ident_pat: ast::IdentPat, ctx: &AssistContext<'_>) -> Option { @@ -145,6 +146,7 @@ fn collect_data(ident_pat: ast::IdentPat, ctx: &AssistContext<'_>) -> Option AssignmentEdit { let ident_pat = builder.make_mut(data.ident_pat.clone()); - let struct_path = mod_path_to_ast(&data.struct_def_path); + let struct_path = mod_path_to_ast(&data.struct_def_path, data.edition); let is_ref = ident_pat.ref_token().is_some(); let is_mut = ident_pat.mut_token().is_some(); @@ -247,7 +249,7 @@ fn generate_field_names(ctx: &AssistContext<'_>, data: &StructEditData) -> Vec<( .visible_fields .iter() .map(|field| { - let field_name = field.name(ctx.db()).display_no_db().to_smolstr(); + let field_name = field.name(ctx.db()).display_no_db(data.edition).to_smolstr(); let new_name = new_field_name(field_name.clone(), &data.names_in_scope); (field_name, new_name) }) diff --git a/crates/ide-assists/src/handlers/expand_glob_import.rs b/crates/ide-assists/src/handlers/expand_glob_import.rs index 9beb616d99..3d6d37ad93 100644 --- a/crates/ide-assists/src/handlers/expand_glob_import.rs +++ b/crates/ide-assists/src/handlers/expand_glob_import.rs @@ -66,7 +66,9 @@ pub(crate) fn expand_glob_import(acc: &mut Assists, ctx: &AssistContext<'_>) -> let names_to_import = find_names_to_import(ctx, refs_in_target, imported_defs); let expanded = make::use_tree_list(names_to_import.iter().map(|n| { - let path = make::ext::ident_path(&n.display(ctx.db()).to_string()); + let path = make::ext::ident_path( + &n.display(ctx.db(), current_module.krate().edition(ctx.db())).to_string(), + ); make::use_tree(path, None, None, false) })) .clone_for_update(); diff --git a/crates/ide-assists/src/handlers/extract_expressions_from_format_string.rs b/crates/ide-assists/src/handlers/extract_expressions_from_format_string.rs index 9180d8dfcb..e4d347ef16 100644 --- a/crates/ide-assists/src/handlers/extract_expressions_from_format_string.rs +++ b/crates/ide-assists/src/handlers/extract_expressions_from_format_string.rs @@ -1,11 +1,7 @@ use crate::{utils, AssistContext, Assists}; -use hir::DescendPreference; use ide_db::{ assists::{AssistId, AssistKind}, - syntax_helpers::{ - format_string::is_format_string, - format_string_exprs::{parse_format_exprs, Arg}, - }, + syntax_helpers::format_string_exprs::{parse_format_exprs, Arg}, }; use itertools::Itertools; use syntax::{ @@ -40,13 +36,7 @@ pub(crate) fn extract_expressions_from_format_string( let tt = fmt_string.syntax().parent().and_then(ast::TokenTree::cast)?; let tt_delimiter = tt.left_delimiter_token()?.kind(); - let expanded_t = ast::String::cast( - ctx.sema - .descend_into_macros_single(DescendPreference::SameKind, fmt_string.syntax().clone()), - )?; - if !is_format_string(&expanded_t) { - return None; - } + let _ = ctx.sema.as_format_args_parts(&fmt_string)?; let (new_fmt, extracted_args) = parse_format_exprs(fmt_string.text()).ok()?; if extracted_args.is_empty() { diff --git a/crates/ide-assists/src/handlers/extract_function.rs b/crates/ide-assists/src/handlers/extract_function.rs index 0a2cb6d5ef..ad0be89645 100644 --- a/crates/ide-assists/src/handlers/extract_function.rs +++ b/crates/ide-assists/src/handlers/extract_function.rs @@ -3,8 +3,8 @@ use std::{iter, ops::RangeInclusive}; use ast::make; use either::Either; use hir::{ - DescendPreference, HasSource, HirDisplay, InFile, Local, LocalSource, ModuleDef, - PathResolution, Semantics, TypeInfo, TypeParam, + HasSource, HirDisplay, InFile, Local, LocalSource, ModuleDef, PathResolution, Semantics, + TypeInfo, TypeParam, }; use ide_db::{ defs::{Definition, NameRefClass}, @@ -23,7 +23,7 @@ use syntax::{ self, edit::IndentLevel, edit_in_place::Indent, AstNode, AstToken, HasGenericParams, HasName, }, - match_ast, ted, SyntaxElement, + match_ast, ted, Edition, SyntaxElement, SyntaxKind::{self, COMMENT}, SyntaxNode, SyntaxToken, TextRange, TextSize, TokenAtOffset, WalkEvent, T, }; @@ -84,7 +84,6 @@ pub(crate) fn extract_function(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op }; let body = extraction_target(&node, range)?; - let (container_info, contains_tail_expr) = body.analyze_container(&ctx.sema)?; let (locals_used, self_param) = body.analyze(&ctx.sema); @@ -92,6 +91,9 @@ pub(crate) fn extract_function(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op let insert_after = node_to_insert_after(&body, anchor)?; let semantics_scope = ctx.sema.scope(&insert_after)?; let module = semantics_scope.module(); + let edition = semantics_scope.krate().edition(ctx.db()); + + let (container_info, contains_tail_expr) = body.analyze_container(&ctx.sema, edition)?; let ret_ty = body.return_ty(ctx)?; let control_flow = body.external_control_flow(ctx, &container_info)?; @@ -217,7 +219,11 @@ pub(crate) fn extract_function(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op ); if let Some(mod_path) = mod_path { - insert_use(&scope, mod_path_to_ast(&mod_path), &ctx.config.insert_use); + insert_use( + &scope, + mod_path_to_ast(&mod_path, edition), + &ctx.config.insert_use, + ); } } } @@ -238,7 +244,13 @@ pub(crate) fn extract_function(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op fn make_function_name(semantics_scope: &hir::SemanticsScope<'_>) -> ast::NameRef { let mut names_in_scope = vec![]; semantics_scope.process_all_names(&mut |name, _| { - names_in_scope.push(name.display(semantics_scope.db.upcast()).to_string()) + names_in_scope.push( + name.display( + semantics_scope.db.upcast(), + semantics_scope.krate().edition(semantics_scope.db), + ) + .to_string(), + ) }); let default_name = "fun_name"; @@ -366,6 +378,7 @@ struct ContainerInfo { ret_type: Option, generic_param_lists: Vec, where_clauses: Vec, + edition: Edition, } /// Control flow that is exported from extracted function @@ -489,8 +502,8 @@ impl Param { } } - fn to_arg(&self, ctx: &AssistContext<'_>) -> ast::Expr { - let var = path_expr_from_local(ctx, self.var); + fn to_arg(&self, ctx: &AssistContext<'_>, edition: Edition) -> ast::Expr { + let var = path_expr_from_local(ctx, self.var, edition); match self.kind() { ParamKind::Value | ParamKind::MutValue => var, ParamKind::SharedRef => make::expr_ref(var, false), @@ -498,8 +511,13 @@ impl Param { } } - fn to_param(&self, ctx: &AssistContext<'_>, module: hir::Module) -> ast::Param { - let var = self.var.name(ctx.db()).display(ctx.db()).to_string(); + fn to_param( + &self, + ctx: &AssistContext<'_>, + module: hir::Module, + edition: Edition, + ) -> ast::Param { + let var = self.var.name(ctx.db()).display(ctx.db(), edition).to_string(); let var_name = make::name(&var); let pat = match self.kind() { ParamKind::MutValue => make::ident_pat(false, true, var_name), @@ -520,7 +538,7 @@ impl Param { } impl TryKind { - fn of_ty(ty: hir::Type, ctx: &AssistContext<'_>) -> Option { + fn of_ty(ty: hir::Type, ctx: &AssistContext<'_>, edition: Edition) -> Option { if ty.is_unknown() { // We favour Result for `expr?` return Some(TryKind::Result { ty }); @@ -529,7 +547,7 @@ impl TryKind { let name = adt.name(ctx.db()); // FIXME: use lang items to determine if it is std type or user defined // E.g. if user happens to define type named `Option`, we would have false positive - let name = &name.display(ctx.db()).to_string(); + let name = &name.display(ctx.db(), edition).to_string(); match name.as_str() { "Option" => Some(TryKind::Option), "Result" => Some(TryKind::Result { ty }), @@ -816,7 +834,7 @@ impl FunctionBody { .descendants_with_tokens() .filter_map(SyntaxElement::into_token) .filter(|it| matches!(it.kind(), SyntaxKind::IDENT | T![self])) - .flat_map(|t| sema.descend_into_macros(DescendPreference::None, t)) + .flat_map(|t| sema.descend_into_macros_exact(t)) .for_each(|t| add_name_if_local(t.parent().and_then(ast::NameRef::cast))); } } @@ -828,6 +846,7 @@ impl FunctionBody { fn analyze_container( &self, sema: &Semantics<'_, RootDatabase>, + edition: Edition, ) -> Option<(ContainerInfo, bool)> { let mut ancestors = self.parent()?.ancestors(); let infer_expr_opt = |expr| sema.type_of_expr(&expr?).map(TypeInfo::adjusted); @@ -927,6 +946,7 @@ impl FunctionBody { ret_type: ty, generic_param_lists, where_clauses, + edition, }, contains_tail_expr, )) @@ -1015,7 +1035,7 @@ impl FunctionBody { let kind = match (try_expr, ret_expr, break_expr, continue_expr) { (Some(_), _, None, None) => { let ret_ty = container_info.ret_type.clone()?; - let kind = TryKind::of_ty(ret_ty, ctx)?; + let kind = TryKind::of_ty(ret_ty, ctx, container_info.edition)?; Some(FlowKind::Try { kind }) } @@ -1397,7 +1417,7 @@ fn fixup_call_site(builder: &mut SourceChangeBuilder, body: &FunctionBody) { fn make_call(ctx: &AssistContext<'_>, fun: &Function, indent: IndentLevel) -> SyntaxNode { let ret_ty = fun.return_type(ctx); - let args = make::arg_list(fun.params.iter().map(|param| param.to_arg(ctx))); + let args = make::arg_list(fun.params.iter().map(|param| param.to_arg(ctx, fun.mods.edition))); let name = fun.name.clone(); let mut call_expr = if fun.self_param.is_some() { let self_arg = make::expr_path(make::ext::ident_path("self")); @@ -1420,13 +1440,13 @@ fn make_call(ctx: &AssistContext<'_>, fun: &Function, indent: IndentLevel) -> Sy [] => None, [var] => { let name = var.local.name(ctx.db()); - let name = make::name(&name.display(ctx.db()).to_string()); + let name = make::name(&name.display(ctx.db(), fun.mods.edition).to_string()); Some(ast::Pat::IdentPat(make::ident_pat(false, var.mut_usage_outside_body, name))) } vars => { let binding_pats = vars.iter().map(|var| { let name = var.local.name(ctx.db()); - let name = make::name(&name.display(ctx.db()).to_string()); + let name = make::name(&name.display(ctx.db(), fun.mods.edition).to_string()); make::ident_pat(false, var.mut_usage_outside_body, name).into() }); Some(ast::Pat::TuplePat(make::tuple_pat(binding_pats))) @@ -1569,8 +1589,8 @@ impl FlowHandler { } } -fn path_expr_from_local(ctx: &AssistContext<'_>, var: Local) -> ast::Expr { - let name = var.name(ctx.db()).display(ctx.db()).to_string(); +fn path_expr_from_local(ctx: &AssistContext<'_>, var: Local, edition: Edition) -> ast::Expr { + let name = var.name(ctx.db()).display(ctx.db(), edition).to_string(); make::expr_path(make::ext::ident_path(&name)) } @@ -1581,7 +1601,7 @@ fn format_function( old_indent: IndentLevel, ) -> ast::Fn { let fun_name = make::name(&fun.name.text()); - let params = fun.make_param_list(ctx, module); + let params = fun.make_param_list(ctx, module, fun.mods.edition); let ret_ty = fun.make_ret_ty(ctx, module); let body = make_body(ctx, old_indent, fun); let (generic_params, where_clause) = make_generic_params_and_where_clause(ctx, fun); @@ -1597,6 +1617,7 @@ fn format_function( fun.control_flow.is_async, fun.mods.is_const, fun.control_flow.is_unsafe, + false, ) } @@ -1707,9 +1728,14 @@ impl Function { type_params_in_descendant_paths.chain(type_params_in_params).collect() } - fn make_param_list(&self, ctx: &AssistContext<'_>, module: hir::Module) -> ast::ParamList { + fn make_param_list( + &self, + ctx: &AssistContext<'_>, + module: hir::Module, + edition: Edition, + ) -> ast::ParamList { let self_param = self.self_param.clone(); - let params = self.params.iter().map(|param| param.to_param(ctx, module)); + let params = self.params.iter().map(|param| param.to_param(ctx, module, edition)); make::param_list(self_param, params) } @@ -1842,10 +1868,12 @@ fn make_body(ctx: &AssistContext<'_>, old_indent: IndentLevel, fun: &Function) - None => match fun.outliving_locals.as_slice() { [] => {} [var] => { - tail_expr = Some(path_expr_from_local(ctx, var.local)); + tail_expr = Some(path_expr_from_local(ctx, var.local, fun.mods.edition)); } vars => { - let exprs = vars.iter().map(|var| path_expr_from_local(ctx, var.local)); + let exprs = vars + .iter() + .map(|var| path_expr_from_local(ctx, var.local, fun.mods.edition)); let expr = make::expr_tuple(exprs); tail_expr = Some(expr); } @@ -5622,7 +5650,7 @@ fn func(i: Struct<'_, T>) { fun_name(i); } -fn $0fun_name(i: Struct) { +fn $0fun_name(i: Struct<'_, T>) { foo(i); } "#, diff --git a/crates/ide-assists/src/handlers/extract_struct_from_enum_variant.rs b/crates/ide-assists/src/handlers/extract_struct_from_enum_variant.rs index a62fdeb617..615b5d3f98 100644 --- a/crates/ide-assists/src/handlers/extract_struct_from_enum_variant.rs +++ b/crates/ide-assists/src/handlers/extract_struct_from_enum_variant.rs @@ -1,7 +1,7 @@ use std::iter; use either::Either; -use hir::{Module, ModuleDef, Name, Variant}; +use hir::{HasCrate, Module, ModuleDef, Name, Variant}; use ide_db::{ defs::Definition, helpers::mod_path_to_ast, @@ -16,7 +16,7 @@ use syntax::{ self, edit::IndentLevel, edit_in_place::Indent, make, AstNode, HasAttrs, HasGenericParams, HasName, HasVisibility, }, - match_ast, ted, SyntaxElement, + match_ast, ted, Edition, SyntaxElement, SyntaxKind::*, SyntaxNode, T, }; @@ -58,6 +58,7 @@ pub(crate) fn extract_struct_from_enum_variant( "Extract struct from enum variant", target, |builder| { + let edition = enum_hir.krate(ctx.db()).edition(ctx.db()); let variant_hir_name = variant_hir.name(ctx.db()); let enum_module_def = ModuleDef::from(enum_hir); let usages = Definition::Variant(variant_hir).usages(&ctx.sema).all(); @@ -82,7 +83,7 @@ pub(crate) fn extract_struct_from_enum_variant( references, ); processed.into_iter().for_each(|(path, node, import)| { - apply_references(ctx.config.insert_use, path, node, import) + apply_references(ctx.config.insert_use, path, node, import, edition) }); } builder.edit_file(ctx.file_id()); @@ -98,7 +99,7 @@ pub(crate) fn extract_struct_from_enum_variant( references, ); processed.into_iter().for_each(|(path, node, import)| { - apply_references(ctx.config.insert_use, path, node, import) + apply_references(ctx.config.insert_use, path, node, import, edition) }); } @@ -169,7 +170,7 @@ fn existing_definition(db: &RootDatabase, variant_name: &ast::Name, variant: &Va ), _ => false, }) - .any(|(name, _)| name.display(db).to_string() == variant_name.to_string()) + .any(|(name, _)| name.eq_ident(variant_name.text().as_str())) } fn extract_generic_params( @@ -359,9 +360,10 @@ fn apply_references( segment: ast::PathSegment, node: SyntaxNode, import: Option<(ImportScope, hir::ModPath)>, + edition: Edition, ) { if let Some((scope, path)) = import { - insert_use(&scope, mod_path_to_ast(&path), &insert_use_cfg); + insert_use(&scope, mod_path_to_ast(&path, edition), &insert_use_cfg); } // deep clone to prevent cycle let path = make::path_from_segments(iter::once(segment.clone_subtree()), false); diff --git a/crates/ide-assists/src/handlers/extract_variable.rs b/crates/ide-assists/src/handlers/extract_variable.rs index 0ef71a3866..5ae75bb1ff 100644 --- a/crates/ide-assists/src/handlers/extract_variable.rs +++ b/crates/ide-assists/src/handlers/extract_variable.rs @@ -20,7 +20,7 @@ use crate::{utils::suggest_name, AssistContext, AssistId, AssistKind, Assists}; // -> // ``` // fn main() { -// let $0var_name = (1 + 2); +// let $0var_name = 1 + 2; // var_name * 4; // } // ``` @@ -58,9 +58,30 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op } let parent = to_extract.syntax().parent().and_then(ast::Expr::cast); - let needs_adjust = parent - .as_ref() - .map_or(false, |it| matches!(it, ast::Expr::FieldExpr(_) | ast::Expr::MethodCallExpr(_))); + // Any expression that autoderefs may need adjustment. + let mut needs_adjust = parent.as_ref().map_or(false, |it| match it { + ast::Expr::FieldExpr(_) + | ast::Expr::MethodCallExpr(_) + | ast::Expr::CallExpr(_) + | ast::Expr::AwaitExpr(_) => true, + ast::Expr::IndexExpr(index) if index.base().as_ref() == Some(&to_extract) => true, + _ => false, + }); + let mut to_extract_no_ref = peel_parens(to_extract.clone()); + let needs_ref = needs_adjust + && match &to_extract_no_ref { + ast::Expr::FieldExpr(_) + | ast::Expr::IndexExpr(_) + | ast::Expr::MacroExpr(_) + | ast::Expr::ParenExpr(_) + | ast::Expr::PathExpr(_) => true, + ast::Expr::PrefixExpr(prefix) if prefix.op_kind() == Some(ast::UnaryOp::Deref) => { + to_extract_no_ref = prefix.expr()?; + needs_adjust = false; + false + } + _ => false, + }; let anchor = Anchor::from(&to_extract)?; let target = to_extract.syntax().text_range(); @@ -87,22 +108,28 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op Some(ast::Expr::RefExpr(expr)) if expr.mut_token().is_some() => { make::ident_pat(false, true, make::name(&var_name)) } + _ if needs_adjust + && !needs_ref + && ty.as_ref().is_some_and(|ty| ty.is_mutable_reference()) => + { + make::ident_pat(false, true, make::name(&var_name)) + } _ => make::ident_pat(false, false, make::name(&var_name)), }; - let to_extract = match ty.as_ref().filter(|_| needs_adjust) { + let to_extract_no_ref = match ty.as_ref().filter(|_| needs_ref) { Some(receiver_type) if receiver_type.is_mutable_reference() => { - make::expr_ref(to_extract, true) + make::expr_ref(to_extract_no_ref, true) } Some(receiver_type) if receiver_type.is_reference() => { - make::expr_ref(to_extract, false) + make::expr_ref(to_extract_no_ref, false) } - _ => to_extract, + _ => to_extract_no_ref, }; let expr_replace = edit.make_syntax_mut(expr_replace); let let_stmt = - make::let_stmt(ident_pat.into(), None, Some(to_extract)).clone_for_update(); + make::let_stmt(ident_pat.into(), None, Some(to_extract_no_ref)).clone_for_update(); let name_expr = make::expr_path(make::ext::ident_path(&var_name)).clone_for_update(); match anchor { @@ -202,6 +229,14 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op ) } +fn peel_parens(mut expr: ast::Expr) -> ast::Expr { + while let ast::Expr::ParenExpr(parens) = &expr { + let Some(expr_inside) = parens.expr() else { break }; + expr = expr_inside; + } + expr +} + /// Check whether the node is a valid expression which can be extracted to a variable. /// In general that's true for any expression, but in some cases that would produce invalid code. fn valid_target_expr(node: SyntaxNode) -> Option { @@ -1220,6 +1255,45 @@ fn foo(s: &S) { ); } + #[test] + fn test_extract_var_index_deref() { + check_assist( + extract_variable, + r#" +//- minicore: index +struct X; + +impl std::ops::Index for X { + type Output = i32; + fn index(&self) -> &Self::Output { 0 } +} + +struct S { + sub: X +} + +fn foo(s: &S) { + $0s.sub$0[0]; +}"#, + r#" +struct X; + +impl std::ops::Index for X { + type Output = i32; + fn index(&self) -> &Self::Output { 0 } +} + +struct S { + sub: X +} + +fn foo(s: &S) { + let $0sub = &s.sub; + sub[0]; +}"#, + ); + } + #[test] fn test_extract_var_reference_parameter_deep_nesting() { check_assist( @@ -1461,4 +1535,60 @@ fn foo() { }"#, ); } + + #[test] + fn generates_no_ref_on_calls() { + check_assist( + extract_variable, + r#" +struct S; +impl S { + fn do_work(&mut self) {} +} +fn bar() -> S { S } +fn foo() { + $0bar()$0.do_work(); +}"#, + r#" +struct S; +impl S { + fn do_work(&mut self) {} +} +fn bar() -> S { S } +fn foo() { + let mut $0bar = bar(); + bar.do_work(); +}"#, + ); + } + + #[test] + fn generates_no_ref_for_deref() { + check_assist( + extract_variable, + r#" +struct S; +impl S { + fn do_work(&mut self) {} +} +fn bar() -> S { S } +fn foo() { + let v = &mut &mut bar(); + $0(**v)$0.do_work(); +} +"#, + r#" +struct S; +impl S { + fn do_work(&mut self) {} +} +fn bar() -> S { S } +fn foo() { + let v = &mut &mut bar(); + let $0s = *v; + s.do_work(); +} +"#, + ); + } } diff --git a/crates/ide-assists/src/handlers/fill_record_pattern_fields.rs b/crates/ide-assists/src/handlers/fill_record_pattern_fields.rs index 758f50d3f4..ee32186480 100644 --- a/crates/ide-assists/src/handlers/fill_record_pattern_fields.rs +++ b/crates/ide-assists/src/handlers/fill_record_pattern_fields.rs @@ -45,8 +45,9 @@ pub(crate) fn fill_record_pattern_fields(acc: &mut Assists, ctx: &AssistContext< let new_field_list = make::record_pat_field_list(old_field_list.fields(), None).clone_for_update(); for (f, _) in missing_fields.iter() { + let edition = ctx.sema.scope(record_pat.syntax())?.krate().edition(ctx.db()); let field = make::record_pat_field_shorthand(make::name_ref( - &f.name(ctx.sema.db).display_no_db().to_smolstr(), + &f.name(ctx.sema.db).display_no_db(edition).to_smolstr(), )); new_field_list.add_field(field.clone_for_update()); } diff --git a/crates/ide-assists/src/handlers/fix_visibility.rs b/crates/ide-assists/src/handlers/fix_visibility.rs index 9950f9c147..7a92d8911b 100644 --- a/crates/ide-assists/src/handlers/fix_visibility.rs +++ b/crates/ide-assists/src/handlers/fix_visibility.rs @@ -4,7 +4,7 @@ use hir::{ use ide_db::FileId; use syntax::{ ast::{self, edit_in_place::HasVisibilityEdit, make, HasVisibility as _}, - AstNode, TextRange, ToSmolStr, + AstNode, TextRange, }; use crate::{AssistContext, AssistId, AssistKind, Assists}; @@ -48,7 +48,7 @@ fn add_vis_to_referenced_module_def(acc: &mut Assists, ctx: &AssistContext<'_>) let (_, def) = module .scope(ctx.db(), None) .into_iter() - .find(|(name, _)| name.display_no_db().to_smolstr() == name_ref.text().as_str())?; + .find(|(name, _)| name.eq_ident(name_ref.text().as_str()))?; let ScopeDef::ModuleDef(def) = def else { return None; }; @@ -71,7 +71,10 @@ fn add_vis_to_referenced_module_def(acc: &mut Assists, ctx: &AssistContext<'_>) let assist_label = match target_name { None => format!("Change visibility to {missing_visibility}"), Some(name) => { - format!("Change visibility of {} to {missing_visibility}", name.display(ctx.db())) + format!( + "Change visibility of {} to {missing_visibility}", + name.display(ctx.db(), current_module.krate().edition(ctx.db())) + ) } }; @@ -92,6 +95,7 @@ fn add_vis_to_referenced_record_field(acc: &mut Assists, ctx: &AssistContext<'_> let (record_field_def, _, _) = ctx.sema.resolve_record_field(&record_field)?; let current_module = ctx.sema.scope(record_field.syntax())?.module(); + let current_edition = current_module.krate().edition(ctx.db()); let visibility = record_field_def.visibility(ctx.db()); if visibility.is_visible_from(ctx.db(), current_module.into()) { return None; @@ -123,8 +127,8 @@ fn add_vis_to_referenced_record_field(acc: &mut Assists, ctx: &AssistContext<'_> let target_name = record_field_def.name(ctx.db()); let assist_label = format!( "Change visibility of {}.{} to {missing_visibility}", - parent_name.display(ctx.db()), - target_name.display(ctx.db()) + parent_name.display(ctx.db(), current_edition), + target_name.display(ctx.db(), current_edition) ); acc.add(AssistId("fix_visibility", AssistKind::QuickFix), assist_label, target, |edit| { diff --git a/crates/ide-assists/src/handlers/generate_delegate_methods.rs b/crates/ide-assists/src/handlers/generate_delegate_methods.rs index 2150003bc1..081e36b4ff 100644 --- a/crates/ide-assists/src/handlers/generate_delegate_methods.rs +++ b/crates/ide-assists/src/handlers/generate_delegate_methods.rs @@ -51,6 +51,7 @@ pub(crate) fn generate_delegate_methods(acc: &mut Assists, ctx: &AssistContext<' let strukt = ctx.find_node_at_offset::()?; let strukt_name = strukt.name()?; let current_module = ctx.sema.scope(strukt.syntax())?.module(); + let current_edition = current_module.krate().edition(ctx.db()); let (field_name, field_ty, target) = match ctx.find_node_at_offset::() { Some(field) => { @@ -89,7 +90,7 @@ pub(crate) fn generate_delegate_methods(acc: &mut Assists, ctx: &AssistContext<' methods.sort_by(|(a, _), (b, _)| a.cmp(b)); for (name, method) in methods { let adt = ast::Adt::Struct(strukt.clone()); - let name = name.display(ctx.db()).to_string(); + let name = name.display(ctx.db(), current_edition).to_string(); // if `find_struct_impl` returns None, that means that a function named `name` already exists. let Some(impl_def) = find_struct_impl(ctx, &adt, std::slice::from_ref(&name)) else { continue; @@ -121,6 +122,7 @@ pub(crate) fn generate_delegate_methods(acc: &mut Assists, ctx: &AssistContext<' let is_async = method_source.async_token().is_some(); let is_const = method_source.const_token().is_some(); let is_unsafe = method_source.unsafe_token().is_some(); + let is_gen = method_source.gen_token().is_some(); let fn_name = make::name(&name); @@ -153,6 +155,7 @@ pub(crate) fn generate_delegate_methods(acc: &mut Assists, ctx: &AssistContext<' is_async, is_const, is_unsafe, + is_gen, ) .clone_for_update(); diff --git a/crates/ide-assists/src/handlers/generate_delegate_trait.rs b/crates/ide-assists/src/handlers/generate_delegate_trait.rs index 5a3457e5b7..bf4ce5c907 100644 --- a/crates/ide-assists/src/handlers/generate_delegate_trait.rs +++ b/crates/ide-assists/src/handlers/generate_delegate_trait.rs @@ -22,7 +22,7 @@ use syntax::{ WherePred, }, ted::{self, Position}, - AstNode, NodeOrToken, SmolStr, SyntaxKind, ToSmolStr, + AstNode, Edition, NodeOrToken, SmolStr, SyntaxKind, ToSmolStr, }; // Assist: generate_delegate_trait @@ -109,6 +109,7 @@ struct Field { ty: ast::Type, range: syntax::TextRange, impls: Vec, + edition: Edition, } impl Field { @@ -119,6 +120,7 @@ impl Field { let db = ctx.sema.db; let module = ctx.sema.file_to_module_def(ctx.file_id())?; + let edition = module.krate().edition(ctx.db()); let (name, range, ty) = match f { Either::Left(f) => { @@ -147,7 +149,7 @@ impl Field { } } - Some(Field { name, ty, range, impls }) + Some(Field { name, ty, range, impls, edition }) } } @@ -163,18 +165,18 @@ enum Delegee { } impl Delegee { - fn signature(&self, db: &dyn HirDatabase) -> String { + fn signature(&self, db: &dyn HirDatabase, edition: Edition) -> String { let mut s = String::new(); let (Delegee::Bound(it) | Delegee::Impls(it, _)) = self; for m in it.module(db).path_to_root(db).iter().rev() { if let Some(name) = m.name(db) { - s.push_str(&format!("{}::", name.display_no_db().to_smolstr())); + s.push_str(&format!("{}::", name.display_no_db(edition).to_smolstr())); } } - s.push_str(&it.name(db).display_no_db().to_smolstr()); + s.push_str(&it.name(db).display_no_db(edition).to_smolstr()); s } } @@ -212,9 +214,11 @@ impl Struct { // if self.hir_ty.impls_trait(db, trait_, &[]) { // continue; // } - let signature = delegee.signature(db); + let signature = delegee.signature(db, field.edition); - let Some(delegate) = generate_impl(ctx, self, &field.ty, &field.name, delegee) else { + let Some(delegate) = + generate_impl(ctx, self, &field.ty, &field.name, delegee, field.edition) + else { continue; }; @@ -240,6 +244,7 @@ fn generate_impl( field_ty: &ast::Type, field_name: &str, delegee: &Delegee, + edition: Edition, ) -> Option { let delegate: ast::Impl; let db = ctx.db(); @@ -259,7 +264,7 @@ fn generate_impl( strukt_params.clone(), strukt_params.map(|params| params.to_generic_args()), delegee.is_auto(db), - make::ty(&delegee.name(db).display_no_db().to_smolstr()), + make::ty(&delegee.name(db).display_no_db(edition).to_smolstr()), strukt_ty, bound_def.where_clause(), ast_strukt.where_clause(), @@ -350,7 +355,7 @@ fn generate_impl( let type_gen_args = strukt_params.clone().map(|params| params.to_generic_args()); let path_type = - make::ty(&trait_.name(db).display_no_db().to_smolstr()).clone_for_update(); + make::ty(&trait_.name(db).display_no_db(edition).to_smolstr()).clone_for_update(); transform_impl(ctx, ast_strukt, &old_impl, &transform_args, path_type.syntax())?; // 3) Generate delegate trait impl @@ -735,6 +740,7 @@ fn func_assoc_item( item.async_token().is_some(), item.const_token().is_some(), item.unsafe_token().is_some(), + item.gen_token().is_some(), ) .clone_for_update(); diff --git a/crates/ide-assists/src/handlers/generate_deref.rs b/crates/ide-assists/src/handlers/generate_deref.rs index 2ac7057fe7..e558bb6da8 100644 --- a/crates/ide-assists/src/handlers/generate_deref.rs +++ b/crates/ide-assists/src/handlers/generate_deref.rs @@ -4,7 +4,7 @@ use hir::{ModPath, ModuleDef}; use ide_db::{famous_defs::FamousDefs, RootDatabase}; use syntax::{ ast::{self, HasName}, - AstNode, SyntaxNode, + AstNode, Edition, SyntaxNode, }; use crate::{ @@ -77,6 +77,7 @@ fn generate_record_deref(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<( field_name.syntax(), deref_type_to_generate, trait_path, + module.krate().edition(ctx.db()), ) }, ) @@ -117,6 +118,7 @@ fn generate_tuple_deref(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<() field_list_index, deref_type_to_generate, trait_path, + module.krate().edition(ctx.db()), ) }, ) @@ -130,6 +132,7 @@ fn generate_edit( field_name: impl Display, deref_type: DerefType, trait_path: ModPath, + edition: Edition, ) { let start_offset = strukt.syntax().text_range().end(); let impl_code = match deref_type { @@ -147,8 +150,11 @@ fn generate_edit( ), }; let strukt_adt = ast::Adt::Struct(strukt); - let deref_impl = - generate_trait_impl_text(&strukt_adt, &trait_path.display(db).to_string(), &impl_code); + let deref_impl = generate_trait_impl_text( + &strukt_adt, + &trait_path.display(db, edition).to_string(), + &impl_code, + ); edit.insert(start_offset, deref_impl); } diff --git a/crates/ide-assists/src/handlers/generate_documentation_template.rs b/crates/ide-assists/src/handlers/generate_documentation_template.rs index 51dd488454..c5c70c9f8e 100644 --- a/crates/ide-assists/src/handlers/generate_documentation_template.rs +++ b/crates/ide-assists/src/handlers/generate_documentation_template.rs @@ -5,7 +5,7 @@ use stdx::{format_to, to_lower_snake_case}; use syntax::{ algo::skip_whitespace_token, ast::{self, edit::IndentLevel, HasDocComments, HasGenericArgs, HasName}, - match_ast, AstNode, AstToken, + match_ast, AstNode, AstToken, Edition, }; use crate::assist_context::{AssistContext, Assists}; @@ -139,7 +139,8 @@ fn make_example_for_fn(ast_func: &ast::Fn, ctx: &AssistContext<'_>) -> Option Vec { } /// Helper function to build the path of the module in the which is the node -fn build_path(ast_func: &ast::Fn, ctx: &AssistContext<'_>) -> Option { +fn build_path(ast_func: &ast::Fn, ctx: &AssistContext<'_>, edition: Edition) -> Option { let crate_name = crate_name(ast_func, ctx)?; let leaf = self_partial_type(ast_func) .or_else(|| ast_func.name().map(|n| n.to_string())) .unwrap_or_else(|| "*".into()); let module_def: ModuleDef = ctx.sema.to_def(ast_func)?.module(ctx.db()).into(); - match module_def.canonical_path(ctx.db()) { + match module_def.canonical_path(ctx.db(), edition) { Some(path) => Some(format!("{crate_name}::{path}::{leaf}")), None => Some(format!("{crate_name}::{leaf}")), } diff --git a/crates/ide-assists/src/handlers/generate_function.rs b/crates/ide-assists/src/handlers/generate_function.rs index b2980d5c63..76a647807c 100644 --- a/crates/ide-assists/src/handlers/generate_function.rs +++ b/crates/ide-assists/src/handlers/generate_function.rs @@ -17,7 +17,7 @@ use syntax::{ self, edit::IndentLevel, edit_in_place::Indent, make, AstNode, BlockExpr, CallExpr, HasArgList, HasGenericParams, HasModuleItem, HasTypeBounds, }, - ted, SyntaxKind, SyntaxNode, TextRange, T, + ted, Edition, SyntaxKind, SyntaxNode, TextRange, T, }; use crate::{ @@ -175,6 +175,7 @@ fn add_func_to_accumulator( edit.edit_file(file); let target = function_builder.target.clone(); + let edition = function_builder.target_edition; let func = function_builder.render(ctx.config.snippet_cap, edit); if let Some(adt) = @@ -183,7 +184,7 @@ fn add_func_to_accumulator( { let name = make::ty_path(make::ext::ident_path(&format!( "{}", - adt.name(ctx.db()).display(ctx.db()) + adt.name(ctx.db()).display(ctx.db(), edition) ))); // FIXME: adt may have generic params. @@ -222,6 +223,7 @@ struct FunctionBuilder { should_focus_return_type: bool, visibility: Visibility, is_async: bool, + target_edition: Edition, } impl FunctionBuilder { @@ -237,6 +239,7 @@ impl FunctionBuilder { ) -> Option { let target_module = target_module.or_else(|| ctx.sema.scope(target.syntax()).map(|it| it.module()))?; + let target_edition = target_module.krate().edition(ctx.db()); let current_module = ctx.sema.scope(call.syntax())?.module(); let visibility = calculate_necessary_visibility(current_module, target_module, ctx); @@ -258,7 +261,9 @@ impl FunctionBuilder { // If generated function has the name "new" and is an associated function, we generate fn body // as a constructor and assume a "Self" return type. - if let Some(body) = make_fn_body_as_new_function(ctx, &fn_name.text(), adt_info) { + if let Some(body) = + make_fn_body_as_new_function(ctx, &fn_name.text(), adt_info, target_edition) + { ret_type = Some(make::ret_type(make::ty_path(make::ext::ident_path("Self")))); should_focus_return_type = false; fn_body = body; @@ -288,6 +293,7 @@ impl FunctionBuilder { should_focus_return_type, visibility, is_async, + target_edition, }) } @@ -299,6 +305,8 @@ impl FunctionBuilder { target_module: Module, target: GeneratedFunctionTarget, ) -> Option { + let target_edition = target_module.krate().edition(ctx.db()); + let current_module = ctx.sema.scope(call.syntax())?.module(); let visibility = calculate_necessary_visibility(current_module, target_module, ctx); @@ -336,6 +344,7 @@ impl FunctionBuilder { should_focus_return_type, visibility, is_async, + target_edition, }) } @@ -356,6 +365,7 @@ impl FunctionBuilder { self.is_async, false, // FIXME : const and unsafe are not handled yet. false, + false, ) .clone_for_update(); @@ -425,6 +435,7 @@ fn make_fn_body_as_new_function( ctx: &AssistContext<'_>, fn_name: &str, adt_info: &Option, + edition: Edition, ) -> Option { if fn_name != "new" { return None; @@ -441,7 +452,10 @@ fn make_fn_body_as_new_function( .iter() .map(|field| { make::record_expr_field( - make::name_ref(&format!("{}", field.name(ctx.db()).display(ctx.db()))), + make::name_ref(&format!( + "{}", + field.name(ctx.db()).display(ctx.db(), edition) + )), Some(placeholder_expr.clone()), ) }) @@ -1102,8 +1116,9 @@ fn fn_arg_type( if ty.is_reference() || ty.is_mutable_reference() { let famous_defs = &FamousDefs(&ctx.sema, ctx.sema.scope(fn_arg.syntax())?.krate()); + let target_edition = target_module.krate().edition(ctx.db()); convert_reference_type(ty.strip_references(), ctx.db(), famous_defs) - .map(|conversion| conversion.convert_type(ctx.db()).to_string()) + .map(|conversion| conversion.convert_type(ctx.db(), target_edition).to_string()) .or_else(|| ty.display_source_code(ctx.db(), target_module.into(), true).ok()) } else { ty.display_source_code(ctx.db(), target_module.into(), true).ok() diff --git a/crates/ide-assists/src/handlers/generate_getter_or_setter.rs b/crates/ide-assists/src/handlers/generate_getter_or_setter.rs index 60214aaaf6..c879a4a3d9 100644 --- a/crates/ide-assists/src/handlers/generate_getter_or_setter.rs +++ b/crates/ide-assists/src/handlers/generate_getter_or_setter.rs @@ -233,7 +233,7 @@ fn generate_getter_from_info( .map(|conversion| { cov_mark::hit!(convert_reference_type); ( - conversion.convert_type(ctx.db()), + conversion.convert_type(ctx.db(), krate.edition(ctx.db())), conversion.getter(record_field_info.field_name.to_string()), ) }) @@ -261,7 +261,19 @@ fn generate_getter_from_info( let ret_type = Some(make::ret_type(ty)); let body = make::block_expr([], Some(body)); - make::fn_(strukt.visibility(), fn_name, None, None, params, body, ret_type, false, false, false) + make::fn_( + strukt.visibility(), + fn_name, + None, + None, + params, + body, + ret_type, + false, + false, + false, + false, + ) } fn generate_setter_from_info(info: &AssistInfo, record_field_info: &RecordFieldInfo) -> ast::Fn { @@ -285,7 +297,19 @@ fn generate_setter_from_info(info: &AssistInfo, record_field_info: &RecordFieldI let body = make::block_expr([assign_stmt.into()], None); // Make the setter fn - make::fn_(strukt.visibility(), fn_name, None, None, params, body, None, false, false, false) + make::fn_( + strukt.visibility(), + fn_name, + None, + None, + params, + body, + None, + false, + false, + false, + false, + ) } fn extract_and_parse( diff --git a/crates/ide-assists/src/handlers/generate_new.rs b/crates/ide-assists/src/handlers/generate_new.rs index b985b5e66c..70d14d6b95 100644 --- a/crates/ide-assists/src/handlers/generate_new.rs +++ b/crates/ide-assists/src/handlers/generate_new.rs @@ -64,10 +64,13 @@ pub(crate) fn generate_new(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option ctx.config.import_path_config(), )?; + let edition = current_module.krate().edition(ctx.db()); + let expr = use_trivial_constructor( ctx.sema.db, - ide_db::helpers::mod_path_to_ast(&type_path), + ide_db::helpers::mod_path_to_ast(&type_path, edition), &ty, + edition, )?; Some(make::record_expr_field(make::name_ref(&name.text()), Some(expr))) @@ -112,6 +115,7 @@ pub(crate) fn generate_new(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option false, false, false, + false, ) .clone_for_update(); fn_.indent(1.into()); diff --git a/crates/ide-assists/src/handlers/inline_const_as_literal.rs b/crates/ide-assists/src/handlers/inline_const_as_literal.rs index 111ea50fdc..f1c2acdd3e 100644 --- a/crates/ide-assists/src/handlers/inline_const_as_literal.rs +++ b/crates/ide-assists/src/handlers/inline_const_as_literal.rs @@ -51,10 +51,13 @@ pub(crate) fn inline_const_as_literal(acc: &mut Assists, ctx: &AssistContext<'_> | ast::Expr::MatchExpr(_) | ast::Expr::MacroExpr(_) | ast::Expr::BinExpr(_) - | ast::Expr::CallExpr(_) => match konst.render_eval(ctx.sema.db) { - Ok(result) => result, - Err(_) => return None, - }, + | ast::Expr::CallExpr(_) => { + let edition = ctx.sema.scope(variable.syntax())?.krate().edition(ctx.db()); + match konst.render_eval(ctx.sema.db, edition) { + Ok(result) => result, + Err(_) => return None, + } + } _ => return None, }; 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 3057745a97..743ea94761 100644 --- a/crates/ide-assists/src/handlers/move_const_to_impl.rs +++ b/crates/ide-assists/src/handlers/move_const_to_impl.rs @@ -104,9 +104,13 @@ 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.file_ranges().map(|it| it.range) { - builder.replace(range, const_ref.clone()); + let usages = usages.iter().flat_map(|(file_id, usages)| { + let edition = file_id.edition(); + usages.iter().map(move |usage| (edition, usage.range)) + }); + for (edition, range) in usages { + let const_ref = format!("Self::{}", name.display(ctx.db(), edition)); + builder.replace(range, const_ref); } // Heuristically inserting the extracted const after the consecutive existing consts diff --git a/crates/ide-assists/src/handlers/move_from_mod_rs.rs b/crates/ide-assists/src/handlers/move_from_mod_rs.rs index 14381085a7..8a7a06b380 100644 --- a/crates/ide-assists/src/handlers/move_from_mod_rs.rs +++ b/crates/ide-assists/src/handlers/move_from_mod_rs.rs @@ -39,7 +39,7 @@ pub(crate) fn move_from_mod_rs(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op } let target = source_file.syntax().text_range(); - let module_name = module.name(ctx.db())?.display(ctx.db()).to_string(); + let module_name = module.name(ctx.db())?.unescaped().display(ctx.db()).to_string(); let path = format!("../{module_name}.rs"); let dst = AnchoredPathBuf { anchor: ctx.file_id().into(), path }; acc.add( diff --git a/crates/ide-assists/src/handlers/move_module_to_file.rs b/crates/ide-assists/src/handlers/move_module_to_file.rs index e679a68f44..9692b70592 100644 --- a/crates/ide-assists/src/handlers/move_module_to_file.rs +++ b/crates/ide-assists/src/handlers/move_module_to_file.rs @@ -61,7 +61,7 @@ pub(crate) fn move_module_to_file(acc: &mut Assists, ctx: &AssistContext<'_>) -> .string_value_unescape() .is_none() => { - format_to!(buf, "{}/", name.display(db)) + format_to!(buf, "{}/", name.unescaped().display(db)) } _ => (), } diff --git a/crates/ide-assists/src/handlers/move_to_mod_rs.rs b/crates/ide-assists/src/handlers/move_to_mod_rs.rs index c89d54ff03..2925e2334b 100644 --- a/crates/ide-assists/src/handlers/move_to_mod_rs.rs +++ b/crates/ide-assists/src/handlers/move_to_mod_rs.rs @@ -39,7 +39,7 @@ pub(crate) fn move_to_mod_rs(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opti } let target = source_file.syntax().text_range(); - let module_name = module.name(ctx.db())?.display(ctx.db()).to_string(); + let module_name = module.name(ctx.db())?.unescaped().display(ctx.db()).to_string(); let path = format!("./{module_name}/mod.rs"); let dst = AnchoredPathBuf { anchor: ctx.file_id().into(), path }; acc.add( diff --git a/crates/ide-assists/src/handlers/qualify_method_call.rs b/crates/ide-assists/src/handlers/qualify_method_call.rs index b1e98045fc..14518c4d2c 100644 --- a/crates/ide-assists/src/handlers/qualify_method_call.rs +++ b/crates/ide-assists/src/handlers/qualify_method_call.rs @@ -42,6 +42,7 @@ pub(crate) fn qualify_method_call(acc: &mut Assists, ctx: &AssistContext<'_>) -> let resolved_call = ctx.sema.resolve_method_call(&call)?; let current_module = ctx.sema.scope(call.syntax())?.module(); + let current_edition = current_module.krate().edition(ctx.db()); let target_module_def = ModuleDef::from(resolved_call); let item_in_ns = ItemInNs::from(target_module_def); let receiver_path = current_module.find_path( @@ -61,6 +62,7 @@ pub(crate) fn qualify_method_call(acc: &mut Assists, ctx: &AssistContext<'_>) -> |replace_with: String| builder.replace(range, replace_with), &receiver_path, item_in_ns, + current_edition, ) }, ); diff --git a/crates/ide-assists/src/handlers/qualify_path.rs b/crates/ide-assists/src/handlers/qualify_path.rs index d8e7da15d5..ac88861fe4 100644 --- a/crates/ide-assists/src/handlers/qualify_path.rs +++ b/crates/ide-assists/src/handlers/qualify_path.rs @@ -8,6 +8,7 @@ use ide_db::{ imports::import_assets::{ImportCandidate, LocatedImport}, }; use syntax::ast::HasGenericArgs; +use syntax::Edition; use syntax::{ ast, ast::{make, HasArgList}, @@ -93,6 +94,8 @@ pub(crate) fn qualify_path(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option NodeOrToken::Token(t) => t.parent()?, }) .map(|scope| scope.module()); + let current_edition = + current_module.map(|it| it.krate().edition(ctx.db())).unwrap_or(Edition::CURRENT); // prioritize more relevant imports proposed_imports.sort_by_key(|import| { Reverse(super::auto_import::relevance_score(ctx, import, current_module.as_ref())) @@ -103,13 +106,14 @@ pub(crate) fn qualify_path(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option acc.add_group( &group_label, AssistId("qualify_path", AssistKind::QuickFix), - label(ctx.db(), candidate, &import), + label(ctx.db(), candidate, &import, current_edition), range, |builder| { qualify_candidate.qualify( |replace_with: String| builder.replace(range, replace_with), &import.import_path, import.item_to_import, + current_edition, ) }, ); @@ -130,8 +134,9 @@ impl QualifyCandidate<'_> { mut replacer: impl FnMut(String), import: &hir::ModPath, item: hir::ItemInNs, + edition: Edition, ) { - let import = mod_path_to_ast(import); + let import = mod_path_to_ast(import, edition); match self { QualifyCandidate::QualifierStart(segment, generics) => { let generics = generics.as_ref().map_or_else(String::new, ToString::to_string); @@ -203,7 +208,7 @@ fn find_trait_method( if let Some(hir::AssocItem::Function(method)) = trait_.items(db).into_iter().find(|item: &hir::AssocItem| { item.name(db) - .map(|name| name.display(db).to_string() == trait_method_name.to_string()) + .map(|name| name.eq_ident(trait_method_name.text().as_str())) .unwrap_or(false) }) { @@ -233,14 +238,19 @@ fn group_label(candidate: &ImportCandidate) -> GroupLabel { GroupLabel(format!("Qualify {name}")) } -fn label(db: &RootDatabase, candidate: &ImportCandidate, import: &LocatedImport) -> String { +fn label( + db: &RootDatabase, + candidate: &ImportCandidate, + import: &LocatedImport, + edition: Edition, +) -> String { let import_path = &import.import_path; match candidate { ImportCandidate::Path(candidate) if candidate.qualifier.is_none() => { - format!("Qualify as `{}`", import_path.display(db)) + format!("Qualify as `{}`", import_path.display(db, edition)) } - _ => format!("Qualify with `{}`", import_path.display(db)), + _ => format!("Qualify with `{}`", import_path.display(db, edition)), } } diff --git a/crates/ide-assists/src/handlers/remove_dbg.rs b/crates/ide-assists/src/handlers/remove_dbg.rs index cffa3f55c9..0e9c463e02 100644 --- a/crates/ide-assists/src/handlers/remove_dbg.rs +++ b/crates/ide-assists/src/handlers/remove_dbg.rs @@ -1,7 +1,7 @@ use itertools::Itertools; use syntax::{ ast::{self, make, AstNode, AstToken}, - match_ast, ted, NodeOrToken, SyntaxElement, TextRange, TextSize, T, + match_ast, ted, Edition, NodeOrToken, SyntaxElement, TextRange, TextSize, T, }; use crate::{AssistContext, AssistId, AssistKind, Assists}; @@ -77,7 +77,7 @@ fn compute_dbg_replacement(macro_expr: ast::MacroExpr) -> Option<(TextRange, Opt let input_expressions = input_expressions .into_iter() .filter_map(|(is_sep, group)| (!is_sep).then_some(group)) - .map(|mut tokens| syntax::hacks::parse_expr_from_str(&tokens.join(""))) + .map(|mut tokens| syntax::hacks::parse_expr_from_str(&tokens.join(""), Edition::CURRENT)) .collect::>>()?; let parent = macro_expr.syntax().parent()?; diff --git a/crates/ide-assists/src/handlers/reorder_fields.rs b/crates/ide-assists/src/handlers/reorder_fields.rs index 0256256697..df7a5112f1 100644 --- a/crates/ide-assists/src/handlers/reorder_fields.rs +++ b/crates/ide-assists/src/handlers/reorder_fields.rs @@ -1,7 +1,7 @@ use either::Either; use ide_db::FxHashMap; use itertools::Itertools; -use syntax::{ast, ted, AstNode}; +use syntax::{ast, ted, AstNode, SmolStr, ToSmolStr}; use crate::{AssistContext, AssistId, AssistKind, Assists}; @@ -25,8 +25,9 @@ pub(crate) fn reorder_fields(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opti path.syntax().parent().and_then(>::cast)?; let ranks = compute_fields_ranks(&path, ctx)?; - let get_rank_of_field = - |of: Option<_>| *ranks.get(&of.unwrap_or_default()).unwrap_or(&usize::MAX); + let get_rank_of_field = |of: Option| { + *ranks.get(of.unwrap_or_default().trim_start_matches("r#")).unwrap_or(&usize::MAX) + }; let field_list = match &record { Either::Left(it) => Either::Left(it.record_expr_field_list()?), @@ -36,7 +37,7 @@ pub(crate) fn reorder_fields(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opti Either::Left(it) => Either::Left(( it.fields() .sorted_unstable_by_key(|field| { - get_rank_of_field(field.field_name().map(|it| it.to_string())) + get_rank_of_field(field.field_name().map(|it| it.to_smolstr())) }) .collect::>(), it, @@ -44,7 +45,7 @@ pub(crate) fn reorder_fields(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opti Either::Right(it) => Either::Right(( it.fields() .sorted_unstable_by_key(|field| { - get_rank_of_field(field.field_name().map(|it| it.to_string())) + get_rank_of_field(field.field_name().map(|it| it.to_smolstr())) }) .collect::>(), it, @@ -97,7 +98,7 @@ fn compute_fields_ranks( .fields(ctx.db()) .into_iter() .enumerate() - .map(|(idx, field)| (field.name(ctx.db()).display(ctx.db()).to_string(), idx)) + .map(|(idx, field)| (field.name(ctx.db()).unescaped().display(ctx.db()).to_string(), idx)) .collect(); Some(res) diff --git a/crates/ide-assists/src/handlers/reorder_impl_items.rs b/crates/ide-assists/src/handlers/reorder_impl_items.rs index cf135f83e7..ada89ce7c4 100644 --- a/crates/ide-assists/src/handlers/reorder_impl_items.rs +++ b/crates/ide-assists/src/handlers/reorder_impl_items.rs @@ -77,7 +77,8 @@ pub(crate) fn reorder_impl_items(acc: &mut Assists, ctx: &AssistContext<'_>) -> ast::AssocItem::MacroCall(_) => None, }; - name.and_then(|n| ranks.get(&n.to_string()).copied()).unwrap_or(usize::MAX) + name.and_then(|n| ranks.get(n.text().as_str().trim_start_matches("r#")).copied()) + .unwrap_or(usize::MAX) }) .collect(); @@ -114,7 +115,7 @@ fn compute_item_ranks( .iter() .flat_map(|i| i.name(ctx.db())) .enumerate() - .map(|(idx, name)| (name.display(ctx.db()).to_string(), idx)) + .map(|(idx, name)| (name.unescaped().display(ctx.db()).to_string(), idx)) .collect(), ) } 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 5ff4af19fb..248f18789c 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 @@ -70,6 +70,7 @@ pub(crate) fn replace_derive_with_manual_impl( let current_module = ctx.sema.scope(adt.syntax())?.module(); let current_crate = current_module.krate(); + let current_edition = current_crate.edition(ctx.db()); let found_traits = items_locator::items_with_name( &ctx.sema, @@ -85,7 +86,7 @@ pub(crate) fn replace_derive_with_manual_impl( current_module .find_path(ctx.sema.db, hir::ModuleDef::Trait(trait_), ctx.config.import_path_config()) .as_ref() - .map(mod_path_to_ast) + .map(|path| mod_path_to_ast(path, current_edition)) .zip(Some(trait_)) }); @@ -214,7 +215,7 @@ fn impl_def_from_trait( let impl_def = generate_trait_impl(adt, make::ty_path(trait_path.clone())); let first_assoc_item = - add_trait_assoc_items_to_impl(sema, &trait_items, trait_, &impl_def, target_scope); + add_trait_assoc_items_to_impl(sema, &trait_items, trait_, &impl_def, &target_scope); // Generate a default `impl` function body for the derived trait. if let ast::AssocItem::Fn(ref func) = first_assoc_item { diff --git a/crates/ide-assists/src/handlers/replace_qualified_name_with_use.rs b/crates/ide-assists/src/handlers/replace_qualified_name_with_use.rs index d0aa835e79..65330b34c4 100644 --- a/crates/ide-assists/src/handlers/replace_qualified_name_with_use.rs +++ b/crates/ide-assists/src/handlers/replace_qualified_name_with_use.rs @@ -5,7 +5,7 @@ use ide_db::{ }; use syntax::{ ast::{self, make, HasGenericArgs}, - match_ast, ted, AstNode, SyntaxNode, + match_ast, ted, AstNode, Edition, SyntaxNode, }; use crate::{AssistContext, AssistId, AssistKind, Assists}; @@ -29,32 +29,32 @@ pub(crate) fn replace_qualified_name_with_use( acc: &mut Assists, ctx: &AssistContext<'_>, ) -> Option<()> { - let path: ast::Path = ctx.find_node_at_offset()?; + let original_path: ast::Path = ctx.find_node_at_offset()?; // We don't want to mess with use statements - if path.syntax().ancestors().find_map(ast::UseTree::cast).is_some() { + if original_path.syntax().ancestors().find_map(ast::UseTree::cast).is_some() { cov_mark::hit!(not_applicable_in_use); return None; } - if path.qualifier().is_none() { + if original_path.qualifier().is_none() { cov_mark::hit!(dont_import_trivial_paths); return None; } // only offer replacement for non assoc items - match ctx.sema.resolve_path(&path)? { + match ctx.sema.resolve_path(&original_path)? { hir::PathResolution::Def(def) if def.as_assoc_item(ctx.sema.db).is_none() => (), _ => return None, } // then search for an import for the first path segment of what we want to replace // that way it is less likely that we import the item from a different location due re-exports - let module = match ctx.sema.resolve_path(&path.first_qualifier_or_self())? { + let module = match ctx.sema.resolve_path(&original_path.first_qualifier_or_self())? { hir::PathResolution::Def(module @ hir::ModuleDef::Module(_)) => module, _ => return None, }; let starts_with_name_ref = !matches!( - path.first_segment().and_then(|it| it.kind()), + original_path.first_segment().and_then(|it| it.kind()), Some( ast::PathSegmentKind::CrateKw | ast::PathSegmentKind::SuperKw @@ -63,7 +63,7 @@ pub(crate) fn replace_qualified_name_with_use( ); let path_to_qualifier = starts_with_name_ref .then(|| { - ctx.sema.scope(path.syntax())?.module().find_use_path( + ctx.sema.scope(original_path.syntax())?.module().find_use_path( ctx.sema.db, module, ctx.config.insert_use.prefix_kind, @@ -72,8 +72,8 @@ pub(crate) fn replace_qualified_name_with_use( }) .flatten(); - let scope = ImportScope::find_insert_use_container(path.syntax(), &ctx.sema)?; - let target = path.syntax().text_range(); + let scope = ImportScope::find_insert_use_container(original_path.syntax(), &ctx.sema)?; + let target = original_path.syntax().text_range(); acc.add( AssistId("replace_qualified_name_with_use", AssistKind::RefactorRewrite), "Replace qualified path with use", @@ -86,13 +86,19 @@ pub(crate) fn replace_qualified_name_with_use( ImportScope::Module(it) => ImportScope::Module(builder.make_mut(it)), ImportScope::Block(it) => ImportScope::Block(builder.make_mut(it)), }; - shorten_paths(scope.as_syntax_node(), &path); - let path = drop_generic_args(&path); + shorten_paths(scope.as_syntax_node(), &original_path); + let path = drop_generic_args(&original_path); + let edition = ctx + .sema + .scope(original_path.syntax()) + .map(|semantics_scope| semantics_scope.krate().edition(ctx.db())) + .unwrap_or(Edition::CURRENT); // stick the found import in front of the to be replaced path - let path = match path_to_qualifier.and_then(|it| mod_path_to_ast(&it).qualifier()) { - Some(qualifier) => make::path_concat(qualifier, path), - None => path, - }; + let path = + match path_to_qualifier.and_then(|it| mod_path_to_ast(&it, edition).qualifier()) { + Some(qualifier) => make::path_concat(qualifier, path), + None => path, + }; insert_use(&scope, path, &ctx.config.insert_use); }, ) diff --git a/crates/ide-assists/src/handlers/term_search.rs b/crates/ide-assists/src/handlers/term_search.rs index 4913cfdea9..66671c934c 100644 --- a/crates/ide-assists/src/handlers/term_search.rs +++ b/crates/ide-assists/src/handlers/term_search.rs @@ -48,15 +48,17 @@ pub(crate) fn term_search(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option< let mut formatter = |_: &hir::Type| String::from("todo!()"); + let edition = scope.krate().edition(ctx.db()); let paths = paths .into_iter() .filter_map(|path| { - path.gen_source_code(&scope, &mut formatter, ctx.config.import_path_config()).ok() + path.gen_source_code(&scope, &mut formatter, ctx.config.import_path_config(), edition) + .ok() }) .unique(); let macro_name = macro_call.name(ctx.sema.db); - let macro_name = macro_name.display(ctx.sema.db); + let macro_name = macro_name.display(ctx.sema.db, edition); for code in paths { acc.add_group( diff --git a/crates/ide-assists/src/handlers/toggle_async_sugar.rs b/crates/ide-assists/src/handlers/toggle_async_sugar.rs index 98975a324d..9ab36bf775 100644 --- a/crates/ide-assists/src/handlers/toggle_async_sugar.rs +++ b/crates/ide-assists/src/handlers/toggle_async_sugar.rs @@ -141,7 +141,8 @@ pub(crate) fn desugar_async_into_impl_future( ModuleDef::Trait(future_trait), ctx.config.import_path_config(), )?; - let trait_path = trait_path.display(ctx.db()); + let edition = scope.krate().edition(ctx.db()); + let trait_path = trait_path.display(ctx.db(), edition); acc.add( AssistId("desugar_async_into_impl_future", AssistKind::RefactorRewrite), diff --git a/crates/ide-assists/src/handlers/toggle_macro_delimiter.rs b/crates/ide-assists/src/handlers/toggle_macro_delimiter.rs new file mode 100644 index 0000000000..eedb2ea3b9 --- /dev/null +++ b/crates/ide-assists/src/handlers/toggle_macro_delimiter.rs @@ -0,0 +1,256 @@ +use ide_db::assists::{AssistId, AssistKind}; +use syntax::{ + ast::{self, make}, + ted, AstNode, T, +}; + +use crate::{AssistContext, Assists}; + +// Assist: toggle_macro_delimiter +// +// Change macro delimiters in the order of `( -> { -> [ -> (`. +// +// ``` +// macro_rules! sth { +// () => {}; +// } +// +// sth!$0( ); +// ``` +// -> +// ``` +// macro_rules! sth { +// () => {}; +// } +// +// sth!{ } +// ``` +pub(crate) fn toggle_macro_delimiter(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { + #[derive(Debug)] + enum MacroDelims { + LPar, + RPar, + LBra, + RBra, + LCur, + RCur, + } + + let makro = ctx.find_node_at_offset::()?.clone_for_update(); + let makro_text_range = makro.syntax().text_range(); + + let cursor_offset = ctx.offset(); + let semicolon = makro.semicolon_token(); + let token_tree = makro.token_tree()?; + + let ltoken = token_tree.left_delimiter_token()?; + let rtoken = token_tree.right_delimiter_token()?; + + if !ltoken.text_range().contains(cursor_offset) && !rtoken.text_range().contains(cursor_offset) + { + return None; + } + + let token = match ltoken.kind() { + T!['{'] => MacroDelims::LCur, + T!['('] => MacroDelims::LPar, + T!['['] => MacroDelims::LBra, + T!['}'] => MacroDelims::RBra, + T![')'] => MacroDelims::RPar, + T!['}'] => MacroDelims::RCur, + _ => return None, + }; + + acc.add( + AssistId("toggle_macro_delimiter", AssistKind::Refactor), + match token { + MacroDelims::LPar => "Replace delimiters with braces", + MacroDelims::RPar => "Replace delimiters with braces", + MacroDelims::LBra => "Replace delimiters with parentheses", + MacroDelims::RBra => "Replace delimiters with parentheses", + MacroDelims::LCur => "Replace delimiters with brackets", + MacroDelims::RCur => "Replace delimiters with brackets", + }, + token_tree.syntax().text_range(), + |builder| { + match token { + MacroDelims::LPar | MacroDelims::RPar => { + ted::replace(ltoken, make::token(T!['{'])); + ted::replace(rtoken, make::token(T!['}'])); + if let Some(sc) = semicolon { + ted::remove(sc); + } + } + MacroDelims::LBra | MacroDelims::RBra => { + ted::replace(ltoken, make::token(T!['('])); + ted::replace(rtoken, make::token(T![')'])); + } + MacroDelims::LCur | MacroDelims::RCur => { + ted::replace(ltoken, make::token(T!['['])); + ted::replace(rtoken, make::token(T![']'])); + } + } + builder.replace(makro_text_range, makro.syntax().text()); + }, + ) +} + +#[cfg(test)] +mod tests { + use crate::tests::{check_assist, check_assist_not_applicable}; + + use super::*; + + #[test] + fn test_par() { + check_assist( + toggle_macro_delimiter, + r#" +macro_rules! sth { + () => {}; +} + +sth!$0( ); + "#, + r#" +macro_rules! sth { + () => {}; +} + +sth!{ } + "#, + ) + } + + #[test] + fn test_braces() { + check_assist( + toggle_macro_delimiter, + r#" +macro_rules! sth { + () => {}; +} + +sth!$0{ }; + "#, + r#" +macro_rules! sth { + () => {}; +} + +sth![ ]; + "#, + ) + } + + #[test] + fn test_brackets() { + check_assist( + toggle_macro_delimiter, + r#" +macro_rules! sth { + () => {}; +} + +sth!$0[ ]; + "#, + r#" +macro_rules! sth { + () => {}; +} + +sth!( ); + "#, + ) + } + + #[test] + fn test_indent() { + check_assist( + toggle_macro_delimiter, + r#" +mod abc { + macro_rules! sth { + () => {}; + } + + sth!$0{ }; +} + "#, + r#" +mod abc { + macro_rules! sth { + () => {}; + } + + sth![ ]; +} + "#, + ) + } + + #[test] + fn test_unrelated_par() { + check_assist_not_applicable( + toggle_macro_delimiter, + r#" +macro_rules! prt { + ($e:expr) => {{ + println!("{}", stringify!{$e}); + }}; +} + +prt!(($03 + 5)); + + "#, + ) + } + + #[test] + fn test_longer_macros() { + check_assist( + toggle_macro_delimiter, + r#" +macro_rules! prt { + ($e:expr) => {{ + println!("{}", stringify!{$e}); + }}; +} + +prt!$0((3 + 5)); +"#, + r#" +macro_rules! prt { + ($e:expr) => {{ + println!("{}", stringify!{$e}); + }}; +} + +prt!{(3 + 5)} +"#, + ) + } + + // FIXME @alibektas : Inner macro_call is not seen as such. So this doesn't work. + #[test] + fn test_nested_macros() { + check_assist_not_applicable( + toggle_macro_delimiter, + r#" +macro_rules! prt { + ($e:expr) => {{ + println!("{}", stringify!{$e}); + }}; +} + +macro_rules! abc { + ($e:expr) => {{ + println!("{}", stringify!{$e}); + }}; +} + +prt!{abc!($03 + 5)}; +"#, + ) + } +} diff --git a/crates/ide-assists/src/handlers/unwrap_block.rs b/crates/ide-assists/src/handlers/unwrap_block.rs index de801279a0..f3e7f5f416 100644 --- a/crates/ide-assists/src/handlers/unwrap_block.rs +++ b/crates/ide-assists/src/handlers/unwrap_block.rs @@ -128,10 +128,12 @@ fn update_expr_string_without_newline(expr_string: String) -> String { } fn update_expr_string_with_pat(expr_str: String, whitespace_pat: &[char]) -> String { - // Remove leading whitespace, index [1..] to remove the leading '{', + // Remove leading whitespace, index to remove the leading '{', // then continue to remove leading whitespace. - let expr_str = - expr_str.trim_start_matches(whitespace_pat)[1..].trim_start_matches(whitespace_pat); + // We cannot assume the `{` is the first character because there are block modifiers + // (`unsafe`, `async` etc.). + let after_open_brace_index = expr_str.find('{').map_or(0, |it| it + 1); + let expr_str = expr_str[after_open_brace_index..].trim_start_matches(whitespace_pat); // Remove trailing whitespace, index [..expr_str.len() - 1] to remove the trailing '}', // then continue to remove trailing whitespace. @@ -824,6 +826,56 @@ fn main() { let a = 1; let x = foo; } +"#, + ); + } + + #[test] + fn unwrap_block_with_modifiers() { + // https://github.com/rust-lang/rust-analyzer/issues/17964 + check_assist( + unwrap_block, + r#" +fn main() { + unsafe $0{ + bar; + } +} +"#, + r#" +fn main() { + bar; +} +"#, + ); + check_assist( + unwrap_block, + r#" +fn main() { + async move $0{ + bar; + } +} +"#, + r#" +fn main() { + bar; +} +"#, + ); + check_assist( + unwrap_block, + r#" +fn main() { + try $0{ + bar; + } +} +"#, + r#" +fn main() { + bar; +} "#, ); } diff --git a/crates/ide-assists/src/lib.rs b/crates/ide-assists/src/lib.rs index a9399ba6b7..c88cb3d5ea 100644 --- a/crates/ide-assists/src/lib.rs +++ b/crates/ide-assists/src/lib.rs @@ -116,6 +116,7 @@ mod handlers { mod bool_to_enum; mod change_visibility; mod convert_bool_then; + mod convert_closure_to_fn; mod convert_comment_block; mod convert_comment_from_or_to_doc; mod convert_from_to_tryfrom; @@ -213,6 +214,7 @@ mod handlers { mod term_search; mod toggle_async_sugar; mod toggle_ignore; + mod toggle_macro_delimiter; mod unmerge_match_arm; mod unmerge_use; mod unnecessary_async; @@ -245,6 +247,7 @@ mod handlers { toggle_async_sugar::sugar_impl_future_into_async, convert_comment_block::convert_comment_block, convert_comment_from_or_to_doc::convert_comment_from_or_to_doc, + convert_closure_to_fn::convert_closure_to_fn, convert_from_to_tryfrom::convert_from_to_tryfrom, convert_integer_literal::convert_integer_literal, convert_into_to_from::convert_into_to_from, @@ -343,6 +346,7 @@ mod handlers { split_import::split_import, term_search::term_search, toggle_ignore::toggle_ignore, + toggle_macro_delimiter::toggle_macro_delimiter, unmerge_match_arm::unmerge_match_arm, unmerge_use::unmerge_use, unnecessary_async::unnecessary_async, diff --git a/crates/ide-assists/src/tests/generated.rs b/crates/ide-assists/src/tests/generated.rs index a2287b2977..dce7bbf342 100644 --- a/crates/ide-assists/src/tests/generated.rs +++ b/crates/ide-assists/src/tests/generated.rs @@ -382,6 +382,40 @@ fn main() { ) } +#[test] +fn doctest_convert_closure_to_fn() { + check_doc_test( + "convert_closure_to_fn", + r#####" +//- minicore: copy +struct String; +impl String { + fn new() -> Self {} + fn push_str(&mut self, s: &str) {} +} +fn main() { + let mut s = String::new(); + let closure = |$0a| s.push_str(a); + closure("abc"); +} +"#####, + r#####" +struct String; +impl String { + fn new() -> Self {} + fn push_str(&mut self, s: &str) {} +} +fn main() { + let mut s = String::new(); + fn closure(a: &str, s: &mut String) { + s.push_str(a) + } + closure("abc", &mut s); +} +"#####, + ) +} + #[test] fn doctest_convert_for_loop_with_for_each() { check_doc_test( @@ -994,7 +1028,7 @@ fn main() { "#####, r#####" fn main() { - let $0var_name = (1 + 2); + let $0var_name = 1 + 2; var_name * 4; } "#####, @@ -3091,6 +3125,27 @@ fn arithmetics { ) } +#[test] +fn doctest_toggle_macro_delimiter() { + check_doc_test( + "toggle_macro_delimiter", + r#####" +macro_rules! sth { + () => {}; +} + +sth!$0( ); +"#####, + r#####" +macro_rules! sth { + () => {}; +} + +sth!{ } +"#####, + ) +} + #[test] fn doctest_unmerge_match_arm() { check_doc_test( diff --git a/crates/ide-assists/src/utils.rs b/crates/ide-assists/src/utils.rs index c67693ea2b..b8a6f3b6db 100644 --- a/crates/ide-assists/src/utils.rs +++ b/crates/ide-assists/src/utils.rs @@ -14,7 +14,7 @@ use syntax::{ edit_in_place::{AttrsOwnerEdit, Indent, Removable}, make, HasArgList, HasAttrs, HasGenericParams, HasName, HasTypeBounds, Whitespace, }, - ted, AstNode, AstToken, Direction, NodeOrToken, SourceFile, + ted, AstNode, AstToken, Direction, Edition, NodeOrToken, SourceFile, SyntaxKind::*, SyntaxNode, SyntaxToken, TextRange, TextSize, T, }; @@ -174,7 +174,7 @@ pub fn add_trait_assoc_items_to_impl( original_items: &[InFile], trait_: hir::Trait, impl_: &ast::Impl, - target_scope: hir::SemanticsScope<'_>, + target_scope: &hir::SemanticsScope<'_>, ) -> ast::AssocItem { let new_indent_level = IndentLevel::from_node(impl_.syntax()) + 1; let items = original_items.iter().map(|InFile { file_id, value: original_item }| { @@ -195,7 +195,7 @@ pub fn add_trait_assoc_items_to_impl( // FIXME: Paths in nested macros are not handled well. See // `add_missing_impl_members::paths_in_nested_macro_should_get_transformed` test. let transform = - PathTransform::trait_impl(&target_scope, &source_scope, trait_, impl_.clone()); + PathTransform::trait_impl(target_scope, &source_scope, trait_, impl_.clone()); transform.apply(cloned_item.syntax()); } cloned_item.remove_attrs_and_docs(); @@ -684,31 +684,31 @@ enum ReferenceConversionType { } impl ReferenceConversion { - pub(crate) fn convert_type(&self, db: &dyn HirDatabase) -> ast::Type { + pub(crate) fn convert_type(&self, db: &dyn HirDatabase, edition: Edition) -> ast::Type { let ty = match self.conversion { - ReferenceConversionType::Copy => self.ty.display(db).to_string(), + ReferenceConversionType::Copy => self.ty.display(db, edition).to_string(), ReferenceConversionType::AsRefStr => "&str".to_owned(), ReferenceConversionType::AsRefSlice => { let type_argument_name = - self.ty.type_arguments().next().unwrap().display(db).to_string(); + self.ty.type_arguments().next().unwrap().display(db, edition).to_string(); format!("&[{type_argument_name}]") } ReferenceConversionType::Dereferenced => { let type_argument_name = - self.ty.type_arguments().next().unwrap().display(db).to_string(); + self.ty.type_arguments().next().unwrap().display(db, edition).to_string(); format!("&{type_argument_name}") } ReferenceConversionType::Option => { let type_argument_name = - self.ty.type_arguments().next().unwrap().display(db).to_string(); + self.ty.type_arguments().next().unwrap().display(db, edition).to_string(); format!("Option<&{type_argument_name}>") } ReferenceConversionType::Result => { let mut type_arguments = self.ty.type_arguments(); let first_type_argument_name = - type_arguments.next().unwrap().display(db).to_string(); + type_arguments.next().unwrap().display(db, edition).to_string(); let second_type_argument_name = - type_arguments.next().unwrap().display(db).to_string(); + type_arguments.next().unwrap().display(db, edition).to_string(); format!("Result<&{first_type_argument_name}, &{second_type_argument_name}>") } }; diff --git a/crates/ide-assists/src/utils/suggest_name.rs b/crates/ide-assists/src/utils/suggest_name.rs index fc43d243b3..3130ef0695 100644 --- a/crates/ide-assists/src/utils/suggest_name.rs +++ b/crates/ide-assists/src/utils/suggest_name.rs @@ -6,7 +6,7 @@ use itertools::Itertools; use stdx::to_lower_snake_case; use syntax::{ ast::{self, HasName}, - match_ast, AstNode, SmolStr, + match_ast, AstNode, Edition, SmolStr, }; /// Trait names, that will be ignored when in `impl Trait` and `dyn Trait` @@ -271,24 +271,25 @@ fn var_name_from_pat(pat: &ast::Pat) -> Option { fn from_type(expr: &ast::Expr, sema: &Semantics<'_, RootDatabase>) -> Option { let ty = sema.type_of_expr(expr)?.adjusted(); let ty = ty.remove_ref().unwrap_or(ty); + let edition = sema.scope(expr.syntax())?.krate().edition(sema.db); - name_of_type(&ty, sema.db) + name_of_type(&ty, sema.db, edition) } -fn name_of_type(ty: &hir::Type, db: &RootDatabase) -> Option { +fn name_of_type(ty: &hir::Type, db: &RootDatabase, edition: Edition) -> Option { let name = if let Some(adt) = ty.as_adt() { - let name = adt.name(db).display(db).to_string(); + let name = adt.name(db).display(db, edition).to_string(); if WRAPPER_TYPES.contains(&name.as_str()) { let inner_ty = ty.type_arguments().next()?; - return name_of_type(&inner_ty, db); + return name_of_type(&inner_ty, db, edition); } name } else if let Some(trait_) = ty.as_dyn_trait() { - trait_name(&trait_, db)? + trait_name(&trait_, db, edition)? } else if let Some(traits) = ty.as_impl_traits(db) { - let mut iter = traits.filter_map(|t| trait_name(&t, db)); + let mut iter = traits.filter_map(|t| trait_name(&t, db, edition)); let name = iter.next()?; if iter.next().is_some() { return None; @@ -300,8 +301,8 @@ fn name_of_type(ty: &hir::Type, db: &RootDatabase) -> Option { normalize(&name) } -fn trait_name(trait_: &hir::Trait, db: &RootDatabase) -> Option { - let name = trait_.name(db).display(db).to_string(); +fn trait_name(trait_: &hir::Trait, db: &RootDatabase, edition: Edition) -> Option { + let name = trait_.name(db).display(db, edition).to_string(); if USELESS_TRAITS.contains(&name.as_str()) { return None; } diff --git a/crates/ide-completion/Cargo.toml b/crates/ide-completion/Cargo.toml index 035b2fc0db..614465b4d0 100644 --- a/crates/ide-completion/Cargo.toml +++ b/crates/ide-completion/Cargo.toml @@ -17,7 +17,6 @@ cov-mark = "2.0.0-pre.1" itertools.workspace = true tracing.workspace = true -once_cell = "1.17.0" smallvec.workspace = true diff --git a/crates/ide-completion/src/completions.rs b/crates/ide-completion/src/completions.rs index 58e9b724df..b537150608 100644 --- a/crates/ide-completion/src/completions.rs +++ b/crates/ide-completion/src/completions.rs @@ -85,6 +85,7 @@ impl Completions { CompletionItemKind::Keyword, ctx.source_range(), SmolStr::new_static(keyword), + ctx.edition, ); item.add_to(self, ctx.db); } @@ -124,7 +125,8 @@ impl Completions { kw: &str, snippet: &str, ) { - let mut item = CompletionItem::new(CompletionItemKind::Keyword, ctx.source_range(), kw); + let mut item = + CompletionItem::new(CompletionItemKind::Keyword, ctx.source_range(), kw, ctx.edition); match ctx.config.snippet_cap { Some(cap) => { @@ -149,7 +151,8 @@ impl Completions { kw: &str, snippet: &str, ) { - let mut item = CompletionItem::new(CompletionItemKind::Keyword, ctx.source_range(), kw); + let mut item = + CompletionItem::new(CompletionItemKind::Keyword, ctx.source_range(), kw, ctx.edition); match ctx.config.snippet_cap { Some(cap) => item.insert_snippet(cap, snippet), @@ -544,7 +547,8 @@ impl Completions { CompletionItem::new( SymbolKind::LifetimeParam, ctx.source_range(), - name.display_no_db().to_smolstr(), + name.display_no_db(ctx.edition).to_smolstr(), + ctx.edition, ) .add_to(self, ctx.db) } @@ -553,7 +557,8 @@ impl Completions { CompletionItem::new( SymbolKind::Label, ctx.source_range(), - name.display_no_db().to_smolstr(), + name.display_no_db(ctx.edition).to_smolstr(), + ctx.edition, ) .add_to(self, ctx.db) } diff --git a/crates/ide-completion/src/completions/attribute.rs b/crates/ide-completion/src/completions/attribute.rs index a7a6cdebd3..9821fb4a2f 100644 --- a/crates/ide-completion/src/completions/attribute.rs +++ b/crates/ide-completion/src/completions/attribute.rs @@ -2,6 +2,8 @@ //! //! This module uses a bit of static metadata to provide completions for builtin-in attributes and lints. +use std::sync::LazyLock; + use ide_db::{ generated::lints::{ Lint, CLIPPY_LINTS, CLIPPY_LINT_GROUPS, DEFAULT_LINTS, FEATURES, RUSTDOC_LINTS, @@ -10,10 +12,9 @@ use ide_db::{ FxHashMap, SymbolKind, }; use itertools::Itertools; -use once_cell::sync::Lazy; use syntax::{ ast::{self, AttrKind}, - AstNode, SyntaxKind, T, + AstNode, Edition, SyntaxKind, T, }; use crate::{ @@ -48,11 +49,15 @@ pub(crate) fn complete_known_attribute_input( match path.text().as_str() { "repr" => repr::complete_repr(acc, ctx, tt), - "feature" => { - lint::complete_lint(acc, ctx, colon_prefix, &parse_tt_as_comma_sep_paths(tt)?, FEATURES) - } + "feature" => lint::complete_lint( + acc, + ctx, + colon_prefix, + &parse_tt_as_comma_sep_paths(tt, ctx.edition)?, + FEATURES, + ), "allow" | "warn" | "deny" | "forbid" => { - let existing_lints = parse_tt_as_comma_sep_paths(tt)?; + let existing_lints = parse_tt_as_comma_sep_paths(tt, ctx.edition)?; let lints: Vec = CLIPPY_LINT_GROUPS .iter() @@ -66,9 +71,12 @@ pub(crate) fn complete_known_attribute_input( lint::complete_lint(acc, ctx, colon_prefix, &existing_lints, &lints); } "cfg" => cfg::complete_cfg(acc, ctx), - "macro_use" => { - macro_use::complete_macro_use(acc, ctx, extern_crate, &parse_tt_as_comma_sep_paths(tt)?) - } + "macro_use" => macro_use::complete_macro_use( + acc, + ctx, + extern_crate, + &parse_tt_as_comma_sep_paths(tt, ctx.edition)?, + ), _ => (), } Some(()) @@ -130,8 +138,12 @@ pub(crate) fn complete_attribute_path( }); let add_completion = |attr_completion: &AttrCompletion| { - let mut item = - CompletionItem::new(SymbolKind::Attribute, ctx.source_range(), attr_completion.label); + let mut item = CompletionItem::new( + SymbolKind::Attribute, + ctx.source_range(), + attr_completion.label, + ctx.edition, + ); if let Some(lookup) = attr_completion.lookup { item.lookup_by(lookup); @@ -215,7 +227,7 @@ macro_rules! attrs { } #[rustfmt::skip] -static KIND_TO_ATTRIBUTES: Lazy> = Lazy::new(|| { +static KIND_TO_ATTRIBUTES: LazyLock> = LazyLock::new(|| { use SyntaxKind::*; [ ( @@ -361,7 +373,9 @@ fn parse_comma_sep_expr(input: ast::TokenTree) -> Option> { input_expressions .into_iter() .filter_map(|(is_sep, group)| (!is_sep).then_some(group)) - .filter_map(|mut tokens| syntax::hacks::parse_expr_from_str(&tokens.join(""))) + .filter_map(|mut tokens| { + syntax::hacks::parse_expr_from_str(&tokens.join(""), Edition::CURRENT) + }) .collect::>(), ) } diff --git a/crates/ide-completion/src/completions/attribute/cfg.rs b/crates/ide-completion/src/completions/attribute/cfg.rs index 6e7d50ede0..cda0da13b2 100644 --- a/crates/ide-completion/src/completions/attribute/cfg.rs +++ b/crates/ide-completion/src/completions/attribute/cfg.rs @@ -8,7 +8,8 @@ use crate::{completions::Completions, context::CompletionContext, CompletionItem pub(crate) fn complete_cfg(acc: &mut Completions, ctx: &CompletionContext<'_>) { let add_completion = |item: &str| { - let mut completion = CompletionItem::new(SymbolKind::BuiltinAttr, ctx.source_range(), item); + let mut completion = + CompletionItem::new(SymbolKind::BuiltinAttr, ctx.source_range(), item, ctx.edition); completion.insert_text(format!(r#""{item}""#)); acc.add(completion.build(ctx.db)); }; @@ -41,7 +42,12 @@ pub(crate) fn complete_cfg(acc: &mut Completions, ctx: &CompletionContext<'_>) { name => ctx.krate.potential_cfg(ctx.db).get_cfg_values(name).cloned().for_each(|s| { let s = s.as_str(); let insert_text = format!(r#""{s}""#); - let mut item = CompletionItem::new(SymbolKind::BuiltinAttr, ctx.source_range(), s); + let mut item = CompletionItem::new( + SymbolKind::BuiltinAttr, + ctx.source_range(), + s, + ctx.edition, + ); item.insert_text(insert_text); acc.add(item.build(ctx.db)); @@ -49,7 +55,8 @@ pub(crate) fn complete_cfg(acc: &mut Completions, ctx: &CompletionContext<'_>) { }, None => ctx.krate.potential_cfg(ctx.db).get_cfg_keys().cloned().unique().for_each(|s| { let s = s.as_str(); - let item = CompletionItem::new(SymbolKind::BuiltinAttr, ctx.source_range(), s); + let item = + CompletionItem::new(SymbolKind::BuiltinAttr, ctx.source_range(), s, ctx.edition); acc.add(item.build(ctx.db)); }), } diff --git a/crates/ide-completion/src/completions/attribute/derive.rs b/crates/ide-completion/src/completions/attribute/derive.rs index 0127a42824..1f8927401b 100644 --- a/crates/ide-completion/src/completions/attribute/derive.rs +++ b/crates/ide-completion/src/completions/attribute/derive.rs @@ -62,7 +62,7 @@ pub(crate) fn complete_derive_path( _ => return acc.add_macro(ctx, path_ctx, mac, name), }; - let name_ = name.display_no_db().to_smolstr(); + let name_ = name.display_no_db(ctx.edition).to_smolstr(); let find = DEFAULT_DERIVE_DEPENDENCIES .iter() .find(|derive_completion| derive_completion.label == name_); @@ -72,10 +72,9 @@ pub(crate) fn complete_derive_path( let mut components = vec![derive_completion.label]; components.extend(derive_completion.dependencies.iter().filter( |&&dependency| { - !existing_derives - .iter() - .map(|it| it.name(ctx.db)) - .any(|it| it.display_no_db().to_smolstr() == dependency) + !existing_derives.iter().map(|it| it.name(ctx.db)).any(|it| { + it.display_no_db(ctx.edition).to_smolstr() == dependency + }) }, )); let lookup = components.join(", "); @@ -85,6 +84,7 @@ pub(crate) fn complete_derive_path( SymbolKind::Derive, ctx.source_range(), SmolStr::from_iter(label), + ctx.edition, ); if let Some(docs) = mac.docs(ctx.db) { item.documentation(docs); diff --git a/crates/ide-completion/src/completions/attribute/lint.rs b/crates/ide-completion/src/completions/attribute/lint.rs index f9dec53806..d5f9cd5fc7 100644 --- a/crates/ide-completion/src/completions/attribute/lint.rs +++ b/crates/ide-completion/src/completions/attribute/lint.rs @@ -54,7 +54,8 @@ pub(super) fn complete_lint( Some(qual) if !is_qualified => format!("{qual}::{name}"), _ => name.to_owned(), }; - let mut item = CompletionItem::new(SymbolKind::Attribute, ctx.source_range(), label); + let mut item = + CompletionItem::new(SymbolKind::Attribute, ctx.source_range(), label, ctx.edition); item.documentation(Documentation::new(description.to_owned())); item.add_to(acc, ctx.db) } diff --git a/crates/ide-completion/src/completions/attribute/macro_use.rs b/crates/ide-completion/src/completions/attribute/macro_use.rs index 7e3a62405a..deb12282c0 100644 --- a/crates/ide-completion/src/completions/attribute/macro_use.rs +++ b/crates/ide-completion/src/completions/attribute/macro_use.rs @@ -28,7 +28,8 @@ pub(super) fn complete_macro_use( continue; } - let item = CompletionItem::new(SymbolKind::Macro, ctx.source_range(), mac_name); + let item = + CompletionItem::new(SymbolKind::Macro, ctx.source_range(), mac_name, ctx.edition); item.add_to(acc, ctx.db); } } diff --git a/crates/ide-completion/src/completions/attribute/repr.rs b/crates/ide-completion/src/completions/attribute/repr.rs index 14f464b775..12652b4489 100644 --- a/crates/ide-completion/src/completions/attribute/repr.rs +++ b/crates/ide-completion/src/completions/attribute/repr.rs @@ -30,7 +30,12 @@ pub(super) fn complete_repr( continue; } - let mut item = CompletionItem::new(SymbolKind::BuiltinAttr, ctx.source_range(), label); + let mut item = CompletionItem::new( + SymbolKind::BuiltinAttr, + ctx.source_range(), + label, + ctx.edition, + ); if let Some(lookup) = lookup { item.lookup_by(lookup); } diff --git a/crates/ide-completion/src/completions/dot.rs b/crates/ide-completion/src/completions/dot.rs index a07daf4c4e..d55bc3ea5d 100644 --- a/crates/ide-completion/src/completions/dot.rs +++ b/crates/ide-completion/src/completions/dot.rs @@ -29,6 +29,7 @@ pub(crate) fn complete_dot( CompletionItemKind::Keyword, ctx.source_range(), SmolStr::new_static("await"), + ctx.edition, ); item.detail("expr.await"); item.add_to(acc, ctx.db); diff --git a/crates/ide-completion/src/completions/env_vars.rs b/crates/ide-completion/src/completions/env_vars.rs index 23d93d3b74..c9013d1d17 100644 --- a/crates/ide-completion/src/completions/env_vars.rs +++ b/crates/ide-completion/src/completions/env_vars.rs @@ -56,7 +56,7 @@ pub(crate) fn complete_cargo_env_vars( let range = original.text_range_between_quotes()?; CARGO_DEFINED_VARS.iter().for_each(|&(var, detail)| { - let mut item = CompletionItem::new(CompletionItemKind::Keyword, range, var); + let mut item = CompletionItem::new(CompletionItemKind::Keyword, range, var, ctx.edition); item.detail(detail); item.add_to(acc, ctx.db); }); diff --git a/crates/ide-completion/src/completions/extern_abi.rs b/crates/ide-completion/src/completions/extern_abi.rs index b5d5604c75..1a06e0a3a0 100644 --- a/crates/ide-completion/src/completions/extern_abi.rs +++ b/crates/ide-completion/src/completions/extern_abi.rs @@ -52,8 +52,13 @@ pub(crate) fn complete_extern_abi( let abi_str = expanded; let source_range = abi_str.text_range_between_quotes()?; for &abi in SUPPORTED_CALLING_CONVENTIONS { - CompletionItem::new(CompletionItemKind::Keyword, source_range, SmolStr::new_static(abi)) - .add_to(acc, ctx.db); + CompletionItem::new( + CompletionItemKind::Keyword, + source_range, + SmolStr::new_static(abi), + ctx.edition, + ) + .add_to(acc, ctx.db); } Some(()) } diff --git a/crates/ide-completion/src/completions/extern_crate.rs b/crates/ide-completion/src/completions/extern_crate.rs index 2427f4e49f..7cb710c2d9 100644 --- a/crates/ide-completion/src/completions/extern_crate.rs +++ b/crates/ide-completion/src/completions/extern_crate.rs @@ -19,7 +19,8 @@ pub(crate) fn complete_extern_crate(acc: &mut Completions, ctx: &CompletionConte let mut item = CompletionItem::new( CompletionItemKind::SymbolKind(SymbolKind::Module), ctx.source_range(), - name.display_no_db().to_smolstr(), + name.display_no_db(ctx.edition).to_smolstr(), + ctx.edition, ); item.set_documentation(module.docs(ctx.db)); diff --git a/crates/ide-completion/src/completions/flyimport.rs b/crates/ide-completion/src/completions/flyimport.rs index fdce7c547a..2a6b310d3a 100644 --- a/crates/ide-completion/src/completions/flyimport.rs +++ b/crates/ide-completion/src/completions/flyimport.rs @@ -411,7 +411,7 @@ fn compute_fuzzy_completion_order_key( cov_mark::hit!(certain_fuzzy_order_test); let import_name = match proposed_mod_path.segments().last() { // FIXME: nasty alloc, this is a hot path! - Some(name) => name.display_no_db().to_smolstr().to_ascii_lowercase(), + Some(name) => name.unescaped().display_no_db().to_smolstr().to_ascii_lowercase(), None => return usize::MAX, }; match import_name.match_indices(user_input_lowercased).next() { diff --git a/crates/ide-completion/src/completions/fn_param.rs b/crates/ide-completion/src/completions/fn_param.rs index a59246229b..ee3b817ee8 100644 --- a/crates/ide-completion/src/completions/fn_param.rs +++ b/crates/ide-completion/src/completions/fn_param.rs @@ -32,7 +32,7 @@ pub(crate) fn complete_fn_param( let comma_wrapper = comma_wrapper(ctx); let mut add_new_item_to_acc = |label: &str| { let mk_item = |label: &str, range: TextRange| { - CompletionItem::new(CompletionItemKind::Binding, range, label) + CompletionItem::new(CompletionItemKind::Binding, range, label, ctx.edition) }; let item = match &comma_wrapper { Some((fmt, range)) => mk_item(&fmt(label), *range), @@ -50,7 +50,7 @@ pub(crate) fn complete_fn_param( ParamKind::Closure(closure) => { let stmt_list = closure.syntax().ancestors().find_map(ast::StmtList::cast)?; params_from_stmt_list_scope(ctx, stmt_list, |name, ty| { - add_new_item_to_acc(&format!("{}: {ty}", name.display(ctx.db))); + add_new_item_to_acc(&format!("{}: {ty}", name.display(ctx.db, ctx.edition))); }); } } @@ -101,8 +101,8 @@ fn fill_fn_params( if let Some(stmt_list) = function.syntax().parent().and_then(ast::StmtList::cast) { params_from_stmt_list_scope(ctx, stmt_list, |name, ty| { file_params - .entry(format!("{}: {ty}", name.display(ctx.db))) - .or_insert(name.display(ctx.db).to_string()); + .entry(format!("{}: {ty}", name.display(ctx.db, ctx.edition))) + .or_insert(name.display(ctx.db, ctx.edition).to_string()); }); } remove_duplicated(&mut file_params, param_list.params()); diff --git a/crates/ide-completion/src/completions/format_string.rs b/crates/ide-completion/src/completions/format_string.rs index 23affc3659..a87c60c694 100644 --- a/crates/ide-completion/src/completions/format_string.rs +++ b/crates/ide-completion/src/completions/format_string.rs @@ -35,7 +35,8 @@ pub(crate) fn format_string( CompletionItem::new( CompletionItemKind::Binding, source_range, - name.display_no_db().to_smolstr(), + name.display_no_db(ctx.edition).to_smolstr(), + ctx.edition, ) .add_to(acc, ctx.db); }); @@ -50,7 +51,8 @@ pub(crate) fn format_string( CompletionItem::new( CompletionItemKind::SymbolKind(symbol_kind), source_range, - name.display_no_db().to_smolstr(), + name.display_no_db(ctx.edition).to_smolstr(), + ctx.edition, ) .add_to(acc, ctx.db); } diff --git a/crates/ide-completion/src/completions/item_list/trait_impl.rs b/crates/ide-completion/src/completions/item_list/trait_impl.rs index 2fd7805e60..fc6e1ebf05 100644 --- a/crates/ide-completion/src/completions/item_list/trait_impl.rs +++ b/crates/ide-completion/src/completions/item_list/trait_impl.rs @@ -184,7 +184,7 @@ fn add_function_impl( let label = format_smolstr!( "{}fn {}({})", if is_async { "async " } else { "" }, - fn_name.display(ctx.db), + fn_name.display(ctx.db, ctx.edition), if func.assoc_fn_params(ctx.db).is_empty() { "" } else { ".." } ); @@ -194,11 +194,11 @@ fn add_function_impl( SymbolKind::Function }); - let mut item = CompletionItem::new(completion_kind, replacement_range, label); + let mut item = CompletionItem::new(completion_kind, replacement_range, label, ctx.edition); item.lookup_by(format!( "{}fn {}", if is_async { "async " } else { "" }, - fn_name.display(ctx.db) + fn_name.display(ctx.db, ctx.edition) )) .set_documentation(func.docs(ctx.db)) .set_relevance(CompletionRelevance { is_item_from_trait: true, ..Default::default() }); @@ -262,7 +262,8 @@ fn add_type_alias_impl( let label = format_smolstr!("type {alias_name} ="); - let mut item = CompletionItem::new(SymbolKind::TypeAlias, replacement_range, label); + let mut item = + CompletionItem::new(SymbolKind::TypeAlias, replacement_range, label, ctx.edition); item.lookup_by(format!("type {alias_name}")) .set_documentation(type_alias.docs(ctx.db)) .set_relevance(CompletionRelevance { is_item_from_trait: true, ..Default::default() }); @@ -320,7 +321,7 @@ fn add_const_impl( const_: hir::Const, impl_def: hir::Impl, ) { - let const_name = const_.name(ctx.db).map(|n| n.display_no_db().to_smolstr()); + let const_name = const_.name(ctx.db).map(|n| n.display_no_db(ctx.edition).to_smolstr()); if let Some(const_name) = const_name { if let Some(source) = ctx.sema.source(const_) { @@ -334,7 +335,8 @@ fn add_const_impl( let label = make_const_compl_syntax(&transformed_const, source.file_id.is_macro()); let replacement = format!("{label} "); - let mut item = CompletionItem::new(SymbolKind::Const, replacement_range, label); + let mut item = + CompletionItem::new(SymbolKind::Const, replacement_range, label, ctx.edition); item.lookup_by(format_smolstr!("const {const_name}")) .set_documentation(const_.docs(ctx.db)) .set_relevance(CompletionRelevance { diff --git a/crates/ide-completion/src/completions/lifetime.rs b/crates/ide-completion/src/completions/lifetime.rs index 03fe93c563..9efc52428e 100644 --- a/crates/ide-completion/src/completions/lifetime.rs +++ b/crates/ide-completion/src/completions/lifetime.rs @@ -41,7 +41,7 @@ pub(crate) fn complete_lifetime( if matches!( res, ScopeDef::GenericParam(hir::GenericParam::LifetimeParam(_)) - if param_lifetime != Some(&*name.display_no_db().to_smolstr()) + if param_lifetime != Some(&*name.display_no_db(ctx.edition).to_smolstr()) ) { acc.add_lifetime(ctx, name); } diff --git a/crates/ide-completion/src/completions/mod_.rs b/crates/ide-completion/src/completions/mod_.rs index d9a10893bf..05e2892fdc 100644 --- a/crates/ide-completion/src/completions/mod_.rs +++ b/crates/ide-completion/src/completions/mod_.rs @@ -53,7 +53,7 @@ pub(crate) fn complete_mod( let existing_mod_declarations = current_module .children(ctx.db) - .filter_map(|module| Some(module.name(ctx.db)?.display(ctx.db).to_string())) + .filter_map(|module| Some(module.name(ctx.db)?.display(ctx.db, ctx.edition).to_string())) .filter(|module| module != ctx.original_token.text()) .collect::>(); @@ -99,7 +99,8 @@ pub(crate) fn complete_mod( if mod_under_caret.semicolon_token().is_none() { label.push(';'); } - let item = CompletionItem::new(SymbolKind::Module, ctx.source_range(), &label); + let item = + CompletionItem::new(SymbolKind::Module, ctx.source_range(), &label, ctx.edition); item.add_to(acc, ctx.db) }); @@ -140,7 +141,9 @@ fn directory_to_look_for_submodules( module_chain_to_containing_module_file(module, db) .into_iter() .filter_map(|module| module.name(db)) - .try_fold(base_directory, |path, name| path.join(&name.display_no_db().to_smolstr())) + .try_fold(base_directory, |path, name| { + path.join(&name.unescaped().display_no_db().to_smolstr()) + }) } fn module_chain_to_containing_module_file( diff --git a/crates/ide-completion/src/completions/postfix.rs b/crates/ide-completion/src/completions/postfix.rs index 977e0d80a4..a632f14893 100644 --- a/crates/ide-completion/src/completions/postfix.rs +++ b/crates/ide-completion/src/completions/postfix.rs @@ -72,7 +72,10 @@ pub(crate) fn complete_postfix( let mut item = postfix_snippet( "drop", "fn drop(&mut self)", - &format!("{path}($0{receiver_text})", path = path.display(ctx.db)), + &format!( + "{path}($0{receiver_text})", + path = path.display(ctx.db, ctx.edition) + ), ); item.set_documentation(drop_fn.docs(ctx.db)); item.add_to(acc, ctx.db); @@ -335,8 +338,12 @@ fn build_postfix_snippet_builder<'ctx>( ) -> impl Fn(&str, &str, &str) -> Builder + 'ctx { move |label, detail, snippet| { let edit = TextEdit::replace(delete_range, snippet.to_owned()); - let mut item = - CompletionItem::new(CompletionItemKind::Snippet, ctx.source_range(), label); + let mut item = CompletionItem::new( + CompletionItemKind::Snippet, + ctx.source_range(), + label, + ctx.edition, + ); item.detail(detail).snippet_edit(cap, edit); let postfix_match = if ctx.original_token.text() == label { cov_mark::hit!(postfix_exact_match_is_high_priority); diff --git a/crates/ide-completion/src/completions/record.rs b/crates/ide-completion/src/completions/record.rs index 1dcf41f8dd..117a5e3d93 100644 --- a/crates/ide-completion/src/completions/record.rs +++ b/crates/ide-completion/src/completions/record.rs @@ -73,6 +73,7 @@ pub(crate) fn complete_record_expr_fields( CompletionItemKind::Snippet, ctx.source_range(), SmolStr::new_static(".."), + ctx.edition, ); item.insert_text("."); item.add_to(acc, ctx.db); @@ -101,6 +102,7 @@ pub(crate) fn add_default_update( SymbolKind::Field, ctx.source_range(), SmolStr::new_static(completion_text), + ctx.edition, ); let completion_text = completion_text.strip_prefix(ctx.token.text()).unwrap_or(completion_text); diff --git a/crates/ide-completion/src/completions/snippet.rs b/crates/ide-completion/src/completions/snippet.rs index e831113350..357709e0c1 100644 --- a/crates/ide-completion/src/completions/snippet.rs +++ b/crates/ide-completion/src/completions/snippet.rs @@ -118,7 +118,8 @@ macro_rules! $1 { } fn snippet(ctx: &CompletionContext<'_>, cap: SnippetCap, label: &str, snippet: &str) -> Builder { - let mut item = CompletionItem::new(CompletionItemKind::Snippet, ctx.source_range(), label); + let mut item = + CompletionItem::new(CompletionItemKind::Snippet, ctx.source_range(), label, ctx.edition); item.insert_snippet(cap, snippet); item } diff --git a/crates/ide-completion/src/completions/use_.rs b/crates/ide-completion/src/completions/use_.rs index 8e5b55360d..45704549e6 100644 --- a/crates/ide-completion/src/completions/use_.rs +++ b/crates/ide-completion/src/completions/use_.rs @@ -107,7 +107,11 @@ pub(crate) fn complete_use_path( let item = CompletionItem::new( CompletionItemKind::SymbolKind(SymbolKind::Enum), ctx.source_range(), - format_smolstr!("{}::", e.name(ctx.db).display(ctx.db)), + format_smolstr!( + "{}::", + e.name(ctx.db).display(ctx.db, ctx.edition) + ), + ctx.edition, ); acc.add(item.build(ctx.db)); } diff --git a/crates/ide-completion/src/context.rs b/crates/ide-completion/src/context.rs index 952d9217c7..bcd9df9419 100644 --- a/crates/ide-completion/src/context.rs +++ b/crates/ide-completion/src/context.rs @@ -15,7 +15,7 @@ use ide_db::{ }; use syntax::{ ast::{self, AttrKind, NameOrNameRef}, - AstNode, SmolStr, + AstNode, Edition, SmolStr, SyntaxKind::{self, *}, SyntaxToken, TextRange, TextSize, T, }; @@ -437,6 +437,7 @@ pub(crate) struct CompletionContext<'a> { pub(crate) module: hir::Module, /// Whether nightly toolchain is used. Cached since this is looked up a lot. is_nightly: bool, + pub(crate) edition: Edition, /// The expected name of what we are completing. /// This is usually the parameter name of the function argument we are completing. @@ -467,8 +468,9 @@ impl CompletionContext<'_> { cov_mark::hit!(completes_if_lifetime_without_idents); TextRange::at(self.original_token.text_range().start(), TextSize::from(1)) } - IDENT | LIFETIME_IDENT | UNDERSCORE | INT_NUMBER => self.original_token.text_range(), - _ if kind.is_keyword() => self.original_token.text_range(), + LIFETIME_IDENT | UNDERSCORE | INT_NUMBER => self.original_token.text_range(), + // We want to consider all keywords in all editions. + _ if kind.is_any_identifier() => self.original_token.text_range(), _ => TextRange::empty(self.position.offset), } } @@ -716,6 +718,7 @@ impl<'a> CompletionContext<'a> { let krate = scope.krate(); let module = scope.module(); + let edition = krate.edition(db); let toolchain = db.toolchain_channel(krate.into()); // `toolchain == None` means we're in some detached files. Since we have no information on @@ -742,6 +745,7 @@ impl<'a> CompletionContext<'a> { krate, module, is_nightly, + edition, expected_name, expected_type, qualifier_ctx, diff --git a/crates/ide-completion/src/context/analysis.rs b/crates/ide-completion/src/context/analysis.rs index 1e972b9b4c..ed359394f1 100644 --- a/crates/ide-completion/src/context/analysis.rs +++ b/crates/ide-completion/src/context/analysis.rs @@ -1385,7 +1385,7 @@ fn pattern_context_for( }).map(|enum_| enum_.variants(sema.db)) }) }).map(|variants| variants.iter().filter_map(|variant| { - let variant_name = variant.name(sema.db).display(sema.db).to_string(); + let variant_name = variant.name(sema.db).unescaped().display(sema.db).to_string(); let variant_already_present = match_arm_list.arms().any(|arm| { arm.pat().and_then(|pat| { diff --git a/crates/ide-completion/src/item.rs b/crates/ide-completion/src/item.rs index 3657a7d969..a30a115da1 100644 --- a/crates/ide-completion/src/item.rs +++ b/crates/ide-completion/src/item.rs @@ -10,7 +10,7 @@ use ide_db::{ use itertools::Itertools; use smallvec::SmallVec; use stdx::{impl_from, never}; -use syntax::{format_smolstr, SmolStr, TextRange, TextSize}; +use syntax::{format_smolstr, Edition, SmolStr, TextRange, TextSize}; use text_edit::TextEdit; use crate::{ @@ -400,6 +400,7 @@ impl CompletionItem { kind: impl Into, source_range: TextRange, label: impl Into, + edition: Edition, ) -> Builder { let label = label.into(); Builder { @@ -419,6 +420,7 @@ impl CompletionItem { ref_match: None, imports_to_add: Default::default(), doc_aliases: vec![], + edition, } } @@ -464,6 +466,7 @@ pub(crate) struct Builder { trigger_call_info: bool, relevance: CompletionRelevance, ref_match: Option<(Mutability, TextSize)>, + edition: Edition, } impl Builder { @@ -517,7 +520,7 @@ impl Builder { label_detail.replace(format_smolstr!( "{} (use {})", label_detail.as_deref().unwrap_or_default(), - import_edit.import_path.display(db) + import_edit.import_path.display(db, self.edition) )); } else if let Some(trait_name) = self.trait_name { label_detail.replace(format_smolstr!( @@ -536,8 +539,8 @@ impl Builder { .into_iter() .filter_map(|import| { Some(( - import.import_path.display(db).to_string(), - import.import_path.segments().last()?.display(db).to_string(), + import.import_path.display(db, self.edition).to_string(), + import.import_path.segments().last()?.display(db, self.edition).to_string(), )) }) .collect(); diff --git a/crates/ide-completion/src/lib.rs b/crates/ide-completion/src/lib.rs index 90c1728074..58d1fad095 100644 --- a/crates/ide-completion/src/lib.rs +++ b/crates/ide-completion/src/lib.rs @@ -245,6 +245,7 @@ pub fn resolve_completion_edits( let current_module = sema.scope(position_for_import)?.module(); let current_crate = current_module.krate(); + let current_edition = current_crate.edition(db); let new_ast = scope.clone_for_update(); let mut import_insert = TextEdit::builder(); @@ -261,9 +262,13 @@ pub fn resolve_completion_edits( .filter_map(|candidate| { current_module.find_use_path(db, candidate, config.insert_use.prefix_kind, cfg) }) - .find(|mod_path| mod_path.display(db).to_string() == full_import_path); + .find(|mod_path| mod_path.display(db, current_edition).to_string() == full_import_path); if let Some(import_path) = import { - insert_use::insert_use(&new_ast, mod_path_to_ast(&import_path), &config.insert_use); + insert_use::insert_use( + &new_ast, + mod_path_to_ast(&import_path, current_edition), + &config.insert_use, + ); } }); diff --git a/crates/ide-completion/src/render.rs b/crates/ide-completion/src/render.rs index 02d667c520..ff5ec3a29f 100644 --- a/crates/ide-completion/src/render.rs +++ b/crates/ide-completion/src/render.rs @@ -17,7 +17,7 @@ use ide_db::{ imports::import_assets::LocatedImport, RootDatabase, SnippetCap, SymbolKind, }; -use syntax::{ast, format_smolstr, AstNode, SmolStr, SyntaxKind, TextRange, ToSmolStr}; +use syntax::{ast, format_smolstr, AstNode, Edition, SmolStr, SyntaxKind, TextRange, ToSmolStr}; use text_edit::TextEdit; use crate::{ @@ -133,19 +133,22 @@ pub(crate) fn render_field( let db = ctx.db(); let is_deprecated = ctx.is_deprecated(field); let name = field.name(db); - let (name, escaped_name) = - (name.unescaped().display(db).to_smolstr(), name.display_no_db().to_smolstr()); + let (name, escaped_name) = ( + name.unescaped().display(db).to_smolstr(), + name.display_no_db(ctx.completion.edition).to_smolstr(), + ); let mut item = CompletionItem::new( SymbolKind::Field, ctx.source_range(), - field_with_receiver(db, receiver.as_ref(), &name), + field_with_receiver(db, receiver.as_ref(), &name, ctx.completion.edition), + ctx.completion.edition, ); item.set_relevance(CompletionRelevance { type_match: compute_type_match(ctx.completion, ty), exact_name_match: compute_exact_name_match(ctx.completion, name.as_str()), ..CompletionRelevance::default() }); - item.detail(ty.display(db).to_string()) + item.detail(ty.display(db, ctx.completion.edition).to_string()) .set_documentation(field.docs(db)) .set_deprecated(is_deprecated) .lookup_by(name); @@ -159,7 +162,8 @@ pub(crate) fn render_field( builder.replace( ctx.source_range(), - field_with_receiver(db, receiver.as_ref(), &escaped_name).into(), + field_with_receiver(db, receiver.as_ref(), &escaped_name, ctx.completion.edition) + .into(), ); let expected_fn_type = @@ -183,7 +187,12 @@ pub(crate) fn render_field( item.text_edit(builder.finish()); } else { - item.insert_text(field_with_receiver(db, receiver.as_ref(), &escaped_name)); + item.insert_text(field_with_receiver( + db, + receiver.as_ref(), + &escaped_name, + ctx.completion.edition, + )); } if let Some(receiver) = &dot_access.receiver { if let Some(original) = ctx.completion.sema.original_ast_node(receiver.clone()) { @@ -200,10 +209,11 @@ fn field_with_receiver( db: &RootDatabase, receiver: Option<&hir::Name>, field_name: &str, + edition: Edition, ) -> SmolStr { receiver.map_or_else( || field_name.into(), - |receiver| format_smolstr!("{}.{field_name}", receiver.display(db)), + |receiver| format_smolstr!("{}.{field_name}", receiver.display(db, edition)), ) } @@ -216,9 +226,16 @@ pub(crate) fn render_tuple_field( let mut item = CompletionItem::new( SymbolKind::Field, ctx.source_range(), - field_with_receiver(ctx.db(), receiver.as_ref(), &field.to_string()), + field_with_receiver( + ctx.db(), + receiver.as_ref(), + &field.to_string(), + ctx.completion.edition, + ), + ctx.completion.edition, ); - item.detail(ty.display(ctx.db()).to_string()).lookup_by(field.to_string()); + item.detail(ty.display(ctx.db(), ctx.completion.edition).to_string()) + .lookup_by(field.to_string()); item.build(ctx.db()) } @@ -226,8 +243,12 @@ pub(crate) fn render_type_inference( ty_string: String, ctx: &CompletionContext<'_>, ) -> CompletionItem { - let mut builder = - CompletionItem::new(CompletionItemKind::InferredType, ctx.source_range(), ty_string); + let mut builder = CompletionItem::new( + CompletionItemKind::InferredType, + ctx.source_range(), + ty_string, + ctx.edition, + ); builder.set_relevance(CompletionRelevance { is_definite: true, ..Default::default() }); builder.build(ctx.db) } @@ -296,7 +317,7 @@ pub(crate) fn render_expr( let cfg = ctx.config.import_path_config(); - let label = expr.gen_source_code(&ctx.scope, &mut label_formatter, cfg).ok()?; + let label = expr.gen_source_code(&ctx.scope, &mut label_formatter, cfg, ctx.edition).ok()?; let source_range = match ctx.original_token.parent() { Some(node) => match node.ancestors().find_map(ast::Path::cast) { @@ -306,10 +327,13 @@ pub(crate) fn render_expr( None => ctx.source_range(), }; - let mut item = CompletionItem::new(CompletionItemKind::Expression, source_range, label); + let mut item = + CompletionItem::new(CompletionItemKind::Expression, source_range, label, ctx.edition); - let snippet = - format!("{}$0", expr.gen_source_code(&ctx.scope, &mut snippet_formatter, cfg).ok()?); + let snippet = format!( + "{}$0", + expr.gen_source_code(&ctx.scope, &mut snippet_formatter, cfg, ctx.edition).ok()? + ); let edit = TextEdit::replace(source_range, snippet); item.snippet_edit(ctx.config.snippet_cap?, edit); item.documentation(Documentation::new(String::from("Autogenerated expression by term search"))); @@ -396,10 +420,10 @@ fn render_resolution_path( let config = completion.config; let requires_import = import_to_add.is_some(); - let name = local_name.display_no_db().to_smolstr(); + let name = local_name.display_no_db(ctx.completion.edition).to_smolstr(); let mut item = render_resolution_simple_(ctx, &local_name, import_to_add, resolution); - if local_name.is_escaped() { - item.insert_text(local_name.display_no_db().to_smolstr()); + if local_name.is_escaped(completion.edition) { + item.insert_text(local_name.display_no_db(completion.edition).to_smolstr()); } // Add `<>` for generic types let type_path_no_ty_args = matches!( @@ -421,14 +445,17 @@ fn render_resolution_path( item.lookup_by(name.clone()) .label(SmolStr::from_iter([&name, "<…>"])) .trigger_call_info() - .insert_snippet(cap, format!("{}<$0>", local_name.display(db))); + .insert_snippet( + cap, + format!("{}<$0>", local_name.display(db, completion.edition)), + ); } } } let mut set_item_relevance = |ty: Type| { if !ty.is_unknown() { - item.detail(ty.display(db).to_string()); + item.detail(ty.display(db, completion.edition).to_string()); } item.set_relevance(CompletionRelevance { @@ -485,6 +512,7 @@ fn render_resolution_simple_( kind, ctx.source_range(), local_name.unescaped().display(db).to_smolstr(), + ctx.completion.edition, ); item.set_relevance(ctx.completion_relevance()) .set_documentation(scope_def_docs(db, resolution)) diff --git a/crates/ide-completion/src/render/const_.rs b/crates/ide-completion/src/render/const_.rs index 3bfec0de6b..415d87c623 100644 --- a/crates/ide-completion/src/render/const_.rs +++ b/crates/ide-completion/src/render/const_.rs @@ -14,11 +14,14 @@ pub(crate) fn render_const(ctx: RenderContext<'_>, const_: hir::Const) -> Option fn render(ctx: RenderContext<'_>, const_: hir::Const) -> Option { let db = ctx.db(); let name = const_.name(db)?; - let (name, escaped_name) = - (name.unescaped().display(db).to_smolstr(), name.display(db).to_smolstr()); - let detail = const_.display(db).to_string(); + let (name, escaped_name) = ( + name.unescaped().display(db).to_smolstr(), + name.display(db, ctx.completion.edition).to_smolstr(), + ); + let detail = const_.display(db, ctx.completion.edition).to_string(); - let mut item = CompletionItem::new(SymbolKind::Const, ctx.source_range(), name); + let mut item = + CompletionItem::new(SymbolKind::Const, ctx.source_range(), name, ctx.completion.edition); item.set_documentation(ctx.docs(const_)) .set_deprecated(ctx.is_deprecated(const_) || ctx.is_deprecated_assoc_item(const_)) .detail(detail) @@ -26,7 +29,7 @@ fn render(ctx: RenderContext<'_>, const_: hir::Const) -> Option if let Some(actm) = const_.as_assoc_item(db) { if let Some(trt) = actm.container_or_implemented_trait(db) { - item.trait_name(trt.name(db).display_no_db().to_smolstr()); + item.trait_name(trt.name(db).display_no_db(ctx.completion.edition).to_smolstr()); } } item.insert_text(escaped_name); diff --git a/crates/ide-completion/src/render/function.rs b/crates/ide-completion/src/render/function.rs index 05b2d0ae38..74092b53f5 100644 --- a/crates/ide-completion/src/render/function.rs +++ b/crates/ide-completion/src/render/function.rs @@ -4,7 +4,7 @@ use hir::{db::HirDatabase, AsAssocItem, HirDisplay}; use ide_db::{SnippetCap, SymbolKind}; use itertools::Itertools; use stdx::{format_to, to_lower_snake_case}; -use syntax::{format_smolstr, AstNode, SmolStr, ToSmolStr}; +use syntax::{format_smolstr, AstNode, Edition, SmolStr, ToSmolStr}; use crate::{ context::{CompletionContext, DotAccess, DotAccessKind, PathCompletionCtx, PathKind}, @@ -62,9 +62,16 @@ fn render( receiver.unescaped().display(ctx.db()), name.unescaped().display(ctx.db()) ), - format_smolstr!("{}.{}", receiver.display(ctx.db()), name.display(ctx.db())), + format_smolstr!( + "{}.{}", + receiver.display(ctx.db(), completion.edition), + name.display(ctx.db(), completion.edition) + ), + ), + _ => ( + name.unescaped().display(db).to_smolstr(), + name.display(db, completion.edition).to_smolstr(), ), - _ => (name.unescaped().display(db).to_smolstr(), name.display(db).to_smolstr()), }; let has_self_param = func.self_param(db).is_some(); let mut item = CompletionItem::new( @@ -75,6 +82,7 @@ fn render( }), ctx.source_range(), call.clone(), + completion.edition, ); let ret_type = func.ret_type(db); @@ -141,9 +149,9 @@ fn render( } let detail = if ctx.completion.config.full_function_signatures { - detail_full(db, func) + detail_full(db, func, ctx.completion.edition) } else { - detail(db, func) + detail(db, func, ctx.completion.edition) }; item.set_documentation(ctx.docs(func)) .set_deprecated(ctx.is_deprecated(func) || ctx.is_deprecated_assoc_item(func)) @@ -161,7 +169,9 @@ fn render( None => { if let Some(actm) = assoc_item { if let Some(trt) = actm.container_or_implemented_trait(db) { - item.trait_name(trt.name(db).display_no_db().to_smolstr()); + item.trait_name( + trt.name(db).display_no_db(ctx.completion.edition).to_smolstr(), + ); } } } @@ -219,7 +229,7 @@ pub(super) fn add_call_parens<'b>( params.iter().enumerate().format_with(", ", |(index, param), f| { match param.name(ctx.db) { Some(n) => { - let smol_str = n.display_no_db().to_smolstr(); + let smol_str = n.display_no_db(ctx.edition).to_smolstr(); let text = smol_str.as_str().trim_start_matches('_'); let ref_ = ref_of_param(ctx, text, param.ty()); f(&format_args!("${{{}:{ref_}{text}}}", index + offset)) @@ -238,7 +248,7 @@ pub(super) fn add_call_parens<'b>( format!( "{}(${{1:{}}}{}{})$0", escaped_name, - self_param.display(ctx.db), + self_param.display(ctx.db, ctx.edition), if params.is_empty() { "" } else { ", " }, function_params_snippet ) @@ -276,7 +286,7 @@ fn ref_of_param(ctx: &CompletionContext<'_>, arg: &str, ty: &hir::Type) -> &'sta "" } -fn detail(db: &dyn HirDatabase, func: hir::Function) -> String { +fn detail(db: &dyn HirDatabase, func: hir::Function, edition: Edition) -> String { let mut ret_ty = func.ret_type(db); let mut detail = String::new(); @@ -293,15 +303,15 @@ fn detail(db: &dyn HirDatabase, func: hir::Function) -> String { format_to!(detail, "unsafe "); } - format_to!(detail, "fn({})", params_display(db, func)); + format_to!(detail, "fn({})", params_display(db, func, edition)); if !ret_ty.is_unit() { - format_to!(detail, " -> {}", ret_ty.display(db)); + format_to!(detail, " -> {}", ret_ty.display(db, edition)); } detail } -fn detail_full(db: &dyn HirDatabase, func: hir::Function) -> String { - let signature = format!("{}", func.display(db)); +fn detail_full(db: &dyn HirDatabase, func: hir::Function, edition: Edition) -> String { + let signature = format!("{}", func.display(db, edition)); let mut detail = String::with_capacity(signature.len()); for segment in signature.split_whitespace() { @@ -315,16 +325,16 @@ fn detail_full(db: &dyn HirDatabase, func: hir::Function) -> String { detail } -fn params_display(db: &dyn HirDatabase, func: hir::Function) -> String { +fn params_display(db: &dyn HirDatabase, func: hir::Function, edition: Edition) -> String { if let Some(self_param) = func.self_param(db) { let assoc_fn_params = func.assoc_fn_params(db); let params = assoc_fn_params .iter() .skip(1) // skip the self param because we are manually handling that - .map(|p| p.ty().display(db)); + .map(|p| p.ty().display(db, edition)); format!( "{}{}", - self_param.display(db), + self_param.display(db, edition), params.format_with("", |display, f| { f(&", ")?; f(&display) @@ -332,7 +342,7 @@ fn params_display(db: &dyn HirDatabase, func: hir::Function) -> String { ) } else { let assoc_fn_params = func.assoc_fn_params(db); - assoc_fn_params.iter().map(|p| p.ty().display(db)).join(", ") + assoc_fn_params.iter().map(|p| p.ty().display(db, edition)).join(", ") } } diff --git a/crates/ide-completion/src/render/literal.rs b/crates/ide-completion/src/render/literal.rs index 27435307d5..c71356e530 100644 --- a/crates/ide-completion/src/render/literal.rs +++ b/crates/ide-completion/src/render/literal.rs @@ -76,16 +76,16 @@ fn render( }; let (qualified_name, escaped_qualified_name) = ( qualified_name.unescaped().display(ctx.db()).to_string(), - qualified_name.display(ctx.db()).to_string(), + qualified_name.display(ctx.db(), completion.edition).to_string(), ); let snippet_cap = ctx.snippet_cap(); let mut rendered = match kind { StructKind::Tuple if should_add_parens => { - render_tuple_lit(db, snippet_cap, &fields, &escaped_qualified_name) + render_tuple_lit(db, snippet_cap, &fields, &escaped_qualified_name, completion.edition) } StructKind::Record if should_add_parens => { - render_record_lit(db, snippet_cap, &fields, &escaped_qualified_name) + render_record_lit(db, snippet_cap, &fields, &escaped_qualified_name, completion.edition) } _ => RenderedLiteral { literal: escaped_qualified_name.clone(), @@ -103,7 +103,10 @@ fn render( } let label = format_literal_label(&qualified_name, kind, snippet_cap); let lookup = if qualified { - format_literal_lookup(&short_qualified_name.display(ctx.db()).to_string(), kind) + format_literal_lookup( + &short_qualified_name.display(ctx.db(), completion.edition).to_string(), + kind, + ) } else { format_literal_lookup(&qualified_name, kind) }; @@ -112,6 +115,7 @@ fn render( CompletionItemKind::SymbolKind(thing.symbol_kind()), ctx.source_range(), label, + completion.edition, ); item.lookup_by(lookup); diff --git a/crates/ide-completion/src/render/macro_.rs b/crates/ide-completion/src/render/macro_.rs index de715bcbfa..6490171fbb 100644 --- a/crates/ide-completion/src/render/macro_.rs +++ b/crates/ide-completion/src/render/macro_.rs @@ -46,8 +46,10 @@ fn render( ctx.source_range() }; - let (name, escaped_name) = - (name.unescaped().display(ctx.db()).to_smolstr(), name.display(ctx.db()).to_smolstr()); + let (name, escaped_name) = ( + name.unescaped().display(ctx.db()).to_smolstr(), + name.display(ctx.db(), completion.edition).to_smolstr(), + ); let docs = ctx.docs(macro_); let docs_str = docs.as_ref().map(Documentation::as_str).unwrap_or_default(); let is_fn_like = macro_.is_fn_like(completion.db); @@ -59,9 +61,10 @@ fn render( SymbolKind::from(macro_.kind(completion.db)), source_range, label(&ctx, needs_bang, bra, ket, &name), + completion.edition, ); item.set_deprecated(ctx.is_deprecated(macro_)) - .detail(macro_.display(completion.db).to_string()) + .detail(macro_.display(completion.db, completion.edition).to_string()) .set_documentation(docs) .set_relevance(ctx.completion_relevance()); diff --git a/crates/ide-completion/src/render/pattern.rs b/crates/ide-completion/src/render/pattern.rs index 598b8762b6..5675dfb5c6 100644 --- a/crates/ide-completion/src/render/pattern.rs +++ b/crates/ide-completion/src/render/pattern.rs @@ -3,7 +3,7 @@ use hir::{db::HirDatabase, Name, StructKind}; use ide_db::{documentation::HasDocs, SnippetCap}; use itertools::Itertools; -use syntax::{SmolStr, ToSmolStr}; +use syntax::{Edition, SmolStr, ToSmolStr}; use crate::{ context::{ParamContext, ParamKind, PathCompletionCtx, PatternContext}, @@ -31,8 +31,10 @@ pub(crate) fn render_struct_pat( } let name = local_name.unwrap_or_else(|| strukt.name(ctx.db())); - let (name, escaped_name) = - (name.unescaped().display(ctx.db()).to_smolstr(), name.display(ctx.db()).to_smolstr()); + let (name, escaped_name) = ( + name.unescaped().display(ctx.db()).to_smolstr(), + name.display(ctx.db(), ctx.completion.edition).to_smolstr(), + ); let kind = strukt.kind(ctx.db()); let label = format_literal_label(name.as_str(), kind, ctx.snippet_cap()); let lookup = format_literal_lookup(name.as_str(), kind); @@ -60,13 +62,13 @@ pub(crate) fn render_variant_pat( let (name, escaped_name) = match path { Some(path) => ( path.unescaped().display(ctx.db()).to_string().into(), - path.display(ctx.db()).to_string().into(), + path.display(ctx.db(), ctx.completion.edition).to_string().into(), ), None => { let name = local_name.unwrap_or_else(|| variant.name(ctx.db())); let it = ( name.unescaped().display(ctx.db()).to_smolstr(), - name.display(ctx.db()).to_smolstr(), + name.display(ctx.db(), ctx.completion.edition).to_smolstr(), ); it } @@ -119,7 +121,12 @@ fn build_completion( relevance.type_match = super::compute_type_match(ctx.completion, &adt_ty); } - let mut item = CompletionItem::new(CompletionItemKind::Binding, ctx.source_range(), label); + let mut item = CompletionItem::new( + CompletionItemKind::Binding, + ctx.source_range(), + label, + ctx.completion.edition, + ); item.set_documentation(ctx.docs(def)) .set_deprecated(ctx.is_deprecated(def)) .detail(&pat) @@ -142,9 +149,14 @@ fn render_pat( ) -> Option { let mut pat = match kind { StructKind::Tuple => render_tuple_as_pat(ctx.snippet_cap(), fields, name, fields_omitted), - StructKind::Record => { - render_record_as_pat(ctx.db(), ctx.snippet_cap(), fields, name, fields_omitted) - } + StructKind::Record => render_record_as_pat( + ctx.db(), + ctx.snippet_cap(), + fields, + name, + fields_omitted, + ctx.completion.edition, + ), StructKind::Unit => name.to_owned(), }; @@ -173,6 +185,7 @@ fn render_record_as_pat( fields: &[hir::Field], name: &str, fields_omitted: bool, + edition: Edition, ) -> String { let fields = fields.iter(); match snippet_cap { @@ -180,7 +193,7 @@ fn render_record_as_pat( format!( "{name} {{ {}{} }}", fields.enumerate().format_with(", ", |(idx, field), f| { - f(&format_args!("{}${}", field.name(db).display(db.upcast()), idx + 1)) + f(&format_args!("{}${}", field.name(db).display(db.upcast(), edition), idx + 1)) }), if fields_omitted { ", .." } else { "" }, name = name @@ -189,7 +202,7 @@ fn render_record_as_pat( None => { format!( "{name} {{ {}{} }}", - fields.map(|field| field.name(db).display_no_db().to_smolstr()).format(", "), + fields.map(|field| field.name(db).display_no_db(edition).to_smolstr()).format(", "), if fields_omitted { ", .." } else { "" }, name = name ) diff --git a/crates/ide-completion/src/render/type_alias.rs b/crates/ide-completion/src/render/type_alias.rs index b81caf2422..09eb19201c 100644 --- a/crates/ide-completion/src/render/type_alias.rs +++ b/crates/ide-completion/src/render/type_alias.rs @@ -33,14 +33,22 @@ fn render( let (name, escaped_name) = if with_eq { ( SmolStr::from_iter([&name.unescaped().display(db).to_smolstr(), " = "]), - SmolStr::from_iter([&name.display_no_db().to_smolstr(), " = "]), + SmolStr::from_iter([&name.display_no_db(ctx.completion.edition).to_smolstr(), " = "]), ) } else { - (name.unescaped().display(db).to_smolstr(), name.display_no_db().to_smolstr()) + ( + name.unescaped().display(db).to_smolstr(), + name.display_no_db(ctx.completion.edition).to_smolstr(), + ) }; - let detail = type_alias.display(db).to_string(); + let detail = type_alias.display(db, ctx.completion.edition).to_string(); - let mut item = CompletionItem::new(SymbolKind::TypeAlias, ctx.source_range(), name); + let mut item = CompletionItem::new( + SymbolKind::TypeAlias, + ctx.source_range(), + name, + ctx.completion.edition, + ); item.set_documentation(ctx.docs(type_alias)) .set_deprecated(ctx.is_deprecated(type_alias) || ctx.is_deprecated_assoc_item(type_alias)) .detail(detail) @@ -48,7 +56,7 @@ fn render( if let Some(actm) = type_alias.as_assoc_item(db) { if let Some(trt) = actm.container_or_implemented_trait(db) { - item.trait_name(trt.name(db).display_no_db().to_smolstr()); + item.trait_name(trt.name(db).display_no_db(ctx.completion.edition).to_smolstr()); } } item.insert_text(escaped_name); diff --git a/crates/ide-completion/src/render/union_literal.rs b/crates/ide-completion/src/render/union_literal.rs index ca7593c122..e053e299d9 100644 --- a/crates/ide-completion/src/render/union_literal.rs +++ b/crates/ide-completion/src/render/union_literal.rs @@ -22,21 +22,29 @@ pub(crate) fn render_union_literal( let name = local_name.unwrap_or_else(|| un.name(ctx.db())); let (qualified_name, escaped_qualified_name) = match path { - Some(p) => (p.unescaped().display(ctx.db()).to_string(), p.display(ctx.db()).to_string()), - None => { - (name.unescaped().display(ctx.db()).to_string(), name.display(ctx.db()).to_string()) - } + Some(p) => ( + p.unescaped().display(ctx.db()).to_string(), + p.display(ctx.db(), ctx.completion.edition).to_string(), + ), + None => ( + name.unescaped().display(ctx.db()).to_string(), + name.display(ctx.db(), ctx.completion.edition).to_string(), + ), }; let label = format_literal_label( - &name.display_no_db().to_smolstr(), + &name.display_no_db(ctx.completion.edition).to_smolstr(), StructKind::Record, ctx.snippet_cap(), ); - let lookup = format_literal_lookup(&name.display_no_db().to_smolstr(), StructKind::Record); + let lookup = format_literal_lookup( + &name.display_no_db(ctx.completion.edition).to_smolstr(), + StructKind::Record, + ); let mut item = CompletionItem::new( CompletionItemKind::SymbolKind(SymbolKind::Union), ctx.source_range(), label, + ctx.completion.edition, ); item.lookup_by(lookup); @@ -54,7 +62,10 @@ pub(crate) fn render_union_literal( escaped_qualified_name, fields .iter() - .map(|field| field.name(ctx.db()).display_no_db().to_smolstr()) + .map(|field| field + .name(ctx.db()) + .display_no_db(ctx.completion.edition) + .to_smolstr()) .format(",") ) } else { @@ -62,7 +73,10 @@ pub(crate) fn render_union_literal( "{} {{ {} }}", escaped_qualified_name, fields.iter().format_with(", ", |field, f| { - f(&format_args!("{}: ()", field.name(ctx.db()).display(ctx.db()))) + f(&format_args!( + "{}: ()", + field.name(ctx.db()).display(ctx.db(), ctx.completion.edition) + )) }) ) }; @@ -73,8 +87,8 @@ pub(crate) fn render_union_literal( fields.iter().format_with(", ", |field, f| { f(&format_args!( "{}: {}", - field.name(ctx.db()).display(ctx.db()), - field.ty(ctx.db()).display(ctx.db()) + field.name(ctx.db()).display(ctx.db(), ctx.completion.edition), + field.ty(ctx.db()).display(ctx.db(), ctx.completion.edition) )) }), if fields_omitted { ", .." } else { "" } diff --git a/crates/ide-completion/src/render/variant.rs b/crates/ide-completion/src/render/variant.rs index bc2df9e39f..d8516ea107 100644 --- a/crates/ide-completion/src/render/variant.rs +++ b/crates/ide-completion/src/render/variant.rs @@ -4,7 +4,7 @@ use crate::context::CompletionContext; use hir::{db::HirDatabase, sym, HasAttrs, HasCrate, HasVisibility, HirDisplay, StructKind}; use ide_db::SnippetCap; use itertools::Itertools; -use syntax::SmolStr; +use syntax::{Edition, SmolStr}; /// A rendered struct, union, or enum variant, split into fields for actual /// auto-completion (`literal`, using `field: ()`) and display in the @@ -21,20 +21,29 @@ pub(crate) fn render_record_lit( snippet_cap: Option, fields: &[hir::Field], path: &str, + edition: Edition, ) -> RenderedLiteral { if snippet_cap.is_none() { return RenderedLiteral { literal: path.to_owned(), detail: path.to_owned() }; } let completions = fields.iter().enumerate().format_with(", ", |(idx, field), f| { if snippet_cap.is_some() { - f(&format_args!("{}: ${{{}:()}}", field.name(db).display(db.upcast()), idx + 1)) + f(&format_args!( + "{}: ${{{}:()}}", + field.name(db).display(db.upcast(), edition), + idx + 1 + )) } else { - f(&format_args!("{}: ()", field.name(db).display(db.upcast()))) + f(&format_args!("{}: ()", field.name(db).display(db.upcast(), edition))) } }); let types = fields.iter().format_with(", ", |field, f| { - f(&format_args!("{}: {}", field.name(db).display(db.upcast()), field.ty(db).display(db))) + f(&format_args!( + "{}: {}", + field.name(db).display(db.upcast(), edition), + field.ty(db).display(db, edition) + )) }); RenderedLiteral { @@ -50,6 +59,7 @@ pub(crate) fn render_tuple_lit( snippet_cap: Option, fields: &[hir::Field], path: &str, + edition: Edition, ) -> RenderedLiteral { if snippet_cap.is_none() { return RenderedLiteral { literal: path.to_owned(), detail: path.to_owned() }; @@ -62,7 +72,7 @@ pub(crate) fn render_tuple_lit( } }); - let types = fields.iter().format_with(", ", |field, f| f(&field.ty(db).display(db))); + let types = fields.iter().format_with(", ", |field, f| f(&field.ty(db).display(db, edition))); RenderedLiteral { literal: format!("{path}({completions})"), diff --git a/crates/ide-completion/src/tests.rs b/crates/ide-completion/src/tests.rs index 415f2afeeb..04ba7e1f41 100644 --- a/crates/ide-completion/src/tests.rs +++ b/crates/ide-completion/src/tests.rs @@ -17,6 +17,7 @@ mod item_list; mod pattern; mod predicate; mod proc_macros; +mod raw_identifiers; mod record; mod special; mod type_pos; @@ -105,22 +106,35 @@ pub(crate) fn completion_list_with_trigger_character( completion_list_with_config(TEST_CONFIG, ra_fixture, true, trigger_character) } +fn completion_list_with_config_raw( + config: CompletionConfig, + ra_fixture: &str, + include_keywords: bool, + trigger_character: Option, +) -> Vec { + // filter out all but one built-in type completion for smaller test outputs + let items = get_all_items(config, ra_fixture, trigger_character); + items + .into_iter() + .filter(|it| it.kind != CompletionItemKind::BuiltinType || it.label == "u32") + .filter(|it| include_keywords || it.kind != CompletionItemKind::Keyword) + .filter(|it| include_keywords || it.kind != CompletionItemKind::Snippet) + .sorted_by_key(|it| (it.kind, it.label.clone(), it.detail.as_ref().map(ToOwned::to_owned))) + .collect() +} + fn completion_list_with_config( config: CompletionConfig, ra_fixture: &str, include_keywords: bool, trigger_character: Option, ) -> String { - // filter out all but one built-in type completion for smaller test outputs - let items = get_all_items(config, ra_fixture, trigger_character); - let items = items - .into_iter() - .filter(|it| it.kind != CompletionItemKind::BuiltinType || it.label == "u32") - .filter(|it| include_keywords || it.kind != CompletionItemKind::Keyword) - .filter(|it| include_keywords || it.kind != CompletionItemKind::Snippet) - .sorted_by_key(|it| (it.kind, it.label.clone(), it.detail.as_ref().map(ToOwned::to_owned))) - .collect(); - render_completion_list(items) + render_completion_list(completion_list_with_config_raw( + config, + ra_fixture, + include_keywords, + trigger_character, + )) } /// Creates analysis from a multi-file fixture, returns positions marked with $0. diff --git a/crates/ide-completion/src/tests/flyimport.rs b/crates/ide-completion/src/tests/flyimport.rs index 158dbaf1b1..8350fdcc4c 100644 --- a/crates/ide-completion/src/tests/flyimport.rs +++ b/crates/ide-completion/src/tests/flyimport.rs @@ -1633,3 +1633,39 @@ fn main() { "#]], ); } + +#[test] +fn trait_impl_on_slice_method_on_deref_slice_type() { + check( + r#" +//- minicore: deref, sized +struct SliceDeref; +impl core::ops::Deref for SliceDeref { + type Target = [()]; + + fn deref(&self) -> &Self::Target { + &[] + } +} +fn main() { + SliceDeref.choose$0(); +} +mod module { + pub(super) trait SliceRandom { + type Item; + + fn choose(&self); + } + + impl SliceRandom for [T] { + type Item = T; + + fn choose(&self) {} + } +} +"#, + expect![[r#" + me choose (use module::SliceRandom) fn(&self) + "#]], + ); +} diff --git a/crates/ide-completion/src/tests/predicate.rs b/crates/ide-completion/src/tests/predicate.rs index 62eb642b3b..46a3e97d3e 100644 --- a/crates/ide-completion/src/tests/predicate.rs +++ b/crates/ide-completion/src/tests/predicate.rs @@ -19,7 +19,7 @@ struct Foo<'lt, T, const C: usize> where $0 {} en Enum Enum ma makro!(…) macro_rules! makro md module - st Foo<…> Foo<{unknown}, _> + st Foo<…> Foo<'_, {unknown}, _> st Record Record st Tuple Tuple st Unit Unit @@ -92,7 +92,7 @@ struct Foo<'lt, T, const C: usize> where for<'a> $0 {} en Enum Enum ma makro!(…) macro_rules! makro md module - st Foo<…> Foo<{unknown}, _> + st Foo<…> Foo<'_, {unknown}, _> st Record Record st Tuple Tuple st Unit Unit diff --git a/crates/ide-completion/src/tests/raw_identifiers.rs b/crates/ide-completion/src/tests/raw_identifiers.rs new file mode 100644 index 0000000000..bc630189ed --- /dev/null +++ b/crates/ide-completion/src/tests/raw_identifiers.rs @@ -0,0 +1,84 @@ +use base_db::SourceDatabase; +use expect_test::{expect, Expect}; +use itertools::Itertools; + +use crate::tests::{completion_list_with_config_raw, position, TEST_CONFIG}; + +fn check(ra_fixture: &str, expect: Expect) { + let completions = completion_list_with_config_raw(TEST_CONFIG, ra_fixture, true, None); + let (db, position) = position(ra_fixture); + let mut actual = db.file_text(position.file_id).to_string(); + completions + .into_iter() + .exactly_one() + .expect("more than one completion") + .text_edit + .apply(&mut actual); + expect.assert_eq(&actual); +} + +#[test] +fn keyword_since_edition_completes_without_raw_on_old_edition() { + check( + r#" +//- /a.rs crate:a edition:2015 +pub fn dyn() {} + +//- /b.rs crate:b edition:2015 deps:a new_source_root:local +fn foo() { + a::dyn$0 +"#, + expect![[r#" + fn foo() { + a::dyn()$0 + "#]], + ); + + check( + r#" +//- /a.rs crate:a edition:2018 +pub fn r#dyn() {} + +//- /b.rs crate:b edition:2015 deps:a new_source_root:local +fn foo() { + a::dyn$0 +"#, + expect![[r#" + fn foo() { + a::dyn()$0 + "#]], + ); +} + +#[test] +fn keyword_since_edition_completes_with_raw_on_new_edition() { + check( + r#" +//- /a.rs crate:a edition:2015 +pub fn dyn() {} + +//- /b.rs crate:b edition:2018 deps:a new_source_root:local +fn foo() { + a::dyn$0 +"#, + expect![[r#" + fn foo() { + a::r#dyn()$0 + "#]], + ); + + check( + r#" +//- /a.rs crate:a edition:2018 +pub fn r#dyn() {} + +//- /b.rs crate:b edition:2018 deps:a new_source_root:local +fn foo() { + a::dyn$0 +"#, + expect![[r#" + fn foo() { + a::r#dyn()$0 + "#]], + ); +} diff --git a/crates/ide-completion/src/tests/type_pos.rs b/crates/ide-completion/src/tests/type_pos.rs index ff38c16108..db4ac9381c 100644 --- a/crates/ide-completion/src/tests/type_pos.rs +++ b/crates/ide-completion/src/tests/type_pos.rs @@ -20,8 +20,8 @@ struct Foo<'lt, T, const C: usize> { en Enum Enum ma makro!(…) macro_rules! makro md module - sp Self Foo<{unknown}, _> - st Foo<…> Foo<{unknown}, _> + sp Self Foo<'_, {unknown}, _> + st Foo<…> Foo<'_, {unknown}, _> st Record Record st Tuple Tuple st Unit Unit @@ -45,8 +45,8 @@ struct Foo<'lt, T, const C: usize>(f$0); en Enum Enum ma makro!(…) macro_rules! makro md module - sp Self Foo<{unknown}, _> - st Foo<…> Foo<{unknown}, _> + sp Self Foo<'_, {unknown}, _> + st Foo<…> Foo<'_, {unknown}, _> st Record Record st Tuple Tuple st Unit Unit diff --git a/crates/ide-db/Cargo.toml b/crates/ide-db/Cargo.toml index 8f3cae2fa1..c078188d6d 100644 --- a/crates/ide-db/Cargo.toml +++ b/crates/ide-db/Cargo.toml @@ -19,7 +19,6 @@ tracing.workspace = true rayon.workspace = true fst = { version = "0.4.7", default-features = false } rustc-hash.workspace = true -once_cell = "1.17.0" either.workspace = true itertools.workspace = true arrayvec.workspace = true diff --git a/crates/ide-db/src/defs.rs b/crates/ide-db/src/defs.rs index 991bef344a..5d4b199908 100644 --- a/crates/ide-db/src/defs.rs +++ b/crates/ide-db/src/defs.rs @@ -14,10 +14,11 @@ use hir::{ ModuleDef, Name, PathResolution, Semantics, Static, StaticLifetime, ToolModule, Trait, TraitAlias, TupleField, TypeAlias, Variant, VariantDef, Visibility, }; +use span::Edition; use stdx::{format_to, impl_from}; use syntax::{ ast::{self, AstNode}, - match_ast, SyntaxKind, SyntaxNode, SyntaxToken, ToSmolStr, + match_ast, SyntaxKind, SyntaxNode, SyntaxToken, }; use crate::documentation::{Documentation, HasDocs}; @@ -157,6 +158,7 @@ impl Definition { &self, db: &RootDatabase, famous_defs: Option<&FamousDefs<'_, '_>>, + edition: Edition, ) -> Option { let docs = match self { Definition::Macro(it) => it.docs(db), @@ -173,8 +175,8 @@ impl Definition { Definition::BuiltinType(it) => { famous_defs.and_then(|fd| { // std exposes prim_{} modules with docstrings on the root to document the builtins - let primitive_mod = format!("prim_{}", it.name().display(fd.0.db)); - let doc_owner = find_std_module(fd, &primitive_mod)?; + let primitive_mod = format!("prim_{}", it.name().display(fd.0.db, edition)); + let doc_owner = find_std_module(fd, &primitive_mod, edition)?; doc_owner.docs(fd.0.db) }) } @@ -192,13 +194,18 @@ impl Definition { let AttributeTemplate { word, list, name_value_str } = it.template(db)?; let mut docs = "Valid forms are:".to_owned(); if word { - format_to!(docs, "\n - #\\[{}]", name.display(db)); + format_to!(docs, "\n - #\\[{}]", name.display(db, edition)); } if let Some(list) = list { - format_to!(docs, "\n - #\\[{}({})]", name.display(db), list); + format_to!(docs, "\n - #\\[{}({})]", name.display(db, edition), list); } if let Some(name_value_str) = name_value_str { - format_to!(docs, "\n - #\\[{} = {}]", name.display(db), name_value_str); + format_to!( + docs, + "\n - #\\[{} = {}]", + name.display(db, edition), + name_value_str + ); } Some(Documentation::new(docs.replace('*', "\\*"))) } @@ -218,57 +225,63 @@ impl Definition { }) } - pub fn label(&self, db: &RootDatabase) -> String { + pub fn label(&self, db: &RootDatabase, edition: Edition) -> String { match *self { - Definition::Macro(it) => it.display(db).to_string(), - Definition::Field(it) => it.display(db).to_string(), - Definition::TupleField(it) => it.display(db).to_string(), - Definition::Module(it) => it.display(db).to_string(), - Definition::Function(it) => it.display(db).to_string(), - Definition::Adt(it) => it.display(db).to_string(), - Definition::Variant(it) => it.display(db).to_string(), - Definition::Const(it) => it.display(db).to_string(), - Definition::Static(it) => it.display(db).to_string(), - Definition::Trait(it) => it.display(db).to_string(), - Definition::TraitAlias(it) => it.display(db).to_string(), - Definition::TypeAlias(it) => it.display(db).to_string(), - Definition::BuiltinType(it) => it.name().display(db).to_string(), - Definition::BuiltinLifetime(it) => it.name().display(db).to_string(), + Definition::Macro(it) => it.display(db, edition).to_string(), + Definition::Field(it) => it.display(db, edition).to_string(), + Definition::TupleField(it) => it.display(db, edition).to_string(), + Definition::Module(it) => it.display(db, edition).to_string(), + Definition::Function(it) => it.display(db, edition).to_string(), + Definition::Adt(it) => it.display(db, edition).to_string(), + Definition::Variant(it) => it.display(db, edition).to_string(), + Definition::Const(it) => it.display(db, edition).to_string(), + Definition::Static(it) => it.display(db, edition).to_string(), + Definition::Trait(it) => it.display(db, edition).to_string(), + Definition::TraitAlias(it) => it.display(db, edition).to_string(), + Definition::TypeAlias(it) => it.display(db, edition).to_string(), + Definition::BuiltinType(it) => it.name().display(db, edition).to_string(), + Definition::BuiltinLifetime(it) => it.name().display(db, edition).to_string(), Definition::Local(it) => { let ty = it.ty(db); - let ty_display = ty.display_truncated(db, None); + let ty_display = ty.display_truncated(db, None, edition); let is_mut = if it.is_mut(db) { "mut " } else { "" }; if it.is_self(db) { format!("{is_mut}self: {ty_display}") } else { let name = it.name(db); let let_kw = if it.is_param(db) { "" } else { "let " }; - format!("{let_kw}{is_mut}{}: {ty_display}", name.display(db)) + format!("{let_kw}{is_mut}{}: {ty_display}", name.display(db, edition)) } } Definition::SelfType(impl_def) => { let self_ty = &impl_def.self_ty(db); match self_ty.as_adt() { - Some(it) => it.display(db).to_string(), - None => self_ty.display(db).to_string(), + Some(it) => it.display(db, edition).to_string(), + None => self_ty.display(db, edition).to_string(), } } - Definition::GenericParam(it) => it.display(db).to_string(), - Definition::Label(it) => it.name(db).display(db).to_string(), - Definition::ExternCrateDecl(it) => it.display(db).to_string(), - Definition::BuiltinAttr(it) => format!("#[{}]", it.name(db).display(db)), - Definition::ToolModule(it) => it.name(db).display(db).to_string(), - Definition::DeriveHelper(it) => format!("derive_helper {}", it.name(db).display(db)), + Definition::GenericParam(it) => it.display(db, edition).to_string(), + Definition::Label(it) => it.name(db).display(db, edition).to_string(), + Definition::ExternCrateDecl(it) => it.display(db, edition).to_string(), + Definition::BuiltinAttr(it) => format!("#[{}]", it.name(db).display(db, edition)), + Definition::ToolModule(it) => it.name(db).display(db, edition).to_string(), + Definition::DeriveHelper(it) => { + format!("derive_helper {}", it.name(db).display(db, edition)) + } } } } -fn find_std_module(famous_defs: &FamousDefs<'_, '_>, name: &str) -> Option { +fn find_std_module( + famous_defs: &FamousDefs<'_, '_>, + name: &str, + edition: Edition, +) -> Option { let db = famous_defs.0.db; let std_crate = famous_defs.std()?; 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) + module.name(db).map_or(false, |module| module.display(db, edition).to_string() == name) }) } @@ -670,7 +683,7 @@ impl NameRefClass { hir::AssocItem::TypeAlias(it) => Some(it), _ => None, }) - .find(|alias| alias.name(sema.db).display_no_db().to_smolstr() == name_ref.text().as_str()) + .find(|alias| alias.name(sema.db).eq_ident(name_ref.text().as_str())) { return Some(NameRefClass::Definition(Definition::TypeAlias(ty))); } diff --git a/crates/ide-db/src/famous_defs.rs b/crates/ide-db/src/famous_defs.rs index 1a16a567f3..ba6e50abf6 100644 --- a/crates/ide-db/src/famous_defs.rs +++ b/crates/ide-db/src/famous_defs.rs @@ -2,7 +2,6 @@ use base_db::{CrateOrigin, LangCrateOrigin, SourceDatabase}; use hir::{Crate, Enum, Function, Macro, Module, ScopeDef, Semantics, Trait}; -use syntax::ToSmolStr; use crate::RootDatabase; @@ -199,18 +198,14 @@ impl FamousDefs<'_, '_> { for segment in path { module = module.children(db).find_map(|child| { let name = child.name(db)?; - if name.display_no_db().to_smolstr() == segment { + if name.eq_ident(segment) { Some(child) } else { None } })?; } - let def = module - .scope(db, None) - .into_iter() - .find(|(name, _def)| name.display_no_db().to_smolstr() == trait_)? - .1; + let def = module.scope(db, None).into_iter().find(|(name, _def)| name.eq_ident(trait_))?.1; Some(def) } } diff --git a/crates/ide-db/src/helpers.rs b/crates/ide-db/src/helpers.rs index e6638dde5d..84fa58d743 100644 --- a/crates/ide-db/src/helpers.rs +++ b/crates/ide-db/src/helpers.rs @@ -3,8 +3,8 @@ use std::collections::VecDeque; use base_db::SourceRootDatabase; -use hir::{Crate, DescendPreference, ItemInNs, ModuleDef, Name, Semantics}; -use span::FileId; +use hir::{Crate, ItemInNs, ModuleDef, Name, Semantics}; +use span::{Edition, FileId}; use syntax::{ ast::{self, make}, AstToken, SyntaxKind, SyntaxToken, ToSmolStr, TokenAtOffset, @@ -35,7 +35,7 @@ pub fn pick_token(mut tokens: TokenAtOffset) -> Option } /// Converts the mod path struct into its ast representation. -pub fn mod_path_to_ast(path: &hir::ModPath) -> ast::Path { +pub fn mod_path_to_ast(path: &hir::ModPath, edition: Edition) -> ast::Path { let _p = tracing::info_span!("mod_path_to_ast").entered(); let mut segments = Vec::new(); @@ -50,11 +50,9 @@ pub fn mod_path_to_ast(path: &hir::ModPath) -> ast::Path { hir::PathKind::Abs => is_abs = true, } - segments.extend( - path.segments().iter().map(|segment| { - make::path_segment(make::name_ref(&segment.display_no_db().to_smolstr())) - }), - ); + segments.extend(path.segments().iter().map(|segment| { + make::path_segment(make::name_ref(&segment.display_no_db(edition).to_smolstr())) + })); make::path_from_segments(segments, is_abs) } @@ -114,11 +112,12 @@ pub fn is_editable_crate(krate: Crate, db: &RootDatabase) -> bool { !db.source_root(source_root_id).is_library } +// FIXME: This is a weird function pub fn get_definition( sema: &Semantics<'_, RootDatabase>, token: SyntaxToken, ) -> Option { - for token in sema.descend_into_macros(DescendPreference::None, token) { + for token in sema.descend_into_macros_exact(token) { let def = IdentClass::classify_token(sema, &token).map(IdentClass::definitions_no_ops); if let Some(&[x]) = def.as_deref() { return Some(x); diff --git a/crates/ide-db/src/imports/import_assets.rs b/crates/ide-db/src/imports/import_assets.rs index 1c4c15f255..82a182806a 100644 --- a/crates/ide-db/src/imports/import_assets.rs +++ b/crates/ide-db/src/imports/import_assets.rs @@ -9,7 +9,7 @@ use itertools::{EitherOrBoth, Itertools}; use rustc_hash::{FxHashMap, FxHashSet}; use syntax::{ ast::{self, make, HasName}, - AstNode, SmolStr, SyntaxNode, ToSmolStr, + AstNode, SmolStr, SyntaxNode, }; use crate::{ @@ -459,7 +459,7 @@ fn find_import_for_segment( unresolved_first_segment: &str, ) -> Option { let segment_is_name = item_name(db, original_item) - .map(|name| name.display_no_db().to_smolstr() == unresolved_first_segment) + .map(|name| name.eq_ident(unresolved_first_segment)) .unwrap_or(false); Some(if segment_is_name { @@ -483,7 +483,7 @@ fn module_with_segment_name( }; while let Some(module) = current_module { if let Some(module_name) = module.name(db) { - if module_name.display_no_db().to_smolstr() == segment_name { + if module_name.eq_ident(segment_name) { return Some(module); } } @@ -531,40 +531,61 @@ fn trait_applicable_items( }) .collect(); - trait_candidates.retain(|&candidate_trait_id| { - // we care about the following cases: - // 1. Trait's definition crate - // 2. Definition crates for all trait's generic arguments - // a. This is recursive for fundamental types: `Into> for ()`` is OK, but - // `Into> for ()`` is *not*. - // 3. Receiver type definition crate - // a. This is recursive for fundamental types - let defining_crate_for_trait = Trait::from(candidate_trait_id).krate(db); - let Some(receiver) = trait_candidate.receiver_ty.fingerprint_for_trait_impl() else { - return false; - }; - - // in order to handle implied bounds through an associated type, keep any - // method receiver that matches `TyFingerprint::Unnameable`. this receiver - // won't be in `TraitImpls` anyways, as `TraitImpls` only contains actual - // implementations. - if matches!(receiver, TyFingerprint::Unnameable) { - return true; + let autoderef_method_receiver = { + let mut deref_chain = trait_candidate.receiver_ty.autoderef(db).collect::>(); + // As a last step, we can do array unsizing (that's the only unsizing that rustc does for method receivers!) + if let Some((ty, _len)) = deref_chain.last().and_then(|ty| ty.as_array(db)) { + let slice = Type::new_slice(ty); + deref_chain.push(slice); } + deref_chain + .into_iter() + .filter_map(|ty| Some((ty.krate(db).into(), ty.fingerprint_for_trait_impl()?))) + .sorted() + .unique() + .collect::>() + }; - let definitions_exist_in_trait_crate = db - .trait_impls_in_crate(defining_crate_for_trait.into()) - .has_impls_for_trait_and_self_ty(candidate_trait_id, receiver); + // can be empty if the entire deref chain is has no valid trait impl fingerprints + if autoderef_method_receiver.is_empty() { + return Default::default(); + } - // this is a closure for laziness: if `definitions_exist_in_trait_crate` is true, - // we can avoid a second db lookup. - let definitions_exist_in_receiver_crate = || { - db.trait_impls_in_crate(trait_candidate.receiver_ty.krate(db).into()) - .has_impls_for_trait_and_self_ty(candidate_trait_id, receiver) - }; + // in order to handle implied bounds through an associated type, keep all traits if any + // type in the deref chain matches `TyFingerprint::Unnameable`. This fingerprint + // won't be in `TraitImpls` anyways, as `TraitImpls` only contains actual implementations. + if !autoderef_method_receiver + .iter() + .any(|(_, fingerprint)| matches!(fingerprint, TyFingerprint::Unnameable)) + { + trait_candidates.retain(|&candidate_trait_id| { + // we care about the following cases: + // 1. Trait's definition crate + // 2. Definition crates for all trait's generic arguments + // a. This is recursive for fundamental types: `Into> for ()`` is OK, but + // `Into> for ()`` is *not*. + // 3. Receiver type definition crate + // a. This is recursive for fundamental types + let defining_crate_for_trait = Trait::from(candidate_trait_id).krate(db); - definitions_exist_in_trait_crate || definitions_exist_in_receiver_crate() - }); + let trait_impls_in_crate = db.trait_impls_in_crate(defining_crate_for_trait.into()); + let definitions_exist_in_trait_crate = + autoderef_method_receiver.iter().any(|&(_, fingerprint)| { + trait_impls_in_crate + .has_impls_for_trait_and_self_ty(candidate_trait_id, fingerprint) + }); + // this is a closure for laziness: if `definitions_exist_in_trait_crate` is true, + // we can avoid a second db lookup. + let definitions_exist_in_receiver_crate = || { + autoderef_method_receiver.iter().any(|&(krate, fingerprint)| { + db.trait_impls_in_crate(krate) + .has_impls_for_trait_and_self_ty(candidate_trait_id, fingerprint) + }) + }; + + definitions_exist_in_trait_crate || definitions_exist_in_receiver_crate() + }); + } let mut located_imports = FxIndexSet::default(); let mut trait_import_paths = FxHashMap::default(); diff --git a/crates/ide-db/src/path_transform.rs b/crates/ide-db/src/path_transform.rs index 0afa9163e3..49b3ca290f 100644 --- a/crates/ide-db/src/path_transform.rs +++ b/crates/ide-db/src/path_transform.rs @@ -5,6 +5,7 @@ use either::Either; use hir::{AsAssocItem, HirDisplay, ImportPathConfig, ModuleDef, SemanticsScope}; use itertools::Itertools; use rustc_hash::FxHashMap; +use span::Edition; use syntax::{ ast::{self, make, AstNode, HasGenericArgs}, ted, NodeOrToken, SyntaxNode, @@ -146,6 +147,7 @@ impl<'a> PathTransform<'a> { let mut type_substs: FxHashMap = Default::default(); let mut const_substs: FxHashMap = Default::default(); let mut defaulted_params: Vec = Default::default(); + let target_edition = target_module.krate().edition(self.source_scope.db); self.generic_def .into_iter() .flat_map(|it| it.type_or_const_params(db)) @@ -190,7 +192,7 @@ impl<'a> PathTransform<'a> { } } (Either::Left(k), None) => { - if let Some(default) = k.default(db) { + if let Some(default) = k.default(db, target_edition) { if let Some(default) = default.expr() { const_substs.insert(k, default.syntax().clone_for_update()); defaulted_params.push(Either::Right(k)); @@ -204,7 +206,9 @@ impl<'a> PathTransform<'a> { .into_iter() .flat_map(|it| it.lifetime_params(db)) .zip(self.substs.lifetimes.clone()) - .filter_map(|(k, v)| Some((k.name(db).display(db.upcast()).to_string(), v.lifetime()?))) + .filter_map(|(k, v)| { + Some((k.name(db).display(db.upcast(), target_edition).to_string(), v.lifetime()?)) + }) .collect(); let ctx = Ctx { type_substs, @@ -213,6 +217,7 @@ impl<'a> PathTransform<'a> { target_module, source_scope: self.source_scope, same_self_type: self.target_scope.has_same_self_type(self.source_scope), + target_edition, }; ctx.transform_default_values(defaulted_params); ctx @@ -226,6 +231,7 @@ struct Ctx<'a> { target_module: hir::Module, source_scope: &'a SemanticsScope<'a>, same_self_type: bool, + target_edition: Edition, } fn preorder_rev(item: &SyntaxNode) -> impl Iterator { @@ -318,7 +324,7 @@ impl Ctx<'_> { hir::ModuleDef::Trait(trait_ref), cfg, )?; - match make::ty_path(mod_path_to_ast(&found_path)) { + match make::ty_path(mod_path_to_ast(&found_path, self.target_edition)) { ast::Type::PathType(path_ty) => Some(path_ty), _ => None, } @@ -374,7 +380,7 @@ impl Ctx<'_> { }; let found_path = self.target_module.find_path(self.source_scope.db.upcast(), def, cfg)?; - let res = mod_path_to_ast(&found_path).clone_for_update(); + let res = mod_path_to_ast(&found_path, self.target_edition).clone_for_update(); if let Some(args) = path.segment().and_then(|it| it.generic_arg_list()) { if let Some(segment) = res.segment() { let old = segment.get_or_create_generic_arg_list(); @@ -417,7 +423,9 @@ impl Ctx<'_> { cfg, )?; - if let Some(qual) = mod_path_to_ast(&found_path).qualifier() { + if let Some(qual) = + mod_path_to_ast(&found_path, self.target_edition).qualifier() + { let res = make::path_concat(qual, path_ty.path()?).clone_for_update(); ted::replace(path.syntax(), res.syntax()); return Some(()); diff --git a/crates/ide-db/src/rename.rs b/crates/ide-db/src/rename.rs index 232f242828..262eefeec0 100644 --- a/crates/ide-db/src/rename.rs +++ b/crates/ide-db/src/rename.rs @@ -29,6 +29,7 @@ use span::{Edition, EditionedFileId, FileId, SyntaxContextId}; use stdx::{never, TupleExt}; use syntax::{ ast::{self, HasName}, + utils::is_raw_identifier, AstNode, SyntaxKind, TextRange, T, }; use text_edit::{TextEdit, TextEditBuilder}; @@ -72,6 +73,9 @@ impl Definition { sema: &Semantics<'_, RootDatabase>, new_name: &str, ) -> Result { + // We append `r#` if needed. + let new_name = new_name.trim_start_matches("r#"); + // self.krate() returns None if // self is a built-in attr, built-in type or tool module. // it is not allowed for these defs to be renamed. @@ -227,8 +231,7 @@ fn rename_mod( module: hir::Module, new_name: &str, ) -> Result { - if IdentifierKind::classify(module.krate().edition(sema.db), new_name)? != IdentifierKind::Ident - { + if IdentifierKind::classify(new_name)? != IdentifierKind::Ident { bail!("Invalid name `{0}`: cannot rename module to {0}", new_name); } @@ -240,7 +243,6 @@ fn rename_mod( let InFile { file_id, value: def_source } = module.definition_source(sema.db); if let ModuleSource::SourceFile(..) = def_source { - let new_name = new_name.trim_start_matches("r#"); let anchor = file_id.original_file(sema.db).file_id(); let is_mod_rs = module.is_mod_rs(sema.db); @@ -289,9 +291,14 @@ fn rename_mod( .original_file_range_opt(sema.db) .map(TupleExt::head) { + let new_name = if is_raw_identifier(new_name, file_id.edition()) { + format!("r#{new_name}") + } else { + new_name.to_owned() + }; source_change.insert_source_edit( file_id.file_id(), - TextEdit::replace(file_range.range, new_name.to_owned()), + TextEdit::replace(file_range.range, new_name), ) }; } @@ -302,7 +309,10 @@ fn rename_mod( let def = Definition::Module(module); let usages = def.usages(sema).all(); let ref_edits = usages.iter().map(|(file_id, references)| { - (EditionedFileId::file_id(file_id), source_edit_from_references(references, def, new_name)) + ( + EditionedFileId::file_id(file_id), + source_edit_from_references(references, def, new_name, file_id.edition()), + ) }); source_change.extend(ref_edits); @@ -314,12 +324,7 @@ fn rename_reference( def: Definition, new_name: &str, ) -> Result { - let ident_kind = IdentifierKind::classify( - def.krate(sema.db) - .ok_or_else(|| RenameError("definition has no krate?".into()))? - .edition(sema.db), - new_name, - )?; + let ident_kind = IdentifierKind::classify(new_name)?; if matches!( def, @@ -351,7 +356,10 @@ fn rename_reference( } let mut source_change = SourceChange::default(); source_change.extend(usages.iter().map(|(file_id, references)| { - (EditionedFileId::file_id(file_id), source_edit_from_references(references, def, new_name)) + ( + EditionedFileId::file_id(file_id), + source_edit_from_references(references, def, new_name, file_id.edition()), + ) })); let mut insert_def_edit = |def| { @@ -367,7 +375,13 @@ pub fn source_edit_from_references( references: &[FileReference], def: Definition, new_name: &str, + edition: Edition, ) -> TextEdit { + let new_name = if is_raw_identifier(new_name, edition) { + format!("r#{new_name}") + } else { + new_name.to_owned() + }; let mut edit = TextEdit::builder(); // macros can cause multiple refs to occur for the same text range, so keep track of what we have edited so far let mut edited_ranges = Vec::new(); @@ -383,10 +397,10 @@ pub fn source_edit_from_references( // to make special rewrites like shorthand syntax and such, so just rename the node in // the macro input FileReferenceNode::NameRef(name_ref) if name_range == range => { - source_edit_from_name_ref(&mut edit, name_ref, new_name, def) + source_edit_from_name_ref(&mut edit, name_ref, &new_name, def) } FileReferenceNode::Name(name) if name_range == range => { - source_edit_from_name(&mut edit, name, new_name) + source_edit_from_name(&mut edit, name, &new_name) } _ => false, }; @@ -394,7 +408,7 @@ pub fn source_edit_from_references( let (range, new_name) = match name { FileReferenceNode::Lifetime(_) => ( TextRange::new(range.start() + syntax::TextSize::from(1), range.end()), - new_name.strip_prefix('\'').unwrap_or(new_name).to_owned(), + new_name.strip_prefix('\'').unwrap_or(&new_name).to_owned(), ), _ => (range, new_name.to_owned()), }; @@ -522,6 +536,13 @@ fn source_edit_from_def( def: Definition, new_name: &str, ) -> Result<(FileId, TextEdit)> { + let new_name_edition_aware = |new_name: &str, file_id: EditionedFileId| { + if is_raw_identifier(new_name, file_id.edition()) { + format!("r#{new_name}") + } else { + new_name.to_owned() + } + }; let mut edit = TextEdit::builder(); if let Definition::Local(local) = def { let mut file_id = None; @@ -536,7 +557,7 @@ fn source_edit_from_def( { Some(FileRange { file_id: file_id2, range }) => { file_id = Some(file_id2); - edit.replace(range, new_name.to_owned()); + edit.replace(range, new_name_edition_aware(new_name, file_id2)); continue; } None => { @@ -550,7 +571,9 @@ fn source_edit_from_def( // special cases required for renaming fields/locals in Record patterns if let Some(pat_field) = pat.syntax().parent().and_then(ast::RecordPatField::cast) { if let Some(name_ref) = pat_field.name_ref() { - if new_name == name_ref.text() && pat.at_token().is_none() { + if new_name == name_ref.text().as_str().trim_start_matches("r#") + && pat.at_token().is_none() + { // Foo { field: ref mut local } -> Foo { ref mut field } // ^^^^^^ delete this // ^^^^^ replace this with `field` @@ -566,7 +589,10 @@ fn source_edit_from_def( // Foo { field: ref mut local @ local 2} -> Foo { field: ref mut new_name @ local2 } // Foo { field: ref mut local } -> Foo { field: ref mut new_name } // ^^^^^ replace this with `new_name` - edit.replace(name_range, new_name.to_owned()); + edit.replace( + name_range, + new_name_edition_aware(new_name, source.file_id), + ); } } else { // Foo { ref mut field } -> Foo { field: ref mut new_name } @@ -576,10 +602,10 @@ fn source_edit_from_def( pat.syntax().text_range().start(), format!("{}: ", pat_field.field_name().unwrap()), ); - edit.replace(name_range, new_name.to_owned()); + edit.replace(name_range, new_name_edition_aware(new_name, source.file_id)); } } else { - edit.replace(name_range, new_name.to_owned()); + edit.replace(name_range, new_name_edition_aware(new_name, source.file_id)); } } } @@ -599,7 +625,7 @@ fn source_edit_from_def( } _ => (range, new_name.to_owned()), }; - edit.replace(range, new_name); + edit.replace(range, new_name_edition_aware(&new_name, file_id)); Ok((file_id.file_id(), edit.finish())) } @@ -611,8 +637,9 @@ pub enum IdentifierKind { } impl IdentifierKind { - pub fn classify(edition: Edition, new_name: &str) -> Result { - match parser::LexedStr::single_token(edition, new_name) { + pub fn classify(new_name: &str) -> Result { + let new_name = new_name.trim_start_matches("r#"); + match parser::LexedStr::single_token(Edition::LATEST, new_name) { Some(res) => match res { (SyntaxKind::IDENT, _) => { if let Some(inner) = new_name.strip_prefix("r#") { @@ -626,6 +653,7 @@ impl IdentifierKind { (SyntaxKind::LIFETIME_IDENT, _) if new_name != "'static" && new_name != "'_" => { Ok(IdentifierKind::Lifetime) } + _ if is_raw_identifier(new_name, Edition::LATEST) => Ok(IdentifierKind::Ident), (_, Some(syntax_error)) => bail!("Invalid name `{}`: {}", new_name, syntax_error), (_, None) => bail!("Invalid name `{}`: not an identifier", new_name), }, diff --git a/crates/ide-db/src/search.rs b/crates/ide-db/src/search.rs index 9e01a6d440..12ce5a403f 100644 --- a/crates/ide-db/src/search.rs +++ b/crates/ide-db/src/search.rs @@ -5,18 +5,23 @@ //! name resolution. use std::mem; +use std::{cell::LazyCell, cmp::Reverse}; use base_db::{salsa::Database, SourceDatabase, SourceRootDatabase}; use hir::{ - sym, AsAssocItem, DefWithBody, DescendPreference, FileRange, HasAttrs, HasSource, HirFileIdExt, - InFile, InRealFile, ModuleSource, PathResolution, Semantics, Visibility, + sym, Adt, AsAssocItem, DefWithBody, FileRange, FileRangeWrapper, HasAttrs, HasContainer, + HasSource, HirFileIdExt, InFile, InFileWrapper, InRealFile, ItemContainer, ModuleSource, + PathResolution, Semantics, Visibility, }; use memchr::memmem::Finder; -use once_cell::unsync::Lazy; use parser::SyntaxKind; -use rustc_hash::FxHashMap; +use rustc_hash::{FxHashMap, FxHashSet}; use span::EditionedFileId; -use syntax::{ast, match_ast, AstNode, AstToken, SyntaxElement, TextRange, TextSize, ToSmolStr}; +use syntax::{ + ast::{self, HasName}, + match_ast, AstNode, AstToken, SmolStr, SyntaxElement, SyntaxNode, TextRange, TextSize, + ToSmolStr, +}; use triomphe::Arc; use crate::{ @@ -442,6 +447,411 @@ impl<'a> FindUsages<'a> { res } + fn scope_files<'b>( + db: &'b RootDatabase, + scope: &'b SearchScope, + ) -> impl Iterator, EditionedFileId, TextRange)> + 'b { + scope.entries.iter().map(|(&file_id, &search_range)| { + let text = db.file_text(file_id.file_id()); + let search_range = + search_range.unwrap_or_else(|| TextRange::up_to(TextSize::of(&*text))); + + (text, file_id, search_range) + }) + } + + fn match_indices<'b>( + text: &'b str, + finder: &'b Finder<'b>, + search_range: TextRange, + ) -> impl Iterator + 'b { + finder.find_iter(text.as_bytes()).filter_map(move |idx| { + let offset: TextSize = idx.try_into().unwrap(); + if !search_range.contains_inclusive(offset) { + return None; + } + // If this is not a word boundary, that means this is only part of an identifier, + // so it can't be what we're looking for. + // This speeds up short identifiers significantly. + if text[..idx] + .chars() + .next_back() + .is_some_and(|ch| matches!(ch, 'A'..='Z' | 'a'..='z' | '_')) + || text[idx + finder.needle().len()..] + .chars() + .next() + .is_some_and(|ch| matches!(ch, 'A'..='Z' | 'a'..='z' | '_' | '0'..='9')) + { + return None; + } + Some(offset) + }) + } + + fn find_nodes<'b>( + sema: &'b Semantics<'_, RootDatabase>, + name: &str, + node: &syntax::SyntaxNode, + offset: TextSize, + ) -> impl Iterator + 'b { + node.token_at_offset(offset) + .find(|it| { + // `name` is stripped of raw ident prefix. See the comment on name retrieval below. + it.text().trim_start_matches("r#") == name + }) + .into_iter() + .flat_map(move |token| { + sema.descend_into_macros_exact_if_in_macro(token) + .into_iter() + .filter_map(|it| it.parent()) + }) + } + + /// Performs a special fast search for associated functions. This is mainly intended + /// to speed up `new()` which can take a long time. + /// + /// The trick is instead of searching for `func_name` search for `TypeThatContainsContainerName::func_name`. + /// We cannot search exactly that (not even in tokens), because `ContainerName` may be aliased. + /// Instead, we perform a textual search for `ContainerName`. Then, we look for all cases where + /// `ContainerName` may be aliased (that includes `use ContainerName as Xyz` and + /// `type Xyz = ContainerName`). We collect a list of all possible aliases of `ContainerName`. + /// The list can have false positives (because there may be multiple types named `ContainerName`), + /// but it cannot have false negatives. Then, we look for `TypeThatContainsContainerNameOrAnyAlias::func_name`. + /// Those that will be found are of high chance to be actual hits (of course, we will need to verify + /// that). + /// + /// Returns true if completed the search. + // FIXME: Extend this to other cases, such as associated types/consts/enum variants (note those can be `use`d). + fn short_associated_function_fast_search( + &self, + sink: &mut dyn FnMut(EditionedFileId, FileReference) -> bool, + search_scope: &SearchScope, + name: &str, + ) -> bool { + if self.scope.is_some() { + return false; + } + + let _p = tracing::info_span!("short_associated_function_fast_search").entered(); + + let container = (|| { + let Definition::Function(function) = self.def else { + return None; + }; + if function.has_self_param(self.sema.db) { + return None; + } + match function.container(self.sema.db) { + // Only freestanding `impl`s qualify; methods from trait + // can be called from within subtraits and bounds. + ItemContainer::Impl(impl_) => { + let has_trait = impl_.trait_(self.sema.db).is_some(); + if has_trait { + return None; + } + let adt = impl_.self_ty(self.sema.db).as_adt()?; + Some(adt) + } + _ => None, + } + })(); + let Some(container) = container else { + return false; + }; + + fn has_any_name(node: &SyntaxNode, mut predicate: impl FnMut(&str) -> bool) -> bool { + node.descendants().any(|node| { + match_ast! { + match node { + ast::Name(it) => predicate(it.text().trim_start_matches("r#")), + ast::NameRef(it) => predicate(it.text().trim_start_matches("r#")), + _ => false + } + } + }) + } + + // This is a fixpoint algorithm with O(number of aliases), but most types have no or few aliases, + // so this should stay fast. + // + /// Returns `(aliases, ranges_where_Self_can_refer_to_our_type)`. + fn collect_possible_aliases( + sema: &Semantics<'_, RootDatabase>, + container: Adt, + ) -> Option<(FxHashSet, Vec>)> { + fn insert_type_alias( + db: &RootDatabase, + to_process: &mut Vec<(SmolStr, SearchScope)>, + alias_name: &str, + def: Definition, + ) { + let alias = alias_name.trim_start_matches("r#").to_smolstr(); + tracing::debug!("found alias: {alias}"); + to_process.push((alias, def.search_scope(db))); + } + + let _p = tracing::info_span!("collect_possible_aliases").entered(); + + let db = sema.db; + let container_name = container.name(db).unescaped().display(db).to_smolstr(); + let search_scope = Definition::from(container).search_scope(db); + let mut seen = FxHashSet::default(); + let mut completed = FxHashSet::default(); + let mut to_process = vec![(container_name, search_scope)]; + let mut is_possibly_self = Vec::new(); + let mut total_files_searched = 0; + + while let Some((current_to_process, current_to_process_search_scope)) = to_process.pop() + { + let is_alias = |alias: &ast::TypeAlias| { + let def = sema.to_def(alias)?; + let ty = def.ty(db); + let is_alias = ty.as_adt()? == container; + is_alias.then_some(def) + }; + + let finder = Finder::new(current_to_process.as_bytes()); + for (file_text, file_id, search_range) in + FindUsages::scope_files(db, ¤t_to_process_search_scope) + { + let tree = LazyCell::new(move || sema.parse(file_id).syntax().clone()); + + for offset in FindUsages::match_indices(&file_text, &finder, search_range) { + let usages = + FindUsages::find_nodes(sema, ¤t_to_process, &tree, offset) + .filter(|it| { + matches!(it.kind(), SyntaxKind::NAME | SyntaxKind::NAME_REF) + }); + for usage in usages { + if let Some(alias) = usage.parent().and_then(|it| { + let path = ast::PathSegment::cast(it)?.parent_path(); + let use_tree = ast::UseTree::cast(path.syntax().parent()?)?; + use_tree.rename()?.name() + }) { + if seen.insert(InFileWrapper::new( + file_id, + alias.syntax().text_range(), + )) { + tracing::debug!("found alias: {alias}"); + cov_mark::hit!(container_use_rename); + // FIXME: `use`s have no easy way to determine their search scope, but they are rare. + to_process.push(( + alias.text().to_smolstr(), + current_to_process_search_scope.clone(), + )); + } + } else if let Some(alias) = + usage.ancestors().find_map(ast::TypeAlias::cast) + { + if let Some(name) = alias.name() { + if seen.insert(InFileWrapper::new( + file_id, + name.syntax().text_range(), + )) { + if let Some(def) = is_alias(&alias) { + cov_mark::hit!(container_type_alias); + insert_type_alias( + sema.db, + &mut to_process, + name.text().as_str(), + def.into(), + ); + } else { + cov_mark::hit!(same_name_different_def_type_alias); + } + } + } + } + + // We need to account for `Self`. It can only refer to our type inside an impl. + let impl_ = 'impl_: { + for ancestor in usage.ancestors() { + if let Some(parent) = ancestor.parent() { + if let Some(parent) = ast::Impl::cast(parent) { + // Only if the GENERIC_PARAM_LIST is directly under impl, otherwise it may be in the self ty. + if matches!( + ancestor.kind(), + SyntaxKind::ASSOC_ITEM_LIST + | SyntaxKind::WHERE_CLAUSE + | SyntaxKind::GENERIC_PARAM_LIST + ) { + break; + } + if parent + .trait_() + .is_some_and(|trait_| *trait_.syntax() == ancestor) + { + break; + } + + // Otherwise, found an impl where its self ty may be our type. + break 'impl_ Some(parent); + } + } + } + None + }; + (|| { + let impl_ = impl_?; + is_possibly_self.push(sema.original_range(impl_.syntax())); + let assoc_items = impl_.assoc_item_list()?; + let type_aliases = assoc_items + .syntax() + .descendants() + .filter_map(ast::TypeAlias::cast); + for type_alias in type_aliases { + let Some(ty) = type_alias.ty() else { continue }; + let Some(name) = type_alias.name() else { continue }; + let contains_self = ty + .syntax() + .descendants_with_tokens() + .any(|node| node.kind() == SyntaxKind::SELF_TYPE_KW); + if !contains_self { + continue; + } + if seen.insert(InFileWrapper::new( + file_id, + name.syntax().text_range(), + )) { + if let Some(def) = is_alias(&type_alias) { + cov_mark::hit!(self_type_alias); + insert_type_alias( + sema.db, + &mut to_process, + name.text().as_str(), + def.into(), + ); + } else { + cov_mark::hit!(same_name_different_def_type_alias); + } + } + } + Some(()) + })(); + } + } + } + + completed.insert(current_to_process); + + total_files_searched += current_to_process_search_scope.entries.len(); + // FIXME: Maybe this needs to be relative to the project size, or at least to the initial search scope? + if total_files_searched > 20_000 && completed.len() > 100 { + // This case is extremely unlikely (even searching for `Vec::new()` on rust-analyzer does not enter + // here - it searches less than 10,000 files, and it does so in five seconds), but if we get here, + // we at a risk of entering an almost-infinite loop of growing the aliases list. So just stop and + // let normal search handle this case. + tracing::info!(aliases_count = %completed.len(), "too much aliases; leaving fast path"); + return None; + } + } + + // Impls can contain each other, so we need to deduplicate their ranges. + is_possibly_self.sort_unstable_by_key(|position| { + (position.file_id, position.range.start(), Reverse(position.range.end())) + }); + is_possibly_self.dedup_by(|pos2, pos1| { + pos1.file_id == pos2.file_id + && pos1.range.start() <= pos2.range.start() + && pos1.range.end() >= pos2.range.end() + }); + + tracing::info!(aliases_count = %completed.len(), "aliases search completed"); + + Some((completed, is_possibly_self)) + } + + fn search( + this: &FindUsages<'_>, + finder: &Finder<'_>, + name: &str, + files: impl Iterator, EditionedFileId, TextRange)>, + mut container_predicate: impl FnMut( + &SyntaxNode, + InFileWrapper, + ) -> bool, + sink: &mut dyn FnMut(EditionedFileId, FileReference) -> bool, + ) { + for (file_text, file_id, search_range) in files { + let tree = LazyCell::new(move || this.sema.parse(file_id).syntax().clone()); + + for offset in FindUsages::match_indices(&file_text, finder, search_range) { + let usages = FindUsages::find_nodes(this.sema, name, &tree, offset) + .filter_map(ast::NameRef::cast); + for usage in usages { + let found_usage = usage + .syntax() + .parent() + .and_then(ast::PathSegment::cast) + .map(|path_segment| { + container_predicate( + path_segment.parent_path().syntax(), + InFileWrapper::new(file_id, usage.syntax().text_range()), + ) + }) + .unwrap_or(false); + if found_usage { + this.found_name_ref(&usage, sink); + } + } + } + } + } + + let Some((container_possible_aliases, is_possibly_self)) = + collect_possible_aliases(self.sema, container) + else { + return false; + }; + + cov_mark::hit!(short_associated_function_fast_search); + + // FIXME: If Rust ever gains the ability to `use Struct::method` we'll also need to account for free + // functions. + let finder = Finder::new(name.as_bytes()); + // The search for `Self` may return duplicate results with `ContainerName`, so deduplicate them. + let mut self_positions = FxHashSet::default(); + tracing::info_span!("Self_search").in_scope(|| { + search( + self, + &finder, + name, + is_possibly_self.into_iter().map(|position| { + ( + self.sema.db.file_text(position.file_id.file_id()), + position.file_id, + position.range, + ) + }), + |path, name_position| { + let has_self = path + .descendants_with_tokens() + .any(|node| node.kind() == SyntaxKind::SELF_TYPE_KW); + if has_self { + self_positions.insert(name_position); + } + has_self + }, + sink, + ) + }); + tracing::info_span!("aliases_search").in_scope(|| { + search( + self, + &finder, + name, + FindUsages::scope_files(self.sema.db, search_scope), + |path, name_position| { + has_any_name(path, |name| container_possible_aliases.contains(name)) + && !self_positions.contains(&name_position) + }, + sink, + ) + }); + + true + } + pub fn search(&self, sink: &mut dyn FnMut(EditionedFileId, FileReference) -> bool) { let _p = tracing::info_span!("FindUsages:search").entered(); let sema = self.sema; @@ -488,65 +898,23 @@ impl<'a> FindUsages<'a> { Some(s) => s.as_str(), None => return, }; + + // FIXME: This should probably depend on the number of the results (specifically, the number of false results). + if name.len() <= 7 && self.short_associated_function_fast_search(sink, &search_scope, name) + { + return; + } + let finder = &Finder::new(name); let include_self_kw_refs = self.include_self_kw_refs.as_ref().map(|ty| (ty, Finder::new("Self"))); - // for<'a> |text: &'a str, name: &'a str, search_range: TextRange| -> impl Iterator + 'a { ... } - fn match_indices<'a>( - text: &'a str, - finder: &'a Finder<'a>, - search_range: TextRange, - ) -> impl Iterator + 'a { - finder.find_iter(text.as_bytes()).filter_map(move |idx| { - let offset: TextSize = idx.try_into().unwrap(); - if !search_range.contains_inclusive(offset) { - return None; - } - Some(offset) - }) - } - - // for<'a> |scope: &'a SearchScope| -> impl Iterator, EditionedFileId, TextRange)> + 'a { ... } - fn scope_files<'a>( - sema: &'a Semantics<'_, RootDatabase>, - scope: &'a SearchScope, - ) -> impl Iterator, EditionedFileId, TextRange)> + 'a { - scope.entries.iter().map(|(&file_id, &search_range)| { - let text = sema.db.file_text(file_id.file_id()); - let search_range = - search_range.unwrap_or_else(|| TextRange::up_to(TextSize::of(&*text))); - - (text, file_id, search_range) - }) - } - - let find_nodes = move |name: &str, node: &syntax::SyntaxNode, offset: TextSize| { - node.token_at_offset(offset) - .find(|it| { - // `name` is stripped of raw ident prefix. See the comment on name retrieval above. - it.text().trim_start_matches("r#") == name - }) - .into_iter() - .flat_map(move |token| { - // FIXME: There should be optimization potential here - // Currently we try to descend everything we find which - // means we call `Semantics::descend_into_macros` on - // every textual hit. That function is notoriously - // expensive even for things that do not get down mapped - // into macros. - sema.descend_into_macros(DescendPreference::None, token) - .into_iter() - .filter_map(|it| it.parent()) - }) - }; - - for (text, file_id, search_range) in scope_files(sema, &search_scope) { + for (text, file_id, search_range) in Self::scope_files(sema.db, &search_scope) { self.sema.db.unwind_if_cancelled(); - let tree = Lazy::new(move || sema.parse(file_id).syntax().clone()); + let tree = LazyCell::new(move || sema.parse(file_id).syntax().clone()); // Search for occurrences of the items name - for offset in match_indices(&text, finder, search_range) { + for offset in Self::match_indices(&text, finder, search_range) { tree.token_at_offset(offset).for_each(|token| { let Some(str_token) = ast::String::cast(token.clone()) else { return }; if let Some((range, nameres)) = @@ -556,7 +924,9 @@ impl<'a> FindUsages<'a> { } }); - for name in find_nodes(name, &tree, offset).filter_map(ast::NameLike::cast) { + for name in + Self::find_nodes(sema, name, &tree, offset).filter_map(ast::NameLike::cast) + { if match name { ast::NameLike::NameRef(name_ref) => self.found_name_ref(&name_ref, sink), ast::NameLike::Name(name) => self.found_name(&name, sink), @@ -568,8 +938,9 @@ impl<'a> FindUsages<'a> { } // Search for occurrences of the `Self` referring to our type if let Some((self_ty, finder)) = &include_self_kw_refs { - for offset in match_indices(&text, finder, search_range) { - for name_ref in find_nodes("Self", &tree, offset).filter_map(ast::NameRef::cast) + for offset in Self::match_indices(&text, finder, search_range) { + for name_ref in + Self::find_nodes(sema, "Self", &tree, offset).filter_map(ast::NameRef::cast) { if self.found_self_ty_name_ref(self_ty, &name_ref, sink) { return; @@ -587,13 +958,13 @@ impl<'a> FindUsages<'a> { let is_crate_root = module.is_crate_root().then(|| Finder::new("crate")); let finder = &Finder::new("super"); - for (text, file_id, search_range) in scope_files(sema, &scope) { + for (text, file_id, search_range) in Self::scope_files(sema.db, &scope) { self.sema.db.unwind_if_cancelled(); - let tree = Lazy::new(move || sema.parse(file_id).syntax().clone()); + let tree = LazyCell::new(move || sema.parse(file_id).syntax().clone()); - for offset in match_indices(&text, finder, search_range) { - for name_ref in - find_nodes("super", &tree, offset).filter_map(ast::NameRef::cast) + for offset in Self::match_indices(&text, finder, search_range) { + for name_ref in Self::find_nodes(sema, "super", &tree, offset) + .filter_map(ast::NameRef::cast) { if self.found_name_ref(&name_ref, sink) { return; @@ -601,9 +972,9 @@ impl<'a> FindUsages<'a> { } } if let Some(finder) = &is_crate_root { - for offset in match_indices(&text, finder, search_range) { - for name_ref in - find_nodes("crate", &tree, offset).filter_map(ast::NameRef::cast) + for offset in Self::match_indices(&text, finder, search_range) { + for name_ref in Self::find_nodes(sema, "crate", &tree, offset) + .filter_map(ast::NameRef::cast) { if self.found_name_ref(&name_ref, sink) { return; @@ -641,11 +1012,12 @@ impl<'a> FindUsages<'a> { let search_range = search_range.unwrap_or_else(|| TextRange::up_to(TextSize::of(&*text))); - let tree = Lazy::new(|| sema.parse(file_id).syntax().clone()); + let tree = LazyCell::new(|| sema.parse(file_id).syntax().clone()); let finder = &Finder::new("self"); - for offset in match_indices(&text, finder, search_range) { - for name_ref in find_nodes("self", &tree, offset).filter_map(ast::NameRef::cast) + for offset in Self::match_indices(&text, finder, search_range) { + for name_ref in + Self::find_nodes(sema, "self", &tree, offset).filter_map(ast::NameRef::cast) { if self.found_self_module_name_ref(&name_ref, sink) { return; diff --git a/crates/ide-db/src/syntax_helpers/format_string_exprs.rs b/crates/ide-db/src/syntax_helpers/format_string_exprs.rs index 8ab5a6ede3..c104aa5718 100644 --- a/crates/ide-db/src/syntax_helpers/format_string_exprs.rs +++ b/crates/ide-db/src/syntax_helpers/format_string_exprs.rs @@ -31,6 +31,7 @@ pub fn with_placeholders(args: Vec) -> Vec { .collect() } +// FIXME Remove this, we have this information in the HIR now /// Parser for a format-like string. It is more allowing in terms of string contents, /// as we expect variable placeholders to be filled with expressions. /// diff --git a/crates/ide-db/src/syntax_helpers/insert_whitespace_into_node.rs b/crates/ide-db/src/syntax_helpers/insert_whitespace_into_node.rs index 97b6d4a572..dd4a665e8e 100644 --- a/crates/ide-db/src/syntax_helpers/insert_whitespace_into_node.rs +++ b/crates/ide-db/src/syntax_helpers/insert_whitespace_into_node.rs @@ -131,5 +131,6 @@ pub fn insert_ws_into(syn: SyntaxNode) -> SyntaxNode { } fn is_text(k: SyntaxKind) -> bool { - k.is_keyword() || k.is_literal() || k == IDENT || k == UNDERSCORE + // Consider all keywords in all editions. + k.is_any_identifier() || k.is_literal() || k == UNDERSCORE } diff --git a/crates/ide-db/src/syntax_helpers/node_ext.rs b/crates/ide-db/src/syntax_helpers/node_ext.rs index 37238cc61d..91e0b4495f 100644 --- a/crates/ide-db/src/syntax_helpers/node_ext.rs +++ b/crates/ide-db/src/syntax_helpers/node_ext.rs @@ -1,6 +1,7 @@ //! Various helper functions to work with SyntaxNodes. use itertools::Itertools; use parser::T; +use span::Edition; use syntax::{ ast::{self, HasLoopBody, MacroCall, PathSegmentKind, VisibilityKind}, AstNode, AstToken, Preorder, RustLanguage, WalkEvent, @@ -456,12 +457,15 @@ impl Iterator for TreeWithDepthIterator { } /// Parses the input token tree as comma separated plain paths. -pub fn parse_tt_as_comma_sep_paths(input: ast::TokenTree) -> Option> { +pub fn parse_tt_as_comma_sep_paths( + input: ast::TokenTree, + edition: Edition, +) -> Option> { let r_paren = input.r_paren_token(); let tokens = input.syntax().children_with_tokens().skip(1).map_while(|it| match it.into_token() { // seeing a keyword means the attribute is unclosed so stop parsing here - Some(tok) if tok.kind().is_keyword() => None, + Some(tok) if tok.kind().is_keyword(edition) => None, // don't include the right token tree parenthesis if it exists tok @ Some(_) if tok == r_paren => None, // only nodes that we can find are other TokenTrees, those are unexpected in this parse though @@ -473,10 +477,12 @@ pub fn parse_tt_as_comma_sep_paths(input: ast::TokenTree) -> Option it.path(), - _ => None, - }) + syntax::hacks::parse_expr_from_str(&tokens.join(""), Edition::CURRENT).and_then( + |expr| match expr { + ast::Expr::PathExpr(it) => it.path(), + _ => None, + }, + ) }) .collect(); Some(paths) diff --git a/crates/ide-db/src/traits.rs b/crates/ide-db/src/traits.rs index 48a585bf33..82aca50d03 100644 --- a/crates/ide-db/src/traits.rs +++ b/crates/ide-db/src/traits.rs @@ -34,19 +34,20 @@ pub fn get_missing_assoc_items( // may share the same name as a function or constant. let mut impl_fns_consts = FxHashSet::default(); let mut impl_type = FxHashSet::default(); + let edition = imp.module(sema.db).krate().edition(sema.db); for item in imp.items(sema.db) { match item { hir::AssocItem::Function(it) => { - impl_fns_consts.insert(it.name(sema.db).display(sema.db).to_string()); + impl_fns_consts.insert(it.name(sema.db).display(sema.db, edition).to_string()); } hir::AssocItem::Const(it) => { if let Some(name) = it.name(sema.db) { - impl_fns_consts.insert(name.display(sema.db).to_string()); + impl_fns_consts.insert(name.display(sema.db, edition).to_string()); } } hir::AssocItem::TypeAlias(it) => { - impl_type.insert(it.name(sema.db).display(sema.db).to_string()); + impl_type.insert(it.name(sema.db).display(sema.db, edition).to_string()); } } } @@ -56,15 +57,14 @@ pub fn get_missing_assoc_items( .items(sema.db) .into_iter() .filter(|i| match i { - hir::AssocItem::Function(f) => { - !impl_fns_consts.contains(&f.name(sema.db).display(sema.db).to_string()) - } + hir::AssocItem::Function(f) => !impl_fns_consts + .contains(&f.name(sema.db).display(sema.db, edition).to_string()), hir::AssocItem::TypeAlias(t) => { - !impl_type.contains(&t.name(sema.db).display(sema.db).to_string()) + !impl_type.contains(&t.name(sema.db).display(sema.db, edition).to_string()) } hir::AssocItem::Const(c) => c .name(sema.db) - .map(|n| !impl_fns_consts.contains(&n.display(sema.db).to_string())) + .map(|n| !impl_fns_consts.contains(&n.display(sema.db, edition).to_string())) .unwrap_or_default(), }) .collect() @@ -116,6 +116,7 @@ mod tests { use expect_test::{expect, Expect}; use hir::FilePosition; use hir::Semantics; + use span::Edition; use syntax::ast::{self, AstNode}; use test_fixture::ChangeFixture; @@ -140,7 +141,7 @@ mod tests { sema.find_node_at_offset_with_descend(file.syntax(), position.offset).unwrap(); let trait_ = crate::traits::resolve_target_trait(&sema, &impl_block); let actual = match trait_ { - Some(trait_) => trait_.name(&db).display(&db).to_string(), + Some(trait_) => trait_.name(&db).display(&db, Edition::CURRENT).to_string(), None => String::new(), }; expect.assert_eq(&actual); @@ -155,7 +156,7 @@ mod tests { let items = crate::traits::get_missing_assoc_items(&sema, &impl_block); let actual = items .into_iter() - .map(|item| item.name(&db).unwrap().display(&db).to_string()) + .map(|item| item.name(&db).unwrap().display(&db, Edition::CURRENT).to_string()) .collect::>() .join("\n"); expect.assert_eq(&actual); diff --git a/crates/ide-db/src/ty_filter.rs b/crates/ide-db/src/ty_filter.rs index 5b566c5067..515bc418cb 100644 --- a/crates/ide-db/src/ty_filter.rs +++ b/crates/ide-db/src/ty_filter.rs @@ -5,10 +5,7 @@ use std::iter; use hir::Semantics; -use syntax::{ - ast::{self, make, Pat}, - ToSmolStr, -}; +use syntax::ast::{self, make, Pat}; use crate::RootDatabase; @@ -29,7 +26,7 @@ impl TryEnum { _ => return None, }; TryEnum::ALL.iter().find_map(|&var| { - if enum_.name(sema.db).display_no_db().to_smolstr() == var.type_name() { + if enum_.name(sema.db).eq_ident(var.type_name()) { return Some(var); } None diff --git a/crates/ide-db/src/use_trivial_constructor.rs b/crates/ide-db/src/use_trivial_constructor.rs index 965f432407..c3f0bf3706 100644 --- a/crates/ide-db/src/use_trivial_constructor.rs +++ b/crates/ide-db/src/use_trivial_constructor.rs @@ -1,6 +1,7 @@ //! Functionality for generating trivial constructors use hir::StructKind; +use span::Edition; use syntax::{ ast::{make, Expr, Path}, ToSmolStr, @@ -11,6 +12,7 @@ pub fn use_trivial_constructor( db: &crate::RootDatabase, path: Path, ty: &hir::Type, + edition: Edition, ) -> Option { match ty.as_adt() { Some(hir::Adt::Enum(x)) => { @@ -19,7 +21,7 @@ pub fn use_trivial_constructor( let path = make::path_qualified( path, make::path_segment(make::name_ref( - &variant.name(db).display_no_db().to_smolstr(), + &variant.name(db).display_no_db(edition).to_smolstr(), )), ); diff --git a/crates/ide-diagnostics/Cargo.toml b/crates/ide-diagnostics/Cargo.toml index 9c3a279a94..bf54f4ab32 100644 --- a/crates/ide-diagnostics/Cargo.toml +++ b/crates/ide-diagnostics/Cargo.toml @@ -18,7 +18,6 @@ either.workspace = true itertools.workspace = true serde_json.workspace = true tracing.workspace = true -once_cell = "1.17.0" # local deps stdx.workspace = true diff --git a/crates/ide-diagnostics/src/handlers/expected_function.rs b/crates/ide-diagnostics/src/handlers/expected_function.rs index 05fb1c29b3..02299197b1 100644 --- a/crates/ide-diagnostics/src/handlers/expected_function.rs +++ b/crates/ide-diagnostics/src/handlers/expected_function.rs @@ -12,7 +12,7 @@ pub(crate) fn expected_function( Diagnostic::new_with_syntax_node_ptr( ctx, DiagnosticCode::RustcHardError("E0618"), - format!("expected function, found {}", d.found.display(ctx.sema.db)), + format!("expected function, found {}", d.found.display(ctx.sema.db, ctx.edition)), d.call.map(|it| it.into()), ) .experimental() diff --git a/crates/ide-diagnostics/src/handlers/json_is_not_rust.rs b/crates/ide-diagnostics/src/handlers/json_is_not_rust.rs index 117088ca09..ccb33fed10 100644 --- a/crates/ide-diagnostics/src/handlers/json_is_not_rust.rs +++ b/crates/ide-diagnostics/src/handlers/json_is_not_rust.rs @@ -12,7 +12,7 @@ use itertools::Itertools; use stdx::{format_to, never}; use syntax::{ ast::{self, make}, - SyntaxKind, SyntaxNode, + Edition, SyntaxKind, SyntaxNode, }; use text_edit::TextEdit; @@ -104,6 +104,7 @@ pub(crate) fn json_in_items( file_id: EditionedFileId, node: &SyntaxNode, config: &DiagnosticsConfig, + edition: Edition, ) { (|| { if node.kind() == SyntaxKind::ERROR @@ -156,7 +157,11 @@ pub(crate) fn json_in_items( config.insert_use.prefix_kind, cfg, ) { - insert_use(&scope, mod_path_to_ast(&it), &config.insert_use); + insert_use( + &scope, + mod_path_to_ast(&it, edition), + &config.insert_use, + ); } } } @@ -168,7 +173,11 @@ pub(crate) fn json_in_items( config.insert_use.prefix_kind, cfg, ) { - insert_use(&scope, mod_path_to_ast(&it), &config.insert_use); + insert_use( + &scope, + mod_path_to_ast(&it, edition), + &config.insert_use, + ); } } } diff --git a/crates/ide-diagnostics/src/handlers/missing_fields.rs b/crates/ide-diagnostics/src/handlers/missing_fields.rs index ea7908525a..86c237f7b5 100644 --- a/crates/ide-diagnostics/src/handlers/missing_fields.rs +++ b/crates/ide-diagnostics/src/handlers/missing_fields.rs @@ -11,7 +11,7 @@ use stdx::format_to; use syntax::{ algo, ast::{self, make}, - AstNode, SyntaxNode, SyntaxNodePtr, ToSmolStr, + AstNode, Edition, SyntaxNode, SyntaxNodePtr, ToSmolStr, }; use text_edit::TextEdit; @@ -31,7 +31,7 @@ use crate::{fix, Diagnostic, DiagnosticCode, DiagnosticsContext}; pub(crate) fn missing_fields(ctx: &DiagnosticsContext<'_>, d: &hir::MissingFields) -> Diagnostic { let mut message = String::from("missing structure fields:\n"); for field in &d.missed_fields { - format_to!(message, "- {}\n", field.display(ctx.sema.db)); + format_to!(message, "- {}\n", field.display(ctx.sema.db, ctx.edition)); } let ptr = InFile::new( @@ -134,8 +134,9 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::MissingFields) -> Option, d: &hir::MissingFields) -> Option, d: &hir::MissingFields) -> Option, d: &hir::MissingFields) -> Option ast::Type { +fn make_ty( + ty: &hir::Type, + db: &dyn HirDatabase, + module: hir::Module, + edition: Edition, +) -> ast::Type { let ty_str = match ty.as_adt() { - Some(adt) => adt.name(db).display(db.upcast()).to_string(), + Some(adt) => adt.name(db).display(db.upcast(), edition).to_string(), None => { ty.display_source_code(db, module.into(), false).ok().unwrap_or_else(|| "_".to_owned()) } @@ -223,13 +229,13 @@ fn get_default_constructor( let famous_defs = FamousDefs(&ctx.sema, krate); if has_new_func { - Some(make::ext::expr_ty_new(&make_ty(ty, ctx.sema.db, module))) + Some(make::ext::expr_ty_new(&make_ty(ty, ctx.sema.db, module, ctx.edition))) } else if ty.as_adt() == famous_defs.core_option_Option()?.ty(ctx.sema.db).as_adt() { Some(make::ext::option_none()) } else if !ty.is_array() && ty.impls_trait(ctx.sema.db, famous_defs.core_default_Default()?, &[]) { - Some(make::ext::expr_ty_default(&make_ty(ty, ctx.sema.db, module))) + Some(make::ext::expr_ty_default(&make_ty(ty, ctx.sema.db, module, ctx.edition))) } else { None } diff --git a/crates/ide-diagnostics/src/handlers/moved_out_of_ref.rs b/crates/ide-diagnostics/src/handlers/moved_out_of_ref.rs index fa9a6577fc..06c6b0f3e4 100644 --- a/crates/ide-diagnostics/src/handlers/moved_out_of_ref.rs +++ b/crates/ide-diagnostics/src/handlers/moved_out_of_ref.rs @@ -8,7 +8,7 @@ pub(crate) fn moved_out_of_ref(ctx: &DiagnosticsContext<'_>, d: &hir::MovedOutOf Diagnostic::new_with_syntax_node_ptr( ctx, DiagnosticCode::RustcHardError("E0507"), - format!("cannot move `{}` out of reference", d.ty.display(ctx.sema.db)), + format!("cannot move `{}` out of reference", d.ty.display(ctx.sema.db, ctx.edition)), d.span, ) .experimental() // spans are broken, and I'm not sure how precise we can detect copy types diff --git a/crates/ide-diagnostics/src/handlers/mutability_errors.rs b/crates/ide-diagnostics/src/handlers/mutability_errors.rs index 00352266dd..e4b1f3ca95 100644 --- a/crates/ide-diagnostics/src/handlers/mutability_errors.rs +++ b/crates/ide-diagnostics/src/handlers/mutability_errors.rs @@ -40,7 +40,7 @@ pub(crate) fn need_mut(ctx: &DiagnosticsContext<'_>, d: &hir::NeedMut) -> Option DiagnosticCode::RustcHardError("E0384"), format!( "cannot mutate immutable variable `{}`", - d.local.name(ctx.sema.db).display(ctx.sema.db) + d.local.name(ctx.sema.db).display(ctx.sema.db, ctx.edition) ), d.span, ) diff --git a/crates/ide-diagnostics/src/handlers/private_assoc_item.rs b/crates/ide-diagnostics/src/handlers/private_assoc_item.rs index f6ed0d7226..fe32c59049 100644 --- a/crates/ide-diagnostics/src/handlers/private_assoc_item.rs +++ b/crates/ide-diagnostics/src/handlers/private_assoc_item.rs @@ -12,7 +12,7 @@ pub(crate) fn private_assoc_item( let name = d .item .name(ctx.sema.db) - .map(|name| format!("`{}` ", name.display(ctx.sema.db))) + .map(|name| format!("`{}` ", name.display(ctx.sema.db, ctx.edition))) .unwrap_or_default(); Diagnostic::new_with_syntax_node_ptr( ctx, diff --git a/crates/ide-diagnostics/src/handlers/private_field.rs b/crates/ide-diagnostics/src/handlers/private_field.rs index e91e64c81b..237a9b8787 100644 --- a/crates/ide-diagnostics/src/handlers/private_field.rs +++ b/crates/ide-diagnostics/src/handlers/private_field.rs @@ -10,8 +10,8 @@ pub(crate) fn private_field(ctx: &DiagnosticsContext<'_>, d: &hir::PrivateField) DiagnosticCode::RustcHardError("E0616"), format!( "field `{}` of `{}` is private", - d.field.name(ctx.sema.db).display(ctx.sema.db), - d.field.parent_def(ctx.sema.db).name(ctx.sema.db).display(ctx.sema.db) + d.field.name(ctx.sema.db).display(ctx.sema.db, ctx.edition), + d.field.parent_def(ctx.sema.db).name(ctx.sema.db).display(ctx.sema.db, ctx.edition) ), d.expr.map(|it| it.into()), ) diff --git a/crates/ide-diagnostics/src/handlers/trait_impl_missing_assoc_item.rs b/crates/ide-diagnostics/src/handlers/trait_impl_missing_assoc_item.rs index 58d1b7f31d..a35b67ce98 100644 --- a/crates/ide-diagnostics/src/handlers/trait_impl_missing_assoc_item.rs +++ b/crates/ide-diagnostics/src/handlers/trait_impl_missing_assoc_item.rs @@ -17,7 +17,7 @@ pub(crate) fn trait_impl_missing_assoc_item( hir::AssocItem::Const(_) => "`const ", hir::AssocItem::TypeAlias(_) => "`type ", })?; - f(&name.display(ctx.sema.db))?; + f(&name.display(ctx.sema.db, ctx.edition))?; f(&"`") }); Diagnostic::new( diff --git a/crates/ide-diagnostics/src/handlers/trait_impl_redundant_assoc_item.rs b/crates/ide-diagnostics/src/handlers/trait_impl_redundant_assoc_item.rs index 6d756484eb..3de51ca4a3 100644 --- a/crates/ide-diagnostics/src/handlers/trait_impl_redundant_assoc_item.rs +++ b/crates/ide-diagnostics/src/handlers/trait_impl_redundant_assoc_item.rs @@ -18,11 +18,11 @@ pub(crate) fn trait_impl_redundant_assoc_item( ) -> Diagnostic { let db = ctx.sema.db; let name = d.assoc_item.0.clone(); - let redundant_assoc_item_name = name.display(db); + let redundant_assoc_item_name = name.display(db, ctx.edition); let assoc_item = d.assoc_item.1; let default_range = d.impl_.syntax_node_ptr().text_range(); - let trait_name = d.trait_.name(db).display_no_db().to_smolstr(); + let trait_name = d.trait_.name(db).display_no_db(ctx.edition).to_smolstr(); let (redundant_item_name, diagnostic_range, redundant_item_def) = match assoc_item { hir::AssocItem::Function(id) => { @@ -30,7 +30,7 @@ pub(crate) fn trait_impl_redundant_assoc_item( ( format!("`fn {redundant_assoc_item_name}`"), function.source(db).map(|it| it.syntax().text_range()).unwrap_or(default_range), - format!("\n {};", function.display(db)), + format!("\n {};", function.display(db, ctx.edition)), ) } hir::AssocItem::Const(id) => { @@ -38,7 +38,7 @@ pub(crate) fn trait_impl_redundant_assoc_item( ( format!("`const {redundant_assoc_item_name}`"), constant.source(db).map(|it| it.syntax().text_range()).unwrap_or(default_range), - format!("\n {};", constant.display(db)), + format!("\n {};", constant.display(db, ctx.edition)), ) } hir::AssocItem::TypeAlias(id) => { @@ -48,7 +48,7 @@ pub(crate) fn trait_impl_redundant_assoc_item( type_alias.source(db).map(|it| it.syntax().text_range()).unwrap_or(default_range), format!( "\n type {};", - type_alias.name(ctx.sema.db).display_no_db().to_smolstr() + type_alias.name(ctx.sema.db).display_no_db(ctx.edition).to_smolstr() ), ) } diff --git a/crates/ide-diagnostics/src/handlers/type_mismatch.rs b/crates/ide-diagnostics/src/handlers/type_mismatch.rs index 6f5c68d4b5..5cce7c4aed 100644 --- a/crates/ide-diagnostics/src/handlers/type_mismatch.rs +++ b/crates/ide-diagnostics/src/handlers/type_mismatch.rs @@ -40,8 +40,12 @@ pub(crate) fn type_mismatch(ctx: &DiagnosticsContext<'_>, d: &hir::TypeMismatch) DiagnosticCode::RustcHardError("E0308"), format!( "expected {}, found {}", - d.expected.display(ctx.sema.db).with_closure_style(ClosureStyle::ClosureWithId), - d.actual.display(ctx.sema.db).with_closure_style(ClosureStyle::ClosureWithId), + d.expected + .display(ctx.sema.db, ctx.edition) + .with_closure_style(ClosureStyle::ClosureWithId), + d.actual + .display(ctx.sema.db, ctx.edition) + .with_closure_style(ClosureStyle::ClosureWithId), ), display_range, ) @@ -199,8 +203,8 @@ fn str_ref_to_owned( expr_ptr: &InFile>, acc: &mut Vec, ) -> Option<()> { - let expected = d.expected.display(ctx.sema.db); - let actual = d.actual.display(ctx.sema.db); + let expected = d.expected.display(ctx.sema.db, ctx.edition); + let actual = d.actual.display(ctx.sema.db, ctx.edition); // FIXME do this properly if expected.to_string() != "String" || actual.to_string() != "&str" { diff --git a/crates/ide-diagnostics/src/handlers/typed_hole.rs b/crates/ide-diagnostics/src/handlers/typed_hole.rs index b4a566e318..b5c242e1e9 100644 --- a/crates/ide-diagnostics/src/handlers/typed_hole.rs +++ b/crates/ide-diagnostics/src/handlers/typed_hole.rs @@ -26,7 +26,9 @@ pub(crate) fn typed_hole(ctx: &DiagnosticsContext<'_>, d: &hir::TypedHole) -> Di ( format!( "invalid `_` expression, expected type `{}`", - d.expected.display(ctx.sema.db).with_closure_style(ClosureStyle::ClosureWithId), + d.expected + .display(ctx.sema.db, ctx.edition) + .with_closure_style(ClosureStyle::ClosureWithId), ), fixes(ctx, d), ) @@ -69,6 +71,7 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::TypedHole) -> Option prefer_prelude: ctx.config.prefer_prelude, prefer_absolute: ctx.config.prefer_absolute, }, + ctx.edition, ) .ok() }) diff --git a/crates/ide-diagnostics/src/handlers/undeclared_label.rs b/crates/ide-diagnostics/src/handlers/undeclared_label.rs index 97943b7e8b..6af36fb9e7 100644 --- a/crates/ide-diagnostics/src/handlers/undeclared_label.rs +++ b/crates/ide-diagnostics/src/handlers/undeclared_label.rs @@ -9,7 +9,7 @@ pub(crate) fn undeclared_label( Diagnostic::new_with_syntax_node_ptr( ctx, DiagnosticCode::RustcHardError("undeclared-label"), - format!("use of undeclared label `{}`", name.display(ctx.sema.db)), + format!("use of undeclared label `{}`", name.display(ctx.sema.db, ctx.edition)), d.node.map(|it| it.into()), ) } diff --git a/crates/ide-diagnostics/src/handlers/unlinked_file.rs b/crates/ide-diagnostics/src/handlers/unlinked_file.rs index a1573bab8a..e0822fc5b3 100644 --- a/crates/ide-diagnostics/src/handlers/unlinked_file.rs +++ b/crates/ide-diagnostics/src/handlers/unlinked_file.rs @@ -11,7 +11,7 @@ use ide_db::{ use paths::Utf8Component; use syntax::{ ast::{self, edit::IndentLevel, HasModuleItem, HasName}, - AstNode, TextRange, ToSmolStr, + AstNode, TextRange, }; use text_edit::TextEdit; @@ -112,8 +112,7 @@ fn fixes( // shouldn't occur _ => continue 'crates, }; - match current.children.iter().find(|(name, _)| name.display_no_db().to_smolstr() == seg) - { + match current.children.iter().find(|(name, _)| name.eq_ident(seg)) { Some((_, &child)) => current = &crate_def_map[child], None => continue 'crates, } @@ -162,11 +161,7 @@ fn fixes( // try finding a parent that has an inline tree from here on let mut current = module; for s in stack.iter().rev() { - match module - .children - .iter() - .find(|(name, _)| name.display_no_db().to_smolstr() == s) - { + match module.children.iter().find(|(name, _)| name.eq_ident(s)) { Some((_, child)) => { current = &crate_def_map[*child]; } diff --git a/crates/ide-diagnostics/src/handlers/unreachable_label.rs b/crates/ide-diagnostics/src/handlers/unreachable_label.rs index 3601041fc7..bdff2417ca 100644 --- a/crates/ide-diagnostics/src/handlers/unreachable_label.rs +++ b/crates/ide-diagnostics/src/handlers/unreachable_label.rs @@ -9,7 +9,7 @@ pub(crate) fn unreachable_label( Diagnostic::new_with_syntax_node_ptr( ctx, DiagnosticCode::RustcHardError("E0767"), - format!("use of unreachable label `{}`", name.display(ctx.sema.db)), + format!("use of unreachable label `{}`", name.display(ctx.sema.db, ctx.edition)), d.node.map(|it| it.into()), ) } diff --git a/crates/ide-diagnostics/src/handlers/unresolved_field.rs b/crates/ide-diagnostics/src/handlers/unresolved_field.rs index eb8eea69f6..76d624c47a 100644 --- a/crates/ide-diagnostics/src/handlers/unresolved_field.rs +++ b/crates/ide-diagnostics/src/handlers/unresolved_field.rs @@ -36,8 +36,8 @@ pub(crate) fn unresolved_field( DiagnosticCode::RustcHardError("E0559"), format!( "no field `{}` on type `{}`{method_suffix}", - d.name.display(ctx.sema.db), - d.receiver.display(ctx.sema.db) + d.name.display(ctx.sema.db, ctx.edition), + d.receiver.display(ctx.sema.db, ctx.edition) ), adjusted_display_range(ctx, d.expr, &|expr| { Some( diff --git a/crates/ide-diagnostics/src/handlers/unresolved_macro_call.rs b/crates/ide-diagnostics/src/handlers/unresolved_macro_call.rs index c8ff54cba3..5b596123e7 100644 --- a/crates/ide-diagnostics/src/handlers/unresolved_macro_call.rs +++ b/crates/ide-diagnostics/src/handlers/unresolved_macro_call.rs @@ -13,7 +13,7 @@ pub(crate) fn unresolved_macro_call( let bang = if d.is_bang { "!" } else { "" }; Diagnostic::new( DiagnosticCode::RustcHardError("unresolved-macro-call"), - format!("unresolved macro `{}{bang}`", d.path.display(ctx.sema.db)), + format!("unresolved macro `{}{bang}`", d.path.display(ctx.sema.db, ctx.edition)), display_range, ) .experimental() diff --git a/crates/ide-diagnostics/src/handlers/unresolved_method.rs b/crates/ide-diagnostics/src/handlers/unresolved_method.rs index 387d56b890..c0d038a238 100644 --- a/crates/ide-diagnostics/src/handlers/unresolved_method.rs +++ b/crates/ide-diagnostics/src/handlers/unresolved_method.rs @@ -30,8 +30,8 @@ pub(crate) fn unresolved_method( DiagnosticCode::RustcHardError("E0599"), format!( "no method `{}` on type `{}`{suffix}", - d.name.display(ctx.sema.db), - d.receiver.display(ctx.sema.db) + d.name.display(ctx.sema.db, ctx.edition), + d.receiver.display(ctx.sema.db, ctx.edition) ), adjusted_display_range(ctx, d.expr, &|expr| { Some( @@ -154,9 +154,10 @@ fn assoc_func_fix(ctx: &DiagnosticsContext<'_>, d: &hir::UnresolvedMethodCall) - }; let mut receiver_type_adt_name = - receiver_type.as_adt()?.name(db).display_no_db().to_smolstr(); + receiver_type.as_adt()?.name(db).display_no_db(ctx.edition).to_smolstr(); - let generic_parameters: Vec = receiver_type.generic_parameters(db).collect(); + let generic_parameters: Vec = + receiver_type.generic_parameters(db, ctx.edition).collect(); // if receiver should be pass as first arg in the assoc func, // we could omit generic parameters cause compiler can deduce it automatically if !need_to_take_receiver_as_first_arg && !generic_parameters.is_empty() { diff --git a/crates/ide-diagnostics/src/handlers/unused_variables.rs b/crates/ide-diagnostics/src/handlers/unused_variables.rs index bf19331d9f..84007b16aa 100644 --- a/crates/ide-diagnostics/src/handlers/unused_variables.rs +++ b/crates/ide-diagnostics/src/handlers/unused_variables.rs @@ -5,7 +5,7 @@ use ide_db::{ source_change::SourceChange, FileRange, RootDatabase, }; -use syntax::TextRange; +use syntax::{Edition, TextRange}; use text_edit::TextEdit; use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext}; @@ -23,7 +23,7 @@ pub(crate) fn unused_variables( return None; } let diagnostic_range = ctx.sema.diagnostics_display_range(ast); - // The range for the Actual Name. We don't want to replace the entire declarition. Using the diagnostic range causes issues within in Array Destructuring. + // The range for the Actual Name. We don't want to replace the entire declaration. Using the diagnostic range causes issues within in Array Destructuring. let name_range = d .local .primary_source(ctx.sema.db) @@ -42,7 +42,14 @@ pub(crate) fn unused_variables( ast, ) .with_fixes(name_range.and_then(|it| { - fixes(ctx.sema.db, var_name, it.range, diagnostic_range.into(), ast.file_id.is_macro()) + fixes( + ctx.sema.db, + var_name, + it.range, + diagnostic_range.into(), + ast.file_id.is_macro(), + ctx.edition, + ) })) .experimental(), ) @@ -54,6 +61,7 @@ fn fixes( name_range: TextRange, diagnostic_range: FileRange, is_in_marco: bool, + edition: Edition, ) -> Option> { if is_in_marco { return None; @@ -63,14 +71,14 @@ fn fixes( id: AssistId("unscore_unused_variable_name", AssistKind::QuickFix), label: Label::new(format!( "Rename unused {} to _{}", - var_name.display(db), - var_name.display(db) + var_name.display(db, edition), + var_name.display(db, edition) )), group: None, target: diagnostic_range.range, source_change: Some(SourceChange::from_text_edit( diagnostic_range.file_id, - TextEdit::replace(name_range, format!("_{}", var_name.display(db))), + TextEdit::replace(name_range, format!("_{}", var_name.display(db, edition))), )), command: None, }]) diff --git a/crates/ide-diagnostics/src/lib.rs b/crates/ide-diagnostics/src/lib.rs index a61c5f0cd4..9b50a435e4 100644 --- a/crates/ide-diagnostics/src/lib.rs +++ b/crates/ide-diagnostics/src/lib.rs @@ -75,6 +75,8 @@ mod handlers { #[cfg(test)] mod tests; +use std::sync::LazyLock; + use hir::{diagnostics::AnyDiagnostic, InFile, Semantics}; use ide_db::{ assists::{Assist, AssistId, AssistKind, AssistResolveStrategy}, @@ -86,11 +88,10 @@ use ide_db::{ syntax_helpers::node_ext::parse_tt_as_comma_sep_paths, EditionedFileId, FileId, FileRange, FxHashMap, FxHashSet, RootDatabase, SnippetCap, }; -use once_cell::sync::Lazy; use stdx::never; use syntax::{ ast::{self, AstNode}, - AstPtr, SyntaxNode, SyntaxNodePtr, TextRange, + AstPtr, Edition, SyntaxNode, SyntaxNodePtr, TextRange, }; // FIXME: Make this an enum @@ -279,6 +280,7 @@ struct DiagnosticsContext<'a> { config: &'a DiagnosticsConfig, sema: Semantics<'a, RootDatabase>, resolve: &'a AssistResolveStrategy, + edition: Edition, } impl DiagnosticsContext<'_> { @@ -359,12 +361,19 @@ pub fn semantic_diagnostics( for node in parse.syntax().descendants() { handlers::useless_braces::useless_braces(&mut res, file_id, &node); handlers::field_shorthand::field_shorthand(&mut res, file_id, &node); - handlers::json_is_not_rust::json_in_items(&sema, &mut res, file_id, &node, config); + handlers::json_is_not_rust::json_in_items( + &sema, + &mut res, + file_id, + &node, + config, + file_id.edition(), + ); } let module = sema.file_to_module_def(file_id); - let ctx = DiagnosticsContext { config, sema, resolve }; + let ctx = DiagnosticsContext { config, sema, resolve, edition: file_id.edition() }; let mut diags = Vec::new(); match module { @@ -490,6 +499,7 @@ pub fn semantic_diagnostics( &mut rustc_stack, &mut clippy_stack, &mut diagnostics_of_range, + ctx.edition, ); res.retain(|d| d.severity != Severity::Allow); @@ -512,11 +522,11 @@ pub fn full_diagnostics( // `__RA_EVERY_LINT` is a fake lint group to allow every lint in proc macros -static RUSTC_LINT_GROUPS_DICT: Lazy>> = - Lazy::new(|| build_group_dict(DEFAULT_LINT_GROUPS, &["warnings", "__RA_EVERY_LINT"], "")); +static RUSTC_LINT_GROUPS_DICT: LazyLock>> = + LazyLock::new(|| build_group_dict(DEFAULT_LINT_GROUPS, &["warnings", "__RA_EVERY_LINT"], "")); -static CLIPPY_LINT_GROUPS_DICT: Lazy>> = - Lazy::new(|| build_group_dict(CLIPPY_LINT_GROUPS, &["__RA_EVERY_LINT"], "clippy::")); +static CLIPPY_LINT_GROUPS_DICT: LazyLock>> = + LazyLock::new(|| build_group_dict(CLIPPY_LINT_GROUPS, &["__RA_EVERY_LINT"], "clippy::")); fn build_group_dict( lint_group: &'static [LintGroup], @@ -544,6 +554,7 @@ fn handle_lint_attributes( rustc_stack: &mut FxHashMap>, clippy_stack: &mut FxHashMap>, diagnostics_of_range: &mut FxHashMap, &mut Diagnostic>, + edition: Edition, ) { let _g = tracing::info_span!("handle_lint_attributes").entered(); let file_id = sema.hir_file_for(root); @@ -552,9 +563,15 @@ fn handle_lint_attributes( match ev { syntax::WalkEvent::Enter(node) => { for attr in node.children().filter_map(ast::Attr::cast) { - parse_lint_attribute(attr, rustc_stack, clippy_stack, |stack, severity| { - stack.push(severity); - }); + parse_lint_attribute( + attr, + rustc_stack, + clippy_stack, + |stack, severity| { + stack.push(severity); + }, + edition, + ); } if let Some(it) = diagnostics_of_range.get_mut(&InFile { file_id, value: node.clone() }) @@ -591,6 +608,7 @@ fn handle_lint_attributes( rustc_stack, clippy_stack, diagnostics_of_range, + edition, ); for stack in [&mut *rustc_stack, &mut *clippy_stack] { stack.entry("__RA_EVERY_LINT".to_owned()).or_default().pop(); @@ -605,17 +623,24 @@ fn handle_lint_attributes( rustc_stack, clippy_stack, diagnostics_of_range, + edition, ); } } } syntax::WalkEvent::Leave(node) => { for attr in node.children().filter_map(ast::Attr::cast) { - parse_lint_attribute(attr, rustc_stack, clippy_stack, |stack, severity| { - if stack.pop() != Some(severity) { - never!("Mismatched serevity in walking lint attributes"); - } - }); + parse_lint_attribute( + attr, + rustc_stack, + clippy_stack, + |stack, severity| { + if stack.pop() != Some(severity) { + never!("Mismatched serevity in walking lint attributes"); + } + }, + edition, + ); } } } @@ -627,6 +652,7 @@ fn parse_lint_attribute( rustc_stack: &mut FxHashMap>, clippy_stack: &mut FxHashMap>, job: impl Fn(&mut Vec, Severity), + edition: Edition, ) { let Some((tag, args_tt)) = attr.as_simple_call() else { return; @@ -637,7 +663,7 @@ fn parse_lint_attribute( "forbid" | "deny" => Severity::Error, _ => return, }; - for lint in parse_tt_as_comma_sep_paths(args_tt).into_iter().flatten() { + for lint in parse_tt_as_comma_sep_paths(args_tt, edition).into_iter().flatten() { if let Some(lint) = lint.as_single_name_ref() { job(rustc_stack.entry(lint.to_string()).or_default(), severity); } diff --git a/crates/ide-ssr/src/matching.rs b/crates/ide-ssr/src/matching.rs index 5f6d77c064..6569f0f555 100644 --- a/crates/ide-ssr/src/matching.rs +++ b/crates/ide-ssr/src/matching.rs @@ -8,6 +8,7 @@ use crate::{ }; use hir::{FileRange, ImportPathConfig, Semantics}; use ide_db::FxHashMap; +use parser::Edition; use std::{cell::Cell, iter::Peekable}; use syntax::{ ast::{self, AstNode, AstToken, HasGenericArgs}, @@ -626,6 +627,11 @@ impl<'db, 'sema> Matcher<'db, 'sema> { match_error!("Failed to get receiver type for `{}`", expr.syntax().text()) })? .original; + let edition = self + .sema + .scope(expr.syntax()) + .map(|it| it.krate().edition(self.sema.db)) + .unwrap_or(Edition::CURRENT); // Temporary needed to make the borrow checker happy. let res = code_type .autoderef(self.sema.db) @@ -635,8 +641,8 @@ impl<'db, 'sema> Matcher<'db, 'sema> { .ok_or_else(|| { match_error!( "Pattern type `{}` didn't match code type `{}`", - pattern_type.display(self.sema.db), - code_type.display(self.sema.db) + pattern_type.display(self.sema.db, edition), + code_type.display(self.sema.db, edition) ) }); res diff --git a/crates/ide-ssr/src/replacing.rs b/crates/ide-ssr/src/replacing.rs index b4b83f62da..65756601f6 100644 --- a/crates/ide-ssr/src/replacing.rs +++ b/crates/ide-ssr/src/replacing.rs @@ -2,6 +2,7 @@ use ide_db::{FxHashMap, FxHashSet}; use itertools::Itertools; +use parser::Edition; use syntax::{ ast::{self, AstNode, AstToken}, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, TextRange, TextSize, @@ -33,7 +34,7 @@ fn matches_to_edit_at_offset( for m in &matches.matches { edit_builder.replace( m.range.range.checked_sub(relative_start).unwrap(), - render_replace(db, m, file_src, rules), + render_replace(db, m, file_src, rules, m.range.file_id.edition()), ); } edit_builder.finish() @@ -54,6 +55,7 @@ struct ReplacementRenderer<'a> { // is parsed, placeholders don't get split. e.g. if a template of `$a.to_string()` results in `1 // + 2.to_string()` then the placeholder value `1 + 2` was split and needs parenthesis. placeholder_tokens_requiring_parenthesis: FxHashSet, + edition: Edition, } fn render_replace( @@ -61,6 +63,7 @@ fn render_replace( match_info: &Match, file_src: &str, rules: &[ResolvedRule], + edition: Edition, ) -> String { let rule = &rules[match_info.rule_index]; let template = rule @@ -76,6 +79,7 @@ fn render_replace( out: String::new(), placeholder_tokens_requiring_parenthesis: FxHashSet::default(), placeholder_tokens_by_range: FxHashMap::default(), + edition, }; renderer.render_node(&template.node); renderer.maybe_rerender_with_extra_parenthesis(&template.node); @@ -105,7 +109,7 @@ impl ReplacementRenderer<'_> { fn render_node(&mut self, node: &SyntaxNode) { if let Some(mod_path) = self.match_info.rendered_template_paths.get(node) { - self.out.push_str(&mod_path.display(self.db).to_string()); + self.out.push_str(&mod_path.display(self.db, self.edition).to_string()); // Emit everything except for the segment's name-ref, since we already effectively // emitted that as part of `mod_path`. if let Some(path) = ast::Path::cast(node.clone()) { diff --git a/crates/ide/src/call_hierarchy.rs b/crates/ide/src/call_hierarchy.rs index 8709310485..155259a138 100644 --- a/crates/ide/src/call_hierarchy.rs +++ b/crates/ide/src/call_hierarchy.rs @@ -2,7 +2,7 @@ use std::iter; -use hir::{DescendPreference, Semantics}; +use hir::Semantics; use ide_db::{ defs::{Definition, NameClass, NameRefClass}, helpers::pick_best_token, @@ -86,7 +86,7 @@ pub(crate) fn outgoing_calls( })?; let mut calls = CallLocations::default(); - sema.descend_into_macros(DescendPreference::None, token) + sema.descend_into_macros_exact(token) .into_iter() .filter_map(|it| it.parent_ancestors().nth(1).and_then(ast::Item::cast)) .filter_map(|item| match item { diff --git a/crates/ide/src/doc_links.rs b/crates/ide/src/doc_links.rs index e9e5240897..925ae62023 100644 --- a/crates/ide/src/doc_links.rs +++ b/crates/ide/src/doc_links.rs @@ -10,10 +10,7 @@ use pulldown_cmark_to_cmark::{cmark_resume_with_options, Options as CMarkOptions use stdx::format_to; use url::Url; -use hir::{ - db::HirDatabase, sym, Adt, AsAssocItem, AssocItem, AssocItemContainer, DescendPreference, - HasAttrs, -}; +use hir::{db::HirDatabase, sym, Adt, AsAssocItem, AssocItem, AssocItemContainer, HasAttrs}; use ide_db::{ base_db::{CrateOrigin, LangCrateOrigin, ReleaseChannel, SourceDatabase}, defs::{Definition, NameClass, NameRefClass}, @@ -144,7 +141,7 @@ pub(crate) fn external_docs( kind if kind.is_trivia() => 0, _ => 1, })?; - let token = sema.descend_into_macros_single(DescendPreference::None, token); + let token = sema.descend_into_macros_single_exact(token); let node = token.parent()?; let definition = match_ast! { @@ -289,7 +286,7 @@ impl DocCommentToken { let original_start = doc_token.text_range().start(); let relative_comment_offset = offset - original_start - prefix_len; - sema.descend_into_macros(DescendPreference::None, doc_token).into_iter().find_map(|t| { + sema.descend_into_macros(doc_token).into_iter().find_map(|t| { let (node, descended_prefix_len) = match_ast! { match t { ast::Comment(comment) => (t.parent()?, TextSize::try_from(comment.prefix().len()).ok()?), @@ -413,7 +410,8 @@ fn rewrite_url_link(db: &RootDatabase, def: Definition, target: &str) -> Option< fn mod_path_of_def(db: &RootDatabase, def: Definition) -> Option { def.canonical_module_path(db).map(|it| { let mut path = String::new(); - it.flat_map(|it| it.name(db)).for_each(|name| format_to!(path, "{}/", name.display(db))); + it.flat_map(|it| it.name(db)) + .for_each(|name| format_to!(path, "{}/", name.unescaped().display(db))); path }) } @@ -588,9 +586,11 @@ fn filename_and_frag_for_def( let res = match def { Definition::Adt(adt) => match adt { - Adt::Struct(s) => format!("struct.{}.html", s.name(db).display(db.upcast())), - Adt::Enum(e) => format!("enum.{}.html", e.name(db).display(db.upcast())), - Adt::Union(u) => format!("union.{}.html", u.name(db).display(db.upcast())), + Adt::Struct(s) => { + format!("struct.{}.html", s.name(db).unescaped().display(db.upcast())) + } + Adt::Enum(e) => format!("enum.{}.html", e.name(db).unescaped().display(db.upcast())), + Adt::Union(u) => format!("union.{}.html", u.name(db).unescaped().display(db.upcast())), }, Definition::Module(m) => match m.name(db) { // `#[doc(keyword = "...")]` is internal used only by rust compiler @@ -599,34 +599,48 @@ fn filename_and_frag_for_def( Some(kw) => { format!("keyword.{}.html", kw) } - None => format!("{}/index.html", name.display(db.upcast())), + None => format!("{}/index.html", name.unescaped().display(db.upcast())), } } None => String::from("index.html"), }, - Definition::Trait(t) => format!("trait.{}.html", t.name(db).display(db.upcast())), - Definition::TraitAlias(t) => format!("traitalias.{}.html", t.name(db).display(db.upcast())), - Definition::TypeAlias(t) => format!("type.{}.html", t.name(db).display(db.upcast())), - Definition::BuiltinType(t) => format!("primitive.{}.html", t.name().display(db.upcast())), - Definition::Function(f) => format!("fn.{}.html", f.name(db).display(db.upcast())), + Definition::Trait(t) => { + format!("trait.{}.html", t.name(db).unescaped().display(db.upcast())) + } + Definition::TraitAlias(t) => { + format!("traitalias.{}.html", t.name(db).unescaped().display(db.upcast())) + } + Definition::TypeAlias(t) => { + format!("type.{}.html", t.name(db).unescaped().display(db.upcast())) + } + Definition::BuiltinType(t) => { + format!("primitive.{}.html", t.name().unescaped().display(db.upcast())) + } + Definition::Function(f) => { + format!("fn.{}.html", f.name(db).unescaped().display(db.upcast())) + } Definition::Variant(ev) => { format!( "enum.{}.html#variant.{}", - ev.parent_enum(db).name(db).display(db.upcast()), - ev.name(db).display(db.upcast()) + ev.parent_enum(db).name(db).unescaped().display(db.upcast()), + ev.name(db).unescaped().display(db.upcast()) ) } - Definition::Const(c) => format!("const.{}.html", c.name(db)?.display(db.upcast())), - Definition::Static(s) => format!("static.{}.html", s.name(db).display(db.upcast())), + Definition::Const(c) => { + format!("const.{}.html", c.name(db)?.unescaped().display(db.upcast())) + } + Definition::Static(s) => { + format!("static.{}.html", s.name(db).unescaped().display(db.upcast())) + } Definition::Macro(mac) => match mac.kind(db) { hir::MacroKind::Declarative | hir::MacroKind::BuiltIn | hir::MacroKind::Attr | hir::MacroKind::ProcMacro => { - format!("macro.{}.html", mac.name(db).display(db.upcast())) + format!("macro.{}.html", mac.name(db).unescaped().display(db.upcast())) } hir::MacroKind::Derive => { - format!("derive.{}.html", mac.name(db).display(db.upcast())) + format!("derive.{}.html", mac.name(db).unescaped().display(db.upcast())) } }, Definition::Field(field) => { @@ -639,7 +653,7 @@ fn filename_and_frag_for_def( return Some(( def, file, - Some(format!("structfield.{}", field.name(db).display(db.upcast()))), + Some(format!("structfield.{}", field.name(db).unescaped().display(db.upcast()))), )); } Definition::SelfType(impl_) => { @@ -649,7 +663,7 @@ fn filename_and_frag_for_def( return Some((adt, file, Some(String::from("impl")))); } Definition::ExternCrateDecl(it) => { - format!("{}/index.html", it.name(db).display(db.upcast())) + format!("{}/index.html", it.name(db).unescaped().display(db.upcast())) } Definition::Local(_) | Definition::GenericParam(_) @@ -679,14 +693,16 @@ fn get_assoc_item_fragment(db: &dyn HirDatabase, assoc_item: hir::AssocItem) -> // Rustdoc makes this decision based on whether a method 'has defaultness'. // Currently this is only the case for provided trait methods. if is_trait_method && !function.has_body(db) { - format!("tymethod.{}", function.name(db).display(db.upcast())) + format!("tymethod.{}", function.name(db).unescaped().display(db.upcast())) } else { - format!("method.{}", function.name(db).display(db.upcast())) + format!("method.{}", function.name(db).unescaped().display(db.upcast())) } } AssocItem::Const(constant) => { - format!("associatedconstant.{}", constant.name(db)?.display(db.upcast())) + format!("associatedconstant.{}", constant.name(db)?.unescaped().display(db.upcast())) + } + AssocItem::TypeAlias(ty) => { + format!("associatedtype.{}", ty.name(db).unescaped().display(db.upcast())) } - AssocItem::TypeAlias(ty) => format!("associatedtype.{}", ty.name(db).display(db.upcast())), }) } diff --git a/crates/ide/src/expand_macro.rs b/crates/ide/src/expand_macro.rs index c8fe45c9cf..a939ed214a 100644 --- a/crates/ide/src/expand_macro.rs +++ b/crates/ide/src/expand_macro.rs @@ -1,8 +1,9 @@ -use hir::{DescendPreference, InFile, MacroFileIdExt, Semantics}; +use hir::{InFile, MacroFileIdExt, Semantics}; use ide_db::{ helpers::pick_best_token, syntax_helpers::insert_whitespace_into_node::insert_ws_into, FileId, RootDatabase, }; +use span::Edition; use syntax::{ast, ted, AstNode, NodeOrToken, SyntaxKind, SyntaxNode, T}; use crate::FilePosition; @@ -40,37 +41,30 @@ pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option< // struct Bar; // ``` - let derive = sema - .descend_into_macros(DescendPreference::None, tok.clone()) - .into_iter() - .find_map(|descended| { - let macro_file = sema.hir_file_for(&descended.parent()?).macro_file()?; - if !macro_file.is_derive_attr_pseudo_expansion(db) { - return None; - } + let derive = sema.descend_into_macros_exact(tok.clone()).into_iter().find_map(|descended| { + let macro_file = sema.hir_file_for(&descended.parent()?).macro_file()?; + if !macro_file.is_derive_attr_pseudo_expansion(db) { + return None; + } - let name = descended.parent_ancestors().filter_map(ast::Path::cast).last()?.to_string(); - // up map out of the #[derive] expansion - let InFile { file_id, value: tokens } = - hir::InMacroFile::new(macro_file, descended).upmap_once(db); - let token = sema.parse_or_expand(file_id).covering_element(tokens[0]).into_token()?; - let attr = token.parent_ancestors().find_map(ast::Attr::cast)?; - let expansions = sema.expand_derive_macro(&attr)?; - let idx = attr - .token_tree()? - .token_trees_and_tokens() - .filter_map(NodeOrToken::into_token) - .take_while(|it| it != &token) - .filter(|it| it.kind() == T![,]) - .count(); - let expansion = format( - db, - SyntaxKind::MACRO_ITEMS, - position.file_id, - expansions.get(idx).cloned()?, - ); - Some(ExpandedMacro { name, expansion }) - }); + let name = descended.parent_ancestors().filter_map(ast::Path::cast).last()?.to_string(); + // up map out of the #[derive] expansion + let InFile { file_id, value: tokens } = + hir::InMacroFile::new(macro_file, descended).upmap_once(db); + let token = sema.parse_or_expand(file_id).covering_element(tokens[0]).into_token()?; + let attr = token.parent_ancestors().find_map(ast::Attr::cast)?; + let expansions = sema.expand_derive_macro(&attr)?; + let idx = attr + .token_tree()? + .token_trees_and_tokens() + .filter_map(NodeOrToken::into_token) + .take_while(|it| it != &token) + .filter(|it| it.kind() == T![,]) + .count(); + let expansion = + format(db, SyntaxKind::MACRO_ITEMS, position.file_id, expansions.get(idx).cloned()?); + Some(ExpandedMacro { name, expansion }) + }); if derive.is_some() { return derive; @@ -83,7 +77,14 @@ pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option< if let Some(item) = ast::Item::cast(node.clone()) { if let Some(def) = sema.resolve_attr_macro_call(&item) { break ( - def.name(db).display(db).to_string(), + def.name(db) + .display( + db, + sema.attach_first_edition(position.file_id) + .map(|it| it.edition()) + .unwrap_or(Edition::CURRENT), + ) + .to_string(), expand_macro_recur(&sema, &item)?, SyntaxKind::MACRO_ITEMS, ); diff --git a/crates/ide/src/extend_selection.rs b/crates/ide/src/extend_selection.rs index 5f6aaeaabb..3d49082f28 100644 --- a/crates/ide/src/extend_selection.rs +++ b/crates/ide/src/extend_selection.rs @@ -1,6 +1,6 @@ use std::iter::successors; -use hir::{DescendPreference, Semantics}; +use hir::Semantics; use ide_db::RootDatabase; use syntax::{ algo::{self, skip_trivia_token}, @@ -140,10 +140,8 @@ fn extend_tokens_from_range( // compute original mapped token range let extended = { - let fst_expanded = - sema.descend_into_macros_single(DescendPreference::None, first_token.clone()); - let lst_expanded = - sema.descend_into_macros_single(DescendPreference::None, last_token.clone()); + let fst_expanded = sema.descend_into_macros_single_exact(first_token.clone()); + let lst_expanded = sema.descend_into_macros_single_exact(last_token.clone()); let mut lca = algo::least_common_ancestor(&fst_expanded.parent()?, &lst_expanded.parent()?)?; lca = shallowest_node(&lca); @@ -157,7 +155,7 @@ fn extend_tokens_from_range( let validate = || { let extended = &extended; move |token: &SyntaxToken| -> bool { - let expanded = sema.descend_into_macros_single(DescendPreference::None, token.clone()); + let expanded = sema.descend_into_macros_single_exact(token.clone()); let parent = match expanded.parent() { Some(it) => it, None => return false, diff --git a/crates/ide/src/goto_declaration.rs b/crates/ide/src/goto_declaration.rs index 6076de54eb..6ae9dde84b 100644 --- a/crates/ide/src/goto_declaration.rs +++ b/crates/ide/src/goto_declaration.rs @@ -1,4 +1,4 @@ -use hir::{AsAssocItem, DescendPreference, Semantics}; +use hir::{AsAssocItem, Semantics}; use ide_db::{ defs::{Definition, NameClass, NameRefClass}, RootDatabase, @@ -29,7 +29,7 @@ pub(crate) fn goto_declaration( .find(|it| matches!(it.kind(), IDENT | T![self] | T![super] | T![crate] | T![Self]))?; let range = original_token.text_range(); let info: Vec = sema - .descend_into_macros(DescendPreference::None, original_token) + .descend_into_macros(original_token) .iter() .filter_map(|token| { let parent = token.parent()?; diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs index 8a8bc07945..971cd3ef58 100644 --- a/crates/ide/src/goto_definition.rs +++ b/crates/ide/src/goto_definition.rs @@ -5,10 +5,7 @@ use crate::{ navigation_target::{self, ToNav}, FilePosition, NavigationTarget, RangeInfo, TryToNav, UpmappingResult, }; -use hir::{ - AsAssocItem, AssocItem, DescendPreference, FileRange, InFile, MacroFileIdExt, ModuleDef, - Semantics, -}; +use hir::{AsAssocItem, AssocItem, FileRange, InFile, MacroFileIdExt, ModuleDef, Semantics}; use ide_db::{ base_db::{AnchoredPath, FileLoader, SourceDatabase}, defs::{Definition, IdentClass}, @@ -17,7 +14,7 @@ use ide_db::{ }; use itertools::Itertools; -use span::FileId; +use span::{Edition, FileId}; use syntax::{ ast::{self, HasLoopBody}, match_ast, AstNode, AstToken, @@ -44,6 +41,8 @@ pub(crate) fn goto_definition( ) -> Option>> { let sema = &Semantics::new(db); let file = sema.parse_guess_edition(file_id).syntax().clone(); + let edition = + sema.attach_first_edition(file_id).map(|it| it.edition()).unwrap_or(Edition::CURRENT); let original_token = pick_best_token(file.token_at_offset(offset), |kind| match kind { IDENT | INT_NUMBER @@ -55,7 +54,7 @@ pub(crate) fn goto_definition( | COMMENT => 4, // index and prefix ops T!['['] | T![']'] | T![?] | T![*] | T![-] | T![!] => 3, - kind if kind.is_keyword() => 2, + kind if kind.is_keyword(edition) => 2, T!['('] | T![')'] => 2, kind if kind.is_trivia() => 0, _ => 1, @@ -84,7 +83,7 @@ pub(crate) fn goto_definition( } let navs = sema - .descend_into_macros(DescendPreference::None, original_token.clone()) + .descend_into_macros(original_token.clone()) .into_iter() .filter_map(|token| { let parent = token.parent()?; @@ -249,10 +248,7 @@ pub(crate) fn find_fn_or_blocks( None }; - sema.descend_into_macros(DescendPreference::None, token.clone()) - .into_iter() - .filter_map(find_ancestors) - .collect_vec() + sema.descend_into_macros(token.clone()).into_iter().filter_map(find_ancestors).collect_vec() } fn nav_for_exit_points( @@ -367,7 +363,7 @@ pub(crate) fn find_loops( None }; - sema.descend_into_macros(DescendPreference::None, token.clone()) + sema.descend_into_macros(token.clone()) .into_iter() .filter_map(find_ancestors) .collect_vec() @@ -2735,4 +2731,23 @@ fn main() { "#, ) } + + #[test] + fn shadow_builtin_macro() { + check( + r#" +//- minicore: column +//- /a.rs crate:a +#[macro_export] +macro_rules! column { () => {} } + // ^^^^^^ + +//- /b.rs crate:b deps:a +use a::column; +fn foo() { + $0column!(); +} + "#, + ); + } } diff --git a/crates/ide/src/goto_implementation.rs b/crates/ide/src/goto_implementation.rs index 2eff7796d5..e36c8ee2f3 100644 --- a/crates/ide/src/goto_implementation.rs +++ b/crates/ide/src/goto_implementation.rs @@ -1,4 +1,4 @@ -use hir::{AsAssocItem, DescendPreference, Impl, Semantics}; +use hir::{AsAssocItem, Impl, Semantics}; use ide_db::{ defs::{Definition, NameClass, NameRefClass}, helpers::pick_best_token, @@ -10,7 +10,7 @@ use crate::{FilePosition, NavigationTarget, RangeInfo, TryToNav}; // Feature: Go to Implementation // -// Navigates to the impl blocks of types. +// Navigates to the impl items of types. // // |=== // | Editor | Shortcut @@ -32,48 +32,55 @@ pub(crate) fn goto_implementation( _ => 0, })?; let range = original_token.text_range(); - let navs = - sema.descend_into_macros_single(DescendPreference::SameText, original_token) - .parent() - .and_then(ast::NameLike::cast) - .and_then(|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, - }) - .and_then(|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.container_or_implemented_trait(sema.db)?; - impls_for_trait_item(&sema, trait_, name) + let navs = sema + .descend_into_macros_exact(original_token) + .iter() + .filter_map(|token| { + token + .parent() + .and_then(ast::NameLike::cast) + .and_then(|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, + }) } - Definition::Const(c) => { - let assoc = c.as_assoc_item(sema.db)?; - let name = assoc.name(sema.db)?; - let trait_ = assoc.container_or_implemented_trait(sema.db)?; - impls_for_trait_item(&sema, trait_, name) - } - _ => return None, - }; - Some(navs) - }) - .unwrap_or_default(); + 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, + }) + .and_then(|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.container_or_implemented_trait(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.container_or_implemented_trait(sema.db)?; + impls_for_trait_item(&sema, trait_, name) + } + _ => return None, + }; + Some(navs) + }) + }) + .flatten() + .collect(); Some(RangeInfo { range, info: navs }) } diff --git a/crates/ide/src/goto_type_definition.rs b/crates/ide/src/goto_type_definition.rs index f75b8fb7d0..ca04b7bb5a 100644 --- a/crates/ide/src/goto_type_definition.rs +++ b/crates/ide/src/goto_type_definition.rs @@ -1,4 +1,4 @@ -use hir::{DescendPreference, GenericParam}; +use hir::GenericParam; use ide_db::{base_db::Upcast, defs::Definition, helpers::pick_best_token, RootDatabase}; use syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, T}; @@ -69,7 +69,7 @@ pub(crate) fn goto_type_definition( } let range = token.text_range(); - sema.descend_into_macros(DescendPreference::None, token) + sema.descend_into_macros(token) .into_iter() .filter_map(|token| { let ty = sema diff --git a/crates/ide/src/highlight_related.rs b/crates/ide/src/highlight_related.rs index 8fcd38b4e3..5348e855be 100644 --- a/crates/ide/src/highlight_related.rs +++ b/crates/ide/src/highlight_related.rs @@ -1,6 +1,6 @@ use std::iter; -use hir::{db, DescendPreference, FilePosition, FileRange, HirFileId, InFile, Semantics}; +use hir::{db, FilePosition, FileRange, HirFileId, InFile, Semantics}; use ide_db::{ defs::{Definition, IdentClass}, helpers::pick_best_token, @@ -65,7 +65,7 @@ pub(crate) fn highlight_related( let token = pick_best_token(syntax.token_at_offset(offset), |kind| match kind { T![?] => 4, // prefer `?` when the cursor is sandwiched like in `await$0?` T![->] => 4, - kind if kind.is_keyword() => 3, + kind if kind.is_keyword(file_id.edition()) => 3, IDENT | INT_NUMBER => 2, T![|] => 1, _ => 0, @@ -542,7 +542,7 @@ fn cover_range(r0: Option, r1: Option) -> Option, token: SyntaxToken) -> FxHashSet { - sema.descend_into_macros(DescendPreference::None, token) + sema.descend_into_macros_exact(token) .into_iter() .filter_map(|token| IdentClass::classify_token(sema, &token)) .flat_map(IdentClass::definitions_no_ops) diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs index 500674e32b..124db2985b 100644 --- a/crates/ide/src/hover.rs +++ b/crates/ide/src/hover.rs @@ -6,7 +6,7 @@ mod tests; use std::{iter, ops::Not}; use either::Either; -use hir::{db::DefDatabase, DescendPreference, HasCrate, HasSource, LangItem, Semantics}; +use hir::{db::DefDatabase, HasCrate, HasSource, LangItem, Semantics}; use ide_db::{ defs::{Definition, IdentClass, NameRefClass, OperatorClass}, famous_defs::FamousDefs, @@ -14,6 +14,7 @@ use ide_db::{ FileRange, FxIndexSet, RootDatabase, }; use itertools::{multizip, Itertools}; +use span::Edition; use syntax::{ast, AstNode, SyntaxKind::*, SyntaxNode, T}; use crate::{ @@ -57,7 +58,7 @@ pub enum HoverDocFormat { PlainText, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Hash, PartialEq, Eq)] pub enum HoverAction { Runnable(Runnable), Implementation(FilePosition), @@ -66,7 +67,11 @@ pub enum HoverAction { } impl HoverAction { - fn goto_type_from_targets(db: &RootDatabase, targets: Vec) -> Option { + fn goto_type_from_targets( + db: &RootDatabase, + targets: Vec, + edition: Edition, + ) -> Option { let targets = targets .into_iter() .filter_map(|it| { @@ -74,7 +79,8 @@ impl HoverAction { mod_path: render::path( db, it.module(db)?, - it.name(db).map(|name| name.display(db).to_string()), + it.name(db).map(|name| name.display(db, edition).to_string()), + edition, ), nav: it.try_to_nav(db)?.call_site(), }) @@ -91,7 +97,7 @@ pub struct HoverGotoTypeData { } /// Contains the results when hovering over an item -#[derive(Debug, Default)] +#[derive(Clone, Debug, Default, Hash, PartialEq, Eq)] pub struct HoverResult { pub markup: Markup, pub actions: Vec, @@ -110,10 +116,12 @@ pub(crate) fn hover( ) -> Option> { let sema = &hir::Semantics::new(db); let file = sema.parse_guess_edition(file_id).syntax().clone(); + let edition = + sema.attach_first_edition(file_id).map(|it| it.edition()).unwrap_or(Edition::CURRENT); let mut res = if range.is_empty() { - hover_simple(sema, FilePosition { file_id, offset: range.start() }, file, config) + hover_offset(sema, FilePosition { file_id, offset: range.start() }, file, config, edition) } else { - hover_ranged(sema, frange, file, config) + hover_ranged(sema, frange, file, config, edition) }?; if let HoverDocFormat::PlainText = config.format { @@ -123,11 +131,12 @@ pub(crate) fn hover( } #[allow(clippy::field_reassign_with_default)] -fn hover_simple( +fn hover_offset( sema: &Semantics<'_, RootDatabase>, FilePosition { file_id, offset }: FilePosition, file: SyntaxNode, config: &HoverConfig, + edition: Edition, ) -> Option> { let original_token = pick_best_token(file.token_at_offset(offset), |kind| match kind { IDENT @@ -140,7 +149,7 @@ fn hover_simple( | T![_] => 4, // index and prefix ops and closure pipe T!['['] | T![']'] | T![?] | T![*] | T![-] | T![!] | T![|] => 3, - kind if kind.is_keyword() => 2, + kind if kind.is_keyword(edition) => 2, T!['('] | T![')'] => 2, kind if kind.is_trivia() => 0, _ => 1, @@ -149,7 +158,7 @@ fn hover_simple( if let Some(doc_comment) = token_as_doc_comment(&original_token) { cov_mark::hit!(no_highlight_on_comment_hover); return doc_comment.get_definition_with_descend_at(sema, offset, |def, node, range| { - let res = hover_for_definition(sema, file_id, def, &node, None, config); + let res = hover_for_definition(sema, file_id, def, &node, None, config, edition); Some(RangeInfo::new(range, res)) }); } @@ -164,38 +173,48 @@ fn hover_simple( &original_token.parent()?, None, config, + edition, ); return Some(RangeInfo::new(range, res)); } - let in_attr = original_token - .parent_ancestors() - .filter_map(ast::Item::cast) - .any(|item| sema.is_attr_macro_call(&item)) - && !matches!( - original_token.parent().and_then(ast::TokenTree::cast), - Some(tt) if tt.syntax().ancestors().any(|it| ast::Meta::can_cast(it.kind())) - ); - // prefer descending the same token kind in attribute expansions, in normal macros text // equivalency is more important - let descended = sema.descend_into_macros( - if in_attr { DescendPreference::SameKind } else { DescendPreference::SameText }, - original_token.clone(), - ); - let descended = || descended.iter(); + let mut descended = sema.descend_into_macros(original_token.clone()); - let result = descended() - // try lint hover - .find_map(|token| { + let kind = original_token.kind(); + let text = original_token.text(); + let ident_kind = kind.is_any_identifier(); + + descended.sort_by_cached_key(|tok| { + let tok_kind = tok.kind(); + + let exact_same_kind = tok_kind == kind; + let both_idents = exact_same_kind || (tok_kind.is_any_identifier() && ident_kind); + let same_text = tok.text() == text; + // anything that mapped into a token tree has likely no semantic information + let no_tt_parent = tok.parent().map_or(false, |it| it.kind() != TOKEN_TREE); + !((both_idents as usize) + | ((exact_same_kind as usize) << 1) + | ((same_text as usize) << 2) + | ((no_tt_parent as usize) << 3)) + }); + + let mut res = vec![]; + for token in descended { + let is_same_kind = token.kind() == kind; + let lint_hover = (|| { // FIXME: Definition should include known lints and the like instead of having this special case here let attr = token.parent_ancestors().find_map(ast::Attr::cast)?; - render::try_for_lint(&attr, token) - }) - // try definitions - .or_else(|| { - descended() - .filter_map(|token| { + render::try_for_lint(&attr, &token) + })(); + if let Some(lint_hover) = lint_hover { + res.push(lint_hover); + continue; + } + let definitions = (|| { + Some( + 'a: { let node = token.parent()?; // special case macro calls, we wanna render the invoked arm index @@ -210,11 +229,11 @@ fn hover_simple( .and_then(ast::MacroCall::cast) { if let Some(macro_) = sema.resolve_macro_call(¯o_call) { - return Some(vec![( + break 'a vec![( Definition::Macro(macro_), sema.resolve_macro_call_arm(¯o_call), node, - )]); + )]; } } } @@ -223,88 +242,101 @@ fn hover_simple( match IdentClass::classify_node(sema, &node)? { // It's better for us to fall back to the keyword hover here, // rendering poll is very confusing - IdentClass::Operator(OperatorClass::Await(_)) => None, + IdentClass::Operator(OperatorClass::Await(_)) => return None, IdentClass::NameRefClass(NameRefClass::ExternCrateShorthand { decl, .. - }) => Some(vec![(Definition::ExternCrateDecl(decl), None, node)]), + }) => { + vec![(Definition::ExternCrateDecl(decl), None, node)] + } - class => Some( + class => { multizip((class.definitions(), iter::repeat(None), iter::repeat(node))) - .collect::>(), - ), + .collect::>() + } } - }) - .flatten() + } + .into_iter() .unique_by(|&(def, _, _)| def) .map(|(def, macro_arm, node)| { - hover_for_definition(sema, file_id, def, &node, macro_arm, config) + hover_for_definition(sema, file_id, def, &node, macro_arm, config, edition) }) - .reduce(|mut acc: HoverResult, HoverResult { markup, actions }| { - acc.actions.extend(actions); - acc.markup = Markup::from(format!("{}\n---\n{markup}", acc.markup)); - acc - }) - }) - // try keywords - .or_else(|| descended().find_map(|token| render::keyword(sema, config, token))) - // try _ hovers - .or_else(|| descended().find_map(|token| render::underscore(sema, config, token))) - // try rest pattern hover - .or_else(|| { - descended().find_map(|token| { - if token.kind() != DOT2 { - return None; - } + .collect::>(), + ) + })(); + if let Some(definitions) = definitions { + res.extend(definitions); + continue; + } + let keywords = || render::keyword(sema, config, &token, edition); + let underscore = || { + if !is_same_kind { + return None; + } + render::underscore(sema, config, &token, edition) + }; + let rest_pat = || { + if !is_same_kind || token.kind() != DOT2 { + return None; + } - let rest_pat = token.parent().and_then(ast::RestPat::cast)?; - let record_pat_field_list = - rest_pat.syntax().parent().and_then(ast::RecordPatFieldList::cast)?; + let rest_pat = token.parent().and_then(ast::RestPat::cast)?; + let record_pat_field_list = + rest_pat.syntax().parent().and_then(ast::RecordPatFieldList::cast)?; - let record_pat = - record_pat_field_list.syntax().parent().and_then(ast::RecordPat::cast)?; + let record_pat = + record_pat_field_list.syntax().parent().and_then(ast::RecordPat::cast)?; - Some(render::struct_rest_pat(sema, config, &record_pat)) - }) - }) - // try () call hovers - .or_else(|| { - descended().find_map(|token| { - if token.kind() != T!['('] && token.kind() != T![')'] { - return None; + Some(render::struct_rest_pat(sema, config, &record_pat, edition)) + }; + let call = || { + if !is_same_kind || token.kind() != T!['('] && token.kind() != T![')'] { + return None; + } + let arg_list = token.parent().and_then(ast::ArgList::cast)?.syntax().parent()?; + let call_expr = syntax::match_ast! { + match arg_list { + ast::CallExpr(expr) => expr.into(), + ast::MethodCallExpr(expr) => expr.into(), + _ => return None, } - let arg_list = token.parent().and_then(ast::ArgList::cast)?.syntax().parent()?; - let call_expr = syntax::match_ast! { - match arg_list { - ast::CallExpr(expr) => expr.into(), - ast::MethodCallExpr(expr) => expr.into(), - _ => return None, - } - }; - render::type_info_of(sema, config, &Either::Left(call_expr)) - }) - }) - // try closure - .or_else(|| { - descended().find_map(|token| { - if token.kind() != T![|] { - return None; - } - let c = token.parent().and_then(|x| x.parent()).and_then(ast::ClosureExpr::cast)?; - render::closure_expr(sema, config, c) - }) - }) - // tokens - .or_else(|| { - render::literal(sema, original_token.clone()) + }; + render::type_info_of(sema, config, &Either::Left(call_expr), edition) + }; + let closure = || { + if !is_same_kind || token.kind() != T![|] { + return None; + } + let c = token.parent().and_then(|x| x.parent()).and_then(ast::ClosureExpr::cast)?; + render::closure_expr(sema, config, c, edition) + }; + let literal = || { + render::literal(sema, original_token.clone(), edition) .map(|markup| HoverResult { markup, actions: vec![] }) - }); + }; + if let Some(result) = keywords() + .or_else(underscore) + .or_else(rest_pat) + .or_else(call) + .or_else(closure) + .or_else(literal) + { + res.push(result) + } + } - result.map(|mut res: HoverResult| { - res.actions = dedupe_or_merge_hover_actions(res.actions); - RangeInfo::new(original_token.text_range(), res) - }) + res.into_iter() + .unique() + .reduce(|mut acc: HoverResult, HoverResult { markup, actions }| { + acc.actions.extend(actions); + acc.markup = Markup::from(format!("{}\n---\n{markup}", acc.markup)); + acc + }) + .map(|mut res: HoverResult| { + res.actions = dedupe_or_merge_hover_actions(res.actions); + RangeInfo::new(original_token.text_range(), res) + }) } fn hover_ranged( @@ -312,6 +344,7 @@ fn hover_ranged( FileRange { range, .. }: FileRange, file: SyntaxNode, config: &HoverConfig, + edition: Edition, ) -> Option> { // FIXME: make this work in attributes let expr_or_pat = file @@ -320,15 +353,17 @@ fn hover_ranged( .take_while(|it| ast::MacroCall::can_cast(it.kind()) || !ast::Item::can_cast(it.kind())) .find_map(Either::::cast)?; let res = match &expr_or_pat { - Either::Left(ast::Expr::TryExpr(try_expr)) => render::try_expr(sema, config, try_expr), + Either::Left(ast::Expr::TryExpr(try_expr)) => { + render::try_expr(sema, config, try_expr, edition) + } Either::Left(ast::Expr::PrefixExpr(prefix_expr)) if prefix_expr.op_kind() == Some(ast::UnaryOp::Deref) => { - render::deref_expr(sema, config, prefix_expr) + render::deref_expr(sema, config, prefix_expr, edition) } _ => None, }; - let res = res.or_else(|| render::type_info_of(sema, config, &expr_or_pat)); + let res = res.or_else(|| render::type_info_of(sema, config, &expr_or_pat, edition)); res.map(|it| { let range = match expr_or_pat { Either::Left(it) => it.syntax().text_range(), @@ -346,6 +381,7 @@ pub(crate) fn hover_for_definition( scope_node: &SyntaxNode, macro_arm: Option, config: &HoverConfig, + edition: Edition, ) -> HoverResult { let famous_defs = match &def { Definition::BuiltinType(_) => sema.scope(scope_node).map(|it| FamousDefs(sema, it.krate())), @@ -369,15 +405,22 @@ pub(crate) fn hover_for_definition( }; let notable_traits = def_ty.map(|ty| notable_traits(db, &ty)).unwrap_or_default(); - let markup = - render::definition(sema.db, def, famous_defs.as_ref(), ¬able_traits, macro_arm, config); + let markup = render::definition( + sema.db, + def, + famous_defs.as_ref(), + ¬able_traits, + macro_arm, + config, + edition, + ); HoverResult { markup: render::process_markup(sema.db, def, &markup, config), actions: [ show_fn_references_action(sema.db, def), show_implementations_action(sema.db, def), runnable_action(sema, def, file_id), - goto_type_action_for_def(sema.db, def, ¬able_traits), + goto_type_action_for_def(sema.db, def, ¬able_traits, edition), ] .into_iter() .flatten() @@ -469,6 +512,7 @@ fn goto_type_action_for_def( db: &RootDatabase, def: Definition, notable_traits: &[(hir::Trait, Vec<(Option, hir::Name)>)], + edition: Edition, ) -> Option { let mut targets: Vec = Vec::new(); let mut push_new_def = |item: hir::ModuleDef| { @@ -499,13 +543,13 @@ fn goto_type_action_for_def( Definition::GenericParam(hir::GenericParam::ConstParam(it)) => it.ty(db), Definition::Field(field) => field.ty(db), Definition::Function(function) => function.ret_type(db), - _ => return HoverAction::goto_type_from_targets(db, targets), + _ => return HoverAction::goto_type_from_targets(db, targets, edition), }; walk_and_push_ty(db, &ty, &mut push_new_def); } - HoverAction::goto_type_from_targets(db, targets) + HoverAction::goto_type_from_targets(db, targets, edition) } fn walk_and_push_ty( diff --git a/crates/ide/src/hover/render.rs b/crates/ide/src/hover/render.rs index 7091b15b8a..3e41b42be4 100644 --- a/crates/ide/src/hover/render.rs +++ b/crates/ide/src/hover/render.rs @@ -20,6 +20,7 @@ use rustc_apfloat::{ ieee::{Half as f16, Quad as f128}, Float, }; +use span::Edition; use stdx::format_to; use syntax::{algo, ast, match_ast, AstNode, AstToken, Direction, SyntaxToken, T}; @@ -34,27 +35,30 @@ pub(super) fn type_info_of( sema: &Semantics<'_, RootDatabase>, _config: &HoverConfig, expr_or_pat: &Either, + edition: Edition, ) -> Option { let ty_info = match expr_or_pat { Either::Left(expr) => sema.type_of_expr(expr)?, Either::Right(pat) => sema.type_of_pat(pat)?, }; - type_info(sema, _config, ty_info) + type_info(sema, _config, ty_info, edition) } pub(super) fn closure_expr( sema: &Semantics<'_, RootDatabase>, config: &HoverConfig, c: ast::ClosureExpr, + edition: Edition, ) -> Option { let TypeInfo { original, .. } = sema.type_of_expr(&c.into())?; - closure_ty(sema, config, &TypeInfo { original, adjusted: None }) + closure_ty(sema, config, &TypeInfo { original, adjusted: None }, edition) } pub(super) fn try_expr( sema: &Semantics<'_, RootDatabase>, _config: &HoverConfig, try_expr: &ast::TryExpr, + edition: Edition, ) -> Option { let inner_ty = sema.type_of_expr(&try_expr.expr()?)?.original; let mut ancestors = try_expr.syntax().ancestors(); @@ -117,12 +121,12 @@ pub(super) fn try_expr( }; walk_and_push_ty(sema.db, &inner_ty, &mut push_new_def); walk_and_push_ty(sema.db, &body_ty, &mut push_new_def); - if let Some(actions) = HoverAction::goto_type_from_targets(sema.db, targets) { + if let Some(actions) = HoverAction::goto_type_from_targets(sema.db, targets, edition) { res.actions.push(actions); } - let inner_ty = inner_ty.display(sema.db).to_string(); - let body_ty = body_ty.display(sema.db).to_string(); + let inner_ty = inner_ty.display(sema.db, edition).to_string(); + let body_ty = body_ty.display(sema.db, edition).to_string(); let ty_len_max = inner_ty.len().max(body_ty.len()); let l = "Propagated as: ".len() - " Type: ".len(); @@ -146,6 +150,7 @@ pub(super) fn deref_expr( sema: &Semantics<'_, RootDatabase>, _config: &HoverConfig, deref_expr: &ast::PrefixExpr, + edition: Edition, ) -> Option { let inner_ty = sema.type_of_expr(&deref_expr.expr()?)?.original; let TypeInfo { original, adjusted } = @@ -163,9 +168,9 @@ pub(super) fn deref_expr( res.markup = if let Some(adjusted_ty) = adjusted { walk_and_push_ty(sema.db, &adjusted_ty, &mut push_new_def); - let original = original.display(sema.db).to_string(); - let adjusted = adjusted_ty.display(sema.db).to_string(); - let inner = inner_ty.display(sema.db).to_string(); + let original = original.display(sema.db, edition).to_string(); + let adjusted = adjusted_ty.display(sema.db, edition).to_string(); + let inner = inner_ty.display(sema.db, edition).to_string(); let type_len = "To type: ".len(); let coerced_len = "Coerced to: ".len(); let deref_len = "Dereferenced from: ".len(); @@ -183,8 +188,8 @@ pub(super) fn deref_expr( ) .into() } else { - let original = original.display(sema.db).to_string(); - let inner = inner_ty.display(sema.db).to_string(); + let original = original.display(sema.db, edition).to_string(); + let inner = inner_ty.display(sema.db, edition).to_string(); let type_len = "To type: ".len(); let deref_len = "Dereferenced from: ".len(); let max_len = (original.len() + type_len).max(inner.len() + deref_len); @@ -197,7 +202,7 @@ pub(super) fn deref_expr( ) .into() }; - if let Some(actions) = HoverAction::goto_type_from_targets(sema.db, targets) { + if let Some(actions) = HoverAction::goto_type_from_targets(sema.db, targets, edition) { res.actions.push(actions); } @@ -208,6 +213,7 @@ pub(super) fn underscore( sema: &Semantics<'_, RootDatabase>, config: &HoverConfig, token: &SyntaxToken, + edition: Edition, ) -> Option { if token.kind() != T![_] { return None; @@ -216,8 +222,8 @@ pub(super) fn underscore( let _it = match_ast! { match parent { ast::InferType(it) => it, - ast::UnderscoreExpr(it) => return type_info_of(sema, config, &Either::Left(ast::Expr::UnderscoreExpr(it))), - ast::WildcardPat(it) => return type_info_of(sema, config, &Either::Right(ast::Pat::WildcardPat(it))), + ast::UnderscoreExpr(it) => return type_info_of(sema, config, &Either::Left(ast::Expr::UnderscoreExpr(it)),edition), + ast::WildcardPat(it) => return type_info_of(sema, config, &Either::Right(ast::Pat::WildcardPat(it)),edition), _ => return None, } }; @@ -250,16 +256,18 @@ pub(super) fn keyword( sema: &Semantics<'_, RootDatabase>, config: &HoverConfig, token: &SyntaxToken, + edition: Edition, ) -> Option { - if !token.kind().is_keyword() || !config.documentation || !config.keywords { + if !token.kind().is_keyword(edition) || !config.documentation || !config.keywords { return None; } let parent = token.parent()?; let famous_defs = FamousDefs(sema, sema.scope(&parent)?.krate()); - let KeywordHint { description, keyword_mod, actions } = keyword_hints(sema, token, parent); + let KeywordHint { description, keyword_mod, actions } = + keyword_hints(sema, token, parent, edition); - let doc_owner = find_std_module(&famous_defs, &keyword_mod)?; + let doc_owner = find_std_module(&famous_defs, &keyword_mod, edition)?; let docs = doc_owner.docs(sema.db)?; let markup = process_markup( sema.db, @@ -277,6 +285,7 @@ pub(super) fn struct_rest_pat( sema: &Semantics<'_, RootDatabase>, _config: &HoverConfig, pattern: &ast::RecordPat, + edition: Edition, ) -> HoverResult { let missing_fields = sema.record_pattern_missing_fields(pattern); @@ -298,7 +307,7 @@ pub(super) fn struct_rest_pat( res.markup = { let mut s = String::from(".., "); for (f, _) in &missing_fields { - s += f.display(sema.db).to_string().as_ref(); + s += f.display(sema.db, edition).to_string().as_ref(); s += ", "; } // get rid of trailing comma @@ -306,7 +315,7 @@ pub(super) fn struct_rest_pat( Markup::fenced_block(&s) }; - if let Some(actions) = HoverAction::goto_type_from_targets(sema.db, targets) { + if let Some(actions) = HoverAction::goto_type_from_targets(sema.db, targets, edition) { res.actions.push(actions); } res @@ -365,7 +374,7 @@ pub(super) fn process_markup( Markup::from(markup) } -fn definition_owner_name(db: &RootDatabase, def: &Definition) -> Option { +fn definition_owner_name(db: &RootDatabase, def: &Definition, edition: Edition) -> Option { match def { Definition::Field(f) => Some(f.parent_def(db).name(db)), Definition::Local(l) => l.parent(db).name(db), @@ -384,17 +393,22 @@ fn definition_owner_name(db: &RootDatabase, def: &Definition) -> Option } } } - .map(|name| name.display(db).to_string()) + .map(|name| name.display(db, edition).to_string()) } -pub(super) fn path(db: &RootDatabase, module: hir::Module, item_name: Option) -> String { +pub(super) fn path( + db: &RootDatabase, + module: hir::Module, + item_name: Option, + edition: Edition, +) -> String { let crate_name = db.crate_graph()[module.krate().into()].display_name.as_ref().map(|it| it.to_string()); let module_path = module .path_to_root(db) .into_iter() .rev() - .flat_map(|it| it.name(db).map(|name| name.display(db).to_string())); + .flat_map(|it| it.name(db).map(|name| name.display(db, edition).to_string())); crate_name.into_iter().chain(module_path).chain(item_name).join("::") } @@ -405,39 +419,42 @@ pub(super) fn definition( notable_traits: &[(Trait, Vec<(Option, Name)>)], macro_arm: Option, config: &HoverConfig, + edition: Edition, ) -> Markup { - let mod_path = definition_mod_path(db, &def); + let mod_path = definition_mod_path(db, &def, edition); let label = match def { Definition::Trait(trait_) => { - trait_.display_limited(db, config.max_trait_assoc_items_count).to_string() + trait_.display_limited(db, config.max_trait_assoc_items_count, edition).to_string() } Definition::Adt(adt @ (Adt::Struct(_) | Adt::Union(_))) => { - adt.display_limited(db, config.max_fields_count).to_string() + adt.display_limited(db, config.max_fields_count, edition).to_string() } Definition::Variant(variant) => { - variant.display_limited(db, config.max_fields_count).to_string() + variant.display_limited(db, config.max_fields_count, edition).to_string() } Definition::Adt(adt @ Adt::Enum(_)) => { - adt.display_limited(db, config.max_enum_variants_count).to_string() + adt.display_limited(db, config.max_enum_variants_count, edition).to_string() } Definition::SelfType(impl_def) => { let self_ty = &impl_def.self_ty(db); match self_ty.as_adt() { - Some(adt) => adt.display_limited(db, config.max_fields_count).to_string(), - None => self_ty.display(db).to_string(), + Some(adt) => adt.display_limited(db, config.max_fields_count, edition).to_string(), + None => self_ty.display(db, edition).to_string(), } } Definition::Macro(it) => { - let mut label = it.display(db).to_string(); + let mut label = it.display(db, edition).to_string(); if let Some(macro_arm) = macro_arm { format_to!(label, " // matched arm #{}", macro_arm); } label } - Definition::Function(fn_) => fn_.display_with_container_bounds(db, true).to_string(), - _ => def.label(db), + Definition::Function(fn_) => { + fn_.display_with_container_bounds(db, true, edition).to_string() + } + _ => def.label(db, edition), }; - let docs = def.docs(db, famous_defs); + let docs = def.docs(db, famous_defs, edition); let value = (|| match def { Definition::Variant(it) => { if !it.parent_enum(db).is_data_carrying(db) { @@ -452,7 +469,7 @@ pub(super) fn definition( } } Definition::Const(it) => { - let body = it.render_eval(db); + let body = it.render_eval(db, edition); match body { Ok(it) => Some(it), Err(_) => { @@ -510,7 +527,7 @@ pub(super) fn definition( }; let mut desc = String::new(); - if let Some(notable_traits) = render_notable_trait_comment(db, notable_traits) { + if let Some(notable_traits) = render_notable_trait_comment(db, notable_traits, edition) { desc.push_str(¬able_traits); desc.push('\n'); } @@ -527,7 +544,11 @@ pub(super) fn definition( markup(docs.map(Into::into), desc, mod_path) } -pub(super) fn literal(sema: &Semantics<'_, RootDatabase>, token: SyntaxToken) -> Option { +pub(super) fn literal( + sema: &Semantics<'_, RootDatabase>, + token: SyntaxToken, + edition: Edition, +) -> Option { let lit = token.parent().and_then(ast::Literal::cast)?; let ty = if let Some(p) = lit.syntax().parent().and_then(ast::Pat::cast) { sema.type_of_pat(&p)? @@ -574,7 +595,7 @@ pub(super) fn literal(sema: &Semantics<'_, RootDatabase>, token: SyntaxToken) -> _ => return None } }; - let ty = ty.display(sema.db); + let ty = ty.display(sema.db, edition); let mut s = format!("```rust\n{ty}\n```\n___\n\n"); match value { @@ -593,6 +614,7 @@ pub(super) fn literal(sema: &Semantics<'_, RootDatabase>, token: SyntaxToken) -> fn render_notable_trait_comment( db: &RootDatabase, notable_traits: &[(Trait, Vec<(Option, Name)>)], + edition: Edition, ) -> Option { let mut desc = String::new(); let mut needs_impl_header = true; @@ -602,17 +624,17 @@ fn render_notable_trait_comment( } else { ", " }); - format_to!(desc, "{}", trait_.name(db).display(db),); + format_to!(desc, "{}", trait_.name(db).display(db, edition)); if !assoc_types.is_empty() { desc.push('<'); format_to!( desc, "{}", assoc_types.iter().format_with(", ", |(ty, name), f| { - f(&name.display(db))?; + f(&name.display(db, edition))?; f(&" = ")?; match ty { - Some(ty) => f(&ty.display(db)), + Some(ty) => f(&ty.display(db, edition)), None => f(&"?"), } }) @@ -627,8 +649,9 @@ fn type_info( sema: &Semantics<'_, RootDatabase>, config: &HoverConfig, ty: TypeInfo, + edition: Edition, ) -> Option { - if let Some(res) = closure_ty(sema, config, &ty) { + if let Some(res) = closure_ty(sema, config, &ty, edition) { return Some(res); }; let db = sema.db; @@ -654,17 +677,17 @@ fn type_info( } else { ", " }); - format_to!(desc, "{}", trait_.name(db).display(db),); + format_to!(desc, "{}", trait_.name(db).display(db, edition)); if !assoc_types.is_empty() { desc.push('<'); format_to!( desc, "{}", assoc_types.into_iter().format_with(", ", |(ty, name), f| { - f(&name.display(db))?; + f(&name.display(db, edition))?; f(&" = ")?; match ty { - Some(ty) => f(&ty.display(db)), + Some(ty) => f(&ty.display(db, edition)), None => f(&"?"), } }) @@ -678,8 +701,8 @@ fn type_info( desc }; - let original = original.display(db).to_string(); - let adjusted = adjusted_ty.display(db).to_string(); + let original = original.display(db, edition).to_string(); + let adjusted = adjusted_ty.display(db, edition).to_string(); let static_text_diff_len = "Coerced to: ".len() - "Type: ".len(); format!( "```text\nType: {:>apad$}\nCoerced to: {:>opad$}\n{notable}```\n", @@ -690,14 +713,15 @@ fn type_info( ) .into() } else { - let mut desc = match render_notable_trait_comment(db, ¬able_traits(db, &original)) { - Some(desc) => desc + "\n", - None => String::new(), - }; - format_to!(desc, "{}", original.display(db)); + let mut desc = + match render_notable_trait_comment(db, ¬able_traits(db, &original), edition) { + Some(desc) => desc + "\n", + None => String::new(), + }; + format_to!(desc, "{}", original.display(db, edition)); Markup::fenced_block(&desc) }; - if let Some(actions) = HoverAction::goto_type_from_targets(db, targets) { + if let Some(actions) = HoverAction::goto_type_from_targets(db, targets, edition) { res.actions.push(actions); } Some(res) @@ -707,6 +731,7 @@ fn closure_ty( sema: &Semantics<'_, RootDatabase>, config: &HoverConfig, TypeInfo { original, adjusted }: &TypeInfo, + edition: Edition, ) -> Option { let c = original.as_closure()?; let mut captures_rendered = c.captured_items(sema.db) @@ -739,12 +764,12 @@ fn closure_ty( walk_and_push_ty(sema.db, adjusted_ty, &mut push_new_def); format!( "\nCoerced to: {}", - adjusted_ty.display(sema.db).with_closure_style(hir::ClosureStyle::ImplFn) + adjusted_ty.display(sema.db, edition).with_closure_style(hir::ClosureStyle::ImplFn) ) } else { String::new() }; - let mut markup = format!("```rust\n{}", c.display_with_id(sema.db),); + let mut markup = format!("```rust\n{}", c.display_with_id(sema.db, edition)); if let Some(layout) = render_memory_layout(config.memory_layout, || original.layout(sema.db), |_| None, |_| None) @@ -757,23 +782,23 @@ fn closure_ty( format_to!( markup, "\n{}\n```{adjusted}\n\n## Captures\n{}", - c.display_with_impl(sema.db), + c.display_with_impl(sema.db, edition), captures_rendered, ); let mut res = HoverResult::default(); - if let Some(actions) = HoverAction::goto_type_from_targets(sema.db, targets) { + if let Some(actions) = HoverAction::goto_type_from_targets(sema.db, targets, edition) { res.actions.push(actions); } res.markup = markup.into(); Some(res) } -fn definition_mod_path(db: &RootDatabase, def: &Definition) -> Option { +fn definition_mod_path(db: &RootDatabase, def: &Definition, edition: Edition) -> Option { if matches!(def, Definition::GenericParam(_) | Definition::Local(_) | Definition::Label(_)) { return None; } - def.module(db).map(|module| path(db, module, definition_owner_name(db, def))) + def.module(db).map(|module| path(db, module, definition_owner_name(db, def, edition), edition)) } fn markup(docs: Option, desc: String, mod_path: Option) -> Markup { @@ -792,12 +817,16 @@ fn markup(docs: Option, desc: String, mod_path: Option) -> Marku buf.into() } -fn find_std_module(famous_defs: &FamousDefs<'_, '_>, name: &str) -> Option { +fn find_std_module( + famous_defs: &FamousDefs<'_, '_>, + name: &str, + edition: Edition, +) -> Option { let db = famous_defs.0.db; let std_crate = famous_defs.std()?; 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) + module.name(db).map_or(false, |module| module.display(db, edition).to_string() == name) }) } @@ -888,6 +917,7 @@ fn keyword_hints( sema: &Semantics<'_, RootDatabase>, token: &SyntaxToken, parent: syntax::SyntaxNode, + edition: Edition, ) -> KeywordHint { match token.kind() { T![await] | T![loop] | T![match] | T![unsafe] | T![as] | T![try] | T![if] | T![else] => { @@ -905,12 +935,12 @@ fn keyword_hints( walk_and_push_ty(sema.db, &ty.original, &mut push_new_def); let ty = ty.adjusted(); - let description = format!("{}: {}", token.text(), ty.display(sema.db)); + let description = format!("{}: {}", token.text(), ty.display(sema.db, edition)); KeywordHint { description, keyword_mod, - actions: HoverAction::goto_type_from_targets(sema.db, targets) + actions: HoverAction::goto_type_from_targets(sema.db, targets, edition) .into_iter() .collect(), } diff --git a/crates/ide/src/hover/tests.rs b/crates/ide/src/hover/tests.rs index 516e32ef91..9585bdbe4c 100644 --- a/crates/ide/src/hover/tests.rs +++ b/crates/ide/src/hover/tests.rs @@ -8465,7 +8465,7 @@ impl Iterator for S { file_id: FileId( 1, ), - full_range: 7800..8008, + full_range: 7800..8042, focus_range: 7865..7871, name: "Future", kind: Trait, @@ -8479,8 +8479,8 @@ impl Iterator for S { file_id: FileId( 1, ), - full_range: 8638..9104, - focus_range: 8682..8690, + full_range: 8672..9171, + focus_range: 8749..8757, name: "Iterator", kind: Trait, container_name: "iterator", @@ -8602,3 +8602,103 @@ fn test() { "#]], ); } + +#[test] +fn issue_17871() { + check( + r#" +trait T { + fn f(); +} + +struct S {} +impl T for S { + fn f() {} +} + +fn main() { + let x$0 = S::f::; +} +"#, + expect![[r#" + *x* + + ```rust + // size = 0, align = 1 + let x: fn f() + ``` + "#]], + ); +} + +#[test] +fn raw_keyword_different_editions() { + check( + r#" +//- /lib1.rs crate:with_edition_2015 edition:2015 +pub fn dyn() {} + +//- /lib2.rs crate:with_edition_2018 edition:2018 deps:with_edition_2015 new_source_root:local +fn foo() { + with_edition_2015::r#dyn$0(); +} + "#, + expect![[r#" + *r#dyn* + + ```rust + with_edition_2015 + ``` + + ```rust + pub fn r#dyn() + ``` + "#]], + ); + + check( + r#" +//- /lib1.rs crate:with_edition_2018 edition:2018 +pub fn r#dyn() {} + +//- /lib2.rs crate:with_edition_2015 edition:2015 deps:with_edition_2018 new_source_root:local +fn foo() { + with_edition_2018::dyn$0(); +} + "#, + expect![[r#" + *dyn* + + ```rust + with_edition_2018 + ``` + + ```rust + pub fn dyn() + ``` + "#]], + ); + + check( + r#" +//- /lib1.rs crate:escaping_needlessly edition:2015 +pub fn r#dyn() {} + +//- /lib2.rs crate:dependent edition:2015 deps:escaping_needlessly new_source_root:local +fn foo() { + escaping_needlessly::dyn$0(); +} + "#, + expect![[r#" + *dyn* + + ```rust + escaping_needlessly + ``` + + ```rust + pub fn dyn() + ``` + "#]], + ); +} diff --git a/crates/ide/src/inlay_hints.rs b/crates/ide/src/inlay_hints.rs index 0a8d272757..6a5d5e26a4 100644 --- a/crates/ide/src/inlay_hints.rs +++ b/crates/ide/src/inlay_hints.rs @@ -11,7 +11,7 @@ use hir::{ use ide_db::{famous_defs::FamousDefs, FileRange, RootDatabase}; use itertools::Itertools; use smallvec::{smallvec, SmallVec}; -use span::EditionedFileId; +use span::{Edition, EditionedFileId}; use stdx::never; use syntax::{ ast::{self, AstNode}, @@ -372,6 +372,7 @@ fn label_of_ty( famous_defs @ FamousDefs(sema, _): &FamousDefs<'_, '_>, config: &InlayHintsConfig, ty: &hir::Type, + edition: Edition, ) -> Option { fn rec( sema: &Semantics<'_, RootDatabase>, @@ -380,6 +381,7 @@ fn label_of_ty( ty: &hir::Type, label_builder: &mut InlayHintLabelBuilder<'_>, config: &InlayHintsConfig, + edition: Edition, ) -> Result<(), HirDisplayError> { let iter_item_type = hint_iterator(sema, famous_defs, ty); match iter_item_type { @@ -410,12 +412,12 @@ fn label_of_ty( label_builder.write_str(LABEL_ITEM)?; label_builder.end_location_link(); label_builder.write_str(LABEL_MIDDLE2)?; - rec(sema, famous_defs, max_length, &ty, label_builder, config)?; + rec(sema, famous_defs, max_length, &ty, label_builder, config, edition)?; label_builder.write_str(LABEL_END)?; Ok(()) } None => ty - .display_truncated(sema.db, max_length) + .display_truncated(sema.db, max_length, edition) .with_closure_style(config.closure_style) .write_to(label_builder), } @@ -427,7 +429,7 @@ fn label_of_ty( location: None, result: InlayHintLabel::default(), }; - let _ = rec(sema, famous_defs, config.max_length, ty, &mut label_builder, config); + let _ = rec(sema, famous_defs, config.max_length, ty, &mut label_builder, config, edition); let r = label_builder.finish(); Some(r) } @@ -569,7 +571,7 @@ fn hints( match node { ast::Expr(expr) => { chaining::hints(hints, famous_defs, config, file_id, &expr); - adjustment::hints(hints, sema, config, &expr); + adjustment::hints(hints, sema, config, file_id, &expr); match expr { ast::Expr::CallExpr(it) => param_name::hints(hints, sema, config, ast::Expr::from(it)), ast::Expr::MethodCallExpr(it) => { @@ -600,7 +602,7 @@ fn hints( // FIXME: record impl lifetimes so they aren't being reused in assoc item lifetime inlay hints ast::Item::Impl(_) => None, ast::Item::Fn(it) => { - implicit_drop::hints(hints, sema, config, &it); + implicit_drop::hints(hints, sema, config, file_id, &it); fn_lifetime_fn::hints(hints, config, it) }, // static type elisions diff --git a/crates/ide/src/inlay_hints/adjustment.rs b/crates/ide/src/inlay_hints/adjustment.rs index 7932d8efbc..756198d0c0 100644 --- a/crates/ide/src/inlay_hints/adjustment.rs +++ b/crates/ide/src/inlay_hints/adjustment.rs @@ -10,6 +10,7 @@ use hir::{ }; use ide_db::RootDatabase; +use span::EditionedFileId; use stdx::never; use syntax::{ ast::{self, make, AstNode}, @@ -25,6 +26,7 @@ pub(super) fn hints( acc: &mut Vec, sema: &Semantics<'_, RootDatabase>, config: &InlayHintsConfig, + file_id: EditionedFileId, expr: &ast::Expr, ) -> Option<()> { if config.adjustment_hints_hide_outside_unsafe && !sema.is_inside_unsafe(expr) { @@ -141,8 +143,8 @@ pub(super) fn hints( if postfix { format!(".{}", text.trim_end()) } else { text.to_owned() }, Some(InlayTooltip::Markdown(format!( "`{}` → `{}` ({coercion} coercion)", - source.display(sema.db), - target.display(sema.db), + source.display(sema.db, file_id.edition()), + target.display(sema.db, file_id.edition()), ))), None, ); diff --git a/crates/ide/src/inlay_hints/bind_pat.rs b/crates/ide/src/inlay_hints/bind_pat.rs index 7310852b8e..82b0a6ffcf 100644 --- a/crates/ide/src/inlay_hints/bind_pat.rs +++ b/crates/ide/src/inlay_hints/bind_pat.rs @@ -22,7 +22,7 @@ pub(super) fn hints( acc: &mut Vec, famous_defs @ FamousDefs(sema, _): &FamousDefs<'_, '_>, config: &InlayHintsConfig, - _file_id: EditionedFileId, + file_id: EditionedFileId, pat: &ast::IdentPat, ) -> Option<()> { if !config.type_hints { @@ -67,7 +67,7 @@ pub(super) fn hints( return None; } - let mut label = label_of_ty(famous_defs, config, &ty)?; + let mut label = label_of_ty(famous_defs, config, &ty, file_id.edition())?; if config.hide_named_constructor_hints && is_named_constructor(sema, pat, &label.to_string()).is_some() @@ -342,7 +342,7 @@ fn f<'a>() { let x = S::<'static>; //^ S<'static> let y = S::<'_>; - //^ S + //^ S<'_> let z = S::<'a>; //^ S<'a> diff --git a/crates/ide/src/inlay_hints/chaining.rs b/crates/ide/src/inlay_hints/chaining.rs index 4e15213b8b..35f4d46e18 100644 --- a/crates/ide/src/inlay_hints/chaining.rs +++ b/crates/ide/src/inlay_hints/chaining.rs @@ -14,7 +14,7 @@ pub(super) fn hints( acc: &mut Vec, famous_defs @ FamousDefs(sema, _): &FamousDefs<'_, '_>, config: &InlayHintsConfig, - _file_id: EditionedFileId, + file_id: EditionedFileId, expr: &ast::Expr, ) -> Option<()> { if !config.chaining_hints { @@ -58,7 +58,7 @@ pub(super) fn hints( } } } - let label = label_of_ty(famous_defs, config, &ty)?; + let label = label_of_ty(famous_defs, config, &ty, file_id.edition())?; acc.push(InlayHint { range: expr.syntax().text_range(), kind: InlayKind::Chaining, diff --git a/crates/ide/src/inlay_hints/closing_brace.rs b/crates/ide/src/inlay_hints/closing_brace.rs index 8f2777f392..d78fd64bdf 100644 --- a/crates/ide/src/inlay_hints/closing_brace.rs +++ b/crates/ide/src/inlay_hints/closing_brace.rs @@ -36,8 +36,12 @@ pub(super) fn hints( let ty = imp.self_ty(sema.db); let trait_ = imp.trait_(sema.db); let hint_text = match trait_ { - Some(tr) => format!("impl {} for {}", tr.name(sema.db).display(sema.db), ty.display_truncated(sema.db, config.max_length)), - None => format!("impl {}", ty.display_truncated(sema.db, config.max_length)), + Some(tr) => format!( + "impl {} for {}", + tr.name(sema.db).display(sema.db, file_id.edition()), + ty.display_truncated(sema.db, config.max_length, file_id.edition(), + )), + None => format!("impl {}", ty.display_truncated(sema.db, config.max_length, file_id.edition())), }; (hint_text, None) }, diff --git a/crates/ide/src/inlay_hints/closure_ret.rs b/crates/ide/src/inlay_hints/closure_ret.rs index f6bd7ca064..325c204069 100644 --- a/crates/ide/src/inlay_hints/closure_ret.rs +++ b/crates/ide/src/inlay_hints/closure_ret.rs @@ -14,7 +14,7 @@ pub(super) fn hints( acc: &mut Vec, famous_defs @ FamousDefs(sema, _): &FamousDefs<'_, '_>, config: &InlayHintsConfig, - _file_id: EditionedFileId, + file_id: EditionedFileId, closure: ast::ClosureExpr, ) -> Option<()> { if config.closure_return_type_hints == ClosureReturnTypeHints::Never { @@ -43,7 +43,7 @@ pub(super) fn hints( return None; } - let mut label = label_of_ty(famous_defs, config, &ty)?; + let mut label = label_of_ty(famous_defs, config, &ty, file_id.edition())?; if arrow.is_none() { label.prepend_str(" -> "); diff --git a/crates/ide/src/inlay_hints/implicit_drop.rs b/crates/ide/src/inlay_hints/implicit_drop.rs index 7f901db28d..b4695a2b35 100644 --- a/crates/ide/src/inlay_hints/implicit_drop.rs +++ b/crates/ide/src/inlay_hints/implicit_drop.rs @@ -12,6 +12,7 @@ use hir::{ }; use ide_db::{FileRange, RootDatabase}; +use span::EditionedFileId; use syntax::{ ast::{self, AstNode}, match_ast, ToSmolStr, @@ -23,6 +24,7 @@ pub(super) fn hints( acc: &mut Vec, sema: &Semantics<'_, RootDatabase>, config: &InlayHintsConfig, + file_id: EditionedFileId, def: &ast::Fn, ) -> Option<()> { if !config.implicit_drop_hints { @@ -100,7 +102,7 @@ pub(super) fn hints( }) }); let binding = &hir.bindings[*binding]; - let name = binding.name.display_no_db().to_smolstr(); + let name = binding.name.display_no_db(file_id.edition()).to_smolstr(); if name.starts_with(" Some(it.name(sema.db).display_no_db().to_smolstr()), + hir::CallableKind::Function(it) => { + Some(it.name(sema.db).unescaped().display_no_db().to_smolstr()) + } _ => None, }; let fn_name = fn_name.as_deref(); diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index eff4bc3d37..ba0aaae19c 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs @@ -104,7 +104,9 @@ pub use crate::{ rename::RenameError, runnables::{Runnable, RunnableKind, TestId}, signature_help::SignatureHelp, - static_index::{StaticIndex, StaticIndexedFile, TokenId, TokenStaticData}, + static_index::{ + StaticIndex, StaticIndexedFile, TokenId, TokenStaticData, VendoredLibrariesConfig, + }, syntax_highlighting::{ tags::{Highlight, HlMod, HlMods, HlOperator, HlPunct, HlTag}, HighlightConfig, HlRange, diff --git a/crates/ide/src/markup.rs b/crates/ide/src/markup.rs index 4a4e29fa33..750d125426 100644 --- a/crates/ide/src/markup.rs +++ b/crates/ide/src/markup.rs @@ -5,7 +5,7 @@ //! what is used by LSP, so let's keep it simple. use std::fmt; -#[derive(Default, Debug)] +#[derive(Clone, Default, Debug, Hash, PartialEq, Eq)] pub struct Markup { text: String, } diff --git a/crates/ide/src/moniker.rs b/crates/ide/src/moniker.rs index 1b64bc9260..4be1b57098 100644 --- a/crates/ide/src/moniker.rs +++ b/crates/ide/src/moniker.rs @@ -3,7 +3,7 @@ use core::fmt; -use hir::{Adt, AsAssocItem, AssocItemContainer, Crate, DescendPreference, MacroKind, Semantics}; +use hir::{Adt, AsAssocItem, AssocItemContainer, Crate, MacroKind, Semantics}; use ide_db::{ base_db::{CrateOrigin, LangCrateOrigin}, defs::{Definition, IdentClass}, @@ -154,7 +154,7 @@ pub(crate) fn moniker( }); } let navs = sema - .descend_into_macros(DescendPreference::None, original_token.clone()) + .descend_into_macros_exact(original_token.clone()) .into_iter() .filter_map(|token| { IdentClass::classify_token(sema, &token).map(IdentClass::definitions_no_ops).map(|it| { @@ -249,10 +249,11 @@ pub(crate) fn def_to_moniker( let module = def.module(db)?; let krate = module.krate(); + let edition = krate.edition(db); let mut description = vec![]; description.extend(module.path_to_root(db).into_iter().filter_map(|x| { Some(MonikerDescriptor { - name: x.name(db)?.display(db).to_string(), + name: x.name(db)?.display(db, edition).to_string(), desc: def_to_kind(db, x.into()).into(), }) })); @@ -265,7 +266,7 @@ pub(crate) fn def_to_moniker( // Because different traits can have functions with the same name, // we have to include the trait name as part of the moniker for uniqueness. description.push(MonikerDescriptor { - name: trait_.name(db).display(db).to_string(), + name: trait_.name(db).display(db, edition).to_string(), desc: def_to_kind(db, trait_.into()).into(), }); } @@ -274,14 +275,14 @@ pub(crate) fn def_to_moniker( // we add both the struct name and the trait name to the path if let Some(adt) = impl_.self_ty(db).as_adt() { description.push(MonikerDescriptor { - name: adt.name(db).display(db).to_string(), + name: adt.name(db).display(db, edition).to_string(), desc: def_to_kind(db, adt.into()).into(), }); } if let Some(trait_) = impl_.trait_(db) { description.push(MonikerDescriptor { - name: trait_.name(db).display(db).to_string(), + name: trait_.name(db).display(db, edition).to_string(), desc: def_to_kind(db, trait_.into()).into(), }); } @@ -291,7 +292,7 @@ pub(crate) fn def_to_moniker( if let Definition::Field(it) = def { description.push(MonikerDescriptor { - name: it.parent_def(db).name(db).display(db).to_string(), + name: it.parent_def(db).name(db).display(db, edition).to_string(), desc: def_to_kind(db, it.parent_def(db).into()).into(), }); } @@ -303,7 +304,7 @@ pub(crate) fn def_to_moniker( let parent_name = parent.name(db); if let Some(name) = parent_name { description.push(MonikerDescriptor { - name: name.display(db).to_string(), + name: name.display(db, edition).to_string(), desc: def_to_kind(db, parent).into(), }); } @@ -326,53 +327,53 @@ pub(crate) fn def_to_moniker( return None; } - MonikerDescriptor { name: local.name(db).display(db).to_string(), desc } + MonikerDescriptor { name: local.name(db).display(db, edition).to_string(), desc } } Definition::Macro(m) => { - MonikerDescriptor { name: m.name(db).display(db).to_string(), desc } + MonikerDescriptor { name: m.name(db).display(db, edition).to_string(), desc } } Definition::Function(f) => { - MonikerDescriptor { name: f.name(db).display(db).to_string(), desc } + MonikerDescriptor { name: f.name(db).display(db, edition).to_string(), desc } } Definition::Variant(v) => { - MonikerDescriptor { name: v.name(db).display(db).to_string(), desc } + MonikerDescriptor { name: v.name(db).display(db, edition).to_string(), desc } } Definition::Const(c) => { - MonikerDescriptor { name: c.name(db)?.display(db).to_string(), desc } + MonikerDescriptor { name: c.name(db)?.display(db, edition).to_string(), desc } } Definition::Trait(trait_) => { - MonikerDescriptor { name: trait_.name(db).display(db).to_string(), desc } + MonikerDescriptor { name: trait_.name(db).display(db, edition).to_string(), desc } } Definition::TraitAlias(ta) => { - MonikerDescriptor { name: ta.name(db).display(db).to_string(), desc } + MonikerDescriptor { name: ta.name(db).display(db, edition).to_string(), desc } } Definition::TypeAlias(ta) => { - MonikerDescriptor { name: ta.name(db).display(db).to_string(), desc } + MonikerDescriptor { name: ta.name(db).display(db, edition).to_string(), desc } } Definition::Module(m) => { - MonikerDescriptor { name: m.name(db)?.display(db).to_string(), desc } + MonikerDescriptor { name: m.name(db)?.display(db, edition).to_string(), desc } } Definition::BuiltinType(b) => { - MonikerDescriptor { name: b.name().display(db).to_string(), desc } + MonikerDescriptor { name: b.name().display(db, edition).to_string(), desc } } Definition::SelfType(imp) => MonikerDescriptor { - name: imp.self_ty(db).as_adt()?.name(db).display(db).to_string(), + name: imp.self_ty(db).as_adt()?.name(db).display(db, edition).to_string(), desc, }, Definition::Field(it) => { - MonikerDescriptor { name: it.name(db).display(db).to_string(), desc } + MonikerDescriptor { name: it.name(db).display(db, edition).to_string(), desc } } Definition::TupleField(it) => { - MonikerDescriptor { name: it.name().display(db).to_string(), desc } + MonikerDescriptor { name: it.name().display(db, edition).to_string(), desc } } Definition::Adt(adt) => { - MonikerDescriptor { name: adt.name(db).display(db).to_string(), desc } + MonikerDescriptor { name: adt.name(db).display(db, edition).to_string(), desc } } Definition::Static(s) => { - MonikerDescriptor { name: s.name(db).display(db).to_string(), desc } + MonikerDescriptor { name: s.name(db).display(db, edition).to_string(), desc } } Definition::ExternCrateDecl(m) => { - MonikerDescriptor { name: m.name(db).display(db).to_string(), desc } + MonikerDescriptor { name: m.name(db).display(db, edition).to_string(), desc } } }; diff --git a/crates/ide/src/navigation_target.rs b/crates/ide/src/navigation_target.rs index 066141d36f..9ace9fda62 100644 --- a/crates/ide/src/navigation_target.rs +++ b/crates/ide/src/navigation_target.rs @@ -5,14 +5,15 @@ use std::fmt; use arrayvec::ArrayVec; use either::Either; use hir::{ - db::ExpandDatabase, symbols::FileSymbol, AssocItem, FieldSource, HasContainer, HasSource, - HirDisplay, HirFileId, InFile, LocalSource, ModuleSource, + db::ExpandDatabase, symbols::FileSymbol, AssocItem, FieldSource, HasContainer, HasCrate, + HasSource, HirDisplay, HirFileId, HirFileIdExt, InFile, LocalSource, ModuleSource, }; use ide_db::{ defs::Definition, documentation::{Documentation, HasDocs}, FileId, FileRange, RootDatabase, SymbolKind, }; +use span::Edition; use stdx::never; use syntax::{ ast::{self, HasName}, @@ -97,7 +98,9 @@ impl NavigationTarget { db: &RootDatabase, module: hir::Module, ) -> UpmappingResult { - let name = module.name(db).map(|it| it.display_no_db().to_smolstr()).unwrap_or_default(); + let edition = module.krate().edition(db); + let name = + module.name(db).map(|it| it.display_no_db(edition).to_smolstr()).unwrap_or_default(); match module.declaration_source(db) { Some(InFile { value, file_id }) => { orig_range_with_focus(db, file_id, value.syntax(), value.name()).map( @@ -110,7 +113,7 @@ impl NavigationTarget { SymbolKind::Module, ); res.docs = module.docs(db); - res.description = Some(module.display(db).to_string()); + res.description = Some(module.display(db, edition).to_string()); res }, ) @@ -175,6 +178,8 @@ impl NavigationTarget { impl TryToNav for FileSymbol { fn try_to_nav(&self, db: &RootDatabase) -> Option> { + let edition = + self.def.module(db).map(|it| it.krate().edition(db)).unwrap_or(Edition::CURRENT); Some( orig_range_with_focus_r( db, @@ -185,27 +190,26 @@ impl TryToNav for FileSymbol { .map(|(FileRange { file_id, range: full_range }, focus_range)| { NavigationTarget { file_id, - name: self - .is_alias - .then(|| self.def.name(db)) - .flatten() - .map_or_else(|| self.name.clone(), |it| it.display_no_db().to_smolstr()), + name: self.is_alias.then(|| self.def.name(db)).flatten().map_or_else( + || self.name.clone(), + |it| it.display_no_db(edition).to_smolstr(), + ), alias: self.is_alias.then(|| self.name.clone()), kind: Some(hir::ModuleDefId::from(self.def).into()), full_range, focus_range, container_name: self.container_name.clone(), description: match self.def { - hir::ModuleDef::Module(it) => Some(it.display(db).to_string()), - hir::ModuleDef::Function(it) => Some(it.display(db).to_string()), - hir::ModuleDef::Adt(it) => Some(it.display(db).to_string()), - hir::ModuleDef::Variant(it) => Some(it.display(db).to_string()), - hir::ModuleDef::Const(it) => Some(it.display(db).to_string()), - hir::ModuleDef::Static(it) => Some(it.display(db).to_string()), - hir::ModuleDef::Trait(it) => Some(it.display(db).to_string()), - hir::ModuleDef::TraitAlias(it) => Some(it.display(db).to_string()), - hir::ModuleDef::TypeAlias(it) => Some(it.display(db).to_string()), - hir::ModuleDef::Macro(it) => Some(it.display(db).to_string()), + hir::ModuleDef::Module(it) => Some(it.display(db, edition).to_string()), + hir::ModuleDef::Function(it) => Some(it.display(db, edition).to_string()), + hir::ModuleDef::Adt(it) => Some(it.display(db, edition).to_string()), + hir::ModuleDef::Variant(it) => Some(it.display(db, edition).to_string()), + hir::ModuleDef::Const(it) => Some(it.display(db, edition).to_string()), + hir::ModuleDef::Static(it) => Some(it.display(db, edition).to_string()), + hir::ModuleDef::Trait(it) => Some(it.display(db, edition).to_string()), + hir::ModuleDef::TraitAlias(it) => Some(it.display(db, edition).to_string()), + hir::ModuleDef::TypeAlias(it) => Some(it.display(db, edition).to_string()), + hir::ModuleDef::Macro(it) => Some(it.display(db, edition).to_string()), hir::ModuleDef::BuiltinType(_) => None, }, docs: None, @@ -271,11 +275,13 @@ pub(crate) trait ToNavFromAst: Sized { } } -fn container_name(db: &RootDatabase, t: impl HasContainer) -> Option { +fn container_name(db: &RootDatabase, t: impl HasContainer, edition: Edition) -> Option { match t.container(db) { - hir::ItemContainer::Trait(it) => Some(it.name(db).display_no_db().to_smolstr()), + hir::ItemContainer::Trait(it) => Some(it.name(db).display_no_db(edition).to_smolstr()), // FIXME: Handle owners of blocks correctly here - hir::ItemContainer::Module(it) => it.name(db).map(|name| name.display_no_db().to_smolstr()), + hir::ItemContainer::Module(it) => { + it.name(db).map(|name| name.display_no_db(edition).to_smolstr()) + } _ => None, } } @@ -283,32 +289,32 @@ fn container_name(db: &RootDatabase, t: impl HasContainer) -> Option { impl ToNavFromAst for hir::Function { const KIND: SymbolKind = SymbolKind::Function; fn container_name(self, db: &RootDatabase) -> Option { - container_name(db, self) + container_name(db, self, self.krate(db).edition(db)) } } impl ToNavFromAst for hir::Const { const KIND: SymbolKind = SymbolKind::Const; fn container_name(self, db: &RootDatabase) -> Option { - container_name(db, self) + container_name(db, self, self.krate(db).edition(db)) } } impl ToNavFromAst for hir::Static { const KIND: SymbolKind = SymbolKind::Static; fn container_name(self, db: &RootDatabase) -> Option { - container_name(db, self) + container_name(db, self, self.krate(db).edition(db)) } } impl ToNavFromAst for hir::Struct { const KIND: SymbolKind = SymbolKind::Struct; fn container_name(self, db: &RootDatabase) -> Option { - container_name(db, self) + container_name(db, self, self.krate(db).edition(db)) } } impl ToNavFromAst for hir::Enum { const KIND: SymbolKind = SymbolKind::Enum; fn container_name(self, db: &RootDatabase) -> Option { - container_name(db, self) + container_name(db, self, self.krate(db).edition(db)) } } impl ToNavFromAst for hir::Variant { @@ -317,25 +323,25 @@ impl ToNavFromAst for hir::Variant { impl ToNavFromAst for hir::Union { const KIND: SymbolKind = SymbolKind::Union; fn container_name(self, db: &RootDatabase) -> Option { - container_name(db, self) + container_name(db, self, self.krate(db).edition(db)) } } impl ToNavFromAst for hir::TypeAlias { const KIND: SymbolKind = SymbolKind::TypeAlias; fn container_name(self, db: &RootDatabase) -> Option { - container_name(db, self) + container_name(db, self, self.krate(db).edition(db)) } } impl ToNavFromAst for hir::Trait { const KIND: SymbolKind = SymbolKind::Trait; fn container_name(self, db: &RootDatabase) -> Option { - container_name(db, self) + container_name(db, self, self.krate(db).edition(db)) } } impl ToNavFromAst for hir::TraitAlias { const KIND: SymbolKind = SymbolKind::TraitAlias; fn container_name(self, db: &RootDatabase) -> Option { - container_name(db, self) + container_name(db, self, self.krate(db).edition(db)) } } @@ -346,6 +352,7 @@ where { fn try_to_nav(&self, db: &RootDatabase) -> Option> { let src = self.source(db)?; + let edition = src.file_id.original_file(db).edition(); Some( NavigationTarget::from_named( db, @@ -354,7 +361,7 @@ where ) .map(|mut res| { res.docs = self.docs(db); - res.description = Some(self.display(db).to_string()); + res.description = Some(self.display(db, edition).to_string()); res.container_name = self.container_name(db); res }), @@ -365,8 +372,10 @@ where impl ToNav for hir::Module { fn to_nav(&self, db: &RootDatabase) -> UpmappingResult { let InFile { file_id, value } = self.definition_source(db); + let edition = self.krate(db).edition(db); - let name = self.name(db).map(|it| it.display_no_db().to_smolstr()).unwrap_or_default(); + let name = + self.name(db).map(|it| it.display_no_db(edition).to_smolstr()).unwrap_or_default(); let (syntax, focus) = match &value { ModuleSource::SourceFile(node) => (node.syntax(), None), ModuleSource::Module(node) => (node.syntax(), node.name()), @@ -418,6 +427,7 @@ impl TryToNav for hir::ExternCrateDecl { let focus = value .rename() .map_or_else(|| value.name_ref().map(Either::Left), |it| it.name().map(Either::Right)); + let edition = self.module(db).krate().edition(db); Some(orig_range_with_focus(db, file_id, value.syntax(), focus).map( |(FileRange { file_id, range: full_range }, focus_range)| { @@ -425,7 +435,7 @@ impl TryToNav for hir::ExternCrateDecl { file_id, self.alias_or_name(db) .unwrap_or_else(|| self.name(db)) - .display_no_db() + .display_no_db(edition) .to_smolstr(), focus_range, full_range, @@ -433,8 +443,8 @@ impl TryToNav for hir::ExternCrateDecl { ); res.docs = self.docs(db); - res.description = Some(self.display(db).to_string()); - res.container_name = container_name(db, *self); + res.description = Some(self.display(db, edition).to_string()); + res.container_name = container_name(db, *self, edition); res }, )) @@ -444,13 +454,14 @@ impl TryToNav for hir::ExternCrateDecl { impl TryToNav for hir::Field { fn try_to_nav(&self, db: &RootDatabase) -> Option> { let src = self.source(db)?; + let edition = self.parent_def(db).module(db).krate().edition(db); let field_source = match &src.value { FieldSource::Named(it) => { NavigationTarget::from_named(db, src.with_value(it), SymbolKind::Field).map( |mut res| { res.docs = self.docs(db); - res.description = Some(self.display(db).to_string()); + res.description = Some(self.display(db, edition).to_string()); res }, ) @@ -531,10 +542,11 @@ impl ToNav for LocalSource { Either::Left(bind_pat) => (bind_pat.syntax(), bind_pat.name()), Either::Right(it) => (it.syntax(), it.name()), }; + let edition = self.local.parent(db).module(db).krate().edition(db); orig_range_with_focus(db, file_id, node, name).map( |(FileRange { file_id, range: full_range }, focus_range)| { - let name = local.name(db).display_no_db().to_smolstr(); + let name = local.name(db).display_no_db(edition).to_smolstr(); let kind = if local.is_self(db) { SymbolKind::SelfParam } else if local.is_param(db) { @@ -567,7 +579,8 @@ impl ToNav for hir::Local { impl TryToNav for hir::Label { fn try_to_nav(&self, db: &RootDatabase) -> Option> { let InFile { file_id, value } = self.source(db)?; - let name = self.name(db).display_no_db().to_smolstr(); + // Labels can't be keywords, so no escaping needed. + let name = self.name(db).display_no_db(Edition::Edition2015).to_smolstr(); Some(orig_range_with_focus(db, file_id, value.syntax(), value.lifetime()).map( |(FileRange { file_id, range: full_range }, focus_range)| NavigationTarget { @@ -588,7 +601,8 @@ impl TryToNav for hir::Label { impl TryToNav for hir::TypeParam { fn try_to_nav(&self, db: &RootDatabase) -> Option> { let InFile { file_id, value } = self.merge().source(db)?; - let name = self.name(db).display_no_db().to_smolstr(); + let edition = self.module(db).krate().edition(db); + let name = self.name(db).display_no_db(edition).to_smolstr(); let value = match value { Either::Left(ast::TypeOrConstParam::Type(x)) => Either::Left(x), @@ -630,7 +644,8 @@ impl TryToNav for hir::TypeOrConstParam { impl TryToNav for hir::LifetimeParam { fn try_to_nav(&self, db: &RootDatabase) -> Option> { let InFile { file_id, value } = self.source(db)?; - let name = self.name(db).display_no_db().to_smolstr(); + // Lifetimes cannot be keywords, so not escaping needed. + let name = self.name(db).display_no_db(Edition::Edition2015).to_smolstr(); Some(orig_range(db, file_id, value.syntax()).map( |(FileRange { file_id, range: full_range }, focus_range)| NavigationTarget { @@ -651,7 +666,8 @@ impl TryToNav for hir::LifetimeParam { impl TryToNav for hir::ConstParam { fn try_to_nav(&self, db: &RootDatabase) -> Option> { let InFile { file_id, value } = self.merge().source(db)?; - let name = self.name(db).display_no_db().to_smolstr(); + let edition = self.module(db).krate().edition(db); + let name = self.name(db).display_no_db(edition).to_smolstr(); let value = match value { Either::Left(ast::TypeOrConstParam::Const(x)) => x, diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs index 64b82b31c7..64d717f88d 100644 --- a/crates/ide/src/references.rs +++ b/crates/ide/src/references.rs @@ -9,7 +9,7 @@ //! at the index that the match starts at and its tree parent is //! resolved to the search element definition, we get a reference. -use hir::{DescendPreference, PathResolution, Semantics}; +use hir::{PathResolution, Semantics}; use ide_db::{ defs::{Definition, NameClass, NameRefClass}, search::{ReferenceCategory, SearchScope, UsageSearchResult}, @@ -17,6 +17,7 @@ use ide_db::{ }; use itertools::Itertools; use nohash_hasher::IntMap; +use span::Edition; use syntax::{ ast::{self, HasName}, match_ast, AstNode, @@ -148,7 +149,7 @@ pub(crate) fn find_defs<'a>( } Some( - sema.descend_into_macros(DescendPreference::SameText, token) + sema.descend_into_macros_exact(token) .into_iter() .filter_map(|it| ast::NameLike::cast(it.parent()?)) .filter_map(move |name_like| { @@ -305,7 +306,9 @@ fn handle_control_flow_keywords( FilePosition { file_id, offset }: FilePosition, ) -> Option { let file = sema.parse_guess_edition(file_id); - let token = file.syntax().token_at_offset(offset).find(|t| t.kind().is_keyword())?; + let edition = + sema.attach_first_edition(file_id).map(|it| it.edition()).unwrap_or(Edition::CURRENT); + let token = file.syntax().token_at_offset(offset).find(|t| t.kind().is_keyword(edition))?; let references = match token.kind() { T![fn] | T![return] | T![try] => highlight_related::highlight_exit_points(sema, token), @@ -2507,4 +2510,244 @@ fn main() { "#]], ) } + + // The following are tests for short_associated_function_fast_search() in crates/ide-db/src/search.rs, because find all references + // use `FindUsages` and I found it easy to test it here. + + #[test] + fn goto_ref_on_short_associated_function() { + cov_mark::check!(short_associated_function_fast_search); + check( + r#" +struct Foo; +impl Foo { + fn new$0() {} +} + +fn bar() { + Foo::new(); +} +fn baz() { + Foo::new; +} + "#, + expect![[r#" + new Function FileId(0) 27..38 30..33 + + FileId(0) 62..65 + FileId(0) 91..94 + "#]], + ); + } + + #[test] + fn goto_ref_on_short_associated_function_with_aliases() { + cov_mark::check!(short_associated_function_fast_search); + cov_mark::check!(container_use_rename); + cov_mark::check!(container_type_alias); + check( + r#" +//- /lib.rs +mod a; +mod b; + +struct Foo; +impl Foo { + fn new$0() {} +} + +fn bar() { + b::c::Baz::new(); +} + +//- /a.rs +use crate::Foo as Bar; + +fn baz() { Bar::new(); } +fn quux() { ::Assoc::new(); } + +//- /b.rs +pub(crate) mod c; + +pub(crate) struct Other; +pub(crate) trait Trait { + type Assoc; +} +impl Trait for Other { + type Assoc = super::Foo; +} + +//- /b/c.rs +type Itself = T; +pub(in super::super) type Baz = Itself; + "#, + expect![[r#" + new Function FileId(0) 42..53 45..48 + + FileId(0) 83..86 + FileId(1) 40..43 + FileId(1) 106..109 + "#]], + ); + } + + #[test] + fn goto_ref_on_short_associated_function_self_works() { + cov_mark::check!(short_associated_function_fast_search); + cov_mark::check!(self_type_alias); + check( + r#" +//- /lib.rs +mod module; + +struct Foo; +impl Foo { + fn new$0() {} + fn bar() { Self::new(); } +} +trait Trait { + type Assoc; + fn baz(); +} +impl Trait for Foo { + type Assoc = Self; + fn baz() { Self::new(); } +} + +//- /module.rs +impl super::Foo { + fn quux() { Self::new(); } +} +fn foo() { ::Assoc::new(); } + "#, + expect![[r#" + new Function FileId(0) 40..51 43..46 + + FileId(0) 73..76 + FileId(0) 195..198 + FileId(1) 40..43 + FileId(1) 99..102 + "#]], + ); + } + + #[test] + fn goto_ref_on_short_associated_function_overlapping_self_ranges() { + check( + r#" +struct Foo; +impl Foo { + fn new$0() {} + fn bar() { + Self::new(); + impl Foo { + fn baz() { Self::new(); } + } + } +} + "#, + expect![[r#" + new Function FileId(0) 27..38 30..33 + + FileId(0) 68..71 + FileId(0) 123..126 + "#]], + ); + } + + #[test] + fn goto_ref_on_short_associated_function_no_direct_self_but_path_contains_self() { + cov_mark::check!(short_associated_function_fast_search); + check( + r#" +struct Foo; +impl Foo { + fn new$0() {} +} +trait Trait { + type Assoc; +} +impl Trait for (A, B) { + type Assoc = B; +} +impl Foo { + fn bar() { + <((), Foo) as Trait>::Assoc::new(); + <((), Self) as Trait>::Assoc::new(); + } +} + "#, + expect![[r#" + new Function FileId(0) 27..38 30..33 + + FileId(0) 188..191 + FileId(0) 233..236 + "#]], + ); + } + + // Checks that we can circumvent our fast path logic using complicated type level functions. + // This mainly exists as a documentation. I don't believe it is fixable. + // Usages search is not 100% accurate anyway; we miss macros. + #[test] + fn goto_ref_on_short_associated_function_complicated_type_magic_can_confuse_our_logic() { + cov_mark::check!(short_associated_function_fast_search); + cov_mark::check!(same_name_different_def_type_alias); + check( + r#" +struct Foo; +impl Foo { + fn new$0() {} +} + +struct ChoiceA; +struct ChoiceB; +trait Choice { + type Choose; +} +impl Choice for ChoiceA { + type Choose = A; +} +impl Choice for ChoiceB { + type Choose = B; +} +type Choose = ::Choose; + +fn bar() { + Choose::<(), ChoiceB>::new(); +} + "#, + expect![[r#" + new Function FileId(0) 27..38 30..33 + + (no references) + "#]], + ); + } + + #[test] + fn goto_ref_on_short_associated_function_same_path_mention_alias_and_self() { + cov_mark::check!(short_associated_function_fast_search); + check( + r#" +struct Foo; +impl Foo { + fn new$0() {} +} + +type IgnoreFirst = B; + +impl Foo { + fn bar() { + >::new(); + } +} + "#, + expect![[r#" + new Function FileId(0) 27..38 30..33 + + FileId(0) 131..134 + "#]], + ); + } } diff --git a/crates/ide/src/rename.rs b/crates/ide/src/rename.rs index 9581474ca7..42b7472c64 100644 --- a/crates/ide/src/rename.rs +++ b/crates/ide/src/rename.rs @@ -12,12 +12,8 @@ use ide_db::{ FileId, FileRange, RootDatabase, }; use itertools::Itertools; -use span::Edition; use stdx::{always, never}; -use syntax::{ - ast, utils::is_raw_identifier, AstNode, SmolStr, SyntaxKind, SyntaxNode, TextRange, TextSize, - ToSmolStr, -}; +use syntax::{ast, AstNode, SyntaxKind, SyntaxNode, TextRange, TextSize}; use text_edit::TextEdit; @@ -102,7 +98,7 @@ pub(crate) fn rename( // FIXME: This can use the `ide_db::rename_reference` (or def.rename) method once we can // properly find "direct" usages/references. .map(|(.., def)| { - match IdentifierKind::classify(Edition::CURRENT_FIXME, new_name)? { + match IdentifierKind::classify(new_name)? { IdentifierKind::Ident => (), IdentifierKind::Lifetime => { bail!("Cannot alias reference to a lifetime identifier") @@ -124,7 +120,10 @@ pub(crate) fn rename( let mut source_change = SourceChange::default(); source_change.extend(usages.references.get_mut(&file_id).iter().map(|refs| { - (position.file_id, source_edit_from_references(refs, def, new_name)) + ( + position.file_id, + source_edit_from_references(refs, def, new_name, file_id.edition()), + ) })); Ok(source_change) @@ -162,11 +161,7 @@ pub(crate) fn will_rename_file( let sema = Semantics::new(db); let module = sema.file_to_module_def(file_id)?; let def = Definition::Module(module); - let mut change = if is_raw_identifier(new_name_stem) { - def.rename(&sema, &SmolStr::from_iter(["r#", new_name_stem])).ok()? - } else { - def.rename(&sema, new_name_stem).ok()? - }; + let mut change = def.rename(&sema, new_name_stem).ok()?; change.file_system_edits.clear(); Some(change) } @@ -270,7 +265,7 @@ fn find_definitions( // if the name differs from the definitions name it has to be an alias if def .name(sema.db) - .map_or(false, |it| it.display_no_db().to_smolstr() != name_ref.text().as_str()) + .map_or(false, |it| !it.eq_ident(name_ref.text().as_str())) { Err(format_err!("Renaming aliases is currently unsupported")) } else { @@ -377,7 +372,7 @@ fn rename_to_self( let usages = def.usages(sema).all(); let mut source_change = SourceChange::default(); source_change.extend(usages.iter().map(|(file_id, references)| { - (file_id.into(), source_edit_from_references(references, def, "self")) + (file_id.into(), source_edit_from_references(references, def, "self", file_id.edition())) })); source_change.insert_source_edit( file_id.original_file(sema.db), @@ -398,7 +393,7 @@ fn rename_self_to_param( return Ok(SourceChange::default()); } - let identifier_kind = IdentifierKind::classify(Edition::CURRENT_FIXME, new_name)?; + let identifier_kind = IdentifierKind::classify(new_name)?; let InFile { file_id, value: self_param } = sema.source(self_param).ok_or_else(|| format_err!("cannot find function source"))?; @@ -413,7 +408,7 @@ fn rename_self_to_param( let mut source_change = SourceChange::default(); source_change.insert_source_edit(file_id.original_file(sema.db), edit); source_change.extend(usages.iter().map(|(file_id, references)| { - (file_id.into(), source_edit_from_references(references, def, new_name)) + (file_id.into(), source_edit_from_references(references, def, new_name, file_id.edition())) })); Ok(source_change) } @@ -634,9 +629,9 @@ impl Foo { #[test] fn test_rename_to_invalid_identifier3() { check( - "let", + "super", r#"fn main() { let i$0 = 1; }"#, - "error: Invalid name `let`: not an identifier", + "error: Invalid name `super`: not an identifier", ); } @@ -685,11 +680,7 @@ impl Foo { #[test] fn test_rename_mod_invalid_raw_ident() { - check( - "r#self", - r#"mod foo$0 {}"#, - "error: Invalid name: `self` cannot be a raw identifier", - ); + check("r#self", r#"mod foo$0 {}"#, "error: Invalid name `self`: not an identifier"); } #[test] @@ -1543,6 +1534,228 @@ pub fn baz() {} ); } + #[test] + fn test_rename_each_usage_gets_appropriate_rawness() { + check_expect( + "dyn", + r#" +//- /a.rs crate:a edition:2015 +pub fn foo() {} + +//- /b.rs crate:b edition:2018 deps:a new_source_root:local +fn bar() { + a::foo$0(); +} + "#, + expect![[r#" + source_file_edits: [ + ( + FileId( + 0, + ), + [ + Indel { + insert: "dyn", + delete: 7..10, + }, + ], + ), + ( + FileId( + 1, + ), + [ + Indel { + insert: "r#dyn", + delete: 18..21, + }, + ], + ), + ] + file_system_edits: [] + "#]], + ); + + check_expect( + "dyn", + r#" +//- /a.rs crate:a edition:2018 +pub fn foo() {} + +//- /b.rs crate:b edition:2015 deps:a new_source_root:local +fn bar() { + a::foo$0(); +} + "#, + expect![[r#" + source_file_edits: [ + ( + FileId( + 0, + ), + [ + Indel { + insert: "r#dyn", + delete: 7..10, + }, + ], + ), + ( + FileId( + 1, + ), + [ + Indel { + insert: "dyn", + delete: 18..21, + }, + ], + ), + ] + file_system_edits: [] + "#]], + ); + + check_expect( + "r#dyn", + r#" +//- /a.rs crate:a edition:2018 +pub fn foo$0() {} + +//- /b.rs crate:b edition:2015 deps:a new_source_root:local +fn bar() { + a::foo(); +} + "#, + expect![[r#" + source_file_edits: [ + ( + FileId( + 0, + ), + [ + Indel { + insert: "r#dyn", + delete: 7..10, + }, + ], + ), + ( + FileId( + 1, + ), + [ + Indel { + insert: "dyn", + delete: 18..21, + }, + ], + ), + ] + file_system_edits: [] + "#]], + ); + } + + #[test] + fn rename_raw_identifier() { + check_expect( + "abc", + r#" +//- /a.rs crate:a edition:2015 +pub fn dyn() {} + +fn foo() { + dyn$0(); +} + +//- /b.rs crate:b edition:2018 deps:a new_source_root:local +fn bar() { + a::r#dyn(); +} + "#, + expect![[r#" + source_file_edits: [ + ( + FileId( + 0, + ), + [ + Indel { + insert: "abc", + delete: 7..10, + }, + Indel { + insert: "abc", + delete: 32..35, + }, + ], + ), + ( + FileId( + 1, + ), + [ + Indel { + insert: "abc", + delete: 18..23, + }, + ], + ), + ] + file_system_edits: [] + "#]], + ); + + check_expect( + "abc", + r#" +//- /a.rs crate:a edition:2018 +pub fn r#dyn() {} + +fn foo() { + r#dyn$0(); +} + +//- /b.rs crate:b edition:2015 deps:a new_source_root:local +fn bar() { + a::dyn(); +} + "#, + expect![[r#" + source_file_edits: [ + ( + FileId( + 0, + ), + [ + Indel { + insert: "abc", + delete: 7..12, + }, + Indel { + insert: "abc", + delete: 34..39, + }, + ], + ), + ( + FileId( + 1, + ), + [ + Indel { + insert: "abc", + delete: 18..21, + }, + ], + ), + ] + file_system_edits: [] + "#]], + ); + } + #[test] fn test_enum_variant_from_module_1() { cov_mark::check!(rename_non_local); diff --git a/crates/ide/src/runnables.rs b/crates/ide/src/runnables.rs index 5d4b8b3643..38dc522789 100644 --- a/crates/ide/src/runnables.rs +++ b/crates/ide/src/runnables.rs @@ -3,7 +3,8 @@ use std::fmt; use ast::HasName; use cfg::{CfgAtom, CfgExpr}; use hir::{ - db::HirDatabase, sym, AsAssocItem, AttrsWithOwner, HasAttrs, HasSource, HirFileIdExt, Semantics, + db::HirDatabase, sym, AsAssocItem, AttrsWithOwner, HasAttrs, HasCrate, HasSource, HirFileIdExt, + Semantics, }; use ide_assists::utils::{has_test_related_attribute, test_related_attribute_syn}; use ide_db::{ @@ -14,7 +15,7 @@ use ide_db::{ FilePosition, FxHashMap, FxHashSet, RootDatabase, SymbolKind, }; use itertools::Itertools; -use span::TextSize; +use span::{Edition, TextSize}; use stdx::{always, format_to}; use syntax::{ ast::{self, AstNode}, @@ -321,6 +322,7 @@ pub(crate) fn runnable_fn( sema: &Semantics<'_, RootDatabase>, def: hir::Function, ) -> Option { + let edition = def.krate(sema.db).edition(sema.db); let under_cfg_test = has_cfg_test(def.module(sema.db).attrs(sema.db)); let kind = if !under_cfg_test && def.is_main(sema.db) { RunnableKind::Bin @@ -328,11 +330,11 @@ pub(crate) fn runnable_fn( let test_id = || { let canonical_path = { let def: hir::ModuleDef = def.into(); - def.canonical_path(sema.db) + def.canonical_path(sema.db, edition) }; canonical_path .map(TestId::Path) - .unwrap_or(TestId::Name(def.name(sema.db).display_no_db().to_smolstr())) + .unwrap_or(TestId::Name(def.name(sema.db).display_no_db(edition).to_smolstr())) }; if def.is_test(sema.db) { @@ -367,8 +369,11 @@ pub(crate) fn runnable_mod( .path_to_root(sema.db) .into_iter() .rev() - .filter_map(|it| it.name(sema.db)) - .map(|it| it.display(sema.db).to_string()) + .filter_map(|module| { + module.name(sema.db).map(|mod_name| { + mod_name.display(sema.db, module.krate().edition(sema.db)).to_string() + }) + }) .join("::"); let attrs = def.attrs(sema.db); @@ -381,6 +386,7 @@ pub(crate) fn runnable_impl( sema: &Semantics<'_, RootDatabase>, def: &hir::Impl, ) -> Option { + let edition = def.module(sema.db).krate().edition(sema.db); let attrs = def.attrs(sema.db); if !has_runnable_doc_test(&attrs) { return None; @@ -389,13 +395,13 @@ pub(crate) fn runnable_impl( let nav = def.try_to_nav(sema.db)?.call_site(); let ty = def.self_ty(sema.db); let adt_name = ty.as_adt()?.name(sema.db); - let mut ty_args = ty.generic_parameters(sema.db).peekable(); + let mut ty_args = ty.generic_parameters(sema.db, edition).peekable(); let params = if ty_args.peek().is_some() { format!("<{}>", ty_args.format_with(",", |ty, cb| cb(&ty))) } else { String::new() }; - let mut test_id = format!("{}{params}", adt_name.display(sema.db)); + let mut test_id = format!("{}{params}", adt_name.display(sema.db, edition)); test_id.retain(|c| c != ' '); let test_id = TestId::Path(test_id); @@ -419,8 +425,11 @@ fn runnable_mod_outline_definition( .path_to_root(sema.db) .into_iter() .rev() - .filter_map(|it| it.name(sema.db)) - .map(|it| it.display(sema.db).to_string()) + .filter_map(|module| { + module.name(sema.db).map(|mod_name| { + mod_name.display(sema.db, module.krate().edition(sema.db)).to_string() + }) + }) .join("::"); let attrs = def.attrs(sema.db); @@ -452,6 +461,7 @@ fn module_def_doctest(db: &RootDatabase, def: Definition) -> Option { Definition::SelfType(it) => it.attrs(db), _ => return None, }; + let edition = def.krate(db).map(|it| it.edition(db)).unwrap_or(Edition::CURRENT); if !has_runnable_doc_test(&attrs) { return None; } @@ -460,29 +470,29 @@ fn module_def_doctest(db: &RootDatabase, def: Definition) -> Option { let mut path = String::new(); def.canonical_module_path(db)? .flat_map(|it| it.name(db)) - .for_each(|name| format_to!(path, "{}::", name.display(db))); + .for_each(|name| format_to!(path, "{}::", name.display(db, edition))); // This probably belongs to canonical_path? if let Some(assoc_item) = def.as_assoc_item(db) { if let Some(ty) = assoc_item.implementing_ty(db) { if let Some(adt) = ty.as_adt() { let name = adt.name(db); - let mut ty_args = ty.generic_parameters(db).peekable(); - format_to!(path, "{}", name.display(db)); + let mut ty_args = ty.generic_parameters(db, edition).peekable(); + format_to!(path, "{}", name.display(db, edition)); if ty_args.peek().is_some() { format_to!(path, "<{}>", ty_args.format_with(",", |ty, cb| cb(&ty))); } - format_to!(path, "::{}", def_name.display(db)); + format_to!(path, "::{}", def_name.display(db, edition)); path.retain(|c| c != ' '); return Some(path); } } } - format_to!(path, "{}", def_name.display(db)); + format_to!(path, "{}", def_name.display(db, edition)); Some(path) })(); - let test_id = - path.map_or_else(|| TestId::Name(def_name.display_no_db().to_smolstr()), TestId::Path); + let test_id = path + .map_or_else(|| TestId::Name(def_name.display_no_db(edition).to_smolstr()), TestId::Path); let mut nav = match def { Definition::Module(def) => NavigationTarget::from_module_to_decl(db, def), diff --git a/crates/ide/src/signature_help.rs b/crates/ide/src/signature_help.rs index b6c9e2f636..516f64959c 100644 --- a/crates/ide/src/signature_help.rs +++ b/crates/ide/src/signature_help.rs @@ -4,15 +4,13 @@ use std::collections::BTreeSet; use either::Either; -use hir::{ - AssocItem, DescendPreference, GenericParam, HirDisplay, ModuleDef, PathResolution, Semantics, - Trait, -}; +use hir::{AssocItem, GenericParam, HirDisplay, ModuleDef, PathResolution, Semantics, Trait}; use ide_db::{ active_parameter::{callable_for_node, generic_def_for_node}, documentation::{Documentation, HasDocs}, FilePosition, FxIndexMap, }; +use span::Edition; use stdx::format_to; use syntax::{ algo, @@ -81,7 +79,9 @@ pub(crate) fn signature_help( // if the cursor is sandwiched between two space tokens and the call is unclosed // this prevents us from leaving the CallExpression .and_then(|tok| algo::skip_trivia_token(tok, Direction::Prev))?; - let token = sema.descend_into_macros_single(DescendPreference::None, token); + let token = sema.descend_into_macros_single_exact(token); + let edition = + sema.attach_first_edition(file_id).map(|it| it.edition()).unwrap_or(Edition::CURRENT); for node in token.parent_ancestors() { match_ast! { @@ -91,49 +91,49 @@ pub(crate) fn signature_help( if cursor_outside { continue; } - return signature_help_for_call(&sema, arg_list, token); + return signature_help_for_call(&sema, arg_list, token, edition); }, ast::GenericArgList(garg_list) => { let cursor_outside = garg_list.r_angle_token().as_ref() == Some(&token); if cursor_outside { continue; } - return signature_help_for_generics(&sema, garg_list, token); + return signature_help_for_generics(&sema, garg_list, token, edition); }, ast::RecordExpr(record) => { let cursor_outside = record.record_expr_field_list().and_then(|list| list.r_curly_token()).as_ref() == Some(&token); if cursor_outside { continue; } - return signature_help_for_record_lit(&sema, record, token); + return signature_help_for_record_lit(&sema, record, token, edition); }, ast::RecordPat(record) => { let cursor_outside = record.record_pat_field_list().and_then(|list| list.r_curly_token()).as_ref() == Some(&token); if cursor_outside { continue; } - return signature_help_for_record_pat(&sema, record, token); + return signature_help_for_record_pat(&sema, record, token, edition); }, ast::TupleStructPat(tuple_pat) => { let cursor_outside = tuple_pat.r_paren_token().as_ref() == Some(&token); if cursor_outside { continue; } - return signature_help_for_tuple_struct_pat(&sema, tuple_pat, token); + return signature_help_for_tuple_struct_pat(&sema, tuple_pat, token, edition); }, ast::TuplePat(tuple_pat) => { let cursor_outside = tuple_pat.r_paren_token().as_ref() == Some(&token); if cursor_outside { continue; } - return signature_help_for_tuple_pat(&sema, tuple_pat, token); + return signature_help_for_tuple_pat(&sema, tuple_pat, token, edition); }, ast::TupleExpr(tuple_expr) => { let cursor_outside = tuple_expr.r_paren_token().as_ref() == Some(&token); if cursor_outside { continue; } - return signature_help_for_tuple_expr(&sema, tuple_expr, token); + return signature_help_for_tuple_expr(&sema, tuple_expr, token, edition); }, _ => (), } @@ -157,6 +157,7 @@ fn signature_help_for_call( sema: &Semantics<'_, RootDatabase>, arg_list: ast::ArgList, token: SyntaxToken, + edition: Edition, ) -> Option { // Find the calling expression and its NameRef let mut nodes = arg_list.syntax().ancestors().skip(1); @@ -181,7 +182,7 @@ fn signature_help_for_call( match callable.kind() { hir::CallableKind::Function(func) => { res.doc = func.docs(db); - format_to!(res.signature, "fn {}", func.name(db).display(db)); + format_to!(res.signature, "fn {}", func.name(db).display(db, edition)); fn_params = Some(match callable.receiver_param(db) { Some(_self) => func.params_without_self(db), None => func.assoc_fn_params(db), @@ -189,15 +190,15 @@ fn signature_help_for_call( } hir::CallableKind::TupleStruct(strukt) => { res.doc = strukt.docs(db); - format_to!(res.signature, "struct {}", strukt.name(db).display(db)); + format_to!(res.signature, "struct {}", strukt.name(db).display(db, edition)); } hir::CallableKind::TupleEnumVariant(variant) => { res.doc = variant.docs(db); format_to!( res.signature, "enum {}::{}", - variant.parent_enum(db).name(db).display(db), - variant.name(db).display(db) + variant.parent_enum(db).name(db).display(db, edition), + variant.name(db).display(db, edition) ); } hir::CallableKind::Closure(closure) => { @@ -210,7 +211,7 @@ fn signature_help_for_call( Some(adt) => format_to!( res.signature, "<{} as {fn_trait}>::{}", - adt.name(db).display(db), + adt.name(db).display(db, edition), fn_trait.function_name() ), None => format_to!(res.signature, "impl {fn_trait}"), @@ -220,7 +221,7 @@ fn signature_help_for_call( res.signature.push('('); { if let Some((self_param, _)) = callable.receiver_param(db) { - format_to!(res.signature, "{}", self_param.display(db)) + format_to!(res.signature, "{}", self_param.display(db, edition)) } let mut buf = String::new(); for (idx, p) in callable.params().into_iter().enumerate() { @@ -240,8 +241,10 @@ fn signature_help_for_call( // This is overly conservative: we do not substitute known type vars // (see FIXME in tests::impl_trait) and falling back on any unknowns. match (p.ty().contains_unknown(), fn_params.as_deref()) { - (true, Some(fn_params)) => format_to!(buf, "{}", fn_params[idx].ty().display(db)), - _ => format_to!(buf, "{}", p.ty().display(db)), + (true, Some(fn_params)) => { + format_to!(buf, "{}", fn_params[idx].ty().display(db, edition)) + } + _ => format_to!(buf, "{}", p.ty().display(db, edition)), } res.push_call_param(&buf); } @@ -250,7 +253,7 @@ fn signature_help_for_call( let mut render = |ret_type: hir::Type| { if !ret_type.is_unit() { - format_to!(res.signature, " -> {}", ret_type.display(db)); + format_to!(res.signature, " -> {}", ret_type.display(db, edition)); } }; match callable.kind() { @@ -270,6 +273,7 @@ fn signature_help_for_generics( sema: &Semantics<'_, RootDatabase>, arg_list: ast::GenericArgList, token: SyntaxToken, + edition: Edition, ) -> Option { let (generics_def, mut active_parameter, first_arg_is_non_lifetime, variant) = generic_def_for_node(sema, &arg_list, &token)?; @@ -284,11 +288,11 @@ fn signature_help_for_generics( match generics_def { hir::GenericDef::Function(it) => { res.doc = it.docs(db); - format_to!(res.signature, "fn {}", it.name(db).display(db)); + format_to!(res.signature, "fn {}", it.name(db).display(db, edition)); } hir::GenericDef::Adt(hir::Adt::Enum(it)) => { res.doc = it.docs(db); - format_to!(res.signature, "enum {}", it.name(db).display(db)); + format_to!(res.signature, "enum {}", it.name(db).display(db, edition)); if let Some(variant) = variant { // In paths, generics of an enum can be specified *after* one of its variants. // eg. `None::` @@ -298,23 +302,23 @@ fn signature_help_for_generics( } hir::GenericDef::Adt(hir::Adt::Struct(it)) => { res.doc = it.docs(db); - format_to!(res.signature, "struct {}", it.name(db).display(db)); + format_to!(res.signature, "struct {}", it.name(db).display(db, edition)); } hir::GenericDef::Adt(hir::Adt::Union(it)) => { res.doc = it.docs(db); - format_to!(res.signature, "union {}", it.name(db).display(db)); + format_to!(res.signature, "union {}", it.name(db).display(db, edition)); } hir::GenericDef::Trait(it) => { res.doc = it.docs(db); - format_to!(res.signature, "trait {}", it.name(db).display(db)); + format_to!(res.signature, "trait {}", it.name(db).display(db, edition)); } hir::GenericDef::TraitAlias(it) => { res.doc = it.docs(db); - format_to!(res.signature, "trait {}", it.name(db).display(db)); + format_to!(res.signature, "trait {}", it.name(db).display(db, edition)); } hir::GenericDef::TypeAlias(it) => { res.doc = it.docs(db); - format_to!(res.signature, "type {}", it.name(db).display(db)); + format_to!(res.signature, "type {}", it.name(db).display(db, edition)); } // These don't have generic args that can be specified hir::GenericDef::Impl(_) | hir::GenericDef::Const(_) => return None, @@ -339,11 +343,11 @@ fn signature_help_for_generics( } buf.clear(); - format_to!(buf, "{}", param.display(db)); + format_to!(buf, "{}", param.display(db, edition)); res.push_generic_param(&buf); } if let hir::GenericDef::Trait(tr) = generics_def { - add_assoc_type_bindings(db, &mut res, tr, arg_list); + add_assoc_type_bindings(db, &mut res, tr, arg_list, edition); } res.signature.push('>'); @@ -355,6 +359,7 @@ fn add_assoc_type_bindings( res: &mut SignatureHelp, tr: Trait, args: ast::GenericArgList, + edition: Edition, ) { if args.syntax().ancestors().find_map(ast::TypeBound::cast).is_none() { // Assoc type bindings are only valid in type bound position. @@ -378,7 +383,7 @@ fn add_assoc_type_bindings( for item in tr.items_with_supertraits(db) { if let AssocItem::TypeAlias(ty) = item { - let name = ty.name(db).display_no_db().to_smolstr(); + let name = ty.name(db).display_no_db(edition).to_smolstr(); if !present_bindings.contains(&*name) { buf.clear(); format_to!(buf, "{} = …", name); @@ -392,6 +397,7 @@ fn signature_help_for_record_lit( sema: &Semantics<'_, RootDatabase>, record: ast::RecordExpr, token: SyntaxToken, + edition: Edition, ) -> Option { signature_help_for_record_( sema, @@ -403,6 +409,7 @@ fn signature_help_for_record_lit( .filter_map(|field| sema.resolve_record_field(&field)) .map(|(field, _, ty)| (field, ty)), token, + edition, ) } @@ -410,6 +417,7 @@ fn signature_help_for_record_pat( sema: &Semantics<'_, RootDatabase>, record: ast::RecordPat, token: SyntaxToken, + edition: Edition, ) -> Option { signature_help_for_record_( sema, @@ -420,6 +428,7 @@ fn signature_help_for_record_pat( .fields() .filter_map(|field| sema.resolve_record_pat_field(&field)), token, + edition, ) } @@ -427,6 +436,7 @@ fn signature_help_for_tuple_struct_pat( sema: &Semantics<'_, RootDatabase>, pat: ast::TupleStructPat, token: SyntaxToken, + edition: Edition, ) -> Option { let path = pat.path()?; let path_res = sema.resolve_path(&path)?; @@ -445,8 +455,8 @@ fn signature_help_for_tuple_struct_pat( format_to!( res.signature, "enum {}::{} (", - en.name(db).display(db), - variant.name(db).display(db) + en.name(db).display(db, edition), + variant.name(db).display(db, edition) ); variant.fields(db) } else { @@ -459,7 +469,7 @@ fn signature_help_for_tuple_struct_pat( match adt { hir::Adt::Struct(it) => { res.doc = it.docs(db); - format_to!(res.signature, "struct {} (", it.name(db).display(db)); + format_to!(res.signature, "struct {} (", it.name(db).display(db, edition)); it.fields(db) } _ => return None, @@ -472,6 +482,7 @@ fn signature_help_for_tuple_struct_pat( token, pat.fields(), fields.into_iter().map(|it| it.ty(db)), + edition, )) } @@ -479,6 +490,7 @@ fn signature_help_for_tuple_pat( sema: &Semantics<'_, RootDatabase>, pat: ast::TuplePat, token: SyntaxToken, + edition: Edition, ) -> Option { let db = sema.db; let field_pats = pat.fields(); @@ -498,6 +510,7 @@ fn signature_help_for_tuple_pat( token, field_pats, fields.into_iter(), + edition, )) } @@ -505,6 +518,7 @@ fn signature_help_for_tuple_expr( sema: &Semantics<'_, RootDatabase>, expr: ast::TupleExpr, token: SyntaxToken, + edition: Edition, ) -> Option { let active_parameter = Some( expr.syntax() @@ -526,7 +540,7 @@ fn signature_help_for_tuple_expr( let fields = expr.original.tuple_fields(db); let mut buf = String::new(); for ty in fields { - format_to!(buf, "{}", ty.display_truncated(db, Some(20))); + format_to!(buf, "{}", ty.display_truncated(db, Some(20), edition)); res.push_call_param(&buf); buf.clear(); } @@ -540,6 +554,7 @@ fn signature_help_for_record_( path: &ast::Path, fields2: impl Iterator, token: SyntaxToken, + edition: Edition, ) -> Option { let active_parameter = field_list_children .filter_map(NodeOrToken::into_token) @@ -566,8 +581,8 @@ fn signature_help_for_record_( format_to!( res.signature, "enum {}::{} {{ ", - en.name(db).display(db), - variant.name(db).display(db) + en.name(db).display(db, edition), + variant.name(db).display(db, edition) ); } else { let adt = match path_res { @@ -580,12 +595,12 @@ fn signature_help_for_record_( hir::Adt::Struct(it) => { fields = it.fields(db); res.doc = it.docs(db); - format_to!(res.signature, "struct {} {{ ", it.name(db).display(db)); + format_to!(res.signature, "struct {} {{ ", it.name(db).display(db, edition)); } hir::Adt::Union(it) => { fields = it.fields(db); res.doc = it.docs(db); - format_to!(res.signature, "union {} {{ ", it.name(db).display(db)); + format_to!(res.signature, "union {} {{ ", it.name(db).display(db, edition)); } _ => return None, } @@ -596,7 +611,12 @@ fn signature_help_for_record_( let mut buf = String::new(); for (field, ty) in fields2 { let name = field.name(db); - format_to!(buf, "{}: {}", name.display(db), ty.display_truncated(db, Some(20))); + format_to!( + buf, + "{}: {}", + name.display(db, edition), + ty.display_truncated(db, Some(20), edition) + ); res.push_record_field(&buf); buf.clear(); @@ -606,7 +626,12 @@ fn signature_help_for_record_( } for (name, field) in fields { let Some(field) = field else { continue }; - format_to!(buf, "{}: {}", name.display(db), field.ty(db).display_truncated(db, Some(20))); + format_to!( + buf, + "{}: {}", + name.display(db, edition), + field.ty(db).display_truncated(db, Some(20), edition) + ); res.push_record_field(&buf); buf.clear(); } @@ -621,6 +646,7 @@ fn signature_help_for_tuple_pat_ish( token: SyntaxToken, mut field_pats: AstChildren, fields: impl ExactSizeIterator, + edition: Edition, ) -> SignatureHelp { let rest_pat = field_pats.find(|it| matches!(it, ast::Pat::RestPat(_))); let is_left_of_rest_pat = @@ -647,7 +673,7 @@ fn signature_help_for_tuple_pat_ish( let mut buf = String::new(); for ty in fields { - format_to!(buf, "{}", ty.display_truncated(db, Some(20))); + format_to!(buf, "{}", ty.display_truncated(db, Some(20), edition)); res.push_call_param(&buf); buf.clear(); } diff --git a/crates/ide/src/static_index.rs b/crates/ide/src/static_index.rs index eaccee08e8..1cbe8c62a8 100644 --- a/crates/ide/src/static_index.rs +++ b/crates/ide/src/static_index.rs @@ -10,6 +10,7 @@ use ide_db::{ helpers::get_definition, FileId, FileRange, FxHashMap, FxHashSet, RootDatabase, }; +use span::Edition; use syntax::{AstNode, SyntaxKind::*, SyntaxNode, TextRange, T}; use crate::inlay_hints::InlayFieldsToResolve; @@ -116,7 +117,16 @@ fn documentation_for_definition( _ => None, }; - def.docs(sema.db, famous_defs.as_ref()) + def.docs( + sema.db, + famous_defs.as_ref(), + def.krate(sema.db).map(|it| it.edition(sema.db)).unwrap_or(Edition::CURRENT), + ) +} + +pub enum VendoredLibrariesConfig<'a> { + Included { workspace_root: &'a VfsPath }, + Excluded, } impl StaticIndex<'_> { @@ -161,6 +171,8 @@ impl StaticIndex<'_> { // hovers let sema = hir::Semantics::new(self.db); let tokens_or_nodes = sema.parse_guess_edition(file_id).syntax().clone(); + let edition = + sema.attach_first_edition(file_id).map(|it| it.edition()).unwrap_or(Edition::CURRENT); let tokens = tokens_or_nodes.descendants_with_tokens().filter_map(|it| match it { syntax::NodeOrToken::Node(_) => None, syntax::NodeOrToken::Token(it) => Some(it), @@ -201,17 +213,20 @@ impl StaticIndex<'_> { &node, None, &hover_config, + edition, )), definition: def.try_to_nav(self.db).map(UpmappingResult::call_site).map(|it| { FileRange { file_id: it.file_id, range: it.focus_or_full_range() } }), references: vec![], moniker: current_crate.and_then(|cc| def_to_moniker(self.db, def, cc)), - display_name: def.name(self.db).map(|name| name.display(self.db).to_string()), + display_name: def + .name(self.db) + .map(|name| name.display(self.db, edition).to_string()), enclosing_moniker: current_crate .zip(def.enclosing_definition(self.db)) .and_then(|(cc, enclosing_def)| def_to_moniker(self.db, enclosing_def, cc)), - signature: Some(def.label(self.db)), + signature: Some(def.label(self.db, edition)), kind: def_to_kind(self.db, def), }); self.def_map.insert(def, it); @@ -230,15 +245,22 @@ impl StaticIndex<'_> { self.files.push(result); } - pub fn compute<'a>(analysis: &'a Analysis, workspace_root: &VfsPath) -> StaticIndex<'a> { + pub fn compute<'a>( + analysis: &'a Analysis, + vendored_libs_config: VendoredLibrariesConfig<'_>, + ) -> StaticIndex<'a> { let db = &*analysis.db; let work = all_modules(db).into_iter().filter(|module| { let file_id = module.definition_source_file_id(db).original_file(db); let source_root = db.file_source_root(file_id.into()); let source_root = db.source_root(source_root); - let is_vendored = source_root - .path_for_file(&file_id.into()) - .is_some_and(|module_path| module_path.starts_with(workspace_root)); + let is_vendored = match vendored_libs_config { + VendoredLibrariesConfig::Included { workspace_root } => source_root + .path_for_file(&file_id.into()) + .is_some_and(|module_path| module_path.starts_with(workspace_root)), + VendoredLibrariesConfig::Excluded => false, + }; + !source_root.is_library || is_vendored }); let mut this = StaticIndex { @@ -268,10 +290,11 @@ mod tests { use ide_db::{base_db::VfsPath, FileRange, FxHashSet}; use syntax::TextSize; - fn check_all_ranges(ra_fixture: &str) { + use super::VendoredLibrariesConfig; + + fn check_all_ranges(ra_fixture: &str, vendored_libs_config: VendoredLibrariesConfig<'_>) { let (analysis, ranges) = fixture::annotations_without_marker(ra_fixture); - let s = - StaticIndex::compute(&analysis, &VfsPath::new_virtual_path("/workspace".to_owned())); + let s = StaticIndex::compute(&analysis, vendored_libs_config); let mut range_set: FxHashSet<_> = ranges.iter().map(|it| it.0).collect(); for f in s.files { for (range, _) in f.tokens { @@ -288,10 +311,9 @@ mod tests { } #[track_caller] - fn check_definitions(ra_fixture: &str) { + fn check_definitions(ra_fixture: &str, vendored_libs_config: VendoredLibrariesConfig<'_>) { let (analysis, ranges) = fixture::annotations_without_marker(ra_fixture); - let s = - StaticIndex::compute(&analysis, &VfsPath::new_virtual_path("/workspace".to_owned())); + let s = StaticIndex::compute(&analysis, vendored_libs_config); let mut range_set: FxHashSet<_> = ranges.iter().map(|it| it.0).collect(); for (_, t) in s.tokens.iter() { if let Some(t) = t.definition { @@ -319,6 +341,9 @@ struct Foo; enum E { X(Foo) } //^ ^ ^^^ "#, + VendoredLibrariesConfig::Included { + workspace_root: &VfsPath::new_virtual_path("/workspace".to_owned()), + }, ); check_definitions( r#" @@ -327,6 +352,9 @@ struct Foo; enum E { X(Foo) } //^ ^ "#, + VendoredLibrariesConfig::Included { + workspace_root: &VfsPath::new_virtual_path("/workspace".to_owned()), + }, ); } @@ -349,6 +377,9 @@ pub func() { } "#, + VendoredLibrariesConfig::Included { + workspace_root: &VfsPath::new_virtual_path("/workspace".to_owned()), + }, ); } @@ -367,9 +398,30 @@ struct ExternalLibrary(i32); struct VendoredLibrary(i32); //^^^^^^^^^^^^^^^ ^^^ "#, + VendoredLibrariesConfig::Included { + workspace_root: &VfsPath::new_virtual_path("/workspace".to_owned()), + }, ); } + #[test] + fn vendored_crate_excluded() { + check_all_ranges( + r#" +//- /workspace/main.rs crate:main deps:external,vendored +struct Main(i32); + //^^^^ ^^^ + +//- /external/lib.rs new_source_root:library crate:external@0.1.0,https://a.b/foo.git library +struct ExternalLibrary(i32); + +//- /workspace/vendored/lib.rs new_source_root:library crate:vendored@0.1.0,https://a.b/bar.git library +struct VendoredLibrary(i32); +"#, + VendoredLibrariesConfig::Excluded, + ) + } + #[test] fn derives() { check_all_ranges( @@ -384,6 +436,9 @@ pub macro Copy {} struct Hello(i32); //^^^^^ ^^^ "#, + VendoredLibrariesConfig::Included { + workspace_root: &VfsPath::new_virtual_path("/workspace".to_owned()), + }, ); } } diff --git a/crates/ide/src/syntax_highlighting.rs b/crates/ide/src/syntax_highlighting.rs index 2318592005..927fdaa178 100644 --- a/crates/ide/src/syntax_highlighting.rs +++ b/crates/ide/src/syntax_highlighting.rs @@ -13,7 +13,9 @@ mod html; #[cfg(test)] mod tests; -use hir::{DescendPreference, Name, Semantics}; +use std::ops::ControlFlow; + +use hir::{InRealFile, Name, Semantics}; use ide_db::{FxHashMap, RootDatabase, SymbolKind}; use span::EditionedFileId; use syntax::{ @@ -399,19 +401,55 @@ fn traverse( // Attempt to descend tokens into macro-calls. let res = match element { NodeOrToken::Token(token) if token.kind() != COMMENT => { - let token = if token.kind() == STRING { - // for strings, try to prefer a string that has not been lost in a token - // tree - // FIXME: This should be done for everything, but check perf first - sema.descend_into_macros(DescendPreference::SameKind, token) - .into_iter() - .max_by_key(|it| { - it.parent().map_or(false, |it| it.kind() != TOKEN_TREE) - }) - .unwrap() - } else { - sema.descend_into_macros_single(DescendPreference::SameKind, token) - }; + let kind = token.kind(); + let text = token.text(); + let ident_kind = kind.is_any_identifier(); + + let mut t = None; + let mut r = 0; + sema.descend_into_macros_breakable( + InRealFile::new(file_id, token.clone()), + |tok| { + let tok = tok.value; + let tok_kind = tok.kind(); + + let exact_same_kind = tok_kind == kind; + let both_idents = + exact_same_kind || (tok_kind.is_any_identifier() && ident_kind); + let same_text = tok.text() == text; + // anything that mapped into a token tree has likely no semantic information + let no_tt_parent = + tok.parent().map_or(false, |it| it.kind() != TOKEN_TREE); + let my_rank = (both_idents as usize) + | ((exact_same_kind as usize) << 1) + | ((same_text as usize) << 2) + | ((no_tt_parent as usize) << 3); + + if my_rank > 0b1110 { + // a rank of 0b1110 means that we have found a maximally interesting + // token so stop early. + t = Some(tok); + return ControlFlow::Break(()); + } + + // r = r.max(my_rank); + // t = Some(t.take_if(|_| r < my_rank).unwrap_or(tok)); + match &mut t { + Some(prev) if r < my_rank => { + *prev = tok; + r = my_rank; + } + Some(_) => (), + None => { + r = my_rank; + t = Some(tok) + } + } + ControlFlow::Continue(()) + }, + ); + + let token = t.unwrap_or(token); match token.parent().and_then(ast::NameLike::cast) { // Remap the token into the wrapping single token nodes Some(parent) => match (token.kind(), parent.syntax().kind()) { @@ -501,7 +539,9 @@ fn traverse( config.syntactic_name_ref_highlighting, name_like, ), - NodeOrToken::Token(token) => highlight::token(sema, token).zip(Some(None)), + NodeOrToken::Token(token) => { + highlight::token(sema, token, file_id.edition()).zip(Some(None)) + } }; if let Some((mut highlight, binding_hash)) = element { if is_unlinked && highlight.tag == HlTag::UnresolvedReference { diff --git a/crates/ide/src/syntax_highlighting/highlight.rs b/crates/ide/src/syntax_highlighting/highlight.rs index 291073f877..eeba9cf35c 100644 --- a/crates/ide/src/syntax_highlighting/highlight.rs +++ b/crates/ide/src/syntax_highlighting/highlight.rs @@ -6,6 +6,7 @@ use ide_db::{ defs::{Definition, IdentClass, NameClass, NameRefClass}, FxHashMap, RootDatabase, SymbolKind, }; +use span::Edition; use stdx::hash_once; use syntax::{ ast, match_ast, AstNode, AstToken, NodeOrToken, @@ -18,7 +19,11 @@ use crate::{ Highlight, HlMod, HlTag, }; -pub(super) fn token(sema: &Semantics<'_, RootDatabase>, token: SyntaxToken) -> Option { +pub(super) fn token( + sema: &Semantics<'_, RootDatabase>, + token: SyntaxToken, + edition: Edition, +) -> Option { if let Some(comment) = ast::Comment::cast(token.clone()) { let h = HlTag::Comment; return Some(match comment.kind().doc { @@ -41,7 +46,7 @@ pub(super) fn token(sema: &Semantics<'_, RootDatabase>, token: SyntaxToken) -> O HlTag::None.into() } p if p.is_punct() => punctuation(sema, token, p), - k if k.is_keyword() => keyword(sema, token, k)?, + k if k.is_keyword(edition) => keyword(sema, token, k)?, _ => return None, }; Some(highlight) diff --git a/crates/ide/src/syntax_highlighting/macro_.rs b/crates/ide/src/syntax_highlighting/macro_.rs index 1099d9c23b..b441b4cc90 100644 --- a/crates/ide/src/syntax_highlighting/macro_.rs +++ b/crates/ide/src/syntax_highlighting/macro_.rs @@ -117,7 +117,7 @@ fn update_macro_state(state: &mut MacroMatcherParseState, tok: &SyntaxToken) { fn is_metavariable(token: &SyntaxToken) -> Option { match token.kind() { - kind if kind == SyntaxKind::IDENT || kind.is_keyword() => { + kind if kind.is_any_identifier() => { if let Some(_dollar) = token.prev_token().filter(|t| t.kind() == T![$]) { return Some(token.text_range()); } 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 17411fefbd..196552020a 100644 --- a/crates/ide/src/syntax_highlighting/test_data/highlight_macros.html +++ b/crates/ide/src/syntax_highlighting/test_data/highlight_macros.html @@ -100,7 +100,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd }; } -include!(concat!("foo/", "foo.rs")); +include!(concat!("foo/", "foo.rs")); struct S<T>(T); fn main() { diff --git a/crates/ide/src/view_item_tree.rs b/crates/ide/src/view_item_tree.rs index dae79998dc..a6352b99d4 100644 --- a/crates/ide/src/view_item_tree.rs +++ b/crates/ide/src/view_item_tree.rs @@ -16,5 +16,5 @@ pub(crate) fn view_item_tree(db: &RootDatabase, file_id: FileId) -> String { let file_id = sema .attach_first_edition(file_id) .unwrap_or_else(|| EditionedFileId::current_edition(file_id)); - db.file_item_tree(file_id.into()).pretty_print(db) + db.file_item_tree(file_id.into()).pretty_print(db, file_id.edition()) } diff --git a/crates/ide/src/view_memory_layout.rs b/crates/ide/src/view_memory_layout.rs index df3f2f18b4..830c39e21e 100644 --- a/crates/ide/src/view_memory_layout.rs +++ b/crates/ide/src/view_memory_layout.rs @@ -6,6 +6,7 @@ use ide_db::{ helpers::{get_definition, pick_best_token}, RootDatabase, }; +use span::Edition; use syntax::{AstNode, SyntaxKind}; use crate::FilePosition; @@ -85,6 +86,10 @@ pub(crate) fn view_memory_layout( ) -> Option { let sema = Semantics::new(db); let file = sema.parse_guess_edition(position.file_id); + let edition = sema + .attach_first_edition(position.file_id) + .map(|it| it.edition()) + .unwrap_or(Edition::CURRENT); let token = pick_best_token(file.syntax().token_at_offset(position.offset), |kind| match kind { SyntaxKind::IDENT => 3, @@ -111,6 +116,7 @@ pub(crate) fn view_memory_layout( ty: &Type, layout: &Layout, parent_idx: usize, + edition: Edition, ) { let mut fields = ty .fields(db) @@ -141,7 +147,7 @@ pub(crate) fn view_memory_layout( if let Ok(child_layout) = child_ty.layout(db) { nodes.push(MemoryLayoutNode { item_name: field.name(db), - typename: child_ty.display(db).to_string(), + typename: child_ty.display(db, edition).to_string(), size: child_layout.size(), alignment: child_layout.align(), offset: match *field { @@ -157,7 +163,7 @@ pub(crate) fn view_memory_layout( item_name: field.name(db) + format!("(no layout data: {:?})", child_ty.layout(db).unwrap_err()) .as_ref(), - typename: child_ty.display(db).to_string(), + typename: child_ty.display(db, edition).to_string(), size: 0, offset: 0, alignment: 0, @@ -170,7 +176,7 @@ pub(crate) fn view_memory_layout( for (i, (_, child_ty)) in fields.iter().enumerate() { if let Ok(child_layout) = child_ty.layout(db) { - read_layout(nodes, db, child_ty, &child_layout, children_start + i); + read_layout(nodes, db, child_ty, &child_layout, children_start + i, edition); } } } @@ -188,7 +194,7 @@ pub(crate) fn view_memory_layout( def => def.name(db).map(|n| n.as_str().to_owned()).unwrap_or("[ROOT]".to_owned()), }; - let typename = ty.display(db).to_string(); + let typename = ty.display(db, edition).to_string(); let mut nodes = vec![MemoryLayoutNode { item_name, @@ -200,7 +206,7 @@ pub(crate) fn view_memory_layout( children_start: -1, children_len: 0, }]; - read_layout(&mut nodes, db, &ty, &layout, 0); + read_layout(&mut nodes, db, &ty, &layout, 0, edition); RecursiveMemoryLayout { nodes } }) diff --git a/crates/intern/src/symbol/symbols.rs b/crates/intern/src/symbol/symbols.rs index 2feca32ff8..7eb8e4a5e2 100644 --- a/crates/intern/src/symbol/symbols.rs +++ b/crates/intern/src/symbol/symbols.rs @@ -239,6 +239,7 @@ define_symbols! { fundamental, future_trait, future, + future_output, Future, ge, get_context, @@ -273,6 +274,7 @@ define_symbols! { iter_mut, iter, Iterator, + iterator, keyword, lang, le, diff --git a/crates/parser/src/grammar/generic_args.rs b/crates/parser/src/grammar/generic_args.rs index 249be2a333..c62c8a9d3f 100644 --- a/crates/parser/src/grammar/generic_args.rs +++ b/crates/parser/src/grammar/generic_args.rs @@ -102,13 +102,18 @@ fn generic_arg(p: &mut Parser<'_>) -> bool { IDENT if p.nth_at(1, T!['(']) => { let m = p.start(); name_ref(p); - params::param_list_fn_trait(p); - if p.at(T![:]) && !p.at(T![::]) { - // test associated_return_type_bounds - // fn foo>() {} + if p.nth_at(1, T![..]) { + let rtn = p.start(); + p.bump(T!['(']); + p.bump(T![..]); + p.expect(T![')']); + rtn.complete(p, RETURN_TYPE_SYNTAX); + // test return_type_syntax_assoc_type_bound + // fn foo>() {} generic_params::bounds(p); m.complete(p, ASSOC_TYPE_ARG); } else { + params::param_list_fn_trait(p); // test bare_dyn_types_with_paren_as_generic_args // type A = S; // type A = S; diff --git a/crates/parser/src/grammar/generic_params.rs b/crates/parser/src/grammar/generic_params.rs index cf80a535ac..e0fa753fa7 100644 --- a/crates/parser/src/grammar/generic_params.rs +++ b/crates/parser/src/grammar/generic_params.rs @@ -119,8 +119,7 @@ fn lifetime_bounds(p: &mut Parser<'_>) { // test type_param_bounds // struct S; pub(super) fn bounds(p: &mut Parser<'_>) { - assert!(p.at(T![:])); - p.bump(T![:]); + p.expect(T![:]); bounds_without_colon(p); } diff --git a/crates/parser/src/grammar/paths.rs b/crates/parser/src/grammar/paths.rs index 01b8f9e918..09db921803 100644 --- a/crates/parser/src/grammar/paths.rs +++ b/crates/parser/src/grammar/paths.rs @@ -140,11 +140,24 @@ fn opt_path_type_args(p: &mut Parser<'_>, mode: Mode) { if p.at(T![::]) && p.nth_at(2, T!['(']) { p.bump(T![::]); } - // test path_fn_trait_args - // type F = Box ()>; if p.at(T!['(']) { - params::param_list_fn_trait(p); - opt_ret_type(p); + if p.nth_at(1, T![..]) { + // test return_type_syntax_in_path + // fn foo() + // where + // T::method(..): Send, + // {} + let rtn = p.start(); + p.bump(T!['(']); + p.bump(T![..]); + p.expect(T![')']); + rtn.complete(p, RETURN_TYPE_SYNTAX); + } else { + // test path_fn_trait_args + // type F = Box ()>; + params::param_list_fn_trait(p); + opt_ret_type(p); + } } else { generic_args::opt_generic_arg_list(p, false); } diff --git a/crates/parser/src/lexed_str.rs b/crates/parser/src/lexed_str.rs index 13fc61074d..ff924830ae 100644 --- a/crates/parser/src/lexed_str.rs +++ b/crates/parser/src/lexed_str.rs @@ -178,19 +178,8 @@ impl<'a> Converter<'a> { rustc_lexer::TokenKind::Whitespace => WHITESPACE, rustc_lexer::TokenKind::Ident if token_text == "_" => UNDERSCORE, - rustc_lexer::TokenKind::Ident - if ["async", "await", "dyn", "try"].contains(&token_text) - && !self.edition.at_least_2018() => - { - IDENT - } - rustc_lexer::TokenKind::Ident - if token_text == "gen" && !self.edition.at_least_2024() => - { - IDENT - } rustc_lexer::TokenKind::Ident => { - SyntaxKind::from_keyword(token_text).unwrap_or(IDENT) + SyntaxKind::from_keyword(token_text, self.edition).unwrap_or(IDENT) } rustc_lexer::TokenKind::InvalidPrefix | rustc_lexer::TokenKind::InvalidIdent => { err = "Ident contains invalid characters"; diff --git a/crates/parser/src/shortcuts.rs b/crates/parser/src/shortcuts.rs index 1cf81e79b0..7adedba7c4 100644 --- a/crates/parser/src/shortcuts.rs +++ b/crates/parser/src/shortcuts.rs @@ -35,12 +35,10 @@ impl LexedStr<'_> { was_joint = false } else if kind == SyntaxKind::IDENT { let token_text = self.text(i); - let contextual_kw = if !edition.at_least_2018() && token_text == "dyn" { - SyntaxKind::DYN_KW - } else { - SyntaxKind::from_contextual_keyword(token_text).unwrap_or(SyntaxKind::IDENT) - }; - res.push_ident(contextual_kw); + res.push_ident( + SyntaxKind::from_contextual_keyword(token_text, edition) + .unwrap_or(SyntaxKind::IDENT), + ) } else { if was_joint { res.was_joint(); diff --git a/crates/parser/src/syntax_kind.rs b/crates/parser/src/syntax_kind.rs index 3ca6bd4cb1..6a8cca9ccc 100644 --- a/crates/parser/src/syntax_kind.rs +++ b/crates/parser/src/syntax_kind.rs @@ -3,6 +3,8 @@ mod generated; +use crate::Edition; + #[allow(unreachable_pub)] pub use self::generated::SyntaxKind; @@ -26,4 +28,11 @@ impl SyntaxKind { pub fn is_trivia(self) -> bool { matches!(self, SyntaxKind::WHITESPACE | SyntaxKind::COMMENT) } + + /// Returns true if this is an identifier or a keyword. + #[inline] + pub fn is_any_identifier(self) -> bool { + // Assuming no edition removed keywords... + self == SyntaxKind::IDENT || self.is_keyword(Edition::LATEST) + } } diff --git a/crates/parser/src/syntax_kind/generated.rs b/crates/parser/src/syntax_kind/generated.rs index 7bddf88740..00f212487a 100644 --- a/crates/parser/src/syntax_kind/generated.rs +++ b/crates/parser/src/syntax_kind/generated.rs @@ -1,6 +1,7 @@ //! Generated by `cargo codegen grammar`, do not edit by hand. #![allow(bad_style, missing_docs, unreachable_pub)] +use crate::Edition; #[doc = r" The kind of syntax node, e.g. `IDENT`, `USE_KW`, or `STRUCT`."] #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] #[repr(u16)] @@ -64,8 +65,6 @@ pub enum SyntaxKind { SELF_TYPE_KW, ABSTRACT_KW, AS_KW, - ASYNC_KW, - AWAIT_KW, BECOME_KW, BOX_KW, BREAK_KW, @@ -73,7 +72,6 @@ pub enum SyntaxKind { CONTINUE_KW, CRATE_KW, DO_KW, - DYN_KW, ELSE_KW, ENUM_KW, EXTERN_KW, @@ -81,7 +79,6 @@ pub enum SyntaxKind { FINAL_KW, FN_KW, FOR_KW, - GEN_KW, IF_KW, IMPL_KW, IN_KW, @@ -103,7 +100,6 @@ pub enum SyntaxKind { SUPER_KW, TRAIT_KW, TRUE_KW, - TRY_KW, TYPE_KW, TYPEOF_KW, UNSAFE_KW, @@ -114,13 +110,18 @@ pub enum SyntaxKind { WHILE_KW, YIELD_KW, ASM_KW, + ASYNC_KW, AUTO_KW, + AWAIT_KW, BUILTIN_KW, DEFAULT_KW, + DYN_KW, FORMAT_ARGS_KW, + GEN_KW, MACRO_RULES_KW, OFFSET_OF_KW, RAW_KW, + TRY_KW, UNION_KW, YEET_KW, BYTE, @@ -252,6 +253,7 @@ pub enum SyntaxKind { RENAME, REST_PAT, RETURN_EXPR, + RETURN_TYPE_SYNTAX, RET_TYPE, SELF_PARAM, SLICE_PAT, @@ -296,14 +298,14 @@ pub enum SyntaxKind { } use self::SyntaxKind::*; impl SyntaxKind { - pub fn is_keyword(self) -> bool { + #[doc = r" Checks whether this syntax kind is a strict keyword for the given edition."] + #[doc = r" Strict keywords are identifiers that are always considered keywords."] + pub fn is_strict_keyword(self, edition: Edition) -> bool { matches!( self, SELF_TYPE_KW | ABSTRACT_KW | AS_KW - | ASYNC_KW - | AWAIT_KW | BECOME_KW | BOX_KW | BREAK_KW @@ -311,7 +313,6 @@ impl SyntaxKind { | CONTINUE_KW | CRATE_KW | DO_KW - | DYN_KW | ELSE_KW | ENUM_KW | EXTERN_KW @@ -319,7 +320,6 @@ impl SyntaxKind { | FINAL_KW | FN_KW | FOR_KW - | GEN_KW | IF_KW | IMPL_KW | IN_KW @@ -341,7 +341,6 @@ impl SyntaxKind { | SUPER_KW | TRAIT_KW | TRUE_KW - | TRY_KW | TYPE_KW | TYPEOF_KW | UNSAFE_KW @@ -351,17 +350,103 @@ impl SyntaxKind { | WHERE_KW | WHILE_KW | YIELD_KW - | ASM_KW - | AUTO_KW - | BUILTIN_KW - | DEFAULT_KW - | FORMAT_ARGS_KW - | MACRO_RULES_KW - | OFFSET_OF_KW - | RAW_KW - | UNION_KW - | YEET_KW - ) + ) || match self { + ASYNC_KW if Edition::Edition2018 <= edition => true, + AWAIT_KW if Edition::Edition2018 <= edition => true, + DYN_KW if Edition::Edition2018 <= edition => true, + GEN_KW if Edition::Edition2024 <= edition => true, + TRY_KW if Edition::Edition2018 <= edition => true, + _ => false, + } + } + #[doc = r" Checks whether this syntax kind is a weak keyword for the given edition."] + #[doc = r" Weak keywords are identifiers that are considered keywords only in certain contexts."] + pub fn is_contextual_keyword(self, edition: Edition) -> bool { + match self { + ASM_KW => true, + AUTO_KW => true, + BUILTIN_KW => true, + DEFAULT_KW => true, + DYN_KW if edition < Edition::Edition2018 => true, + FORMAT_ARGS_KW => true, + MACRO_RULES_KW => true, + OFFSET_OF_KW => true, + RAW_KW => true, + UNION_KW => true, + YEET_KW => true, + _ => false, + } + } + #[doc = r" Checks whether this syntax kind is a strict or weak keyword for the given edition."] + pub fn is_keyword(self, edition: Edition) -> bool { + matches!( + self, + SELF_TYPE_KW + | ABSTRACT_KW + | AS_KW + | BECOME_KW + | BOX_KW + | BREAK_KW + | CONST_KW + | CONTINUE_KW + | CRATE_KW + | DO_KW + | ELSE_KW + | ENUM_KW + | EXTERN_KW + | FALSE_KW + | FINAL_KW + | FN_KW + | FOR_KW + | IF_KW + | IMPL_KW + | IN_KW + | LET_KW + | LOOP_KW + | MACRO_KW + | MATCH_KW + | MOD_KW + | MOVE_KW + | MUT_KW + | OVERRIDE_KW + | PRIV_KW + | PUB_KW + | REF_KW + | RETURN_KW + | SELF_KW + | STATIC_KW + | STRUCT_KW + | SUPER_KW + | TRAIT_KW + | TRUE_KW + | TYPE_KW + | TYPEOF_KW + | UNSAFE_KW + | UNSIZED_KW + | USE_KW + | VIRTUAL_KW + | WHERE_KW + | WHILE_KW + | YIELD_KW + ) || match self { + ASYNC_KW if Edition::Edition2018 <= edition => true, + AWAIT_KW if Edition::Edition2018 <= edition => true, + DYN_KW if Edition::Edition2018 <= edition => true, + GEN_KW if Edition::Edition2024 <= edition => true, + TRY_KW if Edition::Edition2018 <= edition => true, + ASM_KW => true, + AUTO_KW => true, + BUILTIN_KW => true, + DEFAULT_KW => true, + DYN_KW if edition < Edition::Edition2018 => true, + FORMAT_ARGS_KW => true, + MACRO_RULES_KW => true, + OFFSET_OF_KW => true, + RAW_KW => true, + UNION_KW => true, + YEET_KW => true, + _ => false, + } } pub fn is_punct(self) -> bool { matches!( @@ -434,13 +519,11 @@ impl SyntaxKind { | STRING ) } - pub fn from_keyword(ident: &str) -> Option { + pub fn from_keyword(ident: &str, edition: Edition) -> Option { let kw = match ident { "Self" => SELF_TYPE_KW, "abstract" => ABSTRACT_KW, "as" => AS_KW, - "async" => ASYNC_KW, - "await" => AWAIT_KW, "become" => BECOME_KW, "box" => BOX_KW, "break" => BREAK_KW, @@ -448,7 +531,6 @@ impl SyntaxKind { "continue" => CONTINUE_KW, "crate" => CRATE_KW, "do" => DO_KW, - "dyn" => DYN_KW, "else" => ELSE_KW, "enum" => ENUM_KW, "extern" => EXTERN_KW, @@ -456,7 +538,6 @@ impl SyntaxKind { "final" => FINAL_KW, "fn" => FN_KW, "for" => FOR_KW, - "gen" => GEN_KW, "if" => IF_KW, "impl" => IMPL_KW, "in" => IN_KW, @@ -478,7 +559,6 @@ impl SyntaxKind { "super" => SUPER_KW, "trait" => TRAIT_KW, "true" => TRUE_KW, - "try" => TRY_KW, "type" => TYPE_KW, "typeof" => TYPEOF_KW, "unsafe" => UNSAFE_KW, @@ -488,16 +568,22 @@ impl SyntaxKind { "where" => WHERE_KW, "while" => WHILE_KW, "yield" => YIELD_KW, + "async" if Edition::Edition2018 <= edition => ASYNC_KW, + "await" if Edition::Edition2018 <= edition => AWAIT_KW, + "dyn" if Edition::Edition2018 <= edition => DYN_KW, + "gen" if Edition::Edition2024 <= edition => GEN_KW, + "try" if Edition::Edition2018 <= edition => TRY_KW, _ => return None, }; Some(kw) } - pub fn from_contextual_keyword(ident: &str) -> Option { + pub fn from_contextual_keyword(ident: &str, edition: Edition) -> Option { let kw = match ident { "asm" => ASM_KW, "auto" => AUTO_KW, "builtin" => BUILTIN_KW, "default" => DEFAULT_KW, + "dyn" if edition < Edition::Edition2018 => DYN_KW, "format_args" => FORMAT_ARGS_KW, "macro_rules" => MACRO_RULES_KW, "offset_of" => OFFSET_OF_KW, @@ -544,4 +630,4 @@ impl SyntaxKind { } } #[macro_export] -macro_rules ! T { [$] => { $ crate :: SyntaxKind :: DOLLAR } ; [;] => { $ crate :: SyntaxKind :: SEMICOLON } ; [,] => { $ crate :: SyntaxKind :: COMMA } ; ['('] => { $ crate :: SyntaxKind :: L_PAREN } ; [')'] => { $ crate :: SyntaxKind :: R_PAREN } ; ['{'] => { $ crate :: SyntaxKind :: L_CURLY } ; ['}'] => { $ crate :: SyntaxKind :: R_CURLY } ; ['['] => { $ crate :: SyntaxKind :: L_BRACK } ; [']'] => { $ crate :: SyntaxKind :: R_BRACK } ; [<] => { $ crate :: SyntaxKind :: L_ANGLE } ; [>] => { $ crate :: SyntaxKind :: R_ANGLE } ; [@] => { $ crate :: SyntaxKind :: AT } ; [#] => { $ crate :: SyntaxKind :: POUND } ; [~] => { $ crate :: SyntaxKind :: TILDE } ; [?] => { $ crate :: SyntaxKind :: QUESTION } ; [&] => { $ crate :: SyntaxKind :: AMP } ; [|] => { $ crate :: SyntaxKind :: PIPE } ; [+] => { $ crate :: SyntaxKind :: PLUS } ; [*] => { $ crate :: SyntaxKind :: STAR } ; [/] => { $ crate :: SyntaxKind :: SLASH } ; [^] => { $ crate :: SyntaxKind :: CARET } ; [%] => { $ crate :: SyntaxKind :: PERCENT } ; [_] => { $ crate :: SyntaxKind :: UNDERSCORE } ; [.] => { $ crate :: SyntaxKind :: DOT } ; [..] => { $ crate :: SyntaxKind :: DOT2 } ; [...] => { $ crate :: SyntaxKind :: DOT3 } ; [..=] => { $ crate :: SyntaxKind :: DOT2EQ } ; [:] => { $ crate :: SyntaxKind :: COLON } ; [::] => { $ crate :: SyntaxKind :: COLON2 } ; [=] => { $ crate :: SyntaxKind :: EQ } ; [==] => { $ crate :: SyntaxKind :: EQ2 } ; [=>] => { $ crate :: SyntaxKind :: FAT_ARROW } ; [!] => { $ crate :: SyntaxKind :: BANG } ; [!=] => { $ crate :: SyntaxKind :: NEQ } ; [-] => { $ crate :: SyntaxKind :: MINUS } ; [->] => { $ crate :: SyntaxKind :: THIN_ARROW } ; [<=] => { $ crate :: SyntaxKind :: LTEQ } ; [>=] => { $ crate :: SyntaxKind :: GTEQ } ; [+=] => { $ crate :: SyntaxKind :: PLUSEQ } ; [-=] => { $ crate :: SyntaxKind :: MINUSEQ } ; [|=] => { $ crate :: SyntaxKind :: PIPEEQ } ; [&=] => { $ crate :: SyntaxKind :: AMPEQ } ; [^=] => { $ crate :: SyntaxKind :: CARETEQ } ; [/=] => { $ crate :: SyntaxKind :: SLASHEQ } ; [*=] => { $ crate :: SyntaxKind :: STAREQ } ; [%=] => { $ crate :: SyntaxKind :: PERCENTEQ } ; [&&] => { $ crate :: SyntaxKind :: AMP2 } ; [||] => { $ crate :: SyntaxKind :: PIPE2 } ; [<<] => { $ crate :: SyntaxKind :: SHL } ; [>>] => { $ crate :: SyntaxKind :: SHR } ; [<<=] => { $ crate :: SyntaxKind :: SHLEQ } ; [>>=] => { $ crate :: SyntaxKind :: SHREQ } ; [Self] => { $ crate :: SyntaxKind :: SELF_TYPE_KW } ; [abstract] => { $ crate :: SyntaxKind :: ABSTRACT_KW } ; [as] => { $ crate :: SyntaxKind :: AS_KW } ; [async] => { $ crate :: SyntaxKind :: ASYNC_KW } ; [await] => { $ crate :: SyntaxKind :: AWAIT_KW } ; [become] => { $ crate :: SyntaxKind :: BECOME_KW } ; [box] => { $ crate :: SyntaxKind :: BOX_KW } ; [break] => { $ crate :: SyntaxKind :: BREAK_KW } ; [const] => { $ crate :: SyntaxKind :: CONST_KW } ; [continue] => { $ crate :: SyntaxKind :: CONTINUE_KW } ; [crate] => { $ crate :: SyntaxKind :: CRATE_KW } ; [do] => { $ crate :: SyntaxKind :: DO_KW } ; [dyn] => { $ crate :: SyntaxKind :: DYN_KW } ; [else] => { $ crate :: SyntaxKind :: ELSE_KW } ; [enum] => { $ crate :: SyntaxKind :: ENUM_KW } ; [extern] => { $ crate :: SyntaxKind :: EXTERN_KW } ; [false] => { $ crate :: SyntaxKind :: FALSE_KW } ; [final] => { $ crate :: SyntaxKind :: FINAL_KW } ; [fn] => { $ crate :: SyntaxKind :: FN_KW } ; [for] => { $ crate :: SyntaxKind :: FOR_KW } ; [gen] => { $ crate :: SyntaxKind :: GEN_KW } ; [if] => { $ crate :: SyntaxKind :: IF_KW } ; [impl] => { $ crate :: SyntaxKind :: IMPL_KW } ; [in] => { $ crate :: SyntaxKind :: IN_KW } ; [let] => { $ crate :: SyntaxKind :: LET_KW } ; [loop] => { $ crate :: SyntaxKind :: LOOP_KW } ; [macro] => { $ crate :: SyntaxKind :: MACRO_KW } ; [match] => { $ crate :: SyntaxKind :: MATCH_KW } ; [mod] => { $ crate :: SyntaxKind :: MOD_KW } ; [move] => { $ crate :: SyntaxKind :: MOVE_KW } ; [mut] => { $ crate :: SyntaxKind :: MUT_KW } ; [override] => { $ crate :: SyntaxKind :: OVERRIDE_KW } ; [priv] => { $ crate :: SyntaxKind :: PRIV_KW } ; [pub] => { $ crate :: SyntaxKind :: PUB_KW } ; [ref] => { $ crate :: SyntaxKind :: REF_KW } ; [return] => { $ crate :: SyntaxKind :: RETURN_KW } ; [self] => { $ crate :: SyntaxKind :: SELF_KW } ; [static] => { $ crate :: SyntaxKind :: STATIC_KW } ; [struct] => { $ crate :: SyntaxKind :: STRUCT_KW } ; [super] => { $ crate :: SyntaxKind :: SUPER_KW } ; [trait] => { $ crate :: SyntaxKind :: TRAIT_KW } ; [true] => { $ crate :: SyntaxKind :: TRUE_KW } ; [try] => { $ crate :: SyntaxKind :: TRY_KW } ; [type] => { $ crate :: SyntaxKind :: TYPE_KW } ; [typeof] => { $ crate :: SyntaxKind :: TYPEOF_KW } ; [unsafe] => { $ crate :: SyntaxKind :: UNSAFE_KW } ; [unsized] => { $ crate :: SyntaxKind :: UNSIZED_KW } ; [use] => { $ crate :: SyntaxKind :: USE_KW } ; [virtual] => { $ crate :: SyntaxKind :: VIRTUAL_KW } ; [where] => { $ crate :: SyntaxKind :: WHERE_KW } ; [while] => { $ crate :: SyntaxKind :: WHILE_KW } ; [yield] => { $ crate :: SyntaxKind :: YIELD_KW } ; [asm] => { $ crate :: SyntaxKind :: ASM_KW } ; [auto] => { $ crate :: SyntaxKind :: AUTO_KW } ; [builtin] => { $ crate :: SyntaxKind :: BUILTIN_KW } ; [default] => { $ crate :: SyntaxKind :: DEFAULT_KW } ; [format_args] => { $ crate :: SyntaxKind :: FORMAT_ARGS_KW } ; [macro_rules] => { $ crate :: SyntaxKind :: MACRO_RULES_KW } ; [offset_of] => { $ crate :: SyntaxKind :: OFFSET_OF_KW } ; [raw] => { $ crate :: SyntaxKind :: RAW_KW } ; [union] => { $ crate :: SyntaxKind :: UNION_KW } ; [yeet] => { $ crate :: SyntaxKind :: YEET_KW } ; [lifetime_ident] => { $ crate :: SyntaxKind :: LIFETIME_IDENT } ; [int_number] => { $ crate :: SyntaxKind :: INT_NUMBER } ; [ident] => { $ crate :: SyntaxKind :: IDENT } ; [string] => { $ crate :: SyntaxKind :: STRING } ; [shebang] => { $ crate :: SyntaxKind :: SHEBANG } ; } +macro_rules ! T { [$] => { $ crate :: SyntaxKind :: DOLLAR } ; [;] => { $ crate :: SyntaxKind :: SEMICOLON } ; [,] => { $ crate :: SyntaxKind :: COMMA } ; ['('] => { $ crate :: SyntaxKind :: L_PAREN } ; [')'] => { $ crate :: SyntaxKind :: R_PAREN } ; ['{'] => { $ crate :: SyntaxKind :: L_CURLY } ; ['}'] => { $ crate :: SyntaxKind :: R_CURLY } ; ['['] => { $ crate :: SyntaxKind :: L_BRACK } ; [']'] => { $ crate :: SyntaxKind :: R_BRACK } ; [<] => { $ crate :: SyntaxKind :: L_ANGLE } ; [>] => { $ crate :: SyntaxKind :: R_ANGLE } ; [@] => { $ crate :: SyntaxKind :: AT } ; [#] => { $ crate :: SyntaxKind :: POUND } ; [~] => { $ crate :: SyntaxKind :: TILDE } ; [?] => { $ crate :: SyntaxKind :: QUESTION } ; [&] => { $ crate :: SyntaxKind :: AMP } ; [|] => { $ crate :: SyntaxKind :: PIPE } ; [+] => { $ crate :: SyntaxKind :: PLUS } ; [*] => { $ crate :: SyntaxKind :: STAR } ; [/] => { $ crate :: SyntaxKind :: SLASH } ; [^] => { $ crate :: SyntaxKind :: CARET } ; [%] => { $ crate :: SyntaxKind :: PERCENT } ; [_] => { $ crate :: SyntaxKind :: UNDERSCORE } ; [.] => { $ crate :: SyntaxKind :: DOT } ; [..] => { $ crate :: SyntaxKind :: DOT2 } ; [...] => { $ crate :: SyntaxKind :: DOT3 } ; [..=] => { $ crate :: SyntaxKind :: DOT2EQ } ; [:] => { $ crate :: SyntaxKind :: COLON } ; [::] => { $ crate :: SyntaxKind :: COLON2 } ; [=] => { $ crate :: SyntaxKind :: EQ } ; [==] => { $ crate :: SyntaxKind :: EQ2 } ; [=>] => { $ crate :: SyntaxKind :: FAT_ARROW } ; [!] => { $ crate :: SyntaxKind :: BANG } ; [!=] => { $ crate :: SyntaxKind :: NEQ } ; [-] => { $ crate :: SyntaxKind :: MINUS } ; [->] => { $ crate :: SyntaxKind :: THIN_ARROW } ; [<=] => { $ crate :: SyntaxKind :: LTEQ } ; [>=] => { $ crate :: SyntaxKind :: GTEQ } ; [+=] => { $ crate :: SyntaxKind :: PLUSEQ } ; [-=] => { $ crate :: SyntaxKind :: MINUSEQ } ; [|=] => { $ crate :: SyntaxKind :: PIPEEQ } ; [&=] => { $ crate :: SyntaxKind :: AMPEQ } ; [^=] => { $ crate :: SyntaxKind :: CARETEQ } ; [/=] => { $ crate :: SyntaxKind :: SLASHEQ } ; [*=] => { $ crate :: SyntaxKind :: STAREQ } ; [%=] => { $ crate :: SyntaxKind :: PERCENTEQ } ; [&&] => { $ crate :: SyntaxKind :: AMP2 } ; [||] => { $ crate :: SyntaxKind :: PIPE2 } ; [<<] => { $ crate :: SyntaxKind :: SHL } ; [>>] => { $ crate :: SyntaxKind :: SHR } ; [<<=] => { $ crate :: SyntaxKind :: SHLEQ } ; [>>=] => { $ crate :: SyntaxKind :: SHREQ } ; [Self] => { $ crate :: SyntaxKind :: SELF_TYPE_KW } ; [abstract] => { $ crate :: SyntaxKind :: ABSTRACT_KW } ; [as] => { $ crate :: SyntaxKind :: AS_KW } ; [become] => { $ crate :: SyntaxKind :: BECOME_KW } ; [box] => { $ crate :: SyntaxKind :: BOX_KW } ; [break] => { $ crate :: SyntaxKind :: BREAK_KW } ; [const] => { $ crate :: SyntaxKind :: CONST_KW } ; [continue] => { $ crate :: SyntaxKind :: CONTINUE_KW } ; [crate] => { $ crate :: SyntaxKind :: CRATE_KW } ; [do] => { $ crate :: SyntaxKind :: DO_KW } ; [else] => { $ crate :: SyntaxKind :: ELSE_KW } ; [enum] => { $ crate :: SyntaxKind :: ENUM_KW } ; [extern] => { $ crate :: SyntaxKind :: EXTERN_KW } ; [false] => { $ crate :: SyntaxKind :: FALSE_KW } ; [final] => { $ crate :: SyntaxKind :: FINAL_KW } ; [fn] => { $ crate :: SyntaxKind :: FN_KW } ; [for] => { $ crate :: SyntaxKind :: FOR_KW } ; [if] => { $ crate :: SyntaxKind :: IF_KW } ; [impl] => { $ crate :: SyntaxKind :: IMPL_KW } ; [in] => { $ crate :: SyntaxKind :: IN_KW } ; [let] => { $ crate :: SyntaxKind :: LET_KW } ; [loop] => { $ crate :: SyntaxKind :: LOOP_KW } ; [macro] => { $ crate :: SyntaxKind :: MACRO_KW } ; [match] => { $ crate :: SyntaxKind :: MATCH_KW } ; [mod] => { $ crate :: SyntaxKind :: MOD_KW } ; [move] => { $ crate :: SyntaxKind :: MOVE_KW } ; [mut] => { $ crate :: SyntaxKind :: MUT_KW } ; [override] => { $ crate :: SyntaxKind :: OVERRIDE_KW } ; [priv] => { $ crate :: SyntaxKind :: PRIV_KW } ; [pub] => { $ crate :: SyntaxKind :: PUB_KW } ; [ref] => { $ crate :: SyntaxKind :: REF_KW } ; [return] => { $ crate :: SyntaxKind :: RETURN_KW } ; [self] => { $ crate :: SyntaxKind :: SELF_KW } ; [static] => { $ crate :: SyntaxKind :: STATIC_KW } ; [struct] => { $ crate :: SyntaxKind :: STRUCT_KW } ; [super] => { $ crate :: SyntaxKind :: SUPER_KW } ; [trait] => { $ crate :: SyntaxKind :: TRAIT_KW } ; [true] => { $ crate :: SyntaxKind :: TRUE_KW } ; [type] => { $ crate :: SyntaxKind :: TYPE_KW } ; [typeof] => { $ crate :: SyntaxKind :: TYPEOF_KW } ; [unsafe] => { $ crate :: SyntaxKind :: UNSAFE_KW } ; [unsized] => { $ crate :: SyntaxKind :: UNSIZED_KW } ; [use] => { $ crate :: SyntaxKind :: USE_KW } ; [virtual] => { $ crate :: SyntaxKind :: VIRTUAL_KW } ; [where] => { $ crate :: SyntaxKind :: WHERE_KW } ; [while] => { $ crate :: SyntaxKind :: WHILE_KW } ; [yield] => { $ crate :: SyntaxKind :: YIELD_KW } ; [asm] => { $ crate :: SyntaxKind :: ASM_KW } ; [auto] => { $ crate :: SyntaxKind :: AUTO_KW } ; [builtin] => { $ crate :: SyntaxKind :: BUILTIN_KW } ; [default] => { $ crate :: SyntaxKind :: DEFAULT_KW } ; [dyn] => { $ crate :: SyntaxKind :: DYN_KW } ; [format_args] => { $ crate :: SyntaxKind :: FORMAT_ARGS_KW } ; [macro_rules] => { $ crate :: SyntaxKind :: MACRO_RULES_KW } ; [offset_of] => { $ crate :: SyntaxKind :: OFFSET_OF_KW } ; [raw] => { $ crate :: SyntaxKind :: RAW_KW } ; [union] => { $ crate :: SyntaxKind :: UNION_KW } ; [yeet] => { $ crate :: SyntaxKind :: YEET_KW } ; [async] => { $ crate :: SyntaxKind :: ASYNC_KW } ; [await] => { $ crate :: SyntaxKind :: AWAIT_KW } ; [dyn] => { $ crate :: SyntaxKind :: DYN_KW } ; [gen] => { $ crate :: SyntaxKind :: GEN_KW } ; [try] => { $ crate :: SyntaxKind :: TRY_KW } ; [lifetime_ident] => { $ crate :: SyntaxKind :: LIFETIME_IDENT } ; [int_number] => { $ crate :: SyntaxKind :: INT_NUMBER } ; [ident] => { $ crate :: SyntaxKind :: IDENT } ; [string] => { $ crate :: SyntaxKind :: STRING } ; [shebang] => { $ crate :: SyntaxKind :: SHEBANG } ; } diff --git a/crates/parser/test_data/generated/runner.rs b/crates/parser/test_data/generated/runner.rs index 1907f03b44..9ce5a2ae74 100644 --- a/crates/parser/test_data/generated/runner.rs +++ b/crates/parser/test_data/generated/runner.rs @@ -37,10 +37,6 @@ mod ok { #[test] fn assoc_type_eq() { run_and_expect_no_errors("test_data/parser/inline/ok/assoc_type_eq.rs"); } #[test] - fn associated_return_type_bounds() { - run_and_expect_no_errors("test_data/parser/inline/ok/associated_return_type_bounds.rs"); - } - #[test] fn associated_type_bounds() { run_and_expect_no_errors("test_data/parser/inline/ok/associated_type_bounds.rs"); } @@ -519,6 +515,16 @@ mod ok { #[test] fn return_expr() { run_and_expect_no_errors("test_data/parser/inline/ok/return_expr.rs"); } #[test] + fn return_type_syntax_assoc_type_bound() { + run_and_expect_no_errors( + "test_data/parser/inline/ok/return_type_syntax_assoc_type_bound.rs", + ); + } + #[test] + fn return_type_syntax_in_path() { + run_and_expect_no_errors("test_data/parser/inline/ok/return_type_syntax_in_path.rs"); + } + #[test] fn self_param() { run_and_expect_no_errors("test_data/parser/inline/ok/self_param.rs"); } #[test] fn self_param_outer_attr() { diff --git a/crates/parser/test_data/parser/inline/ok/associated_return_type_bounds.rs b/crates/parser/test_data/parser/inline/ok/associated_return_type_bounds.rs deleted file mode 100644 index 42029ac592..0000000000 --- a/crates/parser/test_data/parser/inline/ok/associated_return_type_bounds.rs +++ /dev/null @@ -1 +0,0 @@ -fn foo>() {} diff --git a/crates/parser/test_data/parser/inline/ok/return_type_syntax_assoc_type_bound.rast b/crates/parser/test_data/parser/inline/ok/return_type_syntax_assoc_type_bound.rast new file mode 100644 index 0000000000..30e0e73bbd --- /dev/null +++ b/crates/parser/test_data/parser/inline/ok/return_type_syntax_assoc_type_bound.rast @@ -0,0 +1,49 @@ +SOURCE_FILE + FN + FN_KW "fn" + WHITESPACE " " + NAME + IDENT "foo" + GENERIC_PARAM_LIST + L_ANGLE "<" + TYPE_PARAM + NAME + IDENT "T" + COLON ":" + WHITESPACE " " + TYPE_BOUND_LIST + TYPE_BOUND + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Trait" + GENERIC_ARG_LIST + L_ANGLE "<" + ASSOC_TYPE_ARG + NAME_REF + IDENT "method" + RETURN_TYPE_SYNTAX + L_PAREN "(" + DOT2 ".." + R_PAREN ")" + COLON ":" + WHITESPACE " " + TYPE_BOUND_LIST + TYPE_BOUND + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Send" + R_ANGLE ">" + R_ANGLE ">" + PARAM_LIST + L_PAREN "(" + R_PAREN ")" + WHITESPACE " " + BLOCK_EXPR + STMT_LIST + L_CURLY "{" + R_CURLY "}" + WHITESPACE "\n" diff --git a/crates/parser/test_data/parser/inline/ok/return_type_syntax_assoc_type_bound.rs b/crates/parser/test_data/parser/inline/ok/return_type_syntax_assoc_type_bound.rs new file mode 100644 index 0000000000..8a4cf4c3a0 --- /dev/null +++ b/crates/parser/test_data/parser/inline/ok/return_type_syntax_assoc_type_bound.rs @@ -0,0 +1 @@ +fn foo>() {} diff --git a/crates/parser/test_data/parser/inline/ok/return_type_syntax_in_path.rast b/crates/parser/test_data/parser/inline/ok/return_type_syntax_in_path.rast new file mode 100644 index 0000000000..501dccd79d --- /dev/null +++ b/crates/parser/test_data/parser/inline/ok/return_type_syntax_in_path.rast @@ -0,0 +1,50 @@ +SOURCE_FILE + FN + FN_KW "fn" + WHITESPACE " " + NAME + IDENT "foo" + GENERIC_PARAM_LIST + L_ANGLE "<" + TYPE_PARAM + NAME + IDENT "T" + R_ANGLE ">" + PARAM_LIST + L_PAREN "(" + R_PAREN ")" + WHITESPACE "\n" + WHERE_CLAUSE + WHERE_KW "where" + WHITESPACE "\n " + WHERE_PRED + PATH_TYPE + PATH + PATH + PATH_SEGMENT + NAME_REF + IDENT "T" + COLON2 "::" + PATH_SEGMENT + NAME_REF + IDENT "method" + RETURN_TYPE_SYNTAX + L_PAREN "(" + DOT2 ".." + R_PAREN ")" + COLON ":" + WHITESPACE " " + TYPE_BOUND_LIST + TYPE_BOUND + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Send" + COMMA "," + WHITESPACE "\n" + BLOCK_EXPR + STMT_LIST + L_CURLY "{" + R_CURLY "}" + WHITESPACE "\n" diff --git a/crates/parser/test_data/parser/inline/ok/return_type_syntax_in_path.rs b/crates/parser/test_data/parser/inline/ok/return_type_syntax_in_path.rs new file mode 100644 index 0000000000..a9b63fb01c --- /dev/null +++ b/crates/parser/test_data/parser/inline/ok/return_type_syntax_in_path.rs @@ -0,0 +1,4 @@ +fn foo() +where + T::method(..): Send, +{} diff --git a/crates/proc-macro-api/src/lib.rs b/crates/proc-macro-api/src/lib.rs index 54c1475b8b..d50a3cdbf7 100644 --- a/crates/proc-macro-api/src/lib.rs +++ b/crates/proc-macro-api/src/lib.rs @@ -154,7 +154,8 @@ impl ProcMacro { mixed_site: Span, ) -> Result, PanicMessage>, ServerError> { let version = self.process.version(); - let current_dir = env.get("CARGO_MANIFEST_DIR"); + let current_dir = + env.get("CARGO_RUSTC_CURRENT_DIR").or_else(|| env.get("CARGO_MANIFEST_DIR")); let mut span_data_table = SpanDataIndexMap::default(); let def_site = span_data_table.insert_full(def_site).0; diff --git a/crates/proc-macro-srv/src/server_impl/rust_analyzer_span.rs b/crates/proc-macro-srv/src/server_impl/rust_analyzer_span.rs index 552d99f51b..d508c19dd7 100644 --- a/crates/proc-macro-srv/src/server_impl/rust_analyzer_span.rs +++ b/crates/proc-macro-srv/src/server_impl/rust_analyzer_span.rs @@ -142,7 +142,13 @@ impl server::TokenStream for RaSpanServer { stream.is_empty() } fn from_str(&mut self, src: &str) -> Self::TokenStream { - Self::TokenStream::from_str(src, self.call_site).expect("cannot parse string") + Self::TokenStream::from_str(src, self.call_site).unwrap_or_else(|e| { + Self::TokenStream::from_str( + &format!("compile_error!(\"failed to parse str to token stream: {e}\")"), + self.call_site, + ) + .unwrap() + }) } fn to_string(&mut self, stream: &Self::TokenStream) -> String { stream.to_string() @@ -501,12 +507,17 @@ mod tests { close: span, kind: tt::DelimiterKind::Brace, }, - token_trees: Box::new([]), + token_trees: Box::new([tt::TokenTree::Leaf(tt::Leaf::Literal(tt::Literal { + kind: tt::LitKind::Str, + symbol: Symbol::intern("string"), + suffix: None, + span, + }))]), }), ], }; - assert_eq!(s.to_string(), "struct T {}"); + assert_eq!(s.to_string(), "struct T {\"string\"}"); } #[test] diff --git a/crates/proc-macro-srv/src/server_impl/token_id.rs b/crates/proc-macro-srv/src/server_impl/token_id.rs index 7720c6d83c..e478b1c853 100644 --- a/crates/proc-macro-srv/src/server_impl/token_id.rs +++ b/crates/proc-macro-srv/src/server_impl/token_id.rs @@ -131,7 +131,13 @@ impl server::TokenStream for TokenIdServer { stream.is_empty() } fn from_str(&mut self, src: &str) -> Self::TokenStream { - Self::TokenStream::from_str(src, self.call_site).expect("cannot parse string") + Self::TokenStream::from_str(src, self.call_site).unwrap_or_else(|e| { + Self::TokenStream::from_str( + &format!("compile_error!(\"failed to parse str to token stream: {e}\")"), + self.call_site, + ) + .unwrap() + }) } fn to_string(&mut self, stream: &Self::TokenStream) -> String { stream.to_string() diff --git a/crates/proc-macro-srv/src/server_impl/token_stream.rs b/crates/proc-macro-srv/src/server_impl/token_stream.rs index 4d8d496418..dbcb5a3143 100644 --- a/crates/proc-macro-srv/src/server_impl/token_stream.rs +++ b/crates/proc-macro-srv/src/server_impl/token_stream.rs @@ -131,7 +131,7 @@ pub(super) mod token_stream { call_site, src, ) - .ok_or("lexing error")?; + .ok_or_else(|| format!("lexing error: {src}"))?; Ok(TokenStream::with_subtree(subtree)) } diff --git a/crates/project-model/src/build_dependencies.rs b/crates/project-model/src/build_dependencies.rs index e7a4b8f39f..dc71b13eee 100644 --- a/crates/project-model/src/build_dependencies.rs +++ b/crates/project-model/src/build_dependencies.rs @@ -19,8 +19,8 @@ use serde::Deserialize; use toolchain::Tool; use crate::{ - utf8_stdout, CargoConfig, CargoFeatures, CargoWorkspace, InvocationLocation, - InvocationStrategy, ManifestPath, Package, Sysroot, TargetKind, + utf8_stdout, CargoConfig, CargoFeatures, CargoWorkspace, InvocationStrategy, ManifestPath, + Package, Sysroot, TargetKind, }; /// Output of the build script and proc-macro building steps for a workspace. @@ -63,10 +63,7 @@ impl WorkspaceBuildScripts { progress: &dyn Fn(String), sysroot: &Sysroot, ) -> io::Result { - let current_dir = match &config.invocation_location { - InvocationLocation::Root(root) if config.run_build_script_command.is_some() => root, - _ => workspace.workspace_root(), - }; + let current_dir = workspace.workspace_root(); let allowed_features = workspace.workspace_features(); let cmd = Self::build_command( @@ -85,25 +82,16 @@ impl WorkspaceBuildScripts { config: &CargoConfig, workspaces: &[&CargoWorkspace], progress: &dyn Fn(String), - workspace_root: &AbsPathBuf, + working_directory: &AbsPathBuf, ) -> io::Result> { assert_eq!(config.invocation_strategy, InvocationStrategy::Once); - let current_dir = match &config.invocation_location { - InvocationLocation::Root(root) => root, - InvocationLocation::Workspace => { - return Err(io::Error::new( - io::ErrorKind::Other, - "Cannot run build scripts from workspace with invocation strategy `once`", - )) - } - }; let cmd = Self::build_command( config, &Default::default(), // This is not gonna be used anyways, so just construct a dummy here - &ManifestPath::try_from(workspace_root.clone()).unwrap(), - current_dir, + &ManifestPath::try_from(working_directory.clone()).unwrap(), + working_directory, &Sysroot::empty(), )?; // NB: Cargo.toml could have been modified between `cargo metadata` and diff --git a/crates/project-model/src/cargo_workspace.rs b/crates/project-model/src/cargo_workspace.rs index 38eeedec62..7cc21bcf13 100644 --- a/crates/project-model/src/cargo_workspace.rs +++ b/crates/project-model/src/cargo_workspace.rs @@ -13,7 +13,7 @@ use serde_json::from_value; use span::Edition; use toolchain::Tool; -use crate::{utf8_stdout, InvocationLocation, ManifestPath, Sysroot}; +use crate::{utf8_stdout, ManifestPath, Sysroot}; use crate::{CfgOverrides, InvocationStrategy}; /// [`CargoWorkspace`] represents the logical structure of, well, a Cargo @@ -98,7 +98,6 @@ pub struct CargoConfig { /// Extra env vars to set when invoking the cargo command pub extra_env: FxHashMap, pub invocation_strategy: InvocationStrategy, - pub invocation_location: InvocationLocation, /// Optional path to use instead of `target` when building pub target_dir: Option, } @@ -242,6 +241,10 @@ impl TargetKind { pub fn is_executable(self) -> bool { matches!(self, TargetKind::Bin | TargetKind::Example) } + + pub fn is_proc_macro(self) -> bool { + matches!(self, TargetKind::Lib { is_proc_macro: true }) + } } // Deserialize helper for the cargo metadata @@ -252,6 +255,9 @@ struct PackageMetadata { } impl CargoWorkspace { + /// Fetches the metadata for the given `cargo_toml` manifest. + /// A successful result may contain another metadata error if the initial fetching failed but + /// the `--no-deps` retry succeeded. pub fn fetch_metadata( cargo_toml: &ManifestPath, current_dir: &AbsPath, @@ -259,7 +265,19 @@ impl CargoWorkspace { sysroot: &Sysroot, locked: bool, progress: &dyn Fn(String), - ) -> anyhow::Result { + ) -> anyhow::Result<(cargo_metadata::Metadata, Option)> { + Self::fetch_metadata_(cargo_toml, current_dir, config, sysroot, locked, false, progress) + } + + fn fetch_metadata_( + cargo_toml: &ManifestPath, + current_dir: &AbsPath, + config: &CargoConfig, + sysroot: &Sysroot, + locked: bool, + no_deps: bool, + progress: &dyn Fn(String), + ) -> anyhow::Result<(cargo_metadata::Metadata, Option)> { let targets = find_list_of_build_targets(config, cargo_toml, sysroot); let cargo = sysroot.tool(Tool::Cargo); @@ -314,6 +332,9 @@ impl CargoWorkspace { if locked { other_options.push("--locked".to_owned()); } + if no_deps { + other_options.push("--no-deps".to_owned()); + } meta.other_options(other_options); // FIXME: Fetching metadata is a slow process, as it might require @@ -321,19 +342,42 @@ impl CargoWorkspace { // unclear whether cargo itself supports it. progress("metadata".to_owned()); - (|| -> Result { + (|| -> anyhow::Result<(_, _)> { let output = meta.cargo_command().output()?; if !output.status.success() { - return Err(cargo_metadata::Error::CargoMetadata { + let error = cargo_metadata::Error::CargoMetadata { stderr: String::from_utf8(output.stderr)?, - }); + } + .into(); + if !no_deps { + // If we failed to fetch metadata with deps, try again without them. + // This makes r-a still work partially when offline. + if let Ok((metadata, _)) = Self::fetch_metadata_( + cargo_toml, + current_dir, + config, + sysroot, + locked, + true, + progress, + ) { + return Ok((metadata, Some(error))); + } + } + return Err(error); } let stdout = from_utf8(&output.stdout)? .lines() .find(|line| line.starts_with('{')) .ok_or(cargo_metadata::Error::NoJson)?; - cargo_metadata::MetadataCommand::parse(stdout) + Ok((cargo_metadata::MetadataCommand::parse(stdout)?, None)) })() + .map(|(metadata, error)| { + ( + metadata, + error.map(|e| e.context(format!("Failed to run `{:?}`", meta.cargo_command()))), + ) + }) .with_context(|| format!("Failed to run `{:?}`", meta.cargo_command())) } @@ -431,8 +475,7 @@ impl CargoWorkspace { pkg_data.targets.push(tgt); } } - let resolve = meta.resolve.expect("metadata executed with deps"); - for mut node in resolve.nodes { + for mut node in meta.resolve.map_or_else(Vec::new, |it| it.nodes) { let &source = pkg_by_id.get(&node.id).unwrap(); node.deps.sort_by(|a, b| a.pkg.cmp(&b.pkg)); let dependencies = node diff --git a/crates/project-model/src/env.rs b/crates/project-model/src/env.rs index ac7246acc5..ff9d2035f6 100644 --- a/crates/project-model/src/env.rs +++ b/crates/project-model/src/env.rs @@ -3,7 +3,7 @@ use base_db::Env; use rustc_hash::FxHashMap; use toolchain::Tool; -use crate::{utf8_stdout, ManifestPath, PackageData, Sysroot, TargetKind}; +use crate::{utf8_stdout, CargoWorkspace, ManifestPath, PackageData, Sysroot, TargetKind}; /// Recreates the compile-time environment variables that Cargo sets. /// @@ -50,13 +50,23 @@ pub(crate) fn inject_cargo_env(env: &mut Env) { env.set("CARGO", Tool::Cargo.path().to_string()); } -pub(crate) fn inject_rustc_tool_env(env: &mut Env, cargo_name: &str, kind: TargetKind) { +pub(crate) fn inject_rustc_tool_env( + env: &mut Env, + cargo: &CargoWorkspace, + cargo_name: &str, + kind: TargetKind, +) { _ = kind; // FIXME // if kind.is_executable() { // env.set("CARGO_BIN_NAME", cargo_name); // } env.set("CARGO_CRATE_NAME", cargo_name.replace('-', "_")); + // NOTE: Technically we should set this for all crates, but that will worsen the deduplication + // logic so for now just keeping it proc-macros ought to be fine. + if kind.is_proc_macro() { + env.set("CARGO_RUSTC_CURRENT_DIR", cargo.manifest_path().parent().to_string()); + } } pub(crate) fn cargo_config_env( diff --git a/crates/project-model/src/lib.rs b/crates/project-model/src/lib.rs index 4fa70508bb..b8ac55ed0d 100644 --- a/crates/project-model/src/lib.rs +++ b/crates/project-model/src/lib.rs @@ -186,20 +186,13 @@ fn utf8_stdout(mut cmd: Command) -> anyhow::Result { Ok(stdout.trim().to_owned()) } -#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] +#[derive(Clone, Debug, Default, PartialEq, Eq)] pub enum InvocationStrategy { Once, #[default] PerWorkspace, } -#[derive(Clone, Debug, Default, PartialEq, Eq)] -pub enum InvocationLocation { - Root(AbsPathBuf), - #[default] - Workspace, -} - /// A set of cfg-overrides per crate. #[derive(Default, Debug, Clone, Eq, PartialEq)] pub struct CfgOverrides { diff --git a/crates/project-model/src/project_json.rs b/crates/project-model/src/project_json.rs index 7dea0c3839..a09c7a77ab 100644 --- a/crates/project-model/src/project_json.rs +++ b/crates/project-model/src/project_json.rs @@ -52,7 +52,7 @@ use base_db::{CrateDisplayName, CrateName}; use cfg::CfgAtom; use paths::{AbsPath, AbsPathBuf, Utf8PathBuf}; -use rustc_hash::FxHashMap; +use rustc_hash::{FxHashMap, FxHashSet}; use serde::{de, Deserialize, Serialize}; use span::Edition; @@ -122,6 +122,25 @@ impl ProjectJson { None => None, }; + let cfg = crate_data + .cfg_groups + .iter() + .flat_map(|cfg_extend| { + let cfg_group = data.cfg_groups.get(cfg_extend); + match cfg_group { + Some(cfg_group) => cfg_group.0.iter().cloned(), + None => { + tracing::error!( + "Unknown cfg group `{cfg_extend}` in crate `{}`", + crate_data.display_name.as_deref().unwrap_or(""), + ); + [].iter().cloned() + } + } + }) + .chain(crate_data.cfg.0) + .collect(); + Crate { display_name: crate_data .display_name @@ -131,7 +150,7 @@ impl ProjectJson { edition: crate_data.edition.into(), version: crate_data.version.as_ref().map(ToString::to_string), deps: crate_data.deps, - cfg: crate_data.cfg, + cfg, target: crate_data.target, env: crate_data.env, proc_macro_dylib_path: crate_data @@ -306,11 +325,17 @@ pub enum RunnableKind { pub struct ProjectJsonData { sysroot: Option, sysroot_src: Option, + #[serde(default)] + cfg_groups: FxHashMap, crates: Vec, #[serde(default)] runnables: Vec, } +#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq, Default)] +#[serde(transparent)] +struct CfgList(#[serde(with = "cfg_")] Vec); + #[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)] struct CrateData { display_name: Option, @@ -320,8 +345,9 @@ struct CrateData { version: Option, deps: Vec, #[serde(default)] - #[serde(with = "cfg_")] - cfg: Vec, + cfg_groups: FxHashSet, + #[serde(default)] + cfg: CfgList, target: Option, #[serde(default)] env: FxHashMap, diff --git a/crates/project-model/src/sysroot.rs b/crates/project-model/src/sysroot.rs index 419fac3f41..19f4c35b5a 100644 --- a/crates/project-model/src/sysroot.rs +++ b/crates/project-model/src/sysroot.rs @@ -325,7 +325,7 @@ impl Sysroot { "nightly".to_owned(), ); - let mut res = match CargoWorkspace::fetch_metadata( + let (mut res, _) = match CargoWorkspace::fetch_metadata( &library_manifest, sysroot_src_dir, &cargo_config, @@ -372,18 +372,19 @@ impl Sysroot { .flatten() }; - let resolve = res.resolve.as_mut().expect("metadata executed with deps"); - resolve.nodes.retain_mut(|node| { - // Replace `rustc-std-workspace` crate with the actual one in the dependency list - node.deps.iter_mut().for_each(|dep| { - let real_pkg = patches.clone().find(|((_, fake_id), _)| *fake_id == dep.pkg); - if let Some((_, real)) = real_pkg { - dep.pkg = real; - } + if let Some(resolve) = res.resolve.as_mut() { + resolve.nodes.retain_mut(|node| { + // Replace `rustc-std-workspace` crate with the actual one in the dependency list + node.deps.iter_mut().for_each(|dep| { + let real_pkg = patches.clone().find(|((_, fake_id), _)| *fake_id == dep.pkg); + if let Some((_, real)) = real_pkg { + dep.pkg = real; + } + }); + // Remove this node if it's a fake one + !patches.clone().any(|((_, fake), _)| fake == node.id) }); - // Remove this node if it's a fake one - !patches.clone().any(|((_, fake), _)| fake == node.id) - }); + } // Remove the fake ones from the package list patches.map(|((idx, _), _)| idx).sorted().rev().for_each(|idx| { res.packages.remove(idx); diff --git a/crates/project-model/src/tests.rs b/crates/project-model/src/tests.rs index e3bc81e196..f540bb94c1 100644 --- a/crates/project-model/src/tests.rs +++ b/crates/project-model/src/tests.rs @@ -34,6 +34,7 @@ fn load_cargo_with_overrides( build_scripts: WorkspaceBuildScripts::default(), rustc: Err(None), cargo_config_extra_env: Default::default(), + error: None, }, cfg_overrides, sysroot: Sysroot::empty(), @@ -58,6 +59,7 @@ fn load_cargo_with_fake_sysroot( build_scripts: WorkspaceBuildScripts::default(), rustc: Err(None), cargo_config_extra_env: Default::default(), + error: None, }, sysroot: get_fake_sysroot(), rustc_cfg: Vec::new(), @@ -233,6 +235,12 @@ fn rust_project_hello_world_project_model() { ); } +#[test] +fn rust_project_cfg_groups() { + let (crate_graph, _proc_macros) = load_rust_project("cfg-groups.json"); + check_crate_graph(crate_graph, expect_file!["../test_data/output/rust_project_cfg_groups.txt"]); +} + #[test] fn rust_project_is_proc_macro_has_proc_macro_dep() { let (crate_graph, _proc_macros) = load_rust_project("is-proc-macro-project.json"); @@ -294,6 +302,7 @@ fn smoke_test_real_sysroot_cargo() { build_scripts: WorkspaceBuildScripts::default(), rustc: Err(None), cargo_config_extra_env: Default::default(), + error: None, }, sysroot, rustc_cfg: Vec::new(), diff --git a/crates/project-model/src/workspace.rs b/crates/project-model/src/workspace.rs index 5620dfade2..7834238ace 100644 --- a/crates/project-model/src/workspace.rs +++ b/crates/project-model/src/workspace.rs @@ -69,6 +69,8 @@ pub enum ProjectWorkspaceKind { Cargo { /// The workspace as returned by `cargo metadata`. cargo: CargoWorkspace, + /// Additional `cargo metadata` error. (only populated if retried fetching via `--no-deps` succeeded). + error: Option>, /// The build script results for the workspace. build_scripts: WorkspaceBuildScripts, /// The rustc workspace loaded for this workspace. An `Err(None)` means loading has been @@ -93,7 +95,7 @@ pub enum ProjectWorkspaceKind { /// The file in question. file: ManifestPath, /// Is this file a cargo script file? - cargo: Option<(CargoWorkspace, WorkspaceBuildScripts)>, + cargo: Option<(CargoWorkspace, WorkspaceBuildScripts, Option>)>, /// Environment variables set in the `.cargo/config` file. cargo_config_extra_env: FxHashMap, }, @@ -106,6 +108,7 @@ impl fmt::Debug for ProjectWorkspace { match kind { ProjectWorkspaceKind::Cargo { cargo, + error: _, build_scripts: _, rustc, cargo_config_extra_env, @@ -256,7 +259,7 @@ impl ProjectWorkspace { false, progress, ) { - Ok(meta) => { + Ok((meta, _error)) => { let workspace = CargoWorkspace::new(meta, cargo_toml.clone()); let buildscripts = WorkspaceBuildScripts::rustc_crates( &workspace, @@ -301,7 +304,7 @@ impl ProjectWorkspace { tracing::error!(%e, "failed fetching data layout for {cargo_toml:?} workspace"); } - let meta = CargoWorkspace::fetch_metadata( + let (meta, error) = CargoWorkspace::fetch_metadata( cargo_toml, cargo_toml.parent(), config, @@ -324,6 +327,7 @@ impl ProjectWorkspace { build_scripts: WorkspaceBuildScripts::default(), rustc, cargo_config_extra_env, + error: error.map(Arc::new), }, sysroot, rustc_cfg, @@ -404,10 +408,11 @@ impl ProjectWorkspace { let cargo_script = CargoWorkspace::fetch_metadata(detached_file, dir, config, &sysroot, false, &|_| ()) .ok() - .map(|ws| { + .map(|(ws, error)| { ( CargoWorkspace::new(ws, detached_file.clone()), WorkspaceBuildScripts::default(), + error.map(Arc::new), ) }); @@ -440,15 +445,14 @@ impl ProjectWorkspace { progress: &dyn Fn(String), ) -> anyhow::Result { match &self.kind { - ProjectWorkspaceKind::DetachedFile { cargo: Some((cargo, _)), .. } - | ProjectWorkspaceKind::Cargo { cargo, .. } => { + ProjectWorkspaceKind::DetachedFile { cargo: Some((cargo, _, None)), .. } + | ProjectWorkspaceKind::Cargo { cargo, error: None, .. } => { WorkspaceBuildScripts::run_for_workspace(config, cargo, progress, &self.sysroot) .with_context(|| { format!("Failed to run build scripts for {}", cargo.workspace_root()) }) } - ProjectWorkspaceKind::DetachedFile { cargo: None, .. } - | ProjectWorkspaceKind::Json { .. } => Ok(WorkspaceBuildScripts::default()), + _ => Ok(WorkspaceBuildScripts::default()), } } @@ -458,7 +462,7 @@ impl ProjectWorkspace { workspaces: &[ProjectWorkspace], config: &CargoConfig, progress: &dyn Fn(String), - workspace_root: &AbsPathBuf, + working_directory: &AbsPathBuf, ) -> Vec> { if matches!(config.invocation_strategy, InvocationStrategy::PerWorkspace) || config.run_build_script_command.is_none() @@ -473,13 +477,16 @@ impl ProjectWorkspace { _ => None, }) .collect(); - let outputs = - &mut match WorkspaceBuildScripts::run_once(config, &cargo_ws, progress, workspace_root) - { - Ok(it) => Ok(it.into_iter()), - // io::Error is not Clone? - Err(e) => Err(sync::Arc::new(e)), - }; + let outputs = &mut match WorkspaceBuildScripts::run_once( + config, + &cargo_ws, + progress, + working_directory, + ) { + Ok(it) => Ok(it.into_iter()), + // io::Error is not Clone? + Err(e) => Err(sync::Arc::new(e)), + }; workspaces .iter() @@ -498,7 +505,7 @@ impl ProjectWorkspace { pub fn set_build_scripts(&mut self, bs: WorkspaceBuildScripts) { match &mut self.kind { ProjectWorkspaceKind::Cargo { build_scripts, .. } - | ProjectWorkspaceKind::DetachedFile { cargo: Some((_, build_scripts)), .. } => { + | ProjectWorkspaceKind::DetachedFile { cargo: Some((_, build_scripts, _)), .. } => { *build_scripts = bs } _ => assert_eq!(bs, WorkspaceBuildScripts::default()), @@ -589,6 +596,7 @@ impl ProjectWorkspace { rustc, build_scripts, cargo_config_extra_env: _, + error: _, } => { cargo .packages() @@ -644,7 +652,7 @@ impl ProjectWorkspace { include: vec![file.to_path_buf()], exclude: Vec::new(), }) - .chain(cargo_script.iter().flat_map(|(cargo, build_scripts)| { + .chain(cargo_script.iter().flat_map(|(cargo, build_scripts, _)| { cargo.packages().map(|pkg| { let is_local = cargo[pkg].is_local; let pkg_root = cargo[pkg].manifest.parent().to_path_buf(); @@ -699,7 +707,7 @@ impl ProjectWorkspace { } ProjectWorkspaceKind::DetachedFile { cargo: cargo_script, .. } => { sysroot_package_len - + cargo_script.as_ref().map_or(1, |(cargo, _)| cargo.packages().len()) + + cargo_script.as_ref().map_or(1, |(cargo, _, _)| cargo.packages().len()) } } } @@ -729,6 +737,7 @@ impl ProjectWorkspace { rustc, build_scripts, cargo_config_extra_env: _, + error: _, } => ( cargo_to_crate_graph( load, @@ -742,7 +751,7 @@ impl ProjectWorkspace { sysroot, ), ProjectWorkspaceKind::DetachedFile { file, cargo: cargo_script, .. } => ( - if let Some((cargo, build_scripts)) = cargo_script { + if let Some((cargo, build_scripts, _)) = cargo_script { cargo_to_crate_graph( &mut |path| load(path), None, @@ -791,12 +800,14 @@ impl ProjectWorkspace { rustc, cargo_config_extra_env, build_scripts: _, + error: _, }, ProjectWorkspaceKind::Cargo { cargo: o_cargo, rustc: o_rustc, cargo_config_extra_env: o_cargo_config_extra_env, build_scripts: _, + error: _, }, ) => { cargo == o_cargo @@ -809,12 +820,12 @@ impl ProjectWorkspace { ( ProjectWorkspaceKind::DetachedFile { file, - cargo: Some((cargo_script, _)), + cargo: Some((cargo_script, _, _)), cargo_config_extra_env, }, ProjectWorkspaceKind::DetachedFile { file: o_file, - cargo: Some((o_cargo_script, _)), + cargo: Some((o_cargo_script, _, _)), cargo_config_extra_env: o_cargo_config_extra_env, }, ) => { @@ -1016,6 +1027,7 @@ fn cargo_to_crate_graph( let crate_id = add_target_crate_root( crate_graph, proc_macros, + cargo, pkg_data, build_data, cfg_options.clone(), @@ -1235,6 +1247,7 @@ fn handle_rustc_crates( let crate_id = add_target_crate_root( crate_graph, proc_macros, + rustc_workspace, &rustc_workspace[pkg], build_scripts.get_output(pkg), cfg_options.clone(), @@ -1294,6 +1307,7 @@ fn handle_rustc_crates( fn add_target_crate_root( crate_graph: &mut CrateGraph, proc_macros: &mut ProcMacroPaths, + cargo: &CargoWorkspace, pkg: &PackageData, build_data: Option<&BuildScriptOutput>, cfg_options: CfgOptions, @@ -1327,7 +1341,7 @@ fn add_target_crate_root( let mut env = Env::default(); inject_cargo_package_env(&mut env, pkg); inject_cargo_env(&mut env); - inject_rustc_tool_env(&mut env, cargo_name, kind); + inject_rustc_tool_env(&mut env, cargo, cargo_name, kind); if let Some(envs) = build_data.map(|it| &it.envs) { for (k, v) in envs { diff --git a/crates/project-model/test_data/cfg-groups.json b/crates/project-model/test_data/cfg-groups.json new file mode 100644 index 0000000000..29f83afd9d --- /dev/null +++ b/crates/project-model/test_data/cfg-groups.json @@ -0,0 +1,26 @@ +{ + "sysroot_src": null, + "cfg_groups": { + "group1": ["group1_cfg=\"some_config\"", "group1_other_cfg=\"other_config\""], + "group2": ["group2_cfg=\"yet_another_config\""] + }, + "crates": [ + { + "display_name": "hello_world", + "root_module": "$ROOT$src/lib.rs", + "edition": "2018", + "cfg_groups": ["group1", "group2"], + "deps": [], + "is_workspace_member": true + }, + { + "display_name": "other_crate", + "root_module": "$ROOT$src/lib.rs", + "edition": "2018", + "cfg_groups": ["group2"], + "cfg": ["group2_cfg=\"fourth_config\"", "unrelated_cfg"], + "deps": [], + "is_workspace_member": true + } + ] +} diff --git a/crates/project-model/test_data/output/rust_project_cfg_groups.txt b/crates/project-model/test_data/output/rust_project_cfg_groups.txt new file mode 100644 index 0000000000..8261e5a2d9 --- /dev/null +++ b/crates/project-model/test_data/output/rust_project_cfg_groups.txt @@ -0,0 +1,545 @@ +{ + 0: CrateData { + root_file_id: FileId( + 1, + ), + edition: Edition2021, + version: None, + display_name: Some( + CrateDisplayName { + crate_name: CrateName( + "alloc", + ), + canonical_name: "alloc", + }, + ), + cfg_options: CfgOptions( + [ + "debug_assertions", + "miri", + ], + ), + potential_cfg_options: None, + env: Env { + entries: {}, + }, + dependencies: [ + Dependency { + crate_id: Idx::(1), + name: CrateName( + "core", + ), + prelude: true, + sysroot: false, + }, + ], + origin: Lang( + Alloc, + ), + is_proc_macro: false, + }, + 1: CrateData { + root_file_id: FileId( + 2, + ), + edition: Edition2021, + version: None, + display_name: Some( + CrateDisplayName { + crate_name: CrateName( + "core", + ), + canonical_name: "core", + }, + ), + cfg_options: CfgOptions( + [ + "debug_assertions", + "miri", + ], + ), + potential_cfg_options: None, + env: Env { + entries: {}, + }, + dependencies: [], + origin: Lang( + Core, + ), + is_proc_macro: false, + }, + 2: CrateData { + root_file_id: FileId( + 3, + ), + edition: Edition2021, + version: None, + display_name: Some( + CrateDisplayName { + crate_name: CrateName( + "panic_abort", + ), + canonical_name: "panic_abort", + }, + ), + cfg_options: CfgOptions( + [ + "debug_assertions", + "miri", + ], + ), + potential_cfg_options: None, + env: Env { + entries: {}, + }, + dependencies: [], + origin: Lang( + Other, + ), + is_proc_macro: false, + }, + 3: CrateData { + root_file_id: FileId( + 4, + ), + edition: Edition2021, + version: None, + display_name: Some( + CrateDisplayName { + crate_name: CrateName( + "panic_unwind", + ), + canonical_name: "panic_unwind", + }, + ), + cfg_options: CfgOptions( + [ + "debug_assertions", + "miri", + ], + ), + potential_cfg_options: None, + env: Env { + entries: {}, + }, + dependencies: [], + origin: Lang( + Other, + ), + is_proc_macro: false, + }, + 4: CrateData { + root_file_id: FileId( + 5, + ), + edition: Edition2021, + version: None, + display_name: Some( + CrateDisplayName { + crate_name: CrateName( + "proc_macro", + ), + canonical_name: "proc_macro", + }, + ), + cfg_options: CfgOptions( + [ + "debug_assertions", + "miri", + ], + ), + potential_cfg_options: None, + env: Env { + entries: {}, + }, + dependencies: [ + Dependency { + crate_id: Idx::(6), + name: CrateName( + "std", + ), + prelude: true, + sysroot: false, + }, + Dependency { + crate_id: Idx::(1), + name: CrateName( + "core", + ), + prelude: true, + sysroot: false, + }, + ], + origin: Lang( + ProcMacro, + ), + is_proc_macro: false, + }, + 5: CrateData { + root_file_id: FileId( + 6, + ), + edition: Edition2021, + version: None, + display_name: Some( + CrateDisplayName { + crate_name: CrateName( + "profiler_builtins", + ), + canonical_name: "profiler_builtins", + }, + ), + cfg_options: CfgOptions( + [ + "debug_assertions", + "miri", + ], + ), + potential_cfg_options: None, + env: Env { + entries: {}, + }, + dependencies: [], + origin: Lang( + Other, + ), + is_proc_macro: false, + }, + 6: CrateData { + root_file_id: FileId( + 7, + ), + edition: Edition2021, + version: None, + display_name: Some( + CrateDisplayName { + crate_name: CrateName( + "std", + ), + canonical_name: "std", + }, + ), + cfg_options: CfgOptions( + [ + "debug_assertions", + "miri", + ], + ), + potential_cfg_options: None, + env: Env { + entries: {}, + }, + dependencies: [ + Dependency { + crate_id: Idx::(0), + name: CrateName( + "alloc", + ), + prelude: true, + sysroot: false, + }, + Dependency { + crate_id: Idx::(3), + name: CrateName( + "panic_unwind", + ), + prelude: true, + sysroot: false, + }, + Dependency { + crate_id: Idx::(2), + name: CrateName( + "panic_abort", + ), + prelude: true, + sysroot: false, + }, + Dependency { + crate_id: Idx::(1), + name: CrateName( + "core", + ), + prelude: true, + sysroot: false, + }, + Dependency { + crate_id: Idx::(5), + name: CrateName( + "profiler_builtins", + ), + prelude: true, + sysroot: false, + }, + Dependency { + crate_id: Idx::(9), + name: CrateName( + "unwind", + ), + prelude: true, + sysroot: false, + }, + Dependency { + crate_id: Idx::(7), + name: CrateName( + "std_detect", + ), + prelude: true, + sysroot: false, + }, + Dependency { + crate_id: Idx::(8), + name: CrateName( + "test", + ), + prelude: true, + sysroot: false, + }, + ], + origin: Lang( + Std, + ), + is_proc_macro: false, + }, + 7: CrateData { + root_file_id: FileId( + 8, + ), + edition: Edition2021, + version: None, + display_name: Some( + CrateDisplayName { + crate_name: CrateName( + "std_detect", + ), + canonical_name: "std_detect", + }, + ), + cfg_options: CfgOptions( + [ + "debug_assertions", + "miri", + ], + ), + potential_cfg_options: None, + env: Env { + entries: {}, + }, + dependencies: [], + origin: Lang( + Other, + ), + is_proc_macro: false, + }, + 8: CrateData { + root_file_id: FileId( + 9, + ), + edition: Edition2021, + version: None, + display_name: Some( + CrateDisplayName { + crate_name: CrateName( + "test", + ), + canonical_name: "test", + }, + ), + cfg_options: CfgOptions( + [ + "debug_assertions", + "miri", + ], + ), + potential_cfg_options: None, + env: Env { + entries: {}, + }, + dependencies: [], + origin: Lang( + Test, + ), + is_proc_macro: false, + }, + 9: CrateData { + root_file_id: FileId( + 10, + ), + edition: Edition2021, + version: None, + display_name: Some( + CrateDisplayName { + crate_name: CrateName( + "unwind", + ), + canonical_name: "unwind", + }, + ), + cfg_options: CfgOptions( + [ + "debug_assertions", + "miri", + ], + ), + potential_cfg_options: None, + env: Env { + entries: {}, + }, + dependencies: [], + origin: Lang( + Other, + ), + is_proc_macro: false, + }, + 10: CrateData { + root_file_id: FileId( + 11, + ), + edition: Edition2018, + version: None, + display_name: Some( + CrateDisplayName { + crate_name: CrateName( + "hello_world", + ), + canonical_name: "hello_world", + }, + ), + cfg_options: CfgOptions( + [ + "group1_cfg=some_config", + "group1_other_cfg=other_config", + "group2_cfg=yet_another_config", + "rust_analyzer", + ], + ), + potential_cfg_options: None, + env: Env { + entries: {}, + }, + dependencies: [ + Dependency { + crate_id: Idx::(1), + name: CrateName( + "core", + ), + prelude: true, + sysroot: true, + }, + Dependency { + crate_id: Idx::(0), + name: CrateName( + "alloc", + ), + prelude: false, + sysroot: true, + }, + Dependency { + crate_id: Idx::(6), + name: CrateName( + "std", + ), + prelude: true, + sysroot: true, + }, + Dependency { + crate_id: Idx::(8), + name: CrateName( + "test", + ), + prelude: false, + sysroot: true, + }, + Dependency { + crate_id: Idx::(4), + name: CrateName( + "proc_macro", + ), + prelude: false, + sysroot: true, + }, + ], + origin: Local { + repo: None, + name: Some( + "hello_world", + ), + }, + is_proc_macro: false, + }, + 11: CrateData { + root_file_id: FileId( + 12, + ), + edition: Edition2018, + version: None, + display_name: Some( + CrateDisplayName { + crate_name: CrateName( + "other_crate", + ), + canonical_name: "other_crate", + }, + ), + cfg_options: CfgOptions( + [ + "group2_cfg=fourth_config", + "group2_cfg=yet_another_config", + "rust_analyzer", + "unrelated_cfg", + ], + ), + potential_cfg_options: None, + env: Env { + entries: {}, + }, + dependencies: [ + Dependency { + crate_id: Idx::(1), + name: CrateName( + "core", + ), + prelude: true, + sysroot: true, + }, + Dependency { + crate_id: Idx::(0), + name: CrateName( + "alloc", + ), + prelude: false, + sysroot: true, + }, + Dependency { + crate_id: Idx::(6), + name: CrateName( + "std", + ), + prelude: true, + sysroot: true, + }, + Dependency { + crate_id: Idx::(8), + name: CrateName( + "test", + ), + prelude: false, + sysroot: true, + }, + Dependency { + crate_id: Idx::(4), + name: CrateName( + "proc_macro", + ), + prelude: false, + sysroot: true, + }, + ], + origin: Local { + repo: None, + name: Some( + "other_crate", + ), + }, + is_proc_macro: false, + }, +} \ No newline at end of file diff --git a/crates/rust-analyzer/src/bin/main.rs b/crates/rust-analyzer/src/bin/main.rs index 42953d3b83..21b481c1fa 100644 --- a/crates/rust-analyzer/src/bin/main.rs +++ b/crates/rust-analyzer/src/bin/main.rs @@ -227,7 +227,7 @@ fn run_server() -> anyhow::Result<()> { .filter(|workspaces| !workspaces.is_empty()) .unwrap_or_else(|| vec![root_path.clone()]); let mut config = - Config::new(root_path, capabilities, workspace_roots, visual_studio_code_version, None); + Config::new(root_path, capabilities, workspace_roots, visual_studio_code_version); if let Some(json) = initialization_options { let mut change = ConfigChange::default(); change.change_client_config(json); diff --git a/crates/rust-analyzer/src/cli.rs b/crates/rust-analyzer/src/cli.rs index 0bd6677b66..5eb6ff664f 100644 --- a/crates/rust-analyzer/src/cli.rs +++ b/crates/rust-analyzer/src/cli.rs @@ -21,7 +21,7 @@ use std::io::Read; use anyhow::Result; use hir::{Module, Name}; use hir_ty::db::HirDatabase; -use ide::AnalysisHost; +use ide::{AnalysisHost, Edition}; use itertools::Itertools; use vfs::Vfs; @@ -85,6 +85,6 @@ fn full_name_of_item(db: &dyn HirDatabase, module: Module, name: Name) -> String .rev() .filter_map(|it| it.name(db)) .chain(Some(name)) - .map(|it| it.display(db.upcast()).to_string()) + .map(|it| it.display(db.upcast(), Edition::LATEST).to_string()) .join("::") } diff --git a/crates/rust-analyzer/src/cli/analysis_stats.rs b/crates/rust-analyzer/src/cli/analysis_stats.rs index 06f4ba815d..44e56645ba 100644 --- a/crates/rust-analyzer/src/cli/analysis_stats.rs +++ b/crates/rust-analyzer/src/cli/analysis_stats.rs @@ -17,7 +17,7 @@ use hir_def::{ }; use hir_ty::{Interner, Substitution, TyExt, TypeFlags}; use ide::{ - Analysis, AnalysisHost, AnnotationConfig, DiagnosticsConfig, InlayFieldsToResolve, + Analysis, AnalysisHost, AnnotationConfig, DiagnosticsConfig, Edition, InlayFieldsToResolve, InlayHintsConfig, LineCol, RootDatabase, }; use ide_db::{ @@ -309,7 +309,7 @@ impl flags::AnalysisStats { let mut fail = 0; for &c in consts { all += 1; - let Err(error) = c.render_eval(db) else { + let Err(error) = c.render_eval(db, Edition::LATEST) else { continue; }; if verbosity.is_spammy() { @@ -442,6 +442,7 @@ impl flags::AnalysisStats { prefer_prelude: true, prefer_absolute: false, }, + Edition::LATEST, ) .unwrap(); syntax_hit_found |= trim(&original_text) == trim(&generated); @@ -563,7 +564,7 @@ impl flags::AnalysisStats { .rev() .filter_map(|it| it.name(db)) .chain(Some(body.name(db).unwrap_or_else(Name::missing))) - .map(|it| it.display(db).to_string()) + .map(|it| it.display(db, Edition::LATEST).to_string()) .join("::"); println!("Mir body for {full_name} failed due {e:?}"); } @@ -628,12 +629,14 @@ impl flags::AnalysisStats { .filter_map(|it| it.name(db)) .rev() .chain(Some(body_id.name(db).unwrap_or_else(Name::missing))) - .map(|it| it.display(db).to_string()), + .map(|it| it.display(db, Edition::LATEST).to_string()), ) .join("::") }; if let Some(only_name) = self.only.as_deref() { - if name.display(db).to_string() != only_name && full_name() != only_name { + if name.display(db, Edition::LATEST).to_string() != only_name + && full_name() != only_name + { continue; } } @@ -687,7 +690,10 @@ impl flags::AnalysisStats { end.col, )); } else { - bar.println(format!("{}: Unknown type", name.display(db))); + bar.println(format!( + "{}: Unknown type", + name.display(db, Edition::LATEST) + )); } } true @@ -708,17 +714,20 @@ impl flags::AnalysisStats { start.col, end.line + 1, end.col, - ty.display(db) + ty.display(db, Edition::LATEST) )); } else { - bar.println(format!("unknown location: {}", ty.display(db))); + bar.println(format!( + "unknown location: {}", + ty.display(db, Edition::LATEST) + )); } } if unknown_or_partial && self.output == Some(OutputFormat::Csv) { println!( r#"{},type,"{}""#, location_csv_expr(db, vfs, &sm(), expr_id), - ty.display(db) + ty.display(db, Edition::LATEST) ); } if let Some(mismatch) = inference_result.type_mismatch_for_expr(expr_id) { @@ -733,15 +742,15 @@ impl flags::AnalysisStats { start.col, end.line + 1, end.col, - mismatch.expected.display(db), - mismatch.actual.display(db) + mismatch.expected.display(db, Edition::LATEST), + mismatch.actual.display(db, Edition::LATEST) )); } else { bar.println(format!( "{}: Expected {}, got {}", - name.display(db), - mismatch.expected.display(db), - mismatch.actual.display(db) + name.display(db, Edition::LATEST), + mismatch.expected.display(db, Edition::LATEST), + mismatch.actual.display(db, Edition::LATEST) )); } } @@ -749,8 +758,8 @@ impl flags::AnalysisStats { println!( r#"{},mismatch,"{}","{}""#, location_csv_expr(db, vfs, &sm(), expr_id), - mismatch.expected.display(db), - mismatch.actual.display(db) + mismatch.expected.display(db, Edition::LATEST), + mismatch.actual.display(db, Edition::LATEST) ); } } @@ -785,7 +794,10 @@ impl flags::AnalysisStats { end.col, )); } else { - bar.println(format!("{}: Unknown type", name.display(db))); + bar.println(format!( + "{}: Unknown type", + name.display(db, Edition::LATEST) + )); } } true @@ -806,17 +818,20 @@ impl flags::AnalysisStats { start.col, end.line + 1, end.col, - ty.display(db) + ty.display(db, Edition::LATEST) )); } else { - bar.println(format!("unknown location: {}", ty.display(db))); + bar.println(format!( + "unknown location: {}", + ty.display(db, Edition::LATEST) + )); } } if unknown_or_partial && self.output == Some(OutputFormat::Csv) { println!( r#"{},type,"{}""#, location_csv_pat(db, vfs, &sm(), pat_id), - ty.display(db) + ty.display(db, Edition::LATEST) ); } if let Some(mismatch) = inference_result.type_mismatch_for_pat(pat_id) { @@ -830,15 +845,15 @@ impl flags::AnalysisStats { start.col, end.line + 1, end.col, - mismatch.expected.display(db), - mismatch.actual.display(db) + mismatch.expected.display(db, Edition::LATEST), + mismatch.actual.display(db, Edition::LATEST) )); } else { bar.println(format!( "{}: Expected {}, got {}", - name.display(db), - mismatch.expected.display(db), - mismatch.actual.display(db) + name.display(db, Edition::LATEST), + mismatch.expected.display(db, Edition::LATEST), + mismatch.actual.display(db, Edition::LATEST) )); } } @@ -846,8 +861,8 @@ impl flags::AnalysisStats { println!( r#"{},mismatch,"{}","{}""#, location_csv_pat(db, vfs, &sm(), pat_id), - mismatch.expected.display(db), - mismatch.actual.display(db) + mismatch.expected.display(db, Edition::LATEST), + mismatch.actual.display(db, Edition::LATEST) ); } } @@ -923,12 +938,16 @@ impl flags::AnalysisStats { .filter_map(|it| it.name(db)) .rev() .chain(Some(body_id.name(db).unwrap_or_else(Name::missing))) - .map(|it| it.display(db).to_string()), + .map(|it| it.display(db, Edition::LATEST).to_string()), ) .join("::") }; if let Some(only_name) = self.only.as_deref() { - if body_id.name(db).unwrap_or_else(Name::missing).display(db).to_string() + if body_id + .name(db) + .unwrap_or_else(Name::missing) + .display(db, Edition::LATEST) + .to_string() != only_name && full_name() != only_name { diff --git a/crates/rust-analyzer/src/cli/flags.rs b/crates/rust-analyzer/src/cli/flags.rs index 2a3e74c680..16d90de661 100644 --- a/crates/rust-analyzer/src/cli/flags.rs +++ b/crates/rust-analyzer/src/cli/flags.rs @@ -138,6 +138,9 @@ xflags::xflags! { cmd lsif { required path: PathBuf + + /// Exclude code from vendored libraries from the resulting index. + optional --exclude-vendored-libraries } cmd scip { @@ -148,6 +151,9 @@ xflags::xflags! { /// A path to an json configuration file that can be used to customize cargo behavior. optional --config-path config_path: PathBuf + + /// Exclude code from vendored libraries from the resulting index. + optional --exclude-vendored-libraries } } } @@ -259,6 +265,8 @@ pub struct Search { #[derive(Debug)] pub struct Lsif { pub path: PathBuf, + + pub exclude_vendored_libraries: bool, } #[derive(Debug)] @@ -267,6 +275,7 @@ pub struct Scip { pub output: Option, pub config_path: Option, + pub exclude_vendored_libraries: bool, } impl RustAnalyzer { diff --git a/crates/rust-analyzer/src/cli/lsif.rs b/crates/rust-analyzer/src/cli/lsif.rs index 016f0edc2d..89fe712ced 100644 --- a/crates/rust-analyzer/src/cli/lsif.rs +++ b/crates/rust-analyzer/src/cli/lsif.rs @@ -5,7 +5,7 @@ use std::time::Instant; use ide::{ Analysis, AnalysisHost, FileId, FileRange, MonikerKind, PackageInformation, RootDatabase, - StaticIndex, StaticIndexedFile, TokenId, TokenStaticData, + StaticIndex, StaticIndexedFile, TokenId, TokenStaticData, VendoredLibrariesConfig, }; use ide_db::{line_index::WideEncoding, LineIndexDatabase}; use load_cargo::{load_workspace, LoadCargoConfig, ProcMacroServerChoice}; @@ -296,7 +296,13 @@ impl flags::Lsif { let db = host.raw_database(); let analysis = host.analysis(); - let si = StaticIndex::compute(&analysis, &path.clone().into()); + let vendored_libs_config = if self.exclude_vendored_libraries { + VendoredLibrariesConfig::Excluded + } else { + VendoredLibrariesConfig::Included { workspace_root: &path.clone().into() } + }; + + let si = StaticIndex::compute(&analysis, vendored_libs_config); let mut lsif = LsifManager::new(&analysis, db, &vfs); lsif.add_vertex(lsif::Vertex::MetaData(lsif::MetaData { diff --git a/crates/rust-analyzer/src/cli/scip.rs b/crates/rust-analyzer/src/cli/scip.rs index 2fc0ef8d4d..ceb8534fdf 100644 --- a/crates/rust-analyzer/src/cli/scip.rs +++ b/crates/rust-analyzer/src/cli/scip.rs @@ -4,7 +4,7 @@ use std::{path::PathBuf, time::Instant}; use ide::{ AnalysisHost, LineCol, MonikerDescriptorKind, MonikerResult, StaticIndex, StaticIndexedFile, - SymbolInformationKind, TextRange, TokenId, + SymbolInformationKind, TextRange, TokenId, VendoredLibrariesConfig, }; use ide_db::LineIndexDatabase; use load_cargo::{load_workspace_at, LoadCargoConfig, ProcMacroServerChoice}; @@ -37,7 +37,6 @@ impl flags::Scip { lsp_types::ClientCapabilities::default(), vec![], None, - None, ); if let Some(p) = self.config_path { @@ -63,7 +62,13 @@ impl flags::Scip { let db = host.raw_database(); let analysis = host.analysis(); - let si = StaticIndex::compute(&analysis, &root.clone().into()); + let vendored_libs_config = if self.exclude_vendored_libraries { + VendoredLibrariesConfig::Excluded + } else { + VendoredLibrariesConfig::Included { workspace_root: &root.clone().into() } + }; + + let si = StaticIndex::compute(&analysis, vendored_libs_config); let metadata = scip_types::Metadata { version: scip_types::ProtocolVersion::UnspecifiedProtocolVersion.into(), @@ -137,7 +142,9 @@ impl flags::Scip { let mut symbol_roles = Default::default(); if let Some(def) = token.definition { - if def.range == text_range { + // if the the range of the def and the range of the token are the same, this must be the definition. + // they also must be in the same file. See https://github.com/rust-lang/rust-analyzer/pull/17988 + if def.file_id == file_id && def.range == text_range { symbol_roles |= scip_types::SymbolRole::Definition as i32; } @@ -352,8 +359,12 @@ mod test { let (host, position) = position(ra_fixture); let analysis = host.analysis(); - let si = - StaticIndex::compute(&analysis, &VfsPath::new_virtual_path("/workspace".to_owned())); + let si = StaticIndex::compute( + &analysis, + VendoredLibrariesConfig::Included { + workspace_root: &VfsPath::new_virtual_path("/workspace".to_owned()), + }, + ); let FilePosition { file_id, offset } = position; @@ -617,8 +628,12 @@ pub mod example_mod { host.raw_database_mut().apply_change(change_fixture.change); let analysis = host.analysis(); - let si = - StaticIndex::compute(&analysis, &VfsPath::new_virtual_path("/workspace".to_owned())); + let si = StaticIndex::compute( + &analysis, + VendoredLibrariesConfig::Included { + workspace_root: &VfsPath::new_virtual_path("/workspace".to_owned()), + }, + ); let file = si.files.first().unwrap(); let (_, token_id) = file.tokens.first().unwrap(); diff --git a/crates/rust-analyzer/src/command.rs b/crates/rust-analyzer/src/command.rs index f1009eb466..b19a1b8d16 100644 --- a/crates/rust-analyzer/src/command.rs +++ b/crates/rust-analyzer/src/command.rs @@ -148,7 +148,6 @@ impl CommandHandle { } pub(crate) fn join(mut self) -> io::Result<()> { - let _ = self.child.0.kill(); let exit_status = self.child.0.wait()?; let (read_at_least_one_message, error) = self.thread.join()?; if read_at_least_one_message || exit_status.success() { diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index 02f5d75136..2889af844b 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -3,10 +3,13 @@ //! Of particular interest is the `feature_flags` hash map: while other fields //! configure the server itself, feature flags are passed into analysis, and //! tweak things like automatic insertion of `()` in completions. -use std::{fmt, iter, ops::Not, sync::OnceLock}; +use std::{ + env, fmt, iter, + ops::Not, + sync::{LazyLock, OnceLock}, +}; use cfg::{CfgAtom, CfgDiff}; -use dirs::config_dir; use hir::Symbol; use ide::{ AssistConfig, CallableSnippets, CompletionConfig, DiagnosticsConfig, ExprFillDefaultMode, @@ -80,16 +83,11 @@ config_data! { pub(crate) cargo_autoreload: bool = true, /// Run build scripts (`build.rs`) for more precise code analysis. cargo_buildScripts_enable: bool = true, - /// Specifies the working directory for running build scripts. - /// - "workspace": run build scripts for a workspace in the workspace's root directory. - /// This is incompatible with `#rust-analyzer.cargo.buildScripts.invocationStrategy#` set to `once`. - /// - "root": run build scripts in the project's root directory. - /// This config only has an effect when `#rust-analyzer.cargo.buildScripts.overrideCommand#` - /// is set. - cargo_buildScripts_invocationLocation: InvocationLocation = InvocationLocation::Workspace, /// Specifies the invocation strategy to use when running the build scripts command. - /// If `per_workspace` is set, the command will be executed for each workspace. - /// If `once` is set, the command will be executed once. + /// If `per_workspace` is set, the command will be executed for each Rust workspace with the + /// workspace as the working directory. + /// If `once` is set, the command will be executed once with the opened project as the + /// working directory. /// This config only has an effect when `#rust-analyzer.cargo.buildScripts.overrideCommand#` /// is set. cargo_buildScripts_invocationStrategy: InvocationStrategy = InvocationStrategy::PerWorkspace, @@ -101,8 +99,7 @@ config_data! { /// If there are multiple linked projects/workspaces, this command is invoked for /// each of them, with the working directory being the workspace root /// (i.e., the folder containing the `Cargo.toml`). This can be overwritten - /// by changing `#rust-analyzer.cargo.buildScripts.invocationStrategy#` and - /// `#rust-analyzer.cargo.buildScripts.invocationLocation#`. + /// by changing `#rust-analyzer.cargo.buildScripts.invocationStrategy#`. /// /// By default, a cargo invocation will be constructed for the configured /// targets and features, with the following base command line: @@ -182,14 +179,6 @@ config_data! { /// /// For example for `cargo check`: `dead_code`, `unused_imports`, `unused_variables`,... check_ignore: FxHashSet = FxHashSet::default(), - /// Specifies the working directory for running checks. - /// - "workspace": run checks for workspaces in the corresponding workspaces' root directories. - // FIXME: Ideally we would support this in some way - /// This falls back to "root" if `#rust-analyzer.check.invocationStrategy#` is set to `once`. - /// - "root": run checks in the project's root directory. - /// This config only has an effect when `#rust-analyzer.check.overrideCommand#` - /// is set. - check_invocationLocation | checkOnSave_invocationLocation: InvocationLocation = InvocationLocation::Workspace, /// Specifies the invocation strategy to use when running the check command. /// If `per_workspace` is set, the command will be executed for each workspace. /// If `once` is set, the command will be executed once. @@ -212,8 +201,7 @@ config_data! { /// If there are multiple linked projects/workspaces, this command is invoked for /// each of them, with the working directory being the workspace root /// (i.e., the folder containing the `Cargo.toml`). This can be overwritten - /// by changing `#rust-analyzer.check.invocationStrategy#` and - /// `#rust-analyzer.check.invocationLocation#`. + /// by changing `#rust-analyzer.check.invocationStrategy#`. /// /// If `$saved_file` is part of the command, rust-analyzer will pass /// the absolute path of the saved file to the provided command. This is @@ -309,18 +297,6 @@ config_data! { /// This option does not take effect until rust-analyzer is restarted. rustc_source: Option = None, - /// Additional arguments to `rustfmt`. - rustfmt_extraArgs: Vec = vec![], - /// Advanced option, fully override the command rust-analyzer uses for - /// formatting. This should be the equivalent of `rustfmt` here, and - /// not that of `cargo fmt`. The file contents will be passed on the - /// standard input and the formatted result will be read from the - /// standard output. - rustfmt_overrideCommand: Option> = None, - /// Enables the use of rustfmt's unstable range formatting command for the - /// `textDocument/rangeFormatting` request. The rustfmt option is unstable and only - /// available on a nightly build. - rustfmt_rangeFormatting_enable: bool = false, /// Enables automatic discovery of projects using [`DiscoverWorkspaceConfig::command`]. /// @@ -451,6 +427,25 @@ config_data! { } } +config_data! { + workspace: struct WorkspaceDefaultConfigData <- WorkspaceConfigInput -> { + + /// Additional arguments to `rustfmt`. + rustfmt_extraArgs: Vec = vec![], + /// Advanced option, fully override the command rust-analyzer uses for + /// formatting. This should be the equivalent of `rustfmt` here, and + /// not that of `cargo fmt`. The file contents will be passed on the + /// standard input and the formatted result will be read from the + /// standard output. + rustfmt_overrideCommand: Option> = None, + /// Enables the use of rustfmt's unstable range formatting command for the + /// `textDocument/rangeFormatting` request. The rustfmt option is unstable and only + /// available on a nightly build. + rustfmt_rangeFormatting_enable: bool = false, + + } +} + config_data! { /// Configs that only make sense when they are set by a client. As such they can only be defined /// by setting them using client's settings (e.g `settings.json` on VS Code). @@ -750,10 +745,9 @@ pub enum RatomlFileKind { } #[derive(Debug, Clone)] -// FIXME @alibektas : Seems like a clippy warning of this sort should tell that combining different ConfigInputs into one enum was not a good idea. #[allow(clippy::large_enum_variant)] enum RatomlFile { - Workspace(GlobalLocalConfigInput), + Workspace(WorkspaceLocalConfigInput), Crate(LocalConfigInput), } @@ -772,18 +766,8 @@ pub struct Config { /// by receiving a `lsp_types::notification::DidChangeConfiguration`. client_config: (FullConfigInput, ConfigErrors), - /// Path to the root configuration file. This can be seen as a generic way to define what would be `$XDG_CONFIG_HOME/rust-analyzer/rust-analyzer.toml` in Linux. - /// If not specified by init of a `Config` object this value defaults to : - /// - /// |Platform | Value | Example | - /// | ------- | ------------------------------------- | ---------------------------------------- | - /// | Linux | `$XDG_CONFIG_HOME` or `$HOME`/.config | /home/alice/.config | - /// | macOS | `$HOME`/Library/Application Support | /Users/Alice/Library/Application Support | - /// | Windows | `{FOLDERID_RoamingAppData}` | C:\Users\Alice\AppData\Roaming | - user_config_path: VfsPath, - /// Config node whose values apply to **every** Rust project. - user_config: Option<(GlobalLocalConfigInput, ConfigErrors)>, + user_config: Option<(GlobalWorkspaceLocalConfigInput, ConfigErrors)>, ratoml_file: FxHashMap, @@ -809,8 +793,25 @@ impl std::ops::Deref for Config { } impl Config { - pub fn user_config_path(&self) -> &VfsPath { - &self.user_config_path + /// Path to the root configuration file. This can be seen as a generic way to define what would be `$XDG_CONFIG_HOME/rust-analyzer/rust-analyzer.toml` in Linux. + /// This path is equal to: + /// + /// |Platform | Value | Example | + /// | ------- | ------------------------------------- | ---------------------------------------- | + /// | Linux | `$XDG_CONFIG_HOME` or `$HOME`/.config | /home/alice/.config | + /// | macOS | `$HOME`/Library/Application Support | /Users/Alice/Library/Application Support | + /// | Windows | `{FOLDERID_RoamingAppData}` | C:\Users\Alice\AppData\Roaming | + pub fn user_config_path() -> Option<&'static AbsPath> { + static USER_CONFIG_PATH: LazyLock> = LazyLock::new(|| { + let user_config_path = if let Some(path) = env::var_os("__TEST_RA_USER_CONFIG_DIR") { + std::path::PathBuf::from(path) + } else { + dirs::config_dir()?.join("rust-analyzer") + } + .join("rust-analyzer.toml"); + Some(AbsPathBuf::assert_utf8(user_config_path)) + }); + USER_CONFIG_PATH.as_deref() } pub fn same_source_root_parent_map( @@ -833,13 +834,13 @@ impl Config { if let Ok(table) = toml::from_str(&change) { let mut toml_errors = vec![]; validate_toml_table( - GlobalLocalConfigInput::FIELDS, + GlobalWorkspaceLocalConfigInput::FIELDS, &table, &mut String::new(), &mut toml_errors, ); config.user_config = Some(( - GlobalLocalConfigInput::from_toml(table, &mut toml_errors), + GlobalWorkspaceLocalConfigInput::from_toml(table, &mut toml_errors), ConfigErrors( toml_errors .into_iter() @@ -968,7 +969,7 @@ impl Config { match toml::from_str(&text) { Ok(table) => { validate_toml_table( - GlobalLocalConfigInput::FIELDS, + WorkspaceLocalConfigInput::FIELDS, &table, &mut String::new(), &mut toml_errors, @@ -977,7 +978,7 @@ impl Config { source_root_id, ( RatomlFile::Workspace( - GlobalLocalConfigInput::from_toml( + WorkspaceLocalConfigInput::from_toml( table, &mut toml_errors, ), @@ -1015,7 +1016,7 @@ impl Config { config.source_root_parent_map = source_root_map; } - if config.check_command(None).is_empty() { + if config.check_command().is_empty() { config.validation_errors.0.push(Arc::new(ConfigErrorInner::Json { config_key: "/check/command".to_owned(), error: serde_json::Error::custom("expected a non-empty string"), @@ -1330,24 +1331,8 @@ impl Config { caps: lsp_types::ClientCapabilities, workspace_roots: Vec, visual_studio_code_version: Option, - user_config_path: Option, ) -> Self { static DEFAULT_CONFIG_DATA: OnceLock<&'static DefaultConfigData> = OnceLock::new(); - let user_config_path = if let Some(user_config_path) = user_config_path { - user_config_path.join("rust-analyzer").join("rust-analyzer.toml") - } else { - let p = config_dir() - .expect("A config dir is expected to existed on all platforms ra supports.") - .join("rust-analyzer") - .join("rust-analyzer.toml"); - Utf8PathBuf::from_path_buf(p).expect("Config dir expected to be abs.") - }; - - // A user config cannot be a virtual path as rust-analyzer cannot support watching changes in virtual paths. - // See `GlobalState::process_changes` to get more info. - // FIXME @alibektas : Temporary solution. I don't think this is right as at some point we may allow users to specify - // custom USER_CONFIG_PATHs which may also be relative. - let user_config_path = VfsPath::from(AbsPathBuf::assert(user_config_path)); Config { caps: ClientCapabilities::new(caps), @@ -1360,7 +1345,6 @@ impl Config { default_config: DEFAULT_CONFIG_DATA.get_or_init(|| Box::leak(Box::default())), source_root_parent_map: Arc::new(FxHashMap::default()), user_config: None, - user_config_path, detached_files: Default::default(), validation_errors: Default::default(), ratoml_file: Default::default(), @@ -1459,11 +1443,11 @@ impl Config { pub fn diagnostics(&self, source_root: Option) -> DiagnosticsConfig { DiagnosticsConfig { - enabled: *self.diagnostics_enable(source_root), + enabled: *self.diagnostics_enable(), proc_attr_macros_enabled: self.expand_proc_attr_macros(), proc_macros_enabled: *self.procMacro_enable(), - disable_experimental: !self.diagnostics_experimental_enable(source_root), - disabled: self.diagnostics_disabled(source_root).clone(), + disable_experimental: !self.diagnostics_experimental_enable(), + disabled: self.diagnostics_disabled().clone(), expr_fill_default: match self.assist_expressionFillDefault(source_root) { ExprFillDefaultDef::Todo => ExprFillDefaultMode::Todo, ExprFillDefaultDef::Default => ExprFillDefaultMode::Default, @@ -1473,7 +1457,7 @@ impl Config { prefer_no_std: self.imports_preferNoStd(source_root).to_owned(), prefer_prelude: self.imports_preferPrelude(source_root).to_owned(), prefer_absolute: self.imports_prefixExternPrelude(source_root).to_owned(), - style_lints: self.diagnostics_styleLints_enable(source_root).to_owned(), + style_lints: self.diagnostics_styleLints_enable().to_owned(), term_search_fuel: self.assist_termSearch_fuel(source_root).to_owned() as u64, term_search_borrowck: self.assist_termSearch_borrowcheck(source_root).to_owned(), } @@ -1666,11 +1650,11 @@ impl Config { } pub fn has_linked_projects(&self) -> bool { - !self.linkedProjects(None).is_empty() + !self.linkedProjects().is_empty() } pub fn linked_manifests(&self) -> impl Iterator + '_ { - self.linkedProjects(None).iter().filter_map(|it| match it { + self.linkedProjects().iter().filter_map(|it| match it { ManifestOrProjectJson::Manifest(p) => Some(&**p), // despite having a buildfile, using this variant as a manifest // will fail. @@ -1680,20 +1664,20 @@ impl Config { } pub fn has_linked_project_jsons(&self) -> bool { - self.linkedProjects(None) + self.linkedProjects() .iter() .any(|it| matches!(it, ManifestOrProjectJson::ProjectJson { .. })) } pub fn discover_workspace_config(&self) -> Option<&DiscoverWorkspaceConfig> { - self.workspace_discoverConfig(None).as_ref() + self.workspace_discoverConfig().as_ref() } pub fn linked_or_discovered_projects(&self) -> Vec { - match self.linkedProjects(None).as_slice() { + match self.linkedProjects().as_slice() { [] => { let exclude_dirs: Vec<_> = - self.files_excludeDirs(None).iter().map(|p| self.root_path.join(p)).collect(); + self.files_excludeDirs().iter().map(|p| self.root_path.join(p)).collect(); self.discovered_projects .iter() .filter(|project| { @@ -1728,48 +1712,48 @@ impl Config { } pub fn prefill_caches(&self) -> bool { - self.cachePriming_enable(None).to_owned() + self.cachePriming_enable().to_owned() } pub fn publish_diagnostics(&self) -> bool { - self.diagnostics_enable(None).to_owned() + self.diagnostics_enable().to_owned() } pub fn diagnostics_map(&self) -> DiagnosticsMapConfig { DiagnosticsMapConfig { - remap_prefix: self.diagnostics_remapPrefix(None).clone(), - warnings_as_info: self.diagnostics_warningsAsInfo(None).clone(), - warnings_as_hint: self.diagnostics_warningsAsHint(None).clone(), - check_ignore: self.check_ignore(None).clone(), + remap_prefix: self.diagnostics_remapPrefix().clone(), + warnings_as_info: self.diagnostics_warningsAsInfo().clone(), + warnings_as_hint: self.diagnostics_warningsAsHint().clone(), + check_ignore: self.check_ignore().clone(), } } pub fn extra_args(&self) -> &Vec { - self.cargo_extraArgs(None) + self.cargo_extraArgs() } pub fn extra_env(&self) -> &FxHashMap { - self.cargo_extraEnv(None) + self.cargo_extraEnv() } pub fn check_extra_args(&self) -> Vec { let mut extra_args = self.extra_args().clone(); - extra_args.extend_from_slice(self.check_extraArgs(None)); + extra_args.extend_from_slice(self.check_extraArgs()); extra_args } pub fn check_extra_env(&self) -> FxHashMap { - let mut extra_env = self.cargo_extraEnv(None).clone(); - extra_env.extend(self.check_extraEnv(None).clone()); + let mut extra_env = self.cargo_extraEnv().clone(); + extra_env.extend(self.check_extraEnv().clone()); extra_env } pub fn lru_parse_query_capacity(&self) -> Option { - self.lru_capacity(None).to_owned() + self.lru_capacity().to_owned() } pub fn lru_query_capacities_config(&self) -> Option<&FxHashMap, u16>> { - self.lru_query_capacities(None).is_empty().not().then(|| self.lru_query_capacities(None)) + self.lru_query_capacities().is_empty().not().then(|| self.lru_query_capacities()) } pub fn proc_macro_srv(&self) -> Option { @@ -1778,7 +1762,7 @@ impl Config { } pub fn ignored_proc_macros(&self) -> &FxHashMap, Box<[Box]>> { - self.procMacro_ignored(None) + self.procMacro_ignored() } pub fn expand_proc_macros(&self) -> bool { @@ -1793,11 +1777,7 @@ impl Config { } _ => FilesWatcher::Server, }, - exclude: self - .files_excludeDirs(None) - .iter() - .map(|it| self.root_path.join(it)) - .collect(), + exclude: self.files_excludeDirs().iter().map(|it| self.root_path.join(it)).collect(), } } @@ -1808,22 +1788,22 @@ impl Config { } pub fn cargo_autoreload_config(&self) -> bool { - self.cargo_autoreload(None).to_owned() + self.cargo_autoreload().to_owned() } pub fn run_build_scripts(&self) -> bool { - self.cargo_buildScripts_enable(None).to_owned() || self.procMacro_enable().to_owned() + self.cargo_buildScripts_enable().to_owned() || self.procMacro_enable().to_owned() } pub fn cargo(&self) -> CargoConfig { - let rustc_source = self.rustc_source(None).as_ref().map(|rustc_src| { + let rustc_source = self.rustc_source().as_ref().map(|rustc_src| { if rustc_src == "discover" { RustLibSource::Discover } else { RustLibSource::Path(self.root_path.join(rustc_src)) } }); - let sysroot = self.cargo_sysroot(None).as_ref().map(|sysroot| { + let sysroot = self.cargo_sysroot().as_ref().map(|sysroot| { if sysroot == "discover" { RustLibSource::Discover } else { @@ -1831,24 +1811,24 @@ impl Config { } }); let sysroot_src = - self.cargo_sysrootSrc(None).as_ref().map(|sysroot| self.root_path.join(sysroot)); + self.cargo_sysrootSrc().as_ref().map(|sysroot| self.root_path.join(sysroot)); CargoConfig { - all_targets: *self.cargo_allTargets(None), - features: match &self.cargo_features(None) { + all_targets: *self.cargo_allTargets(), + features: match &self.cargo_features() { CargoFeaturesDef::All => CargoFeatures::All, CargoFeaturesDef::Selected(features) => CargoFeatures::Selected { features: features.clone(), - no_default_features: self.cargo_noDefaultFeatures(None).to_owned(), + no_default_features: self.cargo_noDefaultFeatures().to_owned(), }, }, - target: self.cargo_target(None).clone(), + target: self.cargo_target().clone(), sysroot, sysroot_src, rustc_source, cfg_overrides: project_model::CfgOverrides { global: CfgDiff::new( - self.cargo_cfgs(None) + self.cargo_cfgs() .iter() .map(|(key, val)| match val { Some(val) => CfgAtom::KeyValue { @@ -1863,20 +1843,14 @@ impl Config { .unwrap(), selective: Default::default(), }, - wrap_rustc_in_build_scripts: *self.cargo_buildScripts_useRustcWrapper(None), - invocation_strategy: match self.cargo_buildScripts_invocationStrategy(None) { + wrap_rustc_in_build_scripts: *self.cargo_buildScripts_useRustcWrapper(), + invocation_strategy: match self.cargo_buildScripts_invocationStrategy() { InvocationStrategy::Once => project_model::InvocationStrategy::Once, InvocationStrategy::PerWorkspace => project_model::InvocationStrategy::PerWorkspace, }, - invocation_location: match self.cargo_buildScripts_invocationLocation(None) { - InvocationLocation::Root => { - project_model::InvocationLocation::Root(self.root_path.clone()) - } - InvocationLocation::Workspace => project_model::InvocationLocation::Workspace, - }, - run_build_script_command: self.cargo_buildScripts_overrideCommand(None).clone(), - extra_args: self.cargo_extraArgs(None).clone(), - extra_env: self.cargo_extraEnv(None).clone(), + run_build_script_command: self.cargo_buildScripts_overrideCommand().clone(), + extra_args: self.cargo_extraArgs().clone(), + extra_env: self.cargo_extraEnv().clone(), target_dir: self.target_dir_from_config(), } } @@ -1896,27 +1870,28 @@ impl Config { } pub fn flycheck_workspace(&self) -> bool { - *self.check_workspace(None) + *self.check_workspace() } pub(crate) fn cargo_test_options(&self) -> CargoOptions { CargoOptions { - target_triples: self.cargo_target(None).clone().into_iter().collect(), + target_triples: self.cargo_target().clone().into_iter().collect(), all_targets: false, - no_default_features: *self.cargo_noDefaultFeatures(None), - all_features: matches!(self.cargo_features(None), CargoFeaturesDef::All), - features: match self.cargo_features(None).clone() { + no_default_features: *self.cargo_noDefaultFeatures(), + all_features: matches!(self.cargo_features(), CargoFeaturesDef::All), + features: match self.cargo_features().clone() { CargoFeaturesDef::All => vec![], CargoFeaturesDef::Selected(it) => it, }, extra_args: self.extra_args().clone(), + extra_test_bin_args: self.runnables_extraTestBinaryArgs().clone(), extra_env: self.extra_env().clone(), target_dir: self.target_dir_from_config(), } } pub(crate) fn flycheck(&self) -> FlycheckConfig { - match &self.check_overrideCommand(None) { + match &self.check_overrideCommand() { Some(args) if !args.is_empty() => { let mut args = args.clone(); let command = args.remove(0); @@ -1924,52 +1899,43 @@ impl Config { command, args, extra_env: self.check_extra_env(), - invocation_strategy: match self.check_invocationStrategy(None) { + invocation_strategy: match self.check_invocationStrategy() { InvocationStrategy::Once => crate::flycheck::InvocationStrategy::Once, InvocationStrategy::PerWorkspace => { crate::flycheck::InvocationStrategy::PerWorkspace } }, - invocation_location: match self.check_invocationLocation(None) { - InvocationLocation::Root => { - crate::flycheck::InvocationLocation::Root(self.root_path.clone()) - } - InvocationLocation::Workspace => { - crate::flycheck::InvocationLocation::Workspace - } - }, } } Some(_) | None => FlycheckConfig::CargoCommand { - command: self.check_command(None).clone(), + command: self.check_command().clone(), options: CargoOptions { target_triples: self - .check_targets(None) + .check_targets() .clone() .and_then(|targets| match &targets.0[..] { [] => None, targets => Some(targets.into()), }) - .unwrap_or_else(|| self.cargo_target(None).clone().into_iter().collect()), - all_targets: self - .check_allTargets(None) - .unwrap_or(*self.cargo_allTargets(None)), + .unwrap_or_else(|| self.cargo_target().clone().into_iter().collect()), + all_targets: self.check_allTargets().unwrap_or(*self.cargo_allTargets()), no_default_features: self - .check_noDefaultFeatures(None) - .unwrap_or(*self.cargo_noDefaultFeatures(None)), + .check_noDefaultFeatures() + .unwrap_or(*self.cargo_noDefaultFeatures()), all_features: matches!( - self.check_features(None).as_ref().unwrap_or(self.cargo_features(None)), + self.check_features().as_ref().unwrap_or(self.cargo_features()), CargoFeaturesDef::All ), features: match self - .check_features(None) + .check_features() .clone() - .unwrap_or_else(|| self.cargo_features(None).clone()) + .unwrap_or_else(|| self.cargo_features().clone()) { CargoFeaturesDef::All => vec![], CargoFeaturesDef::Selected(it) => it, }, extra_args: self.check_extra_args(), + extra_test_bin_args: self.runnables_extraTestBinaryArgs().clone(), extra_env: self.check_extra_env(), target_dir: self.target_dir_from_config(), }, @@ -1979,7 +1945,7 @@ impl Config { } fn target_dir_from_config(&self) -> Option { - self.cargo_targetDir(None).as_ref().and_then(|target_dir| match target_dir { + self.cargo_targetDir().as_ref().and_then(|target_dir| match target_dir { TargetDirectory::UseSubdirectory(true) => { Some(Utf8PathBuf::from("target/rust-analyzer")) } @@ -1990,18 +1956,18 @@ impl Config { } pub fn check_on_save(&self) -> bool { - *self.checkOnSave(None) + *self.checkOnSave() } pub fn script_rebuild_on_save(&self) -> bool { - *self.cargo_buildScripts_rebuildOnSave(None) + *self.cargo_buildScripts_rebuildOnSave() } pub fn runnables(&self) -> RunnablesConfig { RunnablesConfig { - override_cargo: self.runnables_command(None).clone(), - cargo_extra_args: self.runnables_extraArgs(None).clone(), - extra_test_binary_args: self.runnables_extraTestBinaryArgs(None).clone(), + override_cargo: self.runnables_command().clone(), + cargo_extra_args: self.runnables_extraArgs().clone(), + extra_test_binary_args: self.runnables_extraTestBinaryArgs().clone(), } } @@ -2072,7 +2038,7 @@ impl Config { } pub fn prime_caches_num_threads(&self) -> usize { - match self.cachePriming_numThreads(None) { + match self.cachePriming_numThreads() { NumThreads::Concrete(0) | NumThreads::Physical => num_cpus::get_physical(), &NumThreads::Concrete(n) => n, NumThreads::Logical => num_cpus::get(), @@ -2348,13 +2314,6 @@ pub(crate) enum InvocationStrategy { #[derive(Serialize, Deserialize, Debug, Clone)] struct CheckOnSaveTargets(#[serde(with = "single_or_array")] Vec); -#[derive(Serialize, Deserialize, Debug, Clone)] -#[serde(rename_all = "snake_case")] -enum InvocationLocation { - Root, - Workspace, -} - #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "snake_case")] enum LifetimeElisionDef { @@ -2578,6 +2537,41 @@ macro_rules! _impl_for_config_data { )* } }; + (workspace, $( + $(#[doc=$doc:literal])* + $vis:vis $field:ident : $ty:ty = $default:expr, + )* + ) => { + impl Config { + $( + $($doc)* + #[allow(non_snake_case)] + $vis fn $field(&self, source_root: Option) -> &$ty { + let mut source_root = source_root.as_ref(); + while let Some(sr) = source_root { + if let Some((RatomlFile::Workspace(config), _)) = self.ratoml_file.get(&sr) { + if let Some(v) = config.workspace.$field.as_ref() { + return &v; + } + } + source_root = self.source_root_parent_map.get(&sr); + } + + if let Some(v) = self.client_config.0.workspace.$field.as_ref() { + return &v; + } + + if let Some((user_config, _)) = self.user_config.as_ref() { + if let Some(v) = user_config.workspace.$field.as_ref() { + return &v; + } + } + + &self.default_config.workspace.$field + } + )* + } + }; (global, $( $(#[doc=$doc:literal])* $vis:vis $field:ident : $ty:ty = $default:expr, @@ -2587,18 +2581,7 @@ macro_rules! _impl_for_config_data { $( $($doc)* #[allow(non_snake_case)] - $vis fn $field(&self, source_root : Option) -> &$ty { - let mut source_root = source_root.as_ref(); - while let Some(sr) = source_root { - if let Some((RatomlFile::Workspace(config), _)) = self.ratoml_file.get(&sr) { - if let Some(v) = config.global.$field.as_ref() { - return &v; - } - } - - source_root = self.source_root_parent_map.get(&sr); - } - + $vis fn $field(&self) -> &$ty { if let Some(v) = self.client_config.0.global.$field.as_ref() { return &v; } @@ -2736,6 +2719,7 @@ use _config_data as config_data; #[derive(Default, Debug, Clone)] struct DefaultConfigData { global: GlobalDefaultConfigData, + workspace: WorkspaceDefaultConfigData, local: LocalDefaultConfigData, client: ClientDefaultConfigData, } @@ -2746,6 +2730,7 @@ struct DefaultConfigData { #[derive(Debug, Clone, Default)] struct FullConfigInput { global: GlobalConfigInput, + workspace: WorkspaceConfigInput, local: LocalConfigInput, client: ClientConfigInput, } @@ -2759,6 +2744,7 @@ impl FullConfigInput { global: GlobalConfigInput::from_json(&mut json, error_sink), local: LocalConfigInput::from_json(&mut json, error_sink), client: ClientConfigInput::from_json(&mut json, error_sink), + workspace: WorkspaceConfigInput::from_json(&mut json, error_sink), } } @@ -2767,6 +2753,7 @@ impl FullConfigInput { GlobalConfigInput::schema_fields(&mut fields); LocalConfigInput::schema_fields(&mut fields); ClientConfigInput::schema_fields(&mut fields); + WorkspaceConfigInput::schema_fields(&mut fields); fields.sort_by_key(|&(x, ..)| x); fields .iter() @@ -2789,21 +2776,45 @@ impl FullConfigInput { /// some rust-analyzer.toml file or JSON blob. An empty rust-analyzer.toml corresponds to /// all fields being None. #[derive(Debug, Clone, Default)] -struct GlobalLocalConfigInput { +struct GlobalWorkspaceLocalConfigInput { global: GlobalConfigInput, local: LocalConfigInput, + workspace: WorkspaceConfigInput, } -impl GlobalLocalConfigInput { +impl GlobalWorkspaceLocalConfigInput { const FIELDS: &'static [&'static [&'static str]] = &[GlobalConfigInput::FIELDS, LocalConfigInput::FIELDS]; fn from_toml( toml: toml::Table, error_sink: &mut Vec<(String, toml::de::Error)>, - ) -> GlobalLocalConfigInput { - GlobalLocalConfigInput { + ) -> GlobalWorkspaceLocalConfigInput { + GlobalWorkspaceLocalConfigInput { global: GlobalConfigInput::from_toml(&toml, error_sink), local: LocalConfigInput::from_toml(&toml, error_sink), + workspace: WorkspaceConfigInput::from_toml(&toml, error_sink), + } + } +} + +/// Workspace and local config levels, all fields `Option`, to describe fields that are actually set by +/// some rust-analyzer.toml file or JSON blob. An empty rust-analyzer.toml corresponds to +/// all fields being None. +#[derive(Debug, Clone, Default)] +#[allow(dead_code)] +struct WorkspaceLocalConfigInput { + workspace: WorkspaceConfigInput, + local: LocalConfigInput, +} + +impl WorkspaceLocalConfigInput { + #[allow(dead_code)] + const FIELDS: &'static [&'static [&'static str]] = + &[WorkspaceConfigInput::FIELDS, LocalConfigInput::FIELDS]; + fn from_toml(toml: toml::Table, error_sink: &mut Vec<(String, toml::de::Error)>) -> Self { + Self { + workspace: WorkspaceConfigInput::from_toml(&toml, error_sink), + local: LocalConfigInput::from_toml(&toml, error_sink), } } } @@ -3192,16 +3203,8 @@ fn field_props(field: &str, ty: &str, doc: &[&str], default: &str) -> serde_json "type": "string", "enum": ["per_workspace", "once"], "enumDescriptions": [ - "The command will be executed for each workspace.", - "The command will be executed once." - ], - }, - "InvocationLocation" => set! { - "type": "string", - "enum": ["workspace", "root"], - "enumDescriptions": [ - "The command will be executed in the corresponding workspace root.", - "The command will be executed in the project root." + "The command will be executed for each Rust workspace with the workspace as the working directory.", + "The command will be executed once with the opened project as the working directory." ], }, "Option" => set! { @@ -3461,7 +3464,7 @@ mod tests { #[test] fn proc_macro_srv_null() { let mut config = - Config::new(AbsPathBuf::assert(project_root()), Default::default(), vec![], None, None); + Config::new(AbsPathBuf::assert(project_root()), Default::default(), vec![], None); let mut change = ConfigChange::default(); change.change_client_config(serde_json::json!({ @@ -3476,7 +3479,7 @@ mod tests { #[test] fn proc_macro_srv_abs() { let mut config = - Config::new(AbsPathBuf::assert(project_root()), Default::default(), vec![], None, None); + Config::new(AbsPathBuf::assert(project_root()), Default::default(), vec![], None); let mut change = ConfigChange::default(); change.change_client_config(serde_json::json!({ "procMacro" : { @@ -3490,7 +3493,7 @@ mod tests { #[test] fn proc_macro_srv_rel() { let mut config = - Config::new(AbsPathBuf::assert(project_root()), Default::default(), vec![], None, None); + Config::new(AbsPathBuf::assert(project_root()), Default::default(), vec![], None); let mut change = ConfigChange::default(); @@ -3510,7 +3513,7 @@ mod tests { #[test] fn cargo_target_dir_unset() { let mut config = - Config::new(AbsPathBuf::assert(project_root()), Default::default(), vec![], None, None); + Config::new(AbsPathBuf::assert(project_root()), Default::default(), vec![], None); let mut change = ConfigChange::default(); @@ -3519,7 +3522,7 @@ mod tests { })); (config, _, _) = config.apply_change(change); - assert_eq!(config.cargo_targetDir(None), &None); + assert_eq!(config.cargo_targetDir(), &None); assert!( matches!(config.flycheck(), FlycheckConfig::CargoCommand { options, .. } if options.target_dir.is_none()) ); @@ -3528,7 +3531,7 @@ mod tests { #[test] fn cargo_target_dir_subdir() { let mut config = - Config::new(AbsPathBuf::assert(project_root()), Default::default(), vec![], None, None); + Config::new(AbsPathBuf::assert(project_root()), Default::default(), vec![], None); let mut change = ConfigChange::default(); change.change_client_config(serde_json::json!({ @@ -3537,7 +3540,7 @@ mod tests { (config, _, _) = config.apply_change(change); - assert_eq!(config.cargo_targetDir(None), &Some(TargetDirectory::UseSubdirectory(true))); + assert_eq!(config.cargo_targetDir(), &Some(TargetDirectory::UseSubdirectory(true))); assert!( matches!(config.flycheck(), FlycheckConfig::CargoCommand { options, .. } if options.target_dir == Some(Utf8PathBuf::from("target/rust-analyzer"))) ); @@ -3546,7 +3549,7 @@ mod tests { #[test] fn cargo_target_dir_relative_dir() { let mut config = - Config::new(AbsPathBuf::assert(project_root()), Default::default(), vec![], None, None); + Config::new(AbsPathBuf::assert(project_root()), Default::default(), vec![], None); let mut change = ConfigChange::default(); change.change_client_config(serde_json::json!({ @@ -3556,7 +3559,7 @@ mod tests { (config, _, _) = config.apply_change(change); assert_eq!( - config.cargo_targetDir(None), + config.cargo_targetDir(), &Some(TargetDirectory::Directory(Utf8PathBuf::from("other_folder"))) ); assert!( @@ -3567,7 +3570,7 @@ mod tests { #[test] fn toml_unknown_key() { let config = - Config::new(AbsPathBuf::assert(project_root()), Default::default(), vec![], None, None); + Config::new(AbsPathBuf::assert(project_root()), Default::default(), vec![], None); let mut change = ConfigChange::default(); diff --git a/crates/rust-analyzer/src/diagnostics.rs b/crates/rust-analyzer/src/diagnostics.rs index 034c49c3d5..5f2871ac99 100644 --- a/crates/rust-analyzer/src/diagnostics.rs +++ b/crates/rust-analyzer/src/diagnostics.rs @@ -75,7 +75,7 @@ impl DiagnosticCollection { flycheck_id: usize, file_id: FileId, diagnostic: lsp_types::Diagnostic, - fix: Option, + fix: Option>, ) { let diagnostics = self.check.entry(flycheck_id).or_default().entry(file_id).or_default(); for existing_diagnostic in diagnostics.iter() { @@ -84,8 +84,10 @@ impl DiagnosticCollection { } } - let check_fixes = Arc::make_mut(&mut self.check_fixes); - check_fixes.entry(flycheck_id).or_default().entry(file_id).or_default().extend(fix); + if let Some(fix) = fix { + let check_fixes = Arc::make_mut(&mut self.check_fixes); + check_fixes.entry(flycheck_id).or_default().entry(file_id).or_default().push(*fix); + } diagnostics.push(diagnostic); self.changes.insert(file_id); } diff --git a/crates/rust-analyzer/src/diagnostics/to_proto.rs b/crates/rust-analyzer/src/diagnostics/to_proto.rs index 208a70bc02..c3ab7f3ae7 100644 --- a/crates/rust-analyzer/src/diagnostics/to_proto.rs +++ b/crates/rust-analyzer/src/diagnostics/to_proto.rs @@ -170,7 +170,7 @@ fn resolve_path( struct SubDiagnostic { related: lsp_types::DiagnosticRelatedInformation, - suggested_fix: Option, + suggested_fix: Option>, } enum MappedRustChildDiagnostic { @@ -241,7 +241,7 @@ fn map_rust_child_diagnostic( location: location(config, workspace_root, spans[0], snap), message: message.clone(), }, - suggested_fix: Some(Fix { + suggested_fix: Some(Box::new(Fix { ranges: spans .iter() .map(|&span| location(config, workspace_root, span, snap).range) @@ -260,7 +260,7 @@ fn map_rust_child_diagnostic( data: None, command: None, }, - }), + })), }) } } @@ -269,7 +269,7 @@ fn map_rust_child_diagnostic( pub(crate) struct MappedRustDiagnostic { pub(crate) url: lsp_types::Url, pub(crate) diagnostic: lsp_types::Diagnostic, - pub(crate) fix: Option, + pub(crate) fix: Option>, } /// Converts a Rust root diagnostic to LSP form @@ -548,7 +548,6 @@ mod tests { ClientCapabilities::default(), Vec::new(), None, - None, ), ); let snap = state.snapshot(); diff --git a/crates/rust-analyzer/src/flycheck.rs b/crates/rust-analyzer/src/flycheck.rs index 8f2e7d1ca2..b035d779a7 100644 --- a/crates/rust-analyzer/src/flycheck.rs +++ b/crates/rust-analyzer/src/flycheck.rs @@ -15,20 +15,13 @@ use toolchain::Tool; use crate::command::{CommandHandle, ParseFromLine}; -#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] +#[derive(Clone, Debug, Default, PartialEq, Eq)] pub(crate) enum InvocationStrategy { Once, #[default] PerWorkspace, } -#[derive(Clone, Debug, Default, PartialEq, Eq)] -pub(crate) enum InvocationLocation { - Root(AbsPathBuf), - #[default] - Workspace, -} - #[derive(Clone, Debug, PartialEq, Eq)] pub(crate) struct CargoOptions { pub(crate) target_triples: Vec, @@ -37,10 +30,19 @@ pub(crate) struct CargoOptions { pub(crate) all_features: bool, pub(crate) features: Vec, pub(crate) extra_args: Vec, + pub(crate) extra_test_bin_args: Vec, pub(crate) extra_env: FxHashMap, pub(crate) target_dir: Option, } +#[derive(Clone)] +pub(crate) enum Target { + Bin(String), + Example(String), + Benchmark(String), + Test(String), +} + impl CargoOptions { pub(crate) fn apply_on_command(&self, cmd: &mut Command) { for target in &self.target_triples { @@ -79,7 +81,6 @@ pub(crate) enum FlycheckConfig { args: Vec, extra_env: FxHashMap, invocation_strategy: InvocationStrategy, - invocation_location: InvocationLocation, }, } @@ -127,13 +128,13 @@ impl FlycheckHandle { /// Schedule a re-start of the cargo check worker to do a workspace wide check. pub(crate) fn restart_workspace(&self, saved_file: Option) { - self.sender.send(StateChange::Restart { package: None, saved_file }).unwrap(); + self.sender.send(StateChange::Restart { package: None, saved_file, target: None }).unwrap(); } /// Schedule a re-start of the cargo check worker to do a package wide check. - pub(crate) fn restart_for_package(&self, package: String) { + pub(crate) fn restart_for_package(&self, package: String, target: Option) { self.sender - .send(StateChange::Restart { package: Some(package), saved_file: None }) + .send(StateChange::Restart { package: Some(package), saved_file: None, target }) .unwrap(); } @@ -191,7 +192,7 @@ pub(crate) enum Progress { } enum StateChange { - Restart { package: Option, saved_file: Option }, + Restart { package: Option, saved_file: Option, target: Option }, Cancel, } @@ -218,6 +219,7 @@ struct FlycheckActor { status: FlycheckStatus, } +#[allow(clippy::large_enum_variant)] enum Event { RequestStateChange(StateChange), CheckEvent(Option), @@ -278,7 +280,7 @@ impl FlycheckActor { tracing::debug!(flycheck_id = self.id, "flycheck cancelled"); self.cancel_check_process(); } - Event::RequestStateChange(StateChange::Restart { package, saved_file }) => { + Event::RequestStateChange(StateChange::Restart { package, saved_file, target }) => { // Cancel the previously spawned process self.cancel_check_process(); while let Ok(restart) = inbox.recv_timeout(Duration::from_millis(50)) { @@ -288,11 +290,12 @@ impl FlycheckActor { } } - let command = - match self.check_command(package.as_deref(), saved_file.as_deref()) { - Some(c) => c, - None => continue, - }; + let Some(command) = + self.check_command(package.as_deref(), saved_file.as_deref(), target) + else { + continue; + }; + let formatted_command = format!("{command:?}"); tracing::debug!(?command, "will restart flycheck"); @@ -388,6 +391,7 @@ impl FlycheckActor { &self, package: Option<&str>, saved_file: Option<&AbsPath>, + target: Option, ) -> Option { match &self.config { FlycheckConfig::CargoCommand { command, options, ansi_color_output } => { @@ -403,6 +407,15 @@ impl FlycheckActor { None => cmd.arg("--workspace"), }; + if let Some(tgt) = target { + match tgt { + Target::Bin(tgt) => cmd.arg("--bin").arg(tgt), + Target::Example(tgt) => cmd.arg("--example").arg(tgt), + Target::Test(tgt) => cmd.arg("--test").arg(tgt), + Target::Benchmark(tgt) => cmd.arg("--bench").arg(tgt), + }; + } + cmd.arg(if *ansi_color_output { "--message-format=json-diagnostic-rendered-ansi" } else { @@ -423,30 +436,17 @@ impl FlycheckActor { cmd.args(&options.extra_args); Some(cmd) } - FlycheckConfig::CustomCommand { - command, - args, - extra_env, - invocation_strategy, - invocation_location, - } => { + FlycheckConfig::CustomCommand { command, args, extra_env, invocation_strategy } => { let mut cmd = Command::new(command); cmd.envs(extra_env); - match invocation_location { - InvocationLocation::Workspace => { - match invocation_strategy { - InvocationStrategy::Once => { - cmd.current_dir(&self.root); - } - InvocationStrategy::PerWorkspace => { - // FIXME: cmd.current_dir(&affected_workspace); - cmd.current_dir(&self.root); - } - } + match invocation_strategy { + InvocationStrategy::Once => { + cmd.current_dir(&self.root); } - InvocationLocation::Root(root) => { - cmd.current_dir(root); + InvocationStrategy::PerWorkspace => { + // FIXME: cmd.current_dir(&affected_workspace); + cmd.current_dir(&self.root); } } diff --git a/crates/rust-analyzer/src/global_state.rs b/crates/rust-analyzer/src/global_state.rs index 7a7ec1d77e..9d0082c370 100644 --- a/crates/rust-analyzer/src/global_state.rs +++ b/crates/rust-analyzer/src/global_state.rs @@ -380,7 +380,7 @@ impl GlobalState { || !self.config.same_source_root_parent_map(&self.local_roots_parent_map) { let config_change = { - let user_config_path = self.config.user_config_path(); + let user_config_path = Config::user_config_path(); let mut change = ConfigChange::default(); let db = self.analysis_host.raw_database(); @@ -399,7 +399,7 @@ impl GlobalState { .collect_vec(); for (file_id, (_change_kind, vfs_path)) in modified_ratoml_files { - if vfs_path == *user_config_path { + if vfs_path.as_path() == user_config_path { change.change_user_config(Some(db.file_text(file_id))); continue; } @@ -667,7 +667,7 @@ impl GlobalStateSnapshot { for workspace in self.workspaces.iter() { match &workspace.kind { ProjectWorkspaceKind::Cargo { cargo, .. } - | ProjectWorkspaceKind::DetachedFile { cargo: Some((cargo, _)), .. } => { + | ProjectWorkspaceKind::DetachedFile { cargo: Some((cargo, _, _)), .. } => { let Some(target_idx) = cargo.target_by_root(path) else { continue; }; @@ -696,6 +696,7 @@ impl GlobalStateSnapshot { }; return Some(TargetSpec::ProjectJson(ProjectJsonTargetSpec { + crate_id, label: build.label, target_kind: build.target_kind, shell_runnables: project.runnables().to_owned(), diff --git a/crates/rust-analyzer/src/handlers/dispatch.rs b/crates/rust-analyzer/src/handlers/dispatch.rs index a105ec6382..f03de8ce0f 100644 --- a/crates/rust-analyzer/src/handlers/dispatch.rs +++ b/crates/rust-analyzer/src/handlers/dispatch.rs @@ -139,16 +139,26 @@ impl RequestDispatcher<'_> { self.on_with_thread_intent::(ThreadIntent::Worker, f) } - /// Dispatches a latency-sensitive request onto the thread pool. + /// Dispatches a latency-sensitive request onto the thread pool. When the VFS is marked not + /// ready this will return a default constructed [`R::Result`]. pub(crate) fn on_latency_sensitive( &mut self, f: fn(GlobalStateSnapshot, R::Params) -> anyhow::Result, ) -> &mut Self where - R: lsp_types::request::Request + 'static, - R::Params: DeserializeOwned + panic::UnwindSafe + Send + fmt::Debug, - R::Result: Serialize, + R: lsp_types::request::Request< + Params: DeserializeOwned + panic::UnwindSafe + Send + fmt::Debug, + Result: Serialize + Default, + > + 'static, { + if !self.global_state.vfs_done { + if let Some(lsp_server::Request { id, .. }) = + self.req.take_if(|it| it.method == R::METHOD) + { + self.global_state.respond(lsp_server::Response::new_ok(id, R::Result::default())); + } + return self; + } self.on_with_thread_intent::(ThreadIntent::LatencySensitive, f) } diff --git a/crates/rust-analyzer/src/handlers/notification.rs b/crates/rust-analyzer/src/handlers/notification.rs index de5d1f2313..38b88ff2d0 100644 --- a/crates/rust-analyzer/src/handlers/notification.rs +++ b/crates/rust-analyzer/src/handlers/notification.rs @@ -10,16 +10,19 @@ use lsp_types::{ DidOpenTextDocumentParams, DidSaveTextDocumentParams, WorkDoneProgressCancelParams, }; use paths::Utf8PathBuf; +use stdx::TupleExt; use triomphe::Arc; use vfs::{AbsPathBuf, ChangeKind, VfsPath}; use crate::{ config::{Config, ConfigChange}, + flycheck::Target, global_state::{FetchWorkspaceRequest, GlobalState}, lsp::{from_proto, utils::apply_document_changes}, lsp_ext::{self, RunFlycheckParams}, mem_docs::DocumentData, reload, + target_spec::TargetSpec, }; pub(crate) fn handle_cancel(state: &mut GlobalState, params: CancelParams) -> anyhow::Result<()> { @@ -287,17 +290,41 @@ fn run_flycheck(state: &mut GlobalState, vfs_path: VfsPath) -> bool { let world = state.snapshot(); let mut updated = false; let task = move || -> std::result::Result<(), ide::Cancelled> { - // Trigger flychecks for all workspaces that depend on the saved file - // Crates containing or depending on the saved file - let crate_ids: Vec<_> = world - .analysis - .crates_for(file_id)? - .into_iter() - .flat_map(|id| world.analysis.transitive_rev_deps(id)) - .flatten() - .unique() - .collect(); + // Is the target binary? If so we let flycheck run only for the workspace that contains the crate. + let target = TargetSpec::for_file(&world, file_id)?.and_then(|it| { + let tgt_kind = it.target_kind(); + let (tgt_name, crate_id) = match it { + TargetSpec::Cargo(c) => (c.target, c.crate_id), + TargetSpec::ProjectJson(p) => (p.label, p.crate_id), + }; + let tgt = match tgt_kind { + project_model::TargetKind::Bin => Target::Bin(tgt_name), + project_model::TargetKind::Example => Target::Example(tgt_name), + project_model::TargetKind::Test => Target::Test(tgt_name), + project_model::TargetKind::Bench => Target::Benchmark(tgt_name), + _ => return None, + }; + + Some((tgt, crate_id)) + }); + + let crate_ids = match target { + // Trigger flychecks for the only crate which the target belongs to + Some((_, krate)) => vec![krate], + None => { + // Trigger flychecks for all workspaces that depend on the saved file + // Crates containing or depending on the saved file + world + .analysis + .crates_for(file_id)? + .into_iter() + .flat_map(|id| world.analysis.transitive_rev_deps(id)) + .flatten() + .unique() + .collect::>() + } + }; let crate_root_paths: Vec<_> = crate_ids .iter() .filter_map(|&crate_id| { @@ -317,7 +344,7 @@ fn run_flycheck(state: &mut GlobalState, vfs_path: VfsPath) -> bool { let package = match &ws.kind { project_model::ProjectWorkspaceKind::Cargo { cargo, .. } | project_model::ProjectWorkspaceKind::DetachedFile { - cargo: Some((cargo, _)), + cargo: Some((cargo, _, _)), .. } => cargo.packages().find_map(|pkg| { let has_target_with_root = cargo[pkg] @@ -346,8 +373,11 @@ fn run_flycheck(state: &mut GlobalState, vfs_path: VfsPath) -> bool { for (id, package) in workspace_ids.clone() { if id == flycheck.id() { updated = true; - match package.filter(|_| !world.config.flycheck_workspace()) { - Some(package) => flycheck.restart_for_package(package), + match package + .filter(|_| !world.config.flycheck_workspace() || target.is_some()) + { + Some(package) => flycheck + .restart_for_package(package, target.clone().map(TupleExt::head)), None => flycheck.restart_workspace(saved_file.clone()), } continue; diff --git a/crates/rust-analyzer/src/handlers/request.rs b/crates/rust-analyzer/src/handlers/request.rs index 34325ac7a9..1ad5ff0c8c 100644 --- a/crates/rust-analyzer/src/handlers/request.rs +++ b/crates/rust-analyzer/src/handlers/request.rs @@ -799,7 +799,7 @@ pub(crate) fn handle_parent_module( .iter() .filter_map(|ws| match &ws.kind { ProjectWorkspaceKind::Cargo { cargo, .. } - | ProjectWorkspaceKind::DetachedFile { cargo: Some((cargo, _)), .. } => { + | ProjectWorkspaceKind::DetachedFile { cargo: Some((cargo, _, _)), .. } => { cargo.parent_manifests(&manifest_path) } _ => None, @@ -1839,7 +1839,7 @@ pub(crate) fn handle_open_docs( let ws_and_sysroot = snap.workspaces.iter().find_map(|ws| match &ws.kind { ProjectWorkspaceKind::Cargo { cargo, .. } - | ProjectWorkspaceKind::DetachedFile { cargo: Some((cargo, _)), .. } => { + | ProjectWorkspaceKind::DetachedFile { cargo: Some((cargo, _, _)), .. } => { Some((cargo, &ws.sysroot)) } ProjectWorkspaceKind::Json { .. } => None, @@ -2113,9 +2113,9 @@ fn run_rustfmt( let edition = editions.iter().copied().max(); let line_index = snap.file_line_index(file_id)?; - let sr = snap.analysis.source_root_id(file_id)?; + let source_root_id = snap.analysis.source_root_id(file_id).ok(); - let mut command = match snap.config.rustfmt(Some(sr)) { + let mut command = match snap.config.rustfmt(source_root_id) { RustfmtConfig::Rustfmt { extra_args, enable_range_formatting } => { // FIXME: Set RUSTUP_TOOLCHAIN let mut cmd = process::Command::new(toolchain::Tool::Rustfmt.path()); @@ -2303,7 +2303,7 @@ pub(crate) fn internal_testing_fetch_config( .transpose()?; serde_json::to_value(match &*params.config { "local" => state.config.assist(source_root).assist_emit_must_use, - "global" => matches!( + "workspace" => matches!( state.config.rustfmt(source_root), RustfmtConfig::Rustfmt { enable_range_formatting: true, .. } ), diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs index 1d4ee71e5c..616d6b4935 100644 --- a/crates/rust-analyzer/src/main_loop.rs +++ b/crates/rust-analyzer/src/main_loop.rs @@ -689,7 +689,7 @@ impl GlobalState { self.fetch_workspaces_queue .op_completed(Some((workspaces, force_reload_crate_graph))); if let Err(e) = self.fetch_workspace_error() { - error!("FetchWorkspaceError:\n{e}"); + error!("FetchWorkspaceError: {e}"); } self.wants_to_switch = Some("fetched workspace".to_owned()); (Progress::End, None) @@ -729,7 +729,7 @@ impl GlobalState { BuildDataProgress::End(build_data_result) => { self.fetch_build_data_queue.op_completed(build_data_result); if let Err(e) = self.fetch_build_data_error() { - error!("FetchBuildDataError:\n{e}"); + error!("FetchBuildDataError: {e}"); } if self.wants_to_switch.is_none() { diff --git a/crates/rust-analyzer/src/op_queue.rs b/crates/rust-analyzer/src/op_queue.rs index 99f9e9829c..eab9733872 100644 --- a/crates/rust-analyzer/src/op_queue.rs +++ b/crates/rust-analyzer/src/op_queue.rs @@ -3,6 +3,26 @@ pub(crate) type Cause = String; +/// A single-item queue that allows callers to request an operation to +/// be performed later. +/// +/// ``` +/// let queue = OpQueue::default(); +/// +/// // Request work to be done. +/// queue.request_op("user pushed a button", ()); +/// +/// // In a later iteration of the server loop, we start the work. +/// if let Some((_cause, ())) = queue.should_start_op() { +/// dbg!("Some slow operation here"); +/// } +/// +/// // In an even later iteration of the server loop, we can see that the work +/// // was completed. +/// if !queue.op_in_progress() { +/// dbg!("Work has been done!"); +/// } +/// ``` #[derive(Debug)] pub(crate) struct OpQueue { op_requested: Option<(Cause, Args)>, diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs index dee34b1b39..68366136ed 100644 --- a/crates/rust-analyzer/src/reload.rs +++ b/crates/rust-analyzer/src/reload.rs @@ -61,6 +61,10 @@ pub(crate) enum ProcMacroProgress { } impl GlobalState { + /// Is the server quiescent? + /// + /// This indicates that we've fully loaded the projects and + /// are ready to do semantic work. pub(crate) fn is_quiescent(&self) -> bool { self.vfs_done && self.last_reported_status.is_some() @@ -71,6 +75,15 @@ impl GlobalState { && self.vfs_progress_config_version >= self.vfs_config_version } + /// Is the server ready to respond to analysis dependent LSP requests? + /// + /// Unlike `is_quiescent`, this returns false when we're indexing + /// the project, because we're holding the salsa lock and cannot + /// respond to LSP requests that depend on salsa data. + fn is_fully_ready(&self) -> bool { + self.is_quiescent() && !self.prime_caches_queue.op_in_progress() + } + pub(crate) fn update_configuration(&mut self, config: Config) { let _p = tracing::info_span!("GlobalState::update_configuration").entered(); let old_config = mem::replace(&mut self.config, Arc::new(config)); @@ -102,13 +115,15 @@ impl GlobalState { } pub(crate) fn current_status(&self) -> lsp_ext::ServerStatusParams { - let quiescent = self.is_quiescent(); - let mut status = - lsp_ext::ServerStatusParams { health: lsp_ext::Health::Ok, quiescent, message: None }; + let mut status = lsp_ext::ServerStatusParams { + health: lsp_ext::Health::Ok, + quiescent: self.is_fully_ready(), + message: None, + }; let mut message = String::new(); - if !self.config.cargo_autoreload(None) - && quiescent + if !self.config.cargo_autoreload() + && self.is_quiescent() && self.fetch_workspaces_queue.op_requested() && self.config.discover_workspace_config().is_none() { @@ -165,6 +180,19 @@ impl GlobalState { self.proc_macro_clients.iter().map(Some).chain(iter::repeat_with(|| None)); for (ws, proc_macro_client) in self.workspaces.iter().zip(proc_macro_clients) { + if let ProjectWorkspaceKind::Cargo { error: Some(error), .. } + | ProjectWorkspaceKind::DetachedFile { + cargo: Some((_, _, Some(error))), .. + } = &ws.kind + { + status.health |= lsp_ext::Health::Warning; + format_to!( + message, + "Failed to read Cargo metadata with dependencies for `{}`: {:#}\n\n", + ws.manifest_or_root(), + error + ); + } if let Some(err) = ws.sysroot.error() { status.health |= lsp_ext::Health::Warning; format_to!( @@ -537,8 +565,25 @@ impl GlobalState { .collect() }; + // Also explicitly watch any build files configured in JSON project files. + for ws in self.workspaces.iter() { + if let ProjectWorkspaceKind::Json(project_json) = &ws.kind { + for (_, krate) in project_json.crates() { + let Some(build) = &krate.build else { + continue; + }; + watchers.push(lsp_types::FileSystemWatcher { + glob_pattern: lsp_types::GlobPattern::String( + build.build_file.to_string(), + ), + kind: None, + }); + } + } + } + watchers.extend( - iter::once(self.config.user_config_path().as_path()) + iter::once(Config::user_config_path()) .chain(self.workspaces.iter().map(|ws| ws.manifest().map(ManifestPath::as_ref))) .flatten() .map(|glob_pattern| lsp_types::FileSystemWatcher { @@ -752,18 +797,22 @@ impl GlobalState { FlycheckConfig::CargoCommand { .. } => { crate::flycheck::InvocationStrategy::PerWorkspace } - FlycheckConfig::CustomCommand { invocation_strategy, .. } => invocation_strategy, + FlycheckConfig::CustomCommand { ref invocation_strategy, .. } => { + invocation_strategy.clone() + } }; self.flycheck = match invocation_strategy { - crate::flycheck::InvocationStrategy::Once => vec![FlycheckHandle::spawn( - 0, - sender, - config, - None, - self.config.root_path().clone(), - None, - )], + crate::flycheck::InvocationStrategy::Once => { + vec![FlycheckHandle::spawn( + 0, + sender, + config, + None, + self.config.root_path().clone(), + None, + )] + } crate::flycheck::InvocationStrategy::PerWorkspace => { self.workspaces .iter() @@ -774,7 +823,7 @@ impl GlobalState { match &ws.kind { ProjectWorkspaceKind::Cargo { cargo, .. } | ProjectWorkspaceKind::DetachedFile { - cargo: Some((cargo, _)), + cargo: Some((cargo, _, _)), .. } => (cargo.workspace_root(), Some(cargo.manifest_path())), ProjectWorkspaceKind::Json(project) => { diff --git a/crates/rust-analyzer/src/target_spec.rs b/crates/rust-analyzer/src/target_spec.rs index 965fd415e9..954e13cbf2 100644 --- a/crates/rust-analyzer/src/target_spec.rs +++ b/crates/rust-analyzer/src/target_spec.rs @@ -62,6 +62,7 @@ pub(crate) struct CargoTargetSpec { #[derive(Clone, Debug)] pub(crate) struct ProjectJsonTargetSpec { + pub(crate) crate_id: CrateId, pub(crate) label: String, pub(crate) target_kind: TargetKind, pub(crate) shell_runnables: Vec, diff --git a/crates/rust-analyzer/src/test_runner.rs b/crates/rust-analyzer/src/test_runner.rs index 293cff4743..5e43a3c60d 100644 --- a/crates/rust-analyzer/src/test_runner.rs +++ b/crates/rust-analyzer/src/test_runner.rs @@ -102,6 +102,11 @@ impl CargoTestHandle { } cmd.args(["-Z", "unstable-options"]); cmd.arg("--format=json"); + + for extra_arg in options.extra_test_bin_args { + cmd.arg(extra_arg); + } + Ok(Self { _handle: CommandHandle::spawn(cmd, sender)? }) } } diff --git a/crates/rust-analyzer/tests/crate_graph.rs b/crates/rust-analyzer/tests/crate_graph.rs index b8a82fd6a7..04b6713b8d 100644 --- a/crates/rust-analyzer/tests/crate_graph.rs +++ b/crates/rust-analyzer/tests/crate_graph.rs @@ -20,6 +20,7 @@ fn load_cargo_with_fake_sysroot(file: &str) -> ProjectWorkspace { build_scripts: WorkspaceBuildScripts::default(), rustc: Err(None), cargo_config_extra_env: Default::default(), + error: None, }, sysroot: get_fake_sysroot(), rustc_cfg: Vec::new(), @@ -93,7 +94,7 @@ fn test_deduplicate_origin_dev() { } } - assert!(crates_named_p2.len() == 1); + assert_eq!(crates_named_p2.len(), 1); let p2 = crates_named_p2[0]; assert!(p2.origin.is_local()); } @@ -119,7 +120,7 @@ fn test_deduplicate_origin_dev_rev() { } } - assert!(crates_named_p2.len() == 1); + assert_eq!(crates_named_p2.len(), 1); let p2 = crates_named_p2[0]; assert!(p2.origin.is_local()); } diff --git a/crates/rust-analyzer/tests/slow-tests/ratoml.rs b/crates/rust-analyzer/tests/slow-tests/ratoml.rs index c06ba9eee1..295d1d4e8e 100644 --- a/crates/rust-analyzer/tests/slow-tests/ratoml.rs +++ b/crates/rust-analyzer/tests/slow-tests/ratoml.rs @@ -8,6 +8,7 @@ use lsp_types::{ }; use paths::Utf8PathBuf; +use rust_analyzer::config::Config; use rust_analyzer::lsp::ext::{InternalTestingFetchConfig, InternalTestingFetchConfigParams}; use serde_json::json; use test_utils::skip_slow_tests; @@ -17,14 +18,13 @@ enum QueryType { /// A query whose config key is a part of the global configs, so that /// testing for changes to this config means testing if global changes /// take affect. - Global, + Workspace, } struct RatomlTest { urls: Vec, server: Server, tmp_path: Utf8PathBuf, - user_config_dir: Utf8PathBuf, } impl RatomlTest { @@ -41,11 +41,7 @@ impl RatomlTest { let full_fixture = fixtures.join("\n"); - let user_cnf_dir = TestDir::new(); - let user_config_dir = user_cnf_dir.path().to_owned(); - - let mut project = - Project::with_fixture(&full_fixture).tmp_dir(tmp_dir).user_config_dir(user_cnf_dir); + let mut project = Project::with_fixture(&full_fixture).tmp_dir(tmp_dir); for root in roots { project = project.root(root); @@ -57,7 +53,7 @@ impl RatomlTest { let server = project.server().wait_until_workspace_is_loaded(); - let mut case = Self { urls: vec![], server, tmp_path, user_config_dir }; + let mut case = Self { urls: vec![], server, tmp_path }; let urls = fixtures.iter().map(|fixture| case.fixture_path(fixture)).collect::>(); case.urls = urls; case @@ -81,7 +77,7 @@ impl RatomlTest { let mut spl = spl.into_iter(); if let Some(first) = spl.next() { if first == "$$CONFIG_DIR$$" { - path = self.user_config_dir.clone(); + path = Config::user_config_path().unwrap().to_path_buf().into(); } else { path = path.join(first); } @@ -165,7 +161,7 @@ impl RatomlTest { fn query(&self, query: QueryType, source_file_idx: usize) -> bool { let config = match query { QueryType::Local => "local".to_owned(), - QueryType::Global => "global".to_owned(), + QueryType::Workspace => "workspace".to_owned(), }; let res = self.server.send_request::( InternalTestingFetchConfigParams { @@ -823,10 +819,8 @@ fn ratoml_multiple_ratoml_in_single_source_root() { // assert!(!server.query(QueryType::AssistEmitMustUse, 5)); // } -/// Having a ratoml file at the root of a project enables -/// configuring global level configurations as well. #[test] -fn ratoml_in_root_is_global() { +fn ratoml_in_root_is_workspace() { if skip_slow_tests() { return; } @@ -854,7 +848,7 @@ fn main() { None, ); - assert!(server.query(QueryType::Global, 2)); + assert!(server.query(QueryType::Workspace, 2)); } #[test] @@ -886,9 +880,9 @@ fn main() { None, ); - assert!(server.query(QueryType::Global, 2)); + assert!(server.query(QueryType::Workspace, 2)); server.edit(1, "rustfmt.rangeFormatting.enable = false".to_owned()); - assert!(!server.query(QueryType::Global, 2)); + assert!(!server.query(QueryType::Workspace, 2)); } #[test] @@ -920,7 +914,7 @@ fn main() { None, ); - assert!(server.query(QueryType::Global, 2)); + assert!(server.query(QueryType::Workspace, 2)); server.delete(1); - assert!(!server.query(QueryType::Global, 2)); + assert!(!server.query(QueryType::Workspace, 2)); } diff --git a/crates/rust-analyzer/tests/slow-tests/support.rs b/crates/rust-analyzer/tests/slow-tests/support.rs index 081ee5fa3e..06ce984681 100644 --- a/crates/rust-analyzer/tests/slow-tests/support.rs +++ b/crates/rust-analyzer/tests/slow-tests/support.rs @@ -8,6 +8,7 @@ use std::{ use crossbeam_channel::{after, select, Receiver}; use lsp_server::{Connection, Message, Notification, Request}; use lsp_types::{notification::Exit, request::Shutdown, TextDocumentIdentifier, Url}; +use parking_lot::{Mutex, MutexGuard}; use paths::{Utf8Path, Utf8PathBuf}; use rust_analyzer::{ config::{Config, ConfigChange, ConfigErrors}, @@ -27,7 +28,6 @@ pub(crate) struct Project<'a> { roots: Vec, config: serde_json::Value, root_dir_contains_symlink: bool, - user_config_path: Option, } impl Project<'_> { @@ -51,15 +51,9 @@ impl Project<'_> { } }), root_dir_contains_symlink: false, - user_config_path: None, } } - pub(crate) fn user_config_dir(mut self, config_path_dir: TestDir) -> Self { - self.user_config_path = Some(config_path_dir.path().to_owned()); - self - } - pub(crate) fn tmp_dir(mut self, tmp_dir: TestDir) -> Self { self.tmp_dir = Some(tmp_dir); self @@ -91,6 +85,7 @@ impl Project<'_> { } pub(crate) fn server(self) -> Server { + static CONFIG_DIR_LOCK: Mutex<()> = Mutex::new(()); let tmp_dir = self.tmp_dir.unwrap_or_else(|| { if self.root_dir_contains_symlink { TestDir::new_symlink() @@ -122,9 +117,13 @@ impl Project<'_> { assert!(mini_core.is_none()); assert!(toolchain.is_none()); + let mut config_dir_guard = None; for entry in fixture { if let Some(pth) = entry.path.strip_prefix("/$$CONFIG_DIR$$") { - let path = self.user_config_path.clone().unwrap().join(&pth['/'.len_utf8()..]); + if config_dir_guard.is_none() { + config_dir_guard = Some(CONFIG_DIR_LOCK.lock()); + } + let path = Config::user_config_path().unwrap().join(&pth['/'.len_utf8()..]); fs::create_dir_all(path.parent().unwrap()).unwrap(); fs::write(path.as_path(), entry.text.as_bytes()).unwrap(); } else { @@ -201,7 +200,6 @@ impl Project<'_> { }, roots, None, - self.user_config_path, ); let mut change = ConfigChange::default(); @@ -213,7 +211,7 @@ impl Project<'_> { config.rediscover_workspaces(); - Server::new(tmp_dir.keep(), config) + Server::new(config_dir_guard, tmp_dir.keep(), config) } } @@ -228,10 +226,15 @@ pub(crate) struct Server { client: Connection, /// XXX: remove the tempdir last dir: TestDir, + _config_dir_guard: Option>, } impl Server { - fn new(dir: TestDir, config: Config) -> Server { + fn new( + config_dir_guard: Option>, + dir: TestDir, + config: Config, + ) -> Server { let (connection, client) = Connection::memory(); let _thread = stdx::thread::Builder::new(stdx::thread::ThreadIntent::Worker) @@ -239,7 +242,14 @@ impl Server { .spawn(move || main_loop(config, connection).unwrap()) .expect("failed to spawn a thread"); - Server { req_id: Cell::new(1), dir, messages: Default::default(), client, _thread } + Server { + req_id: Cell::new(1), + dir, + messages: Default::default(), + client, + _thread, + _config_dir_guard: config_dir_guard, + } } pub(crate) fn doc_id(&self, rel_path: &str) -> TextDocumentIdentifier { diff --git a/crates/salsa/LICENSE-APACHE b/crates/salsa/LICENSE-APACHE index 16fe87b06e..1b5ec8b78e 100644 --- a/crates/salsa/LICENSE-APACHE +++ b/crates/salsa/LICENSE-APACHE @@ -174,28 +174,3 @@ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - -Copyright [yyyy] [name of copyright owner] - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/crates/syntax-bridge/src/lib.rs b/crates/syntax-bridge/src/lib.rs index b0afd245c5..56e43e82ed 100644 --- a/crates/syntax-bridge/src/lib.rs +++ b/crates/syntax-bridge/src/lib.rs @@ -301,13 +301,11 @@ where }; } let leaf: tt::Leaf<_> = match kind { - T![true] | T![false] => make_ident!(), - IDENT => { + k if k.is_any_identifier() => { let text = token.to_text(conv); tt::Ident::new(&text, conv.span_for(abs_range)).into() } UNDERSCORE => make_ident!(), - k if k.is_keyword() => make_ident!(), k if k.is_literal() => { let text = token.to_text(conv); let span = conv.span_for(abs_range); diff --git a/crates/syntax-bridge/src/to_parser_input.rs b/crates/syntax-bridge/src/to_parser_input.rs index 2c54899268..14216e3093 100644 --- a/crates/syntax-bridge/src/to_parser_input.rs +++ b/crates/syntax-bridge/src/to_parser_input.rs @@ -64,14 +64,12 @@ pub fn to_parser_input( "_" => res.push(T![_]), i if i.starts_with('\'') => res.push(LIFETIME_IDENT), _ if ident.is_raw.yes() => res.push(IDENT), - "gen" if !edition.at_least_2024() => res.push(IDENT), - "dyn" if !edition.at_least_2018() => res.push_ident(DYN_KW), - "async" | "await" | "try" if !edition.at_least_2018() => res.push(IDENT), - text => match SyntaxKind::from_keyword(text) { + text => match SyntaxKind::from_keyword(text, edition) { Some(kind) => res.push(kind), None => { - let contextual_keyword = SyntaxKind::from_contextual_keyword(text) - .unwrap_or(SyntaxKind::IDENT); + let contextual_keyword = + SyntaxKind::from_contextual_keyword(text, edition) + .unwrap_or(SyntaxKind::IDENT); res.push_ident(contextual_keyword); } }, diff --git a/crates/syntax/Cargo.toml b/crates/syntax/Cargo.toml index 994c21469f..fcb9b0ea35 100644 --- a/crates/syntax/Cargo.toml +++ b/crates/syntax/Cargo.toml @@ -16,9 +16,8 @@ doctest = false cov-mark = "2.0.0-pre.1" either.workspace = true itertools.workspace = true -rowan = "0.15.15" +rowan = "=0.15.15" rustc-hash.workspace = true -once_cell = "1.17.0" indexmap.workspace = true smol_str.workspace = true triomphe.workspace = true diff --git a/crates/syntax/rust.ungram b/crates/syntax/rust.ungram index c23bcd6914..43375ce6ae 100644 --- a/crates/syntax/rust.ungram +++ b/crates/syntax/rust.ungram @@ -9,8 +9,6 @@ // // -- comment // Name = -- non-terminal definition // 'ident' -- keyword or punct token (terminal) -// '?ident' -- contextual keyword (terminal) -// too) // '#ident' -- generic token (terminal) // '@ident' -- literal token (terminal) // A B -- sequence @@ -40,8 +38,12 @@ PathSegment = '::'? NameRef | NameRef GenericArgList? | NameRef ParamList RetType? +| NameRef ReturnTypeSyntax | '<' Type ('as' PathType)? '>' +ReturnTypeSyntax = + '(' '..' ')' + //*************************// // Generics // @@ -61,7 +63,7 @@ TypeArg = AssocTypeArg = NameRef - (GenericArgList | ParamList RetType?)? + (GenericArgList | ParamList RetType? | ReturnTypeSyntax)? (':' TypeBoundList | ('=' Type | ConstArg)) LifetimeArg = @@ -152,7 +154,7 @@ Item = MacroRules = Attr* Visibility? - '?macro_rules' '!' Name + 'macro_rules' '!' Name TokenTree MacroDef = @@ -188,7 +190,7 @@ UseTreeList = Fn = Attr* Visibility? - '?default'? 'const'? 'async'? 'unsafe'? Abi? + 'default'? 'const'? 'async'? 'gen'? 'unsafe'? Abi? 'fn' Name GenericParamList? ParamList RetType? WhereClause? (body:BlockExpr | ';') @@ -220,7 +222,7 @@ RetType = TypeAlias = Attr* Visibility? - '?default'? + 'default'? 'type' Name GenericParamList? (':' TypeBoundList?)? WhereClause? ('=' Type)? ';' @@ -263,7 +265,7 @@ Variant = Union = Attr* Visibility? - '?union' Name GenericParamList? WhereClause? + 'union' Name GenericParamList? WhereClause? RecordFieldList // A Data Type. @@ -276,7 +278,7 @@ Adt = Const = Attr* Visibility? - '?default'? + 'default'? 'const' (Name | '_') ':' Type ('=' body:Expr)? ';' @@ -287,7 +289,7 @@ Static = Trait = Attr* Visibility? - 'unsafe'? '?auto'? + 'unsafe'? 'auto'? 'trait' Name GenericParamList? (':' TypeBoundList?)? WhereClause? AssocItemList @@ -306,7 +308,7 @@ AssocItem = Impl = Attr* Visibility? - '?default'? 'unsafe'? + 'default'? 'unsafe'? 'impl' GenericParamList? ('const'? '!'? trait:Type 'for')? self_ty:Type WhereClause? AssocItemList @@ -387,13 +389,13 @@ Expr = | UnderscoreExpr OffsetOfExpr = - Attr* '?builtin' '#' '?offset_of' '(' Type ',' fields:(NameRef ('.' NameRef)* ) ')' + Attr* 'builtin' '#' 'offset_of' '(' Type ',' fields:(NameRef ('.' NameRef)* ) ')' AsmExpr = - Attr* '?builtin' '#' '?asm' '(' Expr ')' + Attr* 'builtin' '#' 'asm' '(' Expr ')' FormatArgsExpr = - Attr* '?builtin' '#' '?format_args' '(' + Attr* 'builtin' '#' 'format_args' '(' template:Expr (',' args:(FormatArgsArg (',' FormatArgsArg)* ','?)? )? ')' @@ -425,7 +427,7 @@ StmtList = '}' RefExpr = - Attr* '&' (('?raw' 'const'?)| ('?raw'? 'mut') ) Expr + Attr* '&' (('raw' 'const'?)| ('raw'? 'mut') ) Expr TryExpr = Attr* Expr '?' @@ -550,7 +552,7 @@ YieldExpr = Attr* 'yield' Expr? YeetExpr = - Attr* 'do' '?yeet' Expr? + Attr* 'do' 'yeet' Expr? LetExpr = Attr* 'let' Pat '=' Expr diff --git a/crates/syntax/src/ast/edit.rs b/crates/syntax/src/ast/edit.rs index 5bc6b780e4..de40d638be 100644 --- a/crates/syntax/src/ast/edit.rs +++ b/crates/syntax/src/ast/edit.rs @@ -8,7 +8,7 @@ use crate::{ ted, AstToken, NodeOrToken, SyntaxElement, SyntaxNode, SyntaxToken, }; -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct IndentLevel(pub u8); impl From for IndentLevel { diff --git a/crates/syntax/src/ast/generated/nodes.rs b/crates/syntax/src/ast/generated/nodes.rs index 01886d119d..c9b39e9922 100644 --- a/crates/syntax/src/ast/generated/nodes.rs +++ b/crates/syntax/src/ast/generated/nodes.rs @@ -114,6 +114,8 @@ impl AssocTypeArg { #[inline] pub fn ret_type(&self) -> Option { support::child(&self.syntax) } #[inline] + pub fn return_type_syntax(&self) -> Option { support::child(&self.syntax) } + #[inline] pub fn ty(&self) -> Option { support::child(&self.syntax) } #[inline] pub fn eq_token(&self) -> Option { support::token(&self.syntax, T![=]) } @@ -484,6 +486,8 @@ impl Fn { #[inline] pub fn fn_token(&self) -> Option { support::token(&self.syntax, T![fn]) } #[inline] + pub fn gen_token(&self) -> Option { support::token(&self.syntax, T![gen]) } + #[inline] pub fn unsafe_token(&self) -> Option { support::token(&self.syntax, T![unsafe]) } } @@ -1221,6 +1225,8 @@ impl PathSegment { #[inline] pub fn ret_type(&self) -> Option { support::child(&self.syntax) } #[inline] + pub fn return_type_syntax(&self) -> Option { support::child(&self.syntax) } + #[inline] pub fn ty(&self) -> Option { support::child(&self.syntax) } #[inline] pub fn coloncolon_token(&self) -> Option { support::token(&self.syntax, T![::]) } @@ -1485,6 +1491,19 @@ impl ReturnExpr { pub fn return_token(&self) -> Option { support::token(&self.syntax, T![return]) } } +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct ReturnTypeSyntax { + pub(crate) syntax: SyntaxNode, +} +impl ReturnTypeSyntax { + #[inline] + pub fn l_paren_token(&self) -> Option { support::token(&self.syntax, T!['(']) } + #[inline] + pub fn r_paren_token(&self) -> Option { support::token(&self.syntax, T![')']) } + #[inline] + pub fn dotdot_token(&self) -> Option { support::token(&self.syntax, T![..]) } +} + #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct SelfParam { pub(crate) syntax: SyntaxNode, @@ -3697,6 +3716,20 @@ impl AstNode for ReturnExpr { #[inline] fn syntax(&self) -> &SyntaxNode { &self.syntax } } +impl AstNode for ReturnTypeSyntax { + #[inline] + fn can_cast(kind: SyntaxKind) -> bool { kind == RETURN_TYPE_SYNTAX } + #[inline] + fn cast(syntax: SyntaxNode) -> Option { + if Self::can_cast(syntax.kind()) { + Some(Self { syntax }) + } else { + None + } + } + #[inline] + fn syntax(&self) -> &SyntaxNode { &self.syntax } +} impl AstNode for SelfParam { #[inline] fn can_cast(kind: SyntaxKind) -> bool { kind == SELF_PARAM } @@ -6609,6 +6642,11 @@ impl std::fmt::Display for ReturnExpr { std::fmt::Display::fmt(self.syntax(), f) } } +impl std::fmt::Display for ReturnTypeSyntax { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} impl std::fmt::Display for SelfParam { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) diff --git a/crates/syntax/src/ast/make.rs b/crates/syntax/src/ast/make.rs index 0228d9dd71..abf1a1f382 100644 --- a/crates/syntax/src/ast/make.rs +++ b/crates/syntax/src/ast/make.rs @@ -117,7 +117,7 @@ pub fn name_ref(name_ref: &str) -> ast::NameRef { ast_from_text(&format!("fn f() {{ {raw_escape}{name_ref}; }}")) } fn raw_ident_esc(ident: &str) -> &'static str { - if is_raw_identifier(ident) { + if is_raw_identifier(ident, Edition::CURRENT) { "r#" } else { "" @@ -1035,6 +1035,7 @@ pub fn fn_( is_async: bool, is_const: bool, is_unsafe: bool, + is_gen: bool, ) -> ast::Fn { let type_params = match type_params { Some(type_params) => format!("{type_params}"), @@ -1056,9 +1057,10 @@ pub fn fn_( let async_literal = if is_async { "async " } else { "" }; let const_literal = if is_const { "const " } else { "" }; let unsafe_literal = if is_unsafe { "unsafe " } else { "" }; + let gen_literal = if is_gen { "gen " } else { "" }; ast_from_text(&format!( - "{visibility}{async_literal}{const_literal}{unsafe_literal}fn {fn_name}{type_params}{params} {ret_type}{where_clause}{body}", + "{visibility}{const_literal}{async_literal}{gen_literal}{unsafe_literal}fn {fn_name}{type_params}{params} {ret_type}{where_clause}{body}", )) } pub fn struct_( @@ -1152,12 +1154,13 @@ pub fn token(kind: SyntaxKind) -> SyntaxToken { } pub mod tokens { - use once_cell::sync::Lazy; + use std::sync::LazyLock; + use parser::Edition; use crate::{ast, AstNode, Parse, SourceFile, SyntaxKind::*, SyntaxToken}; - pub(super) static SOURCE_FILE: Lazy> = Lazy::new(|| { + pub(super) static SOURCE_FILE: LazyLock> = LazyLock::new(|| { SourceFile::parse( "const C: <()>::Item = ( true && true , true || true , 1 != 1, 2 == 2, 3 < 3, 4 <= 4, 5 > 5, 6 >= 6, !true, *p, &p , &mut p, { let _ @ [] })\n;\n\nimpl A for B where: {}", Edition::CURRENT, ) diff --git a/crates/syntax/src/ast/node_ext.rs b/crates/syntax/src/ast/node_ext.rs index 5447906206..693bfe330b 100644 --- a/crates/syntax/src/ast/node_ext.rs +++ b/crates/syntax/src/ast/node_ext.rs @@ -17,7 +17,7 @@ use crate::{ ted, NodeOrToken, SmolStr, SyntaxElement, SyntaxToken, TokenText, T, }; -use super::{RangeItem, RangeOp}; +use super::{GenericParam, RangeItem, RangeOp}; impl ast::Lifetime { pub fn text(&self) -> TokenText<'_> { @@ -822,6 +822,15 @@ pub enum TypeOrConstParam { Const(ast::ConstParam), } +impl From for GenericParam { + fn from(value: TypeOrConstParam) -> Self { + match value { + TypeOrConstParam::Type(it) => GenericParam::TypeParam(it), + TypeOrConstParam::Const(it) => GenericParam::ConstParam(it), + } + } +} + impl TypeOrConstParam { pub fn name(&self) -> Option { match self { diff --git a/crates/syntax/src/hacks.rs b/crates/syntax/src/hacks.rs index 36615d11d8..9e63448ce9 100644 --- a/crates/syntax/src/hacks.rs +++ b/crates/syntax/src/hacks.rs @@ -6,9 +6,9 @@ use parser::Edition; use crate::{ast, AstNode}; -pub fn parse_expr_from_str(s: &str) -> Option { +pub fn parse_expr_from_str(s: &str, edition: Edition) -> Option { let s = s.trim(); - let file = ast::SourceFile::parse(&format!("const _: () = {s};"), Edition::CURRENT); + let file = ast::SourceFile::parse(&format!("const _: () = {s};"), edition); let expr = file.syntax_node().descendants().find_map(ast::Expr::cast)?; if expr.syntax().text() != s { return None; diff --git a/crates/syntax/src/utils.rs b/crates/syntax/src/utils.rs index a38f8b2b55..d1f60f0b71 100644 --- a/crates/syntax/src/utils.rs +++ b/crates/syntax/src/utils.rs @@ -2,7 +2,8 @@ use crate::SyntaxKind; -pub fn is_raw_identifier(name: &str) -> bool { - let is_keyword = SyntaxKind::from_keyword(name).is_some(); +#[inline] +pub fn is_raw_identifier(name: &str, edition: parser::Edition) -> bool { + let is_keyword = SyntaxKind::from_keyword(name, edition).is_some(); is_keyword && !matches!(name, "self" | "crate" | "super" | "Self") } diff --git a/crates/test-utils/src/minicore.rs b/crates/test-utils/src/minicore.rs index 2d615c34a3..3be4469bee 100644 --- a/crates/test-utils/src/minicore.rs +++ b/crates/test-utils/src/minicore.rs @@ -63,6 +63,7 @@ //! unsize: sized //! todo: panic //! unimplemented: panic +//! column: #![rustc_coherence_is_core] @@ -1195,6 +1196,7 @@ pub mod future { #[doc(notable_trait)] #[lang = "future_trait"] pub trait Future { + #[lang = "future_output"] type Output; #[lang = "poll"] fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll; @@ -1292,6 +1294,7 @@ pub mod iter { mod traits { mod iterator { #[doc(notable_trait)] + #[lang = "iterator"] pub trait Iterator { type Item; #[lang = "next"] @@ -1615,6 +1618,14 @@ pub mod error { } // endregion:error +// region:column +#[rustc_builtin_macro] +#[macro_export] +macro_rules! column { + () => {}; +} +// endregion:column + pub mod prelude { pub mod v1 { pub use crate::{ diff --git a/crates/tt/src/lib.rs b/crates/tt/src/lib.rs index 7b72f9ff10..8d915d0a51 100644 --- a/crates/tt/src/lib.rs +++ b/crates/tt/src/lib.rs @@ -603,7 +603,7 @@ pub fn pretty(tkns: &[TokenTree]) -> String { TokenTree::Leaf(Leaf::Ident(ident)) => { format!("{}{}", ident.is_raw.as_str(), ident.sym) } - TokenTree::Leaf(Leaf::Literal(literal)) => literal.symbol.as_str().to_owned(), + TokenTree::Leaf(Leaf::Literal(literal)) => format!("{literal}"), TokenTree::Leaf(Leaf::Punct(punct)) => format!("{}", punct.char), TokenTree::Subtree(subtree) => { let content = pretty(&subtree.token_trees); diff --git a/crates/vfs/src/vfs_path.rs b/crates/vfs/src/vfs_path.rs index 92a49e0793..3c8e37413f 100644 --- a/crates/vfs/src/vfs_path.rs +++ b/crates/vfs/src/vfs_path.rs @@ -313,6 +313,20 @@ impl fmt::Debug for VfsPathRepr { } } +impl PartialEq for VfsPath { + fn eq(&self, other: &AbsPath) -> bool { + match &self.0 { + VfsPathRepr::PathBuf(lhs) => lhs == other, + VfsPathRepr::VirtualPath(_) => false, + } + } +} +impl PartialEq for AbsPath { + fn eq(&self, other: &VfsPath) -> bool { + other == self + } +} + /// `/`-separated virtual path. /// /// This is used to describe files that do not reside on the file system. diff --git a/docs/user/generated_config.adoc b/docs/user/generated_config.adoc index 2be338dd4d..e4a8c6493a 100644 --- a/docs/user/generated_config.adoc +++ b/docs/user/generated_config.adoc @@ -45,22 +45,14 @@ Automatically refresh project info via `cargo metadata` on -- Run build scripts (`build.rs`) for more precise code analysis. -- -[[rust-analyzer.cargo.buildScripts.invocationLocation]]rust-analyzer.cargo.buildScripts.invocationLocation (default: `"workspace"`):: -+ --- -Specifies the working directory for running build scripts. -- "workspace": run build scripts for a workspace in the workspace's root directory. - This is incompatible with `#rust-analyzer.cargo.buildScripts.invocationStrategy#` set to `once`. -- "root": run build scripts in the project's root directory. -This config only has an effect when `#rust-analyzer.cargo.buildScripts.overrideCommand#` -is set. --- [[rust-analyzer.cargo.buildScripts.invocationStrategy]]rust-analyzer.cargo.buildScripts.invocationStrategy (default: `"per_workspace"`):: + -- Specifies the invocation strategy to use when running the build scripts command. -If `per_workspace` is set, the command will be executed for each workspace. -If `once` is set, the command will be executed once. +If `per_workspace` is set, the command will be executed for each Rust workspace with the +workspace as the working directory. +If `once` is set, the command will be executed once with the opened project as the +working directory. This config only has an effect when `#rust-analyzer.cargo.buildScripts.overrideCommand#` is set. -- @@ -75,8 +67,7 @@ option. If there are multiple linked projects/workspaces, this command is invoked for each of them, with the working directory being the workspace root (i.e., the folder containing the `Cargo.toml`). This can be overwritten -by changing `#rust-analyzer.cargo.buildScripts.invocationStrategy#` and -`#rust-analyzer.cargo.buildScripts.invocationLocation#`. +by changing `#rust-analyzer.cargo.buildScripts.invocationStrategy#`. By default, a cargo invocation will be constructed for the configured targets and features, with the following base command line: @@ -209,16 +200,6 @@ List of `cargo check` (or other command specified in `check.command`) diagnostic For example for `cargo check`: `dead_code`, `unused_imports`, `unused_variables`,... -- -[[rust-analyzer.check.invocationLocation]]rust-analyzer.check.invocationLocation (default: `"workspace"`):: -+ --- -Specifies the working directory for running checks. -- "workspace": run checks for workspaces in the corresponding workspaces' root directories. - This falls back to "root" if `#rust-analyzer.check.invocationStrategy#` is set to `once`. -- "root": run checks in the project's root directory. -This config only has an effect when `#rust-analyzer.check.overrideCommand#` -is set. --- [[rust-analyzer.check.invocationStrategy]]rust-analyzer.check.invocationStrategy (default: `"per_workspace"`):: + -- @@ -250,8 +231,7 @@ Cargo, you might also want to change If there are multiple linked projects/workspaces, this command is invoked for each of them, with the working directory being the workspace root (i.e., the folder containing the `Cargo.toml`). This can be overwritten -by changing `#rust-analyzer.check.invocationStrategy#` and -`#rust-analyzer.check.invocationLocation#`. +by changing `#rust-analyzer.check.invocationStrategy#`. If `$saved_file` is part of the command, rust-analyzer will pass the absolute path of the saved file to the provided command. This is diff --git a/docs/user/manual.adoc b/docs/user/manual.adoc index 703ec66921..246ebdab2c 100644 --- a/docs/user/manual.adoc +++ b/docs/user/manual.adoc @@ -705,6 +705,12 @@ interface JsonProject { /// several different "sysroots" in one graph of /// crates. sysroot_src?: string; + /// List of groups of common cfg values, to allow + /// sharing them between crates. + /// + /// Maps from group name to its cfgs. Cfg follow + /// the same format as `Crate.cfg`. + cfg_groups?: { [key: string]: string[]; }; /// The set of crates comprising the current /// project. Must include all transitive /// dependencies as well as sysroot crate (libstd, @@ -754,6 +760,12 @@ interface Crate { include_dirs: string[], exclude_dirs: string[], }, + /// List of cfg groups this crate inherits. + /// + /// All cfg in these groups will be concatenated to + /// `cfg`. It is impossible to replace a value from + /// the groups. + cfg_groups?: string[]; /// The set of cfgs activated for a given crate, like /// `["unix", "feature=\"foo\"", "feature=\"bar\""]`. cfg: string[]; diff --git a/editors/code/package.json b/editors/code/package.json index bf9c4a366d..98e8bbf02a 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -330,14 +330,6 @@ "default": false, "type": "boolean" }, - "rust-analyzer.discoverProjectRunner": { - "markdownDescription": "Sets the extension responsible for determining which extension the rust-analyzer extension uses to generate `rust-project.json` files. This should should only be used\n if a build system like Buck or Bazel is also in use.", - "default": null, - "type": [ - "null", - "string" - ] - }, "rust-analyzer.showUnlinkedFileNotification": { "markdownDescription": "Whether to show a notification for unlinked files asking the user to add the corresponding Cargo.toml to the linked projects setting.", "default": true, @@ -671,29 +663,11 @@ } } }, - { - "title": "cargo", - "properties": { - "rust-analyzer.cargo.buildScripts.invocationLocation": { - "markdownDescription": "Specifies the working directory for running build scripts.\n- \"workspace\": run build scripts for a workspace in the workspace's root directory.\n This is incompatible with `#rust-analyzer.cargo.buildScripts.invocationStrategy#` set to `once`.\n- \"root\": run build scripts in the project's root directory.\nThis config only has an effect when `#rust-analyzer.cargo.buildScripts.overrideCommand#`\nis set.", - "default": "workspace", - "type": "string", - "enum": [ - "workspace", - "root" - ], - "enumDescriptions": [ - "The command will be executed in the corresponding workspace root.", - "The command will be executed in the project root." - ] - } - } - }, { "title": "cargo", "properties": { "rust-analyzer.cargo.buildScripts.invocationStrategy": { - "markdownDescription": "Specifies the invocation strategy to use when running the build scripts command.\nIf `per_workspace` is set, the command will be executed for each workspace.\nIf `once` is set, the command will be executed once.\nThis config only has an effect when `#rust-analyzer.cargo.buildScripts.overrideCommand#`\nis set.", + "markdownDescription": "Specifies the invocation strategy to use when running the build scripts command.\nIf `per_workspace` is set, the command will be executed for each Rust workspace with the\nworkspace as the working directory.\nIf `once` is set, the command will be executed once with the opened project as the\nworking directory.\nThis config only has an effect when `#rust-analyzer.cargo.buildScripts.overrideCommand#`\nis set.", "default": "per_workspace", "type": "string", "enum": [ @@ -701,8 +675,8 @@ "once" ], "enumDescriptions": [ - "The command will be executed for each workspace.", - "The command will be executed once." + "The command will be executed for each Rust workspace with the workspace as the working directory.", + "The command will be executed once with the opened project as the working directory." ] } } @@ -711,7 +685,7 @@ "title": "cargo", "properties": { "rust-analyzer.cargo.buildScripts.overrideCommand": { - "markdownDescription": "Override the command rust-analyzer uses to run build scripts and\nbuild procedural macros. The command is required to output json\nand should therefore include `--message-format=json` or a similar\noption.\n\nIf there are multiple linked projects/workspaces, this command is invoked for\neach of them, with the working directory being the workspace root\n(i.e., the folder containing the `Cargo.toml`). This can be overwritten\nby changing `#rust-analyzer.cargo.buildScripts.invocationStrategy#` and\n`#rust-analyzer.cargo.buildScripts.invocationLocation#`.\n\nBy default, a cargo invocation will be constructed for the configured\ntargets and features, with the following base command line:\n\n```bash\ncargo check --quiet --workspace --message-format=json --all-targets --keep-going\n```\n.", + "markdownDescription": "Override the command rust-analyzer uses to run build scripts and\nbuild procedural macros. The command is required to output json\nand should therefore include `--message-format=json` or a similar\noption.\n\nIf there are multiple linked projects/workspaces, this command is invoked for\neach of them, with the working directory being the workspace root\n(i.e., the folder containing the `Cargo.toml`). This can be overwritten\nby changing `#rust-analyzer.cargo.buildScripts.invocationStrategy#`.\n\nBy default, a cargo invocation will be constructed for the configured\ntargets and features, with the following base command line:\n\n```bash\ncargo check --quiet --workspace --message-format=json --all-targets --keep-going\n```\n.", "default": null, "type": [ "null", @@ -973,24 +947,6 @@ } } }, - { - "title": "check", - "properties": { - "rust-analyzer.check.invocationLocation": { - "markdownDescription": "Specifies the working directory for running checks.\n- \"workspace\": run checks for workspaces in the corresponding workspaces' root directories.\n This falls back to \"root\" if `#rust-analyzer.check.invocationStrategy#` is set to `once`.\n- \"root\": run checks in the project's root directory.\nThis config only has an effect when `#rust-analyzer.check.overrideCommand#`\nis set.", - "default": "workspace", - "type": "string", - "enum": [ - "workspace", - "root" - ], - "enumDescriptions": [ - "The command will be executed in the corresponding workspace root.", - "The command will be executed in the project root." - ] - } - } - }, { "title": "check", "properties": { @@ -1003,8 +959,8 @@ "once" ], "enumDescriptions": [ - "The command will be executed for each workspace.", - "The command will be executed once." + "The command will be executed for each Rust workspace with the workspace as the working directory.", + "The command will be executed once with the opened project as the working directory." ] } } @@ -1026,7 +982,7 @@ "title": "check", "properties": { "rust-analyzer.check.overrideCommand": { - "markdownDescription": "Override the command rust-analyzer uses instead of `cargo check` for\ndiagnostics on save. The command is required to output json and\nshould therefore include `--message-format=json` or a similar option\n(if your client supports the `colorDiagnosticOutput` experimental\ncapability, you can use `--message-format=json-diagnostic-rendered-ansi`).\n\nIf you're changing this because you're using some tool wrapping\nCargo, you might also want to change\n`#rust-analyzer.cargo.buildScripts.overrideCommand#`.\n\nIf there are multiple linked projects/workspaces, this command is invoked for\neach of them, with the working directory being the workspace root\n(i.e., the folder containing the `Cargo.toml`). This can be overwritten\nby changing `#rust-analyzer.check.invocationStrategy#` and\n`#rust-analyzer.check.invocationLocation#`.\n\nIf `$saved_file` is part of the command, rust-analyzer will pass\nthe absolute path of the saved file to the provided command. This is\nintended to be used with non-Cargo build systems.\nNote that `$saved_file` is experimental and may be removed in the future.\n\nAn example command would be:\n\n```bash\ncargo check --workspace --message-format=json --all-targets\n```\n.", + "markdownDescription": "Override the command rust-analyzer uses instead of `cargo check` for\ndiagnostics on save. The command is required to output json and\nshould therefore include `--message-format=json` or a similar option\n(if your client supports the `colorDiagnosticOutput` experimental\ncapability, you can use `--message-format=json-diagnostic-rendered-ansi`).\n\nIf you're changing this because you're using some tool wrapping\nCargo, you might also want to change\n`#rust-analyzer.cargo.buildScripts.overrideCommand#`.\n\nIf there are multiple linked projects/workspaces, this command is invoked for\neach of them, with the working directory being the workspace root\n(i.e., the folder containing the `Cargo.toml`). This can be overwritten\nby changing `#rust-analyzer.check.invocationStrategy#`.\n\nIf `$saved_file` is part of the command, rust-analyzer will pass\nthe absolute path of the saved file to the provided command. This is\nintended to be used with non-Cargo build systems.\nNote that `$saved_file` is experimental and may be removed in the future.\n\nAn example command would be:\n\n```bash\ncargo check --workspace --message-format=json --all-targets\n```\n.", "default": null, "type": [ "null", diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts index dc0165df71..1e3dc60809 100644 --- a/editors/code/src/config.ts +++ b/editors/code/src/config.ts @@ -252,10 +252,6 @@ export class Config { await this.cfg.update("checkOnSave", !(value || false), target || null, overrideInLanguage); } - get discoverProjectRunner(): string | undefined { - return this.get("discoverProjectRunner"); - } - get problemMatcher(): string[] { return this.get("runnables.problemMatcher") || []; } diff --git a/rust-version b/rust-version index d4f1703d85..5f21a43f3b 100644 --- a/rust-version +++ b/rust-version @@ -1 +1 @@ -80eb5a8e910e5185d47cdefe3732d839c78a5e7e +6cf068db566de080dfa7ed24a216ea3aed2b98ce diff --git a/xtask/src/codegen/grammar.rs b/xtask/src/codegen/grammar.rs index 0352539754..39e06f9642 100644 --- a/xtask/src/codegen/grammar.rs +++ b/xtask/src/codegen/grammar.rs @@ -396,24 +396,66 @@ fn generate_syntax_kinds(grammar: KindsSrc) -> String { let punctuation = grammar.punct.iter().map(|(_token, name)| format_ident!("{}", name)).collect::>(); - let x = |&name| match name { + let fmt_kw_as_variant = |&name| match name { "Self" => format_ident!("SELF_TYPE_KW"), name => format_ident!("{}_KW", to_upper_snake_case(name)), }; - let full_keywords_values = grammar.keywords; - let full_keywords = full_keywords_values.iter().map(x); + let strict_keywords = grammar.keywords; + let strict_keywords_variants = + strict_keywords.iter().map(fmt_kw_as_variant).collect::>(); + let strict_keywords_tokens = strict_keywords.iter().map(|it| format_ident!("{it}")); - let contextual_keywords_values = &grammar.contextual_keywords; - let contextual_keywords = contextual_keywords_values.iter().map(x); - - let all_keywords_values = grammar - .keywords + let edition_dependent_keywords_variants_match_arm = grammar + .edition_dependent_keywords .iter() - .chain(grammar.contextual_keywords.iter()) - .copied() + .map(|(kw, ed)| { + let kw = fmt_kw_as_variant(kw); + quote! { #kw if #ed <= edition } + }) + .collect::>(); + let edition_dependent_keywords_str_match_arm = grammar + .edition_dependent_keywords + .iter() + .map(|(kw, ed)| { + quote! { #kw if #ed <= edition } + }) + .collect::>(); + let edition_dependent_keywords_variants = grammar + .edition_dependent_keywords + .iter() + .map(|(kw, _)| fmt_kw_as_variant(kw)) + .collect::>(); + let edition_dependent_keywords_tokens = + grammar.edition_dependent_keywords.iter().map(|(it, _)| format_ident!("{it}")); + + let contextual_keywords = grammar.contextual_keywords; + let contextual_keywords_variants = + contextual_keywords.iter().map(fmt_kw_as_variant).collect::>(); + let contextual_keywords_tokens = contextual_keywords.iter().map(|it| format_ident!("{it}")); + let contextual_keywords_str_match_arm = grammar.contextual_keywords.iter().map(|kw| { + match grammar.edition_dependent_keywords.iter().find(|(ed_kw, _)| ed_kw == kw) { + Some((_, ed)) => quote! { #kw if edition < #ed }, + None => quote! { #kw }, + } + }); + let contextual_keywords_variants_match_arm = grammar + .contextual_keywords + .iter() + .map(|kw_s| { + let kw = fmt_kw_as_variant(kw_s); + match grammar.edition_dependent_keywords.iter().find(|(ed_kw, _)| ed_kw == kw_s) { + Some((_, ed)) => quote! { #kw if edition < #ed }, + None => quote! { #kw }, + } + }) + .collect::>(); + + let non_strict_keyword_variants = contextual_keywords_variants + .iter() + .chain(edition_dependent_keywords_variants.iter()) + .sorted() + .dedup() .collect::>(); - let all_keywords_idents = all_keywords_values.iter().map(|kw| format_ident!("{}", kw)); - let all_keywords = all_keywords_values.iter().map(x).collect::>(); let literals = grammar.literals.iter().map(|name| format_ident!("{}", name)).collect::>(); @@ -424,6 +466,8 @@ fn generate_syntax_kinds(grammar: KindsSrc) -> String { let ast = quote! { #![allow(bad_style, missing_docs, unreachable_pub)] + use crate::Edition; + /// The kind of syntax node, e.g. `IDENT`, `USE_KW`, or `STRUCT`. #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] #[repr(u16)] @@ -435,7 +479,8 @@ fn generate_syntax_kinds(grammar: KindsSrc) -> String { #[doc(hidden)] EOF, #(#punctuation,)* - #(#all_keywords,)* + #(#strict_keywords_variants,)* + #(#non_strict_keyword_variants,)* #(#literals,)* #(#tokens,)* #(#nodes,)* @@ -447,31 +492,55 @@ fn generate_syntax_kinds(grammar: KindsSrc) -> String { use self::SyntaxKind::*; impl SyntaxKind { - pub fn is_keyword(self) -> bool { - matches!(self, #(#all_keywords)|*) + /// Checks whether this syntax kind is a strict keyword for the given edition. + /// Strict keywords are identifiers that are always considered keywords. + pub fn is_strict_keyword(self, edition: Edition) -> bool { + matches!(self, #(#strict_keywords_variants)|*) + || match self { + #(#edition_dependent_keywords_variants_match_arm => true,)* + _ => false, + } + } + + /// Checks whether this syntax kind is a weak keyword for the given edition. + /// Weak keywords are identifiers that are considered keywords only in certain contexts. + pub fn is_contextual_keyword(self, edition: Edition) -> bool { + match self { + #(#contextual_keywords_variants_match_arm => true,)* + _ => false, + } + } + + /// Checks whether this syntax kind is a strict or weak keyword for the given edition. + pub fn is_keyword(self, edition: Edition) -> bool { + matches!(self, #(#strict_keywords_variants)|*) + || match self { + #(#edition_dependent_keywords_variants_match_arm => true,)* + #(#contextual_keywords_variants_match_arm => true,)* + _ => false, + } } pub fn is_punct(self) -> bool { - matches!(self, #(#punctuation)|*) - } pub fn is_literal(self) -> bool { matches!(self, #(#literals)|*) } - pub fn from_keyword(ident: &str) -> Option { + pub fn from_keyword(ident: &str, edition: Edition) -> Option { let kw = match ident { - #(#full_keywords_values => #full_keywords,)* + #(#strict_keywords => #strict_keywords_variants,)* + #(#edition_dependent_keywords_str_match_arm => #edition_dependent_keywords_variants,)* _ => return None, }; Some(kw) } - pub fn from_contextual_keyword(ident: &str) -> Option { + pub fn from_contextual_keyword(ident: &str, edition: Edition) -> Option { let kw = match ident { - #(#contextual_keywords_values => #contextual_keywords,)* + #(#contextual_keywords_str_match_arm => #contextual_keywords_variants,)* _ => return None, }; Some(kw) @@ -489,7 +558,9 @@ fn generate_syntax_kinds(grammar: KindsSrc) -> String { #[macro_export] macro_rules! T { #([#punctuation_values] => { $crate::SyntaxKind::#punctuation };)* - #([#all_keywords_idents] => { $crate::SyntaxKind::#all_keywords };)* + #([#strict_keywords_tokens] => { $crate::SyntaxKind::#strict_keywords_variants };)* + #([#contextual_keywords_tokens] => { $crate::SyntaxKind::#contextual_keywords_variants };)* + #([#edition_dependent_keywords_tokens] => { $crate::SyntaxKind::#edition_dependent_keywords_variants };)* [lifetime_ident] => { $crate::SyntaxKind::LIFETIME_IDENT }; [int_number] => { $crate::SyntaxKind::INT_NUMBER }; [ident] => { $crate::SyntaxKind::IDENT }; diff --git a/xtask/src/codegen/grammar/ast_src.rs b/xtask/src/codegen/grammar/ast_src.rs index 3444f89908..34151bd958 100644 --- a/xtask/src/codegen/grammar/ast_src.rs +++ b/xtask/src/codegen/grammar/ast_src.rs @@ -1,5 +1,7 @@ //! Defines input for code generation process. +use quote::ToTokens; + use crate::codegen::grammar::to_upper_snake_case; #[derive(Copy, Clone, Debug)] @@ -10,6 +12,35 @@ pub(crate) struct KindsSrc { pub(crate) literals: &'static [&'static str], pub(crate) tokens: &'static [&'static str], pub(crate) nodes: &'static [&'static str], + pub(crate) edition_dependent_keywords: &'static [(&'static str, Edition)], +} + +#[allow(dead_code)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub(super) enum Edition { + Edition2015, + Edition2018, + Edition2021, + Edition2024, +} + +impl ToTokens for Edition { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + match self { + Edition::Edition2015 => { + tokens.extend(quote::quote! { Edition::Edition2015 }); + } + Edition::Edition2018 => { + tokens.extend(quote::quote! { Edition::Edition2018 }); + } + Edition::Edition2021 => { + tokens.extend(quote::quote! { Edition::Edition2021 }); + } + Edition::Edition2024 => { + tokens.extend(quote::quote! { Edition::Edition2024 }); + } + } + } } /// The punctuations of the language. @@ -75,17 +106,32 @@ const EOF: &str = "EOF"; const RESERVED: &[&str] = &[ "abstract", "become", "box", "do", "final", "macro", "override", "priv", "typeof", "unsized", - "virtual", "yield", "try", + "virtual", "yield", +]; +// keywords that are keywords only in specific parse contexts +#[doc(alias = "WEAK_KEYWORDS")] +const CONTEXTUAL_KEYWORDS: &[&str] = + &["macro_rules", "union", "default", "raw", "dyn", "auto", "yeet"]; +// keywords we use for special macro expansions +const CONTEXTUAL_BUILTIN_KEYWORDS: &[&str] = &["builtin", "offset_of", "format_args", "asm"]; +// keywords that are keywords depending on the edition +const EDITION_DEPENDENT_KEYWORDS: &[(&str, Edition)] = &[ + ("try", Edition::Edition2018), + ("dyn", Edition::Edition2018), + ("async", Edition::Edition2018), + ("await", Edition::Edition2018), + ("gen", Edition::Edition2024), ]; -const CONTEXTUAL_RESERVED: &[&str] = &[]; pub(crate) fn generate_kind_src( nodes: &[AstNodeSrc], enums: &[AstEnumSrc], grammar: &ungrammar::Grammar, ) -> KindsSrc { + let mut contextual_keywords: Vec<&_> = + CONTEXTUAL_KEYWORDS.iter().chain(CONTEXTUAL_BUILTIN_KEYWORDS).copied().collect(); + let mut keywords: Vec<&_> = Vec::new(); - let mut contextual_keywords: Vec<&_> = Vec::new(); let mut tokens: Vec<&_> = TOKENS.to_vec(); let mut literals: Vec<&_> = Vec::new(); let mut used_puncts = vec![false; PUNCT.len()]; @@ -103,9 +149,7 @@ pub(crate) fn generate_kind_src( ("#", token) if !token.is_empty() => { tokens.push(String::leak(to_upper_snake_case(token))); } - ("?", kw) if !kw.is_empty() => { - contextual_keywords.push(String::leak(kw.to_owned())); - } + _ if contextual_keywords.contains(&name) => {} _ if name.chars().all(char::is_alphabetic) => { keywords.push(String::leak(name.to_owned())); } @@ -124,9 +168,14 @@ pub(crate) fn generate_kind_src( keywords.extend(RESERVED.iter().copied()); keywords.sort(); keywords.dedup(); - contextual_keywords.extend(CONTEXTUAL_RESERVED.iter().copied()); contextual_keywords.sort(); contextual_keywords.dedup(); + let mut edition_dependent_keywords: Vec<(&_, _)> = EDITION_DEPENDENT_KEYWORDS.to_vec(); + edition_dependent_keywords.sort(); + edition_dependent_keywords.dedup(); + + keywords.retain(|&it| !contextual_keywords.contains(&it)); + keywords.retain(|&it| !edition_dependent_keywords.iter().any(|&(kw, _)| kw == it)); // we leak things here for simplicity, that way we don't have to deal with lifetimes // The execution is a one shot job so thats fine @@ -142,12 +191,21 @@ pub(crate) fn generate_kind_src( nodes.sort(); let keywords = Vec::leak(keywords); let contextual_keywords = Vec::leak(contextual_keywords); + let edition_dependent_keywords = Vec::leak(edition_dependent_keywords); let literals = Vec::leak(literals); literals.sort(); let tokens = Vec::leak(tokens); tokens.sort(); - KindsSrc { punct: PUNCT, nodes, keywords, contextual_keywords, literals, tokens } + KindsSrc { + punct: PUNCT, + nodes, + keywords, + contextual_keywords, + edition_dependent_keywords, + literals, + tokens, + } } #[derive(Default, Debug)]