From 607847abc3e9a0eb6737414fee24711896e632ca Mon Sep 17 00:00:00 2001 From: John Doty Date: Sun, 28 Jan 2024 22:12:30 -0800 Subject: [PATCH 1/3] [fine] ICE sorts errors on reporting --- fine/src/semantics.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fine/src/semantics.rs b/fine/src/semantics.rs index e55a44a6..561fb9fe 100644 --- a/fine/src/semantics.rs +++ b/fine/src/semantics.rs @@ -1741,7 +1741,7 @@ impl<'a> Semantics<'a> { eprintln!("\n{}", self.syntax_tree.dump(true)); { - let errors = self.errors.borrow(); + let errors = self.snapshot_errors(); if errors.len() == 0 { eprintln!("There were no errors reported during checking.\n"); } else { From f634e4da186301f3517eb8a59cc331262c323875 Mon Sep 17 00:00:00 2001 From: John Doty Date: Sun, 28 Jan 2024 22:12:44 -0800 Subject: [PATCH 2/3] [fine] Don't panic on this parse error --- fine/src/parser.rs | 15 +++++++++++---- .../expression/errors/broken_conditional.fine | 8 ++++++++ 2 files changed, 19 insertions(+), 4 deletions(-) create mode 100644 fine/tests/expression/errors/broken_conditional.fine diff --git a/fine/src/parser.rs b/fine/src/parser.rs index 1e0101c5..e8bd273d 100644 --- a/fine/src/parser.rs +++ b/fine/src/parser.rs @@ -430,7 +430,7 @@ impl<'a> CParser<'a> { } fn expect_start(&mut self, kind: TokenKind) { - assert!(self.eat(kind)); + assert!(self.eat(kind), "should have started with {kind:?}"); } fn advance_with_error(&mut self, error: T) -> MarkClosed @@ -708,7 +708,7 @@ fn block(p: &mut CParser) { while !p.at(TokenKind::RightBrace) && !p.eof() { statement(p); } - p.expect(TokenKind::RightBrace, "expect '}' to start a block"); + p.expect(TokenKind::RightBrace, "expect '}' to end a block"); p.end(m, TreeKind::Block); } @@ -756,6 +756,7 @@ fn statement_return(p: &mut CParser) { let m = p.start(); p.expect_start(TokenKind::Return); + // TODO: Make expression optional if we're returning () expression(p); if !p.at(TokenKind::RightBrace) { p.expect(TokenKind::Semicolon, "expect ';' to end a return statement"); @@ -938,13 +939,19 @@ fn conditional(p: &mut CParser) -> MarkClosed { p.expect_start(TokenKind::If); expression(p); - block(p); + if p.at(TokenKind::LeftBrace) { + block(p) + } else { + p.error("expected a block after `if`") + } if p.eat(TokenKind::Else) { if p.at(TokenKind::If) { // Don't require another block, just jump right into the conditional. conditional(p); - } else { + } else if p.at(TokenKind::LeftBrace) { block(p); + } else { + p.error("expected a block after `else`") } } diff --git a/fine/tests/expression/errors/broken_conditional.fine b/fine/tests/expression/errors/broken_conditional.fine new file mode 100644 index 00000000..84efd0cb --- /dev/null +++ b/fine/tests/expression/errors/broken_conditional.fine @@ -0,0 +1,8 @@ +fun test() { + if true true { } +} + +// NOTE: These errors should be better +// @expect-errors: +// | 2:10: Error at 'true': expected a block after `if` +// | 2:15: Error at '{': expect ';' to end an expression statement From 019d780f85d6d331ff8b75599de1ba53c36b1c7c Mon Sep 17 00:00:00 2001 From: John Doty Date: Sun, 28 Jan 2024 22:15:13 -0800 Subject: [PATCH 3/3] [fine] Design for "intersection" types --- fine/tests/expression/alternates.fine | 129 ++++++++++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 fine/tests/expression/alternates.fine diff --git a/fine/tests/expression/alternates.fine b/fine/tests/expression/alternates.fine new file mode 100644 index 00000000..8ee2ebb4 --- /dev/null +++ b/fine/tests/expression/alternates.fine @@ -0,0 +1,129 @@ +// Examples of alternate types/union types/heterogeneous types. +class Foo { + x: f64; +} + +class Bar { + y: f64; +} + +fun extract_value(v: Foo or Bar) -> f64 { + if match v as Foo { + v.x // Magic type binding! + } else as Bar { + v.y // Same magic re-binding of the variable to the new type + } // No error; exhaustivity analysis should work. +} + +// This is Bob Nystrom's example from +// https://journal.stuffwithstuff.com/2023/08/04/representing-heterogeneous-data/ +// +class MeleeWeapon { + damage: f64; +} + +class RangedWeapon { + minRange: f64; + maxRange: f64; +} + +class Monster { + health: f64; +} + +fun print(x:string) {} + +fun in_range(weapon: MeleeWeapon or RangedWeapon, distance: f64) { + if match weapon as w : RangedWeapon { + distance >= w.minRange and distance <= w.maxRange + } else { + distance == 1 + } +} + +fun attack(weapon: MeleeWeapon or RangedWeapon, monster: Monster, distance: f64) { + // This is worse but it works. + if match weapon as MeleeWeapon and distance > 1 or + match weapon as w:RangedWeapon and (distance < w.minRange or distance > w.maxRange) { + print("You are out of range") + return + } + + // Bob says he doesn't want to do flow analysis but we're doing all our + // tricks with variables and scoping here, with one *big* bit of magic which + // is... + // + // NOTE: special syntax here: `identifier` as `TypeExpression` ALMOST means + // `identifier as identifier : TypeExpression` as the shorthand for checking + // local variables. The *almost* part is that the effective type of the + // variable changes but not the binding. (Is this what we want?) + let damage = if match weapon as w: MeleeWeapon { + roll_dice(w.damage) + } else as w: RangedWeapon { + // This is the trick here: else as re-uses the expression from the match + // and so we can do exhaustivity analysis. + w.maxRange - w.minRange + }; + + if monster.health <= damage { + print("You kill the monster!"); + monster.health = 0 + } else { + print("You wound the monster."); + monster.health = monster.health - damage + } +} + +fun more_examples(weapon: MeleeWeapon or RangedWeapon) -> f64 or () { + if match weapon as w: RangedWeapon and w.maxRange > 10 { + // w is still in scope here; the `and` is bound into a predicate expression + // and breaks exhaustivity + w.minRange + } +} + +// Some fun with iterators + +class Iterator { + current: f64; + + fun next(self) -> f64 or () { + if self.current < 10 { + let result = self.current; + self.current = self.current + 1; + return result; + } + } +} + +fun test() -> f64 { + let sum = 0; + + // A single step of an iterator... + let it = new Iterator { current: 0 }; + if match it.next() as v: f64 { + sum = sum + v; + } + + // Looping by hand over an iterator is pretty clean. + let it = new Iterator { current: 0 }; + while match it.next() as v: f64 { + sum = sum + v; + } + + // Unroll by hand... + let it = new Iterator { current: 0 }; + loop { + if match it.next() as v: f64 { + sum = sum + v; + } else { + return sum; + } + } + + // Not in this test but `for` over an object should turn into something + // like the above. +} + +// @ignore WIP +// @no-errors \ No newline at end of file