Skip to content

Commit 9d79cd5

Browse files
committed
Auto merge of #122747 - Urgau:non-local-defs_perfect_impl, r=lcnr
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). Fixes #121621 Fixes #121746 r? `@lcnr` *(feel free to re-roll)*
2 parents ea40fa2 + 2f2d5cc commit 9d79cd5

File tree

7 files changed

+440
-173
lines changed

7 files changed

+440
-173
lines changed

compiler/rustc_lint/messages.ftl

+1-1
Original file line numberDiff line numberDiff line change
@@ -443,7 +443,7 @@ lint_non_local_definitions_impl = non-local `impl` definition, they should be av
443443
[one] `{$body_name}`
444444
*[other] `{$body_name}` and up {$depth} bodies
445445
}
446-
.non_local = an `impl` definition is non-local if it is nested inside an item and neither the type nor the trait are at the same nesting level as the `impl` block
446+
.non_local = an `impl` definition is non-local if it is nested inside an item and may impact type checking outside of that item. This can be the case if neither the trait or the self type are at the same nesting level as the `impl`
447447
.exception = one exception to the rule are anon-const (`const _: () = {"{"} ... {"}"}`) at top-level module and anon-const at the same nesting as the trait or type
448448
.const_anon = use a const-anon item to suppress this lint
449449

compiler/rustc_lint/src/non_local_def.rs

+169-38
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};
@@ -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,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
}

compiler/rustc_trait_selection/src/traits/error_reporting/ambiguity.rs

+18-6
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-
pub enum Ambiguity {
10+
#[derive(Debug)]
11+
pub enum CandidateSource {
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>,
18-
) -> Vec<Ambiguity> {
19+
) -> Vec<CandidateSource> {
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,15 @@ 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+
let impl_polarity = impl_trait_header.polarity;
48+
49+
match (impl_polarity, predicate_polarity) {
50+
(ty::ImplPolarity::Positive, ty::PredicatePolarity::Positive)
51+
| (ty::ImplPolarity::Negative, ty::PredicatePolarity::Negative) => {}
52+
_ => return false,
53+
}
54+
4355
let impl_predicates = tcx.predicates_of(impl_def_id).instantiate(tcx, impl_args);
4456
ocx.register_obligations(impl_predicates.predicates.iter().map(|&predicate| {
4557
Obligation::new(tcx, ObligationCause::dummy(), param_env, predicate)
@@ -86,7 +98,7 @@ pub fn recompute_applicable_impls<'tcx>(
8698
obligation.predicate.skip_binder().trait_ref.self_ty(),
8799
|impl_def_id| {
88100
if infcx.probe(|_| impl_may_apply(impl_def_id)) {
89-
ambiguities.push(Ambiguity::DefId(impl_def_id))
101+
ambiguities.push(CandidateSource::DefId(impl_def_id))
90102
}
91103
},
92104
);
@@ -101,9 +113,9 @@ pub fn recompute_applicable_impls<'tcx>(
101113
if kind.rebind(trait_pred.trait_ref)
102114
== ty::Binder::dummy(ty::TraitRef::identity(tcx, trait_pred.def_id()))
103115
{
104-
ambiguities.push(Ambiguity::ParamEnv(tcx.def_span(trait_pred.def_id())))
116+
ambiguities.push(CandidateSource::ParamEnv(tcx.def_span(trait_pred.def_id())))
105117
} else {
106-
ambiguities.push(Ambiguity::ParamEnv(span))
118+
ambiguities.push(CandidateSource::ParamEnv(span))
107119
}
108120
}
109121
}

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;

compiler/rustc_trait_selection/src/traits/error_reporting/type_err_ctxt_ext.rs

+5-5
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use crate::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
1010
use crate::infer::InferCtxtExt as _;
1111
use crate::infer::{self, InferCtxt};
1212
use crate::traits::error_reporting::infer_ctxt_ext::InferCtxtExt;
13-
use crate::traits::error_reporting::{ambiguity, ambiguity::Ambiguity::*};
13+
use crate::traits::error_reporting::{ambiguity, ambiguity::CandidateSource::*};
1414
use crate::traits::query::evaluate_obligation::InferCtxtExt as _;
1515
use crate::traits::specialize::to_pretty_impl_header;
1616
use crate::traits::NormalizeExt;
@@ -2386,7 +2386,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
23862386
)
23872387
};
23882388

2389-
let mut ambiguities = ambiguity::recompute_applicable_impls(
2389+
let mut ambiguities = ambiguity::compute_applicable_impls_for_diagnostics(
23902390
self.infcx,
23912391
&obligation.with(self.tcx, trait_ref),
23922392
);
@@ -2702,7 +2702,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
27022702
fn annotate_source_of_ambiguity(
27032703
&self,
27042704
err: &mut Diag<'_>,
2705-
ambiguities: &[ambiguity::Ambiguity],
2705+
ambiguities: &[ambiguity::CandidateSource],
27062706
predicate: ty::Predicate<'tcx>,
27072707
) {
27082708
let mut spans = vec![];
@@ -2711,7 +2711,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
27112711
let mut has_param_env = false;
27122712
for ambiguity in ambiguities {
27132713
match ambiguity {
2714-
ambiguity::Ambiguity::DefId(impl_def_id) => {
2714+
ambiguity::CandidateSource::DefId(impl_def_id) => {
27152715
match self.tcx.span_of_impl(*impl_def_id) {
27162716
Ok(span) => spans.push(span),
27172717
Err(name) => {
@@ -2722,7 +2722,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
27222722
}
27232723
}
27242724
}
2725-
ambiguity::Ambiguity::ParamEnv(span) => {
2725+
ambiguity::CandidateSource::ParamEnv(span) => {
27262726
has_param_env = true;
27272727
spans.push(*span);
27282728
}

0 commit comments

Comments
 (0)