Skip to content

Commit 8a584d6

Browse files
committed
Rework non_local_definitions lint to only be a syntactic heuristic
1 parent f15e95d commit 8a584d6

13 files changed

+35
-641
lines changed

compiler/rustc_lint/messages.ftl

-3
Original file line numberDiff line numberDiff line change
@@ -545,7 +545,6 @@ lint_non_local_definitions_cargo_update = the {$macro_kind} `{$macro_name}` may
545545
lint_non_local_definitions_deprecation = this lint may become deny-by-default in the edition 2024 and higher, see the tracking issue <https://github.com/rust-lang/rust/issues/120363>
546546
547547
lint_non_local_definitions_impl = non-local `impl` definition, `impl` blocks should be written at the same level as their item
548-
.remove_help = remove `{$may_remove_part}` to make the `impl` local
549548
.without_trait = methods and associated constants are still usable outside the current expression, only `impl Local` and `impl dyn Local` can ever be private, and only if the type is nested in the same item as the `impl`
550549
.with_trait = an `impl` is never scoped, even when it is nested inside an item, as it may impact type checking outside of that item, which can be the case if neither the trait or the self type are at the same nesting level as the `impl`
551550
.bounds = `impl` may be usable in bounds, etc. from outside the expression, which might e.g. make something constructible that previously wasn't, because it's still on a publicly-visible type
@@ -570,8 +569,6 @@ lint_non_local_definitions_macro_rules = non-local `macro_rules!` definition, `#
570569
remove the `#[macro_export]` or make this doc-test a standalone test with its own `fn main() {"{"} ... {"}"}`
571570
.non_local = a `macro_rules!` definition is non-local if it is nested inside an item and has a `#[macro_export]` attribute
572571
573-
lint_non_local_definitions_may_move = may need to be moved as well
574-
575572
lint_non_local_definitions_of_trait_not_local = `{$of_trait_str}` is not local
576573
577574
lint_non_local_definitions_self_ty_not_local = `{$self_ty_str}` is not local

compiler/rustc_lint/src/lints.rs

-21
Original file line numberDiff line numberDiff line change
@@ -1357,9 +1357,7 @@ pub enum NonLocalDefinitionsDiag {
13571357
body_name: String,
13581358
cargo_update: Option<NonLocalDefinitionsCargoUpdateNote>,
13591359
const_anon: Option<Option<Span>>,
1360-
move_to: Option<(Span, Vec<Span>)>,
13611360
doctest: bool,
1362-
may_remove: Option<(Span, String)>,
13631361
has_trait: bool,
13641362
self_ty_str: String,
13651363
of_trait_str: Option<String>,
@@ -1383,9 +1381,7 @@ impl<'a> LintDiagnostic<'a, ()> for NonLocalDefinitionsDiag {
13831381
body_name,
13841382
cargo_update,
13851383
const_anon,
1386-
move_to,
13871384
doctest,
1388-
may_remove,
13891385
has_trait,
13901386
self_ty_str,
13911387
of_trait_str,
@@ -1416,27 +1412,10 @@ impl<'a> LintDiagnostic<'a, ()> for NonLocalDefinitionsDiag {
14161412
diag.note(fluent::lint_without_trait);
14171413
}
14181414

1419-
if let Some((move_help, may_move)) = move_to {
1420-
let mut ms = MultiSpan::from_span(move_help);
1421-
for sp in may_move {
1422-
ms.push_span_label(sp, fluent::lint_non_local_definitions_may_move);
1423-
}
1424-
diag.span_help(ms, fluent::lint_non_local_definitions_impl_move_help);
1425-
}
14261415
if doctest {
14271416
diag.help(fluent::lint_doctest);
14281417
}
14291418

1430-
if let Some((span, part)) = may_remove {
1431-
diag.arg("may_remove_part", part);
1432-
diag.span_suggestion(
1433-
span,
1434-
fluent::lint_remove_help,
1435-
"",
1436-
Applicability::MaybeIncorrect,
1437-
);
1438-
}
1439-
14401419
if let Some(const_anon) = const_anon {
14411420
diag.note(fluent::lint_exception);
14421421
if let Some(const_anon) = const_anon {

compiler/rustc_lint/src/non_local_def.rs

+23-213
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,11 @@ use rustc_hir::intravisit::{self, Visitor};
33
use rustc_hir::HirId;
44
use rustc_hir::{def::DefKind, Body, Item, ItemKind, Node, TyKind};
55
use rustc_hir::{Path, QPath};
6-
use rustc_infer::infer::InferCtxt;
7-
use rustc_infer::traits::{Obligation, ObligationCause};
8-
use rustc_middle::ty::{self, Binder, Ty, TyCtxt, TypeFoldable, TypeFolder};
9-
use rustc_middle::ty::{EarlyBinder, TraitRef, TypeSuperFoldable};
6+
use rustc_middle::ty::TyCtxt;
107
use rustc_session::{declare_lint, impl_lint_pass};
118
use rustc_span::def_id::{DefId, LOCAL_CRATE};
129
use rustc_span::Span;
1310
use rustc_span::{sym, symbol::kw, ExpnKind, MacroKind, Symbol};
14-
use rustc_trait_selection::infer::TyCtxtInferExt;
15-
use rustc_trait_selection::traits::error_reporting::ambiguity::{
16-
compute_applicable_impls_for_diagnostics, CandidateSource,
17-
};
1811

1912
use crate::fluent_generated as fluent;
2013
use crate::lints::{NonLocalDefinitionsCargoUpdateNote, NonLocalDefinitionsDiag};
@@ -143,42 +136,22 @@ impl<'tcx> LateLintPass<'tcx> for NonLocalDefinitions {
143136
None
144137
};
145138

146-
// Part 1: Is the Self type local?
147-
let self_ty_has_local_parent =
148-
ty_has_local_parent(&impl_.self_ty.kind, cx, parent, parent_parent);
149-
150-
if self_ty_has_local_parent {
151-
return;
152-
}
153-
154-
// Part 2: Is the Trait local?
155-
let of_trait_has_local_parent = impl_
156-
.of_trait
157-
.map(|of_trait| path_has_local_parent(of_trait.path, cx, parent, parent_parent))
158-
.unwrap_or(false);
159-
160-
if of_trait_has_local_parent {
161-
return;
139+
// 1. We collect all the `hir::Path` from the `Self` type and `Trait` ref
140+
// of the `impl` definition
141+
let mut collector = PathCollector { paths: Vec::new() };
142+
collector.visit_ty(&impl_.self_ty);
143+
if let Some(of_trait) = &impl_.of_trait {
144+
collector.visit_trait_ref(of_trait);
162145
}
163146

164-
// Part 3: Is the impl definition leaking outside it's defining scope?
165-
//
166-
// We always consider inherent impls to be leaking.
167-
let impl_has_enough_non_local_candidates = cx
168-
.tcx
169-
.impl_trait_ref(def_id)
170-
.map(|binder| {
171-
impl_trait_ref_has_enough_non_local_candidates(
172-
cx.tcx,
173-
item.span,
174-
def_id,
175-
binder,
176-
|did| did_has_local_parent(did, cx.tcx, parent, parent_parent),
177-
)
178-
})
179-
.unwrap_or(false);
180-
181-
if impl_has_enough_non_local_candidates {
147+
// 2. We check if any of path reference a "local" parent and if that the case
148+
// we bail out as asked by T-lang, even trough this isn't correct from a
149+
// type-system point of view, as inference exists and could still leak the impl.
150+
if collector
151+
.paths
152+
.iter()
153+
.any(|path| path_has_local_parent(path, cx, parent, parent_parent))
154+
{
182155
return;
183156
}
184157

@@ -200,18 +173,6 @@ impl<'tcx> LateLintPass<'tcx> for NonLocalDefinitions {
200173
let const_anon = matches!(parent_def_kind, DefKind::Const | DefKind::Static { .. })
201174
.then_some(span_for_const_anon_suggestion);
202175

203-
let may_remove = match &impl_.self_ty.kind {
204-
TyKind::Ptr(mut_ty) | TyKind::Ref(_, mut_ty)
205-
if ty_has_local_parent(&mut_ty.ty.kind, cx, parent, parent_parent) =>
206-
{
207-
let type_ =
208-
if matches!(impl_.self_ty.kind, TyKind::Ptr(_)) { "*" } else { "&" };
209-
let part = format!("{}{}", type_, mut_ty.mutbl.prefix_str());
210-
Some((impl_.self_ty.span.shrink_to_lo().until(mut_ty.ty.span), part))
211-
}
212-
_ => None,
213-
};
214-
215176
let impl_span = item.span.shrink_to_lo().to(impl_.self_ty.span);
216177
let mut ms = MultiSpan::from_span(impl_span);
217178

@@ -222,6 +183,7 @@ impl<'tcx> LateLintPass<'tcx> for NonLocalDefinitions {
222183
self_ty_span,
223184
fluent::lint_non_local_definitions_self_ty_not_local,
224185
);
186+
225187
let of_trait_str = if let Some(of_trait) = &impl_.of_trait {
226188
ms.push_span_label(
227189
path_span_without_args(&of_trait.path),
@@ -232,44 +194,14 @@ impl<'tcx> LateLintPass<'tcx> for NonLocalDefinitions {
232194
None
233195
};
234196

235-
let (doctest, move_to) = if is_at_toplevel_doctest() {
236-
(true, None)
237-
} else {
238-
let mut collector = PathCollector { paths: Vec::new() };
239-
collector.visit_ty(&impl_.self_ty);
240-
if let Some(of_trait) = &impl_.of_trait {
241-
collector.visit_trait_ref(of_trait);
242-
}
243-
collector.visit_generics(&impl_.generics);
244-
245-
let mut may_move: Vec<Span> = collector
246-
.paths
247-
.into_iter()
248-
.filter_map(|path| {
249-
if let Some(did) = path.res.opt_def_id()
250-
&& did_has_local_parent(did, cx.tcx, parent, parent_parent)
251-
{
252-
Some(cx.tcx.def_span(did))
253-
} else {
254-
None
255-
}
256-
})
257-
.collect();
258-
may_move.sort();
259-
may_move.dedup();
260-
261-
let move_to = if may_move.is_empty() {
262-
ms.push_span_label(
263-
cx.tcx.def_span(parent),
264-
fluent::lint_non_local_definitions_impl_move_help,
265-
);
266-
None
267-
} else {
268-
Some((cx.tcx.def_span(parent), may_move))
269-
};
197+
let doctest = is_at_toplevel_doctest();
270198

271-
(false, move_to)
272-
};
199+
if !doctest {
200+
ms.push_span_label(
201+
cx.tcx.def_span(parent),
202+
fluent::lint_non_local_definitions_impl_move_help,
203+
);
204+
}
273205

274206
let macro_to_change =
275207
if let ExpnKind::Macro(kind, name) = item.span.ctxt().outer_expn_data().kind {
@@ -291,9 +223,7 @@ impl<'tcx> LateLintPass<'tcx> for NonLocalDefinitions {
291223
const_anon,
292224
self_ty_str,
293225
of_trait_str,
294-
move_to,
295226
doctest,
296-
may_remove,
297227
has_trait: impl_.of_trait.is_some(),
298228
macro_to_change,
299229
},
@@ -321,90 +251,6 @@ impl<'tcx> LateLintPass<'tcx> for NonLocalDefinitions {
321251
}
322252
}
323253

324-
// Detecting if the impl definition is leaking outside of its defining scope.
325-
//
326-
// Rule: for each impl, instantiate all local types with inference vars and
327-
// then assemble candidates for that goal, if there are more than 1 (non-private
328-
// impls), it does not leak.
329-
//
330-
// https://github.com/rust-lang/rust/issues/121621#issuecomment-1976826895
331-
fn impl_trait_ref_has_enough_non_local_candidates<'tcx>(
332-
tcx: TyCtxt<'tcx>,
333-
infer_span: Span,
334-
trait_def_id: DefId,
335-
binder: EarlyBinder<'tcx, TraitRef<'tcx>>,
336-
mut did_has_local_parent: impl FnMut(DefId) -> bool,
337-
) -> bool {
338-
let infcx = tcx
339-
.infer_ctxt()
340-
// We use the new trait solver since the obligation we are trying to
341-
// prove here may overflow and those are fatal in the old trait solver.
342-
// Which is unacceptable for a lint.
343-
//
344-
// Thanksfully the part we use here are very similar to the
345-
// new-trait-solver-as-coherence, which is in stabilization.
346-
//
347-
// https://github.com/rust-lang/rust/issues/123573
348-
.with_next_trait_solver(true)
349-
.build();
350-
351-
let trait_ref = binder.instantiate(tcx, infcx.fresh_args_for_item(infer_span, trait_def_id));
352-
353-
let trait_ref = trait_ref.fold_with(&mut ReplaceLocalTypesWithInfer {
354-
infcx: &infcx,
355-
infer_span,
356-
did_has_local_parent: &mut did_has_local_parent,
357-
});
358-
359-
let poly_trait_obligation = Obligation::new(
360-
tcx,
361-
ObligationCause::dummy(),
362-
ty::ParamEnv::empty(),
363-
Binder::dummy(trait_ref),
364-
);
365-
366-
let ambiguities = compute_applicable_impls_for_diagnostics(&infcx, &poly_trait_obligation);
367-
368-
let mut it = ambiguities.iter().filter(|ambi| match ambi {
369-
CandidateSource::DefId(did) => !did_has_local_parent(*did),
370-
CandidateSource::ParamEnv(_) => unreachable!(),
371-
});
372-
373-
let _ = it.next();
374-
it.next().is_some()
375-
}
376-
377-
/// Replace every local type by inference variable.
378-
///
379-
/// ```text
380-
/// <Global<Local> as std::cmp::PartialEq<Global<Local>>>
381-
/// to
382-
/// <Global<_> as std::cmp::PartialEq<Global<_>>>
383-
/// ```
384-
struct ReplaceLocalTypesWithInfer<'a, 'tcx, F: FnMut(DefId) -> bool> {
385-
infcx: &'a InferCtxt<'tcx>,
386-
did_has_local_parent: F,
387-
infer_span: Span,
388-
}
389-
390-
impl<'a, 'tcx, F: FnMut(DefId) -> bool> TypeFolder<TyCtxt<'tcx>>
391-
for ReplaceLocalTypesWithInfer<'a, 'tcx, F>
392-
{
393-
fn cx(&self) -> TyCtxt<'tcx> {
394-
self.infcx.tcx
395-
}
396-
397-
fn fold_ty(&mut self, t: Ty<'tcx>) -> Ty<'tcx> {
398-
if let Some(def) = t.ty_adt_def()
399-
&& (self.did_has_local_parent)(def.did())
400-
{
401-
self.infcx.next_ty_var(self.infer_span)
402-
} else {
403-
t.super_fold_with(self)
404-
}
405-
}
406-
}
407-
408254
/// Simple hir::Path collector
409255
struct PathCollector<'tcx> {
410256
paths: Vec<Path<'tcx>>,
@@ -417,42 +263,6 @@ impl<'tcx> Visitor<'tcx> for PathCollector<'tcx> {
417263
}
418264
}
419265

420-
/// Given a `Ty` we check if the (outermost) type is local.
421-
fn ty_has_local_parent(
422-
ty_kind: &TyKind<'_>,
423-
cx: &LateContext<'_>,
424-
impl_parent: DefId,
425-
impl_parent_parent: Option<DefId>,
426-
) -> bool {
427-
match ty_kind {
428-
TyKind::Path(QPath::Resolved(_, ty_path)) => {
429-
path_has_local_parent(ty_path, cx, impl_parent, impl_parent_parent)
430-
}
431-
TyKind::TraitObject([principle_poly_trait_ref, ..], _, _) => path_has_local_parent(
432-
principle_poly_trait_ref.trait_ref.path,
433-
cx,
434-
impl_parent,
435-
impl_parent_parent,
436-
),
437-
TyKind::TraitObject([], _, _)
438-
| TyKind::InferDelegation(_, _)
439-
| TyKind::Slice(_)
440-
| TyKind::Array(_, _)
441-
| TyKind::Ptr(_)
442-
| TyKind::Ref(_, _)
443-
| TyKind::BareFn(_)
444-
| TyKind::Never
445-
| TyKind::Tup(_)
446-
| TyKind::Path(_)
447-
| TyKind::Pat(..)
448-
| TyKind::AnonAdt(_)
449-
| TyKind::OpaqueDef(_, _, _)
450-
| TyKind::Typeof(_)
451-
| TyKind::Infer
452-
| TyKind::Err(_) => false,
453-
}
454-
}
455-
456266
/// Given a path and a parent impl def id, this checks if the if parent resolution
457267
/// def id correspond to the def id of the parent impl definition.
458268
///

tests/ui/lint/non-local-defs/exhaustive.rs

+1-6
Original file line numberDiff line numberDiff line change
@@ -55,18 +55,13 @@ fn main() {
5555

5656
struct InsideMain;
5757

58+
impl Trait for &InsideMain {}
5859
impl Trait for *mut InsideMain {}
59-
//~^ WARN non-local `impl` definition
6060
impl Trait for *mut [InsideMain] {}
61-
//~^ WARN non-local `impl` definition
6261
impl Trait for [InsideMain; 8] {}
63-
//~^ WARN non-local `impl` definition
6462
impl Trait for (InsideMain,) {}
65-
//~^ WARN non-local `impl` definition
6663
impl Trait for fn(InsideMain) -> () {}
67-
//~^ WARN non-local `impl` definition
6864
impl Trait for fn() -> InsideMain {}
69-
//~^ WARN non-local `impl` definition
7065

7166
fn inside_inside() {
7267
impl Display for InsideMain {

0 commit comments

Comments
 (0)