Skip to content

Commit 1964206

Browse files
committed
Implement RFC3373 non local definitions lint
1 parent 7df6f4a commit 1964206

File tree

6 files changed

+805
-0
lines changed

6 files changed

+805
-0
lines changed

compiler/rustc_lint/messages.ftl

+8
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,14 @@ lint_non_fmt_panic_unused =
406406
}
407407
.add_fmt_suggestion = or add a "{"{"}{"}"}" format string to use the message literally
408408
409+
lint_non_local_definitions = non local definition should be avoided as they go against expectation
410+
.help =
411+
move this declaration outside the of the {$depth ->
412+
[one] current body
413+
*[other] {$depth} outermost bodies
414+
}
415+
.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>
416+
409417
lint_non_snake_case = {$sort} `{$name}` should have a snake case name
410418
.rename_or_convert_suggestion = rename the identifier or convert it to a snake case raw identifier
411419
.cannot_convert_note = `{$sc}` cannot be used as a raw identifier

compiler/rustc_lint/src/lib.rs

+3
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ mod methods;
7272
mod multiple_supertrait_upcastable;
7373
mod non_ascii_idents;
7474
mod non_fmt_panic;
75+
mod non_local_def;
7576
mod nonstandard_style;
7677
mod noop_method_call;
7778
mod opaque_hidden_inferred_bound;
@@ -107,6 +108,7 @@ use methods::*;
107108
use multiple_supertrait_upcastable::*;
108109
use non_ascii_idents::*;
109110
use non_fmt_panic::NonPanicFmt;
111+
use non_local_def::*;
110112
use nonstandard_style::*;
111113
use noop_method_call::*;
112114
use opaque_hidden_inferred_bound::*;
@@ -233,6 +235,7 @@ late_lint_methods!(
233235
MissingDebugImplementations: MissingDebugImplementations,
234236
MissingDoc: MissingDoc,
235237
AsyncFnInTrait: AsyncFnInTrait,
238+
NonLocalDefinitions: NonLocalDefinitions::default(),
236239
]
237240
]
238241
);

compiler/rustc_lint/src/lints.rs

+9
Original file line numberDiff line numberDiff line change
@@ -1305,6 +1305,15 @@ pub struct SuspiciousDoubleRefCloneDiag<'a> {
13051305
pub ty: Ty<'a>,
13061306
}
13071307

1308+
// non_local_defs.rs
1309+
#[derive(LintDiagnostic)]
1310+
#[diag(lint_non_local_definitions)]
1311+
#[help(lint_help)]
1312+
#[note(lint_deprecation)]
1313+
pub struct NonLocalDefinitionsDiag {
1314+
pub depth: u32,
1315+
}
1316+
13081317
// pass_by_value.rs
13091318
#[derive(LintDiagnostic)]
13101319
#[diag(lint_pass_by_value)]
+243
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
use std::convert::identity;
2+
3+
use rustc_hir::{
4+
Body, FnRetTy, GenericArg, GenericBound, GenericParam, GenericParamKind, Item, ItemKind, Path,
5+
QPath, TyKind, WherePredicate,
6+
};
7+
use rustc_span::{def_id::DefId, sym, MacroKind};
8+
9+
use crate::{lints::NonLocalDefinitionsDiag, LateContext, LateLintPass, LintContext};
10+
11+
declare_lint! {
12+
/// The `non_local_definitions` lint checks for `impl` blocks and `#[macro_export]`
13+
/// macro inside bodies (functions, enum discriminant, ...).
14+
///
15+
/// ### Example
16+
///
17+
/// ```rust
18+
/// trait MyTrait {}
19+
/// struct MyStruct;
20+
///
21+
/// fn foo() {
22+
/// impl MyTrait for MyStruct {}
23+
/// }
24+
/// ```
25+
///
26+
/// {{produces}}
27+
///
28+
/// ### Explanation
29+
///
30+
/// Creating non local definitions go against expectation and can create decrepencies
31+
/// in tooling. In should be avoided.
32+
pub NON_LOCAL_DEFINITIONS,
33+
Warn,
34+
"checks for non local definitions"
35+
}
36+
37+
#[derive(Default)]
38+
pub struct NonLocalDefinitions {
39+
is_in_body: u32,
40+
}
41+
42+
impl_lint_pass!(NonLocalDefinitions => [NON_LOCAL_DEFINITIONS]);
43+
44+
impl<'tcx> LateLintPass<'tcx> for NonLocalDefinitions {
45+
fn check_body(&mut self, _cx: &LateContext<'tcx>, _body: &'tcx Body<'tcx>) {
46+
self.is_in_body += 1;
47+
}
48+
49+
fn check_body_post(&mut self, _cx: &LateContext<'tcx>, _body: &'tcx Body<'tcx>) {
50+
self.is_in_body -= 1;
51+
}
52+
53+
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) {
54+
if self.is_in_body > 0 {
55+
match item.kind {
56+
ItemKind::Impl(impl_) => {
57+
// The RFC states:
58+
//
59+
// > An item nested inside an expression-containing item (through any
60+
// > level of nesting) may not define an impl Trait for Type unless
61+
// > either the **Trait** or the **Type** is also nested inside the
62+
// > same expression-containing item.
63+
//
64+
// To achieve this we get try to get the paths of the _Trait_ and
65+
// _Type_, and we look inside thoses paths to try a find in one
66+
// of them a type whose parent is the same as the impl definition.
67+
//
68+
// If that's the case this means that this impl block decleration
69+
// is using local items and so we don't lint on it.
70+
71+
let parent_impl = cx.tcx.parent(item.owner_id.def_id.into());
72+
73+
let generics_has_local_parent =
74+
generics_params_has_local_parent(&impl_.generics.params, cx, parent_impl)
75+
|| generics_predicates_has_local_parent(
76+
&impl_.generics.predicates,
77+
cx,
78+
parent_impl,
79+
);
80+
81+
let self_ty_has_local_parent =
82+
ty_kind_has_local_parent(&impl_.self_ty.kind, cx, parent_impl);
83+
84+
let of_trait_has_local_parent = impl_
85+
.of_trait
86+
.map(|of_trait| path_has_local_parent(of_trait.path, cx, parent_impl))
87+
.unwrap_or(false);
88+
89+
// If none of them have a local parent (LOGICAL NOR) this means that
90+
// this impl definition is a non-local definition and so we lint on it.
91+
if !(generics_has_local_parent
92+
|| self_ty_has_local_parent
93+
|| of_trait_has_local_parent)
94+
{
95+
cx.emit_span_lint(
96+
NON_LOCAL_DEFINITIONS,
97+
item.span,
98+
NonLocalDefinitionsDiag { depth: self.is_in_body },
99+
);
100+
}
101+
}
102+
ItemKind::Macro(_macro, MacroKind::Bang) => {
103+
if cx.tcx.has_attr(item.owner_id.def_id, sym::macro_export) {
104+
cx.emit_span_lint(
105+
NON_LOCAL_DEFINITIONS,
106+
item.span,
107+
NonLocalDefinitionsDiag { depth: self.is_in_body },
108+
);
109+
}
110+
}
111+
_ => {}
112+
}
113+
}
114+
}
115+
}
116+
117+
/// Given a path and a parent impl def id, this function tries to find if any of
118+
/// the segments, generic type argument have a parent def id that correspond to
119+
/// the def id of the parent impl definition.
120+
///
121+
/// Given this path, we will look at every segments and generic args:
122+
///
123+
/// std::convert::PartialEq<Foo<Bar>>
124+
/// ^^^ ^^^^^^^ ^^^^^^^^^ ^^^ ^^^
125+
///
126+
/// Note that we we are only interested in `TyKind::Path` as it's the only `TyKind`
127+
/// that can point to an item outside the "parent impl definition".
128+
fn path_has_local_parent(path: &Path<'_>, cx: &LateContext<'_>, parent_impl: DefId) -> bool {
129+
if let Some(did) = path.res.opt_def_id()
130+
&& cx.tcx.parent(did) == parent_impl
131+
{
132+
return true;
133+
}
134+
135+
for seg in path.segments {
136+
if let Some(gargs) = seg.args {
137+
for garg in gargs.args {
138+
if let GenericArg::Type(ty) = garg
139+
&& ty_kind_has_local_parent(&ty.kind, cx, parent_impl)
140+
{
141+
return true;
142+
}
143+
}
144+
}
145+
}
146+
147+
false
148+
}
149+
150+
/// Given a hir::TyKind we recurse into all it's variants to find all the hir::Path
151+
/// and execute [`path_has_local_parent`].
152+
fn ty_kind_has_local_parent(
153+
ty_kind: &TyKind<'_>,
154+
cx: &LateContext<'_>,
155+
parent_impl: DefId,
156+
) -> bool {
157+
match ty_kind {
158+
TyKind::InferDelegation(_, _) => false,
159+
TyKind::Slice(ty) | TyKind::Array(ty, _) => {
160+
ty_kind_has_local_parent(&ty.kind, cx, parent_impl)
161+
}
162+
TyKind::Ptr(mut_ty) => ty_kind_has_local_parent(&mut_ty.ty.kind, cx, parent_impl),
163+
TyKind::Ref(_, mut_ty) => ty_kind_has_local_parent(&mut_ty.ty.kind, cx, parent_impl),
164+
TyKind::BareFn(bare_fn_ty) => {
165+
bare_fn_ty
166+
.decl
167+
.inputs
168+
.iter()
169+
.any(|ty| ty_kind_has_local_parent(&ty.kind, cx, parent_impl))
170+
|| match bare_fn_ty.decl.output {
171+
FnRetTy::DefaultReturn(_) => false,
172+
FnRetTy::Return(ty) => ty_kind_has_local_parent(&ty.kind, cx, parent_impl),
173+
}
174+
|| generics_params_has_local_parent(&bare_fn_ty.generic_params, cx, parent_impl)
175+
}
176+
TyKind::Never => false,
177+
TyKind::Tup(tys) => {
178+
tys.iter().any(|ty| ty_kind_has_local_parent(&ty.kind, cx, parent_impl))
179+
}
180+
TyKind::Path(QPath::Resolved(_, ty_path)) => {
181+
path_has_local_parent(ty_path, cx, parent_impl)
182+
}
183+
TyKind::Path(_) => false,
184+
TyKind::OpaqueDef(_, args, _) => args
185+
.iter()
186+
.filter_map(|garg| match garg {
187+
GenericArg::Lifetime(_) => None,
188+
GenericArg::Type(ty) => Some(ty),
189+
GenericArg::Const(_) => None,
190+
GenericArg::Infer(_) => None,
191+
})
192+
.any(|ty| ty_kind_has_local_parent(&ty.kind, cx, parent_impl)),
193+
TyKind::TraitObject(bounds, _, _) => bounds.iter().any(|poly_trait_ref| {
194+
path_has_local_parent(poly_trait_ref.trait_ref.path, cx, parent_impl)
195+
}),
196+
TyKind::Typeof(_) => false,
197+
TyKind::Infer => false,
198+
TyKind::Err(_) => false,
199+
}
200+
}
201+
202+
fn generics_params_has_local_parent(
203+
generic_params: &[GenericParam<'_>],
204+
cx: &LateContext<'_>,
205+
parent_impl: DefId,
206+
) -> bool {
207+
generic_params
208+
.iter()
209+
.filter_map(|gparam| match gparam.kind {
210+
GenericParamKind::Lifetime { kind: _ } => None,
211+
GenericParamKind::Type { default, synthetic: _ } => default,
212+
GenericParamKind::Const { ty, default: _, is_host_effect: _ } => Some(ty),
213+
})
214+
.any(|ty| ty_kind_has_local_parent(&ty.kind, cx, parent_impl))
215+
}
216+
217+
fn generics_predicates_has_local_parent(
218+
predicates: &[WherePredicate<'_>],
219+
cx: &LateContext<'_>,
220+
parent_impl: DefId,
221+
) -> bool {
222+
predicates
223+
.iter()
224+
.filter_map(|predicate| match predicate {
225+
WherePredicate::BoundPredicate(bound_predicate) => Some(
226+
ty_kind_has_local_parent(&bound_predicate.bounded_ty.kind, cx, parent_impl)
227+
|| bound_predicate.bounds.iter().any(|gbound| match gbound {
228+
GenericBound::Trait(poly_trait_ref, _) => {
229+
path_has_local_parent(&poly_trait_ref.trait_ref.path, cx, parent_impl)
230+
}
231+
GenericBound::Outlives(_) => false,
232+
})
233+
|| generics_params_has_local_parent(
234+
bound_predicate.bound_generic_params,
235+
cx,
236+
parent_impl,
237+
),
238+
),
239+
WherePredicate::RegionPredicate(_) => None,
240+
WherePredicate::EqPredicate(_) => None,
241+
})
242+
.any(identity)
243+
}

0 commit comments

Comments
 (0)