Skip to content

Commit c45f33e

Browse files
committed
Expand TypeCanDestroy to handle more types
1 parent 648dfbe commit c45f33e

35 files changed

+3169
-2463
lines changed

core/prelude/iterate.carbon

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,13 @@ export import library "prelude/operators";
1111

1212
interface Iterate {
1313
// TODO: Support iterating ranges of non-copyable values.
14-
let ElementType:! Copy;
14+
let ElementType:! Copy & Destroy;
1515
let CursorType:! type;
1616
fn NewCursor[self: Self]() -> CursorType;
1717
fn Next[self: Self](cursor: CursorType*) -> Optional(ElementType);
1818
}
1919

20-
impl forall [T:! Copy, N:! IntLiteral()]
20+
impl forall [T:! Copy & Destroy, N:! IntLiteral()]
2121
array(T, N) as Iterate
2222
where .ElementType = T and .CursorType = i32 {
2323
fn NewCursor[self: Self]() -> i32 { return 0; }

core/prelude/types/maybe_unformed.carbon

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import library "prelude/destroy";
88

99
private fn MakeMaybeUnformed(t: type) -> type = "maybe_unformed.make_type";
1010

11-
class MaybeUnformed(T:! type) {
11+
// TODO: There should probably support for non-destructible types.
12+
class MaybeUnformed(T:! Destroy) {
1213
adapt MakeMaybeUnformed(T);
1314
}

core/prelude/types/optional.carbon

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ private interface OptionalStorage {
2222

2323
// TODO: We don't have an approved design for an `Optional` type yet, but it's
2424
// used by the design for `Iterate`. The API here is a placeholder.
25-
class Optional(T:! OptionalStorage) {
25+
class Optional(T:! Destroy & OptionalStorage) {
2626
fn None() -> Self {
2727
return T.None() as Self;
2828
}
@@ -59,11 +59,11 @@ class Optional(T:! OptionalStorage) {
5959
// }
6060
// }
6161

62-
private interface OptionalAs(T:! OptionalStorage) {
62+
private interface OptionalAs(T:! Destroy & OptionalStorage) {
6363
fn Convert[self: Self]() -> Optional(T);
6464
}
6565

66-
final impl forall [T:! OptionalStorage]
66+
final impl forall [T:! Destroy & OptionalStorage]
6767
T as OptionalAs(T) {
6868
fn Convert[self: Self]() -> Optional(T) {
6969
return Optional(T).Some(self);
@@ -77,7 +77,7 @@ impl forall [T:! Destroy & OptionalStorage, U:! ImplicitAs(T)]
7777
}
7878
}
7979

80-
impl forall [T:! OptionalStorage, U:! OptionalAs(T)]
80+
impl forall [T:! Destroy & OptionalStorage, U:! OptionalAs(T)]
8181
U as ImplicitAs(Optional(T)) {
8282
fn Convert[self: Self]() -> Optional(T) {
8383
return self.Convert();
@@ -89,12 +89,12 @@ impl forall [T:! OptionalStorage, U:! OptionalAs(T)]
8989
// `bool` is `false`.
9090
//
9191
// TODO: Revisit this once we have choice types implemented in the toolchain.
92-
private class DefaultOptionalStorage(T:! Copy) {
92+
private class DefaultOptionalStorage(T:! Copy & Destroy) {
9393
var value: MaybeUnformed(T);
9494
var has_value: bool;
9595
}
9696

97-
impl forall [T:! Copy] T as OptionalStorage
97+
impl forall [T:! Copy & Destroy] T as OptionalStorage
9898
where .Type = DefaultOptionalStorage(T) {
9999
fn None() -> DefaultOptionalStorage(T) {
100100
returned var me: DefaultOptionalStorage(T);

toolchain/check/impl_lookup.cpp

Lines changed: 199 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include "toolchain/check/impl.h"
2020
#include "toolchain/check/import_ref.h"
2121
#include "toolchain/check/inst.h"
22+
#include "toolchain/check/name_lookup.h"
2223
#include "toolchain/check/subst.h"
2324
#include "toolchain/check/type.h"
2425
#include "toolchain/check/type_completion.h"
@@ -27,6 +28,7 @@
2728
#include "toolchain/sem_ir/ids.h"
2829
#include "toolchain/sem_ir/impl.h"
2930
#include "toolchain/sem_ir/inst.h"
31+
#include "toolchain/sem_ir/specific_interface.h"
3032
#include "toolchain/sem_ir/typed_insts.h"
3133

3234
namespace Carbon::Check {
@@ -566,48 +568,208 @@ static auto GetOrAddLookupImplWitness(Context& context, SemIR::LocId loc_id,
566568
return context.constant_values().GetInstId(witness_const_id);
567569
}
568570

569-
// Returns true if the `Self` should impl `Destroy`.
570-
static auto TypeCanDestroy(Context& context,
571-
SemIR::ConstantId query_self_const_id) -> bool {
572-
auto inst = context.insts().Get(context.constant_values().GetInstId(
573-
GetCanonicalFacetOrTypeValue(context, query_self_const_id)));
574-
575-
// For facet values, look if the FacetType provides the same.
576-
if (auto facet_type =
577-
context.types().TryGetAs<SemIR::FacetType>(inst.type_id())) {
578-
const auto& info = context.facet_types().Get(facet_type->facet_type_id);
579-
if (info.builtin_constraint_mask.HasAnyOf(
580-
SemIR::BuiltinConstraintMask::TypeCanDestroy)) {
571+
// Determines whether `type_id` is a `FacetType` which impls `Destroy`.
572+
//
573+
// This handles cases where the facet type provides either `CanDestroy` or
574+
// `Destroy`; the former case will always have a `Destroy` implementation.
575+
static auto CanDestroyFacetType(Context& context, SemIR::LocId loc_id,
576+
SemIR::FacetType facet_type,
577+
SemIR::InterfaceId& destroy_interface_id)
578+
-> bool {
579+
const auto& info = context.facet_types().Get(facet_type.facet_type_id);
580+
if (info.builtin_constraint_mask.HasAnyOf(
581+
SemIR::BuiltinConstraintMask::TypeCanDestroy)) {
582+
return true;
583+
}
584+
585+
if (info.extend_constraints.empty()) {
586+
return false;
587+
}
588+
589+
// Fetch `Core.Destroy` if it isn't yet known.
590+
if (!destroy_interface_id.has_value()) {
591+
auto destroy_id = LookupNameInCore(context, loc_id, "Destroy");
592+
auto destroy_facet_type =
593+
context.insts().TryGetAs<SemIR::FacetType>(destroy_id);
594+
if (!destroy_facet_type) {
595+
return false;
596+
}
597+
const auto& destroy_facet_type_info =
598+
context.facet_types().Get(destroy_facet_type->facet_type_id);
599+
auto destroy_extend = destroy_facet_type_info.TryAsSingleExtend();
600+
if (!destroy_extend ||
601+
!std::holds_alternative<SemIR::SpecificInterface>(*destroy_extend)) {
602+
return false;
603+
}
604+
605+
destroy_interface_id =
606+
std::get<SemIR::SpecificInterface>(*destroy_extend).interface_id;
607+
}
608+
609+
for (const auto& constraint : info.extend_constraints) {
610+
if (constraint.interface_id == destroy_interface_id) {
581611
return true;
582612
}
583613
}
584614

585-
CARBON_KIND_SWITCH(inst) {
586-
case CARBON_KIND(SemIR::ClassType class_type): {
587-
auto class_info = context.classes().Get(class_type.class_id);
588-
// Incomplete and abstract classes can't be destroyed.
589-
// TODO: Return false if the object repr doesn't impl `Destroy`.
590-
// TODO: Return false for C++ types that lack a destructor.
591-
return class_info.is_complete() &&
592-
class_info.inheritance_kind !=
593-
SemIR::Class::InheritanceKind::Abstract;
615+
return false;
616+
}
617+
618+
// Returns true if an instruction that should be a `FacetValue` is destructible.
619+
static auto CanDestroyFacetValue(Context& context, SemIR::LocId loc_id,
620+
SemIR::InstId facet_value_inst_id,
621+
SemIR::InterfaceId& destroy_interface_id)
622+
-> bool {
623+
if (!facet_value_inst_id.has_value()) {
624+
return false;
625+
}
626+
auto facet_value = context.insts().Get(facet_value_inst_id);
627+
auto facet_type =
628+
context.types().GetAs<SemIR::FacetType>(facet_value.type_id());
629+
return CanDestroyFacetType(context, loc_id, facet_type, destroy_interface_id);
630+
}
631+
632+
// Returns true if the `Self` should impl `Destroy`.
633+
static auto TypeCanDestroy(Context& context, SemIR::LocId loc_id,
634+
SemIR::ConstantId query_self_const_id) -> bool {
635+
// A lazily cached `Destroy` interface.
636+
auto destroy_interface_id = SemIR::InterfaceId::None;
637+
638+
auto query_inst_id = context.constant_values().GetInstId(
639+
GetCanonicalFacetOrTypeValue(context, query_self_const_id));
640+
641+
// When querying a facet type, we can return early.
642+
if (auto facet_type = context.types().TryGetAs<SemIR::FacetType>(
643+
context.insts().Get(query_inst_id).type_id())) {
644+
return CanDestroyFacetType(context, loc_id, *facet_type,
645+
destroy_interface_id);
646+
}
647+
648+
// A stack of IDs to check (ordering shouldn't be important).
649+
struct WorkItem {
650+
SemIR::InstId id;
651+
// The only case `abstract` is allowed during a type walk is when looking at
652+
// a `base` type of a class.
653+
bool allow_abstract = false;
654+
};
655+
llvm::SmallVector<WorkItem> work;
656+
657+
// Start by checking the queried instruction.
658+
work.push_back({.id = query_inst_id});
659+
660+
// Loop through type structures recursively, looking for any types
661+
// incompatible with `Destroy`.
662+
while (!work.empty()) {
663+
auto work_item = work.pop_back_val();
664+
auto inst = context.insts().Get(work_item.id);
665+
666+
CARBON_KIND_SWITCH(inst) {
667+
case CARBON_KIND(SemIR::ArrayType array_type): {
668+
work.push_back({.id = array_type.element_type_inst_id});
669+
break;
670+
}
671+
672+
case CARBON_KIND(SemIR::BaseDecl base_decl): {
673+
work.push_back(
674+
{.id = base_decl.base_type_inst_id, .allow_abstract = true});
675+
break;
676+
}
677+
678+
case CARBON_KIND(SemIR::ClassType class_type): {
679+
auto class_info = context.classes().Get(class_type.class_id);
680+
// Incomplete and abstract classes can't be destroyed.
681+
if (!class_info.is_complete() ||
682+
(!work_item.allow_abstract &&
683+
class_info.inheritance_kind ==
684+
SemIR::Class::InheritanceKind::Abstract)) {
685+
return false;
686+
}
687+
688+
// For C++ types, use Clang to determine whether they can be destructed.
689+
// TODO: Needs appropriate calls.
690+
if (context.name_scopes().Get(class_info.scope_id).is_cpp_scope()) {
691+
break;
692+
}
693+
694+
auto obj_repr_id =
695+
class_info.GetObjectRepr(context.sem_ir(), SemIR::SpecificId::None);
696+
work.push_back({.id = context.types().GetInstId(obj_repr_id)});
697+
break;
698+
}
699+
700+
case CARBON_KIND(SemIR::ConstType const_type): {
701+
work.push_back({.id = const_type.inner_id,
702+
.allow_abstract = work_item.allow_abstract});
703+
break;
704+
}
705+
706+
case CARBON_KIND(SemIR::FacetAccessType facet_access_type): {
707+
if (!CanDestroyFacetValue(context, loc_id,
708+
facet_access_type.facet_value_inst_id,
709+
destroy_interface_id)) {
710+
return false;
711+
}
712+
break;
713+
}
714+
715+
case CARBON_KIND(SemIR::PartialType partial_type): {
716+
// TODO: This may be adjusted per
717+
// https://github.com/carbon-language/carbon-lang/issues/6161.
718+
work.push_back({.id = partial_type.inner_id, .allow_abstract = true});
719+
break;
720+
}
721+
722+
case CARBON_KIND(SemIR::StructType struct_type): {
723+
for (auto field :
724+
context.struct_type_fields().Get(struct_type.fields_id)) {
725+
work.push_back({.id = field.type_inst_id});
726+
}
727+
break;
728+
}
729+
730+
case CARBON_KIND(SemIR::SymbolicBindingType facet_access_type): {
731+
if (!CanDestroyFacetValue(context, loc_id,
732+
facet_access_type.facet_value_inst_id,
733+
destroy_interface_id)) {
734+
return false;
735+
}
736+
break;
737+
}
738+
739+
case CARBON_KIND(SemIR::TupleType tuple_type): {
740+
for (auto element_id :
741+
context.inst_blocks().Get(tuple_type.type_elements_id)) {
742+
work.push_back({.id = element_id});
743+
}
744+
break;
745+
}
746+
747+
case SemIR::FloatLiteralType::Kind:
748+
case SemIR::FloatType::Kind:
749+
case SemIR::IntLiteralType::Kind:
750+
case SemIR::IntType::Kind:
751+
case SemIR::BoolType::Kind:
752+
case SemIR::PointerType::Kind:
753+
case SemIR::TypeType::Kind:
754+
// Trivially destructible.
755+
break;
756+
757+
case SemIR::ImplWitnessAccess::Kind:
758+
case SemIR::MaybeUnformedType::Kind:
759+
// TODO: Need to dig into the type to ensure it does `Destroy`;
760+
// providing `Destroy` for now for compatibility.
761+
break;
762+
763+
case SemIR::SymbolicBinding::Kind:
764+
// TODO: Figure out what to do with these.
765+
return false;
766+
767+
default:
768+
CARBON_FATAL("TypeCanDestroy found unexpected inst: {0}", inst);
594769
}
595-
case SemIR::ArrayType::Kind:
596-
case SemIR::ConstType::Kind:
597-
case SemIR::MaybeUnformedType::Kind:
598-
case SemIR::PartialType::Kind:
599-
case SemIR::StructType::Kind:
600-
case SemIR::TupleType::Kind:
601-
// TODO: Return false for types that indirectly reference a type that
602-
// doesn't impl `Destroy`.
603-
return true;
604-
case SemIR::BoolType::Kind:
605-
case SemIR::PointerType::Kind:
606-
// Trivially destructible.
607-
return true;
608-
default:
609-
return false;
610770
}
771+
772+
return true;
611773
}
612774

613775
auto LookupImplWitness(Context& context, SemIR::LocId loc_id,
@@ -645,7 +807,7 @@ auto LookupImplWitness(Context& context, SemIR::LocId loc_id,
645807
}
646808
if (builtin_constraint_mask.HasAnyOf(
647809
SemIR::BuiltinConstraintMask::TypeCanDestroy) &&
648-
!TypeCanDestroy(context, query_self_const_id)) {
810+
!TypeCanDestroy(context, loc_id, query_self_const_id)) {
649811
return SemIR::InstBlockId::None;
650812
}
651813
if (interfaces.empty()) {

0 commit comments

Comments
 (0)