about summary refs log tree commit diff stats
path: root/053recipe_header.cc
diff options
context:
space:
mode:
Diffstat (limited to '053recipe_header.cc')
-rw-r--r--053recipe_header.cc731
1 files changed, 441 insertions, 290 deletions
diff --git a/053recipe_header.cc b/053recipe_header.cc
index 5102f690..73fde510 100644
--- a/053recipe_header.cc
+++ b/053recipe_header.cc
@@ -1,17 +1,22 @@
 //: Advanced notation for the common/easy case where a recipe takes some fixed
 //: number of ingredients and yields some fixed number of products.
 
-:(scenario recipe_with_header)
-def main [
-  1:num/raw <- add2 3, 5
-]
-def add2 x:num, y:num -> z:num [
-  local-scope
-  load-ingredients
-  z:num <- add x, y
-  return z
-]
-+mem: storing 8 in location 1
+void test_recipe_with_header() {
+  run(
+      "def main [\n"
+      "  1:num/raw <- add2 3, 5\n"
+      "]\n"
+      "def add2 x:num, y:num -> z:num [\n"
+      "  local-scope\n"
+      "  load-ingredients\n"
+      "  z:num <- add x, y\n"
+      "  return z\n"
+      "]\n"
+  );
+  CHECK_TRACE_CONTENTS(
+      "mem: storing 8 in location 1\n"
+  );
+}
 
 //: When loading recipes save any header.
 
@@ -59,63 +64,93 @@ void load_recipe_header(istream& in, recipe& result) {
   // End Load Recipe Header(result)
 }
 
-:(scenario recipe_handles_stray_comma)
-def main [
-  1:num/raw <- add2 3, 5
-]
-def add2 x:num, y:num -> z:num, [
-  local-scope
-  load-ingredients
-  z:num <- add x, y
-  return z
-]
-+mem: storing 8 in location 1
-
-:(scenario recipe_handles_stray_comma_2)
-def main [
-  foo
-]
-def foo, [
-  1:num/raw <- add 2, 2
-]
-def bar [
-  1:num/raw <- add 2, 3
-]
-+mem: storing 4 in location 1
-
-:(scenario recipe_handles_wrong_arrow)
-% Hide_errors = true;
-def foo a:num <- b:num [
-]
-+error: recipe foo should say '->' and not '<-'
-
-:(scenario recipe_handles_missing_bracket)
-% Hide_errors = true;
-def main
-]
-+error: main: recipe body must begin with '['
-
-:(scenario recipe_handles_missing_bracket_2)
-% Hide_errors = true;
-def main
-  local-scope
-  {
-  }
-]
-# doesn't overflow line when reading header
--parse: header ingredient: local-scope
-+error: main: recipe body must begin with '['
-
-:(scenario recipe_handles_missing_bracket_3)
-% Hide_errors = true;
-def main  # comment
-  local-scope
-  {
-  }
-]
-# doesn't overflow line when reading header
--parse: header ingredient: local-scope
-+error: main: recipe body must begin with '['
+void test_recipe_handles_stray_comma() {
+  run(
+      "def main [\n"
+      "  1:num/raw <- add2 3, 5\n"
+      "]\n"
+      "def add2 x:num, y:num -> z:num, [\n"
+      "  local-scope\n"
+      "  load-ingredients\n"
+      "  z:num <- add x, y\n"
+      "  return z\n"
+      "]\n"
+  );
+  CHECK_TRACE_CONTENTS(
+      "mem: storing 8 in location 1\n"
+  );
+}
+
+void test_recipe_handles_stray_comma_2() {
+  run(
+      "def main [\n"
+      "  foo\n"
+      "]\n"
+      "def foo, [\n"
+      "  1:num/raw <- add 2, 2\n"
+      "]\n"
+      "def bar [\n"
+      "  1:num/raw <- add 2, 3\n"
+      "]\n"
+  );
+  CHECK_TRACE_CONTENTS(
+      "mem: storing 4 in location 1\n"
+  );
+}
+
+void test_recipe_handles_wrong_arrow() {
+  Hide_errors = true;
+  run(
+      "def foo a:num <- b:num [\n"
+      "]\n"
+  );
+  CHECK_TRACE_CONTENTS(
+      "error: recipe foo should say '->' and not '<-'\n"
+  );
+}
+
+void test_recipe_handles_missing_bracket() {
+  Hide_errors = true;
+  run(
+      "def main\n"
+      "]\n"
+  );
+  CHECK_TRACE_CONTENTS(
+      "error: main: recipe body must begin with '['\n"
+  );
+}
+
+void test_recipe_handles_missing_bracket_2() {
+  Hide_errors = true;
+  run(
+      "def main\n"
+      "  local-scope\n"
+      "  {\n"
+      "  }\n"
+      "]\n"
+  );
+  // doesn't overflow line when reading header
+  CHECK_TRACE_DOESNT_CONTAIN("parse: header ingredient: local-scope");
+  CHECK_TRACE_CONTENTS(
+      "error: main: recipe body must begin with '['\n"
+  );
+}
+
+void test_recipe_handles_missing_bracket_3() {
+  Hide_errors = true;
+  run(
+      "def main  # comment\n"
+      "  local-scope\n"
+      "  {\n"
+      "  }\n"
+      "]\n"
+  );
+  // doesn't overflow line when reading header
+  CHECK_TRACE_DOESNT_CONTAIN("parse: header ingredient: local-scope");
+  CHECK_TRACE_CONTENTS(
+      "error: main: recipe body must begin with '['\n"
+  );
+}
 
 :(after "Begin debug_string(recipe x)")
 out << "ingredients:\n";
@@ -127,11 +162,17 @@ for (int i = 0;  i < SIZE(x.products);  ++i)
 
 //: If a recipe never mentions any ingredients or products, assume it has a header.
 
-:(scenario recipe_without_ingredients_or_products_has_header)
-def test [
-  1:num <- copy 34
-]
-+parse: recipe test has a header
+:(code)
+void test_recipe_without_ingredients_or_products_has_header() {
+  run(
+      "def test [\n"
+      "  1:num <- copy 34\n"
+      "]\n"
+  );
+  CHECK_TRACE_CONTENTS(
+      "parse: recipe test has a header\n"
+  );
+}
 
 :(before "End Recipe Body(result)")
 if (!result.has_header) {
@@ -154,18 +195,24 @@ if (result.has_header) {
 
 //: Support type abbreviations in headers.
 
-:(scenario type_abbreviations_in_recipe_headers)
-def main [
-  local-scope
-  a:text <- foo
-  1:char/raw <- index *a, 0
-]
-def foo -> a:text [  # 'text' is an abbreviation
-  local-scope
-  load-ingredients
-  a <- new [abc]
-]
-+mem: storing 97 in location 1
+:(code)
+void test_type_abbreviations_in_recipe_headers() {
+  run(
+      "def main [\n"
+      "  local-scope\n"
+      "  a:text <- foo\n"
+      "  1:char/raw <- index *a, 0\n"
+      "]\n"
+      "def foo -> a:text [  # 'text' is an abbreviation\n"
+      "  local-scope\n"
+      "  load-ingredients\n"
+      "  a <- new [abc]\n"
+      "]\n"
+  );
+  CHECK_TRACE_CONTENTS(
+      "mem: storing 97 in location 1\n"
+  );
+}
 
 :(before "End Expand Type Abbreviations(caller)")
 for (long int i = 0;  i < SIZE(caller.ingredients);  ++i)
@@ -221,28 +268,39 @@ case NEXT_INGREDIENT_WITHOUT_TYPECHECKING: {
 }
 
 //: more useful error messages if someone forgets 'load-ingredients'
-
-:(scenario load_ingredients_missing_error)
-% Hide_errors = true;
-def foo a:num [
-  local-scope
-  b:num <- add a:num, 1
-]
-+error: foo: tried to read ingredient 'a' in 'b:num <- add a:num, 1' but it hasn't been written to yet
-+error:   did you forget 'load-ingredients'?
+:(code)
+void test_load_ingredients_missing_error() {
+  Hide_errors = true;
+  run(
+      "def foo a:num [\n"
+      "  local-scope\n"
+      "  b:num <- add a:num, 1\n"
+      "]\n"
+  );
+  CHECK_TRACE_CONTENTS(
+      "error: foo: tried to read ingredient 'a' in 'b:num <- add a:num, 1' but it hasn't been written to yet\n"
+      "error:   did you forget 'load-ingredients'?\n"
+  );
+}
 
 :(after "use-before-set Error")
 if (is_present_in_ingredients(caller, ingredient.name))
   raise << "  did you forget 'load-ingredients'?\n" << end();
 
-:(scenario load_ingredients_missing_error_2)
-% Hide_errors = true;
-def foo a:num [
-  local-scope
-  b:num <- add a, 1
-]
-+error: foo: missing type for 'a' in 'b:num <- add a, 1'
-+error:   did you forget 'load-ingredients'?
+:(code)
+void test_load_ingredients_missing_error_2() {
+  Hide_errors = true;
+  run(
+      "def foo a:num [\n"
+      "  local-scope\n"
+      "  b:num <- add a, 1\n"
+      "]\n"
+  );
+  CHECK_TRACE_CONTENTS(
+      "error: foo: missing type for 'a' in 'b:num <- add a, 1'\n"
+      "error:   did you forget 'load-ingredients'?\n"
+  );
+}
 
 :(after "missing-type Error 1")
 if (is_present_in_ingredients(get(Recipe, get(Recipe_ordinal, recipe_name)), x.name))
@@ -259,29 +317,39 @@ bool is_present_in_ingredients(const recipe& callee, const string& ingredient_na
 
 //:: Check all calls against headers.
 
-:(scenario show_clear_error_on_bad_call)
-% Hide_errors = true;
-def main [
-  1:num <- foo 34
-]
-def foo x:point -> y:num [
-  local-scope
-  load-ingredients
-  return 35
-]
-+error: main: ingredient 0 has the wrong type at '1:num <- foo 34'
-
-:(scenario show_clear_error_on_bad_call_2)
-% Hide_errors = true;
-def main [
-  1:point <- foo 34
-]
-def foo x:num -> y:num [
-  local-scope
-  load-ingredients
-  return x
-]
-+error: main: product 0 has the wrong type at '1:point <- foo 34'
+void test_show_clear_error_on_bad_call() {
+  Hide_errors = true;
+  run(
+      "def main [\n"
+      "  1:num <- foo 34\n"
+      "]\n"
+      "def foo x:point -> y:num [\n"
+      "  local-scope\n"
+      "  load-ingredients\n"
+      "  return 35\n"
+      "]\n"
+  );
+  CHECK_TRACE_CONTENTS(
+      "error: main: ingredient 0 has the wrong type at '1:num <- foo 34'\n"
+  );
+}
+
+void test_show_clear_error_on_bad_call_2() {
+  Hide_errors = true;
+  run(
+      "def main [\n"
+      "  1:point <- foo 34\n"
+      "]\n"
+      "def foo x:num -> y:num [\n"
+      "  local-scope\n"
+      "  load-ingredients\n"
+      "  return x\n"
+      "]\n"
+  );
+  CHECK_TRACE_CONTENTS(
+      "error: main: product 0 has the wrong type at '1:point <- foo 34'\n"
+  );
+}
 
 :(after "Transform.push_back(check_instruction)")
 Transform.push_back(check_calls_against_header);  // idempotent
@@ -314,16 +382,20 @@ void check_calls_against_header(const recipe_ordinal r) {
 
 //:: Check types going in and out of all recipes with headers.
 
-:(scenarios transform)
-:(scenario recipe_headers_are_checked)
-% Hide_errors = true;
-def add2 x:num, y:num -> z:num [
-  local-scope
-  load-ingredients
-  z:&:num <- copy 0/unsafe
-  return z
-]
-+error: add2: replied with the wrong type at 'return z'
+void test_recipe_headers_are_checked() {
+  Hide_errors = true;
+  transform(
+      "def add2 x:num, y:num -> z:num [\n"
+      "  local-scope\n"
+      "  load-ingredients\n"
+      "  z:&:num <- copy 0/unsafe\n"
+      "  return z\n"
+      "]\n"
+  );
+  CHECK_TRACE_CONTENTS(
+      "error: add2: replied with the wrong type at 'return z'\n"
+  );
+}
 
 :(before "End Checks")
 Transform.push_back(check_return_instructions_against_header);  // idempotent
@@ -347,51 +419,76 @@ void check_return_instructions_against_header(const recipe_ordinal r) {
   }
 }
 
-:(scenario recipe_headers_are_checked_2)
-% Hide_errors = true;
-def add2 x:num, y:num [
-  local-scope
-  load-ingredients
-  z:&:num <- copy 0/unsafe
-  return z
-]
-+error: add2: replied with the wrong number of products at 'return z'
-
-:(scenario recipe_headers_are_checked_against_pre_transformed_instructions)
-% Hide_errors = true;
-def foo -> x:num [
-  local-scope
-  x:num <- copy 0
-  z:bool <- copy false
-  return-if z, z
-]
-+error: foo: replied with the wrong type at 'return-if z, z'
-
-:(scenario recipe_headers_check_for_duplicate_names)
-% Hide_errors = true;
-def foo x:num, x:num -> z:num [
-  local-scope
-  load-ingredients
-  return z
-]
-+error: foo: 'x' can't repeat in the ingredients
-
-:(scenario recipe_headers_check_for_duplicate_names_2)
-% Hide_errors = true;
-def foo x:num, x:num [  # no result
-  local-scope
-  load-ingredients
-]
-+error: foo: 'x' can't repeat in the ingredients
-
-:(scenario recipe_headers_check_for_missing_types)
-% Hide_errors = true;
-def main [
-  foo 0
-]
-def foo a [  # no type for 'a'
-]
-+error: foo: ingredient 'a' has no type
+void test_recipe_headers_are_checked_2() {
+  Hide_errors = true;
+  transform(
+      "def add2 x:num, y:num [\n"
+      "  local-scope\n"
+      "  load-ingredients\n"
+      "  z:&:num <- copy 0/unsafe\n"
+      "  return z\n"
+      "]\n"
+  );
+  CHECK_TRACE_CONTENTS(
+      "error: add2: replied with the wrong number of products at 'return z'\n"
+  );
+}
+
+void test_recipe_headers_are_checked_against_pre_transformed_instructions() {
+  Hide_errors = true;
+  transform(
+      "def foo -> x:num [\n"
+      "  local-scope\n"
+      "  x:num <- copy 0\n"
+      "  z:bool <- copy false\n"
+      "  return-if z, z\n"
+      "]\n"
+  );
+  CHECK_TRACE_CONTENTS(
+      "error: foo: replied with the wrong type at 'return-if z, z'\n"
+  );
+}
+
+void test_recipe_headers_check_for_duplicate_names() {
+  Hide_errors = true;
+  transform(
+      "def foo x:num, x:num -> z:num [\n"
+      "  local-scope\n"
+      "  load-ingredients\n"
+      "  return z\n"
+      "]\n"
+  );
+  CHECK_TRACE_CONTENTS(
+      "error: foo: 'x' can't repeat in the ingredients\n"
+  );
+}
+
+void test_recipe_headers_check_for_duplicate_names_2() {
+  Hide_errors = true;
+  transform(
+      "def foo x:num, x:num [  # no result\n"
+      "  local-scope\n"
+      "  load-ingredients\n"
+      "]\n"
+  );
+  CHECK_TRACE_CONTENTS(
+      "error: foo: 'x' can't repeat in the ingredients\n"
+  );
+}
+
+void test_recipe_headers_check_for_missing_types() {
+  Hide_errors = true;
+  transform(
+      "def main [\n"
+      "  foo 0\n"
+      "]\n"
+      "def foo a [\n"  // no type for 'a'
+      "]\n"
+  );
+  CHECK_TRACE_CONTENTS(
+      "error: foo: ingredient 'a' has no type\n"
+  );
+}
 
 :(before "End recipe Fields")
 map<string, int> ingredient_index;
@@ -415,18 +512,22 @@ void check_header_ingredients(const recipe_ordinal r) {
 
 //: Deduce types from the header if possible.
 
-:(scenarios run)
-:(scenario deduce_instruction_types_from_recipe_header)
-def main [
-  1:num/raw <- add2 3, 5
-]
-def add2 x:num, y:num -> z:num [
-  local-scope
-  load-ingredients
-  z <- add x, y  # no type for z
-  return z
-]
-+mem: storing 8 in location 1
+void test_deduce_instruction_types_from_recipe_header() {
+  run(
+      "def main [\n"
+      "  1:num/raw <- add2 3, 5\n"
+      "]\n"
+      "def add2 x:num, y:num -> z:num [\n"
+      "  local-scope\n"
+      "  load-ingredients\n"
+      "  z <- add x, y  # no type for z\n"
+      "  return z\n"
+      "]\n"
+  );
+  CHECK_TRACE_CONTENTS(
+      "mem: storing 8 in location 1\n"
+  );
+}
 
 :(after "Begin Type Modifying Transforms")
 Transform.push_back(deduce_types_from_header);  // idempotent
@@ -473,17 +574,22 @@ void deduce_types_from_header(const recipe_ordinal r) {
 //: One final convenience: no need to say what to return if the information is
 //: in the header.
 
-:(scenario return_based_on_header)
-def main [
-  1:num/raw <- add2 3, 5
-]
-def add2 x:num, y:num -> z:num [
-  local-scope
-  load-ingredients
-  z <- add x, y
-  return
-]
-+mem: storing 8 in location 1
+void test_return_based_on_header() {
+  run(
+      "def main [\n"
+      "  1:num/raw <- add2 3, 5\n"
+      "]\n"
+      "def add2 x:num, y:num -> z:num [\n"
+      "  local-scope\n"
+      "  load-ingredients\n"
+      "  z <- add x, y\n"
+      "  return\n"
+      "]\n"
+  );
+  CHECK_TRACE_CONTENTS(
+      "mem: storing 8 in location 1\n"
+  );
+}
 
 :(after "Transform.push_back(check_header_ingredients)")
 Transform.push_back(fill_in_return_ingredients);  // idempotent
@@ -526,97 +632,142 @@ void add_header_products(instruction& inst, const recipe& caller_recipe) {
   }
 }
 
-:(scenario explicit_return_ignores_header)
-def main [
-  1:num/raw, 2:num/raw <- add2 3, 5
-]
-def add2 a:num, b:num -> y:num, z:num [
-  local-scope
-  load-ingredients
-  y <- add a, b
-  z <- subtract a, b
-  return a, z
-]
-+mem: storing 3 in location 1
-+mem: storing -2 in location 2
-
-:(scenario return_on_fallthrough_based_on_header)
-def main [
-  1:num/raw <- add2 3, 5
-]
-def add2 x:num, y:num -> z:num [
-  local-scope
-  load-ingredients
-  z <- add x, y
-]
-+transform: instruction: return {z: "number"}
-+mem: storing 8 in location 1
-
-:(scenario return_on_fallthrough_already_exists)
-def main [
-  1:num/raw <- add2 3, 5
-]
-def add2 x:num, y:num -> z:num [
-  local-scope
-  load-ingredients
-  z <- add x, y  # no type for z
-  return z
-]
-+transform: instruction: return {z: ()}
--transform: instruction: return z:num
-+mem: storing 8 in location 1
-
-:(scenario return_causes_error_in_empty_recipe)
-% Hide_errors = true;
-def foo -> x:num [
-]
-+error: foo: tried to read ingredient 'x' in 'return x:num' but it hasn't been written to yet
-
-:(scenario return_after_conditional_return_based_on_header)
-def main [
-  1:num/raw <- add2 3, 5
-]
-def add2 x:num, y:num -> z:num [
-  local-scope
-  load-ingredients
-  z <- add x, y  # no type for z
-  return-if false, 34
-]
-+mem: storing 8 in location 1
-
-:(scenario recipe_headers_perform_same_ingredient_check)
-% Hide_errors = true;
-def main [
-  1:num <- copy 34
-  2:num <- copy 34
-  3:num <- add2 1:num, 2:num
-]
-def add2 x:num, y:num -> x:num [
-  local-scope
-  load-ingredients
-]
-+error: main: '3:num <- add2 1:num, 2:num' should write to '1:num' rather than '3:num'
+void test_explicit_return_ignores_header() {
+  run(
+      "def main [\n"
+      "  1:num/raw, 2:num/raw <- add2 3, 5\n"
+      "]\n"
+      "def add2 a:num, b:num -> y:num, z:num [\n"
+      "  local-scope\n"
+      "  load-ingredients\n"
+      "  y <- add a, b\n"
+      "  z <- subtract a, b\n"
+      "  return a, z\n"
+      "]\n"
+  );
+  CHECK_TRACE_CONTENTS(
+      "mem: storing 3 in location 1\n"
+      "mem: storing -2 in location 2\n"
+  );
+}
+
+void test_return_on_fallthrough_based_on_header() {
+  run(
+      "def main [\n"
+      "  1:num/raw <- add2 3, 5\n"
+      "]\n"
+      "def add2 x:num, y:num -> z:num [\n"
+      "  local-scope\n"
+      "  load-ingredients\n"
+      "  z <- add x, y\n"
+      "]\n"
+  );
+  CHECK_TRACE_CONTENTS(
+      "transform: instruction: return {z: \"number\"}\n"
+      "mem: storing 8 in location 1\n"
+  );
+}
+
+void test_return_on_fallthrough_already_exists() {
+  run(
+      "def main [\n"
+      "  1:num/raw <- add2 3, 5\n"
+      "]\n"
+      "def add2 x:num, y:num -> z:num [\n"
+      "  local-scope\n"
+      "  load-ingredients\n"
+      "  z <- add x, y  # no type for z\n"
+      "  return z\n"
+      "]\n"
+  );
+  CHECK_TRACE_CONTENTS(
+      "transform: instruction: return {z: ()}\n"
+  );
+  CHECK_TRACE_DOESNT_CONTAIN("transform: instruction: return z:num");
+  CHECK_TRACE_CONTENTS(
+      "mem: storing 8 in location 1\n"
+  );
+}
+
+void test_return_causes_error_in_empty_recipe() {
+  Hide_errors = true;
+  run(
+      "def foo -> x:num [\n"
+      "]\n"
+  );
+  CHECK_TRACE_CONTENTS(
+      "error: foo: tried to read ingredient 'x' in 'return x:num' but it hasn't been written to yet\n"
+  );
+}
+
+void test_return_after_conditional_return_based_on_header() {
+  run(
+      "def main [\n"
+      "  1:num/raw <- add2 3, 5\n"
+      "]\n"
+      "def add2 x:num, y:num -> z:num [\n"
+      "  local-scope\n"
+      "  load-ingredients\n"
+      "  z <- add x, y\n"  // no type for z
+      "  return-if false, 34\n"
+      "]\n"
+  );
+  CHECK_TRACE_CONTENTS(
+      "mem: storing 8 in location 1\n"
+  );
+}
+
+void test_recipe_headers_perform_same_ingredient_check() {
+  Hide_errors = true;
+  run(
+      "def main [\n"
+      "  1:num <- copy 34\n"
+      "  2:num <- copy 34\n"
+      "  3:num <- add2 1:num, 2:num\n"
+      "]\n"
+      "def add2 x:num, y:num -> x:num [\n"
+      "  local-scope\n"
+      "  load-ingredients\n"
+      "]\n"
+  );
+  CHECK_TRACE_CONTENTS(
+      "error: main: '3:num <- add2 1:num, 2:num' should write to '1:num' rather than '3:num'\n"
+  );
+}
 
 //: One special-case is recipe 'main'. Make sure it's only ever taking in text
 //: ingredients, and returning a single number.
 
-:(scenario recipe_header_ingredients_constrained_for_main)
-% Hide_errors = true;
-def main x:num [
-]
-+error: ingredients of recipe 'main' must all be text (address:array:character)
-
-:(scenario recipe_header_products_constrained_for_main)
-% Hide_errors = true;
-def main -> x:text [
-]
-+error: recipe 'main' must return at most a single product, a number
-
-:(scenario recipe_header_products_constrained_for_main_2)
-% Hide_errors = true;
-def main -> x:num, y:num [
-]
-+error: recipe 'main' must return at most a single product, a number
+void test_recipe_header_ingredients_constrained_for_main() {
+  Hide_errors = true;
+  run(
+      "def main x:num [\n"
+      "]\n"
+  );
+  CHECK_TRACE_CONTENTS(
+      "error: ingredients of recipe 'main' must all be text (address:array:character)\n"
+  );
+}
+void test_recipe_header_products_constrained_for_main() {
+  Hide_errors = true;
+  run(
+      "def main -> x:text [\n"
+      "]\n"
+  );
+  CHECK_TRACE_CONTENTS(
+      "error: recipe 'main' must return at most a single product, a number\n"
+  );
+}
+void test_recipe_header_products_constrained_for_main_2() {
+  Hide_errors = true;
+  run(
+      "def main -> x:num, y:num [\n"
+      "]\n"
+  );
+  CHECK_TRACE_CONTENTS(
+      "error: recipe 'main' must return at most a single product, a number\n"
+  );
+}
 
 :(after "Transform.push_back(expand_type_abbreviations)")
 Transform.push_back(check_recipe_header_constraints);