From b01dfb3f9386f9fb98ace74f5d5b9347b88ca92c Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Wed, 18 Oct 2023 12:49:45 -0700 Subject: [PATCH] Support for class definitions with static member functions. (#3305) This includes being able to define a class that was previously forward-declared, and being able to define a member function out-of-line that was previously declared inside a class. No support for fields or methods yet, and a class definition doesn't yet cause the class to be treated as a complete type. --------- Co-authored-by: Jon Ross-Perkins --- toolchain/check/context.cpp | 20 ++-- toolchain/check/context.h | 14 ++- toolchain/check/declaration_name_stack.cpp | 73 +++++++++---- toolchain/check/declaration_name_stack.h | 10 +- toolchain/check/handle_class.cpp | 101 +++++++++++++++--- toolchain/check/handle_function.cpp | 93 ++++++++++++---- toolchain/check/handle_name.cpp | 42 ++++++-- .../array/fail_incomplete_element.carbon | 2 +- .../check/testdata/basics/verbose.carbon | 2 +- toolchain/check/testdata/class/basic.carbon | 63 +++++++++++ .../testdata/class/fail_incomplete.carbon | 75 +++++++++---- .../testdata/class/fail_redefinition.carbon | 58 ++++++++++ .../check/testdata/class/fail_reorder.carbon | 49 +++++++++ .../check/testdata/class/fail_scope.carbon | 47 ++++++++ .../testdata/class/forward_declared.carbon | 2 +- .../check/testdata/class/redeclaration.carbon | 31 ++++++ toolchain/check/testdata/class/scope.carbon | 61 +++++++++++ .../testdata/namespace/fail_duplicate.carbon | 16 ++- .../struct/fail_nested_incomplete.carbon | 2 +- .../tuples/fail_nested_incomplete.carbon | 2 +- toolchain/diagnostics/diagnostic_kind.def | 6 ++ toolchain/sem_ir/file.cpp | 14 ++- toolchain/sem_ir/file.h | 22 +++- toolchain/sem_ir/formatter.cpp | 57 ++++++---- toolchain/sem_ir/node.h | 13 +++ 25 files changed, 740 insertions(+), 135 deletions(-) create mode 100644 toolchain/check/testdata/class/basic.carbon create mode 100644 toolchain/check/testdata/class/fail_redefinition.carbon create mode 100644 toolchain/check/testdata/class/fail_reorder.carbon create mode 100644 toolchain/check/testdata/class/fail_scope.carbon create mode 100644 toolchain/check/testdata/class/redeclaration.carbon create mode 100644 toolchain/check/testdata/class/scope.carbon diff --git a/toolchain/check/context.cpp b/toolchain/check/context.cpp index 8e8b44c89ab4..0ccdf2256000 100644 --- a/toolchain/check/context.cpp +++ b/toolchain/check/context.cpp @@ -89,6 +89,13 @@ auto Context::DiagnoseNameNotFound(Parse::Node parse_node, emitter_->Emit(parse_node, NameNotFound, semantics_ir_->GetString(name_id)); } +auto Context::NoteIncompleteClass(SemIR::ClassDeclaration class_decl, + DiagnosticBuilder& builder) -> void { + CARBON_DIAGNOSTIC(ClassForwardDeclaredHere, Note, + "Class was forward declared here."); + builder.Note(class_decl.parse_node, ClassForwardDeclaredHere); +} + auto Context::AddNameToLookup(Parse::Node name_node, SemIR::StringId name_id, SemIR::NodeId target_id) -> void { if (current_scope().names.insert(name_id).second) { @@ -128,7 +135,9 @@ auto Context::LookupName(Parse::Node parse_node, SemIR::StringId name_id, } } -auto Context::PushScope() -> void { scope_stack_.push_back({}); } +auto Context::PushScope(SemIR::NameScopeId scope_id) -> void { + scope_stack_.push_back({.scope_id = scope_id}); +} auto Context::PopScope() -> void { auto scope = scope_stack_.pop_back_val(); @@ -394,11 +403,10 @@ class TypeCompleter { case SemIR::ClassDeclaration::Kind: // TODO: Support class definitions and complete class types. if (diagnoser_) { - CARBON_DIAGNOSTIC(ClassForwardDeclaredHere, Note, - "Class was forward declared here."); - (*diagnoser_)() - .Note(type_node.parse_node(), ClassForwardDeclaredHere) - .Emit(); + auto builder = (*diagnoser_)(); + context_.NoteIncompleteClass(type_node.As(), + builder); + builder.Emit(); } return false; diff --git a/toolchain/check/context.h b/toolchain/check/context.h index 01f9721b3328..da82f0c17a6f 100644 --- a/toolchain/check/context.h +++ b/toolchain/check/context.h @@ -66,12 +66,21 @@ class Context { auto DiagnoseNameNotFound(Parse::Node parse_node, SemIR::StringId name_id) -> void; + // Adds a note to a diagnostic explaining that a class is incomplete. + auto NoteIncompleteClass(SemIR::ClassDeclaration class_decl, + DiagnosticBuilder& builder) -> void; + // Pushes a new scope onto scope_stack_. - auto PushScope() -> void; + auto PushScope(SemIR::NameScopeId scope_id = SemIR::NameScopeId::Invalid) + -> void; // Pops the top scope from scope_stack_, cleaning up names from name_lookup_. auto PopScope() -> void; + auto current_scope_id() const -> SemIR::NameScopeId { + return scope_stack_.back().scope_id; + } + // Follows NameReference nodes to find the value named by a given node. auto FollowNameReferences(SemIR::NodeId node_id) -> SemIR::NodeId; @@ -232,6 +241,9 @@ class Context { // An entry in scope_stack_. struct ScopeStackEntry { + // The name scope associated with this entry, if any. + SemIR::NameScopeId scope_id; + // Names which are registered with name_lookup_, and will need to be // deregistered when the scope ends. llvm::DenseSet names; diff --git a/toolchain/check/declaration_name_stack.cpp b/toolchain/check/declaration_name_stack.cpp index 9268a7ce5c53..896097432ac1 100644 --- a/toolchain/check/declaration_name_stack.cpp +++ b/toolchain/check/declaration_name_stack.cpp @@ -11,7 +11,7 @@ namespace Carbon::Check { auto DeclarationNameStack::Push() -> void { declaration_name_stack_.push_back( {.state = NameContext::State::New, - .target_scope_id = SemIR::NameScopeId::Invalid, + .target_scope_id = context_->current_scope_id(), .resolved_node_id = SemIR::NodeId::Invalid}); } @@ -33,29 +33,29 @@ auto DeclarationNameStack::Pop() -> NameContext { return declaration_name_stack_.pop_back_val(); } -auto DeclarationNameStack::AddNameToLookup(NameContext name_context, - SemIR::NodeId target_id) -> void { +auto DeclarationNameStack::LookupOrAddName(NameContext name_context, + SemIR::NodeId target_id) + -> SemIR::NodeId { switch (name_context.state) { case NameContext::State::Error: // The name is invalid and a diagnostic has already been emitted. - return; + return SemIR::NodeId::Invalid; case NameContext::State::New: CARBON_FATAL() << "Name is missing, not expected to call AddNameToLookup " "(but that may change based on error handling)."; case NameContext::State::Resolved: - case NameContext::State::ResolvedNonScope: { - context_->DiagnoseDuplicateName(name_context.parse_node, - name_context.resolved_node_id); - return; - } + case NameContext::State::ResolvedNonScope: + return name_context.resolved_node_id; case NameContext::State::Unresolved: if (name_context.target_scope_id == SemIR::NameScopeId::Invalid) { context_->AddNameToLookup(name_context.parse_node, name_context.unresolved_name_id, target_id); } else { + // TODO: Reject unless the scope is a namespace scope or the name is + // unqualified. bool success = context_->semantics_ir().AddNameScopeEntry( name_context.target_scope_id, name_context.unresolved_name_id, target_id); @@ -64,7 +64,15 @@ auto DeclarationNameStack::AddNameToLookup(NameContext name_context, << name_context.unresolved_name_id << " in " << name_context.target_scope_id; } - return; + return SemIR::NodeId::Invalid; + } +} + +auto DeclarationNameStack::AddNameToLookup(NameContext name_context, + SemIR::NodeId target_id) -> void { + auto existing_node_id = LookupOrAddName(name_context, target_id); + if (existing_node_id.is_valid()) { + context_->DiagnoseDuplicateName(name_context.parse_node, existing_node_id); } } @@ -118,6 +126,18 @@ auto DeclarationNameStack::UpdateScopeIfNeeded(NameContext& name_context) auto resolved_node = context_->semantics_ir().GetNode(name_context.resolved_node_id); switch (resolved_node.kind()) { + case SemIR::ClassDeclaration::Kind: { + auto& class_info = context_->semantics_ir().GetClass( + resolved_node.As().class_id); + // TODO: Check that the class is complete rather than that it has a scope. + if (class_info.scope_id.is_valid()) { + name_context.state = NameContext::State::Resolved; + name_context.target_scope_id = class_info.scope_id; + } else { + name_context.state = NameContext::State::ResolvedNonScope; + } + break; + } case SemIR::Namespace::Kind: name_context.state = NameContext::State::Resolved; name_context.target_scope_id = @@ -147,16 +167,31 @@ auto DeclarationNameStack::CanResolveQualifier(NameContext& name_context, case NameContext::State::ResolvedNonScope: { // Because more qualifiers were found, we diagnose that the earlier // qualifier didn't resolve to a scoped entity. + if (auto class_decl = context_->semantics_ir() + .GetNode(name_context.resolved_node_id) + .TryAs()) { + CARBON_DIAGNOSTIC(QualifiedDeclarationInIncompleteClassScope, Error, + "Cannot declare a member of incomplete class `{0}`.", + std::string); + auto builder = context_->emitter().Build( + name_context.parse_node, QualifiedDeclarationInIncompleteClassScope, + context_->semantics_ir().StringifyTypeExpression( + name_context.resolved_node_id, true)); + context_->NoteIncompleteClass(*class_decl, builder); + builder.Emit(); + } else { + CARBON_DIAGNOSTIC( + QualifiedDeclarationInNonScope, Error, + "Declaration qualifiers are only allowed for entities " + "that provide a scope."); + CARBON_DIAGNOSTIC(QualifiedDeclarationNonScopeEntity, Note, + "Non-scope entity referenced here."); + context_->emitter() + .Build(parse_node, QualifiedDeclarationInNonScope) + .Note(name_context.parse_node, QualifiedDeclarationNonScopeEntity) + .Emit(); + } name_context.state = NameContext::State::Error; - CARBON_DIAGNOSTIC(QualifiedDeclarationInNonScope, Error, - "Declaration qualifiers are only allowed for entities " - "that provide a scope."); - CARBON_DIAGNOSTIC(QualifiedDeclarationNonScopeEntity, Note, - "Non-scope entity referenced here."); - context_->emitter() - .Build(parse_node, QualifiedDeclarationInNonScope) - .Note(name_context.parse_node, QualifiedDeclarationNonScopeEntity) - .Emit(); return false; } diff --git a/toolchain/check/declaration_name_stack.h b/toolchain/check/declaration_name_stack.h index fce289ba20b4..b4eb9b2e04b3 100644 --- a/toolchain/check/declaration_name_stack.h +++ b/toolchain/check/declaration_name_stack.h @@ -75,8 +75,9 @@ class DeclarationNameStack { State state = State::New; - // The scope which qualified names are added to. For unqualified names, - // this will be Invalid to indicate the current scope should be used. + // The scope which qualified names are added to. For unqualified names in + // an unnamed scope, this will be Invalid to indicate the current scope + // should be used. SemIR::NameScopeId target_scope_id = SemIR::NameScopeId::Invalid; // The last parse node used. @@ -117,6 +118,11 @@ class DeclarationNameStack { auto AddNameToLookup(NameContext name_context, SemIR::NodeId target_id) -> void; + // Adds a name to name lookup, or returns the existing node if this name has + // already been declared in this scope. + auto LookupOrAddName(NameContext name_context, SemIR::NodeId target_id) + -> SemIR::NodeId; + private: // Returns true if the context is in a state where it can resolve qualifiers. // Updates name_context as needed. diff --git a/toolchain/check/handle_class.cpp b/toolchain/check/handle_class.cpp index 62db93afacf3..e5134f266030 100644 --- a/toolchain/check/handle_class.cpp +++ b/toolchain/check/handle_class.cpp @@ -17,24 +17,51 @@ auto HandleClassIntroducer(Context& context, Parse::Node parse_node) -> bool { return true; } -static auto BuildClassDeclaration(Context& context) -> void { +static auto BuildClassDeclaration(Context& context) + -> std::tuple { auto name_context = context.declaration_name_stack().Pop(); - auto class_keyword = context.node_stack() .PopForSoloParseNode(); + auto decl_block_id = context.node_block_stack().Pop(); - // TODO: Track this somewhere. - context.node_block_stack().Pop(); + // Add the class declaration. + auto class_decl = + SemIR::ClassDeclaration(class_keyword, SemIR::TypeId::TypeType, + SemIR::ClassId::Invalid, decl_block_id); + auto class_decl_id = context.AddNode(class_decl); - auto class_id = context.semantics_ir().AddClass( - {.name_id = name_context.state == - DeclarationNameStack::NameContext::State::Unresolved - ? name_context.unresolved_name_id - : SemIR::StringId(SemIR::StringId::InvalidIndex)}); - auto class_decl_id = context.AddNode(SemIR::ClassDeclaration( - class_keyword, SemIR::TypeId::TypeType, class_id)); - context.declaration_name_stack().AddNameToLookup(name_context, class_decl_id); + // Check whether this is a redeclaration. + auto existing_id = context.declaration_name_stack().LookupOrAddName( + name_context, class_decl_id); + if (existing_id.is_valid()) { + if (auto existing_class_decl = context.semantics_ir() + .GetNode(existing_id) + .TryAs()) { + // This is a redeclaration of an existing class. + class_decl.class_id = existing_class_decl->class_id; + } else { + // This is a redeclaration of something other than a class. + context.DiagnoseDuplicateName(name_context.parse_node, existing_id); + } + } + + // Create a new class if this isn't a valid redeclaration. + if (!class_decl.class_id.is_valid()) { + // TODO: If this is an invalid redeclaration of a non-class entity or there + // was an error in the qualifier, we will have lost track of the class name + // here. We should keep track of it even if the name is invalid. + class_decl.class_id = context.semantics_ir().AddClass( + {.name_id = name_context.state == + DeclarationNameStack::NameContext::State::Unresolved + ? name_context.unresolved_name_id + : SemIR::StringId(SemIR::StringId::InvalidIndex)}); + } + + // Write the class ID into the ClassDeclaration. + context.semantics_ir().ReplaceNode(class_decl_id, class_decl); + + return {class_decl.class_id, class_decl_id}; } auto HandleClassDeclaration(Context& context, Parse::Node /*parse_node*/) @@ -45,13 +72,53 @@ auto HandleClassDeclaration(Context& context, Parse::Node /*parse_node*/) auto HandleClassDefinitionStart(Context& context, Parse::Node parse_node) -> bool { - BuildClassDeclaration(context); - // TODO: Introduce `Self`. - return context.TODO(parse_node, "HandleClassDefinitionStart"); + auto [class_id, class_decl_id] = BuildClassDeclaration(context); + auto& class_info = context.semantics_ir().GetClass(class_id); + + // Track that this declaration is the definition. + if (class_info.definition_id.is_valid()) { + CARBON_DIAGNOSTIC(ClassRedefinition, Error, "Redefinition of class {0}.", + llvm::StringRef); + CARBON_DIAGNOSTIC(ClassPreviousDefinition, Note, + "Previous definition was here."); + context.emitter() + .Build(parse_node, ClassRedefinition, + context.semantics_ir().GetString(class_info.name_id)) + .Note(context.semantics_ir() + .GetNode(class_info.definition_id) + .parse_node(), + ClassPreviousDefinition) + .Emit(); + } else { + class_info.definition_id = class_decl_id; + class_info.scope_id = context.semantics_ir().AddNameScope(); + + // TODO: Introduce `Self`. + } + + // Enter the class scope. + context.PushScope(class_info.scope_id); + context.node_block_stack().Push(); + + // TODO: Handle the case where there's control flow in the class body. For + // example: + // + // class C { + // var v: if true then i32 else f64; + // } + // + // We may need to track a list of node blocks here, as we do for a function. + class_info.body_block_id = context.node_block_stack().PeekOrAdd(); + return true; } -auto HandleClassDefinition(Context& context, Parse::Node parse_node) -> bool { - return context.TODO(parse_node, "HandleClassDefinition"); +auto HandleClassDefinition(Context& context, Parse::Node /*parse_node*/) + -> bool { + context.node_block_stack().Pop(); + context.PopScope(); + + // TODO: Mark the class as a complete type. + return true; } } // namespace Carbon::Check diff --git a/toolchain/check/handle_function.cpp b/toolchain/check/handle_function.cpp index 17d96590bbc3..e8d501b3dd83 100644 --- a/toolchain/check/handle_function.cpp +++ b/toolchain/check/handle_function.cpp @@ -11,7 +11,7 @@ namespace Carbon::Check { // Build a FunctionDeclaration describing the signature of a function. This // handles the common logic shared by function declaration syntax and function // definition syntax. -static auto BuildFunctionDeclaration(Context& context) +static auto BuildFunctionDeclaration(Context& context, bool is_definition) -> std::pair { // TODO: This contains the IR block for the parameters and return type. At // present, it's just loose, but it's not strictly required for parameter @@ -57,25 +57,56 @@ static auto BuildFunctionDeclaration(Context& context) context.node_stack() .PopForSoloParseNode(); - // TODO: Support out-of-line definitions, which will have a resolved - // name_context. Right now, those become errors in AddNameToLookup. - - // Add the callable. - auto function_id = context.semantics_ir().AddFunction( - {.name_id = name_context.state == - DeclarationNameStack::NameContext::State::Unresolved - ? name_context.unresolved_name_id - : SemIR::StringId(SemIR::StringId::InvalidIndex), - .param_refs_id = param_refs_id, - .return_type_id = return_type_id, - .return_slot_id = return_slot_id, - .body_block_ids = {}}); - auto decl_id = context.AddNode(SemIR::FunctionDeclaration( + // Add the function declaration. + auto function_decl = SemIR::FunctionDeclaration( fn_node, context.GetBuiltinType(SemIR::BuiltinKind::FunctionType), - function_id)); - context.declaration_name_stack().AddNameToLookup(name_context, decl_id); + SemIR::FunctionId::Invalid); + auto function_decl_id = context.AddNode(function_decl); - if (SemIR::IsEntryPoint(context.semantics_ir(), function_id)) { + // Check whether this is a redeclaration. + auto existing_id = context.declaration_name_stack().LookupOrAddName( + name_context, function_decl_id); + if (existing_id.is_valid()) { + if (auto existing_function_decl = + context.semantics_ir() + .GetNode(existing_id) + .TryAs()) { + // This is a redeclaration of an existing function. + function_decl.function_id = existing_function_decl->function_id; + + // TODO: Check that the signature matches! + + // Track the signature from the definition, so that IDs in the body match + // IDs in the signature. + if (is_definition) { + auto& function_info = + context.semantics_ir().GetFunction(function_decl.function_id); + function_info.param_refs_id = param_refs_id; + function_info.return_type_id = return_type_id; + function_info.return_slot_id = return_slot_id; + } + } else { + // This is a redeclaration of something other than a function. + context.DiagnoseDuplicateName(name_context.parse_node, existing_id); + } + } + + // Create a new function if this isn't a valid redeclaration. + if (!function_decl.function_id.is_valid()) { + function_decl.function_id = context.semantics_ir().AddFunction( + {.name_id = name_context.state == + DeclarationNameStack::NameContext::State::Unresolved + ? name_context.unresolved_name_id + : SemIR::StringId(SemIR::StringId::InvalidIndex), + .param_refs_id = param_refs_id, + .return_type_id = return_type_id, + .return_slot_id = return_slot_id}); + } + + // Write the function ID into the FunctionDeclaration. + context.semantics_ir().ReplaceNode(function_decl_id, function_decl); + + if (SemIR::IsEntryPoint(context.semantics_ir(), function_decl.function_id)) { // TODO: Update this once valid signatures for the entry point are decided. if (!context.semantics_ir().GetNodeBlock(param_refs_id).empty() || (return_slot_id.is_valid() && @@ -89,12 +120,12 @@ static auto BuildFunctionDeclaration(Context& context) } } - return {function_id, decl_id}; + return {function_decl.function_id, function_decl_id}; } auto HandleFunctionDeclaration(Context& context, Parse::Node /*parse_node*/) -> bool { - BuildFunctionDeclaration(context); + BuildFunctionDeclaration(context, /*is_definition=*/false); return true; } @@ -127,8 +158,26 @@ auto HandleFunctionDefinition(Context& context, Parse::Node parse_node) auto HandleFunctionDefinitionStart(Context& context, Parse::Node parse_node) -> bool { // Process the declaration portion of the function. - auto [function_id, decl_id] = BuildFunctionDeclaration(context); - const auto& function = context.semantics_ir().GetFunction(function_id); + auto [function_id, decl_id] = + BuildFunctionDeclaration(context, /*is_definition=*/true); + auto& function = context.semantics_ir().GetFunction(function_id); + + // Track that this declaration is the definition. + if (function.definition_id.is_valid()) { + CARBON_DIAGNOSTIC(FunctionRedefinition, Error, + "Redefinition of function {0}.", llvm::StringRef); + CARBON_DIAGNOSTIC(FunctionPreviousDefinition, Note, + "Previous definition was here."); + context.emitter() + .Build(parse_node, FunctionRedefinition, + context.semantics_ir().GetString(function.name_id)) + .Note( + context.semantics_ir().GetNode(function.definition_id).parse_node(), + FunctionPreviousDefinition) + .Emit(); + } else { + function.definition_id = decl_id; + } // Create the function scope and the entry block. context.return_scope_stack().push_back(decl_id); diff --git a/toolchain/check/handle_name.cpp b/toolchain/check/handle_name.cpp index 378142a15859..9a3f1c1f5b3c 100644 --- a/toolchain/check/handle_name.cpp +++ b/toolchain/check/handle_name.cpp @@ -9,19 +9,45 @@ namespace Carbon::Check { +// Returns the name scope corresponding to base_id, or nullopt if not a scope. +// On invalid scopes, prints a diagnostic and still returns the scope. +static auto GetAsNameScope(Context& context, SemIR::NodeId base_id) + -> std::optional { + auto base = + context.semantics_ir().GetNode(context.FollowNameReferences(base_id)); + if (auto base_as_namespace = base.TryAs()) { + return base_as_namespace->name_scope_id; + } + if (auto base_as_class = base.TryAs()) { + auto& class_info = context.semantics_ir().GetClass(base_as_class->class_id); + if (!class_info.scope_id.is_valid()) { + CARBON_DIAGNOSTIC(QualifiedExpressionInIncompleteClassScope, Error, + "Member access into incomplete class `{0}`.", + std::string); + auto builder = context.emitter().Build( + context.semantics_ir().GetNode(base_id).parse_node(), + QualifiedExpressionInIncompleteClassScope, + context.semantics_ir().StringifyTypeExpression(base_id, true)); + context.NoteIncompleteClass(*base_as_class, builder); + builder.Emit(); + } + return class_info.scope_id; + } + return std::nullopt; +} + auto HandleMemberAccessExpression(Context& context, Parse::Node parse_node) -> bool { SemIR::StringId name_id = context.node_stack().Pop(); - auto base_id = context.node_stack().PopExpression(); - if (auto base_namespace = context.semantics_ir() - .GetNode(context.FollowNameReferences(base_id)) - .TryAs()) { - // For a namespace, just resolve the name. - auto node_id = - context.LookupName(parse_node, name_id, base_namespace->name_scope_id, - /*print_diagnostics=*/true); + // If the base is a name scope, such as a class or namespace, perform lookup + // into that scope. + if (auto name_scope_id = GetAsNameScope(context, base_id)) { + auto node_id = name_scope_id->is_valid() + ? context.LookupName(parse_node, name_id, *name_scope_id, + /*print_diagnostics=*/true) + : SemIR::NodeId::BuiltinError; auto node = context.semantics_ir().GetNode(node_id); // TODO: Track that this node was named within `base_id`. context.AddNodeAndPush( diff --git a/toolchain/check/testdata/array/fail_incomplete_element.carbon b/toolchain/check/testdata/array/fail_incomplete_element.carbon index d5b8ff24e1a5..595932741177 100644 --- a/toolchain/check/testdata/array/fail_incomplete_element.carbon +++ b/toolchain/check/testdata/array/fail_incomplete_element.carbon @@ -20,7 +20,7 @@ var a: [Incomplete; 1]; var p: Incomplete* = &a[0]; // CHECK:STDOUT: file "fail_incomplete_element.carbon" { -// CHECK:STDOUT: %Incomplete: type = class_declaration @Incomplete +// CHECK:STDOUT: %Incomplete: type = class_declaration @Incomplete, () // CHECK:STDOUT: %Incomplete.ref.loc15: type = name_reference "Incomplete", %Incomplete // CHECK:STDOUT: %.loc15_21: i32 = int_literal 1 // CHECK:STDOUT: %.loc15_22: type = array_type %.loc15_21, Incomplete diff --git a/toolchain/check/testdata/basics/verbose.carbon b/toolchain/check/testdata/basics/verbose.carbon index 7fefecce9477..e1a4aeffe637 100644 --- a/toolchain/check/testdata/basics/verbose.carbon +++ b/toolchain/check/testdata/basics/verbose.carbon @@ -8,7 +8,7 @@ // NOAUTOUPDATE // SET-CHECK-SUBSET // CHECK:STDERR: Node Push 0: FunctionIntroducer -> -// CHECK:STDERR: AddNode: {kind: FunctionDeclaration, arg0: function{{[0-9]+}}, type: type{{[0-9]+}}} +// CHECK:STDERR: AddNode: {kind: FunctionDeclaration, arg0: {{.*}}, type: type{{[0-9]+}}} // CHECK:STDERR: node_block_stack_ Push 1 // CHECK:STDERR: AddNode: {kind: Return} // CHECK:STDERR: node_block_stack_ Pop 1: block{{[0-9]+}} diff --git a/toolchain/check/testdata/class/basic.carbon b/toolchain/check/testdata/class/basic.carbon new file mode 100644 index 000000000000..4c12bf65b671 --- /dev/null +++ b/toolchain/check/testdata/class/basic.carbon @@ -0,0 +1,63 @@ +// 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 Class { + fn F(n: i32) -> i32 { + return n; + } + + fn G(n: i32) -> i32; + + var k: i32; +} + +fn Class.G(n: i32) -> i32 { + return n; +} + +fn Run() -> i32 { + return Class.F(4); +} + +// CHECK:STDOUT: file "basic.carbon" { +// CHECK:STDOUT: %Class: type = class_declaration @Class, () +// CHECK:STDOUT: %G: = fn_decl @G +// CHECK:STDOUT: %Run: = fn_decl @Run +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @Class { +// CHECK:STDOUT: %F: = fn_decl @F +// CHECK:STDOUT: %G: = fn_decl @G +// CHECK:STDOUT: %k: ref i32 = var "k" +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .F = %F +// CHECK:STDOUT: .G = %G +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @F(%n: i32) -> i32 { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %n.ref: i32 = name_reference "n", %n +// CHECK:STDOUT: return %n.ref +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @G(%n: i32) -> i32 { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %n.ref: i32 = name_reference "n", %n +// CHECK:STDOUT: return %n.ref +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @Run() -> i32 { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %Class.ref: type = name_reference "Class", package.%Class +// CHECK:STDOUT: %F.ref: = name_reference "F", @Class.%F +// CHECK:STDOUT: %.loc22_18: i32 = int_literal 4 +// CHECK:STDOUT: %.loc22_17.1: init i32 = call %F.ref(%.loc22_18) +// CHECK:STDOUT: %.loc22_17.2: ref i32 = temporary_storage +// CHECK:STDOUT: %.loc22_17.3: ref i32 = temporary %.loc22_17.2, %.loc22_17.1 +// CHECK:STDOUT: %.loc22_17.4: i32 = bind_value %.loc22_17.3 +// CHECK:STDOUT: return %.loc22_17.4 +// CHECK:STDOUT: } diff --git a/toolchain/check/testdata/class/fail_incomplete.carbon b/toolchain/check/testdata/class/fail_incomplete.carbon index 0e5a86612c16..2c1f6c64720d 100644 --- a/toolchain/check/testdata/class/fail_incomplete.carbon +++ b/toolchain/check/testdata/class/fail_incomplete.carbon @@ -6,10 +6,31 @@ class Class; +// CHECK:STDERR: fail_incomplete.carbon:[[@LINE+6]]:4: ERROR: Cannot declare a member of incomplete class `Class`. +// CHECK:STDERR: fn Class.Function() {} +// CHECK:STDERR: ^ +// CHECK:STDERR: fail_incomplete.carbon:[[@LINE-5]]:1: Class was forward declared here. +// CHECK:STDERR: class Class; +// CHECK:STDERR: ^ +fn Class.Function() {} + +fn CallClassFunction() { + // CHECK:STDERR: fail_incomplete.carbon:[[@LINE+9]]:3: ERROR: Member access into incomplete class `Class`. + // CHECK:STDERR: Class.Function(); + // CHECK:STDERR: ^ + // CHECK:STDERR: fail_incomplete.carbon:[[@LINE-14]]:1: Class was forward declared here. + // CHECK:STDERR: class Class; + // CHECK:STDERR: ^ + // CHECK:STDERR: fail_incomplete.carbon:[[@LINE+3]]:18: ERROR: Semantics TODO: `Not a callable name`. + // CHECK:STDERR: Class.Function(); + // CHECK:STDERR: ^ + Class.Function(); +} + // CHECK:STDERR: fail_incomplete.carbon:[[@LINE+6]]:17: ERROR: Variable has incomplete type `Class`. // CHECK:STDERR: var global_var: Class; // CHECK:STDERR: ^ -// CHECK:STDERR: fail_incomplete.carbon:[[@LINE-5]]:1: Class was forward declared here. +// CHECK:STDERR: fail_incomplete.carbon:[[@LINE-26]]:1: Class was forward declared here. // CHECK:STDERR: class Class; // CHECK:STDERR: ^ var global_var: Class; @@ -17,7 +38,7 @@ var global_var: Class; // CHECK:STDERR: fail_incomplete.carbon:[[@LINE+6]]:24: ERROR: Function returns incomplete type `Class`. // CHECK:STDERR: fn ConvertFromStruct() -> Class { return {}; } // CHECK:STDERR: ^ -// CHECK:STDERR: fail_incomplete.carbon:[[@LINE-13]]:1: Class was forward declared here. +// CHECK:STDERR: fail_incomplete.carbon:[[@LINE-34]]:1: Class was forward declared here. // CHECK:STDERR: class Class; // CHECK:STDERR: ^ fn ConvertFromStruct() -> Class { return {}; } @@ -31,7 +52,7 @@ fn MemberAccess(p: Class*) -> i32 { // CHECK:STDERR: fail_incomplete.carbon:[[@LINE+6]]:11: ERROR: Invalid use of incomplete type `Class`. // CHECK:STDERR: return (*p).n; // CHECK:STDERR: ^ - // CHECK:STDERR: fail_incomplete.carbon:[[@LINE-27]]:1: Class was forward declared here. + // CHECK:STDERR: fail_incomplete.carbon:[[@LINE-48]]:1: Class was forward declared here. // CHECK:STDERR: class Class; // CHECK:STDERR: ^ return (*p).n; @@ -40,7 +61,7 @@ fn MemberAccess(p: Class*) -> i32 { // CHECK:STDERR: fail_incomplete.carbon:[[@LINE+6]]:20: ERROR: Function returns incomplete type `Class`. // CHECK:STDERR: fn Copy(p: Class*) -> Class { // CHECK:STDERR: ^ -// CHECK:STDERR: fail_incomplete.carbon:[[@LINE-36]]:1: Class was forward declared here. +// CHECK:STDERR: fail_incomplete.carbon:[[@LINE-57]]:1: Class was forward declared here. // CHECK:STDERR: class Class; // CHECK:STDERR: ^ fn Copy(p: Class*) -> Class { @@ -51,7 +72,7 @@ fn Let(p: Class*) { // CHECK:STDERR: fail_incomplete.carbon:[[@LINE+6]]:10: ERROR: `let` binding has incomplete type `Class`. // CHECK:STDERR: let c: Class = *p; // CHECK:STDERR: ^ - // CHECK:STDERR: fail_incomplete.carbon:[[@LINE-47]]:1: Class was forward declared here. + // CHECK:STDERR: fail_incomplete.carbon:[[@LINE-68]]:1: Class was forward declared here. // CHECK:STDERR: class Class; // CHECK:STDERR: ^ let c: Class = *p; @@ -64,7 +85,7 @@ fn TakeIncomplete(c: Class); // CHECK:STDERR: fail_incomplete.carbon:[[@LINE+6]]:23: ERROR: Function returns incomplete type `Class`. // CHECK:STDERR: fn ReturnIncomplete() -> Class; // CHECK:STDERR: ^ -// CHECK:STDERR: fail_incomplete.carbon:[[@LINE-60]]:1: Class was forward declared here. +// CHECK:STDERR: fail_incomplete.carbon:[[@LINE-81]]:1: Class was forward declared here. // CHECK:STDERR: class Class; // CHECK:STDERR: ^ fn ReturnIncomplete() -> Class; @@ -73,7 +94,7 @@ fn CallTakeIncomplete(p: Class*) { // CHECK:STDERR: fail_incomplete.carbon:[[@LINE+9]]:17: ERROR: Forming value of incomplete type `Class`. // CHECK:STDERR: TakeIncomplete(*p); // CHECK:STDERR: ^ - // CHECK:STDERR: fail_incomplete.carbon:[[@LINE-69]]:1: Class was forward declared here. + // CHECK:STDERR: fail_incomplete.carbon:[[@LINE-90]]:1: Class was forward declared here. // CHECK:STDERR: class Class; // CHECK:STDERR: ^ // CHECK:STDERR: fail_incomplete.carbon:[[@LINE-19]]:1: Initializing parameter 1 of function declared here. @@ -84,7 +105,7 @@ fn CallTakeIncomplete(p: Class*) { // CHECK:STDERR: fail_incomplete.carbon:[[@LINE+9]]:17: ERROR: Forming value of incomplete type `Class`. // CHECK:STDERR: TakeIncomplete({}); // CHECK:STDERR: ^ - // CHECK:STDERR: fail_incomplete.carbon:[[@LINE-80]]:1: Class was forward declared here. + // CHECK:STDERR: fail_incomplete.carbon:[[@LINE-101]]:1: Class was forward declared here. // CHECK:STDERR: class Class; // CHECK:STDERR: ^ // CHECK:STDERR: fail_incomplete.carbon:[[@LINE-30]]:1: Initializing parameter 1 of function declared here. @@ -98,7 +119,9 @@ fn CallReturnIncomplete() { } // CHECK:STDOUT: file "fail_incomplete.carbon" { -// CHECK:STDOUT: %Class: type = class_declaration @Class +// CHECK:STDOUT: %Class: type = class_declaration @Class, () +// CHECK:STDOUT: %.loc15: = fn_decl @.1 +// CHECK:STDOUT: %CallClassFunction: = fn_decl @CallClassFunction // CHECK:STDOUT: %Class.ref: type = name_reference "Class", %Class // CHECK:STDOUT: %global_var: ref = var "global_var" // CHECK:STDOUT: %ConvertFromStruct: = fn_decl @ConvertFromStruct @@ -113,24 +136,36 @@ fn CallReturnIncomplete() { // CHECK:STDOUT: // CHECK:STDOUT: class @Class; // CHECK:STDOUT: +// CHECK:STDOUT: fn @.1() { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: return +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @CallClassFunction() { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %Class.ref: type = name_reference "Class", package.%Class +// CHECK:STDOUT: %Function.ref: = name_reference "Function", +// CHECK:STDOUT: return +// CHECK:STDOUT: } +// CHECK:STDOUT: // CHECK:STDOUT: fn @ConvertFromStruct() -> { // CHECK:STDOUT: !entry: -// CHECK:STDOUT: %.loc23_43.1: type = struct_type {} -// CHECK:STDOUT: %.loc23_43.2: {} = struct_literal () +// CHECK:STDOUT: %.loc44_43.1: type = struct_type {} +// CHECK:STDOUT: %.loc44_43.2: {} = struct_literal () // CHECK:STDOUT: return // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: fn @MemberAccess(%p: Class*) -> i32 { // CHECK:STDOUT: !entry: // CHECK:STDOUT: %p.ref: Class* = name_reference "p", %p -// CHECK:STDOUT: %.loc37: ref Class = dereference %p.ref +// CHECK:STDOUT: %.loc58: ref Class = dereference %p.ref // CHECK:STDOUT: return // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: fn @Copy(%p: Class*) -> { // CHECK:STDOUT: !entry: // CHECK:STDOUT: %p.ref: Class* = name_reference "p", %p -// CHECK:STDOUT: %.loc47: ref Class = dereference %p.ref +// CHECK:STDOUT: %.loc68: ref Class = dereference %p.ref // CHECK:STDOUT: return // CHECK:STDOUT: } // CHECK:STDOUT: @@ -138,7 +173,7 @@ fn CallReturnIncomplete() { // CHECK:STDOUT: !entry: // CHECK:STDOUT: %Class.ref: type = name_reference "Class", package.%Class // CHECK:STDOUT: %p.ref: Class* = name_reference "p", %p -// CHECK:STDOUT: %.loc57: ref Class = dereference %p.ref +// CHECK:STDOUT: %.loc78: ref Class = dereference %p.ref // CHECK:STDOUT: %c: = bind_name "c", // CHECK:STDOUT: return // CHECK:STDOUT: } @@ -149,18 +184,18 @@ fn CallReturnIncomplete() { // CHECK:STDOUT: // CHECK:STDOUT: fn @CallTakeIncomplete(%p: Class*) { // CHECK:STDOUT: !entry: -// CHECK:STDOUT: %TakeIncomplete.ref.loc82: = name_reference "TakeIncomplete", package.%TakeIncomplete +// CHECK:STDOUT: %TakeIncomplete.ref.loc103: = name_reference "TakeIncomplete", package.%TakeIncomplete // CHECK:STDOUT: %p.ref: Class* = name_reference "p", %p -// CHECK:STDOUT: %.loc82_18: ref Class = dereference %p.ref -// CHECK:STDOUT: %.loc82_17: type = tuple_type () -// CHECK:STDOUT: %TakeIncomplete.ref.loc93: = name_reference "TakeIncomplete", package.%TakeIncomplete -// CHECK:STDOUT: %.loc93: {} = struct_literal () +// CHECK:STDOUT: %.loc103_18: ref Class = dereference %p.ref +// CHECK:STDOUT: %.loc103_17: type = tuple_type () +// CHECK:STDOUT: %TakeIncomplete.ref.loc114: = name_reference "TakeIncomplete", package.%TakeIncomplete +// CHECK:STDOUT: %.loc114: {} = struct_literal () // CHECK:STDOUT: return // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: fn @CallReturnIncomplete() { // CHECK:STDOUT: !entry: // CHECK:STDOUT: %ReturnIncomplete.ref: = name_reference "ReturnIncomplete", package.%ReturnIncomplete -// CHECK:STDOUT: %.loc97: init = call %ReturnIncomplete.ref() +// CHECK:STDOUT: %.loc118: init = call %ReturnIncomplete.ref() // CHECK:STDOUT: return // CHECK:STDOUT: } diff --git a/toolchain/check/testdata/class/fail_redefinition.carbon b/toolchain/check/testdata/class/fail_redefinition.carbon new file mode 100644 index 000000000000..d8e8670e9182 --- /dev/null +++ b/toolchain/check/testdata/class/fail_redefinition.carbon @@ -0,0 +1,58 @@ +// 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 Class { + fn F(); + fn H(); +} + +// CHECK:STDERR: fail_redefinition.carbon:[[@LINE+6]]:13: ERROR: Redefinition of class Class. +// CHECK:STDERR: class Class { +// CHECK:STDERR: ^ +// CHECK:STDERR: fail_redefinition.carbon:[[@LINE-8]]:1: Previous definition was here. +// CHECK:STDERR: class Class { +// CHECK:STDERR: ^ +class Class { + fn G(); + fn H(); +} + +fn Class.F() {} +fn Class.G() {} +fn Class.H() {} + +// CHECK:STDOUT: file "fail_redefinition.carbon" { +// CHECK:STDOUT: %Class.loc7: type = class_declaration @Class, () +// CHECK:STDOUT: %Class.loc18: type = class_declaration @Class, () +// CHECK:STDOUT: %F: = fn_decl @F +// CHECK:STDOUT: %G: = fn_decl @G +// CHECK:STDOUT: %H: = fn_decl @H +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @Class { +// CHECK:STDOUT: %G: = fn_decl @G +// CHECK:STDOUT: %H: = fn_decl @H +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .F = +// CHECK:STDOUT: .H = +// CHECK:STDOUT: .G = %G +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @F() { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: return +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @H() { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: return +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @G() { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: return +// CHECK:STDOUT: } diff --git a/toolchain/check/testdata/class/fail_reorder.carbon b/toolchain/check/testdata/class/fail_reorder.carbon new file mode 100644 index 000000000000..fdd1d62c636d --- /dev/null +++ b/toolchain/check/testdata/class/fail_reorder.carbon @@ -0,0 +1,49 @@ +// 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 Class { + fn G() -> i32 { + // TODO: This should find the member function `F` even though it's declared + // later. + // CHECK:STDERR: fail_reorder.carbon:[[@LINE+6]]:17: ERROR: Name `F` not found. + // CHECK:STDERR: return Class.F(); + // CHECK:STDERR: ^ + // CHECK:STDERR: fail_reorder.carbon:[[@LINE+3]]:20: ERROR: Semantics TODO: `Not a callable name`. + // CHECK:STDERR: return Class.F(); + // CHECK:STDERR: ^ + return Class.F(); + } + + fn F() -> i32 { + return 1; + } +} + +// CHECK:STDOUT: file "fail_reorder.carbon" { +// CHECK:STDOUT: %Class: type = class_declaration @Class, () +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @Class { +// CHECK:STDOUT: %G: = fn_decl @G +// CHECK:STDOUT: %F: = fn_decl @F +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .G = %G +// CHECK:STDOUT: .F = %F +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @G() -> i32 { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %Class.ref: type = name_reference "Class", package.%Class +// CHECK:STDOUT: %F.ref: = name_reference "F", +// CHECK:STDOUT: return +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @F() -> i32 { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %.loc21: i32 = int_literal 1 +// CHECK:STDOUT: return %.loc21 +// CHECK:STDOUT: } diff --git a/toolchain/check/testdata/class/fail_scope.carbon b/toolchain/check/testdata/class/fail_scope.carbon new file mode 100644 index 000000000000..cfcb9695b7be --- /dev/null +++ b/toolchain/check/testdata/class/fail_scope.carbon @@ -0,0 +1,47 @@ +// 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 Class { + fn F() -> i32 { + return 1; + } + + // TODO: This `F()` should find `Class.F`. + fn G() -> i32 { + // CHECK:STDERR: fail_scope.carbon:[[@LINE+6]]:12: ERROR: Name `F` not found. + // CHECK:STDERR: return F(); + // CHECK:STDERR: ^ + // CHECK:STDERR: fail_scope.carbon:[[@LINE+3]]:14: ERROR: Semantics TODO: `Not a callable name`. + // CHECK:STDERR: return F(); + // CHECK:STDERR: ^ + return F(); + } +} + +// CHECK:STDOUT: file "fail_scope.carbon" { +// CHECK:STDOUT: %Class: type = class_declaration @Class, () +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @Class { +// CHECK:STDOUT: %F: = fn_decl @F +// CHECK:STDOUT: %G: = fn_decl @G +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .F = %F +// CHECK:STDOUT: .G = %G +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @F() -> i32 { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %.loc9: i32 = int_literal 1 +// CHECK:STDOUT: return %.loc9 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @G() -> i32 { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %F.ref: = name_reference "F", +// CHECK:STDOUT: return +// CHECK:STDOUT: } diff --git a/toolchain/check/testdata/class/forward_declared.carbon b/toolchain/check/testdata/class/forward_declared.carbon index 18fe8d9f1cff..36a8660dec0a 100644 --- a/toolchain/check/testdata/class/forward_declared.carbon +++ b/toolchain/check/testdata/class/forward_declared.carbon @@ -9,7 +9,7 @@ class Class; fn F(p: Class*) -> Class* { return p; } // CHECK:STDOUT: file "forward_declared.carbon" { -// CHECK:STDOUT: %Class: type = class_declaration @Class +// CHECK:STDOUT: %Class: type = class_declaration @Class, () // CHECK:STDOUT: %F: = fn_decl @F // CHECK:STDOUT: } // CHECK:STDOUT: diff --git a/toolchain/check/testdata/class/redeclaration.carbon b/toolchain/check/testdata/class/redeclaration.carbon new file mode 100644 index 000000000000..b627f0a4e067 --- /dev/null +++ b/toolchain/check/testdata/class/redeclaration.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 + +class Class; + +class Class { + fn F(); +} + +fn Class.F() {} + +// CHECK:STDOUT: file "redeclaration.carbon" { +// CHECK:STDOUT: %Class.loc7: type = class_declaration @Class, () +// CHECK:STDOUT: %Class.loc9: type = class_declaration @Class, () +// CHECK:STDOUT: %F: = fn_decl @F +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @Class { +// CHECK:STDOUT: %F: = fn_decl @F +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .F = %F +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @F() { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: return +// CHECK:STDOUT: } diff --git a/toolchain/check/testdata/class/scope.carbon b/toolchain/check/testdata/class/scope.carbon new file mode 100644 index 000000000000..1ff3e94b5c61 --- /dev/null +++ b/toolchain/check/testdata/class/scope.carbon @@ -0,0 +1,61 @@ +// 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 Class { + fn F() -> i32 { + return 1; + } +} + +fn F() -> i32 { + return 2; +} + +fn Run() -> i32 { + return F() + Class.F(); +} + +// CHECK:STDOUT: file "scope.carbon" { +// CHECK:STDOUT: %Class: type = class_declaration @Class, () +// CHECK:STDOUT: %F: = fn_decl @F.2 +// CHECK:STDOUT: %Run: = fn_decl @Run +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @Class { +// CHECK:STDOUT: %F: = fn_decl @F.1 +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .F = %F +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @F.1() -> i32 { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %.loc9: i32 = int_literal 1 +// CHECK:STDOUT: return %.loc9 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @F.2() -> i32 { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %.loc14: i32 = int_literal 2 +// CHECK:STDOUT: return %.loc14 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @Run() -> i32 { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %F.ref.loc18_10: = name_reference "F", package.%F +// CHECK:STDOUT: %.loc18_11.1: init i32 = call %F.ref.loc18_10() +// CHECK:STDOUT: %Class.ref: type = name_reference "Class", package.%Class +// CHECK:STDOUT: %F.ref.loc18_21: = name_reference "F", @Class.%F +// CHECK:STDOUT: %.loc18_23.1: init i32 = call %F.ref.loc18_21() +// CHECK:STDOUT: %.loc18_11.2: ref i32 = temporary_storage +// CHECK:STDOUT: %.loc18_11.3: ref i32 = temporary %.loc18_11.2, %.loc18_11.1 +// CHECK:STDOUT: %.loc18_11.4: i32 = bind_value %.loc18_11.3 +// CHECK:STDOUT: %.loc18_23.2: ref i32 = temporary_storage +// CHECK:STDOUT: %.loc18_23.3: ref i32 = temporary %.loc18_23.2, %.loc18_23.1 +// CHECK:STDOUT: %.loc18_23.4: i32 = bind_value %.loc18_23.3 +// CHECK:STDOUT: %.loc18_14: i32 = add %.loc18_11.4, %.loc18_23.4 +// CHECK:STDOUT: return %.loc18_14 +// CHECK:STDOUT: } diff --git a/toolchain/check/testdata/namespace/fail_duplicate.carbon b/toolchain/check/testdata/namespace/fail_duplicate.carbon index a515a5f5ae06..939aa612d3bc 100644 --- a/toolchain/check/testdata/namespace/fail_duplicate.carbon +++ b/toolchain/check/testdata/namespace/fail_duplicate.carbon @@ -9,27 +9,25 @@ namespace Foo; fn Foo.Baz() { } -// CHECK:STDERR: fail_duplicate.carbon:[[@LINE+6]]:8: ERROR: Duplicate name being declared in the same scope. +// CHECK:STDERR: fail_duplicate.carbon:[[@LINE+6]]:14: ERROR: Redefinition of function Baz. // CHECK:STDERR: fn Foo.Baz() { -// CHECK:STDERR: ^ -// CHECK:STDERR: fail_duplicate.carbon:[[@LINE-6]]:1: Name is previously declared here. +// CHECK:STDERR: ^ +// CHECK:STDERR: fail_duplicate.carbon:[[@LINE-6]]:1: Previous definition was here. // CHECK:STDERR: fn Foo.Baz() { // CHECK:STDERR: ^ fn Foo.Baz() { } // CHECK:STDOUT: file "fail_duplicate.carbon" { -// CHECK:STDOUT: %.loc7: = namespace {.Baz = %Baz} -// CHECK:STDOUT: %Baz: = fn_decl @Baz -// CHECK:STDOUT: %.loc18: = fn_decl @.1 +// CHECK:STDOUT: %.loc7: = namespace {.Baz = %Baz.loc9} +// CHECK:STDOUT: %Baz.loc9: = fn_decl @Baz +// CHECK:STDOUT: %Baz.loc18: = fn_decl @Baz // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: fn @Baz() { // CHECK:STDOUT: !entry: // CHECK:STDOUT: return -// CHECK:STDOUT: } // CHECK:STDOUT: -// CHECK:STDOUT: fn @.1() { -// CHECK:STDOUT: !entry: +// CHECK:STDOUT: !.loc19: // CHECK:STDOUT: return // CHECK:STDOUT: } diff --git a/toolchain/check/testdata/struct/fail_nested_incomplete.carbon b/toolchain/check/testdata/struct/fail_nested_incomplete.carbon index d7c5bd56f1e7..c77aa347f3f6 100644 --- a/toolchain/check/testdata/struct/fail_nested_incomplete.carbon +++ b/toolchain/check/testdata/struct/fail_nested_incomplete.carbon @@ -20,7 +20,7 @@ var s: {.a: Incomplete}; var p: Incomplete* = &s.a; // CHECK:STDOUT: file "fail_nested_incomplete.carbon" { -// CHECK:STDOUT: %Incomplete: type = class_declaration @Incomplete +// CHECK:STDOUT: %Incomplete: type = class_declaration @Incomplete, () // CHECK:STDOUT: %Incomplete.ref.loc15: type = name_reference "Incomplete", %Incomplete // CHECK:STDOUT: %.loc15: type = struct_type {.a: Incomplete} // CHECK:STDOUT: %s: ref = var "s" diff --git a/toolchain/check/testdata/tuples/fail_nested_incomplete.carbon b/toolchain/check/testdata/tuples/fail_nested_incomplete.carbon index ea06d602aecd..39fd650bf6b6 100644 --- a/toolchain/check/testdata/tuples/fail_nested_incomplete.carbon +++ b/toolchain/check/testdata/tuples/fail_nested_incomplete.carbon @@ -20,7 +20,7 @@ var t: (i32, Incomplete); var p: Incomplete* = &t[1]; // CHECK:STDOUT: file "fail_nested_incomplete.carbon" { -// CHECK:STDOUT: %Incomplete: type = class_declaration @Incomplete +// CHECK:STDOUT: %Incomplete: type = class_declaration @Incomplete, () // CHECK:STDOUT: %Incomplete.ref.loc15: type = name_reference "Incomplete", %Incomplete // CHECK:STDOUT: %.loc15_24.1: type = tuple_type (type, type) // CHECK:STDOUT: %.loc15_24.2: (type, type) = tuple_literal (i32, %Incomplete.ref.loc15) diff --git a/toolchain/diagnostics/diagnostic_kind.def b/toolchain/diagnostics/diagnostic_kind.def index fcc41218beba..363706fa7062 100644 --- a/toolchain/diagnostics/diagnostic_kind.def +++ b/toolchain/diagnostics/diagnostic_kind.def @@ -116,9 +116,13 @@ CARBON_DIAGNOSTIC_KIND(ArrayInitFromExpressionArgCountMismatch) CARBON_DIAGNOSTIC_KIND(AssignmentToNonAssignable) CARBON_DIAGNOSTIC_KIND(BreakOutsideLoop) CARBON_DIAGNOSTIC_KIND(ClassForwardDeclaredHere) +CARBON_DIAGNOSTIC_KIND(ClassPreviousDefinition) +CARBON_DIAGNOSTIC_KIND(ClassRedefinition) CARBON_DIAGNOSTIC_KIND(ContinueOutsideLoop) CARBON_DIAGNOSTIC_KIND(DereferenceOfNonPointer) CARBON_DIAGNOSTIC_KIND(DereferenceOfType) +CARBON_DIAGNOSTIC_KIND(FunctionPreviousDefinition) +CARBON_DIAGNOSTIC_KIND(FunctionRedefinition) CARBON_DIAGNOSTIC_KIND(NameNotFound) CARBON_DIAGNOSTIC_KIND(NameDeclarationDuplicate) CARBON_DIAGNOSTIC_KIND(NameDeclarationPrevious) @@ -146,8 +150,10 @@ CARBON_DIAGNOSTIC_KIND(ReturnStatementDisallowExpression) CARBON_DIAGNOSTIC_KIND(ReturnStatementImplicitNote) CARBON_DIAGNOSTIC_KIND(ReturnStatementMissingExpression) CARBON_DIAGNOSTIC_KIND(ImplicitAsConversionFailure) +CARBON_DIAGNOSTIC_KIND(QualifiedDeclarationInIncompleteClassScope) CARBON_DIAGNOSTIC_KIND(QualifiedDeclarationInNonScope) CARBON_DIAGNOSTIC_KIND(QualifiedDeclarationNonScopeEntity) +CARBON_DIAGNOSTIC_KIND(QualifiedExpressionInIncompleteClassScope) CARBON_DIAGNOSTIC_KIND(QualifiedExpressionUnsupported) CARBON_DIAGNOSTIC_KIND(QualifiedExpressionNameNotFound) CARBON_DIAGNOSTIC_KIND(UseOfNonExpressionAsValue) diff --git a/toolchain/sem_ir/file.cpp b/toolchain/sem_ir/file.cpp index 16787c4f99ba..804cec348de1 100644 --- a/toolchain/sem_ir/file.cpp +++ b/toolchain/sem_ir/file.cpp @@ -207,6 +207,7 @@ static auto GetTypePrecedence(NodeKind kind) -> int { case ArrayType::Kind: case Builtin::Kind: case ClassDeclaration::Kind: + case NameReference::Kind: case StructType::Kind: case TupleType::Kind: return 0; @@ -238,7 +239,6 @@ static auto GetTypePrecedence(NodeKind kind) -> int { case FunctionDeclaration::Kind: case InitializeFrom::Kind: case IntegerLiteral::Kind: - case NameReference::Kind: case Namespace::Kind: case NoOp::Kind: case Parameter::Kind: @@ -268,6 +268,12 @@ static auto GetTypePrecedence(NodeKind kind) -> int { auto File::StringifyType(TypeId type_id, bool in_type_context) const -> std::string { + return StringifyTypeExpression(GetTypeAllowBuiltinTypes(type_id), + in_type_context); +} + +auto File::StringifyTypeExpression(NodeId outer_node_id, + bool in_type_context) const -> std::string { std::string str; llvm::raw_string_ostream out(str); @@ -281,7 +287,6 @@ auto File::StringifyType(TypeId type_id, bool in_type_context) const return {.node_id = node_id, .index = index + 1}; } }; - auto outer_node_id = GetTypeAllowBuiltinTypes(type_id); llvm::SmallVector steps = {{.node_id = outer_node_id}}; while (!steps.empty()) { @@ -338,6 +343,10 @@ auto File::StringifyType(TypeId type_id, bool in_type_context) const } break; } + case NameReference::Kind: { + out << GetString(node.As().name_id); + break; + } case PointerType::Kind: { if (step.index == 0) { steps.push_back(step.Next()); @@ -414,7 +423,6 @@ auto File::StringifyType(TypeId type_id, bool in_type_context) const case FunctionDeclaration::Kind: case InitializeFrom::Kind: case IntegerLiteral::Kind: - case NameReference::Kind: case Namespace::Kind: case NoOp::Kind: case Parameter::Kind: diff --git a/toolchain/sem_ir/file.h b/toolchain/sem_ir/file.h index 1dd775f49926..eafe81d86e69 100644 --- a/toolchain/sem_ir/file.h +++ b/toolchain/sem_ir/file.h @@ -35,6 +35,9 @@ struct Function : public Printable { // The function name. StringId name_id; + // The definition, if the function has been defined or is currently being + // defined. This is a FunctionDeclaration. + NodeId definition_id = NodeId::Invalid; // A block containing a single reference node per parameter. NodeBlockId param_refs_id; // The return type. This will be invalid if the return type wasn't specified. @@ -48,7 +51,7 @@ struct Function : public Printable { // A list of the statically reachable code blocks in the body of the // function, in lexical order. The first block is the entry block. This will // be empty for declarations that don't have a visible definition. - llvm::SmallVector body_block_ids; + llvm::SmallVector body_block_ids = {}; }; // A class. @@ -60,6 +63,17 @@ struct Class : public Printable { // The class name. StringId name_id; + + // The definition, if the class has been defined or is currently being + // defined. This is a ClassDeclaration. + NodeId definition_id = NodeId::Invalid; + + // The class scope. + NameScopeId scope_id = NameScopeId::Invalid; + + // The first block of the class body. + // TODO: Handle control flow in the class body, such as if-expressions. + NodeBlockId body_block_id = NodeBlockId::Invalid; }; // TODO: Replace this with a Rational type, per the design: @@ -428,6 +442,12 @@ class File : public Printable { auto StringifyType(TypeId type_id, bool in_type_context = false) const -> std::string; + // Same as `StringifyType`, but starting with a node representing a type + // expression rather than a canonical type. + auto StringifyTypeExpression(NodeId outer_node_id, + bool in_type_context = false) const + -> std::string; + auto functions_size() const -> int { return functions_.size(); } auto classes_size() const -> int { return classes_.size(); } auto nodes_size() const -> int { return nodes_.size(); } diff --git a/toolchain/sem_ir/formatter.cpp b/toolchain/sem_ir/formatter.cpp index 340bec874455..6855d7e6d234 100644 --- a/toolchain/sem_ir/formatter.cpp +++ b/toolchain/sem_ir/formatter.cpp @@ -89,7 +89,8 @@ class NodeNamer { class_info.name_id.is_valid() ? semantics_ir.GetString(class_info.name_id).str() : ""); - // TODO: Handle names declared in the class scope. + AddBlockLabel(class_scope, class_info.body_block_id, "class", class_loc); + CollectNamesInBlock(class_scope, class_info.body_block_id); } } @@ -509,9 +510,18 @@ class Formatter { out_ << "\nclass "; FormatClassName(id); - // TODO: Format class definitions. - (void)class_info; - out_ << ";\n"; + + llvm::SaveAndRestore class_scope(scope_, node_namer_.GetScopeFor(id)); + + if (class_info.scope_id.is_valid()) { + out_ << " {\n"; + FormatCodeBlock(class_info.body_block_id); + out_ << "\n!members:"; + FormatNameScope(class_info.scope_id, "", "\n ."); + out_ << "\n}\n"; + } else { + out_ << ";\n"; + } } auto FormatFunction(FunctionId id) -> void { @@ -572,6 +582,26 @@ class Formatter { } } + auto FormatNameScope(NameScopeId id, llvm::StringRef separator, + llvm::StringRef prefix) -> void { + // Name scopes aren't kept in any particular order. Sort the entries before + // we print them for stability and consistency. + llvm::SmallVector> entries; + for (auto [name_id, node_id] : semantics_ir_.GetNameScope(id)) { + entries.push_back({node_id, name_id}); + } + llvm::sort(entries, + [](auto a, auto b) { return a.first.index < b.first.index; }); + + llvm::ListSeparator sep(separator); + for (auto [node_id, name_id] : entries) { + out_ << sep << prefix; + FormatString(name_id); + out_ << " = "; + FormatNodeName(node_id); + } + } + auto FormatInstruction(NodeId node_id) -> void { if (!node_id.is_valid()) { Indent(); @@ -788,26 +818,9 @@ class Formatter { auto FormatArg(MemberIndex index) -> void { out_ << index; } - // TODO: Should we be printing scopes inline, or should we have a separate - // step to print them like we do for functions? auto FormatArg(NameScopeId id) -> void { - // Name scopes aren't kept in any particular order. Sort the entries before - // we print them for stability and consistency. - std::vector> entries; - for (auto [name_id, node_id] : semantics_ir_.GetNameScope(id)) { - entries.push_back({node_id, name_id}); - } - llvm::sort(entries, - [](auto a, auto b) { return a.first.index < b.first.index; }); - out_ << '{'; - llvm::ListSeparator sep; - for (auto [node_id, name_id] : entries) { - out_ << sep << "."; - FormatString(name_id); - out_ << " = "; - FormatNodeName(node_id); - } + FormatNameScope(id, ", ", "."); out_ << '}'; } diff --git a/toolchain/sem_ir/node.h b/toolchain/sem_ir/node.h index 6880a2320dbc..9c7627bdca48 100644 --- a/toolchain/sem_ir/node.h +++ b/toolchain/sem_ir/node.h @@ -56,6 +56,9 @@ constexpr NodeId NodeId::Invalid = NodeId(NodeId::InvalidIndex); // The ID of a function. struct FunctionId : public IndexBase, public Printable { + // An explicitly invalid function ID. + static const FunctionId Invalid; + using IndexBase::IndexBase; auto Print(llvm::raw_ostream& out) const -> void { out << "function"; @@ -63,8 +66,13 @@ struct FunctionId : public IndexBase, public Printable { } }; +constexpr FunctionId FunctionId::Invalid = FunctionId(FunctionId::InvalidIndex); + // The ID of a class. struct ClassId : public IndexBase, public Printable { + // An explicitly invalid class ID. + static const ClassId Invalid; + using IndexBase::IndexBase; auto Print(llvm::raw_ostream& out) const -> void { out << "class"; @@ -72,6 +80,8 @@ struct ClassId : public IndexBase, public Printable { } }; +constexpr ClassId ClassId::Invalid = ClassId(ClassId::InvalidIndex); + // The ID of a cross-referenced IR. struct CrossReferenceIRId : public IndexBase, public Printable { @@ -323,6 +333,9 @@ struct Call { struct ClassDeclaration { ClassId class_id; + // The declaration block, containing the class name's qualifiers and the + // class's generic parameters. + NodeBlockId decl_block_id; }; struct ConstType {