use crate::ParseError;
use crate::block::stmt;
use crate::parser::Parser;
use crate::signature::can_end_a_type;
use crate::token::token_text;
use voltaic_ast::Expr;
use voltaic_ast::Item;
use voltaic_ast::Pattern;
use voltaic_ast::Stmt;
use voltaic_ast::where_cycle;
use voltaic_diag::Code;
use voltaic_lexer::Keyword;
use voltaic_lexer::TokenKind;
pub(crate) fn where_entry_is_a_function(p: &Parser) -> bool {
let named = match p.peek().kind {
TokenKind::Ident(_) => true,
_ => false,
};
if !named {
return false;
}
let Some(next) = p.peek_at(1) else {
return p.reaches_a_binding_eq();
};
match &next.kind {
TokenKind::Colon => !p.reaches_a_binding_eq(),
TokenKind::Eq | TokenKind::LBrace => false,
_ => p.reaches_a_binding_eq(),
}
}
pub(crate) fn where_group(entries: &[Stmt], params: &[Pattern]) -> Result<(), ParseError> {
let bound: Vec<String> = params.iter().flat_map(Pattern::binders).collect();
for entry in entries {
match &entry.expr {
Expr::Item { .. } => {
continue;
}
Expr::Binding(b) => {
for name in b.pattern.binders() {
if bound.contains(&name) {
let message = format!(
"`{}` is already a parameter of this equation, and a `where` entry may not shadow one",
name
);
return Err(ParseError::with_help(
Code::WhereGroupShape,
message,
"a `where` group is sorted into dependency order, so a name that means the parameter in one entry and the local in another would depend on where the sort put them — rename the local",
b.span,
));
}
}
}
other => {
return Err(ParseError::with_help(
Code::WhereGroupShape,
"a `where` entry defines a name, so it is a binding or a function declaration and nothing else",
"an expression evaluated for its effect belongs in the body: a `where` group is a set of definitions and is emitted in dependency order, so an entry with an effect has no position to be emitted at",
other.span(),
));
}
};
}
if let Some(names) = where_cycle(entries) {
let joined = names.join(", ");
let message = format!(
"these `where` bindings depend on each other in a cycle: {}",
joined
);
let blame = entries
.iter()
.find(|e| {
e.where_entry_name()
.is_some_and(|binder| names.contains(&binder))
})
.map_or((0, 0), |e| e.span);
return Err(ParseError::with_help(
Code::WhereGroupShape,
message,
"Rust `let` bindings are evaluated in order, so a cycle among values has no order to emit. Haskell accepts this and diverges; give one of them a parameter, which makes it a nested function and lets the cycle be recursion",
blame,
));
}
Ok(())
}
pub(super) fn where_locals(params: &[Pattern], p: &mut Parser) -> Result<Vec<Stmt>, ParseError> {
let opens = p.check(&TokenKind::Indent)
&& p
.peek_at(1)
.is_some_and(|t| t.kind == TokenKind::Kw(Keyword::Where));
if !opens {
return Ok(Vec::new());
}
p.advance();
p.advance();
p.newline_with_trailing("expected a line break after `where`")?;
p.expect_with(
Code::EmptyBody,
&TokenKind::Indent,
"expected indented `where` bindings",
)?;
let mut locals = Vec::new();
loop {
p.skip_separators();
if p.check(&TokenKind::Dedent) || p.at_eof() {
break;
}
if where_entry_is_a_function(p) {
let start = p.peek_span().0;
let decl = p.where_function()?;
let end = p.prev_span().1;
let entry = Expr::Item {
item: Box::new(Item::Fn(decl)),
span: (start, end),
};
locals.push(Stmt {
expr: entry,
span: (start, end),
leading: Vec::new(),
trailing: None,
});
continue;
}
locals.push(stmt(p)?);
}
p.expect(&TokenKind::Dedent, "expected the `where` bindings to end")?;
p.skip_separators();
p.expect(&TokenKind::Dedent, "expected the `where` clause to end")?;
where_group(&locals, params)?;
Ok(locals)
}
pub(crate) fn where_predicates(p: &mut Parser) -> Result<Option<String>, ParseError> {
if !p.check(&TokenKind::Kw(Keyword::Where)) {
return Ok(None);
}
let opened = p.peek_span();
p.advance();
let mut text = String::new();
let mut last = TokenKind::Eof;
loop {
let kind = &p.peek().kind;
let at_end = match kind {
TokenKind::Newline | TokenKind::Indent | TokenKind::Dedent | TokenKind::Eof => true,
_ => false,
};
if at_end {
break;
}
if p.peek().ws_before && !text.is_empty() {
text.push(' ');
}
text.push_str(&token_text(kind));
last = kind.clone();
p.advance();
}
if text.is_empty() {
return Err(ParseError::with_help(
Code::BoundClauseShape,
"this `where` carries no predicates",
"a signature's `where` holds Rust's own predicates, verbatim: `where I::IntoIter: Clone`. Voltaic's other `where` — the one that introduces local definitions — is indented after the body, which is how doc 03 §14.2 tells the two apart",
opened,
));
}
if !can_end_a_type(&last) {
return Err(ParseError::with_help(
Code::BoundClauseShape,
"this `where` clause does not end where a type can end",
"a predicate ends in a trait name or a closing bracket — `where I::IntoIter: Clone`. This one ends mid-expression, so the verbatim text would reach the generated Rust unfinished",
opened,
));
}
Ok(Some(text))
}