Skip to content

Conversation

@alex-reilly-dd
Copy link

@alex-reilly-dd alex-reilly-dd commented Dec 11, 2025

Full disclosure: This code was almost entirely written by Claude. I ran into a compiler crash, thought I'd have Claude take a crack at fixing it (very skeptical that it would), and as far as I can tell, it has actually fixed the issue.

That said, this is pretty far outside of my area of expertise. If this PR is against contributor guidelines or so bad that it's not at all usable, I will happily close it. I'm not here to take a moral stand on AI. I just want the bug fixed.

I also know my commit messages are lacking. I plan on squashing before I merge.

Issue

The following code causes a compiler crash on the version of Swift that ships with Xcode 26.1. I have confirmed it also crashes when run with the latest main branch by building the compiler locally and using it to execute the repro case.

Repro

import Foundation

public struct CorpusEntry<each Input: Codable & Sendable> {
    public let input: (repeat each Input)
    public let foo: Int

    public init(from decoder: any Decoder) throws {
        let container = try decoder.container(keyedBy: CorpusEntryCodingKeys.self)
        var contentsContainer = try container.nestedUnkeyedContainer(forKey: .input)

        self.input = try (repeat contentsContainer.decode((each Input).self))

        self.foo = try container.decode(Int.self, forKey: .foo)
    }
}

public enum CorpusEntryCodingKeys: String, CodingKey {
    case input
    case foo
}

Crash stack trace

This is from the Swift Development Snapshot on December 1st

SIL verification failed: tuple_element_addr cannot be used with tuples containing pack expansions: !tupleType.containsPackExpansionType()
Verifying instruction:
     %110 = struct_element_addr %3 : $*CorpusEntry<repeat each Input>, #CorpusEntry.input // user: %111
->   %111 = tuple_element_addr %110 : $*(repeat each Input), 0 // user: %112
     destroy_addr %111 : $*repeat each Input      // id: %112
In function:
// CorpusEntry.init(from:)
// Isolation: unspecified
sil [ossa] @$s10ScratchPad11CorpusEntryV4fromACyxxQp_QPGs7Decoder_p_tKcfC : $@convention(method) <each Input where repeat each Input : Decodable, repeat each Input : Encodable, repeat each Input : Sendable> (@in any Decoder, @thin CorpusEntry<repeat each Input>.Type) -> (@out CorpusEntry<repeat each Input>, @error any Error) {
// %0 "$return_value"                             // user: %63
// %1 "decoder"                                   // users: %107, %64, %8, %5
// %2 "$metatype"
bb0(%0 : $*CorpusEntry<repeat each Input>, %1 : $*any Decoder, %2 : $@thin CorpusEntry<repeat each Input>.Type):
  %3 = alloc_stack [dynamic_lifetime] [lexical] [var_decl] $CorpusEntry<repeat each Input>, var, name "self", type $CorpusEntry<repeat each Input> // users: %65, %63, %55, %41, %110, %66, %115
  %4 = integer_literal $Builtin.Int2, 0           // users: %71, %77, %97
  debug_value %1, let, name "decoder", argno 1, expr op_deref // id: %5
  debug_value undef : $any Error, var, name "$error", argno 2 // id: %6
  %7 = alloc_stack [lexical] [var_decl] $KeyedDecodingContainer<CorpusEntryCodingKeys>, let, name "container" // users: %62, %61, %52, %103, %102, %96, %95, %18, %76, %75, %11, %70
  %8 = open_existential_addr immutable_access %1 to $*@opened("37153042-D63C-11F0-9770-36BA171CBFA0", any Decoder) Self // users: %11, %11, %10
  %9 = metatype $@thick CorpusEntryCodingKeys.Type // user: %11
  %10 = witness_method $@opened("37153042-D63C-11F0-9770-36BA171CBFA0", any Decoder) Self, #Decoder.container : <Self where Self : Decoder><Key where Key : CodingKey> (Self) -> (Key.Type) throws -> KeyedDecodingContainer<Key>, %8 : $*@opened("37153042-D63C-11F0-9770-36BA171CBFA0", any Decoder) Self : $@convention(witness_method: Decoder) <τ_0_0 where τ_0_0 : Decoder><τ_1_0 where τ_1_0 : CodingKey> (@thick τ_1_0.Type, @in_guaranteed τ_0_0) -> (@out KeyedDecodingContainer<τ_1_0>, @error any Error) // type-defs: %8; user: %11
  try_apply %10<@opened("37153042-D63C-11F0-9770-36BA171CBFA0", any Decoder) Self, CorpusEntryCodingKeys>(%7, %9, %8) : $@convention(witness_method: Decoder) <τ_0_0 where τ_0_0 : Decoder><τ_1_0 where τ_1_0 : CodingKey> (@thick τ_1_0.Type, @in_guaranteed τ_0_0) -> (@out KeyedDecodingContainer<τ_1_0>, @error any Error), normal bb1, error bb8 // type-defs: %8; id: %11

bb1(%12 : $()):                                   // Preds: bb0
  %13 = alloc_stack [lexical] [var_decl] $any UnkeyedDecodingContainer, var, name "contentsContainer", type $any UnkeyedDecodingContainer // users: %18, %33, %74, %94, %93, %101, %100, %60, %59
  %14 = enum $CorpusEntryCodingKeys, #CorpusEntryCodingKeys.input!enumelt // user: %16
  %15 = alloc_stack $CorpusEntryCodingKeys        // users: %20, %18, %73, %16
  store %14 to [trivial] %15                      // id: %16
  // function_ref KeyedDecodingContainer.nestedUnkeyedContainer(forKey:)
  %17 = function_ref @$ss22KeyedDecodingContainerV013nestedUnkeyedC06forKeys0ebC0_px_tKF : $@convention(method) <τ_0_0 where τ_0_0 : CodingKey> (@in_guaranteed τ_0_0, @in_guaranteed KeyedDecodingContainer<τ_0_0>) -> (@out any UnkeyedDecodingContainer, @error any Error) // user: %18
  try_apply %17<CorpusEntryCodingKeys>(%13, %15, %7) : $@convention(method) <τ_0_0 where τ_0_0 : CodingKey> (@in_guaranteed τ_0_0, @in_guaranteed KeyedDecodingContainer<τ_0_0>) -> (@out any UnkeyedDecodingContainer, @error any Error), normal bb2, error bb9 // id: %18

bb2(%19 : $()):                                   // Preds: bb1
  dealloc_stack %15                               // id: %20
  %21 = alloc_stack $(repeat each Input)          // users: %46, %44, %92, %89, %31
  %22 = integer_literal $Builtin.Word, 0          // user: %25
  %23 = integer_literal $Builtin.Word, 1          // user: %39
  %24 = pack_length $Pack{repeat each Input}      // user: %27
  br bb3(%22)                                     // id: %25

// %26                                            // users: %39, %82, %29, %27
bb3(%26 : $Builtin.Word):                         // Preds: bb5 bb2
  %27 = builtin "cmp_eq_Word"(%26, %24) : $Builtin.Int1 // user: %28
  cond_br %27, bb6, bb4                           // id: %28

bb4:                                              // Preds: bb3
  %29 = dynamic_pack_index %26 of $Pack{repeat each Input} // users: %31, %30
  %30 = open_pack_element %29 of <each Input where repeat each Input : Decodable, repeat each Input : Encodable, repeat each Input : Sendable> at <Pack{repeat each Input}>, shape $each Input, uuid "37154DF2-D63C-11F0-9770-36BA171CBFA0" // users: %36, %32, %31
  %31 = tuple_pack_element_addr %29 of %21 as $*@pack_element("37154DF2-D63C-11F0-9770-36BA171CBFA0") each Input // user: %36
  %32 = metatype $@thick (@pack_element("37154DF2-D63C-11F0-9770-36BA171CBFA0") each Input).Type // type-defs: %30; user: %36
  %33 = begin_access [modify] [static] %13        // users: %38, %79, %34
  %34 = open_existential_addr mutable_access %33 to $*@opened("3715528E-D63C-11F0-9770-36BA171CBFA0", any UnkeyedDecodingContainer) Self // users: %36, %36, %35
  %35 = witness_method $@opened("3715528E-D63C-11F0-9770-36BA171CBFA0", any UnkeyedDecodingContainer) Self, #UnkeyedDecodingContainer.decode : <Self where Self : UnkeyedDecodingContainer><T where T : Decodable> (inout Self) -> (T.Type) throws -> T, %34 : $*@opened("3715528E-D63C-11F0-9770-36BA171CBFA0", any UnkeyedDecodingContainer) Self : $@convention(witness_method: UnkeyedDecodingContainer) <τ_0_0 where τ_0_0 : UnkeyedDecodingContainer><τ_1_0 where τ_1_0 : Decodable> (@thick τ_1_0.Type, @inout τ_0_0) -> (@out τ_1_0, @error any Error) // type-defs: %34; user: %36
  try_apply %35<@opened("3715528E-D63C-11F0-9770-36BA171CBFA0", any UnkeyedDecodingContainer) Self, @pack_element("37154DF2-D63C-11F0-9770-36BA171CBFA0") each Input>(%31, %32, %34) : $@convention(witness_method: UnkeyedDecodingContainer) <τ_0_0 where τ_0_0 : UnkeyedDecodingContainer><τ_1_0 where τ_1_0 : Decodable> (@thick τ_1_0.Type, @inout τ_0_0) -> (@out τ_1_0, @error any Error), normal bb5, error bb10 // type-defs: %30, %34; id: %36

bb5(%37 : $()):                                   // Preds: bb4
  end_access %33                                  // id: %38
  %39 = builtin "add_Word"(%26, %23) : $Builtin.Word // user: %40
  br bb3(%39)                                     // id: %40

bb6:                                              // Preds: bb3
  %41 = begin_access [modify] [static] %3         // users: %45, %42
  %42 = struct_element_addr %41, #CorpusEntry.input // user: %44
  %43 = integer_literal $Builtin.Int2, 1          // user: %104
  copy_addr [take] %21 to [init] %42              // id: %44
  end_access %41                                  // id: %45
  dealloc_stack %21                               // id: %46
  %47 = metatype $@thin Int.Type                  // user: %52
  %48 = enum $CorpusEntryCodingKeys, #CorpusEntryCodingKeys.foo!enumelt // user: %50
  %49 = alloc_stack $CorpusEntryCodingKeys        // users: %54, %52, %99, %50
  store %48 to [trivial] %49                      // id: %50
  // function_ref KeyedDecodingContainer.decode(_:forKey:)
  %51 = function_ref @$ss22KeyedDecodingContainerV6decode_6forKeyS2im_xtKF : $@convention(method) <τ_0_0 where τ_0_0 : CodingKey> (@thin Int.Type, @in_guaranteed τ_0_0, @in_guaranteed KeyedDecodingContainer<τ_0_0>) -> (Int, @error any Error) // user: %52
  try_apply %51<CorpusEntryCodingKeys>(%47, %49, %7) : $@convention(method) <τ_0_0 where τ_0_0 : CodingKey> (@thin Int.Type, @in_guaranteed τ_0_0, @in_guaranteed KeyedDecodingContainer<τ_0_0>) -> (Int, @error any Error), normal bb7, error bb14 // id: %52

// %53                                            // user: %57
bb7(%53 : $Int):                                  // Preds: bb6
  dealloc_stack %49                               // id: %54
  %55 = begin_access [modify] [static] %3         // users: %58, %56
  %56 = struct_element_addr %55, #CorpusEntry.foo // user: %57
  store %53 to [trivial] %56                      // id: %57
  end_access %55                                  // id: %58
  destroy_addr %13                                // id: %59
  dealloc_stack %13                               // id: %60
  destroy_addr %7                                 // id: %61
  dealloc_stack %7                                // id: %62
  copy_addr %3 to [init] %0                       // id: %63
  destroy_addr %1                                 // id: %64
  destroy_addr %3                                 // id: %65
  dealloc_stack %3                                // id: %66
  %67 = tuple ()                                  // user: %68
  return %67                                      // id: %68

// %69                                            // user: %71
bb8(%69 : @owned $any Error):                     // Preds: bb0
  dealloc_stack %7                                // id: %70
  br bb15(%69, %4)                                // id: %71

// %72                                            // user: %77
bb9(%72 : @owned $any Error):                     // Preds: bb1
  dealloc_stack %15                               // id: %73
  dealloc_stack %13                               // id: %74
  destroy_addr %7                                 // id: %75
  dealloc_stack %7                                // id: %76
  br bb15(%72, %4)                                // id: %77

// %78                                            // user: %97
bb10(%78 : @owned $any Error):                    // Preds: bb4
  end_access %33                                  // id: %79
  %80 = integer_literal $Builtin.Word, 0          // user: %84
  %81 = integer_literal $Builtin.Word, 1          // user: %86
  br bb11(%26)                                    // id: %82

// %83                                            // users: %86, %84
bb11(%83 : $Builtin.Word):                        // Preds: bb12 bb10
  %84 = builtin "cmp_eq_Word"(%83, %80) : $Builtin.Int1 // user: %85
  cond_br %84, bb13, bb12                         // id: %85

bb12:                                             // Preds: bb11
  %86 = builtin "sub_Word"(%83, %81) : $Builtin.Word // users: %91, %87
  %87 = dynamic_pack_index %86 of $Pack{repeat each Input} // users: %89, %88
  %88 = open_pack_element %87 of <each Input where repeat each Input : Decodable, repeat each Input : Encodable, repeat each Input : Sendable> at <Pack{repeat each Input}>, shape $each Input, uuid "37162B64-D63C-11F0-9770-36BA171CBFA0" // user: %89
  %89 = tuple_pack_element_addr %87 of %21 as $*@pack_element("37162B64-D63C-11F0-9770-36BA171CBFA0") each Input // user: %90
  destroy_addr %89                                // id: %90
  br bb11(%86)                                    // id: %91

bb13:                                             // Preds: bb11
  dealloc_stack %21                               // id: %92
  destroy_addr %13                                // id: %93
  dealloc_stack %13                               // id: %94
  destroy_addr %7                                 // id: %95
  dealloc_stack %7                                // id: %96
  br bb15(%78, %4)                                // id: %97

// %98                                            // user: %104
bb14(%98 : @owned $any Error):                    // Preds: bb6
  dealloc_stack %49                               // id: %99
  destroy_addr %13                                // id: %100
  dealloc_stack %13                               // id: %101
  destroy_addr %7                                 // id: %102
  dealloc_stack %7                                // id: %103
  br bb15(%98, %43)                               // id: %104

// %105                                           // user: %116
// %106                                           // user: %108
bb15(%105 : @owned $any Error, %106 : $Builtin.Int2): // Preds: bb8 bb9 bb13 bb14
  destroy_addr %1                                 // id: %107
  %108 = builtin "trunc_Int2_Int1"(%106) : $Builtin.Int1 // user: %109
  cond_br %108, bb16, bb17                        // id: %109

bb16:                                             // Preds: bb15
  %110 = struct_element_addr %3, #CorpusEntry.input // user: %111
  %111 = tuple_element_addr %110, 0               // user: %112
  destroy_addr %111                               // id: %112
  br bb18                                         // id: %113

bb17:                                             // Preds: bb15
  br bb18                                         // id: %114

bb18:                                             // Preds: bb17 bb16
  dealloc_stack %3                                // id: %115
  throw %105                                      // id: %116
} // end sil function '$s10ScratchPad11CorpusEntryV4fromACyxxQp_QPGs7Decoder_p_tKcfC'

1.	Apple Swift version 6.3-dev (LLVM 35f48306184cd25, Swift a69dbb3366e64d8)
2.	Compiling with effective version 5.10
3.	While verifying SIL function "@$s10ScratchPad11CorpusEntryV4fromACyxxQp_QPGs7Decoder_p_tKcfC".
 for 'init(from:)' (at /Users/alex.reilly/Documents/Swift/ScratchPad/ScratchPad/main.swift:16:12)
 #0 0x000000010aa2dfd4 (/Users/alex.reilly/Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2025-12-01-a.xctoolchain/usr/bin/swift-frontend+0x105ccdfd4)
 #1 0x000000010aa2c2f0 (/Users/alex.reilly/Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2025-12-01-a.xctoolchain/usr/bin/swift-frontend+0x105ccc2f0)
 #2 0x000000010aa2ea64 (/Users/alex.reilly/Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2025-12-01-a.xctoolchain/usr/bin/swift-frontend+0x105ccea64)
 #3 0x0000000195ee3744 (/usr/lib/system/libsystem_platform.dylib+0x1804e3744)
 #4 0x0000000195ed9888 (/usr/lib/system/libsystem_pthread.dylib+0x1804d9888)
 #5 0x0000000195dde850 (/usr/lib/system/libsystem_c.dylib+0x1803de850)
 #6 0x0000000105aa0474 (/Users/alex.reilly/Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2025-12-01-a.xctoolchain/usr/bin/swift-frontend+0x100d40474)
 #7 0x0000000105aa9ad8 (/Users/alex.reilly/Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2025-12-01-a.xctoolchain/usr/bin/swift-frontend+0x100d49ad8)
 #8 0x0000000105aa87b0 (/Users/alex.reilly/Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2025-12-01-a.xctoolchain/usr/bin/swift-frontend+0x100d487b0)
 #9 0x0000000105aa52c8 (/Users/alex.reilly/Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2025-12-01-a.xctoolchain/usr/bin/swift-frontend+0x100d452c8)
#10 0x0000000105aa0e10 (/Users/alex.reilly/Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2025-12-01-a.xctoolchain/usr/bin/swift-frontend+0x100d40e10)
#11 0x0000000105aa3abc (/Users/alex.reilly/Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2025-12-01-a.xctoolchain/usr/bin/swift-frontend+0x100d43abc)
#12 0x0000000105aa3998 (/Users/alex.reilly/Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2025-12-01-a.xctoolchain/usr/bin/swift-frontend+0x100d43998)
#13 0x00000001052a1b24 (/Users/alex.reilly/Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2025-12-01-a.xctoolchain/usr/bin/swift-frontend+0x100541b24)
#14 0x0000000105038ce8 (/Users/alex.reilly/Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2025-12-01-a.xctoolchain/usr/bin/swift-frontend+0x1002d8ce8)
#15 0x00000001050382d4 (/Users/alex.reilly/Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2025-12-01-a.xctoolchain/usr/bin/swift-frontend+0x1002d82d4)
#16 0x0000000105049068 (/Users/alex.reilly/Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2025-12-01-a.xctoolchain/usr/bin/swift-frontend+0x1002e9068)
#17 0x000000010503c350 (/Users/alex.reilly/Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2025-12-01-a.xctoolchain/usr/bin/swift-frontend+0x1002dc350)
#18 0x0000000105039aac (/Users/alex.reilly/Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2025-12-01-a.xctoolchain/usr/bin/swift-frontend+0x1002d9aac)
#19 0x0000000104d998a4 (/Users/alex.reilly/Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2025-12-01-a.xctoolchain/usr/bin/swift-frontend+0x1000398a4)
#20 0x0000000195b11d54

Fix

Looks like the issue is that there are some portions of the codebase that don't guard against parameter packs, and therefor treat them as normal, multi-element tuples.

Here's how Claude described the fix (edited for brevity).

Parameter packs have unknown arity at compile time, which breaks the assumptions of the
DefiniteInitialization (DI) pass.

Consider the Codable struct that was crashing:

  struct Coder<each Input: Codable>: Codable {
      let inputs: (repeat each Input)  // How many elements? Unknown!
  }

The tuple (repeat each Input) could have 0, 1, 5, or any number of elements depending on how it's instantiated:

  • Coder<Int> → 1 element
  • Coder<Int, String, Bool> → 3 elements
  • Coder<> → 0 elements (empty pack)

DI's element-counting logic (getElementCountRec) was trying to iterate the tuple's elements and assign fixed
indices, but you can't assign static indices to a dynamically-sized structure.

The Solution

Treat the entire pack-expansion tuple as one opaque unit:

  // In getElementCountRec:
  if (TT->containsPackExpansionType())
    return 1;  // Treat as single atomic element

This means:

  • DI tracks the whole (repeat each Input) tuple as a single "blob"
  • No attempt to decompose it into individual elements
  • The runtime handles the actual per-element initialization via tuple_pack_element_addr (which takes a dynamic
    index)

// End Claude explanation

Addendum

This crash is very very particular. The repro case must do all of the following.

  • Throwing initializer
  • More than one field
  • Other field initialized after the parameter pack field
  • Other field initialized with the result of a throwing function

Here is as far as I've pared down the crash case

func throwingCall() throws -> Int { return 0 }

struct ThrowingAfterPackInit<each T> {
    let packed: (repeat each T)
    let other: Int

    init(packed: repeat each T) throws {
        self.packed = (repeat each packed)
        self.other = try throwingCall()
    }
}

@eeckstein eeckstein requested a review from rjmccall December 12, 2025 15:28
Copy link
Contributor

@slavapestov slavapestov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My only concern with this change is that it requires adding the check in four places. Is there a fifth that was missed? Maybe there's a better way to factor this out? If not, that's OK, but someone who understands DI should take a closer look. CC @gottesmm

@slavapestov
Copy link
Contributor

Also this technically changes language semantics, because it precludes the possibility of initializing a tuple like (Int, repeat each T) piecemeal, the way that (Int, String) can be, for example. But we already don't have a syntax to initialize the second component of such a tuple anyway, and if we never do, it's fine.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants