Skip to content

Commit

Permalink
fix(compiler): Properly handle value restriction on function applicat…
Browse files Browse the repository at this point in the history
…ion & mutable vars (#988)

fix(compiler): Ensure TExpApp is always expansive in Grain
fix(runtime): Add types to boxed GC functions to avoid weak type errors
chore(tests): Add regression test for type checker bug
chore(tests): Remove cyclical GC test that does not work (ref #989)
chore(tests): Remove invalid code via type checking in tests (ref #989)
chore(tests): Add test for weak variable error when exporting boxed function
fix(compiler): Handle let-mut value restriction, such that mutable lets are always expansive

Co-authored-by: Blaine Bublitz <[email protected]>
Co-authored-by: Oscar Spencer <[email protected]>
  • Loading branch information
3 people committed Oct 20, 2021
1 parent c404fcf commit ef0a69f
Show file tree
Hide file tree
Showing 7 changed files with 23 additions and 36 deletions.
8 changes: 4 additions & 4 deletions compiler/src/typed/typecore.re
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,7 @@ let rec is_nonexpansive = exp =>
| TExpLambda(_)
| TExpNull => true
| TExpTuple(es) => List.for_all(is_nonexpansive, es)
| TExpLet(rec_flag, mut_flag, binds) =>
| TExpLet(rec_flag, Immutable, binds) =>
List.for_all(vb => is_nonexpansive(vb.vb_expr), binds)
| TExpMatch(e, cases, _) =>
is_nonexpansive(e)
Expand All @@ -360,8 +360,6 @@ let rec is_nonexpansive = exp =>
| TExpIf(c, t, f) => is_nonexpansive(t) && is_nonexpansive(f)
| TExpWhile(c, b) => is_nonexpansive(b)
| TExpBlock([_, ..._] as es) => is_nonexpansive(last(es))
| TExpApp(e, args) =>
is_nonexpansive(e) && List.for_all(is_nonexpansive, args)
| TExpConstruct(_, _, el) => List.for_all(is_nonexpansive, el)
| _ => false
};
Expand Down Expand Up @@ -1937,9 +1935,11 @@ and type_let =
pat_list
(List.map2 (fun (attrs, _) e -> attrs, e) spatl exp_list);*/
end_def();
let mutable_let = mut_flag == Mutable;
List.iter2(
(pat, exp) =>
if (!is_nonexpansive(exp)) {
// All mutable bindings should be treated as expansive
if (mutable_let || !is_nonexpansive(exp)) {
iter_pattern(pat => generalize_expansive(env, pat.pat_type), pat);
},
pat_list,
Expand Down
10 changes: 0 additions & 10 deletions compiler/test/input/recursive-equal-box.gr
Original file line number Diff line number Diff line change
@@ -1,15 +1,5 @@
export enum Opt<a> { None, Some(a) }

let z = box(None)
z := Some(z)

let y = box(None)

assert !(z == y)

y := Some(y)
assert z == y

export enum R { Atom, Recursive(Box<Opt<R>>) }

let t = box(None)
Expand Down
15 changes: 1 addition & 14 deletions compiler/test/input/recursive-equal-mut.gr
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,7 @@

export enum Opt<a> { None, Some(a) }

let mut z = None
z = Some(z)

let mut y = None

assert !(z == y)

y = Some(y)
assert z == y

export record Rec {
int: Number,
mut r: Opt<Rec>
}
export record Rec { int: Number, mut r: Opt<Rec> }

let a = { int: 5, r: None }
let b = { int: 5, r: None }
Expand Down
10 changes: 10 additions & 0 deletions compiler/test/suites/basic_functionality.re
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,16 @@ describe("basic functionality", ({test}) => {
"let foo = (if (false) { ignore(5) }); let bar = foo + 5; bar",
"has type Void but",
);
assertCompileError(
"value_restriction",
{|let f = () => x => x; let id = f(); let a = id(1); let b = id("a")|},
"has type String but",
);
assertCompileError(
"exports_weak_types",
{|export let f = box(x => 0)|},
"type variables that cannot be generalized",
);
assertSnapshot("int32_1", "42l");
assertSnapshot("int64_1", "99999999999999999L");
assertSnapshot("int64_pun_1", "9999999 * 99999999");
Expand Down
6 changes: 0 additions & 6 deletions compiler/test/suites/gc.re
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,6 @@ describe("garbage collection", ({test}) => {
160,
"let f = (() => (1, 2));\n {\n f();\n f();\n f();\n f()\n }",
);
/* Test that cyclic tuples are GC'd properly */
assertRunGC(
"gc2",
256,
"enum Opt<x> { None, Some(x) };\n let f = (() => {\n let x = (box(None), 2);\n let (fst, _) = x\n fst := Some(x)\n });\n {\n f();\n let x = (1, 2);\n x\n }",
);
/* https://github.com/grain-lang/grain/issues/774 */
assertRunGC(
"gc3",
Expand Down
6 changes: 6 additions & 0 deletions compiler/test/suites/let_mut.re
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,12 @@ describe("let mut", ({test}) => {
"record Rec {foo: Number, bar: Bool}; let {foo, bar} = {foo: 1, bar: false}; foo = 6",
"The identifier foo was not declared mutable",
);
// value restriction
assertCompileError(
"let-mut_err_value_restriction",
"enum Baz<a> { Foo(a), Bar }; let mut a = Bar; a = Foo(1); a = Foo(false)",
"an expression was expected of type Baz<Number>",
);
/* Operations on mutable `Number`s */
assertSnapshot("let-mut_addition1", "let mut b = 4; b = b + 19");
assertSnapshot("let-mut_addition2", "let mut b = 4; b = b + 19; b");
Expand Down
4 changes: 2 additions & 2 deletions stdlib/runtime/gc.gr
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ primitive unbox : Box<a> -> a = "@unbox"

exception DecRefError

export let decimalCount32 = box((n) => 0n)
export let utoa32Buffered = box((a, b, c) => void)
export let decimalCount32 = box((n: WasmI32) => 0n)
export let utoa32Buffered = box((a: WasmI32, b: WasmI32, c: WasmI32) => void)

let mut _DEBUG = false

Expand Down

0 comments on commit ef0a69f

Please sign in to comment.