Skip to content

Commit

Permalink
When performing name lookup, determine the generic instance within wh…
Browse files Browse the repository at this point in the history
…ich the lookup result was found. (#4118)

Require types into which qualified lookup is performed to be completely
defined. Eventually this will trigger substitution into the definition
for generic types.
  • Loading branch information
zygoloid authored Jul 10, 2024
1 parent a4ef5dd commit 6d3c915
Show file tree
Hide file tree
Showing 20 changed files with 422 additions and 89 deletions.
89 changes: 67 additions & 22 deletions toolchain/check/context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ auto Context::LookupNameInDecl(SemIR::LocId loc_id, SemIR::NameId name_id,
}

auto Context::LookupUnqualifiedName(Parse::NodeId node_id,
SemIR::NameId name_id) -> SemIR::InstId {
SemIR::NameId name_id) -> LookupResult {
// TODO: Check for shadowed lookup results.

// Find the results from ancestor lexical scopes. These will be combined with
Expand All @@ -272,22 +272,33 @@ auto Context::LookupUnqualifiedName(Parse::NodeId node_id,
scope_stack().LookupInLexicalScopes(name_id);

// Walk the non-lexical scopes and perform lookups into each of them.
for (auto [index, name_scope_id] : llvm::reverse(non_lexical_scopes)) {
if (auto non_lexical_result =
LookupQualifiedName(node_id, name_id, name_scope_id,
/*required=*/false);
non_lexical_result.is_valid()) {
for (auto [index, lookup_scope_id] : llvm::reverse(non_lexical_scopes)) {
// Enclosing non-lexical scopes cannot correspond to an instance of a
// generic, so it's always OK to pass an invalid generic instance here.
// Note that the lookup result might still be found in an extended scope, so
// it can be in a generic instance.
if (auto non_lexical_result = LookupQualifiedName(
node_id, name_id,
{.name_scope_id = lookup_scope_id,
.instance_id = SemIR::GenericInstanceId::Invalid},
/*required=*/false);
non_lexical_result.inst_id.is_valid()) {
return non_lexical_result;
}
}

if (lexical_result.is_valid()) {
return lexical_result;
// A lexical scope never needs an associated generic instance. If there's a
// lexically enclosing generic, then it also encloses the point of use of
// the name.
return {.instance_id = SemIR::GenericInstanceId::Invalid,
.inst_id = lexical_result};
}

// We didn't find anything at all.
DiagnoseNameNotFound(node_id, name_id);
return SemIR::InstId::BuiltinError;
return {.instance_id = SemIR::GenericInstanceId::Invalid,
.inst_id = SemIR::InstId::BuiltinError};
}

// Handles lookup through the import_ir_scopes for LookupNameInExactScope.
Expand Down Expand Up @@ -391,29 +402,36 @@ auto Context::LookupNameInExactScope(SemIRLoc loc, SemIR::NameId name_id,
}

auto Context::LookupQualifiedName(Parse::NodeId node_id, SemIR::NameId name_id,
SemIR::NameScopeId scope_id, bool required)
-> SemIR::InstId {
llvm::SmallVector<SemIR::NameScopeId> scope_ids = {scope_id};
auto result_id = SemIR::InstId::Invalid;
LookupScope scope, bool required)
-> LookupResult {
llvm::SmallVector<LookupScope> scopes = {scope};
LookupResult result = {.instance_id = SemIR::GenericInstanceId::Invalid,
.inst_id = SemIR::InstId::Invalid};
bool has_error = false;

// Walk this scope and, if nothing is found here, the scopes it extends.
while (!scope_ids.empty()) {
auto scope_id = scope_ids.pop_back_val();
while (!scopes.empty()) {
auto [scope_id, instance_id] = scopes.pop_back_val();
const auto& scope = name_scopes().Get(scope_id);
has_error |= scope.has_error;

auto scope_result_id =
LookupNameInExactScope(node_id, name_id, scope_id, scope);
if (!scope_result_id.is_valid()) {
// Nothing found in this scope: also look in its extended scopes.
auto extended = llvm::reverse(scope.extended_scopes);
scope_ids.append(extended.begin(), extended.end());
auto extended = scope.extended_scopes;
scopes.reserve(scopes.size() + extended.size());
for (auto extended_id : llvm::reverse(extended)) {
// TODO: Track a constant describing the extended scope, and substitute
// into it to determine its corresponding generic instance.
scopes.push_back({.name_scope_id = extended_id,
.instance_id = SemIR::GenericInstanceId::Invalid});
}
continue;
}

// If this is our second lookup result, diagnose an ambiguity.
if (result_id.is_valid()) {
if (result.inst_id.is_valid()) {
// TODO: This is currently not reachable because the only scope that can
// extend is a class scope, and it can only extend a single base class.
// Add test coverage once this is possible.
Expand All @@ -423,20 +441,23 @@ auto Context::LookupQualifiedName(Parse::NodeId node_id, SemIR::NameId name_id,
SemIR::NameId);
emitter_->Emit(node_id, NameAmbiguousDueToExtend, name_id);
// TODO: Add notes pointing to the scopes.
return SemIR::InstId::BuiltinError;
return {.instance_id = SemIR::GenericInstanceId::Invalid,
.inst_id = SemIR::InstId::BuiltinError};
}

result_id = scope_result_id;
result.inst_id = scope_result_id;
result.instance_id = instance_id;
}

if (required && !result_id.is_valid()) {
if (required && !result.inst_id.is_valid()) {
if (!has_error) {
DiagnoseNameNotFound(node_id, name_id);
}
return SemIR::InstId::BuiltinError;
return {.instance_id = SemIR::GenericInstanceId::Invalid,
.inst_id = SemIR::InstId::BuiltinError};
}

return result_id;
return result;
}

// Returns the scope of the Core package, or Invalid if it's not found.
Expand Down Expand Up @@ -779,6 +800,7 @@ class TypeCompleter {
}
return false;
}
// TODO: Trigger generic resolution here for a generic class.
Push(class_info.object_repr_id);
break;
}
Expand Down Expand Up @@ -1042,6 +1064,29 @@ auto Context::TryToCompleteType(
return TypeCompleter(*this, diagnoser).Complete(type_id);
}

auto Context::TryToDefineType(
SemIR::TypeId type_id,
std::optional<llvm::function_ref<auto()->DiagnosticBuilder>> diagnoser)
-> bool {
if (!TryToCompleteType(type_id, diagnoser)) {
return false;
}

if (auto interface = types().TryGetAs<SemIR::InterfaceType>(type_id)) {
auto interface_id = interface->interface_id;
if (!interfaces().Get(interface_id).is_defined()) {
auto builder = (*diagnoser)();
NoteUndefinedInterface(interface_id, builder);
builder.Emit();
return false;
}

// TODO: Trigger generic resolution here for a generic instance.
}

return true;
}

auto Context::GetTypeIdForTypeConstant(SemIR::ConstantId constant_id)
-> SemIR::TypeId {
CARBON_CHECK(constant_id.is_constant())
Expand Down
35 changes: 32 additions & 3 deletions toolchain/check/context.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,24 @@

namespace Carbon::Check {

// Information about a scope in which we can perform name lookup.
struct LookupScope {
// The name scope in which names are searched.
SemIR::NameScopeId name_scope_id;
// The generic instance for the name scope, or `Invalid` if the name scope is
// not an instance of a generic.
SemIR::GenericInstanceId instance_id;
};

// A result produced by name lookup.
struct LookupResult {
// The generic instance in which the lookup result was found. `Invalid` if the
// result was not found in a generic instance.
SemIR::GenericInstanceId instance_id;
// The declaration that was found by name lookup.
SemIR::InstId inst_id;
};

// Context and shared functionality for semantics handlers.
class Context {
public:
Expand Down Expand Up @@ -122,7 +140,7 @@ class Context {

// Performs an unqualified name lookup, returning the referenced instruction.
auto LookupUnqualifiedName(Parse::NodeId node_id, SemIR::NameId name_id)
-> SemIR::InstId;
-> LookupResult;

// Performs a name lookup in a specified scope, returning the referenced
// instruction. Does not look into extended scopes. Returns an invalid
Expand All @@ -134,8 +152,8 @@ class Context {
// Performs a qualified name lookup in a specified scope and in scopes that
// it extends, returning the referenced instruction.
auto LookupQualifiedName(Parse::NodeId node_id, SemIR::NameId name_id,
SemIR::NameScopeId scope_id, bool required = true)
-> SemIR::InstId;
LookupScope scope, bool required = true)
-> LookupResult;

// Returns the instruction corresponding to a name in the core package, or
// BuiltinError if not found.
Expand Down Expand Up @@ -239,6 +257,17 @@ class Context {
std::optional<llvm::function_ref<auto()->DiagnosticBuilder>> diagnoser =
std::nullopt) -> bool;

// Attempts to complete and define the type `type_id`. Returns `true` if the
// type is defined, or `false` if no definition is available. A defined type
// has known members.
//
// This is the same as `TryToCompleteType` except for interfaces, which are
// complete before they are fully defined.
auto TryToDefineType(
SemIR::TypeId type_id,
std::optional<llvm::function_ref<auto()->DiagnosticBuilder>> diagnoser =
std::nullopt) -> bool;

// Returns the type `type_id` as a complete type, or produces an incomplete
// type error and returns an error type. This is a convenience wrapper around
// TryToCompleteType.
Expand Down
7 changes: 7 additions & 0 deletions toolchain/check/generic.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,13 @@ auto GetConstantInInstance(Context& context,
return context.constant_values().Get(symbolic.inst_id);
}

auto GetConstantValueInInstance(Context& context,
SemIR::GenericInstanceId instance_id,
SemIR::InstId inst_id) -> SemIR::ConstantId {
return GetConstantInInstance(context, instance_id,
context.constant_values().Get(inst_id));
}

auto GetTypeInInstance(Context& context, SemIR::GenericInstanceId instance_id,
SemIR::TypeId type_id) -> SemIR::TypeId {
auto const_id = context.types().GetConstantId(type_id);
Expand Down
9 changes: 9 additions & 0 deletions toolchain/check/generic.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,15 @@ auto GetConstantInInstance(Context& context,
SemIR::GenericInstanceId instance_id,
SemIR::ConstantId const_id) -> SemIR::ConstantId;

// Gets the substituted constant value of an instruction within a specified
// instance of a generic. Note that this does not perform substitution, and will
// return `Invalid` if the substituted constant value is not yet known.
//
// TODO: Move this to sem_ir so that lowering can use it.
auto GetConstantValueInInstance(Context& context,
SemIR::GenericInstanceId instance_id,
SemIR::InstId inst_id) -> SemIR::ConstantId;

// Gets the substituted value of a type within a specified instance of a
// generic. Note that this does not perform substitution, and will return
// `Invalid` if the substituted type is not yet known.
Expand Down
12 changes: 6 additions & 6 deletions toolchain/check/handle_name.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,15 +80,15 @@ static auto GetIdentifierAsName(Context& context, Parse::NodeId node_id)
// lookup.
static auto HandleNameAsExpr(Context& context, Parse::NodeId node_id,
SemIR::NameId name_id) -> bool {
auto value_id = context.LookupUnqualifiedName(node_id, name_id);
// TODO: Lookup should produce this.
auto instance_id = SemIR::GenericInstanceId::Invalid;
auto value = context.insts().Get(value_id);
auto type_id = GetTypeInInstance(context, instance_id, value.type_id());
auto result = context.LookupUnqualifiedName(node_id, name_id);
auto value = context.insts().Get(result.inst_id);
auto type_id =
GetTypeInInstance(context, result.instance_id, value.type_id());
CARBON_CHECK(type_id.is_valid()) << "Missing type for " << value;

context.AddInstAndPush<SemIR::NameRef>(
node_id, {.type_id = type_id, .name_id = name_id, .value_id = value_id});
node_id,
{.type_id = type_id, .name_id = name_id, .value_id = result.inst_id});
return true;
}

Expand Down
Loading

0 comments on commit 6d3c915

Please sign in to comment.