use crate::ParseError;
use crate::block::reaches_a_fat_arrow;
use crate::expr::parse_expr;
use crate::item::visibility;
use crate::item::walk_item_for_tails;
use crate::operand::MethodContext;
use crate::operand::receiver;
use crate::parser::Parser;
use crate::signature::TypeStop;
use crate::signature::capture_type;
use crate::signature::type_app;
use crate::token::substitute_var;
use crate::token::token_text;
use voltaic_ast::ClassDecl;
use voltaic_ast::ClassMember;
use voltaic_ast::Constraint;
use voltaic_ast::Ident;
use voltaic_ast::InstanceDecl;
use voltaic_ast::Item;
use voltaic_ast::TypeApp;
use voltaic_ast::Visibility;
use voltaic_diag::Code;
use voltaic_lexer::Keyword;
use voltaic_lexer::TokenKind;
use voltaic_lexer::Trivia;
pub(crate) fn class_body(
p: &mut Parser,
ctx: &MethodContext,
) -> Result<Vec<ClassMember>, ParseError> {
let mut members = Vec::new();
let opens =
p.check(&TokenKind::Newline) && p.peek_at(1).is_some_and(|t| t.kind == TokenKind::Indent);
if !opens {
return Ok(members);
}
p.advance();
p.advance();
loop {
p.skip_separators();
if p.check(&TokenKind::Dedent) || p.at_eof() {
break;
}
members.push(class_member(p, ctx)?);
}
p.expect(&TokenKind::Dedent, "expected the body to end")?;
Ok(members)
}
pub(crate) fn class_decl(
p: &mut Parser,
leading: Vec<Trivia>,
start: usize,
visibility: Visibility,
) -> Result<ClassDecl, ParseError> {
p.expect(&TokenKind::Kw(Keyword::Class), "expected `class`")?;
let supers = class_supers(p)?;
let name = p.ident("expected the class's name")?;
let mut params = Vec::new();
while let Some(param) = p.try_ident() {
params.push(param);
}
let mut ctx = MethodContext::for_class(¶ms);
ctx.recording = Some(name.text.clone());
let members = class_body(p, &ctx)?;
Ok(ClassDecl {
name,
params,
supers,
members,
visibility,
span: (start, p.prev_span().1),
leading,
})
}
pub(crate) fn class_member(p: &mut Parser, ctx: &MethodContext) -> Result<ClassMember, ParseError> {
let leading = p.peek_leading();
let start = p.peek_span().0;
let member_visibility = visibility(p)?;
if p.check(&TokenKind::Kw(Keyword::Type)) {
p.advance();
let assoc = p.ident("expected a name after `type`")?;
p.newline_with_trailing("expected a line break after the associated type")?;
return Ok(ClassMember::AssocType(assoc));
}
if p.check(&TokenKind::Kw(Keyword::Const)) {
p.advance();
let assoc = p.ident("expected a name after `const`")?;
p.expect(&TokenKind::Colon, "expected `:` after the constant's name")?;
let ty = capture_type(p, "expected the constant's type", TypeStop::Greedy)?;
let value = if p.check(&TokenKind::Eq) {
p.advance();
Some(parse_expr(p, 0)?)
} else {
None
};
p.newline_with_trailing("expected a line break after the associated constant")?;
return Ok(ClassMember::AssocConst {
name: assoc,
ty,
value,
});
}
let reserved = ctx.reserved();
let TokenKind::Ident(peeked) = &p.peek().kind else {
return Err(ParseError::r#with(
Code::ExpectedName,
"expected a member's name".to_string(),
p.peek_span(),
));
};
let name = Ident {
text: peeked.clone(),
span: p.peek_span(),
};
let has_signature = p.peek_at(1).is_some_and(|t| t.kind == TokenKind::Colon);
let head = if has_signature {
let declared = p.ident("expected a member's name")?;
let stated = p.signature_head(&declared)?;
if let Some(recording_class) = &ctx.recording {
p.record_head(recording_class, &declared.text, &stated);
}
stated
} else {
let borrowed = ctx
.implementing
.as_deref()
.and_then(|implemented| p.borrowed_head(implemented, &name.text));
let Some(found) = borrowed else {
let message = match ctx.implementing.as_deref() {
Some(implemented) => format!(
"`{}` has no signature here, and `{}` declares none for it earlier in this file — write the signature, or declare the class above the instance",
name.text, implemented
),
None => format!(
"`{}` needs a signature: an inherent `instance` has no class to take one from",
name.text
),
};
return Err(ParseError::r#with(Code::ExpectedToken, message, name.span));
};
found
};
let mut decl = p.equations(name, leading, start, &head, &reserved, true)?;
decl.visibility = member_visibility;
receiver(&mut decl.sig);
for pair in &ctx.vars {
substitute_var(&mut decl.sig, &pair.0, &pair.1);
}
Ok(ClassMember::Method(decl))
}
pub(crate) fn class_supers(p: &mut Parser) -> Result<Vec<Ident>, ParseError> {
let mark = p.position();
let mut found = Vec::new();
let parenthesised = p.check(&TokenKind::LParen);
if parenthesised {
p.advance();
}
while let Some(name) = p.try_ident() {
found.push(name);
if !p.check(&TokenKind::Comma) {
break;
}
p.advance();
}
if parenthesised && p.check(&TokenKind::RParen) {
p.advance();
}
if found.is_empty() || !p.check(&TokenKind::FatArrow) {
p.rewind(mark);
return Ok(Vec::new());
}
p.advance();
Ok(found)
}
pub(crate) fn constraints(p: &mut Parser) -> Result<Vec<Constraint>, ParseError> {
let mark = p.position();
let mut found = Vec::new();
let parenthesised = p.check(&TokenKind::LParen);
if parenthesised {
p.advance();
}
loop {
let start = p.peek_span().0;
let Some(trait_name) = p.try_ident() else {
break;
};
let Some(var) = p.try_ident() else {
break;
};
found.push(Constraint {
trait_name,
var,
span: (start, p.prev_span().1),
});
if !p.check(&TokenKind::Comma) {
break;
}
p.advance();
}
if parenthesised && p.check(&TokenKind::RParen) {
p.advance();
}
if found.is_empty() || !p.check(&TokenKind::FatArrow) {
p.rewind(mark);
let Some(span) = reaches_a_fat_arrow(p) else {
return Ok(Vec::new());
};
let mut scanning = true;
while scanning {
match &p.peek().kind {
TokenKind::Newline | TokenKind::Indent | TokenKind::Dedent | TokenKind::Eof => {
scanning = false;
}
_ => p.advance(),
};
}
return Err(ParseError::with_help(
Code::BoundClauseShape,
"a `=>` here is a bound clause, and this is not one",
"doc 18 §2.2 reads a bound as a trait and the variable it bounds, each a bare name: `Ord t =>`, or `Ord t, Clone t =>`. A trait with type arguments (`Into<String> t`), a path on the left (`Clone i::IntoIter`), a `?Sized`, and a bound between two lifetimes are all out of reach — `docs/GRAMMAR.md` grades them, and inline Rust is the hatch",
span,
));
}
p.advance();
Ok(found)
}
pub(crate) fn given_clause(p: &mut Parser) -> Result<Option<String>, ParseError> {
if !p.check(&TokenKind::Kw(Keyword::Given)) {
return Ok(None);
}
let opened = p.peek_span();
p.advance();
if !p.check(&TokenKind::Lt) {
return Err(ParseError::with_help(
Code::BoundClauseShape,
"expected `<` after `given`",
"`given` carries Rust's own generic parameter list, verbatim and in angle brackets: `given <const N: usize>`, `given <T: Display, I: IntoIterator<Item = T>>`",
p.peek_span(),
));
}
let mut depth: i32 = 0;
let mut text = String::new();
loop {
let kind = &p.peek().kind;
let at_end = match kind {
TokenKind::Newline | TokenKind::Indent | TokenKind::Dedent | TokenKind::Eof => true,
_ => false,
};
if at_end {
return Err(ParseError::with_help(
Code::BoundClauseShape,
"this `given` clause is not closed",
"a `given` clause runs from `<` to the `>` that matches it, on one line, and the spine follows it",
opened,
));
}
match kind {
TokenKind::Lt => depth += 1,
TokenKind::Gt => depth -= 1,
TokenKind::Shr => depth -= 2,
TokenKind::Compose => depth -= 3,
_ => (),
};
if p.peek().ws_before && !text.is_empty() {
text.push(' ');
}
text.push_str(&token_text(kind));
p.advance();
if depth <= 0 {
break;
}
}
Ok(Some(text))
}
pub(crate) fn instance_decl(
p: &mut Parser,
leading: Vec<Trivia>,
start: usize,
) -> Result<InstanceDecl, ParseError> {
p.expect(&TokenKind::Kw(Keyword::Instance), "expected `instance`")?;
let binder = given_clause(p)?;
let limits = if binder.is_some() {
Vec::new()
} else {
constraints(p)?
};
let grouped = p.check(&TokenKind::LParen);
let first = type_app(p)?;
let split = if grouped {
(None, first)
} else if p.check(&TokenKind::LParen) {
(Some(first), type_app(p)?)
} else if first.args.is_empty() {
(None, first)
} else {
let mut args = first.args.clone();
let head = args.pop().expect("the argument list is not empty");
let class_end = args.last().map_or(first.head.span.1, |a| a.span.1);
let class_app = TypeApp {
head: first.head.clone(),
args,
span: (first.head.span.0, class_end),
};
let target_app = TypeApp {
head: head.clone(),
args: Vec::new(),
span: head.span,
};
(Some(class_app), target_app)
};
let implemented = split.0;
let target = split.1;
let var = target.vars().first().map(|v| v.to_string());
let subject = var.as_deref().map_or_else(String::new, TypeApp::rust_name);
let mut ctx = MethodContext::for_instance(var.as_deref(), &subject);
ctx.implementing = implemented.as_ref().map(|c| c.head.text.clone());
let members = class_body(p, &ctx)?;
Ok(InstanceDecl {
r#class: implemented,
target,
constraints: limits,
r#given: binder,
members,
span: (start, p.prev_span().1),
leading,
})
}
pub(crate) fn walk_member_for_tails(member: &ClassMember, problems: &mut Vec<ParseError>) {
if let ClassMember::Method(decl) = member {
walk_item_for_tails(&Item::Fn(decl.clone()), problems);
}
();
}