mirror of
https://github.com/open-goal/jak-project
synced 2026-07-08 22:45:00 -04:00
add typecheck and tests
This commit is contained in:
@@ -217,9 +217,7 @@ class StructureType : public ReferenceType {
|
||||
int get_in_memory_alignment() const override;
|
||||
int get_inline_array_alignment() const override;
|
||||
bool lookup_field(const std::string& name, Field* out);
|
||||
bool is_dynamic() const {
|
||||
return m_dynamic;
|
||||
}
|
||||
bool is_dynamic() const { return m_dynamic; }
|
||||
~StructureType() = default;
|
||||
|
||||
protected:
|
||||
|
||||
@@ -39,7 +39,7 @@ bool TypeSpec::operator==(const TypeSpec& other) const {
|
||||
TypeSpec TypeSpec::substitute_for_method_call(const std::string& method_type) const {
|
||||
TypeSpec result;
|
||||
result.m_type = (m_type == "_type_") ? method_type : m_type;
|
||||
for(const auto& x : m_arguments) {
|
||||
for (const auto& x : m_arguments) {
|
||||
result.m_arguments.push_back(x.substitute_for_method_call(method_type));
|
||||
}
|
||||
return result;
|
||||
|
||||
@@ -34,9 +34,7 @@ class TypeSpec {
|
||||
|
||||
const std::string base_type() const { return m_type; }
|
||||
|
||||
bool has_single_arg() const {
|
||||
return m_arguments.size() == 1;
|
||||
}
|
||||
bool has_single_arg() const { return m_arguments.size() == 1; }
|
||||
|
||||
const TypeSpec& get_single_arg() const {
|
||||
assert(m_arguments.size() == 1);
|
||||
@@ -46,6 +44,7 @@ class TypeSpec {
|
||||
TypeSpec substitute_for_method_call(const std::string& method_type) const;
|
||||
|
||||
private:
|
||||
friend class TypeSystem;
|
||||
std::string m_type;
|
||||
std::vector<TypeSpec> m_arguments;
|
||||
};
|
||||
|
||||
@@ -84,7 +84,7 @@ std::string TypeSystem::get_runtime_type(const TypeSpec& ts) {
|
||||
DerefInfo TypeSystem::get_deref_info(const TypeSpec& ts) {
|
||||
DerefInfo info;
|
||||
|
||||
if(!ts.has_single_arg()) {
|
||||
if (!ts.has_single_arg()) {
|
||||
// not enough info.
|
||||
info.can_deref = false;
|
||||
return info;
|
||||
@@ -97,7 +97,7 @@ DerefInfo TypeSystem::get_deref_info(const TypeSpec& ts) {
|
||||
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()) {
|
||||
if (!result_structure_type || result_structure_type->is_dynamic()) {
|
||||
info.can_deref = false;
|
||||
return info;
|
||||
}
|
||||
@@ -109,7 +109,6 @@ DerefInfo TypeSystem::get_deref_info(const TypeSpec& ts) {
|
||||
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_alignment());
|
||||
@@ -203,7 +202,7 @@ TypeSpec TypeSystem::make_inline_array_typespec(const TypeSpec& type) {
|
||||
* 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) {
|
||||
Type* TypeSystem::lookup_type(const std::string& name) const {
|
||||
auto kv = m_types.find(name);
|
||||
if (kv != m_types.end()) {
|
||||
return kv->second.get();
|
||||
@@ -224,7 +223,7 @@ Type* TypeSystem::lookup_type(const std::string& name) {
|
||||
* 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) {
|
||||
Type* TypeSystem::lookup_type(const TypeSpec& ts) const {
|
||||
return lookup_type(ts.base_type());
|
||||
}
|
||||
|
||||
@@ -609,6 +608,39 @@ void TypeSystem::add_builtin_types() {
|
||||
|
||||
// VU FUNCTION
|
||||
// don't inherit
|
||||
(void)vu_function_type;
|
||||
|
||||
// link block
|
||||
(void)link_block_type;
|
||||
|
||||
(void)kheap_type;
|
||||
(void)array_type;
|
||||
(void)pair_type;
|
||||
(void)process_tree_type;
|
||||
(void)process_type;
|
||||
(void)thread_type;
|
||||
(void)connectable_type;
|
||||
(void)stack_frame_type;
|
||||
(void)file_stream_type;
|
||||
(void)pointer_type;
|
||||
(void)inline_array_type;
|
||||
|
||||
(void)number_type; // sign extend?
|
||||
(void)float_type;
|
||||
(void)integer_type; // sign extend?
|
||||
(void)binteger_type; // sign extend?
|
||||
(void)sinteger_type;
|
||||
(void)int8_type;
|
||||
(void)int16_type;
|
||||
(void)int32_type;
|
||||
(void)int64_type;
|
||||
(void)int128_type;
|
||||
(void)uinteger_type;
|
||||
(void)uint8_type;
|
||||
(void)uint16_type;
|
||||
(void)uint32_type;
|
||||
(void)uint64_type;
|
||||
(void)uint128_type;
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -777,4 +809,88 @@ ValueType* TypeSystem::add_builtin_value_type(const std::string& parent,
|
||||
*/
|
||||
void TypeSystem::builtin_structure_inherit(StructureType* st) {
|
||||
st->inherit(get_type_of_type<StructureType>(st->get_parent()));
|
||||
}
|
||||
|
||||
/*!
|
||||
* 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(const TypeSpec& expected,
|
||||
const TypeSpec& actual,
|
||||
const std::string& error_source_name,
|
||||
bool print_on_error,
|
||||
bool throw_on_error) const {
|
||||
bool success = true;
|
||||
// first, typecheck the base types:
|
||||
if (!typecheck_base_types(expected.base_type(), actual.base_type())) {
|
||||
success = false;
|
||||
}
|
||||
|
||||
// next argument checks:
|
||||
if (expected.m_arguments.size() == actual.m_arguments.size()) {
|
||||
for (size_t i = 0; i < expected.m_arguments.size(); 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 (!typecheck(expected.m_arguments[i], actual.m_arguments[i], "", false, false)) {
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// different sizes of arguments.
|
||||
if (expected.m_arguments.empty()) {
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
if (print_on_error) {
|
||||
if (error_source_name.empty()) {
|
||||
fmt::print("[TypeSystem] Got type \"{}\" when expecting \"{}\"\n", actual.print(),
|
||||
expected.print());
|
||||
} else {
|
||||
fmt::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;
|
||||
}
|
||||
|
||||
bool TypeSystem::typecheck_base_types(const std::string& expected,
|
||||
const std::string& actual) const {
|
||||
// just to make sure it exists. (note - could there be a case when it just has to be forward
|
||||
// declared, but not defined?)
|
||||
lookup_type(expected);
|
||||
|
||||
if (expected == actual) {
|
||||
lookup_type(actual); // make sure it exists
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string actual_name = actual;
|
||||
auto actual_type = lookup_type(actual_name);
|
||||
while (actual_type->has_parent()) {
|
||||
actual_name = actual_type->get_parent();
|
||||
actual_type = lookup_type(actual_name);
|
||||
|
||||
if (expected == actual_name) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -46,8 +46,8 @@ class TypeSystem {
|
||||
TypeSpec make_inline_array_typespec(const std::string& type);
|
||||
TypeSpec make_inline_array_typespec(const TypeSpec& type);
|
||||
|
||||
Type* lookup_type(const TypeSpec& ts);
|
||||
Type* lookup_type(const std::string& name);
|
||||
Type* lookup_type(const TypeSpec& ts) const;
|
||||
Type* lookup_type(const std::string& name) const;
|
||||
|
||||
MethodInfo add_method(Type* type, const std::string& method_name, const TypeSpec& ts);
|
||||
MethodInfo add_new_method(Type* type, const TypeSpec& ts);
|
||||
@@ -68,6 +68,11 @@ class TypeSystem {
|
||||
void add_builtin_types();
|
||||
|
||||
std::string print_all_type_information() const;
|
||||
bool typecheck(const TypeSpec& expected,
|
||||
const TypeSpec& actual,
|
||||
const std::string& error_source_name = "",
|
||||
bool print_on_error = true,
|
||||
bool throw_on_error = true) const;
|
||||
|
||||
/*!
|
||||
* Get a type by name and cast to a child class of Type*. Must succeed.
|
||||
@@ -83,6 +88,7 @@ class TypeSystem {
|
||||
}
|
||||
|
||||
private:
|
||||
bool typecheck_base_types(const std::string& expected, const std::string& actual) const;
|
||||
int get_size_in_type(const Field& field);
|
||||
int get_alignment_in_type(const Field& field);
|
||||
Field lookup_field(const std::string& type_name, const std::string& field_name);
|
||||
|
||||
@@ -1225,7 +1225,8 @@ u64 inspect_object(u32 obj) {
|
||||
} else if ((obj & OFFSET_MASK) == PAIR_OFFSET) {
|
||||
return inspect_pair(obj);
|
||||
} else if ((obj & OFFSET_MASK) == BASIC_OFFSET) {
|
||||
return call_method_of_type(obj, Ptr<Type>(*Ptr<u32>(obj - BASIC_OFFSET)), GOAL_INSPECT_METHOD);
|
||||
return call_method_of_type(obj, Ptr<Type>(*Ptr<u32>(obj - BASIC_OFFSET)),
|
||||
GOAL_INSPECT_METHOD);
|
||||
} else {
|
||||
cprintf("#<unknown type %d @ #x%x>", obj & OFFSET_MASK, obj);
|
||||
}
|
||||
|
||||
@@ -19,13 +19,11 @@ extern Ptr<u32> LastSymbol;
|
||||
|
||||
constexpr s32 GOAL_MAX_SYMBOLS = 0x2000;
|
||||
|
||||
|
||||
constexpr s32 SYM_INFO_OFFSET = 0xff34;
|
||||
constexpr u32 EMPTY_HASH = 0x8454B6E6;
|
||||
constexpr u32 OFFSET_MASK = 7;
|
||||
constexpr u32 CRC_POLY = 0x04c11db7;
|
||||
|
||||
|
||||
constexpr u32 DEFAULT_METHOD_COUNT = 12;
|
||||
constexpr u32 FALLBACK_UNKNOWN_METHOD_COUNT = 44;
|
||||
|
||||
|
||||
@@ -34,6 +34,11 @@ TEST(Listener, DeciInit) {
|
||||
*/
|
||||
TEST(Listener, ListenToNothing) {
|
||||
Listener l;
|
||||
if (l.connect_to_target()) {
|
||||
printf(
|
||||
"~~~~~~ Test connected to a runtime when there shouldn't be anything running! Check that "
|
||||
"you don't have gk running in the background!\n");
|
||||
}
|
||||
EXPECT_FALSE(l.connect_to_target());
|
||||
l.disconnect();
|
||||
}
|
||||
|
||||
+62
-11
@@ -143,16 +143,13 @@ TEST(TypeSystem, DerefInfoNoLoadInfoOrStride) {
|
||||
EXPECT_EQ(info.result_type.print(), "int64");
|
||||
|
||||
// test inline-array (won't work because type is dynamically sized)
|
||||
type_spec =
|
||||
ts.make_inline_array_typespec("type");
|
||||
type_spec = ts.make_inline_array_typespec("type");
|
||||
info = ts.get_deref_info(type_spec);
|
||||
EXPECT_FALSE(info.can_deref);
|
||||
|
||||
|
||||
// TODO - replace with a better type.
|
||||
// TODO - maybe block basic or structure from being inline-array-able?
|
||||
type_spec =
|
||||
ts.make_inline_array_typespec("basic");
|
||||
type_spec = ts.make_inline_array_typespec("basic");
|
||||
auto type = ts.lookup_type("basic");
|
||||
info = ts.get_deref_info(type_spec);
|
||||
EXPECT_TRUE(info.can_deref);
|
||||
@@ -203,31 +200,85 @@ TEST(TypeSystem, NewMethod) {
|
||||
TypeSystem ts;
|
||||
ts.add_builtin_types();
|
||||
ts.add_type("test-1", std::make_unique<BasicType>("basic", "test-1"));
|
||||
ts.add_method(ts.lookup_type("test-1"), "new", ts.make_function_typespec({"symbol", "string"}, "test-1"));
|
||||
ts.add_method(ts.lookup_type("test-1"), "new",
|
||||
ts.make_function_typespec({"symbol", "string"}, "test-1"));
|
||||
ts.add_type("test-2", std::make_unique<BasicType>("test-1", "test-2"));
|
||||
ts.add_method(ts.lookup_type("test-2"), "new", ts.make_function_typespec({"symbol", "string", "symbol"}, "test-2"));
|
||||
ts.add_method(ts.lookup_type("test-2"), "new",
|
||||
ts.make_function_typespec({"symbol", "string", "symbol"}, "test-2"));
|
||||
|
||||
EXPECT_EQ(ts.lookup_method("test-1", "new").type.print(), "(function symbol string test-1)");
|
||||
EXPECT_EQ(ts.lookup_method("test-2", "new").type.print(), "(function symbol string symbol test-2)");
|
||||
EXPECT_EQ(ts.lookup_method("test-2", "new").type.print(),
|
||||
"(function symbol string symbol test-2)");
|
||||
|
||||
ts.add_type("test-3", std::make_unique<BasicType>("test-1", "test-3"));
|
||||
EXPECT_EQ(ts.lookup_method("test-3", "new").type.print(), "(function symbol string test-1)");
|
||||
|
||||
ts.add_type("test-4", std::make_unique<BasicType>("test-2", "test-4"));
|
||||
EXPECT_EQ(ts.lookup_method("test-4", "new").type.print(), "(function symbol string symbol test-2)");
|
||||
EXPECT_EQ(ts.lookup_method("test-4", "new").type.print(),
|
||||
"(function symbol string symbol test-2)");
|
||||
}
|
||||
|
||||
|
||||
TEST(TypeSystem, MethodSubstitute) {
|
||||
TypeSystem ts;
|
||||
ts.add_builtin_types();
|
||||
ts.add_type("test-1", std::make_unique<BasicType>("basic", "test-1"));
|
||||
ts.add_method(ts.lookup_type("test-1"), "new", ts.make_function_typespec({"symbol", "string", "_type_"}, "_type_"));
|
||||
ts.add_method(ts.lookup_type("test-1"), "new",
|
||||
ts.make_function_typespec({"symbol", "string", "_type_"}, "_type_"));
|
||||
|
||||
auto final_type = ts.lookup_method("test-1", "new").type.substitute_for_method_call("test-1");
|
||||
EXPECT_EQ(final_type.print(), "(function symbol string test-1 test-1)");
|
||||
}
|
||||
|
||||
namespace {
|
||||
bool ts_name_name(TypeSystem& ts, const std::string& ex, const std::string& act) {
|
||||
return ts.typecheck(ts.make_typespec(ex), ts.make_typespec(act), "", false, false);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST(TypeSystem, TypeCheck) {
|
||||
TypeSystem ts;
|
||||
ts.add_builtin_types();
|
||||
|
||||
EXPECT_TRUE(ts_name_name(ts, "none",
|
||||
"none")); // none - none _shouldn't_ fail (for function return types!)
|
||||
EXPECT_TRUE(ts_name_name(ts, "object", "object"));
|
||||
EXPECT_TRUE(ts_name_name(ts, "object", "type"));
|
||||
EXPECT_TRUE(ts_name_name(ts, "basic", "type"));
|
||||
EXPECT_FALSE(ts_name_name(ts, "type", "basic"));
|
||||
EXPECT_TRUE(ts_name_name(ts, "type", "type"));
|
||||
|
||||
auto f = ts.make_typespec("function");
|
||||
auto f_s_n = ts.make_function_typespec({"string"}, "none");
|
||||
auto f_b_n = ts.make_function_typespec({"basic"}, "none");
|
||||
|
||||
// complex
|
||||
EXPECT_TRUE(ts.typecheck(f, f_s_n));
|
||||
EXPECT_TRUE(ts.typecheck(f_s_n, f_s_n));
|
||||
EXPECT_TRUE(ts.typecheck(f_b_n, f_s_n));
|
||||
|
||||
EXPECT_FALSE(ts.typecheck(f_s_n, f, "", false, false));
|
||||
EXPECT_FALSE(ts.typecheck(f_s_n, f_b_n, "", false, false));
|
||||
|
||||
// number of parameter mismatch
|
||||
auto f_s_s_n = ts.make_function_typespec({"string", "string"}, "none");
|
||||
EXPECT_FALSE(ts.typecheck(f_s_n, f_s_s_n, "", false, false));
|
||||
EXPECT_FALSE(ts.typecheck(f_s_s_n, f_s_n, "", false, false));
|
||||
}
|
||||
|
||||
TEST(TypeSystem, FieldLookup) {
|
||||
// note - this test isn't testing the specific needs_deref, type of the returned info. Until more
|
||||
// stuff is set up that test is kinda useless - it would just be testing against the exact
|
||||
// implementation of lookup_field_info
|
||||
|
||||
TypeSystem ts;
|
||||
ts.add_builtin_types();
|
||||
|
||||
EXPECT_EQ(ts.lookup_field_info("type", "parent").field.offset(), 8);
|
||||
EXPECT_EQ(ts.lookup_field_info("string", "data").type.print(), "(pointer uint8)");
|
||||
|
||||
EXPECT_ANY_THROW(ts.lookup_field_info("type", "not-a-real-field"));
|
||||
}
|
||||
|
||||
// field lookup
|
||||
|
||||
// TODO - a big test to make sure all the builtin types are what we expect.
|
||||
|
||||
Reference in New Issue
Block a user