use crate::{ hdl_type_common::{get_target, type_derives, TypeOptions, WrappedInConst}, kw, Errors, HdlAttr, }; use proc_macro2::TokenStream; use quote::{format_ident, quote_spanned, ToTokens}; use syn::{ parse_quote_spanned, punctuated::Punctuated, spanned::Spanned, token::Brace, Attribute, Field, Fields, FieldsNamed, Generics, Ident, ItemStruct, Token, Visibility, }; #[derive(Clone, Debug)] pub struct ParsedBundle { pub(crate) attrs: Vec, pub(crate) options: HdlAttr, pub(crate) vis: Visibility, pub(crate) struct_token: Token![struct], pub(crate) ident: Ident, pub(crate) generics: Generics, pub(crate) fields: FieldsNamed, pub(crate) field_flips: Vec>>, pub(crate) mask_type_ident: Ident, pub(crate) mask_type_match_variant_ident: Ident, pub(crate) match_variant_ident: Ident, } impl ParsedBundle { fn parse_field( errors: &mut Errors, field: &mut Field, index: usize, ) -> Option> { let Field { attrs, vis: _, mutability: _, ident, colon_token, ty, } = field; let ident = ident.get_or_insert_with(|| format_ident!("_{index}", span = ty.span())); colon_token.get_or_insert(Token![:](ident.span())); let options = errors.unwrap_or_default(HdlAttr::parse_and_take_attr(attrs)); options } fn parse(item: ItemStruct) -> syn::Result { let ItemStruct { mut attrs, vis, struct_token, ident, generics, fields, semi_token, } = item; let mut errors = Errors::new(); let options = errors .unwrap_or_default(HdlAttr::parse_and_take_attr(&mut attrs)) .unwrap_or_default(); let mut fields = match fields { syn::Fields::Named(fields) => fields, syn::Fields::Unnamed(fields) => { errors.error(&fields, "#[hdl] struct must use curly braces: {}"); FieldsNamed { brace_token: Brace(fields.paren_token.span), named: fields.unnamed, } } syn::Fields::Unit => { errors.error(&fields, "#[hdl] struct must use curly braces: {}"); FieldsNamed { brace_token: Brace(semi_token.unwrap_or_default().span), named: Punctuated::default(), } } }; let mut field_flips = Vec::with_capacity(fields.named.len()); for (index, field) in fields.named.iter_mut().enumerate() { field_flips.push(Self::parse_field(&mut errors, field, index)); } errors.finish()?; Ok(Self { attrs, options, vis, struct_token, generics, fields, field_flips, mask_type_ident: format_ident!("__{ident}__MaskType"), mask_type_match_variant_ident: format_ident!("__{ident}__MaskType__MatchVariant"), match_variant_ident: format_ident!("__{ident}__MatchVariant"), ident, }) } } impl ToTokens for ParsedBundle { fn to_tokens(&self, tokens: &mut TokenStream) { let Self { attrs, options, vis, struct_token, ident, generics, fields, field_flips, mask_type_ident, mask_type_match_variant_ident, match_variant_ident, } = self; let TypeOptions { outline_generated: _, connect_inexact, target, } = &options.body; let target = get_target(target, ident); let mut item_attrs = attrs.clone(); item_attrs.push(type_derives(ident.span())); ItemStruct { attrs: item_attrs, vis: vis.clone(), struct_token: *struct_token, ident: ident.clone(), generics: generics.clone(), fields: Fields::Named(fields.clone()), semi_token: None, } .to_tokens(tokens); let (impl_generics, type_generics, where_clause) = generics.split_for_impl(); let mut wrapped_in_const = WrappedInConst::new(tokens, ident.span()); let tokens = wrapped_in_const.inner(); let mut mask_type_fields = fields.clone(); for Field { ident, ty, .. } in &mut mask_type_fields.named { let ident = ident.as_ref().unwrap(); *ty = parse_quote_spanned! {ident.span()=> <#ty as ::fayalite::ty::Type>::MaskType }; } ItemStruct { attrs: vec![ parse_quote_spanned! {ident.span()=> #[allow(non_camel_case_types)] }, type_derives(ident.span()), ], vis: vis.clone(), struct_token: *struct_token, ident: mask_type_ident.clone(), generics: generics.clone(), fields: Fields::Named(mask_type_fields.clone()), semi_token: None, } .to_tokens(tokens); let mut mask_type_match_variant_fields = mask_type_fields; for Field { ident, ty, .. } in &mut mask_type_match_variant_fields.named { let ident = ident.as_ref().unwrap(); *ty = parse_quote_spanned! {ident.span()=> ::fayalite::expr::Expr<#ty> }; } ItemStruct { attrs: vec![parse_quote_spanned! {ident.span()=> #[allow(non_camel_case_types)] }], vis: vis.clone(), struct_token: *struct_token, ident: mask_type_match_variant_ident.clone(), generics: generics.clone(), fields: Fields::Named(mask_type_match_variant_fields), semi_token: None, } .to_tokens(tokens); let mut match_variant_fields = fields.clone(); for Field { ident, ty, .. } in &mut match_variant_fields.named { let ident = ident.as_ref().unwrap(); *ty = parse_quote_spanned! {ident.span()=> ::fayalite::expr::Expr<#ty> }; } ItemStruct { attrs: vec![parse_quote_spanned! {ident.span()=> #[allow(non_camel_case_types)] }], vis: vis.clone(), struct_token: *struct_token, ident: match_variant_ident.clone(), generics: generics.clone(), fields: Fields::Named(match_variant_fields), semi_token: None, } .to_tokens(tokens); let match_variant_body_fields = Vec::from_iter(fields.named.iter().map(|Field { ident, .. }| { let ident: &Ident = ident.as_ref().unwrap(); let ident_str = ident.to_string(); quote_spanned! {ident.span()=> #ident: ::fayalite::expr::Expr::field(this, #ident_str), } })); let mask_type_body_fields = Vec::from_iter(fields.named.iter().map(|Field { ident, .. }| { let ident: &Ident = ident.as_ref().unwrap(); quote_spanned! {ident.span()=> #ident: ::fayalite::ty::Type::mask_type(&self.#ident), } })); let from_canonical_body_fields = Vec::from_iter(fields.named.iter().enumerate().zip(field_flips).map( |((index, Field { ident, .. }), flip)| { let ident: &Ident = ident.as_ref().unwrap(); let ident_str = ident.to_string(); let flipped = flip.is_some(); quote_spanned! {ident.span()=> #ident: { let ::fayalite::bundle::BundleField { name, flipped, ty } = fields[#index]; ::fayalite::__std::assert_eq!(&*name, #ident_str); ::fayalite::__std::assert_eq!(flipped, #flipped); ::fayalite::ty::Type::from_canonical(ty) }, } }, )); let fields_len = fields.named.len(); quote_spanned! {ident.span()=> #[automatically_derived] impl #impl_generics ::fayalite::ty::Type for #mask_type_ident #type_generics #where_clause { type MaskType = #mask_type_ident #type_generics; type MatchVariant = #mask_type_match_variant_ident #type_generics; type MatchActiveScope = (); type MatchVariantAndInactiveScope = MatchVariantWithoutScope< ::MatchVariant, >; type MatchVariantsIter = ::fayalite::__std::iter::Once< ::MatchVariantAndInactiveScope, >; fn match_variants( this: ::fayalite::expr::Expr, module_builder: &mut ::fayalite::module::ModuleBuilder< ::fayalite::module::NormalModule, >, source_location: ::fayalite::source_location::SourceLocation, ) -> ::MatchVariantsIter { let _ = this; let _ = module_builder; let _ = source_location; let retval = #mask_type_match_variant_ident { #(#match_variant_body_fields)* }; ::fayalite::__std::iter::once(::fayalite::ty::MatchVariantWithoutScope(retval)) } fn mask_type(&self) -> ::MaskType { *self } fn canonical(&self) -> ::fayalite::ty::CanonicalType { ::fayalite::ty::Type::canonical(&::fayalite::bundle::Bundle::new(::fayalite::bundle::BundleType::fields(self))) } #[track_caller] fn from_canonical(canonical_type: ::fayalite::ty::CanonicalType) -> Self { let ::fayalite::ty::CanonicalType::Bundle(bundle) = canonical_type else { ::fayalite::__std::panic!("expected bundle"); }; let fields = ::fayalite::bundle::BundleType::fields(&bundle); ::fayalite::__std::assert_eq!(fields.len(), #fields_len, "bundle has wrong number of fields"); Self { #(#from_canonical_body_fields)* } } fn source_location() -> ::fayalite::source_location::SourceLocation { ::fayalite::source_location::SourceLocation::caller() } } #[automatically_derived] impl #impl_generics ::fayalite::ty::Type for #target #type_generics #where_clause { type MaskType = #mask_type_ident #type_generics; type MatchVariant = #match_variant_ident #type_generics; type MatchActiveScope = (); type MatchVariantAndInactiveScope = MatchVariantWithoutScope< ::MatchVariant, >; type MatchVariantsIter = ::fayalite::__std::iter::Once< ::MatchVariantAndInactiveScope, >; fn match_variants( this: ::fayalite::expr::Expr, module_builder: &mut ::fayalite::module::ModuleBuilder< ::fayalite::module::NormalModule, >, source_location: ::fayalite::source_location::SourceLocation, ) -> ::MatchVariantsIter { let _ = this; let _ = module_builder; let _ = source_location; let retval = #mask_type_match_variant_ident { #(#match_variant_body_fields)* }; ::fayalite::__std::iter::once(::fayalite::ty::MatchVariantWithoutScope(retval)) } fn mask_type(&self) -> ::MaskType { #mask_type_ident { #(#mask_type_body_fields)* } } fn canonical(&self) -> ::fayalite::ty::CanonicalType { ::fayalite::ty::Type::canonical(&::fayalite::bundle::Bundle::new(::fayalite::bundle::BundleType::fields(self))) } #[track_caller] fn from_canonical(canonical_type: ::fayalite::ty::CanonicalType) -> Self { let ::fayalite::ty::CanonicalType::Bundle(bundle) = canonical_type else { ::fayalite::__std::panic!("expected bundle"); }; let fields = ::fayalite::bundle::BundleType::fields(&bundle); ::fayalite::__std::assert_eq!(fields.len(), #fields_len, "bundle has wrong number of fields"); Self { #(#from_canonical_body_fields)* } } fn source_location() -> ::fayalite::source_location::SourceLocation { ::fayalite::source_location::SourceLocation::caller() } } } .to_tokens(tokens); } } pub(crate) fn hdl_bundle(item: ItemStruct) -> syn::Result { let item = ParsedBundle::parse(item)?; let outline_generated = item.options.body.outline_generated; let mut contents = item.to_token_stream(); if outline_generated.is_some() { contents = crate::outline_generated(contents, "hdl-bundle-"); } Ok(contents) }