Skip to content

Commit

Permalink
[MOREL-213] Require that a non-terminal yield step is a record expres…
Browse files Browse the repository at this point in the history
…sion

Fixes hydromatic#213
  • Loading branch information
julianhyde committed Jan 14, 2024
1 parent 1ef2bf5 commit 5b496b4
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 3 deletions.
13 changes: 10 additions & 3 deletions src/main/java/net/hydromatic/morel/compile/TypeResolver.java
Original file line number Diff line number Diff line change
Expand Up @@ -359,9 +359,16 @@ private Ast.Exp deduceType(TypeEnv env, Ast.Exp node, Unifier.Variable v) {
for (Ord<Ast.FromStep> step : Ord.zip(from.steps)) {
Pair<TypeEnv, Unifier.Variable> p =
deduceStepType(env, step.e, v3, env3, fieldVars, fromSteps);
if (step.e.op == Op.COMPUTE
&& step.i != from.steps.size() - 1) {
throw new AssertionError("'compute' step must be last in 'from'");
if (step.i != from.steps.size() - 1) {
switch (step.e.op) {
case COMPUTE:
throw new AssertionError("'compute' step must be last in 'from'");
case YIELD:
if (((Ast.Yield) step.e).exp.op != Op.RECORD) {
throw new AssertionError("'yield' step that is not last in 'from'"
+ " must be a record expression");
}
}
}
env3 = p.left;
v3 = p.right;
Expand Down
48 changes: 48 additions & 0 deletions src/test/java/net/hydromatic/morel/MainTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -1773,6 +1773,54 @@ private static List<Object> node(Object... args) {
.assertType("(int * int -> bool) -> int list");
}

@Test void testFromYield() {
ml("from a in [1], b in [true]")
.assertType("{a:int, b:bool} list");
ml("from a in [1], b in [true] yield a")
.assertType("int list");
ml("from a in [1], b in [true] yield {a,b}")
.assertType("{a:int, b:bool} list");
ml("from a in [1], b in [true] yield {y=a,b}")
.assertType("{b:bool, y:int} list");
ml("from a in [1], b in [true] yield {y=a,x=b,z=a}")
.assertType("{x:bool, y:int, z:int} list");
ml("from a in [1], b in [true] yield {y=a,x=b,z=a} yield {z,x}")
.assertType("{x:bool, z:int} list");
ml("from a in [1], b in [true] yield {y=a,x=b,z=a} yield {z}")
.assertType("{z:int} list");
ml("from a in [1], b in [true] yield (b,a)")
.assertType("(bool * int) list");
ml("from a in [1], b in [true] yield (b)")
.assertType("bool list");
ml("from a in [1], b in [true] yield {b,a} yield a")
.assertType("int list");
String value = "'yield' step that is not last in 'from' must be a record "
+ "expression";
ml("from a in [1], b in [true] yield (b,a) where b")
.assertTypeThrows(
throwsA(AssertionError.class,
is(value)));
ml("from a in [1], b in [true] yield {b,a} where b")
.assertType("{a:int, b:bool} list")
.assertEval(is(list(list(1, true))));
ml("from d in [{a=1,b=true}], i in [2] yield i")
.assertType("int list");
// Note that 'd' has record type but is not a record expression;
// we may allow record types in the future.
ml("from d in [{a=1,b=true}], i in [2] yield d yield a")
.assertTypeThrows(throwsA(AssertionError.class, is(value)));
ml("from d in [{a=1,b=true}], i in [2] yield {d.a,d.b} yield a")
.assertType("int list");
ml("from d in [{a=1,b=true}], i in [2] yield d where true")
.assertTypeThrows(throwsA(AssertionError.class, is(value)));
ml("from d in [{a=1,b=true}], i in [2] yield i yield 3")
.assertTypeThrows(throwsA(AssertionError.class, is(value)));
ml("from d in [{a=1,b=true}], i in [2] yield d")
.assertType("{a:int, b:bool} list");
ml("from d in [{a=1,b=true}], i in [2] yield d yield 3")
.assertTypeThrows(throwsA(AssertionError.class, is(value)));
}

@Test void testFromYieldExpression() {
final String ml = "let\n"
+ " val emps = [\n"
Expand Down
28 changes: 28 additions & 0 deletions src/test/resources/script/relational.smli
Original file line number Diff line number Diff line change
Expand Up @@ -804,6 +804,34 @@ order ename;
> {deptno=20,dname="HR",ename="Velma",sumId=101}]
> : {deptno:int, dname:string, ename:string, sumId:int} list

(*) 'group' that yields record
from e in emps, d in depts
where e.deptno = d.deptno
group d;
> val it =
> [{deptno=20,name="HR"},{deptno=30,name="Engineering"},
> {deptno=10,name="Sales"}] : {deptno:int, name:string} list

(*) Yield a variable whose value is a record.
from e in emps, d in depts
where e.deptno = d.deptno
yield e;
> val it =
> [{deptno=10,id=100,name="Fred"},{deptno=20,id=101,name="Velma"},
> {deptno=30,id=102,name="Shaggy"},{deptno=30,id=103,name="Scooby"}]
> : {deptno:int, id:int, name:string} list

(*) Yield a record containing a pair of variables whose values are records.
from e in emps, d in depts
where e.deptno = d.deptno
yield {e, d};
> val it =
> [{d={deptno=10,name="Sales"},e={deptno=10,id=100,name="Fred"}},
> {d={deptno=20,name="HR"},e={deptno=20,id=101,name="Velma"}},
> {d={deptno=30,name="Engineering"},e={deptno=30,id=102,name="Shaggy"}},
> {d={deptno=30,name="Engineering"},e={deptno=30,id=103,name="Scooby"}}]
> : {d:{deptno:int, name:string}, e:{deptno:int, id:int, name:string}} list

(*) empty 'group'
from e in emps
group compute sumId = sum of e.id;
Expand Down

0 comments on commit 5b496b4

Please sign in to comment.