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:
parent
b4d878c521
commit
0e33ea21a6
9 changed files with 96 additions and 106 deletions
|
@ -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]
|
||||||
|
|
|
@ -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,23 +93,22 @@ 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)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let items_code = input.iter().map(|item: &ConfigItem| {
|
let items_code = input.iter().map(|item: &ConfigItem| {
|
||||||
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) => {
|
||||||
|
@ -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! {
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
Loading…
Reference in a new issue