diff --git a/src/main/java/net/hydromatic/morel/compile/TypeResolver.java b/src/main/java/net/hydromatic/morel/compile/TypeResolver.java index ee0a11b6..2d6276ae 100644 --- a/src/main/java/net/hydromatic/morel/compile/TypeResolver.java +++ b/src/main/java/net/hydromatic/morel/compile/TypeResolver.java @@ -359,9 +359,16 @@ private Ast.Exp deduceType(TypeEnv env, Ast.Exp node, Unifier.Variable v) { for (Ord step : Ord.zip(from.steps)) { Pair 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; diff --git a/src/test/java/net/hydromatic/morel/MainTest.java b/src/test/java/net/hydromatic/morel/MainTest.java index eab66f76..b31792a4 100644 --- a/src/test/java/net/hydromatic/morel/MainTest.java +++ b/src/test/java/net/hydromatic/morel/MainTest.java @@ -1773,6 +1773,54 @@ private static List 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" diff --git a/src/test/resources/script/relational.smli b/src/test/resources/script/relational.smli index 69cf6914..7ec5a332 100644 --- a/src/test/resources/script/relational.smli +++ b/src/test/resources/script/relational.smli @@ -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;