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