Files
carbon-lang/toolchain/check/merge.cpp
T
Boaz Bricknerandjonmeow daba2c72cf [NFC] Convert NameScope from struct to class (#4623)
This is a preparation change for adding name poisoning support
(https://github.com/carbon-language/carbon-lang/issues/4622), which is
expected to require more elaborate logic around NameScope since a name
can be not defined yet, defined, or poisoned.

The API separates looking up a name from getting the full entry since we
have cases where the entries are invalidated between the time we're
looking for the name and when we access (and sometimes modify) the
entry.

This change has the following benefits:
* `names` and `name_map` are internal to `NameScope` and are guaranteed
to match.
* `extended_scopes` and `import_ir_scopes` can not be manipulated (only
new scopes can be added).
* `inst_id`, `name_id` and `parent_scope_id` are constants.
* `has_error` can only be mutated from false to true.

---------

Co-authored-by: jonmeow <jperkins@google.com>
2024-12-06 21:50:50 +00:00

458 lines
18 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/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, SemIRLoc new_loc,
SemIRLoc prev_loc) -> void {
CARBON_DIAGNOSTIC(RedeclRedundant, Error,
"redeclaration of `{0} {1}` is redundant", Lex::TokenKind,
SemIR::NameId);
context.emitter()
.Build(new_loc, RedeclRedundant, decl_kind, name_id)
.Note(prev_loc, RedeclPrevDecl)
.Emit();
}
// Diagnoses a redefinition.
static auto DiagnoseRedef(Context& context, Lex::TokenKind decl_kind,
SemIR::NameId name_id, SemIRLoc new_loc,
SemIRLoc prev_loc) -> 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, RedeclRedef, decl_kind, name_id)
.Note(prev_loc, RedeclPrevDef)
.Emit();
}
// Diagnoses an `extern` versus non-`extern` mismatch.
static auto DiagnoseExternMismatch(Context& context, Lex::TokenKind decl_kind,
SemIR::NameId name_id, SemIRLoc new_loc,
SemIRLoc prev_loc) -> void {
CARBON_DIAGNOSTIC(RedeclExternMismatch, Error,
"redeclarations of `{0} {1}` must match use of `extern`",
Lex::TokenKind, SemIR::NameId);
context.emitter()
.Build(new_loc, RedeclExternMismatch, decl_kind, name_id)
.Note(prev_loc, 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,
SemIRLoc new_loc, SemIRLoc prev_loc)
-> void {
CARBON_DIAGNOSTIC(ExternLibraryInImporter, Error,
"cannot declare imported `{0} {1}` as `extern library`",
Lex::TokenKind, SemIR::NameId);
context.emitter()
.Build(new_loc, ExternLibraryInImporter, decl_kind, name_id)
.Note(prev_loc, RedeclPrevDecl)
.Emit();
}
// Diagnoses `extern library` pointing to the wrong library.
static auto DiagnoseExternLibraryIncorrect(Context& context, SemIRLoc new_loc,
SemIRLoc prev_loc) -> 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, ExternLibraryIncorrect, context.sem_ir().library_id())
.Note(prev_loc, ExternLibraryExpected)
.Emit();
}
auto DiagnoseExternRequiresDeclInApiFile(Context& context, SemIRLoc loc)
-> void {
CARBON_DIAGNOSTIC(
ExternRequiresDeclInApiFile, Error,
"`extern` entities must have a declaration in the API file");
context.emitter().Build(loc, ExternRequiresDeclInApiFile).Emit();
}
// Checks to see if a structurally valid redeclaration is allowed in context.
// These all still merge.
auto CheckIsAllowedRedecl(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.is_valid()) {
// Check for disallowed redeclarations in the same file.
if (!new_decl.is_definition) {
DiagnoseRedundant(context, decl_kind, name_id, new_decl.loc,
prev_decl.loc);
return;
}
if (prev_decl.is_definition) {
DiagnoseRedef(context, decl_kind, name_id, new_decl.loc, prev_decl.loc);
return;
}
if (prev_decl.is_extern != new_decl.is_extern) {
DiagnoseExternMismatch(context, decl_kind, name_id, new_decl.loc,
prev_decl.loc);
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, prev_decl.loc);
} else {
DiagnoseRedundant(context, decl_kind, name_id, new_decl.loc,
prev_decl.loc);
}
return;
}
if (prev_decl.is_extern != new_decl.is_extern) {
DiagnoseExternMismatch(context, decl_kind, name_id, new_decl.loc,
prev_decl.loc);
return;
}
return;
}
// Check for disallowed redeclarations cross-library.
if (new_decl.is_extern && context.IsImplFile()) {
// 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);
}
if (prev_decl.is_extern != new_decl.is_extern) {
DiagnoseExternMismatch(context, decl_kind, name_id, new_decl.loc,
prev_decl.loc);
return;
}
if (!prev_decl.extern_library_id.is_valid()) {
if (new_decl.extern_library_id.is_valid()) {
DiagnoseExternLibraryInImporter(context, decl_kind, name_id, new_decl.loc,
prev_decl.loc);
} else {
DiagnoseRedundant(context, decl_kind, name_id, new_decl.loc,
prev_decl.loc);
}
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, prev_decl.loc);
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) {
scope.GetEntry(*entry_id).inst_id = new_inst_id;
}
}
// 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.is_valid() &&
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::SingletonTypeId) {
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 new_param_pattern_id,
SemIR::InstId prev_param_pattern_id,
SemIR::SpecificId prev_specific_id, bool diagnose)
-> bool {
// TODO: Consider differentiating between type and name mistakes. For now,
// taking the simpler approach because I also think we may want to refactor
// params.
CARBON_DIAGNOSTIC(
RedeclParamPrevious, Note,
"previous declaration's corresponding {0:implicit |}parameter here",
BoolAsSelect);
auto emit_diagnostic = [&]() {
if (!diagnose) {
return;
}
CARBON_DIAGNOSTIC(RedeclParamDiffers, Error,
"redeclaration differs at {0:implicit |}parameter {1}",
BoolAsSelect, int32_t);
context.emitter()
.Build(new_param_pattern_id, RedeclParamDiffers, is_implicit_param,
param_index + 1)
.Note(prev_param_pattern_id, RedeclParamPrevious, is_implicit_param)
.Emit();
};
auto new_param_pattern = context.insts().Get(new_param_pattern_id);
auto prev_param_pattern = context.insts().Get(prev_param_pattern_id);
if (new_param_pattern.kind() != prev_param_pattern.kind()) {
emit_diagnostic();
return false;
}
auto prev_param_type_id = SemIR::GetTypeInSpecific(
context.sem_ir(), prev_specific_id, prev_param_pattern.type_id());
if (!context.types().AreEqualAcrossDeclarations(new_param_pattern.type_id(),
prev_param_type_id)) {
if (!diagnose) {
return false;
}
CARBON_DIAGNOSTIC(RedeclParamDiffersType, Error,
"type {3} of {0:implicit |}parameter {1} in "
"redeclaration differs from previous parameter type {2}",
BoolAsSelect, int32_t, SemIR::TypeId, SemIR::TypeId);
context.emitter()
.Build(new_param_pattern_id, RedeclParamDiffersType, is_implicit_param,
param_index + 1, prev_param_type_id, new_param_pattern.type_id())
.Note(prev_param_pattern_id, RedeclParamPrevious, is_implicit_param)
.Emit();
return false;
}
if (new_param_pattern.Is<SemIR::AddrPattern>()) {
new_param_pattern = context.insts().Get(
new_param_pattern.As<SemIR::AddrPattern>().inner_id);
prev_param_pattern = context.insts().Get(
prev_param_pattern.As<SemIR::AddrPattern>().inner_id);
if (new_param_pattern.kind() != prev_param_pattern.kind()) {
emit_diagnostic();
return false;
}
}
new_param_pattern = context.insts().Get(
new_param_pattern.As<SemIR::ValueParamPattern>().subpattern_id);
prev_param_pattern = context.insts().Get(
prev_param_pattern.As<SemIR::ValueParamPattern>().subpattern_id);
if (new_param_pattern.kind() != prev_param_pattern.kind()) {
emit_diagnostic();
return false;
}
auto new_entity_name = context.entity_names().Get(
new_param_pattern.As<SemIR::AnyBindingPattern>().entity_name_id);
auto prev_entity_name = context.entity_names().Get(
prev_param_pattern.As<SemIR::AnyBindingPattern>().entity_name_id);
if (new_entity_name.name_id != prev_entity_name.name_id) {
emit_diagnostic();
return false;
}
return true;
}
// Returns false if the param refs differ for a redeclaration.
static auto CheckRedeclParams(Context& context, SemIRLoc new_decl_loc,
SemIR::InstBlockId new_param_patterns_id,
SemIRLoc prev_decl_loc,
SemIR::InstBlockId prev_param_patterns_id,
bool is_implicit_param,
SemIR::SpecificId prev_specific_id, bool diagnose)
-> 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.
if (new_param_patterns_id.is_valid() != prev_param_patterns_id.is_valid()) {
if (!diagnose) {
return false;
}
CARBON_DIAGNOSTIC(RedeclParamListDiffers, Error,
"redeclaration differs because of "
"{1:|missing }{0:implicit |}parameter list",
BoolAsSelect, BoolAsSelect);
CARBON_DIAGNOSTIC(RedeclParamListPrevious, Note,
"previously declared "
"{1:with|without} {0:implicit |}parameter list",
BoolAsSelect, BoolAsSelect);
context.emitter()
.Build(new_decl_loc, RedeclParamListDiffers, is_implicit_param,
new_param_patterns_id.is_valid())
.Note(prev_decl_loc, RedeclParamListPrevious, is_implicit_param,
prev_param_patterns_id.is_valid())
.Emit();
return false;
}
CARBON_CHECK(new_param_patterns_id.is_valid() &&
prev_param_patterns_id.is_valid());
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}",
BoolAsSelect, int32_t);
CARBON_DIAGNOSTIC(
RedeclParamCountPrevious, Note,
"previously declared with {0:implicit |}parameter count of {1}",
BoolAsSelect, int32_t);
context.emitter()
.Build(new_decl_loc, RedeclParamCountDiffers, is_implicit_param,
new_param_pattern_ids.size())
.Note(prev_decl_loc, 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)) {
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 invalid
// nodes, and we'll need to follow the declaration to its original file to
// get the parse tree.
if (!new_first_param_node_id.is_valid() ||
!prev_first_param_node_id.is_valid()) {
return true;
}
CARBON_CHECK(new_last_param_node_id.is_valid(),
"new_last_param_node_id.is_valid should match "
"new_first_param_node_id.is_valid");
CARBON_CHECK(prev_last_param_node_id.is_valid(),
"prev_last_param_node_id.is_valid should match "
"prev_first_param_node_id.is_valid");
auto new_range = Parse::Tree::PostorderIterator::MakeRange(
new_first_param_node_id, new_last_param_node_id);
auto prev_range = Parse::Tree::PostorderIterator::MakeRange(
prev_first_param_node_id, prev_last_param_node_id);
// zip is using the shortest range. If they differ in length, there should be
// some difference inside the range because the range includes parameter
// brackets. As a consequence, we don't explicitly handle different range
// sizes here.
for (auto [new_node_id, prev_node_id] : llvm::zip(new_range, prev_range)) {
if (!IsNodeSyntaxEqual(context, new_node_id, prev_node_id)) {
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;
}
}
return true;
}
auto CheckRedeclParamsMatch(Context& context, const DeclParams& new_entity,
const DeclParams& prev_entity,
SemIR::SpecificId prev_specific_id,
bool check_syntax, bool diagnose) -> bool {
if (EntityHasParamError(context, new_entity) ||
EntityHasParamError(context, prev_entity)) {
return false;
}
if (!CheckRedeclParams(
context, new_entity.loc, new_entity.implicit_param_patterns_id,
prev_entity.loc, prev_entity.implicit_param_patterns_id,
/*is_implicit_param=*/true, prev_specific_id, diagnose)) {
return false;
}
if (!CheckRedeclParams(context, new_entity.loc, new_entity.param_patterns_id,
prev_entity.loc, prev_entity.param_patterns_id,
/*is_implicit_param=*/false, prev_specific_id,
diagnose)) {
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;
}
} // namespace Carbon::Check