1
- use rustc_hir::{def::DefKind, Body, Item, ItemKind, Node, Path, QPath, TyKind};
1
+ use rustc_hir::{def::DefKind, Body, Item, ItemKind, Node, TyKind};
2
+ use rustc_hir::{Path, QPath};
3
+ use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
4
+ use rustc_infer::infer::InferCtxt;
5
+ use rustc_infer::traits::{Obligation, ObligationCause};
6
+ use rustc_middle::query::Key;
7
+ use rustc_middle::ty::{self, Binder, Ty, TyCtxt, TypeFoldable, TypeFolder};
8
+ use rustc_middle::ty::{EarlyBinder, TraitRef, TypeSuperFoldable};
2
9
use rustc_span::def_id::{DefId, LOCAL_CRATE};
10
+ use rustc_span::Span;
3
11
use rustc_span::{sym, symbol::kw, ExpnKind, MacroKind};
12
+ use rustc_trait_selection::infer::TyCtxtInferExt;
13
+ use rustc_trait_selection::traits::error_reporting::ambiguity::{
14
+ compute_applicable_impls_for_diagnostics, CandidateSource,
15
+ };
4
16
5
17
use crate::lints::{NonLocalDefinitionsCargoUpdateNote, NonLocalDefinitionsDiag};
6
18
use crate::{LateContext, LateLintPass, LintContext};
@@ -66,7 +78,8 @@ impl<'tcx> LateLintPass<'tcx> for NonLocalDefinitions {
66
78
return;
67
79
}
68
80
69
- let parent = cx.tcx.parent(item.owner_id.def_id.into());
81
+ let def_id = item.owner_id.def_id.into();
82
+ let parent = cx.tcx.parent(def_id);
70
83
let parent_def_kind = cx.tcx.def_kind(parent);
71
84
let parent_opt_item_name = cx.tcx.opt_item_name(parent);
72
85
@@ -121,6 +134,7 @@ impl<'tcx> LateLintPass<'tcx> for NonLocalDefinitions {
121
134
None
122
135
};
123
136
137
+ // Part 1: Is the Self type local?
124
138
let self_ty_has_local_parent = match impl_.self_ty.kind {
125
139
TyKind::Path(QPath::Resolved(_, ty_path)) => {
126
140
path_has_local_parent(ty_path, cx, parent, parent_parent)
@@ -150,41 +164,70 @@ impl<'tcx> LateLintPass<'tcx> for NonLocalDefinitions {
150
164
| TyKind::Err(_) => false,
151
165
};
152
166
167
+ if self_ty_has_local_parent {
168
+ return;
169
+ }
170
+
171
+ // Part 2: Is the Trait local?
153
172
let of_trait_has_local_parent = impl_
154
173
.of_trait
155
174
.map(|of_trait| path_has_local_parent(of_trait.path, cx, parent, parent_parent))
156
175
.unwrap_or(false);
157
176
158
- // If none of them have a local parent (LOGICAL NOR) this means that
159
- // this impl definition is a non-local definition and so we lint on it.
160
- if !(self_ty_has_local_parent || of_trait_has_local_parent) {
161
- let const_anon = if self.body_depth == 1
162
- && parent_def_kind == DefKind::Const
163
- && parent_opt_item_name != Some(kw::Underscore)
164
- && let Some(parent) = parent.as_local()
165
- && let Node::Item(item) = cx.tcx.hir_node_by_def_id(parent)
166
- && let ItemKind::Const(ty, _, _) = item.kind
167
- && let TyKind::Tup(&[]) = ty.kind
168
- {
169
- Some(item.ident.span)
170
- } else {
171
- None
172
- };
173
-
174
- cx.emit_span_lint(
175
- NON_LOCAL_DEFINITIONS,
176
- item.span,
177
- NonLocalDefinitionsDiag::Impl {
178
- depth: self.body_depth,
179
- body_kind_descr: cx.tcx.def_kind_descr(parent_def_kind, parent),
180
- body_name: parent_opt_item_name
181
- .map(|s| s.to_ident_string())
182
- .unwrap_or_else(|| "<unnameable>".to_string()),
183
- cargo_update: cargo_update(),
184
- const_anon,
185
- },
186
- )
177
+ if of_trait_has_local_parent {
178
+ return;
179
+ }
180
+
181
+ // Part 3: Is the impl definition leaking outside it's defining scope?
182
+ //
183
+ // We always consider inherent impls to be leaking.
184
+ let impl_has_enough_non_local_candidates = cx
185
+ .tcx
186
+ .impl_trait_ref(def_id)
187
+ .map(|binder| {
188
+ impl_trait_ref_has_enough_non_local_candidates(
189
+ cx.tcx,
190
+ item.span,
191
+ def_id,
192
+ binder,
193
+ |did| did_has_local_parent(did, cx.tcx, parent, parent_parent),
194
+ )
195
+ })
196
+ .unwrap_or(false);
197
+
198
+ if impl_has_enough_non_local_candidates {
199
+ return;
187
200
}
201
+
202
+ // Get the span of the parent const item ident (if it's a not a const anon).
203
+ //
204
+ // Used to suggest changing the const item to a const anon.
205
+ let span_for_const_anon_suggestion = if self.body_depth == 1
206
+ && parent_def_kind == DefKind::Const
207
+ && parent_opt_item_name != Some(kw::Underscore)
208
+ && let Some(parent) = parent.as_local()
209
+ && let Node::Item(item) = cx.tcx.hir_node_by_def_id(parent)
210
+ && let ItemKind::Const(ty, _, _) = item.kind
211
+ && let TyKind::Tup(&[]) = ty.kind
212
+ {
213
+ Some(item.ident.span)
214
+ } else {
215
+ None
216
+ };
217
+
218
+ cx.emit_span_lint(
219
+ NON_LOCAL_DEFINITIONS,
220
+ item.span,
221
+ NonLocalDefinitionsDiag::Impl {
222
+ depth: self.body_depth,
223
+ body_kind_descr: cx.tcx.def_kind_descr(parent_def_kind, parent),
224
+ body_name: parent_opt_item_name
225
+ .map(|s| s.to_ident_string())
226
+ .unwrap_or_else(|| "<unnameable>".to_string()),
227
+ cargo_update: cargo_update(),
228
+ const_anon: span_for_const_anon_suggestion,
229
+ },
230
+ )
188
231
}
189
232
ItemKind::Macro(_macro, MacroKind::Bang)
190
233
if cx.tcx.has_attr(item.owner_id.def_id, sym::macro_export) =>
@@ -207,6 +250,81 @@ impl<'tcx> LateLintPass<'tcx> for NonLocalDefinitions {
207
250
}
208
251
}
209
252
253
+ // Detecting if the impl definition is leaking outside of it's defining scope.
254
+ //
255
+ // Rule: for each impl, instantiate all local types with inference vars and
256
+ // then assemble candidates for that goal, if there are more than 1 (non-private
257
+ // impls), it does not leak.
258
+ //
259
+ // https://github.com/rust-lang/rust/issues/121621#issuecomment-1976826895
260
+ fn impl_trait_ref_has_enough_non_local_candidates<'tcx>(
261
+ tcx: TyCtxt<'tcx>,
262
+ infer_span: Span,
263
+ trait_def_id: DefId,
264
+ binder: EarlyBinder<TraitRef<'tcx>>,
265
+ mut did_has_local_parent: impl FnMut(DefId) -> bool,
266
+ ) -> bool {
267
+ let infcx = tcx.infer_ctxt().build();
268
+ let trait_ref = binder.instantiate(tcx, infcx.fresh_args_for_item(infer_span, trait_def_id));
269
+
270
+ let trait_ref = trait_ref.fold_with(&mut ReplaceLocalTypesWithInfer {
271
+ infcx: &infcx,
272
+ infer_span,
273
+ did_has_local_parent: &mut did_has_local_parent,
274
+ });
275
+
276
+ let poly_trait_obligation = Obligation::new(
277
+ tcx,
278
+ ObligationCause::dummy(),
279
+ ty::ParamEnv::empty(),
280
+ Binder::dummy(trait_ref),
281
+ );
282
+
283
+ let ambiguities = compute_applicable_impls_for_diagnostics(&infcx, &poly_trait_obligation);
284
+
285
+ let mut it = ambiguities.iter().filter(|ambi| match ambi {
286
+ CandidateSource::DefId(did) => !did_has_local_parent(*did),
287
+ CandidateSource::ParamEnv(_) => unreachable!(),
288
+ });
289
+
290
+ let _ = it.next();
291
+ it.next().is_some()
292
+ }
293
+
294
+ /// Replace every local type by inference variable.
295
+ ///
296
+ /// ```text
297
+ /// <Global<Local> as std::cmp::PartialEq<Global<Local>>>
298
+ /// to
299
+ /// <Global<_> as std::cmp::PartialEq<Global<_>>>
300
+ /// ```
301
+ struct ReplaceLocalTypesWithInfer<'a, 'tcx, F: FnMut(DefId) -> bool> {
302
+ infcx: &'a InferCtxt<'tcx>,
303
+ did_has_local_parent: F,
304
+ infer_span: Span,
305
+ }
306
+
307
+ impl<'a, 'tcx, F: FnMut(DefId) -> bool> TypeFolder<TyCtxt<'tcx>>
308
+ for ReplaceLocalTypesWithInfer<'a, 'tcx, F>
309
+ {
310
+ fn interner(&self) -> TyCtxt<'tcx> {
311
+ self.infcx.tcx
312
+ }
313
+
314
+ fn fold_ty(&mut self, t: Ty<'tcx>) -> Ty<'tcx> {
315
+ if let Some(ty_did) = t.ty_def_id()
316
+ && (self.did_has_local_parent)(ty_did)
317
+ {
318
+ self.infcx.next_ty_var(TypeVariableOrigin {
319
+ kind: TypeVariableOriginKind::TypeInference,
320
+ span: self.infer_span,
321
+ })
322
+ } else {
323
+ t.super_fold_with(self)
324
+ }
325
+ }
326
+ }
327
+
210
328
/// Given a path and a parent impl def id, this checks if the if parent resolution
211
329
/// def id correspond to the def id of the parent impl definition.
212
330
///
@@ -216,16 +334,29 @@ impl<'tcx> LateLintPass<'tcx> for NonLocalDefinitions {
216
334
/// std::convert::PartialEq<Foo<Bar>>
217
335
/// ^^^^^^^^^^^^^^^^^^^^^^^
218
336
/// ```
337
+ #[inline]
219
338
fn path_has_local_parent(
220
339
path: &Path<'_>,
221
340
cx: &LateContext<'_>,
222
341
impl_parent: DefId,
223
342
impl_parent_parent: Option<DefId>,
224
343
) -> bool {
225
- path.res.opt_def_id().is_some_and(|did| {
226
- did.is_local() && {
227
- let res_parent = cx.tcx.parent(did);
228
- res_parent == impl_parent || Some(res_parent) == impl_parent_parent
229
- }
230
- })
344
+ path.res
345
+ .opt_def_id()
346
+ .is_some_and(|did| did_has_local_parent(did, cx.tcx, impl_parent, impl_parent_parent))
347
+ }
348
+
349
+ /// Given a def id and a parent impl def id, this checks if the parent
350
+ /// def id correspond to the def id of the parent impl definition.
351
+ #[inline]
352
+ fn did_has_local_parent(
353
+ did: DefId,
354
+ tcx: TyCtxt<'_>,
355
+ impl_parent: DefId,
356
+ impl_parent_parent: Option<DefId>,
357
+ ) -> bool {
358
+ did.is_local() && {
359
+ let res_parent = tcx.parent(did);
360
+ res_parent == impl_parent || Some(res_parent) == impl_parent_parent
361
+ }
231
362
}
0 commit comments