Explorer: Support class extension (#1946)

Support class extension with `extends`
Relates to #1881 

Add support for:
* Class extension with `extends`
* Using base class methods and members
This commit is contained in:
Adrien Leravat
2022-10-21 08:08:07 -07:00
committed by GitHub
parent d76835bed4
commit 7c19ef5be3
14 changed files with 353 additions and 56 deletions
+13 -5
View File
@@ -253,14 +253,14 @@ class ClassDeclaration : public Declaration {
Nonnull<SelfDeclaration*> self_decl,
ClassExtensibility extensibility,
std::optional<Nonnull<TuplePattern*>> type_params,
std::optional<Nonnull<Expression*>> extends,
std::optional<Nonnull<Expression*>> base,
std::vector<Nonnull<Declaration*>> members)
: Declaration(AstNodeKind::ClassDeclaration, source_loc),
name_(std::move(name)),
extensibility_(extensibility),
self_decl_(self_decl),
type_params_(type_params),
extends_(extends),
base_expr_(base),
members_(std::move(members)) {}
static auto classof(const AstNode* node) -> bool {
@@ -275,8 +275,8 @@ class ClassDeclaration : public Declaration {
auto type_params() -> std::optional<Nonnull<TuplePattern*>> {
return type_params_;
}
auto extends() const -> std::optional<Nonnull<Expression*>> {
return extends_;
auto base_expr() const -> std::optional<Nonnull<Expression*>> {
return base_expr_;
}
auto self() const -> Nonnull<const SelfDeclaration*> { return self_decl_; }
auto self() -> Nonnull<SelfDeclaration*> { return self_decl_; }
@@ -295,14 +295,22 @@ class ClassDeclaration : public Declaration {
auto value_category() const -> ValueCategory { return ValueCategory::Let; }
auto base() const -> std::optional<Nonnull<const ClassDeclaration*>> {
return base_;
}
void set_base(Nonnull<const ClassDeclaration*> base_decl) {
base_ = base_decl;
}
private:
std::string name_;
ClassExtensibility extensibility_;
Nonnull<SelfDeclaration*> self_decl_;
std::optional<Nonnull<TuplePattern*>> type_params_;
std::optional<Nonnull<Expression*>> extends_;
std::optional<Nonnull<Expression*>> base_expr_;
std::vector<Nonnull<Declaration*>> members_;
std::optional<Nonnull<FunctionDeclaration*>> destructor_;
std::optional<Nonnull<const ClassDeclaration*>> base_;
};
// EXPERIMENTAL MIXIN FEATURE
+4
View File
@@ -577,6 +577,10 @@ static auto ResolveNames(Declaration& declaration, StaticScope& enclosing_scope,
StaticScope class_scope;
class_scope.AddParent(&enclosing_scope);
enclosing_scope.MarkDeclared(class_decl.name());
if (class_decl.base_expr().has_value()) {
CARBON_RETURN_IF_ERROR(
ResolveNames(**class_decl.base_expr(), class_scope));
}
if (class_decl.type_params().has_value()) {
CARBON_RETURN_IF_ERROR(
ResolveNames(**class_decl.type_params(), class_scope));
+88 -29
View File
@@ -7,14 +7,21 @@
#include <algorithm>
#include <iterator>
#include <map>
#include <optional>
#include <set>
#include <string>
#include <string_view>
#include <unordered_set>
#include <vector>
#include "common/error.h"
#include "common/ostream.h"
#include "explorer/ast/declaration.h"
#include "explorer/ast/expression.h"
#include "explorer/common/arena.h"
#include "explorer/common/error_builders.h"
#include "explorer/common/nonnull.h"
#include "explorer/common/source_location.h"
#include "explorer/interpreter/impl_scope.h"
#include "explorer/interpreter/interpreter.h"
#include "explorer/interpreter/pattern_analysis.h"
@@ -366,21 +373,35 @@ auto TypeChecker::FieldTypesImplicitlyConvertible(
return true;
}
// Returns all class members from class and its parent classes.
static auto GetClassHierarchy(const NominalClassType& class_type)
-> std::vector<Nonnull<const NominalClassType*>> {
Nonnull<const NominalClassType*> curr_class_type = &class_type;
std::vector<Nonnull<const NominalClassType*>> all_classes{curr_class_type};
while (curr_class_type->base().has_value()) {
curr_class_type = curr_class_type->base().value();
all_classes.push_back(curr_class_type);
}
return all_classes;
}
auto TypeChecker::FieldTypes(const NominalClassType& class_type) const
-> std::vector<NamedValue> {
std::vector<NamedValue> field_types;
for (Nonnull<Declaration*> m : class_type.declaration().members()) {
switch (m->kind()) {
case DeclarationKind::VariableDeclaration: {
const auto& var = cast<VariableDeclaration>(*m);
Nonnull<const Value*> field_type =
Substitute(class_type.bindings(), &var.binding().static_type());
field_types.push_back(
{.name = var.binding().name(), .value = field_type});
break;
for (const auto class_type : GetClassHierarchy(class_type)) {
for (Nonnull<Declaration*> m : class_type->declaration().members()) {
switch (m->kind()) {
case DeclarationKind::VariableDeclaration: {
const auto& var = cast<VariableDeclaration>(*m);
Nonnull<const Value*> field_type =
Substitute(class_type->bindings(), &var.binding().static_type());
field_types.push_back(
{.name = var.binding().name(), .value = field_type});
break;
}
default:
break;
}
default:
break;
}
}
return field_types;
@@ -2194,11 +2215,10 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
case Value::Kind::NominalClassType: {
const auto& t_class = cast<NominalClassType>(object_type);
CARBON_ASSIGN_OR_RETURN(
auto type_member, FindMixedMemberAndType(
access.source_loc(), access.member_name(),
t_class.declaration().members(), &t_class));
if (type_member.has_value()) {
auto [member_type, member] = type_member.value();
const auto res,
FindMemberWithParents(access.member_name(), &t_class));
if (res.has_value()) {
auto [member_type, member] = res.value();
Nonnull<const Value*> field_type =
Substitute(t_class.bindings(), member_type);
access.set_member(Member(member));
@@ -2413,9 +2433,9 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
const auto& class_type = cast<NominalClassType>(*type);
CARBON_ASSIGN_OR_RETURN(
auto type_member,
FindMixedMemberAndType(
access.source_loc(), access.member_name(),
class_type.declaration().members(), &class_type));
FindMixedMemberAndType(access.member_name(),
class_type.declaration().members(),
&class_type));
if (type_member.has_value()) {
auto [member_type, member] = type_member.value();
access.set_member(Member(member));
@@ -4067,13 +4087,37 @@ auto TypeChecker::DeclareClassDeclaration(Nonnull<ClassDeclaration*> class_decl,
ImplScope class_scope;
class_scope.AddParent(scope_info.innermost_scope);
if (class_decl->extensibility() != ClassExtensibility::None) {
if (class_decl->extensibility() == ClassExtensibility::Abstract) {
return ProgramError(class_decl->source_loc())
<< "Class prefixes `base` and `abstract` are not supported yet";
<< "Class prefix `abstract` is not supported yet";
}
if (class_decl->extends()) {
return ProgramError(class_decl->source_loc())
<< "Class extension with `extends` is not supported yet";
std::optional<Nonnull<const NominalClassType*>> base_class;
if (class_decl->base_expr().has_value()) {
Nonnull<Expression*> base_class_expr = *class_decl->base_expr();
CARBON_ASSIGN_OR_RETURN(const auto base_type,
TypeCheckTypeExp(base_class_expr, class_scope));
switch (base_type->kind()) {
case Value::Kind::NominalClassType:
base_class = cast<NominalClassType>(base_type);
if (base_class.value()->declaration().extensibility() ==
ClassExtensibility::None) {
return ProgramError(class_decl->source_loc())
<< "Base class `" << base_class.value()->declaration().name()
<< "` is `final` and cannot inherited. Add the `base` or "
"`abstract` class prefix to `"
<< base_class.value()->declaration().name()
<< "` to allow it to be inherited";
}
class_decl->set_base(&base_class.value()->declaration());
break;
default:
return ProgramError(class_decl->source_loc())
<< "Unsupported base class type for class `"
<< class_decl->name()
<< "`. Only simple classes are currently supported as base "
"class.";
}
}
std::vector<Nonnull<const GenericBinding*>> bindings = scope_info.bindings;
@@ -4090,7 +4134,7 @@ auto TypeChecker::DeclareClassDeclaration(Nonnull<ClassDeclaration*> class_decl,
// For class declaration `class MyType(T:! Type, U:! AnInterface)`, `Self`
// should have the value `MyType(T, U)`.
Nonnull<NominalClassType*> self_type = arena_->New<NominalClassType>(
class_decl, Bindings::SymbolicIdentity(arena_, bindings));
class_decl, Bindings::SymbolicIdentity(arena_, bindings), base_class);
self->set_static_type(arena_->New<TypeType>());
self->set_constant_value(self_type);
@@ -5079,9 +5123,25 @@ auto TypeChecker::DeclareDeclaration(Nonnull<Declaration*> d,
return Success();
}
auto TypeChecker::FindMemberWithParents(
std::string_view name, Nonnull<const NominalClassType*> class_type)
-> ErrorOr<std::optional<
std::pair<Nonnull<const Value*>, Nonnull<const Declaration*>>>> {
CARBON_ASSIGN_OR_RETURN(
const auto res,
FindMixedMemberAndType(name, class_type->declaration().members(),
class_type));
if (res.has_value()) {
return res;
}
if (const auto base = class_type->base(); base.has_value()) {
return FindMemberWithParents(name, base.value());
}
return {std::nullopt};
}
auto TypeChecker::FindMixedMemberAndType(
SourceLocation source_loc, const std::string_view& name,
llvm::ArrayRef<Nonnull<Declaration*>> members,
const std::string_view& name, llvm::ArrayRef<Nonnull<Declaration*>> members,
const Nonnull<const Value*> enclosing_type)
-> ErrorOr<std::optional<
std::pair<Nonnull<const Value*>, Nonnull<const Declaration*>>>> {
@@ -5091,8 +5151,7 @@ auto TypeChecker::FindMixedMemberAndType(
Nonnull<const MixinPseudoType*> mixin = &mix_decl.mixin_value();
CARBON_ASSIGN_OR_RETURN(
const auto res,
FindMixedMemberAndType(source_loc, name,
mixin->declaration().members(), mixin));
FindMixedMemberAndType(name, mixin->declaration().members(), mixin));
if (res.has_value()) {
if (isa<NominalClassType>(enclosing_type)) {
Bindings temp_map;
+8 -2
View File
@@ -60,14 +60,20 @@ class TypeChecker {
SourceLocation source_loc) const
-> std::optional<Nonnull<const Witness*>>;
// Return the declaration of the member with the given name, from the class
// and its parents
auto FindMemberWithParents(std::string_view name,
Nonnull<const NominalClassType*> enclosing_type)
-> ErrorOr<std::optional<
std::pair<Nonnull<const Value*>, Nonnull<const Declaration*>>>>;
// Finds the direct or indirect member of a class or mixin by its name and
// returns the member's declaration and type. Indirect members are members of
// mixins that are mixed by member mix declarations. If the member is an
// indirect member from a mix declaration, then the Self type variable within
// the member's type is substituted with the type of the enclosing declaration
// containing the mix declaration.
auto FindMixedMemberAndType(SourceLocation source_loc,
const std::string_view& name,
auto FindMixedMemberAndType(const std::string_view& name,
llvm::ArrayRef<Nonnull<Declaration*>> members,
Nonnull<const Value*> enclosing_type)
-> ErrorOr<std::optional<
+18 -4
View File
@@ -7,6 +7,7 @@
#include <algorithm>
#include "common/check.h"
#include "explorer/ast/declaration.h"
#include "explorer/common/arena.h"
#include "explorer/common/error_builders.h"
#include "explorer/interpreter/action.h"
@@ -98,7 +99,7 @@ static auto GetMember(Nonnull<Arena*> arena, Nonnull<const Value*> v,
// Look for a method in the object's class
const auto& class_type = cast<NominalClassType>(object.type());
std::optional<Nonnull<const FunctionValue*>> func =
class_type.FindFunction(f);
FindFunctionWithParents(f, class_type.declaration());
if (!func) {
return ProgramError(source_loc) << "member " << f << " not in " << *v
<< " or its " << class_type;
@@ -127,7 +128,7 @@ static auto GetMember(Nonnull<Arena*> arena, Nonnull<const Value*> v,
// Access a class function.
const auto& class_type = cast<NominalClassType>(*v);
std::optional<Nonnull<const FunctionValue*>> fun =
class_type.FindFunction(f);
FindFunctionWithParents(f, class_type.declaration());
if (fun == std::nullopt) {
return ProgramError(source_loc)
<< "class function " << f << " not in " << *v;
@@ -979,9 +980,10 @@ auto ChoiceType::FindAlternative(std::string_view name) const
return std::nullopt;
}
auto NominalClassType::FindFunction(std::string_view name) const
auto FindFunction(std::string_view name,
llvm::ArrayRef<Nonnull<Declaration*>> members)
-> std::optional<Nonnull<const FunctionValue*>> {
for (const auto& member : declaration().members()) {
for (const auto& member : members) {
switch (member->kind()) {
case DeclarationKind::MixDeclaration: {
const auto& mix_decl = cast<MixDeclaration>(*member);
@@ -1034,6 +1036,18 @@ auto MixinPseudoType::FindFunction(const std::string_view& name) const
return std::nullopt;
}
auto FindFunctionWithParents(std::string_view name,
const ClassDeclaration& class_decl)
-> std::optional<Nonnull<const FunctionValue*>> {
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());
}
return std::nullopt;
}
auto FindMember(std::string_view name,
llvm::ArrayRef<Nonnull<Declaration*>> members)
-> std::optional<Nonnull<const Declaration*>> {
+23 -8
View File
@@ -620,11 +620,14 @@ class NominalClassType : public Value {
// Construct a fully instantiated generic class type to represent the
// run-time type of an object.
explicit NominalClassType(Nonnull<const ClassDeclaration*> declaration,
Nonnull<const Bindings*> bindings)
explicit NominalClassType(
Nonnull<const ClassDeclaration*> declaration,
Nonnull<const Bindings*> bindings,
std::optional<Nonnull<const NominalClassType*>> base = std::nullopt)
: Value(Kind::NominalClassType),
declaration_(declaration),
bindings_(bindings) {}
bindings_(bindings),
base_(base) {}
static auto classof(const Value* value) -> bool {
return value->kind() == Kind::NominalClassType;
@@ -634,6 +637,10 @@ class NominalClassType : public Value {
auto bindings() const -> const Bindings& { return *bindings_; }
auto base() const -> std::optional<Nonnull<const NominalClassType*>> {
return base_;
}
auto type_args() const -> const BindingMap& { return bindings_->args(); }
// Witnesses for each of the class's impl bindings.
@@ -647,14 +654,10 @@ class NominalClassType : public Value {
return declaration_->type_params().has_value() && type_args().empty();
}
// Returns the value of the function named `name` in this class, or
// nullopt if there is no such function.
auto FindFunction(std::string_view name) const
-> std::optional<Nonnull<const FunctionValue*>>;
private:
Nonnull<const ClassDeclaration*> declaration_;
Nonnull<const Bindings*> bindings_ = Bindings::None();
std::optional<Nonnull<const NominalClassType*>> base_;
};
class MixinPseudoType : public Value {
@@ -692,6 +695,18 @@ class MixinPseudoType : public Value {
Nonnull<const Bindings*> bindings_ = Bindings::None();
};
// Returns the value of the function named `name` in this class, or
// nullopt if there is no such function.
auto FindFunction(std::string_view name,
llvm::ArrayRef<Nonnull<Declaration*>> members)
-> std::optional<Nonnull<const FunctionValue*>>;
// Returns the value of the function named `name` in this class and its
// parents, or nullopt if there is no such function.
auto FindFunctionWithParents(std::string_view name,
const ClassDeclaration& class_decl)
-> std::optional<Nonnull<const FunctionValue*>>;
// Return the declaration of the member with the given name.
auto FindMember(std::string_view name,
llvm::ArrayRef<Nonnull<Declaration*>> members)
@@ -0,0 +1,40 @@
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
// AUTOUPDATE
// RUN: %{explorer-run}
// RUN: %{explorer-run-trace}
// CHECK:STDOUT: Class C, call 0
// CHECK:STDOUT: Class D, call 0
// CHECK:STDOUT: Class C, call 1
// CHECK:STDOUT: Class C, call 2
// CHECK:STDOUT: Class D, call 2
// CHECK:STDOUT: result: 0
package ExplorerTest api;
base class C {
fn FunctionC(var i: i32) {
Print("Class C, call {0}", i);
}
}
class D extends C {
fn FunctionD(var i: i32) {
C.FunctionC(i);
Print("Class D, call {0}", i);
}
}
fn Main() -> i32 {
// Calling function from class type
D.FunctionD(0);
// Note: D.FunctionC(0); is not accessible
// Calling function from class object
var d: D = {};
d.FunctionC(1);
d.FunctionD(2);
return 0;
}
@@ -0,0 +1,38 @@
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
// AUTOUPDATE
// RUN: %{explorer-run}
// RUN: %{explorer-run-trace}
// CHECK:STDOUT: Class 1
// CHECK:STDOUT: Class 2
// CHECK:STDOUT: result: 0
package ExplorerTest api;
base class C {
fn BasePrint(v: i32) {
Print("Class {0}", v);
}
fn Method1[me:Self]() {
me.BasePrint(me.value_c);
}
var value_c: i32;
}
class D extends C {
fn Method2[me:Self]() {
me.BasePrint(me.value_d);
}
var value_d: i32;
}
fn Main() -> i32 {
var d: D = {.value_c = 1, .value_d = 2};
d.Method1();
d.Method2();
return 0;
}
@@ -0,0 +1,33 @@
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
// AUTOUPDATE
// RUN: %{explorer-run}
// RUN: %{explorer-run-trace}
// CHECK:STDOUT: result: 0
package ExplorerTest api;
base class A {
fn FunctionA() {}
var var_a: i32;
}
base class B extends A {
fn FunctionB() {}
var var_b: i32;
}
class C extends B {
fn FunctionC() {}
var var_c: i32;
}
fn Main() -> i32 {
var c: C = {.var_a=1, .var_b=2, .var_c=3};
c.FunctionA();
c.FunctionB();
c.FunctionC();
return 0;
}
+40
View File
@@ -0,0 +1,40 @@
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
// AUTOUPDATE
// RUN: %{explorer-run}
// RUN: %{explorer-run-trace}
// CHECK:STDOUT: Class D
// CHECK:STDOUT: d.value = 2
// CHECK:STDOUT: d.GetValue() = 2
// CHECK:STDOUT: result: 0
package ExplorerTest api;
base class C {
fn Method1() {
Print("Class C");
}
fn GetValue[me: Self]() -> i32 {
return me.value;
}
var value: i32;
}
class D extends C {
fn Method1() {
Print("Class D");
}
var value: i32;
}
fn Main() -> i32 {
// Initialize derived value first, base value second
var d: D = {.value = 2, .value = 1};
d.Method1();
Print("d.value = {0}", d.value);
Print("d.GetValue() = {0}", d.GetValue());
return 0;
}
@@ -0,0 +1,39 @@
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
// AUTOUPDATE
// RUN: %{explorer-run}
// RUN: %{explorer-run-trace}
// CHECK:STDOUT: Read var_c=1
// CHECK:STDOUT: Read var_d=2
// CHECK:STDOUT: Assign var_c=3
// CHECK:STDOUT: Assign var_d=4
// CHECK:STDOUT: result: 0
package ExplorerTest api;
base class C {
var var_c: i32;
}
class D extends C {
var var_d: i32;
}
fn Main() -> i32 {
// Initialization
var d: D = {.var_c= 1, .var_d= 2};
// Read
Print("Read var_c={0}", d.var_c);
Print("Read var_d={0}", d.var_d);
// Assignment
d.var_c = 3;
d.var_d = 4;
Print("Assign var_c={0}", d.var_c);
Print("Assign var_d={0}", d.var_d);
return 0;
}
+1 -1
View File
@@ -10,7 +10,7 @@ package ExplorerTest api;
abstract class C {
fn F() {}
// CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/class/fail_abstract_class.carbon:[[@LINE+1]]: Class prefixes `base` and `abstract` are not supported yet
// CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/class/fail_abstract_class.carbon:[[@LINE+1]]: Class prefix `abstract` is not supported yet
}
fn Main() -> i32 {
@@ -8,13 +8,13 @@
package ExplorerTest api;
class A {
class C {
fn F() {}
}
class B extends A {
fn F() {}
// CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/class/fail_class_extends.carbon:[[@LINE+1]]: Class extension with `extends` is not supported yet
class D extends C {
fn G() {}
// CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/class/fail_extends_final_class.carbon:[[@LINE+1]]: Base class `C` is `final` and cannot inherited. Add the `base` or `abstract` class prefix to `C` to allow it to be inherited
}
fn Main() -> i32 {
@@ -8,9 +8,10 @@
package ExplorerTest api;
base class C {
fn F() {}
// CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/class/fail_base_class.carbon:[[@LINE+1]]: Class prefixes `base` and `abstract` are not supported yet
// CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/class/fail_extends_non_class.carbon:[[@LINE+1]]: Expected a type, but got 3
class C extends 3 {
var x: i32;
var y: i32;
}
fn Main() -> i32 {