open-goal-jak-project/common/type_system/TypeSystem.cpp

2453 lines
87 KiB
C++

/*!
* @file TypeSystem.cpp
* The GOAL Type System.
* Stores types, symbol types, methods, etc, and does typechecking, lowest-common-ancestor, field
* access types, and reverse type lookups.
*/
#include "TypeSystem.h"
#include <algorithm>
#include <stdexcept>
#include "common/log/log.h"
#include "common/util/Assert.h"
#include "common/util/math_util.h"
#include "fmt/color.h"
#include "fmt/core.h"
namespace {
template <typename... Args>
[[noreturn]] void throw_typesystem_error(const std::string& str, Args&&... args) {
lg::print(fg(fmt::color::crimson) | fmt::emphasis::bold, "-- Type Error! --\n");
if (!str.empty() && str.back() == '\n') {
lg::print(fg(fmt::color::yellow), str, std::forward<Args>(args)...);
} else {
lg::print(fg(fmt::color::yellow), str + '\n', std::forward<Args>(args)...);
}
throw std::runtime_error(
fmt::format("Type Error: {}", fmt::format(fmt::runtime(str), std::forward<Args>(args)...)));
}
} // namespace
TypeSystem::TypeSystem() {
// the "none" and "_type_" types are included by default.
add_type("none", std::make_unique<NullType>("none"));
add_type("_type_", std::make_unique<NullType>("_type_"));
add_type("_varargs_", std::make_unique<NullType>("_varargs_"));
}
/*!
* Add a new type. If the type exists, and this new type is different, it is an error if
* throw_on_redefine is set. The type should be fully set up (fields, etc) before running this.
*/
Type* TypeSystem::add_type(const std::string& name, std::unique_ptr<Type> type) {
auto method_kv = m_forward_declared_method_counts.find(name);
if (method_kv != m_forward_declared_method_counts.end()) {
int method_count = get_next_method_id(type.get());
if (method_count != method_kv->second) {
throw_typesystem_error(
"Type {} was defined with {} methods, but was forward declared with {}\n", name,
method_count, method_kv->second);
}
}
auto kv = m_types.find(name);
if (kv != m_types.end()) {
// exists already
if (*kv->second != *type) {
// exists, and we are trying to change it!
// Check if the type is allowed to be redefined
if (m_allow_redefinition ||
std::find(m_types_allowed_to_be_redefined.begin(), m_types_allowed_to_be_redefined.end(),
kv->second->get_name()) != m_types_allowed_to_be_redefined.end()) {
lg::print("[TypeSystem] Type {} was originally\n{}\nand is redefined as\n{}\n",
kv->second->get_name(), kv->second->print(), type->print());
// extra dangerous, we have allowed type redefinition!
// keep the unique_ptr around, just in case somebody references this old type pointer.
m_old_types.push_back(std::move(m_types[name]));
// update the type
m_types[name] = std::move(type);
} else {
throw_typesystem_error(
"Inconsistent type definition. Type {} was originally\n{}\nand is redefined "
"as\n{}\nDiff:\n{}\n",
kv->second->get_name(), kv->second->print(), type->print(), kv->second->diff(*type));
}
}
} else {
// newly defined!
// objects get to skip these checks because it is the root
if (name != "object" && name != "none" && name != "_type_" && name != "_varargs_") {
if (m_forward_declared_types.find(type->get_parent()) != m_forward_declared_types.end()) {
throw_typesystem_error(
"Cannot create new type {}. The parent type {} is not fully defined.\n",
type->get_name(), type->get_parent());
}
if (m_types.find(type->get_parent()) == m_types.end()) {
throw_typesystem_error("Cannot create new type {}. The parent type {} is not defined.\n",
type->get_name(), type->get_parent());
}
}
m_types[name] = std::move(type);
auto fwd_it = m_forward_declared_types.find(name);
if (fwd_it != m_forward_declared_types.end()) {
// need to check parent is correct.
if (!tc(TypeSpec(fwd_it->second), TypeSpec(name))) {
throw_typesystem_error("Type {} was original declared as a child of {}, but is not.\n",
name, fwd_it->second);
}
}
m_forward_declared_types.erase(name);
}
return m_types[name].get();
}
/*!
* Inform the type system that there will eventually be a type named "name".
* This will allow the type system to generate TypeSpecs for this type, but not access detailed
* information, or know the exact size.
*/
void TypeSystem::forward_declare_type_as_type(const std::string& name) {
auto type_it = m_types.find(name);
if (type_it != m_types.end()) {
return;
}
auto it = m_forward_declared_types.find(name);
if (it == m_forward_declared_types.end()) {
m_forward_declared_types[name] = "object";
} else {
throw_typesystem_error(
"Tried to forward declare {} as a type multiple times. Previous: {} Current: object", name,
it->second);
}
}
/*!
* Inform the type system that there will eventually be a type named "name" and that it's a basic.
* This allows the type to be used in a few specific places. For instance a basic can have
* a field where an element is the same type.
*/
void TypeSystem::forward_declare_type_as(const std::string& new_type,
const std::string& parent_type) {
auto type_it = m_types.find(new_type);
if (type_it != m_types.end()) {
auto parent_it = m_types.find(parent_type);
if (parent_it == m_types.end()) {
throw_typesystem_error(
"Got a forward declaration for known type {} where the parent {} is unknown", new_type,
parent_type);
}
if (!tc(TypeSpec(parent_type), TypeSpec(new_type))) {
throw_typesystem_error(
"Got a forward definition that type {} is a {} which disagrees with existing "
"fully-defined types.",
new_type, parent_type);
}
// ignore forward declaration
return;
}
auto fwd_it = m_forward_declared_types.find(new_type);
if (fwd_it == m_forward_declared_types.end()) {
m_forward_declared_types[new_type] = parent_type;
} else {
if (fwd_it->second != parent_type) {
auto old_parent_it = m_types.find(fwd_it->second);
auto new_parent_it = m_types.find(parent_type);
auto old_ts = TypeSpec(fwd_it->second);
if (old_parent_it != m_types.end() && new_parent_it != m_types.end()) {
auto new_ts = TypeSpec(new_parent_it->second->get_name());
if (tc(old_ts, new_ts)) {
// new is more specific or equal to old:
m_forward_declared_types[new_type] = new_ts.base_type();
} else if (tc(new_ts, old_ts)) {
// old is more specific or equal to new:
} else {
throw_typesystem_error(
"Got a forward declaration that type {} is a {}, which disagrees with a previous "
"forward declaration that it was a {} (incompatible types)\n",
new_type, parent_type, fwd_it->second);
}
} else {
// not enough info to know if this is safe or not!.
throw_typesystem_error(
"Got a forward declaration that type {} is a {}, which disagrees with a previous "
"forward declaration that it was a {} (not enough information to know this is okay, "
"forward declare more types to resolve this)\n",
new_type, parent_type, fwd_it->second);
}
}
}
}
void TypeSystem::forward_declare_type_method_count(const std::string& name, int num_methods) {
auto existing_fwd = m_forward_declared_method_counts.find(name);
if (existing_fwd != m_forward_declared_method_counts.end() &&
existing_fwd->second != num_methods) {
throw_typesystem_error(
"Type {} was originally forward declared with {} methods and is now being forward declared "
"with {} methods",
name, existing_fwd->second, num_methods);
}
auto existing_type = m_types.find(name);
if (existing_type != m_types.end()) {
int existing_count = get_next_method_id(existing_type->second.get());
if (existing_count != num_methods) {
throw_typesystem_error(
"Type {} was defined with {} methods and is now being forward declared with {} methods",
name, existing_count, num_methods);
}
}
m_forward_declared_method_counts[name] = num_methods;
}
/*!
* forward declare, but allow the method count to be too large by up to 3 because jak2 stores
* method counts in v2/v4's like n*4 + 3.
*/
void TypeSystem::forward_declare_type_method_count_multiple_of_4(const std::string& name,
int num_methods) {
auto existing_fwd = m_forward_declared_method_counts.find(name);
if (existing_fwd != m_forward_declared_method_counts.end() &&
existing_fwd->second + 3 < num_methods) {
throw_typesystem_error(
"Type {} was originally forward declared with {} methods and is now being forward declared "
"with {} methods",
name, existing_fwd->second, num_methods);
}
auto existing_type = m_types.find(name);
if (existing_type != m_types.end()) {
int existing_count = get_next_method_id(existing_type->second.get());
if (existing_count + 3 < num_methods) {
throw_typesystem_error(
"Type {} was defined with {} methods and is now being forward declared with {} methods",
name, existing_count, num_methods);
}
}
m_forward_declared_method_counts[name] = num_methods;
}
int TypeSystem::get_type_method_count(const std::string& name) const {
auto result = try_get_type_method_count(name);
if (result) {
return *result;
}
throw_typesystem_error("Tried to find the number of methods on type {}, but it is not defined.",
name);
return -1;
}
std::optional<int> TypeSystem::try_get_type_method_count(const std::string& name) const {
auto type_it = m_types.find(name);
if (type_it != m_types.end()) {
return get_next_method_id(type_it->second.get());
}
auto fwd_it = m_forward_declared_method_counts.find(name);
if (fwd_it != m_forward_declared_method_counts.end()) {
return fwd_it->second;
}
return {};
}
/*!
* Get the runtime type (as a name string) of a TypeSpec. Gets the runtime type of the primary
* type of the TypeSpec.
*/
std::string TypeSystem::get_runtime_type(const TypeSpec& ts) {
return lookup_type(ts)->get_runtime_name();
}
/*!
* Get information about what happens if you dereference an object of given type
*/
DerefInfo TypeSystem::get_deref_info(const TypeSpec& ts) const {
DerefInfo info;
if (!ts.has_single_arg()) {
// not enough info.
info.can_deref = false;
return info;
}
// default to GPR
info.reg = RegClass::GPR_64;
info.mem_deref = true;
if (tc(TypeSpec("float"), ts)) {
info.reg = RegClass::FLOAT;
}
if (ts.base_type() == "inline-array") {
auto result_type = lookup_type(ts.get_single_arg());
auto result_structure_type = dynamic_cast<StructureType*>(result_type);
if (!result_structure_type || result_structure_type->is_dynamic()) {
info.can_deref = false;
return info;
}
// it's an inline array of structures. We can "dereference". But really we don't do a memory
// dereference, we just add stride*idx to the pointer.
info.can_deref = true; // deref operators should work...
info.mem_deref = false; // but don't actually dereference a pointer
info.result_type = ts.get_single_arg(); // what we're an inline-array of
info.sign_extend = false; // not applicable anyway
if (result_type->is_reference()) {
info.stride = align(result_type->get_size_in_memory(),
result_type->get_inline_array_stride_alignment());
} else {
// can't have an inline array of value types!
ASSERT(false);
}
} else if (ts.base_type() == "pointer") {
info.can_deref = true;
info.result_type = ts.get_single_arg();
auto result_type = lookup_type_allow_partial_def(info.result_type);
if (result_type->is_reference()) {
// in memory, an array of pointers
info.stride = POINTER_SIZE;
info.sign_extend = false;
info.load_size = POINTER_SIZE;
} else {
// an array of values, which should be loaded in the correct way to the correct register
info.stride = result_type->get_size_in_memory();
info.sign_extend = result_type->get_load_signed();
info.reg = result_type->get_preferred_reg_class();
info.load_size = result_type->get_load_size();
ASSERT(result_type->get_size_in_memory() == result_type->get_load_size());
}
} else {
info.can_deref = false;
}
return info;
}
/*!
* Create a simple typespec. The type must be defined or forward declared for this to work.
* If you really need a TypeSpec which refers to a non-existent type, just construct your own.
*/
TypeSpec TypeSystem::make_typespec(const std::string& name) const {
if (m_types.find(name) != m_types.end() ||
m_forward_declared_types.find(name) != m_forward_declared_types.end()) {
return TypeSpec(name);
} else {
throw_typesystem_error("Type {} is unknown\n", name);
}
}
bool TypeSystem::fully_defined_type_exists(const std::string& name) const {
return m_types.find(name) != m_types.end();
}
bool TypeSystem::fully_defined_type_exists(const TypeSpec& type) const {
return fully_defined_type_exists(type.base_type());
}
bool TypeSystem::partially_defined_type_exists(const std::string& name) const {
return m_forward_declared_types.find(name) != m_forward_declared_types.end();
}
TypeSpec TypeSystem::make_array_typespec(const std::string& array_type,
const TypeSpec& element_type) const {
return TypeSpec(array_type, {element_type});
}
/*!
* Create a typespec for a function. If the function doesn't return anything, use "none" as the
* return type.
*/
TypeSpec TypeSystem::make_function_typespec(const std::vector<std::string>& arg_types,
const std::string& return_type) const {
auto result = make_typespec("function");
for (auto& x : arg_types) {
result.add_arg(make_typespec(x));
}
result.add_arg(make_typespec(return_type));
return result;
}
/*!
* Create a TypeSpec for a pointer to a type.
*/
TypeSpec TypeSystem::make_pointer_typespec(const std::string& type) const {
return make_pointer_typespec(make_typespec(type));
}
/*!
* Create a TypeSpec for a pointer to a type.
*/
TypeSpec TypeSystem::make_pointer_typespec(const TypeSpec& type) const {
return TypeSpec("pointer", {type});
}
/*!
* Create a TypeSpec for an inline-array of type
*/
TypeSpec TypeSystem::make_inline_array_typespec(const std::string& type) const {
return make_inline_array_typespec(make_typespec(type));
}
/*!
* Create a TypeSpec for an inline-array of type
*/
TypeSpec TypeSystem::make_inline_array_typespec(const TypeSpec& type) const {
return TypeSpec("inline-array", {type});
}
/*!
* Get full type information. Throws if the type doesn't exist. If the given type is redefined after
* a call to lookup_type, the Type* will still be valid, but will point to the old data. Whenever
* possible, don't store a Type* and store a TypeSpec instead. The TypeSpec can then be used with
* lookup_type to find the most up-to-date type information.
*/
Type* TypeSystem::lookup_type(const std::string& name) const {
auto kv = m_types.find(name);
if (kv != m_types.end()) {
return kv->second.get();
}
auto fd = m_forward_declared_types.find(name);
if (fd != m_forward_declared_types.end()) {
throw_typesystem_error("Type {} is not fully defined.\n", name);
// kind of a hack... if the type is forward-declared, look for the parent type and hope for the
// best.
// return lookup_type(fd->second);
} else {
throw_typesystem_error("Type {} is not defined.\n", name);
}
throw std::runtime_error("lookup_type failed");
}
/*!
* Get full type information. Throws if the type doesn't exist. If the given type is redefined after
* a call to lookup_type, the Type* will still be valid, but will point to the old data. Whenever
* possible, don't store a Type* and store a TypeSpec instead. The TypeSpec can then be used with
* lookup_type to find the most up-to-date type information.
*/
Type* TypeSystem::lookup_type(const TypeSpec& ts) const {
return lookup_type(ts.base_type());
}
/*!
* Same as lookup_type, but returns null instead of throwing.
*/
Type* TypeSystem::lookup_type_no_throw(const std::string& name) const {
auto kv = m_types.find(name);
if (kv != m_types.end()) {
return kv->second.get();
}
return nullptr;
}
/*!
* Same as lookup_type, but returns null instead of throwing.
*/
Type* TypeSystem::lookup_type_no_throw(const TypeSpec& ts) const {
return lookup_type_no_throw(ts.base_type());
}
/*!
* Get type info. If the type is not fully defined (ie, we are parsing its deftype now) and its
* forward defined as a basic or structure, just get basic/structure.
*/
Type* TypeSystem::lookup_type_allow_partial_def(const TypeSpec& ts) const {
return lookup_type_allow_partial_def(ts.base_type());
}
/*!
* Get type info. If the type is not fully defined (ie, we are parsing its deftype now) and its
* forward defined as a basic or structure, just get basic/structure.
*/
Type* TypeSystem::lookup_type_allow_partial_def(const std::string& name) const {
// look up fully defined types first:
auto kv = m_types.find(name);
if (kv != m_types.end()) {
return kv->second.get();
}
Type* result = nullptr;
std::string current_name = name;
while (!result) {
auto fwd_dec = m_forward_declared_types.find(current_name);
if (fwd_dec == m_forward_declared_types.end()) {
if (current_name == name) {
throw_typesystem_error("The type '{}' is unknown (2).\n", name);
} else {
throw_typesystem_error("When looking up forward defined type {}, could not find a type {}.",
name, current_name);
}
}
current_name = fwd_dec->second;
auto type_lookup = m_types.find(current_name);
if (type_lookup != m_types.end()) {
result = type_lookup->second.get();
}
}
return result;
}
/*!
* Get load size for a type. Will succeed if one of the two conditions is true:
* - Is a fully defined type.
* - Is partially defined, but structure is in the parent.
* This should be safe to use to load a value from a field.
*/
int TypeSystem::get_load_size_allow_partial_def(const TypeSpec& ts) const {
auto fully_defined_it = m_types.find(ts.base_type());
if (fully_defined_it != m_types.end()) {
return fully_defined_it->second->get_load_size();
}
auto partial_def = lookup_type_allow_partial_def(ts);
if (!tc(TypeSpec("structure"), ts)) {
throw_typesystem_error("Cannot perform a load or store from partially defined type {}",
ts.print());
}
ASSERT(partial_def->get_load_size() == 4);
return partial_def->get_load_size();
}
MethodInfo TypeSystem::override_method(Type* type,
const std::string& method_name,
const std::optional<std::string>& docstring) {
// Lookup the method from the parent type
MethodInfo existing_info;
bool exists = try_lookup_method(type->get_parent(), method_name, &existing_info);
if (!exists) {
throw_typesystem_error("Trying to override a method that has no parent declaration");
}
// use the existing ID.
return type->add_method({existing_info.id,
existing_info.name,
existing_info.type,
type->get_name(),
existing_info.no_virtual,
false,
true,
docstring,
{}});
}
MethodInfo TypeSystem::declare_method(const std::string& type_name,
const std::string& method_name,
const std::optional<std::string>& docstring,
bool no_virtual,
const TypeSpec& ts,
bool override_type) {
return declare_method(lookup_type(make_typespec(type_name)), method_name, docstring, no_virtual,
ts, override_type);
}
/*!
* Add a method, if it doesn't exist. If the method already exists (possibly in a parent), checks to
* see if this is an identical definition. If not, it's an error, and if so, nothing happens.
* Returns the info of either the existing or newly created method.
*
* This is not used to override methods, but instead to create truly new methods. The one exception
* is overriding the "new" method - the TypeSystem will track that because overridden new methods
* may have different arguments.
*/
MethodInfo TypeSystem::declare_method(Type* type,
const std::string& method_name,
const std::optional<std::string>& docstring,
bool no_virtual,
const TypeSpec& ts,
bool override_type) {
if (method_name == "new") {
if (override_type) {
throw_typesystem_error("Cannot use :replace option with a new method.");
}
return add_new_method(type, ts, docstring);
}
// look up the method
MethodInfo existing_info;
bool got_existing = try_lookup_method(type, method_name, &existing_info);
if (override_type) {
if (!got_existing) {
if (try_lookup_method(type->get_parent(), method_name, &existing_info)) {
} else {
throw_typesystem_error(
"Cannot use :replace on method {} of {} because this method was not previously "
"declared in a parent.",
method_name, type->get_name());
}
}
// use the existing ID.
return type->add_method({existing_info.id,
method_name,
ts,
type->get_name(),
no_virtual,
true,
false,
docstring,
{}});
} else {
if (got_existing) {
// make sure we aren't changing anything.
if (!existing_info.type.is_compatible_child_method(ts, type->get_name())) {
throw_typesystem_error(
"The method {} of type {} was originally declared as {}, but has been "
"redeclared as {}. Originally declared in {}\n",
method_name, type->get_name(), existing_info.type.print(), ts.print(),
existing_info.defined_in_type);
}
if ((existing_info.no_virtual || no_virtual) &&
existing_info.defined_in_type != type->get_name()) {
throw_typesystem_error(
"Cannot define method {} in type {} when it was defined as no_virtual in parent type "
"{}",
method_name, type->get_name(), existing_info.defined_in_type);
}
if (no_virtual != existing_info.no_virtual) {
throw_typesystem_error(
"The method {} of type {} was originally declared with no_virtual = {}, but has been "
"redeclared as {}",
method_name, type->get_name(), existing_info.no_virtual, no_virtual);
}
return existing_info;
} else {
// add a new method!
return type->add_method({get_next_method_id(type),
method_name,
ts,
type->get_name(),
no_virtual,
false,
false,
docstring,
{}});
}
}
}
/*!
* Adds a new method that is overlayed on top of a different, existing method.
* This should be used basically never (happens once in Jak 1).
*/
MethodInfo TypeSystem::overlay_method(Type* type,
const std::string& method_name,
const std::string& method_overlay_name,
const std::optional<std::string>& docstring,
const TypeSpec& ts) {
// look up the method
MethodInfo existing_info;
bool got_existing = try_lookup_method(type, method_overlay_name, &existing_info);
if (!got_existing) {
if (try_lookup_method(type->get_parent(), method_overlay_name, &existing_info)) {
} else {
throw_typesystem_error(
"Cannot use :overlay-at on method {} of {} because this method was not previously "
"declared in a parent.",
method_overlay_name, type->get_name());
}
}
// use the existing ID.
return type->add_method({existing_info.id, method_name, ts, type->get_name(), false, true, false,
docstring, std::make_optional(method_overlay_name)});
}
MethodInfo TypeSystem::define_method(const std::string& type_name,
const std::string& method_name,
const TypeSpec& ts,
const std::optional<std::string>& docstring) {
return define_method(lookup_type(make_typespec(type_name)), method_name, ts, docstring);
}
/*!
* Add a method, if it doesn't exist. If the method already exists (possibly in a parent), checks to
* see if this is an identical definition. If not, it's an error, and if so, nothing happens.
* Returns the info of either the existing or newly created method.
*
* This is not used to override methods, but instead to create truly new methods. The one exception
* is overriding the "new" method - the TypeSystem will track that because overridden new methods
* may have different arguments.
*/
MethodInfo TypeSystem::define_method(Type* type,
const std::string& method_name,
const TypeSpec& ts,
const std::optional<std::string>& docstring) {
if (method_name == "new") {
return add_new_method(type, ts, docstring);
}
// look up the method
MethodInfo existing_info;
bool got_existing = try_lookup_method(type, method_name, &existing_info);
if (got_existing) {
// Update the docstring
existing_info.docstring = docstring;
int bad_arg_idx = -99;
// make sure we aren't changing anything that isn't the return type.
if (!existing_info.type.is_compatible_child_method(ts, type->get_name(), &bad_arg_idx) &&
bad_arg_idx != (int)ts.arg_count() - 1) {
throw_typesystem_error(
"The method {} of type {} was originally defined as {}, but has been "
"redefined as {} (see argument index {})\n",
method_name, type->get_name(), existing_info.type.print(), ts.print(), bad_arg_idx);
} else if (bad_arg_idx == (int)ts.arg_count() - 1 &&
!tc(existing_info.type.last_arg(), ts.last_arg())) {
throw_typesystem_error(
"The method {} of type {} was originally defined as returning {}, but has been redefined "
"and returns {}\n",
method_name, type->get_name(), existing_info.type.last_arg().print(),
ts.last_arg().print());
}
return existing_info;
} else {
throw_typesystem_error("Cannot add method {} to type {} because it was not declared.\n",
method_name, type->get_name());
}
}
/*!
* Special case to add a new method, as new methods can specialize the arguments.
* If it turns out that other child methods can specialize arguments (seems like a bad idea), this
* may be generalized.
*/
MethodInfo TypeSystem::add_new_method(Type* type,
const TypeSpec& ts,
const std::optional<std::string>& docstring) {
MethodInfo existing;
if (type->get_my_new_method(&existing)) {
// it exists!
if (!existing.type.is_compatible_child_method(ts, type->get_name())) {
throw_typesystem_error(
"Cannot add new method. Type does not match declaration. The new method of {} was "
"originally defined as {}, but has been redefined as {}\n",
type->get_name(), existing.type.print(), ts.print());
}
return existing;
} else {
return type->add_new_method(
{0, "new", ts, type->get_name(), false, false, false, docstring, {}});
}
}
/*!
* Lookup information on a method. Error if it can't be found. Will check parent types if the
* given type doesn't specialize the method.
*/
MethodInfo TypeSystem::lookup_method(const std::string& type_name,
const std::string& method_name) const {
if (method_name == "new") {
return lookup_new_method(type_name);
}
MethodInfo info;
// first lookup the type
auto* type = lookup_type(type_name);
auto* iter_type = type;
// look up the method
while (true) {
if (iter_type->get_my_method(method_name, &info)) {
return info;
}
if (iter_type->has_parent()) {
iter_type = lookup_type(iter_type->get_parent());
} else {
// couldn't find method.
break;
}
}
throw_typesystem_error("The method {} of type {} could not be found.\n", method_name, type_name);
}
bool TypeSystem::try_lookup_method(const std::string& type_name,
const std::string& method_name,
MethodInfo* info) const {
auto kv = m_types.find(type_name);
if (kv == m_types.end()) {
// try to look up a forward declared type.
auto fwd_dec_type = lookup_type_allow_partial_def(type_name);
if (tc(TypeSpec("basic"), TypeSpec(fwd_dec_type->get_name()))) {
// only allow this for basics. It technically should be safe for structures as well.
return try_lookup_method(fwd_dec_type, method_name, info);
}
return false;
}
return try_lookup_method(kv->second.get(), method_name, info);
}
/*!
* Like lookup_method, but won't throw or print an error when things go wrong.
*/
bool TypeSystem::try_lookup_method(const std::string& type_name,
int method_id,
MethodInfo* info) const {
auto kv = m_types.find(type_name);
if (kv == m_types.end()) {
return false;
}
auto* iter_type = kv->second.get();
// look up the method
while (true) {
if (method_id == GOAL_NEW_METHOD) {
if (iter_type->get_my_new_method(info)) {
return true;
}
} else {
if (iter_type->get_my_method(method_id, info)) {
return true;
}
}
if (iter_type->has_parent()) {
iter_type = lookup_type(iter_type->get_parent());
} else {
// couldn't find method.
break;
}
}
return false;
}
bool TypeSystem::try_lookup_method(const Type* type,
const std::string& method_name,
MethodInfo* info) const {
// look up the method
while (true) {
if (method_name == "new") {
if (type->get_my_new_method(info)) {
return true;
}
} else {
if (type->get_my_method(method_name, info)) {
return true;
}
}
if (type->has_parent()) {
type = lookup_type(type->get_parent());
} else {
// couldn't find method.
break;
}
}
return false;
}
/*!
* Lookup information on a method by ID number. Error if it can't be found. Will check parent types
* if the given type doesn't specialize the method.
*/
MethodInfo TypeSystem::lookup_method(const std::string& type_name, int method_id) const {
if (method_id == GOAL_NEW_METHOD) {
return lookup_new_method(type_name);
}
MethodInfo info;
// first lookup the type
auto* type = lookup_type(type_name);
auto* iter_type = type;
// look up the method
while (true) {
if (iter_type->get_my_method(method_id, &info)) {
return info;
}
if (iter_type->has_parent()) {
iter_type = lookup_type(iter_type->get_parent());
} else {
// couldn't find method.
break;
}
}
throw_typesystem_error("The method with id {} of type {} could not be found.", method_id,
type_name);
}
/*!
* Lookup information on a new method and get the most specialized version.
*/
MethodInfo TypeSystem::lookup_new_method(const std::string& type_name) const {
MethodInfo info;
// first lookup the type
auto* type = lookup_type(type_name);
auto* iter_type = type;
// look up the method
while (true) {
if (iter_type->get_my_new_method(&info)) {
return info;
}
if (iter_type->has_parent()) {
iter_type = lookup_type(iter_type->get_parent());
} else {
// couldn't find method.
break;
}
}
throw_typesystem_error("The new method of type {} could not be found.\n", type_name);
}
/*!
* Makes sure a method exists at the given ID for the given type, possibly defined in a parent.
*/
void TypeSystem::assert_method_id(const std::string& type_name,
const std::string& method_name,
int id) {
auto info = lookup_method(type_name, method_name);
if (info.id != id) {
throw_typesystem_error(
"Method ID assertion failed: type {}, method {} id was {}, expected {}\n", type_name,
method_name, info.id, id);
}
}
/*!
* Lookup detailed information about a field of a type by name, including type, offset,
* and how to access it.
*/
FieldLookupInfo TypeSystem::lookup_field_info(const std::string& type_name,
const std::string& field_name) const {
FieldLookupInfo info;
info.field = lookup_field(type_name, field_name);
// get array size, for bounds checking (when possible)
if (info.field.is_array() && !info.field.is_dynamic()) {
info.array_size = info.field.array_size();
}
auto base_type = lookup_type_allow_partial_def(info.field.type());
if (base_type->is_reference()) {
if (info.field.is_inline()) {
if (info.field.is_array()) {
// inline array of reference types
info.needs_deref = false;
info.type = make_inline_array_typespec(info.field.type());
} else {
// inline object
info.needs_deref = false;
info.type = info.field.type();
}
} else {
if (info.field.is_array()) {
info.needs_deref = false;
info.type = make_pointer_typespec(info.field.type());
} else {
info.needs_deref = true;
info.type = info.field.type();
}
}
} else {
if (info.field.is_array()) {
info.needs_deref = false;
info.type = make_pointer_typespec(info.field.type());
} else {
// not array
info.needs_deref = true;
info.type = info.field.type();
}
}
return info;
}
/*!
* Make sure a field is located at the specified offset.
*/
void TypeSystem::assert_field_offset(const std::string& type_name,
const std::string& field_name,
int offset) {
Field field = lookup_field(type_name, field_name);
if (field.offset() != offset) {
throw_typesystem_error("assert_field_offset({}, {}, {}) failed - got {}\n", type_name,
field_name, offset);
throw std::runtime_error("assert_field_offset failed");
}
}
/*!
* Add a field to a type. If offset_override is -1 (the default), will place it automatically.
*/
int TypeSystem::add_field_to_type(StructureType* type,
const std::string& field_name,
const TypeSpec& field_type,
bool is_inline,
bool is_dynamic,
int array_size,
int offset_override,
bool skip_in_static_decomp,
double score,
const std::optional<TypeSpec> decomp_as_ts) {
if (type->lookup_field(field_name, nullptr)) {
throw_typesystem_error("Type {} already has a field named {}\n", type->get_name(), field_name);
}
// first, construct the field
Field field(field_name, field_type);
if (is_inline) {
field.set_inline();
}
if (is_dynamic) {
field.set_dynamic();
type->set_dynamic();
}
if (array_size != -1) {
field.set_array(array_size);
}
int offset = offset_override;
int field_alignment = get_alignment_in_type(field);
if (offset == -1) {
// we need to compute the offset ourself!
offset = align(type->get_size_in_memory(), field_alignment);
} else {
int aligned_offset = align(offset, field_alignment);
field.mark_as_user_placed();
if (offset != aligned_offset) {
throw_typesystem_error(
"Tried to place field {} at {}, but it is not aligned correctly, requires {}\n",
field_name, offset, field_alignment);
}
}
field.set_offset(offset);
field.set_alignment(field_alignment);
if (skip_in_static_decomp) {
field.set_skip_in_static_decomp();
}
field.set_field_score(score);
if (decomp_as_ts) {
field.set_decomp_as_ts(*decomp_as_ts);
}
int after_field = offset + get_size_in_type(field);
if (type->get_size_in_memory() < after_field) {
type->override_size_in_memory(after_field);
}
type->add_field(field, type->get_size_in_memory());
return offset;
}
/*!
* Add types which are built-in to GOAL.
*/
void TypeSystem::add_builtin_types(GameVersion version) {
// some of the basic types have confusing circular dependencies, so this is done manually.
// there are no inlined things so its ok to do some things out of order because the actual size
// doesn't really matter.
// OBJECT
auto obj_type = add_type(
"object", std::make_unique<ValueType>("object", "object", false, 4, true, RegClass::GPR_64));
auto structure_type = add_builtin_structure("object", "structure");
auto basic_type = add_builtin_basic("structure", "basic");
StructureType* symbol_type;
switch (version) {
case GameVersion::Jak1:
symbol_type = add_builtin_basic("basic", "symbol");
break;
case GameVersion::Jak2:
case GameVersion::Jak3:
symbol_type = add_builtin_structure("object", "symbol", true);
symbol_type->override_offset(1);
break;
default:
ASSERT(false);
}
auto type_type = add_builtin_basic("basic", "type");
auto string_type = add_builtin_basic("basic", "string");
string_type->set_final(); // no virtual calls used on string.
auto function_type = add_builtin_basic("basic", "function");
auto vu_function_type = add_builtin_structure("structure", "vu-function");
auto link_block_type = add_builtin_basic("basic", "link-block");
auto kheap_type = add_builtin_structure("structure", "kheap");
auto array_type = add_builtin_basic("basic", "array");
auto pair_type = add_builtin_structure("object", "pair", true);
auto connectable_type = add_builtin_structure("structure", "connectable");
auto file_stream_type = add_builtin_basic("basic", "file-stream");
add_builtin_value_type("object", "pointer", 4);
auto inline_array_type = add_builtin_value_type("object", "inline-array", 4);
inline_array_type->set_runtime_type("pointer");
add_builtin_value_type("object", "number", 8); // sign extend?
add_builtin_value_type("number", "float", 4, false, false, RegClass::FLOAT);
add_builtin_value_type("number", "integer", 8, false, false); // sign extend?
add_builtin_value_type("integer", "binteger", 8, true, false); // sign extend?
add_builtin_value_type("integer", "sinteger", 8, false, true);
add_builtin_value_type("sinteger", "int8", 1, false, true);
add_builtin_value_type("sinteger", "int16", 2, false, true);
add_builtin_value_type("sinteger", "int32", 4, false, true);
add_builtin_value_type("sinteger", "int64", 8, false, true);
add_builtin_value_type("sinteger", "int128", 16, false, true, RegClass::INT_128);
add_builtin_value_type("integer", "uinteger", 8);
add_builtin_value_type("uinteger", "uint8", 1);
add_builtin_value_type("uinteger", "uint16", 2);
add_builtin_value_type("uinteger", "uint32", 4);
add_builtin_value_type("uinteger", "uint64", 8);
add_builtin_value_type("uinteger", "uint128", 16, false, false, RegClass::INT_128);
// add special units types.
add_builtin_value_type("float", "meters", 4, false, false, RegClass::FLOAT)
->set_runtime_type("float");
add_builtin_value_type("float", "degrees", 4, false, false, RegClass::FLOAT)
->set_runtime_type("float");
add_builtin_value_type("int64", "seconds", 8, false, true)->set_runtime_type("int64");
auto int_type = add_builtin_value_type("integer", "int", 8, false, true);
int_type->disallow_in_runtime();
auto uint_type = add_builtin_value_type("uinteger", "uint", 8, false, false);
uint_type->disallow_in_runtime();
// Methods and Fields
forward_declare_type_as("memory-usage-block", "basic");
// OBJECT
declare_method(obj_type, "new", {}, false,
make_function_typespec({"symbol", "type", "int"}, "_type_"), false);
declare_method(obj_type, "delete", {}, false, make_function_typespec({"_type_"}, "none"), false);
declare_method(obj_type, "print", {}, false, make_function_typespec({"_type_"}, "_type_"), false);
declare_method(obj_type, "inspect", {}, false, make_function_typespec({"_type_"}, "_type_"),
false);
declare_method(obj_type, "length", {}, false, make_function_typespec({"_type_"}, "int"),
false); // todo - this integer type?
declare_method(obj_type, "asize-of", {}, false, make_function_typespec({"_type_"}, "int"), false);
declare_method(obj_type, "copy", {}, false,
make_function_typespec({"_type_", "symbol"}, "_type_"), false);
declare_method(obj_type, "relocate", {}, false,
make_function_typespec({"_type_", "int"}, "_type_"), false);
declare_method(obj_type, "mem-usage", {}, false,
make_function_typespec({"_type_", "memory-usage-block", "int"}, "_type_"), false);
// STRUCTURE
// structure new doesn't support dynamic sizing, which is kinda weird - it grabs the size from
// the type. Dynamic structures use new-dynamic-structure, which is used exactly once ever.
declare_method(structure_type, "new", {}, false,
make_function_typespec({"symbol", "type"}, "_type_"), false);
// structure_type is a field-less StructureType, so we have to do this to match the runtime.
// structure_type->override_size_in_memory(4);
// BASIC
// we intentionally don't inherit from structure because structure's size is weird.
add_field_to_type(basic_type, "type", make_typespec("type"));
// the default new basic doesn't support dynamic sizing. anything dynamic will override this
// and then call (method object new) to do the dynamically-sized allocation.
declare_method(basic_type, "new", {}, false, make_function_typespec({"symbol", "type"}, "_type_"),
false);
// SYMBOL
if (version == GameVersion::Jak1) {
builtin_structure_inherit(symbol_type);
}
add_field_to_type(symbol_type, "value", make_typespec("object"));
// a new method which returns type none means new is illegal.
declare_method(symbol_type, "new", {}, false, make_function_typespec({}, "none"), false);
// TYPE
builtin_structure_inherit(type_type);
declare_method(type_type, "new", {}, false,
make_function_typespec({"symbol", "type", "int"}, "_type_"), false);
add_field_to_type(type_type, "symbol", make_typespec("symbol"));
add_field_to_type(type_type, "parent", make_typespec("type"));
add_field_to_type(type_type, "size", make_typespec("uint16")); // actually u16
add_field_to_type(type_type, "psize",
make_typespec("uint16")); // todo, u16 or s16. what really is this?
add_field_to_type(type_type, "heap-base", make_typespec("uint16")); // todo
add_field_to_type(type_type, "allocated-length", make_typespec("uint16")); // todo
add_field_to_type(type_type, "method-table", make_typespec("function"), false, true);
// STRING
builtin_structure_inherit(string_type);
add_field_to_type(string_type, "allocated-length", make_typespec("int32")); // todo integer type
add_field_to_type(string_type, "data", make_typespec("uint8"), false, true); // todo integer type
// string is never deftype'd for the decompiler, so we need to manually give the constructor
// type here.
declare_method(string_type, "new", {}, false,
make_function_typespec({"symbol", "type", "int", "string"}, "_type_"), false);
// FUNCTION
builtin_structure_inherit(function_type);
// ???
// VU FUNCTION
// don't inherit
add_field_to_type(vu_function_type, "length", make_typespec("int32")); // todo integer type
add_field_to_type(vu_function_type, "origin", make_typespec("int32")); // todo sign extend?
add_field_to_type(vu_function_type, "qlength", make_typespec("int32")); // todo integer type
add_field_to_type(vu_function_type, "data", make_typespec("uint8"), false, true, -1, -1, true);
// link block
builtin_structure_inherit(link_block_type);
add_field_to_type(link_block_type, "allocated-length",
make_typespec("int32")); // todo integer type
add_field_to_type(link_block_type, "version", make_typespec("int32")); // todo integer type
// there's probably some dynamically sized stuff after this...
// kheap
add_field_to_type(kheap_type, "base", make_typespec("pointer"));
add_field_to_type(kheap_type, "top", make_typespec("pointer"));
add_field_to_type(kheap_type, "current", make_typespec("pointer"));
add_field_to_type(kheap_type, "top-base", make_typespec("pointer"));
// todo
builtin_structure_inherit(array_type);
declare_method(array_type, "new", {}, false,
make_function_typespec({"symbol", "type", "type", "int"}, "_type_"), false);
// array has: number, number, type
add_field_to_type(array_type, "length", make_typespec("int32"));
add_field_to_type(array_type, "allocated-length", make_typespec("int32"));
add_field_to_type(array_type, "content-type", make_typespec("type"));
add_field_to_type(array_type, "data", make_typespec("uint8"), false, true);
// pair
pair_type->override_offset(2);
declare_method(pair_type, "new", {}, false,
make_function_typespec({"symbol", "type", "object", "object"}, "_type_"), false);
add_field_to_type(pair_type, "car", make_typespec("object"));
add_field_to_type(pair_type, "cdr", make_typespec("object"));
// this type is very strange, as the compiler knows about it in gkernel-h, yet it is
// defined inside of connect.
add_field_to_type(connectable_type, "next0", make_typespec("connectable"));
add_field_to_type(connectable_type, "prev0", make_typespec("connectable"));
add_field_to_type(connectable_type, "next1", make_typespec("connectable"));
add_field_to_type(connectable_type, "prev1", make_typespec("connectable"));
// todo
builtin_structure_inherit(file_stream_type);
add_field_to_type(file_stream_type, "flags", make_typespec("uint32"));
add_field_to_type(file_stream_type, "mode", make_typespec("symbol"));
add_field_to_type(file_stream_type, "name", make_typespec("string"));
add_field_to_type(file_stream_type, "file", make_typespec("uint32"));
declare_method(file_stream_type, "new", {}, false,
make_function_typespec({"symbol", "type", "string", "symbol"}, "_type_"), false);
}
/*!
* Debugging function to print out all types, and their methods and fields.
*/
std::string TypeSystem::print_all_type_information() const {
std::string result;
for (auto& kv : m_types) {
result += kv.second->print() + "\n";
}
return result;
}
/*!
* Get the next free method ID of a type.
*/
int TypeSystem::get_next_method_id(const Type* type) const {
MethodInfo info;
while (true) {
if (type->get_my_last_method(&info)) {
return info.id + 1;
}
if (type->has_parent()) {
type = lookup_type(type->get_parent());
} else {
// nobody has defined any method yet. New is special and doesn't use this, so we return
// one after new.
return 1;
}
}
}
/*!
* Lookup a field of a type by name
*/
Field TypeSystem::lookup_field(const std::string& type_name, const std::string& field_name) const {
auto type = get_type_of_type<StructureType>(type_name);
Field field;
if (!type->lookup_field(field_name, &field)) {
throw_typesystem_error("Type {} has no field named {}\n", type_name, field_name);
}
return field;
}
/*!
* Get the minimum required aligment of a field.
*/
int TypeSystem::get_alignment_in_type(const Field& field) {
auto field_type = lookup_type_allow_partial_def(field.type());
if (field.is_inline()) {
if (field.is_array()) {
// TODO - is this actually correct? or do we use in_memory for the first element and
// inline_array for the ones that follow?
return field_type->get_inline_array_start_alignment();
} else {
// it is an inlined field, so return the alignment in memory
// TODO - for inline, but not inline array, do we use structure alignment always?
return field_type->get_inline_array_start_alignment();
}
}
if (!field_type->is_reference()) {
// it is a value type, so it's stored in full:
return field_type->get_in_memory_alignment();
}
// otherwise it's a reference
return POINTER_SIZE;
}
namespace {
bool allow_inline(const Type* type) {
auto name = type->get_name();
return name != "basic" && name != "structure";
}
} // namespace
/*!
* Get the size of a field in a type. The array sizes should be consistent with get_deref_info's
* stride.
*/
int TypeSystem::get_size_in_type(const Field& field) const {
if (field.is_dynamic()) {
return 0;
}
auto field_type = lookup_type_allow_partial_def(field.type());
if (field.is_array()) {
if (field.is_inline()) {
if (!fully_defined_type_exists(field.type())) {
throw_typesystem_error("Cannot use the forward-declared type {} in an inline array.\n",
field.type().print());
}
if (!allow_inline(field_type)) {
throw_typesystem_error(
"Attempted to use `{}` inline, this probably isn't what you wanted.\n",
field_type->get_name());
}
// TODO - crashes LSP
ASSERT(field_type->is_reference());
return field.array_size() * align(field_type->get_size_in_memory(),
field_type->get_inline_array_stride_alignment());
} else {
if (field_type->is_reference()) {
return field.array_size() * POINTER_SIZE;
} else {
return field.array_size() *
align(field_type->get_size_in_memory(), field_type->get_in_memory_alignment());
}
}
} else {
// not an array
if (field.is_inline()) {
if (!fully_defined_type_exists(field.type())) {
throw_typesystem_error("Cannot use the forward-declared type {} inline.\n",
field.type().print());
}
if (!allow_inline(field_type)) {
throw_typesystem_error(
"Attempted to use `{}` inline, this probably isn't what you wanted. Type "
"may not be defined fully.\n",
field_type->get_name());
}
ASSERT(field_type->is_reference());
// return align(field_type->get_size_in_memory(), field_type->get_in_memory_alignment());
// looking at dead-pool-heap we tightly pack in this case
return field_type->get_size_in_memory();
} else {
if (field_type->is_reference()) {
return POINTER_SIZE;
} else {
return align(field_type->get_size_in_memory(), field_type->get_in_memory_alignment());
}
}
}
}
std::vector<std::string> TypeSystem::get_all_type_names() {
std::vector<std::string> results = {};
for (const auto& [type_name, type_info] : m_types) {
results.push_back(type_name);
}
return results;
}
std::vector<std::string> TypeSystem::search_types_by_parent_type(
const std::string& parent_type,
const std::optional<std::vector<std::string>>& existing_matches) {
std::vector<std::string> results = {};
// If we've been given a list of already matched types, narrow it down from there, otherwise
// iterate through the entire map
if (existing_matches) {
for (const auto& type_name : existing_matches.value()) {
if (typecheck_base_types(parent_type, type_name, false)) {
results.push_back(type_name);
}
}
} else {
for (const auto& [type_name, type_info] : m_types) {
// Only NullType's have no parent
if (!type_info->has_parent()) {
continue;
}
if (typecheck_base_types(parent_type, type_name, false)) {
results.push_back(type_name);
}
}
}
return results;
}
std::vector<std::string> TypeSystem::search_types_by_parent_type_strict(
const std::string& parent_type) {
std::vector<std::string> results = {};
for (const auto& [type_name, type_info] : m_types) {
// Only NullType's have no parent
if (!type_info->has_parent()) {
continue;
}
if (type_info->get_parent() == parent_type) {
results.push_back(type_name);
}
}
return results;
}
std::vector<std::string> TypeSystem::search_types_by_minimum_method_id(
const int minimum_method_id,
const std::optional<std::vector<std::string>>& existing_matches) {
std::vector<std::string> results = {};
// If we've been given a list of already matched types, narrow it down from there, otherwise
// iterate through the entire map
if (existing_matches) {
for (const auto& type_name : existing_matches.value()) {
if (get_type_method_count(type_name) - 1 >= minimum_method_id) {
results.push_back(type_name);
}
}
} else {
for (const auto& [type_name, type_info] : m_types) {
if (get_type_method_count(type_name) - 1 >= minimum_method_id) {
results.push_back(type_name);
}
}
}
return results;
}
std::vector<std::string> TypeSystem::search_types_by_size(
const int min_size,
const std::optional<int> max_size,
const std::optional<std::vector<std::string>>& existing_matches) {
std::vector<std::string> results = {};
// If we've been given a list of already matched types, narrow it down from there, otherwise
// iterate through the entire map
if (existing_matches) {
for (const auto& type_name : existing_matches.value()) {
const auto size_of_type = m_types[type_name]->get_size_in_memory();
if (max_size && size_of_type <= max_size && size_of_type >= min_size) {
results.push_back(type_name);
} else if (!max_size && size_of_type == min_size) {
results.push_back(type_name);
}
}
} else {
for (const auto& [type_name, type_info] : m_types) {
if (dynamic_cast<NullType*>(type_info.get())) {
continue;
}
const auto size_of_type = m_types[type_name]->get_size_in_memory();
if (max_size && size_of_type <= max_size && size_of_type >= min_size) {
results.push_back(type_name);
} else if (!max_size && size_of_type == min_size) {
results.push_back(type_name);
}
}
}
return results;
}
std::vector<std::string> TypeSystem::search_types_by_fields(
const std::vector<TypeSearchFieldInput>& search_fields,
const std::optional<std::vector<std::string>>& existing_matches) {
// TODO - maybe support partial matches eventually
std::vector<std::string> results = {};
if (existing_matches) {
for (const auto& type_name : existing_matches.value()) {
// For each type, look at it's fields
if (dynamic_cast<StructureType*>(m_types[type_name].get()) != nullptr) {
bool type_valid = true;
auto struct_type = dynamic_cast<StructureType*>(m_types[type_name].get());
for (const auto& req_field : search_fields) {
bool field_valid = false;
// iterate through the type's fields until one is found with the right offset
// once found, check the underlying type name, if it doesn't match it's invalid
// if we don't find one with that offset, it's also invalid
for (const auto& type_field : struct_type->fields()) {
if (type_field.offset() == req_field.field_offset &&
type_field.type().base_type() == req_field.field_type_name) {
field_valid = true;
break;
}
}
if (!field_valid) {
type_valid = false;
break;
}
}
if (type_valid) {
results.push_back(type_name);
}
}
}
} else {
for (const auto& [type_name, type_info] : m_types) {
// For each type, look at it's fields
if (dynamic_cast<StructureType*>(type_info.get()) != nullptr) {
bool type_valid = true;
auto struct_type = dynamic_cast<StructureType*>(type_info.get());
for (const auto& req_field : search_fields) {
bool field_valid = false;
// iterate through the type's fields until one is found with the right offset
// once found, check the underlying type name, if it doesn't match it's invalid
// if we don't find one with that offset, it's also invalid
for (const auto& type_field : struct_type->fields()) {
if (type_field.offset() == req_field.field_offset &&
type_field.type().base_type() == req_field.field_type_name) {
field_valid = true;
break;
}
}
if (!field_valid) {
type_valid = false;
break;
}
}
if (type_valid) {
results.push_back(type_name);
}
}
}
}
return results;
}
/*!
* Add a simple structure type - don't use this outside of add_builtin_types as it forces you to do
* things in the wrong order.
*/
StructureType* TypeSystem::add_builtin_structure(const std::string& parent,
const std::string& type_name,
bool boxed) {
add_type(type_name, std::make_unique<StructureType>(parent, type_name, boxed, false, false, 0));
return get_type_of_type<StructureType>(type_name);
}
/*!
* Add a simple basic type - don't use this outside of add_builtin_types as it forces you to do
* things in the wrong order.
*/
BasicType* TypeSystem::add_builtin_basic(const std::string& parent, const std::string& type_name) {
add_type(type_name, std::make_unique<BasicType>(parent, type_name, false, 0));
return get_type_of_type<BasicType>(type_name);
}
/*!
* Add a simple value type - don't use this outside of add_builtin_types as it forces you to do
* things in the wrong order.
*/
ValueType* TypeSystem::add_builtin_value_type(const std::string& parent,
const std::string& type_name,
int size,
bool boxed,
bool sign_extend,
RegClass reg) {
add_type(type_name,
std::make_unique<ValueType>(parent, type_name, boxed, size, sign_extend, reg));
return get_type_of_type<ValueType>(type_name);
}
/*!
* Helper for inheritance of structure types when setting up builtin types.
*/
void TypeSystem::builtin_structure_inherit(StructureType* st) {
st->inherit(get_type_of_type<StructureType>(st->get_parent()));
}
bool TypeSystem::tc(const TypeSpec& less_specific, const TypeSpec& more_specific) const {
return typecheck_and_throw(less_specific, more_specific, "", false, false);
}
/*!
* Main compile-time type check!
* @param expected - the expected type
* @param actual - the actual type (can be more specific)
* @param error_source_name - optional, can provide a name for where the error comes from
* @param print_on_error - print a message explaining the type error, if there is one
* @param throw_on_error - throw a std::runtime_error on failure if set.
* @return if the type check passes
*/
bool TypeSystem::typecheck_and_throw(const TypeSpec& expected,
const TypeSpec& actual,
const std::string& error_source_name,
bool print_on_error,
bool throw_on_error,
bool allow_type_alias) const {
bool success = true;
// first, typecheck the base types:
if (!typecheck_base_types(expected.base_type(), actual.base_type(), allow_type_alias)) {
success = false;
}
// next argument checks:
if (expected.arg_count() == actual.arg_count()) {
for (size_t i = 0; i < expected.arg_count(); i++) {
// don't print/throw because the error would be confusing. Better to fail only the
// outer most check and print a single error message.
if (!tc(expected.get_arg(i), actual.get_arg(i))) {
success = false;
break;
}
}
} else {
// different sizes of arguments.
if (expected.arg_count() == 0) {
// we expect zero arguments, but got some. The actual type is more specific, so this is fine.
} else {
// different sizes, and we expected arguments. No good!
success = false;
}
}
// next, tag checks. It's fine to throw away tags, but the child must match all parent tags
for (auto& tag : expected.tags()) {
if (tag.name == "behavior") {
auto got = actual.try_get_tag(tag.name);
if (!got) {
success = false;
} else {
if (!tc(tag.value, *got)) {
success = false;
}
}
} else {
throw_typesystem_error("Unknown tag {}", tag.name);
}
}
if (!success) {
if (print_on_error) {
if (error_source_name.empty()) {
lg::print("[TypeSystem] Got type \"{}\" when expecting \"{}\"\n", actual.print(),
expected.print());
} else {
lg::print("[TypeSystem] For {}, got type \"{}\" when expecting \"{}\"\n", error_source_name,
actual.print(), expected.print());
}
}
if (throw_on_error) {
throw std::runtime_error("typecheck failed");
}
}
return success;
}
/*!
* Is actual of type expected? For base types.
*/
bool TypeSystem::typecheck_base_types(const std::string& input_expected,
const std::string& input_actual,
bool allow_alias) const {
std::string expected = input_expected;
std::string actual = input_actual;
// the unit types aren't picky.
if (expected == "meters") {
expected = "float";
}
if (expected == "seconds") {
expected = "time-frame";
}
if (actual == "seconds") {
actual = "time-frame";
}
if (expected == "degrees") {
expected = "float";
}
// the decompiler prefers no aliasing so it can detect casts properly
if (allow_alias) {
if (expected == "time-frame") {
expected = "int";
}
if (actual == "time-frame") {
actual = "int";
}
}
// just to make sure it exists.
lookup_type_allow_partial_def(expected);
if (expected == actual || expected == lookup_type_allow_partial_def(actual)->get_name()) {
lookup_type_allow_partial_def(actual); // make sure it exists
return true;
}
std::string actual_name = actual;
auto actual_type = lookup_type_allow_partial_def(actual_name);
while (actual_type->has_parent()) {
actual_name = actual_type->get_parent();
actual_type = lookup_type_allow_partial_def(actual_name);
if (expected == actual_name) {
return true;
}
}
return false;
}
EnumType* TypeSystem::try_enum_lookup(const std::string& type_name) const {
auto it = m_types.find(type_name);
if (it != m_types.end()) {
return dynamic_cast<EnumType*>(it->second.get());
}
return nullptr;
}
EnumType* TypeSystem::try_enum_lookup(const TypeSpec& type) const {
return try_enum_lookup(type.base_type());
}
/*!
* Get a path from type to object.
*/
std::vector<std::string> TypeSystem::get_path_up_tree(const std::string& type) const {
auto parent = lookup_type_allow_partial_def(type)->get_parent();
std::vector<std::string> path = {type};
path.push_back(parent);
auto parent_type = lookup_type_allow_partial_def(parent);
while (parent_type->has_parent()) {
parent = parent_type->get_parent();
parent_type = lookup_type_allow_partial_def(parent);
path.push_back(parent);
}
return path;
}
/*!
* Lowest common ancestor of two base types.
*/
std::string TypeSystem::lca_base(const std::string& a, const std::string& b) const {
if (a == b) {
return a;
}
auto a_up = get_path_up_tree(a);
auto b_up = get_path_up_tree(b);
int ai = a_up.size() - 1;
int bi = b_up.size() - 1;
std::string* result = nullptr;
while (ai >= 0 && bi >= 0) {
if (a_up.at(ai) == b_up.at(bi)) {
result = &a_up.at(ai);
} else {
break;
}
ai--;
bi--;
}
ASSERT(result);
return *result;
}
/*!
* Lowest common ancestor of two typespecs. Will recursively apply to arguments, if compatible.
* Otherwise arguments are stripped off.
* In a situation like lca("(a b)", "(c d)"), the result will be
* (lca(a, b) lca(b, d)).
*/
TypeSpec TypeSystem::lowest_common_ancestor(const TypeSpec& a, const TypeSpec& b) const {
auto result = make_typespec(lca_base(a.base_type(), b.base_type()));
if (result == TypeSpec("function") && a.arg_count() == 2 && b.arg_count() == 2 &&
(a.get_arg(0) == TypeSpec("_varargs_") || b.get_arg(0) == TypeSpec("_varargs_"))) {
return TypeSpec("function");
}
if (!a.empty() && !b.empty() && a.arg_count() == b.arg_count()) {
// recursively add arguments
for (size_t i = 0; i < a.arg_count(); i++) {
result.add_arg(lowest_common_ancestor(a.get_arg(i), b.get_arg(i)));
}
}
return result;
}
TypeSpec TypeSystem::lowest_common_ancestor_reg(const TypeSpec& a, const TypeSpec& b) const {
return coerce_to_reg_type(lowest_common_ancestor(a, b));
}
/*!
* Lowest common ancestor of multiple (or at least one) type.
*/
TypeSpec TypeSystem::lowest_common_ancestor(const std::vector<TypeSpec>& types) const {
ASSERT(!types.empty());
if (types.size() == 1) {
return types.front();
}
auto result = lowest_common_ancestor(types.at(0), types.at(1));
for (size_t i = 2; i < types.size(); i++) {
result = lowest_common_ancestor(result, types.at(i));
}
return result;
}
/*!
* Converts a type in memory to the type you'll get in a register after loading it.
*/
TypeSpec coerce_to_reg_type(const TypeSpec& in) {
if (in.arg_count() == 0) {
if (in.base_type() == "int8" || in.base_type() == "int16" || in.base_type() == "int32" ||
in.base_type() == "int64" || in.base_type() == "integer") {
return TypeSpec("int");
}
if (in.base_type() == "uint8" || in.base_type() == "uint16" || in.base_type() == "uint32" ||
in.base_type() == "uint64" || in.base_type() == "uinteger") {
return TypeSpec("uint");
}
}
return in;
}
/*!
* Is the given type a bitfield type?
*/
bool TypeSystem::is_bitfield_type(const std::string& type_name) const {
return dynamic_cast<BitFieldType*>(lookup_type(type_name));
}
/*!
* Get information about a field within a bitfield type.
*/
BitfieldLookupInfo TypeSystem::lookup_bitfield_info(const std::string& type_name,
const std::string& field_name) const {
auto type = get_type_of_type<BitFieldType>(type_name);
BitField f;
if (!type->lookup_field(field_name, &f)) {
throw_typesystem_error("Type {} has no bitfield named {}\n", type_name, field_name);
}
BitfieldLookupInfo result;
result.result_type = f.type();
result.offset = f.offset();
result.sign_extend = lookup_type(result.result_type)->get_load_signed();
result.size = f.size();
return result;
}
/*!
* Add a new field to a bitfield type.
* Set the field size to -1 if you want to just use the size of the type and not clip it.
*/
void TypeSystem::add_field_to_bitfield(BitFieldType* type,
const std::string& field_name,
const TypeSpec& field_type,
int offset,
int field_size,
bool skip_in_decomp) {
// in bits
auto load_size = lookup_type(field_type)->get_load_size() * 8;
if (field_size == -1) {
field_size = load_size;
}
if (field_size > load_size) {
throw_typesystem_error(
"Type {}'s bitfield {}'s set size is {}, which is larger than the actual "
"type: {}\n",
type->get_name(), field_name, field_size, load_size);
}
if (field_size + offset > type->get_load_size() * 8) {
throw_typesystem_error(
"Type {}'s bitfield {} will run off the end of the type (ends at {} bits, "
"type is {} bits)\n",
type->get_name(), field_name, field_size + offset, type->get_load_size() * 8);
}
// 128-bit bitfields have the limitation that fields cannot cross the 64-bit boundary.
if (offset < 64 && offset + field_size > 64) {
throw_typesystem_error(
"Type {}'s bitfield {} will cross bit 64, which is not permitted. Range [{}, {})",
type->get_name(), field_name, offset, offset + field_size);
}
BitField field(field_type, field_name, offset, field_size, skip_in_decomp);
type->m_fields.push_back(field);
}
/*!
* Generate the part of a deftype for the flag asserts and methods.
* Doesn't include the final close paren of the deftype
* This should work for both structure/bitfield definitions.
*/
std::string TypeSystem::generate_deftype_footer(const Type* type) const {
std::string result;
auto as_structure = dynamic_cast<const StructureType*>(type);
if (as_structure) {
if (as_structure->is_packed()) {
result.append(" :pack-me\n");
}
if (as_structure->is_allowed_misalign()) {
result.append(" :allow-misaligned\n");
}
if (as_structure->is_always_stack_singleton()) {
result.append(" :always-stack-singleton\n");
}
}
if (type->heap_base() &&
type->heap_base() !=
((type->get_size_in_memory() - get_type_of_type<BasicType>("process")->size() + 0xf) &
~0xf)) {
// don't print if auto heap-base does the job
result.append(fmt::format(" :heap-base #x{:x}\n", type->heap_base()));
}
auto method_count = get_next_method_id(type);
// result.append(fmt::format(" :method-count-assert {}\n", get_next_method_id(type)));
// result.append(fmt::format(" :size-assert #x{:x}\n", type->get_size_in_memory()));
TypeFlags flags;
flags.heap_base = type->heap_base();
flags.size = type->get_size_in_memory();
flags.pad = 0;
flags.methods = method_count;
// result.append(fmt::format(" :flag-assert #x{:x}\n", flags.flag));
if (!type->gen_inspect()) {
result.append(" :no-inspect\n ");
}
std::string methods_string;
std::string state_methods_string;
std::string states_string;
// New Method
auto new_info = type->get_new_method_defined_for_type();
if (new_info) {
methods_string.append(" (new (");
for (size_t i = 0; i < new_info->type.arg_count() - 1; i++) {
methods_string.append(new_info->type.get_arg(i).print());
if (i != new_info->type.arg_count() - 2) {
methods_string.push_back(' ');
}
}
methods_string.append(
fmt::format(") {}", new_info->type.get_arg(new_info->type.arg_count() - 1).print(), 0));
auto behavior = new_info->type.try_get_tag("behavior");
if (behavior) {
methods_string.append(fmt::format(" :behavior {}", *behavior));
}
methods_string.append(")\n");
}
// Rest of methods
bool done_with_state_methods = false; // TODO fix this... this depends on the order of m_methods
for (auto& info : type->get_methods_defined_for_type()) {
if (!done_with_state_methods && info.type.base_type() == "state" && !info.overrides_parent) {
if (info.type.arg_count() > 1) {
state_methods_string.append(fmt::format(" ({}", info.name));
for (size_t i = 0; i < info.type.arg_count() - 1; ++i) {
state_methods_string.push_back(' ');
state_methods_string.append(info.type.get_arg(i).print());
}
state_methods_string.append(")\n");
} else {
state_methods_string.append(fmt::format(" {}\n", info.name));
}
continue;
} else {
done_with_state_methods = true;
}
// check if we only override the docstring
if (info.only_overrides_docstring) {
continue;
}
methods_string.append(fmt::format(" ({} (", info.name));
for (size_t i = 0; i < info.type.arg_count() - 1; i++) {
methods_string.append(info.type.get_arg(i).print());
if (i != info.type.arg_count() - 2) {
methods_string.push_back(' ');
}
}
methods_string.append(
fmt::format(") {}", info.type.get_arg(info.type.arg_count() - 1).print()));
auto behavior = info.type.try_get_tag("behavior");
if (behavior) {
methods_string.append(fmt::format(" :behavior {}", *behavior));
}
if (info.type.base_type() == "state") {
methods_string.append(" :state");
}
if (info.no_virtual) {
methods_string.append(" :no-virtual");
}
if (info.overrides_parent) {
if (info.overlay_name.has_value()) {
methods_string.append(fmt::format(" :overlay-at {}", *info.overlay_name));
} else {
methods_string.append(" :replace");
}
}
methods_string.append(fmt::format(")\n", info.id));
}
for (auto& info : type->get_states_declared_for_type()) {
if (info.second.arg_count() > 1) {
states_string.append(fmt::format(" ({}", info.first));
for (size_t i = 0; i < info.second.arg_count() - 1; i++) {
states_string.push_back(' ');
states_string.append(info.second.get_arg(i).print());
}
states_string.append(")\n");
} else {
states_string.append(fmt::format(" {}\n", info.first));
}
}
if (!state_methods_string.empty()) {
result.append(" (:state-methods\n");
result.append(state_methods_string);
result.append(" )\n");
}
if (!methods_string.empty()) {
result.append(" (:methods\n");
result.append(methods_string);
result.append(" )\n");
}
if (!states_string.empty()) {
result.append(" (:states\n");
result.append(states_string);
result.append(" )\n");
}
result.append(" )\n");
return result;
}
std::optional<std::string> find_best_field_in_structure(const TypeSystem& ts,
const StructureType* st,
int offset,
const Field& requesting_field,
bool want_fixed,
int start_field,
int end_field = -1) {
// performs best field lookup within a structure, at an offset.
const Field* best_val = nullptr;
const Field* best_exact = nullptr;
const Field* best_struct = nullptr;
std::pair<const Field*, int> best_val_arr = {nullptr, -1};
std::pair<const Field*, int> best_exact_arr = {nullptr, -1};
std::pair<const Field*, int> best_struct_arr = {nullptr, -1};
std::optional<std::string> best_struct_field_deref;
const Field* best = nullptr;
if (end_field == -1) {
end_field = st->fields().size();
}
for (size_t i = start_field; i < (size_t)end_field; ++i) {
const auto& field = st->fields().at(i);
auto type = ts.lookup_type_allow_partial_def(field.type());
if (field.is_dynamic() || field.offset() > offset || field.user_placed() != want_fixed) {
continue;
}
if (!field.is_array()) {
if (!field.is_inline() && field.offset() + type->get_load_size() > offset) {
if (field.offset() == offset) {
// not array, not inline - can fit in register, only check exact offset.
if (!best_val ||
type->get_load_size() == ts.lookup_type(requesting_field.type())->get_load_size()) {
best_val = &field;
}
}
} else if (field.is_inline() && field.offset() + type->get_size_in_memory() > offset) {
if (field.type() == requesting_field.type() && field.offset() == offset) {
// not array, inlined and exact same as this field, just overlay directly on top
best_exact = &field;
} else {
auto f_type = dynamic_cast<StructureType*>(type);
if (f_type) {
// struct that encompasses this field
// simply search that structure for the field we want, offset by the field's offset
auto best_field_in_struct = find_best_field_in_structure(
ts, f_type, offset - field.offset(), requesting_field, want_fixed, 0);
if (best_field_in_struct) {
best_struct_field_deref = best_field_in_struct;
best_struct = &field;
}
}
}
}
} else {
int rel_offset = offset - field.offset();
// array case (and array encompasses what we want)
ASSERT_MSG(
type->get_size_in_memory() > 0,
fmt::format(
"In type {}, type size was 0 for array field {} of type {}, check that the type "
"is fully defined.",
st->get_name(), field.name(), field.type().print()));
int array_idx = rel_offset / type->get_size_in_memory();
if (!field.is_inline() &&
field.offset() + field.array_size() * type->get_load_size() > offset) {
if (rel_offset % type->get_load_size() == 0) {
// found exact match for array index
if (!best_val_arr.first ||
type->get_load_size() == ts.lookup_type(requesting_field.type())->get_load_size()) {
best_val_arr.first = &field;
best_val_arr.second = rel_offset / type->get_load_size();
}
}
} else if (field.is_inline() &&
field.offset() + field.array_size() * type->get_size_in_memory() > offset) {
if (field.type() == requesting_field.type() &&
rel_offset % type->get_size_in_memory() == 0) {
// same type
best_exact_arr.first = &field;
best_exact_arr.second = array_idx;
} else if (requesting_field.is_array() && rel_offset % type->get_size_in_memory() == 0 &&
array_idx == 0) {
// starts at the same offset as another array. just use the field with nothing extra
best_exact = &field;
} else {
auto f_type = dynamic_cast<StructureType*>(type);
if (f_type && field.offset() + f_type->get_size_in_memory() > offset) {
// struct that encompasses this field
// simply search that structure for the field we want, offset by the field's offset
auto best_field_in_struct =
find_best_field_in_structure(ts, f_type, rel_offset % type->get_size_in_memory(),
requesting_field, want_fixed, 0);
if (best_field_in_struct) {
best_struct_field_deref = best_field_in_struct;
best_struct_arr.first = &field;
best_struct_arr.second = array_idx;
}
}
}
}
}
}
int best_array_idx = -1;
if (best_exact) {
best = best_exact;
} else if (best_exact_arr.first) {
best = best_exact_arr.first;
best_array_idx = best_exact_arr.second;
} else if (best_val) {
best = best_val;
} else if (best_val_arr.first) {
best = best_val_arr.first;
best_array_idx = best_val_arr.second;
} else if (best_struct) {
best = best_struct;
} else if (best_struct_arr.first) {
best = best_struct_arr.first;
best_array_idx = best_struct_arr.second;
}
if (best) {
auto ret =
best_array_idx == -1 ? best->name() : fmt::format("{} {}", best->name(), best_array_idx);
if (best == best_struct || best == best_struct_arr.first) {
return ret + " " + *best_struct_field_deref;
} else {
return ret;
}
} else if (!want_fixed) {
// try again but with a user-placed offset
return find_best_field_in_structure(ts, st, offset, requesting_field, true, start_field,
end_field);
}
return {};
}
std::string TypeSystem::generate_deftype_for_structure(const StructureType* st) const {
std::string result;
result += fmt::format("(deftype {} ({})\n", st->get_name(), st->get_parent());
if (st->m_metadata.docstring) {
result += fmt::format(" \"{}\"\n", st->m_metadata.docstring.value());
}
result += " (";
int longest_field_name = 0;
int longest_type_name = 0;
int longest_mods = 0;
int longest_mods_with_user_placed = 0;
const std::string inline_string = ":inline";
const std::string dynamic_string = ":dynamic";
// calculate longest strings needed, for basic linting
// override fields
for (auto i : st->override_fields()) {
const auto& field = st->fields().at(i);
longest_field_name = std::max(longest_field_name, int(field.name().size()));
longest_type_name = std::max(longest_type_name, int(field.type().print().size()));
}
// normal fields
for (size_t i = st->first_unique_field_idx(); i < st->fields().size(); i++) {
const auto& field = st->fields().at(i);
int mods = 0;
// mods are array size, :inline, :dynamic
if (field.is_array() && !field.is_dynamic()) {
mods++;
mods += std::to_string(field.array_size()).size();
}
if (field.is_inline()) {
mods++; // space
mods += inline_string.size();
}
if (field.is_dynamic()) {
mods++; // space
mods += dynamic_string.size();
}
longest_field_name = std::max(longest_field_name, int(field.name().size()));
if (mods > 0 || field.user_placed()) {
// this is only relevant for fields that have mods
longest_type_name = std::max(longest_type_name, int(field.type().print().size()));
}
longest_mods = std::max(longest_mods, mods);
if (field.user_placed()) {
longest_mods_with_user_placed = std::max(longest_mods_with_user_placed, mods);
}
}
// now actually write out the fields
// override fields first
for (auto i : st->override_fields()) {
const auto& field = st->fields().at(i);
result += "(";
result += field.name();
result.append(2 + (longest_field_name - int(field.name().size())), ' ');
result += field.type().print();
result.append(1 + (longest_type_name - int(field.type().print().size())), ' ');
result.append(":override)\n ");
}
// now normal fields
for (size_t i = st->first_unique_field_idx(); i < st->fields().size(); i++) {
const auto& field = st->fields().at(i);
result += "(";
result += field.name();
result.append(2 + (longest_field_name - int(field.name().size())), ' ');
result += field.type().print();
std::string mods;
if (field.is_array() && !field.is_dynamic()) {
mods += " ";
mods += std::to_string(field.array_size());
}
if (field.is_inline()) {
mods += " ";
mods += inline_string;
}
if (field.is_dynamic()) {
mods += " ";
mods += dynamic_string;
}
if (!mods.empty()) {
result.append(1 + longest_type_name - int(field.type().print().size()), ' ');
}
result.append(mods);
if (field.user_placed()) {
result.append(longest_mods_with_user_placed - int(mods.size()), ' ');
if (mods.empty()) {
result.append(1 + longest_type_name - int(field.type().print().size()), ' ');
}
// find best field for :overlay-at
// we find the first field that does not come after the current one
// and either use it, or check if one of its fields (recursively) is appropriate
// we also check for array offsets. we ALSO do bounds-checking!
// non-fixed offset fields get priority! dynamic fields are IGNORED.
// if all else fails, print as fixed offset.
auto best_match = find_best_field_in_structure(*this, st, field.offset(), field, false, 0, i);
if (!best_match) {
result.append(fmt::format(" :offset {:3d}", field.offset()));
} else if (best_match->find(' ') == std::string::npos) {
result.append(fmt::format(" :overlay-at {}", *best_match));
} else {
result.append(fmt::format(" :overlay-at (-> {})", *best_match));
}
}
result.append(")\n ");
}
result.append(")\n");
result.append(generate_deftype_footer(st));
return result;
}
std::string TypeSystem::generate_deftype_for_bitfield(const BitFieldType* type) const {
std::string result;
result += fmt::format("(deftype {} ({})\n", type->get_name(), type->get_parent());
if (type->m_metadata.docstring) {
result += fmt::format(" \"{}\"\n", type->m_metadata.docstring.value());
}
result += " (";
int longest_field_name = 0;
int longest_type_name = 0;
for (const auto& field : type->fields()) {
longest_field_name = std::max(longest_field_name, int(field.name().size()));
longest_type_name = std::max(longest_type_name, int(field.type().print().size()));
}
for (const auto& field : type->fields()) {
result += "(";
result += field.name();
result.append(1 + (longest_field_name - int(field.name().size())), ' ');
result += field.type().print();
result.append(1 + (longest_type_name - int(field.type().print().size())), ' ');
result.append(fmt::format(":offset {:3d} :size {:3d}", field.offset(), field.size()));
result.append(")\n ");
}
result.append(")\n");
result.append(generate_deftype_footer(type));
return result;
}
std::string TypeSystem::generate_deftype(const Type* type) const {
std::string result;
auto st = dynamic_cast<const StructureType*>(type);
if (st) {
return generate_deftype_for_structure(st);
}
auto bf = dynamic_cast<const BitFieldType*>(type);
if (bf) {
return generate_deftype_for_bitfield(bf);
}
return fmt::format(
";; cannot generate deftype for {}, it is not a structure, basic, or bitfield (parent {})\n",
type->get_name(), type->get_parent());
}
bool TypeSystem::should_use_virtual_methods(const Type* type, int method_id) const {
auto as_basic = dynamic_cast<const BasicType*>(type);
if (as_basic && !as_basic->final() && !lookup_method(type->get_name(), method_id).no_virtual) {
return true;
} else {
return false;
}
}
bool TypeSystem::should_use_virtual_methods(const TypeSpec& type, int method_id) const {
auto it = m_types.find(type.base_type());
if (it != m_types.end()) {
// it's a fully defined type
return should_use_virtual_methods(it->second.get(), method_id);
} else {
// it's a partially defined type.
// for now, we will prohibit calling a method on something that's defined only as a structure
// because we don't know if it's actually a basic, and should use virtual methods.
auto fwd_dec_type = lookup_type_allow_partial_def(type);
if (fwd_dec_type->get_name() == "structure") {
throw_typesystem_error(
"Type {} was forward declared as structure and it is not safe to call a method.",
type.print());
return false;
} else {
return should_use_virtual_methods(fwd_dec_type, method_id);
}
}
}