From f9ce0b194dff0d9950ce80183df9e63a6fb70d13 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Mon, 1 Apr 2024 11:25:27 -0700 Subject: [PATCH] Defer parsing of method bodies until the end of a suitable enclosing scope. (#3832) In parse, form a list of methods that are defined inline, tracking where they start, where they end, and which other inline methods are nested within them. In check, when we reach an inline method body, skip it and add it to a worklist to be processed later. We also track when we reach the start and end of a context in which inline method bodies are deferred, so that we know when to replay the bodies. When suspending a function definition to be processed later, the `DeclNameStack` entry is moved to separate storage, including popping the corresponding scopes from the scope stack and removing the corresponding lexical names from lexical lookup. Later, when we return to the function and parse its definition, the `DeclNameStack` entry is restored. The same is done when we reach the end of a nested context that can have inline methods, so that we can reenter the nested scope before processing its members. --------- Co-authored-by: Jon Ross-Perkins --- common/BUILD | 9 + common/variant_helpers.h | 42 ++ toolchain/check/BUILD | 7 +- toolchain/check/check.cpp | 402 +++++++++++++++++- toolchain/check/decl_name_stack.cpp | 30 ++ toolchain/check/decl_name_stack.h | 23 + toolchain/check/function.h | 14 + toolchain/check/handle.h | 35 ++ toolchain/check/handle_class.cpp | 4 +- toolchain/check/handle_function.cpp | 41 +- toolchain/check/handle_impl.cpp | 2 +- toolchain/check/handle_interface.cpp | 3 +- toolchain/check/handle_named_constraint.cpp | 1 + toolchain/check/lexical_lookup.h | 33 +- toolchain/check/scope_stack.cpp | 41 +- toolchain/check/scope_stack.h | 39 +- .../class/complete_in_member_fn.carbon | 52 +++ .../check/testdata/class/fail_scope.carbon | 6 +- .../class/fail_todo_local_class.carbon | 45 ++ toolchain/check/testdata/class/nested.carbon | 225 ++++++---- .../check/testdata/class/raw_self_type.carbon | 12 +- .../{fail_reorder.carbon => reorder.carbon} | 31 +- .../testdata/class/reorder_qualified.carbon | 192 +++++++++ toolchain/check/testdata/class/scope.carbon | 6 +- toolchain/check/testdata/impl/impl_as.carbon | 15 +- .../check/testdata/impl/self_in_class.carbon | 10 +- .../testdata/impl/self_in_signature.carbon | 20 +- .../testdata/index/fail_invalid_base.carbon | 24 +- .../testdata/interface/default_fn.carbon | 96 +++++ .../testdata/operators/overloaded/add.carbon | 10 +- .../operators/overloaded/bit_and.carbon | 10 +- .../overloaded/bit_complement.carbon | 10 +- .../operators/overloaded/bit_or.carbon | 10 +- .../operators/overloaded/bit_xor.carbon | 10 +- .../testdata/operators/overloaded/div.carbon | 10 +- .../operators/overloaded/left_shift.carbon | 10 +- .../testdata/operators/overloaded/mod.carbon | 10 +- .../testdata/operators/overloaded/mul.carbon | 10 +- .../operators/overloaded/negate.carbon | 10 +- .../operators/overloaded/right_shift.carbon | 10 +- .../testdata/operators/overloaded/sub.carbon | 10 +- .../check/testdata/package_expr/syntax.carbon | 2 +- toolchain/lex/BUILD | 1 + toolchain/lex/lex.cpp | 23 +- toolchain/parse/context.cpp | 44 ++ toolchain/parse/context.h | 14 + toolchain/parse/handle_function.cpp | 8 +- toolchain/parse/tree.h | 37 ++ 48 files changed, 1439 insertions(+), 270 deletions(-) create mode 100644 common/variant_helpers.h create mode 100644 toolchain/check/handle.h create mode 100644 toolchain/check/testdata/class/complete_in_member_fn.carbon create mode 100644 toolchain/check/testdata/class/fail_todo_local_class.carbon rename toolchain/check/testdata/class/{fail_reorder.carbon => reorder.carbon} (57%) create mode 100644 toolchain/check/testdata/class/reorder_qualified.carbon create mode 100644 toolchain/check/testdata/interface/default_fn.carbon diff --git a/common/BUILD b/common/BUILD index c98fd2ffac14..645bf9a11e23 100644 --- a/common/BUILD +++ b/common/BUILD @@ -238,6 +238,15 @@ cc_test( ], ) +cc_library( + name = "variant_helpers", + hdrs = ["variant_helpers.h"], + deps = [ + ":error", + "@llvm-project//llvm:Support", + ], +) + cc_library( name = "vlog", srcs = ["vlog_internal.h"], diff --git a/common/variant_helpers.h b/common/variant_helpers.h new file mode 100644 index 000000000000..d45691fba05d --- /dev/null +++ b/common/variant_helpers.h @@ -0,0 +1,42 @@ +// 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 + +#ifndef CARBON_COMMON_VARIANT_HELPERS_H_ +#define CARBON_COMMON_VARIANT_HELPERS_H_ + +#include + +#include "common/error.h" +#include "llvm/ADT/StringRef.h" + +namespace Carbon { + +namespace Internal { + +// Form an overload set from a list of functions. For example: +// +// ``` +// auto overloaded = Overload{[] (int) {}, [] (float) {}}; +// ``` +template +struct Overload : Fs... { + using Fs::operator()...; +}; +template +Overload(Fs...) -> Overload; + +} // namespace Internal + +// Pattern-match against the type of the value stored in the variant `V`. Each +// element of `fs` should be a function that takes one or more of the variant +// values in `V`. +template +auto VariantMatch(V&& v, Fs&&... fs) -> decltype(auto) { + return std::visit(Internal::Overload{std::forward(fs)...}, + std::forward(v)); +} + +} // namespace Carbon + +#endif // CARBON_COMMON_VARIANT_HELPERS_H_ diff --git a/toolchain/check/BUILD b/toolchain/check/BUILD index 6643a9f2e152..ad966c2a1da3 100644 --- a/toolchain/check/BUILD +++ b/toolchain/check/BUILD @@ -101,7 +101,10 @@ cc_library( cc_library( name = "check", - srcs = ["check.cpp"] + + srcs = [ + "check.cpp", + "handle.h", + ] + # Glob handler files to avoid missing any. glob([ "handle_*.cpp", @@ -117,7 +120,9 @@ cc_library( ":operator", ":pointer_dereference", "//common:check", + "//common:error", "//common:ostream", + "//common:variant_helpers", "//toolchain/base:pretty_stack_trace_function", "//toolchain/base:value_store", "//toolchain/diagnostics:diagnostic_emitter", diff --git a/toolchain/check/check.cpp b/toolchain/check/check.cpp index df506b8c4f0c..59afece898a7 100644 --- a/toolchain/check/check.cpp +++ b/toolchain/check/check.cpp @@ -4,10 +4,15 @@ #include "toolchain/check/check.h" +#include + #include "common/check.h" +#include "common/error.h" #include "toolchain/base/pretty_stack_trace_function.h" #include "toolchain/check/context.h" #include "toolchain/check/diagnostic_helpers.h" +#include "toolchain/check/function.h" +#include "toolchain/check/handle.h" #include "toolchain/check/import.h" #include "toolchain/diagnostics/diagnostic.h" #include "toolchain/diagnostics/diagnostic_emitter.h" @@ -21,11 +26,6 @@ namespace Carbon::Check { -// Parse node handlers. Returns false for unrecoverable errors. -#define CARBON_PARSE_NODE_KIND(Name) \ - auto Handle##Name(Context& context, Parse::Name##Id node_id) -> bool; -#include "toolchain/parse/node_kind.def" - // Handles the transformation of a SemIRLoc to a DiagnosticLoc. // // TODO: Move this to diagnostic_helpers.cpp. @@ -274,14 +274,400 @@ static auto InitPackageScopeAndImports(Context& context, UnitInfo& unit_info) << "Created an unexpected number of IRs"; } +namespace { +// State used to track the next deferred function definition that we will +// encounter and need to reorder. +class NextDeferredDefinitionCache { + public: + explicit NextDeferredDefinitionCache(const Parse::Tree* tree) : tree_(tree) { + SkipTo(Parse::DeferredDefinitionIndex(0)); + } + + // Set the specified deferred definition index as being the next one that will + // be encountered. + auto SkipTo(Parse::DeferredDefinitionIndex next_index) -> void { + index_ = next_index; + if (static_cast(index_.index) == + tree_->deferred_definitions().size()) { + start_id_ = Parse::NodeId::Invalid; + } else { + start_id_ = tree_->deferred_definitions().Get(index_).start_id; + } + } + + // Returns the index of the next deferred definition to be encountered. + auto index() const -> Parse::DeferredDefinitionIndex { return index_; } + + // Returns the ID of the start node of the next deferred definition. + auto start_id() const -> Parse::NodeId { return start_id_; } + + private: + const Parse::Tree* tree_; + Parse::DeferredDefinitionIndex index_ = + Parse::DeferredDefinitionIndex::Invalid; + Parse::NodeId start_id_ = Parse::NodeId::Invalid; +}; +} // namespace + +// Determines whether we are currently declaring a name in a scope in which +// function definitions are deferred. When entering another deferred definition +// scope, the inner scope's function definitions are checked at the end of the +// outer scope, not the inner one. For example: +// +// ``` +// class A { +// class B { +// fn F() -> A { return {}; } +// } +// } // A.B.F is type-checked here, with A complete. +// +// fn F() { +// class C { +// fn G() {} +// } // C.G is type-checked here. +// } +// ``` +static auto IsInDeferredDefinitionScope(Context& context) -> bool { + auto inst_id = context.name_scopes().GetInstIdIfValid( + context.decl_name_stack().PeekTargetScope()); + if (!inst_id.is_valid()) { + return false; + } + switch (context.insts().Get(inst_id).kind()) { + case SemIR::ClassDecl::Kind: + case SemIR::ImplDecl::Kind: + case SemIR::InterfaceDecl::Kind: + // TODO: Named constraints, mixins. + return true; + + default: + return false; + } +} + +// Determines whether this node kind is the start of a deferred definition +// scope. +static auto IsStartOfDeferredDefinitionScope(Parse::NodeKind kind) -> bool { + switch (kind) { + case Parse::NodeKind::ClassDefinitionStart: + case Parse::NodeKind::ImplDefinitionStart: + case Parse::NodeKind::InterfaceDefinitionStart: + case Parse::NodeKind::NamedConstraintDefinitionStart: + // TODO: Mixins. + return true; + default: + return false; + } +} + +// Determines whether this node kind is the end of a deferred definition scope. +static auto IsEndOfDeferredDefinitionScope(Parse::NodeKind kind) -> bool { + switch (kind) { + case Parse::NodeKind::ClassDefinition: + case Parse::NodeKind::ImplDefinition: + case Parse::NodeKind::InterfaceDefinition: + case Parse::NodeKind::NamedConstraintDefinition: + // TODO: Mixins. + return true; + default: + return false; + } +} + +namespace { +// A worklist of pending tasks to perform to check deferred function definitions +// in the right order. +class DeferredDefinitionWorklist { + public: + // A worklist task that indicates we should check a deferred function + // definition that we previously skipped. + struct CheckSkippedDefinition { + // The definition that we skipped. + Parse::DeferredDefinitionIndex definition_index; + // The suspended function. + SuspendedFunction suspended_fn; + }; + + // A worklist task that indicates we should enter a nested deferred definition + // scope. + struct EnterDeferredDefinitionScope { + // The suspended scope. This is only set once we reach the end of the scope. + std::optional suspended_name; + // Whether this scope is itself within an outer deferred definition scope. + // If so, we'll delay processing its contents until we reach the end of the + // enclosing scope. + bool in_deferred_definition_scope; + }; + + // A worklist task that indicates we should leave a deferred definition scope. + struct LeaveDeferredDefinitionScope { + // Whether this scope is within another deferred definition scope. + bool in_deferred_definition_scope; + }; + + // A pending type-checking task. + using Task = + std::variant; + + DeferredDefinitionWorklist() { + // See declaration of `worklist_`. + worklist_.reserve(64); + } + + // Suspend the current function definition and push a task onto the worklist + // to finish it later. + auto SuspendFunctionAndPush(Context& context, + Parse::DeferredDefinitionIndex index, + Parse::FunctionDefinitionStartId node_id) + -> void { + worklist_.push_back(CheckSkippedDefinition{ + index, HandleFunctionDefinitionSuspend(context, node_id)}); + } + + // Push a task to re-enter a function scope, so that functions defined within + // it are type-checked in the right context. + auto PushEnterDeferredDefinitionScope(Context& context) -> void { + enclosing_scopes_.push_back(worklist_.size()); + worklist_.push_back(EnterDeferredDefinitionScope{ + std::nullopt, IsInDeferredDefinitionScope(context)}); + } + + // Suspend the current deferred definition scope, which is finished but still + // on the decl_name_stack, and push a task to leave the scope when we're + // type-checking deferred definitions. Returns `true` if the current list of + // deferred definitions should be type-checked immediately. + auto SuspendFinishedScopeAndPush(Context& context) -> bool; + + // Pop the next task off the worklist. + auto Pop() -> Task { return worklist_.pop_back_val(); } + + // CHECK that the work list has no further work. + auto VerifyEmpty() { + CARBON_CHECK(worklist_.empty() && enclosing_scopes_.empty()) + << "Tasks left behind on worklist."; + } + + private: + // A worklist of type-checking tasks we'll need to do later. + // + // Don't allocate any inline storage here. A Task is fairly large, so we never + // want this to live on the stack. Instead, we reserve space in the + // constructor for a fairly large number of deferred definitions. + llvm::SmallVector worklist_; + + // Indexes in `worklist` of deferred definition scopes that are currently + // still open. + llvm::SmallVector enclosing_scopes_; +}; +} // namespace + +auto DeferredDefinitionWorklist::SuspendFinishedScopeAndPush(Context& context) + -> bool { + auto scope_index = enclosing_scopes_.pop_back_val(); + + // If we've not found any deferred definitions in this scope, clean up the + // stack. + if (scope_index == worklist_.size() - 1) { + context.decl_name_stack().PopScope(); + worklist_.pop_back(); + return false; + } + + // If we're finishing a nested deferred definition scope, keep track of that + // but don't type-check deferred definitions now. + auto& enter_scope = get(worklist_[scope_index]); + if (enter_scope.in_deferred_definition_scope) { + // This is a nested deferred definition scope. Suspend the inner scope so we + // can restore it when we come to type-check the deferred definitions. + enter_scope.suspended_name = context.decl_name_stack().Suspend(); + + // Enqueue a task to leave the nested scope. + worklist_.push_back( + LeaveDeferredDefinitionScope{.in_deferred_definition_scope = true}); + return false; + } + + // We're at the end of a non-nested deferred definition scope. Prepare to + // start checking deferred definitions. Enqueue a task to leave this outer + // scope and end checking deferred definitions. + worklist_.push_back( + LeaveDeferredDefinitionScope{.in_deferred_definition_scope = false}); + + // We'll process the worklist in reverse index order, so reverse the part of + // it we're about to execute so we run our tasks in the order in which they + // were pushed. + std::reverse(worklist_.begin() + scope_index, worklist_.end()); + + // Pop the `EnterDeferredDefinitionScope` that's now on the end of the + // worklist. We stay in that scope rather than suspending then immediately + // resuming it. + CARBON_CHECK( + holds_alternative(worklist_.back())) + << "Unexpected task in worklist."; + worklist_.pop_back(); + return true; +} + +namespace { +// A traversal of the node IDs in the parse tree, in the order in which we need +// to check them. +class NodeIdTraversal { + public: + explicit NodeIdTraversal(Context& context) + : context_(context), next_deferred_definition_(&context.parse_tree()) { + chunks_.push_back( + {.it = context.parse_tree().postorder().begin(), + .end = context.parse_tree().postorder().end(), + .next_definition = Parse::DeferredDefinitionIndex::Invalid}); + } + + // Finds the next `NodeId` to type-check. Returns nullopt if the traversal is + // complete. + auto Next() -> std::optional; + + // Performs any processing necessary after we type-check a node. + auto Handle(Parse::NodeKind parse_kind) -> void { + // When we reach the start of a deferred definition scope, add a task to the + // worklist to check future skipped definitions in the new context. + if (IsStartOfDeferredDefinitionScope(parse_kind)) { + worklist_.PushEnterDeferredDefinitionScope(context_); + } + + // When we reach the end of a deferred definition scope, add a task to the + // worklist to leave the scope. If this is not a nested scope, start + // checking the deferred definitions now. + if (IsEndOfDeferredDefinitionScope(parse_kind)) { + chunks_.back().checking_deferred_definitions = + worklist_.SuspendFinishedScopeAndPush(context_); + } + } + + private: + // A chunk of the parse tree that we need to type-check. + struct Chunk { + Parse::Tree::PostorderIterator it; + Parse::Tree::PostorderIterator end; + // The next definition that will be encountered after this chunk completes. + Parse::DeferredDefinitionIndex next_definition; + // Whether we are currently checking deferred definitions, rather than the + // tokens of this chunk. If so, we'll pull tasks off `worklist` and execute + // them until we're done with this batch of deferred definitions. Otherwise, + // we'll pull node IDs from `*it` until it reaches `end`. + bool checking_deferred_definitions = false; + }; + + // Re-enter a nested deferred definition scope. + auto PerformTask( + DeferredDefinitionWorklist::EnterDeferredDefinitionScope&& enter) + -> void { + CARBON_CHECK(enter.suspended_name) + << "Entering a scope with no suspension information."; + context_.decl_name_stack().Restore(std::move(*enter.suspended_name)); + } + + // Leave a nested or top-level deferred definition scope. + auto PerformTask( + DeferredDefinitionWorklist::LeaveDeferredDefinitionScope&& leave) + -> void { + if (!leave.in_deferred_definition_scope) { + // We're done with checking deferred definitions. + chunks_.back().checking_deferred_definitions = false; + } + context_.decl_name_stack().PopScope(); + } + + // Resume checking a deferred definition. + auto PerformTask( + DeferredDefinitionWorklist::CheckSkippedDefinition&& parse_definition) + -> void { + auto& [definition_index, suspended_fn] = parse_definition; + const auto& definition_info = + context_.parse_tree().deferred_definitions().Get(definition_index); + HandleFunctionDefinitionResume(context_, definition_info.start_id, + std::move(suspended_fn)); + chunks_.push_back( + {.it = context_.parse_tree().postorder(definition_info.start_id).end(), + .end = context_.parse_tree() + .postorder(definition_info.definition_id) + .end(), + .next_definition = next_deferred_definition_.index()}); + ++definition_index.index; + next_deferred_definition_.SkipTo(definition_index); + } + + Context& context_; + NextDeferredDefinitionCache next_deferred_definition_; + DeferredDefinitionWorklist worklist_; + llvm::SmallVector chunks_; +}; +} // namespace + +auto NodeIdTraversal::Next() -> std::optional { + while (true) { + // If we're checking deferred definitions, find the next definition we + // should check, restore its suspended state, and add a corresponding + // `Chunk` to the top of the chunk list. + if (chunks_.back().checking_deferred_definitions) { + std::visit( + [&](auto&& task) { PerformTask(std::forward(task)); }, + worklist_.Pop()); + continue; + } + + // If we're not checking deferred definitions, produce the next parse node + // for this chunk. If we've run out of parse nodes, we're done with this + // chunk of the parse tree. + if (chunks_.back().it == chunks_.back().end) { + auto old_chunk = chunks_.pop_back_val(); + + // If we're out of chunks, then we're done entirely. + if (chunks_.empty()) { + worklist_.VerifyEmpty(); + return std::nullopt; + } + + next_deferred_definition_.SkipTo(old_chunk.next_definition); + continue; + } + + auto node_id = *chunks_.back().it; + + // If we've reached the start of a deferred definition, skip to the end of + // it, and track that we need to check it later. + if (node_id == next_deferred_definition_.start_id()) { + const auto& definition_info = + context_.parse_tree().deferred_definitions().Get( + next_deferred_definition_.index()); + worklist_.SuspendFunctionAndPush(context_, + next_deferred_definition_.index(), + definition_info.start_id); + + // Continue type-checking the parse tree after the end of the definition. + chunks_.back().it = + context_.parse_tree().postorder(definition_info.definition_id).end(); + next_deferred_definition_.SkipTo(definition_info.next_definition_index); + continue; + } + + ++chunks_.back().it; + return node_id; + } +} + // Loops over all nodes in the tree. On some errors, this may return early, // for example if an unrecoverable state is encountered. // NOLINTNEXTLINE(readability-function-size) static auto ProcessNodeIds(Context& context, ErrorTrackingDiagnosticConsumer& err_tracker) -> bool { - for (auto node_id : context.parse_tree().postorder()) { - switch (auto parse_kind = context.parse_tree().node_kind(node_id)) { + NodeIdTraversal traversal(context); + + while (auto maybe_node_id = traversal.Next()) { + auto node_id = *maybe_node_id; + auto parse_kind = context.parse_tree().node_kind(node_id); + + switch (parse_kind) { #define CARBON_PARSE_NODE_KIND(Name) \ case Parse::NodeKind::Name: { \ if (!Check::Handle##Name(context, Parse::Name##Id(node_id))) { \ @@ -293,6 +679,8 @@ static auto ProcessNodeIds(Context& context, } #include "toolchain/parse/node_kind.def" } + + traversal.Handle(parse_kind); } return true; } diff --git a/toolchain/check/decl_name_stack.cpp b/toolchain/check/decl_name_stack.cpp index 5106648fca24..bbc9971fda8c 100644 --- a/toolchain/check/decl_name_stack.cpp +++ b/toolchain/check/decl_name_stack.cpp @@ -63,6 +63,36 @@ auto DeclNameStack::PopScope() -> void { decl_name_stack_.pop_back(); } +auto DeclNameStack::Suspend() -> SuspendedName { + CARBON_CHECK(decl_name_stack_.back().state == NameContext::State::Finished) + << "Missing call to FinishName before Suspend"; + SuspendedName result = {decl_name_stack_.pop_back_val(), {}}; + auto enclosing_index = result.name_context.enclosing_scope; + auto& scope_stack = context_->scope_stack(); + while (scope_stack.PeekIndex() > enclosing_index) { + result.scopes.push_back(scope_stack.Suspend()); + } + CARBON_CHECK(scope_stack.PeekIndex() == enclosing_index) + << "Scope index " << enclosing_index + << " does not enclose the current scope " << scope_stack.PeekIndex(); + return result; +} + +auto DeclNameStack::Restore(SuspendedName sus) -> void { + // The enclosing state must be the same when a name is restored. + CARBON_CHECK(context_->scope_stack().PeekIndex() == + sus.name_context.enclosing_scope) + << "Name restored at the wrong position in the name stack."; + + // clang-tidy warns that the `std::move` below has no effect. While that's + // true, this `move` defends against `NameContext` growing more state later. + // NOLINTNEXTLINE(performance-move-const-arg) + decl_name_stack_.push_back(std::move(sus.name_context)); + for (auto& suspended_scope : llvm::reverse(sus.scopes)) { + context_->scope_stack().Restore(std::move(suspended_scope)); + } +} + auto DeclNameStack::LookupOrAddName(NameContext name_context, SemIR::InstId target_id) -> SemIR::InstId { switch (name_context.state) { diff --git a/toolchain/check/decl_name_stack.h b/toolchain/check/decl_name_stack.h index 5f0100013243..bbeac62072d3 100644 --- a/toolchain/check/decl_name_stack.h +++ b/toolchain/check/decl_name_stack.h @@ -7,6 +7,7 @@ #include "llvm/ADT/SmallVector.h" #include "toolchain/check/scope_index.h" +#include "toolchain/check/scope_stack.h" #include "toolchain/sem_ir/ids.h" namespace Carbon::Check { @@ -136,6 +137,21 @@ class DeclNameStack { }; }; + // Information about a declaration name that has been temporarily removed from + // the stack and will later be restored. Names can only be suspended once they + // are finished. + struct SuspendedName { + // The declaration name information. + NameContext name_context; + + // Suspended scopes. We only preallocate space for two of these, because + // suspended names are usually used for classes and functions with + // unqualified names, which only need at most two scopes -- one scope for + // the parameter and one scope for the entity itself, and we can store quite + // a few of these when processing a large class definition. + llvm::SmallVector scopes; + }; + explicit DeclNameStack(Context* context) : context_(context) {} // Pushes processing of a new declaration name, which will be used @@ -172,6 +188,13 @@ class DeclNameStack { // This should be called at the end of the declaration. auto PopScope() -> void; + // Temporarily remove the current declaration name and its associated scopes + // from the stack. Can only be called once the name is finished. + auto Suspend() -> SuspendedName; + + // Restore a previously suspended name. + auto Restore(SuspendedName sus) -> void; + // Creates and returns a name context corresponding to declaring an // unqualified name in the current context. This is suitable for adding to // name lookup in situations where a qualified name is not permitted, such as diff --git a/toolchain/check/function.h b/toolchain/check/function.h index d14e6bbd900d..e7285cd5176f 100644 --- a/toolchain/check/function.h +++ b/toolchain/check/function.h @@ -6,12 +6,26 @@ #define CARBON_TOOLCHAIN_CHECK_FUNCTION_H_ #include "toolchain/check/context.h" +#include "toolchain/check/decl_name_stack.h" #include "toolchain/check/subst.h" #include "toolchain/sem_ir/function.h" #include "toolchain/sem_ir/ids.h" namespace Carbon::Check { +// State saved for a function definition that has been suspended after +// processing its declaration and before processing its body. This is used for +// inline method handling. +struct SuspendedFunction { + // The function that was declared. + SemIR::FunctionId function_id; + // The instruction ID of the FunctionDecl instruction. + SemIR::InstId decl_id; + // The declaration name information of the function. This includes the scope + // information, such as parameter names. + DeclNameStack::SuspendedName saved_name_state; +}; + // Checks that `new_function_id` has the same parameter types and return type as // `prev_function_id`, applying the specified set of substitutions to the // previous function. Prints a suitable diagnostic and returns false if not. diff --git a/toolchain/check/handle.h b/toolchain/check/handle.h new file mode 100644 index 000000000000..b975b4283472 --- /dev/null +++ b/toolchain/check/handle.h @@ -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 + +#ifndef CARBON_TOOLCHAIN_CHECK_HANDLE_H_ +#define CARBON_TOOLCHAIN_CHECK_HANDLE_H_ + +#include "toolchain/check/context.h" +#include "toolchain/check/function.h" +#include "toolchain/parse/node_ids.h" + +namespace Carbon::Check { + +// Parse node handlers. Returns false for unrecoverable errors. +#define CARBON_PARSE_NODE_KIND(Name) \ + auto Handle##Name(Context& context, Parse::Name##Id node_id) -> bool; +#include "toolchain/parse/node_kind.def" + +// Handle suspending the definition of a function. This is used for inline +// methods, which are processed out of the normal lexical order. This plus +// HandleFunctionDefinitionResume carry out the same actions as +// HandleFunctionDefinitionStart, except that the various context stacks are +// cleared out in between. +auto HandleFunctionDefinitionSuspend(Context& context, + Parse::FunctionDefinitionStartId node_id) + -> SuspendedFunction; + +// Handle resuming the definition of a function, after a previous suspension. +auto HandleFunctionDefinitionResume(Context& context, + Parse::FunctionDefinitionStartId node_id, + SuspendedFunction suspended_fn) -> void; + +} // namespace Carbon::Check + +#endif // CARBON_TOOLCHAIN_CHECK_HANDLE_H_ diff --git a/toolchain/check/handle_class.cpp b/toolchain/check/handle_class.cpp index 78255f38f6f2..47fba7a4e2e4 100644 --- a/toolchain/check/handle_class.cpp +++ b/toolchain/check/handle_class.cpp @@ -332,12 +332,12 @@ auto HandleClassDefinition(Context& context, auto class_id = context.node_stack().Pop(); context.inst_block_stack().Pop(); - context.scope_stack().Pop(); - context.decl_name_stack().PopScope(); // The class type is now fully defined. auto& class_info = context.classes().Get(class_id); class_info.object_repr_id = context.GetStructType(fields_id); + + // The decl_name_stack and scopes are popped by `ProcessNodeIds`. return true; } diff --git a/toolchain/check/handle_function.cpp b/toolchain/check/handle_function.cpp index 399202750e99..95ff38606c2d 100644 --- a/toolchain/check/handle_function.cpp +++ b/toolchain/check/handle_function.cpp @@ -225,12 +225,12 @@ auto HandleFunctionDecl(Context& context, Parse::FunctionDeclId node_id) return true; } -auto HandleFunctionDefinitionStart(Context& context, - Parse::FunctionDefinitionStartId node_id) - -> bool { - // Process the declaration portion of the function. - auto [function_id, decl_id] = - BuildFunctionDecl(context, node_id, /*is_definition=*/true); +// Processes a function definition after a signature for which we have already +// built a function ID. This logic is shared between processing regular function +// definitions and delayed parsing of inline method definitions. +static auto HandleFunctionDefinitionAfterSignature( + Context& context, Parse::FunctionDefinitionStartId node_id, + SemIR::FunctionId function_id, SemIR::InstId decl_id) -> void { auto& function = context.functions().Get(function_id); // Create the function scope and the entry block. @@ -239,7 +239,7 @@ auto HandleFunctionDefinitionStart(Context& context, context.scope_stack().Push(decl_id); context.AddCurrentCodeBlockToFunction(); - // Bring the implicit and explicit parameters into scope. + // Check the parameter types are complete. for (auto param_id : llvm::concat( context.inst_blocks().Get(function.implicit_param_refs_id), context.inst_blocks().Get(function.param_refs_id))) { @@ -264,6 +264,33 @@ auto HandleFunctionDefinitionStart(Context& context, } context.node_stack().Push(node_id, function_id); +} + +auto HandleFunctionDefinitionSuspend(Context& context, + Parse::FunctionDefinitionStartId node_id) + -> SuspendedFunction { + // Process the declaration portion of the function. + auto [function_id, decl_id] = + BuildFunctionDecl(context, node_id, /*is_definition=*/true); + return {function_id, decl_id, context.decl_name_stack().Suspend()}; +} + +auto HandleFunctionDefinitionResume(Context& context, + Parse::FunctionDefinitionStartId node_id, + SuspendedFunction sus_fn) -> void { + context.decl_name_stack().Restore(sus_fn.saved_name_state); + HandleFunctionDefinitionAfterSignature(context, node_id, sus_fn.function_id, + sus_fn.decl_id); +} + +auto HandleFunctionDefinitionStart(Context& context, + Parse::FunctionDefinitionStartId node_id) + -> bool { + // Process the declaration portion of the function. + auto [function_id, decl_id] = + BuildFunctionDecl(context, node_id, /*is_definition=*/true); + HandleFunctionDefinitionAfterSignature(context, node_id, function_id, + decl_id); return true; } diff --git a/toolchain/check/handle_impl.cpp b/toolchain/check/handle_impl.cpp index 80456092cb3f..69a87c0673aa 100644 --- a/toolchain/check/handle_impl.cpp +++ b/toolchain/check/handle_impl.cpp @@ -283,7 +283,7 @@ auto HandleImplDefinition(Context& context, Parse::ImplDefinitionId /*node_id*/) } context.inst_block_stack().Pop(); - context.decl_name_stack().PopScope(); + // The decl_name_stack and scopes are popped by `ProcessNodeIds`. return true; } diff --git a/toolchain/check/handle_interface.cpp b/toolchain/check/handle_interface.cpp index 61847fe3a914..bc3c5248764e 100644 --- a/toolchain/check/handle_interface.cpp +++ b/toolchain/check/handle_interface.cpp @@ -173,8 +173,6 @@ auto HandleInterfaceDefinition(Context& context, auto interface_id = context.node_stack().Pop(); context.inst_block_stack().Pop(); - context.scope_stack().Pop(); - context.decl_name_stack().PopScope(); auto associated_entities_id = context.args_type_info_stack().Pop(); // The interface type is now fully defined. @@ -182,6 +180,7 @@ auto HandleInterfaceDefinition(Context& context, if (!interface_info.associated_entities_id.is_valid()) { interface_info.associated_entities_id = associated_entities_id; } + // The decl_name_stack and scopes are popped by `ProcessNodeIds`. return true; } diff --git a/toolchain/check/handle_named_constraint.cpp b/toolchain/check/handle_named_constraint.cpp index 9d40d678f9e9..de2a6e683aa4 100644 --- a/toolchain/check/handle_named_constraint.cpp +++ b/toolchain/check/handle_named_constraint.cpp @@ -14,6 +14,7 @@ auto HandleNamedConstraintDecl(Context& context, auto HandleNamedConstraintDefinition(Context& context, Parse::NamedConstraintDefinitionId node_id) -> bool { + // Note that the decl_name_stack will be popped by `ProcessNodeIds`. return context.TODO(node_id, "HandleNamedConstraintDefinition"); } diff --git a/toolchain/check/lexical_lookup.h b/toolchain/check/lexical_lookup.h index fad32eda1f0b..d76ee0516f27 100644 --- a/toolchain/check/lexical_lookup.h +++ b/toolchain/check/lexical_lookup.h @@ -28,12 +28,21 @@ class LexicalLookup { ScopeIndex scope_index; }; + // A lookup result that has been temporarily removed from scope. + struct SuspendedResult { + // The lookup index. This is notionally a size_t, but is stored in 32 bits + // to keep this type small, which helps to keep SuspendedFunctions small. + uint32_t index; + // The lookup result. + SemIR::InstId inst_id; + }; + explicit LexicalLookup(const StringStoreWrapper& identifiers) : lookup_(identifiers.size() + SemIR::NameId::NonIndexValueCount) {} // Returns the lexical lookup results for a name. auto Get(SemIR::NameId name_id) -> llvm::SmallVector& { - size_t index = name_id.index + SemIR::NameId::NonIndexValueCount; + auto index = GetLookupIndex(name_id); CARBON_CHECK(index < lookup_.size()) << "An identifier was added after the Context was initialized. " "Currently, we expect that new identifiers will never be used with " @@ -44,7 +53,29 @@ class LexicalLookup { return lookup_[index]; } + // Temporarily remove the top lookup result for `name_id` from scope. + auto Suspend(SemIR::NameId name_id) -> SuspendedResult { + auto index = GetLookupIndex(name_id); + auto& results = lookup_[index]; + CARBON_CHECK(!results.empty()) + << "Suspending a nonexistent result for " << name_id << "."; + CARBON_CHECK(index <= std::numeric_limits::max()) + << "Unexpectedly large index " << index << " for name ID"; + return {static_cast(index), results.pop_back_val().inst_id}; + } + + // Restore a previously-suspended lookup result. + auto Restore(SuspendedResult sus, ScopeIndex index) -> void { + lookup_[sus.index].push_back({sus.inst_id, index}); + } + private: + // Get the index at which the specified name is stored in `lookup_`. + auto GetLookupIndex(SemIR::NameId name_id) -> size_t { + return static_cast(name_id.index) + + SemIR::NameId::NonIndexValueCount; + } + // Maps identifiers to name lookup results. // TODO: Consider TinyPtrVector or similar. For now, use a small size // of 2 to cover the common case. diff --git a/toolchain/check/scope_stack.cpp b/toolchain/check/scope_stack.cpp index 619a470f1831..97f403d2e336 100644 --- a/toolchain/check/scope_stack.cpp +++ b/toolchain/check/scope_stack.cpp @@ -19,13 +19,12 @@ auto ScopeStack::Push(SemIR::InstId scope_inst_id, SemIR::NameScopeId scope_id, {.index = next_scope_index_, .scope_inst_id = scope_inst_id, .scope_id = scope_id, - .prev_lexical_lookup_has_load_error = lexical_lookup_has_load_error_}); + .lexical_lookup_has_load_error = + LexicalLookupHasLoadError() || lexical_lookup_has_load_error}); if (scope_id.is_valid()) { non_lexical_scope_stack_.push_back({next_scope_index_, scope_id}); } - lexical_lookup_has_load_error_ |= lexical_lookup_has_load_error; - // TODO: Handle this case more gracefully. CARBON_CHECK(next_scope_index_.index != std::numeric_limits::max()) << "Ran out of scopes"; @@ -35,8 +34,6 @@ auto ScopeStack::Push(SemIR::InstId scope_inst_id, SemIR::NameScopeId scope_id, auto ScopeStack::Pop() -> void { auto scope = scope_stack_.pop_back_val(); - lexical_lookup_has_load_error_ = scope.prev_lexical_lookup_has_load_error; - for (const auto& str_id : scope.names) { auto& lexical_results = lexical_lookup_.Get(str_id); CARBON_CHECK(lexical_results.back().scope_index == scope.index) @@ -88,8 +85,8 @@ auto ScopeStack::LookupInEnclosingScopes(SemIR::NameId name_id) // If we have no lexical results, check all non-lexical scopes. if (lexical_results.empty()) { - return {lexical_lookup_has_load_error_ ? SemIR::InstId::BuiltinError - : SemIR::InstId::Invalid, + return {LexicalLookupHasLoadError() ? SemIR::InstId::BuiltinError + : SemIR::InstId::Invalid, non_lexical_scope_stack_}; } @@ -142,4 +139,34 @@ auto ScopeStack::SetReturnedVarOrGetExisting(SemIR::InstId inst_id) return SemIR::InstId::Invalid; } +auto ScopeStack::Suspend() -> SuspendedScope { + CARBON_CHECK(!scope_stack_.empty()) << "No scope to suspend"; + SuspendedScope result = {scope_stack_.pop_back_val(), {}}; + if (result.entry.scope_id.is_valid()) { + non_lexical_scope_stack_.pop_back(); + } + for (auto name_id : result.entry.names) { + result.suspended_lookups.push_back(lexical_lookup_.Suspend(name_id)); + } + // This would be easy to support if we had a need, but currently we do not. + CARBON_CHECK(!result.entry.has_returned_var) + << "Should not suspend a scope with a returned var."; + return result; +} + +auto ScopeStack::Restore(SuspendedScope scope) -> void { + for (auto entry : scope.suspended_lookups) { + // clang-tidy warns that the `std::move` below has no effect. While that's + // true, this `move` defends against the suspended lookup growing more state + // later. + // NOLINTNEXTLINE(performance-move-const-arg) + lexical_lookup_.Restore(std::move(entry), scope.entry.index); + } + if (scope.entry.scope_id.is_valid()) { + non_lexical_scope_stack_.push_back( + {scope.entry.index, scope.entry.scope_id}); + } + scope_stack_.push_back(std::move(scope.entry)); +} + } // namespace Carbon::Check diff --git a/toolchain/check/scope_stack.h b/toolchain/check/scope_stack.h index f4a086ec6ef1..db6dfd652fda 100644 --- a/toolchain/check/scope_stack.h +++ b/toolchain/check/scope_stack.h @@ -47,6 +47,9 @@ class ScopeStack { SemIR::NameScopeId name_scope_id; }; + // Information about a scope that has been temporarily removed from the stack. + struct SuspendedScope; + // Pushes a scope onto scope_stack_. NameScopeId::Invalid is used for new // scopes. lexical_lookup_has_load_error is used to limit diagnostics when a // given namespace may contain a mix of both successful and failed name @@ -112,6 +115,12 @@ class ScopeStack { auto LookupOrAddName(SemIR::NameId name_id, SemIR::InstId target_id) -> SemIR::InstId; + // Temporarily removes the top of the stack and its lexical lookup results. + auto Suspend() -> SuspendedScope; + + // Restores a suspended scope stack entry. + auto Restore(SuspendedScope scope) -> void; + // Runs verification that the processing cleanly finished. auto VerifyOnFinish() -> void; @@ -140,22 +149,29 @@ class ScopeStack { // The name scope associated with this entry, if any. SemIR::NameScopeId scope_id; - // The previous state of lexical_lookup_has_load_error_, restored on pop. - bool prev_lexical_lookup_has_load_error; - - // Names which are registered with lexical_lookup_, and will need to be - // unregistered when the scope ends. - llvm::DenseSet names; + // Whether lexical_lookup_ has load errors from this scope or an enclosing + // scope. + bool lexical_lookup_has_load_error; // Whether a `returned var` was introduced in this scope, and needs to be // unregistered when the scope ends. bool has_returned_var = false; + // Names which are registered with lexical_lookup_, and will need to be + // unregistered when the scope ends. + llvm::DenseSet names; + // TODO: This likely needs to track things which need to be destructed. }; auto Peek() const -> const ScopeStackEntry& { return scope_stack_.back(); } + // Returns whether lexical lookup currently has any load errors. + auto LexicalLookupHasLoadError() const -> bool { + return !scope_stack_.empty() && + scope_stack_.back().lexical_lookup_has_load_error; + } + // A stack of scopes from which we can `return`. llvm::SmallVector return_scope_stack_; @@ -175,10 +191,15 @@ class ScopeStack { // Tracks lexical lookup results. LexicalLookup lexical_lookup_; +}; - // Whether lexical_lookup_ has load errors, updated whenever scope_stack_ is - // pushed or popped. - bool lexical_lookup_has_load_error_ = false; +struct ScopeStack::SuspendedScope { + // The suspended scope stack entry. + ScopeStackEntry entry; + // The lexical lookups for the suspended entry. The inline size is an attempt + // to keep the size of a `SuspendedFunction` reasonable while avoiding heap + // allocations most of the time. + llvm::SmallVector suspended_lookups; }; } // namespace Carbon::Check diff --git a/toolchain/check/testdata/class/complete_in_member_fn.carbon b/toolchain/check/testdata/class/complete_in_member_fn.carbon new file mode 100644 index 000000000000..32604df963dc --- /dev/null +++ b/toolchain/check/testdata/class/complete_in_member_fn.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 + +class C { + fn F(c: C) -> i32 { return c.a; } + + var a: i32; +} + +// CHECK:STDOUT: --- complete_in_member_fn.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %C: type = class_type @C [template] +// CHECK:STDOUT: %.1: type = unbound_element_type C, i32 [template] +// CHECK:STDOUT: %.2: type = struct_type {.a: i32} [template] +// CHECK:STDOUT: %.3: type = ptr_type {.a: i32} [template] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file { +// CHECK:STDOUT: package: = namespace [template] { +// CHECK:STDOUT: .C = %C.decl +// CHECK:STDOUT: } +// CHECK:STDOUT: %C.decl: type = class_decl @C [template = constants.%C] {} +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @C { +// CHECK:STDOUT: %F: = fn_decl @F [template] { +// CHECK:STDOUT: %C.ref: type = name_ref C, file.%C.decl [template = constants.%C] +// CHECK:STDOUT: %c.loc8_8.1: C = param c +// CHECK:STDOUT: %c.loc8_8.2: C = bind_name c, %c.loc8_8.1 +// CHECK:STDOUT: %return.var: ref i32 = var +// CHECK:STDOUT: } +// CHECK:STDOUT: %.loc10: = field_decl a, element0 [template] +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .Self = constants.%C +// CHECK:STDOUT: .F = %F +// CHECK:STDOUT: .a = %.loc10 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @F(@C.%c.loc8_8.2: C) -> i32 { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %c.ref: C = name_ref c, @C.%c.loc8_8.2 +// CHECK:STDOUT: %a.ref: = name_ref a, @C.%.loc10 [template = @C.%.loc10] +// CHECK:STDOUT: %.loc8_31.1: ref i32 = class_element_access %c.ref, element0 +// CHECK:STDOUT: %.loc8_31.2: i32 = bind_value %.loc8_31.1 +// CHECK:STDOUT: return %.loc8_31.2 +// CHECK:STDOUT: } +// CHECK:STDOUT: diff --git a/toolchain/check/testdata/class/fail_scope.carbon b/toolchain/check/testdata/class/fail_scope.carbon index 2122a93ba92c..3a4f5e440547 100644 --- a/toolchain/check/testdata/class/fail_scope.carbon +++ b/toolchain/check/testdata/class/fail_scope.carbon @@ -21,8 +21,8 @@ fn G() -> i32 { // CHECK:STDOUT: // CHECK:STDOUT: constants { // CHECK:STDOUT: %Class: type = class_type @Class [template] -// CHECK:STDOUT: %.1: i32 = int_literal 1 [template] -// CHECK:STDOUT: %.2: type = struct_type {} [template] +// CHECK:STDOUT: %.1: type = struct_type {} [template] +// CHECK:STDOUT: %.2: i32 = int_literal 1 [template] // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: file { @@ -48,7 +48,7 @@ fn G() -> i32 { // CHECK:STDOUT: // CHECK:STDOUT: fn @F() -> i32 { // CHECK:STDOUT: !entry: -// CHECK:STDOUT: %.loc9: i32 = int_literal 1 [template = constants.%.1] +// CHECK:STDOUT: %.loc9: i32 = int_literal 1 [template = constants.%.2] // CHECK:STDOUT: return %.loc9 // CHECK:STDOUT: } // CHECK:STDOUT: diff --git a/toolchain/check/testdata/class/fail_todo_local_class.carbon b/toolchain/check/testdata/class/fail_todo_local_class.carbon new file mode 100644 index 000000000000..463b43d2b88f --- /dev/null +++ b/toolchain/check/testdata/class/fail_todo_local_class.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 + +// TODO: Support parsing classes at function scope. +class A { + fn F() { + // CHECK:STDERR: fail_todo_local_class.carbon:[[@LINE+7]]:5: ERROR: Expected expression. + // CHECK:STDERR: class B { + // CHECK:STDERR: ^~~~~ + // CHECK:STDERR: + // CHECK:STDERR: fail_todo_local_class.carbon:[[@LINE+3]]:5: ERROR: Semantics TODO: `HandleInvalidParse`. + // CHECK:STDERR: class B { + // CHECK:STDERR: ^~~~~ + class B { + fn G() { + var b: B = {}; + } + } + } +} + +// CHECK:STDOUT: --- fail_todo_local_class.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %A: type = class_type @A [template] +// CHECK:STDOUT: %.1: type = struct_type {} [template] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file {} +// CHECK:STDOUT: +// CHECK:STDOUT: class @A { +// CHECK:STDOUT: %F: = fn_decl @F [template] {} +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .Self = constants.%A +// CHECK:STDOUT: .F = %F +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @F() { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: } +// CHECK:STDOUT: diff --git a/toolchain/check/testdata/class/nested.carbon b/toolchain/check/testdata/class/nested.carbon index b4c8f8b05e95..9be1283cd692 100644 --- a/toolchain/check/testdata/class/nested.carbon +++ b/toolchain/check/testdata/class/nested.carbon @@ -5,10 +5,28 @@ // AUTOUPDATE class Outer { + fn F() { + // Outer and Inner are both complete here. + var o: Outer; + var i: Inner; + } + class Inner { var pi: Self*; var po: Outer*; var qi: Inner*; + + fn G() { + // Outer and Inner are both complete here. + var o: Outer; + var i: Inner; + } + } + + fn H() { + // Outer and Inner are both complete here. + var o: Outer; + var i: Inner; } var po: Self*; @@ -19,12 +37,12 @@ class Outer { fn F(a: Outer*) { let b: Outer.Inner* = (*a).pi; - (*a).po = a; - (*a).qo = a; - (*a).pi = (*a).pi; - (*b).po = a; - (*b).pi = (*a).pi; - (*b).qi = (*a).pi; + a->po = a; + a->qo = a; + a->pi = a->pi; + b->po = a; + b->pi = a->pi; + b->qi = a->pi; } // CHECK:STDOUT: --- nested.carbon @@ -50,111 +68,150 @@ fn F(a: Outer*) { // CHECK:STDOUT: .F = %F // CHECK:STDOUT: } // CHECK:STDOUT: %Outer.decl: type = class_decl @Outer [template = constants.%Outer] {} -// CHECK:STDOUT: %F: = fn_decl @F [template] { +// CHECK:STDOUT: %F: = fn_decl @F.2 [template] { // CHECK:STDOUT: %Outer.ref: type = name_ref Outer, %Outer.decl [template = constants.%Outer] -// CHECK:STDOUT: %.loc19: type = ptr_type Outer [template = constants.%.3] -// CHECK:STDOUT: %a.loc19_6.1: Outer* = param a -// CHECK:STDOUT: @F.%a: Outer* = bind_name a, %a.loc19_6.1 +// CHECK:STDOUT: %.loc37: type = ptr_type Outer [template = constants.%.3] +// CHECK:STDOUT: %a.loc37_6.1: Outer* = param a +// CHECK:STDOUT: @F.2.%a: Outer* = bind_name a, %a.loc37_6.1 // CHECK:STDOUT: } // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: class @Outer { +// CHECK:STDOUT: %F: = fn_decl @F.1 [template] {} // CHECK:STDOUT: %Inner.decl: type = class_decl @Inner [template = constants.%Inner] {} +// CHECK:STDOUT: %H: = fn_decl @H [template] {} // CHECK:STDOUT: %Self.ref: type = name_ref Self, constants.%Outer [template = constants.%Outer] -// CHECK:STDOUT: %.loc14_15: type = ptr_type Outer [template = constants.%.3] -// CHECK:STDOUT: %.loc14_9: = field_decl po, element0 [template] +// CHECK:STDOUT: %.loc32_15: type = ptr_type Outer [template = constants.%.3] +// CHECK:STDOUT: %.loc32_9: = field_decl po, element0 [template] // CHECK:STDOUT: %Outer.ref: type = name_ref Outer, file.%Outer.decl [template = constants.%Outer] -// CHECK:STDOUT: %.loc15_16: type = ptr_type Outer [template = constants.%.3] -// CHECK:STDOUT: %.loc15_9: = field_decl qo, element1 [template] +// CHECK:STDOUT: %.loc33_16: type = ptr_type Outer [template = constants.%.3] +// CHECK:STDOUT: %.loc33_9: = field_decl qo, element1 [template] // CHECK:STDOUT: %Inner.ref: type = name_ref Inner, %Inner.decl [template = constants.%Inner] -// CHECK:STDOUT: %.loc16_16: type = ptr_type Inner [template = constants.%.1] -// CHECK:STDOUT: %.loc16_9: = field_decl pi, element2 [template] +// CHECK:STDOUT: %.loc34_16: type = ptr_type Inner [template = constants.%.1] +// CHECK:STDOUT: %.loc34_9: = field_decl pi, element2 [template] // CHECK:STDOUT: // CHECK:STDOUT: !members: // CHECK:STDOUT: .Self = constants.%Outer +// CHECK:STDOUT: .F = %F // CHECK:STDOUT: .Inner = %Inner.decl -// CHECK:STDOUT: .po = %.loc14_9 -// CHECK:STDOUT: .qo = %.loc15_9 -// CHECK:STDOUT: .pi = %.loc16_9 +// CHECK:STDOUT: .H = %H +// CHECK:STDOUT: .po = %.loc32_9 +// CHECK:STDOUT: .qo = %.loc33_9 +// CHECK:STDOUT: .pi = %.loc34_9 // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: class @Inner { // CHECK:STDOUT: %Self.ref: type = name_ref Self, constants.%Inner [template = constants.%Inner] -// CHECK:STDOUT: %.loc9_17: type = ptr_type Inner [template = constants.%.1] -// CHECK:STDOUT: %.loc9_11: = field_decl pi, element0 [template] +// CHECK:STDOUT: %.loc15_17: type = ptr_type Inner [template = constants.%.1] +// CHECK:STDOUT: %.loc15_11: = field_decl pi, element0 [template] // CHECK:STDOUT: %Outer.ref: type = name_ref Outer, file.%Outer.decl [template = constants.%Outer] -// CHECK:STDOUT: %.loc10_18: type = ptr_type Outer [template = constants.%.3] -// CHECK:STDOUT: %.loc10_11: = field_decl po, element1 [template] +// CHECK:STDOUT: %.loc16_18: type = ptr_type Outer [template = constants.%.3] +// CHECK:STDOUT: %.loc16_11: = field_decl po, element1 [template] // CHECK:STDOUT: %Inner.ref: type = name_ref Inner, @Outer.%Inner.decl [template = constants.%Inner] -// CHECK:STDOUT: %.loc11_18: type = ptr_type Inner [template = constants.%.1] -// CHECK:STDOUT: %.loc11_11: = field_decl qi, element2 [template] +// CHECK:STDOUT: %.loc17_18: type = ptr_type Inner [template = constants.%.1] +// CHECK:STDOUT: %.loc17_11: = field_decl qi, element2 [template] +// CHECK:STDOUT: %G: = fn_decl @G [template] {} // CHECK:STDOUT: // CHECK:STDOUT: !members: // CHECK:STDOUT: .Self = constants.%Inner -// CHECK:STDOUT: .pi = %.loc9_11 -// CHECK:STDOUT: .po = %.loc10_11 -// CHECK:STDOUT: .qi = %.loc11_11 +// CHECK:STDOUT: .pi = %.loc15_11 +// CHECK:STDOUT: .po = %.loc16_11 +// CHECK:STDOUT: .qi = %.loc17_11 +// CHECK:STDOUT: .G = %G // CHECK:STDOUT: } // CHECK:STDOUT: -// CHECK:STDOUT: fn @F(%a: Outer*) { +// CHECK:STDOUT: fn @F.1() { // CHECK:STDOUT: !entry: // CHECK:STDOUT: %Outer.ref: type = name_ref Outer, file.%Outer.decl [template = constants.%Outer] +// CHECK:STDOUT: %o.var: ref Outer = var o +// CHECK:STDOUT: %o: ref Outer = bind_name o, %o.var // CHECK:STDOUT: %Inner.ref: type = name_ref Inner, @Outer.%Inner.decl [template = constants.%Inner] -// CHECK:STDOUT: %.loc20_21: type = ptr_type Inner [template = constants.%.1] -// CHECK:STDOUT: %a.ref.loc20: Outer* = name_ref a, %a -// CHECK:STDOUT: %.loc20_26: ref Outer = deref %a.ref.loc20 -// CHECK:STDOUT: %pi.ref.loc20: = name_ref pi, @Outer.%.loc16_9 [template = @Outer.%.loc16_9] -// CHECK:STDOUT: %.loc20_29.1: ref Inner* = class_element_access %.loc20_26, element2 -// CHECK:STDOUT: %.loc20_29.2: Inner* = bind_value %.loc20_29.1 -// CHECK:STDOUT: %b: Inner* = bind_name b, %.loc20_29.2 -// CHECK:STDOUT: %a.ref.loc22_5: Outer* = name_ref a, %a -// CHECK:STDOUT: %.loc22_4: ref Outer = deref %a.ref.loc22_5 -// CHECK:STDOUT: %po.ref.loc22: = name_ref po, @Outer.%.loc14_9 [template = @Outer.%.loc14_9] -// CHECK:STDOUT: %.loc22_7: ref Outer* = class_element_access %.loc22_4, element0 -// CHECK:STDOUT: %a.ref.loc22_13: Outer* = name_ref a, %a -// CHECK:STDOUT: assign %.loc22_7, %a.ref.loc22_13 -// CHECK:STDOUT: %a.ref.loc23_5: Outer* = name_ref a, %a -// CHECK:STDOUT: %.loc23_4: ref Outer = deref %a.ref.loc23_5 -// CHECK:STDOUT: %qo.ref: = name_ref qo, @Outer.%.loc15_9 [template = @Outer.%.loc15_9] -// CHECK:STDOUT: %.loc23_7: ref Outer* = class_element_access %.loc23_4, element1 -// CHECK:STDOUT: %a.ref.loc23_13: Outer* = name_ref a, %a -// CHECK:STDOUT: assign %.loc23_7, %a.ref.loc23_13 -// CHECK:STDOUT: %a.ref.loc24_5: Outer* = name_ref a, %a -// CHECK:STDOUT: %.loc24_4: ref Outer = deref %a.ref.loc24_5 -// CHECK:STDOUT: %pi.ref.loc24_7: = name_ref pi, @Outer.%.loc16_9 [template = @Outer.%.loc16_9] -// CHECK:STDOUT: %.loc24_7: ref Inner* = class_element_access %.loc24_4, element2 -// CHECK:STDOUT: %a.ref.loc24_15: Outer* = name_ref a, %a -// CHECK:STDOUT: %.loc24_14: ref Outer = deref %a.ref.loc24_15 -// CHECK:STDOUT: %pi.ref.loc24_17: = name_ref pi, @Outer.%.loc16_9 [template = @Outer.%.loc16_9] -// CHECK:STDOUT: %.loc24_17.1: ref Inner* = class_element_access %.loc24_14, element2 -// CHECK:STDOUT: %.loc24_17.2: Inner* = bind_value %.loc24_17.1 -// CHECK:STDOUT: assign %.loc24_7, %.loc24_17.2 -// CHECK:STDOUT: %b.ref.loc25: Inner* = name_ref b, %b -// CHECK:STDOUT: %.loc25_4: ref Inner = deref %b.ref.loc25 -// CHECK:STDOUT: %po.ref.loc25: = name_ref po, @Inner.%.loc10_11 [template = @Inner.%.loc10_11] -// CHECK:STDOUT: %.loc25_7: ref Outer* = class_element_access %.loc25_4, element1 -// CHECK:STDOUT: %a.ref.loc25: Outer* = name_ref a, %a -// CHECK:STDOUT: assign %.loc25_7, %a.ref.loc25 -// CHECK:STDOUT: %b.ref.loc26: Inner* = name_ref b, %b -// CHECK:STDOUT: %.loc26_4: ref Inner = deref %b.ref.loc26 -// CHECK:STDOUT: %pi.ref.loc26_7: = name_ref pi, @Inner.%.loc9_11 [template = @Inner.%.loc9_11] -// CHECK:STDOUT: %.loc26_7: ref Inner* = class_element_access %.loc26_4, element0 -// CHECK:STDOUT: %a.ref.loc26: Outer* = name_ref a, %a -// CHECK:STDOUT: %.loc26_14: ref Outer = deref %a.ref.loc26 -// CHECK:STDOUT: %pi.ref.loc26_17: = name_ref pi, @Outer.%.loc16_9 [template = @Outer.%.loc16_9] -// CHECK:STDOUT: %.loc26_17.1: ref Inner* = class_element_access %.loc26_14, element2 -// CHECK:STDOUT: %.loc26_17.2: Inner* = bind_value %.loc26_17.1 -// CHECK:STDOUT: assign %.loc26_7, %.loc26_17.2 -// CHECK:STDOUT: %b.ref.loc27: Inner* = name_ref b, %b -// CHECK:STDOUT: %.loc27_4: ref Inner = deref %b.ref.loc27 -// CHECK:STDOUT: %qi.ref: = name_ref qi, @Inner.%.loc11_11 [template = @Inner.%.loc11_11] -// CHECK:STDOUT: %.loc27_7: ref Inner* = class_element_access %.loc27_4, element2 -// CHECK:STDOUT: %a.ref.loc27: Outer* = name_ref a, %a -// CHECK:STDOUT: %.loc27_14: ref Outer = deref %a.ref.loc27 -// CHECK:STDOUT: %pi.ref.loc27: = name_ref pi, @Outer.%.loc16_9 [template = @Outer.%.loc16_9] -// CHECK:STDOUT: %.loc27_17.1: ref Inner* = class_element_access %.loc27_14, element2 -// CHECK:STDOUT: %.loc27_17.2: Inner* = bind_value %.loc27_17.1 -// CHECK:STDOUT: assign %.loc27_7, %.loc27_17.2 +// CHECK:STDOUT: %i.var: ref Inner = var i +// CHECK:STDOUT: %i: ref Inner = bind_name i, %i.var +// CHECK:STDOUT: return +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @G() { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %Outer.ref: type = name_ref Outer, file.%Outer.decl [template = constants.%Outer] +// CHECK:STDOUT: %o.var: ref Outer = var o +// CHECK:STDOUT: %o: ref Outer = bind_name o, %o.var +// CHECK:STDOUT: %Inner.ref: type = name_ref Inner, @Outer.%Inner.decl [template = constants.%Inner] +// CHECK:STDOUT: %i.var: ref Inner = var i +// CHECK:STDOUT: %i: ref Inner = bind_name i, %i.var +// CHECK:STDOUT: return +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @H() { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %Outer.ref: type = name_ref Outer, file.%Outer.decl [template = constants.%Outer] +// CHECK:STDOUT: %o.var: ref Outer = var o +// CHECK:STDOUT: %o: ref Outer = bind_name o, %o.var +// CHECK:STDOUT: %Inner.ref: type = name_ref Inner, @Outer.%Inner.decl [template = constants.%Inner] +// CHECK:STDOUT: %i.var: ref Inner = var i +// CHECK:STDOUT: %i: ref Inner = bind_name i, %i.var +// CHECK:STDOUT: return +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @F.2(%a: Outer*) { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %Outer.ref: type = name_ref Outer, file.%Outer.decl [template = constants.%Outer] +// CHECK:STDOUT: %Inner.ref: type = name_ref Inner, @Outer.%Inner.decl [template = constants.%Inner] +// CHECK:STDOUT: %.loc38_21: type = ptr_type Inner [template = constants.%.1] +// CHECK:STDOUT: %a.ref.loc38: Outer* = name_ref a, %a +// CHECK:STDOUT: %.loc38_26: ref Outer = deref %a.ref.loc38 +// CHECK:STDOUT: %pi.ref.loc38: = name_ref pi, @Outer.%.loc34_9 [template = @Outer.%.loc34_9] +// CHECK:STDOUT: %.loc38_29.1: ref Inner* = class_element_access %.loc38_26, element2 +// CHECK:STDOUT: %.loc38_29.2: Inner* = bind_value %.loc38_29.1 +// CHECK:STDOUT: %b: Inner* = bind_name b, %.loc38_29.2 +// CHECK:STDOUT: %a.ref.loc40_3: Outer* = name_ref a, %a +// CHECK:STDOUT: %.loc40_4.1: ref Outer = deref %a.ref.loc40_3 +// CHECK:STDOUT: %po.ref.loc40: = name_ref po, @Outer.%.loc32_9 [template = @Outer.%.loc32_9] +// CHECK:STDOUT: %.loc40_4.2: ref Outer* = class_element_access %.loc40_4.1, element0 +// CHECK:STDOUT: %a.ref.loc40_11: Outer* = name_ref a, %a +// CHECK:STDOUT: assign %.loc40_4.2, %a.ref.loc40_11 +// CHECK:STDOUT: %a.ref.loc41_3: Outer* = name_ref a, %a +// CHECK:STDOUT: %.loc41_4.1: ref Outer = deref %a.ref.loc41_3 +// CHECK:STDOUT: %qo.ref: = name_ref qo, @Outer.%.loc33_9 [template = @Outer.%.loc33_9] +// CHECK:STDOUT: %.loc41_4.2: ref Outer* = class_element_access %.loc41_4.1, element1 +// CHECK:STDOUT: %a.ref.loc41_11: Outer* = name_ref a, %a +// CHECK:STDOUT: assign %.loc41_4.2, %a.ref.loc41_11 +// CHECK:STDOUT: %a.ref.loc42_3: Outer* = name_ref a, %a +// CHECK:STDOUT: %.loc42_4.1: ref Outer = deref %a.ref.loc42_3 +// CHECK:STDOUT: %pi.ref.loc42_4: = name_ref pi, @Outer.%.loc34_9 [template = @Outer.%.loc34_9] +// CHECK:STDOUT: %.loc42_4.2: ref Inner* = class_element_access %.loc42_4.1, element2 +// CHECK:STDOUT: %a.ref.loc42_11: Outer* = name_ref a, %a +// CHECK:STDOUT: %.loc42_12.1: ref Outer = deref %a.ref.loc42_11 +// CHECK:STDOUT: %pi.ref.loc42_12: = name_ref pi, @Outer.%.loc34_9 [template = @Outer.%.loc34_9] +// CHECK:STDOUT: %.loc42_12.2: ref Inner* = class_element_access %.loc42_12.1, element2 +// CHECK:STDOUT: %.loc42_12.3: Inner* = bind_value %.loc42_12.2 +// CHECK:STDOUT: assign %.loc42_4.2, %.loc42_12.3 +// CHECK:STDOUT: %b.ref.loc43: Inner* = name_ref b, %b +// CHECK:STDOUT: %.loc43_4.1: ref Inner = deref %b.ref.loc43 +// CHECK:STDOUT: %po.ref.loc43: = name_ref po, @Inner.%.loc16_11 [template = @Inner.%.loc16_11] +// CHECK:STDOUT: %.loc43_4.2: ref Outer* = class_element_access %.loc43_4.1, element1 +// CHECK:STDOUT: %a.ref.loc43: Outer* = name_ref a, %a +// CHECK:STDOUT: assign %.loc43_4.2, %a.ref.loc43 +// CHECK:STDOUT: %b.ref.loc44: Inner* = name_ref b, %b +// CHECK:STDOUT: %.loc44_4.1: ref Inner = deref %b.ref.loc44 +// CHECK:STDOUT: %pi.ref.loc44_4: = name_ref pi, @Inner.%.loc15_11 [template = @Inner.%.loc15_11] +// CHECK:STDOUT: %.loc44_4.2: ref Inner* = class_element_access %.loc44_4.1, element0 +// CHECK:STDOUT: %a.ref.loc44: Outer* = name_ref a, %a +// CHECK:STDOUT: %.loc44_12.1: ref Outer = deref %a.ref.loc44 +// CHECK:STDOUT: %pi.ref.loc44_12: = name_ref pi, @Outer.%.loc34_9 [template = @Outer.%.loc34_9] +// CHECK:STDOUT: %.loc44_12.2: ref Inner* = class_element_access %.loc44_12.1, element2 +// CHECK:STDOUT: %.loc44_12.3: Inner* = bind_value %.loc44_12.2 +// CHECK:STDOUT: assign %.loc44_4.2, %.loc44_12.3 +// CHECK:STDOUT: %b.ref.loc45: Inner* = name_ref b, %b +// CHECK:STDOUT: %.loc45_4.1: ref Inner = deref %b.ref.loc45 +// CHECK:STDOUT: %qi.ref: = name_ref qi, @Inner.%.loc17_11 [template = @Inner.%.loc17_11] +// CHECK:STDOUT: %.loc45_4.2: ref Inner* = class_element_access %.loc45_4.1, element2 +// CHECK:STDOUT: %a.ref.loc45: Outer* = name_ref a, %a +// CHECK:STDOUT: %.loc45_12.1: ref Outer = deref %a.ref.loc45 +// CHECK:STDOUT: %pi.ref.loc45: = name_ref pi, @Outer.%.loc34_9 [template = @Outer.%.loc34_9] +// CHECK:STDOUT: %.loc45_12.2: ref Inner* = class_element_access %.loc45_12.1, element2 +// CHECK:STDOUT: %.loc45_12.3: Inner* = bind_value %.loc45_12.2 +// CHECK:STDOUT: assign %.loc45_4.2, %.loc45_12.3 // CHECK:STDOUT: return // CHECK:STDOUT: } // CHECK:STDOUT: diff --git a/toolchain/check/testdata/class/raw_self_type.carbon b/toolchain/check/testdata/class/raw_self_type.carbon index d12a92b46210..a9e493e591ec 100644 --- a/toolchain/check/testdata/class/raw_self_type.carbon +++ b/toolchain/check/testdata/class/raw_self_type.carbon @@ -23,12 +23,12 @@ fn MemberNamedSelf.F(x: Self, y: r#Self) {} // CHECK:STDOUT: // CHECK:STDOUT: constants { // CHECK:STDOUT: %Class: type = class_type @Class [template] -// CHECK:STDOUT: %.1: type = ptr_type Class [template] -// CHECK:STDOUT: %.2: type = struct_type {} [template] -// CHECK:STDOUT: %MemberNamedSelf: type = class_type @MemberNamedSelf [template] -// CHECK:STDOUT: %Self: type = class_type @Self [template] +// CHECK:STDOUT: %.1: type = struct_type {} [template] +// CHECK:STDOUT: %.2: type = ptr_type Class [template] // CHECK:STDOUT: %.3: type = tuple_type () [template] // CHECK:STDOUT: %.4: type = ptr_type {} [template] +// CHECK:STDOUT: %MemberNamedSelf: type = class_type @MemberNamedSelf [template] +// CHECK:STDOUT: %Self: type = class_type @Self [template] // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: file { @@ -81,11 +81,11 @@ fn MemberNamedSelf.F(x: Self, y: r#Self) {} // CHECK:STDOUT: fn @F.1() { // CHECK:STDOUT: !entry: // CHECK:STDOUT: %Self.ref.loc9: type = name_ref Self, constants.%Class [template = constants.%Class] -// CHECK:STDOUT: %.loc9: type = ptr_type Class [template = constants.%.1] +// CHECK:STDOUT: %.loc9: type = ptr_type Class [template = constants.%.2] // CHECK:STDOUT: %Self.var: ref Class* = var r#Self // CHECK:STDOUT: %Self: ref Class* = bind_name r#Self, %Self.var // CHECK:STDOUT: %Self.ref.loc10_12: type = name_ref Self, constants.%Class [template = constants.%Class] -// CHECK:STDOUT: %.loc10_16: type = ptr_type Class [template = constants.%.1] +// CHECK:STDOUT: %.loc10_16: type = ptr_type Class [template = constants.%.2] // CHECK:STDOUT: %p.var: ref Class* = var p // CHECK:STDOUT: %p: ref Class* = bind_name p, %p.var // CHECK:STDOUT: %Self.ref.loc10_20: ref Class* = name_ref r#Self, %Self diff --git a/toolchain/check/testdata/class/fail_reorder.carbon b/toolchain/check/testdata/class/reorder.carbon similarity index 57% rename from toolchain/check/testdata/class/fail_reorder.carbon rename to toolchain/check/testdata/class/reorder.carbon index 2adeca0d5eb7..f5ac9f640d63 100644 --- a/toolchain/check/testdata/class/fail_reorder.carbon +++ b/toolchain/check/testdata/class/reorder.carbon @@ -6,18 +6,6 @@ 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+10]]:12: ERROR: Member access into incomplete class `Class`. - // CHECK:STDERR: return Class.F(); - // CHECK:STDERR: ^~~~~~~ - // CHECK:STDERR: fail_reorder.carbon:[[@LINE-7]]:1: Class is incomplete within its definition. - // CHECK:STDERR: class Class { - // CHECK:STDERR: ^~~~~~~~~~~~~ - // CHECK:STDERR: - // CHECK:STDERR: fail_reorder.carbon:[[@LINE+3]]:12: ERROR: Name `F` not found. - // CHECK:STDERR: return Class.F(); - // CHECK:STDERR: ^~~~~~~ return Class.F(); } @@ -26,12 +14,12 @@ class Class { } } -// CHECK:STDOUT: --- fail_reorder.carbon +// CHECK:STDOUT: --- reorder.carbon // CHECK:STDOUT: // CHECK:STDOUT: constants { // CHECK:STDOUT: %Class: type = class_type @Class [template] -// CHECK:STDOUT: %.1: i32 = int_literal 1 [template] -// CHECK:STDOUT: %.2: type = struct_type {} [template] +// CHECK:STDOUT: %.1: type = struct_type {} [template] +// CHECK:STDOUT: %.2: i32 = int_literal 1 [template] // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: file { @@ -46,7 +34,7 @@ class Class { // CHECK:STDOUT: %return.var.loc8: ref i32 = var // CHECK:STDOUT: } // CHECK:STDOUT: %F: = fn_decl @F [template] { -// CHECK:STDOUT: %return.var.loc24: ref i32 = var +// CHECK:STDOUT: %return.var.loc12: ref i32 = var // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: !members: @@ -58,13 +46,16 @@ class Class { // CHECK:STDOUT: fn @G() -> i32 { // CHECK:STDOUT: !entry: // CHECK:STDOUT: %Class.ref: type = name_ref Class, file.%Class.decl [template = constants.%Class] -// CHECK:STDOUT: %F.ref: = name_ref F, [template = ] -// CHECK:STDOUT: return +// CHECK:STDOUT: %F.ref: = name_ref F, @Class.%F [template = @Class.%F] +// CHECK:STDOUT: %.loc9_19.1: init i32 = call %F.ref() +// CHECK:STDOUT: %.loc9_21: i32 = value_of_initializer %.loc9_19.1 +// CHECK:STDOUT: %.loc9_19.2: i32 = converted %.loc9_19.1, %.loc9_21 +// CHECK:STDOUT: return %.loc9_19.2 // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: fn @F() -> i32 { // CHECK:STDOUT: !entry: -// CHECK:STDOUT: %.loc25: i32 = int_literal 1 [template = constants.%.1] -// CHECK:STDOUT: return %.loc25 +// CHECK:STDOUT: %.loc13: i32 = int_literal 1 [template = constants.%.2] +// CHECK:STDOUT: return %.loc13 // CHECK:STDOUT: } // CHECK:STDOUT: diff --git a/toolchain/check/testdata/class/reorder_qualified.carbon b/toolchain/check/testdata/class/reorder_qualified.carbon new file mode 100644 index 000000000000..2476a6304284 --- /dev/null +++ b/toolchain/check/testdata/class/reorder_qualified.carbon @@ -0,0 +1,192 @@ +// 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 { + class B { + class C; + + fn BF(); + var b: i32; + } + + class B.C { + class D { + fn F(); + + fn DF(); + var d: i32; + } + + fn D.DF() { + // A, B, C, and D are complete here. + var a: A = {.a = 1}; + var b: B = {.b = 2}; + var c: C = {.c = 3}; + var d: D = {.d = 4}; + + // Unqualified lookup looks in all of them. + AF(); + BF(); + CF(); + DF(); + } + + fn CF(); + var c: i32; + } + + fn AF(); + var a: i32; +} + +// CHECK:STDOUT: --- reorder_qualified.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %A: type = class_type @A [template] +// CHECK:STDOUT: %B: type = class_type @B [template] +// CHECK:STDOUT: %C: type = class_type @C [template] +// CHECK:STDOUT: %.1: type = unbound_element_type B, i32 [template] +// CHECK:STDOUT: %.2: type = struct_type {.b: i32} [template] +// CHECK:STDOUT: %D: type = class_type @D [template] +// CHECK:STDOUT: %.3: type = unbound_element_type D, i32 [template] +// CHECK:STDOUT: %.4: type = struct_type {.d: i32} [template] +// CHECK:STDOUT: %.5: type = unbound_element_type C, i32 [template] +// CHECK:STDOUT: %.6: type = struct_type {.c: i32} [template] +// CHECK:STDOUT: %.7: type = unbound_element_type A, i32 [template] +// CHECK:STDOUT: %.8: type = struct_type {.a: i32} [template] +// CHECK:STDOUT: %.9: type = ptr_type {.a: i32} [template] +// CHECK:STDOUT: %.10: i32 = int_literal 1 [template] +// CHECK:STDOUT: %.11: A = struct_value (%.10) [template] +// CHECK:STDOUT: %.12: type = ptr_type {.b: i32} [template] +// CHECK:STDOUT: %.13: i32 = int_literal 2 [template] +// CHECK:STDOUT: %.14: B = struct_value (%.13) [template] +// CHECK:STDOUT: %.15: type = ptr_type {.c: i32} [template] +// CHECK:STDOUT: %.16: i32 = int_literal 3 [template] +// CHECK:STDOUT: %.17: C = struct_value (%.16) [template] +// CHECK:STDOUT: %.18: type = ptr_type {.d: i32} [template] +// CHECK:STDOUT: %.19: i32 = int_literal 4 [template] +// CHECK:STDOUT: %.20: D = struct_value (%.19) [template] +// CHECK:STDOUT: %.21: type = tuple_type () [template] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file { +// CHECK:STDOUT: package: = namespace [template] { +// CHECK:STDOUT: .A = %A.decl +// CHECK:STDOUT: } +// CHECK:STDOUT: %A.decl: type = class_decl @A [template = constants.%A] {} +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @A { +// CHECK:STDOUT: %B.decl: type = class_decl @B [template = constants.%B] {} +// CHECK:STDOUT: %C.decl: type = class_decl @C [template = constants.%C] {} +// CHECK:STDOUT: %AF: = fn_decl @AF [template] {} +// CHECK:STDOUT: %.loc42: = field_decl a, element0 [template] +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .Self = constants.%A +// CHECK:STDOUT: .B = %B.decl +// CHECK:STDOUT: .AF = %AF +// CHECK:STDOUT: .a = %.loc42 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @B { +// CHECK:STDOUT: %C.decl: type = class_decl @C [template = constants.%C] {} +// CHECK:STDOUT: %BF: = fn_decl @BF [template] {} +// CHECK:STDOUT: %.loc12: = field_decl b, element0 [template] +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .Self = constants.%B +// CHECK:STDOUT: .C = %C.decl +// CHECK:STDOUT: .BF = %BF +// CHECK:STDOUT: .b = %.loc12 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @C { +// CHECK:STDOUT: %D.decl: type = class_decl @D [template = constants.%D] {} +// CHECK:STDOUT: %DF: = fn_decl @DF [template] {} +// CHECK:STDOUT: %CF: = fn_decl @CF [template] {} +// CHECK:STDOUT: %.loc38: = field_decl c, element0 [template] +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .Self = constants.%C +// CHECK:STDOUT: .D = %D.decl +// CHECK:STDOUT: .CF = %CF +// CHECK:STDOUT: .c = %.loc38 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @D { +// CHECK:STDOUT: %F: = fn_decl @F [template] {} +// CHECK:STDOUT: %DF: = fn_decl @DF [template] {} +// CHECK:STDOUT: %.loc20: = field_decl d, element0 [template] +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .Self = constants.%D +// CHECK:STDOUT: .F = %F +// CHECK:STDOUT: .DF = %DF +// CHECK:STDOUT: .d = %.loc20 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @BF(); +// CHECK:STDOUT: +// CHECK:STDOUT: fn @F(); +// CHECK:STDOUT: +// CHECK:STDOUT: fn @DF() { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %A.ref: type = name_ref A, file.%A.decl [template = constants.%A] +// CHECK:STDOUT: %a.var: ref A = var a +// CHECK:STDOUT: %a: ref A = bind_name a, %a.var +// CHECK:STDOUT: %.loc25_24: i32 = int_literal 1 [template = constants.%.10] +// CHECK:STDOUT: %.loc25_25.1: {.a: i32} = struct_literal (%.loc25_24) +// CHECK:STDOUT: %.loc25_25.2: ref i32 = class_element_access %a.var, element0 +// CHECK:STDOUT: %.loc25_25.3: init i32 = initialize_from %.loc25_24 to %.loc25_25.2 [template = constants.%.10] +// CHECK:STDOUT: %.loc25_25.4: init A = class_init (%.loc25_25.3), %a.var [template = constants.%.11] +// CHECK:STDOUT: %.loc25_25.5: init A = converted %.loc25_25.1, %.loc25_25.4 [template = constants.%.11] +// CHECK:STDOUT: assign %a.var, %.loc25_25.5 +// CHECK:STDOUT: %B.ref: type = name_ref B, @A.%B.decl [template = constants.%B] +// CHECK:STDOUT: %b.var: ref B = var b +// CHECK:STDOUT: %b: ref B = bind_name b, %b.var +// CHECK:STDOUT: %.loc26_24: i32 = int_literal 2 [template = constants.%.13] +// CHECK:STDOUT: %.loc26_25.1: {.b: i32} = struct_literal (%.loc26_24) +// CHECK:STDOUT: %.loc26_25.2: ref i32 = class_element_access %b.var, element0 +// CHECK:STDOUT: %.loc26_25.3: init i32 = initialize_from %.loc26_24 to %.loc26_25.2 [template = constants.%.13] +// CHECK:STDOUT: %.loc26_25.4: init B = class_init (%.loc26_25.3), %b.var [template = constants.%.14] +// CHECK:STDOUT: %.loc26_25.5: init B = converted %.loc26_25.1, %.loc26_25.4 [template = constants.%.14] +// CHECK:STDOUT: assign %b.var, %.loc26_25.5 +// CHECK:STDOUT: %C.ref: type = name_ref C, @B.%C.decl [template = constants.%C] +// CHECK:STDOUT: %c.var: ref C = var c +// CHECK:STDOUT: %c: ref C = bind_name c, %c.var +// CHECK:STDOUT: %.loc27_24: i32 = int_literal 3 [template = constants.%.16] +// CHECK:STDOUT: %.loc27_25.1: {.c: i32} = struct_literal (%.loc27_24) +// CHECK:STDOUT: %.loc27_25.2: ref i32 = class_element_access %c.var, element0 +// CHECK:STDOUT: %.loc27_25.3: init i32 = initialize_from %.loc27_24 to %.loc27_25.2 [template = constants.%.16] +// CHECK:STDOUT: %.loc27_25.4: init C = class_init (%.loc27_25.3), %c.var [template = constants.%.17] +// CHECK:STDOUT: %.loc27_25.5: init C = converted %.loc27_25.1, %.loc27_25.4 [template = constants.%.17] +// CHECK:STDOUT: assign %c.var, %.loc27_25.5 +// CHECK:STDOUT: %D.ref: type = name_ref D, @C.%D.decl [template = constants.%D] +// CHECK:STDOUT: %d.var: ref D = var d +// CHECK:STDOUT: %d: ref D = bind_name d, %d.var +// CHECK:STDOUT: %.loc28_24: i32 = int_literal 4 [template = constants.%.19] +// CHECK:STDOUT: %.loc28_25.1: {.d: i32} = struct_literal (%.loc28_24) +// CHECK:STDOUT: %.loc28_25.2: ref i32 = class_element_access %d.var, element0 +// CHECK:STDOUT: %.loc28_25.3: init i32 = initialize_from %.loc28_24 to %.loc28_25.2 [template = constants.%.19] +// CHECK:STDOUT: %.loc28_25.4: init D = class_init (%.loc28_25.3), %d.var [template = constants.%.20] +// CHECK:STDOUT: %.loc28_25.5: init D = converted %.loc28_25.1, %.loc28_25.4 [template = constants.%.20] +// CHECK:STDOUT: assign %d.var, %.loc28_25.5 +// CHECK:STDOUT: %AF.ref: = name_ref AF, @A.%AF [template = @A.%AF] +// CHECK:STDOUT: %.loc31: init () = call %AF.ref() +// CHECK:STDOUT: %BF.ref: = name_ref BF, @B.%BF [template = @B.%BF] +// CHECK:STDOUT: %.loc32: init () = call %BF.ref() +// CHECK:STDOUT: %CF.ref: = name_ref CF, @C.%CF [template = @C.%CF] +// CHECK:STDOUT: %.loc33: init () = call %CF.ref() +// CHECK:STDOUT: %DF.ref: = name_ref DF, @D.%DF [template = @D.%DF] +// CHECK:STDOUT: %.loc34: init () = call %DF.ref() +// CHECK:STDOUT: return +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @CF(); +// CHECK:STDOUT: +// CHECK:STDOUT: fn @AF(); +// CHECK:STDOUT: diff --git a/toolchain/check/testdata/class/scope.carbon b/toolchain/check/testdata/class/scope.carbon index 90ae9f391c2e..d09f14e63cc3 100644 --- a/toolchain/check/testdata/class/scope.carbon +++ b/toolchain/check/testdata/class/scope.carbon @@ -27,8 +27,8 @@ fn Run() { // CHECK:STDOUT: // CHECK:STDOUT: constants { // CHECK:STDOUT: %Class: type = class_type @Class [template] -// CHECK:STDOUT: %.1: i32 = int_literal 1 [template] -// CHECK:STDOUT: %.2: type = struct_type {} [template] +// CHECK:STDOUT: %.1: type = struct_type {} [template] +// CHECK:STDOUT: %.2: i32 = int_literal 1 [template] // CHECK:STDOUT: %.3: i32 = int_literal 2 [template] // CHECK:STDOUT: } // CHECK:STDOUT: @@ -61,7 +61,7 @@ fn Run() { // CHECK:STDOUT: // CHECK:STDOUT: fn @F.1() -> i32 { // CHECK:STDOUT: !entry: -// CHECK:STDOUT: %.loc9: i32 = int_literal 1 [template = constants.%.1] +// CHECK:STDOUT: %.loc9: i32 = int_literal 1 [template = constants.%.2] // CHECK:STDOUT: return %.loc9 // CHECK:STDOUT: } // CHECK:STDOUT: diff --git a/toolchain/check/testdata/impl/impl_as.carbon b/toolchain/check/testdata/impl/impl_as.carbon index 44cfdd28d1c5..258bf5520bea 100644 --- a/toolchain/check/testdata/impl/impl_as.carbon +++ b/toolchain/check/testdata/impl/impl_as.carbon @@ -10,7 +10,10 @@ interface Simple { class C { impl as Simple { - fn F() {} + fn F() { + // C is a complete type here. + var c: C = {}; + } } } @@ -23,6 +26,9 @@ class C { // CHECK:STDOUT: %C: type = class_type @C [template] // CHECK:STDOUT: %.4: = interface_witness (@impl.%F) [template] // CHECK:STDOUT: %.5: type = struct_type {} [template] +// CHECK:STDOUT: %.6: type = tuple_type () [template] +// CHECK:STDOUT: %.7: type = ptr_type {} [template] +// CHECK:STDOUT: %.8: C = struct_value () [template] // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: file { @@ -67,6 +73,13 @@ class C { // CHECK:STDOUT: // CHECK:STDOUT: fn @F.2() { // CHECK:STDOUT: !entry: +// CHECK:STDOUT: %C.ref: type = name_ref C, file.%C.decl [template = constants.%C] +// CHECK:STDOUT: %c.var: ref C = var c +// CHECK:STDOUT: %c: ref C = bind_name c, %c.var +// CHECK:STDOUT: %.loc15_19.1: {} = struct_literal () +// CHECK:STDOUT: %.loc15_19.2: init C = class_init (), %c.var [template = constants.%.8] +// CHECK:STDOUT: %.loc15_19.3: init C = converted %.loc15_19.1, %.loc15_19.2 [template = constants.%.8] +// CHECK:STDOUT: assign %c.var, %.loc15_19.3 // CHECK:STDOUT: return // CHECK:STDOUT: } // CHECK:STDOUT: diff --git a/toolchain/check/testdata/impl/self_in_class.carbon b/toolchain/check/testdata/impl/self_in_class.carbon index 149886edc3aa..17d812e43046 100644 --- a/toolchain/check/testdata/impl/self_in_class.carbon +++ b/toolchain/check/testdata/impl/self_in_class.carbon @@ -23,9 +23,9 @@ class A { // CHECK:STDOUT: %.2: type = assoc_entity_type @DefaultConstructible, [template] // CHECK:STDOUT: %.3: in DefaultConstructible> = assoc_entity element0, @DefaultConstructible.%Make [template] // CHECK:STDOUT: %A: type = class_type @A [template] -// CHECK:STDOUT: %.4: i32 = int_literal 0 [template] -// CHECK:STDOUT: %.5: = interface_witness (@impl.%Make) [template] -// CHECK:STDOUT: %.6: type = struct_type {} [template] +// CHECK:STDOUT: %.4: = interface_witness (@impl.%Make) [template] +// CHECK:STDOUT: %.5: type = struct_type {} [template] +// CHECK:STDOUT: %.6: i32 = int_literal 0 [template] // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: file { @@ -58,7 +58,7 @@ class A { // CHECK:STDOUT: %Self.ref: type = name_ref Self, i32 [template = i32] // CHECK:STDOUT: %return.var: ref i32 = var // CHECK:STDOUT: } -// CHECK:STDOUT: %.1: = interface_witness (%Make) [template = constants.%.5] +// CHECK:STDOUT: %.1: = interface_witness (%Make) [template = constants.%.4] // CHECK:STDOUT: // CHECK:STDOUT: !members: // CHECK:STDOUT: .Make = %Make @@ -78,7 +78,7 @@ class A { // CHECK:STDOUT: // CHECK:STDOUT: fn @Make.2() -> i32 { // CHECK:STDOUT: !entry: -// CHECK:STDOUT: %.loc15: i32 = int_literal 0 [template = constants.%.4] +// CHECK:STDOUT: %.loc15: i32 = int_literal 0 [template = constants.%.6] // CHECK:STDOUT: return %.loc15 // CHECK:STDOUT: } // CHECK:STDOUT: diff --git a/toolchain/check/testdata/impl/self_in_signature.carbon b/toolchain/check/testdata/impl/self_in_signature.carbon index 3d8ea947687a..02724efc3b40 100644 --- a/toolchain/check/testdata/impl/self_in_signature.carbon +++ b/toolchain/check/testdata/impl/self_in_signature.carbon @@ -43,10 +43,10 @@ impl D as SelfNested { // CHECK:STDOUT: %D: type = class_type @D [template] // CHECK:STDOUT: %.5: type = tuple_type () [template] // CHECK:STDOUT: %.6: type = ptr_type {} [template] -// CHECK:STDOUT: %.7: C = struct_value () [template] -// CHECK:STDOUT: %.8: = interface_witness (@impl.1.%F) [template] -// CHECK:STDOUT: %.9: D = struct_value () [template] -// CHECK:STDOUT: %.10: = interface_witness (@impl.2.%F) [template] +// CHECK:STDOUT: %.7: = interface_witness (@impl.1.%F) [template] +// CHECK:STDOUT: %.8: C = struct_value () [template] +// CHECK:STDOUT: %.9: = interface_witness (@impl.2.%F) [template] +// CHECK:STDOUT: %.10: D = struct_value () [template] // CHECK:STDOUT: %.11: type = interface_type @SelfNested [template] // CHECK:STDOUT: %.12: type = ptr_type Self [symbolic] // CHECK:STDOUT: %.13: type = struct_type {.x: Self, .y: i32} [symbolic] @@ -154,7 +154,7 @@ impl D as SelfNested { // CHECK:STDOUT: %C.ref.loc16_26: type = name_ref C, file.%C.decl [template = constants.%C] // CHECK:STDOUT: %return.var: ref C = var // CHECK:STDOUT: } -// CHECK:STDOUT: %.1: = interface_witness (%F) [template = constants.%.8] +// CHECK:STDOUT: %.1: = interface_witness (%F) [template = constants.%.7] // CHECK:STDOUT: // CHECK:STDOUT: !members: // CHECK:STDOUT: .F = %F @@ -172,7 +172,7 @@ impl D as SelfNested { // CHECK:STDOUT: %Self.ref.loc20_32: type = name_ref Self, constants.%D [template = constants.%D] // CHECK:STDOUT: %return.var: ref D = var // CHECK:STDOUT: } -// CHECK:STDOUT: %.1: = interface_witness (%F) [template = constants.%.10] +// CHECK:STDOUT: %.1: = interface_witness (%F) [template = constants.%.9] // CHECK:STDOUT: // CHECK:STDOUT: !members: // CHECK:STDOUT: .F = %F @@ -230,16 +230,16 @@ impl D as SelfNested { // CHECK:STDOUT: fn @F.2[@impl.1.%self.loc16_8.2: C](@impl.1.%x.loc16_17.2: C) -> @impl.1.%return.var: C { // CHECK:STDOUT: !entry: // CHECK:STDOUT: %.loc16_38.1: {} = struct_literal () -// CHECK:STDOUT: %.loc16_38.2: init C = class_init (), @impl.1.%return.var [template = constants.%.7] -// CHECK:STDOUT: %.loc16_38.3: init C = converted %.loc16_38.1, %.loc16_38.2 [template = constants.%.7] +// CHECK:STDOUT: %.loc16_38.2: init C = class_init (), @impl.1.%return.var [template = constants.%.8] +// CHECK:STDOUT: %.loc16_38.3: init C = converted %.loc16_38.1, %.loc16_38.2 [template = constants.%.8] // CHECK:STDOUT: return %.loc16_38.3 // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: fn @F.3[@impl.2.%self.loc20_8.2: D](@impl.2.%x.loc20_20.2: D) -> @impl.2.%return.var: D { // CHECK:STDOUT: !entry: // CHECK:STDOUT: %.loc20_47.1: {} = struct_literal () -// CHECK:STDOUT: %.loc20_47.2: init D = class_init (), @impl.2.%return.var [template = constants.%.9] -// CHECK:STDOUT: %.loc20_47.3: init D = converted %.loc20_47.1, %.loc20_47.2 [template = constants.%.9] +// CHECK:STDOUT: %.loc20_47.2: init D = class_init (), @impl.2.%return.var [template = constants.%.10] +// CHECK:STDOUT: %.loc20_47.3: init D = converted %.loc20_47.1, %.loc20_47.2 [template = constants.%.10] // CHECK:STDOUT: return %.loc20_47.3 // CHECK:STDOUT: } // CHECK:STDOUT: diff --git a/toolchain/check/testdata/index/fail_invalid_base.carbon b/toolchain/check/testdata/index/fail_invalid_base.carbon index a406de28347b..703d57122aba 100644 --- a/toolchain/check/testdata/index/fail_invalid_base.carbon +++ b/toolchain/check/testdata/index/fail_invalid_base.carbon @@ -4,10 +4,6 @@ // // AUTOUPDATE -// TODO: The errors here are in bad locations. We should build a SemIR -// representation for a reference to a name so that we can track the location -// properly. - namespace N; // CHECK:STDERR: fail_invalid_base.carbon:[[@LINE+4]]:14: ERROR: Expression cannot be used as a value. // CHECK:STDERR: var a: i32 = N[0]; @@ -70,20 +66,20 @@ var d: i32 = {.a: i32, .b: i32}[0]; // CHECK:STDOUT: fn @__global_init() { // CHECK:STDOUT: !entry: // CHECK:STDOUT: %N.ref: = name_ref N, file.%N [template = file.%N] -// CHECK:STDOUT: %.loc16: i32 = int_literal 0 [template = constants.%.1] +// CHECK:STDOUT: %.loc12: i32 = int_literal 0 [template = constants.%.1] // CHECK:STDOUT: assign file.%a.var, // CHECK:STDOUT: %F.ref: = name_ref F, file.%F [template = file.%F] -// CHECK:STDOUT: %.loc23: i32 = int_literal 1 [template = constants.%.2] +// CHECK:STDOUT: %.loc19: i32 = int_literal 1 [template = constants.%.2] // CHECK:STDOUT: assign file.%b.var, -// CHECK:STDOUT: %.loc29_20: i32 = int_literal 1 [template = constants.%.2] -// CHECK:STDOUT: %.loc29_28: i32 = int_literal 2 [template = constants.%.3] -// CHECK:STDOUT: %.loc29_29.1: {.a: i32, .b: i32} = struct_literal (%.loc29_20, %.loc29_28) -// CHECK:STDOUT: %.loc29_31: i32 = int_literal 0 [template = constants.%.1] -// CHECK:STDOUT: %.loc29_29.2: {.a: i32, .b: i32} = struct_value (%.loc29_20, %.loc29_28) [template = constants.%.6] -// CHECK:STDOUT: %.loc29_29.3: {.a: i32, .b: i32} = converted %.loc29_29.1, %.loc29_29.2 [template = constants.%.6] +// CHECK:STDOUT: %.loc25_20: i32 = int_literal 1 [template = constants.%.2] +// CHECK:STDOUT: %.loc25_28: i32 = int_literal 2 [template = constants.%.3] +// CHECK:STDOUT: %.loc25_29.1: {.a: i32, .b: i32} = struct_literal (%.loc25_20, %.loc25_28) +// CHECK:STDOUT: %.loc25_31: i32 = int_literal 0 [template = constants.%.1] +// CHECK:STDOUT: %.loc25_29.2: {.a: i32, .b: i32} = struct_value (%.loc25_20, %.loc25_28) [template = constants.%.6] +// CHECK:STDOUT: %.loc25_29.3: {.a: i32, .b: i32} = converted %.loc25_29.1, %.loc25_29.2 [template = constants.%.6] // CHECK:STDOUT: assign file.%c.var, -// CHECK:STDOUT: %.loc34_31: type = struct_type {.a: i32, .b: i32} [template = constants.%.4] -// CHECK:STDOUT: %.loc34_33: i32 = int_literal 0 [template = constants.%.1] +// CHECK:STDOUT: %.loc30_31: type = struct_type {.a: i32, .b: i32} [template = constants.%.4] +// CHECK:STDOUT: %.loc30_33: i32 = int_literal 0 [template = constants.%.1] // CHECK:STDOUT: assign file.%d.var, // CHECK:STDOUT: return // CHECK:STDOUT: } diff --git a/toolchain/check/testdata/interface/default_fn.carbon b/toolchain/check/testdata/interface/default_fn.carbon new file mode 100644 index 000000000000..9c8660c37aa2 --- /dev/null +++ b/toolchain/check/testdata/interface/default_fn.carbon @@ -0,0 +1,96 @@ +// 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 C { + interface I { + // TODO: Use `default` here. + fn F() { + // I and F are both complete here, and the impl below is in scope. + var c: C = {}; + c.(I.F)(); + } + } + + impl C as I { + fn F() {} + } +} + +// CHECK:STDOUT: --- default_fn.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %C: type = class_type @C [template] +// CHECK:STDOUT: %.1: type = interface_type @I [template] +// CHECK:STDOUT: %.2: type = assoc_entity_type @I, [template] +// CHECK:STDOUT: %.3: in I> = assoc_entity element0, @I.%F [template] +// CHECK:STDOUT: %.4: = interface_witness (@impl.%F) [template] +// CHECK:STDOUT: %.5: type = struct_type {} [template] +// CHECK:STDOUT: %.6: type = tuple_type () [template] +// CHECK:STDOUT: %.7: type = ptr_type {} [template] +// CHECK:STDOUT: %.8: C = struct_value () [template] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file { +// CHECK:STDOUT: package: = namespace [template] { +// CHECK:STDOUT: .C = %C.decl +// CHECK:STDOUT: } +// CHECK:STDOUT: %C.decl: type = class_decl @C [template = constants.%C] {} +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: interface @I { +// CHECK:STDOUT: %Self: I = bind_symbolic_name Self [symbolic] +// CHECK:STDOUT: %F: = fn_decl @F.1 [template] {} +// CHECK:STDOUT: %.loc10: in I> = assoc_entity element0, %F [template = constants.%.3] +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .Self = %Self +// CHECK:STDOUT: .F = %.loc10 +// CHECK:STDOUT: witness = (%F) +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: impl @impl: C as I { +// CHECK:STDOUT: %F: = fn_decl @F.2 [template] {} +// CHECK:STDOUT: %.1: = interface_witness (%F) [template = constants.%.4] +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .F = %F +// CHECK:STDOUT: witness = %.1 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @C { +// CHECK:STDOUT: %I.decl: type = interface_decl @I [template = constants.%.1] {} +// CHECK:STDOUT: impl_decl @impl { +// CHECK:STDOUT: %C.ref: type = name_ref C, file.%C.decl [template = constants.%C] +// CHECK:STDOUT: %I.ref: type = name_ref I, %I.decl [template = constants.%.1] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .Self = constants.%C +// CHECK:STDOUT: .I = %I.decl +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @F.1() { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %C.ref: type = name_ref C, file.%C.decl [template = constants.%C] +// CHECK:STDOUT: %c.var: ref C = var c +// CHECK:STDOUT: %c: ref C = bind_name c, %c.var +// CHECK:STDOUT: %.loc12_19.1: {} = struct_literal () +// CHECK:STDOUT: %.loc12_19.2: init C = class_init (), %c.var [template = constants.%.8] +// CHECK:STDOUT: %.loc12_19.3: init C = converted %.loc12_19.1, %.loc12_19.2 [template = constants.%.8] +// CHECK:STDOUT: assign %c.var, %.loc12_19.3 +// CHECK:STDOUT: %c.ref: ref C = name_ref c, %c +// CHECK:STDOUT: %I.ref: type = name_ref I, @C.%I.decl [template = constants.%.1] +// CHECK:STDOUT: %F.ref: in I> = name_ref F, @I.%.loc10 [template = constants.%.3] +// CHECK:STDOUT: %.1: = interface_witness_access @impl.%.1, element0 [template = @impl.%F] +// CHECK:STDOUT: %.loc13: init () = call %.1() +// CHECK:STDOUT: return +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @F.2() { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: return +// CHECK:STDOUT: } +// CHECK:STDOUT: diff --git a/toolchain/check/testdata/operators/overloaded/add.carbon b/toolchain/check/testdata/operators/overloaded/add.carbon index 58fe72c7ccee..918739fffadf 100644 --- a/toolchain/check/testdata/operators/overloaded/add.carbon +++ b/toolchain/check/testdata/operators/overloaded/add.carbon @@ -125,8 +125,8 @@ fn TestAssign(a: C*, b: C) { // CHECK:STDOUT: %.2: type = interface_type @Add [template] // CHECK:STDOUT: %.3: type = tuple_type () [template] // CHECK:STDOUT: %.4: type = ptr_type {} [template] -// CHECK:STDOUT: %.5: C = struct_value () [template] -// CHECK:STDOUT: %.6: = interface_witness (@impl.1.%Op) [template] +// CHECK:STDOUT: %.5: = interface_witness (@impl.1.%Op) [template] +// CHECK:STDOUT: %.6: C = struct_value () [template] // CHECK:STDOUT: %.7: type = interface_type @AddAssign [template] // CHECK:STDOUT: %.8: type = ptr_type C [template] // CHECK:STDOUT: %.9: type = ptr_type Self [symbolic] @@ -216,7 +216,7 @@ fn TestAssign(a: C*, b: C) { // CHECK:STDOUT: %C.ref.loc9_31: type = name_ref C, file.%C.decl [template = constants.%C] // CHECK:STDOUT: %return.var: ref C = var // CHECK:STDOUT: } -// CHECK:STDOUT: %.1: = interface_witness (%Op) [template = constants.%.6] +// CHECK:STDOUT: %.1: = interface_witness (%Op) [template = constants.%.5] // CHECK:STDOUT: // CHECK:STDOUT: !members: // CHECK:STDOUT: .Op = %Op @@ -249,8 +249,8 @@ fn TestAssign(a: C*, b: C) { // CHECK:STDOUT: fn @Op.1[@impl.1.%self.loc9_9.2: C](@impl.1.%other.loc9_18.2: C) -> @impl.1.%return.var: C { // CHECK:STDOUT: !entry: // CHECK:STDOUT: %.loc10_13.1: {} = struct_literal () -// CHECK:STDOUT: %.loc10_13.2: init C = class_init (), @impl.1.%return.var [template = constants.%.5] -// CHECK:STDOUT: %.loc10_13.3: init C = converted %.loc10_13.1, %.loc10_13.2 [template = constants.%.5] +// CHECK:STDOUT: %.loc10_13.2: init C = class_init (), @impl.1.%return.var [template = constants.%.6] +// CHECK:STDOUT: %.loc10_13.3: init C = converted %.loc10_13.1, %.loc10_13.2 [template = constants.%.6] // CHECK:STDOUT: return %.loc10_13.3 // CHECK:STDOUT: } // CHECK:STDOUT: diff --git a/toolchain/check/testdata/operators/overloaded/bit_and.carbon b/toolchain/check/testdata/operators/overloaded/bit_and.carbon index 1ca667324e45..a22b80d82009 100644 --- a/toolchain/check/testdata/operators/overloaded/bit_and.carbon +++ b/toolchain/check/testdata/operators/overloaded/bit_and.carbon @@ -125,8 +125,8 @@ fn TestAssign(a: C*, b: C) { // CHECK:STDOUT: %.2: type = interface_type @BitAnd [template] // CHECK:STDOUT: %.3: type = tuple_type () [template] // CHECK:STDOUT: %.4: type = ptr_type {} [template] -// CHECK:STDOUT: %.5: C = struct_value () [template] -// CHECK:STDOUT: %.6: = interface_witness (@impl.1.%Op) [template] +// CHECK:STDOUT: %.5: = interface_witness (@impl.1.%Op) [template] +// CHECK:STDOUT: %.6: C = struct_value () [template] // CHECK:STDOUT: %.7: type = interface_type @BitAndAssign [template] // CHECK:STDOUT: %.8: type = ptr_type C [template] // CHECK:STDOUT: %.9: type = ptr_type Self [symbolic] @@ -216,7 +216,7 @@ fn TestAssign(a: C*, b: C) { // CHECK:STDOUT: %C.ref.loc9_31: type = name_ref C, file.%C.decl [template = constants.%C] // CHECK:STDOUT: %return.var: ref C = var // CHECK:STDOUT: } -// CHECK:STDOUT: %.1: = interface_witness (%Op) [template = constants.%.6] +// CHECK:STDOUT: %.1: = interface_witness (%Op) [template = constants.%.5] // CHECK:STDOUT: // CHECK:STDOUT: !members: // CHECK:STDOUT: .Op = %Op @@ -249,8 +249,8 @@ fn TestAssign(a: C*, b: C) { // CHECK:STDOUT: fn @Op.1[@impl.1.%self.loc9_9.2: C](@impl.1.%other.loc9_18.2: C) -> @impl.1.%return.var: C { // CHECK:STDOUT: !entry: // CHECK:STDOUT: %.loc10_13.1: {} = struct_literal () -// CHECK:STDOUT: %.loc10_13.2: init C = class_init (), @impl.1.%return.var [template = constants.%.5] -// CHECK:STDOUT: %.loc10_13.3: init C = converted %.loc10_13.1, %.loc10_13.2 [template = constants.%.5] +// CHECK:STDOUT: %.loc10_13.2: init C = class_init (), @impl.1.%return.var [template = constants.%.6] +// CHECK:STDOUT: %.loc10_13.3: init C = converted %.loc10_13.1, %.loc10_13.2 [template = constants.%.6] // CHECK:STDOUT: return %.loc10_13.3 // CHECK:STDOUT: } // CHECK:STDOUT: diff --git a/toolchain/check/testdata/operators/overloaded/bit_complement.carbon b/toolchain/check/testdata/operators/overloaded/bit_complement.carbon index 05ad4d6e5d54..e372edd0d0c3 100644 --- a/toolchain/check/testdata/operators/overloaded/bit_complement.carbon +++ b/toolchain/check/testdata/operators/overloaded/bit_complement.carbon @@ -78,8 +78,8 @@ fn TestOp(a: C) -> C { // CHECK:STDOUT: %.2: type = interface_type @BitComplement [template] // CHECK:STDOUT: %.3: type = tuple_type () [template] // CHECK:STDOUT: %.4: type = ptr_type {} [template] -// CHECK:STDOUT: %.5: C = struct_value () [template] -// CHECK:STDOUT: %.6: = interface_witness (@impl.%Op) [template] +// CHECK:STDOUT: %.5: = interface_witness (@impl.%Op) [template] +// CHECK:STDOUT: %.6: C = struct_value () [template] // CHECK:STDOUT: %.7: type = assoc_entity_type @BitComplement, [template] // CHECK:STDOUT: %.8: in BitComplement> = assoc_entity element0, file.%import_ref.6 [template] // CHECK:STDOUT: } @@ -128,7 +128,7 @@ fn TestOp(a: C) -> C { // CHECK:STDOUT: %C.ref.loc9_23: type = name_ref C, file.%C.decl [template = constants.%C] // CHECK:STDOUT: %return.var: ref C = var // CHECK:STDOUT: } -// CHECK:STDOUT: %.1: = interface_witness (%Op) [template = constants.%.6] +// CHECK:STDOUT: %.1: = interface_witness (%Op) [template = constants.%.5] // CHECK:STDOUT: // CHECK:STDOUT: !members: // CHECK:STDOUT: .Op = %Op @@ -143,8 +143,8 @@ fn TestOp(a: C) -> C { // CHECK:STDOUT: fn @Op.1[@impl.%self.loc9_9.2: C]() -> @impl.%return.var: C { // CHECK:STDOUT: !entry: // CHECK:STDOUT: %.loc10_13.1: {} = struct_literal () -// CHECK:STDOUT: %.loc10_13.2: init C = class_init (), @impl.%return.var [template = constants.%.5] -// CHECK:STDOUT: %.loc10_13.3: init C = converted %.loc10_13.1, %.loc10_13.2 [template = constants.%.5] +// CHECK:STDOUT: %.loc10_13.2: init C = class_init (), @impl.%return.var [template = constants.%.6] +// CHECK:STDOUT: %.loc10_13.3: init C = converted %.loc10_13.1, %.loc10_13.2 [template = constants.%.6] // CHECK:STDOUT: return %.loc10_13.3 // CHECK:STDOUT: } // CHECK:STDOUT: diff --git a/toolchain/check/testdata/operators/overloaded/bit_or.carbon b/toolchain/check/testdata/operators/overloaded/bit_or.carbon index 6dad8d70e7a1..95cae617ab65 100644 --- a/toolchain/check/testdata/operators/overloaded/bit_or.carbon +++ b/toolchain/check/testdata/operators/overloaded/bit_or.carbon @@ -125,8 +125,8 @@ fn TestAssign(a: C*, b: C) { // CHECK:STDOUT: %.2: type = interface_type @BitOr [template] // CHECK:STDOUT: %.3: type = tuple_type () [template] // CHECK:STDOUT: %.4: type = ptr_type {} [template] -// CHECK:STDOUT: %.5: C = struct_value () [template] -// CHECK:STDOUT: %.6: = interface_witness (@impl.1.%Op) [template] +// CHECK:STDOUT: %.5: = interface_witness (@impl.1.%Op) [template] +// CHECK:STDOUT: %.6: C = struct_value () [template] // CHECK:STDOUT: %.7: type = interface_type @BitOrAssign [template] // CHECK:STDOUT: %.8: type = ptr_type C [template] // CHECK:STDOUT: %.9: type = ptr_type Self [symbolic] @@ -216,7 +216,7 @@ fn TestAssign(a: C*, b: C) { // CHECK:STDOUT: %C.ref.loc9_31: type = name_ref C, file.%C.decl [template = constants.%C] // CHECK:STDOUT: %return.var: ref C = var // CHECK:STDOUT: } -// CHECK:STDOUT: %.1: = interface_witness (%Op) [template = constants.%.6] +// CHECK:STDOUT: %.1: = interface_witness (%Op) [template = constants.%.5] // CHECK:STDOUT: // CHECK:STDOUT: !members: // CHECK:STDOUT: .Op = %Op @@ -249,8 +249,8 @@ fn TestAssign(a: C*, b: C) { // CHECK:STDOUT: fn @Op.1[@impl.1.%self.loc9_9.2: C](@impl.1.%other.loc9_18.2: C) -> @impl.1.%return.var: C { // CHECK:STDOUT: !entry: // CHECK:STDOUT: %.loc10_13.1: {} = struct_literal () -// CHECK:STDOUT: %.loc10_13.2: init C = class_init (), @impl.1.%return.var [template = constants.%.5] -// CHECK:STDOUT: %.loc10_13.3: init C = converted %.loc10_13.1, %.loc10_13.2 [template = constants.%.5] +// CHECK:STDOUT: %.loc10_13.2: init C = class_init (), @impl.1.%return.var [template = constants.%.6] +// CHECK:STDOUT: %.loc10_13.3: init C = converted %.loc10_13.1, %.loc10_13.2 [template = constants.%.6] // CHECK:STDOUT: return %.loc10_13.3 // CHECK:STDOUT: } // CHECK:STDOUT: diff --git a/toolchain/check/testdata/operators/overloaded/bit_xor.carbon b/toolchain/check/testdata/operators/overloaded/bit_xor.carbon index cb1a16ee987c..e389136d37c0 100644 --- a/toolchain/check/testdata/operators/overloaded/bit_xor.carbon +++ b/toolchain/check/testdata/operators/overloaded/bit_xor.carbon @@ -125,8 +125,8 @@ fn TestAssign(a: C*, b: C) { // CHECK:STDOUT: %.2: type = interface_type @BitXor [template] // CHECK:STDOUT: %.3: type = tuple_type () [template] // CHECK:STDOUT: %.4: type = ptr_type {} [template] -// CHECK:STDOUT: %.5: C = struct_value () [template] -// CHECK:STDOUT: %.6: = interface_witness (@impl.1.%Op) [template] +// CHECK:STDOUT: %.5: = interface_witness (@impl.1.%Op) [template] +// CHECK:STDOUT: %.6: C = struct_value () [template] // CHECK:STDOUT: %.7: type = interface_type @BitXorAssign [template] // CHECK:STDOUT: %.8: type = ptr_type C [template] // CHECK:STDOUT: %.9: type = ptr_type Self [symbolic] @@ -216,7 +216,7 @@ fn TestAssign(a: C*, b: C) { // CHECK:STDOUT: %C.ref.loc9_31: type = name_ref C, file.%C.decl [template = constants.%C] // CHECK:STDOUT: %return.var: ref C = var // CHECK:STDOUT: } -// CHECK:STDOUT: %.1: = interface_witness (%Op) [template = constants.%.6] +// CHECK:STDOUT: %.1: = interface_witness (%Op) [template = constants.%.5] // CHECK:STDOUT: // CHECK:STDOUT: !members: // CHECK:STDOUT: .Op = %Op @@ -249,8 +249,8 @@ fn TestAssign(a: C*, b: C) { // CHECK:STDOUT: fn @Op.1[@impl.1.%self.loc9_9.2: C](@impl.1.%other.loc9_18.2: C) -> @impl.1.%return.var: C { // CHECK:STDOUT: !entry: // CHECK:STDOUT: %.loc10_13.1: {} = struct_literal () -// CHECK:STDOUT: %.loc10_13.2: init C = class_init (), @impl.1.%return.var [template = constants.%.5] -// CHECK:STDOUT: %.loc10_13.3: init C = converted %.loc10_13.1, %.loc10_13.2 [template = constants.%.5] +// CHECK:STDOUT: %.loc10_13.2: init C = class_init (), @impl.1.%return.var [template = constants.%.6] +// CHECK:STDOUT: %.loc10_13.3: init C = converted %.loc10_13.1, %.loc10_13.2 [template = constants.%.6] // CHECK:STDOUT: return %.loc10_13.3 // CHECK:STDOUT: } // CHECK:STDOUT: diff --git a/toolchain/check/testdata/operators/overloaded/div.carbon b/toolchain/check/testdata/operators/overloaded/div.carbon index 1f21e13519ef..0144438c2707 100644 --- a/toolchain/check/testdata/operators/overloaded/div.carbon +++ b/toolchain/check/testdata/operators/overloaded/div.carbon @@ -125,8 +125,8 @@ fn TestAssign(a: C*, b: C) { // CHECK:STDOUT: %.2: type = interface_type @Div [template] // CHECK:STDOUT: %.3: type = tuple_type () [template] // CHECK:STDOUT: %.4: type = ptr_type {} [template] -// CHECK:STDOUT: %.5: C = struct_value () [template] -// CHECK:STDOUT: %.6: = interface_witness (@impl.1.%Op) [template] +// CHECK:STDOUT: %.5: = interface_witness (@impl.1.%Op) [template] +// CHECK:STDOUT: %.6: C = struct_value () [template] // CHECK:STDOUT: %.7: type = interface_type @DivAssign [template] // CHECK:STDOUT: %.8: type = ptr_type C [template] // CHECK:STDOUT: %.9: type = ptr_type Self [symbolic] @@ -216,7 +216,7 @@ fn TestAssign(a: C*, b: C) { // CHECK:STDOUT: %C.ref.loc9_31: type = name_ref C, file.%C.decl [template = constants.%C] // CHECK:STDOUT: %return.var: ref C = var // CHECK:STDOUT: } -// CHECK:STDOUT: %.1: = interface_witness (%Op) [template = constants.%.6] +// CHECK:STDOUT: %.1: = interface_witness (%Op) [template = constants.%.5] // CHECK:STDOUT: // CHECK:STDOUT: !members: // CHECK:STDOUT: .Op = %Op @@ -249,8 +249,8 @@ fn TestAssign(a: C*, b: C) { // CHECK:STDOUT: fn @Op.1[@impl.1.%self.loc9_9.2: C](@impl.1.%other.loc9_18.2: C) -> @impl.1.%return.var: C { // CHECK:STDOUT: !entry: // CHECK:STDOUT: %.loc10_13.1: {} = struct_literal () -// CHECK:STDOUT: %.loc10_13.2: init C = class_init (), @impl.1.%return.var [template = constants.%.5] -// CHECK:STDOUT: %.loc10_13.3: init C = converted %.loc10_13.1, %.loc10_13.2 [template = constants.%.5] +// CHECK:STDOUT: %.loc10_13.2: init C = class_init (), @impl.1.%return.var [template = constants.%.6] +// CHECK:STDOUT: %.loc10_13.3: init C = converted %.loc10_13.1, %.loc10_13.2 [template = constants.%.6] // CHECK:STDOUT: return %.loc10_13.3 // CHECK:STDOUT: } // CHECK:STDOUT: diff --git a/toolchain/check/testdata/operators/overloaded/left_shift.carbon b/toolchain/check/testdata/operators/overloaded/left_shift.carbon index c7eeeb7456d6..ee3a33d26de3 100644 --- a/toolchain/check/testdata/operators/overloaded/left_shift.carbon +++ b/toolchain/check/testdata/operators/overloaded/left_shift.carbon @@ -125,8 +125,8 @@ fn TestAssign(a: C*, b: C) { // CHECK:STDOUT: %.2: type = interface_type @LeftShift [template] // CHECK:STDOUT: %.3: type = tuple_type () [template] // CHECK:STDOUT: %.4: type = ptr_type {} [template] -// CHECK:STDOUT: %.5: C = struct_value () [template] -// CHECK:STDOUT: %.6: = interface_witness (@impl.1.%Op) [template] +// CHECK:STDOUT: %.5: = interface_witness (@impl.1.%Op) [template] +// CHECK:STDOUT: %.6: C = struct_value () [template] // CHECK:STDOUT: %.7: type = interface_type @LeftShiftAssign [template] // CHECK:STDOUT: %.8: type = ptr_type C [template] // CHECK:STDOUT: %.9: type = ptr_type Self [symbolic] @@ -216,7 +216,7 @@ fn TestAssign(a: C*, b: C) { // CHECK:STDOUT: %C.ref.loc9_31: type = name_ref C, file.%C.decl [template = constants.%C] // CHECK:STDOUT: %return.var: ref C = var // CHECK:STDOUT: } -// CHECK:STDOUT: %.1: = interface_witness (%Op) [template = constants.%.6] +// CHECK:STDOUT: %.1: = interface_witness (%Op) [template = constants.%.5] // CHECK:STDOUT: // CHECK:STDOUT: !members: // CHECK:STDOUT: .Op = %Op @@ -249,8 +249,8 @@ fn TestAssign(a: C*, b: C) { // CHECK:STDOUT: fn @Op.1[@impl.1.%self.loc9_9.2: C](@impl.1.%other.loc9_18.2: C) -> @impl.1.%return.var: C { // CHECK:STDOUT: !entry: // CHECK:STDOUT: %.loc10_13.1: {} = struct_literal () -// CHECK:STDOUT: %.loc10_13.2: init C = class_init (), @impl.1.%return.var [template = constants.%.5] -// CHECK:STDOUT: %.loc10_13.3: init C = converted %.loc10_13.1, %.loc10_13.2 [template = constants.%.5] +// CHECK:STDOUT: %.loc10_13.2: init C = class_init (), @impl.1.%return.var [template = constants.%.6] +// CHECK:STDOUT: %.loc10_13.3: init C = converted %.loc10_13.1, %.loc10_13.2 [template = constants.%.6] // CHECK:STDOUT: return %.loc10_13.3 // CHECK:STDOUT: } // CHECK:STDOUT: diff --git a/toolchain/check/testdata/operators/overloaded/mod.carbon b/toolchain/check/testdata/operators/overloaded/mod.carbon index 155c9f90f523..5eb6d1d87a11 100644 --- a/toolchain/check/testdata/operators/overloaded/mod.carbon +++ b/toolchain/check/testdata/operators/overloaded/mod.carbon @@ -125,8 +125,8 @@ fn TestAssign(a: C*, b: C) { // CHECK:STDOUT: %.2: type = interface_type @Mod [template] // CHECK:STDOUT: %.3: type = tuple_type () [template] // CHECK:STDOUT: %.4: type = ptr_type {} [template] -// CHECK:STDOUT: %.5: C = struct_value () [template] -// CHECK:STDOUT: %.6: = interface_witness (@impl.1.%Op) [template] +// CHECK:STDOUT: %.5: = interface_witness (@impl.1.%Op) [template] +// CHECK:STDOUT: %.6: C = struct_value () [template] // CHECK:STDOUT: %.7: type = interface_type @ModAssign [template] // CHECK:STDOUT: %.8: type = ptr_type C [template] // CHECK:STDOUT: %.9: type = ptr_type Self [symbolic] @@ -216,7 +216,7 @@ fn TestAssign(a: C*, b: C) { // CHECK:STDOUT: %C.ref.loc9_31: type = name_ref C, file.%C.decl [template = constants.%C] // CHECK:STDOUT: %return.var: ref C = var // CHECK:STDOUT: } -// CHECK:STDOUT: %.1: = interface_witness (%Op) [template = constants.%.6] +// CHECK:STDOUT: %.1: = interface_witness (%Op) [template = constants.%.5] // CHECK:STDOUT: // CHECK:STDOUT: !members: // CHECK:STDOUT: .Op = %Op @@ -249,8 +249,8 @@ fn TestAssign(a: C*, b: C) { // CHECK:STDOUT: fn @Op.1[@impl.1.%self.loc9_9.2: C](@impl.1.%other.loc9_18.2: C) -> @impl.1.%return.var: C { // CHECK:STDOUT: !entry: // CHECK:STDOUT: %.loc10_13.1: {} = struct_literal () -// CHECK:STDOUT: %.loc10_13.2: init C = class_init (), @impl.1.%return.var [template = constants.%.5] -// CHECK:STDOUT: %.loc10_13.3: init C = converted %.loc10_13.1, %.loc10_13.2 [template = constants.%.5] +// CHECK:STDOUT: %.loc10_13.2: init C = class_init (), @impl.1.%return.var [template = constants.%.6] +// CHECK:STDOUT: %.loc10_13.3: init C = converted %.loc10_13.1, %.loc10_13.2 [template = constants.%.6] // CHECK:STDOUT: return %.loc10_13.3 // CHECK:STDOUT: } // CHECK:STDOUT: diff --git a/toolchain/check/testdata/operators/overloaded/mul.carbon b/toolchain/check/testdata/operators/overloaded/mul.carbon index 16d9f2139b82..4744241eb7b8 100644 --- a/toolchain/check/testdata/operators/overloaded/mul.carbon +++ b/toolchain/check/testdata/operators/overloaded/mul.carbon @@ -125,8 +125,8 @@ fn TestAssign(a: C*, b: C) { // CHECK:STDOUT: %.2: type = interface_type @Mul [template] // CHECK:STDOUT: %.3: type = tuple_type () [template] // CHECK:STDOUT: %.4: type = ptr_type {} [template] -// CHECK:STDOUT: %.5: C = struct_value () [template] -// CHECK:STDOUT: %.6: = interface_witness (@impl.1.%Op) [template] +// CHECK:STDOUT: %.5: = interface_witness (@impl.1.%Op) [template] +// CHECK:STDOUT: %.6: C = struct_value () [template] // CHECK:STDOUT: %.7: type = interface_type @MulAssign [template] // CHECK:STDOUT: %.8: type = ptr_type C [template] // CHECK:STDOUT: %.9: type = ptr_type Self [symbolic] @@ -216,7 +216,7 @@ fn TestAssign(a: C*, b: C) { // CHECK:STDOUT: %C.ref.loc9_31: type = name_ref C, file.%C.decl [template = constants.%C] // CHECK:STDOUT: %return.var: ref C = var // CHECK:STDOUT: } -// CHECK:STDOUT: %.1: = interface_witness (%Op) [template = constants.%.6] +// CHECK:STDOUT: %.1: = interface_witness (%Op) [template = constants.%.5] // CHECK:STDOUT: // CHECK:STDOUT: !members: // CHECK:STDOUT: .Op = %Op @@ -249,8 +249,8 @@ fn TestAssign(a: C*, b: C) { // CHECK:STDOUT: fn @Op.1[@impl.1.%self.loc9_9.2: C](@impl.1.%other.loc9_18.2: C) -> @impl.1.%return.var: C { // CHECK:STDOUT: !entry: // CHECK:STDOUT: %.loc10_13.1: {} = struct_literal () -// CHECK:STDOUT: %.loc10_13.2: init C = class_init (), @impl.1.%return.var [template = constants.%.5] -// CHECK:STDOUT: %.loc10_13.3: init C = converted %.loc10_13.1, %.loc10_13.2 [template = constants.%.5] +// CHECK:STDOUT: %.loc10_13.2: init C = class_init (), @impl.1.%return.var [template = constants.%.6] +// CHECK:STDOUT: %.loc10_13.3: init C = converted %.loc10_13.1, %.loc10_13.2 [template = constants.%.6] // CHECK:STDOUT: return %.loc10_13.3 // CHECK:STDOUT: } // CHECK:STDOUT: diff --git a/toolchain/check/testdata/operators/overloaded/negate.carbon b/toolchain/check/testdata/operators/overloaded/negate.carbon index 011dfe0408e2..6d21e7890f69 100644 --- a/toolchain/check/testdata/operators/overloaded/negate.carbon +++ b/toolchain/check/testdata/operators/overloaded/negate.carbon @@ -78,8 +78,8 @@ fn TestOp(a: C) -> C { // CHECK:STDOUT: %.2: type = interface_type @Negate [template] // CHECK:STDOUT: %.3: type = tuple_type () [template] // CHECK:STDOUT: %.4: type = ptr_type {} [template] -// CHECK:STDOUT: %.5: C = struct_value () [template] -// CHECK:STDOUT: %.6: = interface_witness (@impl.%Op) [template] +// CHECK:STDOUT: %.5: = interface_witness (@impl.%Op) [template] +// CHECK:STDOUT: %.6: C = struct_value () [template] // CHECK:STDOUT: %.7: type = assoc_entity_type @Negate, [template] // CHECK:STDOUT: %.8: in Negate> = assoc_entity element0, file.%import_ref.6 [template] // CHECK:STDOUT: } @@ -128,7 +128,7 @@ fn TestOp(a: C) -> C { // CHECK:STDOUT: %C.ref.loc9_23: type = name_ref C, file.%C.decl [template = constants.%C] // CHECK:STDOUT: %return.var: ref C = var // CHECK:STDOUT: } -// CHECK:STDOUT: %.1: = interface_witness (%Op) [template = constants.%.6] +// CHECK:STDOUT: %.1: = interface_witness (%Op) [template = constants.%.5] // CHECK:STDOUT: // CHECK:STDOUT: !members: // CHECK:STDOUT: .Op = %Op @@ -143,8 +143,8 @@ fn TestOp(a: C) -> C { // CHECK:STDOUT: fn @Op.1[@impl.%self.loc9_9.2: C]() -> @impl.%return.var: C { // CHECK:STDOUT: !entry: // CHECK:STDOUT: %.loc10_13.1: {} = struct_literal () -// CHECK:STDOUT: %.loc10_13.2: init C = class_init (), @impl.%return.var [template = constants.%.5] -// CHECK:STDOUT: %.loc10_13.3: init C = converted %.loc10_13.1, %.loc10_13.2 [template = constants.%.5] +// CHECK:STDOUT: %.loc10_13.2: init C = class_init (), @impl.%return.var [template = constants.%.6] +// CHECK:STDOUT: %.loc10_13.3: init C = converted %.loc10_13.1, %.loc10_13.2 [template = constants.%.6] // CHECK:STDOUT: return %.loc10_13.3 // CHECK:STDOUT: } // CHECK:STDOUT: diff --git a/toolchain/check/testdata/operators/overloaded/right_shift.carbon b/toolchain/check/testdata/operators/overloaded/right_shift.carbon index 76b1969fd414..edaba87827de 100644 --- a/toolchain/check/testdata/operators/overloaded/right_shift.carbon +++ b/toolchain/check/testdata/operators/overloaded/right_shift.carbon @@ -125,8 +125,8 @@ fn TestAssign(a: C*, b: C) { // CHECK:STDOUT: %.2: type = interface_type @RightShift [template] // CHECK:STDOUT: %.3: type = tuple_type () [template] // CHECK:STDOUT: %.4: type = ptr_type {} [template] -// CHECK:STDOUT: %.5: C = struct_value () [template] -// CHECK:STDOUT: %.6: = interface_witness (@impl.1.%Op) [template] +// CHECK:STDOUT: %.5: = interface_witness (@impl.1.%Op) [template] +// CHECK:STDOUT: %.6: C = struct_value () [template] // CHECK:STDOUT: %.7: type = interface_type @RightShiftAssign [template] // CHECK:STDOUT: %.8: type = ptr_type C [template] // CHECK:STDOUT: %.9: type = ptr_type Self [symbolic] @@ -216,7 +216,7 @@ fn TestAssign(a: C*, b: C) { // CHECK:STDOUT: %C.ref.loc9_31: type = name_ref C, file.%C.decl [template = constants.%C] // CHECK:STDOUT: %return.var: ref C = var // CHECK:STDOUT: } -// CHECK:STDOUT: %.1: = interface_witness (%Op) [template = constants.%.6] +// CHECK:STDOUT: %.1: = interface_witness (%Op) [template = constants.%.5] // CHECK:STDOUT: // CHECK:STDOUT: !members: // CHECK:STDOUT: .Op = %Op @@ -249,8 +249,8 @@ fn TestAssign(a: C*, b: C) { // CHECK:STDOUT: fn @Op.1[@impl.1.%self.loc9_9.2: C](@impl.1.%other.loc9_18.2: C) -> @impl.1.%return.var: C { // CHECK:STDOUT: !entry: // CHECK:STDOUT: %.loc10_13.1: {} = struct_literal () -// CHECK:STDOUT: %.loc10_13.2: init C = class_init (), @impl.1.%return.var [template = constants.%.5] -// CHECK:STDOUT: %.loc10_13.3: init C = converted %.loc10_13.1, %.loc10_13.2 [template = constants.%.5] +// CHECK:STDOUT: %.loc10_13.2: init C = class_init (), @impl.1.%return.var [template = constants.%.6] +// CHECK:STDOUT: %.loc10_13.3: init C = converted %.loc10_13.1, %.loc10_13.2 [template = constants.%.6] // CHECK:STDOUT: return %.loc10_13.3 // CHECK:STDOUT: } // CHECK:STDOUT: diff --git a/toolchain/check/testdata/operators/overloaded/sub.carbon b/toolchain/check/testdata/operators/overloaded/sub.carbon index b2bc465c480a..71e2f7fbb6cc 100644 --- a/toolchain/check/testdata/operators/overloaded/sub.carbon +++ b/toolchain/check/testdata/operators/overloaded/sub.carbon @@ -125,8 +125,8 @@ fn TestAssign(a: C*, b: C) { // CHECK:STDOUT: %.2: type = interface_type @Sub [template] // CHECK:STDOUT: %.3: type = tuple_type () [template] // CHECK:STDOUT: %.4: type = ptr_type {} [template] -// CHECK:STDOUT: %.5: C = struct_value () [template] -// CHECK:STDOUT: %.6: = interface_witness (@impl.1.%Op) [template] +// CHECK:STDOUT: %.5: = interface_witness (@impl.1.%Op) [template] +// CHECK:STDOUT: %.6: C = struct_value () [template] // CHECK:STDOUT: %.7: type = interface_type @SubAssign [template] // CHECK:STDOUT: %.8: type = ptr_type C [template] // CHECK:STDOUT: %.9: type = ptr_type Self [symbolic] @@ -216,7 +216,7 @@ fn TestAssign(a: C*, b: C) { // CHECK:STDOUT: %C.ref.loc9_31: type = name_ref C, file.%C.decl [template = constants.%C] // CHECK:STDOUT: %return.var: ref C = var // CHECK:STDOUT: } -// CHECK:STDOUT: %.1: = interface_witness (%Op) [template = constants.%.6] +// CHECK:STDOUT: %.1: = interface_witness (%Op) [template = constants.%.5] // CHECK:STDOUT: // CHECK:STDOUT: !members: // CHECK:STDOUT: .Op = %Op @@ -249,8 +249,8 @@ fn TestAssign(a: C*, b: C) { // CHECK:STDOUT: fn @Op.1[@impl.1.%self.loc9_9.2: C](@impl.1.%other.loc9_18.2: C) -> @impl.1.%return.var: C { // CHECK:STDOUT: !entry: // CHECK:STDOUT: %.loc10_13.1: {} = struct_literal () -// CHECK:STDOUT: %.loc10_13.2: init C = class_init (), @impl.1.%return.var [template = constants.%.5] -// CHECK:STDOUT: %.loc10_13.3: init C = converted %.loc10_13.1, %.loc10_13.2 [template = constants.%.5] +// CHECK:STDOUT: %.loc10_13.2: init C = class_init (), @impl.1.%return.var [template = constants.%.6] +// CHECK:STDOUT: %.loc10_13.3: init C = converted %.loc10_13.1, %.loc10_13.2 [template = constants.%.6] // CHECK:STDOUT: return %.loc10_13.3 // CHECK:STDOUT: } // CHECK:STDOUT: diff --git a/toolchain/check/testdata/package_expr/syntax.carbon b/toolchain/check/testdata/package_expr/syntax.carbon index 86e37a4cbd5e..895ea0ee5bee 100644 --- a/toolchain/check/testdata/package_expr/syntax.carbon +++ b/toolchain/check/testdata/package_expr/syntax.carbon @@ -18,7 +18,7 @@ fn Main() { // --- inside_fn.carbon -package InsideFn api; +package Insidefinition api; var x: i32 = 0; diff --git a/toolchain/lex/BUILD b/toolchain/lex/BUILD index c43418847d40..0e215168d8d6 100644 --- a/toolchain/lex/BUILD +++ b/toolchain/lex/BUILD @@ -186,6 +186,7 @@ cc_library( ":token_kind", ":tokenized_buffer", "//common:check", + "//common:variant_helpers", "//toolchain/base:value_store", "//toolchain/diagnostics:diagnostic_emitter", "//toolchain/source:source_buffer", diff --git a/toolchain/lex/lex.cpp b/toolchain/lex/lex.cpp index 048961bd2829..e9b5066bbb49 100644 --- a/toolchain/lex/lex.cpp +++ b/toolchain/lex/lex.cpp @@ -7,6 +7,7 @@ #include #include "common/check.h" +#include "common/variant_helpers.h" #include "llvm/ADT/StringRef.h" #include "llvm/ADT/StringSwitch.h" #include "llvm/Support/Compiler.h" @@ -177,28 +178,6 @@ class [[clang::internal_linkage]] Lexer { TokenDiagnosticEmitter token_emitter_; }; -// TODO: Move Overload and VariantMatch somewhere more central. - -// Form an overload set from a list of functions. For example: -// -// ``` -// auto overloaded = Overload{[] (int) {}, [] (float) {}}; -// ``` -template -struct Overload : Fs... { - using Fs::operator()...; -}; -template -Overload(Fs...) -> Overload; - -// Pattern-match against the type of the value stored in the variant `V`. Each -// element of `fs` should be a function that takes one or more of the variant -// values in `V`. -template -auto VariantMatch(V&& v, Fs&&... fs) -> decltype(auto) { - return std::visit(Overload{std::forward(fs)...}, std::forward(v)); -} - #if CARBON_USE_SIMD namespace { #if __ARM_NEON diff --git a/toolchain/parse/context.cpp b/toolchain/parse/context.cpp index 70904296dc90..ad0bade02a39 100644 --- a/toolchain/parse/context.cpp +++ b/toolchain/parse/context.cpp @@ -11,8 +11,11 @@ #include "llvm/ADT/STLExtras.h" #include "toolchain/lex/token_kind.h" #include "toolchain/lex/tokenized_buffer.h" +#include "toolchain/parse/node_ids.h" #include "toolchain/parse/node_kind.h" +#include "toolchain/parse/state.h" #include "toolchain/parse/tree.h" +#include "toolchain/parse/typed_nodes.h" namespace Carbon::Parse { @@ -424,6 +427,47 @@ auto Context::EmitExpectedDeclSemiOrDefinition(Lex::TokenKind expected_kind) emitter().Emit(*position(), ExpectedDeclSemiOrDefinition, expected_kind); } +// Returns whether we are currently parsing in a scope in which function +// definitions are deferred, such as a class or interface. +static auto ParsingInDeferredDefinitionScope(Context& context) -> bool { + auto& stack = context.state_stack(); + if (stack.size() < 2 || stack.back().state != State::DeclScopeLoop) { + return false; + } + auto state = stack[stack.size() - 2].state; + return state == State::DeclDefinitionFinishAsClass || + state == State::DeclDefinitionFinishAsImpl || + state == State::DeclDefinitionFinishAsInterface || + state == State::DeclDefinitionFinishAsNamedConstraint; +} + +auto Context::AddFunctionDefinitionStart(Lex::TokenIndex token, + int subtree_start, bool has_error) + -> void { + if (ParsingInDeferredDefinitionScope(*this)) { + enclosing_deferred_definition_stack_.push_back( + tree_->deferred_definitions_.Add( + {.start_id = FunctionDefinitionStartId( + NodeId(tree_->node_impls_.size()))})); + } + + AddNode(NodeKind::FunctionDefinitionStart, token, subtree_start, has_error); +} + +auto Context::AddFunctionDefinition(Lex::TokenIndex token, int subtree_start, + bool has_error) -> void { + if (ParsingInDeferredDefinitionScope(*this)) { + auto definition_index = enclosing_deferred_definition_stack_.pop_back_val(); + auto& definition = tree_->deferred_definitions_.Get(definition_index); + definition.definition_id = + FunctionDefinitionId(NodeId(tree_->node_impls_.size())); + definition.next_definition_index = + DeferredDefinitionIndex(tree_->deferred_definitions().size()); + } + + AddNode(NodeKind::FunctionDefinition, token, subtree_start, has_error); +} + auto Context::PrintForStackDump(llvm::raw_ostream& output) const -> void { output << "Parser stack:\n"; for (auto [i, entry] : llvm::enumerate(state_stack_)) { diff --git a/toolchain/parse/context.h b/toolchain/parse/context.h index ee66c59d0221..763df120e3ef 100644 --- a/toolchain/parse/context.h +++ b/toolchain/parse/context.h @@ -307,6 +307,15 @@ class Context { tree_->imports_.push_back(package); } + // Adds a function definition start node, and begins tracking a deferred + // definition if necessary. + auto AddFunctionDefinitionStart(Lex::TokenIndex token, int subtree_start, + bool has_error) -> void; + // Adds a function definition node, and ends tracking a deferred definition if + // necessary. + auto AddFunctionDefinition(Lex::TokenIndex token, int subtree_start, + bool has_error) -> void; + // Prints information for a stack dump. auto PrintForStackDump(llvm::raw_ostream& output) const -> void; @@ -358,6 +367,11 @@ class Context { llvm::SmallVector state_stack_; + // The deferred definition indexes of functions whose definitions have begun + // but not yet finished. + llvm::SmallVector + enclosing_deferred_definition_stack_; + // The current packaging state, whether `import`/`package` are allowed. PackagingState packaging_state_ = PackagingState::FileStart; // The first non-packaging token, starting as invalid. Used for packaging diff --git a/toolchain/parse/handle_function.cpp b/toolchain/parse/handle_function.cpp index 7568abe1a38a..29a3396634eb 100644 --- a/toolchain/parse/handle_function.cpp +++ b/toolchain/parse/handle_function.cpp @@ -44,8 +44,8 @@ auto HandleFunctionSignatureFinish(Context& context) -> void { break; } case Lex::TokenKind::OpenCurlyBrace: { - context.AddNode(NodeKind::FunctionDefinitionStart, context.Consume(), - state.subtree_start, state.has_error); + context.AddFunctionDefinitionStart(context.Consume(), state.subtree_start, + state.has_error); // Any error is recorded on the FunctionDefinitionStart. state.has_error = false; context.PushState(state, State::FunctionDefinitionFinish); @@ -93,8 +93,8 @@ auto HandleFunctionSignatureFinish(Context& context) -> void { auto HandleFunctionDefinitionFinish(Context& context) -> void { auto state = context.PopState(); - context.AddNode(NodeKind::FunctionDefinition, context.Consume(), - state.subtree_start, state.has_error); + context.AddFunctionDefinition(context.Consume(), state.subtree_start, + state.has_error); } } // namespace Carbon::Parse diff --git a/toolchain/parse/tree.h b/toolchain/parse/tree.h index e287f055a652..c1d7b5ded491 100644 --- a/toolchain/parse/tree.h +++ b/toolchain/parse/tree.h @@ -16,9 +16,41 @@ #include "toolchain/lex/tokenized_buffer.h" #include "toolchain/parse/node_ids.h" #include "toolchain/parse/node_kind.h" +#include "toolchain/parse/typed_nodes.h" namespace Carbon::Parse { +struct DeferredDefinition; + +// The index of a deferred function definition within the parse tree's deferred +// definition store. +struct DeferredDefinitionIndex : public IndexBase { + using ValueType = DeferredDefinition; + + static const DeferredDefinitionIndex Invalid; + + using IndexBase::IndexBase; +}; + +constexpr DeferredDefinitionIndex DeferredDefinitionIndex::Invalid = + DeferredDefinitionIndex(InvalidIndex); + +// A function whose definition is deferred because it is defined inline in a +// class or similar scope. +// +// Such functions are type-checked out of order, with their bodies checked after +// the enclosing declaration is complete. Some additional information is tracked +// for these functions in the parse tree to support this reordering. +struct DeferredDefinition { + // The node that starts the function definition. + FunctionDefinitionStartId start_id; + // The function definition node. + FunctionDefinitionId definition_id = NodeId::Invalid; + // The index of the next method that is not nested within this one. + DeferredDefinitionIndex next_definition_index = + DeferredDefinitionIndex::Invalid; +}; + // Defined in typed_nodes.h. Include that to call `Tree::ExtractFile()`. struct File; @@ -149,6 +181,10 @@ class Tree : public Printable { return packaging_directive_; } auto imports() const -> llvm::ArrayRef { return imports_; } + auto deferred_definitions() const + -> const ValueStore& { + return deferred_definitions_; + } // See the other Print comments. auto Print(llvm::raw_ostream& output) const -> void; @@ -339,6 +375,7 @@ class Tree : public Printable { std::optional packaging_directive_; llvm::SmallVector imports_; + ValueStore deferred_definitions_; }; // A random-access iterator to the depth-first postorder sequence of parse nodes