Skip to content

Commit 5560d46

Browse files
committed
Consider only the outermost const-anon in non_local_def lint
1 parent 0b16baa commit 5560d46

File tree

5 files changed

+164
-45
lines changed

5 files changed

+164
-45
lines changed

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

+67-42
Original file line numberDiff line numberDiff line change
@@ -124,16 +124,6 @@ impl<'tcx> LateLintPass<'tcx> for NonLocalDefinitions {
124124
// If that's the case this means that this impl block declaration
125125
// is using local items and so we don't lint on it.
126126

127-
// We also ignore anon-const in item by including the anon-const
128-
// parent as well.
129-
let parent_parent = if parent_def_kind == DefKind::Const
130-
&& parent_opt_item_name == Some(kw::Underscore)
131-
{
132-
Some(cx.tcx.parent(parent))
133-
} else {
134-
None
135-
};
136-
137127
// 1. We collect all the `hir::Path` from the `Self` type and `Trait` ref
138128
// of the `impl` definition
139129
let mut collector = PathCollector { paths: Vec::new() };
@@ -148,14 +138,31 @@ impl<'tcx> LateLintPass<'tcx> for NonLocalDefinitions {
148138
|p| matches!(p.res, Res::Def(def_kind, _) if def_kind != DefKind::TyParam),
149139
);
150140

151-
// 2. We check if any of path reference a "local" parent and if that the case
152-
// we bail out as asked by T-lang, even though this isn't correct from a
153-
// type-system point of view, as inference exists and could still leak the impl.
154-
if collector
155-
.paths
156-
.iter()
157-
.any(|path| path_has_local_parent(path, cx, parent, parent_parent))
158-
{
141+
// 1.9. We retrieve the parent def id of the impl item, ...
142+
//
143+
// ... modulo const-anons items, for enhanced compatibility with the ecosystem
144+
// as that pattern is common with `serde`, `bevy`, ...
145+
//
146+
// For this example we want the `DefId` parent of the outermost const-anon items.
147+
// ```
148+
// const _: () = { // the parent of this const-anon
149+
// const _: () = {
150+
// impl Foo {}
151+
// };
152+
// };
153+
// ```
154+
let impl_parent = peal_parent_while(cx.tcx, parent, |tcx, did| {
155+
tcx.def_kind(did) == DefKind::Const
156+
&& tcx.opt_item_name(did) == Some(kw::Underscore)
157+
})
158+
.unwrap_or(def_id);
159+
160+
// 2. We check if any of the paths reference a the `impl`-parent.
161+
//
162+
// If that the case we bail out, as was asked by T-lang, even though this isn't
163+
// correct from a type-system point of view, as inference exists and one-impl-rule
164+
// make its so that we could still leak the impl.
165+
if collector.paths.iter().any(|path| path_has_local_parent(path, cx, impl_parent)) {
159166
return;
160167
}
161168

@@ -263,36 +270,54 @@ impl<'tcx> Visitor<'tcx> for PathCollector<'tcx> {
263270
/// ^^^^^^^^^^^^^^^^^^^^^^^
264271
/// ```
265272
#[inline]
266-
fn path_has_local_parent(
267-
path: &Path<'_>,
268-
cx: &LateContext<'_>,
269-
impl_parent: DefId,
270-
impl_parent_parent: Option<DefId>,
271-
) -> bool {
272-
path.res
273-
.opt_def_id()
274-
.is_some_and(|did| did_has_local_parent(did, cx.tcx, impl_parent, impl_parent_parent))
273+
fn path_has_local_parent(path: &Path<'_>, cx: &LateContext<'_>, impl_parent: DefId) -> bool {
274+
path.res.opt_def_id().is_some_and(|did| did_has_local_parent(did, cx.tcx, impl_parent))
275275
}
276276

277-
/// Given a def id and a parent impl def id, this checks if the parent
278-
/// def id (modulo modules) correspond to the def id of the parent impl definition.
277+
/// Given a def id and a parent impl def id, this checks if the parent def id
278+
/// (modulo modules) correspond to the def id of the parent impl definition.
279279
#[inline]
280-
fn did_has_local_parent(
281-
did: DefId,
280+
fn did_has_local_parent(did: DefId, tcx: TyCtxt<'_>, impl_parent: DefId) -> bool {
281+
if !did.is_local() {
282+
return false;
283+
}
284+
285+
let Some(parent_did) = tcx.opt_parent(did) else {
286+
return false;
287+
};
288+
289+
peal_parent_while(tcx, parent_did, |tcx, did| tcx.def_kind(did) == DefKind::Mod)
290+
.map(|parent_did| parent_did == impl_parent)
291+
.unwrap_or(false)
292+
}
293+
294+
/// Given a `DefId` checks if it satisfies `f` if it does check with it's parent and continue
295+
/// until it doesn't satisfies `f` and return the last `DefId` checked.
296+
///
297+
/// In other word this method return the first `DefId` that doesn't satisfies `f`.
298+
#[inline]
299+
fn peal_parent_while(
282300
tcx: TyCtxt<'_>,
283-
impl_parent: DefId,
284-
impl_parent_parent: Option<DefId>,
285-
) -> bool {
286-
did.is_local()
287-
&& if let Some(did_parent) = tcx.opt_parent(did) {
288-
did_parent == impl_parent
289-
|| Some(did_parent) == impl_parent_parent
290-
|| !did_parent.is_crate_root()
291-
&& tcx.def_kind(did_parent) == DefKind::Mod
292-
&& did_has_local_parent(did_parent, tcx, impl_parent, impl_parent_parent)
301+
did: DefId,
302+
mut f: impl FnMut(TyCtxt<'_>, DefId) -> bool,
303+
) -> Option<DefId> {
304+
#[inline]
305+
fn inner(
306+
tcx: TyCtxt<'_>,
307+
did: DefId,
308+
f: &mut impl FnMut(TyCtxt<'_>, DefId) -> bool,
309+
) -> Option<DefId> {
310+
if !did.is_crate_root() && f(tcx, did) {
311+
tcx.opt_parent(did)
312+
.filter(|parent_did| parent_did.is_local())
313+
.map(|parent_did| inner(tcx, parent_did, f))
314+
.flatten()
293315
} else {
294-
false
316+
Some(did)
295317
}
318+
}
319+
320+
inner(tcx, did, &mut f)
296321
}
297322

298323
/// Return for a given `Path` the span until the last args

Diff for: tests/ui/lint/non-local-defs/consts.rs

+21
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,27 @@ fn main() {
6363

6464
1
6565
};
66+
67+
const _: () = {
68+
const _: () = {
69+
impl Test {}
70+
//~^ WARN non-local `impl` definition
71+
};
72+
};
73+
74+
const _: () = {
75+
const _: () = {
76+
mod tmp {
77+
pub(super) struct InnerTest;
78+
}
79+
80+
impl tmp::InnerTest {}
81+
//~^ WARN non-local `impl` definition
82+
// It's unclear if the above should lint or not, it's
83+
// an edge case of an edge case, we can fix it latter
84+
// if there are users.
85+
};
86+
};
6687
}
6788

6889
trait Uto9 {}

Diff for: tests/ui/lint/non-local-defs/consts.stderr

+32-3
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,36 @@ LL | impl Test {
9595
= note: 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>
9696

9797
warning: non-local `impl` definition, `impl` blocks should be written at the same level as their item
98-
--> $DIR/consts.rs:72:9
98+
--> $DIR/consts.rs:69:13
99+
|
100+
LL | const _: () = {
101+
| ----------- move the `impl` block outside of this constant `_` and up 3 bodies
102+
LL | impl Test {}
103+
| ^^^^^----
104+
| |
105+
| `Test` is not local
106+
|
107+
= note: 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`
108+
= note: items in an anonymous const item (`const _: () = { ... }`) are treated as in the same scope as the anonymous const's declaration for the purpose of this lint
109+
= note: 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>
110+
111+
warning: non-local `impl` definition, `impl` blocks should be written at the same level as their item
112+
--> $DIR/consts.rs:80:13
113+
|
114+
LL | const _: () = {
115+
| ----------- move the `impl` block outside of this constant `_` and up 3 bodies
116+
...
117+
LL | impl tmp::InnerTest {}
118+
| ^^^^^--------------
119+
| |
120+
| `InnerTest` is not local
121+
|
122+
= note: 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`
123+
= note: items in an anonymous const item (`const _: () = { ... }`) are treated as in the same scope as the anonymous const's declaration for the purpose of this lint
124+
= note: 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>
125+
126+
warning: non-local `impl` definition, `impl` blocks should be written at the same level as their item
127+
--> $DIR/consts.rs:93:9
99128
|
100129
LL | let _a = || {
101130
| -- move the `impl` block outside of this closure `<unnameable>` and up 2 bodies
@@ -109,7 +138,7 @@ LL | impl Uto9 for Test {}
109138
= note: 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>
110139

111140
warning: non-local `impl` definition, `impl` blocks should be written at the same level as their item
112-
--> $DIR/consts.rs:79:9
141+
--> $DIR/consts.rs:100:9
113142
|
114143
LL | type A = [u32; {
115144
| ____________________-
@@ -126,5 +155,5 @@ LL | | }];
126155
= note: 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`
127156
= note: 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>
128157

129-
warning: 8 warnings emitted
158+
warning: 10 warnings emitted
130159

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// This test check that no matter the nesting of const-anons and modules
2+
// we consider them as transparent.
3+
//
4+
// Similar to https://github.com/rust-lang/rust/issues/131474
5+
6+
//@ check-pass
7+
8+
pub mod tmp {
9+
pub mod tmp {
10+
pub struct Test;
11+
}
12+
}
13+
14+
const _: () = {
15+
const _: () = {
16+
const _: () = {
17+
const _: () = {
18+
impl tmp::tmp::Test {}
19+
};
20+
};
21+
};
22+
};
23+
24+
fn main() {}
+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// This test check that no matter the nesting of const-anons we consider
2+
// them as transparent.
3+
//
4+
// https://github.com/rust-lang/rust/issues/131474
5+
6+
//@ check-pass
7+
8+
pub struct Test;
9+
10+
const _: () = {
11+
const _: () = {
12+
impl Default for Test {
13+
fn default() -> Test {
14+
Test
15+
}
16+
}
17+
};
18+
};
19+
20+
fn main() {}

0 commit comments

Comments
 (0)