4272 - type-check variables in non-local spaces

So far we only checked if a single recipe used a variable with multiple
types in any single space. Now we also ensure that the types deduced for
a variable in a space are identical across recipes.
This commit is contained in:
Kartik Agaram 2018-06-25 13:36:27 -07:00
parent b0631dec20
commit 2caaa7f18f
6 changed files with 87 additions and 42 deletions

View File

@ -17,6 +17,7 @@ typedef int recipe_ordinal;
// Recipes are lists of instructions. To perform or 'run' a recipe, the
// computer runs its instructions.
struct recipe {
recipe_ordinal ordinal;
string name;
vector<instruction> steps;
// End recipe Fields
@ -261,6 +262,7 @@ void restore_non_recipe_snapshots() {
:(code)
recipe::recipe() {
ordinal = -1;
// End recipe Constructor
}

View File

@ -65,7 +65,9 @@ int slurp_recipe(istream& in) {
raise << "empty result.name\n" << end();
trace(9991, "parse") << "--- defining " << result.name << end();
if (!contains_key(Recipe_ordinal, result.name))
put(Recipe_ordinal, result.name, Next_recipe_ordinal++);
put(Recipe_ordinal, result.name, Next_recipe_ordinal);
result.ordinal = get(Recipe_ordinal, result.name);
++Next_recipe_ordinal;
if (Recipe.find(get(Recipe_ordinal, result.name)) != Recipe.end()) {
trace(9991, "parse") << "already exists" << end();
if (should_check_for_redefine(result.name))

View File

@ -292,18 +292,3 @@ bool contains_non_special_name(const recipe_ordinal r) {
}
return false;
}
// reagent comparison -- only between reagents in a single recipe
bool operator==(const reagent& a, const reagent& b) {
if (a.name != b.name) return false;
if (property(a, "space") != property(b, "space")) return false;
return true;
}
bool operator<(const reagent& a, const reagent& b) {
int aspace = 0, bspace = 0;
if (has_property(a, "space")) aspace = to_integer(property(a, "space")->value);
if (has_property(b, "space")) bspace = to_integer(property(b, "space")->value);
if (aspace != bspace) return aspace < bspace;
return a.name < b.name;
}

View File

@ -33,9 +33,11 @@ def increment-counter [
//: surrounding space of each recipe.
:(before "End Globals")
map<recipe_ordinal, recipe_ordinal> Surrounding_space;
map<recipe_ordinal, recipe_ordinal> Surrounding_space; // internal to transform; no need to snapshot
:(before "End Reset")
Surrounding_space.clear();
:(before "Transform.push_back(transform_names)")
:(before "Begin Type Modifying Transforms")
Transform.push_back(collect_surrounding_spaces); // idempotent
:(code)

View File

@ -14,36 +14,53 @@ def main [
]
+error: main: 'x' used with multiple types
:(after "Transform.push_back(expand_type_abbreviations)")
//: we need surrounding-space info for type-checking variables in other spaces
:(after "Transform.push_back(collect_surrounding_spaces)")
Transform.push_back(check_or_set_types_by_name); // idempotent
// Keep the name->type mapping for all recipes around for the entire
// transformation phase.
:(before "End Globals")
map<recipe_ordinal, set<reagent, name_lt> > Types_by_space; // internal to transform; no need to snapshot
:(before "End Reset")
Types_by_space.clear();
:(before "End transform_all")
Types_by_space.clear();
:(before "End Types")
struct name_lt {
bool operator()(const reagent& a, const reagent& b) const { return a.name < b.name; }
};
:(code)
void check_or_set_types_by_name(const recipe_ordinal r) {
trace(9991, "transform") << "--- deduce types for recipe " << get(Recipe, r).name << end();
recipe& caller = get(Recipe, r);
set<reagent> known;
trace(9991, "transform") << "--- deduce types for recipe " << caller.name << end();
for (int i = 0; i < SIZE(caller.steps); ++i) {
instruction& inst = caller.steps.at(i);
for (int in = 0; in < SIZE(inst.ingredients); ++in) {
deduce_missing_type(known, inst.ingredients.at(in), caller);
check_type(known, inst.ingredients.at(in), caller);
}
for (int out = 0; out < SIZE(inst.products); ++out) {
deduce_missing_type(known, inst.products.at(out), caller);
check_type(known, inst.products.at(out), caller);
}
for (int in = 0; in < SIZE(inst.ingredients); ++in)
check_or_set_type(inst.ingredients.at(in), caller);
for (int out = 0; out < SIZE(inst.products); ++out)
check_or_set_type(inst.products.at(out), caller);
}
}
void deduce_missing_type(set<reagent>& known, reagent& x, const recipe& caller) {
void check_or_set_type(reagent& curr, const recipe& caller) {
if (is_literal(curr)) return;
if (is_integer(curr.name)) return; // no type-checking for raw locations
set<reagent, name_lt>& known_types = Types_by_space[owning_recipe(curr, caller.ordinal)];
deduce_missing_type(known_types, curr, caller);
check_type(known_types, curr, caller);
}
void deduce_missing_type(set<reagent, name_lt>& known_types, reagent& x, const recipe& caller) {
// Deduce Missing Type(x, caller)
if (x.type) return;
if (is_jump_target(x.name)) {
x.type = new type_tree("label");
return;
}
if (known.find(x) == known.end()) return;
const reagent& exemplar = *known.find(x);
if (known_types.find(x) == known_types.end()) return;
const reagent& exemplar = *known_types.find(x);
x.type = new type_tree(*exemplar.type);
trace(9992, "transform") << x.name << " <= " << names_to_string(x.type) << end();
// spaces are special; their type includes their /names property
@ -56,20 +73,19 @@ void deduce_missing_type(set<reagent>& known, reagent& x, const recipe& caller)
}
}
void check_type(set<reagent>& known, const reagent& x, const recipe& caller) {
void check_type(set<reagent, name_lt>& known_types, const reagent& x, const recipe& caller) {
if (is_literal(x)) return;
if (is_integer(x.name)) return; // if you use raw locations you're probably doing something unsafe
if (!x.type) return; // might get filled in by other logic later
if (is_jump_target(x.name)) {
if (!x.type->atom || x.type->name != "label")
raise << maybe(caller.name) << "non-label '" << x.name << "' must begin with a letter\n" << end();
return;
}
if (known.find(x) == known.end()) {
if (known_types.find(x) == known_types.end()) {
trace(9992, "transform") << x.name << " => " << names_to_string(x.type) << end();
known.insert(x);
known_types.insert(x);
}
if (!types_strictly_match(known.find(x)->type, x.type)) {
if (!types_strictly_match(known_types.find(x)->type, x.type)) {
raise << maybe(caller.name) << "'" << x.name << "' used with multiple types\n" << end();
return;
}
@ -85,6 +101,14 @@ void check_type(set<reagent>& known, const reagent& x, const recipe& caller) {
}
}
recipe_ordinal owning_recipe(const reagent& x, recipe_ordinal r) {
for (int s = space_index(x); s > 0; --s) {
if (!contains_key(Surrounding_space, r)) break; // error raised elsewhere
r = Surrounding_space[r];
}
return r;
}
:(scenario transform_fills_in_missing_types)
def main [
x:num <- copy 11
@ -167,3 +191,19 @@ def main [
]
+error: illegal name '*'
# no crash
:(scenario transform_checks_types_in_surrounding_spaces)
% Hide_errors = true;
# 'x' is a bool in foo's space
def foo [
local-scope
x:bool <- copy false
return default-space/names:foo
]
# try to read 'x' as a num in foo's space
def main [
local-scope
0:space/names:foo <- foo
x:num/space:1 <- copy 34
]
error: foo: 'x' used with multiple types

View File

@ -350,7 +350,7 @@ void check_immutable_ingredients(const recipe_ordinal r) {
const reagent& current_ingredient = caller.ingredients.at(i);
if (is_present_in_products(caller, current_ingredient.name)) continue; // not expected to be immutable
// End Immutable Ingredients Special-cases
set<reagent> immutable_vars;
set<reagent, name_and_space_lt> immutable_vars;
immutable_vars.insert(current_ingredient);
for (int i = 0; i < SIZE(caller.steps); ++i) {
const instruction& inst = caller.steps.at(i);
@ -361,7 +361,7 @@ void check_immutable_ingredients(const recipe_ordinal r) {
}
}
void update_aliases(const instruction& inst, set<reagent>& current_ingredient_and_aliases) {
void update_aliases(const instruction& inst, set<reagent, name_and_space_lt>& current_ingredient_and_aliases) {
set<int> current_ingredient_indices = ingredient_indices(inst, current_ingredient_and_aliases);
if (!contains_key(Recipe, inst.operation)) {
// primitive recipe
@ -393,7 +393,7 @@ void update_aliases(const instruction& inst, set<reagent>& current_ingredient_an
}
set<int> scan_contained_in_product_indices(const instruction& inst, set<int>& ingredient_indices) {
set<reagent> selected_ingredients;
set<reagent, name_and_space_lt> 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; // optional immutable ingredient
@ -435,6 +435,20 @@ bool is_mu_exclusive_container(const type_tree* type) {
return info.kind == EXCLUSIVE_CONTAINER;
}
:(before "End Types")
// reagent comparison -- only in the context of a single recipe
struct name_and_space_lt {
bool operator()(const reagent& a, const reagent& b) const;
};
:(code)
bool name_and_space_lt::operator()(const reagent& a, const reagent& b) const {
int aspace = 0, bspace = 0;
if (has_property(a, "space")) aspace = to_integer(property(a, "space")->value);
if (has_property(b, "space")) bspace = to_integer(property(b, "space")->value);
if (aspace != bspace) return aspace < bspace;
return a.name < b.name;
}
:(scenarios transform)
:(scenario immutability_infects_contained_in_variables)
% Hide_errors = true;
@ -461,7 +475,7 @@ def test-next x:&:test-list -> y:&:test-list/contained-in:x [
+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) {
void check_immutable_ingredient_in_instruction(const instruction& inst, const set<reagent, name_and_space_lt>& current_ingredient_and_aliases, const string& original_ingredient_name, const recipe& caller) {
// first check if the instruction is directly modifying something it shouldn't
for (int i = 0; i < SIZE(inst.products); ++i) {
if (has_property(inst.products.at(i), "lookup")
@ -527,7 +541,7 @@ bool is_present_in_products(const recipe& callee, const string& ingredient_name)
return false;
}
set<int> ingredient_indices(const instruction& inst, const set<reagent>& ingredient_names) {
set<int> ingredient_indices(const instruction& inst, const set<reagent, name_and_space_lt>& ingredient_names) {
set<int> result;
for (int i = 0; i < SIZE(inst.ingredients); ++i) {
if (is_literal(inst.ingredients.at(i))) continue;