Reimplement destroy as an interface (#5678)

This changes `Destroy` to use an interface for its implementation.

Note that this change includes a lot of test updates. Even when
`Destroy` is a no-op, it still causes code generation as part of
determining that.

Originally I was trying to use ranges to cut down the scope of this, and
to a degree I think they have. But a flipside here is that cases where
no destructors should be generated -- particularly globals -- would be
needed to completely remove destructor calls. Even for ranges, the range
can often include the destructor placement. So I've shifted
frame-of-thought a little: accept a bunch of destructor churn, because
destructors are needed and will be prevalent. The verbosity is a feature
of the design to make desugaring apparent in IR, not a bug.
This commit is contained in:
Jon Ross-Perkins
2025-06-27 21:57:14 +00:00
committed by GitHub
parent bba037738d
commit 0722dab0ef
171 changed files with 11900 additions and 1434 deletions
@@ -112,6 +112,7 @@ fn CallFAndGIncomplete() {
// CHECK:STDOUT: imports {
// CHECK:STDOUT: %Core: <namespace> = namespace file.%Core.import, [concrete] {
// CHECK:STDOUT: import Core//prelude
// CHECK:STDOUT: import Core//prelude/...
// CHECK:STDOUT: }
// CHECK:STDOUT: }
// CHECK:STDOUT:
@@ -197,6 +198,7 @@ fn CallFAndGIncomplete() {
// CHECK:STDOUT:
// CHECK:STDOUT: constants {
// CHECK:STDOUT: %CallFAndGIncomplete.type: type = fn_type @CallFAndGIncomplete [concrete]
// CHECK:STDOUT: %empty_tuple.type: type = tuple_type () [concrete]
// CHECK:STDOUT: %CallFAndGIncomplete: %CallFAndGIncomplete.type = struct_value () [concrete]
// CHECK:STDOUT: %ReturnCUnused.type: type = fn_type @ReturnCUnused [concrete]
// CHECK:STDOUT: %ReturnCUnused: %ReturnCUnused.type = struct_value () [concrete]
@@ -209,6 +211,17 @@ fn CallFAndGIncomplete() {
// CHECK:STDOUT: %complete_type: <witness> = complete_type_witness %empty_struct_type [concrete]
// CHECK:STDOUT: %ReturnDUsed.type: type = fn_type @ReturnDUsed [concrete]
// CHECK:STDOUT: %ReturnDUsed: %ReturnDUsed.type = struct_value () [concrete]
// CHECK:STDOUT: %Destroy.type: type = facet_type <@Destroy> [concrete]
// CHECK:STDOUT: %Op.type.bae: type = fn_type @Op.1 [concrete]
// CHECK:STDOUT: %T: type = bind_symbolic_name T, 0 [symbolic]
// CHECK:STDOUT: %Op.type.bc9: type = fn_type @Op.2, @impl(%T) [symbolic]
// CHECK:STDOUT: %Op.46f: %Op.type.bc9 = struct_value () [symbolic]
// CHECK:STDOUT: %Destroy.impl_witness.45e: <witness> = impl_witness imports.%Destroy.impl_witness_table, @impl(%D) [concrete]
// CHECK:STDOUT: %Op.type.548: type = fn_type @Op.2, @impl(%D) [concrete]
// CHECK:STDOUT: %Op.2d4: %Op.type.548 = struct_value () [concrete]
// CHECK:STDOUT: %Destroy.facet: %Destroy.type = facet_value %D, (%Destroy.impl_witness.45e) [concrete]
// CHECK:STDOUT: %.64e: type = fn_type_with_self_type %Op.type.bae, %Destroy.facet [concrete]
// CHECK:STDOUT: %Op.specific_fn: <specific function> = specific_function %Op.2d4, @Op.2(%D) [concrete]
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: imports {
@@ -220,10 +233,15 @@ fn CallFAndGIncomplete() {
// CHECK:STDOUT: %Main.ReturnDUsed: %ReturnDUsed.type = import_ref Main//incomplete_return, ReturnDUsed, loaded [concrete = constants.%ReturnDUsed]
// CHECK:STDOUT: %Main.Call = import_ref Main//incomplete_return, Call, unloaded
// CHECK:STDOUT: %Core: <namespace> = namespace file.%Core.import, [concrete] {
// CHECK:STDOUT: .Destroy = %Core.Destroy
// CHECK:STDOUT: import Core//prelude
// CHECK:STDOUT: import Core//prelude/...
// CHECK:STDOUT: }
// CHECK:STDOUT: %Main.import_ref.8f2: <witness> = import_ref Main//incomplete_return, loc37_10, loaded [concrete = constants.%complete_type]
// CHECK:STDOUT: %Main.import_ref.cab = import_ref Main//incomplete_return, inst20 [no loc], unloaded
// CHECK:STDOUT: %Core.Destroy: type = import_ref Core//prelude/parts/destroy, Destroy, loaded [concrete = constants.%Destroy.type]
// CHECK:STDOUT: %Core.import_ref.0b9: @impl.%Op.type (%Op.type.bc9) = import_ref Core//prelude/parts/destroy, loc8_23, loaded [symbolic = @impl.%Op (constants.%Op.46f)]
// CHECK:STDOUT: %Destroy.impl_witness_table = impl_witness_table (%Core.import_ref.0b9), @impl [concrete]
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: file {
@@ -266,6 +284,18 @@ fn CallFAndGIncomplete() {
// CHECK:STDOUT: %.loc34_15.1: ref %D = temporary_storage
// CHECK:STDOUT: %ReturnDUsed.call: init %D = call %ReturnDUsed.ref() to %.loc34_15.1
// CHECK:STDOUT: %.loc34_15.2: ref %D = temporary %.loc34_15.1, %ReturnDUsed.call
// CHECK:STDOUT: %impl.elem0.loc34: %.64e = impl_witness_access constants.%Destroy.impl_witness.45e, element0 [concrete = constants.%Op.2d4]
// CHECK:STDOUT: %bound_method.loc34_15.1: <bound method> = bound_method %.loc34_15.1, %impl.elem0.loc34
// CHECK:STDOUT: %specific_fn.loc34: <specific function> = specific_function %impl.elem0.loc34, @Op.2(constants.%D) [concrete = constants.%Op.specific_fn]
// CHECK:STDOUT: %bound_method.loc34_15.2: <bound method> = bound_method %.loc34_15.1, %specific_fn.loc34
// CHECK:STDOUT: %.loc34_15.3: %D = bind_value %.loc34_15.1
// CHECK:STDOUT: %no_op.loc34: init %empty_tuple.type = call %bound_method.loc34_15.2(%.loc34_15.3)
// CHECK:STDOUT: %impl.elem0.loc33: %.64e = impl_witness_access constants.%Destroy.impl_witness.45e, element0 [concrete = constants.%Op.2d4]
// CHECK:STDOUT: %bound_method.loc33_17.1: <bound method> = bound_method %.loc33_17.1, %impl.elem0.loc33
// CHECK:STDOUT: %specific_fn.loc33: <specific function> = specific_function %impl.elem0.loc33, @Op.2(constants.%D) [concrete = constants.%Op.specific_fn]
// CHECK:STDOUT: %bound_method.loc33_17.2: <bound method> = bound_method %.loc33_17.1, %specific_fn.loc33
// CHECK:STDOUT: %.loc33_17.3: %D = bind_value %.loc33_17.1
// CHECK:STDOUT: %no_op.loc33: init %empty_tuple.type = call %bound_method.loc33_17.2(%.loc33_17.3)
// CHECK:STDOUT: return
// CHECK:STDOUT: }
// CHECK:STDOUT: