Skip to content

Commit

Permalink
First iteration of completing and resolving facet types (carbon-langu…
Browse files Browse the repository at this point in the history
…age#4920)

* Add `RequireCompleteFacetType` and `ResolveFacetTypeImplWitness` to
`check::Context`. Goal was to move code from `impl.cpp` (mostly) without
functional changes.
* Complete type information is cached with the facet type, and is stored
in a `complete_facet_types()` table.
* Main functional change is to diagnose attempts to use a rewrite
constraint on an associated function. Some existing diagnostics have
been updated.
* Remove `check::Context::RequireDefinedType`:
  * For class types, use `RequireCompleteType`
  * For facet types, use `RequireCompleteFacetType`
* Introduce a `SemIR::SpecificInterface` to hold an interface and
specific id pair.
* Keep the specific interface ids in the impl object.
* Avoid some extra copies in `Dump` functions.
* Future work missing from this PR:
  * Resolving for member access or actions that require impl lookup.
  * Resolving rewrites constraints that refer to non-concrete values.
* Any support for adding implied constraints that result from a `where`
clause (though TODOs have been added).

---------

Co-authored-by: Josh L <josh11b@users.noreply.github.com>
Co-authored-by: Richard Smith <richard@metafoo.co.uk>
Co-authored-by: Dana Jansens <danakj@orodu.net>
  • Loading branch information
4 people authored Feb 19, 2025
1 parent 7c7e169 commit eb69d74
Show file tree
Hide file tree
Showing 46 changed files with 982 additions and 476 deletions.
3 changes: 3 additions & 0 deletions toolchain/base/value_store.h
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,9 @@ class CanonicalValueStore {
// Returns the value for an ID.
auto Get(IdT id) const -> ConstRefType { return values_.Get(id); }

// Returns the value for an ID. Changes should not affect hash or ==.
auto GetMutable(IdT id) -> RefType { return values_.Get(id); }

// Looks up the canonical ID for a value, or returns `None` if not in the
// store.
auto Lookup(ValueType value) const -> IdT;
Expand Down
2 changes: 2 additions & 0 deletions toolchain/check/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ cc_library(
"decl_name_stack.cpp",
"deduce.cpp",
"eval.cpp",
"facet_type.cpp",
"function.cpp",
"generic.cpp",
"global_init.cpp",
Expand Down Expand Up @@ -56,6 +57,7 @@ cc_library(
"deduce.h",
"diagnostic_helpers.h",
"eval.h",
"facet_type.h",
"function.h",
"generic.h",
"global_init.h",
Expand Down
1 change: 1 addition & 0 deletions toolchain/check/call.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "toolchain/check/context.h"
#include "toolchain/check/convert.h"
#include "toolchain/check/deduce.h"
#include "toolchain/check/facet_type.h"
#include "toolchain/check/function.h"
#include "toolchain/check/type.h"
#include "toolchain/diagnostics/format_providers.h"
Expand Down
2 changes: 2 additions & 0 deletions toolchain/check/context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@
#include "common/check.h"
#include "common/vlog.h"
#include "llvm/ADT/Sequence.h"
#include "toolchain/check/convert.h"
#include "toolchain/check/decl_name_stack.h"
#include "toolchain/check/eval.h"
#include "toolchain/check/generic.h"
#include "toolchain/check/generic_region_stack.h"
#include "toolchain/check/import.h"
#include "toolchain/check/import_ref.h"
#include "toolchain/check/inst_block_stack.h"
#include "toolchain/check/interface.h"
#include "toolchain/check/merge.h"
#include "toolchain/check/type_completion.h"
#include "toolchain/diagnostics/diagnostic_emitter.h"
Expand Down
3 changes: 3 additions & 0 deletions toolchain/check/context.h
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,9 @@ class Context {
auto facet_types() -> CanonicalValueStore<SemIR::FacetTypeId>& {
return sem_ir().facet_types();
}
auto complete_facet_types() -> ValueStore<SemIR::CompleteFacetTypeId>& {
return sem_ir().complete_facet_types();
}
auto impls() -> SemIR::ImplStore& { return sem_ir().impls(); }
auto generics() -> SemIR::GenericStore& { return sem_ir().generics(); }
auto specifics() -> SemIR::SpecificStore& { return sem_ir().specifics(); }
Expand Down
6 changes: 6 additions & 0 deletions toolchain/check/dump.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,12 @@ LLVM_DUMP_METHOD static auto Dump(const Context& context,
SemIR::Dump(context.sem_ir(), name_scope_id);
}

LLVM_DUMP_METHOD static auto Dump(
const Context& context, SemIR::CompleteFacetTypeId complete_facet_type_id)
-> void {
SemIR::Dump(context.sem_ir(), complete_facet_type_id);
}

LLVM_DUMP_METHOD static auto Dump(const Context& context,
SemIR::SpecificId specific_id) -> void {
SemIR::Dump(context.sem_ir(), specific_id);
Expand Down
29 changes: 19 additions & 10 deletions toolchain/check/eval.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

#include "toolchain/base/kind_switch.h"
#include "toolchain/check/diagnostic_helpers.h"
#include "toolchain/check/facet_type.h"
#include "toolchain/check/generic.h"
#include "toolchain/check/import_ref.h"
#include "toolchain/check/type.h"
Expand Down Expand Up @@ -462,19 +463,27 @@ static auto GetConstantValue(EvalContext& eval_context,
static auto GetConstantFacetTypeInfo(EvalContext& eval_context,
SemIR::FacetTypeId facet_type_id,
Phase* phase) -> SemIR::FacetTypeInfo {
SemIR::FacetTypeInfo info = eval_context.facet_types().Get(facet_type_id);
for (auto& interface : info.impls_constraints) {
interface.specific_id =
GetConstantValue(eval_context, interface.specific_id, phase);
}
for (auto& rewrite : info.rewrite_constraints) {
rewrite.lhs_const_id = eval_context.GetInContext(rewrite.lhs_const_id);
rewrite.rhs_const_id = eval_context.GetInContext(rewrite.rhs_const_id);
const auto& orig = eval_context.facet_types().Get(facet_type_id);
SemIR::FacetTypeInfo info;
info.impls_constraints.reserve(orig.impls_constraints.size());
for (const auto& interface : orig.impls_constraints) {
info.impls_constraints.push_back(
{.interface_id = interface.interface_id,
.specific_id =
GetConstantValue(eval_context, interface.specific_id, phase)});
}
info.rewrite_constraints.reserve(orig.rewrite_constraints.size());
for (const auto& rewrite : orig.rewrite_constraints) {
auto lhs_const_id = eval_context.GetInContext(rewrite.lhs_const_id);
auto rhs_const_id = eval_context.GetInContext(rewrite.rhs_const_id);
// `where` requirements using `.Self` should not be considered symbolic
UpdatePhaseIgnorePeriodSelf(eval_context, rewrite.lhs_const_id, phase);
UpdatePhaseIgnorePeriodSelf(eval_context, rewrite.rhs_const_id, phase);
UpdatePhaseIgnorePeriodSelf(eval_context, lhs_const_id, phase);
UpdatePhaseIgnorePeriodSelf(eval_context, rhs_const_id, phase);
info.rewrite_constraints.push_back(
{.lhs_const_id = lhs_const_id, .rhs_const_id = rhs_const_id});
}
// TODO: Process other requirements.
info.other_requirements = orig.other_requirements;
return info;
}

Expand Down
188 changes: 188 additions & 0 deletions toolchain/check/facet_type.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
// 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/facet_type.h"

#include "toolchain/check/convert.h"
#include "toolchain/check/import_ref.h"
#include "toolchain/check/inst.h"
#include "toolchain/check/interface.h"
#include "toolchain/check/type.h"

namespace Carbon::Check {

auto FacetTypeFromInterface(Context& context, SemIR::InterfaceId interface_id,
SemIR::SpecificId specific_id) -> SemIR::FacetType {
SemIR::FacetTypeId facet_type_id = context.facet_types().Add(
SemIR::FacetTypeInfo{.impls_constraints = {{interface_id, specific_id}},
.other_requirements = false});
return {.type_id = SemIR::TypeType::SingletonTypeId,
.facet_type_id = facet_type_id};
}

// Returns `true` if the `FacetAccessWitness` of `witness_id` matches
// `interface`.
static auto WitnessAccessMatchesInterface(
Context& context, SemIR::InstId witness_id,
const SemIR::SpecificInterface& interface) -> bool {
auto access = context.insts().GetAs<SemIR::FacetAccessWitness>(witness_id);
auto type_id = context.insts().Get(access.facet_value_inst_id).type_id();
auto facet_type = context.types().GetAs<SemIR::FacetType>(type_id);
const auto& facet_info = context.facet_types().Get(facet_type.facet_type_id);
if (auto impls = facet_info.TryAsSingleInterface()) {
return impls->interface_id == interface.interface_id &&
impls->specific_id == interface.specific_id;
}
return false;
}

auto ResolveFacetTypeImplWitness(
Context& context, SemIR::LocId witness_loc_id,
SemIR::InstId facet_type_inst_id, SemIR::InstId self_type_inst_id,
const SemIR::SpecificInterface& interface_to_witness,
SemIR::SpecificId self_specific_id) -> SemIR::InstId {
// TODO: Finish facet type resolution. This code currently only handles
// rewrite constraints that set associated constants to a concrete value.
// Need logic to topologically sort rewrites to respect dependencies, and
// afterwards reject duplicates that are not identical.

const auto& interface =
context.interfaces().Get(interface_to_witness.interface_id);
auto assoc_entities =
context.inst_blocks().Get(interface.associated_entities_id);
// TODO: When this function is used for things other than just impls, may want
// to only load the specific associated entities that are mentioned in rewrite
// rules.
for (auto decl_id : assoc_entities) {
LoadImportRef(context, decl_id);
}

SemIR::InstId witness_inst_id = SemIR::InstId::None;
llvm::MutableArrayRef<SemIR::InstId> table;
{
llvm::SmallVector<SemIR::InstId> empty_table(assoc_entities.size(),
SemIR::InstId::None);
auto table_id = context.inst_blocks().Add(empty_table);
table = context.inst_blocks().GetMutable(table_id);
witness_inst_id = AddInst<SemIR::ImplWitness>(
context, witness_loc_id,
{.type_id =
GetSingletonType(context, SemIR::WitnessType::SingletonInstId),
.elements_id = table_id,
.specific_id = self_specific_id});
}

auto facet_type_id =
context.types().GetTypeIdForTypeInstId(facet_type_inst_id);
CARBON_CHECK(facet_type_id != SemIR::ErrorInst::SingletonTypeId);
auto facet_type = context.types().GetAs<SemIR::FacetType>(facet_type_id);
// TODO: This is currently a copy because I'm not sure whether anything could
// cause the facet type store to resize before we are done with it.
auto facet_type_info = context.facet_types().Get(facet_type.facet_type_id);

for (auto rewrite : facet_type_info.rewrite_constraints) {
auto inst_id = context.constant_values().GetInstId(rewrite.lhs_const_id);
auto access = context.insts().GetAs<SemIR::ImplWitnessAccess>(inst_id);
if (!WitnessAccessMatchesInterface(context, access.witness_id,
interface_to_witness)) {
continue;
}
auto& table_entry = table[access.index.index];
if (table_entry == SemIR::ErrorInst::SingletonInstId) {
// Don't overwrite an error value. This prioritizes not generating
// multiple errors for one associated constant over picking a value
// for it to use to attempt recovery.
continue;
}
auto rewrite_value = rewrite.rhs_const_id;

if (table_entry.has_value()) {
auto const_id = context.constant_values().Get(table_entry);
if (const_id != rewrite_value &&
rewrite_value != SemIR::ErrorInst::SingletonConstantId) {
table_entry = SemIR::ErrorInst::SingletonInstId;
// TODO: Figure out how to print the two different values
// `const_id` & `rewrite_value` in the diagnostic
// message.
CARBON_DIAGNOSTIC(AssociatedConstantWithDifferentValues, Error,
"associated constant {0} given two different values",
SemIR::NameId);
auto decl_id = assoc_entities[access.index.index];
SemIR::NameId name_id = SemIR::NameId::None;
if (auto decl = context.insts().TryGetAs<SemIR::AssociatedConstantDecl>(
decl_id)) {
auto& assoc_const =
context.associated_constants().Get(decl->assoc_const_id);
name_id = assoc_const.name_id;
} else {
auto import_ref = context.insts().GetAs<SemIR::AnyImportRef>(decl_id);
const auto& entity_name =
context.entity_names().Get(import_ref.entity_name_id);
name_id = entity_name.name_id;
}
context.emitter().Emit(facet_type_inst_id,
AssociatedConstantWithDifferentValues, name_id);
}
continue;
}
auto decl_id = context.constant_values().GetConstantInstId(
assoc_entities[access.index.index]);
CARBON_CHECK(decl_id.has_value(), "Non-constant associated entity");
if (auto decl =
context.insts().TryGetAs<SemIR::AssociatedConstantDecl>(decl_id)) {
// If the associated constant has a symbolic type, convert the rewrite
// value to that type now we know the value of `Self`.
SemIR::TypeId assoc_const_type_id = decl->type_id;
if (context.types().GetConstantId(assoc_const_type_id).is_symbolic()) {
// Get the type of the associated constant in this interface with this
// value for `Self`.
assoc_const_type_id = GetTypeForSpecificAssociatedEntity(
context, facet_type_inst_id, interface_to_witness.specific_id,
decl_id, context.types().GetTypeIdForTypeInstId(self_type_inst_id),
witness_inst_id);
// Perform the conversion of the value to the type. We skipped this when
// forming the facet type because the type of the associated constant
// was symbolic.
auto converted_inst_id = ConvertToValueOfType(
context, context.insts().GetLocId(facet_type_inst_id),
context.constant_values().GetInstId(rewrite_value),
assoc_const_type_id);
rewrite_value = context.constant_values().Get(converted_inst_id);
// The result of conversion can be non-constant even if the original
// value was constant.
if (!rewrite_value.is_constant() &&
rewrite_value != SemIR::ErrorInst::SingletonConstantId) {
const auto& assoc_const =
context.associated_constants().Get(decl->assoc_const_id);
CARBON_DIAGNOSTIC(
AssociatedConstantNotConstantAfterConversion, Error,
"associated constant {0} given value that is not constant "
"after conversion to {1}",
SemIR::NameId, SemIR::TypeId);
context.emitter().Emit(facet_type_inst_id,
AssociatedConstantNotConstantAfterConversion,
assoc_const.name_id, assoc_const_type_id);
rewrite_value = SemIR::ErrorInst::SingletonConstantId;
}
}
} else {
if (decl_id != SemIR::ErrorInst::SingletonInstId) {
auto type_id = context.insts().Get(decl_id).type_id();
auto type_inst = context.types().GetAsInst(type_id);
auto fn_type = type_inst.As<SemIR::FunctionType>();
const auto& fn = context.functions().Get(fn_type.function_id);
CARBON_DIAGNOSTIC(RewriteForAssociatedFunction, Error,
"rewrite specified for associated function {0}",
SemIR::NameId);
context.emitter().Emit(facet_type_inst_id, RewriteForAssociatedFunction,
fn.name_id);
}
continue;
}
table_entry = context.constant_values().GetInstId(rewrite_value);
}
return witness_inst_id;
}

} // namespace Carbon::Check
45 changes: 45 additions & 0 deletions toolchain/check/facet_type.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// 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

#ifndef CARBON_TOOLCHAIN_CHECK_FACET_TYPE_H_
#define CARBON_TOOLCHAIN_CHECK_FACET_TYPE_H_

#include "toolchain/check/context.h"
#include "toolchain/sem_ir/ids.h"

namespace Carbon::Check {

// Create a FacetType typed instruction object consisting of a single
// interface. The `specific_id` specifies arguments in the case the interface is
// generic.
auto FacetTypeFromInterface(Context& context, SemIR::InterfaceId interface_id,
SemIR::SpecificId specific_id) -> SemIR::FacetType;

// Adds and returns an `ImplWitness` instruction (created with location set to
// `witness_loc_id`) that shows "`Self` type" of type "facet type" (the value of
// the `facet_type_inst_id` instruction) implements interface
// `interface_to_witness`, which must be an interface required by "facet type"
// (as determined by `RequireCompleteFacetType`). This witness reflects the
// values assigned to associated constant members of that interface by rewrite
// constraints in the facet type. `self_specific_id` will be the `specific_id`
// of the resulting witness.
//
// `self_type_inst_id` is an instruction that evaluates to the `Self` type of
// the facet type. For example, in `T:! X where ...`, we will bind the `.Self`
// of the `where` facet type to `T`, and in `(X where ...) where ...`, we will
// bind the inner `.Self` to the outer `.Self`.
//
// If the facet type contains a rewrite, we may have deferred converting the
// rewritten value to the type of the associated constant. That conversion
// will also be performed as part of resolution, and may depend on the
// `Self` type.
auto ResolveFacetTypeImplWitness(
Context& context, SemIR::LocId witness_loc_id,
SemIR::InstId facet_type_inst_id, SemIR::InstId self_type_inst_id,
const SemIR::SpecificInterface& interface_to_witness,
SemIR::SpecificId self_specific_id) -> SemIR::InstId;

} // namespace Carbon::Check

#endif // CARBON_TOOLCHAIN_CHECK_FACET_TYPE_H_
Loading

0 comments on commit eb69d74

Please sign in to comment.