// SPDX-License-Identifier: LGPL-3.0-or-later // See Notices.txt for copyright information use crate::{ fold::impl_fold, module::transform_body::{ expand_aggregate_literals::{AggregateLiteralOptions, StructOrEnumPath}, with_debug_clone_and_fold, Visitor, }, Errors, HdlAttr, }; use proc_macro2::{Span, TokenStream}; use quote::{ToTokens, TokenStreamExt}; use syn::{ fold::{fold_arm, fold_expr_match, fold_pat, Fold}, parse::Nothing, parse_quote_spanned, punctuated::{Pair, Punctuated}, spanned::Spanned, token::{Brace, Paren}, Arm, Attribute, Expr, ExprMatch, FieldPat, Ident, Index, Member, Pat, PatIdent, PatOr, PatParen, PatPath, PatRest, PatStruct, PatTupleStruct, PatWild, Path, Token, TypePath, }; with_debug_clone_and_fold! { struct MatchPatBinding<> { ident: Ident, } } impl ToTokens for MatchPatBinding { fn to_tokens(&self, tokens: &mut TokenStream) { let Self { ident } = self; ident.to_tokens(tokens); } } with_debug_clone_and_fold! { struct MatchPatParen

{ paren_token: Paren, pat: Box

, } } impl ToTokens for MatchPatParen

{ fn to_tokens(&self, tokens: &mut TokenStream) { let Self { paren_token, pat } = self; paren_token.surround(tokens, |tokens| pat.to_tokens(tokens)); } } with_debug_clone_and_fold! { struct MatchPatOr

{ leading_vert: Option, cases: Punctuated, } } impl ToTokens for MatchPatOr

{ fn to_tokens(&self, tokens: &mut TokenStream) { let Self { leading_vert, cases, } = self; leading_vert.to_tokens(tokens); cases.to_tokens(tokens); } } with_debug_clone_and_fold! { struct MatchPatWild<> { underscore_token: Token![_], } } impl ToTokens for MatchPatWild { fn to_tokens(&self, tokens: &mut TokenStream) { let Self { underscore_token } = self; underscore_token.to_tokens(tokens); } } with_debug_clone_and_fold! { struct MatchPatStructField<> { member: Member, colon_token: Option, pat: MatchPatSimple, } } impl ToTokens for MatchPatStructField { fn to_tokens(&self, tokens: &mut TokenStream) { let Self { member, colon_token, pat, } = self; member.to_tokens(tokens); colon_token.to_tokens(tokens); pat.to_tokens(tokens); } } impl MatchPatStructField { fn parse(state: &mut HdlMatchParseState<'_>, field_pat: FieldPat) -> Result { let FieldPat { attrs: _, member, colon_token, pat, } = field_pat; Ok(Self { member, colon_token, pat: MatchPatSimple::parse(state, *pat)?, }) } } with_debug_clone_and_fold! { struct MatchPatStruct<> { resolved_path: Path, brace_token: Brace, fields: Punctuated, rest: Option, } } impl ToTokens for MatchPatStruct { fn to_tokens(&self, tokens: &mut TokenStream) { let Self { resolved_path, brace_token, fields, rest, } = self; resolved_path.to_tokens(tokens); brace_token.surround(tokens, |tokens| { fields.to_tokens(tokens); rest.to_tokens(tokens); }) } } #[derive(Debug, Clone)] enum MatchPatSimple { Paren(MatchPatParen), Or(MatchPatOr), Binding(MatchPatBinding), Wild(MatchPatWild), } impl_fold! { enum MatchPatSimple<> { Paren(MatchPatParen), Or(MatchPatOr), Binding(MatchPatBinding), Wild(MatchPatWild), } } impl ToTokens for MatchPatSimple { fn to_tokens(&self, tokens: &mut TokenStream) { match self { Self::Or(v) => v.to_tokens(tokens), Self::Paren(v) => v.to_tokens(tokens), Self::Binding(v) => v.to_tokens(tokens), Self::Wild(v) => v.to_tokens(tokens), } } } fn is_pat_ident_a_struct_or_enum_name(ident: &Ident) -> bool { ident .to_string() .starts_with(|ch: char| ch.is_ascii_uppercase()) } trait ParseMatchPat: Sized { fn simple(v: MatchPatSimple) -> Self; fn or(v: MatchPatOr) -> Self; fn paren(v: MatchPatParen) -> Self; fn struct_( state: &mut HdlMatchParseState<'_>, v: MatchPatStruct, struct_error_spanned: &dyn ToTokens, ) -> Result; fn parse(state: &mut HdlMatchParseState<'_>, pat: Pat) -> Result { match pat { Pat::Ident(PatIdent { attrs: _, by_ref, mutability, ident, subpat, }) => { if let Some(by_ref) = by_ref { state .errors .error(by_ref, "ref not allowed in #[hdl] patterns"); } if let Some(mutability) = mutability { state .errors .error(mutability, "mut not allowed in #[hdl] patterns"); } if let Some((at_token, _)) = subpat { state .errors .error(at_token, "@ not allowed in #[hdl] patterns"); } if is_pat_ident_a_struct_or_enum_name(&ident) { let ident_span = ident.span(); let resolved_path = state.resolve_enum_struct_path(TypePath { qself: None, path: ident.clone().into(), })?; Self::struct_( state, MatchPatStruct { resolved_path, brace_token: Brace(ident_span), fields: Punctuated::new(), rest: None, }, &ident, ) } else { Ok(Self::simple(MatchPatSimple::Binding(MatchPatBinding { ident, }))) } } Pat::Or(PatOr { attrs: _, leading_vert, cases, }) => Ok(Self::or(MatchPatOr { leading_vert, cases: cases .into_pairs() .filter_map(|pair| { let (pat, punct) = pair.into_tuple(); let pat = Self::parse(state, pat).ok()?; Some(Pair::new(pat, punct)) }) .collect(), })), Pat::Paren(PatParen { attrs: _, paren_token, pat, }) => Ok(Self::paren(MatchPatParen { paren_token, pat: Box::new(Self::parse(state, *pat)?), })), Pat::Path(PatPath { attrs: _, qself, path, }) => { let path = TypePath { qself, path }; let path_span = path.span(); let resolved_path = state.resolve_enum_struct_path(path.clone())?; Self::struct_( state, MatchPatStruct { resolved_path, brace_token: Brace(path_span), fields: Punctuated::new(), rest: None, }, &path, ) } Pat::Struct(PatStruct { attrs: _, qself, path, brace_token, fields, rest, }) => { let fields = fields .into_pairs() .filter_map(|pair| { let (field_pat, punct) = pair.into_tuple(); let field_pat = MatchPatStructField::parse(state, field_pat).ok()?; Some(Pair::new(field_pat, punct)) }) .collect(); let path = TypePath { qself, path }; let resolved_path = state.resolve_enum_struct_path(path.clone())?; Self::struct_( state, MatchPatStruct { resolved_path, brace_token, fields, rest: rest.map( |PatRest { attrs: _, dot2_token, }| dot2_token, ), }, &path, ) } Pat::TupleStruct(PatTupleStruct { attrs: _, qself, path, paren_token, mut elems, }) => { let rest = if let Some(&Pat::Rest(PatRest { attrs: _, dot2_token, })) = elems.last() { elems.pop(); Some(dot2_token) } else { None }; let fields = elems .into_pairs() .enumerate() .filter_map(|(index, pair)| { let (pat, punct) = pair.into_tuple(); let pat = MatchPatSimple::parse(state, pat).ok()?; let mut index = Index::from(index); index.span = state.span; let field = MatchPatStructField { member: index.into(), colon_token: Some(Token![:](state.span)), pat, }; Some(Pair::new(field, punct)) }) .collect(); let path = TypePath { qself, path }; let resolved_path = state.resolve_enum_struct_path(path.clone())?; Self::struct_( state, MatchPatStruct { resolved_path, brace_token: Brace { span: paren_token.span, }, fields, rest, }, &path, ) } Pat::Rest(_) => { state .errors .error(pat, "not allowed here in #[hdl] patterns"); Err(()) } Pat::Wild(PatWild { attrs: _, underscore_token, }) => Ok(Self::simple(MatchPatSimple::Wild(MatchPatWild { underscore_token, }))), Pat::Tuple(_) | Pat::Slice(_) | Pat::Const(_) | Pat::Lit(_) | Pat::Range(_) => { state .errors .error(pat, "not yet implemented in #[hdl] patterns"); Err(()) } _ => { state.errors.error(pat, "not allowed in #[hdl] patterns"); Err(()) } } } } impl ParseMatchPat for MatchPatSimple { fn simple(v: MatchPatSimple) -> Self { v } fn or(v: MatchPatOr) -> Self { Self::Or(v) } fn paren(v: MatchPatParen) -> Self { Self::Paren(v) } fn struct_( state: &mut HdlMatchParseState<'_>, _v: MatchPatStruct, struct_error_spanned: &dyn ToTokens, ) -> Result { state.errors.error( struct_error_spanned, "not yet implemented inside structs/enums in #[hdl] patterns", ); Err(()) } } #[derive(Debug, Clone)] enum MatchPat { Simple(MatchPatSimple), Or(MatchPatOr), Paren(MatchPatParen), Struct(MatchPatStruct), } impl_fold! { enum MatchPat<> { Simple(MatchPatSimple), Or(MatchPatOr), Paren(MatchPatParen), Struct(MatchPatStruct), } } impl ParseMatchPat for MatchPat { fn simple(v: MatchPatSimple) -> Self { Self::Simple(v) } fn or(v: MatchPatOr) -> Self { Self::Or(v) } fn paren(v: MatchPatParen) -> Self { Self::Paren(v) } fn struct_( _state: &mut HdlMatchParseState<'_>, v: MatchPatStruct, _struct_error_spanned: &dyn ToTokens, ) -> Result { Ok(Self::Struct(v)) } } impl ToTokens for MatchPat { fn to_tokens(&self, tokens: &mut TokenStream) { match self { Self::Simple(v) => v.to_tokens(tokens), Self::Or(v) => v.to_tokens(tokens), Self::Paren(v) => v.to_tokens(tokens), Self::Struct(v) => v.to_tokens(tokens), } } } with_debug_clone_and_fold! { struct MatchArm<> { attrs: Vec, pat: MatchPat, fat_arrow_token: Token![=>], body: Box, comma: Option, } } impl MatchArm { fn parse(state: &mut HdlMatchParseState<'_>, arm: Arm) -> Result { let Arm { attrs, pat, guard, fat_arrow_token, body, comma, } = arm; if let Some((if_, _)) = guard { state .errors .error(if_, "#[hdl] match arm if clauses are not implemented"); } Ok(Self { attrs, pat: MatchPat::parse(state, pat)?, fat_arrow_token, body, comma, }) } } impl ToTokens for MatchArm { fn to_tokens(&self, tokens: &mut TokenStream) { let Self { attrs, pat, fat_arrow_token, body, comma, } = self; tokens.append_all(attrs); pat.to_tokens(tokens); fat_arrow_token.to_tokens(tokens); body.to_tokens(tokens); comma.to_tokens(tokens); } } struct RewriteAsCheckMatch { span: Span, } impl Fold for RewriteAsCheckMatch { fn fold_field_pat(&mut self, mut i: FieldPat) -> FieldPat { i.colon_token = Some(Token![:](i.member.span())); i } fn fold_pat(&mut self, i: Pat) -> Pat { match i { Pat::Ident(PatIdent { attrs, by_ref, mutability, ident, subpat: None, }) if is_pat_ident_a_struct_or_enum_name(&ident) => { parse_quote_spanned! {ident.span()=> #(#attrs)* #by_ref #mutability #ident {} } } _ => fold_pat(self, i), } } fn fold_pat_ident(&mut self, mut i: PatIdent) -> PatIdent { i.by_ref = Some(Token![ref](i.ident.span())); i.mutability = None; i } fn fold_arm(&mut self, mut i: Arm) -> Arm { i.body = parse_quote_spanned! {self.span=> match __infallible {} }; i.comma.get_or_insert_with(|| Token![,](self.span)); fold_arm(self, i) } fn fold_expr_match(&mut self, mut i: ExprMatch) -> ExprMatch { i.expr = parse_quote_spanned! {self.span=> __match_value }; fold_expr_match(self, i) } fn fold_expr(&mut self, i: Expr) -> Expr { // don't recurse into expressions i } } struct HdlMatchParseState<'a> { errors: &'a mut Errors, span: Span, } impl HdlMatchParseState<'_> { fn resolve_enum_struct_path(&mut self, path: TypePath) -> Result { let StructOrEnumPath { ty, variant } = StructOrEnumPath::new(self.errors, path, &AggregateLiteralOptions::default())?; Ok(if let Some((_variant_path, variant_name)) = variant { parse_quote_spanned! {self.span=> __MatchTy::<#ty>::#variant_name } } else { parse_quote_spanned! {self.span=> __MatchTy::<#ty> } }) } } impl Visitor { pub(crate) fn process_hdl_match( &mut self, _hdl_attr: HdlAttr, expr_match: ExprMatch, ) -> Expr { let span = expr_match.match_token.span(); let check_match = RewriteAsCheckMatch { span }.fold_expr_match(expr_match.clone()); let ExprMatch { attrs: _, match_token, expr, brace_token: _, arms, } = expr_match; self.require_normal_module(match_token); let mut state = HdlMatchParseState { errors: &mut self.errors, span, }; let arms = Vec::from_iter( arms.into_iter() .filter_map(|arm| MatchArm::parse(&mut state, arm).ok()), ); parse_quote_spanned! {span=> { type __MatchTy = <::Type as ::fayalite::ty::Type>::MatchVariant; let __match_expr = ::fayalite::expr::ToExpr::to_expr(&(#expr)); ::fayalite::expr::check_match_expr(__match_expr, |__match_value, __infallible| { #[allow(unused_variables)] #check_match }); for __match_variant in m.match_(__match_expr) { let (__match_variant, __scope) = ::fayalite::ty::MatchVariantAndInactiveScope::match_activate_scope(__match_variant); #match_token __match_variant { #(#arms)* } } } } } }