Auto merge of #148471 - lnicola:sync-from-ra, r=lnicola

`rust-analyzer` subtree update

Subtree update of `rust-analyzer` to 51af7a37c5.

Created using https://github.com/rust-lang/josh-sync.

r? `@ghost`
This commit is contained in:
bors 2025-11-04 17:23:40 +00:00
commit f15a7f3858
96 changed files with 2985 additions and 1094 deletions

View file

@ -33,6 +33,7 @@ trivias = "trivias"
thir = "thir"
jod = "jod"
tructure = "tructure"
taits = "taits"
[default.extend-identifiers]
anc = "anc"

View file

@ -418,6 +418,22 @@ dependencies = [
"syn",
]
[[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"
@ -1383,6 +1399,12 @@ dependencies = [
"adler2",
]
[[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"
@ -1452,7 +1474,7 @@ version = "0.50.3"
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 @@ dependencies = [
"cargo_metadata 0.21.0",
"cfg",
"crossbeam-channel",
"dhat",
"dirs",
"dissimilar",
"expect-test",
@ -2047,6 +2070,7 @@ dependencies = [
"serde",
"serde_derive",
"serde_json",
"smallvec",
"stdx",
"syntax",
"syntax-bridge",
@ -2528,6 +2552,12 @@ dependencies = [
"syn",
]
[[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"

View file

@ -370,18 +370,13 @@ impl<'a> Ctx<'a> {
});
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 @@ pub(crate) fn lower_use_tree(
}
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 @@ pub(crate) fn visibility_from_ast(
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)

View file

@ -98,7 +98,7 @@ macro_rules! m2 { () => ( ${invalid()} ) }
#[test]
fn test_rustc_issue_57597() {
// <https://github.com/rust-lang/rust/blob/HEAD/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)?)+) => {}; }

View file

@ -1075,7 +1075,9 @@ fn resolver_for_scope_<'db>(
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 @@ impl<'db> Resolver<'db> {
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 @@ impl HasResolver for ModuleId {
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 @@ impl HasResolver for ModuleId {
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 @@ impl HasResolver for ModuleId {
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
}

View file

@ -289,18 +289,21 @@ pub(crate) fn field_visibilities_query(
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.

View file

@ -38,7 +38,7 @@ pub fn autoderef<'db>(
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();

View file

@ -2078,9 +2078,10 @@ pub fn write_visibility<'db>(
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 ...) ")

View file

@ -21,6 +21,7 @@ pub(crate) mod diagnostics;
mod expr;
mod fallback;
mod mutability;
mod opaques;
mod pat;
mod path;
pub(crate) mod unify;
@ -31,8 +32,7 @@ use base_db::Crate;
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_def::{
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 @@ use crate::{
coerce::{CoerceMany, DynamicCoerceMany},
diagnostics::{Diagnostics, InferenceTyLoweringContext as TyLoweringContext},
expr::ExprIsRead,
unify::InferenceTable,
},
lower::{
ImplTraitIdx, ImplTraitLoweringMode, LifetimeElisionKind, diagnostics::TyLoweringDiagnostic,
@ -69,10 +68,7 @@ use crate::{
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 @@ pub(crate) fn infer_query(db: &dyn HirDatabase, def: DefWithBodyId) -> Arc<Infer
ctx.infer_mut_body();
ctx.handle_opaque_type_uses();
ctx.type_inference_fallback();
// Comment from rustc:
@ -148,6 +146,10 @@ pub(crate) fn infer_query(db: &dyn HirDatabase, def: DefWithBodyId) -> Arc<Infer
ctx.infer_closures();
ctx.table.select_obligations_where_possible();
ctx.handle_opaque_type_uses();
Arc::new(ctx.resolve_all())
}
@ -454,7 +456,7 @@ pub struct InferenceResult<'db> {
/// 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 @@ impl<'db> InferenceResult<'db> {
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 @@ impl<'db> InferenceResult<'db> {
// 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 @@ struct InternedStandardTypes<'db> {
re_static: Region<'db>,
re_error: Region<'db>,
re_erased: Region<'db>,
empty_args: GenericArgs<'db>,
empty_tys: Tys<'db>,
@ -742,6 +751,7 @@ impl<'db> InternedStandardTypes<'db> {
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 @@ fn find_continuable<'a, 'db>(
}
}
enum ImplTraitReplacingMode<'db> {
ReturnPosition(FxHashSet<Ty<'db>>),
TypeAlias,
}
impl<'body, 'db> InferenceContext<'body, 'db> {
fn new(
db: &'db dyn HirDatabase,
@ -861,7 +866,7 @@ impl<'body, 'db> InferenceContext<'body, 'db> {
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 @@ impl<'body, 'db> InferenceContext<'body, 'db> {
// `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 @@ impl<'body, 'db> InferenceContext<'body, 'db> {
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 @@ impl<'body, 'db> InferenceContext<'body, 'db> {
*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 @@ impl<'body, 'db> InferenceContext<'body, 'db> {
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 @@ impl<'body, 'db> InferenceContext<'body, 'db> {
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 @@ impl<'body, 'db> InferenceContext<'body, 'db> {
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 @@ impl<'body, 'db> InferenceContext<'body, 'db> {
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 @@ impl<'body, 'db> InferenceContext<'body, 'db> {
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 @@ impl<'body, 'db> InferenceContext<'body, 'db> {
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)
}
}
}

View file

@ -60,8 +60,7 @@ use crate::{
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 @@ impl<'a, 'b, 'db> Coerce<'a, 'b, 'db> {
}
}
// 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 @@ fn coerce<'db>(
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);

View file

@ -1458,10 +1458,11 @@ impl<'db> InferenceContext<'_, 'db> {
) -> 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 @@ impl<'db> InferenceContext<'_, 'db> {
}
});
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 @@ impl<'db> InferenceContext<'_, 'db> {
// 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 @@ impl<'db> InferenceContext<'_, 'db> {
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 @@ impl<'db> InferenceContext<'_, 'db> {
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,

View file

@ -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)
}
}

View file

@ -310,9 +310,10 @@ impl<'db> InferenceContext<'_, 'db> {
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,

View file

@ -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 triomphe::Arc;
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 @@ fn could_unify_impl<'db>(
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 @@ fn could_unify_impl<'db>(
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 @@ pub(crate) struct InferenceTableSnapshot<'db> {
}
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 @@ impl<'db> InferenceTable<'db> {
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.

View file

@ -27,9 +27,11 @@ mod infer;
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 @@ pub mod traits;
mod test_db;
#[cfg(test)]
mod tests;
mod variance;
use std::hash::Hash;
@ -471,6 +472,7 @@ where
}
}
/// 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 @@ pub fn callable_sig_from_fn_trait<'db>(
.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>

View file

@ -489,9 +489,8 @@ pub fn def_crates<'db>(
/// 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 @@ pub(crate) fn lookup_method<'db>(
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 @@ impl ReceiverAdjustments {
// 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 @@ pub(crate) fn iterate_method_candidates<'db, T>(
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 @@ pub fn check_orphan_rules<'db>(db: &'db dyn HirDatabase, impl_: ImplId) -> bool
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 @@ pub fn iterate_path_candidates<'db>(
)
}
/// To be used from `hir` only.
pub fn iterate_method_candidates_dyn<'db>(
ty: &Canonical<'db, Ty<'db>>,
db: &'db dyn HirDatabase,
@ -1016,6 +1014,26 @@ pub fn iterate_method_candidates_dyn<'db>(
name: Option<&Name>,
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",
@ -1046,28 +1064,28 @@ pub fn iterate_method_candidates_dyn<'db>(
// 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 @@ fn iterate_method_candidates_by_receiver<'db>(
#[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))]

View file

@ -17,7 +17,7 @@ use crate::{
display::DisplayTarget,
mir::OperandKind,
next_solver::{
DbInterner, GenericArgs, SolverDefIds, Ty, TypingMode,
DbInterner, GenericArgs, Ty, TypingMode,
infer::{DbInternerInferExt, InferCtxt},
},
};
@ -100,11 +100,11 @@ pub fn borrowck_query<'db>(
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),

View file

@ -154,6 +154,29 @@ impl From<DefWithBodyId> for SolverDefId {
}
}
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 = ();

View file

@ -63,6 +63,14 @@ impl<'db> GenericArg<'db> {
}
}
#[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(),

View file

@ -13,27 +13,27 @@ use opaque_types::{OpaqueHiddenType, OpaqueTypeStorage};
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 @@ use super::{
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 @@ impl<'db> InferCtxt<'db> {
))
}
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.

View file

@ -4,9 +4,11 @@ pub(crate) mod table;
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>,
}

View file

@ -122,14 +122,6 @@ impl<'db> OpaqueTypeStorage<'db> {
}
}
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>,

View file

@ -7,8 +7,8 @@ pub use tls_db::{attach_db, attach_db_allow_change, with_attached_db};
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 rustc_type_ir::{
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 @@ macro_rules! _interned_vec_nolifetime_salsa {
}
};
($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 @@ macro_rules! _interned_vec_nolifetime_salsa {
}
}
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 @@ impl<'db> Interner for DbInterner<'db> {
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 @@ impl<'db> Interner for DbInterner<'db> {
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 @@ impl<'db> Interner for DbInterner<'db> {
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

View file

@ -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 @@ impl<'db> SolverDelegate for SolverContext<'db> {
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 @@ impl<'db> SolverDelegate for SolverContext<'db> {
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 @@ impl<'db> SolverDelegate for SolverContext<'db> {
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 @@ impl<'db> SolverDelegate for SolverContext<'db> {
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 {

View file

@ -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()
}

View file

@ -20,7 +20,7 @@ use crate::{
// 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 @@ fn specializes_cycle(
/// `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 @@ pub(crate) fn specializes(
// 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 @@ pub(crate) fn specializes(
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)
}

View file

@ -591,6 +591,7 @@ fn main() {
"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 @@ fn main() {
"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",

View file

@ -31,7 +31,6 @@ fn test() {
}
#[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 @@ fn foo() {
);
}
#[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 @@ static ALIAS: AliasTy = {
"#,
);
// 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 {}

View file

@ -725,7 +725,7 @@ fn issue_4885() {
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 @@ fn main() {
"#,
);
}
#[test]
fn module_inside_block() {
check_types(
r#"
fn foo() {
mod my_mod {
pub type Bool = bool;
}
let _: my_mod::Bool;
// ^ bool
}
"#,
);
}

View file

@ -180,7 +180,7 @@ impl<'a> IntoIterator for &'a Grid {
"#,
expect![[r#"
150..154 'self': &'a Grid
174..181 '{ }': impl Iterator<Item = &'a ()>
174..181 '{ }': <&'a Grid as IntoIterator>::IntoIter
"#]],
);
}

View file

@ -1211,7 +1211,7 @@ fn test(x: impl Trait<u64>, y: &impl Trait<u64>) {
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 @@ fn test() {
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 @@ fn foo<const C: u8, T>() -> (impl FnOnce(&str, T), impl Trait<u8>) {
}
"#,
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 @@ fn return_pos_impl_trait_in_projection() {
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, ())
}
"#,

View file

@ -107,24 +107,26 @@ pub fn next_trait_solve_canonical_in_ctxt<'db>(
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.

View file

@ -5136,10 +5136,7 @@ impl<'db> Type<'db> {
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 @@ impl<'db> Type<'db> {
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())
}
}

View file

@ -2105,6 +2105,22 @@ impl<'db> SemanticsImpl<'db> {
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

View file

@ -1594,14 +1594,12 @@ fn resolve_hir_path_(
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)

View file

@ -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);
}
}
",
);
}
}

View file

@ -13,7 +13,7 @@ use syntax::{
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 @@ pub(crate) fn generate_blanket_trait_impl(
|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 @@ pub(crate) fn generate_blanket_trait_impl(
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 @@ pub(crate) fn generate_blanket_trait_impl(
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()),
@ -506,6 +501,41 @@ impl<I: Iterator + ?Sized> Foo for $0I {
);
}
#[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(
@ -739,6 +769,49 @@ mod foo {
}
}
}
}
"#,
);
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,
>;
}
}
}
"#,
);
@ -824,6 +897,8 @@ impl<T: Send, T1: ToOwned + ?Sized> Foo<T> for $0T1
where
Self::Owned: Default,
{
type X: Sync;
fn foo(&self, x: Self::X) -> T {
todo!()
}
@ -871,6 +946,8 @@ where
Self: ToOwned,
Self::Owned: Default,
{
type X: Sync;
fn foo(&self, x: Self::X) -> T {
todo!()
}
@ -906,6 +983,8 @@ trait Foo<T: Send> {
}
impl<T: Send, T1: ?Sized> Foo<T> for $0T1 {
type X: Sync;
fn foo(&self, x: Self::X) -> T {
todo!()
}
@ -941,6 +1020,8 @@ trait Foo {
}
impl<T: ?Sized> Foo for $0T {
type X: Sync;
fn foo(&self, x: Self::X) -> i32 {
todo!()
}

View file

@ -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 @@ pub(crate) fn replace_is_method_with_if_let_method(
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 @@ pub(crate) fn replace_is_method_with_if_let_method(
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 @@ pub(crate) fn replace_is_method_with_if_let_method(
};
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(
@ -250,6 +251,25 @@ fn main() {
);
}
#[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(

View file

@ -131,6 +131,7 @@ mod handlers {
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 @@ mod handlers {
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,

View file

@ -731,6 +731,29 @@ fn main() {
)
}
#[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(

View file

@ -531,6 +531,146 @@ fn main() {
);
}
#[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(

View file

@ -1012,6 +1012,25 @@ fn classify_name_ref<'db>(
.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 @@ fn classify_name_ref<'db>(
.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);

View file

@ -1953,3 +1953,25 @@ fn foo() {
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()
"#]],
);
}

View file

@ -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 hir::{
};
use itertools::Itertools;
use rustc_hash::{FxHashMap, FxHashSet};
use smallvec::SmallVec;
use syntax::{
AstNode, SyntaxNode,
ast::{self, HasName, make},
@ -416,7 +417,7 @@ fn path_applicable_imports(
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 @@ fn validate_resolvable(
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 @@ fn validate_resolvable(
}
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> {

View file

@ -387,12 +387,14 @@ impl Definition {
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 {

View file

@ -9,7 +9,7 @@ use syntax::{AstNode, TextRange, ast::HasName};
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 struct AnnotationConfig<'a> {
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 @@ pub(crate) fn resolve_annotation(
) -> 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 @@ mod tests {
annotate_enum_variant_references: true,
location: AnnotationLocation::AboveName,
minicore: MiniCore::default(),
filter_adjacent_derive_implementations: false,
};
fn check_with_config(

View file

@ -8,6 +8,10 @@ use syntax::{AstNode, SyntaxKind::*, T, ast};
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 @@ use crate::{FilePosition, NavigationTarget, RangeInfo, TryToNav};
// ![Go to Implementation](https://user-images.githubusercontent.com/48062697/113065566-02f85480-91b1-11eb-9288-aaad8abd8841.gif)
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 @@ pub(crate) fn goto_implementation(
.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 @@ mod tests {
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 @@ fn test() {
"#,
);
}
#[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 {}
// ^^^
"#,
);
}
}

View file

@ -10809,7 +10809,7 @@ type Foo$0 = impl Sized;
---
needs Drop
no Drop
"#]],
);
check(

View file

@ -86,6 +86,7 @@ pub use crate::{
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 @@ pub use crate::{
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 @@ impl Analysis {
/// 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 @@ impl Analysis {
&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(

View file

@ -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 @@ pub use ide_db::rename::RenameError;
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 @@ pub(crate) fn rename(
db: &RootDatabase,
position: FilePosition,
new_name: &str,
config: &RenameConfig,
) -> RenameResult<SourceChange> {
let sema = Semantics::new(db);
let file_id = sema
@ -158,7 +176,14 @@ pub(crate) fn rename(
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 @@ fn transform_assoc_fn_into_method_call(
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 @@ fn transform_assoc_fn_into_method_call(
.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 @@ fn transform_assoc_fn_into_method_call(
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 @@ fn rename_to_self(
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 @@ fn rename_self_to_param(
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 @@ fn rename_self_to_param(
),
)
}));
transform_method_call_into_assoc_fn(sema, &mut source_change, fn_def, find_path_config);
Ok(source_change)
}
@ -587,7 +801,10 @@ mod tests {
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 @@ mod tests {
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 @@ mod tests {
#[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 @@ mod tests {
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))
}
@ -3585,6 +3804,117 @@ impl Foo {
fn bar(v: Foo) {
v.foo(123);
}
"#,
);
}
#[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);
}
"#,
);

View file

@ -175,6 +175,9 @@ fn signature_help_for_call(
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 @@ fn signature_help_for_call(
}
};
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 @@ mod tests {
#[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 @@ mod tests {
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 @@ fn bar() { foo($03, ); }
);
check(
r#"
//- minicore: sized, fn
fn foo(x: u32, y: u32) -> u32 {x + y}
fn bar() { foo(3$0, ); }
"#,
@ -815,6 +817,7 @@ fn bar() { foo(3$0, ); }
);
check(
r#"
//- minicore: sized, fn
fn foo(x: u32, y: u32) -> u32 {x + y}
fn bar() { foo(3,$0 ); }
"#,
@ -825,6 +828,7 @@ fn bar() { foo(3,$0 ); }
);
check(
r#"
//- minicore: sized, fn
fn foo(x: u32, y: u32) -> u32 {x + y}
fn bar() { foo(3, $0); }
"#,
@ -839,6 +843,7 @@ fn bar() { foo(3, $0); }
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 bar() { foo($0); }
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 bar() { foo($03, ); }
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 bar() { foo($0); }
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 bar() {
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 bar() {
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 main() { S.foo($0); }
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 main() { S(1u32).foo($0); }
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 main() { S::foo($0); }
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 bar() {
fn test_fn_signature_with_docs() {
check(
r#"
//- minicore: sized, fn
/// Adds one to the number given.
///
/// # Examples
@ -1031,6 +1045,7 @@ pub fn r#do() {
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 @@ pub fn do_it() {
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 foo(mut r: impl WriteHandler<()>) {
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 bar() { foo $0 (3, ); }
fn outside_of_arg_list() {
check(
r#"
//- minicore: sized, fn
fn foo(a: u8) {}
fn f() {
foo(123)$0
@ -1126,6 +1144,7 @@ fn f() {
);
check(
r#"
//- minicore: sized, fn
fn foo<T>(a: u8) {}
fn f() {
foo::<u32>$0()
@ -1135,6 +1154,7 @@ fn f() {
);
check(
r#"
//- minicore: sized, fn
fn foo(a: u8) -> u8 {a}
fn bar(a: u8) -> u8 {a}
fn f() {
@ -1148,6 +1168,7 @@ fn f() {
);
check(
r#"
//- minicore: sized, fn
struct Vec<T>(T);
struct Vec2<T>(T);
fn f() {
@ -1165,6 +1186,7 @@ fn f() {
fn test_nested_method_in_lambda() {
check(
r#"
//- minicore: sized, fn
struct Foo;
impl Foo { fn bar(&self, _: u32) { } }
@ -1186,6 +1208,7 @@ fn main() {
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 main() {
fn tuple_struct_pat() {
check(
r#"
//- minicore: sized, fn
/// A cool tuple struct
struct S(u32, i32);
fn main() {
@ -1224,6 +1248,7 @@ fn main() {
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 @@ fn main() {
);
check(
r#"
//- minicore: sized, fn
/// A cool tuple struct
struct S(u32, i32, f32, u16, u8);
fn main() {
@ -1254,6 +1280,7 @@ fn main() {
);
check(
r#"
//- minicore: sized, fn
/// A cool tuple struct
struct S(u32, i32, f32, u16);
fn main() {
@ -1269,6 +1296,7 @@ fn main() {
);
check(
r#"
//- minicore: sized, fn
/// A cool tuple struct
struct S(u32, i32, f32, u16, u8);
fn main() {
@ -1284,6 +1312,7 @@ fn main() {
);
check(
r#"
//- minicore: sized, fn
/// A cool tuple struct
struct S(u32, i32, f32, u16);
fn main() {
@ -1299,6 +1328,7 @@ fn main() {
);
check(
r#"
//- minicore: sized, fn
/// A cool tuple struct
struct S(u32, i32, f32, u16);
fn main() {
@ -1318,6 +1348,7 @@ fn main() {
fn generic_struct() {
check(
r#"
//- minicore: sized, fn
struct S<T>(T);
fn main() {
let s = S($0);
@ -1334,6 +1365,7 @@ fn main() {
fn works_for_enum_variants() {
check(
r#"
//- minicore: sized, fn
enum E {
/// A Variant
A(i32),
@ -1360,6 +1392,7 @@ fn main() {
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 main() {
fn cant_call_enum_record() {
check(
r#"
//- minicore: sized, fn
enum E {
/// A Variant
A(i32),
@ -1394,6 +1428,7 @@ fn main() {
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 @@ id! {
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 test() { S.foo($0); }
fn call_info_for_lambdas() {
check(
r#"
//- minicore: sized, fn
struct S;
fn foo(s: S) -> i32 { 92 }
fn main() {
@ -1443,6 +1480,7 @@ fn main() {
);
check(
r#"
//- minicore: sized, fn
struct S;
fn foo(s: S) -> i32 { 92 }
fn main() {
@ -1456,6 +1494,7 @@ fn main() {
);
check(
r#"
//- minicore: sized, fn
struct S;
fn foo(s: S) -> i32 { 92 }
fn main() {
@ -1474,6 +1513,7 @@ fn main() {
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 main() {
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 main(f: fn(i32, f64) -> char) {
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 @@ fn main() {
);
check(
r#"
//- minicore: sized, fn
struct S;
impl core::ops::FnOnce<(i32, f64)> for S {
type Output = char;
@ -1541,6 +1584,7 @@ fn main() {
);
check(
r#"
//- minicore: sized, fn
struct S;
impl core::ops::FnOnce<(i32, f64)> for S {
type Output = char;
@ -1556,6 +1600,7 @@ fn main() {
);
check(
r#"
//- minicore: sized, fn
struct S;
impl core::ops::FnOnce<(i32, f64)> for S {
type Output = char;
@ -1576,6 +1621,7 @@ fn main() {
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 @@ fn main() {
// check with surrounding space
check(
r#"
//- minicore: sized, fn
fn foo(foo: u32, bar: u32) {}
fn main() {
foo( $0
@ -1603,6 +1650,7 @@ fn main() {
fn test_multiline_argument() {
check(
r#"
//- minicore: sized, fn
fn callee(a: u8, b: u8) {}
fn main() {
callee(match 0 {
@ -1613,6 +1661,7 @@ fn main() {
);
check(
r#"
//- minicore: sized, fn
fn callee(a: u8, b: u8) {}
fn main() {
callee(match 0 {
@ -1626,6 +1675,7 @@ fn main() {
);
check(
r#"
//- minicore: sized, fn
fn callee(a: u8, b: u8) {}
fn main() {
callee($0match 0 {
@ -1643,6 +1693,7 @@ fn main() {
fn test_generics_simple() {
check(
r#"
//- minicore: sized, fn
/// Option docs.
enum Option<T> {
Some(T),
@ -1666,6 +1717,7 @@ fn f() {
fn test_generics_on_variant() {
check(
r#"
//- minicore: sized, fn
/// Option docs.
enum Option<T> {
/// Some docs.
@ -1693,6 +1745,7 @@ fn f() {
fn test_lots_of_generics() {
check(
r#"
//- minicore: sized, fn
trait Tr<T> {}
struct S<T>(T);
@ -1716,6 +1769,7 @@ fn f() {
fn test_generics_in_trait_ufcs() {
check(
r#"
//- minicore: sized, fn
trait Tr {
fn f<T: Tr, U>() {}
}
@ -1739,6 +1793,7 @@ fn f() {
fn test_generics_in_method_call() {
check(
r#"
//- minicore: sized, fn
struct S;
impl S {
@ -1760,6 +1815,7 @@ fn f() {
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 sup() {
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 @@ fn f() {
);
check(
r#"
//- minicore: sized, fn
fn callee<'a, const A: u8, T, const C: u8>() {}
fn f() {
@ -1809,6 +1867,7 @@ fn f() {
fn test_trait_assoc_types() {
check(
r#"
//- minicore: sized, fn
trait Trait<'a, T> {
type Assoc;
}
@ -1821,6 +1880,7 @@ fn f() -> impl Trait<(), $0
);
check(
r#"
//- minicore: sized, fn
trait Iterator {
type Item;
}
@ -1833,6 +1893,7 @@ fn f() -> impl Iterator<$0
);
check(
r#"
//- minicore: sized, fn
trait Iterator {
type Item;
}
@ -1845,6 +1906,7 @@ fn f() -> impl Iterator<Item = $0
);
check(
r#"
//- minicore: sized, fn
trait Tr {
type A;
type B;
@ -1858,6 +1920,7 @@ fn f() -> impl Tr<$0
);
check(
r#"
//- minicore: sized, fn
trait Tr {
type A;
type B;
@ -1871,6 +1934,7 @@ fn f() -> impl Tr<B$0
);
check(
r#"
//- minicore: sized, fn
trait Tr {
type A;
type B;
@ -1884,6 +1948,7 @@ fn f() -> impl Tr<B = $0
);
check(
r#"
//- minicore: sized, fn
trait Tr {
type A;
type B;
@ -1901,6 +1966,7 @@ fn f() -> impl Tr<B = (), $0
fn test_supertrait_assoc() {
check(
r#"
//- minicore: sized, fn
trait Super {
type SuperTy;
}
@ -1920,6 +1986,7 @@ fn f() -> impl Sub<$0
fn no_assoc_types_outside_type_bounds() {
check(
r#"
//- minicore: sized, fn
trait Tr<T> {
type Assoc;
}
@ -1938,6 +2005,7 @@ impl Tr<$0
// 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 f() {
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 f() {
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 @@ fn f<F: FnOnce(u8, u16) -> i32>(f: F) {
);
check(
r#"
//- minicore: sized, fn
fn f<T, F: FnMut(&T, u16) -> &T>(f: F) {
f($0)
}
@ -1996,8 +2067,15 @@ fn f<T, F: FnMut(&T, u16) -> &T>(f: F) {
#[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 @@ fn take<C, Error>(
move || count
}
"#,
expect![[r#"
impl Fn() -> i32
"#]],
expect![""],
);
}
@ -2018,6 +2094,7 @@ fn take<C, Error>(
fn record_literal() {
check(
r#"
//- minicore: sized, fn
struct Strukt<T, U = ()> {
t: T,
u: U,
@ -2041,6 +2118,7 @@ fn f() {
fn record_literal_nonexistent_field() {
check(
r#"
//- minicore: sized, fn
struct Strukt {
a: u8,
}
@ -2062,6 +2140,7 @@ fn f() {
fn tuple_variant_record_literal() {
check(
r#"
//- minicore: sized, fn
enum Opt {
Some(u8),
}
@ -2076,6 +2155,7 @@ fn f() {
);
check(
r#"
//- minicore: sized, fn
enum Opt {
Some(u8),
}
@ -2094,6 +2174,7 @@ fn f() {
fn record_literal_self() {
check(
r#"
//- minicore: sized, fn
struct S { t: u8 }
impl S {
fn new() -> Self {
@ -2112,6 +2193,7 @@ impl S {
fn record_pat() {
check(
r#"
//- minicore: sized, fn
struct Strukt<T, U = ()> {
t: T,
u: U,
@ -2135,6 +2217,7 @@ fn f() {
fn test_enum_in_nested_method_in_lambda() {
check(
r#"
//- minicore: sized, fn
enum A {
A,
B
@ -2158,6 +2241,7 @@ fn main() {
fn test_tuple_expr_free() {
check(
r#"
//- minicore: sized, fn
fn main() {
(0$0, 1, 3);
}
@ -2169,6 +2253,7 @@ fn main() {
);
check(
r#"
//- minicore: sized, fn
fn main() {
($0 1, 3);
}
@ -2180,6 +2265,7 @@ fn main() {
);
check(
r#"
//- minicore: sized, fn
fn main() {
(1, 3 $0);
}
@ -2191,6 +2277,7 @@ fn main() {
);
check(
r#"
//- minicore: sized, fn
fn main() {
(1, 3 $0,);
}
@ -2206,6 +2293,7 @@ fn main() {
fn test_tuple_expr_expected() {
check(
r#"
//- minicore: sized, fn
fn main() {
let _: (&str, u32, u32)= ($0, 1, 3);
}
@ -2218,6 +2306,7 @@ fn main() {
// 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 @@ fn main() {
);
check(
r#"
//- minicore: sized, fn
fn main() {
let _: (&str, u32, u32)= ($0, 1, 3, 5);
}
@ -2244,6 +2334,7 @@ fn main() {
fn test_tuple_pat_free() {
check(
r#"
//- minicore: sized, fn
fn main() {
let ($0, 1, 3);
}
@ -2255,6 +2346,7 @@ fn main() {
);
check(
r#"
//- minicore: sized, fn
fn main() {
let (0$0, 1, 3);
}
@ -2266,6 +2358,7 @@ fn main() {
);
check(
r#"
//- minicore: sized, fn
fn main() {
let ($0 1, 3);
}
@ -2277,6 +2370,7 @@ fn main() {
);
check(
r#"
//- minicore: sized, fn
fn main() {
let (1, 3 $0);
}
@ -2288,6 +2382,7 @@ fn main() {
);
check(
r#"
//- minicore: sized, fn
fn main() {
let (1, 3 $0,);
}
@ -2299,6 +2394,7 @@ fn main() {
);
check(
r#"
//- minicore: sized, fn
fn main() {
let (1, 3 $0, ..);
}
@ -2310,6 +2406,7 @@ fn main() {
);
check(
r#"
//- minicore: sized, fn
fn main() {
let (1, 3, .., $0);
}
@ -2326,6 +2423,7 @@ fn main() {
fn test_tuple_pat_expected() {
check(
r#"
//- minicore: sized, fn
fn main() {
let (0$0, 1, 3): (i32, i32, i32);
}
@ -2337,6 +2435,7 @@ fn main() {
);
check(
r#"
//- minicore: sized, fn
fn main() {
let ($0, 1, 3): (i32, i32, i32);
}
@ -2348,6 +2447,7 @@ fn main() {
);
check(
r#"
//- minicore: sized, fn
fn main() {
let (1, 3 $0): (i32,);
}
@ -2359,6 +2459,7 @@ fn main() {
);
check(
r#"
//- minicore: sized, fn
fn main() {
let (1, 3 $0, ..): (i32, i32, i32, i32);
}
@ -2370,6 +2471,7 @@ fn main() {
);
check(
r#"
//- minicore: sized, fn
fn main() {
let (1, 3, .., $0): (i32, i32, i32);
}
@ -2384,6 +2486,7 @@ fn main() {
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 @@ fn main() {
);
check(
r#"
//- minicore: sized, fn
fn main() {
let ($0 1, 3) = (1, 2, 3);
}
@ -2407,6 +2511,7 @@ fn main() {
);
check(
r#"
//- minicore: sized, fn
fn main() {
let (1, 3 $0) = (1,);
}
@ -2418,6 +2523,7 @@ fn main() {
);
check(
r#"
//- minicore: sized, fn
fn main() {
let (1, 3 $0, ..) = (1, 2, 3, 4);
}
@ -2429,6 +2535,7 @@ fn main() {
);
check(
r#"
//- minicore: sized, fn
fn main() {
let (1, 3, .., $0) = (1, 2, 3);
}
@ -2444,6 +2551,7 @@ fn main() {
fn test_tuple_generic_param() {
check(
r#"
//- minicore: sized, fn
struct S<T>(T);
fn main() {
@ -2461,6 +2569,7 @@ fn main() {
fn test_enum_generic_param() {
check(
r#"
//- minicore: sized, fn
enum Option<T> {
Some(T),
None,
@ -2481,6 +2590,7 @@ fn main() {
fn test_enum_variant_generic_param() {
check(
r#"
//- minicore: sized, fn
enum Option<T> {
Some(T),
None,
@ -2501,6 +2611,7 @@ fn main() {
fn test_generic_arg_with_default() {
check(
r#"
//- minicore: sized, fn
struct S<T = u8> {
field: T,
}
@ -2517,6 +2628,7 @@ fn main() {
check(
r#"
//- minicore: sized, fn
struct S<const C: u8 = 5> {
field: C,
}
@ -2531,4 +2643,27 @@ fn main() {
"#]],
);
}
#[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>
^^^^
"#]],
);
}
}

View file

@ -114,9 +114,9 @@ pub struct HighlightConfig<'a> {
// |-----------|--------------------------------|
// |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 @@ pub struct HighlightConfig<'a> {
// |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.|
//
// ![Semantic Syntax Highlighting](https://user-images.githubusercontent.com/48062697/113164457-06cfb980-9239-11eb-819b-0f93e646acf8.png)
// ![Semantic Syntax Highlighting](https://user-images.githubusercontent.com/48062697/113187625-f7f50100-9250-11eb-825e-91c58f236071.png)

View file

@ -53,7 +53,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
<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">&lt;</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">&gt;</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">&lt;</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">&gt;</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>

View file

@ -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>

View file

@ -1497,3 +1497,17 @@ fn main() {
false,
);
}
#[test]
fn regression_20952() {
check_highlighting(
r#"
//- minicore: fmt
fn main() {
format_args!("{} {}, {} (подозрение на спам: {:.2}%)"б);
}
"#,
expect_file!["./test_data/regression_20952.html"],
false,
);
}

View file

@ -94,7 +94,17 @@ pub(crate) mod entry {
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);
}

View file

@ -588,6 +588,12 @@ fn closure_expr(p: &mut Parser<'_>) -> CompletedMarker {
}
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);

View file

@ -424,6 +424,14 @@ fn fn_(p: &mut Parser<'_>, m: Marker) {
// 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);

View file

@ -265,6 +265,8 @@ mod ok {
#[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 @@ mod err {
#[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 @@ mod err {
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",

View file

@ -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

View file

@ -0,0 +1 @@
fn foo() { || -> A> { let x = 1; } }

View file

@ -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

View file

@ -0,0 +1,2 @@
fn foo() -> A>]) { let x = 1; }
fn foo() -> A>]) where T: Copy { let x = 1; }

View file

@ -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"

View file

@ -0,0 +1,8 @@
#!/usr/bin/env cargo
---
[dependencies]
clap = { version = "4.2", features = ["derive"] }
---
fn main() {}

View file

@ -326,7 +326,7 @@ fn test_fn_like_macro_clone_literals() {
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 @@ fn test_fn_like_macro_clone_literals() {
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

View file

@ -86,6 +86,7 @@ impl WorkspaceBuildScripts {
config,
&allowed_features,
workspace.manifest_path(),
workspace.target_directory().as_ref(),
current_dir,
sysroot,
toolchain,
@ -106,8 +107,9 @@ impl WorkspaceBuildScripts {
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 @@ impl WorkspaceBuildScripts {
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 @@ impl WorkspaceBuildScripts {
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 {

View file

@ -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 @@ impl Default for CargoFeatures {
}
}
#[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 struct CargoConfig {
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 @@ impl FetchMetadata {
}
}
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: _,

View file

@ -62,7 +62,7 @@ pub use crate::{
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},

View file

@ -9,7 +9,7 @@ use std::{env, fs, ops::Not, path::Path, process::Command};
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 @@ impl Sysroot {
&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 @@ impl Sysroot {
match self.load_library_via_cargo(
&library_manifest,
src_root,
target_dir,
cargo_config,
no_deps,
progress,
@ -328,7 +326,6 @@ impl Sysroot {
&self,
library_manifest: &ManifestPath,
current_dir: &AbsPath,
target_dir: &Utf8Path,
cargo_config: &CargoMetadataConfig,
no_deps: bool,
progress: &dyn Fn(String),
@ -345,7 +342,7 @@ impl Sysroot {
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.

View file

@ -238,12 +238,8 @@ fn smoke_test_real_sysroot_cargo() {
);
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);
}

View file

@ -16,7 +16,7 @@ use paths::{AbsPath, AbsPathBuf, Utf8PathBuf};
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 @@ impl ProjectWorkspace {
&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 @@ impl ProjectWorkspace {
},
&sysroot,
*no_deps,
).exec(&target_dir, true, progress) {
).exec(true, progress) {
Ok((meta, _error)) => {
let workspace = CargoWorkspace::new(
meta,
@ -374,16 +369,16 @@ impl ProjectWorkspace {
})
});
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 @@ impl ProjectWorkspace {
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 @@ impl ProjectWorkspace {
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 @@ impl ProjectWorkspace {
.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 @@ impl ProjectWorkspace {
&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 add_dep_inner(graph: &mut CrateGraphBuilder, from: CrateBuilderId, dep: Depen
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()
}

View file

@ -42,6 +42,7 @@ tenthash = "1.1.0"
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 @@ semver.workspace = true
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 @@ in-rust-tree = [
"hir-ty/in-rust-tree",
"load-cargo/in-rust-tree",
]
dhat = ["dep:dhat"]
[lints]
workspace = true

View file

@ -1214,6 +1214,7 @@ impl flags::AnalysisStats {
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 {

View file

@ -9,7 +9,6 @@ use hir::{ChangeWithProcMacros, Crate};
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 @@ impl Tester {
};
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);
}

View file

@ -10,9 +10,9 @@ use hir::Symbol;
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 itertools::{Either, Itertools};
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 @@ config_data! {
/// 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 @@ config_data! {
/// 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 @@ pub struct LensConfig {
// annotations
pub location: AnnotationLocation,
pub filter_adjacent_derive_implementations: bool,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
@ -1469,6 +1479,7 @@ impl LensConfig {
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 @@ impl Config {
}
}
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 @@ impl Config {
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 @@ impl Config {
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 @@ impl Config {
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 @@ impl Config {
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 @@ impl Config {
}
}
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 @@ impl Config {
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 @@ fn doc_comment_to_string(doc: &[&str]) -> String {
#[cfg(test)]
mod tests {
use std::fs;
use std::{borrow::Cow, fs};
use test_utils::{ensure_file_contents, project_root};
@ -4093,9 +4121,13 @@ mod tests {
(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 @@ mod tests {
(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 @@ mod tests {
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"))
));
}
}

View file

@ -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::FileId;
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 @@ pub(crate) struct DiagnosticCollection {
#[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,
}

View file

@ -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 @@ use super::{DiagnosticsMapConfig, Fix};
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 primary_location(
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 @@ fn map_rust_child_diagnostic(
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 @@ fn map_rust_child_diagnostic(
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 @@ pub(crate) struct MappedRustDiagnostic {
/// 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 @@ pub(crate) fn map_rust_diagnostic_to_lsp(
}
}
}
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 @@ mod tests {
),
);
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)
}

View file

@ -191,7 +191,7 @@
},
},
},
message: "Error originated from macro call here",
message: "Actual error occurred here",
},
DiagnosticRelatedInformation {
location: Location {

View file

@ -13,6 +13,7 @@ use crossbeam_channel::{Receiver, Sender, select_biased, unbounded};
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) struct CargoOptions {
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 @@ pub(crate) enum Target {
}
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 @@ impl CargoOptions {
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 @@ impl FlycheckHandle {
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 @@ impl FlycheckHandle {
sysroot_root,
workspace_root,
manifest_path,
ws_target_dir,
);
let (sender, receiver) = unbounded::<StateChange>();
let thread =
@ -314,6 +317,7 @@ struct FlycheckActor {
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 @@ impl FlycheckActor {
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 @@ impl FlycheckActor {
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 @@ impl FlycheckActor {
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 @@ impl FlycheckActor {
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)
}

View file

@ -33,7 +33,9 @@ use triomphe::Arc;
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 @@ pub(crate) fn handle_analyzer_status(
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`."
))
}
#[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."
))
}
}
format_to!(out, "{:>8} Remaining\n", profile::memory_usage().allocated);
Ok(out)
}
pub(crate) fn handle_view_syntax_tree(
@ -264,6 +284,7 @@ pub(crate) fn handle_run_test(
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 @@ pub(crate) fn handle_goto_implementation(
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 @@ pub(crate) fn handle_rename(
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, &params.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, &params.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 @@ pub(crate) fn handle_code_action(
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 @@ pub(crate) fn handle_code_action_resolve(
))
.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 to_command_link(command: lsp_types::Command, tooltip: String) -> lsp_ext::Com
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_impl_command_link(
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 show_ref_command_link(
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 @@ fn runnable_action_links(
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 runnable_action_links(
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 @@ fn prepare_hover_actions(
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()
}

View file

@ -82,3 +82,10 @@ macro_rules! try_default_ {
};
}
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);

View file

@ -26,7 +26,7 @@ use serde_json::to_value;
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 @@ pub(crate) fn completion_items(
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 @@ pub(crate) fn completion_items(
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 @@ fn completion_item(
} 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_kind(kind: AssistKind) -> lsp_types::CodeActionKind {
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 @@ pub(crate) fn code_action(
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())

View file

@ -60,6 +60,14 @@ pub fn main_loop(config: Config, connection: Connection) -> anyhow::Result<()> {
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 @@ impl GlobalState {
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,
);

View file

@ -23,6 +23,7 @@ use ide_db::{
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 @@ impl GlobalState {
None,
self.config.root_path().clone(),
None,
None,
)]
}
crate::flycheck::InvocationStrategy::PerWorkspace => {
@ -890,13 +892,17 @@ impl GlobalState {
| 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 @@ impl GlobalState {
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 @@ impl GlobalState {
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()

View file

@ -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 @@ impl CargoTestHandle {
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 @@ impl CargoTestHandle {
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);

View file

@ -1355,7 +1355,7 @@ pub mod tokens {
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,
)
});

View file

@ -644,6 +644,20 @@ impl SyntaxFactory {
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();

View file

@ -622,6 +622,15 @@ where
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)),
};

View file

@ -635,6 +635,13 @@ Default: `"client"`
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 @@ Default: `null`
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`

View file

@ -1627,6 +1627,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": {
@ -2749,6 +2759,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": {

View file

@ -71,32 +71,9 @@ export function analyzerStatus(ctx: CtxInit): Cmd {
}
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);
};
}

View file

@ -24,7 +24,9 @@ export async function applySnippetWorkspaceEdit(
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);
}

View file

@ -1 +1 @@
fb24b04b096a980bffd80154f6aba22fd07cb3d9
c5dabe8cf798123087d094f06417f5a767ca73e8

View file

@ -45,11 +45,22 @@ impl flags::Dist {
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 @@ fn dist_server(
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 @@ fn dist_server(
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 @@ fn build_command<'a>(
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}"
)
}

View file

@ -42,6 +42,10 @@ xflags::xflags! {
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 @@ xflags::xflags! {
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 struct Install {
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 Release {
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 @@ pub(crate) enum Malloc {
System,
Mimalloc,
Jemalloc,
Dhat,
}
impl Malloc {
@ -288,6 +299,7 @@ impl Malloc {
Malloc::System => &[][..],
Malloc::Mimalloc => &["--features", "mimalloc"],
Malloc::Jemalloc => &["--features", "jemalloc"],
Malloc::Dhat => &["--features", "dhat"],
}
}
}
@ -301,12 +313,15 @@ impl Install {
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 @@ impl Dist {
Malloc::Mimalloc
} else if self.jemalloc {
Malloc::Jemalloc
} else if self.enable_profiling {
Malloc::Dhat
} else {
Malloc::System
}