Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds loop expression #60

Merged
merged 3 commits into from
Nov 23, 2019
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
28 changes: 22 additions & 6 deletions crates/mun_codegen/src/ir/body.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,12 +97,10 @@ impl<'a, 'b, D: IrDatabase> BodyIrGenerator<'a, 'b, D> {
// in the first place. If the return type of the body is `never` there is no need to
// generate a return statement.
let ret_type = &self.infer[self.body.body_expr()];
if !ret_type.is_never() {
if let Some(value) = ret_value {
self.builder.build_return(Some(&value));
} else {
self.builder.build_return(None);
}
if let Some(value) = ret_value {
self.builder.build_return(Some(&value));
} else if !ret_type.is_never() {
self.builder.build_return(None);
}
}

Expand Down Expand Up @@ -133,6 +131,7 @@ impl<'a, 'b, D: IrDatabase> BodyIrGenerator<'a, 'b, D> {
else_branch,
} => self.gen_if(expr, *condition, *then_branch, *else_branch),
Expr::Return { expr: ret_expr } => self.gen_return(expr, *ret_expr),
Expr::Loop { body } => self.gen_loop(expr, *body),
_ => unimplemented!("unimplemented expr type {:?}", &body[expr]),
}
}
Expand Down Expand Up @@ -575,4 +574,21 @@ impl<'a, 'b, D: IrDatabase> BodyIrGenerator<'a, 'b, D> {

None
}

fn gen_loop(&mut self, _expr: ExprId, body_expr: ExprId) -> Option<BasicValueEnum> {
let context = self.module.get_context();
let loop_block = context.append_basic_block(&self.fn_value, "loop");

// Insert an explicit fall through from the current block to the loop
self.builder.build_unconditional_branch(&loop_block);

// Start generating code inside the loop
self.builder.position_at_end(&loop_block);
let _ = self.gen_expr(body_expr);

// Jump to the start of the loop
self.builder.build_unconditional_branch(&loop_block);

None
}
}
27 changes: 27 additions & 0 deletions crates/mun_codegen/src/snapshots/test__fibonacci_loop.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
source: crates/mun_codegen/src/test.rs
expression: "fn fibonacci(n:int):int {\n let a = 0;\n let b = 1;\n let i = 1;\n loop {\n if i > n {\n return a\n }\n let sum = a + b;\n a = b;\n b = sum;\n i += 1;\n }\n}"
---
; ModuleID = 'main.mun'
source_filename = "main.mun"

define i64 @fibonacci(i64) {
body:
br label %loop

loop: ; preds = %if_merge, %body
%b.0 = phi i64 [ 1, %body ], [ %add, %if_merge ]
%a.0 = phi i64 [ 0, %body ], [ %b.0, %if_merge ]
%i.0 = phi i64 [ 1, %body ], [ %add11, %if_merge ]
%greater = icmp sgt i64 %i.0, %0
br i1 %greater, label %then, label %if_merge

then: ; preds = %loop
ret i64 %a.0

if_merge: ; preds = %loop
%add = add i64 %a.0, %b.0
%add11 = add i64 %i.0, 1
br label %loop
}

15 changes: 15 additions & 0 deletions crates/mun_codegen/src/snapshots/test__loop_expr.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
source: crates/mun_codegen/src/test.rs
expression: "fn foo() {\n loop {}\n}"
---
; ModuleID = 'main.mun'
source_filename = "main.mun"

define void @foo() {
body:
br label %loop

loop: ; preds = %loop, %body
br label %loop
}

33 changes: 33 additions & 0 deletions crates/mun_codegen/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,28 @@ fn fibonacci() {
)
}

#[test]
fn fibonacci_loop() {
test_snapshot(
r#"
fn fibonacci(n:int):int {
let a = 0;
let b = 1;
let i = 1;
loop {
if i > n {
return a
}
let sum = a + b;
a = b;
b = sum;
i += 1;
}
}
"#,
)
}

#[test]
fn shadowing() {
test_snapshot(
Expand Down Expand Up @@ -294,6 +316,17 @@ fn true_is_true() {
);
}

#[test]
fn loop_expr() {
test_snapshot(
r#"
fn foo() {
loop {}
}
"#,
)
}

fn test_snapshot(text: &str) {
let text = text.trim().replace("\n ", "\n");

Expand Down
15 changes: 14 additions & 1 deletion crates/mun_hir/src/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use crate::code_model::src::{HasSource, Source};
use crate::name::AsName;
use crate::type_ref::{TypeRef, TypeRefBuilder, TypeRefId, TypeRefMap, TypeRefSourceMap};
pub use mun_syntax::ast::PrefixOp as UnaryOp;
use mun_syntax::ast::{ArgListOwner, BinOp, NameOwner, TypeAscriptionOwner};
use mun_syntax::ast::{ArgListOwner, BinOp, LoopBodyOwner, NameOwner, TypeAscriptionOwner};
use mun_syntax::{ast, AstNode, AstPtr, T};
use rustc_hash::FxHashMap;
use std::ops::Index;
Expand Down Expand Up @@ -201,6 +201,9 @@ pub enum Expr {
Return {
expr: Option<ExprId>,
},
Loop {
body: ExprId,
},
Literal(Literal),
}

Expand Down Expand Up @@ -290,6 +293,9 @@ impl Expr {
f(*expr);
}
}
Expr::Loop { body } => {
f(*body);
}
}
}
}
Expand Down Expand Up @@ -453,6 +459,7 @@ where
fn collect_expr(&mut self, expr: ast::Expr) -> ExprId {
let syntax_ptr = AstPtr::new(&expr.clone());
match expr.kind() {
ast::ExprKind::LoopExpr(expr) => self.collect_loop(expr),
ast::ExprKind::ReturnExpr(r) => self.collect_return(r),
ast::ExprKind::BlockExpr(b) => self.collect_block(b),
ast::ExprKind::Literal(e) => {
Expand Down Expand Up @@ -627,6 +634,12 @@ where
self.alloc_expr(Expr::Return { expr }, syntax_node_ptr)
}

fn collect_loop(&mut self, expr: ast::LoopExpr) -> ExprId {
let syntax_node_ptr = AstPtr::new(&expr.clone().into());
let body = self.collect_block_opt(expr.loop_body());
self.alloc_expr(Expr::Loop { body }, syntax_node_ptr)
}

fn finish(mut self) -> (Body, BodySourceMap) {
let (type_refs, type_ref_source_map) = self.type_ref_builder.finish();
let body = Body {
Expand Down
49 changes: 39 additions & 10 deletions crates/mun_hir/src/ty/infer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,10 +215,30 @@ impl<'a, D: HirDatabase> InferenceResultBuilder<'a, D> {
);
}

/// Infers the type of the `tgt_expr`
fn infer_expr(&mut self, tgt_expr: ExprId, expected: &Expectation) -> Ty {
let ty = self.infer_expr_inner(tgt_expr, expected);
if !expected.is_none() && ty != expected.ty {
self.diagnostics.push(InferenceDiagnostic::MismatchedTypes {
expected: expected.ty.clone(),
found: ty.clone(),
id: tgt_expr,
});
};

ty
}

/// Infer type of expression with possibly implicit coerce to the expected type. Return the type
/// after possible coercion.
/// after possible coercion. Adds a diagnostic message if coercion failed.
fn infer_expr_coerce(&mut self, expr: ExprId, expected: &Expectation) -> Ty {
let ty = self.infer_expr_inner(expr, expected);
self.coerce_expr_ty(expr, ty, expected)
}

/// Performs implicit coercion of the specified `Ty` to an expected type. Returns the type after
/// possible coercion. Adds a diagnostic message if coercion failed.
fn coerce_expr_ty(&mut self, expr: ExprId, ty: Ty, expected: &Expectation) -> Ty {
if !self.coerce(&ty, &expected.ty) {
self.diagnostics.push(InferenceDiagnostic::MismatchedTypes {
expected: expected.ty.clone(),
Expand Down Expand Up @@ -251,7 +271,7 @@ impl<'a, D: HirDatabase> InferenceResultBuilder<'a, D> {
} => self.infer_if(tgt_expr, &expected, *condition, *then_branch, *else_branch),
Expr::BinaryOp { lhs, rhs, op } => match op {
Some(op) => {
let lhs_ty = self.infer_expr_coerce(*lhs, &Expectation::none());
let lhs_ty = self.infer_expr(*lhs, &Expectation::none());
if let BinaryOp::Assignment { op: _op } = op {
let resolver =
expr::resolver_for_expr(self.body.clone(), self.db, tgt_expr);
Expand All @@ -263,7 +283,7 @@ impl<'a, D: HirDatabase> InferenceResultBuilder<'a, D> {
}
};
let rhs_expected = op::binary_op_rhs_expectation(*op, lhs_ty);
let rhs_ty = self.infer_expr_coerce(*rhs, &Expectation::has_type(rhs_expected));
let rhs_ty = self.infer_expr(*rhs, &Expectation::has_type(rhs_expected));
op::binary_op_return_ty(*op, rhs_ty)
}
_ => Ty::Unknown,
Expand All @@ -278,14 +298,18 @@ impl<'a, D: HirDatabase> InferenceResultBuilder<'a, D> {
},
Expr::Return { expr } => {
if let Some(expr) = expr {
self.infer_expr_coerce(*expr, &Expectation::has_type(self.return_ty.clone()));
self.infer_expr(*expr, &Expectation::has_type(self.return_ty.clone()));
} else if self.return_ty != Ty::Empty {
self.diagnostics
.push(InferenceDiagnostic::ReturnMissingExpression { id: tgt_expr });
}

Ty::simple(TypeCtor::Never)
}
Expr::Loop { body } => {
self.infer_expr(*body, &Expectation::has_type(Ty::Empty));
Ty::simple(TypeCtor::Never)
}
_ => Ty::Unknown,
// Expr::UnaryOp { expr: _, op: _ } => {}
// Expr::Block { statements: _, tail: _ } => {}
Expand All @@ -304,7 +328,7 @@ impl<'a, D: HirDatabase> InferenceResultBuilder<'a, D> {
then_branch: ExprId,
else_branch: Option<ExprId>,
) -> Ty {
self.infer_expr_coerce(
self.infer_expr(
condition,
&Expectation::has_type(Ty::simple(TypeCtor::Bool)),
);
Expand Down Expand Up @@ -346,7 +370,7 @@ impl<'a, D: HirDatabase> InferenceResultBuilder<'a, D> {
args: &[ExprId],
_expected: &Expectation,
) -> Ty {
let callee_ty = self.infer_expr_coerce(callee, &Expectation::none());
let callee_ty = self.infer_expr(callee, &Expectation::none());
let (param_tys, ret_ty) = match callee_ty.callable_sig(self.db) {
Some(sig) => (sig.params().to_vec(), sig.ret().clone()),
None => {
Expand Down Expand Up @@ -463,16 +487,21 @@ impl<'a, D: HirDatabase> InferenceResultBuilder<'a, D> {
self.infer_pat(*pat, ty);
}
Statement::Expr(expr) => {
if let ty_app!(TypeCtor::Never) =
self.infer_expr_coerce(*expr, &Expectation::none())
{
if let ty_app!(TypeCtor::Never) = self.infer_expr(*expr, &Expectation::none()) {
diverges = true;
};
}
}
}
let ty = if let Some(expr) = tail {
self.infer_expr_coerce(expr, expected)
// Perform coercion of the trailing expression unless the expression has a Never return
// type because we want the block to get the Never type in that case.
let ty = self.infer_expr_inner(expr, expected);
if let ty_app!(TypeCtor::Never) = ty {
Ty::simple(TypeCtor::Never)
} else {
self.coerce_expr_ty(expr, ty, expected)
}
} else {
Ty::Empty
};
Expand Down
7 changes: 7 additions & 0 deletions crates/mun_hir/src/ty/snapshots/tests__infer_loop.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
source: crates/mun_hir/src/ty/tests.rs
expression: "fn foo() {\n loop {}\n}"
---
[9; 24) '{ loop {} }': never
[15; 22) 'loop {}': never
[20; 22) '{}': nothing
11 changes: 11 additions & 0 deletions crates/mun_hir/src/ty/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,17 @@ fn update_operators() {
)
}

#[test]
fn infer_loop() {
infer_snapshot(
r#"
fn foo() {
loop {}
}
"#,
)
}

fn infer_snapshot(text: &str) {
let text = text.trim().replace("\n ", "\n");
insta::assert_snapshot!(insta::_macro_support::AutoName, infer(&text), &text);
Expand Down
27 changes: 27 additions & 0 deletions crates/mun_runtime/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,33 @@ fn fibonacci() {
assert_invoke_eq!(i64, 987, driver, "fibonacci", 16i64);
}

#[test]
fn fibonacci_loop() {
let mut driver = TestDriver::new(
r#"
fn fibonacci(n:int):int {
let a = 0;
let b = 1;
let i = 1;
loop {
if i > n {
return a
}
let sum = a + b;
a = b;
b = sum;
i += 1;
}
}
"#,
);

assert_invoke_eq!(i64, 5, driver, "fibonacci", 5i64);
assert_invoke_eq!(i64, 89, driver, "fibonacci", 11i64);
assert_invoke_eq!(i64, 987, driver, "fibonacci", 16i64);
assert_invoke_eq!(i64, 46368, driver, "fibonacci", 24i64);
}

#[test]
fn true_is_true() {
let mut driver = TestDriver::new(
Expand Down
Loading