Skip to content

Commit f6f9977

Browse files
committed
Implement RFC3373 non local definitions lint
1 parent e7bbe8c commit f6f9977

File tree

6 files changed

+579
-0
lines changed

6 files changed

+579
-0
lines changed

compiler/rustc_lint/messages.ftl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,10 @@ 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 = move this declaration outside the of the {$depth} outermost bodies
411+
.deprecation = this may become deny-by-default in the Edition 2024 and higher
412+
409413
lint_non_snake_case = {$sort} `{$name}` should have a snake case name
410414
.rename_or_convert_suggestion = rename the identifier or convert it to a snake case raw identifier
411415
.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: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
use rustc_hir::{Body, GenericArg, Item, ItemKind, Path, QPath, TyKind};
2+
use rustc_span::{def_id::DefId, sym, MacroKind};
3+
4+
use crate::{lints::NonLocalDefinitionsDiag, LateContext, LateLintPass, LintContext};
5+
6+
declare_lint! {
7+
/// The `non_local_definitions` lint checks for `impl` blocks and `#[macro_export]`
8+
/// macro inside bodies (functions, enum discriminant, ...).
9+
///
10+
/// ### Example
11+
///
12+
/// ```rust
13+
/// trait MyTrait {}
14+
/// struct MyStruct;
15+
///
16+
/// fn foo() {
17+
/// impl MyTrait for MyStruct {}
18+
/// }
19+
/// ```
20+
///
21+
/// {{produces}}
22+
///
23+
/// ### Explanation
24+
///
25+
/// Creating non local definitions go against expectation and can create decrepencies
26+
/// in tooling. In should be avoided.
27+
pub NON_LOCAL_DEFINITIONS,
28+
Warn,
29+
"checks for non local definitions"
30+
}
31+
32+
#[derive(Default)]
33+
pub struct NonLocalDefinitions {
34+
is_in_body: u32,
35+
}
36+
37+
impl_lint_pass!(NonLocalDefinitions => [NON_LOCAL_DEFINITIONS]);
38+
39+
impl<'tcx> LateLintPass<'tcx> for NonLocalDefinitions {
40+
fn check_body(&mut self, _cx: &LateContext<'tcx>, _body: &'tcx Body<'tcx>) {
41+
self.is_in_body += 1;
42+
}
43+
44+
fn check_body_post(&mut self, _cx: &LateContext<'tcx>, _body: &'tcx Body<'tcx>) {
45+
self.is_in_body -= 1;
46+
}
47+
48+
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) {
49+
if self.is_in_body > 0 {
50+
match item.kind {
51+
ItemKind::Impl(impl_) => {
52+
// The RFC states:
53+
//
54+
// > An item nested inside an expression-containing item (through any
55+
// > level of nesting) may not define an impl Trait for Type unless
56+
// > either the **Trait** or the **Type** is also nested inside the
57+
// > same expression-containing item.
58+
//
59+
// To achieve this we get try to get the paths of the _Trait_ and
60+
// _Type_, and we look inside thoses paths to try a find in one
61+
// of them a type whose parent is the same as the impl definition.
62+
//
63+
// If that's the case this means that this impl block decleration
64+
// is using local items and so we don't lint on it.
65+
66+
let parent_impl = cx.tcx.parent(item.owner_id.def_id.into());
67+
68+
let self_ty_has_local_parent = if let TyKind::Path(QPath::Resolved(
69+
_,
70+
self_ty_path,
71+
)) = impl_.self_ty.kind
72+
{
73+
has_local_parent(self_ty_path, cx, parent_impl)
74+
} else {
75+
false
76+
};
77+
78+
let of_trait_has_local_parent = if let Some(of_trait) = impl_.of_trait {
79+
has_local_parent(of_trait.path, cx, parent_impl)
80+
} else {
81+
false
82+
};
83+
84+
// If none of them have a local parent (LOGICAL NOR) this means that
85+
// this impl definition is a non-local definition and so we lint on it.
86+
if !(self_ty_has_local_parent || of_trait_has_local_parent) {
87+
cx.emit_span_lint(
88+
NON_LOCAL_DEFINITIONS,
89+
item.span,
90+
NonLocalDefinitionsDiag { depth: self.is_in_body },
91+
);
92+
}
93+
}
94+
ItemKind::Macro(_macro, MacroKind::Bang) => {
95+
if cx.tcx.has_attr(item.owner_id.def_id, sym::macro_export) {
96+
cx.emit_span_lint(
97+
NON_LOCAL_DEFINITIONS,
98+
item.span,
99+
NonLocalDefinitionsDiag { depth: self.is_in_body },
100+
);
101+
}
102+
}
103+
_ => {}
104+
}
105+
}
106+
}
107+
}
108+
109+
/// Given a path and a parent impl def id, this function tries to find if any of
110+
/// the segments, generic type argument have a parent def id that correspond to
111+
/// the def id of the parent impl definition.
112+
///
113+
/// Given this path, we will look at every segments and generic args:
114+
///
115+
/// std::convert::PartialEq<Foo<Bar>>
116+
/// ^^^ ^^^^^^^ ^^^^^^^^^ ^^^ ^^^
117+
///
118+
/// Note that we we are only interested in `TyKind::Path` as it's the only `TyKind`
119+
/// that can point to an item outside the "parent impl definition".
120+
fn has_local_parent(path: &Path<'_>, cx: &LateContext<'_>, parent_impl: DefId) -> bool {
121+
if let Some(did) = path.res.opt_def_id()
122+
&& cx.tcx.parent(did) == parent_impl
123+
{
124+
return true;
125+
}
126+
127+
for seg in path.segments {
128+
if let Some(gargs) = seg.args {
129+
for garg in gargs.args {
130+
if let GenericArg::Type(ty) = garg
131+
&& let TyKind::Path(QPath::Resolved(_, ty_path)) = ty.kind
132+
&& has_local_parent(ty_path, cx, parent_impl)
133+
{
134+
return true;
135+
}
136+
}
137+
}
138+
}
139+
140+
false
141+
}

0 commit comments

Comments
 (0)