diff --git a/explorer/ast/declaration.h b/explorer/ast/declaration.h index 09f7a6d3766f..404b6e81a7da 100644 --- a/explorer/ast/declaration.h +++ b/explorer/ast/declaration.h @@ -253,14 +253,14 @@ class ClassDeclaration : public Declaration { Nonnull self_decl, ClassExtensibility extensibility, std::optional> type_params, - std::optional> extends, + std::optional> base, std::vector> 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> { return type_params_; } - auto extends() const -> std::optional> { - return extends_; + auto base_expr() const -> std::optional> { + return base_expr_; } auto self() const -> Nonnull { return self_decl_; } auto self() -> Nonnull { return self_decl_; } @@ -295,14 +295,22 @@ class ClassDeclaration : public Declaration { auto value_category() const -> ValueCategory { return ValueCategory::Let; } + auto base() const -> std::optional> { + return base_; + } + void set_base(Nonnull base_decl) { + base_ = base_decl; + } + private: std::string name_; ClassExtensibility extensibility_; Nonnull self_decl_; std::optional> type_params_; - std::optional> extends_; + std::optional> base_expr_; std::vector> members_; std::optional> destructor_; + std::optional> base_; }; // EXPERIMENTAL MIXIN FEATURE diff --git a/explorer/interpreter/resolve_names.cpp b/explorer/interpreter/resolve_names.cpp index dc754dc64158..6f32d6b5cd43 100644 --- a/explorer/interpreter/resolve_names.cpp +++ b/explorer/interpreter/resolve_names.cpp @@ -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)); diff --git a/explorer/interpreter/type_checker.cpp b/explorer/interpreter/type_checker.cpp index a0a98d86b4ec..540ccd1226c7 100644 --- a/explorer/interpreter/type_checker.cpp +++ b/explorer/interpreter/type_checker.cpp @@ -7,14 +7,21 @@ #include #include #include +#include #include +#include +#include +#include #include #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 curr_class_type = &class_type; + std::vector> 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 { std::vector field_types; - for (Nonnull m : class_type.declaration().members()) { - switch (m->kind()) { - case DeclarationKind::VariableDeclaration: { - const auto& var = cast(*m); - Nonnull 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 m : class_type->declaration().members()) { + switch (m->kind()) { + case DeclarationKind::VariableDeclaration: { + const auto& var = cast(*m); + Nonnull 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 e, case Value::Kind::NominalClassType: { const auto& t_class = cast(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 field_type = Substitute(t_class.bindings(), member_type); access.set_member(Member(member)); @@ -2413,9 +2433,9 @@ auto TypeChecker::TypeCheckExp(Nonnull e, const auto& class_type = cast(*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 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> base_class; + if (class_decl->base_expr().has_value()) { + Nonnull 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(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> bindings = scope_info.bindings; @@ -4090,7 +4134,7 @@ auto TypeChecker::DeclareClassDeclaration(Nonnull class_decl, // For class declaration `class MyType(T:! Type, U:! AnInterface)`, `Self` // should have the value `MyType(T, U)`. Nonnull self_type = arena_->New( - class_decl, Bindings::SymbolicIdentity(arena_, bindings)); + class_decl, Bindings::SymbolicIdentity(arena_, bindings), base_class); self->set_static_type(arena_->New()); self->set_constant_value(self_type); @@ -5079,9 +5123,25 @@ auto TypeChecker::DeclareDeclaration(Nonnull d, return Success(); } +auto TypeChecker::FindMemberWithParents( + std::string_view name, Nonnull class_type) + -> ErrorOr, Nonnull>>> { + 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> members, + const std::string_view& name, llvm::ArrayRef> members, const Nonnull enclosing_type) -> ErrorOr, Nonnull>>> { @@ -5091,8 +5151,7 @@ auto TypeChecker::FindMixedMemberAndType( Nonnull 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(enclosing_type)) { Bindings temp_map; diff --git a/explorer/interpreter/type_checker.h b/explorer/interpreter/type_checker.h index a2be2d238cdc..3258767b2eb0 100644 --- a/explorer/interpreter/type_checker.h +++ b/explorer/interpreter/type_checker.h @@ -60,14 +60,20 @@ class TypeChecker { SourceLocation source_loc) const -> std::optional>; + // Return the declaration of the member with the given name, from the class + // and its parents + auto FindMemberWithParents(std::string_view name, + Nonnull enclosing_type) + -> ErrorOr, Nonnull>>>; + // 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> members, Nonnull enclosing_type) -> ErrorOr #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, Nonnull v, // Look for a method in the object's class const auto& class_type = cast(object.type()); std::optional> 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, Nonnull v, // Access a class function. const auto& class_type = cast(*v); std::optional> 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> members) -> std::optional> { - for (const auto& member : declaration().members()) { + for (const auto& member : members) { switch (member->kind()) { case DeclarationKind::MixDeclaration: { const auto& mix_decl = cast(*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> { + 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> members) -> std::optional> { diff --git a/explorer/interpreter/value.h b/explorer/interpreter/value.h index 2ff2368e5cdc..493eb761e12d 100644 --- a/explorer/interpreter/value.h +++ b/explorer/interpreter/value.h @@ -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 declaration, - Nonnull bindings) + explicit NominalClassType( + Nonnull declaration, + Nonnull bindings, + std::optional> 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> { + 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>; - private: Nonnull declaration_; Nonnull bindings_ = Bindings::None(); + std::optional> base_; }; class MixinPseudoType : public Value { @@ -692,6 +695,18 @@ class MixinPseudoType : public Value { Nonnull 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> members) + -> std::optional>; + +// 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>; + // Return the declaration of the member with the given name. auto FindMember(std::string_view name, llvm::ArrayRef> members) diff --git a/explorer/testdata/class/class_inheritance_function_call.carbon b/explorer/testdata/class/class_inheritance_function_call.carbon new file mode 100644 index 000000000000..cfbfef99229a --- /dev/null +++ b/explorer/testdata/class/class_inheritance_function_call.carbon @@ -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; +} diff --git a/explorer/testdata/class/class_inheritance_methods.carbon b/explorer/testdata/class/class_inheritance_methods.carbon new file mode 100644 index 000000000000..132d8b92435a --- /dev/null +++ b/explorer/testdata/class/class_inheritance_methods.carbon @@ -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; +} diff --git a/explorer/testdata/class/class_inheritance_multiple.carbon b/explorer/testdata/class/class_inheritance_multiple.carbon new file mode 100644 index 000000000000..ff2da24132ee --- /dev/null +++ b/explorer/testdata/class/class_inheritance_multiple.carbon @@ -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; +} diff --git a/explorer/testdata/class/class_inheritance_shadow.carbon b/explorer/testdata/class/class_inheritance_shadow.carbon new file mode 100644 index 000000000000..06719377c2f7 --- /dev/null +++ b/explorer/testdata/class/class_inheritance_shadow.carbon @@ -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; +} diff --git a/explorer/testdata/class/class_inheritance_variables.carbon b/explorer/testdata/class/class_inheritance_variables.carbon new file mode 100644 index 000000000000..7c6b142db9cd --- /dev/null +++ b/explorer/testdata/class/class_inheritance_variables.carbon @@ -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; +} diff --git a/explorer/testdata/class/fail_abstract_class.carbon b/explorer/testdata/class/fail_abstract_class.carbon index d018e7c7cf65..12e6514608be 100644 --- a/explorer/testdata/class/fail_abstract_class.carbon +++ b/explorer/testdata/class/fail_abstract_class.carbon @@ -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 { diff --git a/explorer/testdata/class/fail_class_extends.carbon b/explorer/testdata/class/fail_extends_final_class.carbon similarity index 66% rename from explorer/testdata/class/fail_class_extends.carbon rename to explorer/testdata/class/fail_extends_final_class.carbon index 609deb1763bd..4cee9c1a6923 100644 --- a/explorer/testdata/class/fail_class_extends.carbon +++ b/explorer/testdata/class/fail_extends_final_class.carbon @@ -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 { diff --git a/explorer/testdata/class/fail_base_class.carbon b/explorer/testdata/class/fail_extends_non_class.carbon similarity index 76% rename from explorer/testdata/class/fail_base_class.carbon rename to explorer/testdata/class/fail_extends_non_class.carbon index cb73ae7febad..a0ecf1b263bb 100644 --- a/explorer/testdata/class/fail_base_class.carbon +++ b/explorer/testdata/class/fail_extends_non_class.carbon @@ -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 {