diff --git a/toolchain/check/convert.cpp b/toolchain/check/convert.cpp index 4421d0da9087..d1b3be28e7c9 100644 --- a/toolchain/check/convert.cpp +++ b/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( 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. diff --git a/toolchain/check/handle_class.cpp b/toolchain/check/handle_class.cpp index a4085d39374c..499d1745dfd8 100644 --- a/toolchain/check/handle_class.cpp +++ b/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 { auto name_context = context.decl_name_stack().FinishName(); - auto class_keyword = + auto introducer = context.node_stack().PeekParseNode(); + bool abstract = context.node_stack() - .PopForSoloParseNode(); + .PopAndDiscardSoloParseNodeIf(); + bool base = + context.node_stack() + .PopAndDiscardSoloParseNodeIf(); + context.node_stack() + .PopAndDiscardSoloParseNode(); 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()) { // 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})); } diff --git a/toolchain/check/handle_pattern_binding.cpp b/toolchain/check/handle_pattern_binding.cpp index d7bebbae51b4..59e408287d26 100644 --- a/toolchain/check/handle_pattern_binding.cpp +++ b/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(); @@ -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; diff --git a/toolchain/check/handle_return_statement.cpp b/toolchain/check/handle_return_statement.cpp index 60a2fa753953..9cf3e2d83deb 100644 --- a/toolchain/check/handle_return_statement.cpp +++ b/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(); + .PopAndDiscardSoloParseNode(); context.node_stack() .PopAndDiscardSoloParseNode(); BuildReturnVar(context, parse_node); diff --git a/toolchain/check/handle_variable.cpp b/toolchain/check/handle_variable.cpp index 00d1108e8d7d..2ffe808de87e 100644 --- a/toolchain/check/handle_variable.cpp +++ b/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(); + .PopAndDiscardSoloParseNodeIf(); // If there was an initializer, assign it to the storage. if (has_init) { diff --git a/toolchain/check/node_stack.h b/toolchain/check/node_stack.h index a872ef809a94..f1f2b19ddf63 100644 --- a/toolchain/check/node_stack.h +++ b/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: diff --git a/toolchain/check/testdata/class/fail_abstract.carbon b/toolchain/check/testdata/class/fail_abstract.carbon new file mode 100644 index 000000000000..b706ca54adc6 --- /dev/null +++ b/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: = 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: = field k, member0 +// CHECK:STDOUT: %k: = 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 +// CHECK:STDOUT: } diff --git a/toolchain/check/testdata/class/fail_redeclaration_introducer.carbon b/toolchain/check/testdata/class/fail_redeclaration_introducer.carbon new file mode 100644 index 000000000000..26e1a93e45c9 --- /dev/null +++ b/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: } diff --git a/toolchain/check/testdata/class/redeclaration_introducer.carbon b/toolchain/check/testdata/class/redeclaration_introducer.carbon new file mode 100644 index 000000000000..2328c74edd9c --- /dev/null +++ b/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: } diff --git a/toolchain/diagnostics/diagnostic_kind.def b/toolchain/diagnostics/diagnostic_kind.def index f8039e83410e..27780527c63a 100644 --- a/toolchain/diagnostics/diagnostic_kind.def +++ b/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) diff --git a/toolchain/parse/context.h b/toolchain/parse/context.h index 9346f88f7cda..425fe02271ba 100644 --- a/toolchain/parse/context.h +++ b/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(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. diff --git a/toolchain/parse/handle_decl_scope_loop.cpp b/toolchain/parse/handle_decl_scope_loop.cpp index f53fe1818e07..c9ecca9df885 100644 --- a/toolchain/parse/handle_decl_scope_loop.cpp +++ b/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 diff --git a/toolchain/parse/handle_statement.cpp b/toolchain/parse/handle_statement.cpp index 5a8b47a230b3..20843e513b1d 100644 --- a/toolchain/parse/handle_statement.cpp +++ b/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 ;` context.PushState(State::Expr); diff --git a/toolchain/parse/handle_type.cpp b/toolchain/parse/handle_type.cpp index a0c199ef5457..754a738a2541 100644 --- a/toolchain/parse/handle_type.cpp +++ b/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 diff --git a/toolchain/parse/handle_var.cpp b/toolchain/parse/handle_var.cpp index cf1e6c492912..05a4b519cf60 100644 --- a/toolchain/parse/handle_var.cpp +++ b/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); diff --git a/toolchain/parse/node_kind.def b/toolchain/parse/node_kind.def index 0a985de43717..0a8a543b21d4 100644 --- a/toolchain/parse/node_kind.def +++ b/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, diff --git a/toolchain/parse/state.def b/toolchain/parse/state.def index be26489b14c4..a8737ac4913d 100644 --- a/toolchain/parse/state.def +++ b/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. // diff --git a/toolchain/parse/testdata/class/fail_introducer.carbon b/toolchain/parse/testdata/class/fail_introducer.carbon new file mode 100644 index 000000000000..a5b5d934c7cc --- /dev/null +++ b/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: ] diff --git a/toolchain/parse/testdata/class/introducer.carbon b/toolchain/parse/testdata/class/introducer.carbon new file mode 100644 index 000000000000..5db7a77afa24 --- /dev/null +++ b/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: ] diff --git a/toolchain/parse/testdata/for/fail_returned_var.carbon b/toolchain/parse/testdata/for/fail_returned_var.carbon index 0783253a36b6..d8961fb4e079 100644 --- a/toolchain/parse/testdata/for/fail_returned_var.carbon +++ b/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}, diff --git a/toolchain/parse/testdata/return/fail_var_no_semi.carbon b/toolchain/parse/testdata/return/fail_var_no_semi.carbon index 89805366f74f..0348788ceeb4 100644 --- a/toolchain/parse/testdata/return/fail_var_no_semi.carbon +++ b/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: ''}, diff --git a/toolchain/parse/testdata/return/returned_var.carbon b/toolchain/parse/testdata/return/returned_var.carbon index e281c4a62c36..66c31e23662a 100644 --- a/toolchain/parse/testdata/return/returned_var.carbon +++ b/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: ''}, diff --git a/toolchain/sem_ir/file.h b/toolchain/sem_ir/file.h index b3e1078641ab..1d22ee7cc8ab 100644 --- a/toolchain/sem_ir/file.h +++ b/toolchain/sem_ir/file.h @@ -62,6 +62,15 @@ struct Function : public Printable { // A class. struct Class : public Printable { + 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 { 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.