1
Fork 0
mirror of https://github.com/Steffo99/micronfig.git synced 2024-12-22 20:14:18 +00:00

Improve error messages and allow unqualified types

This commit is contained in:
Steffo 2024-12-19 10:57:24 +01:00
parent b4d878c521
commit 0e33ea21a6
Signed by: steffo
GPG key ID: 5ADA3868646C3FC0
9 changed files with 96 additions and 106 deletions

View file

@ -13,7 +13,7 @@ categories = ["config"]
all-features = true all-features = true
[dependencies] [dependencies]
syn = "2.0" syn = { version = "2.0", features = ["extra-traits"] }
quote = "1.0" quote = "1.0"
[dev-dependencies] [dev-dependencies]

View file

@ -1,17 +1,17 @@
extern crate proc_macro; extern crate proc_macro;
use proc_macro::TokenStream; use proc_macro::TokenStream;
use quote::{quote, ToTokens}; use quote::quote;
use syn::parse::{Parse, ParseStream}; use syn::parse::{Parse, ParseStream};
use syn::{Ident, parse_macro_input, Token, Type, TypePath}; use syn::{Ident, parse_macro_input, Token, Type, TypePath};
use syn::punctuated::Punctuated; use syn::punctuated::Punctuated;
type Config = Punctuated<ConfigItem, Token![,]>; type Config = Punctuated<ConfigItem, Token![,]>;
#[derive(Clone)] #[derive(Clone)]
struct ConfigItem { struct ConfigItem {
identifier: Ident, identifier: Ident,
optional: bool, optional: bool,
first: TypePath,
types: Vec<ConfigPair>, types: Vec<ConfigPair>,
} }
@ -36,34 +36,32 @@ impl Parse for ConfigItem {
let optional = input.lookahead1().peek(Token![?]); let optional = input.lookahead1().peek(Token![?]);
if optional { if optional {
input.parse::<Token![?]>() input.parse::<Token![?]>()
.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 => { true => {
input.parse::<Token![:]>() input.parse::<Token![:]>()
.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::<TypePath>()?; let first = input.parse::<TypePath>()?;
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 mut types = Vec::new(); let mut types = Vec::new();
while let Ok(typ) = input.parse::<ConfigPair>() { while let Ok(typ) = input.parse::<ConfigPair>() {
types.push(typ) 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,17 +93,14 @@ impl Parse for Conversion {
#[proc_macro] #[proc_macro]
pub fn config(input: TokenStream) -> TokenStream { 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! { let cache_code = quote! {
#[allow(non_snake_case)]
mod _cache {
pub static _lock: std::sync::OnceLock<micronfig::cache::Cache> = std::sync::OnceLock::new();
}
#[allow(non_snake_case)] #[allow(non_snake_case)]
fn _cache() -> &'static micronfig::cache::Cache { fn _cache() -> &'static micronfig::cache::Cache {
_cache::_lock.get_or_init(micronfig::cache::Cache::new) static LOCK: std::sync::OnceLock<micronfig::cache::Cache> = std::sync::OnceLock::new();
LOCK.get_or_init(micronfig::cache::Cache::new)
} }
}; };
@ -113,6 +108,8 @@ pub fn config(input: TokenStream) -> TokenStream {
let identifier = &item.identifier; let identifier = &item.identifier;
let identifier_string = identifier.to_string(); let identifier_string = identifier.to_string();
let type_first = &item.first;
let type_final = match item.types.last() { let type_final = match item.types.last() {
Some(pair) => { Some(pair) => {
let typ = pair.r#type.clone(); let typ = pair.r#type.clone();
@ -137,13 +134,17 @@ pub fn config(input: TokenStream) -> TokenStream {
}, },
(Conversion::TryFrom, true) => quote! { (Conversion::TryFrom, true) => quote! {
let value: Option<#typ> = value let value: Option<#typ> = value
.map(|v| v.try_into()) .map(|v| v
.map(|v| v.unwrap_or_else(|err| panic!("Couldn't perform conversion `{:?} => {:?}`: {:#?}", v, #identifier_string, err))); .try_into()
.unwrap_or_else(|err| panic!("{}: Couldn't perform `=> {:?}` conversion: {:#?}", #identifier_string, std::any::type_name::<#typ>(), err))
);
}, },
(Conversion::FromStr, true) => quote! { (Conversion::FromStr, true) => quote! {
let value: Option<#typ> = value let value: Option<#typ> = value
.map(|v| v.parse()) .map(|v| v
.map(|v| v.unwrap_or_else(|err| panic!("Couldn't perform conversion `{:?} > {:?}`: {:#?}", v, #identifier_string, err))); .parse()
.unwrap_or_else(|err| panic!("{}: Couldn't perform `> {:?}` conversion: {:#?}", #identifier_string, std::any::type_name::<#typ>(), err))
);
}, },
(Conversion::From, false) => quote! { (Conversion::From, false) => quote! {
let value: #typ = value let value: #typ = value
@ -152,12 +153,12 @@ pub fn config(input: TokenStream) -> TokenStream {
(Conversion::TryFrom, false) => quote! { (Conversion::TryFrom, false) => quote! {
let value: #typ = value let value: #typ = value
.try_into() .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! { (Conversion::FromStr, false) => quote! {
let value: #typ = value let value: #typ = value
.parse() .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! {}, true => quote! {},
false => quote! { false => quote! {
let value: String = value 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! { 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)] #[allow(non_snake_case)]
pub(crate) fn #identifier() -> &'static #type_final_option { 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 key = #identifier_string.as_ref();
let value: Option<std::string::String> = _cache().get(key); let value: Option<#type_first> = _cache().get(key);
#require_code #require_code
#conversion_code #conversion_code
@ -193,7 +191,10 @@ pub fn config(input: TokenStream) -> TokenStream {
} }
} }
}).reduce(|acc, new| { }).reduce(|acc, new| {
quote! { #acc #new } quote! {
#acc
#new
}
}); });
let quote = quote! { let quote = quote! {

View file

@ -4,21 +4,14 @@ error[E0277]: the trait bound `u64: From<String>` is not satisfied
1 | / micronfig::config! { 1 | / micronfig::config! {
2 | | GARASAUTO: String -> u64, 2 | | GARASAUTO: String -> u64,
3 | | } 3 | | }
| | ^ | |_^ the trait `From<String>` is not implemented for `u64`, which is required by `String: Into<_>`
| | |
| |_the trait `From<String>` is not implemented for `u64`
| in this macro invocation
|
::: src/lib.rs
|
| pub fn config(input: TokenStream) -> TokenStream {
| ------------------------------------------------ in this expansion of `micronfig::config!`
| |
= help: the following other types implement trait `From<T>`: = help: the following other types implement trait `From<T>`:
<u64 as From<bool>> `u64` implements `From<Char>`
<u64 as From<char>> `u64` implements `From<bool>`
<u64 as From<u8>> `u64` implements `From<char>`
<u64 as From<u16>> `u64` implements `From<u16>`
<u64 as From<u32>> `u64` implements `From<u32>`
<u64 as From<NonZeroU64>> `u64` implements `From<u8>`
= note: required for `String` to implement `Into<u64>` = note: required for `String` to implement `Into<u64>`
= note: this error originates in the macro `micronfig::config` (in Nightly builds, run with -Z macro-backtrace for more info)

View file

@ -4,25 +4,21 @@ error[E0277]: the trait bound `Infallible: FromStr` is not satisfied
1 | / micronfig::config! { 1 | / micronfig::config! {
2 | | GARASAUTO: String > std::convert::Infallible, 2 | | GARASAUTO: String > std::convert::Infallible,
3 | | } 3 | | }
| | ^ | |_^ the trait `FromStr` is not implemented for `Infallible`
| | |
| |_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!`
| |
= help: the following other types implement trait `FromStr`: = help: the following other types implement trait `FromStr`:
bool IpAddr
char Ipv4Addr
isize Ipv6Addr
i8 NonZero<i128>
i16 NonZero<i16>
i32 NonZero<i32>
i64 NonZero<i64>
i128 NonZero<i8>
and $N others and $N others
note: required by a bound in `core::str::<impl str>::parse` note: required by a bound in `core::str::<impl str>::parse`
--> $RUST/core/src/str/mod.rs --> $RUST/core/src/str/mod.rs
|
| pub fn parse<F: FromStr>(&self) -> Result<F, F::Err> {
| ^^^^^^^ required by this bound in `core::str::<impl str>::parse`
= note: this error originates in the macro `micronfig::config` (in Nightly builds, run with -Z macro-backtrace for more info)

View file

@ -1,26 +1,19 @@
error[E0277]: the trait bound `u64: From<String>` is not satisfied error[E0277]: the trait bound `u64: TryFrom<String>` is not satisfied
--> tests/sources/wrong_conversion_trait_tryfrom.rs:1:1 --> tests/sources/wrong_conversion_trait_tryfrom.rs:1:1
| |
1 | / micronfig::config! { 1 | / micronfig::config! {
2 | | GARASAUTO: String => u64, 2 | | GARASAUTO: String => u64,
3 | | } 3 | | }
| | ^ | |_^ the trait `From<String>` is not implemented for `u64`, which is required by `String: TryInto<_>`
| | |
| |_the trait `From<String>` is not implemented for `u64`
| in this macro invocation
|
::: src/lib.rs
|
| pub fn config(input: TokenStream) -> TokenStream {
| ------------------------------------------------ in this expansion of `micronfig::config!`
| |
= help: the following other types implement trait `From<T>`: = help: the following other types implement trait `From<T>`:
<u64 as From<bool>> `u64` implements `From<Char>`
<u64 as From<char>> `u64` implements `From<bool>`
<u64 as From<u8>> `u64` implements `From<char>`
<u64 as From<u16>> `u64` implements `From<u16>`
<u64 as From<u32>> `u64` implements `From<u32>`
<u64 as From<NonZeroU64>> `u64` implements `From<u8>`
= note: required for `String` to implement `Into<u64>` = note: required for `String` to implement `Into<u64>`
= note: required for `u64` to implement `TryFrom<String>` = note: required for `u64` to implement `TryFrom<String>`
= note: required for `String` to implement `TryInto<u64>` = note: required for `String` to implement `TryInto<u64>`
= note: this error originates in the macro `micronfig::config` (in Nightly builds, run with -Z macro-backtrace for more info)

View file

@ -1,11 +1,27 @@
error: first type of a conversion chain should always be literally `String`, other aliases are not allowed error[E0308]: mismatched types
--> tests/sources/wrong_start.rs:2:13 --> tests/sources/wrong_start.rs:1:1
| |
2 | GARASAUTO: i64, 1 | / micronfig::config! {
| ^^^ 2 | | GARASAUTO: i64,
3 | | }
| | ^
| | |
| |_expected `Option<i64>`, found `Option<String>`
| expected due to this
|
= note: expected enum `Option<i64>`
found enum `Option<String>`
= 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 error[E0308]: mismatched types
--> tests/sources/wrong_start.rs:7:20 --> tests/sources/wrong_start.rs:1:1
| |
7 | println!("{:#?}", GARASAUTO()); 1 | / micronfig::config! {
| ^^^^^^^^^ not found in this scope 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)

View file

@ -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 error[E0412]: cannot find type `PathBuf` in this scope
--> tests/sources/wrong_unqualified_noimport.rs:2:22 --> tests/sources/wrong_unqualified_noimport.rs:2:22
| |

View file

@ -34,6 +34,7 @@ pass!(string_multi_mixed);
pass!(string_single_explicit); pass!(string_single_explicit);
pass!(string_single_implicit); pass!(string_single_implicit);
pass!(tryfrom_single_custom); pass!(tryfrom_single_custom);
pass!(unqualified_import);
fail!(wrong_conversion_longfatarrow); fail!(wrong_conversion_longfatarrow);
fail!(wrong_conversion_longthinarrow); fail!(wrong_conversion_longthinarrow);
@ -47,5 +48,4 @@ fail!(wrong_nonsense_3);
fail!(wrong_start); fail!(wrong_start);
fail!(wrong_syntax_colon); fail!(wrong_syntax_colon);
fail!(wrong_syntax_type); fail!(wrong_syntax_type);
fail!(wrong_unqualified_import);
fail!(wrong_unqualified_noimport); fail!(wrong_unqualified_noimport);