Refactor handling of cross-package imports. (#3783)

This revamps the support for cross-package imports, making them look
more like a namespace. The planned model is mentioned on
[#toolchain](https://discord.com/channels/655572317891461132/655578254970716160/1217586076022210670).
This does not implement name lookup into the new namespace structure.

A few key changes in this PR (it's a little sprawling) are:

- Moves logic for adding package imports from context.* to import.*
- Remove SemIR::Import, which was the prior model. This is instead now a
SemIR::Namespace with the NameScope getting a new import_ir_scopes
field.
- Allow SemIR::Namespace to use Parse::ImportDirectiveId in addition to
the prior Parse::NamespaceId
- The import_ir_scopes field includes a NameScopeId so that as we
traverse to child namespaces, we can directly perform name lookup in the
other IR.
- is_closed_import now tracks whether a namespace comes from a different
package. This has a diagnostic implemented in decl_name_stack.
This commit is contained in:
Jon Ross-Perkins
2024-03-14 22:03:30 +00:00
committed by GitHub
parent 39462d49b4
commit 8567e02aa7
22 changed files with 487 additions and 169 deletions
+84 -36
View File
@@ -70,6 +70,50 @@ static auto CopyNameFromImportIR(Context& context,
return import_name_id;
}
// Adds a namespace to the IR. The bool on return is true if there was a name
// conflict. diagnose_duplicate_namespace is used when handling a cross-package
// import, where an existing namespace is in the current package and the new
// namespace is a different package.
static auto AddNamespace(
Context& context, SemIR::TypeId namespace_type_id,
Parse::ImportDirectiveId node_id, SemIR::NameId name_id,
SemIR::NameScopeId enclosing_scope_id, bool diagnose_duplicate_namespace,
std::optional<llvm::function_ref<SemIR::InstId()>> make_import_id)
-> std::pair<SemIR::NameScopeId, bool> {
auto& enclosing_scope = context.name_scopes().Get(enclosing_scope_id);
auto [it, success] =
enclosing_scope.names.insert({name_id, SemIR::InstId::Invalid});
if (!success) {
if (auto namespace_inst =
context.insts().TryGetAs<SemIR::Namespace>(it->second)) {
if (diagnose_duplicate_namespace) {
context.DiagnoseDuplicateName(node_id, it->second);
}
return {namespace_inst->name_scope_id, true};
}
}
auto import_id =
make_import_id ? (*make_import_id)() : SemIR::InstId::Invalid;
auto namespace_inst = SemIR::Namespace{
namespace_type_id, SemIR::NameScopeId::Invalid, import_id};
// Use the invalid node because there's no node to associate with.
auto namespace_id = context.AddPlaceholderInst({node_id, namespace_inst});
namespace_inst.name_scope_id =
context.name_scopes().Add(namespace_id, name_id, enclosing_scope_id);
context.ReplaceInstBeforeConstantUse(namespace_id, {node_id, namespace_inst});
// Diagnose if there's a name conflict, but still produce the namespace to
// supersede the name conflict in order to avoid repeat diagnostics.
if (!success) {
context.DiagnoseDuplicateName(namespace_id, it->second);
}
it->second = namespace_id;
return {namespace_inst.name_scope_id, false};
}
// Adds a copied namespace to the cache.
static auto CacheCopiedNamespace(
llvm::DenseMap<SemIR::NameScopeId, SemIR::NameScopeId>& copied_namespaces,
SemIR::NameScopeId import_scope_id, SemIR::NameScopeId to_scope_id)
@@ -90,42 +134,19 @@ static auto CopySingleNameScopeFromImportIR(
SemIR::NameScopeId import_scope_id, SemIR::NameScopeId enclosing_scope_id,
SemIR::NameId name_id, SemIR::TypeId namespace_type_id)
-> SemIR::NameScopeId {
auto& scope = context.name_scopes().Get(enclosing_scope_id);
auto [it, success] = scope.names.insert({name_id, SemIR::InstId::Invalid});
if (!success) {
if (auto namespace_inst =
context.insts().TryGetAs<SemIR::Namespace>(it->second)) {
// Namespaces are open, so we can append to the existing one even if it
// comes from a different file.
CacheCopiedNamespace(copied_namespaces, import_scope_id,
namespace_inst->name_scope_id);
return namespace_inst->name_scope_id;
}
}
// Produce the namespace for the entry.
auto ref_id = context.AddInst(SemIR::ImportRefUsed{
.type_id = namespace_type_id, .ir_id = ir_id, .inst_id = import_inst_id});
auto namespace_inst =
SemIR::Namespace{namespace_type_id, SemIR::NameScopeId::Invalid, ref_id};
// Use the invalid node because there's no node to associate with.
auto namespace_id =
context.AddPlaceholderInst({Parse::NodeId::Invalid, namespace_inst});
namespace_inst.name_scope_id =
context.name_scopes().Add(namespace_id, name_id, enclosing_scope_id);
context.ReplaceInstBeforeConstantUse(
namespace_id, {Parse::NodeId::Invalid, namespace_inst});
auto make_import_id = [&]() {
return context.AddInst(SemIR::ImportRefUsed{.type_id = namespace_type_id,
.ir_id = ir_id,
.inst_id = import_inst_id});
};
auto [namespace_scope_id, _] =
AddNamespace(context, namespace_type_id, Parse::NodeId::Invalid, name_id,
enclosing_scope_id, /*diagnose_duplicate_namespace=*/false,
make_import_id);
// Diagnose if there's a name conflict, but still produce the namespace to
// supersede the name conflict in order to avoid repeat diagnostics.
if (!success) {
context.DiagnoseDuplicateName(namespace_id, it->second);
}
it->second = namespace_id;
CacheCopiedNamespace(copied_namespaces, import_scope_id,
namespace_inst.name_scope_id);
return namespace_inst.name_scope_id;
CacheCopiedNamespace(copied_namespaces, import_scope_id, namespace_scope_id);
return namespace_scope_id;
}
// Copies enclosing name scopes from the import IR. Handles the parent
@@ -180,8 +201,9 @@ static auto CopyEnclosingNameScopesFromImportIR(
return scope_cursor;
}
auto Import(Context& context, SemIR::TypeId namespace_type_id,
const SemIR::File& import_sem_ir) -> void {
auto ImportLibraryFromCurrentPackage(Context& context,
SemIR::TypeId namespace_type_id,
const SemIR::File& import_sem_ir) -> void {
auto ir_id = context.import_irs().Add(&import_sem_ir);
for (const auto import_inst_id :
@@ -227,4 +249,30 @@ auto Import(Context& context, SemIR::TypeId namespace_type_id,
}
}
auto ImportLibrariesFromOtherPackage(Context& context,
SemIR::TypeId namespace_type_id,
Parse::ImportDirectiveId node_id,
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);
auto [namespace_scope_id, is_duplicate] = AddNamespace(
context, namespace_type_id, node_id, name_id, SemIR::NameScopeId::Package,
/*diagnose_duplicate_namespace=*/true, /*make_import_id=*/std::nullopt);
auto& scope = context.name_scopes().Get(namespace_scope_id);
scope.is_closed_import = !is_duplicate;
for (const auto* sem_ir : sem_irs) {
scope.import_ir_scopes.push_back(
{context.import_irs().Add(sem_ir), SemIR::NameScopeId::Package});
}
if (has_load_error) {
scope.has_error = has_load_error;
}
}
} // namespace Carbon::Check