From fd1cecdfe0dc8025b1d5773f4b0592288af600a0 Mon Sep 17 00:00:00 2001 From: y86-dev Date: Wed, 19 Jul 2023 10:55:10 +0200 Subject: [PATCH] kernel update quality-of-life-v2 --- examples/big_struct_in_place.rs | 3 +- src/__internal.rs | 44 +++-- src/lib.rs | 176 +++++++++++++++++--- src/macros.rs | 119 +++++++------ tests/ring_buf.rs | 18 +- tests/ui/no_error_coercion.stderr | 2 +- tests/ui/no_pin_data_but_pinned_drop.stderr | 4 +- tests/ui/try_access_guard.stderr | 28 +--- 8 files changed, 261 insertions(+), 133 deletions(-) diff --git a/examples/big_struct_in_place.rs b/examples/big_struct_in_place.rs index b6e343d..a915f2b 100644 --- a/examples/big_struct_in_place.rs +++ b/examples/big_struct_in_place.rs @@ -1,7 +1,8 @@ #![feature(allocator_api)] -use core::{alloc::AllocError, convert::Infallible}; +use core::alloc::AllocError; use pinned_init::*; +use std::convert::Infallible; // Struct with size over 1GiB #[derive(Debug)] diff --git a/src/__internal.rs b/src/__internal.rs index 3d3ddd0..c4da14d 100644 --- a/src/__internal.rs +++ b/src/__internal.rs @@ -9,13 +9,14 @@ use super::*; -pub use pinned_init_macro::retokenize; +#[doc(hidden)] +pub use paste::paste; /// See the [nomicon] for what subtyping is. See also [this table]. /// /// [nomicon]: https://doc.rust-lang.org/nomicon/subtyping.html /// [this table]: https://doc.rust-lang.org/nomicon/phantom-data.html#table-of-phantomdata-patterns -type Invariant = PhantomData *mut T>; +pub(super) type Invariant = PhantomData *mut T>; /// This is the module-internal type implementing `PinInit` and `Init`. It is unsafe to create this /// type, since the closure needs to fulfill the same safety requirement as the @@ -34,6 +35,18 @@ where } } +// SAFETY: While constructing the `InitClosure`, the user promised that it upholds the +// `__pinned_init` invariants. +unsafe impl PinInit for InitClosure +where + F: FnOnce(*mut T) -> Result<(), E>, +{ + #[inline] + unsafe fn __pinned_init(self, slot: *mut T) -> Result<(), E> { + (self.0)(slot) + } +} + /// This trait is only implemented via the `#[pin_data]` proc-macro. It is used to facilitate /// the pin projections within the initializers. /// @@ -92,7 +105,7 @@ pub unsafe trait InitData: Copy { } } -pub struct AllData(PhantomData *const T>); +pub struct AllData(PhantomData) -> Box>); impl Clone for AllData { #[cfg_attr(coverage_nightly, no_coverage)] @@ -202,7 +215,6 @@ fn stack_init_reuse() { /// Can be forgotton to prevent the drop. pub struct DropGuard { ptr: *mut T, - do_drop: Cell, } impl DropGuard { @@ -218,32 +230,16 @@ impl DropGuard { /// - will not be dropped by any other means. #[inline] pub unsafe fn new(ptr: *mut T) -> Self { - Self { - ptr, - do_drop: Cell::new(true), - } - } - - /// Prevents this guard from dropping the supplied pointer. - /// - /// # Safety - /// - /// This function is unsafe in order to prevent safe code from forgetting this guard. It should - /// only be called by the macros in this module. - #[inline] - pub unsafe fn forget(&self) { - self.do_drop.set(false); + Self { ptr } } } impl Drop for DropGuard { #[inline] fn drop(&mut self) { - if self.do_drop.get() { - // SAFETY: A `DropGuard` can only be constructed using the unsafe `new` function - // ensuring that this operation is safe. - unsafe { ptr::drop_in_place(self.ptr) } - } + // SAFETY: A `DropGuard` can only be constructed using the unsafe `new` function + // ensuring that this operation is safe. + unsafe { ptr::drop_in_place(self.ptr) } } } diff --git a/src/lib.rs b/src/lib.rs index 6fa087f..936e6c4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -259,7 +259,6 @@ extern crate alloc; use alloc::{boxed::Box, sync::Arc}; use core::{ alloc::AllocError, - cell::Cell, convert::Infallible, marker::PhantomData, mem::MaybeUninit, @@ -403,9 +402,7 @@ macro_rules! stack_try_pin_init { (let $var:ident $(: $t:ty)? = $val:expr) => { let val = $val; let mut $var = ::core::pin::pin!($crate::__internal::StackInit$(::<$t>)?::uninit()); - let mut $var = { - $crate::__internal::StackInit::init($var, val) - }; + let mut $var = $crate::__internal::StackInit::init($var, val); }; (let $var:ident $(: $t:ty)? =? $val:expr) => { let val = $val; @@ -857,6 +854,79 @@ pub unsafe trait PinInit: Sized { /// deallocate. /// - `slot` will not move until it is dropped, i.e. it will be pinned. unsafe fn __pinned_init(self, slot: *mut T) -> Result<(), E>; + + /// First initializes the value using `self` then calls the function `f` with the initialized + /// value. + /// + /// # Examples + /// + /// ```rust + /// # #![allow(clippy::disallowed_names)] + /// use pinned_init::*; + /// use core::{mem::MaybeUninit, pin::Pin, convert::Infallible}; + /// #[repr(C)] + /// struct RawFoo([u8; 16]); + /// extern { + /// fn init_foo(_: *mut RawFoo); + /// } + /// + /// #[pin_data] + /// struct Foo { + /// #[pin] + /// raw: MaybeUninit, + /// } + /// + /// impl Foo { + /// fn setup(self: Pin<&mut Self>) { + /// println!("Setting up foo"); + /// } + /// } + /// + /// let foo = pin_init!(Foo { + /// raw <- unsafe { + /// pin_init_from_closure(|slot: *mut MaybeUninit| { + /// init_foo(slot.cast::()); + /// Ok::<_, Infallible>(()) + /// }) + /// }, + /// }).pin_chain(|foo| { + /// foo.setup(); + /// Ok(()) + /// }); + /// ``` + fn pin_chain(self, f: F) -> ChainPinInit + where + F: FnOnce(Pin<&mut T>) -> Result<(), E>, + { + ChainPinInit(self, f, PhantomData) + } +} + +/// An initializer returned by [`PinInit::pin_chain`]. +pub struct ChainPinInit(I, F, __internal::Invariant<(E, Box)>); + +// SAFETY: the `__pinned_init` function is implemented such that it +// - returns `Ok(())` on successful initialization, +// - returns `Err(err)` on error and in this case `slot` will be dropped. +// - considers `slot` pinned. +unsafe impl PinInit for ChainPinInit +where + I: PinInit, + F: FnOnce(Pin<&mut T>) -> Result<(), E>, +{ + unsafe fn __pinned_init(self, slot: *mut T) -> Result<(), E> { + // SAFETY: all requirements fulfilled since this function is `__pinned_init`. + unsafe { self.0.__pinned_init(slot)? }; + // SAFETY: The above call initialized `slot` and we still have unique access. + let val = unsafe { &mut *slot }; + // SAFETY: `slot` is considered pinned + let val = unsafe { Pin::new_unchecked(val) }; + (self.1)(val).map_err(|e| { + // SAFETY: `slot` was initialized above. + unsafe { core::ptr::drop_in_place(slot) }; + e + }) + } } /// An initializer for `T`. @@ -889,7 +959,7 @@ pub unsafe trait PinInit: Sized { /// /// [`Arc`]: alloc::sync::Arc #[must_use = "An initializer must be used in order to create its value."] -pub unsafe trait Init: Sized { +pub unsafe trait Init: PinInit { /// Initializes `slot`. /// /// # Safety @@ -898,16 +968,72 @@ pub unsafe trait Init: Sized { /// - the caller does not touch `slot` when `Err` is returned, they are only permitted to /// deallocate. unsafe fn __init(self, slot: *mut T) -> Result<(), E>; + + /// First initializes the value using `self` then calls the function `f` with the initialized + /// value. + /// + /// # Examples + /// + /// ```rust + /// # #![allow(clippy::disallowed_names)] + /// use pinned_init::*; + /// use core::convert::Infallible; + /// struct Foo { + /// buf: [u8; 1_000_000], + /// } + /// + /// impl Foo { + /// fn setup(&mut self) { + /// println!("Setting up foo"); + /// } + /// } + /// + /// let foo = init!(Foo { + /// buf <- zeroed::<_, Infallible>() + /// }).chain(|foo| { + /// foo.setup(); + /// Ok(()) + /// }); + /// ``` + fn chain(self, f: F) -> ChainInit + where + F: FnOnce(&mut T) -> Result<(), E>, + { + ChainInit(self, f, PhantomData) + } } -// SAFETY: Every in-place initializer can also be used as a pin-initializer. -unsafe impl PinInit for I +/// An initializer returned by [`Init::chain`]. +pub struct ChainInit(I, F, __internal::Invariant<(E, Box)>); + +// SAFETY: the `__init` function is implemented such that it +// - returns `Ok(())` on successful initialization, +// - returns `Err(err)` on error and in this case `slot` will be dropped. +unsafe impl Init for ChainInit where I: Init, + F: FnOnce(&mut T) -> Result<(), E>, +{ + unsafe fn __init(self, slot: *mut T) -> Result<(), E> { + // SAFETY: all requirements fulfilled since this function is `__init`. + unsafe { self.0.__pinned_init(slot)? }; + // SAFETY: The above call initialized `slot` and we still have unique access. + (self.1)(unsafe { &mut *slot }).map_err(|e| { + // SAFETY: `slot` was initialized above. + unsafe { core::ptr::drop_in_place(slot) }; + e + }) + } +} + +// SAFETY: `__pinned_init` behaves exactly the same as `__init`. +unsafe impl PinInit for ChainInit +where + I: Init, + F: FnOnce(&mut T) -> Result<(), E>, { unsafe fn __pinned_init(self, slot: *mut T) -> Result<(), E> { - // SAFETY: `__init` meets the same requirements as `__pinned_init`, except that it does not - // require `slot` to not move after init. + // SAFETY: `__init` has less strict requirements compared to `__pinned_init`. unsafe { self.__init(slot) } } } @@ -986,13 +1112,10 @@ where Ok(()) => {} Err(e) => { // We now free every element that has been initialized before: - for j in 0..i { - let ptr = unsafe { slot.add(j) }; - // SAFETY: The value was initialized in a previous iteration of the loop - // and since we return `Err` below, the caller will consider the memory at - // `slot` as uninitialized. - unsafe { ptr::drop_in_place(ptr) }; - } + // SAFETY: The loop initialized exactly the values from 0..i and since we + // return `Err` below, the caller will consider the memory at `slot` as + // uninitialized. + unsafe { ptr::drop_in_place(ptr::slice_from_raw_parts_mut(slot, i)) }; return Err(e); } } @@ -1040,13 +1163,10 @@ where Err(e) => { // We now have to free every element that has been initialized before, since we // have to abide by the drop guarantee. - for j in 0..i { - let ptr = unsafe { slot.add(j) }; - // SAFETY: The value was initialized in a previous iteration of the loop - // and since we return `Err` below, the caller will consider the memory at - // `slot` as uninitialized. - unsafe { ptr::drop_in_place(ptr) }; - } + // SAFETY: The loop initialized exactly the values from 0..i and since we + // return `Err` below, the caller will consider the memory at `slot` as + // uninitialized. + unsafe { ptr::drop_in_place(ptr::slice_from_raw_parts_mut(slot, i)) }; return Err(e); } } @@ -1059,12 +1179,18 @@ where } // SAFETY: Every type can be initialized by-value. -unsafe impl Init for T { - unsafe fn __init(self, slot: *mut T) -> Result<(), Infallible> { +unsafe impl Init for T { + unsafe fn __init(self, slot: *mut T) -> Result<(), E> { unsafe { slot.write(self) }; Ok(()) } } +// SAFETY: Every type can be initialized by-value. `__pinned_init` calls `__init`. +unsafe impl PinInit for T { + unsafe fn __pinned_init(self, slot: *mut T) -> Result<(), E> { + unsafe { self.__init(slot) } + } +} /// Smart pointer that can initialize memory in-place. pub trait InPlaceInit: Sized { diff --git a/src/macros.rs b/src/macros.rs index 7aaf86f..dd63077 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -1,10 +1,12 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT //! This module provides the macros that actually implement the proc-macros `pin_data` and -//! `pinned_drop`. +//! `pinned_drop`. It also contains `__init_internal` the implementation of the `{try_}{pin_}init!` +//! macros. //! //! These macros should never be called directly, since they expect their input to be -//! in a certain format which is internal. Use the proc-macros instead. +//! in a certain format which is internal. If used incorrectly, these macros can lead to UB even in +//! safe code! Use the public facing macros instead. //! //! This architecture has been chosen because the kernel does not yet have access to `syn` which //! would make matters a lot easier for implementing these as proc-macros. @@ -1108,7 +1110,7 @@ macro_rules! __init_internal { // Get the data about fields from the supplied type. let data = unsafe { use $crate::__internal::$has_data; - $crate::__internal::retokenize!($t::$get_data()) + $crate::__internal::paste!($t::$get_data()) }; // Ensure that `data` really is of type `$data` and help with type inference: let init = $crate::__internal::$data::make_closure::<_, __InitOk, $err>( @@ -1121,7 +1123,7 @@ macro_rules! __init_internal { // error when fields are missing (since they will be zeroed). We also have to // check that the type actually implements `Zeroable`. $({ - fn assert_zeroable(ptr: *mut T) {} + fn assert_zeroable(_: *mut T) {} // Ensure that the struct is indeed `Zeroable`. assert_zeroable(slot); // SAFETY: The type implements `Zeroable` by the check above. @@ -1177,28 +1179,30 @@ macro_rules! __init_internal { // In-place initialization syntax. @munch_fields($field:ident <- $val:expr, $($rest:tt)*), ) => { - let $field = $val; + let init = $val; // Call the initializer. // // SAFETY: `slot` is valid, because we are inside of an initializer closure, we // return when an error/panic occurs. // We also use the `data` to require the correct trait (`Init` or `PinInit`) for `$field`. - unsafe { $data.$field(::core::ptr::addr_of_mut!((*$slot).$field), $field)? }; + unsafe { $data.$field(::core::ptr::addr_of_mut!((*$slot).$field), init)? }; // Create the drop guard: // // We rely on macro hygiene to make it impossible for users to access this local variable. - // - // SAFETY: We forget the guard later when initialization has succeeded. - let guard = unsafe { - $crate::__internal::DropGuard::new(::core::ptr::addr_of_mut!((*$slot).$field)) - }; + // We use `paste!` to create new hygiene for $field. + $crate::__internal::paste! { + // SAFETY: We forget the guard later when initialization has succeeded. + let [<$field>] = unsafe { + $crate::__internal::DropGuard::new(::core::ptr::addr_of_mut!((*$slot).$field)) + }; - $crate::__init_internal!(init_slot($use_data): - @data($data), - @slot($slot), - @guards(guard, $($guards,)*), - @munch_fields($($rest)*), - ); + $crate::__init_internal!(init_slot($use_data): + @data($data), + @slot($slot), + @guards([<$field>], $($guards,)*), + @munch_fields($($rest)*), + ); + } }; (init_slot(): // no use_data, so we use `Init::__init` directly. @data($data:ident), @@ -1207,27 +1211,29 @@ macro_rules! __init_internal { // In-place initialization syntax. @munch_fields($field:ident <- $val:expr, $($rest:tt)*), ) => { - let $field = $val; + let init = $val; // Call the initializer. // // SAFETY: `slot` is valid, because we are inside of an initializer closure, we // return when an error/panic occurs. - unsafe { $crate::Init::__init($field, ::core::ptr::addr_of_mut!((*$slot).$field))? }; + unsafe { $crate::Init::__init(init, ::core::ptr::addr_of_mut!((*$slot).$field))? }; // Create the drop guard: // // We rely on macro hygiene to make it impossible for users to access this local variable. - // - // SAFETY: We forget the guard later when initialization has succeeded. - let guard = unsafe { - $crate::__internal::DropGuard::new(::core::ptr::addr_of_mut!((*$slot).$field)) - }; + // We use `paste!` to create new hygiene for $field. + $crate::__internal::paste! { + // SAFETY: We forget the guard later when initialization has succeeded. + let [<$field>] = unsafe { + $crate::__internal::DropGuard::new(::core::ptr::addr_of_mut!((*$slot).$field)) + }; - $crate::__init_internal!(init_slot(): - @data($data), - @slot($slot), - @guards(guard, $($guards,)*), - @munch_fields($($rest)*), - ); + $crate::__init_internal!(init_slot(): + @data($data), + @slot($slot), + @guards([<$field>], $($guards,)*), + @munch_fields($($rest)*), + ); + } }; (init_slot($($use_data:ident)?): @data($data:ident), @@ -1236,26 +1242,30 @@ macro_rules! __init_internal { // Init by-value. @munch_fields($field:ident $(: $val:expr)?, $($rest:tt)*), ) => { - $(let $field = $val;)? - // Initialize the field. - // - // SAFETY: The memory at `slot` is uninitialized. - unsafe { ::core::ptr::write(::core::ptr::addr_of_mut!((*$slot).$field), $field) }; + { + $(let $field = $val;)? + // Initialize the field. + // + // SAFETY: The memory at `slot` is uninitialized. + unsafe { ::core::ptr::write(::core::ptr::addr_of_mut!((*$slot).$field), $field) }; + } // Create the drop guard: // // We rely on macro hygiene to make it impossible for users to access this local variable. - // - // SAFETY: We forget the guard later when initialization has succeeded. - let guard = unsafe { - $crate::__internal::DropGuard::new(::core::ptr::addr_of_mut!((*$slot).$field)) - }; + // We use `paste!` to create new hygiene for $field. + $crate::__internal::paste! { + // SAFETY: We forget the guard later when initialization has succeeded. + let [<$field>] = unsafe { + $crate::__internal::DropGuard::new(::core::ptr::addr_of_mut!((*$slot).$field)) + }; - $crate::__init_internal!(init_slot($($use_data)?): - @data($data), - @slot($slot), - @guards(guard, $($guards,)*), - @munch_fields($($rest)*), - ); + $crate::__init_internal!(init_slot($($use_data)?): + @data($data), + @slot($slot), + @guards([<$field>], $($guards,)*), + @munch_fields($($rest)*), + ); + } }; (make_initializer: @slot($slot:ident), @@ -1269,13 +1279,18 @@ macro_rules! __init_internal { // actually accessible by using the struct update syntax ourselves. // Since we are in the `if false` branch, this will never get executed. We abuse `slot` to // get the correct type inference here: + #[allow(unused_assignments)] unsafe { let mut zeroed = ::core::mem::zeroed(); // We have to use type inference here to make zeroed have the correct type. This does // not get executed, so it has no effect. ::core::ptr::write($slot, zeroed); zeroed = ::core::mem::zeroed(); - $crate::__internal::retokenize!( + // Here we abuse `paste!` to retokenize `$t`. Declarative macros have some internal + // information that is associated to already parsed fragments, so a path fragment + // cannot be used in this position. Doing the retokenization results in valid rust + // code. + $crate::__internal::paste!( ::core::ptr::write($slot, $t { $($acc)* ..zeroed @@ -1290,10 +1305,14 @@ macro_rules! __init_internal { @acc($($acc:tt)*), ) => { // Endpoint, nothing more to munch, create the initializer. - // Since we are in the `if false` branch, this will never get executed. We abuse `slot` to - // get the correct type inference here: + // Since we are in the closure that is never called, this will never get executed. + // We abuse `slot` to get the correct type inference here: unsafe { - $crate::__internal::retokenize!( + // Here we abuse `paste!` to retokenize `$t`. Declarative macros have some internal + // information that is associated to already parsed fragments, so a path fragment + // cannot be used in this position. Doing the retokenization results in valid rust + // code. + $crate::__internal::paste!( ::core::ptr::write($slot, $t { $($acc)* }); diff --git a/tests/ring_buf.rs b/tests/ring_buf.rs index 01c8d14..0c82257 100644 --- a/tests/ring_buf.rs +++ b/tests/ring_buf.rs @@ -44,7 +44,7 @@ impl RingBuffer { // SAFETY: `this` is a valid pointer. head: unsafe { addr_of_mut!((*this.as_ptr()).buffer).cast::() }, tail: unsafe { addr_of_mut!((*this.as_ptr()).buffer).cast::() }, - _pin <- PhantomPinned, + _pin: PhantomPinned, }) } @@ -107,26 +107,26 @@ impl RingBuffer { #[test] fn on_stack() -> Result<(), Infallible> { - stack_pin_init!(let buf = RingBuffer::::new()); + stack_pin_init!(let buf: RingBuffer = RingBuffer::::new()); while let Some(elem) = buf.as_mut().pop() { panic!("found in empty buffer!: {elem}"); } - assert!(buf.as_mut().push(10)?); - assert!(buf.as_mut().push(42)?); + assert!(buf.as_mut().push::(10)?); + assert!(buf.as_mut().push::(42)?); assert_eq!(buf.as_mut().pop(), Some(10)); assert_eq!(buf.as_mut().pop(), Some(42)); assert_eq!(buf.as_mut().pop(), None); - assert!(buf.as_mut().push(42)?); - assert!(buf.as_mut().push(24)?); + assert!(buf.as_mut().push::(42)?); + assert!(buf.as_mut().push::(24)?); assert_eq!(buf.as_mut().pop(), Some(42)); - assert!(buf.as_mut().push(25)?); + assert!(buf.as_mut().push::(25)?); assert_eq!(buf.as_mut().pop(), Some(24)); assert_eq!(buf.as_mut().pop(), Some(25)); assert_eq!(buf.as_mut().pop(), None); for i in 0..63 { - assert!(buf.as_mut().push(i)?); + assert!(buf.as_mut().push::(i)?); } - assert!(!buf.as_mut().push(42)?); + assert!(!buf.as_mut().push::(42)?); for i in 0..63 { if let Some(value) = buf.as_mut().pop_no_stack() { stack_pin_init!(let value = value); diff --git a/tests/ui/no_error_coercion.stderr b/tests/ui/no_error_coercion.stderr index c66e02f..284d039 100644 --- a/tests/ui/no_error_coercion.stderr +++ b/tests/ui/no_error_coercion.stderr @@ -9,8 +9,8 @@ error[E0277]: `?` couldn't convert the error to `std::alloc::AllocError` | = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait = help: the following other types implement trait `FromResidual`: - as FromResidual>> as FromResidual>> + as FromResidual>> = note: required for `Result` to implement `FromResidual>` = note: this error originates in the macro `$crate::__init_internal` which comes from the expansion of the macro `try_init` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/ui/no_pin_data_but_pinned_drop.stderr b/tests/ui/no_pin_data_but_pinned_drop.stderr index aa3a792..1ff96f9 100644 --- a/tests/ui/no_pin_data_but_pinned_drop.stderr +++ b/tests/ui/no_pin_data_but_pinned_drop.stderr @@ -5,9 +5,9 @@ error[E0277]: the trait bound `Foo: HasPinData` is not satisfied | ^^^ the trait `HasPinData` is not implemented for `Foo` | note: required by a bound in `PinnedDrop` - --> $SRC_DIR/src/lib.rs:1200:30 + --> $SRC_DIR/src/lib.rs:1332:30 | -1200 | pub unsafe trait PinnedDrop: __internal::HasPinData { +1332 | pub unsafe trait PinnedDrop: __internal::HasPinData { | ^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `PinnedDrop` error: aborting due to previous error diff --git a/tests/ui/try_access_guard.stderr b/tests/ui/try_access_guard.stderr index 6ba171e..b06922f 100644 --- a/tests/ui/try_access_guard.stderr +++ b/tests/ui/try_access_guard.stderr @@ -1,23 +1,9 @@ -#![feature(allocator_api)] -extern crate pinned_init; -use pinned_init::*; +error[E0425]: cannot find value `a` in this scope + --> $DIR/try_access_guard.rs:16:34 + | +16 | println!("{:?}", a); + | ^ a field by this name exists in `Self` -#[pin_data] -struct Foo { - a: usize, - b: usize, -} +error: aborting due to previous error -impl Foo { - fn new() -> impl PinInit { - pin_init!(Self { - a: 0, - b: { - println!("{:?}", a); - 0 - } - }) - } -} - -fn main() {} +For more information about this error, try `rustc --explain E0425`.