diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 560ec4e..7958573 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -21,10 +21,14 @@ jobs: rustup override set ${{ matrix.rust }} cargo update + cd "${{github.workspace}}/const_panic_proc_macros/" + cargo test + cd "${{github.workspace}}/" cargo build --no-default-features cargo build cargo test --features "test" cargo test --no-default-features --features "test " cargo test --no-default-features --features "test non_basic" + cargo test --no-default-features --features "test non_basic derive" diff --git a/Cargo.toml b/Cargo.toml index 161f16e..5f6f8f8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "const_panic" -version = "0.1.1" +version = "0.2.0" authors = ["rodrimati1992 "] edition = "2021" license = "Zlib" @@ -17,15 +17,26 @@ include = [ "LICENSE-ZLIB.md", ] -[dependencies] +[workspace] + +[dependencies.const_panic_proc_macros] +version = "=0.2.0" +path = "./const_panic_proc_macros/" +optional = true + +[dev-dependencies.rand] +version = "0.8.4" +default_features = false +features = ["small_rng"] [features] default = ["non_basic"] non_basic = [] docsrs = [] +derive = ["const_panic_proc_macros"] # private feature test = [] [package.metadata.docs.rs] -features = ["non_basic", "docsrs"] +features = ["derive", "docsrs"] diff --git a/Changelog.md b/Changelog.md index 8e72737..81393f7 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,5 +1,38 @@ This changelog is a summary of the changes made in each release. +# 0.2 + +### 0.2.0 + +Added `concat_assert` macro. + +Added `PanicFmt` derive macro. + +Added `"derive"` crate feature, to enable the `PanicFmt` derive. + +Made breaking changes to `impl_panicfmt` to allow generic implementations with type and const parameters. + +Added `NumberFmt` enum, for choosing how numbers are formatted. + +Added `FmtArg::{BIN, ALT_BIN, HEX. ALT_HEX}` associated constants. + +Added `FmtArg::{set_hex, set_bin}` methods. + +Added `PanicVal::from_short_str` constructor. + +Added support for binary and hexadecimal formatting in macros. + +Added `PackedFmtArg` type (which requires the non_basic feature). + +Added `const_panic::fmt::SHORT_STRING_CAP` constant with the capacity of a `ShortString`. + + +Changed `PanicVal` such that only strings can be left or right padded. + +Removed the `PanicVal::{set_leftpad, set_rightpad}` methods. + +Declared `const_panic_proc_macros` crate, depended by `const_panic` when the `"derive"` feature is enabled. + # 0.1 ### 0.1.1 diff --git a/README.md b/README.md index 55853bd..8d1a229 100644 --- a/README.md +++ b/README.md @@ -17,8 +17,8 @@ All of the types that implement the [`PanicFmt`] trait can be formatted in panic ### Basic -```rust,compile_fail -use const_panic::concat_panic; +```compile_fail +use const_panic::concat_assert; const FOO: u32 = 10; const BAR: u32 = 0; @@ -26,15 +26,16 @@ const _: () = assert_non_zero(FOO, BAR); #[track_caller] const fn assert_non_zero(foo: u32, bar: u32) { - if foo == 0 || bar == 0 { - concat_panic!("\nneither foo nor bar can be zero!\nfoo: ", foo, "\nbar: ", bar) + concat_assert!{ + foo != 0 && bar != 0, + "\nneither foo nor bar can be zero!\nfoo: ", foo, "\nbar: ", bar } } ``` The above code fails to compile with this error: ```text error[E0080]: evaluation of constant value failed - --> src/lib.rs:17:15 + --> src/lib.rs:20:15 | 8 | const _: () = assert_non_zero(FOO, BAR); | ^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at ' @@ -42,6 +43,7 @@ neither foo nor bar can be zero! foo: 10 bar: 0', src/lib.rs:8:15 ``` + When called at runtime ```should_panic use const_panic::concat_panic; @@ -69,16 +71,18 @@ note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace Panic formatting for custom types can be done in these ways (in increasing order of verbosity): +- Using the [`PanicFmt` derive] macro +(requires the opt-in `"derive"` feature) - Using the [`impl_panicfmt`] macro (requires the default-enabled `"non_basic"` feature) - Using the [`flatten_panicvals`] macro (requires the default-enabled `"non_basic"` feature) - Manually implementing the [`PanicFmt`] trait as described in its docs. -This example uses the [`impl_panicfmt`] approach. +This example uses the [`PanicFmt` derive] approach. -```rust,compile_fail -use const_panic::concat_panic; +```compile_fail +use const_panic::{PanicFmt, concat_panic}; const LAST: u8 = { Foo{ @@ -110,46 +114,23 @@ impl Foo<'_> { } } +#[derive(PanicFmt)] struct Foo<'a> { x: &'a [u8], y: Bar, z: Qux, } -// You need to replace non-static lifetimes with `'_` here. -const_panic::impl_panicfmt! { - impl Foo<'_>; - - struct Foo { - x: &[u8], - y: Bar, - z: Qux, - } -} - +#[derive(PanicFmt)] struct Bar(bool, bool); -const_panic::impl_panicfmt! { - impl Bar; - - struct Bar(bool, bool); -} - +#[derive(PanicFmt)] enum Qux { Up, Down { x: u32, y: u32 }, Left(u64), } -const_panic::impl_panicfmt!{ - impl Qux; - - enum Qux { - Up, - Down { x: u32, y: u32 }, - Left(u64), - } -} ``` The above code fails to compile with this error: ```text @@ -162,7 +143,7 @@ error[E0080]: evaluation of constant value failed 10 | | z: Qux::Left(23), 11 | | }.pop().1 | |___________^ the evaluated program panicked at ' -expected a non-empty Foo, found: +expected a non-empty Foo, found: Foo { x: [], y: Bar( @@ -174,6 +155,7 @@ Foo { ), }', src/lib.rs:11:7 + ``` # Limitations @@ -184,6 +166,11 @@ because `const_panic` macros use duck typing to call methods on those arguments. One effect of that limitation is that you will have to pass suffixed integer literals (eg: `100u8`) when those integers aren't inferred to be a concrete type. +### Panic message length + +The panic message can only be up to [`MAX_PANIC_MSG_LEN`] long, +after which it is truncated. + # Cargo features - `"non_basic"`(enabled by default): @@ -192,9 +179,12 @@ Enables support for formatting structs, enums, and arrays. Without this feature, you can effectively only format primitive types (custom types can manually implement formatting with more difficulty). +- `"derive"`(disabled by default): +Enables the [`PanicFmt` derive] macro. + # Plans -Adding a derive macro, under an opt-in "derive" feature. +None for now # No-std support @@ -204,7 +194,8 @@ Adding a derive macro, under an opt-in "derive" feature. This requires Rust 1.57.0, because it uses the `panic` macro in a const context. - +[`PanicFmt` derive]: https://docs.rs/const_panic/*/const_panic/derive.PanicFmt.html [`PanicFmt`]: https://docs.rs/const_panic/*/const_panic/fmt/trait.PanicFmt.html [`impl_panicfmt`]: https://docs.rs/const_panic/*/const_panic/macro.impl_panicfmt.html -[`flatten_panicvals`]: https://docs.rs/const_panic/*/const_panic/macro.flatten_panicvals.html \ No newline at end of file +[`flatten_panicvals`]: https://docs.rs/const_panic/*/const_panic/macro.flatten_panicvals.html +[`MAX_PANIC_MSG_LEN`]: https://docs.rs/const_panic/*/const_panic/constant.MAX_PANIC_MSG_LEN.html \ No newline at end of file diff --git a/const_panic_proc_macros/Cargo.toml b/const_panic_proc_macros/Cargo.toml new file mode 100644 index 0000000..19c2a0d --- /dev/null +++ b/const_panic_proc_macros/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "const_panic_proc_macros" +version = "0.2.0" +authors = ["rodrimati1992 "] +edition = "2021" +license = "Zlib" +description = "Implementation detail of the `const_panic` crate" +keywords = [] +categories = [] +repository = "https://github.com/rodrimati1992/const_panic/" +include = [ + "Cargo.toml", + "src/**/*.rs", + "../README.md", + "LICENSE-ZLIB.md", +] + +[badges] +travis-ci = { repository = "rodrimati1992/const_format_crates/" } + +[lib] +proc-macro = true + +[dependencies] +quote = "1.0.7" +proc-macro2 = "1.0.19" +unicode-xid = "0.2" + +[dependencies.syn] +version = "1.0.38" + diff --git a/const_panic_proc_macros/LICENSE-ZLIB.md b/const_panic_proc_macros/LICENSE-ZLIB.md new file mode 100644 index 0000000..22794b1 --- /dev/null +++ b/const_panic_proc_macros/LICENSE-ZLIB.md @@ -0,0 +1,17 @@ +Copyright (c) 2021 Matias Rodriguez. + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. \ No newline at end of file diff --git a/const_panic_proc_macros/src/datastructure.rs b/const_panic_proc_macros/src/datastructure.rs new file mode 100644 index 0000000..74e0dd2 --- /dev/null +++ b/const_panic_proc_macros/src/datastructure.rs @@ -0,0 +1,290 @@ +use syn::{ + self, Attribute, Data, DeriveInput, Field as SynField, Fields as SynFields, Generics, Ident, + Type, Visibility, +}; + +use quote::ToTokens; + +use proc_macro2::TokenStream; + +use alloc::format; + +use core::fmt::{self, Display}; + +use alloc::vec::Vec; + +////////////////////////////////////////////////////////////////////////////// + +/// A type definition(enum,struct,union). +#[derive(Clone)] +pub struct DataStructure<'a> { + pub vis: &'a Visibility, + pub name: &'a Ident, + pub generics: &'a Generics, + pub attrs: &'a [Attribute], + + /// Whether this is a struct/union/enum. + pub data_variant: DataVariant, + + // the amount of lifetime parameters in the type + pub lifetime_count: usize, + + /// The variants in the type definition. + /// + /// If it is a struct or a union this only has 1 element. + pub variants: Vec>, +} + +impl<'a> DataStructure<'a> { + pub fn new(ast: &'a DeriveInput) -> Self { + let name = &ast.ident; + + let data_variant: DataVariant; + + let mut variants = Vec::new(); + + match &ast.data { + Data::Enum(enum_) => { + for (variant, var) in enum_.variants.iter().enumerate() { + variants.push(Struct::new( + StructParams { + variant: variant, + attrs: &var.attrs, + name: &var.ident, + }, + &var.fields, + )); + } + data_variant = DataVariant::Enum; + } + Data::Struct(struct_) => { + variants.push(Struct::new( + StructParams { + variant: 0, + attrs: &[], + name: name, + }, + &struct_.fields, + )); + data_variant = DataVariant::Struct; + } + + Data::Union(union_) => { + let fields = Some(&union_.fields.named); + let sk = StructKind::Braced; + let vari = Struct::with_fields( + StructParams { + variant: 0, + attrs: &[], + name: name, + }, + sk, + fields, + ); + variants.push(vari); + data_variant = DataVariant::Union; + } + } + + Self { + vis: &ast.vis, + name, + attrs: &ast.attrs, + generics: &ast.generics, + data_variant, + lifetime_count: ast.generics.lifetimes().count(), + variants, + } + } +} + +////////////////////////////////////////////////////////////////////////////// + +/// Whether the struct is tupled or not. +#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Ord, Eq, Hash)] +pub enum StructKind { + /// structs declared using the `struct Name( ... ) syntax. + Tupled, + /// structs declared using the `struct Name{ ... }` or `struct name;` syntaxes + Braced, +} + +#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Ord, Eq, Hash)] +pub enum DataVariant { + Struct, + Enum, + Union, +} + +#[derive(Copy, Clone)] +pub struct FieldIndex { + pub variant: usize, + pub pos: usize, +} + +////////////////////////////////////////////////////////////////////////////// + +#[derive(Copy, Clone)] +struct StructParams<'a> { + variant: usize, + attrs: &'a [Attribute], + name: &'a Ident, +} + +/// A struct/union or a variant of an enum. +#[derive(Clone)] +pub struct Struct<'a> { + /// The attributes of this `Struct`. + /// + /// If this is a struct/union:these is the same as DataStructure.attrs. + /// + /// If this is an enum:these are the attributes on the variant. + pub attrs: &'a [Attribute], + /// The name of this `Struct`. + /// + /// If this is a struct/union:these is the same as DataStructure.name. + /// + /// If this is an enum:this is the name of the variant. + pub name: &'a Ident, + pub kind: StructKind, + pub fields: Vec>, + _priv: (), +} + +impl<'a> Struct<'a> { + fn new(p: StructParams<'a>, fields: &'a SynFields) -> Self { + let kind = match *fields { + SynFields::Named { .. } => StructKind::Braced, + SynFields::Unnamed { .. } => StructKind::Tupled, + SynFields::Unit { .. } => StructKind::Braced, + }; + let fields = match fields { + SynFields::Named(f) => Some(&f.named), + SynFields::Unnamed(f) => Some(&f.unnamed), + SynFields::Unit => None, + }; + + Self::with_fields(p, kind, fields) + } + + fn with_fields(p: StructParams<'a>, kind: StructKind, fields: Option) -> Self + where + I: IntoIterator, + { + let fields = match fields { + Some(x) => Field::from_iter(p, x), + None => Vec::new(), + }; + + Self { + attrs: p.attrs, + name: p.name, + kind, + fields, + _priv: (), + } + } +} + +////////////////////////////////////////////////////////////////////////////// + +/// Represent a struct field +/// +#[derive(Clone)] +pub struct Field<'a> { + pub index: FieldIndex, + pub attrs: &'a [Attribute], + /// identifier for the field,which is either an index(in a tuple struct) or a name. + pub ident: FieldIdent<'a>, + pub pattern_ident: Ident, + pub ty: &'a Type, +} + +impl<'a> Field<'a> { + fn new(index: FieldIndex, field: &'a SynField) -> Self { + let span; + let ident = match field.ident.as_ref() { + Some(ident) => { + span = ident.span(); + FieldIdent::Named(ident) + } + None => { + span = syn::spanned::Spanned::span(&field.ty); + FieldIdent::new_index(index.pos) + } + }; + let pattern_ident = Ident::new(&format!("f{}_7ac4rtizw8q", ident), span); + + Self { + index, + attrs: &field.attrs, + ident, + pattern_ident, + ty: &field.ty, + } + } + + /// Gets the identifier of this field as an `&Ident`. + #[allow(dead_code)] + pub fn pattern_ident(&self) -> &Ident { + &self.pattern_ident + } + + fn from_iter(p: StructParams<'a>, fields: I) -> Vec + where + I: IntoIterator, + { + fields + .into_iter() + .enumerate() + .map(|(pos, f)| { + let fi = FieldIndex { + variant: p.variant, + pos, + }; + Field::new(fi, f) + }) + .collect() + } +} + +////////////////////////////////////////////////////////////////////////////// + +#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)] +pub enum FieldIdent<'a> { + Index(usize), + Named(&'a Ident), +} + +impl<'a> Display for FieldIdent<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + FieldIdent::Index(x) => Display::fmt(x, f), + FieldIdent::Named(x) => Display::fmt(x, f), + } + } +} + +impl<'a> ToTokens for FieldIdent<'a> { + fn to_tokens(&self, tokens: &mut TokenStream) { + match *self { + FieldIdent::Index(ind) => syn::Index::from(ind).to_tokens(tokens), + FieldIdent::Named(name) => name.to_tokens(tokens), + } + } +} + +impl<'a> FieldIdent<'a> { + fn new_index(index: usize) -> Self { + FieldIdent::Index(index) + } +} + +////////////////////////////////////////////////////////////////////////////// + +#[derive(Copy, Clone, PartialEq)] +pub(super) enum GenParamKind { + Lifetime, + Type, + Const, +} diff --git a/const_panic_proc_macros/src/derive_debug.rs b/const_panic_proc_macros/src/derive_debug.rs new file mode 100644 index 0000000..414263a --- /dev/null +++ b/const_panic_proc_macros/src/derive_debug.rs @@ -0,0 +1,339 @@ +use crate::{ + datastructure::{DataStructure, DataVariant, GenParamKind, StructKind}, + syntax::ImplHeader, +}; + +use proc_macro2::{Span, TokenStream as TokenStream2}; + +use quote::quote; + +use syn::{punctuated::Punctuated, DeriveInput, Ident}; + +use alloc::{string::ToString, vec::Vec}; + +use self::attribute_parsing::GenParamIgnorance; + +mod attribute_parsing; + +#[cfg(test)] +mod tests; + +pub(crate) fn derive_constdebug_impl(input: DeriveInput) -> syn::Result { + let ds = &DataStructure::new(&input); + let config = attribute_parsing::parse_attributes(ds)?; + let crate_path = &config.crate_path; + + let name = ds.name; + + if config.impls.is_empty() { + let not_ignored = config + .gen_params_props + .iter() + .zip(ds.generics.type_params()) + .filter(|(x, _)| matches!(x.ignored, GenParamIgnorance::Included)) + .map(|(_, tp)| &tp.ident) + .collect::>(); + + if !not_ignored.is_empty() { + let not_ignored = not_ignored.into_iter(); + let msg = alloc::format!( + concat!( + "these type parameters were not ignored or replaced with concrete types:\n", + " {0}\n", + "You must use either or both of these attributes:\n", + "- `#[pfmt(ignore({0}))]`:", + "if the type parameters are only used in marker types (eg: `PhantomData`).\n", + "- `#[pfmt(impl ...)]`:", + "To implement panic formatting with concrete types for those type parameters", + "(this attribute can be used multiple times to add impls).\n", + ), + quote!(#(#not_ignored),*) + ); + return Err(syn::Error::new(Span::call_site(), msg)); + } + } + + let (impl_generics, ty_generics, where_clause) = ds.generics.split_for_impl(); + let preds = Punctuated::new(); + let preds = where_clause.map_or(&preds, |x| &x.predicates).into_iter(); + let preds = quote!(#( #preds, )*); + + let delimiters = ds + .variants + .iter() + .map(|v| match v.kind { + StructKind::Tupled => quote!(__cp_bCj7dq3Pud::TypeDelim::Tupled), + StructKind::Braced => quote!(__cp_bCj7dq3Pud::TypeDelim::Braced), + }) + .collect::>(); + + let mut field_counters = ds.variants.iter().enumerate().map(|(v_index, v)| { + let field_amount = v.fields.len(); + let field_tys = v.fields.iter().map(|x| x.ty); + let delimiter = &delimiters[v_index]; + + quote!( + __cp_bCj7dq3Pud::ComputePvCount { + field_amount: #field_amount, + summed_pv_count: { + 0 + #( + <#field_tys as __cp_bCj7dq3Pud::PanicFmt>::PV_COUNT )* + }, + delimiter: #delimiter, + }.call() + ) + }); + + let pv_count_init; + let match_prefix; + + match ds.data_variant { + DataVariant::Struct => { + pv_count_init = field_counters.next().unwrap(); + match_prefix = quote!(); + } + DataVariant::Enum => { + pv_count_init = quote!( + __cp_bCj7dq3Pud::utils::slice_max_usize(&[ + #( + #field_counters + ),* + ]) + ); + match_prefix = quote!(Self::); + } + DataVariant::Union => { + return Err(syn::Error::new( + Span::call_site(), + "unions are not supported", + )) + } + } + + let args_for_inherent_impl = ArgsForInherentImpl { + comma_sep: Ident::new("COMMA_SEP", Span::call_site()), + comma_term: Ident::new("COMMA_TERM", Span::call_site()), + ds, + delimiters: &delimiters, + match_prefix, + }; + + let single_impl_ihapvcs: ImplHeaderAndPvCountSelf; + let vec_impl_ihapvcs: Vec; + let impl_ihapvcs: &[ImplHeaderAndPvCountSelf]; + + if config.impls.is_empty() { + let replaced_args = ds + .generics + .params + .iter() + .zip(&config.gen_params_props) + .map(|(gp, gpp)| gpp.tokenize_arg(gp)); + + single_impl_ihapvcs = ImplHeaderAndPvCountSelf { + impl_header: quote! { + impl #impl_generics #name #ty_generics + where + #preds + }, + pvcount_self: quote!(#name<#(#replaced_args),*>), + }; + + impl_ihapvcs = core::slice::from_ref(&single_impl_ihapvcs); + } else { + vec_impl_ihapvcs = config + .impls + .iter() + .map( + |ImplHeader { + generics, + self_args, + }| { + let (impl_generics, _, where_clause) = generics.split_for_impl(); + + let additional_preds = Punctuated::new(); + let additional_preds = where_clause + .map_or(&additional_preds, |x| &x.predicates) + .into_iter(); + + let impl_header = quote! { + impl #impl_generics #name #self_args + where + #( #additional_preds, )* + #preds + }; + + let replaced_args = self_args + .args + .iter() + .zip(&config.gen_params_props) + .map(|(ga, gpp)| gpp.tokenize_arg(ga)); + + let pvcount_self = quote!(#name<#(#replaced_args),*>); + + ImplHeaderAndPvCountSelf { + impl_header, + pvcount_self, + } + }, + ) + .collect(); + impl_ihapvcs = &vec_impl_ihapvcs; + } + + let impl_ihapvcs_mapped = impl_ihapvcs + .iter() + .map(|impl_ihapvc| emit_inherent_impl(impl_ihapvc, &args_for_inherent_impl)); + + let ty_params = ds + .generics + .type_params() + .zip( + config + .gen_params_props + .iter() + .filter(|x| matches!(x.kind, GenParamKind::Type)), + ) + .filter(|(_, prop)| matches!(prop.ignored, GenParamIgnorance::Included)) + .map(|(t, _)| &t.ident); + + let ret = quote! { + use #crate_path as __cp_bCj7dq3Pud; + + impl #impl_generics __cp_bCj7dq3Pud::PanicFmt for #name #ty_generics + where + #preds + #(#ty_params: __cp_bCj7dq3Pud::PanicFmt,)* + { + type This = Self; + type Kind = __cp_bCj7dq3Pud::IsCustomType; + + const PV_COUNT: __cp_bCj7dq3Pud::__::usize = #pv_count_init; + } + + #(#impl_ihapvcs_mapped)* + }; + + let ret = quote!( + const _: () = { + #ret + }; + ); + + if config.debug_print { + panic!("\n\n\n{}\n\n\n", ret); + } + Ok(ret) +} + +struct ImplHeaderAndPvCountSelf { + impl_header: TokenStream2, + // The Self type with generic arguments replaced so that they can be used + // to get the PV_COUNT associated constant + pvcount_self: TokenStream2, +} + +struct ArgsForInherentImpl<'a> { + comma_sep: Ident, + comma_term: Ident, + ds: &'a DataStructure<'a>, + delimiters: &'a [TokenStream2], + match_prefix: TokenStream2, +} + +fn emit_inherent_impl( + ImplHeaderAndPvCountSelf { + impl_header, + pvcount_self, + }: &ImplHeaderAndPvCountSelf, + ArgsForInherentImpl { + comma_sep, + comma_term, + ds, + delimiters, + match_prefix, + }: &ArgsForInherentImpl<'_>, +) -> TokenStream2 { + let get_pv_count = quote!(<#pvcount_self as __cp_bCj7dq3Pud::PanicFmt>::PV_COUNT); + + let branches = ds.variants.iter().enumerate().map(|(v_index, v)| { + let vname = v.name; + let vsname = vname.to_string(); + + if v.fields.is_empty() { + quote!( + #match_prefix #vname {} => { + __cp_bCj7dq3Pud::__::flatten_panicvals::<{#get_pv_count}>(&[&[ + __cp_bCj7dq3Pud::PanicVal::write_str(#vsname) + ]]) + } + ) + } else { + let last_field_pos = v.fields.len().saturating_sub(1); + let field_names = v.fields.iter().map(|f| &f.ident); + let field_patia = v.fields.iter().map(|f| &f.pattern_ident); + let delimiter = &delimiters[v_index]; + + let field_fmt = v.fields.iter().map(|f| { + let field_patib = &f.pattern_ident; + + let comma = if f.index.pos == last_field_pos { + &comma_term + } else { + &comma_sep + }; + + let field_name_colon = if let StructKind::Braced = v.kind { + let fname = ::alloc::format!("{}: ", f.ident); + + quote!( + &[__cp_bCj7dq3Pud::PanicVal::write_str(#fname)], + ) + } else { + TokenStream2::new() + }; + + quote!( + #field_name_colon + &__cp_bCj7dq3Pud::PanicFmt::PROOF + .infer(#field_patib) + .coerce(#field_patib) + .to_panicvals(fmtarg), + &__cp_bCj7dq3Pud::fmt::#comma + .to_panicvals(fmtarg), + ) + }); + + quote!( + #match_prefix #vname { #(#field_names: #field_patia,)* } => { + let (open, close) = #delimiter.get_open_and_close(); + + __cp_bCj7dq3Pud::__::flatten_panicvals::<{#get_pv_count}>(&[ + &[ + __cp_bCj7dq3Pud::PanicVal::write_str(#vsname), + open.to_panicval(fmtarg) + ], + #( #field_fmt )* + &close.to_panicvals(fmtarg.unindent()), + ]) + } + ) + } + }); + + quote!( + #impl_header + { + pub const fn to_panicvals( + &self, + mut fmtarg: __cp_bCj7dq3Pud::FmtArg, + ) -> [__cp_bCj7dq3Pud::PanicVal<'_>; #get_pv_count] { + fmtarg = fmtarg.indent(); + + match self { + #(#branches)* + } + } + } + ) +} diff --git a/const_panic_proc_macros/src/derive_debug/attribute_parsing.rs b/const_panic_proc_macros/src/derive_debug/attribute_parsing.rs new file mode 100644 index 0000000..aaaab29 --- /dev/null +++ b/const_panic_proc_macros/src/derive_debug/attribute_parsing.rs @@ -0,0 +1,280 @@ +use crate::{ + datastructure::{DataStructure, DataVariant, Field, GenParamKind, Struct}, + syntax::ImplHeader, + utils::{ParseBufferExt, SynResultExt}, + TokenStream2, +}; + +use syn::{ + parse::{ParseBuffer, Parser}, + Attribute, GenericParam, Ident, Token, +}; + +use quote::{quote, ToTokens}; + +use alloc::vec::Vec; + +use core::marker::PhantomData; + +mod keyword { + syn::custom_keyword!(debug_print); + syn::custom_keyword!(ignore); +} + +#[derive(Copy, Clone)] +pub(crate) enum ParseCtx<'a> { + Container, + Variant(usize, &'a Struct<'a>), + Field(&'a Field<'a>), +} + +struct ParsedAttributes<'a> { + debug_print: bool, + crate_path: syn::Path, + impls: Vec, + gen_params_props: Vec>, + type_const_params: Vec, + _marker: PhantomData<&'a ()>, +} + +pub(super) struct Configuration<'a> { + pub(super) debug_print: bool, + pub(super) crate_path: syn::Path, + pub(super) impls: Vec, + pub(super) gen_params_props: Vec>, + _marker: PhantomData<&'a ()>, +} + +pub(super) fn parse_attributes<'a>(ds: &'a DataStructure<'a>) -> syn::Result> { + let mut this = ParsedAttributes { + debug_print: false, + crate_path: syn::parse_quote!(::const_panic), + impls: Vec::new(), + gen_params_props: ds + .generics + .params + .iter() + .map(|ga| { + let (kind, ignored) = match ga { + GenericParam::Lifetime { .. } => { + (GenParamKind::Lifetime, GenParamIgnorance::Ignored) + } + GenericParam::Type { .. } => (GenParamKind::Type, GenParamIgnorance::Included), + GenericParam::Const { .. } => (GenParamKind::Const, GenParamIgnorance::Ignored), + }; + + GenParamProps { kind, ignored } + }) + .collect(), + type_const_params: ds + .generics + .params + .iter() + .filter_map(|ga| match ga { + GenericParam::Lifetime { .. } => None, + GenericParam::Type(x) => Some(x.ident.clone()), + GenericParam::Const(x) => Some(x.ident.clone()), + }) + .collect(), + _marker: PhantomData, + }; + + let mut res = syn::Result::Ok(()); + + for attr in ds.attrs { + res.combine_err(parse_attribute(&mut this, ds, ParseCtx::Container, attr)); + } + + if ds.data_variant == DataVariant::Enum { + for (i, v) in ds.variants.iter().enumerate() { + let ctx = ParseCtx::Variant(i, v); + for attr in v.attrs { + res.combine_err(parse_attribute(&mut this, ds, ctx, attr)); + } + } + } + + for v in &ds.variants { + for f in &v.fields { + for attr in f.attrs { + res.combine_err(parse_attribute(&mut this, ds, ParseCtx::Field(f), attr)); + } + } + } + + res?; + + finish(this, ds) +} + +fn parse_attribute<'a>( + this: &mut ParsedAttributes<'a>, + ds: &'a DataStructure<'a>, + ctx: ParseCtx<'a>, + attribute: &Attribute, +) -> syn::Result<()> { + if attribute.path.is_ident("pfmt") { + let closure = + move |input: &'_ ParseBuffer<'_>| parse_helper_attribute(this, ds, ctx, input); + + if attribute.tokens.is_empty() { + Parser::parse2(closure, crate::TokenStream2::new()) + } else { + attribute.parse_args_with(closure) + } + } else { + Ok(()) + } +} + +fn parse_helper_attribute<'a>( + this: &mut ParsedAttributes<'a>, + ds: &'a DataStructure<'a>, + ctx: ParseCtx<'a>, + input: &'_ ParseBuffer<'_>, +) -> syn::Result<()> { + let empty = &crate::utils::Empty(input.span()); + + if let Some(_) = input.peek_parse(keyword::debug_print)? { + check_is_container(&ctx, empty)?; + + this.debug_print = true; + } else if let Some(_) = input.peek_parse(Token!(crate))? { + check_is_container(&ctx, empty)?; + + input.parse::()?; + this.crate_path = input.parse::()?; + } else if input.peek(Token!(impl)) { + check_is_container(&ctx, empty)?; + this.impls.push(ImplHeader::parse(ds.name, input)?); + } else if let Some(_) = input.peek_parse(keyword::ignore)? { + check_is_container(&ctx, empty)?; + + let contents; + let _ = syn::parenthesized!(contents in input); + + if contents.is_empty() { + return Ok(()); + } + loop { + let ident = contents.parse::()?; + let gpi: usize = this + .type_const_params + .iter() + .position(|x| *x == ident) + .map(|pos| pos + ds.lifetime_count) + .ok_or_else(|| { + syn::Error::new(ident.span(), "Expected name of a type or const parameter") + })?; + + let gen_props = &mut this.gen_params_props[gpi]; + + gen_props.ignored = if let Some(_) = contents.peek_parse(syn::Token!(=))? { + let replacement = match gen_props.kind { + GenParamKind::Lifetime => unreachable!(), + GenParamKind::Type => contents.parse::()?.into_token_stream(), + GenParamKind::Const => contents.parse::()?.into_token_stream(), + }; + GenParamIgnorance::Replaced(replacement) + } else { + GenParamIgnorance::Ignored + }; + + if contents.is_empty() { + break; + } else { + contents.parse::()?; + } + } + } else { + let span = input.parse::()?.span(); + return Err(syn::Error::new(span, "Invalid attribute")); + } + Ok(()) +} + +fn finish<'a>( + this: ParsedAttributes<'a>, + _ds: &'a DataStructure<'a>, +) -> syn::Result> { + let ParsedAttributes { + debug_print, + crate_path, + impls, + gen_params_props, + type_const_params: _, + _marker, + } = this; + + Ok(Configuration { + debug_print, + crate_path, + impls, + gen_params_props, + _marker, + }) +} + +pub(crate) fn check_is_container( + ctx: &ParseCtx<'_>, + sp: &dyn syn::spanned::Spanned, +) -> syn::Result<()> { + if matches!(ctx, ParseCtx::Container) { + Ok(()) + } else { + Err(syn::Error::new( + sp.span(), + "Can only use this attribute above the type definition", + )) + } +} + +pub(super) struct GenParamProps<'a> { + pub(super) kind: GenParamKind, + pub(super) ignored: GenParamIgnorance<'a>, +} + +pub(super) enum GenParamIgnorance<'a> { + Included, + Ignored, + Replaced(TokenStream2), + #[allow(dead_code)] + ReplacedB(&'a TokenStream2), +} + +impl<'a> GenParamProps<'a> { + #[allow(dead_code)] + pub fn reborrow(&self) -> GenParamProps<'_> { + GenParamProps { + kind: self.kind, + ignored: self.ignored.reborrow(), + } + } + + pub fn tokenize_arg(&self, arg: &A) -> TokenStream2 + where + A: ToTokens, + { + match (self.kind, &self.ignored) { + (_, GenParamIgnorance::Included) => arg.to_token_stream(), + (GenParamKind::Lifetime, GenParamIgnorance::Ignored) => quote!('_), + (GenParamKind::Type, GenParamIgnorance::Ignored) => quote!(()), + (GenParamKind::Const, GenParamIgnorance::Ignored) => { + quote!({ __cp_bCj7dq3Pud::__::ConstDefault::DEFAULT }) + } + (_, GenParamIgnorance::Replaced(x)) => x.to_token_stream(), + (_, GenParamIgnorance::ReplacedB(x)) => x.to_token_stream(), + } + } +} + +impl<'a> GenParamIgnorance<'a> { + pub fn reborrow(&self) -> GenParamIgnorance<'_> { + match self { + GenParamIgnorance::Included => GenParamIgnorance::Included, + GenParamIgnorance::Ignored => GenParamIgnorance::Ignored, + GenParamIgnorance::Replaced(x) => GenParamIgnorance::ReplacedB(x), + GenParamIgnorance::ReplacedB(x) => GenParamIgnorance::ReplacedB(*x), + } + } +} diff --git a/const_panic_proc_macros/src/derive_debug/tests.rs b/const_panic_proc_macros/src/derive_debug/tests.rs new file mode 100644 index 0000000..24d5651 --- /dev/null +++ b/const_panic_proc_macros/src/derive_debug/tests.rs @@ -0,0 +1,181 @@ +use crate::test_utils::StrExt; + +use alloc::string::{String, ToString}; + +fn process_str(s: &str) -> Result { + syn::parse_str(s) + .and_then(crate::derive_debug::derive_constdebug_impl) + .map(|x| x.to_string()) + .map_err(|e| e.to_compile_error().to_string()) +} + +#[test] +fn ignored_generic_arguments() { + { + let s = process_str( + r#" + #[pfmt(ignore(A, B))] + pub struct IgnoredGenericParams<'a, A, B, const X: u32, const Y: char> ( + pub &'a u32, + pub PhantomData, + pub PhantomData, + ); + "#, + ) + .unwrap(); + assert!( + s.consecutive_unspace(&[r#" + ; + < + IgnoredGenericParams< + '_, + (), + (), + {__cp_bCj7dq3Pud::__::ConstDefault::DEFAULT}, + {__cp_bCj7dq3Pud::__::ConstDefault::DEFAULT} + > + as __cp_bCj7dq3Pud::PanicFmt + >::PV_COUNT + ] + "#]), + "\n{}\n", + s, + ); + } + + { + let s = process_str( + r#" + #[pfmt(ignore(A, B = u64, X = 100, Y = '_'))] + pub struct IgnoredGenericParams<'a, A, B, const X: u32, const Y: char> ( + pub &'a u32, + pub PhantomData, + pub PhantomData, + ); + "#, + ) + .unwrap(); + assert!( + s.consecutive_unspace(&[r#" + ; + < + IgnoredGenericParams< + '_, + (), + u64, + 100, + '_' + > + as __cp_bCj7dq3Pud::PanicFmt + >::PV_COUNT + ] + "#]), + "\n{}\n", + s, + ); + } +} + +#[test] +fn ignored_generic_arguments_and_impl() { + let s = process_str( + r#" + #[pfmt(ignore(A))] + #[pfmt(impl<'b, G, const H: u32, const I: char> IgnoredAndImpl<'b, G, u8, H, I>)] + #[pfmt(impl<'b, G, const H: u32, const I: char> IgnoredAndImpl<'b, G, u16, H, I>)] + #[pfmt(impl<'b, G, const H: u32, const I: char> IgnoredAndImpl<'b, G, u32, H, I>)] + pub struct IgnoredAndImpl<'a, A, B, const X: u32, const Y: char> ( + pub &'a u32, + pub PhantomData, + pub PhantomData, + ); + "#, + ) + .unwrap(); + assert!( + s.consecutive_unspace(&[ + "impl<'b, G, const H: u32, const I: char> IgnoredAndImpl<'b, G, u8, H, I>", + "IgnoredAndImpl< + '_, + (), + u8, + {__cp_bCj7dq3Pud::__::ConstDefault::DEFAULT}, + {__cp_bCj7dq3Pud::__::ConstDefault::DEFAULT} + >", + ]), + "\n{}\n", + s, + ); + + assert!( + s.consecutive_unspace(&[ + "impl<'b, G, const H: u32, const I: char> IgnoredAndImpl<'b, G, u16, H, I>", + "IgnoredAndImpl< + '_, + (), + u16, + {__cp_bCj7dq3Pud::__::ConstDefault::DEFAULT}, + {__cp_bCj7dq3Pud::__::ConstDefault::DEFAULT} + >", + ]), + "\n{}\n", + s, + ); + + assert!( + s.consecutive_unspace(&[ + "impl<'b, G, const H: u32, const I: char> IgnoredAndImpl<'b, G, u32, H, I>", + "IgnoredAndImpl< + '_, + (), + u32, + {__cp_bCj7dq3Pud::__::ConstDefault::DEFAULT}, + {__cp_bCj7dq3Pud::__::ConstDefault::DEFAULT} + >", + ]), + "\n{}\n", + s, + ); +} + +#[test] +fn generic_type_error() { + for case in [ + r#" + pub struct IgnoredAndImpl<'a, A, B, const X: u32, const Y: char> ( + pub &'a u32, + pub PhantomData, + pub PhantomData, + ); + "#, + r#" + pub struct IgnoredAndImpl<'a, A, B> ( + pub &'a u32, + pub PhantomData, + pub PhantomData, + ); + "#, + ] { + let err = process_str(case).unwrap_err(); + + assert!( + err.consecutive_unspace(&["`#[pfmt(ignore(", "`#[pfmt(impl",]), + "\n{}\n", + err, + ); + } +} + +#[test] +fn impl_attribute_typename_error() { + let input = r#" + #[pfmt(impl Baaa)] + pub struct Fooo<'a, A> ( + pub PhantomData, + ); + "#; + + let err = process_str(input).unwrap_err(); + + assert!(err.consecutive_unspace(&["expected `Fooo`"]), "\n{}\n", err,); +} diff --git a/const_panic_proc_macros/src/lib.rs b/const_panic_proc_macros/src/lib.rs new file mode 100644 index 0000000..ebf50b1 --- /dev/null +++ b/const_panic_proc_macros/src/lib.rs @@ -0,0 +1,29 @@ +#![no_std] + +extern crate alloc; + +#[cfg(test)] +extern crate std; + +use proc_macro::TokenStream as TokenStream1; + +use proc_macro2::TokenStream as TokenStream2; + +mod datastructure; + +mod derive_debug; + +mod syntax; + +mod utils; + +#[cfg(test)] +mod test_utils; + +#[proc_macro_derive(PanicFmt, attributes(pfmt))] +pub fn derive_const_debug(input: TokenStream1) -> TokenStream1 { + syn::parse(input) + .and_then(derive_debug::derive_constdebug_impl) + .unwrap_or_else(|e| e.to_compile_error()) + .into() +} diff --git a/const_panic_proc_macros/src/syntax.rs b/const_panic_proc_macros/src/syntax.rs new file mode 100644 index 0000000..c5ab442 --- /dev/null +++ b/const_panic_proc_macros/src/syntax.rs @@ -0,0 +1,33 @@ +use proc_macro2::TokenTree as TokenTree2; + +use syn::{parse::ParseStream, token::Impl, Generics, Ident}; + +use alloc::format; + +//////////////////////////////////////////////////////////////////////////////// + +pub(crate) struct ImplHeader { + pub(crate) generics: Generics, + pub(crate) self_args: syn::AngleBracketedGenericArguments, +} + +impl ImplHeader { + pub(crate) fn parse<'a>(name: &'a Ident, input: ParseStream<'_>) -> syn::Result { + let _ = input.parse::()?; + let mut generics = input.parse::()?; + + input.step(|cursor| match cursor.token_tree() { + Some((TokenTree2::Ident(ident), cursor)) if *name == ident => Ok(((), cursor)), + _ => Err(cursor.error(format!("expected `{}`", name))), + })?; + + let self_args = input.parse()?; + + generics.where_clause = input.parse()?; + + Ok(Self { + generics, + self_args, + }) + } +} diff --git a/const_panic_proc_macros/src/test_utils.rs b/const_panic_proc_macros/src/test_utils.rs new file mode 100644 index 0000000..6fea60f --- /dev/null +++ b/const_panic_proc_macros/src/test_utils.rs @@ -0,0 +1,49 @@ +use alloc::{string::String, vec::Vec}; + +pub(crate) fn remove_whitespaces(x: &str) -> String { + x.chars().filter(|x| !x.is_whitespace()).collect() +} + +pub trait StrExt { + fn as_str(&self) -> &str; + + /// Checks that these needles exist consequtively in self. + /// + /// Example: `"hello world".consecutive_in_set(&["he", "wor"])` returns `true`. + /// Example: `"hello world".consecutive_in_set(&["wor", "he"])` returns `false`. + fn consecutive_in_self>(&self, needles: &[S]) -> bool { + let mut rem = self.as_str(); + for needle in needles { + let needle: &str = needle.as_ref(); + rem = match rem.find(needle) { + Some(next) => &rem[next + needle.len()..], + None => return false, + }; + } + true + } + + fn consecutive_unspace(&self, needles: &[&str]) -> bool { + let rem = remove_whitespaces(self.as_str()); + let needles = needles + .iter() + .map(|x| remove_whitespaces(x)) + .collect::>(); + ::std::dbg!(&needles); + rem.consecutive_in_self(&needles) + } +} + +impl StrExt for str { + #[inline(always)] + fn as_str(&self) -> &str { + self + } +} + +impl StrExt for alloc::string::String { + #[inline(always)] + fn as_str(&self) -> &str { + self + } +} diff --git a/const_panic_proc_macros/src/utils.rs b/const_panic_proc_macros/src/utils.rs new file mode 100644 index 0000000..c1221c4 --- /dev/null +++ b/const_panic_proc_macros/src/utils.rs @@ -0,0 +1,47 @@ +use syn::parse::{Parse, ParseBuffer, Peek}; + +use quote::TokenStreamExt; + +pub struct Empty(pub proc_macro2::Span); + +impl quote::ToTokens for Empty { + fn to_tokens(&self, ts: &mut crate::TokenStream2) { + ts.append_all(quote::quote_spanned!(self.0 => ())); + } +} + +pub(crate) trait SynResultExt { + fn combine_err(&mut self, res: syn::Result); +} + +impl SynResultExt for syn::Result { + fn combine_err(&mut self, res: syn::Result) { + if let Err(err) = res { + match self { + this @ Ok(_) => *this = Err(err), + Err(e) => e.combine(err), + } + } + } +} + +pub(crate) trait ParseBufferExt { + fn peek_parse(&self, f: F) -> Result, syn::Error> + where + F: FnOnce(X) -> P + Peek, + P: Parse; +} + +impl ParseBufferExt for ParseBuffer<'_> { + fn peek_parse(&self, f: F) -> Result, syn::Error> + where + F: FnOnce(X) -> P + Peek, + P: Parse, + { + if self.peek(f) { + self.parse::

().map(Some) + } else { + Ok(None) + } + } +} diff --git a/src/array_string.rs b/src/array_string.rs index f2a9424..42cae3d 100644 --- a/src/array_string.rs +++ b/src/array_string.rs @@ -1,4 +1,4 @@ -use crate::{FmtArg, PanicFmt, PanicVal}; +use crate::{utils::RangedBytes, FmtArg, PanicFmt, PanicVal}; use core::{ cmp::PartialEq, @@ -14,6 +14,16 @@ pub struct ArrayString { pub(crate) buffer: [u8; CAP], } +/// Equivalent of `ArrayString` which can only be up to 255 bytes long. +/// +/// This stores the length as a `u8`, while `ArrayString` stores it as a `u32`, +/// making this 3 bytes smaller and 1-aligned (while ArrayString is aligned to a `u32`). +#[derive(Copy, Clone)] +pub(crate) struct TinyString { + len: u8, + buffer: [u8; CAP], +} + const fn add_up_lengths(mut strings: &[&str]) -> usize { let mut len = 0; while let [x, rem @ ..] = strings { @@ -88,6 +98,8 @@ impl ArrayString { /// Constructs this string from a `&[&[PanicVal<'_>]]`. /// + /// Returns `None` if the formatted args would be larger than `CAP`. + /// /// # Example /// /// ```rust @@ -119,6 +131,8 @@ impl ArrayString { /// Constructs this string from a `&[PanicVal<'_>]`. /// + /// Returns `None` if the formatted args would be larger than `CAP`. + /// /// # Example /// /// ```rust @@ -173,21 +187,7 @@ impl ArrayString { /// assert_eq!(ArrayString::<16>::new("Hello, world!").as_bytes(), b"Hello, world!"); /// ``` pub const fn as_bytes(&self) -> &[u8] { - let mut to_truncate = CAP - self.len(); - let mut out: &[u8] = &self.buffer; - - while to_truncate != 0 { - if let [rem @ .., _] = out { - out = rem; - } - to_truncate -= 1; - } - - if out.len() != self.len() { - panic!("BUG!") - } - - out + bytes_up_to(&self.buffer, self.len()) } /// Gets the string. @@ -222,6 +222,24 @@ impl ArrayString { } } +impl ArrayString { + pub(crate) const fn to_compact(self) -> TinyString { + if self.len() <= 255 { + TinyString { + len: self.len as u8, + buffer: self.buffer, + } + } else { + crate::concat_panic(&[&[ + PanicVal::write_str( + "The input string is too long, expected `length <= 255`, found length: ", + ), + PanicVal::from_usize(self.len(), FmtArg::DISPLAY), + ]]) + } + } +} + impl Debug for ArrayString { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { Debug::fmt(self.to_str(), f) @@ -244,3 +262,35 @@ impl PanicFmt for ArrayString { type Kind = crate::fmt::IsCustomType; const PV_COUNT: usize = 1; } + +//////////////////////////////////////////////////////////////////////////////// + +impl TinyString { + pub(crate) const fn ranged(&self) -> RangedBytes<&[u8]> { + RangedBytes { + start: 0, + end: self.len as usize, + bytes: &self.buffer, + } + } +} + +//////////////////////////////////////////////////////////////////////////////// + +const fn bytes_up_to(buffer: &[u8], upto: usize) -> &[u8] { + let mut to_truncate = buffer.len() - upto; + let mut out: &[u8] = buffer; + + while to_truncate != 0 { + if let [rem @ .., _] = out { + out = rem; + } + to_truncate -= 1; + } + + if out.len() != upto { + panic!("BUG!") + } + + out +} diff --git a/src/concat_panic_.rs b/src/concat_panic_.rs index 459fc3c..7707e50 100644 --- a/src/concat_panic_.rs +++ b/src/concat_panic_.rs @@ -1,4 +1,8 @@ -use crate::{fmt::FmtKind, panic_val::PanicVal, utils::WasTruncated}; +use crate::{ + fmt::FmtKind, + panic_val::{PanicClass, PanicVal, StrFmt}, + utils::{string_cap, WasTruncated}, +}; /// Panics by concatenating the argument slice. /// @@ -67,19 +71,37 @@ pub const fn concat_panic(args: &[&[PanicVal<'_>]]) -> ! { // const fn is called at runtime, and the stack is finy. pub const MAX_PANIC_MSG_LEN: usize = 32768; -macro_rules! write_panicval_to_buffer { +// writes a single PanicVal to an array +macro_rules! write_panicval { ( $outer_label:lifetime, - $buffer:ident, - $len:ident, - ($capacity:expr, $max_capacity:expr), - $panicval:expr - $(,)* + $mout:ident, $lout:ident, $tct:expr, + ($buffer:ident, $len:ident, $capacity:expr, $max_capacity:expr) ) => { let rem_space = $capacity - $len; - let arg = $panicval; - let (mut lpad, mut rpad, string, fmt_kind, was_truncated) = arg.__string(rem_space); - let trunc_len = was_truncated.get_length(string); + let (strfmt, class, was_truncated) = $tct; + let StrFmt { + leftpad: mut lpad, + rightpad: mut rpad, + fmt_kind, + } = strfmt; + + let ranged = match class { + PanicClass::PreFmt(str) => str, + PanicClass::Int(int) => { + if int.len() <= string_cap::MEDIUM { + $mout = int.fmt::<{ string_cap::MEDIUM }>(); + $mout.ranged() + } else { + $lout = int.fmt::<{ string_cap::LARGE }>(); + $lout.ranged() + } + } + #[cfg(feature = "non_basic")] + PanicClass::Slice(_) => unreachable!(), + }; + + let trunc_end = ranged.start + was_truncated.get_length(ranged.len()); while lpad != 0 { __write_array! {$buffer, $len, b' '} @@ -87,18 +109,18 @@ macro_rules! write_panicval_to_buffer { } if let FmtKind::Display = fmt_kind { - let mut i = 0; - while i < trunc_len { - __write_array! {$buffer, $len, string[i]} + let mut i = ranged.start; + while i < trunc_end { + __write_array! {$buffer, $len, ranged.bytes[i]} i += 1; } } else if rem_space != 0 { __write_array! {$buffer, $len, b'"'} let mut i = 0; - while i < trunc_len { + while i < trunc_end { use crate::debug_str_fmt::{hex_as_ascii, ForEscaping}; - let c = string[i]; + let c = ranged.bytes[i]; let mut written_c = c; if ForEscaping::is_escaped(c) { __write_array! {$buffer, $len, b'\\'} @@ -134,15 +156,24 @@ macro_rules! write_panicval_to_buffer { }; } -macro_rules! write_to_buffer { - ($args:ident, $buffer:ident, $len:ident, $wptb_args:tt $(,)*) => { +macro_rules! write_to_buffer_inner { + ( + $args:ident + ($buffer:ident, $len:ident, $capacity:expr, $max_capacity:expr) + $wptb_args:tt + ) => { let mut args = $args; + + let mut mout; + let mut lout; + 'outer: while let [mut outer, ref nargs @ ..] = args { while let [arg, nouter @ ..] = outer { - match arg.var { + let tct = arg.to_class_truncated($capacity - $len); + match tct.1 { #[cfg(feature = "non_basic")] #[cfg_attr(feature = "docsrs", doc(cfg(feature = "non_basic")))] - crate::panic_val::PanicVariant::Slice(slice) => { + PanicClass::Slice(slice) => { let mut iter = slice.iter(); 'iter: loop { @@ -150,7 +181,8 @@ macro_rules! write_to_buffer { let mut two_args: &[_] = &two_args; while let [arg, ntwo_args @ ..] = two_args { - write_panicval_to_buffer! {'outer, $buffer, $len, $wptb_args, arg} + let tct = arg.to_class_truncated($capacity - $len); + write_panicval! {'outer, mout, lout, tct, $wptb_args} two_args = ntwo_args; } @@ -161,7 +193,7 @@ macro_rules! write_to_buffer { } } _ => { - write_panicval_to_buffer! {'outer, $buffer, $len, $wptb_args, arg} + write_panicval! {'outer, mout, lout, tct, $wptb_args} } } @@ -172,6 +204,16 @@ macro_rules! write_to_buffer { }; } +macro_rules! write_to_buffer { + ($args:ident $wptb_args:tt) => { + write_to_buffer_inner! { + $args + $wptb_args + $wptb_args + } + }; +} + #[cold] #[inline(never)] #[track_caller] @@ -180,10 +222,8 @@ const fn panic_inner(args: &[&[PanicVal<'_>]]) -> Result( let buffer = &mut buffer[..capacity]; write_to_buffer! { - args, - buffer, - len, - (capacity, max_capacity), + args + (buffer, len, capacity, max_capacity) } } @@ -234,10 +272,8 @@ pub(crate) const fn make_panic_string( let mut len = 0usize; write_to_buffer! { - args, - buffer, - len, - (LEN, LEN + 1), + args + (buffer, len, LEN, LEN + 1) } assert!(len as u32 as usize == len, "the panic message is too large"); diff --git a/src/const_default.rs b/src/const_default.rs new file mode 100644 index 0000000..2ba28be --- /dev/null +++ b/src/const_default.rs @@ -0,0 +1,42 @@ +/// Default values for const parameters +/// +/// This trait is sealed so that it can be potentially replaced with +/// an external `ConstDefault` trait when a potential future feature is enabled. +#[doc(hidden)] +pub trait ConstDefault: sealed::Sealed { + const DEFAULT: Self; +} + +mod sealed { + pub trait Sealed {} +} + +macro_rules! impl_constdefault { + ($($ty:ty = $val:expr),* $(,)?) => ( + $( + impl sealed::Sealed for $ty {} + + impl ConstDefault for $ty { + const DEFAULT: Self = $val; + } + )* + ) +} + +impl_constdefault! { + u8 = 0, + u16 = 0, + u32 = 0, + u64 = 0, + u128 = 0, + usize = 0, + i8 = 0, + i16 = 0, + i32 = 0, + i64 = 0, + i128 = 0, + isize = 0, + bool = false, + char = '\0', + &str = "", +} diff --git a/src/doc_macros.rs b/src/doc_macros.rs index 1d68d10..95c16dd 100644 --- a/src/doc_macros.rs +++ b/src/doc_macros.rs @@ -18,6 +18,13 @@ any of the options below: - `alt_debug:` or `{#?}:`: alternate-`Debug` formats the argument. - `display:` or `{}:`: `Display` formats the argument. - `alt_display:` or `{#}:`: alternate-`Display` formats the argument. +- `bin:` or `{b}:`: `Debug` formats the argument, with binary-formatted numbers. +- `alt_bin:` or `{#b}:`: +alternate-`Debug` formats the argument, with binary-formatted numbers. +- `hex:` or `{X}:`: +`Debug` formats the argument, with hexadecimal-formatted numbers. +- `alt_hex:` or `{#X}:`: +alternate-`Debug` formats the argument, with hexadecimal-formatted numbers. "##, $($additional_fmt_overrides,)? r##" diff --git a/src/doctests.rs b/src/doctests.rs new file mode 100644 index 0000000..ff1e162 --- /dev/null +++ b/src/doctests.rs @@ -0,0 +1,22 @@ +/// ################################################### +/// +/// Ensure that phantom type parameters must be ignored +/// +/// ```compile_fail +/// struct Foo(std::marker::PhantomData); +/// +/// const_panic::impl_panicfmt!{ +/// struct Foo(std::marker::PhantomData); +/// } +/// ``` +/// +/// ```rust +/// struct Foo(std::marker::PhantomData); +/// +/// const_panic::impl_panicfmt!{ +/// struct Foo(std::marker::PhantomData); +/// } +/// ``` +/// +/// +pub struct ImplPanicFmt; diff --git a/src/fmt.rs b/src/fmt.rs index 1e598bc..ebc62d6 100644 --- a/src/fmt.rs +++ b/src/fmt.rs @@ -2,17 +2,27 @@ //! //! Panic formatting for custom types can be done in these ways //! (in increasing order of verbosity): +//! - Using the [`PanicFmt` derive] macro +//! (requires the opt-in `"derive"` feature) //! - Using the [`impl_panicfmt`] macro //! (requires the default-enabled `"non_basic"` feature) //! - Using the [`flatten_panicvals`] macro //! (requires the default-enabled `"non_basic"` feature) //! - Manually implementing the [`PanicFmt`] trait as described in its docs. +//! +//! [`PanicFmt` derive]: derive@crate::PanicFmt +//! [`PanicFmt`]: trait@crate::PanicFmt +//! [`impl_panicfmt`]: crate::impl_panicfmt +//! [`flatten_panicvals`]: crate::flatten_panicvals #[cfg(feature = "non_basic")] mod non_basic_fmt; #[cfg(feature = "non_basic")] -pub use self::non_basic_fmt::*; +mod fmt_compressed; + +#[cfg(feature = "non_basic")] +pub use self::{fmt_compressed::PackedFmtArg, non_basic_fmt::*}; use crate::wrapper::StdWrapper; @@ -42,6 +52,8 @@ use core::marker::PhantomData; /// # Implementation examples /// /// This trait can be implemented in these ways (in increasing order of verbosity): +/// - Using the [`PanicFmt` derive] macro +/// (requires the opt-in `"derive"` feature) /// - Using the [`impl_panicfmt`](impl_panicfmt#examples) macro /// (requires the default-enabled `"non_basic"` feature) /// - Using the [`flatten_panicvals`](flatten_panicvals#examples) macro @@ -93,14 +105,18 @@ use core::marker::PhantomData; /// } /// /// ``` +/// [`PanicFmt` derive]: derive@crate::PanicFmt +/// [`PanicFmt`]: trait@crate::PanicFmt +/// [`impl_panicfmt`]: crate::impl_panicfmt +/// [`flatten_panicvals`]: crate::flatten_panicvals pub trait PanicFmt { /// The type after dereferencing all references. /// - /// User-defined should generally set this to `Self`. + /// User-defined types should generally set this to `Self`. type This: ?Sized; /// Whether this is a user-defined type or standard library type. /// - /// User-defined should generally set this to [`IsCustomType`]. + /// User-defined types should generally set this to [`IsCustomType`]. type Kind; /// The length of the array returned in `Self::to_panicvals` @@ -231,7 +247,6 @@ impl Clone for IsPanicFmt { /// /// ``` #[derive(Debug, Copy, Clone, PartialEq)] -#[non_exhaustive] pub struct FmtArg { /// How much indentation is needed for a field/array element. /// @@ -243,6 +258,8 @@ pub struct FmtArg { pub is_alternate: bool, /// Whether this is intended to be `Display` or `Debug` formatted. pub fmt_kind: FmtKind, + /// What integers are formatted as: decimal, hexadecimal, or binary. + pub number_fmt: NumberFmt, } impl FmtArg { @@ -251,21 +268,32 @@ impl FmtArg { indentation: 0, fmt_kind: FmtKind::Display, is_alternate: false, + number_fmt: NumberFmt::Decimal, }; - /// A `FmtArg` with `Debug` formatting and no indentation. - pub const DEBUG: Self = Self { - indentation: 0, - is_alternate: false, - fmt_kind: FmtKind::Debug, - }; - - /// A `FmtArg` with alternate `Display` formatting and no indentation. + /// A `FmtArg` with alternate `Display` formatting, starting with no indentation. pub const ALT_DISPLAY: Self = Self::DISPLAY.set_alternate(true); - /// A `FmtArg` with alternate `Debug` formatting and no indentation. + /// A `FmtArg` with `Debug` formatting and no indentation. + pub const DEBUG: Self = Self::DISPLAY.set_debug(); + + /// A `FmtArg` with alternate `Debug` formatting, starting with no indentation. pub const ALT_DEBUG: Self = Self::DEBUG.set_alternate(true); + /// A `FmtArg` with `Debug` and `Binary` formatting and no indentation. + pub const BIN: Self = Self::DISPLAY.set_bin(); + + /// A `FmtArg` with alternate `Debug` and `Binary` formatting, + /// starting with no indentation. + pub const ALT_BIN: Self = Self::BIN.set_alternate(true); + + /// A `FmtArg` with `Debug` and `Hexadecimal` formatting and no indentation. + pub const HEX: Self = Self::DISPLAY.set_hex(); + + /// A `FmtArg` with alternate `Debug` and `Hexadecimal` formatting, + /// starting with no indentation. + pub const ALT_HEX: Self = Self::HEX.set_alternate(true); + /// Sets whether alternate formatting is enabled pub const fn set_alternate(mut self, is_alternate: bool) -> Self { self.is_alternate = is_alternate; @@ -283,6 +311,20 @@ impl FmtArg { self.fmt_kind = FmtKind::Debug; self } + + /// Changes the formatting to `Debug`, and number formatting to `Hexadecimal`. + pub const fn set_hex(mut self) -> Self { + self.fmt_kind = FmtKind::Debug; + self.number_fmt = NumberFmt::Hexadecimal; + self + } + + /// Changes the formatting to `Debug`, and number formatting to `Binary`. + pub const fn set_bin(mut self) -> Self { + self.fmt_kind = FmtKind::Debug; + self.number_fmt = NumberFmt::Binary; + self + } } #[cfg(feature = "non_basic")] @@ -304,10 +346,25 @@ impl FmtArg { //////////////////////////////////////////////////////////////////////////////// /// What kind of formatting to do, either `Display` or `Debug`. +#[non_exhaustive] #[derive(Debug, Copy, Clone, PartialEq)] pub enum FmtKind { /// `Debug` formatting - Debug, + Debug = 0, /// `Display` formatting - Display, + Display = 1, +} + +//////////////////////////////////////////////////////////////////////////////// + +/// What integers are formatted as. +#[non_exhaustive] +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum NumberFmt { + /// Formatted as decimal. + Decimal = 0, + /// Formatted as binary, eg: `101`, `0b110`. + Binary = 1, + /// Formatted as hexadecimal, eg: `FAD`, `0xDE`. + Hexadecimal = 2, } diff --git a/src/fmt/fmt_compressed.rs b/src/fmt/fmt_compressed.rs new file mode 100644 index 0000000..ab980de --- /dev/null +++ b/src/fmt/fmt_compressed.rs @@ -0,0 +1,95 @@ +use crate::fmt::{FmtArg, FmtKind, NumberFmt}; + +/// A version of FmtArg which occupies less space, but needs to be unpacked to be used. +#[derive(Copy, Clone)] +#[cfg_attr(feature = "docsrs", doc(cfg(feature = "non_basic")))] +pub struct PackedFmtArg { + indentation: u8, + bitfields: u8, +} + +const FMT_KIND_OFFSET: u8 = 1; +const NUMBER_FMT_OFFSET: u8 = FMT_KIND_OFFSET + FmtKind::BITS; + +impl FmtArg { + /// Converts this `FmtArg` into a `PackedFmtArg`, + /// which is smaller but can only be converted back into a `FmtArg`. + #[cfg_attr(feature = "docsrs", doc(cfg(feature = "non_basic")))] + pub const fn pack(self) -> PackedFmtArg { + let Self { + indentation, + is_alternate, + fmt_kind, + number_fmt, + } = self; + + PackedFmtArg { + indentation, + bitfields: is_alternate as u8 + | ((fmt_kind as u8) << FMT_KIND_OFFSET) + | ((number_fmt as u8) << NUMBER_FMT_OFFSET), + } + } +} + +impl PackedFmtArg { + /// Converts this `PackedFmtArg` back into a `FmtArg`. + pub const fn unpack(self) -> FmtArg { + let indentation = self.indentation; + let is_alternate = (self.bitfields & 1) != 0; + let fmt_kind = FmtKind::from_prim(self.bitfields >> FMT_KIND_OFFSET); + let number_fmt = NumberFmt::from_prim(self.bitfields >> NUMBER_FMT_OFFSET); + + FmtArg { + indentation, + is_alternate, + fmt_kind, + number_fmt, + } + } +} + +macro_rules! enum_prim { + ( + $type:ident, $bits:expr; + default $default:ident, + $($variant:ident),* + $(,)? + ) => ( + #[allow(non_upper_case_globals)] + const _: () = { + $(const $variant: u8 = $type::$variant as u8;)* + const __MASK: u8 = 2u8.pow($bits) - 1; + + match $type::$default { + $($type::$variant => (),)* + $type::$default => (), + } + + impl $type { + const BITS: u8 = $bits; + + const fn from_prim(n: u8) -> Self { + match n & __MASK { + $($variant => Self::$variant,)* + _ => Self::$default, + } + } + } + }; + ) +} +use enum_prim; + +enum_prim! { + FmtKind, 2; + default Debug, + Display, +} + +enum_prim! { + NumberFmt, 2; + default Decimal, + Binary, + Hexadecimal, +} diff --git a/src/fmt/non_basic_fmt.rs b/src/fmt/non_basic_fmt.rs index cdd7184..ec9f698 100644 --- a/src/fmt/non_basic_fmt.rs +++ b/src/fmt/non_basic_fmt.rs @@ -95,7 +95,9 @@ impl Delimiter { /// - [fmtarg.indentation](crate::FmtArg#structfield.indentation) amount of spaces /// /// When the [alternate flag] is disabled, - /// these methods only output the delimiter, + /// these methods output braces with spaces around them, + /// the empty delimiter as one space, + /// and the remaining delimiters with no spaces around them. /// /// [alternate flag]: crate::FmtArg#structfield.is_alternate /// @@ -143,24 +145,45 @@ pub const INDENTATION_STEP: u8 = 4; //////////////////////////////////////////////////////////////////////////////// -/// A stack allocated string type that's convetible into [`PanicVal`]. +/// A stack allocated string type that's convertible into +/// [`PanicVal<'static>`](PanicVal), +/// with [`SHORT_STRING_CAP`] capacity. /// /// # Example /// /// ```rust /// use const_panic::{ /// fmt::ShortString, -/// ArrayString, PanicVal, +/// ArrayString, FmtArg, PanicVal, /// }; /// /// let pv: PanicVal<'static> = /// PanicVal::write_short_str(ShortString::new("3,13,21,34,55,89")); /// -/// assert_eq!(ArrayString::<20>::from_panicvals(&[pv]).unwrap(), "3,13,21,34,55,89") +/// assert_eq!(ArrayString::<20>::from_panicvals(&[pv]).unwrap(), "3,13,21,34,55,89"); +/// +/// +/// let pv_debug: PanicVal<'static> = +/// PanicVal::from_short_str(ShortString::new("foo\n\0bar"), FmtArg::DEBUG); +/// +/// assert_eq!( +/// ArrayString::<20>::from_panicvals(&[pv_debug]).unwrap(), +/// "\"foo\\n\\x00bar\"", +/// ); /// /// ``` #[cfg_attr(feature = "docsrs", doc(cfg(feature = "non_basic")))] -pub type ShortString = ArrayString<16>; +pub type ShortString = ArrayString; + +pub use crate::utils::string_cap::TINY as SHORT_STRING_CAP; + +impl<'a> PanicVal<'a> { + /// Constructs a `PanicVal` from a `ShortString`. + pub const fn from_short_str(this: ShortString, f: FmtArg) -> PanicVal<'a> { + use crate::panic_val::{PanicVariant, StrFmt}; + PanicVal::__new(PanicVariant::ShortString(StrFmt::new(f), this.to_compact())) + } +} //////////////////////////////////////////////////////////////////////////////// @@ -278,6 +301,8 @@ impl ComputePvCount { /// Whether a struct or variant is Tupled or Braced. /// +/// Unit structs/variants are considered braced. +/// /// # Example /// /// ### Formatting @@ -335,7 +360,7 @@ pub enum TypeDelim { } impl TypeDelim { - /// Gets the delimiters that surround a type's fields. + /// Gets the delimiters that surround the fields of a struct or variant. pub const fn get_open_and_close(self) -> (Delimiter, Delimiter) { match self { Self::Tupled => (Delimiter::OpenParen, Delimiter::CloseParen), @@ -346,7 +371,9 @@ impl TypeDelim { //////////////////////////////////////////////////////////////////////////////// -/// A comma separator for use between fields or elements. +/// An [alternate-flag-aware] comma separator for use between fields or elements. +/// +/// When the alternate flag is enabled, this puts each field/element on its own line. /// /// # Example /// @@ -399,12 +426,17 @@ impl TypeDelim { /// /// ``` /// +/// [alternate-flag-aware]: crate::FmtArg#structfield.is_alternate #[cfg_attr(feature = "docsrs", doc(cfg(feature = "non_basic")))] pub const COMMA_SEP: Separator<'_> = Separator::new(",", IsLast::No); -/// A comma for use after the last field or element. +/// An [alternate-flag-aware] comma for use after the last field or element. /// /// For an example, you can look at [the one for `COMMA_SEP`](COMMA_SEP#example) +/// +/// When the alternate flag is enabled, this puts each field/element on its own line. +/// +/// [alternate-flag-aware]: crate::FmtArg#structfield.is_alternate #[cfg_attr(feature = "docsrs", doc(cfg(feature = "non_basic")))] pub const COMMA_TERM: Separator<'_> = Separator::new(",", IsLast::Yes); diff --git a/src/fmt_impls/basic_fmt_impls.rs b/src/fmt_impls/basic_fmt_impls.rs index 7f09e00..727ed17 100644 --- a/src/fmt_impls/basic_fmt_impls.rs +++ b/src/fmt_impls/basic_fmt_impls.rs @@ -1,5 +1,6 @@ use crate::{ - panic_val::{IntVal, PanicVal, PanicVariant}, + panic_val::{IntVal, PanicVal, PanicVariant, StrFmt}, + utils::Packed, FmtArg, PanicFmt, StdWrapper, }; @@ -102,10 +103,8 @@ macro_rules! impl_panicfmt_int { impl PanicVal<'_> { /// Constructs this `PanicVal` from an integer. pub const fn $panic_arg_ctor(this: $ty, f: FmtArg) -> PanicVal<'static> { - PanicVal::__new( - PanicVariant::Int(IntVal::$intarg_contructor(this as _, f)), - f, - ) + const BITS: u8 = core::mem::size_of::<$ty>() as u8 * 8; + IntVal::$intarg_contructor(this as _, BITS, f) } } @@ -140,7 +139,7 @@ impl_panicfmt_panicarg! { impl<'a> PanicVal<'a> { /// Constructs a `PanicVal` from a `&str` pub const fn from_str(this: &'a str, f: FmtArg) -> PanicVal<'a> { - PanicVal::__new(PanicVariant::Str(this), f) + PanicVal::__new(PanicVariant::Str(StrFmt::new(f), Packed(this))) } } @@ -153,11 +152,11 @@ impl PanicFmt for str { impl<'a> StdWrapper<&'a str> { /// Formats this `&str` into a single-`PanicVal` array pub const fn to_panicvals(self: Self, f: FmtArg) -> [PanicVal<'a>; 1] { - [PanicVal::__new(PanicVariant::Str(self.0), f)] + [PanicVal::from_str(self.0, f)] } /// Formats this `&str` into a `PanicVal` pub const fn to_panicval(self: Self, f: FmtArg) -> PanicVal<'a> { - PanicVal::__new(PanicVariant::Str(self.0), f) + PanicVal::from_str(self.0, f) } } diff --git a/src/int_formatting.rs b/src/int_formatting.rs new file mode 100644 index 0000000..bf3e384 --- /dev/null +++ b/src/int_formatting.rs @@ -0,0 +1,141 @@ +use crate::{ + fmt::{FmtArg, NumberFmt}, + utils::{Sign, TailShortString}, +}; + +pub(crate) const fn fmt_decimal(sign: Sign, mut n: u128) -> TailShortString { + let mut start = N; + let mut buffer = [0u8; N]; + + loop { + start -= 1; + let digit = (n % 10) as u8; + buffer[start] = b'0' + digit; + n /= 10; + if n == 0 { + break; + } + } + + if let Sign::Negative = sign { + start -= 1; + buffer[start] = b'-'; + } + + // safety: buffer is only ever written ascii, so its automatically valid utf8. + unsafe { TailShortString::new(start as u8, buffer) } +} + +pub(crate) const fn fmt_binary( + mut n: u128, + is_alternate: bool, +) -> TailShortString { + let mut start = N; + let mut buffer = [0u8; N]; + + loop { + start -= 1; + let digit = (n & 1) as u8; + buffer[start] = b'0' + digit; + n >>= 1; + if n == 0 { + break; + } + } + + if is_alternate { + start -= 1; + buffer[start] = b'b'; + start -= 1; + buffer[start] = b'0'; + } + + // safety: buffer is only ever written ascii, so its automatically valid utf8. + unsafe { TailShortString::new(start as u8, buffer) } +} + +pub(crate) const fn fmt_hexadecimal( + mut n: u128, + is_alternate: bool, +) -> TailShortString { + let mut start = N; + let mut buffer = [0u8; N]; + + loop { + start -= 1; + let digit = (n & 0xF) as u8; + buffer[start] = match digit { + 0..=9 => b'0' + digit, + _ => b'A' - 10 + digit, + }; + n >>= 4; + if n == 0 { + break; + } + } + + if is_alternate { + start -= 1; + buffer[start] = b'x'; + start -= 1; + buffer[start] = b'0'; + } + + // safety: buffer is only ever written ascii, so its automatically valid utf8. + unsafe { TailShortString::new(start as u8, buffer) } +} + +pub(crate) const fn compute_len(sign: Sign, int: u128, bits: u8, fmt: FmtArg) -> u8 { + match fmt.number_fmt { + NumberFmt::Decimal => compute_decimal_len(sign, int), + NumberFmt::Hexadecimal => { + let with_0x = (fmt.is_alternate as u8) * 2; + let i = match sign { + Sign::Negative => bits, + Sign::Positive => (128 - int.leading_zeros()) as u8, + }; + let tmp = if i == 0 { + 1 + } else { + i / 4 + (i % 4 != 0) as u8 + }; + tmp + with_0x + } + NumberFmt::Binary => { + let with_0b = (fmt.is_alternate as u8) * 2; + let i = match sign { + Sign::Negative => bits, + Sign::Positive => (128 - int.leading_zeros()) as u8, + }; + (if i == 0 { 1 } else { i }) + with_0b + } + } +} + +const fn compute_decimal_len(sign: Sign, mut n: u128) -> u8 { + let mut len = matches!(sign, Sign::Negative) as u8 + 1; + if n >= 1_0000_0000_0000_0000 { + n /= 1_0000_0000_0000_0000; + len += 16; + } + if n >= 1_0000_0000_0000 { + n /= 1_0000_0000_0000; + len += 12; + } + if n >= 1_0000_0000 { + n /= 100_000_000; + len += 8; + } + if n >= 1_0000 { + n /= 1_0000; + len += 4; + } + if n >= 100 { + n /= 100; + len += 2; + } + if n >= 10 { + len += 1; + } + len +} diff --git a/src/lib.rs b/src/lib.rs index 2849473..f8ca51c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,7 +13,7 @@ //! ### Basic //! //! ```compile_fail -//! use const_panic::concat_panic; +//! use const_panic::concat_assert; //! //! const FOO: u32 = 10; //! const BAR: u32 = 0; @@ -21,15 +21,16 @@ //! //! #[track_caller] //! const fn assert_non_zero(foo: u32, bar: u32) { -//! if foo == 0 || bar == 0 { -//! concat_panic!("\nneither foo nor bar can be zero!\nfoo: ", foo, "\nbar: ", bar) +//! concat_assert!{ +//! foo != 0 && bar != 0, +//! "\nneither foo nor bar can be zero!\nfoo: ", foo, "\nbar: ", bar //! } //! } //! ``` //! The above code fails to compile with this error: //! ```text //! error[E0080]: evaluation of constant value failed -//! --> src/lib.rs:17:15 +//! --> src/lib.rs:20:15 //! | //! 8 | const _: () = assert_non_zero(FOO, BAR); //! | ^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at ' @@ -65,16 +66,18 @@ //! //! Panic formatting for custom types can be done in these ways //! (in increasing order of verbosity): +//! - Using the [`PanicFmt` derive] macro +//! (requires the opt-in `"derive"` feature) //! - Using the [`impl_panicfmt`] macro //! (requires the default-enabled `"non_basic"` feature) //! - Using the [`flatten_panicvals`] macro //! (requires the default-enabled `"non_basic"` feature) //! - Manually implementing the [`PanicFmt`] trait as described in its docs. //! -//! This example uses the [`impl_panicfmt`] approach. +//! This example uses the [`PanicFmt` derive] approach. //! //! ```compile_fail -//! use const_panic::concat_panic; +//! use const_panic::{PanicFmt, concat_panic}; //! //! const LAST: u8 = { //! Foo{ @@ -106,46 +109,23 @@ //! } //! } //! +//! #[derive(PanicFmt)] //! struct Foo<'a> { //! x: &'a [u8], //! y: Bar, //! z: Qux, //! } //! -//! // You need to replace non-static lifetimes with `'_` here. -//! const_panic::impl_panicfmt! { -//! impl Foo<'_>; -//! -//! struct Foo { -//! x: &[u8], -//! y: Bar, -//! z: Qux, -//! } -//! } -//! +//! #[derive(PanicFmt)] //! struct Bar(bool, bool); //! -//! const_panic::impl_panicfmt! { -//! impl Bar; -//! -//! struct Bar(bool, bool); -//! } -//! +//! #[derive(PanicFmt)] //! enum Qux { //! Up, //! Down { x: u32, y: u32 }, //! Left(u64), //! } //! -//! const_panic::impl_panicfmt!{ -//! impl Qux; -//! -//! enum Qux { -//! Up, -//! Down { x: u32, y: u32 }, -//! Left(u64), -//! } -//! } //! ``` //! The above code fails to compile with this error: //! ```text @@ -189,9 +169,12 @@ //! Without this feature, you can effectively only format primitive types //! (custom types can manually implement formatting with more difficulty). //! +//! - `"derive"`(disabled by default): +//! Enables the [`PanicFmt` derive] macro. +//! //! # Plans //! -//! Adding a derive macro, under an opt-in "derive" feature. +//! None for now //! //! # No-std support //! @@ -202,9 +185,11 @@ //! This requires Rust 1.57.0, because it uses the `panic` macro in a const context. //! //! -//! [`PanicFmt`]: crate::PanicFmt +//! [`PanicFmt` derive]: derive@crate::PanicFmt +//! [`PanicFmt`]: trait@crate::PanicFmt //! [`impl_panicfmt`]: crate::impl_panicfmt //! [`flatten_panicvals`]: crate::flatten_panicvals +//! [`MAX_PANIC_MSG_LEN`]: crate::MAX_PANIC_MSG_LEN #![no_std] #![cfg_attr(feature = "docsrs", feature(doc_cfg))] #![warn(missing_docs)] @@ -212,6 +197,8 @@ #![deny(clippy::shadow_unrelated)] #![deny(clippy::wildcard_imports)] +extern crate self as const_panic; + #[macro_use] mod doc_macros; @@ -222,10 +209,18 @@ mod concat_panic_; mod debug_str_fmt; +mod int_formatting; + pub mod fmt; +#[cfg(all(doctest, feature = "non_basic"))] +pub mod doctests; + mod panic_val; +#[cfg(feature = "non_basic")] +mod const_default; + #[cfg(not(feature = "non_basic"))] mod utils; @@ -281,7 +276,7 @@ pub use crate::fmt::{ComputePvCount, TypeDelim}; #[doc(hidden)] pub mod __ { pub use core::{ - compile_error, concat, + assert, compile_error, concat, option::Option::{None, Some}, primitive::usize, result::Result::{Err, Ok}, @@ -299,11 +294,17 @@ pub mod __ { mod reexported_non_basic { pub use core::option::Option::{self, None, Some}; - pub use crate::utils::{assert_flatten_panicvals_length, flatten_panicvals, panicvals_id}; + pub use crate::{ + const_default::ConstDefault, + utils::{assert_flatten_panicvals_length, flatten_panicvals, panicvals_id}, + }; pub const EPV: crate::PanicVal<'_> = crate::PanicVal::EMPTY; } +#[cfg(feature = "derive")] +include! {"./proc_macro_reexports/panicfmt_derive.rs"} + #[doc(hidden)] #[cfg(feature = "test")] pub mod test_utils; diff --git a/src/macros.rs b/src/macros.rs index 910579e..fb9a3b7 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -1,3 +1,6 @@ +#[macro_use] +mod concat_assert; + #[cfg(feature = "non_basic")] #[macro_use] mod non_basic_macros; @@ -76,15 +79,8 @@ macro_rules! __write_array_checked { /// /// enum IsReal{Yes, No} /// -/// // Manually implementing panic formatting for a field-less enum -/// // -/// // All the code below is equivalent to this `impl_panicfmt` invocation: -/// // ``` -/// // impl_panicfmt!{ -/// // impl IsReal; -/// // enum IsReal { Yes, No } -/// // } -/// // ``` +/// // All the code below manually implements panic formatting for a field-less enum. +/// // This can be written concisely with the `PanicFmt` derive or `impl_panicfmt` macro. /// impl PanicFmt for IsReal { /// type This = Self; /// type Kind = IsCustomType; @@ -153,7 +149,7 @@ macro_rules! coerce_fmt { /// /// const _: () = concat_panic!{ /// // the optional `$fmtarg` parameter. -/// // If this argument wasn't passed, it'd be `FmtArg::DEBUG` +/// // If this argument isn't passed, it defaults to `FmtArg::DEBUG` /// FmtArg::ALT_DEBUG; /// /// "\n\nshowing off literals:\n", @@ -185,6 +181,26 @@ macro_rules! coerce_fmt { /// // `{#}:` is The same as `alt_display:` /// {#}: ["bbb", "ccc"], /// +/// "\n\nbinary formatted:\n", +/// bin: [3u8, 5, 8, 13], +/// // `{b}:` is The same as `bin:` +/// {b}: [3u8, 5, 8, 13], +/// +/// "\n\nalternate-binary formatted:\n", +/// alt_bin: [21u8, 34, 55, 89], +/// // `{#b}:` is The same as `alt_bin:` +/// {#b}: [21u8, 34, 55, 89], +/// +/// "\n\nhexadecimal formatted:\n", +/// hex: [3u8, 5, 8, 13], +/// // `{X}:` is The same as `hex:` +/// {X}: [3u8, 5, 8, 13], +/// +/// "\n\nalternate-hexadecimal formatted:\n", +/// alt_hex: [21u8, 34, 55, 89], +/// // `{#X}:` is The same as `alt_hex:` +/// {#X}: [21u8, 34, 55, 89], +/// /// "\n\n", /// }; /// @@ -192,16 +208,16 @@ macro_rules! coerce_fmt { /// The above code produces this compile-time error: /// ```text /// error[E0080]: evaluation of constant value failed -/// --> src/macros.rs:94:15 +/// --> src/macros.rs:186:15 /// | /// 6 | const _: () = concat_panic!{ /// | _______________^ /// 7 | | // the optional `$fmtarg` parameter. -/// 8 | | // If this argument wasn't passed, it'd be `FmtArg::DEBUG` +/// 8 | | // If this argument isn't passed, it defaults to `FmtArg::DEBUG` /// 9 | | FmtArg::ALT_DEBUG; /// ... | -/// 40 | | "\n\n", -/// 41 | | }; +/// 60 | | "\n\n", +/// 61 | | }; /// | |_^ the evaluated program panicked at ' /// /// showing off literals: @@ -232,7 +248,43 @@ macro_rules! coerce_fmt { /// ccc, /// ] /// +/// binary formatted: +/// [11, 101, 1000, 1101][11, 101, 1000, 1101] +/// +/// alternate-binary formatted: +/// [ +/// 0b10101, +/// 0b100010, +/// 0b110111, +/// 0b1011001, +/// ][ +/// 0b10101, +/// 0b100010, +/// 0b110111, +/// 0b1011001, +/// ] +/// +/// hexadecimal formatted: +/// [3, 5, 8, D][3, 5, 8, D] +/// +/// alternate-hexadecimal formatted: +/// [ +/// 0x15, +/// 0x22, +/// 0x37, +/// 0x59, +/// ][ +/// 0x15, +/// 0x22, +/// 0x37, +/// 0x59, +/// ] +/// /// ', src/macros.rs:6:15 +/// | +/// = note: this error originates in the macro `concat_panic` (in Nightly builds, run with -Z macro-backtrace for more info) +/// +/// error: aborting due to previous error /// /// ``` /// @@ -343,6 +395,30 @@ macro_rules! __set_fmt_from_kw { ({#?}, $fmtarg:ident) => { $fmtarg.set_debug().set_alternate(true) }; + (hex, $fmtarg:ident) => { + $fmtarg.set_hex().set_alternate(false) + }; + ({X}, $fmtarg:ident) => { + $fmtarg.set_hex().set_alternate(false) + }; + (alt_hex, $fmtarg:ident) => { + $fmtarg.set_hex().set_alternate(true) + }; + ({#X}, $fmtarg:ident) => { + $fmtarg.set_hex().set_alternate(true) + }; + (bin, $fmtarg:ident) => { + $fmtarg.set_bin().set_alternate(false) + }; + ({b}, $fmtarg:ident) => { + $fmtarg.set_bin().set_alternate(false) + }; + (alt_bin, $fmtarg:ident) => { + $fmtarg.set_bin().set_alternate(true) + }; + ({#b}, $fmtarg:ident) => { + $fmtarg.set_bin().set_alternate(true) + }; (_, $fmtarg:ident) => { $fmtarg }; @@ -353,7 +429,13 @@ macro_rules! __set_fmt_from_kw { "\n", "expected one of:\n", "- display/{}\n", + "- alt_display/{#}\n", "- debug/{?}\n", + "- alt_debug/{#?}\n", + "- hex/{X}\n", + "- alt_hex/{#X}\n", + "- bin/{b}\n", + "- alt_bin/{#b}\n", )) }; } diff --git a/src/macros/concat_assert.rs b/src/macros/concat_assert.rs new file mode 100644 index 0000000..a88662e --- /dev/null +++ b/src/macros/concat_assert.rs @@ -0,0 +1,123 @@ +/// Asserts that `$condition` is true. +/// +/// When only the `$condition` argument is passed, +/// this delegates to the [`core::assert`] macro. +/// +/// When two or more arguments are passed, +/// this panics with formatting by delegating the second and remaining arguments +/// to the [`concat_panic`](macro@concat_panic) macro. +/// +/// ### Examples +/// +/// ### Formatted assertion +/// +/// ```compile_fail +/// use const_panic::concat_assert; +/// +/// const ONE: Even = Even::new(1); +/// +/// struct Even(u32); +/// +/// impl Even { +/// #[track_caller] +/// const fn new(n: u32) -> Self { +/// concat_assert!(n % 2 == 0, "\nexpected the argument to be even, found: ", n); +/// +/// Even(n) +/// } +/// } +/// ``` +/// the above code errors with this message: +/// ```text +/// error[E0080]: evaluation of constant value failed +/// --> src/macros/concat_assert.rs:16:19 +/// | +/// 4 | const ONE: Even = Even::new(1); +/// | ^^^^^^^^^^^^ the evaluated program panicked at ' +/// expected the argument to be even, found: 1', src/macros/concat_assert.rs:4:19 +/// +/// ``` +/// +/// ### More formatting +/// +/// This example demonstrates what error non-`#[track_caller]` functions produce, +/// and uses the `"non_basic"` feature(enabled by default). +/// +/// ```compile_fail +/// use const_panic::concat_assert; +/// +/// const SUM: u64 = sum(&[3, 5, 8], 1..40); +/// +/// const fn sum(mut slice: &[u32], range: std::ops::Range) -> u64 { +/// concat_assert!( +/// range.start <= range.end && range.end <= slice.len(), +/// "\ncannot index slice of length `", slice.len(), +/// "` with `", range, "` range" +/// ); +/// +/// let mut sum = 0u64; +/// +/// while let [curr, ref rem @ ..] = *slice { +/// sum += curr as u64; +/// +/// slice = rem; +/// } +/// +/// sum +/// } +/// ``` +/// the above code errors with this message: +/// ```text +/// error[E0080]: evaluation of constant value failed +/// --> src/macros/concat_assert.rs:52:5 +/// | +/// 6 | const SUM: u64 = sum(&[3, 5, 8], 1..40); +/// | ---------------------- inside `SUM` at src/macros/concat_assert.rs:6:18 +/// ... +/// 9 | / concat_assert!( +/// 10 | | range.start <= range.end && range.end <= slice.len(), +/// 11 | | "\ncannot index slice of length `", slice.len(), +/// 12 | | "` with `", range, "` range" +/// 13 | | ); +/// | | ^ +/// | | | +/// | |_____the evaluated program panicked at ' +/// cannot index slice of length `3` with `1..40` range', src/macros/concat_assert.rs:9:5 +/// | inside `_doctest_main_src_macros_concat_assert_rs_46_0::sum` at /home/matias/Documents/proyectos programacion/const_panic/src/macros.rs:240:21 +/// | +/// = note: this error originates in the macro `$crate::concat_panic` (in Nightly builds, run with -Z macro-backtrace for more info) +/// ``` +/// +/// ### Unformatted assertion +/// +/// When only the `$condition` argument is passed, +/// this delegates to the [`core::assert`] macro. +/// +/// ```compile_fail +/// use const_panic::concat_assert; +/// +/// const _: () = concat_assert!(cfg!(any(feature = "foo", feature = "bar")) ); +/// ``` +/// the above code errors with this message: +/// ```text +/// error[E0080]: evaluation of constant value failed +/// --> src/macros/concat_assert.rs:48:15 +/// | +/// 6 | const _: () = concat_assert!(cfg!(any(feature = "foo", feature = "bar")) ); +/// | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'assertion failed: cfg!(any(feature = \"foo\", feature = \"bar\"))', src/macros/concat_assert.rs:6:15 +/// | +/// = note: this error originates in the macro `assert` (in Nightly builds, run with -Z macro-backtrace for more info) +/// ``` +/// +/// +#[macro_export] +macro_rules! concat_assert { + ($condition:expr $(,)?) => { + $crate::__::assert!($condition); + }; + ($condition:expr, $($fmt:tt)*) => { + if let false = $condition { + $crate::concat_panic!{$($fmt)*} + } + }; +} diff --git a/src/macros/impl_panicfmt.rs b/src/macros/impl_panicfmt.rs index 5c95a5c..13f166a 100644 --- a/src/macros/impl_panicfmt.rs +++ b/src/macros/impl_panicfmt.rs @@ -1,8 +1,52 @@ /// Implements the [`PanicFmt`](crate::PanicFmt) /// trait and the `to_panicvals` method it requires. /// -/// This macro only accepts concrete types, and types with `'_` lifetime arguments, -/// because it uses them in places where generic types don't yet work. +/// This macro roughly takes a type definition and a (conditionally required) list of impls, +/// [for an example demonstrating all the parts of the syntax look here](#all-the-syntax). +/// +/// For a derive macro alternative, there's the [`PanicFmt`](derive@crate::PanicFmt) derive, +/// which requires the `"derive"` feature(disabled by default). +/// +/// # Limitations +/// +/// ### Type parameters +/// +/// Types with type parameters can't be generically formatted, which has two workarounds. +/// +/// The first workaround is marking a type parameter as ignored with an `ignore` prefix, +/// if the type parameter(s) are only used in marker types (eg: `PhantomData`). +/// [example of this workaround](#phantom-type-parameter-example) +/// +/// The second workaround is to implement panic formatting with concrete type arguments, +/// using trailing `(impl Foo)`s. +/// [example of this workaround](#type-parameter-example) +/// +/// This limitation is caused by: +/// - the lack of trait bound support in stable const fns. +/// - the need to [have a concrete type argument](#concrete-pv-count) +/// +/// [example below](#type-parameter-example) +/// +/// ### Const parameters +/// +/// Const parameters must not affect the value of the `PanicFmt::PV_COUNT` of this type, +/// since the const parameter [must be replaceable with a concrete value](#concrete-pv-count). +///
Note that arrays have a `PV_COUNT` of `1` for all lengths. +/// +///
+/// ### Concrete `Self` type for `PanicFmt::PV_COUNT` +/// +/// The `to_panicvals` method that this macro generates roughly returns a +/// ```text +/// [PanicVal<'_>; ::PV_COUNT] +/// ``` +/// +/// Because of limitations in stable const generics, +/// the generic arguments of `Self` in the above code must be replaced with concrete arguments, +/// requiring: +/// - Lifetime arguments to be replaced with `'_` +/// - Type arguments to be replaced with concrete types (usually `()`) +/// - Const arguments to be replaced with concrete values (usually the default value for the type) /// /// # Examples /// @@ -49,16 +93,10 @@ /// z: Bar, /// } /// -/// // Implementing `PanicFmt` and the `to_panicvals` method for -/// // `Foo<'a>` (with any lifetime). -/// // -/// // Only `Foo<'_>` or `Foo<'static>` can work here, due to what the macro expands into. +/// // Implementing `PanicFmt` and the `to_panicvals` method for `Foo<'a>` /// impl_panicfmt!{ -/// impl Foo<'_>; -/// -/// struct Foo { -/// // removing all lifetimes in fields (or replacing them with `'_`) is required -/// x: &[u8], +/// struct Foo<'a> { +/// x: &'a [u8], /// y: u8, /// z: Bar, /// } @@ -68,9 +106,7 @@ /// struct Bar(bool, bool); /// /// impl_panicfmt!{ -/// impl Bar; -/// -/// struct Bar(bool, bool) +/// struct Bar(bool, bool); /// } /// /// ``` @@ -133,34 +169,184 @@ /// /// /// // Because of limitations of stable const evaluation, -/// // you have to use macros to invoke the `impl_panicfmt` macro -/// // for more than one concrete type. -/// // -/// // This macro invocation implements panic formatting for +/// // `Qux` can't generically implement panic formatting, +/// // so this macro invocation implements panic formatting for these specifically: /// // - `Qux` /// // - `Qux` /// // - `Qux` -/// const_panic::inline_macro! { -/// (u8), -/// (u16), -/// (u32); -/// -/// ($T:ty) => -/// -/// impl_panicfmt!{ -/// impl Qux<$T>; -/// -/// // causes the returned `PanicVal`s from `Qux::to_panicvals` -/// // to be `PanicVal<'static>` instead of `PanicVal<'_>` -/// // (the default is that it borrows from the `self` argument) -/// lifetime = 'static; -/// -/// enum Qux { -/// Up, -/// Down { x: $T, y: $T, z: $T }, -/// Left(u64), -/// } +/// impl_panicfmt!{ +/// enum Qux { +/// Up, +/// Down { x: T, y: T, z: T }, +/// Left(u64), +/// } +/// +/// (impl Qux) +/// (impl Qux) +/// (impl Qux) +/// } +/// ``` +/// +/// +/// ### Type parameters +/// +/// This example demonstrates support for types with type parameters. +/// +/// ```rust +/// use const_panic::{ArrayString, FmtArg, impl_panicfmt}; +/// +/// use std::marker::PhantomData; +/// +/// { +/// let with_int = Foo::<&str, u8> { +/// value: 100u8, +/// _marker: PhantomData, +/// }; +/// assert_eq!( +/// ArrayString::<100>::from_panicvals(&with_int.to_panicvals(FmtArg::DEBUG)).unwrap(), +/// "Foo { value: 100, _marker: PhantomData }", +/// ); +/// } +/// { +/// let with_str = Foo:: { +/// value: "hello", +/// _marker: PhantomData, +/// }; +/// assert_eq!( +/// ArrayString::<100>::from_panicvals(&with_str.to_panicvals(FmtArg::DEBUG)).unwrap(), +/// r#"Foo { value: "hello", _marker: PhantomData }"#, +/// ); +/// } +/// +/// #[derive(Debug)] +/// pub struct Foo { +/// value: B, +/// _marker: PhantomData, +/// } +/// +/// impl_panicfmt!{ +/// // `ignore` here tells the macro that this type parameter is not formatted. +/// struct Foo { +/// value: B, +/// _marker: PhantomData, +/// } +/// +/// // Because type parameters can't be generically formatted, +/// // you need to list impls with concrete `B` type arguments. +/// // +/// // the generic parameters to the impl block go inside `[]` +/// (impl[A] Foo) +/// // the bounds in where clauses also go inside `[]` +/// (impl[A] Foo where[A: 'static]) +/// } +/// +/// +/// +/// ``` +/// +/// +/// +/// ### Phantom Type parameters +/// +/// This example demonstrates how type parameters can be ignored. +/// +/// ```rust +/// use const_panic::{ArrayString, FmtArg, impl_panicfmt}; +/// +/// use std::marker::PhantomData; +/// +/// { +/// let with_int: Foo = Foo{ +/// value: 5, +/// _marker: PhantomData, +/// }; +/// assert_eq!( +/// ArrayString::<100>::from_panicvals(&with_int.to_panicvals(FmtArg::DEBUG)).unwrap(), +/// "Foo { value: 5, _marker: PhantomData }", +/// ); +/// } +/// { +/// let with_str: Foo = Foo { +/// value: 8, +/// _marker: PhantomData, +/// }; +/// assert_eq!( +/// ArrayString::<100>::from_panicvals(&with_str.to_panicvals(FmtArg::DEBUG)).unwrap(), +/// r#"Foo { value: 8, _marker: PhantomData }"#, +/// ); +/// } +/// +/// #[derive(Debug)] +/// pub struct Foo { +/// value: u32, +/// _marker: PhantomData<(PhantomData, B)>, +/// } +/// +/// impl_panicfmt!{ +/// // `ignore` here tells the macro that this type parameter is not formatted. +/// // +/// // `ignore(u8)` tells the macro to use `u8` as the `B` type parameter for +/// // ` as PanicFmt>::PV_COUNT` in the generated `to_panicvals` method. +/// struct Foo +/// // bounds must be written in the where clause, like this: +/// where[ A: ?Sized ] +/// { +/// value: u32, +/// _marker: PhantomData<(PhantomData, B)>, +/// } +/// } +/// ``` +/// +/// +/// ### All the syntax +/// +/// ```rust +/// # use const_panic::impl_panicfmt; +/// # +/// # use std::marker::PhantomData; +/// # +/// # #[derive(Debug)] +/// # pub struct Foo<'a, 'b, C, D, E, const X: u32> +/// # where +/// # C: ?Sized, +/// # D: ?Sized, +/// # E: ?Sized, +/// # { +/// # _lifetimes: PhantomData<(&'a (), &'b ())>, +/// # _marker: PhantomData<(PhantomData, PhantomData, PhantomData)>, +/// # } +/// impl_panicfmt!{ +/// struct Foo< +/// 'a, +/// 'b, +/// // For type parameters that aren't formatted. +/// // Removes the `PanicFmt` bound on this type parameter +/// // and uses `()` as the type argument for this to get +/// // ` as PanicFmt>::PV_COUNT` in the generated `to_panicvals` methods. +/// ignore C, +/// // same as `C`, using `u8` instead of `()` +/// ignore(u8) D, +/// // un-`ignore`d type parameters must be replaced with concrete types in the impls below +/// E, +/// const X: u32, +/// > +/// // bounds must be written in the where clause, like this: +/// where[ C: ?Sized, D: ?Sized, E: ?Sized ] +/// { +/// _lifetimes: PhantomData<(&'a (), &'b ())>, +/// _marker: PhantomData<(PhantomData, PhantomData, PhantomData)>, /// } +/// +/// // The impls for this type, this is required for types with un-`ignore`d type parameters. +/// // Otherwise, a generic impl is automatically generated. +/// ( +/// impl['a, 'b, D, E: ?Sized, const X: u32] Foo<'a, 'b, D, E, u32, X> +/// where[D: ?Sized] +/// ) +/// ( +/// impl['a, 'b, D, E: ?Sized, const X: u32] Foo<'a, 'b, D, E, &str, X> +/// where[D: ?Sized] +/// ) /// } /// ``` /// @@ -168,42 +354,116 @@ #[macro_export] macro_rules! impl_panicfmt { ( - impl $type:path; + $kind:tt $typename:ident < $($rem:tt)* + ) => ( + $crate::__impl_panicfmt_step_aaa!{ + ($kind $typename) + () + ($($rem)*) + } + ); + ( + $kind:tt $typename:ident $($rem:tt)* + ) => ( + $crate::__impl_panicfmt_step_aaa!{ + ($kind $typename) + () + (> $($rem)*) + } + ); +} - $(lifetime = $lt:lifetime;)? +#[doc(hidden)] +#[macro_export] +macro_rules! __impl_panicfmt_step_aaa { + ( + (struct $struct_name:ident) + $generic_params:tt + ( + $(,)? > + $(( $($tupled:tt)* ))? + $(where[$($where_preds:tt)*])?; - struct $typename:ident $({ $($braced:tt)* })? $(( $($tupled:tt)* ))? $(;)? + $($rem:tt)* + ) ) => ( $crate::__impl_panicfmt_step_ccc!{ - ( - impl $type; - struct - lifetime($($lt,)? '_,) - ) + [ + struct $struct_name + $generic_params + ($($($where_preds)*)?) + $($rem)* + ] [] [ - $typename $({ $($braced)* })? $(( $($tupled)* ))? + $struct_name + + ( $($($tupled)*)? ) ] } ); ( - impl $type:path; + (struct $struct_name:ident) + $generic_params:tt + ( + $(,)? > + $(where[$($where_preds:tt)*])? + $(( $($tupled:tt)* ))?; - $(lifetime = $lt:lifetime;)? + $($rem:tt)* + ) + ) => ( + compile_error!{"the where clause must come after the tuple struct fields"} + ); + ( + (struct $struct_name:ident) + $generic_params:tt + ( + $(,)?> + $(where[$($where_preds:tt)*])? + { $($braced:tt)* } - enum $typename:ident{ - $( - $variant:ident $({ $($braced:tt)* })? $(( $($tupled:tt)* ))? - ),* - $(,)? + $($rem:tt)* + ) + ) => ( + $crate::__impl_panicfmt_step_ccc!{ + [ + struct $struct_name + $generic_params + ($($($where_preds)*)?) + $($rem)* + ] + [] + [ + $struct_name + + { $($braced)* } + ] } + ); + ( + (enum $enum_name:ident) + $generic_params:tt + ( + $(,)?> + $(where[$($where_preds:tt)*])? + { + $( + $variant:ident $({ $($braced:tt)* })? $(( $($tupled:tt)* ))? + ),* + $(,)? + } + + $($rem:tt)* + ) ) => ( $crate::__impl_panicfmt_step_ccc!{ - ( - impl $type; - enum - lifetime ($($lt,)? '_,) - ) + [ + enum $enum_name + $generic_params + ($($($where_preds)*)?) + $($rem)* + ] [] [ $( @@ -212,6 +472,124 @@ macro_rules! impl_panicfmt { ] } ); + + + //////////////////////////////////// + + ( + $fixed:tt + ($($prev_params:tt)*) + ($(,)? $(ignore)? $lifetime:lifetime $($rem:tt)*) + ) => ( + $crate::__impl_panicfmt_step_aaa!{ + $fixed + ($($prev_params)* ($lifetime ($lifetime) ignore ('_)) ) + ($($rem)*) + } + ); + ( + $fixed:tt + $prev_params:tt + ($(,)? $(ignore $(($($const_def:tt)*))? )? const $const_param:ident: $const_ty:ty , $($rem:tt)*) + ) => ( + $crate::__impl_panicfmt_step_aaa_const!{ + $fixed + ($(ignore $(($($const_def)*))? )?) + $prev_params + ($const_param: $const_ty) + ($($rem)*) + } + ); + ( + $fixed:tt + $prev_params:tt + ($(,)? $(ignore $(($($const_def:tt)*))? )? const $const_param:ident: $const_ty:ty > $($rem:tt)*) + ) => ( + $crate::__impl_panicfmt_step_aaa_const!{ + $fixed + ($(ignore $(($($const_def)*))? )?) + $prev_params + ($const_param: $const_ty) + (> $($rem)*) + } + ); + ( + $fixed:tt + ($($prev_params:tt)*) + ($(,)? ignore $(($ignore_ty:ty))? $type_param:ident $($rem:tt)*) + ) => ( + $crate::__impl_panicfmt_step_aaa!{ + $fixed + ($($prev_params)* ($type_param ($type_param) ignore (($($ignore_ty)?)) ) ) + ($($rem)*) + } + ); + ( + $fixed:tt + ($($prev_params:tt)*) + ($(,)? $type_param:ident $($rem:tt)*) + ) => ( + $crate::__impl_panicfmt_step_aaa!{ + $fixed + (kept_type[$type_param] $($prev_params)* ($type_param ($type_param) kept ()) ) + ($($rem)*) + } + ); + ( + $fixed:tt + $prev_params:tt + ($($rem:tt)+) + ) => ( + $crate::__::compile_error!{concat!( + "could not parse this in the generic parameter(s) of the type definition:", + stringify!($($rem:tt)+) + )} + ); +} + +#[doc(hidden)] +#[macro_export] +macro_rules! __impl_panicfmt_step_aaa_const { + ( + $fixed:tt + (ignore($const_def:expr)) + ($($prev_params:tt)*) + ($const_param:ident: $const_ty:ty) + $rem:tt + ) => { + $crate::__impl_panicfmt_step_aaa!{ + $fixed + ( + $($prev_params)* + ( + $const_param + (const $const_param: $const_ty) + ignore ({$const_def}) + ) + ) + $rem + } + }; + ( + $fixed:tt + ($(ignore)?) + ($($prev_params:tt)*) + ($const_param:ident: $const_ty:ty) + $rem:tt + ) => { + $crate::__impl_panicfmt_step_aaa!{ + $fixed + ( + $($prev_params)* + ( + $const_param + (const $const_param: $const_ty) + ignore ({$crate::__::ConstDefault::DEFAULT}) + ) + ) + $rem + } + }; } #[doc(hidden)] @@ -270,13 +648,16 @@ macro_rules! __impl_panicfmt_step_ccc { // Finished parsing variants/structs, // ( - ($($kept:tt)*) - [$($variants:tt)*] + $kept:tt + $variants:tt [] ) => { - $crate::__impl_panicfmt_step_finished!{ - $($kept)* - variants( $($variants)* ) + $crate::__impl_panicfmt_step__panicfmt_impl!{ + $kept + variants $variants + + $kept + $variants } }; } @@ -288,7 +669,7 @@ macro_rules! __impl_panicfmt_step_ccc_inner { $kept:tt [$($prev_variants:tt)*] $variant:ident - ($delim:ident $($ignored0:tt)*) + ($delim:ident $($ignore0:tt)*) [$($rem_variants:tt)*] $(prefix (($($p_fname:ident)?, $p_ty:ty) ($p_index:tt $p_fi_index:tt)))* @@ -309,39 +690,58 @@ macro_rules! __impl_panicfmt_step_ccc_inner { ] [$($rem_variants)*] } - } + }; } #[doc(hidden)] #[macro_export] -macro_rules! __impl_panicfmt_step_finished { +macro_rules! __impl_panicfmt_step__panicfmt_impl { ( - impl $type:path; - $type_kind:ident + [ + $type_kind:ident $type_name:ident + ( + $($kept_type:ident [$kept_type_:ident])* + $(( + $gp_arg:tt + ($($gp_param:tt)*) + $ignorance:tt + ($($gp_arg_concrete:tt)*) + ))* + ) + ($($where_preds:tt)*) - lifetime($lt:lifetime , $($ignored0:tt)*) + $($to_panicval_impls:tt)* + ] - variants($( + variants[$( ( $variant:ident $delimiter:ident - ($field_amount:expr, $($ignored2:tt)*) + ($field_amount:expr, $($ignore2:tt)*) => $( $is_last_field:ident ( - ($fpati:tt $($ignored3:tt)?), - ($fname:tt $($ignored4:tt)?), + ($fpati:tt $($ignore3:tt)?), + ($fname:tt $($ignore4:tt)?), $ty:ty ) )* ) - )*) + )*] + + $kept:tt + $variants:tt ) => ( - impl $crate::PanicFmt for $type { + impl<$($($gp_param)*),*> $crate::PanicFmt for $type_name<$($gp_arg),*> + where + $($kept_type_: $crate::PanicFmt,)* + $($where_preds)* + { type This = Self; type Kind = $crate::fmt::IsCustomType; + const PV_COUNT: $crate::__::usize = $crate::utils::slice_max_usize(&[ $( $crate::fmt::ComputePvCount{ @@ -353,16 +753,263 @@ macro_rules! __impl_panicfmt_step_finished { ]); } - impl $type { + macro_rules! __assert_type_name__ { + ($type_name) => () + } + + $crate::__impl_to_panicvals!{ + [$($kept_type)*] + [$($ignorance ($($gp_arg_concrete)*))*] + [ + $type_name + [$($where_preds)*] + $(( + ($($gp_param)*) + $gp_arg + ))* + ] + [$($to_panicval_impls)*] + $kept + $variants + } + ); +} + +#[doc(hidden)] +#[macro_export] +macro_rules! __impl_to_panicvals { + ( + [$(kept_type)*] + $ignorances:tt + $gp_param:tt + [$($to_panicval_impls:tt)+] + $kept:tt + $variants:tt + ) => ( + $( + $crate::__impl_to_panicvals_step_aaa!{ + $ignorances + $to_panicval_impls + $to_panicval_impls + $kept + $variants + } + )* + ); + ( + [] + $ignorances:tt + [$type_name:ident [$($where_preds:tt)*] $(( ($($gp_param:tt)*) $gp_arg:tt ))* ] + [] + $kept:tt + $variants:tt + ) => ( + $crate::__impl_to_panicvals_step_aaa!{ + $ignorances + ( + impl[$($($gp_param)*),*] $type_name<$($gp_arg),*> + where[$($where_preds)*] + ) + ( + impl[$($($gp_param)*),*] $type_name<$($gp_arg),*> + where[$($where_preds)*] + ) + $kept + $variants + } + ); + ([$(kept_type)+] $ignorances:tt $gp_param:tt [] $kept:tt $variants:tt) => ( + $crate::__::compile_error!{"\ + type parameters must either be:\n\ + - all prefixed with ignore\n\ + - be replaced with concrete type arguments in \ + `(impl Foo)`s after the type definition\n\ + "} + ); +} + +#[doc(hidden)] +#[macro_export] +macro_rules! __impl_to_panicvals_step_aaa { + ( + $ignorances:tt + (impl $([$($impl_param:tt)*])? $type_name:ident $(where $where_preds:tt)?) + $impl:tt + $kept:tt + $variants:tt + ) => { + __assert_type_name__!{$type_name} + + $crate::__impl_to_panicvals_step_bbb!{ + ([$($($impl_param)*)?] $type_name $kept $variants) + $ignorances + [] + [> $(where $where_preds)?] + } + }; + ( + $ignorances:tt + (impl $([$($impl_param:tt)*])? $type_name:ident <$($args:tt)*) + (impl $([$($impl_paramb:tt)*])? $type:path $(where $where_preds:tt)? ) + $kept:tt + $variants:tt + ) => { + __assert_type_name__!{$type_name} + + $crate::__impl_to_panicvals_step_bbb!{ + ([$($($impl_param)*)?] $type $kept $variants) + $ignorances + [] + [$($args)*] + } + }; +} + +#[doc(hidden)] +#[macro_export] +macro_rules! __impl_to_panicvals_step_bbb { + // finished parsing + ( + $kept:tt + [] + $cself:tt + [> $(where[$($where_preds:tt)*])?] + ) => { + $crate::__impl_to_panicvals_finish!{ + $kept + [$($($where_preds)*)?] + $cself + } + }; + + + + // const or lifetime argument + ($kept:tt $ignorance:tt $prev_cself:tt [$(,)? $gen_arg:tt , $($rem:tt)*]) => { + $crate::__impl_to_panicvals_step_bbb_inner!{ + $kept + $ignorance + [$gen_arg] + $prev_cself + [$($rem)*] + } + }; + ($kept:tt $ignorance:tt $prev_cself:tt [$(,)? $gen_arg:tt > $($rem:tt)*]) => { + $crate::__impl_to_panicvals_step_bbb_inner!{ + $kept + $ignorance + [$gen_arg] + $prev_cself + [> $($rem)*] + } + }; + + // type argument + ($kept:tt $ignorance:tt $prev_cself:tt [$(,)? $ty_arg:ty , $($rem:tt)* ]) => { + $crate::__impl_to_panicvals_step_bbb_inner!{ + $kept + $ignorance + [$ty_arg] + $prev_cself + [$($rem)*] + } + }; + ($kept:tt $ignorance:tt $prev_cself:tt [$(,)? $ty_arg:ty > $($rem:tt)*]) => { + $crate::__impl_to_panicvals_step_bbb_inner!{ + $kept + $ignorance + [$ty_arg] + $prev_cself + [> $($rem)*] + } + }; +} + +#[doc(hidden)] +#[macro_export] +macro_rules! __impl_to_panicvals_step_bbb_inner { + ( + $kept:tt + [kept $gp_arg_concrete:tt $($rem_ignorance:tt)*] + [$gen_arg:tt] + [$($prev_cself:tt)*] + $rem:tt + ) => { + $crate::__impl_to_panicvals_step_bbb!{ + $kept + [$($rem_ignorance)*] + [$($prev_cself)* $gen_arg,] + $rem + } + }; + ( + $kept:tt + [ignore ($($gp_arg_concrete:tt)*) $($rem_ignorance:tt)*] + $gen_arg:tt + [$($prev_cself:tt)*] + $rem:tt + ) => { + $crate::__impl_to_panicvals_step_bbb!{ + $kept + [$($rem_ignorance)*] + [$($prev_cself)* $($gp_arg_concrete)*,] + $rem + } + }; +} + +#[doc(hidden)] +#[macro_export] +macro_rules! __impl_to_panicvals_finish { + // finished parsing + ( + ( + [$($impl_param:tt)*] + $type:ty + + [ + $type_kind:ident $type_name:ident + $generics:tt + $type_where_preds:tt + + $($to_panicval_impls:tt)* + ] + + [$( + ( + $variant:ident + $delimiter:ident + ($field_amount:expr, $($ignore2:tt)*) + => + $( + $is_last_field:ident + ( + ($fpati:tt $($ignore3:tt)?), + ($fname:tt $($ignore4:tt)?), + $ty:ty + ) + )* + ) + )*] + + ) + [ $($where_preds:tt)* ] + $cself:tt + ) => { + + impl<$($impl_param)*> $type + where + $($where_preds)* + { pub const fn to_panicvals( &self, mut fmt: $crate::FmtArg, - ) -> [$crate::PanicVal<$lt>; <$type as $crate::PanicFmt>::PV_COUNT] { + ) -> [$crate::PanicVal<'_>; $crate::__ipm_cself!($type_name $cself)] { match self { $( $crate::__ipm_pattern!($type_kind $variant{$($fpati: $fname,)* ..}) => $crate::__ipm_fmt!{ - (<$type as $crate::PanicFmt>::PV_COUNT) + ($crate::__ipm_cself!($type_name $cself)) $delimiter $variant fmt @@ -373,7 +1020,7 @@ macro_rules! __impl_panicfmt_step_finished { } } - ) + }; } #[doc(hidden)] @@ -406,7 +1053,8 @@ macro_rules! __ipm_fmt { ], $( $crate::__ipm_pv_fmt_field_name!($delimiter $fname), - &<$ty as $crate::PanicFmt>::PROOF + &$crate::PanicFmt::PROOF + .infer($fname) .coerce($fname) .to_panicvals($fmt), &$crate::__ipm_pv_comma!($is_last_field) @@ -454,3 +1102,11 @@ macro_rules! __ipm_pv_comma { $crate::fmt::COMMA_TERM }; } + +#[doc(hidden)] +#[macro_export] +macro_rules! __ipm_cself { + ($type_name:ident [$($cself:tt)*]) => { + <$type_name<$($cself)*> as $crate::PanicFmt>::PV_COUNT + }; +} diff --git a/src/macros/non_basic_macros.rs b/src/macros/non_basic_macros.rs index 2c0bcb1..ef8adda 100644 --- a/src/macros/non_basic_macros.rs +++ b/src/macros/non_basic_macros.rs @@ -1,7 +1,7 @@ /// Formats multiple values into an array of `PanicVal`s. /// /// The `flatten`ing part comes from the fact that each argument -/// is potentially converted to an array of `PanicVal`, +/// is converted to an array of `PanicVal`, /// which are then concatenated into a single array. /// /// # Arguments @@ -9,7 +9,7 @@ /// The syntax for this macro is /// ```text /// flatten_panicvals!( -/// $fmtarg:expr; +/// $fmtarg:expr $(, $pv_count:expr)?; /// $( /// $($Type:ty => )? $($format_override:tt :)? $arg_to_fmt:expr /// ),* @@ -20,6 +20,12 @@ /// `$fmtarg` is a [`FmtArg`](crate::FmtArg) argument /// which determines how non-literal `$arg_to_fmt` arguments are formatted. /// +/// `$pv_count` is an optional argument which overrides the length of the array +/// that this returns. +///
If this argument is smaller than the flattened arrays would create, +/// it produces a compile-time error. +/// If this is larger, this fills the trailing elements with `PanicVal::EMPTY`. +/// /// [`$format_override`](#formatting-overrides) overrides the `$fmtarg` argument, /// changing how that `$arg_to_fmt` argument is formatted. /// @@ -151,8 +157,7 @@ before formatting the argument, and uses Display formatting for that argument. /// struct Foo(u32, &'static str); /// /// const_panic::impl_panicfmt!{ -/// impl Foo; -/// struct Foo(u32, &'static str) +/// struct Foo(u32, &'static str); /// } /// /// struct Bar { @@ -160,7 +165,6 @@ before formatting the argument, and uses Display formatting for that argument. /// } /// /// const_panic::impl_panicfmt!{ -/// impl Bar; /// struct Bar { /// x: &'static str, /// } @@ -594,10 +598,13 @@ macro_rules! __to_pvf_used_length { /// /// # Example /// -/// Implementing panic formatting for a generic struct. +/// Implementing panic formatting for a generic struct with [`flatten_panicvals`]. /// /// ```rust -/// use const_panic::{ArrayString, FmtArg, impl_panicfmt, inline_macro}; +/// use const_panic::{ +/// fmt::{self, ComputePvCount, FmtArg}, +/// ArrayString, PanicFmt, PanicVal, flatten_panicvals, inline_macro, +/// }; /// /// // Debug formatting /// assert_eq!( @@ -633,24 +640,38 @@ macro_rules! __to_pvf_used_length { /// /// struct Foo(T, T); /// +/// impl PanicFmt for Foo +/// where +/// T: PanicFmt, +/// { +/// type This = Self; +/// type Kind = const_panic::IsCustomType; +/// +/// const PV_COUNT: usize = ComputePvCount{ +/// field_amount: 2, +/// summed_pv_count: T::PV_COUNT * 2, +/// delimiter: fmt::TypeDelim::Tupled, +/// }.call(); +/// } +/// /// // Because of limitations of stable const evaluation, -/// // you have to use macros to implement panic formatting -/// // for more than one concrete type (ignoring lifetimes). -/// // -/// // This macro implements panic formatting for +/// // this macro only implements panic formatting for /// // - `Foo` /// // - `Foo` /// // - `Foo<&str>` -/// inline_macro! { -/// (bool), -/// (u8), -/// // Types with lifetimes must either elide the lifetime, use `'_` or `'static`. -/// (&str); -/// -/// ($T:ty) => -/// impl_panicfmt!{ -/// impl Foo<$T>; -/// struct Foo($T, $T) +/// inline_macro!{ +/// (bool), (u8), (&str); +/// ($T:ty)=> +/// impl Foo<$T> { +/// const fn to_panicvals(&self, fmtarg: FmtArg) -> [PanicVal<'_>; Foo::<$T>::PV_COUNT] { +/// flatten_panicvals! {fmtarg; +/// "Foo", +/// open: fmt::OpenParen, +/// $T => self.0, fmt::COMMA_SEP, +/// $T => self.1, fmt::COMMA_TERM, +/// close: fmt::CloseParen, +/// } +/// } /// } /// } /// diff --git a/src/macros/unwrapping.rs b/src/macros/unwrapping.rs index ac64e06..4c8e732 100644 --- a/src/macros/unwrapping.rs +++ b/src/macros/unwrapping.rs @@ -1,4 +1,4 @@ -/// Returns the value in the `Some` variant. +/// Gets the value in the `Some` variant. /// /// # Panics /// @@ -59,7 +59,7 @@ macro_rules! unwrap_some { }; } -/// Returns the value in the `Ok` variant. +/// Gets the value in the `Ok` variant. /// /// # Panics /// @@ -101,9 +101,8 @@ macro_rules! unwrap_some { /// number: u8, /// } /// +/// // You can also use `#[derive(PanicFmt))]` with the "derive" feature /// const_panic::impl_panicfmt!{ -/// impl OddError; -/// /// struct OddError { /// at: usize, /// number: u8, @@ -140,7 +139,7 @@ macro_rules! unwrap_ok { }; } -/// Returns the value in the `Err` variant. +/// Gets the value in the `Err` variant. /// /// # Panics /// diff --git a/src/panic_val.rs b/src/panic_val.rs index 46f4f46..0f0c50d 100644 --- a/src/panic_val.rs +++ b/src/panic_val.rs @@ -1,10 +1,13 @@ use crate::{ - fmt::{FmtArg, FmtKind}, - utils::{Sign, TailShortString, WasTruncated}, + fmt::{FmtArg, FmtKind, NumberFmt}, + utils::{string_cap, Packed, PreFmtString, RangedBytes, Sign, TailShortString, WasTruncated}, }; #[cfg(feature = "non_basic")] -use crate::fmt::{IsLast, ShortString}; +use crate::{ + array_string::TinyString, + fmt::{IsLast, ShortString}, +}; /// An opaque enum of the values that this crate knows how to format, /// along with some formatting metadata. @@ -17,60 +20,114 @@ use crate::fmt::{IsLast, ShortString}; /// - [`ShortString`](crate::fmt::ShortString) /// (with the "non_basic" feature, enabled by default) /// -#[non_exhaustive] #[derive(Copy, Clone)] pub struct PanicVal<'a> { pub(crate) var: PanicVariant<'a>, - leftpad: u8, - rightpad: u8, - fmt_kind: FmtKind, } -#[non_exhaustive] #[derive(Copy, Clone)] pub(crate) enum PanicVariant<'a> { - Str(&'a str), + Str(StrFmt, Packed<&'a str>), #[cfg(feature = "non_basic")] - ShortString(ShortString), + ShortString(StrFmt, TinyString<{ string_cap::TINY }>), + PreFmt(PreFmtString), Int(IntVal), #[cfg(feature = "non_basic")] Slice(crate::slice_stuff::Slice<'a>), } +pub(crate) enum PanicClass<'a> { + PreFmt(RangedBytes<&'a [u8]>), + Int(IntVal), + #[cfg(feature = "non_basic")] + Slice(crate::slice_stuff::Slice<'a>), +} + +#[derive(Copy, Clone)] +pub(crate) struct StrFmt { + pub(crate) leftpad: u8, + pub(crate) rightpad: u8, + pub(crate) fmt_kind: FmtKind, +} + +impl StrFmt { + const DISPLAY: Self = Self { + leftpad: 0, + rightpad: 0, + fmt_kind: FmtKind::Display, + }; + + pub const fn new(fmtarg: FmtArg) -> Self { + Self { + leftpad: 0, + rightpad: 0, + fmt_kind: fmtarg.fmt_kind, + } + } +} + impl<'a> PanicVal<'a> { /// A `PanicVal` that formats to nothing. pub const EMPTY: Self = PanicVal::write_str(""); /// How many spaces are printed before this pub const fn leftpad(&self) -> u8 { - self.leftpad + use self::PanicVariant as PV; + + match self.var { + PV::Str(strfmt, ..) => strfmt.leftpad, + #[cfg(feature = "non_basic")] + PV::ShortString(strfmt, ..) => strfmt.leftpad, + _ => 0, + } } /// How many spaces are printed after this pub const fn rightpad(&self) -> u8 { - self.rightpad - } - /// Sets the amount of spaces printed before this to `fmtarg.indentation`. - pub const fn with_leftpad(mut self, fmtarg: FmtArg) -> Self { - self.leftpad = fmtarg.indentation; - self - } + use self::PanicVariant as PV; - /// Sets the amount of spaces printed after this to `fmtarg.indentation`. - pub const fn with_rightpad(mut self, fmtarg: FmtArg) -> Self { - self.rightpad = fmtarg.indentation; - self + match self.var { + PV::Str(strfmt, ..) => strfmt.rightpad, + #[cfg(feature = "non_basic")] + PV::ShortString(strfmt, ..) => strfmt.rightpad, + _ => 0, + } } +} + +macro_rules! mutate_strfmt { + ($self:ident, |$strfmt:ident| $mutator:expr) => { + match $self.var { + PanicVariant::Str(mut $strfmt, str) => { + $mutator; + PanicVal { + var: PanicVariant::Str($strfmt, str), + } + } + #[cfg(feature = "non_basic")] + PanicVariant::ShortString(mut $strfmt, str) => { + $mutator; + PanicVal { + var: PanicVariant::ShortString($strfmt, str), + } + } + var => PanicVal { var }, + } + }; +} - /// Sets the amount of spaces printed before this - pub const fn set_leftpad(mut self, fmtarg: FmtArg) -> Self { - self.leftpad = fmtarg.indentation; - self +impl<'a> PanicVal<'a> { + /// Sets the amount of spaces printed before this to `fmtarg.indentation`. + /// + /// Note that only strings can be padded. + pub const fn with_leftpad(self, fmtarg: FmtArg) -> Self { + mutate_strfmt! {self, |strfmt| strfmt.leftpad = fmtarg.indentation} } - /// Sets the amount of spaces printed after this - pub const fn set_rightpad(mut self, fmtarg: FmtArg) -> Self { - self.rightpad = fmtarg.indentation; - self + /// Sets the amount of spaces printed after this to `fmtarg.indentation`. + /// + /// Note that only strings can be padded. + pub const fn with_rightpad(self, fmtarg: FmtArg) -> Self { + mutate_strfmt! {self, |strfmt| strfmt.rightpad = fmtarg.indentation} } /// Constructs a PanicVal which outputs the contents of `string` verbatim. @@ -78,28 +135,29 @@ impl<'a> PanicVal<'a> { /// Equivalent to `PanicVal::from_str(string, FmtArg::DISPLAY)` pub const fn write_str(string: &'a str) -> Self { PanicVal { - var: PanicVariant::Str(string), - leftpad: 0, - rightpad: 0, - fmt_kind: FmtKind::Display, + var: PanicVariant::Str(StrFmt::DISPLAY, Packed(string)), } } - /// Constructs a PanicVal from a [`ShortString`], that's output verbatim. + /// Constructs a PanicVal from a [`ShortString`], which outputs the string verbatim. #[cfg(feature = "non_basic")] pub const fn write_short_str(string: ShortString) -> Self { Self { - var: PanicVariant::ShortString(string), - ..Self::EMPTY + var: PanicVariant::ShortString(StrFmt::DISPLAY, string.to_compact()), } } + /// Constructs a `PanicVal` usable as a separator between fields or elements. /// + /// This is sensitive to the [`fmtarg.is_alternate`] flag, + /// for more details on that you can look at the docs for + /// [`Separator::to_panicval`](crate::fmt::Separator#method.to_panicval) /// /// # Panics /// - /// This may panic if `string.len()` is greater than 12. + /// This panics if `string.len()` is greater than 12. /// + /// [`fmtarg.is_alternate`]: crate::FmtArg#structfield.is_alternate #[cfg(feature = "non_basic")] pub const fn from_element_separator( separator: &str, @@ -113,123 +171,192 @@ impl<'a> PanicVal<'a> { (IsLast::Yes, true) => (ShortString::concat(&[separator, "\n"]), 0), }; - Self { - var: PanicVariant::ShortString(concat), + let strfmt = StrFmt { leftpad: 0, rightpad, fmt_kind: FmtKind::Display, + }; + Self { + var: PanicVariant::ShortString(strfmt, concat.to_compact()), } } - pub(crate) const fn __new(var: PanicVariant<'a>, fmtarg: FmtArg) -> Self { - Self { - var, - leftpad: 0, - rightpad: 0, - fmt_kind: fmtarg.fmt_kind, + #[inline(always)] + pub(crate) const fn __new(var: PanicVariant<'a>) -> Self { + Self { var } + } + + pub(crate) const fn to_class(&self) -> (StrFmt, PanicClass<'_>) { + match &self.var { + &PanicVariant::Str(strfmt, Packed(str)) => { + let ranged = RangedBytes { + start: 0, + end: str.len(), + bytes: str.as_bytes(), + }; + + (strfmt, PanicClass::PreFmt(ranged)) + } + #[cfg(feature = "non_basic")] + PanicVariant::ShortString(strfmt, str) => (*strfmt, PanicClass::PreFmt(str.ranged())), + PanicVariant::PreFmt(str) => (StrFmt::DISPLAY, PanicClass::PreFmt(str.ranged())), + PanicVariant::Int(int) => (StrFmt::DISPLAY, PanicClass::Int(*int)), + #[cfg(feature = "non_basic")] + PanicVariant::Slice(slice) => ( + StrFmt::new(slice.fmtarg.unpack()), + PanicClass::Slice(*slice), + ), } } - // Gets the bytes in the string and integer variants, - // truncating them to `truncate_to`. - // - pub(crate) const fn __string( + pub(crate) const fn to_class_truncated( &self, mut truncate_to: usize, - ) -> (usize, usize, &[u8], FmtKind, WasTruncated) { - let leftpad = self.leftpad as usize; - if leftpad > truncate_to { + ) -> (StrFmt, PanicClass<'_>, WasTruncated) { + let (mut strfmt, class) = self.to_class(); + + if strfmt.leftpad as usize > truncate_to { return ( - leftpad - truncate_to, - 0, - &[], - FmtKind::Display, + StrFmt { + leftpad: strfmt.leftpad - truncate_to as u8, + rightpad: 0, + fmt_kind: FmtKind::Display, + }, + PanicClass::PreFmt(RangedBytes::EMPTY), WasTruncated::Yes(0), ); } else { - truncate_to -= leftpad; + truncate_to -= strfmt.leftpad as usize; }; - let string; - let was_trunc; - let fmt_kind; + let was_trunc: WasTruncated; + let orig_len: usize; - match &self.var { - PanicVariant::Str(str) => { - string = str.as_bytes(); - fmt_kind = self.fmt_kind; - was_trunc = if let FmtKind::Display = self.fmt_kind { - crate::utils::truncated_str_len(string, truncate_to) + match class { + PanicClass::PreFmt(str) => { + was_trunc = if let PanicVariant::PreFmt(pfmt) = self.var { + if pfmt.len() <= truncate_to { + WasTruncated::No + } else { + WasTruncated::Yes(0) + } } else { - crate::utils::truncated_debug_str_len(string, truncate_to) + if let FmtKind::Display = strfmt.fmt_kind { + crate::utils::truncated_str_len(str, truncate_to) + } else { + crate::utils::truncated_debug_str_len(str, truncate_to) + } }; + orig_len = str.len(); } - #[cfg(feature = "non_basic")] - PanicVariant::ShortString(str) => { - string = str.as_bytes(); - fmt_kind = self.fmt_kind; - was_trunc = if let FmtKind::Display = self.fmt_kind { - crate::utils::truncated_str_len(string, truncate_to) - } else { - crate::utils::truncated_debug_str_len(string, truncate_to) - }; - } - PanicVariant::Int(int) => { - string = int.0.get(); - fmt_kind = FmtKind::Display; - was_trunc = if int.0.len() <= truncate_to { + PanicClass::Int(int) => { + strfmt.fmt_kind = FmtKind::Display; + was_trunc = if int.len() <= truncate_to { WasTruncated::No } else { WasTruncated::Yes(0) }; + orig_len = int.len(); } #[cfg(feature = "non_basic")] - PanicVariant::Slice(_) => panic!("this method should only be called on non-slices"), - }; - truncate_to -= was_trunc.get_length(string); + PanicClass::Slice(_) => { + was_trunc = WasTruncated::No; + orig_len = 0; + } + } + truncate_to -= was_trunc.get_length(orig_len); - let rightpad = crate::utils::min_usize(self.rightpad as usize, truncate_to); + strfmt.rightpad = crate::utils::min_usize(strfmt.rightpad as usize, truncate_to) as u8; - (leftpad, rightpad, string, fmt_kind, was_trunc) + (strfmt, class, was_trunc) } } #[derive(Copy, Clone)] -pub struct IntVal(TailShortString<40>); +pub(crate) struct IntVal { + sign: Sign, + number_fmt: NumberFmt, + is_alternate: bool, + // the size of the integer in bits + bits: u8, + // the length of the integer in bytes, once written. + len: u8, + + value: Packed, +} impl IntVal { - pub(crate) const fn from_u128(n: u128, f: FmtArg) -> Self { - Self::new(Sign::Positive, n, f) + pub(crate) const fn from_u128(n: u128, bits: u8, f: FmtArg) -> PanicVal<'static> { + Self::new(Sign::Positive, n, bits, f) } - pub(crate) const fn from_i128(n: i128, f: FmtArg) -> Self { + pub(crate) const fn from_i128(n: i128, bits: u8, f: FmtArg) -> PanicVal<'static> { let is_neg = if n < 0 { Sign::Negative } else { Sign::Positive }; - Self::new(is_neg, n.unsigned_abs(), f) - } - const fn new(sign: Sign, mut n: u128, _f: FmtArg) -> Self { - let mut start = 40usize; - let mut buffer = [0u8; 40]; - - loop { - start -= 1; - let digit = (n % 10) as u8; - buffer[start] = b'0' + digit; - n /= 10; - if n == 0 { - break; + Self::new(is_neg, n.unsigned_abs(), bits, f) + } + + const fn new(sign: Sign, n: u128, bits: u8, fmtarg: FmtArg) -> PanicVal<'static> { + use crate::int_formatting::compute_len; + + let len = compute_len(sign, n, bits, fmtarg); + + let this = IntVal { + sign, + number_fmt: fmtarg.number_fmt, + is_alternate: fmtarg.is_alternate, + bits, + len, + value: Packed(n), + }; + + let var = if len as usize <= string_cap::PREFMT { + PanicVariant::PreFmt(this.fmt::<{ string_cap::PREFMT }>()) + } else { + PanicVariant::Int(this) + }; + PanicVal { var } + } + + pub(crate) const fn fmt(self) -> TailShortString { + use crate::int_formatting::{fmt_binary, fmt_decimal, fmt_hexadecimal}; + + let IntVal { + sign, + number_fmt, + is_alternate, + len: _, + bits, + value: Packed(n), + } = self; + + match number_fmt { + NumberFmt::Decimal => fmt_decimal::(sign, n), + NumberFmt::Binary => { + let masked = apply_mask(sign, n, bits); + fmt_binary::(masked, is_alternate) + } + NumberFmt::Hexadecimal => { + let masked = apply_mask(sign, n, bits); + fmt_hexadecimal::(masked, is_alternate) } } + } - if let Sign::Negative = sign { - start -= 1; - buffer[start] = b'-'; - } + pub(crate) const fn len(&self) -> usize { + self.len as usize + } +} + +const fn apply_mask(sign: Sign, n: u128, bits: u8) -> u128 { + if let Sign::Negative = sign { + let mask: u128 = if bits == 128 { !0 } else { (1 << bits) - 1 }; - // safety: buffer is only ever written ascii, so its automatically valid utf8. - unsafe { Self(TailShortString::new(start as u8, buffer)) } + (n as i128).wrapping_neg() as u128 & mask + } else { + n } } diff --git a/src/proc_macro_reexports/panicfmt_derive.rs b/src/proc_macro_reexports/panicfmt_derive.rs new file mode 100644 index 0000000..9ea8023 --- /dev/null +++ b/src/proc_macro_reexports/panicfmt_derive.rs @@ -0,0 +1,300 @@ +/** + +Derives the [`PanicFmt`](trait@crate::PanicFmt) trait. + +This requires the `"derive"` feature, disabled by default. + +This generates a generic [`PanicFmt`](trait@crate::PanicFmt) impl, +as well as one or more inherent `to_panicvals` method definitions +[as described in the trait docs](trait@crate::PanicFmt#implementor). + +You can also use [`impl_panicfmt`] as an alternative that requires less time to compile +from scratch, but requires repeating the type definition. + +[Jump straight to examples](#examples) + +# Limitations + + +### Type parameters + +Types with type parameters can't be generically formatted, +to work around this you can use either or both of these attributes: +- `#[pfmt(ignore(T))]`: +if the type parameter(s) are only used in marker types (eg: `PhantomData`). +- `#[pfmt(impl Foo)]`: +to implement panic formatting with concrete type arguments +(this attribute can be used multiple times to add impls). + +This limitation is caused by: +- the lack of trait bound support in stable const fns. +- the need to [have a concrete type argument](#concrete-pv-count) + +[example below](#type-parameter-example) + +### Const parameters + +Const parameters must not affect the value of the `PanicFmt::PV_COUNT` of this type, +since the const parameter [must be replaceable with a concrete value](#concrete-pv-count). +
Note that arrays have a `PV_COUNT` of `1` for all lengths. + + +### Concrete `Self` type for `PanicFmt::PV_COUNT` + +The `to_panicvals` method that this macro generates roughly returns a +```text +[PanicVal<'_>; ::PV_COUNT] +``` + +Because of limitations in stable const generics, +the generic arguments of `Self` in the above code must be replaced with concrete arguments, +requiring: +- Lifetime arguments to be replaced with `'_` +- Type arguments to be replaced with concrete types +(usually `()` or the concrete types used in [`#[pfmt(impl ....)]`](#pfmt-impl-attr) attributes) +- Const arguments to be replaced with concrete values (usually the default value for the type) + + +# Attributes + +### Container attributes + +Attributes used above the type definition. + +### `#[pfmt(crate = foo::bar)]` + +Replaces the path to `const_panic` with `foo::bar` + +[example](#crate-example) + +### `#[pfmt(debug_print)]`:
+ +For diagnostics, causes the derive macro to panic with the code generated by it. + + +##### `#[pfmt(ignored(T, C))]` + +Accepts the names of type and const parameters, +replacing the generic arguments in [`here`](#concrete-pv-count) with a concrete value. + +For type parameters, this replaces the type parameter with `()` unless overriden, +and also tells the derive not to require `T: PanicFmt` in +the `PanicFmt` implementation for the deriving type +(since the type parameter is not formatted). + +Const parameters are ignored by default, +replacing them with the default value for that type [^1] + +The concrete value for each generic parameter can be overriden with `T = value` + +examples: +- `#[pfmt(ignored(T))]` +- `#[pfmt(ignored(T = u16))]` +- `#[pfmt(ignored(T = u32, C))]` +- `#[pfmt(ignored(T, C = 100))]` +- `#[pfmt(ignored(U = str, A = false))]` + +([more conplete example](#phantom-type-parameter-example)) + +[^1]: a private trait is used to get the default value for const parameters. + + +##### `#[pfmt(impl Foo)]` + +Tells the derive to generate an inherent `to_panicvals` method for the type in the attribute +(it must be the deriving type with concrete enough generic arguments). + +examples: +- `#[pfmt(impl Foo)]` +- `#[pfmt(impl Foo)]`: +this also requires a [`#[pfmt(ignored(T))]`](#pfmt-ignored-attr) attribute + +([more conplete example](#type-parameter-example)) + +# Examples + +### Basic struct + +```rust +use const_panic::{ArrayString, FmtArg, PanicFmt}; + +let foo = Foo { x: 3, y: &[3, 5, 8] }; +assert_eq!( + ArrayString::<100>::from_panicvals(&foo.to_panicvals(FmtArg::DEBUG)).unwrap(), + "Foo { x: 3, y: [3, 5, 8] }", +); + +#[derive(PanicFmt)] +struct Foo<'a> { + x: u32, + y: &'a [u8], +} +``` + +### Basic enum + +```rust +use const_panic::{ArrayString, FmtArg, PanicFmt}; + +let bar = Foo::Bar; +assert_eq!( + ArrayString::<100>::from_panicvals(&bar.to_panicvals(FmtArg::DEBUG)).unwrap(), + "Bar", +); + +let baz = Foo::Baz("hello", true); +assert_eq!( + ArrayString::<100>::from_panicvals(&baz.to_panicvals(FmtArg::DEBUG)).unwrap(), + "Baz(\"hello\", true)", +); + +#[derive(PanicFmt)] +enum Foo { + Bar, + Baz(&'static str, bool), +} + +``` + + +### Type parameters + +This example demonstrates support for types with type parameters. + +```rust +use const_panic::{ArrayString, FmtArg, PanicFmt}; + +use std::marker::PhantomData; + +{ + let with_int = Foo::<&str, u8> { + value: 100u8, + _marker: PhantomData, + }; + assert_eq!( + ArrayString::<100>::from_panicvals(&with_int.to_panicvals(FmtArg::DEBUG)).unwrap(), + "Foo { value: 100, _marker: PhantomData }", + ); +} +{ + let with_str = Foo:: { + value: "hello", + _marker: PhantomData, + }; + assert_eq!( + ArrayString::<100>::from_panicvals(&with_str.to_panicvals(FmtArg::DEBUG)).unwrap(), + r#"Foo { value: "hello", _marker: PhantomData }"#, + ); +} + +#[derive(Debug, PanicFmt)] +// Tells the derive that the `A` type parameter is not formatted, +// removing the `A: PanicFmt` bound in `impl PanicFmt for Foo`, +// and using `()` as the `A` type parmeter for +// ` as PanicFmt>::PV_COUNT` in the generated `to_panicvals` method. +#[pfmt(ignore(A))] +// Defines a `to_panicvals` method for `Foo` +#[pfmt(impl Foo)] +// Defines a `to_panicvals` method for `Foo` +#[pfmt(impl Foo)] +pub struct Foo { + value: B, + _marker: PhantomData, +} + +``` + + +### Phantom Type parameters + +This example demonstrates how type parameters can be ignored with +`#[pfmt(ignore(...))]`. + +```rust +use const_panic::{ArrayString, FmtArg, PanicFmt}; + +use std::marker::PhantomData; + +{ + let with_int: Foo = Foo{ + value: 5, + _marker: PhantomData, + }; + assert_eq!( + ArrayString::<100>::from_panicvals(&with_int.to_panicvals(FmtArg::DEBUG)).unwrap(), + "Foo { value: 5, _marker: PhantomData }", + ); +} +{ + let with_str: Foo = Foo { + value: 8, + _marker: PhantomData, + }; + assert_eq!( + ArrayString::<100>::from_panicvals(&with_str.to_panicvals(FmtArg::DEBUG)).unwrap(), + r#"Foo { value: 8, _marker: PhantomData }"#, + ); +} + +#[derive(Debug, PanicFmt)] +// Tells the derive that the `A` and `B` type parameters are not formatted, +// removing the `A: PanicFmt` and `B: PanicFmt` bounds in the `PanicFmt` impl for `Foo`, +// and using `()` and `u8` as the `A` and `B` type parameters for +// ` as PanicFmt>::PV_COUNT` in the generated `to_panicvals` method. +#[pfmt(ignore(A, B = u8))] +pub struct Foo { + value: u32, + _marker: PhantomData<(PhantomData, B)>, +} + +``` + +### Const-generic struct + +```rust +use const_panic::{ArrayString, FmtArg, PanicFmt}; + +let empty = Foo([]); +assert_eq!( + ArrayString::<100>::from_panicvals(&empty.to_panicvals(FmtArg::DEBUG)).unwrap(), + "Foo([])", +); + +let three = Foo([3, 5, 8]); +assert_eq!( + ArrayString::<100>::from_panicvals(&three.to_panicvals(FmtArg::DEBUG)).unwrap(), + "Foo([3, 5, 8])", +); + +#[derive(PanicFmt)] +struct Foo([u8; LEN]); + +``` + + + +### Crate renaming + +This example demonstrates how the `const_panic` crate can be renamed, +passing the new name to the derive macro. + +```rust +# extern crate const_panic as cpanic; +# extern crate std as const_panic; +# +use cpanic::{ArrayString, FmtArg, PanicFmt};; + +let foo = Foo(Some(13)); +assert_eq!( + ArrayString::<100>::from_panicvals(&foo.to_panicvals(FmtArg::DEBUG)).unwrap(), + "Foo(Some(13))", +); + +#[derive(PanicFmt)] +#[pfmt(crate = cpanic)] +struct Foo(Option); +``` + +*/ +#[cfg_attr(feature = "docsrs", doc(cfg(feature = "derive")))] +pub use const_panic_proc_macros::PanicFmt; \ No newline at end of file diff --git a/src/slice_stuff.rs b/src/slice_stuff.rs index 1c9e766..73887fe 100644 --- a/src/slice_stuff.rs +++ b/src/slice_stuff.rs @@ -1,6 +1,7 @@ use crate::{ - fmt::{FmtArg, PanicFmt}, + fmt::{FmtArg, PackedFmtArg, PanicFmt}, panic_val::{PanicVal, PanicVariant}, + utils::Packed, StdWrapper, }; @@ -8,17 +9,17 @@ macro_rules! impl_panicfmt_array { ($(($variant:ident, $panicval_ctor:ident, $ty:ty)),* $(,)*) => { #[derive(Copy, Clone)] - #[non_exhaustive] + #[repr(packed)] pub(crate) struct Slice<'s> { - pub(crate) fmtarg: FmtArg, + pub(crate) fmtarg: PackedFmtArg, pub(crate) vari: SliceV<'s>, } + #[repr(u8)] #[derive(Copy, Clone)] - #[non_exhaustive] pub(crate) enum SliceV<'s> { $( - $variant(&'s [$ty]), + $variant(Packed<&'s [$ty]>), )* } @@ -28,23 +29,25 @@ macro_rules! impl_panicfmt_array { pub(crate) const fn arr_len(self) -> usize { match self.vari { $( - SliceV::$variant(arr) => arr.len(), + SliceV::$variant(Packed(arr)) => arr.len(), )* } } - pub(crate) const fn get(self, index: usize) -> PanicVal<'s> { - match self.vari { + } + + impl<'s> SliceV<'s> { + const fn get(self, index: usize, fmtarg: FmtArg) -> PanicVal<'s> { + match self { $( - SliceV::$variant(arr) => { + SliceV::$variant(Packed(arr)) => { let elem: &'s <$ty as PanicFmt>::This = &arr[index]; - StdWrapper(elem).to_panicval(self.fmtarg) + StdWrapper(elem).to_panicval(fmtarg) }, )* } } } - #[cfg_attr(feature = "docsrs", doc(cfg(feature = "non_basic")))] impl<'s> PanicVal<'s> { $( @@ -56,10 +59,9 @@ macro_rules! impl_panicfmt_array { } PanicVal::__new( PanicVariant::Slice(Slice{ - fmtarg, - vari: SliceV::$variant(this), - }), - fmtarg + fmtarg: fmtarg.pack(), + vari: SliceV::$variant(Packed(this)), + }) ) } )* @@ -123,60 +125,62 @@ impl_panicfmt_array! { } #[derive(Copy, Clone)] -pub(crate) struct SliceIter<'b, 's> { - slice: &'b Slice<'s>, +pub(crate) struct SliceIter<'s> { + slice: SliceV<'s>, + fmtarg: FmtArg, state: IterState, - arr_len: usize, + arr_len: u32, } -#[derive(Copy, Clone)] -enum IterState { - Start, - Index(usize), - End, +#[derive(Copy, Clone, PartialEq, Eq)] +struct IterState(u32); + +#[allow(non_upper_case_globals)] +impl IterState { + const Start: Self = Self(u32::MAX - 1); + const End: Self = Self(u32::MAX); } impl<'s> Slice<'s> { - pub(crate) const fn iter<'b>(&'b self) -> SliceIter<'b, 's> { + pub(crate) const fn iter<'b>(&'b self) -> SliceIter<'s> { SliceIter { - slice: self, + slice: self.vari, + fmtarg: self.fmtarg.unpack(), state: IterState::Start, - arr_len: self.arr_len(), + arr_len: self.arr_len() as u32, } } } -impl<'b, 's> SliceIter<'b, 's> { +impl<'s> SliceIter<'s> { pub(crate) const fn next(mut self) -> ([PanicVal<'s>; 2], Option) { - let slice = self.slice; + let fmtarg = self.fmtarg; + let ret = match self.state { IterState::Start => { self.state = if self.arr_len == 0 { IterState::End } else { - IterState::Index(0) + IterState(0) }; - [ - crate::fmt::OpenBracket.to_panicval(slice.fmtarg), - PanicVal::EMPTY, - ] + [crate::fmt::OpenBracket.to_panicval(fmtarg), PanicVal::EMPTY] } - IterState::Index(x) => { + IterState::End => { + let close_brace = crate::fmt::CloseBracket.to_panicval(fmtarg.unindent()); + return ([close_brace, PanicVal::EMPTY], None); + } + IterState(x) => { let comma = if x + 1 == self.arr_len { self.state = IterState::End; crate::fmt::COMMA_TERM } else { - self.state = IterState::Index(x + 1); + self.state = IterState(x + 1); crate::fmt::COMMA_SEP } - .to_panicval(slice.fmtarg); + .to_panicval(fmtarg); - [slice.get(x), comma] - } - IterState::End => { - let close_brace = crate::fmt::CloseBracket.to_panicval(slice.fmtarg.unindent()); - return ([close_brace, PanicVal::EMPTY], None); + [self.slice.get(x as usize, fmtarg), comma] } }; diff --git a/src/utils.rs b/src/utils.rs index 892f29d..f95d601 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -22,6 +22,23 @@ pub(crate) struct TailShortString { buffer: [u8; LEN], } +pub(crate) type PreFmtString = TailShortString<{ string_cap::PREFMT }>; + +pub(crate) mod string_cap { + /// The capacity of a [`ShortString`](crate::fmt::ShortString). + #[cfg(feature = "non_basic")] + pub const TINY: usize = 16; + + // the TailShortString that's stored in PanicVal + pub(crate) const PREFMT: usize = 21; + + // length of string to alternate binary format a 64 bit integer + pub(crate) const MEDIUM: usize = 66; + + // length of string to alternate binary format a 128 bit integer + pub(crate) const LARGE: usize = 130; +} + impl TailShortString { /// /// # Safety @@ -36,23 +53,55 @@ impl TailShortString { LEN - self.start as usize } - pub(crate) const fn get(&self) -> &[u8] { - let mut rem = self.start; - let mut out: &[u8] = &self.buffer; - while rem != 0 { - if let [_, rem @ ..] = out { - out = rem; - } - rem -= 1; + pub(crate) const fn ranged(&self) -> RangedBytes<&[u8]> { + RangedBytes { + start: self.start as usize, + end: LEN, + bytes: &self.buffer, } - out } } +//////////////////////////////////////////////////////// + +#[repr(packed)] +#[derive(Copy)] +pub(crate) struct Packed(pub(crate) T); + +impl Clone for Packed { + fn clone(&self) -> Self { + *self + } +} + +//////////////////////////////////////////////////////// + +#[derive(Copy, Clone)] +pub(crate) struct RangedBytes { + pub(crate) start: usize, + pub(crate) end: usize, + pub(crate) bytes: B, +} + +impl RangedBytes { + pub(crate) const fn len(&self) -> usize { + self.end - self.start + } +} +impl RangedBytes<&'static [u8]> { + pub const EMPTY: Self = RangedBytes { + start: 0, + end: 0, + bytes: &[], + }; +} + +//////////////////////////////////////////////////////// + #[derive(Copy, Clone)] pub(crate) enum Sign { - Negative, Positive, + Negative = 1, } #[derive(Copy, Clone)] @@ -62,10 +111,10 @@ pub(crate) enum WasTruncated { } impl WasTruncated { - pub(crate) const fn get_length(self, s: &[u8]) -> usize { + pub(crate) const fn get_length(self, len: usize) -> usize { match self { WasTruncated::Yes(x) => x, - WasTruncated::No => s.len(), + WasTruncated::No => len, } } } @@ -76,25 +125,31 @@ const fn is_char_boundary(b: u8) -> bool { // truncates a utf8-encoded string to the character before the `truncate_to` index // -pub(crate) const fn truncated_str_len(bytes: &[u8], truncate_to: usize) -> WasTruncated { - if bytes.len() <= truncate_to { +pub(crate) const fn truncated_str_len( + ranged: RangedBytes<&[u8]>, + truncate_to: usize, +) -> WasTruncated { + if ranged.len() <= truncate_to { WasTruncated::No } else { - let mut i = truncate_to; - while i != 0 { + let mut i = ranged.start + truncate_to; + while i != ranged.start { // if it's a non-continuation byte, break - if is_char_boundary(bytes[i]) { + if is_char_boundary(ranged.bytes[i]) { break; } i -= 1; } - WasTruncated::Yes(i) + WasTruncated::Yes(i - ranged.start) } } -pub(crate) const fn truncated_debug_str_len(bytes: &[u8], truncate_to: usize) -> WasTruncated { - let blen = bytes.len(); +pub(crate) const fn truncated_debug_str_len( + ranged: RangedBytes<&[u8]>, + truncate_to: usize, +) -> WasTruncated { + let blen = ranged.end; // `* 4` because the longest escape is written like `\xNN` which is 4 bytes // `+ 2` for the quote characters @@ -103,21 +158,21 @@ pub(crate) const fn truncated_debug_str_len(bytes: &[u8], truncate_to: usize) -> } else if truncate_to == 0 { WasTruncated::Yes(0) } else { - let mut i = 0; + let mut i = ranged.start; // = 1 for opening quote char let mut fmtlen = 1; loop { - let next_i = next_char_boundary(bytes, min_usize(i + 1, bytes.len())); + let next_i = next_char_boundary(ranged, min_usize(i + 1, ranged.end)); let mut j = i; while j < next_i { - fmtlen += ForEscaping::byte_len(bytes[j]); + fmtlen += ForEscaping::byte_len(ranged.bytes[j]); j += 1; } if fmtlen > truncate_to { break; - } else if next_i == bytes.len() { + } else if next_i == ranged.end { i = next_i; break; } else { @@ -128,13 +183,13 @@ pub(crate) const fn truncated_debug_str_len(bytes: &[u8], truncate_to: usize) -> if i == blen && fmtlen < truncate_to { WasTruncated::No } else { - WasTruncated::Yes(i) + WasTruncated::Yes(i - ranged.start) } } } -const fn next_char_boundary(bytes: &[u8], mut i: usize) -> usize { - while i < bytes.len() && !is_char_boundary(bytes[i]) { +const fn next_char_boundary(ranged: RangedBytes<&[u8]>, mut i: usize) -> usize { + while i < ranged.end && !is_char_boundary(ranged.bytes[i]) { i += 1; } i diff --git a/src/utils/non_basic_utils.rs b/src/utils/non_basic_utils.rs index 844a9d4..024a336 100644 --- a/src/utils/non_basic_utils.rs +++ b/src/utils/non_basic_utils.rs @@ -15,6 +15,7 @@ pub const fn panicvals_id<'a, 'b, const LEN: usize>( /// # Panics /// /// Panics if the amount of `PanicVal`s in the slices is greater than `LEN`. +/// pub const fn flatten_panicvals<'a, const LEN: usize>( mut input: &[&[PanicVal<'a>]], ) -> [PanicVal<'a>; LEN] { @@ -33,7 +34,17 @@ pub const fn flatten_panicvals<'a, const LEN: usize>( out } -/// Gets the maximum value betweem `l` and `r` +/// Gets the maximum value between `l` and `r` +/// +/// # Example +/// +/// ```rust +/// use const_panic::utils::max_usize; +/// +/// assert_eq!(max_usize(5, 3), 5); +/// assert_eq!(max_usize(5, 8), 8); +/// +/// ``` pub const fn max_usize(l: usize, r: usize) -> usize { if l > r { l @@ -43,6 +54,19 @@ pub const fn max_usize(l: usize, r: usize) -> usize { } /// Gets the maximum value in `slice`, returns `0` if the slice is empty. +/// +/// # Example +/// +/// ```rust +/// use const_panic::utils::slice_max_usize; +/// +/// assert_eq!(slice_max_usize(&[]), 0); +/// assert_eq!(slice_max_usize(&[3]), 3); +/// assert_eq!(slice_max_usize(&[5, 3]), 5); +/// assert_eq!(slice_max_usize(&[5, 8, 3]), 8); +/// assert_eq!(slice_max_usize(&[5, 13, 8, 3]), 13); +/// +/// ``` pub const fn slice_max_usize(mut slice: &[usize]) -> usize { let mut max = 0; diff --git a/tests/derive_rename_crate.rs b/tests/derive_rename_crate.rs new file mode 100644 index 0000000..c29e145 --- /dev/null +++ b/tests/derive_rename_crate.rs @@ -0,0 +1,45 @@ +#![cfg(feature = "derive")] + +extern crate const_panic as cpanic; +extern crate std as const_panic; + +use cpanic::{FmtArg, PanicFmt}; + +macro_rules! fmt_flatten { + ($fmt:expr, $val:expr) => { + cpanic::ArrayString::<256>::from_panicvals(&$val.to_panicvals($fmt)).unwrap() + }; +} + +#[test] +fn derive_struct_formatting() { + let foo = Foo { + x: &[3, 5, 8, 13], + y: 21, + }; + assert_eq!(fmt_flatten!(FmtArg::DEBUG, foo), *format!("{:?}", foo)); + assert_eq!(fmt_flatten!(FmtArg::ALT_DEBUG, foo), *format!("{:#?}", foo)); +} + +#[derive(Debug, PanicFmt)] +#[pfmt(crate = ::cpanic)] +struct Foo<'a> { + x: &'a [u8], + y: u8, +} + +#[test] +fn derive_enum_formatting() { + for val in [Qux::Up, Qux::Down { x: 21, y: 34 }, Qux::Left(55)] { + assert_eq!(fmt_flatten!(FmtArg::DEBUG, val), *format!("{:?}", val)); + assert_eq!(fmt_flatten!(FmtArg::ALT_DEBUG, val), *format!("{:#?}", val)); + } +} + +#[derive(Debug, PanicFmt)] +#[pfmt(crate = cpanic)] +enum Qux { + Up, + Down { x: u32, y: u32 }, + Left(u64), +} diff --git a/tests/main_test_modules.rs b/tests/main_test_modules.rs index c8b408b..289a627 100644 --- a/tests/main_test_modules.rs +++ b/tests/main_test_modules.rs @@ -12,6 +12,8 @@ mod main_tests { #[cfg(feature = "non_basic")] mod arraystring_tests; + mod assert_tests; + #[cfg(feature = "non_basic")] mod impl_panicfmt_tests; @@ -21,6 +23,9 @@ mod main_tests { #[cfg(feature = "non_basic")] mod other_fmt_tests; + #[cfg(feature = "derive")] + mod derive_tests; + mod integer_tests; mod misc_macros_tests; diff --git a/tests/main_tests/array_tests.rs b/tests/main_tests/array_tests.rs index 4fc62a5..13bc361 100644 --- a/tests/main_tests/array_tests.rs +++ b/tests/main_tests/array_tests.rs @@ -70,6 +70,31 @@ fn string_test() { ); } +#[test] +fn bin_integer_test() { + let array = [-4, -3, -2, -1, 0i8, 1, 2, 3, 4]; + assert_eq!( + overf_fmt!(1024; bin: array).unwrap(), + "[11111100, 11111101, 11111110, 11111111, 0, 1, 10, 11, 100]" + ); + + assert_eq!( + overf_fmt!(1024; alt_bin: array).unwrap(), + concat!( + "[\n", + " 0b11111100,\n", + " 0b11111101,\n", + " 0b11111110,\n", + " 0b11111111,\n", + " 0b0,\n", + " 0b1,\n", + " 0b10,\n", + " 0b11,\n", + " 0b100,\n", + "]", + ) + ); +} #[test] fn integer_test() { macro_rules! test_case { @@ -81,10 +106,25 @@ fn integer_test() { *format!("{:?}", array), ); + assert_eq!( + overf_fmt!(1024; debug: array).unwrap(), + *format!("{:?}", array), + ); + assert_eq!( overf_fmt!(1024; alt_debug: array).unwrap(), *format!("{:#?}", array), ); + + assert_eq!( + overf_fmt!(1024; hex: array).unwrap(), + *format!("{:X?}", array), + ); + + assert_eq!( + overf_fmt!(1024; alt_hex: array).unwrap(), + *format!("{:#X?}", array), + ); }) } diff --git a/tests/main_tests/arraystring_tests.rs b/tests/main_tests/arraystring_tests.rs index 4f4942e..27e74fa 100644 --- a/tests/main_tests/arraystring_tests.rs +++ b/tests/main_tests/arraystring_tests.rs @@ -1,4 +1,7 @@ -use const_panic::{fmt::ShortString, ArrayString, PanicVal}; +use const_panic::{ + fmt::{ShortString, SHORT_STRING_CAP}, + ArrayString, FmtArg, PanicVal, +}; #[test] fn concat_test() { @@ -57,20 +60,76 @@ fn concat_and_from_panicvals_test() { } } +use rand::{rngs::SmallRng, Rng, SeedableRng}; + +fn strings_iter<'a>(cap: usize, rng: &'a mut SmallRng) -> impl Iterator + 'a { + const CHARS: &[char] = &['ñ', 'ö', 'f', 'o', '个', '人']; + + std::iter::repeat_with(move || { + let mut len = 0; + let used_cap = rng.gen_range(0..=cap); + let out = std::iter::repeat_with(|| CHARS[rng.gen_range(0..CHARS.len())]) + .take_while(move |c| { + len += c.len_utf8(); + len <= used_cap + }) + .collect::(); + assert!(used_cap <= cap); + assert!(out.len() <= used_cap); + out + }) +} + #[test] fn fmt_arraystring_test() { - let string = "hello\nworld\r\0"; - let string_debug = r#""hello\nworld\r\x00""#; - - assert_eq!(trunc_fmt!(200; ShortString::new(string)), string_debug); - assert_eq!( - trunc_fmt!(200; debug: ShortString::new(string)), - string_debug - ); - assert_eq!(trunc_fmt!(200; display: ShortString::new(string)), string); - - let short = PanicVal::write_short_str(ShortString::new(string)); - assert_eq!(trunc_fmt!(200; short), string); - assert_eq!(trunc_fmt!(200; debug: short), string); - assert_eq!(trunc_fmt!(200; display: short), string); + let mut rng = SmallRng::seed_from_u64(6249204433781597762); + + fn as_test_case(rng: &mut SmallRng) { + let strings = strings_iter(LEN, rng); + + for string in strings.take(100) { + let string = &*string; + let string_debug = &*format!("{:?}", string); + + assert_eq!( + trunc_fmt!(200; ArrayString::::new(string)), + string_debug + ); + assert_eq!( + trunc_fmt!(200; debug: ArrayString::::new(string)), + string_debug + ); + assert_eq!( + trunc_fmt!(200; display: ArrayString::::new(string)), + string + ); + } + } + + macro_rules! test_lengths { + ($($len:tt)*) => ( + $(as_test_case::<$len>(&mut rng);)* + ) + } + + test_lengths! { + 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 + 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 + } + + for string in strings_iter(SHORT_STRING_CAP, &mut rng).take(100) { + let string = &*string; + let string_dbg = &*format!("{:?}", string); + + let short = PanicVal::write_short_str(ShortString::new(string)); + assert_eq!(trunc_fmt!(200; short), string); + assert_eq!(trunc_fmt!(200; debug: short), string); + assert_eq!(trunc_fmt!(200; display: short), string); + + let short_dbg = PanicVal::from_short_str(ShortString::new(string), FmtArg::DEBUG); + let short_disp = PanicVal::from_short_str(ShortString::new(string), FmtArg::DISPLAY); + assert_eq!(trunc_fmt!(200; short_dbg), string_dbg); + assert_eq!(trunc_fmt!(200; debug: short_dbg), string_dbg); + assert_eq!(trunc_fmt!(200; display: short_disp), string); + } } diff --git a/tests/main_tests/assert_tests.rs b/tests/main_tests/assert_tests.rs new file mode 100644 index 0000000..e479acc --- /dev/null +++ b/tests/main_tests/assert_tests.rs @@ -0,0 +1,11 @@ +use const_panic::concat_assert; + +#[test] +fn test_concat_assert() { + let zero = 0; + concat_assert!(zero == 0); + std::panic::catch_unwind(|| concat_assert!(zero == 1)).unwrap_err(); + + concat_assert!(zero == 0, "hello", 100u8); + std::panic::catch_unwind(|| concat_assert!(zero == 1, "hello", 100u8)).unwrap_err(); +} diff --git a/tests/main_tests/derive_tests.rs b/tests/main_tests/derive_tests.rs new file mode 100644 index 0000000..413aef6 --- /dev/null +++ b/tests/main_tests/derive_tests.rs @@ -0,0 +1,258 @@ +use const_panic::{FmtArg, PanicFmt}; + +use core::marker::PhantomData; + +macro_rules! fmt_flatten { + ($($args:tt)*) => ( + const_panic::ArrayString::<256>::from_panicvals( + &const_panic::flatten_panicvals!($($args)*) + ).unwrap() + ) +} + +#[test] +fn struct_formatting() { + let foo = Foo { + x: &[3, 5, 8, 13], + y: 21, + z: Bar(false, true), + w: Baz { + h: &["hi", "hel\nlo"], + }, + }; + + assert_eq!( + fmt_flatten!(FmtArg::DEBUG; Foo => foo), + *format!("{:?}", foo) + ); + + assert_eq!( + fmt_flatten!(FmtArg::ALT_DEBUG; Foo => foo), + *format!("{:#?}", foo) + ); + + assert_eq!( + fmt_flatten!(FmtArg::HEX; Foo => foo), + *format!("{:X?}", foo) + ); + + assert_eq!( + fmt_flatten!(FmtArg::ALT_HEX; Foo => foo), + *format!("{:#X?}", foo) + ); + + assert_eq!( + fmt_flatten!(FmtArg::DISPLAY; Foo => foo), + "Foo { x: [3, 5, 8, 13], y: 21, z: Bar(false, true), w: Baz { h: [hi, hel\nlo] } }" + ); + + assert_eq!( + fmt_flatten!(FmtArg::ALT_DISPLAY; Foo => foo), + concat!( + "Foo {\n", + " x: [\n", + " 3,\n", + " 5,\n", + " 8,\n", + " 13,\n", + " ],\n", + " y: 21,\n", + " z: Bar(\n", + " false,\n", + " true,\n", + " ),\n", + " w: Baz {\n", + " h: [\n", + " hi,\n", + " hel\n", + "lo,\n", + " ],\n", + " },\n", + "}", + ) + ); +} + +#[derive(Debug, PanicFmt)] +struct Foo<'a> { + x: &'a [u8], + y: u8, + z: Bar, + w: Baz, +} + +#[derive(Debug, PanicFmt)] +struct Bar(bool, bool); + +#[derive(Debug, PanicFmt)] +struct Baz { + h: &'static [&'static str], +} + +#[test] +fn const_gen_struct_formatting() { + assert_eq!( + fmt_flatten!(FmtArg::DEBUG; ConstGenS<0> => ConstGenS([])), + "ConstGenS([])" + ); + assert_eq!( + fmt_flatten!(FmtArg::DEBUG; ConstGenS<1> => ConstGenS([3])), + "ConstGenS([3])" + ); + assert_eq!( + fmt_flatten!(FmtArg::DEBUG; ConstGenS<2> => ConstGenS([3, 5])), + "ConstGenS([3, 5])" + ); + assert_eq!( + fmt_flatten!(FmtArg::DEBUG; ConstGenS<3> => ConstGenS([3, 5, 8])), + "ConstGenS([3, 5, 8])" + ); +} + +#[derive(Debug, PanicFmt)] +struct ConstGenS([u32; N]); + +#[test] +fn enum_formatting() { + const_panic::inline_macro! { + (u8 = Qux::Up), + (u16 = Qux::Down { x: 21, y: 34 }), + (u32 = Qux::Left(55)); + ($T:ty = $val:expr) => + + let val: Qux<$T> = $val; + + assert_eq!( + fmt_flatten!(FmtArg::DEBUG; Qux<$T> => val), + *format!("{:?}", val) + ); + + assert_eq!( + fmt_flatten!(FmtArg::ALT_DEBUG; Qux<$T> => val), + *format!("{:#?}", val) + ); + } +} + +#[derive(Debug, PanicFmt)] +#[pfmt(impl Qux)] +#[pfmt(impl Qux)] +#[pfmt(impl Qux)] +enum Qux { + Up, + Down { x: T, y: T }, + Left(u64), +} + +#[test] +fn const_gen_enum_formatting() { + assert_eq!( + fmt_flatten!(FmtArg::DEBUG; ConstGenE<0> => ConstGenE::X(&[])), + "X([])" + ); + assert_eq!( + fmt_flatten!(FmtArg::DEBUG; ConstGenE<1> => ConstGenE::X(&[3])), + "X([3])" + ); + assert_eq!( + fmt_flatten!(FmtArg::DEBUG; ConstGenE<2> => ConstGenE::X(&[3, 5])), + "X([3, 5])" + ); + assert_eq!( + fmt_flatten!(FmtArg::DEBUG; ConstGenE<3> => ConstGenE::X(&[3, 5, 8])), + "X([3, 5, 8])" + ); +} + +#[derive(Debug, PanicFmt)] +enum ConstGenE<'a, const N: usize> { + X(&'a [u32; N]), +} + +#[test] +fn ignored_generic_params_formatting() { + #[derive(Debug)] + struct NoFmt; + + const_panic::inline_macro! { + (implicit_gpi), + (explicit_gpi); + ($module:ident) => + { + use $module::IgnoredGenericParams as IGP; + + let foo = IGP(&3, PhantomData, PhantomData); + + assert_eq!( + fmt_flatten!(FmtArg::DEBUG; IGP => foo), + *format!("{:?}", foo) + ); + + assert_eq!( + fmt_flatten!(FmtArg::ALT_DEBUG; IGP => foo), + *format!("{:#?}", foo) + ); + } + } +} + +mod implicit_gpi { + use super::*; + + #[derive(Debug, PanicFmt)] + #[pfmt(ignore(A, B))] + pub struct IgnoredGenericParams<'a, A, B, const X: u32, const Y: char>( + pub &'a u32, + pub PhantomData, + pub PhantomData, + ); +} + +mod explicit_gpi { + use super::*; + + #[derive(Debug, PanicFmt)] + #[pfmt(ignore(A = u32, B = u64, X = 100, Y = '_'))] + pub struct IgnoredGenericParams<'a, A, B, const X: u32, const Y: char>( + pub &'a u32, + pub PhantomData, + pub PhantomData, + ); +} + +#[test] +fn ignored_generic_params_and_impl_formatting() { + #[derive(Debug)] + struct NoFmt; + + const_panic::inline_macro! { + (IgnoredAndImpl), + (IgnoredAndImpl), + (IgnoredAndImpl); + ($ty:path) => + { + let foo = $ty(&3, PhantomData, PhantomData); + + assert_eq!( + fmt_flatten!(FmtArg::DEBUG; $ty => foo), + *format!("{:?}", foo) + ); + + assert_eq!( + fmt_flatten!(FmtArg::ALT_DEBUG; $ty => foo), + *format!("{:#?}", foo) + ); + } + } +} + +#[derive(Debug, PanicFmt)] +#[pfmt(ignore(A))] +#[pfmt(impl<'b, G, const H: u32, const I: char> IgnoredAndImpl<'b, G, u8, H, I>)] +#[pfmt(impl<'b, G, const H: u32, const I: char> IgnoredAndImpl<'b, G, u16, H, I>)] +#[pfmt(impl<'b, G, const H: u32, const I: char> IgnoredAndImpl<'b, G, u32, H, I>)] +pub struct IgnoredAndImpl<'a, A, B, const X: u32, const Y: char>( + pub &'a u32, + pub PhantomData, + pub PhantomData, +); diff --git a/tests/main_tests/impl_panicfmt_tests.rs b/tests/main_tests/impl_panicfmt_tests.rs index e9395b7..40ad927 100644 --- a/tests/main_tests/impl_panicfmt_tests.rs +++ b/tests/main_tests/impl_panicfmt_tests.rs @@ -1,5 +1,7 @@ use const_panic::FmtArg; +use std::marker::PhantomData; + #[test] fn struct_formatting() { let array = [3, 5, 8, 13]; @@ -21,10 +23,16 @@ fn struct_formatting() { *format!("{:#?}", foo) ); - // making sure that the `lifetime = 'static;` argument to `impl_panicfmt` has an effect. - let _: [const_panic::PanicVal<'static>; 1] = foo.w.to_panicvals(FmtArg::DEBUG); + assert_eq!(trunc_fmt!(999;FmtArg::HEX; foo), *format!("{:X?}", foo)); + + assert_eq!( + trunc_fmt!(999;FmtArg::ALT_HEX; foo), + *format!("{:#X?}", foo) + ); + + let _: [const_panic::PanicVal<'_>; 1] = foo.w.to_panicvals(FmtArg::DEBUG); - let _: [const_panic::PanicVal<'static>; as const_panic::PanicFmt>::PV_COUNT] = + let _: [const_panic::PanicVal<'_>; as const_panic::PanicFmt>::PV_COUNT] = foo.b.to_panicvals(FmtArg::DEBUG); } @@ -40,9 +48,7 @@ struct Foo<'a> { } const_panic::impl_panicfmt! { - impl Foo<'_>; - - struct Foo { + struct Foo<'a> { x: &[u8], y: u8, z: Bar, @@ -51,13 +57,14 @@ const_panic::impl_panicfmt! { b: Qux, c: Qux, } + + (impl Foo<'_>) } #[derive(Debug)] struct Bar(bool, bool); const_panic::impl_panicfmt! { - impl Bar; struct Bar(bool, bool); } @@ -65,11 +72,7 @@ const_panic::impl_panicfmt! { struct Baz; const_panic::impl_panicfmt! { - impl Baz; - - lifetime = 'static; - - struct Baz + struct Baz; } #[derive(Debug)] @@ -79,88 +82,264 @@ enum Qux { Left(u64), } -const_panic::inline_macro! { - (u8, ()), - (u16, (lifetime = 'static;)), - (u32, ()); +const_panic::impl_panicfmt! { + enum Qux { + Up, + Down{ + x: T, + y: T, + }, + Left(u64), + } - ($T:ty , ($($other:tt)*)) => - const_panic::impl_panicfmt!{ - impl Qux<$T>; + (impl Qux ) + (impl Qux) + (impl Qux) +} - $($other)* +#[test] +fn to_panicvals_lifetime_test() { + let struct_tup = StaticTupleStruct(&5u32); + let struct_brace = StaticBracedStruct { x: &8 }; + let enum_ = StaticEnum::Bar(&13u8); - enum Qux { - Up, - Down{ - x: $T, - y: $T, - }, - Left(u64), - } - } + assert_eq!( + trunc_fmt!(999;FmtArg::DEBUG; struct_tup), + *format!("{:?}", struct_tup) + ); + assert_eq!( + trunc_fmt!(999;FmtArg::DEBUG; struct_brace), + *format!("{:?}", struct_brace) + ); + assert_eq!( + trunc_fmt!(999;FmtArg::DEBUG; enum_), + *format!("{:?}", enum_) + ); +} + +#[derive(Debug)] +struct StaticTupleStruct<'a>(&'a u32) +where + 'a: 'static; + +const_panic::impl_panicfmt! { + struct StaticTupleStruct<'a> (&'a u32) + where ['a: 'static]; +} + +#[derive(Debug)] +struct StaticBracedStruct<'a> +where + 'a: 'static, +{ + x: &'a u32, +} + +const_panic::impl_panicfmt! { + struct StaticBracedStruct<'a> + where ['a: 'static] + { + x: &'a u32, + } +} + +#[derive(Debug)] +enum StaticEnum<'a> +where + 'a: 'static, +{ + #[allow(dead_code)] + Foo, + Bar(&'a u8), +} + +const_panic::impl_panicfmt! { + enum StaticEnum<'a> + where ['a: 'static] + {Foo, Bar(&u8)} } #[test] -fn to_panicvals_lifetime_test() { - use const_panic::PanicVal; +fn generic_parsing() { + const_panic::inline_macro! { + (GenericParsing0<'_>), + (GenericParsing1<'_>), + (GenericParsing2<'_, ()>), + (GenericParsing3<'_, ()>), + (GenericParsing4<'_, (), 0>), + (GenericParsing5<'_, (), 0>), + (GenericParsing6<'_, (), 0>), + (GenericParsingB0<'_>), + (GenericParsingB1<'_>), + (GenericParsingB2<'_, str>), + (GenericParsingB3<'_, [u8]>), + (GenericParsingB4<'_, u32, 0>), + (GenericParsingB5<'_, u32, 0>), + (GenericParsingB6<'_, u32, 0>); + ($type_name:path) => + { + let foo = $type_name(PhantomData); + assert_eq!(trunc_fmt!(999;FmtArg::DEBUG; foo), *format!("{:?}", foo)); + } + } +} + +#[derive(Debug)] +struct GenericParsing0<'a>(PhantomData<(&'a (), ())>); + +#[derive(Debug)] +struct GenericParsing1<'a>(PhantomData<(&'a (), ())>); + +#[derive(Debug)] +struct GenericParsing2<'a, T>(PhantomData<(&'a (), T)>); + +#[derive(Debug)] +struct GenericParsing3<'a, T>(PhantomData<(&'a (), T)>); + +#[derive(Debug)] +struct GenericParsing4<'a, T, const U: u32>(PhantomData<(&'a (), T)>); + +#[derive(Debug)] +struct GenericParsing5<'a, T, const U: u32>(PhantomData<(&'a (), T)>); - let u32_ = 5; - let u8_ = 3; +#[derive(Debug)] +struct GenericParsing6<'a, T, const U: u32>(PhantomData<(&'a (), T)>); - let _: &[PanicVal<'static>] = &StaticStruct(8).to_panicvals(FmtArg::DEBUG); +const_panic::impl_panicfmt! { + struct GenericParsing0<'a,>(PhantomData<(&'a (), ())>); +} - let _: &[PanicVal<'static>] = &StaticEnum::Foo.to_panicvals(FmtArg::DEBUG); +const_panic::impl_panicfmt! { + struct GenericParsing1<'a,>(PhantomData<(&'a (), ())>); +} - let _: &[PanicVal<'_>] = &NonStaticStruct(&u32_).to_panicvals(FmtArg::DEBUG); +const_panic::impl_panicfmt! { + struct GenericParsing2<'a, ignore T>(PhantomData<(&'a (), T)>); +} - let _: &[PanicVal<'_>] = &NonStaticEnum::Bar(&u8_).to_panicvals(FmtArg::DEBUG); +const_panic::impl_panicfmt! { + struct GenericParsing3<'a, ignore T,>(PhantomData<(&'a (), T)>); } -struct StaticStruct(u32); +const_panic::impl_panicfmt! { + struct GenericParsing4<'a, ignore T, const U: u32>(PhantomData<(&'a (), T)>); +} const_panic::impl_panicfmt! { - impl StaticStruct; + struct GenericParsing5<'a, ignore(PhantomData) T, ignore const U: u32,>( + PhantomData<(&'a (), T)> + ); +} + +const_panic::impl_panicfmt! { + struct GenericParsing6<'a, ignore T, ignore(2) const U: u32,>( + PhantomData<(&'a (), T)> + ); +} + +#[derive(Debug)] +struct GenericParsingB0<'a>(PhantomData<(&'a (), ())>); + +#[derive(Debug)] +struct GenericParsingB1<'a>(PhantomData<(&'a (), ())>); + +#[derive(Debug)] +struct GenericParsingB2<'a, T: ?Sized>(PhantomData<(&'a (), T)>); - lifetime = 'static; +#[derive(Debug)] +struct GenericParsingB3<'a, T: ?Sized>(PhantomData<(&'a (), T)>); - struct StaticStruct(u32); +#[derive(Debug)] +struct GenericParsingB4<'a, T, const U: u32>(PhantomData<(&'a (), T)>); + +#[derive(Debug)] +struct GenericParsingB5<'a, T, const U: u32>(PhantomData<(&'a (), T)>); + +#[derive(Debug)] +struct GenericParsingB6<'a, T, const U: u32>(PhantomData<(&'a (), T)>); + +const_panic::impl_panicfmt! { + struct GenericParsingB0<'a>(PhantomData<(&'a (), ())>); + + (impl['a] GenericParsingB0<'a>) } -enum StaticEnum { - Foo, - #[allow(dead_code)] - Bar, +const_panic::impl_panicfmt! { + struct GenericParsingB1<'a>(PhantomData<(&'a (), ())>); + + (impl['a] GenericParsingB1<'a,> where[]) } const_panic::impl_panicfmt! { - impl StaticEnum; + struct GenericParsingB2<'a, ignore T>(PhantomData<(&'a (), T)>) + where[T: ?Sized]; + + (impl['a, T] GenericParsingB2<'a, T> where[T: ?Sized]) +} - lifetime = 'static; +const_panic::impl_panicfmt! { + struct GenericParsingB3<'a, ignore T,>(PhantomData<(&'a (), T)>) + where[T: ?Sized]; - enum StaticEnum {Foo, Bar} + (impl['a, T: ?Sized] GenericParsingB3<'a, T,>) } -struct NonStaticStruct<'a>(&'a u32); +const_panic::impl_panicfmt! { + struct GenericParsingB4<'a, T, const U: u32>(PhantomData<(&'a (), T)>); + + (impl['a, const U: u32] GenericParsingB4<'a, u32, U,>) +} const_panic::impl_panicfmt! { - impl NonStaticStruct<'_>; + struct GenericParsingB5<'a, ignore(PhantomData) T, ignore const U: u32,>( + PhantomData<(&'a (), T)> + ); - lifetime = '_; + (impl['a, const U: u32] GenericParsingB5<'a, u32, U>) +} - struct NonStaticStruct(&u32); +const_panic::impl_panicfmt! { + struct GenericParsingB6<'a, ignore T, ignore(2) const U: u32,>( + PhantomData<(&'a (), T)> + ); + + (impl['a] GenericParsingB6<'a, u32, 0>) } -enum NonStaticEnum<'a> { - #[allow(dead_code)] - Foo, - Bar(&'a u8), +#[test] +fn where_clause_emision() { + assert_eq!( + trunc_fmt!(999;FmtArg::DEBUG; Unit0), + *format!("{:?}", Unit0) + ); + + assert_eq!(HasConsts::A, 3); + assert_eq!(HasConsts::B, 5); } +struct HasConsts; + +#[derive(Debug)] +struct Unit0; + const_panic::impl_panicfmt! { - impl NonStaticEnum<'_>; + struct Unit0 + where[[(); { + impl HasConsts { + const A: u8 = 3; + } + + 0 + }]:]; - lifetime = '_; + ( + impl Unit0 + where[[(); { + impl HasConsts { + const B: u8 = 5; + } - enum NonStaticEnum {Foo, Bar(&u8)} + 0 + }]:] + ) } diff --git a/tests/main_tests/integer_tests.rs b/tests/main_tests/integer_tests.rs index 31f657a..0cfdf65 100644 --- a/tests/main_tests/integer_tests.rs +++ b/tests/main_tests/integer_tests.rs @@ -4,103 +4,90 @@ use core::num::{ NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8, NonZeroUsize, }; +use const_panic::FmtArg; + +fn tn_of_val(_: T) -> &'static str { + std::any::type_name::() +} + macro_rules! test_case { - ($($nums:expr),* $(,)*)=> ({ - for int in [$($nums),*] { - let string = format!("{:?}", int); + ($num:expr)=> ( + let int = $num; + + for (fmt, string) in [ + (FmtArg::DEBUG, format!("{:?}", int)), + (FmtArg::ALT_DEBUG, format!("{:?}", int)), + (FmtArg::DISPLAY, format!("{:?}", int)), + (FmtArg::ALT_DISPLAY, format!("{:?}", int)), + (FmtArg::HEX, format!("{:X}", int)), + (FmtArg::ALT_HEX, format!("{:#X}", int)), + (FmtArg::BIN, format!("{:b}", int)), + (FmtArg::ALT_BIN, format!("{:#b}", int)), + ] { + let msg = || format!( + "string.len(): {} num: {:?} fmt_override: {:?} type: {}", + string.len(), int, fmt, tn_of_val(int), + ); assert_eq!( - overf_fmt!(string.len(); int).unwrap(), + overf_fmt!(string.len(); fmt; int).unwrap(), *string, + "{}", + msg(), ); - assert_eq!(trunc_fmt!(string.len(); int), *string); + assert_eq!(trunc_fmt!(string.len(); fmt; int), *string, "{}", msg()); - overf_fmt!(string.len() - 1; int).unwrap_err(); - assert_eq!(trunc_fmt!(string.len() - 1; int), ""); + overf_fmt!(string.len() - 1; fmt; int).unwrap_err(); + assert_eq!( + trunc_fmt!(string.len() - 1; fmt; int), + "", + "{}", msg() + ); } - }) + ) +} + +macro_rules! int_test { + ($ty:ty) => {{ + let zero: $ty = 0; + let iter = (zero..10) + .chain( + std::iter::successors(Some::<$ty>(1), |n| n.checked_mul(10)) + .chain(std::iter::successors(Some::<$ty>(1), |n| n.checked_mul(2))) + .flat_map(|x| [x - 1, x, x + 1]), + ) + .chain((0..3).flat_map(|x| [<$ty>::MIN + x, <$ty>::MAX - x])) + .flat_map(|x| [zero.saturating_sub(x), x]); + + println!("{}:", stringify!($ty)); + for int in iter { + print!("{:?} ", int); + test_case! {int} + } + println!(); + }}; } #[test] fn integer_test() { - test_case!(0u8, 1, 2, u8::MAX / 2, u8::MAX); - test_case!(0u16, 1, 2, u16::MAX / 2, u16::MAX); - test_case!(0u32, 1, 2, u32::MAX / 2, u32::MAX); - test_case!(0u64, 1, 2, u64::MAX / 2, u64::MAX); - test_case!(0u128, 1, 2, u128::MAX / 2, u128::MAX); - test_case!(0usize, 1, 2, usize::MAX / 2, usize::MAX); - - test_case!( - i8::MIN, - i8::MIN / 2, - -2, - -1, - 0i8, - 1, - 2, - i8::MAX / 2, - i8::MAX - ); - test_case!( - i16::MIN, - i16::MIN / 2, - -2, - -1, - 0i16, - 1, - 2, - i16::MAX / 2, - i16::MAX - ); - test_case!( - i32::MIN, - i32::MIN / 2, - -2, - -1, - 0i32, - 1, - 2, - i32::MAX / 2, - i32::MAX - ); - test_case!( - i64::MIN, - i64::MIN / 2, - -2, - -1, - 0i64, - 1, - 2, - i64::MAX / 2, - i64::MAX - ); - test_case!( - i128::MIN, - i128::MIN / 2, - -2, - -1, - 0i128, - 1, - 2, - i128::MAX / 2, - i128::MAX - ); - test_case!( - isize::MIN, - isize::MIN / 2, - -2, - -1, - 0isize, - 1, - 2, - isize::MAX / 2, - isize::MAX - ); + int_test! {u8} + int_test! {u16} + int_test! {u32} + int_test! {u64} + int_test! {u128} + int_test! {usize} + int_test! {i8} + int_test! {i16} + int_test! {i32} + int_test! {i64} + int_test! {i128} + int_test! {isize} } // Tests aren't so thorough, since NonZero integers just delegate to the built-in ones. #[cfg(feature = "non_basic")] #[test] +#[cfg(feature = "non_basic")] fn nonzero_integer_test() { test_case! {NonZeroU8::new(5).unwrap()} test_case! {NonZeroI8::new(-5).unwrap()} diff --git a/tests/main_tests/misc_macros_tests.rs b/tests/main_tests/misc_macros_tests.rs index 5651976..f15799c 100644 --- a/tests/main_tests/misc_macros_tests.rs +++ b/tests/main_tests/misc_macros_tests.rs @@ -1,6 +1,6 @@ use const_panic::{ __set_fmt_from_kw as set_fmt_fkw, - fmt::{self, FmtArg, FmtKind}, + fmt::{self, FmtArg, FmtKind, NumberFmt}, }; #[test] @@ -101,4 +101,46 @@ fn set_fmt_from_kw_test() { fmt.fmt_kind = FmtKind::Debug; fmt.is_alternate = true; }} + + case! {hex, initb, |fmt| { + fmt.fmt_kind = FmtKind::Debug; + fmt.is_alternate = false; + fmt.number_fmt = NumberFmt::Hexadecimal; + }} + case! {{X}, initb, |fmt| { + fmt.fmt_kind = FmtKind::Debug; + fmt.is_alternate = false; + fmt.number_fmt = NumberFmt::Hexadecimal; + }} + case! {alt_hex, inita, |fmt| { + fmt.fmt_kind = FmtKind::Debug; + fmt.is_alternate = true; + fmt.number_fmt = NumberFmt::Hexadecimal; + }} + case! {{#X}, inita, |fmt| { + fmt.fmt_kind = FmtKind::Debug; + fmt.is_alternate = true; + fmt.number_fmt = NumberFmt::Hexadecimal; + }} + + case! {bin, initb, |fmt| { + fmt.fmt_kind = FmtKind::Debug; + fmt.is_alternate = false; + fmt.number_fmt = NumberFmt::Binary; + }} + case! {{b}, initb, |fmt| { + fmt.fmt_kind = FmtKind::Debug; + fmt.is_alternate = false; + fmt.number_fmt = NumberFmt::Binary; + }} + case! {alt_bin, inita, |fmt| { + fmt.fmt_kind = FmtKind::Debug; + fmt.is_alternate = true; + fmt.number_fmt = NumberFmt::Binary; + }} + case! {{#b}, inita, |fmt| { + fmt.fmt_kind = FmtKind::Debug; + fmt.is_alternate = true; + fmt.number_fmt = NumberFmt::Binary; + }} } diff --git a/tests/main_tests/option_fmt_tests.rs b/tests/main_tests/option_fmt_tests.rs index b1f0185..4bf602d 100644 --- a/tests/main_tests/option_fmt_tests.rs +++ b/tests/main_tests/option_fmt_tests.rs @@ -32,6 +32,12 @@ fn test_option_fmt() { test_case! {Some(3i128), FmtArg::DEBUG, "Some(3)"} test_case! {Some(3isize), FmtArg::DEBUG, "Some(3)"} + test_case! {Some(-1i8), FmtArg::DEBUG, "Some(-1)"} + test_case! {Some(-2i8), FmtArg::HEX, "Some(FE)"} + test_case! {Some(-3i8), FmtArg::ALT_HEX, "Some(\n 0xFD,\n)"} + test_case! {Some(-4i8), FmtArg::BIN, "Some(11111100)"} + test_case! {Some(-5i8), FmtArg::ALT_BIN, "Some(\n 0b11111011,\n)"} + test_case! {NonNull::new(&mut 100), FmtArg::DEBUG, "Some()"} test_case! {NonZeroU8::new(5), FmtArg::DEBUG, "Some(5)"}