-
-
Notifications
You must be signed in to change notification settings - Fork 14.9k
[DO NOT MERGE] Method receiver lint #152214
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
BennoLossin
wants to merge
6
commits into
rust-lang:main
Choose a base branch
from
BennoLossin:method-receiver-lint
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from 2 commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
7d3d136
WIP: implement lint & fix it in std
BennoLossin c5b9d19
add test for inherent_method_on_receiver
BennoLossin 564dfee
move `LangItem::Receiver` into the feature gate check
BennoLossin e41b9c3
remove param_env and fix typing mode
BennoLossin 244f34c
expect lint in compiler's crates
BennoLossin 71b9538
fix ui tests
BennoLossin File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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()); | ||
| if let Some(CheckResult { impl_plus_target }) = | ||
| check(cx.tcx, &infcx, cx.typing_env(), cx.param_env, self_ty) | ||
|
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); | ||
|
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) | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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() {} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.