Skip to content

Commit a1d7bff

Browse files
committed
Eliminate false-positives in the non-local lint with the type-system
1 parent 6173240 commit a1d7bff

File tree

3 files changed

+326
-66
lines changed

3 files changed

+326
-66
lines changed

compiler/rustc_lint/src/non_local_def.rs

+168-37
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,8 @@ 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+
let parent = cx.tcx.parent(def_id);
7083
let parent_def_kind = cx.tcx.def_kind(parent);
7184
let parent_opt_item_name = cx.tcx.opt_item_name(parent);
7285

@@ -121,6 +134,7 @@ impl<'tcx> LateLintPass<'tcx> for NonLocalDefinitions {
121134
None
122135
};
123136

137+
// Part 1: Is the Self type local?
124138
let self_ty_has_local_parent = match impl_.self_ty.kind {
125139
TyKind::Path(QPath::Resolved(_, ty_path)) => {
126140
path_has_local_parent(ty_path, cx, parent, parent_parent)
@@ -150,41 +164,70 @@ impl<'tcx> LateLintPass<'tcx> for NonLocalDefinitions {
150164
| TyKind::Err(_) => false,
151165
};
152166

167+
if self_ty_has_local_parent {
168+
return;
169+
}
170+
171+
// Part 2: Is the Trait local?
153172
let of_trait_has_local_parent = impl_
154173
.of_trait
155174
.map(|of_trait| path_has_local_parent(of_trait.path, cx, parent, parent_parent))
156175
.unwrap_or(false);
157176

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-
)
177+
if of_trait_has_local_parent {
178+
return;
179+
}
180+
181+
// Part 3: Is the impl definition leaking outside it's defining scope?
182+
//
183+
// We always consider inherent impls to be leaking.
184+
let impl_has_enough_non_local_candidates = cx
185+
.tcx
186+
.impl_trait_ref(def_id)
187+
.map(|binder| {
188+
impl_trait_ref_has_enough_non_local_candidates(
189+
cx.tcx,
190+
item.span,
191+
def_id,
192+
binder,
193+
|did| did_has_local_parent(did, cx.tcx, parent, parent_parent),
194+
)
195+
})
196+
.unwrap_or(false);
197+
198+
if impl_has_enough_non_local_candidates {
199+
return;
187200
}
201+
202+
// Get the span of the parent const item ident (if it's a not a const anon).
203+
//
204+
// Used to suggest changing the const item to a const anon.
205+
let span_for_const_anon_suggestion = if self.body_depth == 1
206+
&& parent_def_kind == DefKind::Const
207+
&& parent_opt_item_name != Some(kw::Underscore)
208+
&& let Some(parent) = parent.as_local()
209+
&& let Node::Item(item) = cx.tcx.hir_node_by_def_id(parent)
210+
&& let ItemKind::Const(ty, _, _) = item.kind
211+
&& let TyKind::Tup(&[]) = ty.kind
212+
{
213+
Some(item.ident.span)
214+
} else {
215+
None
216+
};
217+
218+
cx.emit_span_lint(
219+
NON_LOCAL_DEFINITIONS,
220+
item.span,
221+
NonLocalDefinitionsDiag::Impl {
222+
depth: self.body_depth,
223+
body_kind_descr: cx.tcx.def_kind_descr(parent_def_kind, parent),
224+
body_name: parent_opt_item_name
225+
.map(|s| s.to_ident_string())
226+
.unwrap_or_else(|| "<unnameable>".to_string()),
227+
cargo_update: cargo_update(),
228+
const_anon: span_for_const_anon_suggestion,
229+
},
230+
)
188231
}
189232
ItemKind::Macro(_macro, MacroKind::Bang)
190233
if cx.tcx.has_attr(item.owner_id.def_id, sym::macro_export) =>
@@ -207,6 +250,81 @@ impl<'tcx> LateLintPass<'tcx> for NonLocalDefinitions {
207250
}
208251
}
209252

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

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)