Skip to content

Commit

Permalink
Facet member access (carbon-language#4371)
Browse files Browse the repository at this point in the history
Adds `FacetAccessWitness` instruction and uses it in `member_access.cpp`
to support accessing members of facets. Still to do: interface witness
access is producing runtime values when it should produce symbolic
values.

---------

Co-authored-by: Josh L <[email protected]>
  • Loading branch information
2 people authored and bricknerb committed Nov 28, 2024
1 parent d7ddf36 commit 4767d5b
Show file tree
Hide file tree
Showing 12 changed files with 309 additions and 142 deletions.
16 changes: 15 additions & 1 deletion toolchain/check/eval.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1627,7 +1627,21 @@ static auto TryEvalInstInContext(EvalContext& eval_context,
return MakeNonConstantResult(phase);
}
}

case CARBON_KIND(SemIR::FacetAccessWitness typed_inst): {
Phase phase = Phase::Template;
if (ReplaceFieldWithConstantValue(
eval_context, &typed_inst,
&SemIR::FacetAccessWitness::facet_value_inst_id, &phase)) {
if (auto facet_value = eval_context.insts().TryGetAs<SemIR::FacetValue>(
typed_inst.facet_value_inst_id)) {
return eval_context.constant_values().Get(
facet_value->witness_inst_id);
}
return MakeConstantResult(eval_context.context(), typed_inst, phase);
} else {
return MakeNonConstantResult(phase);
}
}
case CARBON_KIND(SemIR::WhereExpr typed_inst): {
Phase phase = Phase::Template;
SemIR::TypeId base_facet_type_id =
Expand Down
146 changes: 110 additions & 36 deletions toolchain/check/member_access.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -124,18 +124,55 @@ static auto ScopeNeedsImplLookup(Context& context,
return true;
}

static auto GetInterfaceFromFacetType(Context& context, SemIR::TypeId type_id)
-> std::optional<SemIR::FacetTypeInfo::ImplsConstraint> {
auto facet_type = context.types().GetAs<SemIR::FacetType>(type_id);
const auto& facet_type_info =
context.facet_types().Get(facet_type.facet_type_id);
return facet_type_info.TryAsSingleInterface();
}

static auto AccessMemberOfInterfaceWitness(
Context& context, SemIR::LocId loc_id, SemIR::InstId witness_id,
SemIR::SpecificId interface_specific_id,
SemIR::AssociatedEntityType assoc_type, SemIR::InstId member_id)
-> SemIR::InstId {
auto member_value_id = context.constant_values().GetConstantInstId(member_id);
if (!member_value_id.is_valid()) {
if (member_value_id != SemIR::InstId::BuiltinErrorInst) {
context.TODO(member_id, "non-constant associated entity");
}
return SemIR::InstId::BuiltinErrorInst;
}

auto assoc_entity =
context.insts().TryGetAs<SemIR::AssociatedEntity>(member_value_id);
if (!assoc_entity) {
context.TODO(member_id, "unexpected value for associated entity");
return SemIR::InstId::BuiltinErrorInst;
}

// TODO: This produces the type of the associated entity with no value for
// `Self`. The type `Self` might appear in the type of an associated constant,
// and if so, we'll need to substitute it here somehow.
auto subst_type_id = SemIR::GetTypeInSpecific(
context.sem_ir(), interface_specific_id, assoc_type.entity_type_id);

return context.GetOrAddInst<SemIR::InterfaceWitnessAccess>(
loc_id, {.type_id = subst_type_id,
.witness_id = witness_id,
.index = assoc_entity->index});
}

// Performs impl lookup for a member name expression. This finds the relevant
// impl witness and extracts the corresponding impl member.
static auto PerformImplLookup(
Context& context, SemIR::LocId loc_id, SemIR::ConstantId type_const_id,
SemIR::AssociatedEntityType assoc_type, SemIR::InstId member_id,
Context::BuildDiagnosticFn missing_impl_diagnoser = nullptr)
-> SemIR::InstId {
auto facet_type =
context.types().GetAs<SemIR::FacetType>(assoc_type.interface_type_id);
const auto& facet_type_info =
context.facet_types().Get(facet_type.facet_type_id);
auto interface_type = facet_type_info.TryAsSingleInterface();
auto interface_type =
GetInterfaceFromFacetType(context, assoc_type.interface_type_id);
if (!interface_type) {
context.TODO(loc_id,
"Lookup of impl witness not yet supported except for a single "
Expand Down Expand Up @@ -170,42 +207,20 @@ static auto PerformImplLookup(
}
return SemIR::InstId::BuiltinErrorInst;
}

auto member_value_id = context.constant_values().GetConstantInstId(member_id);
if (!member_value_id.is_valid()) {
if (member_value_id != SemIR::InstId::BuiltinErrorInst) {
context.TODO(member_id, "non-constant associated entity");
}
return SemIR::InstId::BuiltinErrorInst;
}

auto assoc_entity =
context.insts().TryGetAs<SemIR::AssociatedEntity>(member_value_id);
if (!assoc_entity) {
context.TODO(member_id, "unexpected value for associated entity");
return SemIR::InstId::BuiltinErrorInst;
}

// TODO: This produces the type of the associated entity with no value for
// `Self`. The type `Self` might appear in the type of an associated constant,
// and if so, we'll need to substitute it here somehow.
auto subst_type_id = SemIR::GetTypeInSpecific(
context.sem_ir(), interface_type->specific_id, assoc_type.entity_type_id);

return context.GetOrAddInst<SemIR::InterfaceWitnessAccess>(
loc_id, {.type_id = subst_type_id,
.witness_id = witness_id,
.index = assoc_entity->index});
return AccessMemberOfInterfaceWitness(context, loc_id, witness_id,
interface_type->specific_id, assoc_type,
member_id);
}

// Performs a member name lookup into the specified scope, including performing
// impl lookup if necessary. If the scope is invalid, assume an error has
// already been diagnosed, and return BuiltinErrorInst.
static auto LookupMemberNameInScope(Context& context, SemIR::LocId loc_id,
SemIR::InstId /*base_id*/,
SemIR::InstId base_id,
SemIR::NameId name_id,
SemIR::ConstantId name_scope_const_id,
llvm::ArrayRef<LookupScope> lookup_scopes)
llvm::ArrayRef<LookupScope> lookup_scopes,
bool lookup_in_type_of_base)
-> SemIR::InstId {
AccessInfo access_info = {
.constant_id = name_scope_const_id,
Expand Down Expand Up @@ -250,7 +265,64 @@ static auto LookupMemberNameInScope(Context& context, SemIR::LocId loc_id,
// impl member is not supposed to be treated as ambiguous.
if (auto assoc_type =
context.types().TryGetAs<SemIR::AssociatedEntityType>(type_id)) {
if (ScopeNeedsImplLookup(context, name_scope_const_id)) {
if (lookup_in_type_of_base) {
SemIR::TypeId base_type_id = context.insts().Get(base_id).type_id();
if (base_type_id != SemIR::TypeId::TypeType &&
context.IsFacetType(base_type_id)) {
// Handles `T.F` when `T` is a non-type facet.

auto assoc_interface =
GetInterfaceFromFacetType(context, assoc_type->interface_type_id);
// An associated entity should always be associated with a single
// interface.
CARBON_CHECK(assoc_interface);

// First look for `*assoc_interface` in the type of the base. If it is
// found, get the witness that the interface is implemented from
// `base_id`.
auto facet_type = context.types().GetAs<SemIR::FacetType>(base_type_id);
const auto& facet_type_info =
context.facet_types().Get(facet_type.facet_type_id);
// Witness that `T` implements the `*assoc_interface`.
SemIR::InstId witness_inst_id = SemIR::InstId::Invalid;
for (auto base_interface : facet_type_info.impls_constraints) {
// Get the witness that `T` implements `base_type_id`.
if (base_interface == *assoc_interface) {
witness_inst_id = context.GetOrAddInst<SemIR::FacetAccessWitness>(
loc_id, {.type_id = context.GetBuiltinType(
SemIR::BuiltinInstKind::WitnessType),
.facet_value_inst_id = base_id});
// TODO: Result will eventually be a facet type witness instead of
// an interface witness. Will need to use the index
// `*assoc_interface` was found in
// `facet_type_info.impls_constraints` to get the correct interface
// witness out.
break;
}
}
// TODO: If that fails, would need to do impl lookup to see if the facet
// value implements the interface of `*assoc_type`.
if (!witness_inst_id.is_valid()) {
context.TODO(member_id,
"associated entity not found in facet type, need to do "
"impl lookup");
return SemIR::InstId::BuiltinErrorInst;
}

member_id = AccessMemberOfInterfaceWitness(
context, loc_id, witness_inst_id, assoc_interface->specific_id,
*assoc_type, member_id);
} else {
// Handles `x.F` if `x` is of type `class C` that extends an interface
// containing `F`.
SemIR::ConstantId constant_id =
context.types().GetConstantId(base_type_id);
member_id = PerformImplLookup(context, loc_id, constant_id, *assoc_type,
member_id);
}
} else if (ScopeNeedsImplLookup(context, name_scope_const_id)) {
// Handles `T.F` where `T` is a type extending an interface containing
// `F`.
member_id = PerformImplLookup(context, loc_id, name_scope_const_id,
*assoc_type, member_id);
}
Expand Down Expand Up @@ -342,7 +414,8 @@ auto PerformMemberAccess(Context& context, SemIR::LocId loc_id,
if (context.AppendLookupScopesForConstant(loc_id, base_const_id,
&lookup_scopes)) {
return LookupMemberNameInScope(context, loc_id, base_id, name_id,
base_const_id, lookup_scopes);
base_const_id, lookup_scopes,
/*lookup_in_type_of_base=*/false);
}
}

Expand Down Expand Up @@ -401,7 +474,8 @@ auto PerformMemberAccess(Context& context, SemIR::LocId loc_id,

// Perform lookup into the base type.
auto member_id = LookupMemberNameInScope(context, loc_id, base_id, name_id,
base_type_const_id, lookup_scopes);
base_type_const_id, lookup_scopes,
/*lookup_in_type_of_base=*/true);

// Perform instance binding if we found an instance member.
member_id = PerformInstanceBinding(context, loc_id, base_id, member_id);
Expand Down
38 changes: 17 additions & 21 deletions toolchain/check/testdata/impl/fail_todo_impl_assoc_const.carbon
Original file line number Diff line number Diff line change
Expand Up @@ -10,35 +10,28 @@

interface I { let T:! type; }

// CHECK:STDERR: fail_todo_impl_assoc_const.carbon:[[@LINE+10]]:1: error: semantics TODO: `impl of interface with associated constant` [SemanticsTodo]
// CHECK:STDERR: fail_todo_impl_assoc_const.carbon:[[@LINE+3]]:1: error: semantics TODO: `impl of interface with associated constant` [SemanticsTodo]
// CHECK:STDERR: impl bool as I where .T = bool {}
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// CHECK:STDERR:
// CHECK:STDERR: fail_todo_impl_assoc_const.carbon:[[@LINE+6]]:27: error: cannot implicitly convert from `type` to `<associated type in I>` [ImplicitAsConversionFailure]
// CHECK:STDERR: impl bool as I where .T = bool {}
// CHECK:STDERR: ^~~~
// CHECK:STDERR: fail_todo_impl_assoc_const.carbon:[[@LINE+3]]:27: note: type `type` does not implement interface `ImplicitAs(<associated type in I>)` [MissingImplInMemberAccessNote]
// CHECK:STDERR: impl bool as I where .T = bool {}
// CHECK:STDERR: ^~~~
impl bool as I where .T = bool {}

// CHECK:STDOUT: --- fail_todo_impl_assoc_const.carbon
// CHECK:STDOUT:
// CHECK:STDOUT: constants {
// CHECK:STDOUT: %I.type.1: type = facet_type <@I> [template]
// CHECK:STDOUT: %Self.1: %I.type.1 = bind_symbolic_name Self, 0 [symbolic]
// CHECK:STDOUT: %Self: %I.type.1 = bind_symbolic_name Self, 0 [symbolic]
// CHECK:STDOUT: %.1: type = assoc_entity_type %I.type.1, type [template]
// CHECK:STDOUT: %.2: %.1 = assoc_entity element0, @I.%T [template]
// CHECK:STDOUT: %Bool.type: type = fn_type @Bool [template]
// CHECK:STDOUT: %Bool: %Bool.type = struct_value () [template]
// CHECK:STDOUT: %.Self: %I.type.1 = bind_symbolic_name .Self, 0 [symbolic]
// CHECK:STDOUT: %.3: <witness> = facet_access_witness %.Self [symbolic]
// CHECK:STDOUT: %I.type.2: type = facet_type <@I where TODO> [template]
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: imports {
// CHECK:STDOUT: %Core: <namespace> = namespace file.%Core.import, [template] {
// CHECK:STDOUT: .Bool = %import_ref.1
// CHECK:STDOUT: .ImplicitAs = %import_ref.2
// CHECK:STDOUT: .Bool = %import_ref
// CHECK:STDOUT: import Core//prelude
// CHECK:STDOUT: import Core//prelude/...
// CHECK:STDOUT: }
Expand All @@ -51,24 +44,27 @@ impl bool as I where .T = bool {}
// CHECK:STDOUT: }
// CHECK:STDOUT: %Core.import = import Core
// CHECK:STDOUT: %I.decl: type = interface_decl @I [template = constants.%I.type.1] {} {}
// CHECK:STDOUT: impl_decl @impl.9 [template] {} {
// CHECK:STDOUT: %bool.make_type.loc23_6: init type = call constants.%Bool() [template = bool]
// CHECK:STDOUT: %.loc23_6.1: type = value_of_initializer %bool.make_type.loc23_6 [template = bool]
// CHECK:STDOUT: %.loc23_6.2: type = converted %bool.make_type.loc23_6, %.loc23_6.1 [template = bool]
// CHECK:STDOUT: impl_decl @impl [template] {} {
// CHECK:STDOUT: %bool.make_type.loc16_6: init type = call constants.%Bool() [template = bool]
// CHECK:STDOUT: %.loc16_6.1: type = value_of_initializer %bool.make_type.loc16_6 [template = bool]
// CHECK:STDOUT: %.loc16_6.2: type = converted %bool.make_type.loc16_6, %.loc16_6.1 [template = bool]
// CHECK:STDOUT: %I.ref: type = name_ref I, file.%I.decl [template = constants.%I.type.1]
// CHECK:STDOUT: %.Self: %I.type.1 = bind_symbolic_name .Self, 0 [symbolic = constants.%.Self]
// CHECK:STDOUT: %.Self.ref: %I.type.1 = name_ref .Self, %.Self [symbolic = constants.%.Self]
// CHECK:STDOUT: %T.ref: %.1 = name_ref T, @I.%.loc11 [template = constants.%.2]
// CHECK:STDOUT: %bool.make_type.loc23_27: init type = call constants.%Bool() [template = bool]
// CHECK:STDOUT: %.loc23_27: %.1 = converted %bool.make_type.loc23_27, <error> [template = <error>]
// CHECK:STDOUT: %.loc23_16: type = where_expr %.Self [template = constants.%I.type.2] {
// CHECK:STDOUT: requirement_rewrite %T.ref, <error>
// CHECK:STDOUT: %.loc16_22.1: <witness> = facet_access_witness %.Self.ref [symbolic = constants.%.3]
// CHECK:STDOUT: %.loc16_22.2: type = interface_witness_access %.loc16_22.1, element0
// CHECK:STDOUT: %bool.make_type.loc16_27: init type = call constants.%Bool() [template = bool]
// CHECK:STDOUT: %.loc16_27.1: type = value_of_initializer %bool.make_type.loc16_27 [template = bool]
// CHECK:STDOUT: %.loc16_27.2: type = converted %bool.make_type.loc16_27, %.loc16_27.1 [template = bool]
// CHECK:STDOUT: %.loc16_16: type = where_expr %.Self [template = constants.%I.type.2] {
// CHECK:STDOUT: requirement_rewrite %.loc16_22.2, %.loc16_27.2
// CHECK:STDOUT: }
// CHECK:STDOUT: }
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: interface @I {
// CHECK:STDOUT: %Self: %I.type.1 = bind_symbolic_name Self, 0 [symbolic = constants.%Self.1]
// CHECK:STDOUT: %Self: %I.type.1 = bind_symbolic_name Self, 0 [symbolic = constants.%Self]
// CHECK:STDOUT: %T: type = assoc_const_decl T [template]
// CHECK:STDOUT: %.loc11: %.1 = assoc_entity element0, %T [template = constants.%.2]
// CHECK:STDOUT:
Expand All @@ -78,7 +74,7 @@ impl bool as I where .T = bool {}
// CHECK:STDOUT: witness = (%T)
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: impl @impl.9: %.loc23_6.2 as %.loc23_16 {
// CHECK:STDOUT: impl @impl: %.loc16_6.2 as %.loc16_16 {
// CHECK:STDOUT: !members:
// CHECK:STDOUT: witness = <error>
// CHECK:STDOUT: }
Expand Down
Loading

0 comments on commit 4767d5b

Please sign in to comment.