add typecheck and tests

This commit is contained in:
water
2020-08-27 16:47:54 -04:00
parent a32e6288ac
commit 75f63d8ff3
9 changed files with 202 additions and 28 deletions
+1 -3
View File
@@ -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:
+1 -1
View File
@@ -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;
+2 -3
View File
@@ -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;
};
+121 -5
View File
@@ -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;
}
+8 -2
View File
@@ -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);
+2 -1
View File
@@ -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);
}
-2
View File
@@ -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;
+5
View File
@@ -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
View File
@@ -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.