diff --git a/bilge-impl/src/bitsize.rs b/bilge-impl/src/bitsize.rs index 6c8d306..5773f8f 100644 --- a/bilge-impl/src/bitsize.rs +++ b/bilge-impl/src/bitsize.rs @@ -120,40 +120,14 @@ fn analyze_enum(bitsize: BitSize, variants: Iter) { } } -fn generate_struct(item: &ItemStruct, declared_bitsize: u8) -> TokenStream { - let ItemStruct { vis, ident, fields, .. } = item; - let declared_bitsize = declared_bitsize as usize; - - let computed_bitsize = fields.iter().fold(quote!(0), |acc, next| { - let field_size = shared::generate_type_bitsize(&next.ty); - quote!(#acc + #field_size) - }); - - // we could remove this if the whole struct gets passed - let is_tuple_struct = fields.iter().any(|field| field.ident.is_none()); - let fields_def = if is_tuple_struct { - let fields = fields.iter(); - quote! { - ( #(#fields,)* ); - } - } else { - let fields = fields.iter(); - quote! { - { #(#fields,)* } - } +fn generate_struct(item: &ItemStruct, _declared_bitsize: u8) -> TokenStream { + let item = ItemStruct { + attrs: Vec::new(), + ..item.clone() }; quote! { - #vis struct #ident #fields_def - - // constness: when we get const blocks evaluated at compile time, add a const computed_bitsize - const _: () = assert!( - (#computed_bitsize) == (#declared_bitsize), - concat!("struct size and declared bit size differ: ", - // stringify!(#computed_bitsize), - " != ", - stringify!(#declared_bitsize)) - ); + #item } } diff --git a/bilge-impl/src/bitsize_internal.rs b/bilge-impl/src/bitsize_internal.rs index e6061a7..81ffdbc 100644 --- a/bilge-impl/src/bitsize_internal.rs +++ b/bilge-impl/src/bitsize_internal.rs @@ -1,6 +1,6 @@ use proc_macro2::{Ident, TokenStream}; use quote::quote; -use syn::{Attribute, Field, Item, ItemEnum, ItemStruct, Type}; +use syn::{Attribute, Field, Generics, Item, ItemEnum, ItemStruct, Type}; use crate::shared::{self, unreachable}; @@ -12,36 +12,36 @@ struct ItemIr<'a> { name: &'a Ident, /// generated item (and setters, getters, constructor, impl Bitsized) expanded: TokenStream, + generics: &'a Generics, + check_expr: TokenStream, } pub(super) fn bitsize_internal(args: TokenStream, item: TokenStream) -> TokenStream { - let (item, arb_int) = parse(item, args); + let (item, arb_int, declared_bitsize) = parse(item, args); let ir = match item { - Item::Struct(ref item) => { - let expanded = generate_struct(item, &arb_int); - let attrs = &item.attrs; - let name = &item.ident; - ItemIr { attrs, name, expanded } - } - Item::Enum(ref item) => { - let expanded = generate_enum(item); - let attrs = &item.attrs; - let name = &item.ident; - ItemIr { attrs, name, expanded } - } + Item::Struct(ref item) => generate_struct(item, &arb_int, declared_bitsize), + Item::Enum(ref item) => generate_enum(item), _ => unreachable(()), }; generate_common(ir, &arb_int) } -fn parse(item: TokenStream, args: TokenStream) -> (Item, TokenStream) { +fn parse(item: TokenStream, args: TokenStream) -> (Item, TokenStream, u8) { let item = syn::parse2(item).unwrap_or_else(unreachable); - let (_declared_bitsize, arb_int) = shared::bitsize_and_arbitrary_int_from(args); - (item, arb_int) + let (declared_bitsize, arb_int) = shared::bitsize_and_arbitrary_int_from(args); + (item, arb_int, declared_bitsize) } -fn generate_struct(struct_data: &ItemStruct, arb_int: &TokenStream) -> TokenStream { - let ItemStruct { vis, ident, fields, .. } = struct_data; +fn generate_struct<'a>(struct_data: &'a ItemStruct, arb_int: &TokenStream, declared_bitsize: u8) -> ItemIr<'a> { + let ItemStruct { + vis, + ident, + fields, + generics, + attrs, + .. + } = struct_data; + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); let mut fieldless_next_int = 0; let mut previous_field_sizes = vec![]; @@ -66,12 +66,15 @@ fn generate_struct(struct_data: &ItemStruct, arb_int: &TokenStream) -> TokenStre let const_ = if cfg!(feature = "nightly") { quote!(const) } else { quote!() }; - quote! { - #vis struct #ident { + let phantom_type = generate_phantom_type(generics); + + let expanded = quote! { + #vis struct #ident #generics #where_clause { /// WARNING: modifying this value directly can break invariants value: #arb_int, + _phantom: ::core::marker::PhantomData<#phantom_type> } - impl #ident { + impl #impl_generics #ident #ty_generics #where_clause { // #[inline] #[allow(clippy::too_many_arguments, clippy::type_complexity, unused_parens)] pub #const_ fn new(#( #constructor_args )*) -> Self { @@ -81,10 +84,48 @@ fn generate_struct(struct_data: &ItemStruct, arb_int: &TokenStream) -> TokenStre let mut offset = 0; let raw_value = #( #constructor_parts )|*; let value = #arb_int::new(raw_value); - Self { value } + Self { value, _phantom: ::core::marker::PhantomData } } #( #accessors )* } + }; + + let computed_bitsize = fields.iter().fold(quote!(0), |acc, next| { + let field_size = shared::generate_type_bitsize(&next.ty); + quote!(#acc + #field_size) + }); + + let declared_bitsize = declared_bitsize as usize; + + let check_expr = quote!(assert!( + (#computed_bitsize) == (#declared_bitsize), + concat!("struct size and declared bit size differ: ", + // stringify!(#computed_bitsize), + " != ", + stringify!(#declared_bitsize)) + )); + + ItemIr { + attrs, + name: ident, + expanded, + generics, + check_expr, + } +} + +/// Returns a tuple with the following types, in order: +/// - The original struct's type parameters +/// - References to `()` bound by the original struct's lifetime parameters +/// If there are 0 generics, the type will simply be `()`. +/// If there is a single generic type or lifetime, the type will not wrapped in a tuple. +fn generate_phantom_type(generics: &Generics) -> TokenStream { + let phantom_ty = generics.type_params().map(|e| &e.ident).map(|ident| quote!(#ident)); + let phantom_lt = generics.lifetimes().map(|l| &l.lifetime).map(|lifetime| quote!(& #lifetime ())); + // TODO: integrate user-provided PhantomData somehow? (so that the user can set the variance) + let phantom = phantom_ty.chain(phantom_lt); + quote! { + (#(#phantom),*) } } @@ -209,27 +250,59 @@ fn generate_constructor_stuff(ty: &Type, name: &Ident) -> (TokenStream, TokenStr (constructor_arg, constructor_part) } -fn generate_enum(enum_data: &ItemEnum) -> TokenStream { - let ItemEnum { vis, ident, variants, .. } = enum_data; - quote! { +fn generate_enum(enum_data: &ItemEnum) -> ItemIr { + let ItemEnum { + vis, + ident, + variants, + generics, + attrs, + .. + } = enum_data; + let expanded = quote! { #vis enum #ident { #variants } + }; + ItemIr { + attrs, + name: ident, + expanded, + generics, + check_expr: quote! { () }, } } /// We have _one_ `generate_common` function, which holds everything struct and enum have _in common_. /// Everything else has its own `generate_` functions. fn generate_common(ir: ItemIr, arb_int: &TokenStream) -> TokenStream { - let ItemIr { attrs, name, expanded } = ir; + let ItemIr { + attrs, + name, + expanded, + generics, + check_expr, + } = ir; + + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); quote! { #(#attrs)* #expanded - impl ::bilge::Bitsized for #name { - type ArbitraryInt = #arb_int; - const BITS: usize = ::BITS; - const MAX: Self::ArbitraryInt = ::MAX; - } + const _: () = { + trait Assertion { + const SIZE_CHECK: (); + } + + impl #impl_generics Assertion for #name #ty_generics #where_clause { + const SIZE_CHECK: () = #check_expr; + } + + impl #impl_generics ::bilge::Bitsized for #name #ty_generics #where_clause { + type ArbitraryInt = #arb_int; + const BITS: usize = (::BITS, ::SIZE_CHECK).0; + const MAX: Self::ArbitraryInt = (::MAX, ::SIZE_CHECK).0; + } + }; } } diff --git a/bilge-impl/src/debug_bits.rs b/bilge-impl/src/debug_bits.rs index 6921f6b..43f350f 100644 --- a/bilge-impl/src/debug_bits.rs +++ b/bilge-impl/src/debug_bits.rs @@ -17,7 +17,7 @@ pub(super) fn debug_bits(item: TokenStream) -> TokenStream { }; let fmt_impl = match struct_data.fields { - Fields::Named(fields) => { + Fields::Named(ref fields) => { let calls = fields.named.iter().map(|f| { // We can unwrap since this is a named field let call = f.ident.as_ref().unwrap(); @@ -30,7 +30,7 @@ pub(super) fn debug_bits(item: TokenStream) -> TokenStream { #(#calls)*.finish() } } - Fields::Unnamed(fields) => { + Fields::Unnamed(ref fields) => { let calls = fields.unnamed.iter().map(|_| { let call: Ident = syn::parse_str(&format!("val_{}", fieldless_next_int)).unwrap_or_else(unreachable); fieldless_next_int += 1; @@ -45,8 +45,12 @@ pub(super) fn debug_bits(item: TokenStream) -> TokenStream { Fields::Unit => todo!("this is a unit struct, which is not supported right now"), }; + let (impl_generics, ty_generics, where_clause) = derive_input.generics.split_for_impl(); + + let where_clause = shared::generate_trait_where_clause(&derive_input.generics, where_clause, quote!(::core::fmt::Debug)); + quote! { - impl ::core::fmt::Debug for #name { + impl #impl_generics ::core::fmt::Debug for #name #ty_generics #where_clause { fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { #fmt_impl } diff --git a/bilge-impl/src/default_bits.rs b/bilge-impl/src/default_bits.rs index f8ce9df..b0f3073 100644 --- a/bilge-impl/src/default_bits.rs +++ b/bilge-impl/src/default_bits.rs @@ -1,35 +1,39 @@ use proc_macro2::{Ident, TokenStream}; use proc_macro_error::abort_call_site; use quote::quote; -use syn::{Data, DeriveInput, Fields, Type}; +use syn::{Data, DeriveInput, Fields, Generics, Type}; use crate::shared::{self, fallback::Fallback, unreachable, BitSize}; pub(crate) fn default_bits(item: TokenStream) -> TokenStream { let derive_input = parse(item); //TODO: does fallback need handling? - let (derive_data, _, name, ..) = analyze(&derive_input); + let (derive_data, _, name, generics, ..) = analyze(&derive_input); match derive_data { - Data::Struct(data) => generate_struct_default_impl(name, &data.fields), + Data::Struct(data) => generate_struct_default_impl(name, &data.fields, generics), Data::Enum(_) => abort_call_site!("use derive(Default) for enums"), _ => unreachable(()), } } -fn generate_struct_default_impl(struct_name: &Ident, fields: &Fields) -> TokenStream { +fn generate_struct_default_impl(struct_name: &Ident, fields: &Fields, generics: &Generics) -> TokenStream { let default_value = fields .iter() .map(|field| generate_default_inner(&field.ty)) .reduce(|acc, next| quote!(#acc | #next)); + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + + let where_clause = shared::generate_trait_where_clause(generics, where_clause, quote!(::core::default::Default)); + quote! { - impl ::core::default::Default for #struct_name { + impl #impl_generics ::core::default::Default for #struct_name #ty_generics #where_clause { fn default() -> Self { let mut offset = 0; let value = #default_value; - let value = <#struct_name as Bitsized>::ArbitraryInt::new(value); - Self { value } + let value = <#struct_name #ty_generics as Bitsized>::ArbitraryInt::new(value); + Self { value, _phantom: ::core::marker::PhantomData } } } } @@ -87,6 +91,6 @@ fn parse(item: TokenStream) -> DeriveInput { shared::parse_derive(item) } -fn analyze(derive_input: &DeriveInput) -> (&Data, TokenStream, &Ident, BitSize, Option) { +fn analyze(derive_input: &DeriveInput) -> (&Data, TokenStream, &Ident, &Generics, BitSize, Option) { shared::analyze_derive(derive_input, false) } diff --git a/bilge-impl/src/fmt_bits.rs b/bilge-impl/src/fmt_bits.rs index 1a4e6a7..934fbf6 100644 --- a/bilge-impl/src/fmt_bits.rs +++ b/bilge-impl/src/fmt_bits.rs @@ -1,21 +1,21 @@ use proc_macro2::{Ident, TokenStream}; use quote::quote; -use syn::{punctuated::Iter, Data, DeriveInput, Fields, Variant}; +use syn::{punctuated::Iter, Data, DeriveInput, Fields, Generics, Variant}; use crate::shared::{self, discriminant_assigner::DiscriminantAssigner, fallback::Fallback, unreachable, BitSize}; pub(crate) fn binary(item: TokenStream) -> TokenStream { let derive_input = parse(item); - let (derive_data, arb_int, name, bitsize, fallback) = analyze(&derive_input); + let (derive_data, arb_int, name, generics, bitsize, fallback) = analyze(&derive_input); match derive_data { - Data::Struct(data) => generate_struct_binary_impl(name, &data.fields), + Data::Struct(data) => generate_struct_binary_impl(name, &data.fields, generics), Data::Enum(data) => generate_enum_binary_impl(name, data.variants.iter(), arb_int, bitsize, fallback), _ => unreachable(()), } } -fn generate_struct_binary_impl(struct_name: &Ident, fields: &Fields) -> TokenStream { +fn generate_struct_binary_impl(struct_name: &Ident, fields: &Fields, generics: &Generics) -> TokenStream { let write_underscore = quote! { write!(f, "_")?; }; // fields are printed from most significant to least significant, separated by an underscore @@ -37,8 +37,10 @@ fn generate_struct_binary_impl(struct_name: &Ident, fields: &Fields) -> TokenStr }) .reduce(|acc, next| quote!(#acc #write_underscore #next)); + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + quote! { - impl ::core::fmt::Binary for #struct_name { + impl #impl_generics ::core::fmt::Binary for #struct_name #ty_generics #where_clause { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let struct_size = <#struct_name as Bitsized>::BITS; let mut last_bit_pos = struct_size; @@ -107,6 +109,6 @@ fn parse(item: TokenStream) -> DeriveInput { shared::parse_derive(item) } -fn analyze(derive_input: &DeriveInput) -> (&Data, TokenStream, &Ident, BitSize, Option) { +fn analyze(derive_input: &DeriveInput) -> (&Data, TokenStream, &Ident, &Generics, BitSize, Option) { shared::analyze_derive(derive_input, false) } diff --git a/bilge-impl/src/from_bits.rs b/bilge-impl/src/from_bits.rs index 39bbe5d..f5098ef 100644 --- a/bilge-impl/src/from_bits.rs +++ b/bilge-impl/src/from_bits.rs @@ -2,15 +2,15 @@ use itertools::Itertools; use proc_macro2::{Ident, TokenStream}; use proc_macro_error::{abort, abort_call_site}; use quote::quote; -use syn::{punctuated::Iter, Data, DeriveInput, Fields, Type, Variant}; +use syn::{punctuated::Iter, Data, DeriveInput, Fields, Generics, Type, Variant}; use crate::shared::{self, discriminant_assigner::DiscriminantAssigner, enum_fills_bitsize, fallback::Fallback, unreachable, BitSize}; pub(super) fn from_bits(item: TokenStream) -> TokenStream { let derive_input = parse(item); - let (derive_data, arb_int, name, internal_bitsize, fallback) = analyze(&derive_input); + let (derive_data, arb_int, name, generics, internal_bitsize, fallback) = analyze(&derive_input); let expanded = match &derive_data { - Data::Struct(struct_data) => generate_struct(arb_int, name, &struct_data.fields), + Data::Struct(struct_data) => generate_struct(arb_int, name, &struct_data.fields, generics), Data::Enum(enum_data) => { let variants = enum_data.variants.iter(); let match_arms = analyze_enum(variants, name, internal_bitsize, fallback.as_ref(), &arb_int); @@ -25,7 +25,7 @@ fn parse(item: TokenStream) -> DeriveInput { shared::parse_derive(item) } -fn analyze(derive_input: &DeriveInput) -> (&syn::Data, TokenStream, &Ident, BitSize, Option) { +fn analyze(derive_input: &DeriveInput) -> (&syn::Data, TokenStream, &Ident, &Generics, BitSize, Option) { shared::analyze_derive(derive_input, false) } @@ -141,7 +141,7 @@ fn generate_filled_check_for(ty: &Type, vec: &mut Vec) { } } -fn generate_struct(arb_int: TokenStream, struct_type: &Ident, fields: &Fields) -> TokenStream { +fn generate_struct(arb_int: TokenStream, struct_type: &Ident, fields: &Fields, generics: &Generics) -> TokenStream { let const_ = if cfg!(feature = "nightly") { quote!(const) } else { quote!() }; let mut assumes = Vec::new(); @@ -152,15 +152,19 @@ fn generate_struct(arb_int: TokenStream, struct_type: &Ident, fields: &Fields) - // a single check per type is enough, so the checks can be deduped let assumes = assumes.into_iter().unique_by(TokenStream::to_string); + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + quote! { - impl #const_ ::core::convert::From<#arb_int> for #struct_type { + impl #impl_generics #const_ ::core::convert::From<#arb_int> for #struct_type #ty_generics #where_clause { fn from(value: #arb_int) -> Self { + ::BITS; #( #assumes )* - Self { value } + Self { value, _phantom: ::core::marker::PhantomData } } } - impl #const_ ::core::convert::From<#struct_type> for #arb_int { - fn from(value: #struct_type) -> Self { + impl #impl_generics #const_ ::core::convert::From<#struct_type #ty_generics> for #arb_int #where_clause { + fn from(value: #struct_type #ty_generics) -> Self { + <#struct_type #ty_generics as Bitsized>::BITS; value.value } } diff --git a/bilge-impl/src/shared.rs b/bilge-impl/src/shared.rs index 8f490b8..6ed2411 100644 --- a/bilge-impl/src/shared.rs +++ b/bilge-impl/src/shared.rs @@ -2,11 +2,13 @@ pub mod discriminant_assigner; pub mod fallback; pub mod util; +use std::borrow::Cow; + use fallback::{fallback_variant, Fallback}; use proc_macro2::{Ident, Literal, TokenStream}; use proc_macro_error::{abort, abort_call_site}; use quote::quote; -use syn::{Attribute, DeriveInput, LitInt, Meta, Type}; +use syn::{Attribute, DeriveInput, Generics, LitInt, Meta, Type, WhereClause, WherePredicate}; use util::PathExt; /// As arbitrary_int is limited to basic rust primitives, the maximum is u128. @@ -23,11 +25,11 @@ pub(crate) fn parse_derive(item: TokenStream) -> DeriveInput { // allow since we want `if try_from` blocks to stand out #[allow(clippy::collapsible_if)] -pub(crate) fn analyze_derive(derive_input: &DeriveInput, try_from: bool) -> (&syn::Data, TokenStream, &Ident, BitSize, Option) { +pub(crate) fn analyze_derive(derive_input: &DeriveInput, try_from: bool) -> (&syn::Data, TokenStream, &Ident, &Generics, BitSize, Option) { let DeriveInput { attrs, ident, - // generics, + generics, data, .. } = derive_input; @@ -57,7 +59,7 @@ pub(crate) fn analyze_derive(derive_input: &DeriveInput, try_from: bool) -> (&sy abort_call_site!("fallback is not allowed with `TryFromBits`"; help = "use `#[derive(FromBits)]` or remove this `#[fallback]`") } - (data, arb_int, ident, bitsize, fallback) + (data, arb_int, ident, generics, bitsize, fallback) } // If we want to support bitsize(u4) besides bitsize(4), do that here. @@ -194,3 +196,30 @@ pub(crate) fn bitsize_internal_arg(attr: &Attribute) -> Option { None } + +pub(crate) fn generate_trait_where_clause<'a>( + generics: &Generics, existing_clause: Option<&'a WhereClause>, req_trait: TokenStream, +) -> Option> { + // NOTE: This is not *ideal*, but it's approximately what the standard library does, + // for various reasons. see https://github.com/rust-lang/rust/issues/26925 + let mut extra_predicates = generics + .type_params() + .map(|t| { + let ty = &t.ident; + let res: WherePredicate = syn::parse_quote!(#ty : #req_trait); + res + }) + .peekable(); + + let predicates = match (existing_clause, extra_predicates.peek()) { + (Some(clause), Some(_)) => clause.predicates.iter().cloned().chain(extra_predicates).collect(), + (Some(clause), None) => return Some(Cow::Borrowed(clause)), + (None, Some(_)) => extra_predicates.collect(), + (None, None) => return None, + }; + + Some(Cow::Owned(WhereClause { + where_token: existing_clause.map(|c| c.where_token).unwrap_or_default(), + predicates, + })) +} diff --git a/bilge-impl/src/try_from_bits.rs b/bilge-impl/src/try_from_bits.rs index 3c8bf08..ad970e2 100644 --- a/bilge-impl/src/try_from_bits.rs +++ b/bilge-impl/src/try_from_bits.rs @@ -1,6 +1,7 @@ use proc_macro2::{Ident, TokenStream}; use proc_macro_error::{abort, emit_call_site_warning}; use quote::quote; +use syn::Generics; use syn::{punctuated::Iter, Data, DeriveInput, Fields, Type, Variant}; use crate::shared::{self, discriminant_assigner::DiscriminantAssigner, enum_fills_bitsize, fallback::Fallback, unreachable, BitSize}; @@ -8,9 +9,9 @@ use crate::shared::{bitsize_from_type_ident, last_ident_of_path}; pub(super) fn try_from_bits(item: TokenStream) -> TokenStream { let derive_input = parse(item); - let (derive_data, arb_int, name, internal_bitsize, ..) = analyze(&derive_input); + let (derive_data, arb_int, name, generics, internal_bitsize, ..) = analyze(&derive_input); match derive_data { - Data::Struct(ref data) => codegen_struct(arb_int, name, &data.fields), + Data::Struct(ref data) => codegen_struct(arb_int, name, &data.fields, generics), Data::Enum(ref enum_data) => { let variants = enum_data.variants.iter(); let match_arms = analyze_enum(variants, name, internal_bitsize, &arb_int); @@ -24,7 +25,7 @@ fn parse(item: TokenStream) -> DeriveInput { shared::parse_derive(item) } -fn analyze(derive_input: &DeriveInput) -> (&syn::Data, TokenStream, &Ident, BitSize, Option) { +fn analyze(derive_input: &DeriveInput) -> (&syn::Data, TokenStream, &Ident, &Generics, BitSize, Option) { shared::analyze_derive(derive_input, true) } @@ -64,6 +65,7 @@ fn codegen_enum(arb_int: TokenStream, enum_type: &Ident, match_arms: (Vec ::core::result::Result { + ::BITS; match number.value() { #( #from_int_match_arms )* i => Err(::bilge::give_me_error()), @@ -81,7 +83,7 @@ fn generate_field_check(ty: &Type) -> TokenStream { crate::bitsize_internal::struct_gen::generate_getter_inner(ty, false) } -fn codegen_struct(arb_int: TokenStream, struct_type: &Ident, fields: &Fields) -> TokenStream { +fn codegen_struct(arb_int: TokenStream, struct_type: &Ident, fields: &Fields, generics: &Generics) -> TokenStream { let is_ok: TokenStream = fields .iter() .map(|field| { @@ -104,8 +106,10 @@ fn codegen_struct(arb_int: TokenStream, struct_type: &Ident, fields: &Fields) -> let const_ = if cfg!(feature = "nightly") { quote!(const) } else { quote!() }; + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + quote! { - impl #const_ ::core::convert::TryFrom<#arb_int> for #struct_type { + impl #impl_generics #const_ ::core::convert::TryFrom<#arb_int> for #struct_type #ty_generics #where_clause { type Error = ::bilge::BitsError; // validates all values, which means enums, even in inner structs (TODO: and reserved fields?) @@ -113,21 +117,24 @@ fn codegen_struct(arb_int: TokenStream, struct_type: &Ident, fields: &Fields) -> type ArbIntOf = ::ArbitraryInt; type BaseIntOf = as Number>::UnderlyingType; + ::BITS; + // cursor starts at value's first field let mut cursor = value.value(); let is_ok: bool = {#is_ok}; if is_ok { - Ok(Self { value }) + Ok(Self { value, _phantom: ::core::marker::PhantomData }) } else { Err(::bilge::give_me_error()) } } } - impl #const_ ::core::convert::From<#struct_type> for #arb_int { - fn from(struct_value: #struct_type) -> Self { + impl #impl_generics #const_ ::core::convert::From<#struct_type #ty_generics> for #arb_int #where_clause { + fn from(struct_value: #struct_type #ty_generics) -> Self { + <#struct_type #ty_generics as Bitsized>::BITS; struct_value.value } } diff --git a/tests/struct.rs b/tests/struct.rs index a948ad9..76385cf 100644 --- a/tests/struct.rs +++ b/tests/struct.rs @@ -510,6 +510,14 @@ impl_from!(T; Generic => u2; |val| val.0); #[derive(DefaultBits, PartialEq, DebugBits, FromBits)] struct UsingGeneric(Generic<()>); +#[bitsize(2)] +#[derive(DefaultBits, PartialEq, DebugBits, FromBits)] +struct IsGeneric(Generic); + +#[bitsize(2)] +#[derive(DefaultBits, PartialEq, DebugBits, TryFromBits)] +struct IsGenericUnfilled(Generic); + #[bitsize(2)] #[derive(DefaultBits, PartialEq, DebugBits, TryFromBits)] struct UsingGenericUnfilled(Generic<()>); diff --git a/tests/ui/where-clause-is-preserved.rs b/tests/ui/where-clause-is-preserved.rs new file mode 100644 index 0000000..3383119 --- /dev/null +++ b/tests/ui/where-clause-is-preserved.rs @@ -0,0 +1,40 @@ +#![feature(const_trait_impl)] + +use bilge::prelude::*; +use core::marker::PhantomData; + +#[derive(Default)] +struct Generic(u2, PhantomData); + +impl Bitsized for Generic { + type ArbitraryInt = u2; + const BITS: usize = 2; + const MAX: Self::ArbitraryInt = ::MAX; +} + +impl const From for Generic { + fn from(val: u2) -> Self { + Self(val, PhantomData) + } +} + +impl const From> for u2 { + fn from(val: Generic) -> u2 { + val.0 + } +} + +#[bitsize(5)] +struct BoundedGeneric(Generic, u3) +where + T: Iterator; + +fn main() { + // kinda ick, but the only way I could find to check that the clause was on the struct directly + BoundedGeneric::<()> { + value: <_>::default(), + _phantom: PhantomData, + }; + // check that where clause is present on inherent impl + BoundedGeneric::<()>::new(<_>::default(), u3::new(0)); +} diff --git a/tests/ui/where-clause-is-preserved.stderr b/tests/ui/where-clause-is-preserved.stderr new file mode 100644 index 0000000..2ac8671 --- /dev/null +++ b/tests/ui/where-clause-is-preserved.stderr @@ -0,0 +1,44 @@ +error[E0277]: `()` is not an iterator + --> tests/ui/where-clause-is-preserved.rs:34:22 + | +34 | BoundedGeneric::<()> { + | ^^ `()` is not an iterator + | + = help: the trait `Iterator` is not implemented for `()` +note: required by a bound in `BoundedGeneric` + --> tests/ui/where-clause-is-preserved.rs:30:8 + | +28 | struct BoundedGeneric(Generic, u3) + | -------------- required by a bound in this +29 | where +30 | T: Iterator; + | ^^^^^^^^^^^^^^^^^^^ required by this bound in `BoundedGeneric` + +error[E0599]: the function or associated item `new` exists for struct `BoundedGeneric<()>`, but its trait bounds were not satisfied + --> tests/ui/where-clause-is-preserved.rs:39:27 + | +27 | #[bitsize(5)] + | ------------- function or associated item `new` not found for this struct +... +39 | BoundedGeneric::<()>::new(<_>::default(), u3::new(0)); + | ^^^ function or associated item cannot be called on `BoundedGeneric<()>` due to unsatisfied trait bounds + | + = note: the following trait bounds were not satisfied: + `<() as Iterator>::Item = ()` + `(): Iterator` + +error[E0277]: `()` is not an iterator + --> tests/ui/where-clause-is-preserved.rs:39:5 + | +39 | BoundedGeneric::<()>::new(<_>::default(), u3::new(0)); + | ^^^^^^^^^^^^^^^^^^^^ `()` is not an iterator + | + = help: the trait `Iterator` is not implemented for `()` +note: required by a bound in `BoundedGeneric` + --> tests/ui/where-clause-is-preserved.rs:30:8 + | +28 | struct BoundedGeneric(Generic, u3) + | -------------- required by a bound in this +29 | where +30 | T: Iterator; + | ^^^^^^^^^^^^^^^^^^^ required by this bound in `BoundedGeneric`