diff --git a/common/type_system/Type.h b/common/type_system/Type.h index 162421ca8a..9a24354f8c 100644 --- a/common/type_system/Type.h +++ b/common/type_system/Type.h @@ -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: diff --git a/common/type_system/TypeSpec.cpp b/common/type_system/TypeSpec.cpp index 6ddd8cca4d..c78def6cd8 100644 --- a/common/type_system/TypeSpec.cpp +++ b/common/type_system/TypeSpec.cpp @@ -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; diff --git a/common/type_system/TypeSpec.h b/common/type_system/TypeSpec.h index 892a364a37..04a0431b08 100644 --- a/common/type_system/TypeSpec.h +++ b/common/type_system/TypeSpec.h @@ -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 m_arguments; }; diff --git a/common/type_system/TypeSystem.cpp b/common/type_system/TypeSystem.cpp index 3ad371cf21..41129aac70 100644 --- a/common/type_system/TypeSystem.cpp +++ b/common/type_system/TypeSystem.cpp @@ -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(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(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; } \ No newline at end of file diff --git a/common/type_system/TypeSystem.h b/common/type_system/TypeSystem.h index 600b2e44a4..a8cdbba7cf 100644 --- a/common/type_system/TypeSystem.h +++ b/common/type_system/TypeSystem.h @@ -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); diff --git a/game/kernel/kscheme.cpp b/game/kernel/kscheme.cpp index 673781988f..1fb6a379b6 100644 --- a/game/kernel/kscheme.cpp +++ b/game/kernel/kscheme.cpp @@ -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(*Ptr(obj - BASIC_OFFSET)), GOAL_INSPECT_METHOD); + return call_method_of_type(obj, Ptr(*Ptr(obj - BASIC_OFFSET)), + GOAL_INSPECT_METHOD); } else { cprintf("#", obj & OFFSET_MASK, obj); } diff --git a/game/kernel/kscheme.h b/game/kernel/kscheme.h index 8129ebd50d..5fa1bbf33e 100644 --- a/game/kernel/kscheme.h +++ b/game/kernel/kscheme.h @@ -19,13 +19,11 @@ extern Ptr 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; diff --git a/test/test_listener_deci2.cpp b/test/test_listener_deci2.cpp index 2096150ead..8347bb5438 100644 --- a/test/test_listener_deci2.cpp +++ b/test/test_listener_deci2.cpp @@ -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(); } diff --git a/test/test_type_system.cpp b/test/test_type_system.cpp index 6f01b261d6..f6504988f2 100644 --- a/test/test_type_system.cpp +++ b/test/test_type_system.cpp @@ -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("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("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("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("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("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.