Files
carbon-lang/toolchain/sem_ir/name_scope.cpp
T
Dana Jansens 7f7186c227 Extended name lookup replaces inner Self (#6632)
When doing name lookup into an extended scope of an interface or named
constraint, the containing scope has an inner `Self` facet which can
appear in the specific of the extended scope. For instance a constraint
`N` which requires an interface `Z(Self)`:

```js
constraint N {
  extend require impls Z(Self);
}
```

When doing member lookup into a facet constrained by `N`, we need to
find the specific interface `Z(...)` where the `Self` is replaced by the
self-type the member lookup is happening on in order for impl lookup to
find a witness later.

Inside that specific interface we repeat the name lookup to find an
associated entity. Then to produce a witness we perform impl lookup
against the specific interface that name lookup returned with the
self-type of the member access. So if we do member access into `A:! N`
for a member `F`, like `A.F`, we would be doing impl lookup with a query
self of `A` and looking for the interface `Z(...)` returned from name
lookup.

When impl lookup has a facet as the query self, which we do here as `A`,
it takes its type (a facet type) and identifies it to find all the
required interfaces, and it substitutes the query self into those
specific interfaces for `Self`. If the `Z(...)` we acquired from name
lookup is `Z(Self)` it will fail the lookup for `A as Z(Self)`, since in
the facet type of `A` it finds a witness for `Z(A)` instead.

Thus, we replace the inner `Self` in extended scopes, such as `N`, with
the self-type of the member access, which produces the extended scope
`Z(A)` for this example. This allows the impl lookup for `A as Z(A)` to
find a witness from the facet type of `A`.

In order to do this, we include an instruction for the inner self when
registering the extended scope. Then, when we find the extended scope in
name lookup, we can use its CompileTimeBindIndex to replace any instance
of that `Self` facet with a new facet. If the self-type of member access
is a type, we construct a FacetValue with an empty facet type that
refers to the type.
2026-01-21 17:48:18 +00:00

107 lines
3.2 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/sem_ir/name_scope.h"
#include <optional>
#include <utility>
#include "toolchain/sem_ir/file.h"
namespace Carbon::SemIR {
NameScopeStore::NameScopeStore(const File* file)
// 1 reserved untagged id because the Package NameScope is used across
// Files.
: file_(file), values_(file->check_ir_id(), 1) {}
auto NameScope::Print(llvm::raw_ostream& out) const -> void {
out << "{inst: " << inst_id_ << ", parent_scope: " << parent_scope_id_
<< ", has_error: " << (has_error_ ? "true" : "false");
out << ", extended_scopes: [";
llvm::ListSeparator scope_sep;
for (auto [id, inner_self_id] : extended_scopes_) {
if (inner_self_id.has_value()) {
out << scope_sep << "(" << id << ", inner self: " << inner_self_id << ")";
} else {
out << scope_sep << id;
}
}
out << "]";
out << ", names: {";
llvm::ListSeparator sep;
for (auto entry : names_) {
if (entry.result.is_poisoned()) {
continue;
}
out << sep << entry.name_id << ": " << entry.result.target_inst_id();
}
out << "}";
out << "}";
}
auto NameScope::AddRequired(Entry name_entry) -> void {
CARBON_CHECK(!name_entry.result.is_poisoned(),
"Cannot add a poisoned name: {0}.", name_entry.name_id);
auto add_name = [&] {
EntryId index(names_.size());
names_.push_back(name_entry);
return index;
};
auto result = name_map_.Insert(name_entry.name_id, add_name);
if (!result.is_inserted()) {
// A required name can overwrite poison.
auto& name = names_[result.value().index];
CARBON_CHECK(name.result.is_poisoned(), "Failed to add required name: {0}",
name_entry.name_id);
name = name_entry;
}
}
auto NameScope::LookupOrAdd(NameId name_id, InstId inst_id,
AccessKind access_kind)
-> std::pair<bool, EntryId> {
auto insert_result = name_map_.Insert(name_id, EntryId(names_.size()));
if (!insert_result.is_inserted()) {
return {false, EntryId(insert_result.value())};
}
names_.push_back({.name_id = name_id,
.result = ScopeLookupResult::MakeWrappedLookupResult(
inst_id, access_kind)});
return {true, EntryId(names_.size() - 1)};
}
auto NameScope::LookupOrPoison(LocId loc_id, NameId name_id)
-> std::optional<EntryId> {
if (!name_id.AsIdentifierId().has_value()) {
return Lookup(name_id);
}
auto insert_result = name_map_.Insert(name_id, EntryId(names_.size()));
if (insert_result.is_inserted()) {
names_.push_back({.name_id = name_id,
.result = ScopeLookupResult::MakePoisoned(loc_id)});
return std::nullopt;
}
return insert_result.value();
}
auto NameScopeStore::GetInstIfValid(NameScopeId scope_id) const
-> std::pair<InstId, std::optional<Inst>> {
if (!scope_id.has_value()) {
return {InstId::None, std::nullopt};
}
auto inst_id = Get(scope_id).inst_id();
if (!inst_id.has_value()) {
return {InstId::None, std::nullopt};
}
return {inst_id, file_->insts().Get(inst_id)};
}
} // namespace Carbon::SemIR