Skip to content
Draft
Show file tree
Hide file tree
Changes from 2 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
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
145 changes: 145 additions & 0 deletions compiler/rustc_lint/src/receiver.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
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, ParamEnv, Ty, TyCtxt, TypingEnv, TypingMode};
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(TypingMode::non_body_analysis());
Comment thread
BennoLossin marked this conversation as resolved.
Outdated
if let Some(CheckResult { impl_plus_target }) =
check(cx.tcx, &infcx, cx.typing_env(), cx.param_env, self_ty)
Comment thread
BennoLossin marked this conversation as resolved.
Outdated
{
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>,
param_env: ParamEnv<'tcx>,
ty: Ty<'tcx>,
) -> Option<CheckResult> {
let deref_trait = tcx.require_lang_item(LangItem::Deref, DUMMY_SP);
Comment thread
BennoLossin marked this conversation as resolved.
let deref_target = tcx.require_lang_item(LangItem::DerefTarget, DUMMY_SP);
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(deref_trait, [ty], 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, param_env, target_ty) {
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()
&& infcx.type_implements_trait(receiver_trait, [ty], 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, param_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)
}
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
99 changes: 99 additions & 0 deletions tests/ui/lint/lint-inherent-method-on-receiver.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
#![feature(arbitrary_self_types)]
#![deny(inherent_method_on_receiver)]
#![allow(dead_code)]

use std::ops::{Deref, Receiver};

// Base case to trigger the lint on `Deref`

pub struct Thing<T>(T);

impl<T> Deref for Thing<T> {
type Target = T;

fn deref(&self) -> &T {
&self.0
}
}

impl<T> Thing<T> {
pub fn method(&self) {}
//~^ ERROR: inherent methods on types that implement `Deref` or `Receiver` shadow methods of their target [inherent_method_on_receiver]
}

// Base case to trigger the lint on `Receiver`

pub struct Thing2<T>(T);

impl<T> Receiver for Thing2<T> {
type Target = T;
}

impl<T> Thing2<T> {
pub fn method(&self) {}
//~^ ERROR: inherent methods on types that implement `Deref` or `Receiver` shadow methods of their target [inherent_method_on_receiver]
}

// Also work with the `self: X` syntax

pub struct Thing3<T>(T);

impl<T> Receiver for Thing3<T> {
type Target = T;
}

impl<T> Thing3<T> {
pub fn method(self: Box<Self>) {}
//~^ ERROR: inherent methods on types that implement `Deref` or `Receiver` shadow methods of their target [inherent_method_on_receiver]
}

// Not publicily accessible, no lint

pub struct Thing4<T>(T);

impl<T> Receiver for Thing4<T> {
type Target = T;
}

impl<T> Thing4<T> {
fn method(&self) {}
}

// Not publicily accessible, no lint 2

pub(crate) struct Thing5<T>(T);

impl<T> Receiver for Thing5<T> {
type Target = T;
}

impl<T> Thing5<T> {
fn method(&self) {}
}

// Only true generics are triggering the lint

pub(crate) struct Thing6<T>(T);

impl<T> Receiver for Thing6<T> {
type Target = [T];
}

impl<T> Thing6<T> {
pub fn method(&self) {}
}

// Receiving to something concrete, but that then derefs to a generic still triggers the lint

pub struct Thing7<T>(T);

impl<T> Receiver for Thing7<T> {
type Target = Thing<T>;
}

impl<T> Thing7<T> {
pub fn method(&self) {}
//~^ ERROR: inherent methods on types that implement `Deref` or `Receiver` shadow methods of their target [inherent_method_on_receiver]
}

fn main() {}
Loading
Loading