[opengoal] make none a child of object (#3001)

Previously, `object` and `none` were both top-level types. This made
decompilation rather messy as they have no LCA and resulted in a lot of
variables coming out as type `none` which is very very wrong and
additionally there were plenty of casts to `object`. This changes it so
`none` becomes a child of `object` (it is still represented by
`NullType` which remains unusable in compilation).

This change makes `object` the sole top-level type, and the type that
can represent *any* GOAL object. I believe this matches the original
GOAL built-in type structure. A function that has a return type of
`object` can now return an integer or a `none` at the same time.
However, keep in mind that the return value of `(none)` is still
undefined, just as before. This also makes a cast to `object`
meaningless in 90% of the situations it showed up in (as every single
thing is already an `object`) and the decompiler will no longer emit
them. Casts to `none` are also reduced. Yay!

Additionally, state handlers also don't get the final `(none)` printed
out anymore. The return type of a state handler is completely
meaningless outside the event handler (which is return type `object`
anyway) so there are no limitations on what the last form needs to be. I
did this instead of making them return `object` to trick the decompiler
into not trying to output a variable to be used as a return value
(internally, in the decompiler they still have return type `none`, but
they have `object` elsewhere).

Fixes #1703 
Fixes #830 
Fixes #928
This commit is contained in:
ManDude
2023-09-22 10:54:49 +01:00
committed by GitHub
parent 697b07abd5
commit fe491c2b5e
964 changed files with 24949 additions and 39444 deletions
+18 -7
View File
@@ -187,8 +187,8 @@ Val* Compiler::compile_cond(const goos::Object& form, const goos::Object& rest,
std::vector<TypeSpec> case_result_types;
for_each_in_list(rest, [&](const goos::Object& o) {
auto test = pair_car(o);
auto clauses = pair_cdr(o);
const auto& test = pair_car(o);
const auto& clauses = pair_cdr(o);
if (got_else) {
throw_compiler_error(form, "Cond from cannot have any cases after else.");
@@ -228,12 +228,14 @@ Val* Compiler::compile_cond(const goos::Object& form, const goos::Object& rest,
// CODE
Val* case_result = get_none();
const goos::Object* case_clause = nullptr;
for_each_in_list(clauses, [&](const goos::Object& clause) {
case_result = compile_error_guard(clause, env);
if (!dynamic_cast<None*>(case_result)) {
case_result = case_result->to_reg(clause, env);
}
case_clause = &clause;
});
if (case_clause && !dynamic_cast<None*>(case_result)) {
case_result = case_result->to_reg(*case_clause, env);
}
case_result_types.push_back(case_result->type());
if (!is_none(case_result)) {
@@ -251,15 +253,24 @@ Val* Compiler::compile_cond(const goos::Object& form, const goos::Object& rest,
});
if (!got_else) {
// if no else, clause, return #f. But don't retype. todo what does goal do here?
// if no else clause, return #f.
auto get_false = std::make_unique<IR_LoadSymbolPointer>(result, "#f");
env->emit(form, std::move(get_false));
}
if (case_result_types.empty()) {
// completely empty cond
result->set_type(TypeSpec("none"));
} else {
result->set_type(coerce_to_reg_type(m_ts.lowest_common_ancestor(case_result_types)));
auto lca = m_ts.lowest_common_ancestor(case_result_types);
if (!got_else && lca == TypeSpec("none")) {
// least common ancestor was none, but there is still the #f from the missing else case.
// elevate cond to an `object` overall so we can capture that #f!
// (`object` is the direct ancestor of `none` and the ancestor to all types overall)
// TODO : what does goal do here?
lca = TypeSpec("object");
}
result->set_type(coerce_to_reg_type(lca));
}
// maybe use 128-bit register