use crate::SyntaxKind::{
COLON, COMMA, GT, IDENTIFIER, L_PAREN, LT, R_PAREN, TYPE_ANNOTATION, TYPE_EXPR, TYPE_FN,
TYPE_GENERIC, TYPE_NAME,
};
use super::Parser;
use super::expression::skip_balanced;
pub(crate) fn at_type_annotation(p: &Parser<'_, '_>) -> bool {
p.current() == COLON
}
pub(crate) fn type_annotation(p: &mut Parser<'_, '_>) {
p.start_node(TYPE_ANNOTATION);
p.skip_ws();
p.bump(); p.skip_ws();
type_expr(p);
p.finish_node();
}
pub(crate) fn type_expr(p: &mut Parser<'_, '_>) {
p.start_node(TYPE_EXPR);
if p.at_depth_limit() {
p.error("nesting depth limit exceeded".into());
p.finish_node();
return;
}
if p.at_kw_text("fn") {
type_fn(p);
} else if p.at_ident_or_keyword() {
type_name_or_generic(p);
} else {
p.error("expected type".into());
}
p.finish_node();
}
fn type_name_or_generic(p: &mut Parser<'_, '_>) {
let checkpoint = p.checkpoint();
p.start_node(IDENTIFIER);
p.expect_ident_or_keyword();
p.finish_node();
if p.current() == LT {
p.skip_ws();
p.start_node_at(checkpoint, TYPE_GENERIC);
p.bump(); if p.at_depth_limit() {
p.error("nesting depth limit exceeded".into());
skip_balanced(p, LT, GT);
p.finish_node();
return;
}
p.depth += 1;
p.skip_ws();
type_expr(p);
loop {
p.skip_ws();
if !p.eat(COMMA) {
break;
}
p.skip_ws();
type_expr(p);
}
p.skip_ws();
p.expect(GT);
p.depth -= 1;
p.finish_node();
} else {
p.start_node_at(checkpoint, TYPE_NAME);
p.finish_node();
}
}
fn type_fn(p: &mut Parser<'_, '_>) {
p.start_node(TYPE_FN);
p.bump(); p.skip_ws();
p.expect(L_PAREN);
if p.at_depth_limit() {
p.error("nesting depth limit exceeded".into());
skip_balanced(p, L_PAREN, R_PAREN);
p.finish_node();
return;
}
p.depth += 1;
p.skip_ws();
if p.current() != R_PAREN {
type_expr(p);
loop {
p.skip_ws();
if !p.eat(COMMA) {
break;
}
p.skip_ws();
if p.current() == R_PAREN {
break; }
type_expr(p);
}
}
p.skip_ws();
p.expect(R_PAREN);
p.depth -= 1;
p.skip_ws();
p.expect(COLON);
p.skip_ws();
type_expr(p);
p.finish_node();
}
#[cfg(test)]
mod tests {
use crate::SyntaxKind;
use crate::parse;
fn dump(src: &str) -> String {
let parsed = parse(src);
format!("{:#?}", parsed.syntax())
}
#[test]
fn param_annotation_parses_type_generic_and_type_annotation_nodes() {
let out = dump("=== function heal(ref hp: int, amount: int): int ===\n~ return hp\n");
assert!(out.contains("TYPE_ANNOTATION"), "{out}");
assert!(out.contains("TYPE_NAME"), "{out}");
}
#[test]
fn type_name_range_excludes_trailing_trivia() {
let parsed = parse("VAR p: Point = 0\n");
let root = parsed.syntax();
let type_name = root
.descendants()
.find(|n| n.kind() == SyntaxKind::TYPE_NAME)
.expect("TYPE_NAME node");
let ident = type_name
.descendants_with_tokens()
.find_map(|e| e.into_token().filter(|t| t.kind() == SyntaxKind::IDENT))
.expect("IDENT token inside TYPE_NAME");
assert_eq!(
type_name.text_range(),
ident.text_range(),
"TYPE_NAME must span exactly its IDENT, not any trailing trivia: {root:#?}"
);
}
#[test]
fn var_annotation_parses() {
let out = dump("VAR gold: int = 100\n");
assert!(out.contains("TYPE_ANNOTATION"), "{out}");
assert_eq!(parse("VAR gold: int = 100\n").errors().len(), 0);
}
#[test]
fn temp_ascription_parses() {
let out = dump("~ temp name: string = who\n");
assert!(out.contains("TYPE_ANNOTATION"), "{out}");
}
#[test]
fn generic_list_type_parses() {
let out = dump("VAR w: List<Weathers> = sunny\n");
assert!(
out.contains(&format!("{:?}", SyntaxKind::TYPE_GENERIC)),
"{out}"
);
}
#[test]
fn generic_map_type_parses_two_args() {
let out = dump("VAR m: Map<string, int> = 0\n");
assert!(
out.contains(&format!("{:?}", SyntaxKind::TYPE_GENERIC)),
"{out}"
);
}
#[test]
fn generic_handle_type_parses_like_any_other_generic() {
let out = dump("VAR h: Handle<AudioInstance> = 0\n");
assert!(
out.contains(&format!("{:?}", SyntaxKind::TYPE_GENERIC)),
"{out}"
);
assert_eq!(
parse("VAR h: Handle<AudioInstance> = 0\n").errors().len(),
0
);
}
#[test]
fn fn_type_parses_reserved_syntax() {
let out = dump("VAR cb: fn(int, int): bool = 0\n");
assert!(out.contains(&format!("{:?}", SyntaxKind::TYPE_FN)), "{out}");
}
#[test]
fn void_return_type_parses_as_type_name() {
let out = dump("=== function noop(): void ===\n~ return\n");
assert!(out.contains("TYPE_NAME"), "{out}");
}
#[test]
fn stitch_header_return_type_annotation_parses() {
for src in [
"=== camp ===\n= fire(logs): int\n~ return logs\n",
"=== camp ===\n= fire: int\n~ return 1\n",
] {
let parsed = parse(src);
assert_eq!(parsed.errors().len(), 0, "{src:?}: {:?}", parsed.errors());
let stitch_header = parsed
.syntax()
.descendants()
.find(|n| n.kind() == SyntaxKind::STITCH_HEADER)
.expect("STITCH_HEADER node for the parsed stitch");
assert!(
stitch_header
.descendants()
.any(|n| n.kind() == SyntaxKind::TYPE_ANNOTATION),
"{src:?}: {stitch_header:#?}"
);
}
}
#[test]
fn unknown_type_name_still_parses_no_error() {
let parsed = parse("VAR p: Frobnicator = 0\n");
assert_eq!(parsed.errors().len(), 0, "{:?}", parsed.errors());
}
#[test]
fn no_annotation_still_parses_plain_declarations() {
assert_eq!(parse("VAR gold = 100\n").errors().len(), 0);
assert_eq!(parse("~ temp name = who\n").errors().len(), 0);
assert_eq!(
parse("=== heal(hp, amount) ===\n~ return hp\n")
.errors()
.len(),
0
);
}
}