Add builtin functions for destroy, with special requirements in facet types (#6035)

This is in support of a goal of changing the blanket `destroy` impl to
use (roughly):

```
private fn CanAggregateDestroy() -> type = "type.can_aggregate_destroy";

// Handles aggregate type destruction.
impl forall [AggregateDestroyT:! CanAggregateDestroy()] AggregateDestroyT as Destroy {
  fn Op[addr self: Self*]() = "type.aggregate_destroy";
}
```

That isn't done here because there's still other issues that migrating
raises. What this *does* do is add the builtin functions, and in
particular, support to `FacetTypeInfo` to make `CanAggregateDestroy`
work.

The "special requirement" approach in `FacetTypeInfo` allows us to
support restricting a blanket impl under the current approach of impls.
Maybe we'll find a cleaner approach that can work in the future, but
this fits into the current model by propagating similar to other
requirements. I'm using an enum mask because we have a number of similar
things to add (e.g. copy, move) but I'm not sure we need a full vector.

A few alternatives considered were:

- Supporting syntax more like `where .Self impls
TypeCanAggregateDestroy(.Self, SupportedInterface,
UnsupportedInterface)`. I think it'd be a little cleaner, but requires
better compile-time evaluation in order to assess the type of the call.
Right now it's expected to be a `FacetType` too early to make this work,
and I was concerned about pouring too much more time down this route.
- Providing an actual interface, in particular doing name lookup back
into `Core.` for an interface. This would've added name lookup overhead,
and the question of whether an `impl` exists.
- Generating an interface. This avoids the name lookup, but would still
raise the question of whether an `impl` should also be generated. Work
I've previously done generating interfaces for class destruction also
feels complex to both write and understand (an unfortunate issue).
- Still modeling as an `ImplsConstraint`, for example by defining a
special `InterfaceId::CanAggregateDestroy = -2` similar to what we do on
other ids. I was hesitant because of how this expands the number of
modes of `InterfaceId`, and things for consuming code to watch out for,
for what feels like a relatively niche set of use-cases that are only
interface-like.

---------

Co-authored-by: Dana Jansens <danakj@orodu.net>
This commit is contained in:
Jon Ross-Perkins
2025-09-15 17:03:43 +00:00
committed by GitHub
co-authored by Dana Jansens
parent 3ec0bcb4fd
commit 5e3bb523f8
401 changed files with 20575 additions and 20097 deletions
+26 -26
View File
@@ -200,8 +200,8 @@ fn F() {
// CHECK:STDOUT: %ptr.5c7: type = ptr_type %S [concrete]
// CHECK:STDOUT: %foo.type: type = fn_type @foo [concrete]
// CHECK:STDOUT: %foo: %foo.type = struct_value () [concrete]
// CHECK:STDOUT: %T.as.Destroy.impl.Op.type.f1d: type = fn_type @T.as.Destroy.impl.Op, @T.as.Destroy.impl(%S) [concrete]
// CHECK:STDOUT: %T.as.Destroy.impl.Op.777: %T.as.Destroy.impl.Op.type.f1d = struct_value () [concrete]
// CHECK:STDOUT: %T.as.Destroy.impl.Op.type.d46: type = fn_type @T.as.Destroy.impl.Op, @T.as.Destroy.impl(%S) [concrete]
// CHECK:STDOUT: %T.as.Destroy.impl.Op.062: %T.as.Destroy.impl.Op.type.d46 = struct_value () [concrete]
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: imports {
@@ -240,7 +240,7 @@ fn F() {
// CHECK:STDOUT: %s.ref: ref %S = name_ref s, %s
// CHECK:STDOUT: %addr.loc9: %ptr.5c7 = addr_of %s.ref
// CHECK:STDOUT: %foo.call: init %empty_tuple.type = call imports.%foo.decl(%addr.loc9)
// CHECK:STDOUT: %T.as.Destroy.impl.Op.bound: <bound method> = bound_method %s.var, constants.%T.as.Destroy.impl.Op.777
// CHECK:STDOUT: %T.as.Destroy.impl.Op.bound: <bound method> = bound_method %s.var, constants.%T.as.Destroy.impl.Op.062
// CHECK:STDOUT: <elided>
// CHECK:STDOUT: %bound_method: <bound method> = bound_method %s.var, %T.as.Destroy.impl.Op.specific_fn
// CHECK:STDOUT: %addr.loc8: %ptr.5c7 = addr_of %s.var
@@ -261,23 +261,23 @@ fn F() {
// CHECK:STDOUT: %Copy.type: type = facet_type <@Copy> [concrete]
// CHECK:STDOUT: %Copy.Op.type: type = fn_type @Copy.Op [concrete]
// CHECK:STDOUT: %T.8b3: type = bind_symbolic_name T, 0 [symbolic]
// CHECK:STDOUT: %ptr.as.Copy.impl.Op.type.f23: type = fn_type @ptr.as.Copy.impl.Op, @ptr.as.Copy.impl(%T.8b3) [symbolic]
// CHECK:STDOUT: %ptr.as.Copy.impl.Op.abf: %ptr.as.Copy.impl.Op.type.f23 = struct_value () [symbolic]
// CHECK:STDOUT: %Copy.impl_witness.dea: <witness> = impl_witness imports.%Copy.impl_witness_table.a71, @ptr.as.Copy.impl(%S) [concrete]
// CHECK:STDOUT: %ptr.as.Copy.impl.Op.type.7f7: type = fn_type @ptr.as.Copy.impl.Op, @ptr.as.Copy.impl(%S) [concrete]
// CHECK:STDOUT: %ptr.as.Copy.impl.Op.0c6: %ptr.as.Copy.impl.Op.type.7f7 = struct_value () [concrete]
// CHECK:STDOUT: %Copy.facet.334: %Copy.type = facet_value %ptr.5c7, (%Copy.impl_witness.dea) [concrete]
// CHECK:STDOUT: %.816: type = fn_type_with_self_type %Copy.Op.type, %Copy.facet.334 [concrete]
// CHECK:STDOUT: %ptr.as.Copy.impl.Op.specific_fn: <specific function> = specific_function %ptr.as.Copy.impl.Op.0c6, @ptr.as.Copy.impl.Op(%S) [concrete]
// CHECK:STDOUT: %ptr.as.Copy.impl.Op.type.31f: type = fn_type @ptr.as.Copy.impl.Op, @ptr.as.Copy.impl(%T.8b3) [symbolic]
// CHECK:STDOUT: %ptr.as.Copy.impl.Op.8a8: %ptr.as.Copy.impl.Op.type.31f = struct_value () [symbolic]
// CHECK:STDOUT: %Copy.impl_witness.692: <witness> = impl_witness imports.%Copy.impl_witness_table.53c, @ptr.as.Copy.impl(%S) [concrete]
// CHECK:STDOUT: %ptr.as.Copy.impl.Op.type.bf9: type = fn_type @ptr.as.Copy.impl.Op, @ptr.as.Copy.impl(%S) [concrete]
// CHECK:STDOUT: %ptr.as.Copy.impl.Op.1cb: %ptr.as.Copy.impl.Op.type.bf9 = struct_value () [concrete]
// CHECK:STDOUT: %Copy.facet.7cc: %Copy.type = facet_value %ptr.5c7, (%Copy.impl_witness.692) [concrete]
// CHECK:STDOUT: %.dce: type = fn_type_with_self_type %Copy.Op.type, %Copy.facet.7cc [concrete]
// CHECK:STDOUT: %ptr.as.Copy.impl.Op.specific_fn: <specific function> = specific_function %ptr.as.Copy.impl.Op.1cb, @ptr.as.Copy.impl.Op(%S) [concrete]
// CHECK:STDOUT: %.c5d: type = cpp_overload_set_type @Copy.Op [concrete]
// CHECK:STDOUT: %empty_struct: %.c5d = struct_value () [concrete]
// CHECK:STDOUT: %ptr.dfe: type = ptr_type %ptr.5c7 [concrete]
// CHECK:STDOUT: %foo.type: type = fn_type @foo [concrete]
// CHECK:STDOUT: %foo: %foo.type = struct_value () [concrete]
// CHECK:STDOUT: %T.as.Destroy.impl.Op.type.3e5: type = fn_type @T.as.Destroy.impl.Op, @T.as.Destroy.impl(%ptr.5c7) [concrete]
// CHECK:STDOUT: %T.as.Destroy.impl.Op.014: %T.as.Destroy.impl.Op.type.3e5 = struct_value () [concrete]
// CHECK:STDOUT: %T.as.Destroy.impl.Op.type.f1d: type = fn_type @T.as.Destroy.impl.Op, @T.as.Destroy.impl(%S) [concrete]
// CHECK:STDOUT: %T.as.Destroy.impl.Op.777: %T.as.Destroy.impl.Op.type.f1d = struct_value () [concrete]
// CHECK:STDOUT: %T.as.Destroy.impl.Op.type.929: type = fn_type @T.as.Destroy.impl.Op, @T.as.Destroy.impl(%ptr.5c7) [concrete]
// CHECK:STDOUT: %T.as.Destroy.impl.Op.76a: %T.as.Destroy.impl.Op.type.929 = struct_value () [concrete]
// CHECK:STDOUT: %T.as.Destroy.impl.Op.type.d46: type = fn_type @T.as.Destroy.impl.Op, @T.as.Destroy.impl(%S) [concrete]
// CHECK:STDOUT: %T.as.Destroy.impl.Op.062: %T.as.Destroy.impl.Op.type.d46 = struct_value () [concrete]
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: imports {
@@ -287,8 +287,8 @@ fn F() {
// CHECK:STDOUT: import Cpp//...
// CHECK:STDOUT: }
// CHECK:STDOUT: %S.decl: type = class_decl @S [concrete = constants.%S] {} {}
// CHECK:STDOUT: %Core.import_ref.de9: @ptr.as.Copy.impl.%ptr.as.Copy.impl.Op.type (%ptr.as.Copy.impl.Op.type.f23) = import_ref Core//prelude/parts/copy, loc36_31, loaded [symbolic = @ptr.as.Copy.impl.%ptr.as.Copy.impl.Op (constants.%ptr.as.Copy.impl.Op.abf)]
// CHECK:STDOUT: %Copy.impl_witness_table.a71 = impl_witness_table (%Core.import_ref.de9), @ptr.as.Copy.impl [concrete]
// CHECK:STDOUT: %Core.import_ref.0e4: @ptr.as.Copy.impl.%ptr.as.Copy.impl.Op.type (%ptr.as.Copy.impl.Op.type.31f) = import_ref Core//prelude/parts/copy, loc36_31, loaded [symbolic = @ptr.as.Copy.impl.%ptr.as.Copy.impl.Op (constants.%ptr.as.Copy.impl.Op.8a8)]
// CHECK:STDOUT: %Copy.impl_witness_table.53c = impl_witness_table (%Core.import_ref.0e4), @ptr.as.Copy.impl [concrete]
// CHECK:STDOUT: %.a21: %.c5d = cpp_overload_set_value @Copy.Op [concrete = constants.%empty_struct]
// CHECK:STDOUT: %foo.decl: %foo.type = fn_decl @foo [concrete = constants.%foo] {
// CHECK:STDOUT: <elided>
@@ -320,7 +320,7 @@ fn F() {
// CHECK:STDOUT: %p.var: ref %ptr.5c7 = var %p.var_patt
// CHECK:STDOUT: %s.ref: ref %S = name_ref s, %s
// CHECK:STDOUT: %addr.loc9_19: %ptr.5c7 = addr_of %s.ref
// CHECK:STDOUT: %impl.elem0: %.816 = impl_witness_access constants.%Copy.impl_witness.dea, element0 [concrete = constants.%ptr.as.Copy.impl.Op.0c6]
// CHECK:STDOUT: %impl.elem0: %.dce = impl_witness_access constants.%Copy.impl_witness.692, element0 [concrete = constants.%ptr.as.Copy.impl.Op.1cb]
// CHECK:STDOUT: %bound_method.loc9_19.1: <bound method> = bound_method %addr.loc9_19, %impl.elem0
// CHECK:STDOUT: %specific_fn: <specific function> = specific_function %impl.elem0, @ptr.as.Copy.impl.Op(constants.%S) [concrete = constants.%ptr.as.Copy.impl.Op.specific_fn]
// CHECK:STDOUT: %bound_method.loc9_19.2: <bound method> = bound_method %addr.loc9_19, %specific_fn
@@ -337,12 +337,12 @@ fn F() {
// CHECK:STDOUT: %p.ref: ref %ptr.5c7 = name_ref p, %p
// CHECK:STDOUT: %addr.loc10: %ptr.dfe = addr_of %p.ref
// CHECK:STDOUT: %foo.call: init %empty_tuple.type = call imports.%foo.decl(%addr.loc10)
// CHECK:STDOUT: %T.as.Destroy.impl.Op.bound.loc9: <bound method> = bound_method %p.var, constants.%T.as.Destroy.impl.Op.014
// CHECK:STDOUT: %T.as.Destroy.impl.Op.bound.loc9: <bound method> = bound_method %p.var, constants.%T.as.Destroy.impl.Op.76a
// CHECK:STDOUT: <elided>
// CHECK:STDOUT: %bound_method.loc9_3: <bound method> = bound_method %p.var, %T.as.Destroy.impl.Op.specific_fn.1
// CHECK:STDOUT: %addr.loc9_3: %ptr.dfe = addr_of %p.var
// CHECK:STDOUT: %T.as.Destroy.impl.Op.call.loc9: init %empty_tuple.type = call %bound_method.loc9_3(%addr.loc9_3)
// CHECK:STDOUT: %T.as.Destroy.impl.Op.bound.loc8: <bound method> = bound_method %s.var, constants.%T.as.Destroy.impl.Op.777
// CHECK:STDOUT: %T.as.Destroy.impl.Op.bound.loc8: <bound method> = bound_method %s.var, constants.%T.as.Destroy.impl.Op.062
// CHECK:STDOUT: <elided>
// CHECK:STDOUT: %bound_method.loc8: <bound method> = bound_method %s.var, %T.as.Destroy.impl.Op.specific_fn.2
// CHECK:STDOUT: %addr.loc8: %ptr.5c7 = addr_of %s.var
@@ -364,8 +364,8 @@ fn F() {
// CHECK:STDOUT: %ptr.ff5: type = ptr_type %const [concrete]
// CHECK:STDOUT: %foo.type: type = fn_type @foo [concrete]
// CHECK:STDOUT: %foo: %foo.type = struct_value () [concrete]
// CHECK:STDOUT: %T.as.Destroy.impl.Op.type.365: type = fn_type @T.as.Destroy.impl.Op, @T.as.Destroy.impl(%const) [concrete]
// CHECK:STDOUT: %T.as.Destroy.impl.Op.b63: %T.as.Destroy.impl.Op.type.365 = struct_value () [concrete]
// CHECK:STDOUT: %T.as.Destroy.impl.Op.type.4ce: type = fn_type @T.as.Destroy.impl.Op, @T.as.Destroy.impl(%const) [concrete]
// CHECK:STDOUT: %T.as.Destroy.impl.Op.7f5: %T.as.Destroy.impl.Op.type.4ce = struct_value () [concrete]
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: imports {
@@ -405,7 +405,7 @@ fn F() {
// CHECK:STDOUT: %s.ref: ref %const = name_ref s, %s
// CHECK:STDOUT: %addr.loc11: %ptr.ff5 = addr_of %s.ref
// CHECK:STDOUT: %foo.call: init %empty_tuple.type = call imports.%foo.decl(%addr.loc11)
// CHECK:STDOUT: %T.as.Destroy.impl.Op.bound: <bound method> = bound_method %s.var, constants.%T.as.Destroy.impl.Op.b63
// CHECK:STDOUT: %T.as.Destroy.impl.Op.bound: <bound method> = bound_method %s.var, constants.%T.as.Destroy.impl.Op.7f5
// CHECK:STDOUT: <elided>
// CHECK:STDOUT: %bound_method: <bound method> = bound_method %s.var, %T.as.Destroy.impl.Op.specific_fn
// CHECK:STDOUT: %addr.loc10: %ptr.ff5 = addr_of %s.var
@@ -558,8 +558,8 @@ fn F() {
// CHECK:STDOUT: %.c5d: type = cpp_overload_set_type @Destroy.Op [concrete]
// CHECK:STDOUT: %empty_struct: %.c5d = struct_value () [concrete]
// CHECK:STDOUT: %ptr.5c7: type = ptr_type %S [concrete]
// CHECK:STDOUT: %T.as.Destroy.impl.Op.type.f1d: type = fn_type @T.as.Destroy.impl.Op, @T.as.Destroy.impl(%S) [concrete]
// CHECK:STDOUT: %T.as.Destroy.impl.Op.777: %T.as.Destroy.impl.Op.type.f1d = struct_value () [concrete]
// CHECK:STDOUT: %T.as.Destroy.impl.Op.type.d46: type = fn_type @T.as.Destroy.impl.Op, @T.as.Destroy.impl(%S) [concrete]
// CHECK:STDOUT: %T.as.Destroy.impl.Op.062: %T.as.Destroy.impl.Op.type.d46 = struct_value () [concrete]
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: imports {
@@ -592,7 +592,7 @@ fn F() {
// CHECK:STDOUT: %foo.ref: %.c5d = name_ref foo, imports.%.a21 [concrete = constants.%empty_struct]
// CHECK:STDOUT: %s.ref: ref %S = name_ref s, %s
// CHECK:STDOUT: %addr.loc16: %ptr.5c7 = addr_of %s.ref
// CHECK:STDOUT: %T.as.Destroy.impl.Op.bound: <bound method> = bound_method %s.var, constants.%T.as.Destroy.impl.Op.777
// CHECK:STDOUT: %T.as.Destroy.impl.Op.bound: <bound method> = bound_method %s.var, constants.%T.as.Destroy.impl.Op.062
// CHECK:STDOUT: <elided>
// CHECK:STDOUT: %bound_method: <bound method> = bound_method %s.var, %T.as.Destroy.impl.Op.specific_fn
// CHECK:STDOUT: %addr.loc8: %ptr.5c7 = addr_of %s.var