Handle import refs in TryGetExistingDecl (#7620)

This solves the problem where `interface` imports are incorrectly
diagnosed as duplicate names in impl files.
Follows the implementation logic used in `handle_class.cpp`.

---------

Co-authored-by: Richard Smith <richard@metafoo.co.uk>
This commit is contained in:
Özgür T. Önsoy
2026-08-11 19:29:46 +00:00
committed by GitHub
co-authored by Richard Smith
parent 0f93cbd370
commit 1f22ba91bb
10 changed files with 388 additions and 83 deletions
+97 -39
View File
@@ -6,8 +6,11 @@
#include <algorithm>
#include <cstddef>
#include <optional>
#include <type_traits>
#include "common/concepts.h"
#include "toolchain/base/kind_switch.h"
#include "toolchain/check/context.h"
#include "toolchain/check/core_identifier.h"
#include "toolchain/check/eval.h"
@@ -153,28 +156,6 @@ auto AddSelfSymbolicBindingToScope(Context& context,
return self_param_inst_id;
}
template <typename EntityT>
requires std::same_as<EntityT, SemIR::Interface>
static auto TryGetEntity(Context& context, SemIR::Inst inst)
-> const SemIR::EntityWithParamsBase* {
if (auto decl = inst.TryAs<SemIR::InterfaceDecl>()) {
return &context.interfaces().Get(decl->interface_id);
} else {
return nullptr;
}
}
template <typename EntityT>
requires std::same_as<EntityT, SemIR::NamedConstraint>
static auto TryGetEntity(Context& context, SemIR::Inst inst)
-> const SemIR::EntityWithParamsBase* {
if (auto decl = inst.TryAs<SemIR::NamedConstraintDecl>()) {
return &context.named_constraints().Get(decl->named_constraint_id);
} else {
return nullptr;
}
}
template <typename EntityT>
requires std::same_as<EntityT, SemIR::Interface>
static constexpr auto DeclTokenKind() -> Lex::TokenKind {
@@ -193,6 +174,11 @@ auto TryGetExistingDecl(Context& context, const NameComponent& name,
SemIR::ScopeLookupResult lookup_result,
const EntityT& entity, bool is_definition)
-> std::optional<SemIR::Inst> {
using EntityIdT =
std::conditional_t<std::is_same_v<EntityT, SemIR::Interface>,
SemIR::InterfaceId, SemIR::NamedConstraintId>;
constexpr bool IsInterface = std::is_same_v<EntityIdT, SemIR::InterfaceId>;
if (lookup_result.is_poisoned()) {
// This is a declaration of a poisoned name.
DiagnosePoisonedName(context, name.name_id,
@@ -204,39 +190,100 @@ auto TryGetExistingDecl(Context& context, const NameComponent& name,
return std::nullopt;
}
SemIR::InstId existing_id = lookup_result.target_inst_id();
SemIR::Inst existing_decl_inst = context.insts().Get(existing_id);
const auto* existing_decl_entity =
TryGetEntity<EntityT>(context, existing_decl_inst);
if (!existing_decl_entity) {
auto prev_id = lookup_result.target_inst_id();
auto prev = context.insts().Get(prev_id);
auto prev_entity_id = EntityIdT::None;
auto prev_import_ir_id = SemIR::ImportIRId::None;
auto existing_decl_id = SemIR::InstId::None;
CARBON_KIND_SWITCH(prev) {
case CARBON_KIND(SemIR::InterfaceDecl interface_decl): {
if constexpr (IsInterface) {
prev_entity_id = interface_decl.interface_id;
existing_decl_id = prev_id;
}
break;
}
case CARBON_KIND(SemIR::NamedConstraintDecl named_constraint_decl): {
if constexpr (!IsInterface) {
prev_entity_id = named_constraint_decl.named_constraint_id;
existing_decl_id = prev_id;
}
break;
}
case CARBON_KIND(SemIR::ImportRefLoaded import_ref): {
auto import_ir_inst =
context.import_ir_insts().Get(import_ref.import_ir_inst_id);
// Verify the decl so that things like aliases are name conflicts.
const auto* import_ir =
context.import_irs().Get(import_ir_inst.ir_id()).sem_ir;
if constexpr (IsInterface) {
if (!import_ir->insts().Is<SemIR::InterfaceDecl>(
import_ir_inst.inst_id())) {
break;
}
} else {
if (!import_ir->insts().Is<SemIR::NamedConstraintDecl>(
import_ir_inst.inst_id())) {
break;
}
}
// Use the constant value to get the ID.
auto decl_value = context.insts().Get(
context.constant_values().GetConstantInstId(prev_id));
if (auto facet_type = decl_value.TryAs<SemIR::FacetType>()) {
auto declared_facet_type = context.declared_facet_types().Get(
facet_type->declared_facet_type_id);
if constexpr (IsInterface) {
prev_entity_id =
declared_facet_type.extend_constraints[0].interface_id;
} else {
prev_entity_id = declared_facet_type.extend_named_constraints[0]
.named_constraint_id;
}
prev_import_ir_id = import_ir_inst.ir_id();
}
break;
}
default:
break;
}
if (!prev_entity_id.has_value()) {
// This is a redeclaration with a different entity kind.
DiagnoseDuplicateName(context, name.name_id, name.name_loc_id,
SemIR::LocId(existing_id));
SemIR::LocId(prev_id));
return std::nullopt;
}
auto& prev_entity = [&]() -> EntityT& {
if constexpr (IsInterface) {
return context.interfaces().Get(prev_entity_id);
} else {
return context.named_constraints().Get(prev_entity_id);
}
}();
if (!CheckRedeclParamsMatch(
context,
DeclParams(SemIR::LocId(entity.latest_decl_id()),
name.first_param_node_id, name.last_param_node_id,
name.implicit_param_patterns_id, name.param_patterns_id),
DeclParams(*existing_decl_entity))) {
DeclParams(prev_entity))) {
// Mismatch is diagnosed already if found.
return std::nullopt;
}
// TODO: This should be refactored a little, particularly for
// prev_import_ir_id. See similar logic for classes and functions, which
// might also be refactored to merge.
DiagnoseIfInvalidRedecl(
context, DeclTokenKind<EntityT>(), existing_decl_entity->name_id,
context, DeclTokenKind<EntityT>(), prev_entity.name_id,
RedeclInfo(entity, SemIR::LocId(entity.latest_decl_id()), is_definition),
RedeclInfo(*existing_decl_entity,
SemIR::LocId(existing_decl_entity->latest_decl_id()),
existing_decl_entity->has_definition_started()),
/*prev_import_ir_id=*/SemIR::ImportIRId::None);
RedeclInfo(prev_entity, SemIR::LocId(prev_entity.latest_decl_id()),
prev_entity.has_definition_started()),
prev_import_ir_id);
if (is_definition && existing_decl_entity->has_definition_started()) {
if (is_definition && prev_entity.has_definition_started()) {
// DiagnoseIfInvalidRedecl would diagnose an error in this case, since we'd
// have two definitions. Given the declaration parts of the definitions
// match, we would be able to use the prior declaration for error recovery,
@@ -246,8 +293,19 @@ auto TryGetExistingDecl(Context& context, const NameComponent& name,
return std::nullopt;
}
if (is_definition) {
prev_entity.MergeDefinition(entity);
}
if (prev_import_ir_id.has_value()) {
prev_entity.first_owning_decl_id = entity.first_owning_decl_id;
ReplacePrevInstForMerge(context, entity.parent_scope_id,
prev_entity.name_id, entity.first_owning_decl_id);
}
// This is a matching redeclaration of an existing entity of the same type.
return existing_decl_inst;
return existing_decl_id.has_value() ? std::optional<SemIR::Inst>(prev)
: std::nullopt;
}
template auto TryGetExistingDecl(Context& context, const NameComponent& name,