# Mu - 072recipe.cc

      1 //: So far we've been calling a fixed recipe in each instruction, but we'd
      2 //: also like to make the recipe a variable, pass recipes to "higher-order"
      3 //: recipes, return recipes from recipes and so on.
      4
      5 :(scenario call_literal_recipe)
      6 def main [
      7   1:num <- call f, 34
      8 ]
      9 def f x:num -> y:num [
     10   local-scope
     11   load-ingredients
     12   y <- copy x
     13 ]
     14 +mem: storing 34 in location 1
     15
     16 :(before "End Mu Types Initialization")
     17 put(Type_ordinal, "recipe-literal", 0);
     18 // 'recipe' variables can store recipe-literal
     19 type_ordinal recipe = put(Type_ordinal, "recipe", Next_type_ordinal++);
     20 get_or_insert(Type, recipe).name = "recipe";
     21
     22 :(after "Deduce Missing Type(x, caller)")
     23 if (!x.type)
     24   try_initialize_recipe_literal(x, caller);
     25 :(before "Type Check in Type-ingredient-aware check_or_set_types_by_name")
     26 if (!x.type)
     27   try_initialize_recipe_literal(x, variant);
     28 :(code)
     29 void try_initialize_recipe_literal(reagent& x, const recipe& caller) {
     30   if (x.type) return;
     31   if (!contains_key(Recipe_ordinal, x.name)) return;
     32   if (contains_reagent_with_non_recipe_literal_type(caller, x.name)) return;
     33   x.type = new type_tree("recipe-literal");
     34   x.set_value(get(Recipe_ordinal, x.name));
     35 }
     36 bool contains_reagent_with_non_recipe_literal_type(const recipe& caller, const string& name) {
     37   for (int i = 0;  i < SIZE(caller.steps);  ++i) {
     38     const instruction& inst = caller.steps.at(i);
     39     for (int i = 0;  i < SIZE(inst.ingredients);  ++i)
     40       if (is_matching_non_recipe_literal(inst.ingredients.at(i), name)) return true;
     41     for (int i = 0;  i < SIZE(inst.products);  ++i)
     42       if (is_matching_non_recipe_literal(inst.products.at(i), name)) return true;
     43   }
     44   return false;
     45 }
     46 bool is_matching_non_recipe_literal(const reagent& x, const string& name) {
     47   if (x.name != name) return false;
     48   if (!x.type) return false;
     49   return !x.type->atom || x.type->name != "recipe-literal";
     50 }
     51
     52 //: It's confusing to use variable names that are also recipe names. Always
     53 //: assume variable types override recipe literals.
     54 :(scenario error_on_recipe_literal_used_as_a_variable)
     55 % Hide_errors = true;
     56 def main [
     57   local-scope
     58   a:bool <- equal break 0
     59   break:bool <- copy 0
     60 ]
     61 +error: main: missing type for 'break' in 'a:bool <- equal break, 0'
     62
     63 :(before "End Primitive Recipe Declarations")
     64 CALL,
     65 :(before "End Primitive Recipe Numbers")
     66 put(Recipe_ordinal, "call", CALL);
     67 :(before "End Primitive Recipe Checks")
     68 case CALL: {
     69   if (inst.ingredients.empty()) {
     70     raise << maybe(get(Recipe, r).name) << "'call' requires at least one ingredient (the recipe to call)\n" << end();
     71     break;
     72   }
     73   if (!is_mu_recipe(inst.ingredients.at(0))) {
     74     raise << maybe(get(Recipe, r).name) << "first ingredient of 'call' should be a recipe, but got '" << inst.ingredients.at(0).original_string << "'\n" << end();
     75     break;
     76   }
     77   break;
     78 }
     79 :(before "End Primitive Recipe Implementations")
     80 case CALL: {
     81   // Begin Call
     82   if (Trace_stream) {
     83     ++Trace_stream->callstack_depth;
     84     trace("trace") << "indirect 'call': incrementing callstack depth to " << Trace_stream->callstack_depth << end();
     85     assert(Trace_stream->callstack_depth < 9000);  // 9998-101 plus cushion
     86   }
     87   if (!ingredients.at(0).at(0)) {
     88     raise << maybe(current_recipe_name()) << "tried to call empty recipe in '" << to_string(current_instruction()) << "'" << end();
     89     break;
     90   }
     91   const call& caller_frame = current_call();
     92   instruction/*copy*/ call_instruction = to_instruction(caller_frame);
     93   call_instruction.operation = ingredients.at(0).at(0);
     94   call_instruction.ingredients.erase(call_instruction.ingredients.begin());
     95   Current_routine->calls.push_front(call(ingredients.at(0).at(0)));
     96   ingredients.erase(ingredients.begin());  // drop the callee
     97   finish_call_housekeeping(call_instruction, ingredients);
     98   // not done with caller
     99   write_products = false;
    100   fall_through_to_next_instruction = false;
    101   break;
    102 }
    103
    104 :(scenario call_variable)
    105 def main [
    106   {1: (recipe number -> number)} <- copy f
    107   2:num <- call {1: (recipe number -> number)}, 34
    108 ]
    109 def f x:num -> y:num [
    110   local-scope
    111   load-ingredients
    112   y <- copy x
    113 ]
    114 +mem: storing 34 in location 2
    115
    116 :(scenario call_literal_recipe_repeatedly)
    117 def main [
    118   1:num <- call f, 34
    119   1:num <- call f, 35
    120 ]
    121 def f x:num -> y:num [
    122   local-scope
    123   load-ingredients
    124   y <- copy x
    125 ]
    126 +mem: storing 34 in location 1
    127 +mem: storing 35 in location 1
    128
    129 :(scenario call_shape_shifting_recipe)
    130 def main [
    131   1:num <- call f, 34
    132 ]
    133 def f x:_elem -> y:_elem [
    134   local-scope
    135   load-ingredients
    136   y <- copy x
    137 ]
    138 +mem: storing 34 in location 1
    139
    140 :(scenario call_shape_shifting_recipe_inside_shape_shifting_recipe)
    141 def main [
    142   1:num <- f 34
    143 ]
    144 def f x:_elem -> y:_elem [
    145   local-scope
    146   load-ingredients
    147   y <- call g x
    148 ]
    149 def g x:_elem -> y:_elem [
    150   local-scope
    151   load-ingredients
    152   y <- copy x
    153 ]
    154 +mem: storing 34 in location 1
    155
    156 :(scenario call_shape_shifting_recipe_repeatedly_inside_shape_shifting_recipe)
    157 def main [
    158   1:num <- f 34
    159 ]
    160 def f x:_elem -> y:_elem [
    161   local-scope
    162   load-ingredients
    163   y <- call g x
    164   y <- call g x
    165 ]
    166 def g x:_elem -> y:_elem [
    167   local-scope
    168   load-ingredients
    169   y <- copy x
    170 ]
    171 +mem: storing 34 in location 1
    172
    173 //:: check types for 'call' instructions
    174
    175 :(scenario call_check_literal_recipe)
    176 % Hide_errors = true;
    177 def main [
    178   1:num <- call f, 34
    179 ]
    180 def f x:point -> y:point [
    181   local-scope
    182   load-ingredients
    183   y <- copy x
    184 ]
    185 +error: main: ingredient 0 has the wrong type at '1:num <- call f, 34'
    186 +error: main: product 0 has the wrong type at '1:num <- call f, 34'
    187
    188 :(scenario call_check_variable_recipe)
    189 % Hide_errors = true;
    190 def main [
    191   {1: (recipe point -> point)} <- copy f
    192   2:num <- call {1: (recipe point -> point)}, 34
    193 ]
    194 def f x:point -> y:point [
    195   local-scope
    196   load-ingredients
    197   y <- copy x
    198 ]
    199 +error: main: ingredient 0 has the wrong type at '2:num <- call {1: (recipe point -> point)}, 34'
    200 +error: main: product 0 has the wrong type at '2:num <- call {1: (recipe point -> point)}, 34'
    201
    202 :(before "End resolve_ambiguous_call(r, index, inst, caller_recipe) Special-cases")
    203 if (inst.name == "call" && !inst.ingredients.empty() && is_recipe_literal(inst.ingredients.at(0))) {
    204   resolve_indirect_ambiguous_call(r, index, inst, caller_recipe);
    205   return;
    206 }
    207 :(code)
    208 bool is_recipe_literal(const reagent& x) {
    209   return x.type && x.type->atom && x.type->name == "recipe-literal";
    210 }
    211 void resolve_indirect_ambiguous_call(const recipe_ordinal r, int index, instruction& inst, const recipe& caller_recipe) {
    212   instruction inst2;
    213   inst2.name = inst.ingredients.at(0).name;
    214   for (int i = /*skip recipe*/1;  i < SIZE(inst.ingredients);  ++i)
    215     inst2.ingredients.push_back(inst.ingredients.at(i));
    216   for (int i = 0;  i < SIZE(inst.products);  ++i)
    217     inst2.products.push_back(inst.products.at(i));
    218   resolve_ambiguous_call(r, index, inst2, caller_recipe);
    219   inst.ingredients.at(0).name = inst2.name;
    220   inst.ingredients.at(0).set_value(get(Recipe_ordinal, inst2.name));
    221 }
    222
    223 :(after "Transform.push_back(check_instruction)")
    224 Transform.push_back(check_indirect_calls_against_header);  // idempotent
    225 :(code)
    226 void check_indirect_calls_against_header(const recipe_ordinal r) {
    227   trace(9991, "transform") << "--- type-check 'call' instructions inside recipe " << get(Recipe, r).name << end();
    228   const recipe& caller = get(Recipe, r);
    229   for (int i = 0;  i < SIZE(caller.steps);  ++i) {
    230     const instruction& inst = caller.steps.at(i);
    231     if (!is_indirect_call(inst.operation)) continue;
    232     if (inst.ingredients.empty()) continue;  // error raised above
    233     const reagent& callee = inst.ingredients.at(0);
    234     if (!is_mu_recipe(callee)) continue;  // error raised above
    235     const recipe callee_header = is_literal(callee) ? get(Recipe, callee.value) : from_reagent(inst.ingredients.at(0));
    236     if (!callee_header.has_header) continue;
    237     if (is_indirect_call_with_ingredients(inst.operation)) {
    238       for (long int i = /*skip callee*/1;  i < min(SIZE(inst.ingredients), SIZE(callee_header.ingredients)+/*skip callee*/1);  ++i) {
    239         if (!types_coercible(callee_header.ingredients.at(i-/*skip callee*/1), inst.ingredients.at(i)))
    240           raise << maybe(caller.name) << "ingredient " << i-/*skip callee*/1 << " has the wrong type at '" << to_original_string(inst) << "'\n" << end();
    241       }
    242     }
    243     if (is_indirect_call_with_products(inst.operation)) {
    244       for (long int i = 0;  i < min(SIZE(inst.products), SIZE(callee_header.products));  ++i) {
    245         if (is_dummy(inst.products.at(i))) continue;
    246         if (!types_coercible(callee_header.products.at(i), inst.products.at(i)))
    247           raise << maybe(caller.name) << "product " << i << " has the wrong type at '" << to_original_string(inst) << "'\n" << end();
    248       }
    249     }
    250   }
    251 }
    252
    253 bool is_indirect_call(const recipe_ordinal r) {
    254   return is_indirect_call_with_ingredients(r) || is_indirect_call_with_products(r);
    255 }
    256
    257 bool is_indirect_call_with_ingredients(const recipe_ordinal r) {
    258   if (r == CALL) return true;
    259   // End is_indirect_call_with_ingredients Special-cases
    260   return false;
    261 }
    262 bool is_indirect_call_with_products(const recipe_ordinal r) {
    263   if (r == CALL) return true;
    264   // End is_indirect_call_with_products Special-cases
    265   return false;
    266 }
    267
    268 recipe from_reagent(const reagent& r) {
    269   assert(r.type);
    270   recipe result_header;  // will contain only ingredients and products, nothing else
    271   result_header.has_header = true;
    272   // Begin Reagent->Recipe(r, recipe_header)
    273   if (r.type->atom) {
    274     assert(r.type->name == "recipe");
    275     return result_header;
    276   }
    277   const type_tree* root_type = r.type->atom ? r.type : r.type->left;
    278   assert(root_type->atom);
    279   assert(root_type->name == "recipe");
    280   const type_tree* curr = r.type->right;
    281   for (/*nada*/;  curr && !curr->atom;  curr = curr->right) {
    282     if (curr->left->atom && curr->left->name == "->") {
    283       curr = curr->right;  // skip delimiter
    284       goto read_products;
    285     }
    286     result_header.ingredients.push_back(next_recipe_reagent(curr->left));
    287   }
    288   if (curr) {
    289     assert(curr->atom);
    290     result_header.ingredients.push_back(next_recipe_reagent(curr));
    291     return result_header;  // no products
    292   }
    293   read_products:
    294   for (/*nada*/;  curr && !curr->atom;  curr = curr->right)
    295     result_header.products.push_back(next_recipe_reagent(curr->left));
    296   if (curr) {
    297     assert(curr->atom);
    298     result_header.products.push_back(next_recipe_reagent(curr));
    299   }
    300   return result_header;
    301 }
    302
    303 :(before "End Unit Tests")
    304 void test_from_reagent_atomic() {
    305   reagent a("{f: recipe}");
    306   recipe r_header = from_reagent(a);
    307   CHECK(r_header.ingredients.empty());
    308   CHECK(r_header.products.empty());
    309 }
    310 void test_from_reagent_non_atomic() {
    311   reagent a("{f: (recipe number -> number)}");
    312   recipe r_header = from_reagent(a);
    313   CHECK_EQ(SIZE(r_header.ingredients), 1);
    314   CHECK_EQ(SIZE(r_header.products), 1);
    315 }
    316 void test_from_reagent_reads_ingredient_at_end() {
    317   reagent a("{f: (recipe number number)}");
    318   recipe r_header = from_reagent(a);
    319   CHECK_EQ(SIZE(r_header.ingredients), 2);
    320   CHECK(r_header.products.empty());
    321 }
    322 void test_from_reagent_reads_sole_ingredient_at_end() {
    323   reagent a("{f: (recipe number)}");
    324   recipe r_header = from_reagent(a);
    325   CHECK_EQ(SIZE(r_header.ingredients), 1);
    326   CHECK(r_header.products.empty());
    327 }
    328
    329 :(code)
    330 reagent next_recipe_reagent(const type_tree* curr) {
    331   if (!curr->left) return reagent("recipe:"+curr->name);
    332   return reagent(new type_tree(*curr));
    333 }
    334
    335 bool is_mu_recipe(const reagent& r) {
    336   if (!r.type) return false;
    337   if (r.type->atom) {
    338     // End is_mu_recipe Atom Cases(r)
    339     return r.type->name == "recipe-literal";
    340   }
    341   return r.type->left->atom && r.type->left->name == "recipe";
    342 }
    343
    344 :(scenario copy_typecheck_recipe_variable)
    345 % Hide_errors = true;
    346 def main [
    347   3:num <- copy 34  # abc def
    348   {1: (recipe number -> number)} <- copy f  # store literal in a matching variable
    349   {2: (recipe boolean -> boolean)} <- copy {1: (recipe number -> number)}  # mismatch between recipe variables
    350 ]
    351 def f x:num -> y:num [
    352   local-scope
    353   load-ingredients
    354   y <- copy x
    355 ]
    356 +error: main: can't copy '{1: (recipe number -> number)}' to '{2: (recipe boolean -> boolean)}'; types don't match
    357
    358 :(scenario copy_typecheck_recipe_variable_2)
    359 % Hide_errors = true;
    360 def main [
    361   {1: (recipe number -> number)} <- copy f  # mismatch with a recipe literal
    362 ]
    363 def f x:bool -> y:bool [
    364   local-scope
    365   load-ingredients
    366   y <- copy x
    367 ]
    368 +error: main: can't copy 'f' to '{1: (recipe number -> number)}'; types don't match
    369
    370 :(before "End Matching Types For Literal(to)")
    371 if (is_mu_recipe(to)) {
    372   if (!contains_key(Recipe, from.value)) {
    373     raise << "trying to store recipe " << from.name << " into " << to_string(to) << " but there's no such recipe\n" << end();
    374     return false;
    375   }
    376   const recipe& rrhs = get(Recipe, from.value);
    377   const recipe& rlhs = from_reagent(to);
    378   for (long int i = 0;  i < min(SIZE(rlhs.ingredients), SIZE(rrhs.ingredients));  ++i) {
    379     if (!types_match(rlhs.ingredients.at(i), rrhs.ingredients.at(i)))
    380       return false;
    381   }
    382   for (long int i = 0;  i < min(SIZE(rlhs.products), SIZE(rrhs.products));  ++i) {
    383     if (!types_match(rlhs.products.at(i), rrhs.products.at(i)))
    384       return false;
    385   }
    386   return true;
    387 }
    388
    389 :(scenario call_variable_compound_ingredient)
    390 def main [
    391   {1: (recipe (address number) -> number)} <- copy f
    392   2:&:num <- copy null
    393   3:num <- call {1: (recipe (address number) -> number)}, 2:&:num
    394 ]
    395 def f x:&:num -> y:num [
    396   local-scope
    397   load-ingredients
    398   y <- deaddress x
    399 ]
    400 $error: 0
    401
    402 //: make sure we don't accidentally break on a recipe literal
    403 :(scenario jump_forbidden_on_recipe_literals)
    404 % Hide_errors = true;
    405 def foo [
    406   local-scope
    407 ]
    408 def main [
    409   local-scope
    410   {
    411     break-if foo
    412   }
    413 ]
    414 # error should be as if foo is not a recipe
    415 +error: main: missing type for 'foo' in 'break-if foo'
    416
    417 :(before "End JUMP_IF Checks")
    418 check_for_recipe_literals(inst, get(Recipe, r));
    419 :(before "End JUMP_UNLESS Checks")
    420 check_for_recipe_literals(inst, get(Recipe, r));
    421 :(code)
    422 void check_for_recipe_literals(const instruction& inst, const recipe& caller) {
    423   for (int i = 0;  i < SIZE(inst.ingredients);  ++i) {
    424     if (is_mu_recipe(inst.ingredients.at(i))) {
    425       raise << maybe(caller.name) << "missing type for '" << inst.ingredients.at(i).original_string << "' in '" << to_original_string(inst) << "'\n" << end();
    426       if (is_present_in_ingredients(caller, inst.ingredients.at(i).name))
    427         raise << "  did you forget 'load-ingredients'?\n" << end();
    428     }
    429   }
    430 }
    431
    432 :(scenario load_ingredients_missing_error_3)
    433 % Hide_errors = true;
    434 def foo {f: (recipe num -> num)} [
    435   local-scope
    436   b:num <- call f, 1
    437 ]
    438 +error: foo: missing type for 'f' in 'b:num <- call f, 1'
    439 +error:   did you forget 'load-ingredients'?
    440
    441 :(before "End Mu Types Initialization")
    442 put(Type_abbreviations, "function", new_type_tree("recipe"));
    443 put(Type_abbreviations, "fn", new_type_tree("recipe"));
    444
    445 //: copying functions to variables
    446
    447 :(scenario copy_recipe_to_variable)
    448 def main [
    449   {1: (fn number -> number)} <- copy f
    450   2:num <- call {1: (function number -> number)}, 34
    451 ]
    452 def f x:num -> y:num [
    453   local-scope
    454   load-ingredients
    455   y <- copy x
    456 ]
    457 +mem: storing 34 in location 2
    458
    459 :(scenario copy_overloaded_recipe_to_variable)
    460 def main [
    461   local-scope
    462   {x: (fn num -> num)} <- copy f
    463   1:num/raw <- call x, 34
    464 ]
    465 # variant f
    466 def f x:bool -> y:bool [
    467   local-scope
    468   load-ingredients
    469   y <- copy x
    470 ]
    471 # variant f_2
    472 def f x:num -> y:num [
    473   local-scope
    474   load-ingredients
    475   y <- copy x
    476 ]
    477 # x contains f_2
    478 +mem: storing 34 in location 1
    479
    480 :(before "End resolve_ambiguous_call(r, index, inst, caller_recipe) Special-cases")
    481 if (inst.name == "copy") {
    482   for (int i = 0;  i < SIZE(inst.ingredients);  ++i) {
    483     if (!is_recipe_literal(inst.ingredients.at(i))) continue;
    484     if (non_ghost_size(get_or_insert(Recipe_variants, inst.ingredients.at(i).name)) < 1) continue;
    485     // potentially overloaded recipe
    486     string new_name = resolve_ambiguous_call(inst.ingredients.at(i).name, inst.products.at(i), r, index, caller_recipe);
    487     if (new_name == "") continue;
    488     inst.ingredients.at(i).name = new_name;
    489     inst.ingredients.at(i).value = get(Recipe_ordinal, new_name);
    490   }
    491   return;
    492 }
    493 :(code)
    494 string resolve_ambiguous_call(const string& recipe_name, const reagent& call_types, const recipe_ordinal r, int index, const recipe& caller_recipe) {
    495   instruction inst;
    496   inst.name = recipe_name;
    497   if (!is_mu_recipe(call_types)) return "";  // error raised elsewhere
    498   if (is_recipe_literal(call_types)) return "";  // error raised elsewhere
    499   construct_fake_call(call_types, inst);
    500   resolve_ambiguous_call(r, index, inst, caller_recipe);
    501   return inst.name;
    502 }
    503 void construct_fake_call(const reagent& recipe_var, instruction& out) {
    504   assert(recipe_var.type->left->name == "recipe");
    505   type_tree* stem = NULL;
    506   for (stem = recipe_var.type->right;  stem && stem->left->name != "->";  stem = stem->right)
    507     out.ingredients.push_back(copy(stem->left));
    508   if (stem == NULL) return;
    509   for (/*skip '->'*/stem = stem->right;  stem;  stem = stem->right)
    510     out.products.push_back(copy(stem->left));
    511 }
    512
    513 :(scenario copy_shape_shifting_recipe_to_variable)
    514 def main [
    515   local-scope
    516   {x: (fn num -> num)} <- copy f
    517   1:num/raw <- call x, 34
    518 ]
    519 def f x:_elem -> y:_elem [
    520   local-scope
    521   load-inputs
    522   y <- copy x
    523 ]
    524 +mem: storing 34 in location 1
    525
    526 //: passing function literals to (higher-order) functions
    527
    528 :(scenario pass_overloaded_recipe_literal_to_ingredient)
    529 # like copy_overloaded_recipe_to_variable except we bind 'x' in the course of
    530 # a 'call' rather than 'copy'
    531 def main [
    532   1:num <- g f
    533 ]
    534 def g {x: (fn num -> num)} -> result:num [
    535   local-scope
    536   load-ingredients
    537   result <- call x, 34
    538 ]
    539 # variant f
    540 def f x:bool -> y:bool [
    541   local-scope
    542   load-ingredients
    543   y <- copy x
    544 ]
    545 # variant f_2
    546 def f x:num -> y:num [
    547   local-scope
    548   load-ingredients
    549   y <- copy x
    550 ]
    551 # x contains f_2
    552 +mem: storing 34 in location 1
    553
    554 :(after "End resolve_ambiguous_call(r, index, inst, caller_recipe) Special-cases")
    555 for (int i = 0;  i < SIZE(inst.ingredients);  ++i) {
    556   if (!is_mu_recipe(inst.ingredients.at(i))) continue;
    557   if (non_ghost_size(get_or_insert(Recipe_variants, inst.ingredients.at(i).name)) < 1) continue;
    558   if (get(Recipe_ordinal, inst.name) < MAX_PRIMITIVE_RECIPES) continue;
    559   if (non_ghost_size(get_or_insert(Recipe_variants, inst.name)) > 1) {
    560     raise << maybe(caller_recipe.name) << "sorry, we're not yet smart enough to simultaneously guess which overloads you want for '" << inst.name << "' and '" << inst.ingredients.at(i).name << "'\n" << end();
    561     return;
    562   }
    563   const recipe& callee = get(Recipe, get(Recipe_ordinal, inst.name));
    564   if (!callee.has_header) {
    565     raise << maybe(caller_recipe.name) << "sorry, we're not yet smart enough to guess which variant of '" << inst.ingredients.at(i).name << "' you want, when the caller '" << inst.name << "' doesn't have a header\n" << end();
    566     return;
    567   }
    568   string new_name = resolve_ambiguous_call(inst.ingredients.at(i).name, callee.ingredients.at(i), r, index, caller_recipe);
    569   if (new_name != "") {
    570     inst.ingredients.at(i).name = new_name;
    571     inst.ingredients.at(i).value = get(Recipe_ordinal, new_name);
    572   }
    573 }
    574
    575 :(scenario return_overloaded_recipe_literal_to_caller)
    576 def main [
    577   local-scope
    578   {x: (fn num -> num)} <- g
    579   1:num/raw <- call x, 34
    580 ]
    581 def g -> {x: (fn num -> num)} [
    582   local-scope
    583   return f
    584 ]
    585 # variant f
    586 def f x:bool -> y:bool [
    587   local-scope
    588   load-ingredients
    589   y <- copy x
    590 ]
    591 # variant f_2
    592 def f x:num -> y:num [
    593   local-scope
    594   load-ingredients
    595   y <- copy x
    596 ]
    597 # x contains f_2
    598 +mem: storing 34 in location 1
    599
    600 :(before "End resolve_ambiguous_call(r, index, inst, caller_recipe) Special-cases")
    601 if (inst.name == "return" || inst.name == "reply") {
    602   for (int i = 0;  i < SIZE(inst.ingredients);  ++i) {
    603     if (!is_recipe_literal(inst.ingredients.at(i))) continue;
    604     if (non_ghost_size(get_or_insert(Recipe_variants, inst.ingredients.at(i).name)) < 1) continue;
    605     // potentially overloaded recipe
    606     if (!caller_recipe.has_header) {
    607       raise << maybe(caller_recipe.name) << "sorry, we're not yet smart enough to guess which variant of '" << inst.ingredients.at(i).name << "' you want, without a recipe header\n" << end();
    608       return;
    609     }
    610     string new_name = resolve_ambiguous_call(inst.ingredients.at(i).name, caller_recipe.products.at(i), r, index, caller_recipe);
    611     if (new_name == "") continue;
    612     inst.ingredients.at(i).name = new_name;
    613     inst.ingredients.at(i).value = get(Recipe_ordinal, new_name);
    614   }
    615   return;
    616 }