Skip to content

Commit 8df9081

Browse files
committed
Eliminate false-positives with the type-system
1 parent 53a7780 commit 8df9081

File tree

3 files changed

+322
-66
lines changed

3 files changed

+322
-66
lines changed

compiler/rustc_lint/src/non_local_def.rs

Lines changed: 164 additions & 37 deletions
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::{self, Binder, Ty, TyCtxt, TypeFoldable, TypeFolder};
8+
use rustc_middle::ty::{EarlyBinder, TraitRef, TypeSuperFoldable};
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, CandidateSource,
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

@@ -121,6 +135,7 @@ impl<'tcx> LateLintPass<'tcx> for NonLocalDefinitions {
121135
None
122136
};
123137

138+
// Part 1: Is the Self type local?
124139
let self_ty_has_local_parent = match impl_.self_ty.kind {
125140
TyKind::Path(QPath::Resolved(_, ty_path)) => {
126141
path_has_local_parent(ty_path, cx, parent, parent_parent)
@@ -150,41 +165,65 @@ impl<'tcx> LateLintPass<'tcx> for NonLocalDefinitions {
150165
| TyKind::Err(_) => false,
151166
};
152167

168+
if self_ty_has_local_parent {
169+
return;
170+
}
171+
172+
// Part 2: Is the Trait local?
153173
let of_trait_has_local_parent = impl_
154174
.of_trait
155175
.map(|of_trait| path_has_local_parent(of_trait.path, cx, parent, parent_parent))
156176
.unwrap_or(false);
157177

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) {
161-
let const_anon = if self.body_depth == 1
162-
&& parent_def_kind == DefKind::Const
163-
&& parent_opt_item_name != Some(kw::Underscore)
164-
&& let Some(parent) = parent.as_local()
165-
&& let Node::Item(item) = cx.tcx.hir_node_by_def_id(parent)
166-
&& let ItemKind::Const(ty, _, _) = item.kind
167-
&& let TyKind::Tup(&[]) = ty.kind
168-
{
169-
Some(item.ident.span)
170-
} else {
171-
None
172-
};
173-
174-
cx.emit_span_lint(
175-
NON_LOCAL_DEFINITIONS,
176-
item.span,
177-
NonLocalDefinitionsDiag::Impl {
178-
depth: self.body_depth,
179-
body_kind_descr: cx.tcx.def_kind_descr(parent_def_kind, parent),
180-
body_name: parent_opt_item_name
181-
.map(|s| s.to_ident_string())
182-
.unwrap_or_else(|| "<unnameable>".to_string()),
183-
cargo_update: cargo_update(),
184-
const_anon,
185-
},
186-
)
178+
if of_trait_has_local_parent {
179+
return;
180+
}
181+
182+
// Part 3: Is the impl definition leaking outside it defining scope?
183+
let impl_has_enough_non_local_candidates = cx
184+
.tcx
185+
.impl_trait_ref(def_id)
186+
.map(|binder| {
187+
impl_trait_ref_has_enough_non_local_candidates(
188+
cx.tcx,
189+
item.span,
190+
def_id,
191+
binder,
192+
|did| did_has_local_parent(did, cx.tcx, parent, parent_parent),
193+
)
194+
})
195+
.unwrap_or(false);
196+
197+
if impl_has_enough_non_local_candidates {
198+
return;
187199
}
200+
201+
let const_anon = if self.body_depth == 1
202+
&& parent_def_kind == DefKind::Const
203+
&& parent_opt_item_name != Some(kw::Underscore)
204+
&& let Some(parent) = parent.as_local()
205+
&& let Node::Item(item) = cx.tcx.hir_node_by_def_id(parent)
206+
&& let ItemKind::Const(ty, _, _) = item.kind
207+
&& let TyKind::Tup(&[]) = ty.kind
208+
{
209+
Some(item.ident.span)
210+
} else {
211+
None
212+
};
213+
214+
cx.emit_span_lint(
215+
NON_LOCAL_DEFINITIONS,
216+
item.span,
217+
NonLocalDefinitionsDiag::Impl {
218+
depth: self.body_depth,
219+
body_kind_descr: cx.tcx.def_kind_descr(parent_def_kind, parent),
220+
body_name: parent_opt_item_name
221+
.map(|s| s.to_ident_string())
222+
.unwrap_or_else(|| "<unnameable>".to_string()),
223+
cargo_update: cargo_update(),
224+
const_anon,
225+
},
226+
)
188227
}
189228
ItemKind::Macro(_macro, MacroKind::Bang)
190229
if cx.tcx.has_attr(item.owner_id.def_id, sym::macro_export) =>
@@ -207,6 +246,81 @@ impl<'tcx> LateLintPass<'tcx> for NonLocalDefinitions {
207246
}
208247
}
209248

249+
// Detecting if the impl definition is leaking outside of it's defining scope.
250+
//
251+
// Rule: for each impl, instantiate all local types with inference vars and
252+
// then assemble candidates for that goal, if there are more than 1 (non-private
253+
// impls), it does not leak.
254+
//
255+
// https://github.com/rust-lang/rust/issues/121621#issuecomment-1976826895
256+
fn impl_trait_ref_has_enough_non_local_candidates<'tcx>(
257+
tcx: TyCtxt<'tcx>,
258+
infer_span: Span,
259+
trait_def_id: DefId,
260+
binder: EarlyBinder<TraitRef<'tcx>>,
261+
mut did_has_local_parent: impl FnMut(DefId) -> bool,
262+
) -> bool {
263+
let infcx = tcx.infer_ctxt().build();
264+
let trait_ref = binder.instantiate(tcx, infcx.fresh_args_for_item(infer_span, trait_def_id));
265+
266+
let trait_ref = trait_ref.fold_with(&mut ReplaceLocalTypesWithInfer {
267+
infcx: &infcx,
268+
infer_span,
269+
did_has_local_parent: &mut did_has_local_parent,
270+
});
271+
272+
let poly_trait_obligation = Obligation::new(
273+
tcx,
274+
ObligationCause::dummy(),
275+
ty::ParamEnv::empty(),
276+
Binder::dummy(trait_ref),
277+
);
278+
279+
let ambiguities = compute_applicable_impls_for_diagnostics(&infcx, &poly_trait_obligation);
280+
281+
let mut it = ambiguities.iter().filter(|ambi| match ambi {
282+
CandidateSource::DefId(did) => !did_has_local_parent(*did),
283+
CandidateSource::ParamEnv(_) => unreachable!(),
284+
});
285+
286+
let _ = it.next();
287+
it.next().is_some()
288+
}
289+
290+
/// Replace every local type by inference variable.
291+
///
292+
/// ```text
293+
/// <Global<Local> as std::cmp::PartialEq<Global<Local>>>
294+
/// to
295+
/// <Global<_> as std::cmp::PartialEq<Global<_>>>
296+
/// ```
297+
struct ReplaceLocalTypesWithInfer<'a, 'tcx, F: FnMut(DefId) -> bool> {
298+
infcx: &'a InferCtxt<'tcx>,
299+
did_has_local_parent: F,
300+
infer_span: Span,
301+
}
302+
303+
impl<'a, 'tcx, F: FnMut(DefId) -> bool> TypeFolder<TyCtxt<'tcx>>
304+
for ReplaceLocalTypesWithInfer<'a, 'tcx, F>
305+
{
306+
fn interner(&self) -> TyCtxt<'tcx> {
307+
self.infcx.tcx
308+
}
309+
310+
fn fold_ty(&mut self, t: Ty<'tcx>) -> Ty<'tcx> {
311+
if let Some(ty_did) = t.ty_def_id()
312+
&& (self.did_has_local_parent)(ty_did)
313+
{
314+
self.infcx.next_ty_var(TypeVariableOrigin {
315+
kind: TypeVariableOriginKind::TypeInference,
316+
span: self.infer_span,
317+
})
318+
} else {
319+
t.super_fold_with(self)
320+
}
321+
}
322+
}
323+
210324
/// Given a path and a parent impl def id, this checks if the if parent resolution
211325
/// def id correspond to the def id of the parent impl definition.
212326
///
@@ -216,16 +330,29 @@ impl<'tcx> LateLintPass<'tcx> for NonLocalDefinitions {
216330
/// std::convert::PartialEq<Foo<Bar>>
217331
/// ^^^^^^^^^^^^^^^^^^^^^^^
218332
/// ```
333+
#[inline]
219334
fn path_has_local_parent(
220335
path: &Path<'_>,
221336
cx: &LateContext<'_>,
222337
impl_parent: DefId,
223338
impl_parent_parent: Option<DefId>,
224339
) -> 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-
})
340+
path.res
341+
.opt_def_id()
342+
.is_some_and(|did| did_has_local_parent(did, cx.tcx, impl_parent, impl_parent_parent))
343+
}
344+
345+
/// Given a def id and a parent impl def id, this checks if the parent
346+
/// def id correspond to the def id of the parent impl definition.
347+
#[inline]
348+
fn did_has_local_parent(
349+
did: DefId,
350+
tcx: TyCtxt<'_>,
351+
impl_parent: DefId,
352+
impl_parent_parent: Option<DefId>,
353+
) -> bool {
354+
did.is_local() && {
355+
let res_parent = tcx.parent(did);
356+
res_parent == impl_parent || Some(res_parent) == impl_parent_parent
357+
}
231358
}

tests/ui/lint/non_local_definitions.rs

Lines changed: 87 additions & 1 deletion
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)