Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
# 0.13

## Backwards-incompatible changes
- Remove `typle_for!`. Use `typle!`.

## Other changes
- Allow `typle!` macro with no range in singleton position.

# 0.12

## Backwards-incompatible changes
Expand Down
72 changes: 10 additions & 62 deletions src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1106,52 +1106,6 @@ impl TypleContext {
Ok(())
}

fn replace_typle_for_expr<'a>(
&'a self,
mac: &mut Macro,
state: &'a mut BlockState,
) -> syn::Result<impl Iterator<Item = syn::Result<Expr>> + 'a> {
let token_stream = std::mem::take(&mut mac.tokens);
let mut tokens = token_stream.into_iter();
let (pattern, range) = self.parse_pattern_range(&mut tokens)?;
let token_stream = tokens.collect::<TokenStream>();
let mut context = self.clone();
if let Some(ident) = &pattern {
context.constants.insert(ident.clone(), 0);
}
Ok(range
.zip_clone(token_stream)
.flat_map(move |(index, token_stream)| {
if let Some(ident) = &pattern {
*context.constants.get_mut(ident).unwrap() = index;
}
let token_stream = match context.evaluate_if(token_stream) {
Ok(Some(token_stream)) => token_stream,
Ok(None) => {
return None;
}
Err(e) => {
return Some(Err(e));
}
};
let mut expr = match syn::parse2::<Expr>(token_stream) {
Ok(expr) => expr,
Err(e) => return Some(Err(e)),
};
match context.replace_expr(&mut expr, state) {
Ok(()) => {
if let Expr::Verbatim(_token_stream) = expr {
// Verbatim causes typle to omit the component from the tuple
None
} else {
Some(Ok(expr))
}
}
Err(error) => Some(Err(error)),
}
}))
}

fn replace_typle_fold_expr(
&self,
mac: &mut Macro,
Expand Down Expand Up @@ -1262,25 +1216,19 @@ impl TypleContext {
attrs: &mut Vec<Attribute>,
state: &mut BlockState,
) -> syn::Result<Option<Expr>> {
// typle_for!(i in .. => Some<t[[i]]) -> (Some<t.0>, Some<t.1>)
// typle_for!(i in .. => t.to_string()) -> (t.0.to_string(), t.1.to_string())
// as opposed to
// Some(t) -> Some((t.0, t.1))
// t.to_string() -> (t.0, t.1).to_string()
self.replace_attrs(attrs)?;
if let Some(macro_name) = mac.path.get_ident() {
let default_span = macro_name.span();
if macro_name == "typle_for" {
let expr = {
let elems = self.replace_typle_for_expr(mac, state)?;
let tuple = syn::ExprTuple {
attrs: std::mem::take(attrs),
paren_token: token::Paren::default(),
elems: elems.collect::<syn::Result<_>>()?,
};
Expr::Tuple(tuple)
};
return Ok(Some(expr));
if macro_name == "typle" {
// This is outside a comma-separated sequence so only no-range form is accepted
// typle!(=> if T::LEN == 0 {} else {})
let token_stream = std::mem::take(&mut mac.tokens);
return self.expand_typle_macro_singleton(token_stream, |context, token_stream| {
let mut expr = syn::parse2::<Expr>(token_stream)?;
let mut state = BlockState::default();
context.replace_expr(&mut expr, &mut state)?;
Ok(Some(expr))
});
} else if macro_name == "typle_fold" {
let expr =
self.replace_typle_fold_expr(mac, std::mem::take(attrs), state, default_span)?;
Expand Down
143 changes: 9 additions & 134 deletions src/context/pat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,144 +142,19 @@ impl TypleContext {
fn replace_macro_pat(&self, m: &mut syn::PatMacro) -> syn::Result<Option<Pat>> {
self.replace_attrs(&mut m.attrs)?;
if let Some(macro_name) = m.mac.path.get_ident() {
if macro_name == "typle_for" {
let mut tuple = syn::PatTuple {
attrs: std::mem::take(&mut m.attrs),
paren_token: token::Paren::default(),
elems: Punctuated::new(),
};
if macro_name == "typle" {
// This is outside a comma-separated sequence so only no-range form is accepted
// typle!(=> if T::LEN == 0 {} else {})
let token_stream = std::mem::take(&mut m.mac.tokens);
let mut tokens = token_stream.into_iter();
let (pattern, range) = self.parse_pattern_range(&mut tokens)?;
if range.is_empty() {
return Ok(Some(Pat::Tuple(tuple)));
}
let token_stream = tokens.collect::<TokenStream>();
let body_span = token_stream.span();
match m.mac.delimiter {
MacroDelimiter::Paren(_) => {
let pat = Pat::parse_single.parse2(token_stream)?;
let mut context = self.clone();
if let Some(ident) = &pattern {
context.constants.insert(ident.clone(), 0);
}
for (index, mut component) in range.zip_clone(pat) {
if let Some(ident) = &pattern {
*context.constants.get_mut(ident).unwrap() = index;
}
let mut state = BlockState::default();
context.replace_pat(&mut component, &mut state)?;
tuple.elems.push(component);
}
}
MacroDelimiter::Brace(_) => {
let Ok(expr) = syn::parse2::<Expr>(token_stream) else {
return Err(syn::Error::new(
body_span,
"cannot parse, wrap types in `typle_pat!()`",
));
};
let mut context = self.clone();
context.const_if = true;
if let Some(ident) = &pattern {
context.constants.insert(ident.clone(), 0);
}
for (index, mut expr) in range.zip_clone(expr) {
if let Some(ident) = &pattern {
*context.constants.get_mut(ident).unwrap() = index;
}
let mut state = BlockState::default();
context.replace_expr(&mut expr, &mut state)?;
if let Expr::Verbatim(_) = expr {
// Verbatim omits the pattern from the tuple.
} else {
let mut pat = expr_to_pat(expr)?;
context.replace_pat(&mut pat, &mut state)?;
tuple.elems.push(pat);
}
}
}
MacroDelimiter::Bracket(_) => {
abort!(m, "expected parentheses or braces");
}
}
return Ok(Some(Pat::Tuple(tuple)));
return self.expand_typle_macro_singleton(token_stream, |context, token_stream| {
let mut pat = Pat::parse_single.parse2(token_stream)?;
let mut state = BlockState::default();
context.replace_pat(&mut pat, &mut state)?;
Ok(Some(pat))
});
}
}
m.mac.tokens = self.replace_macro_token_stream(std::mem::take(&mut m.mac.tokens))?;
Ok(None)
}
}

fn expr_to_pat(expr: Expr) -> syn::Result<Pat> {
let default_span = expr.span();
match expr {
Expr::Block(expr) => {
let mut iter = expr.block.stmts.into_iter();
let Some(stmt) = iter.next() else {
// empty block represents missing pattern
return Ok(Pat::Verbatim(TokenStream::new()));
};
if iter.next().is_some() {
return Err(syn::Error::new(
default_span,
"typle requires a block with a single pattern",
));
}
match stmt {
Stmt::Local(local) => Err(syn::Error::new(
local.span(),
"let statement not supported here",
)),
Stmt::Item(item) => Err(syn::Error::new(item.span(), "item not supported here")),
Stmt::Expr(expr, None) => expr_to_pat(expr),
Stmt::Expr(_, Some(semi)) => {
Err(syn::Error::new(semi.span(), "unexpected semicolon"))
}
Stmt::Macro(m) => match m.mac.path.get_ident() {
Some(ident) if ident == "typle_pat" => Pat::parse_single.parse2(m.mac.tokens),
_ => Ok(Pat::Macro(syn::PatMacro {
attrs: m.attrs,
mac: m.mac,
})),
},
}
}
Expr::Const(expr) => Ok(Pat::Const(expr)),
Expr::Infer(expr) => Ok(Pat::Wild(syn::PatWild {
attrs: expr.attrs,
underscore_token: expr.underscore_token,
})),
Expr::Lit(expr) => Ok(Pat::Lit(expr)),
Expr::Macro(expr) => match expr.mac.path.get_ident() {
Some(ident) if ident == "typle_pat" => Pat::parse_single.parse2(expr.mac.tokens),
_ => Ok(Pat::Macro(expr)),
},
Expr::Paren(expr) => Ok(Pat::Paren(syn::PatParen {
attrs: expr.attrs,
paren_token: expr.paren_token,
pat: Box::new(expr_to_pat(*expr.expr)?),
})),
Expr::Path(expr) => Ok(Pat::Path(expr)),
Expr::Reference(expr) => Ok(Pat::Reference(syn::PatReference {
attrs: expr.attrs,
and_token: expr.and_token,
mutability: expr.mutability,
pat: Box::new(expr_to_pat(*expr.expr)?),
})),
Expr::Tuple(expr) => Ok(Pat::Tuple(syn::PatTuple {
attrs: expr.attrs,
paren_token: expr.paren_token,
elems: expr
.elems
.into_iter()
.map(expr_to_pat)
.collect::<syn::Result<_>>()?,
})),
Expr::Verbatim(token_stream) => Ok(Pat::Verbatim(token_stream)),
_ => Err(syn::Error::new(
default_span,
"typle does not support this pattern",
)),
}
}
60 changes: 52 additions & 8 deletions src/context/shared.rs
Original file line number Diff line number Diff line change
Expand Up @@ -284,8 +284,10 @@ impl TypleContext {
Ok(())
}

pub(super) fn evaluate_if(&self, ts: TokenStream) -> syn::Result<Option<TokenStream>> {
let mut tokens = ts.into_iter();
pub(super) fn evaluate_if(
&self,
mut tokens: impl Iterator<Item = TokenTree>,
) -> syn::Result<Option<TokenStream>> {
match tokens.next() {
Some(TokenTree::Ident(ident)) if ident == "if" => {
let mut tokens = tokens.collect::<Vec<_>>();
Expand All @@ -302,9 +304,9 @@ impl TypleContext {
self.replace_expr(&mut cond, &mut state)?;
let b = evaluate_bool(&cond)?;
self.evaluate_if(if b {
group0.stream()
group0.stream().into_iter()
} else {
group1.stream()
group1.stream().into_iter()
})
}
Some(tt) => {
Expand All @@ -321,7 +323,7 @@ impl TypleContext {
self.replace_expr(&mut cond, &mut state)?;
let b = evaluate_bool(&cond)?;
if b {
self.evaluate_if(group1.stream())
self.evaluate_if(group1.stream().into_iter())
} else {
Ok(None)
}
Expand Down Expand Up @@ -385,7 +387,13 @@ impl TypleContext {
{
// Tuple::LEN or T::LEN
let Some(typle_len) = self.typle_len else {
abort!(ident2, "LEN not defined outside fn or impl");
abort!(
ident2,
format!(
"LEN only defined for fn or impl using {} bound",
self.typle_macro.trait_ident
)
);
};
return Ok(Some(Lit::Int(syn::LitInt::new(
&typle_len.to_string(),
Expand All @@ -402,7 +410,13 @@ impl TypleContext {
{
// Tuple::LAST or T::LAST
let Some(typle_len) = self.typle_len else {
abort!(ident2, "LAST not defined outside fn or impl");
abort!(
ident2,
format!(
"LAST only defined for fn or impl using {} bound",
self.typle_macro.trait_ident
)
);
};
if typle_len == 0 {
abort!(ident2, "LAST not defined when LEN == 0");
Expand Down Expand Up @@ -478,7 +492,7 @@ impl TypleContext {
if let Some(ident) = &pattern {
*context.constants.get_mut(ident).unwrap() = index;
}
match context.evaluate_if(token_stream) {
match context.evaluate_if(token_stream.into_iter()) {
Ok(Some(token_stream)) => match f(&context, token_stream) {
Ok(iter) => iter,
Err(err) => Replacements::Singleton(Err(err)),
Expand All @@ -489,4 +503,34 @@ impl TypleContext {
},
))
}

pub(super) fn expand_typle_macro_singleton<T, F>(
&self,
token_stream: TokenStream,
f: F,
) -> syn::Result<Option<T>>
where
T: 'static,
F: Fn(&TypleContext, TokenStream) -> syn::Result<Option<T>>,
{
let span = token_stream.span();
let mut tokens = token_stream.into_iter();
match tokens.next() {
Some(TokenTree::Punct(punct)) if punct.as_char() == '=' => match tokens.next() {
Some(TokenTree::Punct(punct)) if punct.as_char() == '>' => {
match self.evaluate_if(tokens)? {
Some(token_stream) => {
return f(&self, token_stream);
}
None => {
return Ok(None);
}
}
}
_ => {}
},
_ => {}
}
abort!(span, "Expected => at start of macro");
}
}
Loading