:(scenario can_modify_ingredients_that_are_also_products)
def main [
local-scope
p:point <- merge 34, 35
p <- foo p
]
def foo p:point -> p:point [
local-scope
load-ingredients
p <- put p, x:offset, 34
]
$error: 0
:(scenario can_modify_ingredients_that_are_also_products_2)
def main [
local-scope
p:address:point <- new point:type
p <- foo p
]
def foo p:address:point -> p:address:point [
local-scope
load-ingredients
*p <- put *p, x:offset, 34
]
$error: 0
:(scenario can_modify_ingredients_that_are_also_products_3)
def main [
local-scope
p:address:array:number <- new number:type, 3
p <- foo p
]
def foo p:address:array:number -> p:address:array:number [
local-scope
load-ingredients
*p <- put-index *p, 0, 34
]
$error: 0
:(scenario ignore_literal_ingredients_for_immutability_checks)
def main [
local-scope
p:address:d1 <- new d1:type
q:number <- foo p
]
def foo p:address:d1 -> q:number [
local-scope
load-ingredients
x:address:d1 <- new d1:type
*x <- put *x, p:offset, 34
reply 36
]
container d1 [
p:number
q:number
]
$error: 0
:(scenario cannot_modify_immutable_ingredients)
% Hide_errors = true;
def main [
local-scope
x:address:number <- new number:type
foo x
]
def foo x:address:number [
local-scope
load-ingredients
*x <- copy 34
]
+error: foo: cannot modify 'x' in instruction '*x <- copy 34' because it's an ingredient of recipe foo but not also a product
:(scenario cannot_modify_immutable_containers)
% Hide_errors = true;
def main [
local-scope
x:point-number <- merge 34, 35, 36
foo x
]
def foo x:point-number [
local-scope
load-ingredients
y:point <- get x, xy:offset
y <- put y, x:offset, 37
]
+error: foo: cannot modify 'y' in instruction 'y <- put y, x:offset, 37' because that would modify 'x' which is an ingredient of recipe foo but not also a product
:(scenario can_modify_immutable_pointers)
def main [
local-scope
x:address:number <- new number:type
foo x
]
def foo x:address:number [
local-scope
load-ingredients
x <- copy 0
]
$error: 0
:(scenario can_modify_immutable_pointers_but_not_their_payloads)
% Hide_errors = true;
def main [
local-scope
x:address:number <- new number:type
foo x
]
def foo x:address:number [
local-scope
load-ingredients
x <- new number:type
*x <- copy 34
]
+error: foo: cannot modify 'x' in instruction '*x <- copy 34' because it's an ingredient of recipe foo but not also a product
:(scenario cannot_call_mutating_recipes_on_immutable_ingredients)
% Hide_errors = true;
def main [
local-scope
p:address:point <- new point:type
foo p
]
def foo p:address:point [
local-scope
load-ingredients
bar p
]
def bar p:address:point -> p:address:point [
local-scope
load-ingredients
]
+error: foo: cannot modify 'p' in instruction 'bar p' because it's an ingredient of recipe foo but not also a product
:(scenario cannot_modify_copies_of_immutable_ingredients)
% Hide_errors = true;
def main [
local-scope
p:address:point <- new point:type
foo p
]
def foo p:address:point [
local-scope
load-ingredients
q:address:point <- copy p
*q <- put *q, x:offset, 34
]
+error: foo: cannot modify 'q' in instruction '*q <- put *q, x:offset, 34' because that would modify p which is an ingredient of recipe foo but not also a product
:(scenario can_modify_copies_of_mutable_ingredients)
def main [
local-scope
p:address:point <- new point:type
foo p
]
def foo p:address:point -> p:address:point [
local-scope
load-ingredients
q:address:point <- copy p
*q <- put *q, x:offset, 34
]
$error: 0
:(scenario cannot_modify_address_inside_immutable_ingredients)
% Hide_errors = true;
container foo [
x:address:array:number
]
def main [
]
def foo a:address:foo [
local-scope
load-ingredients
x:address:array:number <- get *a, x:offset
*x <- put-index *x, 0, 34
]
+error: foo: cannot modify 'x' in instruction '*x <- put-index *x, 0, 34' because that would modify a which is an ingredient of recipe foo but not also a product
:(scenario cannot_modify_address_inside_immutable_ingredients_2)
container foo [
x:address:array:number
]
def main [
]
def foo a:address:foo [
local-scope
load-ingredients
b:foo <- merge 0
x:address:array:number <- get b, x:offset
*x <- put-index *x, 0, 34
]
$error: 0
:(scenario cannot_modify_address_inside_immutable_ingredients_3)
% Hide_errors = true;
container foo [
x:number
]
def main [
]
def foo a:address:array:address:number [
local-scope
load-ingredients
x:address:number <- index *a, 0
*x <- copy 34
]
+error: foo: cannot modify 'x' in instruction '*x <- copy 34' because that would modify a which is an ingredient of recipe foo but not also a product
:(scenario cannot_modify_address_inside_immutable_ingredients_4)
container foo [
x:address:array:number
]
def main [
]
def foo a:address:array:address:number [
local-scope
load-ingredients
b:address:array:address:number <- new {(address number): type}, 3
x:address:number <- index *b, 0
*x <- copy 34
]
$error: 0
:(scenario can_traverse_immutable_ingredients)
container test-list [
next:address:test-list
]
def main [
local-scope
p:address:test-list <- new test-list:type
foo p
]
def foo p:address:test-list [
local-scope
load-ingredients
p2:address:test-list <- bar p
]
def bar x:address:test-list -> y:address:test-list [
local-scope
load-ingredients
y <- get *x, next:offset
]
$error: 0
:(scenario treat_optional_ingredients_as_mutable)
def main [
k:address:number <- new number:type
test k
]
def test k:address:number [
local-scope
load-ingredients
foo k
]
def foo -> [
local-scope
load-ingredients
k:address:number, found?:boolean <- next-ingredient
]
$error: 0
:(scenario treat_optional_ingredients_as_mutable_2)
% Hide_errors = true;
def main [
local-scope
p:address:point <- new point:type
foo p
]
def foo p:address:point [
local-scope
load-ingredients
bar p
]
def bar [
local-scope
load-ingredients
p:address:point <- next-ingredient
]
+error: foo: cannot modify 'p' in instruction 'bar p' because it's an ingredient of recipe foo but not also a product
:(scenario check_space_of_reagents_in_immutability_checks)
def main [
a:address:array:location <- new-closure
b:address:number <- new number:type
run-closure b:address:number, a:address:array:location
]
def new-closure [
new-default-space
x:address:number <- new number:type
return default-space
]
def run-closure x:address:number, s:address:array:location [
local-scope
load-ingredients
0:address:array:location/names:new-closure <- copy s
*x:address:number/space:1 <- copy 34
]
$error: 0
:(before "End Transforms")
Transform.push_back(check_immutable_ingredients);
:(code)
void check_immutable_ingredients(recipe_ordinal r) {
const recipe& caller = get(Recipe, r);
trace(9991, "transform") << "--- check mutability of ingredients in recipe " << caller.name << end();
if (!caller.has_header) return;
for (int i = 0; i < SIZE(caller.ingredients); ++i) {
const reagent& current_ingredient = caller.ingredients.at(i);
if (is_present_in_products(caller, current_ingredient.name)) continue;
set<reagent> immutable_vars;
immutable_vars.insert(current_ingredient);
for (int i = 0; i < SIZE(caller.steps); ++i) {
const instruction& inst = caller.steps.at(i);
check_immutable_ingredient_in_instruction(inst, immutable_vars, current_ingredient.name, caller);
update_aliases(inst, immutable_vars);
}
}
}
void update_aliases(const instruction& inst, set<reagent>& current_ingredient_and_aliases) {
set<int> current_ingredient_indices = ingredient_indices(inst, current_ingredient_and_aliases);
if (!contains_key(Recipe, inst.operation)) {
switch (inst.operation) {
case COPY:
for (set<int>::iterator p = current_ingredient_indices.begin(); p != current_ingredient_indices.end(); ++p)
current_ingredient_and_aliases.insert(inst.products.at(*p).name);
break;
case GET:
case INDEX:
case MAYBE_CONVERT:
if (!current_ingredient_indices.empty()) {
if (is_mu_address(inst.products.at(0)) || is_mu_container(inst.products.at(0)) || is_mu_exclusive_container(inst.products.at(0)))
current_ingredient_and_aliases.insert(inst.products.at(0));
}
break;
default: break;
}
}
else {
set<int> contained_in_product_indices = scan_contained_in_product_indices(inst, current_ingredient_indices);
for (set<int>::iterator p = contained_in_product_indices.begin(); p != contained_in_product_indices.end(); ++p) {
if (*p < SIZE(inst.products))
current_ingredient_and_aliases.insert(inst.products.at(*p));
}
}
}
set<int> scan_contained_in_product_indices(const instruction& inst, set<int>& ingredient_indices) {
set<reagent> selected_ingredients;
const recipe& callee = get(Recipe, inst.operation);
for (set<int>::iterator p = ingredient_indices.begin(); p != ingredient_indices.end(); ++p) {
if (*p >= SIZE(callee.ingredients)) continue;
selected_ingredients.insert(callee.ingredients.at(*p));
}
set<int> result;
for (int i = 0; i < SIZE(callee.products); ++i) {
const reagent& current_product = callee.products.at(i);
TODO
const string_tree* contained_in_name = property(current_product, "contained-in");
if (contained_in_name && selected_ingredients.find(contained_in_name->value) != selected_ingredients.end())
result.insert(i);
}
return result;
}
:(scenarios transform)
:(scenario immutability_infects_contained_in_variables)
% Hide_errors = true;
container test-list [
value:number
next:address:test-list
]
def main [
local-scope
p:address:test-list <- new test-list:type
foo p
]
def foo p:address:test-list [
local-scope
load-ingredients
p2:address:test-list <- test-next p
*p2 <- put *p2, value:offset, 34
]
def test-next x:address:test-list -> y:address:test-list/contained-in:x [
local-scope
load-ingredients
y <- get *x, next:offset
]
+error: foo: cannot modify 'p2' in instruction '*p2 <- put *p2, value:offset, 34' because that would modify p which is an ingredient of recipe foo but not also a product
:(code)
void check_immutable_ingredient_in_instruction(const instruction& inst, const set<reagent>& current_ingredient_and_aliases, const string& original_ingredient_name, const recipe& caller) {
for (int i = 0; i < SIZE(inst.products); ++i) {
if (has_property(inst.products.at(i), "lookup")
&& current_ingredient_and_aliases.find(inst.products.at(i)) != current_ingredient_and_aliases.end()) {
string current_product_name = inst.products.at(i).name;
if (current_product_name == original_ingredient_name)
raise << maybe(caller.name) << "cannot modify '" << current_product_name << "' in instruction '" << to_original_string(inst) << "' because it's an ingredient of recipe " << caller.name << " but not also a product\n" << end();
else
raise << maybe(caller.name) << "cannot modify '" << current_product_name << "' in instruction '" << to_original_string(inst) << "' because that would modify " << original_ingredient_name << " which is an ingredient of recipe " << caller.name << " but not also a product\n" << end();
return;
}
}
set<int> current_ingredient_indices = ingredient_indices(inst, current_ingredient_and_aliases);
if (current_ingredient_indices.empty()) return;
for (set<int>::iterator p = current_ingredient_indices.begin(); p != current_ingredient_indices.end(); ++p) {
const int current_ingredient_index = *p;
reagent current_ingredient = inst.ingredients.at(current_ingredient_index);
canonize_type(current_ingredient);
const string& current_ingredient_name = current_ingredient.name;
if (!contains_key(Recipe, inst.operation)) {
if (inst.operation == PUT || inst.operation == PUT_INDEX) {
if (current_ingredient_index == 0) {
if (current_ingredient_name == original_ingredient_name)
raise << maybe(caller.name) << "cannot modify '" << current_ingredient_name << "' in instruction '" << to_original_string(inst) << "' because it's an ingredient of recipe " << caller.name << " but not also a product\n" << end();
else
raise << maybe(caller.name) << "cannot modify '" << current_ingredient_name << "' in instruction '" << to_original_string(inst) << "' because that would modify '" << original_ingredient_name << "' which is an ingredient of recipe " << caller.name << " but not also a product\n" << end();
}
}
}
else {
if (is_modified_in_recipe(inst.operation, current_ingredient_index, caller)) {
if (current_ingredient_name == original_ingredient_name)
raise << maybe(caller.name) << "cannot modify '" << current_ingredient_name << "' in instruction '" << to_original_string(inst) << "' because it's an ingredient of recipe " << caller.name << " but not also a product\n" << end();
else
raise << maybe(caller.name) << "cannot modify '" << current_ingredient_name << "' in instruction '" << to_original_string(inst) << "' because that would modify '" << original_ingredient_name << "' which is an ingredient of recipe " << caller.name << " but not also a product\n" << end();
}
}
}
}
bool is_modified_in_recipe(recipe_ordinal r, int ingredient_index, const recipe& caller) {
const recipe& callee = get(Recipe, r);
if (!callee.has_header) {
raise << maybe(caller.name) << "can't check mutability of ingredients in recipe " << callee.name << " because it uses 'next-ingredient' directly, rather than a recipe header.\n" << end();
return true;
}
if (ingredient_index >= SIZE(callee.ingredients)) return false;
return is_present_in_products(callee, callee.ingredients.at(ingredient_index).name);
}
bool is_present_in_products(const recipe& callee, const string& ingredient_name) {
for (int i = 0; i < SIZE(callee.products); ++i) {
if (callee.products.at(i).name == ingredient_name)
return true;
}
return false;
}
bool is_present_in_ingredients(const recipe& callee, const string& ingredient_name) {
for (int i = 0; i < SIZE(callee.ingredients); ++i) {
if (callee.ingredients.at(i).name == ingredient_name)
return true;
}
return false;
}
set<int> ingredient_indices(const instruction& inst, const set<reagent>& ingredient_names) {
set<int> result;
for (int i = 0; i < SIZE(inst.ingredients); ++i) {
if (is_literal(inst.ingredients.at(i))) continue;
if (ingredient_names.find(inst.ingredients.at(i)) != ingredient_names.end())
result.insert(i);
}
return result;
}
:(scenarios transform)
:(scenario can_modify_contained_in_addresses)
container test-list [
value:number
next:address:test-list
]
def main [
local-scope
p:address:test-list <- new test-list:type
foo p
]
def foo p:address:test-list -> p:address:test-list [
local-scope
load-ingredients
p2:address:test-list <- test-next p
p <- test-remove p2, p
]
def test-next x:address:test-list -> y:address:test-list [
local-scope
load-ingredients
y <- get *x, next:offset
]
def test-remove x:address:test-list/contained-in:from, from:address:test-list -> from:address:test-list [
local-scope
load-ingredients
*x <- put *x, value:offset, 34
]
$error: 0
:(before "End Immutable Ingredients Special-cases")
if (has_property(current_ingredient, "contained-in")) {
const string_tree* tmp = property(current_ingredient, "contained-in");
if (tmp->left || tmp->right
|| !is_present_in_ingredients(caller, tmp->value)
|| !is_present_in_products(caller, tmp->value))
raise << maybe(caller.name) << "/contained-in can only point to another ingredient+product, but got '" << to_string(property(current_ingredient, "contained-in")) << "'\n" << end();
continue;
}