Unnamed repository; edit this file 'description' to name the repository.
Auto merge of #134177 - matthiaskrgr:rollup-hgp8q60, r=matthiaskrgr
Rollup of 6 pull requests
Successful merges:
- #132975 (De-duplicate and improve definition of core::ffi::c_char)
- #133598 (Change `GetManyMutError` to match T-libs-api decision)
- #134148 (add comments in check_expr_field)
- #134163 (coverage: Rearrange the code for embedding per-function coverage metadata)
- #134165 (wasm(32|64): update alignment string)
- #134170 (Subtree update of `rust-analyzer`)
r? `@ghost`
`@rustbot` modify labels: rollup
226 files changed, 7515 insertions, 2865 deletions
diff --git a/Cargo.lock b/Cargo.lock index 2bd4d17fe2..8c1d82de1d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -85,6 +85,12 @@ dependencies = [ ] [[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -509,6 +515,7 @@ dependencies = [ "base-db", "cfg", "either", + "expect-test", "hir-def", "hir-expand", "hir-ty", @@ -519,6 +526,9 @@ dependencies = [ "span", "stdx", "syntax", + "syntax-bridge", + "test-fixture", + "test-utils", "tracing", "triomphe", "tt", @@ -1492,9 +1502,9 @@ dependencies = [ [[package]] name = "ra-ap-rustc_abi" -version = "0.80.0" +version = "0.85.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613760a3071b25a67a8d7bc97b37c7fd4722562e9479137b83ae9cf8f8c1601a" +checksum = "af462c3a2d524b84a51b6848b439787f01b35c6c1086d3e3086a5f5eea92ed9a" dependencies = [ "bitflags 2.6.0", "ra-ap-rustc_index", @@ -1503,20 +1513,19 @@ dependencies = [ [[package]] name = "ra-ap-rustc_index" -version = "0.80.0" +version = "0.85.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b2bc6b4ecede8ff28295041e22c2e66853f8e0125990c05135bad3c30bad12c" +checksum = "be6bb8cb0ab78d94a222f1ffd3e87254cdfb57413382b8d6ebe26a85482f99d1" dependencies = [ - "arrayvec", "ra-ap-rustc_index_macros", "smallvec", ] [[package]] name = "ra-ap-rustc_index_macros" -version = "0.80.0" +version = "0.85.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2374a39fb2d92d0509178c2b442eadca3cc10e403ef9729a040c1855b08ff261" +checksum = "c24b1641455b46e87435b7321219672077066e678963d239a4a2904732979b16" dependencies = [ "proc-macro2", "quote", @@ -1525,9 +1534,9 @@ dependencies = [ [[package]] name = "ra-ap-rustc_lexer" -version = "0.80.0" +version = "0.85.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a2cf8e48b69af3ecc29ed3449892e8a999111d2f75212a78aa242e117cf1711" +checksum = "94daa86974417981fed2f12bd8fb00158dfa6fee561152bed689278c846d0272" dependencies = [ "unicode-properties", "unicode-xid", @@ -1535,9 +1544,9 @@ dependencies = [ [[package]] name = "ra-ap-rustc_parse_format" -version = "0.80.0" +version = "0.85.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d6f59a22b559263c5c42747ae362cf5d4fb272293fa119a4623f8ec288f9656" +checksum = "fc07f6bd581746f358e39c4b6bfe8d455b3d6ad1a857821016d0d42eeb5e1e3e" dependencies = [ "ra-ap-rustc_index", "ra-ap-rustc_lexer", @@ -1545,9 +1554,9 @@ dependencies = [ [[package]] name = "ra-ap-rustc_pattern_analysis" -version = "0.80.0" +version = "0.85.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7d0575b54ffe09bc5d2f158454bc05f0c30c01d9992310965f854be50ae22b8" +checksum = "2f49b86e1276c1c3c72898410def29b699415f4e7d1dfb3531daf79794694372" dependencies = [ "ra-ap-rustc_index", "rustc-hash 2.0.0", @@ -1645,6 +1654,7 @@ version = "0.0.0" dependencies = [ "always-assert", "anyhow", + "base64", "cargo_metadata", "cfg", "crossbeam-channel", @@ -1655,6 +1665,7 @@ dependencies = [ "hir-def", "hir-ty", "ide", + "ide-completion", "ide-db", "ide-ssr", "intern", @@ -1683,6 +1694,7 @@ dependencies = [ "stdx", "syntax", "syntax-bridge", + "tenthash", "test-fixture", "test-utils", "tikv-jemallocator", @@ -1987,6 +1999,12 @@ dependencies = [ ] [[package]] +name = "tenthash" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d67f9f3cf70e0852941d7bc3cb884b49b24b8ee956baf91ad0abae31f5ef11fb" + +[[package]] name = "test-fixture" version = "0.0.0" dependencies = [ diff --git a/Cargo.toml b/Cargo.toml index 632b290ba9..f7074f9135 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -84,11 +84,11 @@ tt = { path = "./crates/tt", version = "0.0.0" } vfs-notify = { path = "./crates/vfs-notify", version = "0.0.0" } vfs = { path = "./crates/vfs", version = "0.0.0" } -ra-ap-rustc_lexer = { version = "0.80", default-features = false } -ra-ap-rustc_parse_format = { version = "0.80", default-features = false } -ra-ap-rustc_index = { version = "0.80", default-features = false } -ra-ap-rustc_abi = { version = "0.80", default-features = false } -ra-ap-rustc_pattern_analysis = { version = "0.80", default-features = false } +ra-ap-rustc_lexer = { version = "0.85", default-features = false } +ra-ap-rustc_parse_format = { version = "0.85", default-features = false } +ra-ap-rustc_index = { version = "0.85", default-features = false } +ra-ap-rustc_abi = { version = "0.85", default-features = false } +ra-ap-rustc_pattern_analysis = { version = "0.85", default-features = false } # local crates that aren't published to crates.io. These should not have versions. test-fixture = { path = "./crates/test-fixture" } diff --git a/crates/base-db/src/input.rs b/crates/base-db/src/input.rs index 57522d6932..e86944eeb3 100644 --- a/crates/base-db/src/input.rs +++ b/crates/base-db/src/input.rs @@ -547,29 +547,6 @@ impl CrateGraph { None } - // Work around for https://github.com/rust-lang/rust-analyzer/issues/6038. - // As hacky as it gets. - pub fn patch_cfg_if(&mut self) -> bool { - // we stupidly max by version in an attempt to have all duplicated std's depend on the same cfg_if so that deduplication still works - let cfg_if = - self.hacky_find_crate("cfg_if").max_by_key(|&it| self.arena[it].version.clone()); - let std = self.hacky_find_crate("std").next(); - match (cfg_if, std) { - (Some(cfg_if), Some(std)) => { - self.arena[cfg_if].dependencies.clear(); - self.arena[std] - .dependencies - .push(Dependency::new(CrateName::new("cfg_if").unwrap(), cfg_if)); - true - } - _ => false, - } - } - - fn hacky_find_crate<'a>(&'a self, display_name: &'a str) -> impl Iterator<Item = CrateId> + 'a { - self.iter().filter(move |it| self[*it].display_name.as_deref() == Some(display_name)) - } - /// Removes all crates from this crate graph except for the ones in `to_keep` and fixes up the dependencies. /// Returns a mapping from old crate ids to new crate ids. pub fn remove_crates_except(&mut self, to_keep: &[CrateId]) -> Vec<Option<CrateId>> { diff --git a/crates/hir-def/src/body.rs b/crates/hir-def/src/body.rs index 5a386f6cf8..867bee95be 100644 --- a/crates/hir-def/src/body.rs +++ b/crates/hir-def/src/body.rs @@ -31,7 +31,7 @@ use crate::{ path::{ModPath, Path}, src::HasSource, type_ref::{TypeRef, TypeRefId, TypesMap, TypesSourceMap}, - BlockId, DefWithBodyId, HasModule, Lookup, + BlockId, DefWithBodyId, HasModule, Lookup, SyntheticSyntax, }; /// A wrapper around [`span::SyntaxContextId`] that is intended only for comparisons. @@ -141,7 +141,7 @@ pub struct BodySourceMap { field_map_back: FxHashMap<ExprId, FieldSource>, pat_field_map_back: FxHashMap<PatId, PatFieldSource>, - types: TypesSourceMap, + pub types: TypesSourceMap, // FIXME: Make this a sane struct. template_map: Option< @@ -160,9 +160,6 @@ pub struct BodySourceMap { diagnostics: Vec<BodyDiagnostic>, } -#[derive(Default, Debug, Eq, PartialEq, Clone, Copy)] -pub struct SyntheticSyntax; - #[derive(Debug, Eq, PartialEq)] pub enum BodyDiagnostic { InactiveCode { node: InFile<SyntaxNodePtr>, cfg: CfgExpr, opts: CfgOptions }, @@ -408,7 +405,8 @@ impl Body { f(else_branch); } } - Expr::Let { expr, .. } => { + Expr::Let { expr, pat } => { + self.walk_exprs_in_pat(*pat, &mut f); f(*expr); } Expr::Block { statements, tail, .. } @@ -444,7 +442,10 @@ impl Body { } Expr::Match { expr, arms } => { f(*expr); - arms.iter().map(|arm| arm.expr).for_each(f); + arms.iter().for_each(|arm| { + f(arm.expr); + self.walk_exprs_in_pat(arm.pat, &mut f); + }); } Expr::Break { expr, .. } | Expr::Return { expr } @@ -505,6 +506,131 @@ impl Body { } } + pub fn walk_child_exprs_without_pats(&self, expr_id: ExprId, mut f: impl FnMut(ExprId)) { + let expr = &self[expr_id]; + match expr { + Expr::Continue { .. } + | Expr::Const(_) + | Expr::Missing + | Expr::Path(_) + | Expr::OffsetOf(_) + | Expr::Literal(_) + | Expr::Underscore => {} + Expr::InlineAsm(it) => it.operands.iter().for_each(|(_, op)| match op { + AsmOperand::In { expr, .. } + | AsmOperand::Out { expr: Some(expr), .. } + | AsmOperand::InOut { expr, .. } => f(*expr), + AsmOperand::SplitInOut { in_expr, out_expr, .. } => { + f(*in_expr); + if let Some(out_expr) = out_expr { + f(*out_expr); + } + } + AsmOperand::Out { expr: None, .. } + | AsmOperand::Const(_) + | AsmOperand::Label(_) + | AsmOperand::Sym(_) => (), + }), + Expr::If { condition, then_branch, else_branch } => { + f(*condition); + f(*then_branch); + if let &Some(else_branch) = else_branch { + f(else_branch); + } + } + Expr::Let { expr, .. } => { + f(*expr); + } + Expr::Block { statements, tail, .. } + | Expr::Unsafe { statements, tail, .. } + | Expr::Async { statements, tail, .. } => { + for stmt in statements.iter() { + match stmt { + Statement::Let { initializer, else_branch, .. } => { + if let &Some(expr) = initializer { + f(expr); + } + if let &Some(expr) = else_branch { + f(expr); + } + } + Statement::Expr { expr: expression, .. } => f(*expression), + Statement::Item(_) => (), + } + } + if let &Some(expr) = tail { + f(expr); + } + } + Expr::Loop { body, .. } => f(*body), + Expr::Call { callee, args, .. } => { + f(*callee); + args.iter().copied().for_each(f); + } + Expr::MethodCall { receiver, args, .. } => { + f(*receiver); + args.iter().copied().for_each(f); + } + Expr::Match { expr, arms } => { + f(*expr); + arms.iter().map(|arm| arm.expr).for_each(f); + } + Expr::Break { expr, .. } + | Expr::Return { expr } + | Expr::Yield { expr } + | Expr::Yeet { expr } => { + if let &Some(expr) = expr { + f(expr); + } + } + Expr::Become { expr } => f(*expr), + Expr::RecordLit { fields, spread, .. } => { + for field in fields.iter() { + f(field.expr); + } + if let &Some(expr) = spread { + f(expr); + } + } + Expr::Closure { body, .. } => { + f(*body); + } + Expr::BinaryOp { lhs, rhs, .. } => { + f(*lhs); + f(*rhs); + } + Expr::Range { lhs, rhs, .. } => { + if let &Some(lhs) = rhs { + f(lhs); + } + if let &Some(rhs) = lhs { + f(rhs); + } + } + Expr::Index { base, index, .. } => { + f(*base); + f(*index); + } + Expr::Field { expr, .. } + | Expr::Await { expr } + | Expr::Cast { expr, .. } + | Expr::Ref { expr, .. } + | Expr::UnaryOp { expr, .. } + | Expr::Box { expr } => { + f(*expr); + } + Expr::Tuple { exprs, .. } => exprs.iter().copied().for_each(f), + Expr::Array(a) => match a { + Array::ElementList { elements, .. } => elements.iter().copied().for_each(f), + Array::Repeat { initializer, repeat } => { + f(*initializer); + f(*repeat) + } + }, + &Expr::Assignment { target: _, value } => f(value), + } + } + pub fn walk_exprs_in_pat(&self, pat_id: PatId, f: &mut impl FnMut(ExprId)) { self.walk_pats(pat_id, &mut |pat| { if let Pat::Expr(expr) | Pat::ConstBlock(expr) = self[pat] { diff --git a/crates/hir-def/src/body/lower.rs b/crates/hir-def/src/body/lower.rs index 1ab49e9156..3b73d40963 100644 --- a/crates/hir-def/src/body/lower.rs +++ b/crates/hir-def/src/body/lower.rs @@ -1510,20 +1510,20 @@ impl ExprCollector<'_> { BuiltinShadowMode::Other, None, ); + // Funnily enough, record structs/variants *can* be shadowed + // by pattern bindings (but unit or tuple structs/variants + // can't). match resolved.take_values() { Some(ModuleDefId::ConstId(_)) => (None, Pat::Path(name.into())), - Some(ModuleDefId::EnumVariantId(_)) => { - // this is only really valid for unit variants, but - // shadowing other enum variants with a pattern is - // an error anyway + Some(ModuleDefId::EnumVariantId(variant)) + if self.db.variant_data(variant.into()).kind() + != StructKind::Record => + { (None, Pat::Path(name.into())) } Some(ModuleDefId::AdtId(AdtId::StructId(s))) if self.db.struct_data(s).variant_data.kind() != StructKind::Record => { - // Funnily enough, record structs *can* be shadowed - // by pattern bindings (but unit or tuple structs - // can't). (None, Pat::Path(name.into())) } // shadowing statics is an error as well, so we just ignore that case here diff --git a/crates/hir-def/src/body/tests.rs b/crates/hir-def/src/body/tests.rs index 3b29d98d19..8f01091584 100644 --- a/crates/hir-def/src/body/tests.rs +++ b/crates/hir-def/src/body/tests.rs @@ -1,6 +1,5 @@ mod block; -use base_db::SourceDatabase; use expect_test::{expect, Expect}; use test_fixture::WithFixture; @@ -11,7 +10,7 @@ use super::*; fn lower(ra_fixture: &str) -> (TestDB, Arc<Body>, DefWithBodyId) { let db = TestDB::with_files(ra_fixture); - let krate = db.crate_graph().iter().next().unwrap(); + let krate = db.fetch_test_crate(); let def_map = db.crate_def_map(krate); let mut fn_def = None; 'outer: for (_, module) in def_map.modules() { @@ -404,3 +403,26 @@ fn foo() { }"#]] .assert_eq(&body.pretty_print(&db, def, Edition::CURRENT)) } + +#[test] +fn shadowing_record_variant() { + let (_, body, _) = lower( + r#" +enum A { + B { field: i32 }, +} +fn f() { + use A::*; + match () { + B => {} + }; +} + "#, + ); + assert_eq!(body.bindings.len(), 1, "should have a binding for `B`"); + assert_eq!( + body.bindings[BindingId::from_raw(RawIdx::from_u32(0))].name.as_str(), + "B", + "should have a binding for `B`", + ); +} diff --git a/crates/hir-def/src/data.rs b/crates/hir-def/src/data.rs index 2a13f74aac..15dd6aba31 100644 --- a/crates/hir-def/src/data.rs +++ b/crates/hir-def/src/data.rs @@ -369,7 +369,7 @@ impl ImplData { let item_tree = tree_id.item_tree(db); let impl_def = &item_tree[tree_id.value]; - let target_trait = impl_def.target_trait.clone(); + let target_trait = impl_def.target_trait; let self_ty = impl_def.self_ty; let is_negative = impl_def.is_negative; let is_unsafe = impl_def.is_unsafe; diff --git a/crates/hir-def/src/generics.rs b/crates/hir-def/src/generics.rs index 11e9bb0d88..7b3f1d06d2 100644 --- a/crates/hir-def/src/generics.rs +++ b/crates/hir-def/src/generics.rs @@ -26,8 +26,8 @@ use crate::{ nameres::{DefMap, MacroSubNs}, path::{AssociatedTypeBinding, GenericArg, GenericArgs, NormalPath, Path}, type_ref::{ - ArrayType, ConstRef, FnType, LifetimeRef, RefType, TypeBound, TypeRef, TypeRefId, TypesMap, - TypesSourceMap, + ArrayType, ConstRef, FnType, LifetimeRef, PathId, RefType, TypeBound, TypeRef, TypeRefId, + TypesMap, TypesSourceMap, }, AdtId, ConstParamId, GenericDefId, HasModule, ItemTreeLoc, LifetimeParamId, LocalLifetimeParamId, LocalTypeOrConstParamId, Lookup, TypeOrConstParamId, TypeParamId, @@ -225,6 +225,11 @@ impl GenericParams { } #[inline] + pub fn no_predicates(&self) -> bool { + self.where_predicates.is_empty() + } + + #[inline] pub fn where_predicates(&self) -> std::slice::Iter<'_, WherePredicate> { self.where_predicates.iter() } @@ -874,14 +879,20 @@ fn copy_type_bound( to: &mut TypesMap, to_source_map: &mut TypesSourceMap, ) -> TypeBound { + let mut copy_path_id = |path: PathId| { + let new_path = copy_path(&from[path], from, from_source_map, to, to_source_map); + let new_path_id = to.types.alloc(TypeRef::Path(new_path)); + if let Some(&ptr) = from_source_map.types_map_back.get(path.type_ref()) { + to_source_map.types_map_back.insert(new_path_id, ptr); + } + PathId::from_type_ref_unchecked(new_path_id) + }; + match bound { - TypeBound::Path(path, modifier) => { - TypeBound::Path(copy_path(path, from, from_source_map, to, to_source_map), *modifier) + &TypeBound::Path(path, modifier) => TypeBound::Path(copy_path_id(path), modifier), + TypeBound::ForLifetime(lifetimes, path) => { + TypeBound::ForLifetime(lifetimes.clone(), copy_path_id(*path)) } - TypeBound::ForLifetime(lifetimes, path) => TypeBound::ForLifetime( - lifetimes.clone(), - copy_path(path, from, from_source_map, to, to_source_map), - ), TypeBound::Lifetime(lifetime) => TypeBound::Lifetime(lifetime.clone()), TypeBound::Use(use_args) => TypeBound::Use(use_args.clone()), TypeBound::Error => TypeBound::Error, diff --git a/crates/hir-def/src/hir/type_ref.rs b/crates/hir-def/src/hir/type_ref.rs index 4d83ef99c8..6d4d519cd2 100644 --- a/crates/hir-def/src/hir/type_ref.rs +++ b/crates/hir-def/src/hir/type_ref.rs @@ -23,6 +23,7 @@ use crate::{ hir::Literal, lower::LowerCtx, path::{GenericArg, Path}, + SyntheticSyntax, }; #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] @@ -91,19 +92,37 @@ impl Rawness { } } -#[derive(Clone, PartialEq, Eq, Hash, Debug)] +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] +/// A `TypeRefId` that is guaranteed to always be `TypeRef::Path`. We use this for things like +/// impl's trait, that are always paths but need to be traced back to source code. +pub struct PathId(TypeRefId); + +impl PathId { + #[inline] + pub fn from_type_ref_unchecked(type_ref: TypeRefId) -> Self { + Self(type_ref) + } + + #[inline] + pub fn type_ref(self) -> TypeRefId { + self.0 + } +} + +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] pub struct TraitRef { - pub path: Path, + pub path: PathId, } impl TraitRef { /// Converts an `ast::PathType` to a `hir::TraitRef`. pub(crate) fn from_ast(ctx: &mut LowerCtx<'_>, node: ast::Type) -> Option<Self> { // FIXME: Use `Path::from_src` - match node { - ast::Type::PathType(path) => { - path.path().and_then(|it| ctx.lower_path(it)).map(|path| TraitRef { path }) - } + match &node { + ast::Type::PathType(path) => path + .path() + .and_then(|it| ctx.lower_path(it)) + .map(|path| TraitRef { path: ctx.alloc_path(path, AstPtr::new(&node)) }), _ => None, } } @@ -173,11 +192,24 @@ impl TypesMap { impl Index<TypeRefId> for TypesMap { type Output = TypeRef; + #[inline] fn index(&self, index: TypeRefId) -> &Self::Output { &self.types[index] } } +impl Index<PathId> for TypesMap { + type Output = Path; + + #[inline] + fn index(&self, index: PathId) -> &Self::Output { + let TypeRef::Path(path) = &self[index.type_ref()] else { + unreachable!("`PathId` always points to `TypeRef::Path`"); + }; + path + } +} + pub type TypePtr = AstPtr<ast::Type>; pub type TypeSource = InFile<TypePtr>; @@ -187,6 +219,12 @@ pub struct TypesSourceMap { } impl TypesSourceMap { + pub const EMPTY: Self = Self { types_map_back: ArenaMap::new() }; + + pub fn type_syntax(&self, id: TypeRefId) -> Result<TypeSource, SyntheticSyntax> { + self.types_map_back.get(id).cloned().ok_or(SyntheticSyntax) + } + pub(crate) fn shrink_to_fit(&mut self) { let TypesSourceMap { types_map_back } = self; types_map_back.shrink_to_fit(); @@ -214,15 +252,15 @@ impl LifetimeRef { #[derive(Clone, PartialEq, Eq, Hash, Debug)] pub enum TypeBound { - Path(Path, TraitBoundModifier), - ForLifetime(Box<[Name]>, Path), + Path(PathId, TraitBoundModifier), + ForLifetime(Box<[Name]>, PathId), Lifetime(LifetimeRef), Use(Box<[UseArgRef]>), Error, } #[cfg(target_pointer_width = "64")] -const _: [(); 32] = [(); ::std::mem::size_of::<TypeBound>()]; +const _: [(); 24] = [(); ::std::mem::size_of::<TypeBound>()]; #[derive(Clone, PartialEq, Eq, Hash, Debug)] pub enum UseArgRef { @@ -365,8 +403,8 @@ impl TypeRef { TypeRef::ImplTrait(bounds) | TypeRef::DynTrait(bounds) => { for bound in bounds { match bound { - TypeBound::Path(path, _) | TypeBound::ForLifetime(_, path) => { - go_path(path, f, map) + &TypeBound::Path(path, _) | &TypeBound::ForLifetime(_, path) => { + go_path(&map[path], f, map) } TypeBound::Lifetime(_) | TypeBound::Error | TypeBound::Use(_) => (), } @@ -397,8 +435,8 @@ impl TypeRef { } for bound in binding.bounds.iter() { match bound { - TypeBound::Path(path, _) | TypeBound::ForLifetime(_, path) => { - go_path(path, f, map) + &TypeBound::Path(path, _) | &TypeBound::ForLifetime(_, path) => { + go_path(&map[path], f, map) } TypeBound::Lifetime(_) | TypeBound::Error | TypeBound::Use(_) => (), } @@ -425,7 +463,7 @@ pub(crate) fn type_bounds_from_ast( impl TypeBound { pub(crate) fn from_ast(ctx: &mut LowerCtx<'_>, node: ast::TypeBound) -> Self { - let mut lower_path_type = |path_type: ast::PathType| ctx.lower_path(path_type.path()?); + let mut lower_path_type = |path_type: &ast::PathType| ctx.lower_path(path_type.path()?); match node.kind() { ast::TypeBoundKind::PathType(path_type) => { @@ -433,8 +471,10 @@ impl TypeBound { Some(_) => TraitBoundModifier::Maybe, None => TraitBoundModifier::None, }; - lower_path_type(path_type) - .map(|p| TypeBound::Path(p, m)) + lower_path_type(&path_type) + .map(|p| { + TypeBound::Path(ctx.alloc_path(p, AstPtr::new(&path_type).upcast()), m) + }) .unwrap_or(TypeBound::Error) } ast::TypeBoundKind::ForType(for_type) => { @@ -445,12 +485,14 @@ impl TypeBound { .collect(), None => Box::default(), }; - let path = for_type.ty().and_then(|ty| match ty { - ast::Type::PathType(path_type) => lower_path_type(path_type), + let path = for_type.ty().and_then(|ty| match &ty { + ast::Type::PathType(path_type) => lower_path_type(path_type).map(|p| (p, ty)), _ => None, }); match path { - Some(p) => TypeBound::ForLifetime(lt_refs, p), + Some((p, ty)) => { + TypeBound::ForLifetime(lt_refs, ctx.alloc_path(p, AstPtr::new(&ty))) + } None => TypeBound::Error, } } @@ -470,10 +512,10 @@ impl TypeBound { } } - pub fn as_path(&self) -> Option<(&Path, &TraitBoundModifier)> { + pub fn as_path<'a>(&self, map: &'a TypesMap) -> Option<(&'a Path, TraitBoundModifier)> { match self { - TypeBound::Path(p, m) => Some((p, m)), - TypeBound::ForLifetime(_, p) => Some((p, &TraitBoundModifier::None)), + &TypeBound::Path(p, m) => Some((&map[p], m)), + &TypeBound::ForLifetime(_, p) => Some((&map[p], TraitBoundModifier::None)), TypeBound::Lifetime(_) | TypeBound::Error | TypeBound::Use(_) => None, } } diff --git a/crates/hir-def/src/item_tree/lower.rs b/crates/hir-def/src/item_tree/lower.rs index d519c1708b..71848845a8 100644 --- a/crates/hir-def/src/item_tree/lower.rs +++ b/crates/hir-def/src/item_tree/lower.rs @@ -34,7 +34,7 @@ use crate::{ lower::LowerCtx, path::AssociatedTypeBinding, type_ref::{ - LifetimeRef, RefType, TraitBoundModifier, TraitRef, TypeBound, TypeRef, TypeRefId, + LifetimeRef, PathId, RefType, TraitBoundModifier, TraitRef, TypeBound, TypeRef, TypeRefId, TypesMap, TypesSourceMap, }, visibility::RawVisibility, @@ -514,7 +514,7 @@ impl<'a> Ctx<'a> { }; let ret_type = if func.async_token().is_some() { - let future_impl = desugar_future_path(ret_type); + let future_impl = desugar_future_path(&mut body_ctx, ret_type); let ty_bound = TypeBound::Path(future_impl, TraitBoundModifier::None); body_ctx.alloc_type_ref_desugared(TypeRef::ImplTrait(ThinVec::from_iter([ty_bound]))) } else { @@ -936,7 +936,7 @@ impl<'a> Ctx<'a> { } } -fn desugar_future_path(orig: TypeRefId) -> Path { +fn desugar_future_path(ctx: &mut LowerCtx<'_>, orig: TypeRefId) -> PathId { let path = path![core::future::Future]; let mut generic_args: Vec<_> = std::iter::repeat(None).take(path.segments().len() - 1).collect(); @@ -948,7 +948,8 @@ fn desugar_future_path(orig: TypeRefId) -> Path { }; generic_args.push(Some(GenericArgs { bindings: Box::new([binding]), ..GenericArgs::empty() })); - Path::from_known_path(path, generic_args) + let path = Path::from_known_path(path, generic_args); + PathId::from_type_ref_unchecked(ctx.alloc_type_ref_desugared(TypeRef::Path(path))) } enum HasImplicitSelf { diff --git a/crates/hir-def/src/item_tree/pretty.rs b/crates/hir-def/src/item_tree/pretty.rs index b6816a1f96..70bf2f13c8 100644 --- a/crates/hir-def/src/item_tree/pretty.rs +++ b/crates/hir-def/src/item_tree/pretty.rs @@ -484,7 +484,7 @@ impl Printer<'_> { w!(self, "!"); } if let Some(tr) = target_trait { - self.print_path(&tr.path, types_map); + self.print_path(&types_map[tr.path], types_map); w!(self, " for "); } self.print_type_ref(*self_ty, types_map); @@ -648,9 +648,9 @@ impl Printer<'_> { let (target, bound) = match pred { WherePredicate::TypeBound { target, bound } => (target, bound), WherePredicate::Lifetime { target, bound } => { - wln!( + w!( this, - "{}: {},", + "{}: {}", target.name.display(self.db.upcast(), edition), bound.name.display(self.db.upcast(), edition) ); diff --git a/crates/hir-def/src/item_tree/tests.rs b/crates/hir-def/src/item_tree/tests.rs index 5c07369f4b..0f53969d6c 100644 --- a/crates/hir-def/src/item_tree/tests.rs +++ b/crates/hir-def/src/item_tree/tests.rs @@ -351,7 +351,8 @@ trait Tr<'a, T: 'a>: Super where Self: for<'a> Tr<'a, T> {} where T: Copy, T: 'a, - T: 'b + T: 'b, + 'b: 'a { pub(self) field: &'a &'b T, } @@ -370,7 +371,8 @@ trait Tr<'a, T: 'a>: Super where Self: for<'a> Tr<'a, T> {} where T: Copy, T: 'a, - T: 'b + T: 'b, + 'b: 'a { // AstId: 9 pub(self) fn f<G>( diff --git a/crates/hir-def/src/lang_item.rs b/crates/hir-def/src/lang_item.rs index 166c965d14..0629d87e54 100644 --- a/crates/hir-def/src/lang_item.rs +++ b/crates/hir-def/src/lang_item.rs @@ -376,6 +376,9 @@ language_item_table! { Fn, sym::fn_, fn_trait, Target::Trait, GenericRequirement::Exact(1); FnMut, sym::fn_mut, fn_mut_trait, Target::Trait, GenericRequirement::Exact(1); FnOnce, sym::fn_once, fn_once_trait, Target::Trait, GenericRequirement::Exact(1); + AsyncFn, sym::async_fn, async_fn_trait, Target::Trait, GenericRequirement::Exact(1); + AsyncFnMut, sym::async_fn_mut, async_fn_mut_trait, Target::Trait, GenericRequirement::Exact(1); + AsyncFnOnce, sym::async_fn_once, async_fn_once_trait, Target::Trait, GenericRequirement::Exact(1); FnOnceOutput, sym::fn_once_output, fn_once_output, Target::AssocTy, GenericRequirement::None; diff --git a/crates/hir-def/src/lib.rs b/crates/hir-def/src/lib.rs index eb55ba1d53..8af27513eb 100644 --- a/crates/hir-def/src/lib.rs +++ b/crates/hir-def/src/lib.rs @@ -1535,3 +1535,6 @@ fn macro_call_as_call_id_with_eager( pub struct UnresolvedMacro { pub path: hir_expand::mod_path::ModPath, } + +#[derive(Default, Debug, Eq, PartialEq, Clone, Copy)] +pub struct SyntheticSyntax; diff --git a/crates/hir-def/src/lower.rs b/crates/hir-def/src/lower.rs index 6d1a3d1744..7cddd48eb1 100644 --- a/crates/hir-def/src/lower.rs +++ b/crates/hir-def/src/lower.rs @@ -2,7 +2,7 @@ use std::{cell::OnceCell, mem}; use hir_expand::{span_map::SpanMap, AstId, HirFileId, InFile}; -use span::{AstIdMap, AstIdNode}; +use span::{AstIdMap, AstIdNode, Edition, EditionedFileId, FileId, RealSpanMap}; use stdx::thin_vec::ThinVec; use syntax::ast; use triomphe::Arc; @@ -10,7 +10,7 @@ use triomphe::Arc; use crate::{ db::DefDatabase, path::Path, - type_ref::{TypeBound, TypePtr, TypeRef, TypeRefId, TypesMap, TypesSourceMap}, + type_ref::{PathId, TypeBound, TypePtr, TypeRef, TypeRefId, TypesMap, TypesSourceMap}, }; pub struct LowerCtx<'a> { @@ -63,6 +63,30 @@ impl<'a> LowerCtx<'a> { } } + /// Prepares a `LowerCtx` for synthetic AST that needs to be lowered. This is intended for IDE things. + pub fn for_synthetic_ast( + db: &'a dyn DefDatabase, + ast_id_map: Arc<AstIdMap>, + types_map: &'a mut TypesMap, + types_source_map: &'a mut TypesSourceMap, + ) -> Self { + let file_id = EditionedFileId::new( + FileId::from_raw(EditionedFileId::MAX_FILE_ID), + Edition::Edition2015, + ); + LowerCtx { + db, + // Make up an invalid file id, so that if we will try to actually access it salsa will panic. + file_id: file_id.into(), + span_map: SpanMap::RealSpanMap(Arc::new(RealSpanMap::absolute(file_id))).into(), + ast_id_map: ast_id_map.into(), + impl_trait_bounds: Vec::new(), + outer_impl_trait: false, + types_map, + types_source_map, + } + } + pub(crate) fn span_map(&self) -> &SpanMap { self.span_map.get_or_init(|| self.db.span_map(self.file_id)) } @@ -118,4 +142,8 @@ impl<'a> LowerCtx<'a> { pub(crate) fn alloc_error_type(&mut self) -> TypeRefId { self.types_map.types.alloc(TypeRef::Error) } + + pub(crate) fn alloc_path(&mut self, path: Path, node: TypePtr) -> PathId { + PathId::from_type_ref_unchecked(self.alloc_type_ref(TypeRef::Path(path), node)) + } } diff --git a/crates/hir-def/src/macro_expansion_tests/mbe.rs b/crates/hir-def/src/macro_expansion_tests/mbe.rs index d568f6faa7..511626b5ed 100644 --- a/crates/hir-def/src/macro_expansion_tests/mbe.rs +++ b/crates/hir-def/src/macro_expansion_tests/mbe.rs @@ -1733,7 +1733,7 @@ m!(C("0")); macro_rules! m { ($k:expr) => { fn f() { K::$k; } } } -/* parse error: expected identifier */ +/* parse error: expected identifier, `self`, `super`, `crate`, or `Self` */ /* parse error: expected SEMICOLON */ /* parse error: expected SEMICOLON */ /* parse error: expected expression, item or let statement */ @@ -1759,8 +1759,9 @@ fn f() { // [email protected] "K" // [email protected] "::" -// [email protected] "(" +// [email protected] "(" diff --git a/crates/hir-def/src/macro_expansion_tests/mbe/matching.rs b/crates/hir-def/src/macro_expansion_tests/mbe/matching.rs index 23d8b023b8..e9a977da91 100644 --- a/crates/hir-def/src/macro_expansion_tests/mbe/matching.rs +++ b/crates/hir-def/src/macro_expansion_tests/mbe/matching.rs @@ -184,3 +184,31 @@ fn test() { "#]], ); } + +#[test] +fn meta_variable_raw_name_equals_non_raw() { + check( + r#" +macro_rules! m { + ($r#name:tt) => { + $name + } +} + +fn test() { + m!(1234) +} +"#, + expect![[r#" +macro_rules! m { + ($r#name:tt) => { + $name + } +} + +fn test() { + 1234 +} +"#]], + ); +} diff --git a/crates/hir-def/src/macro_expansion_tests/mod.rs b/crates/hir-def/src/macro_expansion_tests/mod.rs index d5b94f0ae4..0475e40c5b 100644 --- a/crates/hir-def/src/macro_expansion_tests/mod.rs +++ b/crates/hir-def/src/macro_expansion_tests/mod.rs @@ -16,7 +16,6 @@ mod proc_macros; use std::{iter, ops::Range, sync}; -use base_db::SourceDatabase; use expect_test::Expect; use hir_expand::{ db::ExpandDatabase, @@ -63,7 +62,7 @@ pub fn identity_when_valid(_attr: TokenStream, item: TokenStream) -> TokenStream }, )]; let db = TestDB::with_files_extra_proc_macros(ra_fixture, extra_proc_macros); - let krate = db.crate_graph().iter().next().unwrap(); + let krate = db.fetch_test_crate(); let def_map = db.crate_def_map(krate); let local_id = DefMap::ROOT; let module = def_map.module_id(local_id); diff --git a/crates/hir-def/src/nameres/collector.rs b/crates/hir-def/src/nameres/collector.rs index a37e3c70e2..98b08bcf70 100644 --- a/crates/hir-def/src/nameres/collector.rs +++ b/crates/hir-def/src/nameres/collector.rs @@ -910,8 +910,13 @@ impl DefCollector<'_> { self.update(module_id, &items, vis, Some(ImportType::Glob(id))); // record the glob import in case we add further items let glob = self.glob_imports.entry(m.local_id).or_default(); - if !glob.iter().any(|(mid, _, _)| *mid == module_id) { - glob.push((module_id, vis, id)); + match glob.iter_mut().find(|(mid, _, _)| *mid == module_id) { + None => glob.push((module_id, vis, id)), + Some((_, old_vis, _)) => { + if let Some(new_vis) = old_vis.max(vis, &self.def_map) { + *old_vis = new_vis; + } + } } } } diff --git a/crates/hir-def/src/nameres/tests.rs b/crates/hir-def/src/nameres/tests.rs index e1e30e5cec..32c158415b 100644 --- a/crates/hir-def/src/nameres/tests.rs +++ b/crates/hir-def/src/nameres/tests.rs @@ -13,13 +13,13 @@ use crate::{db::DefDatabase, nameres::DefMap, test_db::TestDB}; fn compute_crate_def_map(ra_fixture: &str) -> Arc<DefMap> { let db = TestDB::with_files(ra_fixture); - let krate = db.crate_graph().iter().next().unwrap(); + let krate = db.fetch_test_crate(); db.crate_def_map(krate) } fn render_crate_def_map(ra_fixture: &str) -> String { let db = TestDB::with_files(ra_fixture); - let krate = db.crate_graph().iter().next().unwrap(); + let krate = db.fetch_test_crate(); db.crate_def_map(krate).dump(&db) } diff --git a/crates/hir-def/src/nameres/tests/globs.rs b/crates/hir-def/src/nameres/tests/globs.rs index 543ab41cd5..8963a57679 100644 --- a/crates/hir-def/src/nameres/tests/globs.rs +++ b/crates/hir-def/src/nameres/tests/globs.rs @@ -451,3 +451,42 @@ mod glob_target { "#]], ); } + +#[test] +fn regression_18580() { + check( + r#" +pub mod libs { + pub struct Placeholder; +} + +pub mod reexport_2 { + use reexport_1::*; + pub use reexport_1::*; + + pub mod reexport_1 { + pub use crate::libs::*; + } +} + +use reexport_2::*; +"#, + expect![[r#" + crate + Placeholder: t v + libs: t + reexport_1: t + reexport_2: t + + crate::libs + Placeholder: t v + + crate::reexport_2 + Placeholder: t v + reexport_1: t + + crate::reexport_2::reexport_1 + Placeholder: t v + "#]], + ); +} diff --git a/crates/hir-def/src/nameres/tests/incremental.rs b/crates/hir-def/src/nameres/tests/incremental.rs index d920c10826..1cfbabca28 100644 --- a/crates/hir-def/src/nameres/tests/incremental.rs +++ b/crates/hir-def/src/nameres/tests/incremental.rs @@ -1,15 +1,11 @@ -use base_db::{SourceDatabase, SourceDatabaseFileInputExt as _}; +use base_db::SourceDatabaseFileInputExt as _; use test_fixture::WithFixture; use crate::{db::DefDatabase, nameres::tests::TestDB, AdtId, ModuleDefId}; fn check_def_map_is_not_recomputed(ra_fixture_initial: &str, ra_fixture_change: &str) { let (mut db, pos) = TestDB::with_position(ra_fixture_initial); - let krate = { - let crate_graph = db.crate_graph(); - // Some of these tests use minicore/proc-macros which will be injected as the first crate - crate_graph.iter().last().unwrap() - }; + let krate = db.fetch_test_crate(); { let events = db.log_executed(|| { db.crate_def_map(krate); @@ -120,28 +116,31 @@ fn f() { foo } ); } -#[test] -fn typing_inside_an_attribute_arg_should_not_invalidate_def_map() { - check_def_map_is_not_recomputed( - r" -//- proc_macros: identity -//- /lib.rs -mod foo; +// Would be nice if this was the case, but as attribute inputs are stored in the item tree, this is +// not currently the case. +// #[test] +// fn typing_inside_an_attribute_arg_should_not_invalidate_def_map() { +// check_def_map_is_not_recomputed( +// r" +// //- proc_macros: identity +// //- /lib.rs +// mod foo; + +// //- /foo/mod.rs +// pub mod bar; + +// //- /foo/bar.rs +// $0 +// #[proc_macros::identity] +// fn f() {} +// ", +// r" +// #[proc_macros::identity(foo)] +// fn f() {} +// ", +// ); +// } -//- /foo/mod.rs -pub mod bar; - -//- /foo/bar.rs -$0 -#[proc_macros::identity] -fn f() {} -", - r" -#[proc_macros::identity(foo)] -fn f() {} -", - ); -} #[test] fn typing_inside_macro_heavy_file_should_not_invalidate_def_map() { check_def_map_is_not_recomputed( @@ -198,31 +197,33 @@ pub struct S {} ); } -#[test] -fn typing_inside_a_derive_should_not_invalidate_def_map() { - check_def_map_is_not_recomputed( - r" -//- proc_macros: derive_identity -//- minicore:derive -//- /lib.rs -mod foo; - -//- /foo/mod.rs -pub mod bar; - -//- /foo/bar.rs -$0 -#[derive(proc_macros::DeriveIdentity)] -#[allow()] -struct S; -", - r" -#[derive(proc_macros::DeriveIdentity)] -#[allow(dead_code)] -struct S; -", - ); -} +// Would be nice if this was the case, but as attribute inputs are stored in the item tree, this is +// not currently the case. +// #[test] +// fn typing_inside_a_derive_should_not_invalidate_def_map() { +// check_def_map_is_not_recomputed( +// r" +// //- proc_macros: derive_identity +// //- minicore:derive +// //- /lib.rs +// mod foo; + +// //- /foo/mod.rs +// pub mod bar; + +// //- /foo/bar.rs +// $0 +// #[derive(proc_macros::DeriveIdentity)] +// #[allow()] +// struct S; +// ", +// r" +// #[derive(proc_macros::DeriveIdentity)] +// #[allow(dead_code)] +// struct S; +// ", +// ); +// } #[test] fn typing_inside_a_function_should_not_invalidate_item_expansions() { diff --git a/crates/hir-def/src/path.rs b/crates/hir-def/src/path.rs index aa2c4a6f1b..44e132061a 100644 --- a/crates/hir-def/src/path.rs +++ b/crates/hir-def/src/path.rs @@ -1,5 +1,7 @@ //! A desugared representation of paths like `crate::foo` or `<Type as Trait>::bar`. mod lower; +#[cfg(test)] +mod tests; use std::{ fmt::{self, Display}, @@ -19,6 +21,8 @@ use syntax::ast; pub use hir_expand::mod_path::{path, ModPath, PathKind}; +pub use lower::hir_segment_to_ast_segment; + #[derive(Debug, Clone, PartialEq, Eq)] pub enum ImportAlias { /// Unnamed alias, as in `use Foo as _;` @@ -230,7 +234,7 @@ impl Path { } } -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct PathSegment<'a> { pub name: &'a Name, pub args_and_bindings: Option<&'a GenericArgs>, @@ -274,6 +278,12 @@ impl<'a> PathSegments<'a> { generic_args: self.generic_args.map(|it| it.get(..len).unwrap_or(it)), } } + pub fn strip_last(&self) -> PathSegments<'a> { + PathSegments { + segments: self.segments.split_last().map_or(&[], |it| it.1), + generic_args: self.generic_args.map(|it| it.split_last().map_or(&[][..], |it| it.1)), + } + } pub fn iter(&self) -> impl Iterator<Item = PathSegment<'a>> { self.segments .iter() diff --git a/crates/hir-def/src/path/lower.rs b/crates/hir-def/src/path/lower.rs index 553e615b94..3b7e7653fb 100644 --- a/crates/hir-def/src/path/lower.rs +++ b/crates/hir-def/src/path/lower.rs @@ -17,13 +17,31 @@ use crate::{ type_ref::{LifetimeRef, TypeBound, TypeRef}, }; +#[cfg(test)] +thread_local! { + /// This is used to test `hir_segment_to_ast_segment()`. It's a hack, but it makes testing much easier. + pub(super) static SEGMENT_LOWERING_MAP: std::cell::RefCell<rustc_hash::FxHashMap<ast::PathSegment, usize>> = std::cell::RefCell::default(); +} + /// Converts an `ast::Path` to `Path`. Works with use trees. /// It correctly handles `$crate` based path from macro call. +// If you modify the logic of the lowering, make sure to check if `hir_segment_to_ast_segment()` +// also needs an update. pub(super) fn lower_path(ctx: &mut LowerCtx<'_>, mut path: ast::Path) -> Option<Path> { let mut kind = PathKind::Plain; let mut type_anchor = None; let mut segments = Vec::new(); let mut generic_args = Vec::new(); + #[cfg(test)] + let mut ast_segments = Vec::new(); + #[cfg(test)] + let mut ast_segments_offset = 0; + #[allow(unused_mut)] + let mut push_segment = |_segment: &ast::PathSegment, segments: &mut Vec<Name>, name| { + #[cfg(test)] + ast_segments.push(_segment.clone()); + segments.push(name); + }; loop { let segment = path.segment()?; @@ -34,6 +52,10 @@ pub(super) fn lower_path(ctx: &mut LowerCtx<'_>, mut path: ast::Path) -> Option< match segment.kind()? { ast::PathSegmentKind::Name(name_ref) => { if name_ref.text() == "$crate" { + if path.qualifier().is_some() { + // FIXME: Report an error. + return None; + } break kind = resolve_crate_root( ctx.db.upcast(), ctx.span_map().span_for_range(name_ref.syntax().text_range()).ctx, @@ -48,7 +70,7 @@ pub(super) fn lower_path(ctx: &mut LowerCtx<'_>, mut path: ast::Path) -> Option< .or_else(|| { lower_generic_args_from_fn_path( ctx, - segment.param_list(), + segment.parenthesized_arg_list(), segment.ret_type(), ) }); @@ -56,10 +78,10 @@ pub(super) fn lower_path(ctx: &mut LowerCtx<'_>, mut path: ast::Path) -> Option< generic_args.resize(segments.len(), None); generic_args.push(args); } - segments.push(name); + push_segment(&segment, &mut segments, name); } ast::PathSegmentKind::SelfTypeKw => { - segments.push(Name::new_symbol_root(sym::Self_.clone())); + push_segment(&segment, &mut segments, Name::new_symbol_root(sym::Self_.clone())); } ast::PathSegmentKind::Type { type_ref, trait_ref } => { assert!(path.qualifier().is_none()); // this can only occur at the first segment @@ -81,6 +103,10 @@ pub(super) fn lower_path(ctx: &mut LowerCtx<'_>, mut path: ast::Path) -> Option< kind = mod_path.kind; segments.extend(mod_path.segments().iter().cloned().rev()); + #[cfg(test)] + { + ast_segments_offset = mod_path.segments().len(); + } if let Some(path_generic_args) = path_generic_args { generic_args.resize(segments.len() - num_segments, None); generic_args.extend(Vec::from(path_generic_args).into_iter().rev()); @@ -112,10 +138,18 @@ pub(super) fn lower_path(ctx: &mut LowerCtx<'_>, mut path: ast::Path) -> Option< } } ast::PathSegmentKind::CrateKw => { + if path.qualifier().is_some() { + // FIXME: Report an error. + return None; + } kind = PathKind::Crate; break; } ast::PathSegmentKind::SelfKw => { + if path.qualifier().is_some() { + // FIXME: Report an error. + return None; + } // don't break out if `self` is the last segment of a path, this mean we got a // use tree like `foo::{self}` which we want to resolve as `foo` if !segments.is_empty() { @@ -162,6 +196,13 @@ pub(super) fn lower_path(ctx: &mut LowerCtx<'_>, mut path: ast::Path) -> Option< } } + #[cfg(test)] + { + ast_segments.reverse(); + SEGMENT_LOWERING_MAP + .with_borrow_mut(|map| map.extend(ast_segments.into_iter().zip(ast_segments_offset..))); + } + let mod_path = Interned::new(ModPath::from_segments(kind, segments)); if type_anchor.is_none() && generic_args.is_empty() { return Some(Path::BarePath(mod_path)); @@ -181,6 +222,41 @@ pub(super) fn lower_path(ctx: &mut LowerCtx<'_>, mut path: ast::Path) -> Option< } } +/// This function finds the AST segment that corresponds to the HIR segment +/// with index `segment_idx` on the path that is lowered from `path`. +pub fn hir_segment_to_ast_segment(path: &ast::Path, segment_idx: u32) -> Option<ast::PathSegment> { + // Too tightly coupled to `lower_path()`, but unfortunately we cannot decouple them, + // as keeping source maps for all paths segments will have a severe impact on memory usage. + + let mut segments = path.segments(); + if let Some(ast::PathSegmentKind::Type { trait_ref: Some(trait_ref), .. }) = + segments.clone().next().and_then(|it| it.kind()) + { + segments.next(); + return find_segment(trait_ref.path()?.segments().chain(segments), segment_idx); + } + return find_segment(segments, segment_idx); + + fn find_segment( + segments: impl Iterator<Item = ast::PathSegment>, + segment_idx: u32, + ) -> Option<ast::PathSegment> { + segments + .filter(|segment| match segment.kind() { + Some( + ast::PathSegmentKind::CrateKw + | ast::PathSegmentKind::SelfKw + | ast::PathSegmentKind::SuperKw + | ast::PathSegmentKind::Type { .. }, + ) + | None => false, + Some(ast::PathSegmentKind::Name(name)) => name.text() != "$crate", + Some(ast::PathSegmentKind::SelfTypeKw) => true, + }) + .nth(segment_idx as usize) + } +} + pub(super) fn lower_generic_args( lower_ctx: &mut LowerCtx<'_>, node: ast::GenericArgList, @@ -247,12 +323,12 @@ pub(super) fn lower_generic_args( /// -> Z` (which desugars to `Fn<(X, Y), Output=Z>`). fn lower_generic_args_from_fn_path( ctx: &mut LowerCtx<'_>, - params: Option<ast::ParamList>, + args: Option<ast::ParenthesizedArgList>, ret_type: Option<ast::RetType>, ) -> Option<GenericArgs> { - let params = params?; + let params = args?; let mut param_types = Vec::new(); - for param in params.params() { + for param in params.type_args() { let type_ref = TypeRef::from_ast_opt(ctx, param.ty()); param_types.push(type_ref); } diff --git a/crates/hir-def/src/path/tests.rs b/crates/hir-def/src/path/tests.rs new file mode 100644 index 0000000000..67a27bf85e --- /dev/null +++ b/crates/hir-def/src/path/tests.rs @@ -0,0 +1,126 @@ +use expect_test::{expect, Expect}; +use span::Edition; +use syntax::ast::{self, make}; +use test_fixture::WithFixture; + +use crate::{ + lower::LowerCtx, + path::{ + lower::{hir_segment_to_ast_segment, SEGMENT_LOWERING_MAP}, + Path, + }, + pretty, + test_db::TestDB, + type_ref::{TypesMap, TypesSourceMap}, +}; + +fn lower_path(path: ast::Path) -> (TestDB, TypesMap, Option<Path>) { + let (db, file_id) = TestDB::with_single_file(""); + let mut types_map = TypesMap::default(); + let mut types_source_map = TypesSourceMap::default(); + let mut ctx = LowerCtx::new(&db, file_id.into(), &mut types_map, &mut types_source_map); + let lowered_path = ctx.lower_path(path); + (db, types_map, lowered_path) +} + +#[track_caller] +fn check_hir_to_ast(path: &str, ignore_segments: &[&str]) { + let path = make::path_from_text(path); + SEGMENT_LOWERING_MAP.with_borrow_mut(|map| map.clear()); + let _ = lower_path(path.clone()).2.expect("failed to lower path"); + SEGMENT_LOWERING_MAP.with_borrow(|map| { + for (segment, segment_idx) in map { + if ignore_segments.contains(&&*segment.to_string()) { + continue; + } + + let restored_segment = hir_segment_to_ast_segment(&path, *segment_idx as u32) + .unwrap_or_else(|| { + panic!( + "failed to map back segment `{segment}` \ + numbered {segment_idx} in HIR from path `{path}`" + ) + }); + assert_eq!( + segment, &restored_segment, + "mapping back `{segment}` numbered {segment_idx} in HIR \ + from path `{path}` produced incorrect segment `{restored_segment}`" + ); + } + }); +} + +#[test] +fn hir_to_ast_trait_ref() { + check_hir_to_ast("<A as B::C::D>::E::F", &["A"]); +} + +#[test] +fn hir_to_ast_plain_path() { + check_hir_to_ast("A::B::C::D::E::F", &[]); +} + +#[test] +fn hir_to_ast_crate_path() { + check_hir_to_ast("crate::A::B::C", &[]); + check_hir_to_ast("crate::super::super::A::B::C", &[]); +} + +#[test] +fn hir_to_ast_self_path() { + check_hir_to_ast("self::A::B::C", &[]); + check_hir_to_ast("self::super::super::A::B::C", &[]); +} + +#[test] +fn hir_to_ast_super_path() { + check_hir_to_ast("super::A::B::C", &[]); + check_hir_to_ast("super::super::super::A::B::C", &[]); +} + +#[test] +fn hir_to_ast_type_anchor_path() { + check_hir_to_ast("<A::B>::C::D", &["A", "B"]); +} + +#[test] +fn hir_to_ast_path_super_in_middle() { + check_hir_to_ast("A::super::B::super::super::C::D", &[]); +} + +#[track_caller] +fn check_fail_lowering(path: &str) { + let (_, _, lowered_path) = lower_path(make::path_from_text(path)); + assert!(lowered_path.is_none(), "path `{path}` should fail lowering"); +} + +#[test] +fn keywords_in_middle_fail_lowering1() { + check_fail_lowering("self::A::self::B::super::C::crate::D"); +} + +#[test] +fn keywords_in_middle_fail_lowering2() { + check_fail_lowering("A::super::self::C::D"); +} + +#[test] +fn keywords_in_middle_fail_lowering3() { + check_fail_lowering("A::crate::B::C::D"); +} + +#[track_caller] +fn check_path_lowering(path: &str, expected: Expect) { + let (db, types_map, lowered_path) = lower_path(make::path_from_text(path)); + let lowered_path = lowered_path.expect("failed to lower path"); + let mut buf = String::new(); + pretty::print_path(&db, &lowered_path, &types_map, &mut buf, Edition::CURRENT) + .expect("failed to pretty-print path"); + expected.assert_eq(&buf); +} + +#[test] +fn fn_like_path_with_coloncolon() { + check_path_lowering("Fn::(A, B) -> C", expect![[r#"Fn::<(A, B), Output = C>"#]]); + check_path_lowering("Fn::(A, B)", expect![[r#"Fn::<(A, B), Output = ()>"#]]); +} diff --git a/crates/hir-def/src/pretty.rs b/crates/hir-def/src/pretty.rs index 9ceb82d5fd..eb9488feaa 100644 --- a/crates/hir-def/src/pretty.rs +++ b/crates/hir-def/src/pretty.rs @@ -271,7 +271,7 @@ pub(crate) fn print_type_bounds( TraitBoundModifier::None => (), TraitBoundModifier::Maybe => write!(buf, "?")?, } - print_path(db, path, map, buf, edition)?; + print_path(db, &map[*path], map, buf, edition)?; } TypeBound::ForLifetime(lifetimes, path) => { write!( @@ -279,7 +279,7 @@ pub(crate) fn print_type_bounds( "for<{}> ", lifetimes.iter().map(|it| it.display(db.upcast(), edition)).format(", ") )?; - print_path(db, path, map, buf, edition)?; + print_path(db, &map[*path], map, buf, edition)?; } TypeBound::Lifetime(lt) => write!(buf, "{}", lt.name.display(db.upcast(), edition))?, TypeBound::Use(args) => { diff --git a/crates/hir-def/src/resolver.rs b/crates/hir-def/src/resolver.rs index 26655e40ca..316406c151 100644 --- a/crates/hir-def/src/resolver.rs +++ b/crates/hir-def/src/resolver.rs @@ -576,10 +576,12 @@ impl Resolver { match scope { Scope::BlockScope(m) => traits.extend(m.def_map[m.module_id].scope.traits()), &Scope::ImplDefScope(impl_) => { - if let Some(target_trait) = &db.impl_data(impl_).target_trait { - if let Some(TypeNs::TraitId(trait_)) = - self.resolve_path_in_type_ns_fully(db, &target_trait.path) - { + let impl_data = db.impl_data(impl_); + if let Some(target_trait) = impl_data.target_trait { + if let Some(TypeNs::TraitId(trait_)) = self.resolve_path_in_type_ns_fully( + db, + &impl_data.types_map[target_trait.path], + ) { traits.insert(trait_); } } @@ -640,9 +642,9 @@ impl Resolver { }) } - pub fn generic_params(&self) -> Option<&Arc<GenericParams>> { + pub fn generic_params(&self) -> Option<&GenericParams> { self.scopes().find_map(|scope| match scope { - Scope::GenericParams { params, .. } => Some(params), + Scope::GenericParams { params, .. } => Some(&**params), _ => None, }) } diff --git a/crates/hir-def/src/test_db.rs b/crates/hir-def/src/test_db.rs index 0c36c88fb0..54e6c1fd20 100644 --- a/crates/hir-def/src/test_db.rs +++ b/crates/hir-def/src/test_db.rs @@ -78,6 +78,19 @@ impl FileLoader for TestDB { } impl TestDB { + pub(crate) fn fetch_test_crate(&self) -> CrateId { + let crate_graph = self.crate_graph(); + let it = crate_graph + .iter() + .find(|&idx| { + crate_graph[idx].display_name.as_ref().map(|it| it.canonical_name().as_str()) + == Some("ra_test_fixture") + }) + .or_else(|| crate_graph.iter().next()) + .unwrap(); + it + } + pub(crate) fn module_for_file(&self, file_id: FileId) -> ModuleId { for &krate in self.relevant_crates(file_id).iter() { let crate_def_map = self.crate_def_map(krate); diff --git a/crates/hir-expand/src/files.rs b/crates/hir-expand/src/files.rs index d41f69812e..8c04d05402 100644 --- a/crates/hir-expand/src/files.rs +++ b/crates/hir-expand/src/files.rs @@ -180,6 +180,13 @@ impl<FileId: FileIdToSyntax, T> InFileWrapper<FileId, T> { } } +#[allow(private_bounds)] +impl<FileId: FileIdToSyntax, N: AstNode> InFileWrapper<FileId, AstPtr<N>> { + pub fn to_node(&self, db: &dyn ExpandDatabase) -> N { + self.value.to_node(&self.file_syntax(db)) + } +} + impl<FileId: Copy, N: AstNode> InFileWrapper<FileId, N> { pub fn syntax(&self) -> InFileWrapper<FileId, &SyntaxNode> { self.with_value(self.value.syntax()) diff --git a/crates/hir-expand/src/fixup.rs b/crates/hir-expand/src/fixup.rs index b6d5828da9..0af29681a1 100644 --- a/crates/hir-expand/src/fixup.rs +++ b/crates/hir-expand/src/fixup.rs @@ -110,7 +110,8 @@ pub(crate) fn fixup_syntax( } }, ast::ExprStmt(it) => { - if it.semicolon_token().is_none() { + let needs_semi = it.semicolon_token().is_none() && it.expr().map_or(false, |e| e.syntax().kind() != SyntaxKind::BLOCK_EXPR); + if needs_semi { append.insert(node.clone().into(), vec![ Leaf::Punct(Punct { char: ';', @@ -908,4 +909,19 @@ fn foo () {|| __ra_fixup} "#]], ); } + + #[test] + fn fixup_regression_() { + check( + r#" +fn foo() { + {} + {} +} +"#, + expect![[r#" +fn foo () {{} {}} +"#]], + ); + } } diff --git a/crates/hir-expand/src/lib.rs b/crates/hir-expand/src/lib.rs index 7d2f556406..2ee598dfbf 100644 --- a/crates/hir-expand/src/lib.rs +++ b/crates/hir-expand/src/lib.rs @@ -269,6 +269,13 @@ pub enum MacroDefKind { ProcMacro(AstId<ast::Fn>, CustomProcMacroExpander, ProcMacroKind), } +impl MacroDefKind { + #[inline] + pub fn is_declarative(&self) -> bool { + matches!(self, MacroDefKind::Declarative(..)) + } +} + #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct EagerCallInfo { /// The expanded argument of the eager macro. diff --git a/crates/hir-ty/src/db.rs b/crates/hir-ty/src/db.rs index 3a3a05c369..6856eaa3e0 100644 --- a/crates/hir-ty/src/db.rs +++ b/crates/hir-ty/src/db.rs @@ -22,7 +22,7 @@ use crate::{ consteval::ConstEvalError, dyn_compatibility::DynCompatibilityViolation, layout::{Layout, LayoutError}, - lower::{GenericDefaults, GenericPredicates}, + lower::{Diagnostics, GenericDefaults, GenericPredicates}, method_resolution::{InherentImpls, TraitImpls, TyFingerprint}, mir::{BorrowckResult, MirBody, MirLowerError}, Binders, ClosureId, Const, FnDefId, ImplTraitId, ImplTraits, InferenceResult, Interner, @@ -115,21 +115,35 @@ pub trait HirDatabase: DefDatabase + Upcast<dyn DefDatabase> { #[ra_salsa::cycle(crate::lower::ty_recover)] fn ty(&self, def: TyDefId) -> Binders<Ty>; + #[ra_salsa::invoke(crate::lower::type_for_type_alias_with_diagnostics_query)] + fn type_for_type_alias_with_diagnostics(&self, def: TypeAliasId) -> (Binders<Ty>, Diagnostics); + /// Returns the type of the value of the given constant, or `None` if the `ValueTyDefId` is /// a `StructId` or `EnumVariantId` with a record constructor. #[ra_salsa::invoke(crate::lower::value_ty_query)] fn value_ty(&self, def: ValueTyDefId) -> Option<Binders<Ty>>; + #[ra_salsa::invoke(crate::lower::impl_self_ty_with_diagnostics_query)] + #[ra_salsa::cycle(crate::lower::impl_self_ty_with_diagnostics_recover)] + fn impl_self_ty_with_diagnostics(&self, def: ImplId) -> (Binders<Ty>, Diagnostics); #[ra_salsa::invoke(crate::lower::impl_self_ty_query)] - #[ra_salsa::cycle(crate::lower::impl_self_ty_recover)] fn impl_self_ty(&self, def: ImplId) -> Binders<Ty>; + #[ra_salsa::invoke(crate::lower::const_param_ty_with_diagnostics_query)] + fn const_param_ty_with_diagnostics(&self, def: ConstParamId) -> (Ty, Diagnostics); #[ra_salsa::invoke(crate::lower::const_param_ty_query)] fn const_param_ty(&self, def: ConstParamId) -> Ty; + #[ra_salsa::invoke(crate::lower::impl_trait_with_diagnostics_query)] + fn impl_trait_with_diagnostics(&self, def: ImplId) -> Option<(Binders<TraitRef>, Diagnostics)>; #[ra_salsa::invoke(crate::lower::impl_trait_query)] fn impl_trait(&self, def: ImplId) -> Option<Binders<TraitRef>>; + #[ra_salsa::invoke(crate::lower::field_types_with_diagnostics_query)] + fn field_types_with_diagnostics( + &self, + var: VariantId, + ) -> (Arc<ArenaMap<LocalFieldId, Binders<Ty>>>, Diagnostics); #[ra_salsa::invoke(crate::lower::field_types_query)] fn field_types(&self, var: VariantId) -> Arc<ArenaMap<LocalFieldId, Binders<Ty>>>; @@ -154,6 +168,11 @@ pub trait HirDatabase: DefDatabase + Upcast<dyn DefDatabase> { #[ra_salsa::invoke(crate::lower::generic_predicates_query)] fn generic_predicates(&self, def: GenericDefId) -> GenericPredicates; + #[ra_salsa::invoke(crate::lower::generic_predicates_without_parent_with_diagnostics_query)] + fn generic_predicates_without_parent_with_diagnostics( + &self, + def: GenericDefId, + ) -> (GenericPredicates, Diagnostics); #[ra_salsa::invoke(crate::lower::generic_predicates_without_parent_query)] fn generic_predicates_without_parent(&self, def: GenericDefId) -> GenericPredicates; @@ -164,8 +183,13 @@ pub trait HirDatabase: DefDatabase + Upcast<dyn DefDatabase> { #[ra_salsa::invoke(crate::lower::trait_environment_query)] fn trait_environment(&self, def: GenericDefId) -> Arc<TraitEnvironment>; + #[ra_salsa::invoke(crate::lower::generic_defaults_with_diagnostics_query)] + #[ra_salsa::cycle(crate::lower::generic_defaults_with_diagnostics_recover)] + fn generic_defaults_with_diagnostics( + &self, + def: GenericDefId, + ) -> (GenericDefaults, Diagnostics); #[ra_salsa::invoke(crate::lower::generic_defaults_query)] - #[ra_salsa::cycle(crate::lower::generic_defaults_recover)] fn generic_defaults(&self, def: GenericDefId) -> GenericDefaults; #[ra_salsa::invoke(InherentImpls::inherent_impls_in_crate_query)] diff --git a/crates/hir-ty/src/diagnostics.rs b/crates/hir-ty/src/diagnostics.rs index af4d2c9fc0..30c02a2936 100644 --- a/crates/hir-ty/src/diagnostics.rs +++ b/crates/hir-ty/src/diagnostics.rs @@ -9,5 +9,5 @@ pub use crate::diagnostics::{ expr::{ record_literal_missing_fields, record_pattern_missing_fields, BodyValidationDiagnostic, }, - unsafe_check::{missing_unsafe, unsafe_expressions, UnsafeExpr}, + unsafe_check::{missing_unsafe, unsafe_expressions, InsideUnsafeBlock, UnsafetyReason}, }; 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 58de19ba81..5452f5c680 100644 --- a/crates/hir-ty/src/diagnostics/match_check/pat_analysis.rs +++ b/crates/hir-ty/src/diagnostics/match_check/pat_analysis.rs @@ -383,9 +383,6 @@ impl<'db> PatCx for MatchCheckCtx<'db> { } else { 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 = - LazyCell::new(|| self.is_foreign_non_exhaustive(adt)); let visibilities = LazyCell::new(|| self.db.field_visibilities(variant)); self.list_variant_fields(ty, variant) @@ -396,8 +393,7 @@ impl<'db> PatCx for MatchCheckCtx<'db> { .is_visible_from(self.db.upcast(), self.module) }; let is_uninhabited = self.is_uninhabited(&ty); - let private_uninhabited = - is_uninhabited && (!is_visible() || *is_non_exhaustive); + let private_uninhabited = is_uninhabited && !is_visible(); (ty, PrivateUninhabitedField(private_uninhabited)) }) .collect() diff --git a/crates/hir-ty/src/diagnostics/unsafe_check.rs b/crates/hir-ty/src/diagnostics/unsafe_check.rs index c7f7fb7ad3..193aaa52c2 100644 --- a/crates/hir-ty/src/diagnostics/unsafe_check.rs +++ b/crates/hir-ty/src/diagnostics/unsafe_check.rs @@ -1,12 +1,16 @@ //! Provides validations for unsafe code. Currently checks if unsafe functions are missing //! unsafe blocks. +use std::mem; + +use either::Either; use hir_def::{ body::Body, - hir::{Expr, ExprId, ExprOrPatId, Pat, UnaryOp}, - resolver::{resolver_for_expr, ResolveValueResult, Resolver, ValueNs}, + hir::{Expr, ExprId, ExprOrPatId, Pat, PatId, Statement, UnaryOp}, + path::Path, + resolver::{HasResolver, ResolveValueResult, Resolver, ValueNs}, type_ref::Rawness, - DefWithBodyId, + AdtId, DefWithBodyId, FieldId, VariantId, }; use crate::{ @@ -16,7 +20,10 @@ use crate::{ /// Returns `(unsafe_exprs, fn_is_unsafe)`. /// /// If `fn_is_unsafe` is false, `unsafe_exprs` are hard errors. If true, they're `unsafe_op_in_unsafe_fn`. -pub fn missing_unsafe(db: &dyn HirDatabase, def: DefWithBodyId) -> (Vec<ExprOrPatId>, bool) { +pub fn missing_unsafe( + db: &dyn HirDatabase, + def: DefWithBodyId, +) -> (Vec<(ExprOrPatId, UnsafetyReason)>, bool) { let _p = tracing::info_span!("missing_unsafe").entered(); let mut res = Vec::new(); @@ -30,111 +37,243 @@ pub fn missing_unsafe(db: &dyn HirDatabase, def: DefWithBodyId) -> (Vec<ExprOrPa let body = db.body(def); let infer = db.infer(def); - unsafe_expressions(db, &infer, def, &body, body.body_expr, &mut |expr| { - if !expr.inside_unsafe_block { - res.push(expr.node); + let mut callback = |node, inside_unsafe_block, reason| { + if inside_unsafe_block == InsideUnsafeBlock::No { + res.push((node, reason)); } - }); + }; + let mut visitor = UnsafeVisitor::new(db, &infer, &body, def, &mut callback); + visitor.walk_expr(body.body_expr); + + if !is_unsafe { + // Unsafety in function parameter patterns (that can only be union destructuring) + // cannot be inserted into an unsafe block, so even with `unsafe_op_in_unsafe_fn` + // it is turned off for unsafe functions. + for ¶m in &body.params { + visitor.walk_pat(param); + } + } (res, is_unsafe) } -pub struct UnsafeExpr { - pub node: ExprOrPatId, - pub inside_unsafe_block: bool, +#[derive(Debug, Clone, Copy)] +pub enum UnsafetyReason { + UnionField, + UnsafeFnCall, + InlineAsm, + RawPtrDeref, + MutableStatic, + ExternStatic, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum InsideUnsafeBlock { + No, + Yes, } -// FIXME: Move this out, its not a diagnostic only thing anymore, and handle unsafe pattern accesses as well pub fn unsafe_expressions( db: &dyn HirDatabase, infer: &InferenceResult, def: DefWithBodyId, body: &Body, current: ExprId, - unsafe_expr_cb: &mut dyn FnMut(UnsafeExpr), + unsafe_expr_cb: &mut dyn FnMut(ExprOrPatId, InsideUnsafeBlock, UnsafetyReason), ) { - walk_unsafe( - db, - infer, - body, - &mut resolver_for_expr(db.upcast(), def, current), - def, - current, - false, - unsafe_expr_cb, - ) + let mut visitor = UnsafeVisitor::new(db, infer, body, def, unsafe_expr_cb); + _ = visitor.resolver.update_to_inner_scope(db.upcast(), def, current); + visitor.walk_expr(current); } -fn walk_unsafe( - db: &dyn HirDatabase, - infer: &InferenceResult, - body: &Body, - resolver: &mut Resolver, +struct UnsafeVisitor<'a> { + db: &'a dyn HirDatabase, + infer: &'a InferenceResult, + body: &'a Body, + resolver: Resolver, def: DefWithBodyId, - current: ExprId, - inside_unsafe_block: bool, - unsafe_expr_cb: &mut dyn FnMut(UnsafeExpr), -) { - let mut mark_unsafe_path = |path, node| { - let g = resolver.update_to_inner_scope(db.upcast(), def, current); - let hygiene = body.expr_or_pat_path_hygiene(node); - let value_or_partial = resolver.resolve_path_in_value_ns(db.upcast(), path, hygiene); - if let Some(ResolveValueResult::ValueNs(ValueNs::StaticId(id), _)) = value_or_partial { - let static_data = db.static_data(id); - if static_data.mutable || (static_data.is_extern && !static_data.has_safe_kw) { - unsafe_expr_cb(UnsafeExpr { node, inside_unsafe_block }); + inside_unsafe_block: InsideUnsafeBlock, + inside_assignment: bool, + inside_union_destructure: bool, + unsafe_expr_cb: &'a mut dyn FnMut(ExprOrPatId, InsideUnsafeBlock, UnsafetyReason), +} + +impl<'a> UnsafeVisitor<'a> { + fn new( + db: &'a dyn HirDatabase, + infer: &'a InferenceResult, + body: &'a Body, + def: DefWithBodyId, + unsafe_expr_cb: &'a mut dyn FnMut(ExprOrPatId, InsideUnsafeBlock, UnsafetyReason), + ) -> Self { + let resolver = def.resolver(db.upcast()); + Self { + db, + infer, + body, + resolver, + def, + inside_unsafe_block: InsideUnsafeBlock::No, + inside_assignment: false, + inside_union_destructure: false, + unsafe_expr_cb, + } + } + + fn call_cb(&mut self, node: ExprOrPatId, reason: UnsafetyReason) { + (self.unsafe_expr_cb)(node, self.inside_unsafe_block, reason); + } + + fn walk_pats_top(&mut self, pats: impl Iterator<Item = PatId>, parent_expr: ExprId) { + let guard = self.resolver.update_to_inner_scope(self.db.upcast(), self.def, parent_expr); + pats.for_each(|pat| self.walk_pat(pat)); + self.resolver.reset_to_guard(guard); + } + + fn walk_pat(&mut self, current: PatId) { + let pat = &self.body.pats[current]; + + if self.inside_union_destructure { + match pat { + Pat::Tuple { .. } + | Pat::Record { .. } + | Pat::Range { .. } + | Pat::Slice { .. } + | Pat::Path(..) + | Pat::Lit(..) + | Pat::Bind { .. } + | Pat::TupleStruct { .. } + | Pat::Ref { .. } + | Pat::Box { .. } + | Pat::Expr(..) + | Pat::ConstBlock(..) => self.call_cb(current.into(), UnsafetyReason::UnionField), + // `Or` only wraps other patterns, and `Missing`/`Wild` do not constitute a read. + Pat::Missing | Pat::Wild | Pat::Or(_) => {} } } - resolver.reset_to_guard(g); - }; - let expr = &body.exprs[current]; - match expr { - &Expr::Call { callee, .. } => { - if let Some(func) = infer[callee].as_fn_def(db) { - if is_fn_unsafe_to_call(db, func) { - unsafe_expr_cb(UnsafeExpr { node: current.into(), inside_unsafe_block }); + match pat { + Pat::Record { .. } => { + if let Some((AdtId::UnionId(_), _)) = self.infer[current].as_adt() { + let old_inside_union_destructure = + mem::replace(&mut self.inside_union_destructure, true); + self.body.walk_pats_shallow(current, |pat| self.walk_pat(pat)); + self.inside_union_destructure = old_inside_union_destructure; + return; } } - } - Expr::Path(path) => mark_unsafe_path(path, current.into()), - Expr::Ref { expr, rawness: Rawness::RawPtr, mutability: _ } => { - if let Expr::Path(_) = body.exprs[*expr] { - // Do not report unsafe for `addr_of[_mut]!(EXTERN_OR_MUT_STATIC)`, - // see https://github.com/rust-lang/rust/pull/125834. - return; + Pat::Path(path) => self.mark_unsafe_path(current.into(), path), + &Pat::ConstBlock(expr) => { + let old_inside_assignment = mem::replace(&mut self.inside_assignment, false); + self.walk_expr(expr); + self.inside_assignment = old_inside_assignment; } + &Pat::Expr(expr) => self.walk_expr(expr), + _ => {} } - Expr::MethodCall { .. } => { - if infer - .method_resolution(current) - .map(|(func, _)| is_fn_unsafe_to_call(db, func)) - .unwrap_or(false) - { - unsafe_expr_cb(UnsafeExpr { node: current.into(), inside_unsafe_block }); + + self.body.walk_pats_shallow(current, |pat| self.walk_pat(pat)); + } + + fn walk_expr(&mut self, current: ExprId) { + let expr = &self.body.exprs[current]; + let inside_assignment = mem::replace(&mut self.inside_assignment, false); + match expr { + &Expr::Call { callee, .. } => { + if let Some(func) = self.infer[callee].as_fn_def(self.db) { + if is_fn_unsafe_to_call(self.db, func) { + self.call_cb(current.into(), UnsafetyReason::UnsafeFnCall); + } + } } - } - Expr::UnaryOp { expr, op: UnaryOp::Deref } => { - if let TyKind::Raw(..) = &infer[*expr].kind(Interner) { - unsafe_expr_cb(UnsafeExpr { node: current.into(), inside_unsafe_block }); + Expr::Path(path) => { + let guard = + self.resolver.update_to_inner_scope(self.db.upcast(), self.def, current); + self.mark_unsafe_path(current.into(), path); + self.resolver.reset_to_guard(guard); } - } - Expr::Unsafe { .. } => { - return body.walk_child_exprs(current, |child| { - walk_unsafe(db, infer, body, resolver, def, child, true, unsafe_expr_cb); - }); - } - &Expr::Assignment { target, value: _ } => { - body.walk_pats(target, &mut |pat| { - if let Pat::Path(path) = &body[pat] { - mark_unsafe_path(path, pat.into()); + Expr::Ref { expr, rawness: Rawness::RawPtr, mutability: _ } => { + if let Expr::Path(_) = self.body.exprs[*expr] { + // Do not report unsafe for `addr_of[_mut]!(EXTERN_OR_MUT_STATIC)`, + // see https://github.com/rust-lang/rust/pull/125834. + return; + } + } + Expr::MethodCall { .. } => { + if self + .infer + .method_resolution(current) + .map(|(func, _)| is_fn_unsafe_to_call(self.db, func)) + .unwrap_or(false) + { + self.call_cb(current.into(), UnsafetyReason::UnsafeFnCall); } - }); + } + Expr::UnaryOp { expr, op: UnaryOp::Deref } => { + if let TyKind::Raw(..) = &self.infer[*expr].kind(Interner) { + self.call_cb(current.into(), UnsafetyReason::RawPtrDeref); + } + } + Expr::Unsafe { .. } => { + let old_inside_unsafe_block = + mem::replace(&mut self.inside_unsafe_block, InsideUnsafeBlock::Yes); + self.body.walk_child_exprs_without_pats(current, |child| self.walk_expr(child)); + self.inside_unsafe_block = old_inside_unsafe_block; + return; + } + &Expr::Assignment { target, value: _ } => { + let old_inside_assignment = mem::replace(&mut self.inside_assignment, true); + self.walk_pats_top(std::iter::once(target), current); + self.inside_assignment = old_inside_assignment; + } + Expr::InlineAsm(_) => self.call_cb(current.into(), UnsafetyReason::InlineAsm), + // rustc allows union assignment to propagate through field accesses and casts. + Expr::Cast { .. } => self.inside_assignment = inside_assignment, + Expr::Field { .. } => { + self.inside_assignment = inside_assignment; + if !inside_assignment { + if let Some(Either::Left(FieldId { parent: VariantId::UnionId(_), .. })) = + self.infer.field_resolution(current) + { + self.call_cb(current.into(), UnsafetyReason::UnionField); + } + } + } + Expr::Block { statements, .. } | Expr::Async { statements, .. } => { + self.walk_pats_top( + statements.iter().filter_map(|statement| match statement { + &Statement::Let { pat, .. } => Some(pat), + _ => None, + }), + current, + ); + } + Expr::Match { arms, .. } => { + self.walk_pats_top(arms.iter().map(|arm| arm.pat), current); + } + &Expr::Let { pat, .. } => { + self.walk_pats_top(std::iter::once(pat), current); + } + Expr::Closure { args, .. } => { + self.walk_pats_top(args.iter().copied(), current); + } + _ => {} } - _ => {} + + self.body.walk_child_exprs_without_pats(current, |child| self.walk_expr(child)); } - body.walk_child_exprs(current, |child| { - walk_unsafe(db, infer, body, resolver, def, child, inside_unsafe_block, unsafe_expr_cb); - }); + fn mark_unsafe_path(&mut self, node: ExprOrPatId, path: &Path) { + let hygiene = self.body.expr_or_pat_path_hygiene(node); + let value_or_partial = + self.resolver.resolve_path_in_value_ns(self.db.upcast(), path, hygiene); + if let Some(ResolveValueResult::ValueNs(ValueNs::StaticId(id), _)) = value_or_partial { + let static_data = self.db.static_data(id); + if static_data.mutable { + self.call_cb(node, UnsafetyReason::MutableStatic); + } else if static_data.is_extern && !static_data.has_safe_kw { + self.call_cb(node, UnsafetyReason::ExternStatic); + } + } + } } diff --git a/crates/hir-ty/src/display.rs b/crates/hir-ty/src/display.rs index 4e95bdf219..3dfa0e97ce 100644 --- a/crates/hir-ty/src/display.rs +++ b/crates/hir-ty/src/display.rs @@ -1047,10 +1047,14 @@ impl HirDisplay for Ty { ); // 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 { + let params_len = parameters.len(); // `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; + assert!(params_len >= parameters.len()); + let defaults = params_len - parameters.len(); + let without_impl = + self_param as usize + type_ + const_ + lifetime - defaults; // parent's params (those from enclosing impl or trait, if any). let (fn_params, parent_params) = parameters.split_at(without_impl + impl_); @@ -2062,12 +2066,12 @@ impl HirDisplayWithTypesMap for TypeBound { types_map: &TypesMap, ) -> Result<(), HirDisplayError> { match self { - TypeBound::Path(path, modifier) => { + &TypeBound::Path(path, modifier) => { match modifier { TraitBoundModifier::None => (), TraitBoundModifier::Maybe => write!(f, "?")?, } - path.hir_fmt(f, types_map) + types_map[path].hir_fmt(f, types_map) } TypeBound::Lifetime(lifetime) => { write!(f, "{}", lifetime.name.display(f.db.upcast(), f.edition())) @@ -2079,7 +2083,7 @@ impl HirDisplayWithTypesMap for TypeBound { "for<{}> ", lifetimes.iter().map(|it| it.display(f.db.upcast(), edition)).format(", ") )?; - path.hir_fmt(f, types_map) + types_map[*path].hir_fmt(f, types_map) } TypeBound::Use(args) => { let edition = f.edition(); diff --git a/crates/hir-ty/src/generics.rs b/crates/hir-ty/src/generics.rs index c094bc3951..fe7541d237 100644 --- a/crates/hir-ty/src/generics.rs +++ b/crates/hir-ty/src/generics.rs @@ -55,6 +55,10 @@ impl Generics { self.def } + pub(crate) fn self_types_map(&self) -> &TypesMap { + &self.params.types_map + } + pub(crate) fn iter_id(&self) -> impl Iterator<Item = GenericParamId> + '_ { self.iter_self_id().chain(self.iter_parent_id()) } @@ -86,15 +90,13 @@ impl Generics { self.iter_self().chain(self.iter_parent()) } - pub(crate) fn iter_with_types_map( + pub(crate) fn iter_parents_with_types_map( &self, ) -> impl Iterator<Item = ((GenericParamId, GenericParamDataRef<'_>), &TypesMap)> + '_ { - self.iter_self().zip(std::iter::repeat(&self.params.types_map)).chain( - self.iter_parent().zip( - self.parent_generics() - .into_iter() - .flat_map(|it| std::iter::repeat(&it.params.types_map)), - ), + self.iter_parent().zip( + self.parent_generics() + .into_iter() + .flat_map(|it| std::iter::repeat(&it.params.types_map)), ) } diff --git a/crates/hir-ty/src/infer.rs b/crates/hir-ty/src/infer.rs index 01e0b635b2..dbee5a1a91 100644 --- a/crates/hir-ty/src/infer.rs +++ b/crates/hir-ty/src/infer.rs @@ -58,7 +58,7 @@ use crate::{ fold_tys, generics::Generics, infer::{coerce::CoerceMany, expr::ExprIsRead, unify::InferenceTable}, - lower::ImplTraitLoweringMode, + lower::{ImplTraitLoweringMode, TyLoweringDiagnostic}, mir::MirSpan, to_assoc_type_id, traits::FnTrait, @@ -191,6 +191,14 @@ impl<T> InferOk<T> { } } +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum InferenceTyDiagnosticSource { + /// Diagnostics that come from types in the body. + Body, + /// Diagnostics that come from types in fn parameters/return type, or static & const types. + Signature, +} + #[derive(Debug)] pub(crate) struct TypeError; pub(crate) type InferResult<T> = Result<InferOk<T>, TypeError>; @@ -264,6 +272,10 @@ pub enum InferenceDiagnostic { expr_ty: Ty, cast_ty: Ty, }, + TyDiagnostic { + source: InferenceTyDiagnosticSource, + diag: TyLoweringDiagnostic, + }, } /// A mismatch between an expected and an inferred type. @@ -858,7 +870,8 @@ impl<'a> InferenceContext<'a> { } fn collect_const(&mut self, data: &ConstData) { - let return_ty = self.make_ty(data.type_ref, &data.types_map); + let return_ty = + self.make_ty(data.type_ref, &data.types_map, InferenceTyDiagnosticSource::Signature); // Constants might be defining usage sites of TAITs. self.make_tait_coercion_table(iter::once(&return_ty)); @@ -867,7 +880,8 @@ impl<'a> InferenceContext<'a> { } fn collect_static(&mut self, data: &StaticData) { - let return_ty = self.make_ty(data.type_ref, &data.types_map); + let return_ty = + self.make_ty(data.type_ref, &data.types_map, InferenceTyDiagnosticSource::Signature); // Statics might be defining usage sites of TAITs. self.make_tait_coercion_table(iter::once(&return_ty)); @@ -877,11 +891,12 @@ impl<'a> InferenceContext<'a> { fn collect_fn(&mut self, func: FunctionId) { let data = self.db.function_data(func); - let mut param_tys = self.with_ty_lowering(&data.types_map, |ctx| { - ctx.type_param_mode(ParamLoweringMode::Placeholder) - .impl_trait_mode(ImplTraitLoweringMode::Param); - data.params.iter().map(|&type_ref| ctx.lower_ty(type_ref)).collect::<Vec<_>>() - }); + let mut param_tys = + self.with_ty_lowering(&data.types_map, InferenceTyDiagnosticSource::Signature, |ctx| { + ctx.type_param_mode(ParamLoweringMode::Placeholder) + .impl_trait_mode(ImplTraitLoweringMode::Param); + data.params.iter().map(|&type_ref| ctx.lower_ty(type_ref)).collect::<Vec<_>>() + }); // Check if function contains a va_list, if it does then we append it to the parameter types // that are collected from the function data if data.is_varargs() { @@ -918,11 +933,12 @@ impl<'a> InferenceContext<'a> { } let return_ty = data.ret_type; - let return_ty = self.with_ty_lowering(&data.types_map, |ctx| { - ctx.type_param_mode(ParamLoweringMode::Placeholder) - .impl_trait_mode(ImplTraitLoweringMode::Opaque) - .lower_ty(return_ty) - }); + let return_ty = + self.with_ty_lowering(&data.types_map, InferenceTyDiagnosticSource::Signature, |ctx| { + ctx.type_param_mode(ParamLoweringMode::Placeholder) + .impl_trait_mode(ImplTraitLoweringMode::Opaque) + .lower_ty(return_ty) + }); let return_ty = self.insert_type_vars(return_ty); let return_ty = if let Some(rpits) = self.db.return_type_impl_traits(func) { @@ -1226,9 +1242,20 @@ impl<'a> InferenceContext<'a> { self.result.diagnostics.push(diagnostic); } + fn push_ty_diagnostics( + &mut self, + source: InferenceTyDiagnosticSource, + diagnostics: Vec<TyLoweringDiagnostic>, + ) { + self.result.diagnostics.extend( + diagnostics.into_iter().map(|diag| InferenceDiagnostic::TyDiagnostic { source, diag }), + ); + } + fn with_ty_lowering<R>( - &self, + &mut self, types_map: &TypesMap, + types_source: InferenceTyDiagnosticSource, f: impl FnOnce(&mut crate::lower::TyLoweringContext<'_>) -> R, ) -> R { let mut ctx = crate::lower::TyLoweringContext::new( @@ -1237,32 +1264,41 @@ impl<'a> InferenceContext<'a> { types_map, self.owner.into(), ); - f(&mut ctx) + let result = f(&mut ctx); + self.push_ty_diagnostics(types_source, ctx.diagnostics); + result } fn with_body_ty_lowering<R>( - &self, + &mut self, f: impl FnOnce(&mut crate::lower::TyLoweringContext<'_>) -> R, ) -> R { - self.with_ty_lowering(&self.body.types, f) + self.with_ty_lowering(&self.body.types, InferenceTyDiagnosticSource::Body, f) } - fn make_ty(&mut self, type_ref: TypeRefId, types_map: &TypesMap) -> Ty { - let ty = self.with_ty_lowering(types_map, |ctx| ctx.lower_ty(type_ref)); + fn make_ty( + &mut self, + type_ref: TypeRefId, + types_map: &TypesMap, + type_source: InferenceTyDiagnosticSource, + ) -> Ty { + let ty = self.with_ty_lowering(types_map, type_source, |ctx| ctx.lower_ty(type_ref)); let ty = self.insert_type_vars(ty); self.normalize_associated_types_in(ty) } fn make_body_ty(&mut self, type_ref: TypeRefId) -> Ty { - self.make_ty(type_ref, &self.body.types) + self.make_ty(type_ref, &self.body.types, InferenceTyDiagnosticSource::Body) } fn err_ty(&self) -> Ty { self.result.standard_types.unknown.clone() } - fn make_lifetime(&mut self, lifetime_ref: &LifetimeRef) -> Lifetime { - let lt = self.with_ty_lowering(TypesMap::EMPTY, |ctx| ctx.lower_lifetime(lifetime_ref)); + fn make_body_lifetime(&mut self, lifetime_ref: &LifetimeRef) -> Lifetime { + let lt = self.with_ty_lowering(TypesMap::EMPTY, InferenceTyDiagnosticSource::Body, |ctx| { + ctx.lower_lifetime(lifetime_ref) + }); self.insert_type_vars(lt) } @@ -1431,12 +1467,20 @@ impl<'a> InferenceContext<'a> { Some(ResolveValueResult::ValueNs(value, _)) => match value { ValueNs::EnumVariantId(var) => { let substs = ctx.substs_from_path(path, var.into(), true); + self.push_ty_diagnostics( + InferenceTyDiagnosticSource::Body, + ctx.diagnostics, + ); let ty = self.db.ty(var.lookup(self.db.upcast()).parent.into()); let ty = self.insert_type_vars(ty.substitute(Interner, &substs)); return (ty, Some(var.into())); } ValueNs::StructId(strukt) => { let substs = ctx.substs_from_path(path, strukt.into(), true); + self.push_ty_diagnostics( + InferenceTyDiagnosticSource::Body, + ctx.diagnostics, + ); let ty = self.db.ty(strukt.into()); let ty = self.insert_type_vars(ty.substitute(Interner, &substs)); return (ty, Some(strukt.into())); @@ -1462,18 +1506,21 @@ impl<'a> InferenceContext<'a> { return match resolution { TypeNs::AdtId(AdtId::StructId(strukt)) => { let substs = ctx.substs_from_path(path, strukt.into(), true); + self.push_ty_diagnostics(InferenceTyDiagnosticSource::Body, ctx.diagnostics); let ty = self.db.ty(strukt.into()); let ty = self.insert_type_vars(ty.substitute(Interner, &substs)); forbid_unresolved_segments((ty, Some(strukt.into())), unresolved) } TypeNs::AdtId(AdtId::UnionId(u)) => { let substs = ctx.substs_from_path(path, u.into(), true); + self.push_ty_diagnostics(InferenceTyDiagnosticSource::Body, ctx.diagnostics); let ty = self.db.ty(u.into()); let ty = self.insert_type_vars(ty.substitute(Interner, &substs)); forbid_unresolved_segments((ty, Some(u.into())), unresolved) } TypeNs::EnumVariantId(var) => { let substs = ctx.substs_from_path(path, var.into(), true); + self.push_ty_diagnostics(InferenceTyDiagnosticSource::Body, ctx.diagnostics); let ty = self.db.ty(var.lookup(self.db.upcast()).parent.into()); let ty = self.insert_type_vars(ty.substitute(Interner, &substs)); forbid_unresolved_segments((ty, Some(var.into())), unresolved) @@ -1519,6 +1566,9 @@ impl<'a> InferenceContext<'a> { resolved_segment, current_segment, false, + &mut |_, _reason| { + // FIXME: Report an error. + }, ); ty = self.table.insert_type_vars(ty); @@ -1532,6 +1582,7 @@ impl<'a> InferenceContext<'a> { remaining_idx += 1; remaining_segments = remaining_segments.skip(1); } + self.push_ty_diagnostics(InferenceTyDiagnosticSource::Body, ctx.diagnostics); let variant = ty.as_adt().and_then(|(id, _)| match id { AdtId::StructId(s) => Some(VariantId::StructId(s)), @@ -1550,6 +1601,7 @@ impl<'a> InferenceContext<'a> { }; let substs = ctx.substs_from_path_segment(resolved_seg, Some(it.into()), true, None); + self.push_ty_diagnostics(InferenceTyDiagnosticSource::Body, ctx.diagnostics); let ty = self.db.ty(it.into()); let ty = self.insert_type_vars(ty.substitute(Interner, &substs)); diff --git a/crates/hir-ty/src/infer/coerce.rs b/crates/hir-ty/src/infer/coerce.rs index 366c3cb0f1..2fe90a8a92 100644 --- a/crates/hir-ty/src/infer/coerce.rs +++ b/crates/hir-ty/src/infer/coerce.rs @@ -125,7 +125,11 @@ impl CoerceMany { // pointers to have a chance at getting a match. See // https://github.com/rust-lang/rust/blob/7b805396bf46dce972692a6846ce2ad8481c5f85/src/librustc_typeck/check/coercion.rs#L877-L916 let sig = match (self.merged_ty().kind(Interner), expr_ty.kind(Interner)) { - (TyKind::FnDef(x, _), TyKind::FnDef(y, _)) if x == y => None, + (TyKind::FnDef(x, _), TyKind::FnDef(y, _)) + if x == y && ctx.table.unify(&self.merged_ty(), &expr_ty) => + { + None + } (TyKind::Closure(x, _), TyKind::Closure(y, _)) if x == y => None, (TyKind::FnDef(..) | TyKind::Closure(..), TyKind::FnDef(..) | TyKind::Closure(..)) => { // FIXME: we're ignoring safety here. To be more correct, if we have one FnDef and one Closure, diff --git a/crates/hir-ty/src/infer/expr.rs b/crates/hir-ty/src/infer/expr.rs index 32b4ea2f28..a13541be69 100644 --- a/crates/hir-ty/src/infer/expr.rs +++ b/crates/hir-ty/src/infer/expr.rs @@ -1287,8 +1287,8 @@ impl InferenceContext<'_> { tgt_expr: ExprId, ) { match fn_x { - FnTrait::FnOnce => (), - FnTrait::FnMut => { + FnTrait::FnOnce | FnTrait::AsyncFnOnce => (), + FnTrait::FnMut | FnTrait::AsyncFnMut => { if let TyKind::Ref(Mutability::Mut, lt, inner) = derefed_callee.kind(Interner) { if adjustments .last() @@ -1312,7 +1312,7 @@ impl InferenceContext<'_> { )); } } - FnTrait::Fn => { + FnTrait::Fn | FnTrait::AsyncFn => { if !matches!(derefed_callee.kind(Interner), TyKind::Ref(Mutability::Not, _, _)) { adjustments.push(Adjustment::borrow( Mutability::Not, @@ -2155,7 +2155,7 @@ impl InferenceContext<'_> { DebruijnIndex::INNERMOST, ) }, - |this, lt_ref| this.make_lifetime(lt_ref), + |this, lt_ref| this.make_body_lifetime(lt_ref), ), }; diff --git a/crates/hir-ty/src/infer/path.rs b/crates/hir-ty/src/infer/path.rs index 7550d197a3..a6296c3af2 100644 --- a/crates/hir-ty/src/infer/path.rs +++ b/crates/hir-ty/src/infer/path.rs @@ -19,7 +19,7 @@ use crate::{ TyBuilder, TyExt, TyKind, ValueTyDefId, }; -use super::{ExprOrPatId, InferenceContext}; +use super::{ExprOrPatId, InferenceContext, InferenceTyDiagnosticSource}; impl InferenceContext<'_> { pub(super) fn infer_path(&mut self, path: &Path, id: ExprOrPatId) -> Option<Ty> { @@ -163,6 +163,7 @@ impl InferenceContext<'_> { let remaining_segments_for_ty = path.segments().take(path.segments().len() - 1); let (ty, _) = ctx.lower_ty_relative_path(ty, orig_ns, remaining_segments_for_ty); + self.push_ty_diagnostics(InferenceTyDiagnosticSource::Body, ctx.diagnostics); let ty = self.table.insert_type_vars(ty); let ty = self.table.normalize_associated_types_in(ty); self.resolve_ty_assoc_item(ty, last.name, id).map(|(it, substs)| (it, Some(substs)))? @@ -265,6 +266,9 @@ impl InferenceContext<'_> { resolved_segment, remaining_segments_for_ty, true, + &mut |_, _reason| { + // FIXME: Report an error. + }, ) }); if ty.is_unknown() { diff --git a/crates/hir-ty/src/infer/unify.rs b/crates/hir-ty/src/infer/unify.rs index 54aa18ce20..165861c1b1 100644 --- a/crates/hir-ty/src/infer/unify.rs +++ b/crates/hir-ty/src/infer/unify.rs @@ -666,7 +666,7 @@ impl<'a> InferenceTable<'a> { highest_known_var: InferenceVar, } impl TypeFolder<Interner> for VarFudger<'_, '_> { - fn as_dyn(&mut self) -> &mut dyn TypeFolder<Interner, Error = Self::Error> { + fn as_dyn(&mut self) -> &mut dyn TypeFolder<Interner> { self } @@ -794,69 +794,75 @@ impl<'a> InferenceTable<'a> { ty: &Ty, num_args: usize, ) -> Option<(FnTrait, Vec<Ty>, Ty)> { - let krate = self.trait_env.krate; - let fn_once_trait = FnTrait::FnOnce.get_id(self.db, krate)?; - let trait_data = self.db.trait_data(fn_once_trait); - let output_assoc_type = - trait_data.associated_type_by_name(&Name::new_symbol_root(sym::Output.clone()))?; - - let mut arg_tys = Vec::with_capacity(num_args); - let arg_ty = TyBuilder::tuple(num_args) - .fill(|it| { - let arg = match it { - ParamKind::Type => self.new_type_var(), - ParamKind::Lifetime => unreachable!("Tuple with lifetime parameter"), - ParamKind::Const(_) => unreachable!("Tuple with const parameter"), - }; - arg_tys.push(arg.clone()); - arg.cast(Interner) - }) - .build(); - - let b = TyBuilder::trait_ref(self.db, fn_once_trait); - if b.remaining() != 2 { - return None; - } - let mut trait_ref = b.push(ty.clone()).push(arg_ty).build(); + for (fn_trait_name, output_assoc_name, subtraits) in [ + (FnTrait::FnOnce, sym::Output.clone(), &[FnTrait::Fn, FnTrait::FnMut][..]), + (FnTrait::AsyncFnMut, sym::CallRefFuture.clone(), &[FnTrait::AsyncFn]), + (FnTrait::AsyncFnOnce, sym::CallOnceFuture.clone(), &[]), + ] { + let krate = self.trait_env.krate; + let fn_trait = fn_trait_name.get_id(self.db, krate)?; + let trait_data = self.db.trait_data(fn_trait); + let output_assoc_type = + trait_data.associated_type_by_name(&Name::new_symbol_root(output_assoc_name))?; + + let mut arg_tys = Vec::with_capacity(num_args); + let arg_ty = TyBuilder::tuple(num_args) + .fill(|it| { + let arg = match it { + ParamKind::Type => self.new_type_var(), + ParamKind::Lifetime => unreachable!("Tuple with lifetime parameter"), + ParamKind::Const(_) => unreachable!("Tuple with const parameter"), + }; + arg_tys.push(arg.clone()); + arg.cast(Interner) + }) + .build(); + + let b = TyBuilder::trait_ref(self.db, fn_trait); + if b.remaining() != 2 { + return None; + } + let mut trait_ref = b.push(ty.clone()).push(arg_ty).build(); - let projection = { - TyBuilder::assoc_type_projection( + let projection = TyBuilder::assoc_type_projection( self.db, output_assoc_type, Some(trait_ref.substitution.clone()), ) - .build() - }; + .fill_with_unknown() + .build(); - let trait_env = self.trait_env.env.clone(); - let obligation = InEnvironment { - goal: trait_ref.clone().cast(Interner), - environment: trait_env.clone(), - }; - let canonical = self.canonicalize(obligation.clone()); - if self.db.trait_solve(krate, self.trait_env.block, canonical.cast(Interner)).is_some() { - self.register_obligation(obligation.goal); - let return_ty = self.normalize_projection_ty(projection); - for fn_x in [FnTrait::Fn, FnTrait::FnMut, FnTrait::FnOnce] { - let fn_x_trait = fn_x.get_id(self.db, krate)?; - trait_ref.trait_id = to_chalk_trait_id(fn_x_trait); - let obligation: chalk_ir::InEnvironment<chalk_ir::Goal<Interner>> = InEnvironment { - goal: trait_ref.clone().cast(Interner), - environment: trait_env.clone(), - }; - let canonical = self.canonicalize(obligation.clone()); - if self - .db - .trait_solve(krate, self.trait_env.block, canonical.cast(Interner)) - .is_some() - { - return Some((fn_x, arg_tys, return_ty)); + let trait_env = self.trait_env.env.clone(); + let obligation = InEnvironment { + goal: trait_ref.clone().cast(Interner), + environment: trait_env.clone(), + }; + let canonical = self.canonicalize(obligation.clone()); + if self.db.trait_solve(krate, self.trait_env.block, canonical.cast(Interner)).is_some() + { + self.register_obligation(obligation.goal); + let return_ty = self.normalize_projection_ty(projection); + for &fn_x in subtraits { + let fn_x_trait = fn_x.get_id(self.db, krate)?; + trait_ref.trait_id = to_chalk_trait_id(fn_x_trait); + let obligation: chalk_ir::InEnvironment<chalk_ir::Goal<Interner>> = + InEnvironment { + goal: trait_ref.clone().cast(Interner), + environment: trait_env.clone(), + }; + let canonical = self.canonicalize(obligation.clone()); + if self + .db + .trait_solve(krate, self.trait_env.block, canonical.cast(Interner)) + .is_some() + { + return Some((fn_x, arg_tys, return_ty)); + } } + return Some((fn_trait_name, arg_tys, return_ty)); } - unreachable!("It should at least implement FnOnce at this point"); - } else { - None } + None } pub(super) fn insert_type_vars<T>(&mut self, ty: T) -> T @@ -1004,7 +1010,7 @@ mod resolve { where F: Fn(InferenceVar, VariableKind, GenericArg, DebruijnIndex) -> GenericArg, { - fn as_dyn(&mut self) -> &mut dyn TypeFolder<Interner, Error = Self::Error> { + fn as_dyn(&mut self) -> &mut dyn TypeFolder<Interner> { self } diff --git a/crates/hir-ty/src/inhabitedness.rs b/crates/hir-ty/src/inhabitedness.rs index c0a781b17e..56b549436c 100644 --- a/crates/hir-ty/src/inhabitedness.rs +++ b/crates/hir-ty/src/inhabitedness.rs @@ -5,8 +5,7 @@ use chalk_ir::{ visit::{TypeSuperVisitable, TypeVisitable, TypeVisitor}, DebruijnIndex, }; -use hir_def::{visibility::Visibility, AdtId, EnumVariantId, HasModule, ModuleId, VariantId}; -use intern::sym; +use hir_def::{visibility::Visibility, AdtId, EnumVariantId, ModuleId, VariantId}; use rustc_hash::FxHashSet; use crate::{ @@ -118,11 +117,6 @@ impl UninhabitedFrom<'_> { variant: VariantId, subst: &Substitution, ) -> ControlFlow<VisiblyUninhabited> { - let is_local = variant.krate(self.db.upcast()) == self.target_mod.krate(); - if !is_local && self.db.attrs(variant.into()).by_key(&sym::non_exhaustive).exists() { - return CONTINUE_OPAQUELY_INHABITED; - } - let variant_data = self.db.variant_data(variant); let fields = variant_data.fields(); if fields.is_empty() { diff --git a/crates/hir-ty/src/lib.rs b/crates/hir-ty/src/lib.rs index 22e7b1d920..8bb90ca31e 100644 --- a/crates/hir-ty/src/lib.rs +++ b/crates/hir-ty/src/lib.rs @@ -84,12 +84,14 @@ pub use infer::{ cast::CastError, closure::{CaptureKind, CapturedItem}, could_coerce, could_unify, could_unify_deeply, Adjust, Adjustment, AutoBorrow, BindingMode, - InferenceDiagnostic, InferenceResult, OverloadedDeref, PointerCast, + InferenceDiagnostic, InferenceResult, InferenceTyDiagnosticSource, OverloadedDeref, + PointerCast, }; pub use interner::Interner; pub use lower::{ - associated_type_shorthand_candidates, ImplTraitLoweringMode, ParamLoweringMode, TyDefId, - TyLoweringContext, ValueTyDefId, + associated_type_shorthand_candidates, GenericArgsProhibitedReason, ImplTraitLoweringMode, + ParamLoweringMode, TyDefId, TyLoweringContext, TyLoweringDiagnostic, TyLoweringDiagnosticKind, + ValueTyDefId, }; pub use mapping::{ from_assoc_type_id, from_chalk_trait_id, from_foreign_def_id, from_placeholder_idx, @@ -385,7 +387,6 @@ pub enum FnAbi { Fastcall, FastcallUnwind, Msp430Interrupt, - PlatformIntrinsic, PtxKernel, RiscvInterruptM, RiscvInterruptS, @@ -444,7 +445,6 @@ impl FnAbi { s if *s == sym::fastcall_dash_unwind => FnAbi::FastcallUnwind, s if *s == sym::fastcall => FnAbi::Fastcall, s if *s == sym::msp430_dash_interrupt => FnAbi::Msp430Interrupt, - s if *s == sym::platform_dash_intrinsic => FnAbi::PlatformIntrinsic, s if *s == sym::ptx_dash_kernel => FnAbi::PtxKernel, s if *s == sym::riscv_dash_interrupt_dash_m => FnAbi::RiscvInterruptM, s if *s == sym::riscv_dash_interrupt_dash_s => FnAbi::RiscvInterruptS, @@ -487,7 +487,6 @@ impl FnAbi { FnAbi::Fastcall => "fastcall", FnAbi::FastcallUnwind => "fastcall-unwind", FnAbi::Msp430Interrupt => "msp430-interrupt", - FnAbi::PlatformIntrinsic => "platform-intrinsic", FnAbi::PtxKernel => "ptx-kernel", FnAbi::RiscvInterruptM => "riscv-interrupt-m", FnAbi::RiscvInterruptS => "riscv-interrupt-s", @@ -646,7 +645,7 @@ pub(crate) fn fold_free_vars<T: HasInterner<Interner = Interner> + TypeFoldable< F2: FnMut(Ty, BoundVar, DebruijnIndex) -> Const, > TypeFolder<Interner> for FreeVarFolder<F1, F2> { - fn as_dyn(&mut self) -> &mut dyn TypeFolder<Interner, Error = Self::Error> { + fn as_dyn(&mut self) -> &mut dyn TypeFolder<Interner> { self } @@ -697,7 +696,7 @@ pub(crate) fn fold_tys_and_consts<T: HasInterner<Interner = Interner> + TypeFold impl<F: FnMut(Either<Ty, Const>, DebruijnIndex) -> Either<Ty, Const>> TypeFolder<Interner> for TyFolder<F> { - fn as_dyn(&mut self) -> &mut dyn TypeFolder<Interner, Error = Self::Error> { + fn as_dyn(&mut self) -> &mut dyn TypeFolder<Interner> { self } diff --git a/crates/hir-ty/src/lower.rs b/crates/hir-ty/src/lower.rs index b868ea95f8..b23f2749ab 100644 --- a/crates/hir-ty/src/lower.rs +++ b/crates/hir-ty/src/lower.rs @@ -33,8 +33,8 @@ use hir_def::{ path::{GenericArg, GenericArgs, ModPath, Path, PathKind, PathSegment, PathSegments}, resolver::{HasResolver, LifetimeNs, Resolver, TypeNs}, type_ref::{ - ConstRef, LifetimeRef, TraitBoundModifier, TraitRef as HirTraitRef, TypeBound, TypeRef, - TypeRefId, TypesMap, TypesSourceMap, + ConstRef, LifetimeRef, PathId, TraitBoundModifier, TraitRef as HirTraitRef, TypeBound, + TypeRef, TypeRefId, TypesMap, TypesSourceMap, }, AdtId, AssocItemId, CallableDefId, ConstId, ConstParamId, DefWithBodyId, EnumId, EnumVariantId, FunctionId, GenericDefId, GenericParamId, HasModule, ImplId, InTypeConstLoc, ItemContainerId, @@ -48,7 +48,7 @@ use rustc_pattern_analysis::Captures; use smallvec::SmallVec; use stdx::{impl_from, never}; use syntax::ast; -use triomphe::Arc; +use triomphe::{Arc, ThinArc}; use crate::{ all_super_traits, @@ -102,6 +102,31 @@ impl ImplTraitLoweringState { } } +type TypeSource = Either<TypeRefId, hir_def::type_ref::TypeSource>; + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct TyLoweringDiagnostic { + pub source: TypeSource, + pub kind: TyLoweringDiagnosticKind, +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum TyLoweringDiagnosticKind { + GenericArgsProhibited { segment: u32, reason: GenericArgsProhibitedReason }, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum GenericArgsProhibitedReason { + Module, + TyParam, + SelfTy, + PrimitiveTy, + /// When there is a generic enum, within the expression `Enum::Variant`, + /// either `Enum` or `Variant` are allowed to have generic arguments, but not both. + // FIXME: This is not used now but it should be. + EnumVariant, +} + #[derive(Debug)] pub struct TyLoweringContext<'a> { pub db: &'a dyn HirDatabase, @@ -125,6 +150,7 @@ pub struct TyLoweringContext<'a> { expander: Option<Expander>, /// Tracks types with explicit `?Sized` bounds. pub(crate) unsized_types: FxHashSet<Ty>, + pub(crate) diagnostics: Vec<TyLoweringDiagnostic>, } impl<'a> TyLoweringContext<'a> { @@ -159,6 +185,7 @@ impl<'a> TyLoweringContext<'a> { type_param_mode, expander: None, unsized_types: FxHashSet::default(), + diagnostics: Vec::new(), } } @@ -198,6 +225,20 @@ impl<'a> TyLoweringContext<'a> { self.type_param_mode = type_param_mode; self } + + pub fn push_diagnostic(&mut self, type_ref: TypeRefId, kind: TyLoweringDiagnosticKind) { + let source = match self.types_source_map { + Some(source_map) => { + let Ok(source) = source_map.type_syntax(type_ref) else { + stdx::never!("error in synthetic type"); + return; + }; + Either::Right(source) + } + None => Either::Left(type_ref), + }; + self.diagnostics.push(TyLoweringDiagnostic { source, kind }); + } } #[derive(Copy, Clone, Debug, PartialEq, Eq, Default)] @@ -264,7 +305,8 @@ impl<'a> TyLoweringContext<'a> { .intern(Interner) } TypeRef::Path(path) => { - let (ty, res_) = self.lower_path(path); + let (ty, res_) = + self.lower_path(path, PathId::from_type_ref_unchecked(type_ref_id)); res = res_; ty } @@ -463,6 +505,7 @@ impl<'a> TyLoweringContext<'a> { impl_trait_mode: mem::take(&mut self.impl_trait_mode), expander: self.expander.take(), unsized_types: mem::take(&mut self.unsized_types), + diagnostics: mem::take(&mut self.diagnostics), }; let ty = inner_ctx.lower_ty(type_ref); @@ -470,6 +513,7 @@ impl<'a> TyLoweringContext<'a> { self.impl_trait_mode = inner_ctx.impl_trait_mode; self.expander = inner_ctx.expander; self.unsized_types = inner_ctx.unsized_types; + self.diagnostics = inner_ctx.diagnostics; self.expander.as_mut().unwrap().exit(mark); Some(ty) @@ -541,6 +585,10 @@ impl<'a> TyLoweringContext<'a> { resolved_segment: PathSegment<'_>, remaining_segments: PathSegments<'_>, infer_args: bool, + on_prohibited_generics_for_resolved_segment: &mut dyn FnMut( + &mut Self, + GenericArgsProhibitedReason, + ), ) -> (Ty, Option<TypeNs>) { let ty = match resolution { TypeNs::TraitId(trait_) => { @@ -607,28 +655,44 @@ impl<'a> TyLoweringContext<'a> { // FIXME(trait_alias): Implement trait alias. return (TyKind::Error.intern(Interner), None); } - TypeNs::GenericParam(param_id) => match self.type_param_mode { - ParamLoweringMode::Placeholder => { - TyKind::Placeholder(to_placeholder_idx(self.db, param_id.into())) + TypeNs::GenericParam(param_id) => { + if resolved_segment.args_and_bindings.is_some() { + on_prohibited_generics_for_resolved_segment( + self, + GenericArgsProhibitedReason::TyParam, + ); } - ParamLoweringMode::Variable => { - let idx = match self - .generics() - .expect("generics in scope") - .type_or_const_param_idx(param_id.into()) - { - None => { - never!("no matching generics"); - return (TyKind::Error.intern(Interner), None); - } - Some(idx) => idx, - }; - TyKind::BoundVar(BoundVar::new(self.in_binders, idx)) + match self.type_param_mode { + ParamLoweringMode::Placeholder => { + TyKind::Placeholder(to_placeholder_idx(self.db, param_id.into())) + } + ParamLoweringMode::Variable => { + let idx = match self + .generics() + .expect("generics in scope") + .type_or_const_param_idx(param_id.into()) + { + None => { + never!("no matching generics"); + return (TyKind::Error.intern(Interner), None); + } + Some(idx) => idx, + }; + + TyKind::BoundVar(BoundVar::new(self.in_binders, idx)) + } } + .intern(Interner) } - .intern(Interner), TypeNs::SelfType(impl_id) => { + if resolved_segment.args_and_bindings.is_some() { + on_prohibited_generics_for_resolved_segment( + self, + GenericArgsProhibitedReason::SelfTy, + ); + } + let generics = self.generics().expect("impl should have generic param scope"); match self.type_param_mode { @@ -654,6 +718,13 @@ impl<'a> TyLoweringContext<'a> { } } TypeNs::AdtSelfType(adt) => { + if resolved_segment.args_and_bindings.is_some() { + on_prohibited_generics_for_resolved_segment( + self, + GenericArgsProhibitedReason::SelfTy, + ); + } + let generics = generics(self.db.upcast(), adt.into()); let substs = match self.type_param_mode { ParamLoweringMode::Placeholder => generics.placeholder_subst(self.db), @@ -666,6 +737,12 @@ impl<'a> TyLoweringContext<'a> { TypeNs::AdtId(it) => self.lower_path_inner(resolved_segment, it.into(), infer_args), TypeNs::BuiltinType(it) => { + if resolved_segment.args_and_bindings.is_some() { + on_prohibited_generics_for_resolved_segment( + self, + GenericArgsProhibitedReason::PrimitiveTy, + ); + } self.lower_path_inner(resolved_segment, it.into(), infer_args) } TypeNs::TypeAliasId(it) => { @@ -677,7 +754,7 @@ impl<'a> TyLoweringContext<'a> { self.lower_ty_relative_path(ty, Some(resolution), remaining_segments) } - pub(crate) fn lower_path(&mut self, path: &Path) -> (Ty, Option<TypeNs>) { + pub(crate) fn lower_path(&mut self, path: &Path, path_id: PathId) -> (Ty, Option<TypeNs>) { // Resolve the path (in type namespace) if let Some(type_ref) = path.type_anchor() { let (ty, res) = self.lower_ty_ext(type_ref); @@ -692,19 +769,44 @@ impl<'a> TyLoweringContext<'a> { if matches!(resolution, TypeNs::TraitId(_)) && remaining_index.is_none() { // trait object type without dyn - let bound = TypeBound::Path(path.clone(), TraitBoundModifier::None); + let bound = TypeBound::Path(path_id, TraitBoundModifier::None); let ty = self.lower_dyn_trait(&[bound]); return (ty, None); } - let (resolved_segment, remaining_segments) = match remaining_index { - None => ( - path.segments().last().expect("resolved path has at least one element"), - PathSegments::EMPTY, - ), - Some(i) => (path.segments().get(i - 1).unwrap(), path.segments().skip(i)), - }; - self.lower_partly_resolved_path(resolution, resolved_segment, remaining_segments, false) + let (module_segments, resolved_segment_idx, resolved_segment, remaining_segments) = + match remaining_index { + None => ( + path.segments().strip_last(), + path.segments().len() - 1, + path.segments().last().expect("resolved path has at least one element"), + PathSegments::EMPTY, + ), + Some(i) => ( + path.segments().take(i - 1), + i - 1, + path.segments().get(i - 1).unwrap(), + path.segments().skip(i), + ), + }; + + self.prohibit_generics(path_id, 0, module_segments, GenericArgsProhibitedReason::Module); + + self.lower_partly_resolved_path( + resolution, + resolved_segment, + remaining_segments, + false, + &mut |this, reason| { + this.push_diagnostic( + path_id.type_ref(), + TyLoweringDiagnosticKind::GenericArgsProhibited { + segment: resolved_segment_idx as u32, + reason, + }, + ) + }, + ) } fn select_associated_type(&mut self, res: Option<TypeNs>, segment: PathSegment<'_>) -> Ty { @@ -741,12 +843,8 @@ impl<'a> TyLoweringContext<'a> { // generic params. It's inefficient to splice the `Substitution`s, so we may want // that method to optionally take parent `Substitution` as we already know them at // this point (`t.substitution`). - let substs = self.substs_from_path_segment( - segment.clone(), - Some(associated_ty.into()), - false, - None, - ); + let substs = + self.substs_from_path_segment(segment, Some(associated_ty.into()), false, None); let len_self = crate::generics::generics(self.db.upcast(), associated_ty.into()).len_self(); @@ -998,12 +1096,41 @@ impl<'a> TyLoweringContext<'a> { TraitRef { trait_id: to_chalk_trait_id(resolved), substitution: substs } } - fn lower_trait_ref_from_path(&mut self, path: &Path, explicit_self_ty: Ty) -> Option<TraitRef> { + fn prohibit_generics( + &mut self, + path_id: PathId, + idx: u32, + segments: PathSegments<'_>, + reason: GenericArgsProhibitedReason, + ) { + segments.iter().zip(idx..).for_each(|(segment, idx)| { + if segment.args_and_bindings.is_some() { + self.push_diagnostic( + path_id.type_ref(), + TyLoweringDiagnosticKind::GenericArgsProhibited { segment: idx, reason }, + ); + } + }); + } + + fn lower_trait_ref_from_path( + &mut self, + path_id: PathId, + explicit_self_ty: Ty, + ) -> Option<TraitRef> { + let path = &self.types_map[path_id]; let resolved = match self.resolver.resolve_path_in_type_ns_fully(self.db.upcast(), path)? { // FIXME(trait_alias): We need to handle trait alias here. TypeNs::TraitId(tr) => tr, _ => return None, }; + // Do this after we verify it's indeed a trait to not confuse the user if they're not modules. + self.prohibit_generics( + path_id, + 0, + path.segments().strip_last(), + GenericArgsProhibitedReason::Module, + ); let segment = path.segments().last().expect("path should have at least one segment"); Some(self.lower_trait_ref_from_resolved_path(resolved, segment, explicit_self_ty)) } @@ -1013,7 +1140,7 @@ impl<'a> TyLoweringContext<'a> { trait_ref: &HirTraitRef, explicit_self_ty: Ty, ) -> Option<TraitRef> { - self.lower_trait_ref_from_path(&trait_ref.path, explicit_self_ty) + self.lower_trait_ref_from_path(trait_ref.path, explicit_self_ty) } fn trait_ref_substs_from_path( @@ -1072,11 +1199,11 @@ impl<'a> TyLoweringContext<'a> { ) -> impl Iterator<Item = QuantifiedWhereClause> + use<'b, 'a> { let mut trait_ref = None; let clause = match bound { - TypeBound::Path(path, TraitBoundModifier::None) => { + &TypeBound::Path(path, TraitBoundModifier::None) => { trait_ref = self.lower_trait_ref_from_path(path, self_ty); trait_ref.clone().map(WhereClause::Implemented).map(crate::wrap_empty_binders) } - TypeBound::Path(path, TraitBoundModifier::Maybe) => { + &TypeBound::Path(path, TraitBoundModifier::Maybe) => { let sized_trait = self .db .lang_item(self.resolver.krate(), LangItem::Sized) @@ -1092,7 +1219,7 @@ impl<'a> TyLoweringContext<'a> { } None } - TypeBound::ForLifetime(_, path) => { + &TypeBound::ForLifetime(_, path) => { // FIXME Don't silently drop the hrtb lifetimes here trait_ref = self.lower_trait_ref_from_path(path, self_ty); trait_ref.clone().map(WhereClause::Implemented).map(crate::wrap_empty_binders) @@ -1121,8 +1248,8 @@ impl<'a> TyLoweringContext<'a> { trait_ref: TraitRef, ) -> impl Iterator<Item = QuantifiedWhereClause> + use<'b, 'a> { let last_segment = match bound { - TypeBound::Path(path, TraitBoundModifier::None) | TypeBound::ForLifetime(_, path) => { - path.segments().last() + &TypeBound::Path(path, TraitBoundModifier::None) | &TypeBound::ForLifetime(_, path) => { + self.types_map[path].segments().last() } TypeBound::Path(_, TraitBoundModifier::Maybe) | TypeBound::Use(_) @@ -1227,7 +1354,9 @@ impl<'a> TyLoweringContext<'a> { } _ => unreachable!(), } - ext.lower_ty(type_ref) + let ty = ext.lower_ty(type_ref); + self.diagnostics.extend(ext.diagnostics); + ty } else { self.lower_ty(type_ref) }; @@ -1523,11 +1652,24 @@ fn named_associated_type_shorthand_candidates<R>( } } -/// Build the type of all specific fields of a struct or enum variant. +pub(crate) type Diagnostics = Option<ThinArc<(), TyLoweringDiagnostic>>; + +fn create_diagnostics(diagnostics: Vec<TyLoweringDiagnostic>) -> Diagnostics { + (!diagnostics.is_empty()).then(|| ThinArc::from_header_and_iter((), diagnostics.into_iter())) +} + pub(crate) fn field_types_query( db: &dyn HirDatabase, variant_id: VariantId, ) -> Arc<ArenaMap<LocalFieldId, Binders<Ty>>> { + db.field_types_with_diagnostics(variant_id).0 +} + +/// Build the type of all specific fields of a struct or enum variant. +pub(crate) fn field_types_with_diagnostics_query( + db: &dyn HirDatabase, + variant_id: VariantId, +) -> (Arc<ArenaMap<LocalFieldId, Binders<Ty>>>, Diagnostics) { let var_data = variant_id.variant_data(db.upcast()); let (resolver, def): (_, GenericDefId) = match variant_id { VariantId::StructId(it) => (it.resolver(db.upcast()), it.into()), @@ -1543,7 +1685,7 @@ pub(crate) fn field_types_query( for (field_id, field_data) in var_data.fields().iter() { res.insert(field_id, make_binders(db, &generics, ctx.lower_ty(field_data.type_ref))); } - Arc::new(res) + (Arc::new(res), create_diagnostics(ctx.diagnostics)) } /// This query exists only to be used when resolving short-hand associated types @@ -1593,9 +1735,10 @@ pub(crate) fn generic_predicates_for_param_query( } match bound { - TypeBound::ForLifetime(_, path) | TypeBound::Path(path, _) => { + &TypeBound::ForLifetime(_, path) | &TypeBound::Path(path, _) => { // Only lower the bound if the trait could possibly define the associated // type we're looking for. + let path = &ctx.types_map[path]; let Some(assoc_name) = &assoc_name else { return true }; let Some(TypeNs::TraitId(tr)) = @@ -1743,15 +1886,22 @@ pub(crate) fn generic_predicates_query( db: &dyn HirDatabase, def: GenericDefId, ) -> GenericPredicates { - generic_predicates_filtered_by(db, def, |_, _| true) + generic_predicates_filtered_by(db, def, |_, _| true).0 } -/// Resolve the where clause(s) of an item with generics, -/// except the ones inherited from the parent pub(crate) fn generic_predicates_without_parent_query( db: &dyn HirDatabase, def: GenericDefId, ) -> GenericPredicates { + db.generic_predicates_without_parent_with_diagnostics(def).0 +} + +/// Resolve the where clause(s) of an item with generics, +/// except the ones inherited from the parent +pub(crate) fn generic_predicates_without_parent_with_diagnostics_query( + db: &dyn HirDatabase, + def: GenericDefId, +) -> (GenericPredicates, Diagnostics) { generic_predicates_filtered_by(db, def, |_, d| *d == def) } @@ -1761,7 +1911,7 @@ fn generic_predicates_filtered_by<F>( db: &dyn HirDatabase, def: GenericDefId, filter: F, -) -> GenericPredicates +) -> (GenericPredicates, Diagnostics) where F: Fn(&WherePredicate, &GenericDefId) -> bool, { @@ -1802,7 +1952,10 @@ where ); }; } - GenericPredicates(predicates.is_empty().not().then(|| predicates.into())) + ( + GenericPredicates(predicates.is_empty().not().then(|| predicates.into())), + create_diagnostics(ctx.diagnostics), + ) } /// Generate implicit `: Sized` predicates for all generics that has no `?Sized` bound. @@ -1855,75 +2008,110 @@ impl ops::Deref for GenericDefaults { } } -/// Resolve the default type params from generics pub(crate) fn generic_defaults_query(db: &dyn HirDatabase, def: GenericDefId) -> GenericDefaults { + db.generic_defaults_with_diagnostics(def).0 +} + +/// Resolve the default type params from generics. +/// +/// Diagnostics are only returned for this `GenericDefId` (returned defaults include parents). +pub(crate) fn generic_defaults_with_diagnostics_query( + db: &dyn HirDatabase, + def: GenericDefId, +) -> (GenericDefaults, Diagnostics) { let generic_params = generics(db.upcast(), def); if generic_params.len() == 0 { - return GenericDefaults(None); + return (GenericDefaults(None), None); } let resolver = def.resolver(db.upcast()); let parent_start_idx = generic_params.len_self(); - let mut ctx = TyLoweringContext::new(db, &resolver, TypesMap::EMPTY, def.into()) - .with_impl_trait_mode(ImplTraitLoweringMode::Disallowed) - .with_type_param_mode(ParamLoweringMode::Variable); - GenericDefaults(Some(Arc::from_iter(generic_params.iter_with_types_map().enumerate().map( - |(idx, ((id, p), types_map))| { - ctx.types_map = types_map; - match p { - GenericParamDataRef::TypeParamData(p) => { - let ty = p.default.as_ref().map_or(TyKind::Error.intern(Interner), |ty| { - // Each default can only refer to previous parameters. - // Type variable default referring to parameter coming - // after it is forbidden (FIXME: report diagnostic) - fallback_bound_vars(ctx.lower_ty(*ty), idx, parent_start_idx) - }); - crate::make_binders(db, &generic_params, ty.cast(Interner)) - } - GenericParamDataRef::ConstParamData(p) => { - let GenericParamId::ConstParamId(id) = id else { - unreachable!("Unexpected lifetime or type argument") - }; + let mut ctx = + TyLoweringContext::new(db, &resolver, generic_params.self_types_map(), def.into()) + .with_impl_trait_mode(ImplTraitLoweringMode::Disallowed) + .with_type_param_mode(ParamLoweringMode::Variable); + let mut idx = 0; + let mut defaults = generic_params + .iter_self() + .map(|(id, p)| { + let result = + handle_generic_param(&mut ctx, idx, id, p, parent_start_idx, &generic_params); + idx += 1; + result + }) + .collect::<Vec<_>>(); + let diagnostics = create_diagnostics(mem::take(&mut ctx.diagnostics)); + defaults.extend(generic_params.iter_parents_with_types_map().map(|((id, p), types_map)| { + ctx.types_map = types_map; + let result = handle_generic_param(&mut ctx, idx, id, p, parent_start_idx, &generic_params); + idx += 1; + result + })); + let defaults = GenericDefaults(Some(Arc::from_iter(defaults))); + return (defaults, diagnostics); + + fn handle_generic_param( + ctx: &mut TyLoweringContext<'_>, + idx: usize, + id: GenericParamId, + p: GenericParamDataRef<'_>, + parent_start_idx: usize, + generic_params: &Generics, + ) -> Binders<crate::GenericArg> { + match p { + GenericParamDataRef::TypeParamData(p) => { + let ty = p.default.as_ref().map_or(TyKind::Error.intern(Interner), |ty| { + // Each default can only refer to previous parameters. + // Type variable default referring to parameter coming + // after it is forbidden (FIXME: report diagnostic) + fallback_bound_vars(ctx.lower_ty(*ty), idx, parent_start_idx) + }); + crate::make_binders(ctx.db, generic_params, ty.cast(Interner)) + } + GenericParamDataRef::ConstParamData(p) => { + let GenericParamId::ConstParamId(id) = id else { + unreachable!("Unexpected lifetime or type argument") + }; - let mut val = p.default.as_ref().map_or_else( - || unknown_const_as_generic(db.const_param_ty(id)), - |c| { - let param_ty = ctx.lower_ty(p.ty); - let c = ctx.lower_const(c, param_ty); - c.cast(Interner) - }, - ); - // Each default can only refer to previous parameters, see above. - val = fallback_bound_vars(val, idx, parent_start_idx); - make_binders(db, &generic_params, val) - } - GenericParamDataRef::LifetimeParamData(_) => { - make_binders(db, &generic_params, error_lifetime().cast(Interner)) - } + let mut val = p.default.as_ref().map_or_else( + || unknown_const_as_generic(ctx.db.const_param_ty(id)), + |c| { + let param_ty = ctx.lower_ty(p.ty); + let c = ctx.lower_const(c, param_ty); + c.cast(Interner) + }, + ); + // Each default can only refer to previous parameters, see above. + val = fallback_bound_vars(val, idx, parent_start_idx); + make_binders(ctx.db, generic_params, val) } - }, - )))) + GenericParamDataRef::LifetimeParamData(_) => { + make_binders(ctx.db, generic_params, error_lifetime().cast(Interner)) + } + } + } } -pub(crate) fn generic_defaults_recover( +pub(crate) fn generic_defaults_with_diagnostics_recover( db: &dyn HirDatabase, _cycle: &Cycle, def: &GenericDefId, -) -> GenericDefaults { +) -> (GenericDefaults, Diagnostics) { let generic_params = generics(db.upcast(), *def); if generic_params.len() == 0 { - return GenericDefaults(None); + return (GenericDefaults(None), None); } // FIXME: this code is not covered in tests. // we still need one default per parameter - GenericDefaults(Some(Arc::from_iter(generic_params.iter_id().map(|id| { + let defaults = GenericDefaults(Some(Arc::from_iter(generic_params.iter_id().map(|id| { let val = match id { GenericParamId::TypeParamId(_) => TyKind::Error.intern(Interner).cast(Interner), GenericParamId::ConstParamId(id) => unknown_const_as_generic(db.const_param_ty(id)), GenericParamId::LifetimeParamId(_) => error_lifetime().cast(Interner), }; crate::make_binders(db, &generic_params, val) - })))) + })))); + (defaults, None) } fn fn_sig_for_fn(db: &dyn HirDatabase, def: FunctionId) -> PolyFnSig { @@ -2066,7 +2254,10 @@ fn type_for_adt(db: &dyn HirDatabase, adt: AdtId) -> Binders<Ty> { make_binders(db, &generics, ty) } -fn type_for_type_alias(db: &dyn HirDatabase, t: TypeAliasId) -> Binders<Ty> { +pub(crate) fn type_for_type_alias_with_diagnostics_query( + db: &dyn HirDatabase, + t: TypeAliasId, +) -> (Binders<Ty>, Diagnostics) { let generics = generics(db.upcast(), t.into()); let resolver = t.resolver(db.upcast()); let type_alias_data = db.type_alias_data(t); @@ -2081,7 +2272,7 @@ fn type_for_type_alias(db: &dyn HirDatabase, t: TypeAliasId) -> Binders<Ty> { .map(|type_ref| ctx.lower_ty(type_ref)) .unwrap_or_else(|| TyKind::Error.intern(Interner)) }; - make_binders(db, &generics, inner) + (make_binders(db, &generics, inner), create_diagnostics(ctx.diagnostics)) } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] @@ -2124,7 +2315,7 @@ pub(crate) fn ty_query(db: &dyn HirDatabase, def: TyDefId) -> Binders<Ty> { match def { TyDefId::BuiltinType(it) => Binders::empty(Interner, TyBuilder::builtin(it)), TyDefId::AdtId(it) => type_for_adt(db, it), - TyDefId::TypeAliasId(it) => type_for_type_alias(db, it), + TyDefId::TypeAliasId(it) => db.type_for_type_alias_with_diagnostics(it).0, } } @@ -2149,47 +2340,73 @@ pub(crate) fn value_ty_query(db: &dyn HirDatabase, def: ValueTyDefId) -> Option< } pub(crate) fn impl_self_ty_query(db: &dyn HirDatabase, impl_id: ImplId) -> Binders<Ty> { + db.impl_self_ty_with_diagnostics(impl_id).0 +} + +pub(crate) fn impl_self_ty_with_diagnostics_query( + db: &dyn HirDatabase, + impl_id: ImplId, +) -> (Binders<Ty>, Diagnostics) { let impl_data = db.impl_data(impl_id); let resolver = impl_id.resolver(db.upcast()); let generics = generics(db.upcast(), impl_id.into()); let mut ctx = TyLoweringContext::new(db, &resolver, &impl_data.types_map, impl_id.into()) .with_type_param_mode(ParamLoweringMode::Variable); - make_binders(db, &generics, ctx.lower_ty(impl_data.self_ty)) + ( + make_binders(db, &generics, ctx.lower_ty(impl_data.self_ty)), + create_diagnostics(ctx.diagnostics), + ) } -// returns None if def is a type arg pub(crate) fn const_param_ty_query(db: &dyn HirDatabase, def: ConstParamId) -> Ty { + db.const_param_ty_with_diagnostics(def).0 +} + +// returns None if def is a type arg +pub(crate) fn const_param_ty_with_diagnostics_query( + db: &dyn HirDatabase, + def: ConstParamId, +) -> (Ty, Diagnostics) { let parent_data = db.generic_params(def.parent()); let data = &parent_data[def.local_id()]; let resolver = def.parent().resolver(db.upcast()); let mut ctx = TyLoweringContext::new(db, &resolver, &parent_data.types_map, def.parent().into()); - match data { + let ty = match data { TypeOrConstParamData::TypeParamData(_) => { never!(); Ty::new(Interner, TyKind::Error) } TypeOrConstParamData::ConstParamData(d) => ctx.lower_ty(d.ty), - } + }; + (ty, create_diagnostics(ctx.diagnostics)) } -pub(crate) fn impl_self_ty_recover( +pub(crate) fn impl_self_ty_with_diagnostics_recover( db: &dyn HirDatabase, _cycle: &Cycle, impl_id: &ImplId, -) -> Binders<Ty> { +) -> (Binders<Ty>, Diagnostics) { let generics = generics(db.upcast(), (*impl_id).into()); - make_binders(db, &generics, TyKind::Error.intern(Interner)) + (make_binders(db, &generics, TyKind::Error.intern(Interner)), None) } pub(crate) fn impl_trait_query(db: &dyn HirDatabase, impl_id: ImplId) -> Option<Binders<TraitRef>> { + db.impl_trait_with_diagnostics(impl_id).map(|it| it.0) +} + +pub(crate) fn impl_trait_with_diagnostics_query( + db: &dyn HirDatabase, + impl_id: ImplId, +) -> Option<(Binders<TraitRef>, Diagnostics)> { let impl_data = db.impl_data(impl_id); let resolver = impl_id.resolver(db.upcast()); let mut ctx = TyLoweringContext::new(db, &resolver, &impl_data.types_map, impl_id.into()) .with_type_param_mode(ParamLoweringMode::Variable); let (self_ty, binders) = db.impl_self_ty(impl_id).into_value_and_skipped_binders(); let target_trait = impl_data.target_trait.as_ref()?; - Some(Binders::new(binders, ctx.lower_trait_ref(target_trait, self_ty)?)) + let trait_ref = Binders::new(binders, ctx.lower_trait_ref(target_trait, self_ty)?); + Some((trait_ref, create_diagnostics(ctx.diagnostics))) } pub(crate) fn return_type_impl_traits( diff --git a/crates/hir-ty/src/mir/lower.rs b/crates/hir-ty/src/mir/lower.rs index c4e0640051..1d1044df6e 100644 --- a/crates/hir-ty/src/mir/lower.rs +++ b/crates/hir-ty/src/mir/lower.rs @@ -2023,11 +2023,11 @@ pub fn mir_body_for_closure_query( ctx.result.locals.alloc(Local { ty: infer[*root].clone() }); let closure_local = ctx.result.locals.alloc(Local { ty: match kind { - FnTrait::FnOnce => infer[expr].clone(), - FnTrait::FnMut => { + FnTrait::FnOnce | FnTrait::AsyncFnOnce => infer[expr].clone(), + FnTrait::FnMut | FnTrait::AsyncFnMut => { TyKind::Ref(Mutability::Mut, error_lifetime(), infer[expr].clone()).intern(Interner) } - FnTrait::Fn => { + FnTrait::Fn | FnTrait::AsyncFn => { TyKind::Ref(Mutability::Not, error_lifetime(), infer[expr].clone()).intern(Interner) } }, @@ -2055,8 +2055,10 @@ pub fn mir_body_for_closure_query( let mut err = None; let closure_local = ctx.result.locals.iter().nth(1).unwrap().0; let closure_projection = match kind { - FnTrait::FnOnce => vec![], - FnTrait::FnMut | FnTrait::Fn => vec![ProjectionElem::Deref], + FnTrait::FnOnce | FnTrait::AsyncFnOnce => vec![], + FnTrait::FnMut | FnTrait::Fn | FnTrait::AsyncFnMut | FnTrait::AsyncFn => { + vec![ProjectionElem::Deref] + } }; ctx.result.walk_places(|p, store| { if let Some(it) = upvar_map.get(&p.local) { diff --git a/crates/hir-ty/src/tests.rs b/crates/hir-ty/src/tests.rs index bcf9d5ceff..cabeeea2bd 100644 --- a/crates/hir-ty/src/tests.rs +++ b/crates/hir-ty/src/tests.rs @@ -18,13 +18,13 @@ use std::sync::LazyLock; use base_db::SourceDatabaseFileInputExt as _; use expect_test::Expect; use hir_def::{ - body::{Body, BodySourceMap, SyntheticSyntax}, + body::{Body, BodySourceMap}, db::DefDatabase, hir::{ExprId, Pat, PatId}, item_scope::ItemScope, nameres::DefMap, src::HasSource, - AssocItemId, DefWithBodyId, HasModule, LocalModuleId, Lookup, ModuleDefId, + AssocItemId, DefWithBodyId, HasModule, LocalModuleId, Lookup, ModuleDefId, SyntheticSyntax, }; use hir_expand::{db::ExpandDatabase, FileRange, InFile}; use itertools::Itertools; diff --git a/crates/hir-ty/src/tests/coercion.rs b/crates/hir-ty/src/tests/coercion.rs index 273571901a..7992f1feee 100644 --- a/crates/hir-ty/src/tests/coercion.rs +++ b/crates/hir-ty/src/tests/coercion.rs @@ -942,3 +942,19 @@ fn main() { "#, ) } + +#[test] +fn regression_18626() { + check_no_mismatches( + r#" +fn f() { + trait T { + fn f() {} + } + impl T for i32 {} + impl T for u32 {} + &[i32::f, u32::f] as &[fn()]; +} + "#, + ); +} diff --git a/crates/hir-ty/src/tests/traits.rs b/crates/hir-ty/src/tests/traits.rs index 624148cab2..e15d44bd6d 100644 --- a/crates/hir-ty/src/tests/traits.rs +++ b/crates/hir-ty/src/tests/traits.rs @@ -1631,6 +1631,29 @@ fn test<'lifetime>( } #[test] +fn lifetime_bounds() { + check_infer( + r#" +//- minicore: sized, coerce_unsized +trait Trait<'a>: Sized { + fn f(&'a self) {} +} +fn test<'a, 'b: 'a>(it: impl Trait<'a>){ + it.f(); +} +"#, + expect![[r#" + 38..42 'self': &'a Self + 44..46 '{}': () + 69..71 'it': impl Trait<'a> + 88..103 '{ it.f(); }': () + 94..96 'it': impl Trait<'a> + 94..100 'it.f()': () + "#]], + ); +} + +#[test] fn error_bound_chalk() { check_types( r#" @@ -4811,3 +4834,53 @@ fn bar(v: *const ()) { "#]], ); } + +#[test] +fn async_fn_traits() { + check_infer( + r#" +//- minicore: async_fn +async fn foo<T: AsyncFn(u32) -> i32>(a: T) { + let fut1 = a(0); + fut1.await; +} +async fn bar<T: AsyncFnMut(u32) -> i32>(mut b: T) { + let fut2 = b(0); + fut2.await; +} +async fn baz<T: AsyncFnOnce(u32) -> i32>(c: T) { + let fut3 = c(0); + fut3.await; +} + "#, + expect![[r#" + 37..38 'a': T + 43..83 '{ ...ait; }': () + 43..83 '{ ...ait; }': impl Future<Output = ()> + 53..57 'fut1': AsyncFnMut::CallRefFuture<'?, T, (u32,)> + 60..61 'a': T + 60..64 'a(0)': AsyncFnMut::CallRefFuture<'?, T, (u32,)> + 62..63 '0': u32 + 70..74 'fut1': AsyncFnMut::CallRefFuture<'?, T, (u32,)> + 70..80 'fut1.await': i32 + 124..129 'mut b': T + 134..174 '{ ...ait; }': () + 134..174 '{ ...ait; }': impl Future<Output = ()> + 144..148 'fut2': AsyncFnMut::CallRefFuture<'?, T, (u32,)> + 151..152 'b': T + 151..155 'b(0)': AsyncFnMut::CallRefFuture<'?, T, (u32,)> + 153..154 '0': u32 + 161..165 'fut2': AsyncFnMut::CallRefFuture<'?, T, (u32,)> + 161..171 'fut2.await': i32 + 216..217 'c': T + 222..262 '{ ...ait; }': () + 222..262 '{ ...ait; }': impl Future<Output = ()> + 232..236 'fut3': AsyncFnOnce::CallOnceFuture<T, (u32,)> + 239..240 'c': T + 239..243 'c(0)': AsyncFnOnce::CallOnceFuture<T, (u32,)> + 241..242 '0': u32 + 249..253 'fut3': AsyncFnOnce::CallOnceFuture<T, (u32,)> + 249..259 'fut3.await': i32 + "#]], + ); +} diff --git a/crates/hir-ty/src/traits.rs b/crates/hir-ty/src/traits.rs index 51ccd4ef29..8cb7dbf60f 100644 --- a/crates/hir-ty/src/traits.rs +++ b/crates/hir-ty/src/traits.rs @@ -220,6 +220,10 @@ pub enum FnTrait { FnOnce, FnMut, Fn, + + AsyncFnOnce, + AsyncFnMut, + AsyncFn, } impl fmt::Display for FnTrait { @@ -228,6 +232,9 @@ impl fmt::Display for FnTrait { FnTrait::FnOnce => write!(f, "FnOnce"), FnTrait::FnMut => write!(f, "FnMut"), FnTrait::Fn => write!(f, "Fn"), + FnTrait::AsyncFnOnce => write!(f, "AsyncFnOnce"), + FnTrait::AsyncFnMut => write!(f, "AsyncFnMut"), + FnTrait::AsyncFn => write!(f, "AsyncFn"), } } } @@ -238,6 +245,9 @@ impl FnTrait { FnTrait::FnOnce => "call_once", FnTrait::FnMut => "call_mut", FnTrait::Fn => "call", + FnTrait::AsyncFnOnce => "async_call_once", + FnTrait::AsyncFnMut => "async_call_mut", + FnTrait::AsyncFn => "async_call", } } @@ -246,6 +256,9 @@ impl FnTrait { FnTrait::FnOnce => LangItem::FnOnce, FnTrait::FnMut => LangItem::FnMut, FnTrait::Fn => LangItem::Fn, + FnTrait::AsyncFnOnce => LangItem::AsyncFnOnce, + FnTrait::AsyncFnMut => LangItem::AsyncFnMut, + FnTrait::AsyncFn => LangItem::AsyncFn, } } @@ -254,15 +267,19 @@ impl FnTrait { LangItem::FnOnce => Some(FnTrait::FnOnce), LangItem::FnMut => Some(FnTrait::FnMut), LangItem::Fn => Some(FnTrait::Fn), + LangItem::AsyncFnOnce => Some(FnTrait::AsyncFnOnce), + LangItem::AsyncFnMut => Some(FnTrait::AsyncFnMut), + LangItem::AsyncFn => Some(FnTrait::AsyncFn), _ => None, } } pub const fn to_chalk_ir(self) -> rust_ir::ClosureKind { + // Chalk doesn't support async fn traits. match self { - FnTrait::FnOnce => rust_ir::ClosureKind::FnOnce, - FnTrait::FnMut => rust_ir::ClosureKind::FnMut, - FnTrait::Fn => rust_ir::ClosureKind::Fn, + FnTrait::AsyncFnOnce | FnTrait::FnOnce => rust_ir::ClosureKind::FnOnce, + FnTrait::AsyncFnMut | FnTrait::FnMut => rust_ir::ClosureKind::FnMut, + FnTrait::AsyncFn | FnTrait::Fn => rust_ir::ClosureKind::Fn, } } @@ -271,6 +288,9 @@ impl FnTrait { FnTrait::FnOnce => Name::new_symbol_root(sym::call_once.clone()), FnTrait::FnMut => Name::new_symbol_root(sym::call_mut.clone()), FnTrait::Fn => Name::new_symbol_root(sym::call.clone()), + FnTrait::AsyncFnOnce => Name::new_symbol_root(sym::async_call_once.clone()), + FnTrait::AsyncFnMut => Name::new_symbol_root(sym::async_call_mut.clone()), + FnTrait::AsyncFn => Name::new_symbol_root(sym::async_call.clone()), } } diff --git a/crates/hir-ty/src/utils.rs b/crates/hir-ty/src/utils.rs index 28bda1e10e..06719b09f7 100644 --- a/crates/hir-ty/src/utils.rs +++ b/crates/hir-ty/src/utils.rs @@ -185,7 +185,7 @@ fn direct_super_traits_cb(db: &dyn DefDatabase, trait_: TraitId, cb: impl FnMut( } }; match is_trait { - true => bound.as_path(), + true => bound.as_path(&generic_params.types_map), false => None, } } diff --git a/crates/hir/Cargo.toml b/crates/hir/Cargo.toml index 26666d6feb..6aadc5c4f7 100644 --- a/crates/hir/Cargo.toml +++ b/crates/hir/Cargo.toml @@ -33,6 +33,14 @@ syntax.workspace = true tt.workspace = true span.workspace = true +[dev-dependencies] +expect-test.workspace = true + +# local deps +test-utils.workspace = true +test-fixture.workspace = true +syntax-bridge.workspace = true + [features] in-rust-tree = ["hir-expand/in-rust-tree"] diff --git a/crates/hir/src/diagnostics.rs b/crates/hir/src/diagnostics.rs index 8297acde85..612c6adb20 100644 --- a/crates/hir/src/diagnostics.rs +++ b/crates/hir/src/diagnostics.rs @@ -3,21 +3,35 @@ //! //! This probably isn't the best way to do this -- ideally, diagnostics should //! be expressed in terms of hir types themselves. -pub use hir_ty::diagnostics::{CaseType, IncorrectCase}; -use hir_ty::{ - db::HirDatabase, diagnostics::BodyValidationDiagnostic, CastError, InferenceDiagnostic, -}; - use cfg::{CfgExpr, CfgOptions}; use either::Either; -pub use hir_def::VariantId; -use hir_def::{body::SyntheticSyntax, hir::ExprOrPatId, path::ModPath, AssocItemId, DefWithBodyId}; +use hir_def::{ + hir::ExprOrPatId, + path::{hir_segment_to_ast_segment, ModPath}, + type_ref::TypesSourceMap, + AssocItemId, DefWithBodyId, SyntheticSyntax, +}; use hir_expand::{name::Name, HirFileId, InFile}; -use syntax::{ast, AstPtr, SyntaxError, SyntaxNodePtr, TextRange}; +use hir_ty::{ + db::HirDatabase, + diagnostics::{BodyValidationDiagnostic, UnsafetyReason}, + CastError, InferenceDiagnostic, InferenceTyDiagnosticSource, TyLoweringDiagnostic, + TyLoweringDiagnosticKind, +}; +use syntax::{ + ast::{self, HasGenericArgs}, + AstPtr, SyntaxError, SyntaxNodePtr, TextRange, +}; use triomphe::Arc; use crate::{AssocItem, Field, Local, Trait, Type}; +pub use hir_def::VariantId; +pub use hir_ty::{ + diagnostics::{CaseType, IncorrectCase}, + GenericArgsProhibitedReason, +}; + macro_rules! diagnostics { ($($diag:ident,)*) => { #[derive(Debug)] @@ -96,6 +110,7 @@ diagnostics![ UnresolvedIdent, UnusedMut, UnusedVariable, + GenericArgsProhibited, ]; #[derive(Debug)] @@ -258,9 +273,10 @@ pub struct PrivateField { #[derive(Debug)] pub struct MissingUnsafe { - pub expr: InFile<AstPtr<Either<ast::Expr, ast::Pat>>>, + pub node: InFile<AstPtr<Either<ast::Expr, ast::Pat>>>, /// If true, the diagnostics is an `unsafe_op_in_unsafe_fn` lint instead of a hard error. pub only_lint: bool, + pub reason: UnsafetyReason, } #[derive(Debug)] @@ -385,6 +401,12 @@ pub struct InvalidCast { pub cast_ty: Type, } +#[derive(Debug)] +pub struct GenericArgsProhibited { + pub args: InFile<AstPtr<Either<ast::GenericArgList, ast::ParenthesizedArgList>>>, + pub reason: GenericArgsProhibitedReason, +} + impl AnyDiagnostic { pub(crate) fn body_validation_diagnostic( db: &dyn HirDatabase, @@ -524,6 +546,7 @@ impl AnyDiagnostic { db: &dyn HirDatabase, def: DefWithBodyId, d: &InferenceDiagnostic, + outer_types_source_map: &TypesSourceMap, source_map: &hir_def::body::BodySourceMap, ) -> Option<AnyDiagnostic> { let expr_syntax = |expr| { @@ -637,6 +660,44 @@ impl AnyDiagnostic { let cast_ty = Type::new(db, def, cast_ty.clone()); InvalidCast { expr, error: *error, expr_ty, cast_ty }.into() } + InferenceDiagnostic::TyDiagnostic { source, diag } => { + let source_map = match source { + InferenceTyDiagnosticSource::Body => &source_map.types, + InferenceTyDiagnosticSource::Signature => outer_types_source_map, + }; + Self::ty_diagnostic(diag, source_map, db)? + } + }) + } + + pub(crate) fn ty_diagnostic( + diag: &TyLoweringDiagnostic, + source_map: &TypesSourceMap, + db: &dyn HirDatabase, + ) -> Option<AnyDiagnostic> { + let source = match diag.source { + Either::Left(type_ref_id) => { + let Ok(source) = source_map.type_syntax(type_ref_id) else { + stdx::never!("error on synthetic type syntax"); + return None; + }; + source + } + Either::Right(source) => source, + }; + let syntax = || source.value.to_node(&db.parse_or_expand(source.file_id)); + Some(match diag.kind { + TyLoweringDiagnosticKind::GenericArgsProhibited { segment, reason } => { + let ast::Type::PathType(syntax) = syntax() else { return None }; + let segment = hir_segment_to_ast_segment(&syntax.path()?, segment)?; + let args = if let Some(generics) = segment.generic_arg_list() { + AstPtr::new(&generics).wrap_left() + } else { + AstPtr::new(&segment.parenthesized_arg_list()?).wrap_right() + }; + let args = source.with_value(args); + GenericArgsProhibited { args, reason }.into() + } }) } } diff --git a/crates/hir/src/display.rs b/crates/hir/src/display.rs index 9275f45d88..959d62d595 100644 --- a/crates/hir/src/display.rs +++ b/crates/hir/src/display.rs @@ -132,12 +132,18 @@ impl HirDisplay for Function { } else { match &data.types_map[data.ret_type] { TypeRef::ImplTrait(bounds) => match &bounds[0] { - TypeBound::Path(path, _) => Some( - *path.segments().iter().last().unwrap().args_and_bindings.unwrap().bindings - [0] - .type_ref - .as_ref() - .unwrap(), + &TypeBound::Path(path, _) => Some( + *data.types_map[path] + .segments() + .iter() + .last() + .unwrap() + .args_and_bindings + .unwrap() + .bindings[0] + .type_ref + .as_ref() + .unwrap(), ), _ => None, }, diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index c9498b3aea..83d72dfcf1 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -20,12 +20,11 @@ #![cfg_attr(feature = "in-rust-tree", feature(rustc_private))] #![recursion_limit = "512"] -mod semantics; -mod source_analyzer; - mod attrs; mod from_id; mod has_source; +mod semantics; +mod source_analyzer; pub mod db; pub mod diagnostics; @@ -43,7 +42,7 @@ use arrayvec::ArrayVec; use base_db::{CrateDisplayName, CrateId, CrateOrigin}; use either::Either; use hir_def::{ - body::{BodyDiagnostic, SyntheticSyntax}, + body::BodyDiagnostic, data::adt::VariantData, generics::{LifetimeParamData, TypeOrConstParamData, TypeParamProvenance}, hir::{BindingAnnotation, BindingId, ExprId, ExprOrPatId, LabelId, Pat}, @@ -54,11 +53,12 @@ use hir_def::{ path::ImportAlias, per_ns::PerNs, resolver::{HasResolver, Resolver}, + type_ref::TypesSourceMap, AssocItemId, AssocItemLoc, AttrDefId, CallableDefId, ConstId, ConstParamId, CrateRootModuleId, DefWithBodyId, EnumId, EnumVariantId, ExternCrateId, FunctionId, GenericDefId, GenericParamId, HasModule, ImplId, InTypeConstId, ItemContainerId, LifetimeParamId, LocalFieldId, Lookup, - MacroExpander, ModuleId, StaticId, StructId, TraitAliasId, TraitId, TupleId, TypeAliasId, - TypeOrConstParamId, TypeParamId, UnionId, + MacroExpander, ModuleId, StaticId, StructId, SyntheticSyntax, TraitAliasId, TraitId, TupleId, + TypeAliasId, TypeOrConstParamId, TypeParamId, UnionId, }; use hir_expand::{ attrs::collect_attrs, proc_macro::ProcMacroKind, AstId, MacroCallKind, RenderedExpandError, @@ -76,8 +76,8 @@ use hir_ty::{ traits::FnTrait, AliasTy, CallableSig, Canonical, CanonicalVarKinds, Cast, ClosureId, GenericArg, GenericArgData, Interner, ParamKind, QuantifiedWhereClause, Scalar, Substitution, - TraitEnvironment, TraitRefExt, Ty, TyBuilder, TyDefId, TyExt, TyKind, ValueTyDefId, - WhereClause, + TraitEnvironment, TraitRefExt, Ty, TyBuilder, TyDefId, TyExt, TyKind, TyLoweringDiagnostic, + ValueTyDefId, WhereClause, }; use itertools::Itertools; use nameres::diagnostics::DefDiagnosticKind; @@ -89,7 +89,7 @@ use syntax::{ ast::{self, HasAttrs as _, HasGenericParams, HasName}, format_smolstr, AstNode, AstPtr, SmolStr, SyntaxNode, SyntaxNodePtr, TextRange, ToSmolStr, T, }; -use triomphe::Arc; +use triomphe::{Arc, ThinArc}; use crate::db::{DefDatabase, HirDatabase}; @@ -147,6 +147,7 @@ pub use { }, hir_ty::{ consteval::ConstEvalError, + diagnostics::UnsafetyReason, display::{ClosureStyle, HirDisplay, HirDisplayError, HirWrite}, dyn_compatibility::{DynCompatibilityViolation, MethodViolationCode}, layout::LayoutError, @@ -410,6 +411,10 @@ impl ModuleDef { } } + if let Some(def) = self.as_self_generic_def() { + def.diagnostics(db, &mut acc); + } + acc } @@ -430,6 +435,23 @@ impl ModuleDef { } } + /// Returns only defs that have generics from themselves, not their parent. + pub fn as_self_generic_def(self) -> Option<GenericDef> { + match self { + ModuleDef::Function(it) => Some(it.into()), + ModuleDef::Adt(it) => Some(it.into()), + ModuleDef::Trait(it) => Some(it.into()), + ModuleDef::TraitAlias(it) => Some(it.into()), + ModuleDef::TypeAlias(it) => Some(it.into()), + ModuleDef::Module(_) + | ModuleDef::Variant(_) + | ModuleDef::Static(_) + | ModuleDef::Const(_) + | ModuleDef::BuiltinType(_) + | ModuleDef::Macro(_) => None, + } + } + pub fn attrs(&self, db: &dyn HirDatabase) -> Option<AttrsWithOwner> { Some(match self { ModuleDef::Module(it) => it.attrs(db), @@ -604,17 +626,42 @@ impl Module { ModuleDef::Adt(adt) => { match adt { Adt::Struct(s) => { + let tree_id = s.id.lookup(db.upcast()).id; + let tree_source_maps = tree_id.item_tree_with_source_map(db.upcast()).1; + push_ty_diagnostics( + db, + acc, + db.field_types_with_diagnostics(s.id.into()).1, + tree_source_maps.strukt(tree_id.value).item(), + ); for diag in db.struct_data_with_diagnostics(s.id).1.iter() { emit_def_diagnostic(db, acc, diag, edition); } } Adt::Union(u) => { + let tree_id = u.id.lookup(db.upcast()).id; + let tree_source_maps = tree_id.item_tree_with_source_map(db.upcast()).1; + push_ty_diagnostics( + db, + acc, + db.field_types_with_diagnostics(u.id.into()).1, + tree_source_maps.union(tree_id.value).item(), + ); for diag in db.union_data_with_diagnostics(u.id).1.iter() { emit_def_diagnostic(db, acc, diag, edition); } } Adt::Enum(e) => { for v in e.variants(db) { + let tree_id = v.id.lookup(db.upcast()).id; + let tree_source_maps = + tree_id.item_tree_with_source_map(db.upcast()).1; + push_ty_diagnostics( + db, + acc, + db.field_types_with_diagnostics(v.id.into()).1, + tree_source_maps.variant(tree_id.value), + ); 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, edition); @@ -625,6 +672,17 @@ impl Module { acc.extend(def.diagnostics(db, style_lints)) } ModuleDef::Macro(m) => emit_macro_def_diagnostics(db, acc, m), + ModuleDef::TypeAlias(type_alias) => { + let tree_id = type_alias.id.lookup(db.upcast()).id; + let tree_source_maps = tree_id.item_tree_with_source_map(db.upcast()).1; + push_ty_diagnostics( + db, + acc, + db.type_for_type_alias_with_diagnostics(type_alias.id).1, + tree_source_maps.type_alias(tree_id.value).item(), + ); + acc.extend(def.diagnostics(db, style_lints)); + } _ => acc.extend(def.diagnostics(db, style_lints)), } } @@ -634,8 +692,11 @@ impl Module { let mut impl_assoc_items_scratch = vec![]; for impl_def in self.impl_defs(db) { + GenericDef::Impl(impl_def).diagnostics(db, acc); + let loc = impl_def.id.lookup(db.upcast()); - let tree = loc.id.item_tree(db.upcast()); + let (tree, tree_source_maps) = loc.id.item_tree_with_source_map(db.upcast()); + let source_map = tree_source_maps.impl_(loc.id.value).item(); let node = &tree[loc.id.value]; let file_id = loc.id.file_id(); if file_id.macro_file().map_or(false, |it| it.is_builtin_derive(db.upcast())) { @@ -770,6 +831,19 @@ impl Module { impl_assoc_items_scratch.clear(); } + push_ty_diagnostics( + db, + acc, + db.impl_self_ty_with_diagnostics(impl_def.id).1, + source_map, + ); + push_ty_diagnostics( + db, + acc, + db.impl_trait_with_diagnostics(impl_def.id).and_then(|it| it.1), + source_map, + ); + for &item in db.impl_data(impl_def.id).items.iter() { AssocItem::from(item).diagnostics(db, acc, style_lints); } @@ -1801,6 +1875,25 @@ impl DefWithBody { let krate = self.module(db).id.krate(); let (body, source_map) = db.body_with_source_map(self.into()); + let item_tree_source_maps; + let outer_types_source_map = match self { + DefWithBody::Function(function) => { + let function = function.id.lookup(db.upcast()).id; + item_tree_source_maps = function.item_tree_with_source_map(db.upcast()).1; + item_tree_source_maps.function(function.value).item() + } + DefWithBody::Static(statik) => { + let statik = statik.id.lookup(db.upcast()).id; + item_tree_source_maps = statik.item_tree_with_source_map(db.upcast()).1; + item_tree_source_maps.statik(statik.value) + } + DefWithBody::Const(konst) => { + let konst = konst.id.lookup(db.upcast()).id; + item_tree_source_maps = konst.item_tree_with_source_map(db.upcast()).1; + item_tree_source_maps.konst(konst.value) + } + DefWithBody::Variant(_) | DefWithBody::InTypeConst(_) => &TypesSourceMap::EMPTY, + }; for (_, def_map) in body.blocks(db.upcast()) { Module { id: def_map.module_id(DefMap::ROOT) }.diagnostics(db, acc, style_lints); @@ -1860,7 +1953,13 @@ impl DefWithBody { let infer = db.infer(self.into()); for d in &infer.diagnostics { - acc.extend(AnyDiagnostic::inference_diagnostic(db, self.into(), d, &source_map)); + acc.extend(AnyDiagnostic::inference_diagnostic( + db, + self.into(), + d, + outer_types_source_map, + &source_map, + )); } for (pat_or_expr, mismatch) in infer.type_mismatches() { @@ -1890,10 +1989,10 @@ impl DefWithBody { ); } - let (unafe_exprs, only_lint) = hir_ty::diagnostics::missing_unsafe(db, self.into()); - for expr in unafe_exprs { - match source_map.expr_or_pat_syntax(expr) { - Ok(expr) => acc.push(MissingUnsafe { expr, only_lint }.into()), + let (unsafe_exprs, only_lint) = hir_ty::diagnostics::missing_unsafe(db, self.into()); + for (node, reason) in unsafe_exprs { + match source_map.expr_or_pat_syntax(node) { + Ok(node) => acc.push(MissingUnsafe { node, only_lint, reason }.into()), Err(SyntheticSyntax) => { // FIXME: Here and elsewhere in this file, the `expr` was // desugared, report or assert that this doesn't happen. @@ -3324,12 +3423,22 @@ impl AssocItem { ) { match self { AssocItem::Function(func) => { + GenericDef::Function(func).diagnostics(db, acc); DefWithBody::from(func).diagnostics(db, acc, style_lints); } AssocItem::Const(const_) => { DefWithBody::from(const_).diagnostics(db, acc, style_lints); } AssocItem::TypeAlias(type_alias) => { + GenericDef::TypeAlias(type_alias).diagnostics(db, acc); + let tree_id = type_alias.id.lookup(db.upcast()).id; + let tree_source_maps = tree_id.item_tree_with_source_map(db.upcast()).1; + push_ty_diagnostics( + db, + acc, + db.type_for_type_alias_with_diagnostics(type_alias.id).1, + tree_source_maps.type_alias(tree_id.value).item(), + ); for diag in hir_ty::diagnostics::incorrect_case(db, type_alias.id.into()) { acc.push(diag.into()); } @@ -3416,6 +3525,97 @@ impl GenericDef { }) .collect() } + + fn id(self) -> GenericDefId { + match self { + GenericDef::Function(it) => it.id.into(), + GenericDef::Adt(it) => it.into(), + GenericDef::Trait(it) => it.id.into(), + GenericDef::TraitAlias(it) => it.id.into(), + GenericDef::TypeAlias(it) => it.id.into(), + GenericDef::Impl(it) => it.id.into(), + GenericDef::Const(it) => it.id.into(), + } + } + + pub fn diagnostics(self, db: &dyn HirDatabase, acc: &mut Vec<AnyDiagnostic>) { + let def = self.id(); + + let item_tree_source_maps; + let (generics, generics_source_map) = db.generic_params_with_source_map(def); + + if generics.is_empty() && generics.no_predicates() { + return; + } + + let source_map = match &generics_source_map { + Some(it) => it, + None => match def { + GenericDefId::FunctionId(it) => { + let id = it.lookup(db.upcast()).id; + item_tree_source_maps = id.item_tree_with_source_map(db.upcast()).1; + item_tree_source_maps.function(id.value).generics() + } + GenericDefId::AdtId(AdtId::EnumId(it)) => { + let id = it.lookup(db.upcast()).id; + item_tree_source_maps = id.item_tree_with_source_map(db.upcast()).1; + item_tree_source_maps.enum_generic(id.value) + } + GenericDefId::AdtId(AdtId::StructId(it)) => { + let id = it.lookup(db.upcast()).id; + item_tree_source_maps = id.item_tree_with_source_map(db.upcast()).1; + item_tree_source_maps.strukt(id.value).generics() + } + GenericDefId::AdtId(AdtId::UnionId(it)) => { + let id = it.lookup(db.upcast()).id; + item_tree_source_maps = id.item_tree_with_source_map(db.upcast()).1; + item_tree_source_maps.union(id.value).generics() + } + GenericDefId::TraitId(it) => { + let id = it.lookup(db.upcast()).id; + item_tree_source_maps = id.item_tree_with_source_map(db.upcast()).1; + item_tree_source_maps.trait_generic(id.value) + } + GenericDefId::TraitAliasId(it) => { + let id = it.lookup(db.upcast()).id; + item_tree_source_maps = id.item_tree_with_source_map(db.upcast()).1; + item_tree_source_maps.trait_alias_generic(id.value) + } + GenericDefId::TypeAliasId(it) => { + let id = it.lookup(db.upcast()).id; + item_tree_source_maps = id.item_tree_with_source_map(db.upcast()).1; + item_tree_source_maps.type_alias(id.value).generics() + } + GenericDefId::ImplId(it) => { + let id = it.lookup(db.upcast()).id; + item_tree_source_maps = id.item_tree_with_source_map(db.upcast()).1; + item_tree_source_maps.impl_(id.value).generics() + } + GenericDefId::ConstId(_) => return, + }, + }; + + push_ty_diagnostics(db, acc, db.generic_defaults_with_diagnostics(def).1, source_map); + push_ty_diagnostics( + db, + acc, + db.generic_predicates_without_parent_with_diagnostics(def).1, + source_map, + ); + for (param_id, param) in generics.iter_type_or_consts() { + if let TypeOrConstParamData::ConstParamData(_) = param { + push_ty_diagnostics( + db, + acc, + db.const_param_ty_with_diagnostics(ConstParamId::from_unchecked( + TypeOrConstParamId { parent: def, local_id: param_id }, + )) + .1, + source_map, + ); + } + } + } } /// A single local definition. @@ -3581,6 +3781,18 @@ impl Local { } } +impl PartialOrd for Local { + fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { + Some(self.cmp(other)) + } +} + +impl Ord for Local { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.binding_id.cmp(&other.binding_id) + } +} + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct DeriveHelper { pub(crate) derive: MacroId, @@ -5799,3 +6011,19 @@ pub enum DocLinkDef { Field(Field), SelfType(Trait), } + +fn push_ty_diagnostics( + db: &dyn HirDatabase, + acc: &mut Vec<AnyDiagnostic>, + diagnostics: Option<ThinArc<(), TyLoweringDiagnostic>>, + source_map: &TypesSourceMap, +) { + if let Some(diagnostics) = diagnostics { + acc.extend( + diagnostics + .slice + .iter() + .filter_map(|diagnostic| AnyDiagnostic::ty_diagnostic(diagnostic, source_map, db)), + ); + } +} diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs index 9d3f8e5fba..f9d3f9d07e 100644 --- a/crates/hir/src/semantics.rs +++ b/crates/hir/src/semantics.rs @@ -34,7 +34,7 @@ use intern::Symbol; use itertools::Itertools; use rustc_hash::{FxHashMap, FxHashSet}; use smallvec::{smallvec, SmallVec}; -use span::{EditionedFileId, FileId, HirFileIdRepr, SyntaxContextId}; +use span::{AstIdMap, EditionedFileId, FileId, HirFileIdRepr, SyntaxContextId}; use stdx::TupleExt; use syntax::{ algo::skip_trivia_token, @@ -42,6 +42,7 @@ use syntax::{ AstNode, AstToken, Direction, SyntaxKind, SyntaxNode, SyntaxNodePtr, SyntaxToken, TextRange, TextSize, }; +use triomphe::Arc; use crate::{ db::HirDatabase, @@ -509,6 +510,22 @@ impl<'db> SemanticsImpl<'db> { self.with_ctx(|ctx| ctx.has_derives(adt)) } + pub fn derive_helpers_in_scope(&self, adt: &ast::Adt) -> Option<Vec<(Symbol, Symbol)>> { + let sa = self.analyze_no_infer(adt.syntax())?; + let id = self.db.ast_id_map(sa.file_id).ast_id(adt); + let result = sa + .resolver + .def_map() + .derive_helpers_in_scope(InFile::new(sa.file_id, id))? + .iter() + .map(|(name, macro_, _)| { + let macro_name = Macro::from(*macro_).name(self.db).symbol().clone(); + (name.symbol().clone(), macro_name) + }) + .collect(); + Some(result) + } + pub fn derive_helper(&self, attr: &ast::Attr) -> Option<Vec<(Macro, MacroFileId)>> { let adt = attr.syntax().ancestors().find_map(ast::Item::cast).and_then(|it| match it { ast::Item::Struct(it) => Some(ast::Adt::Struct(it)), @@ -1500,6 +1517,10 @@ impl<'db> SemanticsImpl<'db> { self.analyze(path.syntax())?.resolve_path(self.db, path) } + pub fn resolve_use_type_arg(&self, name: &ast::NameRef) -> Option<TypeParam> { + self.analyze(name.syntax())?.resolve_use_type_arg(name) + } + pub fn resolve_mod_path( &self, scope: &SyntaxNode, @@ -1973,10 +1994,16 @@ impl SemanticsScope<'_> { /// Resolve a path as-if it was written at the given scope. This is /// necessary a heuristic, as it doesn't take hygiene into account. pub fn speculative_resolve(&self, ast_path: &ast::Path) -> Option<PathResolution> { + let root = ast_path.syntax().ancestors().last().unwrap(); + let ast_id_map = Arc::new(AstIdMap::from_source(&root)); let (mut types_map, mut types_source_map) = (TypesMap::default(), TypesSourceMap::default()); - let mut ctx = - LowerCtx::new(self.db.upcast(), self.file_id, &mut types_map, &mut types_source_map); + let mut ctx = LowerCtx::for_synthetic_ast( + self.db.upcast(), + ast_id_map, + &mut types_map, + &mut types_source_map, + ); let path = Path::from_src(&mut ctx, ast_path.clone())?; resolve_hir_path( self.db, @@ -2003,6 +2030,10 @@ impl SemanticsScope<'_> { ) } + pub fn generic_def(&self) -> Option<crate::GenericDef> { + self.resolver.generic_def().map(|id| id.into()) + } + pub fn extern_crates(&self) -> impl Iterator<Item = (Name, Module)> + '_ { self.resolver.extern_crates_in_scope().map(|(name, id)| (name, Module { id })) } diff --git a/crates/hir/src/source_analyzer.rs b/crates/hir/src/source_analyzer.rs index c16454cff6..4329a888b3 100644 --- a/crates/hir/src/source_analyzer.rs +++ b/crates/hir/src/source_analyzer.rs @@ -36,7 +36,7 @@ use hir_expand::{ use hir_ty::{ diagnostics::{ record_literal_missing_fields, record_pattern_missing_fields, unsafe_expressions, - UnsafeExpr, + InsideUnsafeBlock, }, lang_items::lang_items_for_bin_op, method_resolution, Adjustment, InferenceResult, Interner, Substitution, Ty, TyExt, TyKind, @@ -642,6 +642,14 @@ impl SourceAnalyzer { } } + pub(crate) fn resolve_use_type_arg(&self, name: &ast::NameRef) -> Option<crate::TypeParam> { + let name = name.as_name(); + self.resolver + .all_generic_params() + .find_map(|(params, parent)| params.find_type_by_name(&name, *parent)) + .map(crate::TypeParam::from) + } + pub(crate) fn resolve_path( &self, db: &dyn HirDatabase, @@ -939,8 +947,8 @@ impl SourceAnalyzer { *def, body, expr_id, - &mut |UnsafeExpr { inside_unsafe_block, .. }| { - is_unsafe |= !inside_unsafe_block + &mut |_, inside_unsafe_block, _| { + is_unsafe |= inside_unsafe_block == InsideUnsafeBlock::No }, ) }; 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 7f8ea44fb1..57df39d541 100644 --- a/crates/ide-assists/src/handlers/add_missing_impl_members.rs +++ b/crates/ide-assists/src/handlers/add_missing_impl_members.rs @@ -2318,4 +2318,49 @@ impl<'a> Test<'a, i32> for bool { "#, ); } + + #[test] + fn issue_17321() { + check_assist( + add_missing_impl_members, + r#" +fn main() {} + +mod other_file_1 { + pub const SOME_CONSTANT: usize = 8; +} + +mod other_file_2 { + use crate::other_file_1::SOME_CONSTANT; + + pub trait Trait { + type Iter: Iterator<Item = [u8; SOME_CONSTANT]>; + } +} + +pub struct MyStruct; + +impl other_file_2::Trait for MyStruct$0 {}"#, + r#" +fn main() {} + +mod other_file_1 { + pub const SOME_CONSTANT: usize = 8; +} + +mod other_file_2 { + use crate::other_file_1::SOME_CONSTANT; + + pub trait Trait { + type Iter: Iterator<Item = [u8; SOME_CONSTANT]>; + } +} + +pub struct MyStruct; + +impl other_file_2::Trait for MyStruct { + $0type Iter; +}"#, + ); + } } diff --git a/crates/ide-assists/src/handlers/add_turbo_fish.rs b/crates/ide-assists/src/handlers/add_turbo_fish.rs index 17efbcbd6c..0f6970d940 100644 --- a/crates/ide-assists/src/handlers/add_turbo_fish.rs +++ b/crates/ide-assists/src/handlers/add_turbo_fish.rs @@ -1,8 +1,9 @@ use either::Either; use ide_db::defs::{Definition, NameRefClass}; use syntax::{ - ast::{self, make, HasArgList, HasGenericArgs}, - ted, AstNode, + ast::{self, make, syntax_factory::SyntaxFactory, HasArgList, HasGenericArgs}, + syntax_editor::Position, + AstNode, }; use crate::{ @@ -91,20 +92,34 @@ pub(crate) fn add_turbo_fish(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opti AssistId("add_type_ascription", AssistKind::RefactorRewrite), "Add `: _` before assignment operator", ident.text_range(), - |edit| { - let let_stmt = edit.make_mut(let_stmt); + |builder| { + let mut editor = builder.make_editor(let_stmt.syntax()); if let_stmt.semicolon_token().is_none() { - ted::append_child(let_stmt.syntax(), make::tokens::semicolon()); + editor.insert( + Position::last_child_of(let_stmt.syntax()), + make::tokens::semicolon(), + ); } let placeholder_ty = make::ty_placeholder().clone_for_update(); - let_stmt.set_ty(Some(placeholder_ty.clone())); - - if let Some(cap) = ctx.config.snippet_cap { - edit.add_placeholder_snippet(cap, placeholder_ty); + if let Some(pat) = let_stmt.pat() { + let elements = vec![ + make::token(syntax::SyntaxKind::COLON).into(), + make::token(syntax::SyntaxKind::WHITESPACE).into(), + placeholder_ty.syntax().clone().into(), + ]; + editor.insert_all(Position::after(pat.syntax()), elements); + if let Some(cap) = ctx.config.snippet_cap { + editor.add_annotation( + placeholder_ty.syntax(), + builder.make_placeholder_snippet(cap), + ); + } } + + builder.add_file_edits(ctx.file_id(), editor); }, )? } else { @@ -123,38 +138,58 @@ pub(crate) fn add_turbo_fish(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opti AssistId("add_turbo_fish", AssistKind::RefactorRewrite), "Add `::<>`", ident.text_range(), - |edit| { - edit.trigger_parameter_hints(); + |builder| { + builder.trigger_parameter_hints(); + + let make = SyntaxFactory::new(); + let mut editor = match &turbofish_target { + Either::Left(it) => builder.make_editor(it.syntax()), + Either::Right(it) => builder.make_editor(it.syntax()), + }; + + let fish_head = get_fish_head(&make, number_of_arguments); - let new_arg_list = match turbofish_target { + match turbofish_target { Either::Left(path_segment) => { - edit.make_mut(path_segment).get_or_create_generic_arg_list() + if let Some(generic_arg_list) = path_segment.generic_arg_list() { + editor.replace(generic_arg_list.syntax(), fish_head.syntax()); + } else { + editor.insert( + Position::last_child_of(path_segment.syntax()), + fish_head.syntax(), + ); + } } Either::Right(method_call) => { - edit.make_mut(method_call).get_or_create_generic_arg_list() + if let Some(generic_arg_list) = method_call.generic_arg_list() { + editor.replace(generic_arg_list.syntax(), fish_head.syntax()); + } else { + let position = if let Some(arg_list) = method_call.arg_list() { + Position::before(arg_list.syntax()) + } else { + Position::last_child_of(method_call.syntax()) + }; + editor.insert(position, fish_head.syntax()); + } } }; - let fish_head = get_fish_head(number_of_arguments).clone_for_update(); - - // Note: we need to replace the `new_arg_list` instead of being able to use something like - // `GenericArgList::add_generic_arg` as `PathSegment::get_or_create_generic_arg_list` - // always creates a non-turbofish form generic arg list. - ted::replace(new_arg_list.syntax(), fish_head.syntax()); - if let Some(cap) = ctx.config.snippet_cap { for arg in fish_head.generic_args() { - edit.add_placeholder_snippet(cap, arg) + editor.add_annotation(arg.syntax(), builder.make_placeholder_snippet(cap)); } } + + editor.add_mappings(make.finish_with_mappings()); + builder.add_file_edits(ctx.file_id(), editor); }, ) } /// This will create a turbofish generic arg list corresponding to the number of arguments -fn get_fish_head(number_of_arguments: usize) -> ast::GenericArgList { +fn get_fish_head(make: &SyntaxFactory, number_of_arguments: usize) -> ast::GenericArgList { let args = (0..number_of_arguments).map(|_| make::type_arg(make::ty_placeholder()).into()); - make::turbofish_generic_arg_list(args) + make.turbofish_generic_arg_list(args) } #[cfg(test)] diff --git a/crates/ide-assists/src/handlers/bool_to_enum.rs b/crates/ide-assists/src/handlers/bool_to_enum.rs index 605fd14052..f699899066 100644 --- a/crates/ide-assists/src/handlers/bool_to_enum.rs +++ b/crates/ide-assists/src/handlers/bool_to_enum.rs @@ -512,9 +512,11 @@ fn make_bool_enum(make_pub: bool) -> ast::Enum { let enum_def = make::enum_( if make_pub { Some(make::visibility_pub()) } else { None }, make::name("Bool"), + None, + None, make::variant_list(vec![ - make::variant(make::name("True"), None), - make::variant(make::name("False"), None), + make::variant(None, make::name("True"), None, None), + make::variant(None, make::name("False"), None, None), ]), ) .clone_for_update(); diff --git a/crates/ide-assists/src/handlers/extract_function.rs b/crates/ide-assists/src/handlers/extract_function.rs index ad0be89645..6937d33ebc 100644 --- a/crates/ide-assists/src/handlers/extract_function.rs +++ b/crates/ide-assists/src/handlers/extract_function.rs @@ -18,6 +18,7 @@ use ide_db::{ }, FxIndexSet, RootDatabase, }; +use itertools::Itertools; use syntax::{ ast::{ self, edit::IndentLevel, edit_in_place::Indent, AstNode, AstToken, HasGenericParams, @@ -114,8 +115,7 @@ pub(crate) fn extract_function(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op return; } - let params = - body.extracted_function_params(ctx, &container_info, locals_used.iter().copied()); + let params = body.extracted_function_params(ctx, &container_info, locals_used); let name = make_function_name(&semantics_scope); @@ -1067,9 +1067,11 @@ impl FunctionBody { &self, ctx: &AssistContext<'_>, container_info: &ContainerInfo, - locals: impl Iterator<Item = Local>, + locals: FxIndexSet<Local>, ) -> Vec<Param> { locals + .into_iter() + .sorted() .map(|local| (local, local.primary_source(ctx.db()))) .filter(|(_, src)| is_defined_outside_of_body(ctx, self, src)) .filter_map(|(local, src)| match src.into_ident_pat() { @@ -3167,11 +3169,11 @@ fn foo() { let mut c = C { p: P { n: 0 } }; let mut v = C { p: P { n: 0 } }; let u = C { p: P { n: 0 } }; - fun_name(&mut c, &u, &mut v); + fun_name(&mut c, &mut v, &u); let m = c.p.n + v.p.n + u.p.n; } -fn $0fun_name(c: &mut C, u: &C, v: &mut C) { +fn $0fun_name(c: &mut C, v: &mut C, u: &C) { c.p.n += u.p.n; let r = &mut v.p.n; } @@ -5602,10 +5604,10 @@ fn parent(factor: i32) { fn parent(factor: i32) { let v = &[1, 2, 3]; - fun_name(v, factor); + fun_name(factor, v); } -fn $0fun_name(v: &[i32; 3], factor: i32) { +fn $0fun_name(factor: i32, v: &[i32; 3]) { v.iter().map(|it| it * factor); } "#, @@ -5786,11 +5788,11 @@ struct Struct<T: Into<i32>>(T); impl <T: Into<i32> + Copy> Struct<T> { fn func<V: Into<i32>>(&self, v: V) -> i32 { let t = self.0; - fun_name(t, v) + fun_name(v, t) } } -fn $0fun_name<T: Into<i32> + Copy, V: Into<i32>>(t: T, v: V) -> i32 { +fn $0fun_name<T: Into<i32> + Copy, V: Into<i32>>(v: V, t: T) -> i32 { t.into() + v.into() } "#, @@ -5815,11 +5817,11 @@ struct Struct<T: Into<i32>, U: Debug>(T, U); impl <T: Into<i32> + Copy, U: Debug> Struct<T, U> { fn func<V: Into<i32>>(&self, v: V) -> i32 { let t = self.0; - fun_name(t, v) + fun_name(v, t) } } -fn $0fun_name<T: Into<i32> + Copy, V: Into<i32>>(t: T, v: V) -> i32 { +fn $0fun_name<T: Into<i32> + Copy, V: Into<i32>>(v: V, t: T) -> i32 { t.into() + v.into() } "#, @@ -5844,11 +5846,11 @@ struct Struct<T>(T) where T: Into<i32>; impl <T> Struct<T> where T: Into<i32> + Copy { fn func<V>(&self, v: V) -> i32 where V: Into<i32> { let t = self.0; - fun_name(t, v) + fun_name(v, t) } } -fn $0fun_name<T, V>(t: T, v: V) -> i32 where T: Into<i32> + Copy, V: Into<i32> { +fn $0fun_name<T, V>(v: V, t: T) -> i32 where T: Into<i32> + Copy, V: Into<i32> { t.into() + v.into() } "#, @@ -5873,11 +5875,11 @@ struct Struct<T, U>(T, U) where T: Into<i32>, U: Debug; impl <T, U> Struct<T, U> where T: Into<i32> + Copy, U: Debug { fn func<V>(&self, v: V) -> i32 where V: Into<i32> { let t = self.0; - fun_name(t, v) + fun_name(v, t) } } -fn $0fun_name<T, V>(t: T, v: V) -> i32 where T: Into<i32> + Copy, V: Into<i32> { +fn $0fun_name<T, V>(v: V, t: T) -> i32 where T: Into<i32> + Copy, V: Into<i32> { t.into() + v.into() } "#, @@ -6107,6 +6109,31 @@ fn $0fun_name() -> i32 { } #[test] + fn sort_params_in_order() { + check_assist( + extract_function, + r#" +fn existing(a: i32, b: i32, c: i32) { + let x = 32; + + let p = $0x + b + c + a$0; +} +"#, + r#" +fn existing(a: i32, b: i32, c: i32) { + let x = 32; + + let p = fun_name(a, b, c, x); +} + +fn $0fun_name(a: i32, b: i32, c: i32, x: i32) -> i32 { + x + b + c + a +} +"#, + ); + } + + #[test] fn in_left_curly_is_not_applicable() { cov_mark::check!(extract_function_in_braces_is_not_applicable); check_assist_not_applicable(extract_function, r"fn foo() { $0}$0"); diff --git a/crates/ide-assists/src/handlers/flip_binexpr.rs b/crates/ide-assists/src/handlers/flip_binexpr.rs index 8b46a23f9a..818a868fe3 100644 --- a/crates/ide-assists/src/handlers/flip_binexpr.rs +++ b/crates/ide-assists/src/handlers/flip_binexpr.rs @@ -1,4 +1,7 @@ -use syntax::ast::{self, AstNode, BinExpr}; +use syntax::{ + ast::{self, syntax_factory::SyntaxFactory, AstNode, BinExpr}, + SyntaxKind, T, +}; use crate::{AssistContext, AssistId, AssistKind, Assists}; @@ -19,22 +22,17 @@ use crate::{AssistContext, AssistId, AssistKind, Assists}; // ``` pub(crate) fn flip_binexpr(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { let expr = ctx.find_node_at_offset::<BinExpr>()?; - let rhs = expr.rhs()?.syntax().clone(); - let lhs = expr.lhs()?.syntax().clone(); - - let lhs = if let Some(bin_expr) = BinExpr::cast(lhs.clone()) { - if bin_expr.op_kind() == expr.op_kind() { - bin_expr.rhs()?.syntax().clone() - } else { - lhs - } - } else { - lhs + let lhs = expr.lhs()?; + let rhs = expr.rhs()?; + + let lhs = match &lhs { + ast::Expr::BinExpr(bin_expr) if bin_expr.op_kind() == expr.op_kind() => bin_expr.rhs()?, + _ => lhs, }; - let op_range = expr.op_token()?.text_range(); + let op_token = expr.op_token()?; // The assist should be applied only if the cursor is on the operator - let cursor_in_range = op_range.contains_range(ctx.selection_trimmed()); + let cursor_in_range = op_token.text_range().contains_range(ctx.selection_trimmed()); if !cursor_in_range { return None; } @@ -47,13 +45,17 @@ pub(crate) fn flip_binexpr(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option acc.add( AssistId("flip_binexpr", AssistKind::RefactorRewrite), "Flip binary expression", - op_range, - |edit| { - if let FlipAction::FlipAndReplaceOp(new_op) = action { - edit.replace(op_range, new_op); - } - edit.replace(lhs.text_range(), rhs.text()); - edit.replace(rhs.text_range(), lhs.text()); + op_token.text_range(), + |builder| { + let mut editor = builder.make_editor(&expr.syntax().parent().unwrap()); + let make = SyntaxFactory::new(); + if let FlipAction::FlipAndReplaceOp(binary_op) = action { + editor.replace(op_token, make.token(binary_op)) + }; + editor.replace(lhs.syntax(), rhs.syntax()); + editor.replace(rhs.syntax(), lhs.syntax()); + editor.add_mappings(make.finish_with_mappings()); + builder.add_file_edits(ctx.file_id(), editor); }, ) } @@ -62,7 +64,7 @@ enum FlipAction { // Flip the expression Flip, // Flip the expression and replace the operator with this string - FlipAndReplaceOp(&'static str), + FlipAndReplaceOp(SyntaxKind), // Do not flip the expression DontFlip, } @@ -73,10 +75,10 @@ impl From<ast::BinaryOp> for FlipAction { ast::BinaryOp::Assignment { .. } => FlipAction::DontFlip, ast::BinaryOp::CmpOp(ast::CmpOp::Ord { ordering, strict }) => { let rev_op = match (ordering, strict) { - (ast::Ordering::Less, true) => ">", - (ast::Ordering::Less, false) => ">=", - (ast::Ordering::Greater, true) => "<", - (ast::Ordering::Greater, false) => "<=", + (ast::Ordering::Less, true) => T![>], + (ast::Ordering::Less, false) => T![>=], + (ast::Ordering::Greater, true) => T![<], + (ast::Ordering::Greater, false) => T![<=], }; FlipAction::FlipAndReplaceOp(rev_op) } diff --git a/crates/ide-assists/src/handlers/flip_comma.rs b/crates/ide-assists/src/handlers/flip_comma.rs index af2c2c759e..95e035c053 100644 --- a/crates/ide-assists/src/handlers/flip_comma.rs +++ b/crates/ide-assists/src/handlers/flip_comma.rs @@ -1,7 +1,8 @@ -use ide_db::base_db::SourceDatabase; -use syntax::TextSize; use syntax::{ - algo::non_trivia_sibling, ast, AstNode, Direction, SyntaxKind, SyntaxToken, TextRange, T, + algo::non_trivia_sibling, + ast::{self, syntax_factory::SyntaxFactory}, + syntax_editor::{Element, SyntaxMapping}, + AstNode, Direction, NodeOrToken, SyntaxElement, SyntaxKind, SyntaxToken, T, }; use crate::{AssistContext, AssistId, AssistKind, Assists}; @@ -25,8 +26,6 @@ pub(crate) fn flip_comma(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<( let comma = ctx.find_token_syntax_at_offset(T![,])?; let prev = non_trivia_sibling(comma.clone().into(), Direction::Prev)?; let next = non_trivia_sibling(comma.clone().into(), Direction::Next)?; - let (mut prev_text, mut next_text) = (prev.to_string(), next.to_string()); - let (mut prev_range, mut next_range) = (prev.text_range(), next.text_range()); // Don't apply a "flip" in case of a last comma // that typically comes before punctuation @@ -40,53 +39,84 @@ pub(crate) fn flip_comma(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<( return None; } - if let Some(parent) = comma.parent().and_then(ast::TokenTree::cast) { - // An attribute. It often contains a path followed by a token tree (e.g. `align(2)`), so we have - // to be smarter. - let prev_start = - match comma.siblings_with_tokens(Direction::Prev).skip(1).find(|it| it.kind() == T![,]) - { - Some(it) => position_after_token(it.as_token().unwrap()), - None => position_after_token(&parent.left_delimiter_token()?), - }; - let prev_end = prev.text_range().end(); - let next_start = next.text_range().start(); - let next_end = - match comma.siblings_with_tokens(Direction::Next).skip(1).find(|it| it.kind() == T![,]) - { - Some(it) => position_before_token(it.as_token().unwrap()), - None => position_before_token(&parent.right_delimiter_token()?), - }; - prev_range = TextRange::new(prev_start, prev_end); - next_range = TextRange::new(next_start, next_end); - let file_text = ctx.db().file_text(ctx.file_id().file_id()); - prev_text = file_text[prev_range].to_owned(); - next_text = file_text[next_range].to_owned(); - } + let prev = match prev { + SyntaxElement::Node(node) => node.syntax_element(), + _ => prev, + }; + let next = match next { + SyntaxElement::Node(node) => node.syntax_element(), + _ => next, + }; acc.add( AssistId("flip_comma", AssistKind::RefactorRewrite), "Flip comma", comma.text_range(), - |edit| { - edit.replace(prev_range, next_text); - edit.replace(next_range, prev_text); + |builder| { + let parent = comma.parent().unwrap(); + let mut editor = builder.make_editor(&parent); + + if let Some(parent) = ast::TokenTree::cast(parent) { + // An attribute. It often contains a path followed by a + // token tree (e.g. `align(2)`), so we have to be smarter. + let (new_tree, mapping) = flip_tree(parent.clone(), comma); + editor.replace(parent.syntax(), new_tree.syntax()); + editor.add_mappings(mapping); + } else { + editor.replace(prev.clone(), next.clone()); + editor.replace(next.clone(), prev.clone()); + } + + builder.add_file_edits(ctx.file_id(), editor); }, ) } -fn position_before_token(token: &SyntaxToken) -> TextSize { - match non_trivia_sibling(token.clone().into(), Direction::Prev) { - Some(prev_token) => prev_token.text_range().end(), - None => token.text_range().start(), - } -} - -fn position_after_token(token: &SyntaxToken) -> TextSize { - match non_trivia_sibling(token.clone().into(), Direction::Next) { - Some(prev_token) => prev_token.text_range().start(), - None => token.text_range().end(), - } +fn flip_tree(tree: ast::TokenTree, comma: SyntaxToken) -> (ast::TokenTree, SyntaxMapping) { + let mut tree_iter = tree.token_trees_and_tokens(); + let before: Vec<_> = + tree_iter.by_ref().take_while(|it| it.as_token() != Some(&comma)).collect(); + let after: Vec<_> = tree_iter.collect(); + + let not_ws = |element: &NodeOrToken<_, SyntaxToken>| match element { + NodeOrToken::Token(token) => token.kind() != SyntaxKind::WHITESPACE, + NodeOrToken::Node(_) => true, + }; + + let is_comma = |element: &NodeOrToken<_, SyntaxToken>| match element { + NodeOrToken::Token(token) => token.kind() == T![,], + NodeOrToken::Node(_) => false, + }; + + let prev_start_untrimmed = match before.iter().rposition(is_comma) { + Some(pos) => pos + 1, + None => 1, + }; + let prev_end = 1 + before.iter().rposition(not_ws).unwrap(); + let prev_start = prev_start_untrimmed + + before[prev_start_untrimmed..prev_end].iter().position(not_ws).unwrap(); + + let next_start = after.iter().position(not_ws).unwrap(); + let next_end_untrimmed = match after.iter().position(is_comma) { + Some(pos) => pos, + None => after.len() - 1, + }; + let next_end = 1 + after[..next_end_untrimmed].iter().rposition(not_ws).unwrap(); + + let result = [ + &before[1..prev_start], + &after[next_start..next_end], + &before[prev_end..], + &[NodeOrToken::Token(comma)], + &after[..next_start], + &before[prev_start..prev_end], + &after[next_end..after.len() - 1], + ] + .concat(); + + let make = SyntaxFactory::new(); + let new_token_tree = make.token_tree(tree.left_delimiter_token().unwrap().kind(), result); + (new_token_tree, make.finish_with_mappings()) } #[cfg(test)] @@ -147,4 +177,9 @@ mod tests { r#"#[foo(bar, qux, baz(1 + 1), other)] struct Foo;"#, ); } + + #[test] + fn flip_comma_attribute_incomplete() { + check_assist_not_applicable(flip_comma, r#"#[repr(align(2),$0)] struct Foo;"#); + } } diff --git a/crates/ide-assists/src/handlers/flip_trait_bound.rs b/crates/ide-assists/src/handlers/flip_trait_bound.rs index 70b5efcb64..298e5bd82c 100644 --- a/crates/ide-assists/src/handlers/flip_trait_bound.rs +++ b/crates/ide-assists/src/handlers/flip_trait_bound.rs @@ -23,11 +23,11 @@ pub(crate) fn flip_trait_bound(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op let plus = ctx.find_token_syntax_at_offset(T![+])?; // Make sure we're in a `TypeBoundList` - ast::TypeBoundList::cast(plus.parent()?)?; + let parent = ast::TypeBoundList::cast(plus.parent()?)?; let (before, after) = ( - non_trivia_sibling(plus.clone().into(), Direction::Prev)?, - non_trivia_sibling(plus.clone().into(), Direction::Next)?, + non_trivia_sibling(plus.clone().into(), Direction::Prev)?.into_node()?, + non_trivia_sibling(plus.clone().into(), Direction::Next)?.into_node()?, ); let target = plus.text_range(); @@ -35,9 +35,11 @@ pub(crate) fn flip_trait_bound(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op AssistId("flip_trait_bound", AssistKind::RefactorRewrite), "Flip trait bounds", target, - |edit| { - edit.replace(before.text_range(), after.to_string()); - edit.replace(after.text_range(), before.to_string()); + |builder| { + let mut editor = builder.make_editor(parent.syntax()); + editor.replace(before.clone(), after.clone()); + editor.replace(after, before); + builder.add_file_edits(ctx.file_id(), editor); }, ) } diff --git a/crates/ide-assists/src/handlers/generate_documentation_template.rs b/crates/ide-assists/src/handlers/generate_documentation_template.rs index c5c70c9f8e..862be791d1 100644 --- a/crates/ide-assists/src/handlers/generate_documentation_template.rs +++ b/crates/ide-assists/src/handlers/generate_documentation_template.rs @@ -88,7 +88,7 @@ pub(crate) fn generate_documentation_template( // /// # Examples // /// // /// ``` -// /// use test::add; +// /// use ra_test_fixture::add; // /// // /// assert_eq!(add(a, b), ); // /// ``` @@ -596,7 +596,7 @@ pub fn noop_with_param(_a: i32) {} /// # Examples /// /// ``` -/// use test::noop_with_param; +/// use ra_test_fixture::noop_with_param; /// /// noop_with_param(_a); /// ``` @@ -641,7 +641,7 @@ pub unsafe fn noop_unsafe() {} /// # Examples /// /// ``` -/// use test::noop_unsafe; +/// use ra_test_fixture::noop_unsafe; /// /// unsafe { noop_unsafe() }; /// ``` @@ -758,7 +758,7 @@ pub fn returns_a_value$0() -> i32 { /// # Examples /// /// ``` -/// use test::returns_a_value; +/// use ra_test_fixture::returns_a_value; /// /// assert_eq!(returns_a_value(), ); /// ``` @@ -807,7 +807,7 @@ pub fn modifies_a_value$0(a: &mut i32) { /// # Examples /// /// ``` -/// use test::modifies_a_value; +/// use ra_test_fixture::modifies_a_value; /// /// let mut a = ; /// modifies_a_value(&mut a); @@ -836,7 +836,7 @@ pub fn sum3$0(a: i32, b: i32, c: i32) -> i32 { /// # Examples /// /// ``` -/// use test::sum3; +/// use ra_test_fixture::sum3; /// /// let result = sum3(a, b, c); /// assert_eq!(result, ); @@ -868,7 +868,7 @@ pub mod a { /// # Examples /// /// ``` - /// use test::a::b::noop; + /// use ra_test_fixture::a::b::noop; /// /// noop(); /// ``` @@ -898,7 +898,7 @@ impl MyStruct { /// # Examples /// /// ``` - /// use test::MyStruct; + /// use ra_test_fixture::MyStruct; /// /// MyStruct::noop(); /// ``` @@ -1169,7 +1169,7 @@ impl<T> MyGenericStruct<T> { /// # Examples /// /// ``` - /// use test::MyGenericStruct; + /// use ra_test_fixture::MyGenericStruct; /// /// let my_generic_struct = ; /// my_generic_struct.consume(); @@ -1199,7 +1199,7 @@ impl<T> MyGenericStruct<T> { /// # Examples /// /// ``` - /// use test::MyGenericStruct; + /// use ra_test_fixture::MyGenericStruct; /// /// let mut my_generic_struct = ; /// my_generic_struct.modify(new_value); diff --git a/crates/ide-assists/src/handlers/generate_enum_variant.rs b/crates/ide-assists/src/handlers/generate_enum_variant.rs index 5d58459121..bb08cb904e 100644 --- a/crates/ide-assists/src/handlers/generate_enum_variant.rs +++ b/crates/ide-assists/src/handlers/generate_enum_variant.rs @@ -1,7 +1,7 @@ use hir::{HasSource, HirDisplay, InRealFile}; use ide_db::assists::{AssistId, AssistKind}; use syntax::{ - ast::{self, make, HasArgList}, + ast::{self, syntax_factory::SyntaxFactory, HasArgList}, match_ast, AstNode, SyntaxNode, }; @@ -33,7 +33,7 @@ use crate::assist_context::{AssistContext, Assists}; // ``` pub(crate) fn generate_enum_variant(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { let path: ast::Path = ctx.find_node_at_offset()?; - let parent = path_parent(&path)?; + let parent = PathParent::new(&path)?; if ctx.sema.resolve_path(&path).is_some() { // No need to generate anything if the path resolves @@ -46,14 +46,32 @@ pub(crate) fn generate_enum_variant(acc: &mut Assists, ctx: &AssistContext<'_>) return None; } - if let Some(hir::PathResolution::Def(hir::ModuleDef::Adt(hir::Adt::Enum(e)))) = + let Some(hir::PathResolution::Def(hir::ModuleDef::Adt(hir::Adt::Enum(e)))) = ctx.sema.resolve_path(&path.qualifier()?) - { - let target = path.syntax().text_range(); - return add_variant_to_accumulator(acc, ctx, target, e, &name_ref, parent); - } + else { + return None; + }; - None + let target = path.syntax().text_range(); + let name_ref: &ast::NameRef = &name_ref; + let db = ctx.db(); + let InRealFile { file_id, value: enum_node } = e.source(db)?.original_ast_node_rooted(db)?; + + acc.add( + AssistId("generate_enum_variant", AssistKind::Generate), + "Generate variant", + target, + |builder| { + let mut editor = builder.make_editor(enum_node.syntax()); + let make = SyntaxFactory::new(); + let field_list = parent.make_field_list(ctx, &make); + let variant = make.variant(None, make.name(&name_ref.text()), field_list, None); + if let Some(it) = enum_node.variant_list() { + it.add_variant(&mut editor, &variant); + } + builder.add_file_edits(file_id, editor); + }, + ) } #[derive(Debug)] @@ -65,6 +83,20 @@ enum PathParent { } impl PathParent { + fn new(path: &ast::Path) -> Option<Self> { + let parent = path.syntax().parent()?; + + match_ast! { + match parent { + ast::PathExpr(it) => Some(PathParent::PathExpr(it)), + ast::RecordExpr(it) => Some(PathParent::RecordExpr(it)), + ast::PathPat(it) => Some(PathParent::PathPat(it)), + ast::UseTree(it) => Some(PathParent::UseTree(it)), + _ => None + } + } + } + fn syntax(&self) -> &SyntaxNode { match self { PathParent::PathExpr(it) => it.syntax(), @@ -74,97 +106,49 @@ impl PathParent { } } - fn make_field_list(&self, ctx: &AssistContext<'_>) -> Option<ast::FieldList> { + fn make_field_list( + &self, + ctx: &AssistContext<'_>, + make: &SyntaxFactory, + ) -> Option<ast::FieldList> { let scope = ctx.sema.scope(self.syntax())?; match self { PathParent::PathExpr(it) => { - if let Some(call_expr) = it.syntax().parent().and_then(ast::CallExpr::cast) { - make_tuple_field_list(call_expr, ctx, &scope) - } else { - None - } + let call_expr = ast::CallExpr::cast(it.syntax().parent()?)?; + let args = call_expr.arg_list()?.args(); + let tuple_fields = args.map(|arg| { + let ty = + expr_ty(ctx, make, arg, &scope).unwrap_or_else(|| make.ty_infer().into()); + make.tuple_field(None, ty) + }); + Some(make.tuple_field_list(tuple_fields).into()) + } + PathParent::RecordExpr(it) => { + let fields = it.record_expr_field_list()?.fields(); + let record_fields = fields.map(|field| { + let name = name_from_field(make, &field); + + let ty = field + .expr() + .and_then(|it| expr_ty(ctx, make, it, &scope)) + .unwrap_or_else(|| make.ty_infer().into()); + + make.record_field(None, name, ty) + }); + Some(make.record_field_list(record_fields).into()) } - PathParent::RecordExpr(it) => make_record_field_list(it, ctx, &scope), PathParent::UseTree(_) | PathParent::PathPat(_) => None, } } } -fn path_parent(path: &ast::Path) -> Option<PathParent> { - let parent = path.syntax().parent()?; - - match_ast! { - match parent { - ast::PathExpr(it) => Some(PathParent::PathExpr(it)), - ast::RecordExpr(it) => Some(PathParent::RecordExpr(it)), - ast::PathPat(it) => Some(PathParent::PathPat(it)), - ast::UseTree(it) => Some(PathParent::UseTree(it)), - _ => None - } - } -} - -fn add_variant_to_accumulator( - acc: &mut Assists, - ctx: &AssistContext<'_>, - target: syntax::TextRange, - adt: hir::Enum, - name_ref: &ast::NameRef, - parent: PathParent, -) -> Option<()> { - let db = ctx.db(); - let InRealFile { file_id, value: enum_node } = adt.source(db)?.original_ast_node_rooted(db)?; - - acc.add( - AssistId("generate_enum_variant", AssistKind::Generate), - "Generate variant", - target, - |builder| { - builder.edit_file(file_id.file_id()); - let node = builder.make_mut(enum_node); - let variant = make_variant(ctx, name_ref, parent); - if let Some(it) = node.variant_list() { - it.add_variant(variant.clone_for_update()) - } - }, - ) -} - -fn make_variant( - ctx: &AssistContext<'_>, - name_ref: &ast::NameRef, - parent: PathParent, -) -> ast::Variant { - let field_list = parent.make_field_list(ctx); - make::variant(make::name(&name_ref.text()), field_list) -} - -fn make_record_field_list( - record: &ast::RecordExpr, - ctx: &AssistContext<'_>, - scope: &hir::SemanticsScope<'_>, -) -> Option<ast::FieldList> { - let fields = record.record_expr_field_list()?.fields(); - let record_fields = fields.map(|field| { - let name = name_from_field(&field); - - let ty = field - .expr() - .and_then(|it| expr_ty(ctx, it, scope)) - .unwrap_or_else(make::ty_placeholder); - - make::record_field(None, name, ty) - }); - Some(make::record_field_list(record_fields).into()) -} - -fn name_from_field(field: &ast::RecordExprField) -> ast::Name { +fn name_from_field(make: &SyntaxFactory, field: &ast::RecordExprField) -> ast::Name { let text = match field.name_ref() { Some(it) => it.to_string(), None => name_from_field_shorthand(field).unwrap_or("unknown".to_owned()), }; - make::name(&text) + make.name(&text) } fn name_from_field_shorthand(field: &ast::RecordExprField) -> Option<String> { @@ -175,27 +159,15 @@ fn name_from_field_shorthand(field: &ast::RecordExprField) -> Option<String> { Some(path.as_single_name_ref()?.to_string()) } -fn make_tuple_field_list( - call_expr: ast::CallExpr, - ctx: &AssistContext<'_>, - scope: &hir::SemanticsScope<'_>, -) -> Option<ast::FieldList> { - let args = call_expr.arg_list()?.args(); - let tuple_fields = args.map(|arg| { - let ty = expr_ty(ctx, arg, scope).unwrap_or_else(make::ty_placeholder); - make::tuple_field(None, ty) - }); - Some(make::tuple_field_list(tuple_fields).into()) -} - fn expr_ty( ctx: &AssistContext<'_>, + make: &SyntaxFactory, arg: ast::Expr, scope: &hir::SemanticsScope<'_>, ) -> Option<ast::Type> { let ty = ctx.sema.type_of_expr(&arg).map(|it| it.adjusted())?; let text = ty.display_source_code(ctx.db(), scope.module().into(), false).ok()?; - Some(make::ty(&text)) + Some(make.ty(&text)) } #[cfg(test)] diff --git a/crates/ide-assists/src/handlers/introduce_named_generic.rs b/crates/ide-assists/src/handlers/introduce_named_generic.rs index bf6ac1719f..8c276415bb 100644 --- a/crates/ide-assists/src/handlers/introduce_named_generic.rs +++ b/crates/ide-assists/src/handlers/introduce_named_generic.rs @@ -1,9 +1,6 @@ use ide_db::syntax_helpers::suggest_name; use itertools::Itertools; -use syntax::{ - ast::{self, edit_in_place::GenericParamsOwnerEdit, make, AstNode, HasGenericParams, HasName}, - ted, -}; +use syntax::ast::{self, syntax_factory::SyntaxFactory, AstNode, HasGenericParams, HasName}; use crate::{AssistContext, AssistId, AssistKind, Assists}; @@ -25,42 +22,42 @@ pub(crate) fn introduce_named_generic(acc: &mut Assists, ctx: &AssistContext<'_> let type_bound_list = impl_trait_type.type_bound_list()?; + let make = SyntaxFactory::new(); let target = fn_.syntax().text_range(); acc.add( AssistId("introduce_named_generic", AssistKind::RefactorRewrite), "Replace impl trait with generic", target, - |edit| { - let impl_trait_type = edit.make_mut(impl_trait_type); - let fn_ = edit.make_mut(fn_); - let fn_generic_param_list = fn_.get_or_create_generic_param_list(); - - let existing_names = fn_generic_param_list - .generic_params() - .flat_map(|param| match param { - ast::GenericParam::TypeParam(t) => t.name().map(|name| name.to_string()), - p => Some(p.to_string()), - }) - .collect_vec(); + |builder| { + let mut editor = builder.make_editor(fn_.syntax()); + + let existing_names = match fn_.generic_param_list() { + Some(generic_param_list) => generic_param_list + .generic_params() + .flat_map(|param| match param { + ast::GenericParam::TypeParam(t) => t.name().map(|name| name.to_string()), + p => Some(p.to_string()), + }) + .collect_vec(), + None => Vec::new(), + }; let type_param_name = suggest_name::NameGenerator::new_with_names( existing_names.iter().map(|s| s.as_str()), ) .for_impl_trait_as_generic(&impl_trait_type); - let type_param = make::type_param(make::name(&type_param_name), Some(type_bound_list)) - .clone_for_update(); - let new_ty = make::ty(&type_param_name).clone_for_update(); + let type_param = make.type_param(make.name(&type_param_name), Some(type_bound_list)); + let new_ty = make.ty(&type_param_name); - ted::replace(impl_trait_type.syntax(), new_ty.syntax()); - fn_generic_param_list.add_generic_param(type_param.into()); + editor.replace(impl_trait_type.syntax(), new_ty.syntax()); + editor.add_generic_param(&fn_, type_param.clone().into()); if let Some(cap) = ctx.config.snippet_cap { - if let Some(generic_param) = - fn_.generic_param_list().and_then(|it| it.generic_params().last()) - { - edit.add_tabstop_before(cap, generic_param); - } + editor.add_annotation(type_param.syntax(), builder.make_tabstop_before(cap)); } + + editor.add_mappings(make.finish_with_mappings()); + builder.add_file_edits(ctx.file_id(), editor); }, ) } diff --git a/crates/ide-assists/src/handlers/reorder_fields.rs b/crates/ide-assists/src/handlers/reorder_fields.rs index 4d3e85ab1b..972303c2a0 100644 --- a/crates/ide-assists/src/handlers/reorder_fields.rs +++ b/crates/ide-assists/src/handlers/reorder_fields.rs @@ -92,10 +92,9 @@ fn replace<T: AstNode + PartialEq>( fields: impl Iterator<Item = T>, sorted_fields: impl IntoIterator<Item = T>, ) { - fields.zip(sorted_fields).for_each(|(field, sorted_field)| { - // FIXME: remove `clone_for_update` when `SyntaxEditor` handles it for us - editor.replace(field.syntax(), sorted_field.syntax().clone_for_update()) - }); + fields + .zip(sorted_fields) + .for_each(|(field, sorted_field)| editor.replace(field.syntax(), sorted_field.syntax())); } fn compute_fields_ranks( diff --git a/crates/ide-assists/src/handlers/reorder_impl_items.rs b/crates/ide-assists/src/handlers/reorder_impl_items.rs index d7fa882612..eb1d538f87 100644 --- a/crates/ide-assists/src/handlers/reorder_impl_items.rs +++ b/crates/ide-assists/src/handlers/reorder_impl_items.rs @@ -101,10 +101,10 @@ pub(crate) fn reorder_impl_items(acc: &mut Assists, ctx: &AssistContext<'_>) -> |builder| { let mut editor = builder.make_editor(&parent_node); - assoc_items.into_iter().zip(sorted).for_each(|(old, new)| { - // FIXME: remove `clone_for_update` when `SyntaxEditor` handles it for us - editor.replace(old.syntax(), new.clone_for_update().syntax()) - }); + assoc_items + .into_iter() + .zip(sorted) + .for_each(|(old, new)| editor.replace(old.syntax(), new.syntax())); builder.add_file_edits(ctx.file_id(), editor); }, diff --git a/crates/ide-assists/src/handlers/sort_items.rs b/crates/ide-assists/src/handlers/sort_items.rs index 64e30b1834..54e16d4d80 100644 --- a/crates/ide-assists/src/handlers/sort_items.rs +++ b/crates/ide-assists/src/handlers/sort_items.rs @@ -4,7 +4,7 @@ use itertools::Itertools; use syntax::{ ast::{self, HasName}, - ted, AstNode, TextRange, + AstNode, SyntaxNode, }; use crate::{utils::get_methods, AssistContext, AssistId, AssistKind, Assists}; @@ -114,7 +114,7 @@ trait AddRewrite { label: &str, old: Vec<T>, new: Vec<T>, - target: TextRange, + target: &SyntaxNode, ) -> Option<()>; } @@ -124,15 +124,22 @@ impl AddRewrite for Assists { label: &str, old: Vec<T>, new: Vec<T>, - target: TextRange, + target: &SyntaxNode, ) -> Option<()> { - self.add(AssistId("sort_items", AssistKind::RefactorRewrite), label, target, |builder| { - let mutable: Vec<T> = old.into_iter().map(|it| builder.make_mut(it)).collect(); - mutable - .into_iter() - .zip(new) - .for_each(|(old, new)| ted::replace(old.syntax(), new.clone_for_update().syntax())); - }) + self.add( + AssistId("sort_items", AssistKind::RefactorRewrite), + label, + target.text_range(), + |builder| { + let mut editor = builder.make_editor(target); + + old.into_iter() + .zip(new) + .for_each(|(old, new)| editor.replace(old.syntax(), new.syntax())); + + builder.add_file_edits(builder.file_id, editor) + }, + ) } } @@ -167,7 +174,7 @@ fn add_sort_methods_assist( return None; } - acc.add_rewrite("Sort methods alphabetically", methods, sorted, item_list.syntax().text_range()) + acc.add_rewrite("Sort methods alphabetically", methods, sorted, item_list.syntax()) } fn add_sort_fields_assist( @@ -182,12 +189,7 @@ fn add_sort_fields_assist( return None; } - acc.add_rewrite( - "Sort fields alphabetically", - fields, - sorted, - record_field_list.syntax().text_range(), - ) + acc.add_rewrite("Sort fields alphabetically", fields, sorted, record_field_list.syntax()) } fn add_sort_variants_assist(acc: &mut Assists, variant_list: ast::VariantList) -> Option<()> { @@ -199,12 +201,7 @@ fn add_sort_variants_assist(acc: &mut Assists, variant_list: ast::VariantList) - return None; } - acc.add_rewrite( - "Sort variants alphabetically", - variants, - sorted, - variant_list.syntax().text_range(), - ) + acc.add_rewrite("Sort variants alphabetically", variants, sorted, variant_list.syntax()) } fn sort_by_name<T: HasName + Clone>(initial: &[T]) -> Vec<T> { diff --git a/crates/ide-assists/src/tests/generated.rs b/crates/ide-assists/src/tests/generated.rs index eef4da55e9..69ea200db1 100644 --- a/crates/ide-assists/src/tests/generated.rs +++ b/crates/ide-assists/src/tests/generated.rs @@ -1392,7 +1392,7 @@ pub fn add(a: i32, b: i32) -> i32 { a + b } /// # Examples /// /// ``` -/// use test::add; +/// use ra_test_fixture::add; /// /// assert_eq!(add(a, b), ); /// ``` diff --git a/crates/ide-completion/src/completions/attribute.rs b/crates/ide-completion/src/completions/attribute.rs index d0b489c4e8..cf5427bae3 100644 --- a/crates/ide-completion/src/completions/attribute.rs +++ b/crates/ide-completion/src/completions/attribute.rs @@ -86,10 +86,21 @@ pub(crate) fn complete_attribute_path( acc: &mut Completions, ctx: &CompletionContext<'_>, path_ctx @ PathCompletionCtx { qualified, .. }: &PathCompletionCtx, - &AttrCtx { kind, annotated_item_kind }: &AttrCtx, + &AttrCtx { kind, annotated_item_kind, ref derive_helpers }: &AttrCtx, ) { let is_inner = kind == AttrKind::Inner; + for (derive_helper, derive_name) in derive_helpers { + let mut item = CompletionItem::new( + SymbolKind::Attribute, + ctx.source_range(), + derive_helper.as_str(), + ctx.edition, + ); + item.detail(format!("derive helper of `{derive_name}`")); + item.add_to(acc, ctx.db); + } + match qualified { Qualified::With { resolution: Some(hir::PathResolution::Def(hir::ModuleDef::Module(module))), diff --git a/crates/ide-completion/src/completions/extern_abi.rs b/crates/ide-completion/src/completions/extern_abi.rs index b0e417e6b3..847fa4cf88 100644 --- a/crates/ide-completion/src/completions/extern_abi.rs +++ b/crates/ide-completion/src/completions/extern_abi.rs @@ -38,7 +38,6 @@ const SUPPORTED_CALLING_CONVENTIONS: &[&str] = &[ "system-unwind", "rust-intrinsic", "rust-call", - "platform-intrinsic", "unadjusted", ]; diff --git a/crates/ide-completion/src/completions/lifetime.rs b/crates/ide-completion/src/completions/lifetime.rs index 9efc52428e..0692446381 100644 --- a/crates/ide-completion/src/completions/lifetime.rs +++ b/crates/ide-completion/src/completions/lifetime.rs @@ -8,7 +8,6 @@ //! show up for normal completions, or they won't show completions other than lifetimes depending //! on the fixture input. use hir::{sym, Name, ScopeDef}; -use syntax::{ast, ToSmolStr, TokenText}; use crate::{ completions::Completions, @@ -21,33 +20,24 @@ pub(crate) fn complete_lifetime( ctx: &CompletionContext<'_>, lifetime_ctx: &LifetimeContext, ) { - let (lp, lifetime) = match lifetime_ctx { - LifetimeContext { kind: LifetimeKind::Lifetime, lifetime } => (None, lifetime), - LifetimeContext { - kind: LifetimeKind::LifetimeParam { is_decl: false, param }, - lifetime, - } => (Some(param), lifetime), - _ => return, - }; - let param_lifetime = match (lifetime, lp.and_then(|lp| lp.lifetime())) { - (Some(lt), Some(lp)) if lp == lt.clone() => return, - (Some(_), Some(lp)) => Some(lp), - _ => None, + let &LifetimeContext { kind: LifetimeKind::Lifetime { in_lifetime_param_bound, def }, .. } = + lifetime_ctx + else { + return; }; - let param_lifetime = param_lifetime.as_ref().map(ast::Lifetime::text); - let param_lifetime = param_lifetime.as_ref().map(TokenText::as_str); ctx.process_all_names_raw(&mut |name, res| { - if matches!( - res, - ScopeDef::GenericParam(hir::GenericParam::LifetimeParam(_)) - if param_lifetime != Some(&*name.display_no_db(ctx.edition).to_smolstr()) - ) { + if matches!(res, ScopeDef::GenericParam(hir::GenericParam::LifetimeParam(_))) { acc.add_lifetime(ctx, name); } }); - if param_lifetime.is_none() { - acc.add_lifetime(ctx, Name::new_symbol_root(sym::tick_static.clone())); + acc.add_lifetime(ctx, Name::new_symbol_root(sym::tick_static.clone())); + if !in_lifetime_param_bound + && def.is_some_and(|def| { + !matches!(def, hir::GenericDef::Function(_) | hir::GenericDef::Impl(_)) + }) + { + acc.add_lifetime(ctx, Name::new_symbol_root(sym::tick_underscore.clone())); } } @@ -222,6 +212,8 @@ fn foo<'footime, 'lifetime: 'a$0>() {} "#, expect![[r#" lt 'footime + lt 'lifetime + lt 'static "#]], ); } diff --git a/crates/ide-completion/src/context.rs b/crates/ide-completion/src/context.rs index efbee39a2d..3a66170633 100644 --- a/crates/ide-completion/src/context.rs +++ b/crates/ide-completion/src/context.rs @@ -7,8 +7,8 @@ mod tests; use std::{iter, ops::ControlFlow}; use hir::{ - HasAttrs, Local, ModuleSource, Name, PathResolution, ScopeDef, Semantics, SemanticsScope, Type, - TypeInfo, + HasAttrs, Local, ModuleSource, Name, PathResolution, ScopeDef, Semantics, SemanticsScope, + Symbol, Type, TypeInfo, }; use ide_db::{ base_db::SourceDatabase, famous_defs::FamousDefs, helpers::is_editable_crate, FilePosition, @@ -133,6 +133,7 @@ pub(crate) type ExistingDerives = FxHashSet<hir::Macro>; pub(crate) struct AttrCtx { pub(crate) kind: AttrKind, pub(crate) annotated_item_kind: Option<SyntaxKind>, + pub(crate) derive_helpers: Vec<(Symbol, Symbol)>, } #[derive(Debug, PartialEq, Eq)] @@ -289,15 +290,14 @@ pub(crate) struct ParamContext { /// The state of the lifetime we are completing. #[derive(Debug)] pub(crate) struct LifetimeContext { - pub(crate) lifetime: Option<ast::Lifetime>, pub(crate) kind: LifetimeKind, } /// The kind of lifetime we are completing. #[derive(Debug)] pub(crate) enum LifetimeKind { - LifetimeParam { is_decl: bool, param: ast::LifetimeParam }, - Lifetime, + LifetimeParam, + Lifetime { in_lifetime_param_bound: bool, def: Option<hir::GenericDef> }, LabelRef, LabelDef, } diff --git a/crates/ide-completion/src/context/analysis.rs b/crates/ide-completion/src/context/analysis.rs index 468ad81ad2..4a678963b9 100644 --- a/crates/ide-completion/src/context/analysis.rs +++ b/crates/ide-completion/src/context/analysis.rs @@ -562,7 +562,7 @@ fn expected_type_and_name( } fn classify_lifetime( - _sema: &Semantics<'_, RootDatabase>, + sema: &Semantics<'_, RootDatabase>, original_file: &SyntaxNode, lifetime: ast::Lifetime, ) -> Option<LifetimeContext> { @@ -571,21 +571,22 @@ fn classify_lifetime( return None; } + let lifetime = + find_node_at_offset::<ast::Lifetime>(original_file, lifetime.syntax().text_range().start()); let kind = match_ast! { match parent { - ast::LifetimeParam(param) => LifetimeKind::LifetimeParam { - is_decl: param.lifetime().as_ref() == Some(&lifetime), - param - }, + ast::LifetimeParam(_) => LifetimeKind::LifetimeParam, ast::BreakExpr(_) => LifetimeKind::LabelRef, ast::ContinueExpr(_) => LifetimeKind::LabelRef, ast::Label(_) => LifetimeKind::LabelDef, - _ => LifetimeKind::Lifetime, + _ => { + let def = lifetime.as_ref().and_then(|lt| sema.scope(lt.syntax())?.generic_def()); + LifetimeKind::Lifetime { in_lifetime_param_bound: ast::TypeBound::can_cast(parent.kind()), def } + }, } }; - let lifetime = find_node_at_offset(original_file, lifetime.syntax().text_range().start()); - Some(LifetimeContext { lifetime, kind }) + Some(LifetimeContext { kind }) } fn classify_name( @@ -1129,7 +1130,22 @@ fn classify_name_ref( let is_trailing_outer_attr = kind != AttrKind::Inner && non_trivia_sibling(attr.syntax().clone().into(), syntax::Direction::Next).is_none(); let annotated_item_kind = if is_trailing_outer_attr { None } else { Some(attached.kind()) }; - Some(PathKind::Attr { attr_ctx: AttrCtx { kind, annotated_item_kind } }) + let derive_helpers = annotated_item_kind + .filter(|kind| { + matches!( + kind, + SyntaxKind::STRUCT + | SyntaxKind::ENUM + | SyntaxKind::UNION + | SyntaxKind::VARIANT + | SyntaxKind::TUPLE_FIELD + | SyntaxKind::RECORD_FIELD + ) + }) + .and_then(|_| nameref.as_ref()?.syntax().ancestors().find_map(ast::Adt::cast)) + .and_then(|adt| sema.derive_helpers_in_scope(&adt)) + .unwrap_or_default(); + Some(PathKind::Attr { attr_ctx: AttrCtx { kind, annotated_item_kind, derive_helpers } }) }; // Infer the path kind diff --git a/crates/ide-completion/src/item.rs b/crates/ide-completion/src/item.rs index 52f6bedaaa..8878fbbea3 100644 --- a/crates/ide-completion/src/item.rs +++ b/crates/ide-completion/src/item.rs @@ -346,8 +346,7 @@ pub enum CompletionItemKind { impl_from!(SymbolKind for CompletionItemKind); impl CompletionItemKind { - #[cfg(test)] - pub(crate) fn tag(self) -> &'static str { + pub fn tag(self) -> &'static str { match self { CompletionItemKind::SymbolKind(kind) => match kind { SymbolKind::Attribute => "at", diff --git a/crates/ide-completion/src/lib.rs b/crates/ide-completion/src/lib.rs index dfee01b187..14f42b4005 100644 --- a/crates/ide-completion/src/lib.rs +++ b/crates/ide-completion/src/lib.rs @@ -19,7 +19,7 @@ use ide_db::{ }, items_locator, syntax_helpers::tree_diff::diff, - FilePosition, RootDatabase, + FilePosition, FxHashSet, RootDatabase, }; use crate::{ @@ -34,6 +34,7 @@ pub use crate::{ config::{CallableSnippets, CompletionConfig}, item::{ CompletionItem, CompletionItemKind, CompletionRelevance, CompletionRelevancePostfixMatch, + CompletionRelevanceReturnType, CompletionRelevanceTypeMatch, }, snippet::{Snippet, SnippetScope}, }; @@ -50,6 +51,18 @@ pub struct CompletionFieldsToResolve { } impl CompletionFieldsToResolve { + pub fn from_client_capabilities(client_capability_fields: &FxHashSet<&str>) -> Self { + Self { + resolve_label_details: client_capability_fields.contains("labelDetails"), + resolve_tags: client_capability_fields.contains("tags"), + resolve_detail: client_capability_fields.contains("detail"), + resolve_documentation: client_capability_fields.contains("documentation"), + resolve_filter_text: client_capability_fields.contains("filterText"), + resolve_text_edit: client_capability_fields.contains("textEdit"), + resolve_command: client_capability_fields.contains("command"), + } + } + pub const fn empty() -> Self { Self { resolve_label_details: false, diff --git a/crates/ide-completion/src/tests/attribute.rs b/crates/ide-completion/src/tests/attribute.rs index 45679355b4..1443ebc6c0 100644 --- a/crates/ide-completion/src/tests/attribute.rs +++ b/crates/ide-completion/src/tests/attribute.rs @@ -9,6 +9,70 @@ fn check(ra_fixture: &str, expect: Expect) { } #[test] +fn derive_helpers() { + check( + r#" +//- /mac.rs crate:mac +#![crate_type = "proc-macro"] + +#[proc_macro_derive(MyDerive, attributes(my_cool_helper_attribute))] +pub fn my_derive() {} + +//- /lib.rs crate:lib deps:mac +#[rustc_builtin_macro] +pub macro derive($item:item) {} + +#[derive(mac::MyDerive)] +pub struct Foo(#[m$0] i32); +"#, + expect![[r#" + at allow(…) + at automatically_derived + at cfg(…) + at cfg_attr(…) + at cold + at deny(…) + at deprecated + at derive macro derive + at derive(…) + at doc = "…" + at doc(alias = "…") + at doc(hidden) + at expect(…) + at export_name = "…" + at forbid(…) + at global_allocator + at ignore = "…" + at inline + at link + at link_name = "…" + at link_section = "…" + at macro_export + at macro_use + at must_use + at my_cool_helper_attribute derive helper of `MyDerive` + at no_mangle + at non_exhaustive + at panic_handler + at path = "…" + at proc_macro + at proc_macro_attribute + at proc_macro_derive(…) + at repr(…) + at should_panic + at target_feature(enable = "…") + at test + at track_caller + at used + at warn(…) + md mac + kw crate:: + kw self:: + "#]], + ) +} + +#[test] fn proc_macros() { check( r#" diff --git a/crates/ide-db/src/defs.rs b/crates/ide-db/src/defs.rs index fdac4dd2ef..932ca37302 100644 --- a/crates/ide-db/src/defs.rs +++ b/crates/ide-db/src/defs.rs @@ -733,6 +733,12 @@ impl NameRefClass { } None }, + ast::UseBoundGenericArgs(_) => { + sema.resolve_use_type_arg(name_ref) + .map(GenericParam::TypeParam) + .map(Definition::GenericParam) + .map(NameRefClass::Definition) + }, ast::ExternCrate(extern_crate_ast) => { let extern_crate = sema.to_def(&extern_crate_ast)?; let krate = extern_crate.resolved_crate(sema.db)?; @@ -764,6 +770,7 @@ impl NameRefClass { sema.resolve_label(lifetime).map(Definition::Label).map(NameRefClass::Definition) } SyntaxKind::LIFETIME_ARG + | SyntaxKind::USE_BOUND_GENERIC_ARGS | SyntaxKind::SELF_PARAM | SyntaxKind::TYPE_BOUND | SyntaxKind::WHERE_PRED @@ -772,16 +779,6 @@ impl NameRefClass { .map(GenericParam::LifetimeParam) .map(Definition::GenericParam) .map(NameRefClass::Definition), - // lifetime bounds, as in the 'b in 'a: 'b aren't wrapped in TypeBound nodes so we gotta check - // if our lifetime is in a LifetimeParam without being the constrained lifetime - _ if ast::LifetimeParam::cast(parent).and_then(|param| param.lifetime()).as_ref() - != Some(lifetime) => - { - sema.resolve_lifetime_param(lifetime) - .map(GenericParam::LifetimeParam) - .map(Definition::GenericParam) - .map(NameRefClass::Definition) - } _ => None, } } diff --git a/crates/ide-db/src/path_transform.rs b/crates/ide-db/src/path_transform.rs index 49b3ca290f..a508f2fedd 100644 --- a/crates/ide-db/src/path_transform.rs +++ b/crates/ide-db/src/path_transform.rs @@ -286,7 +286,8 @@ impl Ctx<'_> { return None; } if path.segment().map_or(false, |s| { - s.param_list().is_some() || (s.self_token().is_some() && path.parent_path().is_none()) + s.parenthesized_arg_list().is_some() + || (s.self_token().is_some() && path.parent_path().is_none()) }) { // don't try to qualify `Fn(Foo) -> Bar` paths, they are in prelude anyway // don't try to qualify sole `self` either, they are usually locals, but are returned as modules due to namespace clashing diff --git a/crates/ide-db/src/syntax_helpers/suggest_name.rs b/crates/ide-db/src/syntax_helpers/suggest_name.rs index 2679cbef61..b3ecc26cb2 100644 --- a/crates/ide-db/src/syntax_helpers/suggest_name.rs +++ b/crates/ide-db/src/syntax_helpers/suggest_name.rs @@ -29,7 +29,7 @@ const USELESS_NAME_PREFIXES: &[&str] = &["from_", "with_", "into_"]; /// # Examples /// `Option<Name>` -> `Name` /// `Result<User, Error>` -> `User` -const WRAPPER_TYPES: &[&str] = &["Box", "Option", "Result"]; +const WRAPPER_TYPES: &[&str] = &["Box", "Arc", "Rc", "Option", "Result"]; /// Prefixes to strip from methods names /// @@ -859,6 +859,32 @@ fn foo() { $0(bar())$0; } } #[test] + fn arc_value() { + check( + r#" +struct Arc<T>(*const T); +struct Seed; +fn bar() -> Arc<Seed> {} +fn foo() { $0(bar())$0; } +"#, + "seed", + ); + } + + #[test] + fn rc_value() { + check( + r#" +struct Rc<T>(*const T); +struct Seed; +fn bar() -> Rc<Seed> {} +fn foo() { $0(bar())$0; } +"#, + "seed", + ); + } + + #[test] fn ref_call() { check( r#" diff --git a/crates/ide-diagnostics/src/handlers/expected_function.rs b/crates/ide-diagnostics/src/handlers/expected_function.rs index 02299197b1..e3a1e12e02 100644 --- a/crates/ide-diagnostics/src/handlers/expected_function.rs +++ b/crates/ide-diagnostics/src/handlers/expected_function.rs @@ -37,4 +37,25 @@ fn foo() { "#, ); } + + #[test] + fn no_error_for_async_fn_traits() { + check_diagnostics( + r#" +//- minicore: async_fn +async fn f(it: impl AsyncFn(u32) -> i32) { + let fut = it(0); + let _: i32 = fut.await; +} +async fn g(mut it: impl AsyncFnMut(u32) -> i32) { + let fut = it(0); + let _: i32 = fut.await; +} +async fn h(it: impl AsyncFnOnce(u32) -> i32) { + let fut = it(0); + let _: i32 = fut.await; +} + "#, + ); + } } diff --git a/crates/ide-diagnostics/src/handlers/generic_args_prohibited.rs b/crates/ide-diagnostics/src/handlers/generic_args_prohibited.rs new file mode 100644 index 0000000000..a319a0bcf6 --- /dev/null +++ b/crates/ide-diagnostics/src/handlers/generic_args_prohibited.rs @@ -0,0 +1,442 @@ +use either::Either; +use hir::GenericArgsProhibitedReason; +use ide_db::assists::Assist; +use ide_db::source_change::SourceChange; +use ide_db::text_edit::TextEdit; +use syntax::{ast, AstNode, TextRange}; + +use crate::{fix, Diagnostic, DiagnosticCode, DiagnosticsContext}; + +// Diagnostic: generic-args-prohibited +// +// This diagnostic is shown when generic arguments are provided for a type that does not accept +// generic arguments. +pub(crate) fn generic_args_prohibited( + ctx: &DiagnosticsContext<'_>, + d: &hir::GenericArgsProhibited, +) -> Diagnostic { + Diagnostic::new_with_syntax_node_ptr( + ctx, + DiagnosticCode::RustcHardError("E0109"), + describe_reason(d.reason), + d.args.map(Into::into), + ) + .with_fixes(fixes(ctx, d)) +} + +fn describe_reason(reason: GenericArgsProhibitedReason) -> String { + let kind = match reason { + GenericArgsProhibitedReason::Module => "modules", + GenericArgsProhibitedReason::TyParam => "type parameters", + GenericArgsProhibitedReason::SelfTy => "`Self`", + GenericArgsProhibitedReason::PrimitiveTy => "builtin types", + GenericArgsProhibitedReason::EnumVariant => { + return "you can specify generic arguments on either the enum or the variant, but not both" + .to_owned(); + } + }; + format!("generic arguments are not allowed on {kind}") +} + +fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::GenericArgsProhibited) -> Option<Vec<Assist>> { + let file_id = d.args.file_id.file_id()?; + let syntax = d.args.to_node(ctx.sema.db); + let range = match &syntax { + Either::Left(_) => syntax.syntax().text_range(), + Either::Right(param_list) => { + let path_segment = ast::PathSegment::cast(param_list.syntax().parent()?)?; + let start = if let Some(coloncolon) = path_segment.coloncolon_token() { + coloncolon.text_range().start() + } else { + param_list.syntax().text_range().start() + }; + let end = if let Some(ret_type) = path_segment.ret_type() { + ret_type.syntax().text_range().end() + } else { + param_list.syntax().text_range().end() + }; + TextRange::new(start, end) + } + }; + Some(vec![fix( + "remove_generic_args", + "Remove these generics", + SourceChange::from_text_edit(file_id, TextEdit::delete(range)), + syntax.syntax().text_range(), + )]) +} + +#[cfg(test)] +mod tests { + // This diagnostic was the first to be emitted in ty lowering, so the tests here also test + // diagnostics in ty lowering in general (which is why there are so many of them). + + use crate::tests::{check_diagnostics, check_fix}; + + #[test] + fn primitives() { + check_diagnostics( + r#" +//- /core.rs crate:core library +#![rustc_coherence_is_core] +impl str { + pub fn trim() {} +} + +//- /lib.rs crate:foo deps:core +fn bar<T>() {} + +fn foo() { + let _: (bool<()>, ()); + // ^^^^ 💡 error: generic arguments are not allowed on builtin types + let _ = <str<'_>>::trim; + // ^^^^ 💡 error: generic arguments are not allowed on builtin types + bar::<u32<{ const { 1 + 1 } }>>(); + // ^^^^^^^^^^^^^^^^^^^^^ 💡 error: generic arguments are not allowed on builtin types +} + "#, + ); + } + + #[test] + fn modules() { + check_diagnostics( + r#" +pub mod foo { + pub mod bar { + pub struct Baz; + + impl Baz { + pub fn qux() {} + } + } +} + +fn foo() { + let _: foo::<'_>::bar::Baz; + // ^^^^^^ 💡 error: generic arguments are not allowed on modules + let _ = <foo::bar<()>::Baz>::qux; + // ^^^^ 💡 error: generic arguments are not allowed on modules +} + "#, + ); + } + + #[test] + fn type_parameters() { + check_diagnostics( + r#" +fn foo<T, U>() { + let _: T<'a>; + // ^^^^ 💡 error: generic arguments are not allowed on type parameters + let _: U::<{ 1 + 2 }>; + // ^^^^^^^^^^^^^ 💡 error: generic arguments are not allowed on type parameters +} + "#, + ); + } + + #[test] + fn fn_like_generic_args() { + check_diagnostics( + r#" +fn foo() { + let _: bool(bool, i32) -> (); + // ^^^^^^^^^^^ 💡 error: generic arguments are not allowed on builtin types +} + "#, + ); + } + + #[test] + fn fn_signature() { + check_diagnostics( + r#" +fn foo( + _a: bool<'_>, + // ^^^^ 💡 error: generic arguments are not allowed on builtin types + _b: i32::<i64>, + // ^^^^^^^ 💡 error: generic arguments are not allowed on builtin types + _c: &(&str<1>) + // ^^^ 💡 error: generic arguments are not allowed on builtin types +) -> ((), i32<bool>) { + // ^^^^^^ 💡 error: generic arguments are not allowed on builtin types + ((), 0) +} + "#, + ); + } + + #[test] + fn const_static_type() { + check_diagnostics( + r#" +const A: i32<bool> = 0; + // ^^^^^^ 💡 error: generic arguments are not allowed on builtin types +static A: i32::<{ 1 + 3 }> = 0; + // ^^^^^^^^^^^^^ 💡 error: generic arguments are not allowed on builtin types + "#, + ); + } + + #[test] + fn fix() { + check_fix( + r#" +fn foo() { + let _: bool<'_, (), { 1 + 1 }>$0; +}"#, + r#" +fn foo() { + let _: bool; +}"#, + ); + check_fix( + r#" +fn foo() { + let _: bool::$0<'_, (), { 1 + 1 }>; +}"#, + r#" +fn foo() { + let _: bool; +}"#, + ); + check_fix( + r#" +fn foo() { + let _: bool(i$032); +}"#, + r#" +fn foo() { + let _: bool; +}"#, + ); + check_fix( + r#" +fn foo() { + let _: bool$0(i32) -> i64; +}"#, + r#" +fn foo() { + let _: bool; +}"#, + ); + check_fix( + r#" +fn foo() { + let _: bool::(i$032) -> i64; +}"#, + r#" +fn foo() { + let _: bool; +}"#, + ); + check_fix( + r#" +fn foo() { + let _: bool::(i32)$0; +}"#, + r#" +fn foo() { + let _: bool; +}"#, + ); + } + + #[test] + fn in_fields() { + check_diagnostics( + r#" +struct A(bool<i32>); + // ^^^^^ 💡 error: generic arguments are not allowed on builtin types +struct B { v: bool<(), 1> } + // ^^^^^^^ 💡 error: generic arguments are not allowed on builtin types +union C { + a: bool<i32>, + // ^^^^^ 💡 error: generic arguments are not allowed on builtin types + b: i32<bool>, + // ^^^^^^ 💡 error: generic arguments are not allowed on builtin types + } +enum D { + A(bool<i32>), + // ^^^^^ 💡 error: generic arguments are not allowed on builtin types + B { v: i32<bool> }, + // ^^^^^^ 💡 error: generic arguments are not allowed on builtin types +} + "#, + ); + } + + #[test] + fn in_generics() { + check_diagnostics( + r#" +mod foo { + pub trait Trait {} +} + +struct A<A: foo::<()>::Trait>(A) + // ^^^^^^ 💡 error: generic arguments are not allowed on modules + where bool<i32>: foo::Trait; + // ^^^^^ 💡 error: generic arguments are not allowed on builtin types +union B<A: foo::<()>::Trait> + // ^^^^^^ 💡 error: generic arguments are not allowed on modules + where bool<i32>: foo::Trait + // ^^^^^ 💡 error: generic arguments are not allowed on builtin types +{ a: A } +enum C<A: foo::<()>::Trait> + // ^^^^^^ 💡 error: generic arguments are not allowed on modules + where bool<i32>: foo::Trait + // ^^^^^ 💡 error: generic arguments are not allowed on builtin types +{} + +fn f<A: foo::<()>::Trait>() + // ^^^^^^ 💡 error: generic arguments are not allowed on modules + where bool<i32>: foo::Trait + // ^^^^^ 💡 error: generic arguments are not allowed on builtin types +{} + +type D<A: foo::<()>::Trait> = A + // ^^^^^^ 💡 error: generic arguments are not allowed on modules + where bool<i32>: foo::Trait; + // ^^^^^ 💡 error: generic arguments are not allowed on builtin types + +trait E<A: foo::<()>::Trait> + // ^^^^^^ 💡 error: generic arguments are not allowed on modules + where bool<i32>: foo::Trait + // ^^^^^ 💡 error: generic arguments are not allowed on builtin types +{ + fn f<B: foo::<()>::Trait>() + // ^^^^^^ 💡 error: generic arguments are not allowed on modules + where bool<i32>: foo::Trait + // ^^^^^ 💡 error: generic arguments are not allowed on builtin types + {} + + type D<B: foo::<()>::Trait> = A + // ^^^^^^ 💡 error: generic arguments are not allowed on modules + where bool<i32>: foo::Trait; + // ^^^^^ 💡 error: generic arguments are not allowed on builtin types +} + +impl<A: foo::<()>::Trait> E for () + // ^^^^^^ 💡 error: generic arguments are not allowed on modules + where bool<i32>: foo::Trait + // ^^^^^ 💡 error: generic arguments are not allowed on builtin types +{ + fn f<B: foo::<()>::Trait>() + // ^^^^^^ 💡 error: generic arguments are not allowed on modules + where bool<i32>: foo::Trait + // ^^^^^ 💡 error: generic arguments are not allowed on builtin types + {} + + type D<B: foo::<()>::Trait> = A + // ^^^^^^ 💡 error: generic arguments are not allowed on modules + where bool<i32>: foo::Trait; + // ^^^^^ 💡 error: generic arguments are not allowed on builtin types +} + "#, + ); + } + + #[test] + fn assoc_items() { + check_diagnostics( + r#" +struct Foo; + +trait Trait { + fn f() -> bool<i32> { true } + // ^^^^^ 💡 error: generic arguments are not allowed on builtin types + type T = i32<bool>; + // ^^^^^^ 💡 error: generic arguments are not allowed on builtin types +} + +impl Trait for Foo { + fn f() -> bool<i32> { true } + // ^^^^^ 💡 error: generic arguments are not allowed on builtin types + type T = i32<bool>; + // ^^^^^^ 💡 error: generic arguments are not allowed on builtin types +} + +impl Foo { + fn f() -> bool<i32> { true } + // ^^^^^ 💡 error: generic arguments are not allowed on builtin types + type T = i32<bool>; + // ^^^^^^ 💡 error: generic arguments are not allowed on builtin types +} + "#, + ); + } + + #[test] + fn const_param_ty() { + check_diagnostics( + r#" +fn foo< + const A: bool<i32>, + // ^^^^^ 💡 error: generic arguments are not allowed on builtin types + B, + C, + const D: bool<i32>, + // ^^^^^ 💡 error: generic arguments are not allowed on builtin types + const E: bool<i32>, + // ^^^^^ 💡 error: generic arguments are not allowed on builtin types +>() {} + "#, + ); + } + + #[test] + fn generic_defaults() { + check_diagnostics( + r#" +struct Foo<A = bool<i32>>(A); + // ^^^^^ 💡 error: generic arguments are not allowed on builtin types + "#, + ); + } + + #[test] + fn impl_self_ty() { + check_diagnostics( + r#" +struct Foo<A>(A); +trait Trait {} +impl Foo<bool<i32>> {} + // ^^^^^ 💡 error: generic arguments are not allowed on builtin types +impl Trait for Foo<bool<i32>> {} + // ^^^^^ 💡 error: generic arguments are not allowed on builtin types + "#, + ); + } + + #[test] + fn impl_trait() { + check_diagnostics( + r#" +mod foo { + pub trait Trait {} +} +impl foo::<()>::Trait for () {} + // ^^^^^^ 💡 error: generic arguments are not allowed on modules + "#, + ); + } + + #[test] + fn type_alias() { + check_diagnostics( + r#" +pub trait Trait { + type Assoc; +} +type T = bool<i32>; + // ^^^^^ 💡 error: generic arguments are not allowed on builtin types +impl Trait for () { + type Assoc = i32<bool>; + // ^^^^^^ 💡 error: generic arguments are not allowed on builtin types +} + "#, + ); + } +} diff --git a/crates/ide-diagnostics/src/handlers/missing_match_arms.rs b/crates/ide-diagnostics/src/handlers/missing_match_arms.rs index f39738f2fb..08e6e7dced 100644 --- a/crates/ide-diagnostics/src/handlers/missing_match_arms.rs +++ b/crates/ide-diagnostics/src/handlers/missing_match_arms.rs @@ -1114,6 +1114,25 @@ fn test(x: Option<lib::PrivatelyUninhabited>) { } } + #[test] + fn non_exhaustive_may_be_empty() { + check_diagnostics_no_bails( + r" +//- /main.rs crate:main deps:dep +// In a different crate +fn empty_match_on_empty_struct<T>(x: dep::UninhabitedStruct) -> T { + match x {} +} +//- /dep.rs crate:dep +#[non_exhaustive] +pub struct UninhabitedStruct { + pub never: !, + // other fields +} +", + ); + } + mod false_negatives { //! The implementation of match checking here is a work in progress. As we roll this out, we //! prefer false negatives to false positives (ideally there would be no false positives). This diff --git a/crates/ide-diagnostics/src/handlers/missing_unsafe.rs b/crates/ide-diagnostics/src/handlers/missing_unsafe.rs index a630d3c7c3..2bfdda3565 100644 --- a/crates/ide-diagnostics/src/handlers/missing_unsafe.rs +++ b/crates/ide-diagnostics/src/handlers/missing_unsafe.rs @@ -1,5 +1,5 @@ use hir::db::ExpandDatabase; -use hir::HirFileIdExt; +use hir::{HirFileIdExt, UnsafetyReason}; use ide_db::text_edit::TextEdit; use ide_db::{assists::Assist, source_change::SourceChange}; use syntax::{ast, SyntaxNode}; @@ -16,23 +16,35 @@ pub(crate) fn missing_unsafe(ctx: &DiagnosticsContext<'_>, d: &hir::MissingUnsaf } else { DiagnosticCode::RustcHardError("E0133") }; + let operation = display_unsafety_reason(d.reason); Diagnostic::new_with_syntax_node_ptr( ctx, code, - "this operation is unsafe and requires an unsafe function or block", - d.expr.map(|it| it.into()), + format!("{operation} is unsafe and requires an unsafe function or block"), + d.node.map(|it| it.into()), ) .with_fixes(fixes(ctx, d)) } +fn display_unsafety_reason(reason: UnsafetyReason) -> &'static str { + match reason { + UnsafetyReason::UnionField => "access to union field", + UnsafetyReason::UnsafeFnCall => "call to unsafe function", + UnsafetyReason::InlineAsm => "use of inline assembly", + UnsafetyReason::RawPtrDeref => "dereference of raw pointer", + UnsafetyReason::MutableStatic => "use of mutable static", + UnsafetyReason::ExternStatic => "use of extern static", + } +} + fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::MissingUnsafe) -> Option<Vec<Assist>> { // The fixit will not work correctly for macro expansions, so we don't offer it in that case. - if d.expr.file_id.is_macro() { + if d.node.file_id.is_macro() { return None; } - let root = ctx.sema.db.parse_or_expand(d.expr.file_id); - let node = d.expr.value.to_node(&root); + let root = ctx.sema.db.parse_or_expand(d.node.file_id); + let node = d.node.value.to_node(&root); let expr = node.syntax().ancestors().find_map(ast::Expr::cast)?; let node_to_add_unsafe_block = pick_best_node_to_add_unsafe_block(&expr)?; @@ -40,7 +52,7 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::MissingUnsafe) -> Option<Vec<Ass let replacement = format!("unsafe {{ {} }}", node_to_add_unsafe_block.text()); let edit = TextEdit::replace(node_to_add_unsafe_block.text_range(), replacement); let source_change = - SourceChange::from_text_edit(d.expr.file_id.original_file(ctx.sema.db), edit); + SourceChange::from_text_edit(d.node.file_id.original_file(ctx.sema.db), edit); Some(vec![fix("add_unsafe", "Add unsafe block", source_change, expr.syntax().text_range())]) } @@ -110,7 +122,7 @@ fn main() { let x = &5_usize as *const usize; unsafe { let _y = *x; } let _z = *x; -} //^^💡 error: this operation is unsafe and requires an unsafe function or block +} //^^💡 error: dereference of raw pointer is unsafe and requires an unsafe function or block "#, ) } @@ -136,9 +148,9 @@ unsafe fn unsafe_fn() { fn main() { unsafe_fn(); - //^^^^^^^^^^^💡 error: this operation is unsafe and requires an unsafe function or block + //^^^^^^^^^^^💡 error: call to unsafe function is unsafe and requires an unsafe function or block HasUnsafe.unsafe_fn(); - //^^^^^^^^^^^^^^^^^^^^^💡 error: this operation is unsafe and requires an unsafe function or block + //^^^^^^^^^^^^^^^^^^^^^💡 error: call to unsafe function is unsafe and requires an unsafe function or block unsafe { unsafe_fn(); HasUnsafe.unsafe_fn(); @@ -162,7 +174,7 @@ static mut STATIC_MUT: Ty = Ty { a: 0 }; fn main() { let _x = STATIC_MUT.a; - //^^^^^^^^^^💡 error: this operation is unsafe and requires an unsafe function or block + //^^^^^^^^^^💡 error: use of mutable static is unsafe and requires an unsafe function or block unsafe { let _x = STATIC_MUT.a; } @@ -184,9 +196,9 @@ extern "C" { fn main() { let _x = EXTERN; - //^^^^^^💡 error: this operation is unsafe and requires an unsafe function or block + //^^^^^^💡 error: use of extern static is unsafe and requires an unsafe function or block let _x = EXTERN_MUT; - //^^^^^^^^^^💡 error: this operation is unsafe and requires an unsafe function or block + //^^^^^^^^^^💡 error: use of mutable static is unsafe and requires an unsafe function or block unsafe { let _x = EXTERN; let _x = EXTERN_MUT; @@ -234,7 +246,7 @@ extern "rust-intrinsic" { fn main() { let _ = bitreverse(12); let _ = floorf32(12.0); - //^^^^^^^^^^^^^^💡 error: this operation is unsafe and requires an unsafe function or block + //^^^^^^^^^^^^^^💡 error: call to unsafe function is unsafe and requires an unsafe function or block } "#, ); @@ -567,7 +579,7 @@ unsafe fn not_safe() -> u8 { fn main() { ed2021::safe(); ed2024::not_safe(); - //^^^^^^^^^^^^^^^^^^💡 error: this operation is unsafe and requires an unsafe function or block + //^^^^^^^^^^^^^^^^^^💡 error: call to unsafe function is unsafe and requires an unsafe function or block } "#, ) @@ -591,7 +603,7 @@ unsafe fn foo(p: *mut i32) { #![warn(unsafe_op_in_unsafe_fn)] unsafe fn foo(p: *mut i32) { *p = 123; - //^^💡 warn: this operation is unsafe and requires an unsafe function or block + //^^💡 warn: dereference of raw pointer is unsafe and requires an unsafe function or block } "#, ) @@ -618,17 +630,119 @@ unsafe extern { fn main() { f(); g(); - //^^^💡 error: this operation is unsafe and requires an unsafe function or block + //^^^💡 error: call to unsafe function is unsafe and requires an unsafe function or block h(); - //^^^💡 error: this operation is unsafe and requires an unsafe function or block + //^^^💡 error: call to unsafe function is unsafe and requires an unsafe function or block let _ = S1; let _ = S2; - //^^💡 error: this operation is unsafe and requires an unsafe function or block + //^^💡 error: use of extern static is unsafe and requires an unsafe function or block let _ = S3; - //^^💡 error: this operation is unsafe and requires an unsafe function or block + //^^💡 error: use of extern static is unsafe and requires an unsafe function or block +} +"#, + ); + } + + #[test] + fn no_unsafe_diagnostic_when_destructuring_union_with_wildcard() { + check_diagnostics( + r#" +union Union { field: i32 } +fn foo(v: &Union) { + let Union { field: _ } = v; + let Union { field: _ | _ } = v; + Union { field: _ } = *v; +} +"#, + ); + } + + #[test] + fn union_destructuring() { + check_diagnostics( + r#" +union Union { field: u8 } +fn foo(v @ Union { field: _field }: &Union) { + // ^^^^^^ error: access to union field is unsafe and requires an unsafe function or block + let Union { mut field } = v; + // ^^^^^^^^^💡 error: access to union field is unsafe and requires an unsafe function or block + let Union { field: 0..=255 } = v; + // ^^^^^^^💡 error: access to union field is unsafe and requires an unsafe function or block + let Union { field: 0 + // ^💡 error: access to union field is unsafe and requires an unsafe function or block + | 1..=255 } = v; + // ^^^^^^^💡 error: access to union field is unsafe and requires an unsafe function or block + Union { field } = *v; + // ^^^^^💡 error: access to union field is unsafe and requires an unsafe function or block + match v { + Union { field: _field } => {} + // ^^^^^^💡 error: access to union field is unsafe and requires an unsafe function or block + } + if let Union { field: _field } = v {} + // ^^^^^^💡 error: access to union field is unsafe and requires an unsafe function or block + (|&Union { field }| { _ = field; })(v); + // ^^^^^💡 error: access to union field is unsafe and requires an unsafe function or block +} +"#, + ); + } + + #[test] + fn union_field_access() { + check_diagnostics( + r#" +union Union { field: u8 } +fn foo(v: &Union) { + v.field; + // ^^^^^^^💡 error: access to union field is unsafe and requires an unsafe function or block } "#, ); } + + #[test] + fn inline_asm() { + check_diagnostics( + r#" +//- minicore: asm +fn foo() { + core::arch::asm!(""); + // ^^^^ error: use of inline assembly is unsafe and requires an unsafe function or block +} +"#, + ); + } + + #[test] + fn unsafe_op_in_unsafe_fn_dismissed_in_signature() { + check_diagnostics( + r#" +#![warn(unsafe_op_in_unsafe_fn)] +union Union { field: u32 } +unsafe fn foo(Union { field: _field }: Union) {} + "#, + ) + } + + #[test] + fn union_assignment_allowed() { + check_diagnostics( + r#" +union Union { field: u32 } +fn foo(mut v: Union) { + v.field = 123; + (v.field,) = (123,); + *&mut v.field = 123; + // ^^^^^^^💡 error: access to union field is unsafe and requires an unsafe function or block +} +struct Struct { field: u32 } +union Union2 { field: Struct } +fn bar(mut v: Union2) { + v.field.field = 123; +} + + "#, + ) + } } diff --git a/crates/ide-diagnostics/src/handlers/type_mismatch.rs b/crates/ide-diagnostics/src/handlers/type_mismatch.rs index 93fe9374a3..bfdda53740 100644 --- a/crates/ide-diagnostics/src/handlers/type_mismatch.rs +++ b/crates/ide-diagnostics/src/handlers/type_mismatch.rs @@ -1,12 +1,16 @@ use either::Either; -use hir::{db::ExpandDatabase, ClosureStyle, HirDisplay, HirFileIdExt, InFile, Type}; -use ide_db::text_edit::TextEdit; -use ide_db::{famous_defs::FamousDefs, source_change::SourceChange}; +use hir::{db::ExpandDatabase, CallableKind, ClosureStyle, HirDisplay, HirFileIdExt, InFile, Type}; +use ide_db::{ + famous_defs::FamousDefs, + source_change::{SourceChange, SourceChangeBuilder}, + text_edit::TextEdit, +}; use syntax::{ ast::{ self, edit::{AstNodeEdit, IndentLevel}, - BlockExpr, Expr, ExprStmt, + syntax_factory::SyntaxFactory, + BlockExpr, Expr, ExprStmt, HasArgList, }, AstNode, AstPtr, TextSize, }; @@ -63,6 +67,7 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::TypeMismatch) -> Option<Vec<Assi let expr_ptr = &InFile { file_id: d.expr_or_pat.file_id, value: expr_ptr }; add_reference(ctx, d, expr_ptr, &mut fixes); add_missing_ok_or_some(ctx, d, expr_ptr, &mut fixes); + remove_unnecessary_wrapper(ctx, d, expr_ptr, &mut fixes); remove_semicolon(ctx, d, expr_ptr, &mut fixes); str_ref_to_owned(ctx, d, expr_ptr, &mut fixes); } @@ -184,6 +189,89 @@ fn add_missing_ok_or_some( Some(()) } +fn remove_unnecessary_wrapper( + ctx: &DiagnosticsContext<'_>, + d: &hir::TypeMismatch, + expr_ptr: &InFile<AstPtr<ast::Expr>>, + acc: &mut Vec<Assist>, +) -> Option<()> { + let db = ctx.sema.db; + let root = db.parse_or_expand(expr_ptr.file_id); + let expr = expr_ptr.value.to_node(&root); + let expr = ctx.sema.original_ast_node(expr.clone())?; + + let Expr::CallExpr(call_expr) = expr else { + return None; + }; + + let callable = ctx.sema.resolve_expr_as_callable(&call_expr.expr()?)?; + let CallableKind::TupleEnumVariant(variant) = callable.kind() else { + return None; + }; + + let actual_enum = d.actual.as_adt()?.as_enum()?; + let famous_defs = FamousDefs(&ctx.sema, ctx.sema.scope(call_expr.syntax())?.krate()); + let core_option = famous_defs.core_option_Option(); + let core_result = famous_defs.core_result_Result(); + if Some(actual_enum) != core_option && Some(actual_enum) != core_result { + return None; + } + + let inner_type = variant.fields(db).first()?.ty_with_args(db, d.actual.type_arguments()); + if !d.expected.could_unify_with(db, &inner_type) { + return None; + } + + let inner_arg = call_expr.arg_list()?.args().next()?; + + let file_id = expr_ptr.file_id.original_file(db); + let mut builder = SourceChangeBuilder::new(file_id); + let mut editor; + match inner_arg { + // We're returning `()` + Expr::TupleExpr(tup) if tup.fields().next().is_none() => { + let parent = call_expr + .syntax() + .parent() + .and_then(Either::<ast::ReturnExpr, ast::StmtList>::cast)?; + + editor = builder.make_editor(parent.syntax()); + let make = SyntaxFactory::new(); + + match parent { + Either::Left(ret_expr) => { + editor.replace(ret_expr.syntax(), make.expr_return(None).syntax()); + } + Either::Right(stmt_list) => { + let new_block = if stmt_list.statements().next().is_none() { + make.expr_empty_block() + } else { + make.block_expr(stmt_list.statements(), None) + }; + + editor.replace(stmt_list.syntax().parent()?, new_block.syntax()); + } + } + + editor.add_mappings(make.finish_with_mappings()); + } + _ => { + editor = builder.make_editor(call_expr.syntax()); + editor.replace(call_expr.syntax(), inner_arg.syntax()); + } + } + + builder.add_file_edits(file_id, editor); + let name = format!("Remove unnecessary {}() wrapper", variant.name(db).as_str()); + acc.push(fix( + "remove_unnecessary_wrapper", + &name, + builder.finish(), + call_expr.syntax().text_range(), + )); + Some(()) +} + fn remove_semicolon( ctx: &DiagnosticsContext<'_>, d: &hir::TypeMismatch, @@ -243,7 +331,7 @@ fn str_ref_to_owned( #[cfg(test)] mod tests { use crate::tests::{ - check_diagnostics, check_diagnostics_with_disabled, check_fix, check_no_fix, + check_diagnostics, check_diagnostics_with_disabled, check_fix, check_has_fix, check_no_fix, }; #[test] @@ -260,7 +348,7 @@ fn test(_arg: &i32) {} } #[test] - fn test_add_reference_to_int() { + fn add_reference_to_int() { check_fix( r#" fn main() { @@ -278,7 +366,7 @@ fn test(_arg: &i32) {} } #[test] - fn test_add_mutable_reference_to_int() { + fn add_mutable_reference_to_int() { check_fix( r#" fn main() { @@ -296,7 +384,7 @@ fn test(_arg: &mut i32) {} } #[test] - fn test_add_reference_to_array() { + fn add_reference_to_array() { check_fix( r#" //- minicore: coerce_unsized @@ -315,7 +403,7 @@ fn test(_arg: &[i32]) {} } #[test] - fn test_add_reference_with_autoderef() { + fn add_reference_with_autoderef() { check_fix( r#" //- minicore: coerce_unsized, deref @@ -348,7 +436,7 @@ fn test(_arg: &Bar) {} } #[test] - fn test_add_reference_to_method_call() { + fn add_reference_to_method_call() { check_fix( r#" fn main() { @@ -372,7 +460,7 @@ impl Test { } #[test] - fn test_add_reference_to_let_stmt() { + fn add_reference_to_let_stmt() { check_fix( r#" fn main() { @@ -388,7 +476,7 @@ fn main() { } #[test] - fn test_add_reference_to_macro_call() { + fn add_reference_to_macro_call() { check_fix( r#" macro_rules! thousand { @@ -416,7 +504,7 @@ fn main() { } #[test] - fn test_add_mutable_reference_to_let_stmt() { + fn add_mutable_reference_to_let_stmt() { check_fix( r#" fn main() { @@ -432,29 +520,6 @@ fn main() { } #[test] - fn test_wrap_return_type_option() { - check_fix( - r#" -//- minicore: option, result -fn div(x: i32, y: i32) -> Option<i32> { - if y == 0 { - return None; - } - x / y$0 -} -"#, - r#" -fn div(x: i32, y: i32) -> Option<i32> { - if y == 0 { - return None; - } - Some(x / y) -} -"#, - ); - } - - #[test] fn const_generic_type_mismatch() { check_diagnostics( r#" @@ -487,59 +552,82 @@ fn div(x: i32, y: i32) -> Option<i32> { } #[test] - fn test_wrap_return_type_option_tails() { + fn wrap_return_type() { + check_fix( + r#" +//- minicore: option, result +fn div(x: i32, y: i32) -> Result<i32, ()> { + if y == 0 { + return Err(()); + } + x / y$0 +} +"#, + r#" +fn div(x: i32, y: i32) -> Result<i32, ()> { + if y == 0 { + return Err(()); + } + Ok(x / y) +} +"#, + ); + } + + #[test] + fn wrap_return_type_option() { check_fix( r#" //- minicore: option, result fn div(x: i32, y: i32) -> Option<i32> { if y == 0 { - Some(0) - } else if true { - 100$0 - } else { - None + return None; } + x / y$0 } "#, r#" fn div(x: i32, y: i32) -> Option<i32> { if y == 0 { - Some(0) - } else if true { - Some(100) - } else { - None + return None; } + Some(x / y) } "#, ); } #[test] - fn test_wrap_return_type() { + fn wrap_return_type_option_tails() { check_fix( r#" //- minicore: option, result -fn div(x: i32, y: i32) -> Result<i32, ()> { +fn div(x: i32, y: i32) -> Option<i32> { if y == 0 { - return Err(()); + Some(0) + } else if true { + 100$0 + } else { + None } - x / y$0 } "#, r#" -fn div(x: i32, y: i32) -> Result<i32, ()> { +fn div(x: i32, y: i32) -> Option<i32> { if y == 0 { - return Err(()); + Some(0) + } else if true { + Some(100) + } else { + None } - Ok(x / y) } "#, ); } #[test] - fn test_wrap_return_type_handles_generic_functions() { + fn wrap_return_type_handles_generic_functions() { check_fix( r#" //- minicore: option, result @@ -562,7 +650,7 @@ fn div<T>(x: T) -> Result<T, i32> { } #[test] - fn test_wrap_return_type_handles_type_aliases() { + fn wrap_return_type_handles_type_aliases() { check_fix( r#" //- minicore: option, result @@ -589,7 +677,7 @@ fn div(x: i32, y: i32) -> MyResult<i32> { } #[test] - fn test_wrapped_unit_as_block_tail_expr() { + fn wrapped_unit_as_block_tail_expr() { check_fix( r#" //- minicore: result @@ -619,7 +707,7 @@ fn foo() -> Result<(), ()> { } #[test] - fn test_wrapped_unit_as_return_expr() { + fn wrapped_unit_as_return_expr() { check_fix( r#" //- minicore: result @@ -642,7 +730,7 @@ fn foo(b: bool) -> Result<(), String> { } #[test] - fn test_in_const_and_static() { + fn wrap_in_const_and_static() { check_fix( r#" //- minicore: option, result @@ -664,7 +752,7 @@ const _: Option<()> = {Some(())}; } #[test] - fn test_wrap_return_type_not_applicable_when_expr_type_does_not_match_ok_type() { + fn wrap_return_type_not_applicable_when_expr_type_does_not_match_ok_type() { check_no_fix( r#" //- minicore: option, result @@ -674,7 +762,7 @@ fn foo() -> Result<(), i32> { 0$0 } } #[test] - fn test_wrap_return_type_not_applicable_when_return_type_is_not_result_or_option() { + fn wrap_return_type_not_applicable_when_return_type_is_not_result_or_option() { check_no_fix( r#" //- minicore: option, result @@ -686,6 +774,254 @@ fn foo() -> SomeOtherEnum { 0$0 } } #[test] + fn unwrap_return_type() { + check_fix( + r#" +//- minicore: option, result +fn div(x: i32, y: i32) -> i32 { + if y == 0 { + panic!(); + } + Ok(x / y)$0 +} +"#, + r#" +fn div(x: i32, y: i32) -> i32 { + if y == 0 { + panic!(); + } + x / y +} +"#, + ); + } + + #[test] + fn unwrap_return_type_option() { + check_fix( + r#" +//- minicore: option, result +fn div(x: i32, y: i32) -> i32 { + if y == 0 { + panic!(); + } + Some(x / y)$0 +} +"#, + r#" +fn div(x: i32, y: i32) -> i32 { + if y == 0 { + panic!(); + } + x / y +} +"#, + ); + } + + #[test] + fn unwrap_return_type_option_tails() { + check_fix( + r#" +//- minicore: option, result +fn div(x: i32, y: i32) -> i32 { + if y == 0 { + 42 + } else if true { + Some(100)$0 + } else { + 0 + } +} +"#, + r#" +fn div(x: i32, y: i32) -> i32 { + if y == 0 { + 42 + } else if true { + 100 + } else { + 0 + } +} +"#, + ); + } + + #[test] + fn unwrap_return_type_option_tail_unit() { + check_fix( + r#" +//- minicore: option, result +fn div(x: i32, y: i32) { + if y == 0 { + panic!(); + } + + Ok(())$0 +} +"#, + r#" +fn div(x: i32, y: i32) { + if y == 0 { + panic!(); + } +} +"#, + ); + } + + #[test] + fn unwrap_return_type_handles_generic_functions() { + check_fix( + r#" +//- minicore: option, result +fn div<T>(x: T) -> T { + if x == 0 { + panic!(); + } + $0Ok(x) +} +"#, + r#" +fn div<T>(x: T) -> T { + if x == 0 { + panic!(); + } + x +} +"#, + ); + } + + #[test] + fn unwrap_return_type_handles_type_aliases() { + check_fix( + r#" +//- minicore: option, result +type MyResult<T> = T; + +fn div(x: i32, y: i32) -> MyResult<i32> { + if y == 0 { + panic!(); + } + Ok(x $0/ y) +} +"#, + r#" +type MyResult<T> = T; + +fn div(x: i32, y: i32) -> MyResult<i32> { + if y == 0 { + panic!(); + } + x / y +} +"#, + ); + } + + #[test] + fn unwrap_tail_expr() { + check_fix( + r#" +//- minicore: result +fn foo() -> () { + println!("Hello, world!"); + Ok(())$0 +} + "#, + r#" +fn foo() -> () { + println!("Hello, world!"); +} + "#, + ); + } + + #[test] + fn unwrap_to_empty_block() { + check_fix( + r#" +//- minicore: result +fn foo() -> () { + Ok(())$0 +} + "#, + r#" +fn foo() -> () {} + "#, + ); + } + + #[test] + fn unwrap_to_return_expr() { + check_has_fix( + r#" +//- minicore: result +fn foo(b: bool) -> () { + if b { + return $0Ok(()); + } + + panic!("oh dear"); +}"#, + r#" +fn foo(b: bool) -> () { + if b { + return; + } + + panic!("oh dear"); +}"#, + ); + } + + #[test] + fn unwrap_in_const_and_static() { + check_fix( + r#" +//- minicore: option, result +static A: () = {Some(($0))}; + "#, + r#" +static A: () = {}; + "#, + ); + check_fix( + r#" +//- minicore: option, result +const _: () = {Some(($0))}; + "#, + r#" +const _: () = {}; + "#, + ); + } + + #[test] + fn unwrap_return_type_not_applicable_when_inner_type_does_not_match_return_type() { + check_no_fix( + r#" +//- minicore: result +fn foo() -> i32 { $0Ok(()) } +"#, + ); + } + + #[test] + fn unwrap_return_type_not_applicable_when_wrapper_type_is_not_result_or_option() { + check_no_fix( + r#" +//- minicore: option, result +enum SomeOtherEnum { Ok(i32), Err(String) } + +fn foo() -> i32 { SomeOtherEnum::Ok($042) } +"#, + ); + } + + #[test] fn remove_semicolon() { check_fix(r#"fn f() -> i32 { 92$0; }"#, r#"fn f() -> i32 { 92 }"#); } diff --git a/crates/ide-diagnostics/src/handlers/unresolved_method.rs b/crates/ide-diagnostics/src/handlers/unresolved_method.rs index 81cb452121..4ab649cc16 100644 --- a/crates/ide-diagnostics/src/handlers/unresolved_method.rs +++ b/crates/ide-diagnostics/src/handlers/unresolved_method.rs @@ -167,9 +167,9 @@ fn assoc_func_fix(ctx: &DiagnosticsContext<'_>, d: &hir::UnresolvedMethodCall) - } let method_name = call.name_ref()?; - let assoc_func_call = format!("{receiver_type_adt_name}::{method_name}()"); + let assoc_func_path = format!("{receiver_type_adt_name}::{method_name}"); - let assoc_func_call = make::expr_path(make::path_from_text(&assoc_func_call)); + let assoc_func_path = make::expr_path(make::path_from_text(&assoc_func_path)); let args: Vec<_> = if need_to_take_receiver_as_first_arg { std::iter::once(receiver).chain(call.arg_list()?.args()).collect() @@ -178,7 +178,7 @@ fn assoc_func_fix(ctx: &DiagnosticsContext<'_>, d: &hir::UnresolvedMethodCall) - }; let args = make::arg_list(args); - let assoc_func_call_expr_string = make::expr_call(assoc_func_call, args).to_string(); + let assoc_func_call_expr_string = make::expr_call(assoc_func_path, args).to_string(); let file_id = ctx.sema.original_range_opt(call.receiver()?.syntax())?.file_id; diff --git a/crates/ide-diagnostics/src/lib.rs b/crates/ide-diagnostics/src/lib.rs index 1f1b6478d3..ad33956908 100644 --- a/crates/ide-diagnostics/src/lib.rs +++ b/crates/ide-diagnostics/src/lib.rs @@ -27,6 +27,7 @@ mod handlers { pub(crate) mod await_outside_of_async; pub(crate) mod break_outside_of_loop; pub(crate) mod expected_function; + pub(crate) mod generic_args_prohibited; pub(crate) mod inactive_code; pub(crate) mod incoherent_impl; pub(crate) mod incorrect_case; @@ -468,6 +469,7 @@ pub fn semantic_diagnostics( Some(it) => it, None => continue, }, + AnyDiagnostic::GenericArgsProhibited(d) => handlers::generic_args_prohibited::generic_args_prohibited(&ctx, &d) }; res.push(d) } @@ -542,7 +544,13 @@ fn handle_diag_from_macros( sema.db.lookup_intern_syntax_context(span.ctx).outer_expn.is_some_and(|expansion| { let macro_call = sema.db.lookup_intern_macro_call(expansion.as_macro_file().macro_call_id); + // We don't want to show diagnostics for non-local macros at all, but proc macros authors + // seem to rely on being able to emit non-warning-free code, so we don't want to show warnings + // for them even when the proc macro comes from the same workspace (in rustc that's not a + // problem because it doesn't have the concept of workspaces, and proc macros always reside + // in a different crate). !Crate::from(macro_call.def.krate).origin(sema.db).is_local() + || !macro_call.def.kind.is_declarative() }) }) { // Disable suggestions for external macros, they'll change library code and it's just bad. diff --git a/crates/ide-ssr/src/matching.rs b/crates/ide-ssr/src/matching.rs index 6569f0f555..4edc3633fb 100644 --- a/crates/ide-ssr/src/matching.rs +++ b/crates/ide-ssr/src/matching.rs @@ -359,8 +359,8 @@ impl<'db, 'sema> Matcher<'db, 'sema> { )?; self.attempt_match_opt( phase, - pattern_segment.param_list(), - code_segment.param_list(), + pattern_segment.parenthesized_arg_list(), + code_segment.parenthesized_arg_list(), )?; } if matches!(phase, Phase::Second(_)) { diff --git a/crates/ide/src/hover/tests.rs b/crates/ide/src/hover/tests.rs index 0986d5542c..ea18b89c5c 100644 --- a/crates/ide/src/hover/tests.rs +++ b/crates/ide/src/hover/tests.rs @@ -288,7 +288,7 @@ m!(ab$0c); *abc* ```rust - test + ra_test_fixture ``` ```rust @@ -301,7 +301,7 @@ m!(ab$0c); --- ```rust - test::module + ra_test_fixture::module ``` ```rust @@ -327,7 +327,7 @@ fn main() { "#, expect![[r#" *foo* - test + ra_test_fixture pub fn foo() -> u32 "#]], @@ -487,7 +487,7 @@ fn main() { GoToType( [ HoverGotoTypeData { - mod_path: "test::S2", + mod_path: "ra_test_fixture::S2", nav: NavigationTarget { file_id: FileId( 0, @@ -500,7 +500,7 @@ fn main() { }, }, HoverGotoTypeData { - mod_path: "test::S", + mod_path: "ra_test_fixture::S", nav: NavigationTarget { file_id: FileId( 0, @@ -583,7 +583,7 @@ fn main() { let foo_test = fo$0o(); } *foo* ```rust - test + ra_test_fixture ``` ```rust @@ -605,7 +605,7 @@ fn main() { f$0oo(); } *foo* ```rust - test + ra_test_fixture ``` ```rust @@ -627,7 +627,7 @@ fn main() { m::f$0oo(); } *foo* ```rust - test::m + ra_test_fixture::m ``` ```rust @@ -649,7 +649,7 @@ fn main() { fo$0o(); } *foo* ```rust - test + ra_test_fixture ``` ```rust @@ -667,7 +667,7 @@ fn main() { fo$0o(); } *foo* ```rust - test + ra_test_fixture ``` ```rust @@ -691,7 +691,7 @@ fn main() { let foo_test = fo$0o(); } *foo* ```rust - test + ra_test_fixture ``` ```rust @@ -715,7 +715,7 @@ fn main() { } *foo* ```rust - test + ra_test_fixture ``` ```rust @@ -743,7 +743,7 @@ fn main() { } *foo* ```rust - test + ra_test_fixture ``` ```rust @@ -776,7 +776,7 @@ fn main() { } *foo* ```rust - test + ra_test_fixture ``` ```rust @@ -801,7 +801,7 @@ struct Foo { fiel$0d_a: u8, field_b: i32, field_c: i16 } *field_a* ```rust - test::Foo + ra_test_fixture::Foo ``` ```rust @@ -830,7 +830,7 @@ fn main() { *field_a* ```rust - test::Foo + ra_test_fixture::Foo ``` ```rust @@ -852,7 +852,7 @@ fn main() { *field_a* ```rust - test::Foo + ra_test_fixture::Foo ``` ```rust @@ -880,7 +880,7 @@ fn main() { *0* ```rust - test::Foo + ra_test_fixture::Foo ``` ```rust @@ -900,7 +900,7 @@ fn foo(foo: Foo) { *0* ```rust - test::Foo + ra_test_fixture::Foo ``` ```rust @@ -920,7 +920,7 @@ struct Foo$0(pub u32) where u32: Copy; *Foo* ```rust - test + ra_test_fixture ``` ```rust @@ -946,7 +946,7 @@ struct Foo$0 { field: u32 } *Foo* ```rust - test + ra_test_fixture ``` ```rust @@ -968,7 +968,7 @@ struct Foo$0 where u32: Copy { field: u32 } *Foo* ```rust - test + ra_test_fixture ``` ```rust @@ -998,7 +998,7 @@ fn hover_record_struct_limit() { *Foo* ```rust - test + ra_test_fixture ``` ```rust @@ -1023,7 +1023,7 @@ fn hover_record_struct_limit() { *Foo* ```rust - test + ra_test_fixture ``` ```rust @@ -1046,7 +1046,7 @@ fn hover_record_struct_limit() { *Foo* ```rust - test + ra_test_fixture ``` ```rust @@ -1072,7 +1072,7 @@ fn hover_record_struct_limit() { *Foo* ```rust - test + ra_test_fixture ``` ```rust @@ -1093,7 +1093,7 @@ fn hover_record_struct_limit() { *Foo* ```rust - test + ra_test_fixture ``` ```rust @@ -1116,7 +1116,7 @@ fn hover_record_struct_limit() { *Foo* ```rust - test + ra_test_fixture ``` ```rust @@ -1141,7 +1141,7 @@ fn hover_record_variant_limit() { *A* ```rust - test::Foo + ra_test_fixture::Foo ``` ```rust @@ -1162,7 +1162,7 @@ fn hover_record_variant_limit() { *A* ```rust - test::Foo + ra_test_fixture::Foo ``` ```rust @@ -1183,7 +1183,7 @@ fn hover_record_variant_limit() { *A* ```rust - test::Foo + ra_test_fixture::Foo ``` ```rust @@ -1204,7 +1204,7 @@ fn hover_record_variant_limit() { *A* ```rust - test::Foo + ra_test_fixture::Foo ``` ```rust @@ -1225,7 +1225,7 @@ fn hover_record_variant_limit() { *A* ```rust - test::Foo + ra_test_fixture::Foo ``` ```rust @@ -1248,7 +1248,7 @@ fn hover_enum_limit() { *Foo* ```rust - test + ra_test_fixture ``` ```rust @@ -1270,7 +1270,7 @@ fn hover_enum_limit() { *Foo* ```rust - test + ra_test_fixture ``` ```rust @@ -1292,7 +1292,7 @@ fn hover_enum_limit() { *Foo* ```rust - test + ra_test_fixture ``` ```rust @@ -1311,7 +1311,7 @@ fn hover_enum_limit() { *Foo* ```rust - test + ra_test_fixture ``` ```rust @@ -1339,7 +1339,7 @@ fn hover_enum_limit() { *Enum* ```rust - test + ra_test_fixture ``` ```rust @@ -1371,7 +1371,7 @@ fn hover_union_limit() { *Foo* ```rust - test + ra_test_fixture ``` ```rust @@ -1393,7 +1393,7 @@ fn hover_union_limit() { *Foo* ```rust - test + ra_test_fixture ``` ```rust @@ -1415,7 +1415,7 @@ fn hover_union_limit() { *Foo* ```rust - test + ra_test_fixture ``` ```rust @@ -1434,7 +1434,7 @@ fn hover_union_limit() { *Foo* ```rust - test + ra_test_fixture ``` ```rust @@ -1458,7 +1458,7 @@ struct Foo$0 where u32: Copy; *Foo* ```rust - test + ra_test_fixture ``` ```rust @@ -1484,7 +1484,7 @@ type Fo$0o: Trait = S where T: Trait; *Foo* ```rust - test + ra_test_fixture ``` ```rust @@ -1504,7 +1504,7 @@ fn hover_const_static() { *foo* ```rust - test + ra_test_fixture ``` ```rust @@ -1522,7 +1522,7 @@ const foo$0: u32 = { *foo* ```rust - test + ra_test_fixture ``` ```rust @@ -1540,7 +1540,7 @@ const foo$0: u32 = { *foo* ```rust - test + ra_test_fixture ``` ```rust @@ -1555,7 +1555,7 @@ const foo$0: u32 = { *FOO* ```rust - test + ra_test_fixture ``` ```rust @@ -1573,7 +1573,7 @@ const foo$0: u32 = { *BAR* ```rust - test + ra_test_fixture ``` ```rust @@ -1591,7 +1591,7 @@ fn hover_unsigned_max_const() { *A* ```rust - test + ra_test_fixture ``` ```rust @@ -1612,7 +1612,7 @@ fn hover_eval_complex_constants() { *foo* ```rust - test + ra_test_fixture ``` ```rust @@ -1667,16 +1667,16 @@ use Option::Some; fn main() { So$0me(12); } "#, expect![[r#" - *Some* + *Some* - ```rust - test::Option - ``` + ```rust + ra_test_fixture::Option + ``` - ```rust - Some(T) - ``` - "#]], + ```rust + Some(T) + ``` + "#]], ); check( @@ -1711,20 +1711,20 @@ enum Option<T> { } "#, expect![[r#" - *None* + *None* - ```rust - test::Option - ``` + ```rust + ra_test_fixture::Option + ``` - ```rust - None - ``` + ```rust + None + ``` - --- + --- - The None variant - "#]], + The None variant + "#]], ); check( @@ -1738,20 +1738,20 @@ fn main() { } "#, expect![[r#" - *Some* + *Some* - ```rust - test::Option - ``` + ```rust + ra_test_fixture::Option + ``` - ```rust - Some(T) - ``` + ```rust + Some(T) + ``` - --- + --- - The Some variant - "#]], + The Some variant + "#]], ); } @@ -1885,7 +1885,7 @@ fn main() { let foo_test = wrapper::Thing::new$0(); } *new* ```rust - test::wrapper::Thing + ra_test_fixture::wrapper::Thing ``` ```rust @@ -1916,7 +1916,7 @@ fn main() { *C* ```rust - test::X + ra_test_fixture::X ``` ```rust @@ -1936,18 +1936,18 @@ impl Thing { } "#, expect![[r#" - *Self* + *Self* - ```rust - test - ``` + ```rust + ra_test_fixture + ``` - ```rust - struct Thing { - x: u32, - } - ``` - "#]], + ```rust + struct Thing { + x: u32, + } + ``` + "#]], ); check_hover_fields_limit( None, @@ -1958,16 +1958,16 @@ impl Thing { } "#, expect![[r#" - *Self* + *Self* - ```rust - test - ``` + ```rust + ra_test_fixture + ``` - ```rust - struct Thing - ``` - "#]], + ```rust + struct Thing + ``` + "#]], ); check( r#" @@ -1977,18 +1977,18 @@ impl Thing { } "#, expect![[r#" - *Self* + *Self* - ```rust - test - ``` + ```rust + ra_test_fixture + ``` - ```rust - struct Thing { - x: u32, - } - ``` - "#]], + ```rust + struct Thing { + x: u32, + } + ``` + "#]], ); check( r#" @@ -2001,7 +2001,7 @@ impl Thing { *Self* ```rust - test + ra_test_fixture ``` ```rust @@ -2022,7 +2022,7 @@ impl Thing { *Self* ```rust - test + ra_test_fixture ``` ```rust @@ -2042,7 +2042,7 @@ impl usize { *Self* ```rust - test + ra_test_fixture ``` ```rust @@ -2060,7 +2060,7 @@ impl fn() -> usize { *Self* ```rust - test + ra_test_fixture ``` ```rust @@ -2103,7 +2103,7 @@ fn f() { fo$0o!(); } *foo* ```rust - test + ra_test_fixture ``` ```rust @@ -2128,7 +2128,7 @@ fn f() { fo$0o!(); } *foo* ```rust - test + ra_test_fixture ``` ```rust @@ -2172,7 +2172,7 @@ id! { *foo* ```rust - test + ra_test_fixture ``` ```rust @@ -2194,7 +2194,7 @@ fn foo$0() {} *foo* ```rust - test + ra_test_fixture ``` ```rust @@ -2252,7 +2252,7 @@ fn foo() { let a = id!([0u32, bar$0()] ); } *bar* ```rust - test + ra_test_fixture ``` ```rust @@ -2278,7 +2278,7 @@ fn foo() { *bar* ```rust - test + ra_test_fixture ``` ```rust @@ -2302,7 +2302,7 @@ fn foo(Foo { b$0ar }: &Foo) {} GoToType( [ HoverGotoTypeData { - mod_path: "test::Bar", + mod_path: "ra_test_fixture::Bar", nav: NavigationTarget { file_id: FileId( 0, @@ -2334,7 +2334,7 @@ fn bar() { fo$0o(); } *foo* ```rust - test + ra_test_fixture ``` ```rust @@ -2356,7 +2356,7 @@ fn test_hover_function_show_qualifiers() { *foo* ```rust - test + ra_test_fixture ``` ```rust @@ -2370,7 +2370,7 @@ fn test_hover_function_show_qualifiers() { *foo* ```rust - test + ra_test_fixture ``` ```rust @@ -2385,7 +2385,7 @@ fn test_hover_function_show_qualifiers() { *foo* ```rust - test::m + ra_test_fixture::m ``` ```rust @@ -2403,7 +2403,7 @@ fn test_hover_function_show_types() { *foo* ```rust - test + ra_test_fixture ``` ```rust @@ -2426,7 +2426,7 @@ fn main() { foo$0; } *foo* ```rust - test + ra_test_fixture ``` ```rust @@ -2446,7 +2446,7 @@ fn main() { foo$0; } *foo* ```rust - test + ra_test_fixture ``` ```rust @@ -2464,7 +2464,7 @@ fn test_hover_function_pointer_show_identifiers() { *foo* ```rust - test + ra_test_fixture ``` ```rust @@ -2486,7 +2486,7 @@ fn test_hover_function_pointer_no_identifier() { *foo* ```rust - test + ra_test_fixture ``` ```rust @@ -2601,16 +2601,16 @@ mod my { pub struct Bar; } fn my() {} "#, expect![[r#" - *my* + *my* - ```rust - test - ``` + ```rust + ra_test_fixture + ``` - ```rust - mod my - ``` - "#]], + ```rust + mod my + ``` + "#]], ); } @@ -2636,7 +2636,7 @@ fn foo() { let bar = Ba$0r; } *Bar* ```rust - test + ra_test_fixture ``` ```rust @@ -2672,7 +2672,7 @@ fn foo() { let bar = Ba$0r; } *Bar* ```rust - test + ra_test_fixture ``` ```rust @@ -2701,7 +2701,7 @@ fn foo() { let bar = Ba$0r; } *Bar* ```rust - test + ra_test_fixture ``` ```rust @@ -2729,7 +2729,7 @@ pub struct B$0ar *Bar* ```rust - test + ra_test_fixture ``` ```rust @@ -2760,7 +2760,7 @@ pub struct B$0ar *Bar* ```rust - test + ra_test_fixture ``` ```rust @@ -2809,7 +2809,7 @@ pub fn fo$0o() {} *foo* ```rust - test + ra_test_fixture ``` ```rust @@ -2853,7 +2853,7 @@ fn test_hover_layout_of_variant() { *Variant1* ```rust - test::Foo + ra_test_fixture::Foo ``` ```rust @@ -2878,7 +2878,7 @@ fn test_hover_layout_of_variant_generic() { *None* ```rust - test::Option + ra_test_fixture::Option ``` ```rust @@ -2899,7 +2899,7 @@ struct S$0<T>(core::marker::PhantomData<T>); *S* ```rust - test + ra_test_fixture ``` ```rust @@ -2924,7 +2924,7 @@ fn test_hover_layout_of_enum() { *Foo* ```rust - test + ra_test_fixture ``` ```rust @@ -2949,7 +2949,7 @@ fn test_hover_no_memory_layout() { *field_a* ```rust - test::Foo + ra_test_fixture::Foo ``` ```rust @@ -3003,7 +3003,7 @@ fn foo() { let bar = Bar; bar.fo$0o(); } *foo* ```rust - test::Bar + ra_test_fixture::Bar ``` ```rust @@ -3041,7 +3041,7 @@ fn foo() { let bar = Bar; bar.fo$0o(); } *foo* ```rust - test::Bar + ra_test_fixture::Bar ``` ```rust @@ -3069,7 +3069,7 @@ fn main() { let foo_test = unsafe { fo$0o(1, 2, 3); } } *foo* ```rust - test::<extern> + ra_test_fixture::<extern> ``` ```rust @@ -3267,7 +3267,7 @@ fn main() { let s$0t = S{ f1:0 }; } GoToType( [ HoverGotoTypeData { - mod_path: "test::S", + mod_path: "ra_test_fixture::S", nav: NavigationTarget { file_id: FileId( 0, @@ -3300,7 +3300,7 @@ fn main() { let s$0t = S{ f1:Arg(0) }; } GoToType( [ HoverGotoTypeData { - mod_path: "test::Arg", + mod_path: "ra_test_fixture::Arg", nav: NavigationTarget { file_id: FileId( 0, @@ -3313,7 +3313,7 @@ fn main() { let s$0t = S{ f1:Arg(0) }; } }, }, HoverGotoTypeData { - mod_path: "test::S", + mod_path: "ra_test_fixture::S", nav: NavigationTarget { file_id: FileId( 0, @@ -3359,7 +3359,7 @@ fn main() { let s$0t = S{ f1: S{ f1: Arg(0) } }; } GoToType( [ HoverGotoTypeData { - mod_path: "test::Arg", + mod_path: "ra_test_fixture::Arg", nav: NavigationTarget { file_id: FileId( 0, @@ -3372,7 +3372,7 @@ fn main() { let s$0t = S{ f1: S{ f1: Arg(0) } }; } }, }, HoverGotoTypeData { - mod_path: "test::S", + mod_path: "ra_test_fixture::S", nav: NavigationTarget { file_id: FileId( 0, @@ -3408,7 +3408,7 @@ fn main() { let s$0t = (A(1), B(2), M::C(3) ); } GoToType( [ HoverGotoTypeData { - mod_path: "test::A", + mod_path: "ra_test_fixture::A", nav: NavigationTarget { file_id: FileId( 0, @@ -3421,7 +3421,7 @@ fn main() { let s$0t = (A(1), B(2), M::C(3) ); } }, }, HoverGotoTypeData { - mod_path: "test::B", + mod_path: "ra_test_fixture::B", nav: NavigationTarget { file_id: FileId( 0, @@ -3434,7 +3434,7 @@ fn main() { let s$0t = (A(1), B(2), M::C(3) ); } }, }, HoverGotoTypeData { - mod_path: "test::M::C", + mod_path: "ra_test_fixture::M::C", nav: NavigationTarget { file_id: FileId( 0, @@ -3468,7 +3468,7 @@ fn main() { let s$0t = foo(); } GoToType( [ HoverGotoTypeData { - mod_path: "test::Foo", + mod_path: "ra_test_fixture::Foo", nav: NavigationTarget { file_id: FileId( 0, @@ -3502,7 +3502,7 @@ fn main() { let s$0t = foo(); } GoToType( [ HoverGotoTypeData { - mod_path: "test::Foo", + mod_path: "ra_test_fixture::Foo", nav: NavigationTarget { file_id: FileId( 0, @@ -3515,7 +3515,7 @@ fn main() { let s$0t = foo(); } }, }, HoverGotoTypeData { - mod_path: "test::S", + mod_path: "ra_test_fixture::S", nav: NavigationTarget { file_id: FileId( 0, @@ -3549,7 +3549,7 @@ fn main() { let s$0t = foo(); } GoToType( [ HoverGotoTypeData { - mod_path: "test::Bar", + mod_path: "ra_test_fixture::Bar", nav: NavigationTarget { file_id: FileId( 0, @@ -3562,7 +3562,7 @@ fn main() { let s$0t = foo(); } }, }, HoverGotoTypeData { - mod_path: "test::Foo", + mod_path: "ra_test_fixture::Foo", nav: NavigationTarget { file_id: FileId( 0, @@ -3599,7 +3599,7 @@ fn main() { let s$0t = foo(); } GoToType( [ HoverGotoTypeData { - mod_path: "test::Bar", + mod_path: "ra_test_fixture::Bar", nav: NavigationTarget { file_id: FileId( 0, @@ -3612,7 +3612,7 @@ fn main() { let s$0t = foo(); } }, }, HoverGotoTypeData { - mod_path: "test::Foo", + mod_path: "ra_test_fixture::Foo", nav: NavigationTarget { file_id: FileId( 0, @@ -3625,7 +3625,7 @@ fn main() { let s$0t = foo(); } }, }, HoverGotoTypeData { - mod_path: "test::S1", + mod_path: "ra_test_fixture::S1", nav: NavigationTarget { file_id: FileId( 0, @@ -3638,7 +3638,7 @@ fn main() { let s$0t = foo(); } }, }, HoverGotoTypeData { - mod_path: "test::S2", + mod_path: "ra_test_fixture::S2", nav: NavigationTarget { file_id: FileId( 0, @@ -3669,7 +3669,7 @@ fn foo(ar$0g: &impl Foo) {} GoToType( [ HoverGotoTypeData { - mod_path: "test::Foo", + mod_path: "ra_test_fixture::Foo", nav: NavigationTarget { file_id: FileId( 0, @@ -3703,7 +3703,7 @@ fn foo(ar$0g: &impl Foo + Bar<S>) {} GoToType( [ HoverGotoTypeData { - mod_path: "test::Bar", + mod_path: "ra_test_fixture::Bar", nav: NavigationTarget { file_id: FileId( 0, @@ -3716,7 +3716,7 @@ fn foo(ar$0g: &impl Foo + Bar<S>) {} }, }, HoverGotoTypeData { - mod_path: "test::Foo", + mod_path: "ra_test_fixture::Foo", nav: NavigationTarget { file_id: FileId( 0, @@ -3729,7 +3729,7 @@ fn foo(ar$0g: &impl Foo + Bar<S>) {} }, }, HoverGotoTypeData { - mod_path: "test::S", + mod_path: "ra_test_fixture::S", nav: NavigationTarget { file_id: FileId( 0, @@ -3816,7 +3816,7 @@ fn foo(ar$0g: &impl Foo<S>) {} GoToType( [ HoverGotoTypeData { - mod_path: "test::Foo", + mod_path: "ra_test_fixture::Foo", nav: NavigationTarget { file_id: FileId( 0, @@ -3829,7 +3829,7 @@ fn foo(ar$0g: &impl Foo<S>) {} }, }, HoverGotoTypeData { - mod_path: "test::S", + mod_path: "ra_test_fixture::S", nav: NavigationTarget { file_id: FileId( 0, @@ -3866,7 +3866,7 @@ fn main() { let s$0t = foo(); } GoToType( [ HoverGotoTypeData { - mod_path: "test::B", + mod_path: "ra_test_fixture::B", nav: NavigationTarget { file_id: FileId( 0, @@ -3879,7 +3879,7 @@ fn main() { let s$0t = foo(); } }, }, HoverGotoTypeData { - mod_path: "test::Foo", + mod_path: "ra_test_fixture::Foo", nav: NavigationTarget { file_id: FileId( 0, @@ -3892,7 +3892,7 @@ fn main() { let s$0t = foo(); } }, }, HoverGotoTypeData { - mod_path: "test::S", + mod_path: "ra_test_fixture::S", nav: NavigationTarget { file_id: FileId( 0, @@ -3923,7 +3923,7 @@ fn foo(ar$0g: &dyn Foo) {} GoToType( [ HoverGotoTypeData { - mod_path: "test::Foo", + mod_path: "ra_test_fixture::Foo", nav: NavigationTarget { file_id: FileId( 0, @@ -3955,7 +3955,7 @@ fn foo(ar$0g: &dyn Foo<S>) {} GoToType( [ HoverGotoTypeData { - mod_path: "test::Foo", + mod_path: "ra_test_fixture::Foo", nav: NavigationTarget { file_id: FileId( 0, @@ -3968,7 +3968,7 @@ fn foo(ar$0g: &dyn Foo<S>) {} }, }, HoverGotoTypeData { - mod_path: "test::S", + mod_path: "ra_test_fixture::S", nav: NavigationTarget { file_id: FileId( 0, @@ -4003,7 +4003,7 @@ fn foo(a$0rg: &impl ImplTrait<B<dyn DynTrait<B<S>>>>) {} GoToType( [ HoverGotoTypeData { - mod_path: "test::B", + mod_path: "ra_test_fixture::B", nav: NavigationTarget { file_id: FileId( 0, @@ -4016,7 +4016,7 @@ fn foo(a$0rg: &impl ImplTrait<B<dyn DynTrait<B<S>>>>) {} }, }, HoverGotoTypeData { - mod_path: "test::DynTrait", + mod_path: "ra_test_fixture::DynTrait", nav: NavigationTarget { file_id: FileId( 0, @@ -4029,7 +4029,7 @@ fn foo(a$0rg: &impl ImplTrait<B<dyn DynTrait<B<S>>>>) {} }, }, HoverGotoTypeData { - mod_path: "test::ImplTrait", + mod_path: "ra_test_fixture::ImplTrait", nav: NavigationTarget { file_id: FileId( 0, @@ -4042,7 +4042,7 @@ fn foo(a$0rg: &impl ImplTrait<B<dyn DynTrait<B<S>>>>) {} }, }, HoverGotoTypeData { - mod_path: "test::S", + mod_path: "ra_test_fixture::S", nav: NavigationTarget { file_id: FileId( 0, @@ -4084,7 +4084,7 @@ fn main() { let s$0t = test().get(); } GoToType( [ HoverGotoTypeData { - mod_path: "test::Foo", + mod_path: "ra_test_fixture::Foo", nav: NavigationTarget { file_id: FileId( 0, @@ -4117,7 +4117,7 @@ impl<const BAR: Bar> Foo<BAR$0> {} GoToType( [ HoverGotoTypeData { - mod_path: "test::Bar", + mod_path: "ra_test_fixture::Bar", nav: NavigationTarget { file_id: FileId( 0, @@ -4149,7 +4149,7 @@ fn foo<T: Foo>(t: T$0){} GoToType( [ HoverGotoTypeData { - mod_path: "test::Foo", + mod_path: "ra_test_fixture::Foo", nav: NavigationTarget { file_id: FileId( 0, @@ -4182,7 +4182,7 @@ impl Foo { GoToType( [ HoverGotoTypeData { - mod_path: "test::Foo", + mod_path: "ra_test_fixture::Foo", nav: NavigationTarget { file_id: FileId( 0, @@ -4257,7 +4257,7 @@ fn main() { --- ```rust - test::S + ra_test_fixture::S ``` ```rust @@ -4282,7 +4282,7 @@ struct S$0T<const C: usize = 1, T = Foo>(T); *ST* ```rust - test + ra_test_fixture ``` ```rust @@ -4307,7 +4307,7 @@ struct S$0T<const C: usize = {40 + 2}, T = Foo>(T); *ST* ```rust - test + ra_test_fixture ``` ```rust @@ -4333,7 +4333,7 @@ struct S$0T<const C: usize = VAL, T = Foo>(T); *ST* ```rust - test + ra_test_fixture ``` ```rust @@ -4527,21 +4527,21 @@ mod Foo$0 { } "#, expect![[r#" - *Foo* + *Foo* - ```rust - test - ``` + ```rust + ra_test_fixture + ``` - ```rust - mod Foo - ``` + ```rust + mod Foo + ``` - --- + --- - Be quick; - time is mana - "#]], + Be quick; + time is mana + "#]], ); } @@ -4558,21 +4558,21 @@ mod Foo$0 { } "#, expect![[r#" - *Foo* + *Foo* - ```rust - test - ``` + ```rust + ra_test_fixture + ``` - ```rust - mod Foo - ``` + ```rust + mod Foo + ``` - --- + --- - Be quick; - time is mana - "#]], + Be quick; + time is mana + "#]], ); } @@ -4592,7 +4592,7 @@ fn foo$0() {} *foo* ```rust - test + ra_test_fixture ``` ```rust @@ -4902,7 +4902,7 @@ type Fo$0o2 = Foo<2>; *Foo2* ```rust - test + ra_test_fixture ``` ```rust @@ -4948,7 +4948,7 @@ enum E { *A* ```rust - test::E + ra_test_fixture::E ``` ```rust @@ -4977,7 +4977,7 @@ enum E { *A* ```rust - test::E + ra_test_fixture::E ``` ```rust @@ -5007,7 +5007,7 @@ enum E { *B* ```rust - test::E + ra_test_fixture::E ``` ```rust @@ -5037,7 +5037,7 @@ enum E { *B* ```rust - test::E + ra_test_fixture::E ``` ```rust @@ -5074,7 +5074,7 @@ fn main() { *B* ```rust - test + ra_test_fixture ``` ```rust @@ -5111,7 +5111,7 @@ fn main() { *AA* ```rust - test + ra_test_fixture ``` ```rust @@ -5138,7 +5138,7 @@ fn main() { *B* ```rust - test::T + ra_test_fixture::T ``` ```rust @@ -5167,7 +5167,7 @@ fn main() { *B* ```rust - test::T + ra_test_fixture::T ``` ```rust @@ -5199,7 +5199,7 @@ fn main() { *B* ```rust - test::T + ra_test_fixture::T ``` ```rust @@ -5222,7 +5222,7 @@ const FOO$0: usize = 1 << 3; *FOO* ```rust - test + ra_test_fixture ``` ```rust @@ -5243,7 +5243,7 @@ const FOO$0: usize = (1 << 3) + (1 << 2); *FOO* ```rust - test + ra_test_fixture ``` ```rust @@ -5265,7 +5265,7 @@ const FOO$0: usize = 2 - 3; *FOO* ```rust - test + ra_test_fixture ``` ```rust @@ -5287,7 +5287,7 @@ const FOO$0: i32 = 2 - 3; *FOO* ```rust - test + ra_test_fixture ``` ```rust @@ -5308,7 +5308,7 @@ const FOO$0: &str = "bar"; *FOO* ```rust - test + ra_test_fixture ``` ```rust @@ -5330,7 +5330,7 @@ const FOO$0: char = 'a'; *FOO* ```rust - test + ra_test_fixture ``` ```rust @@ -5352,7 +5352,7 @@ const FOO$0: char = '\x61'; *FOO* ```rust - test + ra_test_fixture ``` ```rust @@ -5374,7 +5374,7 @@ const FOO$0: u8 = b'a'; *FOO* ```rust - test + ra_test_fixture ``` ```rust @@ -5396,7 +5396,7 @@ const FOO$0: u8 = b'\x61'; *FOO* ```rust - test + ra_test_fixture ``` ```rust @@ -5418,7 +5418,7 @@ const FOO$0: u8 = b'\x61'; *FOO* ```rust - test + ra_test_fixture ``` ```rust @@ -5440,7 +5440,7 @@ const FOO$0: f32 = 1f32; *FOO* ```rust - test + ra_test_fixture ``` ```rust @@ -5462,7 +5462,7 @@ const FOO$0: &i32 = &2; *FOO* ```rust - test + ra_test_fixture ``` ```rust @@ -5484,7 +5484,7 @@ const FOO$0: f64 = 1.0f64; *FOO* ```rust - test + ra_test_fixture ``` ```rust @@ -5512,7 +5512,7 @@ const FOO$0: f64 = expf64(1.2); *FOO* ```rust - test + ra_test_fixture ``` ```rust @@ -5530,7 +5530,7 @@ const FOO$0: f32 = 1.9999999403953552_f32; *FOO* ```rust - test + ra_test_fixture ``` ```rust @@ -5552,7 +5552,7 @@ const FOO$0: f16 = -1.0f16; *FOO* ```rust - test + ra_test_fixture ``` ```rust @@ -5573,7 +5573,7 @@ const FOO$0: f128 = -1.0f128; *FOO* ```rust - test + ra_test_fixture ``` ```rust @@ -5604,7 +5604,7 @@ const FOO$0: Enum = VX; *FOO* ```rust - test + ra_test_fixture ``` ```rust @@ -5621,7 +5621,7 @@ const FOO$0: Option<i32> = Some(2); *FOO* ```rust - test + ra_test_fixture ``` ```rust @@ -5638,7 +5638,7 @@ const FOO$0: Option<&i32> = Some(2).as_ref(); *FOO* ```rust - test + ra_test_fixture ``` ```rust @@ -5661,7 +5661,7 @@ const FOO$0: &dyn Debug = &2i32; *FOO* ```rust - test + ra_test_fixture ``` ```rust @@ -5682,7 +5682,7 @@ const FOO$0: &[i32] = &[1, 2, 3 + 4]; *FOO* ```rust - test + ra_test_fixture ``` ```rust @@ -5699,7 +5699,7 @@ const FOO$0: &[i32; 5] = &[12; 5]; *FOO* ```rust - test + ra_test_fixture ``` ```rust @@ -5720,7 +5720,7 @@ const FOO$0: (&i32, &[i32], &i32) = { *FOO* ```rust - test + ra_test_fixture ``` ```rust @@ -5743,7 +5743,7 @@ const FOO$0: Tree = { *FOO* ```rust - test + ra_test_fixture ``` ```rust @@ -5763,7 +5763,7 @@ const FOO$0: &S<[u8]> = core::mem::transmute::<&[u8], _>(&[1, 2, 3]); *FOO* ```rust - test + ra_test_fixture ``` ```rust @@ -5783,7 +5783,7 @@ const FOO$0: &str = "foo"; *FOO* ```rust - test + ra_test_fixture ``` ```rust @@ -5806,7 +5806,7 @@ const FOO$0: X = X { *FOO* ```rust - test + ra_test_fixture ``` ```rust @@ -5825,7 +5825,7 @@ const FOO$0: (&str, &str) = { *FOO* ```rust - test + ra_test_fixture ``` ```rust @@ -5856,7 +5856,7 @@ fn test() { *FOO* ```rust - test::S + ra_test_fixture::S ``` ```rust @@ -5883,7 +5883,7 @@ fn foo() { *FOO* ```rust - test + ra_test_fixture ``` ```rust @@ -5912,7 +5912,7 @@ fn foo(e: E) { *A* ```rust - test::E + ra_test_fixture::E ``` ```rust @@ -5942,7 +5942,7 @@ pub fn the_function() -> AA { *CONST* ```rust - test + ra_test_fixture ``` ```rust @@ -5984,20 +5984,20 @@ mod foo$0; //! For the horde! "#, expect![[r#" - *foo* + *foo* - ```rust - test - ``` + ```rust + ra_test_fixture + ``` - ```rust - mod foo - ``` + ```rust + mod foo + ``` - --- + --- - For the horde! - "#]], + For the horde! + "#]], ); } @@ -6013,20 +6013,20 @@ mod foo { use foo::bar::{self$0}; "#, expect![[r#" - *self* + *self* - ```rust - test::foo - ``` + ```rust + ra_test_fixture::foo + ``` - ```rust - mod bar - ``` + ```rust + mod bar + ``` - --- + --- - But this should appear - "#]], + But this should appear + "#]], ) } @@ -6166,7 +6166,7 @@ fn main() { *bar* ```rust - test + ra_test_fixture ``` ```rust @@ -6200,7 +6200,7 @@ pub fn gimme() -> theitem::TheItem { *[`TheItem`]* ```rust - test::theitem + ra_test_fixture::theitem ``` ```rust @@ -6248,7 +6248,7 @@ impl T1 for Foo { *Bar* ```rust - test::t2::T2 + ra_test_fixture::t2::T2 ``` ```rust @@ -6270,7 +6270,7 @@ trait A { *Assoc* ```rust - test::A + ra_test_fixture::A ``` ```rust @@ -6291,7 +6291,7 @@ trait A { *Assoc* ```rust - test::A + ra_test_fixture::A ``` ```rust @@ -6310,7 +6310,7 @@ trait A where *Assoc* ```rust - test::A + ra_test_fixture::A ``` ```rust @@ -6572,12 +6572,12 @@ fn hover_rename() { use self as foo$0; "#, expect![[r#" - *foo* + *foo* - ```rust - extern crate test - ``` - "#]], + ```rust + extern crate ra_test_fixture + ``` + "#]], ); check( r#" @@ -6585,16 +6585,16 @@ mod bar {} use bar::{self as foo$0}; "#, expect![[r#" - *foo* + *foo* - ```rust - test - ``` + ```rust + ra_test_fixture + ``` - ```rust - mod bar - ``` - "#]], + ```rust + mod bar + ``` + "#]], ); check( r#" @@ -6603,24 +6603,24 @@ mod bar { } "#, expect![[r#" - *foo* + *foo* - ```rust - extern crate test - ``` - "#]], + ```rust + extern crate ra_test_fixture + ``` + "#]], ); check( r#" use crate as foo$0; "#, expect![[r#" - *foo* + *foo* - ```rust - extern crate test - ``` - "#]], + ```rust + extern crate ra_test_fixture + ``` + "#]], ); } @@ -6645,7 +6645,7 @@ identity!{ *Copy* ```rust - test + ra_test_fixture ``` ```rust @@ -6669,7 +6669,7 @@ struct Foo; *Copy* ```rust - test + ra_test_fixture ``` ```rust @@ -6691,7 +6691,7 @@ struct Foo; *Copy* ```rust - test::foo + ra_test_fixture::foo ``` ```rust @@ -6910,7 +6910,7 @@ fn foo() { GoToType( [ HoverGotoTypeData { - mod_path: "test::Foo", + mod_path: "ra_test_fixture::Foo", nav: NavigationTarget { file_id: FileId( 0, @@ -7110,7 +7110,7 @@ foo_macro!( *[`Foo`]* ```rust - test + ra_test_fixture ``` ```rust @@ -7119,7 +7119,7 @@ foo_macro!( --- - Doc comment for [`Foo`](https://docs.rs/test/*/test/struct.Foo.html) + Doc comment for [`Foo`](https://docs.rs/ra_test_fixture/*/ra_test_fixture/struct.Foo.html) "#]], ); } @@ -7135,7 +7135,7 @@ pub struct Foo(i32); *[`Foo`]* ```rust - test + ra_test_fixture ``` ```rust @@ -7144,7 +7144,7 @@ pub struct Foo(i32); --- - Doc comment for [`Foo`](https://docs.rs/test/*/test/struct.Foo.html) + Doc comment for [`Foo`](https://docs.rs/ra_test_fixture/*/ra_test_fixture/struct.Foo.html) "#]], ); } @@ -7160,7 +7160,7 @@ pub struct Foo<T>(T); *[`Foo<T>`]* ```rust - test + ra_test_fixture ``` ```rust @@ -7169,7 +7169,7 @@ pub struct Foo<T>(T); --- - Doc comment for [`Foo<T>`](https://docs.rs/test/*/test/struct.Foo.html) + Doc comment for [`Foo<T>`](https://docs.rs/ra_test_fixture/*/ra_test_fixture/struct.Foo.html) "#]], ); } @@ -7259,7 +7259,7 @@ enum Enum { *RecordV* ```rust - test::Enum + ra_test_fixture::Enum ``` ```rust @@ -7285,7 +7285,7 @@ enum Enum { *field* ```rust - test::RecordV + ra_test_fixture::RecordV ``` ```rust @@ -7315,7 +7315,7 @@ impl T for () { *func* ```rust - test + ra_test_fixture ``` ```rust @@ -7341,7 +7341,7 @@ impl T$0 for () {} *T* ```rust - test + ra_test_fixture ``` ```rust @@ -7360,7 +7360,7 @@ impl T$0 for () {} *T* ```rust - test + ra_test_fixture ``` ```rust @@ -7383,7 +7383,7 @@ impl T$0 for () {} *T* ```rust - test + ra_test_fixture ``` ```rust @@ -7406,7 +7406,7 @@ impl T$0 for () {} *T* ```rust - test + ra_test_fixture ``` ```rust @@ -7433,7 +7433,7 @@ impl T$0 for () {} *T* ```rust - test + ra_test_fixture ``` ```rust @@ -7460,7 +7460,7 @@ impl T$0 for () {} *T* ```rust - test + ra_test_fixture ``` ```rust @@ -7529,7 +7529,7 @@ fn f() { *** ```rust - test::Struct + ra_test_fixture::Struct ``` ```rust @@ -7558,7 +7558,7 @@ fn main() { $0V; } *V* ```rust - test + ra_test_fixture ``` ```rust @@ -7585,7 +7585,7 @@ fn main() { $0V; } *V* ```rust - test + ra_test_fixture ``` ```rust @@ -7765,7 +7765,7 @@ fn test() { *foo* ```rust - test::S + ra_test_fixture::S ``` ```rust @@ -7794,7 +7794,7 @@ fn test() { *foo* ```rust - test::S + ra_test_fixture::S ``` ```rust @@ -7824,7 +7824,7 @@ mod m { *foo* ```rust - test::S + ra_test_fixture::S ``` ```rust @@ -7854,7 +7854,7 @@ fn test() { *A* ```rust - test::S + ra_test_fixture::S ``` ```rust @@ -7883,7 +7883,7 @@ fn test() { *A* ```rust - test::S + ra_test_fixture::S ``` ```rust @@ -7913,7 +7913,7 @@ mod m { *A* ```rust - test::S + ra_test_fixture::S ``` ```rust @@ -7936,7 +7936,7 @@ fn test() { *f* ```rust - test::S + ra_test_fixture::S ``` ```rust @@ -8091,7 +8091,7 @@ fn main() { *foo* ```rust - test::S + ra_test_fixture::S ``` ```rust @@ -8573,7 +8573,7 @@ impl Iterator for S { *S* ```rust - test + ra_test_fixture ``` ```rust @@ -8595,7 +8595,7 @@ extern "C" { *STATIC* ```rust - test::<extern> + ra_test_fixture::<extern> ``` ```rust @@ -8613,7 +8613,7 @@ extern "C" { *fun* ```rust - test::<extern> + ra_test_fixture::<extern> ``` ```rust @@ -8631,7 +8631,7 @@ extern "C" { *Ty* ```rust - test::<extern> + ra_test_fixture::<extern> ``` ```rust @@ -8731,7 +8731,7 @@ impl Iterator for S { }, }, HoverGotoTypeData { - mod_path: "test::Notable", + mod_path: "ra_test_fixture::Notable", nav: NavigationTarget { file_id: FileId( 0, @@ -8744,7 +8744,7 @@ impl Iterator for S { }, }, HoverGotoTypeData { - mod_path: "test::S2", + mod_path: "ra_test_fixture::S2", nav: NavigationTarget { file_id: FileId( 0, @@ -8775,7 +8775,7 @@ struct Pedro$0<'a> { *Pedro* ```rust - test + ra_test_fixture ``` ```rust @@ -8962,7 +8962,7 @@ fn test_hover_function_with_pat_param() { *test_1* ```rust - test + ra_test_fixture ``` ```rust @@ -8978,7 +8978,7 @@ fn test_hover_function_with_pat_param() { *test_2* ```rust - test + ra_test_fixture ``` ```rust @@ -8994,7 +8994,7 @@ fn test_hover_function_with_pat_param() { *test_3* ```rust - test + ra_test_fixture ``` ```rust @@ -9010,7 +9010,7 @@ fn test_hover_function_with_pat_param() { *test_4* ```rust - test + ra_test_fixture ``` ```rust @@ -9026,7 +9026,7 @@ fn test_hover_function_with_pat_param() { *test_5* ```rust - test + ra_test_fixture ``` ```rust @@ -9042,7 +9042,7 @@ fn test_hover_function_with_pat_param() { *test_6* ```rust - test + ra_test_fixture ``` ```rust @@ -9058,7 +9058,7 @@ fn test_hover_function_with_pat_param() { *test_7* ```rust - test + ra_test_fixture ``` ```rust @@ -9074,7 +9074,7 @@ fn test_hover_function_with_pat_param() { *test_8* ```rust - test + ra_test_fixture ``` ```rust @@ -9090,7 +9090,7 @@ fn test_hover_function_with_pat_param() { *test_9* ```rust - test + ra_test_fixture ``` ```rust @@ -9106,7 +9106,7 @@ fn test_hover_function_with_pat_param() { *test_10* ```rust - test + ra_test_fixture ``` ```rust @@ -9122,7 +9122,7 @@ fn test_hover_function_with_pat_param() { *test_10* ```rust - test + ra_test_fixture ``` ```rust @@ -9148,7 +9148,7 @@ mod m { *C* ```rust - test::m::m2 + ra_test_fixture::m::m2 ``` ```rust @@ -9173,17 +9173,17 @@ macro_rules! foo { foo!(BAR_$0); "#, expect![[r#" - *BAR_* + *BAR_* - ```rust - test - ``` + ```rust + ra_test_fixture + ``` - ```rust - pub static BAR_: {error} = Foo::new(||{ - crate; - }) - ``` + ```rust + pub static BAR_: {error} = Foo::new(||{ + crate; + }) + ``` "#]], ); } @@ -9202,7 +9202,7 @@ type A$0 = B; *A* ```rust - test + ra_test_fixture ``` ```rust @@ -9235,7 +9235,7 @@ type A$0 = B; *A* ```rust - test + ra_test_fixture ``` ```rust @@ -9269,7 +9269,7 @@ type A$0 = B; *A* ```rust - test + ra_test_fixture ``` ```rust @@ -9301,7 +9301,7 @@ type A$0 = B; *A* ```rust - test + ra_test_fixture ``` ```rust @@ -9362,7 +9362,7 @@ trait Compat$0 {} *Compat* ```rust - test + ra_test_fixture ``` ```rust @@ -9384,7 +9384,7 @@ trait UnCompat$0 { *UnCompat* ```rust - test + ra_test_fixture ``` ```rust @@ -9407,7 +9407,7 @@ fn f<T: UnCompat$0> *UnCompat* ```rust - test + ra_test_fixture ``` ```rust @@ -9416,3 +9416,53 @@ fn f<T: UnCompat$0> "#]], ); } + +#[test] +fn issue_18613() { + check( + r#" +fn main() { + struct S<T, D = bool>(); + let x$0 = S::<()>; +}"#, + expect![[r#" + *x* + + ```rust + let x: fn S<()>() -> S<()> + ``` + + --- + + size = 0, align = 1 + "#]], + ); + + check( + r#" +pub struct Global; +pub struct Box<T, A = Global>(T, A); + +impl<T> Box<T> { + pub fn new(x: T) -> Self { loop {} } +} + +pub struct String; + +fn main() { + let box_value$0 = Box::<String>new(); +} +"#, + expect![[r#" + *box_value* + + ```rust + let box_value: fn Box<String>(String, Global) -> Box<String> + ``` + + --- + + size = 0, align = 1 + "#]], + ); +} diff --git a/crates/ide/src/inlay_hints.rs b/crates/ide/src/inlay_hints.rs index c58ca0f01c..aa99ba49bc 100644 --- a/crates/ide/src/inlay_hints.rs +++ b/crates/ide/src/inlay_hints.rs @@ -8,8 +8,8 @@ use hir::{ sym, ClosureStyle, HasVisibility, HirDisplay, HirDisplayError, HirWrite, ModuleDef, ModuleDefId, Semantics, }; -use ide_db::text_edit::TextEdit; use ide_db::{famous_defs::FamousDefs, FileRange, RootDatabase}; +use ide_db::{text_edit::TextEdit, FxHashSet}; use itertools::Itertools; use smallvec::{smallvec, SmallVec}; use span::{Edition, EditionedFileId}; @@ -29,6 +29,7 @@ mod closing_brace; mod closure_captures; mod closure_ret; mod discriminant; +mod extern_block; mod generic_param; mod implicit_drop; mod implicit_static; @@ -116,6 +117,7 @@ pub(crate) fn inlay_hints( #[derive(Default)] struct InlayHintCtx { lifetime_stacks: Vec<Vec<SmolStr>>, + extern_block_parent: Option<ast::ExternBlock>, } pub(crate) fn inlay_hints_resolve( @@ -174,12 +176,18 @@ fn handle_event(ctx: &mut InlayHintCtx, node: WalkEvent<SyntaxNode>) -> Option<S .unwrap_or_default(); ctx.lifetime_stacks.push(params); } + if let Some(node) = ast::ExternBlock::cast(node.clone()) { + ctx.extern_block_parent = Some(node); + } Some(node) } WalkEvent::Leave(n) => { if ast::AnyHasGenericParams::can_cast(n.kind()) { ctx.lifetime_stacks.pop(); } + if ast::ExternBlock::can_cast(n.kind()) { + ctx.extern_block_parent = None; + } None } } @@ -234,12 +242,20 @@ fn hints( ast::Item(it) => match it { ast::Item::Fn(it) => { implicit_drop::hints(hints, famous_defs, config, file_id, &it); + if let Some(extern_block) = &ctx.extern_block_parent { + extern_block::fn_hints(hints, famous_defs, config, file_id, &it, extern_block); + } lifetime::fn_hints(hints, ctx, famous_defs, config, file_id, it) }, - // static type elisions - ast::Item::Static(it) => implicit_static::hints(hints, famous_defs, config, file_id, Either::Left(it)), + ast::Item::Static(it) => { + if let Some(extern_block) = &ctx.extern_block_parent { + extern_block::static_hints(hints, famous_defs, config, file_id, &it, extern_block); + } + implicit_static::hints(hints, famous_defs, config, file_id, Either::Left(it)) + }, ast::Item::Const(it) => implicit_static::hints(hints, famous_defs, config, file_id, Either::Right(it)), ast::Item::Enum(it) => discriminant::enum_hints(hints, famous_defs, config, file_id, it), + ast::Item::ExternBlock(it) => extern_block::extern_block_hints(hints, famous_defs, config, file_id, it), _ => None, }, // FIXME: trait object type elisions @@ -289,6 +305,16 @@ pub struct InlayFieldsToResolve { } impl InlayFieldsToResolve { + pub fn from_client_capabilities(client_capability_fields: &FxHashSet<&str>) -> Self { + Self { + resolve_text_edits: client_capability_fields.contains("textEdits"), + resolve_hint_tooltip: client_capability_fields.contains("tooltip"), + resolve_label_tooltip: client_capability_fields.contains("label.tooltip"), + resolve_label_location: client_capability_fields.contains("label.location"), + resolve_label_command: client_capability_fields.contains("label.command"), + } + } + pub const fn empty() -> Self { Self { resolve_text_edits: false, @@ -358,6 +384,7 @@ pub enum InlayKind { Type, Drop, RangeExclusive, + ExternUnsafety, } #[derive(Debug, Hash)] diff --git a/crates/ide/src/inlay_hints/extern_block.rs b/crates/ide/src/inlay_hints/extern_block.rs new file mode 100644 index 0000000000..4cc4925cda --- /dev/null +++ b/crates/ide/src/inlay_hints/extern_block.rs @@ -0,0 +1,138 @@ +//! Extern block hints +use ide_db::{famous_defs::FamousDefs, text_edit::TextEdit}; +use span::EditionedFileId; +use syntax::{ast, AstNode, SyntaxToken}; + +use crate::{InlayHint, InlayHintsConfig}; + +pub(super) fn extern_block_hints( + acc: &mut Vec<InlayHint>, + FamousDefs(_sema, _): &FamousDefs<'_, '_>, + _config: &InlayHintsConfig, + _file_id: EditionedFileId, + extern_block: ast::ExternBlock, +) -> Option<()> { + if extern_block.unsafe_token().is_some() { + return None; + } + let abi = extern_block.abi()?; + acc.push(InlayHint { + range: abi.syntax().text_range(), + position: crate::InlayHintPosition::Before, + pad_left: false, + pad_right: true, + kind: crate::InlayKind::ExternUnsafety, + label: crate::InlayHintLabel::from("unsafe"), + text_edit: Some(TextEdit::insert(abi.syntax().text_range().start(), "unsafe ".to_owned())), + resolve_parent: Some(extern_block.syntax().text_range()), + }); + Some(()) +} + +pub(super) fn fn_hints( + acc: &mut Vec<InlayHint>, + FamousDefs(_sema, _): &FamousDefs<'_, '_>, + _config: &InlayHintsConfig, + _file_id: EditionedFileId, + fn_: &ast::Fn, + extern_block: &ast::ExternBlock, +) -> Option<()> { + let implicit_unsafe = fn_.safe_token().is_none() && fn_.unsafe_token().is_none(); + if !implicit_unsafe { + return None; + } + let fn_ = fn_.fn_token()?; + acc.push(item_hint(extern_block, fn_)); + Some(()) +} + +pub(super) fn static_hints( + acc: &mut Vec<InlayHint>, + FamousDefs(_sema, _): &FamousDefs<'_, '_>, + _config: &InlayHintsConfig, + _file_id: EditionedFileId, + static_: &ast::Static, + extern_block: &ast::ExternBlock, +) -> Option<()> { + let implicit_unsafe = static_.safe_token().is_none() && static_.unsafe_token().is_none(); + if !implicit_unsafe { + return None; + } + let static_ = static_.static_token()?; + acc.push(item_hint(extern_block, static_)); + Some(()) +} + +fn item_hint(extern_block: &ast::ExternBlock, token: SyntaxToken) -> InlayHint { + InlayHint { + range: token.text_range(), + position: crate::InlayHintPosition::Before, + pad_left: false, + pad_right: true, + kind: crate::InlayKind::ExternUnsafety, + label: crate::InlayHintLabel::from("unsafe"), + text_edit: { + let mut builder = TextEdit::builder(); + builder.insert(token.text_range().start(), "unsafe ".to_owned()); + if extern_block.unsafe_token().is_none() { + if let Some(abi) = extern_block.abi() { + builder.insert(abi.syntax().text_range().start(), "unsafe ".to_owned()); + } + } + Some(builder.finish()) + }, + resolve_parent: Some(extern_block.syntax().text_range()), + } +} + +#[cfg(test)] +mod tests { + use crate::inlay_hints::tests::{check_with_config, DISABLED_CONFIG}; + + #[test] + fn unadorned() { + check_with_config( + DISABLED_CONFIG, + r#" + extern "C" { +//^^^^^^^^^^ unsafe + static FOO: (); + // ^^^^^^ unsafe + pub static FOO: (); + // ^^^^^^unsafe + unsafe static FOO: (); + safe static FOO: (); + fn foo(); + // ^^ unsafe + pub fn foo(); + // ^^ unsafe + unsafe fn foo(); + safe fn foo(); +} +"#, + ); + } + + #[test] + fn adorned() { + check_with_config( + DISABLED_CONFIG, + r#" +unsafe extern "C" { + static FOO: (); + // ^^^^^^ unsafe + pub static FOO: (); + // ^^^^^^unsafe + unsafe static FOO: (); + safe static FOO: (); + fn foo(); + // ^^ unsafe + pub fn foo(); + // ^^ unsafe + unsafe fn foo(); + safe fn foo(); +} +"#, + ); + } +} diff --git a/crates/ide/src/inlay_hints/lifetime.rs b/crates/ide/src/inlay_hints/lifetime.rs index 2163c959b1..1fdd698991 100644 --- a/crates/ide/src/inlay_hints/lifetime.rs +++ b/crates/ide/src/inlay_hints/lifetime.rs @@ -41,7 +41,15 @@ pub(super) fn fn_hints( fd, config, file_id, - param_list, + param_list.params().filter_map(|it| { + Some(( + it.pat().and_then(|it| match it { + ast::Pat::IdentPat(p) => p.name(), + _ => None, + }), + it.ty()?, + )) + }), generic_param_list, ret_type, self_param, @@ -90,7 +98,15 @@ pub(super) fn fn_ptr_hints( fd, config, file_id, - param_list, + param_list.params().filter_map(|it| { + Some(( + it.pat().and_then(|it| match it { + ast::Pat::IdentPat(p) => p.name(), + _ => None, + }), + it.ty()?, + )) + }), generic_param_list, ret_type, None, @@ -148,7 +164,7 @@ pub(super) fn fn_path_hints( fd, config, file_id, - param_list, + param_list.type_args().filter_map(|it| Some((None, it.ty()?))), generic_param_list, ret_type, None, @@ -177,8 +193,8 @@ pub(super) fn fn_path_hints( ) } -fn path_as_fn(path: &ast::Path) -> Option<(ast::ParamList, Option<ast::RetType>)> { - path.segment().and_then(|it| it.param_list().zip(Some(it.ret_type()))) +fn path_as_fn(path: &ast::Path) -> Option<(ast::ParenthesizedArgList, Option<ast::RetType>)> { + path.segment().and_then(|it| it.parenthesized_arg_list().zip(Some(it.ret_type()))) } fn hints_( @@ -187,7 +203,7 @@ fn hints_( FamousDefs(_, _): &FamousDefs<'_, '_>, config: &InlayHintsConfig, _file_id: EditionedFileId, - param_list: ast::ParamList, + params: impl Iterator<Item = (Option<ast::Name>, ast::Type)>, generic_param_list: Option<ast::GenericParamList>, ret_type: Option<ast::RetType>, self_param: Option<ast::SelfParam>, @@ -217,45 +233,34 @@ fn hints_( let is_elided = is_elided(&lifetime); acc.push((None, self_param.amp_token(), lifetime, is_elided)); } - param_list - .params() - .filter_map(|it| { - Some(( - it.pat().and_then(|it| match it { - ast::Pat::IdentPat(p) => p.name(), - _ => None, - }), - it.ty()?, - )) - }) - .for_each(|(name, ty)| { - // FIXME: check path types - walk_ty(&ty, &mut |ty| match ty { - ast::Type::RefType(r) => { - let lifetime = r.lifetime(); - let is_elided = is_elided(&lifetime); - acc.push((name.clone(), r.amp_token(), lifetime, is_elided)); - false - } - ast::Type::FnPtrType(_) => { + params.for_each(|(name, ty)| { + // FIXME: check path types + walk_ty(&ty, &mut |ty| match ty { + ast::Type::RefType(r) => { + let lifetime = r.lifetime(); + let is_elided = is_elided(&lifetime); + acc.push((name.clone(), r.amp_token(), lifetime, is_elided)); + false + } + ast::Type::FnPtrType(_) => { + is_trivial = false; + true + } + ast::Type::PathType(t) => { + if t.path() + .and_then(|it| it.segment()) + .and_then(|it| it.parenthesized_arg_list()) + .is_some() + { is_trivial = false; true + } else { + false } - ast::Type::PathType(t) => { - if t.path() - .and_then(|it| it.segment()) - .and_then(|it| it.param_list()) - .is_some() - { - is_trivial = false; - true - } else { - false - } - } - _ => false, - }) - }); + } + _ => false, + }) + }); acc }; @@ -339,7 +344,10 @@ fn hints_( true } ast::Type::PathType(t) => { - if t.path().and_then(|it| it.segment()).and_then(|it| it.param_list()).is_some() + if t.path() + .and_then(|it| it.segment()) + .and_then(|it| it.parenthesized_arg_list()) + .is_some() { is_trivial = false; true diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index d4ef9570e1..c960b88a3e 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs @@ -402,6 +402,8 @@ impl Analysis { self.with_db(|db| typing::on_enter(db, position)) } + pub const SUPPORTED_TRIGGER_CHARS: &'static str = typing::TRIGGER_CHARS; + /// Returns an edit which should be applied after a character was typed. /// /// This is useful for some on-the-fly fixups, like adding `;` to `let =` @@ -410,14 +412,16 @@ impl Analysis { &self, position: FilePosition, char_typed: char, - autoclose: bool, + chars_to_exclude: Option<String>, ) -> Cancellable<Option<SourceChange>> { // Fast path to not even parse the file. if !typing::TRIGGER_CHARS.contains(char_typed) { return Ok(None); } - if char_typed == '<' && !autoclose { - return Ok(None); + if let Some(chars_to_exclude) = chars_to_exclude { + if chars_to_exclude.contains(char_typed) { + return Ok(None); + } } self.with_db(|db| typing::on_char_typed(db, position, char_typed)) diff --git a/crates/ide/src/rename.rs b/crates/ide/src/rename.rs index 665fc954d2..a9519c03b3 100644 --- a/crates/ide/src/rename.rs +++ b/crates/ide/src/rename.rs @@ -3105,4 +3105,73 @@ fn main() { let _: Baz; } "#, ); } + + #[test] + fn rename_type_param_ref_in_use_bound() { + check( + "U", + r#" +fn foo<T>() -> impl use<T$0> Trait {} +"#, + r#" +fn foo<U>() -> impl use<U> Trait {} +"#, + ); + } + + #[test] + fn rename_type_param_in_use_bound() { + check( + "U", + r#" +fn foo<T$0>() -> impl use<T> Trait {} +"#, + r#" +fn foo<U>() -> impl use<U> Trait {} +"#, + ); + } + + #[test] + fn rename_lifetime_param_ref_in_use_bound() { + check( + "u", + r#" +fn foo<'t>() -> impl use<'t$0> Trait {} +"#, + r#" +fn foo<'u>() -> impl use<'u> Trait {} +"#, + ); + } + + #[test] + fn rename_lifetime_param_in_use_bound() { + check( + "u", + r#" +fn foo<'t$0>() -> impl use<'t> Trait {} +"#, + r#" +fn foo<'u>() -> impl use<'u> Trait {} +"#, + ); + } + + #[test] + fn rename_parent_type_param_in_use_bound() { + check( + "U", + r#" +trait Trait<T> { + fn foo() -> impl use<T$0> Trait {} +} +"#, + r#" +trait Trait<U> { + fn foo() -> impl use<U> Trait {} +} +"#, + ); + } } diff --git a/crates/ide/src/syntax_highlighting/highlight.rs b/crates/ide/src/syntax_highlighting/highlight.rs index 96375937a1..3767a3917c 100644 --- a/crates/ide/src/syntax_highlighting/highlight.rs +++ b/crates/ide/src/syntax_highlighting/highlight.rs @@ -151,6 +151,14 @@ fn punctuation( T!['['] | T![']'] => HlPunct::Bracket, T!['{'] | T!['}'] => HlPunct::Brace, T!['('] | T![')'] => HlPunct::Parenthesis, + T![>] + if parent + .as_ref() + .and_then(SyntaxNode::parent) + .map_or(false, |it| it.kind() == MACRO_RULES) => + { + return HlOperator::Other.into() + } T![<] | T![>] => HlPunct::Angle, T![,] => HlPunct::Comma, T![:] => HlPunct::Colon, diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_block_mod_items.html b/crates/ide/src/syntax_highlighting/test_data/highlight_block_mod_items.html index 70f2d7203e..edd9639768 100644 --- a/crates/ide/src/syntax_highlighting/test_data/highlight_block_mod_items.html +++ b/crates/ide/src/syntax_highlighting/test_data/highlight_block_mod_items.html @@ -46,7 +46,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd .unresolved_reference { color: #FC5555; text-decoration: wavy underline; } </style> <pre><code><span class="keyword">macro_rules</span><span class="macro_bang">!</span> <span class="macro declaration">foo</span> <span class="brace">{</span> - <span class="parenthesis">(</span><span class="punctuation">$</span>foo<span class="colon">:</span>ident<span class="parenthesis">)</span> <span class="operator">=</span><span class="angle">></span> <span class="brace">{</span> + <span class="parenthesis">(</span><span class="punctuation">$</span>foo<span class="colon">:</span>ident<span class="parenthesis">)</span> <span class="operator">=</span><span class="operator">></span> <span class="brace">{</span> <span class="keyword">mod</span> y <span class="brace">{</span> <span class="keyword">pub</span> <span class="keyword">struct</span> <span class="punctuation">$</span>foo<span class="semicolon">;</span> <span class="brace">}</span> diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_const.html b/crates/ide/src/syntax_highlighting/test_data/highlight_const.html index a14f2cc88c..05289cfe3f 100644 --- a/crates/ide/src/syntax_highlighting/test_data/highlight_const.html +++ b/crates/ide/src/syntax_highlighting/test_data/highlight_const.html @@ -46,7 +46,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd .unresolved_reference { color: #FC5555; text-decoration: wavy underline; } </style> <pre><code><span class="keyword">macro_rules</span><span class="macro_bang">!</span> <span class="macro declaration">id</span> <span class="brace">{</span> - <span class="parenthesis">(</span><span class="punctuation">$</span><span class="parenthesis">(</span><span class="punctuation">$</span>tt<span class="colon">:</span>tt<span class="parenthesis">)</span><span class="punctuation">*</span><span class="parenthesis">)</span> <span class="operator">=</span><span class="angle">></span> <span class="brace">{</span> + <span class="parenthesis">(</span><span class="punctuation">$</span><span class="parenthesis">(</span><span class="punctuation">$</span>tt<span class="colon">:</span>tt<span class="parenthesis">)</span><span class="punctuation">*</span><span class="parenthesis">)</span> <span class="operator">=</span><span class="operator">></span> <span class="brace">{</span> <span class="punctuation">$</span><span class="parenthesis">(</span><span class="punctuation">$</span>tt<span class="parenthesis">)</span><span class="punctuation">*</span> <span class="brace">}</span><span class="semicolon">;</span> <span class="brace">}</span> @@ -79,7 +79,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd <span class="brace">}</span> <span class="keyword">macro_rules</span><span class="macro_bang">!</span> <span class="macro declaration">unsafe_deref</span> <span class="brace">{</span> - <span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="operator">=</span><span class="angle">></span> <span class="brace">{</span> + <span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="operator">=</span><span class="operator">></span> <span class="brace">{</span> <span class="punctuation">*</span><span class="parenthesis">(</span><span class="punctuation">&</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="keyword">as</span> <span class="punctuation">*</span><span class="keyword">const</span> <span class="parenthesis">(</span><span class="parenthesis">)</span><span class="parenthesis">)</span> <span class="brace">}</span><span class="semicolon">;</span> <span class="brace">}</span></code></pre>
\ No newline at end of file diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html b/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html index 35650bbe87..aa9d23250c 100644 --- a/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html +++ b/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html @@ -147,11 +147,11 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd <span class="brace">}</span> <span class="comment documentation">/// ```</span> -<span class="comment documentation">///</span><span class="comment documentation"> </span><span class="keyword injected">macro_rules</span><span class="macro_bang injected">!</span><span class="none injected"> </span><span class="macro declaration injected">noop</span><span class="none injected"> </span><span class="brace injected">{</span><span class="none injected"> </span><span class="parenthesis injected">(</span><span class="punctuation injected">$</span><span class="none injected">expr</span><span class="colon injected">:</span><span class="none injected">expr</span><span class="parenthesis injected">)</span><span class="none injected"> </span><span class="operator injected">=</span><span class="angle injected">></span><span class="none injected"> </span><span class="brace injected">{</span><span class="none injected"> </span><span class="punctuation injected">$</span><span class="none injected">expr </span><span class="brace injected">}</span><span class="brace injected">}</span> +<span class="comment documentation">///</span><span class="comment documentation"> </span><span class="keyword injected">macro_rules</span><span class="macro_bang injected">!</span><span class="none injected"> </span><span class="macro declaration injected">noop</span><span class="none injected"> </span><span class="brace injected">{</span><span class="none injected"> </span><span class="parenthesis injected">(</span><span class="punctuation injected">$</span><span class="none injected">expr</span><span class="colon injected">:</span><span class="none injected">expr</span><span class="parenthesis injected">)</span><span class="none injected"> </span><span class="operator injected">=</span><span class="operator injected">></span><span class="none injected"> </span><span class="brace injected">{</span><span class="none injected"> </span><span class="punctuation injected">$</span><span class="none injected">expr </span><span class="brace injected">}</span><span class="brace injected">}</span> <span class="comment documentation">///</span><span class="comment documentation"> </span><span class="macro injected">noop</span><span class="macro_bang injected">!</span><span class="parenthesis injected macro">(</span><span class="numeric_literal injected macro">1</span><span class="parenthesis injected macro">)</span><span class="semicolon injected">;</span> <span class="comment documentation">/// ```</span> <span class="keyword">macro_rules</span><span class="macro_bang">!</span> <span class="macro declaration">noop</span> <span class="brace">{</span> - <span class="parenthesis">(</span><span class="punctuation">$</span>expr<span class="colon">:</span>expr<span class="parenthesis">)</span> <span class="operator">=</span><span class="angle">></span> <span class="brace">{</span> + <span class="parenthesis">(</span><span class="punctuation">$</span>expr<span class="colon">:</span>expr<span class="parenthesis">)</span> <span class="operator">=</span><span class="operator">></span> <span class="brace">{</span> <span class="punctuation">$</span>expr <span class="brace">}</span> <span class="brace">}</span> diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_keywords_2015.html b/crates/ide/src/syntax_highlighting/test_data/highlight_keywords_2015.html index a790b38578..c2bf94fd9b 100644 --- a/crates/ide/src/syntax_highlighting/test_data/highlight_keywords_2015.html +++ b/crates/ide/src/syntax_highlighting/test_data/highlight_keywords_2015.html @@ -54,7 +54,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd <span class="brace">}</span> <span class="keyword">macro_rules</span><span class="macro_bang">!</span> <span class="macro declaration">void</span> <span class="brace">{</span> - <span class="parenthesis">(</span><span class="punctuation">$</span><span class="parenthesis">(</span><span class="punctuation">$</span>tt<span class="colon">:</span>tt<span class="parenthesis">)</span><span class="punctuation">*</span><span class="parenthesis">)</span> <span class="operator">=</span><span class="angle">></span> <span class="brace">{</span><span class="brace">}</span> + <span class="parenthesis">(</span><span class="punctuation">$</span><span class="parenthesis">(</span><span class="punctuation">$</span>tt<span class="colon">:</span>tt<span class="parenthesis">)</span><span class="punctuation">*</span><span class="parenthesis">)</span> <span class="operator">=</span><span class="operator">></span> <span class="brace">{</span><span class="brace">}</span> <span class="brace">}</span> <span class="keyword">struct</span> <span class="struct declaration">__</span> <span class="keyword">where</span> <span class="self_type_keyword">Self</span><span class="colon">:</span><span class="semicolon">;</span> diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_keywords_2018.html b/crates/ide/src/syntax_highlighting/test_data/highlight_keywords_2018.html index 6dac066bfa..a30d16d532 100644 --- a/crates/ide/src/syntax_highlighting/test_data/highlight_keywords_2018.html +++ b/crates/ide/src/syntax_highlighting/test_data/highlight_keywords_2018.html @@ -54,7 +54,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd <span class="brace">}</span> <span class="keyword">macro_rules</span><span class="macro_bang">!</span> <span class="macro declaration">void</span> <span class="brace">{</span> - <span class="parenthesis">(</span><span class="punctuation">$</span><span class="parenthesis">(</span><span class="punctuation">$</span>tt<span class="colon">:</span>tt<span class="parenthesis">)</span><span class="punctuation">*</span><span class="parenthesis">)</span> <span class="operator">=</span><span class="angle">></span> <span class="brace">{</span><span class="brace">}</span> + <span class="parenthesis">(</span><span class="punctuation">$</span><span class="parenthesis">(</span><span class="punctuation">$</span>tt<span class="colon">:</span>tt<span class="parenthesis">)</span><span class="punctuation">*</span><span class="parenthesis">)</span> <span class="operator">=</span><span class="operator">></span> <span class="brace">{</span><span class="brace">}</span> <span class="brace">}</span> <span class="keyword">struct</span> <span class="struct declaration">__</span> <span class="keyword">where</span> <span class="self_type_keyword">Self</span><span class="colon">:</span><span class="semicolon">;</span> diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_keywords_2021.html b/crates/ide/src/syntax_highlighting/test_data/highlight_keywords_2021.html index 6dac066bfa..a30d16d532 100644 --- a/crates/ide/src/syntax_highlighting/test_data/highlight_keywords_2021.html +++ b/crates/ide/src/syntax_highlighting/test_data/highlight_keywords_2021.html @@ -54,7 +54,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd <span class="brace">}</span> <span class="keyword">macro_rules</span><span class="macro_bang">!</span> <span class="macro declaration">void</span> <span class="brace">{</span> - <span class="parenthesis">(</span><span class="punctuation">$</span><span class="parenthesis">(</span><span class="punctuation">$</span>tt<span class="colon">:</span>tt<span class="parenthesis">)</span><span class="punctuation">*</span><span class="parenthesis">)</span> <span class="operator">=</span><span class="angle">></span> <span class="brace">{</span><span class="brace">}</span> + <span class="parenthesis">(</span><span class="punctuation">$</span><span class="parenthesis">(</span><span class="punctuation">$</span>tt<span class="colon">:</span>tt<span class="parenthesis">)</span><span class="punctuation">*</span><span class="parenthesis">)</span> <span class="operator">=</span><span class="operator">></span> <span class="brace">{</span><span class="brace">}</span> <span class="brace">}</span> <span class="keyword">struct</span> <span class="struct declaration">__</span> <span class="keyword">where</span> <span class="self_type_keyword">Self</span><span class="colon">:</span><span class="semicolon">;</span> diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_keywords_2024.html b/crates/ide/src/syntax_highlighting/test_data/highlight_keywords_2024.html index 4ccc407990..b82a3f9f81 100644 --- a/crates/ide/src/syntax_highlighting/test_data/highlight_keywords_2024.html +++ b/crates/ide/src/syntax_highlighting/test_data/highlight_keywords_2024.html @@ -54,7 +54,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd <span class="brace">}</span> <span class="keyword">macro_rules</span><span class="macro_bang">!</span> <span class="macro declaration">void</span> <span class="brace">{</span> - <span class="parenthesis">(</span><span class="punctuation">$</span><span class="parenthesis">(</span><span class="punctuation">$</span>tt<span class="colon">:</span>tt<span class="parenthesis">)</span><span class="punctuation">*</span><span class="parenthesis">)</span> <span class="operator">=</span><span class="angle">></span> <span class="brace">{</span><span class="brace">}</span> + <span class="parenthesis">(</span><span class="punctuation">$</span><span class="parenthesis">(</span><span class="punctuation">$</span>tt<span class="colon">:</span>tt<span class="parenthesis">)</span><span class="punctuation">*</span><span class="parenthesis">)</span> <span class="operator">=</span><span class="operator">></span> <span class="brace">{</span><span class="brace">}</span> <span class="brace">}</span> <span class="keyword">struct</span> <span class="struct declaration">__</span> <span class="keyword">where</span> <span class="self_type_keyword">Self</span><span class="colon">:</span><span class="semicolon">;</span> 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 196552020a..06673d1a73 100644 --- a/crates/ide/src/syntax_highlighting/test_data/highlight_macros.html +++ b/crates/ide/src/syntax_highlighting/test_data/highlight_macros.html @@ -54,7 +54,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd <span class="brace macro proc_macro">}</span> <span class="struct declaration macro proc_macro">Foo</span> <span class="keyword macro proc_macro">struct</span> <span class="brace macro proc_macro">}</span> <span class="keyword">macro_rules</span><span class="macro_bang">!</span> <span class="macro declaration">def_fn</span> <span class="brace">{</span> - <span class="parenthesis">(</span><span class="punctuation">$</span><span class="parenthesis">(</span><span class="punctuation">$</span>tt<span class="colon">:</span>tt<span class="parenthesis">)</span><span class="punctuation">*</span><span class="parenthesis">)</span> <span class="operator">=</span><span class="angle">></span> <span class="brace">{</span><span class="punctuation">$</span><span class="parenthesis">(</span><span class="punctuation">$</span>tt<span class="parenthesis">)</span><span class="punctuation">*</span><span class="brace">}</span> + <span class="parenthesis">(</span><span class="punctuation">$</span><span class="parenthesis">(</span><span class="punctuation">$</span>tt<span class="colon">:</span>tt<span class="parenthesis">)</span><span class="punctuation">*</span><span class="parenthesis">)</span> <span class="operator">=</span><span class="operator">></span> <span class="brace">{</span><span class="punctuation">$</span><span class="parenthesis">(</span><span class="punctuation">$</span>tt<span class="parenthesis">)</span><span class="punctuation">*</span><span class="brace">}</span> <span class="brace">}</span> <span class="macro">def_fn</span><span class="macro_bang">!</span> <span class="brace macro">{</span> @@ -64,24 +64,24 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd <span class="brace macro">}</span> <span class="keyword">macro_rules</span><span class="macro_bang">!</span> <span class="macro declaration">dont_color_me_braces</span> <span class="brace">{</span> - <span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="operator">=</span><span class="angle">></span> <span class="brace">{</span><span class="numeric_literal">0</span><span class="brace">}</span> + <span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="operator">=</span><span class="operator">></span> <span class="brace">{</span><span class="numeric_literal">0</span><span class="brace">}</span> <span class="brace">}</span> <span class="keyword">macro_rules</span><span class="macro_bang">!</span> <span class="macro declaration">noop</span> <span class="brace">{</span> - <span class="parenthesis">(</span><span class="punctuation">$</span>expr<span class="colon">:</span>expr<span class="parenthesis">)</span> <span class="operator">=</span><span class="angle">></span> <span class="brace">{</span> + <span class="parenthesis">(</span><span class="punctuation">$</span>expr<span class="colon">:</span>expr<span class="parenthesis">)</span> <span class="operator">=</span><span class="operator">></span> <span class="brace">{</span> <span class="punctuation">$</span>expr <span class="brace">}</span> <span class="brace">}</span> <span class="comment documentation">/// textually shadow previous definition</span> <span class="keyword">macro_rules</span><span class="macro_bang">!</span> <span class="macro declaration">noop</span> <span class="brace">{</span> - <span class="parenthesis">(</span><span class="punctuation">$</span>expr<span class="colon">:</span>expr<span class="parenthesis">)</span> <span class="operator">=</span><span class="angle">></span> <span class="brace">{</span> + <span class="parenthesis">(</span><span class="punctuation">$</span>expr<span class="colon">:</span>expr<span class="parenthesis">)</span> <span class="operator">=</span><span class="operator">></span> <span class="brace">{</span> <span class="punctuation">$</span>expr <span class="brace">}</span> <span class="brace">}</span> <span class="keyword">macro_rules</span><span class="macro_bang">!</span> <span class="macro declaration">keyword_frag</span> <span class="brace">{</span> - <span class="parenthesis">(</span><span class="punctuation">$</span>type<span class="colon">:</span>ty<span class="parenthesis">)</span> <span class="operator">=</span><span class="angle">></span> <span class="parenthesis">(</span><span class="punctuation">$</span>type<span class="parenthesis">)</span> + <span class="parenthesis">(</span><span class="punctuation">$</span>type<span class="colon">:</span>ty<span class="parenthesis">)</span> <span class="operator">=</span><span class="operator">></span> <span class="parenthesis">(</span><span class="punctuation">$</span>type<span class="parenthesis">)</span> <span class="brace">}</span> <span class="keyword">macro</span> <span class="macro declaration">with_args</span><span class="parenthesis">(</span><span class="punctuation">$</span>i<span class="colon">:</span>ident<span class="parenthesis">)</span> <span class="brace">{</span> @@ -95,7 +95,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd <span class="brace">}</span> <span class="keyword">macro_rules</span><span class="macro_bang">!</span> <span class="macro declaration">id</span> <span class="brace">{</span> - <span class="parenthesis">(</span><span class="punctuation">$</span><span class="parenthesis">(</span><span class="punctuation">$</span>tt<span class="colon">:</span>tt<span class="parenthesis">)</span><span class="punctuation">*</span><span class="parenthesis">)</span> <span class="operator">=</span><span class="angle">></span> <span class="brace">{</span> + <span class="parenthesis">(</span><span class="punctuation">$</span><span class="parenthesis">(</span><span class="punctuation">$</span>tt<span class="colon">:</span>tt<span class="parenthesis">)</span><span class="punctuation">*</span><span class="parenthesis">)</span> <span class="operator">=</span><span class="operator">></span> <span class="brace">{</span> <span class="punctuation">$</span><span class="parenthesis">(</span><span class="punctuation">$</span>tt<span class="parenthesis">)</span><span class="punctuation">*</span> <span class="brace">}</span><span class="semicolon">;</span> <span class="brace">}</span> diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html b/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html index 5594a36e73..1385ae0510 100644 --- a/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html +++ b/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html @@ -46,7 +46,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd .unresolved_reference { color: #FC5555; text-decoration: wavy underline; } </style> <pre><code><span class="keyword">macro_rules</span><span class="macro_bang">!</span> <span class="macro declaration">println</span> <span class="brace">{</span> - <span class="parenthesis">(</span><span class="punctuation">$</span><span class="parenthesis">(</span><span class="punctuation">$</span>arg<span class="colon">:</span>tt<span class="parenthesis">)</span><span class="punctuation">*</span><span class="parenthesis">)</span> <span class="operator">=</span><span class="angle">></span> <span class="parenthesis">(</span><span class="brace">{</span> + <span class="parenthesis">(</span><span class="punctuation">$</span><span class="parenthesis">(</span><span class="punctuation">$</span>arg<span class="colon">:</span>tt<span class="parenthesis">)</span><span class="punctuation">*</span><span class="parenthesis">)</span> <span class="operator">=</span><span class="operator">></span> <span class="parenthesis">(</span><span class="brace">{</span> <span class="punctuation">$</span>crate<span class="colon">:</span><span class="colon">:</span>io<span class="colon">:</span><span class="colon">:</span>_print<span class="parenthesis">(</span>format_args_nl<span class="punctuation">!</span><span class="parenthesis">(</span><span class="punctuation">$</span><span class="parenthesis">(</span><span class="punctuation">$</span>arg<span class="parenthesis">)</span><span class="punctuation">*</span><span class="parenthesis">)</span><span class="parenthesis">)</span><span class="semicolon">;</span> <span class="brace">}</span><span class="parenthesis">)</span> <span class="brace">}</span> @@ -74,12 +74,12 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd <span class="brace">}</span> <span class="keyword">macro_rules</span><span class="macro_bang">!</span> <span class="macro declaration">toho</span> <span class="brace">{</span> - <span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="operator">=</span><span class="angle">></span> <span class="parenthesis">(</span><span class="punctuation">$</span>crate<span class="colon">:</span><span class="colon">:</span>panic<span class="punctuation">!</span><span class="parenthesis">(</span><span class="string_literal">"not yet implemented"</span><span class="parenthesis">)</span><span class="parenthesis">)</span><span class="semicolon">;</span> - <span class="parenthesis">(</span><span class="punctuation">$</span><span class="parenthesis">(</span><span class="punctuation">$</span>arg<span class="colon">:</span>tt<span class="parenthesis">)</span><span class="punctuation">+</span><span class="parenthesis">)</span> <span class="operator">=</span><span class="angle">></span> <span class="parenthesis">(</span><span class="punctuation">$</span>crate<span class="colon">:</span><span class="colon">:</span>panic<span class="punctuation">!</span><span class="parenthesis">(</span><span class="string_literal">"not yet implemented: {}"</span><span class="comma">,</span> format_args<span class="punctuation">!</span><span class="parenthesis">(</span><span class="punctuation">$</span><span class="parenthesis">(</span><span class="punctuation">$</span>arg<span class="parenthesis">)</span><span class="punctuation">+</span><span class="parenthesis">)</span><span class="parenthesis">)</span><span class="parenthesis">)</span><span class="semicolon">;</span> + <span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="operator">=</span><span class="operator">></span> <span class="parenthesis">(</span><span class="punctuation">$</span>crate<span class="colon">:</span><span class="colon">:</span>panic<span class="punctuation">!</span><span class="parenthesis">(</span><span class="string_literal">"not yet implemented"</span><span class="parenthesis">)</span><span class="parenthesis">)</span><span class="semicolon">;</span> + <span class="parenthesis">(</span><span class="punctuation">$</span><span class="parenthesis">(</span><span class="punctuation">$</span>arg<span class="colon">:</span>tt<span class="parenthesis">)</span><span class="punctuation">+</span><span class="parenthesis">)</span> <span class="operator">=</span><span class="operator">></span> <span class="parenthesis">(</span><span class="punctuation">$</span>crate<span class="colon">:</span><span class="colon">:</span>panic<span class="punctuation">!</span><span class="parenthesis">(</span><span class="string_literal">"not yet implemented: {}"</span><span class="comma">,</span> format_args<span class="punctuation">!</span><span class="parenthesis">(</span><span class="punctuation">$</span><span class="parenthesis">(</span><span class="punctuation">$</span>arg<span class="parenthesis">)</span><span class="punctuation">+</span><span class="parenthesis">)</span><span class="parenthesis">)</span><span class="parenthesis">)</span><span class="semicolon">;</span> <span class="brace">}</span> <span class="keyword">macro_rules</span><span class="macro_bang">!</span> <span class="macro declaration">reuse_twice</span> <span class="brace">{</span> - <span class="parenthesis">(</span><span class="punctuation">$</span>literal<span class="colon">:</span>literal<span class="parenthesis">)</span> <span class="operator">=</span><span class="angle">></span> <span class="brace">{</span><span class="brace">{</span>stringify<span class="punctuation">!</span><span class="parenthesis">(</span><span class="punctuation">$</span>literal<span class="parenthesis">)</span><span class="semicolon">;</span> format_args<span class="punctuation">!</span><span class="parenthesis">(</span><span class="punctuation">$</span>literal<span class="parenthesis">)</span><span class="brace">}</span><span class="brace">}</span><span class="semicolon">;</span> + <span class="parenthesis">(</span><span class="punctuation">$</span>literal<span class="colon">:</span>literal<span class="parenthesis">)</span> <span class="operator">=</span><span class="operator">></span> <span class="brace">{</span><span class="brace">{</span>stringify<span class="punctuation">!</span><span class="parenthesis">(</span><span class="punctuation">$</span>literal<span class="parenthesis">)</span><span class="semicolon">;</span> format_args<span class="punctuation">!</span><span class="parenthesis">(</span><span class="punctuation">$</span>literal<span class="parenthesis">)</span><span class="brace">}</span><span class="brace">}</span><span class="semicolon">;</span> <span class="brace">}</span> <span class="keyword">fn</span> <span class="function declaration">main</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span> diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html b/crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html index be6176894b..4e69c82f3d 100644 --- a/crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html +++ b/crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html @@ -46,12 +46,12 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd .unresolved_reference { color: #FC5555; text-decoration: wavy underline; } </style> <pre><code><span class="keyword">macro_rules</span><span class="macro_bang">!</span> <span class="macro declaration">id</span> <span class="brace">{</span> - <span class="parenthesis">(</span><span class="punctuation">$</span><span class="parenthesis">(</span><span class="punctuation">$</span>tt<span class="colon">:</span>tt<span class="parenthesis">)</span><span class="punctuation">*</span><span class="parenthesis">)</span> <span class="operator">=</span><span class="angle">></span> <span class="brace">{</span> + <span class="parenthesis">(</span><span class="punctuation">$</span><span class="parenthesis">(</span><span class="punctuation">$</span>tt<span class="colon">:</span>tt<span class="parenthesis">)</span><span class="punctuation">*</span><span class="parenthesis">)</span> <span class="operator">=</span><span class="operator">></span> <span class="brace">{</span> <span class="punctuation">$</span><span class="parenthesis">(</span><span class="punctuation">$</span>tt<span class="parenthesis">)</span><span class="punctuation">*</span> <span class="brace">}</span><span class="semicolon">;</span> <span class="brace">}</span> <span class="keyword">macro_rules</span><span class="macro_bang">!</span> <span class="macro declaration">unsafe_deref</span> <span class="brace">{</span> - <span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="operator">=</span><span class="angle">></span> <span class="brace">{</span> + <span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="operator">=</span><span class="operator">></span> <span class="brace">{</span> <span class="punctuation">*</span><span class="parenthesis">(</span><span class="punctuation">&</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="keyword">as</span> <span class="punctuation">*</span><span class="keyword">const</span> <span class="parenthesis">(</span><span class="parenthesis">)</span><span class="parenthesis">)</span> <span class="brace">}</span><span class="semicolon">;</span> <span class="brace">}</span> diff --git a/crates/ide/src/typing.rs b/crates/ide/src/typing.rs index 9bb5de9f2e..3d9146cc4c 100644 --- a/crates/ide/src/typing.rs +++ b/crates/ide/src/typing.rs @@ -15,12 +15,14 @@ mod on_enter; +use std::iter; + use ide_db::{base_db::SourceDatabase, FilePosition, RootDatabase}; -use span::EditionedFileId; +use span::{Edition, EditionedFileId}; use syntax::{ algo::{ancestors_at_offset, find_node_at_offset}, ast::{self, edit::IndentLevel, AstToken}, - AstNode, Parse, SourceFile, SyntaxKind, TextRange, TextSize, T, + AstNode, Parse, SourceFile, SyntaxKind, TextRange, TextSize, }; use ide_db::text_edit::TextEdit; @@ -30,7 +32,7 @@ use crate::SourceChange; pub(crate) use on_enter::on_enter; // Don't forget to add new trigger characters to `server_capabilities` in `caps.rs`. -pub(crate) const TRIGGER_CHARS: &str = ".=<>{("; +pub(crate) const TRIGGER_CHARS: &str = ".=<>{(|"; struct ExtendedTextEdit { edit: TextEdit, @@ -47,6 +49,7 @@ struct ExtendedTextEdit { // - typing `.` in a chain method call auto-indents // - typing `{` or `(` in front of an expression inserts a closing `}` or `)` after the expression // - typing `{` in a use item adds a closing `}` in the right place +// - typing `>` to complete a return type `->` will insert a whitespace after it // // VS Code:: // @@ -66,55 +69,67 @@ pub(crate) fn on_char_typed( if !stdx::always!(TRIGGER_CHARS.contains(char_typed)) { return None; } - let file = &db.parse(EditionedFileId::current_edition(position.file_id)); - if !stdx::always!(file.tree().syntax().text().char_at(position.offset) == Some(char_typed)) { + // FIXME: We need to figure out the edition of the file here, but that means hitting the + // database for more than just parsing the file which is bad. + // FIXME: We are hitting the database here, if we are unlucky this call might block momentarily + // causing the editor to feel sluggish! + let edition = Edition::CURRENT_FIXME; + let file = &db.parse(EditionedFileId::new(position.file_id, edition)); + let char_matches_position = + file.tree().syntax().text().char_at(position.offset) == Some(char_typed); + if !stdx::always!(char_matches_position) { return None; } - let edit = on_char_typed_inner(file, position.offset, char_typed)?; + + let edit = on_char_typed_(file, position.offset, char_typed, edition)?; + let mut sc = SourceChange::from_text_edit(position.file_id, edit.edit); sc.is_snippet = edit.is_snippet; Some(sc) } -fn on_char_typed_inner( +fn on_char_typed_( file: &Parse<SourceFile>, offset: TextSize, char_typed: char, + edition: Edition, ) -> Option<ExtendedTextEdit> { - if !stdx::always!(TRIGGER_CHARS.contains(char_typed)) { - return None; - } - let conv = |text_edit: Option<TextEdit>| { - Some(ExtendedTextEdit { edit: text_edit?, is_snippet: false }) - }; match char_typed { - '.' => conv(on_dot_typed(&file.tree(), offset)), - '=' => conv(on_eq_typed(&file.tree(), offset)), - '<' => on_left_angle_typed(&file.tree(), offset), - '>' => conv(on_right_angle_typed(&file.tree(), offset)), - '{' => conv(on_opening_bracket_typed(file, offset, '{')), - '(' => conv(on_opening_bracket_typed(file, offset, '(')), + '.' => on_dot_typed(&file.tree(), offset), + '=' => on_eq_typed(&file.tree(), offset), + '>' => on_right_angle_typed(&file.tree(), offset), + '{' | '(' | '<' => on_opening_delimiter_typed(file, offset, char_typed, edition), + '|' => on_pipe_typed(&file.tree(), offset), _ => None, } + .map(conv) +} + +fn conv(edit: TextEdit) -> ExtendedTextEdit { + ExtendedTextEdit { edit, is_snippet: false } } -/// Inserts a closing bracket when the user types an opening bracket, wrapping an existing expression in a +/// Inserts a closing delimiter when the user types an opening bracket, wrapping an existing expression in a /// block, or a part of a `use` item (for `{`). -fn on_opening_bracket_typed( +fn on_opening_delimiter_typed( file: &Parse<SourceFile>, offset: TextSize, opening_bracket: char, + edition: Edition, ) -> Option<TextEdit> { - let (closing_bracket, expected_ast_bracket) = match opening_bracket { - '{' => ('}', SyntaxKind::L_CURLY), - '(' => (')', SyntaxKind::L_PAREN), + type FilterFn = fn(SyntaxKind) -> bool; + let (closing_bracket, expected_ast_bracket, allowed_kinds) = match opening_bracket { + '{' => ('}', SyntaxKind::L_CURLY, &[ast::Expr::can_cast as FilterFn] as &[FilterFn]), + '(' => ( + ')', + SyntaxKind::L_PAREN, + &[ast::Expr::can_cast as FilterFn, ast::Pat::can_cast, ast::Type::can_cast] + as &[FilterFn], + ), + '<' => ('>', SyntaxKind::L_ANGLE, &[ast::Type::can_cast as FilterFn] as &[FilterFn]), _ => return None, }; - if !stdx::always!(file.tree().syntax().text().char_at(offset) == Some(opening_bracket)) { - return None; - } - let brace_token = file.tree().syntax().token_at_offset(offset).right_biased()?; if brace_token.kind() != expected_ast_bracket { return None; @@ -125,58 +140,53 @@ fn on_opening_bracket_typed( if !stdx::always!(range.len() == TextSize::of(opening_bracket)) { return None; } - // FIXME: Edition - let file = file.reparse(range, "", span::Edition::CURRENT_FIXME); + let reparsed = file.reparse(range, "", edition).tree(); - if let Some(edit) = bracket_expr(&file.tree(), offset, opening_bracket, closing_bracket) { + if let Some(edit) = + on_delimited_node_typed(&reparsed, offset, opening_bracket, closing_bracket, allowed_kinds) + { return Some(edit); } - if closing_bracket == '}' { - if let Some(edit) = brace_use_path(&file.tree(), offset) { - return Some(edit); - } + match opening_bracket { + '{' => on_left_brace_typed(&reparsed, offset), + '<' => on_left_angle_typed(&file.tree(), &reparsed, offset), + _ => None, } +} - return None; - - fn brace_use_path(file: &SourceFile, offset: TextSize) -> Option<TextEdit> { - let segment: ast::PathSegment = find_node_at_offset(file.syntax(), offset)?; - if segment.syntax().text_range().start() != offset { - return None; - } - - let tree: ast::UseTree = find_node_at_offset(file.syntax(), offset)?; - - Some(TextEdit::insert(tree.syntax().text_range().end() + TextSize::of("{"), "}".to_owned())) +fn on_left_brace_typed(reparsed: &SourceFile, offset: TextSize) -> Option<TextEdit> { + let segment: ast::PathSegment = find_node_at_offset(reparsed.syntax(), offset)?; + if segment.syntax().text_range().start() != offset { + return None; } - fn bracket_expr( - file: &SourceFile, - offset: TextSize, - opening_bracket: char, - closing_bracket: char, - ) -> Option<TextEdit> { - let mut expr: ast::Expr = find_node_at_offset(file.syntax(), offset)?; - if expr.syntax().text_range().start() != offset { - return None; - } - - // Enclose the outermost expression starting at `offset` - while let Some(parent) = expr.syntax().parent() { - if parent.text_range().start() != expr.syntax().text_range().start() { - break; - } + let tree: ast::UseTree = find_node_at_offset(reparsed.syntax(), offset)?; - match ast::Expr::cast(parent) { - Some(parent) => expr = parent, - None => break, - } - } + Some(TextEdit::insert(tree.syntax().text_range().end() + TextSize::of("{"), "}".to_owned())) +} - if let Some(parent) = expr.syntax().parent().and_then(ast::Expr::cast) { - let mut node = expr.syntax().clone(); - let all_prev_sib_attr = loop { +fn on_delimited_node_typed( + reparsed: &SourceFile, + offset: TextSize, + opening_bracket: char, + closing_bracket: char, + kinds: &[fn(SyntaxKind) -> bool], +) -> Option<TextEdit> { + let t = reparsed.syntax().token_at_offset(offset).right_biased()?; + let (filter, node) = t + .parent_ancestors() + .take_while(|n| n.text_range().start() == offset) + .find_map(|n| kinds.iter().find(|&kind_filter| kind_filter(n.kind())).zip(Some(n)))?; + let mut node = node + .ancestors() + .take_while(|n| n.text_range().start() == offset && filter(n.kind())) + .last()?; + + if let Some(parent) = node.parent().filter(|it| filter(it.kind())) { + let all_prev_sib_attr = { + let mut node = node.clone(); + loop { match node.prev_sibling() { Some(sib) if sib.kind().is_trivia() || sib.kind() == SyntaxKind::ATTR => { node = sib @@ -184,26 +194,32 @@ fn on_opening_bracket_typed( Some(_) => break false, None => break true, }; - }; - - if all_prev_sib_attr { - expr = parent; } - } + }; - // Insert the closing bracket right after the expression. - Some(TextEdit::insert( - expr.syntax().text_range().end() + TextSize::of(opening_bracket), - closing_bracket.to_string(), - )) + if all_prev_sib_attr { + node = parent; + } } -} + // Insert the closing bracket right after the node. + Some(TextEdit::insert( + node.text_range().end() + TextSize::of(opening_bracket), + closing_bracket.to_string(), + )) +} /// Returns an edit which should be applied after `=` was typed. Primarily, /// this works when adding `let =`. // FIXME: use a snippet completion instead of this hack here. fn on_eq_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> { - if !stdx::always!(file.syntax().text().char_at(offset) == Some('=')) { + let text = file.syntax().text(); + let has_newline = iter::successors(Some(offset), |&offset| Some(offset + TextSize::new(1))) + .filter_map(|offset| text.char_at(offset)) + .find(|&c| !c.is_whitespace() || c == '\n') + == Some('n'); + // don't attempt to add `;` if there is a newline after the `=`, the intent is likely to write + // out the expression afterwards! + if has_newline { return None; } @@ -289,9 +305,6 @@ fn on_eq_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> { /// Returns an edit which should be applied when a dot ('.') is typed on a blank line, indenting the line appropriately. fn on_dot_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> { - if !stdx::always!(file.syntax().text().char_at(offset) == Some('.')) { - return None; - } let whitespace = file.syntax().token_at_offset(offset).left_biased().and_then(ast::Whitespace::cast)?; @@ -342,14 +355,15 @@ fn on_dot_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> { } /// Add closing `>` for generic arguments/parameters. -fn on_left_angle_typed(file: &SourceFile, offset: TextSize) -> Option<ExtendedTextEdit> { - let file_text = file.syntax().text(); - if !stdx::always!(file_text.char_at(offset) == Some('<')) { - return None; - } +fn on_left_angle_typed( + file: &SourceFile, + reparsed: &SourceFile, + offset: TextSize, +) -> Option<TextEdit> { + let file_text = reparsed.syntax().text(); - // Find the next non-whitespace char in the line. - let mut next_offset = offset + TextSize::of('<'); + // Find the next non-whitespace char in the line, check if its a `>` + let mut next_offset = offset; while file_text.char_at(next_offset) == Some(' ') { next_offset += TextSize::of(' ') } @@ -357,34 +371,36 @@ fn on_left_angle_typed(file: &SourceFile, offset: TextSize) -> Option<ExtendedTe return None; } - let range = TextRange::at(offset, TextSize::of('<')); - if let Some(t) = file.syntax().token_at_offset(offset).left_biased() { - if T![impl] == t.kind() { - return Some(ExtendedTextEdit { - edit: TextEdit::replace(range, "<$0>".to_owned()), - is_snippet: true, - }); - } - } - - if ancestors_at_offset(file.syntax(), offset).any(|n| { - ast::GenericParamList::can_cast(n.kind()) || ast::GenericArgList::can_cast(n.kind()) - }) { - Some(ExtendedTextEdit { - edit: TextEdit::replace(range, "<$0>".to_owned()), - is_snippet: true, + if ancestors_at_offset(file.syntax(), offset) + .take_while(|n| !ast::Item::can_cast(n.kind())) + .any(|n| { + ast::GenericParamList::can_cast(n.kind()) + || ast::GenericArgList::can_cast(n.kind()) + || ast::UseBoundGenericArgs::can_cast(n.kind()) }) + { + // Insert the closing bracket right after + Some(TextEdit::insert(offset + TextSize::of('<'), '>'.to_string())) } else { None } } +fn on_pipe_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> { + let pipe_token = file.syntax().token_at_offset(offset).right_biased()?; + if pipe_token.kind() != SyntaxKind::PIPE { + return None; + } + if pipe_token.parent().and_then(ast::ParamList::cast)?.r_paren_token().is_some() { + return None; + } + let after_lpipe = offset + TextSize::of('|'); + Some(TextEdit::insert(after_lpipe, "|".to_owned())) +} + /// Adds a space after an arrow when `fn foo() { ... }` is turned into `fn foo() -> { ... }` fn on_right_angle_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> { let file_text = file.syntax().text(); - if !stdx::always!(file_text.char_at(offset) == Some('>')) { - return None; - } let after_arrow = offset + TextSize::of('>'); if file_text.char_at(after_arrow) != Some('{') { return None; @@ -411,7 +427,7 @@ mod tests { let edit = TextEdit::insert(offset, char_typed.to_string()); edit.apply(&mut before); let parse = SourceFile::parse(&before, span::Edition::CURRENT_FIXME); - on_char_typed_inner(&parse, offset, char_typed).map(|it| { + on_char_typed_(&parse, offset, char_typed, span::Edition::CURRENT_FIXME).map(|it| { it.apply(&mut before); before.to_string() }) @@ -426,7 +442,7 @@ mod tests { fn type_char_noop(char_typed: char, ra_fixture_before: &str) { let file_change = do_type_char(char_typed, ra_fixture_before); - assert!(file_change.is_none()) + assert_eq!(file_change, None) } #[test] @@ -471,6 +487,15 @@ fn foo() { } ", ); + type_char_noop( + '=', + r" +fn foo() { + let foo =$0 + 1 + 1 +} +", + ); } #[test] @@ -1067,6 +1092,81 @@ fn f() { } #[test] + fn adds_closing_parenthesis_for_pat() { + type_char( + '(', + r#" +fn f() { match () { $0() => () } } +"#, + r#" +fn f() { match () { (()) => () } } +"#, + ); + type_char( + '(', + r#" +fn f($0n: ()) {} +"#, + r#" +fn f((n): ()) {} +"#, + ); + } + + #[test] + fn adds_closing_parenthesis_for_ty() { + type_char( + '(', + r#" +fn f(n: $0()) {} +"#, + r#" +fn f(n: (())) {} +"#, + ); + type_char( + '(', + r#" +fn f(n: $0a::b::<d>::c) {} +"#, + r#" +fn f(n: (a::b::<d>::c)) {} +"#, + ); + } + + #[test] + fn adds_closing_angles_for_ty() { + type_char( + '<', + r#" +fn f(n: $0()) {} +"#, + r#" +fn f(n: <()>) {} +"#, + ); + type_char( + '<', + r#" +fn f(n: $0a::b::<d>::c) {} +"#, + r#" +fn f(n: <a::b::<d>::c>) {} +"#, + ); + type_char( + '<', + r#" +fn f(n: a$0b::<d>::c) {} +"#, + r#" +fn f(n: a<>b::<d>::c) {} +"#, + ); + } + + #[test] fn parenthesis_noop_in_string_literal() { // Regression test for #9351 type_char_noop( @@ -1157,6 +1257,12 @@ use $0Thing as _; use some::pa$0th::to::Item; "#, ); + type_char_noop( + '<', + r#" +use some::pa$0th::to::Item; + "#, + ); } #[test] @@ -1170,7 +1276,7 @@ fn foo() { "#, r#" fn foo() { - bar::<$0> + bar::<> } "#, ); @@ -1184,7 +1290,7 @@ fn foo(bar: &[u64]) { "#, r#" fn foo(bar: &[u64]) { - bar.iter().collect::<$0>(); + bar.iter().collect::<>(); } "#, ); @@ -1198,7 +1304,7 @@ fn foo(bar: &[u64]) { fn foo$0() {} "#, r#" -fn foo<$0>() {} +fn foo<>() {} "#, ); type_char( @@ -1207,7 +1313,7 @@ fn foo<$0>() {} fn foo$0 "#, r#" -fn foo<$0> +fn foo<> "#, ); type_char( @@ -1216,7 +1322,7 @@ fn foo<$0> struct Foo$0 {} "#, r#" -struct Foo<$0> {} +struct Foo<> {} "#, ); type_char( @@ -1225,7 +1331,7 @@ struct Foo<$0> {} struct Foo$0(); "#, r#" -struct Foo<$0>(); +struct Foo<>(); "#, ); type_char( @@ -1234,7 +1340,7 @@ struct Foo<$0>(); struct Foo$0 "#, r#" -struct Foo<$0> +struct Foo<> "#, ); type_char( @@ -1243,7 +1349,7 @@ struct Foo<$0> enum Foo$0 "#, r#" -enum Foo<$0> +enum Foo<> "#, ); type_char( @@ -1252,7 +1358,7 @@ enum Foo<$0> trait Foo$0 "#, r#" -trait Foo<$0> +trait Foo<> "#, ); type_char( @@ -1261,16 +1367,7 @@ trait Foo<$0> type Foo$0 = Bar; "#, r#" -type Foo<$0> = Bar; - "#, - ); - type_char( - '<', - r#" -impl$0 Foo {} - "#, - r#" -impl<$0> Foo {} +type Foo<> = Bar; "#, ); type_char( @@ -1279,7 +1376,7 @@ impl<$0> Foo {} impl<T> Foo$0 {} "#, r#" -impl<T> Foo<$0> {} +impl<T> Foo<> {} "#, ); type_char( @@ -1288,7 +1385,7 @@ impl<T> Foo<$0> {} impl Foo$0 {} "#, r#" -impl Foo<$0> {} +impl Foo<> {} "#, ); } @@ -1438,4 +1535,42 @@ fn foo() { "#, ); } + + #[test] + fn completes_pipe_param_list() { + type_char( + '|', + r#" +fn foo() { + $0 +} +"#, + r#" +fn foo() { + || +} +"#, + ); + type_char( + '|', + r#" +fn foo() { + $0 a +} +"#, + r#" +fn foo() { + || a +} +"#, + ); + type_char_noop( + '|', + r#" +fn foo() { + let $0 +} +"#, + ); + } } diff --git a/crates/intern/src/symbol.rs b/crates/intern/src/symbol.rs index ef76192ba8..200b14027f 100644 --- a/crates/intern/src/symbol.rs +++ b/crates/intern/src/symbol.rs @@ -77,8 +77,12 @@ impl TaggedArcPtr { } /// Retrieves the tag. + /// + /// # Safety + /// + /// You can only drop the `Arc` if the instance is dropped. #[inline] - pub(crate) fn try_as_arc_owned(self) -> Option<ManuallyDrop<Arc<Box<str>>>> { + pub(crate) unsafe fn try_as_arc_owned(self) -> Option<ManuallyDrop<Arc<Box<str>>>> { // Unpack the tag from the alignment niche let tag = Strict::addr(self.packed.as_ptr()) & Self::BOOL_BITS; if tag != 0 { @@ -245,16 +249,14 @@ impl Symbol { } } - ManuallyDrop::into_inner( - match shard.raw_entry_mut().from_key_hashed_nocheck::<str>(hash, arc.as_ref()) { - RawEntryMut::Occupied(occ) => occ.remove_entry(), - RawEntryMut::Vacant(_) => unreachable!(), - } - .0 - .0 - .try_as_arc_owned() - .unwrap(), - ); + let ptr = match shard.raw_entry_mut().from_key_hashed_nocheck::<str>(hash, arc.as_ref()) { + RawEntryMut::Occupied(occ) => occ.remove_entry(), + RawEntryMut::Vacant(_) => unreachable!(), + } + .0 + .0; + // SAFETY: We're dropping, we have ownership. + ManuallyDrop::into_inner(unsafe { ptr.try_as_arc_owned().unwrap() }); debug_assert_eq!(Arc::count(arc), 1); // Shrink the backing storage if the shard is less than 50% occupied. @@ -267,7 +269,8 @@ impl Symbol { impl Drop for Symbol { #[inline] fn drop(&mut self) { - let Some(arc) = self.repr.try_as_arc_owned() else { + // SAFETY: We're dropping, we have ownership. + let Some(arc) = (unsafe { self.repr.try_as_arc_owned() }) else { return; }; // When the last `Ref` is dropped, remove the object from the global map. @@ -288,7 +291,8 @@ impl Clone for Symbol { } fn increase_arc_refcount(repr: TaggedArcPtr) -> TaggedArcPtr { - let Some(arc) = repr.try_as_arc_owned() else { + // SAFETY: We're not dropping the `Arc`. + let Some(arc) = (unsafe { repr.try_as_arc_owned() }) else { return repr; }; // increase the ref count diff --git a/crates/intern/src/symbol/symbols.rs b/crates/intern/src/symbol/symbols.rs index 865518fe94..1120d3c7d6 100644 --- a/crates/intern/src/symbol/symbols.rs +++ b/crates/intern/src/symbol/symbols.rs @@ -80,6 +80,7 @@ define_symbols! { self_ = "self", Self_ = "Self", tick_static = "'static", + tick_underscore = "'_", dollar_crate = "$crate", MISSING_NAME = "[missing name]", fn_ = "fn", @@ -99,7 +100,6 @@ define_symbols! { cdecl_dash_unwind = "cdecl-unwind", fastcall_dash_unwind = "fastcall-unwind", msp430_dash_interrupt = "msp430-interrupt", - platform_dash_intrinsic = "platform-intrinsic", ptx_dash_kernel = "ptx-kernel", riscv_dash_interrupt_dash_m = "riscv-interrupt-m", riscv_dash_interrupt_dash_s = "riscv-interrupt-s", @@ -150,6 +150,9 @@ define_symbols! { C, call_mut, call_once, + async_call_once, + async_call_mut, + async_call, call, cdecl, Center, @@ -221,6 +224,9 @@ define_symbols! { fn_mut, fn_once_output, fn_once, + async_fn_once, + async_fn_mut, + async_fn, fn_ptr_addr, fn_ptr_trait, format_alignment, @@ -334,6 +340,8 @@ define_symbols! { Option, Ord, Output, + CallRefFuture, + CallOnceFuture, owned_box, packed, panic_2015, diff --git a/crates/load-cargo/src/lib.rs b/crates/load-cargo/src/lib.rs index 0e1606a699..cf26845b11 100644 --- a/crates/load-cargo/src/lib.rs +++ b/crates/load-cargo/src/lib.rs @@ -123,7 +123,7 @@ pub fn load_workspace( .collect() }; - let project_folders = ProjectFolders::new(std::slice::from_ref(&ws), &[]); + let project_folders = ProjectFolders::new(std::slice::from_ref(&ws), &[], None); loader.set_config(vfs::loader::Config { load: project_folders.load, watch: vec![], @@ -153,7 +153,11 @@ pub struct ProjectFolders { } impl ProjectFolders { - pub fn new(workspaces: &[ProjectWorkspace], global_excludes: &[AbsPathBuf]) -> ProjectFolders { + pub fn new( + workspaces: &[ProjectWorkspace], + global_excludes: &[AbsPathBuf], + user_config_dir_path: Option<&AbsPath>, + ) -> ProjectFolders { let mut res = ProjectFolders::default(); let mut fsc = FileSetConfig::builder(); let mut local_filesets = vec![]; @@ -291,6 +295,22 @@ impl ProjectFolders { } } + if let Some(user_config_path) = user_config_dir_path { + let ratoml_path = { + let mut p = user_config_path.to_path_buf(); + p.push("rust-analyzer.toml"); + p + }; + + let file_set_roots = vec![VfsPath::from(ratoml_path.to_owned())]; + let entry = vfs::loader::Entry::Files(vec![ratoml_path.to_owned()]); + + res.watch.push(res.load.len()); + res.load.push(entry); + local_filesets.push(fsc.len() as u64); + fsc.add_file_set(file_set_roots) + } + let fsc = fsc.build(); res.source_root_config = SourceRootConfig { fsc, local_filesets }; diff --git a/crates/parser/src/grammar.rs b/crates/parser/src/grammar.rs index a50a2182a7..fe6b904bd8 100644 --- a/crates/parser/src/grammar.rs +++ b/crates/parser/src/grammar.rs @@ -242,7 +242,7 @@ fn opt_visibility(p: &mut Parser<'_>, in_tuple_field: bool) -> bool { // struct MyStruct(pub ()); if !(in_tuple_field && matches!(p.nth(1), T![ident] | T![')'])) { p.bump(T!['(']); - paths::use_path(p); + paths::vis_path(p); p.expect(T![')']); } } @@ -252,7 +252,7 @@ fn opt_visibility(p: &mut Parser<'_>, in_tuple_field: bool) -> bool { T![in] => { p.bump(T!['(']); p.bump(T![in]); - paths::use_path(p); + paths::vis_path(p); p.expect(T![')']); } _ => {} @@ -307,13 +307,49 @@ fn name(p: &mut Parser<'_>) { name_r(p, TokenSet::EMPTY); } -fn name_ref(p: &mut Parser<'_>) { - if p.at(IDENT) { +fn name_ref_or_self(p: &mut Parser<'_>) { + if matches!(p.current(), T![ident] | T![self]) { let m = p.start(); - p.bump(IDENT); + p.bump_any(); + m.complete(p, NAME_REF); + } else { + p.err_and_bump("expected identifier or `self`"); + } +} + +fn name_ref_or_upper_self(p: &mut Parser<'_>) { + if matches!(p.current(), T![ident] | T![Self]) { + let m = p.start(); + p.bump_any(); + m.complete(p, NAME_REF); + } else { + p.err_and_bump("expected identifier or `Self`"); + } +} + +const PATH_NAME_REF_KINDS: TokenSet = + TokenSet::new(&[IDENT, T![self], T![super], T![crate], T![Self]]); + +fn name_ref_mod_path(p: &mut Parser<'_>) { + if p.at_ts(PATH_NAME_REF_KINDS) { + let m = p.start(); + p.bump_any(); + m.complete(p, NAME_REF); + } else { + p.err_and_bump("expected identifier, `self`, `super`, `crate`, or `Self`"); + } +} + +const PATH_NAME_REF_OR_INDEX_KINDS: TokenSet = + PATH_NAME_REF_KINDS.union(TokenSet::new(&[INT_NUMBER])); + +fn name_ref_mod_path_or_index(p: &mut Parser<'_>) { + if p.at_ts(PATH_NAME_REF_OR_INDEX_KINDS) { + let m = p.start(); + p.bump_any(); m.complete(p, NAME_REF); } else { - p.err_and_bump("expected identifier"); + p.err_and_bump("expected integer, identifier, `self`, `super`, `crate`, or `Self`"); } } diff --git a/crates/parser/src/grammar/attributes.rs b/crates/parser/src/grammar/attributes.rs index 82e4d66148..ccb556b2cc 100644 --- a/crates/parser/src/grammar/attributes.rs +++ b/crates/parser/src/grammar/attributes.rs @@ -36,6 +36,14 @@ fn attr(p: &mut Parser<'_>, inner: bool) { attr.complete(p, ATTR); } +// test_err meta_recovery +// #![] +// #![p = ] +// #![p::] +// #![p:: =] +// #![unsafe] +// #![unsafe =] + // test metas // #![simple_ident] // #![simple::path] @@ -63,7 +71,7 @@ pub(super) fn meta(p: &mut Parser<'_>) { if is_unsafe { p.expect(T!['(']); } - paths::use_path(p); + paths::attr_path(p); match p.current() { T![=] => { diff --git a/crates/parser/src/grammar/expressions.rs b/crates/parser/src/grammar/expressions.rs index e565874a42..3b3f11be13 100644 --- a/crates/parser/src/grammar/expressions.rs +++ b/crates/parser/src/grammar/expressions.rs @@ -449,7 +449,9 @@ fn postfix_dot_expr<const FLOAT_RECOVERY: bool>( let nth1 = if FLOAT_RECOVERY { 0 } else { 1 }; let nth2 = if FLOAT_RECOVERY { 1 } else { 2 }; - if p.nth(nth1) == IDENT && (p.nth(nth2) == T!['('] || p.nth_at(nth2, T![::])) { + if PATH_NAME_REF_KINDS.contains(p.nth(nth1)) + && (p.nth(nth2) == T!['('] || p.nth_at(nth2, T![::])) + { return Ok(method_call_expr::<FLOAT_RECOVERY>(p, lhs)); } @@ -510,22 +512,27 @@ fn index_expr(p: &mut Parser<'_>, lhs: CompletedMarker) -> CompletedMarker { // y.bar::<T>(1, 2,); // x.0.0.call(); // x.0. call(); +// x.0() // } fn method_call_expr<const FLOAT_RECOVERY: bool>( p: &mut Parser<'_>, lhs: CompletedMarker, ) -> CompletedMarker { if FLOAT_RECOVERY { - assert!(p.nth(0) == IDENT && (p.nth(1) == T!['('] || p.nth_at(1, T![::]))); + assert!(p.at_ts(PATH_NAME_REF_KINDS) && (p.nth(1) == T!['('] || p.nth_at(1, T![::]))); } else { - assert!(p.at(T![.]) && p.nth(1) == IDENT && (p.nth(2) == T!['('] || p.nth_at(2, T![::]))); + assert!( + p.at(T![.]) + && PATH_NAME_REF_KINDS.contains(p.nth(1)) + && (p.nth(2) == T!['('] || p.nth_at(2, T![::])) + ); } let m = lhs.precede(p); if !FLOAT_RECOVERY { p.bump(T![.]); } - name_ref(p); - generic_args::opt_generic_arg_list(p, true); + name_ref_mod_path(p); + generic_args::opt_generic_arg_list_expr(p); if p.at(T!['(']) { arg_list(p); } else { @@ -543,6 +550,8 @@ fn method_call_expr<const FLOAT_RECOVERY: bool>( // test field_expr // fn foo() { +// x.self; +// x.Self; // x.foo; // x.0.bar; // x.0.1; @@ -560,8 +569,8 @@ fn field_expr<const FLOAT_RECOVERY: bool>( if !FLOAT_RECOVERY { p.bump(T![.]); } - if p.at(IDENT) || p.at(INT_NUMBER) { - name_ref_or_index(p); + if p.at_ts(PATH_NAME_REF_OR_INDEX_KINDS) { + name_ref_mod_path_or_index(p); } else if p.at(FLOAT_NUMBER) { return match p.split_float(m) { (true, m) => { @@ -679,34 +688,37 @@ pub(crate) fn record_expr_field_list(p: &mut Parser<'_>) { IDENT | INT_NUMBER if p.nth_at(1, T![::]) => { // test_err record_literal_missing_ellipsis_recovery // fn main() { - // S { S::default() } + // S { S::default() }; + // S { 0::default() }; // } m.abandon(p); p.expect(T![..]); expr(p); } + IDENT | INT_NUMBER if p.nth_at(1, T![..]) => { + // test_err record_literal_before_ellipsis_recovery + // fn main() { + // S { field ..S::default() } + // S { 0 ..S::default() } + // } + name_ref_or_index(p); + p.error("expected `:`"); + m.complete(p, RECORD_EXPR_FIELD); + } IDENT | INT_NUMBER => { - if p.nth_at(1, T![..]) { - // test_err record_literal_before_ellipsis_recovery - // fn main() { - // S { field ..S::default() } - // } + // test_err record_literal_field_eq_recovery + // fn main() { + // S { field = foo } + // S { 0 = foo } + // } + if p.nth_at(1, T![:]) { name_ref_or_index(p); - p.error("expected `:`"); - } else { - // test_err record_literal_field_eq_recovery - // fn main() { - // S { field = foo } - // } - if p.nth_at(1, T![:]) { - name_ref_or_index(p); - p.bump(T![:]); - } else if p.nth_at(1, T![=]) { - name_ref_or_index(p); - p.err_and_bump("expected `:`"); - } - expr(p); + p.bump(T![:]); + } else if p.nth_at(1, T![=]) { + name_ref_or_index(p); + p.err_and_bump("expected `:`"); } + expr(p); m.complete(p, RECORD_EXPR_FIELD); } T![.] if p.at(T![..]) => { diff --git a/crates/parser/src/grammar/expressions/atom.rs b/crates/parser/src/grammar/expressions/atom.rs index 97e0392ce1..407320e1d0 100644 --- a/crates/parser/src/grammar/expressions/atom.rs +++ b/crates/parser/src/grammar/expressions/atom.rs @@ -259,13 +259,7 @@ fn builtin_expr(p: &mut Parser<'_>) -> Option<CompletedMarker> { type_(p); p.expect(T![,]); while !p.at(EOF) && !p.at(T![')']) { - if p.at(IDENT) || p.at(INT_NUMBER) { - name_ref_or_index(p); - // } else if p.at(FLOAT_NUMBER) { - // FIXME: needs float hack - } else { - p.err_and_bump("expected field name or number"); - } + name_ref_mod_path_or_index(p); if !p.at(T![')']) { p.expect(T![.]); } @@ -351,10 +345,7 @@ fn parse_asm_expr(p: &mut Parser<'_>, m: Marker) -> Option<CompletedMarker> { name(p); p.bump(T![=]); allow_templates = false; - true - } else { - false - }; + } let op = p.start(); let dir_spec = p.start(); @@ -405,6 +396,19 @@ fn parse_asm_expr(p: &mut Parser<'_>, m: Marker) -> Option<CompletedMarker> { op.abandon(p); op_n.abandon(p); p.err_and_bump("expected asm operand"); + + // improves error recovery and handles err_and_bump recovering from `{` which gets + // the parser stuck here + if p.at(T!['{']) { + // test_err bad_asm_expr + // fn foo() { + // builtin#asm( + // label crashy = { return; } + // ); + // } + expr(p); + } + if p.at(T!['}']) { break; } @@ -465,9 +469,9 @@ fn parse_clobber_abi(p: &mut Parser<'_>) { fn parse_reg(p: &mut Parser<'_>) { p.expect(T!['(']); - if p.at(T![ident]) { + if p.at_ts(PATH_NAME_REF_KINDS) { let m = p.start(); - name_ref(p); + name_ref_mod_path(p); m.complete(p, ASM_REG_SPEC); } else if p.at(T![string]) { let m = p.start(); diff --git a/crates/parser/src/grammar/generic_args.rs b/crates/parser/src/grammar/generic_args.rs index c62c8a9d3f..b9d5bff663 100644 --- a/crates/parser/src/grammar/generic_args.rs +++ b/crates/parser/src/grammar/generic_args.rs @@ -1,14 +1,13 @@ use super::*; -// test_err generic_arg_list_recover -// type T = T<0, ,T>; -pub(super) fn opt_generic_arg_list(p: &mut Parser<'_>, colon_colon_required: bool) { +// test_err generic_arg_list_recover_expr +// const _: () = T::<0, ,T>; +// const _: () = T::<0, ,T>(); +pub(super) fn opt_generic_arg_list_expr(p: &mut Parser<'_>) { let m; if p.at(T![::]) && p.nth(2) == T![<] { m = p.start(); p.bump(T![::]); - } else if !colon_colon_required && p.at(T![<]) && p.nth(1) != T![=] { - m = p.start(); } else { return; } @@ -25,7 +24,7 @@ pub(super) fn opt_generic_arg_list(p: &mut Parser<'_>, colon_colon_required: boo m.complete(p, GENERIC_ARG_LIST); } -const GENERIC_ARG_FIRST: TokenSet = TokenSet::new(&[ +pub(crate) const GENERIC_ARG_FIRST: TokenSet = TokenSet::new(&[ LIFETIME_IDENT, IDENT, T!['{'], @@ -46,21 +45,27 @@ const GENERIC_ARG_FIRST: TokenSet = TokenSet::new(&[ const GENERIC_ARG_RECOVERY_SET: TokenSet = TokenSet::new(&[T![>], T![,]]); // test generic_arg -// type T = S<i32>; -fn generic_arg(p: &mut Parser<'_>) -> bool { +// type T = S<i32, dyn T, fn()>; +pub(crate) fn generic_arg(p: &mut Parser<'_>) -> bool { match p.current() { LIFETIME_IDENT if !p.nth_at(1, T![+]) => lifetime_arg(p), T!['{'] | T![true] | T![false] | T![-] => const_arg(p), k if k.is_literal() => const_arg(p), - // test associated_type_bounds - // fn print_all<T: Iterator<Item, Item::Item, Item::<true>, Item: Display, Item<'a> = Item>>(printables: T) {} + // test generic_arg_bounds + // type Plain = Foo<Item, Item::Item, Item: Bound, Item = Item>; + // type GenericArgs = Foo<Item<T>, Item::<T>, Item<T>: Bound, Item::<T>: Bound, Item<T> = Item, Item::<T> = Item>; + // type ParenthesizedArgs = Foo<Item(T), Item::(T), Item(T): Bound, Item::(T): Bound, Item(T) = Item, Item::(T) = Item>; + // type RTN = Foo<Item(..), Item(..), Item(..): Bound, Item(..): Bound, Item(..) = Item, Item(..) = Item>; + // test edition_2015_dyn_prefix_inside_generic_arg 2015 + // type A = Foo<dyn T>; + T![ident] if !p.edition().at_least_2018() && types::is_dyn_weak(p) => type_arg(p), // test macro_inside_generic_arg // type A = Foo<syn::Token![_]>; - IDENT if [T![<], T![=], T![:]].contains(&p.nth(1)) && !p.nth_at(1, T![::]) => { + k if PATH_NAME_REF_KINDS.contains(k) => { let m = p.start(); - name_ref(p); - opt_generic_arg_list(p, false); + name_ref_mod_path(p); + paths::opt_path_type_args(p); match p.current() { T![=] => { p.bump_any(); @@ -88,45 +93,26 @@ fn generic_arg(p: &mut Parser<'_>) -> bool { } // test assoc_type_bound // type T = StreamingIterator<Item<'a>: Clone>; + // type T = StreamingIterator<Item(T): Clone>; T![:] if !p.at(T![::]) => { generic_params::bounds(p); m.complete(p, ASSOC_TYPE_ARG); } + // Turned out to be just a normal path type (mirror `path_or_macro_type`) _ => { let m = m.complete(p, PATH_SEGMENT).precede(p).complete(p, PATH); let m = paths::type_path_for_qualifier(p, m); - m.precede(p).complete(p, PATH_TYPE).precede(p).complete(p, TYPE_ARG); + let m = if p.at(T![!]) && !p.at(T![!=]) { + let m = m.precede(p); + items::macro_call_after_excl(p); + m.complete(p, MACRO_CALL).precede(p).complete(p, MACRO_TYPE) + } else { + m.precede(p).complete(p, PATH_TYPE) + }; + types::opt_type_bounds_as_dyn_trait_type(p, m).precede(p).complete(p, TYPE_ARG); } } } - IDENT if p.nth_at(1, T!['(']) => { - let m = p.start(); - name_ref(p); - 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<T: Trait<method(..): Send>>() {} - 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<Fn(i32)>; - // type A = S<Fn(i32) + Send>; - // type B = S<Fn(i32) -> i32>; - // type C = S<Fn(i32) -> i32 + Send>; - opt_ret_type(p); - let m = m.complete(p, PATH_SEGMENT).precede(p).complete(p, PATH); - let m = paths::type_path_for_qualifier(p, m); - let m = m.precede(p).complete(p, PATH_TYPE); - let m = types::opt_type_bounds_as_dyn_trait_type(p, m); - m.precede(p).complete(p, TYPE_ARG); - } - } _ if p.at_ts(types::TYPE_FIRST) => type_arg(p), _ => return false, } @@ -168,10 +154,10 @@ pub(super) fn const_arg_expr(p: &mut Parser<'_>) { expressions::literal(p); lm.complete(p, PREFIX_EXPR); } - _ if paths::is_use_path_start(p) => { + _ if paths::is_path_start(p) => { // This shouldn't be hit by `const_arg` let lm = p.start(); - paths::use_path(p); + paths::expr_path(p); lm.complete(p, PATH_EXPR); } _ => { @@ -190,7 +176,7 @@ pub(super) fn const_arg(p: &mut Parser<'_>) { m.complete(p, CONST_ARG); } -fn type_arg(p: &mut Parser<'_>) { +pub(crate) fn type_arg(p: &mut Parser<'_>) { let m = p.start(); types::type_(p); m.complete(p, TYPE_ARG); diff --git a/crates/parser/src/grammar/generic_params.rs b/crates/parser/src/grammar/generic_params.rs index 92311238c2..9d4fdbfaf2 100644 --- a/crates/parser/src/grammar/generic_params.rs +++ b/crates/parser/src/grammar/generic_params.rs @@ -56,7 +56,7 @@ fn generic_param(p: &mut Parser<'_>, m: Marker) -> bool { fn lifetime_param(p: &mut Parser<'_>, m: Marker) { assert!(p.at(LIFETIME_IDENT)); lifetime(p); - if p.at(T![:]) { + if p.eat(T![:]) { lifetime_bounds(p); } m.complete(p, LIFETIME_PARAM); @@ -106,14 +106,19 @@ fn const_param(p: &mut Parser<'_>, m: Marker) { } fn lifetime_bounds(p: &mut Parser<'_>) { - assert!(p.at(T![:])); - p.bump(T![:]); - while p.at(LIFETIME_IDENT) { - lifetime(p); + let marker = p.start(); + while { + if !matches!(p.current(), LIFETIME_IDENT | T![>] | T![,]) { + p.error("expected lifetime"); + } + + type_bound(p) + } { if !p.eat(T![+]) { break; } } + marker.complete(p, TYPE_BOUND_LIST); } // test type_param_bounds @@ -145,6 +150,9 @@ fn type_bound(p: &mut Parser<'_>) -> bool { T![for] => types::for_type(p, false), // test precise_capturing // fn captures<'a: 'a, 'b: 'b, T>() -> impl Sized + use<'b, T, Self> {} + + // test_err precise_capturing_invalid + // type T = impl use<self, 1>; T![use] if p.nth_at(1, T![<]) => { p.bump_any(); let m = p.start(); @@ -156,14 +164,10 @@ fn type_bound(p: &mut Parser<'_>) -> bool { || "expected identifier or lifetime".into(), TokenSet::new(&[T![Self], IDENT, LIFETIME_IDENT]), |p| { - if p.at(T![Self]) { - let m = p.start(); - p.bump(T![Self]); - m.complete(p, NAME_REF); - } else if p.at(LIFETIME_IDENT) { + if p.at(LIFETIME_IDENT) { lifetime(p); } else { - name_ref(p); + name_ref_or_upper_self(p); } true }, diff --git a/crates/parser/src/grammar/items.rs b/crates/parser/src/grammar/items.rs index 7d98499008..8ece5af527 100644 --- a/crates/parser/src/grammar/items.rs +++ b/crates/parser/src/grammar/items.rs @@ -254,22 +254,16 @@ fn opt_item_without_modifiers(p: &mut Parser<'_>, m: Marker) -> Result<(), Marke // test extern_crate // extern crate foo; +// extern crate self; fn extern_crate(p: &mut Parser<'_>, m: Marker) { p.bump(T![extern]); p.bump(T![crate]); - if p.at(T![self]) { - // test extern_crate_self - // extern crate self; - let m = p.start(); - p.bump(T![self]); - m.complete(p, NAME_REF); - } else { - name_ref(p); - } + name_ref_or_self(p); // test extern_crate_rename // extern crate foo as bar; + // extern crate self as bar; opt_rename(p); p.expect(T![;]); m.complete(p, EXTERN_CRATE); diff --git a/crates/parser/src/grammar/params.rs b/crates/parser/src/grammar/params.rs index c535267c16..51ffcd0706 100644 --- a/crates/parser/src/grammar/params.rs +++ b/crates/parser/src/grammar/params.rs @@ -14,12 +14,6 @@ pub(super) fn param_list_fn_def(p: &mut Parser<'_>) { list_(p, Flavor::FnDef); } -// test param_list_opt_patterns -// fn foo<F: FnMut(&mut Foo<'a>)>(){} -pub(super) fn param_list_fn_trait(p: &mut Parser<'_>) { - list_(p, Flavor::FnTrait); -} - pub(super) fn param_list_fn_ptr(p: &mut Parser<'_>) { list_(p, Flavor::FnPointer); } @@ -28,10 +22,9 @@ pub(super) fn param_list_closure(p: &mut Parser<'_>) { list_(p, Flavor::Closure); } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] enum Flavor { - FnDef, // Includes trait fn params; omitted param idents are not supported - FnTrait, // Params for `Fn(...)`/`FnMut(...)`/`FnOnce(...)` annotations + FnDef, // Includes trait fn params; omitted param idents are not supported FnPointer, Closure, } @@ -41,7 +34,7 @@ fn list_(p: &mut Parser<'_>, flavor: Flavor) { let (bra, ket) = match flavor { Closure => (T![|], T![|]), - FnDef | FnTrait | FnPointer => (T!['('], T![')']), + FnDef | FnPointer => (T!['('], T![')']), }; let list_marker = p.start(); @@ -119,11 +112,6 @@ fn param(p: &mut Parser<'_>, m: Marker, flavor: Flavor) { } } } - // test value_parameters_no_patterns - // type F = Box<Fn(i32, &i32, &i32, ())>; - Flavor::FnTrait => { - types::type_(p); - } // test fn_pointer_param_ident_path // type Foo = fn(Bar::Baz); // type Qux = fn(baz: Bar::Baz); diff --git a/crates/parser/src/grammar/paths.rs b/crates/parser/src/grammar/paths.rs index 09db921803..3410505cd4 100644 --- a/crates/parser/src/grammar/paths.rs +++ b/crates/parser/src/grammar/paths.rs @@ -19,6 +19,14 @@ pub(super) fn use_path(p: &mut Parser<'_>) { path(p, Mode::Use); } +pub(super) fn vis_path(p: &mut Parser<'_>) { + path(p, Mode::Vis); +} + +pub(super) fn attr_path(p: &mut Parser<'_>) { + path(p, Mode::Attr); +} + pub(crate) fn type_path(p: &mut Parser<'_>) { path(p, Mode::Type); } @@ -37,15 +45,20 @@ pub(crate) fn type_path_for_qualifier( #[derive(Clone, Copy, Eq, PartialEq)] enum Mode { Use, + Attr, Type, Expr, + Vis, } -fn path(p: &mut Parser<'_>, mode: Mode) { +fn path(p: &mut Parser<'_>, mode: Mode) -> Option<CompletedMarker> { let path = p.start(); - path_segment(p, mode, true); + if path_segment(p, mode, true).is_none() { + path.abandon(p); + return None; + } let qual = path.complete(p, PATH); - path_for_qualifier(p, mode, qual); + Some(path_for_qualifier(p, mode, qual)) } fn path_for_qualifier( @@ -71,7 +84,7 @@ const EXPR_PATH_SEGMENT_RECOVERY_SET: TokenSet = items::ITEM_RECOVERY_SET.union(TokenSet::new(&[T![')'], T![,], T![let]])); const TYPE_PATH_SEGMENT_RECOVERY_SET: TokenSet = types::TYPE_RECOVERY_SET; -fn path_segment(p: &mut Parser<'_>, mode: Mode, first: bool) { +fn path_segment(p: &mut Parser<'_>, mode: Mode, first: bool) -> Option<CompletedMarker> { let m = p.start(); // test qual_paths // type X = <A as B>::Output; @@ -93,75 +106,105 @@ fn path_segment(p: &mut Parser<'_>, mode: Mode, first: bool) { p.error("expected `::`"); } } else { - let empty = if first { - p.eat(T![::]); - false - } else { - true - }; - match p.current() { - IDENT => { - name_ref(p); - opt_path_type_args(p, mode); - } + let mut empty = if first { !p.eat(T![::]) } else { true }; + if p.at_ts(PATH_NAME_REF_KINDS) { // test crate_path // use crate::foo; - T![self] | T![super] | T![crate] | T![Self] => { - let m = p.start(); - p.bump_any(); - m.complete(p, NAME_REF); - } - _ => { - let recover_set = match mode { - Mode::Use => items::ITEM_RECOVERY_SET, - Mode::Type => TYPE_PATH_SEGMENT_RECOVERY_SET, - Mode::Expr => EXPR_PATH_SEGMENT_RECOVERY_SET, - }; - p.err_recover("expected identifier", recover_set); - if empty { - // test_err empty_segment - // use crate::; - m.abandon(p); - return; + name_ref_mod_path(p); + opt_path_args(p, mode); + } else { + let recover_set = match mode { + Mode::Use => items::ITEM_RECOVERY_SET, + Mode::Attr => { + items::ITEM_RECOVERY_SET.union(TokenSet::new(&[T![']'], T![=], T![#]])) } + Mode::Vis => items::ITEM_RECOVERY_SET.union(TokenSet::new(&[T![')']])), + Mode::Type => TYPE_PATH_SEGMENT_RECOVERY_SET, + Mode::Expr => EXPR_PATH_SEGMENT_RECOVERY_SET, + }; + empty &= p.err_recover( + "expected identifier, `self`, `super`, `crate`, or `Self`", + recover_set, + ); + if empty { + // test_err empty_segment + // use crate::; + m.abandon(p); + return None; } - }; + } } - m.complete(p, PATH_SEGMENT); + Some(m.complete(p, PATH_SEGMENT)) } -fn opt_path_type_args(p: &mut Parser<'_>, mode: Mode) { +pub(crate) fn opt_path_type_args(p: &mut Parser<'_>) { + // test typepathfn_with_coloncolon + // type F = Start::(Middle) -> (Middle)::End; + // type GenericArg = S<Start(Middle)::End>; + let m; + if p.at(T![::]) && matches!(p.nth(2), T![<] | T!['(']) { + m = p.start(); + p.bump(T![::]); + } else if (p.current() == T![<] && p.nth(1) != T![=]) || p.current() == T!['('] { + m = p.start(); + } else { + return; + } + let current = p.current(); + if current == T![<] { + // test_err generic_arg_list_recover + // type T = T<0, ,T>; + // type T = T::<0, ,T>; + delimited( + p, + T![<], + T![>], + T![,], + || "expected generic argument".into(), + generic_args::GENERIC_ARG_FIRST, + generic_args::generic_arg, + ); + m.complete(p, GENERIC_ARG_LIST); + } else if p.nth_at(1, T![..]) { + // test return_type_syntax_in_path + // fn foo<T>() + // where + // T::method(..): Send, + // method(..): Send, + // method::(..): Send, + // {} + p.bump(T!['(']); + p.bump(T![..]); + p.expect(T![')']); + m.complete(p, RETURN_TYPE_SYNTAX); + } else { + // test path_fn_trait_args + // type F = Box<Fn(i32) -> ()>; + // type F = Box<::Fn(i32) -> ()>; + // type F = Box<Fn::(i32) -> ()>; + // type F = Box<::Fn::(i32) -> ()>; + delimited( + p, + T!['('], + T![')'], + T![,], + || "expected type".into(), + types::TYPE_FIRST, + |p| { + let progress = types::TYPE_FIRST.contains(p.current()); + generic_args::type_arg(p); + progress + }, + ); + m.complete(p, PARENTHESIZED_ARG_LIST); + opt_ret_type(p); + } +} + +fn opt_path_args(p: &mut Parser<'_>, mode: Mode) { match mode { - Mode::Use => {} - Mode::Type => { - // test typepathfn_with_coloncolon - // type F = Start::(Middle) -> (Middle)::End; - // type GenericArg = S<Start(Middle)::End>; - if p.at(T![::]) && p.nth_at(2, T!['(']) { - p.bump(T![::]); - } - if p.at(T!['(']) { - if p.nth_at(1, T![..]) { - // test return_type_syntax_in_path - // fn foo<T>() - // 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<Fn(i32) -> ()>; - params::param_list_fn_trait(p); - opt_ret_type(p); - } - } else { - generic_args::opt_generic_arg_list(p, false); - } - } - Mode::Expr => generic_args::opt_generic_arg_list(p, true), + Mode::Use | Mode::Attr | Mode::Vis => {} + Mode::Type => opt_path_type_args(p), + Mode::Expr => generic_args::opt_generic_arg_list_expr(p), } } diff --git a/crates/parser/src/grammar/types.rs b/crates/parser/src/grammar/types.rs index f4e57d3d6f..0133b7d5d8 100644 --- a/crates/parser/src/grammar/types.rs +++ b/crates/parser/src/grammar/types.rs @@ -50,7 +50,7 @@ fn type_with_bounds_cond(p: &mut Parser<'_>, allow_bounds: bool) { // Some path types are not allowed to have bounds (no plus) T![<] => path_type_bounds(p, allow_bounds), T![ident] if !p.edition().at_least_2018() && is_dyn_weak(p) => dyn_trait_type_weak(p), - _ if paths::is_path_start(p) => path_or_macro_type_(p, allow_bounds), + _ if paths::is_path_start(p) => path_or_macro_type(p, allow_bounds), LIFETIME_IDENT if p.nth_at(1, T![+]) => bare_dyn_trait_type(p), _ => { p.err_recover("expected type", TYPE_RECOVERY_SET); @@ -58,7 +58,7 @@ fn type_with_bounds_cond(p: &mut Parser<'_>, allow_bounds: bool) { } } -fn is_dyn_weak(p: &Parser<'_>) -> bool { +pub(crate) fn is_dyn_weak(p: &Parser<'_>) -> bool { const WEAK_DYN_PATH_FIRST: TokenSet = TokenSet::new(&[ IDENT, T![self], @@ -337,7 +337,7 @@ pub(super) fn path_type(p: &mut Parser<'_>) { // test macro_call_type // type A = foo!(); // type B = crate::foo!(); -fn path_or_macro_type_(p: &mut Parser<'_>, allow_bounds: bool) { +fn path_or_macro_type(p: &mut Parser<'_>, allow_bounds: bool) { assert!(paths::is_path_start(p)); let r = p.start(); let m = p.start(); diff --git a/crates/parser/src/parser.rs b/crates/parser/src/parser.rs index f6b3783d1c..75a75f601c 100644 --- a/crates/parser/src/parser.rs +++ b/crates/parser/src/parser.rs @@ -258,22 +258,25 @@ impl<'t> Parser<'t> { self.err_recover(message, TokenSet::EMPTY); } - /// Create an error node and consume the next token. - pub(crate) fn err_recover(&mut self, message: &str, recovery: TokenSet) { + /// Create an error node and consume the next token unless it is in the recovery set. + /// + /// Returns true if recovery kicked in. + pub(crate) fn err_recover(&mut self, message: &str, recovery: TokenSet) -> bool { if matches!(self.current(), T!['{'] | T!['}']) { self.error(message); - return; + return true; } if self.at_ts(recovery) { self.error(message); - return; + return true; } let m = self.start(); self.error(message); self.bump_any(); m.complete(self, ERROR); + false } fn do_bump(&mut self, kind: SyntaxKind, n_raw_tokens: u8) { @@ -324,10 +327,10 @@ impl Marker { self.bomb.defuse(); let idx = self.pos as usize; if idx == p.events.len() - 1 { - match p.events.pop() { - Some(Event::Start { kind: TOMBSTONE, forward_parent: None }) => (), - _ => unreachable!(), - } + assert!(matches!( + p.events.pop(), + Some(Event::Start { kind: TOMBSTONE, forward_parent: None }) + )); } } } diff --git a/crates/parser/src/syntax_kind/generated.rs b/crates/parser/src/syntax_kind/generated.rs index 21730244a3..0c9c6ffd71 100644 --- a/crates/parser/src/syntax_kind/generated.rs +++ b/crates/parser/src/syntax_kind/generated.rs @@ -256,6 +256,7 @@ pub enum SyntaxKind { OR_PAT, PARAM, PARAM_LIST, + PARENTHESIZED_ARG_LIST, PAREN_EXPR, PAREN_PAT, PAREN_TYPE, diff --git a/crates/parser/test_data/generated/runner.rs b/crates/parser/test_data/generated/runner.rs index 62b381b668..003b7fda94 100644 --- a/crates/parser/test_data/generated/runner.rs +++ b/crates/parser/test_data/generated/runner.rs @@ -39,10 +39,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_type_bounds() { - run_and_expect_no_errors("test_data/parser/inline/ok/associated_type_bounds.rs"); - } - #[test] fn async_trait_bound() { run_and_expect_no_errors("test_data/parser/inline/ok/async_trait_bound.rs"); } @@ -59,12 +55,6 @@ mod ok { ); } #[test] - fn bare_dyn_types_with_paren_as_generic_args() { - run_and_expect_no_errors( - "test_data/parser/inline/ok/bare_dyn_types_with_paren_as_generic_args.rs", - ); - } - #[test] fn become_expr() { run_and_expect_no_errors("test_data/parser/inline/ok/become_expr.rs"); } #[test] fn bind_pat() { run_and_expect_no_errors("test_data/parser/inline/ok/bind_pat.rs"); } @@ -200,6 +190,13 @@ mod ok { ); } #[test] + fn edition_2015_dyn_prefix_inside_generic_arg() { + run_and_expect_no_errors_with_edition( + "test_data/parser/inline/ok/edition_2015_dyn_prefix_inside_generic_arg.rs", + crate::Edition::Edition2015, + ); + } + #[test] fn effect_blocks() { run_and_expect_no_errors("test_data/parser/inline/ok/effect_blocks.rs"); } #[test] fn exclusive_range_pat() { @@ -220,10 +217,6 @@ mod ok { run_and_expect_no_errors("test_data/parser/inline/ok/extern_crate_rename.rs"); } #[test] - fn extern_crate_self() { - run_and_expect_no_errors("test_data/parser/inline/ok/extern_crate_self.rs"); - } - #[test] fn field_expr() { run_and_expect_no_errors("test_data/parser/inline/ok/field_expr.rs"); } #[test] fn fn_() { run_and_expect_no_errors("test_data/parser/inline/ok/fn_.rs"); } @@ -281,6 +274,10 @@ mod ok { #[test] fn generic_arg() { run_and_expect_no_errors("test_data/parser/inline/ok/generic_arg.rs"); } #[test] + fn generic_arg_bounds() { + run_and_expect_no_errors("test_data/parser/inline/ok/generic_arg_bounds.rs"); + } + #[test] fn generic_param_attribute() { run_and_expect_no_errors("test_data/parser/inline/ok/generic_param_attribute.rs"); } @@ -423,10 +420,6 @@ mod ok { #[test] fn param_list() { run_and_expect_no_errors("test_data/parser/inline/ok/param_list.rs"); } #[test] - fn param_list_opt_patterns() { - run_and_expect_no_errors("test_data/parser/inline/ok/param_list_opt_patterns.rs"); - } - #[test] fn param_list_vararg() { run_and_expect_no_errors("test_data/parser/inline/ok/param_list_vararg.rs"); } @@ -521,12 +514,6 @@ 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"); } @@ -685,10 +672,6 @@ mod ok { #[test] fn use_tree_star() { run_and_expect_no_errors("test_data/parser/inline/ok/use_tree_star.rs"); } #[test] - fn value_parameters_no_patterns() { - run_and_expect_no_errors("test_data/parser/inline/ok/value_parameters_no_patterns.rs"); - } - #[test] fn variant_discriminant() { run_and_expect_no_errors("test_data/parser/inline/ok/variant_discriminant.rs"); } @@ -728,6 +711,8 @@ mod err { run_and_expect_errors("test_data/parser/inline/err/async_without_semicolon.rs"); } #[test] + fn bad_asm_expr() { run_and_expect_errors("test_data/parser/inline/err/bad_asm_expr.rs"); } + #[test] fn comma_after_functional_update_syntax() { run_and_expect_errors( "test_data/parser/inline/err/comma_after_functional_update_syntax.rs", @@ -754,6 +739,10 @@ mod err { run_and_expect_errors("test_data/parser/inline/err/generic_arg_list_recover.rs"); } #[test] + fn generic_arg_list_recover_expr() { + run_and_expect_errors("test_data/parser/inline/err/generic_arg_list_recover_expr.rs"); + } + #[test] fn generic_param_list_recover() { run_and_expect_errors("test_data/parser/inline/err/generic_param_list_recover.rs"); } @@ -772,6 +761,8 @@ mod err { run_and_expect_errors("test_data/parser/inline/err/match_arms_recovery.rs"); } #[test] + fn meta_recovery() { run_and_expect_errors("test_data/parser/inline/err/meta_recovery.rs"); } + #[test] fn method_call_missing_argument_list() { run_and_expect_errors("test_data/parser/inline/err/method_call_missing_argument_list.rs"); } @@ -788,6 +779,10 @@ mod err { run_and_expect_errors("test_data/parser/inline/err/pointer_type_no_mutability.rs"); } #[test] + fn precise_capturing_invalid() { + run_and_expect_errors("test_data/parser/inline/err/precise_capturing_invalid.rs"); + } + #[test] fn pub_expr() { run_and_expect_errors("test_data/parser/inline/err/pub_expr.rs"); } #[test] fn record_literal_before_ellipsis_recovery() { diff --git a/crates/parser/test_data/parser/err/0004_use_path_bad_segment.rast b/crates/parser/test_data/parser/err/0004_use_path_bad_segment.rast index 44e192a5fc..7273c98456 100644 --- a/crates/parser/test_data/parser/err/0004_use_path_bad_segment.rast +++ b/crates/parser/test_data/parser/err/0004_use_path_bad_segment.rast @@ -9,7 +9,8 @@ SOURCE_FILE NAME_REF IDENT "foo" COLON2 "::" - ERROR - INT_NUMBER "92" + PATH_SEGMENT + ERROR + INT_NUMBER "92" SEMICOLON ";" -error 9: expected identifier +error 9: expected identifier, `self`, `super`, `crate`, or `Self` diff --git a/crates/parser/test_data/parser/err/0048_double_fish.rast b/crates/parser/test_data/parser/err/0048_double_fish.rast index 207a5c24df..47daaf7b3b 100644 --- a/crates/parser/test_data/parser/err/0048_double_fish.rast +++ b/crates/parser/test_data/parser/err/0048_double_fish.rast @@ -39,8 +39,9 @@ SOURCE_FILE IDENT "lol" R_ANGLE ">" COLON2 "::" - ERROR - L_ANGLE "<" + PATH_SEGMENT + ERROR + L_ANGLE "<" TYPE_ARG PATH_TYPE PATH @@ -91,8 +92,9 @@ SOURCE_FILE IDENT "lol" R_ANGLE ">" COLON2 "::" - ERROR - L_ANGLE "<" + PATH_SEGMENT + ERROR + L_ANGLE "<" EXPR_STMT BIN_EXPR PATH_EXPR @@ -113,10 +115,10 @@ SOURCE_FILE WHITESPACE "\n" R_CURLY "}" WHITESPACE "\n" -error 30: expected identifier +error 30: expected identifier, `self`, `super`, `crate`, or `Self` error 31: expected COMMA error 37: expected expression -error 75: expected identifier +error 75: expected identifier, `self`, `super`, `crate`, or `Self` error 76: expected SEMICOLON error 82: expected expression error 83: expected SEMICOLON diff --git a/crates/parser/test_data/parser/inline/err/arg_list_recovery.rast b/crates/parser/test_data/parser/inline/err/arg_list_recovery.rast index cd5aa680c6..755b20bb27 100644 --- a/crates/parser/test_data/parser/inline/err/arg_list_recovery.rast +++ b/crates/parser/test_data/parser/inline/err/arg_list_recovery.rast @@ -98,7 +98,7 @@ SOURCE_FILE WHITESPACE "\n" R_CURLY "}" WHITESPACE "\n" -error 25: expected identifier +error 25: expected identifier, `self`, `super`, `crate`, or `Self` error 39: expected COMMA error 39: expected expression error 55: expected expression diff --git a/crates/parser/test_data/parser/inline/err/bad_asm_expr.rast b/crates/parser/test_data/parser/inline/err/bad_asm_expr.rast new file mode 100644 index 0000000000..306446e64d --- /dev/null +++ b/crates/parser/test_data/parser/inline/err/bad_asm_expr.rast @@ -0,0 +1,50 @@ +SOURCE_FILE + FN + FN_KW "fn" + WHITESPACE " " + NAME + IDENT "foo" + PARAM_LIST + L_PAREN "(" + R_PAREN ")" + WHITESPACE " " + BLOCK_EXPR + STMT_LIST + L_CURLY "{" + WHITESPACE "\n " + EXPR_STMT + ASM_EXPR + BUILTIN_KW "builtin" + POUND "#" + ASM_KW "asm" + L_PAREN "(" + WHITESPACE "\n " + PATH_EXPR + PATH + PATH_SEGMENT + NAME_REF + IDENT "label" + WHITESPACE " " + NAME + IDENT "crashy" + WHITESPACE " " + EQ "=" + WHITESPACE " " + BLOCK_EXPR + STMT_LIST + L_CURLY "{" + WHITESPACE " " + EXPR_STMT + RETURN_EXPR + RETURN_KW "return" + SEMICOLON ";" + WHITESPACE " " + R_CURLY "}" + WHITESPACE "\n " + R_PAREN ")" + SEMICOLON ";" + WHITESPACE "\n" + R_CURLY "}" + WHITESPACE "\n" +error 41: expected COMMA +error 50: expected asm operand diff --git a/crates/parser/test_data/parser/inline/err/bad_asm_expr.rs b/crates/parser/test_data/parser/inline/err/bad_asm_expr.rs new file mode 100644 index 0000000000..6056f925e3 --- /dev/null +++ b/crates/parser/test_data/parser/inline/err/bad_asm_expr.rs @@ -0,0 +1,5 @@ +fn foo() { + builtin#asm( + label crashy = { return; } + ); +} diff --git a/crates/parser/test_data/parser/inline/err/crate_visibility_empty_recover.rast b/crates/parser/test_data/parser/inline/err/crate_visibility_empty_recover.rast index 0fe4ca42d7..172bc099b5 100644 --- a/crates/parser/test_data/parser/inline/err/crate_visibility_empty_recover.rast +++ b/crates/parser/test_data/parser/inline/err/crate_visibility_empty_recover.rast @@ -3,10 +3,7 @@ SOURCE_FILE VISIBILITY PUB_KW "pub" L_PAREN "(" - PATH - PATH_SEGMENT - ERROR - R_PAREN ")" + R_PAREN ")" WHITESPACE " " STRUCT_KW "struct" WHITESPACE " " @@ -14,5 +11,4 @@ SOURCE_FILE IDENT "S" SEMICOLON ";" WHITESPACE "\n" -error 4: expected identifier -error 5: expected R_PAREN +error 4: expected identifier, `self`, `super`, `crate`, or `Self` diff --git a/crates/parser/test_data/parser/inline/err/empty_segment.rast b/crates/parser/test_data/parser/inline/err/empty_segment.rast index b03f5ad9f7..7f256218ef 100644 --- a/crates/parser/test_data/parser/inline/err/empty_segment.rast +++ b/crates/parser/test_data/parser/inline/err/empty_segment.rast @@ -11,4 +11,4 @@ SOURCE_FILE COLON2 "::" SEMICOLON ";" WHITESPACE "\n" -error 11: expected identifier +error 11: expected identifier, `self`, `super`, `crate`, or `Self` diff --git a/crates/parser/test_data/parser/inline/err/generic_arg_list_recover.rast b/crates/parser/test_data/parser/inline/err/generic_arg_list_recover.rast index 4cf5a3386b..16183ce9ef 100644 --- a/crates/parser/test_data/parser/inline/err/generic_arg_list_recover.rast +++ b/crates/parser/test_data/parser/inline/err/generic_arg_list_recover.rast @@ -30,4 +30,37 @@ SOURCE_FILE R_ANGLE ">" SEMICOLON ";" WHITESPACE "\n" + TYPE_ALIAS + TYPE_KW "type" + WHITESPACE " " + NAME + IDENT "T" + WHITESPACE " " + EQ "=" + WHITESPACE " " + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "T" + GENERIC_ARG_LIST + COLON2 "::" + L_ANGLE "<" + CONST_ARG + LITERAL + INT_NUMBER "0" + COMMA "," + WHITESPACE " " + ERROR + COMMA "," + TYPE_ARG + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "T" + R_ANGLE ">" + SEMICOLON ";" + WHITESPACE "\n" error 14: expected generic argument +error 35: expected generic argument diff --git a/crates/parser/test_data/parser/inline/err/generic_arg_list_recover.rs b/crates/parser/test_data/parser/inline/err/generic_arg_list_recover.rs index 7d849aa1be..aa65a0673a 100644 --- a/crates/parser/test_data/parser/inline/err/generic_arg_list_recover.rs +++ b/crates/parser/test_data/parser/inline/err/generic_arg_list_recover.rs @@ -1 +1,2 @@ type T = T<0, ,T>; +type T = T::<0, ,T>; diff --git a/crates/parser/test_data/parser/inline/err/generic_arg_list_recover_expr.rast b/crates/parser/test_data/parser/inline/err/generic_arg_list_recover_expr.rast new file mode 100644 index 0000000000..de403d3049 --- /dev/null +++ b/crates/parser/test_data/parser/inline/err/generic_arg_list_recover_expr.rast @@ -0,0 +1,79 @@ +SOURCE_FILE + CONST + CONST_KW "const" + WHITESPACE " " + UNDERSCORE "_" + COLON ":" + WHITESPACE " " + TUPLE_TYPE + L_PAREN "(" + R_PAREN ")" + WHITESPACE " " + EQ "=" + WHITESPACE " " + PATH_EXPR + PATH + PATH_SEGMENT + NAME_REF + IDENT "T" + GENERIC_ARG_LIST + COLON2 "::" + L_ANGLE "<" + CONST_ARG + LITERAL + INT_NUMBER "0" + COMMA "," + WHITESPACE " " + ERROR + COMMA "," + TYPE_ARG + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "T" + R_ANGLE ">" + SEMICOLON ";" + WHITESPACE "\n" + CONST + CONST_KW "const" + WHITESPACE " " + UNDERSCORE "_" + COLON ":" + WHITESPACE " " + TUPLE_TYPE + L_PAREN "(" + R_PAREN ")" + WHITESPACE " " + EQ "=" + WHITESPACE " " + CALL_EXPR + PATH_EXPR + PATH + PATH_SEGMENT + NAME_REF + IDENT "T" + GENERIC_ARG_LIST + COLON2 "::" + L_ANGLE "<" + CONST_ARG + LITERAL + INT_NUMBER "0" + COMMA "," + WHITESPACE " " + ERROR + COMMA "," + TYPE_ARG + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "T" + R_ANGLE ">" + ARG_LIST + L_PAREN "(" + R_PAREN ")" + SEMICOLON ";" + WHITESPACE "\n" +error 21: expected generic argument +error 47: expected generic argument diff --git a/crates/parser/test_data/parser/inline/err/generic_arg_list_recover_expr.rs b/crates/parser/test_data/parser/inline/err/generic_arg_list_recover_expr.rs new file mode 100644 index 0000000000..74cc63ac4a --- /dev/null +++ b/crates/parser/test_data/parser/inline/err/generic_arg_list_recover_expr.rs @@ -0,0 +1,2 @@ +const _: () = T::<0, ,T>; +const _: () = T::<0, ,T>(); diff --git a/crates/parser/test_data/parser/inline/err/meta_recovery.rast b/crates/parser/test_data/parser/inline/err/meta_recovery.rast new file mode 100644 index 0000000000..b5c16e0798 --- /dev/null +++ b/crates/parser/test_data/parser/inline/err/meta_recovery.rast @@ -0,0 +1,83 @@ +SOURCE_FILE + ATTR + POUND "#" + BANG "!" + L_BRACK "[" + META + R_BRACK "]" + WHITESPACE "\n" + ATTR + POUND "#" + BANG "!" + L_BRACK "[" + META + PATH + PATH_SEGMENT + NAME_REF + IDENT "p" + WHITESPACE " " + EQ "=" + WHITESPACE " " + R_BRACK "]" + WHITESPACE "\n" + ATTR + POUND "#" + BANG "!" + L_BRACK "[" + META + PATH + PATH + PATH_SEGMENT + NAME_REF + IDENT "p" + COLON2 "::" + R_BRACK "]" + WHITESPACE "\n" + ATTR + POUND "#" + BANG "!" + L_BRACK "[" + META + PATH + PATH + PATH_SEGMENT + NAME_REF + IDENT "p" + COLON2 "::" + WHITESPACE " " + EQ "=" + R_BRACK "]" + WHITESPACE "\n" + ATTR + POUND "#" + BANG "!" + L_BRACK "[" + META + UNSAFE_KW "unsafe" + R_BRACK "]" + WHITESPACE "\n" + ATTR + POUND "#" + BANG "!" + L_BRACK "[" + META + UNSAFE_KW "unsafe" + WHITESPACE " " + EQ "=" + R_BRACK "]" + WHITESPACE "\n" +error 3: expected identifier, `self`, `super`, `crate`, or `Self` +error 11: expected expression +error 11: expected expression +error 20: expected identifier, `self`, `super`, `crate`, or `Self` +error 28: expected identifier, `self`, `super`, `crate`, or `Self` +error 30: expected expression +error 30: expected expression +error 41: expected L_PAREN +error 41: expected identifier, `self`, `super`, `crate`, or `Self` +error 41: expected R_PAREN +error 52: expected L_PAREN +error 52: expected identifier, `self`, `super`, `crate`, or `Self` +error 54: expected expression +error 54: expected expression +error 54: expected R_PAREN diff --git a/crates/parser/test_data/parser/inline/err/meta_recovery.rs b/crates/parser/test_data/parser/inline/err/meta_recovery.rs new file mode 100644 index 0000000000..51d30adf8b --- /dev/null +++ b/crates/parser/test_data/parser/inline/err/meta_recovery.rs @@ -0,0 +1,6 @@ +#![] +#![p = ] +#![p::] +#![p:: =] +#![unsafe] +#![unsafe =] diff --git a/crates/parser/test_data/parser/inline/err/precise_capturing_invalid.rast b/crates/parser/test_data/parser/inline/err/precise_capturing_invalid.rast new file mode 100644 index 0000000000..5ae184c5fe --- /dev/null +++ b/crates/parser/test_data/parser/inline/err/precise_capturing_invalid.rast @@ -0,0 +1,28 @@ +SOURCE_FILE + TYPE_ALIAS + TYPE_KW "type" + WHITESPACE " " + NAME + IDENT "T" + WHITESPACE " " + EQ "=" + WHITESPACE " " + IMPL_TRAIT_TYPE + IMPL_KW "impl" + WHITESPACE " " + TYPE_BOUND_LIST + TYPE_BOUND + USE_KW "use" + USE_BOUND_GENERIC_ARGS + L_ANGLE "<" + ERROR + SELF_KW "self" + COMMA "," + WHITESPACE " " + ERROR + INT_NUMBER "1" + R_ANGLE ">" + SEMICOLON ";" + WHITESPACE "\n" +error 18: expected identifier or `Self` +error 24: expected identifier or `Self` diff --git a/crates/parser/test_data/parser/inline/err/precise_capturing_invalid.rs b/crates/parser/test_data/parser/inline/err/precise_capturing_invalid.rs new file mode 100644 index 0000000000..3180338d33 --- /dev/null +++ b/crates/parser/test_data/parser/inline/err/precise_capturing_invalid.rs @@ -0,0 +1 @@ +type T = impl use<self, 1>; diff --git a/crates/parser/test_data/parser/inline/err/record_literal_before_ellipsis_recovery.rast b/crates/parser/test_data/parser/inline/err/record_literal_before_ellipsis_recovery.rast index 741b7845e7..08ae906421 100644 --- a/crates/parser/test_data/parser/inline/err/record_literal_before_ellipsis_recovery.rast +++ b/crates/parser/test_data/parser/inline/err/record_literal_before_ellipsis_recovery.rast @@ -12,6 +12,38 @@ SOURCE_FILE STMT_LIST L_CURLY "{" WHITESPACE "\n " + EXPR_STMT + RECORD_EXPR + PATH + PATH_SEGMENT + NAME_REF + IDENT "S" + WHITESPACE " " + RECORD_EXPR_FIELD_LIST + L_CURLY "{" + WHITESPACE " " + RECORD_EXPR_FIELD + NAME_REF + IDENT "field" + WHITESPACE " " + DOT2 ".." + CALL_EXPR + PATH_EXPR + PATH + PATH + PATH_SEGMENT + NAME_REF + IDENT "S" + COLON2 "::" + PATH_SEGMENT + NAME_REF + IDENT "default" + ARG_LIST + L_PAREN "(" + R_PAREN ")" + WHITESPACE " " + R_CURLY "}" + WHITESPACE "\n " RECORD_EXPR PATH PATH_SEGMENT @@ -23,7 +55,7 @@ SOURCE_FILE WHITESPACE " " RECORD_EXPR_FIELD NAME_REF - IDENT "field" + INT_NUMBER "0" WHITESPACE " " DOT2 ".." CALL_EXPR @@ -47,3 +79,6 @@ SOURCE_FILE WHITESPACE "\n" error 25: expected `:` error 25: expected COMMA +error 42: expected SEMICOLON +error 52: expected `:` +error 52: expected COMMA diff --git a/crates/parser/test_data/parser/inline/err/record_literal_before_ellipsis_recovery.rs b/crates/parser/test_data/parser/inline/err/record_literal_before_ellipsis_recovery.rs index a4e5b2f693..65398ccb88 100644 --- a/crates/parser/test_data/parser/inline/err/record_literal_before_ellipsis_recovery.rs +++ b/crates/parser/test_data/parser/inline/err/record_literal_before_ellipsis_recovery.rs @@ -1,3 +1,4 @@ fn main() { S { field ..S::default() } + S { 0 ..S::default() } } diff --git a/crates/parser/test_data/parser/inline/err/record_literal_field_eq_recovery.rast b/crates/parser/test_data/parser/inline/err/record_literal_field_eq_recovery.rast index ad4deeb0b6..ad3b6f208e 100644 --- a/crates/parser/test_data/parser/inline/err/record_literal_field_eq_recovery.rast +++ b/crates/parser/test_data/parser/inline/err/record_literal_field_eq_recovery.rast @@ -12,6 +12,31 @@ SOURCE_FILE STMT_LIST L_CURLY "{" WHITESPACE "\n " + EXPR_STMT + RECORD_EXPR + PATH + PATH_SEGMENT + NAME_REF + IDENT "S" + WHITESPACE " " + RECORD_EXPR_FIELD_LIST + L_CURLY "{" + WHITESPACE " " + RECORD_EXPR_FIELD + NAME_REF + IDENT "field" + WHITESPACE " " + ERROR + EQ "=" + WHITESPACE " " + PATH_EXPR + PATH + PATH_SEGMENT + NAME_REF + IDENT "foo" + WHITESPACE " " + R_CURLY "}" + WHITESPACE "\n " RECORD_EXPR PATH PATH_SEGMENT @@ -23,7 +48,7 @@ SOURCE_FILE WHITESPACE " " RECORD_EXPR_FIELD NAME_REF - IDENT "field" + INT_NUMBER "0" WHITESPACE " " ERROR EQ "=" @@ -39,3 +64,5 @@ SOURCE_FILE R_CURLY "}" WHITESPACE "\n" error 26: expected `:` +error 33: expected SEMICOLON +error 44: expected `:` diff --git a/crates/parser/test_data/parser/inline/err/record_literal_field_eq_recovery.rs b/crates/parser/test_data/parser/inline/err/record_literal_field_eq_recovery.rs index 1eb1aa9b92..9ddc46e0da 100644 --- a/crates/parser/test_data/parser/inline/err/record_literal_field_eq_recovery.rs +++ b/crates/parser/test_data/parser/inline/err/record_literal_field_eq_recovery.rs @@ -1,3 +1,4 @@ fn main() { S { field = foo } + S { 0 = foo } } diff --git a/crates/parser/test_data/parser/inline/err/record_literal_missing_ellipsis_recovery.rast b/crates/parser/test_data/parser/inline/err/record_literal_missing_ellipsis_recovery.rast index 0c5b618e6f..9cd07d2ea3 100644 --- a/crates/parser/test_data/parser/inline/err/record_literal_missing_ellipsis_recovery.rast +++ b/crates/parser/test_data/parser/inline/err/record_literal_missing_ellipsis_recovery.rast @@ -12,32 +12,70 @@ SOURCE_FILE STMT_LIST L_CURLY "{" WHITESPACE "\n " - RECORD_EXPR - PATH - PATH_SEGMENT - NAME_REF - IDENT "S" - WHITESPACE " " - RECORD_EXPR_FIELD_LIST - L_CURLY "{" + EXPR_STMT + RECORD_EXPR + PATH + PATH_SEGMENT + NAME_REF + IDENT "S" WHITESPACE " " - CALL_EXPR - PATH_EXPR - PATH + RECORD_EXPR_FIELD_LIST + L_CURLY "{" + WHITESPACE " " + CALL_EXPR + PATH_EXPR PATH + PATH + PATH_SEGMENT + NAME_REF + IDENT "S" + COLON2 "::" PATH_SEGMENT NAME_REF - IDENT "S" - COLON2 "::" - PATH_SEGMENT - NAME_REF - IDENT "default" - ARG_LIST - L_PAREN "(" - R_PAREN ")" + IDENT "default" + ARG_LIST + L_PAREN "(" + R_PAREN ")" + WHITESPACE " " + R_CURLY "}" + SEMICOLON ";" + WHITESPACE "\n " + EXPR_STMT + RECORD_EXPR + PATH + PATH_SEGMENT + NAME_REF + IDENT "S" WHITESPACE " " - R_CURLY "}" + RECORD_EXPR_FIELD_LIST + L_CURLY "{" + WHITESPACE " " + LITERAL + INT_NUMBER "0" + ERROR + COLON ":" + ERROR + COLON ":" + RECORD_EXPR_FIELD + CALL_EXPR + PATH_EXPR + PATH + PATH_SEGMENT + NAME_REF + IDENT "default" + ARG_LIST + L_PAREN "(" + R_PAREN ")" + WHITESPACE " " + R_CURLY "}" + SEMICOLON ";" WHITESPACE "\n" R_CURLY "}" WHITESPACE "\n" error 19: expected DOT2 +error 43: expected DOT2 +error 45: expected COMMA +error 45: expected identifier +error 46: expected COMMA +error 46: expected identifier +error 47: expected COMMA diff --git a/crates/parser/test_data/parser/inline/err/record_literal_missing_ellipsis_recovery.rs b/crates/parser/test_data/parser/inline/err/record_literal_missing_ellipsis_recovery.rs index 1b594e8ab9..a63c3c9e7c 100644 --- a/crates/parser/test_data/parser/inline/err/record_literal_missing_ellipsis_recovery.rs +++ b/crates/parser/test_data/parser/inline/err/record_literal_missing_ellipsis_recovery.rs @@ -1,3 +1,4 @@ fn main() { - S { S::default() } + S { S::default() }; + S { 0::default() }; } diff --git a/crates/parser/test_data/parser/inline/ok/assoc_type_bound.rast b/crates/parser/test_data/parser/inline/ok/assoc_type_bound.rast index f2e4e01069..0f62e1dd18 100644 --- a/crates/parser/test_data/parser/inline/ok/assoc_type_bound.rast +++ b/crates/parser/test_data/parser/inline/ok/assoc_type_bound.rast @@ -35,3 +35,42 @@ SOURCE_FILE R_ANGLE ">" SEMICOLON ";" WHITESPACE "\n" + TYPE_ALIAS + TYPE_KW "type" + WHITESPACE " " + NAME + IDENT "T" + WHITESPACE " " + EQ "=" + WHITESPACE " " + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "StreamingIterator" + GENERIC_ARG_LIST + L_ANGLE "<" + ASSOC_TYPE_ARG + NAME_REF + IDENT "Item" + PARENTHESIZED_ARG_LIST + L_PAREN "(" + TYPE_ARG + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "T" + R_PAREN ")" + COLON ":" + WHITESPACE " " + TYPE_BOUND_LIST + TYPE_BOUND + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Clone" + R_ANGLE ">" + SEMICOLON ";" + WHITESPACE "\n" diff --git a/crates/parser/test_data/parser/inline/ok/assoc_type_bound.rs b/crates/parser/test_data/parser/inline/ok/assoc_type_bound.rs index daae97e4fd..83fea1c6c8 100644 --- a/crates/parser/test_data/parser/inline/ok/assoc_type_bound.rs +++ b/crates/parser/test_data/parser/inline/ok/assoc_type_bound.rs @@ -1 +1,2 @@ type T = StreamingIterator<Item<'a>: Clone>; +type T = StreamingIterator<Item(T): Clone>; diff --git a/crates/parser/test_data/parser/inline/ok/associated_type_bounds.rast b/crates/parser/test_data/parser/inline/ok/associated_type_bounds.rast deleted file mode 100644 index 8cbc98c51c..0000000000 --- a/crates/parser/test_data/parser/inline/ok/associated_type_bounds.rast +++ /dev/null @@ -1,111 +0,0 @@ -SOURCE_FILE - FN - FN_KW "fn" - WHITESPACE " " - NAME - IDENT "print_all" - 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 "Iterator" - GENERIC_ARG_LIST - L_ANGLE "<" - TYPE_ARG - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "Item" - COMMA "," - WHITESPACE " " - TYPE_ARG - PATH_TYPE - PATH - PATH - PATH_SEGMENT - NAME_REF - IDENT "Item" - COLON2 "::" - PATH_SEGMENT - NAME_REF - IDENT "Item" - COMMA "," - WHITESPACE " " - TYPE_ARG - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "Item" - GENERIC_ARG_LIST - COLON2 "::" - L_ANGLE "<" - CONST_ARG - LITERAL - TRUE_KW "true" - R_ANGLE ">" - COMMA "," - WHITESPACE " " - ASSOC_TYPE_ARG - NAME_REF - IDENT "Item" - COLON ":" - WHITESPACE " " - TYPE_BOUND_LIST - TYPE_BOUND - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "Display" - COMMA "," - WHITESPACE " " - ASSOC_TYPE_ARG - NAME_REF - IDENT "Item" - GENERIC_ARG_LIST - L_ANGLE "<" - LIFETIME_ARG - LIFETIME - LIFETIME_IDENT "'a" - R_ANGLE ">" - WHITESPACE " " - EQ "=" - WHITESPACE " " - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "Item" - R_ANGLE ">" - R_ANGLE ">" - PARAM_LIST - L_PAREN "(" - PARAM - IDENT_PAT - NAME - IDENT "printables" - COLON ":" - WHITESPACE " " - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "T" - R_PAREN ")" - WHITESPACE " " - BLOCK_EXPR - STMT_LIST - L_CURLY "{" - R_CURLY "}" - WHITESPACE "\n" diff --git a/crates/parser/test_data/parser/inline/ok/associated_type_bounds.rs b/crates/parser/test_data/parser/inline/ok/associated_type_bounds.rs deleted file mode 100644 index 0f7a2d1608..0000000000 --- a/crates/parser/test_data/parser/inline/ok/associated_type_bounds.rs +++ /dev/null @@ -1 +0,0 @@ -fn print_all<T: Iterator<Item, Item::Item, Item::<true>, Item: Display, Item<'a> = Item>>(printables: T) {} diff --git a/crates/parser/test_data/parser/inline/ok/async_trait_bound.rast b/crates/parser/test_data/parser/inline/ok/async_trait_bound.rast index ebf758286a..df0ba55d03 100644 --- a/crates/parser/test_data/parser/inline/ok/async_trait_bound.rast +++ b/crates/parser/test_data/parser/inline/ok/async_trait_bound.rast @@ -23,9 +23,9 @@ SOURCE_FILE PATH_SEGMENT NAME_REF IDENT "Fn" - PARAM_LIST + PARENTHESIZED_ARG_LIST L_PAREN "(" - PARAM + TYPE_ARG REF_TYPE AMP "&" PATH_TYPE diff --git a/crates/parser/test_data/parser/inline/ok/bare_dyn_types_with_paren_as_generic_args.rast b/crates/parser/test_data/parser/inline/ok/bare_dyn_types_with_paren_as_generic_args.rast deleted file mode 100644 index d5f97bad89..0000000000 --- a/crates/parser/test_data/parser/inline/ok/bare_dyn_types_with_paren_as_generic_args.rast +++ /dev/null @@ -1,175 +0,0 @@ -SOURCE_FILE - TYPE_ALIAS - TYPE_KW "type" - WHITESPACE " " - NAME - IDENT "A" - WHITESPACE " " - EQ "=" - WHITESPACE " " - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "S" - GENERIC_ARG_LIST - L_ANGLE "<" - TYPE_ARG - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "Fn" - PARAM_LIST - L_PAREN "(" - PARAM - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "i32" - R_PAREN ")" - R_ANGLE ">" - SEMICOLON ";" - WHITESPACE "\n" - TYPE_ALIAS - TYPE_KW "type" - WHITESPACE " " - NAME - IDENT "A" - WHITESPACE " " - EQ "=" - WHITESPACE " " - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "S" - GENERIC_ARG_LIST - L_ANGLE "<" - TYPE_ARG - DYN_TRAIT_TYPE - TYPE_BOUND_LIST - TYPE_BOUND - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "Fn" - PARAM_LIST - L_PAREN "(" - PARAM - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "i32" - R_PAREN ")" - WHITESPACE " " - PLUS "+" - WHITESPACE " " - TYPE_BOUND - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "Send" - R_ANGLE ">" - SEMICOLON ";" - WHITESPACE "\n" - TYPE_ALIAS - TYPE_KW "type" - WHITESPACE " " - NAME - IDENT "B" - WHITESPACE " " - EQ "=" - WHITESPACE " " - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "S" - GENERIC_ARG_LIST - L_ANGLE "<" - TYPE_ARG - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "Fn" - PARAM_LIST - L_PAREN "(" - PARAM - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "i32" - R_PAREN ")" - WHITESPACE " " - RET_TYPE - THIN_ARROW "->" - WHITESPACE " " - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "i32" - R_ANGLE ">" - SEMICOLON ";" - WHITESPACE "\n" - TYPE_ALIAS - TYPE_KW "type" - WHITESPACE " " - NAME - IDENT "C" - WHITESPACE " " - EQ "=" - WHITESPACE " " - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "S" - GENERIC_ARG_LIST - L_ANGLE "<" - TYPE_ARG - DYN_TRAIT_TYPE - TYPE_BOUND_LIST - TYPE_BOUND - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "Fn" - PARAM_LIST - L_PAREN "(" - PARAM - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "i32" - R_PAREN ")" - WHITESPACE " " - RET_TYPE - THIN_ARROW "->" - WHITESPACE " " - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "i32" - WHITESPACE " " - PLUS "+" - WHITESPACE " " - TYPE_BOUND - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "Send" - R_ANGLE ">" - SEMICOLON ";" - WHITESPACE "\n" diff --git a/crates/parser/test_data/parser/inline/ok/bare_dyn_types_with_paren_as_generic_args.rs b/crates/parser/test_data/parser/inline/ok/bare_dyn_types_with_paren_as_generic_args.rs deleted file mode 100644 index 800002b1b8..0000000000 --- a/crates/parser/test_data/parser/inline/ok/bare_dyn_types_with_paren_as_generic_args.rs +++ /dev/null @@ -1,4 +0,0 @@ -type A = S<Fn(i32)>; -type A = S<Fn(i32) + Send>; -type B = S<Fn(i32) -> i32>; -type C = S<Fn(i32) -> i32 + Send>; diff --git a/crates/parser/test_data/parser/inline/ok/edition_2015_dyn_prefix_inside_generic_arg.rast b/crates/parser/test_data/parser/inline/ok/edition_2015_dyn_prefix_inside_generic_arg.rast new file mode 100644 index 0000000000..24e671b343 --- /dev/null +++ b/crates/parser/test_data/parser/inline/ok/edition_2015_dyn_prefix_inside_generic_arg.rast @@ -0,0 +1,32 @@ +SOURCE_FILE + TYPE_ALIAS + COMMENT "// 2015" + WHITESPACE "\n" + TYPE_KW "type" + WHITESPACE " " + NAME + IDENT "A" + WHITESPACE " " + EQ "=" + WHITESPACE " " + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Foo" + GENERIC_ARG_LIST + L_ANGLE "<" + TYPE_ARG + DYN_TRAIT_TYPE + DYN_KW "dyn" + WHITESPACE " " + TYPE_BOUND_LIST + TYPE_BOUND + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "T" + R_ANGLE ">" + SEMICOLON ";" + WHITESPACE "\n" diff --git a/crates/parser/test_data/parser/inline/ok/edition_2015_dyn_prefix_inside_generic_arg.rs b/crates/parser/test_data/parser/inline/ok/edition_2015_dyn_prefix_inside_generic_arg.rs new file mode 100644 index 0000000000..84cece5748 --- /dev/null +++ b/crates/parser/test_data/parser/inline/ok/edition_2015_dyn_prefix_inside_generic_arg.rs @@ -0,0 +1,2 @@ +// 2015 +type A = Foo<dyn T>; diff --git a/crates/parser/test_data/parser/inline/ok/extern_crate.rast b/crates/parser/test_data/parser/inline/ok/extern_crate.rast index 0a660957d1..aa555ed229 100644 --- a/crates/parser/test_data/parser/inline/ok/extern_crate.rast +++ b/crates/parser/test_data/parser/inline/ok/extern_crate.rast @@ -8,3 +8,12 @@ SOURCE_FILE IDENT "foo" SEMICOLON ";" WHITESPACE "\n" + EXTERN_CRATE + EXTERN_KW "extern" + WHITESPACE " " + CRATE_KW "crate" + WHITESPACE " " + NAME_REF + SELF_KW "self" + SEMICOLON ";" + WHITESPACE "\n" diff --git a/crates/parser/test_data/parser/inline/ok/extern_crate.rs b/crates/parser/test_data/parser/inline/ok/extern_crate.rs index 49af74e1b7..3c498c8738 100644 --- a/crates/parser/test_data/parser/inline/ok/extern_crate.rs +++ b/crates/parser/test_data/parser/inline/ok/extern_crate.rs @@ -1 +1,2 @@ extern crate foo; +extern crate self; diff --git a/crates/parser/test_data/parser/inline/ok/extern_crate_rename.rast b/crates/parser/test_data/parser/inline/ok/extern_crate_rename.rast index 5a5aca96f9..5f0a5b5bb0 100644 --- a/crates/parser/test_data/parser/inline/ok/extern_crate_rename.rast +++ b/crates/parser/test_data/parser/inline/ok/extern_crate_rename.rast @@ -14,3 +14,18 @@ SOURCE_FILE IDENT "bar" SEMICOLON ";" WHITESPACE "\n" + EXTERN_CRATE + EXTERN_KW "extern" + WHITESPACE " " + CRATE_KW "crate" + WHITESPACE " " + NAME_REF + SELF_KW "self" + WHITESPACE " " + RENAME + AS_KW "as" + WHITESPACE " " + NAME + IDENT "bar" + SEMICOLON ";" + WHITESPACE "\n" diff --git a/crates/parser/test_data/parser/inline/ok/extern_crate_rename.rs b/crates/parser/test_data/parser/inline/ok/extern_crate_rename.rs index fc76e17dda..6d1873d659 100644 --- a/crates/parser/test_data/parser/inline/ok/extern_crate_rename.rs +++ b/crates/parser/test_data/parser/inline/ok/extern_crate_rename.rs @@ -1 +1,2 @@ extern crate foo as bar; +extern crate self as bar; diff --git a/crates/parser/test_data/parser/inline/ok/extern_crate_self.rast b/crates/parser/test_data/parser/inline/ok/extern_crate_self.rast deleted file mode 100644 index edea4245f2..0000000000 --- a/crates/parser/test_data/parser/inline/ok/extern_crate_self.rast +++ /dev/null @@ -1,10 +0,0 @@ -SOURCE_FILE - EXTERN_CRATE - EXTERN_KW "extern" - WHITESPACE " " - CRATE_KW "crate" - WHITESPACE " " - NAME_REF - SELF_KW "self" - SEMICOLON ";" - WHITESPACE "\n" diff --git a/crates/parser/test_data/parser/inline/ok/extern_crate_self.rs b/crates/parser/test_data/parser/inline/ok/extern_crate_self.rs deleted file mode 100644 index c969ed1093..0000000000 --- a/crates/parser/test_data/parser/inline/ok/extern_crate_self.rs +++ /dev/null @@ -1 +0,0 @@ -extern crate self; diff --git a/crates/parser/test_data/parser/inline/ok/field_expr.rast b/crates/parser/test_data/parser/inline/ok/field_expr.rast index dd27dc4896..3bac226d34 100644 --- a/crates/parser/test_data/parser/inline/ok/field_expr.rast +++ b/crates/parser/test_data/parser/inline/ok/field_expr.rast @@ -21,6 +21,30 @@ SOURCE_FILE IDENT "x" DOT "." NAME_REF + SELF_KW "self" + SEMICOLON ";" + WHITESPACE "\n " + EXPR_STMT + FIELD_EXPR + PATH_EXPR + PATH + PATH_SEGMENT + NAME_REF + IDENT "x" + DOT "." + NAME_REF + SELF_TYPE_KW "Self" + SEMICOLON ";" + WHITESPACE "\n " + EXPR_STMT + FIELD_EXPR + PATH_EXPR + PATH + PATH_SEGMENT + NAME_REF + IDENT "x" + DOT "." + NAME_REF IDENT "foo" SEMICOLON ";" WHITESPACE "\n " diff --git a/crates/parser/test_data/parser/inline/ok/field_expr.rs b/crates/parser/test_data/parser/inline/ok/field_expr.rs index 98dbe45a7e..b156d6f7d8 100644 --- a/crates/parser/test_data/parser/inline/ok/field_expr.rs +++ b/crates/parser/test_data/parser/inline/ok/field_expr.rs @@ -1,4 +1,6 @@ fn foo() { + x.self; + x.Self; x.foo; x.0.bar; x.0.1; diff --git a/crates/parser/test_data/parser/inline/ok/generic_arg.rast b/crates/parser/test_data/parser/inline/ok/generic_arg.rast index 5a01f154ba..e32cf08565 100644 --- a/crates/parser/test_data/parser/inline/ok/generic_arg.rast +++ b/crates/parser/test_data/parser/inline/ok/generic_arg.rast @@ -20,6 +20,27 @@ SOURCE_FILE PATH_SEGMENT NAME_REF IDENT "i32" + COMMA "," + WHITESPACE " " + TYPE_ARG + DYN_TRAIT_TYPE + DYN_KW "dyn" + WHITESPACE " " + TYPE_BOUND_LIST + TYPE_BOUND + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "T" + COMMA "," + WHITESPACE " " + TYPE_ARG + FN_PTR_TYPE + FN_KW "fn" + PARAM_LIST + L_PAREN "(" + R_PAREN ")" R_ANGLE ">" SEMICOLON ";" WHITESPACE "\n" diff --git a/crates/parser/test_data/parser/inline/ok/generic_arg.rs b/crates/parser/test_data/parser/inline/ok/generic_arg.rs index f2ccc558bb..cf991b5b36 100644 --- a/crates/parser/test_data/parser/inline/ok/generic_arg.rs +++ b/crates/parser/test_data/parser/inline/ok/generic_arg.rs @@ -1 +1 @@ -type T = S<i32>; +type T = S<i32, dyn T, fn()>; diff --git a/crates/parser/test_data/parser/inline/ok/generic_arg_bounds.rast b/crates/parser/test_data/parser/inline/ok/generic_arg_bounds.rast new file mode 100644 index 0000000000..fee5913aca --- /dev/null +++ b/crates/parser/test_data/parser/inline/ok/generic_arg_bounds.rast @@ -0,0 +1,461 @@ +SOURCE_FILE + TYPE_ALIAS + TYPE_KW "type" + WHITESPACE " " + NAME + IDENT "Plain" + WHITESPACE " " + EQ "=" + WHITESPACE " " + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Foo" + GENERIC_ARG_LIST + L_ANGLE "<" + TYPE_ARG + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Item" + COMMA "," + WHITESPACE " " + TYPE_ARG + PATH_TYPE + PATH + PATH + PATH_SEGMENT + NAME_REF + IDENT "Item" + COLON2 "::" + PATH_SEGMENT + NAME_REF + IDENT "Item" + COMMA "," + WHITESPACE " " + ASSOC_TYPE_ARG + NAME_REF + IDENT "Item" + COLON ":" + WHITESPACE " " + TYPE_BOUND_LIST + TYPE_BOUND + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Bound" + COMMA "," + WHITESPACE " " + ASSOC_TYPE_ARG + NAME_REF + IDENT "Item" + WHITESPACE " " + EQ "=" + WHITESPACE " " + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Item" + R_ANGLE ">" + SEMICOLON ";" + WHITESPACE "\n" + TYPE_ALIAS + TYPE_KW "type" + WHITESPACE " " + NAME + IDENT "GenericArgs" + WHITESPACE " " + EQ "=" + WHITESPACE " " + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Foo" + GENERIC_ARG_LIST + L_ANGLE "<" + TYPE_ARG + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Item" + GENERIC_ARG_LIST + L_ANGLE "<" + TYPE_ARG + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "T" + R_ANGLE ">" + COMMA "," + WHITESPACE " " + TYPE_ARG + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Item" + GENERIC_ARG_LIST + COLON2 "::" + L_ANGLE "<" + TYPE_ARG + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "T" + R_ANGLE ">" + COMMA "," + WHITESPACE " " + ASSOC_TYPE_ARG + NAME_REF + IDENT "Item" + GENERIC_ARG_LIST + L_ANGLE "<" + TYPE_ARG + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "T" + R_ANGLE ">" + COLON ":" + WHITESPACE " " + TYPE_BOUND_LIST + TYPE_BOUND + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Bound" + COMMA "," + WHITESPACE " " + ASSOC_TYPE_ARG + NAME_REF + IDENT "Item" + GENERIC_ARG_LIST + COLON2 "::" + L_ANGLE "<" + TYPE_ARG + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "T" + R_ANGLE ">" + COLON ":" + WHITESPACE " " + TYPE_BOUND_LIST + TYPE_BOUND + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Bound" + COMMA "," + WHITESPACE " " + ASSOC_TYPE_ARG + NAME_REF + IDENT "Item" + GENERIC_ARG_LIST + L_ANGLE "<" + TYPE_ARG + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "T" + R_ANGLE ">" + WHITESPACE " " + EQ "=" + WHITESPACE " " + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Item" + COMMA "," + WHITESPACE " " + ASSOC_TYPE_ARG + NAME_REF + IDENT "Item" + GENERIC_ARG_LIST + COLON2 "::" + L_ANGLE "<" + TYPE_ARG + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "T" + R_ANGLE ">" + WHITESPACE " " + EQ "=" + WHITESPACE " " + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Item" + R_ANGLE ">" + SEMICOLON ";" + WHITESPACE "\n" + TYPE_ALIAS + TYPE_KW "type" + WHITESPACE " " + NAME + IDENT "ParenthesizedArgs" + WHITESPACE " " + EQ "=" + WHITESPACE " " + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Foo" + GENERIC_ARG_LIST + L_ANGLE "<" + TYPE_ARG + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Item" + PARENTHESIZED_ARG_LIST + L_PAREN "(" + TYPE_ARG + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "T" + R_PAREN ")" + COMMA "," + WHITESPACE " " + TYPE_ARG + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Item" + PARENTHESIZED_ARG_LIST + COLON2 "::" + L_PAREN "(" + TYPE_ARG + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "T" + R_PAREN ")" + COMMA "," + WHITESPACE " " + ASSOC_TYPE_ARG + NAME_REF + IDENT "Item" + PARENTHESIZED_ARG_LIST + L_PAREN "(" + TYPE_ARG + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "T" + R_PAREN ")" + COLON ":" + WHITESPACE " " + TYPE_BOUND_LIST + TYPE_BOUND + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Bound" + COMMA "," + WHITESPACE " " + ASSOC_TYPE_ARG + NAME_REF + IDENT "Item" + PARENTHESIZED_ARG_LIST + COLON2 "::" + L_PAREN "(" + TYPE_ARG + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "T" + R_PAREN ")" + COLON ":" + WHITESPACE " " + TYPE_BOUND_LIST + TYPE_BOUND + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Bound" + COMMA "," + WHITESPACE " " + ASSOC_TYPE_ARG + NAME_REF + IDENT "Item" + PARENTHESIZED_ARG_LIST + L_PAREN "(" + TYPE_ARG + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "T" + R_PAREN ")" + WHITESPACE " " + EQ "=" + WHITESPACE " " + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Item" + COMMA "," + WHITESPACE " " + ASSOC_TYPE_ARG + NAME_REF + IDENT "Item" + PARENTHESIZED_ARG_LIST + COLON2 "::" + L_PAREN "(" + TYPE_ARG + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "T" + R_PAREN ")" + WHITESPACE " " + EQ "=" + WHITESPACE " " + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Item" + R_ANGLE ">" + SEMICOLON ";" + WHITESPACE "\n" + TYPE_ALIAS + TYPE_KW "type" + WHITESPACE " " + NAME + IDENT "RTN" + WHITESPACE " " + EQ "=" + WHITESPACE " " + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Foo" + GENERIC_ARG_LIST + L_ANGLE "<" + TYPE_ARG + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Item" + RETURN_TYPE_SYNTAX + L_PAREN "(" + DOT2 ".." + R_PAREN ")" + COMMA "," + WHITESPACE " " + TYPE_ARG + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Item" + RETURN_TYPE_SYNTAX + L_PAREN "(" + DOT2 ".." + R_PAREN ")" + COMMA "," + WHITESPACE " " + ASSOC_TYPE_ARG + NAME_REF + IDENT "Item" + RETURN_TYPE_SYNTAX + L_PAREN "(" + DOT2 ".." + R_PAREN ")" + COLON ":" + WHITESPACE " " + TYPE_BOUND_LIST + TYPE_BOUND + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Bound" + COMMA "," + WHITESPACE " " + ASSOC_TYPE_ARG + NAME_REF + IDENT "Item" + RETURN_TYPE_SYNTAX + L_PAREN "(" + DOT2 ".." + R_PAREN ")" + COLON ":" + WHITESPACE " " + TYPE_BOUND_LIST + TYPE_BOUND + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Bound" + COMMA "," + WHITESPACE " " + ASSOC_TYPE_ARG + NAME_REF + IDENT "Item" + RETURN_TYPE_SYNTAX + L_PAREN "(" + DOT2 ".." + R_PAREN ")" + WHITESPACE " " + EQ "=" + WHITESPACE " " + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Item" + COMMA "," + WHITESPACE " " + ASSOC_TYPE_ARG + NAME_REF + IDENT "Item" + RETURN_TYPE_SYNTAX + L_PAREN "(" + DOT2 ".." + R_PAREN ")" + WHITESPACE " " + EQ "=" + WHITESPACE " " + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Item" + R_ANGLE ">" + SEMICOLON ";" + WHITESPACE "\n" diff --git a/crates/parser/test_data/parser/inline/ok/generic_arg_bounds.rs b/crates/parser/test_data/parser/inline/ok/generic_arg_bounds.rs new file mode 100644 index 0000000000..1abd0aeb59 --- /dev/null +++ b/crates/parser/test_data/parser/inline/ok/generic_arg_bounds.rs @@ -0,0 +1,4 @@ +type Plain = Foo<Item, Item::Item, Item: Bound, Item = Item>; +type GenericArgs = Foo<Item<T>, Item::<T>, Item<T>: Bound, Item::<T>: Bound, Item<T> = Item, Item::<T> = Item>; +type ParenthesizedArgs = Foo<Item(T), Item::(T), Item(T): Bound, Item::(T): Bound, Item(T) = Item, Item::(T) = Item>; +type RTN = Foo<Item(..), Item(..), Item(..): Bound, Item(..): Bound, Item(..) = Item, Item(..) = Item>; diff --git a/crates/parser/test_data/parser/inline/ok/lifetime_param.rast b/crates/parser/test_data/parser/inline/ok/lifetime_param.rast index c595031f35..315200aca2 100644 --- a/crates/parser/test_data/parser/inline/ok/lifetime_param.rast +++ b/crates/parser/test_data/parser/inline/ok/lifetime_param.rast @@ -11,8 +11,10 @@ SOURCE_FILE LIFETIME_IDENT "'a" COLON ":" WHITESPACE " " - LIFETIME - LIFETIME_IDENT "'b" + TYPE_BOUND_LIST + TYPE_BOUND + LIFETIME + LIFETIME_IDENT "'b" R_ANGLE ">" PARAM_LIST L_PAREN "(" diff --git a/crates/parser/test_data/parser/inline/ok/method_call_expr.rast b/crates/parser/test_data/parser/inline/ok/method_call_expr.rast index b28b8eb673..3245042cf2 100644 --- a/crates/parser/test_data/parser/inline/ok/method_call_expr.rast +++ b/crates/parser/test_data/parser/inline/ok/method_call_expr.rast @@ -101,6 +101,20 @@ SOURCE_FILE L_PAREN "(" R_PAREN ")" SEMICOLON ";" + WHITESPACE "\n " + CALL_EXPR + FIELD_EXPR + PATH_EXPR + PATH + PATH_SEGMENT + NAME_REF + IDENT "x" + DOT "." + NAME_REF + INT_NUMBER "0" + ARG_LIST + L_PAREN "(" + R_PAREN ")" WHITESPACE "\n" R_CURLY "}" WHITESPACE "\n" diff --git a/crates/parser/test_data/parser/inline/ok/method_call_expr.rs b/crates/parser/test_data/parser/inline/ok/method_call_expr.rs index 48bb6381e8..bb54d75c19 100644 --- a/crates/parser/test_data/parser/inline/ok/method_call_expr.rs +++ b/crates/parser/test_data/parser/inline/ok/method_call_expr.rs @@ -3,4 +3,5 @@ fn foo() { y.bar::<T>(1, 2,); x.0.0.call(); x.0. call(); + x.0() } diff --git a/crates/parser/test_data/parser/inline/ok/param_list_opt_patterns.rast b/crates/parser/test_data/parser/inline/ok/param_list_opt_patterns.rast deleted file mode 100644 index e9d93a0d0a..0000000000 --- a/crates/parser/test_data/parser/inline/ok/param_list_opt_patterns.rast +++ /dev/null @@ -1,48 +0,0 @@ -SOURCE_FILE - FN - FN_KW "fn" - WHITESPACE " " - NAME - IDENT "foo" - GENERIC_PARAM_LIST - L_ANGLE "<" - TYPE_PARAM - NAME - IDENT "F" - COLON ":" - WHITESPACE " " - TYPE_BOUND_LIST - TYPE_BOUND - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "FnMut" - PARAM_LIST - L_PAREN "(" - PARAM - REF_TYPE - AMP "&" - MUT_KW "mut" - WHITESPACE " " - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "Foo" - GENERIC_ARG_LIST - L_ANGLE "<" - LIFETIME_ARG - LIFETIME - LIFETIME_IDENT "'a" - R_ANGLE ">" - R_PAREN ")" - R_ANGLE ">" - PARAM_LIST - L_PAREN "(" - R_PAREN ")" - BLOCK_EXPR - STMT_LIST - L_CURLY "{" - R_CURLY "}" - WHITESPACE "\n" diff --git a/crates/parser/test_data/parser/inline/ok/param_list_opt_patterns.rs b/crates/parser/test_data/parser/inline/ok/param_list_opt_patterns.rs deleted file mode 100644 index 9b93442c0f..0000000000 --- a/crates/parser/test_data/parser/inline/ok/param_list_opt_patterns.rs +++ /dev/null @@ -1 +0,0 @@ -fn foo<F: FnMut(&mut Foo<'a>)>(){} diff --git a/crates/parser/test_data/parser/inline/ok/path_fn_trait_args.rast b/crates/parser/test_data/parser/inline/ok/path_fn_trait_args.rast index fd83daf841..924f7ba2c9 100644 --- a/crates/parser/test_data/parser/inline/ok/path_fn_trait_args.rast +++ b/crates/parser/test_data/parser/inline/ok/path_fn_trait_args.rast @@ -20,9 +20,133 @@ SOURCE_FILE PATH_SEGMENT NAME_REF IDENT "Fn" - PARAM_LIST + PARENTHESIZED_ARG_LIST L_PAREN "(" - PARAM + TYPE_ARG + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "i32" + R_PAREN ")" + WHITESPACE " " + RET_TYPE + THIN_ARROW "->" + WHITESPACE " " + TUPLE_TYPE + L_PAREN "(" + R_PAREN ")" + R_ANGLE ">" + SEMICOLON ";" + WHITESPACE "\n" + TYPE_ALIAS + TYPE_KW "type" + WHITESPACE " " + NAME + IDENT "F" + WHITESPACE " " + EQ "=" + WHITESPACE " " + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Box" + GENERIC_ARG_LIST + L_ANGLE "<" + TYPE_ARG + PATH_TYPE + PATH + PATH_SEGMENT + COLON2 "::" + NAME_REF + IDENT "Fn" + PARENTHESIZED_ARG_LIST + L_PAREN "(" + TYPE_ARG + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "i32" + R_PAREN ")" + WHITESPACE " " + RET_TYPE + THIN_ARROW "->" + WHITESPACE " " + TUPLE_TYPE + L_PAREN "(" + R_PAREN ")" + R_ANGLE ">" + SEMICOLON ";" + WHITESPACE "\n" + TYPE_ALIAS + TYPE_KW "type" + WHITESPACE " " + NAME + IDENT "F" + WHITESPACE " " + EQ "=" + WHITESPACE " " + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Box" + GENERIC_ARG_LIST + L_ANGLE "<" + TYPE_ARG + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Fn" + PARENTHESIZED_ARG_LIST + COLON2 "::" + L_PAREN "(" + TYPE_ARG + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "i32" + R_PAREN ")" + WHITESPACE " " + RET_TYPE + THIN_ARROW "->" + WHITESPACE " " + TUPLE_TYPE + L_PAREN "(" + R_PAREN ")" + R_ANGLE ">" + SEMICOLON ";" + WHITESPACE "\n" + TYPE_ALIAS + TYPE_KW "type" + WHITESPACE " " + NAME + IDENT "F" + WHITESPACE " " + EQ "=" + WHITESPACE " " + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Box" + GENERIC_ARG_LIST + L_ANGLE "<" + TYPE_ARG + PATH_TYPE + PATH + PATH_SEGMENT + COLON2 "::" + NAME_REF + IDENT "Fn" + PARENTHESIZED_ARG_LIST + COLON2 "::" + L_PAREN "(" + TYPE_ARG PATH_TYPE PATH PATH_SEGMENT diff --git a/crates/parser/test_data/parser/inline/ok/path_fn_trait_args.rs b/crates/parser/test_data/parser/inline/ok/path_fn_trait_args.rs index 17ed20e5b1..7aa655d7ea 100644 --- a/crates/parser/test_data/parser/inline/ok/path_fn_trait_args.rs +++ b/crates/parser/test_data/parser/inline/ok/path_fn_trait_args.rs @@ -1 +1,4 @@ type F = Box<Fn(i32) -> ()>; +type F = Box<::Fn(i32) -> ()>; +type F = Box<Fn::(i32) -> ()>; +type F = Box<::Fn::(i32) -> ()>; diff --git a/crates/parser/test_data/parser/inline/ok/precise_capturing.rast b/crates/parser/test_data/parser/inline/ok/precise_capturing.rast index f9c0a245af..5a67cc2176 100644 --- a/crates/parser/test_data/parser/inline/ok/precise_capturing.rast +++ b/crates/parser/test_data/parser/inline/ok/precise_capturing.rast @@ -11,8 +11,10 @@ SOURCE_FILE LIFETIME_IDENT "'a" COLON ":" WHITESPACE " " - LIFETIME - LIFETIME_IDENT "'a" + TYPE_BOUND_LIST + TYPE_BOUND + LIFETIME + LIFETIME_IDENT "'a" COMMA "," WHITESPACE " " LIFETIME_PARAM @@ -20,8 +22,10 @@ SOURCE_FILE LIFETIME_IDENT "'b" COLON ":" WHITESPACE " " - LIFETIME - LIFETIME_IDENT "'b" + TYPE_BOUND_LIST + TYPE_BOUND + LIFETIME + LIFETIME_IDENT "'b" COMMA "," WHITESPACE " " TYPE_PARAM 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 deleted file mode 100644 index 30e0e73bbd..0000000000 --- a/crates/parser/test_data/parser/inline/ok/return_type_syntax_assoc_type_bound.rast +++ /dev/null @@ -1,49 +0,0 @@ -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 deleted file mode 100644 index 8a4cf4c3a0..0000000000 --- a/crates/parser/test_data/parser/inline/ok/return_type_syntax_assoc_type_bound.rs +++ /dev/null @@ -1 +0,0 @@ -fn foo<T: Trait<method(..): Send>>() {} 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 index 501dccd79d..15a0558b53 100644 --- 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 @@ -42,6 +42,49 @@ SOURCE_FILE NAME_REF IDENT "Send" COMMA "," + WHITESPACE "\n " + WHERE_PRED + PATH_TYPE + PATH + 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 " + WHERE_PRED + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "method" + RETURN_TYPE_SYNTAX + COLON2 "::" + 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 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 index a9b63fb01c..64b48c1638 100644 --- 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 @@ -1,4 +1,6 @@ fn foo<T>() where T::method(..): Send, + method(..): Send, + method::(..): Send, {} diff --git a/crates/parser/test_data/parser/inline/ok/typepathfn_with_coloncolon.rast b/crates/parser/test_data/parser/inline/ok/typepathfn_with_coloncolon.rast index 67277d0639..fb442bfb73 100644 --- a/crates/parser/test_data/parser/inline/ok/typepathfn_with_coloncolon.rast +++ b/crates/parser/test_data/parser/inline/ok/typepathfn_with_coloncolon.rast @@ -13,10 +13,10 @@ SOURCE_FILE PATH_SEGMENT NAME_REF IDENT "Start" - COLON2 "::" - PARAM_LIST + PARENTHESIZED_ARG_LIST + COLON2 "::" L_PAREN "(" - PARAM + TYPE_ARG PATH_TYPE PATH PATH_SEGMENT @@ -63,9 +63,9 @@ SOURCE_FILE PATH_SEGMENT NAME_REF IDENT "Start" - PARAM_LIST + PARENTHESIZED_ARG_LIST L_PAREN "(" - PARAM + TYPE_ARG PATH_TYPE PATH PATH_SEGMENT diff --git a/crates/parser/test_data/parser/inline/ok/value_parameters_no_patterns.rast b/crates/parser/test_data/parser/inline/ok/value_parameters_no_patterns.rast deleted file mode 100644 index 902b06484c..0000000000 --- a/crates/parser/test_data/parser/inline/ok/value_parameters_no_patterns.rast +++ /dev/null @@ -1,60 +0,0 @@ -SOURCE_FILE - TYPE_ALIAS - TYPE_KW "type" - WHITESPACE " " - NAME - IDENT "F" - WHITESPACE " " - EQ "=" - WHITESPACE " " - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "Box" - GENERIC_ARG_LIST - L_ANGLE "<" - TYPE_ARG - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "Fn" - PARAM_LIST - L_PAREN "(" - PARAM - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "i32" - COMMA "," - WHITESPACE " " - PARAM - REF_TYPE - AMP "&" - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "i32" - COMMA "," - WHITESPACE " " - PARAM - REF_TYPE - AMP "&" - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "i32" - COMMA "," - WHITESPACE " " - PARAM - TUPLE_TYPE - L_PAREN "(" - R_PAREN ")" - R_PAREN ")" - R_ANGLE ">" - SEMICOLON ";" - WHITESPACE "\n" diff --git a/crates/parser/test_data/parser/inline/ok/value_parameters_no_patterns.rs b/crates/parser/test_data/parser/inline/ok/value_parameters_no_patterns.rs deleted file mode 100644 index 93636e926e..0000000000 --- a/crates/parser/test_data/parser/inline/ok/value_parameters_no_patterns.rs +++ /dev/null @@ -1 +0,0 @@ -type F = Box<Fn(i32, &i32, &i32, ())>; diff --git a/crates/parser/test_data/parser/inline/ok/where_pred_for.rast b/crates/parser/test_data/parser/inline/ok/where_pred_for.rast index 8407e99f61..0cc365efbe 100644 --- a/crates/parser/test_data/parser/inline/ok/where_pred_for.rast +++ b/crates/parser/test_data/parser/inline/ok/where_pred_for.rast @@ -40,9 +40,9 @@ SOURCE_FILE PATH_SEGMENT NAME_REF IDENT "Fn" - PARAM_LIST + PARENTHESIZED_ARG_LIST L_PAREN "(" - PARAM + TYPE_ARG REF_TYPE AMP "&" LIFETIME diff --git a/crates/parser/test_data/parser/ok/0018_struct_type_params.rast b/crates/parser/test_data/parser/ok/0018_struct_type_params.rast index 11ebc7efb9..1e4eb15609 100644 --- a/crates/parser/test_data/parser/ok/0018_struct_type_params.rast +++ b/crates/parser/test_data/parser/ok/0018_struct_type_params.rast @@ -96,6 +96,7 @@ SOURCE_FILE LIFETIME LIFETIME_IDENT "'a" COLON ":" + TYPE_BOUND_LIST R_ANGLE ">" SEMICOLON ";" WHITESPACE "\n" @@ -111,8 +112,10 @@ SOURCE_FILE LIFETIME_IDENT "'a" COLON ":" WHITESPACE " " - LIFETIME - LIFETIME_IDENT "'b" + TYPE_BOUND_LIST + TYPE_BOUND + LIFETIME + LIFETIME_IDENT "'b" R_ANGLE ">" SEMICOLON ";" WHITESPACE "\n" @@ -128,10 +131,12 @@ SOURCE_FILE LIFETIME_IDENT "'a" COLON ":" WHITESPACE " " - LIFETIME - LIFETIME_IDENT "'b" - WHITESPACE " " - PLUS "+" + TYPE_BOUND_LIST + TYPE_BOUND + LIFETIME + LIFETIME_IDENT "'b" + WHITESPACE " " + PLUS "+" WHITESPACE " " R_ANGLE ">" SEMICOLON ";" @@ -148,13 +153,16 @@ SOURCE_FILE LIFETIME_IDENT "'a" COLON ":" WHITESPACE " " - LIFETIME - LIFETIME_IDENT "'b" - WHITESPACE " " - PLUS "+" - WHITESPACE " " - LIFETIME - LIFETIME_IDENT "'c" + TYPE_BOUND_LIST + TYPE_BOUND + LIFETIME + LIFETIME_IDENT "'b" + WHITESPACE " " + PLUS "+" + WHITESPACE " " + TYPE_BOUND + LIFETIME + LIFETIME_IDENT "'c" R_ANGLE ">" SEMICOLON ";" WHITESPACE "\n" @@ -202,9 +210,11 @@ SOURCE_FILE LIFETIME_IDENT "'a" COLON ":" WHITESPACE " " - LIFETIME - LIFETIME_IDENT "'b" - PLUS "+" + TYPE_BOUND_LIST + TYPE_BOUND + LIFETIME + LIFETIME_IDENT "'b" + PLUS "+" COMMA "," WHITESPACE " " LIFETIME_PARAM @@ -212,8 +222,10 @@ SOURCE_FILE LIFETIME_IDENT "'b" COLON ":" WHITESPACE " " - LIFETIME - LIFETIME_IDENT "'c" + TYPE_BOUND_LIST + TYPE_BOUND + LIFETIME + LIFETIME_IDENT "'c" COMMA "," R_ANGLE ">" SEMICOLON ";" diff --git a/crates/parser/test_data/parser/ok/0020_type_param_bounds.rast b/crates/parser/test_data/parser/ok/0020_type_param_bounds.rast index 043a966ff9..448cf49446 100644 --- a/crates/parser/test_data/parser/ok/0020_type_param_bounds.rast +++ b/crates/parser/test_data/parser/ok/0020_type_param_bounds.rast @@ -237,8 +237,10 @@ SOURCE_FILE LIFETIME_IDENT "'a" COLON ":" WHITESPACE " " - LIFETIME - LIFETIME_IDENT "'d" + TYPE_BOUND_LIST + TYPE_BOUND + LIFETIME + LIFETIME_IDENT "'d" COMMA "," WHITESPACE " " LIFETIME_PARAM @@ -246,13 +248,16 @@ SOURCE_FILE LIFETIME_IDENT "'d" COLON ":" WHITESPACE " " - LIFETIME - LIFETIME_IDENT "'a" - WHITESPACE " " - PLUS "+" - WHITESPACE " " - LIFETIME - LIFETIME_IDENT "'b" + TYPE_BOUND_LIST + TYPE_BOUND + LIFETIME + LIFETIME_IDENT "'a" + WHITESPACE " " + PLUS "+" + WHITESPACE " " + TYPE_BOUND + LIFETIME + LIFETIME_IDENT "'b" COMMA "," WHITESPACE " " TYPE_PARAM diff --git a/crates/parser/test_data/parser/ok/0045_block_attrs.rast b/crates/parser/test_data/parser/ok/0045_block_attrs.rast index fad574a476..c22d99f1ae 100644 --- a/crates/parser/test_data/parser/ok/0045_block_attrs.rast +++ b/crates/parser/test_data/parser/ok/0045_block_attrs.rast @@ -180,7 +180,7 @@ SOURCE_FILE PATH_SEGMENT NAME_REF IDENT "Fn" - PARAM_LIST + PARENTHESIZED_ARG_LIST L_PAREN "(" R_PAREN ")" WHITESPACE " " diff --git a/crates/parser/test_data/parser/ok/0051_parameter_attrs.rast b/crates/parser/test_data/parser/ok/0051_parameter_attrs.rast index f8b11e7782..eafee90db4 100644 --- a/crates/parser/test_data/parser/ok/0051_parameter_attrs.rast +++ b/crates/parser/test_data/parser/ok/0051_parameter_attrs.rast @@ -138,63 +138,6 @@ SOURCE_FILE WHITESPACE " " R_CURLY "}" WHITESPACE "\n\n" - FN - FN_KW "fn" - WHITESPACE " " - NAME - IDENT "foo" - GENERIC_PARAM_LIST - L_ANGLE "<" - TYPE_PARAM - NAME - IDENT "F" - COLON ":" - WHITESPACE " " - TYPE_BOUND_LIST - TYPE_BOUND - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "FnMut" - PARAM_LIST - L_PAREN "(" - PARAM - ATTR - POUND "#" - L_BRACK "[" - META - PATH - PATH_SEGMENT - NAME_REF - IDENT "attr" - R_BRACK "]" - WHITESPACE " " - REF_TYPE - AMP "&" - MUT_KW "mut" - WHITESPACE " " - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "Foo" - GENERIC_ARG_LIST - L_ANGLE "<" - LIFETIME_ARG - LIFETIME - LIFETIME_IDENT "'a" - R_ANGLE ">" - R_PAREN ")" - R_ANGLE ">" - PARAM_LIST - L_PAREN "(" - R_PAREN ")" - BLOCK_EXPR - STMT_LIST - L_CURLY "{" - R_CURLY "}" - WHITESPACE "\n\n" TRAIT TRAIT_KW "trait" WHITESPACE " " diff --git a/crates/parser/test_data/parser/ok/0051_parameter_attrs.rs b/crates/parser/test_data/parser/ok/0051_parameter_attrs.rs index de350d8587..0a0100e5d0 100644 --- a/crates/parser/test_data/parser/ok/0051_parameter_attrs.rs +++ b/crates/parser/test_data/parser/ok/0051_parameter_attrs.rs @@ -3,8 +3,6 @@ fn g2(#[attr1] x: u8) {} extern "C" { fn printf(format: *const i8, #[attr] ...) -> i32; } -fn foo<F: FnMut(#[attr] &mut Foo<'a>)>(){} - trait Foo { fn bar(#[attr] _: u64, # [attr] mut x: i32); } diff --git a/crates/parser/test_data/parser/ok/0054_qual_path_in_type_arg.rast b/crates/parser/test_data/parser/ok/0054_qual_path_in_type_arg.rast index 4e1e31f376..31cca601ca 100644 --- a/crates/parser/test_data/parser/ok/0054_qual_path_in_type_arg.rast +++ b/crates/parser/test_data/parser/ok/0054_qual_path_in_type_arg.rast @@ -58,9 +58,9 @@ SOURCE_FILE PATH_SEGMENT NAME_REF IDENT "FnMut" - PARAM_LIST + PARENTHESIZED_ARG_LIST L_PAREN "(" - PARAM + TYPE_ARG PATH_TYPE PATH PATH @@ -101,9 +101,9 @@ SOURCE_FILE PATH_SEGMENT NAME_REF IDENT "FnMut" - PARAM_LIST + PARENTHESIZED_ARG_LIST L_PAREN "(" - PARAM + TYPE_ARG REF_TYPE AMP "&" PATH_TYPE diff --git a/crates/parser/test_data/parser/ok/0065_plus_after_fn_trait_bound.rast b/crates/parser/test_data/parser/ok/0065_plus_after_fn_trait_bound.rast index ba7b6042a9..d7ee11077c 100644 --- a/crates/parser/test_data/parser/ok/0065_plus_after_fn_trait_bound.rast +++ b/crates/parser/test_data/parser/ok/0065_plus_after_fn_trait_bound.rast @@ -32,7 +32,7 @@ SOURCE_FILE PATH_SEGMENT NAME_REF IDENT "Fn" - PARAM_LIST + PARENTHESIZED_ARG_LIST L_PAREN "(" R_PAREN ")" WHITESPACE " " diff --git a/crates/parser/test_data/parser/ok/0067_where_for_pred.rast b/crates/parser/test_data/parser/ok/0067_where_for_pred.rast index 136fce93d7..cd3b21ae94 100644 --- a/crates/parser/test_data/parser/ok/0067_where_for_pred.rast +++ b/crates/parser/test_data/parser/ok/0067_where_for_pred.rast @@ -40,9 +40,9 @@ SOURCE_FILE PATH_SEGMENT NAME_REF IDENT "Fn" - PARAM_LIST + PARENTHESIZED_ARG_LIST L_PAREN "(" - PARAM + TYPE_ARG REF_TYPE AMP "&" LIFETIME @@ -165,9 +165,9 @@ SOURCE_FILE PATH_SEGMENT NAME_REF IDENT "Fn" - PARAM_LIST + PARENTHESIZED_ARG_LIST L_PAREN "(" - PARAM + TYPE_ARG REF_TYPE AMP "&" LIFETIME diff --git a/crates/project-model/src/workspace.rs b/crates/project-model/src/workspace.rs index f5ba71fcd0..988eff9be4 100644 --- a/crates/project-model/src/workspace.rs +++ b/crates/project-model/src/workspace.rs @@ -747,17 +747,14 @@ impl ProjectWorkspace { let _p = tracing::info_span!("ProjectWorkspace::to_crate_graph").entered(); let Self { kind, sysroot, cfg_overrides, rustc_cfg, .. } = self; - let ((mut crate_graph, proc_macros), sysroot) = match kind { - ProjectWorkspaceKind::Json(project) => ( - project_json_to_crate_graph( - rustc_cfg.clone(), - load, - project, - sysroot, - extra_env, - cfg_overrides, - ), + let (crate_graph, proc_macros) = match kind { + ProjectWorkspaceKind::Json(project) => project_json_to_crate_graph( + rustc_cfg.clone(), + load, + project, sysroot, + extra_env, + cfg_overrides, ), ProjectWorkspaceKind::Cargo { cargo, @@ -766,20 +763,17 @@ impl ProjectWorkspace { cargo_config_extra_env: _, error: _, set_test, - } => ( - cargo_to_crate_graph( - load, - rustc.as_ref().map(|a| a.as_ref()).ok(), - cargo, - sysroot, - rustc_cfg.clone(), - cfg_overrides, - build_scripts, - *set_test, - ), + } => cargo_to_crate_graph( + load, + rustc.as_ref().map(|a| a.as_ref()).ok(), + cargo, sysroot, + rustc_cfg.clone(), + cfg_overrides, + build_scripts, + *set_test, ), - ProjectWorkspaceKind::DetachedFile { file, cargo: cargo_script, set_test, .. } => ( + ProjectWorkspaceKind::DetachedFile { file, cargo: cargo_script, set_test, .. } => { if let Some((cargo, build_scripts, _)) = cargo_script { cargo_to_crate_graph( &mut |path| load(path), @@ -800,16 +794,10 @@ impl ProjectWorkspace { cfg_overrides, *set_test, ) - }, - sysroot, - ), + } + } }; - if matches!(sysroot.mode(), SysrootMode::Stitched(_)) && crate_graph.patch_cfg_if() { - debug!("Patched std to depend on cfg-if") - } else { - debug!("Did not patch std to depend on cfg-if") - } (crate_graph, proc_macros) } diff --git a/crates/rust-analyzer/Cargo.toml b/crates/rust-analyzer/Cargo.toml index 2dd2f2242a..7c8610280b 100644 --- a/crates/rust-analyzer/Cargo.toml +++ b/crates/rust-analyzer/Cargo.toml @@ -21,9 +21,11 @@ path = "src/bin/main.rs" [dependencies] anyhow.workspace = true +base64 = "0.22" crossbeam-channel.workspace = true dirs = "5.0.1" dissimilar.workspace = true +ide-completion.workspace = true itertools.workspace = true scip = "0.5.1" lsp-types = { version = "=0.95.0", features = ["proposed"] } @@ -34,6 +36,7 @@ rayon.workspace = true rustc-hash.workspace = true serde_json = { workspace = true, features = ["preserve_order"] } serde.workspace = true +tenthash = "0.4.0" num_cpus = "1.15.0" mimalloc = { version = "0.1.30", default-features = false, optional = true } lsp-server.workspace = true diff --git a/crates/rust-analyzer/src/bin/main.rs b/crates/rust-analyzer/src/bin/main.rs index e872585c57..eac33be566 100644 --- a/crates/rust-analyzer/src/bin/main.rs +++ b/crates/rust-analyzer/src/bin/main.rs @@ -20,7 +20,6 @@ use rust_analyzer::{ config::{Config, ConfigChange, ConfigErrors}, from_json, }; -use semver::Version; use tracing_subscriber::fmt::writer::BoxMakeWriter; use vfs::AbsPathBuf; @@ -204,18 +203,12 @@ fn run_server() -> anyhow::Result<()> { } }; - let mut visual_studio_code_version = None; - if let Some(client_info) = client_info { + if let Some(client_info) = &client_info { tracing::info!( "Client '{}' {}", client_info.name, client_info.version.as_deref().unwrap_or_default() ); - visual_studio_code_version = client_info - .name - .starts_with("Visual Studio Code") - .then(|| client_info.version.as_deref().map(Version::parse).and_then(Result::ok)) - .flatten(); } let workspace_roots = workspace_folders @@ -230,8 +223,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); + let mut config = Config::new(root_path, capabilities, workspace_roots, client_info); if let Some(json) = initialization_options { let mut change = ConfigChange::default(); change.change_client_config(json); diff --git a/crates/rust-analyzer/src/cli/analysis_stats.rs b/crates/rust-analyzer/src/cli/analysis_stats.rs index 802d0c69a4..e3ea441f3a 100644 --- a/crates/rust-analyzer/src/cli/analysis_stats.rs +++ b/crates/rust-analyzer/src/cli/analysis_stats.rs @@ -13,8 +13,9 @@ use hir::{ ModuleDef, Name, }; use hir_def::{ - body::{BodySourceMap, SyntheticSyntax}, + body::BodySourceMap, hir::{ExprId, PatId}, + SyntheticSyntax, }; use hir_ty::{Interner, Substitution, TyExt, TypeFlags}; use ide::{ diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index 37d45255e2..bf7aca42fa 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -3,11 +3,7 @@ //! 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::{ - env, fmt, iter, - ops::Not, - sync::{LazyLock, OnceLock}, -}; +use std::{env, fmt, iter, ops::Not, sync::OnceLock}; use cfg::{CfgAtom, CfgDiff}; use hir::Symbol; @@ -308,8 +304,8 @@ config_data! { /// Show documentation. signatureInfo_documentation_enable: bool = true, - /// Whether to insert closing angle brackets when typing an opening angle bracket of a generic argument list. - typing_autoClosingAngleBrackets_enable: bool = false, + /// Specify the characters to exclude from triggering typing assists. The default trigger characters are `.`, `=`, `<`, `>`, `{`, and `(`. + typing_excludeChars: Option<String> = Some("|<".to_owned()), /// Enables automatic discovery of projects using [`DiscoverWorkspaceConfig::command`]. @@ -730,6 +726,12 @@ enum RatomlFile { Crate(LocalConfigInput), } +#[derive(Clone, Debug)] +struct ClientInfo { + name: String, + version: Option<Version>, +} + #[derive(Clone)] pub struct Config { /// Projects that have a Cargo.toml or a rust-project.json in a @@ -744,7 +746,7 @@ pub struct Config { caps: ClientCapabilities, root_path: AbsPathBuf, snippets: Vec<Snippet>, - visual_studio_code_version: Option<Version>, + client_info: Option<ClientInfo>, default_config: &'static DefaultConfigData, /// Config node that obtains its initial value during the server initialization and @@ -777,7 +779,7 @@ impl fmt::Debug for Config { .field("caps", &self.caps) .field("root_path", &self.root_path) .field("snippets", &self.snippets) - .field("visual_studio_code_version", &self.visual_studio_code_version) + .field("client_info", &self.client_info) .field("client_config", &self.client_config) .field("user_config", &self.user_config) .field("ratoml_file", &self.ratoml_file) @@ -798,25 +800,14 @@ impl std::ops::Deref for Config { } impl Config { - /// 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<Option<AbsPathBuf>> = 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() + /// Path to the user configuration dir. This can be seen as a generic way to define what would be `$XDG_CONFIG_HOME/rust-analyzer` in Linux. + pub fn user_config_dir_path() -> Option<AbsPathBuf> { + 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") + }; + Some(AbsPathBuf::assert_utf8(user_config_path)) } pub fn same_source_root_parent_map( @@ -1256,7 +1247,7 @@ pub struct NotificationsConfig { pub cargo_toml_not_found: bool, } -#[derive(Deserialize, Serialize, Debug, Clone)] +#[derive(Debug, Clone)] pub enum RustfmtConfig { Rustfmt { extra_args: Vec<String>, enable_range_formatting: bool }, CustomCommand { command: String, args: Vec<String> }, @@ -1335,7 +1326,7 @@ impl Config { root_path: AbsPathBuf, caps: lsp_types::ClientCapabilities, workspace_roots: Vec<AbsPathBuf>, - visual_studio_code_version: Option<Version>, + client_info: Option<lsp_types::ClientInfo>, ) -> Self { static DEFAULT_CONFIG_DATA: OnceLock<&'static DefaultConfigData> = OnceLock::new(); @@ -1346,7 +1337,10 @@ impl Config { root_path, snippets: Default::default(), workspace_roots, - visual_studio_code_version, + client_info: client_info.map(|it| ClientInfo { + name: it.name, + version: it.version.as_deref().map(Version::parse).and_then(Result::ok), + }), client_config: (FullConfigInput::default(), ConfigErrors(vec![])), default_config: DEFAULT_CONFIG_DATA.get_or_init(|| Box::leak(Box::default())), source_root_parent_map: Arc::new(FxHashMap::default()), @@ -1446,14 +1440,10 @@ impl Config { limit: self.completion_limit(source_root).to_owned(), enable_term_search: self.completion_termSearch_enable(source_root).to_owned(), term_search_fuel: self.completion_termSearch_fuel(source_root).to_owned() as u64, - fields_to_resolve: CompletionFieldsToResolve { - resolve_label_details: client_capability_fields.contains("labelDetails"), - resolve_tags: client_capability_fields.contains("tags"), - resolve_detail: client_capability_fields.contains("detail"), - resolve_documentation: client_capability_fields.contains("documentation"), - resolve_filter_text: client_capability_fields.contains("filterText"), - resolve_text_edit: client_capability_fields.contains("textEdit"), - resolve_command: client_capability_fields.contains("command"), + fields_to_resolve: if self.client_is_neovim() { + CompletionFieldsToResolve::empty() + } else { + CompletionFieldsToResolve::from_client_capabilities(&client_capability_fields) }, } } @@ -1614,13 +1604,9 @@ impl Config { } else { None }, - fields_to_resolve: InlayFieldsToResolve { - resolve_text_edits: client_capability_fields.contains("textEdits"), - resolve_hint_tooltip: client_capability_fields.contains("tooltip"), - resolve_label_tooltip: client_capability_fields.contains("label.tooltip"), - resolve_label_location: client_capability_fields.contains("label.location"), - resolve_label_command: client_capability_fields.contains("label.command"), - }, + fields_to_resolve: InlayFieldsToResolve::from_client_capabilities( + &client_capability_fields, + ), implicit_drop_hints: self.inlayHints_implicitDrops_enable().to_owned(), range_exclusive_hints: self.inlayHints_rangeExclusiveHints_enable().to_owned(), } @@ -2166,14 +2152,25 @@ impl Config { } } - pub fn typing_autoclose_angle(&self) -> bool { - *self.typing_autoClosingAngleBrackets_enable() + pub fn typing_exclude_chars(&self) -> Option<String> { + self.typing_excludeChars().clone() } // VSCode is our reference implementation, so we allow ourselves to work around issues by // special casing certain versions pub fn visual_studio_code_version(&self) -> Option<&Version> { - self.visual_studio_code_version.as_ref() + self.client_info + .as_ref() + .filter(|it| it.name.starts_with("Visual Studio Code")) + .and_then(|it| it.version.as_ref()) + } + + pub fn client_is_helix(&self) -> bool { + self.client_info.as_ref().map(|it| it.name == "helix").unwrap_or_default() + } + + pub fn client_is_neovim(&self) -> bool { + self.client_info.as_ref().map(|it| it.name == "Neovim").unwrap_or_default() } } // Deserialization definitions diff --git a/crates/rust-analyzer/src/global_state.rs b/crates/rust-analyzer/src/global_state.rs index 7fbeaa4e3e..5f83570284 100644 --- a/crates/rust-analyzer/src/global_state.rs +++ b/crates/rust-analyzer/src/global_state.rs @@ -392,7 +392,14 @@ impl GlobalState { || !self.config.same_source_root_parent_map(&self.local_roots_parent_map) { let config_change = { - let user_config_path = Config::user_config_path(); + let user_config_path = (|| { + let mut p = Config::user_config_dir_path()?; + p.push("rust-analyzer.toml"); + Some(p) + })(); + + let user_config_abs_path = user_config_path.as_deref(); + let mut change = ConfigChange::default(); let db = self.analysis_host.raw_database(); @@ -411,7 +418,7 @@ impl GlobalState { .collect_vec(); for (file_id, (_change_kind, vfs_path)) in modified_ratoml_files { - if vfs_path.as_path() == user_config_path { + if vfs_path.as_path() == user_config_abs_path { change.change_user_config(Some(db.file_text(file_id))); continue; } diff --git a/crates/rust-analyzer/src/handlers/notification.rs b/crates/rust-analyzer/src/handlers/notification.rs index bb03eb3c89..5e7262b14c 100644 --- a/crates/rust-analyzer/src/handlers/notification.rs +++ b/crates/rust-analyzer/src/handlers/notification.rs @@ -379,9 +379,7 @@ 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(source_root_id) && target.is_some() - }) { + match package.filter(|_| !world.config.flycheck_workspace(source_root_id)) { Some(package) => flycheck .restart_for_package(package, target.clone().map(TupleExt::head)), None => flycheck.restart_workspace(saved_file.clone()), diff --git a/crates/rust-analyzer/src/handlers/request.rs b/crates/rust-analyzer/src/handlers/request.rs index 4975467ece..fa78be5cb6 100644 --- a/crates/rust-analyzer/src/handlers/request.rs +++ b/crates/rust-analyzer/src/handlers/request.rs @@ -10,6 +10,7 @@ use std::{ use anyhow::Context; +use base64::{prelude::BASE64_STANDARD, Engine}; use ide::{ AnnotationConfig, AssistKind, AssistResolveStrategy, Cancellable, CompletionFieldsToResolve, FilePosition, FileRange, HoverAction, HoverGotoTypeData, InlayFieldsToResolve, Query, @@ -36,6 +37,7 @@ use triomphe::Arc; use vfs::{AbsPath, AbsPathBuf, FileId, VfsPath}; use crate::{ + completion_item_hash, config::{Config, RustfmtConfig, WorkspaceSymbolConfig}, diagnostics::convert_diagnostic, global_state::{FetchWorkspaceRequest, GlobalState, GlobalStateSnapshot}, @@ -459,9 +461,9 @@ pub(crate) fn handle_on_type_formatting( if char_typed == '>' { return Ok(None); } + let chars_to_exclude = snap.config.typing_exclude_chars(); - let edit = - snap.analysis.on_char_typed(position, char_typed, snap.config.typing_autoclose_angle())?; + let edit = snap.analysis.on_char_typed(position, char_typed, chars_to_exclude)?; let edit = match edit { Some(it) => it, None => return Ok(None), @@ -1127,7 +1129,7 @@ pub(crate) fn handle_completion_resolve( forced_resolve_completions_config.fields_to_resolve = CompletionFieldsToResolve::empty(); let position = FilePosition { file_id, offset }; - let Some(resolved_completions) = snap.analysis.completions( + let Some(completions) = snap.analysis.completions( &forced_resolve_completions_config, position, resolve_data.trigger_character, @@ -1135,6 +1137,19 @@ pub(crate) fn handle_completion_resolve( else { return Ok(original_completion); }; + let Ok(resolve_data_hash) = BASE64_STANDARD.decode(resolve_data.hash) else { + return Ok(original_completion); + }; + + let Some(corresponding_completion) = completions.into_iter().find(|completion_item| { + // Avoid computing hashes for items that obviously do not match + // r-a might append a detail-based suffix to the label, so we cannot check for equality + original_completion.label.starts_with(completion_item.label.as_str()) + && resolve_data_hash == completion_item_hash(completion_item, resolve_data.for_ref) + }) else { + return Ok(original_completion); + }; + let mut resolved_completions = to_proto::completion_items( &snap.config, &forced_resolve_completions_config.fields_to_resolve, @@ -1142,15 +1157,11 @@ pub(crate) fn handle_completion_resolve( snap.file_version(position.file_id), resolve_data.position, resolve_data.trigger_character, - resolved_completions, + vec![corresponding_completion], ); - - let mut resolved_completion = - if resolved_completions.get(resolve_data.completion_item_index).is_some() { - resolved_completions.swap_remove(resolve_data.completion_item_index) - } else { - return Ok(original_completion); - }; + let Some(mut resolved_completion) = resolved_completions.pop() else { + return Ok(original_completion); + }; if !resolve_data.imports.is_empty() { let additional_edits = snap @@ -2341,6 +2352,10 @@ fn run_rustfmt( ); Ok(None) } + // rustfmt panicked at lexing/parsing the file + Some(101) if !rustfmt_not_installed && captured_stderr.starts_with("error[") => { + Ok(None) + } _ => { // Something else happened - e.g. `rustfmt` is missing or caught a signal Err(LspError::new( diff --git a/crates/rust-analyzer/src/lib.rs b/crates/rust-analyzer/src/lib.rs index 234204695c..15d60c873f 100644 --- a/crates/rust-analyzer/src/lib.rs +++ b/crates/rust-analyzer/src/lib.rs @@ -47,7 +47,9 @@ use self::lsp::ext as lsp_ext; #[cfg(test)] mod integrated_benchmarks; +use ide::{CompletionItem, CompletionRelevance}; use serde::de::DeserializeOwned; +use tenthash::TentHasher; pub use crate::{ lsp::capabilities::server_capabilities, main_loop::main_loop, reload::ws_to_crate_graph, @@ -61,3 +63,79 @@ pub fn from_json<T: DeserializeOwned>( serde_json::from_value(json.clone()) .map_err(|e| anyhow::format_err!("Failed to deserialize {what}: {e}; {json}")) } + +fn completion_item_hash(item: &CompletionItem, is_ref_completion: bool) -> [u8; 20] { + fn hash_completion_relevance(hasher: &mut TentHasher, relevance: &CompletionRelevance) { + use ide_completion::{ + CompletionRelevancePostfixMatch, CompletionRelevanceReturnType, + CompletionRelevanceTypeMatch, + }; + + hasher.update([ + u8::from(relevance.exact_name_match), + u8::from(relevance.is_local), + u8::from(relevance.is_name_already_imported), + u8::from(relevance.requires_import), + u8::from(relevance.is_private_editable), + ]); + if let Some(type_match) = &relevance.type_match { + let label = match type_match { + CompletionRelevanceTypeMatch::CouldUnify => "could_unify", + CompletionRelevanceTypeMatch::Exact => "exact", + }; + hasher.update(label); + } + if let Some(trait_) = &relevance.trait_ { + hasher.update([u8::from(trait_.is_op_method), u8::from(trait_.notable_trait)]); + } + if let Some(postfix_match) = &relevance.postfix_match { + let label = match postfix_match { + CompletionRelevancePostfixMatch::NonExact => "non_exact", + CompletionRelevancePostfixMatch::Exact => "exact", + }; + hasher.update(label); + } + if let Some(function) = &relevance.function { + hasher.update([u8::from(function.has_params), u8::from(function.has_self_param)]); + let label = match function.return_type { + CompletionRelevanceReturnType::Other => "other", + CompletionRelevanceReturnType::DirectConstructor => "direct_constructor", + CompletionRelevanceReturnType::Constructor => "constructor", + CompletionRelevanceReturnType::Builder => "builder", + }; + hasher.update(label); + } + } + + let mut hasher = TentHasher::new(); + hasher.update([ + u8::from(is_ref_completion), + u8::from(item.is_snippet), + u8::from(item.deprecated), + u8::from(item.trigger_call_info), + ]); + hasher.update(&item.label); + if let Some(label_detail) = &item.label_detail { + hasher.update(label_detail); + } + // NB: do not hash edits or source range, as those may change between the time the client sends the resolve request + // and the time it receives it: some editors do allow changing the buffer between that, leading to ranges being different. + // + // Documentation hashing is skipped too, as it's a large blob to process, + // while not really making completion properties more unique as they are already. + hasher.update(item.kind.tag()); + hasher.update(&item.lookup); + if let Some(detail) = &item.detail { + hasher.update(detail); + } + hash_completion_relevance(&mut hasher, &item.relevance); + if let Some((mutability, text_size)) = &item.ref_match { + hasher.update(mutability.as_keyword_for_ref()); + hasher.update(u32::from(*text_size).to_le_bytes()); + } + for (import_path, import_name) in &item.import_to_add { + hasher.update(import_path); + hasher.update(import_name); + } + hasher.finalize() +} diff --git a/crates/rust-analyzer/src/lsp/capabilities.rs b/crates/rust-analyzer/src/lsp/capabilities.rs index 1db616898e..b1136dbbda 100644 --- a/crates/rust-analyzer/src/lsp/capabilities.rs +++ b/crates/rust-analyzer/src/lsp/capabilities.rs @@ -1,4 +1,5 @@ //! Advertises the capabilities of the LSP Server. +use ide::{CompletionFieldsToResolve, InlayFieldsToResolve}; use ide_db::{line_index::WideEncoding, FxHashSet}; use lsp_types::{ CallHierarchyServerCapability, CodeActionKind, CodeActionOptions, CodeActionProviderCapability, @@ -40,7 +41,11 @@ pub fn server_capabilities(config: &Config) -> ServerCapabilities { })), hover_provider: Some(HoverProviderCapability::Simple(true)), completion_provider: Some(CompletionOptions { - resolve_provider: config.caps().completions_resolve_provider(), + resolve_provider: if config.client_is_neovim() { + config.completion_item_edit_resolve().then_some(true) + } else { + Some(config.caps().completions_resolve_provider()) + }, trigger_characters: Some(vec![ ":".to_owned(), ".".to_owned(), @@ -71,9 +76,12 @@ pub fn server_capabilities(config: &Config) -> ServerCapabilities { RustfmtConfig::Rustfmt { enable_range_formatting: true, .. } => Some(OneOf::Left(true)), _ => Some(OneOf::Left(false)), }, - document_on_type_formatting_provider: Some(DocumentOnTypeFormattingOptions { - first_trigger_character: "=".to_owned(), - more_trigger_character: Some(more_trigger_character(config)), + document_on_type_formatting_provider: Some({ + let mut chars = ide::Analysis::SUPPORTED_TRIGGER_CHARS.chars(); + DocumentOnTypeFormattingOptions { + first_trigger_character: chars.next().unwrap().to_string(), + more_trigger_character: Some(chars.map(|c| c.to_string()).collect()), + } }), selection_range_provider: Some(SelectionRangeProviderCapability::Simple(true)), folding_range_provider: Some(FoldingRangeProviderCapability::Simple(true)), @@ -136,7 +144,7 @@ pub fn server_capabilities(config: &Config) -> ServerCapabilities { inlay_hint_provider: Some(OneOf::Right(InlayHintServerCapabilities::Options( InlayHintOptions { work_done_progress_options: Default::default(), - resolve_provider: Some(true), + resolve_provider: Some(config.caps().inlay_hints_resolve_provider()), }, ))), inline_value_provider: None, @@ -176,8 +184,18 @@ impl ClientCapabilities { Self(caps) } - fn completions_resolve_provider(&self) -> Option<bool> { - self.completion_item_edit_resolve().then_some(true) + fn completions_resolve_provider(&self) -> bool { + let client_capabilities = self.completion_resolve_support_properties(); + let fields_to_resolve = + CompletionFieldsToResolve::from_client_capabilities(&client_capabilities); + fields_to_resolve != CompletionFieldsToResolve::empty() + } + + fn inlay_hints_resolve_provider(&self) -> bool { + let client_capabilities = self.inlay_hint_resolve_support_properties(); + let fields_to_resolve = + InlayFieldsToResolve::from_client_capabilities(&client_capabilities); + fields_to_resolve != InlayFieldsToResolve::empty() } fn experimental_bool(&self, index: &'static str) -> bool { @@ -517,11 +535,3 @@ impl ClientCapabilities { .unwrap_or_default() } } - -fn more_trigger_character(config: &Config) -> Vec<String> { - let mut res = vec![".".to_owned(), ">".to_owned(), "{".to_owned(), "(".to_owned()]; - if config.snippet_cap().is_some() { - res.push("<".to_owned()); - } - res -} diff --git a/crates/rust-analyzer/src/lsp/ext.rs b/crates/rust-analyzer/src/lsp/ext.rs index 6ddfe118d5..df06270a8b 100644 --- a/crates/rust-analyzer/src/lsp/ext.rs +++ b/crates/rust-analyzer/src/lsp/ext.rs @@ -826,7 +826,8 @@ pub struct CompletionResolveData { pub imports: Vec<CompletionImport>, pub version: Option<i32>, pub trigger_character: Option<char>, - pub completion_item_index: usize, + pub for_ref: bool, + pub hash: String, } #[derive(Debug, Serialize, Deserialize)] diff --git a/crates/rust-analyzer/src/lsp/to_proto.rs b/crates/rust-analyzer/src/lsp/to_proto.rs index d444f90a13..612cb547b4 100644 --- a/crates/rust-analyzer/src/lsp/to_proto.rs +++ b/crates/rust-analyzer/src/lsp/to_proto.rs @@ -5,6 +5,7 @@ use std::{ sync::atomic::{AtomicU32, Ordering}, }; +use base64::{prelude::BASE64_STANDARD, Engine}; use ide::{ Annotation, AnnotationKind, Assist, AssistKind, Cancellable, CompletionFieldsToResolve, CompletionItem, CompletionItemKind, CompletionRelevance, Documentation, FileId, FileRange, @@ -21,6 +22,7 @@ use serde_json::to_value; use vfs::AbsPath; use crate::{ + completion_item_hash, config::{CallInfoConfig, Config}, global_state::GlobalStateSnapshot, line_index::{LineEndings, LineIndex, PositionEncoding}, @@ -295,7 +297,7 @@ fn completion_item( // non-trivial mapping here. let mut text_edit = None; let source_range = item.source_range; - for indel in item.text_edit { + for indel in &item.text_edit { if indel.delete.contains_range(source_range) { // Extract this indel as the main edit text_edit = Some(if indel.delete == source_range { @@ -347,7 +349,7 @@ fn completion_item( something_to_resolve |= item.documentation.is_some(); None } else { - item.documentation.map(documentation) + item.documentation.clone().map(documentation) }; let mut lsp_item = lsp_types::CompletionItem { @@ -371,10 +373,10 @@ fn completion_item( } else { lsp_item.label_details = Some(lsp_types::CompletionItemLabelDetails { detail: item.label_detail.as_ref().map(ToString::to_string), - description: item.detail, + description: item.detail.clone(), }); } - } else if let Some(label_detail) = item.label_detail { + } else if let Some(label_detail) = &item.label_detail { lsp_item.label.push_str(label_detail.as_str()); } @@ -383,6 +385,7 @@ fn completion_item( let imports = if config.completion(None).enable_imports_on_the_fly && !item.import_to_add.is_empty() { item.import_to_add + .clone() .into_iter() .map(|(import_path, import_name)| lsp_ext::CompletionImport { full_import_path: import_path, @@ -393,16 +396,15 @@ fn completion_item( Vec::new() }; let (ref_resolve_data, resolve_data) = if something_to_resolve || !imports.is_empty() { - let mut item_index = acc.len(); let ref_resolve_data = if ref_match.is_some() { let ref_resolve_data = lsp_ext::CompletionResolveData { position: tdpp.clone(), imports: Vec::new(), version, trigger_character: completion_trigger_character, - completion_item_index: item_index, + for_ref: true, + hash: BASE64_STANDARD.encode(completion_item_hash(&item, true)), }; - item_index += 1; Some(to_value(ref_resolve_data).unwrap()) } else { None @@ -412,7 +414,8 @@ fn completion_item( imports, version, trigger_character: completion_trigger_character, - completion_item_index: item_index, + for_ref: false, + hash: BASE64_STANDARD.encode(completion_item_hash(&item, false)), }; (ref_resolve_data, Some(to_value(resolve_data).unwrap())) } else { diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs index 73fce42437..a34f0a3c92 100644 --- a/crates/rust-analyzer/src/main_loop.rs +++ b/crates/rust-analyzer/src/main_loop.rs @@ -809,7 +809,7 @@ impl GlobalState { } } vfs::loader::Message::Progress { n_total, n_done, dir, config_version } => { - let _p = span!(Level::INFO, "GlobalState::handle_vfs_mgs/progress").entered(); + let _p = span!(Level::INFO, "GlobalState::handle_vfs_msg/progress").entered(); always!(config_version <= self.vfs_config_version); let (n_done, state) = match n_done { diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs index bc85afa0e4..4549735fef 100644 --- a/crates/rust-analyzer/src/reload.rs +++ b/crates/rust-analyzer/src/reload.rs @@ -590,7 +590,7 @@ impl GlobalState { } watchers.extend( - iter::once(Config::user_config_path()) + iter::once(Config::user_config_dir_path().as_deref()) .chain(self.workspaces.iter().map(|ws| ws.manifest().map(ManifestPath::as_ref))) .flatten() .map(|glob_pattern| lsp_types::FileSystemWatcher { @@ -613,7 +613,11 @@ impl GlobalState { } let files_config = self.config.files(); - let project_folders = ProjectFolders::new(&self.workspaces, &files_config.exclude); + let project_folders = ProjectFolders::new( + &self.workspaces, + &files_config.exclude, + Config::user_config_dir_path().as_deref(), + ); if (self.proc_macro_clients.is_empty() || !same_workspaces) && self.config.expand_proc_macros() diff --git a/crates/rust-analyzer/tests/slow-tests/ratoml.rs b/crates/rust-analyzer/tests/slow-tests/ratoml.rs index a857e0c296..5dfaf0d365 100644 --- a/crates/rust-analyzer/tests/slow-tests/ratoml.rs +++ b/crates/rust-analyzer/tests/slow-tests/ratoml.rs @@ -46,7 +46,7 @@ impl RatomlTest { project = project.with_config(client_config); } - let server = project.server().wait_until_workspace_is_loaded(); + let server = project.server_with_lock(true).wait_until_workspace_is_loaded(); let mut case = Self { urls: vec![], server, tmp_path }; let urls = fixtures.iter().map(|fixture| case.fixture_path(fixture)).collect::<Vec<_>>(); @@ -72,7 +72,7 @@ impl RatomlTest { let mut spl = spl.into_iter(); if let Some(first) = spl.next() { if first == "$$CONFIG_DIR$$" { - path = Config::user_config_path().unwrap().to_path_buf().into(); + path = Config::user_config_dir_path().unwrap().into(); } else { path = path.join(first); } @@ -285,7 +285,6 @@ enum Value { // } #[test] -#[ignore = "the user config is currently not being watched on startup, fix this"] fn ratoml_user_config_detected() { if skip_slow_tests() { return; @@ -294,7 +293,7 @@ fn ratoml_user_config_detected() { let server = RatomlTest::new( vec![ r#" -//- /$$CONFIG_DIR$$/rust-analyzer/rust-analyzer.toml +//- /$$CONFIG_DIR$$/rust-analyzer.toml assist.emitMustUse = true "#, r#" @@ -322,7 +321,6 @@ enum Value { } #[test] -#[ignore = "the user config is currently not being watched on startup, fix this"] fn ratoml_create_user_config() { if skip_slow_tests() { return; @@ -353,10 +351,7 @@ enum Value { 1, InternalTestingFetchConfigResponse::AssistEmitMustUse(false), ); - server.create( - "//- /$$CONFIG_DIR$$/rust-analyzer/rust-analyzer.toml", - RatomlTest::EMIT_MUST_USE.to_owned(), - ); + server.create("//- /$$CONFIG_DIR$$/rust-analyzer.toml", RatomlTest::EMIT_MUST_USE.to_owned()); server.query( InternalTestingFetchConfigOption::AssistEmitMustUse, 1, @@ -365,7 +360,6 @@ enum Value { } #[test] -#[ignore = "the user config is currently not being watched on startup, fix this"] fn ratoml_modify_user_config() { if skip_slow_tests() { return; @@ -386,7 +380,7 @@ enum Value { Text(String), }"#, r#" -//- /$$CONFIG_DIR$$/rust-analyzer/rust-analyzer.toml +//- /$$CONFIG_DIR$$/rust-analyzer.toml assist.emitMustUse = true"#, ], vec!["p1"], @@ -407,7 +401,6 @@ assist.emitMustUse = true"#, } #[test] -#[ignore = "the user config is currently not being watched on startup, fix this"] fn ratoml_delete_user_config() { if skip_slow_tests() { return; @@ -428,7 +421,7 @@ enum Value { Text(String), }"#, r#" -//- /$$CONFIG_DIR$$/rust-analyzer/rust-analyzer.toml +//- /$$CONFIG_DIR$$/rust-analyzer.toml assist.emitMustUse = true"#, ], vec!["p1"], diff --git a/crates/rust-analyzer/tests/slow-tests/support.rs b/crates/rust-analyzer/tests/slow-tests/support.rs index 78572e37a9..5a88a5515c 100644 --- a/crates/rust-analyzer/tests/slow-tests/support.rs +++ b/crates/rust-analyzer/tests/slow-tests/support.rs @@ -1,7 +1,7 @@ use std::{ cell::{Cell, RefCell}, - fs, - sync::Once, + env, fs, + sync::{Once, OnceLock}, time::Duration, }; @@ -127,7 +127,53 @@ impl Project<'_> { } pub(crate) fn server(self) -> Server { - static CONFIG_DIR_LOCK: Mutex<()> = Mutex::new(()); + Project::server_with_lock(self, false) + } + + /// `prelock` : Forcefully acquire a lock that will maintain the path to the config dir throughout the whole test. + /// + /// When testing we set the user config dir by setting an envvar `__TEST_RA_USER_CONFIG_DIR`. + /// This value must be maintained until the end of a test case. When tests run in parallel + /// this value may change thus making the tests flaky. As such, we use a `MutexGuard` that locks + /// the process until `Server` is dropped. To optimize parallelization we use a lock only when it is + /// needed, that is when a test uses config directory to do stuff. Our naive approach is to use a lock + /// if there is a path to config dir in the test fixture. However, in certain cases we create a + /// file in the config dir after server is run, something where our naive approach comes short. + /// Using a `prelock` allows us to force a lock when we know we need it. + pub(crate) fn server_with_lock(self, config_lock: bool) -> Server { + static CONFIG_DIR_LOCK: OnceLock<(Utf8PathBuf, Mutex<()>)> = OnceLock::new(); + + let config_dir_guard = if config_lock { + Some({ + let (path, mutex) = CONFIG_DIR_LOCK.get_or_init(|| { + let value = TestDir::new().keep().path().to_owned(); + env::set_var("__TEST_RA_USER_CONFIG_DIR", &value); + (value, Mutex::new(())) + }); + #[allow(dyn_drop)] + (mutex.lock(), { + Box::new({ + struct Dropper(Utf8PathBuf); + impl Drop for Dropper { + fn drop(&mut self) { + for entry in fs::read_dir(&self.0).unwrap() { + let path = entry.unwrap().path(); + if path.is_file() { + fs::remove_file(path).unwrap(); + } else if path.is_dir() { + fs::remove_dir_all(path).unwrap(); + } + } + } + } + Dropper(path.clone()) + }) as Box<dyn Drop> + }) + }) + } else { + None + }; + let tmp_dir = self.tmp_dir.unwrap_or_else(|| { if self.root_dir_contains_symlink { TestDir::new_symlink() @@ -160,13 +206,9 @@ 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$$") { - 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()..]); + let path = Config::user_config_dir_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 { @@ -269,12 +311,14 @@ pub(crate) struct Server { client: Connection, /// XXX: remove the tempdir last dir: TestDir, - _config_dir_guard: Option<MutexGuard<'static, ()>>, + #[allow(dyn_drop)] + _config_dir_guard: Option<(MutexGuard<'static, ()>, Box<dyn Drop>)>, } impl Server { + #[allow(dyn_drop)] fn new( - config_dir_guard: Option<MutexGuard<'static, ()>>, + config_dir_guard: Option<(MutexGuard<'static, ()>, Box<dyn Drop>)>, dir: TestDir, config: Config, ) -> Server { diff --git a/crates/syntax/rust.ungram b/crates/syntax/rust.ungram index 02c59646a9..30428329dd 100644 --- a/crates/syntax/rust.ungram +++ b/crates/syntax/rust.ungram @@ -37,7 +37,7 @@ Path = PathSegment = '::'? NameRef | NameRef GenericArgList? -| NameRef ParamList RetType? +| NameRef ParenthesizedArgList RetType? | NameRef ReturnTypeSyntax | '<' Type ('as' PathType)? '>' @@ -49,6 +49,9 @@ ReturnTypeSyntax = // Generics // //*************************// +ParenthesizedArgList = + '::'? '(' (TypeArg (',' TypeArg)* ','?)? ')' + GenericArgList = '::'? '<' (GenericArg (',' GenericArg)* ','?)? '>' diff --git a/crates/syntax/src/ast/edit_in_place.rs b/crates/syntax/src/ast/edit_in_place.rs index f1286e7aa2..ffe9f16cfd 100644 --- a/crates/syntax/src/ast/edit_in_place.rs +++ b/crates/syntax/src/ast/edit_in_place.rs @@ -909,30 +909,6 @@ fn get_or_insert_comma_after(syntax: &SyntaxNode) -> SyntaxToken { } } -impl ast::VariantList { - pub fn add_variant(&self, variant: ast::Variant) { - let (indent, position) = match self.variants().last() { - Some(last_item) => ( - IndentLevel::from_node(last_item.syntax()), - Position::after(get_or_insert_comma_after(last_item.syntax())), - ), - None => match self.l_curly_token() { - Some(l_curly) => { - normalize_ws_between_braces(self.syntax()); - (IndentLevel::from_token(&l_curly) + 1, Position::after(&l_curly)) - } - None => (IndentLevel::single(), Position::last_child_of(self.syntax())), - }, - }; - let elements: Vec<SyntaxElement> = vec![ - make::tokens::whitespace(&format!("{}{indent}", "\n")).into(), - variant.syntax().clone().into(), - ast::make::token(T![,]).into(), - ]; - ted::insert_all(position, elements); - } -} - fn normalize_ws_between_braces(node: &SyntaxNode) -> Option<()> { let l = node .children_with_tokens() @@ -1055,8 +1031,6 @@ mod tests { use std::fmt; use parser::Edition; - use stdx::trim_indent; - use test_utils::assert_eq_text; use crate::SourceFile; @@ -1170,102 +1144,4 @@ mod tests { check("let a: u8 = 3;", "let a = 3;", None); check("let a: = 3;", "let a = 3;", None); } - - #[test] - fn add_variant_to_empty_enum() { - let variant = make::variant(make::name("Bar"), None).clone_for_update(); - - check_add_variant( - r#" -enum Foo {} -"#, - r#" -enum Foo { - Bar, -} -"#, - variant, - ); - } - - #[test] - fn add_variant_to_non_empty_enum() { - let variant = make::variant(make::name("Baz"), None).clone_for_update(); - - check_add_variant( - r#" -enum Foo { - Bar, -} -"#, - r#" -enum Foo { - Bar, - Baz, -} -"#, - variant, - ); - } - - #[test] - fn add_variant_with_tuple_field_list() { - let variant = make::variant( - make::name("Baz"), - Some(ast::FieldList::TupleFieldList(make::tuple_field_list(std::iter::once( - make::tuple_field(None, make::ty("bool")), - )))), - ) - .clone_for_update(); - - check_add_variant( - r#" -enum Foo { - Bar, -} -"#, - r#" -enum Foo { - Bar, - Baz(bool), -} -"#, - variant, - ); - } - - #[test] - fn add_variant_with_record_field_list() { - let variant = make::variant( - make::name("Baz"), - Some(ast::FieldList::RecordFieldList(make::record_field_list(std::iter::once( - make::record_field(None, make::name("x"), make::ty("bool")), - )))), - ) - .clone_for_update(); - - check_add_variant( - r#" -enum Foo { - Bar, -} -"#, - r#" -enum Foo { - Bar, - Baz { x: bool }, -} -"#, - variant, - ); - } - - fn check_add_variant(before: &str, expected: &str, variant: ast::Variant) { - let enum_ = ast_mut_from_text::<ast::Enum>(before); - if let Some(it) = enum_.variant_list() { - it.add_variant(variant) - } - let after = enum_.to_string(); - assert_eq_text!(&trim_indent(expected.trim()), &trim_indent(after.trim())); - } } diff --git a/crates/syntax/src/ast/generated/nodes.rs b/crates/syntax/src/ast/generated/nodes.rs index 23d2b355a9..01dcb646b3 100644 --- a/crates/syntax/src/ast/generated/nodes.rs +++ b/crates/syntax/src/ast/generated/nodes.rs @@ -1363,6 +1363,21 @@ impl ParenType { } #[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct ParenthesizedArgList { + pub(crate) syntax: SyntaxNode, +} +impl ParenthesizedArgList { + #[inline] + pub fn type_args(&self) -> AstChildren<TypeArg> { support::children(&self.syntax) } + #[inline] + pub fn l_paren_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T!['(']) } + #[inline] + pub fn r_paren_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![')']) } + #[inline] + pub fn coloncolon_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![::]) } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Path { pub(crate) syntax: SyntaxNode, } @@ -1403,7 +1418,9 @@ impl PathSegment { #[inline] pub fn name_ref(&self) -> Option<NameRef> { support::child(&self.syntax) } #[inline] - pub fn param_list(&self) -> Option<ParamList> { support::child(&self.syntax) } + pub fn parenthesized_arg_list(&self) -> Option<ParenthesizedArgList> { + support::child(&self.syntax) + } #[inline] pub fn path_type(&self) -> Option<PathType> { support::child(&self.syntax) } #[inline] @@ -3760,6 +3777,20 @@ impl AstNode for ParenType { #[inline] fn syntax(&self) -> &SyntaxNode { &self.syntax } } +impl AstNode for ParenthesizedArgList { + #[inline] + fn can_cast(kind: SyntaxKind) -> bool { kind == PARENTHESIZED_ARG_LIST } + #[inline] + fn cast(syntax: SyntaxNode) -> Option<Self> { + if Self::can_cast(syntax.kind()) { + Some(Self { syntax }) + } else { + None + } + } + #[inline] + fn syntax(&self) -> &SyntaxNode { &self.syntax } +} impl AstNode for Path { #[inline] fn can_cast(kind: SyntaxKind) -> bool { kind == PATH } @@ -7097,6 +7128,11 @@ impl std::fmt::Display for ParenType { std::fmt::Display::fmt(self.syntax(), f) } } +impl std::fmt::Display for ParenthesizedArgList { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} impl std::fmt::Display for Path { 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 2ec83d23b2..05c2a8354d 100644 --- a/crates/syntax/src/ast/make.rs +++ b/crates/syntax/src/ast/make.rs @@ -402,7 +402,7 @@ pub fn join_paths(paths: impl IntoIterator<Item = ast::Path>) -> ast::Path { // FIXME: should not be pub pub fn path_from_text(text: &str) -> ast::Path { - ast_from_text(&format!("fn main() {{ let test = {text}; }}")) + ast_from_text(&format!("fn main() {{ let test: {text}; }}")) } pub fn use_tree_glob() -> ast::UseTree { @@ -1053,7 +1053,17 @@ pub fn variant_list(variants: impl IntoIterator<Item = ast::Variant>) -> ast::Va ast_from_text(&format!("enum f {{ {variants} }}")) } -pub fn variant(name: ast::Name, field_list: Option<ast::FieldList>) -> ast::Variant { +pub fn variant( + visibility: Option<ast::Visibility>, + name: ast::Name, + field_list: Option<ast::FieldList>, + discriminant: Option<ast::Expr>, +) -> ast::Variant { + let visibility = match visibility { + None => String::new(), + Some(it) => format!("{it} "), + }; + let field_list = match field_list { None => String::new(), Some(it) => match it { @@ -1061,7 +1071,12 @@ pub fn variant(name: ast::Name, field_list: Option<ast::FieldList>) -> ast::Vari ast::FieldList::TupleFieldList(tuple) => format!("{tuple}"), }, }; - ast_from_text(&format!("enum f {{ {name}{field_list} }}")) + + let discriminant = match discriminant { + Some(it) => format!(" = {it}"), + None => String::new(), + }; + ast_from_text(&format!("enum f {{ {visibility}{name}{field_list}{discriminant} }}")) } pub fn fn_( @@ -1122,6 +1137,8 @@ pub fn struct_( pub fn enum_( visibility: Option<ast::Visibility>, enum_name: ast::Name, + generic_param_list: Option<ast::GenericParamList>, + where_clause: Option<ast::WhereClause>, variant_list: ast::VariantList, ) -> ast::Enum { let visibility = match visibility { @@ -1129,7 +1146,12 @@ pub fn enum_( Some(it) => format!("{it} "), }; - ast_from_text(&format!("{visibility}enum {enum_name} {variant_list}")) + let generic_params = generic_param_list.map(|it| it.to_string()).unwrap_or_default(); + let where_clause = where_clause.map(|it| format!(" {it}")).unwrap_or_default(); + + ast_from_text(&format!( + "{visibility}enum {enum_name}{generic_params}{where_clause} {variant_list}" + )) } pub fn attr_outer(meta: ast::Meta) -> ast::Attr { diff --git a/crates/syntax/src/ast/syntax_factory/constructors.rs b/crates/syntax/src/ast/syntax_factory/constructors.rs index 9f88add0f7..e86c291f76 100644 --- a/crates/syntax/src/ast/syntax_factory/constructors.rs +++ b/crates/syntax/src/ast/syntax_factory/constructors.rs @@ -2,9 +2,9 @@ use itertools::Itertools; use crate::{ - ast::{self, make, HasName}, + ast::{self, make, HasGenericParams, HasName, HasTypeBounds, HasVisibility}, syntax_editor::SyntaxMappingBuilder, - AstNode, + AstNode, NodeOrToken, SyntaxKind, SyntaxNode, SyntaxToken, }; use super::SyntaxFactory; @@ -14,6 +14,40 @@ impl SyntaxFactory { make::name(name).clone_for_update() } + pub fn ty(&self, text: &str) -> ast::Type { + make::ty(text).clone_for_update() + } + + pub fn ty_infer(&self) -> ast::InferType { + let ast::Type::InferType(ast) = make::ty_placeholder().clone_for_update() else { + unreachable!() + }; + + ast + } + + pub fn type_param( + &self, + name: ast::Name, + bounds: Option<ast::TypeBoundList>, + ) -> ast::TypeParam { + let ast = make::type_param(name.clone(), bounds.clone()).clone_for_update(); + + if let Some(mut mapping) = self.mappings() { + let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); + builder.map_node(name.syntax().clone(), ast.name().unwrap().syntax().clone()); + if let Some(input) = bounds { + builder.map_node( + input.syntax().clone(), + ast.type_bound_list().unwrap().syntax().clone(), + ); + } + builder.finish(&mut mapping); + } + + ast + } + pub fn ident_pat(&self, ref_: bool, mut_: bool, name: ast::Name) -> ast::IdentPat { let ast = make::ident_pat(ref_, mut_, name.clone()).clone_for_update(); @@ -32,22 +66,52 @@ impl SyntaxFactory { tail_expr: Option<ast::Expr>, ) -> ast::BlockExpr { let stmts = stmts.into_iter().collect_vec(); - let input = stmts.iter().map(|it| it.syntax().clone()).collect_vec(); + let mut input = stmts.iter().map(|it| it.syntax().clone()).collect_vec(); let ast = make::block_expr(stmts, tail_expr.clone()).clone_for_update(); - if let Some((mut mapping, stmt_list)) = self.mappings().zip(ast.stmt_list()) { + if let Some(mut mapping) = self.mappings() { + let stmt_list = ast.stmt_list().unwrap(); let mut builder = SyntaxMappingBuilder::new(stmt_list.syntax().clone()); + if let Some(input) = tail_expr { + builder.map_node( + input.syntax().clone(), + stmt_list.tail_expr().unwrap().syntax().clone(), + ); + } else if let Some(ast_tail) = stmt_list.tail_expr() { + // The parser interpreted the last statement (probably a statement with a block) as an Expr + let last_stmt = input.pop().unwrap(); + + builder.map_node(last_stmt, ast_tail.syntax().clone()); + } + builder.map_children( input.into_iter(), stmt_list.statements().map(|it| it.syntax().clone()), ); - if let Some((input, output)) = tail_expr.zip(stmt_list.tail_expr()) { - builder.map_node(input.syntax().clone(), output.syntax().clone()); - } + builder.finish(&mut mapping); + } + + ast + } + + pub fn expr_empty_block(&self) -> ast::BlockExpr { + ast::BlockExpr { syntax: make::expr_empty_block().syntax().clone_for_update() } + } + pub fn expr_bin(&self, lhs: ast::Expr, op: ast::BinaryOp, rhs: ast::Expr) -> ast::BinExpr { + let ast::Expr::BinExpr(ast) = + make::expr_bin_op(lhs.clone(), op, rhs.clone()).clone_for_update() + else { + unreachable!() + }; + + if let Some(mut mapping) = self.mappings() { + let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); + builder.map_node(lhs.syntax().clone(), ast.lhs().unwrap().syntax().clone()); + builder.map_node(rhs.syntax().clone(), ast.rhs().unwrap().syntax().clone()); builder.finish(&mut mapping); } @@ -83,6 +147,22 @@ impl SyntaxFactory { ast.into() } + pub fn expr_return(&self, expr: Option<ast::Expr>) -> ast::ReturnExpr { + let ast::Expr::ReturnExpr(ast) = make::expr_return(expr.clone()).clone_for_update() else { + unreachable!() + }; + + if let Some(mut mapping) = self.mappings() { + let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); + if let Some(input) = expr { + builder.map_node(input.syntax().clone(), ast.expr().unwrap().syntax().clone()); + } + builder.finish(&mut mapping); + } + + ast + } + pub fn let_stmt( &self, pattern: ast::Pat, @@ -107,4 +187,261 @@ impl SyntaxFactory { ast } + + pub fn turbofish_generic_arg_list( + &self, + args: impl IntoIterator<Item = ast::GenericArg> + Clone, + ) -> ast::GenericArgList { + let ast = make::turbofish_generic_arg_list(args.clone()).clone_for_update(); + + if let Some(mut mapping) = self.mappings() { + let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); + builder.map_children( + args.into_iter().map(|arg| arg.syntax().clone()), + ast.generic_args().map(|arg| arg.syntax().clone()), + ); + builder.finish(&mut mapping); + } + + ast + } + + pub fn record_field_list( + &self, + fields: impl IntoIterator<Item = ast::RecordField>, + ) -> ast::RecordFieldList { + let fields: Vec<ast::RecordField> = fields.into_iter().collect(); + let input: Vec<_> = fields.iter().map(|it| it.syntax().clone()).collect(); + let ast = make::record_field_list(fields).clone_for_update(); + + if let Some(mut mapping) = self.mappings() { + let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); + + builder.map_children(input.into_iter(), ast.fields().map(|it| it.syntax().clone())); + + builder.finish(&mut mapping); + } + + ast + } + + pub fn record_field( + &self, + visibility: Option<ast::Visibility>, + name: ast::Name, + ty: ast::Type, + ) -> ast::RecordField { + let ast = + make::record_field(visibility.clone(), name.clone(), ty.clone()).clone_for_update(); + + if let Some(mut mapping) = self.mappings() { + let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); + if let Some(visibility) = visibility { + builder.map_node( + visibility.syntax().clone(), + ast.visibility().unwrap().syntax().clone(), + ); + } + + builder.map_node(name.syntax().clone(), ast.name().unwrap().syntax().clone()); + builder.map_node(ty.syntax().clone(), ast.ty().unwrap().syntax().clone()); + + builder.finish(&mut mapping); + } + + ast + } + + pub fn tuple_field_list( + &self, + fields: impl IntoIterator<Item = ast::TupleField>, + ) -> ast::TupleFieldList { + let fields: Vec<ast::TupleField> = fields.into_iter().collect(); + let input: Vec<_> = fields.iter().map(|it| it.syntax().clone()).collect(); + let ast = make::tuple_field_list(fields).clone_for_update(); + + if let Some(mut mapping) = self.mappings() { + let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); + + builder.map_children(input.into_iter(), ast.fields().map(|it| it.syntax().clone())); + + builder.finish(&mut mapping); + } + + ast + } + + pub fn tuple_field( + &self, + visibility: Option<ast::Visibility>, + ty: ast::Type, + ) -> ast::TupleField { + let ast = make::tuple_field(visibility.clone(), ty.clone()).clone_for_update(); + + if let Some(mut mapping) = self.mappings() { + let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); + if let Some(visibility) = visibility { + builder.map_node( + visibility.syntax().clone(), + ast.visibility().unwrap().syntax().clone(), + ); + } + + builder.map_node(ty.syntax().clone(), ast.ty().unwrap().syntax().clone()); + + builder.finish(&mut mapping); + } + + ast + } + + pub fn item_enum( + &self, + visibility: Option<ast::Visibility>, + name: ast::Name, + generic_param_list: Option<ast::GenericParamList>, + where_clause: Option<ast::WhereClause>, + variant_list: ast::VariantList, + ) -> ast::Enum { + let ast = make::enum_( + visibility.clone(), + name.clone(), + generic_param_list.clone(), + where_clause.clone(), + variant_list.clone(), + ) + .clone_for_update(); + + if let Some(mut mapping) = self.mappings() { + let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); + if let Some(visibility) = visibility { + builder.map_node( + visibility.syntax().clone(), + ast.visibility().unwrap().syntax().clone(), + ); + } + + builder.map_node(name.syntax().clone(), ast.name().unwrap().syntax().clone()); + + if let Some(generic_param_list) = generic_param_list { + builder.map_node( + generic_param_list.syntax().clone(), + ast.generic_param_list().unwrap().syntax().clone(), + ); + } + + if let Some(where_clause) = where_clause { + builder.map_node( + where_clause.syntax().clone(), + ast.where_clause().unwrap().syntax().clone(), + ); + } + + builder.map_node( + variant_list.syntax().clone(), + ast.variant_list().unwrap().syntax().clone(), + ); + + builder.finish(&mut mapping); + } + + ast + } + + pub fn variant_list( + &self, + variants: impl IntoIterator<Item = ast::Variant>, + ) -> ast::VariantList { + let variants: Vec<ast::Variant> = variants.into_iter().collect(); + let input: Vec<_> = variants.iter().map(|it| it.syntax().clone()).collect(); + let ast = make::variant_list(variants).clone_for_update(); + + if let Some(mut mapping) = self.mappings() { + let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); + + builder.map_children(input.into_iter(), ast.variants().map(|it| it.syntax().clone())); + + builder.finish(&mut mapping); + } + + ast + } + + pub fn variant( + &self, + visibility: Option<ast::Visibility>, + name: ast::Name, + field_list: Option<ast::FieldList>, + discriminant: Option<ast::Expr>, + ) -> ast::Variant { + let ast = make::variant( + visibility.clone(), + name.clone(), + field_list.clone(), + discriminant.clone(), + ) + .clone_for_update(); + + if let Some(mut mapping) = self.mappings() { + let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); + if let Some(visibility) = visibility { + builder.map_node( + visibility.syntax().clone(), + ast.visibility().unwrap().syntax().clone(), + ); + } + + builder.map_node(name.syntax().clone(), ast.name().unwrap().syntax().clone()); + + if let Some(field_list) = field_list { + builder.map_node( + field_list.syntax().clone(), + ast.field_list().unwrap().syntax().clone(), + ); + } + + if let Some(discriminant) = discriminant { + builder + .map_node(discriminant.syntax().clone(), ast.expr().unwrap().syntax().clone()); + } + + builder.finish(&mut mapping); + } + + ast + } + + pub fn token_tree( + &self, + delimiter: SyntaxKind, + tt: Vec<NodeOrToken<ast::TokenTree, SyntaxToken>>, + ) -> ast::TokenTree { + let tt: Vec<_> = tt.into_iter().collect(); + let input: Vec<_> = tt.iter().cloned().filter_map(only_nodes).collect(); + + let ast = make::token_tree(delimiter, tt).clone_for_update(); + + if let Some(mut mapping) = self.mappings() { + let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); + builder.map_children( + input.into_iter(), + ast.token_trees_and_tokens().filter_map(only_nodes), + ); + builder.finish(&mut mapping); + } + + return ast; + + fn only_nodes(element: NodeOrToken<ast::TokenTree, SyntaxToken>) -> Option<SyntaxNode> { + element.as_node().map(|it| it.syntax().clone()) + } + } + + pub fn token(&self, kind: SyntaxKind) -> SyntaxToken { + make::token(kind) + } + + pub fn whitespace(&self, text: &str) -> ast::SyntaxToken { + make::tokens::whitespace(text) + } } diff --git a/crates/syntax/src/syntax_editor.rs b/crates/syntax/src/syntax_editor.rs index 714f5a9911..992a847663 100644 --- a/crates/syntax/src/syntax_editor.rs +++ b/crates/syntax/src/syntax_editor.rs @@ -16,6 +16,7 @@ use rustc_hash::FxHashMap; use crate::{SyntaxElement, SyntaxNode, SyntaxToken}; mod edit_algo; +mod edits; mod mapping; pub use mapping::{SyntaxMapping, SyntaxMappingBuilder}; @@ -326,7 +327,7 @@ mod tests { use crate::{ ast::{self, make, syntax_factory::SyntaxFactory}, - AstNode, + AstNode, SyntaxKind, }; use super::*; @@ -539,4 +540,50 @@ mod tests { }"#]]; expect.assert_eq(&edit.new_root.to_string()); } + + #[test] + fn test_replace_token_in_parent() { + let parent_fn = make::fn_( + None, + make::name("it"), + None, + None, + make::param_list(None, []), + make::block_expr([], Some(make::expr_unit())), + Some(make::ret_type(make::ty_unit())), + false, + false, + false, + false, + ); + + let mut editor = SyntaxEditor::new(parent_fn.syntax().clone()); + + if let Some(ret_ty) = parent_fn.ret_type() { + editor.delete(ret_ty.syntax().clone()); + + if let Some(SyntaxElement::Token(token)) = ret_ty.syntax().next_sibling_or_token() { + if token.kind().is_trivia() { + editor.delete(token); + } + } + } + + if let Some(tail) = parent_fn.body().unwrap().tail_expr() { + // FIXME: We do this because `xtask tidy` will not allow us to have trailing whitespace in the expect string. + if let Some(SyntaxElement::Token(token)) = tail.syntax().prev_sibling_or_token() { + if let SyntaxKind::WHITESPACE = token.kind() { + editor.delete(token); + } + } + editor.delete(tail.syntax().clone()); + } + + let edit = editor.finish(); + + let expect = expect![[r#" +fn it() { +}"#]]; + expect.assert_eq(&edit.new_root.to_string()); + } } diff --git a/crates/syntax/src/syntax_editor/edit_algo.rs b/crates/syntax/src/syntax_editor/edit_algo.rs index b769c94110..57ecbe5701 100644 --- a/crates/syntax/src/syntax_editor/edit_algo.rs +++ b/crates/syntax/src/syntax_editor/edit_algo.rs @@ -73,7 +73,7 @@ pub(super) fn apply_edits(editor: SyntaxEditor) -> SyntaxEdit { }) .all(|(l, r)| { get_node_depth(l.target_parent()) != get_node_depth(r.target_parent()) - || l.target_range().intersect(r.target_range()).is_none() + || (l.target_range().end() <= r.target_range().start()) }); if stdx::never!( @@ -128,13 +128,14 @@ pub(super) fn apply_edits(editor: SyntaxEditor) -> SyntaxEdit { // Add to changed ancestors, if applicable match change { - Change::Insert(_, _) | Change::InsertAll(_, _) => {} - Change::Replace(target, _) | Change::ReplaceWithMany(target, _) => { + Change::Replace(SyntaxElement::Node(target), _) + | Change::ReplaceWithMany(SyntaxElement::Node(target), _) => { changed_ancestors.push_back(ChangedAncestor::single(target, change_index)) } Change::ReplaceAll(range, _) => { changed_ancestors.push_back(ChangedAncestor::multiple(range, change_index)) } + _ => (), } } @@ -154,6 +155,12 @@ pub(super) fn apply_edits(editor: SyntaxEditor) -> SyntaxEdit { } }; } + Change::Replace(SyntaxElement::Node(target), Some(SyntaxElement::Node(new_target))) => { + *target = tree_mutator.make_syntax_mut(target); + if new_target.ancestors().any(|node| node == tree_mutator.immutable) { + *new_target = new_target.clone_for_update(); + } + } Change::Replace(target, _) | Change::ReplaceWithMany(target, _) => { *target = tree_mutator.make_element_mut(target); } @@ -304,13 +311,8 @@ enum ChangedAncestorKind { } impl ChangedAncestor { - fn single(element: &SyntaxElement, change_index: usize) -> Self { - let kind = match element { - SyntaxElement::Node(node) => ChangedAncestorKind::Single { node: node.clone() }, - SyntaxElement::Token(token) => { - ChangedAncestorKind::Single { node: token.parent().unwrap() } - } - }; + fn single(node: &SyntaxNode, change_index: usize) -> Self { + let kind = ChangedAncestorKind::Single { node: node.clone() }; Self { kind, change_index } } diff --git a/crates/syntax/src/syntax_editor/edits.rs b/crates/syntax/src/syntax_editor/edits.rs new file mode 100644 index 0000000000..8069fdd06f --- /dev/null +++ b/crates/syntax/src/syntax_editor/edits.rs @@ -0,0 +1,274 @@ +//! Structural editing for ast using `SyntaxEditor` + +use crate::{ + ast::{ + self, edit::IndentLevel, make, syntax_factory::SyntaxFactory, AstNode, Fn, GenericParam, + HasGenericParams, HasName, + }, + syntax_editor::{Position, SyntaxEditor}, + Direction, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, T, +}; + +impl SyntaxEditor { + /// Adds a new generic param to the function using `SyntaxEditor` + pub fn add_generic_param(&mut self, function: &Fn, new_param: GenericParam) { + match function.generic_param_list() { + Some(generic_param_list) => match generic_param_list.generic_params().last() { + Some(last_param) => { + // There exists a generic param list and it's not empty + let position = generic_param_list.r_angle_token().map_or_else( + || Position::last_child_of(function.syntax()), + Position::before, + ); + + if last_param + .syntax() + .next_sibling_or_token() + .map_or(false, |it| it.kind() == SyntaxKind::COMMA) + { + self.insert( + Position::after(last_param.syntax()), + new_param.syntax().clone(), + ); + self.insert( + Position::after(last_param.syntax()), + make::token(SyntaxKind::WHITESPACE), + ); + self.insert( + Position::after(last_param.syntax()), + make::token(SyntaxKind::COMMA), + ); + } else { + let elements = vec![ + make::token(SyntaxKind::COMMA).into(), + make::token(SyntaxKind::WHITESPACE).into(), + new_param.syntax().clone().into(), + ]; + self.insert_all(position, elements); + } + } + None => { + // There exists a generic param list but it's empty + let position = Position::after(generic_param_list.l_angle_token().unwrap()); + self.insert(position, new_param.syntax()); + } + }, + None => { + // There was no generic param list + let position = if let Some(name) = function.name() { + Position::after(name.syntax) + } else if let Some(fn_token) = function.fn_token() { + Position::after(fn_token) + } else if let Some(param_list) = function.param_list() { + Position::before(param_list.syntax) + } else { + Position::last_child_of(function.syntax()) + }; + let elements = vec![ + make::token(SyntaxKind::L_ANGLE).into(), + new_param.syntax().clone().into(), + make::token(SyntaxKind::R_ANGLE).into(), + ]; + self.insert_all(position, elements); + } + } + } +} + +fn get_or_insert_comma_after(editor: &mut SyntaxEditor, syntax: &SyntaxNode) -> SyntaxToken { + let make = SyntaxFactory::without_mappings(); + match syntax + .siblings_with_tokens(Direction::Next) + .filter_map(|it| it.into_token()) + .find(|it| it.kind() == T![,]) + { + Some(it) => it, + None => { + let comma = make.token(T![,]); + editor.insert(Position::after(syntax), &comma); + comma + } + } +} + +impl ast::VariantList { + pub fn add_variant(&self, editor: &mut SyntaxEditor, variant: &ast::Variant) { + let make = SyntaxFactory::without_mappings(); + let (indent, position) = match self.variants().last() { + Some(last_item) => ( + IndentLevel::from_node(last_item.syntax()), + Position::after(get_or_insert_comma_after(editor, last_item.syntax())), + ), + None => match self.l_curly_token() { + Some(l_curly) => { + normalize_ws_between_braces(editor, self.syntax()); + (IndentLevel::from_token(&l_curly) + 1, Position::after(&l_curly)) + } + None => (IndentLevel::single(), Position::last_child_of(self.syntax())), + }, + }; + let elements: Vec<SyntaxElement> = vec![ + make.whitespace(&format!("{}{indent}", "\n")).into(), + variant.syntax().clone().into(), + make.token(T![,]).into(), + ]; + editor.insert_all(position, elements); + } +} + +fn normalize_ws_between_braces(editor: &mut SyntaxEditor, node: &SyntaxNode) -> Option<()> { + let make = SyntaxFactory::without_mappings(); + let l = node + .children_with_tokens() + .filter_map(|it| it.into_token()) + .find(|it| it.kind() == T!['{'])?; + let r = node + .children_with_tokens() + .filter_map(|it| it.into_token()) + .find(|it| it.kind() == T!['}'])?; + + let indent = IndentLevel::from_node(node); + + match l.next_sibling_or_token() { + Some(ws) if ws.kind() == SyntaxKind::WHITESPACE => { + if ws.next_sibling_or_token()?.into_token()? == r { + editor.replace(ws, make.whitespace(&format!("\n{indent}"))); + } + } + Some(ws) if ws.kind() == T!['}'] => { + editor.insert(Position::after(l), make.whitespace(&format!("\n{indent}"))); + } + _ => (), + } + Some(()) +} + +#[cfg(test)] +mod tests { + use parser::Edition; + use stdx::trim_indent; + use test_utils::assert_eq_text; + + use crate::SourceFile; + + use super::*; + + fn ast_from_text<N: AstNode>(text: &str) -> N { + let parse = SourceFile::parse(text, Edition::CURRENT); + let node = match parse.tree().syntax().descendants().find_map(N::cast) { + Some(it) => it, + None => { + let node = std::any::type_name::<N>(); + panic!("Failed to make ast node `{node}` from text {text}") + } + }; + let node = node.clone_subtree(); + assert_eq!(node.syntax().text_range().start(), 0.into()); + node + } + + #[test] + fn add_variant_to_empty_enum() { + let make = SyntaxFactory::without_mappings(); + let variant = make.variant(None, make.name("Bar"), None, None); + + check_add_variant( + r#" +enum Foo {} +"#, + r#" +enum Foo { + Bar, +} +"#, + variant, + ); + } + + #[test] + fn add_variant_to_non_empty_enum() { + let make = SyntaxFactory::without_mappings(); + let variant = make.variant(None, make.name("Baz"), None, None); + + check_add_variant( + r#" +enum Foo { + Bar, +} +"#, + r#" +enum Foo { + Bar, + Baz, +} +"#, + variant, + ); + } + + #[test] + fn add_variant_with_tuple_field_list() { + let make = SyntaxFactory::without_mappings(); + let variant = make.variant( + None, + make.name("Baz"), + Some(make.tuple_field_list([make.tuple_field(None, make.ty("bool"))]).into()), + None, + ); + + check_add_variant( + r#" +enum Foo { + Bar, +} +"#, + r#" +enum Foo { + Bar, + Baz(bool), +} +"#, + variant, + ); + } + + #[test] + fn add_variant_with_record_field_list() { + let make = SyntaxFactory::without_mappings(); + let variant = make.variant( + None, + make.name("Baz"), + Some( + make.record_field_list([make.record_field(None, make.name("x"), make.ty("bool"))]) + .into(), + ), + None, + ); + + check_add_variant( + r#" +enum Foo { + Bar, +} +"#, + r#" +enum Foo { + Bar, + Baz { x: bool }, +} +"#, + variant, + ); + } + + fn check_add_variant(before: &str, expected: &str, variant: ast::Variant) { + let enum_ = ast_from_text::<ast::Enum>(before); + let mut editor = SyntaxEditor::new(enum_.syntax().clone()); + if let Some(it) = enum_.variant_list() { + it.add_variant(&mut editor, &variant) + } + let edit = editor.finish(); + let after = edit.new_root.to_string(); + assert_eq_text!(&trim_indent(expected.trim()), &trim_indent(after.trim())); + } +} diff --git a/crates/test-fixture/src/lib.rs b/crates/test-fixture/src/lib.rs index 593e31c2fb..889a7d10ad 100644 --- a/crates/test-fixture/src/lib.rs +++ b/crates/test-fixture/src/lib.rs @@ -236,7 +236,7 @@ impl ChangeFixture { crate_graph.add_crate_root( crate_root, Edition::CURRENT, - Some(CrateName::new("test").unwrap().into()), + Some(CrateName::new("ra_test_fixture").unwrap().into()), None, From::from(default_cfg.clone()), Some(From::from(default_cfg)), diff --git a/crates/test-utils/src/minicore.rs b/crates/test-utils/src/minicore.rs index 07767d5ae9..99dfabe174 100644 --- a/crates/test-utils/src/minicore.rs +++ b/crates/test-utils/src/minicore.rs @@ -12,6 +12,7 @@ //! asm: //! assert: //! as_ref: sized +//! async_fn: fn, tuple, future, copy //! bool_impl: option, fn //! builtin_impls: //! cell: copy, drop @@ -29,7 +30,7 @@ //! eq: sized //! error: fmt //! fmt: option, result, transmute, coerce_unsized, copy, clone, derive -//! fn: +//! fn: tuple //! from: sized //! future: pin //! coroutine: pin @@ -60,6 +61,7 @@ //! sync: sized //! transmute: //! try: infallible +//! tuple: //! unpin: sized //! unsize: sized //! todo: panic @@ -138,10 +140,10 @@ pub mod marker { } // endregion:copy - // region:fn + // region:tuple #[lang = "tuple_trait"] pub trait Tuple {} - // endregion:fn + // endregion:tuple // region:phantom_data #[lang = "phantom_data"] @@ -682,6 +684,112 @@ pub mod ops { } pub use self::function::{Fn, FnMut, FnOnce}; // endregion:fn + + // region:async_fn + mod async_function { + use crate::{future::Future, marker::Tuple}; + + #[lang = "async_fn"] + #[fundamental] + pub trait AsyncFn<Args: Tuple>: AsyncFnMut<Args> { + extern "rust-call" fn async_call(&self, args: Args) -> Self::CallRefFuture<'_>; + } + + #[lang = "async_fn_mut"] + #[fundamental] + pub trait AsyncFnMut<Args: Tuple>: AsyncFnOnce<Args> { + #[lang = "call_ref_future"] + type CallRefFuture<'a>: Future<Output = Self::Output> + where + Self: 'a; + extern "rust-call" fn async_call_mut(&mut self, args: Args) -> Self::CallRefFuture<'_>; + } + + #[lang = "async_fn_once"] + #[fundamental] + pub trait AsyncFnOnce<Args: Tuple> { + #[lang = "async_fn_once_output"] + type Output; + #[lang = "call_once_future"] + type CallOnceFuture: Future<Output = Self::Output>; + extern "rust-call" fn async_call_once(self, args: Args) -> Self::CallOnceFuture; + } + + mod impls { + use super::{AsyncFn, AsyncFnMut, AsyncFnOnce}; + use crate::marker::Tuple; + + impl<A: Tuple, F: ?Sized> AsyncFn<A> for &F + where + F: AsyncFn<A>, + { + extern "rust-call" fn async_call(&self, args: A) -> Self::CallRefFuture<'_> { + F::async_call(*self, args) + } + } + + impl<A: Tuple, F: ?Sized> AsyncFnMut<A> for &F + where + F: AsyncFn<A>, + { + type CallRefFuture<'a> + = F::CallRefFuture<'a> + where + Self: 'a; + + extern "rust-call" fn async_call_mut( + &mut self, + args: A, + ) -> Self::CallRefFuture<'_> { + F::async_call(*self, args) + } + } + + impl<'a, A: Tuple, F: ?Sized> AsyncFnOnce<A> for &'a F + where + F: AsyncFn<A>, + { + type Output = F::Output; + type CallOnceFuture = F::CallRefFuture<'a>; + + extern "rust-call" fn async_call_once(self, args: A) -> Self::CallOnceFuture { + F::async_call(self, args) + } + } + + impl<A: Tuple, F: ?Sized> AsyncFnMut<A> for &mut F + where + F: AsyncFnMut<A>, + { + type CallRefFuture<'a> + = F::CallRefFuture<'a> + where + Self: 'a; + + extern "rust-call" fn async_call_mut( + &mut self, + args: A, + ) -> Self::CallRefFuture<'_> { + F::async_call_mut(*self, args) + } + } + + impl<'a, A: Tuple, F: ?Sized> AsyncFnOnce<A> for &'a mut F + where + F: AsyncFnMut<A>, + { + type Output = F::Output; + type CallOnceFuture = F::CallRefFuture<'a>; + + extern "rust-call" fn async_call_once(self, args: A) -> Self::CallOnceFuture { + F::async_call_mut(self, args) + } + } + } + } + pub use self::async_function::{AsyncFn, AsyncFnMut, AsyncFnOnce}; + // endregion:async_fn + // region:try mod try_ { use crate::convert::Infallible; @@ -1684,6 +1792,7 @@ pub mod prelude { marker::Sync, // :sync mem::drop, // :drop ops::Drop, // :drop + ops::{AsyncFn, AsyncFnMut, AsyncFnOnce}, // :async_fn ops::{Fn, FnMut, FnOnce}, // :fn option::Option::{self, None, Some}, // :option panic, // :panic diff --git a/docs/dev/lsp-extensions.md b/docs/dev/lsp-extensions.md index b7c536e027..2aad2cfa36 100644 --- a/docs/dev/lsp-extensions.md +++ b/docs/dev/lsp-extensions.md @@ -1,5 +1,5 @@ <!--- -lsp/ext.rs hash: 96f88b7a5d0080c6 +lsp/ext.rs hash: 14b7fb1309f5bb00 If you need to change the above hash to make the test pass, please check if you need to adjust this doc as well and ping this issue: diff --git a/docs/user/generated_config.adoc b/docs/user/generated_config.adoc index 052d0a2a41..1195a85cf7 100644 --- a/docs/user/generated_config.adoc +++ b/docs/user/generated_config.adoc @@ -992,10 +992,10 @@ Show full signature of the callable. Only shows parameters if disabled. -- Show documentation. -- -[[rust-analyzer.typing.autoClosingAngleBrackets.enable]]rust-analyzer.typing.autoClosingAngleBrackets.enable (default: `false`):: +[[rust-analyzer.typing.excludeChars]]rust-analyzer.typing.excludeChars (default: `"|<"`):: + -- -Whether to insert closing angle brackets when typing an opening angle bracket of a generic argument list. +Specify the characters to exclude from triggering typing assists. The default trigger characters are `.`, `=`, `<`, `>`, `{`, and `(`. -- [[rust-analyzer.workspace.discoverConfig]]rust-analyzer.workspace.discoverConfig (default: `null`):: + diff --git a/editors/code/package.json b/editors/code/package.json index 82c43b76fd..469c1b458d 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -425,6 +425,41 @@ ], "default": "openLogs", "markdownDescription": "Action to run when clicking the extension status bar item." + }, + "rust-analyzer.statusBar.documentSelector": { + "type": [ + "array", + "null" + ], + "items": { + "type": "object", + "properties": { + "language": { + "type": [ + "string", + "null" + ] + }, + "pattern": { + "type": [ + "string", + "null" + ] + } + } + }, + "default": [ + { + "language": "rust" + }, + { + "pattern": "**/Cargo.toml" + }, + { + "pattern": "**/Cargo.lock" + } + ], + "markdownDescription": "Determines when to show the extension status bar item based on the currently open file. Use `{ \"pattern\": \"**\" }` to always show. Use `null` to never show." } } }, @@ -2570,10 +2605,13 @@ { "title": "typing", "properties": { - "rust-analyzer.typing.autoClosingAngleBrackets.enable": { - "markdownDescription": "Whether to insert closing angle brackets when typing an opening angle bracket of a generic argument list.", - "default": false, - "type": "boolean" + "rust-analyzer.typing.excludeChars": { + "markdownDescription": "Specify the characters to exclude from triggering typing assists. The default trigger characters are `.`, `=`, `<`, `>`, `{`, and `(`.", + "default": "|<", + "type": [ + "null", + "string" + ] } } }, diff --git a/editors/code/src/client.ts b/editors/code/src/client.ts index eac7b849fd..4ce19f5c66 100644 --- a/editors/code/src/client.ts +++ b/editors/code/src/client.ts @@ -324,7 +324,7 @@ class ExperimentalFeatures implements lc.StaticFeature { } fillClientCapabilities(capabilities: lc.ClientCapabilities): void { capabilities.experimental = { - snippetTextEdit: true, + snippetTextEdit: false, codeActionGroup: true, hoverActions: true, serverStatusNotification: true, diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts index 67bc72f1e1..f7ef80df2b 100644 --- a/editors/code/src/config.ts +++ b/editors/code/src/config.ts @@ -261,9 +261,9 @@ export class Config { return this.get<boolean | undefined>("testExplorer"); } - get runnablesExtraEnv() { + runnablesExtraEnv(label: string): Record<string, string> | undefined { const item = this.get<any>("runnables.extraEnv") ?? this.get<any>("runnableEnv"); - if (!item) return item; + if (!item) return undefined; const fixRecord = (r: Record<string, any>) => { for (const key in r) { if (typeof r[key] !== "string") { @@ -271,11 +271,28 @@ export class Config { } } }; + + const platform = process.platform; + const checkPlatform = (it: RunnableEnvCfgItem) => { + if (it.platform) { + const platforms = Array.isArray(it.platform) ? it.platform : [it.platform]; + return platforms.indexOf(platform) >= 0; + } + return true; + }; + if (item instanceof Array) { - item.forEach((x) => fixRecord(x.env)); - } else { - fixRecord(item); + const env = {}; + for (const it of item) { + const masked = !it.mask || new RegExp(it.mask).test(label); + if (masked && checkPlatform(it)) { + Object.assign(env, it.env); + } + } + fixRecord(env); + return env; } + fixRecord(item); return item; } @@ -331,6 +348,10 @@ export class Config { return this.get<string>("statusBar.clickAction"); } + get statusBarDocumentSelector() { + return this.get<vscode.DocumentSelector>("statusBar.documentSelector"); + } + get initializeStopped() { return this.get<boolean>("initializeStopped"); } diff --git a/editors/code/src/ctx.ts b/editors/code/src/ctx.ts index 234fe6ab02..4a3f66b00d 100644 --- a/editors/code/src/ctx.ts +++ b/editors/code/src/ctx.ts @@ -88,6 +88,7 @@ export class Ctx implements RustAnalyzerExtensionApi { private _treeView: vscode.TreeView<Dependency | DependencyFile | DependencyId> | undefined; private lastStatus: ServerStatusParams | { health: "stopped" } = { health: "stopped" }; private _serverVersion: string; + private statusBarActiveEditorListener: Disposable; get serverPath(): string | undefined { return this._serverPath; @@ -119,6 +120,10 @@ export class Ctx implements RustAnalyzerExtensionApi { this._serverVersion = "<not running>"; this.config = new Config(extCtx.subscriptions); this.statusBar = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left); + this.updateStatusBarVisibility(vscode.window.activeTextEditor); + this.statusBarActiveEditorListener = vscode.window.onDidChangeActiveTextEditor((editor) => + this.updateStatusBarVisibility(editor), + ); if (this.config.testExplorer) { this.testController = vscode.tests.createTestController( "rustAnalyzerTestController", @@ -141,6 +146,7 @@ export class Ctx implements RustAnalyzerExtensionApi { dispose() { this.config.dispose(); this.statusBar.dispose(); + this.statusBarActiveEditorListener.dispose(); this.testController?.dispose(); void this.disposeClient(); this.commandDisposables.forEach((disposable) => disposable.dispose()); @@ -404,7 +410,6 @@ export class Ctx implements RustAnalyzerExtensionApi { let icon = ""; const status = this.lastStatus; const statusBar = this.statusBar; - statusBar.show(); statusBar.tooltip = new vscode.MarkdownString("", true); statusBar.tooltip.isTrusted = true; switch (status.health) { @@ -472,6 +477,17 @@ export class Ctx implements RustAnalyzerExtensionApi { statusBar.text = `${icon}rust-analyzer`; } + private updateStatusBarVisibility(editor: vscode.TextEditor | undefined) { + const documentSelector = this.config.statusBarDocumentSelector; + if (documentSelector != null) { + if (editor != null && vscode.languages.match(documentSelector, editor.document) > 0) { + this.statusBar.show(); + return; + } + } + this.statusBar.hide(); + } + pushExtCleanup(d: Disposable) { this.extCtx.subscriptions.push(d); } diff --git a/editors/code/src/debug.ts b/editors/code/src/debug.ts index 9e2e3d2185..f21ca2e8f9 100644 --- a/editors/code/src/debug.ts +++ b/editors/code/src/debug.ts @@ -148,8 +148,16 @@ async function getDebugConfiguration( return path.normalize(p).replace(wsFolder, `\${workspaceFolder${workspaceQualifier}}`); } - const env = prepareEnv(inheritEnv, runnable.label, runnableArgs, config.runnablesExtraEnv); - const executable = await getDebugExecutable(runnableArgs, env); + const executable = await getDebugExecutable( + runnableArgs, + prepareEnv(true, {}, config.runnablesExtraEnv(runnable.label)), + ); + + const env = prepareEnv( + inheritEnv, + runnableArgs.environment, + config.runnablesExtraEnv(runnable.label), + ); let sourceFileMap = debugOptions.sourceFileMap; if (sourceFileMap === "auto") { diff --git a/editors/code/src/run.ts b/editors/code/src/run.ts index 8a82a5a58c..f71ab7ffbd 100644 --- a/editors/code/src/run.ts +++ b/editors/code/src/run.ts @@ -5,7 +5,7 @@ import * as tasks from "./tasks"; import type { CtxInit } from "./ctx"; import { makeDebugConfig } from "./debug"; -import type { Config, RunnableEnvCfg, RunnableEnvCfgItem } from "./config"; +import type { Config } from "./config"; import type { LanguageClient } from "vscode-languageclient/node"; import { unwrapUndefinable, type RustEditor } from "./util"; @@ -81,32 +81,13 @@ export function prepareBaseEnv( export function prepareEnv( inheritEnv: boolean, - label: string, - runnableArgs: ra.CargoRunnableArgs, - runnableEnvCfg?: RunnableEnvCfg, + runnableEnv?: Record<string, string>, + runnableEnvCfg?: Record<string, string>, ): Record<string, string> { - const env = prepareBaseEnv(inheritEnv, runnableArgs.environment); - const platform = process.platform; - - const checkPlatform = (it: RunnableEnvCfgItem) => { - if (it.platform) { - const platforms = Array.isArray(it.platform) ? it.platform : [it.platform]; - return platforms.indexOf(platform) >= 0; - } - return true; - }; + const env = prepareBaseEnv(inheritEnv, runnableEnv); if (runnableEnvCfg) { - if (Array.isArray(runnableEnvCfg)) { - for (const it of runnableEnvCfg) { - const masked = !it.mask || new RegExp(it.mask).test(label); - if (masked && checkPlatform(it)) { - Object.assign(env, it.env); - } - } - } else { - Object.assign(env, runnableEnvCfg); - } + Object.assign(env, runnableEnvCfg); } return env; @@ -140,7 +121,11 @@ export async function createTaskFromRunnable( }; options = { cwd: runnableArgs.workspaceRoot || ".", - env: prepareEnv(true, runnable.label, runnableArgs, config.runnablesExtraEnv), + env: prepareEnv( + true, + runnableArgs.environment, + config.runnablesExtraEnv(runnable.label), + ), }; } else { const runnableArgs = runnable.args; diff --git a/editors/code/tests/unit/runnable_env.test.ts b/editors/code/tests/unit/runnable_env.test.ts deleted file mode 100644 index f0a62a3cce..0000000000 --- a/editors/code/tests/unit/runnable_env.test.ts +++ /dev/null @@ -1,122 +0,0 @@ -import * as assert from "assert"; -import { prepareEnv } from "../../src/run"; -import type { RunnableEnvCfg } from "../../src/config"; -import type { Context } from "."; -import type * as ra from "../../src/lsp_ext"; - -function makeRunnable(label: string): ra.Runnable { - return { - label, - kind: "cargo", - args: { - cargoArgs: [], - cwd: ".", - executableArgs: [], - }, - }; -} - -function fakePrepareEnv(runnableName: string, config?: RunnableEnvCfg): Record<string, string> { - const runnable = makeRunnable(runnableName); - const runnableArgs = runnable.args as ra.CargoRunnableArgs; - return prepareEnv(false, runnable.label, runnableArgs, config); -} - -export async function getTests(ctx: Context) { - await ctx.suite("Runnable env", (suite) => { - suite.addTest("Global config works", async () => { - const binEnv = fakePrepareEnv("run project_name", { GLOBAL: "g" }); - assert.strictEqual(binEnv["GLOBAL"], "g"); - - const testEnv = fakePrepareEnv("test some::mod::test_name", { GLOBAL: "g" }); - assert.strictEqual(testEnv["GLOBAL"], "g"); - }); - - suite.addTest("null mask works", async () => { - const config = [ - { - env: { DATA: "data" }, - }, - ]; - const binEnv = fakePrepareEnv("run project_name", config); - assert.strictEqual(binEnv["DATA"], "data"); - - const testEnv = fakePrepareEnv("test some::mod::test_name", config); - assert.strictEqual(testEnv["DATA"], "data"); - }); - - suite.addTest("order works", async () => { - const config = [ - { - env: { DATA: "data" }, - }, - { - env: { DATA: "newdata" }, - }, - ]; - const binEnv = fakePrepareEnv("run project_name", config); - assert.strictEqual(binEnv["DATA"], "newdata"); - - const testEnv = fakePrepareEnv("test some::mod::test_name", config); - assert.strictEqual(testEnv["DATA"], "newdata"); - }); - - suite.addTest("mask works", async () => { - const config = [ - { - env: { DATA: "data" }, - }, - { - mask: "^run", - env: { DATA: "rundata" }, - }, - { - mask: "special_test$", - env: { DATA: "special_test" }, - }, - ]; - const binEnv = fakePrepareEnv("run project_name", config); - assert.strictEqual(binEnv["DATA"], "rundata"); - - const testEnv = fakePrepareEnv("test some::mod::test_name", config); - assert.strictEqual(testEnv["DATA"], "data"); - - const specialTestEnv = fakePrepareEnv("test some::mod::special_test", config); - assert.strictEqual(specialTestEnv["DATA"], "special_test"); - }); - - suite.addTest("exact test name works", async () => { - const config = [ - { - env: { DATA: "data" }, - }, - { - mask: "some::mod::test_name", - env: { DATA: "test special" }, - }, - ]; - const testEnv = fakePrepareEnv("test some::mod::test_name", config); - assert.strictEqual(testEnv["DATA"], "test special"); - - const specialTestEnv = fakePrepareEnv("test some::mod::another_test", config); - assert.strictEqual(specialTestEnv["DATA"], "data"); - }); - - suite.addTest("test mod name works", async () => { - const config = [ - { - env: { DATA: "data" }, - }, - { - mask: "some::mod", - env: { DATA: "mod special" }, - }, - ]; - const testEnv = fakePrepareEnv("test some::mod::test_name", config); - assert.strictEqual(testEnv["DATA"], "mod special"); - - const specialTestEnv = fakePrepareEnv("test some::mod::another_test", config); - assert.strictEqual(specialTestEnv["DATA"], "mod special"); - }); - }); -} diff --git a/rust-version b/rust-version index 8f41ed9e14..7d60fa6cb7 100644 --- a/rust-version +++ b/rust-version @@ -1 +1 @@ -f005c7437def424a1c43cbc380352a58d8ac920b +5a6036a1802262f8cf02192b02026688d396f1d7 |