Explorar o código

Parsing and basic checking for `abstract class` and `base class`. (#3385)

For now, we require the same introducer to be used each time a class is
declared, but see #3384.

---------

Co-authored-by: Jon Ross-Perkins <jperkins@google.com>
Richard Smith %!s(int64=2) %!d(string=hai) anos
pai
achega
2715e2276e

+ 12 - 1
toolchain/check/convert.cpp

@@ -539,9 +539,20 @@ static auto ConvertStructToClass(Context& context, SemIR::StructType src_type,
                                  SemIR::InstId value_id,
                                  ConversionTarget target) -> SemIR::InstId {
   PendingBlock target_block(context);
+  auto& class_info = context.classes().Get(dest_type.class_id);
+  if (class_info.inheritance_kind == SemIR::Class::Abstract) {
+    CARBON_DIAGNOSTIC(ConstructionOfAbstractClass, Error,
+                      "Cannot construct instance of abstract class. "
+                      "Consider using `partial {0}` instead.",
+                      std::string);
+    context.emitter().Emit(context.insts().Get(value_id).parse_node(),
+                           ConstructionOfAbstractClass,
+                           context.sem_ir().StringifyType(target.type_id));
+    return SemIR::InstId::BuiltinError;
+  }
   auto dest_struct_type = context.insts().GetAs<SemIR::StructType>(
       context.sem_ir().GetTypeAllowBuiltinTypes(
-          context.classes().Get(dest_type.class_id).object_representation_id));
+          class_info.object_representation_id));
 
   // If we're trying to create a class value, form a temporary for the value to
   // point to.

+ 45 - 5
toolchain/check/handle_class.cpp

@@ -18,17 +18,38 @@ auto HandleClassIntroducer(Context& context, Parse::Node parse_node) -> bool {
   return true;
 }
 
+auto HandleAbstractModifier(Context& context, Parse::Node parse_node) -> bool {
+  context.node_stack().Push(parse_node);
+  return true;
+}
+
+auto HandleBaseModifier(Context& context, Parse::Node parse_node) -> bool {
+  context.node_stack().Push(parse_node);
+  return true;
+}
+
 static auto BuildClassDecl(Context& context)
     -> std::tuple<SemIR::ClassId, SemIR::InstId> {
   auto name_context = context.decl_name_stack().FinishName();
-  auto class_keyword =
+  auto introducer = context.node_stack().PeekParseNode();
+  bool abstract =
       context.node_stack()
-          .PopForSoloParseNode<Parse::NodeKind::ClassIntroducer>();
+          .PopAndDiscardSoloParseNodeIf<Parse::NodeKind::AbstractModifier>();
+  bool base =
+      context.node_stack()
+          .PopAndDiscardSoloParseNodeIf<Parse::NodeKind::BaseModifier>();
+  context.node_stack()
+      .PopAndDiscardSoloParseNode<Parse::NodeKind::ClassIntroducer>();
   auto decl_block_id = context.inst_block_stack().Pop();
 
+  CARBON_CHECK(!(abstract && base)) << "Cannot be both `abstract` and `base`";
+  auto inheritance_kind = abstract ? SemIR::Class::Abstract
+                          : base   ? SemIR::Class::Base
+                                   : SemIR::Class::Final;
+
   // Add the class declaration.
   auto class_decl =
-      SemIR::ClassDecl{class_keyword, SemIR::ClassId::Invalid, decl_block_id};
+      SemIR::ClassDecl{introducer, SemIR::ClassId::Invalid, decl_block_id};
   auto class_decl_id = context.AddInst(class_decl);
 
   // Check whether this is a redeclaration.
@@ -39,6 +60,24 @@ static auto BuildClassDecl(Context& context)
             context.insts().Get(existing_id).TryAs<SemIR::ClassDecl>()) {
       // This is a redeclaration of an existing class.
       class_decl.class_id = existing_class_decl->class_id;
+      auto& class_info = context.classes().Get(class_decl.class_id);
+
+      // The introducer kind must match the previous declaration.
+      // TODO: The rule here is not yet decided. See #3384.
+      if (class_info.inheritance_kind != inheritance_kind) {
+        CARBON_DIAGNOSTIC(ClassRedeclarationDifferentIntroducer, Error,
+                          "Class redeclared with different inheritance kind.");
+        CARBON_DIAGNOSTIC(ClassRedeclarationDifferentIntroducerPrevious, Note,
+                          "Previously declared here.");
+        context.emitter()
+            .Build(introducer, ClassRedeclarationDifferentIntroducer)
+            .Note(existing_class_decl->parse_node,
+                  ClassRedeclarationDifferentIntroducerPrevious)
+            .Emit();
+      }
+
+      // TODO: Check that the generic parameter list agrees with the prior
+      // declaration.
     } else {
       // This is a redeclaration of something other than a class.
       context.DiagnoseDuplicateName(name_context.parse_node, existing_id);
@@ -57,13 +96,14 @@ static auto BuildClassDecl(Context& context)
                  : SemIR::NameId::Invalid,
          // `.self_type_id` depends on `class_id`, so is set below.
          .self_type_id = SemIR::TypeId::Invalid,
-         .decl_id = class_decl_id});
+         .decl_id = class_decl_id,
+         .inheritance_kind = inheritance_kind});
 
     // Build the `Self` type.
     auto& class_info = context.classes().Get(class_decl.class_id);
     class_info.self_type_id =
         context.CanonicalizeType(context.AddInst(SemIR::ClassType{
-            class_keyword, context.GetBuiltinType(SemIR::BuiltinKind::TypeType),
+            introducer, context.GetBuiltinType(SemIR::BuiltinKind::TypeType),
             class_decl.class_id}));
   }
 

+ 3 - 3
toolchain/check/handle_pattern_binding.cpp

@@ -62,7 +62,7 @@ auto HandlePatternBinding(Context& context, Parse::Node parse_node) -> bool {
   // error locations.
   switch (auto context_parse_node_kind = context.parse_tree().node_kind(
               context.node_stack().PeekParseNode())) {
-    case Parse::NodeKind::ReturnedSpecifier:
+    case Parse::NodeKind::ReturnedModifier:
     case Parse::NodeKind::VariableIntroducer: {
       // A `var` declaration at class scope introduces a field.
       auto enclosing_class_decl = context.GetCurrentScopeAs<SemIR::ClassDecl>();
@@ -79,7 +79,7 @@ auto HandlePatternBinding(Context& context, Parse::Node parse_node) -> bool {
       }
       SemIR::InstId value_id = SemIR::InstId::Invalid;
       SemIR::TypeId value_type_id = cast_type_id;
-      if (context_parse_node_kind == Parse::NodeKind::ReturnedSpecifier) {
+      if (context_parse_node_kind == Parse::NodeKind::ReturnedModifier) {
         CARBON_CHECK(!enclosing_class_decl) << "`returned var` at class scope";
         value_id =
             CheckReturnedVar(context, context.node_stack().PeekParseNode(),
@@ -108,7 +108,7 @@ auto HandlePatternBinding(Context& context, Parse::Node parse_node) -> bool {
           SemIR::BindName{name_node, value_type_id, name_id, value_id});
       context.node_stack().Push(parse_node, bind_id);
 
-      if (context_parse_node_kind == Parse::NodeKind::ReturnedSpecifier) {
+      if (context_parse_node_kind == Parse::NodeKind::ReturnedModifier) {
         RegisterReturnedVar(context, bind_id);
       }
       break;

+ 3 - 4
toolchain/check/handle_return_statement.cpp

@@ -14,8 +14,7 @@ auto HandleReturnStatementStart(Context& context, Parse::Node parse_node)
   return true;
 }
 
-auto HandleReturnVarSpecifier(Context& context, Parse::Node parse_node)
-    -> bool {
+auto HandleReturnVarModifier(Context& context, Parse::Node parse_node) -> bool {
   // No action, just a bracketing node.
   context.node_stack().Push(parse_node);
   return true;
@@ -31,10 +30,10 @@ auto HandleReturnStatement(Context& context, Parse::Node parse_node) -> bool {
       BuildReturnWithNoExpr(context, parse_node);
       break;
 
-    case Parse::NodeKind::ReturnVarSpecifier:
+    case Parse::NodeKind::ReturnVarModifier:
       // This is a `return var;` statement.
       context.node_stack()
-          .PopAndDiscardSoloParseNode<Parse::NodeKind::ReturnVarSpecifier>();
+          .PopAndDiscardSoloParseNode<Parse::NodeKind::ReturnVarModifier>();
       context.node_stack()
           .PopAndDiscardSoloParseNode<Parse::NodeKind::ReturnStatementStart>();
       BuildReturnVar(context, parse_node);

+ 2 - 2
toolchain/check/handle_variable.cpp

@@ -15,7 +15,7 @@ auto HandleVariableIntroducer(Context& context, Parse::Node parse_node)
   return true;
 }
 
-auto HandleReturnedSpecifier(Context& context, Parse::Node parse_node) -> bool {
+auto HandleReturnedModifier(Context& context, Parse::Node parse_node) -> bool {
   // No action, just a bracketing node.
   context.node_stack().Push(parse_node);
   return true;
@@ -54,7 +54,7 @@ auto HandleVariableDecl(Context& context, Parse::Node parse_node) -> bool {
 
   // Pop the `returned` specifier if present.
   context.node_stack()
-      .PopAndDiscardSoloParseNodeIf<Parse::NodeKind::ReturnedSpecifier>();
+      .PopAndDiscardSoloParseNodeIf<Parse::NodeKind::ReturnedModifier>();
 
   // If there was an initializer, assign it to the storage.
   if (has_init) {

+ 4 - 2
toolchain/check/node_stack.h

@@ -328,7 +328,9 @@ class NodeStack {
         return IdKind::ClassId;
       case Parse::NodeKind::Name:
         return IdKind::NameId;
+      case Parse::NodeKind::AbstractModifier:
       case Parse::NodeKind::ArrayExprSemi:
+      case Parse::NodeKind::BaseModifier:
       case Parse::NodeKind::ClassIntroducer:
       case Parse::NodeKind::CodeBlockStart:
       case Parse::NodeKind::FunctionIntroducer:
@@ -338,9 +340,9 @@ class NodeStack {
       case Parse::NodeKind::ParamListStart:
       case Parse::NodeKind::ParenExprOrTupleLiteralStart:
       case Parse::NodeKind::QualifiedDecl:
-      case Parse::NodeKind::ReturnedSpecifier:
+      case Parse::NodeKind::ReturnedModifier:
       case Parse::NodeKind::ReturnStatementStart:
-      case Parse::NodeKind::ReturnVarSpecifier:
+      case Parse::NodeKind::ReturnVarModifier:
       case Parse::NodeKind::SelfValueName:
       case Parse::NodeKind::StructLiteralOrStructTypeLiteralStart:
       case Parse::NodeKind::VariableInitializer:

+ 43 - 0
toolchain/check/testdata/class/fail_abstract.carbon

@@ -0,0 +1,43 @@
+// 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
+
+abstract class Class {
+  var k: i32;
+}
+
+fn Make() -> Class {
+  // CHECK:STDERR: fail_abstract.carbon:[[@LINE+3]]:17: ERROR: Cannot construct instance of abstract class. Consider using `partial Class` instead.
+  // CHECK:STDERR:   return {.k = 1};
+  // CHECK:STDERR:                 ^
+  return {.k = 1};
+}
+
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.loc9: type = struct_type {.k: i32}
+// CHECK:STDOUT:   %.loc7: type = ptr_type {.k: i32}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file "fail_abstract.carbon" {
+// CHECK:STDOUT:   class_decl @Class, ()
+// CHECK:STDOUT:   %Class: type = class_type @Class
+// CHECK:STDOUT:   %Make: <function> = fn_decl @Make
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @Class {
+// CHECK:STDOUT:   %.loc8_8.1: type = unbound_field_type Class, i32
+// CHECK:STDOUT:   %.loc8_8.2: <unbound field of class Class> = field k, member0
+// CHECK:STDOUT:   %k: <unbound field of class Class> = bind_name k, %.loc8_8.2
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .k = %k
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Make() -> %return: Class {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %.loc15_16: i32 = int_literal 1
+// CHECK:STDOUT:   %.loc15_17: {.k: i32} = struct_literal (%.loc15_16)
+// CHECK:STDOUT:   return <error>
+// CHECK:STDOUT: }

+ 139 - 0
toolchain/check/testdata/class/fail_redeclaration_introducer.carbon

@@ -0,0 +1,139 @@
+// 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
+
+class A;
+// CHECK:STDERR: fail_redeclaration_introducer.carbon:[[@LINE+6]]:1: ERROR: Class redeclared with different inheritance kind.
+// CHECK:STDERR: base class A {}
+// CHECK:STDERR: ^
+// CHECK:STDERR: fail_redeclaration_introducer.carbon:[[@LINE-4]]:1: Previously declared here.
+// CHECK:STDERR: class A;
+// CHECK:STDERR: ^
+base class A {}
+
+class B;
+// CHECK:STDERR: fail_redeclaration_introducer.carbon:[[@LINE+6]]:1: ERROR: Class redeclared with different inheritance kind.
+// CHECK:STDERR: abstract class B {}
+// CHECK:STDERR: ^
+// CHECK:STDERR: fail_redeclaration_introducer.carbon:[[@LINE-4]]:1: Previously declared here.
+// CHECK:STDERR: class B;
+// CHECK:STDERR: ^
+abstract class B {}
+
+base class C;
+// CHECK:STDERR: fail_redeclaration_introducer.carbon:[[@LINE+6]]:1: ERROR: Class redeclared with different inheritance kind.
+// CHECK:STDERR: class C {}
+// CHECK:STDERR: ^
+// CHECK:STDERR: fail_redeclaration_introducer.carbon:[[@LINE-4]]:1: Previously declared here.
+// CHECK:STDERR: base class C;
+// CHECK:STDERR: ^
+class C {}
+
+base class D;
+// CHECK:STDERR: fail_redeclaration_introducer.carbon:[[@LINE+6]]:1: ERROR: Class redeclared with different inheritance kind.
+// CHECK:STDERR: abstract class D {}
+// CHECK:STDERR: ^
+// CHECK:STDERR: fail_redeclaration_introducer.carbon:[[@LINE-4]]:1: Previously declared here.
+// CHECK:STDERR: base class D;
+// CHECK:STDERR: ^
+abstract class D {}
+
+abstract class E;
+// CHECK:STDERR: fail_redeclaration_introducer.carbon:[[@LINE+6]]:1: ERROR: Class redeclared with different inheritance kind.
+// CHECK:STDERR: class E {}
+// CHECK:STDERR: ^
+// CHECK:STDERR: fail_redeclaration_introducer.carbon:[[@LINE-4]]:1: Previously declared here.
+// CHECK:STDERR: abstract class E;
+// CHECK:STDERR: ^
+class E {}
+
+abstract class F;
+// CHECK:STDERR: fail_redeclaration_introducer.carbon:[[@LINE+6]]:1: ERROR: Class redeclared with different inheritance kind.
+// CHECK:STDERR: base class F {}
+// CHECK:STDERR: ^
+// CHECK:STDERR: fail_redeclaration_introducer.carbon:[[@LINE-4]]:1: Previously declared here.
+// CHECK:STDERR: abstract class F;
+// CHECK:STDERR: ^
+base class F {}
+
+class G {}
+// CHECK:STDERR: fail_redeclaration_introducer.carbon:[[@LINE+6]]:1: ERROR: Class redeclared with different inheritance kind.
+// CHECK:STDERR: abstract class G;
+// CHECK:STDERR: ^
+// CHECK:STDERR: fail_redeclaration_introducer.carbon:[[@LINE-4]]:1: Previously declared here.
+// CHECK:STDERR: class G {}
+// CHECK:STDERR: ^
+abstract class G;
+// CHECK:STDERR: fail_redeclaration_introducer.carbon:[[@LINE+6]]:1: ERROR: Class redeclared with different inheritance kind.
+// CHECK:STDERR: base class G;
+// CHECK:STDERR: ^
+// CHECK:STDERR: fail_redeclaration_introducer.carbon:[[@LINE-11]]:1: Previously declared here.
+// CHECK:STDERR: class G {}
+// CHECK:STDERR: ^
+base class G;
+
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.loc14: type = struct_type {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file "fail_redeclaration_introducer.carbon" {
+// CHECK:STDOUT:   class_decl @A, ()
+// CHECK:STDOUT:   %A: type = class_type @A
+// CHECK:STDOUT:   class_decl @A, ()
+// CHECK:STDOUT:   class_decl @B, ()
+// CHECK:STDOUT:   %B: type = class_type @B
+// CHECK:STDOUT:   class_decl @B, ()
+// CHECK:STDOUT:   class_decl @C, ()
+// CHECK:STDOUT:   %C: type = class_type @C
+// CHECK:STDOUT:   class_decl @C, ()
+// CHECK:STDOUT:   class_decl @D, ()
+// CHECK:STDOUT:   %D: type = class_type @D
+// CHECK:STDOUT:   class_decl @D, ()
+// CHECK:STDOUT:   class_decl @E, ()
+// CHECK:STDOUT:   %E: type = class_type @E
+// CHECK:STDOUT:   class_decl @E, ()
+// CHECK:STDOUT:   class_decl @F, ()
+// CHECK:STDOUT:   %F: type = class_type @F
+// CHECK:STDOUT:   class_decl @F, ()
+// CHECK:STDOUT:   class_decl @G, ()
+// CHECK:STDOUT:   %G: type = class_type @G
+// CHECK:STDOUT:   class_decl @G, ()
+// CHECK:STDOUT:   class_decl @G, ()
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @A {
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @B {
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @C {
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @D {
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @E {
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @F {
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @G {
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT: }

+ 44 - 0
toolchain/check/testdata/class/redeclaration_introducer.carbon

@@ -0,0 +1,44 @@
+// 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
+
+class A;
+base class B;
+abstract class C;
+
+class A {}
+base class B {}
+abstract class C {}
+
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.loc11: type = struct_type {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file "redeclaration_introducer.carbon" {
+// CHECK:STDOUT:   class_decl @A, ()
+// CHECK:STDOUT:   %A: type = class_type @A
+// CHECK:STDOUT:   class_decl @B, ()
+// CHECK:STDOUT:   %B: type = class_type @B
+// CHECK:STDOUT:   class_decl @C, ()
+// CHECK:STDOUT:   %C: type = class_type @C
+// CHECK:STDOUT:   class_decl @A, ()
+// CHECK:STDOUT:   class_decl @B, ()
+// CHECK:STDOUT:   class_decl @C, ()
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @A {
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @B {
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @C {
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT: }

+ 3 - 0
toolchain/diagnostics/diagnostic_kind.def

@@ -132,8 +132,11 @@ CARBON_DIAGNOSTIC_KIND(AssignmentToNonAssignable)
 CARBON_DIAGNOSTIC_KIND(BreakOutsideLoop)
 CARBON_DIAGNOSTIC_KIND(ClassForwardDeclaredHere)
 CARBON_DIAGNOSTIC_KIND(ClassPreviousDefinition)
+CARBON_DIAGNOSTIC_KIND(ClassRedeclarationDifferentIntroducer)
+CARBON_DIAGNOSTIC_KIND(ClassRedeclarationDifferentIntroducerPrevious)
 CARBON_DIAGNOSTIC_KIND(ClassRedefinition)
 CARBON_DIAGNOSTIC_KIND(ClassIncompleteWithinDefinition)
+CARBON_DIAGNOSTIC_KIND(ConstructionOfAbstractClass)
 CARBON_DIAGNOSTIC_KIND(ContinueOutsideLoop)
 CARBON_DIAGNOSTIC_KIND(CopyOfUncopyableType)
 CARBON_DIAGNOSTIC_KIND(DereferenceOfNonPointer)

+ 19 - 6
toolchain/parse/context.h

@@ -18,6 +18,15 @@
 
 namespace Carbon::Parse {
 
+// An amount by which to look ahead of the current token. Lookahead should be
+// used sparingly, and unbounded lookahead should be avoided.
+//
+// TODO: Decide whether we want to avoid lookahead altogether.
+enum class Lookahead : int32_t {
+  CurrentToken = 0,
+  NextToken = 1,
+};
+
 // Context and shared functionality for parser handlers. See state.def for state
 // documentation.
 class Context {
@@ -197,14 +206,18 @@ class Context {
   auto ConsumeListToken(NodeKind comma_kind, Lex::TokenKind close_kind,
                         bool already_has_error) -> ListTokenKind;
 
-  // Gets the kind of the next token to be consumed.
-  auto PositionKind() const -> Lex::TokenKind {
-    return tokens_->GetKind(*position_);
+  // Gets the kind of the next token to be consumed. If `lookahead` is
+  // provided, it specifies which token to inspect.
+  auto PositionKind(Lookahead lookahead = Lookahead::CurrentToken) const
+      -> Lex::TokenKind {
+    return tokens_->GetKind(position_[static_cast<int32_t>(lookahead)]);
   }
 
-  // Tests whether the next token to be consumed is of the specified kind.
-  auto PositionIs(Lex::TokenKind kind) const -> bool {
-    return PositionKind() == kind;
+  // Tests whether the next token to be consumed is of the specified kind. If
+  // `lookahead` is provided, it specifies which token to inspect.
+  auto PositionIs(Lex::TokenKind kind,
+                  Lookahead lookahead = Lookahead::CurrentToken) const -> bool {
+    return PositionKind(lookahead) == kind;
   }
 
   // Pops the state and keeps the value for inspection.

+ 78 - 54
toolchain/parse/handle_decl_scope_loop.cpp

@@ -22,75 +22,99 @@ static auto HandleUnrecognizedDecl(Context& context) -> void {
 auto HandleDeclScopeLoop(Context& context) -> void {
   // This maintains the current state unless we're at the end of the scope.
 
-  switch (auto position_kind = context.PositionKind()) {
+  auto position_kind = context.PositionKind();
+  switch (position_kind) {
     case Lex::TokenKind::CloseCurlyBrace:
     case Lex::TokenKind::EndOfFile: {
       // This is the end of the scope, so the loop state ends.
       context.PopAndDiscardState();
-      break;
+      return;
     }
     // `import` and `package` manage their packaging state.
     case Lex::TokenKind::Import: {
       context.PushState(State::Import);
-      break;
+      return;
     }
     case Lex::TokenKind::Package: {
       context.PushState(State::Package);
+      return;
+    }
+    default: {
       break;
     }
-    default:
-      // Because a non-packaging keyword was encountered, packaging is complete.
-      // Misplaced packaging keywords may lead to this being re-triggered.
-      if (context.packaging_state() !=
-          Context::PackagingState::AfterNonPackagingDecl) {
-        if (!context.first_non_packaging_token().is_valid()) {
-          context.set_first_non_packaging_token(*context.position());
-        }
-        context.set_packaging_state(
-            Context::PackagingState::AfterNonPackagingDecl);
-      }
-      switch (position_kind) {
-        // Remaining keywords are only valid after imports are complete, and
-        // so all result in a `set_packaging_state` call. Note, this may not
-        // always be necessary but is probably cheaper than validating.
-        case Lex::TokenKind::Class: {
-          context.PushState(State::TypeIntroducerAsClass);
-          break;
-        }
-        case Lex::TokenKind::Constraint: {
-          context.PushState(State::TypeIntroducerAsNamedConstraint);
-          break;
-        }
-        case Lex::TokenKind::Fn: {
-          context.PushState(State::FunctionIntroducer);
-          break;
-        }
-        case Lex::TokenKind::Interface: {
-          context.PushState(State::TypeIntroducerAsInterface);
-          break;
-        }
-        case Lex::TokenKind::Namespace: {
-          context.PushState(State::Namespace);
-          break;
-        }
-        case Lex::TokenKind::Semi: {
-          context.AddLeafNode(NodeKind::EmptyDecl, context.Consume());
-          break;
-        }
-        case Lex::TokenKind::Var: {
-          context.PushState(State::VarAsDecl);
-          break;
-        }
-        case Lex::TokenKind::Let: {
-          context.PushState(State::Let);
-          break;
-        }
-        default: {
-          HandleUnrecognizedDecl(context);
-          break;
-        }
+  }
+
+  // Because a non-packaging keyword was encountered, packaging is complete.
+  // Misplaced packaging keywords may lead to this being re-triggered.
+  if (context.packaging_state() !=
+      Context::PackagingState::AfterNonPackagingDecl) {
+    if (!context.first_non_packaging_token().is_valid()) {
+      context.set_first_non_packaging_token(*context.position());
+    }
+    context.set_packaging_state(Context::PackagingState::AfterNonPackagingDecl);
+  }
+
+  // Remaining keywords are only valid after imports are complete, and so all
+  // result in a `set_packaging_state` call. Note, this may not always be
+  // necessary but is probably cheaper than validating.
+  switch (position_kind) {
+    case Lex::TokenKind::Abstract:
+    case Lex::TokenKind::Base: {
+      if (context.PositionIs(Lex::TokenKind::Class, Lookahead::NextToken)) {
+        context.PushState(State::TypeAfterIntroducerAsClass);
+        auto modifier_token = context.Consume();
+        auto class_token = context.Consume();
+        context.AddLeafNode(NodeKind::ClassIntroducer, class_token);
+        context.AddLeafNode(position_kind == Lex::TokenKind::Abstract
+                                ? NodeKind::AbstractModifier
+                                : NodeKind::BaseModifier,
+                            modifier_token);
+        return;
       }
+      break;
+    }
+    case Lex::TokenKind::Class: {
+      context.PushState(State::TypeAfterIntroducerAsClass);
+      context.AddLeafNode(NodeKind::ClassIntroducer, context.Consume());
+      return;
+    }
+    case Lex::TokenKind::Constraint: {
+      context.PushState(State::TypeAfterIntroducerAsNamedConstraint);
+      context.AddLeafNode(NodeKind::NamedConstraintIntroducer,
+                          context.Consume());
+      return;
+    }
+    case Lex::TokenKind::Fn: {
+      context.PushState(State::FunctionIntroducer);
+      return;
+    }
+    case Lex::TokenKind::Interface: {
+      context.PushState(State::TypeAfterIntroducerAsInterface);
+      context.AddLeafNode(NodeKind::InterfaceIntroducer, context.Consume());
+      return;
+    }
+    case Lex::TokenKind::Namespace: {
+      context.PushState(State::Namespace);
+      return;
+    }
+    case Lex::TokenKind::Semi: {
+      context.AddLeafNode(NodeKind::EmptyDecl, context.Consume());
+      return;
+    }
+    case Lex::TokenKind::Var: {
+      context.PushState(State::VarAsDecl);
+      return;
+    }
+    case Lex::TokenKind::Let: {
+      context.PushState(State::Let);
+      return;
+    }
+    default: {
+      break;
+    }
   }
+
+  HandleUnrecognizedDecl(context);
 }
 
 }  // namespace Carbon::Parse

+ 1 - 1
toolchain/parse/handle_statement.cpp

@@ -186,7 +186,7 @@ auto HandleStatementReturn(Context& context) -> void {
 
   if (auto var_token = context.ConsumeIf(Lex::TokenKind::Var)) {
     // `return var;`
-    context.AddLeafNode(NodeKind::ReturnVarSpecifier, *var_token);
+    context.AddLeafNode(NodeKind::ReturnVarModifier, *var_token);
   } else if (!context.PositionIs(Lex::TokenKind::Semi)) {
     // `return <expression>;`
     context.PushState(State::Expr);

+ 9 - 15
toolchain/parse/handle_type.cpp

@@ -6,31 +6,25 @@
 
 namespace Carbon::Parse {
 
-// Handles processing of a type's introducer.
-static auto HandleTypeIntroducer(Context& context, NodeKind introducer_kind,
-                                 State after_params_state) -> void {
+// Handles processing of a type declaration or definition after its introducer.
+static auto HandleTypeAfterIntroducer(Context& context,
+                                      State after_params_state) -> void {
   auto state = context.PopState();
-
-  context.AddLeafNode(introducer_kind, context.Consume());
-
   state.state = after_params_state;
   context.PushState(state);
   context.PushState(State::DeclNameAndParamsAsOptional, state.token);
 }
 
-auto HandleTypeIntroducerAsClass(Context& context) -> void {
-  HandleTypeIntroducer(context, NodeKind::ClassIntroducer,
-                       State::TypeAfterParamsAsClass);
+auto HandleTypeAfterIntroducerAsClass(Context& context) -> void {
+  HandleTypeAfterIntroducer(context, State::TypeAfterParamsAsClass);
 }
 
-auto HandleTypeIntroducerAsInterface(Context& context) -> void {
-  HandleTypeIntroducer(context, NodeKind::InterfaceIntroducer,
-                       State::TypeAfterParamsAsInterface);
+auto HandleTypeAfterIntroducerAsInterface(Context& context) -> void {
+  HandleTypeAfterIntroducer(context, State::TypeAfterParamsAsInterface);
 }
 
-auto HandleTypeIntroducerAsNamedConstraint(Context& context) -> void {
-  HandleTypeIntroducer(context, NodeKind::NamedConstraintIntroducer,
-                       State::TypeAfterParamsAsNamedConstraint);
+auto HandleTypeAfterIntroducerAsNamedConstraint(Context& context) -> void {
+  HandleTypeAfterIntroducer(context, State::TypeAfterParamsAsNamedConstraint);
 }
 
 // Handles processing after params, deciding whether it's a declaration or

+ 1 - 1
toolchain/parse/handle_var.cpp

@@ -19,7 +19,7 @@ static auto HandleVar(Context& context, State finish_state,
 
   context.AddLeafNode(NodeKind::VariableIntroducer, context.Consume());
   if (returned_token.is_valid()) {
-    context.AddLeafNode(NodeKind::ReturnedSpecifier, returned_token);
+    context.AddLeafNode(NodeKind::ReturnedModifier, returned_token);
   }
 
   context.PushState(State::PatternAsVariable);

+ 7 - 4
toolchain/parse/node_kind.def

@@ -225,7 +225,7 @@ CARBON_PARSE_NODE_KIND_BRACKET(LetDecl, LetIntroducer,
 
 // `var` and `returned var`:
 //   VariableIntroducer
-//   _optional_ ReturnedSpecifier
+//   _optional_ ReturnedModifier
 //   _external_: PatternBinding
 //   _optional_ VariableInitializer
 //   _optional_ _external_: expression
@@ -236,7 +236,7 @@ CARBON_PARSE_NODE_KIND_BRACKET(LetDecl, LetIntroducer,
 CARBON_PARSE_NODE_KIND_CHILD_COUNT(VariableIntroducer, 0,
                                    CARBON_TOKEN(Var)
                                        CARBON_IF_ERROR(CARBON_TOKEN(Returned)))
-CARBON_PARSE_NODE_KIND_CHILD_COUNT(ReturnedSpecifier, 0, CARBON_TOKEN(Returned))
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(ReturnedModifier, 0, CARBON_TOKEN(Returned))
 CARBON_PARSE_NODE_KIND_CHILD_COUNT(VariableInitializer, 0, CARBON_TOKEN(Equal))
 CARBON_PARSE_NODE_KIND_BRACKET(VariableDecl, VariableIntroducer,
                                CARBON_TOKEN(Semi)
@@ -267,11 +267,11 @@ CARBON_PARSE_NODE_KIND_CHILD_COUNT(ContinueStatement, 1,
 
 // `return`:
 //   ReturnStatementStart
-//   _optional_ ReturnVarSpecifier or _external_: expression
+//   _optional_ ReturnVarModifier or _external_: expression
 // ReturnStatement
 CARBON_PARSE_NODE_KIND_CHILD_COUNT(ReturnStatementStart, 0,
                                    CARBON_TOKEN(Return))
-CARBON_PARSE_NODE_KIND_CHILD_COUNT(ReturnVarSpecifier, 0, CARBON_TOKEN(Var))
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(ReturnVarModifier, 0, CARBON_TOKEN(Var))
 CARBON_PARSE_NODE_KIND_BRACKET(ReturnStatement, ReturnStatementStart,
                                CARBON_TOKEN(Semi)
                                    CARBON_IF_ERROR(CARBON_TOKEN(Return)))
@@ -532,6 +532,7 @@ CARBON_PARSE_NODE_KIND_BRACKET(StructTypeLiteral,
 
 // `class`:
 //     ClassIntroducer
+//     _optional_ AbstractModifier or BaseModifier
 //     _external_: Name or QualifiedDecl
 //   ClassDefinitionStart
 //   _external_: declarations
@@ -541,6 +542,8 @@ CARBON_PARSE_NODE_KIND_BRACKET(StructTypeLiteral,
 // ClassDefinitionStart and later nodes are removed and replaced by
 // ClassDecl.
 CARBON_PARSE_NODE_KIND_CHILD_COUNT(ClassIntroducer, 0, CARBON_TOKEN(Class))
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(AbstractModifier, 0, CARBON_TOKEN(Abstract))
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(BaseModifier, 0, CARBON_TOKEN(Base))
 CARBON_PARSE_NODE_KIND_BRACKET(ClassDefinitionStart, ClassIntroducer,
                                CARBON_TOKEN(OpenCurlyBrace))
 CARBON_PARSE_NODE_KIND_BRACKET(ClassDefinition, ClassDefinitionStart,

+ 15 - 11
toolchain/parse/state.def

@@ -283,14 +283,18 @@ CARBON_PARSE_STATE(DeclNameAndParamsAfterImplicit)
 // Handles processing of a declaration scope. Things like fn, class, interface,
 // and so on.
 //
-//  class ...
-// ^
-//   1. TypeIntroducerAsClass
+// class ...
+// ^~~~~
+// abstract class ...
+// ^~~~~~~~~~~~~~
+// base class ...
+// ^~~~~~~~~~
+//   1. TypeAfterIntroducerAsClass
 //   2. DeclScopeLoop
 //
-//  constraint ...
-// ^
-//   1. TypeIntroducerAsNamedConstraint
+// constraint ...
+// ^~~~~~~~~~
+//   1. TypeAfterIntroducerAsNamedConstraint
 //   2. DeclScopeLoop
 //
 //  fn ...
@@ -298,9 +302,9 @@ CARBON_PARSE_STATE(DeclNameAndParamsAfterImplicit)
 //   1. FunctionIntroducer
 //   2. DeclScopeLoop
 //
-//  interface ...
-// ^
-//   1. TypeIntroducerAsInterface
+// interface ...
+// ^~~~~~~~~
+//   1. TypeAfterIntroducerAsInterface
 //   2. DeclScopeLoop
 //
 //  namespace ...
@@ -989,13 +993,13 @@ CARBON_PARSE_STATE(StatementWhileBlockFinish)
 CARBON_PARSE_STATE_VARIANTS3(TypeDefinitionFinish, Class, Interface,
                              NamedConstraint)
 
-// Handles processing of a type's introducer.
+// Handles processing of a type after its introducer.
 //
 // class/interface/constraint ...
 // ^~~~~~~~~~~~~~~~~~~~~~~~~~
 //   1. DeclNameAndParamsAsOptional
 //   2. TypeAfterParamsAs(Class|Interface|NamedConstraint)
-CARBON_PARSE_STATE_VARIANTS3(TypeIntroducer, Class, Interface, NamedConstraint)
+CARBON_PARSE_STATE_VARIANTS3(TypeAfterIntroducer, Class, Interface, NamedConstraint)
 
 // Handles processing of a type after its optional parameters.
 //

+ 31 - 0
toolchain/parse/testdata/class/fail_introducer.carbon

@@ -0,0 +1,31 @@
+// 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
+
+// TODO: Improve the diagnostic to say that the `abstract` modifier can't be
+// used on an `interface`.
+// CHECK:STDERR: fail_introducer.carbon:[[@LINE+3]]:1: ERROR: Unrecognized declaration introducer.
+// CHECK:STDERR: abstract interface I;
+// CHECK:STDERR: ^
+abstract interface I;
+
+// CHECK:STDERR: fail_introducer.carbon:[[@LINE+3]]:1: ERROR: Unrecognized declaration introducer.
+// CHECK:STDERR: base fn F();
+// CHECK:STDERR: ^
+base fn F();
+
+// CHECK:STDERR: fail_introducer.carbon:[[@LINE+3]]:1: ERROR: Unrecognized declaration introducer.
+// CHECK:STDERR: abstract base class C;
+// CHECK:STDERR: ^
+abstract base class C;
+
+// CHECK:STDOUT: - filename: fail_introducer.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:     {kind: 'EmptyDecl', text: ';', has_error: yes},
+// CHECK:STDOUT:     {kind: 'EmptyDecl', text: ';', has_error: yes},
+// CHECK:STDOUT:     {kind: 'EmptyDecl', text: ';', has_error: yes},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]

+ 44 - 0
toolchain/parse/testdata/class/introducer.carbon

@@ -0,0 +1,44 @@
+// 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
+
+class A;
+base class B;
+abstract class C;
+
+class A {}
+base class B {}
+abstract class C {}
+
+// CHECK:STDOUT: - filename: introducer.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:       {kind: 'ClassIntroducer', text: 'class'},
+// CHECK:STDOUT:       {kind: 'Name', text: 'A'},
+// CHECK:STDOUT:     {kind: 'ClassDecl', text: ';', subtree_size: 3},
+// CHECK:STDOUT:       {kind: 'ClassIntroducer', text: 'class'},
+// CHECK:STDOUT:       {kind: 'BaseModifier', text: 'base'},
+// CHECK:STDOUT:       {kind: 'Name', text: 'B'},
+// CHECK:STDOUT:     {kind: 'ClassDecl', text: ';', subtree_size: 4},
+// CHECK:STDOUT:       {kind: 'ClassIntroducer', text: 'class'},
+// CHECK:STDOUT:       {kind: 'AbstractModifier', text: 'abstract'},
+// CHECK:STDOUT:       {kind: 'Name', text: 'C'},
+// CHECK:STDOUT:     {kind: 'ClassDecl', text: ';', subtree_size: 4},
+// CHECK:STDOUT:         {kind: 'ClassIntroducer', text: 'class'},
+// CHECK:STDOUT:         {kind: 'Name', text: 'A'},
+// CHECK:STDOUT:       {kind: 'ClassDefinitionStart', text: '{', subtree_size: 3},
+// CHECK:STDOUT:     {kind: 'ClassDefinition', text: '}', subtree_size: 4},
+// CHECK:STDOUT:         {kind: 'ClassIntroducer', text: 'class'},
+// CHECK:STDOUT:         {kind: 'BaseModifier', text: 'base'},
+// CHECK:STDOUT:         {kind: 'Name', text: 'B'},
+// CHECK:STDOUT:       {kind: 'ClassDefinitionStart', text: '{', subtree_size: 4},
+// CHECK:STDOUT:     {kind: 'ClassDefinition', text: '}', subtree_size: 5},
+// CHECK:STDOUT:         {kind: 'ClassIntroducer', text: 'class'},
+// CHECK:STDOUT:         {kind: 'AbstractModifier', text: 'abstract'},
+// CHECK:STDOUT:         {kind: 'Name', text: 'C'},
+// CHECK:STDOUT:       {kind: 'ClassDefinitionStart', text: '{', subtree_size: 4},
+// CHECK:STDOUT:     {kind: 'ClassDefinition', text: '}', subtree_size: 5},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]

+ 1 - 1
toolchain/parse/testdata/for/fail_returned_var.carbon

@@ -29,7 +29,7 @@ fn foo() -> i32 {
 // CHECK:STDOUT:         {kind: 'ForHeader', text: ')', has_error: yes, subtree_size: 3},
 // CHECK:STDOUT:           {kind: 'CodeBlockStart', text: '{'},
 // CHECK:STDOUT:             {kind: 'ReturnStatementStart', text: 'return'},
-// CHECK:STDOUT:             {kind: 'ReturnVarSpecifier', text: 'var'},
+// CHECK:STDOUT:             {kind: 'ReturnVarModifier', text: 'var'},
 // CHECK:STDOUT:           {kind: 'ReturnStatement', text: ';', subtree_size: 3},
 // CHECK:STDOUT:         {kind: 'CodeBlock', text: '}', subtree_size: 5},
 // CHECK:STDOUT:       {kind: 'ForStatement', text: 'for', subtree_size: 9},

+ 1 - 1
toolchain/parse/testdata/return/fail_var_no_semi.carbon

@@ -20,7 +20,7 @@ fn F() {
 // CHECK:STDOUT:         {kind: 'ParamList', text: ')', subtree_size: 2},
 // CHECK:STDOUT:       {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 5},
 // CHECK:STDOUT:         {kind: 'ReturnStatementStart', text: 'return'},
-// CHECK:STDOUT:         {kind: 'ReturnVarSpecifier', text: 'var'},
+// CHECK:STDOUT:         {kind: 'ReturnVarModifier', text: 'var'},
 // CHECK:STDOUT:       {kind: 'ReturnStatement', text: 'return', has_error: yes, subtree_size: 3},
 // CHECK:STDOUT:     {kind: 'FunctionDefinition', text: '}', subtree_size: 9},
 // CHECK:STDOUT:     {kind: 'FileEnd', text: ''},

+ 2 - 2
toolchain/parse/testdata/return/returned_var.carbon

@@ -20,7 +20,7 @@ fn F() -> String {
 // CHECK:STDOUT:         {kind: 'ReturnType', text: '->', subtree_size: 2},
 // CHECK:STDOUT:       {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 7},
 // CHECK:STDOUT:         {kind: 'VariableIntroducer', text: 'var'},
-// CHECK:STDOUT:         {kind: 'ReturnedSpecifier', text: 'returned'},
+// CHECK:STDOUT:         {kind: 'ReturnedModifier', text: 'returned'},
 // CHECK:STDOUT:           {kind: 'Name', text: 's'},
 // CHECK:STDOUT:           {kind: 'Literal', text: 'String'},
 // CHECK:STDOUT:         {kind: 'PatternBinding', text: ':', subtree_size: 3},
@@ -28,7 +28,7 @@ fn F() -> String {
 // CHECK:STDOUT:         {kind: 'Literal', text: '"hello"'},
 // CHECK:STDOUT:       {kind: 'VariableDecl', text: ';', subtree_size: 8},
 // CHECK:STDOUT:         {kind: 'ReturnStatementStart', text: 'return'},
-// CHECK:STDOUT:         {kind: 'ReturnVarSpecifier', text: 'var'},
+// CHECK:STDOUT:         {kind: 'ReturnVarModifier', text: 'var'},
 // CHECK:STDOUT:       {kind: 'ReturnStatement', text: ';', subtree_size: 3},
 // CHECK:STDOUT:     {kind: 'FunctionDefinition', text: '}', subtree_size: 19},
 // CHECK:STDOUT:     {kind: 'FileEnd', text: ''},

+ 12 - 0
toolchain/sem_ir/file.h

@@ -62,6 +62,15 @@ struct Function : public Printable<Function> {
 
 // A class.
 struct Class : public Printable<Class> {
+  enum InheritanceKind {
+    // `abstract class`
+    Abstract,
+    // `base class`
+    Base,
+    // `class`
+    Final,
+  };
+
   auto Print(llvm::raw_ostream& out) const -> void {
     out << "{name: " << name_id;
     out << "}";
@@ -80,6 +89,9 @@ struct Class : public Printable<Class> {
   TypeId self_type_id;
   // The first declaration of the class. This is a ClassDecl.
   InstId decl_id = InstId::Invalid;
+  // The kind of inheritance that this class supports.
+  // TODO: The rules here are not yet decided. See #3384.
+  InheritanceKind inheritance_kind;
 
   // The following members are set at the `{` of the class definition.