use crate::ast::{BinOp, Pattern, UnaryOp};
use crate::effects::EffectSet;
use crate::span::Span;
use crate::types::QalaType;
pub type TypedAst = Vec<TypedItem>;
#[derive(Debug, Clone, PartialEq)]
pub enum TypedItem {
Fn(TypedFnDecl),
Struct(TypedStructDecl),
Enum(TypedEnumDecl),
Interface(TypedInterfaceDecl),
}
impl TypedItem {
pub fn span(&self) -> Span {
match self {
TypedItem::Fn(d) => d.span,
TypedItem::Struct(d) => d.span,
TypedItem::Enum(d) => d.span,
TypedItem::Interface(d) => d.span,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct TypedFnDecl {
pub type_name: Option<String>,
pub name: String,
pub params: Vec<TypedParam>,
pub ret_ty: QalaType,
pub effect: EffectSet,
pub body: TypedBlock,
pub span: Span,
}
impl TypedFnDecl {
pub fn effect(&self) -> EffectSet {
self.effect
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct TypedParam {
pub is_self: bool,
pub name: String,
pub ty: QalaType,
pub default: Option<TypedExpr>,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
pub struct TypedStructDecl {
pub name: String,
pub fields: Vec<TypedField>,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
pub struct TypedField {
pub name: String,
pub ty: QalaType,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
pub struct TypedEnumDecl {
pub name: String,
pub variants: Vec<TypedVariant>,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
pub struct TypedVariant {
pub name: String,
pub fields: Vec<QalaType>,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
pub struct TypedInterfaceDecl {
pub name: String,
pub methods: Vec<TypedMethodSig>,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
pub struct TypedMethodSig {
pub name: String,
pub params: Vec<TypedParam>,
pub ret_ty: QalaType,
pub effect: EffectSet,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
pub struct TypedBlock {
pub stmts: Vec<TypedStmt>,
pub value: Option<Box<TypedExpr>>,
pub ty: QalaType,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
pub enum TypedStmt {
Let {
is_mut: bool,
name: String,
ty: QalaType,
init: TypedExpr,
span: Span,
},
If {
cond: TypedExpr,
then_block: TypedBlock,
else_branch: Option<TypedElseBranch>,
span: Span,
},
While {
cond: TypedExpr,
body: TypedBlock,
span: Span,
},
For {
var: String,
var_ty: QalaType,
iter: TypedExpr,
body: TypedBlock,
span: Span,
},
Return {
value: Option<TypedExpr>,
span: Span,
},
Break { span: Span },
Continue { span: Span },
Defer { expr: TypedExpr, span: Span },
Expr { expr: TypedExpr, span: Span },
}
impl TypedStmt {
pub fn span(&self) -> Span {
match self {
TypedStmt::Let { span, .. }
| TypedStmt::If { span, .. }
| TypedStmt::While { span, .. }
| TypedStmt::For { span, .. }
| TypedStmt::Return { span, .. }
| TypedStmt::Break { span }
| TypedStmt::Continue { span }
| TypedStmt::Defer { span, .. }
| TypedStmt::Expr { span, .. } => *span,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum TypedElseBranch {
Block(TypedBlock),
If(Box<TypedStmt>),
}
#[derive(Debug, Clone, PartialEq)]
pub enum TypedExpr {
Int {
value: i64,
ty: QalaType,
span: Span,
},
Float {
value: f64,
ty: QalaType,
span: Span,
},
Byte { value: u8, ty: QalaType, span: Span },
Str {
value: String,
ty: QalaType,
span: Span,
},
Bool {
value: bool,
ty: QalaType,
span: Span,
},
Ident {
name: String,
ty: QalaType,
span: Span,
},
Paren {
inner: Box<TypedExpr>,
ty: QalaType,
span: Span,
},
Tuple {
elems: Vec<TypedExpr>,
ty: QalaType,
span: Span,
},
ArrayLit {
elems: Vec<TypedExpr>,
ty: QalaType,
span: Span,
},
ArrayRepeat {
value: Box<TypedExpr>,
count: Box<TypedExpr>,
ty: QalaType,
span: Span,
},
StructLit {
name: String,
fields: Vec<TypedFieldInit>,
ty: QalaType,
span: Span,
},
FieldAccess {
obj: Box<TypedExpr>,
name: String,
ty: QalaType,
span: Span,
},
MethodCall {
receiver: Box<TypedExpr>,
name: String,
args: Vec<TypedExpr>,
ty: QalaType,
span: Span,
},
Call {
callee: Box<TypedExpr>,
args: Vec<TypedExpr>,
ty: QalaType,
span: Span,
},
Index {
obj: Box<TypedExpr>,
index: Box<TypedExpr>,
ty: QalaType,
span: Span,
},
Try {
expr: Box<TypedExpr>,
ty: QalaType,
span: Span,
},
Unary {
op: UnaryOp,
operand: Box<TypedExpr>,
ty: QalaType,
span: Span,
},
Binary {
op: BinOp,
lhs: Box<TypedExpr>,
rhs: Box<TypedExpr>,
ty: QalaType,
span: Span,
},
Range {
start: Option<Box<TypedExpr>>,
end: Option<Box<TypedExpr>>,
inclusive: bool,
ty: QalaType,
span: Span,
},
Pipeline {
lhs: Box<TypedExpr>,
call: Box<TypedExpr>,
ty: QalaType,
span: Span,
},
Comptime {
body: Box<TypedExpr>,
ty: QalaType,
span: Span,
},
Block {
block: TypedBlock,
ty: QalaType,
span: Span,
},
Match {
scrutinee: Box<TypedExpr>,
arms: Vec<TypedMatchArm>,
ty: QalaType,
span: Span,
},
OrElse {
expr: Box<TypedExpr>,
fallback: Box<TypedExpr>,
ty: QalaType,
span: Span,
},
Interpolation {
parts: Vec<TypedInterpPart>,
ty: QalaType,
span: Span,
},
}
impl TypedExpr {
pub fn ty(&self) -> &QalaType {
match self {
TypedExpr::Int { ty, .. }
| TypedExpr::Float { ty, .. }
| TypedExpr::Byte { ty, .. }
| TypedExpr::Str { ty, .. }
| TypedExpr::Bool { ty, .. }
| TypedExpr::Ident { ty, .. }
| TypedExpr::Paren { ty, .. }
| TypedExpr::Tuple { ty, .. }
| TypedExpr::ArrayLit { ty, .. }
| TypedExpr::ArrayRepeat { ty, .. }
| TypedExpr::StructLit { ty, .. }
| TypedExpr::FieldAccess { ty, .. }
| TypedExpr::MethodCall { ty, .. }
| TypedExpr::Call { ty, .. }
| TypedExpr::Index { ty, .. }
| TypedExpr::Try { ty, .. }
| TypedExpr::Unary { ty, .. }
| TypedExpr::Binary { ty, .. }
| TypedExpr::Range { ty, .. }
| TypedExpr::Pipeline { ty, .. }
| TypedExpr::Comptime { ty, .. }
| TypedExpr::Block { ty, .. }
| TypedExpr::Match { ty, .. }
| TypedExpr::OrElse { ty, .. }
| TypedExpr::Interpolation { ty, .. } => ty,
}
}
pub fn span(&self) -> Span {
match self {
TypedExpr::Int { span, .. }
| TypedExpr::Float { span, .. }
| TypedExpr::Byte { span, .. }
| TypedExpr::Str { span, .. }
| TypedExpr::Bool { span, .. }
| TypedExpr::Ident { span, .. }
| TypedExpr::Paren { span, .. }
| TypedExpr::Tuple { span, .. }
| TypedExpr::ArrayLit { span, .. }
| TypedExpr::ArrayRepeat { span, .. }
| TypedExpr::StructLit { span, .. }
| TypedExpr::FieldAccess { span, .. }
| TypedExpr::MethodCall { span, .. }
| TypedExpr::Call { span, .. }
| TypedExpr::Index { span, .. }
| TypedExpr::Try { span, .. }
| TypedExpr::Unary { span, .. }
| TypedExpr::Binary { span, .. }
| TypedExpr::Range { span, .. }
| TypedExpr::Pipeline { span, .. }
| TypedExpr::Comptime { span, .. }
| TypedExpr::Block { span, .. }
| TypedExpr::Match { span, .. }
| TypedExpr::OrElse { span, .. }
| TypedExpr::Interpolation { span, .. } => *span,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct TypedFieldInit {
pub name: String,
pub value: TypedExpr,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
pub enum TypedInterpPart {
Literal(String),
Expr(TypedExpr),
}
#[derive(Debug, Clone, PartialEq)]
pub struct TypedMatchArm {
pub pattern: Pattern,
pub guard: Option<TypedExpr>,
pub body: TypedMatchArmBody,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
pub enum TypedMatchArmBody {
Expr(Box<TypedExpr>),
Block(TypedBlock),
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::{BinOp, Pattern, UnaryOp};
use crate::effects::EffectSet;
use crate::span::Span;
use crate::types::{QalaType, Symbol};
fn span(n: usize) -> Span {
Span::new(n, n + 1)
}
#[test]
fn nodes_are_debug_clone_and_partial_eq() {
let a = TypedExpr::Binary {
op: BinOp::Add,
lhs: Box::new(TypedExpr::Int {
value: 1,
ty: QalaType::I64,
span: span(0),
}),
rhs: Box::new(TypedExpr::Int {
value: 2,
ty: QalaType::I64,
span: span(2),
}),
ty: QalaType::I64,
span: span(0),
};
let b = a.clone();
assert_eq!(a, b);
let _ = format!("{a:?}");
let c = TypedExpr::Binary {
op: BinOp::Mul, lhs: Box::new(TypedExpr::Int {
value: 1,
ty: QalaType::I64,
span: span(0),
}),
rhs: Box::new(TypedExpr::Int {
value: 2,
ty: QalaType::I64,
span: span(2),
}),
ty: QalaType::I64,
span: span(0),
};
assert_ne!(a, c);
}
#[test]
fn typed_expr_ty_returns_the_stored_type() {
let cases: Vec<(TypedExpr, QalaType)> = vec![
(
TypedExpr::Int {
value: 0,
ty: QalaType::I64,
span: span(1),
},
QalaType::I64,
),
(
TypedExpr::Float {
value: 0.0,
ty: QalaType::F64,
span: span(2),
},
QalaType::F64,
),
(
TypedExpr::Byte {
value: 0,
ty: QalaType::Byte,
span: span(3),
},
QalaType::Byte,
),
(
TypedExpr::Str {
value: String::new(),
ty: QalaType::Str,
span: span(4),
},
QalaType::Str,
),
(
TypedExpr::Bool {
value: true,
ty: QalaType::Bool,
span: span(5),
},
QalaType::Bool,
),
(
TypedExpr::Ident {
name: "x".to_string(),
ty: QalaType::Named(Symbol("Shape".to_string())),
span: span(6),
},
QalaType::Named(Symbol("Shape".to_string())),
),
(
TypedExpr::Unary {
op: UnaryOp::Neg,
operand: Box::new(TypedExpr::Int {
value: 1,
ty: QalaType::I64,
span: span(8),
}),
ty: QalaType::I64,
span: span(7),
},
QalaType::I64,
),
(
TypedExpr::Tuple {
elems: vec![
TypedExpr::Int {
value: 1,
ty: QalaType::I64,
span: span(10),
},
TypedExpr::Bool {
value: true,
ty: QalaType::Bool,
span: span(11),
},
],
ty: QalaType::Tuple(vec![QalaType::I64, QalaType::Bool]),
span: span(9),
},
QalaType::Tuple(vec![QalaType::I64, QalaType::Bool]),
),
(
TypedExpr::Call {
callee: Box::new(TypedExpr::Ident {
name: "f".to_string(),
ty: QalaType::Unknown,
span: span(13),
}),
args: vec![],
ty: QalaType::Unknown,
span: span(12),
},
QalaType::Unknown,
),
];
for (expr, expected) in cases {
assert_eq!(expr.ty(), &expected, "ty() mismatch for {expr:?}");
}
}
#[test]
fn typed_expr_span_returns_the_stored_span() {
let cases: Vec<(TypedExpr, Span)> = vec![
(
TypedExpr::Int {
value: 0,
ty: QalaType::I64,
span: span(1),
},
span(1),
),
(
TypedExpr::Float {
value: 0.0,
ty: QalaType::F64,
span: span(2),
},
span(2),
),
(
TypedExpr::Byte {
value: 0,
ty: QalaType::Byte,
span: span(3),
},
span(3),
),
(
TypedExpr::Str {
value: String::new(),
ty: QalaType::Str,
span: span(4),
},
span(4),
),
(
TypedExpr::Bool {
value: true,
ty: QalaType::Bool,
span: span(5),
},
span(5),
),
(
TypedExpr::Ident {
name: "x".to_string(),
ty: QalaType::I64,
span: span(6),
},
span(6),
),
(
TypedExpr::Unary {
op: UnaryOp::Neg,
operand: Box::new(TypedExpr::Int {
value: 1,
ty: QalaType::I64,
span: span(8),
}),
ty: QalaType::I64,
span: span(7),
},
span(7),
),
(
TypedExpr::MethodCall {
receiver: Box::new(TypedExpr::Ident {
name: "f".to_string(),
ty: QalaType::FileHandle,
span: span(10),
}),
name: "read_all".to_string(),
args: vec![],
ty: QalaType::Str,
span: span(9),
},
span(9),
),
(
TypedExpr::Pipeline {
lhs: Box::new(TypedExpr::Int {
value: 5,
ty: QalaType::I64,
span: span(12),
}),
call: Box::new(TypedExpr::Ident {
name: "double".to_string(),
ty: QalaType::Function {
params: vec![QalaType::I64],
returns: Box::new(QalaType::I64),
},
span: span(14),
}),
ty: QalaType::I64,
span: span(11),
},
span(11),
),
(
TypedExpr::OrElse {
expr: Box::new(TypedExpr::Ident {
name: "a".to_string(),
ty: QalaType::Option(Box::new(QalaType::Str)),
span: span(16),
}),
fallback: Box::new(TypedExpr::Str {
value: "no data".to_string(),
ty: QalaType::Str,
span: span(18),
}),
ty: QalaType::Str,
span: span(15),
},
span(15),
),
(
TypedExpr::Interpolation {
parts: vec![
TypedInterpPart::Literal("fib(".to_string()),
TypedInterpPart::Expr(TypedExpr::Ident {
name: "i".to_string(),
ty: QalaType::I64,
span: span(20),
}),
TypedInterpPart::Literal(")".to_string()),
],
ty: QalaType::Str,
span: span(19),
},
span(19),
),
(
TypedExpr::Match {
scrutinee: Box::new(TypedExpr::Ident {
name: "v".to_string(),
ty: QalaType::I64,
span: span(22),
}),
arms: vec![TypedMatchArm {
pattern: Pattern::Wildcard { span: span(23) },
guard: None,
body: TypedMatchArmBody::Expr(Box::new(TypedExpr::Int {
value: 0,
ty: QalaType::I64,
span: span(24),
})),
span: span(23),
}],
ty: QalaType::I64,
span: span(21),
},
span(21),
),
];
for (expr, expected) in cases {
assert_eq!(expr.span(), expected, "span() mismatch for {expr:?}");
}
}
#[test]
fn typed_stmt_span_returns_the_stored_span() {
let cases: Vec<(TypedStmt, Span)> = vec![
(
TypedStmt::Let {
is_mut: false,
name: "x".to_string(),
ty: QalaType::I64,
init: TypedExpr::Int {
value: 1,
ty: QalaType::I64,
span: span(1),
},
span: span(0),
},
span(0),
),
(TypedStmt::Break { span: span(2) }, span(2)),
(TypedStmt::Continue { span: span(3) }, span(3)),
(
TypedStmt::Return {
value: None,
span: span(4),
},
span(4),
),
(
TypedStmt::For {
var: "i".to_string(),
var_ty: QalaType::I64,
iter: TypedExpr::Range {
start: Some(Box::new(TypedExpr::Int {
value: 0,
ty: QalaType::I64,
span: span(6),
})),
end: Some(Box::new(TypedExpr::Int {
value: 15,
ty: QalaType::I64,
span: span(8),
})),
inclusive: false,
ty: QalaType::Array(Box::new(QalaType::I64), None),
span: span(6),
},
body: TypedBlock {
stmts: vec![],
value: None,
ty: QalaType::Void,
span: span(9),
},
span: span(5),
},
span(5),
),
(
TypedStmt::Defer {
expr: TypedExpr::Call {
callee: Box::new(TypedExpr::Ident {
name: "close".to_string(),
ty: QalaType::Function {
params: vec![QalaType::FileHandle],
returns: Box::new(QalaType::Void),
},
span: span(11),
}),
args: vec![TypedExpr::Ident {
name: "f".to_string(),
ty: QalaType::FileHandle,
span: span(12),
}],
ty: QalaType::Void,
span: span(11),
},
span: span(10),
},
span(10),
),
];
for (stmt, expected) in cases {
assert_eq!(stmt.span(), expected, "span() mismatch for {stmt:?}");
}
}
#[test]
fn typed_item_span_delegates_to_the_wrapped_decl() {
let f = TypedItem::Fn(TypedFnDecl {
type_name: None,
name: "main".to_string(),
params: vec![],
ret_ty: QalaType::Void,
effect: EffectSet::io(),
body: TypedBlock {
stmts: vec![],
value: None,
ty: QalaType::Void,
span: span(1),
},
span: span(0),
});
assert_eq!(f.span(), span(0));
let s = TypedItem::Struct(TypedStructDecl {
name: "S".to_string(),
fields: vec![],
span: span(2),
});
assert_eq!(s.span(), span(2));
let e = TypedItem::Enum(TypedEnumDecl {
name: "E".to_string(),
variants: vec![],
span: span(3),
});
assert_eq!(e.span(), span(3));
let i = TypedItem::Interface(TypedInterfaceDecl {
name: "I".to_string(),
methods: vec![],
span: span(4),
});
assert_eq!(i.span(), span(4));
}
#[test]
fn typed_fn_decl_effect_returns_the_stored_effect_set() {
let cases = [
EffectSet::pure(),
EffectSet::io(),
EffectSet::alloc(),
EffectSet::panic(),
EffectSet::io().union(EffectSet::alloc()),
EffectSet::full(),
];
for eff in cases {
let f = TypedFnDecl {
type_name: None,
name: "f".to_string(),
params: vec![],
ret_ty: QalaType::Void,
effect: eff,
body: TypedBlock {
stmts: vec![],
value: None,
ty: QalaType::Void,
span: span(1),
},
span: span(0),
};
assert_eq!(f.effect(), eff, "effect() mismatch for {eff:?}");
}
}
#[test]
fn typed_block_ty_is_void_for_empty_block() {
let b = TypedBlock {
stmts: vec![],
value: None,
ty: QalaType::Void,
span: span(0),
};
assert_eq!(b.ty, QalaType::Void);
let b2 = TypedBlock {
stmts: vec![TypedStmt::Let {
is_mut: false,
name: "x".to_string(),
ty: QalaType::I64,
init: TypedExpr::Int {
value: 1,
ty: QalaType::I64,
span: span(1),
},
span: span(0),
}],
value: Some(Box::new(TypedExpr::Ident {
name: "x".to_string(),
ty: QalaType::I64,
span: span(3),
})),
ty: QalaType::I64,
span: span(0),
};
assert_eq!(b2.ty, QalaType::I64);
assert!(b2.value.is_some());
assert_eq!(b2.stmts.len(), 1);
}
#[test]
fn typed_ast_is_a_vec_of_typed_items() {
let mut prog: TypedAst = vec![];
assert!(prog.is_empty());
prog.push(TypedItem::Struct(TypedStructDecl {
name: "Point".to_string(),
fields: vec![
TypedField {
name: "x".to_string(),
ty: QalaType::I64,
span: span(1),
},
TypedField {
name: "y".to_string(),
ty: QalaType::I64,
span: span(2),
},
],
span: span(0),
}));
assert_eq!(prog.len(), 1);
}
#[test]
fn reexported_unary_and_bin_ops_are_usable_in_typed_ast() {
let _u: UnaryOp = UnaryOp::Not;
let _b: BinOp = BinOp::Add;
let neg = TypedExpr::Unary {
op: UnaryOp::Neg,
operand: Box::new(TypedExpr::Int {
value: 5,
ty: QalaType::I64,
span: span(1),
}),
ty: QalaType::I64,
span: span(0),
};
assert_eq!(neg.ty(), &QalaType::I64);
}
#[test]
fn pattern_is_reused_from_ast_in_typed_match_arms() {
let arm = TypedMatchArm {
pattern: Pattern::Variant {
name: "Circle".to_string(),
sub: vec![Pattern::Binding {
name: "r".to_string(),
span: span(1),
}],
span: span(0),
},
guard: Some(TypedExpr::Binary {
op: BinOp::Gt,
lhs: Box::new(TypedExpr::Ident {
name: "r".to_string(),
ty: QalaType::F64,
span: span(2),
}),
rhs: Box::new(TypedExpr::Float {
value: 0.0,
ty: QalaType::F64,
span: span(3),
}),
ty: QalaType::Bool,
span: span(2),
}),
body: TypedMatchArmBody::Expr(Box::new(TypedExpr::Float {
value: 1.0,
ty: QalaType::F64,
span: span(4),
})),
span: span(0),
};
assert_eq!(
arm.pattern,
Pattern::Variant {
name: "Circle".to_string(),
sub: vec![Pattern::Binding {
name: "r".to_string(),
span: span(1)
}],
span: span(0),
}
);
assert!(arm.guard.is_some());
}
#[test]
fn faithful_nodes_pipeline_match_orelse_interpolation_exist() {
let pipe = TypedExpr::Pipeline {
lhs: Box::new(TypedExpr::Int {
value: 1,
ty: QalaType::I64,
span: span(1),
}),
call: Box::new(TypedExpr::Ident {
name: "double".to_string(),
ty: QalaType::Function {
params: vec![QalaType::I64],
returns: Box::new(QalaType::I64),
},
span: span(3),
}),
ty: QalaType::I64,
span: span(0),
};
assert_eq!(pipe.ty(), &QalaType::I64);
assert_eq!(pipe.span(), span(0));
let m = TypedExpr::Match {
scrutinee: Box::new(TypedExpr::Bool {
value: true,
ty: QalaType::Bool,
span: span(5),
}),
arms: vec![TypedMatchArm {
pattern: Pattern::Bool {
value: true,
span: span(6),
},
guard: None,
body: TypedMatchArmBody::Expr(Box::new(TypedExpr::Int {
value: 1,
ty: QalaType::I64,
span: span(7),
})),
span: span(6),
}],
ty: QalaType::I64,
span: span(4),
};
assert_eq!(m.ty(), &QalaType::I64);
let or = TypedExpr::OrElse {
expr: Box::new(TypedExpr::Ident {
name: "x".to_string(),
ty: QalaType::Option(Box::new(QalaType::I64)),
span: span(9),
}),
fallback: Box::new(TypedExpr::Int {
value: 0,
ty: QalaType::I64,
span: span(10),
}),
ty: QalaType::I64,
span: span(8),
};
assert_eq!(or.ty(), &QalaType::I64);
let interp = TypedExpr::Interpolation {
parts: vec![
TypedInterpPart::Literal("hello, ".to_string()),
TypedInterpPart::Expr(TypedExpr::Ident {
name: "name".to_string(),
ty: QalaType::Str,
span: span(12),
}),
],
ty: QalaType::Str,
span: span(11),
};
assert_eq!(interp.ty(), &QalaType::Str);
}
}