Skip to content

Commit dad5310

Browse files
committed
Add guards to func definitions.
For example: ```trilogy func check n if n < 5 = ... func check n if n > 5 = ... func check n = { assert n == 5 } ```
1 parent 2586bc4 commit dad5310

9 files changed

Lines changed: 117 additions & 49 deletions

File tree

TODO.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
* Type-agnostic global functions (similar to core, but safe)
1313
* Consistent interfaces to individual core standard modules
1414
* Maybe adjust naming and organization of `core.c`, which is kind of scary conflictable
15-
* Guards on `func`
1615
* Improve error messages:
1716
* Eliminate places where panics are visible to the end user; should be covered by `yield`/`end` (rewrite spec I guess)
1817
* Review error handling in scanner/parser/IR, so all are nicely reported; maybe reimplement this using trait objects so it's not so spread out

testsuite/func-guards/main.tri

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
func check n if n < 5 = false
2+
func check n = n == 5
3+
4+
proc main!() {
5+
assert check 5
6+
assert check 5
7+
}

trilogy-ir/src/ir/function.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ pub struct Function {
88
pub span: Span,
99
pub head_span: Span,
1010
pub parameters: Vec<Expression>,
11+
pub guard: Option<Expression>,
1112
pub body: Expression,
1213
}
1314

@@ -22,13 +23,18 @@ impl Function {
2223
.into_iter()
2324
.map(|param| Expression::convert_pattern(converter, param))
2425
.collect();
26+
let guard = ast
27+
.head
28+
.guard
29+
.map(|guard| Expression::convert(converter, guard.expression));
2530
let body = Expression::builtin(span, Builtin::Return)
2631
.apply_to(span, Expression::convert(converter, ast.body));
2732
converter.pop_scope();
2833
Self {
2934
span,
3035
head_span,
3136
parameters,
37+
guard,
3238
body,
3339
}
3440
}
@@ -48,6 +54,7 @@ impl Function {
4854
head_span: ast.r#fn.span.union(ast.dot.span),
4955
span,
5056
parameters,
57+
guard: None,
5158
body,
5259
}
5360
}

trilogy-ir/src/ir/handler.rs

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use trilogy_parser::{Spanned, syntax};
77
pub struct Handler {
88
pub span: Span,
99
pub pattern: Expression,
10-
pub guard: Expression,
10+
pub guard: Option<Expression>,
1111
pub body: Expression,
1212
}
1313

@@ -60,10 +60,7 @@ impl Handler {
6060
let pattern = Expression::convert_pattern(converter, handler.pattern);
6161
let pattern =
6262
Expression::reference(pattern.span, effect.clone()).and(pattern.span, pattern);
63-
let guard = handler
64-
.guard
65-
.map(|ast| Expression::convert(converter, ast))
66-
.unwrap_or_else(|| Expression::boolean(span, true));
63+
let guard = handler.guard.map(|ast| Expression::convert(converter, ast));
6764
converter.scope.set_allow_resume_cancel(true);
6865
let body = Self::convert_strategy(converter, handler.strategy, effect);
6966
Self {
@@ -78,13 +75,12 @@ impl Handler {
7875
let else_span = handler.else_token().span;
7976
let effect = Identifier::temporary(converter, else_span);
8077
let pattern = Expression::reference(else_span, effect.clone());
81-
let guard = Expression::boolean(else_span, true);
8278
converter.scope.set_allow_resume_cancel(true);
8379
let body = Self::convert_strategy(converter, handler.strategy, effect);
8480
Self {
8581
span,
8682
pattern,
87-
guard,
83+
guard: None,
8884
body,
8985
}
9086
}

trilogy-ir/src/ir/match.rs

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,21 +29,20 @@ impl Match {
2929
let span = ast.r#else.span().union(ast.body.span());
3030
converter.push_scope();
3131
let pattern = Expression::wildcard(ast.r#else.span);
32-
let guard = Expression::boolean(ast.r#else.span, true);
3332
let body = Expression::convert(converter, ast.body);
3433
converter.pop_scope();
3534
cases.push(Case {
3635
span,
3736
pattern,
38-
guard,
37+
guard: None,
3938
body,
4039
});
4140
}
4241
None => {
4342
cases.push(Case {
4443
span,
4544
pattern: Expression::wildcard(span),
46-
guard: Expression::boolean(span, true),
45+
guard: None,
4746
body: Expression::unit(span),
4847
});
4948
}
@@ -67,21 +66,20 @@ impl Match {
6766
let span = ast.r#else.span().union(ast.body.span());
6867
converter.push_scope();
6968
let pattern = Expression::wildcard(ast.r#else.span);
70-
let guard = Expression::boolean(ast.r#else.span, true);
7169
let body = Expression::convert(converter, ast.body);
7270
converter.pop_scope();
7371
cases.push(Case {
7472
span,
7573
pattern,
76-
guard,
74+
guard: None,
7775
body,
7876
});
7977
}
8078
None => {
8179
cases.push(Case {
8280
span,
8381
pattern: Expression::wildcard(span),
84-
guard: Expression::boolean(span, true),
82+
guard: None,
8583
body: Expression::end(span),
8684
});
8785
}
@@ -94,7 +92,7 @@ impl Match {
9492
pub struct Case {
9593
pub span: Span,
9694
pub pattern: Expression,
97-
pub guard: Expression,
95+
pub guard: Option<Expression>,
9896
pub body: Expression,
9997
}
10098

@@ -109,8 +107,7 @@ impl Case {
109107
.unwrap_or_else(|| Expression::wildcard(case_span));
110108
let guard = ast
111109
.guard
112-
.map(|ast| Expression::convert(converter, ast.expression))
113-
.unwrap_or_else(|| Expression::boolean(case_span, true));
110+
.map(|ast| Expression::convert(converter, ast.expression));
114111
let body = match ast.body {
115112
FollowingExpression::Then(_, body) => Expression::convert(converter, body),
116113
FollowingExpression::Block(body) => Expression::convert_block(converter, body),

trilogy-ir/src/visitor/mod.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,9 @@ impl IrVisitable for Match {
298298
impl IrVisitable for Case {
299299
fn visit<V: IrVisitor>(&self, visitor: &mut V) {
300300
visitor.visit_pattern(&self.pattern);
301-
visitor.visit_expression(&self.guard);
301+
if let Some(guard) = &self.guard {
302+
visitor.visit_expression(guard);
303+
}
302304
visitor.visit_expression(&self.body);
303305
}
304306
}
@@ -308,6 +310,9 @@ impl IrVisitable for Function {
308310
for parameter in &self.parameters {
309311
visitor.visit_pattern(parameter);
310312
}
313+
if let Some(guard) = &self.guard {
314+
visitor.visit_expression(guard);
315+
}
311316
visitor.visit_expression(&self.body);
312317
}
313318
}
@@ -333,7 +338,9 @@ impl IrVisitable for Handled {
333338
impl IrVisitable for Handler {
334339
fn visit<V: IrVisitor>(&self, visitor: &mut V) {
335340
visitor.visit_pattern(&self.pattern);
336-
visitor.visit_expression(&self.guard);
341+
if let Some(guard) = &self.guard {
342+
visitor.visit_expression(guard);
343+
}
337344
visitor.visit_expression(&self.body);
338345
}
339346
}

trilogy-ir/src/visitor/references.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,9 @@ impl IrVisitor for References {
5858

5959
fn visit_case(&mut self, node: &Case) {
6060
self.ignore.extend(node.bindings());
61-
node.guard.visit(self);
61+
if let Some(guard) = &node.guard {
62+
guard.visit(self);
63+
}
6264
node.body.visit(self);
6365
}
6466

@@ -79,7 +81,9 @@ impl IrVisitor for References {
7981

8082
fn visit_handler(&mut self, node: &Handler) {
8183
self.ignore.extend(node.bindings());
82-
node.guard.visit(self);
84+
if let Some(guard) = &node.guard {
85+
guard.visit(self);
86+
}
8387
node.body.visit(self);
8488
}
8589

trilogy-llvm/src/expression/mod.rs

Lines changed: 42 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,12 @@ impl<'ctx> Codegen<'ctx> {
292292
// The handler works similar to a match expression, but matching against the effect
293293
// and doing much more control flow work.
294294
for handler in handlers {
295+
if matches!(
296+
handler.guard.as_ref().map(|g| &g.value),
297+
Some(Value::Boolean(false))
298+
) {
299+
continue;
300+
}
295301
let next_case_function = self.add_continuation("");
296302
let (go_to_next_case, next_case_cp) =
297303
self.capture_current_continuation(next_case_function, "when.next");
@@ -301,30 +307,34 @@ impl<'ctx> Codegen<'ctx> {
301307
{
302308
break;
303309
}
304-
let Some(guard_bool) = self.compile_expression(&handler.guard, "when.guard") else {
305-
self.become_continuation_point(next_case_cp);
306-
self.begin_next_function(next_case_function);
307-
continue;
308-
};
309-
let guard_flag = self.trilogy_boolean_untag(guard_bool, "");
310-
// NOTE: bool doesn't really need to be destroyed... but do it anyway
311-
self.trilogy_value_destroy(guard_bool);
312-
let body_block = self.context.append_basic_block(self.get_function(), "body");
313-
let next_block = self.context.append_basic_block(self.get_function(), "next");
314-
let body_cp = self.branch_continuation_point();
315-
self.builder
316-
.build_conditional_branch(guard_flag, body_block, next_block)
317-
.unwrap();
318-
let snapshot = self.snapshot_function_context();
319-
320-
self.builder.position_at_end(next_block);
321-
let go_next = self.use_temporary_clone(go_to_next_case).unwrap();
322-
self.void_call_continuation(go_next);
323-
324-
self.builder.position_at_end(body_block);
325-
self.destroy_owned_temporary(go_to_next_case);
326-
self.restore_function_context(snapshot);
327-
self.become_continuation_point(body_cp);
310+
if let Some(guard) = &handler.guard
311+
&& !matches!(guard.value, Value::Boolean(true))
312+
{
313+
let Some(guard_bool) = self.compile_expression(guard, "when.guard") else {
314+
self.become_continuation_point(next_case_cp);
315+
self.begin_next_function(next_case_function);
316+
continue;
317+
};
318+
let guard_flag = self.trilogy_boolean_untag(guard_bool, "");
319+
// NOTE: bool doesn't really need to be destroyed... but do it anyway
320+
self.trilogy_value_destroy(guard_bool);
321+
let body_block = self.context.append_basic_block(self.get_function(), "body");
322+
let next_block = self.context.append_basic_block(self.get_function(), "next");
323+
let body_cp = self.branch_continuation_point();
324+
self.builder
325+
.build_conditional_branch(guard_flag, body_block, next_block)
326+
.unwrap();
327+
let snapshot = self.snapshot_function_context();
328+
329+
self.builder.position_at_end(next_block);
330+
let go_next = self.use_temporary_clone(go_to_next_case).unwrap();
331+
self.void_call_continuation(go_next);
332+
333+
self.builder.position_at_end(body_block);
334+
self.destroy_owned_temporary(go_to_next_case);
335+
self.restore_function_context(snapshot);
336+
self.become_continuation_point(body_cp);
337+
}
328338
self.push_handler_scope(resume);
329339
if let Some(result) = self.compile_expression(&handler.body, "handler_result") {
330340
// If a handler runs off, it ends. Most handlers should choose to explicitly cancel
@@ -545,7 +555,10 @@ impl<'ctx> Codegen<'ctx> {
545555
for case in &expr.cases {
546556
// An unmatchable case can be skipped; rare this would occur, but easy to handle
547557
// since we're handling true specially anyway
548-
if matches!(case.guard.value, Value::Boolean(false)) {
558+
if matches!(
559+
case.guard.as_ref().map(|g| &g.value),
560+
Some(Value::Boolean(false))
561+
) {
549562
continue;
550563
}
551564

@@ -554,8 +567,10 @@ impl<'ctx> Codegen<'ctx> {
554567
self.capture_current_continuation(next_case_function, "match.next");
555568
self.compile_pattern_match(&case.pattern, discriminant, go_to_next_case)?;
556569

557-
if !matches!(case.guard.value, Value::Boolean(true)) {
558-
let Some(guard_bool) = self.compile_expression(&case.guard, "match.guard") else {
570+
if let Some(guard) = &case.guard
571+
&& !matches!(guard.value, Value::Boolean(true))
572+
{
573+
let Some(guard_bool) = self.compile_expression(guard, "match.guard") else {
559574
self.become_continuation_point(next_case_cp);
560575
self.begin_next_function(next_case_function);
561576
continue;

trilogy-llvm/src/function.rs

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@ use crate::Codegen;
22
use inkwell::values::{BasicValue, FunctionValue};
33
use source_span::Span;
44
use std::borrow::Borrow;
5-
use trilogy_ir::{Id, ir};
5+
use trilogy_ir::{
6+
Id,
7+
ir::{self, Expression, Value},
8+
};
69

710
impl<'ctx> Codegen<'ctx> {
811
fn write_function_accessor(
@@ -72,6 +75,13 @@ impl<'ctx> Codegen<'ctx> {
7275
'outer: for (i, overload) in overloads.iter().enumerate() {
7376
let overload = overload.borrow();
7477
assert_eq!(overload.parameters.len(), arity);
78+
if matches!(
79+
overload.guard.as_ref().map(|g| &g.value),
80+
Some(Value::Boolean(false))
81+
) {
82+
continue;
83+
}
84+
7585
self.set_span(overload.head_span);
7686

7787
let next_overload_function = self.add_continuation(if i == overloads.len() - 1 {
@@ -90,6 +100,32 @@ impl<'ctx> Codegen<'ctx> {
90100
break 'outer;
91101
}
92102
}
103+
104+
if let Some(guard) = &overload.guard
105+
&& !matches!(guard.value, Value::Boolean(true))
106+
{
107+
let Some(guard_bool) = self.compile_expression(guard, "match.guard") else {
108+
self.become_continuation_point(next_overload_cp);
109+
self.begin_next_function(next_overload_function);
110+
continue;
111+
};
112+
let guard_flag = self.trilogy_boolean_untag(guard_bool, "");
113+
self.trilogy_value_destroy(guard_bool); // NOTE: bool doesn't really need to be destroyed... but do it anyway
114+
let next_block = self.context.append_basic_block(self.get_function(), "next");
115+
let body_block = self.context.append_basic_block(self.get_function(), "body");
116+
let body_cp = self.branch_continuation_point();
117+
self.builder
118+
.build_conditional_branch(guard_flag, body_block, next_block)
119+
.unwrap();
120+
121+
self.builder.position_at_end(next_block);
122+
let go_next = self.use_temporary_clone(go_to_next_overload).unwrap();
123+
self.void_call_continuation(go_next);
124+
125+
self.builder.position_at_end(body_block);
126+
self.become_continuation_point(body_cp);
127+
}
128+
93129
self.destroy_owned_temporary(go_to_next_overload);
94130

95131
if let Some(value) = self.compile_expression(&overload.body, "") {

0 commit comments

Comments
 (0)