mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-09-24 22:02:23 +01:00
Following up on discussion from #3948, doing a general rename of "enclosing scope" to "parent scope" (and "enclosing scopes" to "ancestor scopes"). The intent is to improve understandability and collide less with C++ terminology for "enclosing scope". Note this changes most uses of "enclosing", but leaves behind a few like "enclosing function" and "enclosing block". Note this does create some "parent class" mentions for "adapt" and "var" (the class they're within), which is maybe unfortunate, but we'd probably say "base class" if we meant inheritance so perhaps that's okay. Along the same lines, these are the only `parent_class` uses I see now, and we do have a few `base_class`.
382 lines
15 KiB
C++
382 lines
15 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/decl_name_stack.h"
|
|
|
|
#include "toolchain/base/kind_switch.h"
|
|
#include "toolchain/check/context.h"
|
|
#include "toolchain/check/diagnostic_helpers.h"
|
|
#include "toolchain/diagnostics/diagnostic.h"
|
|
#include "toolchain/sem_ir/ids.h"
|
|
|
|
namespace Carbon::Check {
|
|
|
|
auto DeclNameStack::NameContext::prev_inst_id() -> SemIR::InstId {
|
|
switch (state) {
|
|
case NameContext::State::Error:
|
|
// The name is invalid and a diagnostic has already been emitted.
|
|
return SemIR::InstId::Invalid;
|
|
|
|
case NameContext::State::Empty:
|
|
CARBON_FATAL()
|
|
<< "Name is missing, not expected to call existing_inst_id (but "
|
|
"that may change based on error handling).";
|
|
|
|
case NameContext::State::Resolved:
|
|
case NameContext::State::ResolvedNonScope:
|
|
return resolved_inst_id;
|
|
|
|
case NameContext::State::Unresolved:
|
|
return SemIR::InstId::Invalid;
|
|
|
|
case NameContext::State::Finished:
|
|
CARBON_FATAL() << "Finished state should only be used internally";
|
|
}
|
|
}
|
|
|
|
auto DeclNameStack::MakeEmptyNameContext() -> NameContext {
|
|
return NameContext{
|
|
.initial_scope_index = context_->scope_stack().PeekIndex(),
|
|
.parent_scope_id = context_->scope_stack().PeekNameScopeId()};
|
|
}
|
|
|
|
auto DeclNameStack::MakeUnqualifiedName(SemIR::LocId loc_id,
|
|
SemIR::NameId name_id) -> NameContext {
|
|
NameContext context = MakeEmptyNameContext();
|
|
ApplyAndLookupName(context, loc_id, name_id, /*is_unqualified=*/true);
|
|
return context;
|
|
}
|
|
|
|
auto DeclNameStack::PushScopeAndStartName() -> void {
|
|
decl_name_stack_.push_back(MakeEmptyNameContext());
|
|
|
|
// Create a scope for any parameters introduced in this name.
|
|
context_->scope_stack().Push();
|
|
}
|
|
|
|
auto DeclNameStack::FinishName(const NameComponent& name) -> NameContext {
|
|
CARBON_CHECK(decl_name_stack_.back().state != NameContext::State::Finished)
|
|
<< "Finished name twice";
|
|
|
|
ApplyAndLookupName(decl_name_stack_.back(), name.name_loc_id, name.name_id,
|
|
/*is_unqualified=*/false);
|
|
|
|
NameContext result = decl_name_stack_.back();
|
|
decl_name_stack_.back().state = NameContext::State::Finished;
|
|
return result;
|
|
}
|
|
|
|
auto DeclNameStack::FinishImplName() -> NameContext {
|
|
CARBON_CHECK(decl_name_stack_.back().state == NameContext::State::Empty)
|
|
<< "Impl has a name";
|
|
|
|
NameContext result = decl_name_stack_.back();
|
|
decl_name_stack_.back().state = NameContext::State::Finished;
|
|
return result;
|
|
}
|
|
|
|
auto DeclNameStack::PopScope() -> void {
|
|
CARBON_CHECK(decl_name_stack_.back().state == NameContext::State::Finished)
|
|
<< "Missing call to FinishName before PopScope";
|
|
context_->scope_stack().PopTo(decl_name_stack_.back().initial_scope_index);
|
|
decl_name_stack_.pop_back();
|
|
}
|
|
|
|
auto DeclNameStack::Suspend() -> SuspendedName {
|
|
CARBON_CHECK(decl_name_stack_.back().state == NameContext::State::Finished)
|
|
<< "Missing call to FinishName before Suspend";
|
|
SuspendedName result = {.name_context = decl_name_stack_.pop_back_val(),
|
|
.scopes = {}};
|
|
auto scope_index = result.name_context.initial_scope_index;
|
|
auto& scope_stack = context_->scope_stack();
|
|
while (scope_stack.PeekIndex() > scope_index) {
|
|
result.scopes.push_back(scope_stack.Suspend());
|
|
}
|
|
CARBON_CHECK(scope_stack.PeekIndex() == scope_index)
|
|
<< "Scope index " << scope_index << " does not enclose the current scope "
|
|
<< scope_stack.PeekIndex();
|
|
return result;
|
|
}
|
|
|
|
auto DeclNameStack::Restore(SuspendedName sus) -> void {
|
|
// The parent state must be the same when a name is restored.
|
|
CARBON_CHECK(context_->scope_stack().PeekIndex() ==
|
|
sus.name_context.initial_scope_index)
|
|
<< "Name restored at the wrong position in the name stack.";
|
|
|
|
// clang-tidy warns that the `std::move` below has no effect. While that's
|
|
// true, this `move` defends against `NameContext` growing more state later.
|
|
// NOLINTNEXTLINE(performance-move-const-arg)
|
|
decl_name_stack_.push_back(std::move(sus.name_context));
|
|
for (auto& suspended_scope : llvm::reverse(sus.scopes)) {
|
|
context_->scope_stack().Restore(std::move(suspended_scope));
|
|
}
|
|
}
|
|
|
|
auto DeclNameStack::AddName(NameContext name_context, SemIR::InstId target_id)
|
|
-> void {
|
|
switch (name_context.state) {
|
|
case NameContext::State::Error:
|
|
return;
|
|
|
|
case NameContext::State::Unresolved:
|
|
if (!name_context.parent_scope_id.is_valid()) {
|
|
context_->AddNameToLookup(name_context.unresolved_name_id, target_id);
|
|
} else {
|
|
auto& name_scope =
|
|
context_->name_scopes().Get(name_context.parent_scope_id);
|
|
if (name_context.has_qualifiers) {
|
|
auto inst = context_->insts().Get(name_scope.inst_id);
|
|
if (!inst.Is<SemIR::Namespace>()) {
|
|
// TODO: Point at the declaration for the scoped entity.
|
|
CARBON_DIAGNOSTIC(
|
|
QualifiedDeclOutsideScopeEntity, Error,
|
|
"Out-of-line declaration requires a declaration in "
|
|
"scoped entity.");
|
|
context_->emitter().Emit(name_context.loc_id,
|
|
QualifiedDeclOutsideScopeEntity);
|
|
}
|
|
}
|
|
|
|
// Exports are only tracked when the declaration is at the file-level
|
|
// scope. Otherwise, it's in some other entity, such as a class.
|
|
if (name_context.initial_scope_index == ScopeIndex::Package) {
|
|
context_->AddExport(target_id);
|
|
}
|
|
|
|
auto [_, success] = name_scope.names.insert(
|
|
{name_context.unresolved_name_id, target_id});
|
|
CARBON_CHECK(success)
|
|
<< "Duplicate names should have been resolved previously: "
|
|
<< name_context.unresolved_name_id << " in "
|
|
<< name_context.parent_scope_id;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
CARBON_FATAL() << "Should not be calling AddName";
|
|
break;
|
|
}
|
|
}
|
|
|
|
auto DeclNameStack::AddNameOrDiagnoseDuplicate(NameContext name_context,
|
|
SemIR::InstId target_id)
|
|
-> void {
|
|
if (auto id = name_context.prev_inst_id(); id.is_valid()) {
|
|
context_->DiagnoseDuplicateName(target_id, id);
|
|
} else {
|
|
AddName(name_context, target_id);
|
|
}
|
|
}
|
|
|
|
auto DeclNameStack::LookupOrAddName(NameContext name_context,
|
|
SemIR::InstId target_id) -> SemIR::InstId {
|
|
if (auto id = name_context.prev_inst_id(); id.is_valid()) {
|
|
return id;
|
|
}
|
|
AddName(name_context, target_id);
|
|
return SemIR::InstId::Invalid;
|
|
}
|
|
|
|
auto DeclNameStack::ApplyNameQualifier(const NameComponent& name) -> void {
|
|
if (name.implicit_params_id.is_valid() || name.params_id.is_valid()) {
|
|
context_->TODO(name.params_loc_id, "name qualifier with parameters");
|
|
}
|
|
|
|
auto& name_context = decl_name_stack_.back();
|
|
ApplyAndLookupName(name_context, name.name_loc_id, name.name_id,
|
|
/*is_unqualified=*/false);
|
|
name_context.has_qualifiers = true;
|
|
if (!CheckValidAsQualifier(name_context)) {
|
|
name_context.state = NameContext::State::Error;
|
|
}
|
|
}
|
|
|
|
auto DeclNameStack::ApplyAndLookupName(NameContext& name_context,
|
|
SemIR::LocId loc_id,
|
|
SemIR::NameId name_id,
|
|
bool is_unqualified) -> void {
|
|
// The location of the name is the location of the last name token we've
|
|
// processed so far.
|
|
name_context.loc_id = loc_id;
|
|
|
|
// Don't perform any more lookups after we hit an error. We still track the
|
|
// final name, though.
|
|
if (name_context.state == NameContext::State::Error) {
|
|
name_context.unresolved_name_id = name_id;
|
|
return;
|
|
}
|
|
|
|
// For identifier nodes, we need to perform a lookup on the identifier.
|
|
auto resolved_inst_id = context_->LookupNameInDecl(
|
|
name_context.loc_id, name_id, name_context.parent_scope_id);
|
|
if (!resolved_inst_id.is_valid()) {
|
|
// Invalid indicates an unresolved name. Store it and return.
|
|
name_context.unresolved_name_id = name_id;
|
|
name_context.state = NameContext::State::Unresolved;
|
|
return;
|
|
} else {
|
|
// Store the resolved instruction and continue for the target scope
|
|
// update.
|
|
name_context.resolved_inst_id = resolved_inst_id;
|
|
}
|
|
|
|
// Enter the scope of the existing entity.
|
|
UpdateScopeIfNeeded(name_context, is_unqualified);
|
|
}
|
|
|
|
// Push a scope corresponding to a name qualifier. For example, for
|
|
//
|
|
// fn Class(T:! type).F(n: i32)
|
|
//
|
|
// we will push the scope for `Class(T:! type)` between the scope containing the
|
|
// declaration of `T` and the scope containing the declaration of `n`.
|
|
static auto PushNameQualifierScope(Context& context,
|
|
SemIR::InstId scope_inst_id,
|
|
SemIR::NameScopeId scope_id,
|
|
bool has_error = false) -> void {
|
|
// If the qualifier has no parameters, we don't need to keep around a
|
|
// parameter scope.
|
|
context.scope_stack().PopIfEmpty();
|
|
|
|
context.scope_stack().Push(scope_inst_id, scope_id, has_error);
|
|
|
|
// Enter a parameter scope in case the qualified name itself has parameters.
|
|
context.scope_stack().Push();
|
|
}
|
|
|
|
auto DeclNameStack::UpdateScopeIfNeeded(NameContext& name_context,
|
|
bool is_unqualified) -> void {
|
|
// This will only be reached for resolved instructions. We update the target
|
|
// scope based on the resolved type.
|
|
CARBON_KIND_SWITCH(context_->insts().Get(name_context.resolved_inst_id)) {
|
|
case CARBON_KIND(SemIR::ClassDecl resolved_inst): {
|
|
const auto& class_info = context_->classes().Get(resolved_inst.class_id);
|
|
if (class_info.is_defined()) {
|
|
name_context.state = NameContext::State::Resolved;
|
|
name_context.parent_scope_id = class_info.scope_id;
|
|
if (!is_unqualified) {
|
|
PushNameQualifierScope(*context_, name_context.resolved_inst_id,
|
|
class_info.scope_id);
|
|
}
|
|
} else {
|
|
name_context.state = NameContext::State::ResolvedNonScope;
|
|
}
|
|
break;
|
|
}
|
|
case CARBON_KIND(SemIR::InterfaceDecl resolved_inst): {
|
|
const auto& interface_info =
|
|
context_->interfaces().Get(resolved_inst.interface_id);
|
|
if (interface_info.is_defined()) {
|
|
name_context.state = NameContext::State::Resolved;
|
|
name_context.parent_scope_id = interface_info.scope_id;
|
|
if (!is_unqualified) {
|
|
PushNameQualifierScope(*context_, name_context.resolved_inst_id,
|
|
interface_info.scope_id);
|
|
}
|
|
} else {
|
|
name_context.state = NameContext::State::ResolvedNonScope;
|
|
}
|
|
break;
|
|
}
|
|
case CARBON_KIND(SemIR::Namespace resolved_inst): {
|
|
auto scope_id = resolved_inst.name_scope_id;
|
|
name_context.state = NameContext::State::Resolved;
|
|
name_context.parent_scope_id = scope_id;
|
|
auto& scope = context_->name_scopes().Get(scope_id);
|
|
if (scope.is_closed_import) {
|
|
CARBON_DIAGNOSTIC(QualifiedDeclOutsidePackage, Error,
|
|
"Imported packages cannot be used for declarations.");
|
|
CARBON_DIAGNOSTIC(QualifiedDeclOutsidePackageSource, Note,
|
|
"Package imported here.");
|
|
context_->emitter()
|
|
.Build(name_context.loc_id, QualifiedDeclOutsidePackage)
|
|
.Note(scope.inst_id, QualifiedDeclOutsidePackageSource)
|
|
.Emit();
|
|
// Only error once per package.
|
|
scope.is_closed_import = false;
|
|
}
|
|
if (!is_unqualified) {
|
|
PushNameQualifierScope(*context_, name_context.resolved_inst_id,
|
|
scope_id,
|
|
context_->name_scopes().Get(scope_id).has_error);
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
name_context.state = NameContext::State::ResolvedNonScope;
|
|
break;
|
|
}
|
|
}
|
|
|
|
auto DeclNameStack::CheckValidAsQualifier(const NameContext& name_context)
|
|
-> bool {
|
|
switch (name_context.state) {
|
|
case NameContext::State::Error:
|
|
// Already in an error state, so return without examining.
|
|
return false;
|
|
|
|
case NameContext::State::Resolved:
|
|
return true;
|
|
|
|
case NameContext::State::Empty:
|
|
CARBON_FATAL() << "No qualifier to resolve";
|
|
|
|
case NameContext::State::Finished:
|
|
CARBON_FATAL() << "Added a qualifier after calling FinishName";
|
|
|
|
case NameContext::State::Unresolved:
|
|
// Because more qualifiers were found, we diagnose that the earlier
|
|
// qualifier failed to resolve.
|
|
context_->DiagnoseNameNotFound(name_context.loc_id,
|
|
name_context.unresolved_name_id);
|
|
return false;
|
|
|
|
case NameContext::State::ResolvedNonScope: {
|
|
// Because more qualifiers were found, we diagnose that the earlier
|
|
// qualifier didn't resolve to a scoped entity.
|
|
if (auto class_decl = context_->insts().TryGetAs<SemIR::ClassDecl>(
|
|
name_context.resolved_inst_id)) {
|
|
CARBON_DIAGNOSTIC(QualifiedDeclInIncompleteClassScope, Error,
|
|
"Cannot declare a member of incomplete class `{0}`.",
|
|
SemIR::TypeId);
|
|
auto builder = context_->emitter().Build(
|
|
name_context.loc_id, QualifiedDeclInIncompleteClassScope,
|
|
context_->classes().Get(class_decl->class_id).self_type_id);
|
|
context_->NoteIncompleteClass(class_decl->class_id, builder);
|
|
builder.Emit();
|
|
} else if (auto interface_decl =
|
|
context_->insts().TryGetAs<SemIR::InterfaceDecl>(
|
|
name_context.resolved_inst_id)) {
|
|
CARBON_DIAGNOSTIC(
|
|
QualifiedDeclInUndefinedInterfaceScope, Error,
|
|
"Cannot declare a member of undefined interface `{0}`.",
|
|
std::string);
|
|
auto builder = context_->emitter().Build(
|
|
name_context.loc_id, QualifiedDeclInUndefinedInterfaceScope,
|
|
context_->sem_ir().StringifyTypeExpr(
|
|
context_->sem_ir()
|
|
.constant_values()
|
|
.Get(name_context.resolved_inst_id)
|
|
.inst_id()));
|
|
context_->NoteUndefinedInterface(interface_decl->interface_id, builder);
|
|
builder.Emit();
|
|
} else {
|
|
CARBON_DIAGNOSTIC(QualifiedNameInNonScope, Error,
|
|
"Name qualifiers are only allowed for entities that "
|
|
"provide a scope.");
|
|
CARBON_DIAGNOSTIC(QualifiedNameNonScopeEntity, Note,
|
|
"Referenced non-scope entity declared here.");
|
|
context_->emitter()
|
|
.Build(name_context.loc_id, QualifiedNameInNonScope)
|
|
.Note(name_context.resolved_inst_id, QualifiedNameNonScopeEntity)
|
|
.Emit();
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace Carbon::Check
|