Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion lib/SILOptimizer/Mandatory/DIMemoryUseCollector.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,13 @@ static void gatherDestroysOfContainer(const DIMemoryObjectInfo &memoryInfo,
static unsigned getElementCountRec(TypeExpansionContext context,
SILModule &Module, SILType T,
bool IsSelfOfNonDelegatingInitializer) {
// If this is a tuple, it is always recursively flattened.
// If this is a tuple, it is usually recursively flattened.
if (CanTupleType TT = T.getAs<TupleType>()) {
assert(!IsSelfOfNonDelegatingInitializer && "self never has tuple type");
// Don't flatten tuples containing pack expansions - they have a dynamic
// number of elements that cannot be statically enumerated.
if (TT->containsPackExpansionType())
return 1;
unsigned NumElements = 0;
for (unsigned i = 0, e = TT->getNumElements(); i < e; ++i)
NumElements +=
Expand Down Expand Up @@ -216,6 +220,11 @@ static SILType getElementTypeRec(TypeExpansionContext context,
// If this is a tuple type, walk into it.
if (CanTupleType TT = T.getAs<TupleType>()) {
assert(!IsSelfOfNonDelegatingInitializer && "self never has tuple type");
// Tuples containing pack expansions are treated as single elements.
if (TT->containsPackExpansionType()) {
assert(EltNo == 0 && "pack expansion tuple should be single element");
return T;
}
for (unsigned i = 0, e = TT->getNumElements(); i < e; ++i) {
auto FieldType = T.getTupleElementType(i);
unsigned NumFieldElements =
Expand Down Expand Up @@ -309,6 +318,13 @@ SILValue DIMemoryObjectInfo::emitElementAddressForDestroy(
if (CanTupleType TT = PointeeType.getAs<TupleType>()) {
assert(!IsSelf && "self never has tuple type");

// Tuples containing pack expansions are treated as single elements
// since they have a dynamic number of elements.
if (TT->containsPackExpansionType()) {
assert(EltNo == 0 && "pack expansion tuple should be single element");
return Ptr;
}

// Figure out which field we're walking into.
unsigned FieldNo = 0;
for (unsigned i = 0, e = TT->getNumElements(); i < e; ++i) {
Expand Down Expand Up @@ -406,6 +422,12 @@ static void getPathStringToElementRec(TypeExpansionContext context,
return;
}

// Tuples containing pack expansions are treated as single elements.
if (TT->containsPackExpansionType()) {
assert(EltNo == 0 && "pack expansion tuple should be single element");
return;
}

unsigned FieldNo = 0;
for (unsigned i = 0, e = TT->getNumElements(); i < e; ++i) {
auto Field = TT->getElement(i);
Expand Down
214 changes: 214 additions & 0 deletions test/Interpreter/variadic_generic_tuple_init.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
// RUN: %target-run-simple-swift(-target %target-swift-5.9-abi-triple)

// REQUIRES: executable_test

// UNSUPPORTED: use_os_stdlib
// UNSUPPORTED: back_deployment_runtime

// This test verifies runtime behavior of structs with pack expansion tuple
// properties, ensuring that definite initialization correctly handles
// the initialization and that values are accessible at runtime.

import StdlibUnittest

var suite = TestSuite("VariadicGenericTupleInit")

// MARK: - Basic struct with pack expansion tuple

struct BasicPackTuple<each T> {
let values: (repeat each T)

init(_ values: repeat each T) {
self.values = (repeat each values)
}
}

suite.test("BasicPackTuple single element") {
let s = BasicPackTuple(42)
expectEqual(42, s.values)
}

suite.test("BasicPackTuple multiple elements") {
let s = BasicPackTuple(1, "hello", true)
expectEqual(1, s.values.0)
expectEqual("hello", s.values.1)
expectEqual(true, s.values.2)
}

// MARK: - Struct with mixed properties

struct MixedProperties<each T> {
let prefix: Int
let packed: (repeat each T)
let suffix: String

init(prefix: Int, suffix: String, _ values: repeat each T) {
self.prefix = prefix
self.packed = (repeat each values)
self.suffix = suffix
}
}

suite.test("MixedProperties") {
let s = MixedProperties(prefix: 10, suffix: "end", 3.14, "middle")
expectEqual(10, s.prefix)
expectEqual(3.14, s.packed.0)
expectEqual("middle", s.packed.1)
expectEqual("end", s.suffix)
}

// MARK: - Conditional initialization

struct ConditionalInit<each T> {
let values: (repeat each T)
let flag: Bool

init(condition: Bool, _ values: repeat each T) {
self.flag = condition
if condition {
self.values = (repeat each values)
} else {
self.values = (repeat each values)
}
}
}

suite.test("ConditionalInit true branch") {
let s = ConditionalInit(condition: true, "a", "b")
expectTrue(s.flag)
expectEqual("a", s.values.0)
expectEqual("b", s.values.1)
}

suite.test("ConditionalInit false branch") {
let s = ConditionalInit(condition: false, 1, 2, 3)
expectFalse(s.flag)
expectEqual(1, s.values.0)
expectEqual(2, s.values.1)
expectEqual(3, s.values.2)
}

// MARK: - Nested struct

struct Outer<each T> {
struct Inner {
let data: (repeat each T)

init(_ values: repeat each T) {
self.data = (repeat each values)
}
}

let inner: Inner

init(_ values: repeat each T) {
self.inner = Inner(repeat each values)
}
}

suite.test("Nested struct") {
let s = Outer(100, 200)
expectEqual(100, s.inner.data.0)
expectEqual(200, s.inner.data.1)
}

// MARK: - Class with pack expansion tuple

class ClassWithPackTuple<each T> {
let values: (repeat each T)

init(_ values: repeat each T) {
self.values = (repeat each values)
}
}

suite.test("Class with pack tuple") {
let c = ClassWithPackTuple("x", "y", "z")
expectEqual("x", c.values.0)
expectEqual("y", c.values.1)
expectEqual("z", c.values.2)
}

// MARK: - Failable initializer

struct FailablePackInit<each T> {
let values: (repeat each T)

init?(_ values: repeat each T, shouldFail: Bool) {
if shouldFail {
return nil
}
self.values = (repeat each values)
}
}

suite.test("Failable init success") {
let s = FailablePackInit(1, 2, shouldFail: false)
expectNotNil(s)
expectEqual(1, s!.values.0)
expectEqual(2, s!.values.1)
}

suite.test("Failable init failure") {
let s = FailablePackInit(1, 2, shouldFail: true)
expectNil(s)
}

// MARK: - Throwing initializer

struct ThrowingPackInit<each T> {
let values: (repeat each T)

init(_ values: repeat each T) throws {
self.values = (repeat each values)
}
}

suite.test("Throwing init") {
do {
let s = try ThrowingPackInit("a", "b")
expectEqual("a", s.values.0)
expectEqual("b", s.values.1)
} catch {
expectUnreachable("Should not throw")
}
}

// MARK: - Throwing initializer with pack expansion tuple (crash case)
// This is a minimal reproducer for the DI crash. The crash occurs when:
// 1. Struct has multiple properties including a pack expansion tuple
// 2. Throwing initializer where a throwing call comes AFTER the pack tuple assignment
// 3. DI generates cleanup code that incorrectly uses tuple_element_addr on the pack tuple

func produceValue<T>(type: T.Type) -> T {
fatalError("not called in test")
}

func produceInt() throws -> Int {
return 42
}

struct ThrowingContainer<each Input> {
let inputs: (repeat each Input)
let tag: Int

init() throws {
self.inputs = (repeat produceValue(type: (each Input).self))
self.tag = try produceInt()
}

init(inputs: repeat each Input, tag: Int) {
self.inputs = (repeat each inputs)
self.tag = tag
}
}

suite.test("Throwing init with pack tuple - success path") {
// Use the non-throwing init for runtime test
let c = ThrowingContainer<Int, String>(inputs: 42, "test", tag: 99)
expectEqual(42, c.inputs.0)
expectEqual("test", c.inputs.1)
expectEqual(99, c.tag)
}

runAllTests()
Loading