Skip to content

Commit c5a16cd

Browse files
committed
Auto merge of #122747 - Urgau:non-local-defs_perfect_impl, r=<try>
Implement T-types suggested logic for perfect non-local impl detection This implement [T-types suggested logic](#121621 (comment)) for perfect non-local impl detection: > for each impl, instantiate all local types with inference vars and then assemble candidates for that goal, if there are more than 1 (non-private impls), it does not leak This extension to the current logic is meant to address issues reported in #121621. This PR also re-enables the lint `non_local_definitions` to warn-by-default. Implementation was discussed in this [zulip thread](https://rust-lang.zulipchat.com/#narrow/stream/144729-t-types/topic/Implementing.20new.20non-local.20impl.20defs.20logic). r? `@lcnr` *(feel free to re-roll)*
2 parents 47dd709 + eed8e95 commit c5a16cd

File tree

6 files changed

+306
-50
lines changed

6 files changed

+306
-50
lines changed

Diff for: compiler/rustc_lint/src/non_local_def.rs

+125-12
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};
@@ -35,7 +47,7 @@ declare_lint! {
3547
/// All nested bodies (functions, enum discriminant, array length, consts) (expect for
3648
/// `const _: Ty = { ... }` in top-level module, which is still undecided) are checked.
3749
pub NON_LOCAL_DEFINITIONS,
38-
Allow,
50+
Warn,
3951
"checks for non-local definitions",
4052
report_in_external_macro
4153
}
@@ -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
}

Diff for: compiler/rustc_trait_selection/src/traits/error_reporting/ambiguity.rs

+19-1
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,21 @@ use rustc_span::{Span, DUMMY_SP};
77

88
use crate::traits::ObligationCtxt;
99

10+
#[derive(Debug)]
1011
pub enum Ambiguity {
1112
DefId(DefId),
1213
ParamEnv(Span),
1314
}
1415

15-
pub fn recompute_applicable_impls<'tcx>(
16+
pub fn compute_applicable_impls_for_diagnostics<'tcx>(
1617
infcx: &InferCtxt<'tcx>,
1718
obligation: &PolyTraitObligation<'tcx>,
1819
) -> Vec<Ambiguity> {
1920
let tcx = infcx.tcx;
2021
let param_env = obligation.param_env;
2122

23+
let predicate_polarity = obligation.predicate.skip_binder().polarity;
24+
2225
let impl_may_apply = |impl_def_id| {
2326
let ocx = ObligationCtxt::new(infcx);
2427
infcx.enter_forall(obligation.predicate, |placeholder_obligation| {
@@ -40,6 +43,21 @@ pub fn recompute_applicable_impls<'tcx>(
4043
return false;
4144
}
4245

46+
let impl_trait_header = tcx.impl_trait_header(impl_def_id).unwrap();
47+
48+
let impl_polarity = impl_trait_header.polarity;
49+
match impl_polarity {
50+
ty::ImplPolarity::Positive | ty::ImplPolarity::Negative => {
51+
match impl_polarity == predicate_polarity {
52+
true => {}
53+
false => return false,
54+
}
55+
}
56+
ty::ImplPolarity::Reservation => {
57+
return false;
58+
}
59+
}
60+
4361
let impl_predicates = tcx.predicates_of(impl_def_id).instantiate(tcx, impl_args);
4462
ocx.register_obligations(impl_predicates.predicates.iter().map(|&predicate| {
4563
Obligation::new(tcx, ObligationCause::dummy(), param_env, predicate)

Diff for: compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// ignore-tidy-filelength :(
22

3-
mod ambiguity;
3+
pub mod ambiguity;
44
mod infer_ctxt_ext;
55
pub mod on_unimplemented;
66
pub mod suggestions;

Diff for: compiler/rustc_trait_selection/src/traits/error_reporting/type_err_ctxt_ext.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -2382,7 +2382,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
23822382
)
23832383
};
23842384

2385-
let mut ambiguities = ambiguity::recompute_applicable_impls(
2385+
let mut ambiguities = ambiguity::compute_applicable_impls_for_diagnostics(
23862386
self.infcx,
23872387
&obligation.with(self.tcx, trait_ref),
23882388
);

Diff for: tests/ui/lint/non_local_definitions.rs

+88-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
//@ rustc-env:CARGO_CRATE_NAME=non_local_def
55

66
#![feature(inline_const)]
7-
#![warn(non_local_definitions)]
7+
88

99
extern crate non_local_macro;
1010

@@ -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)