From f05b2856cda0eadff17d760431d8ba7d424fcf5c Mon Sep 17 00:00:00 2001 From: Icxolu <10486322+Icxolu@users.noreply.github.com> Date: Sun, 18 Jan 2026 22:04:15 +0100 Subject: [PATCH] deprecate super class initialization from tuples --- guide/src/class.md | 11 +++---- guide/src/migration.md | 46 +++++++++++++++++++++++++++ newsfragments/5741.changed.md | 1 + pyo3-macros-backend/src/pymethod.rs | 9 ++++-- pytests/src/subclassing.rs | 4 +-- src/impl_/pyclass/probes.rs | 39 +---------------------- src/instance.rs | 13 +++++--- src/internal/pyclass_init.rs | 41 ++++++++++++++++-------- src/pycell.rs | 10 +++--- src/pyclass/guard.rs | 10 +++--- src/types/pysuper.rs | 4 +-- tests/test_class_basics.rs | 34 +++++++------------- tests/test_class_conversion.rs | 8 +++-- tests/test_class_init.rs | 8 ++--- tests/test_gc.rs | 8 ++--- tests/test_inheritance.rs | 8 ++--- tests/test_super.rs | 4 +-- tests/ui/deprecated_super_init.rs | 32 +++++++++++++++++++ tests/ui/deprecated_super_init.stderr | 19 +++++++++++ 19 files changed, 195 insertions(+), 114 deletions(-) create mode 100644 newsfragments/5741.changed.md create mode 100644 tests/ui/deprecated_super_init.rs create mode 100644 tests/ui/deprecated_super_init.stderr diff --git a/guide/src/class.md b/guide/src/class.md index a20311efade..d0dcf262259 100644 --- a/guide/src/class.md +++ b/guide/src/class.md @@ -405,8 +405,7 @@ Consult the table below to determine which type your constructor should return: | | **Cannot fail** | **May fail** | |-----------------------------|---------------------------|-----------------------------------| |**No inheritance** | `T` | `PyResult` | -|**Inheritance(T Inherits U)**| `(T, U)` | `PyResult<(T, U)>` | -|**Inheritance(General Case)**| [`PyClassInitializer`] | `PyResult>` | +|**Inheritance** | [`PyClassInitializer`] | `PyResult>` | ## Inheritance @@ -414,8 +413,7 @@ By default, `object`, i.e. `PyAny` is used as the base class. To override this default, use the `extends` parameter for `pyclass` with the full path to the base class. Currently, only classes defined in Rust and builtins provided by PyO3 can be inherited from; inheriting from other classes defined in Python is not yet supported ([#991](https://github.com/PyO3/pyo3/issues/991)). -For convenience, `(T, U)` implements `Into>` where `U` is the base class of `T`. -But for a more deeply nested inheritance, you have to return `PyClassInitializer` explicitly. +To initialize a class, which inherits from another class, use the `PyClassInitializer` API. To get a parent class from a child, use [`PyRef`] instead of `&self` for methods, or [`PyRefMut`] instead of `&mut self`. Then you can access a parent class by `self_.as_super()` as `&PyRef`, or by `self_.into_super()` as `PyRef` (and similar for the `PyRefMut` case). @@ -449,8 +447,9 @@ struct SubClass { #[pymethods] impl SubClass { #[new] - fn new() -> (Self, BaseClass) { - (SubClass { val2: 15 }, BaseClass::new()) + fn new() -> PyClassInitializer { + PyClassInitializer::from(BaseClass::new()) + .add_subclass(SubClass { val2: 15 }) } fn method2(self_: PyRef<'_, Self>) -> PyResult { diff --git a/guide/src/migration.md b/guide/src/migration.md index 75600c65656..995764dc770 100644 --- a/guide/src/migration.md +++ b/guide/src/migration.md @@ -47,6 +47,52 @@ In PyO3 0.29, `pyo3-build-config` no longer inlines any configuration into itsel This has upside of faster compilation - `pyo3-build-config` no longer has a build script and never recompiles when changing Python version. It also guarantees that all builds are based on the single fully-configured build configuration resolved by `pyo3-ffi`. +### Deprecation of super class initialization from tuples + +Performing superclass initialitation from a subclass via a tuple is deprecated and will be removed in a future PyO3 version. +This also includes the `From<(S, B)> for PyClassInitializer` impl which we can't warn against. +To migrate use `PyClassInitializer` directly: + +Before: + +```rust +# #![allow(deprecated)] +# use pyo3::prelude::*; +#[pyclass(subclass)] +struct Base; + +#[pyclass(extends=Base)] +struct Sub; + +#[pymethods] +impl Sub { + #[new] + fn new() -> (Self, Base) { + (Self, Base) + } +} +``` + +After: + +```rust +# use pyo3::prelude::*; +#[pyclass(subclass)] +struct Base; + +#[pyclass(extends=Base)] +struct Sub; + +#[pymethods] +impl Sub { + #[new] + fn new() -> PyClassInitializer { + PyClassInitializer::from(Base) + .add_subclass(Self) + } +} +``` + ## from 0.27.* to 0.28 ### Default to supporting free-threaded Python diff --git a/newsfragments/5741.changed.md b/newsfragments/5741.changed.md new file mode 100644 index 00000000000..86325ca4c49 --- /dev/null +++ b/newsfragments/5741.changed.md @@ -0,0 +1 @@ +Super class initialization from tuples is deprecated. \ No newline at end of file diff --git a/pyo3-macros-backend/src/pymethod.rs b/pyo3-macros-backend/src/pymethod.rs index a697a330601..1e2e1228e6b 100644 --- a/pyo3-macros-backend/src/pymethod.rs +++ b/pyo3-macros-backend/src/pymethod.rs @@ -1483,14 +1483,19 @@ fn generate_method_body( #pyo3_path::impl_::pymethods::tp_new_impl::<_, #cls>(#py, #initializer, #slf) }; + let value = syn::Ident::new("value", Span::call_site()); + let resolver = quote_spanned! { *output_span => + #pyo3_path::impl_::pymethods::tp_new_resolver::<#cls, _>(&#value).resolve(#value); + }; + let body = quote! { #text_signature_impl #warnings #arg_convert let result = #call; - let value = #pyo3_path::impl_::wrap::OkWrapper::new(&result).ok_wrap(result)?; - let #initializer = #pyo3_path::impl_::pymethods::tp_new_resolver::<#cls, _>(&value).resolve(value); + let #value = #pyo3_path::impl_::wrap::OkWrapper::new(&result).ok_wrap(result)?; + let #initializer = #resolver; unsafe { #conversion } }; (arg_idents, arg_types, body) diff --git a/pytests/src/subclassing.rs b/pytests/src/subclassing.rs index f750ce757ba..24e1bacaf0a 100644 --- a/pytests/src/subclassing.rs +++ b/pytests/src/subclassing.rs @@ -29,8 +29,8 @@ pub mod subclassing { #[pymethods] impl Subclass { #[new] - fn new() -> (Self, Subclassable) { - (Subclass {}, Subclassable::new()) + fn new() -> PyClassInitializer { + PyClassInitializer::from(Subclassable::new()).add_subclass(Self {}) } fn __str__(&self) -> &'static str { diff --git a/src/impl_/pyclass/probes.rs b/src/impl_/pyclass/probes.rs index 3ab484e65df..6497ccd55c0 100644 --- a/src/impl_/pyclass/probes.rs +++ b/src/impl_/pyclass/probes.rs @@ -1,9 +1,7 @@ use core::marker::PhantomData; use crate::conversion::IntoPyObject; -use crate::impl_::pyclass::PyClassBaseType; -use crate::impl_::pyclass_init::PyNativeTypeInitializer; -use crate::{FromPyObject, Py, PyClass, PyClassInitializer}; +use crate::{FromPyObject, Py}; /// Trait used to combine with zero-sized types to calculate at compile time /// some property of a type. @@ -98,41 +96,6 @@ impl IsReturningEmptyTuple> { pub const VALUE: bool = true; } -probe!(IsPyClass); - -impl IsPyClass -where - T: PyClass, -{ - pub const VALUE: bool = true; -} - -impl IsPyClass> -where - T: PyClass, -{ - pub const VALUE: bool = true; -} - -probe!(IsInitializerTuple); - -impl IsInitializerTuple<(S, B)> -where - S: PyClass, - B: PyClass + PyClassBaseType>, - B::BaseType: PyClassBaseType>, -{ - pub const VALUE: bool = true; -} -impl IsInitializerTuple> -where - S: PyClass, - B: PyClass + PyClassBaseType>, - B::BaseType: PyClassBaseType>, -{ - pub const VALUE: bool = true; -} - #[cfg(test)] macro_rules! value_of { ($probe:ident, $ty:ty) => {{ diff --git a/src/instance.rs b/src/instance.rs index 99927305f82..8aa8371d04f 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -683,7 +683,8 @@ where /// struct SubClass; /// /// Python::attach(|py| { - /// let obj = Bound::new(py, (SubClass, BaseClass)).unwrap(); + /// let initializer = PyClassInitializer::from(BaseClass).add_subclass(SubClass); + /// let obj = Bound::new(py, initializer).unwrap(); /// assert!(obj.as_super().pyrepr().is_ok()); /// }) /// # } @@ -735,7 +736,8 @@ where /// struct SubClass; /// /// Python::attach(|py| { - /// let obj = Bound::new(py, (SubClass, BaseClass)).unwrap(); + /// let initializer = PyClassInitializer::from(BaseClass).add_subclass(SubClass); + /// let obj = Bound::new(py, initializer).unwrap(); /// assert!(obj.into_super().pyrepr().is_ok()); /// }) /// # } @@ -2849,6 +2851,7 @@ a = A() #[cfg(feature = "macros")] mod using_macros { use super::*; + use crate::PyClassInitializer; #[crate::pyclass(crate = "crate")] struct SomeClass(i32); @@ -2929,7 +2932,8 @@ a = A() #[test] fn test_as_super() { Python::attach(|py| { - let obj = Bound::new(py, (SubClass, BaseClass)).unwrap(); + let initializer = PyClassInitializer::from(BaseClass).add_subclass(SubClass); + let obj = Bound::new(py, initializer).unwrap(); let _: &Bound<'_, BaseClass> = obj.as_super(); let _: &Bound<'_, PyAny> = obj.as_super().as_super(); assert!(obj.as_super().pyrepr_by_ref().is_ok()); @@ -2939,7 +2943,8 @@ a = A() #[test] fn test_into_super() { Python::attach(|py| { - let obj = Bound::new(py, (SubClass, BaseClass)).unwrap(); + let initializer = PyClassInitializer::from(BaseClass).add_subclass(SubClass); + let obj = Bound::new(py, initializer).unwrap(); let _: Bound<'_, BaseClass> = obj.clone().into_super(); let _: Bound<'_, PyAny> = obj.clone().into_super().into_super(); assert!(obj.into_super().pyrepr_by_val().is_ok()); diff --git a/src/internal/pyclass_init.rs b/src/internal/pyclass_init.rs index 19195d19774..b113782b630 100644 --- a/src/internal/pyclass_init.rs +++ b/src/internal/pyclass_init.rs @@ -60,7 +60,8 @@ impl PyObjectInit for PyNativeTypeInitializer { } } -pub struct TpNewValueTypeResolver( +pub struct TpNewValueTypeResolver(TpNewTupleResolver); +pub struct TpNewTupleResolver( ResolveToArbitraryObject, PhantomData<(ClassT, ValueT)>, ); @@ -76,10 +77,20 @@ pub struct ResolveToArbitraryObject(()); /// `ClassT` because that implementation ignores the `cls` parameter for `PyClassInit` (and would /// therefore be incorrect when instantiating subclasses). pub fn tp_new_resolver(_: &ValueT) -> TpNewValueTypeResolver { - TpNewValueTypeResolver(ResolveToArbitraryObject(()), PhantomData) + TpNewValueTypeResolver(TpNewTupleResolver( + ResolveToArbitraryObject(()), + PhantomData, + )) } impl core::ops::Deref for TpNewValueTypeResolver { + type Target = TpNewTupleResolver; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl core::ops::Deref for TpNewTupleResolver { type Target = ResolveToArbitraryObject; fn deref(&self) -> &Self::Target { &self.0 @@ -100,6 +111,21 @@ where } } +impl TpNewTupleResolver +where + S: PyClass, + B: PyClass + PyClassBaseType>, + B::BaseType: PyClassBaseType>, +{ + #[deprecated( + since = "0.29.0", + note = "Tuple syntax for super class initialization is phased out. Use `PyClassInitializer` instead." + )] + pub fn resolve(&self, value: (S, B)) -> PyClassInitializer { + value.into() + } +} + /// All other conversions fall back to IntoPyObject via the deref implementation impl ResolveToArbitraryObject { pub fn resolve(&self, value: ValueT) -> ValueT { @@ -169,14 +195,3 @@ where self.into() } } - -impl IntoPyClassInitializer for (S, B) -where - S: PyClass, - B: PyClass + PyClassBaseType>, - B::BaseType: PyClassBaseType>, -{ - fn into_pyclass_initializer(self) -> PyClassInitializer { - self.into() - } -} diff --git a/src/pycell.rs b/src/pycell.rs index 0b2f53b68e5..bc78d9d06f3 100644 --- a/src/pycell.rs +++ b/src/pycell.rs @@ -233,8 +233,9 @@ use impl_::{PyClassBorrowChecker, PyClassObjectBaseLayout, PyClassObjectLayout}; /// #[pymethods] /// impl Child { /// #[new] -/// fn new() -> (Self, Parent) { -/// (Child { name: "Caterpillar" }, Parent { basename: "Butterfly" }) +/// fn new() -> PyClassInitializer { +/// PyClassInitializer::from(Parent { basename: "Butterfly" }) +/// .add_subclass(Child { name: "Caterpillar" }) /// } /// /// fn format(slf: PyRef<'_, Self>) -> String { @@ -430,8 +431,9 @@ where /// #[pymethods] /// impl Sub { /// #[new] - /// fn new() -> (Self, Base) { - /// (Self { sub_name: "sub_name" }, Base { base_name: "base_name" }) + /// fn new() -> PyClassInitializer { + /// PyClassInitializer::from(Base { base_name: "base_name" }) + /// .add_subclass(Self { sub_name: "sub_name" }) /// } /// fn sub_name_len(&self) -> usize { /// self.sub_name.len() diff --git a/src/pyclass/guard.rs b/src/pyclass/guard.rs index 676b6808bf1..25528f7d780 100644 --- a/src/pyclass/guard.rs +++ b/src/pyclass/guard.rs @@ -62,8 +62,9 @@ use core::ptr::NonNull; /// #[pymethods] /// impl Child { /// #[new] -/// fn new() -> (Self, Parent) { -/// (Child { name: "Caterpillar" }, Parent { basename: "Butterfly" }) +/// fn new() -> PyClassInitializer { +/// PyClassInitializer::from(Parent { basename: "Butterfly" }) +/// .add_subclass(Child { name: "Caterpillar" }) /// } /// /// fn format(slf: PyClassGuard<'_, Self>) -> String { @@ -184,8 +185,9 @@ where /// #[pymethods] /// impl Sub { /// #[new] - /// fn new() -> (Self, Base) { - /// (Self { sub_name: "sub_name" }, Base { base_name: "base_name" }) + /// fn new() -> PyClassInitializer { + /// PyClassInitializer::from(Base { base_name: "base_name" }) + /// .add_subclass(Self { sub_name: "sub_name" }) /// } /// fn sub_name_len(&self) -> usize { /// self.sub_name.len() diff --git a/src/types/pysuper.rs b/src/types/pysuper.rs index f68cb1a392a..06e2b5b8168 100644 --- a/src/types/pysuper.rs +++ b/src/types/pysuper.rs @@ -64,8 +64,8 @@ impl PySuper { /// #[pymethods] /// impl SubClass { /// #[new] - /// fn new() -> (Self, BaseClass) { - /// (SubClass {}, BaseClass::new()) + /// fn new() -> PyClassInitializer { + /// PyClassInitializer::from(BaseClass::new()).add_subclass(SubClass {}) /// } /// /// fn method<'py>(self_: &Bound<'py, Self>) -> PyResult> { diff --git a/tests/test_class_basics.rs b/tests/test_class_basics.rs index 810bab39277..90c9c1706fa 100644 --- a/tests/test_class_basics.rs +++ b/tests/test_class_basics.rs @@ -282,8 +282,8 @@ struct UnsendableChild {} #[pymethods] impl UnsendableChild { #[new] - fn new(value: usize) -> (UnsendableChild, UnsendableBase) { - (UnsendableChild {}, UnsendableBase::new(value)) + fn new(value: usize) -> PyClassInitializer { + PyClassInitializer::from(UnsendableBase::new(value)).add_subclass(UnsendableChild {}) } } @@ -489,16 +489,11 @@ struct InheritDict { #[cfg(any(Py_3_9, not(Py_LIMITED_API)))] fn inherited_dict() { Python::attach(|py| { - let inst = Py::new( - py, - ( - InheritDict { _value: 0 }, - DunderDictSupport { - _pad: *b"DEADBEEFDEADBEEFDEADBEEFDEADBEEF", - }, - ), - ) - .unwrap(); + let initializer = PyClassInitializer::from(DunderDictSupport { + _pad: *b"DEADBEEFDEADBEEFDEADBEEFDEADBEEF", + }) + .add_subclass(InheritDict { _value: 0 }); + let inst = Py::new(py, initializer).unwrap(); py_run!( py, inst, @@ -572,16 +567,11 @@ struct InheritWeakRef { #[cfg(any(Py_3_9, not(Py_LIMITED_API)))] fn inherited_weakref() { Python::attach(|py| { - let inst = Py::new( - py, - ( - InheritWeakRef { _value: 0 }, - WeakRefSupport { - _pad: *b"DEADBEEFDEADBEEFDEADBEEFDEADBEEF", - }, - ), - ) - .unwrap(); + let initializer = PyClassInitializer::from(WeakRefSupport { + _pad: *b"DEADBEEFDEADBEEFDEADBEEFDEADBEEF", + }) + .add_subclass(InheritWeakRef { _value: 0 }); + let inst = Py::new(py, initializer).unwrap(); py_run!( py, inst, diff --git a/tests/test_class_conversion.rs b/tests/test_class_conversion.rs index be0a6ffe140..f7ad6ea86ef 100644 --- a/tests/test_class_conversion.rs +++ b/tests/test_class_conversion.rs @@ -122,7 +122,9 @@ fn test_polymorphic_container_does_not_accept_other_types() { #[test] fn test_pyref_as_base() { Python::attach(|py| { - let cell = Bound::new(py, (SubClass {}, BaseClass { value: 120 })).unwrap(); + let initializer = + PyClassInitializer::from(BaseClass { value: 120 }).add_subclass(SubClass {}); + let cell = Bound::new(py, initializer).unwrap(); // First try PyRefMut let sub: PyRefMut<'_, SubClass> = cell.borrow_mut(); @@ -142,7 +144,9 @@ fn test_pyref_as_base() { #[test] fn test_pycell_deref() { Python::attach(|py| { - let obj = Bound::new(py, (SubClass {}, BaseClass { value: 120 })).unwrap(); + let initializer = + PyClassInitializer::from(BaseClass { value: 120 }).add_subclass(SubClass {}); + let obj = Bound::new(py, initializer).unwrap(); // Should be able to deref as PyAny assert_eq!( diff --git a/tests/test_class_init.rs b/tests/test_class_init.rs index 3b7f5e36eb8..0d06fb50ccf 100644 --- a/tests/test_class_init.rs +++ b/tests/test_class_init.rs @@ -35,8 +35,8 @@ struct SubWithoutInit; #[pymethods] impl SubWithoutInit { #[new] - fn new() -> (Self, Base) { - (Self, Base::new()) + fn new() -> PyClassInitializer { + PyClassInitializer::from(Base::new()).add_subclass(Self) } } @@ -60,8 +60,8 @@ struct SubWithInit; #[pymethods] impl SubWithInit { #[new] - fn new() -> (Self, Base) { - (Self, Base::new()) + fn new() -> PyClassInitializer { + PyClassInitializer::from(Base::new()).add_subclass(Self) } fn __init__(mut slf: pyo3::PyClassGuardMut<'_, Self>) { diff --git a/tests/test_gc.rs b/tests/test_gc.rs index 29a219c9199..d177e954870 100644 --- a/tests/test_gc.rs +++ b/tests/test_gc.rs @@ -254,11 +254,9 @@ fn inheritance_with_new_methods_with_drop() { #[pymethods] impl SubClassWithDrop { #[new] - fn new() -> (Self, BaseClassWithDrop) { - ( - SubClassWithDrop { guard: None }, - BaseClassWithDrop { guard: None }, - ) + fn new() -> PyClassInitializer { + PyClassInitializer::from(BaseClassWithDrop { guard: None }) + .add_subclass(SubClassWithDrop { guard: None }) } } diff --git a/tests/test_inheritance.rs b/tests/test_inheritance.rs index 4a8066eee55..b4a682d1e4e 100644 --- a/tests/test_inheritance.rs +++ b/tests/test_inheritance.rs @@ -57,8 +57,8 @@ struct SubClass { #[pymethods] impl SubClass { #[new] - fn new() -> (Self, BaseClass) { - (SubClass { val2: 5 }, BaseClass { val1: 10 }) + fn new() -> PyClassInitializer { + PyClassInitializer::from(BaseClass { val1: 10 }).add_subclass(SubClass { val2: 5 }) } fn sub_method(&self, x: usize) -> usize { x * self.val2 @@ -146,9 +146,9 @@ struct SubClass2 {} #[pymethods] impl SubClass2 { #[new] - fn new(value: isize) -> PyResult<(Self, BaseClassWithResult)> { + fn new(value: isize) -> PyResult> { let base = BaseClassWithResult::new(value)?; - Ok((Self {}, base)) + Ok(PyClassInitializer::from(base).add_subclass(Self {})) } } diff --git a/tests/test_super.rs b/tests/test_super.rs index 3a3ce5f6390..2bb0fbfcb50 100644 --- a/tests/test_super.rs +++ b/tests/test_super.rs @@ -26,8 +26,8 @@ struct SubClass {} #[pymethods] impl SubClass { #[new] - fn new() -> (Self, BaseClass) { - (SubClass {}, BaseClass::new()) + fn new() -> PyClassInitializer { + PyClassInitializer::from(BaseClass::new()).add_subclass(Self {}) } fn method<'py>(self_: &Bound<'py, Self>) -> PyResult> { diff --git a/tests/ui/deprecated_super_init.rs b/tests/ui/deprecated_super_init.rs new file mode 100644 index 00000000000..fe35a0bf2f8 --- /dev/null +++ b/tests/ui/deprecated_super_init.rs @@ -0,0 +1,32 @@ +#![deny(deprecated)] + +use pyo3::prelude::*; + +#[pyclass(subclass)] +struct Base; + +#[pyclass(extends=Base)] +struct Sub1; + +#[pymethods] +impl Sub1 { + #[new] + fn new() -> (Sub1, Base) { + //~^ERROR: use of deprecated method `pyo3::internal::pyclass_init::TpNewTupleResolver::::resolve`: Tuple syntax for super class initialization is phased out. Use `PyClassInitializer` instead. + (Sub1, Base) + } +} + +#[pyclass(extends=Base)] +struct Sub2; + +#[pymethods] +impl Sub2 { + #[new] + fn new() -> PyResult<(Sub2, Base)> { + //~^ERROR: use of deprecated method `pyo3::internal::pyclass_init::TpNewTupleResolver::::resolve`: Tuple syntax for super class initialization is phased out. Use `PyClassInitializer` instead. + Ok((Sub2, Base)) + } +} + +fn main() {} diff --git a/tests/ui/deprecated_super_init.stderr b/tests/ui/deprecated_super_init.stderr new file mode 100644 index 00000000000..7f0ebffe25b --- /dev/null +++ b/tests/ui/deprecated_super_init.stderr @@ -0,0 +1,19 @@ +error: use of deprecated method `pyo3::internal::pyclass_init::TpNewTupleResolver::::resolve`: Tuple syntax for super class initialization is phased out. Use `PyClassInitializer` instead. + --> tests/ui/deprecated_super_init.rs:14:17 + | +14 | fn new() -> (Sub1, Base) { + | ^^^^^^^^^^^^ + | +note: the lint level is defined here + --> tests/ui/deprecated_super_init.rs:1:9 + | + 1 | #![deny(deprecated)] + | ^^^^^^^^^^ + +error: use of deprecated method `pyo3::internal::pyclass_init::TpNewTupleResolver::::resolve`: Tuple syntax for super class initialization is phased out. Use `PyClassInitializer` instead. + --> tests/ui/deprecated_super_init.rs:26:17 + | +26 | fn new() -> PyResult<(Sub2, Base)> { + | ^^^^^^^^ + +error: aborting due to 2 previous errors