From 0e33ea21a6dceeb6a0702af3d84131408c4d565a Mon Sep 17 00:00:00 2001 From: Stefano Pigozzi Date: Thu, 19 Dec 2024 10:57:24 +0100 Subject: [PATCH] Improve error messages and allow unqualified types --- micronfig_macros/Cargo.toml | 2 +- micronfig_macros/src/lib.rs | 79 ++++++++++--------- ...lified_import.rs => unqualified_import.rs} | 0 .../wrong_conversion_trait_from.stderr | 23 ++---- .../wrong_conversion_trait_fromstr.stderr | 30 +++---- .../wrong_conversion_trait_tryfrom.stderr | 25 +++--- .../tests/sources/wrong_start.stderr | 32 ++++++-- .../sources/wrong_unqualified_noimport.stderr | 9 --- micronfig_macros/tests/tests.rs | 2 +- 9 files changed, 96 insertions(+), 106 deletions(-) rename micronfig_macros/tests/sources/{wrong_unqualified_import.rs => unqualified_import.rs} (100%) diff --git a/micronfig_macros/Cargo.toml b/micronfig_macros/Cargo.toml index 6458b87..12d1864 100644 --- a/micronfig_macros/Cargo.toml +++ b/micronfig_macros/Cargo.toml @@ -13,7 +13,7 @@ categories = ["config"] all-features = true [dependencies] -syn = "2.0" +syn = { version = "2.0", features = ["extra-traits"] } quote = "1.0" [dev-dependencies] diff --git a/micronfig_macros/src/lib.rs b/micronfig_macros/src/lib.rs index c7902bd..a97bd4c 100644 --- a/micronfig_macros/src/lib.rs +++ b/micronfig_macros/src/lib.rs @@ -1,17 +1,17 @@ extern crate proc_macro; use proc_macro::TokenStream; -use quote::{quote, ToTokens}; +use quote::quote; use syn::parse::{Parse, ParseStream}; use syn::{Ident, parse_macro_input, Token, Type, TypePath}; use syn::punctuated::Punctuated; - type Config = Punctuated; #[derive(Clone)] struct ConfigItem { identifier: Ident, optional: bool, + first: TypePath, types: Vec, } @@ -36,34 +36,32 @@ impl Parse for ConfigItem { let optional = input.lookahead1().peek(Token![?]); if optional { input.parse::() - .expect("this token to be parsed correctly, as it has been previously peeked"); + .expect("Expected `?`, as it was previously peeked"); } - let types = match input.lookahead1().peek(Token![:]) { + let (first, types) = match input.lookahead1().peek(Token![:]) { true => { input.parse::() - .expect("this token to be parsed correctly, as it has been previously peeked"); + .expect("Expected `:`, as it was previously peeked"); - let string_type = input.parse::()?; - if &*string_type.to_token_stream().to_string() != "String" { - return Err( - syn::Error::new_spanned( - string_type, - "first type of a conversion chain should always be literally `String`, other aliases are not allowed" - ) - ); - } + let first = input.parse::()?; let mut types = Vec::new(); while let Ok(typ) = input.parse::() { types.push(typ) } - types + + (first, types) + }, + false => { + let first = syn::parse_quote!(String); + let types = Vec::new(); + + (first, types) }, - false => Vec::new(), }; - Ok(Self { identifier, optional, types }) + Ok(Self { identifier, optional, first, types }) } } @@ -95,23 +93,22 @@ impl Parse for Conversion { #[proc_macro] pub fn config(input: TokenStream) -> TokenStream { - let input: Config = parse_macro_input!(input with syn::punctuated::Punctuated::parse_terminated); + let input: Config = parse_macro_input!(input with Punctuated::parse_terminated); let cache_code = quote! { - #[allow(non_snake_case)] - mod _cache { - pub static _lock: std::sync::OnceLock = std::sync::OnceLock::new(); - } - #[allow(non_snake_case)] fn _cache() -> &'static micronfig::cache::Cache { - _cache::_lock.get_or_init(micronfig::cache::Cache::new) + static LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + + LOCK.get_or_init(micronfig::cache::Cache::new) } }; let items_code = input.iter().map(|item: &ConfigItem| { let identifier = &item.identifier; let identifier_string = identifier.to_string(); + + let type_first = &item.first; let type_final = match item.types.last() { Some(pair) => { @@ -137,13 +134,17 @@ pub fn config(input: TokenStream) -> TokenStream { }, (Conversion::TryFrom, true) => quote! { let value: Option<#typ> = value - .map(|v| v.try_into()) - .map(|v| v.unwrap_or_else(|err| panic!("Couldn't perform conversion `{:?} => {:?}`: {:#?}", v, #identifier_string, err))); + .map(|v| v + .try_into() + .unwrap_or_else(|err| panic!("{}: Couldn't perform `=> {:?}` conversion: {:#?}", #identifier_string, std::any::type_name::<#typ>(), err)) + ); }, (Conversion::FromStr, true) => quote! { let value: Option<#typ> = value - .map(|v| v.parse()) - .map(|v| v.unwrap_or_else(|err| panic!("Couldn't perform conversion `{:?} > {:?}`: {:#?}", v, #identifier_string, err))); + .map(|v| v + .parse() + .unwrap_or_else(|err| panic!("{}: Couldn't perform `> {:?}` conversion: {:#?}", #identifier_string, std::any::type_name::<#typ>(), err)) + ); }, (Conversion::From, false) => quote! { let value: #typ = value @@ -152,12 +153,12 @@ pub fn config(input: TokenStream) -> TokenStream { (Conversion::TryFrom, false) => quote! { let value: #typ = value .try_into() - .unwrap_or_else(|err| panic!("Couldn't perform conversion `{:?} => {:?}`: {:#?}", value, #identifier_string, err)); + .unwrap_or_else(|err| panic!("{}: Couldn't perform `=> {:?}` conversion: {:#?}", #identifier_string, std::any::type_name::<#typ>(), err)); }, (Conversion::FromStr, false) => quote! { let value: #typ = value .parse() - .unwrap_or_else(|err| panic!("Couldn't perform conversion `{:?} > {:?}`: {:#?}", value, #identifier_string, err)); + .unwrap_or_else(|err| panic!("{}: Couldn't perform `> {:?}` conversion: {:#?}", #identifier_string, std::any::type_name::<#typ>(), err)); }, } } @@ -169,21 +170,18 @@ pub fn config(input: TokenStream) -> TokenStream { true => quote! {}, false => quote! { let value: String = value - .unwrap_or_else(|| panic!("Unset configuration variable: {}", #identifier_string)); + .unwrap_or_else(|| panic!("{}: Is required, but has no value set", #identifier_string)); }, }; quote! { - #[allow(non_snake_case)] - mod #identifier { - pub(super) static _lock: std::sync::OnceLock<#type_final_option> = std::sync::OnceLock::new(); - } - #[allow(non_snake_case)] pub(crate) fn #identifier() -> &'static #type_final_option { - #identifier::_lock.get_or_init(|| { + static LOCK: std::sync::OnceLock<#type_final_option> = std::sync::OnceLock::new(); + + LOCK.get_or_init(|| { let key = #identifier_string.as_ref(); - let value: Option = _cache().get(key); + let value: Option<#type_first> = _cache().get(key); #require_code #conversion_code @@ -193,7 +191,10 @@ pub fn config(input: TokenStream) -> TokenStream { } } }).reduce(|acc, new| { - quote! { #acc #new } + quote! { + #acc + #new + } }); let quote = quote! { diff --git a/micronfig_macros/tests/sources/wrong_unqualified_import.rs b/micronfig_macros/tests/sources/unqualified_import.rs similarity index 100% rename from micronfig_macros/tests/sources/wrong_unqualified_import.rs rename to micronfig_macros/tests/sources/unqualified_import.rs diff --git a/micronfig_macros/tests/sources/wrong_conversion_trait_from.stderr b/micronfig_macros/tests/sources/wrong_conversion_trait_from.stderr index d268237..8ba7475 100644 --- a/micronfig_macros/tests/sources/wrong_conversion_trait_from.stderr +++ b/micronfig_macros/tests/sources/wrong_conversion_trait_from.stderr @@ -4,21 +4,14 @@ error[E0277]: the trait bound `u64: From` is not satisfied 1 | / micronfig::config! { 2 | | GARASAUTO: String -> u64, 3 | | } - | | ^ - | | | - | |_the trait `From` is not implemented for `u64` - | in this macro invocation - | - ::: src/lib.rs - | - | pub fn config(input: TokenStream) -> TokenStream { - | ------------------------------------------------ in this expansion of `micronfig::config!` + | |_^ the trait `From` is not implemented for `u64`, which is required by `String: Into<_>` | = help: the following other types implement trait `From`: - > - > - > - > - > - > + `u64` implements `From` + `u64` implements `From` + `u64` implements `From` + `u64` implements `From` + `u64` implements `From` + `u64` implements `From` = note: required for `String` to implement `Into` + = note: this error originates in the macro `micronfig::config` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/micronfig_macros/tests/sources/wrong_conversion_trait_fromstr.stderr b/micronfig_macros/tests/sources/wrong_conversion_trait_fromstr.stderr index 68895db..94d4098 100644 --- a/micronfig_macros/tests/sources/wrong_conversion_trait_fromstr.stderr +++ b/micronfig_macros/tests/sources/wrong_conversion_trait_fromstr.stderr @@ -4,25 +4,21 @@ error[E0277]: the trait bound `Infallible: FromStr` is not satisfied 1 | / micronfig::config! { 2 | | GARASAUTO: String > std::convert::Infallible, 3 | | } - | | ^ - | | | - | |_the trait `FromStr` is not implemented for `Infallible` - | in this macro invocation - | - ::: src/lib.rs - | - | pub fn config(input: TokenStream) -> TokenStream { - | ------------------------------------------------ in this expansion of `micronfig::config!` + | |_^ the trait `FromStr` is not implemented for `Infallible` | = help: the following other types implement trait `FromStr`: - bool - char - isize - i8 - i16 - i32 - i64 - i128 + IpAddr + Ipv4Addr + Ipv6Addr + NonZero + NonZero + NonZero + NonZero + NonZero and $N others note: required by a bound in `core::str::::parse` --> $RUST/core/src/str/mod.rs + | + | pub fn parse(&self) -> Result { + | ^^^^^^^ required by this bound in `core::str::::parse` + = note: this error originates in the macro `micronfig::config` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/micronfig_macros/tests/sources/wrong_conversion_trait_tryfrom.stderr b/micronfig_macros/tests/sources/wrong_conversion_trait_tryfrom.stderr index e8a556c..2788e5a 100644 --- a/micronfig_macros/tests/sources/wrong_conversion_trait_tryfrom.stderr +++ b/micronfig_macros/tests/sources/wrong_conversion_trait_tryfrom.stderr @@ -1,26 +1,19 @@ -error[E0277]: the trait bound `u64: From` is not satisfied +error[E0277]: the trait bound `u64: TryFrom` is not satisfied --> tests/sources/wrong_conversion_trait_tryfrom.rs:1:1 | 1 | / micronfig::config! { 2 | | GARASAUTO: String => u64, 3 | | } - | | ^ - | | | - | |_the trait `From` is not implemented for `u64` - | in this macro invocation - | - ::: src/lib.rs - | - | pub fn config(input: TokenStream) -> TokenStream { - | ------------------------------------------------ in this expansion of `micronfig::config!` + | |_^ the trait `From` is not implemented for `u64`, which is required by `String: TryInto<_>` | = help: the following other types implement trait `From`: - > - > - > - > - > - > + `u64` implements `From` + `u64` implements `From` + `u64` implements `From` + `u64` implements `From` + `u64` implements `From` + `u64` implements `From` = note: required for `String` to implement `Into` = note: required for `u64` to implement `TryFrom` = note: required for `String` to implement `TryInto` + = note: this error originates in the macro `micronfig::config` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/micronfig_macros/tests/sources/wrong_start.stderr b/micronfig_macros/tests/sources/wrong_start.stderr index c386c51..e5dd9db 100644 --- a/micronfig_macros/tests/sources/wrong_start.stderr +++ b/micronfig_macros/tests/sources/wrong_start.stderr @@ -1,11 +1,27 @@ -error: first type of a conversion chain should always be literally `String`, other aliases are not allowed - --> tests/sources/wrong_start.rs:2:13 +error[E0308]: mismatched types + --> tests/sources/wrong_start.rs:1:1 | -2 | GARASAUTO: i64, - | ^^^ +1 | / micronfig::config! { +2 | | GARASAUTO: i64, +3 | | } + | | ^ + | | | + | |_expected `Option`, found `Option` + | expected due to this + | + = note: expected enum `Option` + found enum `Option` + = note: this error originates in the macro `micronfig::config` (in Nightly builds, run with -Z macro-backtrace for more info) -error[E0425]: cannot find function, tuple struct or tuple variant `GARASAUTO` in this scope - --> tests/sources/wrong_start.rs:7:20 +error[E0308]: mismatched types + --> tests/sources/wrong_start.rs:1:1 | -7 | println!("{:#?}", GARASAUTO()); - | ^^^^^^^^^ not found in this scope +1 | / micronfig::config! { +2 | | GARASAUTO: i64, +3 | | } + | | ^- help: try using a conversion method: `.to_string()` + | | | + | |_expected `String`, found `i64` + | expected due to this + | + = note: this error originates in the macro `micronfig::config` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/micronfig_macros/tests/sources/wrong_unqualified_noimport.stderr b/micronfig_macros/tests/sources/wrong_unqualified_noimport.stderr index 5ea88f1..9584333 100644 --- a/micronfig_macros/tests/sources/wrong_unqualified_noimport.stderr +++ b/micronfig_macros/tests/sources/wrong_unqualified_noimport.stderr @@ -1,12 +1,3 @@ -error[E0412]: cannot find type `PathBuf` in this scope - --> tests/sources/wrong_unqualified_noimport.rs:2:22 - | -2 | GARASAUTO: String > PathBuf, - | ^^^^^^^ not found in this scope - | - = help: consider importing this struct: - std::path::PathBuf - error[E0412]: cannot find type `PathBuf` in this scope --> tests/sources/wrong_unqualified_noimport.rs:2:22 | diff --git a/micronfig_macros/tests/tests.rs b/micronfig_macros/tests/tests.rs index 77bfcf3..d196ce0 100644 --- a/micronfig_macros/tests/tests.rs +++ b/micronfig_macros/tests/tests.rs @@ -34,6 +34,7 @@ pass!(string_multi_mixed); pass!(string_single_explicit); pass!(string_single_implicit); pass!(tryfrom_single_custom); +pass!(unqualified_import); fail!(wrong_conversion_longfatarrow); fail!(wrong_conversion_longthinarrow); @@ -47,5 +48,4 @@ fail!(wrong_nonsense_3); fail!(wrong_start); fail!(wrong_syntax_colon); fail!(wrong_syntax_type); -fail!(wrong_unqualified_import); fail!(wrong_unqualified_noimport);