Repository: greyblake/nutype (language: rust)

Issue:
Generate core::error::Error for no_std
Rust  1.81.0 stabilizes `core::error::Error`:  https://blog.rust-lang.org/2024/09/05/Rust-1.81.0.html#whats-in-1810-stable

With that we should able to remove the condition here:
https://github.com/greyblake/nutype/blob/master/nutype_macros/src/common/gen/error.rs#L14-L31

And generate `core::error::Error` instead of `::std::error::Error`.

Since it's a breaking (the older versions of Rust will not be properly supported), add a `[BREAKING]` note change to the CHANGELOG.


Discussion / hints:

Fix patch:
diff --git a/Cargo.lock b/Cargo.lock
index 0c7a8b5..48c0f27 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -329,6 +329,7 @@ dependencies = [
  "proc-macro2",
  "quote",
  "regex",
+ "rustc_version",
  "syn 2.0.66",
  "urlencoding",
 ]
@@ -426,6 +427,15 @@ dependencies = [
  "serde_derive",
 ]
 
+[[package]]
+name = "rustc_version"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
+dependencies = [
+ "semver",
+]
+
 [[package]]
 name = "ryu"
 version = "1.0.13"
@@ -456,6 +466,12 @@ dependencies = [
  "syn 1.0.109",
 ]
 
+[[package]]
+name = "semver"
+version = "1.0.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
+
 [[package]]
 name = "serde"
 version = "1.0.203"
diff --git a/nutype_macros/Cargo.toml b/nutype_macros/Cargo.toml
index fd57dcc..6b0ce3f 100644
--- a/nutype_macros/Cargo.toml
+++ b/nutype_macros/Cargo.toml
@@ -26,6 +26,12 @@ cfg-if = "1.0"
 kinded = "0.3.0"
 urlencoding = "2.0"
 
+[build-dependencies]
+rustc_version = "0.4.1"
+
+[lints.rust]
+unexpected_cfgs = { level = "warn", check-cfg = ['cfg(ERROR_IN_CORE)'] }
+
 [lib]
 proc-macro = true
 
diff --git a/nutype_macros/build.rs b/nutype_macros/build.rs
new file mode 100644
index 0000000..8041087
--- /dev/null
+++ b/nutype_macros/build.rs
@@ -0,0 +1,20 @@
+use rustc_version::{version, version_meta, Channel};
+
+fn main() {
+    let version = version().expect("Couldn't get Rust version");
+    let version_meta = version_meta().expect("Couldn't get Rust channel");
+
+    // Assert we haven't travelled back in time
+    assert!(
+        version.major >= 1,
+        "How did you get a version before 1.0.0?"
+    );
+
+    // Generic setting
+    println!("cargo:rerun-if-changed=build.rs");
+
+    // feature `error-in-core` landed in rust 1.81.0
+    if matches!(version_meta.channel, Channel::Nightly) || version.minor >= 81 {
+        println!("cargo:rustc-cfg=ERROR_IN_CORE");
+    }
+}
diff --git a/nutype_macros/src/common/gen/error.rs b/nutype_macros/src/common/gen/error.rs
index 64fb8e7..af9e8cf 100644
--- a/nutype_macros/src/common/gen/error.rs
+++ b/nutype_macros/src/common/gen/error.rs
@@ -11,15 +11,22 @@ pub fn gen_error_type_name(type_name: &TypeName) -> ErrorTypePath {
     ErrorTypePath::new(ident)
 }
 
-// NOTE: There is no `::core::error::Error` yet in stable Rust.
-// So for `no_std` we just don't implement `Error` trait.
+// NOTE: `::core::error::Error` is stable only for rust >= 1.81.0.
 #[allow(unused_variables)]
 pub fn gen_impl_error_trait(error_type_path: &ErrorTypePath) -> TokenStream {
     cfg_if! {
-        if #[cfg(feature = "std")] {
+        if #[cfg(any(ERROR_IN_CORE, feature = "std"))] {
+            cfg_if! {
+                if #[cfg(ERROR_IN_CORE)] {
+                    let error = quote! { ::core::error::Error };
+                } else {
+                    let error = quote! { ::std::error::Error };
+                }
+            };
+
             quote! {
-                impl ::std::error::Error for #error_type_path {
-                    fn source(&self) -> Option<&(dyn ::std::error::Error + 'static)> {
+                impl #error for #error_type_path {
+                    fn source(&self) -> Option<&(dyn #error + 'static)> {
                         None
                     }
                 }
diff --git a/nutype_macros/src/common/gen/parse_error.rs b/nutype_macros/src/common/gen/parse_error.rs
index 2aa8e87..b2d07d6 100644
--- a/nutype_macros/src/common/gen/parse_error.rs
+++ b/nutype_macros/src/common/gen/parse_error.rs
@@ -68,27 +68,33 @@ pub fn gen_def_parse_error(
     };
 
     cfg_if! {
-        if #[cfg(feature = "std")] {
+        if #[cfg(any(ERROR_IN_CORE, feature = "std"))] {
+            cfg_if! {
+                if #[cfg(ERROR_IN_CORE)] {
+                    let error = quote! { ::core::error::Error };
+                } else {
+                    let error = quote! { ::std::error::Error };
+                }
+            };
             let generics_with_fromstr_and_debug_bounds = add_bound_to_all_type_params(
                 &generics_with_fromstr_bound,
                 syn::parse_quote!(::core::fmt::Debug),
             );
-            let impl_std_error = quote! {
-                impl #generics_with_fromstr_and_debug_bounds ::std::error::Error for #parse_error_type_name #generics_without_bounds {
-                    fn source(&self) -> Option<&(dyn ::std::error::Error + 'static)> {
+            let impl_error = quote! {
+                impl #generics_with_fromstr_and_debug_bounds #error for #parse_error_type_name #generics_without_bounds {
+                    fn source(&self) -> Option<&(dyn #error + 'static)> {
                         None
                     }
                 }
             };
         } else {
-            // NOTE: There is no `::core::error::Error` yet in stable Rust.
-            // So for `no_std` we just don't implement `Error` trait.
-            let impl_std_error = quote! {};
+            // NOTE: `::core::error::Error` is stable only for rust >= 1.81.0.
+            let impl_error = quote! {};
         }
     };
 
     quote! {
         #definition
-        #impl_std_error
+        #impl_error
     }
 }


Test patch: