Explorer: support virtual class methods (#2462)

Features:
* Add `virtual` virtual override keyword for functions
* Support `virtual` class methods using dynamic dispatch

Changes:
* Add `vtable` in `NominalClassType`, 
* Add `NominalClassValue**` in class values pointing to  descendant-most class
* Resolve virtual methods during member lookup

Limitations:
* Does not include yet `impl`, `abstract` virtual override keywords, or the complete logic for virtual function declaration

Depends on #2460 
Relates to #1881 
Relates to #2493
This commit is contained in:
Adrien Leravat
2023-01-02 12:37:49 -08:00
committed by GitHub
parent dd26ea6a15
commit 8301258ef8
17 changed files with 359 additions and 39 deletions
+40 -5
View File
@@ -38,6 +38,19 @@ auto StructValue::FindField(std::string_view name) const
return std::nullopt;
}
NominalClassValue::NominalClassValue(
Nonnull<const Value*> type, Nonnull<const Value*> inits,
std::optional<Nonnull<const NominalClassValue*>> base,
Nonnull<const NominalClassValue** const> class_value_ptr)
: Value(Kind::NominalClassValue),
type_(type),
inits_(inits),
base_(base),
class_value_ptr_(class_value_ptr) {
// Update ancestors's class value to point to latest child.
*class_value_ptr_ = this;
}
static auto FindClassField(Nonnull<const NominalClassValue*> object,
std::string_view name)
-> std::optional<Nonnull<const Value*>> {
@@ -148,8 +161,30 @@ static auto GetNamedElement(Nonnull<Arena*> arena, Nonnull<const Value*> v,
} else if ((*func)->declaration().is_method()) {
// Found a method. Turn it into a bound method.
const auto& m = cast<FunctionValue>(**func);
return arena->New<BoundMethodValue>(&m.declaration(), me_value,
&class_type.bindings());
if (!m.declaration().is_virtual()) {
return arena->New<BoundMethodValue>(&m.declaration(), me_value,
&class_type.bindings());
}
// Method is virtual, get child-most class value and perform vtable
// lookup.
const auto& last_child_value = **object.class_value_ptr();
const auto& last_child_type =
cast<NominalClassType>(last_child_value.type());
const auto res = last_child_type.vtable().find(f);
CARBON_CHECK(res != last_child_type.vtable().end());
const auto [virtual_method, level] = res->second;
const auto level_diff = last_child_type.hierarchy_level() - level;
const auto* m_class_value = &last_child_value;
// Get class value matching the virtual method, and turn it into a
// bound method.
for (int i = 0; i < level_diff; ++i) {
CARBON_CHECK(m_class_value->base())
<< "Error trying to access function class value";
m_class_value = *m_class_value->base();
}
return arena->New<BoundMethodValue>(
cast<FunctionDeclaration>(virtual_method), m_class_value,
&class_type.bindings());
} else {
// Found a class function
// TODO: This should not be reachable.
@@ -255,15 +290,15 @@ static auto SetFieldImpl(
if (auto inits = SetFieldImpl(arena, &object.inits(), path_begin,
path_end, field_value, source_loc);
inits.ok()) {
return arena->New<NominalClassValue>(&object.type(), *inits,
object.base());
return arena->New<NominalClassValue>(
&object.type(), *inits, object.base(), object.class_value_ptr());
} else if (object.base().has_value()) {
auto new_base = SetFieldImpl(arena, object.base().value(), path_begin,
path_end, field_value, source_loc);
if (new_base.ok()) {
return arena->New<NominalClassValue>(
&object.type(), &object.inits(),
cast<NominalClassValue>(*new_base));
cast<NominalClassValue>(*new_base), object.class_value_ptr());
}
}
// Failed to match, show full object content