Pass imports to SemIR. (#3415)

This adds instructions so that we get printing. I may adjust the
instruction format a little further to add a type, but I think the basic
setup will remain.

Note this builds on #3414
This commit is contained in:
Jon Ross-Perkins
2023-11-28 19:55:38 +00:00
committed by GitHub
parent 35d15a390c
commit 239f8030a4
20 changed files with 228 additions and 38 deletions
+1
View File
@@ -68,6 +68,7 @@ cc_library(
"//toolchain/sem_ir:builtin_kind",
"//toolchain/sem_ir:entry_point",
"//toolchain/sem_ir:file",
"//toolchain/sem_ir:ids",
"//toolchain/sem_ir:inst",
"//toolchain/sem_ir:inst_kind",
"@llvm-project//llvm:Support",
+96 -35
View File
@@ -17,6 +17,27 @@
namespace Carbon::Check {
struct UnitInfo {
// A given import within the file, with its destination.
struct Import {
Parse::Tree::PackagingNames names;
UnitInfo* unit_info;
};
// A file's imports corresponding to a single package, for the map.
struct PackageImports {
// Use the constructor so that the SmallVector is only constructed
// as-needed.
explicit PackageImports(Parse::Node node) : node(node) {}
// The first `import` directive in the file, which declared the package's
// identifier (even if the import failed). Used for associating diagnostics
// not specific to a single import.
Parse::Node node;
// Whether there's an import that failed to load.
bool has_load_error = false;
// The list of valid imports.
llvm::SmallVector<Import> imports;
};
explicit UnitInfo(Unit& unit)
: unit(&unit),
translator(unit.tokens, unit.tokens->source().filename(),
@@ -31,8 +52,11 @@ struct UnitInfo {
ErrorTrackingDiagnosticConsumer err_tracker;
DiagnosticEmitter<Parse::Node> emitter;
// A list of outgoing imports.
llvm::SmallVector<std::pair<Parse::Node, UnitInfo*>> imports;
// A map of package names to outgoing imports. If the
// import's target isn't available, the unit will be nullptr to assist with
// name lookup. Invalid imports (for example, `import Main;`) aren't added
// because they won't add identifiers to name lookup.
llvm::DenseMap<IdentifierId, PackageImports> package_imports_map;
// The remaining number of imports which must be checked before this unit can
// be processed.
@@ -43,6 +67,42 @@ struct UnitInfo {
llvm::SmallVector<UnitInfo*> incoming_imports;
};
// Add imports to the root block.
static auto AddImports(Context& context, UnitInfo& unit_info) -> void {
for (auto& [package_id, package_imports] : unit_info.package_imports_map) {
llvm::SmallVector<const SemIR::File*> sem_irs;
for (auto import : package_imports.imports) {
sem_irs.push_back(&**import.unit_info->unit->sem_ir);
}
context.AddPackageImports(package_imports.node, package_id, sem_irs,
package_imports.has_load_error);
}
}
// Loops over all nodes in the tree. On some errors, this may return early,
// for example if an unrecoverable state is encountered.
static auto ProcessParseNodes(Context& context,
ErrorTrackingDiagnosticConsumer& err_tracker)
-> bool {
for (auto parse_node : context.parse_tree().postorder()) {
// clang warns on unhandled enum values; clang-tidy is incorrect here.
// NOLINTNEXTLINE(bugprone-switch-missing-default-case)
switch (auto parse_kind = context.parse_tree().node_kind(parse_node)) {
#define CARBON_PARSE_NODE_KIND(Name) \
case Parse::NodeKind::Name: { \
if (!Check::Handle##Name(context, parse_node)) { \
CARBON_CHECK(err_tracker.seen_error()) \
<< "Handle" #Name " returned false without printing a diagnostic"; \
return false; \
} \
break; \
}
#include "toolchain/parse/node_kind.def"
}
}
return true;
}
// Produces and checks the IR for the provided Parse::Tree.
// TODO: Both valid and invalid imports should be recorded on the SemIR. Invalid
// imports should suppress errors where it makes sense.
@@ -54,10 +114,9 @@ static auto CheckParseTree(const SemIR::File& builtin_ir, UnitInfo& unit_info,
// For ease-of-access.
SemIR::File& sem_ir = **unit_info.unit->sem_ir;
const Parse::Tree& parse_tree = *unit_info.unit->parse_tree;
Check::Context context(*unit_info.unit->tokens, unit_info.emitter, parse_tree,
sem_ir, vlog_stream);
Context context(*unit_info.unit->tokens, unit_info.emitter,
*unit_info.unit->parse_tree, sem_ir, vlog_stream);
PrettyStackTraceFunction context_dumper(
[&](llvm::raw_ostream& output) { context.PrintForStackDump(output); });
@@ -65,24 +124,11 @@ static auto CheckParseTree(const SemIR::File& builtin_ir, UnitInfo& unit_info,
context.inst_block_stack().Push();
context.PushScope();
// Loops over all nodes in the tree. On some errors, this may return early,
// for example if an unrecoverable state is encountered.
for (auto parse_node : parse_tree.postorder()) {
// clang warns on unhandled enum values; clang-tidy is incorrect here.
// NOLINTNEXTLINE(bugprone-switch-missing-default-case)
switch (auto parse_kind = parse_tree.node_kind(parse_node)) {
#define CARBON_PARSE_NODE_KIND(Name) \
case Parse::NodeKind::Name: { \
if (!Check::Handle##Name(context, parse_node)) { \
CARBON_CHECK(unit_info.err_tracker.seen_error()) \
<< "Handle" #Name " returned false without printing a diagnostic"; \
sem_ir.set_has_errors(true); \
return; \
} \
break; \
}
#include "toolchain/parse/node_kind.def"
}
AddImports(context, unit_info);
if (!ProcessParseNodes(context, unit_info.err_tracker)) {
context.sem_ir().set_has_errors(true);
return;
}
// Pop information for the file-level scope.
@@ -228,11 +274,19 @@ static auto TrackImport(
return;
}
// Get the package imports.
auto package_imports_it =
unit_info.package_imports_map.try_emplace(import.package_id, import.node)
.first;
if (auto api = api_map.find(import_key); api != api_map.end()) {
unit_info.imports.push_back({import.node, api->second});
// Add references between the file and imported api.
package_imports_it->second.imports.push_back({import, api->second});
++unit_info.imports_remaining;
api->second->incoming_imports.push_back(&unit_info);
} else {
// The imported api is missing.
package_imports_it->second.has_load_error = true;
CARBON_DIAGNOSTIC(LibraryApiNotFound, Error,
"Corresponding API not found.");
CARBON_DIAGNOSTIC(ImportNotFound, Error, "Imported API not found.");
@@ -392,17 +446,24 @@ auto CheckParseTrees(const SemIR::File& builtin_ir,
// TODO: Better identify cycles, maybe try to untangle them.
for (auto& unit_info : unit_infos) {
if (unit_info.imports_remaining > 0) {
for (auto* import_it = unit_info.imports.begin();
import_it != unit_info.imports.end();) {
auto* import_unit = import_it->second->unit;
if (*import_unit->sem_ir) {
++import_it;
} else {
CARBON_DIAGNOSTIC(ImportCycleDetected, Error,
"Import cannot be used due to a cycle. Cycle "
"must be fixed to import.");
unit_info.emitter.Emit(import_it->first, ImportCycleDetected);
import_it = unit_info.imports.erase(import_it);
for (auto& [package_id, package_imports] :
unit_info.package_imports_map) {
for (auto* import_it = package_imports.imports.begin();
import_it != package_imports.imports.end();) {
if (*import_it->unit_info->unit->sem_ir) {
// The import is checked, so continue.
++import_it;
} else {
// The import hasn't been checked, indicating a cycle.
CARBON_DIAGNOSTIC(ImportCycleDetected, Error,
"Import cannot be used due to a cycle. Cycle "
"must be fixed to import.");
unit_info.emitter.Emit(import_it->names.node,
ImportCycleDetected);
// Make this look the same as an import which wasn't found.
package_imports.has_load_error = true;
import_it = package_imports.imports.erase(import_it);
}
}
}
}
+42
View File
@@ -15,8 +15,10 @@
#include "toolchain/lex/tokenized_buffer.h"
#include "toolchain/parse/node_kind.h"
#include "toolchain/sem_ir/file.h"
#include "toolchain/sem_ir/ids.h"
#include "toolchain/sem_ir/inst.h"
#include "toolchain/sem_ir/inst_kind.h"
#include "toolchain/sem_ir/typed_insts.h"
namespace Carbon::Check {
@@ -112,6 +114,45 @@ auto Context::NoteIncompleteClass(SemIR::ClassId class_id,
}
}
auto Context::AddPackageImports(Parse::Node import_node,
IdentifierId package_id,
llvm::ArrayRef<const SemIR::File*> sem_irs,
bool has_load_error) -> void {
CARBON_CHECK(has_load_error || !sem_irs.empty())
<< "There should be either a load error or at least one IR.";
auto name_id = SemIR::NameId::ForIdentifier(package_id);
SemIR::CrossRefIRId first_id(cross_ref_irs().size());
for (const auto* sem_ir : sem_irs) {
cross_ref_irs().Add(sem_ir);
}
if (has_load_error) {
cross_ref_irs().Add(nullptr);
}
SemIR::CrossRefIRId last_id(cross_ref_irs().size() - 1);
auto type_id = GetBuiltinType(SemIR::BuiltinKind::NamespaceType);
auto inst_id = AddInst(SemIR::Import{.parse_node = import_node,
.type_id = type_id,
.first_cross_ref_ir_id = first_id,
.last_cross_ref_ir_id = last_id});
if (name_id.is_valid()) {
// Add the import to lookup. Should always succeed because imports will be
// uniquely named.
AddNameToLookup(import_node, name_id, inst_id);
// Add a name for formatted output. This isn't used in name lookup in order
// to reduce indirection, but it's separate from the Import because it
// otherwise fits in an Inst.
AddInst(SemIR::BindName{.parse_node = import_node,
.type_id = type_id,
.name_id = name_id,
.value_id = inst_id});
} else {
// TODO: All names from the current package should be added.
}
}
auto Context::AddNameToLookup(Parse::Node name_node, SemIR::NameId name_id,
SemIR::InstId target_id) -> void {
if (current_scope().names.insert(name_id).second) {
@@ -811,6 +852,7 @@ class TypeCompleter {
case SemIR::Deref::Kind:
case SemIR::Field::Kind:
case SemIR::FunctionDecl::Kind:
case SemIR::Import::Kind:
case SemIR::InitializeFrom::Kind:
case SemIR::IntegerLiteral::Kind:
case SemIR::NameRef::Kind:
+7
View File
@@ -14,6 +14,7 @@
#include "toolchain/check/node_stack.h"
#include "toolchain/parse/tree.h"
#include "toolchain/sem_ir/file.h"
#include "toolchain/sem_ir/ids.h"
#include "toolchain/sem_ir/inst.h"
namespace Carbon::Check {
@@ -62,6 +63,12 @@ class Context {
// result.
auto AddInstAndPush(Parse::Node parse_node, SemIR::Inst inst) -> void;
// Adds a package's imports to name lookup, with all libraries together.
// sem_irs will all be non-null; has_load_error must be used for any errors.
auto AddPackageImports(Parse::Node import_node, IdentifierId package_id,
llvm::ArrayRef<const SemIR::File*> sem_irs,
bool has_load_error) -> void;
// Adds a name to name lookup. Prints a diagnostic for name conflicts.
auto AddNameToLookup(Parse::Node name_node, SemIR::NameId name_id,
SemIR::InstId target_id) -> void;
@@ -39,10 +39,14 @@ import library "lib";
// CHECK:STDOUT: file "api_lib.carbon" {
// CHECK:STDOUT: }
// CHECK:STDOUT: file "same_package.carbon" {
// CHECK:STDOUT: %import: <namespace> = import ir1, ir2
// CHECK:STDOUT: }
// CHECK:STDOUT: file "different_package.carbon" {
// CHECK:STDOUT: %import: <namespace> = import ir1, ir2
// CHECK:STDOUT: %Api: <namespace> = bind_name Api, %import
// CHECK:STDOUT: }
// CHECK:STDOUT: file "main_lib_api.carbon" {
// CHECK:STDOUT: }
// CHECK:STDOUT: file "main_import.carbon" {
// CHECK:STDOUT: %import: <namespace> = import ir1, ir1
// CHECK:STDOUT: }
@@ -26,8 +26,13 @@ package Foo library "Bar" impl;
library "Bar" impl;
// CHECK:STDOUT: file "no_api.impl.carbon" {
// CHECK:STDOUT: %import: <namespace> = import ir1, ir1
// CHECK:STDOUT: %Foo: <namespace> = bind_name Foo, %import
// CHECK:STDOUT: }
// CHECK:STDOUT: file "no_api_lib.impl.carbon" {
// CHECK:STDOUT: %import: <namespace> = import ir1, ir1
// CHECK:STDOUT: %Foo: <namespace> = bind_name Foo, %import
// CHECK:STDOUT: }
// CHECK:STDOUT: file "no_api_main_lib.impl.carbon" {
// CHECK:STDOUT: %import: <namespace> = import ir1, ir1
// CHECK:STDOUT: }
+10
View File
@@ -48,12 +48,22 @@ package CycleChild api;
import B;
// CHECK:STDOUT: file "a.carbon" {
// CHECK:STDOUT: %import: <namespace> = import ir1, ir1
// CHECK:STDOUT: %B: <namespace> = bind_name B, %import
// CHECK:STDOUT: }
// CHECK:STDOUT: file "b.carbon" {
// CHECK:STDOUT: %import: <namespace> = import ir1, ir1
// CHECK:STDOUT: %C: <namespace> = bind_name C, %import
// CHECK:STDOUT: }
// CHECK:STDOUT: file "c.carbon" {
// CHECK:STDOUT: %import: <namespace> = import ir1, ir1
// CHECK:STDOUT: %A: <namespace> = bind_name A, %import
// CHECK:STDOUT: }
// CHECK:STDOUT: file "c.impl.carbon" {
// CHECK:STDOUT: %import: <namespace> = import ir1, ir1
// CHECK:STDOUT: %C: <namespace> = bind_name C, %import
// CHECK:STDOUT: }
// CHECK:STDOUT: file "cycle_child.carbon" {
// CHECK:STDOUT: %import: <namespace> = import ir1, ir1
// CHECK:STDOUT: %B: <namespace> = bind_name B, %import
// CHECK:STDOUT: }
@@ -77,16 +77,23 @@ package SwappedExt impl;
// CHECK:STDOUT: file "main_lib.incorrect" {
// CHECK:STDOUT: }
// CHECK:STDOUT: file "main_lib_impl.incorrect" {
// CHECK:STDOUT: %import: <namespace> = import ir1, ir1
// CHECK:STDOUT: }
// CHECK:STDOUT: file "package.incorrect" {
// CHECK:STDOUT: }
// CHECK:STDOUT: file "package_impl.incorrect" {
// CHECK:STDOUT: %import: <namespace> = import ir1, ir1
// CHECK:STDOUT: %Package: <namespace> = bind_name Package, %import
// CHECK:STDOUT: }
// CHECK:STDOUT: file "package_lib.incorrect" {
// CHECK:STDOUT: }
// CHECK:STDOUT: file "package_lib_impl.incorrect" {
// CHECK:STDOUT: %import: <namespace> = import ir1, ir1
// CHECK:STDOUT: %Package: <namespace> = bind_name Package, %import
// CHECK:STDOUT: }
// CHECK:STDOUT: file "swapped_ext.impl.carbon" {
// CHECK:STDOUT: }
// CHECK:STDOUT: file "swapped_ext.carbon" {
// CHECK:STDOUT: %import: <namespace> = import ir1, ir1
// CHECK:STDOUT: %SwappedExt: <namespace> = bind_name SwappedExt, %import
// CHECK:STDOUT: }
@@ -41,6 +41,8 @@ import library default;
// CHECK:STDOUT: file "default_api.carbon" {
// CHECK:STDOUT: }
// CHECK:STDOUT: file "default.impl.carbon" {
// CHECK:STDOUT: %import: <namespace> = import ir1, ir1
// CHECK:STDOUT: %A: <namespace> = bind_name A, %import
// CHECK:STDOUT: }
// CHECK:STDOUT: file "main_import_default.carbon" {
// CHECK:STDOUT: }
@@ -93,10 +93,16 @@ import ImportNotFound;
// CHECK:STDOUT: file "implicit_api.carbon" {
// CHECK:STDOUT: }
// CHECK:STDOUT: file "implicit.impl.carbon" {
// CHECK:STDOUT: %import: <namespace> = import ir1, ir1
// CHECK:STDOUT: %Implicit: <namespace> = bind_name Implicit, %import
// CHECK:STDOUT: }
// CHECK:STDOUT: file "implicit_lib_api.carbon" {
// CHECK:STDOUT: }
// CHECK:STDOUT: file "implicit_lib.impl.carbon" {
// CHECK:STDOUT: %import: <namespace> = import ir1, ir1
// CHECK:STDOUT: %Implicit: <namespace> = bind_name Implicit, %import
// CHECK:STDOUT: }
// CHECK:STDOUT: file "not_found.carbon" {
// CHECK:STDOUT: %import: <namespace> = import ir1, ir1
// CHECK:STDOUT: %ImportNotFound: <namespace> = bind_name ImportNotFound, %import
// CHECK:STDOUT: }
@@ -65,6 +65,10 @@ import library default;
// CHECK:STDOUT: file "main_lib.carbon" {
// CHECK:STDOUT: }
// CHECK:STDOUT: file "import.carbon" {
// CHECK:STDOUT: %import.loc2: <namespace> = import ir1, ir2
// CHECK:STDOUT: %Api: <namespace> = bind_name Api, %import.loc2
// CHECK:STDOUT: %import.loc20: <namespace> = import ir3, ir3
// CHECK:STDOUT: }
// CHECK:STDOUT: file "default_import.carbon" {
// CHECK:STDOUT: %import: <namespace> = import ir1, ir1
// CHECK:STDOUT: }
@@ -50,16 +50,23 @@ library "lib" impl;
// CHECK:STDOUT: file "with_impl.carbon" {
// CHECK:STDOUT: }
// CHECK:STDOUT: file "with_impl.impl.carbon" {
// CHECK:STDOUT: %import: <namespace> = import ir1, ir1
// CHECK:STDOUT: %WithImpl: <namespace> = bind_name WithImpl, %import
// CHECK:STDOUT: }
// CHECK:STDOUT: file "with_impl_extra.impl.carbon" {
// CHECK:STDOUT: %import: <namespace> = import ir1, ir1
// CHECK:STDOUT: %WithImpl: <namespace> = bind_name WithImpl, %import
// CHECK:STDOUT: }
// CHECK:STDOUT: file "with_impl_lib.carbon" {
// CHECK:STDOUT: }
// CHECK:STDOUT: file "with_impl_lib.impl.carbon" {
// CHECK:STDOUT: %import: <namespace> = import ir1, ir1
// CHECK:STDOUT: %WithImpl: <namespace> = bind_name WithImpl, %import
// CHECK:STDOUT: }
// CHECK:STDOUT: file "main.carbon" {
// CHECK:STDOUT: }
// CHECK:STDOUT: file "main_lib.carbon" {
// CHECK:STDOUT: }
// CHECK:STDOUT: file "main_lib.impl.carbon" {
// CHECK:STDOUT: %import: <namespace> = import ir1, ir1
// CHECK:STDOUT: }
+8
View File
@@ -186,6 +186,14 @@ auto HandleFunctionDecl(FunctionContext& /*context*/, SemIR::InstId /*inst_id*/,
<< inst;
}
auto HandleImport(FunctionContext& /*context*/, SemIR::InstId /*inst_id*/,
SemIR::Import inst) -> void {
CARBON_FATAL()
<< "Should not be encountered. If that changes, we may want to change "
"higher-level logic to skip them rather than calling this. "
<< inst;
}
auto HandleInitializeFrom(FunctionContext& context, SemIR::InstId /*inst_id*/,
SemIR::InitializeFrom inst) -> void {
auto storage_type_id = context.sem_ir().insts().Get(inst.dest_id).type_id();
+2
View File
@@ -65,6 +65,7 @@ cc_library(
hdrs = ["file.h"],
deps = [
":builtin_kind",
":ids",
":inst",
":inst_kind",
":value_stores",
@@ -81,6 +82,7 @@ cc_library(
hdrs = ["formatter.h"],
deps = [
":file",
":ids",
":inst_kind",
"//toolchain/base:value_store",
"//toolchain/lex:tokenized_buffer",
+1 -1
View File
@@ -70,7 +70,7 @@ CARBON_SEM_IR_BUILTIN_KIND(FunctionType, "<function>")
// The type of bound method values.
CARBON_SEM_IR_BUILTIN_KIND(BoundMethodType, "<bound method>")
// The type of namespace names.
// The type of namespace and imported package names.
CARBON_SEM_IR_BUILTIN_KIND(NamespaceType, "<namespace>")
// Keep invalid last, so that we can use values as array indices without needing
+4
View File
@@ -10,6 +10,7 @@
#include "toolchain/base/value_store.h"
#include "toolchain/base/yaml.h"
#include "toolchain/sem_ir/builtin_kind.h"
#include "toolchain/sem_ir/ids.h"
#include "toolchain/sem_ir/inst.h"
#include "toolchain/sem_ir/inst_kind.h"
@@ -203,6 +204,7 @@ static auto GetTypePrecedence(InstKind kind) -> int {
case Deref::Kind:
case Field::Kind:
case FunctionDecl::Kind:
case Import::Kind:
case InitializeFrom::Kind:
case IntegerLiteral::Kind:
case Namespace::Kind:
@@ -406,6 +408,7 @@ auto File::StringifyTypeExpr(InstId outer_inst_id, bool in_type_context) const
case Deref::Kind:
case Field::Kind:
case FunctionDecl::Kind:
case Import::Kind:
case InitializeFrom::Kind:
case IntegerLiteral::Kind:
case Namespace::Kind:
@@ -473,6 +476,7 @@ auto GetExprCategory(const File& file, InstId inst_id) -> ExprCategory {
case ClassDecl::Kind:
case Field::Kind:
case FunctionDecl::Kind:
case Import::Kind:
case Namespace::Kind:
case NoOp::Kind:
case Return::Kind:
+1
View File
@@ -11,6 +11,7 @@
#include "llvm/Support/FormatVariadic.h"
#include "toolchain/base/value_store.h"
#include "toolchain/base/yaml.h"
#include "toolchain/sem_ir/ids.h"
#include "toolchain/sem_ir/inst.h"
#include "toolchain/sem_ir/value_stores.h"
+7 -2
View File
@@ -11,6 +11,7 @@
#include "toolchain/base/value_store.h"
#include "toolchain/lex/tokenized_buffer.h"
#include "toolchain/parse/tree.h"
#include "toolchain/sem_ir/ids.h"
namespace Carbon::SemIR {
@@ -440,6 +441,10 @@ class InstNamer {
sem_ir_.classes().Get(inst.As<ClassType>().class_id).name_id);
continue;
}
case Import::Kind: {
add_inst_name("import");
continue;
}
case NameRef::Kind: {
add_inst_name_id(inst.As<NameRef>().name_id, ".ref");
continue;
@@ -494,8 +499,6 @@ class Formatter {
FormatConstants();
out_ << "file \"" << sem_ir_.filename() << "\" {\n";
// TODO: Include information from the `package` declaration, once we
// fully support it.
// TODO: Handle the case where there are multiple top-level instruction
// blocks. For example, there may be branching in the initializer of a
// global or a type expression.
@@ -853,6 +856,8 @@ class Formatter {
auto FormatArg(ClassId id) -> void { FormatClassName(id); }
auto FormatArg(CrossRefIRId id) -> void { out_ << id; }
auto FormatArg(IntegerId id) -> void {
sem_ir_.integers().Get(id).print(out_, /*isSigned=*/false);
}
+1
View File
@@ -43,6 +43,7 @@ CARBON_SEM_IR_INST_KIND(CrossRef)
CARBON_SEM_IR_INST_KIND(Deref)
CARBON_SEM_IR_INST_KIND(Field)
CARBON_SEM_IR_INST_KIND(FunctionDecl)
CARBON_SEM_IR_INST_KIND(Import)
CARBON_SEM_IR_INST_KIND(InitializeFrom)
CARBON_SEM_IR_INST_KIND(IntegerLiteral)
CARBON_SEM_IR_INST_KIND(NameRef)
+13
View File
@@ -290,6 +290,19 @@ struct FunctionDecl {
FunctionId function_id;
};
// An import corresponds to some number of IRs. The range of imported IRs is
// inclusive of last_cross_ref_ir_id, and will always be non-empty. If
// there was an import error, first_cross_ref_ir_id will reference a
// nullptr IR; there should only ever be one nullptr in the range.
struct Import {
static constexpr auto Kind = InstKind::Import.Define("import");
Parse::Node parse_node;
TypeId type_id;
CrossRefIRId first_cross_ref_ir_id;
CrossRefIRId last_cross_ref_ir_id;
};
// Finalizes the initialization of `dest_id` from the initializer expression
// `src_id`, by performing a final copy from source to destination, for types
// whose initialization is not in-place.