From 2472f44e44d4913203b9bad3efb8e6db3cfc675b Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Tue, 10 Jun 2025 15:09:31 -0700 Subject: [PATCH] Track pending thunks on the deferred definition worklist. (#5609) Instead of using somewhat different approaches for defining in-line methods at the end of the enclosing scope and defining thunks at the end of the enclosing scope, we now use the same worklist for both. This fixes a bug where we would crash when defining thunks if there happens to be nothing else on the deferred definition worklist, leading to our leaving the enclosing impl scope before we try to define the pending thunk. That would only happen if the impl contains no in-line member function bodies, so only if the impl has only a forward declaration or a builtin declaration for every method. The latter case happens (a lot) if we start using thunks in the prelude impls. One complicating factor here is that this means the deferred definition worklist moves from the layer containing `check/handle*` and `check/check_unit.cpp` into the layer containing `check/context.cpp`. Allowing that required moving a couple of other things that it depends on -- notably `SuspendedFunction` and `HandleSuspendedFunction` -- around. --- toolchain/check/BUILD | 7 +- toolchain/check/check_unit.cpp | 2 +- toolchain/check/context.cpp | 2 + toolchain/check/context.h | 11 +-- toolchain/check/deferred_definition_scope.h | 68 ------------------- .../check/deferred_definition_worklist.cpp | 20 ++++-- .../check/deferred_definition_worklist.h | 62 ++++++++++++++--- toolchain/check/function.h | 15 ---- toolchain/check/handle.h | 9 +-- toolchain/check/handle_function.cpp | 8 +-- toolchain/check/node_id_traversal.cpp | 48 ++++++------- toolchain/check/node_id_traversal.h | 10 ++- .../check/testdata/impl/impl_thunk.carbon | 2 +- .../impl/impl_thunk_min_prelude.carbon | 39 +++++++++++ .../check/testdata/impl/import_thunk.carbon | 2 +- toolchain/check/thunk.cpp | 26 +++---- toolchain/check/thunk.h | 6 +- 17 files changed, 172 insertions(+), 165 deletions(-) delete mode 100644 toolchain/check/deferred_definition_scope.h diff --git a/toolchain/check/BUILD b/toolchain/check/BUILD index d084e57a7794..221c3014b56d 100644 --- a/toolchain/check/BUILD +++ b/toolchain/check/BUILD @@ -23,6 +23,7 @@ cc_library( "convert.cpp", "decl_name_stack.cpp", "deduce.cpp", + "deferred_definition_worklist.cpp", "eval.cpp", "eval_inst.cpp", "facet_type.cpp", @@ -66,7 +67,7 @@ cc_library( "decl_introducer_state.h", "decl_name_stack.h", "deduce.h", - "deferred_definition_scope.h", + "deferred_definition_worklist.h", "diagnostic_helpers.h", "eval.h", "eval_inst.h", @@ -110,6 +111,7 @@ cc_library( "//common:array_stack", "//common:check", "//common:concepts", + "//common:emplace_by_calling", "//common:find", "//common:map", "//common:ostream", @@ -163,8 +165,6 @@ cc_library( "check.cpp", "check_unit.cpp", "check_unit.h", - "deferred_definition_worklist.cpp", - "deferred_definition_worklist.h", "handle.h", "node_id_traversal.cpp", "node_id_traversal.h", @@ -179,7 +179,6 @@ cc_library( ":diagnostic_emitter", ":dump", "//common:check", - "//common:emplace_by_calling", "//common:error", "//common:find", "//common:map", diff --git a/toolchain/check/check_unit.cpp b/toolchain/check/check_unit.cpp index a0859cfe917f..c34651fa6f1d 100644 --- a/toolchain/check/check_unit.cpp +++ b/toolchain/check/check_unit.cpp @@ -367,7 +367,7 @@ auto CheckUnit::ImportOtherPackages(SemIR::TypeId namespace_type_id) -> void { // for example if an unrecoverable state is encountered. // NOLINTNEXTLINE(readability-function-size) auto CheckUnit::ProcessNodeIds() -> bool { - NodeIdTraversal traversal(&context_, vlog_stream_); + NodeIdTraversal traversal(&context_); Parse::NodeId node_id = Parse::NodeId::None; diff --git a/toolchain/check/context.cpp b/toolchain/check/context.cpp index 9d067589db2c..767d13fdd28e 100644 --- a/toolchain/check/context.cpp +++ b/toolchain/check/context.cpp @@ -8,6 +8,7 @@ #include #include "common/check.h" +#include "toolchain/check/deferred_definition_worklist.h" namespace Carbon::Check { @@ -26,6 +27,7 @@ Context::Context(DiagnosticEmitterBase* emitter, args_type_info_stack_("args_type_info_stack_", *sem_ir, vlog_stream), decl_name_stack_(this), scope_stack_(sem_ir_), + deferred_definition_worklist_(vlog_stream), vtable_stack_("vtable_stack_", *sem_ir, vlog_stream), global_init_(this), region_stack_([this](SemIR::LocId loc_id, std::string label) { diff --git a/toolchain/check/context.h b/toolchain/check/context.h index ef96ee1b4e70..2dfa547d0b0f 100644 --- a/toolchain/check/context.h +++ b/toolchain/check/context.h @@ -13,7 +13,7 @@ #include "toolchain/base/value_store.h" #include "toolchain/check/decl_introducer_state.h" #include "toolchain/check/decl_name_stack.h" -#include "toolchain/check/deferred_definition_scope.h" +#include "toolchain/check/deferred_definition_worklist.h" #include "toolchain/check/diagnostic_helpers.h" #include "toolchain/check/full_pattern_stack.h" #include "toolchain/check/generic_region_stack.h" @@ -129,8 +129,8 @@ class Context { return scope_stack_.full_pattern_stack(); } - auto deferred_definition_scope_stack() -> DeferredDefinitionScopeStack& { - return deferred_definition_scope_stack_; + auto deferred_definition_worklist() -> DeferredDefinitionWorklist& { + return deferred_definition_worklist_; } auto generic_region_stack() -> GenericRegionStack& { @@ -340,8 +340,9 @@ class Context { // The stack of scopes we are currently within. ScopeStack scope_stack_; - // The stack of non-nested deferred definition scopes we are currently within. - DeferredDefinitionScopeStack deferred_definition_scope_stack_; + // The worklist of deferred definition tasks to perform at the end of the + // enclosing deferred definition scope. + DeferredDefinitionWorklist deferred_definition_worklist_; // The stack of generic regions we are currently within. GenericRegionStack generic_region_stack_; diff --git a/toolchain/check/deferred_definition_scope.h b/toolchain/check/deferred_definition_scope.h deleted file mode 100644 index ca5511a6183e..000000000000 --- a/toolchain/check/deferred_definition_scope.h +++ /dev/null @@ -1,68 +0,0 @@ -// 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_DEFERRED_DEFINITION_SCOPE_H_ -#define CARBON_TOOLCHAIN_CHECK_DEFERRED_DEFINITION_SCOPE_H_ - -#include "common/array_stack.h" -#include "toolchain/check/scope_stack.h" -#include "toolchain/sem_ir/inst.h" - -namespace Carbon::Check { - -// A thunk that has been declared but not yet defined. -// -// This type is large, so moves of this type should be avoided. -struct PendingThunk : public MoveOnly { - SemIR::FunctionId signature_id; - SemIR::FunctionId function_id; - SemIR::InstId decl_id; - SemIR::InstId callee_id; - ScopeStack::SuspendedScope scope; -}; - -// A stack of the current non-nested deferred definition scopes. For example, in -// this code: -// -// class A { -// class B { -// fn F() { -// class C { -// // ... -// } -// } -// } -// } -// -// ... we have two non-nested deferred definition scopes: one for class A, and -// one for class C. The scope for class B is nested, so is not tracked. -// -// At the end of each such scope, pending function definitions for functions -// defined inline are processed. At this location, we also generate bodies for -// thunks generated by checking. -class DeferredDefinitionScopeStack { - public: - // Push a new scope. - auto Push() -> void { pending_thunks_.PushArray(); } - - // Pop a scope. - auto Pop() -> void { pending_thunks_.PopArray(); } - - // Add a pending thunk definition for the current scope. - auto AddPendingThunk(PendingThunk&& thunk) -> void { - pending_thunks_.AppendToTop(std::move(thunk)); - } - - // Peek the list of pending thunks in this scope. - auto PeekPendingThunks() -> llvm::MutableArrayRef { - return pending_thunks_.PeekArray(); - } - - private: - ArrayStack pending_thunks_; -}; - -} // namespace Carbon::Check - -#endif // CARBON_TOOLCHAIN_CHECK_DEFERRED_DEFINITION_SCOPE_H_ diff --git a/toolchain/check/deferred_definition_worklist.cpp b/toolchain/check/deferred_definition_worklist.cpp index 960c3325389b..277bf8acf54e 100644 --- a/toolchain/check/deferred_definition_worklist.cpp +++ b/toolchain/check/deferred_definition_worklist.cpp @@ -11,7 +11,7 @@ #include "common/emplace_by_calling.h" #include "common/vlog.h" #include "toolchain/base/kind_switch.h" -#include "toolchain/check/handle.h" +#include "toolchain/check/context.h" namespace Carbon::Check { @@ -25,17 +25,23 @@ DeferredDefinitionWorklist::DeferredDefinitionWorklist( } auto DeferredDefinitionWorklist::SuspendFunctionAndPush( - Context& context, Parse::DeferredDefinitionIndex index, - Parse::FunctionDefinitionStartId node_id) -> void { - // TODO: Investigate factoring out `HandleFunctionDefinitionSuspend` to make - // `DeferredDefinitionWorklist` reusable. + Parse::DeferredDefinitionIndex index, + llvm::function_refSuspendedFunction> suspend) -> void { worklist_.emplace_back(EmplaceByCalling([&] { - return CheckSkippedDefinition{ - index, HandleFunctionDefinitionSuspend(context, node_id)}; + return CheckSkippedDefinition{.definition_index = index, + .suspended_fn = suspend()}; })); CARBON_VLOG("{0}Push CheckSkippedDefinition {1}\n", VlogPrefix, index.index); } +auto DeferredDefinitionWorklist::SuspendThunkAndPush(Context& context, + ThunkInfo info) -> void { + worklist_.emplace_back(EmplaceByCalling([&] { + return DefineThunk{.info = info, .scope = context.scope_stack().Suspend()}; + })); + CARBON_VLOG("{0}Push DefineThunk {1}\n", VlogPrefix, info.function_id); +} + auto DeferredDefinitionWorklist::PushEnterDeferredDefinitionScope( Context& context) -> bool { bool nested = !entered_scopes_.empty() && diff --git a/toolchain/check/deferred_definition_worklist.h b/toolchain/check/deferred_definition_worklist.h index b13d6952822e..ca6acd80dbe7 100644 --- a/toolchain/check/deferred_definition_worklist.h +++ b/toolchain/check/deferred_definition_worklist.h @@ -11,7 +11,6 @@ #include "common/ostream.h" #include "llvm/ADT/SmallVector.h" #include "toolchain/check/decl_name_stack.h" -#include "toolchain/check/function.h" #include "toolchain/parse/tree.h" namespace Carbon::Check { @@ -20,15 +19,49 @@ namespace Carbon::Check { // in the right order. class DeferredDefinitionWorklist { public: + // 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. + // + // This type is large, so moves of this type should be avoided. + struct SuspendedFunction : public MoveOnly { + // 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; + }; + // A worklist task that indicates we should check a deferred function // definition that we previously skipped. - struct CheckSkippedDefinition { + // + // This type is large, so moves of this type should be avoided. + struct CheckSkippedDefinition : public MoveOnly { // The definition that we skipped. Parse::DeferredDefinitionIndex definition_index; // The suspended function. SuspendedFunction suspended_fn; }; + // A description of a thunk. + struct ThunkInfo { + SemIR::FunctionId signature_id; + SemIR::FunctionId function_id; + SemIR::InstId decl_id; + SemIR::InstId callee_id; + }; + + // A worklist task that indicates we should define a thunk that was previously + // declared. + // + // This type is large, so moves of this type should be avoided. + struct DefineThunk : public MoveOnly { + ThunkInfo info; + ScopeStack::SuspendedScope scope; + }; + // A worklist task that indicates we should enter a nested deferred definition // scope. We delay processing the contents of nested deferred definition // scopes until we reach the end of the parent scope. For example: @@ -46,7 +79,10 @@ class DeferredDefinitionWorklist { // } // C.G is type-checked here. // } // ``` - struct EnterNestedDeferredDefinitionScope { + // + // This type is large, so moves of this type should be avoided. + struct EnterNestedDeferredDefinitionScope + : public MoveOnly { // The suspended scope. This is only set once we reach the end of the scope. std::optional suspended_name; }; @@ -56,17 +92,21 @@ class DeferredDefinitionWorklist { struct LeaveNestedDeferredDefinitionScope {}; // A pending type-checking task. - using Task = - std::variant; + using Task = std::variant; explicit DeferredDefinitionWorklist(llvm::raw_ostream* vlog_stream); - // Suspends 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; + // Suspends the current function definition and pushes a task onto the + // worklist to finish it later. + auto SuspendFunctionAndPush( + Parse::DeferredDefinitionIndex index, + llvm::function_refSuspendedFunction> suspend) -> void; + + // Suspends the current thunk scope and pushes a task onto the worklist to + // define it later. + auto SuspendThunkAndPush(Context& context, ThunkInfo info) -> void; // Pushes a task to re-enter a function scope, so that functions defined // within it are type-checked in the right context. Returns whether a diff --git a/toolchain/check/function.h b/toolchain/check/function.h index 991065edf804..289b1aa47c7e 100644 --- a/toolchain/check/function.h +++ b/toolchain/check/function.h @@ -13,21 +13,6 @@ 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. -// -// This type is large, so moves of this type should be avoided. -struct SuspendedFunction : public MoveOnly { - // 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; -}; - // Returns the ID of the self parameter pattern, or None. // TODO: Do this during initial traversal of implicit params. auto FindSelfPattern(Context& context, diff --git a/toolchain/check/handle.h b/toolchain/check/handle.h index 2540b55da29f..db53d8806dfc 100644 --- a/toolchain/check/handle.h +++ b/toolchain/check/handle.h @@ -6,6 +6,7 @@ #define CARBON_TOOLCHAIN_CHECK_HANDLE_H_ #include "toolchain/check/context.h" +#include "toolchain/check/deferred_definition_worklist.h" #include "toolchain/check/function.h" #include "toolchain/parse/node_ids.h" @@ -23,12 +24,12 @@ namespace Carbon::Check { // cleared out in between. auto HandleFunctionDefinitionSuspend(Context& context, Parse::FunctionDefinitionStartId node_id) - -> SuspendedFunction; + -> DeferredDefinitionWorklist::SuspendedFunction; // Handle resuming the definition of a function, after a previous suspension. -auto HandleFunctionDefinitionResume(Context& context, - Parse::FunctionDefinitionStartId node_id, - SuspendedFunction&& suspended_fn) -> void; +auto HandleFunctionDefinitionResume( + Context& context, Parse::FunctionDefinitionStartId node_id, + DeferredDefinitionWorklist::SuspendedFunction&& suspended_fn) -> void; } // namespace Carbon::Check diff --git a/toolchain/check/handle_function.cpp b/toolchain/check/handle_function.cpp index dff8c413b0e8..6ce6d47f524d 100644 --- a/toolchain/check/handle_function.cpp +++ b/toolchain/check/handle_function.cpp @@ -585,7 +585,7 @@ static auto HandleFunctionDefinitionAfterSignature( auto HandleFunctionDefinitionSuspend(Context& context, Parse::FunctionDefinitionStartId node_id) - -> SuspendedFunction { + -> DeferredDefinitionWorklist::SuspendedFunction { // Process the declaration portion of the function. auto [function_id, decl_id] = BuildFunctionDecl(context, node_id, /*is_definition=*/true); @@ -594,9 +594,9 @@ auto HandleFunctionDefinitionSuspend(Context& context, .saved_name_state = context.decl_name_stack().Suspend()}; } -auto HandleFunctionDefinitionResume(Context& context, - Parse::FunctionDefinitionStartId node_id, - SuspendedFunction&& suspended_fn) -> void { +auto HandleFunctionDefinitionResume( + Context& context, Parse::FunctionDefinitionStartId node_id, + DeferredDefinitionWorklist::SuspendedFunction&& suspended_fn) -> void { context.decl_name_stack().Restore(std::move(suspended_fn.saved_name_state)); HandleFunctionDefinitionAfterSignature( context, node_id, suspended_fn.function_id, suspended_fn.decl_id); diff --git a/toolchain/check/node_id_traversal.cpp b/toolchain/check/node_id_traversal.cpp index b081cbea6a7e..ae100073a0bf 100644 --- a/toolchain/check/node_id_traversal.cpp +++ b/toolchain/check/node_id_traversal.cpp @@ -14,11 +14,10 @@ namespace Carbon::Check { -NodeIdTraversal::NodeIdTraversal(Context* context, - llvm::raw_ostream* vlog_stream) +NodeIdTraversal::NodeIdTraversal(Context* context) : context_(context), next_deferred_definition_(&context->parse_tree()), - worklist_(vlog_stream) { + worklist_(&context->deferred_definition_worklist()) { auto range = context->parse_tree().postorder(); chunks_.push_back({.it = range.begin(), .end = range.end(), @@ -31,15 +30,15 @@ auto NodeIdTraversal::Next() -> std::optional { // should check, restore its suspended state, and add a corresponding // `Chunk` to the top of the chunk list. if (chunks_.back().checking_deferred_definitions) { - if (chunks_.back().next_worklist_index < worklist_.size()) { + if (chunks_.back().next_worklist_index < worklist().size()) { std::visit([&](auto& task) { PerformTask(std::move(task)); }, - worklist_[chunks_.back().next_worklist_index++]); + worklist()[chunks_.back().next_worklist_index++]); continue; } // Worklist is empty: discard the worklist items associated with this // chunk, and leave the scope. - worklist_.truncate(chunks_.back().first_worklist_index); + worklist().truncate(chunks_.back().first_worklist_index); // We reach here when // `DeferredDefinitionScope::SuspendFinishedScopeAndPush` returns // `NonNestedWithWork`. In this case it's our responsibility to pop the @@ -57,7 +56,7 @@ auto NodeIdTraversal::Next() -> std::optional { // If we're out of chunks, then we're done entirely. if (chunks_.empty()) { - worklist_.VerifyEmpty(); + worklist().VerifyEmpty(); return std::nullopt; } @@ -73,9 +72,10 @@ auto NodeIdTraversal::Next() -> std::optional { 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); + worklist().SuspendFunctionAndPush(next_deferred_definition_.index(), [&] { + return HandleFunctionDefinitionSuspend(*context_, + definition_info.start_id); + }); // Continue type-checking the parse tree after the end of the definition. chunks_.back().it = @@ -125,29 +125,16 @@ auto NodeIdTraversal::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)) { - if (worklist_.PushEnterDeferredDefinitionScope(*context_)) { - // Track that we're within a new non-nested deferred definition scope. - context_->deferred_definition_scope_stack().Push(); - } + 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)) { - auto scope_kind = worklist_.SuspendFinishedScopeAndPush(*context_); + auto scope_kind = worklist().SuspendFinishedScopeAndPush(*context_); - // At the end of a non-nested scope, define any pending thunks and clean up - // the stack. - if (scope_kind != DeferredDefinitionWorklist::FinishedScopeKind::Nested) { - for (auto& thunk : - context_->deferred_definition_scope_stack().PeekPendingThunks()) { - BuildThunkDefinition(*context_, std::move(thunk)); - } - context_->deferred_definition_scope_stack().Pop(); - } - - // If we have function definitions in this scope, process them next. + // If we have deferred tasks in this scope, perform them next. if (scope_kind == DeferredDefinitionWorklist::FinishedScopeKind::NonNestedWithWork) { chunks_.back().checking_deferred_definitions = true; @@ -184,12 +171,17 @@ auto NodeIdTraversal::PerformTask( .end = range.end(), .next_definition = next_deferred_definition_.index(), .checking_deferred_definitions = false, - .first_worklist_index = worklist_.size(), - .next_worklist_index = worklist_.size()}); + .first_worklist_index = worklist().size(), + .next_worklist_index = worklist().size()}); ++definition_index.index; next_deferred_definition_.SkipTo(definition_index); } +auto NodeIdTraversal::PerformTask( + DeferredDefinitionWorklist::DefineThunk&& define_thunk) -> void { + BuildThunkDefinition(*context_, std::move(define_thunk)); +} + NodeIdTraversal::NextDeferredDefinitionCache::NextDeferredDefinitionCache( const Parse::Tree* tree) : tree_(tree) { diff --git a/toolchain/check/node_id_traversal.h b/toolchain/check/node_id_traversal.h index 65e5d6954ade..babcfea334b1 100644 --- a/toolchain/check/node_id_traversal.h +++ b/toolchain/check/node_id_traversal.h @@ -18,7 +18,7 @@ namespace Carbon::Check { class NodeIdTraversal { public: // `context` must not be null. - explicit NodeIdTraversal(Context* context, llvm::raw_ostream* vlog_stream); + explicit NodeIdTraversal(Context* context); // Finds the next `NodeId` to type-check. Returns nullopt if the traversal is // complete. @@ -70,6 +70,8 @@ class NodeIdTraversal { size_t next_worklist_index; }; + auto worklist() -> DeferredDefinitionWorklist& { return *worklist_; } + // Re-enter a nested deferred definition scope. auto PerformTask( DeferredDefinitionWorklist::EnterNestedDeferredDefinitionScope&& enter) @@ -85,9 +87,13 @@ class NodeIdTraversal { DeferredDefinitionWorklist::CheckSkippedDefinition&& parse_definition) -> void; + // Define a thunk. + auto PerformTask(DeferredDefinitionWorklist::DefineThunk&& define_thunk) + -> void; + Context* context_; NextDeferredDefinitionCache next_deferred_definition_; - DeferredDefinitionWorklist worklist_; + DeferredDefinitionWorklist* worklist_; llvm::SmallVector chunks_; }; diff --git a/toolchain/check/testdata/impl/impl_thunk.carbon b/toolchain/check/testdata/impl/impl_thunk.carbon index 12e635bd8c22..35cb833aadfb 100644 --- a/toolchain/check/testdata/impl/impl_thunk.carbon +++ b/toolchain/check/testdata/impl/impl_thunk.carbon @@ -643,9 +643,9 @@ impl () as I({}) { // CHECK:STDOUT: %pattern_type.afe: type = pattern_type %ptr.79f [symbolic] // CHECK:STDOUT: %F.type.39e918.2: type = fn_type @F.3 [concrete] // CHECK:STDOUT: %F.c04b92.2: %F.type.39e918.2 = struct_value () [concrete] +// CHECK:STDOUT: %require_complete.4ae: = require_complete_type %U [symbolic] // CHECK:STDOUT: %require_complete.6e5: = require_complete_type %ptr.79f [symbolic] // CHECK:STDOUT: %F.specific_fn: = specific_function %F.c04b92.1, @F.2(%ptr.79f) [symbolic] -// CHECK:STDOUT: %require_complete.4ae: = require_complete_type %U [symbolic] // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: impl @impl: %.loc8_7.2 as %I.ref { diff --git a/toolchain/check/testdata/impl/impl_thunk_min_prelude.carbon b/toolchain/check/testdata/impl/impl_thunk_min_prelude.carbon index 345c8f69923a..6853f299af6d 100644 --- a/toolchain/check/testdata/impl/impl_thunk_min_prelude.carbon +++ b/toolchain/check/testdata/impl/impl_thunk_min_prelude.carbon @@ -39,3 +39,42 @@ class A { fn Convert[self: Self]() -> A { return {}; } } } + +// --- fail_todo_out_of_line_thunk.carbon + +library "[[@TEST_NAME]]"; + +class Wrap(T:! type) {} + +interface OpWith(U:! type) { + fn Op[self: Self](u: U); +} + +impl forall [T:! type, U:! Core.ImplicitAs(Wrap(T))] Wrap(T) as OpWith(U) { + // CHECK:STDERR: fail_todo_out_of_line_thunk.carbon:[[@LINE+7]]:3: error: use of undefined generic function [MissingGenericFunctionDefinition] + // CHECK:STDERR: fn Op[self: Self](other: Self); + // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // CHECK:STDERR: fail_todo_out_of_line_thunk.carbon:[[@LINE+4]]:3: note: generic function declared here [MissingGenericFunctionDefinitionHere] + // CHECK:STDERR: fn Op[self: Self](other: Self); + // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // CHECK:STDERR: + fn Op[self: Self](other: Self); +} + +// TODO: Once we support the syntax for defining impl members out of line, +// define the above function here. +// fn (forall [T:! type, U:! Core.ImplicitAs(Wrap(T))] Wrap(T) as OpWith(U)).Op[self: Self](other: Self) {} + +// --- builtin_thunk.carbon + +library "[[@TEST_NAME]]"; + +class Wrap(T:! type) {} + +interface OpWith(U:! type) { + fn Op[self: Self](u: U); +} + +impl forall [T:! type, U:! Core.ImplicitAs(Wrap(T))] Wrap(T) as OpWith(U) { + fn Op[self: Self](other: Self) = "no_op"; +} diff --git a/toolchain/check/testdata/impl/import_thunk.carbon b/toolchain/check/testdata/impl/import_thunk.carbon index 525083ae19a1..8d21fd05ae54 100644 --- a/toolchain/check/testdata/impl/import_thunk.carbon +++ b/toolchain/check/testdata/impl/import_thunk.carbon @@ -112,8 +112,8 @@ fn G() { // CHECK:STDOUT: %I.facet: %I.type = facet_value %C.13320f.2, (%I.impl_witness) [symbolic] // CHECK:STDOUT: %F.type.0daaa1.2: type = fn_type @F.3, @impl(%Y) [symbolic] // CHECK:STDOUT: %F.49c1ac.2: %F.type.0daaa1.2 = struct_value () [symbolic] -// CHECK:STDOUT: %F.specific_fn: = specific_function %F.49c1ac.1, @F.2(%Y) [symbolic] // CHECK:STDOUT: %require_complete: = require_complete_type %C.13320f.2 [symbolic] +// CHECK:STDOUT: %F.specific_fn: = specific_function %F.49c1ac.1, @F.2(%Y) [symbolic] // CHECK:STDOUT: %C.val: %C.13320f.2 = struct_value () [symbolic] // CHECK:STDOUT: } // CHECK:STDOUT: diff --git a/toolchain/check/thunk.cpp b/toolchain/check/thunk.cpp index 0dd89c103f69..bb45d925acef 100644 --- a/toolchain/check/thunk.cpp +++ b/toolchain/check/thunk.cpp @@ -9,7 +9,7 @@ #include "toolchain/base/kind_switch.h" #include "toolchain/check/call.h" #include "toolchain/check/convert.h" -#include "toolchain/check/deferred_definition_scope.h" +#include "toolchain/check/deferred_definition_worklist.h" #include "toolchain/check/diagnostic_helpers.h" #include "toolchain/check/function.h" #include "toolchain/check/generic.h" @@ -272,13 +272,13 @@ auto BuildThunk(Context& context, SemIR::FunctionId signature_id, // Register the thunk to be defined when we reach the end of the enclosing // deferred definition scope, for example an `impl` or `class` definition, as // if the thunk's body were written inline in this location. - context.deferred_definition_scope_stack().AddPendingThunk({ - .signature_id = signature_id, - .function_id = function_id, - .decl_id = thunk_id, - .callee_id = callee_id, - .scope = context.scope_stack().Suspend(), - }); + context.deferred_definition_worklist().SuspendThunkAndPush( + context, { + .signature_id = signature_id, + .function_id = function_id, + .decl_id = thunk_id, + .callee_id = callee_id, + }); return thunk_id; } @@ -419,11 +419,13 @@ static auto BuildThunkDefinition(Context& context, FinishGenericDefinition(context, function.generic_id); } -auto BuildThunkDefinition(Context& context, PendingThunk&& thunk) -> void { - context.scope_stack().Restore(std::move(thunk.scope)); +auto BuildThunkDefinition(Context& context, + DeferredDefinitionWorklist::DefineThunk&& task) + -> void { + context.scope_stack().Restore(std::move(task.scope)); - BuildThunkDefinition(context, thunk.signature_id, thunk.function_id, - thunk.decl_id, thunk.callee_id); + BuildThunkDefinition(context, task.info.signature_id, task.info.function_id, + task.info.decl_id, task.info.callee_id); context.scope_stack().Pop(); } diff --git a/toolchain/check/thunk.h b/toolchain/check/thunk.h index de7db65efdd3..0f7a9c4b6479 100644 --- a/toolchain/check/thunk.h +++ b/toolchain/check/thunk.h @@ -6,7 +6,7 @@ #define CARBON_TOOLCHAIN_CHECK_THUNK_H_ #include "toolchain/check/context.h" -#include "toolchain/check/deferred_definition_scope.h" +#include "toolchain/check/deferred_definition_worklist.h" #include "toolchain/sem_ir/ids.h" namespace Carbon::Check { @@ -20,7 +20,9 @@ auto BuildThunk(Context& context, SemIR::FunctionId signature_id, // Builds the definition for a thunk whose definition was deferred until the end // of the enclosing scope. -auto BuildThunkDefinition(Context& context, PendingThunk&& thunk) -> void; +auto BuildThunkDefinition(Context& context, + DeferredDefinitionWorklist::DefineThunk&& task) + -> void; } // namespace Carbon::Check