1 //:: Container definitions can contain 'type ingredients'
  2 
  3 //: pre-requisite: extend our notion of containers to not necessarily be
  4 //: atomic types
  5 :(before "End is_mu_container(type) Special-cases")
  6 if (!type->atom)
  7   return is_mu_container(get_base_type(type));
  8 :(before "End is_mu_exclusive_container(type) Special-cases")
  9 if (!type->atom)
 10   return is_mu_exclusive_container(get_base_type(type));
 11 :(after "Update GET base_type in Check")
 12 base_type = get_base_type(base_type);
 13 :(after "Update GET base_type in Run")
 14 base_type = get_base_type(base_type);
 15 :(after "Update PUT base_type in Check")
 16 base_type = get_base_type(base_type);
 17 :(after "Update PUT base_type in Run")
 18 base_type = get_base_type(base_type);
 19 :(after "Update MAYBE_CONVERT base_type in Check")
 20 base_type = get_base_type(base_type);
 21 :(after "Update base_type in size_of(type)")
 22 base_type = get_base_type(base_type);
 23 :(after "Update base_type in element_type")
 24 base_type = get_base_type(base_type);
 25 :(after "Update base_type in compute_container_address_offsets")
 26 base_type = get_base_type(base_type);
 27 :(after "Update base_type in append_container_address_offsets")
 28 base_type = get_base_type(base_type);
 29 :(after "Update element_base_type For Exclusive Container in append_addresses")
 30 element_base_type = get_base_type(element_base_type);
 31 :(after "Update base_type in skip_addresses")
 32 base_type = get_base_type(base_type);
 33 :(replace{} "const type_tree* get_base_type(const type_tree* t)")
 34 const type_tree* get_base_type(const type_tree* t) {
 35   const type_tree* result = t->atom ? t : t->left;
 36   if (!result->atom)
 37   ¦ raise << "invalid type " << to_string(t) << '\n' << end();
 38   return result;
 39 }
 40 
 41 :(scenario ill_formed_container)
 42 % Hide_errors = true;
 43 def main [
 44   {1: ((foo) num)} <- copy 0
 45 ]
 46 # no crash
 47 
 48 :(scenario size_of_shape_shifting_container)
 49 container foo:_t [
 50   x:_t
 51   y:num
 52 ]
 53 def main [
 54   1:foo:num <- merge 12, 13
 55   3:foo:point <- merge 14, 15, 16
 56 ]
 57 +mem: storing 12 in location 1
 58 +mem: storing 13 in location 2
 59 +mem: storing 14 in location 3
 60 +mem: storing 15 in location 4
 61 +mem: storing 16 in location 5
 62 
 63 :(scenario size_of_shape_shifting_container_2)
 64 # multiple type ingredients
 65 container foo:_a:_b [
 66   x:_a
 67   y:_b
 68 ]
 69 def main [
 70   1:foo:num:bool <- merge 34, 1/true
 71 ]
 72 $error: 0
 73 
 74 :(scenario size_of_shape_shifting_container_3)
 75 container foo:_a:_b [
 76   x:_a
 77   y:_b
 78 ]
 79 def main [
 80   1:text <- new [abc]
 81   # compound types for type ingredients
 82   {2: (foo number (address array character))} <- merge 34/x, 1:text/y
 83 ]
 84 $error: 0
 85 
 86 :(scenario size_of_shape_shifting_container_4)
 87 container foo:_a:_b [
 88   x:_a
 89   y:_b
 90 ]
 91 container bar:_a:_b [
 92   # dilated element
 93   {data: (foo _a (address _b))}
 94 ]
 95 def main [
 96   1:text <- new [abc]
 97   2:bar:num:@:char <- merge 34/x, 1:text/y
 98 ]
 99 $error: 0
100 
101 :(scenario shape_shifting_container_extend)
102 container foo:_a [
103   x:_a
104 ]
105 container foo:_a [
106   y:_a
107 ]
108 $error: 0
109 
110 :(scenario shape_shifting_container_extend_error)
111 % Hide_errors = true;
112 container foo:_a [
113   x:_a
114 ]
115 container foo:_b [
116   y:_b
117 ]
118 +error: headers of container 'foo' must use identical type ingredients
119 
120 :(scenario type_ingredient_must_start_with_underscore)
121 % Hide_errors = true;
122 container foo:t [
123   x:num
124 ]
125 +error: foo: type ingredient 't' must begin with an underscore
126 
127 :(before "End Globals")
128 // We'll use large type ordinals to mean "the following type of the variable".
129 // For example, if we have a generic type called foo:_elem, the type
130 // ingredient _elem in foo's type_info will have value START_TYPE_INGREDIENTS,
131 // and we'll handle it by looking in the current reagent for the next type
132 // that appears after foo.
133 extern const int START_TYPE_INGREDIENTS = 2000;
134 :(before "End Commandline Parsing")  // after loading .mu files
135 assert(Next_type_ordinal < START_TYPE_INGREDIENTS);
136 
137 :(before "End type_info Fields")
138 map<string, type_ordinal> type_ingredient_names;
139 
140 //: Suppress unknown type checks in shape-shifting containers.
141 
142 :(before "Check Container Field Types(info)")
143 if (!info.type_ingredient_names.empty()) continue;
144 
145 :(before "End container Name Refinements")
146 if (name.find(':') != string::npos) {
147   trace("parse") << "container has type ingredients; parsing" << end();
148   if (!read_type_ingredients(name, command)) {
149   ¦ // error; skip rest of the container definition and continue
150   ¦ slurp_balanced_bracket(in);
151   ¦ return;
152   }
153 }
154 
155 :(code)
156 bool read_type_ingredients(string& name, const string& command) {
157   string save_name = name;
158   istringstream in(save_name);
159   name = slurp_until(in, ':');
160   map<string, type_ordinal> type_ingredient_names;
161   if (!slurp_type_ingredients(in, type_ingredient_names, name)) {
162   ¦ return false;
163   }
164   if (contains_key(Type_ordinal, name)
165   ¦ ¦ && contains_key(Type, get(Type_ordinal, name))) {
166   ¦ const type_info& previous_info = get(Type, get(Type_ordinal, name));
167   ¦ // we've already seen this container; make sure type ingredients match
168   ¦ if (!type_ingredients_match(type_ingredient_names, previous_info.type_ingredient_names)) {
169   ¦ ¦ raise << "headers of " << command << " '" << name << "' must use identical type ingredients\n" << end();
170   ¦ ¦ return false;
171   ¦ }
172   ¦ return true;
173   }
174   // we haven't seen this container before
175   if (!contains_key(Type_ordinal, name) || get(Type_ordinal, name) == 0)
176   ¦ put(Type_ordinal, name, Next_type_ordinal++);
177   type_info& info = get_or_insert(Type, get(Type_ordinal, name));
178   info.type_ingredient_names.swap(type_ingredient_names);
179   return true;
180 }
181 
182 bool slurp_type_ingredients(istream& in, map<string, type_ordinal>& out, const string& container_name) {
183   int next_type_ordinal = START_TYPE_INGREDIENTS;
184   while (has_data(in)) {
185   ¦ string curr = slurp_until(in, ':');
186   ¦ if (curr.empty()) {
187   ¦ ¦ raise << container_name << ": empty type ingredients not permitted\n" << end();
188   ¦ ¦ return false;
189   ¦ }
190   ¦ if (!starts_with(curr, "_")) {
191   ¦ ¦ raise << container_name << ": type ingredient '" << curr << "' must begin with an underscore\n" << end();
192   ¦ ¦ return false;
193   ¦ }
194   ¦ if (out.find(curr) != out.end()) {
195   ¦ ¦ raise << container_name << ": can't repeat type ingredient name'" << curr << "' in a single container definition\n" << end();
196   ¦ ¦ return false;
197   ¦ }
198   ¦ put(out, curr, next_type_ordinal++);
199   }
200   return true;
201 }
202 
203 bool type_ingredients_match(const map<string, type_ordinal>& a, const map<string, type_ordinal>& b) {
204   if (SIZE(a) != SIZE(b)) return false;
205   for (map<string, type_ordinal>::const_iterator p = a.begin();  p != a.end();  ++p) {
206   ¦ if (!contains_key(b, p->first)) return false;
207   ¦ if (p->second != get(b, p->first)) return false;
208   }
209   return true;
210 }
211 
212 :(before "End insert_container Special-cases")
213 // check for use of type ingredients
214 else if (is_type_ingredient_name(type->name)) {
215   type->value = get(info.type_ingredient_names, type->name);
216 }
217 :(code)
218 bool is_type_ingredient_name(const string& type) {
219   return starts_with(type, "_");
220 }
221 
222 :(before "End Container Type Checks")
223 if (type->value >= START_TYPE_INGREDIENTS
224   ¦ && (type->value - START_TYPE_INGREDIENTS) < SIZE(get(Type, type->value).type_ingredient_names))
225   return;
226 
227 :(scenario size_of_shape_shifting_exclusive_container)
228 exclusive-container foo:_t [
229   x:_t
230   y:num
231 ]
232 def main [
233   1:foo:num <- merge 0/x, 34
234   3:foo:point <- merge 0/x, 15, 16
235   6:foo:point <- merge 1/y, 23
236 ]
237 +run: {1: ("foo" "number")} <- merge {0: "literal", "x": ()}, {34: "literal"}
238 +mem: storing 0 in location 1
239 +mem: storing 34 in location 2
240 +run: {3: ("foo" "point")} <- merge {0: "literal", "x": ()}, {15: "literal"}, {16: "literal"}
241 +mem: storing 0 in location 3
242 +mem: storing 15 in location 4
243 +mem: storing 16 in location 5
244 +run: {6: ("foo" "point")} <- merge {1: "literal", "y": ()}, {23: "literal"}
245 +mem: storing 1 in location 6
246 +mem: storing 23 in location 7
247 +run: return
248 # no other stores
249 % CHECK_EQ(trace_count_prefix("mem", "storing"), 7);
250 
251 :(before "End variant_type Special-cases")
252 if (contains_type_ingredient(element))
253   replace_type_ingredients(element.type, type->right, info, " while computing variant type of exclusive-container");
254 
255 :(scenario get_on_shape_shifting_container)
256 container foo:_t [
257   x:_t
258   y:num
259 ]
260 def main [
261   1:foo:point <- merge 14, 15, 16
262   2:num <- get 1:foo:point, y:offset
263 ]
264 +mem: storing 16 in location 2
265 
266 :(scenario get_on_shape_shifting_container_2)
267 container foo:_t [
268   x:_t
269   y:num
270 ]
271 def main [
272   1:foo:point <- merge 14, 15, 16
273   2:point <- get 1:foo:point, x:offset
274 ]
275 +mem: storing 14 in location 2
276 +mem: storing 15 in location 3
277 
278 :(scenario get_on_shape_shifting_container_3)
279 container foo:_t [
280   x:_t
281   y:num
282 ]
283 def main [
284   1:foo:&:point <- merge 34/unsafe, 48
285   3:&:point <- get 1:foo:&:point, x:offset
286 ]
287 +mem: storing 34 in location 3
288 
289 :(scenario get_on_shape_shifting_container_inside_container)
290 container foo:_t [
291   x:_t
292   y:num
293 ]
294 container bar [
295   x:foo:point
296   y:num
297 ]
298 def main [
299   1:bar <- merge 14, 15, 16, 17
300   2:num <- get 1:bar, 1:offset
301 ]
302 +mem: storing 17 in location 2
303 
304 :(scenario get_on_complex_shape_shifting_container)
305 container foo:_a:_b [
306   x:_a
307   y:_b
308 ]
309 def main [
310   1:text <- new [abc]
311   {2: (foo number (address array character))} <- merge 34/x, 1:text/y
312   3:text <- get {2: (foo number (address array character))}, y:offset
313   4:bool <- equal 1:text, 3:text
314 ]
315 +mem: storing 1 in location 4
316 
317 :(before "End element_type Special-cases")
318 replace_type_ingredients(element, type, info, " while computing element type of container");
319 :(before "Compute Container Size(element, full_type)")
320 replace_type_ingredients(element, full_type, container_info, location_for_error_messages);
321 :(before "Compute Exclusive Container Size(element, full_type)")
322 replace_type_ingredients(element, full_type, exclusive_container_info, location_for_error_messages);
323 :(before "Compute Container Address Offset(element)")
324 replace_type_ingredients(element, type, info, location_for_error_messages);
325 if (contains_type_ingredient(element)) return;  // error raised elsewhere
326 
327 :(after "Compute size_of Container")
328 assert(!contains_type_ingredient(type));
329 :(after "Compute size_of Exclusive Container")
330 assert(!contains_type_ingredient(type));
331 
332 :(code)
333 bool contains_type_ingredient(const reagent& x) {
334   return contains_type_ingredient(x.type);
335 }
336 
337 bool contains_type_ingredient(const type_tree* type) {
338   if (!type) return false;
339   if (type->atom) return type->value >= START_TYPE_INGREDIENTS;
340   return contains_type_ingredient(type->left) || contains_type_ingredient(type->right);
341 }
342 
343 void replace_type_ingredients(reagent& element, const type_tree* caller_type, const type_info& info, const string& location_for_error_messages) {
344   if (contains_type_ingredient(element)) {
345   ¦ if (!caller_type->right)
346   ¦ ¦ raise << "illegal type " << names_to_string(caller_type) << " seems to be missing a type ingredient or three" << location_for_error_messages << '\n' << end();
347   ¦ replace_type_ingredients(element.type, caller_type->right, info, location_for_error_messages);
348   }
349 }
350 
351 // replace all type_ingredients in element_type with corresponding elements of callsite_type
352 void replace_type_ingredients(type_tree* element_type, const type_tree* callsite_type, const type_info& container_info, const string& location_for_error_messages) {
353   if (!callsite_type) return;  // error but it's already been raised above
354   if (!element_type) return;
355   if (!element_type->atom) {
356   ¦ if (element_type->right == NULL && is_type_ingredient(element_type->left)) {
357   ¦ ¦ int type_ingredient_index = to_type_ingredient_index(element_type->left);
358   ¦ ¦ if (corresponding(callsite_type, type_ingredient_index, is_final_type_ingredient(type_ingredient_index, container_info))->right) {
359   ¦ ¦ ¦ // replacing type ingredient at end of list, and replacement is a non-degenerate compound type -- (a b) but not (a)
360   ¦ ¦ ¦ replace_type_ingredient_at(type_ingredient_index, element_type, callsite_type, container_info, location_for_error_messages);
361   ¦ ¦ ¦ return;
362   ¦ ¦ }
363   ¦ }
364   ¦ replace_type_ingredients(element_type->left, callsite_type, container_info, location_for_error_messages);
365   ¦ replace_type_ingredients(element_type->right, callsite_type, container_info, location_for_error_messages);
366   ¦ return;
367   }
368   if (is_type_ingredient(element_type))
369   ¦ replace_type_ingredient_at(to_type_ingredient_index(element_type), element_type, callsite_type, container_info, location_for_error_messages);
370 }
371 
372 const type_tree* corresponding(const type_tree* type, int index, bool final) {
373   for (const type_tree* curr = type;  curr;  curr = curr->right, --index) {
374   ¦ assert_for_now(!curr->atom);
375   ¦ if (index == 0)
376   ¦ ¦ return final ? curr : curr->left;
377   }
378   assert_for_now(false);
379 }
380 
381 bool is_type_ingredient(const type_tree* type) {
382   return type->atom && type->value >= START_TYPE_INGREDIENTS;
383 }
384 
385 int to_type_ingredient_index(const type_tree* type) {
386   assert(type->atom);
387   return type->value-START_TYPE_INGREDIENTS;
388 }
389 
390 void replace_type_ingredient_at(const int type_ingredient_index, type_tree* element_type, const type_tree* callsite_type, const type_info& container_info, const string& location_for_error_messages) {
391   if (!has_nth_type(callsite_type, type_ingredient_index)) {
392   ¦ raise << "illegal type " << names_to_string(callsite_type) << " seems to be missing a type ingredient or three" << location_for_error_messages << '\n' << end();
393   ¦ return;
394   }
395   *element_type = *nth_type_ingredient(callsite_type, type_ingredient_index, container_info);
396 }
397 
398 const type_tree* nth_type_ingredient(const type_tree* callsite_type, int type_ingredient_index, const type_info& container_info) {
399   bool final = is_final_type_ingredient(type_ingredient_index, container_info);
400   const type_tree* curr = callsite_type;
401   for (int i = 0;  i < type_ingredient_index;  ++i) {
402   ¦ assert(curr);
403   ¦ assert(!curr->atom);
404 //?     cerr << "type ingredient " << i << " is " << to_string(curr->left) << '\n';
405   ¦ curr = curr->right;
406   }
407   assert(curr);
408   if (curr->atom) return curr;
409   if (!final) return curr->left;
410   if (!curr->right) return curr->left;
411   return curr;
412 }
413 
414 bool is_final_type_ingredient(int type_ingredient_index, const type_info& container_info) {
415   for (map<string, type_ordinal>::const_iterator p = container_info.type_ingredient_names.begin();
416   ¦ ¦ ¦p != container_info.type_ingredient_names.end();
417   ¦ ¦ ¦++p) {
418   ¦ if (p->second > START_TYPE_INGREDIENTS+type_ingredient_index) return false;
419   }
420   return true;
421 }
422 
423 :(before "End Unit Tests")
424 void test_replace_type_ingredients_entire() {
425   run("container foo:_elem [\n"
426   ¦ ¦ "  x:_elem\n"
427   ¦ ¦ "  y:num\n"
428   ¦ ¦ "]\n");
429   reagent callsite("x:foo:point");
430   reagent element = element_type(callsite.type, 0);
431   CHECK_EQ(to_string(element), "{x: \"point\"}");
432 }
433 
434 void test_replace_type_ingredients_tail() {
435   run("container foo:_elem [\n"
436   ¦ ¦ "  x:_elem\n"
437   ¦ ¦ "]\n"
438   ¦ ¦ "container bar:_elem [\n"
439   ¦ ¦ "  x:foo:_elem\n"
440   ¦ ¦ "]\n");
441   reagent callsite("x:bar:point");
442   reagent element = element_type(callsite.type, 0);
443   CHECK_EQ(to_string(element), "{x: (\"foo\" \"point\")}");
444 }
445 
446 void test_replace_type_ingredients_head_tail_multiple() {
447   run("container foo:_elem [\n"
448   ¦ ¦ "  x:_elem\n"
449   ¦ ¦ "]\n"
450   ¦ ¦ "container bar:_elem [\n"
451   ¦ ¦ "  x:foo:_elem\n"
452   ¦ ¦ "]\n");
453   reagent callsite("x:bar:address:array:character");
454   reagent element = element_type(callsite.type, 0);
455   CHECK_EQ(to_string(element), "{x: (\"foo\" \"address\" \"array\" \"character\")}");
456 }
457 
458 void test_replace_type_ingredients_head_middle() {
459   run("container foo:_elem [\n"
460   ¦ ¦ "  x:_elem\n"
461   ¦ ¦ "]\n"
462   ¦ ¦ "container bar:_elem [\n"
463   ¦ ¦ "  x:foo:_elem:num\n"
464   ¦ ¦ "]\n");
465   reagent callsite("x:bar:address");
466   reagent element = element_type(callsite.type, 0);
467   CHECK_EQ(to_string(element), "{x: (\"foo\" \"address\" \"number\")}");
468 }
469 
470 void test_replace_last_type_ingredient_with_multiple() {
471   run("container foo:_a:_b [\n"
472   ¦ ¦ "  x:_a\n"
473   ¦ ¦ "  y:_b\n"
474   ¦ ¦ "]\n");
475   reagent callsite("{f: (foo number (address array character))}");
476   reagent element1 = element_type(callsite.type, 0);
477   CHECK_EQ(to_string(element1), "{x: \"number\"}");
478   reagent element2 = element_type(callsite.type, 1);
479   CHECK_EQ(to_string(element2), "{y: (\"address\" \"array\" \"character\")}");
480 }
481 
482 void test_replace_last_type_ingredient_inside_compound() {
483   run("container foo:_a:_b [\n"
484   ¦ ¦ "  {x: (bar _a (address _b))}\n"
485   ¦ ¦ "]\n");
486   reagent callsite("f:foo:number:array:character");
487   reagent element = element_type(callsite.type, 0);
488   CHECK_EQ(names_to_string_without_quotes(element.type), "(bar number (address array character))");
489 }
490 
491 void test_replace_middle_type_ingredient_with_multiple() {
492   run("container foo:_a:_b:_c [\n"
493   ¦ ¦ "  x:_a\n"
494   ¦ ¦ "  y:_b\n"
495   ¦ ¦ "  z:_c\n"
496   ¦ ¦ "]\n");
497   reagent callsite("{f: (foo number (address array character) boolean)}");
498   reagent element1 = element_type(callsite.type, 0);
499   CHECK_EQ(to_string(element1), "{x: \"number\"}");
500   reagent element2 = element_type(callsite.type, 1);
501   CHECK_EQ(to_string(element2), "{y: (\"address\" \"array\" \"character\")}");
502   reagent element3 = element_type(callsite.type, 2);
503   CHECK_EQ(to_string(element3), "{z: \"boolean\"}");
504 }
505 
506 void test_replace_middle_type_ingredient_with_multiple2() {
507   run("container foo:_key:_value [\n"
508   ¦ ¦ "  key:_key\n"
509   ¦ ¦ "  value:_value\n"
510   ¦ ¦ "]\n");
511   reagent callsite("{f: (foo (address array character) number)}");
512   reagent element = element_type(callsite.type, 0);
513   CHECK_EQ(to_string(element), "{key: (\"address\" \"array\" \"character\")}");
514 }
515 
516 void test_replace_middle_type_ingredient_with_multiple3() {
517   run("container foo_table:_key:_value [\n"
518   ¦ ¦ "  data:&:@:foo_table_row:_key:_value\n"
519   ¦ ¦ "]\n"
520   ¦ ¦ "\n"
521   ¦ ¦ "container foo_table_row:_key:_value [\n"
522   ¦ ¦ "  key:_key\n"
523   ¦ ¦ "  value:_value\n"
524   ¦ ¦ "]\n");
525   reagent callsite("{f: (foo_table (address array character) number)}");
526   reagent element = element_type(callsite.type, 0);
527   CHECK_EQ(to_string(element), "{data: (\"address\" \"array\" \"foo_table_row\" (\"address\" \"array\" \"character\") \"number\")}");
528 }
529 
530 :(code)
531 bool has_nth_type(const type_tree* base, int n) {
532   assert(n >= 0);
533   if (!base) return false;
534   if (n == 0) return true;
535   return has_nth_type(base->right, n-1);
536 }
537 
538 :(scenario get_on_shape_shifting_container_error)
539 % Hide_errors = true;
540 container foo:_t [
541   x:_t
542   y:num
543 ]
544 def main [
545   10:foo:point <- merge 14, 15, 16
546   1:num <- get 10:foo, 1:offset
547 ]
548 +error: illegal type "foo" seems to be missing a type ingredient or three in '1:num <- get 10:foo, 1:offset'
549 
550 //:: fix up previous layers
551 
552 //: We have two transforms in previous layers -- for computing sizes and
553 //: offsets containing addresses for containers and exclusive containers --
554 //: that we need to teach about type ingredients.
555 
556 :(before "End compute_container_sizes Non-atom Special-cases")
557 const type_tree* root = get_base_type(type);
558 if (contains_key(Type, root->value)) {
559   type_info& info = get(Type, root->value);
560   if (info.kind == CONTAINER) {
561   ¦ compute_container_sizes(info, type, pending_metadata, location_for_error_messages);
562   ¦ return;
563   }
564   if (info.kind == EXCLUSIVE_CONTAINER) {
565   ¦ compute_exclusive_container_sizes(info, type, pending_metadata, location_for_error_messages);
566   ¦ return;
567   }
568 }  // otherwise error raised elsewhere
569 
570 :(before "End Unit Tests")
571 void test_container_sizes_shape_shifting_container() {
572   run("container foo:_t [\n"
573   ¦ ¦ "  x:num\n"
574   ¦ ¦ "  y:_t\n"
575   ¦ ¦ "]\n");
576   reagent r("x:foo:point");
577   compute_container_sizes(r, "");
578   CHECK_EQ(r.metadata.size, 3);
579 }
580 
581 void test_container_sizes_shape_shifting_exclusive_container() {
582   run("exclusive-container foo:_t [\n"
583   ¦ ¦ "  x:num\n"
584   ¦ ¦ "  y:_t\n"
585   ¦ ¦ "]\n");
586   reagent r("x:foo:point");
587   compute_container_sizes(r, "");
588   CHECK_EQ(r.metadata.size, 3);
589   reagent r2("x:foo:num");
590   compute_container_sizes(r2, "");
591   CHECK_EQ(r2.metadata.size, 2);
592 }
593 
594 void test_container_sizes_compound_type_ingredient() {
595   run("container foo:_t [\n"
596   ¦ ¦ "  x:num\n"
597   ¦ ¦ "  y:_t\n"
598   ¦ ¦ "]\n");
599   reagent r("x:foo:&:point");
600   compute_container_sizes(r, "");
601   CHECK_EQ(r.metadata.size, 2);
602   // scan also pre-computes metadata for type ingredient
603   reagent point("x:point");
604   CHECK(contains_key(Container_metadata, point.type));
605   CHECK_EQ(get(Container_metadata, point.type).size, 2);
606 }
607 
608 void test_container_sizes_recursive_shape_shifting_container() {
609   run("container foo:_t [\n"
610   ¦ ¦ "  x:num\n"
611   ¦ ¦ "  y:&:foo:_t\n"
612   ¦ ¦ "]\n");
613   reagent r2("x:foo:num");
614   compute_container_sizes(r2, "");
615   CHECK_EQ(r2.metadata.size, 2);
616 }
617 
618 :(before "End compute_container_address_offsets Non-atom Special-cases")
619 const type_tree* root = get_base_type(type);
620 if (!contains_key(Type, root->value)) return;  // error raised elsewhere
621 type_info& info = get(Type, root->value);
622 if (info.kind == CONTAINER) {
623   compute_container_address_offsets(info, type, location_for_error_messages);
624   return;
625 }
626 if (info.kind == EXCLUSIVE_CONTAINER) {
627   compute_exclusive_container_address_offsets(info, type, location_for_error_messages);
628   return;
629 }
630 
631 :(before "End Unit Tests")
632 void test_container_address_offsets_in_shape_shifting_container() {
633   run("container foo:_t [\n"
634   ¦ ¦ "  x:num\n"
635   ¦ ¦ "  y:_t\n"
636   ¦ ¦ "]\n");
637   reagent r("x:foo:&:num");
638   compute_container_sizes(r, "");
639   compute_container_address_offsets(r, "");
640   CHECK_EQ(SIZE(r.metadata.address), 1);
641   CHECK(contains_key(r.metadata.address, set<tag_condition_info>()));
642   set<address_element_info>& offset_info = get(r.metadata.address, set<tag_condition_info>());
643   CHECK_EQ(SIZE(offset_info), 1);
644   CHECK_EQ(offset_info.begin()->offset, 1);  //
645   CHECK(offset_info.begin()->payload_type->atom);
646   CHECK_EQ(offset_info.begin()->payload_type->name, "number");
647 }
648 
649 void test_container_address_offsets_in_nested_shape_shifting_container() {
650   run("container foo:_t [\n"
651   ¦ ¦ "  x:num\n"
652   ¦ ¦ "  y:_t\n"
653   ¦ ¦ "]\n"
654   ¦ ¦ "container bar:_t [\n"
655   ¦ ¦ "  x:_t\n"
656   ¦ ¦ "  y:foo:_t\n"
657   ¦ ¦ "]\n");
658   reagent r("x:bar:&:num");
659   CLEAR_TRACE;
660   compute_container_sizes(r, "");
661   compute_container_address_offsets(r, "");
662   CHECK_EQ(SIZE(r.metadata.address), 1);
663   CHECK(contains_key(r.metadata.address, set<tag_condition_info>()));
664   set<address_element_info>& offset_info = get(r.metadata.address, set<tag_condition_info>());
665   CHECK_EQ(SIZE(offset_info), 2);
666   CHECK_EQ(offset_info.begin()->offset, 0);  //
667   CHECK(offset_info.begin()->payload_type->atom);
668   CHECK_EQ(offset_info.begin()->payload_type->name, "number");
669   CHECK_EQ((++offset_info.begin())->offset, 2);  //
670   CHECK((++offset_info.begin())->payload_type->atom);
671   CHECK_EQ((++offset_info.begin())->payload_type->name, "number");
672 }
673 
674 :(scenario typos_in_container_definitions)
675 % Hide_errors = true;
676 container foo:_t [
677   x:adress:_t  # typo
678 ]
679 def main [
680   local-scope
681   x:address:foo:num <- new {(foo num): type}
682 ]
683 # no crash
684 
685 :(scenario typos_in_recipes)
686 % Hide_errors = true;
687 def foo [
688   local-scope
689   x:adress:array:number <- copy 0  # typo
690 ]
691 # shouldn't crash
692 
693 //:: 'merge' on shape-shifting containers
694 
695 :(scenario merge_check_shape_shifting_container_containing_exclusive_container)
696 container foo:_elem [
697   x:num
698   y:_elem
699 ]
700 exclusive-container bar [
701   x:num
702   y:num
703 ]
704 def main [
705   1:foo:bar <- merge 23, 1/y, 34
706 ]
707 +mem: storing 23 in location 1
708 +mem: storing 1 in location 2
709 +mem: storing 34 in location 3
710 $error: 0
711 
712 :(scenario merge_check_shape_shifting_container_containing_exclusive_container_2)
713 % Hide_errors = true;
714 container foo:_elem [
715   x:num
716   y:_elem
717 ]
718 exclusive-container bar [
719   x:num
720   y:num
721 ]
722 def main [
723   1:foo:bar <- merge 23, 1/y, 34, 35
724 ]
725 +error: main: too many ingredients in '1:foo:bar <- merge 23, 1/y, 34, 35'
726 
727 :(scenario merge_check_shape_shifting_exclusive_container_containing_container)
728 exclusive-container foo:_elem [
729   x:num
730   y:_elem
731 ]
732 container bar [
733   x:num
734   y:num
735 ]
736 def main [
737   1:foo:bar <- merge 1/y, 23, 34
738 ]
739 +mem: storing 1 in location 1
740 +mem: storing 23 in location 2
741 +mem: storing 34 in location 3
742 $error: 0
743 
744 :(scenario merge_check_shape_shifting_exclusive_container_containing_container_2)
745 exclusive-container foo:_elem [
746   x:num
747   y:_elem
748 ]
749 container bar [
750   x:num
751   y:num
752 ]
753 def main [
754   1:foo:bar <- merge 0/x, 23
755 ]
756 $error: 0
757 
758 :(scenario merge_check_shape_shifting_exclusive_container_containing_container_3)
759 % Hide_errors = true;
760 exclusive-container foo:_elem [
761   x:num
762   y:_elem
763 ]
764 container bar [
765   x:num
766   y:num
767 ]
768 def main [
769   1:foo:bar <- merge 1/y, 23
770 ]
771 +error: main: too few ingredients in '1:foo:bar <- merge 1/y, 23'