about summary refs log tree commit diff stats
path: root/056shape_shifting_recipe.cc
diff options
context:
space:
mode:
Diffstat (limited to '056shape_shifting_recipe.cc')
-rw-r--r--056shape_shifting_recipe.cc1363
1 files changed, 758 insertions, 605 deletions
diff --git a/056shape_shifting_recipe.cc b/056shape_shifting_recipe.cc
index 7eb5f0e3..3e3d9a8d 100644
--- a/056shape_shifting_recipe.cc
+++ b/056shape_shifting_recipe.cc
@@ -1,24 +1,29 @@
 //:: Like container definitions, recipes too can contain type parameters.
 
-:(scenario shape_shifting_recipe)
-def main [
-  10:point <- merge 14, 15
-  12:point <- foo 10:point
-]
-# non-matching variant
-def foo a:num -> result:num [
-  local-scope
-  load-ingredients
-  result <- copy 34
-]
-# matching shape-shifting variant
-def foo a:_t -> result:_t [
-  local-scope
-  load-ingredients
-  result <- copy a
-]
-+mem: storing 14 in location 12
-+mem: storing 15 in location 13
+void test_shape_shifting_recipe() {
+  run(
+      "def main [\n"
+      "  10:point <- merge 14, 15\n"
+      "  12:point <- foo 10:point\n"
+      "]\n"
+      // non-matching variant
+      "def foo a:num -> result:num [\n"
+      "  local-scope\n"
+      "  load-ingredients\n"
+      "  result <- copy 34\n"
+      "]\n"
+      // matching shape-shifting variant
+      "def foo a:_t -> result:_t [\n"
+      "  local-scope\n"
+      "  load-ingredients\n"
+      "  result <- copy a\n"
+      "]\n"
+  );
+  CHECK_TRACE_CONTENTS(
+      "mem: storing 14 in location 12\n"
+      "mem: storing 15 in location 13\n"
+  );
+}
 
 //: Before anything else, disable transforms for shape-shifting recipes and
 //: make sure we never try to actually run a shape-shifting recipe. We should
@@ -536,138 +541,171 @@ void ensure_all_concrete_types(/*const*/ reagent& x, const recipe& exemplar) {
   }
 }
 
-:(scenario shape_shifting_recipe_2)
-def main [
-  10:point <- merge 14, 15
-  12:point <- foo 10:point
-]
-# non-matching shape-shifting variant
-def foo a:_t, b:_t -> result:num [
-  local-scope
-  load-ingredients
-  result <- copy 34
-]
-# matching shape-shifting variant
-def foo a:_t -> result:_t [
-  local-scope
-  load-ingredients
-  result <- copy a
-]
-+mem: storing 14 in location 12
-+mem: storing 15 in location 13
-
-:(scenario shape_shifting_recipe_nonroot)
-def main [
-  10:foo:point <- merge 14, 15, 16
-  20:point <- bar 10:foo:point
-]
-# shape-shifting recipe with type ingredient following some other type
-def bar a:foo:_t -> result:_t [
-  local-scope
-  load-ingredients
-  result <- get a, x:offset
-]
-container foo:_t [
-  x:_t
-  y:num
-]
-+mem: storing 14 in location 20
-+mem: storing 15 in location 21
-
-:(scenario shape_shifting_recipe_nested)
-container c:_a:_b [
-  a:_a
-  b:_b
-]
-def main [
-  s:text <- new [abc]
-  {x: (c (address array character) number)} <- merge s, 34
-  foo x
-]
-def foo x:c:_bar:_baz [
-  local-scope
-  load-ingredients
-]
-# no errors
-
-:(scenario shape_shifting_recipe_type_deduction_ignores_offsets)
-def main [
-  10:foo:point <- merge 14, 15, 16
-  20:point <- bar 10:foo:point
-]
-def bar a:foo:_t -> result:_t [
-  local-scope
-  load-ingredients
-  x:num <- copy 1
-  result <- get a, x:offset  # shouldn't collide with other variable
-]
-container foo:_t [
-  x:_t
-  y:num
-]
-+mem: storing 14 in location 20
-+mem: storing 15 in location 21
-
-:(scenario shape_shifting_recipe_empty)
-def main [
-  foo 1
-]
-# shape-shifting recipe with no body
-def foo a:_t [
-]
-# shouldn't crash
-
-:(scenario shape_shifting_recipe_handles_shape_shifting_new_ingredient)
-def main [
-  1:&:foo:point <- bar 3
-  11:foo:point <- copy *1:&:foo:point
-]
-container foo:_t [
-  x:_t
-  y:num
-]
-def bar x:num -> result:&:foo:_t [
-  local-scope
-  load-ingredients
-  # new refers to _t in its ingredient *value*
-  result <- new {(foo _t) : type}
-]
-+mem: storing 0 in location 11
-+mem: storing 0 in location 12
-+mem: storing 0 in location 13
-
-:(scenario shape_shifting_recipe_handles_shape_shifting_new_ingredient_2)
-def main [
-  1:&:foo:point <- bar 3
-  11:foo:point <- copy *1:&:foo:point
-]
-def bar x:num -> result:&:foo:_t [
-  local-scope
-  load-ingredients
-  # new refers to _t in its ingredient *value*
-  result <- new {(foo _t) : type}
-]
-# container defined after use
-container foo:_t [
-  x:_t
-  y:num
-]
-+mem: storing 0 in location 11
-+mem: storing 0 in location 12
-+mem: storing 0 in location 13
-
-:(scenario shape_shifting_recipe_called_with_dummy)
-def main [
-  _ <- bar 34
-]
-def bar x:_t -> result:&:_t [
-  local-scope
-  load-ingredients
-  result <- copy null
-]
-$error: 0
+void test_shape_shifting_recipe_2() {
+  run(
+      "def main [\n"
+      "  10:point <- merge 14, 15\n"
+      "  12:point <- foo 10:point\n"
+      "]\n"
+      // non-matching shape-shifting variant
+      "def foo a:_t, b:_t -> result:num [\n"
+      "  local-scope\n"
+      "  load-ingredients\n"
+      "  result <- copy 34\n"
+      "]\n"
+      // matching shape-shifting variant
+      "def foo a:_t -> result:_t [\n"
+      "  local-scope\n"
+      "  load-ingredients\n"
+      "  result <- copy a\n"
+      "]\n"
+  );
+  CHECK_TRACE_CONTENTS(
+      "mem: storing 14 in location 12\n"
+      "mem: storing 15 in location 13\n"
+  );
+}
+
+void test_shape_shifting_recipe_nonroot() {
+  run(
+      "def main [\n"
+      "  10:foo:point <- merge 14, 15, 16\n"
+      "  20:point <- bar 10:foo:point\n"
+      "]\n"
+      // shape-shifting recipe with type ingredient following some other type
+      "def bar a:foo:_t -> result:_t [\n"
+      "  local-scope\n"
+      "  load-ingredients\n"
+      "  result <- get a, x:offset\n"
+      "]\n"
+      "container foo:_t [\n"
+      "  x:_t\n"
+      "  y:num\n"
+      "]\n"
+  );
+  CHECK_TRACE_CONTENTS(
+      "mem: storing 14 in location 20\n"
+      "mem: storing 15 in location 21\n"
+  );
+}
+
+void test_shape_shifting_recipe_nested() {
+  run(
+      "container c:_a:_b [\n"
+      "  a:_a\n"
+      "  b:_b\n"
+      "]\n"
+      "def main [\n"
+      "  s:text <- new [abc]\n"
+      "  {x: (c (address array character) number)} <- merge s, 34\n"
+      "  foo x\n"
+      "]\n"
+      "def foo x:c:_bar:_baz [\n"
+      "  local-scope\n"
+      "  load-ingredients\n"
+      "]\n"
+  );
+  // no errors
+}
+
+void test_shape_shifting_recipe_type_deduction_ignores_offsets() {
+  run(
+      "def main [\n"
+      "  10:foo:point <- merge 14, 15, 16\n"
+      "  20:point <- bar 10:foo:point\n"
+      "]\n"
+      "def bar a:foo:_t -> result:_t [\n"
+      "  local-scope\n"
+      "  load-ingredients\n"
+      "  x:num <- copy 1\n"
+      "  result <- get a, x:offset  # shouldn't collide with other variable\n"
+      "]\n"
+      "container foo:_t [\n"
+      "  x:_t\n"
+      "  y:num\n"
+      "]\n"
+  );
+  CHECK_TRACE_CONTENTS(
+      "mem: storing 14 in location 20\n"
+      "mem: storing 15 in location 21\n"
+  );
+}
+
+void test_shape_shifting_recipe_empty() {
+  run(
+      "def main [\n"
+      "  foo 1\n"
+      "]\n"
+      // shape-shifting recipe with no body
+      "def foo a:_t [\n"
+      "]\n"
+  );
+  // shouldn't crash
+}
+
+void test_shape_shifting_recipe_handles_shape_shifting_new_ingredient() {
+  run(
+      "def main [\n"
+      "  1:&:foo:point <- bar 3\n"
+      "  11:foo:point <- copy *1:&:foo:point\n"
+      "]\n"
+      "container foo:_t [\n"
+      "  x:_t\n"
+      "  y:num\n"
+      "]\n"
+      "def bar x:num -> result:&:foo:_t [\n"
+      "  local-scope\n"
+      "  load-ingredients\n"
+         // new refers to _t in its ingredient *value*
+      "  result <- new {(foo _t) : type}\n"
+      "]\n"
+  );
+  CHECK_TRACE_CONTENTS(
+      "mem: storing 0 in location 11\n"
+      "mem: storing 0 in location 12\n"
+      "mem: storing 0 in location 13\n"
+  );
+}
+
+void test_shape_shifting_recipe_handles_shape_shifting_new_ingredient_2() {
+  run(
+      "def main [\n"
+      "  1:&:foo:point <- bar 3\n"
+      "  11:foo:point <- copy *1:&:foo:point\n"
+      "]\n"
+      "def bar x:num -> result:&:foo:_t [\n"
+      "  local-scope\n"
+      "  load-ingredients\n"
+         // new refers to _t in its ingredient *value*
+      "  result <- new {(foo _t) : type}\n"
+      "]\n"
+      // container defined after use
+      "container foo:_t [\n"
+      "  x:_t\n"
+      "  y:num\n"
+      "]\n"
+  );
+  CHECK_TRACE_CONTENTS(
+      "mem: storing 0 in location 11\n"
+      "mem: storing 0 in location 12\n"
+      "mem: storing 0 in location 13\n"
+  );
+}
+
+void test_shape_shifting_recipe_called_with_dummy() {
+  run(
+      "def main [\n"
+      "  _ <- bar 34\n"
+      "]\n"
+      "def bar x:_t -> result:&:_t [\n"
+      "  local-scope\n"
+      "  load-ingredients\n"
+      "  result <- copy null\n"
+      "]\n"
+  );
+  CHECK_TRACE_COUNT("error", 0);
+}
 
-:(code)
 // this one needs a little more fine-grained control
 void test_shape_shifting_new_ingredient_does_not_pollute_global_namespace() {
   // if you specialize a shape-shifting recipe that allocates a type-ingredient..
@@ -695,460 +733,575 @@ void test_shape_shifting_new_ingredient_does_not_pollute_global_namespace() {
 }
 
 //: specializing a type ingredient with a compound type
-:(scenario shape_shifting_recipe_supports_compound_types)
-def main [
-  1:&:point <- new point:type
-  *1:&:point <- put *1:&:point, y:offset, 34
-  3:&:point <- bar 1:&:point  # specialize _t to address:point
-  5:point <- copy *3:&:point
-]
-def bar a:_t -> result:_t [
-  local-scope
-  load-ingredients
-  result <- copy a
-]
-+mem: storing 34 in location 6
+void test_shape_shifting_recipe_supports_compound_types() {
+  run(
+      "def main [\n"
+      "  1:&:point <- new point:type\n"
+      "  *1:&:point <- put *1:&:point, y:offset, 34\n"
+      "  3:&:point <- bar 1:&:point  # specialize _t to address:point\n"
+      "  5:point <- copy *3:&:point\n"
+      "]\n"
+      "def bar a:_t -> result:_t [\n"
+      "  local-scope\n"
+      "  load-ingredients\n"
+      "  result <- copy a\n"
+      "]\n"
+  );
+  CHECK_TRACE_CONTENTS(
+      "mem: storing 34 in location 6\n"
+  );
+}
 
 //: specializing a type ingredient with a compound type -- while *inside* another compound type
-:(scenario shape_shifting_recipe_supports_compound_types_2)
-container foo:_t [
-  value:_t
-]
-def bar x:&:foo:_t -> result:_t [
-  local-scope
-  load-ingredients
-  result <- get *x, value:offset
-]
-def main [
-  1:&:foo:&:point <- new {(foo address point): type}
-  2:&:point <- bar 1:&:foo:&:point
-]
-# no errors; call to 'bar' successfully specialized
-
-:(scenario shape_shifting_recipe_error)
-% Hide_errors = true;
-def main [
-  a:num <- copy 3
-  b:&:num <- foo a
-]
-def foo a:_t -> b:_t [
-  load-ingredients
-  b <- copy a
-]
-+error: main: no call found for 'b:&:num <- foo a'
-
-:(scenario specialize_inside_recipe_without_header)
-def main [
-  foo 3
-]
-def foo [
-  local-scope
-  x:num <- next-ingredient  # ensure no header
-  1:num/raw <- bar x  # call a shape-shifting recipe
-]
-def bar x:_elem -> y:_elem [
-  local-scope
-  load-ingredients
-  y <- add x, 1
-]
-+mem: storing 4 in location 1
-
-:(scenario specialize_with_literal)
-def main [
-  local-scope
-  # permit literal to map to number
-  1:num/raw <- foo 3
-]
-def foo x:_elem -> y:_elem [
-  local-scope
-  load-ingredients
-  y <- add x, 1
-]
-+mem: storing 4 in location 1
-
-:(scenario specialize_with_literal_2)
-def main [
-  local-scope
-  # permit literal to map to character
-  1:char/raw <- foo 3
-]
-def foo x:_elem -> y:_elem [
-  local-scope
-  load-ingredients
-  y <- add x, 1
-]
-+mem: storing 4 in location 1
-
-:(scenario specialize_with_literal_3)
-def main [
-  local-scope
-  # permit '0' to map to address to shape-shifting type-ingredient
-  1:&:char/raw <- foo null
-]
-def foo x:&:_elem -> y:&:_elem [
-  local-scope
-  load-ingredients
-  y <- copy x
-]
-+mem: storing 0 in location 1
-$error: 0
-
-:(scenario specialize_with_literal_4)
-% Hide_errors = true;
-def main [
-  local-scope
-  # ambiguous call: what's the type of its ingredient?!
-  foo 0
-]
-def foo x:&:_elem -> y:&:_elem [
-  local-scope
-  load-ingredients
-  y <- copy x
-]
-+error: main: instruction 'foo' has no valid specialization
-
-:(scenario specialize_with_literal_5)
-def main [
-  foo 3, 4  # recipe mapping two variables to literals
-]
-def foo x:_elem, y:_elem [
-  local-scope
-  load-ingredients
-  1:num/raw <- add x, y
-]
-+mem: storing 7 in location 1
-
-:(scenario multiple_shape_shifting_variants)
-# try to call two different shape-shifting recipes with the same name
-def main [
-  e1:d1:num <- merge 3
-  e2:d2:num <- merge 4, 5
-  1:num/raw <- foo e1
-  2:num/raw <- foo e2
-]
-# the two shape-shifting definitions
-def foo a:d1:_elem -> b:num [
-  local-scope
-  load-ingredients
-  return 34
-]
-def foo a:d2:_elem -> b:num [
-  local-scope
-  load-ingredients
-  return 35
-]
-# the shape-shifting containers they use
-container d1:_elem [
-  x:_elem
-]
-container d2:_elem [
-  x:num
-  y:_elem
-]
-+mem: storing 34 in location 1
-+mem: storing 35 in location 2
-
-:(scenario multiple_shape_shifting_variants_2)
-# static dispatch between shape-shifting variants, _including pointer lookups_
-def main [
-  e1:d1:num <- merge 3
-  e2:&:d2:num <- new {(d2 number): type}
-  1:num/raw <- foo e1
-  2:num/raw <- foo *e2  # different from previous scenario
-]
-def foo a:d1:_elem -> b:num [
-  local-scope
-  load-ingredients
-  return 34
-]
-def foo a:d2:_elem -> b:num [
-  local-scope
-  load-ingredients
-  return 35
-]
-container d1:_elem [
-  x:_elem
-]
-container d2:_elem [
-  x:num
-  y:_elem
-]
-+mem: storing 34 in location 1
-+mem: storing 35 in location 2
-
-:(scenario missing_type_in_shape_shifting_recipe)
-% Hide_errors = true;
-def main [
-  a:d1:num <- merge 3
-  foo a
-]
-def foo a:d1:_elem -> b:num [
-  local-scope
-  load-ingredients
-  copy e  # no such variable
-  return 34
-]
-container d1:_elem [
-  x:_elem
-]
-+error: foo: unknown type for 'e' in 'copy e' (check the name for typos)
-+error: specializing foo: missing type for 'e'
-# and it doesn't crash
-
-:(scenario missing_type_in_shape_shifting_recipe_2)
-% Hide_errors = true;
-def main [
-  a:d1:num <- merge 3
-  foo a
-]
-def foo a:d1:_elem -> b:num [
-  local-scope
-  load-ingredients
-  get e, x:offset  # unknown variable in a 'get', which does some extra checking
-  return 34
-]
-container d1:_elem [
-  x:_elem
-]
-+error: foo: unknown type for 'e' in 'get e, x:offset' (check the name for typos)
-+error: specializing foo: missing type for 'e'
-# and it doesn't crash
-
-:(scenarios transform)
-:(scenario specialize_recursive_shape_shifting_recipe)
-def main [
-  1:num <- copy 34
-  2:num <- foo 1:num
-]
-def foo x:_elem -> y:num [
-  local-scope
-  load-ingredients
-  {
-    break
-    y:num <- foo x
-  }
-  return y
-]
-+transform: new specialization: foo_2
-# transform terminates
-
-:(scenarios run)
-:(scenario specialize_most_similar_variant)
-def main [
-  1:&:num <- new number:type
-  10:num <- foo 1:&:num
-]
-def foo x:_elem -> y:num [
-  local-scope
-  load-ingredients
-  return 34
-]
-def foo x:&:_elem -> y:num [
-  local-scope
-  load-ingredients
-  return 35
-]
-+mem: storing 35 in location 10
-
-:(scenario specialize_most_similar_variant_2)
-# version with headers padded with lots of unrelated concrete types
-def main [
-  1:num <- copy 23
-  2:&:@:num <- copy null
-  4:num <- foo 2:&:@:num, 1:num
-]
-# variant with concrete type
-def foo dummy:&:@:num, x:num -> y:num, dummy:&:@:num [
-  local-scope
-  load-ingredients
-  return 34
-]
-# shape-shifting variant
-def foo dummy:&:@:num, x:_elem -> y:num, dummy:&:@:num [
-  local-scope
-  load-ingredients
-  return 35
-]
-# prefer the concrete variant
-+mem: storing 34 in location 4
-
-:(scenario specialize_most_similar_variant_3)
-def main [
-  1:text <- new [abc]
-  foo 1:text
-]
-def foo x:text [
-  10:num <- copy 34
-]
-def foo x:&:_elem [
-  10:num <- copy 35
-]
-# make sure the more precise version was used
-+mem: storing 34 in location 10
-
-:(scenario specialize_literal_as_number)
-def main [
-  1:num <- foo 23
-]
-def foo x:_elem -> y:num [
-  local-scope
-  load-ingredients
-  return 34
-]
-def foo x:char -> y:num [
-  local-scope
-  load-ingredients
-  return 35
-]
-+mem: storing 34 in location 1
-
-:(scenario specialize_literal_as_number_2)
-# version calling with literal
-def main [
-  1:num <- foo 0
-]
-# variant with concrete type
-def foo x:num -> y:num [
-  local-scope
-  load-ingredients
-  return 34
-]
-# shape-shifting variant
-def foo x:&:_elem -> y:num [
-  local-scope
-  load-ingredients
-  return 35
-]
-# prefer the concrete variant, ignore concrete types in scoring the shape-shifting variant
-+mem: storing 34 in location 1
-
-:(scenario specialize_literal_as_address)
-def main [
-  1:num <- foo null
-]
-# variant with concrete address type
-def foo x:&:num -> y:num [
-  local-scope
-  load-ingredients
-  return 34
-]
-# shape-shifting variant
-def foo x:&:_elem -> y:num [
-  local-scope
-  load-ingredients
-  return 35
-]
-# prefer the concrete variant, ignore concrete types in scoring the shape-shifting variant
-+mem: storing 34 in location 1
-
-:(scenario missing_type_during_specialization)
-% Hide_errors = true;
-# define a shape-shifting recipe
-def foo a:_elem [
-]
-# define a container with field 'z'
-container foo2 [
-  z:num
-]
-def main [
-  local-scope
-  x:foo2 <- merge 34
-  y:num <- get x, z:offse  # typo in 'offset'
-  # define a variable with the same name 'z'
-  z:num <- copy 34
-  # trigger specialization of the shape-shifting recipe
-  foo z
-]
-# shouldn't crash
-
-:(scenario missing_type_during_specialization2)
-% Hide_errors = true;
-# define a shape-shifting recipe
-def foo a:_elem [
-]
-# define a container with field 'z'
-container foo2 [
-  z:num
-]
-def main [
-  local-scope
-  x:foo2 <- merge 34
-  y:num <- get x, z:offse  # typo in 'offset'
-  # define a variable with the same name 'z'
-  z:&:num <- copy 34
-  # trigger specialization of the shape-shifting recipe
-  foo *z
-]
-# shouldn't crash
-
-:(scenario tangle_shape_shifting_recipe)
-# shape-shifting recipe
-def foo a:_elem [
-  local-scope
-  load-ingredients
-  <label1>
-]
-# tangle some code that refers to the type ingredient
-after <label1> [
-  b:_elem <- copy a
-]
-# trigger specialization
-def main [
-  local-scope
-  foo 34
-]
-$error: 0
-
-:(scenario tangle_shape_shifting_recipe_with_type_abbreviation)
-# shape-shifting recipe
-def foo a:_elem [
-  local-scope
-  load-ingredients
-  <label1>
-]
-# tangle some code that refers to the type ingredient
-after <label1> [
-  b:bool <- copy false  # type abbreviation
-]
-# trigger specialization
-def main [
-  local-scope
-  foo 34
-]
-$error: 0
-
-:(scenario shape_shifting_recipe_coexists_with_primitive)
-# recipe overloading a primitive with a generic type
-def add a:&:foo:_elem [
-  assert 0, [should not get here]
-]
-def main [
-  # call primitive add with literal 0
-  add 0, 0
-]
-$error: 0
-
-:(scenario specialization_heuristic_test_1)
-# modeled on the 'buffer' container in text.mu
-container foo_buffer:_elem [
-  x:num
-]
-def main [
-  append 1:&:foo_buffer:char/raw, 2:text/raw
-]
-def append buf:&:foo_buffer:_elem, x:_elem -> buf:&:foo_buffer:_elem [
-  local-scope
-  load-ingredients
-  stash 34
-]
-def append buf:&:foo_buffer:char, x:_elem -> buf:&:foo_buffer:char [
-  local-scope
-  load-ingredients
-  stash 35
-]
-def append buf:&:foo_buffer:_elem, x:&:@:_elem -> buf:&:foo_buffer:_elem [
-  local-scope
-  load-ingredients
-  stash 36
-]
-+app: 36
+void test_shape_shifting_recipe_supports_compound_types_2() {
+  run(
+      "container foo:_t [\n"
+      "  value:_t\n"
+      "]\n"
+      "def bar x:&:foo:_t -> result:_t [\n"
+      "  local-scope\n"
+      "  load-ingredients\n"
+      "  result <- get *x, value:offset\n"
+      "]\n"
+      "def main [\n"
+      "  1:&:foo:&:point <- new {(foo address point): type}\n"
+      "  2:&:point <- bar 1:&:foo:&:point\n"
+      "]\n"
+  );
+  // no errors; call to 'bar' successfully specialized
+}
+
+void test_shape_shifting_recipe_error() {
+  Hide_errors = true;
+  run(
+      "def main [\n"
+      "  a:num <- copy 3\n"
+      "  b:&:num <- foo a\n"
+      "]\n"
+      "def foo a:_t -> b:_t [\n"
+      "  load-ingredients\n"
+      "  b <- copy a\n"
+      "]\n"
+  );
+  CHECK_TRACE_CONTENTS(
+      "error: main: no call found for 'b:&:num <- foo a'\n"
+  );
+}
+
+void test_specialize_inside_recipe_without_header() {
+  run(
+      "def main [\n"
+      "  foo 3\n"
+      "]\n"
+      "def foo [\n"
+      "  local-scope\n"
+      "  x:num <- next-ingredient  # ensure no header\n"
+      "  1:num/raw <- bar x  # call a shape-shifting recipe\n"
+      "]\n"
+      "def bar x:_elem -> y:_elem [\n"
+      "  local-scope\n"
+      "  load-ingredients\n"
+      "  y <- add x, 1\n"
+      "]\n"
+  );
+  CHECK_TRACE_CONTENTS(
+      "mem: storing 4 in location 1\n"
+  );
+}
+
+void test_specialize_with_literal() {
+  run(
+      "def main [\n"
+      "  local-scope\n"
+         // permit literal to map to number
+      "  1:num/raw <- foo 3\n"
+      "]\n"
+      "def foo x:_elem -> y:_elem [\n"
+      "  local-scope\n"
+      "  load-ingredients\n"
+      "  y <- add x, 1\n"
+      "]\n"
+  );
+  CHECK_TRACE_CONTENTS(
+      "mem: storing 4 in location 1\n"
+  );
+}
+
+void test_specialize_with_literal_2() {
+  run(
+      "def main [\n"
+      "  local-scope\n"
+         // permit literal to map to character
+      "  1:char/raw <- foo 3\n"
+      "]\n"
+      "def foo x:_elem -> y:_elem [\n"
+      "  local-scope\n"
+      "  load-ingredients\n"
+      "  y <- add x, 1\n"
+      "]\n"
+  );
+  CHECK_TRACE_CONTENTS(
+      "mem: storing 4 in location 1\n"
+  );
+}
+
+void test_specialize_with_literal_3() {
+  run(
+      "def main [\n"
+      "  local-scope\n"
+         // permit '0' to map to address to shape-shifting type-ingredient
+      "  1:&:char/raw <- foo null\n"
+      "]\n"
+      "def foo x:&:_elem -> y:&:_elem [\n"
+      "  local-scope\n"
+      "  load-ingredients\n"
+      "  y <- copy x\n"
+      "]\n"
+  );
+  CHECK_TRACE_CONTENTS(
+      "mem: storing 0 in location 1\n"
+  );
+  CHECK_TRACE_COUNT("error", 0);
+}
+
+void test_specialize_with_literal_4() {
+  Hide_errors = true;
+  run(
+      "def main [\n"
+      "  local-scope\n"
+         // ambiguous call: what's the type of its ingredient?!
+      "  foo 0\n"
+      "]\n"
+      "def foo x:&:_elem -> y:&:_elem [\n"
+      "  local-scope\n"
+      "  load-ingredients\n"
+      "  y <- copy x\n"
+      "]\n"
+  );
+  CHECK_TRACE_CONTENTS(
+      "error: main: instruction 'foo' has no valid specialization\n"
+  );
+}
+
+void test_specialize_with_literal_5() {
+  run(
+      "def main [\n"
+      "  foo 3, 4\n"  // recipe mapping two variables to literals
+      "]\n"
+      "def foo x:_elem, y:_elem [\n"
+      "  local-scope\n"
+      "  load-ingredients\n"
+      "  1:num/raw <- add x, y\n"
+      "]\n"
+  );
+  CHECK_TRACE_CONTENTS(
+      "mem: storing 7 in location 1\n"
+  );
+}
+
+void test_multiple_shape_shifting_variants() {
+  run(
+      // try to call two different shape-shifting recipes with the same name
+      "def main [\n"
+      "  e1:d1:num <- merge 3\n"
+      "  e2:d2:num <- merge 4, 5\n"
+      "  1:num/raw <- foo e1\n"
+      "  2:num/raw <- foo e2\n"
+      "]\n"
+      // the two shape-shifting definitions
+      "def foo a:d1:_elem -> b:num [\n"
+      "  local-scope\n"
+      "  load-ingredients\n"
+      "  return 34\n"
+      "]\n"
+      "def foo a:d2:_elem -> b:num [\n"
+      "  local-scope\n"
+      "  load-ingredients\n"
+      "  return 35\n"
+      "]\n"
+      // the shape-shifting containers they use
+      "container d1:_elem [\n"
+      "  x:_elem\n"
+      "]\n"
+      "container d2:_elem [\n"
+      "  x:num\n"
+      "  y:_elem\n"
+      "]\n"
+  );
+  CHECK_TRACE_CONTENTS(
+      "mem: storing 34 in location 1\n"
+      "mem: storing 35 in location 2\n"
+  );
+}
+
+void test_multiple_shape_shifting_variants_2() {
+  run(
+      // static dispatch between shape-shifting variants, _including pointer lookups_
+      "def main [\n"
+      "  e1:d1:num <- merge 3\n"
+      "  e2:&:d2:num <- new {(d2 number): type}\n"
+      "  1:num/raw <- foo e1\n"
+      "  2:num/raw <- foo *e2\n"  // different from previous scenario
+      "]\n"
+      "def foo a:d1:_elem -> b:num [\n"
+      "  local-scope\n"
+      "  load-ingredients\n"
+      "  return 34\n"
+      "]\n"
+      "def foo a:d2:_elem -> b:num [\n"
+      "  local-scope\n"
+      "  load-ingredients\n"
+      "  return 35\n"
+      "]\n"
+      "container d1:_elem [\n"
+      "  x:_elem\n"
+      "]\n"
+      "container d2:_elem [\n"
+      "  x:num\n"
+      "  y:_elem\n"
+      "]\n"
+  );
+  CHECK_TRACE_CONTENTS(
+      "mem: storing 34 in location 1\n"
+      "mem: storing 35 in location 2\n"
+  );
+}
+
+void test_missing_type_in_shape_shifting_recipe() {
+  Hide_errors = true;
+  run(
+      "def main [\n"
+      "  a:d1:num <- merge 3\n"
+      "  foo a\n"
+      "]\n"
+      "def foo a:d1:_elem -> b:num [\n"
+      "  local-scope\n"
+      "  load-ingredients\n"
+      "  copy e\n"  // no such variable
+      "  return 34\n"
+      "]\n"
+      "container d1:_elem [\n"
+      "  x:_elem\n"
+      "]\n"
+  );
+  CHECK_TRACE_CONTENTS(
+      "error: foo: unknown type for 'e' in 'copy e' (check the name for typos)\n"
+      "error: specializing foo: missing type for 'e'\n"
+  );
+  // and it doesn't crash
+}
+
+void test_missing_type_in_shape_shifting_recipe_2() {
+  Hide_errors = true;
+  run(
+      "def main [\n"
+      "  a:d1:num <- merge 3\n"
+      "  foo a\n"
+      "]\n"
+      "def foo a:d1:_elem -> b:num [\n"
+      "  local-scope\n"
+      "  load-ingredients\n"
+      "  get e, x:offset\n"  // unknown variable in a 'get', which does some extra checking
+      "  return 34\n"
+      "]\n"
+      "container d1:_elem [\n"
+      "  x:_elem\n"
+      "]\n"
+  );
+  CHECK_TRACE_CONTENTS(
+      "error: foo: unknown type for 'e' in 'get e, x:offset' (check the name for typos)\n"
+      "error: specializing foo: missing type for 'e'\n"
+  );
+  // and it doesn't crash
+}
+
+void test_specialize_recursive_shape_shifting_recipe() {
+  transform(
+      "def main [\n"
+      "  1:num <- copy 34\n"
+      "  2:num <- foo 1:num\n"
+      "]\n"
+      "def foo x:_elem -> y:num [\n"
+      "  local-scope\n"
+      "  load-ingredients\n"
+      "  {\n"
+      "    break\n"
+      "    y:num <- foo x\n"
+      "  }\n"
+      "  return y\n"
+      "]\n"
+  );
+  CHECK_TRACE_CONTENTS(
+      "transform: new specialization: foo_2\n"
+  );
+  // transform terminates
+}
+
+void test_specialize_most_similar_variant() {
+  run(
+      "def main [\n"
+      "  1:&:num <- new number:type\n"
+      "  10:num <- foo 1:&:num\n"
+      "]\n"
+      "def foo x:_elem -> y:num [\n"
+      "  local-scope\n"
+      "  load-ingredients\n"
+      "  return 34\n"
+      "]\n"
+      "def foo x:&:_elem -> y:num [\n"
+      "  local-scope\n"
+      "  load-ingredients\n"
+      "  return 35\n"
+      "]\n"
+  );
+  CHECK_TRACE_CONTENTS(
+      "mem: storing 35 in location 10\n"
+  );
+}
+
+void test_specialize_most_similar_variant_2() {
+  run(
+      // version with headers padded with lots of unrelated concrete types
+      "def main [\n"
+      "  1:num <- copy 23\n"
+      "  2:&:@:num <- copy null\n"
+      "  4:num <- foo 2:&:@:num, 1:num\n"
+      "]\n"
+      // variant with concrete type
+      "def foo dummy:&:@:num, x:num -> y:num, dummy:&:@:num [\n"
+      "  local-scope\n"
+      "  load-ingredients\n"
+      "  return 34\n"
+      "]\n"
+      // shape-shifting variant
+      "def foo dummy:&:@:num, x:_elem -> y:num, dummy:&:@:num [\n"
+      "  local-scope\n"
+      "  load-ingredients\n"
+      "  return 35\n"
+      "]\n"
+  );
+  // prefer the concrete variant
+  CHECK_TRACE_CONTENTS(
+      "mem: storing 34 in location 4\n"
+  );
+}
+
+void test_specialize_most_similar_variant_3() {
+  run(
+      "def main [\n"
+      "  1:text <- new [abc]\n"
+      "  foo 1:text\n"
+      "]\n"
+      "def foo x:text [\n"
+      "  10:num <- copy 34\n"
+      "]\n"
+      "def foo x:&:_elem [\n"
+      "  10:num <- copy 35\n"
+      "]\n"
+  );
+  // make sure the more precise version was used
+  CHECK_TRACE_CONTENTS(
+      "mem: storing 34 in location 10\n"
+  );
+}
+
+void test_specialize_literal_as_number() {
+  run(
+      "def main [\n"
+      "  1:num <- foo 23\n"
+      "]\n"
+      "def foo x:_elem -> y:num [\n"
+      "  local-scope\n"
+      "  load-ingredients\n"
+      "  return 34\n"
+      "]\n"
+      "def foo x:char -> y:num [\n"
+      "  local-scope\n"
+      "  load-ingredients\n"
+      "  return 35\n"
+      "]\n"
+  );
+  CHECK_TRACE_CONTENTS(
+      "mem: storing 34 in location 1\n"
+  );
+}
+
+void test_specialize_literal_as_number_2() {
+  run(
+      // version calling with literal
+      "def main [\n"
+      "  1:num <- foo 0\n"
+      "]\n"
+      // variant with concrete type
+      "def foo x:num -> y:num [\n"
+      "  local-scope\n"
+      "  load-ingredients\n"
+      "  return 34\n"
+      "]\n"
+      // shape-shifting variant
+      "def foo x:&:_elem -> y:num [\n"
+      "  local-scope\n"
+      "  load-ingredients\n"
+      "  return 35\n"
+      "]\n"
+  );
+  // prefer the concrete variant, ignore concrete types in scoring the shape-shifting variant
+  CHECK_TRACE_CONTENTS(
+      "mem: storing 34 in location 1\n"
+  );
+}
+
+void test_specialize_literal_as_address() {
+  run(
+      "def main [\n"
+      "  1:num <- foo null\n"
+      "]\n"
+      // variant with concrete address type
+      "def foo x:&:num -> y:num [\n"
+      "  local-scope\n"
+      "  load-ingredients\n"
+      "  return 34\n"
+      "]\n"
+      // shape-shifting variant
+      "def foo x:&:_elem -> y:num [\n"
+      "  local-scope\n"
+      "  load-ingredients\n"
+      "  return 35\n"
+      "]\n"
+  );
+  // prefer the concrete variant, ignore concrete types in scoring the shape-shifting variant
+  CHECK_TRACE_CONTENTS(
+      "mem: storing 34 in location 1\n"
+  );
+}
+
+void test_missing_type_during_specialization() {
+  Hide_errors = true;
+  run(
+      // define a shape-shifting recipe
+      "def foo a:_elem [\n"
+      "]\n"
+      // define a container with field 'z'
+      "container foo2 [\n"
+      "  z:num\n"
+      "]\n"
+      "def main [\n"
+      "  local-scope\n"
+      "  x:foo2 <- merge 34\n"
+      "  y:num <- get x, z:offse  # typo in 'offset'\n"
+         // define a variable with the same name 'z'
+      "  z:num <- copy 34\n"
+      "  foo z\n"
+      "]\n"
+  );
+  // shouldn't crash
+}
+
+void test_missing_type_during_specialization2() {
+  Hide_errors = true;
+  run(
+      // define a shape-shifting recipe
+      "def foo a:_elem [\n"
+      "]\n"
+      // define a container with field 'z'
+      "container foo2 [\n"
+      "  z:num\n"
+      "]\n"
+      "def main [\n"
+      "  local-scope\n"
+      "  x:foo2 <- merge 34\n"
+      "  y:num <- get x, z:offse  # typo in 'offset'\n"
+         // define a variable with the same name 'z'
+      "  z:&:num <- copy 34\n"
+         // trigger specialization of the shape-shifting recipe
+      "  foo *z\n"
+      "]\n"
+  );
+  // shouldn't crash
+}
+
+void test_tangle_shape_shifting_recipe() {
+  run(
+      // shape-shifting recipe
+      "def foo a:_elem [\n"
+      "  local-scope\n"
+      "  load-ingredients\n"
+      "  <label1>\n"
+      "]\n"
+      // tangle some code that refers to the type ingredient
+      "after <label1> [\n"
+      "  b:_elem <- copy a\n"
+      "]\n"
+      // trigger specialization
+      "def main [\n"
+      "  local-scope\n"
+      "  foo 34\n"
+      "]\n"
+  );
+  CHECK_TRACE_COUNT("error", 0);
+}
+
+void test_tangle_shape_shifting_recipe_with_type_abbreviation() {
+  run(
+      // shape-shifting recipe
+      "def foo a:_elem [\n"
+      "  local-scope\n"
+      "  load-ingredients\n"
+      "  <label1>\n"
+      "]\n"
+      // tangle some code that refers to the type ingredient
+      "after <label1> [\n"
+      "  b:bool <- copy false\n"  // type abbreviation
+      "]\n"
+      // trigger specialization
+      "def main [\n"
+      "  local-scope\n"
+      "  foo 34\n"
+      "]\n"
+  );
+  CHECK_TRACE_COUNT("error", 0);
+}
+
+void test_shape_shifting_recipe_coexists_with_primitive() {
+  run(
+      // recipe overloading a primitive with a generic type
+      "def add a:&:foo:_elem [\n"
+      "  assert 0, [should not get here]\n"
+      "]\n"
+      "def main [\n"
+         // call primitive add with literal 0
+      "  add 0, 0\n"
+      "]\n"
+  );
+  CHECK_TRACE_COUNT("error", 0);
+}
+
+void test_specialization_heuristic_test_1() {
+  run(
+      // modeled on the 'buffer' container in text.mu
+      "container foo_buffer:_elem [\n"
+      "  x:num\n"
+      "]\n"
+      "def main [\n"
+      "  append 1:&:foo_buffer:char/raw, 2:text/raw\n"
+      "]\n"
+      "def append buf:&:foo_buffer:_elem, x:_elem -> buf:&:foo_buffer:_elem [\n"
+      "  local-scope\n"
+      "  load-ingredients\n"
+      "  stash 34\n"
+      "]\n"
+      "def append buf:&:foo_buffer:char, x:_elem -> buf:&:foo_buffer:char [\n"
+      "  local-scope\n"
+      "  load-ingredients\n"
+      "  stash 35\n"
+      "]\n"
+      "def append buf:&:foo_buffer:_elem, x:&:@:_elem -> buf:&:foo_buffer:_elem [\n"
+      "  local-scope\n"
+      "  load-ingredients\n"
+      "  stash 36\n"
+      "]\n"
+  );
+  CHECK_TRACE_CONTENTS(
+      "app: 36\n"
+  );
+}