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 } ;
29use rustc_span:: def_id:: { DefId , LOCAL_CRATE } ;
10+ use rustc_span:: Span ;
311use 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+ } ;
416
517use crate :: lints:: { NonLocalDefinitionsCargoUpdateNote , NonLocalDefinitionsDiag } ;
618use crate :: { LateContext , LateLintPass , LintContext } ;
@@ -66,7 +78,9 @@ impl<'tcx> LateLintPass<'tcx> for NonLocalDefinitions {
6678 return ;
6779 }
6880
69- let parent = cx. tcx . parent ( item. owner_id . def_id . into ( ) ) ;
81+ let def_id = item. owner_id . def_id . into ( ) ;
82+
83+ let parent = cx. tcx . parent ( def_id) ;
7084 let parent_def_kind = cx. tcx . def_kind ( parent) ;
7185 let parent_opt_item_name = cx. tcx . opt_item_name ( parent) ;
7286
@@ -121,6 +135,7 @@ impl<'tcx> LateLintPass<'tcx> for NonLocalDefinitions {
121135 None
122136 } ;
123137
138+ // Part 1: Is the Self type local?
124139 let self_ty_has_local_parent = match impl_. self_ty . kind {
125140 TyKind :: Path ( QPath :: Resolved ( _, ty_path) ) => {
126141 path_has_local_parent ( ty_path, cx, parent, parent_parent)
@@ -150,41 +165,65 @@ impl<'tcx> LateLintPass<'tcx> for NonLocalDefinitions {
150165 | TyKind :: Err ( _) => false ,
151166 } ;
152167
168+ if self_ty_has_local_parent {
169+ return ;
170+ }
171+
172+ // Part 2: Is the Trait local?
153173 let of_trait_has_local_parent = impl_
154174 . of_trait
155175 . map ( |of_trait| path_has_local_parent ( of_trait. path , cx, parent, parent_parent) )
156176 . unwrap_or ( false ) ;
157177
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- )
178+ if of_trait_has_local_parent {
179+ return ;
180+ }
181+
182+ // Part 3: Is the impl definition leaking outside it defining scope?
183+ let impl_has_enough_non_local_candidates = cx
184+ . tcx
185+ . impl_trait_ref ( def_id)
186+ . map ( |binder| {
187+ impl_trait_ref_has_enough_non_local_candidates (
188+ cx. tcx ,
189+ item. span ,
190+ def_id,
191+ binder,
192+ |did| did_has_local_parent ( did, cx. tcx , parent, parent_parent) ,
193+ )
194+ } )
195+ . unwrap_or ( false ) ;
196+
197+ if impl_has_enough_non_local_candidates {
198+ return ;
187199 }
200+
201+ let const_anon = if self . body_depth == 1
202+ && parent_def_kind == DefKind :: Const
203+ && parent_opt_item_name != Some ( kw:: Underscore )
204+ && let Some ( parent) = parent. as_local ( )
205+ && let Node :: Item ( item) = cx. tcx . hir_node_by_def_id ( parent)
206+ && let ItemKind :: Const ( ty, _, _) = item. kind
207+ && let TyKind :: Tup ( & [ ] ) = ty. kind
208+ {
209+ Some ( item. ident . span )
210+ } else {
211+ None
212+ } ;
213+
214+ cx. emit_span_lint (
215+ NON_LOCAL_DEFINITIONS ,
216+ item. span ,
217+ NonLocalDefinitionsDiag :: Impl {
218+ depth : self . body_depth ,
219+ body_kind_descr : cx. tcx . def_kind_descr ( parent_def_kind, parent) ,
220+ body_name : parent_opt_item_name
221+ . map ( |s| s. to_ident_string ( ) )
222+ . unwrap_or_else ( || "<unnameable>" . to_string ( ) ) ,
223+ cargo_update : cargo_update ( ) ,
224+ const_anon,
225+ } ,
226+ )
188227 }
189228 ItemKind :: Macro ( _macro, MacroKind :: Bang )
190229 if cx. tcx . has_attr ( item. owner_id . def_id , sym:: macro_export) =>
@@ -207,6 +246,81 @@ impl<'tcx> LateLintPass<'tcx> for NonLocalDefinitions {
207246 }
208247}
209248
249+ // Detecting if the impl definition is leaking outside of it's defining scope.
250+ //
251+ // Rule: for each impl, instantiate all local types with inference vars and
252+ // then assemble candidates for that goal, if there are more than 1 (non-private
253+ // impls), it does not leak.
254+ //
255+ // https://github.com/rust-lang/rust/issues/121621#issuecomment-1976826895
256+ fn impl_trait_ref_has_enough_non_local_candidates < ' tcx > (
257+ tcx : TyCtxt < ' tcx > ,
258+ infer_span : Span ,
259+ trait_def_id : DefId ,
260+ binder : EarlyBinder < TraitRef < ' tcx > > ,
261+ mut did_has_local_parent : impl FnMut ( DefId ) -> bool ,
262+ ) -> bool {
263+ let infcx = tcx. infer_ctxt ( ) . build ( ) ;
264+ let trait_ref = binder. instantiate ( tcx, infcx. fresh_args_for_item ( infer_span, trait_def_id) ) ;
265+
266+ let trait_ref = trait_ref. fold_with ( & mut ReplaceLocalTypesWithInfer {
267+ infcx : & infcx,
268+ infer_span,
269+ did_has_local_parent : & mut did_has_local_parent,
270+ } ) ;
271+
272+ let poly_trait_obligation = Obligation :: new (
273+ tcx,
274+ ObligationCause :: dummy ( ) ,
275+ ty:: ParamEnv :: empty ( ) ,
276+ Binder :: dummy ( trait_ref) ,
277+ ) ;
278+
279+ let ambiguities = compute_applicable_impls_for_diagnostics ( & infcx, & poly_trait_obligation) ;
280+
281+ let mut it = ambiguities. iter ( ) . filter ( |ambi| match ambi {
282+ CandidateSource :: DefId ( did) => !did_has_local_parent ( * did) ,
283+ CandidateSource :: ParamEnv ( _) => unreachable ! ( ) ,
284+ } ) ;
285+
286+ let _ = it. next ( ) ;
287+ it. next ( ) . is_some ( )
288+ }
289+
290+ /// Replace every local type by inference variable.
291+ ///
292+ /// ```text
293+ /// <Global<Local> as std::cmp::PartialEq<Global<Local>>>
294+ /// to
295+ /// <Global<_> as std::cmp::PartialEq<Global<_>>>
296+ /// ```
297+ struct ReplaceLocalTypesWithInfer < ' a , ' tcx , F : FnMut ( DefId ) -> bool > {
298+ infcx : & ' a InferCtxt < ' tcx > ,
299+ did_has_local_parent : F ,
300+ infer_span : Span ,
301+ }
302+
303+ impl < ' a , ' tcx , F : FnMut ( DefId ) -> bool > TypeFolder < TyCtxt < ' tcx > >
304+ for ReplaceLocalTypesWithInfer < ' a , ' tcx , F >
305+ {
306+ fn interner ( & self ) -> TyCtxt < ' tcx > {
307+ self . infcx . tcx
308+ }
309+
310+ fn fold_ty ( & mut self , t : Ty < ' tcx > ) -> Ty < ' tcx > {
311+ if let Some ( ty_did) = t. ty_def_id ( )
312+ && ( self . did_has_local_parent ) ( ty_did)
313+ {
314+ self . infcx . next_ty_var ( TypeVariableOrigin {
315+ kind : TypeVariableOriginKind :: TypeInference ,
316+ span : self . infer_span ,
317+ } )
318+ } else {
319+ t. super_fold_with ( self )
320+ }
321+ }
322+ }
323+
210324/// Given a path and a parent impl def id, this checks if the if parent resolution
211325/// def id correspond to the def id of the parent impl definition.
212326///
@@ -216,16 +330,29 @@ impl<'tcx> LateLintPass<'tcx> for NonLocalDefinitions {
216330/// std::convert::PartialEq<Foo<Bar>>
217331/// ^^^^^^^^^^^^^^^^^^^^^^^
218332/// ```
333+ #[ inline]
219334fn path_has_local_parent (
220335 path : & Path < ' _ > ,
221336 cx : & LateContext < ' _ > ,
222337 impl_parent : DefId ,
223338 impl_parent_parent : Option < DefId > ,
224339) -> 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- } )
340+ path. res
341+ . opt_def_id ( )
342+ . is_some_and ( |did| did_has_local_parent ( did, cx. tcx , impl_parent, impl_parent_parent) )
343+ }
344+
345+ /// Given a def id and a parent impl def id, this checks if the parent
346+ /// def id correspond to the def id of the parent impl definition.
347+ #[ inline]
348+ fn did_has_local_parent (
349+ did : DefId ,
350+ tcx : TyCtxt < ' _ > ,
351+ impl_parent : DefId ,
352+ impl_parent_parent : Option < DefId > ,
353+ ) -> bool {
354+ did. is_local ( ) && {
355+ let res_parent = tcx. parent ( did) ;
356+ res_parent == impl_parent || Some ( res_parent) == impl_parent_parent
357+ }
231358}
0 commit comments