Fix typechecking of mutually recursive functions.

This commit is contained in:
Danila Fedorin 2020-06-21 00:47:26 -07:00
parent 8524e098a8
commit 7c4cfbf3d4
5 changed files with 33 additions and 15 deletions

View File

@ -139,7 +139,7 @@ void definition_group::typecheck(type_mgr& mgr, type_env_ptr& env) {
def_defn->typecheck(mgr); def_defn->typecheck(mgr);
} }
for(auto& def_defnn_name : group->members) { for(auto& def_defnn_name : group->members) {
this->env->generalize(def_defnn_name, mgr); this->env->generalize(def_defnn_name, *group, mgr);
} }
} }
} }

View File

@ -205,7 +205,13 @@ void type_mgr::find_free(const type_ptr& t, std::set<std::string>& into) const {
void type_mgr::find_free(const type_scheme_ptr& t, std::set<std::string>& into) const { void type_mgr::find_free(const type_scheme_ptr& t, std::set<std::string>& into) const {
std::set<std::string> monotype_free; std::set<std::string> monotype_free;
find_free(t->monotype, monotype_free); type_mgr limited_mgr;
for(auto& binding : types) {
auto existing_position = std::find(t->forall.begin(), t->forall.end(), binding.first);
if(existing_position != t->forall.end()) continue;
limited_mgr.types[binding.first] = binding.second;
}
limited_mgr.find_free(t->monotype, monotype_free);
for(auto& not_free : t->forall) { for(auto& not_free : t->forall) {
monotype_free.erase(not_free); monotype_free.erase(not_free);
} }

View File

@ -8,11 +8,11 @@ void type_env::find_free(const type_mgr& mgr, std::set<std::string>& into) const
} }
} }
void type_env::find_free_except(const type_mgr& mgr, const std::string& avoid, void type_env::find_free_except(const type_mgr& mgr, const group& avoid,
std::set<std::string>& into) const { std::set<std::string>& into) const {
if(parent != nullptr) parent->find_free(mgr, into); if(parent != nullptr) parent->find_free(mgr, into);
for(auto& binding : names) { for(auto& binding : names) {
if(binding.first == avoid) continue; if(avoid.members.find(binding.first) != avoid.members.end()) continue;
mgr.find_free(binding.second.type, into); mgr.find_free(binding.second.type, into);
} }
} }
@ -65,7 +65,7 @@ void type_env::bind_type(const std::string& type_name, type_ptr t) {
type_names[type_name] = t; type_names[type_name] = t;
} }
void type_env::generalize(const std::string& name, type_mgr& mgr) { void type_env::generalize(const std::string& name, const group& grp, type_mgr& mgr) {
auto names_it = names.find(name); auto names_it = names.find(name);
if(names_it == names.end()) throw 0; if(names_it == names.end()) throw 0;
if(names_it->second.type->forall.size() > 0) throw 0; if(names_it->second.type->forall.size() > 0) throw 0;
@ -73,7 +73,7 @@ void type_env::generalize(const std::string& name, type_mgr& mgr) {
std::set<std::string> free_in_type; std::set<std::string> free_in_type;
std::set<std::string> free_in_env; std::set<std::string> free_in_env;
mgr.find_free(names_it->second.type->monotype, free_in_type); mgr.find_free(names_it->second.type->monotype, free_in_type);
find_free_except(mgr, name, free_in_env); find_free_except(mgr, grp, free_in_env);
for(auto& free : free_in_type) { for(auto& free : free_in_type) {
if(free_in_env.find(free) != free_in_env.end()) continue; if(free_in_env.find(free) != free_in_env.end()) continue;
names_it->second.type->forall.push_back(free); names_it->second.type->forall.push_back(free);

View File

@ -2,6 +2,7 @@
#include <map> #include <map>
#include <string> #include <string>
#include <set> #include <set>
#include "graph.hpp"
#include "type.hpp" #include "type.hpp"
struct type_env; struct type_env;
@ -29,7 +30,7 @@ struct type_env {
type_env() : type_env(nullptr) {} type_env() : type_env(nullptr) {}
void find_free(const type_mgr& mgr, std::set<std::string>& into) const; void find_free(const type_mgr& mgr, std::set<std::string>& into) const;
void find_free_except(const type_mgr& mgr, const std::string& avoid, void find_free_except(const type_mgr& mgr, const group& avoid,
std::set<std::string>& into) const; std::set<std::string>& into) const;
type_scheme_ptr lookup(const std::string& name) const; type_scheme_ptr lookup(const std::string& name) const;
bool is_global(const std::string& name) const; bool is_global(const std::string& name) const;
@ -41,7 +42,7 @@ struct type_env {
void bind(const std::string& name, type_scheme_ptr t, void bind(const std::string& name, type_scheme_ptr t,
visibility v = visibility::local); visibility v = visibility::local);
void bind_type(const std::string& type_name, type_ptr t); void bind_type(const std::string& type_name, type_ptr t);
void generalize(const std::string& name, type_mgr& mgr); void generalize(const std::string& name, const group& grp, type_mgr& mgr);
}; };

View File

@ -486,17 +486,17 @@ we're trying to operate on is global or not? I propose a flag in our
this, we update the implementation of `type_env` to map variables to this, we update the implementation of `type_env` to map variables to
values of a struct `variable_data`: values of a struct `variable_data`:
{{< codelines "C++" "compiler/12/type_env.hpp" 13 22 >}} {{< codelines "C++" "compiler/12/type_env.hpp" 14 23 >}}
The `visibility` enum is defined as follows: The `visibility` enum is defined as follows:
{{< codelines "C++" "compiler/12/type_env.hpp" 10 10 >}} {{< codelines "C++" "compiler/12/type_env.hpp" 11 11 >}}
As you can see from the above snippet, we also added a `mangled_name` field As you can see from the above snippet, we also added a `mangled_name` field
to the new `variable_data` struct. We will be using this field shortly. We to the new `variable_data` struct. We will be using this field shortly. We
also add a few methods to our `type_env`, and end up with the following: also add a few methods to our `type_env`, and end up with the following:
{{< codelines "C++" "compiler/12/type_env.hpp" 31 44 >}} {{< codelines "C++" "compiler/12/type_env.hpp" 32 45 >}}
We will come back to `find_free` and `find_free_except`, as well as We will come back to `find_free` and `find_free_except`, as well as
`set_mangled_name` and `get_mangled_name`. For now, we just adjust `bind` to `set_mangled_name` and `get_mangled_name`. For now, we just adjust `bind` to
@ -812,9 +812,12 @@ void type_env::find_free_except(const type_mgr& mgr, const std::string& avoid,
Why `find_free_except`? When generalizing a variable whose type was already Why `find_free_except`? When generalizing a variable whose type was already
stored in the environment, all the type variables we could generalize would stored in the environment, all the type variables we could generalize would
not be 'free'. If they only occur in the type we're generalizing, though, not be 'free'. If they only occur in the type we're generalizing, though,
we shouldn't let that stop us! Thus, when finding free type variables, we will we shouldn't let that stop us! More generally, if we see type variables that
avoid looking at the particular variable whose type is being generalized. The are only found in the same mutually recursive group as the binding we're
implementations of the two methods are straightforward: generalizing, we are free to generalize them too. Thus, we pass in
a reference to a `group`, and check if a variable is a member of that group
before searching it for free type variables. The implementations of the two
methods are straightforward:
{{< codelines "C++" "compiler/12/type_env.cpp" 4 18 >}} {{< codelines "C++" "compiler/12/type_env.cpp" 4 18 >}}
@ -824,7 +827,15 @@ that have the same name as the variable we're generalizing, but aren't found
in the same scope. As far as we're concerned, they're different variables! in the same scope. As far as we're concerned, they're different variables!
The two methods use another `find_free` method which we add to `type_mgr`: The two methods use another `find_free` method which we add to `type_mgr`:
{{< codelines "C++" "compiler/12/type.cpp" 206 213 >}} {{< codelines "C++" "compiler/12/type.cpp" 206 219 >}}
This one is a bit of a hack. Typically, while running `find_free`, a
`type_mgr` will resolve any type variables. However, variables from the
`forall` quantifier of a type scheme should not be resolved, since they
are explicitly generic. To prevent the type manager from erroneously resolving
such type variables, we create a new type manager that does not have
these variables bound to anything, and thus marks them as free. We then
filter these variables out of the final list of free variables.
Finally, `generalize` makes sure not to use variables that it finds free: Finally, `generalize` makes sure not to use variables that it finds free: