Files
carbon-lang/toolchain/check/merge.cpp
T
Lucile Rose Nihlen ca9e985fa8 Reconcile function default values between decl and def (#7665)
Updates the pattern matching code to support unspecified default values.
Adds logic to decl and def merge code to diagnose mismatches in defaults
if specified in both places, or if let entirely unspecified.

Per https://github.com/carbon-language/carbon-lang/pull/7521.
2026-09-08 20:41:58 +00:00

953 lines
39 KiB
C++

// 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
#include "toolchain/check/merge.h"
#include "toolchain/base/kind_switch.h"
#include "toolchain/check/eval.h"
#include "toolchain/check/import.h"
#include "toolchain/check/import_ref.h"
#include "toolchain/diagnostics/format_providers.h"
#include "toolchain/sem_ir/ids.h"
#include "toolchain/sem_ir/typed_insts.h"
namespace Carbon::Check {
CARBON_DIAGNOSTIC(RedeclPrevDecl, Note, "previously declared here");
// Diagnoses a redeclaration which is redundant.
static auto DiagnoseRedundant(Context& context, Lex::TokenKind decl_kind,
SemIR::NameId name_id, SemIR::LocId new_loc_id,
SemIR::LocId prev_loc_id) -> void {
CARBON_DIAGNOSTIC(RedeclRedundant, Error,
"redeclaration of `{0} {1}` is redundant", Lex::TokenKind,
SemIR::NameId);
context.emitter()
.Build(new_loc_id, RedeclRedundant, decl_kind, name_id)
.Note(prev_loc_id, RedeclPrevDecl)
.Emit();
}
// Diagnoses a redefinition.
static auto DiagnoseRedef(Context& context, Lex::TokenKind decl_kind,
SemIR::NameId name_id, SemIR::LocId new_loc_id,
SemIR::LocId prev_loc_id) -> void {
CARBON_DIAGNOSTIC(RedeclRedef, Error, "redefinition of `{0} {1}`",
Lex::TokenKind, SemIR::NameId);
CARBON_DIAGNOSTIC(RedeclPrevDef, Note, "previously defined here");
context.emitter()
.Build(new_loc_id, RedeclRedef, decl_kind, name_id)
.Note(prev_loc_id, RedeclPrevDef)
.Emit();
}
// Diagnoses an `extern` versus non-`extern` mismatch.
static auto DiagnoseExternMismatch(Context& context, Lex::TokenKind decl_kind,
SemIR::NameId name_id,
SemIR::LocId new_loc_id,
SemIR::LocId prev_loc_id) -> void {
CARBON_DIAGNOSTIC(RedeclExternMismatch, Error,
"redeclarations of `{0} {1}` must match use of `extern`",
Lex::TokenKind, SemIR::NameId);
context.emitter()
.Build(new_loc_id, RedeclExternMismatch, decl_kind, name_id)
.Note(prev_loc_id, RedeclPrevDecl)
.Emit();
}
// Diagnoses `extern library` declared in a library importing the owned entity.
static auto DiagnoseExternLibraryInImporter(Context& context,
Lex::TokenKind decl_kind,
SemIR::NameId name_id,
SemIR::LocId new_loc_id,
SemIR::LocId prev_loc_id) -> void {
CARBON_DIAGNOSTIC(ExternLibraryInImporter, Error,
"cannot declare imported `{0} {1}` as `extern library`",
Lex::TokenKind, SemIR::NameId);
context.emitter()
.Build(new_loc_id, ExternLibraryInImporter, decl_kind, name_id)
.Note(prev_loc_id, RedeclPrevDecl)
.Emit();
}
// Diagnoses `extern library` pointing to the wrong library.
static auto DiagnoseExternLibraryIncorrect(Context& context,
SemIR::LocId new_loc_id,
SemIR::LocId prev_loc_id) -> void {
CARBON_DIAGNOSTIC(
ExternLibraryIncorrect, Error,
"declaration in {0} doesn't match `extern library` declaration",
SemIR::LibraryNameId);
CARBON_DIAGNOSTIC(ExternLibraryExpected, Note,
"previously declared with `extern library` here");
context.emitter()
.Build(new_loc_id, ExternLibraryIncorrect, context.sem_ir().library_id())
.Note(prev_loc_id, ExternLibraryExpected)
.Emit();
}
auto DiagnoseExternRequiresDeclInApiFile(Context& context, SemIR::LocId loc_id)
-> void {
CARBON_DIAGNOSTIC(
ExternRequiresDeclInApiFile, Error,
"`extern` entities must have a declaration in the API file");
context.emitter().Emit(loc_id, ExternRequiresDeclInApiFile);
}
auto DiagnoseIfInvalidRedecl(Context& context, Lex::TokenKind decl_kind,
SemIR::NameId name_id, RedeclInfo new_decl,
RedeclInfo prev_decl,
SemIR::ImportIRId import_ir_id) -> void {
if (!import_ir_id.has_value()) {
// Check for disallowed redeclarations in the same file.
if (!new_decl.is_definition) {
DiagnoseRedundant(context, decl_kind, name_id, new_decl.loc_id,
prev_decl.loc_id);
return;
}
if (prev_decl.is_definition) {
DiagnoseRedef(context, decl_kind, name_id, new_decl.loc_id,
prev_decl.loc_id);
return;
}
if (prev_decl.is_extern != new_decl.is_extern) {
DiagnoseExternMismatch(context, decl_kind, name_id, new_decl.loc_id,
prev_decl.loc_id);
return;
}
return;
}
if (import_ir_id == SemIR::ImportIRId::ApiForImpl) {
// Check for disallowed redeclarations in the same library. Note that a
// forward declaration in the impl is allowed.
if (prev_decl.is_definition) {
if (new_decl.is_definition) {
DiagnoseRedef(context, decl_kind, name_id, new_decl.loc_id,
prev_decl.loc_id);
} else {
DiagnoseRedundant(context, decl_kind, name_id, new_decl.loc_id,
prev_decl.loc_id);
}
return;
}
if (prev_decl.is_extern != new_decl.is_extern) {
DiagnoseExternMismatch(context, decl_kind, name_id, new_decl.loc_id,
prev_decl.loc_id);
return;
}
return;
}
// Check for disallowed redeclarations cross-library.
if (new_decl.is_extern && context.sem_ir().is_impl()) {
// We continue after issuing the "missing API declaration" diagnostic,
// because it may still be helpful to note other issues with the
// declarations.
DiagnoseExternRequiresDeclInApiFile(context, new_decl.loc_id);
}
if (prev_decl.is_extern != new_decl.is_extern) {
DiagnoseExternMismatch(context, decl_kind, name_id, new_decl.loc_id,
prev_decl.loc_id);
return;
}
if (!prev_decl.extern_library_id.has_value()) {
if (new_decl.extern_library_id.has_value()) {
DiagnoseExternLibraryInImporter(context, decl_kind, name_id,
new_decl.loc_id, prev_decl.loc_id);
} else {
DiagnoseRedundant(context, decl_kind, name_id, new_decl.loc_id,
prev_decl.loc_id);
}
return;
}
if (prev_decl.extern_library_id != SemIR::LibraryNameId::Error &&
prev_decl.extern_library_id != context.sem_ir().library_id()) {
DiagnoseExternLibraryIncorrect(context, new_decl.loc_id, prev_decl.loc_id);
return;
}
}
auto ReplacePrevInstForMerge(Context& context, SemIR::NameScopeId scope_id,
SemIR::NameId name_id, SemIR::InstId new_inst_id)
-> void {
auto& scope = context.name_scopes().Get(scope_id);
auto entry_id = scope.Lookup(name_id);
if (entry_id) {
auto& result = scope.GetEntry(*entry_id).result;
result = SemIR::ScopeLookupResult::MakeWrappedLookupResult(
new_inst_id, result.access_kind());
}
}
// Returns true if there was an error in declaring the entity, which will have
// previously been diagnosed.
static auto EntityHasParamError(Context& context, const DeclParams& info)
-> bool {
for (auto param_patterns_id :
{info.implicit_param_patterns_id, info.param_patterns_id}) {
if (param_patterns_id.has_value() &&
param_patterns_id != SemIR::InstBlockId::Empty) {
for (auto param_id : context.inst_blocks().Get(param_patterns_id)) {
if (context.insts().Get(param_id).type_id() ==
SemIR::ErrorInst::TypeId) {
return true;
}
}
}
}
return false;
}
// Returns false if a param differs for a redeclaration. The caller is expected
// to provide a diagnostic.
static auto CheckRedeclParam(Context& context, bool is_implicit_param,
int32_t param_index,
SemIR::InstId orig_new_param_pattern_id,
SemIR::InstId orig_prev_param_pattern_id,
SemIR::SpecificId prev_specific_id, bool diagnose,
bool check_syntax) -> bool {
CARBON_DIAGNOSTIC(
RedeclParamPrevious, Note,
"previous declaration's corresponding {0:implicit |}parameter here",
Diagnostics::BoolAsSelect);
auto emit_general_diagnostic = [&]() {
if (!diagnose) {
return;
}
CARBON_DIAGNOSTIC(RedeclParamDiffers, Error,
"redeclaration differs at {0:implicit |}parameter {1}",
Diagnostics::BoolAsSelect, int32_t);
context.emitter()
.Build(orig_new_param_pattern_id, RedeclParamDiffers, is_implicit_param,
param_index + 1)
.Note(orig_prev_param_pattern_id, RedeclParamPrevious,
is_implicit_param)
.Emit();
};
struct PatternPair {
SemIR::InstId prev_id;
SemIR::InstId new_id;
};
llvm::SmallVector<PatternPair, 1> pattern_stack;
pattern_stack.push_back({.prev_id = orig_prev_param_pattern_id,
.new_id = orig_new_param_pattern_id});
// When `self_type_override_id` is specified, we need to disable type checking
// as soon as we determine this is a `self` parameter, and that decision needs
// to persist across the handling of any subpatterns.
bool check_type = true;
do {
auto patterns = pattern_stack.pop_back_val();
auto new_param_pattern = context.insts().Get(patterns.new_id);
auto prev_param_const_id = SemIR::GetConstantValueInSpecific(
context.sem_ir(), prev_specific_id, patterns.prev_id);
auto prev_param_pattern =
context.constant_values().GetInst(prev_param_const_id);
if (new_param_pattern.kind() != prev_param_pattern.kind()) {
emit_general_diagnostic();
return false;
}
// Conditionally checks for and diagnoses a type mismatch between the old
// and new parameter patterns. Returns false if a mismatch was found.
auto check_for_type_mismatch_with = [&](SemIR::TypeId prev_param_type_id) {
if (check_type && !context.types().AreEqualAcrossDeclarations(
new_param_pattern.type_id(), prev_param_type_id)) {
if (diagnose) {
CARBON_DIAGNOSTIC(
RedeclParamDiffersType, Error,
"type {3} of {0:implicit |}parameter {1} in "
"redeclaration differs from previous parameter type {2}",
Diagnostics::BoolAsSelect, int32_t, SemIR::TypeId, SemIR::TypeId);
context.emitter()
.Build(orig_new_param_pattern_id, RedeclParamDiffersType,
is_implicit_param, param_index + 1, prev_param_type_id,
new_param_pattern.type_id())
.Note(orig_prev_param_pattern_id, RedeclParamPrevious,
is_implicit_param)
.Emit();
}
return false;
}
return true;
};
auto check_for_type_mismatch = [&]() {
return check_for_type_mismatch_with(SemIR::GetTypeOfInstInSpecific(
context.sem_ir(), prev_specific_id, patterns.prev_id));
};
CARBON_KIND_SWITCH(new_param_pattern) {
case CARBON_KIND_ANY(SemIR::AnyLeafParamPattern, _): {
if (!check_for_type_mismatch()) {
return false;
}
break;
}
case CARBON_KIND_ANY(SemIR::AnyVarPattern, new_var_param_pattern): {
auto prev_var_param_pattern =
prev_param_pattern.As<SemIR::AnyVarPattern>();
pattern_stack.push_back(
{.prev_id = prev_var_param_pattern.subpattern_id,
.new_id = new_var_param_pattern.subpattern_id});
break;
}
case CARBON_KIND_ANY(SemIR::AnyBindingPattern, new_any_binding_pattern): {
auto prev_any_binding_pattern =
prev_param_pattern.As<SemIR::AnyBindingPattern>();
auto new_name_id = context.entity_names()
.Get(new_any_binding_pattern.entity_name_id)
.name_id;
auto prev_name_id = context.entity_names()
.Get(prev_any_binding_pattern.entity_name_id)
.name_id;
if (new_any_binding_pattern.kind ==
SemIR::WrapperBindingPattern::Kind) {
// The subpattern handling will take care of checking for type
// mismatch.
pattern_stack.push_back(
{.prev_id = prev_any_binding_pattern.subpattern_id,
.new_id = new_any_binding_pattern.subpattern_id});
} else if (!check_for_type_mismatch()) {
return false;
}
if (check_syntax && new_name_id != prev_name_id) {
emit_general_diagnostic();
return false;
}
break;
}
case CARBON_KIND(SemIR::DefaultValuePattern new_default_value_pattern): {
auto prev_default_value_pattern =
prev_param_pattern.As<SemIR::DefaultValuePattern>();
pattern_stack.push_back(
{.prev_id = prev_default_value_pattern.subpattern_id,
.new_id = new_default_value_pattern.subpattern_id});
// The node kind comparison should catch this on the mismatched patterns
// prior to this, so the indices should never mismatch.
CARBON_CHECK(prev_default_value_pattern.default_value_id.index ==
new_default_value_pattern.default_value_id.index);
break;
}
default: {
CARBON_FATAL("Unexpected inst kind in parameter pattern: {0}",
new_param_pattern.kind());
}
}
} while (!pattern_stack.empty());
return true;
}
// Returns false if the param refs differ for a redeclaration.
static auto CheckRedeclParams(Context& context, SemIR::LocId new_decl_loc_id,
SemIR::InstBlockId new_param_patterns_id,
SemIR::LocId prev_decl_loc_id,
SemIR::InstBlockId prev_param_patterns_id,
bool is_implicit_param,
SemIR::SpecificId prev_specific_id, bool diagnose,
bool check_syntax) -> bool {
// This will often occur for empty params.
if (new_param_patterns_id == prev_param_patterns_id) {
return true;
}
// If exactly one of the parameter lists was present, they differ. An absent
// parameter list (`None`) and a present-but-empty one (`Empty`) are
// intentionally treated as different, following the syntactic redeclaration
// matching design.
if (new_param_patterns_id.has_value() != prev_param_patterns_id.has_value()) {
if (!diagnose) {
return false;
}
CARBON_DIAGNOSTIC(RedeclParamListDiffers, Error,
"redeclaration differs because of "
"{1:|missing }{0:implicit |}parameter list",
Diagnostics::BoolAsSelect, Diagnostics::BoolAsSelect);
CARBON_DIAGNOSTIC(RedeclParamListPrevious, Note,
"previously declared "
"{1:with|without} {0:implicit |}parameter list",
Diagnostics::BoolAsSelect, Diagnostics::BoolAsSelect);
context.emitter()
.Build(new_decl_loc_id, RedeclParamListDiffers, is_implicit_param,
new_param_patterns_id.has_value())
.Note(prev_decl_loc_id, RedeclParamListPrevious, is_implicit_param,
prev_param_patterns_id.has_value())
.Emit();
return false;
}
CARBON_CHECK(new_param_patterns_id.has_value() &&
prev_param_patterns_id.has_value());
const auto new_param_pattern_ids =
context.inst_blocks().Get(new_param_patterns_id);
const auto prev_param_pattern_ids =
context.inst_blocks().Get(prev_param_patterns_id);
if (new_param_pattern_ids.size() != prev_param_pattern_ids.size()) {
if (!diagnose) {
return false;
}
CARBON_DIAGNOSTIC(
RedeclParamCountDiffers, Error,
"redeclaration differs because of {0:implicit |}parameter count of {1}",
Diagnostics::BoolAsSelect, int32_t);
CARBON_DIAGNOSTIC(
RedeclParamCountPrevious, Note,
"previously declared with {0:implicit |}parameter count of {1}",
Diagnostics::BoolAsSelect, int32_t);
context.emitter()
.Build(new_decl_loc_id, RedeclParamCountDiffers, is_implicit_param,
new_param_pattern_ids.size())
.Note(prev_decl_loc_id, RedeclParamCountPrevious, is_implicit_param,
prev_param_pattern_ids.size())
.Emit();
return false;
}
for (auto [index, new_param_pattern_id, prev_param_pattern_id] :
llvm::enumerate(new_param_pattern_ids, prev_param_pattern_ids)) {
if (!CheckRedeclParam(context, is_implicit_param, index,
new_param_pattern_id, prev_param_pattern_id,
prev_specific_id, diagnose, check_syntax)) {
return false;
}
}
return true;
}
// Returns true if the two nodes represent the same syntax.
// TODO: Detect raw identifiers (will require token changes).
static auto IsNodeSyntaxEqual(Context& context, Parse::NodeId new_node_id,
Parse::NodeId prev_node_id) -> bool {
if (context.parse_tree().node_kind(new_node_id) !=
context.parse_tree().node_kind(prev_node_id)) {
return false;
}
// TODO: Should there be a trivial way to check if we need to check spellings?
// Identifiers and literals need their text checked for cross-file matching,
// but not intra-file. Keywords and operators shouldn't need the token text
// examined at all.
auto new_spelling = context.tokens().GetTokenText(
context.parse_tree().node_token(new_node_id));
auto prev_spelling = context.tokens().GetTokenText(
context.parse_tree().node_token(prev_node_id));
return new_spelling == prev_spelling;
}
// Returns false if redeclaration parameter syntax doesn't match.
static auto CheckRedeclParamSyntax(Context& context,
Parse::NodeId new_first_param_node_id,
Parse::NodeId new_last_param_node_id,
Parse::NodeId prev_first_param_node_id,
Parse::NodeId prev_last_param_node_id,
bool diagnose) -> bool {
// Parse nodes may not always be available to compare.
// TODO: Support cross-file syntax checks. Right now imports provide
// `NodeId::None`, and we'll need to follow the declaration to its original
// file to get the parse tree.
if (!new_first_param_node_id.has_value() ||
!prev_first_param_node_id.has_value()) {
return true;
}
CARBON_CHECK(new_last_param_node_id.has_value(),
"new_last_param_node_id.has_value should match "
"new_first_param_node_id.has_value");
CARBON_CHECK(prev_last_param_node_id.has_value(),
"prev_last_param_node_id.has_value should match "
"prev_first_param_node_id.has_value");
Parse::Tree::PostorderIterator new_iter(new_first_param_node_id);
Parse::Tree::PostorderIterator new_end(new_last_param_node_id);
Parse::Tree::PostorderIterator prev_iter(prev_first_param_node_id);
Parse::Tree::PostorderIterator prev_end(prev_last_param_node_id);
// Done when one past the last node to check.
++new_end;
++prev_end;
// Compare up to the shortest length.
for (; new_iter != new_end && prev_iter != prev_end;
++new_iter, ++prev_iter) {
auto new_node_id = *new_iter;
auto new_node_kind = context.parse_tree().node_kind(new_node_id);
// Skip over "unused" markers.
if (new_node_kind == Parse::NodeKind::UnusedPattern) {
++new_iter;
new_node_id = *new_iter;
new_node_kind = context.parse_tree().node_kind(new_node_id);
}
auto prev_node_id = *prev_iter;
auto prev_node_kind = context.parse_tree().node_kind(prev_node_id);
if (prev_node_kind == Parse::NodeKind::UnusedPattern) {
++prev_iter;
prev_node_id = *prev_iter;
prev_node_kind = context.parse_tree().node_kind(prev_node_id);
}
if (!IsNodeSyntaxEqual(context, new_node_id, prev_node_id)) {
// The `self` parameter's type must be spelled the same way (`self` vs.
// `self: Self`) in a redeclaration as in the previous declaration. This
// is not a special case: like any other parameter-type spelling
// difference (e.g. `self: Self` vs. `self: C`), it falls through to the
// generic "syntax differs" diagnostic below, following the token-based
// redeclaration matching rule from proposal #3763.
//
// Skip difference if it is `Self as` vs. `as` in an `impl` declaration.
// https://github.com/carbon-language/carbon-lang/blob/trunk/proposals/p003763-matching-redeclarations.md#redeclarations
if (new_node_kind == Parse::NodeKind::ImplDefaultSelfAs &&
prev_node_kind == Parse::NodeKind::SelfTypeNameExpr &&
context.parse_tree().node_kind(prev_iter[1]) ==
Parse::NodeKind::ImplTypeAs) {
++prev_iter;
continue;
}
if (prev_node_kind == Parse::NodeKind::ImplDefaultSelfAs &&
new_node_kind == Parse::NodeKind::SelfTypeNameExpr &&
context.parse_tree().node_kind(new_iter[1]) ==
Parse::NodeKind::ImplTypeAs) {
++new_iter;
continue;
}
// We don't require default values to be repeated on re-declaration,
// so skip over any comparisons to unspecified default values.
if (prev_node_kind == Parse::NodeKind::DefaultValueUnspecified ||
new_node_kind == Parse::NodeKind::DefaultValueUnspecified) {
++prev_iter;
++new_iter;
continue;
}
if (!diagnose) {
return false;
}
CARBON_DIAGNOSTIC(RedeclParamSyntaxDiffers, Error,
"redeclaration syntax differs here");
CARBON_DIAGNOSTIC(RedeclParamSyntaxPrevious, Note,
"comparing with previous declaration here");
context.emitter()
.Build(new_node_id, RedeclParamSyntaxDiffers)
.Note(prev_node_id, RedeclParamSyntaxPrevious)
.Emit();
return false;
}
}
// The prefixes are the same, but the lengths may still be different. This is
// only relevant for `impl` declarations where the final bracketing node is
// not included in the range of nodes being compared, and in those cases
// `diagnose` is false.
if (new_iter != new_end) {
CARBON_CHECK(!diagnose);
return false;
} else if (prev_iter != prev_end) {
CARBON_CHECK(!diagnose);
return false;
}
return true;
}
auto CheckRedeclParamsMatch(Context& context, const DeclParams& new_entity,
const DeclParams& prev_entity,
SemIR::SpecificId prev_specific_id, bool diagnose,
bool check_syntax) -> bool {
if (EntityHasParamError(context, new_entity) ||
EntityHasParamError(context, prev_entity)) {
return false;
}
if (!CheckRedeclParams(
context, new_entity.loc_id, new_entity.implicit_param_patterns_id,
prev_entity.loc_id, prev_entity.implicit_param_patterns_id,
/*is_implicit_param=*/true, prev_specific_id, diagnose,
check_syntax)) {
return false;
}
if (!CheckRedeclParams(context, new_entity.loc_id,
new_entity.param_patterns_id, prev_entity.loc_id,
prev_entity.param_patterns_id,
/*is_implicit_param=*/false, prev_specific_id,
diagnose, check_syntax)) {
return false;
}
if (check_syntax &&
!CheckRedeclParamSyntax(context, new_entity.first_param_node_id,
new_entity.last_param_node_id,
prev_entity.first_param_node_id,
prev_entity.last_param_node_id, diagnose)) {
return false;
}
return true;
}
// Fills the previous class id, type id, and import ir id.
static auto FillPrevEntityInfo(Context& context,
const SemIR::ImportIRInst& import_ir_inst,
SemIR::Inst decl_val,
SemIR::ClassId& prev_entity_id,
SemIR::TypeId& prev_type_id,
SemIR::ImportIRId& prev_import_ir_id) -> void {
// 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 (!import_ir->insts().Is<SemIR::ClassDecl>(import_ir_inst.inst_id())) {
return;
}
if (auto class_type = decl_val.TryAs<SemIR::ClassType>()) {
prev_entity_id = class_type->class_id;
prev_type_id = SemIR::TypeId::None;
prev_import_ir_id = import_ir_inst.ir_id();
} else if (auto generic_class_type =
context.types().TryGetAs<SemIR::GenericClassType>(
decl_val.type_id())) {
prev_entity_id = generic_class_type->class_id;
prev_type_id = SemIR::TypeId::None;
prev_import_ir_id = import_ir_inst.ir_id();
}
}
// Fills the previous function id, type id, and import ir id.
static auto FillPrevEntityInfo(Context& context,
const SemIR::ImportIRInst& import_ir_inst,
SemIR::Inst decl_val,
SemIR::FunctionId& prev_entity_id,
SemIR::TypeId& prev_type_id,
SemIR::ImportIRId& prev_import_ir_id) -> void {
// 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 (!import_ir->insts().Is<SemIR::FunctionDecl>(import_ir_inst.inst_id())) {
return;
}
if (auto struct_value = decl_val.TryAs<SemIR::StructValue>()) {
if (auto function_type = context.types().TryGetAs<SemIR::FunctionType>(
struct_value->type_id)) {
prev_entity_id = function_type->function_id;
prev_type_id = struct_value->type_id;
prev_import_ir_id = import_ir_inst.ir_id();
}
}
}
// Fills the previous interface id, type id, and import ir id.
static auto FillPrevEntityInfo(Context& context,
const SemIR::ImportIRInst& import_ir_inst,
SemIR::Inst decl_val,
SemIR::InterfaceId& prev_entity_id,
SemIR::TypeId& prev_type_id,
SemIR::ImportIRId& prev_import_ir_id) -> void {
// 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 (!import_ir->insts().Is<SemIR::InterfaceDecl>(import_ir_inst.inst_id())) {
return;
}
if (auto facet_type = decl_val.TryAs<SemIR::FacetType>()) {
auto declared_facet_type =
context.declared_facet_types().Get(facet_type->declared_facet_type_id);
prev_entity_id = declared_facet_type.extend_constraints[0].interface_id;
prev_type_id = SemIR::TypeId::None;
prev_import_ir_id = import_ir_inst.ir_id();
}
}
// Fills the previous named constraint id, type id, and import ir id.
static auto FillPrevEntityInfo(Context& context,
const SemIR::ImportIRInst& import_ir_inst,
SemIR::Inst decl_val,
SemIR::NamedConstraintId& prev_entity_id,
SemIR::TypeId& prev_type_id,
SemIR::ImportIRId& prev_import_ir_id) -> void {
// 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 (!import_ir->insts().Is<SemIR::NamedConstraintDecl>(
import_ir_inst.inst_id())) {
return;
}
if (auto facet_type = decl_val.TryAs<SemIR::FacetType>()) {
auto declared_facet_type =
context.declared_facet_types().Get(facet_type->declared_facet_type_id);
prev_entity_id =
declared_facet_type.extend_named_constraints[0].named_constraint_id;
prev_type_id = SemIR::TypeId::None;
prev_import_ir_id = import_ir_inst.ir_id();
}
}
// Updates the default values in `prev_function` to include any of those not
// previously specified and that are now specified in `new_function`.
static auto MergeFunctionParamDefaultValues(Context& context,
SemIR::Function& prev_function,
const SemIR::Function& new_function)
-> void {
CARBON_CHECK(prev_function.call_param_default_values_id.has_value() ==
new_function.call_param_default_values_id.has_value());
if (!prev_function.call_param_default_values_id.has_value()) {
return;
}
auto prev_value_inst_ids =
context.inst_blocks().Get(prev_function.call_param_default_values_id);
auto new_value_inst_ids =
context.inst_blocks().Get(new_function.call_param_default_values_id);
CARBON_CHECK(prev_value_inst_ids.size() == new_value_inst_ids.size());
llvm::SmallVector<SemIR::InstId> merged_value_inst_ids;
bool merge_has_new_info = false;
merged_value_inst_ids.reserve(prev_value_inst_ids.size());
for (size_t i = 0; i < prev_value_inst_ids.size(); ++i) {
bool had_value =
!context.insts().Is<SemIR::UnspecifiedValue>(prev_value_inst_ids[i]);
auto merged_id = had_value ? prev_value_inst_ids[i] : new_value_inst_ids[i];
merge_has_new_info |=
!had_value && !context.insts().Is<SemIR::UnspecifiedValue>(merged_id);
merged_value_inst_ids.push_back(merged_id);
}
if (merge_has_new_info) {
auto merged_block_id = context.inst_blocks().Add(merged_value_inst_ids);
prev_function.call_param_default_values_id = merged_block_id;
}
}
template <typename EntityT>
auto TryMergeRedecl(Context& context,
const DeclNameStack::NameContext& name_context,
std::optional<SemIR::ScopeLookupResult> lookup_result,
MergeRedeclEntityInfo<EntityT> entity_info,
bool is_definition) -> bool {
constexpr bool IsClass = std::is_same_v<EntityT, SemIR::Class>;
constexpr bool IsFunction = std::is_same_v<EntityT, SemIR::Function>;
constexpr bool IsInterface = std::is_same_v<EntityT, SemIR::Interface>;
constexpr bool IsNamedConstraint =
std::is_same_v<EntityT, SemIR::NamedConstraint>;
if constexpr (IsFunction) {
CARBON_CHECK(!lookup_result.has_value());
// Diagnose if we are declaring a poisoned name. However, don't diagnose
// at impl scope: if the name was referenced before being declared, we
// will have produced an error already.
if (name_context.state == DeclNameStack::NameContext::State::Poisoned) {
if (!context.name_scopes().InstIs<SemIR::ImplDecl>(
name_context.parent_scope_id)) {
DiagnosePoisonedName(context, name_context.name_id_for_new_inst(),
name_context.poisoning_loc_id,
name_context.loc_id);
}
return false;
}
} else if constexpr (IsClass || IsInterface || IsNamedConstraint) {
CARBON_CHECK(lookup_result.has_value());
if (lookup_result->is_poisoned()) {
DiagnosePoisonedName(context, name_context.name_id_for_new_inst(),
lookup_result->poisoning_loc_id(),
name_context.loc_id);
return false;
}
if (!lookup_result->is_found()) {
return false;
}
} else {
CARBON_FATAL("Unhandled entity type.");
}
auto prev_id = lookup_result ? lookup_result->target_inst_id()
: name_context.prev_inst_id();
if (!prev_id.has_value()) {
return false;
}
auto prev = context.insts().Get(prev_id);
auto prev_entity_id = MergeRedeclEntityInfo<EntityT>::EntityIdT::None;
auto prev_type_id = SemIR::TypeId::None;
auto prev_import_ir_id = SemIR::ImportIRId::None;
CARBON_KIND_SWITCH(prev) {
case CARBON_KIND(SemIR::AssociatedEntity assoc_entity): {
if constexpr (IsFunction) {
// This is a function in an interface definition scope.
auto function_decl =
context.insts().GetAs<SemIR::FunctionDecl>(assoc_entity.decl_id);
prev_entity_id = function_decl.function_id;
prev_type_id = function_decl.type_id;
}
break;
}
case CARBON_KIND(SemIR::ClassDecl class_decl): {
if constexpr (IsClass) {
prev_entity_id = class_decl.class_id;
}
break;
}
case CARBON_KIND(SemIR::FunctionDecl function_decl): {
if constexpr (IsFunction) {
prev_entity_id = function_decl.function_id;
prev_type_id = function_decl.type_id;
}
break;
}
case CARBON_KIND(SemIR::InterfaceDecl interface_decl): {
if constexpr (IsInterface) {
prev_entity_id = interface_decl.interface_id;
}
break;
}
case CARBON_KIND(SemIR::NamedConstraintDecl named_constraint_decl): {
if constexpr (IsNamedConstraint) {
prev_entity_id = named_constraint_decl.named_constraint_id;
}
break;
}
case CARBON_KIND(SemIR::ImportRefLoaded import_ref): {
// TODO: Should we get canonical inst for all entity types?
auto import_ir_inst = [&]() -> SemIR::ImportIRInst {
if constexpr (IsClass || IsInterface || IsNamedConstraint) {
return context.import_ir_insts().Get(import_ref.import_ir_inst_id);
} else if constexpr (IsFunction) {
return GetCanonicalImportIRInst(context, prev_id);
} else {
CARBON_FATAL("Unhandled entity type.");
}
}();
auto decl_val = context.insts().Get(
context.constant_values().GetConstantInstId(prev_id));
FillPrevEntityInfo(context, import_ir_inst, decl_val, prev_entity_id,
prev_type_id, prev_import_ir_id);
break;
}
default: {
break;
}
}
if (!prev_entity_id.has_value()) {
// This is a redeclaration with a different entity kind.
DiagnoseDuplicateName(context, name_context.name_id, name_context.loc_id,
SemIR::LocId(prev_id));
return false;
}
auto& prev_entity = [&]() -> EntityT& {
if constexpr (IsClass) {
return context.classes().Get(prev_entity_id);
} else if constexpr (IsFunction) {
return context.functions().Get(prev_entity_id);
} else if constexpr (IsInterface) {
return context.interfaces().Get(prev_entity_id);
} else if constexpr (IsNamedConstraint) {
return context.named_constraints().Get(prev_entity_id);
} else {
CARBON_FATAL("Unhandled entity type.");
}
}();
if constexpr (IsClass || IsInterface || IsNamedConstraint) {
if (!CheckRedeclParamsMatch(context, DeclParams(entity_info.new_entity),
DeclParams(prev_entity))) {
// Mismatch is diagnosed already if found.
return false;
}
} else if constexpr (IsFunction) {
if (!CheckFunctionTypeMatches(context, entity_info.new_entity,
prev_entity)) {
// Mismatch is diagnosed already if found.
return false;
}
} else {
CARBON_FATAL("Unhandled entity type.");
}
DiagnoseIfInvalidRedecl(
context, MergeRedeclEntityInfo<EntityT>::DeclTokenKind,
prev_entity.name_id,
RedeclInfo(entity_info.new_entity,
SemIR::LocId(entity_info.new_entity.latest_decl_id()),
is_definition),
RedeclInfo(prev_entity, SemIR::LocId(prev_entity.latest_decl_id()),
prev_entity.has_definition_started()),
prev_import_ir_id);
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,
// except that having two definitions causes larger problems for generics.
// All interfaces (and named constraints) are generic with an implicit Self
// compile time binding.
return false;
}
if (!prev_entity.first_owning_decl_id.has_value()) {
prev_entity.first_owning_decl_id =
entity_info.new_entity.first_owning_decl_id;
}
if (is_definition) {
prev_entity.MergeDefinition(entity_info.new_entity);
if constexpr (IsFunction) {
MergeFunctionParamDefaultValues(context, prev_entity,
entity_info.new_entity);
}
}
auto replace_prev_inst = prev_import_ir_id.has_value();
if constexpr (IsClass) {
replace_prev_inst |=
prev_entity.is_extern && !entity_info.new_entity.is_extern;
}
if (replace_prev_inst) {
ReplacePrevInstForMerge(context, entity_info.new_entity.parent_scope_id,
prev_entity.name_id,
entity_info.new_entity.first_owning_decl_id);
}
// When merging, use the existing entity rather than adding a new one.
if constexpr (IsClass) {
// TODO: Fix `extern` logic. It doesn't work correctly, but doesn't seem
// worth ripping out because existing code may incrementally help.
entity_info.new_entity_decl.class_id = prev_entity_id;
entity_info.new_entity_decl.type_id = prev.type_id();
// TODO: Validate that the redeclaration doesn't set an access modifier.
} else if constexpr (IsFunction) {
entity_info.new_entity_decl.function_id = prev_entity_id;
entity_info.new_entity_decl.type_id = prev_type_id;
} else if constexpr (IsInterface) {
entity_info.new_entity_decl.interface_id = prev_entity_id;
entity_info.new_entity_decl.type_id = prev.type_id();
} else if constexpr (IsNamedConstraint) {
entity_info.new_entity_decl.named_constraint_id = prev_entity_id;
entity_info.new_entity_decl.type_id = prev.type_id();
} else {
CARBON_FATAL("Unhandled entity type.");
}
return true;
}
template auto TryMergeRedecl(Context&, const DeclNameStack::NameContext&,
std::optional<SemIR::ScopeLookupResult>,
MergeRedeclEntityInfo<SemIR::Class>, bool) -> bool;
template auto TryMergeRedecl(Context&, const DeclNameStack::NameContext&,
std::optional<SemIR::ScopeLookupResult>,
MergeRedeclEntityInfo<SemIR::Function>, bool)
-> bool;
template auto TryMergeRedecl(Context&, const DeclNameStack::NameContext&,
std::optional<SemIR::ScopeLookupResult>,
MergeRedeclEntityInfo<SemIR::Interface>, bool)
-> bool;
template auto TryMergeRedecl(Context&, const DeclNameStack::NameContext&,
std::optional<SemIR::ScopeLookupResult>,
MergeRedeclEntityInfo<SemIR::NamedConstraint>,
bool) -> bool;
} // namespace Carbon::Check