Просмотр исходного кода

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
Adrien Leravat 3 лет назад
Родитель
Сommit
8301258ef8

+ 6 - 3
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, &params,
-                           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, &params,
-                                   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, &params,
-                           ReturnTerm::Omitted(DummyLoc), &body);
+                           ReturnTerm::Omitted(DummyLoc), &body,
+                           /*is_virtual=*/false);
   AST ast = {.declarations = {&decl}};
 
   EXPECT_THAT(ast, ASTDeclarations(ElementsAre(MatchesFunctionDeclaration())));

+ 3 - 2
explorer/ast/declaration.cpp

@@ -341,14 +341,15 @@ auto FunctionDeclaration::Create(Nonnull<Arena*> arena,
                                  std::vector<Nonnull<AstNode*>> deduced_params,
                                  Nonnull<TuplePattern*> param_pattern,
                                  ReturnTerm return_term,
-                                 std::optional<Nonnull<Block*>> body)
+                                 std::optional<Nonnull<Block*>> body,
+                                 bool is_virtual)
     -> ErrorOr<Nonnull<FunctionDeclaration*>> {
   DeducedParameters split_params;
   CARBON_ASSIGN_OR_RETURN(split_params,
                           SplitDeducedParameters(source_loc, deduced_params));
   return arena->New<FunctionDeclaration>(
       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 {

+ 12 - 6
explorer/ast/declaration.h

@@ -132,14 +132,15 @@ class CallableDeclaration : public Declaration {
                       std::optional<Nonnull<Pattern*>> self_pattern,
                       Nonnull<TuplePattern*> param_pattern,
                       ReturnTerm return_term,
-                      std::optional<Nonnull<Block*>> body)
+                      std::optional<Nonnull<Block*>> 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<Nonnull<const Block*>> { return body_; }
   auto body() -> std::optional<Nonnull<Block*>> { 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<TuplePattern*> param_pattern_;
   ReturnTerm return_term_;
   std::optional<Nonnull<Block*>> body_;
+  bool is_virtual_;
 };
 
 class FunctionDeclaration : public CallableDeclaration {
@@ -183,7 +186,7 @@ class FunctionDeclaration : public CallableDeclaration {
                      std::vector<Nonnull<AstNode*>> deduced_params,
                      Nonnull<TuplePattern*> param_pattern,
                      ReturnTerm return_term,
-                     std::optional<Nonnull<Block*>> body)
+                     std::optional<Nonnull<Block*>> body, bool is_virtual)
       -> ErrorOr<Nonnull<FunctionDeclaration*>>;
 
   // Use `Create()` instead. This is public only so Arena::New() can call it.
@@ -192,10 +195,11 @@ class FunctionDeclaration : public CallableDeclaration {
                       std::optional<Nonnull<Pattern*>> self_pattern,
                       Nonnull<TuplePattern*> param_pattern,
                       ReturnTerm return_term,
-                      std::optional<Nonnull<Block*>> body)
+                      std::optional<Nonnull<Block*>> 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<Nonnull<Block*>> 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());

+ 6 - 3
explorer/interpreter/interpreter.cpp

@@ -638,7 +638,7 @@ auto Interpreter::InstantiateType(Nonnull<const Value*> type,
           Nonnull<const Bindings*> bindings,
           InstantiateBindings(&class_type.bindings(), source_loc));
       return arena_->New<NominalClassType>(&class_type.declaration(), bindings,
-                                           base);
+                                           base, class_type.vtable());
     }
     case Value::Kind::ChoiceType: {
       const auto& choice_type = cast<ChoiceType>(*type);
@@ -718,8 +718,11 @@ auto Interpreter::ConvertStructToClass(
   }
   auto* converted_init_struct =
       arena_->New<StructValue>(std::move(struct_values));
+  Nonnull<const NominalClassValue** const> class_value_ptr =
+      base_instance ? (*base_instance)->class_value_ptr()
+                    : arena_->New<const NominalClassValue*>();
   return arena_->New<NominalClassValue>(inst_class, converted_init_struct,
-                                        base_instance);
+                                        base_instance, class_value_ptr);
 }
 
 auto Interpreter::Convert(Nonnull<const Value*> value,
@@ -1061,7 +1064,7 @@ auto Interpreter::CallFunction(const CallExpression& call,
         case DeclarationKind::ClassDeclaration: {
           const auto& class_decl = cast<ClassDeclaration>(decl);
           return todo_.FinishAction(arena_->New<NominalClassType>(
-              &class_decl, bindings, class_decl.base_type()));
+              &class_decl, bindings, class_decl.base_type(), VTable()));
         }
         case DeclarationKind::InterfaceDeclaration:
           return todo_.FinishAction(arena_->New<InterfaceType>(

+ 20 - 1
explorer/interpreter/type_checker.cpp

@@ -4506,10 +4506,29 @@ auto TypeChecker::DeclareClassDeclaration(Nonnull<ClassDeclaration*> 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<FunctionDeclaration>(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<NominalClassType*> self_type = arena_->New<NominalClassType>(
-      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<TypeType>());
   self->set_constant_value(self_type);
 

+ 40 - 5
explorer/interpreter/value.cpp

@@ -38,6 +38,19 @@ auto StructValue::FindField(std::string_view name) const
   return std::nullopt;
 }
 
+NominalClassValue::NominalClassValue(
+    Nonnull<const Value*> type, Nonnull<const Value*> inits,
+    std::optional<Nonnull<const NominalClassValue*>> base,
+    Nonnull<const NominalClassValue** const> 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<const NominalClassValue*> object,
                            std::string_view name)
     -> std::optional<Nonnull<const Value*>> {
@@ -148,8 +161,30 @@ static auto GetNamedElement(Nonnull<Arena*> arena, Nonnull<const Value*> v,
         } else if ((*func)->declaration().is_method()) {
           // Found a method. Turn it into a bound method.
           const auto& m = cast<FunctionValue>(**func);
-          return arena->New<BoundMethodValue>(&m.declaration(), me_value,
-                                              &class_type.bindings());
+          if (!m.declaration().is_virtual()) {
+            return arena->New<BoundMethodValue>(&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<NominalClassType>(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<BoundMethodValue>(
+              cast<FunctionDeclaration>(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<NominalClassValue>(&object.type(), *inits,
-                                             object.base());
+        return arena->New<NominalClassValue>(
+            &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<NominalClassValue>(
               &object.type(), &object.inits(),
-              cast<NominalClassValue>(*new_base));
+              cast<NominalClassValue>(*new_base), object.class_value_ptr());
         }
       }
       // Failed to match, show full object content

+ 40 - 12
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<std::pair<Nonnull<const CallableDeclaration*>, 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<const Value*> type, Nonnull<const Value*> inits,
-                    std::optional<Nonnull<const NominalClassValue*>> base)
-      : Value(Kind::NominalClassValue),
-        type_(type),
-        inits_(inits),
-        base_(base) {}
+                    std::optional<Nonnull<const NominalClassValue*>> base,
+                    Nonnull<const NominalClassValue** const> class_value_ptr);
 
   static auto classof(const Value* value) -> bool {
     return value->kind() == Kind::NominalClassValue;
@@ -353,7 +359,7 @@ class NominalClassValue : public Value {
 
   template <typename F>
   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<Nonnull<const NominalClassValue*>> {
     return base_;
   }
+  // Returns a pointer of pointer to the child-most class value.
+  auto class_value_ptr() const -> Nonnull<const NominalClassValue** const> {
+    return class_value_ptr_;
+  }
 
  private:
   Nonnull<const Value*> type_;
   Nonnull<const Value*> inits_;  // The initializing StructValue.
   std::optional<Nonnull<const NominalClassValue*>> base_;
+  Nonnull<const NominalClassValue** const> 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<const ClassDeclaration*> declaration)
-      : Value(Kind::NominalClassType), declaration_(declaration) {
+  explicit NominalClassType(
+      Nonnull<const ClassDeclaration*> declaration,
+      std::optional<Nonnull<const NominalClassType*>> 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<const ClassDeclaration*> declaration,
       Nonnull<const Bindings*> bindings,
-      std::optional<Nonnull<const NominalClassType*>> base)
+      std::optional<Nonnull<const NominalClassType*>> 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 <typename F>
   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<const ClassDeclaration*> declaration_;
   Nonnull<const Bindings*> bindings_ = Bindings::None();
-  std::optional<Nonnull<const NominalClassType*>> base_;
+  const std::optional<Nonnull<const NominalClassType*>> base_;
+  const VTable vtable_;
+  int hierarchy_level_;
 };
 
 class MixinPseudoType : public Value {

+ 29 - 0
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<T>` by transforming the `T` if it's present.
   template <typename T>
@@ -80,6 +81,12 @@ class TransformBase {
     return Transform(*v);
   }
 
+  // Transform `pair<T, U>` by transforming T and U.
+  template <typename T, typename U>
+  auto operator()(const std::pair<T, U>& pair) -> std::pair<T, U> {
+    return std::pair<T, U>{Transform(pair.first), Transform(pair.second)};
+  }
+
   // Transform `vector<T>` by transforming its elements.
   template <typename T>
   auto operator()(const std::vector<T>& vec) -> std::vector<T> {
@@ -101,6 +108,16 @@ class TransformBase {
     return result;
   }
 
+  // Transform `llvm::StringMap<T>` by transforming its keys and values.
+  template <typename T>
+  auto operator()(const llvm::StringMap<T>& map) -> llvm::StringMap<T> {
+    llvm::StringMap<T> result;
+    for (const auto& it : map) {
+      result.insert({Transform(it.first()), Transform(it.second)});
+    }
+    return result;
+  }
+
  private:
   Nonnull<Arena*> arena_;
 };
@@ -159,6 +176,18 @@ class ValueTransform : public TransformBase<Derived> {
   auto operator()(Nonnull<const Element*> elem) -> Nonnull<const Element*> {
     return TransformDerived<Nonnull<const Element*>>(elem);
   }
+
+  // Preserve vtable during transformation.
+  auto operator()(Nonnull<const VTable* const> vtable)
+      -> Nonnull<const VTable* const> {
+    return vtable;
+  }
+
+  // Preserve class value ptr during transformation.
+  auto operator()(Nonnull<const NominalClassValue** const> value_ptr)
+      -> Nonnull<const NominalClassValue** const> {
+    return value_ptr;
+  }
 };
 
 }  // namespace Carbon

+ 2 - 0
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 */

+ 14 - 5
explorer/syntax/parser.ypp

@@ -104,11 +104,12 @@
 %type <std::vector<LibraryName>> import_directives
 %type <std::string> optional_library_path
 %type <bool> api_or_impl
+%type <bool> fn_virtual_override_intro
 %type <ClassExtensibility> class_declaration_extensibility
 %type <std::optional<Nonnull<Expression*>>> class_declaration_extends
 %type <Nonnull<Declaration*>> declaration
 %type <Nonnull<FunctionDeclaration*>> function_declaration
-%type <Nonnull<DestructorDeclaration*>> destructor_declaration;
+%type <Nonnull<DestructorDeclaration*>> destructor_declaration
 %type <Nonnull<MixDeclaration*>> mix_declaration
 %type <Nonnull<AliasDeclaration*>> alias_declaration
 %type <std::vector<Nonnull<Declaration*>>> 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<FunctionDeclaration*> 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<FunctionDeclaration*> 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 {

+ 21 - 0
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;
+}

+ 24 - 0
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();
+}

+ 48 - 0
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;
+}

+ 53 - 0
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;
+}

+ 39 - 0
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;
+}

+ 1 - 1
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 {}
 }
 

+ 1 - 1
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() {}
 }