Skip to content

Commit ace9acc

Browse files
committed
Rework non_local_definitions lint to only be a syntactic heuristic
1 parent 2f4e22f commit ace9acc

13 files changed

+35
-642
lines changed

compiler/rustc_lint/messages.ftl

-3
Original file line numberDiff line numberDiff line change
@@ -575,7 +575,6 @@ lint_non_local_definitions_cargo_update = the {$macro_kind} `{$macro_name}` may
575575
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>
576576
577577
lint_non_local_definitions_impl = non-local `impl` definition, `impl` blocks should be written at the same level as their item
578-
.remove_help = remove `{$may_remove_part}` to make the `impl` local
579578
.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`
580579
.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`
581580
.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
@@ -600,8 +599,6 @@ lint_non_local_definitions_macro_rules = non-local `macro_rules!` definition, `#
600599
remove the `#[macro_export]` or make this doc-test a standalone test with its own `fn main() {"{"} ... {"}"}`
601600
.non_local = a `macro_rules!` definition is non-local if it is nested inside an item and has a `#[macro_export]` attribute
602601
603-
lint_non_local_definitions_may_move = may need to be moved as well
604-
605602
lint_non_local_definitions_of_trait_not_local = `{$of_trait_str}` is not local
606603
607604
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
@@ -1360,9 +1360,7 @@ pub enum NonLocalDefinitionsDiag {
13601360
body_name: String,
13611361
cargo_update: Option<NonLocalDefinitionsCargoUpdateNote>,
13621362
const_anon: Option<Option<Span>>,
1363-
move_to: Option<(Span, Vec<Span>)>,
13641363
doctest: bool,
1365-
may_remove: Option<(Span, String)>,
13661364
has_trait: bool,
13671365
self_ty_str: String,
13681366
of_trait_str: Option<String>,
@@ -1386,9 +1384,7 @@ impl<'a> LintDiagnostic<'a, ()> for NonLocalDefinitionsDiag {
13861384
body_name,
13871385
cargo_update,
13881386
const_anon,
1389-
move_to,
13901387
doctest,
1391-
may_remove,
13921388
has_trait,
13931389
self_ty_str,
13941390
of_trait_str,
@@ -1419,27 +1415,10 @@ impl<'a> LintDiagnostic<'a, ()> for NonLocalDefinitionsDiag {
14191415
diag.note(fluent::lint_without_trait);
14201416
}
14211417

1422-
if let Some((move_help, may_move)) = move_to {
1423-
let mut ms = MultiSpan::from_span(move_help);
1424-
for sp in may_move {
1425-
ms.push_span_label(sp, fluent::lint_non_local_definitions_may_move);
1426-
}
1427-
diag.span_help(ms, fluent::lint_non_local_definitions_impl_move_help);
1428-
}
14291418
if doctest {
14301419
diag.help(fluent::lint_doctest);
14311420
}
14321421

1433-
if let Some((span, part)) = may_remove {
1434-
diag.arg("may_remove_part", part);
1435-
diag.span_suggestion(
1436-
span,
1437-
fluent::lint_remove_help,
1438-
"",
1439-
Applicability::MaybeIncorrect,
1440-
);
1441-
}
1442-
14431422
if let Some(const_anon) = const_anon {
14441423
diag.note(fluent::lint_exception);
14451424
if let Some(const_anon) = const_anon {

compiler/rustc_lint/src/non_local_def.rs

+23-214
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,11 @@ use rustc_errors::MultiSpan;
22
use rustc_hir::def::DefKind;
33
use rustc_hir::intravisit::{self, Visitor};
44
use rustc_hir::{Body, HirId, Item, ItemKind, Node, Path, QPath, TyKind};
5-
use rustc_infer::infer::InferCtxt;
6-
use rustc_infer::traits::{Obligation, ObligationCause};
7-
use rustc_middle::ty::{
8-
self, Binder, EarlyBinder, TraitRef, Ty, TyCtxt, TypeFoldable, TypeFolder, TypeSuperFoldable,
9-
};
5+
use rustc_middle::ty::TyCtxt;
106
use rustc_session::{declare_lint, impl_lint_pass};
117
use rustc_span::def_id::{DefId, LOCAL_CRATE};
128
use rustc_span::symbol::kw;
139
use rustc_span::{sym, ExpnKind, MacroKind, Span, Symbol};
14-
use rustc_trait_selection::error_reporting::traits::ambiguity::{
15-
compute_applicable_impls_for_diagnostics, CandidateSource,
16-
};
17-
use rustc_trait_selection::infer::TyCtxtInferExt;
1810

1911
use crate::lints::{NonLocalDefinitionsCargoUpdateNote, NonLocalDefinitionsDiag};
2012
use crate::{fluent_generated as fluent, LateContext, LateLintPass, LintContext};
@@ -142,42 +134,22 @@ impl<'tcx> LateLintPass<'tcx> for NonLocalDefinitions {
142134
None
143135
};
144136

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

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

@@ -199,18 +171,6 @@ impl<'tcx> LateLintPass<'tcx> for NonLocalDefinitions {
199171
let const_anon = matches!(parent_def_kind, DefKind::Const | DefKind::Static { .. })
200172
.then_some(span_for_const_anon_suggestion);
201173

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

@@ -221,6 +181,7 @@ impl<'tcx> LateLintPass<'tcx> for NonLocalDefinitions {
221181
self_ty_span,
222182
fluent::lint_non_local_definitions_self_ty_not_local,
223183
);
184+
224185
let of_trait_str = if let Some(of_trait) = &impl_.of_trait {
225186
ms.push_span_label(
226187
path_span_without_args(&of_trait.path),
@@ -231,44 +192,14 @@ impl<'tcx> LateLintPass<'tcx> for NonLocalDefinitions {
231192
None
232193
};
233194

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

270-
(false, move_to)
271-
};
197+
if !doctest {
198+
ms.push_span_label(
199+
cx.tcx.def_span(parent),
200+
fluent::lint_non_local_definitions_impl_move_help,
201+
);
202+
}
272203

273204
let macro_to_change =
274205
if let ExpnKind::Macro(kind, name) = item.span.ctxt().outer_expn_data().kind {
@@ -290,9 +221,7 @@ impl<'tcx> LateLintPass<'tcx> for NonLocalDefinitions {
290221
const_anon,
291222
self_ty_str,
292223
of_trait_str,
293-
move_to,
294224
doctest,
295-
may_remove,
296225
has_trait: impl_.of_trait.is_some(),
297226
macro_to_change,
298227
},
@@ -320,90 +249,6 @@ impl<'tcx> LateLintPass<'tcx> for NonLocalDefinitions {
320249
}
321250
}
322251

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

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

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)