Skip to content

Commit e7952fb

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

File tree

6 files changed

+936
-0
lines changed

6 files changed

+936
-0
lines changed

compiler/rustc_lint/messages.ftl

Lines changed: 8 additions & 0 deletions
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

Lines changed: 3 additions & 0 deletions
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

Lines changed: 9 additions & 0 deletions
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)]
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
use rustc_hir::{
2+
def::DefKind, Body, FnRetTy, GenericArg, GenericParam, GenericParamKind, Item, ItemKind, Path,
3+
QPath, TyKind,
4+
};
5+
use rustc_span::{def_id::DefId, sym, symbol::kw, MacroKind};
6+
7+
use crate::{lints::NonLocalDefinitionsDiag, LateContext, LateLintPass, LintContext};
8+
9+
declare_lint! {
10+
/// The `non_local_definitions` lint checks for `impl` blocks and `#[macro_export]`
11+
/// macro inside bodies (functions, enum discriminant, ...).
12+
///
13+
/// ### Example
14+
///
15+
/// ```rust
16+
/// trait MyTrait {}
17+
/// struct MyStruct;
18+
///
19+
/// fn foo() {
20+
/// impl MyTrait for MyStruct {}
21+
/// }
22+
/// ```
23+
///
24+
/// {{produces}}
25+
///
26+
/// ### Explanation
27+
///
28+
/// Creating non local definitions go against expectation and can create decrepencies
29+
/// in tooling. In should be avoided.
30+
pub NON_LOCAL_DEFINITIONS,
31+
Warn,
32+
"checks for non local definitions"
33+
}
34+
35+
#[derive(Default)]
36+
pub struct NonLocalDefinitions {
37+
is_in_body: u32,
38+
}
39+
40+
impl_lint_pass!(NonLocalDefinitions => [NON_LOCAL_DEFINITIONS]);
41+
42+
impl<'tcx> LateLintPass<'tcx> for NonLocalDefinitions {
43+
fn check_body(&mut self, _cx: &LateContext<'tcx>, _body: &'tcx Body<'tcx>) {
44+
self.is_in_body += 1;
45+
}
46+
47+
fn check_body_post(&mut self, _cx: &LateContext<'tcx>, _body: &'tcx Body<'tcx>) {
48+
self.is_in_body -= 1;
49+
}
50+
51+
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) {
52+
if self.is_in_body > 0 {
53+
let parent_impl = cx.tcx.parent(item.owner_id.def_id.into());
54+
55+
// Per RFC we (currently) ignore anon-const (`const _: Ty = ...`).
56+
if cx.tcx.def_kind(parent_impl) == DefKind::Const
57+
&& cx.tcx.opt_item_name(parent_impl) == Some(kw::Underscore)
58+
{
59+
return;
60+
}
61+
62+
match item.kind {
63+
ItemKind::Impl(impl_) => {
64+
// The RFC states:
65+
//
66+
// > An item nested inside an expression-containing item (through any
67+
// > level of nesting) may not define an impl Trait for Type unless
68+
// > either the **Trait** or the **Type** is also nested inside the
69+
// > same expression-containing item.
70+
//
71+
// To achieve this we get try to get the paths of the _Trait_ and
72+
// _Type_, and we look inside thoses paths to try a find in one
73+
// of them a type whose parent is the same as the impl definition.
74+
//
75+
// If that's the case this means that this impl block decleration
76+
// is using local items and so we don't lint on it.
77+
78+
let generics_has_local_parent =
79+
generics_params_has_local_parent(&impl_.generics.params, cx, parent_impl);
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 checks if the if parent resolution
118+
/// def id correspond to the def id of the parent impl definition.
119+
///
120+
/// Given this path, we will look at the path (and ignore any generic args):
121+
///
122+
/// ```text
123+
/// std::convert::PartialEq<Foo<Bar>>
124+
/// ^^^^^^^^^^^^^^^^^^^^^^^
125+
/// ```
126+
fn path_has_local_parent(path: &Path<'_>, cx: &LateContext<'_>, parent_impl: DefId) -> bool {
127+
if let Some(did) = path.res.opt_def_id()
128+
&& cx.tcx.parent(did) == parent_impl
129+
{
130+
return true;
131+
}
132+
133+
false
134+
}
135+
136+
/// Given a hir::TyKind we recurse into all it's variants to find all the hir::Path
137+
/// and execute [`path_has_local_parent`].
138+
fn ty_kind_has_local_parent(
139+
ty_kind: &TyKind<'_>,
140+
cx: &LateContext<'_>,
141+
parent_impl: DefId,
142+
) -> bool {
143+
match ty_kind {
144+
TyKind::InferDelegation(_, _) => false,
145+
TyKind::Slice(ty) | TyKind::Array(ty, _) => {
146+
ty_kind_has_local_parent(&ty.kind, cx, parent_impl)
147+
}
148+
TyKind::Ptr(mut_ty) => ty_kind_has_local_parent(&mut_ty.ty.kind, cx, parent_impl),
149+
TyKind::Ref(_, mut_ty) => ty_kind_has_local_parent(&mut_ty.ty.kind, cx, parent_impl),
150+
TyKind::BareFn(bare_fn_ty) => {
151+
bare_fn_ty
152+
.decl
153+
.inputs
154+
.iter()
155+
.any(|ty| ty_kind_has_local_parent(&ty.kind, cx, parent_impl))
156+
|| match bare_fn_ty.decl.output {
157+
FnRetTy::DefaultReturn(_) => false,
158+
FnRetTy::Return(ty) => ty_kind_has_local_parent(&ty.kind, cx, parent_impl),
159+
}
160+
|| generics_params_has_local_parent(&bare_fn_ty.generic_params, cx, parent_impl)
161+
}
162+
TyKind::Never => false,
163+
TyKind::Tup(tys) => {
164+
tys.iter().any(|ty| ty_kind_has_local_parent(&ty.kind, cx, parent_impl))
165+
}
166+
TyKind::Path(QPath::Resolved(_, ty_path)) => {
167+
path_has_local_parent(ty_path, cx, parent_impl)
168+
}
169+
TyKind::Path(_) => false,
170+
TyKind::OpaqueDef(_, args, _) => args
171+
.iter()
172+
.filter_map(|garg| match garg {
173+
GenericArg::Lifetime(_) => None,
174+
GenericArg::Type(ty) => Some(ty),
175+
GenericArg::Const(_) => None,
176+
GenericArg::Infer(_) => None,
177+
})
178+
.any(|ty| ty_kind_has_local_parent(&ty.kind, cx, parent_impl)),
179+
TyKind::TraitObject(bounds, _, _) => bounds.iter().any(|poly_trait_ref| {
180+
path_has_local_parent(poly_trait_ref.trait_ref.path, cx, parent_impl)
181+
}),
182+
TyKind::Typeof(_) => false,
183+
TyKind::Infer => false,
184+
TyKind::Err(_) => false,
185+
}
186+
}
187+
188+
fn generics_params_has_local_parent(
189+
generic_params: &[GenericParam<'_>],
190+
cx: &LateContext<'_>,
191+
parent_impl: DefId,
192+
) -> bool {
193+
generic_params
194+
.iter()
195+
.filter_map(|gparam| match gparam.kind {
196+
GenericParamKind::Lifetime { kind: _ } => None,
197+
GenericParamKind::Type { default, synthetic: _ } => default,
198+
GenericParamKind::Const { ty, default: _, is_host_effect: _ } => Some(ty),
199+
})
200+
.any(|ty| ty_kind_has_local_parent(&ty.kind, cx, parent_impl))
201+
}

0 commit comments

Comments
 (0)