Auto merge of #148944 - theemathas:rm_inherit_overflow, r=joboet
Remove `rustc_inherit_overflow_checks` from `position()` in slice iterators
This method implementation can never cause an overflow, since `i` can never go over the slice's length.
diff --git a/.typos.toml b/.typos.toml
index 9946415..e954b08 100644
--- a/.typos.toml
+++ b/.typos.toml
@@ -33,6 +33,7 @@
thir = "thir"
jod = "jod"
tructure = "tructure"
+taits = "taits"
[default.extend-identifiers]
anc = "anc"
diff --git a/Cargo.lock b/Cargo.lock
index 535833d..12b5f8a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -419,6 +419,22 @@
]
[[package]]
+name = "dhat"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "98cd11d84628e233de0ce467de10b8633f4ddaecafadefc86e13b84b8739b827"
+dependencies = [
+ "backtrace",
+ "lazy_static",
+ "mintex",
+ "parking_lot",
+ "rustc-hash 1.1.0",
+ "serde",
+ "serde_json",
+ "thousands",
+]
+
+[[package]]
name = "dirs"
version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1384,6 +1400,12 @@
]
[[package]]
+name = "mintex"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c505b3e17ed6b70a7ed2e67fbb2c560ee327353556120d6e72f5232b6880d536"
+
+[[package]]
name = "mio"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1452,7 +1474,7 @@
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
dependencies = [
- "windows-sys 0.60.2",
+ "windows-sys 0.61.0",
]
[[package]]
@@ -2011,6 +2033,7 @@
"cargo_metadata 0.21.0",
"cfg",
"crossbeam-channel",
+ "dhat",
"dirs",
"dissimilar",
"expect-test",
@@ -2047,6 +2070,7 @@
"serde",
"serde_derive",
"serde_json",
+ "smallvec",
"stdx",
"syntax",
"syntax-bridge",
@@ -2529,6 +2553,12 @@
]
[[package]]
+name = "thousands"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3bf63baf9f5039dadc247375c29eb13706706cfde997d0330d05aa63a77d8820"
+
+[[package]]
name = "thread_local"
version = "1.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/crates/hir-def/src/item_tree/lower.rs b/crates/hir-def/src/item_tree/lower.rs
index 454e063..db50e65 100644
--- a/crates/hir-def/src/item_tree/lower.rs
+++ b/crates/hir-def/src/item_tree/lower.rs
@@ -370,18 +370,13 @@
});
match &vis {
RawVisibility::Public => RawVisibilityId::PUB,
- RawVisibility::Module(path, explicitness) if path.segments().is_empty() => {
- match (path.kind, explicitness) {
- (PathKind::SELF, VisibilityExplicitness::Explicit) => {
- RawVisibilityId::PRIV_EXPLICIT
- }
- (PathKind::SELF, VisibilityExplicitness::Implicit) => {
- RawVisibilityId::PRIV_IMPLICIT
- }
- (PathKind::Crate, _) => RawVisibilityId::PUB_CRATE,
- _ => RawVisibilityId(self.visibilities.insert_full(vis).0 as u32),
- }
+ RawVisibility::PubSelf(VisibilityExplicitness::Explicit) => {
+ RawVisibilityId::PRIV_EXPLICIT
}
+ RawVisibility::PubSelf(VisibilityExplicitness::Implicit) => {
+ RawVisibilityId::PRIV_IMPLICIT
+ }
+ RawVisibility::PubCrate => RawVisibilityId::PUB_CRATE,
_ => RawVisibilityId(self.visibilities.insert_full(vis).0 as u32),
}
}
@@ -466,10 +461,7 @@
}
fn private_vis() -> RawVisibility {
- RawVisibility::Module(
- Interned::new(ModPath::from_kind(PathKind::SELF)),
- VisibilityExplicitness::Implicit,
- )
+ RawVisibility::PubSelf(VisibilityExplicitness::Implicit)
}
pub(crate) fn visibility_from_ast(
@@ -486,9 +478,11 @@
Some(path) => path,
}
}
- ast::VisibilityKind::PubCrate => ModPath::from_kind(PathKind::Crate),
+ ast::VisibilityKind::PubCrate => return RawVisibility::PubCrate,
ast::VisibilityKind::PubSuper => ModPath::from_kind(PathKind::Super(1)),
- ast::VisibilityKind::PubSelf => ModPath::from_kind(PathKind::SELF),
+ ast::VisibilityKind::PubSelf => {
+ return RawVisibility::PubSelf(VisibilityExplicitness::Explicit);
+ }
ast::VisibilityKind::Pub => return RawVisibility::Public,
};
RawVisibility::Module(Interned::new(path), VisibilityExplicitness::Explicit)
diff --git a/crates/hir-def/src/macro_expansion_tests/mbe/meta_syntax.rs b/crates/hir-def/src/macro_expansion_tests/mbe/meta_syntax.rs
index 2c94f0e..311fdc3 100644
--- a/crates/hir-def/src/macro_expansion_tests/mbe/meta_syntax.rs
+++ b/crates/hir-def/src/macro_expansion_tests/mbe/meta_syntax.rs
@@ -98,7 +98,7 @@
#[test]
fn test_rustc_issue_57597() {
- // <https://github.com/rust-lang/rust/blob/master/tests/ui/issues/issue-57597.rs>
+ // <https://github.com/rust-lang/rust/blob/ec2cc76/tests/ui/macros/issue-57597.rs>
check(
r#"
macro_rules! m0 { ($($($i:ident)?)+) => {}; }
diff --git a/crates/hir-def/src/resolver.rs b/crates/hir-def/src/resolver.rs
index 698292c..abcf0a3 100644
--- a/crates/hir-def/src/resolver.rs
+++ b/crates/hir-def/src/resolver.rs
@@ -1075,7 +1075,9 @@
if let Some(block) = scopes.block(scope) {
let def_map = block_def_map(db, block);
let local_def_map = block.lookup(db).module.only_local_def_map(db);
- r = r.push_block_scope(def_map, local_def_map);
+ // Using `DefMap::ROOT` is okay here since inside modules other than the root,
+ // there can't directly be expressions.
+ r = r.push_block_scope(def_map, local_def_map, DefMap::ROOT);
// FIXME: This adds as many module scopes as there are blocks, but resolving in each
// already traverses all parents, so this is O(n²). I think we could only store the
// innermost module scope instead?
@@ -1108,12 +1110,9 @@
self,
def_map: &'db DefMap,
local_def_map: &'db LocalDefMap,
+ module_id: LocalModuleId,
) -> Resolver<'db> {
- self.push_scope(Scope::BlockScope(ModuleItemMap {
- def_map,
- local_def_map,
- module_id: DefMap::ROOT,
- }))
+ self.push_scope(Scope::BlockScope(ModuleItemMap { def_map, local_def_map, module_id }))
}
fn push_expr_scope(
@@ -1273,7 +1272,7 @@
let (mut def_map, local_def_map) = self.local_def_map(db);
let mut module_id = self.local_id;
- if !self.is_block_module() {
+ if !self.is_within_block() {
return Resolver {
scopes: vec![],
module_scope: ModuleItemMap { def_map, local_def_map, module_id },
@@ -1283,9 +1282,9 @@
let mut modules: SmallVec<[_; 1]> = smallvec![];
while let Some(parent) = def_map.parent() {
let block_def_map = mem::replace(&mut def_map, parent.def_map(db));
- modules.push(block_def_map);
- if !parent.is_block_module() {
- module_id = parent.local_id;
+ let block_module_id = mem::replace(&mut module_id, parent.local_id);
+ modules.push((block_def_map, block_module_id));
+ if !parent.is_within_block() {
break;
}
}
@@ -1293,8 +1292,8 @@
scopes: Vec::with_capacity(modules.len()),
module_scope: ModuleItemMap { def_map, local_def_map, module_id },
};
- for def_map in modules.into_iter().rev() {
- resolver = resolver.push_block_scope(def_map, local_def_map);
+ for (def_map, module_id) in modules.into_iter().rev() {
+ resolver = resolver.push_block_scope(def_map, local_def_map, module_id);
}
resolver
}
diff --git a/crates/hir-def/src/visibility.rs b/crates/hir-def/src/visibility.rs
index b5eb84c..948f6ed 100644
--- a/crates/hir-def/src/visibility.rs
+++ b/crates/hir-def/src/visibility.rs
@@ -289,18 +289,21 @@
pub fn visibility_from_ast(
db: &dyn DefDatabase,
- has_resolver: impl HasResolver,
+ has_resolver: impl HasResolver + HasModule,
ast_vis: InFile<Option<ast::Visibility>>,
) -> Visibility {
let mut span_map = None;
let raw_vis = crate::item_tree::visibility_from_ast(db, ast_vis.value, &mut |range| {
span_map.get_or_insert_with(|| db.span_map(ast_vis.file_id)).span_for_range(range).ctx
});
- if raw_vis == RawVisibility::Public {
- return Visibility::Public;
+ match raw_vis {
+ RawVisibility::PubSelf(explicitness) => {
+ Visibility::Module(has_resolver.module(db), explicitness)
+ }
+ RawVisibility::PubCrate => Visibility::PubCrate(has_resolver.krate(db)),
+ RawVisibility::Public => Visibility::Public,
+ RawVisibility::Module(..) => Visibility::resolve(db, &has_resolver.resolver(db), &raw_vis),
}
-
- Visibility::resolve(db, &has_resolver.resolver(db), &raw_vis)
}
/// Resolve visibility of a type alias.
diff --git a/crates/hir-ty/src/autoderef.rs b/crates/hir-ty/src/autoderef.rs
index 6dd3cdb..392b0b0 100644
--- a/crates/hir-ty/src/autoderef.rs
+++ b/crates/hir-ty/src/autoderef.rs
@@ -38,7 +38,7 @@
env: Arc<TraitEnvironment<'db>>,
ty: Canonical<'db, Ty<'db>>,
) -> impl Iterator<Item = Ty<'db>> + use<'db> {
- let mut table = InferenceTable::new(db, env);
+ let mut table = InferenceTable::new(db, env, None);
let ty = table.instantiate_canonical(ty);
let mut autoderef = Autoderef::new_no_tracking(&mut table, ty);
let mut v = Vec::new();
diff --git a/crates/hir-ty/src/display.rs b/crates/hir-ty/src/display.rs
index dd1b212..0a37966 100644
--- a/crates/hir-ty/src/display.rs
+++ b/crates/hir-ty/src/display.rs
@@ -2078,9 +2078,10 @@
if vis_id == module_id {
// pub(self) or omitted
Ok(())
- } else if root_module_id == vis_id {
+ } else if root_module_id == vis_id && !root_module_id.is_within_block() {
write!(f, "pub(crate) ")
- } else if module_id.containing_module(f.db) == Some(vis_id) {
+ } else if module_id.containing_module(f.db) == Some(vis_id) && !vis_id.is_block_module()
+ {
write!(f, "pub(super) ")
} else {
write!(f, "pub(in ...) ")
diff --git a/crates/hir-ty/src/infer.rs b/crates/hir-ty/src/infer.rs
index 361e665..016edb2 100644
--- a/crates/hir-ty/src/infer.rs
+++ b/crates/hir-ty/src/infer.rs
@@ -21,6 +21,7 @@
mod expr;
mod fallback;
mod mutability;
+mod opaques;
mod pat;
mod path;
pub(crate) mod unify;
@@ -31,8 +32,7 @@
use either::Either;
use hir_def::{
AdtId, AssocItemId, ConstId, DefWithBodyId, FieldId, FunctionId, GenericDefId, GenericParamId,
- ImplId, ItemContainerId, LocalFieldId, Lookup, TraitId, TupleFieldId, TupleId, TypeAliasId,
- VariantId,
+ ItemContainerId, LocalFieldId, Lookup, TraitId, TupleFieldId, TupleId, TypeAliasId, VariantId,
expr_store::{Body, ExpressionStore, HygieneId, path::Path},
hir::{BindingAnnotation, BindingId, ExprId, ExprOrPatId, LabelId, PatId},
lang_item::{LangItem, LangItemTarget, lang_item},
@@ -44,11 +44,11 @@
use hir_expand::{mod_path::ModPath, name::Name};
use indexmap::IndexSet;
use intern::sym;
-use la_arena::{ArenaMap, Entry};
+use la_arena::ArenaMap;
use rustc_ast_ir::Mutability;
use rustc_hash::{FxHashMap, FxHashSet};
use rustc_type_ir::{
- AliasTyKind, Flags, TypeFlags, TypeFoldable, TypeSuperVisitable, TypeVisitable, TypeVisitor,
+ AliasTyKind, TypeFoldable,
inherent::{AdtDef, IntoKind, Region as _, SliceLike, Ty as _},
};
use stdx::never;
@@ -61,7 +61,6 @@
coerce::{CoerceMany, DynamicCoerceMany},
diagnostics::{Diagnostics, InferenceTyLoweringContext as TyLoweringContext},
expr::ExprIsRead,
- unify::InferenceTable,
},
lower::{
ImplTraitIdx, ImplTraitLoweringMode, LifetimeElisionKind, diagnostics::TyLoweringDiagnostic,
@@ -69,10 +68,7 @@
mir::MirSpan,
next_solver::{
AliasTy, Const, DbInterner, ErrorGuaranteed, GenericArg, GenericArgs, Region, Ty, TyKind,
- Tys,
- abi::Safety,
- fold::fold_tys,
- infer::traits::{Obligation, ObligationCause},
+ Tys, abi::Safety, infer::traits::ObligationCause,
},
traits::FnTrait,
utils::TargetFeatureIsSafeInTarget,
@@ -132,6 +128,8 @@
ctx.infer_mut_body();
+ ctx.handle_opaque_type_uses();
+
ctx.type_inference_fallback();
// Comment from rustc:
@@ -148,6 +146,10 @@
ctx.infer_closures();
+ ctx.table.select_obligations_where_possible();
+
+ ctx.handle_opaque_type_uses();
+
Arc::new(ctx.resolve_all())
}
@@ -454,7 +456,7 @@
/// unresolved or missing subpatterns or subpatterns of mismatched types.
pub(crate) type_of_pat: ArenaMap<PatId, Ty<'db>>,
pub(crate) type_of_binding: ArenaMap<BindingId, Ty<'db>>,
- pub(crate) type_of_rpit: ArenaMap<ImplTraitIdx<'db>, Ty<'db>>,
+ pub(crate) type_of_opaque: FxHashMap<InternedOpaqueTyId, Ty<'db>>,
type_mismatches: FxHashMap<ExprOrPatId, TypeMismatch<'db>>,
/// Whether there are any type-mismatching errors in the result.
// FIXME: This isn't as useful as initially thought due to us falling back placeholders to
@@ -499,7 +501,7 @@
type_of_expr: Default::default(),
type_of_pat: Default::default(),
type_of_binding: Default::default(),
- type_of_rpit: Default::default(),
+ type_of_opaque: Default::default(),
type_mismatches: Default::default(),
has_errors: Default::default(),
error_ty,
@@ -640,8 +642,14 @@
// This method is consumed by external tools to run rust-analyzer as a library. Don't remove, please.
pub fn return_position_impl_trait_types(
&self,
+ db: &'db dyn HirDatabase,
) -> impl Iterator<Item = (ImplTraitIdx<'db>, Ty<'db>)> {
- self.type_of_rpit.iter().map(|(k, v)| (k, *v))
+ self.type_of_opaque.iter().filter_map(move |(&id, &ty)| {
+ let ImplTraitId::ReturnTypeImplTrait(_, rpit_idx) = id.loc(db) else {
+ return None;
+ };
+ Some((rpit_idx, ty))
+ })
}
}
@@ -707,6 +715,7 @@
re_static: Region<'db>,
re_error: Region<'db>,
+ re_erased: Region<'db>,
empty_args: GenericArgs<'db>,
empty_tys: Tys<'db>,
@@ -742,6 +751,7 @@
re_static,
re_error: Region::error(interner),
+ re_erased: Region::new_erased(interner),
empty_args: GenericArgs::new_from_iter(interner, []),
empty_tys: Tys::new_from_iter(interner, []),
@@ -848,11 +858,6 @@
}
}
-enum ImplTraitReplacingMode<'db> {
- ReturnPosition(FxHashSet<Ty<'db>>),
- TypeAlias,
-}
-
impl<'body, 'db> InferenceContext<'body, 'db> {
fn new(
db: &'db dyn HirDatabase,
@@ -861,7 +866,7 @@
resolver: Resolver<'db>,
) -> Self {
let trait_env = db.trait_environment_for_body(owner);
- let table = unify::InferenceTable::new(db, trait_env);
+ let table = unify::InferenceTable::new(db, trait_env, Some(owner));
let types = InternedStandardTypes::new(table.interner());
InferenceContext {
result: InferenceResult::new(types.error),
@@ -952,7 +957,7 @@
// `InferenceResult` in the middle of inference. See the fixme comment in `consteval::eval_to_const`. If you
// used this function for another workaround, mention it here. If you really need this function and believe that
// there is no problem in it being `pub(crate)`, remove this comment.
- pub(crate) fn resolve_all(self) -> InferenceResult<'db> {
+ fn resolve_all(self) -> InferenceResult<'db> {
let InferenceContext {
mut table, mut result, tuple_field_accesses_rev, diagnostics, ..
} = self;
@@ -967,7 +972,7 @@
type_of_expr,
type_of_pat,
type_of_binding,
- type_of_rpit,
+ type_of_opaque,
type_mismatches,
has_errors,
error_ty: _,
@@ -999,11 +1004,7 @@
*has_errors = *has_errors || ty.references_non_lt_error();
}
type_of_binding.shrink_to_fit();
- for ty in type_of_rpit.values_mut() {
- *ty = table.resolve_completely(*ty);
- *has_errors = *has_errors || ty.references_non_lt_error();
- }
- type_of_rpit.shrink_to_fit();
+ type_of_opaque.shrink_to_fit();
*has_errors |= !type_mismatches.is_empty();
@@ -1084,9 +1085,6 @@
LifetimeElisionKind::for_const(self.interner(), id.loc(self.db).container),
);
- // Constants might be defining usage sites of TAITs.
- self.make_tait_coercion_table(iter::once(return_ty));
-
self.return_ty = return_ty;
}
@@ -1098,9 +1096,6 @@
LifetimeElisionKind::Elided(self.types.re_static),
);
- // Statics might be defining usage sites of TAITs.
- self.make_tait_coercion_table(iter::once(return_ty));
-
self.return_ty = return_ty;
}
@@ -1138,16 +1133,12 @@
let ty = self.process_user_written_ty(ty);
self.write_binding_ty(self_param, ty);
}
- let mut tait_candidates = FxHashSet::default();
for (ty, pat) in param_tys.zip(&*self.body.params) {
let ty = self.process_user_written_ty(ty);
self.infer_top_pat(*pat, ty, None);
- if ty.flags().intersects(TypeFlags::HAS_TY_OPAQUE.union(TypeFlags::HAS_TY_INFER)) {
- tait_candidates.insert(ty);
- }
}
- let return_ty = match data.ret_type {
+ self.return_ty = match data.ret_type {
Some(return_ty) => {
let return_ty = self.with_ty_lowering(
&data.store,
@@ -1158,45 +1149,12 @@
ctx.lower_ty(return_ty)
},
);
- let return_ty = self.insert_type_vars(return_ty);
- if let Some(rpits) = self.db.return_type_impl_traits(func) {
- let mut mode = ImplTraitReplacingMode::ReturnPosition(FxHashSet::default());
- let result = self.insert_inference_vars_for_impl_trait(return_ty, &mut mode);
- if let ImplTraitReplacingMode::ReturnPosition(taits) = mode {
- tait_candidates.extend(taits);
- }
- let rpits = (*rpits).as_ref().skip_binder();
- for (id, _) in rpits.impl_traits.iter() {
- if let Entry::Vacant(e) = self.result.type_of_rpit.entry(id) {
- never!("Missed RPIT in `insert_inference_vars_for_rpit`");
- e.insert(self.types.error);
- }
- }
- result
- } else {
- return_ty
- }
+ self.process_user_written_ty(return_ty)
}
None => self.types.unit,
};
- self.return_ty = self.process_user_written_ty(return_ty);
self.return_coercion = Some(CoerceMany::new(self.return_ty));
-
- // Functions might be defining usage sites of TAITs.
- // To define an TAITs, that TAIT must appear in the function's signatures.
- // So, it suffices to check for params and return types.
- fold_tys(self.interner(), self.return_ty, |ty| {
- match ty.kind() {
- TyKind::Alias(AliasTyKind::Opaque, _) | TyKind::Infer(..) => {
- tait_candidates.insert(self.return_ty);
- }
- _ => {}
- }
- ty
- });
-
- self.make_tait_coercion_table(tait_candidates.iter().copied());
}
#[inline]
@@ -1204,193 +1162,6 @@
self.table.interner()
}
- fn insert_inference_vars_for_impl_trait<T>(
- &mut self,
- t: T,
- mode: &mut ImplTraitReplacingMode<'db>,
- ) -> T
- where
- T: TypeFoldable<DbInterner<'db>>,
- {
- fold_tys(self.interner(), t, |ty| {
- let ty = self.table.try_structurally_resolve_type(ty);
- let opaque_ty_id = match ty.kind() {
- TyKind::Alias(AliasTyKind::Opaque, alias_ty) => alias_ty.def_id.expect_opaque_ty(),
- _ => return ty,
- };
- let (impl_traits, idx) = match self.db.lookup_intern_impl_trait_id(opaque_ty_id) {
- // We don't replace opaque types from other kind with inference vars
- // because `insert_inference_vars_for_impl_traits` for each kinds
- // and unreplaced opaque types of other kind are resolved while
- // inferencing because of `tait_coercion_table`.
- ImplTraitId::ReturnTypeImplTrait(def, idx) => {
- if matches!(mode, ImplTraitReplacingMode::TypeAlias) {
- // RPITs don't have `tait_coercion_table`, so use inserted inference
- // vars for them.
- if let Some(ty) = self.result.type_of_rpit.get(idx) {
- return *ty;
- }
- return ty;
- }
- (self.db.return_type_impl_traits(def), idx)
- }
- ImplTraitId::TypeAliasImplTrait(def, idx) => {
- if let ImplTraitReplacingMode::ReturnPosition(taits) = mode {
- // Gather TAITs while replacing RPITs because TAITs inside RPITs
- // may not visited while replacing TAITs
- taits.insert(ty);
- return ty;
- }
- (self.db.type_alias_impl_traits(def), idx)
- }
- };
- let Some(impl_traits) = impl_traits else {
- return ty;
- };
- let bounds =
- (*impl_traits).as_ref().map_bound(|its| its.impl_traits[idx].predicates.as_slice());
- let var = match self.result.type_of_rpit.entry(idx) {
- Entry::Occupied(entry) => return *entry.get(),
- Entry::Vacant(entry) => *entry.insert(self.table.next_ty_var()),
- };
- for clause in bounds.iter_identity_copied() {
- let clause = self.insert_inference_vars_for_impl_trait(clause, mode);
- self.table.register_predicate(Obligation::new(
- self.interner(),
- ObligationCause::new(),
- self.table.trait_env.env,
- clause,
- ));
- }
- var
- })
- }
-
- /// The coercion of a non-inference var into an opaque type should fail,
- /// but not in the defining sites of the TAITs.
- /// In such cases, we insert an proxy inference var for each TAIT,
- /// and coerce into it instead of TAIT itself.
- ///
- /// The inference var stretagy is effective because;
- ///
- /// - It can still unify types that coerced into TAITs
- /// - We are pushing `impl Trait` bounds into it
- ///
- /// This function inserts a map that maps the opaque type to that proxy inference var.
- fn make_tait_coercion_table(&mut self, tait_candidates: impl Iterator<Item = Ty<'db>>) {
- struct TypeAliasImplTraitCollector<'a, 'db> {
- db: &'a dyn HirDatabase,
- table: &'a mut InferenceTable<'db>,
- assocs: FxHashMap<InternedOpaqueTyId, (ImplId, Ty<'db>)>,
- non_assocs: FxHashMap<InternedOpaqueTyId, Ty<'db>>,
- }
-
- impl<'db> TypeVisitor<DbInterner<'db>> for TypeAliasImplTraitCollector<'_, 'db> {
- type Result = ();
-
- fn visit_ty(&mut self, ty: Ty<'db>) {
- let ty = self.table.try_structurally_resolve_type(ty);
-
- if let TyKind::Alias(AliasTyKind::Opaque, alias_ty) = ty.kind()
- && let id = alias_ty.def_id.expect_opaque_ty()
- && let ImplTraitId::TypeAliasImplTrait(alias_id, _) =
- self.db.lookup_intern_impl_trait_id(id)
- {
- let loc = self.db.lookup_intern_type_alias(alias_id);
- match loc.container {
- ItemContainerId::ImplId(impl_id) => {
- self.assocs.insert(id, (impl_id, ty));
- }
- ItemContainerId::ModuleId(..) | ItemContainerId::ExternBlockId(..) => {
- self.non_assocs.insert(id, ty);
- }
- _ => {}
- }
- }
-
- ty.super_visit_with(self)
- }
- }
-
- let mut collector = TypeAliasImplTraitCollector {
- db: self.db,
- table: &mut self.table,
- assocs: FxHashMap::default(),
- non_assocs: FxHashMap::default(),
- };
- for ty in tait_candidates {
- ty.visit_with(&mut collector);
- }
-
- // Non-assoc TAITs can be define-used everywhere as long as they are
- // in function signatures or const types, etc
- let mut taits = collector.non_assocs;
-
- // assoc TAITs(ATPITs) can be only define-used inside their impl block.
- // They cannot be define-used in inner items like in the following;
- //
- // ```
- // impl Trait for Struct {
- // type Assoc = impl Default;
- //
- // fn assoc_fn() -> Self::Assoc {
- // let foo: Self::Assoc = true; // Allowed here
- //
- // fn inner() -> Self::Assoc {
- // false // Not allowed here
- // }
- //
- // foo
- // }
- // }
- // ```
- let impl_id = match self.owner {
- DefWithBodyId::FunctionId(it) => {
- let loc = self.db.lookup_intern_function(it);
- if let ItemContainerId::ImplId(impl_id) = loc.container {
- Some(impl_id)
- } else {
- None
- }
- }
- DefWithBodyId::ConstId(it) => {
- let loc = self.db.lookup_intern_const(it);
- if let ItemContainerId::ImplId(impl_id) = loc.container {
- Some(impl_id)
- } else {
- None
- }
- }
- _ => None,
- };
-
- if let Some(impl_id) = impl_id {
- taits.extend(collector.assocs.into_iter().filter_map(|(id, (impl_, ty))| {
- if impl_ == impl_id { Some((id, ty)) } else { None }
- }));
- }
-
- let tait_coercion_table: FxHashMap<_, _> = taits
- .into_iter()
- .filter_map(|(id, ty)| {
- if let ImplTraitId::TypeAliasImplTrait(..) = self.db.lookup_intern_impl_trait_id(id)
- {
- let ty = self.insert_inference_vars_for_impl_trait(
- ty,
- &mut ImplTraitReplacingMode::TypeAlias,
- );
- Some((id, ty))
- } else {
- None
- }
- })
- .collect();
-
- if !tait_coercion_table.is_empty() {
- self.table.tait_coercion_table = Some(tait_coercion_table);
- }
- }
-
fn infer_body(&mut self) {
match self.return_coercion {
Some(_) => self.infer_return(self.body.body_expr),
@@ -2006,12 +1777,15 @@
Some(struct_.into())
}
- fn get_traits_in_scope(&self) -> Either<FxHashSet<TraitId>, &FxHashSet<TraitId>> {
- let mut b_traits = self.resolver.traits_in_scope_from_block_scopes().peekable();
+ fn get_traits_in_scope<'a>(
+ resolver: &Resolver<'db>,
+ traits_in_scope: &'a FxHashSet<TraitId>,
+ ) -> Either<FxHashSet<TraitId>, &'a FxHashSet<TraitId>> {
+ let mut b_traits = resolver.traits_in_scope_from_block_scopes().peekable();
if b_traits.peek().is_some() {
- Either::Left(self.traits_in_scope.iter().copied().chain(b_traits).collect())
+ Either::Left(traits_in_scope.iter().copied().chain(b_traits).collect())
} else {
- Either::Right(&self.traits_in_scope)
+ Either::Right(traits_in_scope)
}
}
}
diff --git a/crates/hir-ty/src/infer/coerce.rs b/crates/hir-ty/src/infer/coerce.rs
index 78889cc..40de923 100644
--- a/crates/hir-ty/src/infer/coerce.rs
+++ b/crates/hir-ty/src/infer/coerce.rs
@@ -60,8 +60,7 @@
next_solver::{
Binder, BoundConst, BoundRegion, BoundRegionKind, BoundTy, BoundTyKind, CallableIdWrapper,
Canonical, ClauseKind, CoercePredicate, Const, ConstKind, DbInterner, ErrorGuaranteed,
- GenericArgs, PolyFnSig, PredicateKind, Region, RegionKind, SolverDefId, TraitRef, Ty,
- TyKind,
+ GenericArgs, PolyFnSig, PredicateKind, Region, RegionKind, TraitRef, Ty, TyKind,
infer::{
InferCtxt, InferOk, InferResult,
relate::RelateResult,
@@ -223,24 +222,6 @@
}
}
- // If we are coercing into a TAIT, coerce into its proxy inference var, instead.
- // FIXME(next-solver): This should not be here. This is not how rustc does thing, and it also not allows us
- // to normalize opaques defined in our scopes. Instead, we should properly register
- // `TypingMode::Analysis::defining_opaque_types_and_generators`, and rely on the solver to reveal
- // them for us (we'll also need some global-like registry for the values, something we cannot
- // really implement, therefore we can really support only RPITs and ITIAT or the new `#[define_opaque]`
- // TAIT, not the old global TAIT).
- let mut b = b;
- if let Some(tait_table) = &self.table.tait_coercion_table
- && let TyKind::Alias(rustc_type_ir::Opaque, opaque_ty) = b.kind()
- && let SolverDefId::InternedOpaqueTyId(opaque_ty_id) = opaque_ty.def_id
- && !matches!(a.kind(), TyKind::Infer(..) | TyKind::Alias(rustc_type_ir::Opaque, _))
- && let Some(ty) = tait_table.get(&opaque_ty_id)
- {
- b = self.table.shallow_resolve(*ty);
- }
- let b = b;
-
// Coercing *from* an unresolved inference variable means that
// we have no information about the source type. This will always
// ultimately fall back to some form of subtyping.
@@ -1528,7 +1509,7 @@
env: Arc<TraitEnvironment<'db>>,
tys: &Canonical<'db, (Ty<'db>, Ty<'db>)>,
) -> Result<(Vec<Adjustment<'db>>, Ty<'db>), TypeError<DbInterner<'db>>> {
- let mut table = InferenceTable::new(db, env);
+ let mut table = InferenceTable::new(db, env, None);
let interner = table.interner();
let ((ty1_with_vars, ty2_with_vars), vars) = table.infer_ctxt.instantiate_canonical(tys);
diff --git a/crates/hir-ty/src/infer/expr.rs b/crates/hir-ty/src/infer/expr.rs
index fd4e374..b7ab109 100644
--- a/crates/hir-ty/src/infer/expr.rs
+++ b/crates/hir-ty/src/infer/expr.rs
@@ -1458,10 +1458,11 @@
) -> Ty<'db> {
let coerce_ty = expected.coercion_target_type(&mut self.table);
let g = self.resolver.update_to_inner_scope(self.db, self.owner, expr);
- let prev_env = block_id.map(|block_id| {
+ let prev_state = block_id.map(|block_id| {
let prev_env = self.table.trait_env.clone();
TraitEnvironment::with_block(&mut self.table.trait_env, block_id);
- prev_env
+ let prev_block = self.table.infer_ctxt.interner.block.replace(block_id);
+ (prev_env, prev_block)
});
let (break_ty, ty) =
@@ -1576,8 +1577,9 @@
}
});
self.resolver.reset_to_guard(g);
- if let Some(prev_env) = prev_env {
+ if let Some((prev_env, prev_block)) = prev_state {
self.table.trait_env = prev_env;
+ self.table.infer_ctxt.interner.block = prev_block;
}
break_ty.unwrap_or(ty)
@@ -1689,10 +1691,11 @@
// work out while people are typing
let canonicalized_receiver = self.canonicalize(receiver_ty);
let resolved = method_resolution::lookup_method(
- self.db,
&canonicalized_receiver,
- self.table.trait_env.clone(),
- self.get_traits_in_scope().as_ref().left_or_else(|&it| it),
+ &mut self.table,
+ Self::get_traits_in_scope(&self.resolver, &self.traits_in_scope)
+ .as_ref()
+ .left_or_else(|&it| it),
VisibleFromModule::Filter(self.resolver.module()),
name,
);
@@ -1844,10 +1847,11 @@
let canonicalized_receiver = self.canonicalize(receiver_ty);
let resolved = method_resolution::lookup_method(
- self.db,
&canonicalized_receiver,
- self.table.trait_env.clone(),
- self.get_traits_in_scope().as_ref().left_or_else(|&it| it),
+ &mut self.table,
+ Self::get_traits_in_scope(&self.resolver, &self.traits_in_scope)
+ .as_ref()
+ .left_or_else(|&it| it),
VisibleFromModule::Filter(self.resolver.module()),
method_name,
);
@@ -1892,9 +1896,10 @@
let assoc_func_with_same_name = method_resolution::iterate_method_candidates(
&canonicalized_receiver,
- self.db,
- self.table.trait_env.clone(),
- self.get_traits_in_scope().as_ref().left_or_else(|&it| it),
+ &mut self.table,
+ Self::get_traits_in_scope(&self.resolver, &self.traits_in_scope)
+ .as_ref()
+ .left_or_else(|&it| it),
VisibleFromModule::Filter(self.resolver.module()),
Some(method_name),
method_resolution::LookupMode::Path,
diff --git a/crates/hir-ty/src/infer/opaques.rs b/crates/hir-ty/src/infer/opaques.rs
new file mode 100644
index 0000000..f7719f5
--- /dev/null
+++ b/crates/hir-ty/src/infer/opaques.rs
@@ -0,0 +1,147 @@
+//! Defining opaque types via inference.
+
+use rustc_type_ir::{TypeVisitableExt, fold_regions};
+use tracing::{debug, instrument};
+
+use crate::{
+ infer::InferenceContext,
+ next_solver::{
+ EarlyBinder, OpaqueTypeKey, SolverDefId, TypingMode,
+ infer::{opaque_types::OpaqueHiddenType, traits::ObligationCause},
+ },
+};
+
+impl<'db> InferenceContext<'_, 'db> {
+ /// This takes all the opaque type uses during HIR typeck. It first computes
+ /// the concrete hidden type by iterating over all defining uses.
+ ///
+ /// A use during HIR typeck is defining if all non-lifetime arguments are
+ /// unique generic parameters and the hidden type does not reference any
+ /// inference variables.
+ ///
+ /// It then uses these defining uses to guide inference for all other uses.
+ #[instrument(level = "debug", skip(self))]
+ pub(super) fn handle_opaque_type_uses(&mut self) {
+ // We clone the opaques instead of stealing them here as they are still used for
+ // normalization in the next generation trait solver.
+ let opaque_types: Vec<_> = self.table.infer_ctxt.clone_opaque_types();
+
+ self.compute_definition_site_hidden_types(opaque_types);
+ }
+}
+
+#[expect(unused, reason = "rustc has this")]
+#[derive(Copy, Clone, Debug)]
+enum UsageKind<'db> {
+ None,
+ NonDefiningUse(OpaqueTypeKey<'db>, OpaqueHiddenType<'db>),
+ UnconstrainedHiddenType(OpaqueHiddenType<'db>),
+ HasDefiningUse(OpaqueHiddenType<'db>),
+}
+
+impl<'db> UsageKind<'db> {
+ fn merge(&mut self, other: UsageKind<'db>) {
+ match (&*self, &other) {
+ (UsageKind::HasDefiningUse(_), _) | (_, UsageKind::None) => unreachable!(),
+ (UsageKind::None, _) => *self = other,
+ // When mergining non-defining uses, prefer earlier ones. This means
+ // the error happens as early as possible.
+ (
+ UsageKind::NonDefiningUse(..) | UsageKind::UnconstrainedHiddenType(..),
+ UsageKind::NonDefiningUse(..),
+ ) => {}
+ // When merging unconstrained hidden types, we prefer later ones. This is
+ // used as in most cases, the defining use is the final return statement
+ // of our function, and other uses with defining arguments are likely not
+ // intended to be defining.
+ (
+ UsageKind::NonDefiningUse(..) | UsageKind::UnconstrainedHiddenType(..),
+ UsageKind::UnconstrainedHiddenType(..) | UsageKind::HasDefiningUse(_),
+ ) => *self = other,
+ }
+ }
+}
+
+impl<'db> InferenceContext<'_, 'db> {
+ fn compute_definition_site_hidden_types(
+ &mut self,
+ mut opaque_types: Vec<(OpaqueTypeKey<'db>, OpaqueHiddenType<'db>)>,
+ ) {
+ for entry in opaque_types.iter_mut() {
+ *entry = self.table.infer_ctxt.resolve_vars_if_possible(*entry);
+ }
+ debug!(?opaque_types);
+
+ let interner = self.interner();
+ let TypingMode::Analysis { defining_opaque_types_and_generators } =
+ self.table.infer_ctxt.typing_mode()
+ else {
+ unreachable!();
+ };
+
+ for def_id in defining_opaque_types_and_generators {
+ let def_id = match def_id {
+ SolverDefId::InternedOpaqueTyId(it) => it,
+ _ => continue,
+ };
+
+ // We do actually need to check this the second pass (we can't just
+ // store this), because we can go from `UnconstrainedHiddenType` to
+ // `HasDefiningUse` (because of fallback)
+ let mut usage_kind = UsageKind::None;
+ for &(opaque_type_key, hidden_type) in &opaque_types {
+ if opaque_type_key.def_id != def_id.into() {
+ continue;
+ }
+
+ usage_kind.merge(self.consider_opaque_type_use(opaque_type_key, hidden_type));
+
+ if let UsageKind::HasDefiningUse(..) = usage_kind {
+ break;
+ }
+ }
+
+ if let UsageKind::HasDefiningUse(ty) = usage_kind {
+ for &(opaque_type_key, hidden_type) in &opaque_types {
+ if opaque_type_key.def_id != def_id.into() {
+ continue;
+ }
+
+ let expected =
+ EarlyBinder::bind(ty.ty).instantiate(interner, opaque_type_key.args);
+ self.demand_eqtype(expected, hidden_type.ty);
+ }
+
+ self.result.type_of_opaque.insert(def_id, ty.ty);
+
+ continue;
+ }
+
+ self.result.type_of_opaque.insert(def_id, self.types.error);
+ }
+ }
+
+ #[tracing::instrument(skip(self), ret)]
+ fn consider_opaque_type_use(
+ &self,
+ opaque_type_key: OpaqueTypeKey<'db>,
+ hidden_type: OpaqueHiddenType<'db>,
+ ) -> UsageKind<'db> {
+ // We ignore uses of the opaque if they have any inference variables
+ // as this can frequently happen with recursive calls.
+ //
+ // See `tests/ui/traits/next-solver/opaques/universal-args-non-defining.rs`.
+ if hidden_type.ty.has_non_region_infer() {
+ return UsageKind::UnconstrainedHiddenType(hidden_type);
+ }
+
+ let cause = ObligationCause::new();
+ let at = self.table.infer_ctxt.at(&cause, self.table.trait_env.env);
+ let hidden_type = match at.deeply_normalize(hidden_type) {
+ Ok(hidden_type) => hidden_type,
+ Err(_errors) => OpaqueHiddenType { ty: self.types.error },
+ };
+ let hidden_type = fold_regions(self.interner(), hidden_type, |_, _| self.types.re_erased);
+ UsageKind::HasDefiningUse(hidden_type)
+ }
+}
diff --git a/crates/hir-ty/src/infer/path.rs b/crates/hir-ty/src/infer/path.rs
index 2dae7cb..9ade842 100644
--- a/crates/hir-ty/src/infer/path.rs
+++ b/crates/hir-ty/src/infer/path.rs
@@ -310,9 +310,10 @@
let mut not_visible = None;
let res = method_resolution::iterate_method_candidates(
&canonical_ty,
- self.db,
- self.table.trait_env.clone(),
- self.get_traits_in_scope().as_ref().left_or_else(|&it| it),
+ &mut self.table,
+ Self::get_traits_in_scope(&self.resolver, &self.traits_in_scope)
+ .as_ref()
+ .left_or_else(|&it| it),
VisibleFromModule::Filter(self.resolver.module()),
Some(name),
method_resolution::LookupMode::Path,
diff --git a/crates/hir-ty/src/infer/unify.rs b/crates/hir-ty/src/infer/unify.rs
index a18cdda..0f582a1 100644
--- a/crates/hir-ty/src/infer/unify.rs
+++ b/crates/hir-ty/src/infer/unify.rs
@@ -2,10 +2,10 @@
use std::fmt;
-use hir_def::{AdtId, GenericParamId, lang_item::LangItem};
+use hir_def::{AdtId, DefWithBodyId, GenericParamId, lang_item::LangItem};
use hir_expand::name::Name;
use intern::sym;
-use rustc_hash::{FxHashMap, FxHashSet};
+use rustc_hash::FxHashSet;
use rustc_type_ir::{
DebruijnIndex, InferConst, InferTy, RegionVid, TyVid, TypeFoldable, TypeFolder,
TypeSuperFoldable, TypeVisitableExt, UpcastFrom,
@@ -17,12 +17,12 @@
use crate::{
TraitEnvironment,
- db::{HirDatabase, InternedOpaqueTyId},
+ db::HirDatabase,
infer::InferenceContext,
next_solver::{
self, AliasTy, Binder, Canonical, ClauseKind, Const, ConstKind, DbInterner,
ErrorGuaranteed, GenericArg, GenericArgs, Predicate, PredicateKind, Region, RegionKind,
- SolverDefId, SolverDefIds, TraitRef, Ty, TyKind, TypingMode,
+ SolverDefId, TraitRef, Ty, TyKind, TypingMode,
fulfill::{FulfillmentCtxt, NextSolverError},
infer::{
DbInternerInferExt, InferCtxt, InferOk, InferResult,
@@ -139,10 +139,7 @@
select: for<'a> fn(&mut ObligationCtxt<'a, 'db>) -> Vec<NextSolverError<'db>>,
) -> bool {
let interner = DbInterner::new_with(db, Some(env.krate), env.block);
- // FIXME(next-solver): I believe this should use `PostAnalysis` (this is only used for IDE things),
- // but this causes some bug because of our incorrect impl of `type_of_opaque_hir_typeck()` for TAIT
- // and async blocks.
- let infcx = interner.infer_ctxt().build(TypingMode::non_body_analysis());
+ let infcx = interner.infer_ctxt().build(TypingMode::PostAnalysis);
let cause = ObligationCause::dummy();
let at = infcx.at(&cause, env.env);
let ((ty1_with_vars, ty2_with_vars), _) = infcx.instantiate_canonical(tys);
@@ -158,7 +155,6 @@
pub(crate) struct InferenceTable<'db> {
pub(crate) db: &'db dyn HirDatabase,
pub(crate) trait_env: Arc<TraitEnvironment<'db>>,
- pub(crate) tait_coercion_table: Option<FxHashMap<InternedOpaqueTyId, Ty<'db>>>,
pub(crate) infer_ctxt: InferCtxt<'db>,
pub(super) fulfillment_cx: FulfillmentCtxt<'db>,
pub(super) diverging_type_vars: FxHashSet<Ty<'db>>,
@@ -170,15 +166,23 @@
}
impl<'db> InferenceTable<'db> {
- pub(crate) fn new(db: &'db dyn HirDatabase, trait_env: Arc<TraitEnvironment<'db>>) -> Self {
+ /// Inside hir-ty you should use this for inference only, and always pass `owner`.
+ /// Outside it, always pass `owner = None`.
+ pub(crate) fn new(
+ db: &'db dyn HirDatabase,
+ trait_env: Arc<TraitEnvironment<'db>>,
+ owner: Option<DefWithBodyId>,
+ ) -> Self {
let interner = DbInterner::new_with(db, Some(trait_env.krate), trait_env.block);
- let infer_ctxt = interner.infer_ctxt().build(rustc_type_ir::TypingMode::Analysis {
- defining_opaque_types_and_generators: SolverDefIds::new_from_iter(interner, []),
- });
+ let typing_mode = match owner {
+ Some(owner) => TypingMode::typeck_for_body(interner, owner.into()),
+ // IDE things wants to reveal opaque types.
+ None => TypingMode::PostAnalysis,
+ };
+ let infer_ctxt = interner.infer_ctxt().build(typing_mode);
InferenceTable {
db,
trait_env,
- tait_coercion_table: None,
fulfillment_cx: FulfillmentCtxt::new(&infer_ctxt),
infer_ctxt,
diverging_type_vars: FxHashSet::default(),
@@ -698,40 +702,7 @@
where
T: TypeFoldable<DbInterner<'db>>,
{
- struct Folder<'a, 'db> {
- table: &'a mut InferenceTable<'db>,
- }
- impl<'db> TypeFolder<DbInterner<'db>> for Folder<'_, 'db> {
- fn cx(&self) -> DbInterner<'db> {
- self.table.interner()
- }
-
- fn fold_ty(&mut self, ty: Ty<'db>) -> Ty<'db> {
- if !ty.references_error() {
- return ty;
- }
-
- if ty.is_ty_error() { self.table.next_ty_var() } else { ty.super_fold_with(self) }
- }
-
- fn fold_const(&mut self, ct: Const<'db>) -> Const<'db> {
- if !ct.references_error() {
- return ct;
- }
-
- if ct.is_ct_error() {
- self.table.next_const_var()
- } else {
- ct.super_fold_with(self)
- }
- }
-
- fn fold_region(&mut self, r: Region<'db>) -> Region<'db> {
- if r.is_error() { self.table.next_region_var() } else { r }
- }
- }
-
- ty.fold_with(&mut Folder { table: self })
+ self.infer_ctxt.insert_type_vars(ty)
}
/// Replaces `Ty::Error` by a new type var, so we can maybe still infer it.
diff --git a/crates/hir-ty/src/lib.rs b/crates/hir-ty/src/lib.rs
index 25579e0..fdacc1d 100644
--- a/crates/hir-ty/src/lib.rs
+++ b/crates/hir-ty/src/lib.rs
@@ -27,9 +27,11 @@
mod inhabitedness;
mod lower;
pub mod next_solver;
+mod opaques;
mod specialization;
mod target_feature;
mod utils;
+mod variance;
pub mod autoderef;
pub mod consteval;
@@ -50,7 +52,6 @@
mod test_db;
#[cfg(test)]
mod tests;
-mod variance;
use std::hash::Hash;
@@ -471,6 +472,7 @@
}
}
+/// To be used from `hir` only.
pub fn callable_sig_from_fn_trait<'db>(
self_ty: Ty<'db>,
trait_env: Arc<TraitEnvironment<'db>>,
@@ -482,7 +484,7 @@
.trait_items(db)
.associated_type_by_name(&Name::new_symbol_root(sym::Output))?;
- let mut table = InferenceTable::new(db, trait_env.clone());
+ let mut table = InferenceTable::new(db, trait_env.clone(), None);
// Register two obligations:
// - Self: FnOnce<?args_ty>
diff --git a/crates/hir-ty/src/method_resolution.rs b/crates/hir-ty/src/method_resolution.rs
index cec6356..1e30897 100644
--- a/crates/hir-ty/src/method_resolution.rs
+++ b/crates/hir-ty/src/method_resolution.rs
@@ -489,9 +489,8 @@
/// Look up the method with the given name.
pub(crate) fn lookup_method<'db>(
- db: &'db dyn HirDatabase,
ty: &Canonical<'db, Ty<'db>>,
- env: Arc<TraitEnvironment<'db>>,
+ table: &mut InferenceTable<'db>,
traits_in_scope: &FxHashSet<TraitId>,
visible_from_module: VisibleFromModule,
name: &Name,
@@ -499,8 +498,7 @@
let mut not_visible = None;
let res = iterate_method_candidates(
ty,
- db,
- env,
+ table,
traits_in_scope,
visible_from_module,
Some(name),
@@ -656,8 +654,7 @@
// FIXME add a context type here?
pub(crate) fn iterate_method_candidates<'db, T>(
ty: &Canonical<'db, Ty<'db>>,
- db: &'db dyn HirDatabase,
- env: Arc<TraitEnvironment<'db>>,
+ table: &mut InferenceTable<'db>,
traits_in_scope: &FxHashSet<TraitId>,
visible_from_module: VisibleFromModule,
name: Option<&Name>,
@@ -665,10 +662,9 @@
mut callback: impl FnMut(ReceiverAdjustments, AssocItemId, bool) -> Option<T>,
) -> Option<T> {
let mut slot = None;
- _ = iterate_method_candidates_dyn(
+ _ = iterate_method_candidates_dyn_impl(
ty,
- db,
- env,
+ table,
traits_in_scope,
visible_from_module,
name,
@@ -985,6 +981,7 @@
is_not_orphan
}
+/// To be used from `hir` only.
pub fn iterate_path_candidates<'db>(
ty: &Canonical<'db, Ty<'db>>,
db: &'db dyn HirDatabase,
@@ -1007,6 +1004,7 @@
)
}
+/// To be used from `hir` only.
pub fn iterate_method_candidates_dyn<'db>(
ty: &Canonical<'db, Ty<'db>>,
db: &'db dyn HirDatabase,
@@ -1017,6 +1015,26 @@
mode: LookupMode,
callback: &mut dyn MethodCandidateCallback,
) -> ControlFlow<()> {
+ iterate_method_candidates_dyn_impl(
+ ty,
+ &mut InferenceTable::new(db, env, None),
+ traits_in_scope,
+ visible_from_module,
+ name,
+ mode,
+ callback,
+ )
+}
+
+fn iterate_method_candidates_dyn_impl<'db>(
+ ty: &Canonical<'db, Ty<'db>>,
+ table: &mut InferenceTable<'db>,
+ traits_in_scope: &FxHashSet<TraitId>,
+ visible_from_module: VisibleFromModule,
+ name: Option<&Name>,
+ mode: LookupMode,
+ callback: &mut dyn MethodCandidateCallback,
+) -> ControlFlow<()> {
let _p = tracing::info_span!(
"iterate_method_candidates_dyn",
?mode,
@@ -1046,28 +1064,28 @@
// the methods by autoderef order of *receiver types*, not *self
// types*.
- let mut table = InferenceTable::new(db, env);
- let ty = table.instantiate_canonical(*ty);
- let deref_chain = autoderef_method_receiver(&mut table, ty);
+ table.run_in_snapshot(|table| {
+ let ty = table.instantiate_canonical(*ty);
+ let deref_chain = autoderef_method_receiver(table, ty);
- deref_chain.into_iter().try_for_each(|(receiver_ty, adj)| {
- iterate_method_candidates_with_autoref(
- &mut table,
- receiver_ty,
- adj,
- traits_in_scope,
- visible_from_module,
- name,
- callback,
- )
+ deref_chain.into_iter().try_for_each(|(receiver_ty, adj)| {
+ iterate_method_candidates_with_autoref(
+ table,
+ receiver_ty,
+ adj,
+ traits_in_scope,
+ visible_from_module,
+ name,
+ callback,
+ )
+ })
})
}
LookupMode::Path => {
// No autoderef for path lookups
iterate_method_candidates_for_self_ty(
ty,
- db,
- env,
+ table,
traits_in_scope,
visible_from_module,
name,
@@ -1250,39 +1268,39 @@
#[tracing::instrument(skip_all, fields(name = ?name))]
fn iterate_method_candidates_for_self_ty<'db>(
self_ty: &Canonical<'db, Ty<'db>>,
- db: &'db dyn HirDatabase,
- env: Arc<TraitEnvironment<'db>>,
+ table: &mut InferenceTable<'db>,
traits_in_scope: &FxHashSet<TraitId>,
visible_from_module: VisibleFromModule,
name: Option<&Name>,
callback: &mut dyn MethodCandidateCallback,
) -> ControlFlow<()> {
- let mut table = InferenceTable::new(db, env);
- let self_ty = table.instantiate_canonical(*self_ty);
- iterate_inherent_methods(
- self_ty,
- &mut table,
- name,
- None,
- None,
- visible_from_module,
- LookupMode::Path,
- &mut |adjustments, item, is_visible| {
- callback.on_inherent_method(adjustments, item, is_visible)
- },
- )?;
- iterate_trait_method_candidates(
- self_ty,
- &mut table,
- traits_in_scope,
- name,
- None,
- None,
- LookupMode::Path,
- &mut |adjustments, item, is_visible| {
- callback.on_trait_method(adjustments, item, is_visible)
- },
- )
+ table.run_in_snapshot(|table| {
+ let self_ty = table.instantiate_canonical(*self_ty);
+ iterate_inherent_methods(
+ self_ty,
+ table,
+ name,
+ None,
+ None,
+ visible_from_module,
+ LookupMode::Path,
+ &mut |adjustments, item, is_visible| {
+ callback.on_inherent_method(adjustments, item, is_visible)
+ },
+ )?;
+ iterate_trait_method_candidates(
+ self_ty,
+ table,
+ traits_in_scope,
+ name,
+ None,
+ None,
+ LookupMode::Path,
+ &mut |adjustments, item, is_visible| {
+ callback.on_trait_method(adjustments, item, is_visible)
+ },
+ )
+ })
}
#[tracing::instrument(skip_all, fields(name = ?name, visible_from_module, receiver_ty))]
diff --git a/crates/hir-ty/src/mir/borrowck.rs b/crates/hir-ty/src/mir/borrowck.rs
index db16c94..0189265 100644
--- a/crates/hir-ty/src/mir/borrowck.rs
+++ b/crates/hir-ty/src/mir/borrowck.rs
@@ -17,7 +17,7 @@
display::DisplayTarget,
mir::OperandKind,
next_solver::{
- DbInterner, GenericArgs, SolverDefIds, Ty, TypingMode,
+ DbInterner, GenericArgs, Ty, TypingMode,
infer::{DbInternerInferExt, InferCtxt},
},
};
@@ -100,11 +100,11 @@
let interner = DbInterner::new_with(db, Some(module.krate()), module.containing_block());
let env = db.trait_environment_for_body(def);
let mut res = vec![];
+ // This calculates opaques defining scope which is a bit costly therefore is put outside `all_mir_bodies()`.
+ let typing_mode = TypingMode::borrowck(interner, def.into());
all_mir_bodies(db, def, |body| {
// FIXME(next-solver): Opaques.
- let infcx = interner.infer_ctxt().build(TypingMode::Borrowck {
- defining_opaque_types: SolverDefIds::new_from_iter(interner, []),
- });
+ let infcx = interner.infer_ctxt().build(typing_mode);
res.push(BorrowckResult {
mutability_of_locals: mutability_of_locals(&infcx, &body),
moved_out_of_ref: moved_out_of_ref(&infcx, &env, &body),
diff --git a/crates/hir-ty/src/next_solver/def_id.rs b/crates/hir-ty/src/next_solver/def_id.rs
index 0ff0b08..77f2106 100644
--- a/crates/hir-ty/src/next_solver/def_id.rs
+++ b/crates/hir-ty/src/next_solver/def_id.rs
@@ -154,6 +154,29 @@
}
}
+impl TryFrom<SolverDefId> for DefWithBodyId {
+ type Error = ();
+
+ #[inline]
+ fn try_from(value: SolverDefId) -> Result<Self, Self::Error> {
+ let id = match value {
+ SolverDefId::ConstId(id) => id.into(),
+ SolverDefId::FunctionId(id) => id.into(),
+ SolverDefId::StaticId(id) => id.into(),
+ SolverDefId::EnumVariantId(id) | SolverDefId::Ctor(Ctor::Enum(id)) => id.into(),
+ SolverDefId::InternedOpaqueTyId(_)
+ | SolverDefId::TraitId(_)
+ | SolverDefId::TypeAliasId(_)
+ | SolverDefId::ImplId(_)
+ | SolverDefId::InternedClosureId(_)
+ | SolverDefId::InternedCoroutineId(_)
+ | SolverDefId::Ctor(Ctor::Struct(_))
+ | SolverDefId::AdtId(_) => return Err(()),
+ };
+ Ok(id)
+ }
+}
+
impl TryFrom<SolverDefId> for GenericDefId {
type Error = ();
diff --git a/crates/hir-ty/src/next_solver/generic_arg.rs b/crates/hir-ty/src/next_solver/generic_arg.rs
index 90bd44a..dedd6a1 100644
--- a/crates/hir-ty/src/next_solver/generic_arg.rs
+++ b/crates/hir-ty/src/next_solver/generic_arg.rs
@@ -63,6 +63,14 @@
}
}
+ #[inline]
+ pub(crate) fn expect_region(self) -> Region<'db> {
+ match self {
+ GenericArg::Lifetime(region) => region,
+ _ => panic!("expected a region, got {self:?}"),
+ }
+ }
+
pub fn error_from_id(interner: DbInterner<'db>, id: GenericParamId) -> GenericArg<'db> {
match id {
GenericParamId::TypeParamId(_) => Ty::new_error(interner, ErrorGuaranteed).into(),
diff --git a/crates/hir-ty/src/next_solver/infer/mod.rs b/crates/hir-ty/src/next_solver/infer/mod.rs
index 36c6c48..7b8f52b 100644
--- a/crates/hir-ty/src/next_solver/infer/mod.rs
+++ b/crates/hir-ty/src/next_solver/infer/mod.rs
@@ -13,27 +13,27 @@
use region_constraints::{RegionConstraintCollector, RegionConstraintStorage};
use rustc_next_trait_solver::solve::SolverDelegateEvalExt;
use rustc_pattern_analysis::Captures;
-use rustc_type_ir::TypeFoldable;
-use rustc_type_ir::error::{ExpectedFound, TypeError};
-use rustc_type_ir::inherent::{
- Const as _, GenericArg as _, GenericArgs as _, IntoKind, SliceLike, Term as _, Ty as _,
-};
use rustc_type_ir::{
ClosureKind, ConstVid, FloatVarValue, FloatVid, GenericArgKind, InferConst, InferTy,
- IntVarValue, IntVid, OutlivesPredicate, RegionVid, TyVid, UniverseIndex,
+ IntVarValue, IntVid, OutlivesPredicate, RegionVid, TermKind, TyVid, TypeFoldable, TypeFolder,
+ TypeSuperFoldable, TypeVisitableExt, UniverseIndex,
+ error::{ExpectedFound, TypeError},
+ inherent::{
+ Const as _, GenericArg as _, GenericArgs as _, IntoKind, SliceLike, Term as _, Ty as _,
+ },
};
-use rustc_type_ir::{TermKind, TypeVisitableExt};
use snapshot::undo_log::InferCtxtUndoLogs;
use tracing::{debug, instrument};
use traits::{ObligationCause, PredicateObligations};
use type_variable::TypeVariableOrigin;
use unify_key::{ConstVariableOrigin, ConstVariableValue, ConstVidKey};
-use crate::next_solver::fold::BoundVarReplacerDelegate;
-use crate::next_solver::infer::select::EvaluationResult;
-use crate::next_solver::infer::traits::PredicateObligation;
-use crate::next_solver::obligation_ctxt::ObligationCtxt;
-use crate::next_solver::{BoundConst, BoundRegion, BoundTy, BoundVarKind, Goal, SolverContext};
+use crate::next_solver::{
+ BoundConst, BoundRegion, BoundTy, BoundVarKind, Goal, SolverContext,
+ fold::BoundVarReplacerDelegate,
+ infer::{select::EvaluationResult, traits::PredicateObligation},
+ obligation_ctxt::ObligationCtxt,
+};
use super::{
AliasTerm, Binder, CanonicalQueryInput, CanonicalVarValues, Const, ConstKind, DbInterner,
@@ -46,7 +46,7 @@
pub mod at;
pub mod canonical;
mod context;
-mod opaque_types;
+pub mod opaque_types;
pub mod region_constraints;
pub mod relate;
pub mod resolve;
@@ -400,6 +400,46 @@
))
}
+ pub(crate) fn insert_type_vars<T>(&self, ty: T) -> T
+ where
+ T: TypeFoldable<DbInterner<'db>>,
+ {
+ struct Folder<'a, 'db> {
+ infcx: &'a InferCtxt<'db>,
+ }
+ impl<'db> TypeFolder<DbInterner<'db>> for Folder<'_, 'db> {
+ fn cx(&self) -> DbInterner<'db> {
+ self.infcx.interner
+ }
+
+ fn fold_ty(&mut self, ty: Ty<'db>) -> Ty<'db> {
+ if !ty.references_error() {
+ return ty;
+ }
+
+ if ty.is_ty_error() { self.infcx.next_ty_var() } else { ty.super_fold_with(self) }
+ }
+
+ fn fold_const(&mut self, ct: Const<'db>) -> Const<'db> {
+ if !ct.references_error() {
+ return ct;
+ }
+
+ if ct.is_ct_error() {
+ self.infcx.next_const_var()
+ } else {
+ ct.super_fold_with(self)
+ }
+ }
+
+ fn fold_region(&mut self, r: Region<'db>) -> Region<'db> {
+ if r.is_error() { self.infcx.next_region_var() } else { r }
+ }
+ }
+
+ ty.fold_with(&mut Folder { infcx: self })
+ }
+
/// Evaluates whether the predicate can be satisfied in the given
/// `ParamEnv`, and returns `false` if not certain. However, this is
/// not entirely accurate if inference variables are involved.
diff --git a/crates/hir-ty/src/next_solver/infer/opaque_types/mod.rs b/crates/hir-ty/src/next_solver/infer/opaque_types/mod.rs
index 06d9984..6b6104b 100644
--- a/crates/hir-ty/src/next_solver/infer/opaque_types/mod.rs
+++ b/crates/hir-ty/src/next_solver/infer/opaque_types/mod.rs
@@ -4,9 +4,11 @@
pub(crate) use table::{OpaqueTypeStorage, OpaqueTypeTable};
+use macros::{TypeFoldable, TypeVisitable};
+
use crate::next_solver::{OpaqueTypeKey, Ty, infer::InferCtxt};
-#[derive(Copy, Clone, Debug)]
+#[derive(Copy, Clone, Debug, TypeVisitable, TypeFoldable)]
pub struct OpaqueHiddenType<'db> {
pub ty: Ty<'db>,
}
diff --git a/crates/hir-ty/src/next_solver/infer/opaque_types/table.rs b/crates/hir-ty/src/next_solver/infer/opaque_types/table.rs
index 0f8b238..00177d2 100644
--- a/crates/hir-ty/src/next_solver/infer/opaque_types/table.rs
+++ b/crates/hir-ty/src/next_solver/infer/opaque_types/table.rs
@@ -122,14 +122,6 @@
}
}
-impl<'db> Drop for OpaqueTypeStorage<'db> {
- fn drop(&mut self) {
- if !self.opaque_types.is_empty() {
- panic!("{:?}", self.opaque_types)
- }
- }
-}
-
pub(crate) struct OpaqueTypeTable<'a, 'db> {
storage: &'a mut OpaqueTypeStorage<'db>,
diff --git a/crates/hir-ty/src/next_solver/interner.rs b/crates/hir-ty/src/next_solver/interner.rs
index c1ccbaf..b18e08b 100644
--- a/crates/hir-ty/src/next_solver/interner.rs
+++ b/crates/hir-ty/src/next_solver/interner.rs
@@ -7,8 +7,8 @@
use base_db::Crate;
use hir_def::{
- AdtId, AttrDefId, BlockId, CallableDefId, EnumVariantId, ItemContainerId, StructId, UnionId,
- VariantId,
+ AdtId, AttrDefId, BlockId, CallableDefId, DefWithBodyId, EnumVariantId, ItemContainerId,
+ StructId, UnionId, VariantId,
lang_item::LangItem,
signatures::{FieldData, FnFlags, ImplFlags, StructFlags, TraitFlags},
};
@@ -29,7 +29,7 @@
use crate::{
FnAbi,
- db::{HirDatabase, InternedCoroutine},
+ db::{HirDatabase, InternedCoroutine, InternedCoroutineId},
method_resolution::{ALL_FLOAT_FPS, ALL_INT_FPS, TyFingerprint},
next_solver::{
AdtIdWrapper, BoundConst, CallableIdWrapper, CanonicalVarKind, ClosureIdWrapper,
@@ -96,7 +96,7 @@
}
};
($name:ident, $ty:ty, nofold) => {
- #[salsa::interned(constructor = new_, debug)]
+ #[salsa::interned(constructor = new_)]
pub struct $name {
#[returns(ref)]
inner_: smallvec::SmallVec<[$ty; 2]>,
@@ -119,6 +119,12 @@
}
}
+ impl<'db> std::fmt::Debug for $name<'db> {
+ fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ self.as_slice().fmt(fmt)
+ }
+ }
+
impl<'db> rustc_type_ir::inherent::SliceLike for $name<'db> {
type Item = $ty;
@@ -1866,9 +1872,42 @@
Binder::bind_with_vars(inner, bound_vars)
}
- fn opaque_types_defined_by(self, _defining_anchor: Self::LocalDefId) -> Self::LocalDefIds {
- // FIXME(next-solver)
- SolverDefIds::new_from_iter(self, [])
+ fn opaque_types_defined_by(self, def_id: Self::LocalDefId) -> Self::LocalDefIds {
+ let Ok(def_id) = DefWithBodyId::try_from(def_id) else {
+ return SolverDefIds::default();
+ };
+ let mut result = Vec::new();
+ crate::opaques::opaque_types_defined_by(self.db, def_id, &mut result);
+ SolverDefIds::new_from_iter(self, result)
+ }
+
+ fn opaque_types_and_coroutines_defined_by(self, def_id: Self::LocalDefId) -> Self::LocalDefIds {
+ let Ok(def_id) = DefWithBodyId::try_from(def_id) else {
+ return SolverDefIds::default();
+ };
+ let mut result = Vec::new();
+
+ crate::opaques::opaque_types_defined_by(self.db, def_id, &mut result);
+
+ // Collect coroutines.
+ let body = self.db.body(def_id);
+ body.exprs().for_each(|(expr_id, expr)| {
+ if matches!(
+ expr,
+ hir_def::hir::Expr::Async { .. }
+ | hir_def::hir::Expr::Closure {
+ closure_kind: hir_def::hir::ClosureKind::Async
+ | hir_def::hir::ClosureKind::Coroutine(_),
+ ..
+ }
+ ) {
+ let coroutine =
+ InternedCoroutineId::new(self.db, InternedCoroutine(def_id, expr_id));
+ result.push(coroutine.into());
+ }
+ });
+
+ SolverDefIds::new_from_iter(self, result)
}
fn alias_has_const_conditions(self, _def_id: Self::DefId) -> bool {
@@ -1913,12 +1952,10 @@
let impl_trait_id = self.db().lookup_intern_impl_trait_id(opaque);
match impl_trait_id {
crate::ImplTraitId::ReturnTypeImplTrait(func, idx) => {
- let infer = self.db().infer(func.into());
- EarlyBinder::bind(infer.type_of_rpit[idx])
+ crate::opaques::rpit_hidden_types(self.db, func)[idx]
}
- crate::ImplTraitId::TypeAliasImplTrait(..) => {
- // FIXME(next-solver)
- EarlyBinder::bind(Ty::new_error(self, ErrorGuaranteed))
+ crate::ImplTraitId::TypeAliasImplTrait(type_alias, idx) => {
+ crate::opaques::tait_hidden_types(self.db, type_alias)[idx]
}
}
}
@@ -1969,13 +2006,6 @@
true
}
- fn opaque_types_and_coroutines_defined_by(
- self,
- _defining_anchor: Self::LocalDefId,
- ) -> Self::LocalDefIds {
- Default::default()
- }
-
type Probe = rustc_type_ir::solve::inspect::Probe<DbInterner<'db>>;
fn mk_probe(self, probe: rustc_type_ir::solve::inspect::Probe<Self>) -> Self::Probe {
probe
diff --git a/crates/hir-ty/src/next_solver/solver.rs b/crates/hir-ty/src/next_solver/solver.rs
index 487d164..7b96b40 100644
--- a/crates/hir-ty/src/next_solver/solver.rs
+++ b/crates/hir-ty/src/next_solver/solver.rs
@@ -2,17 +2,22 @@
use hir_def::{AssocItemId, GeneralConstId};
use rustc_next_trait_solver::delegate::SolverDelegate;
-use rustc_type_ir::GenericArgKind;
-use rustc_type_ir::lang_items::SolverTraitLangItem;
use rustc_type_ir::{
- InferCtxtLike, Interner, PredicatePolarity, TypeFlags, TypeVisitableExt,
- inherent::{IntoKind, Term as _, Ty as _},
+ AliasTyKind, GenericArgKind, InferCtxtLike, Interner, PredicatePolarity, TypeFlags,
+ TypeVisitableExt,
+ inherent::{IntoKind, SliceLike, Term as _, Ty as _},
+ lang_items::SolverTraitLangItem,
solve::{Certainty, NoSolution},
};
+use tracing::debug;
-use crate::next_solver::{CanonicalVarKind, ImplIdWrapper};
-use crate::next_solver::{
- ClauseKind, CoercePredicate, PredicateKind, SubtypePredicate, util::sizedness_fast_path,
+use crate::{
+ ImplTraitId,
+ next_solver::{
+ AliasTy, CanonicalVarKind, Clause, ClauseKind, CoercePredicate, GenericArgs, ImplIdWrapper,
+ ParamEnv, Predicate, PredicateKind, SubtypePredicate, Ty, TyKind, fold::fold_tys,
+ util::sizedness_fast_path,
+ },
};
use super::{
@@ -76,7 +81,7 @@
fn well_formed_goals(
&self,
- _param_env: <Self::Interner as rustc_type_ir::Interner>::ParamEnv,
+ _param_env: ParamEnv<'db>,
_arg: <Self::Interner as rustc_type_ir::Interner>::Term,
) -> Option<
Vec<
@@ -125,18 +130,60 @@
fn add_item_bounds_for_hidden_type(
&self,
- _def_id: <Self::Interner as rustc_type_ir::Interner>::DefId,
- _args: <Self::Interner as rustc_type_ir::Interner>::GenericArgs,
- _param_env: <Self::Interner as rustc_type_ir::Interner>::ParamEnv,
- _hidden_ty: <Self::Interner as rustc_type_ir::Interner>::Ty,
- _goals: &mut Vec<
- rustc_type_ir::solve::Goal<
- Self::Interner,
- <Self::Interner as rustc_type_ir::Interner>::Predicate,
- >,
- >,
+ def_id: SolverDefId,
+ args: GenericArgs<'db>,
+ param_env: ParamEnv<'db>,
+ hidden_ty: Ty<'db>,
+ goals: &mut Vec<Goal<'db, Predicate<'db>>>,
) {
- unimplemented!()
+ let interner = self.interner;
+ let opaque_id = def_id.expect_opaque_ty();
+ // Require that the hidden type is well-formed. We have to
+ // make sure we wf-check the hidden type to fix #114728.
+ //
+ // However, we don't check that all types are well-formed.
+ // We only do so for types provided by the user or if they are
+ // "used", e.g. for method selection.
+ //
+ // This means we never check the wf requirements of the hidden
+ // type during MIR borrowck, causing us to infer the wrong
+ // lifetime for its member constraints which then results in
+ // unexpected region errors.
+ goals.push(Goal::new(interner, param_env, ClauseKind::WellFormed(hidden_ty.into())));
+
+ let replace_opaques_in = |clause: Clause<'db>| {
+ fold_tys(interner, clause, |ty| match ty.kind() {
+ // Replace all other mentions of the same opaque type with the hidden type,
+ // as the bounds must hold on the hidden type after all.
+ TyKind::Alias(
+ AliasTyKind::Opaque,
+ AliasTy { def_id: def_id2, args: args2, .. },
+ ) if def_id == def_id2 && args == args2 => hidden_ty,
+ _ => ty,
+ })
+ };
+
+ let db = interner.db;
+ let (opaques_table, opaque_idx) = match opaque_id.loc(db) {
+ ImplTraitId::ReturnTypeImplTrait(func, opaque_idx) => {
+ (db.return_type_impl_traits(func), opaque_idx)
+ }
+ ImplTraitId::TypeAliasImplTrait(type_alias, opaque_idx) => {
+ (db.type_alias_impl_traits(type_alias), opaque_idx)
+ }
+ };
+ let item_bounds = opaques_table
+ .as_deref()
+ .unwrap()
+ .as_ref()
+ .map_bound(|table| &table.impl_traits[opaque_idx].predicates);
+ for predicate in item_bounds.iter_instantiated_copied(interner, args.as_slice()) {
+ let predicate = replace_opaques_in(predicate);
+
+ // Require that the predicate holds for the concrete type.
+ debug!(?predicate);
+ goals.push(Goal::new(interner, param_env, predicate));
+ }
}
fn fetch_eligible_assoc_item(
@@ -190,8 +237,8 @@
fn is_transmutable(
&self,
- _dst: <Self::Interner as rustc_type_ir::Interner>::Ty,
- _src: <Self::Interner as rustc_type_ir::Interner>::Ty,
+ _dst: Ty<'db>,
+ _src: Ty<'db>,
_assume: <Self::Interner as rustc_type_ir::Interner>::Const,
) -> Result<Certainty, NoSolution> {
unimplemented!()
@@ -199,7 +246,7 @@
fn evaluate_const(
&self,
- _param_env: <Self::Interner as rustc_type_ir::Interner>::ParamEnv,
+ _param_env: ParamEnv<'db>,
uv: rustc_type_ir::UnevaluatedConst<Self::Interner>,
) -> Option<<Self::Interner as rustc_type_ir::Interner>::Const> {
let c = match uv.def {
diff --git a/crates/hir-ty/src/opaques.rs b/crates/hir-ty/src/opaques.rs
new file mode 100644
index 0000000..8531f24
--- /dev/null
+++ b/crates/hir-ty/src/opaques.rs
@@ -0,0 +1,199 @@
+//! Handling of opaque types, detection of defining scope and hidden type.
+
+use hir_def::{
+ AssocItemId, AssocItemLoc, DefWithBodyId, FunctionId, HasModule, ItemContainerId, TypeAliasId,
+};
+use hir_expand::name::Name;
+use la_arena::ArenaMap;
+use rustc_type_ir::inherent::Ty as _;
+use syntax::ast;
+use triomphe::Arc;
+
+use crate::{
+ ImplTraitId,
+ db::{HirDatabase, InternedOpaqueTyId},
+ lower::{ImplTraitIdx, ImplTraits},
+ next_solver::{
+ DbInterner, EarlyBinder, ErrorGuaranteed, SolverDefId, Ty, TypingMode,
+ infer::{DbInternerInferExt, traits::ObligationCause},
+ obligation_ctxt::ObligationCtxt,
+ },
+};
+
+pub(crate) fn opaque_types_defined_by(
+ db: &dyn HirDatabase,
+ def_id: DefWithBodyId,
+ result: &mut Vec<SolverDefId>,
+) {
+ if let DefWithBodyId::FunctionId(func) = def_id {
+ // A function may define its own RPITs.
+ extend_with_opaques(
+ db,
+ db.return_type_impl_traits(func),
+ |opaque_idx| ImplTraitId::ReturnTypeImplTrait(func, opaque_idx),
+ result,
+ );
+ }
+
+ let extend_with_taits = |type_alias| {
+ extend_with_opaques(
+ db,
+ db.type_alias_impl_traits(type_alias),
+ |opaque_idx| ImplTraitId::TypeAliasImplTrait(type_alias, opaque_idx),
+ result,
+ );
+ };
+
+ // Collect opaques from assoc items.
+ let extend_with_atpit_from_assoc_items = |assoc_items: &[(Name, AssocItemId)]| {
+ assoc_items
+ .iter()
+ .filter_map(|&(_, assoc_id)| match assoc_id {
+ AssocItemId::TypeAliasId(it) => Some(it),
+ AssocItemId::FunctionId(_) | AssocItemId::ConstId(_) => None,
+ })
+ .for_each(extend_with_taits);
+ };
+ let extend_with_atpit_from_container = |container| match container {
+ ItemContainerId::ImplId(impl_id) => {
+ if db.impl_signature(impl_id).target_trait.is_some() {
+ extend_with_atpit_from_assoc_items(&impl_id.impl_items(db).items);
+ }
+ }
+ ItemContainerId::TraitId(trait_id) => {
+ extend_with_atpit_from_assoc_items(&trait_id.trait_items(db).items);
+ }
+ _ => {}
+ };
+ match def_id {
+ DefWithBodyId::ConstId(id) => extend_with_atpit_from_container(id.loc(db).container),
+ DefWithBodyId::FunctionId(id) => extend_with_atpit_from_container(id.loc(db).container),
+ DefWithBodyId::StaticId(_) | DefWithBodyId::VariantId(_) => {}
+ }
+
+ // FIXME: Collect opaques from `#[define_opaque]`.
+
+ fn extend_with_opaques<'db>(
+ db: &'db dyn HirDatabase,
+ opaques: Option<Arc<EarlyBinder<'db, ImplTraits<'db>>>>,
+ mut make_impl_trait: impl FnMut(ImplTraitIdx<'db>) -> ImplTraitId<'db>,
+ result: &mut Vec<SolverDefId>,
+ ) {
+ if let Some(opaques) = opaques {
+ for (opaque_idx, _) in (*opaques).as_ref().skip_binder().impl_traits.iter() {
+ let opaque_id = InternedOpaqueTyId::new(db, make_impl_trait(opaque_idx));
+ result.push(opaque_id.into());
+ }
+ }
+ }
+}
+
+// These are firewall queries to prevent drawing dependencies between infers:
+
+#[salsa::tracked(returns(ref), unsafe(non_update_return_type))]
+pub(crate) fn rpit_hidden_types<'db>(
+ db: &'db dyn HirDatabase,
+ function: FunctionId,
+) -> ArenaMap<ImplTraitIdx<'db>, EarlyBinder<'db, Ty<'db>>> {
+ let infer = db.infer(function.into());
+ let mut result = ArenaMap::new();
+ for (opaque, hidden_type) in infer.return_position_impl_trait_types(db) {
+ result.insert(opaque, EarlyBinder::bind(hidden_type));
+ }
+ result.shrink_to_fit();
+ result
+}
+
+#[salsa::tracked(returns(ref), unsafe(non_update_return_type))]
+pub(crate) fn tait_hidden_types<'db>(
+ db: &'db dyn HirDatabase,
+ type_alias: TypeAliasId,
+) -> ArenaMap<ImplTraitIdx<'db>, EarlyBinder<'db, Ty<'db>>> {
+ let loc = type_alias.loc(db);
+ let module = loc.module(db);
+ let interner = DbInterner::new_with(db, Some(module.krate()), module.containing_block());
+ let infcx = interner.infer_ctxt().build(TypingMode::non_body_analysis());
+ let mut ocx = ObligationCtxt::new(&infcx);
+ let cause = ObligationCause::dummy();
+ let param_env = db.trait_environment(type_alias.into()).env;
+
+ let defining_bodies = tait_defining_bodies(db, &loc);
+
+ let taits_count = db
+ .type_alias_impl_traits(type_alias)
+ .map_or(0, |taits| (*taits).as_ref().skip_binder().impl_traits.len());
+
+ let mut result = ArenaMap::with_capacity(taits_count);
+ for defining_body in defining_bodies {
+ let infer = db.infer(defining_body);
+ for (&opaque, &hidden_type) in &infer.type_of_opaque {
+ let ImplTraitId::TypeAliasImplTrait(opaque_owner, opaque_idx) = opaque.loc(db) else {
+ continue;
+ };
+ if opaque_owner != type_alias {
+ continue;
+ }
+ // In the presence of errors, we attempt to create a unified type from all
+ // types. rustc doesn't do that, but this should improve the experience.
+ let hidden_type = infcx.insert_type_vars(hidden_type);
+ match result.entry(opaque_idx) {
+ la_arena::Entry::Vacant(entry) => {
+ entry.insert(EarlyBinder::bind(hidden_type));
+ }
+ la_arena::Entry::Occupied(entry) => {
+ _ = ocx.eq(&cause, param_env, entry.get().instantiate_identity(), hidden_type);
+ }
+ }
+ }
+ }
+
+ _ = ocx.try_evaluate_obligations();
+
+ // Fill missing entries.
+ for idx in 0..taits_count {
+ let idx = la_arena::Idx::from_raw(la_arena::RawIdx::from_u32(idx as u32));
+ match result.entry(idx) {
+ la_arena::Entry::Vacant(entry) => {
+ entry.insert(EarlyBinder::bind(Ty::new_error(interner, ErrorGuaranteed)));
+ }
+ la_arena::Entry::Occupied(mut entry) => {
+ *entry.get_mut() = entry.get().map_bound(|hidden_type| {
+ infcx.resolve_vars_if_possible(hidden_type).replace_infer_with_error(interner)
+ });
+ }
+ }
+ }
+
+ result
+}
+
+fn tait_defining_bodies(
+ db: &dyn HirDatabase,
+ loc: &AssocItemLoc<ast::TypeAlias>,
+) -> Vec<DefWithBodyId> {
+ let from_assoc_items = |assoc_items: &[(Name, AssocItemId)]| {
+ // Associated Type Position Impl Trait.
+ assoc_items
+ .iter()
+ .filter_map(|&(_, assoc_id)| match assoc_id {
+ AssocItemId::FunctionId(it) => Some(it.into()),
+ AssocItemId::ConstId(it) => Some(it.into()),
+ AssocItemId::TypeAliasId(_) => None,
+ })
+ .collect()
+ };
+ match loc.container {
+ ItemContainerId::ImplId(impl_id) => {
+ if db.impl_signature(impl_id).target_trait.is_some() {
+ return from_assoc_items(&impl_id.impl_items(db).items);
+ }
+ }
+ ItemContainerId::TraitId(trait_id) => {
+ return from_assoc_items(&trait_id.trait_items(db).items);
+ }
+ _ => {}
+ }
+
+ // FIXME: Support general TAITs, or decisively decide not to.
+ Vec::new()
+}
diff --git a/crates/hir-ty/src/specialization.rs b/crates/hir-ty/src/specialization.rs
index 611947b..f4ee4de 100644
--- a/crates/hir-ty/src/specialization.rs
+++ b/crates/hir-ty/src/specialization.rs
@@ -20,7 +20,7 @@
// and indeed I was unable to cause cycles even with erroneous code. However, in r-a we can
// create a cycle if there is an error in the impl's where clauses. I believe well formed code
// cannot create a cycle, but a cycle handler is required nevertheless.
-fn specializes_cycle(
+fn specializes_query_cycle(
_db: &dyn HirDatabase,
_specializing_impl_def_id: ImplId,
_parent_impl_def_id: ImplId,
@@ -39,31 +39,14 @@
/// `parent_impl_def_id` is a const impl (conditionally based off of some `[const]`
/// bounds), then `specializing_impl_def_id` must also be const for the same
/// set of types.
-#[salsa::tracked(cycle_result = specializes_cycle)]
-pub(crate) fn specializes(
+#[salsa::tracked(cycle_result = specializes_query_cycle)]
+fn specializes_query(
db: &dyn HirDatabase,
specializing_impl_def_id: ImplId,
parent_impl_def_id: ImplId,
) -> bool {
- let module = specializing_impl_def_id.loc(db).container;
-
- // We check that the specializing impl comes from a crate that has specialization enabled.
- //
- // We don't really care if the specialized impl (the parent) is in a crate that has
- // specialization enabled, since it's not being specialized.
- //
- // rustc also checks whether the specializing impls comes from a macro marked
- // `#[allow_internal_unstable(specialization)]`, but `#[allow_internal_unstable]`
- // is an internal feature, std is not using it for specialization nor is likely to
- // ever use it, and we don't have the span information necessary to replicate that.
- let def_map = crate_def_map(db, module.krate());
- if !def_map.is_unstable_feature_enabled(&sym::specialization)
- && !def_map.is_unstable_feature_enabled(&sym::min_specialization)
- {
- return false;
- }
-
- let interner = DbInterner::new_with(db, Some(module.krate()), module.containing_block());
+ let trait_env = db.trait_environment(specializing_impl_def_id.into());
+ let interner = DbInterner::new_with(db, Some(trait_env.krate), trait_env.block);
let specializing_impl_signature = db.impl_signature(specializing_impl_def_id);
let parent_impl_signature = db.impl_signature(parent_impl_def_id);
@@ -87,7 +70,7 @@
// create a parameter environment corresponding to an identity instantiation of the specializing impl,
// i.e. the most generic instantiation of the specializing impl.
- let param_env = db.trait_environment(specializing_impl_def_id.into()).env;
+ let param_env = trait_env.env;
// Create an infcx, taking the predicates of the specializing impl as assumptions:
let infcx = interner.infer_ctxt().build(TypingMode::non_body_analysis());
@@ -148,3 +131,31 @@
true
}
+
+// This function is used to avoid creating the query for crates that does not define `#![feature(specialization)]`,
+// as the solver is calling this a lot, and creating the query consumes a lot of memory.
+pub(crate) fn specializes(
+ db: &dyn HirDatabase,
+ specializing_impl_def_id: ImplId,
+ parent_impl_def_id: ImplId,
+) -> bool {
+ let module = specializing_impl_def_id.loc(db).container;
+
+ // We check that the specializing impl comes from a crate that has specialization enabled.
+ //
+ // We don't really care if the specialized impl (the parent) is in a crate that has
+ // specialization enabled, since it's not being specialized.
+ //
+ // rustc also checks whether the specializing impls comes from a macro marked
+ // `#[allow_internal_unstable(specialization)]`, but `#[allow_internal_unstable]`
+ // is an internal feature, std is not using it for specialization nor is likely to
+ // ever use it, and we don't have the span information necessary to replicate that.
+ let def_map = crate_def_map(db, module.krate());
+ if !def_map.is_unstable_feature_enabled(&sym::specialization)
+ && !def_map.is_unstable_feature_enabled(&sym::min_specialization)
+ {
+ return false;
+ }
+
+ specializes_query(db, specializing_impl_def_id, parent_impl_def_id)
+}
diff --git a/crates/hir-ty/src/tests/incremental.rs b/crates/hir-ty/src/tests/incremental.rs
index bc47019..14ec161 100644
--- a/crates/hir-ty/src/tests/incremental.rs
+++ b/crates/hir-ty/src/tests/incremental.rs
@@ -591,6 +591,7 @@
"function_signature_shim",
"function_signature_with_source_map_shim",
"trait_environment_shim",
+ "return_type_impl_traits_shim",
"expr_scopes_shim",
"struct_signature_shim",
"struct_signature_with_source_map_shim",
@@ -686,6 +687,7 @@
"return_type_impl_traits_shim",
"infer_shim",
"function_signature_with_source_map_shim",
+ "return_type_impl_traits_shim",
"expr_scopes_shim",
"struct_signature_with_source_map_shim",
"generic_predicates_shim",
diff --git a/crates/hir-ty/src/tests/opaque_types.rs b/crates/hir-ty/src/tests/opaque_types.rs
index 5cdd170..ca98633 100644
--- a/crates/hir-ty/src/tests/opaque_types.rs
+++ b/crates/hir-ty/src/tests/opaque_types.rs
@@ -31,7 +31,6 @@
}
#[test]
-#[ignore = "FIXME(next-solver): This currently generates a type mismatch, need to switch opaque type handling to the solver"]
fn associated_type_impl_traits_complex() {
check_types(
r#"
@@ -116,6 +115,7 @@
);
}
+#[ignore = "FIXME(next-solver): TAIT support was removed, need to rework it to work with `#[define_opaque]`"]
#[test]
fn type_alias_impl_trait_simple() {
check_no_mismatches(
@@ -135,9 +135,6 @@
"#,
);
- // FIXME(next-solver): This should emit type mismatch error but leaving it for now
- // as we should fully migrate into next-solver without chalk-ir and TAIT should be
- // reworked on r-a to handle `#[define_opaque(T)]`
check_infer_with_mismatches(
r#"
trait Trait {}
diff --git a/crates/hir-ty/src/tests/regression.rs b/crates/hir-ty/src/tests/regression.rs
index 7c79393..75d3203 100644
--- a/crates/hir-ty/src/tests/regression.rs
+++ b/crates/hir-ty/src/tests/regression.rs
@@ -725,7 +725,7 @@
138..146 'bar(key)': impl Future<Output = <K as Foo<R>>::Bar>
142..145 'key': &'? K
162..165 'key': &'? K
- 224..227 '{ }': ()
+ 224..227 '{ }': impl Future<Output = <K as Foo<R>>::Bar>
"#]],
);
}
@@ -2506,3 +2506,19 @@
"#,
);
}
+
+#[test]
+fn module_inside_block() {
+ check_types(
+ r#"
+fn foo() {
+ mod my_mod {
+ pub type Bool = bool;
+ }
+
+ let _: my_mod::Bool;
+ // ^ bool
+}
+ "#,
+ );
+}
diff --git a/crates/hir-ty/src/tests/regression/new_solver.rs b/crates/hir-ty/src/tests/regression/new_solver.rs
index f8b73cd..64c69af 100644
--- a/crates/hir-ty/src/tests/regression/new_solver.rs
+++ b/crates/hir-ty/src/tests/regression/new_solver.rs
@@ -180,7 +180,7 @@
"#,
expect![[r#"
150..154 'self': &'a Grid
- 174..181 '{ }': impl Iterator<Item = &'a ()>
+ 174..181 '{ }': <&'a Grid as IntoIterator>::IntoIter
"#]],
);
}
diff --git a/crates/hir-ty/src/tests/traits.rs b/crates/hir-ty/src/tests/traits.rs
index f72ca22..c0e4393 100644
--- a/crates/hir-ty/src/tests/traits.rs
+++ b/crates/hir-ty/src/tests/traits.rs
@@ -1211,7 +1211,7 @@
expect![[r#"
29..33 'self': &'? Self
54..58 'self': &'? Self
- 98..100 '{}': ()
+ 98..100 '{}': impl Trait<u64>
110..111 'x': impl Trait<u64>
130..131 'y': &'? impl Trait<u64>
151..268 '{ ...2(); }': ()
@@ -1373,11 +1373,11 @@
expect![[r#"
49..53 'self': &'? mut Self
101..105 'self': &'? Self
- 184..195 '{ loop {} }': ({unknown}, {unknown})
+ 184..195 '{ loop {} }': (impl Iterator<Item = impl Trait<u32>>, impl Trait<u64>)
186..193 'loop {}': !
191..193 '{}': ()
206..207 't': T
- 268..279 '{ loop {} }': ({unknown}, {unknown})
+ 268..279 '{ loop {} }': (impl Iterator<Item = impl Trait<T>>, impl Trait<T>)
270..277 'loop {}': !
275..277 '{}': ()
291..413 '{ ...o(); }': ()
@@ -1419,7 +1419,7 @@
}
"#,
expect![[r#"
- 134..165 '{ ...(C)) }': (impl FnOnce(&'? str, T), Bar<u8>)
+ 134..165 '{ ...(C)) }': (impl FnOnce(&'? str, T), impl Trait<u8>)
140..163 '(|inpu...ar(C))': (impl FnOnce(&'? str, T), Bar<u8>)
141..154 '|input, t| {}': impl FnOnce(&'? str, T)
142..147 'input': &'? str
@@ -1441,7 +1441,7 @@
trait Future { type Output; }
impl Future for () { type Output = i32; }
type Foo<F> = (<F as Future>::Output, F);
-fn foo<X>() -> Foo<impl Future<Output = ()>> {
+fn foo<X>() -> Foo<impl Future<Output = i32>> {
(0, ())
}
"#,
diff --git a/crates/hir-ty/src/traits.rs b/crates/hir-ty/src/traits.rs
index 7f6d4ff..00c8eb7 100644
--- a/crates/hir-ty/src/traits.rs
+++ b/crates/hir-ty/src/traits.rs
@@ -107,24 +107,26 @@
infer_ctxt: &InferCtxt<'db>,
goal: Canonical<'db, Goal<'db, Predicate<'db>>>,
) -> NextTraitSolveResult {
- let context = SolverContext(infer_ctxt.clone());
+ infer_ctxt.probe(|_| {
+ let context = <&SolverContext<'db>>::from(infer_ctxt);
- tracing::info!(?goal);
+ tracing::info!(?goal);
- let (goal, var_values) = context.instantiate_canonical(&goal);
- tracing::info!(?var_values);
+ let (goal, var_values) = context.instantiate_canonical(&goal);
+ tracing::info!(?var_values);
- let res = context.evaluate_root_goal(goal, Span::dummy(), None);
+ let res = context.evaluate_root_goal(goal, Span::dummy(), None);
- let res = res.map(|r| (r.has_changed, r.certainty));
+ let res = res.map(|r| (r.has_changed, r.certainty));
- tracing::debug!("solve_nextsolver({:?}) => {:?}", goal, res);
+ tracing::debug!("solve_nextsolver({:?}) => {:?}", goal, res);
- match res {
- Err(_) => NextTraitSolveResult::NoSolution,
- Ok((_, Certainty::Yes)) => NextTraitSolveResult::Certain,
- Ok((_, Certainty::Maybe { .. })) => NextTraitSolveResult::Uncertain,
- }
+ match res {
+ Err(_) => NextTraitSolveResult::NoSolution,
+ Ok((_, Certainty::Yes)) => NextTraitSolveResult::Certain,
+ Ok((_, Certainty::Maybe { .. })) => NextTraitSolveResult::Uncertain,
+ }
+ })
}
/// Solve a trait goal using next trait solver.
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index 2bb2f80..f2faf99 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -5136,10 +5136,7 @@
AliasTy::new(interner, alias.id.into(), args),
);
- // FIXME(next-solver): This needs to be `PostAnalysis`, but this currently causes errors due to our incorrect
- // handling of opaques. `non_body_analysis()` will also cause errors (from not revealing opaques inside their
- // defining places), so we choose between two bad options.
- let infcx = interner.infer_ctxt().build(TypingMode::non_body_analysis());
+ let infcx = interner.infer_ctxt().build(TypingMode::PostAnalysis);
let ty = structurally_normalize_ty(&infcx, projection, self.env.clone());
if ty.is_ty_error() { None } else { Some(self.derived(ty)) }
}
@@ -5758,8 +5755,7 @@
pub fn drop_glue(&self, db: &'db dyn HirDatabase) -> DropGlue {
let interner = DbInterner::new_with(db, Some(self.env.krate), self.env.block);
- // FIXME: This should be `PostAnalysis` I believe.
- let infcx = interner.infer_ctxt().build(TypingMode::non_body_analysis());
+ let infcx = interner.infer_ctxt().build(TypingMode::PostAnalysis);
hir_ty::drop::has_drop_glue(&infcx, self.ty, self.env.clone())
}
}
diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs
index 62ce3da..ec43442 100644
--- a/crates/hir/src/semantics.rs
+++ b/crates/hir/src/semantics.rs
@@ -2105,6 +2105,22 @@
parent = parent_;
}
}
+
+ pub fn impl_generated_from_derive(&self, impl_: Impl) -> Option<Adt> {
+ let source = hir_def::src::HasSource::ast_ptr(&impl_.id.loc(self.db), self.db);
+ let mut file_id = source.file_id;
+ let adt_ast_id = loop {
+ let macro_call = file_id.macro_file()?;
+ match macro_call.loc(self.db).kind {
+ hir_expand::MacroCallKind::Derive { ast_id, .. } => break ast_id,
+ hir_expand::MacroCallKind::FnLike { ast_id, .. } => file_id = ast_id.file_id,
+ hir_expand::MacroCallKind::Attr { ast_id, .. } => file_id = ast_id.file_id,
+ }
+ };
+ let adt_source = adt_ast_id.to_in_file_node(self.db);
+ self.cache(adt_source.value.syntax().ancestors().last().unwrap(), adt_source.file_id);
+ ToDef::to_def(self, adt_source.as_ref())
+ }
}
// FIXME This can't be the best way to do this
diff --git a/crates/hir/src/source_analyzer.rs b/crates/hir/src/source_analyzer.rs
index 15eab14..f994ed2 100644
--- a/crates/hir/src/source_analyzer.rs
+++ b/crates/hir/src/source_analyzer.rs
@@ -1594,14 +1594,12 @@
Some(unresolved) => resolver
.generic_def()
.and_then(|def| {
- hir_ty::attach_db(db, || {
- hir_ty::associated_type_shorthand_candidates(
- db,
- def,
- res.in_type_ns()?,
- |name, _| name == unresolved.name,
- )
- })
+ hir_ty::associated_type_shorthand_candidates(
+ db,
+ def,
+ res.in_type_ns()?,
+ |name, _| name == unresolved.name,
+ )
})
.map(TypeAlias::from)
.map(Into::into)
diff --git a/crates/ide-assists/src/handlers/convert_range_for_to_while.rs b/crates/ide-assists/src/handlers/convert_range_for_to_while.rs
new file mode 100644
index 0000000..68cb764
--- /dev/null
+++ b/crates/ide-assists/src/handlers/convert_range_for_to_while.rs
@@ -0,0 +1,259 @@
+use ide_db::assists::AssistId;
+use itertools::Itertools;
+use syntax::{
+ AstNode, T,
+ algo::previous_non_trivia_token,
+ ast::{
+ self, HasArgList, HasLoopBody, HasName, RangeItem, edit::AstNodeEdit, make,
+ syntax_factory::SyntaxFactory,
+ },
+ syntax_editor::{Element, Position},
+};
+
+use crate::assist_context::{AssistContext, Assists};
+
+// Assist: convert_range_for_to_while
+//
+// Convert for each range into while loop.
+//
+// ```
+// fn foo() {
+// $0for i in 3..7 {
+// foo(i);
+// }
+// }
+// ```
+// ->
+// ```
+// fn foo() {
+// let mut i = 3;
+// while i < 7 {
+// foo(i);
+// i += 1;
+// }
+// }
+// ```
+pub(crate) fn convert_range_for_to_while(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let for_kw = ctx.find_token_syntax_at_offset(T![for])?;
+ let for_ = ast::ForExpr::cast(for_kw.parent()?)?;
+ let ast::Pat::IdentPat(pat) = for_.pat()? else { return None };
+ let iterable = for_.iterable()?;
+ let (start, end, step, inclusive) = extract_range(&iterable)?;
+ let name = pat.name()?;
+ let body = for_.loop_body()?;
+ let last = previous_non_trivia_token(body.stmt_list()?.r_curly_token()?)?;
+
+ let description = if end.is_some() {
+ "Replace with while expression"
+ } else {
+ "Replace with loop expression"
+ };
+ acc.add(
+ AssistId::refactor("convert_range_for_to_while"),
+ description,
+ for_.syntax().text_range(),
+ |builder| {
+ let mut edit = builder.make_editor(for_.syntax());
+ let make = SyntaxFactory::with_mappings();
+
+ let indent = for_.indent_level();
+ let pat = make.ident_pat(pat.ref_token().is_some(), true, name.clone());
+ let let_stmt = make.let_stmt(pat.into(), None, Some(start));
+ edit.insert_all(
+ Position::before(for_.syntax()),
+ vec![
+ let_stmt.syntax().syntax_element(),
+ make.whitespace(&format!("\n{}", indent)).syntax_element(),
+ ],
+ );
+
+ let mut elements = vec![];
+
+ let var_expr = make.expr_path(make.ident_path(&name.text()));
+ let op = ast::BinaryOp::CmpOp(ast::CmpOp::Ord {
+ ordering: ast::Ordering::Less,
+ strict: !inclusive,
+ });
+ if let Some(end) = end {
+ elements.extend([
+ make.token(T![while]).syntax_element(),
+ make.whitespace(" ").syntax_element(),
+ make.expr_bin(var_expr.clone(), op, end).syntax().syntax_element(),
+ ]);
+ } else {
+ elements.push(make.token(T![loop]).syntax_element());
+ }
+
+ edit.replace_all(
+ for_kw.syntax_element()..=iterable.syntax().syntax_element(),
+ elements,
+ );
+
+ let op = ast::BinaryOp::Assignment { op: Some(ast::ArithOp::Add) };
+ edit.insert_all(
+ Position::after(last),
+ vec![
+ make.whitespace(&format!("\n{}", indent + 1)).syntax_element(),
+ make.expr_bin(var_expr, op, step).syntax().syntax_element(),
+ make.token(T![;]).syntax_element(),
+ ],
+ );
+
+ edit.add_mappings(make.finish_with_mappings());
+ builder.add_file_edits(ctx.vfs_file_id(), edit);
+ },
+ )
+}
+
+fn extract_range(iterable: &ast::Expr) -> Option<(ast::Expr, Option<ast::Expr>, ast::Expr, bool)> {
+ Some(match iterable {
+ ast::Expr::ParenExpr(expr) => extract_range(&expr.expr()?)?,
+ ast::Expr::RangeExpr(range) => {
+ let inclusive = range.op_kind()? == ast::RangeOp::Inclusive;
+ (range.start()?, range.end(), make::expr_literal("1").into(), inclusive)
+ }
+ ast::Expr::MethodCallExpr(call) if call.name_ref()?.text() == "step_by" => {
+ let [step] = call.arg_list()?.args().collect_array()?;
+ let (start, end, _, inclusive) = extract_range(&call.receiver()?)?;
+ (start, end, step, inclusive)
+ }
+ _ => return None,
+ })
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_assist, check_assist_not_applicable};
+
+ use super::*;
+
+ #[test]
+ fn test_convert_range_for_to_while() {
+ check_assist(
+ convert_range_for_to_while,
+ "
+fn foo() {
+ $0for i in 3..7 {
+ foo(i);
+ }
+}
+ ",
+ "
+fn foo() {
+ let mut i = 3;
+ while i < 7 {
+ foo(i);
+ i += 1;
+ }
+}
+ ",
+ );
+ }
+
+ #[test]
+ fn test_convert_range_for_to_while_no_end_bound() {
+ check_assist(
+ convert_range_for_to_while,
+ "
+fn foo() {
+ $0for i in 3.. {
+ foo(i);
+ }
+}
+ ",
+ "
+fn foo() {
+ let mut i = 3;
+ loop {
+ foo(i);
+ i += 1;
+ }
+}
+ ",
+ );
+ }
+
+ #[test]
+ fn test_convert_range_for_to_while_with_mut_binding() {
+ check_assist(
+ convert_range_for_to_while,
+ "
+fn foo() {
+ $0for mut i in 3..7 {
+ foo(i);
+ }
+}
+ ",
+ "
+fn foo() {
+ let mut i = 3;
+ while i < 7 {
+ foo(i);
+ i += 1;
+ }
+}
+ ",
+ );
+ }
+
+ #[test]
+ fn test_convert_range_for_to_while_with_label() {
+ check_assist(
+ convert_range_for_to_while,
+ "
+fn foo() {
+ 'a: $0for mut i in 3..7 {
+ foo(i);
+ }
+}
+ ",
+ "
+fn foo() {
+ let mut i = 3;
+ 'a: while i < 7 {
+ foo(i);
+ i += 1;
+ }
+}
+ ",
+ );
+ }
+
+ #[test]
+ fn test_convert_range_for_to_while_step_by() {
+ check_assist(
+ convert_range_for_to_while,
+ "
+fn foo() {
+ $0for mut i in (3..7).step_by(2) {
+ foo(i);
+ }
+}
+ ",
+ "
+fn foo() {
+ let mut i = 3;
+ while i < 7 {
+ foo(i);
+ i += 2;
+ }
+}
+ ",
+ );
+ }
+
+ #[test]
+ fn test_convert_range_for_to_while_not_applicable_non_range() {
+ check_assist_not_applicable(
+ convert_range_for_to_while,
+ "
+fn foo() {
+ let ident = 3..7;
+ $0for mut i in ident {
+ foo(i);
+ }
+}
+ ",
+ );
+ }
+}
diff --git a/crates/ide-assists/src/handlers/generate_blanket_trait_impl.rs b/crates/ide-assists/src/handlers/generate_blanket_trait_impl.rs
index c25b0bb..b0fa9e6 100644
--- a/crates/ide-assists/src/handlers/generate_blanket_trait_impl.rs
+++ b/crates/ide-assists/src/handlers/generate_blanket_trait_impl.rs
@@ -13,7 +13,7 @@
AstNode,
ast::{
self, AssocItem, BlockExpr, GenericParam, HasAttrs, HasGenericParams, HasName,
- HasTypeBounds, HasVisibility, edit_in_place::Indent, make,
+ HasTypeBounds, HasVisibility, edit::AstNodeEdit, make,
},
syntax_editor::Position,
};
@@ -75,7 +75,7 @@
|builder| {
let mut edit = builder.make_editor(traitd.syntax());
let namety = make::ty_path(make::path_from_text(&name.text()));
- let trait_where_clause = traitd.where_clause().map(|it| it.clone_for_update());
+ let trait_where_clause = traitd.where_clause().map(|it| it.reset_indent());
let bounds = traitd.type_bound_list().and_then(exlucde_sized);
let is_unsafe = traitd.unsafe_token().is_some();
let thisname = this_name(&traitd);
@@ -90,10 +90,6 @@
let trait_gen_args =
traitd.generic_param_list().map(|param_list| param_list.to_generic_args());
- if let Some(ref where_clause) = trait_where_clause {
- where_clause.reindent_to(0.into());
- }
-
let impl_ = make::impl_trait(
cfg_attrs(&traitd),
is_unsafe,
@@ -112,20 +108,19 @@
if let Some(trait_assoc_list) = traitd.assoc_item_list() {
let assoc_item_list = impl_.get_or_create_assoc_item_list();
- for method in trait_assoc_list.assoc_items() {
- let AssocItem::Fn(method) = method else {
- continue;
+ for item in trait_assoc_list.assoc_items() {
+ let item = match item {
+ ast::AssocItem::Fn(method) if method.body().is_none() => {
+ todo_fn(&method, ctx.config).into()
+ }
+ ast::AssocItem::Const(_) | ast::AssocItem::TypeAlias(_) => item,
+ _ => continue,
};
- if method.body().is_some() {
- continue;
- }
- let f = todo_fn(&method, ctx.config).clone_for_update();
- f.indent(1.into());
- assoc_item_list.add_item(AssocItem::Fn(f));
+ assoc_item_list.add_item(item.reset_indent().indent(1.into()));
}
}
- impl_.indent(indent);
+ let impl_ = impl_.indent(indent);
edit.insert_all(
Position::after(traitd.syntax()),
@@ -507,6 +502,41 @@
}
#[test]
+ fn test_gen_blanket_other_assoc_items() {
+ check_assist(
+ generate_blanket_trait_impl,
+ r#"
+trait $0Foo {
+ type Item;
+
+ const N: usize;
+
+ fn foo(&self);
+}
+"#,
+ r#"
+trait Foo {
+ type Item;
+
+ const N: usize;
+
+ fn foo(&self);
+}
+
+impl<T: ?Sized> Foo for $0T {
+ type Item;
+
+ const N: usize;
+
+ fn foo(&self) {
+ todo!()
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
fn test_gen_blanket_indent() {
check_assist(
generate_blanket_trait_impl,
@@ -742,6 +772,49 @@
}
"#,
);
+ check_assist(
+ generate_blanket_trait_impl,
+ r#"
+mod foo {
+ mod bar {
+ trait $0Foo {
+ type Item: Bar<
+ Self,
+ >;
+
+ const N: Baz<
+ Self,
+ >;
+ }
+ }
+}
+ "#,
+ r#"
+mod foo {
+ mod bar {
+ trait Foo {
+ type Item: Bar<
+ Self,
+ >;
+
+ const N: Baz<
+ Self,
+ >;
+ }
+
+ impl<T: ?Sized> Foo for $0T {
+ type Item: Bar<
+ Self,
+ >;
+
+ const N: Baz<
+ Self,
+ >;
+ }
+ }
+}
+ "#,
+ );
}
#[test]
@@ -824,6 +897,8 @@
where
Self::Owned: Default,
{
+ type X: Sync;
+
fn foo(&self, x: Self::X) -> T {
todo!()
}
@@ -871,6 +946,8 @@
Self: ToOwned,
Self::Owned: Default,
{
+ type X: Sync;
+
fn foo(&self, x: Self::X) -> T {
todo!()
}
@@ -906,6 +983,8 @@
}
impl<T: Send, T1: ?Sized> Foo<T> for $0T1 {
+ type X: Sync;
+
fn foo(&self, x: Self::X) -> T {
todo!()
}
@@ -941,6 +1020,8 @@
}
impl<T: ?Sized> Foo for $0T {
+ type X: Sync;
+
fn foo(&self, x: Self::X) -> i32 {
todo!()
}
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 c57fd4d..5a23077 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,3 +1,4 @@
+use either::Either;
use ide_db::syntax_helpers::suggest_name;
use syntax::ast::{self, AstNode, syntax_factory::SyntaxFactory};
@@ -24,9 +25,9 @@
acc: &mut Assists,
ctx: &AssistContext<'_>,
) -> Option<()> {
- let if_expr = ctx.find_node_at_offset::<ast::IfExpr>()?;
+ let has_cond = ctx.find_node_at_offset::<Either<ast::IfExpr, ast::WhileExpr>>()?;
- let cond = if_expr.condition()?;
+ let cond = either::for_both!(&has_cond, it => it.condition())?;
let cond = cover_let_chain(cond, ctx.selection_trimmed())?;
let call_expr = match cond {
ast::Expr::MethodCallExpr(call) => call,
@@ -39,7 +40,7 @@
let receiver = call_expr.receiver()?;
let mut name_generator = suggest_name::NameGenerator::new_from_scope_locals(
- ctx.sema.scope(if_expr.syntax()),
+ ctx.sema.scope(has_cond.syntax()),
);
let var_name = if let ast::Expr::PathExpr(path_expr) = receiver.clone() {
name_generator.suggest_name(&path_expr.path()?.to_string())
@@ -48,9 +49,9 @@
};
let (assist_id, message, text) = if name_ref.text() == "is_some" {
- ("replace_is_some_with_if_let_some", "Replace `is_some` with `if let Some`", "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 `if let Ok`", "Ok")
+ ("replace_is_ok_with_if_let_ok", "Replace `is_ok` with `let Ok`", "Ok")
};
acc.add(
@@ -251,6 +252,25 @@
}
#[test]
+ fn replace_is_some_with_while_let_some() {
+ check_assist(
+ replace_is_method_with_if_let_method,
+ r#"
+fn main() {
+ let mut x = Some(1);
+ while x.is_som$0e() { x = None }
+}
+"#,
+ r#"
+fn main() {
+ let mut x = Some(1);
+ while let Some(${0:x1}) = x { x = None }
+}
+"#,
+ );
+ }
+
+ #[test]
fn replace_is_some_with_if_let_some_not_applicable_after_l_curly() {
check_assist_not_applicable(
replace_is_method_with_if_let_method,
diff --git a/crates/ide-assists/src/lib.rs b/crates/ide-assists/src/lib.rs
index e9f2d68..4b4aa94 100644
--- a/crates/ide-assists/src/lib.rs
+++ b/crates/ide-assists/src/lib.rs
@@ -131,6 +131,7 @@
mod convert_match_to_let_else;
mod convert_named_struct_to_tuple_struct;
mod convert_nested_function_to_closure;
+ mod convert_range_for_to_while;
mod convert_to_guarded_return;
mod convert_tuple_return_type_to_struct;
mod convert_tuple_struct_to_named_struct;
@@ -268,6 +269,7 @@
convert_match_to_let_else::convert_match_to_let_else,
convert_named_struct_to_tuple_struct::convert_named_struct_to_tuple_struct,
convert_nested_function_to_closure::convert_nested_function_to_closure,
+ convert_range_for_to_while::convert_range_for_to_while,
convert_to_guarded_return::convert_to_guarded_return,
convert_tuple_return_type_to_struct::convert_tuple_return_type_to_struct,
convert_tuple_struct_to_named_struct::convert_tuple_struct_to_named_struct,
diff --git a/crates/ide-assists/src/tests/generated.rs b/crates/ide-assists/src/tests/generated.rs
index a99fe8d..7f0836a 100644
--- a/crates/ide-assists/src/tests/generated.rs
+++ b/crates/ide-assists/src/tests/generated.rs
@@ -732,6 +732,29 @@
}
#[test]
+fn doctest_convert_range_for_to_while() {
+ check_doc_test(
+ "convert_range_for_to_while",
+ r#####"
+fn foo() {
+ $0for i in 3..7 {
+ foo(i);
+ }
+}
+"#####,
+ r#####"
+fn foo() {
+ let mut i = 3;
+ while i < 7 {
+ foo(i);
+ i += 1;
+ }
+}
+"#####,
+ )
+}
+
+#[test]
fn doctest_convert_to_guarded_return() {
check_doc_test(
"convert_to_guarded_return",
diff --git a/crates/ide-completion/src/completions/keyword.rs b/crates/ide-completion/src/completions/keyword.rs
index 6162d98..eab2b90 100644
--- a/crates/ide-completion/src/completions/keyword.rs
+++ b/crates/ide-completion/src/completions/keyword.rs
@@ -532,6 +532,146 @@
}
#[test]
+ fn if_completion_in_format() {
+ check_edit(
+ "if",
+ r#"
+//- minicore: fmt
+fn main() {
+ format_args!("{}", $0);
+}
+"#,
+ r#"
+fn main() {
+ format_args!("{}", if $1 {
+ $2
+} else {
+ $0
+});
+}
+"#,
+ );
+
+ check_edit(
+ "if",
+ r#"
+//- minicore: fmt
+fn main() {
+ format_args!("{}", if$0);
+}
+"#,
+ r#"
+fn main() {
+ format_args!("{}", if $1 {
+ $2
+} else {
+ $0
+});
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn if_completion_in_value_expected_expressions() {
+ check_edit(
+ "if",
+ r#"
+fn main() {
+ 2 + $0;
+}
+"#,
+ r#"
+fn main() {
+ 2 + if $1 {
+ $2
+} else {
+ $0
+};
+}
+"#,
+ );
+
+ check_edit(
+ "if",
+ r#"
+fn main() {
+ -$0;
+}
+"#,
+ r#"
+fn main() {
+ -if $1 {
+ $2
+} else {
+ $0
+};
+}
+"#,
+ );
+
+ check_edit(
+ "if",
+ r#"
+fn main() {
+ return $0;
+}
+"#,
+ r#"
+fn main() {
+ return if $1 {
+ $2
+} else {
+ $0
+};
+}
+"#,
+ );
+
+ check_edit(
+ "if",
+ r#"
+fn main() {
+ loop {
+ break $0;
+ }
+}
+"#,
+ r#"
+fn main() {
+ loop {
+ break if $1 {
+ $2
+} else {
+ $0
+};
+ }
+}
+"#,
+ );
+
+ check_edit(
+ "if",
+ r#"
+struct Foo { x: i32 }
+fn main() {
+ Foo { x: $0 }
+}
+"#,
+ r#"
+struct Foo { x: i32 }
+fn main() {
+ Foo { x: if $1 {
+ $2
+} else {
+ $0
+} }
+}
+"#,
+ );
+ }
+
+ #[test]
fn completes_let_in_block() {
check_edit(
"let",
diff --git a/crates/ide-completion/src/context/analysis.rs b/crates/ide-completion/src/context/analysis.rs
index b3d9ff0..d6d3978 100644
--- a/crates/ide-completion/src/context/analysis.rs
+++ b/crates/ide-completion/src/context/analysis.rs
@@ -1012,6 +1012,25 @@
.and_then(|next| next.first_token())
.is_some_and(|token| token.kind() == SyntaxKind::ELSE_KW)
};
+ let is_in_value = |it: &SyntaxNode| {
+ let Some(node) = it.parent() else { return false };
+ let kind = node.kind();
+ ast::LetStmt::can_cast(kind)
+ || ast::ArgList::can_cast(kind)
+ || ast::ArrayExpr::can_cast(kind)
+ || ast::ParenExpr::can_cast(kind)
+ || ast::BreakExpr::can_cast(kind)
+ || ast::ReturnExpr::can_cast(kind)
+ || ast::PrefixExpr::can_cast(kind)
+ || ast::FormatArgsArg::can_cast(kind)
+ || ast::RecordExprField::can_cast(kind)
+ || ast::BinExpr::cast(node.clone())
+ .and_then(|expr| expr.rhs())
+ .is_some_and(|expr| expr.syntax() == it)
+ || ast::IndexExpr::cast(node)
+ .and_then(|expr| expr.index())
+ .is_some_and(|expr| expr.syntax() == it)
+ };
// We do not want to generate path completions when we are sandwiched between an item decl signature and its body.
// ex. trait Foo $0 {}
@@ -1307,7 +1326,7 @@
.and_then(ast::LetStmt::cast)
.is_some_and(|it| it.semicolon_token().is_none())
|| after_incomplete_let && incomplete_expr_stmt.unwrap_or(true) && !before_else_kw;
- let in_value = it.parent().and_then(Either::<ast::LetStmt, ast::ArgList>::cast).is_some();
+ let in_value = is_in_value(it);
let impl_ = fetch_immediate_impl_or_trait(sema, original_file, expr.syntax())
.and_then(Either::left);
diff --git a/crates/ide-completion/src/tests/flyimport.rs b/crates/ide-completion/src/tests/flyimport.rs
index 0cd4208..e139a5e 100644
--- a/crates/ide-completion/src/tests/flyimport.rs
+++ b/crates/ide-completion/src/tests/flyimport.rs
@@ -1953,3 +1953,25 @@
expect![""],
);
}
+
+#[test]
+fn multiple_matches_with_qualifier() {
+ check(
+ r#"
+//- /foo.rs crate:foo
+pub mod env {
+ pub fn var() {}
+ pub fn _var() {}
+}
+
+//- /bar.rs crate:bar deps:foo
+fn main() {
+ env::var$0
+}
+ "#,
+ expect![[r#"
+ fn _var() (use foo::env) fn()
+ fn var() (use foo::env) fn()
+ "#]],
+ );
+}
diff --git a/crates/ide-db/src/generated/lints.rs b/crates/ide-db/src/generated/lints.rs
index 14bc380..5eb7e92 100644
--- a/crates/ide-db/src/generated/lints.rs
+++ b/crates/ide-db/src/generated/lints.rs
@@ -7511,7 +7511,7 @@
an executable without the `std` crate, you might run into the need
for lang item definitions.
-[personality]: https://github.com/rust-lang/rust/blob/master/library/std/src/sys/personality/gcc.rs
+[personality]: https://github.com/rust-lang/rust/blob/HEAD/library/std/src/sys/personality/gcc.rs
## Example: Implementing a `Box`
@@ -7586,7 +7586,7 @@
An up-to-date list of all language items can be found [here] in the compiler code.
-[here]: https://github.com/rust-lang/rust/blob/master/compiler/rustc_hir/src/lang_items.rs
+[here]: https://github.com/rust-lang/rust/blob/HEAD/compiler/rustc_hir/src/lang_items.rs
"##,
default_severity: Severity::Allow,
warn_since: None,
diff --git a/crates/ide-db/src/imports/import_assets.rs b/crates/ide-db/src/imports/import_assets.rs
index 0c235c8..50edfca 100644
--- a/crates/ide-db/src/imports/import_assets.rs
+++ b/crates/ide-db/src/imports/import_assets.rs
@@ -1,6 +1,6 @@
//! Look up accessible paths for items.
-use std::ops::ControlFlow;
+use std::{convert::Infallible, ops::ControlFlow};
use hir::{
AsAssocItem, AssocItem, AssocItemContainer, Complete, Crate, FindPathConfig, HasCrate,
@@ -9,6 +9,7 @@
};
use itertools::Itertools;
use rustc_hash::{FxHashMap, FxHashSet};
+use smallvec::SmallVec;
use syntax::{
AstNode, SyntaxNode,
ast::{self, HasName, make},
@@ -416,7 +417,7 @@
NameToImport::Exact(first_qsegment.as_str().to_owned(), true),
AssocSearchMode::Exclude,
)
- .filter_map(|(item, do_not_complete)| {
+ .flat_map(|(item, do_not_complete)| {
// we found imports for `first_qsegment`, now we need to filter these imports by whether
// they result in resolving the rest of the path successfully
validate_resolvable(
@@ -446,10 +447,10 @@
resolved_qualifier: ItemInNs,
unresolved_qualifier: &[Name],
complete_in_flyimport: CompleteInFlyimport,
-) -> Option<LocatedImport> {
+) -> SmallVec<[LocatedImport; 1]> {
let _p = tracing::info_span!("ImportAssets::import_for_item").entered();
- let qualifier = {
+ let qualifier = (|| {
let mut adjusted_resolved_qualifier = resolved_qualifier;
if !unresolved_qualifier.is_empty() {
match resolved_qualifier {
@@ -464,69 +465,80 @@
}
match adjusted_resolved_qualifier {
- ItemInNs::Types(def) => def,
- _ => return None,
+ ItemInNs::Types(def) => Some(def),
+ _ => None,
}
- };
- let import_path_candidate = mod_path(resolved_qualifier)?;
+ })();
+ let Some(qualifier) = qualifier else { return SmallVec::new() };
+ let Some(import_path_candidate) = mod_path(resolved_qualifier) else { return SmallVec::new() };
+ let mut result = SmallVec::new();
let ty = match qualifier {
ModuleDef::Module(module) => {
- return items_locator::items_with_name_in_module(
+ items_locator::items_with_name_in_module::<Infallible>(
db,
module,
candidate.clone(),
AssocSearchMode::Exclude,
- |it| match scope_filter(it) {
- true => ControlFlow::Break(it),
- false => ControlFlow::Continue(()),
+ |item| {
+ if scope_filter(item) {
+ result.push(LocatedImport::new(
+ import_path_candidate.clone(),
+ resolved_qualifier,
+ item,
+ complete_in_flyimport,
+ ));
+ }
+ ControlFlow::Continue(())
},
- )
- .map(|item| {
- LocatedImport::new(
- import_path_candidate,
- resolved_qualifier,
- item,
- complete_in_flyimport,
- )
- });
+ );
+ return result;
}
// FIXME
- ModuleDef::Trait(_) => return None,
+ ModuleDef::Trait(_) => return SmallVec::new(),
ModuleDef::TypeAlias(alias) => alias.ty(db),
ModuleDef::BuiltinType(builtin) => builtin.ty(db),
ModuleDef::Adt(adt) => adt.ty(db),
- _ => return None,
+ _ => return SmallVec::new(),
};
- ty.iterate_path_candidates(db, scope, &FxHashSet::default(), None, None, |assoc| {
- // FIXME: Support extra trait imports
- if assoc.container_or_implemented_trait(db).is_some() {
- return None;
- }
- let name = assoc.name(db)?;
- let is_match = match candidate {
- NameToImport::Prefix(text, true) => name.as_str().starts_with(text),
- NameToImport::Prefix(text, false) => {
- name.as_str().chars().zip(text.chars()).all(|(name_char, candidate_char)| {
- name_char.eq_ignore_ascii_case(&candidate_char)
- })
+ ty.iterate_path_candidates::<Infallible>(
+ db,
+ scope,
+ &FxHashSet::default(),
+ None,
+ None,
+ |assoc| {
+ // FIXME: Support extra trait imports
+ if assoc.container_or_implemented_trait(db).is_some() {
+ return None;
}
- NameToImport::Exact(text, true) => name.as_str() == text,
- NameToImport::Exact(text, false) => name.as_str().eq_ignore_ascii_case(text),
- NameToImport::Fuzzy(text, true) => text.chars().all(|c| name.as_str().contains(c)),
- NameToImport::Fuzzy(text, false) => text
- .chars()
- .all(|c| name.as_str().chars().any(|name_char| name_char.eq_ignore_ascii_case(&c))),
- };
- if !is_match {
- return None;
- }
- Some(LocatedImport::new(
- import_path_candidate.clone(),
- resolved_qualifier,
- assoc_to_item(assoc),
- complete_in_flyimport,
- ))
- })
+ let name = assoc.name(db)?;
+ let is_match = match candidate {
+ NameToImport::Prefix(text, true) => name.as_str().starts_with(text),
+ NameToImport::Prefix(text, false) => {
+ name.as_str().chars().zip(text.chars()).all(|(name_char, candidate_char)| {
+ name_char.eq_ignore_ascii_case(&candidate_char)
+ })
+ }
+ NameToImport::Exact(text, true) => name.as_str() == text,
+ NameToImport::Exact(text, false) => name.as_str().eq_ignore_ascii_case(text),
+ NameToImport::Fuzzy(text, true) => text.chars().all(|c| name.as_str().contains(c)),
+ NameToImport::Fuzzy(text, false) => text.chars().all(|c| {
+ name.as_str().chars().any(|name_char| name_char.eq_ignore_ascii_case(&c))
+ }),
+ };
+ if !is_match {
+ return None;
+ }
+ result.push(LocatedImport::new(
+ import_path_candidate.clone(),
+ resolved_qualifier,
+ assoc_to_item(assoc),
+ complete_in_flyimport,
+ ));
+ None
+ },
+ );
+ result
}
pub fn item_for_path_search(db: &RootDatabase, item: ItemInNs) -> Option<ItemInNs> {
diff --git a/crates/ide-db/src/search.rs b/crates/ide-db/src/search.rs
index f1d076e..018c841 100644
--- a/crates/ide-db/src/search.rs
+++ b/crates/ide-db/src/search.rs
@@ -387,12 +387,14 @@
return SearchScope::reverse_dependencies(db, module.krate());
}
- let vis = self.visibility(db);
- if let Some(Visibility::Public) = vis {
- return SearchScope::reverse_dependencies(db, module.krate());
- }
- if let Some(Visibility::Module(module, _)) = vis {
- return SearchScope::module_and_children(db, module.into());
+ if let Some(vis) = self.visibility(db) {
+ return match vis {
+ Visibility::Module(module, _) => {
+ SearchScope::module_and_children(db, module.into())
+ }
+ Visibility::PubCrate(krate) => SearchScope::krate(db, krate.into()),
+ Visibility::Public => SearchScope::reverse_dependencies(db, module.krate()),
+ };
}
let range = match module_source {
diff --git a/crates/ide/src/annotations.rs b/crates/ide/src/annotations.rs
index 36c4404..6fb8ded 100644
--- a/crates/ide/src/annotations.rs
+++ b/crates/ide/src/annotations.rs
@@ -9,7 +9,7 @@
use crate::{
NavigationTarget, RunnableKind,
annotations::fn_references::find_all_methods,
- goto_implementation::goto_implementation,
+ goto_implementation::{GotoImplementationConfig, goto_implementation},
navigation_target,
references::{FindAllRefsConfig, find_all_refs},
runnables::{Runnable, runnables},
@@ -44,6 +44,7 @@
pub annotate_method_references: bool,
pub annotate_enum_variant_references: bool,
pub location: AnnotationLocation,
+ pub filter_adjacent_derive_implementations: bool,
pub minicore: MiniCore<'a>,
}
@@ -204,7 +205,12 @@
) -> Annotation {
match annotation.kind {
AnnotationKind::HasImpls { pos, ref mut data } => {
- *data = goto_implementation(db, pos).map(|range| range.info);
+ let goto_implementation_config = GotoImplementationConfig {
+ filter_adjacent_derive_implementations: config
+ .filter_adjacent_derive_implementations,
+ };
+ *data =
+ goto_implementation(db, &goto_implementation_config, pos).map(|range| range.info);
}
AnnotationKind::HasReferences { pos, ref mut data } => {
*data = find_all_refs(
@@ -253,6 +259,7 @@
annotate_enum_variant_references: true,
location: AnnotationLocation::AboveName,
minicore: MiniCore::default(),
+ filter_adjacent_derive_implementations: false,
};
fn check_with_config(
diff --git a/crates/ide/src/goto_implementation.rs b/crates/ide/src/goto_implementation.rs
index 875403c..0572bca 100644
--- a/crates/ide/src/goto_implementation.rs
+++ b/crates/ide/src/goto_implementation.rs
@@ -8,6 +8,10 @@
use crate::{FilePosition, NavigationTarget, RangeInfo, TryToNav};
+pub struct GotoImplementationConfig {
+ pub filter_adjacent_derive_implementations: bool,
+}
+
// Feature: Go to Implementation
//
// Navigates to the impl items of types.
@@ -19,6 +23,7 @@
// 
pub(crate) fn goto_implementation(
db: &RootDatabase,
+ config: &GotoImplementationConfig,
FilePosition { file_id, offset }: FilePosition,
) -> Option<RangeInfo<Vec<NavigationTarget>>> {
let sema = Semantics::new(db);
@@ -55,7 +60,19 @@
.and_then(|def| {
let navs = match def {
Definition::Trait(trait_) => impls_for_trait(&sema, trait_),
- Definition::Adt(adt) => impls_for_ty(&sema, adt.ty(sema.db)),
+ Definition::Adt(adt) => {
+ let mut impls = Impl::all_for_type(db, adt.ty(sema.db));
+ if config.filter_adjacent_derive_implementations {
+ impls.retain(|impl_| {
+ sema.impl_generated_from_derive(*impl_) != Some(adt)
+ });
+ }
+ impls
+ .into_iter()
+ .filter_map(|imp| imp.try_to_nav(&sema))
+ .flatten()
+ .collect()
+ }
Definition::TypeAlias(alias) => impls_for_ty(&sema, alias.ty(sema.db)),
Definition::BuiltinType(builtin) => {
impls_for_ty(&sema, builtin.ty(sema.db))
@@ -125,12 +142,24 @@
use ide_db::FileRange;
use itertools::Itertools;
- use crate::fixture;
+ use crate::{GotoImplementationConfig, fixture};
+ const TEST_CONFIG: &GotoImplementationConfig =
+ &GotoImplementationConfig { filter_adjacent_derive_implementations: false };
+
+ #[track_caller]
fn check(#[rust_analyzer::rust_fixture] ra_fixture: &str) {
+ check_with_config(TEST_CONFIG, ra_fixture);
+ }
+
+ #[track_caller]
+ fn check_with_config(
+ config: &GotoImplementationConfig,
+ #[rust_analyzer::rust_fixture] ra_fixture: &str,
+ ) {
let (analysis, position, expected) = fixture::annotations(ra_fixture);
- let navs = analysis.goto_implementation(position).unwrap().unwrap().info;
+ let navs = analysis.goto_implementation(config, position).unwrap().unwrap().info;
let cmp = |frange: &FileRange| (frange.file_id, frange.range.start());
@@ -416,4 +445,22 @@
"#,
);
}
+
+ #[test]
+ fn filter_adjacent_derives() {
+ check_with_config(
+ &GotoImplementationConfig { filter_adjacent_derive_implementations: true },
+ r#"
+//- minicore: clone, copy, derive
+
+#[derive(Clone, Copy)]
+struct Foo$0;
+
+trait Bar {}
+
+impl Bar for Foo {}
+ // ^^^
+ "#,
+ );
+ }
}
diff --git a/crates/ide/src/hover/tests.rs b/crates/ide/src/hover/tests.rs
index 91fb4d0..3a19531 100644
--- a/crates/ide/src/hover/tests.rs
+++ b/crates/ide/src/hover/tests.rs
@@ -10809,7 +10809,7 @@
---
- needs Drop
+ no Drop
"#]],
);
check(
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs
index 8572528..2609457 100644
--- a/crates/ide/src/lib.rs
+++ b/crates/ide/src/lib.rs
@@ -86,6 +86,7 @@
file_structure::{FileStructureConfig, StructureNode, StructureNodeKind},
folding_ranges::{Fold, FoldKind},
goto_definition::GotoDefinitionConfig,
+ goto_implementation::GotoImplementationConfig,
highlight_related::{HighlightRelatedConfig, HighlightedRange},
hover::{
HoverAction, HoverConfig, HoverDocFormat, HoverGotoTypeData, HoverResult,
@@ -106,7 +107,7 @@
move_item::Direction,
navigation_target::{NavigationTarget, TryToNav, UpmappingResult},
references::{FindAllRefsConfig, ReferenceSearchResult},
- rename::RenameError,
+ rename::{RenameConfig, RenameError},
runnables::{Runnable, RunnableKind, TestId, UpdateTest},
signature_help::SignatureHelp,
static_index::{
@@ -537,9 +538,10 @@
/// Returns the impls from the symbol at `position`.
pub fn goto_implementation(
&self,
+ config: &GotoImplementationConfig,
position: FilePosition,
) -> Cancellable<Option<RangeInfo<Vec<NavigationTarget>>>> {
- self.with_db(|db| goto_implementation::goto_implementation(db, position))
+ self.with_db(|db| goto_implementation::goto_implementation(db, config, position))
}
/// Returns the type definitions for the symbol at `position`.
@@ -830,8 +832,9 @@
&self,
position: FilePosition,
new_name: &str,
+ config: &RenameConfig,
) -> Cancellable<Result<SourceChange, RenameError>> {
- self.with_db(|db| rename::rename(db, position, new_name))
+ self.with_db(|db| rename::rename(db, position, new_name, config))
}
pub fn prepare_rename(
diff --git a/crates/ide/src/rename.rs b/crates/ide/src/rename.rs
index 8922a8e..ce59639 100644
--- a/crates/ide/src/rename.rs
+++ b/crates/ide/src/rename.rs
@@ -4,7 +4,7 @@
//! tests. This module also implements a couple of magic tricks, like renaming
//! `self` and to `self` (to switch between associated function and method).
-use hir::{AsAssocItem, InFile, Name, Semantics, sym};
+use hir::{AsAssocItem, FindPathConfig, HasContainer, HirDisplay, InFile, Name, Semantics, sym};
use ide_db::{
FileId, FileRange, RootDatabase,
defs::{Definition, NameClass, NameRefClass},
@@ -27,6 +27,23 @@
type RenameResult<T> = Result<T, RenameError>;
+pub struct RenameConfig {
+ pub prefer_no_std: bool,
+ pub prefer_prelude: bool,
+ pub prefer_absolute: bool,
+}
+
+impl RenameConfig {
+ fn find_path_config(&self) -> FindPathConfig {
+ FindPathConfig {
+ prefer_no_std: self.prefer_no_std,
+ prefer_prelude: self.prefer_prelude,
+ prefer_absolute: self.prefer_absolute,
+ allow_unstable: true,
+ }
+ }
+}
+
/// This is similar to `collect::<Result<Vec<_>, _>>`, but unlike it, it succeeds if there is *any* `Ok` item.
fn ok_if_any<T, E>(iter: impl Iterator<Item = Result<T, E>>) -> Result<Vec<T>, E> {
let mut err = None;
@@ -100,6 +117,7 @@
db: &RootDatabase,
position: FilePosition,
new_name: &str,
+ config: &RenameConfig,
) -> RenameResult<SourceChange> {
let sema = Semantics::new(db);
let file_id = sema
@@ -158,7 +176,14 @@
if let Definition::Local(local) = def {
if let Some(self_param) = local.as_self_param(sema.db) {
cov_mark::hit!(rename_self_to_param);
- return rename_self_to_param(&sema, local, self_param, &new_name, kind);
+ return rename_self_to_param(
+ &sema,
+ local,
+ self_param,
+ &new_name,
+ kind,
+ config.find_path_config(),
+ );
}
if kind == IdentifierKind::LowercaseSelf {
cov_mark::hit!(rename_to_self);
@@ -360,7 +385,7 @@
f: hir::Function,
) {
let calls = Definition::Function(f).usages(sema).all();
- for (file_id, calls) in calls {
+ for (_file_id, calls) in calls {
for call in calls {
let Some(fn_name) = call.name.as_name_ref() else { continue };
let Some(path) = fn_name.syntax().parent().and_then(ast::PathSegment::cast) else {
@@ -409,6 +434,12 @@
.unwrap_or_else(|| arg_list.syntax().text_range().end()),
};
let replace_range = TextRange::new(replace_start, replace_end);
+ let macro_file = sema.hir_file_for(fn_name.syntax());
+ let Some((replace_range, _)) =
+ InFile::new(macro_file, replace_range).original_node_file_range_opt(sema.db)
+ else {
+ continue;
+ };
let Some(macro_mapped_self) = sema.original_range_opt(self_arg.syntax()) else {
continue;
@@ -426,8 +457,8 @@
replacement.push('(');
source_change.insert_source_edit(
- file_id.file_id(sema.db),
- TextEdit::replace(replace_range, replacement),
+ replace_range.file_id.file_id(sema.db),
+ TextEdit::replace(replace_range.range, replacement),
);
}
}
@@ -514,12 +545,189 @@
Ok(source_change)
}
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+enum CallReceiverAdjust {
+ Deref,
+ Ref,
+ RefMut,
+ None,
+}
+
+fn method_to_assoc_fn_call_self_adjust(
+ sema: &Semantics<'_, RootDatabase>,
+ self_arg: &ast::Expr,
+) -> CallReceiverAdjust {
+ let mut result = CallReceiverAdjust::None;
+ let self_adjust = sema.expr_adjustments(self_arg);
+ if let Some(self_adjust) = self_adjust {
+ let mut i = 0;
+ while i < self_adjust.len() {
+ if matches!(self_adjust[i].kind, hir::Adjust::Deref(..))
+ && matches!(
+ self_adjust.get(i + 1),
+ Some(hir::Adjustment { kind: hir::Adjust::Borrow(..), .. })
+ )
+ {
+ // Deref then ref (reborrow), skip them.
+ i += 2;
+ continue;
+ }
+
+ match self_adjust[i].kind {
+ hir::Adjust::Deref(_) if result == CallReceiverAdjust::None => {
+ // Autoref takes precedence over deref, because if given a `&Type` the compiler will deref
+ // it automatically.
+ result = CallReceiverAdjust::Deref;
+ }
+ hir::Adjust::Borrow(hir::AutoBorrow::Ref(mutability)) => {
+ match (result, mutability) {
+ (CallReceiverAdjust::RefMut, hir::Mutability::Shared) => {}
+ (_, hir::Mutability::Mut) => result = CallReceiverAdjust::RefMut,
+ (_, hir::Mutability::Shared) => result = CallReceiverAdjust::Ref,
+ }
+ }
+ _ => {}
+ }
+
+ i += 1;
+ }
+ }
+ result
+}
+
+fn transform_method_call_into_assoc_fn(
+ sema: &Semantics<'_, RootDatabase>,
+ source_change: &mut SourceChange,
+ f: hir::Function,
+ find_path_config: FindPathConfig,
+) {
+ let calls = Definition::Function(f).usages(sema).all();
+ for (_file_id, calls) in calls {
+ for call in calls {
+ let Some(fn_name) = call.name.as_name_ref() else { continue };
+ let Some(method_call) = fn_name.syntax().parent().and_then(ast::MethodCallExpr::cast)
+ else {
+ continue;
+ };
+ let Some(mut self_arg) = method_call.receiver() else {
+ continue;
+ };
+
+ let Some(scope) = sema.scope(fn_name.syntax()) else {
+ continue;
+ };
+ let self_adjust = method_to_assoc_fn_call_self_adjust(sema, &self_arg);
+
+ // Strip parentheses, function arguments have higher precedence than any operator.
+ while let ast::Expr::ParenExpr(it) = &self_arg {
+ self_arg = match it.expr() {
+ Some(it) => it,
+ None => break,
+ };
+ }
+
+ let needs_comma = method_call.arg_list().is_some_and(|it| it.args().next().is_some());
+
+ let self_needs_parens = self_adjust != CallReceiverAdjust::None
+ && self_arg.precedence().needs_parentheses_in(ExprPrecedence::Prefix);
+
+ let replace_start = method_call.syntax().text_range().start();
+ let replace_end = method_call
+ .arg_list()
+ .and_then(|it| it.l_paren_token())
+ .map(|it| it.text_range().end())
+ .unwrap_or_else(|| method_call.syntax().text_range().end());
+ let replace_range = TextRange::new(replace_start, replace_end);
+ let macro_file = sema.hir_file_for(fn_name.syntax());
+ let Some((replace_range, _)) =
+ InFile::new(macro_file, replace_range).original_node_file_range_opt(sema.db)
+ else {
+ continue;
+ };
+
+ let fn_container_path = match f.container(sema.db) {
+ hir::ItemContainer::Trait(trait_) => {
+ // FIXME: We always put it as `Trait::function`. Is it better to use `Type::function` (but
+ // that could conflict with an inherent method)? Or maybe `<Type as Trait>::function`?
+ // Or let the user decide?
+ let Some(path) = scope.module().find_path(
+ sema.db,
+ hir::ItemInNs::Types(trait_.into()),
+ find_path_config,
+ ) else {
+ continue;
+ };
+ path.display(sema.db, replace_range.file_id.edition(sema.db)).to_string()
+ }
+ hir::ItemContainer::Impl(impl_) => {
+ let ty = impl_.self_ty(sema.db);
+ match ty.as_adt() {
+ Some(adt) => {
+ let Some(path) = scope.module().find_path(
+ sema.db,
+ hir::ItemInNs::Types(adt.into()),
+ find_path_config,
+ ) else {
+ continue;
+ };
+ path.display(sema.db, replace_range.file_id.edition(sema.db))
+ .to_string()
+ }
+ None => {
+ let Ok(mut ty) =
+ ty.display_source_code(sema.db, scope.module().into(), false)
+ else {
+ continue;
+ };
+ ty.insert(0, '<');
+ ty.push('>');
+ ty
+ }
+ }
+ }
+ _ => continue,
+ };
+
+ let Some(macro_mapped_self) = sema.original_range_opt(self_arg.syntax()) else {
+ continue;
+ };
+ let mut replacement = String::new();
+ replacement.push_str(&fn_container_path);
+ replacement.push_str("::");
+ format_to!(replacement, "{fn_name}");
+ replacement.push('(');
+ replacement.push_str(match self_adjust {
+ CallReceiverAdjust::Deref => "*",
+ CallReceiverAdjust::Ref => "&",
+ CallReceiverAdjust::RefMut => "&mut ",
+ CallReceiverAdjust::None => "",
+ });
+ if self_needs_parens {
+ replacement.push('(');
+ }
+ replacement.push_str(macro_mapped_self.text(sema.db));
+ if self_needs_parens {
+ replacement.push(')');
+ }
+ if needs_comma {
+ replacement.push_str(", ");
+ }
+
+ source_change.insert_source_edit(
+ replace_range.file_id.file_id(sema.db),
+ TextEdit::replace(replace_range.range, replacement),
+ );
+ }
+ }
+}
+
fn rename_self_to_param(
sema: &Semantics<'_, RootDatabase>,
local: hir::Local,
self_param: hir::SelfParam,
new_name: &Name,
identifier_kind: IdentifierKind,
+ find_path_config: FindPathConfig,
) -> RenameResult<SourceChange> {
if identifier_kind == IdentifierKind::LowercaseSelf {
// Let's do nothing rather than complain.
@@ -527,6 +735,11 @@
return Ok(SourceChange::default());
}
+ let fn_def = match local.parent(sema.db) {
+ hir::DefWithBody::Function(func) => func,
+ _ => bail!("Cannot rename local to self outside of function"),
+ };
+
let InFile { file_id, value: self_param } =
sema.source(self_param).ok_or_else(|| format_err!("cannot find function source"))?;
@@ -554,6 +767,7 @@
),
)
}));
+ transform_method_call_into_assoc_fn(sema, &mut source_change, fn_def, find_path_config);
Ok(source_change)
}
@@ -587,7 +801,10 @@
use crate::fixture;
- use super::{RangeInfo, RenameError};
+ use super::{RangeInfo, RenameConfig, RenameError};
+
+ const TEST_CONFIG: RenameConfig =
+ RenameConfig { prefer_no_std: false, prefer_prelude: true, prefer_absolute: false };
#[track_caller]
fn check(
@@ -603,7 +820,7 @@
panic!("Prepare rename to '{new_name}' was failed: {err}")
}
let rename_result = analysis
- .rename(position, new_name)
+ .rename(position, new_name, &TEST_CONFIG)
.unwrap_or_else(|err| panic!("Rename to '{new_name}' was cancelled: {err}"));
match rename_result {
Ok(source_change) => {
@@ -635,7 +852,7 @@
#[track_caller]
fn check_conflicts(new_name: &str, #[rust_analyzer::rust_fixture] ra_fixture: &str) {
let (analysis, position, conflicts) = fixture::annotations(ra_fixture);
- let source_change = analysis.rename(position, new_name).unwrap().unwrap();
+ let source_change = analysis.rename(position, new_name, &TEST_CONFIG).unwrap().unwrap();
let expected_conflicts = conflicts
.into_iter()
.map(|(file_range, _)| (file_range.file_id, file_range.range))
@@ -662,8 +879,10 @@
expect: Expect,
) {
let (analysis, position) = fixture::position(ra_fixture);
- let source_change =
- analysis.rename(position, new_name).unwrap().expect("Expect returned a RenameError");
+ let source_change = analysis
+ .rename(position, new_name, &TEST_CONFIG)
+ .unwrap()
+ .expect("Expect returned a RenameError");
expect.assert_eq(&filter_expect(source_change))
}
@@ -3589,4 +3808,115 @@
"#,
);
}
+
+ #[test]
+ fn rename_to_self_callers_in_macro() {
+ check(
+ "self",
+ r#"
+struct Foo;
+
+impl Foo {
+ fn foo(th$0is: &Self, v: i32) {}
+}
+
+macro_rules! m { ($it:expr) => { $it } }
+fn bar(v: Foo) {
+ m!(Foo::foo(&v, 123));
+}
+ "#,
+ r#"
+struct Foo;
+
+impl Foo {
+ fn foo(&self, v: i32) {}
+}
+
+macro_rules! m { ($it:expr) => { $it } }
+fn bar(v: Foo) {
+ m!(v.foo( 123));
+}
+ "#,
+ );
+ }
+
+ #[test]
+ fn rename_from_self_callers() {
+ check(
+ "this",
+ r#"
+//- minicore: add
+struct Foo;
+impl Foo {
+ fn foo(&sel$0f) {}
+}
+impl core::ops::Add for Foo {
+ type Output = Foo;
+
+ fn add(self, _rhs: Self) -> Self::Output {
+ Foo
+ }
+}
+
+fn bar(v: &Foo) {
+ v.foo();
+ (Foo + Foo).foo();
+}
+
+mod baz {
+ fn baz(v: super::Foo) {
+ v.foo();
+ }
+}
+ "#,
+ r#"
+struct Foo;
+impl Foo {
+ fn foo(this: &Self) {}
+}
+impl core::ops::Add for Foo {
+ type Output = Foo;
+
+ fn add(self, _rhs: Self) -> Self::Output {
+ Foo
+ }
+}
+
+fn bar(v: &Foo) {
+ Foo::foo(v);
+ Foo::foo(&(Foo + Foo));
+}
+
+mod baz {
+ fn baz(v: super::Foo) {
+ crate::Foo::foo(&v);
+ }
+}
+ "#,
+ );
+ // Multiple args:
+ check(
+ "this",
+ r#"
+struct Foo;
+impl Foo {
+ fn foo(&sel$0f, _v: i32) {}
+}
+
+fn bar() {
+ Foo.foo(1);
+}
+ "#,
+ r#"
+struct Foo;
+impl Foo {
+ fn foo(this: &Self, _v: i32) {}
+}
+
+fn bar() {
+ Foo::foo(&Foo, 1);
+}
+ "#,
+ );
+ }
}
diff --git a/crates/ide/src/signature_help.rs b/crates/ide/src/signature_help.rs
index 5f7e12c..f9ec448 100644
--- a/crates/ide/src/signature_help.rs
+++ b/crates/ide/src/signature_help.rs
@@ -175,6 +175,9 @@
match callable.kind() {
hir::CallableKind::Function(func) => {
res.doc = func.docs(db);
+ if func.is_async(db) {
+ format_to!(res.signature, "async ");
+ }
format_to!(res.signature, "fn {}", func.name(db).display(db, edition));
let generic_params = GenericDef::Function(func)
@@ -283,13 +286,16 @@
}
};
match callable.kind() {
- hir::CallableKind::Function(func) if callable.return_type().contains_unknown() => {
- render(func.ret_type(db))
+ hir::CallableKind::Function(func) => render(func.async_ret_type(db).unwrap_or_else(|| {
+ if callable.return_type().contains_unknown() {
+ func.ret_type(db)
+ } else {
+ callable.return_type()
+ }
+ })),
+ hir::CallableKind::Closure(_) | hir::CallableKind::FnPtr | hir::CallableKind::FnImpl(_) => {
+ render(callable.return_type())
}
- hir::CallableKind::Function(_)
- | hir::CallableKind::Closure(_)
- | hir::CallableKind::FnPtr
- | hir::CallableKind::FnImpl(_) => render(callable.return_type()),
hir::CallableKind::TupleStruct(_) | hir::CallableKind::TupleEnumVariant(_) => {}
}
Some(res)
@@ -751,13 +757,7 @@
#[track_caller]
fn check(#[rust_analyzer::rust_fixture] ra_fixture: &str, expect: Expect) {
- let fixture = format!(
- r#"
-//- minicore: sized, fn
-{ra_fixture}
- "#
- );
- let (db, position) = position(&fixture);
+ let (db, position) = position(ra_fixture);
let sig_help = hir::attach_db(&db, || crate::signature_help::signature_help(&db, position));
let actual = match sig_help {
Some(sig_help) => {
@@ -795,6 +795,7 @@
fn test_fn_signature_two_args() {
check(
r#"
+//- minicore: sized, fn
fn foo(x: u32, y: u32) -> u32 {x + y}
fn bar() { foo($03, ); }
"#,
@@ -805,6 +806,7 @@
);
check(
r#"
+//- minicore: sized, fn
fn foo(x: u32, y: u32) -> u32 {x + y}
fn bar() { foo(3$0, ); }
"#,
@@ -815,6 +817,7 @@
);
check(
r#"
+//- minicore: sized, fn
fn foo(x: u32, y: u32) -> u32 {x + y}
fn bar() { foo(3,$0 ); }
"#,
@@ -825,6 +828,7 @@
);
check(
r#"
+//- minicore: sized, fn
fn foo(x: u32, y: u32) -> u32 {x + y}
fn bar() { foo(3, $0); }
"#,
@@ -839,6 +843,7 @@
fn test_fn_signature_two_args_empty() {
check(
r#"
+//- minicore: sized, fn
fn foo(x: u32, y: u32) -> u32 {x + y}
fn bar() { foo($0); }
"#,
@@ -853,6 +858,7 @@
fn test_fn_signature_two_args_first_generics() {
check(
r#"
+//- minicore: sized, fn
fn foo<T, U: Copy + Display>(x: T, y: U) -> u32
where T: Copy + Display, U: Debug
{ x + y }
@@ -870,6 +876,7 @@
fn test_fn_signature_no_params() {
check(
r#"
+//- minicore: sized, fn
fn foo<T>() -> T where T: Copy + Display {}
fn bar() { foo($0); }
"#,
@@ -883,6 +890,7 @@
fn test_fn_signature_for_impl() {
check(
r#"
+//- minicore: sized, fn
struct F;
impl F { pub fn new() { } }
fn bar() {
@@ -899,6 +907,7 @@
fn test_fn_signature_for_method_self() {
check(
r#"
+//- minicore: sized, fn
struct S;
impl S { pub fn do_it(&self) {} }
@@ -917,6 +926,7 @@
fn test_fn_signature_for_method_with_arg() {
check(
r#"
+//- minicore: sized, fn
struct S;
impl S {
fn foo(&self, x: i32) {}
@@ -935,6 +945,7 @@
fn test_fn_signature_for_generic_method() {
check(
r#"
+//- minicore: sized, fn
struct S<T>(T);
impl<T> S<T> {
fn foo(&self, x: T) {}
@@ -953,6 +964,7 @@
fn test_fn_signature_for_method_with_arg_as_assoc_fn() {
check(
r#"
+//- minicore: sized, fn
struct S;
impl S {
fn foo(&self, x: i32) {}
@@ -971,6 +983,7 @@
fn test_fn_signature_with_docs_simple() {
check(
r#"
+//- minicore: sized, fn
/// test
// non-doc-comment
fn foo(j: u32) -> u32 {
@@ -994,6 +1007,7 @@
fn test_fn_signature_with_docs() {
check(
r#"
+//- minicore: sized, fn
/// Adds one to the number given.
///
/// # Examples
@@ -1031,6 +1045,7 @@
fn test_fn_signature_with_docs_impl() {
check(
r#"
+//- minicore: sized, fn
struct addr;
impl addr {
/// Adds one to the number given.
@@ -1073,6 +1088,7 @@
fn test_fn_signature_with_docs_from_actix() {
check(
r#"
+//- minicore: sized, fn
trait Actor {
/// Actor execution context type
type Context;
@@ -1106,6 +1122,7 @@
fn call_info_bad_offset() {
check(
r#"
+//- minicore: sized, fn
fn foo(x: u32, y: u32) -> u32 {x + y}
fn bar() { foo $0 (3, ); }
"#,
@@ -1117,6 +1134,7 @@
fn outside_of_arg_list() {
check(
r#"
+//- minicore: sized, fn
fn foo(a: u8) {}
fn f() {
foo(123)$0
@@ -1126,6 +1144,7 @@
);
check(
r#"
+//- minicore: sized, fn
fn foo<T>(a: u8) {}
fn f() {
foo::<u32>$0()
@@ -1135,6 +1154,7 @@
);
check(
r#"
+//- minicore: sized, fn
fn foo(a: u8) -> u8 {a}
fn bar(a: u8) -> u8 {a}
fn f() {
@@ -1148,6 +1168,7 @@
);
check(
r#"
+//- minicore: sized, fn
struct Vec<T>(T);
struct Vec2<T>(T);
fn f() {
@@ -1165,6 +1186,7 @@
fn test_nested_method_in_lambda() {
check(
r#"
+//- minicore: sized, fn
struct Foo;
impl Foo { fn bar(&self, _: u32) { } }
@@ -1186,6 +1208,7 @@
fn works_for_tuple_structs() {
check(
r#"
+//- minicore: sized, fn
/// A cool tuple struct
struct S(u32, i32);
fn main() {
@@ -1205,6 +1228,7 @@
fn tuple_struct_pat() {
check(
r#"
+//- minicore: sized, fn
/// A cool tuple struct
struct S(u32, i32);
fn main() {
@@ -1224,6 +1248,7 @@
fn tuple_struct_pat_rest() {
check(
r#"
+//- minicore: sized, fn
/// A cool tuple struct
struct S(u32, i32, f32, u16);
fn main() {
@@ -1239,6 +1264,7 @@
);
check(
r#"
+//- minicore: sized, fn
/// A cool tuple struct
struct S(u32, i32, f32, u16, u8);
fn main() {
@@ -1254,6 +1280,7 @@
);
check(
r#"
+//- minicore: sized, fn
/// A cool tuple struct
struct S(u32, i32, f32, u16);
fn main() {
@@ -1269,6 +1296,7 @@
);
check(
r#"
+//- minicore: sized, fn
/// A cool tuple struct
struct S(u32, i32, f32, u16, u8);
fn main() {
@@ -1284,6 +1312,7 @@
);
check(
r#"
+//- minicore: sized, fn
/// A cool tuple struct
struct S(u32, i32, f32, u16);
fn main() {
@@ -1299,6 +1328,7 @@
);
check(
r#"
+//- minicore: sized, fn
/// A cool tuple struct
struct S(u32, i32, f32, u16);
fn main() {
@@ -1318,6 +1348,7 @@
fn generic_struct() {
check(
r#"
+//- minicore: sized, fn
struct S<T>(T);
fn main() {
let s = S($0);
@@ -1334,6 +1365,7 @@
fn works_for_enum_variants() {
check(
r#"
+//- minicore: sized, fn
enum E {
/// A Variant
A(i32),
@@ -1360,6 +1392,7 @@
fn cant_call_struct_record() {
check(
r#"
+//- minicore: sized, fn
struct S { x: u32, y: i32 }
fn main() {
let s = S($0);
@@ -1373,6 +1406,7 @@
fn cant_call_enum_record() {
check(
r#"
+//- minicore: sized, fn
enum E {
/// A Variant
A(i32),
@@ -1394,6 +1428,7 @@
fn fn_signature_for_call_in_macro() {
check(
r#"
+//- minicore: sized, fn
macro_rules! id { ($($tt:tt)*) => { $($tt)* } }
fn foo() { }
id! {
@@ -1410,6 +1445,7 @@
fn fn_signature_for_method_call_defined_in_macro() {
check(
r#"
+//- minicore: sized, fn
macro_rules! id { ($($tt:tt)*) => { $($tt)* } }
struct S;
id! {
@@ -1429,6 +1465,7 @@
fn call_info_for_lambdas() {
check(
r#"
+//- minicore: sized, fn
struct S;
fn foo(s: S) -> i32 { 92 }
fn main() {
@@ -1443,6 +1480,7 @@
);
check(
r#"
+//- minicore: sized, fn
struct S;
fn foo(s: S) -> i32 { 92 }
fn main() {
@@ -1456,6 +1494,7 @@
);
check(
r#"
+//- minicore: sized, fn
struct S;
fn foo(s: S) -> i32 { 92 }
fn main() {
@@ -1474,6 +1513,7 @@
fn call_info_for_fn_def_over_reference() {
check(
r#"
+//- minicore: sized, fn
struct S;
fn foo(s: S) -> i32 { 92 }
fn main() {
@@ -1492,6 +1532,7 @@
fn call_info_for_fn_ptr() {
check(
r#"
+//- minicore: sized, fn
fn main(f: fn(i32, f64) -> char) {
f(0, $0)
}
@@ -1507,6 +1548,7 @@
fn call_info_for_fn_impl() {
check(
r#"
+//- minicore: sized, fn
struct S;
impl core::ops::FnOnce<(i32, f64)> for S {
type Output = char;
@@ -1524,6 +1566,7 @@
);
check(
r#"
+//- minicore: sized, fn
struct S;
impl core::ops::FnOnce<(i32, f64)> for S {
type Output = char;
@@ -1541,6 +1584,7 @@
);
check(
r#"
+//- minicore: sized, fn
struct S;
impl core::ops::FnOnce<(i32, f64)> for S {
type Output = char;
@@ -1556,6 +1600,7 @@
);
check(
r#"
+//- minicore: sized, fn
struct S;
impl core::ops::FnOnce<(i32, f64)> for S {
type Output = char;
@@ -1576,6 +1621,7 @@
fn call_info_for_unclosed_call() {
check(
r#"
+//- minicore: sized, fn
fn foo(foo: u32, bar: u32) {}
fn main() {
foo($0
@@ -1588,6 +1634,7 @@
// check with surrounding space
check(
r#"
+//- minicore: sized, fn
fn foo(foo: u32, bar: u32) {}
fn main() {
foo( $0
@@ -1603,6 +1650,7 @@
fn test_multiline_argument() {
check(
r#"
+//- minicore: sized, fn
fn callee(a: u8, b: u8) {}
fn main() {
callee(match 0 {
@@ -1613,6 +1661,7 @@
);
check(
r#"
+//- minicore: sized, fn
fn callee(a: u8, b: u8) {}
fn main() {
callee(match 0 {
@@ -1626,6 +1675,7 @@
);
check(
r#"
+//- minicore: sized, fn
fn callee(a: u8, b: u8) {}
fn main() {
callee($0match 0 {
@@ -1643,6 +1693,7 @@
fn test_generics_simple() {
check(
r#"
+//- minicore: sized, fn
/// Option docs.
enum Option<T> {
Some(T),
@@ -1666,6 +1717,7 @@
fn test_generics_on_variant() {
check(
r#"
+//- minicore: sized, fn
/// Option docs.
enum Option<T> {
/// Some docs.
@@ -1693,6 +1745,7 @@
fn test_lots_of_generics() {
check(
r#"
+//- minicore: sized, fn
trait Tr<T> {}
struct S<T>(T);
@@ -1716,6 +1769,7 @@
fn test_generics_in_trait_ufcs() {
check(
r#"
+//- minicore: sized, fn
trait Tr {
fn f<T: Tr, U>() {}
}
@@ -1739,6 +1793,7 @@
fn test_generics_in_method_call() {
check(
r#"
+//- minicore: sized, fn
struct S;
impl S {
@@ -1760,6 +1815,7 @@
fn test_generic_param_in_method_call() {
check(
r#"
+//- minicore: sized, fn
struct Foo;
impl Foo {
fn test<V>(&mut self, val: V) {}
@@ -1779,6 +1835,7 @@
fn test_generic_kinds() {
check(
r#"
+//- minicore: sized, fn
fn callee<'a, const A: u8, T, const C: u8>() {}
fn f() {
@@ -1792,6 +1849,7 @@
);
check(
r#"
+//- minicore: sized, fn
fn callee<'a, const A: u8, T, const C: u8>() {}
fn f() {
@@ -1809,6 +1867,7 @@
fn test_trait_assoc_types() {
check(
r#"
+//- minicore: sized, fn
trait Trait<'a, T> {
type Assoc;
}
@@ -1821,6 +1880,7 @@
);
check(
r#"
+//- minicore: sized, fn
trait Iterator {
type Item;
}
@@ -1833,6 +1893,7 @@
);
check(
r#"
+//- minicore: sized, fn
trait Iterator {
type Item;
}
@@ -1845,6 +1906,7 @@
);
check(
r#"
+//- minicore: sized, fn
trait Tr {
type A;
type B;
@@ -1858,6 +1920,7 @@
);
check(
r#"
+//- minicore: sized, fn
trait Tr {
type A;
type B;
@@ -1871,6 +1934,7 @@
);
check(
r#"
+//- minicore: sized, fn
trait Tr {
type A;
type B;
@@ -1884,6 +1948,7 @@
);
check(
r#"
+//- minicore: sized, fn
trait Tr {
type A;
type B;
@@ -1901,6 +1966,7 @@
fn test_supertrait_assoc() {
check(
r#"
+//- minicore: sized, fn
trait Super {
type SuperTy;
}
@@ -1920,6 +1986,7 @@
fn no_assoc_types_outside_type_bounds() {
check(
r#"
+//- minicore: sized, fn
trait Tr<T> {
type Assoc;
}
@@ -1938,6 +2005,7 @@
// FIXME: Substitute type vars in impl trait (`U` -> `i8`)
check(
r#"
+//- minicore: sized, fn
trait Trait<T> {}
struct Wrap<T>(T);
fn foo<U>(x: Wrap<impl Trait<U>>) {}
@@ -1956,6 +2024,7 @@
fn fully_qualified_syntax() {
check(
r#"
+//- minicore: sized, fn
fn f() {
trait A { fn foo(&self, other: Self); }
A::foo(&self$0, other);
@@ -1972,6 +2041,7 @@
fn help_for_generic_call() {
check(
r#"
+//- minicore: sized, fn
fn f<F: FnOnce(u8, u16) -> i32>(f: F) {
f($0)
}
@@ -1983,6 +2053,7 @@
);
check(
r#"
+//- minicore: sized, fn
fn f<T, F: FnMut(&T, u16) -> &T>(f: F) {
f($0)
}
@@ -1996,8 +2067,15 @@
#[test]
fn regression_13579() {
+ // FIXME(next-solver): There should be signature help available here.
+ // The reason it is not is because of a trait solver bug. Since `Error` is not provided
+ // nor it can be inferred, it becomes an error type. The bug is that the solver ignores
+ // predicates on error types, and they do not guide infer vars, not allowing us to infer
+ // that `take`'s return type is callable.
+ // https://github.com/rust-lang/rust/pull/146602 should fix the solver bug.
check(
r#"
+//- minicore: sized, fn
fn f() {
take(2)($0);
}
@@ -2008,9 +2086,7 @@
move || count
}
"#,
- expect![[r#"
- impl Fn() -> i32
- "#]],
+ expect![""],
);
}
@@ -2018,6 +2094,7 @@
fn record_literal() {
check(
r#"
+//- minicore: sized, fn
struct Strukt<T, U = ()> {
t: T,
u: U,
@@ -2041,6 +2118,7 @@
fn record_literal_nonexistent_field() {
check(
r#"
+//- minicore: sized, fn
struct Strukt {
a: u8,
}
@@ -2062,6 +2140,7 @@
fn tuple_variant_record_literal() {
check(
r#"
+//- minicore: sized, fn
enum Opt {
Some(u8),
}
@@ -2076,6 +2155,7 @@
);
check(
r#"
+//- minicore: sized, fn
enum Opt {
Some(u8),
}
@@ -2094,6 +2174,7 @@
fn record_literal_self() {
check(
r#"
+//- minicore: sized, fn
struct S { t: u8 }
impl S {
fn new() -> Self {
@@ -2112,6 +2193,7 @@
fn record_pat() {
check(
r#"
+//- minicore: sized, fn
struct Strukt<T, U = ()> {
t: T,
u: U,
@@ -2135,6 +2217,7 @@
fn test_enum_in_nested_method_in_lambda() {
check(
r#"
+//- minicore: sized, fn
enum A {
A,
B
@@ -2158,6 +2241,7 @@
fn test_tuple_expr_free() {
check(
r#"
+//- minicore: sized, fn
fn main() {
(0$0, 1, 3);
}
@@ -2169,6 +2253,7 @@
);
check(
r#"
+//- minicore: sized, fn
fn main() {
($0 1, 3);
}
@@ -2180,6 +2265,7 @@
);
check(
r#"
+//- minicore: sized, fn
fn main() {
(1, 3 $0);
}
@@ -2191,6 +2277,7 @@
);
check(
r#"
+//- minicore: sized, fn
fn main() {
(1, 3 $0,);
}
@@ -2206,6 +2293,7 @@
fn test_tuple_expr_expected() {
check(
r#"
+//- minicore: sized, fn
fn main() {
let _: (&str, u32, u32)= ($0, 1, 3);
}
@@ -2218,6 +2306,7 @@
// FIXME: Should typeck report a 4-ary tuple for the expression here?
check(
r#"
+//- minicore: sized, fn
fn main() {
let _: (&str, u32, u32, u32) = ($0, 1, 3);
}
@@ -2229,6 +2318,7 @@
);
check(
r#"
+//- minicore: sized, fn
fn main() {
let _: (&str, u32, u32)= ($0, 1, 3, 5);
}
@@ -2244,6 +2334,7 @@
fn test_tuple_pat_free() {
check(
r#"
+//- minicore: sized, fn
fn main() {
let ($0, 1, 3);
}
@@ -2255,6 +2346,7 @@
);
check(
r#"
+//- minicore: sized, fn
fn main() {
let (0$0, 1, 3);
}
@@ -2266,6 +2358,7 @@
);
check(
r#"
+//- minicore: sized, fn
fn main() {
let ($0 1, 3);
}
@@ -2277,6 +2370,7 @@
);
check(
r#"
+//- minicore: sized, fn
fn main() {
let (1, 3 $0);
}
@@ -2288,6 +2382,7 @@
);
check(
r#"
+//- minicore: sized, fn
fn main() {
let (1, 3 $0,);
}
@@ -2299,6 +2394,7 @@
);
check(
r#"
+//- minicore: sized, fn
fn main() {
let (1, 3 $0, ..);
}
@@ -2310,6 +2406,7 @@
);
check(
r#"
+//- minicore: sized, fn
fn main() {
let (1, 3, .., $0);
}
@@ -2326,6 +2423,7 @@
fn test_tuple_pat_expected() {
check(
r#"
+//- minicore: sized, fn
fn main() {
let (0$0, 1, 3): (i32, i32, i32);
}
@@ -2337,6 +2435,7 @@
);
check(
r#"
+//- minicore: sized, fn
fn main() {
let ($0, 1, 3): (i32, i32, i32);
}
@@ -2348,6 +2447,7 @@
);
check(
r#"
+//- minicore: sized, fn
fn main() {
let (1, 3 $0): (i32,);
}
@@ -2359,6 +2459,7 @@
);
check(
r#"
+//- minicore: sized, fn
fn main() {
let (1, 3 $0, ..): (i32, i32, i32, i32);
}
@@ -2370,6 +2471,7 @@
);
check(
r#"
+//- minicore: sized, fn
fn main() {
let (1, 3, .., $0): (i32, i32, i32);
}
@@ -2384,6 +2486,7 @@
fn test_tuple_pat_expected_inferred() {
check(
r#"
+//- minicore: sized, fn
fn main() {
let (0$0, 1, 3) = (1, 2 ,3);
}
@@ -2395,6 +2498,7 @@
);
check(
r#"
+//- minicore: sized, fn
fn main() {
let ($0 1, 3) = (1, 2, 3);
}
@@ -2407,6 +2511,7 @@
);
check(
r#"
+//- minicore: sized, fn
fn main() {
let (1, 3 $0) = (1,);
}
@@ -2418,6 +2523,7 @@
);
check(
r#"
+//- minicore: sized, fn
fn main() {
let (1, 3 $0, ..) = (1, 2, 3, 4);
}
@@ -2429,6 +2535,7 @@
);
check(
r#"
+//- minicore: sized, fn
fn main() {
let (1, 3, .., $0) = (1, 2, 3);
}
@@ -2444,6 +2551,7 @@
fn test_tuple_generic_param() {
check(
r#"
+//- minicore: sized, fn
struct S<T>(T);
fn main() {
@@ -2461,6 +2569,7 @@
fn test_enum_generic_param() {
check(
r#"
+//- minicore: sized, fn
enum Option<T> {
Some(T),
None,
@@ -2481,6 +2590,7 @@
fn test_enum_variant_generic_param() {
check(
r#"
+//- minicore: sized, fn
enum Option<T> {
Some(T),
None,
@@ -2501,6 +2611,7 @@
fn test_generic_arg_with_default() {
check(
r#"
+//- minicore: sized, fn
struct S<T = u8> {
field: T,
}
@@ -2517,6 +2628,7 @@
check(
r#"
+//- minicore: sized, fn
struct S<const C: u8 = 5> {
field: C,
}
@@ -2531,4 +2643,27 @@
"#]],
);
}
+
+ #[test]
+ fn test_async_function() {
+ check(
+ r#"
+//- minicore: sized, fn, future, result
+pub async fn conn_mut<F, T>(f: F) -> Result<T, i32>
+where
+ F: FnOnce() -> T,
+{
+ Ok(f())
+}
+
+fn main() {
+ conn_mut($0)
+}
+ "#,
+ expect![[r#"
+ async fn conn_mut<F: FnOnce() -> T, T>(f: F) -> Result<T, i32>
+ ^^^^
+ "#]],
+ );
+ }
}
diff --git a/crates/ide/src/syntax_highlighting.rs b/crates/ide/src/syntax_highlighting.rs
index 66895cb..531c7e1 100644
--- a/crates/ide/src/syntax_highlighting.rs
+++ b/crates/ide/src/syntax_highlighting.rs
@@ -114,9 +114,9 @@
// |-----------|--------------------------------|
// |operator| Emitted for general operators.|
// |arithmetic| Emitted for the arithmetic operators `+`, `-`, `*`, `/`, `+=`, `-=`, `*=`, `/=`.|
-// |bitwise| Emitted for the bitwise operators `|`, `&`, `!`, `^`, `|=`, `&=`, `^=`.|
+// |bitwise| Emitted for the bitwise operators `\|`, `&`, `!`, `^`, `\|=`, `&=`, `^=`.|
// |comparison| Emitted for the comparison oerators `>`, `<`, `==`, `>=`, `<=`, `!=`.|
-// |logical| Emitted for the logical operators `||`, `&&`, `!`.|
+// |logical| Emitted for the logical operators `\|\|`, `&&`, `!`.|
//
// - For punctuation:
//
@@ -172,20 +172,20 @@
// |constant| Emitted for const.|
// |consuming| Emitted for locals that are being consumed when use in a function call.|
// |controlFlow| Emitted for control-flow related tokens, this includes th `?` operator.|
-// |crateRoot| Emitted for crate names, like `serde` and `crate.|
+// |crateRoot| Emitted for crate names, like `serde` and `crate`.|
// |declaration| Emitted for names of definitions, like `foo` in `fn foo(){}`.|
-// |defaultLibrary| Emitted for items from built-in crates (std, core, allc, test and proc_macro).|
+// |defaultLibrary| Emitted for items from built-in crates (std, core, alloc, test and proc_macro).|
// |documentation| Emitted for documentation comment.|
// |injected| Emitted for doc-string injected highlighting like rust source blocks in documentation.|
// |intraDocLink| Emitted for intra doc links in doc-string.|
-// |library| Emitted for items that are defined outside of the current crae.|
+// |library| Emitted for items that are defined outside of the current crate.|
// |macro| Emitted for tokens inside macro call.|
// |mutable| Emitted for mutable locals and statics as well as functions taking `&mut self`.|
-// |public| Emitted for items that are from the current crate and are `pub.|
-// |reference| Emitted for locals behind a reference and functions taking self` by reference.|
-// |static| Emitted for "static" functions, also known as functions that d not take a `self` param, as well as statics and consts.|
+// |public| Emitted for items that are from the current crate and are `pub`.|
+// |reference| Emitted for locals behind a reference and functions taking `self` by reference.|
+// |static| Emitted for "static" functions, also known as functions that do not take a `self` param, as well as statics and consts.|
// |trait| Emitted for associated trait item.|
-// |unsafe| Emitted for unsafe operations, like unsafe function calls, as ell as the `unsafe` token.|
+// |unsafe| Emitted for unsafe operations, like unsafe function calls, as well as the `unsafe` token.|
//
// 
// 
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_block_mod_items.html b/crates/ide/src/syntax_highlighting/test_data/highlight_block_mod_items.html
index 3beda39..711f534 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_block_mod_items.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_block_mod_items.html
@@ -53,7 +53,7 @@
<span class="macro public">foo</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="struct declaration macro public">Bar</span><span class="parenthesis">)</span><span class="semicolon">;</span>
<span class="keyword">fn</span> <span class="function declaration">func</span><span class="parenthesis">(</span><span class="punctuation">_</span><span class="colon">:</span> <span class="module">y</span><span class="operator">::</span><span class="struct public">Bar</span><span class="parenthesis">)</span> <span class="brace">{</span>
<span class="keyword">mod</span> <span class="module declaration">inner</span> <span class="brace">{</span>
- <span class="keyword">struct</span> <span class="struct declaration">Innerest</span><span class="angle"><</span><span class="keyword const">const</span> <span class="const_param const declaration">C</span><span class="colon">:</span> <span class="unresolved_reference">usize</span><span class="angle">></span> <span class="brace">{</span> <span class="field declaration">field</span><span class="colon">:</span> <span class="bracket">[</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span> <span class="brace">{</span><span class="const_param const">C</span><span class="brace">}</span><span class="bracket">]</span> <span class="brace">}</span>
+ <span class="keyword">struct</span> <span class="struct declaration">Innerest</span><span class="angle"><</span><span class="keyword const">const</span> <span class="const_param const declaration">C</span><span class="colon">:</span> <span class="builtin_type">usize</span><span class="angle">></span> <span class="brace">{</span> <span class="field declaration">field</span><span class="colon">:</span> <span class="bracket">[</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span> <span class="brace">{</span><span class="const_param const">C</span><span class="brace">}</span><span class="bracket">]</span> <span class="brace">}</span>
<span class="brace">}</span>
<span class="brace">}</span>
<span class="brace">}</span>
diff --git a/crates/ide/src/syntax_highlighting/test_data/regression_20952.html b/crates/ide/src/syntax_highlighting/test_data/regression_20952.html
new file mode 100644
index 0000000..2c0250c
--- /dev/null
+++ b/crates/ide/src/syntax_highlighting/test_data/regression_20952.html
@@ -0,0 +1,45 @@
+
+<style>
+body { margin: 0; }
+pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padding: 0.4em; }
+
+.lifetime { color: #DFAF8F; font-style: italic; }
+.label { color: #DFAF8F; font-style: italic; }
+.comment { color: #7F9F7F; }
+.documentation { color: #629755; }
+.intra_doc_link { font-style: italic; }
+.injected { opacity: 0.65 ; }
+.struct, .enum { color: #7CB8BB; }
+.enum_variant { color: #BDE0F3; }
+.string_literal { color: #CC9393; }
+.field { color: #94BFF3; }
+.function { color: #93E0E3; }
+.parameter { color: #94BFF3; }
+.text { color: #DCDCCC; }
+.type { color: #7CB8BB; }
+.builtin_type { color: #8CD0D3; }
+.type_param { color: #DFAF8F; }
+.attribute { color: #94BFF3; }
+.numeric_literal { color: #BFEBBF; }
+.bool_literal { color: #BFE6EB; }
+.macro { color: #94BFF3; }
+.proc_macro { color: #94BFF3; text-decoration: underline; }
+.derive { color: #94BFF3; font-style: italic; }
+.module { color: #AFD8AF; }
+.value_param { color: #DCDCCC; }
+.variable { color: #DCDCCC; }
+.format_specifier { color: #CC696B; }
+.mutable { text-decoration: underline; }
+.escape_sequence { color: #94BFF3; }
+.keyword { color: #F0DFAF; font-weight: bold; }
+.control { font-style: italic; }
+.reference { font-style: italic; font-weight: bold; }
+.const { font-weight: bolder; }
+.unsafe { color: #BC8383; }
+
+.invalid_escape_sequence { color: #FC5555; text-decoration: wavy underline; }
+.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
+</style>
+<pre><code><span class="keyword">fn</span> <span class="function declaration">main</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span>
+ <span class="macro default_library library">format_args</span><span class="macro_bang">!</span><span class="parenthesis">(</span>"{} {}, {} (подозрение на спам: {:.2}%)"б<span class="parenthesis">)</span><span class="semicolon">;</span>
+<span class="brace">}</span></code></pre>
\ No newline at end of file
diff --git a/crates/ide/src/syntax_highlighting/tests.rs b/crates/ide/src/syntax_highlighting/tests.rs
index 4e84127..58c613e 100644
--- a/crates/ide/src/syntax_highlighting/tests.rs
+++ b/crates/ide/src/syntax_highlighting/tests.rs
@@ -1497,3 +1497,17 @@
false,
);
}
+
+#[test]
+fn regression_20952() {
+ check_highlighting(
+ r#"
+//- minicore: fmt
+fn main() {
+ format_args!("{} {}, {} (подозрение на спам: {:.2}%)"б);
+}
+"#,
+ expect_file!["./test_data/regression_20952.html"],
+ false,
+ );
+}
diff --git a/crates/parser/src/grammar.rs b/crates/parser/src/grammar.rs
index 8ddf50d..bf84302 100644
--- a/crates/parser/src/grammar.rs
+++ b/crates/parser/src/grammar.rs
@@ -94,7 +94,17 @@
pub(crate) fn source_file(p: &mut Parser<'_>) {
let m = p.start();
+ // test frontmatter
+ // #!/usr/bin/env cargo
+ //
+ // ---
+ // [dependencies]
+ // clap = { version = "4.2", features = ["derive"] }
+ // ---
+ //
+ // fn main() {}
p.eat(SHEBANG);
+ p.eat(FRONTMATTER);
items::mod_contents(p, false);
m.complete(p, SOURCE_FILE);
}
diff --git a/crates/parser/src/grammar/expressions/atom.rs b/crates/parser/src/grammar/expressions/atom.rs
index ed8a91c..cde62e0 100644
--- a/crates/parser/src/grammar/expressions/atom.rs
+++ b/crates/parser/src/grammar/expressions/atom.rs
@@ -588,6 +588,12 @@
}
params::param_list_closure(p);
if opt_ret_type(p) {
+ // test_err closure_ret_recovery
+ // fn foo() { || -> A> { let x = 1; } }
+ while p.at(T![>]) {
+ // recover from unbalanced return type brackets
+ p.err_and_bump("expected a curly brace");
+ }
// test lambda_ret_block
// fn main() { || -> i32 { 92 }(); }
block_expr(p);
diff --git a/crates/parser/src/grammar/items.rs b/crates/parser/src/grammar/items.rs
index 8e551b0..c609f93 100644
--- a/crates/parser/src/grammar/items.rs
+++ b/crates/parser/src/grammar/items.rs
@@ -424,6 +424,14 @@
// fn bar() -> () {}
opt_ret_type(p);
+ // test_err fn_ret_recovery
+ // fn foo() -> A>]) { let x = 1; }
+ // fn foo() -> A>]) where T: Copy { let x = 1; }
+ while p.at(T![')']) | p.at(T![']']) | p.at(T![>]) {
+ // recover from unbalanced return type brackets
+ p.err_and_bump("expected a curly brace");
+ }
+
// test function_where_clause
// fn foo<T>() where T: Copy {}
generic_params::opt_where_clause(p);
diff --git a/crates/parser/test_data/generated/runner.rs b/crates/parser/test_data/generated/runner.rs
index 9bdbe56..7f5ff0e 100644
--- a/crates/parser/test_data/generated/runner.rs
+++ b/crates/parser/test_data/generated/runner.rs
@@ -265,6 +265,8 @@
#[test]
fn for_type() { run_and_expect_no_errors("test_data/parser/inline/ok/for_type.rs"); }
#[test]
+ fn frontmatter() { run_and_expect_no_errors("test_data/parser/inline/ok/frontmatter.rs"); }
+ #[test]
fn full_range_expr() {
run_and_expect_no_errors("test_data/parser/inline/ok/full_range_expr.rs");
}
@@ -749,6 +751,10 @@
#[test]
fn bad_asm_expr() { run_and_expect_errors("test_data/parser/inline/err/bad_asm_expr.rs"); }
#[test]
+ fn closure_ret_recovery() {
+ run_and_expect_errors("test_data/parser/inline/err/closure_ret_recovery.rs");
+ }
+ #[test]
fn comma_after_default_values_syntax() {
run_and_expect_errors("test_data/parser/inline/err/comma_after_default_values_syntax.rs");
}
@@ -773,6 +779,10 @@
run_and_expect_errors("test_data/parser/inline/err/fn_pointer_type_missing_fn.rs");
}
#[test]
+ fn fn_ret_recovery() {
+ run_and_expect_errors("test_data/parser/inline/err/fn_ret_recovery.rs");
+ }
+ #[test]
fn gen_fn() {
run_and_expect_errors_with_edition(
"test_data/parser/inline/err/gen_fn.rs",
diff --git a/crates/parser/test_data/parser/inline/err/closure_ret_recovery.rast b/crates/parser/test_data/parser/inline/err/closure_ret_recovery.rast
new file mode 100644
index 0000000..f626651
--- /dev/null
+++ b/crates/parser/test_data/parser/inline/err/closure_ret_recovery.rast
@@ -0,0 +1,52 @@
+SOURCE_FILE
+ FN
+ FN_KW "fn"
+ WHITESPACE " "
+ NAME
+ IDENT "foo"
+ PARAM_LIST
+ L_PAREN "("
+ R_PAREN ")"
+ WHITESPACE " "
+ BLOCK_EXPR
+ STMT_LIST
+ L_CURLY "{"
+ WHITESPACE " "
+ CLOSURE_EXPR
+ PARAM_LIST
+ PIPE "|"
+ PIPE "|"
+ WHITESPACE " "
+ RET_TYPE
+ THIN_ARROW "->"
+ WHITESPACE " "
+ PATH_TYPE
+ PATH
+ PATH_SEGMENT
+ NAME_REF
+ IDENT "A"
+ ERROR
+ R_ANGLE ">"
+ WHITESPACE " "
+ BLOCK_EXPR
+ STMT_LIST
+ L_CURLY "{"
+ WHITESPACE " "
+ LET_STMT
+ LET_KW "let"
+ WHITESPACE " "
+ IDENT_PAT
+ NAME
+ IDENT "x"
+ WHITESPACE " "
+ EQ "="
+ WHITESPACE " "
+ LITERAL
+ INT_NUMBER "1"
+ SEMICOLON ";"
+ WHITESPACE " "
+ R_CURLY "}"
+ WHITESPACE " "
+ R_CURLY "}"
+ WHITESPACE "\n"
+error 18: expected a curly brace
diff --git a/crates/parser/test_data/parser/inline/err/closure_ret_recovery.rs b/crates/parser/test_data/parser/inline/err/closure_ret_recovery.rs
new file mode 100644
index 0000000..7a758ec
--- /dev/null
+++ b/crates/parser/test_data/parser/inline/err/closure_ret_recovery.rs
@@ -0,0 +1 @@
+fn foo() { || -> A> { let x = 1; } }
diff --git a/crates/parser/test_data/parser/inline/err/fn_ret_recovery.rast b/crates/parser/test_data/parser/inline/err/fn_ret_recovery.rast
new file mode 100644
index 0000000..0323df8
--- /dev/null
+++ b/crates/parser/test_data/parser/inline/err/fn_ret_recovery.rast
@@ -0,0 +1,112 @@
+SOURCE_FILE
+ FN
+ FN_KW "fn"
+ WHITESPACE " "
+ NAME
+ IDENT "foo"
+ PARAM_LIST
+ L_PAREN "("
+ R_PAREN ")"
+ WHITESPACE " "
+ RET_TYPE
+ THIN_ARROW "->"
+ WHITESPACE " "
+ PATH_TYPE
+ PATH
+ PATH_SEGMENT
+ NAME_REF
+ IDENT "A"
+ ERROR
+ R_ANGLE ">"
+ ERROR
+ R_BRACK "]"
+ ERROR
+ R_PAREN ")"
+ WHITESPACE " "
+ BLOCK_EXPR
+ STMT_LIST
+ L_CURLY "{"
+ WHITESPACE " "
+ LET_STMT
+ LET_KW "let"
+ WHITESPACE " "
+ IDENT_PAT
+ NAME
+ IDENT "x"
+ WHITESPACE " "
+ EQ "="
+ WHITESPACE " "
+ LITERAL
+ INT_NUMBER "1"
+ SEMICOLON ";"
+ WHITESPACE " "
+ R_CURLY "}"
+ WHITESPACE "\n"
+ FN
+ FN_KW "fn"
+ WHITESPACE " "
+ NAME
+ IDENT "foo"
+ PARAM_LIST
+ L_PAREN "("
+ R_PAREN ")"
+ WHITESPACE " "
+ RET_TYPE
+ THIN_ARROW "->"
+ WHITESPACE " "
+ PATH_TYPE
+ PATH
+ PATH_SEGMENT
+ NAME_REF
+ IDENT "A"
+ ERROR
+ R_ANGLE ">"
+ ERROR
+ R_BRACK "]"
+ ERROR
+ R_PAREN ")"
+ WHITESPACE " "
+ WHERE_CLAUSE
+ WHERE_KW "where"
+ WHITESPACE " "
+ WHERE_PRED
+ PATH_TYPE
+ PATH
+ PATH_SEGMENT
+ NAME_REF
+ IDENT "T"
+ COLON ":"
+ WHITESPACE " "
+ TYPE_BOUND_LIST
+ TYPE_BOUND
+ PATH_TYPE
+ PATH
+ PATH_SEGMENT
+ NAME_REF
+ IDENT "Copy"
+ WHITESPACE " "
+ BLOCK_EXPR
+ STMT_LIST
+ L_CURLY "{"
+ WHITESPACE " "
+ LET_STMT
+ LET_KW "let"
+ WHITESPACE " "
+ IDENT_PAT
+ NAME
+ IDENT "x"
+ WHITESPACE " "
+ EQ "="
+ WHITESPACE " "
+ LITERAL
+ INT_NUMBER "1"
+ SEMICOLON ";"
+ WHITESPACE " "
+ R_CURLY "}"
+ WHITESPACE "\n"
+error 13: expected a curly brace
+error 14: expected a curly brace
+error 15: expected a curly brace
+error 45: expected a curly brace
+error 46: expected a curly brace
+error 47: expected a curly brace
diff --git a/crates/parser/test_data/parser/inline/err/fn_ret_recovery.rs b/crates/parser/test_data/parser/inline/err/fn_ret_recovery.rs
new file mode 100644
index 0000000..73e3d84
--- /dev/null
+++ b/crates/parser/test_data/parser/inline/err/fn_ret_recovery.rs
@@ -0,0 +1,2 @@
+fn foo() -> A>]) { let x = 1; }
+fn foo() -> A>]) where T: Copy { let x = 1; }
diff --git a/crates/parser/test_data/parser/inline/ok/frontmatter.rast b/crates/parser/test_data/parser/inline/ok/frontmatter.rast
new file mode 100644
index 0000000..bad8959
--- /dev/null
+++ b/crates/parser/test_data/parser/inline/ok/frontmatter.rast
@@ -0,0 +1,18 @@
+SOURCE_FILE
+ SHEBANG "#!/usr/bin/env cargo\n"
+ FRONTMATTER "\n---\n[dependencies]\nclap = { version = \"4.2\", features = [\"derive\"] }\n---\n"
+ WHITESPACE "\n"
+ FN
+ FN_KW "fn"
+ WHITESPACE " "
+ NAME
+ IDENT "main"
+ PARAM_LIST
+ L_PAREN "("
+ R_PAREN ")"
+ WHITESPACE " "
+ BLOCK_EXPR
+ STMT_LIST
+ L_CURLY "{"
+ R_CURLY "}"
+ WHITESPACE "\n"
diff --git a/crates/parser/test_data/parser/inline/ok/frontmatter.rs b/crates/parser/test_data/parser/inline/ok/frontmatter.rs
new file mode 100644
index 0000000..1f9f7a7
--- /dev/null
+++ b/crates/parser/test_data/parser/inline/ok/frontmatter.rs
@@ -0,0 +1,8 @@
+#!/usr/bin/env cargo
+
+---
+[dependencies]
+clap = { version = "4.2", features = ["derive"] }
+---
+
+fn main() {}
diff --git a/crates/proc-macro-srv/src/tests/mod.rs b/crates/proc-macro-srv/src/tests/mod.rs
index 08495f5..d4f9976 100644
--- a/crates/proc-macro-srv/src/tests/mod.rs
+++ b/crates/proc-macro-srv/src/tests/mod.rs
@@ -326,7 +326,7 @@
PUNCH , [alone] 1
LITERAL Str hello bridge 1
PUNCH , [alone] 1
- LITERAL Str suffixedsuffix 1
+ LITERAL Err(()) "suffixed"suffix 1
PUNCH , [alone] 1
LITERAL StrRaw(2) raw 1
PUNCH , [alone] 1
@@ -372,7 +372,7 @@
PUNCH , [alone] 42:Root[0000, 0]@27..28#ROOT2024
LITERAL Str hello bridge 42:Root[0000, 0]@29..43#ROOT2024
PUNCH , [alone] 42:Root[0000, 0]@43..44#ROOT2024
- LITERAL Str suffixedsuffix 42:Root[0000, 0]@45..61#ROOT2024
+ LITERAL Err(()) "suffixed"suffix 42:Root[0000, 0]@45..61#ROOT2024
PUNCH , [alone] 42:Root[0000, 0]@61..62#ROOT2024
LITERAL StrRaw(2) raw 42:Root[0000, 0]@63..73#ROOT2024
PUNCH , [alone] 42:Root[0000, 0]@73..74#ROOT2024
diff --git a/crates/project-model/src/build_dependencies.rs b/crates/project-model/src/build_dependencies.rs
index 3a682d5..fedc694 100644
--- a/crates/project-model/src/build_dependencies.rs
+++ b/crates/project-model/src/build_dependencies.rs
@@ -86,6 +86,7 @@
config,
&allowed_features,
workspace.manifest_path(),
+ workspace.target_directory().as_ref(),
current_dir,
sysroot,
toolchain,
@@ -106,8 +107,9 @@
let (_guard, cmd) = Self::build_command(
config,
&Default::default(),
- // This is not gonna be used anyways, so just construct a dummy here
+ // These are not gonna be used anyways, so just construct a dummy here
&ManifestPath::try_from(working_directory.clone()).unwrap(),
+ working_directory.as_ref(),
working_directory,
&Sysroot::empty(),
None,
@@ -430,6 +432,7 @@
config: &CargoConfig,
allowed_features: &FxHashSet<String>,
manifest_path: &ManifestPath,
+ target_dir: &Utf8Path,
current_dir: &AbsPath,
sysroot: &Sysroot,
toolchain: Option<&semver::Version>,
@@ -450,8 +453,9 @@
cmd.arg("--manifest-path");
cmd.arg(manifest_path);
- if let Some(target_dir) = &config.target_dir {
- cmd.arg("--target-dir").arg(target_dir);
+ if let Some(target_dir) = config.target_dir_config.target_dir(Some(target_dir)) {
+ cmd.arg("--target-dir");
+ cmd.arg(target_dir.as_ref());
}
if let Some(target) = &config.target {
diff --git a/crates/project-model/src/cargo_workspace.rs b/crates/project-model/src/cargo_workspace.rs
index 76ba01f..7311049 100644
--- a/crates/project-model/src/cargo_workspace.rs
+++ b/crates/project-model/src/cargo_workspace.rs
@@ -1,7 +1,6 @@
//! See [`CargoWorkspace`].
-use std::ops;
-use std::str::from_utf8;
+use std::{borrow::Cow, ops, str::from_utf8};
use anyhow::Context;
use base_db::Env;
@@ -95,6 +94,29 @@
}
}
+#[derive(Clone, Debug, Default, PartialEq, Eq)]
+pub enum TargetDirectoryConfig {
+ #[default]
+ None,
+ UseSubdirectory,
+ Directory(Utf8PathBuf),
+}
+
+impl TargetDirectoryConfig {
+ pub fn target_dir<'a>(
+ &'a self,
+ ws_target_dir: Option<&'a Utf8Path>,
+ ) -> Option<Cow<'a, Utf8Path>> {
+ match self {
+ TargetDirectoryConfig::None => None,
+ TargetDirectoryConfig::UseSubdirectory => {
+ Some(Cow::Owned(ws_target_dir?.join("rust-analyzer")))
+ }
+ TargetDirectoryConfig::Directory(dir) => Some(Cow::Borrowed(dir)),
+ }
+ }
+}
+
#[derive(Default, Clone, Debug, PartialEq, Eq)]
pub struct CargoConfig {
/// Whether to pass `--all-targets` to cargo invocations.
@@ -121,7 +143,7 @@
pub extra_env: FxHashMap<String, Option<String>>,
pub invocation_strategy: InvocationStrategy,
/// Optional path to use instead of `target` when building
- pub target_dir: Option<Utf8PathBuf>,
+ pub target_dir_config: TargetDirectoryConfig,
/// Gate `#[test]` behind `#[cfg(test)]`
pub set_test: bool,
/// Load the project without any dependencies
@@ -715,21 +737,15 @@
}
}
- pub(crate) fn no_deps_metadata(&self) -> Option<&cargo_metadata::Metadata> {
- self.no_deps_result.as_ref().ok()
- }
-
/// Executes the metadata-fetching command.
///
/// A successful result may still contain a metadata error if the full fetch failed,
/// but the fallback `--no-deps` pre-fetch succeeded during command construction.
pub(crate) fn exec(
self,
- target_dir: &Utf8Path,
locked: bool,
progress: &dyn Fn(String),
) -> anyhow::Result<(cargo_metadata::Metadata, Option<anyhow::Error>)> {
- _ = target_dir;
let Self {
mut command,
manifest_path: _,
diff --git a/crates/project-model/src/lib.rs b/crates/project-model/src/lib.rs
index e36b904..910bc0a 100644
--- a/crates/project-model/src/lib.rs
+++ b/crates/project-model/src/lib.rs
@@ -62,7 +62,7 @@
build_dependencies::{ProcMacroDylibPath, WorkspaceBuildScripts},
cargo_workspace::{
CargoConfig, CargoFeatures, CargoMetadataConfig, CargoWorkspace, Package, PackageData,
- PackageDependency, RustLibSource, Target, TargetData, TargetKind,
+ PackageDependency, RustLibSource, Target, TargetData, TargetDirectoryConfig, TargetKind,
},
manifest_path::ManifestPath,
project_json::{ProjectJson, ProjectJsonData},
diff --git a/crates/project-model/src/sysroot.rs b/crates/project-model/src/sysroot.rs
index 5cc399b..920afe6 100644
--- a/crates/project-model/src/sysroot.rs
+++ b/crates/project-model/src/sysroot.rs
@@ -9,7 +9,7 @@
use anyhow::{Result, format_err};
use itertools::Itertools;
-use paths::{AbsPath, AbsPathBuf, Utf8Path, Utf8PathBuf};
+use paths::{AbsPath, AbsPathBuf, Utf8PathBuf};
use rustc_hash::FxHashMap;
use stdx::format_to;
use toolchain::{Tool, probe_for_binary};
@@ -219,7 +219,6 @@
&self,
sysroot_source_config: &RustSourceWorkspaceConfig,
no_deps: bool,
- target_dir: &Utf8Path,
progress: &dyn Fn(String),
) -> Option<RustLibSrcWorkspace> {
assert!(matches!(self.workspace, RustLibSrcWorkspace::Empty), "workspace already loaded");
@@ -233,7 +232,6 @@
match self.load_library_via_cargo(
&library_manifest,
src_root,
- target_dir,
cargo_config,
no_deps,
progress,
@@ -328,7 +326,6 @@
&self,
library_manifest: &ManifestPath,
current_dir: &AbsPath,
- target_dir: &Utf8Path,
cargo_config: &CargoMetadataConfig,
no_deps: bool,
progress: &dyn Fn(String),
@@ -345,7 +342,7 @@
let locked = true;
let (mut res, err) =
FetchMetadata::new(library_manifest, current_dir, &cargo_config, self, no_deps)
- .exec(target_dir, locked, progress)?;
+ .exec(locked, progress)?;
// Patch out `rustc-std-workspace-*` crates to point to the real crates.
// This is done prior to `CrateGraph` construction to prevent de-duplication logic from failing.
diff --git a/crates/project-model/src/tests.rs b/crates/project-model/src/tests.rs
index 711cdd1..1908fc0 100644
--- a/crates/project-model/src/tests.rs
+++ b/crates/project-model/src/tests.rs
@@ -238,12 +238,8 @@
);
let cwd = AbsPathBuf::assert_utf8(temp_dir().join("smoke_test_real_sysroot_cargo"));
std::fs::create_dir_all(&cwd).unwrap();
- let loaded_sysroot = sysroot.load_workspace(
- &RustSourceWorkspaceConfig::default_cargo(),
- false,
- &Utf8PathBuf::default(),
- &|_| (),
- );
+ let loaded_sysroot =
+ sysroot.load_workspace(&RustSourceWorkspaceConfig::default_cargo(), false, &|_| ());
if let Some(loaded_sysroot) = loaded_sysroot {
sysroot.set_workspace(loaded_sysroot);
}
diff --git a/crates/project-model/src/workspace.rs b/crates/project-model/src/workspace.rs
index b88db41..aa2e159 100644
--- a/crates/project-model/src/workspace.rs
+++ b/crates/project-model/src/workspace.rs
@@ -16,7 +16,7 @@
use rustc_hash::{FxHashMap, FxHashSet};
use semver::Version;
use span::{Edition, FileId};
-use toolchain::{NO_RUSTUP_AUTO_INSTALL_ENV, Tool};
+use toolchain::Tool;
use tracing::instrument;
use tracing::{debug, error, info};
use triomphe::Arc;
@@ -295,11 +295,6 @@
&sysroot,
*no_deps,
);
- let target_dir = config
- .target_dir
- .clone()
- .or_else(|| fetch_metadata.no_deps_metadata().map(|m| m.target_directory.clone()))
- .unwrap_or_else(|| workspace_dir.join("target").into());
// We spawn a bunch of processes to query various information about the workspace's
// toolchain and sysroot
@@ -345,7 +340,7 @@
},
&sysroot,
*no_deps,
- ).exec(&target_dir, true, progress) {
+ ).exec(true, progress) {
Ok((meta, _error)) => {
let workspace = CargoWorkspace::new(
meta,
@@ -374,16 +369,16 @@
})
});
- let cargo_metadata = s.spawn(|| fetch_metadata.exec(&target_dir, false, progress));
+ let cargo_metadata = s.spawn(|| fetch_metadata.exec(false, progress));
let loaded_sysroot = s.spawn(|| {
sysroot.load_workspace(
&RustSourceWorkspaceConfig::CargoMetadata(sysroot_metadata_config(
config,
+ workspace_dir,
&targets,
toolchain.clone(),
)),
config.no_deps,
- &target_dir,
progress,
)
});
@@ -463,12 +458,6 @@
let targets = target_tuple::get(query_config, config.target.as_deref(), &config.extra_env)
.unwrap_or_default();
let toolchain = version::get(query_config, &config.extra_env).ok().flatten();
- let project_root = project_json.project_root();
- let target_dir = config
- .target_dir
- .clone()
- .or_else(|| cargo_target_dir(project_json.manifest()?, &config.extra_env, &sysroot))
- .unwrap_or_else(|| project_root.join("target").into());
// We spawn a bunch of processes to query various information about the workspace's
// toolchain and sysroot
@@ -486,18 +475,17 @@
sysroot.load_workspace(
&RustSourceWorkspaceConfig::Json(*sysroot_project),
config.no_deps,
- &target_dir,
progress,
)
} else {
sysroot.load_workspace(
&RustSourceWorkspaceConfig::CargoMetadata(sysroot_metadata_config(
config,
+ project_json.project_root(),
&targets,
toolchain.clone(),
)),
config.no_deps,
- &target_dir,
progress,
)
}
@@ -545,20 +533,15 @@
.unwrap_or_default();
let rustc_cfg = rustc_cfg::get(query_config, None, &config.extra_env);
let target_data = target_data::get(query_config, None, &config.extra_env);
- let target_dir = config
- .target_dir
- .clone()
- .or_else(|| cargo_target_dir(detached_file, &config.extra_env, &sysroot))
- .unwrap_or_else(|| dir.join("target").into());
let loaded_sysroot = sysroot.load_workspace(
&RustSourceWorkspaceConfig::CargoMetadata(sysroot_metadata_config(
config,
+ dir,
&targets,
toolchain.clone(),
)),
config.no_deps,
- &target_dir,
&|_| (),
);
if let Some(loaded_sysroot) = loaded_sysroot {
@@ -579,21 +562,15 @@
&sysroot,
config.no_deps,
);
- let target_dir = config
- .target_dir
- .clone()
- .or_else(|| fetch_metadata.no_deps_metadata().map(|m| m.target_directory.clone()))
- .unwrap_or_else(|| dir.join("target").into());
- let cargo_script =
- fetch_metadata.exec(&target_dir, false, &|_| ()).ok().map(|(ws, error)| {
- let cargo_config_extra_env =
- cargo_config_env(detached_file, &config_file, &config.extra_env);
- (
- CargoWorkspace::new(ws, detached_file.clone(), cargo_config_extra_env, false),
- WorkspaceBuildScripts::default(),
- error.map(Arc::new),
- )
- });
+ let cargo_script = fetch_metadata.exec(false, &|_| ()).ok().map(|(ws, error)| {
+ let cargo_config_extra_env =
+ cargo_config_env(detached_file, &config_file, &config.extra_env);
+ (
+ CargoWorkspace::new(ws, detached_file.clone(), cargo_config_extra_env, false),
+ WorkspaceBuildScripts::default(),
+ error.map(Arc::new),
+ )
+ });
Ok(ProjectWorkspace {
kind: ProjectWorkspaceKind::DetachedFile {
@@ -1890,37 +1867,32 @@
fn sysroot_metadata_config(
config: &CargoConfig,
+ current_dir: &AbsPath,
targets: &[String],
toolchain_version: Option<Version>,
) -> CargoMetadataConfig {
+ // We run `cargo metadata` on sysroot with sysroot dir as a working directory, but still pass
+ // the `targets` from the cargo config evaluated from the workspace's `current_dir`.
+ // So, we need to *canonicalize* those *might-be-relative-paths-to-custom-target-json-files*.
+ //
+ // See https://github.com/rust-lang/cargo/blob/f7acf448fc127df9a77c52cc2bba027790ac4931/src/cargo/core/compiler/compile_kind.rs#L171-L192
+ let targets = targets
+ .iter()
+ .map(|target| {
+ if target.ends_with(".json") {
+ current_dir.join(target).to_string()
+ } else {
+ target.to_owned()
+ }
+ })
+ .collect();
+
CargoMetadataConfig {
features: Default::default(),
- targets: targets.to_vec(),
+ targets,
extra_args: Default::default(),
extra_env: config.extra_env.clone(),
toolchain_version,
kind: "sysroot",
}
}
-
-fn cargo_target_dir(
- manifest: &ManifestPath,
- extra_env: &FxHashMap<String, Option<String>>,
- sysroot: &Sysroot,
-) -> Option<Utf8PathBuf> {
- let cargo = sysroot.tool(Tool::Cargo, manifest.parent(), extra_env);
- let mut meta = cargo_metadata::MetadataCommand::new();
- meta.env(NO_RUSTUP_AUTO_INSTALL_ENV.0, NO_RUSTUP_AUTO_INSTALL_ENV.1);
- meta.cargo_path(cargo.get_program());
- meta.manifest_path(manifest);
- // `--no-deps` doesn't (over)write lockfiles as it doesn't do any package resolve.
- // So we can use it to get `target_directory` before copying lockfiles
- meta.no_deps();
- let mut other_options = vec![];
- if manifest.is_rust_manifest() {
- meta.env("RUSTC_BOOTSTRAP", "1");
- other_options.push("-Zscript".to_owned());
- }
- meta.other_options(other_options);
- meta.exec().map(|m| m.target_directory).ok()
-}
diff --git a/crates/rust-analyzer/Cargo.toml b/crates/rust-analyzer/Cargo.toml
index c746f84..b9dfe1f 100644
--- a/crates/rust-analyzer/Cargo.toml
+++ b/crates/rust-analyzer/Cargo.toml
@@ -42,6 +42,7 @@
num_cpus = "1.17.0"
mimalloc = { version = "0.1.46", default-features = false, optional = true }
lsp-server.workspace = true
+smallvec.workspace = true
tracing.workspace = true
tracing-subscriber.workspace = true
tracing-tree.workspace = true
@@ -53,6 +54,7 @@
memchr = "2.7.5"
cargo_metadata.workspace = true
process-wrap.workspace = true
+dhat = { version = "0.3.3", optional = true }
cfg.workspace = true
hir-def.workspace = true
@@ -105,6 +107,7 @@
"hir-ty/in-rust-tree",
"load-cargo/in-rust-tree",
]
+dhat = ["dep:dhat"]
[lints]
workspace = true
diff --git a/crates/rust-analyzer/src/cli/analysis_stats.rs b/crates/rust-analyzer/src/cli/analysis_stats.rs
index de24bc0..5e4a277 100644
--- a/crates/rust-analyzer/src/cli/analysis_stats.rs
+++ b/crates/rust-analyzer/src/cli/analysis_stats.rs
@@ -1214,6 +1214,7 @@
annotate_method_references: false,
annotate_enum_variant_references: false,
location: ide::AnnotationLocation::AboveName,
+ filter_adjacent_derive_implementations: false,
minicore: MiniCore::default(),
};
for &file_id in file_ids {
diff --git a/crates/rust-analyzer/src/cli/rustc_tests.rs b/crates/rust-analyzer/src/cli/rustc_tests.rs
index 2056714..eb28a47 100644
--- a/crates/rust-analyzer/src/cli/rustc_tests.rs
+++ b/crates/rust-analyzer/src/cli/rustc_tests.rs
@@ -9,7 +9,6 @@
use ide::{AnalysisHost, DiagnosticCode, DiagnosticsConfig};
use ide_db::base_db;
use itertools::Either;
-use paths::Utf8PathBuf;
use profile::StopWatch;
use project_model::toolchain_info::{QueryConfig, target_data};
use project_model::{
@@ -75,12 +74,8 @@
};
let mut sysroot = Sysroot::discover(tmp_file.parent().unwrap(), &cargo_config.extra_env);
- let loaded_sysroot = sysroot.load_workspace(
- &RustSourceWorkspaceConfig::default_cargo(),
- false,
- &Utf8PathBuf::default(),
- &|_| (),
- );
+ let loaded_sysroot =
+ sysroot.load_workspace(&RustSourceWorkspaceConfig::default_cargo(), false, &|_| ());
if let Some(loaded_sysroot) = loaded_sysroot {
sysroot.set_workspace(loaded_sysroot);
}
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index 652c2e3..b8c4b9f 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -10,9 +10,9 @@
use ide::{
AnnotationConfig, AssistConfig, CallHierarchyConfig, CallableSnippets, CompletionConfig,
CompletionFieldsToResolve, DiagnosticsConfig, GenericParameterHints, GotoDefinitionConfig,
- HighlightConfig, HighlightRelatedConfig, HoverConfig, HoverDocFormat, InlayFieldsToResolve,
- InlayHintsConfig, JoinLinesConfig, MemoryLayoutHoverConfig, MemoryLayoutHoverRenderKind,
- Snippet, SnippetScope, SourceRootId,
+ GotoImplementationConfig, HighlightConfig, HighlightRelatedConfig, HoverConfig, HoverDocFormat,
+ InlayFieldsToResolve, InlayHintsConfig, JoinLinesConfig, MemoryLayoutHoverConfig,
+ MemoryLayoutHoverRenderKind, RenameConfig, Snippet, SnippetScope, SourceRootId,
};
use ide_db::{
MiniCore, SnippetCap,
@@ -23,7 +23,7 @@
use paths::{Utf8Path, Utf8PathBuf};
use project_model::{
CargoConfig, CargoFeatures, ProjectJson, ProjectJsonData, ProjectJsonFromCommand,
- ProjectManifest, RustLibSource,
+ ProjectManifest, RustLibSource, TargetDirectoryConfig,
};
use rustc_hash::{FxHashMap, FxHashSet};
use semver::Version;
@@ -98,6 +98,9 @@
/// Code's `files.watcherExclude`.
files_exclude | files_excludeDirs: Vec<Utf8PathBuf> = vec![],
+ /// If this is `true`, when "Goto Implementations" and in "Implementations" lens, are triggered on a `struct` or `enum` or `union`, we filter out trait implementations that originate from `derive`s above the type.
+ gotoImplementations_filterAdjacentDerives: bool = false,
+
/// Highlight related return values while the cursor is on any `match`, `if`, or match arm
/// arrow (`=>`).
highlightRelated_branchExitPoints_enable: bool = true,
@@ -378,6 +381,12 @@
/// Internal config, path to proc-macro server executable.
procMacro_server: Option<Utf8PathBuf> = None,
+ /// The path where to save memory profiling output.
+ ///
+ /// **Note:** Memory profiling is not enabled by default in rust-analyzer builds, you need to build
+ /// from source for it.
+ profiling_memoryProfile: Option<Utf8PathBuf> = None,
+
/// Exclude imports from find-all-references.
references_excludeImports: bool = false,
@@ -1413,6 +1422,7 @@
// annotations
pub location: AnnotationLocation,
+ pub filter_adjacent_derive_implementations: bool,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
@@ -1469,6 +1479,7 @@
annotate_enum_variant_references: self.enum_variant_refs,
location: self.location.into(),
minicore,
+ filter_adjacent_derive_implementations: self.filter_adjacent_derive_implementations,
}
}
}
@@ -1705,6 +1716,14 @@
}
}
+ pub fn rename(&self, source_root: Option<SourceRootId>) -> RenameConfig {
+ RenameConfig {
+ prefer_no_std: self.imports_preferNoStd(source_root).to_owned(),
+ prefer_prelude: self.imports_preferPrelude(source_root).to_owned(),
+ prefer_absolute: self.imports_prefixExternPrelude(source_root).to_owned(),
+ }
+ }
+
pub fn call_hierarchy<'a>(&self, minicore: MiniCore<'a>) -> CallHierarchyConfig<'a> {
CallHierarchyConfig { exclude_tests: self.references_excludeTests().to_owned(), minicore }
}
@@ -2157,6 +2176,11 @@
Some(AbsPathBuf::try_from(path).unwrap_or_else(|path| self.root_path.join(path)))
}
+ pub fn dhat_output_file(&self) -> Option<AbsPathBuf> {
+ let path = self.profiling_memoryProfile().clone()?;
+ Some(AbsPathBuf::try_from(path).unwrap_or_else(|path| self.root_path.join(path)))
+ }
+
pub fn ignored_proc_macros(
&self,
source_root: Option<SourceRootId>,
@@ -2277,7 +2301,7 @@
run_build_script_command: self.cargo_buildScripts_overrideCommand(source_root).clone(),
extra_args: self.cargo_extraArgs(source_root).clone(),
extra_env: self.cargo_extraEnv(source_root).clone(),
- target_dir: self.target_dir_from_config(source_root),
+ target_dir_config: self.target_dir_from_config(source_root),
set_test: *self.cfg_setTest(source_root),
no_deps: *self.cargo_noDeps(source_root),
}
@@ -2365,7 +2389,7 @@
extra_args: self.extra_args(source_root).clone(),
extra_test_bin_args: self.runnables_extraTestBinaryArgs(source_root).clone(),
extra_env: self.extra_env(source_root).clone(),
- target_dir: self.target_dir_from_config(source_root),
+ target_dir_config: self.target_dir_from_config(source_root),
set_test: true,
}
}
@@ -2423,7 +2447,7 @@
extra_args: self.check_extra_args(source_root),
extra_test_bin_args: self.runnables_extraTestBinaryArgs(source_root).clone(),
extra_env: self.check_extra_env(source_root),
- target_dir: self.target_dir_from_config(source_root),
+ target_dir_config: self.target_dir_from_config(source_root),
set_test: *self.cfg_setTest(source_root),
},
ansi_color_output: self.color_diagnostic_output(),
@@ -2431,17 +2455,12 @@
}
}
- fn target_dir_from_config(&self, source_root: Option<SourceRootId>) -> Option<Utf8PathBuf> {
- self.cargo_targetDir(source_root).as_ref().and_then(|target_dir| match target_dir {
- TargetDirectory::UseSubdirectory(true) => {
- let env_var = env::var("CARGO_TARGET_DIR").ok();
- let mut path = Utf8PathBuf::from(env_var.as_deref().unwrap_or("target"));
- path.push("rust-analyzer");
- Some(path)
- }
- TargetDirectory::UseSubdirectory(false) => None,
- TargetDirectory::Directory(dir) => Some(dir.clone()),
- })
+ fn target_dir_from_config(&self, source_root: Option<SourceRootId>) -> TargetDirectoryConfig {
+ match &self.cargo_targetDir(source_root) {
+ Some(TargetDirectory::UseSubdirectory(true)) => TargetDirectoryConfig::UseSubdirectory,
+ Some(TargetDirectory::UseSubdirectory(false)) | None => TargetDirectoryConfig::None,
+ Some(TargetDirectory::Directory(dir)) => TargetDirectoryConfig::Directory(dir.clone()),
+ }
}
pub fn check_on_save(&self, source_root: Option<SourceRootId>) -> bool {
@@ -2495,6 +2514,15 @@
refs_trait: *self.lens_enable() && *self.lens_references_trait_enable(),
enum_variant_refs: *self.lens_enable() && *self.lens_references_enumVariant_enable(),
location: *self.lens_location(),
+ filter_adjacent_derive_implementations: *self
+ .gotoImplementations_filterAdjacentDerives(),
+ }
+ }
+
+ pub fn goto_implementation(&self) -> GotoImplementationConfig {
+ GotoImplementationConfig {
+ filter_adjacent_derive_implementations: *self
+ .gotoImplementations_filterAdjacentDerives(),
}
}
@@ -3958,7 +3986,7 @@
#[cfg(test)]
mod tests {
- use std::fs;
+ use std::{borrow::Cow, fs};
use test_utils::{ensure_file_contents, project_root};
@@ -4093,9 +4121,13 @@
(config, _, _) = config.apply_change(change);
assert_eq!(config.cargo_targetDir(None), &None);
- assert!(
- matches!(config.flycheck(None), FlycheckConfig::CargoCommand { options, .. } if options.target_dir.is_none())
- );
+ assert!(matches!(
+ config.flycheck(None),
+ FlycheckConfig::CargoCommand {
+ options: CargoOptions { target_dir_config: TargetDirectoryConfig::None, .. },
+ ..
+ }
+ ));
}
#[test]
@@ -4111,11 +4143,16 @@
(config, _, _) = config.apply_change(change);
assert_eq!(config.cargo_targetDir(None), &Some(TargetDirectory::UseSubdirectory(true)));
- let target =
+ let ws_target_dir =
Utf8PathBuf::from(std::env::var("CARGO_TARGET_DIR").unwrap_or("target".to_owned()));
- assert!(
- matches!(config.flycheck(None), FlycheckConfig::CargoCommand { options, .. } if options.target_dir == Some(target.join("rust-analyzer")))
- );
+ assert!(matches!(
+ config.flycheck(None),
+ FlycheckConfig::CargoCommand {
+ options: CargoOptions { target_dir_config, .. },
+ ..
+ } if target_dir_config.target_dir(Some(&ws_target_dir)).map(Cow::into_owned)
+ == Some(ws_target_dir.join("rust-analyzer"))
+ ));
}
#[test]
@@ -4134,8 +4171,13 @@
config.cargo_targetDir(None),
&Some(TargetDirectory::Directory(Utf8PathBuf::from("other_folder")))
);
- assert!(
- matches!(config.flycheck(None), FlycheckConfig::CargoCommand { options, .. } if options.target_dir == Some(Utf8PathBuf::from("other_folder")))
- );
+ assert!(matches!(
+ config.flycheck(None),
+ FlycheckConfig::CargoCommand {
+ options: CargoOptions { target_dir_config, .. },
+ ..
+ } if target_dir_config.target_dir(None).map(Cow::into_owned)
+ == Some(Utf8PathBuf::from("other_folder"))
+ ));
}
}
diff --git a/crates/rust-analyzer/src/diagnostics.rs b/crates/rust-analyzer/src/diagnostics.rs
index 4bfad98..4a24780 100644
--- a/crates/rust-analyzer/src/diagnostics.rs
+++ b/crates/rust-analyzer/src/diagnostics.rs
@@ -1,5 +1,5 @@
//! Book keeping for keeping diagnostics easily in sync with the client.
-pub(crate) mod to_proto;
+pub(crate) mod flycheck_to_proto;
use std::mem;
@@ -8,6 +8,7 @@
use ide_db::{FxHashMap, base_db::DbPanicContext};
use itertools::Itertools;
use rustc_hash::FxHashSet;
+use smallvec::SmallVec;
use stdx::iter_eq_by;
use triomphe::Arc;
@@ -57,7 +58,7 @@
#[derive(Debug, Clone)]
pub(crate) struct Fix {
// Fixes may be triggerable from multiple ranges.
- pub(crate) ranges: Vec<lsp_types::Range>,
+ pub(crate) ranges: SmallVec<[lsp_types::Range; 1]>,
pub(crate) action: lsp_ext::CodeAction,
}
diff --git a/crates/rust-analyzer/src/diagnostics/to_proto.rs b/crates/rust-analyzer/src/diagnostics/flycheck_to_proto.rs
similarity index 88%
rename from crates/rust-analyzer/src/diagnostics/to_proto.rs
rename to crates/rust-analyzer/src/diagnostics/flycheck_to_proto.rs
index 3f64628..a6d7bcb 100644
--- a/crates/rust-analyzer/src/diagnostics/to_proto.rs
+++ b/crates/rust-analyzer/src/diagnostics/flycheck_to_proto.rs
@@ -4,6 +4,7 @@
use crate::flycheck::{Applicability, DiagnosticLevel, DiagnosticSpan};
use itertools::Itertools;
use rustc_hash::FxHashMap;
+use smallvec::SmallVec;
use stdx::format_to;
use vfs::{AbsPath, AbsPathBuf};
@@ -18,12 +19,12 @@
fn diagnostic_severity(
config: &DiagnosticsMapConfig,
level: crate::flycheck::DiagnosticLevel,
- code: Option<crate::flycheck::DiagnosticCode>,
+ code: Option<&crate::flycheck::DiagnosticCode>,
) -> Option<lsp_types::DiagnosticSeverity> {
let res = match level {
DiagnosticLevel::Ice => lsp_types::DiagnosticSeverity::ERROR,
DiagnosticLevel::Error => lsp_types::DiagnosticSeverity::ERROR,
- DiagnosticLevel::Warning => match &code {
+ DiagnosticLevel::Warning => match code {
// HACK: special case for `warnings` rustc lint.
Some(code)
if config.warnings_as_hint.iter().any(|lint| {
@@ -143,11 +144,11 @@
fn diagnostic_related_information(
config: &DiagnosticsMapConfig,
workspace_root: &AbsPath,
- span: &DiagnosticSpan,
+ span: DiagnosticSpan,
snap: &GlobalStateSnapshot,
) -> Option<lsp_types::DiagnosticRelatedInformation> {
- let message = span.label.clone()?;
- let location = location(config, workspace_root, span, snap);
+ let location = location(config, workspace_root, &span, snap);
+ let message = span.label?;
Some(lsp_types::DiagnosticRelatedInformation { location, message })
}
@@ -184,7 +185,7 @@
rd: &crate::flycheck::Diagnostic,
snap: &GlobalStateSnapshot,
) -> MappedRustChildDiagnostic {
- let spans: Vec<&DiagnosticSpan> = rd.spans.iter().filter(|s| s.is_primary).collect();
+ let spans: SmallVec<[&DiagnosticSpan; 1]> = rd.spans.iter().filter(|s| s.is_primary).collect();
if spans.is_empty() {
// `rustc` uses these spanless children as a way to print multi-line
// messages
@@ -227,42 +228,37 @@
message.push_str(&suggestions);
}
- if edit_map.is_empty() {
- MappedRustChildDiagnostic::SubDiagnostic(SubDiagnostic {
- related: lsp_types::DiagnosticRelatedInformation {
- location: location(config, workspace_root, spans[0], snap),
- message,
- },
- suggested_fix: None,
- })
+ let suggested_fix = if edit_map.is_empty() {
+ None
} else {
- MappedRustChildDiagnostic::SubDiagnostic(SubDiagnostic {
- related: lsp_types::DiagnosticRelatedInformation {
- location: location(config, workspace_root, spans[0], snap),
- message: message.clone(),
+ Some(Box::new(Fix {
+ ranges: spans
+ .iter()
+ .map(|&span| location(config, workspace_root, span, snap).range)
+ .collect(),
+ action: lsp_ext::CodeAction {
+ title: message.clone(),
+ group: None,
+ kind: Some(lsp_types::CodeActionKind::QUICKFIX),
+ edit: Some(lsp_ext::SnippetWorkspaceEdit {
+ // FIXME: there's no good reason to use edit_map here....
+ changes: Some(edit_map),
+ document_changes: None,
+ change_annotations: None,
+ }),
+ is_preferred: Some(is_preferred),
+ data: None,
+ command: None,
},
- suggested_fix: Some(Box::new(Fix {
- ranges: spans
- .iter()
- .map(|&span| location(config, workspace_root, span, snap).range)
- .collect(),
- action: lsp_ext::CodeAction {
- title: message,
- group: None,
- kind: Some(lsp_types::CodeActionKind::QUICKFIX),
- edit: Some(lsp_ext::SnippetWorkspaceEdit {
- // FIXME: there's no good reason to use edit_map here....
- changes: Some(edit_map),
- document_changes: None,
- change_annotations: None,
- }),
- is_preferred: Some(is_preferred),
- data: None,
- command: None,
- },
- })),
- })
- }
+ }))
+ };
+ MappedRustChildDiagnostic::SubDiagnostic(SubDiagnostic {
+ related: lsp_types::DiagnosticRelatedInformation {
+ location: location(config, workspace_root, spans[0], snap),
+ message,
+ },
+ suggested_fix,
+ })
}
#[derive(Debug)]
@@ -284,48 +280,56 @@
/// If the diagnostic has no primary span this will return `None`
pub(crate) fn map_rust_diagnostic_to_lsp(
config: &DiagnosticsMapConfig,
- rd: &crate::flycheck::Diagnostic,
+ crate::flycheck::Diagnostic {
+ mut message,
+ code: diagnostic_code,
+ level,
+ spans,
+ children,
+ rendered,
+ ..
+ }: crate::flycheck::Diagnostic,
workspace_root: &AbsPath,
snap: &GlobalStateSnapshot,
) -> Vec<MappedRustDiagnostic> {
- let primary_spans: Vec<&DiagnosticSpan> = rd.spans.iter().filter(|s| s.is_primary).collect();
+ let (primary_spans, secondary_spans): (
+ SmallVec<[DiagnosticSpan; 1]>,
+ SmallVec<[DiagnosticSpan; 1]>,
+ ) = spans.into_iter().partition(|s| s.is_primary);
if primary_spans.is_empty() {
return Vec::new();
}
- let severity = diagnostic_severity(config, rd.level, rd.code.clone());
+ let mut code = diagnostic_code.as_ref().map(|c| &*c.code);
- let mut source = String::from("rustc");
- let mut code = rd.code.as_ref().map(|c| c.code.clone());
-
- if let Some(code_val) = &code
+ if let Some(code_val) = code
&& config.check_ignore.contains(code_val)
{
return Vec::new();
}
- if let Some(code_val) = &code {
+ let severity = diagnostic_severity(config, level, diagnostic_code.as_ref());
+
+ let mut source = "rustc";
+ if let Some(code_val) = code {
// See if this is an RFC #2103 scoped lint (e.g. from Clippy)
- let scoped_code: Vec<&str> = code_val.split("::").collect();
- if scoped_code.len() == 2 {
- source = String::from(scoped_code[0]);
- code = Some(String::from(scoped_code[1]));
+ if let Some((s, c)) = code_val.split("::").collect_tuple() {
+ source = s;
+ code = Some(c);
}
}
let mut needs_primary_span_label = true;
let mut subdiagnostics = Vec::new();
- let mut tags = Vec::new();
- for secondary_span in rd.spans.iter().filter(|s| !s.is_primary) {
+ for secondary_span in secondary_spans {
let related = diagnostic_related_information(config, workspace_root, secondary_span, snap);
if let Some(related) = related {
subdiagnostics.push(SubDiagnostic { related, suggested_fix: None });
}
}
- let mut message = rd.message.clone();
- for child in &rd.children {
+ for child in &children {
let child = map_rust_child_diagnostic(config, workspace_root, child, snap);
match child {
MappedRustChildDiagnostic::SubDiagnostic(sub) => {
@@ -340,155 +344,146 @@
}
}
}
+ let message = message;
- if let Some(code) = &rd.code {
- let code = code.code.as_str();
- if matches!(
- code,
- "dead_code"
- | "unknown_lints"
- | "unreachable_code"
- | "unused_attributes"
- | "unused_imports"
- | "unused_macros"
- | "unused_variables"
- ) {
- tags.push(lsp_types::DiagnosticTag::UNNECESSARY);
- }
-
- if matches!(code, "deprecated") {
- tags.push(lsp_types::DiagnosticTag::DEPRECATED);
+ let mut tag = None;
+ if let Some(code) = &diagnostic_code {
+ match &*code.code {
+ "dead_code" | "unknown_lints" | "unreachable_code" | "unused_attributes"
+ | "unused_imports" | "unused_macros" | "unused_variables" => {
+ tag = Some(lsp_types::DiagnosticTag::UNNECESSARY);
+ }
+ "deprecated" => {
+ tag = Some(lsp_types::DiagnosticTag::DEPRECATED);
+ }
+ _ => {}
}
}
- let code_description = match source.as_str() {
- "rustc" => rustc_code_description(code.as_deref()),
- "clippy" => clippy_code_description(code.as_deref()),
+ let code_description = match source {
+ "rustc" => rustc_code_description(code),
+ "clippy" => clippy_code_description(code),
_ => None,
};
+ // Each primary diagnostic span may result in multiple LSP diagnostics.
+ let mut diagnostics = Vec::new();
- primary_spans
- .iter()
- .flat_map(|primary_span| {
- let primary_location = primary_location(config, workspace_root, primary_span, snap);
- let message = {
- let mut message = message.clone();
- if needs_primary_span_label && let Some(primary_span_label) = &primary_span.label {
- format_to!(message, "\n{}", primary_span_label);
- }
- message
- };
- // Each primary diagnostic span may result in multiple LSP diagnostics.
- let mut diagnostics = Vec::new();
+ for primary_span in primary_spans {
+ let primary_location = primary_location(config, workspace_root, &primary_span, snap);
+ let message = {
+ let mut message = message.clone();
+ if needs_primary_span_label && let Some(primary_span_label) = &primary_span.label {
+ format_to!(message, "\n{}", primary_span_label);
+ }
+ message
+ };
- let mut related_info_macro_calls = vec![];
+ let mut related_info_macro_calls = vec![];
- // If error occurs from macro expansion, add related info pointing to
- // where the error originated
- // Also, we would generate an additional diagnostic, so that exact place of macro
- // will be highlighted in the error origin place.
- let span_stack = std::iter::successors(Some(*primary_span), |span| {
- Some(&span.expansion.as_ref()?.span)
- });
- for (i, span) in span_stack.enumerate() {
- if is_dummy_macro_file(&span.file_name) {
- continue;
- }
-
- // First span is the original diagnostic, others are macro call locations that
- // generated that code.
- let is_in_macro_call = i != 0;
-
- let secondary_location = location(config, workspace_root, span, snap);
- if secondary_location == primary_location {
- continue;
- }
- related_info_macro_calls.push(lsp_types::DiagnosticRelatedInformation {
- location: secondary_location.clone(),
- message: if is_in_macro_call {
- "Error originated from macro call here".to_owned()
- } else {
- "Actual error occurred here".to_owned()
- },
- });
- // For the additional in-macro diagnostic we add the inverse message pointing to the error location in code.
- let information_for_additional_diagnostic =
- vec![lsp_types::DiagnosticRelatedInformation {
- location: primary_location.clone(),
- message: "Exact error occurred here".to_owned(),
- }];
-
- let diagnostic = lsp_types::Diagnostic {
- range: secondary_location.range,
- // downgrade to hint if we're pointing at the macro
- severity: Some(lsp_types::DiagnosticSeverity::HINT),
- code: code.clone().map(lsp_types::NumberOrString::String),
- code_description: code_description.clone(),
- source: Some(source.clone()),
- message: message.clone(),
- related_information: Some(information_for_additional_diagnostic),
- tags: if tags.is_empty() { None } else { Some(tags.clone()) },
- data: Some(serde_json::json!({ "rendered": rd.rendered })),
- };
- diagnostics.push(MappedRustDiagnostic {
- url: secondary_location.uri,
- diagnostic,
- fix: None,
- });
+ // If error occurs from macro expansion, add related info pointing to
+ // where the error originated
+ // Also, we would generate an additional diagnostic, so that exact place of macro
+ // will be highlighted in the error origin place.
+ let span_stack =
+ std::iter::successors(Some(&primary_span), |span| Some(&span.expansion.as_ref()?.span))
+ .skip(1);
+ for (i, span) in span_stack.enumerate() {
+ if is_dummy_macro_file(&span.file_name) {
+ continue;
+ }
+ let secondary_location = location(config, workspace_root, span, snap);
+ if secondary_location == primary_location {
+ continue;
}
- // Emit the primary diagnostic.
- diagnostics.push(MappedRustDiagnostic {
- url: primary_location.uri.clone(),
- diagnostic: lsp_types::Diagnostic {
- range: primary_location.range,
- severity,
- code: code.clone().map(lsp_types::NumberOrString::String),
- code_description: code_description.clone(),
- source: Some(source.clone()),
- message,
- related_information: {
- let info = related_info_macro_calls
- .iter()
- .cloned()
- .chain(subdiagnostics.iter().map(|sub| sub.related.clone()))
- .collect::<Vec<_>>();
- if info.is_empty() { None } else { Some(info) }
- },
- tags: if tags.is_empty() { None } else { Some(tags.clone()) },
- data: Some(serde_json::json!({ "rendered": rd.rendered })),
+ // First span is the original diagnostic, others are macro call locations that
+ // generated that code.
+ let is_in_macro_call = i != 0;
+
+ related_info_macro_calls.push(lsp_types::DiagnosticRelatedInformation {
+ location: secondary_location.clone(),
+ message: if is_in_macro_call {
+ "Error originated from macro call here".to_owned()
+ } else {
+ "Actual error occurred here".to_owned()
},
+ });
+ // For the additional in-macro diagnostic we add the inverse message pointing to the error location in code.
+ let information_for_additional_diagnostic =
+ vec![lsp_types::DiagnosticRelatedInformation {
+ location: primary_location.clone(),
+ message: "Exact error occurred here".to_owned(),
+ }];
+
+ let diagnostic = lsp_types::Diagnostic {
+ range: secondary_location.range,
+ // downgrade to hint if we're pointing at the macro
+ severity: Some(lsp_types::DiagnosticSeverity::HINT),
+ code: code.map(ToOwned::to_owned).map(lsp_types::NumberOrString::String),
+ code_description: code_description.clone(),
+ source: Some(source.to_owned()),
+ message: message.clone(),
+ related_information: Some(information_for_additional_diagnostic),
+ tags: tag.clone().map(|tag| vec![tag]),
+ data: Some(serde_json::json!({ "rendered": rendered })),
+ };
+ diagnostics.push(MappedRustDiagnostic {
+ url: secondary_location.uri,
+ diagnostic,
fix: None,
});
+ }
- // Emit hint-level diagnostics for all `related_information` entries such as "help"s.
- // This is useful because they will show up in the user's editor, unlike
- // `related_information`, which just produces hard-to-read links, at least in VS Code.
- let back_ref = lsp_types::DiagnosticRelatedInformation {
- location: primary_location,
- message: "original diagnostic".to_owned(),
- };
- for sub in &subdiagnostics {
- diagnostics.push(MappedRustDiagnostic {
- url: sub.related.location.uri.clone(),
- fix: sub.suggested_fix.clone(),
- diagnostic: lsp_types::Diagnostic {
- range: sub.related.location.range,
- severity: Some(lsp_types::DiagnosticSeverity::HINT),
- code: code.clone().map(lsp_types::NumberOrString::String),
- code_description: code_description.clone(),
- source: Some(source.clone()),
- message: sub.related.message.clone(),
- related_information: Some(vec![back_ref.clone()]),
- tags: None, // don't apply modifiers again
- data: None,
- },
- });
- }
+ // Emit the primary diagnostic.
+ diagnostics.push(MappedRustDiagnostic {
+ url: primary_location.uri.clone(),
+ diagnostic: lsp_types::Diagnostic {
+ range: primary_location.range,
+ severity,
+ code: code.map(ToOwned::to_owned).map(lsp_types::NumberOrString::String),
+ code_description: code_description.clone(),
+ source: Some(source.to_owned()),
+ message,
+ related_information: {
+ let info = related_info_macro_calls
+ .iter()
+ .cloned()
+ .chain(subdiagnostics.iter().map(|sub| sub.related.clone()))
+ .collect::<Vec<_>>();
+ if info.is_empty() { None } else { Some(info) }
+ },
+ tags: tag.clone().map(|tag| vec![tag]),
+ data: Some(serde_json::json!({ "rendered": rendered })),
+ },
+ fix: None,
+ });
- diagnostics
- })
- .collect()
+ // Emit hint-level diagnostics for all `related_information` entries such as "help"s.
+ // This is useful because they will show up in the user's editor, unlike
+ // `related_information`, which just produces hard-to-read links, at least in VS Code.
+ let back_ref = lsp_types::DiagnosticRelatedInformation {
+ location: primary_location,
+ message: "original diagnostic".to_owned(),
+ };
+ for sub in &subdiagnostics {
+ diagnostics.push(MappedRustDiagnostic {
+ url: sub.related.location.uri.clone(),
+ fix: sub.suggested_fix.clone(),
+ diagnostic: lsp_types::Diagnostic {
+ range: sub.related.location.range,
+ severity: Some(lsp_types::DiagnosticSeverity::HINT),
+ code: code.map(ToOwned::to_owned).map(lsp_types::NumberOrString::String),
+ code_description: code_description.clone(),
+ source: Some(source.to_owned()),
+ message: sub.related.message.clone(),
+ related_information: Some(vec![back_ref.clone()]),
+ tags: None, // don't apply modifiers again
+ data: None,
+ },
+ });
+ }
+ }
+ diagnostics
}
fn rustc_code_description(code: Option<&str>) -> Option<lsp_types::CodeDescription> {
@@ -545,7 +540,7 @@
),
);
let snap = state.snapshot();
- let mut actual = map_rust_diagnostic_to_lsp(&config, &diagnostic, workspace_root, &snap);
+ let mut actual = map_rust_diagnostic_to_lsp(&config, diagnostic, workspace_root, &snap);
actual.iter_mut().for_each(|diag| diag.diagnostic.data = None);
expect.assert_debug_eq(&actual)
}
diff --git a/crates/rust-analyzer/src/diagnostics/test_data/macro_compiler_error.txt b/crates/rust-analyzer/src/diagnostics/test_data/macro_compiler_error.txt
index fe5cf9b..b44569b 100644
--- a/crates/rust-analyzer/src/diagnostics/test_data/macro_compiler_error.txt
+++ b/crates/rust-analyzer/src/diagnostics/test_data/macro_compiler_error.txt
@@ -191,7 +191,7 @@
},
},
},
- message: "Error originated from macro call here",
+ message: "Actual error occurred here",
},
DiagnosticRelatedInformation {
location: Location {
diff --git a/crates/rust-analyzer/src/flycheck.rs b/crates/rust-analyzer/src/flycheck.rs
index 73a51bb..68337dd 100644
--- a/crates/rust-analyzer/src/flycheck.rs
+++ b/crates/rust-analyzer/src/flycheck.rs
@@ -13,6 +13,7 @@
use ide_db::FxHashSet;
use itertools::Itertools;
use paths::{AbsPath, AbsPathBuf, Utf8Path, Utf8PathBuf};
+use project_model::TargetDirectoryConfig;
use rustc_hash::FxHashMap;
use serde::Deserialize as _;
use serde_derive::Deserialize;
@@ -46,7 +47,7 @@
pub(crate) extra_args: Vec<String>,
pub(crate) extra_test_bin_args: Vec<String>,
pub(crate) extra_env: FxHashMap<String, Option<String>>,
- pub(crate) target_dir: Option<Utf8PathBuf>,
+ pub(crate) target_dir_config: TargetDirectoryConfig,
}
#[derive(Clone, Debug)]
@@ -58,7 +59,7 @@
}
impl CargoOptions {
- pub(crate) fn apply_on_command(&self, cmd: &mut Command) {
+ pub(crate) fn apply_on_command(&self, cmd: &mut Command, ws_target_dir: Option<&Utf8Path>) {
for target in &self.target_tuples {
cmd.args(["--target", target.as_str()]);
}
@@ -82,8 +83,8 @@
cmd.arg(self.features.join(" "));
}
}
- if let Some(target_dir) = &self.target_dir {
- cmd.arg("--target-dir").arg(target_dir);
+ if let Some(target_dir) = self.target_dir_config.target_dir(ws_target_dir) {
+ cmd.arg("--target-dir").arg(target_dir.as_ref());
}
}
}
@@ -158,6 +159,7 @@
sysroot_root: Option<AbsPathBuf>,
workspace_root: AbsPathBuf,
manifest_path: Option<AbsPathBuf>,
+ ws_target_dir: Option<Utf8PathBuf>,
) -> FlycheckHandle {
let actor = FlycheckActor::new(
id,
@@ -167,6 +169,7 @@
sysroot_root,
workspace_root,
manifest_path,
+ ws_target_dir,
);
let (sender, receiver) = unbounded::<StateChange>();
let thread =
@@ -314,6 +317,7 @@
sender: Sender<FlycheckMessage>,
config: FlycheckConfig,
manifest_path: Option<AbsPathBuf>,
+ ws_target_dir: Option<Utf8PathBuf>,
/// Either the workspace root of the workspace we are flychecking,
/// or the project root of the project.
root: Arc<AbsPathBuf>,
@@ -355,6 +359,7 @@
sysroot_root: Option<AbsPathBuf>,
workspace_root: AbsPathBuf,
manifest_path: Option<AbsPathBuf>,
+ ws_target_dir: Option<Utf8PathBuf>,
) -> FlycheckActor {
tracing::info!(%id, ?workspace_root, "Spawning flycheck");
FlycheckActor {
@@ -366,6 +371,7 @@
root: Arc::new(workspace_root),
scope: FlycheckScope::Workspace,
manifest_path,
+ ws_target_dir,
command_handle: None,
command_receiver: None,
diagnostics_cleared_for: Default::default(),
@@ -428,15 +434,32 @@
CargoCheckParser,
sender,
match &self.config {
- FlycheckConfig::CargoCommand { options, .. } => Some(
- options
- .target_dir
- .as_deref()
- .unwrap_or(
- Utf8Path::new("target").join("rust-analyzer").as_path(),
- )
- .join(format!("flycheck{}", self.id)),
- ),
+ FlycheckConfig::CargoCommand { options, .. } => {
+ let ws_target_dir =
+ self.ws_target_dir.as_ref().map(Utf8PathBuf::as_path);
+ let target_dir =
+ options.target_dir_config.target_dir(ws_target_dir);
+
+ // If `"rust-analyzer.cargo.targetDir": null`, we should use
+ // workspace's target dir instead of hard-coded fallback.
+ let target_dir = target_dir.as_deref().or(ws_target_dir);
+
+ Some(
+ // As `CommandHandle::spawn`'s working directory is
+ // rust-analyzer's working directory, which might be different
+ // from the flycheck's working directory, we should canonicalize
+ // the output directory, otherwise we might write it into the
+ // wrong target dir.
+ // If `target_dir` is an absolute path, it will replace
+ // `self.root` and that's an intended behavior.
+ self.root
+ .join(target_dir.unwrap_or(
+ Utf8Path::new("target").join("rust-analyzer").as_path(),
+ ))
+ .join(format!("flycheck{}", self.id))
+ .into(),
+ )
+ }
_ => None,
},
) {
@@ -672,7 +695,10 @@
cmd.arg("--keep-going");
- options.apply_on_command(&mut cmd);
+ options.apply_on_command(
+ &mut cmd,
+ self.ws_target_dir.as_ref().map(Utf8PathBuf::as_path),
+ );
cmd.args(&options.extra_args);
Some(cmd)
}
diff --git a/crates/rust-analyzer/src/handlers/request.rs b/crates/rust-analyzer/src/handlers/request.rs
index 55d092f..7e52673 100644
--- a/crates/rust-analyzer/src/handlers/request.rs
+++ b/crates/rust-analyzer/src/handlers/request.rs
@@ -33,7 +33,9 @@
use vfs::{AbsPath, AbsPathBuf, FileId, VfsPath};
use crate::{
- config::{Config, RustfmtConfig, WorkspaceSymbolConfig},
+ config::{
+ ClientCommandsConfig, Config, HoverActionsConfig, RustfmtConfig, WorkspaceSymbolConfig,
+ },
diagnostics::convert_diagnostic,
global_state::{FetchWorkspaceRequest, GlobalState, GlobalStateSnapshot},
line_index::LineEndings,
@@ -124,17 +126,35 @@
Ok(buf)
}
-pub(crate) fn handle_memory_usage(state: &mut GlobalState, _: ()) -> anyhow::Result<String> {
+pub(crate) fn handle_memory_usage(_state: &mut GlobalState, _: ()) -> anyhow::Result<String> {
let _p = tracing::info_span!("handle_memory_usage").entered();
- let mem = state.analysis_host.per_query_memory_usage();
- let mut out = String::new();
- for (name, bytes, entries) in mem {
- format_to!(out, "{:>8} {:>6} {}\n", bytes, entries, name);
+ #[cfg(not(feature = "dhat"))]
+ {
+ Err(anyhow::anyhow!(
+ "Memory profiling is not enabled for this build of rust-analyzer.\n\n\
+ To build rust-analyzer with profiling support, pass `--features dhat --profile dev-rel` to `cargo build`
+ when building from source, or pass `--enable-profiling` to `cargo xtask`."
+ ))
}
- format_to!(out, "{:>8} Remaining\n", profile::memory_usage().allocated);
-
- Ok(out)
+ #[cfg(feature = "dhat")]
+ {
+ if let Some(dhat_output_file) = _state.config.dhat_output_file() {
+ let mut profiler = crate::DHAT_PROFILER.lock().unwrap();
+ let old_profiler = profiler.take();
+ // Need to drop the old profiler before creating a new one.
+ drop(old_profiler);
+ *profiler = Some(dhat::Profiler::builder().file_name(&dhat_output_file).build());
+ Ok(format!(
+ "Memory profile was saved successfully to {dhat_output_file}.\n\n\
+ See https://docs.rs/dhat/latest/dhat/#viewing for how to inspect the profile."
+ ))
+ } else {
+ Err(anyhow::anyhow!(
+ "Please set `rust-analyzer.profiling.memoryProfile` to the path where you want to save the profile."
+ ))
+ }
+ }
}
pub(crate) fn handle_view_syntax_tree(
@@ -264,6 +284,7 @@
path,
state.config.cargo_test_options(None),
cargo.workspace_root(),
+ Some(cargo.target_directory().as_ref()),
target,
state.test_run_sender.clone(),
)?;
@@ -847,10 +868,11 @@
let _p = tracing::info_span!("handle_goto_implementation").entered();
let position =
try_default!(from_proto::file_position(&snap, params.text_document_position_params)?);
- let nav_info = match snap.analysis.goto_implementation(position)? {
- None => return Ok(None),
- Some(it) => it,
- };
+ let nav_info =
+ match snap.analysis.goto_implementation(&snap.config.goto_implementation(), position)? {
+ None => return Ok(None),
+ Some(it) => it,
+ };
let src = FileRange { file_id: position.file_id, range: nav_info.range };
let res = to_proto::goto_definition_response(&snap, Some(src), nav_info.info)?;
Ok(Some(res))
@@ -1325,8 +1347,12 @@
let _p = tracing::info_span!("handle_rename").entered();
let position = try_default!(from_proto::file_position(&snap, params.text_document_position)?);
- let mut change =
- snap.analysis.rename(position, ¶ms.new_name)?.map_err(to_proto::rename_error)?;
+ let source_root = snap.analysis.source_root_id(position.file_id).ok();
+ let config = snap.config.rename(source_root);
+ let mut change = snap
+ .analysis
+ .rename(position, ¶ms.new_name, &config)?
+ .map_err(to_proto::rename_error)?;
// this is kind of a hack to prevent double edits from happening when moving files
// When a module gets renamed by renaming the mod declaration this causes the file to move
@@ -1459,13 +1485,14 @@
resolve,
frange,
)?;
+ let client_commands = snap.config.client_commands();
for (index, assist) in assists.into_iter().enumerate() {
let resolve_data = if code_action_resolve_cap {
Some((index, params.clone(), snap.file_version(file_id)))
} else {
None
};
- let code_action = to_proto::code_action(&snap, assist, resolve_data)?;
+ let code_action = to_proto::code_action(&snap, &client_commands, assist, resolve_data)?;
// Check if the client supports the necessary `ResourceOperation`s.
let changes = code_action.edit.as_ref().and_then(|it| it.document_changes.as_ref());
@@ -1566,7 +1593,7 @@
))
.into());
}
- let ca = to_proto::code_action(&snap, assist.clone(), None)?;
+ let ca = to_proto::code_action(&snap, &snap.config.client_commands(), assist.clone(), None)?;
code_action.edit = ca.edit;
code_action.command = ca.command;
@@ -2130,10 +2157,15 @@
fn show_impl_command_link(
snap: &GlobalStateSnapshot,
position: &FilePosition,
+ implementations: bool,
+ show_references: bool,
) -> Option<lsp_ext::CommandLinkGroup> {
- if snap.config.hover_actions().implementations
- && snap.config.client_commands().show_reference
- && let Some(nav_data) = snap.analysis.goto_implementation(*position).unwrap_or(None)
+ if implementations
+ && show_references
+ && let Some(nav_data) = snap
+ .analysis
+ .goto_implementation(&snap.config.goto_implementation(), *position)
+ .unwrap_or(None)
{
let uri = to_proto::url(snap, position.file_id);
let line_index = snap.file_line_index(position.file_id).ok()?;
@@ -2157,9 +2189,11 @@
fn show_ref_command_link(
snap: &GlobalStateSnapshot,
position: &FilePosition,
+ references: bool,
+ show_reference: bool,
) -> Option<lsp_ext::CommandLinkGroup> {
- if snap.config.hover_actions().references
- && snap.config.client_commands().show_reference
+ if references
+ && show_reference
&& let Some(ref_search_res) = snap
.analysis
.find_all_refs(
@@ -2194,8 +2228,9 @@
fn runnable_action_links(
snap: &GlobalStateSnapshot,
runnable: Runnable,
+ hover_actions_config: &HoverActionsConfig,
+ client_commands_config: &ClientCommandsConfig,
) -> Option<lsp_ext::CommandLinkGroup> {
- let hover_actions_config = snap.config.hover_actions();
if !hover_actions_config.runnable() {
return None;
}
@@ -2205,7 +2240,6 @@
return None;
}
- let client_commands_config = snap.config.client_commands();
if !(client_commands_config.run_single || client_commands_config.debug_single) {
return None;
}
@@ -2240,11 +2274,10 @@
fn goto_type_action_links(
snap: &GlobalStateSnapshot,
nav_targets: &[HoverGotoTypeData],
+ hover_actions: &HoverActionsConfig,
+ client_commands: &ClientCommandsConfig,
) -> Option<lsp_ext::CommandLinkGroup> {
- if !snap.config.hover_actions().goto_type_def
- || nav_targets.is_empty()
- || !snap.config.client_commands().goto_location
- {
+ if !hover_actions.goto_type_def || nav_targets.is_empty() || !client_commands.goto_location {
return None;
}
@@ -2264,13 +2297,29 @@
snap: &GlobalStateSnapshot,
actions: &[HoverAction],
) -> Vec<lsp_ext::CommandLinkGroup> {
+ let hover_actions = snap.config.hover_actions();
+ let client_commands = snap.config.client_commands();
actions
.iter()
.filter_map(|it| match it {
- HoverAction::Implementation(position) => show_impl_command_link(snap, position),
- HoverAction::Reference(position) => show_ref_command_link(snap, position),
- HoverAction::Runnable(r) => runnable_action_links(snap, r.clone()),
- HoverAction::GoToType(targets) => goto_type_action_links(snap, targets),
+ HoverAction::Implementation(position) => show_impl_command_link(
+ snap,
+ position,
+ hover_actions.implementations,
+ client_commands.show_reference,
+ ),
+ HoverAction::Reference(position) => show_ref_command_link(
+ snap,
+ position,
+ hover_actions.references,
+ client_commands.show_reference,
+ ),
+ HoverAction::Runnable(r) => {
+ runnable_action_links(snap, r.clone(), &hover_actions, &client_commands)
+ }
+ HoverAction::GoToType(targets) => {
+ goto_type_action_links(snap, targets, &hover_actions, &client_commands)
+ }
})
.collect()
}
diff --git a/crates/rust-analyzer/src/lib.rs b/crates/rust-analyzer/src/lib.rs
index 44af8fb..6ae527a 100644
--- a/crates/rust-analyzer/src/lib.rs
+++ b/crates/rust-analyzer/src/lib.rs
@@ -82,3 +82,10 @@
};
}
pub(crate) use try_default_ as try_default;
+
+#[cfg(feature = "dhat")]
+#[global_allocator]
+static ALLOC: dhat::Alloc = dhat::Alloc;
+
+#[cfg(feature = "dhat")]
+static DHAT_PROFILER: std::sync::Mutex<Option<dhat::Profiler>> = std::sync::Mutex::new(None);
diff --git a/crates/rust-analyzer/src/lsp/to_proto.rs b/crates/rust-analyzer/src/lsp/to_proto.rs
index 024c13e..995e6c4 100644
--- a/crates/rust-analyzer/src/lsp/to_proto.rs
+++ b/crates/rust-analyzer/src/lsp/to_proto.rs
@@ -26,7 +26,7 @@
use vfs::AbsPath;
use crate::{
- config::{CallInfoConfig, Config},
+ config::{CallInfoConfig, ClientCommandsConfig, Config},
global_state::GlobalStateSnapshot,
line_index::{LineEndings, LineIndex, PositionEncoding},
lsp::{
@@ -258,10 +258,12 @@
let max_relevance = items.iter().map(|it| it.relevance.score()).max().unwrap_or_default();
let mut res = Vec::with_capacity(items.len());
+ let client_commands = config.client_commands();
for item in items {
completion_item(
&mut res,
config,
+ &client_commands,
fields_to_resolve,
line_index,
version,
@@ -283,6 +285,7 @@
fn completion_item(
acc: &mut Vec<lsp_types::CompletionItem>,
config: &Config,
+ client_commands: &ClientCommandsConfig,
fields_to_resolve: &CompletionFieldsToResolve,
line_index: &LineIndex,
version: Option<i32>,
@@ -342,7 +345,7 @@
} else {
item.deprecated.then(|| vec![lsp_types::CompletionItemTag::DEPRECATED])
};
- let command = if item.trigger_call_info && config.client_commands().trigger_parameter_hints {
+ let command = if item.trigger_call_info && client_commands.trigger_parameter_hints {
if fields_to_resolve.resolve_command {
something_to_resolve |= true;
None
@@ -1500,6 +1503,7 @@
pub(crate) fn code_action(
snap: &GlobalStateSnapshot,
+ commands: &ClientCommandsConfig,
assist: Assist,
resolve_data: Option<(usize, lsp_types::CodeActionParams, Option<i32>)>,
) -> Cancellable<lsp_ext::CodeAction> {
@@ -1513,7 +1517,6 @@
command: None,
};
- let commands = snap.config.client_commands();
res.command = match assist.command {
Some(assists::Command::TriggerParameterHints) if commands.trigger_parameter_hints => {
Some(command::trigger_parameter_hints())
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs
index c0947b2..c2b887c 100644
--- a/crates/rust-analyzer/src/main_loop.rs
+++ b/crates/rust-analyzer/src/main_loop.rs
@@ -60,6 +60,14 @@
SetThreadPriority(thread, thread_priority_above_normal);
}
+ #[cfg(feature = "dhat")]
+ {
+ if let Some(dhat_output_file) = config.dhat_output_file() {
+ *crate::DHAT_PROFILER.lock().unwrap() =
+ Some(dhat::Profiler::builder().file_name(&dhat_output_file).build());
+ }
+ }
+
GlobalState::new(connection.sender, config).run(connection.receiver)
}
@@ -1023,9 +1031,9 @@
package_id,
} => {
let snap = self.snapshot();
- let diagnostics = crate::diagnostics::to_proto::map_rust_diagnostic_to_lsp(
+ let diagnostics = crate::diagnostics::flycheck_to_proto::map_rust_diagnostic_to_lsp(
&self.config.diagnostics_map(None),
- &diagnostic,
+ diagnostic,
&workspace_root,
&snap,
);
diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs
index 1475f02..bb971eb 100644
--- a/crates/rust-analyzer/src/reload.rs
+++ b/crates/rust-analyzer/src/reload.rs
@@ -23,6 +23,7 @@
use itertools::Itertools;
use load_cargo::{ProjectFolders, load_proc_macro};
use lsp_types::FileSystemWatcher;
+use paths::Utf8Path;
use proc_macro_api::ProcMacroClient;
use project_model::{ManifestPath, ProjectWorkspace, ProjectWorkspaceKind, WorkspaceBuildScripts};
use stdx::{format_to, thread::ThreadIntent};
@@ -876,6 +877,7 @@
None,
self.config.root_path().clone(),
None,
+ None,
)]
}
crate::flycheck::InvocationStrategy::PerWorkspace => {
@@ -890,13 +892,17 @@
| ProjectWorkspaceKind::DetachedFile {
cargo: Some((cargo, _, _)),
..
- } => (cargo.workspace_root(), Some(cargo.manifest_path())),
+ } => (
+ cargo.workspace_root(),
+ Some(cargo.manifest_path()),
+ Some(cargo.target_directory()),
+ ),
ProjectWorkspaceKind::Json(project) => {
// Enable flychecks for json projects if a custom flycheck command was supplied
// in the workspace configuration.
match config {
FlycheckConfig::CustomCommand { .. } => {
- (project.path(), None)
+ (project.path(), None, None)
}
_ => return None,
}
@@ -906,7 +912,7 @@
ws.sysroot.root().map(ToOwned::to_owned),
))
})
- .map(|(id, (root, manifest_path), sysroot_root)| {
+ .map(|(id, (root, manifest_path, target_dir), sysroot_root)| {
FlycheckHandle::spawn(
id,
next_gen,
@@ -915,6 +921,7 @@
sysroot_root,
root.to_path_buf(),
manifest_path.map(|it| it.to_path_buf()),
+ target_dir.map(|it| AsRef::<Utf8Path>::as_ref(it).to_path_buf()),
)
})
.collect()
diff --git a/crates/rust-analyzer/src/test_runner.rs b/crates/rust-analyzer/src/test_runner.rs
index 0c8658c..9a65e70 100644
--- a/crates/rust-analyzer/src/test_runner.rs
+++ b/crates/rust-analyzer/src/test_runner.rs
@@ -2,7 +2,7 @@
//! thread and report the result of each test in a channel.
use crossbeam_channel::Sender;
-use paths::AbsPath;
+use paths::{AbsPath, Utf8Path};
use project_model::TargetKind;
use serde::Deserialize as _;
use serde_derive::Deserialize;
@@ -98,6 +98,7 @@
path: Option<&str>,
options: CargoOptions,
root: &AbsPath,
+ ws_target_dir: Option<&Utf8Path>,
test_target: TestTarget,
sender: Sender<CargoTestMessage>,
) -> std::io::Result<Self> {
@@ -123,7 +124,7 @@
cmd.arg("--no-fail-fast");
cmd.arg("--manifest-path");
cmd.arg(root.join("Cargo.toml"));
- options.apply_on_command(&mut cmd);
+ options.apply_on_command(&mut cmd, ws_target_dir);
cmd.arg("--");
if let Some(path) = path {
cmd.arg(path);
diff --git a/crates/syntax/src/ast/make.rs b/crates/syntax/src/ast/make.rs
index 051c583..dba3920 100644
--- a/crates/syntax/src/ast/make.rs
+++ b/crates/syntax/src/ast/make.rs
@@ -1355,7 +1355,7 @@
pub(super) static SOURCE_FILE: LazyLock<Parse<SourceFile>> = LazyLock::new(|| {
SourceFile::parse(
- "use crate::foo; const C: <()>::Item = ( true && true , true || true , 1 != 1, 2 == 2, 3 < 3, 4 <= 4, 5 > 5, 6 >= 6, !true, *p, &p , &mut p, async { let _ @ [] })\n;\n\nunsafe impl A for B where: {}",
+ "use crate::foo; const C: <()>::Item = ( true && true , true || true , 1 != 1, 2 == 2, 3 < 3, 4 <= 4, 5 > 5, 6 >= 6, !true, *p, &p , &mut p, async { let _ @ [] }, while loop {} {})\n;\n\nunsafe impl A for B where: {}",
Edition::CURRENT,
)
});
diff --git a/crates/syntax/src/ast/syntax_factory/constructors.rs b/crates/syntax/src/ast/syntax_factory/constructors.rs
index 8bf27f9..9695523 100644
--- a/crates/syntax/src/ast/syntax_factory/constructors.rs
+++ b/crates/syntax/src/ast/syntax_factory/constructors.rs
@@ -644,6 +644,20 @@
ast
}
+ pub fn expr_loop(&self, body: ast::BlockExpr) -> ast::LoopExpr {
+ let ast::Expr::LoopExpr(ast) = make::expr_loop(body.clone()).clone_for_update() else {
+ unreachable!()
+ };
+
+ if let Some(mut mapping) = self.mappings() {
+ let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone());
+ builder.map_node(body.syntax().clone(), ast.loop_body().unwrap().syntax().clone());
+ builder.finish(&mut mapping);
+ }
+
+ ast
+ }
+
pub fn expr_while_loop(&self, condition: ast::Expr, body: ast::BlockExpr) -> ast::WhileExpr {
let ast = make::expr_while_loop(condition.clone(), body.clone()).clone_for_update();
diff --git a/crates/tt/src/lib.rs b/crates/tt/src/lib.rs
index 243a27b..f9a547f 100644
--- a/crates/tt/src/lib.rs
+++ b/crates/tt/src/lib.rs
@@ -622,6 +622,15 @@
let lit = &lit[start_offset..lit.len() - end_offset];
let suffix = match suffix {
"" | "_" => None,
+ // ill-suffixed literals
+ _ if !matches!(kind, LitKind::Integer | LitKind::Float | LitKind::Err(_)) => {
+ return Literal {
+ span,
+ symbol: Symbol::intern(text),
+ kind: LitKind::Err(()),
+ suffix: None,
+ };
+ }
suffix => Some(Symbol::intern(suffix)),
};
diff --git a/docs/book/src/configuration_generated.md b/docs/book/src/configuration_generated.md
index d768993..21e199c 100644
--- a/docs/book/src/configuration_generated.md
+++ b/docs/book/src/configuration_generated.md
@@ -635,6 +635,13 @@
Controls file watching implementation.
+## rust-analyzer.gotoImplementations.filterAdjacentDerives {#gotoImplementations.filterAdjacentDerives}
+
+Default: `false`
+
+If this is `true`, when "Goto Implementations" and in "Implementations" lens, are triggered on a `struct` or `enum` or `union`, we filter out trait implementations that originate from `derive`s above the type.
+
+
## rust-analyzer.highlightRelated.branchExitPoints.enable {#highlightRelated.branchExitPoints.enable}
Default: `true`
@@ -1289,6 +1296,16 @@
Internal config, path to proc-macro server executable.
+## rust-analyzer.profiling.memoryProfile {#profiling.memoryProfile}
+
+Default: `null`
+
+The path where to save memory profiling output.
+
+**Note:** Memory profiling is not enabled by default in rust-analyzer builds, you need to build
+from source for it.
+
+
## rust-analyzer.references.excludeImports {#references.excludeImports}
Default: `false`
diff --git a/docs/book/src/non_cargo_based_projects.md b/docs/book/src/non_cargo_based_projects.md
index befb631..74489a9 100644
--- a/docs/book/src/non_cargo_based_projects.md
+++ b/docs/book/src/non_cargo_based_projects.md
@@ -31,7 +31,7 @@
/// It should point to the directory where std,
/// core, and friends can be found:
///
- /// https://github.com/rust-lang/rust/tree/master/library.
+ /// https://github.com/rust-lang/rust/tree/HEAD/library.
///
/// If provided, rust-analyzer automatically adds
/// dependencies on sysroot crates. Conversely,
diff --git a/editors/code/package.json b/editors/code/package.json
index d659421..7db4986 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -1628,6 +1628,16 @@
}
},
{
+ "title": "Goto Implementations",
+ "properties": {
+ "rust-analyzer.gotoImplementations.filterAdjacentDerives": {
+ "markdownDescription": "If this is `true`, when \"Goto Implementations\" and in \"Implementations\" lens, are triggered on a `struct` or `enum` or `union`, we filter out trait implementations that originate from `derive`s above the type.",
+ "default": false,
+ "type": "boolean"
+ }
+ }
+ },
+ {
"title": "Highlight Related",
"properties": {
"rust-analyzer.highlightRelated.branchExitPoints.enable": {
@@ -2750,6 +2760,19 @@
}
},
{
+ "title": "Profiling",
+ "properties": {
+ "rust-analyzer.profiling.memoryProfile": {
+ "markdownDescription": "The path where to save memory profiling output.\n\n**Note:** Memory profiling is not enabled by default in rust-analyzer builds, you need to build\nfrom source for it.",
+ "default": null,
+ "type": [
+ "null",
+ "string"
+ ]
+ }
+ }
+ },
+ {
"title": "References",
"properties": {
"rust-analyzer.references.excludeImports": {
diff --git a/editors/code/src/commands.ts b/editors/code/src/commands.ts
index 25b3001..16fc586 100644
--- a/editors/code/src/commands.ts
+++ b/editors/code/src/commands.ts
@@ -71,32 +71,9 @@
}
export function memoryUsage(ctx: CtxInit): Cmd {
- const tdcp = new (class implements vscode.TextDocumentContentProvider {
- readonly uri = vscode.Uri.parse("rust-analyzer-memory://memory");
- readonly eventEmitter = new vscode.EventEmitter<vscode.Uri>();
-
- provideTextDocumentContent(_uri: vscode.Uri): vscode.ProviderResult<string> {
- if (!vscode.window.activeTextEditor) return "";
-
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- return ctx.client.sendRequest(ra.memoryUsage).then((mem: any) => {
- return "Per-query memory usage:\n" + mem + "\n(note: database has been cleared)";
- });
- }
-
- get onDidChange(): vscode.Event<vscode.Uri> {
- return this.eventEmitter.event;
- }
- })();
-
- ctx.pushExtCleanup(
- vscode.workspace.registerTextDocumentContentProvider("rust-analyzer-memory", tdcp),
- );
-
return async () => {
- tdcp.eventEmitter.fire(tdcp.uri);
- const document = await vscode.workspace.openTextDocument(tdcp.uri);
- return vscode.window.showTextDocument(document, vscode.ViewColumn.Two, true);
+ const response = await ctx.client.sendRequest(ra.memoryUsage);
+ vscode.window.showInformationMessage(response);
};
}
diff --git a/editors/code/src/snippets.ts b/editors/code/src/snippets.ts
index e3f43a8..a469a9c 100644
--- a/editors/code/src/snippets.ts
+++ b/editors/code/src/snippets.ts
@@ -24,7 +24,9 @@
for (const indel of edits) {
assert(
!(indel instanceof vscode.SnippetTextEdit),
- `bad ws edit: snippet received with multiple edits: ${JSON.stringify(edit)}`,
+ `bad ws edit: snippet received with multiple edits: ${JSON.stringify(
+ edit,
+ )}`,
);
builder.replace(indel.range, indel.newText);
}
diff --git a/rust-version b/rust-version
index c9529fd..0e89b4a 100644
--- a/rust-version
+++ b/rust-version
@@ -1 +1 @@
-fb24b04b096a980bffd80154f6aba22fd07cb3d9
+c5dabe8cf798123087d094f06417f5a767ca73e8
diff --git a/xtask/src/dist.rs b/xtask/src/dist.rs
index dbfecdb..1b1fb53 100644
--- a/xtask/src/dist.rs
+++ b/xtask/src/dist.rs
@@ -45,11 +45,22 @@
allocator,
self.zig,
self.pgo,
+ // Profiling requires debug information.
+ self.enable_profiling,
)?;
let release_tag = if stable { date_iso(sh)? } else { "nightly".to_owned() };
dist_client(sh, &version, &release_tag, &target)?;
} else {
- dist_server(sh, "0.0.0-standalone", &target, allocator, self.zig, self.pgo)?;
+ dist_server(
+ sh,
+ "0.0.0-standalone",
+ &target,
+ allocator,
+ self.zig,
+ self.pgo,
+ // Profiling requires debug information.
+ self.enable_profiling,
+ )?;
}
Ok(())
}
@@ -92,9 +103,11 @@
allocator: Malloc,
zig: bool,
pgo: Option<PgoTrainingCrate>,
+ dev_rel: bool,
) -> anyhow::Result<()> {
let _e = sh.push_env("CFG_RELEASE", release);
let _e = sh.push_env("CARGO_PROFILE_RELEASE_LTO", "thin");
+ let _e = sh.push_env("CARGO_PROFILE_DEV_REL_LTO", "thin");
// Uncomment to enable debug info for releases. Note that:
// * debug info is split on windows and macs, so it does nothing for those platforms,
@@ -120,7 +133,7 @@
None
};
- let mut cmd = build_command(sh, command, &target_name, features);
+ let mut cmd = build_command(sh, command, &target_name, features, dev_rel);
if let Some(profile) = pgo_profile {
cmd = cmd.env("RUSTFLAGS", format!("-Cprofile-use={}", profile.to_str().unwrap()));
}
@@ -141,10 +154,12 @@
command: &str,
target_name: &str,
features: &[&str],
+ dev_rel: bool,
) -> Cmd<'a> {
+ let profile = if dev_rel { "dev-rel" } else { "release" };
cmd!(
sh,
- "cargo {command} --manifest-path ./crates/rust-analyzer/Cargo.toml --bin rust-analyzer --target {target_name} {features...} --release"
+ "cargo {command} --manifest-path ./crates/rust-analyzer/Cargo.toml --bin rust-analyzer --target {target_name} {features...} --profile {profile}"
)
}
diff --git a/xtask/src/flags.rs b/xtask/src/flags.rs
index 8f70a18..e72d8f2 100644
--- a/xtask/src/flags.rs
+++ b/xtask/src/flags.rs
@@ -42,6 +42,10 @@
optional --mimalloc
/// Use jemalloc allocator for server.
optional --jemalloc
+ // Enable memory profiling support.
+ //
+ // **Warning:** This will produce a slower build of rust-analyzer, use only for profiling.
+ optional --enable-profiling
/// Install the proc-macro server.
optional --proc-macro-server
@@ -67,6 +71,10 @@
optional --mimalloc
/// Use jemalloc allocator for server
optional --jemalloc
+ // Enable memory profiling support.
+ //
+ // **Warning:** This will produce a slower build of rust-analyzer, use only for profiling.
+ optional --enable-profiling
optional --client-patch-version version: String
/// Use cargo-zigbuild
optional --zig
@@ -125,6 +133,7 @@
pub server: bool,
pub mimalloc: bool,
pub jemalloc: bool,
+ pub enable_profiling: bool,
pub proc_macro_server: bool,
pub dev_rel: bool,
pub force_always_assert: bool,
@@ -143,6 +152,7 @@
pub struct Dist {
pub mimalloc: bool,
pub jemalloc: bool,
+ pub enable_profiling: bool,
pub client_patch_version: Option<String>,
pub zig: bool,
pub pgo: Option<PgoTrainingCrate>,
@@ -280,6 +290,7 @@
System,
Mimalloc,
Jemalloc,
+ Dhat,
}
impl Malloc {
@@ -288,6 +299,7 @@
Malloc::System => &[][..],
Malloc::Mimalloc => &["--features", "mimalloc"],
Malloc::Jemalloc => &["--features", "jemalloc"],
+ Malloc::Dhat => &["--features", "dhat"],
}
}
}
@@ -301,12 +313,15 @@
Malloc::Mimalloc
} else if self.jemalloc {
Malloc::Jemalloc
+ } else if self.enable_profiling {
+ Malloc::Dhat
} else {
Malloc::System
};
Some(ServerOpt {
malloc,
- dev_rel: self.dev_rel,
+ // Profiling requires debug information.
+ dev_rel: self.dev_rel || self.enable_profiling,
pgo: self.pgo.clone(),
force_always_assert: self.force_always_assert,
})
@@ -331,6 +346,8 @@
Malloc::Mimalloc
} else if self.jemalloc {
Malloc::Jemalloc
+ } else if self.enable_profiling {
+ Malloc::Dhat
} else {
Malloc::System
}