Explorer: support .base to initialize parent class from struct (#2361)

Relates to https://github.com/carbon-language/carbon-lang/issues/1881

- Add support for `.base` field in structs for [parent class initialization](https://github.com/carbon-language/carbon-lang/blob/trunk/docs/design/classes.md#constructors)
    - Disabling base class initialization without `.base`
- Support class constructors (`Create() -> Self`) for base classes
- Direct access to base class(es) attributes with `object.var` remains unaffected

Changes:
- Add `TypeChecker::FieldTypesWithBase` to help assessing if a struct with `base` fields can be converted to a class
- Add a new `base_type()` attribute+getter to `NominalClassDeclaration` to as a first step to allow resolving parametrized classes
- Add a new `base` attribute+getter to `NominalClassValue` that contains the base class `NominalClassValue`. It is currently used mainly to get and set members of a class object.
- Add `Interpreter::ConvertClassWithBase` to build `NominalClassValue` from a init struct, that contains `.base` fields with either `NominalClassValue` or `StructValue`
- Add `FindClassField` to find a field in a class or its base classes
- Remove superfluous `ClassDeclaration::base()` in favor of `ClassDeclaration::base_type()`

Limitations;
- Though some work is done in that direction, parametrized base class where time is not know at the declaration site are not supported. Namely the example below does not compile
```
base class A(T:! Type) {}
class B(T:! Type) extends A(T) {}
```
But this one is functional already
```
base class A(T:! Type) {}
class B extends A(i32) {}
```

Co-authored-by: Richard Smith <richard@metafoo.co.uk>
This commit is contained in:
Adrien Leravat
2022-11-29 13:14:30 -08:00
committed by GitHub
co-authored by Richard Smith
parent 84deb62aef
commit 46f4887cf7
18 changed files with 350 additions and 96 deletions
+38 -7
View File
@@ -5,8 +5,11 @@
#include "explorer/interpreter/value.h"
#include <algorithm>
#include <optional>
#include <string_view>
#include "common/check.h"
#include "common/error.h"
#include "explorer/ast/declaration.h"
#include "explorer/common/arena.h"
#include "explorer/common/error_builders.h"
@@ -33,6 +36,18 @@ auto StructValue::FindField(std::string_view name) const
return std::nullopt;
}
static auto FindClassField(Nonnull<const NominalClassValue*> object,
std::string_view name)
-> std::optional<Nonnull<const Value*>> {
if (auto field = cast<StructValue>(object->inits()).FindField(name)) {
return field;
}
if (object->base().has_value()) {
return FindClassField(object->base().value(), name);
}
return std::nullopt;
}
static auto GetMember(Nonnull<Arena*> arena, Nonnull<const Value*> v,
const FieldPath::Component& field,
SourceLocation source_loc, Nonnull<const Value*> me_value)
@@ -89,7 +104,7 @@ static auto GetMember(Nonnull<Arena*> arena, Nonnull<const Value*> v,
const auto& object = cast<NominalClassValue>(*v);
// Look for a field.
if (std::optional<Nonnull<const Value*>> field =
cast<StructValue>(object.inits()).FindField(f)) {
FindClassField(&object, f)) {
return *field;
} else {
// Look for a method in the object's class
@@ -176,10 +191,23 @@ static auto SetFieldImpl(
}
case Value::Kind::NominalClassValue: {
const auto& object = cast<NominalClassValue>(*value);
CARBON_ASSIGN_OR_RETURN(Nonnull<const Value*> inits,
SetFieldImpl(arena, &object.inits(), path_begin,
path_end, field_value, source_loc));
return arena->New<NominalClassValue>(&object.type(), inits);
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());
} 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));
}
}
// Failed to match, show full object content
return ProgramError(source_loc)
<< "field " << (*path_begin).name() << " not in " << *value;
}
case Value::Kind::TupleType:
case Value::Kind::TupleValue: {
@@ -271,6 +299,9 @@ void Value::Print(llvm::raw_ostream& out) const {
case Value::Kind::NominalClassValue: {
const auto& s = cast<NominalClassValue>(*this);
out << cast<NominalClassType>(s.type()).declaration().name() << s.inits();
if (s.base().has_value()) {
out << " base " << *s.base().value();
}
break;
}
case Value::Kind::TupleType:
@@ -1066,8 +1097,8 @@ auto FindFunctionWithParents(std::string_view name,
if (auto fun = FindFunction(name, class_decl.members()); fun.has_value()) {
return fun;
}
if (class_decl.base().has_value()) {
return FindFunctionWithParents(name, *class_decl.base().value());
if (const auto base_type = class_decl.base_type(); base_type.has_value()) {
return FindFunctionWithParents(name, base_type.value()->declaration());
}
return std::nullopt;
}