@@ -682,3 +682,84 @@ main = do
682682 "both derived impls should work, got: {stdout}" ,
683683 ) ;
684684}
685+
686+ // ── 13. `where` over a source relation, yielding the whole row ────
687+
688+ /// `x <- *rel` reads either as a comprehension bind (iterate ROWS) or as a
689+ /// whole-relation bind, and codegen picks between them from how the block uses
690+ /// the name. `yield u` — the most ordinary comprehension there is — looks like
691+ /// a whole-relation use, so the block took the guard reading: `where u.age >=
692+ /// 25` asked the FIRST row's age, waved every row through, and `yield u` handed
693+ /// back the entire relation with the filter silently dropped.
694+ const ADULTS : & str = "type User = {name: Text, age: Int}\n \
695+ *users : [User]\n \
696+ &adults = do\n \
697+ \x20 u <- *users\n \
698+ \x20 where u.age >= 25\n \
699+ \x20 yield u\n ";
700+
701+ #[ test]
702+ fn a_where_filters_the_rows_of_a_source_relation ( ) {
703+ let src = format ! (
704+ "{ADULTS}\
705+ main = do\n \
706+ \x20 replace *users = [{{name: \" Alice\" , age: 30}}, {{name: \" Bob\" , age: 20}}, {{name: \" Carol\" , age: 40}}]\n \
707+ \x20 a <- &adults\n \
708+ \x20 forEach a (\\ u -> println u.name)\n \
709+ \x20 yield {{}}\n "
710+ ) ;
711+ let ( stdout, stderr, ok) = compile_and_run ( "where_filters_source" , & src, & [ ] ) ;
712+ assert ! ( ok, "the program must run: {stderr}" ) ;
713+ assert ! ( stdout. contains( "Alice" ) , "a row passing the where must be yielded: {stdout:?}" ) ;
714+ assert ! ( stdout. contains( "Carol" ) , "a row passing the where must be yielded: {stdout:?}" ) ;
715+ assert ! (
716+ !stdout. contains( "Bob" ) ,
717+ "a row failing the where must be filtered out, got: {stdout:?}" ,
718+ ) ;
719+ }
720+
721+ /// The same misreading, with a predicate the first row fails: the guard was
722+ /// false, so the whole block collapsed to `{}` and the caller's `count` met a
723+ /// Unit where it expected a relation ("expected Relation in len, got Unit").
724+ /// Filtering every row out must give an empty relation, not a non-relation.
725+ #[ test]
726+ fn a_where_that_no_row_satisfies_yields_an_empty_relation ( ) {
727+ let src = "type User = {name: Text, age: Int}\n \
728+ *users : [User]\n \
729+ &adults = do\n \
730+ \x20 u <- *users\n \
731+ \x20 where u.age >= 99\n \
732+ \x20 yield u\n \
733+ main = do\n \
734+ \x20 replace *users = [{name: \" Alice\" , age: 30}]\n \
735+ \x20 a <- &adults\n \
736+ \x20 println (\" count: \" ++ show (count a))\n \
737+ \x20 yield {}\n ";
738+ let ( stdout, stderr, ok) = compile_and_run ( "where_matches_nothing" , src, & [ ] ) ;
739+ assert ! ( ok, "filtering every row out must not panic: {stderr}" ) ;
740+ assert ! ( stdout. contains( "count: 0" ) , "the result must be empty, got: {stdout:?}" ) ;
741+ }
742+
743+ /// The other reading of `x <- *rel` — bind the WHOLE relation and pass it on as
744+ /// a value (DESIGN.md's `&seniors`) — must survive the fix. Here a `where` over
745+ /// the name *as a value* is a genuine guard on the relation, not a row filter.
746+ #[ test]
747+ fn a_where_over_the_relation_as_a_value_stays_a_guard ( ) {
748+ let src = "type Person = {name: Text, age: Int}\n \
749+ *people : [Person]\n \
750+ &seniorsIfCrowd = do\n \
751+ \x20 people <- *people\n \
752+ \x20 where count people > 1\n \
753+ \x20 yield (filter (\\ p -> p.age > 65) people)\n \
754+ main = do\n \
755+ \x20 replace *people = [{name: \" Alice\" , age: 70}, {name: \" Bob\" , age: 20}]\n \
756+ \x20 s <- &seniorsIfCrowd\n \
757+ \x20 forEach s (\\ p -> println p.name)\n \
758+ \x20 yield {}\n ";
759+ let ( stdout, stderr, ok) = compile_and_run ( "where_as_relation_guard" , src, & [ ] ) ;
760+ assert ! ( ok, "the program must run: {stderr}" ) ;
761+ assert ! (
762+ stdout. contains( "Alice" ) && !stdout. contains( "Bob" ) ,
763+ "the whole relation must reach `filter`, which keeps only Alice, got: {stdout:?}" ,
764+ ) ;
765+ }
0 commit comments