From 094c77e26ecd495f667d00eba6744d59793b1175 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Sun, 26 Oct 2025 02:13:10 -0700 Subject: [PATCH] add #[hdl(get(|v| ...))] type GetStuff> = MyType or DynSize; --- .../src/hdl_bundle.rs | 5 + .../fayalite-proc-macros-impl/src/hdl_enum.rs | 5 + .../src/hdl_type_alias.rs | 556 ++++++++++++++++-- .../src/hdl_type_common.rs | 2 + crates/fayalite-proc-macros-impl/src/lib.rs | 1 + crates/fayalite/src/phantom_const.rs | 68 +++ crates/fayalite/src/prelude.rs | 2 +- crates/fayalite/tests/hdl_types.rs | 19 +- 8 files changed, 600 insertions(+), 58 deletions(-) diff --git a/crates/fayalite-proc-macros-impl/src/hdl_bundle.rs b/crates/fayalite-proc-macros-impl/src/hdl_bundle.rs index 09189bd..e8dc51b 100644 --- a/crates/fayalite-proc-macros-impl/src/hdl_bundle.rs +++ b/crates/fayalite-proc-macros-impl/src/hdl_bundle.rs @@ -87,7 +87,11 @@ impl ParsedBundle { no_static: _, no_runtime_generics: _, cmp_eq: _, + ref get, } = options.body; + if let Some((get, ..)) = get { + errors.error(get, "#[hdl(get(...))] is not allowed on structs"); + } let mut fields = match fields { syn::Fields::Named(fields) => fields, syn::Fields::Unnamed(fields) => { @@ -445,6 +449,7 @@ impl ToTokens for ParsedBundle { no_static, no_runtime_generics, cmp_eq, + get: _, } = &options.body; let target = get_target(target, ident); let mut item_attrs = attrs.clone(); diff --git a/crates/fayalite-proc-macros-impl/src/hdl_enum.rs b/crates/fayalite-proc-macros-impl/src/hdl_enum.rs index 47a5df1..885cf87 100644 --- a/crates/fayalite-proc-macros-impl/src/hdl_enum.rs +++ b/crates/fayalite-proc-macros-impl/src/hdl_enum.rs @@ -159,10 +159,14 @@ impl ParsedEnum { no_static: _, no_runtime_generics: _, cmp_eq, + ref get, } = options.body; if let Some((cmp_eq,)) = cmp_eq { errors.error(cmp_eq, "#[hdl(cmp_eq)] is not yet implemented for enums"); } + if let Some((get, ..)) = get { + errors.error(get, "#[hdl(get(...))] is not allowed on enums"); + } attrs.retain(|attr| { if attr.path().is_ident("repr") { errors.error(attr, "#[repr] is not supported on #[hdl] enums"); @@ -225,6 +229,7 @@ impl ToTokens for ParsedEnum { no_static, no_runtime_generics, cmp_eq: _, // TODO: implement cmp_eq for enums + get: _, } = &options.body; let target = get_target(target, ident); let mut struct_attrs = attrs.clone(); diff --git a/crates/fayalite-proc-macros-impl/src/hdl_type_alias.rs b/crates/fayalite-proc-macros-impl/src/hdl_type_alias.rs index d4a035b..8235366 100644 --- a/crates/fayalite-proc-macros-impl/src/hdl_type_alias.rs +++ b/crates/fayalite-proc-macros-impl/src/hdl_type_alias.rs @@ -4,28 +4,353 @@ use crate::{ Errors, HdlAttr, hdl_type_common::{ ItemOptions, MakeHdlTypeExpr, MaybeParsed, ParsedGenerics, ParsedType, TypesParser, - get_target, + WrappedInConst, common_derives, get_target, known_items, }, kw, }; use proc_macro2::TokenStream; -use quote::ToTokens; -use syn::{Attribute, Generics, Ident, ItemType, Token, Type, Visibility, parse_quote_spanned}; +use quote::{ToTokens, format_ident, quote_spanned}; +use syn::{ + AngleBracketedGenericArguments, Attribute, Expr, Fields, GenericArgument, GenericParam, + Generics, Ident, ItemStruct, ItemType, Path, PathArguments, Token, TraitBound, + TraitBoundModifier, Type, TypeGroup, TypeParam, TypeParamBound, TypeParen, Visibility, + parse_quote_spanned, punctuated::Pair, token::Paren, +}; #[derive(Clone, Debug)] -pub(crate) struct ParsedTypeAlias { - pub(crate) attrs: Vec, - pub(crate) options: HdlAttr, - pub(crate) vis: Visibility, - pub(crate) type_token: Token![type], - pub(crate) ident: Ident, - pub(crate) generics: MaybeParsed, - pub(crate) eq_token: Token![=], - pub(crate) ty: MaybeParsed, - pub(crate) semi_token: Token![;], +pub(crate) struct PhantomConstGetBound { + pub(crate) phantom_const_get: known_items::PhantomConstGet, + pub(crate) colon2_token: Option, + pub(crate) lt_token: Token![<], + pub(crate) ty: Type, + pub(crate) comma_token: Option, + pub(crate) gt_token: Token![>], +} + +impl From for Path { + fn from(value: PhantomConstGetBound) -> Self { + let PhantomConstGetBound { + phantom_const_get, + colon2_token, + lt_token, + ty, + comma_token, + gt_token, + } = value; + let mut path = phantom_const_get.path; + path.segments.last_mut().expect("known to exist").arguments = + PathArguments::AngleBracketed(AngleBracketedGenericArguments { + colon2_token, + lt_token, + args: FromIterator::from_iter([Pair::new(GenericArgument::Type(ty), comma_token)]), + gt_token, + }); + path + } +} + +impl From for TraitBound { + fn from(value: PhantomConstGetBound) -> Self { + let path = Path::from(value); + TraitBound { + paren_token: None, + modifier: TraitBoundModifier::None, + lifetimes: None, + path, + } + } +} + +impl From for TypeParamBound { + fn from(value: PhantomConstGetBound) -> Self { + TraitBound::from(value).into() + } +} + +impl PhantomConstGetBound { + fn parse_opt(bound: TypeParamBound) -> Option { + let TypeParamBound::Trait(TraitBound { + paren_token: None, + modifier: TraitBoundModifier::None, + lifetimes: None, + path, + }) = bound + else { + return None; + }; + let Ok(( + phantom_const_get, + PathArguments::AngleBracketed(AngleBracketedGenericArguments { + colon2_token, + lt_token, + args, + gt_token, + }), + )) = known_items::PhantomConstGet::parse_path_with_arguments(path) + else { + return None; + }; + let mut args = args.into_pairs(); + let (GenericArgument::Type(ty), comma_token) = args.next()?.into_tuple() else { + return None; + }; + let None = args.next() else { + return None; + }; + Some(Self { + phantom_const_get, + colon2_token, + lt_token, + ty, + comma_token, + gt_token, + }) + } +} + +#[derive(Clone, Debug)] +pub(crate) struct PhantomConstAccessorTypeParam { + attrs: Vec, + ident: Ident, + colon_token: Token![:], + phantom_const_get_bound: PhantomConstGetBound, + plus_token: Option, +} + +impl From for TypeParam { + fn from(value: PhantomConstAccessorTypeParam) -> Self { + let PhantomConstAccessorTypeParam { + attrs, + ident, + colon_token, + phantom_const_get_bound, + plus_token, + } = value; + TypeParam { + attrs, + ident, + colon_token: Some(colon_token), + bounds: FromIterator::from_iter([Pair::new( + phantom_const_get_bound.into(), + plus_token, + )]), + eq_token: None, + default: None, + } + } +} + +impl From for GenericParam { + fn from(value: PhantomConstAccessorTypeParam) -> Self { + TypeParam::from(value).into() + } +} + +impl PhantomConstAccessorTypeParam { + fn parse_opt(generic_param: GenericParam) -> Option { + let GenericParam::Type(TypeParam { + attrs, + ident, + colon_token, + bounds, + eq_token: None, + default: None, + }) = generic_param + else { + return None; + }; + let colon_token = colon_token.unwrap_or(Token![:](ident.span())); + let mut bounds = bounds.into_pairs(); + let (bound, plus_token) = bounds.next()?.into_tuple(); + let phantom_const_get_bound = PhantomConstGetBound::parse_opt(bound)?; + let None = bounds.next() else { + return None; + }; + Some(Self { + attrs, + ident, + colon_token, + phantom_const_get_bound, + plus_token, + }) + } +} + +#[derive(Clone, Debug)] +pub(crate) struct PhantomConstAccessorGenerics { + lt_token: Token![<], + type_param: PhantomConstAccessorTypeParam, + comma_token: Option, + gt_token: Token![>], +} + +impl From for Generics { + fn from(value: PhantomConstAccessorGenerics) -> Self { + let PhantomConstAccessorGenerics { + lt_token, + type_param, + comma_token, + gt_token, + } = value; + Generics { + lt_token: Some(lt_token), + params: FromIterator::from_iter([Pair::new(type_param.into(), comma_token)]), + gt_token: Some(gt_token), + where_clause: None, + } + } +} + +impl<'a> From<&'a PhantomConstAccessorGenerics> for Generics { + fn from(value: &'a PhantomConstAccessorGenerics) -> Self { + value.clone().into() + } +} + +impl PhantomConstAccessorGenerics { + fn parse_opt(generics: Generics) -> Option { + let Generics { + lt_token, + params, + gt_token, + where_clause: None, + } = generics + else { + return None; + }; + let mut params = params.into_pairs(); + let (generic_param, comma_token) = params.next()?.into_tuple(); + let type_param = PhantomConstAccessorTypeParam::parse_opt(generic_param)?; + let span = type_param.ident.span(); + let lt_token = lt_token.unwrap_or(Token![<](span)); + let gt_token = gt_token.unwrap_or(Token![>](span)); + let None = params.next() else { + return None; + }; + Some(Self { + lt_token, + type_param, + comma_token, + gt_token, + }) + } +} + +#[derive(Clone, Debug)] +pub(crate) enum ParsedTypeAlias { + TypeAlias { + attrs: Vec, + options: HdlAttr, + vis: Visibility, + type_token: Token![type], + ident: Ident, + generics: MaybeParsed, + eq_token: Token![=], + ty: MaybeParsed, + semi_token: Token![;], + }, + PhantomConstAccessor { + attrs: Vec, + options: HdlAttr, + get: (kw::get, Paren, Expr), + vis: Visibility, + type_token: Token![type], + ident: Ident, + generics: PhantomConstAccessorGenerics, + eq_token: Token![=], + ty: Type, + ty_is_dyn_size: Option, + semi_token: Token![;], + }, } impl ParsedTypeAlias { + fn ty_is_dyn_size(ty: &Type) -> Option { + match ty { + Type::Group(TypeGroup { + group_token: _, + elem, + }) => Self::ty_is_dyn_size(elem), + Type::Paren(TypeParen { + paren_token: _, + elem, + }) => Self::ty_is_dyn_size(elem), + Type::Path(syn::TypePath { qself: None, path }) => { + known_items::DynSize::parse_path(path.clone()).ok() + } + _ => None, + } + } + fn parse_phantom_const_accessor( + item: ItemType, + mut errors: Errors, + options: HdlAttr, + get: (kw::get, Paren, Expr), + ) -> syn::Result { + let ItemType { + attrs, + vis, + type_token, + ident, + generics, + eq_token, + ty, + semi_token, + } = item; + let ItemOptions { + outline_generated: _, + ref target, + custom_bounds, + no_static, + no_runtime_generics, + cmp_eq, + get: _, + } = options.body; + if let Some((no_static,)) = no_static { + errors.error(no_static, "no_static is not valid on type aliases"); + } + if let Some((target, ..)) = target { + errors.error( + target, + "target is not implemented on PhantomConstGet type aliases", + ); + } + if let Some((no_runtime_generics,)) = no_runtime_generics { + errors.error( + no_runtime_generics, + "no_runtime_generics is not implemented on PhantomConstGet type aliases", + ); + } + if let Some((cmp_eq,)) = cmp_eq { + errors.error(cmp_eq, "cmp_eq is not valid on type aliases"); + } + if let Some((custom_bounds,)) = custom_bounds { + errors.error( + custom_bounds, + "custom_bounds is not implemented on PhantomConstGet type aliases", + ); + } + let Some(generics) = PhantomConstAccessorGenerics::parse_opt(generics) else { + errors.error(ident, "#[hdl(get(...))] type alias must be of the form:\ntype MyTypeGetter> = RetType;"); + errors.finish()?; + unreachable!(); + }; + errors.finish()?; + let ty_is_dyn_size = Self::ty_is_dyn_size(&ty); + Ok(Self::PhantomConstAccessor { + attrs, + options, + get, + vis, + type_token, + ident, + generics, + eq_token, + ty: *ty, + ty_is_dyn_size, + semi_token, + }) + } fn parse(item: ItemType) -> syn::Result { let ItemType { mut attrs, @@ -51,7 +376,25 @@ impl ParsedTypeAlias { no_static, no_runtime_generics: _, cmp_eq, + ref mut get, } = options.body; + if let Some(get) = get.take() { + return Self::parse_phantom_const_accessor( + ItemType { + attrs, + vis, + type_token, + ident, + generics, + eq_token, + ty, + semi_token, + }, + errors, + options, + get, + ); + } if let Some((no_static,)) = no_static { errors.error(no_static, "no_static is not valid on type aliases"); } @@ -67,7 +410,7 @@ impl ParsedTypeAlias { }; let ty = TypesParser::maybe_run(generics.as_ref(), *ty, &mut errors); errors.finish()?; - Ok(Self { + Ok(Self::TypeAlias { attrs, options, vis, @@ -83,54 +426,155 @@ impl ParsedTypeAlias { impl ToTokens for ParsedTypeAlias { fn to_tokens(&self, tokens: &mut TokenStream) { - let Self { - attrs, - options, - vis, - type_token, - ident, - generics, - eq_token, - ty, - semi_token, - } = self; - let ItemOptions { - outline_generated: _, - target, - custom_bounds: _, - no_static: _, - no_runtime_generics, - cmp_eq: _, - } = &options.body; - let target = get_target(target, ident); - let mut type_attrs = attrs.clone(); - type_attrs.push(parse_quote_spanned! {ident.span()=> - #[allow(type_alias_bounds)] - }); - ItemType { - attrs: type_attrs, - vis: vis.clone(), - type_token: *type_token, - ident: ident.clone(), - generics: generics.into(), - eq_token: *eq_token, - ty: Box::new(ty.clone().into()), - semi_token: *semi_token, - } - .to_tokens(tokens); - if let (MaybeParsed::Parsed(generics), MaybeParsed::Parsed(ty), None) = - (generics, ty, no_runtime_generics) - { - generics.make_runtime_generics(tokens, vis, ident, &target, |context| { - ty.make_hdl_type_expr(context) - }) + match self { + Self::TypeAlias { + attrs, + options, + vis, + type_token, + ident, + generics, + eq_token, + ty, + semi_token, + } => { + let ItemOptions { + outline_generated: _, + target, + custom_bounds: _, + no_static: _, + no_runtime_generics, + cmp_eq: _, + get: _, + } = &options.body; + let target = get_target(target, ident); + let mut type_attrs = attrs.clone(); + type_attrs.push(parse_quote_spanned! {ident.span()=> + #[allow(type_alias_bounds)] + }); + ItemType { + attrs: type_attrs, + vis: vis.clone(), + type_token: *type_token, + ident: ident.clone(), + generics: generics.into(), + eq_token: *eq_token, + ty: Box::new(ty.clone().into()), + semi_token: *semi_token, + } + .to_tokens(tokens); + if let (MaybeParsed::Parsed(generics), MaybeParsed::Parsed(ty), None) = + (generics, ty, no_runtime_generics) + { + generics.make_runtime_generics(tokens, vis, ident, &target, |context| { + ty.make_hdl_type_expr(context) + }) + } + } + Self::PhantomConstAccessor { + attrs, + options, + get: (_get_kw, _get_paren, get_expr), + vis, + type_token, + ident, + generics, + eq_token, + ty, + ty_is_dyn_size, + semi_token, + } => { + let ItemOptions { + outline_generated: _, + target: _, + custom_bounds: _, + no_static: _, + no_runtime_generics: _, + cmp_eq: _, + get: _, + } = &options.body; + let span = ident.span(); + let mut type_attrs = attrs.clone(); + type_attrs.push(parse_quote_spanned! {span=> + #[allow(type_alias_bounds)] + }); + let type_param_ident = &generics.type_param.ident; + let syn_generics = Generics::from(generics); + ItemType { + attrs: type_attrs, + vis: vis.clone(), + type_token: *type_token, + ident: ident.clone(), + generics: syn_generics.clone(), + eq_token: *eq_token, + ty: parse_quote_spanned! {span=> + <#ty as ::fayalite::phantom_const::ReturnSelfUnchanged<#type_param_ident>>::Type + }, + semi_token: *semi_token, + } + .to_tokens(tokens); + let generics_accumulation_ident = + format_ident!("__{}__GenericsAccumulation", ident); + ItemStruct { + attrs: vec![ + common_derives(span), + parse_quote_spanned! {span=> + #[allow(non_camel_case_types)] + }, + ], + vis: vis.clone(), + struct_token: Token![struct](span), + ident: generics_accumulation_ident.clone(), + generics: Generics::default(), + fields: Fields::Unnamed(parse_quote_spanned! {span=> + (()) + }), + semi_token: Some(Token![;](span)), + } + .to_tokens(tokens); + quote_spanned! {span=> + #[allow(non_upper_case_globals, dead_code)] + #vis const #ident: #generics_accumulation_ident = #generics_accumulation_ident(()); + } + .to_tokens(tokens); + let mut wrapped_in_const = WrappedInConst::new(tokens, span); + let tokens = wrapped_in_const.inner(); + let (impl_generics, _type_generics, where_clause) = syn_generics.split_for_impl(); + let phantom_const_get_ty = &generics.type_param.phantom_const_get_bound.ty; + let index_output = if let Some(ty_is_dyn_size) = ty_is_dyn_size { + known_items::usize(ty_is_dyn_size.span).to_token_stream() + } else { + ty.to_token_stream() + }; + quote_spanned! {span=> + #[allow(non_upper_case_globals)] + #[automatically_derived] + impl #impl_generics ::fayalite::__std::ops::Index<#type_param_ident> + for #generics_accumulation_ident + #where_clause + { + type Output = #index_output; + + fn index(&self, __param: #type_param_ident) -> &Self::Output { + ::fayalite::phantom_const::type_alias_phantom_const_get_helper::<#phantom_const_get_ty, #index_output>( + __param, + #get_expr, + ) + } + } + } + .to_tokens(tokens); + } } } } pub(crate) fn hdl_type_alias_impl(item: ItemType) -> syn::Result { let item = ParsedTypeAlias::parse(item)?; - let outline_generated = item.options.body.outline_generated; + let outline_generated = match &item { + ParsedTypeAlias::TypeAlias { options, .. } + | ParsedTypeAlias::PhantomConstAccessor { options, .. } => options.body.outline_generated, + }; let mut contents = item.to_token_stream(); if outline_generated.is_some() { contents = crate::outline_generated(contents, "hdl-type-alias-"); diff --git a/crates/fayalite-proc-macros-impl/src/hdl_type_common.rs b/crates/fayalite-proc-macros-impl/src/hdl_type_common.rs index 1206f11..73acc39 100644 --- a/crates/fayalite-proc-macros-impl/src/hdl_type_common.rs +++ b/crates/fayalite-proc-macros-impl/src/hdl_type_common.rs @@ -27,6 +27,7 @@ crate::options! { NoStatic(no_static), NoRuntimeGenerics(no_runtime_generics), CmpEq(cmp_eq), + Get(get, Expr), } } @@ -2045,6 +2046,7 @@ pub(crate) mod known_items { impl_known_item!(::fayalite::int::Size); impl_known_item!(::fayalite::int::UInt); impl_known_item!(::fayalite::int::UIntType); + impl_known_item!(::fayalite::phantom_const::PhantomConstGet); impl_known_item!(::fayalite::reset::ResetType); impl_known_item!(::fayalite::ty::CanonicalType); impl_known_item!(::fayalite::ty::StaticType); diff --git a/crates/fayalite-proc-macros-impl/src/lib.rs b/crates/fayalite-proc-macros-impl/src/lib.rs index 13336fa..13ec7a2 100644 --- a/crates/fayalite-proc-macros-impl/src/lib.rs +++ b/crates/fayalite-proc-macros-impl/src/lib.rs @@ -76,6 +76,7 @@ mod kw { custom_keyword!(connect_inexact); custom_keyword!(custom_bounds); custom_keyword!(flip); + custom_keyword!(get); custom_keyword!(hdl); custom_keyword!(hdl_module); custom_keyword!(incomplete_wire); diff --git a/crates/fayalite/src/phantom_const.rs b/crates/fayalite/src/phantom_const.rs index b852056..9f25166 100644 --- a/crates/fayalite/src/phantom_const.rs +++ b/crates/fayalite/src/phantom_const.rs @@ -415,3 +415,71 @@ impl ToSimValueWithType for Phanto SimValue::into_canonical(SimValue::from_value(Self::from_canonical(ty), *self)) } } + +mod sealed { + pub trait Sealed {} +} + +pub trait PhantomConstGet: sealed::Sealed { + fn get(&self) -> Interned; +} + +impl>> + sealed::Sealed for This +{ +} + +impl>> + PhantomConstGet for This +{ + fn get(&self) -> Interned { + This::Target::get(&**self) + } +} + +macro_rules! impl_phantom_const_get { + ( + impl PhantomConstGet<$T:ident> for $ty:ty { + fn $get:ident(&$get_self:ident) -> _ $get_body:block + } + ) => { + impl<$T: ?Sized + PhantomConstValue> sealed::Sealed<$T> for $ty {} + + impl<$T: ?Sized + PhantomConstValue> PhantomConstGet<$T> for $ty { + fn $get(&$get_self) -> Interned<$T> $get_body + } + }; +} + +impl_phantom_const_get! { + impl PhantomConstGet for PhantomConst { + fn get(&self) -> _ { + PhantomConst::get(*self) + } + } +} + +impl_phantom_const_get! { + impl PhantomConstGet for Expr> { + fn get(&self) -> _ { + PhantomConst::get(Expr::ty(*self)) + } + } +} + +#[doc(hidden)] +pub trait ReturnSelfUnchanged { + type Type: ?Sized; +} + +impl ReturnSelfUnchanged for This { + type Type = This; +} + +#[doc(hidden)] +pub fn type_alias_phantom_const_get_helper( + param: impl PhantomConstGet, + get: impl FnOnce(Interned) -> R, +) -> &'static R { + Interned::into_inner(get(param.get()).intern_sized()) +} diff --git a/crates/fayalite/src/prelude.rs b/crates/fayalite/src/prelude.rs index 5bb4b77..4cc173e 100644 --- a/crates/fayalite/src/prelude.rs +++ b/crates/fayalite/src/prelude.rs @@ -27,7 +27,7 @@ pub use crate::{ Instance, Module, ModuleBuilder, annotate, connect, connect_any, incomplete_wire, instance, memory, memory_array, memory_with_init, reg_builder, wire, }, - phantom_const::PhantomConst, + phantom_const::{PhantomConst, PhantomConstGet}, platform::{DynPlatform, Platform, PlatformIOBuilder, peripherals}, reg::Reg, reset::{AsyncReset, Reset, SyncReset, ToAsyncReset, ToReset, ToSyncReset}, diff --git a/crates/fayalite/tests/hdl_types.rs b/crates/fayalite/tests/hdl_types.rs index 8802fd4..8742bb0 100644 --- a/crates/fayalite/tests/hdl_types.rs +++ b/crates/fayalite/tests/hdl_types.rs @@ -4,7 +4,6 @@ use fayalite::{ bundle::BundleType, enum_::EnumType, int::{BoolOrIntType, IntType}, - phantom_const::PhantomConst, prelude::*, ty::StaticType, }; @@ -197,3 +196,21 @@ check_bounds!(CheckBoundsTTT2<#[a, Type] A: BundleType +, #[b, Type] B: Type +, check_bounds!(CheckBoundsTTT3<#[a, Type] A: EnumType +, #[b, Type] B: Type +, #[c, Type] C: Type +>); check_bounds!(CheckBoundsTTT4<#[a, Type] A: IntType +, #[b, Type] B: Type +, #[c, Type] C: Type +>); check_bounds!(CheckBoundsTTT5<#[a, Type] A: StaticType +, #[b, Type] B: Type +, #[c, Type] C: Type +>); + +#[derive(Clone, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)] +pub struct MyPhantomConstInner { + pub a: usize, + pub b: UInt, +} + +#[hdl(outline_generated, get(|v| v.a))] +pub type GetA> = DynSize; + +#[hdl(outline_generated, get(|v| v.b))] +pub type GetB> = UInt; + +#[hdl(outline_generated, no_static)] +pub struct MyTypeWithPhantomConstParameter> { + pub a: ArrayType>, + pub b: HdlOption>, +}