mirror of
https://github.com/open-goal/jak-project
synced 2026-05-23 06:54:31 -04:00
[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:
@@ -91,21 +91,31 @@ bool convert_to_expressions(
|
||||
needs_cast = true;
|
||||
|
||||
} else {
|
||||
bool found_early_return = false;
|
||||
for (auto e : new_entries) {
|
||||
e->apply([&](FormElement* elt) {
|
||||
auto as_ret = dynamic_cast<ReturnElement*>(elt);
|
||||
if (as_ret) {
|
||||
found_early_return = true;
|
||||
// note : a return type of "object" will accept ANYTHING as a return value
|
||||
// "object" is the parent type of everything, including "none" (as of sep 2023)
|
||||
// if a function wants to return an object, we can safely discard the cast
|
||||
// since there is no possible level of polymorphism at this highest level.
|
||||
if (f.type.last_arg() != TypeSpec("object")) {
|
||||
bool found_early_return = false;
|
||||
for (auto e : new_entries) {
|
||||
e->apply([&](FormElement* elt) {
|
||||
auto as_ret = dynamic_cast<ReturnElement*>(elt);
|
||||
if (as_ret) {
|
||||
found_early_return = true;
|
||||
}
|
||||
});
|
||||
if (found_early_return) {
|
||||
break;
|
||||
}
|
||||
});
|
||||
if (found_early_return) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found_early_return && f.type.last_arg() != return_type) {
|
||||
needs_cast = true;
|
||||
// the return value of this function is not an exact match
|
||||
// we cast it to avoid complicated issues with polymorphism (e.g. methods)
|
||||
// we don't run this if we find a (return statement because we do not handle
|
||||
// type checking on those at the moment.
|
||||
if (!found_early_return && f.type.last_arg() != return_type) {
|
||||
needs_cast = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,8 +134,10 @@ bool convert_to_expressions(
|
||||
} else {
|
||||
// or just get all the expressions
|
||||
new_entries = stack.rewrite(pool, f.ir2.env);
|
||||
new_entries.push_back(
|
||||
pool.alloc_element<GenericElement>(GenericOperator::make_fixed(FixedOperatorKind::NONE)));
|
||||
if (!f.ir2.skip_final_none) {
|
||||
new_entries.push_back(pool.alloc_element<GenericElement>(
|
||||
GenericOperator::make_fixed(FixedOperatorKind::NONE)));
|
||||
}
|
||||
}
|
||||
|
||||
// if we are a totally empty function, insert a placeholder so we don't have to handle
|
||||
|
||||
Reference in New Issue
Block a user