forked from libre-chip/fayalite
WIP: use HdlOption[the_type_var] or UInt[123 + n] for creating types
This commit is contained in:
parent
cd99dbc849
commit
5835b995a9
63 changed files with 13500 additions and 13210 deletions
|
@ -2,23 +2,20 @@
|
|||
// 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,
|
||||
},
|
||||
module::transform_body::{with_debug_clone_and_fold, Visitor},
|
||||
Errors, HdlAttr, PairsIterExt,
|
||||
};
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use quote::{ToTokens, TokenStreamExt};
|
||||
use quote::{format_ident, ToTokens, TokenStreamExt};
|
||||
use syn::{
|
||||
fold::{fold_arm, fold_expr_match, fold_pat, Fold},
|
||||
parse::Nothing,
|
||||
parse_quote_spanned,
|
||||
punctuated::{Pair, Punctuated},
|
||||
punctuated::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,
|
||||
Arm, Attribute, Expr, ExprMatch, FieldPat, Ident, Member, Pat, PatIdent, PatOr, PatParen,
|
||||
PatPath, PatRest, PatStruct, PatTupleStruct, PatWild, Path, PathSegment, Token, TypePath,
|
||||
};
|
||||
|
||||
with_debug_clone_and_fold! {
|
||||
|
@ -81,7 +78,7 @@ impl ToTokens for MatchPatWild {
|
|||
|
||||
with_debug_clone_and_fold! {
|
||||
struct MatchPatStructField<> {
|
||||
member: Member,
|
||||
field_name: Ident,
|
||||
colon_token: Option<Token![:]>,
|
||||
pat: MatchPatSimple,
|
||||
}
|
||||
|
@ -90,12 +87,19 @@ with_debug_clone_and_fold! {
|
|||
impl ToTokens for MatchPatStructField {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let Self {
|
||||
member,
|
||||
field_name,
|
||||
colon_token,
|
||||
pat,
|
||||
} = self;
|
||||
member.to_tokens(tokens);
|
||||
colon_token.to_tokens(tokens);
|
||||
field_name.to_tokens(tokens);
|
||||
if let (None, MatchPatSimple::Binding(MatchPatBinding { ident })) = (colon_token, pat) {
|
||||
if field_name == ident {
|
||||
return;
|
||||
}
|
||||
}
|
||||
colon_token
|
||||
.unwrap_or_else(|| Token))
|
||||
.to_tokens(tokens);
|
||||
pat.to_tokens(tokens);
|
||||
}
|
||||
}
|
||||
|
@ -108,8 +112,16 @@ impl MatchPatStructField {
|
|||
colon_token,
|
||||
pat,
|
||||
} = field_pat;
|
||||
let field_name = if let Member::Named(field_name) = member {
|
||||
field_name
|
||||
} else {
|
||||
state
|
||||
.errors
|
||||
.error(&member, "field name must not be a number");
|
||||
format_ident!("_{}", member)
|
||||
};
|
||||
Ok(Self {
|
||||
member,
|
||||
field_name,
|
||||
colon_token,
|
||||
pat: MatchPatSimple::parse(state, *pat)?,
|
||||
})
|
||||
|
@ -118,7 +130,7 @@ impl MatchPatStructField {
|
|||
|
||||
with_debug_clone_and_fold! {
|
||||
struct MatchPatStruct<> {
|
||||
resolved_path: Path,
|
||||
path: Path,
|
||||
brace_token: Brace,
|
||||
fields: Punctuated<MatchPatStructField, Token![,]>,
|
||||
rest: Option<Token![..]>,
|
||||
|
@ -128,12 +140,12 @@ with_debug_clone_and_fold! {
|
|||
impl ToTokens for MatchPatStruct {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let Self {
|
||||
resolved_path,
|
||||
path,
|
||||
brace_token,
|
||||
fields,
|
||||
rest,
|
||||
} = self;
|
||||
resolved_path.to_tokens(tokens);
|
||||
path.to_tokens(tokens);
|
||||
brace_token.surround(tokens, |tokens| {
|
||||
fields.to_tokens(tokens);
|
||||
rest.to_tokens(tokens);
|
||||
|
@ -141,6 +153,30 @@ impl ToTokens for MatchPatStruct {
|
|||
}
|
||||
}
|
||||
|
||||
with_debug_clone_and_fold! {
|
||||
struct MatchPatEnumVariant<> {
|
||||
variant_path: Path,
|
||||
enum_path: Path,
|
||||
variant_name: Ident,
|
||||
field: Option<(Paren, MatchPatSimple)>,
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for MatchPatEnumVariant {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let Self {
|
||||
variant_path,
|
||||
enum_path: _,
|
||||
variant_name: _,
|
||||
field,
|
||||
} = self;
|
||||
variant_path.to_tokens(tokens);
|
||||
if let Some((paren_token, field)) = field {
|
||||
paren_token.surround(tokens, |tokens| field.to_tokens(tokens));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum MatchPatSimple {
|
||||
Paren(MatchPatParen<MatchPatSimple>),
|
||||
|
@ -169,21 +205,70 @@ impl ToTokens for MatchPatSimple {
|
|||
}
|
||||
}
|
||||
|
||||
fn is_pat_ident_a_struct_or_enum_name(ident: &Ident) -> bool {
|
||||
ident
|
||||
.to_string()
|
||||
.starts_with(|ch: char| ch.is_ascii_uppercase())
|
||||
struct EnumPath {
|
||||
variant_path: Path,
|
||||
enum_path: Path,
|
||||
variant_name: Ident,
|
||||
}
|
||||
|
||||
fn parse_enum_path(variant_path: TypePath) -> Result<EnumPath, TypePath> {
|
||||
let TypePath {
|
||||
qself: None,
|
||||
path: variant_path,
|
||||
} = variant_path
|
||||
else {
|
||||
return Err(variant_path);
|
||||
};
|
||||
if variant_path.is_ident("HdlNone") || variant_path.is_ident("HdlSome") {
|
||||
let ident = variant_path.get_ident().unwrap();
|
||||
return Ok(EnumPath {
|
||||
enum_path: parse_quote_spanned! {ident.span()=>
|
||||
::fayalite::enum_::HdlOption::<_>
|
||||
},
|
||||
variant_name: ident.clone(),
|
||||
variant_path,
|
||||
});
|
||||
}
|
||||
if variant_path.segments.len() < 2 {
|
||||
return Err(TypePath {
|
||||
qself: None,
|
||||
path: variant_path,
|
||||
});
|
||||
}
|
||||
let mut enum_path = variant_path.clone();
|
||||
let PathSegment {
|
||||
ident: variant_name,
|
||||
arguments,
|
||||
} = enum_path.segments.pop().unwrap().into_value();
|
||||
if !arguments.is_none() {
|
||||
return Err(TypePath {
|
||||
qself: None,
|
||||
path: variant_path,
|
||||
});
|
||||
}
|
||||
enum_path.segments.pop_punct();
|
||||
Ok(EnumPath {
|
||||
variant_path,
|
||||
enum_path,
|
||||
variant_name,
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_enum_ident(ident: Ident) -> Result<EnumPath, Ident> {
|
||||
parse_enum_path(TypePath {
|
||||
qself: None,
|
||||
path: ident.into(),
|
||||
})
|
||||
.map_err(|p| p.path.segments.into_iter().next().unwrap().ident)
|
||||
}
|
||||
|
||||
trait ParseMatchPat: Sized {
|
||||
fn simple(v: MatchPatSimple) -> Self;
|
||||
fn or(v: MatchPatOr<Self>) -> Self;
|
||||
fn paren(v: MatchPatParen<Self>) -> Self;
|
||||
fn struct_(
|
||||
state: &mut HdlMatchParseState<'_>,
|
||||
v: MatchPatStruct,
|
||||
struct_error_spanned: &dyn ToTokens,
|
||||
) -> Result<Self, ()>;
|
||||
fn struct_(state: &mut HdlMatchParseState<'_>, v: MatchPatStruct) -> Result<Self, ()>;
|
||||
fn enum_variant(state: &mut HdlMatchParseState<'_>, v: MatchPatEnumVariant)
|
||||
-> Result<Self, ()>;
|
||||
fn parse(state: &mut HdlMatchParseState<'_>, pat: Pat) -> Result<Self, ()> {
|
||||
match pat {
|
||||
Pat::Ident(PatIdent {
|
||||
|
@ -208,26 +293,23 @@ trait ParseMatchPat: Sized {
|
|||
.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_(
|
||||
match parse_enum_ident(ident) {
|
||||
Ok(EnumPath {
|
||||
variant_path,
|
||||
enum_path,
|
||||
variant_name,
|
||||
}) => Self::enum_variant(
|
||||
state,
|
||||
MatchPatStruct {
|
||||
resolved_path,
|
||||
brace_token: Brace(ident_span),
|
||||
fields: Punctuated::new(),
|
||||
rest: None,
|
||||
MatchPatEnumVariant {
|
||||
variant_path,
|
||||
enum_path,
|
||||
variant_name,
|
||||
field: None,
|
||||
},
|
||||
&ident,
|
||||
)
|
||||
} else {
|
||||
Ok(Self::simple(MatchPatSimple::Binding(MatchPatBinding {
|
||||
),
|
||||
Err(ident) => Ok(Self::simple(MatchPatSimple::Binding(MatchPatBinding {
|
||||
ident,
|
||||
})))
|
||||
}))),
|
||||
}
|
||||
}
|
||||
Pat::Or(PatOr {
|
||||
|
@ -254,18 +336,21 @@ trait ParseMatchPat: Sized {
|
|||
qself,
|
||||
path,
|
||||
}) => {
|
||||
let path = TypePath { qself, path };
|
||||
let path_span = path.span();
|
||||
let resolved_path = state.resolve_enum_struct_path(path.clone())?;
|
||||
Self::struct_(
|
||||
let EnumPath {
|
||||
variant_path,
|
||||
enum_path,
|
||||
variant_name,
|
||||
} = parse_enum_path(TypePath { qself, path }).map_err(|path| {
|
||||
state.errors.error(path, "unsupported enum variant path");
|
||||
})?;
|
||||
Self::enum_variant(
|
||||
state,
|
||||
MatchPatStruct {
|
||||
resolved_path,
|
||||
brace_token: Brace(path_span),
|
||||
fields: Punctuated::new(),
|
||||
rest: None,
|
||||
MatchPatEnumVariant {
|
||||
variant_path,
|
||||
enum_path,
|
||||
variant_name,
|
||||
field: None,
|
||||
},
|
||||
&path,
|
||||
)
|
||||
}
|
||||
Pat::Struct(PatStruct {
|
||||
|
@ -282,12 +367,16 @@ trait ParseMatchPat: Sized {
|
|||
MatchPatStructField::parse(state, field_pat).ok()
|
||||
})
|
||||
.collect();
|
||||
let path = TypePath { qself, path };
|
||||
let resolved_path = state.resolve_enum_struct_path(path.clone())?;
|
||||
if qself.is_some() {
|
||||
state
|
||||
.errors
|
||||
.error(TypePath { qself, path }, "unsupported struct path");
|
||||
return Err(());
|
||||
}
|
||||
Self::struct_(
|
||||
state,
|
||||
MatchPatStruct {
|
||||
resolved_path,
|
||||
path,
|
||||
brace_token,
|
||||
fields,
|
||||
rest: rest.map(
|
||||
|
@ -297,7 +386,6 @@ trait ParseMatchPat: Sized {
|
|||
}| dot2_token,
|
||||
),
|
||||
},
|
||||
&path,
|
||||
)
|
||||
}
|
||||
Pat::TupleStruct(PatTupleStruct {
|
||||
|
@ -307,45 +395,44 @@ trait ParseMatchPat: Sized {
|
|||
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),
|
||||
pat,
|
||||
};
|
||||
Some(Pair::new(field, punct))
|
||||
let EnumPath {
|
||||
variant_path,
|
||||
enum_path,
|
||||
variant_name,
|
||||
} = parse_enum_path(TypePath { qself, path }).map_err(|path| {
|
||||
state.errors.error(path, "unsupported enum variant path");
|
||||
})?;
|
||||
if elems.is_empty() {
|
||||
let mut tokens = TokenStream::new();
|
||||
paren_token.surround(&mut tokens, |_| {});
|
||||
state.errors.error(
|
||||
tokens,
|
||||
"field-less enum variants must not be matched using parenthesis",
|
||||
);
|
||||
}
|
||||
if elems.len() != 1 {
|
||||
state.errors.error(
|
||||
variant_path,
|
||||
"enum variant pattern must have exactly one field",
|
||||
);
|
||||
return Err(());
|
||||
}
|
||||
let field = elems.pop().unwrap().into_value();
|
||||
let field = if let Pat::Rest(rest) = field {
|
||||
MatchPatSimple::Wild(MatchPatWild {
|
||||
underscore_token: Token),
|
||||
})
|
||||
.collect();
|
||||
let path = TypePath { qself, path };
|
||||
let resolved_path = state.resolve_enum_struct_path(path.clone())?;
|
||||
Self::struct_(
|
||||
} else {
|
||||
MatchPatSimple::parse(state, field)?
|
||||
};
|
||||
Self::enum_variant(
|
||||
state,
|
||||
MatchPatStruct {
|
||||
resolved_path,
|
||||
brace_token: Brace {
|
||||
span: paren_token.span,
|
||||
},
|
||||
fields,
|
||||
rest,
|
||||
MatchPatEnumVariant {
|
||||
variant_path,
|
||||
enum_path,
|
||||
variant_name,
|
||||
field: Some((paren_token, field)),
|
||||
},
|
||||
&path,
|
||||
)
|
||||
}
|
||||
Pat::Rest(_) => {
|
||||
|
@ -387,14 +474,21 @@ impl ParseMatchPat for MatchPatSimple {
|
|||
Self::Paren(v)
|
||||
}
|
||||
|
||||
fn struct_(
|
||||
fn struct_(state: &mut HdlMatchParseState<'_>, v: MatchPatStruct) -> Result<Self, ()> {
|
||||
state.errors.error(
|
||||
v.path,
|
||||
"matching structs is not yet implemented inside structs/enums in #[hdl] patterns",
|
||||
);
|
||||
Err(())
|
||||
}
|
||||
|
||||
fn enum_variant(
|
||||
state: &mut HdlMatchParseState<'_>,
|
||||
_v: MatchPatStruct,
|
||||
struct_error_spanned: &dyn ToTokens,
|
||||
v: MatchPatEnumVariant,
|
||||
) -> Result<Self, ()> {
|
||||
state.errors.error(
|
||||
struct_error_spanned,
|
||||
"not yet implemented inside structs/enums in #[hdl] patterns",
|
||||
v.variant_path,
|
||||
"matching enum variants is not yet implemented inside structs/enums in #[hdl] patterns",
|
||||
);
|
||||
Err(())
|
||||
}
|
||||
|
@ -406,6 +500,7 @@ enum MatchPat {
|
|||
Or(MatchPatOr<MatchPat>),
|
||||
Paren(MatchPatParen<MatchPat>),
|
||||
Struct(MatchPatStruct),
|
||||
EnumVariant(MatchPatEnumVariant),
|
||||
}
|
||||
|
||||
impl_fold! {
|
||||
|
@ -414,6 +509,7 @@ impl_fold! {
|
|||
Or(MatchPatOr<MatchPat>),
|
||||
Paren(MatchPatParen<MatchPat>),
|
||||
Struct(MatchPatStruct),
|
||||
EnumVariant(MatchPatEnumVariant),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -430,13 +526,16 @@ impl ParseMatchPat for MatchPat {
|
|||
Self::Paren(v)
|
||||
}
|
||||
|
||||
fn struct_(
|
||||
_state: &mut HdlMatchParseState<'_>,
|
||||
v: MatchPatStruct,
|
||||
_struct_error_spanned: &dyn ToTokens,
|
||||
) -> Result<Self, ()> {
|
||||
fn struct_(_state: &mut HdlMatchParseState<'_>, v: MatchPatStruct) -> Result<Self, ()> {
|
||||
Ok(Self::Struct(v))
|
||||
}
|
||||
|
||||
fn enum_variant(
|
||||
_state: &mut HdlMatchParseState<'_>,
|
||||
v: MatchPatEnumVariant,
|
||||
) -> Result<Self, ()> {
|
||||
Ok(Self::EnumVariant(v))
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for MatchPat {
|
||||
|
@ -446,6 +545,7 @@ impl ToTokens for MatchPat {
|
|||
Self::Or(v) => v.to_tokens(tokens),
|
||||
Self::Paren(v) => v.to_tokens(tokens),
|
||||
Self::Struct(v) => v.to_tokens(tokens),
|
||||
Self::EnumVariant(v) => v.to_tokens(tokens),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -511,23 +611,58 @@ impl Fold for RewriteAsCheckMatch {
|
|||
i.colon_token = Some(Token));
|
||||
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 {}
|
||||
fn fold_pat(&mut self, pat: Pat) -> Pat {
|
||||
match pat {
|
||||
Pat::Ident(mut pat_ident) => match parse_enum_ident(pat_ident.ident) {
|
||||
Ok(EnumPath {
|
||||
variant_path: _,
|
||||
enum_path,
|
||||
variant_name,
|
||||
}) => parse_quote_spanned! {self.span=>
|
||||
__MatchTy::<#enum_path>::#variant_name {}
|
||||
},
|
||||
Err(ident) => {
|
||||
pat_ident.ident = ident;
|
||||
Pat::Ident(self.fold_pat_ident(pat_ident))
|
||||
}
|
||||
}
|
||||
_ => fold_pat(self, i),
|
||||
},
|
||||
Pat::TupleStruct(PatTupleStruct {
|
||||
attrs,
|
||||
qself,
|
||||
path,
|
||||
paren_token,
|
||||
elems,
|
||||
}) => match parse_enum_path(TypePath { qself, path }) {
|
||||
Ok(EnumPath {
|
||||
variant_path: _,
|
||||
enum_path,
|
||||
variant_name,
|
||||
}) => {
|
||||
let path = parse_quote_spanned! {self.span=>
|
||||
__MatchTy::<#enum_path>::#variant_name
|
||||
};
|
||||
let elems = Punctuated::from_iter(
|
||||
elems.into_pairs().map_pair_value(|p| fold_pat(self, p)),
|
||||
);
|
||||
Pat::TupleStruct(PatTupleStruct {
|
||||
attrs,
|
||||
qself: None,
|
||||
path,
|
||||
paren_token,
|
||||
elems,
|
||||
})
|
||||
}
|
||||
Err(TypePath { qself, path }) => {
|
||||
Pat::TupleStruct(self.fold_pat_tuple_struct(PatTupleStruct {
|
||||
attrs,
|
||||
qself,
|
||||
path,
|
||||
paren_token,
|
||||
elems,
|
||||
}))
|
||||
}
|
||||
},
|
||||
_ => fold_pat(self, pat),
|
||||
}
|
||||
}
|
||||
fn fold_pat_ident(&mut self, mut i: PatIdent) -> PatIdent {
|
||||
|
@ -556,26 +691,9 @@ impl Fold for RewriteAsCheckMatch {
|
|||
|
||||
struct HdlMatchParseState<'a> {
|
||||
errors: &'a mut Errors,
|
||||
span: Span,
|
||||
}
|
||||
|
||||
impl HdlMatchParseState<'_> {
|
||||
fn resolve_enum_struct_path(&mut self, path: TypePath) -> Result<Path, ()> {
|
||||
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 {
|
||||
impl Visitor<'_> {
|
||||
pub(crate) fn process_hdl_match(
|
||||
&mut self,
|
||||
_hdl_attr: HdlAttr<Nothing>,
|
||||
|
@ -593,22 +711,20 @@ impl Visitor {
|
|||
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=>
|
||||
let expr = quote::quote_spanned! {span=>
|
||||
{
|
||||
type __MatchTy<V> =
|
||||
<<V as ::fayalite::expr::ToExpr>::Type as ::fayalite::ty::Type>::MatchVariant;
|
||||
type __MatchTy<T> = <T 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) {
|
||||
for __match_variant in ::fayalite::module::match_(__match_expr) {
|
||||
let (__match_variant, __scope) =
|
||||
::fayalite::ty::MatchVariantAndInactiveScope::match_activate_scope(
|
||||
__match_variant,
|
||||
|
@ -618,6 +734,8 @@ impl Visitor {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
println!("{}", expr);
|
||||
syn::parse2(expr).unwrap()
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue