Skip to content
This repository was archived by the owner on Feb 18, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 12 additions & 6 deletions dora-parser/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1257,17 +1257,17 @@ impl Expr {
id: NodeId,
pos: Position,
span: Span,
cond: Box<Expr>,
then_block: Box<Expr>,
cond_head: Box<StmtLetType>,
branches: Vec<Branch>,
else_block: Option<Box<Expr>>,
) -> Expr {
Expr::If(ExprIfType {
id,
pos,
span,

cond,
then_block,
cond_head,
branches,
else_block,
})
}
Expand Down Expand Up @@ -1897,11 +1897,17 @@ pub struct ExprIfType {
pub pos: Position,
pub span: Span,

pub cond: Box<Expr>,
pub then_block: Box<Expr>,
pub cond_head: Box<StmtLetType>,
pub branches: Vec<Branch>,
pub else_block: Option<Box<Expr>>,
}

#[derive(Clone, Debug)]
pub struct Branch {
pub cond_tail: Option<Box<Expr>>,
pub then_block: Box<Expr>,
}

#[derive(Clone, Debug)]
pub struct ExprTupleType {
pub id: NodeId,
Expand Down
24 changes: 15 additions & 9 deletions dora-parser/src/ast/dump.rs
Original file line number Diff line number Diff line change
Expand Up @@ -516,16 +516,22 @@ impl<'a> AstDumper<'a> {

self.indent(|d| {
d.indent(|d| {
d.dump_expr(&expr.cond);
});
dump!(d, "then");
d.indent(|d| {
d.dump_expr(&expr.then_block);
});
dump!(d, "else");
d.indent(|d| {
d.dump_expr(&expr.then_block);
d.dump_expr(&expr.cond_head.expr.as_ref().unwrap());
});
for branch in &expr.branches {
dump!(d, "then");
d.indent(|d| {
if let Some(cond_tail) = &branch.cond_tail {
d.dump_expr(cond_tail);
}
});
}
if let Some(else_block) = &expr.else_block {
dump!(d, "else");
d.indent(|d| {
d.dump_expr(else_block);
});
}
});
}

Expand Down
9 changes: 7 additions & 2 deletions dora-parser/src/ast/visit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -318,8 +318,13 @@ pub fn walk_expr<V: Visitor>(v: &mut V, e: &Expr) {
}

Expr::If(ref value) => {
v.visit_expr(&value.cond);
v.visit_expr(&value.then_block);
v.visit_stmt(&Stmt::Let(value.cond_head.as_ref().clone()));
for branch in &value.branches {
if let Some(cond_tail) = &branch.cond_tail {
v.visit_expr(cond_tail);
}
v.visit_expr(&branch.then_block);
}

if let Some(ref b) = value.else_block {
v.visit_expr(b);
Expand Down
110 changes: 104 additions & 6 deletions dora-parser/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1278,7 +1278,7 @@ impl<'a> Parser<'a> {

let cond = self.parse_expression()?;

let then_block = self.parse_block()?;
let branches = self.parse_branches()?;

let else_block = if self.token.is(TokenKind::Else) {
self.advance_token()?;
Expand All @@ -1294,16 +1294,52 @@ impl<'a> Parser<'a> {

let span = self.span_from(start);

let let_pattern = LetPattern::Ident(LetIdentType {
id: self.generate_id(),
pos,
span,
mutable: false,
name: self.interner.intern(TokenKind::DotDotDot.name()),
});
let let_condition = StmtLetType {
id: self.generate_id(),
pos,
span,
pattern: Box::from(let_pattern),
data_type: None,
expr: Some(cond),
};
Ok(Box::new(Expr::create_if(
self.generate_id(),
pos,
span,
cond,
then_block,
Box::from(let_condition),
branches,
else_block,
)))
}

fn parse_branches(&mut self) -> Result<Vec<Branch>, ParseErrorAndPos> {
let mut branches = Vec::new();
if self.token.is(TokenKind::DotDotDot) {
while self.token.is(TokenKind::DotDotDot) {
let cond_tail = Some(self.parse_expression()?);
let then_block = self.parse_block()?;
branches.push(Branch {
cond_tail,
then_block,
})
}
} else {
let then_block = self.parse_block()?;
branches.push(Branch {
cond_tail: None,
then_block,
})
}
Ok(branches)
}

fn parse_match(&mut self) -> ExprResult {
let start = self.token.span.start();
let pos = self.expect_token(TokenKind::Match)?.position;
Expand Down Expand Up @@ -1718,6 +1754,7 @@ impl<'a> Parser<'a> {
TokenKind::LParen => self.parse_parentheses(),
TokenKind::LBrace => self.parse_block(),
TokenKind::If => self.parse_if(),
TokenKind::DotDotDot => self.parse_dotdotdot(),
TokenKind::LitChar(_) => self.parse_lit_char(),
TokenKind::LitInt(_, _, _) => self.parse_lit_int(),
TokenKind::LitFloat(_, _) => self.parse_lit_float(),
Expand All @@ -1734,6 +1771,20 @@ impl<'a> Parser<'a> {
}
}

fn parse_dotdotdot(&mut self) -> ExprResult {
let pos = self.token.position;
let span = self.token.span;
self.expect_token(TokenKind::DotDotDot)?;

Ok(Box::new(Expr::create_ident(
self.generate_id(),
pos,
span,
self.interner.intern(TokenKind::DotDotDot.name()),
None,
)))
}

fn parse_identifier(&mut self) -> ExprResult {
let pos = self.token.position;
let span = self.token.span;
Expand Down Expand Up @@ -2783,7 +2834,7 @@ mod tests {
let (expr, _) = parse_expr("if true { 2; } else { 3; }");
let ifexpr = expr.to_if().unwrap();

assert!(ifexpr.cond.is_lit_bool());
assert!(ifexpr.cond_head.expr.as_ref().unwrap().is_lit_bool());
assert!(ifexpr.else_block.is_some());
}

Expand All @@ -2792,7 +2843,7 @@ mod tests {
let (expr, _) = parse_expr("if true { 2; }");
let ifexpr = expr.to_if().unwrap();

assert!(ifexpr.cond.is_lit_bool());
assert!(ifexpr.cond_head.expr.as_ref().unwrap().is_lit_bool());
assert!(ifexpr.else_block.is_none());
}

Expand Down Expand Up @@ -3148,7 +3199,7 @@ mod tests {
fn parse_struct_lit_if() {
let (expr, _) = parse_expr("if i < n { }");
let ifexpr = expr.to_if().unwrap();
let bin = ifexpr.cond.to_bin().unwrap();
let bin = ifexpr.cond_head.expr.as_ref().unwrap().to_bin().unwrap();

assert!(bin.lhs.is_ident());
assert!(bin.rhs.is_ident());
Expand Down Expand Up @@ -3481,6 +3532,53 @@ mod tests {
);
}

#[test]
fn parse_if_pattern() {
let (expr, _) = parse_expr(
"if true
... == 5 { \"\" }
... == 6.0 { '2' }
else { 4 }",
);

let expr = expr.to_if().unwrap();

let branch0 = &expr.branches[0];
let branch0_cond_tail = &branch0.cond_tail.as_ref().unwrap().to_bin().unwrap();
assert!(branch0_cond_tail.lhs.is_ident());
assert!(branch0_cond_tail.rhs.is_lit_int());
assert!(branch0
.then_block
.to_block()
.unwrap()
.expr
.as_ref()
.unwrap()
.is_lit_str());

let branch1 = &expr.branches[1];
let branch1_cond_tail = &branch1.cond_tail.as_ref().unwrap().to_bin().unwrap();
assert!(branch1_cond_tail.lhs.is_ident());
assert!(branch1_cond_tail.rhs.is_lit_float());
assert!(branch1
.then_block
.to_block()
.unwrap()
.expr
.as_ref()
.unwrap()
.is_lit_char());

let else_block = expr.else_block.as_ref().unwrap();
assert!(else_block
.to_block()
.unwrap()
.expr
.as_ref()
.unwrap()
.is_lit_int())
}

#[test]
fn parse_tuple() {
let (expr, _) = parse_expr("(1,)");
Expand Down
100 changes: 73 additions & 27 deletions dora/src/language/fctbodyck/body.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ use crate::language::{report_sym_shadow, TypeParamContext};

use dora_parser::ast;
use dora_parser::ast::visit::Visitor;
use dora_parser::ast::Expr;
use dora_parser::interner::Name;
use dora_parser::lexer::position::Position;
use dora_parser::lexer::token::{FloatSuffix, IntBase, IntSuffix};
Expand Down Expand Up @@ -809,36 +810,46 @@ impl<'a> TypeCheck<'a> {
}

fn check_expr_if(&mut self, expr: &ast::ExprIfType, expected_ty: SourceType) -> SourceType {
let expr_type = self.check_expr(&expr.cond, SourceType::Any);
let let_stmt = expr.cond_head.as_ref();
self.check_stmt_let(let_stmt);
let mut branch_types = Vec::new();
let mut require_cond_head_is_bool = false;
for branch in &expr.branches {
if branch.cond_tail.is_some() {
let cond_tail = branch.cond_tail.as_ref().unwrap();
let cond_tail_type = self.check_expr(&cond_tail, expected_ty.clone());
self.check_if_condition_is_bool(cond_tail_type, cond_tail);
} else {
// if any branch is empty, the condition head needs to be of type bool
require_cond_head_is_bool = true;
}

if !expr_type.is_bool() && !expr_type.is_error() {
let expr_type = expr_type.name_fct(self.sa, self.fct);
let msg = ErrorMessage::IfCondType(expr_type);
self.sa.diag.lock().report(self.file_id, expr.pos, msg);
branch_types.push((
self.check_expr(&branch.then_block, SourceType::Any),
expr_always_returns(&branch.then_block),
));
}
if require_cond_head_is_bool {
let cond_expr = let_stmt.expr.as_ref().unwrap();
let cond_type = self.analysis.ty(cond_expr.id());
self.check_if_condition_is_bool(cond_type, cond_expr);
}
let merged_type = if expr.else_block.is_some() {
let else_block = expr.else_block.as_ref().unwrap();
branch_types.push((
self.check_expr(else_block, expected_ty),
expr_always_returns(else_block),
));

let then_type = self.check_expr(&expr.then_block, expected_ty.clone());

let merged_type = if let Some(ref else_block) = expr.else_block {
let else_type = self.check_expr(else_block, expected_ty);

if expr_always_returns(&expr.then_block) {
else_type
} else if expr_always_returns(else_block) {
then_type
} else if then_type.is_error() {
else_type
} else if else_type.is_error() {
then_type
} else if !then_type.allows(self.sa, else_type.clone()) {
let then_type_name = then_type.name_fct(self.sa, self.fct);
let else_type_name = else_type.name_fct(self.sa, self.fct);
let msg = ErrorMessage::IfBranchTypesIncompatible(then_type_name, else_type_name);
self.sa.diag.lock().report(self.file_id, expr.pos, msg);
then_type
} else {
then_type
}
branch_types
.iter()
.fold((SourceType::Error, true), |t1, t2| {
(
self.merge_branch_types(expr, t1.0, t1.1, t2.clone().0, t2.1),
false,
)
})
.0
} else {
SourceType::Unit
};
Expand All @@ -848,6 +859,41 @@ impl<'a> TypeCheck<'a> {
merged_type
}

fn check_if_condition_is_bool(&mut self, cond_type: SourceType, cond: &Box<Expr>) {
if !cond_type.is_bool() && !cond_type.is_error() {
let expr_type = cond_type.name_fct(self.sa, self.fct);
let msg = ErrorMessage::IfCondType(expr_type);
self.sa.diag.lock().report(self.file_id, cond.pos(), msg);
}
}

fn merge_branch_types(
&mut self,
expr: &ast::ExprIfType,
type1: SourceType,
always_returns1: bool,
type2: SourceType,
always_returns2: bool,
) -> SourceType {
if always_returns1 {
type2
} else if always_returns2 {
type1
} else if type1.is_error() {
type2
} else if type2.is_error() {
type1
} else if !type1.allows(self.sa, type2.clone()) {
let then_type_name = type1.name_fct(self.sa, self.fct);
let else_type_name = type2.name_fct(self.sa, self.fct);
let msg = ErrorMessage::IfBranchTypesIncompatible(then_type_name, else_type_name);
self.sa.diag.lock().report(self.file_id, expr.pos, msg);
type1
} else {
type1
}
}

fn check_expr_ident(&mut self, e: &ast::ExprIdentType, expected_ty: SourceType) -> SourceType {
let sym = self.symtable.get(e.name);

Expand Down
2 changes: 1 addition & 1 deletion dora/src/language/fctbodyck/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ fn type_if() {
ok("fun x() { if false { } }");
err(
"fun x() { if 4i32 { } }",
pos(1, 11),
pos(1, 14),
ErrorMessage::IfCondType("Int32".into()),
);
}
Expand Down
Loading