From 46f4887cf7ca90934548d048c282977218eda4cd Mon Sep 17 00:00:00 2001 From: Adrien Leravat Date: Tue, 29 Nov 2022 13:14:30 -0800 Subject: [PATCH] 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 --- explorer/ast/declaration.h | 20 +-- explorer/interpreter/BUILD | 2 + explorer/interpreter/interpreter.cpp | 61 +++++++-- explorer/interpreter/type_checker.cpp | 118 +++++++++--------- explorer/interpreter/type_checker.h | 13 +- explorer/interpreter/value.cpp | 45 +++++-- explorer/interpreter/value.h | 16 ++- explorer/syntax/parser.ypp | 4 +- .../testdata/class/base_constructor.carbon | 33 +++++ .../class_inheritance_function_call.carbon | 2 +- .../class/class_inheritance_methods.carbon | 2 +- .../class/class_inheritance_multiple.carbon | 17 ++- .../class/class_inheritance_shadow.carbon | 2 +- .../class/class_inheritance_variables.carbon | 2 +- .../class/fail_direct_base_class_init.carbon | 23 ++++ .../fail_field_access_mismatch_base.carbon | 24 ++++ .../fail_field_assign_mismatch_base.carbon | 25 ++++ .../class/parametrized_base_class.carbon | 37 ++++++ 18 files changed, 350 insertions(+), 96 deletions(-) create mode 100644 explorer/testdata/class/base_constructor.carbon create mode 100644 explorer/testdata/class/fail_direct_base_class_init.carbon create mode 100644 explorer/testdata/class/fail_field_access_mismatch_base.carbon create mode 100644 explorer/testdata/class/fail_field_assign_mismatch_base.carbon create mode 100644 explorer/testdata/class/parametrized_base_class.carbon diff --git a/explorer/ast/declaration.h b/explorer/ast/declaration.h index e5522257b924..cd8a2a759e89 100644 --- a/explorer/ast/declaration.h +++ b/explorer/ast/declaration.h @@ -28,6 +28,7 @@ namespace Carbon { class MixinPseudoType; class ConstraintType; +class NominalClassType; // Abstract base class of all AST nodes representing patterns. // @@ -275,9 +276,6 @@ class ClassDeclaration : public Declaration { auto type_params() -> std::optional> { return type_params_; } - auto base_expr() const -> std::optional> { - return base_expr_; - } auto self() const -> Nonnull { return self_decl_; } auto self() -> Nonnull { return self_decl_; } @@ -295,11 +293,18 @@ class ClassDeclaration : public Declaration { auto value_category() const -> ValueCategory { return ValueCategory::Let; } - auto base() const -> std::optional> { - return base_; + auto base_expr() const -> std::optional> { + return base_expr_; } - void set_base(Nonnull base_decl) { - base_ = base_decl; + + // Returns the original base type, before instantiation & substitutions + // Use `NominalClassType::base()` to get the instantiated type. + auto base_type() const -> std::optional> { + return base_type_; + } + void set_base_type( + std::optional> base_type) { + base_type_ = base_type; } private: @@ -311,6 +316,7 @@ class ClassDeclaration : public Declaration { std::vector> members_; std::optional> destructor_; std::optional> base_; + std::optional> base_type_; }; // EXPERIMENTAL MIXIN FEATURE diff --git a/explorer/interpreter/BUILD b/explorer/interpreter/BUILD index 3b40bd74d06c..cbacea5419b7 100644 --- a/explorer/interpreter/BUILD +++ b/explorer/interpreter/BUILD @@ -23,6 +23,7 @@ cc_library( ":heap_allocation_interface", ":stack", "//common:check", + "//common:error", "//common:ostream", "//explorer/ast", "//explorer/common:arena", @@ -193,6 +194,7 @@ cc_library( "@llvm-project//llvm:Support", ], ) + cc_library( name = "type_checker", srcs = [ diff --git a/explorer/interpreter/interpreter.cpp b/explorer/interpreter/interpreter.cpp index 11591b7ae1d2..853faaa97d02 100644 --- a/explorer/interpreter/interpreter.cpp +++ b/explorer/interpreter/interpreter.cpp @@ -22,6 +22,7 @@ #include "explorer/interpreter/action.h" #include "explorer/interpreter/action_stack.h" #include "explorer/interpreter/stack.h" +#include "explorer/interpreter/value.h" #include "llvm/ADT/StringExtras.h" #include "llvm/Support/Casting.h" #include "llvm/Support/Error.h" @@ -104,6 +105,12 @@ class Interpreter { Nonnull destination_type, SourceLocation source_loc) -> ErrorOr>; + // Create a class value and its base class(es) from an init struct. + auto ConvertStructToClass(Nonnull init, + Nonnull class_type, + SourceLocation source_loc) + -> ErrorOr>; + // Evaluate an expression immediately, recursively, and return its result. // // TODO: Stop using this. @@ -610,10 +617,17 @@ auto Interpreter::InstantiateType(Nonnull type, } case Value::Kind::NominalClassType: { const auto& class_type = cast(*type); + std::optional> base = class_type.base(); + if (base.has_value()) { + CARBON_ASSIGN_OR_RETURN(const auto inst_base, + InstantiateType(base.value(), source_loc)); + base = cast(inst_base); + } CARBON_ASSIGN_OR_RETURN( Nonnull bindings, InstantiateBindings(&class_type.bindings(), source_loc)); - return arena_->New(&class_type.declaration(), bindings); + return arena_->New(&class_type.declaration(), bindings, + base); } case Value::Kind::ChoiceType: { const auto& choice_type = cast(*type); @@ -661,6 +675,35 @@ auto Interpreter::InstantiateWitness(Nonnull witness) return cast(value); } +auto Interpreter::ConvertStructToClass( + Nonnull init_struct, + Nonnull class_type, SourceLocation source_loc) + -> ErrorOr> { + std::vector struct_values; + std::optional> base_instance; + // Instantiate the `destination_type` to obtain the runtime + // type of the object. + CARBON_ASSIGN_OR_RETURN(Nonnull inst_class, + InstantiateType(class_type, source_loc)); + for (const auto& field : init_struct->elements()) { + if (field.name == NominalClassValue::BaseField) { + CARBON_CHECK(class_type->base().has_value()) + << "Invalid 'base' field for class '" + << class_type->declaration().name() << "' without base class."; + CARBON_ASSIGN_OR_RETURN( + auto base, + Convert(field.value, class_type->base().value(), source_loc)); + base_instance = cast(base); + } else { + struct_values.push_back(field); + } + } + auto* converted_init_struct = + arena_->New(std::move(struct_values)); + return arena_->New(inst_class, converted_init_struct, + base_instance); +} + auto Interpreter::Convert(Nonnull value, Nonnull destination_type, SourceLocation source_loc) @@ -730,12 +773,12 @@ auto Interpreter::Convert(Nonnull value, return arena_->New(std::move(new_elements)); } case Value::Kind::NominalClassType: { - // Instantiate the `destination_type` to obtain the runtime - // type of the object. CARBON_ASSIGN_OR_RETURN( - Nonnull inst_dest, - InstantiateType(destination_type, source_loc)); - return arena_->New(inst_dest, value); + auto class_value, + ConvertStructToClass(cast(value), + cast(destination_type), + source_loc)); + return class_value; } case Value::Kind::TypeType: case Value::Kind::ConstraintType: @@ -962,9 +1005,11 @@ auto Interpreter::CallFunction(const CallExpression& call, Nonnull bindings = arena_->New(std::move(generic_args), std::move(witnesses)); switch (decl.kind()) { - case DeclarationKind::ClassDeclaration: + case DeclarationKind::ClassDeclaration: { + const auto& class_decl = cast(decl); return todo_.FinishAction(arena_->New( - &cast(decl), bindings)); + &class_decl, bindings, class_decl.base_type())); + } case DeclarationKind::InterfaceDeclaration: return todo_.FinishAction(arena_->New( &cast(decl), bindings)); diff --git a/explorer/interpreter/type_checker.cpp b/explorer/interpreter/type_checker.cpp index 78c153fbf191..3793b47776b3 100644 --- a/explorer/interpreter/type_checker.cpp +++ b/explorer/interpreter/type_checker.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -426,40 +427,37 @@ 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 (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; + 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; } } return field_types; } +auto TypeChecker::FieldTypesWithBase(const NominalClassType& class_type) const + -> std::vector { + auto fields = FieldTypes(class_type); + if (const auto base_type = class_type.base()) { + auto base_fields = FieldTypesWithBase(*base_type.value()); + fields.emplace_back(NamedValue{std::string(NominalClassValue::BaseField), + base_type.value()}); + } + return fields; +} + auto TypeChecker::IsImplicitlyConvertible( Nonnull source, Nonnull destination, const ImplScope& impl_scope, bool allow_user_defined_conversions) const @@ -484,10 +482,11 @@ auto TypeChecker::IsImplicitlyConvertible( } break; case Value::Kind::NominalClassType: - if (FieldTypesImplicitlyConvertible( - cast(*source).fields(), - FieldTypes(cast(*destination)), - impl_scope)) { + if (IsImplicitlyConvertible( + source, + arena_->New( + FieldTypesWithBase(cast(*destination))), + impl_scope, allow_user_defined_conversions)) { return true; } break; @@ -1897,10 +1896,15 @@ auto TypeChecker::SubstituteImpl(const Bindings& bindings, } case Value::Kind::NominalClassType: { const auto& class_type = cast(*type); + auto base_type = class_type.base(); + if (base_type.has_value()) { + base_type = cast( + SubstituteImpl(base_type.value()->bindings(), base_type.value())); + } Nonnull new_class_type = arena_->New( &class_type.declaration(), - substitute_into_bindings(&class_type.bindings())); + substitute_into_bindings(&class_type.bindings()), base_type); return new_class_type; } case Value::Kind::InterfaceType: { @@ -2567,9 +2571,9 @@ auto TypeChecker::TypeCheckExp(Nonnull e, const auto res, FindMemberWithParents(access.member_name(), &t_class)); if (res.has_value()) { - auto [member_type, member] = res.value(); + auto [member_type, member, member_t_class] = res.value(); Nonnull field_type = - Substitute(t_class.bindings(), member_type); + Substitute(member_t_class->bindings(), member_type); access.set_member(Member(member)); access.set_static_type(field_type); access.set_is_type_access(!IsInstanceMember(access.member())); @@ -4510,27 +4514,24 @@ auto TypeChecker::DeclareClassDeclaration(Nonnull class_decl, 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."; + if (base_type->kind() != Value::Kind::NominalClassType) { + return ProgramError(class_decl->source_loc()) + << "Unsupported base class type for class `" << class_decl->name() + << "`. Only simple classes are currently supported as base " + "class."; } + + 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_type(base_class); } std::vector> bindings = scope_info.bindings; @@ -5599,17 +5600,18 @@ auto TypeChecker::DeclareDeclaration(Nonnull d, } auto TypeChecker::FindMemberWithParents( - std::string_view name, Nonnull class_type) + std::string_view name, Nonnull enclosing_class) -> ErrorOr, Nonnull>>> { + std::tuple, Nonnull, + Nonnull>>> { CARBON_ASSIGN_OR_RETURN( const auto res, - FindMixedMemberAndType(name, class_type->declaration().members(), - class_type)); + FindMixedMemberAndType(name, enclosing_class->declaration().members(), + enclosing_class)); if (res.has_value()) { - return res; + return {std::make_tuple(res->first, res->second, enclosing_class)}; } - if (const auto base = class_type->base(); base.has_value()) { + if (const auto base = enclosing_class->base(); base.has_value()) { return FindMemberWithParents(name, base.value()); } return {std::nullopt}; diff --git a/explorer/interpreter/type_checker.h b/explorer/interpreter/type_checker.h index ba9b89b2fd9f..ea1ac7338084 100644 --- a/explorer/interpreter/type_checker.h +++ b/explorer/interpreter/type_checker.h @@ -60,12 +60,13 @@ class TypeChecker { SourceLocation source_loc) const -> std::optional>; - // Return the declaration of the member with the given name, from the class - // and its parents + // Return the declaration of the member with the given name and the class type + // that owns it, from the class and its parents auto FindMemberWithParents(std::string_view name, - Nonnull enclosing_type) + Nonnull enclosing_class) -> ErrorOr, Nonnull>>>; + std::tuple, Nonnull, + 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 @@ -345,6 +346,10 @@ class TypeChecker { auto FieldTypes(const NominalClassType& class_type) const -> std::vector; + // Returns the field names and types of the class and its parents. + auto FieldTypesWithBase(const NominalClassType& class_type) const + -> std::vector; + // Returns true if source_fields and destination_fields contain the same set // of names, and each value in source_fields is implicitly convertible to // the corresponding value in destination_fields. All values in both arguments diff --git a/explorer/interpreter/value.cpp b/explorer/interpreter/value.cpp index 2eaa40869401..faeb39b0dc72 100644 --- a/explorer/interpreter/value.cpp +++ b/explorer/interpreter/value.cpp @@ -5,8 +5,11 @@ #include "explorer/interpreter/value.h" #include +#include +#include #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 object, + std::string_view name) + -> std::optional> { + if (auto field = cast(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, Nonnull v, const FieldPath::Component& field, SourceLocation source_loc, Nonnull me_value) @@ -89,7 +104,7 @@ static auto GetMember(Nonnull arena, Nonnull v, const auto& object = cast(*v); // Look for a field. if (std::optional> field = - cast(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(*value); - CARBON_ASSIGN_OR_RETURN(Nonnull inits, - SetFieldImpl(arena, &object.inits(), path_begin, - path_end, field_value, source_loc)); - return arena->New(&object.type(), inits); + if (auto inits = SetFieldImpl(arena, &object.inits(), path_begin, + path_end, field_value, source_loc); + inits.ok()) { + return arena->New(&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( + &object.type(), &object.inits(), + cast(*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(*this); out << cast(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; } diff --git a/explorer/interpreter/value.h b/explorer/interpreter/value.h index 90bbfd8e2b04..089e165f4ac2 100644 --- a/explorer/interpreter/value.h +++ b/explorer/interpreter/value.h @@ -326,8 +326,14 @@ class StructValue : public Value { // A value of a nominal class type, i.e., an object. class NominalClassValue : public Value { public: - NominalClassValue(Nonnull type, Nonnull inits) - : Value(Kind::NominalClassValue), type_(type), inits_(inits) {} + static constexpr llvm::StringLiteral BaseField{"base"}; + + NominalClassValue(Nonnull type, Nonnull inits, + std::optional> base) + : Value(Kind::NominalClassValue), + type_(type), + inits_(inits), + base_(base) {} static auto classof(const Value* value) -> bool { return value->kind() == Kind::NominalClassValue; @@ -335,10 +341,14 @@ class NominalClassValue : public Value { auto type() const -> const Value& { return *type_; } auto inits() const -> const Value& { return *inits_; } + auto base() const -> std::optional> { + return base_; + } private: Nonnull type_; Nonnull inits_; // The initializing StructValue. + std::optional> base_; }; // An alternative constructor value. @@ -649,7 +659,7 @@ class NominalClassType : public Value { explicit NominalClassType( Nonnull declaration, Nonnull bindings, - std::optional> base = std::nullopt) + std::optional> base) : Value(Kind::NominalClassType), declaration_(declaration), bindings_(bindings), diff --git a/explorer/syntax/parser.ypp b/explorer/syntax/parser.ypp index c926a2aac3a6..cbf41cd26227 100644 --- a/explorer/syntax/parser.ypp +++ b/explorer/syntax/parser.ypp @@ -724,7 +724,9 @@ if_expression: expression: if_expression ; -designator: PERIOD identifier { $$ = $2; } +designator: + PERIOD identifier { $$ = $2; } +| PERIOD BASE { $$ = "base"; } ; paren_expression: paren_expression_base { $$ = ExpressionFromParenContents(arena, context.source_loc(), $1); } diff --git a/explorer/testdata/class/base_constructor.carbon b/explorer/testdata/class/base_constructor.carbon new file mode 100644 index 000000000000..66dcfc6ac747 --- /dev/null +++ b/explorer/testdata/class/base_constructor.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: 1 +// CHECK:STDOUT: 2 +// CHECK:STDOUT: result: 0 + +package ExplorerTest api; + +base class A { + fn Create() -> Self { + return {.value_a = 1}; + } + var value_a: i32; +} + +class B extends A { + fn Create() -> Self { + return {.base = A.Create(), .value_b = 2}; + } + var value_b: i32; +} + +fn Main() -> i32 { + var b: B = B.Create(); + Print("{0}", b.value_a); + Print("{0}", b.value_b); + return 0; +} diff --git a/explorer/testdata/class/class_inheritance_function_call.carbon b/explorer/testdata/class/class_inheritance_function_call.carbon index cfbfef99229a..54693ecbd9eb 100644 --- a/explorer/testdata/class/class_inheritance_function_call.carbon +++ b/explorer/testdata/class/class_inheritance_function_call.carbon @@ -33,7 +33,7 @@ fn Main() -> i32 { // Note: D.FunctionC(0); is not accessible // Calling function from class object - var d: D = {}; + var d: D = {.base={}}; 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 index 132d8b92435a..3b09b8f92c4b 100644 --- a/explorer/testdata/class/class_inheritance_methods.carbon +++ b/explorer/testdata/class/class_inheritance_methods.carbon @@ -31,7 +31,7 @@ class D extends C { } fn Main() -> i32 { - var d: D = {.value_c = 1, .value_d = 2}; + var d: D = {.base = {.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 index ff2da24132ee..a96ec3a9f9aa 100644 --- a/explorer/testdata/class/class_inheritance_multiple.carbon +++ b/explorer/testdata/class/class_inheritance_multiple.carbon @@ -5,29 +5,38 @@ // AUTOUPDATE // RUN: %{explorer-run} // RUN: %{explorer-run-trace} +// CHECK:STDOUT: A +// CHECK:STDOUT: c.a: 1 +// CHECK:STDOUT: c.b: 2 +// CHECK:STDOUT: c.c: 3 // CHECK:STDOUT: result: 0 package ExplorerTest api; base class A { fn FunctionA() {} - var var_a: i32; + var a: i32; + var aa: String; } base class B extends A { fn FunctionB() {} - var var_b: i32; + var b: i32; } class C extends B { fn FunctionC() {} - var var_c: i32; + var c: i32; } fn Main() -> i32 { - var c: C = {.var_a=1, .var_b=2, .var_c=3}; + var c: C = {.base={.base={.aa="A", .a=1}, .b=2, }, .c=3}; c.FunctionA(); c.FunctionB(); c.FunctionC(); + Print(c.aa); + Print("c.a: {0}", c.a); + Print("c.b: {0}", c.b); + Print("c.c: {0}", c.c); return 0; } diff --git a/explorer/testdata/class/class_inheritance_shadow.carbon b/explorer/testdata/class/class_inheritance_shadow.carbon index 06719377c2f7..26e69ce46194 100644 --- a/explorer/testdata/class/class_inheritance_shadow.carbon +++ b/explorer/testdata/class/class_inheritance_shadow.carbon @@ -31,7 +31,7 @@ class D extends C { fn Main() -> i32 { // Initialize derived value first, base value second - var d: D = {.value = 2, .value = 1}; + var d: D = {.value = 2, .base={.value = 1}}; d.Method1(); Print("d.value = {0}", d.value); diff --git a/explorer/testdata/class/class_inheritance_variables.carbon b/explorer/testdata/class/class_inheritance_variables.carbon index 7c6b142db9cd..710c7cf6ed53 100644 --- a/explorer/testdata/class/class_inheritance_variables.carbon +++ b/explorer/testdata/class/class_inheritance_variables.carbon @@ -23,7 +23,7 @@ class D extends C { fn Main() -> i32 { // Initialization - var d: D = {.var_c= 1, .var_d= 2}; + var d: D = {.base = {.var_c= 1}, .var_d= 2}; // Read Print("Read var_c={0}", d.var_c); diff --git a/explorer/testdata/class/fail_direct_base_class_init.carbon b/explorer/testdata/class/fail_direct_base_class_init.carbon new file mode 100644 index 000000000000..f85de82afdd9 --- /dev/null +++ b/explorer/testdata/class/fail_direct_base_class_init.carbon @@ -0,0 +1,23 @@ +// 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: %{not} %{explorer-run} +// RUN: %{not} %{explorer-run-trace} + +package ExplorerTest api; + +base class A { + var a: i32; +} + +class B extends A { + var b: i32; +} + +fn Main() -> i32 { + // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/class/fail_direct_base_class_init.carbon:[[@LINE+1]]: type error in name binding: '{.a: i32, .b: i32}' is not implicitly convertible to 'class B' + var b: B = {.a=0, .b=1}; + return 0; +} diff --git a/explorer/testdata/class/fail_field_access_mismatch_base.carbon b/explorer/testdata/class/fail_field_access_mismatch_base.carbon new file mode 100644 index 000000000000..9b8ff25d52a3 --- /dev/null +++ b/explorer/testdata/class/fail_field_access_mismatch_base.carbon @@ -0,0 +1,24 @@ +// 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: %{not} %{explorer-run} +// RUN: %{not} %{explorer-run-trace} + +package ExplorerTest api; + +base class Point { + var x: i32; + var y: i32; +} + +class Point3D extends Point { + var z: i32; +} + +fn Main() -> i32 { + var p: Point3D = {.base={.x = 1, .y = 2}, .z = 3}; + // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/class/fail_field_access_mismatch_base.carbon:[[@LINE+1]]: class Point3D does not have a field named a + return p.a; +} diff --git a/explorer/testdata/class/fail_field_assign_mismatch_base.carbon b/explorer/testdata/class/fail_field_assign_mismatch_base.carbon new file mode 100644 index 000000000000..67b8db49e03c --- /dev/null +++ b/explorer/testdata/class/fail_field_assign_mismatch_base.carbon @@ -0,0 +1,25 @@ +// 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: %{not} %{explorer-run} +// RUN: %{not} %{explorer-run-trace} + +package ExplorerTest api; + +base class Point { + var x: i32; + var y: i32; +} + +class Point3D extends Point { + var z: i32; +} + +fn Main() -> i32 { + var p: Point3D = {.base={.x = 1, .y = 2}, .z = 3}; + // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/class/fail_field_assign_mismatch_base.carbon:[[@LINE+1]]: class Point3D does not have a field named a + p.a = 0; + return 0; +} diff --git a/explorer/testdata/class/parametrized_base_class.carbon b/explorer/testdata/class/parametrized_base_class.carbon new file mode 100644 index 000000000000..7f514a20e2af --- /dev/null +++ b/explorer/testdata/class/parametrized_base_class.carbon @@ -0,0 +1,37 @@ +// 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: 2 +// CHECK:STDOUT: 1 +// CHECK:STDOUT: result: 0 + +package ExplorerTest api; + +interface Number { + fn Zero() -> Self; + fn Add[me: Self](other: Self) -> Self; +} + +external impl i32 as Number { + fn Zero() -> i32 { return 0; } + fn Add[me: i32](other: i32) -> i32 { return me + other; } +} + +base class A(T:! Number) { + var value_a: T; +} + +class B extends A(i32) { + var value_b: i32; +} + +fn Main() -> i32 { + var b: B = {.base = {.value_a = 1}, .value_b = 2}; + Print("{0}", b.value_b); + Print("{0}", b.value_a); + return 0; +}