From 8301258ef865518c9fae012199ab58ae63c2db8e Mon Sep 17 00:00:00 2001 From: Adrien Leravat Date: Mon, 2 Jan 2023 14:37:49 -0600 Subject: [PATCH] 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 --- explorer/ast/ast_test_matchers_test.cpp | 9 ++-- explorer/ast/declaration.cpp | 5 +- explorer/ast/declaration.h | 18 ++++--- explorer/interpreter/interpreter.cpp | 9 ++-- explorer/interpreter/type_checker.cpp | 21 +++++++- explorer/interpreter/value.cpp | 45 ++++++++++++++-- explorer/interpreter/value.h | 52 +++++++++++++----- explorer/interpreter/value_transform.h | 29 ++++++++++ explorer/syntax/lexer.lpp | 2 + explorer/syntax/parser.ypp | 19 +++++-- .../class/fail_virtual_class_function.carbon | 21 ++++++++ .../class/fail_virtual_method_absent.carbon | 24 +++++++++ explorer/testdata/class/virtual_method.carbon | 48 +++++++++++++++++ .../testdata/class/virtual_method_self.carbon | 53 +++++++++++++++++++ .../class/virtual_method_shadowed_attr.carbon | 39 ++++++++++++++ .../testdata/impl/fail_bad_member_kind.carbon | 2 +- .../testdata/mixin/fail_mix_in_impl.carbon | 2 +- 17 files changed, 359 insertions(+), 39 deletions(-) create mode 100644 explorer/testdata/class/fail_virtual_class_function.carbon create mode 100644 explorer/testdata/class/fail_virtual_method_absent.carbon create mode 100644 explorer/testdata/class/virtual_method.carbon create mode 100644 explorer/testdata/class/virtual_method_self.carbon create mode 100644 explorer/testdata/class/virtual_method_shadowed_attr.carbon diff --git a/explorer/ast/ast_test_matchers_test.cpp b/explorer/ast/ast_test_matchers_test.cpp index 9ccf8afef7de..d6ccfddf6603 100644 --- a/explorer/ast/ast_test_matchers_test.cpp +++ b/explorer/ast/ast_test_matchers_test.cpp @@ -102,7 +102,8 @@ TEST(MatchesFunctionDeclarationTest, BasicUsage) { TuplePattern params(DummyLoc, {}); Block body(DummyLoc, {}); FunctionDeclaration decl(DummyLoc, "Foo", {}, std::nullopt, ¶ms, - ReturnTerm::Omitted(DummyLoc), &body); + ReturnTerm::Omitted(DummyLoc), &body, + /*is_virtual=*/false); EXPECT_THAT(decl, MatchesFunctionDeclaration()); EXPECT_THAT(&decl, MatchesFunctionDeclaration()); @@ -115,7 +116,8 @@ TEST(MatchesFunctionDeclarationTest, BasicUsage) { Not(MatchesFunctionDeclaration().WithBody(MatchesLiteral(0)))); FunctionDeclaration forward_decl(DummyLoc, "Foo", {}, std::nullopt, ¶ms, - ReturnTerm::Omitted(DummyLoc), std::nullopt); + ReturnTerm::Omitted(DummyLoc), std::nullopt, + /*is_virtual=*/false); EXPECT_THAT(forward_decl, MatchesFunctionDeclaration().WithName("Foo")); EXPECT_THAT(forward_decl, Not(MatchesFunctionDeclaration().WithBody(_))); @@ -148,7 +150,8 @@ TEST(ASTDeclarationsTest, BasicUsage) { TuplePattern params(DummyLoc, {}); Block body(DummyLoc, {}); FunctionDeclaration decl(DummyLoc, "Foo", {}, std::nullopt, ¶ms, - ReturnTerm::Omitted(DummyLoc), &body); + ReturnTerm::Omitted(DummyLoc), &body, + /*is_virtual=*/false); AST ast = {.declarations = {&decl}}; EXPECT_THAT(ast, ASTDeclarations(ElementsAre(MatchesFunctionDeclaration()))); diff --git a/explorer/ast/declaration.cpp b/explorer/ast/declaration.cpp index 16e1204eed27..ae6092425155 100644 --- a/explorer/ast/declaration.cpp +++ b/explorer/ast/declaration.cpp @@ -341,14 +341,15 @@ auto FunctionDeclaration::Create(Nonnull arena, std::vector> deduced_params, Nonnull param_pattern, ReturnTerm return_term, - std::optional> body) + std::optional> body, + bool is_virtual) -> ErrorOr> { DeducedParameters split_params; CARBON_ASSIGN_OR_RETURN(split_params, SplitDeducedParameters(source_loc, deduced_params)); return arena->New( source_loc, name, std::move(split_params.resolved_params), - split_params.self_pattern, param_pattern, return_term, body); + split_params.self_pattern, param_pattern, return_term, body, is_virtual); } void CallableDeclaration::PrintDepth(int depth, llvm::raw_ostream& out) const { diff --git a/explorer/ast/declaration.h b/explorer/ast/declaration.h index bcb6bac5dc1e..4694fa7ccad5 100644 --- a/explorer/ast/declaration.h +++ b/explorer/ast/declaration.h @@ -132,14 +132,15 @@ class CallableDeclaration : public Declaration { std::optional> self_pattern, Nonnull param_pattern, ReturnTerm return_term, - std::optional> body) + std::optional> body, bool is_virtual) : Declaration(kind, loc), name_(std::move(name)), deduced_parameters_(std::move(deduced_params)), self_pattern_(self_pattern), param_pattern_(param_pattern), return_term_(return_term), - body_(body) {} + body_(body), + is_virtual_(is_virtual) {} void PrintDepth(int depth, llvm::raw_ostream& out) const; @@ -160,6 +161,7 @@ class CallableDeclaration : public Declaration { auto return_term() -> ReturnTerm& { return return_term_; } auto body() const -> std::optional> { return body_; } auto body() -> std::optional> { return body_; } + auto is_virtual() const -> bool { return is_virtual_; } auto value_category() const -> ValueCategory { return ValueCategory::Let; } @@ -172,6 +174,7 @@ class CallableDeclaration : public Declaration { Nonnull param_pattern_; ReturnTerm return_term_; std::optional> body_; + bool is_virtual_; }; class FunctionDeclaration : public CallableDeclaration { @@ -183,7 +186,7 @@ class FunctionDeclaration : public CallableDeclaration { std::vector> deduced_params, Nonnull param_pattern, ReturnTerm return_term, - std::optional> body) + std::optional> body, bool is_virtual) -> ErrorOr>; // Use `Create()` instead. This is public only so Arena::New() can call it. @@ -192,10 +195,11 @@ class FunctionDeclaration : public CallableDeclaration { std::optional> self_pattern, Nonnull param_pattern, ReturnTerm return_term, - std::optional> body) + std::optional> body, bool is_virtual) : CallableDeclaration(AstNodeKind::FunctionDeclaration, source_loc, std::move(name), std::move(deduced_params), - self_pattern, param_pattern, return_term, body) {} + self_pattern, param_pattern, return_term, body, + is_virtual) {} static auto classof(const AstNode* node) -> bool { return InheritsFromFunctionDeclaration(node->kind()); @@ -222,7 +226,9 @@ class DestructorDeclaration : public CallableDeclaration { std::optional> body) : CallableDeclaration(AstNodeKind::DestructorDeclaration, source_loc, "destructor", std::move(deduced_params), - self_pattern, param_pattern, return_term, body) {} + self_pattern, param_pattern, return_term, body, + // TODO: Add virtual destructors. + /*is_virtual=*/false) {} static auto classof(const AstNode* node) -> bool { return InheritsFromDestructorDeclaration(node->kind()); diff --git a/explorer/interpreter/interpreter.cpp b/explorer/interpreter/interpreter.cpp index e32f50be4723..f13fd0194aa3 100644 --- a/explorer/interpreter/interpreter.cpp +++ b/explorer/interpreter/interpreter.cpp @@ -638,7 +638,7 @@ auto Interpreter::InstantiateType(Nonnull type, Nonnull bindings, InstantiateBindings(&class_type.bindings(), source_loc)); return arena_->New(&class_type.declaration(), bindings, - base); + base, class_type.vtable()); } case Value::Kind::ChoiceType: { const auto& choice_type = cast(*type); @@ -718,8 +718,11 @@ auto Interpreter::ConvertStructToClass( } auto* converted_init_struct = arena_->New(std::move(struct_values)); + Nonnull class_value_ptr = + base_instance ? (*base_instance)->class_value_ptr() + : arena_->New(); return arena_->New(inst_class, converted_init_struct, - base_instance); + base_instance, class_value_ptr); } auto Interpreter::Convert(Nonnull value, @@ -1061,7 +1064,7 @@ auto Interpreter::CallFunction(const CallExpression& call, case DeclarationKind::ClassDeclaration: { const auto& class_decl = cast(decl); return todo_.FinishAction(arena_->New( - &class_decl, bindings, class_decl.base_type())); + &class_decl, bindings, class_decl.base_type(), VTable())); } case DeclarationKind::InterfaceDeclaration: return todo_.FinishAction(arena_->New( diff --git a/explorer/interpreter/type_checker.cpp b/explorer/interpreter/type_checker.cpp index 651559eea0c8..ba2db216211c 100644 --- a/explorer/interpreter/type_checker.cpp +++ b/explorer/interpreter/type_checker.cpp @@ -4506,10 +4506,29 @@ auto TypeChecker::DeclareClassDeclaration(Nonnull class_decl, } } + // Generate a vtable for the type if necessary. + VTable class_vtable = base_class ? (*base_class)->vtable() : VTable(); + const int class_level = base_class ? (*base_class)->hierarchy_level() + 1 : 0; + for (const auto* m : class_decl->members()) { + const auto* fun = dyn_cast(m); + if (!fun || !fun->is_virtual()) { + continue; + } + // TODO: Implement complete declaration logic from + // /docs/design/classes.md#virtual-methods. + if (!fun->is_method()) { + return ProgramError(fun->source_loc()) + << "Error declaring `" << fun->name() << "`" + << ": class functions cannot be virtual."; + } + class_vtable[fun->name()] = {fun, class_level}; + } + // 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), base_class); + class_decl, Bindings::SymbolicIdentity(arena_, bindings), base_class, + std::move(class_vtable)); self->set_static_type(arena_->New()); self->set_constant_value(self_type); diff --git a/explorer/interpreter/value.cpp b/explorer/interpreter/value.cpp index fcc8835a646e..ef981ed596ab 100644 --- a/explorer/interpreter/value.cpp +++ b/explorer/interpreter/value.cpp @@ -38,6 +38,19 @@ auto StructValue::FindField(std::string_view name) const return std::nullopt; } +NominalClassValue::NominalClassValue( + Nonnull type, Nonnull inits, + std::optional> base, + Nonnull 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 object, std::string_view name) -> std::optional> { @@ -148,8 +161,30 @@ static auto GetNamedElement(Nonnull arena, Nonnull v, } else if ((*func)->declaration().is_method()) { // Found a method. Turn it into a bound method. const auto& m = cast(**func); - return arena->New(&m.declaration(), me_value, - &class_type.bindings()); + if (!m.declaration().is_virtual()) { + return arena->New(&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(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( + cast(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(&object.type(), *inits, - object.base()); + return arena->New( + &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( &object.type(), &object.inits(), - cast(*new_base)); + cast(*new_base), object.class_value_ptr()); } } // Failed to match, show full object content diff --git a/explorer/interpreter/value.h b/explorer/interpreter/value.h index 46d5daee6e1d..d120e2845c60 100644 --- a/explorer/interpreter/value.h +++ b/explorer/interpreter/value.h @@ -19,6 +19,7 @@ #include "explorer/interpreter/address.h" #include "explorer/interpreter/element_path.h" #include "explorer/interpreter/stack.h" +#include "llvm/ADT/StringMap.h" #include "llvm/Support/Compiler.h" namespace Carbon { @@ -36,6 +37,9 @@ struct AllocateTrait { } }; +using VTable = + llvm::StringMap, int>>; + // Abstract base class of all AST nodes representing values. // // Value and its derived classes support LLVM-style RTTI, including @@ -340,12 +344,14 @@ class NominalClassValue : public Value { public: static constexpr llvm::StringLiteral BaseField{"base"}; + // Takes the class type, inits, an optional base, a pointer to a + // NominalClassValue*, that must be common to all NominalClassValue of the + // same object. The pointee is updated, when `NominalClassValue`s are + // constructed, to point to the `NominalClassValue` corresponding to the + // child-most class type. NominalClassValue(Nonnull type, Nonnull inits, - std::optional> base) - : Value(Kind::NominalClassValue), - type_(type), - inits_(inits), - base_(base) {} + std::optional> base, + Nonnull class_value_ptr); static auto classof(const Value* value) -> bool { return value->kind() == Kind::NominalClassValue; @@ -353,7 +359,7 @@ class NominalClassValue : public Value { template auto Decompose(F f) const { - return f(type_, inits_, base_); + return f(type_, inits_, base_, class_value_ptr_); } auto type() const -> const Value& { return *type_; } @@ -361,11 +367,16 @@ class NominalClassValue : public Value { auto base() const -> std::optional> { return base_; } + // Returns a pointer of pointer to the child-most class value. + auto class_value_ptr() const -> Nonnull { + return class_value_ptr_; + } private: Nonnull type_; Nonnull inits_; // The initializing StructValue. std::optional> base_; + Nonnull class_value_ptr_; }; // An alternative constructor value. @@ -731,8 +742,14 @@ class StructType : public Value { class NominalClassType : public Value { public: // Construct a non-generic class type. - explicit NominalClassType(Nonnull declaration) - : Value(Kind::NominalClassType), declaration_(declaration) { + explicit NominalClassType( + Nonnull declaration, + std::optional> base, VTable class_vtable) + : Value(Kind::NominalClassType), + declaration_(declaration), + base_(base), + vtable_(std::move(class_vtable)), + hierarchy_level_(base ? (*base)->hierarchy_level() + 1 : 0) { CARBON_CHECK(!declaration->type_params().has_value()) << "missing arguments for parameterized class type"; } @@ -742,11 +759,13 @@ class NominalClassType : public Value { explicit NominalClassType( Nonnull declaration, Nonnull bindings, - std::optional> base) + std::optional> base, VTable class_vtable) : Value(Kind::NominalClassType), declaration_(declaration), bindings_(bindings), - base_(base) {} + base_(base), + vtable_(std::move(class_vtable)), + hierarchy_level_(base ? (*base)->hierarchy_level() + 1 : 0) {} static auto classof(const Value* value) -> bool { return value->kind() == Kind::NominalClassType; @@ -754,7 +773,7 @@ class NominalClassType : public Value { template auto Decompose(F f) const { - return f(declaration_, bindings_, base_); + return f(declaration_, bindings_, base_, vtable_); } auto declaration() const -> const ClassDeclaration& { return *declaration_; } @@ -772,6 +791,13 @@ class NominalClassType : public Value { return bindings_->witnesses(); } + auto vtable() const -> const VTable& { return vtable_; } + + // Returns how many levels from the top ancestor class it is. i.e. a class + // with no base returns `0`, while a class with a `.base` and `.base.base` + // returns `2`. + auto hierarchy_level() const -> int { return hierarchy_level_; } + // Returns whether this a parameterized class. That is, a class with // parameters and no corresponding arguments. auto IsParameterized() const -> bool { @@ -784,7 +810,9 @@ class NominalClassType : public Value { private: Nonnull declaration_; Nonnull bindings_ = Bindings::None(); - std::optional> base_; + const std::optional> base_; + const VTable vtable_; + int hierarchy_level_; }; class MixinPseudoType : public Value { diff --git a/explorer/interpreter/value_transform.h b/explorer/interpreter/value_transform.h index 93a5badef4cf..679b3776635a 100644 --- a/explorer/interpreter/value_transform.h +++ b/explorer/interpreter/value_transform.h @@ -70,6 +70,7 @@ class TransformBase { return v; } auto operator()(const std::string& str) -> const std::string& { return str; } + auto operator()(llvm::StringRef str) -> llvm::StringRef { return str; } // Transform `optional` by transforming the `T` if it's present. template @@ -80,6 +81,12 @@ class TransformBase { return Transform(*v); } + // Transform `pair` by transforming T and U. + template + auto operator()(const std::pair& pair) -> std::pair { + return std::pair{Transform(pair.first), Transform(pair.second)}; + } + // Transform `vector` by transforming its elements. template auto operator()(const std::vector& vec) -> std::vector { @@ -101,6 +108,16 @@ class TransformBase { return result; } + // Transform `llvm::StringMap` by transforming its keys and values. + template + auto operator()(const llvm::StringMap& map) -> llvm::StringMap { + llvm::StringMap result; + for (const auto& it : map) { + result.insert({Transform(it.first()), Transform(it.second)}); + } + return result; + } + private: Nonnull arena_; }; @@ -159,6 +176,18 @@ class ValueTransform : public TransformBase { auto operator()(Nonnull elem) -> Nonnull { return TransformDerived>(elem); } + + // Preserve vtable during transformation. + auto operator()(Nonnull vtable) + -> Nonnull { + return vtable; + } + + // Preserve class value ptr during transformation. + auto operator()(Nonnull value_ptr) + -> Nonnull { + return value_ptr; + } }; } // namespace Carbon diff --git a/explorer/syntax/lexer.lpp b/explorer/syntax/lexer.lpp index 1f21202f9ae7..5735c560f4b9 100644 --- a/explorer/syntax/lexer.lpp +++ b/explorer/syntax/lexer.lpp @@ -113,6 +113,7 @@ TYPE "Type" UNDERSCORE "_" UNIMPL_EXAMPLE "__unimplemented_example_infix" VAR "var" +VIRTUAL "virtual" WHERE "where" WHILE "while" /* table-end */ @@ -217,6 +218,7 @@ operand_start [(A-Za-z0-9_\"] {UNDERSCORE} { return CARBON_SIMPLE_TOKEN(UNDERSCORE); } {UNIMPL_EXAMPLE} { return CARBON_SIMPLE_TOKEN(UNIMPL_EXAMPLE); } {VAR} { return CARBON_SIMPLE_TOKEN(VAR); } +{VIRTUAL} { return CARBON_SIMPLE_TOKEN(VIRTUAL); } {WHERE} { return CARBON_SIMPLE_TOKEN(WHERE); } {WHILE} { return CARBON_SIMPLE_TOKEN(WHILE); } /* table-end */ diff --git a/explorer/syntax/parser.ypp b/explorer/syntax/parser.ypp index 9276a4da27a6..2df82c08694e 100644 --- a/explorer/syntax/parser.ypp +++ b/explorer/syntax/parser.ypp @@ -104,11 +104,12 @@ %type > import_directives %type optional_library_path %type api_or_impl +%type fn_virtual_override_intro %type class_declaration_extensibility %type >> class_declaration_extends %type > declaration %type > function_declaration -%type > destructor_declaration; +%type > destructor_declaration %type > mix_declaration %type > alias_declaration %type >> declaration_list @@ -281,6 +282,7 @@ UNDERSCORE UNIMPL_EXAMPLE VAR + VIRTUAL WHERE WHILE // table-end @@ -1062,11 +1064,18 @@ impl_deduced_params: | FORALL LEFT_SQUARE_BRACKET deduced_param_list RIGHT_SQUARE_BRACKET { $$ = $3; } ; +// This includes the FN keyword to work around a shift-reduce conflict between virtual function's `IMPL FN` and interfaces `IMPL`. +fn_virtual_override_intro: + FN + { $$ = false; } +| VIRTUAL FN + { $$ = true; } +; function_declaration: - FN identifier deduced_params maybe_empty_tuple_pattern return_term block + fn_virtual_override_intro identifier deduced_params maybe_empty_tuple_pattern return_term block { ErrorOr fn = FunctionDeclaration::Create( - arena, context.source_loc(), $2, $3, $4, $5, $6); + arena, context.source_loc(), $2, $3, $4, $5, $6, $1); if (fn.ok()) { $$ = *fn; } else { @@ -1074,10 +1083,10 @@ function_declaration: YYERROR; } } -| FN identifier deduced_params maybe_empty_tuple_pattern return_term SEMICOLON +| fn_virtual_override_intro identifier deduced_params maybe_empty_tuple_pattern return_term SEMICOLON { ErrorOr fn = FunctionDeclaration::Create( - arena, context.source_loc(), $2, $3, $4, $5, std::nullopt); + arena, context.source_loc(), $2, $3, $4, $5, std::nullopt, $1); if (fn.ok()) { $$ = *fn; } else { diff --git a/explorer/testdata/class/fail_virtual_class_function.carbon b/explorer/testdata/class/fail_virtual_class_function.carbon new file mode 100644 index 000000000000..12915b108b98 --- /dev/null +++ b/explorer/testdata/class/fail_virtual_class_function.carbon @@ -0,0 +1,21 @@ +// 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; + +class C { + virtual fn Foo() -> i32 { + return 1; + // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/class/fail_virtual_class_function.carbon:[[@LINE+1]]: Error declaring `Foo`: class functions cannot be virtual. + } +} + +fn Main() -> i32 { + let c: C = {}; + return 0; +} diff --git a/explorer/testdata/class/fail_virtual_method_absent.carbon b/explorer/testdata/class/fail_virtual_method_absent.carbon new file mode 100644 index 000000000000..723b1e3db016 --- /dev/null +++ b/explorer/testdata/class/fail_virtual_method_absent.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 A {} + +class B extends A { + virtual fn Foo[self: Self]() -> i32 { return 0; } +} + +fn Main() -> i32 { + var b: B = { .base = {}}; + var bp: B* = &b; + var ap: A* = bp; + // Shouldn't look into the vtable for B. + // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/class/fail_virtual_method_absent.carbon:[[@LINE+1]]: class A does not have a field named Foo + return ap->Foo(); +} diff --git a/explorer/testdata/class/virtual_method.carbon b/explorer/testdata/class/virtual_method.carbon new file mode 100644 index 000000000000..69575af8f4cb --- /dev/null +++ b/explorer/testdata/class/virtual_method.carbon @@ -0,0 +1,48 @@ +// 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: c.Foo() -> 1 +// CHECK:STDOUT: c.Bar() -> 2 +// CHECK:STDOUT: d.Foo() -> 3 +// CHECK:STDOUT: d.Bar() -> 4 +// CHECK:STDOUT: cc.Foo() -> 3 +// CHECK:STDOUT: cc.Bar() -> 2 +// CHECK:STDOUT: result: 0 + +package ExplorerTest api; + +base class C { + virtual fn Foo[self: Self]() -> i32 { + return 1; + } + fn Bar[self: Self]() -> i32 { + return 2; + } +} + +class D extends C { + // TODO: This should instead use `impl` when supported + virtual fn Foo[self: Self]() -> i32 { + return 3; + } + fn Bar[self: Self]() -> i32 { + return 4; + } +} + +fn Main() -> i32 { + var c: C = {}; + Print("c.Foo() -> {0}", c.Foo()); + Print("c.Bar() -> {0}", c.Bar()); + var d: D = {.base = {}}; + Print("d.Foo() -> {0}", d.Foo()); + Print("d.Bar() -> {0}", d.Bar()); + var cc: C* = &d; + Print("cc.Foo() -> {0}", (*cc).Foo()); + Print("cc.Bar() -> {0}", (*cc).Bar()); + return 0; +} diff --git a/explorer/testdata/class/virtual_method_self.carbon b/explorer/testdata/class/virtual_method_self.carbon new file mode 100644 index 000000000000..f4285bc52f4b --- /dev/null +++ b/explorer/testdata/class/virtual_method_self.carbon @@ -0,0 +1,53 @@ +// 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: c.Foo(): 1 +// CHECK:STDOUT: d.Foo(): 2 +// CHECK:STDOUT: e.Foo(): 3 +// CHECK:STDOUT: (*dp).Foo(): 3 +// CHECK:STDOUT: (*dc).Foo(): 3 +// CHECK:STDOUT: result: 0 + +package ExplorerTest api; + +base class C { + var value_c: i32; + virtual fn Foo[self: Self]() -> i32 { + return self.value_c; + } +} + +base class D extends C { + var value_d: i32; + // TODO: This should instead use `impl` when supported + virtual fn Foo[self: Self]() -> i32 { + return self.value_d; + } +} + +class E extends D { + var value_e: i32; + // TODO: This should instead use `impl` when supported + virtual fn Foo[self: Self]() -> i32 { + return self.value_e; + } +} + +fn Main() -> i32 { + var c: C = {.value_c = 1}; + Print("c.Foo(): {0}", c.Foo()); + var d: D = {.value_d = 2, .base = {.value_c = 1}}; + Print("d.Foo(): {0}", d.Foo()); + var e: E = {.value_e = 3, .base={.value_d = 2, .base = {.value_c = 1}}}; + Print("e.Foo(): {0}", e.Foo()); + var dp: D* = &e; + Print("(*dp).Foo(): {0}", (*dp).Foo()); + var dc: C* = &e; + Print("(*dc).Foo(): {0}", (*dc).Foo()); + + return 0; +} diff --git a/explorer/testdata/class/virtual_method_shadowed_attr.carbon b/explorer/testdata/class/virtual_method_shadowed_attr.carbon new file mode 100644 index 000000000000..5fe047d99e58 --- /dev/null +++ b/explorer/testdata/class/virtual_method_shadowed_attr.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: c.Foo(): 1 +// CHECK:STDOUT: d.Foo(): 2 +// CHECK:STDOUT: (*cp).Foo(): 2 +// CHECK:STDOUT: result: 0 + +package ExplorerTest api; + +base class C { + var value: i32; + virtual fn Foo[self: Self]() -> i32 { + return self.value; + } +} + +base class D extends C { + var value: i32; + // TODO: This should instead use `impl` when supported + virtual fn Foo[self: Self]() -> i32 { + return self.value; + } +} + +fn Main() -> i32 { + var c: C = {.value = 1}; + Print("c.Foo(): {0}", c.Foo()); + var d: D = {.value = 2, .base = {.value = 1}}; + Print("d.Foo(): {0}", d.Foo()); + var cp: C* = &d; + Print("(*cp).Foo(): {0}", (*cp).Foo()); + + return 0; +} diff --git a/explorer/testdata/impl/fail_bad_member_kind.carbon b/explorer/testdata/impl/fail_bad_member_kind.carbon index 44672cfa1128..d18200f76a15 100644 --- a/explorer/testdata/impl/fail_bad_member_kind.carbon +++ b/explorer/testdata/impl/fail_bad_member_kind.carbon @@ -14,7 +14,7 @@ interface A { } external impl i32 as A { - // CHECK:STDERR: SYNTAX ERROR: {{.*}}/explorer/testdata/impl/fail_bad_member_kind.carbon:[[@LINE+1]]: syntax error, unexpected CLASS, expecting ALIAS or FN or RIGHT_CURLY_BRACE + // CHECK:STDERR: SYNTAX ERROR: {{.*}}/explorer/testdata/impl/fail_bad_member_kind.carbon:[[@LINE+1]]: syntax error, unexpected CLASS, expecting ALIAS or FN or RIGHT_CURLY_BRACE or VIRTUAL class T {} } diff --git a/explorer/testdata/mixin/fail_mix_in_impl.carbon b/explorer/testdata/mixin/fail_mix_in_impl.carbon index ef988bc48c74..0473ca1322da 100644 --- a/explorer/testdata/mixin/fail_mix_in_impl.carbon +++ b/explorer/testdata/mixin/fail_mix_in_impl.carbon @@ -19,7 +19,7 @@ interface A { } external impl i32 as A { - // CHECK:STDERR: SYNTAX ERROR: {{.*}}/explorer/testdata/mixin/fail_mix_in_impl.carbon:[[@LINE+1]]: syntax error, unexpected MIX, expecting ALIAS or FN or RIGHT_CURLY_BRACE + // CHECK:STDERR: SYNTAX ERROR: {{.*}}/explorer/testdata/mixin/fail_mix_in_impl.carbon:[[@LINE+1]]: syntax error, unexpected MIX, expecting ALIAS or FN or RIGHT_CURLY_BRACE or VIRTUAL __mix Operations; fn F() {} }