Skip to content

Commit 7739f03

Browse files
committed
Eliminate false-positives with the type-system
1 parent e21968b commit 7739f03

File tree

3 files changed

+282
-40
lines changed

3 files changed

+282
-40
lines changed

compiler/rustc_lint/src/non_local_def.rs

+124-11
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,18 @@
1-
use rustc_hir::{def::DefKind, Body, Item, ItemKind, Node, Path, QPath, TyKind};
1+
use rustc_hir::{def::DefKind, Body, Item, ItemKind, Node, TyKind};
2+
use rustc_hir::{Path, QPath};
3+
use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
4+
use rustc_infer::infer::InferCtxt;
5+
use rustc_infer::traits::{Obligation, ObligationCause};
6+
use rustc_middle::query::Key;
7+
use rustc_middle::ty::TypeSuperFoldable;
8+
use rustc_middle::ty::{self, Binder, Ty, TyCtxt, TypeFoldable, TypeFolder};
29
use rustc_span::def_id::{DefId, LOCAL_CRATE};
10+
use rustc_span::Span;
311
use rustc_span::{sym, symbol::kw, ExpnKind, MacroKind};
12+
use rustc_trait_selection::infer::TyCtxtInferExt;
13+
use rustc_trait_selection::traits::error_reporting::ambiguity::{
14+
compute_applicable_impls_for_diagnostics, Ambiguity,
15+
};
416

517
use crate::lints::{NonLocalDefinitionsCargoUpdateNote, NonLocalDefinitionsDiag};
618
use crate::{LateContext, LateLintPass, LintContext};
@@ -66,7 +78,9 @@ impl<'tcx> LateLintPass<'tcx> for NonLocalDefinitions {
6678
return;
6779
}
6880

69-
let parent = cx.tcx.parent(item.owner_id.def_id.into());
81+
let def_id = item.owner_id.def_id.into();
82+
83+
let parent = cx.tcx.parent(def_id);
7084
let parent_def_kind = cx.tcx.def_kind(parent);
7185
let parent_opt_item_name = cx.tcx.opt_item_name(parent);
7286

@@ -155,9 +169,54 @@ impl<'tcx> LateLintPass<'tcx> for NonLocalDefinitions {
155169
.map(|of_trait| path_has_local_parent(of_trait.path, cx, parent, parent_parent))
156170
.unwrap_or(false);
157171

158-
// If none of them have a local parent (LOGICAL NOR) this means that
159-
// this impl definition is a non-local definition and so we lint on it.
160-
if !(self_ty_has_local_parent || of_trait_has_local_parent) {
172+
// Detecting if the impl definition is leaking outside of it's defining scope.
173+
//
174+
// Rule: for each impl, instantiate all local types with inference vars and
175+
// then assemble candidates for that goal, if there are more than 1 (non-private
176+
// impls), it does not leak.
177+
//
178+
// https://github.com/rust-lang/rust/issues/121621#issuecomment-1976826895
179+
let impl_is_not_leaky = cx
180+
.tcx
181+
.impl_trait_ref(def_id)
182+
.map(|binder| {
183+
let infcx = cx.tcx.infer_ctxt().build();
184+
let trait_ref = binder
185+
.instantiate(cx.tcx, infcx.fresh_args_for_item(item.span, def_id));
186+
187+
let trait_ref = trait_ref.fold_with(&mut LocalTypeInferenceFolder {
188+
infcx: &infcx,
189+
to_ignore: 1,
190+
impl_parent: parent,
191+
impl_parent_parent: parent_parent,
192+
span: item.span,
193+
});
194+
195+
let poly_trait_obligation = Obligation::new(
196+
cx.tcx,
197+
ObligationCause::dummy(),
198+
ty::ParamEnv::empty(),
199+
Binder::dummy(trait_ref),
200+
);
201+
202+
let ambiguities = compute_applicable_impls_for_diagnostics(
203+
&infcx,
204+
&poly_trait_obligation,
205+
);
206+
207+
let mut it = ambiguities.iter().filter(|ambi| match ambi {
208+
Ambiguity::DefId(did) => {
209+
!did_has_local_parent(*did, cx.tcx, parent, parent_parent)
210+
}
211+
Ambiguity::ParamEnv(_) => false,
212+
});
213+
214+
let _ = it.next();
215+
it.next().is_some()
216+
})
217+
.unwrap_or(false);
218+
219+
if !(self_ty_has_local_parent || of_trait_has_local_parent || impl_is_not_leaky) {
161220
let const_anon = if self.body_depth == 1
162221
&& parent_def_kind == DefKind::Const
163222
&& parent_opt_item_name != Some(kw::Underscore)
@@ -207,6 +266,47 @@ impl<'tcx> LateLintPass<'tcx> for NonLocalDefinitions {
207266
}
208267
}
209268

269+
/// Replace every local type by inference variable.
270+
///
271+
/// ```text
272+
/// <Global<Local> as std::cmp::PartialEq<Global<Local>>>
273+
/// to
274+
/// <Global<_> as std::cmp::PartialEq<Global<_>>>
275+
/// ```
276+
struct LocalTypeInferenceFolder<'a, 'tcx> {
277+
infcx: &'a InferCtxt<'tcx>,
278+
to_ignore: u32,
279+
impl_parent: DefId,
280+
impl_parent_parent: Option<DefId>,
281+
span: Span,
282+
}
283+
284+
impl<'a, 'tcx> TypeFolder<TyCtxt<'tcx>> for LocalTypeInferenceFolder<'a, 'tcx> {
285+
fn interner(&self) -> TyCtxt<'tcx> {
286+
self.infcx.tcx
287+
}
288+
289+
fn fold_ty(&mut self, t: Ty<'tcx>) -> Ty<'tcx> {
290+
if let Some(ty_did) = t.ty_def_id()
291+
&& did_has_local_parent(
292+
ty_did,
293+
self.infcx.tcx,
294+
self.impl_parent,
295+
self.impl_parent_parent,
296+
)
297+
&& self.to_ignore == 0
298+
{
299+
self.infcx.next_ty_var(TypeVariableOrigin {
300+
kind: TypeVariableOriginKind::TypeInference,
301+
span: self.span,
302+
})
303+
} else {
304+
self.to_ignore = self.to_ignore.saturating_sub(1);
305+
t.super_fold_with(self)
306+
}
307+
}
308+
}
309+
210310
/// Given a path and a parent impl def id, this checks if the if parent resolution
211311
/// def id correspond to the def id of the parent impl definition.
212312
///
@@ -216,16 +316,29 @@ impl<'tcx> LateLintPass<'tcx> for NonLocalDefinitions {
216316
/// std::convert::PartialEq<Foo<Bar>>
217317
/// ^^^^^^^^^^^^^^^^^^^^^^^
218318
/// ```
319+
#[inline]
219320
fn path_has_local_parent(
220321
path: &Path<'_>,
221322
cx: &LateContext<'_>,
222323
impl_parent: DefId,
223324
impl_parent_parent: Option<DefId>,
224325
) -> bool {
225-
path.res.opt_def_id().is_some_and(|did| {
226-
did.is_local() && {
227-
let res_parent = cx.tcx.parent(did);
228-
res_parent == impl_parent || Some(res_parent) == impl_parent_parent
229-
}
230-
})
326+
path.res
327+
.opt_def_id()
328+
.is_some_and(|did| did_has_local_parent(did, cx.tcx, impl_parent, impl_parent_parent))
329+
}
330+
331+
/// Given a def id and a parent impl def id, this checks if the parent
332+
/// def id correspond to the def id of the parent impl definition.
333+
#[inline]
334+
fn did_has_local_parent(
335+
did: DefId,
336+
tcx: TyCtxt<'_>,
337+
impl_parent: DefId,
338+
impl_parent_parent: Option<DefId>,
339+
) -> bool {
340+
did.is_local() && {
341+
let res_parent = tcx.parent(did);
342+
res_parent == impl_parent || Some(res_parent) == impl_parent_parent
343+
}
231344
}

tests/ui/lint/non_local_definitions.rs

+87-1
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,6 @@ struct Cat;
282282

283283
fn meow() {
284284
impl From<Cat> for () {
285-
//~^ WARN non-local `impl` definition
286285
fn from(_: Cat) -> () {
287286
todo!()
288287
}
@@ -374,6 +373,72 @@ fn rawr() {
374373
todo!()
375374
}
376375
}
376+
377+
#[derive(Debug)]
378+
struct Elephant;
379+
380+
impl From<Wrap<Wrap<Elephant>>> for () {
381+
//~^ WARN non-local `impl` definition
382+
fn from(_: Wrap<Wrap<Elephant>>) -> Self {
383+
todo!()
384+
}
385+
}
386+
}
387+
388+
pub trait StillNonLocal {}
389+
390+
impl StillNonLocal for &str {}
391+
392+
fn only_global() {
393+
struct Foo;
394+
impl StillNonLocal for &Foo {}
395+
//~^ WARN non-local `impl` definition
396+
}
397+
398+
struct GlobalSameFunction;
399+
400+
fn same_function() {
401+
struct Local1(GlobalSameFunction);
402+
impl From<Local1> for GlobalSameFunction {
403+
//~^ WARN non-local `impl` definition
404+
fn from(x: Local1) -> GlobalSameFunction {
405+
x.0
406+
}
407+
}
408+
409+
struct Local2(GlobalSameFunction);
410+
impl From<Local2> for GlobalSameFunction {
411+
//~^ WARN non-local `impl` definition
412+
fn from(x: Local2) -> GlobalSameFunction {
413+
x.0
414+
}
415+
}
416+
}
417+
418+
struct GlobalDifferentFunction;
419+
420+
fn diff_foo() {
421+
struct Local(GlobalDifferentFunction);
422+
423+
impl From<Local> for GlobalDifferentFunction {
424+
// FIXME(Urgau): Should warn but doesn't since we currently consider
425+
// the other impl to be "global", but that's not the case for the type-system
426+
fn from(x: Local) -> GlobalDifferentFunction {
427+
x.0
428+
}
429+
}
430+
}
431+
432+
fn diff_bar() {
433+
struct Local(GlobalDifferentFunction);
434+
435+
impl From<Local> for GlobalDifferentFunction {
436+
// FIXME(Urgau): Should warn but doesn't since we currently consider
437+
// the other impl to be "global", but that's not the case for the type-system
438+
fn from(x: Local) -> GlobalDifferentFunction {
439+
x.0
440+
}
441+
}
377442
}
378443

379444
macro_rules! m {
@@ -404,3 +469,24 @@ fn bitflags() {
404469
impl Flags {}
405470
};
406471
}
472+
473+
// https://github.com/rust-lang/rust/issues/121621#issuecomment-1976826895
474+
fn commonly_reported() {
475+
struct Local(u8);
476+
impl From<Local> for u8 {
477+
fn from(x: Local) -> u8 {
478+
x.0
479+
}
480+
}
481+
}
482+
483+
// https://github.com/rust-lang/rust/issues/121621#issue-2153187542
484+
pub trait Serde {}
485+
486+
impl Serde for &[u8] {}
487+
impl Serde for &str {}
488+
489+
fn serde() {
490+
struct Thing;
491+
impl Serde for &Thing {}
492+
}

0 commit comments

Comments
 (0)