1
- use rustc_data_structures:: fx:: FxIndexSet ;
1
+ use std:: assert_matches:: debug_assert_matches;
2
+ use std:: cell:: LazyCell ;
3
+
4
+ use rustc_data_structures:: fx:: { FxHashMap , FxIndexMap , FxIndexSet } ;
2
5
use rustc_data_structures:: unord:: UnordSet ;
3
6
use rustc_errors:: { Applicability , LintDiagnostic } ;
4
7
use rustc_hir as hir;
5
8
use rustc_hir:: def:: DefKind ;
6
9
use rustc_hir:: def_id:: { DefId , LocalDefId } ;
10
+ use rustc_infer:: infer:: outlives:: env:: OutlivesEnvironment ;
11
+ use rustc_infer:: infer:: TyCtxtInferExt ;
7
12
use rustc_macros:: LintDiagnostic ;
8
- use rustc_middle:: bug;
9
13
use rustc_middle:: middle:: resolve_bound_vars:: ResolvedArg ;
14
+ use rustc_middle:: ty:: relate:: {
15
+ structurally_relate_consts, structurally_relate_tys, Relate , RelateResult , TypeRelation ,
16
+ } ;
10
17
use rustc_middle:: ty:: {
11
18
self , Ty , TyCtxt , TypeSuperVisitable , TypeVisitable , TypeVisitableExt , TypeVisitor ,
12
19
} ;
20
+ use rustc_middle:: { bug, span_bug} ;
13
21
use rustc_session:: lint:: FutureIncompatibilityReason ;
14
22
use rustc_session:: { declare_lint, declare_lint_pass} ;
15
23
use rustc_span:: edition:: Edition ;
16
- use rustc_span:: Span ;
24
+ use rustc_span:: { Span , Symbol } ;
25
+ use rustc_trait_selection:: traits:: outlives_bounds:: InferCtxtExt ;
26
+ use rustc_trait_selection:: traits:: ObligationCtxt ;
17
27
18
28
use crate :: { fluent_generated as fluent, LateContext , LateLintPass } ;
19
29
@@ -119,42 +129,87 @@ impl<'tcx> LateLintPass<'tcx> for ImplTraitOvercaptures {
119
129
}
120
130
}
121
131
132
+ #[ derive( PartialEq , Eq , Hash , Debug , Copy , Clone ) ]
133
+ enum ParamKind {
134
+ // Early-bound var.
135
+ Early ( Symbol , u32 ) ,
136
+ // Late-bound var on function, not within a binder. We can capture these.
137
+ Free ( DefId , Symbol ) ,
138
+ // Late-bound var in a binder. We can't capture these yet.
139
+ Late ,
140
+ }
141
+
122
142
fn check_fn ( tcx : TyCtxt < ' _ > , parent_def_id : LocalDefId ) {
123
143
let sig = tcx. fn_sig ( parent_def_id) . instantiate_identity ( ) ;
124
144
125
- let mut in_scope_parameters = FxIndexSet :: default ( ) ;
145
+ let mut in_scope_parameters = FxIndexMap :: default ( ) ;
126
146
// Populate the in_scope_parameters list first with all of the generics in scope
127
147
let mut current_def_id = Some ( parent_def_id. to_def_id ( ) ) ;
128
148
while let Some ( def_id) = current_def_id {
129
149
let generics = tcx. generics_of ( def_id) ;
130
150
for param in & generics. own_params {
131
- in_scope_parameters. insert ( param. def_id ) ;
151
+ in_scope_parameters. insert ( param. def_id , ParamKind :: Early ( param . name , param . index ) ) ;
132
152
}
133
153
current_def_id = generics. parent ;
134
154
}
135
155
156
+ for bound_var in sig. bound_vars ( ) {
157
+ let ty:: BoundVariableKind :: Region ( ty:: BoundRegionKind :: BrNamed ( def_id, name) ) = bound_var
158
+ else {
159
+ span_bug ! ( tcx. def_span( parent_def_id) , "unexpected non-lifetime binder on fn sig" ) ;
160
+ } ;
161
+
162
+ in_scope_parameters. insert ( def_id, ParamKind :: Free ( def_id, name) ) ;
163
+ }
164
+
165
+ let sig = tcx. liberate_late_bound_regions ( parent_def_id. to_def_id ( ) , sig) ;
166
+
136
167
// Then visit the signature to walk through all the binders (incl. the late-bound
137
168
// vars on the function itself, which we need to count too).
138
169
sig. visit_with ( & mut VisitOpaqueTypes {
139
170
tcx,
140
171
parent_def_id,
141
172
in_scope_parameters,
142
173
seen : Default :: default ( ) ,
174
+ // Lazily compute these two, since they're likely a bit expensive.
175
+ variances : LazyCell :: new ( || {
176
+ let mut functional_variances = FunctionalVariances {
177
+ tcx : tcx,
178
+ variances : FxHashMap :: default ( ) ,
179
+ ambient_variance : ty:: Covariant ,
180
+ generics : tcx. generics_of ( parent_def_id) ,
181
+ } ;
182
+ functional_variances. relate ( sig, sig) . unwrap ( ) ;
183
+ functional_variances. variances
184
+ } ) ,
185
+ outlives_env : LazyCell :: new ( || {
186
+ let param_env = tcx. param_env ( parent_def_id) ;
187
+ let infcx = tcx. infer_ctxt ( ) . build ( ) ;
188
+ let ocx = ObligationCtxt :: new ( & infcx) ;
189
+ let assumed_wf_tys = ocx. assumed_wf_types ( param_env, parent_def_id) . unwrap_or_default ( ) ;
190
+ let implied_bounds =
191
+ infcx. implied_bounds_tys_compat ( param_env, parent_def_id, & assumed_wf_tys, false ) ;
192
+ OutlivesEnvironment :: with_bounds ( param_env, implied_bounds)
193
+ } ) ,
143
194
} ) ;
144
195
}
145
196
146
- struct VisitOpaqueTypes < ' tcx > {
197
+ struct VisitOpaqueTypes < ' tcx , VarFn , OutlivesFn > {
147
198
tcx : TyCtxt < ' tcx > ,
148
199
parent_def_id : LocalDefId ,
149
- in_scope_parameters : FxIndexSet < DefId > ,
200
+ in_scope_parameters : FxIndexMap < DefId , ParamKind > ,
201
+ variances : LazyCell < FxHashMap < DefId , ty:: Variance > , VarFn > ,
202
+ outlives_env : LazyCell < OutlivesEnvironment < ' tcx > , OutlivesFn > ,
150
203
seen : FxIndexSet < LocalDefId > ,
151
204
}
152
205
153
- impl < ' tcx > TypeVisitor < TyCtxt < ' tcx > > for VisitOpaqueTypes < ' tcx > {
154
- fn visit_binder < T : TypeVisitable < TyCtxt < ' tcx > > > (
155
- & mut self ,
156
- t : & ty:: Binder < ' tcx , T > ,
157
- ) -> Self :: Result {
206
+ impl < ' tcx , VarFn , OutlivesFn > TypeVisitor < TyCtxt < ' tcx > >
207
+ for VisitOpaqueTypes < ' tcx , VarFn , OutlivesFn >
208
+ where
209
+ VarFn : FnOnce ( ) -> FxHashMap < DefId , ty:: Variance > ,
210
+ OutlivesFn : FnOnce ( ) -> OutlivesEnvironment < ' tcx > ,
211
+ {
212
+ fn visit_binder < T : TypeVisitable < TyCtxt < ' tcx > > > ( & mut self , t : & ty:: Binder < ' tcx , T > ) {
158
213
// When we get into a binder, we need to add its own bound vars to the scope.
159
214
let mut added = vec ! [ ] ;
160
215
for arg in t. bound_vars ( ) {
@@ -163,8 +218,8 @@ impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for VisitOpaqueTypes<'tcx> {
163
218
ty:: BoundVariableKind :: Region ( ty:: BoundRegionKind :: BrNamed ( def_id, ..) )
164
219
| ty:: BoundVariableKind :: Ty ( ty:: BoundTyKind :: Param ( def_id, _) ) => {
165
220
added. push ( def_id) ;
166
- let unique = self . in_scope_parameters . insert ( def_id) ;
167
- assert ! ( unique) ;
221
+ let unique = self . in_scope_parameters . insert ( def_id, ParamKind :: Late ) ;
222
+ assert_eq ! ( unique, None ) ;
168
223
}
169
224
_ => {
170
225
self . tcx . dcx ( ) . span_delayed_bug (
@@ -184,7 +239,7 @@ impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for VisitOpaqueTypes<'tcx> {
184
239
}
185
240
}
186
241
187
- fn visit_ty ( & mut self , t : Ty < ' tcx > ) -> Self :: Result {
242
+ fn visit_ty ( & mut self , t : Ty < ' tcx > ) {
188
243
if !t. has_aliases ( ) {
189
244
return ;
190
245
}
@@ -207,89 +262,126 @@ impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for VisitOpaqueTypes<'tcx> {
207
262
&& let hir:: OpaqueTyOrigin :: FnReturn ( parent_def_id) = opaque. origin
208
263
&& parent_def_id == self . parent_def_id
209
264
{
210
- // Compute the set of args that are captured by the opaque...
211
- let mut captured = FxIndexSet :: default ( ) ;
212
- let variances = self . tcx . variances_of ( opaque_def_id) ;
213
- let mut current_def_id = Some ( opaque_def_id. to_def_id ( ) ) ;
214
- while let Some ( def_id) = current_def_id {
215
- let generics = self . tcx . generics_of ( def_id) ;
216
- for param in & generics. own_params {
217
- // A param is captured if it's invariant.
218
- if variances[ param. index as usize ] != ty:: Invariant {
219
- continue ;
220
- }
221
- // We need to turn all `ty::Param`/`ConstKind::Param` and
222
- // `ReEarlyParam`/`ReBound` into def ids.
223
- captured. insert ( extract_def_id_from_arg (
224
- self . tcx ,
225
- generics,
226
- opaque_ty. args [ param. index as usize ] ,
227
- ) ) ;
228
- }
229
- current_def_id = generics. parent ;
230
- }
231
-
232
- // Compute the set of in scope params that are not captured. Get their spans,
233
- // since that's all we really care about them for emitting the diagnostic.
234
- let uncaptured_spans: Vec < _ > = self
235
- . in_scope_parameters
236
- . iter ( )
237
- . filter ( |def_id| !captured. contains ( * def_id) )
238
- . map ( |def_id| self . tcx . def_span ( def_id) )
239
- . collect ( ) ;
240
-
241
265
let opaque_span = self . tcx . def_span ( opaque_def_id) ;
242
266
let new_capture_rules =
243
267
opaque_span. at_least_rust_2024 ( ) || self . tcx . features ( ) . lifetime_capture_rules_2024 ;
244
-
245
- // If we have uncaptured args, and if the opaque doesn't already have
246
- // `use<>` syntax on it, and we're < edition 2024, then warn the user.
247
268
if !new_capture_rules
248
269
&& !opaque. bounds . iter ( ) . any ( |bound| matches ! ( bound, hir:: GenericBound :: Use ( ..) ) )
249
- && !uncaptured_spans. is_empty ( )
250
270
{
251
- let suggestion = if let Ok ( snippet) =
252
- self . tcx . sess . source_map ( ) . span_to_snippet ( opaque_span)
253
- && snippet. starts_with ( "impl " )
254
- {
255
- let ( lifetimes, others) : ( Vec < _ > , Vec < _ > ) = captured
256
- . into_iter ( )
257
- . partition ( |def_id| self . tcx . def_kind ( * def_id) == DefKind :: LifetimeParam ) ;
258
- // Take all lifetime params first, then all others (ty/ct).
259
- let generics: Vec < _ > = lifetimes
260
- . into_iter ( )
261
- . chain ( others)
262
- . map ( |def_id| self . tcx . item_name ( def_id) . to_string ( ) )
263
- . collect ( ) ;
264
- // Make sure that we're not trying to name any APITs
265
- if generics. iter ( ) . all ( |name| !name. starts_with ( "impl " ) ) {
266
- Some ( (
267
- format ! ( " + use<{}>" , generics. join( ", " ) ) ,
268
- opaque_span. shrink_to_hi ( ) ,
269
- ) )
271
+ // Compute the set of args that are captured by the opaque...
272
+ let mut captured = FxIndexSet :: default ( ) ;
273
+ let mut captured_regions = FxIndexSet :: default ( ) ;
274
+ let variances = self . tcx . variances_of ( opaque_def_id) ;
275
+ let mut current_def_id = Some ( opaque_def_id. to_def_id ( ) ) ;
276
+ while let Some ( def_id) = current_def_id {
277
+ let generics = self . tcx . generics_of ( def_id) ;
278
+ for param in & generics. own_params {
279
+ // A param is captured if it's invariant.
280
+ if variances[ param. index as usize ] != ty:: Invariant {
281
+ continue ;
282
+ }
283
+
284
+ let arg = opaque_ty. args [ param. index as usize ] ;
285
+ // We need to turn all `ty::Param`/`ConstKind::Param` and
286
+ // `ReEarlyParam`/`ReBound` into def ids.
287
+ captured. insert ( extract_def_id_from_arg ( self . tcx , generics, arg) ) ;
288
+
289
+ captured_regions. extend ( arg. as_region ( ) ) ;
290
+ }
291
+ current_def_id = generics. parent ;
292
+ }
293
+
294
+ // Compute the set of in scope params that are not captured.
295
+ let mut uncaptured_args: FxIndexSet < _ > = self
296
+ . in_scope_parameters
297
+ . iter ( )
298
+ . filter ( |& ( def_id, _) | !captured. contains ( def_id) )
299
+ . collect ( ) ;
300
+ // Remove the set of lifetimes that are in-scope that outlive some other captured
301
+ // lifetime and are contravariant (i.e. covariant in argument position).
302
+ uncaptured_args. retain ( |& ( def_id, kind) | {
303
+ let Some ( ty:: Bivariant | ty:: Contravariant ) = self . variances . get ( def_id) else {
304
+ // Keep all covariant/invariant args. Also if variance is `None`,
305
+ // then that means it's either not a lifetime, or it didn't show up
306
+ // anywhere in the signature.
307
+ return true ;
308
+ } ;
309
+ // We only computed variance of lifetimes...
310
+ debug_assert_matches ! ( self . tcx. def_kind( def_id) , DefKind :: LifetimeParam ) ;
311
+ let uncaptured = match * kind {
312
+ ParamKind :: Early ( name, index) => ty:: Region :: new_early_param (
313
+ self . tcx ,
314
+ ty:: EarlyParamRegion { name, index } ,
315
+ ) ,
316
+ ParamKind :: Free ( def_id, name) => ty:: Region :: new_late_param (
317
+ self . tcx ,
318
+ self . parent_def_id . to_def_id ( ) ,
319
+ ty:: BoundRegionKind :: BrNamed ( def_id, name) ,
320
+ ) ,
321
+ // Totally ignore late bound args from binders.
322
+ ParamKind :: Late => return true ,
323
+ } ;
324
+ // Does this region outlive any captured region?
325
+ !captured_regions. iter ( ) . any ( |r| {
326
+ self . outlives_env
327
+ . free_region_map ( )
328
+ . sub_free_regions ( self . tcx , * r, uncaptured)
329
+ } )
330
+ } ) ;
331
+
332
+ // If we have uncaptured args, and if the opaque doesn't already have
333
+ // `use<>` syntax on it, and we're < edition 2024, then warn the user.
334
+ if !uncaptured_args. is_empty ( ) {
335
+ let suggestion = if let Ok ( snippet) =
336
+ self . tcx . sess . source_map ( ) . span_to_snippet ( opaque_span)
337
+ && snippet. starts_with ( "impl " )
338
+ {
339
+ let ( lifetimes, others) : ( Vec < _ > , Vec < _ > ) =
340
+ captured. into_iter ( ) . partition ( |def_id| {
341
+ self . tcx . def_kind ( * def_id) == DefKind :: LifetimeParam
342
+ } ) ;
343
+ // Take all lifetime params first, then all others (ty/ct).
344
+ let generics: Vec < _ > = lifetimes
345
+ . into_iter ( )
346
+ . chain ( others)
347
+ . map ( |def_id| self . tcx . item_name ( def_id) . to_string ( ) )
348
+ . collect ( ) ;
349
+ // Make sure that we're not trying to name any APITs
350
+ if generics. iter ( ) . all ( |name| !name. starts_with ( "impl " ) ) {
351
+ Some ( (
352
+ format ! ( " + use<{}>" , generics. join( ", " ) ) ,
353
+ opaque_span. shrink_to_hi ( ) ,
354
+ ) )
355
+ } else {
356
+ None
357
+ }
270
358
} else {
271
359
None
272
- }
273
- } else {
274
- None
275
- } ;
276
-
277
- self . tcx . emit_node_span_lint (
278
- IMPL_TRAIT_OVERCAPTURES ,
279
- self . tcx . local_def_id_to_hir_id ( opaque_def_id) ,
280
- opaque_span,
281
- ImplTraitOvercapturesLint {
282
- self_ty : t,
283
- num_captured : uncaptured_spans. len ( ) ,
284
- uncaptured_spans,
285
- suggestion,
286
- } ,
287
- ) ;
360
+ } ;
361
+
362
+ let uncaptured_spans: Vec < _ > = uncaptured_args
363
+ . into_iter ( )
364
+ . map ( |( def_id, _) | self . tcx . def_span ( def_id) )
365
+ . collect ( ) ;
366
+
367
+ self . tcx . emit_node_span_lint (
368
+ IMPL_TRAIT_OVERCAPTURES ,
369
+ self . tcx . local_def_id_to_hir_id ( opaque_def_id) ,
370
+ opaque_span,
371
+ ImplTraitOvercapturesLint {
372
+ self_ty : t,
373
+ num_captured : uncaptured_spans. len ( ) ,
374
+ uncaptured_spans,
375
+ suggestion,
376
+ } ,
377
+ ) ;
378
+ }
288
379
}
380
+
289
381
// Otherwise, if we are edition 2024, have `use<>` syntax, and
290
382
// have no uncaptured args, then we should warn to the user that
291
383
// it's redundant to capture all args explicitly.
292
- else if new_capture_rules
384
+ if new_capture_rules
293
385
&& let Some ( ( captured_args, capturing_span) ) =
294
386
opaque. bounds . iter ( ) . find_map ( |bound| match * bound {
295
387
hir:: GenericBound :: Use ( a, s) => Some ( ( a, s) ) ,
@@ -327,7 +419,7 @@ impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for VisitOpaqueTypes<'tcx> {
327
419
if self
328
420
. in_scope_parameters
329
421
. iter ( )
330
- . all ( |def_id| explicitly_captured. contains ( def_id) )
422
+ . all ( |( def_id, _ ) | explicitly_captured. contains ( def_id) )
331
423
{
332
424
self . tcx . emit_node_span_lint (
333
425
IMPL_TRAIT_REDUNDANT_CAPTURES ,
@@ -396,7 +488,11 @@ fn extract_def_id_from_arg<'tcx>(
396
488
ty:: ReBound (
397
489
_,
398
490
ty:: BoundRegion { kind : ty:: BoundRegionKind :: BrNamed ( def_id, ..) , .. } ,
399
- ) => def_id,
491
+ )
492
+ | ty:: ReLateParam ( ty:: LateParamRegion {
493
+ scope : _,
494
+ bound_region : ty:: BoundRegionKind :: BrNamed ( def_id, ..) ,
495
+ } ) => def_id,
400
496
_ => unreachable ! ( ) ,
401
497
} ,
402
498
ty:: GenericArgKind :: Type ( ty) => {
@@ -413,3 +509,106 @@ fn extract_def_id_from_arg<'tcx>(
413
509
}
414
510
}
415
511
}
512
+
513
+ /// Computes the variances of regions that appear in the type, but considering
514
+ /// late-bound regions too, which don't have their variance computed usually.
515
+ ///
516
+ /// Like generalization, this is a unary operation implemented on top of the binary
517
+ /// relation infrastructure, mostly because it's much easier to have the relation
518
+ /// track the variance for you, rather than having to do it yourself.
519
+ struct FunctionalVariances < ' tcx > {
520
+ tcx : TyCtxt < ' tcx > ,
521
+ variances : FxHashMap < DefId , ty:: Variance > ,
522
+ ambient_variance : ty:: Variance ,
523
+ generics : & ' tcx ty:: Generics ,
524
+ }
525
+
526
+ impl < ' tcx > TypeRelation < TyCtxt < ' tcx > > for FunctionalVariances < ' tcx > {
527
+ fn cx ( & self ) -> TyCtxt < ' tcx > {
528
+ self . tcx
529
+ }
530
+
531
+ fn relate_with_variance < T : ty:: relate:: Relate < TyCtxt < ' tcx > > > (
532
+ & mut self ,
533
+ variance : rustc_type_ir:: Variance ,
534
+ _: ty:: VarianceDiagInfo < TyCtxt < ' tcx > > ,
535
+ a : T ,
536
+ b : T ,
537
+ ) -> RelateResult < ' tcx , T > {
538
+ let old_variance = self . ambient_variance ;
539
+ self . ambient_variance = self . ambient_variance . xform ( variance) ;
540
+ self . relate ( a, b) . unwrap ( ) ;
541
+ self . ambient_variance = old_variance;
542
+ Ok ( a)
543
+ }
544
+
545
+ fn tys ( & mut self , a : Ty < ' tcx > , b : Ty < ' tcx > ) -> RelateResult < ' tcx , Ty < ' tcx > > {
546
+ structurally_relate_tys ( self , a, b) . unwrap ( ) ;
547
+ Ok ( a)
548
+ }
549
+
550
+ fn regions (
551
+ & mut self ,
552
+ a : ty:: Region < ' tcx > ,
553
+ _: ty:: Region < ' tcx > ,
554
+ ) -> RelateResult < ' tcx , ty:: Region < ' tcx > > {
555
+ let def_id = match * a {
556
+ ty:: ReEarlyParam ( ebr) => self . generics . region_param ( ebr, self . tcx ) . def_id ,
557
+ ty:: ReBound (
558
+ _,
559
+ ty:: BoundRegion { kind : ty:: BoundRegionKind :: BrNamed ( def_id, ..) , .. } ,
560
+ )
561
+ | ty:: ReLateParam ( ty:: LateParamRegion {
562
+ scope : _,
563
+ bound_region : ty:: BoundRegionKind :: BrNamed ( def_id, ..) ,
564
+ } ) => def_id,
565
+ _ => {
566
+ return Ok ( a) ;
567
+ }
568
+ } ;
569
+
570
+ if let Some ( variance) = self . variances . get_mut ( & def_id) {
571
+ * variance = unify ( * variance, self . ambient_variance ) ;
572
+ } else {
573
+ self . variances . insert ( def_id, self . ambient_variance ) ;
574
+ }
575
+
576
+ Ok ( a)
577
+ }
578
+
579
+ fn consts (
580
+ & mut self ,
581
+ a : ty:: Const < ' tcx > ,
582
+ b : ty:: Const < ' tcx > ,
583
+ ) -> RelateResult < ' tcx , ty:: Const < ' tcx > > {
584
+ structurally_relate_consts ( self , a, b) . unwrap ( ) ;
585
+ Ok ( a)
586
+ }
587
+
588
+ fn binders < T > (
589
+ & mut self ,
590
+ a : ty:: Binder < ' tcx , T > ,
591
+ b : ty:: Binder < ' tcx , T > ,
592
+ ) -> RelateResult < ' tcx , ty:: Binder < ' tcx , T > >
593
+ where
594
+ T : Relate < TyCtxt < ' tcx > > ,
595
+ {
596
+ self . relate ( a. skip_binder ( ) , b. skip_binder ( ) ) . unwrap ( ) ;
597
+ Ok ( a)
598
+ }
599
+ }
600
+
601
+ /// What is the variance that satisfies the two variances?
602
+ fn unify ( a : ty:: Variance , b : ty:: Variance ) -> ty:: Variance {
603
+ match ( a, b) {
604
+ // Bivariance is lattice bottom.
605
+ ( ty:: Bivariant , other) | ( other, ty:: Bivariant ) => other,
606
+ // Invariant is lattice top.
607
+ ( ty:: Invariant , _) | ( _, ty:: Invariant ) => ty:: Invariant ,
608
+ // If type is required to be covariant and contravariant, then it's invariant.
609
+ ( ty:: Contravariant , ty:: Covariant ) | ( ty:: Covariant , ty:: Contravariant ) => ty:: Invariant ,
610
+ // Otherwise, co + co = co, contra + contra = contra.
611
+ ( ty:: Contravariant , ty:: Contravariant ) => ty:: Contravariant ,
612
+ ( ty:: Covariant , ty:: Covariant ) => ty:: Covariant ,
613
+ }
614
+ }
0 commit comments