use crate::{ hdl_type_common::{ common_derives, get_target, ItemOptions, MakeHdlTypeExpr, MaybeParsed, ParsedGenerics, ParsedType, SplitForImpl, TypesParser, WrappedInConst, }, kw, Errors, HdlAttr, PairsIterExt, }; use proc_macro2::TokenStream; use quote::{format_ident, quote_spanned, ToTokens}; use syn::{ parse_quote_spanned, punctuated::{Pair, Punctuated}, token::{Brace, Paren}, Attribute, Field, FieldMutability, Fields, FieldsNamed, FieldsUnnamed, Generics, Ident, ItemEnum, ItemStruct, Token, Type, Variant, Visibility, }; crate::options! { #[options = VariantOptions] pub(crate) enum VariantOption {} } crate::options! { #[options = FieldOptions] pub(crate) enum FieldOption {} } #[derive(Clone, Debug)] pub(crate) struct ParsedVariantField { pub(crate) paren_token: Paren, pub(crate) attrs: Vec, pub(crate) options: HdlAttr, pub(crate) ty: MaybeParsed, pub(crate) comma_token: Option, } #[derive(Clone, Debug)] pub(crate) struct ParsedVariant { pub(crate) attrs: Vec, pub(crate) options: HdlAttr, pub(crate) ident: Ident, pub(crate) field: Option, } impl ParsedVariant { fn parse( errors: &mut Errors, variant: Variant, generics: &MaybeParsed, ) -> Self { let Variant { mut attrs, ident, fields, discriminant, } = variant; let options = errors .unwrap_or_default(HdlAttr::parse_and_take_attr(&mut attrs)) .unwrap_or_default(); let field = match fields { Fields::Unnamed(FieldsUnnamed { paren_token, unnamed, }) if unnamed.len() == 1 => { let (field, comma_token) = unnamed.into_pairs().next().unwrap().into_tuple(); let Field { mut attrs, vis, mutability, ident: _, colon_token: _, ty, } = field; let options = errors .unwrap_or_default(HdlAttr::parse_and_take_attr(&mut attrs)) .unwrap_or_default(); if !matches!(vis, Visibility::Inherited) { errors.error( &vis, "enum variant fields must not have a visibility specifier", ); } if !matches!(mutability, FieldMutability::None) { // FIXME: use mutability as the spanned tokens, // blocked on https://github.com/dtolnay/syn/issues/1717 errors.error(&ty, "field mutability is not supported"); } Some(ParsedVariantField { paren_token, attrs, options, ty: TypesParser::maybe_run(generics.as_ref(), ty, errors), comma_token, }) } Fields::Unit => None, Fields::Unnamed(fields) if fields.unnamed.is_empty() => None, Fields::Named(fields) if fields.named.is_empty() => None, Fields::Unnamed(_) | Fields::Named(_) => { errors.error( fields, "enum variant must either have no fields or a single parenthesized field", ); None } }; if let Some((eq, _)) = discriminant { errors.error(eq, "custom enum discriminants are not allowed"); } Self { attrs, options, ident, field, } } } #[derive(Clone, Debug)] pub(crate) struct ParsedEnum { pub(crate) attrs: Vec, pub(crate) options: HdlAttr, pub(crate) vis: Visibility, pub(crate) enum_token: Token![enum], pub(crate) ident: Ident, pub(crate) generics: MaybeParsed, pub(crate) brace_token: Brace, pub(crate) variants: Punctuated, pub(crate) match_variant_ident: Ident, } impl ParsedEnum { fn parse(item: ItemEnum) -> syn::Result { let ItemEnum { mut attrs, vis, enum_token, ident, mut generics, brace_token, variants, } = item; let mut errors = Errors::new(); let mut options = errors .unwrap_or_default(HdlAttr::::parse_and_take_attr( &mut attrs, )) .unwrap_or_default(); errors.ok(options.body.validate()); let ItemOptions { outline_generated: _, target: _, custom_bounds, no_static: _, no_runtime_generics: _, } = options.body; attrs.retain(|attr| { if attr.path().is_ident("repr") { errors.error(attr, "#[repr] is not supported on #[hdl] enums"); false } else { true } }); let generics = if custom_bounds.is_some() { MaybeParsed::Unrecognized(generics) } else if let Some(generics) = errors.ok(ParsedGenerics::parse(&mut generics)) { MaybeParsed::Parsed(generics) } else { MaybeParsed::Unrecognized(generics) }; let variants = Punctuated::from_iter( variants .into_pairs() .map_pair_value(|v| ParsedVariant::parse(&mut errors, v, &generics)), ); errors.finish()?; Ok(Self { attrs, options, vis, enum_token, generics, brace_token, variants, match_variant_ident: format_ident!("__{}__MatchVariant", ident), ident, }) } } impl ToTokens for ParsedEnum { fn to_tokens(&self, tokens: &mut TokenStream) { let Self { attrs, options, vis, enum_token, ident, generics, brace_token, variants, match_variant_ident, } = self; let ItemOptions { outline_generated: _, target, custom_bounds: _, no_static, no_runtime_generics, } = &options.body; let target = get_target(target, ident); let mut struct_attrs = attrs.clone(); struct_attrs.push(common_derives(ident.span())); struct_attrs.push(parse_quote_spanned! {ident.span()=> #[allow(non_snake_case)] }); let struct_fields = Punctuated::from_iter(variants.pairs().map_pair_value_ref( |ParsedVariant { attrs: _, options, ident, field, }| { let VariantOptions {} = options.body; let colon_token; let ty = if let Some(ParsedVariantField { paren_token, attrs: _, options, ty, comma_token: _, }) = field { let FieldOptions {} = options.body; colon_token = Token![:](paren_token.span.open()); ty.clone().into() } else { colon_token = Token![:](ident.span()); parse_quote_spanned! {ident.span()=> () } }; Field { attrs: vec![], vis: vis.clone(), mutability: FieldMutability::None, ident: Some(ident.clone()), colon_token: Some(colon_token), ty, } }, )); ItemStruct { attrs: struct_attrs, vis: vis.clone(), struct_token: Token![struct](enum_token.span), ident: ident.clone(), generics: generics.into(), fields: if struct_fields.is_empty() { Fields::Unit } else { Fields::Named(FieldsNamed { brace_token: *brace_token, named: struct_fields, }) }, semi_token: None, } .to_tokens(tokens); let (impl_generics, type_generics, where_clause) = generics.split_for_impl(); if let (MaybeParsed::Parsed(generics), None) = (generics, no_runtime_generics) { generics.make_runtime_generics(tokens, vis, ident, &target, |context| { let fields: Vec<_> = variants .iter() .map(|ParsedVariant { ident, field, .. }| { if let Some(ParsedVariantField { ty: MaybeParsed::Parsed(ty), .. }) = field { let expr = ty.make_hdl_type_expr(context); quote_spanned! {ident.span()=> #ident: #expr, } } else { quote_spanned! {ident.span()=> #ident: (), } } }) .collect(); parse_quote_spanned! {ident.span()=> #target { #(#fields)* } } }) } let mut wrapped_in_const = WrappedInConst::new(tokens, ident.span()); let tokens = wrapped_in_const.inner(); { let mut wrapped_in_const = WrappedInConst::new(tokens, ident.span()); let tokens = wrapped_in_const.inner(); let mut enum_attrs = attrs.clone(); enum_attrs.push(parse_quote_spanned! {ident.span()=> #[allow(dead_code)] }); ItemEnum { attrs: enum_attrs, vis: vis.clone(), enum_token: *enum_token, ident: ident.clone(), generics: generics.into(), brace_token: *brace_token, variants: Punctuated::from_iter(variants.pairs().map_pair_value_ref( |ParsedVariant { attrs, options: _, ident, field, }| Variant { attrs: attrs.clone(), ident: ident.clone(), fields: match field { Some(ParsedVariantField { paren_token, attrs, options: _, ty, comma_token, }) => Fields::Unnamed(FieldsUnnamed { paren_token: *paren_token, unnamed: Punctuated::from_iter([Pair::new( Field { attrs: attrs.clone(), vis: Visibility::Inherited, mutability: FieldMutability::None, ident: None, colon_token: None, ty: ty.clone().into(), }, *comma_token, )]), }), None => Fields::Unit, }, discriminant: None, }, )), } .to_tokens(tokens); } let mut enum_attrs = attrs.clone(); enum_attrs.push(parse_quote_spanned! {ident.span()=> #[allow(dead_code, non_camel_case_types)] }); ItemEnum { attrs: enum_attrs, vis: vis.clone(), enum_token: *enum_token, ident: match_variant_ident.clone(), generics: generics.into(), brace_token: *brace_token, variants: Punctuated::from_iter(variants.pairs().map_pair_value_ref( |ParsedVariant { attrs, options: _, ident, field, }| Variant { attrs: attrs.clone(), ident: ident.clone(), fields: match field { Some(ParsedVariantField { paren_token, attrs, options: _, ty, comma_token, }) => Fields::Unnamed(FieldsUnnamed { paren_token: *paren_token, unnamed: Punctuated::from_iter([Pair::new( Field { attrs: attrs.clone(), vis: Visibility::Inherited, mutability: FieldMutability::None, ident: None, colon_token: None, ty: parse_quote_spanned! {ident.span()=> ::fayalite::expr::Expr<#ty> }, }, *comma_token, )]), }), None => Fields::Unit, }, discriminant: None, }, )), } .to_tokens(tokens); for (index, ParsedVariant { ident, field, .. }) in variants.iter().enumerate() { if let Some(ParsedVariantField { ty, .. }) = field { quote_spanned! {ident.span()=> #[automatically_derived] impl #impl_generics #target #type_generics #where_clause { #[allow(non_snake_case, dead_code)] #vis fn #ident<__V: ::fayalite::expr::ToExpr>( self, v: __V, ) -> ::fayalite::expr::Expr { ::fayalite::expr::ToExpr::to_expr( &::fayalite::expr::ops::EnumLiteral::new_by_index( self, #index, ::fayalite::__std::option::Option::Some( ::fayalite::expr::Expr::canonical( ::fayalite::expr::ToExpr::to_expr(&v), ), ), ), ) } } } } else { quote_spanned! {ident.span()=> #[automatically_derived] impl #impl_generics #target #type_generics #where_clause { #[allow(non_snake_case, dead_code)] #vis fn #ident(self) -> ::fayalite::expr::Expr { ::fayalite::expr::ToExpr::to_expr( &::fayalite::expr::ops::EnumLiteral::new_by_index( self, #index, ::fayalite::__std::option::Option::None, ), ) } } } } .to_tokens(tokens); } let from_canonical_body_fields = Vec::from_iter(variants.iter().enumerate().map( |(index, ParsedVariant { ident, field, .. })| { let ident_str = ident.to_string(); let val = if field.is_some() { let missing_value_msg = format!("expected variant {ident} to have a field"); quote_spanned! {ident.span()=> ::fayalite::ty::Type::from_canonical(ty.expect(#missing_value_msg)) } } else { quote_spanned! {ident.span()=> ::fayalite::__std::assert!(ty.is_none()); } }; quote_spanned! {ident.span()=> #ident: { let ::fayalite::enum_::EnumVariant { name, ty, } = variants[#index]; ::fayalite::__std::assert_eq!(&*name, #ident_str); #val }, } }, )); let match_active_scope_match_arms = Vec::from_iter(variants.iter().enumerate().map( |(index, ParsedVariant { ident, field, .. })| { if field.is_some() { quote_spanned! {ident.span()=> #index => #match_variant_ident::#ident( ::fayalite::expr::ToExpr::to_expr( &::fayalite::expr::ops::VariantAccess::new_by_index( variant_access.base(), variant_access.variant_index(), ), ), ), } } else { quote_spanned! {ident.span()=> #index => #match_variant_ident::#ident, } } }, )); let variants_body_variants = Vec::from_iter(variants.iter().map( |ParsedVariant { attrs: _, options, ident, field, }| { let VariantOptions {} = options.body; let ident_str = ident.to_string(); match field { Some(ParsedVariantField { options, .. }) => { let FieldOptions {} = options.body; quote_spanned! {ident.span()=> ::fayalite::enum_::EnumVariant { name: ::fayalite::intern::Intern::intern(#ident_str), ty: ::fayalite::__std::option::Option::Some( ::fayalite::ty::Type::canonical(&self.#ident), ), }, } } None => quote_spanned! {ident.span()=> ::fayalite::enum_::EnumVariant { name: ::fayalite::intern::Intern::intern(#ident_str), ty: ::fayalite::__std::option::Option::None, }, }, } }, )); let variants_len = variants.len(); quote_spanned! {ident.span()=> #[automatically_derived] impl #impl_generics ::fayalite::ty::Type for #target #type_generics #where_clause { type BaseType = ::fayalite::enum_::Enum; type MaskType = ::fayalite::int::Bool; type MatchVariant = #match_variant_ident #type_generics; type MatchActiveScope = ::fayalite::module::Scope; type MatchVariantAndInactiveScope = ::fayalite::enum_::EnumMatchVariantAndInactiveScope; type MatchVariantsIter = ::fayalite::enum_::EnumMatchVariantsIter; fn match_variants( this: ::fayalite::expr::Expr, source_location: ::fayalite::source_location::SourceLocation, ) -> ::MatchVariantsIter { ::fayalite::module::enum_match_variants_helper(this, source_location) } fn mask_type(&self) -> ::MaskType { ::fayalite::int::Bool } fn canonical(&self) -> ::fayalite::ty::CanonicalType { ::fayalite::ty::CanonicalType::Enum(::fayalite::enum_::Enum::new(::fayalite::enum_::EnumType::variants(self))) } #[track_caller] #[allow(non_snake_case)] fn from_canonical(canonical_type: ::fayalite::ty::CanonicalType) -> Self { let ::fayalite::ty::CanonicalType::Enum(enum_) = canonical_type else { ::fayalite::__std::panic!("expected enum"); }; let variants = ::fayalite::enum_::EnumType::variants(&enum_); ::fayalite::__std::assert_eq!(variants.len(), #variants_len, "enum has wrong number of variants"); Self { #(#from_canonical_body_fields)* } } fn source_location() -> ::fayalite::source_location::SourceLocation { ::fayalite::source_location::SourceLocation::caller() } } #[automatically_derived] impl #impl_generics ::fayalite::enum_::EnumType for #target #type_generics #where_clause { fn match_activate_scope( v: ::MatchVariantAndInactiveScope, ) -> (::MatchVariant, ::MatchActiveScope) { let (variant_access, scope) = v.activate(); ( match variant_access.variant_index() { #(#match_active_scope_match_arms)* #variants_len.. => ::fayalite::__std::panic!("invalid variant index"), }, scope, ) } fn variants(&self) -> ::fayalite::intern::Interned<[::fayalite::enum_::EnumVariant]> { ::fayalite::intern::Intern::intern(&[ #(#variants_body_variants)* ][..]) } } } .to_tokens(tokens); if let (None, MaybeParsed::Parsed(generics)) = (no_static, &self.generics) { let static_generics = generics.clone().for_static_type(); let (static_impl_generics, static_type_generics, static_where_clause) = static_generics.split_for_impl(); let static_type_body_variants = Vec::from_iter(variants.iter().map(|ParsedVariant { ident, field, .. }| { if let Some(_) = field { quote_spanned! {ident.span()=> #ident: ::fayalite::ty::StaticType::TYPE, } } else { quote_spanned! {ident.span()=> #ident: (), } } })); let type_properties = format_ident!("__type_properties", span = ident.span()); let type_properties_variants = Vec::from_iter(variants.iter().map(|ParsedVariant { ident, field, .. }| { let variant = if let Some(ParsedVariantField { ty, .. }) = field { quote_spanned! {ident.span()=> ::fayalite::__std::option::Option::Some( <#ty as ::fayalite::ty::StaticType>::TYPE_PROPERTIES, ) } } else { quote_spanned! {ident.span()=> ::fayalite::__std::option::Option::None } }; quote_spanned! {ident.span()=> let #type_properties = #type_properties.variant(#variant); } })); quote_spanned! {ident.span()=> #[automatically_derived] impl #static_impl_generics ::fayalite::ty::StaticType for #target #static_type_generics #static_where_clause { const TYPE: Self = Self { #(#static_type_body_variants)* }; const MASK_TYPE: ::MaskType = ::fayalite::int::Bool; const TYPE_PROPERTIES: ::fayalite::ty::TypeProperties = { let #type_properties = ::fayalite::enum_::EnumTypePropertiesBuilder::new(); #(#type_properties_variants)* #type_properties.finish() }; const MASK_TYPE_PROPERTIES: ::fayalite::ty::TypeProperties = <::fayalite::int::Bool as ::fayalite::ty::StaticType>::TYPE_PROPERTIES; } } .to_tokens(tokens); } } } pub(crate) fn hdl_enum(item: ItemEnum) -> syn::Result { let item = ParsedEnum::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-enum-"); } Ok(contents) }