]> git.proxmox.com Git - rustc.git/blame - src/tools/clippy/clippy_lints/src/casts/mod.rs
New upstream version 1.63.0+dfsg1
[rustc.git] / src / tools / clippy / clippy_lints / src / casts / mod.rs
CommitLineData
04454e1e 1mod cast_abs_to_unsigned;
5e7ed085 2mod cast_enum_constructor;
f20569fa
XL
3mod cast_lossless;
4mod cast_possible_truncation;
5mod cast_possible_wrap;
6mod cast_precision_loss;
7mod cast_ptr_alignment;
8mod cast_ref_to_mut;
9mod cast_sign_loss;
5e7ed085 10mod cast_slice_different_sizes;
f20569fa
XL
11mod char_lit_as_u8;
12mod fn_to_numeric_cast;
3c0e092e 13mod fn_to_numeric_cast_any;
f20569fa
XL
14mod fn_to_numeric_cast_with_truncation;
15mod ptr_as_ptr;
16mod unnecessary_cast;
17mod utils;
18
cdc7bbd5 19use clippy_utils::is_hir_ty_cfg_dependant;
f20569fa
XL
20use rustc_hir::{Expr, ExprKind};
21use rustc_lint::{LateContext, LateLintPass, LintContext};
22use rustc_middle::lint::in_external_macro;
23use rustc_semver::RustcVersion;
24use rustc_session::{declare_tool_lint, impl_lint_pass};
25
f20569fa 26declare_clippy_lint! {
94222f64
XL
27 /// ### What it does
28 /// Checks for casts from any numerical to a float type where
f20569fa
XL
29 /// the receiving type cannot store all values from the original type without
30 /// rounding errors. This possible rounding is to be expected, so this lint is
31 /// `Allow` by default.
32 ///
33 /// Basically, this warns on casting any integer with 32 or more bits to `f32`
34 /// or any 64-bit integer to `f64`.
35 ///
94222f64
XL
36 /// ### Why is this bad?
37 /// It's not bad at all. But in some applications it can be
f20569fa
XL
38 /// helpful to know where precision loss can take place. This lint can help find
39 /// those places in the code.
40 ///
94222f64 41 /// ### Example
f20569fa
XL
42 /// ```rust
43 /// let x = u64::MAX;
44 /// x as f64;
45 /// ```
a2a8927a 46 #[clippy::version = "pre 1.29.0"]
f20569fa
XL
47 pub CAST_PRECISION_LOSS,
48 pedantic,
49 "casts that cause loss of precision, e.g., `x as f32` where `x: u64`"
50}
51
52declare_clippy_lint! {
94222f64
XL
53 /// ### What it does
54 /// Checks for casts from a signed to an unsigned numerical
f20569fa
XL
55 /// type. In this case, negative values wrap around to large positive values,
56 /// which can be quite surprising in practice. However, as the cast works as
57 /// defined, this lint is `Allow` by default.
58 ///
94222f64
XL
59 /// ### Why is this bad?
60 /// Possibly surprising results. You can activate this lint
f20569fa
XL
61 /// as a one-time check to see where numerical wrapping can arise.
62 ///
94222f64 63 /// ### Example
f20569fa
XL
64 /// ```rust
65 /// let y: i8 = -1;
66 /// y as u128; // will return 18446744073709551615
67 /// ```
a2a8927a 68 #[clippy::version = "pre 1.29.0"]
f20569fa
XL
69 pub CAST_SIGN_LOSS,
70 pedantic,
71 "casts from signed types to unsigned types, e.g., `x as u32` where `x: i32`"
72}
73
74declare_clippy_lint! {
94222f64
XL
75 /// ### What it does
76 /// Checks for casts between numerical types that may
f20569fa
XL
77 /// truncate large values. This is expected behavior, so the cast is `Allow` by
78 /// default.
79 ///
94222f64
XL
80 /// ### Why is this bad?
81 /// In some problem domains, it is good practice to avoid
f20569fa
XL
82 /// truncation. This lint can be activated to help assess where additional
83 /// checks could be beneficial.
84 ///
94222f64 85 /// ### Example
f20569fa
XL
86 /// ```rust
87 /// fn as_u8(x: u64) -> u8 {
88 /// x as u8
89 /// }
90 /// ```
a2a8927a 91 #[clippy::version = "pre 1.29.0"]
f20569fa
XL
92 pub CAST_POSSIBLE_TRUNCATION,
93 pedantic,
94 "casts that may cause truncation of the value, e.g., `x as u8` where `x: u32`, or `x as i32` where `x: f32`"
95}
96
97declare_clippy_lint! {
94222f64
XL
98 /// ### What it does
99 /// Checks for casts from an unsigned type to a signed type of
f20569fa
XL
100 /// the same size. Performing such a cast is a 'no-op' for the compiler,
101 /// i.e., nothing is changed at the bit level, and the binary representation of
102 /// the value is reinterpreted. This can cause wrapping if the value is too big
103 /// for the target signed type. However, the cast works as defined, so this lint
104 /// is `Allow` by default.
105 ///
94222f64
XL
106 /// ### Why is this bad?
107 /// While such a cast is not bad in itself, the results can
f20569fa
XL
108 /// be surprising when this is not the intended behavior, as demonstrated by the
109 /// example below.
110 ///
94222f64 111 /// ### Example
f20569fa
XL
112 /// ```rust
113 /// u32::MAX as i32; // will yield a value of `-1`
114 /// ```
a2a8927a 115 #[clippy::version = "pre 1.29.0"]
f20569fa
XL
116 pub CAST_POSSIBLE_WRAP,
117 pedantic,
118 "casts that may cause wrapping around the value, e.g., `x as i32` where `x: u32` and `x > i32::MAX`"
119}
120
121declare_clippy_lint! {
94222f64
XL
122 /// ### What it does
123 /// Checks for casts between numerical types that may
f20569fa
XL
124 /// be replaced by safe conversion functions.
125 ///
94222f64
XL
126 /// ### Why is this bad?
127 /// Rust's `as` keyword will perform many kinds of
f20569fa
XL
128 /// conversions, including silently lossy conversions. Conversion functions such
129 /// as `i32::from` will only perform lossless conversions. Using the conversion
130 /// functions prevents conversions from turning into silent lossy conversions if
131 /// the types of the input expressions ever change, and make it easier for
132 /// people reading the code to know that the conversion is lossless.
133 ///
94222f64 134 /// ### Example
f20569fa
XL
135 /// ```rust
136 /// fn as_u64(x: u8) -> u64 {
137 /// x as u64
138 /// }
139 /// ```
140 ///
141 /// Using `::from` would look like this:
142 ///
143 /// ```rust
144 /// fn as_u64(x: u8) -> u64 {
145 /// u64::from(x)
146 /// }
147 /// ```
a2a8927a 148 #[clippy::version = "pre 1.29.0"]
f20569fa
XL
149 pub CAST_LOSSLESS,
150 pedantic,
151 "casts using `as` that are known to be lossless, e.g., `x as u64` where `x: u8`"
152}
153
154declare_clippy_lint! {
94222f64
XL
155 /// ### What it does
156 /// Checks for casts to the same type, casts of int literals to integer types
f20569fa
XL
157 /// and casts of float literals to float types.
158 ///
94222f64
XL
159 /// ### Why is this bad?
160 /// It's just unnecessary.
f20569fa 161 ///
94222f64 162 /// ### Example
f20569fa
XL
163 /// ```rust
164 /// let _ = 2i32 as i32;
165 /// let _ = 0.5 as f32;
166 /// ```
167 ///
168 /// Better:
169 ///
170 /// ```rust
171 /// let _ = 2_i32;
172 /// let _ = 0.5_f32;
173 /// ```
a2a8927a 174 #[clippy::version = "pre 1.29.0"]
f20569fa
XL
175 pub UNNECESSARY_CAST,
176 complexity,
177 "cast to the same type, e.g., `x as i32` where `x: i32`"
178}
179
180declare_clippy_lint! {
94222f64
XL
181 /// ### What it does
182 /// Checks for casts, using `as` or `pointer::cast`,
f20569fa
XL
183 /// from a less-strictly-aligned pointer to a more-strictly-aligned pointer
184 ///
94222f64
XL
185 /// ### Why is this bad?
186 /// Dereferencing the resulting pointer may be undefined
f20569fa
XL
187 /// behavior.
188 ///
94222f64
XL
189 /// ### Known problems
190 /// Using `std::ptr::read_unaligned` and `std::ptr::write_unaligned` or similar
f20569fa
XL
191 /// on the resulting pointer is fine. Is over-zealous: Casts with manual alignment checks or casts like
192 /// u64-> u8 -> u16 can be fine. Miri is able to do a more in-depth analysis.
193 ///
94222f64 194 /// ### Example
f20569fa
XL
195 /// ```rust
196 /// let _ = (&1u8 as *const u8) as *const u16;
197 /// let _ = (&mut 1u8 as *mut u8) as *mut u16;
198 ///
199 /// (&1u8 as *const u8).cast::<u16>();
200 /// (&mut 1u8 as *mut u8).cast::<u16>();
201 /// ```
a2a8927a 202 #[clippy::version = "pre 1.29.0"]
f20569fa
XL
203 pub CAST_PTR_ALIGNMENT,
204 pedantic,
205 "cast from a pointer to a more-strictly-aligned pointer"
206}
207
208declare_clippy_lint! {
94222f64
XL
209 /// ### What it does
210 /// Checks for casts of function pointers to something other than usize
f20569fa 211 ///
94222f64 212 /// ### Why is this bad?
f20569fa
XL
213 /// Casting a function pointer to anything other than usize/isize is not portable across
214 /// architectures, because you end up losing bits if the target type is too small or end up with a
215 /// bunch of extra bits that waste space and add more instructions to the final binary than
216 /// strictly necessary for the problem
217 ///
218 /// Casting to isize also doesn't make sense since there are no signed addresses.
219 ///
94222f64 220 /// ### Example
f20569fa 221 /// ```rust
f20569fa 222 /// fn fun() -> i32 { 1 }
923072b8
FG
223 /// let _ = fun as i64;
224 /// ```
f20569fa 225 ///
923072b8
FG
226 /// Use instead:
227 /// ```rust
228 /// # fn fun() -> i32 { 1 }
229 /// let _ = fun as usize;
f20569fa 230 /// ```
a2a8927a 231 #[clippy::version = "pre 1.29.0"]
f20569fa
XL
232 pub FN_TO_NUMERIC_CAST,
233 style,
234 "casting a function pointer to a numeric type other than usize"
235}
236
237declare_clippy_lint! {
94222f64
XL
238 /// ### What it does
239 /// Checks for casts of a function pointer to a numeric type not wide enough to
f20569fa
XL
240 /// store address.
241 ///
94222f64 242 /// ### Why is this bad?
f20569fa
XL
243 /// Such a cast discards some bits of the function's address. If this is intended, it would be more
244 /// clearly expressed by casting to usize first, then casting the usize to the intended type (with
245 /// a comment) to perform the truncation.
246 ///
94222f64 247 /// ### Example
f20569fa 248 /// ```rust
f20569fa
XL
249 /// fn fn1() -> i16 {
250 /// 1
251 /// };
252 /// let _ = fn1 as i32;
923072b8 253 /// ```
f20569fa 254 ///
923072b8
FG
255 /// Use instead:
256 /// ```rust
257 /// // Cast to usize first, then comment with the reason for the truncation
258 /// fn fn1() -> i16 {
f20569fa
XL
259 /// 1
260 /// };
923072b8 261 /// let fn_ptr = fn1 as usize;
f20569fa
XL
262 /// let fn_ptr_truncated = fn_ptr as i32;
263 /// ```
a2a8927a 264 #[clippy::version = "pre 1.29.0"]
f20569fa
XL
265 pub FN_TO_NUMERIC_CAST_WITH_TRUNCATION,
266 style,
267 "casting a function pointer to a numeric type not wide enough to store the address"
268}
269
3c0e092e
XL
270declare_clippy_lint! {
271 /// ### What it does
272 /// Checks for casts of a function pointer to any integer type.
273 ///
274 /// ### Why is this bad?
275 /// Casting a function pointer to an integer can have surprising results and can occur
04454e1e 276 /// accidentally if parentheses are omitted from a function call. If you aren't doing anything
3c0e092e
XL
277 /// low-level with function pointers then you can opt-out of casting functions to integers in
278 /// order to avoid mistakes. Alternatively, you can use this lint to audit all uses of function
279 /// pointer casts in your code.
280 ///
281 /// ### Example
282 /// ```rust
923072b8 283 /// // fn1 is cast as `usize`
3c0e092e
XL
284 /// fn fn1() -> u16 {
285 /// 1
286 /// };
287 /// let _ = fn1 as usize;
923072b8 288 /// ```
3c0e092e 289 ///
923072b8
FG
290 /// Use instead:
291 /// ```rust
292 /// // maybe you intended to call the function?
3c0e092e
XL
293 /// fn fn2() -> u16 {
294 /// 1
295 /// };
296 /// let _ = fn2() as usize;
297 ///
923072b8
FG
298 /// // or
299 ///
300 /// // maybe you intended to cast it to a function type?
3c0e092e
XL
301 /// fn fn3() -> u16 {
302 /// 1
303 /// }
304 /// let _ = fn3 as fn() -> u16;
305 /// ```
a2a8927a 306 #[clippy::version = "1.58.0"]
3c0e092e
XL
307 pub FN_TO_NUMERIC_CAST_ANY,
308 restriction,
309 "casting a function pointer to any integer type"
310}
311
f20569fa 312declare_clippy_lint! {
94222f64
XL
313 /// ### What it does
314 /// Checks for casts of `&T` to `&mut T` anywhere in the code.
f20569fa 315 ///
94222f64 316 /// ### Why is this bad?
923072b8 317 /// It’s basically guaranteed to be undefined behavior.
f20569fa
XL
318 /// `UnsafeCell` is the only way to obtain aliasable data that is considered
319 /// mutable.
320 ///
94222f64 321 /// ### Example
f20569fa
XL
322 /// ```rust,ignore
323 /// fn x(r: &i32) {
324 /// unsafe {
325 /// *(r as *const _ as *mut _) += 1;
326 /// }
327 /// }
328 /// ```
329 ///
330 /// Instead consider using interior mutability types.
331 ///
332 /// ```rust
333 /// use std::cell::UnsafeCell;
334 ///
335 /// fn x(r: &UnsafeCell<i32>) {
336 /// unsafe {
337 /// *r.get() += 1;
338 /// }
339 /// }
340 /// ```
a2a8927a 341 #[clippy::version = "1.33.0"]
f20569fa
XL
342 pub CAST_REF_TO_MUT,
343 correctness,
344 "a cast of reference to a mutable pointer"
345}
346
347declare_clippy_lint! {
94222f64
XL
348 /// ### What it does
349 /// Checks for expressions where a character literal is cast
f20569fa
XL
350 /// to `u8` and suggests using a byte literal instead.
351 ///
94222f64
XL
352 /// ### Why is this bad?
353 /// In general, casting values to smaller types is
f20569fa
XL
354 /// error-prone and should be avoided where possible. In the particular case of
355 /// converting a character literal to u8, it is easy to avoid by just using a
356 /// byte literal instead. As an added bonus, `b'a'` is even slightly shorter
357 /// than `'a' as u8`.
358 ///
94222f64 359 /// ### Example
f20569fa
XL
360 /// ```rust,ignore
361 /// 'x' as u8
362 /// ```
363 ///
364 /// A better version, using the byte literal:
365 ///
366 /// ```rust,ignore
367 /// b'x'
368 /// ```
a2a8927a 369 #[clippy::version = "pre 1.29.0"]
f20569fa
XL
370 pub CHAR_LIT_AS_U8,
371 complexity,
372 "casting a character literal to `u8` truncates"
373}
374
375declare_clippy_lint! {
94222f64 376 /// ### What it does
f20569fa
XL
377 /// Checks for `as` casts between raw pointers without changing its mutability,
378 /// namely `*const T` to `*const U` and `*mut T` to `*mut U`.
379 ///
94222f64 380 /// ### Why is this bad?
f20569fa
XL
381 /// Though `as` casts between raw pointers is not terrible, `pointer::cast` is safer because
382 /// it cannot accidentally change the pointer's mutability nor cast the pointer to other types like `usize`.
383 ///
94222f64 384 /// ### Example
f20569fa
XL
385 /// ```rust
386 /// let ptr: *const u32 = &42_u32;
387 /// let mut_ptr: *mut u32 = &mut 42_u32;
388 /// let _ = ptr as *const i32;
389 /// let _ = mut_ptr as *mut i32;
390 /// ```
391 /// Use instead:
392 /// ```rust
393 /// let ptr: *const u32 = &42_u32;
394 /// let mut_ptr: *mut u32 = &mut 42_u32;
395 /// let _ = ptr.cast::<i32>();
396 /// let _ = mut_ptr.cast::<i32>();
397 /// ```
a2a8927a 398 #[clippy::version = "1.51.0"]
f20569fa
XL
399 pub PTR_AS_PTR,
400 pedantic,
401 "casting using `as` from and to raw pointers that doesn't change its mutability, where `pointer::cast` could take the place of `as`"
402}
403
5e7ed085
FG
404declare_clippy_lint! {
405 /// ### What it does
406 /// Checks for casts from an enum type to an integral type which will definitely truncate the
407 /// value.
408 ///
409 /// ### Why is this bad?
410 /// The resulting integral value will not match the value of the variant it came from.
411 ///
412 /// ### Example
413 /// ```rust
414 /// enum E { X = 256 };
415 /// let _ = E::X as u8;
416 /// ```
923072b8 417 #[clippy::version = "1.61.0"]
5e7ed085
FG
418 pub CAST_ENUM_TRUNCATION,
419 suspicious,
420 "casts from an enum type to an integral type which will truncate the value"
421}
422
423declare_clippy_lint! {
923072b8 424 /// ### What it does
5e7ed085
FG
425 /// Checks for `as` casts between raw pointers to slices with differently sized elements.
426 ///
427 /// ### Why is this bad?
428 /// The produced raw pointer to a slice does not update its length metadata. The produced
429 /// pointer will point to a different number of bytes than the original pointer because the
430 /// length metadata of a raw slice pointer is in elements rather than bytes.
431 /// Producing a slice reference from the raw pointer will either create a slice with
432 /// less data (which can be surprising) or create a slice with more data and cause Undefined Behavior.
433 ///
434 /// ### Example
435 /// // Missing data
436 /// ```rust
437 /// let a = [1_i32, 2, 3, 4];
438 /// let p = &a as *const [i32] as *const [u8];
439 /// unsafe {
440 /// println!("{:?}", &*p);
441 /// }
442 /// ```
443 /// // Undefined Behavior (note: also potential alignment issues)
444 /// ```rust
445 /// let a = [1_u8, 2, 3, 4];
446 /// let p = &a as *const [u8] as *const [u32];
447 /// unsafe {
448 /// println!("{:?}", &*p);
449 /// }
450 /// ```
451 /// Instead use `ptr::slice_from_raw_parts` to construct a slice from a data pointer and the correct length
452 /// ```rust
453 /// let a = [1_i32, 2, 3, 4];
454 /// let old_ptr = &a as *const [i32];
455 /// // The data pointer is cast to a pointer to the target `u8` not `[u8]`
456 /// // The length comes from the known length of 4 i32s times the 4 bytes per i32
457 /// let new_ptr = core::ptr::slice_from_raw_parts(old_ptr as *const u8, 16);
458 /// unsafe {
459 /// println!("{:?}", &*new_ptr);
460 /// }
461 /// ```
923072b8 462 #[clippy::version = "1.61.0"]
5e7ed085
FG
463 pub CAST_SLICE_DIFFERENT_SIZES,
464 correctness,
465 "casting using `as` between raw pointers to slices of types with different sizes"
466}
467
468declare_clippy_lint! {
469 /// ### What it does
470 /// Checks for casts from an enum tuple constructor to an integer.
471 ///
472 /// ### Why is this bad?
473 /// The cast is easily confused with casting a c-like enum value to an integer.
474 ///
475 /// ### Example
476 /// ```rust
477 /// enum E { X(i32) };
478 /// let _ = E::X as usize;
479 /// ```
480 #[clippy::version = "1.61.0"]
481 pub CAST_ENUM_CONSTRUCTOR,
482 suspicious,
483 "casts from an enum tuple constructor to an integer"
484}
485
04454e1e
FG
486declare_clippy_lint! {
487 /// ### What it does
488 /// Checks for uses of the `abs()` method that cast the result to unsigned.
489 ///
490 /// ### Why is this bad?
491 /// The `unsigned_abs()` method avoids panic when called on the MIN value.
492 ///
493 /// ### Example
494 /// ```rust
495 /// let x: i32 = -42;
496 /// let y: u32 = x.abs() as u32;
497 /// ```
498 /// Use instead:
499 /// ```rust
500 /// let x: i32 = -42;
501 /// let y: u32 = x.unsigned_abs();
502 /// ```
503 #[clippy::version = "1.61.0"]
504 pub CAST_ABS_TO_UNSIGNED,
505 suspicious,
506 "casting the result of `abs()` to an unsigned integer can panic"
507}
508
f20569fa
XL
509pub struct Casts {
510 msrv: Option<RustcVersion>,
511}
512
513impl Casts {
514 #[must_use]
515 pub fn new(msrv: Option<RustcVersion>) -> Self {
516 Self { msrv }
517 }
518}
519
520impl_lint_pass!(Casts => [
521 CAST_PRECISION_LOSS,
522 CAST_SIGN_LOSS,
523 CAST_POSSIBLE_TRUNCATION,
524 CAST_POSSIBLE_WRAP,
525 CAST_LOSSLESS,
526 CAST_REF_TO_MUT,
527 CAST_PTR_ALIGNMENT,
5e7ed085 528 CAST_SLICE_DIFFERENT_SIZES,
f20569fa 529 UNNECESSARY_CAST,
3c0e092e 530 FN_TO_NUMERIC_CAST_ANY,
f20569fa
XL
531 FN_TO_NUMERIC_CAST,
532 FN_TO_NUMERIC_CAST_WITH_TRUNCATION,
533 CHAR_LIT_AS_U8,
534 PTR_AS_PTR,
5e7ed085 535 CAST_ENUM_TRUNCATION,
04454e1e
FG
536 CAST_ENUM_CONSTRUCTOR,
537 CAST_ABS_TO_UNSIGNED
f20569fa
XL
538]);
539
540impl<'tcx> LateLintPass<'tcx> for Casts {
541 fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
5e7ed085 542 if !in_external_macro(cx.sess(), expr.span) {
923072b8 543 ptr_as_ptr::check(cx, expr, self.msrv);
5e7ed085
FG
544 }
545
f20569fa
XL
546 if expr.span.from_expansion() {
547 return;
548 }
549
cdc7bbd5 550 if let ExprKind::Cast(cast_expr, cast_to) = expr.kind {
f20569fa
XL
551 if is_hir_ty_cfg_dependant(cx, cast_to) {
552 return;
553 }
554 let (cast_from, cast_to) = (
555 cx.typeck_results().expr_ty(cast_expr),
556 cx.typeck_results().expr_ty(expr),
557 );
558
559 if unnecessary_cast::check(cx, expr, cast_expr, cast_from, cast_to) {
560 return;
561 }
562
3c0e092e 563 fn_to_numeric_cast_any::check(cx, expr, cast_expr, cast_from, cast_to);
f20569fa
XL
564 fn_to_numeric_cast::check(cx, expr, cast_expr, cast_from, cast_to);
565 fn_to_numeric_cast_with_truncation::check(cx, expr, cast_expr, cast_from, cast_to);
a2a8927a
XL
566
567 if cast_to.is_numeric() && !in_external_macro(cx.sess(), expr.span) {
5e7ed085 568 cast_possible_truncation::check(cx, expr, cast_expr, cast_from, cast_to);
a2a8927a 569 if cast_from.is_numeric() {
a2a8927a
XL
570 cast_possible_wrap::check(cx, expr, cast_from, cast_to);
571 cast_precision_loss::check(cx, expr, cast_from, cast_to);
572 cast_sign_loss::check(cx, expr, cast_expr, cast_from, cast_to);
923072b8 573 cast_abs_to_unsigned::check(cx, expr, cast_expr, cast_from, cast_to, self.msrv);
a2a8927a 574 }
923072b8 575 cast_lossless::check(cx, expr, cast_expr, cast_from, cast_to, self.msrv);
5e7ed085 576 cast_enum_constructor::check(cx, expr, cast_expr, cast_from);
f20569fa
XL
577 }
578 }
579
580 cast_ref_to_mut::check(cx, expr);
581 cast_ptr_alignment::check(cx, expr);
582 char_lit_as_u8::check(cx, expr);
923072b8
FG
583 ptr_as_ptr::check(cx, expr, self.msrv);
584 cast_slice_different_sizes::check(cx, expr, self.msrv);
f20569fa
XL
585 }
586
587 extract_msrv_attr!(LateContext);
588}