use crate::comment::Comment;
use crate::node::Spanned;
use crate::token::Token;
use crate::type_annotation::{RecordField, TypeAnnotation};
use super::{ParseResult, Parser};
pub fn parse_type(p: &mut Parser) -> ParseResult<Spanned<TypeAnnotation>> {
let start = p.current_pos();
let pre_left_len = p.collected_comments.len();
let first_token_offset = p.peek_past_whitespace_offset();
let mut left = parse_type_app(p)?;
let mut leading_on_left: Vec<Spanned<Comment>> = Vec::new();
let mut i = pre_left_len;
while i < p.collected_comments.len() {
let c = &p.collected_comments[i];
if c.span.end.offset <= first_token_offset {
leading_on_left.push(p.collected_comments.remove(i));
} else {
i += 1;
}
}
if !leading_on_left.is_empty() {
let mut merged = leading_on_left;
merged.extend(std::mem::take(&mut left.comments));
left.comments = merged;
}
p.skip_whitespace();
if matches!(p.peek(), Token::Arrow) {
let arrow_offset = p.peek_span().start.offset;
let left_end = left.span.end.offset;
let mut pre_arrow: Vec<Spanned<Comment>> = Vec::new();
let mut i = 0;
while i < p.collected_comments.len() {
let c = &p.collected_comments[i];
if c.span.start.offset >= left_end && c.span.end.offset <= arrow_offset {
pre_arrow.push(p.collected_comments.remove(i));
} else {
i += 1;
}
}
p.advance(); let mut right = parse_type(p)?; if !pre_arrow.is_empty() {
let mut merged = pre_arrow;
merged.extend(std::mem::take(&mut right.comments));
right.comments = merged;
}
let ty = TypeAnnotation::FunctionType {
from: Box::new(left),
to: Box::new(right),
};
Ok(p.spanned_from(start, ty))
} else {
Ok(left)
}
}
fn parse_type_app(p: &mut Parser) -> ParseResult<Spanned<TypeAnnotation>> {
let start = p.current_pos();
p.skip_whitespace();
match p.peek().clone() {
Token::UpperName(_) => {
let (module_name, name) = parse_qualified_upper(p)?;
let name_span = name.span;
let mut args = Vec::new();
loop {
p.skip_whitespace();
if !can_start_atomic_type(p.peek()) {
break;
}
if p.current_column() <= name_span.start.column
&& p.current_pos().line != name_span.start.line
{
break;
}
args.push(parse_type_atomic(p)?);
}
let ty = TypeAnnotation::Typed {
module_name,
name,
args,
};
Ok(p.spanned_from(start, ty))
}
_ => parse_type_atomic(p),
}
}
pub fn parse_type_atomic_public(p: &mut Parser) -> ParseResult<Spanned<TypeAnnotation>> {
parse_type_atomic(p)
}
fn parse_type_atomic(p: &mut Parser) -> ParseResult<Spanned<TypeAnnotation>> {
p.skip_whitespace();
let start = p.current_pos();
match p.peek().clone() {
Token::LowerName(name) => {
p.advance();
Ok(p.spanned_from(start, TypeAnnotation::GenericType(name)))
}
Token::UpperName(_) => {
let (module_name, name) = parse_qualified_upper(p)?;
let ty = TypeAnnotation::Typed {
module_name,
name,
args: Vec::new(),
};
Ok(p.spanned_from(start, ty))
}
Token::LeftParen => {
p.advance(); p.skip_whitespace();
if matches!(p.peek(), Token::RightParen) {
p.advance();
return Ok(p.spanned_from(start, TypeAnnotation::Unit));
}
let first = parse_type(p)?;
p.skip_whitespace();
match p.peek() {
Token::Comma => {
let mut elements = vec![first];
while p.eat(&Token::Comma) {
elements.push(parse_type(p)?);
}
p.expect(&Token::RightParen)?;
Ok(p.spanned_from(start, TypeAnnotation::Tupled(elements)))
}
Token::RightParen => {
p.advance();
if matches!(first.value, TypeAnnotation::FunctionType { .. }) {
Ok(p.spanned_from(start, first.value))
} else {
Ok(first)
}
}
_ => Err(p.error("expected `,` or `)` in type")),
}
}
Token::LeftBrace => parse_record_type(p),
_ => Err(p.error(format!(
"expected type, found {}",
super::describe(p.peek())
))),
}
}
fn parse_record_type(p: &mut Parser) -> ParseResult<Spanned<TypeAnnotation>> {
let start = p.current_pos();
p.expect(&Token::LeftBrace)?;
p.skip_whitespace();
if matches!(p.peek(), Token::RightBrace) {
p.advance();
return Ok(p.spanned_from(start, TypeAnnotation::Record(Vec::new())));
}
if matches!(p.peek(), Token::LowerName(_)) {
let save_pos = p.pos;
let maybe_base = p.expect_lower_name();
if let Ok(base) = maybe_base {
p.skip_whitespace();
if matches!(p.peek(), Token::Pipe) {
p.advance(); let fields = parse_record_fields(p)?;
p.expect(&Token::RightBrace)?;
return Ok(p.spanned_from(start, TypeAnnotation::GenericRecord { base, fields }));
}
p.pos = save_pos;
} else {
p.pos = save_pos;
}
}
let fields = parse_record_fields(p)?;
p.expect(&Token::RightBrace)?;
Ok(p.spanned_from(start, TypeAnnotation::Record(fields)))
}
fn parse_record_fields(p: &mut Parser) -> ParseResult<Vec<Spanned<RecordField>>> {
let start_snapshot = p.pending_comments_snapshot();
let mut fields = Vec::new();
fields.push(parse_record_field(p)?);
while p.eat(&Token::Comma) {
p.skip_whitespace();
let mut field = parse_record_field(p)?;
let prev_end = fields.last().unwrap().span.end.offset;
let field_start = field.span.start.offset;
let all = p.take_pending_comments_since(start_snapshot);
let (leading, other): (Vec<_>, Vec<_>) = all
.into_iter()
.partition(|c| c.span.start.offset > prev_end && c.span.end.offset <= field_start);
p.restore_pending_comments(other);
if !leading.is_empty() {
let mut all_leading = leading;
all_leading.extend(std::mem::take(&mut field.comments));
field.comments = all_leading;
}
fields.push(field);
}
Ok(fields)
}
fn parse_record_field(p: &mut Parser) -> ParseResult<Spanned<RecordField>> {
let start = p.current_pos();
let name = p.expect_lower_name()?;
p.expect(&Token::Colon)?;
let type_annotation = parse_type(p)?;
Ok(p.spanned_from(
start,
RecordField {
name,
type_annotation,
},
))
}
fn parse_qualified_upper(p: &mut Parser) -> ParseResult<(Vec<String>, Spanned<String>)> {
let mut parts = Vec::new();
let first = p.expect_upper_name()?;
parts.push(first);
while matches!(p.peek(), Token::Dot) {
if !matches!(p.peek_nth_past_whitespace(1), Token::UpperName(_)) {
break;
}
let dot_pos = p.pos;
p.advance(); if matches!(p.peek(), Token::Newline) {
p.pos = dot_pos;
break;
}
match p.peek() {
Token::UpperName(_) => {
let name = p.expect_upper_name()?;
parts.push(name);
}
_ => {
p.pos = dot_pos;
break;
}
}
}
let name = parts.pop().unwrap();
let module_name = parts.into_iter().map(|s| s.value).collect();
Ok((module_name, name))
}
fn can_start_atomic_type(tok: &Token) -> bool {
matches!(
tok,
Token::LowerName(_) | Token::UpperName(_) | Token::LeftParen | Token::LeftBrace
)
}