Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions compiler/rustc_data_structures/src/atomic_ref.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ impl<T: 'static> AtomicRef<T> {
AtomicRef(AtomicPtr::new(initial as *const T as *mut T), PhantomData)
}

#[cfg_attr(not(bootstrap), expect(inherent_method_on_receiver, reason = "compiler internals"))]
pub fn swap(&self, new: &'static T) -> &'static T {
// We never allow storing anything but a `'static` reference so it's safe to
// return it for the same.
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_data_structures/src/marker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -197,12 +197,14 @@ impl<T> FromDyn<T> {
}

#[inline(always)]
#[cfg_attr(not(bootstrap), expect(inherent_method_on_receiver, reason = "compiler internals"))]
pub fn derive<O>(&self, val: O) -> FromDyn<O> {
// We already did the check for `sync::is_dyn_thread_safe()` when creating `Self`
FromDyn(val)
}

#[inline(always)]
#[cfg_attr(not(bootstrap), expect(inherent_method_on_receiver, reason = "compiler internals"))]
pub fn into_inner(self) -> T {
self.0
}
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_data_structures/src/sync/freeze.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ pub struct FreezeWriteGuard<'a, T: ?Sized> {
}

impl<'a, T> FreezeWriteGuard<'a, T> {
#[cfg_attr(not(bootstrap), expect(inherent_method_on_receiver, reason = "compiler internals"))]
pub fn freeze(self) -> &'a T {
self.frozen.store(true, Ordering::Release);

Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_data_structures/src/sync/worker_local.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ impl<T> WorkerLocal<T> {

/// Returns the worker-local values for each thread
#[inline]
#[cfg_attr(not(bootstrap), expect(inherent_method_on_receiver, reason = "compiler internals"))]
pub fn into_inner(self) -> impl Iterator<Item = T> {
self.locals.into_vec().into_iter().map(|local| local.0)
}
Expand Down
3 changes: 3 additions & 0 deletions compiler/rustc_data_structures/src/tagged_ptr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,13 +136,15 @@ where

/// Retrieves the pointer.
#[inline]
#[cfg_attr(not(bootstrap), expect(inherent_method_on_receiver, reason = "compiler internals"))]
pub fn pointer(self) -> &'a P {
// SAFETY: pointer_raw returns the original pointer
unsafe { self.pointer_raw().as_ref() }
}

/// Retrieves the tag.
#[inline]
#[cfg_attr(not(bootstrap), expect(inherent_method_on_receiver, reason = "compiler internals"))]
pub fn tag(&self) -> T {
// Unpack the tag, according to the `self.packed` encoding scheme
let tag = self.packed.addr().get() >> Self::TAG_BIT_SHIFT;
Expand All @@ -155,6 +157,7 @@ where

/// Sets the tag to a new value.
#[inline]
#[cfg_attr(not(bootstrap), expect(inherent_method_on_receiver, reason = "compiler internals"))]
pub fn set_tag(&mut self, tag: T) {
self.packed = Self::pack(self.pointer_raw(), tag);
}
Expand Down
4 changes: 4 additions & 0 deletions compiler/rustc_lint/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ mod pass_by_value;
mod passes;
mod precedence;
mod ptr_nulls;
mod receiver;
mod redundant_semicolon;
mod reference_casting;
mod shadowed_into_iter;
Expand Down Expand Up @@ -139,6 +140,8 @@ pub use rustc_errors::BufferedEarlyLint;
pub use rustc_session::lint::Level::{self, *};
pub use rustc_session::lint::{FutureIncompatibleInfo, Lint, LintId, LintPass, LintVec};

use crate::receiver::InherentMethodOnReceiver;

rustc_fluent_macro::fluent_messages! { "../messages.ftl" }

pub fn provide(providers: &mut Providers) {
Expand Down Expand Up @@ -204,6 +207,7 @@ late_lint_methods!(
LetUnderscore: LetUnderscore,
InvalidReferenceCasting: InvalidReferenceCasting,
ImplicitAutorefs: ImplicitAutorefs,
InherentMethodOnReceiver: InherentMethodOnReceiver,
// Depends on referenced function signatures in expressions
UnusedResults: UnusedResults,
UnitBindings: UnitBindings,
Expand Down
154 changes: 154 additions & 0 deletions compiler/rustc_lint/src/receiver.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
use rustc_hir::def_id::DefId;
use rustc_hir::{ImplItemImplKind, ImplItemKind, LangItem, PatKind};
use rustc_infer::infer::InferCtxt;
use rustc_middle::ty::{self, AliasTy, AliasTyKind, Ty, TyCtxt, TypingEnv};
use rustc_session::{declare_lint, declare_lint_pass};
use rustc_span::{DUMMY_SP, kw, sym};
use rustc_trait_selection::infer::{InferCtxtExt, TyCtxtInferExt};

use crate::{LateLintPass, LintContext};

declare_lint! {
INHERENT_METHOD_ON_RECEIVER,
Warn,
"inherent methods on types that implement `Deref` or `Receiver` shadow methods of their target",
}

declare_lint_pass!(InherentMethodOnReceiver => [INHERENT_METHOD_ON_RECEIVER]);

// FIXME:
// - make this lint nightly-only

impl<'tcx> LateLintPass<'tcx> for InherentMethodOnReceiver {
fn check_impl_item(
&mut self,
cx: &crate::LateContext<'tcx>,
item: &'tcx rustc_hir::ImplItem<'tcx>,
) {
let ImplItemImplKind::Inherent { .. } = item.impl_kind else {
return;
};
if !cx.tcx.effective_visibilities(()).is_exported(item.owner_id.def_id) {
return;
}
let Some(impl_id) = cx.tcx.inherent_impl_of_assoc(item.owner_id.def_id.to_def_id()) else {
return;
};
let ImplItemKind::Fn(signature, body) = item.kind else {
return;
};

let Some(first_param) = cx.tcx.hir_body(body).params.first() else {
return;
};

if !signature.decl.implicit_self.has_implicit_self() {
let PatKind::Binding(_, _, ident, None) = first_param.pat.kind else {
return;
};
if ident.name != kw::SelfLower {
return;
}
}
let self_ty: ty::EarlyBinder<'tcx, Ty<'tcx>> = cx.tcx.type_of(impl_id);
let self_ty = self_ty.instantiate_identity();
let infcx: InferCtxt<'tcx> = cx.tcx.infer_ctxt().build(cx.typing_mode());
if let Some(CheckResult { impl_plus_target }) =
check(cx.tcx, &infcx, cx.typing_env(), self_ty)
{
cx.span_lint(INHERENT_METHOD_ON_RECEIVER, first_param.span, |lint| {
for (impl_id, target_id) in impl_plus_target {
lint.span_label(cx.tcx.def_span(impl_id), "trait implemented here");
lint.span_label(cx.tcx.def_span(target_id), "with `Target` set here");
}
lint.primary_message(
"inherent methods on types that implement `Deref` or `Receiver` shadow \
methods of their target",
);
});
}
}
}

struct CheckResult {
impl_plus_target: Vec<(DefId, DefId)>,
}

fn check<'tcx>(
tcx: TyCtxt<'tcx>,
infcx: &InferCtxt<'tcx>,
typing_env: TypingEnv<'tcx>,
ty: Ty<'tcx>,
) -> Option<CheckResult> {
let deref_trait = tcx.require_lang_item(LangItem::Deref, DUMMY_SP);
let deref_target = tcx.require_lang_item(LangItem::DerefTarget, DUMMY_SP);

if infcx
.type_implements_trait(deref_trait, [ty], typing_env.param_env)
.must_apply_modulo_regions()
{
let target_ty = tcx.normalize_erasing_regions(
typing_env,
Ty::new_alias(tcx, AliasTyKind::Projection, AliasTy::new(tcx, deref_target, [ty])),
);
if let ty::Param(_) = target_ty.kind() {
let (impl_id, target_id) = find_impl_and_target_id(tcx, deref_trait, ty);
return Some(CheckResult { impl_plus_target: vec![(impl_id, target_id)] });
}
if let Some(mut result) = check(tcx, infcx, typing_env, target_ty) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels like you could end up in an infinite loop, e.g.

impl Deref for StructA {
  type Target = StructB;
}
impl Deref for StructB {
  type Target = StructA;
}

That doesn't matter for the crater run tho

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we could use recursion limit: tcx.recursion_limit().value_within_limit(..)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't we just keep track of all the types we have walked through and then we stop when we encounter a cycle?

let (impl_id, target_id) = find_impl_and_target_id(tcx, deref_trait, ty);
result.impl_plus_target.push((impl_id, target_id));
return Some(result);
}
}
if tcx.features().arbitrary_self_types() {
let receiver_trait = tcx.require_lang_item(LangItem::Receiver, DUMMY_SP);
let receiver_target = tcx.require_lang_item(LangItem::ReceiverTarget, DUMMY_SP);
if infcx
.type_implements_trait(receiver_trait, [ty], typing_env.param_env)
.must_apply_modulo_regions()
{
let target_ty = tcx.normalize_erasing_regions(
typing_env,
Ty::new_alias(
tcx,
AliasTyKind::Projection,
AliasTy::new(tcx, receiver_target, [ty]),
),
);
if let ty::Param(_) = target_ty.kind() {
let (impl_id, target_id) = find_impl_and_target_id(tcx, receiver_trait, ty);
return Some(CheckResult { impl_plus_target: vec![(impl_id, target_id)] });
}
if let Some(mut result) = check(tcx, infcx, typing_env, target_ty) {
let (impl_id, target_id) = find_impl_and_target_id(tcx, receiver_trait, ty);
result.impl_plus_target.push((impl_id, target_id));
return Some(result);
}
}
}
None
}

fn find_impl_and_target_id<'tcx>(
tcx: TyCtxt<'tcx>,
trait_id: DefId,
ty: Ty<'tcx>,
) -> (DefId, DefId) {
let mut impl_id: Option<DefId> = None;
tcx.for_each_relevant_impl(trait_id, ty, |did| {
if let Some(_impl_id) = impl_id.take() {
// let spans = vec![tcx.def_span(impl_id)];
// println!("{impl_id:?} {did:?}");
// span_bug!(spans, "found two impl blocks for the trait");
}
impl_id = Some(did);
});
let impl_id = impl_id.expect("trait not implemented?");

let targets: Vec<_> =
tcx.associated_items(impl_id).filter_by_name_unhygienic(sym::Target).collect();
//assert!(targets.len() == 1, "did not find exactly one target");
let target_id = targets[0].def_id;
(impl_id, target_id)
}
1 change: 1 addition & 0 deletions compiler/rustc_thread_pool/src/worker_local.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ impl<T> WorkerLocal<T> {

/// Returns the worker-local value for each thread
#[inline]
#[cfg_attr(not(bootstrap), expect(inherent_method_on_receiver, reason = "compiler internals"))]
pub fn into_inner(self) -> Vec<T> {
self.locals.into_iter().map(|c| c.0).collect()
}
Expand Down
2 changes: 2 additions & 0 deletions library/alloc/src/borrow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,7 @@ impl<B: ?Sized + ToOwned> Cow<'_, B> {
/// );
/// ```
#[stable(feature = "rust1", since = "1.0.0")]
#[expect(inherent_method_on_receiver, reason = "stable API cannot be changed")]
pub fn to_mut(&mut self) -> &mut <B as ToOwned>::Owned {
match *self {
Borrowed(borrowed) => {
Expand Down Expand Up @@ -328,6 +329,7 @@ impl<B: ?Sized + ToOwned> Cow<'_, B> {
/// );
/// ```
#[stable(feature = "rust1", since = "1.0.0")]
#[expect(inherent_method_on_receiver, reason = "stable API cannot be changed")]
pub fn into_owned(self) -> <B as ToOwned>::Owned {
match self {
Borrowed(borrowed) => borrowed.to_owned(),
Expand Down
3 changes: 3 additions & 0 deletions library/alloc/src/collections/binary_heap/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,9 @@ impl<'a, T: Ord, A: Allocator> PeekMut<'a, T, A> {
/// ```
#[unstable(feature = "binary_heap_peek_mut_refresh", issue = "138355")]
#[must_use = "is equivalent to dropping and getting a new PeekMut except for return information"]
// FIXME(inherent_method_on_receiver): t-libs-api needs to weigh in on whether this should be an
// associated functions instead
#[allow(inherent_method_on_receiver)]
pub fn refresh(&mut self) -> bool {
// The length of the underlying heap is unchanged by sifting down. The value stored for leak
// amplification thus remains accurate. We erase the leak amplification firstly because the
Expand Down
6 changes: 6 additions & 0 deletions library/core/src/pin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1531,6 +1531,7 @@ impl<'a, T: ?Sized> Pin<&'a T> {
///
/// [`pin` module]: self#projections-and-structural-pinning
#[stable(feature = "pin", since = "1.33.0")]
#[expect(inherent_method_on_receiver, reason = "stable API cannot be changed")]
pub unsafe fn map_unchecked<U, F>(self, func: F) -> Pin<&'a U>
where
U: ?Sized,
Expand Down Expand Up @@ -1565,6 +1566,7 @@ impl<'a, T: ?Sized> Pin<&'a T> {
#[must_use]
#[rustc_const_stable(feature = "const_pin", since = "1.84.0")]
#[stable(feature = "pin", since = "1.33.0")]
#[expect(inherent_method_on_receiver, reason = "stable API cannot be changed")]
pub const fn get_ref(self) -> &'a T {
self.pointer
}
Expand All @@ -1576,6 +1578,7 @@ impl<'a, T: ?Sized> Pin<&'a mut T> {
#[must_use = "`self` will be dropped if the result is not used"]
#[rustc_const_stable(feature = "const_pin", since = "1.84.0")]
#[stable(feature = "pin", since = "1.33.0")]
#[expect(inherent_method_on_receiver, reason = "stable API cannot be changed")]
pub const fn into_ref(self) -> Pin<&'a T> {
Pin { pointer: self.pointer }
}
Expand All @@ -1593,6 +1596,7 @@ impl<'a, T: ?Sized> Pin<&'a mut T> {
#[must_use = "`self` will be dropped if the result is not used"]
#[stable(feature = "pin", since = "1.33.0")]
#[rustc_const_stable(feature = "const_pin", since = "1.84.0")]
#[expect(inherent_method_on_receiver, reason = "stable API cannot be changed")]
pub const fn get_mut(self) -> &'a mut T
where
T: Unpin,
Expand All @@ -1614,6 +1618,7 @@ impl<'a, T: ?Sized> Pin<&'a mut T> {
#[must_use = "`self` will be dropped if the result is not used"]
#[stable(feature = "pin", since = "1.33.0")]
#[rustc_const_stable(feature = "const_pin", since = "1.84.0")]
#[expect(inherent_method_on_receiver, reason = "stable API cannot be changed")]
pub const unsafe fn get_unchecked_mut(self) -> &'a mut T {
self.pointer
}
Expand All @@ -1635,6 +1640,7 @@ impl<'a, T: ?Sized> Pin<&'a mut T> {
/// [`pin` module]: self#projections-and-structural-pinning
#[must_use = "`self` will be dropped if the result is not used"]
#[stable(feature = "pin", since = "1.33.0")]
#[expect(inherent_method_on_receiver, reason = "stable API cannot be changed")]
pub unsafe fn map_unchecked_mut<U, F>(self, func: F) -> Pin<&'a mut U>
where
U: ?Sized,
Expand Down
Loading
Loading