Unnamed repository; edit this file 'description' to name the repository.
Auto merge of #153026 - JonathanBrouwer:rollup-PAPpAYW, r=JonathanBrouwer
Rollup of 14 pull requests Successful merges: - rust-lang/rust#153007 (`rust-analyzer` subtree update) - rust-lang/rust#152670 (Simplify ThinLTO handling) - rust-lang/rust#152768 (Enable autodiff in ci for all major os) - rust-lang/rust#152908 (Enable rust.remap-debuginfo in the dist profile) - rust-lang/rust#152999 (Check importing `crate`/`$crate`/`super` after handling `self`) - rust-lang/rust#152003 (Reflection TypeId::trait_info_of) - rust-lang/rust#152976 (Revert relative paths for std links in rustc-docs) - rust-lang/rust#152985 (Port `#[feature]` to the new attribute system) - rust-lang/rust#152989 (Port `#[rustc_inherit_overflow_checks]` to the new attribute parsers) - rust-lang/rust#152991 (fix interpreter tracing output) - rust-lang/rust#153004 (Superficial tweaks to the query modifier docs in `rustc_middle::query::modifiers`) - rust-lang/rust#153008 (bootstrap.compiler.toml: update name of primary branch) - rust-lang/rust#153016 (Migration of `LintDiagnostic` - part 2) - rust-lang/rust#153020 (rustdoc: Improve sentence for documented empty impl blocks) Failed merges: - rust-lang/rust#152988 (Port `#[register_tool]` to the new attribute system)
bors 8 weeks ago
parent 61e7ff7 · parent 8734618 · commit 28e3ea5
-rw-r--r--.github/workflows/ci.yaml22
-rw-r--r--crates/hir-def/src/hir/generics.rs2
-rw-r--r--crates/hir-def/src/lib.rs16
-rw-r--r--crates/hir-ty/src/builtin_derive.rs73
-rw-r--r--crates/hir-ty/src/generics.rs16
-rw-r--r--crates/hir-ty/src/lib.rs64
-rw-r--r--crates/hir-ty/src/lower.rs796
-rw-r--r--crates/hir-ty/src/lower/path.rs98
-rw-r--r--crates/hir-ty/src/next_solver/util.rs31
-rw-r--r--crates/hir-ty/src/representability.rs131
-rw-r--r--crates/hir-ty/src/tests/regression.rs25
-rw-r--r--crates/hir-ty/src/utils.rs65
-rw-r--r--crates/hir/src/lib.rs2
-rw-r--r--crates/ide-assists/src/handlers/add_missing_impl_members.rs80
-rw-r--r--crates/ide-assists/src/handlers/convert_bool_then.rs30
-rw-r--r--crates/ide-assists/src/handlers/convert_to_guarded_return.rs26
-rw-r--r--crates/ide-assists/src/handlers/generate_function.rs51
-rw-r--r--crates/ide-assists/src/handlers/generate_getter_or_setter.rs35
-rw-r--r--crates/ide-assists/src/handlers/replace_if_let_with_match.rs25
-rw-r--r--crates/ide-assists/src/handlers/replace_is_method_with_if_let_method.rs58
-rw-r--r--crates/ide-assists/src/handlers/replace_let_with_if_let.rs23
-rw-r--r--crates/ide-assists/src/handlers/toggle_macro_delimiter.rs55
-rw-r--r--crates/ide-completion/src/completions/fn_param.rs54
-rw-r--r--crates/ide-completion/src/completions/postfix.rs152
-rw-r--r--crates/ide-completion/src/context/analysis.rs2
-rw-r--r--crates/ide-completion/src/tests/attribute.rs66
-rw-r--r--crates/ide-completion/src/tests/expression.rs2
-rw-r--r--crates/ide-completion/src/tests/fn_param.rs54
-rw-r--r--crates/ide-db/src/imports/insert_use.rs41
-rw-r--r--crates/ide-db/src/imports/insert_use/tests.rs153
-rw-r--r--crates/ide-db/src/imports/merge_imports.rs16
-rw-r--r--crates/ide-db/src/path_transform.rs117
-rw-r--r--crates/ide-db/src/search.rs2
-rw-r--r--crates/ide-db/src/ty_filter.rs10
-rw-r--r--crates/ide/src/references.rs24
-rw-r--r--crates/ide/src/signature_help.rs4
-rw-r--r--crates/load-cargo/src/lib.rs47
-rw-r--r--crates/proc-macro-api/src/bidirectional_protocol/msg.rs8
-rw-r--r--crates/proc-macro-srv-cli/src/main_loop.rs36
-rw-r--r--crates/proc-macro-srv/src/lib.rs1
-rw-r--r--crates/proc-macro-srv/src/server_impl/rust_analyzer_span.rs4
-rw-r--r--crates/proc-macro-srv/src/tests/utils.rs4
-rw-r--r--crates/profile/src/google_cpu_profiler.rs2
-rw-r--r--crates/project-model/src/build_dependencies.rs34
-rw-r--r--crates/project-model/src/cargo_config_file.rs62
-rw-r--r--crates/project-model/src/cargo_workspace.rs49
-rw-r--r--crates/project-model/src/project_json.rs4
-rw-r--r--crates/rust-analyzer/src/flycheck.rs38
-rw-r--r--crates/rust-analyzer/tests/slow-tests/flycheck.rs112
-rw-r--r--crates/rust-analyzer/tests/slow-tests/main.rs1
-rw-r--r--crates/rust-analyzer/tests/slow-tests/support.rs51
-rw-r--r--docs/book/src/non_cargo_based_projects.md7
-rw-r--r--docs/book/src/vs_code.md6
-rw-r--r--editors/code/package-lock.json6
-rw-r--r--rust-version2
55 files changed, 2058 insertions, 837 deletions
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index ca7d3058d8..04de6d11e3 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -50,7 +50,7 @@ jobs:
ref: ${{ github.event.pull_request.head.sha }}
- name: Install rustup-toolchain-install-master
- run: cargo install [email protected]
+ run: cargo install [email protected]
# Install a pinned rustc commit to avoid surprises
- name: Install Rust toolchain
@@ -226,7 +226,12 @@ jobs:
strategy:
matrix:
- target: [powerpc-unknown-linux-gnu, x86_64-unknown-linux-musl, wasm32-unknown-unknown]
+ target:
+ [
+ powerpc-unknown-linux-gnu,
+ x86_64-unknown-linux-musl,
+ wasm32-unknown-unknown,
+ ]
include:
# The rust-analyzer binary is not expected to compile on WASM, but the IDE
# crate should
@@ -330,7 +335,18 @@ jobs:
run: typos
conclusion:
- needs: [rust, rust-cross, typescript, typo-check, proc-macro-srv, miri, rustfmt, clippy, analysis-stats]
+ needs:
+ [
+ rust,
+ rust-cross,
+ typescript,
+ typo-check,
+ proc-macro-srv,
+ miri,
+ rustfmt,
+ clippy,
+ analysis-stats,
+ ]
# We need to ensure this job does *not* get skipped if its dependencies fail,
# because a skipped job is considered a success by GitHub. So we have to
# overwrite `if:`. We use `!cancelled()` to ensure the job does still not get run
diff --git a/crates/hir-def/src/hir/generics.rs b/crates/hir-def/src/hir/generics.rs
index 482cf36f95..022f8adfdb 100644
--- a/crates/hir-def/src/hir/generics.rs
+++ b/crates/hir-def/src/hir/generics.rs
@@ -184,7 +184,7 @@ static EMPTY: LazyLock<Arc<GenericParams>> = LazyLock::new(|| {
impl GenericParams {
/// The index of the self param in the generic of the non-parent definition.
- pub(crate) const SELF_PARAM_ID_IN_SELF: la_arena::Idx<TypeOrConstParamData> =
+ pub const SELF_PARAM_ID_IN_SELF: la_arena::Idx<TypeOrConstParamData> =
LocalTypeOrConstParamId::from_raw(RawIdx::from_u32(0));
pub fn new(db: &dyn DefDatabase, def: GenericDefId) -> Arc<GenericParams> {
diff --git a/crates/hir-def/src/lib.rs b/crates/hir-def/src/lib.rs
index 8d6c418d75..de674be05f 100644
--- a/crates/hir-def/src/lib.rs
+++ b/crates/hir-def/src/lib.rs
@@ -86,7 +86,7 @@ use crate::{
builtin_type::BuiltinType,
db::DefDatabase,
expr_store::ExpressionStoreSourceMap,
- hir::generics::{LocalLifetimeParamId, LocalTypeOrConstParamId},
+ hir::generics::{GenericParams, LocalLifetimeParamId, LocalTypeOrConstParamId},
nameres::{
LocalDefMap,
assoc::{ImplItems, TraitItems},
@@ -553,15 +553,25 @@ pub struct TypeOrConstParamId {
pub struct TypeParamId(TypeOrConstParamId);
impl TypeParamId {
+ #[inline]
pub fn parent(&self) -> GenericDefId {
self.0.parent
}
+
+ #[inline]
pub fn local_id(&self) -> LocalTypeOrConstParamId {
self.0.local_id
}
-}
-impl TypeParamId {
+ #[inline]
+ pub fn trait_self(trait_: TraitId) -> TypeParamId {
+ TypeParamId::from_unchecked(TypeOrConstParamId {
+ parent: trait_.into(),
+ local_id: GenericParams::SELF_PARAM_ID_IN_SELF,
+ })
+ }
+
+ #[inline]
/// Caller should check if this toc id really belongs to a type
pub fn from_unchecked(it: TypeOrConstParamId) -> Self {
Self(it)
diff --git a/crates/hir-ty/src/builtin_derive.rs b/crates/hir-ty/src/builtin_derive.rs
index 0c3c513668..5a93c2b536 100644
--- a/crates/hir-ty/src/builtin_derive.rs
+++ b/crates/hir-ty/src/builtin_derive.rs
@@ -35,6 +35,24 @@ fn coerce_pointee_new_type_param(trait_id: TraitId) -> TypeParamId {
})
}
+fn trait_args(trait_: BuiltinDeriveImplTrait, self_ty: Ty<'_>) -> GenericArgs<'_> {
+ match trait_ {
+ BuiltinDeriveImplTrait::Copy
+ | BuiltinDeriveImplTrait::Clone
+ | BuiltinDeriveImplTrait::Default
+ | BuiltinDeriveImplTrait::Debug
+ | BuiltinDeriveImplTrait::Hash
+ | BuiltinDeriveImplTrait::Eq
+ | BuiltinDeriveImplTrait::Ord => GenericArgs::new_from_slice(&[self_ty.into()]),
+ BuiltinDeriveImplTrait::PartialOrd | BuiltinDeriveImplTrait::PartialEq => {
+ GenericArgs::new_from_slice(&[self_ty.into(), self_ty.into()])
+ }
+ BuiltinDeriveImplTrait::CoerceUnsized | BuiltinDeriveImplTrait::DispatchFromDyn => {
+ panic!("`CoerceUnsized` and `DispatchFromDyn` have special generics")
+ }
+ }
+}
+
pub(crate) fn generics_of<'db>(interner: DbInterner<'db>, id: BuiltinDeriveImplId) -> Generics {
let db = interner.db;
let loc = id.loc(db);
@@ -95,21 +113,19 @@ pub fn impl_trait<'db>(
| BuiltinDeriveImplTrait::Debug
| BuiltinDeriveImplTrait::Hash
| BuiltinDeriveImplTrait::Ord
- | BuiltinDeriveImplTrait::Eq => {
+ | BuiltinDeriveImplTrait::Eq
+ | BuiltinDeriveImplTrait::PartialOrd
+ | BuiltinDeriveImplTrait::PartialEq => {
let self_ty = Ty::new_adt(
interner,
loc.adt,
GenericArgs::identity_for_item(interner, loc.adt.into()),
);
- EarlyBinder::bind(TraitRef::new(interner, trait_id.into(), [self_ty]))
- }
- BuiltinDeriveImplTrait::PartialOrd | BuiltinDeriveImplTrait::PartialEq => {
- let self_ty = Ty::new_adt(
+ EarlyBinder::bind(TraitRef::new_from_args(
interner,
- loc.adt,
- GenericArgs::identity_for_item(interner, loc.adt.into()),
- );
- EarlyBinder::bind(TraitRef::new(interner, trait_id.into(), [self_ty, self_ty]))
+ trait_id.into(),
+ trait_args(loc.trait_, self_ty),
+ ))
}
BuiltinDeriveImplTrait::CoerceUnsized | BuiltinDeriveImplTrait::DispatchFromDyn => {
let generic_params = GenericParams::new(db, loc.adt.into());
@@ -260,7 +276,8 @@ fn simple_trait_predicates<'db>(
let param_idx =
param_idx.into_raw().into_u32() + (generic_params.len_lifetimes() as u32);
let param_ty = Ty::new_param(interner, param_id, param_idx);
- let trait_ref = TraitRef::new(interner, trait_id.into(), [param_ty]);
+ let trait_args = trait_args(loc.trait_, param_ty);
+ let trait_ref = TraitRef::new_from_args(interner, trait_id.into(), trait_args);
trait_ref.upcast(interner)
});
let mut assoc_type_bounds = Vec::new();
@@ -270,12 +287,14 @@ fn simple_trait_predicates<'db>(
&mut assoc_type_bounds,
interner.db.field_types(id.into()),
trait_id,
+ loc.trait_,
),
AdtId::UnionId(id) => extend_assoc_type_bounds(
interner,
&mut assoc_type_bounds,
interner.db.field_types(id.into()),
trait_id,
+ loc.trait_,
),
AdtId::EnumId(id) => {
for &(variant_id, _, _) in &id.enum_variants(interner.db).variants {
@@ -284,6 +303,7 @@ fn simple_trait_predicates<'db>(
&mut assoc_type_bounds,
interner.db.field_types(variant_id.into()),
trait_id,
+ loc.trait_,
)
}
}
@@ -305,12 +325,14 @@ fn extend_assoc_type_bounds<'db>(
interner: DbInterner<'db>,
assoc_type_bounds: &mut Vec<Clause<'db>>,
fields: &ArenaMap<LocalFieldId, StoredEarlyBinder<StoredTy>>,
- trait_: TraitId,
+ trait_id: TraitId,
+ trait_: BuiltinDeriveImplTrait,
) {
struct ProjectionFinder<'a, 'db> {
interner: DbInterner<'db>,
assoc_type_bounds: &'a mut Vec<Clause<'db>>,
- trait_: TraitId,
+ trait_id: TraitId,
+ trait_: BuiltinDeriveImplTrait,
}
impl<'db> TypeVisitor<DbInterner<'db>> for ProjectionFinder<'_, 'db> {
@@ -319,7 +341,12 @@ fn extend_assoc_type_bounds<'db>(
fn visit_ty(&mut self, t: Ty<'db>) -> Self::Result {
if let TyKind::Alias(AliasTyKind::Projection, _) = t.kind() {
self.assoc_type_bounds.push(
- TraitRef::new(self.interner, self.trait_.into(), [t]).upcast(self.interner),
+ TraitRef::new_from_args(
+ self.interner,
+ self.trait_id.into(),
+ trait_args(self.trait_, t),
+ )
+ .upcast(self.interner),
);
}
@@ -327,7 +354,7 @@ fn extend_assoc_type_bounds<'db>(
}
}
- let mut visitor = ProjectionFinder { interner, assoc_type_bounds, trait_ };
+ let mut visitor = ProjectionFinder { interner, assoc_type_bounds, trait_id, trait_ };
for (_, field) in fields.iter() {
field.get().instantiate_identity().visit_with(&mut visitor);
}
@@ -488,10 +515,12 @@ struct MultiGenericParams<'a, T, #[pointee] U: ?Sized, const N: usize>(*const U)
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
struct Simple;
-trait Trait {}
+trait Trait {
+ type Assoc;
+}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
-struct WithGenerics<'a, T: Trait, const N: usize>(&'a [T; N]);
+struct WithGenerics<'a, T: Trait, const N: usize>(&'a [T; N], T::Assoc);
"#,
expect![[r#"
@@ -514,41 +543,49 @@ struct WithGenerics<'a, T: Trait, const N: usize>(&'a [T; N]);
Clause(Binder { value: ConstArgHasType(#2, usize), bound_vars: [] })
Clause(Binder { value: TraitPredicate(#1: Sized, polarity:Positive), bound_vars: [] })
Clause(Binder { value: TraitPredicate(#1: Debug, polarity:Positive), bound_vars: [] })
+ Clause(Binder { value: TraitPredicate(Alias(Projection, AliasTy { args: [#1], def_id: TypeAliasId("Assoc"), .. }): Debug, polarity:Positive), bound_vars: [] })
Clause(Binder { value: TraitPredicate(#1: Trait, polarity:Positive), bound_vars: [] })
Clause(Binder { value: ConstArgHasType(#2, usize), bound_vars: [] })
Clause(Binder { value: TraitPredicate(#1: Sized, polarity:Positive), bound_vars: [] })
Clause(Binder { value: TraitPredicate(#1: Clone, polarity:Positive), bound_vars: [] })
+ Clause(Binder { value: TraitPredicate(Alias(Projection, AliasTy { args: [#1], def_id: TypeAliasId("Assoc"), .. }): Clone, polarity:Positive), bound_vars: [] })
Clause(Binder { value: TraitPredicate(#1: Trait, polarity:Positive), bound_vars: [] })
Clause(Binder { value: ConstArgHasType(#2, usize), bound_vars: [] })
Clause(Binder { value: TraitPredicate(#1: Sized, polarity:Positive), bound_vars: [] })
Clause(Binder { value: TraitPredicate(#1: Copy, polarity:Positive), bound_vars: [] })
+ Clause(Binder { value: TraitPredicate(Alias(Projection, AliasTy { args: [#1], def_id: TypeAliasId("Assoc"), .. }): Copy, polarity:Positive), bound_vars: [] })
Clause(Binder { value: TraitPredicate(#1: Trait, polarity:Positive), bound_vars: [] })
Clause(Binder { value: ConstArgHasType(#2, usize), bound_vars: [] })
Clause(Binder { value: TraitPredicate(#1: Sized, polarity:Positive), bound_vars: [] })
- Clause(Binder { value: TraitPredicate(#1: PartialEq, polarity:Positive), bound_vars: [] })
+ Clause(Binder { value: TraitPredicate(#1: PartialEq<[#1]>, polarity:Positive), bound_vars: [] })
+ Clause(Binder { value: TraitPredicate(Alias(Projection, AliasTy { args: [#1], def_id: TypeAliasId("Assoc"), .. }): PartialEq<[Alias(Projection, AliasTy { args: [#1], def_id: TypeAliasId("Assoc"), .. })]>, polarity:Positive), bound_vars: [] })
Clause(Binder { value: TraitPredicate(#1: Trait, polarity:Positive), bound_vars: [] })
Clause(Binder { value: ConstArgHasType(#2, usize), bound_vars: [] })
Clause(Binder { value: TraitPredicate(#1: Sized, polarity:Positive), bound_vars: [] })
Clause(Binder { value: TraitPredicate(#1: Eq, polarity:Positive), bound_vars: [] })
+ Clause(Binder { value: TraitPredicate(Alias(Projection, AliasTy { args: [#1], def_id: TypeAliasId("Assoc"), .. }): Eq, polarity:Positive), bound_vars: [] })
Clause(Binder { value: TraitPredicate(#1: Trait, polarity:Positive), bound_vars: [] })
Clause(Binder { value: ConstArgHasType(#2, usize), bound_vars: [] })
Clause(Binder { value: TraitPredicate(#1: Sized, polarity:Positive), bound_vars: [] })
- Clause(Binder { value: TraitPredicate(#1: PartialOrd, polarity:Positive), bound_vars: [] })
+ Clause(Binder { value: TraitPredicate(#1: PartialOrd<[#1]>, polarity:Positive), bound_vars: [] })
+ Clause(Binder { value: TraitPredicate(Alias(Projection, AliasTy { args: [#1], def_id: TypeAliasId("Assoc"), .. }): PartialOrd<[Alias(Projection, AliasTy { args: [#1], def_id: TypeAliasId("Assoc"), .. })]>, polarity:Positive), bound_vars: [] })
Clause(Binder { value: TraitPredicate(#1: Trait, polarity:Positive), bound_vars: [] })
Clause(Binder { value: ConstArgHasType(#2, usize), bound_vars: [] })
Clause(Binder { value: TraitPredicate(#1: Sized, polarity:Positive), bound_vars: [] })
Clause(Binder { value: TraitPredicate(#1: Ord, polarity:Positive), bound_vars: [] })
+ Clause(Binder { value: TraitPredicate(Alias(Projection, AliasTy { args: [#1], def_id: TypeAliasId("Assoc"), .. }): Ord, polarity:Positive), bound_vars: [] })
Clause(Binder { value: TraitPredicate(#1: Trait, polarity:Positive), bound_vars: [] })
Clause(Binder { value: ConstArgHasType(#2, usize), bound_vars: [] })
Clause(Binder { value: TraitPredicate(#1: Sized, polarity:Positive), bound_vars: [] })
Clause(Binder { value: TraitPredicate(#1: Hash, polarity:Positive), bound_vars: [] })
+ Clause(Binder { value: TraitPredicate(Alias(Projection, AliasTy { args: [#1], def_id: TypeAliasId("Assoc"), .. }): Hash, polarity:Positive), bound_vars: [] })
"#]],
);
diff --git a/crates/hir-ty/src/generics.rs b/crates/hir-ty/src/generics.rs
index 5f0261437b..b1500bcdb7 100644
--- a/crates/hir-ty/src/generics.rs
+++ b/crates/hir-ty/src/generics.rs
@@ -224,22 +224,6 @@ impl Generics {
}
}
-pub(crate) fn trait_self_param_idx(db: &dyn DefDatabase, def: GenericDefId) -> Option<usize> {
- match def {
- GenericDefId::TraitId(_) => {
- let params = db.generic_params(def);
- params.trait_self_param().map(|idx| idx.into_raw().into_u32() as usize)
- }
- GenericDefId::ImplId(_) => None,
- _ => {
- let parent_def = parent_generic_def(db, def)?;
- let parent_params = db.generic_params(parent_def);
- let parent_self_idx = parent_params.trait_self_param()?.into_raw().into_u32() as usize;
- Some(parent_self_idx)
- }
- }
-}
-
pub(crate) fn parent_generic_def(db: &dyn DefDatabase, def: GenericDefId) -> Option<GenericDefId> {
let container = match def {
GenericDefId::FunctionId(it) => it.lookup(db).container,
diff --git a/crates/hir-ty/src/lib.rs b/crates/hir-ty/src/lib.rs
index f8920904f0..8d87276a0b 100644
--- a/crates/hir-ty/src/lib.rs
+++ b/crates/hir-ty/src/lib.rs
@@ -31,6 +31,7 @@ mod inhabitedness;
mod lower;
pub mod next_solver;
mod opaques;
+mod representability;
mod specialization;
mod target_feature;
mod utils;
@@ -57,9 +58,12 @@ mod test_db;
#[cfg(test)]
mod tests;
-use std::hash::Hash;
+use std::{hash::Hash, ops::ControlFlow};
-use hir_def::{CallableDefId, TypeOrConstParamId, type_ref::Rawness};
+use hir_def::{
+ CallableDefId, GenericDefId, TypeAliasId, TypeOrConstParamId, TypeParamId,
+ hir::generics::GenericParams, resolver::TypeNs, type_ref::Rawness,
+};
use hir_expand::name::Name;
use indexmap::{IndexMap, map::Entry};
use intern::{Symbol, sym};
@@ -77,10 +81,11 @@ use crate::{
db::HirDatabase,
display::{DisplayTarget, HirDisplay},
infer::unify::InferenceTable,
+ lower::SupertraitsInfo,
next_solver::{
AliasTy, Binder, BoundConst, BoundRegion, BoundRegionKind, BoundTy, BoundTyKind, Canonical,
- CanonicalVarKind, CanonicalVars, Const, ConstKind, DbInterner, FnSig, GenericArgs,
- PolyFnSig, Predicate, Region, RegionKind, TraitRef, Ty, TyKind, Tys, abi,
+ CanonicalVarKind, CanonicalVars, ClauseKind, Const, ConstKind, DbInterner, FnSig,
+ GenericArgs, PolyFnSig, Predicate, Region, RegionKind, TraitRef, Ty, TyKind, Tys, abi,
},
};
@@ -94,7 +99,7 @@ pub use infer::{
};
pub use lower::{
GenericPredicates, ImplTraits, LifetimeElisionKind, TyDefId, TyLoweringContext, ValueTyDefId,
- associated_type_shorthand_candidates, diagnostics::*,
+ diagnostics::*,
};
pub use next_solver::interner::{attach_db, attach_db_allow_change, with_attached_db};
pub use target_feature::TargetFeatures;
@@ -479,6 +484,55 @@ where
}
/// To be used from `hir` only.
+pub fn associated_type_shorthand_candidates(
+ db: &dyn HirDatabase,
+ def: GenericDefId,
+ res: TypeNs,
+ mut cb: impl FnMut(&Name, TypeAliasId) -> bool,
+) -> Option<TypeAliasId> {
+ let interner = DbInterner::new_no_crate(db);
+ let (def, param) = match res {
+ TypeNs::GenericParam(param) => (def, param),
+ TypeNs::SelfType(impl_) => {
+ let impl_trait = db.impl_trait(impl_)?.skip_binder().def_id.0;
+ let param = TypeParamId::from_unchecked(TypeOrConstParamId {
+ parent: impl_trait.into(),
+ local_id: GenericParams::SELF_PARAM_ID_IN_SELF,
+ });
+ (impl_trait.into(), param)
+ }
+ _ => return None,
+ };
+
+ let mut dedup_map = FxHashSet::default();
+ let param_ty = Ty::new_param(interner, param, param_idx(db, param.into()).unwrap() as u32);
+ // We use the ParamEnv and not the predicates because the ParamEnv elaborates bounds.
+ let param_env = db.trait_environment(def);
+ for clause in param_env.clauses {
+ let ClauseKind::Trait(trait_clause) = clause.kind().skip_binder() else { continue };
+ if trait_clause.self_ty() != param_ty {
+ continue;
+ }
+ let trait_id = trait_clause.def_id().0;
+ dedup_map.extend(
+ SupertraitsInfo::query(db, trait_id)
+ .defined_assoc_types
+ .iter()
+ .map(|(name, id)| (name, *id)),
+ );
+ }
+
+ dedup_map
+ .into_iter()
+ .try_for_each(
+ |(name, id)| {
+ if cb(name, id) { ControlFlow::Break(id) } else { ControlFlow::Continue(()) }
+ },
+ )
+ .break_value()
+}
+
+/// To be used from `hir` only.
pub fn callable_sig_from_fn_trait<'db>(
self_ty: Ty<'db>,
trait_env: ParamEnvAndCrate<'db>,
diff --git a/crates/hir-ty/src/lower.rs b/crates/hir-ty/src/lower.rs
index 386556b156..1290874177 100644
--- a/crates/hir-ty/src/lower.rs
+++ b/crates/hir-ty/src/lower.rs
@@ -20,7 +20,8 @@ use hir_def::{
builtin_type::BuiltinType,
expr_store::{ExpressionStore, HygieneId, path::Path},
hir::generics::{
- GenericParamDataRef, TypeOrConstParamData, TypeParamProvenance, WherePredicate,
+ GenericParamDataRef, GenericParams, TypeOrConstParamData, TypeParamProvenance,
+ WherePredicate,
},
item_tree::FieldsShape,
lang_item::LangItems,
@@ -36,27 +37,23 @@ use la_arena::{Arena, ArenaMap, Idx};
use path::{PathDiagnosticCallback, PathLoweringContext};
use rustc_ast_ir::Mutability;
use rustc_hash::FxHashSet;
-use rustc_pattern_analysis::Captures;
use rustc_type_ir::{
AliasTyKind, BoundVarIndexKind, ConstKind, DebruijnIndex, ExistentialPredicate,
ExistentialProjection, ExistentialTraitRef, FnSig, Interner, OutlivesPredicate, TermKind,
TyKind::{self},
TypeFoldable, TypeVisitableExt, Upcast, UpcastFrom, elaborate,
- inherent::{
- Clause as _, GenericArg as _, GenericArgs as _, IntoKind as _, Region as _, SliceLike,
- Ty as _,
- },
+ inherent::{Clause as _, GenericArgs as _, IntoKind as _, Region as _, Ty as _},
};
-use smallvec::{SmallVec, smallvec};
+use smallvec::SmallVec;
use stdx::{impl_from, never};
use tracing::debug;
use triomphe::{Arc, ThinArc};
use crate::{
- FnAbi, ImplTraitId, TyLoweringDiagnostic, TyLoweringDiagnosticKind, all_super_traits,
+ FnAbi, ImplTraitId, TyLoweringDiagnostic, TyLoweringDiagnosticKind,
consteval::intern_const_ref,
db::{HirDatabase, InternedOpaqueTyId},
- generics::{Generics, generics, trait_self_param_idx},
+ generics::{Generics, generics},
next_solver::{
AliasTy, Binder, BoundExistentialPredicates, Clause, ClauseKind, Clauses, Const,
DbInterner, EarlyBinder, EarlyParamRegion, ErrorGuaranteed, FxIndexMap, GenericArg,
@@ -618,33 +615,12 @@ impl<'db, 'a> TyLoweringContext<'db, 'a> {
&'b mut self,
where_predicate: &'b WherePredicate,
ignore_bindings: bool,
- generics: &Generics,
- predicate_filter: PredicateFilter,
) -> impl Iterator<Item = (Clause<'db>, GenericPredicateSource)> + use<'a, 'b, 'db> {
match where_predicate {
WherePredicate::ForLifetime { target, bound, .. }
| WherePredicate::TypeBound { target, bound } => {
- if let PredicateFilter::SelfTrait = predicate_filter {
- let target_type = &self.store[*target];
- let self_type = 'is_self: {
- if let TypeRef::Path(path) = target_type
- && path.is_self_type()
- {
- break 'is_self true;
- }
- if let TypeRef::TypeParam(param) = target_type
- && generics[param.local_id()].is_trait_self()
- {
- break 'is_self true;
- }
- false
- };
- if !self_type {
- return Either::Left(Either::Left(iter::empty()));
- }
- }
let self_ty = self.lower_ty(*target);
- Either::Left(Either::Right(self.lower_type_bound(bound, self_ty, ignore_bindings)))
+ Either::Left(self.lower_type_bound(bound, self_ty, ignore_bindings))
}
&WherePredicate::Lifetime { bound, target } => Either::Right(iter::once((
Clause(Predicate::new(
@@ -1626,6 +1602,92 @@ pub(crate) fn field_types_with_diagnostics_query<'db>(
(res, create_diagnostics(ctx.diagnostics))
}
+#[derive(Debug, PartialEq, Eq, Default)]
+pub(crate) struct SupertraitsInfo {
+ /// This includes the trait itself.
+ pub(crate) all_supertraits: Box<[TraitId]>,
+ pub(crate) direct_supertraits: Box<[TraitId]>,
+ pub(crate) defined_assoc_types: Box<[(Name, TypeAliasId)]>,
+}
+
+impl SupertraitsInfo {
+ #[inline]
+ pub(crate) fn query(db: &dyn HirDatabase, trait_: TraitId) -> &Self {
+ return supertraits_info(db, trait_);
+
+ #[salsa::tracked(returns(ref), cycle_result = supertraits_info_cycle)]
+ fn supertraits_info(db: &dyn HirDatabase, trait_: TraitId) -> SupertraitsInfo {
+ let mut all_supertraits = FxHashSet::default();
+ let mut direct_supertraits = FxHashSet::default();
+ let mut defined_assoc_types = FxHashSet::default();
+
+ all_supertraits.insert(trait_);
+ defined_assoc_types.extend(trait_.trait_items(db).items.iter().filter_map(
+ |(name, id)| match *id {
+ AssocItemId::TypeAliasId(id) => Some((name.clone(), id)),
+ _ => None,
+ },
+ ));
+
+ let resolver = trait_.resolver(db);
+ let signature = db.trait_signature(trait_);
+ for pred in signature.generic_params.where_predicates() {
+ let (WherePredicate::TypeBound { target, bound }
+ | WherePredicate::ForLifetime { lifetimes: _, target, bound }) = pred
+ else {
+ continue;
+ };
+ let (TypeBound::Path(bounded_trait, TraitBoundModifier::None)
+ | TypeBound::ForLifetime(_, bounded_trait)) = *bound
+ else {
+ continue;
+ };
+ let target = &signature.store[*target];
+ match target {
+ TypeRef::TypeParam(param)
+ if param.local_id() == GenericParams::SELF_PARAM_ID_IN_SELF => {}
+ TypeRef::Path(path) if path.is_self_type() => {}
+ _ => continue,
+ }
+ let Some(TypeNs::TraitId(bounded_trait)) =
+ resolver.resolve_path_in_type_ns_fully(db, &signature.store[bounded_trait])
+ else {
+ continue;
+ };
+ let SupertraitsInfo {
+ all_supertraits: bounded_trait_all_supertraits,
+ direct_supertraits: _,
+ defined_assoc_types: bounded_traits_defined_assoc_types,
+ } = SupertraitsInfo::query(db, bounded_trait);
+ all_supertraits.extend(bounded_trait_all_supertraits);
+ direct_supertraits.insert(bounded_trait);
+ defined_assoc_types.extend(bounded_traits_defined_assoc_types.iter().cloned());
+ }
+
+ SupertraitsInfo {
+ all_supertraits: Box::from_iter(all_supertraits),
+ direct_supertraits: Box::from_iter(direct_supertraits),
+ defined_assoc_types: Box::from_iter(defined_assoc_types),
+ }
+ }
+
+ fn supertraits_info_cycle(
+ _db: &dyn HirDatabase,
+ _: salsa::Id,
+ _trait_: TraitId,
+ ) -> SupertraitsInfo {
+ SupertraitsInfo::default()
+ }
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+enum TypeParamAssocTypeShorthandError {
+ AssocTypeNotFound,
+ AmbiguousAssocType,
+ Cycle,
+}
+
/// Predicates for `param_id` of the form `P: SomeTrait`. If
/// `assoc_name` is provided, only return predicates referencing traits
/// that have an associated type of that name.
@@ -1640,15 +1702,14 @@ pub(crate) fn field_types_with_diagnostics_query<'db>(
/// following bounds are disallowed: `T: Foo<U::Item>, U: Foo<T::Item>`, but
/// these are fine: `T: Foo<U::Item>, U: Foo<()>`.
#[tracing::instrument(skip(db), ret)]
-#[salsa::tracked(returns(ref), cycle_result = generic_predicates_for_param_cycle_result)]
-pub(crate) fn generic_predicates_for_param<'db>(
- db: &'db dyn HirDatabase,
+#[salsa::tracked(returns(ref), cycle_result = resolve_type_param_assoc_type_shorthand_cycle_result)]
+fn resolve_type_param_assoc_type_shorthand(
+ db: &dyn HirDatabase,
def: GenericDefId,
- param_id: TypeOrConstParamId,
- assoc_name: Option<Name>,
-) -> StoredEarlyBinder<StoredClauses> {
+ param: TypeParamId,
+ assoc_name: Name,
+) -> Result<StoredEarlyBinder<(TypeAliasId, StoredGenericArgs)>, TypeParamAssocTypeShorthandError> {
let generics = generics(db, def);
- let interner = DbInterner::new_no_crate(db);
let resolver = def.resolver(db);
let mut ctx = TyLoweringContext::new(
db,
@@ -1657,128 +1718,109 @@ pub(crate) fn generic_predicates_for_param<'db>(
def,
LifetimeElisionKind::AnonymousReportError,
);
+ let interner = ctx.interner;
+ let mut result = None;
+ let param_ty = Ty::new_param(
+ interner,
+ param,
+ generics.type_or_const_param_idx(param.into()).unwrap() as u32,
+ );
- // we have to filter out all other predicates *first*, before attempting to lower them
- let has_relevant_bound = |pred: &_, ctx: &mut TyLoweringContext<'_, '_>| match pred {
- WherePredicate::ForLifetime { target, bound, .. }
- | WherePredicate::TypeBound { target, bound, .. } => {
- let invalid_target = { ctx.lower_ty_only_param(*target) != Some(param_id) };
- if invalid_target {
- // FIXME(sized-hierarchy): Revisit and adjust this properly once we have implemented
- // sized-hierarchy correctly.
- // If this is filtered out without lowering, `?Sized` or `PointeeSized` is not gathered into
- // `ctx.unsized_types`
- let lower = || -> bool {
- match bound {
- TypeBound::Path(_, TraitBoundModifier::Maybe) => true,
- TypeBound::Path(path, _) | TypeBound::ForLifetime(_, path) => {
- let TypeRef::Path(path) = &ctx.store[path.type_ref()] else {
- return false;
- };
- let Some(pointee_sized) = ctx.lang_items.PointeeSized else {
- return false;
- };
- // Lower the path directly with `Resolver` instead of PathLoweringContext`
- // to prevent diagnostics duplications.
- ctx.resolver.resolve_path_in_type_ns_fully(ctx.db, path).is_some_and(
- |it| matches!(it, TypeNs::TraitId(tr) if tr == pointee_sized),
- )
- }
- _ => false,
- }
- }();
- if lower {
- ctx.lower_where_predicate(pred, true, &generics, PredicateFilter::All)
- .for_each(drop);
- }
- return false;
- }
-
- match bound {
- &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.store[path];
-
- let Some(assoc_name) = &assoc_name else { return true };
- let Some(TypeNs::TraitId(tr)) =
- resolver.resolve_path_in_type_ns_fully(db, path)
- else {
- return false;
- };
-
- trait_or_supertrait_has_assoc_type(db, tr, assoc_name)
- }
- TypeBound::Use(_) | TypeBound::Lifetime(_) | TypeBound::Error => false,
- }
+ if let GenericDefId::TraitId(containing_trait) = param.parent()
+ && param.local_id() == GenericParams::SELF_PARAM_ID_IN_SELF
+ {
+ // Add the trait's own associated types.
+ if let Some(assoc_type) =
+ containing_trait.trait_items(db).associated_type_by_name(&assoc_name)
+ {
+ let args = GenericArgs::identity_for_item(interner, containing_trait.into());
+ result = Some(StoredEarlyBinder::bind((assoc_type, args.store())));
}
- WherePredicate::Lifetime { .. } => false,
- };
- let mut predicates = Vec::new();
+ }
+
for maybe_parent_generics in
std::iter::successors(Some(&generics), |generics| generics.parent_generics())
{
ctx.store = maybe_parent_generics.store();
for pred in maybe_parent_generics.where_predicates() {
- if has_relevant_bound(pred, &mut ctx) {
- predicates.extend(
- ctx.lower_where_predicate(
- pred,
- true,
- maybe_parent_generics,
- PredicateFilter::All,
- )
- .map(|(pred, _)| pred),
- );
+ let (WherePredicate::TypeBound { target, bound }
+ | WherePredicate::ForLifetime { lifetimes: _, target, bound }) = pred
+ else {
+ continue;
+ };
+ let (TypeBound::Path(bounded_trait_path, TraitBoundModifier::None)
+ | TypeBound::ForLifetime(_, bounded_trait_path)) = *bound
+ else {
+ continue;
+ };
+ let Some(target) = ctx.lower_ty_only_param(*target) else { continue };
+ if target != param.into() {
+ continue;
+ }
+ let Some(TypeNs::TraitId(bounded_trait)) =
+ resolver.resolve_path_in_type_ns_fully(db, &ctx.store[bounded_trait_path])
+ else {
+ continue;
+ };
+ if !SupertraitsInfo::query(db, bounded_trait)
+ .defined_assoc_types
+ .iter()
+ .any(|(name, _)| *name == assoc_name)
+ {
+ continue;
}
+
+ let Some((bounded_trait_ref, _)) =
+ ctx.lower_trait_ref_from_path(bounded_trait_path, param_ty)
+ else {
+ continue;
+ };
+ // Now, search from the start on the *bounded* trait like if we wrote `Self::Assoc`. Eventually, we'll get
+ // the correct trait ref (or a cycle).
+ let lookup_on_bounded_trait = resolve_type_param_assoc_type_shorthand(
+ db,
+ bounded_trait.into(),
+ TypeParamId::trait_self(bounded_trait),
+ assoc_name.clone(),
+ );
+ let lookup_on_bounded_trait = match lookup_on_bounded_trait {
+ Ok(it) => it,
+ Err(
+ err @ (TypeParamAssocTypeShorthandError::AmbiguousAssocType
+ | TypeParamAssocTypeShorthandError::Cycle),
+ ) => return Err(*err),
+ Err(TypeParamAssocTypeShorthandError::AssocTypeNotFound) => {
+ never!("we checked that the trait defines this assoc type");
+ continue;
+ }
+ };
+ let (assoc_type, args) = lookup_on_bounded_trait
+ .get_with(|(assoc_type, args)| (*assoc_type, args.as_ref()))
+ .skip_binder();
+ let args = EarlyBinder::bind(args).instantiate(interner, bounded_trait_ref.args);
+ let current_result = StoredEarlyBinder::bind((assoc_type, args.store()));
+ // If we already have a result, this is an ambiguity - unless this is the same result, then we are fine
+ // (e.g. rustc allows to write the same bound twice without ambiguity).
+ if let Some(existing_result) = result
+ && existing_result != current_result
+ {
+ return Err(TypeParamAssocTypeShorthandError::AmbiguousAssocType);
+ }
+ result = Some(current_result);
}
}
- let args = GenericArgs::identity_for_item(interner, def.into());
- if !args.is_empty() {
- let explicitly_unsized_tys = ctx.unsized_types;
- if let Some(implicitly_sized_predicates) = implicitly_sized_clauses(
- db,
- ctx.lang_items,
- param_id.parent,
- &explicitly_unsized_tys,
- &args,
- ) {
- predicates.extend(implicitly_sized_predicates);
- };
- }
- StoredEarlyBinder::bind(Clauses::new_from_slice(&predicates).store())
+ result.ok_or(TypeParamAssocTypeShorthandError::AssocTypeNotFound)
}
-pub(crate) fn generic_predicates_for_param_cycle_result(
- db: &dyn HirDatabase,
+fn resolve_type_param_assoc_type_shorthand_cycle_result(
+ _db: &dyn HirDatabase,
_: salsa::Id,
_def: GenericDefId,
- _param_id: TypeOrConstParamId,
- _assoc_name: Option<Name>,
-) -> StoredEarlyBinder<StoredClauses> {
- StoredEarlyBinder::bind(Clauses::empty(DbInterner::new_no_crate(db)).store())
-}
-
-/// Check if this trait or any of its supertraits define an associated
-/// type with the given name.
-fn trait_or_supertrait_has_assoc_type(
- db: &dyn HirDatabase,
- tr: TraitId,
- assoc_name: &Name,
-) -> bool {
- for trait_id in all_super_traits(db, tr) {
- if trait_id
- .trait_items(db)
- .items
- .iter()
- .any(|(name, item)| matches!(item, AssocItemId::TypeAliasId(_)) && name == assoc_name)
- {
- return true;
- }
- }
-
- false
+ _param: TypeParamId,
+ _assoc_name: Name,
+) -> Result<StoredEarlyBinder<(TypeAliasId, StoredGenericArgs)>, TypeParamAssocTypeShorthandError> {
+ Err(TypeParamAssocTypeShorthandError::Cycle)
}
#[inline]
@@ -1904,7 +1946,7 @@ impl<'db> GenericPredicates {
db: &'db dyn HirDatabase,
def: GenericDefId,
) -> (GenericPredicates, Diagnostics) {
- generic_predicates_filtered_by(db, def, PredicateFilter::All, |_| true)
+ generic_predicates(db, def)
}
}
@@ -2042,24 +2084,10 @@ pub(crate) fn trait_environment<'db>(db: &'db dyn HirDatabase, def: GenericDefId
}
}
-#[derive(Copy, Clone, Debug, PartialEq, Eq)]
-pub(crate) enum PredicateFilter {
- SelfTrait,
- All,
-}
-
/// Resolve the where clause(s) of an item with generics,
/// with a given filter
-#[tracing::instrument(skip(db, filter), ret)]
-pub(crate) fn generic_predicates_filtered_by<F>(
- db: &dyn HirDatabase,
- def: GenericDefId,
- predicate_filter: PredicateFilter,
- filter: F,
-) -> (GenericPredicates, Diagnostics)
-where
- F: Fn(GenericDefId) -> bool,
-{
+#[tracing::instrument(skip(db), ret)]
+fn generic_predicates(db: &dyn HirDatabase, def: GenericDefId) -> (GenericPredicates, Diagnostics) {
let generics = generics(db, def);
let resolver = def.resolver(db);
let interner = DbInterner::new_no_crate(db);
@@ -2081,9 +2109,9 @@ where
let all_generics =
std::iter::successors(Some(&generics), |generics| generics.parent_generics())
.collect::<ArrayVec<_, 2>>();
- let own_implicit_trait_predicate = implicit_trait_predicate(interner, def, predicate_filter);
+ let own_implicit_trait_predicate = implicit_trait_predicate(interner, def);
let parent_implicit_trait_predicate = if all_generics.len() > 1 {
- implicit_trait_predicate(interner, all_generics.last().unwrap().def(), predicate_filter)
+ implicit_trait_predicate(interner, all_generics.last().unwrap().def())
} else {
None
};
@@ -2091,97 +2119,85 @@ where
// Collect only diagnostics from the child, not including parents.
ctx.diagnostics.clear();
- if filter(maybe_parent_generics.def()) {
- ctx.store = maybe_parent_generics.store();
- for pred in maybe_parent_generics.where_predicates() {
- tracing::debug!(?pred);
- for (pred, source) in
- ctx.lower_where_predicate(pred, false, maybe_parent_generics, predicate_filter)
- {
- match source {
- GenericPredicateSource::SelfOnly => {
- if maybe_parent_generics.def() == def {
- own_predicates.push(pred);
- } else {
- parent_predicates.push(pred);
- }
+ ctx.store = maybe_parent_generics.store();
+ for pred in maybe_parent_generics.where_predicates() {
+ tracing::debug!(?pred);
+ for (pred, source) in ctx.lower_where_predicate(pred, false) {
+ match source {
+ GenericPredicateSource::SelfOnly => {
+ if maybe_parent_generics.def() == def {
+ own_predicates.push(pred);
+ } else {
+ parent_predicates.push(pred);
}
- GenericPredicateSource::AssocTyBound => {
- if maybe_parent_generics.def() == def {
- own_assoc_ty_bounds.push(pred);
- } else {
- parent_assoc_ty_bounds.push(pred);
- }
+ }
+ GenericPredicateSource::AssocTyBound => {
+ if maybe_parent_generics.def() == def {
+ own_assoc_ty_bounds.push(pred);
+ } else {
+ parent_assoc_ty_bounds.push(pred);
}
}
}
}
+ }
- if maybe_parent_generics.def() == def {
- push_const_arg_has_type_predicates(db, &mut own_predicates, maybe_parent_generics);
- } else {
- push_const_arg_has_type_predicates(
- db,
- &mut parent_predicates,
- maybe_parent_generics,
- );
- }
+ if maybe_parent_generics.def() == def {
+ push_const_arg_has_type_predicates(db, &mut own_predicates, maybe_parent_generics);
+ } else {
+ push_const_arg_has_type_predicates(db, &mut parent_predicates, maybe_parent_generics);
+ }
- if let Some(sized_trait) = sized_trait {
- let mut add_sized_clause = |param_idx, param_id, param_data| {
- let (
- GenericParamId::TypeParamId(param_id),
- GenericParamDataRef::TypeParamData(param_data),
- ) = (param_id, param_data)
- else {
- return;
- };
+ if let Some(sized_trait) = sized_trait {
+ let mut add_sized_clause = |param_idx, param_id, param_data| {
+ let (
+ GenericParamId::TypeParamId(param_id),
+ GenericParamDataRef::TypeParamData(param_data),
+ ) = (param_id, param_data)
+ else {
+ return;
+ };
- if param_data.provenance == TypeParamProvenance::TraitSelf {
- return;
- }
+ if param_data.provenance == TypeParamProvenance::TraitSelf {
+ return;
+ }
- let param_ty = Ty::new_param(interner, param_id, param_idx);
- if ctx.unsized_types.contains(&param_ty) {
- return;
- }
- let trait_ref = TraitRef::new_from_args(
- interner,
- sized_trait.into(),
- GenericArgs::new_from_slice(&[param_ty.into()]),
- );
- let clause = Clause(Predicate::new(
- interner,
- Binder::dummy(rustc_type_ir::PredicateKind::Clause(
- rustc_type_ir::ClauseKind::Trait(TraitPredicate {
- trait_ref,
- polarity: rustc_type_ir::PredicatePolarity::Positive,
- }),
- )),
- ));
- if maybe_parent_generics.def() == def {
- own_predicates.push(clause);
- } else {
- parent_predicates.push(clause);
- }
- };
- let parent_params_len = maybe_parent_generics.len_parent();
- maybe_parent_generics.iter_self().enumerate().for_each(
- |(param_idx, (param_id, param_data))| {
- add_sized_clause(
- (param_idx + parent_params_len) as u32,
- param_id,
- param_data,
- );
- },
+ let param_ty = Ty::new_param(interner, param_id, param_idx);
+ if ctx.unsized_types.contains(&param_ty) {
+ return;
+ }
+ let trait_ref = TraitRef::new_from_args(
+ interner,
+ sized_trait.into(),
+ GenericArgs::new_from_slice(&[param_ty.into()]),
);
- }
-
- // We do not clear `ctx.unsized_types`, as the `?Sized` clause of a child (e.g. an associated type) can
- // be declared on the parent (e.g. the trait). It is nevertheless fine to register the implicit `Sized`
- // predicates before lowering the child, as a child cannot define a `?Sized` predicate for its parent.
- // But we do have to lower the parent first.
+ let clause = Clause(Predicate::new(
+ interner,
+ Binder::dummy(rustc_type_ir::PredicateKind::Clause(
+ rustc_type_ir::ClauseKind::Trait(TraitPredicate {
+ trait_ref,
+ polarity: rustc_type_ir::PredicatePolarity::Positive,
+ }),
+ )),
+ ));
+ if maybe_parent_generics.def() == def {
+ own_predicates.push(clause);
+ } else {
+ parent_predicates.push(clause);
+ }
+ };
+ let parent_params_len = maybe_parent_generics.len_parent();
+ maybe_parent_generics.iter_self().enumerate().for_each(
+ |(param_idx, (param_id, param_data))| {
+ add_sized_clause((param_idx + parent_params_len) as u32, param_id, param_data);
+ },
+ );
}
+
+ // We do not clear `ctx.unsized_types`, as the `?Sized` clause of a child (e.g. an associated type) can
+ // be declared on the parent (e.g. the trait). It is nevertheless fine to register the implicit `Sized`
+ // predicates before lowering the child, as a child cannot define a `?Sized` predicate for its parent.
+ // But we do have to lower the parent first.
}
let diagnostics = create_diagnostics(ctx.diagnostics);
@@ -2229,7 +2245,6 @@ where
fn implicit_trait_predicate<'db>(
interner: DbInterner<'db>,
def: GenericDefId,
- predicate_filter: PredicateFilter,
) -> Option<Clause<'db>> {
// For traits, add `Self: Trait` predicate. This is
// not part of the predicates that a user writes, but it
@@ -2243,9 +2258,7 @@ where
// prove that the trait applies to the types that were
// used, and adding the predicate into this list ensures
// that this is done.
- if let GenericDefId::TraitId(def_id) = def
- && predicate_filter == PredicateFilter::All
- {
+ if let GenericDefId::TraitId(def_id) = def {
Some(TraitRef::identity(interner, def_id.into()).upcast(interner))
} else {
None
@@ -2282,49 +2295,6 @@ fn push_const_arg_has_type_predicates<'db>(
}
}
-/// Generate implicit `: Sized` predicates for all generics that has no `?Sized` bound.
-/// Exception is Self of a trait def.
-fn implicitly_sized_clauses<'a, 'subst, 'db>(
- db: &'db dyn HirDatabase,
- lang_items: &LangItems,
- def: GenericDefId,
- explicitly_unsized_tys: &'a FxHashSet<Ty<'db>>,
- args: &'subst GenericArgs<'db>,
-) -> Option<impl Iterator<Item = Clause<'db>> + Captures<'a> + Captures<'subst>> {
- let interner = DbInterner::new_no_crate(db);
- let sized_trait = lang_items.Sized?;
-
- let trait_self_idx = trait_self_param_idx(db, def);
-
- Some(
- args.iter()
- .enumerate()
- .filter_map(
- move |(idx, generic_arg)| {
- if Some(idx) == trait_self_idx { None } else { Some(generic_arg) }
- },
- )
- .filter_map(|generic_arg| generic_arg.as_type())
- .filter(move |self_ty| !explicitly_unsized_tys.contains(self_ty))
- .map(move |self_ty| {
- let trait_ref = TraitRef::new_from_args(
- interner,
- sized_trait.into(),
- GenericArgs::new_from_slice(&[self_ty.into()]),
- );
- Clause(Predicate::new(
- interner,
- Binder::dummy(rustc_type_ir::PredicateKind::Clause(
- rustc_type_ir::ClauseKind::Trait(TraitPredicate {
- trait_ref,
- polarity: rustc_type_ir::PredicatePolarity::Positive,
- }),
- )),
- ))
- }),
- )
-}
-
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct GenericDefaults(Option<Arc<[Option<StoredEarlyBinder<StoredGenericArg>>]>>);
@@ -2602,235 +2572,19 @@ pub(crate) fn associated_ty_item_bounds<'db>(
pub(crate) fn associated_type_by_name_including_super_traits<'db>(
db: &'db dyn HirDatabase,
trait_ref: TraitRef<'db>,
- name: &Name,
-) -> Option<(TraitRef<'db>, TypeAliasId)> {
- let module = trait_ref.def_id.0.module(db);
- let interner = DbInterner::new_with(db, module.krate(db));
- all_supertraits_trait_refs(db, trait_ref.def_id.0)
- .map(|t| t.instantiate(interner, trait_ref.args))
- .find_map(|t| {
- let trait_id = t.def_id.0;
- let assoc_type = trait_id.trait_items(db).associated_type_by_name(name)?;
- Some((t, assoc_type))
- })
-}
-
-pub fn associated_type_shorthand_candidates(
- db: &dyn HirDatabase,
- def: GenericDefId,
- res: TypeNs,
- mut cb: impl FnMut(&Name, TypeAliasId) -> bool,
-) -> Option<TypeAliasId> {
- let interner = DbInterner::new_no_crate(db);
- named_associated_type_shorthand_candidates(interner, def, res, None, |name, _, id| {
- cb(name, id).then_some(id)
- })
-}
-
-#[tracing::instrument(skip(interner, check_alias))]
-fn named_associated_type_shorthand_candidates<'db, R>(
- interner: DbInterner<'db>,
- // If the type parameter is defined in an impl and we're in a method, there
- // might be additional where clauses to consider
- def: GenericDefId,
- res: TypeNs,
- assoc_name: Option<Name>,
- mut check_alias: impl FnMut(&Name, TraitRef<'db>, TypeAliasId) -> Option<R>,
-) -> Option<R> {
- let db = interner.db;
- let mut search = |t: TraitRef<'db>| -> Option<R> {
- let mut checked_traits = FxHashSet::default();
- let mut check_trait = |trait_ref: TraitRef<'db>| {
- let trait_id = trait_ref.def_id.0;
- let name = &db.trait_signature(trait_id).name;
- tracing::debug!(?trait_id, ?name);
- if !checked_traits.insert(trait_id) {
- return None;
- }
- let data = trait_id.trait_items(db);
-
- tracing::debug!(?data.items);
- for (name, assoc_id) in &data.items {
- if let &AssocItemId::TypeAliasId(alias) = assoc_id
- && let Some(ty) = check_alias(name, trait_ref, alias)
- {
- return Some(ty);
- }
- }
- None
- };
- let mut stack: SmallVec<[_; 4]> = smallvec![t];
- while let Some(trait_ref) = stack.pop() {
- if let Some(alias) = check_trait(trait_ref) {
- return Some(alias);
- }
- let predicates = generic_predicates_filtered_by(
- db,
- GenericDefId::TraitId(trait_ref.def_id.0),
- PredicateFilter::SelfTrait,
- // We are likely in the midst of lowering generic predicates of `def`.
- // So, if we allow `pred == def` we might fall into an infinite recursion.
- // Actually, we have already checked for the case `pred == def` above as we started
- // with a stack including `trait_id`
- |pred| pred != def && pred == GenericDefId::TraitId(trait_ref.def_id.0),
- )
- .0
- .predicates;
- for pred in predicates.get().instantiate_identity() {
- tracing::debug!(?pred);
- let sup_trait_ref = match pred.kind().skip_binder() {
- rustc_type_ir::ClauseKind::Trait(pred) => pred.trait_ref,
- _ => continue,
- };
- let sup_trait_ref =
- EarlyBinder::bind(sup_trait_ref).instantiate(interner, trait_ref.args);
- stack.push(sup_trait_ref);
- }
- tracing::debug!(?stack);
- }
-
- None
- };
-
- match res {
- TypeNs::SelfType(impl_id) => {
- let trait_ref = db.impl_trait(impl_id)?;
-
- // FIXME(next-solver): same method in `lower` checks for impl or not
- // Is that needed here?
-
- // we're _in_ the impl -- the binders get added back later. Correct,
- // but it would be nice to make this more explicit
- search(trait_ref.skip_binder())
- }
- TypeNs::GenericParam(param_id) => {
- // Handle `Self::Type` referring to own associated type in trait definitions
- // This *must* be done first to avoid cycles with
- // `generic_predicates_for_param`, but not sure that it's sufficient,
- if let GenericDefId::TraitId(trait_id) = param_id.parent() {
- let trait_name = &db.trait_signature(trait_id).name;
- tracing::debug!(?trait_name);
- let trait_generics = generics(db, trait_id.into());
- tracing::debug!(?trait_generics);
- if trait_generics[param_id.local_id()].is_trait_self() {
- let args = GenericArgs::identity_for_item(interner, trait_id.into());
- let trait_ref = TraitRef::new_from_args(interner, trait_id.into(), args);
- tracing::debug!(?args, ?trait_ref);
- return search(trait_ref);
- }
- }
-
- let predicates =
- generic_predicates_for_param(db, def, param_id.into(), assoc_name.clone());
- predicates
- .get()
- .iter_identity()
- .find_map(|pred| match pred.kind().skip_binder() {
- rustc_type_ir::ClauseKind::Trait(trait_predicate) => Some(trait_predicate),
- _ => None,
- })
- .and_then(|trait_predicate| {
- let trait_ref = trait_predicate.trait_ref;
- assert!(
- !trait_ref.has_escaping_bound_vars(),
- "FIXME unexpected higher-ranked trait bound"
- );
- search(trait_ref)
- })
- }
- _ => None,
- }
-}
-
-/// During lowering, elaborating supertraits can cause cycles. To avoid that, we have a separate query
-/// to only collect supertraits.
-///
-/// Technically, it is possible to avoid even more cycles by only collecting the `TraitId` of supertraits
-/// without their args. However rustc doesn't do that, so we don't either.
-pub(crate) fn all_supertraits_trait_refs(
- db: &dyn HirDatabase,
- trait_: TraitId,
-) -> impl ExactSizeIterator<Item = EarlyBinder<'_, TraitRef<'_>>> {
+ name: Name,
+) -> Option<(TypeAliasId, GenericArgs<'db>)> {
+ let assoc_type = resolve_type_param_assoc_type_shorthand(
+ db,
+ trait_ref.def_id.0.into(),
+ TypeParamId::trait_self(trait_ref.def_id.0),
+ name.clone(),
+ )
+ .as_ref()
+ .ok()?;
+ let (assoc_type, trait_args) = assoc_type
+ .get_with(|(assoc_type, trait_args)| (*assoc_type, trait_args.as_ref()))
+ .skip_binder();
let interner = DbInterner::new_no_crate(db);
- return all_supertraits_trait_refs_query(db, trait_).iter().map(move |trait_ref| {
- trait_ref.get_with(|(trait_, args)| {
- TraitRef::new_from_args(interner, (*trait_).into(), args.as_ref())
- })
- });
-
- #[salsa_macros::tracked(returns(deref), cycle_result = all_supertraits_trait_refs_cycle_result)]
- pub(crate) fn all_supertraits_trait_refs_query(
- db: &dyn HirDatabase,
- trait_: TraitId,
- ) -> Box<[StoredEarlyBinder<(TraitId, StoredGenericArgs)>]> {
- let resolver = trait_.resolver(db);
- let signature = db.trait_signature(trait_);
- let mut ctx = TyLoweringContext::new(
- db,
- &resolver,
- &signature.store,
- trait_.into(),
- LifetimeElisionKind::AnonymousReportError,
- );
- let interner = ctx.interner;
-
- let self_param_ty = Ty::new_param(
- interner,
- TypeParamId::from_unchecked(TypeOrConstParamId {
- parent: trait_.into(),
- local_id: Idx::from_raw(la_arena::RawIdx::from_u32(0)),
- }),
- 0,
- );
-
- let mut supertraits = FxHashSet::default();
- supertraits.insert(StoredEarlyBinder::bind((
- trait_,
- GenericArgs::identity_for_item(interner, trait_.into()).store(),
- )));
-
- for pred in signature.generic_params.where_predicates() {
- let WherePredicate::TypeBound { target, bound } = pred else {
- continue;
- };
- let target = &signature.store[*target];
- if let TypeRef::TypeParam(param_id) = target
- && param_id.local_id().into_raw().into_u32() == 0
- {
- // This is `Self`.
- } else if let TypeRef::Path(path) = target
- && path.is_self_type()
- {
- // Also `Self`.
- } else {
- // Not `Self`!
- continue;
- }
-
- ctx.lower_type_bound(bound, self_param_ty, true).for_each(|(clause, _)| {
- if let ClauseKind::Trait(trait_ref) = clause.kind().skip_binder() {
- supertraits.extend(
- all_supertraits_trait_refs(db, trait_ref.trait_ref.def_id.0).map(|t| {
- let trait_ref = t.instantiate(interner, trait_ref.trait_ref.args);
- StoredEarlyBinder::bind((trait_ref.def_id.0, trait_ref.args.store()))
- }),
- );
- }
- });
- }
-
- Box::from_iter(supertraits)
- }
-
- pub(crate) fn all_supertraits_trait_refs_cycle_result(
- db: &dyn HirDatabase,
- _: salsa::Id,
- trait_: TraitId,
- ) -> Box<[StoredEarlyBinder<(TraitId, StoredGenericArgs)>]> {
- let interner = DbInterner::new_no_crate(db);
- Box::new([StoredEarlyBinder::bind((
- trait_,
- GenericArgs::identity_for_item(interner, trait_.into()).store(),
- ))])
- }
+ Some((assoc_type, EarlyBinder::bind(trait_args).instantiate(interner, trait_ref.args)))
}
diff --git a/crates/hir-ty/src/lower/path.rs b/crates/hir-ty/src/lower/path.rs
index 517f67b828..d47d696259 100644
--- a/crates/hir-ty/src/lower/path.rs
+++ b/crates/hir-ty/src/lower/path.rs
@@ -2,7 +2,7 @@
use either::Either;
use hir_def::{
- GenericDefId, GenericParamId, Lookup, TraitId, TypeAliasId,
+ GenericDefId, GenericParamId, Lookup, TraitId, TypeParamId,
expr_store::{
ExpressionStore, HygieneId,
path::{
@@ -17,7 +17,6 @@ use hir_def::{
signatures::TraitFlags,
type_ref::{TypeRef, TypeRefId},
};
-use hir_expand::name::Name;
use rustc_type_ir::{
AliasTerm, AliasTy, AliasTyKind,
inherent::{GenericArgs as _, Region as _, Ty as _},
@@ -31,13 +30,10 @@ use crate::{
consteval::{unknown_const, unknown_const_as_generic},
db::HirDatabase,
generics::{Generics, generics},
- lower::{
- GenericPredicateSource, LifetimeElisionKind, PathDiagnosticCallbackData,
- named_associated_type_shorthand_candidates,
- },
+ lower::{GenericPredicateSource, LifetimeElisionKind, PathDiagnosticCallbackData},
next_solver::{
- Binder, Clause, Const, DbInterner, ErrorGuaranteed, GenericArg, GenericArgs, Predicate,
- ProjectionPredicate, Region, TraitRef, Ty,
+ Binder, Clause, Const, DbInterner, EarlyBinder, ErrorGuaranteed, GenericArg, GenericArgs,
+ Predicate, ProjectionPredicate, Region, TraitRef, Ty,
},
};
@@ -479,43 +475,59 @@ impl<'a, 'b, 'db> PathLoweringContext<'a, 'b, 'db> {
#[tracing::instrument(skip(self), ret)]
fn select_associated_type(&mut self, res: Option<TypeNs>, infer_args: bool) -> Ty<'db> {
let interner = self.ctx.interner;
- let Some(res) = res else {
- return Ty::new_error(self.ctx.interner, ErrorGuaranteed);
- };
+ let db = self.ctx.db;
let def = self.ctx.def;
let segment = self.current_or_prev_segment;
let assoc_name = segment.name;
- let check_alias = |name: &Name, t: TraitRef<'db>, associated_ty: TypeAliasId| {
- if name != assoc_name {
- return None;
+ let error_ty = || Ty::new_error(self.ctx.interner, ErrorGuaranteed);
+ let (assoc_type, trait_args) = match res {
+ Some(TypeNs::GenericParam(param)) => {
+ let Ok(assoc_type) = super::resolve_type_param_assoc_type_shorthand(
+ db,
+ def,
+ param,
+ assoc_name.clone(),
+ ) else {
+ return error_ty();
+ };
+ assoc_type
+ .get_with(|(assoc_type, trait_args)| (*assoc_type, trait_args.as_ref()))
+ .skip_binder()
}
-
- // FIXME: `substs_from_path_segment()` pushes `TyKind::Error` for every parent
- // 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(associated_ty.into(), infer_args, None, true);
-
- let substs = GenericArgs::new_from_iter(
- interner,
- t.args.iter().chain(substs.iter().skip(t.args.len())),
- );
-
- Some(Ty::new_alias(
- interner,
- AliasTyKind::Projection,
- AliasTy::new_from_args(interner, associated_ty.into(), substs),
- ))
+ Some(TypeNs::SelfType(impl_)) => {
+ let Some(impl_trait) = db.impl_trait(impl_) else {
+ return error_ty();
+ };
+ let impl_trait = impl_trait.instantiate_identity();
+ // Searching for `Self::Assoc` in `impl Trait for Type` is like searching for `Self::Assoc` in `Trait`.
+ let Ok(assoc_type) = super::resolve_type_param_assoc_type_shorthand(
+ db,
+ impl_trait.def_id.0.into(),
+ TypeParamId::trait_self(impl_trait.def_id.0),
+ assoc_name.clone(),
+ ) else {
+ return error_ty();
+ };
+ let (assoc_type, trait_args) = assoc_type
+ .get_with(|(assoc_type, trait_args)| (*assoc_type, trait_args.as_ref()))
+ .skip_binder();
+ (assoc_type, EarlyBinder::bind(trait_args).instantiate(interner, impl_trait.args))
+ }
+ _ => return error_ty(),
};
- named_associated_type_shorthand_candidates(
+
+ // FIXME: `substs_from_path_segment()` pushes `TyKind::Error` for every parent
+ // 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(assoc_type.into(), infer_args, None, true);
+
+ let substs = GenericArgs::new_from_iter(
interner,
- def,
- res,
- Some(assoc_name.clone()),
- check_alias,
- )
- .unwrap_or_else(|| Ty::new_error(interner, ErrorGuaranteed))
+ trait_args.iter().chain(substs.iter().skip(trait_args.len())),
+ );
+
+ Ty::new_projection_from_args(interner, assoc_type.into(), substs)
}
fn lower_path_inner(&mut self, typeable: TyDefId, infer_args: bool) -> Ty<'db> {
@@ -860,9 +872,9 @@ impl<'a, 'b, 'db> PathLoweringContext<'a, 'b, 'db> {
let found = associated_type_by_name_including_super_traits(
self.ctx.db,
trait_ref,
- &binding.name,
+ binding.name.clone(),
);
- let (super_trait_ref, associated_ty) = match found {
+ let (associated_ty, super_trait_args) = match found {
None => return SmallVec::new(),
Some(t) => t,
};
@@ -876,7 +888,7 @@ impl<'a, 'b, 'db> PathLoweringContext<'a, 'b, 'db> {
binding.args.as_ref(),
associated_ty.into(),
false, // this is not relevant
- Some(super_trait_ref.self_ty()),
+ Some(super_trait_args.type_at(0)),
PathGenericsSource::AssocType {
segment: this.current_segment_u32(),
assoc_type: binding_idx as u32,
@@ -887,7 +899,7 @@ impl<'a, 'b, 'db> PathLoweringContext<'a, 'b, 'db> {
});
let args = GenericArgs::new_from_iter(
interner,
- super_trait_ref.args.iter().chain(args.iter().skip(super_trait_ref.args.len())),
+ super_trait_args.iter().chain(args.iter().skip(super_trait_args.len())),
);
let projection_term =
AliasTerm::new_from_args(interner, associated_ty.into(), args);
diff --git a/crates/hir-ty/src/next_solver/util.rs b/crates/hir-ty/src/next_solver/util.rs
index 9a1b476976..c175062bda 100644
--- a/crates/hir-ty/src/next_solver/util.rs
+++ b/crates/hir-ty/src/next_solver/util.rs
@@ -13,13 +13,16 @@ use rustc_type_ir::{
solve::SizedTraitKind,
};
-use crate::next_solver::{
- BoundConst, FxIndexMap, ParamEnv, Placeholder, PlaceholderConst, PlaceholderRegion,
- PolyTraitRef,
- infer::{
- InferCtxt,
- traits::{Obligation, ObligationCause, PredicateObligation},
+use crate::{
+ next_solver::{
+ BoundConst, FxIndexMap, ParamEnv, Placeholder, PlaceholderConst, PlaceholderRegion,
+ PolyTraitRef,
+ infer::{
+ InferCtxt,
+ traits::{Obligation, ObligationCause, PredicateObligation},
+ },
},
+ representability::Representability,
};
use super::{
@@ -419,10 +422,18 @@ pub fn sizedness_constraint_for_ty<'db>(
.next_back()
.and_then(|ty| sizedness_constraint_for_ty(interner, sizedness, ty)),
- Adt(adt, args) => adt.struct_tail_ty(interner).and_then(|tail_ty| {
- let tail_ty = tail_ty.instantiate(interner, args);
- sizedness_constraint_for_ty(interner, sizedness, tail_ty)
- }),
+ Adt(adt, args) => {
+ if crate::representability::representability(interner.db, adt.def_id().0)
+ == Representability::Infinite
+ {
+ return None;
+ }
+
+ adt.struct_tail_ty(interner).and_then(|tail_ty| {
+ let tail_ty = tail_ty.instantiate(interner, args);
+ sizedness_constraint_for_ty(interner, sizedness, tail_ty)
+ })
+ }
Placeholder(..) | Bound(..) | Infer(..) => {
panic!("unexpected type `{ty:?}` in sizedness_constraint_for_ty")
diff --git a/crates/hir-ty/src/representability.rs b/crates/hir-ty/src/representability.rs
new file mode 100644
index 0000000000..7e40f2d7ac
--- /dev/null
+++ b/crates/hir-ty/src/representability.rs
@@ -0,0 +1,131 @@
+//! Detecting whether a type is infinitely-sized.
+
+use hir_def::{AdtId, VariantId};
+use rustc_type_ir::inherent::{AdtDef, IntoKind};
+
+use crate::{
+ db::HirDatabase,
+ next_solver::{GenericArgKind, GenericArgs, Ty, TyKind},
+};
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub(crate) enum Representability {
+ Representable,
+ Infinite,
+}
+
+macro_rules! rtry {
+ ($e:expr) => {
+ match $e {
+ e @ Representability::Infinite => return e,
+ Representability::Representable => {}
+ }
+ };
+}
+
+#[salsa::tracked(cycle_result = representability_cycle)]
+pub(crate) fn representability(db: &dyn HirDatabase, id: AdtId) -> Representability {
+ match id {
+ AdtId::StructId(id) => variant_representability(db, id.into()),
+ AdtId::UnionId(id) => variant_representability(db, id.into()),
+ AdtId::EnumId(id) => {
+ for &(variant, ..) in &id.enum_variants(db).variants {
+ rtry!(variant_representability(db, variant.into()));
+ }
+ Representability::Representable
+ }
+ }
+}
+
+pub(crate) fn representability_cycle(
+ _db: &dyn HirDatabase,
+ _: salsa::Id,
+ _id: AdtId,
+) -> Representability {
+ Representability::Infinite
+}
+
+fn variant_representability(db: &dyn HirDatabase, id: VariantId) -> Representability {
+ for ty in db.field_types(id).values() {
+ rtry!(representability_ty(db, ty.get().instantiate_identity()));
+ }
+ Representability::Representable
+}
+
+fn representability_ty<'db>(db: &'db dyn HirDatabase, ty: Ty<'db>) -> Representability {
+ match ty.kind() {
+ TyKind::Adt(adt_id, args) => representability_adt_ty(db, adt_id.def_id().0, args),
+ // FIXME(#11924) allow zero-length arrays?
+ TyKind::Array(ty, _) => representability_ty(db, ty),
+ TyKind::Tuple(tys) => {
+ for ty in tys {
+ rtry!(representability_ty(db, ty));
+ }
+ Representability::Representable
+ }
+ _ => Representability::Representable,
+ }
+}
+
+fn representability_adt_ty<'db>(
+ db: &'db dyn HirDatabase,
+ def_id: AdtId,
+ args: GenericArgs<'db>,
+) -> Representability {
+ rtry!(representability(db, def_id));
+
+ // At this point, we know that the item of the ADT type is representable;
+ // but the type parameters may cause a cycle with an upstream type
+ let params_in_repr = params_in_repr(db, def_id);
+ for (i, arg) in args.iter().enumerate() {
+ if let GenericArgKind::Type(ty) = arg.kind()
+ && params_in_repr[i]
+ {
+ rtry!(representability_ty(db, ty));
+ }
+ }
+ Representability::Representable
+}
+
+fn params_in_repr(db: &dyn HirDatabase, def_id: AdtId) -> Box<[bool]> {
+ let generics = db.generic_params(def_id.into());
+ let mut params_in_repr = (0..generics.len_lifetimes() + generics.len_type_or_consts())
+ .map(|_| false)
+ .collect::<Box<[bool]>>();
+ let mut handle_variant = |variant| {
+ for field in db.field_types(variant).values() {
+ params_in_repr_ty(db, field.get().instantiate_identity(), &mut params_in_repr);
+ }
+ };
+ match def_id {
+ AdtId::StructId(def_id) => handle_variant(def_id.into()),
+ AdtId::UnionId(def_id) => handle_variant(def_id.into()),
+ AdtId::EnumId(def_id) => {
+ for &(variant, ..) in &def_id.enum_variants(db).variants {
+ handle_variant(variant.into());
+ }
+ }
+ }
+ params_in_repr
+}
+
+fn params_in_repr_ty<'db>(db: &'db dyn HirDatabase, ty: Ty<'db>, params_in_repr: &mut [bool]) {
+ match ty.kind() {
+ TyKind::Adt(adt, args) => {
+ let inner_params_in_repr = self::params_in_repr(db, adt.def_id().0);
+ for (i, arg) in args.iter().enumerate() {
+ if let GenericArgKind::Type(ty) = arg.kind()
+ && inner_params_in_repr[i]
+ {
+ params_in_repr_ty(db, ty, params_in_repr);
+ }
+ }
+ }
+ TyKind::Array(ty, _) => params_in_repr_ty(db, ty, params_in_repr),
+ TyKind::Tuple(tys) => tys.iter().for_each(|ty| params_in_repr_ty(db, ty, params_in_repr)),
+ TyKind::Param(param) => {
+ params_in_repr[param.index as usize] = true;
+ }
+ _ => {}
+ }
+}
diff --git a/crates/hir-ty/src/tests/regression.rs b/crates/hir-ty/src/tests/regression.rs
index 5291bf80e8..658c304aac 100644
--- a/crates/hir-ty/src/tests/regression.rs
+++ b/crates/hir-ty/src/tests/regression.rs
@@ -2770,3 +2770,28 @@ unsafe extern "C" {
"#,
);
}
+
+#[test]
+fn infinitely_sized_type() {
+ check_infer(
+ r#"
+//- minicore: sized
+
+pub struct Recursive {
+ pub content: Recursive,
+}
+
+fn is_sized<T: Sized>() {}
+
+fn foo() {
+ is_sized::<Recursive>();
+}
+ "#,
+ expect![[r#"
+ 79..81 '{}': ()
+ 92..124 '{ ...>(); }': ()
+ 98..119 'is_siz...rsive>': fn is_sized<Recursive>()
+ 98..121 'is_siz...ive>()': ()
+ "#]],
+ );
+}
diff --git a/crates/hir-ty/src/utils.rs b/crates/hir-ty/src/utils.rs
index 148300deb8..be64f55ea5 100644
--- a/crates/hir-ty/src/utils.rs
+++ b/crates/hir-ty/src/utils.rs
@@ -1,28 +1,19 @@
//! Helper functions for working with def, which don't need to be a separate
//! query, but can't be computed directly from `*Data` (ie, which need a `db`).
-use std::cell::LazyCell;
-
use base_db::target::{self, TargetData};
use hir_def::{
- EnumId, EnumVariantId, FunctionId, Lookup, TraitId,
- attrs::AttrFlags,
- db::DefDatabase,
- hir::generics::WherePredicate,
- lang_item::LangItems,
- resolver::{HasResolver, TypeNs},
- type_ref::{TraitBoundModifier, TypeRef},
+ EnumId, EnumVariantId, FunctionId, Lookup, TraitId, attrs::AttrFlags, lang_item::LangItems,
};
use intern::sym;
use rustc_abi::TargetDataLayout;
-use smallvec::{SmallVec, smallvec};
use span::Edition;
use crate::{
TargetFeatures,
db::HirDatabase,
layout::{Layout, TagEncoding},
- lower::all_supertraits_trait_refs,
+ lower::SupertraitsInfo,
mir::pad16,
};
@@ -51,55 +42,13 @@ pub(crate) fn fn_traits(lang_items: &LangItems) -> impl Iterator<Item = TraitId>
}
/// Returns an iterator over the direct super traits (including the trait itself).
-pub fn direct_super_traits(db: &dyn DefDatabase, trait_: TraitId) -> SmallVec<[TraitId; 4]> {
- let mut result = smallvec![trait_];
- direct_super_traits_cb(db, trait_, |tt| {
- if !result.contains(&tt) {
- result.push(tt);
- }
- });
- result
-}
-
-/// Returns an iterator over the whole super trait hierarchy (including the
-/// trait itself).
-pub fn all_super_traits(db: &dyn HirDatabase, trait_: TraitId) -> SmallVec<[TraitId; 4]> {
- let mut supertraits = all_supertraits_trait_refs(db, trait_)
- .map(|trait_ref| trait_ref.skip_binder().def_id.0)
- .collect::<SmallVec<[_; _]>>();
- supertraits.sort_unstable();
- supertraits.dedup();
- supertraits
+pub fn direct_super_traits(db: &dyn HirDatabase, trait_: TraitId) -> &[TraitId] {
+ &SupertraitsInfo::query(db, trait_).direct_supertraits
}
-fn direct_super_traits_cb(db: &dyn DefDatabase, trait_: TraitId, cb: impl FnMut(TraitId)) {
- let resolver = LazyCell::new(|| trait_.resolver(db));
- let (generic_params, store) = db.generic_params_and_store(trait_.into());
- let trait_self = generic_params.trait_self_param();
- generic_params
- .where_predicates()
- .iter()
- .filter_map(|pred| match pred {
- WherePredicate::ForLifetime { target, bound, .. }
- | WherePredicate::TypeBound { target, bound } => {
- let is_trait = match &store[*target] {
- TypeRef::Path(p) => p.is_self_type(),
- TypeRef::TypeParam(p) => Some(p.local_id()) == trait_self,
- _ => false,
- };
- match is_trait {
- true => bound.as_path(&store),
- false => None,
- }
- }
- WherePredicate::Lifetime { .. } => None,
- })
- .filter(|(_, bound_modifier)| matches!(bound_modifier, TraitBoundModifier::None))
- .filter_map(|(path, _)| match resolver.resolve_path_in_type_ns_fully(db, path) {
- Some(TypeNs::TraitId(t)) => Some(t),
- _ => None,
- })
- .for_each(cb);
+/// Returns the whole super trait hierarchy (including the trait itself).
+pub fn all_super_traits(db: &dyn HirDatabase, trait_: TraitId) -> &[TraitId] {
+ &SupertraitsInfo::query(db, trait_).all_supertraits
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index 5820a6714b..9dbee16dae 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -6167,6 +6167,7 @@ impl<'db> Type<'db> {
self.autoderef_(db)
.filter_map(|ty| ty.dyn_trait())
.flat_map(move |dyn_trait_id| hir_ty::all_super_traits(db, dyn_trait_id))
+ .copied()
.map(Trait::from)
}
@@ -6184,6 +6185,7 @@ impl<'db> Type<'db> {
_ => None,
})
.flat_map(|t| hir_ty::all_super_traits(db, t))
+ .copied()
})
.map(Trait::from)
}
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 65ca1ceae1..afdced4215 100644
--- a/crates/ide-assists/src/handlers/add_missing_impl_members.rs
+++ b/crates/ide-assists/src/handlers/add_missing_impl_members.rs
@@ -2537,4 +2537,84 @@ impl Test for () {
"#,
);
}
+
+ #[test]
+ fn test_param_name_not_qualified() {
+ check_assist(
+ add_missing_impl_members,
+ r#"
+mod ptr {
+ pub struct NonNull<T>(T);
+}
+mod alloc {
+ use super::ptr::NonNull;
+ pub trait Allocator {
+ unsafe fn deallocate(&self, ptr: NonNull<u8>);
+ }
+}
+
+struct System;
+
+unsafe impl alloc::Allocator for System {
+ $0
+}
+"#,
+ r#"
+mod ptr {
+ pub struct NonNull<T>(T);
+}
+mod alloc {
+ use super::ptr::NonNull;
+ pub trait Allocator {
+ unsafe fn deallocate(&self, ptr: NonNull<u8>);
+ }
+}
+
+struct System;
+
+unsafe impl alloc::Allocator for System {
+ unsafe fn deallocate(&self, ptr: ptr::NonNull<u8>) {
+ ${0:todo!()}
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_param_name_shadows_module() {
+ check_assist(
+ add_missing_impl_members,
+ r#"
+mod m { }
+use m as p;
+
+pub trait Allocator {
+ fn deallocate(&self, p: u8);
+}
+
+struct System;
+
+impl Allocator for System {
+ $0
+}
+"#,
+ r#"
+mod m { }
+use m as p;
+
+pub trait Allocator {
+ fn deallocate(&self, p: u8);
+}
+
+struct System;
+
+impl Allocator for System {
+ fn deallocate(&self, p: u8) {
+ ${0:todo!()}
+ }
+}
+"#,
+ );
+ }
}
diff --git a/crates/ide-assists/src/handlers/convert_bool_then.rs b/crates/ide-assists/src/handlers/convert_bool_then.rs
index 91cee59ad8..d2c4ed9b5a 100644
--- a/crates/ide-assists/src/handlers/convert_bool_then.rs
+++ b/crates/ide-assists/src/handlers/convert_bool_then.rs
@@ -102,6 +102,11 @@ pub(crate) fn convert_if_to_bool_then(acc: &mut Assists, ctx: &AssistContext<'_>
ast::Expr::BlockExpr(block) => unwrap_trivial_block(block),
e => e,
};
+ let cond = if invert_cond {
+ invert_boolean_expression(&make, cond)
+ } else {
+ cond.clone_for_update()
+ };
let parenthesize = matches!(
cond,
@@ -123,11 +128,7 @@ pub(crate) fn convert_if_to_bool_then(acc: &mut Assists, ctx: &AssistContext<'_>
| ast::Expr::WhileExpr(_)
| ast::Expr::YieldExpr(_)
);
- let cond = if invert_cond {
- invert_boolean_expression(&make, cond)
- } else {
- cond.clone_for_update()
- };
+
let cond = if parenthesize { make.expr_paren(cond).into() } else { cond };
let arg_list = make.arg_list(Some(make.expr_closure(None, closure_body).into()));
let mcall = make.expr_method_call(cond, make.name_ref("then"), arg_list);
@@ -591,4 +592,23 @@ fn main() {
",
);
}
+ #[test]
+ fn convert_if_to_bool_then_invert_method_call() {
+ check_assist(
+ convert_if_to_bool_then,
+ r"
+//- minicore:option
+fn main() {
+ let test = &[()];
+ let value = if$0 test.is_empty() { None } else { Some(()) };
+}
+",
+ r"
+fn main() {
+ let test = &[()];
+ let value = (!test.is_empty()).then(|| ());
+}
+",
+ );
+ }
}
diff --git a/crates/ide-assists/src/handlers/convert_to_guarded_return.rs b/crates/ide-assists/src/handlers/convert_to_guarded_return.rs
index ea5c1637b7..dc51bf4b5b 100644
--- a/crates/ide-assists/src/handlers/convert_to_guarded_return.rs
+++ b/crates/ide-assists/src/handlers/convert_to_guarded_return.rs
@@ -942,6 +942,32 @@ fn main() {
}
#[test]
+ fn convert_let_ref_stmt_inside_fn() {
+ check_assist(
+ convert_to_guarded_return,
+ r#"
+//- minicore: option
+fn foo() -> &'static Option<i32> {
+ &None
+}
+
+fn main() {
+ let x$0 = foo();
+}
+"#,
+ r#"
+fn foo() -> &'static Option<i32> {
+ &None
+}
+
+fn main() {
+ let Some(x) = foo() else { return };
+}
+"#,
+ );
+ }
+
+ #[test]
fn convert_let_stmt_inside_fn_return_option() {
check_assist(
convert_to_guarded_return,
diff --git a/crates/ide-assists/src/handlers/generate_function.rs b/crates/ide-assists/src/handlers/generate_function.rs
index ded3b0f5ac..f62eccaf19 100644
--- a/crates/ide-assists/src/handlers/generate_function.rs
+++ b/crates/ide-assists/src/handlers/generate_function.rs
@@ -147,7 +147,16 @@ fn gen_method(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
return None;
}
- let (impl_, file) = get_adt_source(ctx, &adt, fn_name.text().as_str())?;
+ let enclosing_impl = ctx.find_node_at_offset::<ast::Impl>();
+ let cursor_impl = enclosing_impl.filter(|impl_| {
+ ctx.sema.to_def(impl_).map_or(false, |def| def.self_ty(ctx.sema.db).as_adt() == Some(adt))
+ });
+
+ let (impl_, file) = if let Some(impl_) = cursor_impl {
+ (Some(impl_), ctx.vfs_file_id())
+ } else {
+ get_adt_source(ctx, &adt, fn_name.text().as_str())?
+ };
let target = get_method_target(ctx, &impl_, &adt)?;
let function_builder = FunctionBuilder::from_method_call(
@@ -3206,4 +3215,44 @@ fn bar(arg: impl Fn(_) -> bool) {
"#,
);
}
+ #[test]
+ fn generate_method_uses_current_impl_block() {
+ check_assist(
+ generate_function,
+ r"
+struct Foo;
+
+impl Foo {
+ fn new() -> Self {
+ Foo
+ }
+}
+
+impl Foo {
+ fn method1(&self) {
+ self.method2$0(42)
+ }
+}
+",
+ r"
+struct Foo;
+
+impl Foo {
+ fn new() -> Self {
+ Foo
+ }
+}
+
+impl Foo {
+ fn method1(&self) {
+ self.method2(42)
+ }
+
+ fn method2(&self, arg: i32) {
+ ${0:todo!()}
+ }
+}
+",
+ )
+ }
}
diff --git a/crates/ide-assists/src/handlers/generate_getter_or_setter.rs b/crates/ide-assists/src/handlers/generate_getter_or_setter.rs
index 51b967437b..92a654743b 100644
--- a/crates/ide-assists/src/handlers/generate_getter_or_setter.rs
+++ b/crates/ide-assists/src/handlers/generate_getter_or_setter.rs
@@ -11,7 +11,7 @@ use syntax::{
use crate::{
AssistContext, AssistId, Assists, GroupLabel,
- utils::{convert_reference_type, find_struct_impl},
+ utils::{convert_reference_type, find_struct_impl, is_selected},
};
// Assist: generate_setter
@@ -377,7 +377,7 @@ fn extract_and_parse_record_fields(
let info_of_record_fields_in_selection = ele
.fields()
.filter_map(|record_field| {
- if selection_range.contains_range(record_field.syntax().text_range()) {
+ if is_selected(&record_field, selection_range, false) {
let record_field_info = parse_record_field(record_field, assist_type)?;
field_names.push(record_field_info.fn_name.clone());
return Some(record_field_info);
@@ -948,6 +948,37 @@ impl Context {
}
#[test]
+ fn test_generate_multiple_getters_from_partial_selection() {
+ check_assist(
+ generate_getter,
+ r#"
+struct Context {
+ data$0: Data,
+ count$0: usize,
+ other: usize,
+}
+ "#,
+ r#"
+struct Context {
+ data: Data,
+ count: usize,
+ other: usize,
+}
+
+impl Context {
+ fn data(&self) -> &Data {
+ &self.data
+ }
+
+ fn $0count(&self) -> &usize {
+ &self.count
+ }
+}
+ "#,
+ );
+ }
+
+ #[test]
fn test_generate_multiple_getters_from_selection_one_already_exists() {
// As impl for one of the fields already exist, skip it
check_assist_not_applicable(
diff --git a/crates/ide-assists/src/handlers/replace_if_let_with_match.rs b/crates/ide-assists/src/handlers/replace_if_let_with_match.rs
index 915dd3ffca..d2452f28c4 100644
--- a/crates/ide-assists/src/handlers/replace_if_let_with_match.rs
+++ b/crates/ide-assists/src/handlers/replace_if_let_with_match.rs
@@ -850,6 +850,31 @@ fn foo(x: Option<i32>) {
}
#[test]
+ fn special_case_option_ref() {
+ check_assist(
+ replace_if_let_with_match,
+ r#"
+//- minicore: option
+fn foo(x: &Option<i32>) {
+ $0if let Some(x) = x {
+ println!("{}", x)
+ } else {
+ println!("none")
+ }
+}
+"#,
+ r#"
+fn foo(x: &Option<i32>) {
+ match x {
+ Some(x) => println!("{}", x),
+ None => println!("none"),
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
fn special_case_inverted_option() {
check_assist(
replace_if_let_with_match,
diff --git a/crates/ide-assists/src/handlers/replace_is_method_with_if_let_method.rs b/crates/ide-assists/src/handlers/replace_is_method_with_if_let_method.rs
index 5a2307739c..d22e951b5d 100644
--- a/crates/ide-assists/src/handlers/replace_is_method_with_if_let_method.rs
+++ b/crates/ide-assists/src/handlers/replace_is_method_with_if_let_method.rs
@@ -1,6 +1,6 @@
use either::Either;
use ide_db::syntax_helpers::suggest_name;
-use syntax::ast::{self, AstNode, syntax_factory::SyntaxFactory};
+use syntax::ast::{self, AstNode, HasArgList, syntax_factory::SyntaxFactory};
use crate::{AssistContext, AssistId, Assists, utils::cover_let_chain};
@@ -34,8 +34,9 @@ pub(crate) fn replace_is_method_with_if_let_method(
_ => return None,
};
- let name_ref = call_expr.name_ref()?;
- match name_ref.text().as_str() {
+ let token = call_expr.name_ref()?.ident_token()?;
+ let method_kind = token.text().strip_suffix("_and").unwrap_or(token.text());
+ match method_kind {
"is_some" | "is_ok" => {
let receiver = call_expr.receiver()?;
@@ -47,8 +48,9 @@ pub(crate) fn replace_is_method_with_if_let_method(
} else {
name_generator.for_variable(&receiver, &ctx.sema)
};
+ let (pat, predicate) = method_predicate(&call_expr).unzip();
- let (assist_id, message, text) = if name_ref.text() == "is_some" {
+ let (assist_id, message, text) = if method_kind == "is_some" {
("replace_is_some_with_if_let_some", "Replace `is_some` with `let Some`", "Some")
} else {
("replace_is_ok_with_if_let_ok", "Replace `is_ok` with `let Ok`", "Ok")
@@ -62,19 +64,29 @@ pub(crate) fn replace_is_method_with_if_let_method(
let make = SyntaxFactory::with_mappings();
let mut editor = edit.make_editor(call_expr.syntax());
- let var_pat = make.ident_pat(false, false, make.name(&var_name));
- let pat = make.tuple_struct_pat(make.ident_path(text), [var_pat.into()]);
- let let_expr = make.expr_let(pat.into(), receiver);
+ let var_pat = pat.unwrap_or_else(|| {
+ make.ident_pat(false, false, make.name(&var_name)).into()
+ });
+ let pat = make.tuple_struct_pat(make.ident_path(text), [var_pat]).into();
+ let let_expr = make.expr_let(pat, receiver);
if let Some(cap) = ctx.config.snippet_cap
&& let Some(ast::Pat::TupleStructPat(pat)) = let_expr.pat()
&& let Some(first_var) = pat.fields().next()
+ && predicate.is_none()
{
let placeholder = edit.make_placeholder_snippet(cap);
editor.add_annotation(first_var.syntax(), placeholder);
}
- editor.replace(call_expr.syntax(), let_expr.syntax());
+ let new_expr = if let Some(predicate) = predicate {
+ let op = ast::BinaryOp::LogicOp(ast::LogicOp::And);
+ make.expr_bin(let_expr.into(), op, predicate).into()
+ } else {
+ ast::Expr::from(let_expr)
+ };
+ editor.replace(call_expr.syntax(), new_expr.syntax());
+
editor.add_mappings(make.finish_with_mappings());
edit.add_file_edits(ctx.vfs_file_id(), editor);
},
@@ -84,6 +96,17 @@ pub(crate) fn replace_is_method_with_if_let_method(
}
}
+fn method_predicate(call_expr: &ast::MethodCallExpr) -> Option<(ast::Pat, ast::Expr)> {
+ let argument = call_expr.arg_list()?.args().next()?;
+ match argument {
+ ast::Expr::ClosureExpr(it) => {
+ let pat = it.param_list()?.params().next()?.pat()?;
+ Some((pat, it.body()?))
+ }
+ _ => None,
+ }
+}
+
#[cfg(test)]
mod tests {
use crate::tests::{check_assist, check_assist_not_applicable};
@@ -195,6 +218,25 @@ fn main() {
}
#[test]
+ fn replace_is_some_and_with_if_let_chain_some_works() {
+ check_assist(
+ replace_is_method_with_if_let_method,
+ r#"
+fn main() {
+ let x = Some(1);
+ if x.is_som$0e_and(|it| it != 3) {}
+}
+"#,
+ r#"
+fn main() {
+ let x = Some(1);
+ if let Some(it) = x && it != 3 {}
+}
+"#,
+ );
+ }
+
+ #[test]
fn replace_is_some_with_if_let_some_in_let_chain() {
check_assist(
replace_is_method_with_if_let_method,
diff --git a/crates/ide-assists/src/handlers/replace_let_with_if_let.rs b/crates/ide-assists/src/handlers/replace_let_with_if_let.rs
index 15977c420e..5587f1b59c 100644
--- a/crates/ide-assists/src/handlers/replace_let_with_if_let.rs
+++ b/crates/ide-assists/src/handlers/replace_let_with_if_let.rs
@@ -102,6 +102,29 @@ mod tests {
use super::*;
#[test]
+ fn replace_let_try_enum_ref() {
+ check_assist(
+ replace_let_with_if_let,
+ r"
+//- minicore: option
+fn main(action: Action) {
+ $0let x = compute();
+}
+
+fn compute() -> &'static Option<i32> { &None }
+ ",
+ r"
+fn main(action: Action) {
+ if let Some(x) = compute() {
+ }
+}
+
+fn compute() -> &'static Option<i32> { &None }
+ ",
+ )
+ }
+
+ #[test]
fn replace_let_unknown_enum() {
check_assist(
replace_let_with_if_let,
diff --git a/crates/ide-assists/src/handlers/toggle_macro_delimiter.rs b/crates/ide-assists/src/handlers/toggle_macro_delimiter.rs
index 60b0797f02..15143575e7 100644
--- a/crates/ide-assists/src/handlers/toggle_macro_delimiter.rs
+++ b/crates/ide-assists/src/handlers/toggle_macro_delimiter.rs
@@ -1,6 +1,7 @@
use ide_db::assists::AssistId;
use syntax::{
- AstNode, SyntaxToken, T,
+ AstNode, SyntaxKind, SyntaxToken, T,
+ algo::{previous_non_trivia_token, skip_trivia_token},
ast::{self, syntax_factory::SyntaxFactory},
};
@@ -36,15 +37,18 @@ pub(crate) fn toggle_macro_delimiter(acc: &mut Assists, ctx: &AssistContext<'_>)
RCur,
}
- let makro = ctx.find_node_at_offset::<ast::MacroCall>()?;
+ let token_tree = ctx.find_node_at_offset::<ast::TokenTree>()?;
let cursor_offset = ctx.offset();
- let semicolon = macro_semicolon(&makro);
- let token_tree = makro.token_tree()?;
+ let semicolon = macro_semicolon(&token_tree);
let ltoken = token_tree.left_delimiter_token()?;
let rtoken = token_tree.right_delimiter_token()?;
+ if !is_macro_call(&token_tree)? {
+ return None;
+ }
+
if !ltoken.text_range().contains(cursor_offset) && !rtoken.text_range().contains(cursor_offset)
{
return None;
@@ -70,7 +74,7 @@ pub(crate) fn toggle_macro_delimiter(acc: &mut Assists, ctx: &AssistContext<'_>)
token_tree.syntax().text_range(),
|builder| {
let make = SyntaxFactory::with_mappings();
- let mut editor = builder.make_editor(makro.syntax());
+ let mut editor = builder.make_editor(token_tree.syntax());
match token {
MacroDelims::LPar | MacroDelims::RPar => {
@@ -102,12 +106,21 @@ pub(crate) fn toggle_macro_delimiter(acc: &mut Assists, ctx: &AssistContext<'_>)
)
}
-fn macro_semicolon(makro: &ast::MacroCall) -> Option<SyntaxToken> {
- makro.semicolon_token().or_else(|| {
- let macro_expr = ast::MacroExpr::cast(makro.syntax().parent()?)?;
- let expr_stmt = ast::ExprStmt::cast(macro_expr.syntax().parent()?)?;
- expr_stmt.semicolon_token()
- })
+fn is_macro_call(token_tree: &ast::TokenTree) -> Option<bool> {
+ let parent = token_tree.syntax().parent()?;
+ if ast::MacroCall::can_cast(parent.kind()) {
+ return Some(true);
+ }
+
+ let token_tree = ast::TokenTree::cast(parent)?;
+ let prev = previous_non_trivia_token(token_tree.syntax().clone())?;
+ let prev_prev = previous_non_trivia_token(prev.clone())?;
+ Some(prev.kind() == T![!] && prev_prev.kind() == SyntaxKind::IDENT)
+}
+
+fn macro_semicolon(token_tree: &ast::TokenTree) -> Option<SyntaxToken> {
+ let next_token = token_tree.syntax().last_token()?.next_token()?;
+ skip_trivia_token(next_token, syntax::Direction::Next).filter(|it| it.kind() == T![;])
}
fn needs_semicolon(tt: ast::TokenTree) -> bool {
@@ -402,10 +415,9 @@ prt!{(3 + 5)}
)
}
- // FIXME @alibektas : Inner macro_call is not seen as such. So this doesn't work.
#[test]
fn test_nested_macros() {
- check_assist_not_applicable(
+ check_assist(
toggle_macro_delimiter,
r#"
macro_rules! prt {
@@ -420,7 +432,22 @@ macro_rules! abc {
}};
}
-prt!{abc!($03 + 5)};
+prt!{abc!$0(3 + 5)};
+"#,
+ r#"
+macro_rules! prt {
+ ($e:expr) => {{
+ println!("{}", stringify!{$e});
+ }};
+}
+
+macro_rules! abc {
+ ($e:expr) => {{
+ println!("{}", stringify!{$e});
+ }};
+}
+
+prt!{abc!{3 + 5}};
"#,
)
}
diff --git a/crates/ide-completion/src/completions/fn_param.rs b/crates/ide-completion/src/completions/fn_param.rs
index 34d25c9c67..96dac66b8a 100644
--- a/crates/ide-completion/src/completions/fn_param.rs
+++ b/crates/ide-completion/src/completions/fn_param.rs
@@ -4,9 +4,9 @@ use hir::HirDisplay;
use ide_db::FxHashMap;
use itertools::Either;
use syntax::{
- AstNode, Direction, SyntaxKind, TextRange, TextSize, algo,
+ AstNode, Direction, SmolStr, SyntaxKind, TextRange, TextSize, ToSmolStr, algo,
ast::{self, HasModuleItem},
- match_ast,
+ format_smolstr, match_ast,
};
use crate::{
@@ -25,7 +25,7 @@ pub(crate) fn complete_fn_param(
ctx: &CompletionContext<'_>,
pattern_ctx: &PatternContext,
) -> Option<()> {
- let (ParamContext { param_list, kind, .. }, impl_or_trait) = match pattern_ctx {
+ let (ParamContext { param_list, kind, param, .. }, impl_or_trait) = match pattern_ctx {
PatternContext { param_ctx: Some(kind), impl_or_trait, .. } => (kind, impl_or_trait),
_ => return None,
};
@@ -46,13 +46,18 @@ pub(crate) fn complete_fn_param(
match kind {
ParamKind::Function(function) => {
- fill_fn_params(ctx, function, param_list, impl_or_trait, add_new_item_to_acc);
+ fill_fn_params(ctx, function, param_list, param, impl_or_trait, add_new_item_to_acc);
}
ParamKind::Closure(closure) => {
- let stmt_list = closure.syntax().ancestors().find_map(ast::StmtList::cast)?;
- params_from_stmt_list_scope(ctx, stmt_list, |name, ty| {
- add_new_item_to_acc(&format!("{}: {ty}", name.display(ctx.db, ctx.edition)));
- });
+ if is_simple_param(param) {
+ let stmt_list = closure.syntax().ancestors().find_map(ast::StmtList::cast)?;
+ params_from_stmt_list_scope(ctx, stmt_list, |name, ty| {
+ add_new_item_to_acc(&format_smolstr!(
+ "{}: {ty}",
+ name.display(ctx.db, ctx.edition)
+ ));
+ });
+ }
}
}
@@ -63,17 +68,20 @@ fn fill_fn_params(
ctx: &CompletionContext<'_>,
function: &ast::Fn,
param_list: &ast::ParamList,
+ current_param: &ast::Param,
impl_or_trait: &Option<Either<ast::Impl, ast::Trait>>,
mut add_new_item_to_acc: impl FnMut(&str),
) {
let mut file_params = FxHashMap::default();
let mut extract_params = |f: ast::Fn| {
+ if !is_simple_param(current_param) {
+ return;
+ }
f.param_list().into_iter().flat_map(|it| it.params()).for_each(|param| {
if let Some(pat) = param.pat() {
- // FIXME: We should be able to turn these into SmolStr without having to allocate a String
- let whole_param = param.syntax().text().to_string();
- let binding = pat.syntax().text().to_string();
+ let whole_param = param.to_smolstr();
+ let binding = pat.to_smolstr();
file_params.entry(whole_param).or_insert(binding);
}
});
@@ -99,11 +107,13 @@ fn fill_fn_params(
};
}
- if let Some(stmt_list) = function.syntax().parent().and_then(ast::StmtList::cast) {
+ if let Some(stmt_list) = function.syntax().parent().and_then(ast::StmtList::cast)
+ && is_simple_param(current_param)
+ {
params_from_stmt_list_scope(ctx, stmt_list, |name, ty| {
file_params
- .entry(format!("{}: {ty}", name.display(ctx.db, ctx.edition)))
- .or_insert(name.display(ctx.db, ctx.edition).to_string());
+ .entry(format_smolstr!("{}: {ty}", name.display(ctx.db, ctx.edition)))
+ .or_insert(name.display(ctx.db, ctx.edition).to_smolstr());
});
}
remove_duplicated(&mut file_params, param_list.params());
@@ -139,11 +149,11 @@ fn params_from_stmt_list_scope(
}
fn remove_duplicated(
- file_params: &mut FxHashMap<String, String>,
+ file_params: &mut FxHashMap<SmolStr, SmolStr>,
fn_params: ast::AstChildren<ast::Param>,
) {
fn_params.for_each(|param| {
- let whole_param = param.syntax().text().to_string();
+ let whole_param = param.to_smolstr();
file_params.remove(&whole_param);
match param.pat() {
@@ -151,7 +161,7 @@ fn remove_duplicated(
// if the type is missing we are checking the current param to be completed
// in which case this would find itself removing the suggestions due to itself
Some(pattern) if param.ty().is_some() => {
- let binding = pattern.syntax().text().to_string();
+ let binding = pattern.to_smolstr();
file_params.retain(|_, v| v != &binding);
}
_ => (),
@@ -173,7 +183,7 @@ fn should_add_self_completions(
}
}
-fn comma_wrapper(ctx: &CompletionContext<'_>) -> Option<(impl Fn(&str) -> String, TextRange)> {
+fn comma_wrapper(ctx: &CompletionContext<'_>) -> Option<(impl Fn(&str) -> SmolStr, TextRange)> {
let param =
ctx.original_token.parent_ancestors().find(|node| node.kind() == SyntaxKind::PARAM)?;
@@ -196,5 +206,11 @@ fn comma_wrapper(ctx: &CompletionContext<'_>) -> Option<(impl Fn(&str) -> String
matches!(prev_token_kind, SyntaxKind::COMMA | SyntaxKind::L_PAREN | SyntaxKind::PIPE);
let leading = if has_leading_comma { "" } else { ", " };
- Some((move |label: &_| format!("{leading}{label}{trailing}"), param.text_range()))
+ Some((move |label: &_| format_smolstr!("{leading}{label}{trailing}"), param.text_range()))
+}
+
+fn is_simple_param(param: &ast::Param) -> bool {
+ param
+ .pat()
+ .is_none_or(|pat| matches!(pat, ast::Pat::IdentPat(ident_pat) if ident_pat.pat().is_none()))
}
diff --git a/crates/ide-completion/src/completions/postfix.rs b/crates/ide-completion/src/completions/postfix.rs
index cffc44f8af..a58592b136 100644
--- a/crates/ide-completion/src/completions/postfix.rs
+++ b/crates/ide-completion/src/completions/postfix.rs
@@ -16,7 +16,7 @@ use itertools::Itertools;
use stdx::never;
use syntax::{
SmolStr,
- SyntaxKind::{EXPR_STMT, STMT_LIST},
+ SyntaxKind::{BLOCK_EXPR, EXPR_STMT, STMT_LIST},
T, TextRange, TextSize, ToSmolStr,
ast::{self, AstNode, AstToken},
format_smolstr, match_ast,
@@ -91,8 +91,7 @@ pub(crate) fn complete_postfix(
// so it's better to consider references now to avoid breaking the compilation
let (dot_receiver_including_refs, prefix) = include_references(dot_receiver);
- let mut receiver_text =
- get_receiver_text(&ctx.sema, dot_receiver, receiver_is_ambiguous_float_literal);
+ let mut receiver_text = receiver_text;
receiver_text.insert_str(0, &prefix);
let postfix_snippet =
match build_postfix_snippet_builder(ctx, cap, &dot_receiver_including_refs) {
@@ -111,7 +110,7 @@ pub(crate) fn complete_postfix(
postfix_snippet("call", "function(expr)", &format!("${{1}}({receiver_text})"))
.add_to(acc, ctx.db);
- let try_enum = TryEnum::from_ty(&ctx.sema, &receiver_ty.strip_references());
+ let try_enum = TryEnum::from_ty(&ctx.sema, receiver_ty);
let mut is_in_cond = false;
if let Some(parent) = dot_receiver_including_refs.syntax().parent()
&& let Some(second_ancestor) = parent.parent()
@@ -155,12 +154,26 @@ pub(crate) fn complete_postfix(
postfix_snippet("let", "let", &format!("let $1 = {receiver_text}"))
.add_to(acc, ctx.db);
}
- _ if matches!(second_ancestor.kind(), STMT_LIST | EXPR_STMT) => {
+ _ if matches!(second_ancestor.kind(), STMT_LIST | EXPR_STMT | BLOCK_EXPR) => {
postfix_snippet("let", "let", &format!("let $0 = {receiver_text};"))
.add_to(acc, ctx.db);
postfix_snippet("letm", "let mut", &format!("let mut $0 = {receiver_text};"))
.add_to(acc, ctx.db);
}
+ _ if ast::MatchArm::can_cast(second_ancestor.kind()) => {
+ postfix_snippet(
+ "let",
+ "let",
+ &format!("{{\n let $1 = {receiver_text};\n $0\n}}"),
+ )
+ .add_to(acc, ctx.db);
+ postfix_snippet(
+ "letm",
+ "let mut",
+ &format!("{{\n let mut $1 = {receiver_text};\n $0\n}}"),
+ )
+ .add_to(acc, ctx.db);
+ }
_ => (),
}
}
@@ -672,6 +685,87 @@ fn main() {
sn while while expr {}
"#]],
);
+ check(
+ r#"
+fn main() {
+ &baz.l$0
+ res
+}
+"#,
+ expect![[r#"
+ sn box Box::new(expr)
+ sn call function(expr)
+ sn const const {}
+ sn dbg dbg!(expr)
+ sn dbgr dbg!(&expr)
+ sn deref *expr
+ sn if if expr {}
+ sn let let
+ sn letm let mut
+ sn match match expr {}
+ sn not !expr
+ sn ref &expr
+ sn refm &mut expr
+ sn return return expr
+ sn unsafe unsafe {}
+ sn while while expr {}
+ "#]],
+ );
+ }
+
+ #[test]
+ fn let_tail_block() {
+ check(
+ r#"
+fn main() {
+ baz.l$0
+}
+"#,
+ expect![[r#"
+ sn box Box::new(expr)
+ sn call function(expr)
+ sn const const {}
+ sn dbg dbg!(expr)
+ sn dbgr dbg!(&expr)
+ sn deref *expr
+ sn if if expr {}
+ sn let let
+ sn letm let mut
+ sn match match expr {}
+ sn not !expr
+ sn ref &expr
+ sn refm &mut expr
+ sn return return expr
+ sn unsafe unsafe {}
+ sn while while expr {}
+ "#]],
+ );
+
+ check(
+ r#"
+fn main() {
+ &baz.l$0
+}
+"#,
+ expect![[r#"
+ sn box Box::new(expr)
+ sn call function(expr)
+ sn const const {}
+ sn dbg dbg!(expr)
+ sn dbgr dbg!(&expr)
+ sn deref *expr
+ sn if if expr {}
+ sn let let
+ sn letm let mut
+ sn match match expr {}
+ sn not !expr
+ sn ref &expr
+ sn refm &mut expr
+ sn return return expr
+ sn unsafe unsafe {}
+ sn while while expr {}
+ "#]],
+ );
}
#[test]
@@ -796,6 +890,54 @@ fn main() {
}
#[test]
+ fn match_arm_let_block() {
+ check(
+ r#"
+fn main() {
+ match 2 {
+ bar => bar.$0
+ }
+}
+"#,
+ expect![[r#"
+ sn box Box::new(expr)
+ sn call function(expr)
+ sn const const {}
+ sn dbg dbg!(expr)
+ sn dbgr dbg!(&expr)
+ sn deref *expr
+ sn let let
+ sn letm let mut
+ sn match match expr {}
+ sn ref &expr
+ sn refm &mut expr
+ sn return return expr
+ sn unsafe unsafe {}
+ "#]],
+ );
+ check_edit(
+ "let",
+ r#"
+fn main() {
+ match 2 {
+ bar => bar.$0
+ }
+}
+"#,
+ r#"
+fn main() {
+ match 2 {
+ bar => {
+ let $1 = bar;
+ $0
+}
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
fn option_letelse() {
check_edit(
"lete",
diff --git a/crates/ide-completion/src/context/analysis.rs b/crates/ide-completion/src/context/analysis.rs
index 1c8bc656ca..4b0cc0c7cd 100644
--- a/crates/ide-completion/src/context/analysis.rs
+++ b/crates/ide-completion/src/context/analysis.rs
@@ -1501,7 +1501,7 @@ fn classify_name_ref<'db>(
| SyntaxKind::RECORD_FIELD
)
})
- .and_then(|_| nameref.as_ref()?.syntax().ancestors().find_map(ast::Adt::cast))
+ .and_then(|_| find_node_at_offset::<ast::Adt>(original_file, original_offset))
.and_then(|adt| sema.derive_helpers_in_scope(&adt))
.unwrap_or_default();
Some(PathKind::Attr { attr_ctx: AttrCtx { kind, annotated_item_kind, derive_helpers } })
diff --git a/crates/ide-completion/src/tests/attribute.rs b/crates/ide-completion/src/tests/attribute.rs
index 3701416dfc..131911be91 100644
--- a/crates/ide-completion/src/tests/attribute.rs
+++ b/crates/ide-completion/src/tests/attribute.rs
@@ -68,7 +68,71 @@ pub struct Foo(#[m$0] i32);
kw crate::
kw self::
"#]],
- )
+ );
+ 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(#[$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 diagnostic::do_not_recommend
+ at diagnostic::on_unimplemented
+ at doc = "…"
+ at doc = include_str!("…")
+ 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 unsafe(…)
+ at used
+ at warn(…)
+ md mac
+ kw crate::
+ kw self::
+ "#]],
+ );
}
#[test]
diff --git a/crates/ide-completion/src/tests/expression.rs b/crates/ide-completion/src/tests/expression.rs
index df39591a33..5fef8c44de 100644
--- a/crates/ide-completion/src/tests/expression.rs
+++ b/crates/ide-completion/src/tests/expression.rs
@@ -3268,6 +3268,8 @@ fn foo() {
sn dbg dbg!(expr)
sn dbgr dbg!(&expr)
sn deref *expr
+ sn let let
+ sn letm let mut
sn match match expr {}
sn ref &expr
sn refm &mut expr
diff --git a/crates/ide-completion/src/tests/fn_param.rs b/crates/ide-completion/src/tests/fn_param.rs
index 02cba6b646..d6d73da3f1 100644
--- a/crates/ide-completion/src/tests/fn_param.rs
+++ b/crates/ide-completion/src/tests/fn_param.rs
@@ -293,6 +293,60 @@ fn bar(bar$0) {}
}
#[test]
+fn not_shows_fully_equal_inside_pattern_params() {
+ check(
+ r#"
+fn foo(bar: u32) {}
+fn bar((a, bar$0)) {}
+"#,
+ expect![[r#"
+ kw mut
+ kw ref
+ "#]],
+ )
+}
+
+#[test]
+fn not_shows_locals_inside_pattern_params() {
+ check(
+ r#"
+fn outer() {
+ let foo = 3;
+ {
+ let bar = 3;
+ |($0)| {};
+ let baz = 3;
+ let qux = 3;
+ }
+ let fez = 3;
+}
+"#,
+ expect![[r#"
+ kw mut
+ kw ref
+ "#]],
+ );
+ check(
+ r#"
+fn outer() {
+ let foo = 3;
+ {
+ let bar = 3;
+ fn inner(($0)) {}
+ let baz = 3;
+ let qux = 3;
+ }
+ let fez = 3;
+}
+"#,
+ expect![[r#"
+ kw mut
+ kw ref
+ "#]],
+ );
+}
+
+#[test]
fn completes_for_params_with_attributes() {
check(
r#"
diff --git a/crates/ide-db/src/imports/insert_use.rs b/crates/ide-db/src/imports/insert_use.rs
index f26952fa15..da8525d1fb 100644
--- a/crates/ide-db/src/imports/insert_use.rs
+++ b/crates/ide-db/src/imports/insert_use.rs
@@ -94,7 +94,7 @@ impl ImportScope {
.item_list()
.map(ImportScopeKind::Module)
.map(|kind| ImportScope { kind, required_cfgs });
- } else if let Some(has_attrs) = ast::AnyHasAttrs::cast(syntax) {
+ } else if let Some(has_attrs) = ast::AnyHasAttrs::cast(syntax.clone()) {
if block.is_none()
&& let Some(b) = ast::BlockExpr::cast(has_attrs.syntax().clone())
&& let Some(b) = sema.original_ast_node(b)
@@ -105,11 +105,34 @@ impl ImportScope {
.attrs()
.any(|attr| attr.as_simple_call().is_some_and(|(ident, _)| ident == "cfg"))
{
- if let Some(b) = block {
- return Some(ImportScope {
- kind: ImportScopeKind::Block(b),
- required_cfgs,
+ if let Some(b) = block.clone() {
+ let current_cfgs = has_attrs.attrs().filter(|attr| {
+ attr.as_simple_call().is_some_and(|(ident, _)| ident == "cfg")
});
+
+ let total_cfgs: Vec<_> =
+ required_cfgs.iter().cloned().chain(current_cfgs).collect();
+
+ let parent = syntax.parent();
+ let mut can_merge = false;
+ if let Some(parent) = parent {
+ can_merge = parent.children().filter_map(ast::Use::cast).any(|u| {
+ let u_attrs = u.attrs().filter(|attr| {
+ attr.as_simple_call().is_some_and(|(ident, _)| ident == "cfg")
+ });
+ crate::imports::merge_imports::eq_attrs(
+ u_attrs,
+ total_cfgs.iter().cloned(),
+ )
+ });
+ }
+
+ if !can_merge {
+ return Some(ImportScope {
+ kind: ImportScopeKind::Block(b),
+ required_cfgs,
+ });
+ }
}
required_cfgs.extend(has_attrs.attrs().filter(|attr| {
attr.as_simple_call().is_some_and(|(ident, _)| ident == "cfg")
@@ -546,7 +569,9 @@ fn insert_use_(scope: &ImportScope, use_item: ast::Use, group_imports: bool) {
// skip the curly brace
.skip(l_curly.is_some() as usize)
.take_while(|child| match child {
- NodeOrToken::Node(node) => is_inner_attribute(node.clone()),
+ NodeOrToken::Node(node) => {
+ is_inner_attribute(node.clone()) && ast::Item::cast(node.clone()).is_none()
+ }
NodeOrToken::Token(token) => {
[SyntaxKind::WHITESPACE, SyntaxKind::COMMENT, SyntaxKind::SHEBANG]
.contains(&token.kind())
@@ -667,7 +692,9 @@ fn insert_use_with_editor_(
// skip the curly brace
.skip(l_curly.is_some() as usize)
.take_while(|child| match child {
- NodeOrToken::Node(node) => is_inner_attribute(node.clone()),
+ NodeOrToken::Node(node) => {
+ is_inner_attribute(node.clone()) && ast::Item::cast(node.clone()).is_none()
+ }
NodeOrToken::Token(token) => {
[SyntaxKind::WHITESPACE, SyntaxKind::COMMENT, SyntaxKind::SHEBANG]
.contains(&token.kind())
diff --git a/crates/ide-db/src/imports/insert_use/tests.rs b/crates/ide-db/src/imports/insert_use/tests.rs
index 3350e1c3d2..6c7b97458d 100644
--- a/crates/ide-db/src/imports/insert_use/tests.rs
+++ b/crates/ide-db/src/imports/insert_use/tests.rs
@@ -1438,3 +1438,156 @@ fn check_guess(#[rust_analyzer::rust_fixture] ra_fixture: &str, expected: Import
let file = ImportScope { kind: ImportScopeKind::File(syntax), required_cfgs: vec![] };
assert_eq!(super::guess_granularity_from_scope(&file), expected);
}
+
+#[test]
+fn insert_with_existing_imports_and_cfg_module() {
+ check(
+ "std::fmt",
+ r#"
+use foo::bar;
+
+#[cfg(target_arch = "x86_64")]
+pub mod api;
+"#,
+ r#"
+use std::fmt;
+
+use foo::bar;
+
+#[cfg(target_arch = "x86_64")]
+pub mod api;
+"#,
+ ImportGranularity::Crate,
+ );
+}
+
+#[test]
+fn insert_before_cfg_module() {
+ check(
+ "std::fmt",
+ r#"
+#[cfg(target_arch = "x86_64")]
+pub mod api;
+"#,
+ r#"
+use std::fmt;
+
+#[cfg(target_arch = "x86_64")]
+pub mod api;
+"#,
+ ImportGranularity::Crate,
+ );
+}
+
+fn check_merge(ra_fixture0: &str, ra_fixture1: &str, last: &str, mb: MergeBehavior) {
+ let use0 = ast::SourceFile::parse(ra_fixture0, span::Edition::CURRENT)
+ .tree()
+ .syntax()
+ .descendants()
+ .find_map(ast::Use::cast)
+ .unwrap();
+
+ let use1 = ast::SourceFile::parse(ra_fixture1, span::Edition::CURRENT)
+ .tree()
+ .syntax()
+ .descendants()
+ .find_map(ast::Use::cast)
+ .unwrap();
+
+ let result = try_merge_imports(&use0, &use1, mb);
+ assert_eq!(result.map(|u| u.to_string().trim().to_owned()), Some(last.trim().to_owned()));
+}
+
+#[test]
+fn merge_gated_imports() {
+ check_merge(
+ r#"#[cfg(test)] use foo::bar;"#,
+ r#"#[cfg(test)] use foo::baz;"#,
+ r#"#[cfg(test)] use foo::{bar, baz};"#,
+ MergeBehavior::Crate,
+ );
+}
+
+#[test]
+fn merge_gated_imports_with_different_values() {
+ let use0 = ast::SourceFile::parse(r#"#[cfg(a)] use foo::bar;"#, span::Edition::CURRENT)
+ .tree()
+ .syntax()
+ .descendants()
+ .find_map(ast::Use::cast)
+ .unwrap();
+
+ let use1 = ast::SourceFile::parse(r#"#[cfg(b)] use foo::baz;"#, span::Edition::CURRENT)
+ .tree()
+ .syntax()
+ .descendants()
+ .find_map(ast::Use::cast)
+ .unwrap();
+
+ let result = try_merge_imports(&use0, &use1, MergeBehavior::Crate);
+ assert_eq!(result, None);
+}
+
+#[test]
+fn merge_gated_imports_different_order() {
+ check_merge(
+ r#"#[cfg(a)] #[cfg(b)] use foo::bar;"#,
+ r#"#[cfg(b)] #[cfg(a)] use foo::baz;"#,
+ r#"#[cfg(a)] #[cfg(b)] use foo::{bar, baz};"#,
+ MergeBehavior::Crate,
+ );
+}
+
+#[test]
+fn merge_into_existing_cfg_import() {
+ check(
+ r#"foo::Foo"#,
+ r#"
+#[cfg(target_os = "windows")]
+use bar::Baz;
+
+#[cfg(target_os = "windows")]
+fn buzz() {
+ Foo$0;
+}
+"#,
+ r#"
+#[cfg(target_os = "windows")]
+use bar::Baz;
+#[cfg(target_os = "windows")]
+use foo::Foo;
+
+#[cfg(target_os = "windows")]
+fn buzz() {
+ Foo;
+}
+"#,
+ ImportGranularity::Crate,
+ );
+}
+
+#[test]
+fn reproduce_user_issue_missing_semicolon() {
+ check(
+ "std::fmt",
+ r#"
+use {
+ foo
+}
+
+#[cfg(target_arch = "x86_64")]
+pub mod api;
+"#,
+ r#"
+use std::fmt;
+
+use {
+ foo
+}
+
+#[cfg(target_arch = "x86_64")]
+pub mod api;
+"#,
+ ImportGranularity::Crate,
+ );
+}
diff --git a/crates/ide-db/src/imports/merge_imports.rs b/crates/ide-db/src/imports/merge_imports.rs
index 635ed7368c..3301719f5c 100644
--- a/crates/ide-db/src/imports/merge_imports.rs
+++ b/crates/ide-db/src/imports/merge_imports.rs
@@ -4,7 +4,7 @@ use std::cmp::Ordering;
use itertools::{EitherOrBoth, Itertools};
use parser::T;
use syntax::{
- Direction, SyntaxElement, algo,
+ Direction, SyntaxElement, ToSmolStr, algo,
ast::{
self, AstNode, HasAttrs, HasName, HasVisibility, PathSegmentKind, edit_in_place::Removable,
make,
@@ -691,14 +691,12 @@ pub fn eq_attrs(
attrs0: impl Iterator<Item = ast::Attr>,
attrs1: impl Iterator<Item = ast::Attr>,
) -> bool {
- // FIXME order of attributes should not matter
- let attrs0 = attrs0
- .flat_map(|attr| attr.syntax().descendants_with_tokens())
- .flat_map(|it| it.into_token());
- let attrs1 = attrs1
- .flat_map(|attr| attr.syntax().descendants_with_tokens())
- .flat_map(|it| it.into_token());
- stdx::iter_eq_by(attrs0, attrs1, |tok, tok2| tok.text() == tok2.text())
+ let mut attrs0: Vec<_> = attrs0.map(|attr| attr.syntax().text().to_smolstr()).collect();
+ let mut attrs1: Vec<_> = attrs1.map(|attr| attr.syntax().text().to_smolstr()).collect();
+ attrs0.sort_unstable();
+ attrs1.sort_unstable();
+
+ attrs0 == attrs1
}
fn path_is_self(path: &ast::Path) -> bool {
diff --git a/crates/ide-db/src/path_transform.rs b/crates/ide-db/src/path_transform.rs
index 48305c2082..01a326a0dc 100644
--- a/crates/ide-db/src/path_transform.rs
+++ b/crates/ide-db/src/path_transform.rs
@@ -553,6 +553,39 @@ impl Ctx<'_> {
return None;
}
+ // Similarly, modules cannot be used in pattern position.
+ if matches!(def, hir::ModuleDef::Module(_)) {
+ return None;
+ }
+
+ if matches!(
+ def,
+ hir::ModuleDef::Function(_)
+ | hir::ModuleDef::Trait(_)
+ | hir::ModuleDef::TypeAlias(_)
+ ) {
+ return None;
+ }
+
+ if let hir::ModuleDef::Adt(adt) = def {
+ match adt {
+ hir::Adt::Struct(s)
+ if s.kind(self.source_scope.db) != hir::StructKind::Unit =>
+ {
+ return None;
+ }
+ hir::Adt::Union(_) => return None,
+ hir::Adt::Enum(_) => return None,
+ _ => (),
+ }
+ }
+
+ if let hir::ModuleDef::Variant(v) = def
+ && v.kind(self.source_scope.db) != hir::StructKind::Unit
+ {
+ return None;
+ }
+
let cfg = FindPathConfig {
prefer_no_std: false,
prefer_prelude: true,
@@ -632,3 +665,87 @@ fn find_trait_for_assoc_item(
None
}
+
+#[cfg(test)]
+mod tests {
+ use crate::RootDatabase;
+ use crate::path_transform::PathTransform;
+ use hir::Semantics;
+ use syntax::{AstNode, ast::HasName};
+ use test_fixture::WithFixture;
+ use test_utils::assert_eq_text;
+
+ #[test]
+ fn test_transform_ident_pat() {
+ let (db, file_id) = RootDatabase::with_single_file(
+ r#"
+mod foo {
+ pub struct UnitStruct;
+ pub struct RecordStruct {}
+ pub enum Enum { UnitVariant, RecordVariant {} }
+ pub fn function() {}
+ pub const CONST: i32 = 0;
+ pub static STATIC: i32 = 0;
+ pub type Alias = i32;
+ pub union Union { f: i32 }
+}
+
+mod bar {
+ fn anchor() {}
+}
+
+fn main() {
+ use foo::*;
+ use foo::Enum::*;
+ let UnitStruct = ();
+ let RecordStruct = ();
+ let Enum = ();
+ let UnitVariant = ();
+ let RecordVariant = ();
+ let function = ();
+ let CONST = ();
+ let STATIC = ();
+ let Alias = ();
+ let Union = ();
+}
+"#,
+ );
+ let sema = Semantics::new(&db);
+ let source_file = sema.parse(file_id);
+
+ let function = source_file
+ .syntax()
+ .descendants()
+ .filter_map(syntax::ast::Fn::cast)
+ .find(|it| it.name().unwrap().text() == "main")
+ .unwrap();
+ let source_scope = sema.scope(function.body().unwrap().syntax()).unwrap();
+
+ let anchor = source_file
+ .syntax()
+ .descendants()
+ .filter_map(syntax::ast::Fn::cast)
+ .find(|it| it.name().unwrap().text() == "anchor")
+ .unwrap();
+ let target_scope = sema.scope(anchor.body().unwrap().syntax()).unwrap();
+
+ let transform = PathTransform::generic_transformation(&target_scope, &source_scope);
+ let transformed = transform.apply(function.body().unwrap().syntax());
+
+ let expected = r#"{
+ use crate::foo::*;
+ use crate::foo::Enum::*;
+ let crate::foo::UnitStruct = ();
+ let RecordStruct = ();
+ let Enum = ();
+ let crate::foo::Enum::UnitVariant = ();
+ let RecordVariant = ();
+ let function = ();
+ let crate::foo::CONST = ();
+ let crate::foo::STATIC = ();
+ let Alias = ();
+ let Union = ();
+}"#;
+ assert_eq_text!(expected, &transformed.to_string());
+ }
+}
diff --git a/crates/ide-db/src/search.rs b/crates/ide-db/src/search.rs
index 1d865892a2..4196a13aa3 100644
--- a/crates/ide-db/src/search.rs
+++ b/crates/ide-db/src/search.rs
@@ -1370,7 +1370,7 @@ fn is_name_ref_in_import(name_ref: &ast::NameRef) -> bool {
}
fn is_name_ref_in_test(sema: &Semantics<'_, RootDatabase>, name_ref: &ast::NameRef) -> bool {
- name_ref.syntax().ancestors().any(|node| match ast::Fn::cast(node) {
+ sema.ancestors_with_macros(name_ref.syntax().clone()).any(|node| match ast::Fn::cast(node) {
Some(it) => sema.to_def(&it).is_some_and(|func| func.is_test(sema.db)),
None => false,
})
diff --git a/crates/ide-db/src/ty_filter.rs b/crates/ide-db/src/ty_filter.rs
index 095256d829..b5c43b3b36 100644
--- a/crates/ide-db/src/ty_filter.rs
+++ b/crates/ide-db/src/ty_filter.rs
@@ -19,8 +19,16 @@ pub enum TryEnum {
impl TryEnum {
const ALL: [TryEnum; 2] = [TryEnum::Option, TryEnum::Result];
- /// Returns `Some(..)` if the provided type is an enum that implements `std::ops::Try`.
+ /// Returns `Some(..)` if the provided `ty.strip_references()` is an enum that implements `std::ops::Try`.
pub fn from_ty(sema: &Semantics<'_, RootDatabase>, ty: &hir::Type<'_>) -> Option<TryEnum> {
+ Self::from_ty_without_strip(sema, &ty.strip_references())
+ }
+
+ /// Returns `Some(..)` if the provided type is an enum that implements `std::ops::Try`.
+ pub fn from_ty_without_strip(
+ sema: &Semantics<'_, RootDatabase>,
+ ty: &hir::Type<'_>,
+ ) -> Option<TryEnum> {
let enum_ = match ty.as_adt() {
Some(hir::Adt::Enum(it)) => it,
_ => return None,
diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs
index 38ee097033..102eb91b74 100644
--- a/crates/ide/src/references.rs
+++ b/crates/ide/src/references.rs
@@ -513,26 +513,32 @@ fn test() {
}
#[test]
- fn test_access() {
+ fn exclude_tests_macro_refs() {
check(
r#"
-struct S { f$0: u32 }
+macro_rules! my_macro {
+ ($e:expr) => { $e };
+}
+
+fn foo$0() -> i32 { 42 }
+
+fn bar() {
+ foo();
+}
#[test]
-fn test() {
- let mut x = S { f: 92 };
- x.f = 92;
+fn t2() {
+ my_macro!(foo());
}
"#,
expect![[r#"
- f Field FileId(0) 11..17 11..12
+ foo Function FileId(0) 52..74 55..58
- FileId(0) 61..62 read test
- FileId(0) 76..77 write test
+ FileId(0) 91..94
+ FileId(0) 133..136 test
"#]],
);
}
-
#[test]
fn test_struct_literal_after_space() {
check(
diff --git a/crates/ide/src/signature_help.rs b/crates/ide/src/signature_help.rs
index f86974b4ec..9ab07565e9 100644
--- a/crates/ide/src/signature_help.rs
+++ b/crates/ide/src/signature_help.rs
@@ -1975,8 +1975,8 @@ trait Sub: Super + Super {
fn f() -> impl Sub<$0
"#,
expect![[r#"
- trait Sub<SuperTy = …, SubTy = …>
- ^^^^^^^^^^^ ---------
+ trait Sub<SubTy = …, SuperTy = …>
+ ^^^^^^^^^ -----------
"#]],
);
}
diff --git a/crates/load-cargo/src/lib.rs b/crates/load-cargo/src/lib.rs
index 70a00cf825..654ff4f75b 100644
--- a/crates/load-cargo/src/lib.rs
+++ b/crates/load-cargo/src/lib.rs
@@ -612,6 +612,53 @@ impl ProcMacroExpander for Expander {
Ok(SubResponse::ByteRangeResult { range: range.range.into() })
}
+ SubRequest::SpanSource { file_id, ast_id, start, end, ctx } => {
+ let span = Span {
+ range: TextRange::new(TextSize::from(start), TextSize::from(end)),
+ anchor: SpanAnchor {
+ file_id: span::EditionedFileId::from_raw(file_id),
+ ast_id: span::ErasedFileAstId::from_raw(ast_id),
+ },
+ // SAFETY: We only receive spans from the server. If someone mess up the communication UB can happen,
+ // but that will be their problem.
+ ctx: unsafe { SyntaxContext::from_u32(ctx) },
+ };
+
+ let mut current_span = span;
+ let mut current_ctx = span.ctx;
+
+ while let Some(macro_call_id) = current_ctx.outer_expn(db) {
+ let macro_call_loc = db.lookup_intern_macro_call(macro_call_id.into());
+
+ let call_site_file = macro_call_loc.kind.file_id();
+
+ let resolved = db.resolve_span(current_span);
+
+ current_ctx = macro_call_loc.ctxt;
+ current_span = Span {
+ range: resolved.range,
+ anchor: SpanAnchor {
+ file_id: resolved.file_id.editioned_file_id(db),
+ ast_id: span::ROOT_ERASED_FILE_AST_ID,
+ },
+ ctx: current_ctx,
+ };
+
+ if call_site_file.file_id().is_some() {
+ break;
+ }
+ }
+
+ let resolved = db.resolve_span(current_span);
+
+ Ok(SubResponse::SpanSourceResult {
+ file_id: resolved.file_id.editioned_file_id(db).as_u32(),
+ ast_id: span::ROOT_ERASED_FILE_AST_ID.into_raw(),
+ start: u32::from(resolved.range.start()),
+ end: u32::from(resolved.range.end()),
+ ctx: current_span.ctx.into_u32(),
+ })
+ }
};
match self.0.expand(
subtree.view(),
diff --git a/crates/proc-macro-api/src/bidirectional_protocol/msg.rs b/crates/proc-macro-api/src/bidirectional_protocol/msg.rs
index 3f0422dc5b..10a8d66677 100644
--- a/crates/proc-macro-api/src/bidirectional_protocol/msg.rs
+++ b/crates/proc-macro-api/src/bidirectional_protocol/msg.rs
@@ -21,6 +21,7 @@ pub enum SubRequest {
LocalFilePath { file_id: u32 },
LineColumn { file_id: u32, ast_id: u32, offset: u32 },
ByteRange { file_id: u32, ast_id: u32, start: u32, end: u32 },
+ SpanSource { file_id: u32, ast_id: u32, start: u32, end: u32, ctx: u32 },
}
#[derive(Debug, Serialize, Deserialize)]
@@ -42,6 +43,13 @@ pub enum SubResponse {
ByteRangeResult {
range: Range<usize>,
},
+ SpanSourceResult {
+ file_id: u32,
+ ast_id: u32,
+ start: u32,
+ end: u32,
+ ctx: u32,
+ },
Cancel {
reason: String,
},
diff --git a/crates/proc-macro-srv-cli/src/main_loop.rs b/crates/proc-macro-srv-cli/src/main_loop.rs
index 9be3199a38..2c54b18077 100644
--- a/crates/proc-macro-srv-cli/src/main_loop.rs
+++ b/crates/proc-macro-srv-cli/src/main_loop.rs
@@ -273,6 +273,42 @@ impl proc_macro_srv::ProcMacroClientInterface for ProcMacroClientHandle<'_> {
other => handle_failure(other),
}
}
+
+ fn span_source(
+ &mut self,
+ proc_macro_srv::span::Span { range, anchor, ctx }: proc_macro_srv::span::Span,
+ ) -> proc_macro_srv::span::Span {
+ match self.roundtrip(bidirectional::SubRequest::SpanSource {
+ file_id: anchor.file_id.as_u32(),
+ ast_id: anchor.ast_id.into_raw(),
+ start: range.start().into(),
+ end: range.end().into(),
+ ctx: ctx.into_u32(),
+ }) {
+ Ok(bidirectional::SubResponse::SpanSourceResult {
+ file_id,
+ ast_id,
+ start,
+ end,
+ ctx,
+ }) => {
+ proc_macro_srv::span::Span {
+ range: proc_macro_srv::span::TextRange::new(
+ proc_macro_srv::span::TextSize::new(start),
+ proc_macro_srv::span::TextSize::new(end),
+ ),
+ anchor: proc_macro_srv::span::SpanAnchor {
+ file_id: proc_macro_srv::span::EditionedFileId::from_raw(file_id),
+ ast_id: proc_macro_srv::span::ErasedFileAstId::from_raw(ast_id),
+ },
+ // SAFETY: We only receive spans from the server. If someone mess up the communication UB can happen,
+ // but that will be their problem.
+ ctx: unsafe { proc_macro_srv::span::SyntaxContext::from_u32(ctx) },
+ }
+ }
+ other => handle_failure(other),
+ }
+ }
}
fn handle_expand_ra(
diff --git a/crates/proc-macro-srv/src/lib.rs b/crates/proc-macro-srv/src/lib.rs
index c548dc620a..6b770e440c 100644
--- a/crates/proc-macro-srv/src/lib.rs
+++ b/crates/proc-macro-srv/src/lib.rs
@@ -120,6 +120,7 @@ pub trait ProcMacroClientInterface {
fn line_column(&mut self, span: Span) -> Option<(u32, u32)>;
fn byte_range(&mut self, span: Span) -> Range<usize>;
+ fn span_source(&mut self, span: Span) -> Span;
}
const EXPANDER_STACK_SIZE: usize = 8 * 1024 * 1024;
diff --git a/crates/proc-macro-srv/src/server_impl/rust_analyzer_span.rs b/crates/proc-macro-srv/src/server_impl/rust_analyzer_span.rs
index d85deba757..5eb16c37ac 100644
--- a/crates/proc-macro-srv/src/server_impl/rust_analyzer_span.rs
+++ b/crates/proc-macro-srv/src/server_impl/rust_analyzer_span.rs
@@ -169,7 +169,9 @@ impl server::Server for RaSpanServer<'_> {
None
}
fn span_source(&mut self, span: Self::Span) -> Self::Span {
- // FIXME requires db, returns the top level call site
+ if let Some(ref mut callback) = self.callback {
+ return callback.span_source(span);
+ }
span
}
fn span_byte_range(&mut self, span: Self::Span) -> Range<usize> {
diff --git a/crates/proc-macro-srv/src/tests/utils.rs b/crates/proc-macro-srv/src/tests/utils.rs
index b7c5c4fdd2..28d826d01e 100644
--- a/crates/proc-macro-srv/src/tests/utils.rs
+++ b/crates/proc-macro-srv/src/tests/utils.rs
@@ -142,6 +142,10 @@ impl ProcMacroClientInterface for MockCallback<'_> {
fn byte_range(&mut self, span: Span) -> Range<usize> {
Range { start: span.range.start().into(), end: span.range.end().into() }
}
+
+ fn span_source(&mut self, span: Span) -> Span {
+ span
+ }
}
pub fn assert_expand_with_callback(
diff --git a/crates/profile/src/google_cpu_profiler.rs b/crates/profile/src/google_cpu_profiler.rs
index cae6caeaa6..d77c945f26 100644
--- a/crates/profile/src/google_cpu_profiler.rs
+++ b/crates/profile/src/google_cpu_profiler.rs
@@ -9,7 +9,7 @@ use std::{
#[link(name = "profiler")]
#[allow(non_snake_case)]
-extern "C" {
+unsafe extern "C" {
fn ProfilerStart(fname: *const c_char) -> i32;
fn ProfilerStop();
}
diff --git a/crates/project-model/src/build_dependencies.rs b/crates/project-model/src/build_dependencies.rs
index fedc6944f5..aff5391697 100644
--- a/crates/project-model/src/build_dependencies.rs
+++ b/crates/project-model/src/build_dependencies.rs
@@ -22,8 +22,9 @@ use triomphe::Arc;
use crate::{
CargoConfig, CargoFeatures, CargoWorkspace, InvocationStrategy, ManifestPath, Package, Sysroot,
- TargetKind, cargo_config_file::make_lockfile_copy,
- cargo_workspace::MINIMUM_TOOLCHAIN_VERSION_SUPPORTING_LOCKFILE_PATH, utf8_stdout,
+ TargetKind,
+ cargo_config_file::{LockfileCopy, LockfileUsage, make_lockfile_copy},
+ utf8_stdout,
};
/// Output of the build script and proc-macro building steps for a workspace.
@@ -436,7 +437,7 @@ impl WorkspaceBuildScripts {
current_dir: &AbsPath,
sysroot: &Sysroot,
toolchain: Option<&semver::Version>,
- ) -> io::Result<(Option<temp_dir::TempDir>, Command)> {
+ ) -> io::Result<(Option<LockfileCopy>, Command)> {
match config.run_build_script_command.as_deref() {
Some([program, args @ ..]) => {
let mut cmd = toolchain::command(program, current_dir, &config.extra_env);
@@ -461,17 +462,26 @@ impl WorkspaceBuildScripts {
if let Some(target) = &config.target {
cmd.args(["--target", target]);
}
- let mut temp_dir_guard = None;
- if toolchain
- .is_some_and(|v| *v >= MINIMUM_TOOLCHAIN_VERSION_SUPPORTING_LOCKFILE_PATH)
- {
+ let mut lockfile_copy = None;
+ if let Some(toolchain) = toolchain {
let lockfile_path =
<_ as AsRef<Utf8Path>>::as_ref(manifest_path).with_extension("lock");
- if let Some((temp_dir, target_lockfile)) = make_lockfile_copy(&lockfile_path) {
+ lockfile_copy = make_lockfile_copy(toolchain, &lockfile_path);
+ if let Some(lockfile_copy) = &lockfile_copy {
requires_unstable_options = true;
- temp_dir_guard = Some(temp_dir);
- cmd.arg("--lockfile-path");
- cmd.arg(target_lockfile.as_str());
+ match lockfile_copy.usage {
+ LockfileUsage::WithFlag => {
+ cmd.arg("--lockfile-path");
+ cmd.arg(lockfile_copy.path.as_str());
+ }
+ LockfileUsage::WithEnvVar => {
+ cmd.arg("-Zlockfile-path");
+ cmd.env(
+ "CARGO_RESOLVER_LOCKFILE_PATH",
+ lockfile_copy.path.as_os_str(),
+ );
+ }
+ }
}
}
match &config.features {
@@ -542,7 +552,7 @@ impl WorkspaceBuildScripts {
cmd.env("__CARGO_TEST_CHANNEL_OVERRIDE_DO_NOT_USE_THIS", "nightly");
cmd.arg("-Zunstable-options");
}
- Ok((temp_dir_guard, cmd))
+ Ok((lockfile_copy, cmd))
}
}
}
diff --git a/crates/project-model/src/cargo_config_file.rs b/crates/project-model/src/cargo_config_file.rs
index 5d6e5fd648..ae36deb71f 100644
--- a/crates/project-model/src/cargo_config_file.rs
+++ b/crates/project-model/src/cargo_config_file.rs
@@ -132,25 +132,65 @@ impl<'a> CargoConfigFileReader<'a> {
}
}
+pub(crate) struct LockfileCopy {
+ pub(crate) path: Utf8PathBuf,
+ pub(crate) usage: LockfileUsage,
+ _temp_dir: temp_dir::TempDir,
+}
+
+pub(crate) enum LockfileUsage {
+ /// Rust [1.82.0, 1.95.0). `cargo <subcmd> --lockfile-path <lockfile path>`
+ WithFlag,
+ /// Rust >= 1.95.0. `CARGO_RESOLVER_LOCKFILE_PATH=<lockfile path> cargo <subcmd>`
+ WithEnvVar,
+}
+
pub(crate) fn make_lockfile_copy(
+ toolchain_version: &semver::Version,
lockfile_path: &Utf8Path,
-) -> Option<(temp_dir::TempDir, Utf8PathBuf)> {
+) -> Option<LockfileCopy> {
+ const MINIMUM_TOOLCHAIN_VERSION_SUPPORTING_LOCKFILE_PATH_FLAG: semver::Version =
+ semver::Version {
+ major: 1,
+ minor: 82,
+ patch: 0,
+ pre: semver::Prerelease::EMPTY,
+ build: semver::BuildMetadata::EMPTY,
+ };
+
+ const MINIMUM_TOOLCHAIN_VERSION_SUPPORTING_LOCKFILE_PATH_ENV: semver::Version =
+ semver::Version {
+ major: 1,
+ minor: 95,
+ patch: 0,
+ pre: semver::Prerelease::EMPTY,
+ build: semver::BuildMetadata::EMPTY,
+ };
+
+ let usage = if *toolchain_version >= MINIMUM_TOOLCHAIN_VERSION_SUPPORTING_LOCKFILE_PATH_ENV {
+ LockfileUsage::WithEnvVar
+ } else if *toolchain_version >= MINIMUM_TOOLCHAIN_VERSION_SUPPORTING_LOCKFILE_PATH_FLAG {
+ LockfileUsage::WithFlag
+ } else {
+ return None;
+ };
+
let temp_dir = temp_dir::TempDir::with_prefix("rust-analyzer").ok()?;
- let target_lockfile = temp_dir.path().join("Cargo.lock").try_into().ok()?;
- match std::fs::copy(lockfile_path, &target_lockfile) {
+ let path: Utf8PathBuf = temp_dir.path().join("Cargo.lock").try_into().ok()?;
+ let path = match std::fs::copy(lockfile_path, &path) {
Ok(_) => {
- tracing::debug!("Copied lock file from `{}` to `{}`", lockfile_path, target_lockfile);
- Some((temp_dir, target_lockfile))
+ tracing::debug!("Copied lock file from `{}` to `{}`", lockfile_path, path);
+ path
}
// lockfile does not yet exist, so we can just create a new one in the temp dir
- Err(e) if e.kind() == std::io::ErrorKind::NotFound => Some((temp_dir, target_lockfile)),
+ Err(e) if e.kind() == std::io::ErrorKind::NotFound => path,
Err(e) => {
- tracing::warn!(
- "Failed to copy lock file from `{lockfile_path}` to `{target_lockfile}`: {e}",
- );
- None
+ tracing::warn!("Failed to copy lock file from `{lockfile_path}` to `{path}`: {e}",);
+ return None;
}
- }
+ };
+
+ Some(LockfileCopy { path, usage, _temp_dir: temp_dir })
}
#[test]
diff --git a/crates/project-model/src/cargo_workspace.rs b/crates/project-model/src/cargo_workspace.rs
index 483ab28450..792206b74f 100644
--- a/crates/project-model/src/cargo_workspace.rs
+++ b/crates/project-model/src/cargo_workspace.rs
@@ -16,18 +16,10 @@ use toolchain::{NO_RUSTUP_AUTO_INSTALL_ENV, Tool};
use triomphe::Arc;
use crate::{
- CfgOverrides, InvocationStrategy, ManifestPath, Sysroot, cargo_config_file::make_lockfile_copy,
+ CfgOverrides, InvocationStrategy, ManifestPath, Sysroot,
+ cargo_config_file::{LockfileCopy, LockfileUsage, make_lockfile_copy},
};
-pub(crate) const MINIMUM_TOOLCHAIN_VERSION_SUPPORTING_LOCKFILE_PATH: semver::Version =
- semver::Version {
- major: 1,
- minor: 82,
- patch: 0,
- pre: semver::Prerelease::EMPTY,
- build: semver::BuildMetadata::EMPTY,
- };
-
/// [`CargoWorkspace`] represents the logical structure of, well, a Cargo
/// workspace. It pretty closely mirrors `cargo metadata` output.
///
@@ -628,7 +620,7 @@ pub(crate) struct FetchMetadata {
command: cargo_metadata::MetadataCommand,
#[expect(dead_code)]
manifest_path: ManifestPath,
- lockfile_path: Option<Utf8PathBuf>,
+ lockfile_copy: Option<LockfileCopy>,
#[expect(dead_code)]
kind: &'static str,
no_deps: bool,
@@ -688,15 +680,14 @@ impl FetchMetadata {
}
}
- let mut lockfile_path = None;
+ let mut lockfile_copy = None;
if cargo_toml.is_rust_manifest() {
other_options.push("-Zscript".to_owned());
- } else if config
- .toolchain_version
- .as_ref()
- .is_some_and(|v| *v >= MINIMUM_TOOLCHAIN_VERSION_SUPPORTING_LOCKFILE_PATH)
- {
- lockfile_path = Some(<_ as AsRef<Utf8Path>>::as_ref(cargo_toml).with_extension("lock"));
+ } else if let Some(v) = config.toolchain_version.as_ref() {
+ lockfile_copy = make_lockfile_copy(
+ v,
+ &<_ as AsRef<Utf8Path>>::as_ref(cargo_toml).with_extension("lock"),
+ );
}
if !config.targets.is_empty() {
@@ -729,7 +720,7 @@ impl FetchMetadata {
Self {
manifest_path: cargo_toml.clone(),
command,
- lockfile_path,
+ lockfile_copy,
kind: config.kind,
no_deps,
no_deps_result,
@@ -749,7 +740,7 @@ impl FetchMetadata {
let Self {
mut command,
manifest_path: _,
- lockfile_path,
+ lockfile_copy,
kind: _,
no_deps,
no_deps_result,
@@ -761,13 +752,17 @@ impl FetchMetadata {
}
let mut using_lockfile_copy = false;
- let mut _temp_dir_guard;
- if let Some(lockfile) = lockfile_path
- && let Some((temp_dir, target_lockfile)) = make_lockfile_copy(&lockfile)
- {
- _temp_dir_guard = temp_dir;
- other_options.push("--lockfile-path".to_owned());
- other_options.push(target_lockfile.to_string());
+ if let Some(lockfile_copy) = &lockfile_copy {
+ match lockfile_copy.usage {
+ LockfileUsage::WithFlag => {
+ other_options.push("--lockfile-path".to_owned());
+ other_options.push(lockfile_copy.path.to_string());
+ }
+ LockfileUsage::WithEnvVar => {
+ other_options.push("-Zlockfile-path".to_owned());
+ command.env("CARGO_RESOLVER_LOCKFILE_PATH", lockfile_copy.path.as_os_str());
+ }
+ }
using_lockfile_copy = true;
}
if using_lockfile_copy || other_options.iter().any(|it| it.starts_with("-Z")) {
diff --git a/crates/project-model/src/project_json.rs b/crates/project-model/src/project_json.rs
index 6938010cbd..9b9111012b 100644
--- a/crates/project-model/src/project_json.rs
+++ b/crates/project-model/src/project_json.rs
@@ -391,7 +391,6 @@ struct CrateData {
display_name: Option<String>,
root_module: Utf8PathBuf,
edition: EditionData,
- #[serde(default)]
version: Option<semver::Version>,
deps: Vec<Dep>,
#[serde(default)]
@@ -408,11 +407,8 @@ struct CrateData {
source: Option<CrateSource>,
#[serde(default)]
is_proc_macro: bool,
- #[serde(default)]
repository: Option<String>,
- #[serde(default)]
build: Option<BuildData>,
- #[serde(default)]
proc_macro_cwd: Option<Utf8PathBuf>,
}
diff --git a/crates/rust-analyzer/src/flycheck.rs b/crates/rust-analyzer/src/flycheck.rs
index 47f7a57f72..cdaf944bba 100644
--- a/crates/rust-analyzer/src/flycheck.rs
+++ b/crates/rust-analyzer/src/flycheck.rs
@@ -382,6 +382,7 @@ enum FlycheckCommandOrigin {
ProjectJsonRunnable,
}
+#[derive(Debug)]
enum StateChange {
Restart {
generation: DiagnosticsGeneration,
@@ -435,6 +436,7 @@ enum DiagnosticsReceived {
}
#[allow(clippy::large_enum_variant)]
+#[derive(Debug)]
enum Event {
RequestStateChange(StateChange),
CheckEvent(Option<CheckMessage>),
@@ -445,6 +447,7 @@ const SAVED_FILE_PLACEHOLDER_DOLLAR: &str = "$saved_file";
const LABEL_INLINE: &str = "{label}";
const SAVED_FILE_INLINE: &str = "{saved_file}";
+#[derive(Debug)]
struct Substitutions<'a> {
label: Option<&'a str>,
saved_file: Option<&'a str>,
@@ -556,17 +559,37 @@ impl FlycheckActor {
self.cancel_check_process();
}
Event::RequestStateChange(StateChange::Restart {
- generation,
- scope,
- saved_file,
- target,
+ mut generation,
+ mut scope,
+ mut saved_file,
+ mut target,
}) => {
// Cancel the previously spawned process
self.cancel_check_process();
+
+ // Debounce by briefly waiting for other state changes.
while let Ok(restart) = inbox.recv_timeout(Duration::from_millis(50)) {
- // restart chained with a stop, so just cancel
- if let StateChange::Cancel = restart {
- continue 'event;
+ match restart {
+ StateChange::Cancel => {
+ // We got a cancel straight after this restart request, so
+ // don't do anything.
+ continue 'event;
+ }
+ StateChange::Restart {
+ generation: g,
+ scope: s,
+ saved_file: sf,
+ target: t,
+ } => {
+ // We got another restart request. Take the parameters
+ // from the last restart request in this time window,
+ // because the most recent request is probably the most
+ // relevant to the user.
+ generation = g;
+ scope = s;
+ saved_file = sf;
+ target = t;
+ }
}
}
@@ -950,6 +973,7 @@ impl FlycheckActor {
}
#[allow(clippy::large_enum_variant)]
+#[derive(Debug)]
enum CheckMessage {
/// A message from `cargo check`, including details like the path
/// to the relevant `Cargo.toml`.
diff --git a/crates/rust-analyzer/tests/slow-tests/flycheck.rs b/crates/rust-analyzer/tests/slow-tests/flycheck.rs
new file mode 100644
index 0000000000..c1d53fb33a
--- /dev/null
+++ b/crates/rust-analyzer/tests/slow-tests/flycheck.rs
@@ -0,0 +1,112 @@
+use test_utils::skip_slow_tests;
+
+use crate::support::Project;
+
+#[test]
+fn test_flycheck_diagnostics_for_unused_variable() {
+ if skip_slow_tests() {
+ return;
+ }
+
+ let server = Project::with_fixture(
+ r#"
+//- /Cargo.toml
+[package]
+name = "foo"
+version = "0.0.0"
+
+//- /src/main.rs
+fn main() {
+ let x = 1;
+}
+"#,
+ )
+ .with_config(serde_json::json!({
+ "checkOnSave": true,
+ }))
+ .server()
+ .wait_until_workspace_is_loaded();
+
+ let diagnostics = server.wait_for_diagnostics();
+ assert!(
+ diagnostics.diagnostics.iter().any(|d| d.message.contains("unused variable")),
+ "expected unused variable diagnostic, got: {:?}",
+ diagnostics.diagnostics,
+ );
+}
+
+#[test]
+fn test_flycheck_diagnostic_cleared_after_fix() {
+ if skip_slow_tests() {
+ return;
+ }
+
+ let server = Project::with_fixture(
+ r#"
+//- /Cargo.toml
+[package]
+name = "foo"
+version = "0.0.0"
+
+//- /src/main.rs
+fn main() {
+ let x = 1;
+}
+"#,
+ )
+ .with_config(serde_json::json!({
+ "checkOnSave": true,
+ }))
+ .server()
+ .wait_until_workspace_is_loaded();
+
+ // Wait for the unused variable diagnostic to appear.
+ let diagnostics = server.wait_for_diagnostics();
+ assert!(
+ diagnostics.diagnostics.iter().any(|d| d.message.contains("unused variable")),
+ "expected unused variable diagnostic, got: {:?}",
+ diagnostics.diagnostics,
+ );
+
+ // Fix the code by removing the unused variable.
+ server.write_file_and_save("src/main.rs", "fn main() {}\n".to_owned());
+
+ // Wait for diagnostics to be cleared.
+ server.wait_for_diagnostics_cleared();
+}
+
+#[test]
+fn test_flycheck_diagnostic_with_override_command() {
+ if skip_slow_tests() {
+ return;
+ }
+
+ let server = Project::with_fixture(
+ r#"
+//- /Cargo.toml
+[package]
+name = "foo"
+version = "0.0.0"
+
+//- /src/main.rs
+fn main() {}
+"#,
+ )
+ .with_config(serde_json::json!({
+ "checkOnSave": true,
+ "check": {
+ "overrideCommand": ["rustc", "--error-format=json", "$saved_file"]
+ }
+ }))
+ .server()
+ .wait_until_workspace_is_loaded();
+
+ server.write_file_and_save("src/main.rs", "fn main() {\n let x = 1;\n}\n".to_owned());
+
+ let diagnostics = server.wait_for_diagnostics();
+ assert!(
+ diagnostics.diagnostics.iter().any(|d| d.message.contains("unused variable")),
+ "expected unused variable diagnostic, got: {:?}",
+ diagnostics.diagnostics,
+ );
+}
diff --git a/crates/rust-analyzer/tests/slow-tests/main.rs b/crates/rust-analyzer/tests/slow-tests/main.rs
index b4a7b44d16..26ba549a29 100644
--- a/crates/rust-analyzer/tests/slow-tests/main.rs
+++ b/crates/rust-analyzer/tests/slow-tests/main.rs
@@ -15,6 +15,7 @@
extern crate rustc_driver as _;
mod cli;
+mod flycheck;
mod ratoml;
mod support;
mod testdir;
diff --git a/crates/rust-analyzer/tests/slow-tests/support.rs b/crates/rust-analyzer/tests/slow-tests/support.rs
index 195ad226ae..7ee31f3d53 100644
--- a/crates/rust-analyzer/tests/slow-tests/support.rs
+++ b/crates/rust-analyzer/tests/slow-tests/support.rs
@@ -8,7 +8,9 @@ use std::{
use crossbeam_channel::{Receiver, after, select};
use itertools::Itertools;
use lsp_server::{Connection, Message, Notification, Request};
-use lsp_types::{TextDocumentIdentifier, Url, notification::Exit, request::Shutdown};
+use lsp_types::{
+ PublishDiagnosticsParams, TextDocumentIdentifier, Url, notification::Exit, request::Shutdown,
+};
use parking_lot::{Mutex, MutexGuard};
use paths::{Utf8Path, Utf8PathBuf};
use rust_analyzer::{
@@ -407,6 +409,53 @@ impl Server {
.unwrap_or_else(|Timeout| panic!("timeout while waiting for ws to load"));
self
}
+ pub(crate) fn wait_for_diagnostics(&self) -> PublishDiagnosticsParams {
+ for msg in self.messages.borrow().iter() {
+ if let Message::Notification(n) = msg
+ && n.method == "textDocument/publishDiagnostics"
+ {
+ let params: PublishDiagnosticsParams =
+ serde_json::from_value(n.params.clone()).unwrap();
+ if !params.diagnostics.is_empty() {
+ return params;
+ }
+ }
+ }
+ loop {
+ let msg = self
+ .recv()
+ .unwrap_or_else(|Timeout| panic!("timeout while waiting for diagnostics"))
+ .expect("connection closed while waiting for diagnostics");
+ if let Message::Notification(n) = &msg
+ && n.method == "textDocument/publishDiagnostics"
+ {
+ let params: PublishDiagnosticsParams =
+ serde_json::from_value(n.params.clone()).unwrap();
+ if !params.diagnostics.is_empty() {
+ return params;
+ }
+ }
+ }
+ }
+
+ pub(crate) fn wait_for_diagnostics_cleared(&self) {
+ loop {
+ let msg = self
+ .recv()
+ .unwrap_or_else(|Timeout| panic!("timeout while waiting for diagnostics to clear"))
+ .expect("connection closed while waiting for diagnostics to clear");
+ if let Message::Notification(n) = &msg
+ && n.method == "textDocument/publishDiagnostics"
+ {
+ let params: PublishDiagnosticsParams =
+ serde_json::from_value(n.params.clone()).unwrap();
+ if params.diagnostics.is_empty() {
+ return;
+ }
+ }
+ }
+ }
+
fn wait_for_message_cond(
&self,
n: usize,
diff --git a/docs/book/src/non_cargo_based_projects.md b/docs/book/src/non_cargo_based_projects.md
index f1f10ae336..9cc3292444 100644
--- a/docs/book/src/non_cargo_based_projects.md
+++ b/docs/book/src/non_cargo_based_projects.md
@@ -135,7 +135,7 @@ interface Crate {
cfg_groups?: string[];
/// The set of cfgs activated for a given crate, like
/// `["unix", "feature=\"foo\"", "feature=\"bar\""]`.
- cfg: string[];
+ cfg?: string[];
/// Target tuple for this Crate.
///
/// Used when running `rustc --print cfg`
@@ -143,7 +143,7 @@ interface Crate {
target?: string;
/// Environment variables, used for
/// the `env!` macro
- env: { [key: string]: string; };
+ env?: { [key: string]: string; };
/// Extra crate-level attributes applied to this crate.
///
/// rust-analyzer will behave as if these attributes
@@ -155,7 +155,8 @@ interface Crate {
crate_attrs?: string[];
/// Whether the crate is a proc-macro crate.
- is_proc_macro: boolean;
+ /// Defaults to `false` if unspecified.
+ is_proc_macro?: boolean;
/// For proc-macro crates, path to compiled
/// proc-macro (.so file).
proc_macro_dylib_path?: string;
diff --git a/docs/book/src/vs_code.md b/docs/book/src/vs_code.md
index 75f940e69a..69a96156b8 100644
--- a/docs/book/src/vs_code.md
+++ b/docs/book/src/vs_code.md
@@ -98,12 +98,12 @@ some directories, `/usr/bin` will always be mounted under
system-wide installation of Rust, or any other libraries you might want
to link to. Some compilers and libraries can be acquired as Flatpak
SDKs, such as `org.freedesktop.Sdk.Extension.rust-stable` or
-`org.freedesktop.Sdk.Extension.llvm15`.
+`org.freedesktop.Sdk.Extension.llvm21`.
If you use a Flatpak SDK for Rust, it must be in your `PATH`:
- * install the SDK extensions with `flatpak install org.freedesktop.Sdk.Extension.{llvm15,rust-stable}//23.08`
- * enable SDK extensions in the editor with the environment variable `FLATPAK_ENABLE_SDK_EXT=llvm15,rust-stable` (this can be done using flatseal or `flatpak override`)
+ * install the SDK extensions with `flatpak install org.freedesktop.Sdk.Extension.{llvm21,rust-stable}//25.08`
+ * enable SDK extensions in the editor with the environment variable `FLATPAK_ENABLE_SDK_EXT=llvm21,rust-stable` (this can be done using flatseal or `flatpak override`)
If you want to use Flatpak in combination with `rustup`, the following
steps might help:
diff --git a/editors/code/package-lock.json b/editors/code/package-lock.json
index 84be0a666f..d3fbea2336 100644
--- a/editors/code/package-lock.json
+++ b/editors/code/package-lock.json
@@ -4650,9 +4650,9 @@
}
},
"node_modules/lodash": {
- "version": "4.17.21",
- "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
- "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
+ "version": "4.17.23",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
+ "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==",
"dev": true,
"license": "MIT"
},
diff --git a/rust-version b/rust-version
index b22c6c3869..b6e1b2bc55 100644
--- a/rust-version
+++ b/rust-version
@@ -1 +1 @@
-139651428df86cf88443295542c12ea617cbb587
+c78a29473a68f07012904af11c92ecffa68fcc75