diff --git a/testing/file_test/file_test_base.cpp b/testing/file_test/file_test_base.cpp index 71c2f7011f51..89f41b6eb070 100644 --- a/testing/file_test/file_test_base.cpp +++ b/testing/file_test/file_test_base.cpp @@ -278,7 +278,7 @@ static auto TryConsumeConflictMarker(llvm::StringRef line, bool* inside_conflict_marker) -> ErrorOr { bool is_start = line.starts_with("<<<<<<<"); - bool is_middle = line.starts_with("======="); + bool is_middle = line.starts_with("=======") || line.starts_with("|||||||"); bool is_end = line.starts_with(">>>>>>>"); // When running the test, any conflict marker is an error. diff --git a/toolchain/check/context.cpp b/toolchain/check/context.cpp index bb70a62b84c6..8d8cf9566cb5 100644 --- a/toolchain/check/context.cpp +++ b/toolchain/check/context.cpp @@ -360,6 +360,7 @@ auto Context::GetConstantValue(SemIR::InstId inst_id) -> SemIR::InstId { inst_id = inst.As().value_id; break; + case SemIR::Base::Kind: case SemIR::Field::Kind: case SemIR::FunctionDecl::Kind: return inst_id; @@ -836,6 +837,7 @@ class TypeCompleter { case SemIR::ArrayIndex::Kind: case SemIR::ArrayInit::Kind: case SemIR::Assign::Kind: + case SemIR::Base::Kind: case SemIR::BinaryOperatorAdd::Kind: case SemIR::BindName::Kind: case SemIR::BindValue::Kind: diff --git a/toolchain/check/decl_name_stack.cpp b/toolchain/check/decl_name_stack.cpp index cb2a0d452a2a..429b7b78fea1 100644 --- a/toolchain/check/decl_name_stack.cpp +++ b/toolchain/check/decl_name_stack.cpp @@ -36,8 +36,7 @@ auto DeclNameStack::FinishName() -> NameContext { .PopAndDiscardSoloParseNode(); } else { // The name had no qualifiers, so we need to process the node now. - auto [parse_node, name_id] = - context_->node_stack().PopWithParseNode(); + auto [parse_node, name_id] = context_->node_stack().PopNameWithParseNode(); ApplyNameQualifier(parse_node, name_id); } diff --git a/toolchain/check/handle_binding_pattern.cpp b/toolchain/check/handle_binding_pattern.cpp index e2aac1123b36..6233d895ffea 100644 --- a/toolchain/check/handle_binding_pattern.cpp +++ b/toolchain/check/handle_binding_pattern.cpp @@ -55,8 +55,7 @@ auto HandleBindingPattern(Context& context, Parse::NodeId parse_node) -> bool { // TODO: Handle `_` bindings. // Every other kind of pattern binding has a name. - auto [name_node, name_id] = - context.node_stack().PopWithParseNode(); + auto [name_node, name_id] = context.node_stack().PopNameWithParseNode(); // Allocate an instruction of the appropriate kind, linked to the name for // error locations. diff --git a/toolchain/check/handle_class.cpp b/toolchain/check/handle_class.cpp index 22433a06dce1..86330f78086b 100644 --- a/toolchain/check/handle_class.cpp +++ b/toolchain/check/handle_class.cpp @@ -3,6 +3,7 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception #include "toolchain/check/context.h" +#include "toolchain/check/convert.h" namespace Carbon::Check { @@ -166,6 +167,99 @@ auto HandleClassDefinitionStart(Context& context, Parse::NodeId parse_node) return true; } +auto HandleBaseIntroducer(Context& /*context*/, Parse::NodeId /*parse_node*/) + -> bool { + return true; +} + +auto HandleBaseColon(Context& /*context*/, Parse::NodeId /*parse_node*/) + -> bool { + return true; +} + +auto HandleBaseDecl(Context& context, Parse::NodeId parse_node) -> bool { + auto base_type_expr_id = context.node_stack().PopExpr(); + + auto enclosing_class_decl = context.GetCurrentScopeAs(); + if (!enclosing_class_decl) { + CARBON_DIAGNOSTIC(BaseOutsideClass, Error, + "`base` declaration can only be used in a class."); + context.emitter().Emit(parse_node, BaseOutsideClass); + return true; + } + + auto& class_info = context.classes().Get(enclosing_class_decl->class_id); + if (class_info.base_id.is_valid()) { + CARBON_DIAGNOSTIC(BaseRepeated, Error, + "Multiple `base` declarations in class. Multiple " + "inheritance is not permitted."); + CARBON_DIAGNOSTIC(BasePrevious, Note, + "Previous `base` declaration is here."); + context.emitter() + .Build(parse_node, BaseRepeated) + .Note(context.insts().Get(class_info.base_id).parse_node(), + BasePrevious) + .Emit(); + return true; + } + + auto base_type_id = ExprAsType(context, parse_node, base_type_expr_id); + if (!context.TryToCompleteType(base_type_id, [&] { + CARBON_DIAGNOSTIC(IncompleteTypeInBaseDecl, Error, + "Base `{0}` is an incomplete type.", std::string); + return context.emitter().Build( + parse_node, IncompleteTypeInBaseDecl, + context.sem_ir().StringifyType(base_type_id, true)); + })) { + base_type_id = SemIR::TypeId::Error; + } + + if (base_type_id != SemIR::TypeId::Error) { + // For now, we treat all types that aren't introduced by a `class` + // declaration as being final classes. + // TODO: Once we have a better idea of which types are considered to be + // classes, produce a better diagnostic for deriving from a non-class type. + auto base_class = + context.insts() + .Get(context.sem_ir().GetTypeAllowBuiltinTypes(base_type_id)) + .TryAs(); + if (!base_class || + context.classes().Get(base_class->class_id).inheritance_kind == + SemIR::Class::Final) { + CARBON_DIAGNOSTIC(BaseIsFinal, Error, + "Deriving from final type `{0}`. Base type must be an " + "`abstract` or `base` class.", + std::string); + context.emitter().Emit( + parse_node, BaseIsFinal, + context.sem_ir().StringifyType(base_type_id, true)); + } + } + + // The `base` value in the class scope has an unbound element type. Instance + // binding will be performed when it's found by name lookup into an instance. + auto field_type_inst_id = context.AddInst(SemIR::UnboundElementType{ + parse_node, context.GetBuiltinType(SemIR::BuiltinKind::TypeType), + class_info.self_type_id, base_type_id}); + auto field_type_id = context.CanonicalizeType(field_type_inst_id); + class_info.base_id = context.AddInst(SemIR::Base{ + parse_node, field_type_id, base_type_id, + SemIR::ElementIndex( + context.args_type_info_stack().PeekCurrentBlockContents().size())}); + + // Add a corresponding field to the object representation of the class. + // TODO: Consider whether we want to use `partial T` here. + context.args_type_info_stack().AddInst( + SemIR::StructTypeField{parse_node, SemIR::NameId::Base, base_type_id}); + + // Bind the name `base` in the class to the base field. + context.decl_name_stack().AddNameToLookup( + context.decl_name_stack().MakeUnqualifiedName(parse_node, + SemIR::NameId::Base), + class_info.base_id); + return true; +} + auto HandleClassDefinition(Context& context, Parse::NodeId parse_node) -> bool { auto fields_id = context.args_type_info_stack().Pop(); auto class_id = diff --git a/toolchain/check/handle_name.cpp b/toolchain/check/handle_name.cpp index c4ca23eb6c4a..71876af98c57 100644 --- a/toolchain/check/handle_name.cpp +++ b/toolchain/check/handle_name.cpp @@ -55,9 +55,22 @@ static auto GetExprValueForLookupResult(Context& context, return lookup_result_id; } +static auto GetClassElementIndex(Context& context, SemIR::InstId element_id) + -> SemIR::ElementIndex { + auto element_inst = context.insts().Get(element_id); + if (auto field = element_inst.TryAs()) { + return field->index; + } + if (auto base = element_inst.TryAs()) { + return base->index; + } + CARBON_FATAL() << "Unexpected value " << element_inst + << " in class element name"; +} + auto HandleMemberAccessExpr(Context& context, Parse::NodeId parse_node) -> bool { - SemIR::NameId name_id = context.node_stack().Pop(); + SemIR::NameId name_id = context.node_stack().PopName(); auto base_id = context.node_stack().PopExpr(); // If the base is a name scope, such as a class or namespace, perform lookup @@ -112,32 +125,29 @@ auto HandleMemberAccessExpr(Context& context, Parse::NodeId parse_node) auto member_type_id = context.insts().Get(member_id).type_id(); auto member_type_inst = context.insts().Get( context.sem_ir().GetTypeAllowBuiltinTypes(member_type_id)); - if (auto unbound_field_type = + if (auto unbound_element_type = member_type_inst.TryAs()) { - // TODO: Check that the unbound field type describes a member of this + // TODO: Check that the unbound element type describes a member of this // class. Perform a conversion of the base if necessary. - // Find the named field and build a field access expression. - auto field_id = context.GetConstantValue(member_id); - CARBON_CHECK(field_id.is_valid()) + // Find the specified element, which could be either a field or a base + // class, and build an element access expression. + auto element_id = context.GetConstantValue(member_id); + CARBON_CHECK(element_id.is_valid()) << "Non-constant value " << context.insts().Get(member_id) - << " of unbound field type"; - auto field = context.insts().Get(field_id).TryAs(); - CARBON_CHECK(field) - << "Unexpected value " << context.insts().Get(field_id) - << " for field name expression"; + << " of unbound element type"; + auto index = GetClassElementIndex(context, element_id); auto access_id = context.AddInst(SemIR::ClassElementAccess{ - parse_node, unbound_field_type->element_type_id, base_id, - field->index}); + parse_node, unbound_element_type->element_type_id, base_id, index}); if (SemIR::GetExprCategory(context.sem_ir(), base_id) == SemIR::ExprCategory::Value && SemIR::GetExprCategory(context.sem_ir(), access_id) != SemIR::ExprCategory::Value) { - // Class field access on a value expression produces an ephemeral - // reference if the class's value representation is a pointer to the - // object representation. Add a value binding in that case so that the - // expression category of the result matches the expression category - // of the base. + // Class element access on a value expression produces an + // ephemeral reference if the class's value representation is a + // pointer to the object representation. Add a value binding in + // that case so that the expression category of the result + // matches the expression category of the base. access_id = ConvertToValueExpr(context, access_id); } context.node_stack().Push(parse_node, access_id); @@ -223,17 +233,46 @@ auto HandlePointerMemberAccessExpr(Context& context, Parse::NodeId parse_node) return context.TODO(parse_node, "HandlePointerMemberAccessExpr"); } +static auto GetIdentifierAsName(Context& context, Parse::NodeId parse_node) + -> std::optional { + auto token = context.parse_tree().node_token(parse_node); + if (context.tokens().GetKind(token) != Lex::TokenKind::Identifier) { + CARBON_CHECK(context.parse_tree().node_has_error(parse_node)); + return std::nullopt; + } + return SemIR::NameId::ForIdentifier(context.tokens().GetIdentifier(token)); +} + auto HandleName(Context& context, Parse::NodeId parse_node) -> bool { - auto name_id = SemIR::NameId::ForIdentifier(context.tokens().GetIdentifier( - context.parse_tree().node_token(parse_node))); // The parent is responsible for binding the name. - context.node_stack().Push(parse_node, name_id); + auto name_id = GetIdentifierAsName(context, parse_node); + if (!name_id) { + return context.TODO(parse_node, "Error recovery from keyword name."); + } + context.node_stack().Push(parse_node, *name_id); return true; } auto HandleNameExpr(Context& context, Parse::NodeId parse_node) -> bool { - auto name_id = SemIR::NameId::ForIdentifier(context.tokens().GetIdentifier( - context.parse_tree().node_token(parse_node))); + auto name_id = GetIdentifierAsName(context, parse_node); + if (!name_id) { + return context.TODO(parse_node, "Error recovery from keyword name."); + } + auto value_id = context.LookupUnqualifiedName(parse_node, *name_id); + value_id = GetExprValueForLookupResult(context, value_id); + auto value = context.insts().Get(value_id); + context.AddInstAndPush(parse_node, SemIR::NameRef{parse_node, value.type_id(), + *name_id, value_id}); + return true; +} + +auto HandleBaseName(Context& context, Parse::NodeId parse_node) -> bool { + context.node_stack().Push(parse_node, SemIR::NameId::Base); + return true; +} + +auto HandleBaseNameExpr(Context& context, Parse::NodeId parse_node) -> bool { + auto name_id = SemIR::NameId::Base; auto value_id = context.LookupUnqualifiedName(parse_node, name_id); value_id = GetExprValueForLookupResult(context, value_id); auto value = context.insts().Get(value_id); @@ -243,8 +282,7 @@ auto HandleNameExpr(Context& context, Parse::NodeId parse_node) -> bool { } auto HandleQualifiedDecl(Context& context, Parse::NodeId parse_node) -> bool { - auto [parse_node2, name_id2] = - context.node_stack().PopWithParseNode(); + auto [parse_node2, name_id2] = context.node_stack().PopNameWithParseNode(); Parse::NodeId parse_node1 = context.node_stack().PeekParseNode(); switch (context.parse_tree().node_kind(parse_node1)) { diff --git a/toolchain/check/handle_struct.cpp b/toolchain/check/handle_struct.cpp index 5bf9a0e4265b..d6fa8b88d714 100644 --- a/toolchain/check/handle_struct.cpp +++ b/toolchain/check/handle_struct.cpp @@ -15,9 +15,7 @@ auto HandleStructComma(Context& context, Parse::NodeId /*parse_node*/) -> bool { auto HandleStructFieldDesignator(Context& context, Parse::NodeId /*parse_node*/) -> bool { // This leaves the designated name on top because the `.` isn't interesting. - CARBON_CHECK( - context.parse_tree().node_kind(context.node_stack().PeekParseNode()) == - Parse::NodeKind::Name); + CARBON_CHECK(context.node_stack().PeekIsName()); return true; } @@ -25,8 +23,7 @@ auto HandleStructFieldType(Context& context, Parse::NodeId parse_node) -> bool { auto [type_node, type_id] = context.node_stack().PopExprWithParseNode(); SemIR::TypeId cast_type_id = ExprAsType(context, type_node, type_id); - auto [name_node, name_id] = - context.node_stack().PopWithParseNode(); + auto [name_node, name_id] = context.node_stack().PopNameWithParseNode(); context.AddInstAndPush( parse_node, SemIR::StructTypeField{name_node, name_id, cast_type_id}); @@ -41,8 +38,7 @@ auto HandleStructFieldUnknown(Context& context, Parse::NodeId parse_node) auto HandleStructFieldValue(Context& context, Parse::NodeId parse_node) -> bool { auto value_inst_id = context.node_stack().PopExpr(); - auto [name_node, name_id] = - context.node_stack().PopWithParseNode(); + auto [name_node, name_id] = context.node_stack().PopNameWithParseNode(); // Store the name for the type. context.args_type_info_stack().AddInst(SemIR::StructTypeField{ diff --git a/toolchain/check/node_stack.h b/toolchain/check/node_stack.h index 151c75b1159d..626b8da742a5 100644 --- a/toolchain/check/node_stack.h +++ b/toolchain/check/node_stack.h @@ -39,8 +39,7 @@ class NodeStack { // Pushes a solo parse tree node onto the stack. Used when there is no // IR generated by the node. auto Push(Parse::NodeId parse_node) -> void { - CARBON_CHECK(ParseNodeKindToIdKind(parse_tree_->node_kind(parse_node)) == - IdKind::SoloParseNode) + CARBON_CHECK(ParseNodeIdKind(parse_node) == IdKind::SoloParseNode) << "Parse kind expects an Id: " << parse_tree_->node_kind(parse_node); CARBON_VLOG() << "Node Push " << stack_.size() << ": " << parse_tree_->node_kind(parse_node) << " -> \n"; @@ -52,8 +51,7 @@ class NodeStack { // Pushes a parse tree node onto the stack with an ID. template auto Push(Parse::NodeId parse_node, IdT id) -> void { - CARBON_CHECK(ParseNodeKindToIdKind(parse_tree_->node_kind(parse_node)) == - IdTypeToIdKind()) + CARBON_CHECK(ParseNodeIdKind(parse_node) == IdTypeToIdKind()) << "Parse kind expected a different IdT: " << parse_tree_->node_kind(parse_node) << " -> " << id << "\n"; CARBON_CHECK(id.is_valid()) << "Push called with invalid id: " @@ -68,7 +66,14 @@ class NodeStack { // Returns whether the node on the top of the stack is the specified kind. template auto PeekIs() const -> bool { - return parse_tree_->node_kind(PeekParseNode()) == RequiredParseKind; + return !stack_.empty() && + parse_tree_->node_kind(PeekParseNode()) == RequiredParseKind; + } + + // Returns whether the node on the top of the stack is a name. + auto PeekIsName() const -> bool { + return !stack_.empty() && + ParseNodeIdKind(PeekParseNode()) == IdKind::NameId; } // Pops the top of the stack without any verification. @@ -117,6 +122,12 @@ class NodeStack { return PopWithParseNode(); } + // Pops a name from the top of the stack and returns the parse_node and + // the ID. + auto PopNameWithParseNode() -> std::pair { + return PopWithParseNode(); + } + // Pops the top of the stack and returns the parse_node and the ID. template auto PopWithParseNode() -> auto { @@ -161,6 +172,9 @@ class NodeStack { // Expressions map multiple Parse::NodeKinds to SemIR::InstId always. auto PopExpr() -> SemIR::InstId { return PopExprWithParseNode().second; } + // Pops a name from the top of the stack and returns the ID. + auto PopName() -> SemIR::NameId { return PopNameWithParseNode().second; } + // Pops the top of the stack and returns the ID. template auto Pop() -> auto { @@ -333,6 +347,7 @@ class NodeStack { return IdKind::FunctionId; case Parse::NodeKind::ClassDefinitionStart: return IdKind::ClassId; + case Parse::NodeKind::BaseName: case Parse::NodeKind::Name: return IdKind::NameId; case Parse::NodeKind::AbstractModifier: @@ -360,6 +375,10 @@ class NodeStack { } } + auto ParseNodeIdKind(Parse::NodeId node) const -> IdKind { + return ParseNodeKindToIdKind(parse_tree_->node_kind(node)); + } + // Translates an ID type to the enum ID kind for comparison with // ParseNodeKindToIdKind. template diff --git a/toolchain/check/testdata/class/base.carbon b/toolchain/check/testdata/class/base.carbon new file mode 100644 index 000000000000..75b9049d6e70 --- /dev/null +++ b/toolchain/check/testdata/class/base.carbon @@ -0,0 +1,110 @@ +// 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 + +base class Base { + var b: i32; +} + +class Derived { + // TODO: `extend base: Base;` + base: Base; + + var d: i32; +} + +fn Make() -> Derived { + return {.base = {.b = 4}, .d = 7}; +} + +fn Access(d: Derived) -> (i32, i32) { + return (d.d, d.base.b); +} + +// CHECK:STDOUT: --- base.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %.loc9: type = struct_type {.b: i32} +// CHECK:STDOUT: %.loc7: type = ptr_type {.b: i32} +// CHECK:STDOUT: %.loc16_1.1: type = struct_type {.base: Base, .d: i32} +// CHECK:STDOUT: %.loc16_1.2: type = struct_type {.base: {.b: i32}*, .d: i32} +// CHECK:STDOUT: %.loc16_1.3: type = ptr_type {.base: {.b: i32}*, .d: i32} +// CHECK:STDOUT: %.loc11: type = ptr_type {.base: Base, .d: i32} +// CHECK:STDOUT: %.loc19: type = struct_type {.base: {.b: i32}, .d: i32} +// CHECK:STDOUT: %.loc22_35.1: type = tuple_type (type, type) +// CHECK:STDOUT: %.loc22_35.2: type = tuple_type (i32, i32) +// CHECK:STDOUT: %.loc22_35.3: type = ptr_type (i32, i32) +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file { +// CHECK:STDOUT: package: = namespace {.Base = %Base.decl, .Derived = %Derived.decl, .Make = %Make, .Access = %Access} +// CHECK:STDOUT: %Base.decl = class_decl @Base, () +// CHECK:STDOUT: %Base: type = class_type @Base +// CHECK:STDOUT: %Derived.decl = class_decl @Derived, () +// CHECK:STDOUT: %Derived: type = class_type @Derived +// CHECK:STDOUT: %Make: = fn_decl @Make +// CHECK:STDOUT: %Access: = fn_decl @Access +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @Base { +// CHECK:STDOUT: %.loc8_8.1: type = unbound_element_type Base, i32 +// CHECK:STDOUT: %.loc8_8.2: = field b, element0 +// CHECK:STDOUT: %b: = bind_name b, %.loc8_8.2 +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .b = %b +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @Derived { +// CHECK:STDOUT: %Base.ref: type = name_ref Base, file.%Base +// CHECK:STDOUT: %.loc13_13.1: type = unbound_element_type Derived, Base +// CHECK:STDOUT: %.loc13_13.2: = base Base, element0 +// CHECK:STDOUT: %.loc15_8.1: type = unbound_element_type Derived, i32 +// CHECK:STDOUT: %.loc15_8.2: = field d, element1 +// CHECK:STDOUT: %d: = bind_name d, %.loc15_8.2 +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .base = %.loc13_13.2 +// CHECK:STDOUT: .d = %d +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @Make() -> %return: Derived { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %.loc19_25: i32 = int_literal 4 +// CHECK:STDOUT: %.loc19_26.1: {.b: i32} = struct_literal (%.loc19_25) +// CHECK:STDOUT: %.loc19_34: i32 = int_literal 7 +// CHECK:STDOUT: %.loc19_35.1: {.base: {.b: i32}, .d: i32} = struct_literal (%.loc19_26.1, %.loc19_34) +// CHECK:STDOUT: %.loc19_35.2: ref Base = class_element_access %return, element0 +// CHECK:STDOUT: %.loc19_26.2: ref i32 = class_element_access %.loc19_35.2, element0 +// CHECK:STDOUT: %.loc19_26.3: init i32 = initialize_from %.loc19_25 to %.loc19_26.2 +// CHECK:STDOUT: %.loc19_26.4: init Base = class_init (%.loc19_26.3), %.loc19_35.2 +// CHECK:STDOUT: %.loc19_26.5: init Base = converted %.loc19_26.1, %.loc19_26.4 +// CHECK:STDOUT: %.loc19_35.3: ref i32 = class_element_access %return, element1 +// CHECK:STDOUT: %.loc19_35.4: init i32 = initialize_from %.loc19_34 to %.loc19_35.3 +// CHECK:STDOUT: %.loc19_35.5: init Derived = class_init (%.loc19_26.5, %.loc19_35.4), %return +// CHECK:STDOUT: %.loc19_35.6: init Derived = converted %.loc19_35.1, %.loc19_35.5 +// CHECK:STDOUT: return %.loc19_35.6 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @Access(%d: Derived) -> %return: (i32, i32) { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %d.ref.loc23_11: Derived = name_ref d, %d +// CHECK:STDOUT: %.loc23_12.1: ref i32 = class_element_access %d.ref.loc23_11, element1 +// CHECK:STDOUT: %.loc23_12.2: i32 = bind_value %.loc23_12.1 +// CHECK:STDOUT: %d.ref.loc23_16: Derived = name_ref d, %d +// CHECK:STDOUT: %.loc23_17.1: ref Base = class_element_access %d.ref.loc23_16, element0 +// CHECK:STDOUT: %.loc23_17.2: Base = bind_value %.loc23_17.1 +// CHECK:STDOUT: %.loc23_22.1: ref i32 = class_element_access %.loc23_17.2, element0 +// CHECK:STDOUT: %.loc23_22.2: i32 = bind_value %.loc23_22.1 +// CHECK:STDOUT: %.loc23_24.1: (i32, i32) = tuple_literal (%.loc23_12.2, %.loc23_22.2) +// CHECK:STDOUT: %.loc23_24.2: ref i32 = tuple_access %return, element0 +// CHECK:STDOUT: %.loc23_24.3: init i32 = initialize_from %.loc23_12.2 to %.loc23_24.2 +// CHECK:STDOUT: %.loc23_24.4: ref i32 = tuple_access %return, element1 +// CHECK:STDOUT: %.loc23_24.5: init i32 = initialize_from %.loc23_22.2 to %.loc23_24.4 +// CHECK:STDOUT: %.loc23_24.6: init (i32, i32) = tuple_init (%.loc23_24.3, %.loc23_24.5) to %return +// CHECK:STDOUT: %.loc23_24.7: init (i32, i32) = converted %.loc23_24.1, %.loc23_24.6 +// CHECK:STDOUT: return %.loc23_24.7 +// CHECK:STDOUT: } +// CHECK:STDOUT: diff --git a/toolchain/check/testdata/class/fail_abstract.carbon b/toolchain/check/testdata/class/fail_abstract.carbon index 70c6f2664f7d..53c57b5fa4a5 100644 --- a/toolchain/check/testdata/class/fail_abstract.carbon +++ b/toolchain/check/testdata/class/fail_abstract.carbon @@ -4,44 +4,102 @@ // // AUTOUPDATE -abstract class Class { - var k: i32; +abstract class Abstract { + var a: 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}; +class Derived { + // TODO: `extend base: Base;` + base: Abstract; + + var d: i32; +} + +fn Make() -> Derived { + // TODO: This should be valid, and should construct an instance of `partial Abstract` as the base. + // CHECK:STDERR: fail_abstract.carbon:[[@LINE+3]]:26: ERROR: Cannot construct instance of abstract class. Consider using `partial Abstract` instead. + // CHECK:STDERR: return {.base = {.a = 1}, .d = 7}; + // CHECK:STDERR: ^ + return {.base = {.a = 1}, .d = 7}; +} + +fn Access(d: Derived) -> (i32, i32) { + return (d.d, d.base.a); } // CHECK:STDOUT: --- fail_abstract.carbon // CHECK:STDOUT: // CHECK:STDOUT: constants { -// CHECK:STDOUT: %.loc9: type = struct_type {.k: i32} -// CHECK:STDOUT: %.loc7: type = ptr_type {.k: i32} +// CHECK:STDOUT: %.loc9: type = struct_type {.a: i32} +// CHECK:STDOUT: %.loc7: type = ptr_type {.a: i32} +// CHECK:STDOUT: %.loc16_1.1: type = struct_type {.base: Abstract, .d: i32} +// CHECK:STDOUT: %.loc16_1.2: type = struct_type {.base: {.a: i32}*, .d: i32} +// CHECK:STDOUT: %.loc16_1.3: type = ptr_type {.base: {.a: i32}*, .d: i32} +// CHECK:STDOUT: %.loc11: type = ptr_type {.base: Abstract, .d: i32} +// CHECK:STDOUT: %.loc23: type = struct_type {.base: {.a: i32}, .d: i32} +// CHECK:STDOUT: %.loc26_35.1: type = tuple_type (type, type) +// CHECK:STDOUT: %.loc26_35.2: type = tuple_type (i32, i32) +// CHECK:STDOUT: %.loc26_35.3: type = ptr_type (i32, i32) // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: file { -// CHECK:STDOUT: package: = namespace {.Class = %Class.decl, .Make = %Make} -// CHECK:STDOUT: %Class.decl = class_decl @Class, () -// CHECK:STDOUT: %Class: type = class_type @Class +// CHECK:STDOUT: package: = namespace {.Abstract = %Abstract.decl, .Derived = %Derived.decl, .Make = %Make, .Access = %Access} +// CHECK:STDOUT: %Abstract.decl = class_decl @Abstract, () +// CHECK:STDOUT: %Abstract: type = class_type @Abstract +// CHECK:STDOUT: %Derived.decl = class_decl @Derived, () +// CHECK:STDOUT: %Derived: type = class_type @Derived // CHECK:STDOUT: %Make: = fn_decl @Make +// CHECK:STDOUT: %Access: = fn_decl @Access // CHECK:STDOUT: } // CHECK:STDOUT: -// CHECK:STDOUT: class @Class { -// CHECK:STDOUT: %.loc8_8.1: type = unbound_element_type Class, i32 -// CHECK:STDOUT: %.loc8_8.2: = field k, element0 -// CHECK:STDOUT: %k: = bind_name k, %.loc8_8.2 +// CHECK:STDOUT: class @Abstract { +// CHECK:STDOUT: %.loc8_8.1: type = unbound_element_type Abstract, i32 +// CHECK:STDOUT: %.loc8_8.2: = field a, element0 +// CHECK:STDOUT: %a: = bind_name a, %.loc8_8.2 // CHECK:STDOUT: // CHECK:STDOUT: !members: -// CHECK:STDOUT: .k = %k +// CHECK:STDOUT: .a = %a // CHECK:STDOUT: } // CHECK:STDOUT: -// CHECK:STDOUT: fn @Make() -> %return: Class { +// CHECK:STDOUT: class @Derived { +// CHECK:STDOUT: %Abstract.ref: type = name_ref Abstract, file.%Abstract +// CHECK:STDOUT: %.loc13_17.1: type = unbound_element_type Derived, Abstract +// CHECK:STDOUT: %.loc13_17.2: = base Abstract, element0 +// CHECK:STDOUT: %.loc15_8.1: type = unbound_element_type Derived, i32 +// CHECK:STDOUT: %.loc15_8.2: = field d, element1 +// CHECK:STDOUT: %d: = bind_name d, %.loc15_8.2 +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .base = %.loc13_17.2 +// CHECK:STDOUT: .d = %d +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @Make() -> %return: Derived { // CHECK:STDOUT: !entry: -// CHECK:STDOUT: %.loc15_16: i32 = int_literal 1 -// CHECK:STDOUT: %.loc15_17: {.k: i32} = struct_literal (%.loc15_16) +// CHECK:STDOUT: %.loc23_25: i32 = int_literal 1 +// CHECK:STDOUT: %.loc23_26: {.a: i32} = struct_literal (%.loc23_25) +// CHECK:STDOUT: %.loc23_34: i32 = int_literal 7 +// CHECK:STDOUT: %.loc23_35: {.base: {.a: i32}, .d: i32} = struct_literal (%.loc23_26, %.loc23_34) // CHECK:STDOUT: return // CHECK:STDOUT: } // CHECK:STDOUT: +// CHECK:STDOUT: fn @Access(%d: Derived) -> %return: (i32, i32) { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %d.ref.loc27_11: Derived = name_ref d, %d +// CHECK:STDOUT: %.loc27_12.1: ref i32 = class_element_access %d.ref.loc27_11, element1 +// CHECK:STDOUT: %.loc27_12.2: i32 = bind_value %.loc27_12.1 +// CHECK:STDOUT: %d.ref.loc27_16: Derived = name_ref d, %d +// CHECK:STDOUT: %.loc27_17.1: ref Abstract = class_element_access %d.ref.loc27_16, element0 +// CHECK:STDOUT: %.loc27_17.2: Abstract = bind_value %.loc27_17.1 +// CHECK:STDOUT: %.loc27_22.1: ref i32 = class_element_access %.loc27_17.2, element0 +// CHECK:STDOUT: %.loc27_22.2: i32 = bind_value %.loc27_22.1 +// CHECK:STDOUT: %.loc27_24.1: (i32, i32) = tuple_literal (%.loc27_12.2, %.loc27_22.2) +// CHECK:STDOUT: %.loc27_24.2: ref i32 = tuple_access %return, element0 +// CHECK:STDOUT: %.loc27_24.3: init i32 = initialize_from %.loc27_12.2 to %.loc27_24.2 +// CHECK:STDOUT: %.loc27_24.4: ref i32 = tuple_access %return, element1 +// CHECK:STDOUT: %.loc27_24.5: init i32 = initialize_from %.loc27_22.2 to %.loc27_24.4 +// CHECK:STDOUT: %.loc27_24.6: init (i32, i32) = tuple_init (%.loc27_24.3, %.loc27_24.5) to %return +// CHECK:STDOUT: %.loc27_24.7: init (i32, i32) = converted %.loc27_24.1, %.loc27_24.6 +// CHECK:STDOUT: return %.loc27_24.7 +// CHECK:STDOUT: } +// CHECK:STDOUT: diff --git a/toolchain/check/testdata/class/fail_base_as_declared_name.carbon b/toolchain/check/testdata/class/fail_base_as_declared_name.carbon new file mode 100644 index 000000000000..027d19ab4138 --- /dev/null +++ b/toolchain/check/testdata/class/fail_base_as_declared_name.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 + +namespace N; + +// CHECK:STDERR: fail_base_as_declared_name.carbon:[[@LINE+6]]:6: ERROR: Expected identifier after `.`. +// CHECK:STDERR: fn N.base() {} +// CHECK:STDERR: ^~~~ +// CHECK:STDERR: fail_base_as_declared_name.carbon:[[@LINE+3]]:6: ERROR: Semantics TODO: `Error recovery from keyword name.`. +// CHECK:STDERR: fn N.base() {} +// CHECK:STDERR: ^~~~ +fn N.base() {} + +// CHECK:STDOUT: --- fail_base_as_declared_name.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: file { +// CHECK:STDOUT: } +// CHECK:STDOUT: diff --git a/toolchain/check/testdata/class/fail_base_bad_type.carbon b/toolchain/check/testdata/class/fail_base_bad_type.carbon new file mode 100644 index 000000000000..c61591f80276 --- /dev/null +++ b/toolchain/check/testdata/class/fail_base_bad_type.carbon @@ -0,0 +1,164 @@ +// 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 + +base class Base {} +class Final {} + +class DeriveFromNonType { + // CHECK:STDERR: fail_base_bad_type.carbon:[[@LINE+3]]:11: ERROR: Cannot implicitly convert from `i32` to `type`. + // CHECK:STDERR: base: 32; + // CHECK:STDERR: ^ + base: 32; +} + +class DeriveFromi32 { + // CHECK:STDERR: fail_base_bad_type.carbon:[[@LINE+3]]:12: ERROR: Deriving from final type `i32`. Base type must be an `abstract` or `base` class. + // CHECK:STDERR: base: i32; + // CHECK:STDERR: ^ + base: i32; +} + +class DeriveFromTuple { + // CHECK:STDERR: fail_base_bad_type.carbon:[[@LINE+3]]:16: ERROR: Deriving from final type `(Base,)`. Base type must be an `abstract` or `base` class. + // CHECK:STDERR: base: (Base,); + // CHECK:STDERR: ^ + base: (Base,); +} + +// TODO: Should we allow this? +// We do allow `{.base = {.a: i32, .b: i32}}`. +class DeriveFromStruct { + // CHECK:STDERR: fail_base_bad_type.carbon:[[@LINE+3]]:27: ERROR: Deriving from final type `{.a: i32, .b: i32}`. Base type must be an `abstract` or `base` class. + // CHECK:STDERR: base: {.a: i32, .b: i32}; + // CHECK:STDERR: ^ + base: {.a: i32, .b: i32}; +} + +base class Incomplete; + +class DeriveFromIncomplete { + // CHECK:STDERR: fail_base_bad_type.carbon:[[@LINE+6]]:19: ERROR: Base `Incomplete` is an incomplete type. + // CHECK:STDERR: base: Incomplete; + // CHECK:STDERR: ^ + // CHECK:STDERR: fail_base_bad_type.carbon:[[@LINE-6]]:1: Class was forward declared here. + // CHECK:STDERR: base class Incomplete; + // CHECK:STDERR: ^~~~ + base: Incomplete; +} + +class DeriveFromFinal { + // CHECK:STDERR: fail_base_bad_type.carbon:[[@LINE+3]]:14: ERROR: Deriving from final type `Final`. Base type must be an `abstract` or `base` class. + // CHECK:STDERR: base: Final; + // CHECK:STDERR: ^ + base: Final; +} + +// CHECK:STDOUT: --- fail_base_bad_type.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %.loc7_18.1: type = struct_type {} +// CHECK:STDOUT: %.loc15: type = struct_type {.base: } +// CHECK:STDOUT: %.loc22: type = struct_type {.base: i32} +// CHECK:STDOUT: %.loc28_15: type = tuple_type (type) +// CHECK:STDOUT: %.loc28_16.1: type = tuple_type (Base) +// CHECK:STDOUT: %.loc7_18.2: type = tuple_type () +// CHECK:STDOUT: %.loc7_1: type = ptr_type {} +// CHECK:STDOUT: %.loc28_16.2: type = tuple_type ({}*) +// CHECK:STDOUT: %.loc29: type = struct_type {.base: (Base,)} +// CHECK:STDOUT: %.loc37: type = ptr_type {.a: i32, .b: i32} +// CHECK:STDOUT: %.loc38: type = struct_type {.base: {.a: i32, .b: i32}} +// CHECK:STDOUT: %.loc57: type = struct_type {.base: Final} +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file { +// CHECK:STDOUT: package: = namespace {.Base = %Base.decl, .Final = %Final.decl, .DeriveFromNonType = %DeriveFromNonType.decl, .DeriveFromi32 = %DeriveFromi32.decl, .DeriveFromTuple = %DeriveFromTuple.decl, .DeriveFromStruct = %DeriveFromStruct.decl, .Incomplete = %Incomplete.decl, .DeriveFromIncomplete = %DeriveFromIncomplete.decl, .DeriveFromFinal = %DeriveFromFinal.decl} +// CHECK:STDOUT: %Base.decl = class_decl @Base, () +// CHECK:STDOUT: %Base: type = class_type @Base +// CHECK:STDOUT: %Final.decl = class_decl @Final, () +// CHECK:STDOUT: %Final: type = class_type @Final +// CHECK:STDOUT: %DeriveFromNonType.decl = class_decl @DeriveFromNonType, () +// CHECK:STDOUT: %DeriveFromNonType: type = class_type @DeriveFromNonType +// CHECK:STDOUT: %DeriveFromi32.decl = class_decl @DeriveFromi32, () +// CHECK:STDOUT: %DeriveFromi32: type = class_type @DeriveFromi32 +// CHECK:STDOUT: %DeriveFromTuple.decl = class_decl @DeriveFromTuple, () +// CHECK:STDOUT: %DeriveFromTuple: type = class_type @DeriveFromTuple +// CHECK:STDOUT: %DeriveFromStruct.decl = class_decl @DeriveFromStruct, () +// CHECK:STDOUT: %DeriveFromStruct: type = class_type @DeriveFromStruct +// CHECK:STDOUT: %Incomplete.decl = class_decl @Incomplete, () +// CHECK:STDOUT: %Incomplete: type = class_type @Incomplete +// CHECK:STDOUT: %DeriveFromIncomplete.decl = class_decl @DeriveFromIncomplete, () +// CHECK:STDOUT: %DeriveFromIncomplete: type = class_type @DeriveFromIncomplete +// CHECK:STDOUT: %DeriveFromFinal.decl = class_decl @DeriveFromFinal, () +// CHECK:STDOUT: %DeriveFromFinal: type = class_type @DeriveFromFinal +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @Base { +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @Final { +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @DeriveFromNonType { +// CHECK:STDOUT: %.loc14_9: i32 = int_literal 32 +// CHECK:STDOUT: %.loc14_11.1: type = unbound_element_type DeriveFromNonType, +// CHECK:STDOUT: %.loc14_11.2: = base , element0 +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .base = %.loc14_11.2 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @DeriveFromi32 { +// CHECK:STDOUT: %.loc21_12.1: type = unbound_element_type DeriveFromi32, i32 +// CHECK:STDOUT: %.loc21_12.2: = base i32, element0 +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .base = %.loc21_12.2 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @DeriveFromTuple { +// CHECK:STDOUT: %Base.ref: type = name_ref Base, file.%Base +// CHECK:STDOUT: %.loc28_15: (type,) = tuple_literal (%Base.ref) +// CHECK:STDOUT: %.loc28_16.1: type = converted %.loc28_15, constants.%.loc28_16.1 +// CHECK:STDOUT: %.loc28_16.2: type = unbound_element_type DeriveFromTuple, (Base,) +// CHECK:STDOUT: %.loc28_16.3: = base (Base,), element0 +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .base = %.loc28_16.3 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @DeriveFromStruct { +// CHECK:STDOUT: %.loc37_26: type = struct_type {.a: i32, .b: i32} +// CHECK:STDOUT: %.loc37_27.1: type = unbound_element_type DeriveFromStruct, {.a: i32, .b: i32} +// CHECK:STDOUT: %.loc37_27.2: = base {.a: i32, .b: i32}, element0 +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .base = %.loc37_27.2 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @Incomplete; +// CHECK:STDOUT: +// CHECK:STDOUT: class @DeriveFromIncomplete { +// CHECK:STDOUT: %Incomplete.ref: type = name_ref Incomplete, file.%Incomplete +// CHECK:STDOUT: %.loc49_19.1: type = unbound_element_type DeriveFromIncomplete, +// CHECK:STDOUT: %.loc49_19.2: = base , element0 +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .base = %.loc49_19.2 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @DeriveFromFinal { +// CHECK:STDOUT: %Final.ref: type = name_ref Final, file.%Final +// CHECK:STDOUT: %.loc56_14.1: type = unbound_element_type DeriveFromFinal, Final +// CHECK:STDOUT: %.loc56_14.2: = base Final, element0 +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .base = %.loc56_14.2 +// CHECK:STDOUT: } +// CHECK:STDOUT: diff --git a/toolchain/check/testdata/class/fail_base_misplaced.carbon b/toolchain/check/testdata/class/fail_base_misplaced.carbon new file mode 100644 index 000000000000..6d41b23fb4fe --- /dev/null +++ b/toolchain/check/testdata/class/fail_base_misplaced.carbon @@ -0,0 +1,41 @@ +// 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 + +base class B {} + +// CHECK:STDERR: fail_base_misplaced.carbon:[[@LINE+3]]:8: ERROR: `base` declaration can only be used in a class. +// CHECK:STDERR: base: B; +// CHECK:STDERR: ^ +base: B; + +fn F() { + // CHECK:STDERR: fail_base_misplaced.carbon:[[@LINE+6]]:3: ERROR: Expected expression. + // CHECK:STDERR: base: B; + // CHECK:STDERR: ^~~~ + // CHECK:STDERR: fail_base_misplaced.carbon:[[@LINE+3]]:3: ERROR: Semantics TODO: `HandleInvalidParse`. + // CHECK:STDERR: base: B; + // CHECK:STDERR: ^~~~ + base: B; +} + +// CHECK:STDOUT: --- fail_base_misplaced.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %.loc7: type = struct_type {} +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file { +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @B { +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @F() { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: } +// CHECK:STDOUT: diff --git a/toolchain/check/testdata/class/fail_base_repeated.carbon b/toolchain/check/testdata/class/fail_base_repeated.carbon new file mode 100644 index 000000000000..033af15b0c48 --- /dev/null +++ b/toolchain/check/testdata/class/fail_base_repeated.carbon @@ -0,0 +1,83 @@ +// 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 + +base class B1 {} +base class B2 {} + +class C { + base: B1; + // CHECK:STDERR: fail_base_repeated.carbon:[[@LINE+6]]:11: ERROR: Multiple `base` declarations in class. Multiple inheritance is not permitted. + // CHECK:STDERR: base: B2; + // CHECK:STDERR: ^ + // CHECK:STDERR: fail_base_repeated.carbon:[[@LINE-4]]:11: Previous `base` declaration is here. + // CHECK:STDERR: base: B1; + // CHECK:STDERR: ^ + base: B2; +} + +class D { + // TODO: Consider adding a custom diagnostic for this case. + base: B1; + // CHECK:STDERR: fail_base_repeated.carbon:[[@LINE+6]]:11: ERROR: Multiple `base` declarations in class. Multiple inheritance is not permitted. + // CHECK:STDERR: base: B1; + // CHECK:STDERR: ^ + // CHECK:STDERR: fail_base_repeated.carbon:[[@LINE-4]]:11: Previous `base` declaration is here. + // CHECK:STDERR: base: B1; + // CHECK:STDERR: ^ + base: B1; +} + +// CHECK:STDOUT: --- fail_base_repeated.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %.loc7_16.1: type = struct_type {} +// CHECK:STDOUT: %.loc7_16.2: type = tuple_type () +// CHECK:STDOUT: %.loc7_1: type = ptr_type {} +// CHECK:STDOUT: %.loc19: type = struct_type {.base: B1} +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file { +// CHECK:STDOUT: package: = namespace {.B1 = %B1.decl, .B2 = %B2.decl, .C = %C.decl, .D = %D.decl} +// CHECK:STDOUT: %B1.decl = class_decl @B1, () +// CHECK:STDOUT: %B1: type = class_type @B1 +// CHECK:STDOUT: %B2.decl = class_decl @B2, () +// CHECK:STDOUT: %B2: type = class_type @B2 +// CHECK:STDOUT: %C.decl = class_decl @C, () +// CHECK:STDOUT: %C: type = class_type @C +// CHECK:STDOUT: %D.decl = class_decl @D, () +// CHECK:STDOUT: %D: type = class_type @D +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @B1 { +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @B2 { +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @C { +// CHECK:STDOUT: %B1.ref: type = name_ref B1, file.%B1 +// CHECK:STDOUT: %.loc11_11.1: type = unbound_element_type C, B1 +// CHECK:STDOUT: %.loc11_11.2: = base B1, element0 +// CHECK:STDOUT: %B2.ref: type = name_ref B2, file.%B2 +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .base = %.loc11_11.2 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @D { +// CHECK:STDOUT: %B1.ref.loc23: type = name_ref B1, file.%B1 +// CHECK:STDOUT: %.loc23_11.1: type = unbound_element_type D, B1 +// CHECK:STDOUT: %.loc23_11.2: = base B1, element0 +// CHECK:STDOUT: %B1.ref.loc30: type = name_ref B1, file.%B1 +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .base = %.loc23_11.2 +// CHECK:STDOUT: } +// CHECK:STDOUT: diff --git a/toolchain/check/testdata/class/fail_base_unbound.carbon b/toolchain/check/testdata/class/fail_base_unbound.carbon new file mode 100644 index 000000000000..b80eb6eee5cd --- /dev/null +++ b/toolchain/check/testdata/class/fail_base_unbound.carbon @@ -0,0 +1,52 @@ +// 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 + +base class B {} + +class C { + base: B; +} + +// CHECK:STDERR: fail_base_unbound.carbon:[[@LINE+3]]:13: ERROR: Expression cannot be used as a value. +// CHECK:STDERR: let b: B = C.base; +// CHECK:STDERR: ^ +let b: B = C.base; + +// CHECK:STDOUT: --- fail_base_unbound.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %.loc7_15.1: type = struct_type {} +// CHECK:STDOUT: %.loc7_15.2: type = tuple_type () +// CHECK:STDOUT: %.loc7_1: type = ptr_type {} +// CHECK:STDOUT: %.loc11: type = struct_type {.base: B} +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file { +// CHECK:STDOUT: package: = namespace {.B = %B.decl, .C = %C.decl} +// CHECK:STDOUT: %B.decl = class_decl @B, () +// CHECK:STDOUT: %B: type = class_type @B +// CHECK:STDOUT: %C.decl = class_decl @C, () +// CHECK:STDOUT: %C: type = class_type @C +// CHECK:STDOUT: %B.ref: type = name_ref B, %B +// CHECK:STDOUT: %C.ref: type = name_ref C, %C +// CHECK:STDOUT: %base.ref: = name_ref base, @C.%.loc10_10.2 +// CHECK:STDOUT: %b: B = bind_name b, +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @B { +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @C { +// CHECK:STDOUT: %B.ref: type = name_ref B, file.%B +// CHECK:STDOUT: %.loc10_10.1: type = unbound_element_type C, B +// CHECK:STDOUT: %.loc10_10.2: = base B, element0 +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .base = %.loc10_10.2 +// CHECK:STDOUT: } +// CHECK:STDOUT: diff --git a/toolchain/check/testdata/struct/fail_keyword_name.carbon b/toolchain/check/testdata/struct/fail_keyword_name.carbon new file mode 100644 index 000000000000..cd3136aaac44 --- /dev/null +++ b/toolchain/check/testdata/struct/fail_keyword_name.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 + +// CHECK:STDERR: fail_keyword_name.carbon:[[@LINE+6]]:13: ERROR: Expected identifier after `.`. +// CHECK:STDERR: fn F() -> {.class: i32}; +// CHECK:STDERR: ^~~~~ +// CHECK:STDERR: fail_keyword_name.carbon:[[@LINE+3]]:13: ERROR: Semantics TODO: `Error recovery from keyword name.`. +// CHECK:STDERR: fn F() -> {.class: i32}; +// CHECK:STDERR: ^~~~~ +fn F() -> {.class: i32}; + +// CHECK:STDERR: fail_keyword_name.carbon:[[@LINE+3]]:19: ERROR: Expected identifier after `.`. +// CHECK:STDERR: fn G() { return {.return = 5}; }; +// CHECK:STDERR: ^~~~~~ +fn G() { return {.return = 5}; }; + +// CHECK:STDOUT: --- fail_keyword_name.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: file { +// CHECK:STDOUT: } +// CHECK:STDOUT: diff --git a/toolchain/diagnostics/diagnostic_kind.def b/toolchain/diagnostics/diagnostic_kind.def index 43d5ad09975c..ebab5f55b05a 100644 --- a/toolchain/diagnostics/diagnostic_kind.def +++ b/toolchain/diagnostics/diagnostic_kind.def @@ -143,13 +143,11 @@ CARBON_DIAGNOSTIC_KIND(InCallToFunctionParam) CARBON_DIAGNOSTIC_KIND(InCallToFunctionSelf) CARBON_DIAGNOSTIC_KIND(MissingObjectInMethodCall) -CARBON_DIAGNOSTIC_KIND(AddressOfEphemeralRef) -CARBON_DIAGNOSTIC_KIND(AddressOfNonRef) -CARBON_DIAGNOSTIC_KIND(AddrOnNonSelfParam) -CARBON_DIAGNOSTIC_KIND(ArrayInitFromLiteralArgCountMismatch) -CARBON_DIAGNOSTIC_KIND(ArrayInitFromExprArgCountMismatch) -CARBON_DIAGNOSTIC_KIND(AssignmentToNonAssignable) -CARBON_DIAGNOSTIC_KIND(BreakOutsideLoop) +// Class checking. +CARBON_DIAGNOSTIC_KIND(BaseIsFinal) +CARBON_DIAGNOSTIC_KIND(BaseOutsideClass) +CARBON_DIAGNOSTIC_KIND(BasePrevious) +CARBON_DIAGNOSTIC_KIND(BaseRepeated) CARBON_DIAGNOSTIC_KIND(ClassForwardDeclaredHere) CARBON_DIAGNOSTIC_KIND(ClassPreviousDefinition) CARBON_DIAGNOSTIC_KIND(ClassRedeclarationDifferentIntroducer) @@ -157,6 +155,14 @@ CARBON_DIAGNOSTIC_KIND(ClassRedeclarationDifferentIntroducerPrevious) CARBON_DIAGNOSTIC_KIND(ClassRedefinition) CARBON_DIAGNOSTIC_KIND(ClassIncompleteWithinDefinition) CARBON_DIAGNOSTIC_KIND(ConstructionOfAbstractClass) + +CARBON_DIAGNOSTIC_KIND(AddressOfEphemeralRef) +CARBON_DIAGNOSTIC_KIND(AddressOfNonRef) +CARBON_DIAGNOSTIC_KIND(AddrOnNonSelfParam) +CARBON_DIAGNOSTIC_KIND(ArrayInitFromLiteralArgCountMismatch) +CARBON_DIAGNOSTIC_KIND(ArrayInitFromExprArgCountMismatch) +CARBON_DIAGNOSTIC_KIND(AssignmentToNonAssignable) +CARBON_DIAGNOSTIC_KIND(BreakOutsideLoop) CARBON_DIAGNOSTIC_KIND(ContinueOutsideLoop) CARBON_DIAGNOSTIC_KIND(CopyOfUncopyableType) CARBON_DIAGNOSTIC_KIND(DerefOfNonPointer) @@ -168,6 +174,7 @@ CARBON_DIAGNOSTIC_KIND(NameDeclDuplicate) CARBON_DIAGNOSTIC_KIND(NameDeclPrevious) CARBON_DIAGNOSTIC_KIND(MissingReturnStatement) CARBON_DIAGNOSTIC_KIND(RepeatedConst) +CARBON_DIAGNOSTIC_KIND(IncompleteTypeInBaseDecl) CARBON_DIAGNOSTIC_KIND(IncompleteTypeInConversion) CARBON_DIAGNOSTIC_KIND(IncompleteTypeInFunctionParam) CARBON_DIAGNOSTIC_KIND(IncompleteTypeInFunctionReturnType) diff --git a/toolchain/lower/handle_aggregates.cpp b/toolchain/lower/handle_aggregates.cpp index 85c97ea96d24..d0aecdd3c8e7 100644 --- a/toolchain/lower/handle_aggregates.cpp +++ b/toolchain/lower/handle_aggregates.cpp @@ -157,6 +157,11 @@ auto HandleClassInit(FunctionContext& context, SemIR::InstId inst_id, "class.init")); } +auto HandleBase(FunctionContext& /*context*/, SemIR::InstId /*inst_id*/, + SemIR::Base /*inst*/) -> void { + // No action to perform. +} + auto HandleField(FunctionContext& /*context*/, SemIR::InstId /*inst_id*/, SemIR::Field /*inst*/) -> void { // No action to perform. diff --git a/toolchain/lower/testdata/class/base.carbon b/toolchain/lower/testdata/class/base.carbon new file mode 100644 index 000000000000..c79a1af22fb3 --- /dev/null +++ b/toolchain/lower/testdata/class/base.carbon @@ -0,0 +1,60 @@ +// 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 + +base class Base { + var b: i32; +} + +class Derived { + // TODO: `extend base: Base;` + base: Base; + + var d: i32; +} + +fn Make() -> Derived { + return {.base = {.b = 4}, .d = 7}; +} + +fn Access(d: Derived) -> (i32, i32) { + return (d.d, d.base.b); +} + +fn Convert(p: Derived*) -> Base* { + // TODO: This should be allowed as an implicit conversion. + // TODO: We should support `p->base`. + return &(*p).base; +} + +// CHECK:STDOUT: ; ModuleID = 'base.carbon' +// CHECK:STDOUT: source_filename = "base.carbon" +// CHECK:STDOUT: +// CHECK:STDOUT: define void @Make(ptr sret({ { i32 }, i32 }) %return) { +// CHECK:STDOUT: %base = getelementptr inbounds { { i32 }, i32 }, ptr %return, i32 0, i32 0 +// CHECK:STDOUT: %b = getelementptr inbounds { i32 }, ptr %base, i32 0, i32 0 +// CHECK:STDOUT: store i32 4, ptr %b, align 4 +// CHECK:STDOUT: %d = getelementptr inbounds { { i32 }, i32 }, ptr %return, i32 0, i32 1 +// CHECK:STDOUT: store i32 7, ptr %d, align 4 +// CHECK:STDOUT: ret void +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: define void @Access(ptr sret({ i32, i32 }) %return, ptr %d) { +// CHECK:STDOUT: %d1 = getelementptr inbounds { { i32 }, i32 }, ptr %d, i32 0, i32 1 +// CHECK:STDOUT: %1 = load i32, ptr %d1, align 4 +// CHECK:STDOUT: %base = getelementptr inbounds { { i32 }, i32 }, ptr %d, i32 0, i32 0 +// CHECK:STDOUT: %b = getelementptr inbounds { i32 }, ptr %base, i32 0, i32 0 +// CHECK:STDOUT: %2 = load i32, ptr %b, align 4 +// CHECK:STDOUT: %tuple.elem = getelementptr inbounds { i32, i32 }, ptr %return, i32 0, i32 0 +// CHECK:STDOUT: store i32 %1, ptr %tuple.elem, align 4 +// CHECK:STDOUT: %tuple.elem2 = getelementptr inbounds { i32, i32 }, ptr %return, i32 0, i32 1 +// CHECK:STDOUT: store i32 %2, ptr %tuple.elem2, align 4 +// CHECK:STDOUT: ret void +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: define ptr @Convert(ptr %p) { +// CHECK:STDOUT: %base = getelementptr inbounds { { i32 }, i32 }, ptr %p, i32 0, i32 0 +// CHECK:STDOUT: ret ptr %base +// CHECK:STDOUT: } diff --git a/toolchain/parse/handle_base.cpp b/toolchain/parse/handle_base.cpp new file mode 100644 index 000000000000..deb1805773ef --- /dev/null +++ b/toolchain/parse/handle_base.cpp @@ -0,0 +1,29 @@ +// 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 + +#include "toolchain/parse/context.h" + +namespace Carbon::Parse { + +// Handles processing of a complete `base: B` declaration. +auto HandleBaseDecl(Context& context) -> void { + auto state = context.PopState(); + + auto semi = context.ConsumeIf(Lex::TokenKind::Semi); + if (!semi && !state.has_error) { + context.EmitExpectedDeclSemi(context.tokens().GetKind(state.token)); + state.has_error = true; + } + + if (state.has_error) { + context.RecoverFromDeclError(state, NodeKind::BaseDecl, + /*skip_past_likely_end=*/true); + return; + } + + context.AddNode(NodeKind::BaseDecl, *semi, state.subtree_start, + state.has_error); +} + +} // namespace Carbon::Parse diff --git a/toolchain/parse/handle_decl_scope_loop.cpp b/toolchain/parse/handle_decl_scope_loop.cpp index a98dff9552b2..179025dd0e2a 100644 --- a/toolchain/parse/handle_decl_scope_loop.cpp +++ b/toolchain/parse/handle_decl_scope_loop.cpp @@ -62,8 +62,20 @@ auto HandleDeclScopeLoop(Context& context) -> void { // 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::Colon, Lookahead::NextToken)) { + context.PushState(State::BaseDecl); + context.PushState(State::Expr); + context.AddLeafNode(NodeKind::BaseIntroducer, context.Consume()); + context.AddLeafNode(NodeKind::BaseColon, context.Consume()); + return; + } + // TODO: If the next token isn't a colon or `class`, try to recover based + // on whether we're in a class, whether we have an `extend` modifier, and + // the following tokens. + [[fallthrough]]; + } + case Lex::TokenKind::Abstract: { if (context.PositionIs(Lex::TokenKind::Class, Lookahead::NextToken)) { context.PushState(State::TypeAfterIntroducerAsClass); auto modifier_token = context.Consume(); diff --git a/toolchain/parse/handle_period.cpp b/toolchain/parse/handle_period.cpp index d557efcd1efe..0b0fa29570af 100644 --- a/toolchain/parse/handle_period.cpp +++ b/toolchain/parse/handle_period.cpp @@ -13,12 +13,19 @@ static auto HandlePeriodOrArrow(Context& context, NodeKind node_kind, bool is_arrow) -> void { auto state = context.PopState(); - // `.` identifier + // We're handling `.something` or `->something`. auto dot = context.ConsumeChecked(is_arrow ? Lex::TokenKind::MinusGreater : Lex::TokenKind::Period); - if (!context.ConsumeAndAddLeafNodeIf(Lex::TokenKind::Identifier, - NodeKind::Name)) { + if (context.ConsumeAndAddLeafNodeIf(Lex::TokenKind::Identifier, + NodeKind::Name)) { + // OK, `.` identifier. + } else if (node_kind != NodeKind::QualifiedDecl && + context.ConsumeAndAddLeafNodeIf(Lex::TokenKind::Base, + NodeKind::BaseName)) { + // OK, `.base`. This is allowed in any name context other than declaring a + // new qualified name: `fn Namespace.base() {}` + } else { CARBON_DIAGNOSTIC(ExpectedIdentifierAfterDotOrArrow, Error, "Expected identifier after `{0}`.", llvm::StringLiteral); context.emitter().Emit( diff --git a/toolchain/parse/node_kind.def b/toolchain/parse/node_kind.def index 74bff85feddb..8de3d2813bac 100644 --- a/toolchain/parse/node_kind.def +++ b/toolchain/parse/node_kind.def @@ -584,6 +584,18 @@ CARBON_PARSE_NODE_KIND_BRACKET(ClassDecl, ClassIntroducer, CARBON_TOKEN(Semi) CARBON_IF_ERROR(CARBON_TOKEN(Class))) +// `base`: +// BaseIntroducer +// _repeated_ _external_: AccessModifierKeyword or DeclModifierKeyword +// BaseColon +// _external_: expression +// BaseDecl +CARBON_PARSE_NODE_KIND_CHILD_COUNT(BaseIntroducer, 0, CARBON_TOKEN(Base)) +CARBON_PARSE_NODE_KIND_CHILD_COUNT(BaseColon, 0, CARBON_TOKEN(Colon)) +CARBON_PARSE_NODE_KIND_BRACKET(BaseDecl, BaseIntroducer, + CARBON_TOKEN(Semi) + CARBON_IF_ERROR(CARBON_TOKEN(Base))) + // `interface`: // InterfaceIntroducer // _external_: Name or QualifiedDecl @@ -638,6 +650,13 @@ CARBON_PARSE_NODE_KIND_CHILD_COUNT(SelfValueNameExpr, 0, CARBON_PARSE_NODE_KIND_CHILD_COUNT(SelfTypeNameExpr, 0, CARBON_TOKEN(SelfTypeIdentifier)) +// The `base` value keyword, introduced by `base: B`. Typically referenced in +// an expression, as in `x.base` or `{.base = ...}`, but can also be used as a +// declared name, as in `{.base: partial B}`. +// +// BaseName +CARBON_PARSE_NODE_KIND_CHILD_COUNT(BaseName, 0, CARBON_TOKEN(Base)) + // The `package` keyword in an expression. CARBON_PARSE_NODE_KIND_CHILD_COUNT(PackageExpr, 0, CARBON_TOKEN(Package)) diff --git a/toolchain/parse/state.def b/toolchain/parse/state.def index 1d399da17572..3bb7fb94fc04 100644 --- a/toolchain/parse/state.def +++ b/toolchain/parse/state.def @@ -292,6 +292,12 @@ CARBON_PARSE_STATE(DeclNameAndParamsAfterImplicit) // 1. TypeAfterIntroducerAsClass // 2. DeclScopeLoop // +// base : ... +// ^~~~~~ +// 1. Expr +// 2. BaseDecl +// 3. DeclScopeLoop +// // constraint ... // ^~~~~~~~~~ // 1. TypeAfterIntroducerAsNamedConstraint @@ -1021,6 +1027,15 @@ CARBON_PARSE_STATE_VARIANTS3(TypeAfterIntroducer, Class, Interface, NamedConstra // (state done) CARBON_PARSE_STATE_VARIANTS3(TypeAfterParams, Class, Interface, NamedConstraint) +// Handles processing of a completed `base: B` declaration. +// +// base: B ; +// ^ +// base: B ??? ; +// ^ +// (state done) +CARBON_PARSE_STATE(BaseDecl) + // Handles the start of a `var` or `returned var`. // // var ... (variant is not Returned) diff --git a/toolchain/parse/testdata/class/base.carbon b/toolchain/parse/testdata/class/base.carbon new file mode 100644 index 000000000000..4cbb2a549a1a --- /dev/null +++ b/toolchain/parse/testdata/class/base.carbon @@ -0,0 +1,35 @@ +// 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 + +base class B {} + +class D { + base: B; + base class Nested; +} + +// CHECK:STDOUT: - filename: base.carbon +// CHECK:STDOUT: parse_tree: [ +// CHECK:STDOUT: {kind: 'FileStart', text: ''}, +// 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: 'Name', text: 'D'}, +// CHECK:STDOUT: {kind: 'ClassDefinitionStart', text: '{', subtree_size: 3}, +// CHECK:STDOUT: {kind: 'BaseIntroducer', text: 'base'}, +// CHECK:STDOUT: {kind: 'BaseColon', text: ':'}, +// CHECK:STDOUT: {kind: 'NameExpr', text: 'B'}, +// CHECK:STDOUT: {kind: 'BaseDecl', text: ';', subtree_size: 4}, +// CHECK:STDOUT: {kind: 'ClassIntroducer', text: 'class'}, +// CHECK:STDOUT: {kind: 'BaseModifier', text: 'base'}, +// CHECK:STDOUT: {kind: 'Name', text: 'Nested'}, +// CHECK:STDOUT: {kind: 'ClassDecl', text: ';', subtree_size: 4}, +// CHECK:STDOUT: {kind: 'ClassDefinition', text: '}', subtree_size: 12}, +// CHECK:STDOUT: {kind: 'FileEnd', text: ''}, +// CHECK:STDOUT: ] diff --git a/toolchain/parse/testdata/class/base_misplaced.carbon b/toolchain/parse/testdata/class/base_misplaced.carbon new file mode 100644 index 000000000000..37e249673666 --- /dev/null +++ b/toolchain/parse/testdata/class/base_misplaced.carbon @@ -0,0 +1,18 @@ +// 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 + +// This is rejected by check. +base: i32; + +// CHECK:STDOUT: - filename: base_misplaced.carbon +// CHECK:STDOUT: parse_tree: [ +// CHECK:STDOUT: {kind: 'FileStart', text: ''}, +// CHECK:STDOUT: {kind: 'BaseIntroducer', text: 'base'}, +// CHECK:STDOUT: {kind: 'BaseColon', text: ':'}, +// CHECK:STDOUT: {kind: 'IntTypeLiteral', text: 'i32'}, +// CHECK:STDOUT: {kind: 'BaseDecl', text: ';', subtree_size: 4}, +// CHECK:STDOUT: {kind: 'FileEnd', text: ''}, +// CHECK:STDOUT: ] diff --git a/toolchain/parse/testdata/class/fail_base.carbon b/toolchain/parse/testdata/class/fail_base.carbon new file mode 100644 index 000000000000..4f93cc177d62 --- /dev/null +++ b/toolchain/parse/testdata/class/fail_base.carbon @@ -0,0 +1,45 @@ +// 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_base.carbon:[[@LINE+3]]:3: ERROR: Unrecognized declaration introducer. + // CHECK:STDERR: base; + // CHECK:STDERR: ^~~~ + base; + + // CHECK:STDERR: fail_base.carbon:[[@LINE+3]]:9: ERROR: Expected expression. + // CHECK:STDERR: base: ; + // CHECK:STDERR: ^ + base: ; + + // CHECK:STDERR: fail_base.carbon:[[@LINE+3]]:7: ERROR: Unrecognized declaration introducer. + // CHECK:STDERR: base: { } + // CHECK:STDERR: ^ + base: { } + // We should resume parsing here after the previous error. + var n: i32; +} + +// CHECK:STDOUT: - filename: fail_base.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: 'ClassDefinitionStart', text: '{', subtree_size: 3}, +// CHECK:STDOUT: {kind: 'EmptyDecl', text: ';', has_error: yes}, +// CHECK:STDOUT: {kind: 'BaseIntroducer', text: 'base'}, +// CHECK:STDOUT: {kind: 'BaseColon', text: ':'}, +// CHECK:STDOUT: {kind: 'InvalidParse', text: ';', has_error: yes}, +// CHECK:STDOUT: {kind: 'BaseDecl', text: 'base', has_error: yes, subtree_size: 4}, +// CHECK:STDOUT: {kind: 'EmptyDecl', text: ':', has_error: yes}, +// CHECK:STDOUT: {kind: 'VariableIntroducer', text: 'var'}, +// CHECK:STDOUT: {kind: 'Name', text: 'n'}, +// CHECK:STDOUT: {kind: 'IntTypeLiteral', text: 'i32'}, +// CHECK:STDOUT: {kind: 'BindingPattern', text: ':', subtree_size: 3}, +// CHECK:STDOUT: {kind: 'VariableDecl', text: ';', subtree_size: 5}, +// CHECK:STDOUT: {kind: 'ClassDefinition', text: '}', subtree_size: 15}, +// CHECK:STDOUT: {kind: 'FileEnd', text: ''}, +// CHECK:STDOUT: ] diff --git a/toolchain/parse/testdata/class/fail_base_misplaced.carbon b/toolchain/parse/testdata/class/fail_base_misplaced.carbon new file mode 100644 index 000000000000..814ab4365b1e --- /dev/null +++ b/toolchain/parse/testdata/class/fail_base_misplaced.carbon @@ -0,0 +1,33 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// AUTOUPDATE + +base class B {} + +fn F() { + // CHECK:STDERR: fail_base_misplaced.carbon:[[@LINE+3]]:3: ERROR: Expected expression. + // CHECK:STDERR: base: B; + // CHECK:STDERR: ^~~~ + base: B; +} + +// CHECK:STDOUT: - filename: fail_base_misplaced.carbon +// CHECK:STDOUT: parse_tree: [ +// CHECK:STDOUT: {kind: 'FileStart', text: ''}, +// 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: 'FunctionIntroducer', text: 'fn'}, +// CHECK:STDOUT: {kind: 'Name', text: 'F'}, +// CHECK:STDOUT: {kind: 'ParamListStart', text: '('}, +// CHECK:STDOUT: {kind: 'ParamList', text: ')', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 5}, +// CHECK:STDOUT: {kind: 'InvalidParse', text: 'base', has_error: yes}, +// CHECK:STDOUT: {kind: 'ExprStatement', text: ';', has_error: yes, subtree_size: 2}, +// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 8}, +// CHECK:STDOUT: {kind: 'FileEnd', text: ''}, +// CHECK:STDOUT: ] diff --git a/toolchain/sem_ir/file.cpp b/toolchain/sem_ir/file.cpp index ecb4513929b0..9ac8efc29ed7 100644 --- a/toolchain/sem_ir/file.cpp +++ b/toolchain/sem_ir/file.cpp @@ -187,6 +187,7 @@ static auto GetTypePrecedence(InstKind kind) -> int { case ArrayIndex::Kind: case ArrayInit::Kind: case Assign::Kind: + case Base::Kind: case BinaryOperatorAdd::Kind: case BindName::Kind: case BindValue::Kind: @@ -389,6 +390,7 @@ auto File::StringifyTypeExpr(InstId outer_inst_id, bool in_type_context) const case ArrayIndex::Kind: case ArrayInit::Kind: case Assign::Kind: + case Base::Kind: case BinaryOperatorAdd::Kind: case BindName::Kind: case BindValue::Kind: @@ -470,6 +472,7 @@ auto GetExprCategory(const File& file, InstId inst_id) -> ExprCategory { // NOLINTNEXTLINE(bugprone-switch-missing-default-case) switch (inst.kind()) { case Assign::Kind: + case Base::Kind: case Branch::Kind: case BranchIf::Kind: case BranchWithArg::Kind: diff --git a/toolchain/sem_ir/file.h b/toolchain/sem_ir/file.h index e7cbb9937a1d..3ec5c1d841b7 100644 --- a/toolchain/sem_ir/file.h +++ b/toolchain/sem_ir/file.h @@ -105,6 +105,12 @@ struct Class : public Printable { // TODO: Handle control flow in the class body, such as if-expressions. InstBlockId body_block_id = InstBlockId::Invalid; + // The following members are accumulated throughout the class definition. + + // The base class declaration. Invalid if the class has no base class. This is + // a Base instruction. + InstId base_id = InstId::Invalid; + // The following members are set at the `}` of the class definition. // The object representation type to use for this class. This is valid once diff --git a/toolchain/sem_ir/ids.h b/toolchain/sem_ir/ids.h index 3feecd4daaa5..ce524d349a3a 100644 --- a/toolchain/sem_ir/ids.h +++ b/toolchain/sem_ir/ids.h @@ -135,7 +135,7 @@ constexpr BoolValue BoolValue::False = BoolValue(0); constexpr BoolValue BoolValue::True = BoolValue(1); // The ID of a name. A name is either a string or a special name such as -// `self`, or eventually `Self` or `base`. +// `self`, `Self`, or `base`. struct NameId : public IdBase, public Printable { // An explicitly invalid ID. static const NameId Invalid; @@ -147,6 +147,8 @@ struct NameId : public IdBase, public Printable { static const NameId ReturnSlot; // The name of `package`. static const NameId PackageNamespace; + // The name of `base`. + static const NameId Base; // Returns the NameId corresponding to a particular IdentifierId. static auto ForIdentifier(IdentifierId id) -> NameId { @@ -175,6 +177,8 @@ struct NameId : public IdBase, public Printable { out << "ReturnSlot"; } else if (*this == PackageNamespace) { out << "PackageNamespace"; + } else if (*this == Base) { + out << "Base"; } else { CARBON_CHECK(index >= 0) << "Unknown index"; IdBase::Print(out); @@ -187,6 +191,7 @@ constexpr NameId NameId::SelfValue = NameId(NameId::InvalidIndex - 1); constexpr NameId NameId::SelfType = NameId(NameId::InvalidIndex - 2); constexpr NameId NameId::ReturnSlot = NameId(NameId::InvalidIndex - 3); constexpr NameId NameId::PackageNamespace = NameId(NameId::InvalidIndex - 4); +constexpr NameId NameId::Base = NameId(NameId::InvalidIndex - 5); // The ID of a name scope. struct NameScopeId : public IdBase, public Printable { diff --git a/toolchain/sem_ir/inst_kind.def b/toolchain/sem_ir/inst_kind.def index 8a5f5c0ae1ac..8b4097d466ea 100644 --- a/toolchain/sem_ir/inst_kind.def +++ b/toolchain/sem_ir/inst_kind.def @@ -22,6 +22,7 @@ CARBON_SEM_IR_INST_KIND(ArrayIndex) CARBON_SEM_IR_INST_KIND(ArrayInit) CARBON_SEM_IR_INST_KIND(ArrayType) CARBON_SEM_IR_INST_KIND(Assign) +CARBON_SEM_IR_INST_KIND(Base) CARBON_SEM_IR_INST_KIND(BinaryOperatorAdd) CARBON_SEM_IR_INST_KIND(BindName) CARBON_SEM_IR_INST_KIND(BindValue) diff --git a/toolchain/sem_ir/typed_insts.h b/toolchain/sem_ir/typed_insts.h index a0d46026d2ca..86270ca548f2 100644 --- a/toolchain/sem_ir/typed_insts.h +++ b/toolchain/sem_ir/typed_insts.h @@ -86,6 +86,18 @@ struct Assign { InstId rhs_id; }; +// A base in a class, of the form `base: base_type;`. A base class is an +// element of the derived class, and the type of the `Base` instruction is an +// `UnboundElementType`. +struct Base { + static constexpr auto Kind = InstKind::Base.Define("base"); + + Parse::NodeId parse_node; + TypeId type_id; + TypeId base_type_id; + ElementIndex index; +}; + struct BinaryOperatorAdd { static constexpr auto Kind = InstKind::BinaryOperatorAdd.Define("add"); diff --git a/toolchain/sem_ir/value_stores.cpp b/toolchain/sem_ir/value_stores.cpp index 2782d63d133f..4271884b7cb3 100644 --- a/toolchain/sem_ir/value_stores.cpp +++ b/toolchain/sem_ir/value_stores.cpp @@ -21,6 +21,8 @@ static auto GetSpecialName(NameId name_id, bool for_ir) -> llvm::StringRef { return for_ir ? "return" : ""; case NameId::PackageNamespace.index: return "package"; + case NameId::Base.index: + return "base"; default: CARBON_FATAL() << "Unknown special name"; }