From 01233ba7a3b2881c4c55acd8e78266dc71ee2cd4 Mon Sep 17 00:00:00 2001 From: Mike Ash Date: Thu, 7 May 2026 14:31:23 -0400 Subject: [PATCH 01/25] [Reflection] Support parameter packs in generic substitutions. Fill in the GenericParamKind::TypePack case in MetadataReader's getGenericSubst method. rdar://165872192 --- include/swift/Remote/MetadataReader.h | 79 +++++++++++++++++-- .../Reflection/reflect_variadic_generic.swift | 65 +++++++++++++++ 2 files changed, 139 insertions(+), 5 deletions(-) create mode 100644 validation-test/Reflection/reflect_variadic_generic.swift diff --git a/include/swift/Remote/MetadataReader.h b/include/swift/Remote/MetadataReader.h index 6ff58aa1b06..076c0f02160 100644 --- a/include/swift/Remote/MetadataReader.h +++ b/include/swift/Remote/MetadataReader.h @@ -3150,14 +3150,28 @@ private: auto numGenericArgs = generics->getGenericContextHeader().getNumArguments(); - + auto offsetToGenericArgs = readGenericArgsOffset(metadata, descriptor); if (!offsetToGenericArgs) return {}; - auto genericArgsAddr = getAddress(metadata) + auto genericArgsBaseAddr = getAddress(metadata) + sizeof(StoredPointer) * *offsetToGenericArgs; + // The generic argument layout begins with one StoredSize "shape class" + // entry per same-shape equivalence class. These hold pack lengths for + // pack-typed key arguments. Skip past them to reach the metadata pointers. + auto packShapeHeader = generics->getGenericPackShapeHeader(); + auto packShapeDescriptors = generics->getGenericPackShapeDescriptors(); + if (numGenericArgs < packShapeHeader.NumShapeClasses) + return {}; + numGenericArgs -= packShapeHeader.NumShapeClasses; + + auto genericArgsAddr = genericArgsBaseAddr + + sizeof(StoredPointer) * packShapeHeader.NumShapeClasses; + + unsigned packIndex = 0; + std::vector builtSubsts; for (auto param : generics->getGenericParams()) { switch (param.getKind()) { @@ -3187,10 +3201,65 @@ private: return {}; } break; - + case GenericParamKind::TypePack: - // assert(false && "Packs not supported here yet"); - return {}; + if (param.hasKeyArgument()) { + if (numGenericArgs == 0) + return {}; + --numGenericArgs; + + // Find the matching pack shape descriptor. By invariant the + // metadata pack descriptors come before any witness table pack + // descriptors, in the same order as the pack-typed parameters + // with key arguments, so the next descriptor is ours. + if (packIndex >= packShapeDescriptors.size()) + return {}; + const auto &packDescriptor = packShapeDescriptors[packIndex++]; + if (packDescriptor.Kind != GenericPackKind::Metadata) + return {}; + + // The pack length is stored in the shape class slot at the + // start of the generic argument layout. + auto packLengthAddr = genericArgsBaseAddr + + sizeof(StoredPointer) * packDescriptor.ShapeClass; + StoredSize packLength; + if (!Reader->readInteger(packLengthAddr, &packLength)) + return {}; + + // Read the pack pointer. Its low bit indicates heap vs. stack + // lifetime; the actual element array is at the address with the + // low bit cleared. + StoredPointer rawPackPtr; + if (!Reader->readInteger(genericArgsAddr, &rawPackPtr)) + return {}; + genericArgsAddr += sizeof(StoredPointer); + + auto packElementsAddr = RemoteAddress( + rawPackPtr & ~StoredPointer(1), + genericArgsAddr.getAddressSpace()); + + std::vector elements; + elements.reserve(packLength); + for (StoredSize i = 0; i < packLength; ++i) { + RemoteAddress elementMetadata; + if (!Reader->template readRemoteAddress( + packElementsAddr, elementMetadata)) { + return {}; + } + packElementsAddr += sizeof(StoredPointer); + + auto builtElement = readTypeFromMetadata( + elementMetadata, false, recursion_limit); + if (!builtElement) + return {}; + elements.push_back(builtElement); + } + + builtSubsts.push_back(Builder.createPackType(elements)); + } else { + return {}; + } + break; default: // We don't know about this kind of parameter. diff --git a/validation-test/Reflection/reflect_variadic_generic.swift b/validation-test/Reflection/reflect_variadic_generic.swift new file mode 100644 index 00000000000..6d1eb8fdf7c --- /dev/null +++ b/validation-test/Reflection/reflect_variadic_generic.swift @@ -0,0 +1,65 @@ +// RUN: %empty-directory(%t) +// RUN: %target-build-swift -target %target-swift-5.9-abi-triple -lswiftSwiftReflectionTest %s -o %t/reflect_variadic_generic +// RUN: %target-codesign %t/reflect_variadic_generic + +// RUN: %target-run %target-swift-reflection-test %t/reflect_variadic_generic | tee /dev/stderr | %FileCheck %s --dump-input=fail + +// REQUIRES: reflection_test_support +// REQUIRES: executable_test +// UNSUPPORTED: use_os_stdlib +// UNSUPPORTED: asan + +import SwiftReflectionTest + +struct VariadicStruct { + var values: (repeat each T) +} + +class VariadicHolder { + var contents: (repeat each T) + init(_ values: repeat each T) { + contents = (repeat each values) + } +} + +// Empty pack. +reflect(any: VariadicStruct< >(values: ())) + +// CHECK: Reflecting an existential. +// CHECK: Type reference: +// CHECK: (bound_generic_struct reflect_variadic_generic.VariadicStruct +// CHECK-NEXT: (pack)) + +// One-element pack. +reflect(any: VariadicStruct(values: 5)) + +// CHECK: Reflecting an existential. +// CHECK: Type reference: +// CHECK: (bound_generic_struct reflect_variadic_generic.VariadicStruct +// CHECK-NEXT: (pack +// CHECK-NEXT: (struct Swift.Int))) + +// Multi-element pack with mixed element types. +reflect(any: VariadicStruct(values: (1, "two", true))) + +// CHECK: Reflecting an existential. +// CHECK: Type reference: +// CHECK: (bound_generic_struct reflect_variadic_generic.VariadicStruct +// CHECK-NEXT: (pack +// CHECK-NEXT: (struct Swift.Int) +// CHECK-NEXT: (struct Swift.String) +// CHECK-NEXT: (struct Swift.Bool))) + +// Class with parameter pack. +reflect(object: VariadicHolder(7, 3.14)) + +// CHECK: Reflecting an object. +// CHECK: Type reference: +// CHECK: (bound_generic_class reflect_variadic_generic.VariadicHolder +// CHECK-NEXT: (pack +// CHECK-NEXT: (struct Swift.Int) +// CHECK-NEXT: (struct Swift.Double))) + +doneReflecting() + +// CHECK: Done. From 2bcfe0b09229af903dc3c4f619bfcefebf553e43 Mon Sep 17 00:00:00 2001 From: Mike Ash Date: Thu, 7 May 2026 14:07:29 -0400 Subject: [PATCH 02/25] [Reflection] Support inspection of InlineArray. Report the array count as NumFields, and report the same info for each child since InlineArray is homogeneous. rdar://176382725 --- include/swift/Remote/MetadataReader.h | 17 +++ include/swift/RemoteInspection/TypeLowering.h | 7 +- .../SwiftRemoteMirrorTypes.h | 3 + .../public/RemoteInspection/TypeLowering.cpp | 11 +- .../SwiftRemoteMirror/SwiftRemoteMirror.cpp | 14 +- test/Reflection/typeref_lowering.swift | 10 +- .../Reflection/reflect_InlineArray.swift | 130 ++++++++++++++++++ 7 files changed, 185 insertions(+), 7 deletions(-) create mode 100644 validation-test/Reflection/reflect_InlineArray.swift diff --git a/include/swift/Remote/MetadataReader.h b/include/swift/Remote/MetadataReader.h index 6ff58aa1b06..dd1d8ed7bb6 100644 --- a/include/swift/Remote/MetadataReader.h +++ b/include/swift/Remote/MetadataReader.h @@ -1243,6 +1243,23 @@ public: TypeCache[TypeCacheKey] = BuiltForeign; return BuiltForeign; } + case MetadataKind::FixedArray: { + auto fixedArray = cast>(Meta); + auto elementAddress = RemoteAddress( + fixedArray->Element, MetadataAddress.getAddressSpace()); + auto Element = + readTypeFromMetadata(elementAddress, false, recursion_limit); + if (!Element) return BuiltType(); + + auto count = static_cast(fixedArray->Count); + BuiltType Size = (count < 0) ? Builder.createNegativeIntegerType(count) + : Builder.createIntegerType(count); + if (!Size) return BuiltType(); + + auto BuiltFixedArray = Builder.createBuiltinFixedArrayType(Size, Element); + TypeCache[TypeCacheKey] = BuiltFixedArray; + return BuiltFixedArray; + } case MetadataKind::HeapLocalVariable: case MetadataKind::HeapGenericLocalVariable: case MetadataKind::ErrorObject: diff --git a/include/swift/RemoteInspection/TypeLowering.h b/include/swift/RemoteInspection/TypeLowering.h index 98f0ed33174..144111b198f 100644 --- a/include/swift/RemoteInspection/TypeLowering.h +++ b/include/swift/RemoteInspection/TypeLowering.h @@ -383,17 +383,22 @@ public: /// Array based layouts like Builtin.FixedArray class ArrayTypeInfo : public TypeInfo { + const TypeRef *ElementTR; const TypeInfo *ElementTI; + intptr_t ElementCount; public: - explicit ArrayTypeInfo(intptr_t size, const TypeInfo *elementTI); + explicit ArrayTypeInfo(intptr_t size, const TypeRef *elementTR, + const TypeInfo *elementTI); bool readExtraInhabitantIndex(remote::MemoryReader &reader, remote::RemoteAddress address, int *extraInhabitantIndex) const override; BitMask getSpareBits(TypeConverter &TC, bool &hasAddrOnly) const override; + const TypeRef *getElementTypeRef() const { return ElementTR; } const TypeInfo *getElementTypeInfo() const { return ElementTI; } + intptr_t getElementCount() const { return ElementCount; } static bool classof(const TypeInfo *TI) { return TI->getKind() == TypeInfoKind::Array; } diff --git a/include/swift/SwiftRemoteMirror/SwiftRemoteMirrorTypes.h b/include/swift/SwiftRemoteMirror/SwiftRemoteMirrorTypes.h index ae52d80b5db..e83974458f1 100644 --- a/include/swift/SwiftRemoteMirror/SwiftRemoteMirrorTypes.h +++ b/include/swift/SwiftRemoteMirror/SwiftRemoteMirrorTypes.h @@ -145,6 +145,9 @@ typedef enum swift_layout_kind { SWIFT_CLOSURE_CONTEXT, // A contiguous list of N Ts, typically for Builtin.FixedArray. + // NumFields will report the array count. All children are the same type, so + // users can make a single call to swift_reflection_childOfTypeRef regardless + // of the count. SWIFT_ARRAY, } swift_layout_kind_t; diff --git a/stdlib/public/RemoteInspection/TypeLowering.cpp b/stdlib/public/RemoteInspection/TypeLowering.cpp index 336eb29f36f..307c468b635 100644 --- a/stdlib/public/RemoteInspection/TypeLowering.cpp +++ b/stdlib/public/RemoteInspection/TypeLowering.cpp @@ -222,6 +222,9 @@ public: case TypeInfoKind::Array: { printHeader("array"); printBasic(TI); + auto &ArrayTI = cast(TI); + printField("count", std::to_string(ArrayTI.getElementCount())); + printRec(*ArrayTI.getElementTypeInfo()); stream << ")"; return; } @@ -493,7 +496,8 @@ BitMask RecordTypeInfo::getSpareBits(TypeConverter &TC, bool &hasAddrOnly) const return mask; } -ArrayTypeInfo::ArrayTypeInfo(intptr_t size, const TypeInfo *elementTI) +ArrayTypeInfo::ArrayTypeInfo(intptr_t size, const TypeRef *elementTR, + const TypeInfo *elementTI) : TypeInfo(TypeInfoKind::Array, /* size */ elementTI->getStride() * size, /* alignment */ elementTI->getAlignment(), @@ -501,7 +505,7 @@ ArrayTypeInfo::ArrayTypeInfo(intptr_t size, const TypeInfo *elementTI) /* numExtraInhabitants */ elementTI->getNumExtraInhabitants(), /* borrowability */ elementTI->getBorrowability(), /* FixedArray is always afd */ true), - ElementTI(elementTI) {} + ElementTR(elementTR), ElementTI(elementTI), ElementCount(size) {} bool ArrayTypeInfo::readExtraInhabitantIndex( remote::MemoryReader &reader, remote::RemoteAddress address, @@ -2762,7 +2766,8 @@ public: return nullptr; } - return TC.makeTypeInfo(sizeInt->getValue(), elementTI); + return TC.makeTypeInfo(sizeInt->getValue(), + BA->getElementType(), elementTI); } const TypeInfo *visitBuiltinBorrowTypeRef(const BuiltinBorrowTypeRef *BA) { diff --git a/stdlib/public/SwiftRemoteMirror/SwiftRemoteMirror.cpp b/stdlib/public/SwiftRemoteMirror/SwiftRemoteMirror.cpp index 94e4213c951..f1b27c5f7f9 100644 --- a/stdlib/public/SwiftRemoteMirror/SwiftRemoteMirror.cpp +++ b/stdlib/public/SwiftRemoteMirror/SwiftRemoteMirror.cpp @@ -586,6 +586,8 @@ static swift_typeinfo_t convertTypeInfo(const TypeInfo *TI) { NumFields = RecordTI->getNumCases(); } else if (auto *RecordTI = dyn_cast(TI)) { NumFields = RecordTI->getNumFields(); + } else if (auto *ArrayTI = dyn_cast(TI)) { + NumFields = ArrayTI->getElementCount(); } return { @@ -601,13 +603,23 @@ static swift_childinfo_t convertChild(const TypeInfo *TI, unsigned Index) { if (!TI) return {}; + if (auto *ArrayTI = dyn_cast(TI)) { + auto *ElementTI = ArrayTI->getElementTypeInfo(); + return { + "element", + Index * ElementTI->getStride(), + getTypeInfoKind(*ElementTI), + reinterpret_cast(ArrayTI->getElementTypeRef()), + }; + } + const FieldInfo *FieldInfo = nullptr; if (auto *EnumTI = dyn_cast(TI)) { FieldInfo = &(EnumTI->getCases()[Index]); } else if (auto *RecordTI = dyn_cast(TI)) { FieldInfo = &(RecordTI->getFields()[Index]); } else { - assert(false && "convertChild(TI): TI must be record or enum typeinfo"); + assert(false && "convertChild(TI): TI must be record, enum, or array typeinfo"); return { "unknown TypeInfo kind", 0, diff --git a/test/Reflection/typeref_lowering.swift b/test/Reflection/typeref_lowering.swift index ebd8e633ad5..2a8f28a1c97 100644 --- a/test/Reflection/typeref_lowering.swift +++ b/test/Reflection/typeref_lowering.swift @@ -1277,12 +1277,18 @@ $1_SiBV // CHECK-64: (builtin_fixed_array // CHECK-64-NEXT: (integer value=2) // CHECK-64-NEXT: (struct Swift.Int)) -// CHECK-64-NEXT: (array size=16 alignment=8 stride=16 num_extra_inhabitants=0 bitwise_takable=1) +// CHECK-64-NEXT: (array size=16 alignment=8 stride=16 num_extra_inhabitants=0 bitwise_takable=1 count=2 +// CHECK-64-NEXT: (struct size=8 alignment=8 stride=8 num_extra_inhabitants=0 bitwise_takable=1 +// CHECK-64-NEXT: (field name=_value offset=0 +// CHECK-64-NEXT: (builtin size=8 alignment=8 stride=8 num_extra_inhabitants=0 bitwise_takable=1)))) // CHECK-32: (builtin_fixed_array // CHECK-32-NEXT: (integer value=2) // CHECK-32-NEXT: (struct Swift.Int)) -// CHECK-32-NEXT: (array size=8 alignment=4 stride=8 num_extra_inhabitants=0 bitwise_takable=1) +// CHECK-32-NEXT: (array size=8 alignment=4 stride=8 num_extra_inhabitants=0 bitwise_takable=1 count=2 +// CHECK-32-NEXT: (struct size=4 alignment=4 stride=4 num_extra_inhabitants=0 bitwise_takable=1 +// CHECK-32-NEXT: (field name=_value offset=0 +// CHECK-32-NEXT: (builtin size=4 alignment=4 stride=4 num_extra_inhabitants=0 bitwise_takable=1)))) SiBW // CHECK-64: (builtin_borrow diff --git a/validation-test/Reflection/reflect_InlineArray.swift b/validation-test/Reflection/reflect_InlineArray.swift new file mode 100644 index 00000000000..cf6e93c3de3 --- /dev/null +++ b/validation-test/Reflection/reflect_InlineArray.swift @@ -0,0 +1,130 @@ +// RUN: %empty-directory(%t) +// RUN: %target-build-swift -lswiftSwiftReflectionTest %s -o %t/reflect_InlineArray -Xfrontend -disable-availability-checking +// RUN: %target-codesign %t/reflect_InlineArray + +// RUN: %target-run %target-swift-reflection-test %t/reflect_InlineArray | %FileCheck %s --check-prefix=CHECK-%target-ptrsize + +// REQUIRES: reflection_test_support +// REQUIRES: executable_test +// UNSUPPORTED: use_os_stdlib +// UNSUPPORTED: back_deployment_runtime +// UNSUPPORTED: asan + +import SwiftReflectionTest + +class TestClass { + var t: InlineArray<42, Int> + var u: InlineArray<42, Int> + var v: InlineArray<42, Int> + var bytes: InlineArray<7, UInt8> + var nested: InlineArray<3, InlineArray<5, Int16>> + init(t: InlineArray<42, Int>) { + self.t = t + self.u = t + self.v = t + self.bytes = .init(repeating: 0) + self.nested = .init(repeating: .init(repeating: 0)) + } +} + +var obj = TestClass(t: .init(repeating: 42)) + +reflect(object: obj) + +// CHECK-64: Reflecting an object. +// CHECK-64: Instance pointer in child address space: 0x{{[0-9a-fA-F]+}} +// CHECK-64: Type reference: +// CHECK-64: (class reflect_InlineArray.TestClass) + +// CHECK-64: Type info: +// CHECK-64: (class_instance size={{[0-9]+}} alignment=8 stride={{[0-9]+}} num_extra_inhabitants=0 bitwise_takable=1 +// CHECK-64: (field name=t offset={{[0-9]+}} +// CHECK-64: (struct size=336 alignment=8 stride=336 num_extra_inhabitants=0 bitwise_takable=1 +// CHECK-64: (field name=_storage offset=0 +// CHECK-64: (array size=336 alignment=8 stride=336 num_extra_inhabitants=0 bitwise_takable=1 count=42 +// CHECK-64: (struct size=8 alignment=8 stride=8 num_extra_inhabitants=0 bitwise_takable=1 +// CHECK-64: (field name=_value offset=0 +// CHECK-64: (builtin size=8 alignment=8 stride=8 num_extra_inhabitants=0 bitwise_takable=1))))))) +// CHECK-64: (field name=u offset={{[0-9]+}} +// CHECK-64: (struct size=336 alignment=8 stride=336 num_extra_inhabitants=0 bitwise_takable=1 +// CHECK-64: (field name=_storage offset=0 +// CHECK-64: (array size=336 alignment=8 stride=336 num_extra_inhabitants=0 bitwise_takable=1 count=42 +// CHECK-64: (struct size=8 alignment=8 stride=8 num_extra_inhabitants=0 bitwise_takable=1 +// CHECK-64: (field name=_value offset=0 +// CHECK-64: (builtin size=8 alignment=8 stride=8 num_extra_inhabitants=0 bitwise_takable=1))))))) +// CHECK-64: (field name=v offset={{[0-9]+}} +// CHECK-64: (struct size=336 alignment=8 stride=336 num_extra_inhabitants=0 bitwise_takable=1 +// CHECK-64: (field name=_storage offset=0 +// CHECK-64: (array size=336 alignment=8 stride=336 num_extra_inhabitants=0 bitwise_takable=1 count=42 +// CHECK-64: (struct size=8 alignment=8 stride=8 num_extra_inhabitants=0 bitwise_takable=1 +// CHECK-64: (field name=_value offset=0 +// CHECK-64: (builtin size=8 alignment=8 stride=8 num_extra_inhabitants=0 bitwise_takable=1))))))) +// CHECK-64: (field name=bytes offset={{[0-9]+}} +// CHECK-64: (struct size=7 alignment=1 stride=7 num_extra_inhabitants=0 bitwise_takable=1 +// CHECK-64: (field name=_storage offset=0 +// CHECK-64: (array size=7 alignment=1 stride=7 num_extra_inhabitants=0 bitwise_takable=1 count=7 +// CHECK-64: (struct size=1 alignment=1 stride=1 num_extra_inhabitants=0 bitwise_takable=1 +// CHECK-64: (field name=_value offset=0 +// CHECK-64: (builtin size=1 alignment=1 stride=1 num_extra_inhabitants=0 bitwise_takable=1))))))) +// CHECK-64: (field name=nested offset={{[0-9]+}} +// CHECK-64: (struct size=30 alignment=2 stride=30 num_extra_inhabitants=0 bitwise_takable=1 +// CHECK-64: (field name=_storage offset=0 +// CHECK-64: (array size=30 alignment=2 stride=30 num_extra_inhabitants=0 bitwise_takable=1 count=3 +// CHECK-64: (struct size=10 alignment=2 stride=10 num_extra_inhabitants=0 bitwise_takable=1 +// CHECK-64: (field name=_storage offset=0 +// CHECK-64: (array size=10 alignment=2 stride=10 num_extra_inhabitants=0 bitwise_takable=1 count=5 +// CHECK-64: (struct size=2 alignment=2 stride=2 num_extra_inhabitants=0 bitwise_takable=1 +// CHECK-64: (field name=_value offset=0 +// CHECK-64: (builtin size=2 alignment=2 stride=2 num_extra_inhabitants=0 bitwise_takable=1))))))))))) + +// CHECK-32: Reflecting an object. +// CHECK-32: Instance pointer in child address space: 0x{{[0-9a-fA-F]+}} +// CHECK-32: Type reference: +// CHECK-32: (class reflect_InlineArray.TestClass) + +// CHECK-32: Type info: +// CHECK-32: (class_instance size={{[0-9]+}} alignment={{[0-9]+}} stride={{[0-9]+}} num_extra_inhabitants={{[0-9]+}} bitwise_takable=1 +// CHECK-32: (field name=t offset={{[0-9]+}} +// CHECK-32: (struct size=168 alignment=4 stride=168 num_extra_inhabitants=0 bitwise_takable=1 +// CHECK-32: (field name=_storage offset=0 +// CHECK-32: (array size=168 alignment=4 stride=168 num_extra_inhabitants=0 bitwise_takable=1 count=42 +// CHECK-32: (struct size=4 alignment=4 stride=4 num_extra_inhabitants=0 bitwise_takable=1 +// CHECK-32: (field name=_value offset=0 +// CHECK-32: (builtin size=4 alignment=4 stride=4 num_extra_inhabitants=0 bitwise_takable=1))))))) +// CHECK-32: (field name=u offset={{[0-9]+}} +// CHECK-32: (struct size=168 alignment=4 stride=168 num_extra_inhabitants=0 bitwise_takable=1 +// CHECK-32: (field name=_storage offset=0 +// CHECK-32: (array size=168 alignment=4 stride=168 num_extra_inhabitants=0 bitwise_takable=1 count=42 +// CHECK-32: (struct size=4 alignment=4 stride=4 num_extra_inhabitants=0 bitwise_takable=1 +// CHECK-32: (field name=_value offset=0 +// CHECK-32: (builtin size=4 alignment=4 stride=4 num_extra_inhabitants=0 bitwise_takable=1))))))) +// CHECK-32: (field name=v offset={{[0-9]+}} +// CHECK-32: (struct size=168 alignment=4 stride=168 num_extra_inhabitants=0 bitwise_takable=1 +// CHECK-32: (field name=_storage offset=0 +// CHECK-32: (array size=168 alignment=4 stride=168 num_extra_inhabitants=0 bitwise_takable=1 count=42 +// CHECK-32: (struct size=4 alignment=4 stride=4 num_extra_inhabitants=0 bitwise_takable=1 +// CHECK-32: (field name=_value offset=0 +// CHECK-32: (builtin size=4 alignment=4 stride=4 num_extra_inhabitants=0 bitwise_takable=1))))))) +// CHECK-32: (field name=bytes offset={{[0-9]+}} +// CHECK-32: (struct size=7 alignment=1 stride=7 num_extra_inhabitants=0 bitwise_takable=1 +// CHECK-32: (field name=_storage offset=0 +// CHECK-32: (array size=7 alignment=1 stride=7 num_extra_inhabitants=0 bitwise_takable=1 count=7 +// CHECK-32: (struct size=1 alignment=1 stride=1 num_extra_inhabitants=0 bitwise_takable=1 +// CHECK-32: (field name=_value offset=0 +// CHECK-32: (builtin size=1 alignment=1 stride=1 num_extra_inhabitants=0 bitwise_takable=1))))))) +// CHECK-32: (field name=nested offset={{[0-9]+}} +// CHECK-32: (struct size=30 alignment=2 stride=30 num_extra_inhabitants=0 bitwise_takable=1 +// CHECK-32: (field name=_storage offset=0 +// CHECK-32: (array size=30 alignment=2 stride=30 num_extra_inhabitants=0 bitwise_takable=1 count=3 +// CHECK-32: (struct size=10 alignment=2 stride=10 num_extra_inhabitants=0 bitwise_takable=1 +// CHECK-32: (field name=_storage offset=0 +// CHECK-32: (array size=10 alignment=2 stride=10 num_extra_inhabitants=0 bitwise_takable=1 count=5 +// CHECK-32: (struct size=2 alignment=2 stride=2 num_extra_inhabitants=0 bitwise_takable=1 +// CHECK-32: (field name=_value offset=0 +// CHECK-32: (builtin size=2 alignment=2 stride=2 num_extra_inhabitants=0 bitwise_takable=1))))))))))) + +doneReflecting() + +// CHECK-64: Done. + +// CHECK-32: Done. From f6b456403cdd6bcf972c0c2ba942b14141df324e Mon Sep 17 00:00:00 2001 From: Mike Ash Date: Wed, 13 May 2026 09:56:09 -0400 Subject: [PATCH 03/25] [Distributed] Fix leaks in failure paths of swift_getFunctionFullNameFromMangledName. Fix several early returns which didn't free the string copy made earlier in the function. Fix most of them by doing the string copy only at the very end where it's needed. When we race to insert into the cache and we lose that race, then free the copies before returning the other thread's cached entry. rdar://170736413 --- stdlib/public/runtime/Casting.cpp | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/stdlib/public/runtime/Casting.cpp b/stdlib/public/runtime/Casting.cpp index 319f3dab73e..5e639d816d4 100644 --- a/stdlib/public/runtime/Casting.cpp +++ b/stdlib/public/runtime/Casting.cpp @@ -267,12 +267,7 @@ swift::swift_getFunctionFullNameFromMangledName( return TypeNamePair{nullptr, 0}; } - // Read-only lookup failed, we may need to demangle and cache the entry. - // We have to copy the string to be able to refer to it "forever": - auto copy = (char *)malloc(mangledNameLength); - memcpy(copy, mangledNameStart, mangledNameLength); - mangledName = StringRef(copy, mangledNameLength); - + // Read-only lookup failed. Demangle and cache the entry. std::string demangled; StackAllocatedDemangler<1024> Dem; NodePointer node = Dem.demangleSymbol(mangledName); @@ -361,6 +356,11 @@ swift::swift_getFunctionFullNameFromMangledName( } demangled += ")"; + // We have to copy the string to be able to refer to it "forever": + auto copy = (char *)malloc(mangledNameLength); + memcpy(copy, mangledNameStart, mangledNameLength); + llvm::StringRef copiedMangledName(copy, mangledNameLength); + // We have to copy the string to be able to refer to it; auto size = demangled.size(); auto result = (char *)malloc(size + 1); @@ -370,7 +370,15 @@ swift::swift_getFunctionFullNameFromMangledName( { LazyMutex::ScopedLock guard(MangledToPrettyFunctionNameCacheLock); - cache.insert({mangledName, {result, size}}); + auto [it, inserted] = cache.insert({copiedMangledName, {result, size}}); + if (!inserted) { + // We raced with another thread and lost. Free our data and return theirs. + free(copy); + free(result); + return {it->second.first, it->second.second}; + } + + // Successfully inserted into the cache. Return our cached value. return TypeNamePair{result, size}; } } From 280eed8b7dbfb6c430fc9542051adae4a1b5a72d Mon Sep 17 00:00:00 2001 From: Aidan Hall Date: Tue, 12 May 2026 19:10:24 +0100 Subject: [PATCH 04/25] Lifetimes: Replace deps on partial_apply parameters with 'captures' --- include/swift/AST/LifetimeDependence.h | 15 +++++ lib/AST/LifetimeDependence.cpp | 77 ++++++++++++++++++++++++++ lib/SIL/IR/SILBuilder.cpp | 6 +- 3 files changed, 97 insertions(+), 1 deletion(-) diff --git a/include/swift/AST/LifetimeDependence.h b/include/swift/AST/LifetimeDependence.h index d1af822138f..0a5aadc2ddc 100644 --- a/include/swift/AST/LifetimeDependence.h +++ b/include/swift/AST/LifetimeDependence.h @@ -418,6 +418,21 @@ public: uncurry(ASTContext &ctx, ArrayRef inner, unsigned numInnerParams, unsigned numOuterParams); + /// Compute the lifetime dependencies for the result of a partial application + /// of a SIL function with the given lifetimes and number of parameters, + /// binding the given number of arguments. + /// + /// Dependencies on parameters that are bound by the partial_apply are + /// replaced with 'captures' dependencies. + /// + /// Example: binding 1 argument of a 2-argument function. + /// %b = ... : B + /// %f = function_ref @closure : (A, B) -> @lifetime(borrow 1) C + /// %c = partial_apply %f(%b) : (A) -> @lifetime(captures) C + static ArrayRef + partialApply(ASTContext &ctx, ArrayRef lifetimes, + unsigned numFormalParams, unsigned numBoundParams); + bool operator==(const LifetimeDependenceInfo &other) const { return this->hasImmortalSpecifier() == other.hasImmortalSpecifier() && this->hasCaptures() == other.hasCaptures() && diff --git a/lib/AST/LifetimeDependence.cpp b/lib/AST/LifetimeDependence.cpp index 88a3538e488..4f96f2ba884 100644 --- a/lib/AST/LifetimeDependence.cpp +++ b/lib/AST/LifetimeDependence.cpp @@ -2064,6 +2064,83 @@ ArrayRef LifetimeDependenceInfo::uncurry( return ctx.AllocateCopy(uncurried); } +ArrayRef LifetimeDependenceInfo::partialApply( + ASTContext &ctx, ArrayRef lifetimes, + unsigned numFormalParams, unsigned numBoundParams) { + + ASSERT(numBoundParams > 0 && + "A partial application must bind at least 1 argument"); + ASSERT(numBoundParams <= numFormalParams && + "A partial application can only bind as many parameters as the " + "function has."); + + // How many parameters the resulting closure will have. + const unsigned numClosureParams = numFormalParams - numBoundParams; + + SmallVector curried; + + for (const auto &dep : lifetimes) { + // Determine the new target index. + unsigned targetIndex; + if (dep.getTargetIndex() == numFormalParams) { + // The target is the result. + // Its index is the number of parameters. + targetIndex = numClosureParams; + } else if (dep.getTargetIndex() >= numClosureParams) { + // The target is a captured parameter. + // The resulting closure does not need a lifetime dependence entry for it. + continue; + } else { + // The target is an uncaptured parameter. + // Its index remains the same. + targetIndex = dep.getTargetIndex(); + } + + auto flags = dep.flags; + + const auto captureBoundParams = [&](IndexSubset *indices) -> IndexSubset * { + if (!indices) + return nullptr; + + ASSERT(indices->getCapacity() == numFormalParams && + "There should be 1 index per parameter. SIL functions cannot have " + "an implicit self parameter."); + + auto bits = indices->getBitVector(); + + if (bits.find_last() >= int(numClosureParams)) { + // One of the lifetime source parameters is bound by the partial_apply. + // This becomes a captures dependence in the resulting closure. + flags.setCaptures(true); + } + + // Remove the indices of the captured parameters. + + if (bits.find_first() >= int(numClosureParams)) { + // All lifetime sources are captured. The resulting empty list of + // indices should be represented with a nullptr. + return nullptr; + } + + bits.resize(numClosureParams); + + return IndexSubset::get(ctx, bits); + }; + + auto inherit = captureBoundParams(dep.getInheritIndices()); + auto scope = captureBoundParams(dep.getScopeIndices()); + auto addressable = captureBoundParams(dep.getAddressableIndices()); + auto conditionallyAddressable = + captureBoundParams(dep.getConditionallyAddressableIndices()); + + curried.push_back(LifetimeDependenceInfo(inherit, scope, targetIndex, + addressable, + conditionallyAddressable, flags)); + } + + return ctx.AllocateCopy(curried); +} + void LifetimeDependenceInfo::dump() const { llvm::errs() << "target: " << getTargetIndex() << '\n'; if (hasImmortalSpecifier()) { diff --git a/lib/SIL/IR/SILBuilder.cpp b/lib/SIL/IR/SILBuilder.cpp index 1f119403299..69454716363 100644 --- a/lib/SIL/IR/SILBuilder.cpp +++ b/lib/SIL/IR/SILBuilder.cpp @@ -72,7 +72,11 @@ SILType SILBuilder::getPartialApplyResultType( .intoBuilder() .withRepresentation(SILFunctionType::Representation::Thick) .withIsolation(resultIsolation) - .withIsPseudogeneric(false); + .withIsPseudogeneric(false) + .withLifetimeDependencies(LifetimeDependenceInfo::partialApply( + context.getContext()->getASTContext(), + FTI->getLifetimeDependencies(), FTI->getNumParameters(), + argCount)); if (onStack) extInfoBuilder = extInfoBuilder.withNoEscape(); auto extInfo = extInfoBuilder.build(); From 2809bfc5e08571e2690e80d9ad5ee6fefa9af343 Mon Sep 17 00:00:00 2001 From: Aidan Hall Date: Tue, 12 May 2026 21:13:20 +0100 Subject: [PATCH 05/25] Lifetimes: Test partial_apply result dependencies --- test/IRGen/rdar176795176.swift | 136 ++++++++++++++ test/SILGen/partial_apply_lifetime.swift | 218 +++++++++++++++++++++++ 2 files changed, 354 insertions(+) create mode 100644 test/IRGen/rdar176795176.swift create mode 100644 test/SILGen/partial_apply_lifetime.swift diff --git a/test/IRGen/rdar176795176.swift b/test/IRGen/rdar176795176.swift new file mode 100644 index 00000000000..4a65255c881 --- /dev/null +++ b/test/IRGen/rdar176795176.swift @@ -0,0 +1,136 @@ +// RUN: %target-swift-frontend -emit-ir -O %s + +// Minimal reproducer for IRGen crash: rdar://176795176 + +// MARK: - NE + +public struct NE: ~Escapable { + @_lifetime(immortal) + public init() {} +} + +// MARK: - INES + +public protocol INES: ~Escapable { + @_lifetime(copy self) + consuming func f(at indices: Range) -> NE +} + +// MARK: - NES + +public protocol NES: ~Copyable { + associatedtype Bytes: INES & ~Escapable +} + +// MARK: - IRef + +public protocol IRef: ~Copyable { + associatedtype PR: ~Copyable + associatedtype P: BitwiseCopyable + + static func a(of p: P) -> UnsafePointer +} + +// MARK: - Ref + +@frozen +public struct Ref: ~Escapable & ~Copyable +where + L: ~Escapable, + C: ~Copyable & IRef, + PR: ~Copyable +{ + @usableFromInline + let p: C.P + + @_lifetime(immortal) + @usableFromInline + init(immortal p: C.P) { + self.p = p + } +} + +extension Ref: Copyable +where + L: ~Escapable, + C: Copyable +{} + +extension Ref: BitwiseCopyable +where + L: ~Escapable, + C: Copyable +{} + +// MARK: - The crashing extension + +extension Ref: INES +where + L: ~Escapable, + C: Copyable, + PR: NES & ~Copyable +{ + @_lifetime(copy self) + public consuming func f( + at indices: Range + ) -> NE { + let bytes = self.fromPR { + return _overrideL(immortal: $0[bytes: indices]) + } + return _overrideL(bytes, copy: self) + } +} + +extension Ref +where + L: ~Escapable, + C: ~Copyable, + PR: ~Copyable +{ + @_lifetime(copy self) + fileprivate func fromPR( + body: (borrowing PR) throws(Thrown) -> Output + ) throws(Thrown) -> Output + where + Thrown: Error, + Output: ~Copyable + { + return _overrideL( + try body(C.a(of: self.p).pointee), + copy: self + ) + } +} + +// Subscripts used by f +extension NES where Self: ~Copyable { + subscript(bytes indices: Range) -> NE { + @_lifetime(borrow self) + get { + NE() + } + } +} + +// _overrideL stubs — mimicking stdlib + +@_unsafeNonescapableResult +@_lifetime(immortal) +@_alwaysEmitIntoClient +@_transparent +public func _overrideL( + immortal value: consuming T +) -> T { + value +} + +@_unsafeNonescapableResult +@_lifetime(copy source) +@_alwaysEmitIntoClient +@_transparent +public func _overrideL( + _ value: consuming T, + copy source: borrowing U +) -> T { + value +} diff --git a/test/SILGen/partial_apply_lifetime.swift b/test/SILGen/partial_apply_lifetime.swift new file mode 100644 index 00000000000..97bd20bc8a5 --- /dev/null +++ b/test/SILGen/partial_apply_lifetime.swift @@ -0,0 +1,218 @@ +// RUN: %target-swift-emit-silgen -module-name partial_apply_lifetime -enable-experimental-feature Lifetimes %s | %FileCheck %s + +// These tests exercise the lifetime dependencies computed for partial_apply +// result types by LifetimeDependenceInfo::partialApply. Each case pins down +// exactly what partialApply must produce by declaring an explicit +// @_lifetime(...) on the callee's closure parameter and then checking the +// convert_escape_to_noescape target type (which is the partial_apply's result +// type, minus the @noescape attribute). A convert_function between the +// partial_apply and the consuming apply would mean partialApply disagreed with +// the callee's expected type, so `CHECK-NOT: convert_function` is added as an +// extra guard. + +struct NE: ~Escapable { + @_lifetime(immortal) + init() {} +} + +// ----------------------------------------------------------------------------- +// Baseline: a single-capture closure with a borrow-on-capture result. +// Lifted closure: (NE) -> @lifetime(borrow 0) NE +// Bind 1 captured NE -> () -> @lifetime(captures) NE +// Verifies: target=result is remapped, all-bound scope source collapses to +// nullptr, and the captures flag is set. +// ----------------------------------------------------------------------------- + +@_lifetime(copy f) +func copyNE(f: () -> NE) -> NE { + f() +} + +// CHECK-LABEL: sil hidden [ossa] @$s22partial_apply_lifetime9callGetNE3ne1AA0F0VAE_tF : $@convention(thin) (@guaranteed NE) -> @lifetime(copy 0) @owned NE { +// CHECK: [[FNREF:%[0-9]+]] = function_ref @$s22partial_apply_lifetime9callGetNE3ne1AA0F0VAE_tFAEyXEfU_ : $@convention(thin) (@guaranteed NE) -> @lifetime(borrow 0) @owned NE +// CHECK: [[CLOSURE:%[0-9]+]] = partial_apply [callee_guaranteed] [[FNREF]] +// CHECK-NOT: convert_function +// CHECK: [[NECLOSURE:%[0-9]+]] = convert_escape_to_noescape [not_guaranteed] [[CLOSURE]] to $@noescape @callee_guaranteed () -> @lifetime(captures) @owned NE +// CHECK: [[GETNE:%[0-9]+]] = function_ref @$s22partial_apply_lifetime6copyNE1fAA0E0VAEyXE_tF +// CHECK: apply [[GETNE]]([[NECLOSURE]]) +// CHECK-LABEL: } // end sil function '$s22partial_apply_lifetime9callGetNE3ne1AA0F0VAE_tF' +func callGetNE(ne1: NE) -> NE { + copyNE { ne1 } +} + +// ----------------------------------------------------------------------------- +// Dependency on an unbound formal parameter: the source index is kept and no +// captures flag is added. +// +// Lifted closure: (NE, Bool) -> @lifetime(borrow 0) NE +// Bind 1 (the captured Bool) -> (NE) -> @lifetime(borrow 0) NE +// ----------------------------------------------------------------------------- + +@_lifetime(copy f) +func eatOneBorrow(f: @_lifetime(borrow ne) (_ ne: NE) -> NE) -> NE { + let local = NE() + return f(local) +} + +// CHECK-LABEL: sil hidden [ossa] @$s22partial_apply_lifetime19callBorrowOnUnbound4condAA2NEVSb_tF : +// CHECK: [[FNREF:%[0-9]+]] = function_ref @$s22partial_apply_lifetime19callBorrowOnUnbound4condAA2NEVSb_tFA2EXEfU_ : $@convention(thin) (@guaranteed NE, Bool) -> @lifetime(borrow 0) @owned NE +// CHECK: [[CLOSURE:%[0-9]+]] = partial_apply [callee_guaranteed] [[FNREF]] +// CHECK-NOT: convert_function +// CHECK: convert_escape_to_noescape [not_guaranteed] [[CLOSURE]] to $@noescape @callee_guaranteed (@guaranteed NE) -> @lifetime(borrow 0) @owned NE +// CHECK-LABEL: } // end sil function '$s22partial_apply_lifetime19callBorrowOnUnbound4condAA2NEVSb_tF' +func callBorrowOnUnbound(cond: Bool) -> NE { + eatOneBorrow { n in if cond { return n } else { return n } } +} + +// ----------------------------------------------------------------------------- +// Dependency on a bound-only source: the index list collapses to nullptr and +// the captures flag is set. +// +// Lifted closure: (NE, NE) -> @lifetime(borrow 1) NE +// Bind 1 (the captured NE) -> (NE) -> @lifetime(captures) NE +// ----------------------------------------------------------------------------- + +@_lifetime(copy f) +func eatOneCaptures(f: @_lifetime(captures) (NE) -> NE) -> NE { + let local = NE() + return f(local) +} + +// CHECK-LABEL: sil hidden [ossa] @$s22partial_apply_lifetime14callDepOnBound5boundAA2NEVAE_tF : +// CHECK: [[FNREF:%[0-9]+]] = function_ref @$s22partial_apply_lifetime14callDepOnBound5boundAA2NEVAE_tFA2EXEfU_ : $@convention(thin) (@guaranteed NE, @guaranteed NE) -> @lifetime(borrow 1) @owned NE +// CHECK: [[CLOSURE:%[0-9]+]] = partial_apply [callee_guaranteed] [[FNREF]] +// CHECK-NOT: convert_function +// CHECK: convert_escape_to_noescape [not_guaranteed] [[CLOSURE]] to $@noescape @callee_guaranteed (@guaranteed NE) -> @lifetime(captures) @owned NE +// CHECK-LABEL: } // end sil function '$s22partial_apply_lifetime14callDepOnBound5boundAA2NEVAE_tF' +@_lifetime(copy bound) +func callDepOnBound(bound: NE) -> NE { + eatOneCaptures { _ in bound } +} + +// ----------------------------------------------------------------------------- +// Dependency on a mix of bound and unbound sources: the bound bits are trimmed +// off and the captures flag is set, while the unbound bits remain. +// +// Lifted closure: (NE, Bool, NE) -> @lifetime(borrow 0, borrow 1, borrow 2) NE +// Bind 2 (the captured Bool and NE) -> +// (NE) -> @lifetime(captures, borrow 0) NE +// ----------------------------------------------------------------------------- + +@_lifetime(copy f) +func eatOneCapturesAndBorrow(f: @_lifetime(captures, borrow ne) (_ ne: NE) -> NE) -> NE { + let local = NE() + return f(local) +} + +// CHECK-LABEL: sil hidden [ossa] @$s22partial_apply_lifetime9callMixed5bound4condAA2NEVAF_SbtF : +// CHECK: [[FNREF:%[0-9]+]] = function_ref @$s22partial_apply_lifetime9callMixed5bound4condAA2NEVAF_SbtFA2FXEfU_ : $@convention(thin) (@guaranteed NE, Bool, @guaranteed NE) -> @lifetime(borrow 0, borrow 1, borrow 2) @owned NE +// CHECK: [[CLOSURE:%[0-9]+]] = partial_apply [callee_guaranteed] [[FNREF]] +// CHECK-NOT: convert_function +// CHECK: convert_escape_to_noescape [not_guaranteed] [[CLOSURE]] to $@noescape @callee_guaranteed (@guaranteed NE) -> @lifetime(captures, borrow 0) @owned NE +// CHECK-LABEL: } // end sil function '$s22partial_apply_lifetime9callMixed5bound4condAA2NEVAF_SbtF' +@_lifetime(copy bound) +func callMixed(bound: NE, cond: Bool) -> NE { + eatOneCapturesAndBorrow { n in cond ? n : bound } +} + +// ----------------------------------------------------------------------------- +// Multiple bound parameters with every source bound: exercises +// numBoundParams > 1 in combination with the all-bound collapse path. Every +// captured parameter contributes a source, so the result ends up as a pure +// captures-only dependency. +// +// Lifted closure: (Bool, NE, NE) -> @lifetime(borrow 0, borrow 1, borrow 2) NE +// Bind 3 (the three captures) -> () -> @lifetime(captures) NE +// ----------------------------------------------------------------------------- + +@_lifetime(copy f) +func eatZeroCaptures(f: @_lifetime(captures) () -> NE) -> NE { + f() +} + +// CHECK-LABEL: sil hidden [ossa] @$s22partial_apply_lifetime18callBindMultiBound1a1b4condAA2NEVAG_AGSbtF : +// CHECK: [[FNREF:%[0-9]+]] = function_ref @$s22partial_apply_lifetime18callBindMultiBound1a1b4condAA2NEVAG_AGSbtFAGyXEfU_ : $@convention(thin) (Bool, @guaranteed NE, @guaranteed NE) -> @lifetime(borrow 0, borrow 1, borrow 2) @owned NE +// CHECK: [[CLOSURE:%[0-9]+]] = partial_apply [callee_guaranteed] [[FNREF]] +// CHECK-NOT: convert_function +// CHECK: convert_escape_to_noescape [not_guaranteed] [[CLOSURE]] to $@noescape @callee_guaranteed () -> @lifetime(captures) @owned NE +// CHECK-LABEL: } // end sil function '$s22partial_apply_lifetime18callBindMultiBound1a1b4condAA2NEVAG_AGSbtF' +@_lifetime(copy a, copy b) +func callBindMultiBound(a: NE, b: NE, cond: Bool) -> NE { + eatZeroCaptures { cond ? a : b } +} + +// ----------------------------------------------------------------------------- +// Multiple bound parameters with the dependency on the unbound formal: the +// partialApply transform should leave the source index untouched and not set +// captures. +// +// Lifted closure: (NE, Bool, Int) -> @lifetime(borrow 0) NE +// Bind 2 (cond and tag) -> (NE) -> @lifetime(borrow 0) NE +// ----------------------------------------------------------------------------- + +// CHECK-LABEL: sil hidden [ossa] @$s22partial_apply_lifetime20callBindMultiUnbound4cond3tagAA2NEVSb_SitF : +// CHECK: [[FNREF:%[0-9]+]] = function_ref @$s22partial_apply_lifetime20callBindMultiUnbound4cond3tagAA2NEVSb_SitFA2FXEfU_ : $@convention(thin) (@guaranteed NE, Bool, Int) -> @lifetime(borrow 0) @owned NE +// CHECK: [[CLOSURE:%[0-9]+]] = partial_apply [callee_guaranteed] [[FNREF]] +// CHECK-NOT: convert_function +// CHECK: convert_escape_to_noescape [not_guaranteed] [[CLOSURE]] to $@noescape @callee_guaranteed (@guaranteed NE) -> @lifetime(borrow 0) @owned NE +// CHECK-LABEL: } // end sil function '$s22partial_apply_lifetime20callBindMultiUnbound4cond3tagAA2NEVSb_SitF' +@_lifetime(immortal) +func callBindMultiUnbound(cond: Bool, tag: Int) -> NE { + eatOneBorrow { n in + if cond && tag > 0 { return n } else { return n } + } +} + +// ----------------------------------------------------------------------------- +// @_lifetime(immortal) should round-trip through partialApply unchanged. The +// immortal entry has no source index lists (all nullptr), so the captures +// flag must not be set. +// +// Lifted closure: (Int) -> @lifetime(immortal) NE +// Bind 1 -> () -> @lifetime(immortal) NE +// ----------------------------------------------------------------------------- + +@_lifetime(copy f) +func eatImmortal(f: @_lifetime(immortal) () -> NE) -> NE { + f() +} + +// CHECK-LABEL: sil hidden [ossa] @$s22partial_apply_lifetime12callImmortal5extraAA2NEVSi_tF : +// CHECK: [[FNREF:%[0-9]+]] = function_ref @$s22partial_apply_lifetime12callImmortal5extraAA2NEVSi_tFAEyXEfU_ : $@convention(thin) (Int) -> @lifetime(immortal) @owned NE +// CHECK: [[CLOSURE:%[0-9]+]] = partial_apply [callee_guaranteed] [[FNREF]] +// CHECK-NOT: convert_function +// CHECK: convert_escape_to_noescape [not_guaranteed] [[CLOSURE]] to $@noescape @callee_guaranteed () -> @lifetime(immortal) @owned NE +// CHECK-NOT: captures +// CHECK-LABEL: } // end sil function '$s22partial_apply_lifetime12callImmortal5extraAA2NEVSi_tF' +@_lifetime(immortal) +func callImmortal(extra: Int) -> NE { + eatImmortal { let _ = extra; return NE() } +} + +// ----------------------------------------------------------------------------- +// Mixed dependency kinds (inherit + scope) within a single entry: exercises +// that captureBoundParams is applied independently to each index list so that +// an unbound inherit index is preserved while the corresponding scope list +// collapses and sets the captures flag. +// +// Lifted closure: (NE, Bool, NE) -> @lifetime(copy 0, borrow 1, borrow 2) NE +// Bind 2 (cond and the captured NE) -> +// (NE) -> @lifetime(captures, copy 0) NE +// ----------------------------------------------------------------------------- + +@_lifetime(copy f) +func eatOneCapturesCopy(f: @_lifetime(captures, copy ne) (_ ne: NE) -> NE) -> NE { + let local = NE() + return f(local) +} + +// CHECK-LABEL: sil hidden [ossa] @$s22partial_apply_lifetime14callMixedKinds5bound4condAA2NEVAF_SbtF : +// CHECK: [[FNREF:%[0-9]+]] = function_ref @$s22partial_apply_lifetime14callMixedKinds5bound4condAA2NEVAF_SbtFA2FXEfU_ : $@convention(thin) (@guaranteed NE, Bool, @guaranteed NE) -> @lifetime(copy 0, borrow 1, borrow 2) @owned NE +// CHECK: [[CLOSURE:%[0-9]+]] = partial_apply [callee_guaranteed] [[FNREF]] +// CHECK-NOT: convert_function +// CHECK: convert_escape_to_noescape [not_guaranteed] [[CLOSURE]] to $@noescape @callee_guaranteed (@guaranteed NE) -> @lifetime(captures, copy 0) @owned NE +// CHECK-LABEL: } // end sil function '$s22partial_apply_lifetime14callMixedKinds5bound4condAA2NEVAF_SbtF' +@_lifetime(copy bound) +func callMixedKinds(bound: NE, cond: Bool) -> NE { + eatOneCapturesCopy { n in cond ? n : bound } +} From ad949f8ff377f3691a995f58e587e1d3050084ed Mon Sep 17 00:00:00 2001 From: Andrew Trick Date: Tue, 12 May 2026 17:17:11 -0700 Subject: [PATCH 06/25] Fix an assert in LifetimeDependenceInfo::partialApply. Allow partial_applies with no captures. --- lib/AST/LifetimeDependence.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/AST/LifetimeDependence.cpp b/lib/AST/LifetimeDependence.cpp index 4f96f2ba884..60d83e5e572 100644 --- a/lib/AST/LifetimeDependence.cpp +++ b/lib/AST/LifetimeDependence.cpp @@ -2068,8 +2068,9 @@ ArrayRef LifetimeDependenceInfo::partialApply( ASTContext &ctx, ArrayRef lifetimes, unsigned numFormalParams, unsigned numBoundParams) { - ASSERT(numBoundParams > 0 && - "A partial application must bind at least 1 argument"); + if (numBoundParams == 0) + return lifetimes; + ASSERT(numBoundParams <= numFormalParams && "A partial application can only bind as many parameters as the " "function has."); From 7e3b90b719090f0f9a47d2421b90cb7b44fd277f Mon Sep 17 00:00:00 2001 From: Andrew Trick Date: Tue, 12 May 2026 17:17:57 -0700 Subject: [PATCH 07/25] Add a FIXME in LifetimeDependenceInfo::partialApply. We need to follow up to avoid linear memory growth. This is not a major problem today because AllocateCopy will almost always be called with zero size. --- lib/AST/LifetimeDependence.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/AST/LifetimeDependence.cpp b/lib/AST/LifetimeDependence.cpp index 60d83e5e572..01d5989b95f 100644 --- a/lib/AST/LifetimeDependence.cpp +++ b/lib/AST/LifetimeDependence.cpp @@ -2139,6 +2139,9 @@ ArrayRef LifetimeDependenceInfo::partialApply( conditionallyAddressable, flags)); } + // FIXME: Avoid allocating context memory for every partial apply. Instead, + // cache a single uniqueLifetimeDependenceInfo array for each combination + // of FunctionType + numBoundParams. return ctx.AllocateCopy(curried); } From 526083f1b22f9b8310a14a3d7b7d24c450b7112c Mon Sep 17 00:00:00 2001 From: Andrew Trick Date: Tue, 12 May 2026 17:41:31 -0700 Subject: [PATCH 08/25] Add REQUIRES to test/SILGen/partial_apply_lifetime.swift --- test/SILGen/partial_apply_lifetime.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/SILGen/partial_apply_lifetime.swift b/test/SILGen/partial_apply_lifetime.swift index 97bd20bc8a5..eb9e16f9bab 100644 --- a/test/SILGen/partial_apply_lifetime.swift +++ b/test/SILGen/partial_apply_lifetime.swift @@ -1,5 +1,7 @@ // RUN: %target-swift-emit-silgen -module-name partial_apply_lifetime -enable-experimental-feature Lifetimes %s | %FileCheck %s +// REQUIRES: swift_feature_Lifetimes + // These tests exercise the lifetime dependencies computed for partial_apply // result types by LifetimeDependenceInfo::partialApply. Each case pins down // exactly what partialApply must produce by declaring an explicit From cd22caf8229c7d0067b94439c514f8d5d4dcba00 Mon Sep 17 00:00:00 2001 From: Andrew Trick Date: Tue, 12 May 2026 21:37:52 -0700 Subject: [PATCH 09/25] Fix test/IRGen/rdar176795176.swift: add Lifetimes feature flag --- test/IRGen/rdar176795176.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/IRGen/rdar176795176.swift b/test/IRGen/rdar176795176.swift index 4a65255c881..04baa7d6385 100644 --- a/test/IRGen/rdar176795176.swift +++ b/test/IRGen/rdar176795176.swift @@ -1,4 +1,6 @@ -// RUN: %target-swift-frontend -emit-ir -O %s +// RUN: %target-swift-frontend -emit-ir -O -enable-experimental-feature Lifetimes %s + +// REQUIRES: swift_feature_Lifetimes // Minimal reproducer for IRGen crash: rdar://176795176 From 56de5913eda991e8418aaf4c2f3fd2e0baaaacdd Mon Sep 17 00:00:00 2001 From: Aidan Hall Date: Wed, 13 May 2026 12:31:40 +0100 Subject: [PATCH 10/25] LifetimeDependenceInfo::partialApply: allow fewer indices than formal params --- lib/AST/LifetimeDependence.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/AST/LifetimeDependence.cpp b/lib/AST/LifetimeDependence.cpp index 01d5989b95f..45593d41a1d 100644 --- a/lib/AST/LifetimeDependence.cpp +++ b/lib/AST/LifetimeDependence.cpp @@ -2103,8 +2103,9 @@ ArrayRef LifetimeDependenceInfo::partialApply( if (!indices) return nullptr; - ASSERT(indices->getCapacity() == numFormalParams && - "There should be 1 index per parameter. SIL functions cannot have " + ASSERT(indices->getCapacity() <= numFormalParams && + "There should be at most 1 index per parameter. SIL functions " + "cannot have " "an implicit self parameter."); auto bits = indices->getBitVector(); @@ -2115,7 +2116,8 @@ ArrayRef LifetimeDependenceInfo::partialApply( flags.setCaptures(true); } - // Remove the indices of the captured parameters. + // Remove the indices of the captured parameters, leaving only those of + // the closure parameters. if (bits.find_first() >= int(numClosureParams)) { // All lifetime sources are captured. The resulting empty list of @@ -2123,7 +2125,8 @@ ArrayRef LifetimeDependenceInfo::partialApply( return nullptr; } - bits.resize(numClosureParams); + if (bits.size() > numClosureParams) + bits.resize(numClosureParams); return IndexSubset::get(ctx, bits); }; From f5833c9191831d43de0a9eadb91afaa80932d4f7 Mon Sep 17 00:00:00 2001 From: Aidan Hall Date: Wed, 13 May 2026 13:12:51 +0100 Subject: [PATCH 11/25] LifetimeDependenceInfo::partialApply: test typed throws & inout --- test/SILGen/partial_apply_lifetime.swift | 132 ++++++++++++----------- 1 file changed, 71 insertions(+), 61 deletions(-) diff --git a/test/SILGen/partial_apply_lifetime.swift b/test/SILGen/partial_apply_lifetime.swift index eb9e16f9bab..27244e7be33 100644 --- a/test/SILGen/partial_apply_lifetime.swift +++ b/test/SILGen/partial_apply_lifetime.swift @@ -17,13 +17,7 @@ struct NE: ~Escapable { init() {} } -// ----------------------------------------------------------------------------- // Baseline: a single-capture closure with a borrow-on-capture result. -// Lifted closure: (NE) -> @lifetime(borrow 0) NE -// Bind 1 captured NE -> () -> @lifetime(captures) NE -// Verifies: target=result is remapped, all-bound scope source collapses to -// nullptr, and the captures flag is set. -// ----------------------------------------------------------------------------- @_lifetime(copy f) func copyNE(f: () -> NE) -> NE { @@ -42,13 +36,8 @@ func callGetNE(ne1: NE) -> NE { copyNE { ne1 } } -// ----------------------------------------------------------------------------- // Dependency on an unbound formal parameter: the source index is kept and no // captures flag is added. -// -// Lifted closure: (NE, Bool) -> @lifetime(borrow 0) NE -// Bind 1 (the captured Bool) -> (NE) -> @lifetime(borrow 0) NE -// ----------------------------------------------------------------------------- @_lifetime(copy f) func eatOneBorrow(f: @_lifetime(borrow ne) (_ ne: NE) -> NE) -> NE { @@ -66,13 +55,7 @@ func callBorrowOnUnbound(cond: Bool) -> NE { eatOneBorrow { n in if cond { return n } else { return n } } } -// ----------------------------------------------------------------------------- -// Dependency on a bound-only source: the index list collapses to nullptr and -// the captures flag is set. -// -// Lifted closure: (NE, NE) -> @lifetime(borrow 1) NE -// Bind 1 (the captured NE) -> (NE) -> @lifetime(captures) NE -// ----------------------------------------------------------------------------- +// Dependency on a borrowed source: replaced with captures. @_lifetime(copy f) func eatOneCaptures(f: @_lifetime(captures) (NE) -> NE) -> NE { @@ -91,14 +74,9 @@ func callDepOnBound(bound: NE) -> NE { eatOneCaptures { _ in bound } } -// ----------------------------------------------------------------------------- -// Dependency on a mix of bound and unbound sources: the bound bits are trimmed -// off and the captures flag is set, while the unbound bits remain. -// -// Lifted closure: (NE, Bool, NE) -> @lifetime(borrow 0, borrow 1, borrow 2) NE -// Bind 2 (the captured Bool and NE) -> -// (NE) -> @lifetime(captures, borrow 0) NE -// ----------------------------------------------------------------------------- +// Dependency on a mix of captured and uncaptured sources: only the dependencies +// on captured parameters (those bound by the partial apply) are replaced with +// the captures dependency source. @_lifetime(copy f) func eatOneCapturesAndBorrow(f: @_lifetime(captures, borrow ne) (_ ne: NE) -> NE) -> NE { @@ -117,15 +95,7 @@ func callMixed(bound: NE, cond: Bool) -> NE { eatOneCapturesAndBorrow { n in cond ? n : bound } } -// ----------------------------------------------------------------------------- -// Multiple bound parameters with every source bound: exercises -// numBoundParams > 1 in combination with the all-bound collapse path. Every -// captured parameter contributes a source, so the result ends up as a pure -// captures-only dependency. -// -// Lifted closure: (Bool, NE, NE) -> @lifetime(borrow 0, borrow 1, borrow 2) NE -// Bind 3 (the three captures) -> () -> @lifetime(captures) NE -// ----------------------------------------------------------------------------- +// Multiple parameters, all captured. @_lifetime(copy f) func eatZeroCaptures(f: @_lifetime(captures) () -> NE) -> NE { @@ -143,14 +113,7 @@ func callBindMultiBound(a: NE, b: NE, cond: Bool) -> NE { eatZeroCaptures { cond ? a : b } } -// ----------------------------------------------------------------------------- -// Multiple bound parameters with the dependency on the unbound formal: the -// partialApply transform should leave the source index untouched and not set -// captures. -// -// Lifted closure: (NE, Bool, Int) -> @lifetime(borrow 0) NE -// Bind 2 (cond and tag) -> (NE) -> @lifetime(borrow 0) NE -// ----------------------------------------------------------------------------- +// Multiple parameters: some captured, some not. // CHECK-LABEL: sil hidden [ossa] @$s22partial_apply_lifetime20callBindMultiUnbound4cond3tagAA2NEVSb_SitF : // CHECK: [[FNREF:%[0-9]+]] = function_ref @$s22partial_apply_lifetime20callBindMultiUnbound4cond3tagAA2NEVSb_SitFA2FXEfU_ : $@convention(thin) (@guaranteed NE, Bool, Int) -> @lifetime(borrow 0) @owned NE @@ -165,14 +128,7 @@ func callBindMultiUnbound(cond: Bool, tag: Int) -> NE { } } -// ----------------------------------------------------------------------------- -// @_lifetime(immortal) should round-trip through partialApply unchanged. The -// immortal entry has no source index lists (all nullptr), so the captures -// flag must not be set. -// -// Lifted closure: (Int) -> @lifetime(immortal) NE -// Bind 1 -> () -> @lifetime(immortal) NE -// ----------------------------------------------------------------------------- +// No lifetime sources: unaffected. @_lifetime(copy f) func eatImmortal(f: @_lifetime(immortal) () -> NE) -> NE { @@ -191,16 +147,7 @@ func callImmortal(extra: Int) -> NE { eatImmortal { let _ = extra; return NE() } } -// ----------------------------------------------------------------------------- -// Mixed dependency kinds (inherit + scope) within a single entry: exercises -// that captureBoundParams is applied independently to each index list so that -// an unbound inherit index is preserved while the corresponding scope list -// collapses and sets the captures flag. -// -// Lifted closure: (NE, Bool, NE) -> @lifetime(copy 0, borrow 1, borrow 2) NE -// Bind 2 (cond and the captured NE) -> -// (NE) -> @lifetime(captures, copy 0) NE -// ----------------------------------------------------------------------------- +// Mixed dependency kinds (inherit + scope) within a single entry. @_lifetime(copy f) func eatOneCapturesCopy(f: @_lifetime(captures, copy ne) (_ ne: NE) -> NE) -> NE { @@ -218,3 +165,66 @@ func eatOneCapturesCopy(f: @_lifetime(captures, copy ne) (_ ne: NE) -> NE) -> NE func callMixedKinds(bound: NE, cond: Bool) -> NE { eatOneCapturesCopy { n in cond ? n : bound } } + +// Converting a non-throwing function to typed-throws: this legitimately requires a +// convert_function. + +func takeTypedThrowingNEReturning(_ f: (NE) throws(E) -> NE) {} + +// CHECK-LABEL: sil hidden [ossa] @$s22partial_apply_lifetime34reabstractToTypedThrowsNEReturningyyAA2NEVADXEF : +// CHECK: [[THUNK:%[0-9]+]] = function_ref @$s22partial_apply_lifetime2NEVACIggo_A2Cs5NeverOIeggozr_TR : $@convention(thin) (@guaranteed NE, @lifetime(captures, copy 0) @guaranteed @noescape @callee_guaranteed (@guaranteed NE) -> @lifetime(captures, copy 0) @owned NE) -> (@owned NE, @error_indirect Never) +// CHECK: [[CLOSURE:%[0-9]+]] = partial_apply [callee_guaranteed] [[THUNK]] +// CHECK: convert_function [[CLOSURE]] to $@callee_guaranteed @substituted <τ_0_0> (@guaranteed NE) -> @lifetime(captures, copy 0) (@owned NE, @error_indirect τ_0_0) for +// CHECK-LABEL: } // end sil function '$s22partial_apply_lifetime34reabstractToTypedThrowsNEReturningyyAA2NEVADXEF' +func reabstractToTypedThrowsNEReturning(_ f: (NE) -> NE) { + takeTypedThrowingNEReturning(f) +} + +// Typed throws with inout parameter. + +func takeTypedThrowingInout(_ f: (inout NE) throws(E) -> Void) {} + +// CHECK-LABEL: sil hidden [ossa] @$s22partial_apply_lifetime28reabstractToTypedThrowsInoutyyyAA2NEVzXEF : +// CHECK: [[THUNK:%[0-9]+]] = function_ref @$s22partial_apply_lifetime2NEVIgl_ACs5NeverOIeglzr_TR : $@convention(thin) (@lifetime(copy 0) @inout NE, @guaranteed @noescape @callee_guaranteed (@lifetime(copy 0) @inout NE) -> ()) -> @error_indirect Never +// CHECK: [[CLOSURE:%[0-9]+]] = partial_apply [callee_guaranteed] [[THUNK]] +// CHECK: convert_function [[CLOSURE]] to $@callee_guaranteed @substituted <τ_0_0> (@lifetime(copy 0) @inout NE) -> @error_indirect τ_0_0 for +// CHECK-LABEL: } // end sil function '$s22partial_apply_lifetime28reabstractToTypedThrowsInoutyyyAA2NEVzXEF' +func reabstractToTypedThrowsInout(_ f: (inout NE) -> Void) { + takeTypedThrowingInout(f) +} + +// Inout parameter, without a legitimate need for a convert_function: +// No convert_function is emitted. + +struct Holder: ~Escapable { + @_lifetime(immortal) + init() {} + + @_lifetime(self: copy other) + mutating func mut(other: NE) {} +} + +func consumeInout(_ f: (inout Holder) -> ()) {} + +// CHECK-LABEL: sil hidden [ossa] @$s22partial_apply_lifetime13driveMutation5otheryAA2NEV_tF : +// CHECK: [[FNREF:%[0-9]+]] = function_ref @$s22partial_apply_lifetime13driveMutation5otheryAA2NEV_tFyAA6HolderVzXEfU_ : $@convention(thin) (@lifetime(copy 0) @inout Holder, @guaranteed NE) -> () +// CHECK: [[CLOSURE:%[0-9]+]] = partial_apply [callee_guaranteed] [[FNREF]] +// CHECK-NOT: convert_function +// CHECK: convert_escape_to_noescape [not_guaranteed] [[CLOSURE]] to $@noescape @callee_guaranteed (@lifetime(copy 0) @inout Holder) -> () +// CHECK-LABEL: } // end sil function '$s22partial_apply_lifetime13driveMutation5otheryAA2NEV_tF' +func driveMutation(other: NE) { + consumeInout { (h: inout Holder) in h.mut(other: other) } +} + +// Captured inout parameter. + +// CHECK-LABEL: sil hidden [ossa] @$s22partial_apply_lifetime21callWithCapturedInoutyAA2NEVADzF : $@convention(thin) (@lifetime(copy 0) @inout NE) -> @lifetime(borrow 0) @owned NE { +// CHECK: [[FNREF:%[0-9]+]] = function_ref @$s22partial_apply_lifetime21callWithCapturedInoutyAA2NEVADzFADyXEfU_ : $@convention(thin) (@inout_aliasable NE) -> @lifetime(borrow 0) @owned NE +// CHECK: [[CLOSURE:%[0-9]+]] = partial_apply [callee_guaranteed] [[FNREF]] +// CHECK-NOT: convert_function +// CHECK: convert_escape_to_noescape [not_guaranteed] [[CLOSURE]] to $@noescape @callee_guaranteed () -> @lifetime(captures) @owned NE +// CHECK-LABEL: } // end sil function '$s22partial_apply_lifetime21callWithCapturedInoutyAA2NEVADzF' +@_lifetime(&ne) +func callWithCapturedInout(_ ne: inout NE) -> NE { + copyNE { ne } +} From 306b4396f455f16ea1cc4000cab17ce10096ff7f Mon Sep 17 00:00:00 2001 From: Jake Petroules Date: Wed, 13 May 2026 10:19:55 -0700 Subject: [PATCH 12/25] Encode the minimum OS version in ToolchainInfo.plist (fix typo) Fix typo from initial change in #89062 --- utils/build-script-impl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/build-script-impl b/utils/build-script-impl index fa57964cc69..8dca4d02f40 100755 --- a/utils/build-script-impl +++ b/utils/build-script-impl @@ -3187,7 +3187,7 @@ function build_and_test_installable_package() { call ${PLISTBUDDY_BIN} -c "Add Aliases array" "${DARWIN_TOOLCHAIN_INFO_PLIST}" # Marker used to populate in the final toolchain .pkg distribution - call ${PLISTBUDDY_BIN} -c "Add MinimumSystemVersion '${DARWIN_DEPLOYMENT_VERSION_OSX}'" "${DARWIN_TOOLCHAIN_INFO_PLIST}" + call ${PLISTBUDDY_BIN} -c "Add MinimumSystemVersion string '${DARWIN_DEPLOYMENT_VERSION_OSX}'" "${DARWIN_TOOLCHAIN_INFO_PLIST}" aliases_count=0 local IFS="," From deacce8bf9f4b5969a822e60feb5f54ae80dc08b Mon Sep 17 00:00:00 2001 From: Slava Pestov Date: Wed, 13 May 2026 10:19:36 -0400 Subject: [PATCH 13/25] New congruence enumeration benchmark --- benchmark/CMakeLists.txt | 1 + benchmark/single-source/ToddCoxeter.swift | 580 ++++++++++++++++++++++ benchmark/utils/main.swift | 2 + 3 files changed, 583 insertions(+) create mode 100644 benchmark/single-source/ToddCoxeter.swift diff --git a/benchmark/CMakeLists.txt b/benchmark/CMakeLists.txt index 6440c3b76c9..4d4dd1c9ac3 100644 --- a/benchmark/CMakeLists.txt +++ b/benchmark/CMakeLists.txt @@ -212,6 +212,7 @@ set(SWIFT_BENCH_MODULES single-source/SuperChars single-source/TaskGroups single-source/TaskLocalGet + single-source/ToddCoxeter single-source/TwoSum single-source/TypeFlood single-source/UTF8Decode diff --git a/benchmark/single-source/ToddCoxeter.swift b/benchmark/single-source/ToddCoxeter.swift new file mode 100644 index 00000000000..576ae05e13e --- /dev/null +++ b/benchmark/single-source/ToddCoxeter.swift @@ -0,0 +1,580 @@ +// +// This file implements an algorithm to construct the Cayley graph for a +// finite monoid. Specifically, this implements the "modified Felsch +// strategy" for congruence enumeration, described in this paper: +// +// "The Todd–Coxeter algorithm for semigroups and monoids" +// https://link.springer.com/article/10.1007/s00233-024-10431-z +// +// A few things you would need for a "real" implementation are missing: +// +// 1) There is no garbage collection for nodes in the graph, so when two nodes +// are merged, the dead node is not reused. Fixing this would decrease +// memory pressure. +// 2) Other strategies, such as the HLT strategy, could be implemented, since +// alternating Flesch with HLT gives better performance on some workloads. +// 3) The constructed word graph is not standardized, and the only thing this +// implementation does with it is count the number of elements. +// + +import TestsUtils + +public let benchmarks = [ + BenchmarkInfo(name: "ToddCoxeter", runFunction: run_ToddCoxeter, tags: [.miniapplication]) +] + +func run(rules: [[Word]], maxNodes: Int, maxLinks: Int) + throws(CongruenceError) -> Int { + let alphabet = 2 + let rules = rules.map { Rule(lhs: $0[0], rhs: $0[1]) } + let p = Presentation(alphabet: alphabet, rules: rules) + + var c = Congruence(presentation: p, maxNodes: maxNodes, maxLinks: maxLinks) + return try c.enumerate() +} + +let problems: [([[Word]], Int, Int, Int)] = [ + // has 1083 elements. This takes about half a millisecond + // on an Apple MacBook Pro with M4 chip. This runs by default. + ([[[0, 0, 0], []], [[0, 1, 1, 1, 1, 0], [1]]], 2000, 3000, 1083), + + // has 10,587 elements. This takes about 9 milliseconds + // on an Apple MacBook Pro with M4 chip. + ([[[0, 0, 0], []], [[0, 1, 0, 1, 1, 1, 0], [1]]], 30000, 30000, 10587), + + // has 2,361,964 elements. This takes about 22 seconds + // on an Apple MacBook Pro with M4 chip. + ([[[0, 0, 0, 0], []], [[0, 1, 0, 1, 1, 0], [1]]], 40000000, 40000000, 2361964), +] + +// Change 0 to 1 or 2 to run the longer workloads. +let problem = problems[0] + +func run_ToddCoxeter(_ n: Int) { + let (rules, maxNodes, maxLinks, expected) = problem + for _ in 0 ..< n { + let result = try! run(rules: rules, maxNodes: maxNodes, maxLinks: maxLinks) + precondition(result == expected) + } +} + +typealias Symbol = UInt8 + +typealias Word = [Symbol] + +struct Rule { + var lhs: Word + var rhs: Word +} + +struct Presentation { + var alphabet: Int + var rules: [Rule] +} + +/// Utility function. This actually only gets called with an input value of '2' +/// so it doesn't need to be optimized. +func nextPowerOfTwo(_ x: Int) -> Int { + precondition(x > 0) + var y = 1 + while y < x { + y *= 2 + } + return y +} + +enum CongruenceError: Error { + case tooManyNodes + case tooManyLinks +} + +/// A tree: +/// - The nodes in the tree correspond to the factors of the words that appear +/// in the monoid's defining relations. +/// - For each letter 'a' in the alphabet, there is an edge joniing each node +/// 'x' with the node 'ax'. +/// - Each node then stores the index of each defining relation where one of the +/// two sides has the node's word as a prefix. +/// - The nodes are stored in a flat array, with the root appearing at index 0. +struct FelschTree { + typealias Node = Int16 + + static let none: Node = -1 + + var alphabet: Int + + /// Mapping from node index to list of defining relations that have this node's + /// word as a prefix. + var values: [[Int]] = [] + + /// An array storing the mth child of the nth node at index 'n * alphabet + m'. + var children: [Node] = [] + + init(alphabet: Int) { + self.alphabet = alphabet + + let root = createNode() + precondition(root == 0) + } + + func child(_ node: Node, _ s: Symbol) -> Int { + return Int(node) * alphabet + Int(s) + } + + func follow(_ node: Node, _ s: Symbol) -> Node { + return children[child(node, s)] + } + + mutating func setValue(_ node: Node, _ value: [Int]) { + precondition(values[Int(node)].isEmpty) + values[Int(node)] = value + } + + mutating func createNode() -> Node { + let node = Node(values.count) + values.append([]) + children.append(contentsOf: Array(repeating: Self.none, count: alphabet)) + return node + } + + mutating func insert(word: Word, from: Int, value: [Int]) { + precondition(from > 0) + + var from = from + var j: Node = 0 + + while from > 0 { + let s = word[from - 1] + var next = children[child(j, s)] + if next == Self.none { + next = createNode() + children[child(j, s)] = next + } + j = next + from -= 1 + } + + setValue(j, value) + } + + mutating func insert(word: Word, value: [Int]) { + insert(word: word, from: word.count, value: value) + } +} + +extension Presentation { + func buildFelschTree() -> FelschTree { + // Collect unique factors of the defining relations. + var factors: [Word] = [] + var unique: Set = [] + + for rule in rules { + for side in [rule.lhs, rule.rhs] { + if side.isEmpty { + continue + } + for i in 0 ... side.count { + for j in i ..< side.count { + let factor = Word(side[i ... j]) + if unique.insert(factor).inserted { + factors.append(factor) + } + } + } + } + } + + // Build the tree. + var result = FelschTree(alphabet: alphabet) + for factor in factors { + let value = rules.indices.filter { + return rules[$0].lhs.starts(with: factor) || + rules[$0].rhs.starts(with: factor) + } + + if !value.isEmpty { + // Add a node for each factor that is a prefix of at least one side of + // at least one defining relation. + result.insert(word: factor, value: value) + } + } + + return result + } +} + +struct Congruence: ~Copyable { + typealias Node = Int32 + typealias Link = Int32 + + static let none: Int32 = -1 + + let p: Presentation + + // Base-2 logarithm of the alphabet rounded up to the next power of two. + // Shifting by ashift converts between indices into the 'values' and + // 'parents' arrays below. + let ashift: Int + + let tree: FelschTree + + // Counters. + var iterations = 0 + var merged = 0 + var checked = 0 + var nodes = 0 + + // First node we have not yet performed "TC1" on. + var cursor: Node = 0 + + // The word graph is represented as an array of nodes. Nodes are identified + // by their index in this array. Nodes are sized to the alphabet, rounded up + // to the next power of two, so the index is always a multiple of the node + // size. The mth child of the nth node is stored at index 'm + n'. This is the + // destination of the edge labeled by the nth letter of the alphabet. + var values: [Node] + + // Union-find map, the parent of the nth node is stored at index 'n >> ashift'. + var parents: [Node] + + // Next word graph nodes that is unused. + var freeNode: Node = 0 + + // For each node, we also store a linked list of predecessors. The head of the + // mth list of predecessors for the nth node is stored at index 'm + n' in this + // array. This list points at all nodes whose mth child is n. + var heads: [Link] + + // A list of linked list nodes + var links: [(Node, Link)] + + // Next list node that is unused. + var freeLink: Link = 0 + + // When we record an edge from 'x' to 'y' labeled by 's', we add (x, s) to + // this list. + var recentList: [(Node, Symbol)] = [] + + // Work list for steps "PD1" and "PD2" in the modified Felsch strategy. + var workList: [(Node, FelschTree.Node)] = [] + + // Work list for step "TC3". + var mergeList: [(Node, Node)] = [] + + init(presentation: Presentation, maxNodes: Int, maxLinks: Int) { + self.p = presentation + ashift = nextPowerOfTwo(p.alphabet) + tree = p.buildFelschTree() + values = Array(repeating: Self.none, count: maxNodes << ashift) + parents = Array(repeating: Self.none, count: maxNodes) + heads = Array(repeating: Self.none, count: maxNodes << ashift) + links = Array(repeating: (Self.none, Self.none), + count: maxLinks << ashift) + } + + mutating func find(_ node: Node) -> Node { + let index = Int(node) >> ashift + let parent = parents[index] + if node != parent { + let canonical = find(parent) + if canonical != parent { + parents[index] = canonical + } + return canonical + } + + return node + } + + /// Allocate a graph node. + mutating func createNode() throws(CongruenceError) -> Node { + if freeNode == values.count { + throw CongruenceError.tooManyNodes + } + + let node = freeNode + freeNode += (1 << ashift) + + nodes += 1 + parents[Int(node) >> ashift] = node + + return node + } + + /// Allocate a linked list node. + mutating func createLink() throws(CongruenceError) -> Link { + if freeLink == links.count { + throw CongruenceError.tooManyLinks + } + + let link = freeLink + freeLink += 1 + return link + } + + /// Record a predecessor of a node in the reverse index. + mutating func recordLink(from: Node, label: Symbol, to: Node) + throws(CongruenceError) { + let link = try createLink() + let index = Int(to) + Int(label) + links[Int(link)] = (from, heads[index]) + heads[index] = link + } + + func rawEdge(from: Node, label: Symbol) -> Node { + let index = Int(from) + Int(label) + return values[index] + } + + func edge(from: Node, label: Symbol) -> Node { + let next = rawEdge(from: from, label: label) + if next == Self.none { + return next + } + precondition(parents[Int(next) >> ashift] == next) + return next + } + + func follow(from: Node, word: some Sequence) -> Node { + var node = from + for s in word { + node = edge(from: node, label: s) + if node == Self.none { + break + } + } + return node + } + + mutating func setEdge(from: Node, label: Symbol, to: Node, + overwrite: Bool=false) throws(CongruenceError) { + precondition(parents[Int(from) >> ashift] == from) + precondition(parents[Int(to) >> ashift] == to) + + let i = Int(from) + Int(label) + precondition(overwrite != (values[i] == Self.none)) + + values[i] = to + + try recordLink(from: from, label: label, to: to) + + recentList.append((from, label)) + } + + /// TC1: Define a new node. + mutating func expand(from: Node) throws(CongruenceError) -> Bool { + var changed = false + + for s in 0 ..< Symbol(p.alphabet) { + let next = edge(from: from, label: s) + if next == Self.none { + let to = try createNode() + try setEdge(from: from, label: s, to: to) + changed = true + } + } + + return changed + } + + /// TC2: Follow paths defined by a relation. + mutating func relation(from: Node, rule: Rule) throws(CongruenceError) { + checked += 1 + + if !rule.lhs.isEmpty && !rule.rhs.isEmpty { + let lhsP = rule.lhs.dropLast() + let xP = follow(from: from, word: lhsP) + if xP == Self.none { + return + } + let lhsS = rule.lhs.last! + let x = edge(from: xP, label: lhsS) + + let rhsP = rule.rhs.dropLast() + let yP = follow(from: from, word: rhsP) + if yP == Self.none { + return + } + let rhsS = rule.rhs.last! + let y = edge(from: yP, label: rhsS) + + if x == y { + return + } else if x == Self.none { + precondition(y != Self.none) + try setEdge(from: xP, label: lhsS, to: y) + return + } else if y == Self.none { + precondition(x != Self.none) + try setEdge(from: yP, label: rhsS, to: x) + return + } else { + precondition(x != y) + mergeList.append((x, y)) + return + } + } else if !rule.lhs.isEmpty && rule.rhs.isEmpty { + let lhsP = rule.lhs.dropLast() + + let xP = follow(from: from, word: lhsP) + if xP == Self.none { + return + } + + let lhsS = rule.lhs.last! + let x = edge(from: xP, label: lhsS) + + if x == from { + return + } else if x == Self.none { + try setEdge(from: xP, label: lhsS, to: from) + return + } else { + mergeList.append((x, from)) + return + } + } else if rule.lhs.isEmpty && !rule.rhs.isEmpty { + let rhsP = rule.rhs.dropLast() + let yP = follow(from: from, word: rhsP) + if yP == Self.none { + return + } + + let rhsS = rule.rhs.last! + let y = edge(from: yP, label: rhsS) + + if y == from { + return + } else if y == Self.none { + try setEdge(from: yP, label: rhsS, to: from) + return + } else { + mergeList.append((y, from)) + return + } + } + + /// The if statements above should be exhaustive. + fatalError() + } + + /// Reverse index walk using Felsch tree. Given a recently-added edge, + /// walk all nodes that possibly changed as a result, and re-apply "TC2" + /// to each node. + mutating func closure(from: Node, label: Symbol) throws(CongruenceError) { + let node = tree.follow(0, label) + precondition(node != FelschTree.none) + + precondition(workList.isEmpty) + workList.append((from, node)) + + while !workList.isEmpty { + let (from, node) = workList.removeLast() + precondition(parents[Int(from) >> ashift] == from) + + for i in tree.values[Int(node)] { + try relation(from: from, rule: p.rules[i]) + } + + for s in 0 ..< Symbol(p.alphabet) { + let child = tree.follow(node, s) + if child != FelschTree.none { + var link = heads[Int(from) + Int(s)] + while link != Self.none { + let pred = find(links[Int(link)].0) + workList.append((pred, child)) + link = links[Int(link)].1 + } + } + } + } + } + + /// Step "PD2" in modified Felsch strategy. + mutating func closure() throws(CongruenceError) { + while !recentList.isEmpty { + let (node, s) = recentList.removeLast() + if parents[Int(node) >> ashift] == node { + try closure(from: node, label: s) + } + + try merge() + } + } + + /// TC3: Process coincidences or a determination. + mutating func merge() throws(CongruenceError) { + func mergeLinks(_ x: Node, _ y: Node) throws(CongruenceError) { + for s in 0 ..< Symbol(p.alphabet) { + let index = Int(x) + Int(s) + var link = heads[index] + while link != Self.none { + let ref = links[Int(link)].0 + if parents[Int(ref) >> ashift] == ref { + let next = rawEdge(from: ref, label: s) + if next != y { + try setEdge(from: ref, label: s, to: y, overwrite: true) + } + } + link = links[Int(link)].1 + } + } + } + + func union(_ x: Node, _ y: Node) throws(CongruenceError) { + var x = find(x) + var y = find(y) + if x == y { + return + } else if x < y { + (x, y) = (y, x) + } + + parents[Int(x) >> ashift] = y + + try mergeLinks(x, y) + + merged += 1 + + for i in 0 ..< Symbol(p.alphabet) { + let xx = edge(from: x, label: i) + let yy = edge(from: y, label: i) + + if xx != yy && xx != Self.none && yy != Self.none { + mergeList.append((xx, yy)) + } else if yy == Self.none && xx != Self.none { + try setEdge(from: y, label: i, to: xx) + } + } + } + + while !mergeList.isEmpty { + let (x, y) = mergeList.removeLast() + try union(x, y) + } + } + + /// Run the congruence enumeration. + mutating func enumerate() throws(CongruenceError) -> Int { + let root = try! createNode() + precondition(root == 0) + + repeat { + try closure() + + while Int(cursor) < values.count { + if parents[Int(cursor) >> ashift] == cursor { + if try expand(from: cursor) { + break + } + } + + cursor += 1 + } + + precondition(mergeList.isEmpty) + } while !recentList.isEmpty + + return nodes - merged + } +} diff --git a/benchmark/utils/main.swift b/benchmark/utils/main.swift index 8c2a30900ca..dc5500c71b8 100644 --- a/benchmark/utils/main.swift +++ b/benchmark/utils/main.swift @@ -217,6 +217,7 @@ import Suffix import SuperChars import TaskGroups import TaskLocalGet +import ToddCoxeter import TwoSum import TypeFlood import UTF8Decode @@ -433,6 +434,7 @@ register(Suffix.benchmarks) register(SuperChars.benchmarks) register(TaskGroups.benchmarks) register(TaskLocalGet.benchmarks) +register(ToddCoxeter.benchmarks) register(TwoSum.benchmarks) register(TypeFlood.benchmarks) register(UTF8Decode.benchmarks) From 9f4310de9b1cafe1cac7f07e79567ca094becf98 Mon Sep 17 00:00:00 2001 From: Mike Ash Date: Thu, 14 May 2026 11:32:57 -0400 Subject: [PATCH 14/25] [IRGen] Include addressableForDependencies in FixedLayoutKey. This key is used for deduping data, so this is needed to avoid coalescing the info from two types that are have identical info except for this flag. rdar://177075642 --- lib/IRGen/GenValueWitness.cpp | 9 +++-- lib/IRGen/IRGenModule.h | 11 +++--- ...t_addressable_for_dependencies_dedup.swift | 34 +++++++++++++++++++ 3 files changed, 48 insertions(+), 6 deletions(-) create mode 100644 test/IRGen/type_layout_addressable_for_dependencies_dedup.swift diff --git a/lib/IRGen/GenValueWitness.cpp b/lib/IRGen/GenValueWitness.cpp index 6e989bbbbcf..85be36d89b7 100644 --- a/lib/IRGen/GenValueWitness.cpp +++ b/lib/IRGen/GenValueWitness.cpp @@ -1622,7 +1622,11 @@ llvm::Constant *IRGenModule::emitFixedTypeLayout(CanType t, // Otherwise, see if a layout has been emitted with these characteristics // already. - FixedLayoutKey key{size, numExtraInhabitants, align, pod, unsigned(bt)}; + bool addressableForDependencies = + getTypeProperties(silTy, TypeExpansionContext::minimal()) + .isAddressableForDependencies(); + FixedLayoutKey key{size, numExtraInhabitants, align, + pod, unsigned(bt), addressableForDependencies}; auto found = PrivateFixedLayouts.find(key); if (found != PrivateFixedLayouts.end()) @@ -1657,7 +1661,8 @@ llvm::Constant *IRGenModule::emitFixedTypeLayout(CanType t, "type_layout_" + llvm::Twine(size) + "_" + llvm::Twine(align) + "_" + llvm::Twine::utohexstr(numExtraInhabitants) - + pod_bt_string(pod, bt), + + pod_bt_string(pod, bt) + + (addressableForDependencies ? "_afd" : ""), getPointerAlignment(), /*constant*/ true, llvm::GlobalValue::PrivateLinkage); diff --git a/lib/IRGen/IRGenModule.h b/lib/IRGen/IRGenModule.h index 26ac53aaddd..afe1a4d3265 100644 --- a/lib/IRGen/IRGenModule.h +++ b/lib/IRGen/IRGenModule.h @@ -1454,6 +1454,7 @@ private: unsigned align: 16; unsigned pod: 1; unsigned bitwiseTakable: 2; + unsigned addressableForDependencies: 1; }; friend struct ::llvm::DenseMapInfo; llvm::DenseMap PrivateFixedLayouts; @@ -2162,23 +2163,25 @@ struct DenseMapInfo { using FixedLayoutKey = swift::irgen::IRGenModule::FixedLayoutKey; static inline FixedLayoutKey getEmptyKey() { - return {0, 0xFFFFFFFFu, 0, 0, 0}; + return {0, 0xFFFFFFFFu, 0, 0, 0, 0}; } static inline FixedLayoutKey getTombstoneKey() { - return {0, 0xFFFFFFFEu, 0, 0, 0}; + return {0, 0xFFFFFFFEu, 0, 0, 0, 0}; } static unsigned getHashValue(const FixedLayoutKey &key) { return hash_combine(key.size, key.numExtraInhabitants, key.align, - (bool)key.pod, (bool)key.bitwiseTakable); + (bool)key.pod, (bool)key.bitwiseTakable, + (bool)key.addressableForDependencies); } static bool isEqual(const FixedLayoutKey &a, const FixedLayoutKey &b) { return a.size == b.size && a.numExtraInhabitants == b.numExtraInhabitants && a.align == b.align && a.pod == b.pod - && a.bitwiseTakable == b.bitwiseTakable; + && a.bitwiseTakable == b.bitwiseTakable + && a.addressableForDependencies == b.addressableForDependencies; } }; diff --git a/test/IRGen/type_layout_addressable_for_dependencies_dedup.swift b/test/IRGen/type_layout_addressable_for_dependencies_dedup.swift new file mode 100644 index 00000000000..04fc043faf2 --- /dev/null +++ b/test/IRGen/type_layout_addressable_for_dependencies_dedup.swift @@ -0,0 +1,34 @@ +// RUN: %target-swift-frontend -enable-experimental-feature AddressableTypes -emit-ir %s | %FileCheck %s + +// REQUIRES: swift_feature_AddressableTypes + +// Two types with identical {size, EI, align, pod, bitwiseTakable} but +// differing IsAddressableForDependencies must NOT share a single private +// type_layout_* global. The Flags word inside the global encodes AFD, so +// sharing causes the second-emitted type's runtime VWT to silently inherit +// the first type's AFD bit (after the runtime-side patch that ORs each +// field's TypeLayout AFD into the aggregate VWT during instantiation). + +@_addressableForDependencies +struct AFD { var x: UInt16; var y: UInt16 } + +struct NotAFD { var x: UInt16; var y: UInt16 } + +struct G { + var t: T + var afd: AFD + var notAFD: NotAFD +} + +func use(_ g: G) { + _ = g +} + +// AFD field: alignment mask 1 (align=2) + IsAddressableForDependencies +// (0x02000000) = 0x02000001 = 33554433. +// +// CHECK-DAG: @type_layout_4_2_0_pod_afd = private constant {{.*}} {{i64|i32}} 4, {{i64|i32}} 4, i32 33554433, i32 0 } +// +// Non-AFD field: alignment mask 1 = 0x00000001 = 1. +// +// CHECK-DAG: @type_layout_4_2_0_pod = private constant {{.*}} {{i64|i32}} 4, {{i64|i32}} 4, i32 1, i32 0 } From c296d3fb47d0c83d63f36503d6559287fc3e00b5 Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Wed, 13 May 2026 22:47:24 -0700 Subject: [PATCH 15/25] [TypeChecker] PropertyWrappers: When setting projected value setter access account for `projectedValue` setter Previously, the synthesis set the access of the setter of a newly synthesized projected value property to match that of the parent property setter, but `projectedValue` of the property wrapper could be less accessible than that and the synthesis needs to account for that. Otherwise, the interface file might get a setter printed even though it's not part of the ABI. Resolves: rdar://176978806 --- lib/Sema/TypeCheckStorage.cpp | 9 +- ...er_with_internal_set_projected_value.swift | 113 ++++++++++++++++++ 2 files changed, 120 insertions(+), 2 deletions(-) create mode 100644 test/ModuleInterface/property_wrapper_with_internal_set_projected_value.swift diff --git a/lib/Sema/TypeCheckStorage.cpp b/lib/Sema/TypeCheckStorage.cpp index 6dbe65cf215..4a3f4a80fdc 100644 --- a/lib/Sema/TypeCheckStorage.cpp +++ b/lib/Sema/TypeCheckStorage.cpp @@ -3433,8 +3433,13 @@ static VarDecl *synthesizePropertyWrapperProjectionVar( // Determine the access level for the property. property->overwriteAccess(var->getFormalAccess()); - // Determine setter access. - property->overwriteSetterAccess(var->getSetterFormalAccess()); + // Determine setter access. `projectedValue` setter could be less + // accessible than the variable itself and vice versa, we need to + // account for that and take the less permitting access of the two. + property->overwriteSetterAccess( + wrapperVar ? std::min(var->getSetterFormalAccess(), + wrapperVar->getSetterFormalAccess()) + : var->getSetterFormalAccess()); // Add the accessors we need. if (var->hasImplicitPropertyWrapper()) { diff --git a/test/ModuleInterface/property_wrapper_with_internal_set_projected_value.swift b/test/ModuleInterface/property_wrapper_with_internal_set_projected_value.swift new file mode 100644 index 00000000000..1e610a5539d --- /dev/null +++ b/test/ModuleInterface/property_wrapper_with_internal_set_projected_value.swift @@ -0,0 +1,113 @@ +// RUN: %empty-directory(%t) + +// RUN: %target-swift-emit-module-interface(%t/TestResilient.swiftinterface) %s -module-name TestResilient +// RUN: %target-swift-typecheck-module-from-interface(%t/TestResilient.swiftinterface) -module-name TestResilient +// RUN: %FileCheck %s < %t/TestResilient.swiftinterface + +// RUN: %target-swift-frontend -compile-module-from-interface %t/TestResilient.swiftinterface -o %t/TestResilient.swiftmodule + +// CHECK: @propertyWrapper public struct WrapperWithInternalSet { +// CHECK: public var wrappedValue: T +// CHECK: public var projectedValue: Swift::Bool { +// CHECK: get +// CHECK: } +// CHECK: public init(wrappedValue: T) +// CHECK: } +@propertyWrapper +public struct WrapperWithInternalSet { + public var wrappedValue: T + public internal(set) var projectedValue: Bool = true + public init(wrappedValue: T) { self.wrappedValue = wrappedValue } +} + +// CHECK: @propertyWrapper public struct WrapperWithPrivateSet { +// CHECK: public var wrappedValue: T +// CHECK: public var projectedValue: Swift::Bool { +// CHECK: get +// CHECK: } +// CHECK: public init(wrappedValue: T) +// CHECK: } +@propertyWrapper +public struct WrapperWithPrivateSet { + public var wrappedValue: T + public private(set) var projectedValue: Bool = true + public init(wrappedValue: T) { self.wrappedValue = wrappedValue } +} + +// CHECK: @propertyWrapper public struct WrapperWithImmutable { +// CHECK: public var wrappedValue: T +// CHECK: public let projectedValue: Swift::Bool +// CHECK: public init(wrappedValue: T) +// CHECK: } +@propertyWrapper +public struct WrapperWithImmutable { + public var wrappedValue: T + public let projectedValue: Bool = true + public init(wrappedValue: T) { self.wrappedValue = wrappedValue } +} + +// CHECK: @propertyWrapper public struct WrapperWithComputedPublic { +// CHECK: public var wrappedValue: T +// CHECK: public var projectedValue: Swift::Bool { +// CHECK: get +// CHECK: set +// CHECK: } +// CHECK: } +@propertyWrapper +public struct WrapperWithComputedPublic { + public var wrappedValue: T + public var projectedValue: Bool { + get { false } + set { } + } + public init(wrappedValue: T) { self.wrappedValue = wrappedValue } +} + +// CHECK: @propertyWrapper public struct WrapperWithComputedImmutable { +// CHECK: public var wrappedValue: T +// CHECK: public var projectedValue: Swift::Bool { +// CHECK: get +// CHECK: } +// CHECK: public init(wrappedValue: T) +// CHECK: } +@propertyWrapper +public struct WrapperWithComputedImmutable { + public var wrappedValue: T + public var projectedValue: Bool { + get { false } + } + public init(wrappedValue: T) { self.wrappedValue = wrappedValue } +} + +public struct Test { + // CHECK: public var $v1: Swift::Bool { + // CHECK: get + // CHECK: } + @WrapperWithInternalSet public var v1: Int + + // CHECK: public var $v2: Swift::Bool { + // CHECK: get + // CHECK: } + @WrapperWithPrivateSet public var v2: String + + // CHECK: public var $v3: Swift::Bool { + // CHECK: get + // CHECK: } + @WrapperWithImmutable public var v3: Bool + + // CHECK: public var $v4: Swift::Bool { + // CHECK: get + // CHECK: set + // CHECK: } + @WrapperWithComputedPublic public var v4: Double + + // CHECK: public var $v5: Swift::Bool { + // CHECK: get + // CHECK: } + @WrapperWithComputedImmutable public var v5: Int? + + // CHECK: public var $v6: Swift::Bool { + // CHECK: get + // CHECK: } + @WrapperWithComputedPublic public private(set) var v6: Bool? +} From e653441d08f3f2aaec371dfcfe746a2ca0626091 Mon Sep 17 00:00:00 2001 From: Dario Rexin Date: Tue, 12 May 2026 23:36:46 -0700 Subject: [PATCH 16/25] [AST] Print movesAsLike if present on @_rawLayout rdar://166720900 A missing movesAsLike on the attribute in an interface file will cause miscompiles. --- lib/AST/Attr.cpp | 2 ++ test/ModuleInterface/attr-rawlayout.swift | 25 +++++++++++++++++++++++ test/Serialization/attr-rawlayout.swift | 10 ++++++++- 3 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 test/ModuleInterface/attr-rawlayout.swift diff --git a/lib/AST/Attr.cpp b/lib/AST/Attr.cpp index 8b2bb176d7d..d6befce6319 100644 --- a/lib/AST/Attr.cpp +++ b/lib/AST/Attr.cpp @@ -1729,6 +1729,8 @@ bool DeclAttribute::printImpl(ASTPrinter &Printer, const PrintOptions &Options, } else { llvm_unreachable("unhandled @_rawLayout form"); } + if (attr->shouldMoveAsLikeType()) + Printer << ", movesAsLike"; Printer << ")"; break; } diff --git a/test/ModuleInterface/attr-rawlayout.swift b/test/ModuleInterface/attr-rawlayout.swift new file mode 100644 index 00000000000..5b032a388c0 --- /dev/null +++ b/test/ModuleInterface/attr-rawlayout.swift @@ -0,0 +1,25 @@ +// RUN: %target-swift-emit-module-interface(%t.swiftinterface) %s -module-name attrs -enable-experimental-feature RawLayout +// RUN: %target-swift-typecheck-module-from-interface(%t.swiftinterface) -module-name attrs +// RUN: %FileCheck %s --input-file %t.swiftinterface + +// REQUIRES: swift_feature_RawLayout + +// CHECK: @_rawLayout(size: 5, alignment: 4) public struct A_ExplicitSizeAlign +@_rawLayout(size: 5, alignment: 4) +public struct A_ExplicitSizeAlign: ~Copyable {} + +// CHECK: @_rawLayout(like: T) public struct B_Cell +@_rawLayout(like: T) +public struct B_Cell: ~Copyable {} + +// CHECK: @_rawLayout(like: T, movesAsLike) public struct B2_CellMovesAsLike +@_rawLayout(like: T, movesAsLike) +public struct B2_CellMovesAsLike: ~Copyable {} + +// CHECK: @_rawLayout(likeArrayOf: T, count: 8) public struct C_SmallVector +@_rawLayout(likeArrayOf: T, count: 8) +public struct C_SmallVector: ~Copyable {} + +// CHECK: @_rawLayout(likeArrayOf: T, count: 8, movesAsLike) public struct C2_SmallVectorMovesAsLike +@_rawLayout(likeArrayOf: T, count: 8, movesAsLike) +public struct C2_SmallVectorMovesAsLike: ~Copyable {} diff --git a/test/Serialization/attr-rawlayout.swift b/test/Serialization/attr-rawlayout.swift index bf7b468e3c0..76937c9e2ba 100644 --- a/test/Serialization/attr-rawlayout.swift +++ b/test/Serialization/attr-rawlayout.swift @@ -5,16 +5,24 @@ // REQUIRES: swift_feature_RawLayout -// BC-CHECK: : ~Copyable {} + // MODULE-CHECK: @_rawLayout(like: T) struct B_Cell @_rawLayout(like: T) struct B_Cell: ~Copyable {} +// MODULE-CHECK: @_rawLayout(likeArrayOf: T, count: 8, movesAsLike) struct C2_SmallVectorMovesAsLike +@_rawLayout(likeArrayOf: T, count: 8, movesAsLike) +struct C2_SmallVectorMovesAsLike: ~Copyable {} + // MODULE-CHECK: @_rawLayout(likeArrayOf: T, count: 8) struct C_SmallVector @_rawLayout(likeArrayOf: T, count: 8) struct C_SmallVector: ~Copyable {} From 3032c70fcb5d1c096b3b80ab025e6935a40dc71c Mon Sep 17 00:00:00 2001 From: Slava Pestov Date: Wed, 13 May 2026 13:20:28 -0400 Subject: [PATCH 17/25] Sema: Add test cases for three 6.3 regressions that were since fixed - rdar://176848577 - rdar://176813402 - rdar://170160180 --- .../fast/rdar170160180.swift | 116 ++++++++++++++++++ .../fast/rdar176813402.swift | 25 ++++ .../fast/rdar176848577.swift | 50 ++++++++ 3 files changed, 191 insertions(+) create mode 100644 validation-test/Sema/type_checker_perf/fast/rdar170160180.swift create mode 100644 validation-test/Sema/type_checker_perf/fast/rdar176813402.swift create mode 100644 validation-test/Sema/type_checker_perf/fast/rdar176848577.swift diff --git a/validation-test/Sema/type_checker_perf/fast/rdar170160180.swift b/validation-test/Sema/type_checker_perf/fast/rdar170160180.swift new file mode 100644 index 00000000000..354b3f18217 --- /dev/null +++ b/validation-test/Sema/type_checker_perf/fast/rdar170160180.swift @@ -0,0 +1,116 @@ +// RUN: %target-typecheck-verify-swift -solver-scope-threshold=3000 + +class RequestRecord: Record { + var urlNoQuery: String + var method: String? + var hash: String + var instance: Int64 + + required init(row: Row) { fatalError() } +} + +extension Database { + func record(tmp: RequestRecord) { + // The formerly slow expression + let _ = RequestRecord.filter( + Column("") == tmp.hash + && Column("") == tmp.instance + && Column("") == tmp.urlNoQuery + && Column("") == tmp.method + && Column("") == tmp.method + && Column("") == tmp.method + ) + .fetchOne(self) + } +} + +// The rest was reduced from https://github.com/groue/GRDB.swift + +struct Database {} + +class Record {} + +extension Record: TableRecord { } +extension Record: FetchableRecord { } + +protocol TableRecord {} + +protocol DatabaseValueConvertible {} + +protocol StatementColumnConvertible {} + +protocol FetchableRecord {} + +struct Row {} + +protocol FetchRequest { + associatedtype RowDecoder +} + +extension FetchRequest where Self.RowDecoder : DatabaseValueConvertible { + func fetchOne(_: Database) -> Self.RowDecoder? { fatalError() } +} + +extension FetchRequest where Self.RowDecoder : DatabaseValueConvertible & StatementColumnConvertible { + func fetchOne(_: Database) -> Self.RowDecoder? { fatalError() } +} + +extension FetchRequest where Self.RowDecoder : FetchableRecord { + func fetchOne(_: Database) -> Self.RowDecoder? { fatalError() } +} + +extension FetchRequest where Self.RowDecoder == Row { + func fetchOne(_: Database) -> Row? { fatalError() } +} + +struct QueryInterfaceRequest: FetchRequest { +} + +extension TableRecord { + static func filter(_ predicate: some SQLSpecificExpressible) -> QueryInterfaceRequest { + fatalError() + } +} + +struct Request {} + +struct Column: Sendable { + init(_ name: String) {} + init(_ codingKey: some CodingKey) {} +} + +protocol SQLExpressible {} + +extension Column: SQLSpecificExpressible {} + +protocol SQLSpecificExpressible: SQLExpressible {} + +struct SQLExpression: SQLSpecificExpressible {} + +extension SQLSpecificExpressible { + static func && (lhs: Self, rhs: some SQLExpressible) -> SQLExpression { fatalError() } + static func && (lhs: some SQLExpressible, rhs: Self) -> SQLExpression { fatalError() } + static func && (lhs: Self, rhs: some SQLSpecificExpressible) -> SQLExpression { fatalError() } + static func == (lhs: Self, rhs: (any SQLExpressible)?) -> SQLExpression { fatalError() } + static func == (lhs: Self, rhs: Bool) -> SQLExpression { fatalError() } + static func == (lhs: (any SQLExpressible)?, rhs: Self) -> SQLExpression { fatalError() } + static func == (lhs: Bool, rhs: Self) -> SQLExpression { fatalError() } + static func == (lhs: Self, rhs: some SQLSpecificExpressible) -> SQLExpression { fatalError() } +} + +struct AssociationAggregate { + static func && (lhs: Self, rhs: Self) -> Self { fatalError()} + static func && (lhs: Self, rhs: some SQLExpressible) -> Self { fatalError()} + static func && (lhs: some SQLExpressible, rhs: Self) -> Self { fatalError()} + static func == (lhs: Self, rhs: Self) -> Self { fatalError() } + static func == (lhs: Self, rhs: (any SQLExpressible)?) -> Self { fatalError()} + static func == (lhs: (any SQLExpressible)?, rhs: Self) -> Self { fatalError()} + static func == (lhs: Self, rhs: Bool) -> Self { fatalError()} + static func == (lhs: Bool, rhs: Self) -> Self { fatalError()} +} + +struct Data {} + +extension Data: SQLExpressible {} +extension Int64: SQLExpressible {} +extension String: SQLExpressible {} \ No newline at end of file diff --git a/validation-test/Sema/type_checker_perf/fast/rdar176813402.swift b/validation-test/Sema/type_checker_perf/fast/rdar176813402.swift new file mode 100644 index 00000000000..e9d28161769 --- /dev/null +++ b/validation-test/Sema/type_checker_perf/fast/rdar176813402.swift @@ -0,0 +1,25 @@ +// RUN: %target-typecheck-verify-swift -solver-scope-threshold=150 + +// This took ~2000 scopes in 6.2 and ~240k scopes in 6.3. + +struct S1 { + var x: Int + var y: Int + var z: Int +} + +struct S2 { + var x: Int32 + var y: Int32 + var z: Int32 +} + +func test(u: [Int32], v: [Int32]) { + let _ = stride(from: 0, to: u.count, by: 3).map { + S2(x: u[$0 + 1], y: u[$0], z: u[$0 + 2]) + } + + let _ = stride(from: 0, to: v.count, by: 3).map { + S1(x: Int(v[$0]), y: Int(v[$0 + 1]), z: Int(v[$0 + 2])) + } +} diff --git a/validation-test/Sema/type_checker_perf/fast/rdar176848577.swift b/validation-test/Sema/type_checker_perf/fast/rdar176848577.swift new file mode 100644 index 00000000000..8dbfe56ae42 --- /dev/null +++ b/validation-test/Sema/type_checker_perf/fast/rdar176848577.swift @@ -0,0 +1,50 @@ +// RUN: %target-typecheck-verify-swift -solver-scope-threshold=100 + +protocol P1 {} + +extension P1 { + static func +(lhs: Self, rhs: Self) -> Self { fatalError() } + static func -(lhs: Self, rhs: Self) -> Self { fatalError() } + static func *(lhs: Self, rhs: Double) -> Self { fatalError() } + static func /(lhs: Self, rhs: Double) -> Self { fatalError() } +} + +struct S1: P1 {} + +protocol P2 { + associatedtype A + associatedtype B +} + +protocol P3 { + associatedtype A: P2 +} + +extension P3 { + var x: A.B { fatalError() } + var y: A.B { fatalError() } +} + +struct S3: P3 { + // Note that S3 has two overloads of 'x' and 'y' each, one that + // returns A.B and one that returns 'S1'. + var x: S1 { fatalError() } + var y: S1 { fatalError() } +} + +struct S2: P2 { + typealias A = S3 + typealias B = Double +} + +struct G { + init(first: A.A, second: A.A) {} + init(first: (A.B, A.B), second: (A.B, A.B)) {} +} + +func test(x: S3, y: Double, z: Double) { + // This used to be slow because the solver would struggle with dead ends + // involving the wrong overloads of 'x.x' and 'x.y'. + let _ = G(first: (x.x - y / 2, x.y - z / 2), + second: (x.x + y / 2, x.y + z / 2)) +} From f5f9af3e6f46fc73731ca5eb4c63bd0893209f49 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Thu, 14 May 2026 14:55:12 -0700 Subject: [PATCH 18/25] [gardening] silence an unnecessary-unsafe warning --- stdlib/public/core/StringStorageBridge.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stdlib/public/core/StringStorageBridge.swift b/stdlib/public/core/StringStorageBridge.swift index 6ce244fe3ee..a0fba48144d 100644 --- a/stdlib/public/core/StringStorageBridge.swift +++ b/stdlib/public/core/StringStorageBridge.swift @@ -835,7 +835,7 @@ fileprivate func isEqual( */ let chunkSize = Swift.min(64, remainingOtherByteCount) // nil = keep looping; .some(equal) = terminate with that equality result - let chunkResult: Bool? = unsafe withUnsafeTemporaryAllocation( + let chunkResult: Bool? = withUnsafeTemporaryAllocation( of: UInt8.self, capacity: chunkSize ) { tmpBuffer in From 86bfcafb92dd214a708c81bd1ed0e790f170f7f2 Mon Sep 17 00:00:00 2001 From: Allan Shortlidge Date: Wed, 13 May 2026 14:49:14 -0700 Subject: [PATCH 19/25] tests: Move regression test case for GH issue #64694 to appropriate file. --- test/attr/attr_availability.swift | 8 ++++++++ test/attr/attr_availability_unavailable_query.swift | 10 ---------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/test/attr/attr_availability.swift b/test/attr/attr_availability.swift index 993e5a77876..a26ef15830a 100644 --- a/test/attr/attr_availability.swift +++ b/test/attr/attr_availability.swift @@ -1217,9 +1217,17 @@ struct BadRename { init(range: Range, step: Int) { } } +func log(message: String) { } + +@available(*, unavailable, renamed: "log(message:)") +func log(format: String, _ args: Any...) { fatalError() } // expected-note {{'log(format:_:)' has been explicitly marked unavailable here}} + func testBadRename() { _ = BadRename(from: 5, to: 17) // expected-warning{{'init(from:to:step:)' is deprecated: replaced by 'init(range:step:)'}}{{documentation-file=deprecated-declaration}} // expected-note@-1{{use 'init(range:step:)' instead}} + + // Regression test for https://github.com/apple/swift/issues/64694 + log(format: "") // expected-error{{'log(format:_:)' has been renamed to 'log(message:)'}} } struct AvailableGenericParam<@available(*, deprecated) T> {} diff --git a/test/attr/attr_availability_unavailable_query.swift b/test/attr/attr_availability_unavailable_query.swift index 3f4e3ac34e9..763e1cd2b17 100644 --- a/test/attr/attr_availability_unavailable_query.swift +++ b/test/attr/attr_availability_unavailable_query.swift @@ -59,13 +59,3 @@ func testUnavailableExpandAllElsePaths() { foo() } } - -func log(message: String) {} - -@available(*, unavailable, renamed: "log(message:)") -func log(format: String, _ args: Any...) { fatalError() } // expected-note {{'log(format:_:)' has been explicitly marked unavailable here}} - -// Regression test for https://github.com/apple/swift/issues/64694 -func testUnavailableRenamedFromVariadicDoesntAssert() { - log(format: "") // expected-error{{'log(format:_:)' has been renamed to 'log(message:)'}} -} From 801c73e99073e7659c3f1c62d90803c8f26a91b0 Mon Sep 17 00:00:00 2001 From: Allan Shortlidge Date: Thu, 14 May 2026 09:15:02 -0700 Subject: [PATCH 20/25] test: Refactor platform-specific availability scope tests into a separate file. --- test/Availability/availability_scopes.swift | 539 +++--------------- .../availability_scopes_macos.swift | 467 +++++++++++++++ 2 files changed, 540 insertions(+), 466 deletions(-) create mode 100644 test/Availability/availability_scopes_macos.swift diff --git a/test/Availability/availability_scopes.swift b/test/Availability/availability_scopes.swift index 84918b8fd7b..3634cec4478 100644 --- a/test/Availability/availability_scopes.swift +++ b/test/Availability/availability_scopes.swift @@ -1,477 +1,84 @@ -// RUN: %target-swift-frontend -typecheck -dump-availability-scopes %s -target %target-cpu-apple-macos50 -swift-version 5 > %t.dump 2>&1 +// RUN: %target-swift-frontend -typecheck -dump-availability-scopes %s -swift-version 5 > %t.dump 2>&1 // RUN: %FileCheck --strict-whitespace %s < %t.dump -// REQUIRES: OS=macosx - -// CHECK: {{^}}(root version=50 - -// CHECK-NEXT: {{^}} (decl version=51 decl=SomeClass -// CHECK-NEXT: {{^}} (decl version=52 decl=someMethod() -// CHECK-NEXT: {{^}} (decl version=53 decl=someInnerFunc() -// CHECK-NEXT: {{^}} (decl version=53 decl=InnerClass -// CHECK-NEXT: {{^}} (decl version=54 decl=innerClassMethod -// CHECK-NEXT: {{^}} (decl_implicit version=51 decl=someStaticProperty -// CHECK-NEXT: {{^}} (decl version=52 decl=someStaticProperty -// CHECK-NEXT: {{^}} (decl_implicit version=51 decl=someStaticPropertyInferredType -// CHECK-NEXT: {{^}} (decl version=52 decl=someStaticPropertyInferredType -// CHECK-NEXT: {{^}} (decl_implicit version=51 decl=multiPatternStaticPropertyA -// CHECK-NEXT: {{^}} (decl version=52 decl=multiPatternStaticPropertyA -// CHECK-NEXT: {{^}} (decl_implicit version=51 decl=someComputedProperty -// CHECK-NEXT: {{^}} (decl version=52 decl=someComputedProperty -// CHECK-NEXT: {{^}} (decl version=52 decl=someOtherMethod() -@available(OSX 51, *) -class SomeClass { - @available(OSX 52, *) - func someMethod() { - - @available(OSX 53, *) - func someInnerFunc() { } - - @available(OSX 53, *) - class InnerClass { - @available(OSX 54, *) - func innerClassMethod() { } - } - } - - func someUnrefinedMethod() { } - - @available(OSX 52, *) - static var someStaticProperty: Int = 7 - - @available(OSX 52, *) - static var someStaticPropertyInferredType = 7 - - @available(OSX 52, *) - static var multiPatternStaticPropertyA = 7, - multiPatternStaticPropertyB = 8 - - @available(OSX 52, *) - var someComputedProperty: Int { - get { } - set(v) { } - } - - @available(OSX 52, *) - func someOtherMethod() { } -} - -// CHECK-NEXT: {{^}} (decl version=51 decl=someFunction() -@available(OSX 51, *) -func someFunction() { } - -// CHECK-NEXT: {{^}} (decl version=51 decl=SomeProtocol -// CHECK-NEXT: {{^}} (decl version=52 decl=protoMethod() -// CHECK-NEXT: {{^}} (decl_implicit version=51 decl=protoProperty -// CHECK-NEXT: {{^}} (decl version=52 decl=protoProperty -@available(OSX 51, *) -protocol SomeProtocol { - @available(OSX 52, *) - func protoMethod() -> Int - - @available(OSX 52, *) - var protoProperty: Int { get } -} - -// CHECK-NEXT: {{^}} (decl_implicit version=50 decl=extension.SomeClass -// CHECK-NEXT: {{^}} (decl version=51 decl=extension.SomeClass -// CHECK-NEXT: {{^}} (decl version=52 decl=someExtensionFunction() -@available(OSX 51, *) -extension SomeClass { - @available(OSX 52, *) - func someExtensionFunction() { } -} - -// CHECK-NEXT: {{^}} (decl version=51 decl=functionWithStmtCondition -// CHECK-NEXT: {{^}} (condition_following_availability version=52 -// CHECK-NEXT: {{^}} (condition_following_availability version=53 -// CHECK-NEXT: {{^}} (if_then version=53 -// CHECK-NEXT: {{^}} (condition_following_availability version=54 -// CHECK-NEXT: {{^}} (if_then version=54 -// CHECK-NEXT: {{^}} (condition_following_availability version=55 -// CHECK-NEXT: {{^}} (decl version=55 decl=funcInGuardElse() -// CHECK-NEXT: {{^}} (guard_fallthrough version=55 -// CHECK-NEXT: {{^}} (condition_following_availability version=56 -// CHECK-NEXT: {{^}} (guard_fallthrough version=56 -// CHECK-NEXT: {{^}} (decl version=57 decl=funcInInnerIfElse() -// CHECK-NEXT: {{^}} (decl version=53 decl=funcInOuterIfElse() -@available(OSX 51, *) -func functionWithStmtCondition() { - if #available(OSX 52, *), - let x = (nil as Int?), - #available(OSX 53, *) { - if #available(OSX 54, *) { - guard #available(OSX 55, *) else { - @available(OSX 55, *) - func funcInGuardElse() { } - } - guard #available(OSX 56, *) else { } - } else { - @available(OSX 57, *) - func funcInInnerIfElse() { } - } - } else { - @available(OSX 53, *) - func funcInOuterIfElse() { } - } -} - -// CHECK-NEXT: {{^}} (decl version=51 decl=functionWithUnnecessaryStmtCondition -// CHECK-NEXT: {{^}} (condition_following_availability version=53 -// CHECK-NEXT: {{^}} (if_then version=53 -// CHECK-NEXT: {{^}} (condition_following_availability version=54 -// CHECK-NEXT: {{^}} (if_then version=54 - -@available(OSX 51, *) -func functionWithUnnecessaryStmtCondition() { - // Shouldn't introduce availability scope for then branch when unnecessary - if #available(OSX 51, *) { - } - - if #available(OSX 10.9, *) { - } - - // Nested in conjunctive statement condition - if #available(OSX 53, *), - let x = (nil as Int?), - #available(OSX 52, *) { - } - - if #available(OSX 54, *), - #available(OSX 54, *) { - } - - // Wildcard is same as minimum deployment target - if #available(iOS 7.0, *) { - } -} - -// CHECK-NEXT: {{^}} (decl version=51 decl=functionWithUnnecessaryStmtConditionsHavingElseBranch -// CHECK-NEXT: {{^}} (if_else version=none -// CHECK-NEXT: {{^}} (decl version=none decl=funcInInnerIfElse() -// CHECK-NEXT: {{^}} (if_else version=none -// CHECK-NEXT: {{^}} (guard_else version=none -// CHECK-NEXT: {{^}} (guard_else version=none -// CHECK-NEXT: {{^}} (if_else version=none - -@available(OSX 51, *) -func functionWithUnnecessaryStmtConditionsHavingElseBranch(p: Int?) { - // Else branch context version is bottom when check is unnecessary - if #available(OSX 51, *) { - } else { - if #available(OSX 52, *) { - } - - @available(OSX 52, *) - func funcInInnerIfElse() { } - - if #available(iOS 7.0, *) { - } else { - } - } - - if #available(iOS 7.0, *) { - } else { - } - - guard #available(iOS 8.0, *) else { } - - // Else branch will execute if p is nil, so it is not dead. - if #available(iOS 7.0, *), - let x = p { - } else { - } - - if #available(iOS 7.0, *), - let x = p, - #available(iOS 7.0, *) { - } else { - } - - // Else branch is dead - guard #available(iOS 7.0, *), - #available(iOS 8.0, *) else { } - - if #available(OSX 51, *), - #available(OSX 51, *) { - } else { - } - -} - -// CHECK-NEXT: {{^}} (decl version=51 decl=functionWithWhile() -// CHECK-NEXT: {{^}} (condition_following_availability version=52 -// CHECK-NEXT: {{^}} (while_body version=52 -// CHECK-NEXT: {{^}} (decl version=54 decl=funcInWhileBody() -@available(OSX 51, *) -func functionWithWhile() { - while #available(OSX 52, *), - let x = (nil as Int?) { - @available(OSX 54, *) - func funcInWhileBody() { } - } -} - -// CHECK-NEXT: {{^}} (decl version=51 decl=functionWithDefer() -// CHECK-NEXT: {{^}} (condition_following_availability version=52 -// CHECK-NEXT: {{^}} (if_then version=52 -@available(OSX 51, *) -func functionWithDefer() { - defer { - if #available(OSX 52, *) {} - } -} - -// CHECK-NEXT: {{^}} (decl_implicit version=50 decl=extension.SomeClass -// CHECK-NEXT: {{^}} (decl version=51 decl=extension.SomeClass -// CHECK-NEXT: {{^}} (decl_implicit version=51 decl=someStaticPropertyWithClosureInit -// CHECK-NEXT: {{^}} (decl version=52 decl=someStaticPropertyWithClosureInit -// CHECK-NEXT: {{^}} (condition_following_availability version=54 -// CHECK-NEXT: {{^}} (if_then version=54 -// CHECK-NEXT: {{^}} (decl_implicit version=51 decl=someStaticPropertyWithClosureInitInferred -// CHECK-NEXT: {{^}} (decl version=52 decl=someStaticPropertyWithClosureInitInferred -// CHECK-NEXT: {{^}} (condition_following_availability version=54 -// CHECK-NEXT: {{^}} (if_then version=54 -@available(OSX 51, *) -extension SomeClass { - @available(OSX 52, *) - static var someStaticPropertyWithClosureInit: Int = { - if #available(OSX 54, *) { - return 54 - } - return 53 - }() - - @available(OSX 52, *) - static var someStaticPropertyWithClosureInitInferred = { - if #available(OSX 54, *) { - return 54 - } - return 53 - }() -} - -// CHECK-NEXT: {{^}} (decl_implicit version=50 decl=extension.SomeClass -// CHECK-NEXT: {{^}} (decl version=50 unavailable=macOS decl=extension.SomeClass -// CHECK-NEXT: {{^}} (decl version=50 unavailable=macOS decl=functionWithStmtConditionsInUnavailableExt() -// CHECK-NEXT: {{^}} (condition_following_availability version=52 unavailable=macOS -// CHECK-NEXT: {{^}} (condition_following_availability version=53 unavailable=macOS -// CHECK-NEXT: {{^}} (if_then version=53 unavailable=macOS -// CHECK-NEXT: {{^}} (condition_following_availability version=54 unavailable=macOS -// CHECK-NEXT: {{^}} (if_then version=54 unavailable=macOS -// CHECK-NEXT: {{^}} (condition_following_availability version=55 unavailable=macOS -// CHECK-NEXT: {{^}} (decl version=54 unavailable=macOS decl=funcInGuardElse() -// CHECK-NEXT: {{^}} (guard_fallthrough version=55 unavailable=macOS -// CHECK-NEXT: {{^}} (condition_following_availability version=56 unavailable=macOS -// CHECK-NEXT: {{^}} (guard_fallthrough version=56 unavailable=macOS -// CHECK-NEXT: {{^}} (decl version=53 unavailable=macOS decl=funcInInnerIfElse() -// CHECK-NEXT: {{^}} (decl version=50 unavailable=macOS decl=funcInOuterIfElse() -@available(OSX, unavailable) -extension SomeClass { - @available(OSX 51, *) - func functionWithStmtConditionsInUnavailableExt() { - if #available(OSX 52, *), - let x = (nil as Int?), - #available(OSX 53, *) { - if #available(OSX 54, *) { - guard #available(OSX 55, *) else { - @available(OSX 55, *) - func funcInGuardElse() { } - } - guard #available(OSX 56, *) else { } - } else { - @available(OSX 57, *) - func funcInInnerIfElse() { } - } - } else { - @available(OSX 53, *) - func funcInOuterIfElse() { } - } - } -} - -// CHECK-NEXT: {{^}} (decl_implicit version=50 decl=wrappedValue - -@propertyWrapper -struct Wrapper { - var wrappedValue: T -} - -// CHECK-NEXT: {{^}} (decl version=51 decl=SomeStruct -// CHECK-NEXT: {{^}} (decl_implicit version=51 decl=someLazyVar -// CHECK-NEXT: {{^}} (condition_following_availability version=52 -// CHECK-NEXT: {{^}} (guard_fallthrough version=52 -// CHECK-NEXT: {{^}} (decl_implicit version=51 decl=someWrappedVar -// CHECK-NEXT: {{^}} (condition_following_availability version=52 -// CHECK-NEXT: {{^}} (guard_fallthrough version=52 -// CHECK-NEXT: {{^}} (decl version=52 decl=someMethodAvailable52() -@available(OSX 51, *) -struct SomeStruct { - lazy var someLazyVar = { - guard #available(OSX 52, *) else { - return someMethod() - } - return someMethodAvailable52() - }() - - @Wrapper var someWrappedVar = { - guard #available(OSX 52, *) else { - return 51 - } - return 52 - }() - - func someMethod() -> Int { return 51 } - - @available(OSX 52, *) - func someMethodAvailable52() -> Int { return 52 } -} - -// CHECK-NEXT: {{^}} (decl version=51 decl=SomeEnum -// CHECK-NEXT: {{^}} (decl version=52 decl=a -// CHECK-NEXT: {{^}} (decl version=53 decl=b - -@available(OSX 51, *) -enum SomeEnum { - @available(OSX 52, *) - case a - - @available(OSX 53, *) - case b, c -} - -// CHECK-NEXT: {{^}} (decl_implicit version=50 decl=someComputedGlobalVar -// CHECK-NEXT: {{^}} (decl version=51 decl=_ -// CHECK-NEXT: {{^}} (decl version=52 decl=_ - -var someComputedGlobalVar: Int { - @available(OSX 51, *) - get { 1 } - - @available(OSX 52, *) - set { } -} - -// CHECK-NEXT: {{^}} (decl_implicit version=50 decl=interpolated -// CHECK-NEXT: {{^}} (decl_implicit version=50 decl=string - -func testStringInterpolation() { - let interpolated = """ - \([""].map { - let string = $0 - return string - }) - """ -} - -// CHECK-NEXT: {{^}} (decl_implicit version=50 decl=result -// CHECK-NEXT: {{^}} (decl_implicit version=50 decl=unusedA -// CHECK-NEXT: {{^}} (decl_implicit version=50 decl=unusedB - -func testSequenceExprs(b: Bool, x: Int?) { - let result = b - ? x.map { - let unusedA: Int - return $0 - } - : x.map { - let unusedB: Int - return $0 - } -} - -// CHECK-NEXT: {{^}} (decl version=50 unavailable=macOS decl=unavailableOnMacOS() -// CHECK-NEXT: {{^}} (decl_implicit version=50 unavailable=macOS decl=x - -@available(macOS, unavailable) -func unavailableOnMacOS() { - let x = 1 -} - -// CHECK-NEXT: {{^}} (decl_implicit version=50 decl=extension.SomeEnum -// CHECK-NEXT: {{^}} (decl version=51 decl=extension.SomeEnum -// CHECK-NEXT: {{^}} (decl_implicit version=51 decl=unavailableOnMacOS -// CHECK-NEXT: {{^}} (decl version=51 unavailable=macOS decl=unavailableOnMacOS -@available(OSX 51, *) -extension SomeEnum { - @available(macOS, unavailable) - var unavailableOnMacOS: Int { 1 } -} - -// CHECK-NEXT: {{^}} (decl_implicit version=50 decl=extension.SomeEnum -// CHECK-NEXT: {{^}} (decl version=50 unavailable=macOS decl=extension.SomeEnum -// CHECK-NEXT: {{^}} (decl_implicit version=50 unavailable=macOS decl=availableMacOS_52 -// CHECK-NEXT: {{^}} (decl version=50 unavailable=macOS decl=availableMacOS_52 -// CHECK-NEXT: {{^}} (decl version=50 unavailable=*,macOS decl=neverAvailable() - -@available(macOS, unavailable) -extension SomeEnum { - @available(OSX 52, *) - var availableMacOS_52: Int { 1 } - - @available(macOSApplicationExtension, unavailable) - func unavailableInAppExtensions() {} - - @available(*, unavailable) - func neverAvailable() {} -} - -// CHECK-NEXT: {{^}} (decl version=50 unavailable=macOS decl=unavailableOnMacOSAndIntroduced() - -@available(macOS, unavailable) -@available(macOS, introduced: 52) -func unavailableOnMacOSAndIntroduced() { -} - -// CHECK-NEXT: {{^}} (decl version=50 unavailable=macOS decl=introducedOnMacOSAndUnavailable() - -@available(macOS, introduced: 53) -@available(macOS, unavailable) -func introducedOnMacOSAndUnavailable() { -} - - -// CHECK-NEXT: {{^}} (decl version=50 unavailable=macOS decl=unavailableOnMacOSAndIntroducedSameAttr() - -@available(macOS, unavailable, introduced: 54) -func unavailableOnMacOSAndIntroducedSameAttr() { -} - -// CHECK-NEXT: {{^}} (decl version=50 unavailable=* decl=NeverAvailable -// CHECK-NEXT: {{^}} (decl version=50 unavailable=* decl=unavailableOnMacOS() +// CHECK: {{^}}(root +// CHECK-NEXT: {{^}} (decl version={{.*}} unavailable=* decl=universallyUnavailable() @available(*, unavailable) -struct NeverAvailable { - @available(macOS, unavailable) - func unavailableOnMacOS() {} -} +func universallyUnavailable() { } -// CHECK-NEXT: {{^}} (decl version=50 deprecated decl=deprecatedOnMacOS() -// CHECK-NEXT: {{^}} (decl_implicit version=50 deprecated decl=x - -@available(macOS, deprecated) -func deprecatedOnMacOS() { - let x = 1 -} - -// Since availableOniOS() doesn't have any active @available attributes it -// shouldn't create a scope. -// CHECK-NOT: availableOniOS - -@available(iOS, introduced: 53) -func availableOniOS() { } - -// CHECK-NEXT: {{^}} (decl version=50 decl=availableInSwift5 +// CHECK-NEXT: {{^}} (decl version={{.*}} deprecated decl=universallyDeprecated() +@available(*, deprecated) +func universallyDeprecated() { } +// CHECK-NEXT: {{^}} (decl version={{.*}} decl=introducedInSwift5() @available(swift 5) -func availableInSwift5() { } - -// CHECK-NEXT: {{^}} (decl version=50 unavailable=swift decl=availableInSwift6 +func introducedInSwift5() { } +// CHECK-NEXT: {{^}} (decl version={{.*}} unavailable=swift decl=introducedInSwift6() @available(swift 6) -func availableInSwift6() { } +func introducedInSwift6() { } -// CHECK-NEXT: {{^}} (decl version=51 decl=FinalDecl +// CHECK-NEXT: {{^}} (decl version={{.*}} unavailable=swift decl=obsoletedInSwift5() +@available(swift, obsoleted: 5) +func obsoletedInSwift5() { } -@available(OSX 51, *) -typealias FinalDecl = Int +// CHECK-NEXT: {{^}} (decl version={{.*}} decl=obsoletedInSwift6() +@available(swift, obsoleted: 6) +func obsoletedInSwift6() { } + +// CHECK-NEXT: {{^}} (decl version={{.*}} unavailable=* decl=UniversallyUnavailable +// CHECK-NEXT: {{^}} (decl version={{.*}} unavailable=* decl=universallyUnavailable() +// CHECK-NEXT: {{^}} (decl version={{.*}} unavailable=* deprecated decl=universallyDeprecated() +// CHECK-NEXT: {{^}} (decl version={{.*}} unavailable=* decl=introducedInSwift5() +// CHECK-NEXT: {{^}} (decl version={{.*}} unavailable=*,swift decl=introducedInSwift6() +// CHECK-NEXT: {{^}} (decl version={{.*}} unavailable=*,swift decl=obsoletedInSwift5 +// CHECK-NEXT: {{^}} (decl version={{.*}} unavailable=* decl=obsoletedInSwift6() +@available(*, unavailable) +struct UniversallyUnavailable { + @available(*, unavailable) + func universallyUnavailable() { } + + @available(*, deprecated) + func universallyDeprecated() { } + + @available(swift 5) + func introducedInSwift5() { } + + @available(swift 6) + func introducedInSwift6() { } + + @available(swift, obsoleted: 5) + func obsoletedInSwift5() { } + + @available(swift, obsoleted: 6) + func obsoletedInSwift6() { } +} + +// CHECK-NEXT: {{^}} (decl version={{.*}} unavailable=swift decl=IntroducedInSwift6 +// CHECK-NEXT: {{^}} (decl version={{.*}} unavailable=*,swift decl=universallyUnavailable() +// CHECK-NEXT: {{^}} (decl version={{.*}} unavailable=swift deprecated decl=universallyDeprecated() +// CHECK-NEXT: {{^}} (decl version={{.*}} unavailable=swift decl=introducedInSwift5() +// CHECK-NEXT: {{^}} (decl version={{.*}} unavailable=swift decl=introducedInSwift6() +// CHECK-NEXT: {{^}} (decl version={{.*}} unavailable=swift decl=obsoletedInSwift5 +// CHECK-NEXT: {{^}} (decl version={{.*}} unavailable=swift decl=obsoletedInSwift6() +@available(swift 6) +struct IntroducedInSwift6 { + @available(*, unavailable) + func universallyUnavailable() { } + + @available(*, deprecated) + func universallyDeprecated() { } + + @available(swift 5) + func introducedInSwift5() { } + + @available(swift 6) + func introducedInSwift6() { } + + @available(swift, obsoleted: 5) + func obsoletedInSwift5() { } + + @available(swift, obsoleted: 6) + func obsoletedInSwift6() { } +} diff --git a/test/Availability/availability_scopes_macos.swift b/test/Availability/availability_scopes_macos.swift new file mode 100644 index 00000000000..e6c970b99c7 --- /dev/null +++ b/test/Availability/availability_scopes_macos.swift @@ -0,0 +1,467 @@ +// RUN: %target-swift-frontend -typecheck -dump-availability-scopes %s -target %target-cpu-apple-macos50 -swift-version 5 > %t.dump 2>&1 +// RUN: %FileCheck --strict-whitespace %s < %t.dump + +// REQUIRES: OS=macosx + +// CHECK: {{^}}(root version=50 + +// CHECK-NEXT: {{^}} (decl version=51 decl=SomeClass +// CHECK-NEXT: {{^}} (decl version=52 decl=someMethod() +// CHECK-NEXT: {{^}} (decl version=53 decl=someInnerFunc() +// CHECK-NEXT: {{^}} (decl version=53 decl=InnerClass +// CHECK-NEXT: {{^}} (decl version=54 decl=innerClassMethod +// CHECK-NEXT: {{^}} (decl_implicit version=51 decl=someStaticProperty +// CHECK-NEXT: {{^}} (decl version=52 decl=someStaticProperty +// CHECK-NEXT: {{^}} (decl_implicit version=51 decl=someStaticPropertyInferredType +// CHECK-NEXT: {{^}} (decl version=52 decl=someStaticPropertyInferredType +// CHECK-NEXT: {{^}} (decl_implicit version=51 decl=multiPatternStaticPropertyA +// CHECK-NEXT: {{^}} (decl version=52 decl=multiPatternStaticPropertyA +// CHECK-NEXT: {{^}} (decl_implicit version=51 decl=someComputedProperty +// CHECK-NEXT: {{^}} (decl version=52 decl=someComputedProperty +// CHECK-NEXT: {{^}} (decl version=52 decl=someOtherMethod() +@available(OSX 51, *) +class SomeClass { + @available(OSX 52, *) + func someMethod() { + + @available(OSX 53, *) + func someInnerFunc() { } + + @available(OSX 53, *) + class InnerClass { + @available(OSX 54, *) + func innerClassMethod() { } + } + } + + func someUnrefinedMethod() { } + + @available(OSX 52, *) + static var someStaticProperty: Int = 7 + + @available(OSX 52, *) + static var someStaticPropertyInferredType = 7 + + @available(OSX 52, *) + static var multiPatternStaticPropertyA = 7, + multiPatternStaticPropertyB = 8 + + @available(OSX 52, *) + var someComputedProperty: Int { + get { } + set(v) { } + } + + @available(OSX 52, *) + func someOtherMethod() { } +} + +// CHECK-NEXT: {{^}} (decl version=51 decl=someFunction() +@available(OSX 51, *) +func someFunction() { } + +// CHECK-NEXT: {{^}} (decl version=51 decl=SomeProtocol +// CHECK-NEXT: {{^}} (decl version=52 decl=protoMethod() +// CHECK-NEXT: {{^}} (decl_implicit version=51 decl=protoProperty +// CHECK-NEXT: {{^}} (decl version=52 decl=protoProperty +@available(OSX 51, *) +protocol SomeProtocol { + @available(OSX 52, *) + func protoMethod() -> Int + + @available(OSX 52, *) + var protoProperty: Int { get } +} + +// CHECK-NEXT: {{^}} (decl_implicit version=50 decl=extension.SomeClass +// CHECK-NEXT: {{^}} (decl version=51 decl=extension.SomeClass +// CHECK-NEXT: {{^}} (decl version=52 decl=someExtensionFunction() +@available(OSX 51, *) +extension SomeClass { + @available(OSX 52, *) + func someExtensionFunction() { } +} + +// CHECK-NEXT: {{^}} (decl version=51 decl=functionWithStmtCondition +// CHECK-NEXT: {{^}} (condition_following_availability version=52 +// CHECK-NEXT: {{^}} (condition_following_availability version=53 +// CHECK-NEXT: {{^}} (if_then version=53 +// CHECK-NEXT: {{^}} (condition_following_availability version=54 +// CHECK-NEXT: {{^}} (if_then version=54 +// CHECK-NEXT: {{^}} (condition_following_availability version=55 +// CHECK-NEXT: {{^}} (decl version=55 decl=funcInGuardElse() +// CHECK-NEXT: {{^}} (guard_fallthrough version=55 +// CHECK-NEXT: {{^}} (condition_following_availability version=56 +// CHECK-NEXT: {{^}} (guard_fallthrough version=56 +// CHECK-NEXT: {{^}} (decl version=57 decl=funcInInnerIfElse() +// CHECK-NEXT: {{^}} (decl version=53 decl=funcInOuterIfElse() +@available(OSX 51, *) +func functionWithStmtCondition() { + if #available(OSX 52, *), + let x = (nil as Int?), + #available(OSX 53, *) { + if #available(OSX 54, *) { + guard #available(OSX 55, *) else { + @available(OSX 55, *) + func funcInGuardElse() { } + } + guard #available(OSX 56, *) else { } + } else { + @available(OSX 57, *) + func funcInInnerIfElse() { } + } + } else { + @available(OSX 53, *) + func funcInOuterIfElse() { } + } +} + +// CHECK-NEXT: {{^}} (decl version=51 decl=functionWithUnnecessaryStmtCondition +// CHECK-NEXT: {{^}} (condition_following_availability version=53 +// CHECK-NEXT: {{^}} (if_then version=53 +// CHECK-NEXT: {{^}} (condition_following_availability version=54 +// CHECK-NEXT: {{^}} (if_then version=54 + +@available(OSX 51, *) +func functionWithUnnecessaryStmtCondition() { + // Shouldn't introduce availability scope for then branch when unnecessary + if #available(OSX 51, *) { + } + + if #available(OSX 10.9, *) { + } + + // Nested in conjunctive statement condition + if #available(OSX 53, *), + let x = (nil as Int?), + #available(OSX 52, *) { + } + + if #available(OSX 54, *), + #available(OSX 54, *) { + } + + // Wildcard is same as minimum deployment target + if #available(iOS 7.0, *) { + } +} + +// CHECK-NEXT: {{^}} (decl version=51 decl=functionWithUnnecessaryStmtConditionsHavingElseBranch +// CHECK-NEXT: {{^}} (if_else version=none +// CHECK-NEXT: {{^}} (decl version=none decl=funcInInnerIfElse() +// CHECK-NEXT: {{^}} (if_else version=none +// CHECK-NEXT: {{^}} (guard_else version=none +// CHECK-NEXT: {{^}} (guard_else version=none +// CHECK-NEXT: {{^}} (if_else version=none + +@available(OSX 51, *) +func functionWithUnnecessaryStmtConditionsHavingElseBranch(p: Int?) { + // Else branch context version is bottom when check is unnecessary + if #available(OSX 51, *) { + } else { + if #available(OSX 52, *) { + } + + @available(OSX 52, *) + func funcInInnerIfElse() { } + + if #available(iOS 7.0, *) { + } else { + } + } + + if #available(iOS 7.0, *) { + } else { + } + + guard #available(iOS 8.0, *) else { } + + // Else branch will execute if p is nil, so it is not dead. + if #available(iOS 7.0, *), + let x = p { + } else { + } + + if #available(iOS 7.0, *), + let x = p, + #available(iOS 7.0, *) { + } else { + } + + // Else branch is dead + guard #available(iOS 7.0, *), + #available(iOS 8.0, *) else { } + + if #available(OSX 51, *), + #available(OSX 51, *) { + } else { + } + +} + +// CHECK-NEXT: {{^}} (decl version=51 decl=functionWithWhile() +// CHECK-NEXT: {{^}} (condition_following_availability version=52 +// CHECK-NEXT: {{^}} (while_body version=52 +// CHECK-NEXT: {{^}} (decl version=54 decl=funcInWhileBody() +@available(OSX 51, *) +func functionWithWhile() { + while #available(OSX 52, *), + let x = (nil as Int?) { + @available(OSX 54, *) + func funcInWhileBody() { } + } +} + +// CHECK-NEXT: {{^}} (decl version=51 decl=functionWithDefer() +// CHECK-NEXT: {{^}} (condition_following_availability version=52 +// CHECK-NEXT: {{^}} (if_then version=52 +@available(OSX 51, *) +func functionWithDefer() { + defer { + if #available(OSX 52, *) {} + } +} + +// CHECK-NEXT: {{^}} (decl_implicit version=50 decl=extension.SomeClass +// CHECK-NEXT: {{^}} (decl version=51 decl=extension.SomeClass +// CHECK-NEXT: {{^}} (decl_implicit version=51 decl=someStaticPropertyWithClosureInit +// CHECK-NEXT: {{^}} (decl version=52 decl=someStaticPropertyWithClosureInit +// CHECK-NEXT: {{^}} (condition_following_availability version=54 +// CHECK-NEXT: {{^}} (if_then version=54 +// CHECK-NEXT: {{^}} (decl_implicit version=51 decl=someStaticPropertyWithClosureInitInferred +// CHECK-NEXT: {{^}} (decl version=52 decl=someStaticPropertyWithClosureInitInferred +// CHECK-NEXT: {{^}} (condition_following_availability version=54 +// CHECK-NEXT: {{^}} (if_then version=54 +@available(OSX 51, *) +extension SomeClass { + @available(OSX 52, *) + static var someStaticPropertyWithClosureInit: Int = { + if #available(OSX 54, *) { + return 54 + } + return 53 + }() + + @available(OSX 52, *) + static var someStaticPropertyWithClosureInitInferred = { + if #available(OSX 54, *) { + return 54 + } + return 53 + }() +} + +// CHECK-NEXT: {{^}} (decl_implicit version=50 decl=extension.SomeClass +// CHECK-NEXT: {{^}} (decl version=50 unavailable=macOS decl=extension.SomeClass +// CHECK-NEXT: {{^}} (decl version=50 unavailable=macOS decl=functionWithStmtConditionsInUnavailableExt() +// CHECK-NEXT: {{^}} (condition_following_availability version=52 unavailable=macOS +// CHECK-NEXT: {{^}} (condition_following_availability version=53 unavailable=macOS +// CHECK-NEXT: {{^}} (if_then version=53 unavailable=macOS +// CHECK-NEXT: {{^}} (condition_following_availability version=54 unavailable=macOS +// CHECK-NEXT: {{^}} (if_then version=54 unavailable=macOS +// CHECK-NEXT: {{^}} (condition_following_availability version=55 unavailable=macOS +// CHECK-NEXT: {{^}} (decl version=54 unavailable=macOS decl=funcInGuardElse() +// CHECK-NEXT: {{^}} (guard_fallthrough version=55 unavailable=macOS +// CHECK-NEXT: {{^}} (condition_following_availability version=56 unavailable=macOS +// CHECK-NEXT: {{^}} (guard_fallthrough version=56 unavailable=macOS +// CHECK-NEXT: {{^}} (decl version=53 unavailable=macOS decl=funcInInnerIfElse() +// CHECK-NEXT: {{^}} (decl version=50 unavailable=macOS decl=funcInOuterIfElse() +@available(OSX, unavailable) +extension SomeClass { + @available(OSX 51, *) + func functionWithStmtConditionsInUnavailableExt() { + if #available(OSX 52, *), + let x = (nil as Int?), + #available(OSX 53, *) { + if #available(OSX 54, *) { + guard #available(OSX 55, *) else { + @available(OSX 55, *) + func funcInGuardElse() { } + } + guard #available(OSX 56, *) else { } + } else { + @available(OSX 57, *) + func funcInInnerIfElse() { } + } + } else { + @available(OSX 53, *) + func funcInOuterIfElse() { } + } + } +} + +// CHECK-NEXT: {{^}} (decl_implicit version=50 decl=wrappedValue + +@propertyWrapper +struct Wrapper { + var wrappedValue: T +} + +// CHECK-NEXT: {{^}} (decl version=51 decl=SomeStruct +// CHECK-NEXT: {{^}} (decl_implicit version=51 decl=someLazyVar +// CHECK-NEXT: {{^}} (condition_following_availability version=52 +// CHECK-NEXT: {{^}} (guard_fallthrough version=52 +// CHECK-NEXT: {{^}} (decl_implicit version=51 decl=someWrappedVar +// CHECK-NEXT: {{^}} (condition_following_availability version=52 +// CHECK-NEXT: {{^}} (guard_fallthrough version=52 +// CHECK-NEXT: {{^}} (decl version=52 decl=someMethodAvailable52() +@available(OSX 51, *) +struct SomeStruct { + lazy var someLazyVar = { + guard #available(OSX 52, *) else { + return someMethod() + } + return someMethodAvailable52() + }() + + @Wrapper var someWrappedVar = { + guard #available(OSX 52, *) else { + return 51 + } + return 52 + }() + + func someMethod() -> Int { return 51 } + + @available(OSX 52, *) + func someMethodAvailable52() -> Int { return 52 } +} + +// CHECK-NEXT: {{^}} (decl version=51 decl=SomeEnum +// CHECK-NEXT: {{^}} (decl version=52 decl=a +// CHECK-NEXT: {{^}} (decl version=53 decl=b + +@available(OSX 51, *) +enum SomeEnum { + @available(OSX 52, *) + case a + + @available(OSX 53, *) + case b, c +} + +// CHECK-NEXT: {{^}} (decl_implicit version=50 decl=someComputedGlobalVar +// CHECK-NEXT: {{^}} (decl version=51 decl=_ +// CHECK-NEXT: {{^}} (decl version=52 decl=_ + +var someComputedGlobalVar: Int { + @available(OSX 51, *) + get { 1 } + + @available(OSX 52, *) + set { } +} + +// CHECK-NEXT: {{^}} (decl_implicit version=50 decl=interpolated +// CHECK-NEXT: {{^}} (decl_implicit version=50 decl=string + +func testStringInterpolation() { + let interpolated = """ + \([""].map { + let string = $0 + return string + }) + """ +} + +// CHECK-NEXT: {{^}} (decl_implicit version=50 decl=result +// CHECK-NEXT: {{^}} (decl_implicit version=50 decl=unusedA +// CHECK-NEXT: {{^}} (decl_implicit version=50 decl=unusedB + +func testSequenceExprs(b: Bool, x: Int?) { + let result = b + ? x.map { + let unusedA: Int + return $0 + } + : x.map { + let unusedB: Int + return $0 + } +} + +// CHECK-NEXT: {{^}} (decl version=50 unavailable=macOS decl=unavailableOnMacOS() +// CHECK-NEXT: {{^}} (decl_implicit version=50 unavailable=macOS decl=x + +@available(macOS, unavailable) +func unavailableOnMacOS() { + let x = 1 +} + +// CHECK-NEXT: {{^}} (decl_implicit version=50 decl=extension.SomeEnum +// CHECK-NEXT: {{^}} (decl version=51 decl=extension.SomeEnum +// CHECK-NEXT: {{^}} (decl_implicit version=51 decl=unavailableOnMacOS +// CHECK-NEXT: {{^}} (decl version=51 unavailable=macOS decl=unavailableOnMacOS +@available(OSX 51, *) +extension SomeEnum { + @available(macOS, unavailable) + var unavailableOnMacOS: Int { 1 } +} + +// CHECK-NEXT: {{^}} (decl_implicit version=50 decl=extension.SomeEnum +// CHECK-NEXT: {{^}} (decl version=50 unavailable=macOS decl=extension.SomeEnum +// CHECK-NEXT: {{^}} (decl_implicit version=50 unavailable=macOS decl=availableMacOS_52 +// CHECK-NEXT: {{^}} (decl version=50 unavailable=macOS decl=availableMacOS_52 +// CHECK-NEXT: {{^}} (decl version=50 unavailable=*,macOS decl=neverAvailable() + +@available(macOS, unavailable) +extension SomeEnum { + @available(OSX 52, *) + var availableMacOS_52: Int { 1 } + + @available(macOSApplicationExtension, unavailable) + func unavailableInAppExtensions() {} + + @available(*, unavailable) + func neverAvailable() {} +} + +// CHECK-NEXT: {{^}} (decl version=50 unavailable=macOS decl=unavailableOnMacOSAndIntroduced() + +@available(macOS, unavailable) +@available(macOS, introduced: 52) +func unavailableOnMacOSAndIntroduced() { +} + +// CHECK-NEXT: {{^}} (decl version=50 unavailable=macOS decl=introducedOnMacOSAndUnavailable() + +@available(macOS, introduced: 53) +@available(macOS, unavailable) +func introducedOnMacOSAndUnavailable() { +} + + +// CHECK-NEXT: {{^}} (decl version=50 unavailable=macOS decl=unavailableOnMacOSAndIntroducedSameAttr() + +@available(macOS, unavailable, introduced: 54) +func unavailableOnMacOSAndIntroducedSameAttr() { +} + +// CHECK-NEXT: {{^}} (decl version=50 unavailable=* decl=NeverAvailable +// CHECK-NEXT: {{^}} (decl version=50 unavailable=* decl=unavailableOnMacOS() + +@available(*, unavailable) +struct NeverAvailable { + @available(macOS, unavailable) + func unavailableOnMacOS() {} +} + +// CHECK-NEXT: {{^}} (decl version=50 deprecated decl=deprecatedOnMacOS() +// CHECK-NEXT: {{^}} (decl_implicit version=50 deprecated decl=x + +@available(macOS, deprecated) +func deprecatedOnMacOS() { + let x = 1 +} + +// Since availableOniOS() doesn't have any active @available attributes it +// shouldn't create a scope. +// CHECK-NOT: availableOniOS + +@available(iOS, introduced: 53) +func availableOniOS() { } + +// CHECK-NEXT: {{^}} (decl version=51 decl=FinalDecl + +@available(OSX 51, *) +typealias FinalDecl = Int From fb7c8816a56fb8d573647d55c4f3f08218081410 Mon Sep 17 00:00:00 2001 From: Allan Shortlidge Date: Wed, 13 May 2026 11:28:34 -0700 Subject: [PATCH 21/25] AST: Fix availability scopes for `if #unavailable` else branches. When an `if #unavalable` statement also includes a secondary (non-availability) condition, the availability scope for the else branch should not be refined. The else branch can be reached because the secondary condition failed, in which case the availability condition may not hold. Resolves rdar://165863221. --- lib/AST/AvailabilityScopeBuilder.cpp | 67 ++++++++++--------- .../availability_custom_domains.swift | 18 ++++- .../result_builder_availability.swift | 9 ++- .../attr_availability_unavailable_query.swift | 36 ++++++++++ 4 files changed, 94 insertions(+), 36 deletions(-) diff --git a/lib/AST/AvailabilityScopeBuilder.cpp b/lib/AST/AvailabilityScopeBuilder.cpp index 92ee6051c19..3d1195944bb 100644 --- a/lib/AST/AvailabilityScopeBuilder.cpp +++ b/lib/AST/AvailabilityScopeBuilder.cpp @@ -1001,6 +1001,7 @@ private: // Tracks if we're refining for availability or unavailability. std::optional isUnavailability = std::nullopt; + bool hasAnyNonAvailabilityCondition = false; for (StmtConditionElement element : cond) { auto *currentScope = getCurrentScope(); @@ -1008,10 +1009,7 @@ private: // If the element is not a condition, walk it in the current scope. if (element.getKind() != StmtConditionElement::CK_Availability) { - // Assume any condition element that is not a #available() can - // potentially be false, so conservatively make the false flow's - // refinement undefined since there is nothing we can prove about it. - falseFlowBuilder.setUndefined(); + hasAnyNonAvailabilityCondition = true; element.walk(*this); continue; } @@ -1137,44 +1135,53 @@ private: ++nestedCount; } + // Determine the availability context for the branch where the availability + // conditions hold. If there are any scopes on the stack, it will be the + // availability context for the scope at the top. Otherwise, no distinct + // context is introduced by the availability conditions. + std::optional trueRefinement = std::nullopt; + if (nestedCount > 0) + trueRefinement = getCurrentScope()->getAvailabilityContext(); + + // Pop the stack. + while (nestedCount-- > 0) + ContextStack.pop_back(); + + DEBUG_ASSERT(getCurrentScope() == startingScope); + + // Determine availability for the branch where the availability conditions + // do not hold. auto startingContext = startingScope->getAvailabilityContext(); auto falseFlowContext = falseFlowBuilder.constrainContext(startingContext); - // The version range for the false branch should never have any versions - // that weren't possible when the condition started evaluating. + // The availability context for the false flow should either be the same + // as the starting context or it should refine it. If not, there's a logic + // error. DEBUG_ASSERT(falseFlowContext.isContainedIn(startingContext)); // If the starting availability context is not completely contained in the // false flow context then it must be the case that false flow context - // is strictly smaller than the starting context (because the false flow - // context *is* contained in the starting context), so we should introduce a - // new availability scope for the false flow. + // is strictly contained in the starting context. Introduce a new + // availability scope for the false flow in that case. std::optional falseRefinement = std::nullopt; - if (!startingScope->getAvailabilityContext().isContainedIn( - falseFlowContext)) { + if (!startingContext.isContainedIn(falseFlowContext)) falseRefinement = falseFlowContext; - } - auto makeResult = - [isUnavailability](std::optional trueRefinement, - std::optional falseRefinement) { - if (isUnavailability.has_value() && *isUnavailability) { - // If this is an unavailability check, invert the result. - return std::make_pair(falseRefinement, trueRefinement); - } - return std::make_pair(trueRefinement, falseRefinement); - }; + // For #unavailable, the then/else semantics are inverted: the then branch + // executes when the availability condition is NOT met, and the else + // executes it IS met. So swap the refinements. + auto thenRefinement = trueRefinement; + auto elseRefinement = falseRefinement; + if (isUnavailability && *isUnavailability) + std::swap(thenRefinement, elseRefinement); - if (nestedCount == 0) - return makeResult(std::nullopt, falseRefinement); + // If there were any non-availability conditions in the if statement then + // the else branch cannot be refined at all because it can be reached + // regardless of any availability condition. + if (hasAnyNonAvailabilityCondition) + elseRefinement = std::nullopt; - AvailabilityScope *nestedScope = getCurrentScope(); - while (nestedCount-- > 0) - ContextStack.pop_back(); - - assert(getCurrentScope() == startingScope); - - return makeResult(nestedScope->getAvailabilityContext(), falseRefinement); + return {thenRefinement, elseRefinement}; } /// Return the best active spec for the target platform or nullptr if no diff --git a/test/Availability/availability_custom_domains.swift b/test/Availability/availability_custom_domains.swift index 517c97494a1..2abe7ac259a 100644 --- a/test/Availability/availability_custom_domains.swift +++ b/test/Availability/availability_custom_domains.swift @@ -64,7 +64,7 @@ func testDeployment() { // expected-note 3 {{add '@available' attribute to enclo // FIXME: [availability] Test @inlinable functions. -func testIfAvailable(_ truthy: Bool) { // expected-note 9 {{add '@available' attribute to enclosing global function}} +func testIfAvailable(_ truthy: Bool) { // expected-note 11 {{add '@available' attribute to enclosing global function}} if #available(EnabledDomain) { // expected-note {{enclosing scope here}} availableInEnabledDomain() availableInAlwaysEnabledDomain() @@ -142,6 +142,18 @@ func testIfAvailable(_ truthy: Bool) { // expected-note 9 {{add '@available' att unavailableInEnabledDomain() // expected-error {{'unavailableInEnabledDomain()' is unavailable}} } + if #unavailable(EnabledDomain), truthy { + availableInEnabledDomain() // expected-error {{'availableInEnabledDomain()' is only available in EnabledDomain}} + // expected-note@-1 {{add 'if #available' version check}} + unavailableInEnabledDomain() + } else { + // In this branch, the state of EnabledDomain remains unknown since + // execution will reach here if "truthy" is false. + availableInEnabledDomain() // expected-error {{'availableInEnabledDomain()' is only available in EnabledDomain}} + // expected-note@-1 {{add 'if #available' version check}} + unavailableInEnabledDomain() // expected-error {{'unavailableInEnabledDomain()' is unavailable}} + } + // FIXME: [availability] Support mixed #available and #unavailable. if #unavailable(EnabledDomain), #available(DynamicDomain) { // expected-error@-1 {{#available and #unavailable cannot be in the same statement}} @@ -242,8 +254,8 @@ func testAlwaysEnabledDomainUnavailable() { @available(*, unavailable) func testUniversallyUnavailable() { - // expected-note@-1 {{add '@available' attribute to enclosing global function}}{{243:1-1=@available(EnabledDomain)\n}} - // expected-note@-2 {{add '@available' attribute to enclosing global function}}{{243:1-1=@available(DynamicDomain)\n}} + // expected-note@-1 {{add '@available' attribute to enclosing global function}}{{-1:1-1=@available(EnabledDomain)\n}} + // expected-note@-2 {{add '@available' attribute to enclosing global function}}{{-1:1-1=@available(DynamicDomain)\n}} alwaysAvailable() // FIXME: [availability] Diagnostic consistency: potentially unavailable declaration shouldn't be diagnosed // in contexts that are unavailable to broader domains diff --git a/test/Availability/result_builder_availability.swift b/test/Availability/result_builder_availability.swift index 0777d011a82..35015d1f012 100644 --- a/test/Availability/result_builder_availability.swift +++ b/test/Availability/result_builder_availability.swift @@ -100,9 +100,11 @@ tuplify(true) { cond in globalFuncAvailableOn52() // expected-error{{'globalFuncAvailableOn52()' is only available in macOS 52 or newer}} // expected-note@-1{{add 'if #available' version check}} } else if true { - globalFuncAvailableOn52() + globalFuncAvailableOn52() // expected-error{{'globalFuncAvailableOn52()' is only available in macOS 52 or newer}} + // expected-note@-1{{add 'if #available' version check}} } else if false { - globalFuncAvailableOn52() + globalFuncAvailableOn52() // expected-error{{'globalFuncAvailableOn52()' is only available in macOS 52 or newer}} + // expected-note@-1{{add 'if #available' version check}} } } } @@ -162,7 +164,8 @@ tuplifyWithAvailabilityErasure(true) { cond in if cond, #unavailable(OSX 52) { cond } else { - globalFuncAvailableOn52() + globalFuncAvailableOn52() // expected-error{{'globalFuncAvailableOn52()' is only available in macOS 52 or newer}} + // expected-note@-1{{add 'if #available' version check}} } // https://github.com/apple/swift/issues/63764 diff --git a/test/attr/attr_availability_unavailable_query.swift b/test/attr/attr_availability_unavailable_query.swift index 763e1cd2b17..d6205680680 100644 --- a/test/attr/attr_availability_unavailable_query.swift +++ b/test/attr/attr_availability_unavailable_query.swift @@ -59,3 +59,39 @@ func testUnavailableExpandAllElsePaths() { foo() } } + +// Verify that secondary (non-availability) conditions prevent the else branch +// from being refined. The else branch can fire because the secondary condition +// failed, in which case the platform may still be unavailable. +// expected-note@+1 *{{add '@available' attribute to enclosing global function}} +func testUnavailableWithSecondaryConditions() { + let x: Int? = 0 + + // With a secondary condition, the else branch is not refined because it + // could be reached when the secondary condition fails (not because the + // platform became available). + if #unavailable(macOS 998.0), let x { + _ = x + } else { + foo() // expected-error{{'foo()' is only available in macOS 998.0 or newer}} + // expected-note@-1 {{add 'if #available' version check}} + } + + // Same with a boolean secondary condition. + if #unavailable(macOS 998.0), x != nil { + } else { + foo() // expected-error{{'foo()' is only available in macOS 998.0 or newer}} + // expected-note@-1 {{add 'if #available' version check}} + } + + // Else-if chains are also not refined. + if #unavailable(macOS 998.0), let x { + _ = x + } else if x == nil { + foo() // expected-error{{'foo()' is only available in macOS 998.0 or newer}} + // expected-note@-1 {{add 'if #available' version check}} + } else { + foo() // expected-error{{'foo()' is only available in macOS 998.0 or newer}} + // expected-note@-1 {{add 'if #available' version check}} + } +} From ceb302c7aa168324dc1fb2d2be460b9743e344d7 Mon Sep 17 00:00:00 2001 From: Kavon Farvardin Date: Thu, 14 May 2026 17:07:31 -0700 Subject: [PATCH 22/25] NFC: further constexpr-ize InvertibleProtocolSet I'd like `InvertibleProtocolSet::allKnown()` to be evaluated at compile-time. --- include/swift/ABI/InvertibleProtocols.h | 38 ++++++++++++------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/include/swift/ABI/InvertibleProtocols.h b/include/swift/ABI/InvertibleProtocols.h index 895d199f41a..48af490dc09 100644 --- a/include/swift/ABI/InvertibleProtocols.h +++ b/include/swift/ABI/InvertibleProtocols.h @@ -40,7 +40,7 @@ class InvertibleProtocolSet { StorageType bits; /// Retrieve the mask for this bit. - static StorageType getMask(InvertibleProtocolKind kind) { + static constexpr StorageType getMask(InvertibleProtocolKind kind) { return 1 << static_cast(kind); } @@ -48,7 +48,7 @@ public: explicit constexpr InvertibleProtocolSet(StorageType bits) : bits(bits) {} constexpr InvertibleProtocolSet() : bits(0) {} - InvertibleProtocolSet( + constexpr InvertibleProtocolSet( std::initializer_list elements ) : bits(0) { for (auto element : elements) @@ -56,51 +56,51 @@ public: } /// Retrieve the raw bits that describe this set. - StorageType rawBits() const { return bits; } + constexpr StorageType rawBits() const { return bits; } /// Whether the set contains no protocols. - bool empty() const { return bits == 0; } + constexpr bool empty() const { return bits == 0; } /// Check whether the set contains the specific invertible protocol. - bool contains(InvertibleProtocolKind kind) const { + constexpr bool contains(InvertibleProtocolKind kind) const { return bits & getMask(kind); } /// Insert the invertible protocol into the set. - void insert(InvertibleProtocolKind kind) { + constexpr void insert(InvertibleProtocolKind kind) { bits = bits | getMask(kind); } /// Insert all of the invertible protocols from the other set into this /// one. - void insertAll(InvertibleProtocolSet other) { + constexpr void insertAll(InvertibleProtocolSet other) { bits |= other.bits; } /// Remove all of the invertible protocols from this one not present /// in the other set. - void intersect(InvertibleProtocolSet other) { + constexpr void intersect(InvertibleProtocolSet other) { bits &= other.bits; } /// Remove the given invertible protocol from the set. - void remove(InvertibleProtocolKind kind) { + constexpr void remove(InvertibleProtocolKind kind) { uint16_t mask = getMask(kind); bits = bits & ~mask; } /// Clear out all of the protocols from the set. - void clear() { bits = 0; } + constexpr void clear() { bits = 0; } #define INVERTIBLE_PROTOCOL(Name, Bit) \ - bool contains##Name() const { \ + constexpr bool contains##Name() const { \ return contains(InvertibleProtocolKind::Name); \ } #include "swift/ABI/InvertibleProtocols.def" /// Produce a invertible protocol set containing all known invertible /// protocols. - static InvertibleProtocolSet allKnown() { + static constexpr InvertibleProtocolSet allKnown() { InvertibleProtocolSet result; #define INVERTIBLE_PROTOCOL(Name, Bit) \ result.insert(InvertibleProtocolKind::Name); @@ -114,7 +114,7 @@ public: /// or mangled names generated by a newer compiler that has introduced /// another kind of invertible protocol. The Swift runtime will need to /// step lightly around protocol sets with unknown protocols. - bool hasUnknownProtocols() const { + constexpr bool hasUnknownProtocols() const { return !(*this - allKnown()).empty(); } @@ -198,32 +198,32 @@ public: iterator begin() const { return iterator(rawBits()); } iterator end() const { return iterator(0); } - friend bool operator==( + friend constexpr bool operator==( InvertibleProtocolSet lhs, InvertibleProtocolSet rhs) { return lhs.bits == rhs.bits; } - friend bool operator!=( + friend constexpr bool operator!=( InvertibleProtocolSet lhs, InvertibleProtocolSet rhs) { return lhs.bits != rhs.bits; } - friend InvertibleProtocolSet operator-( + friend constexpr InvertibleProtocolSet operator-( InvertibleProtocolSet lhs, InvertibleProtocolSet rhs) { return InvertibleProtocolSet(lhs.bits & ~rhs.bits); } - InvertibleProtocolSet &operator-=(InvertibleProtocolSet rhs) { + constexpr InvertibleProtocolSet &operator-=(InvertibleProtocolSet rhs) { bits = bits & ~rhs.bits; return *this; } - friend InvertibleProtocolSet operator|( + friend constexpr InvertibleProtocolSet operator|( InvertibleProtocolSet lhs, InvertibleProtocolSet rhs) { return InvertibleProtocolSet(lhs.bits | rhs.bits); } - InvertibleProtocolSet &operator|=(InvertibleProtocolSet rhs) { + constexpr InvertibleProtocolSet &operator|=(InvertibleProtocolSet rhs) { bits |= rhs.bits; return *this; } From 19119ad88a7df65fe0acd7c97f6f31fc167c743a Mon Sep 17 00:00:00 2001 From: Kavon Farvardin Date: Thu, 14 May 2026 17:35:08 -0700 Subject: [PATCH 23/25] introduce @_preInverseGenerics(except:) @_preInverseGenerics(except: ) is an extension of the existing @_preInverseGenerics attribute that provides selective control over which inverse requirements are mangled into a declaration's symbol name. While the bare @_preInverseGenerics strips all inverse constraints (~Copyable and ~Escapable) from mangling, the 'except:' form allows specific inverses to be retained. This is needed when a type like Span already had ~Copyable mangled into its ABI-stable symbols and now needs to retroactively adopt ~Escapable without changing those existing symbols. You can now express that with `@_preInverseGenerics(except: ~Copyable)` to strip-out every inverse except ~Copyable to preserve the pre-existing ~Copyable-containing symbols. It requires the new experimental feature `PreInverseGenericsExcept`. rdar://176395527 --- include/swift/AST/ASTBridging.h | 6 ++ include/swift/AST/ASTMangler.h | 9 +- include/swift/AST/Attr.h | 51 +++++++++++ include/swift/AST/DeclAttr.def | 2 +- include/swift/AST/DiagnosticsParse.def | 3 + include/swift/AST/DiagnosticsSema.def | 10 +++ include/swift/AST/ExistentialLayout.h | 9 +- include/swift/AST/TypeCheckRequests.h | 22 +++++ include/swift/AST/TypeCheckerTypeIDZone.def | 3 + include/swift/Basic/Features.def | 3 + lib/AST/ASTDumper.cpp | 7 +- lib/AST/ASTMangler.cpp | 83 ++++++++++-------- lib/AST/Attr.cpp | 53 ++++++++++++ lib/AST/Bridging/DeclAttributeBridging.cpp | 7 ++ lib/AST/FeatureSet.cpp | 15 ++++ lib/AST/Type.cpp | 10 +-- lib/AST/TypeCheckRequests.cpp | 19 +++++ lib/ASTGen/Sources/ASTGen/DeclAttrs.swift | 20 ++++- lib/IRGen/IRGenMangler.cpp | 10 +-- lib/IRGen/IRGenMangler.h | 16 ++-- lib/Parse/ParseDecl.cpp | 33 ++++++++ lib/Sema/TypeCheckAttr.cpp | 78 ++++++++++++++++- lib/Serialization/Deserialization.cpp | 20 +++++ lib/Serialization/ModuleFormat.h | 8 +- lib/Serialization/Serialization.cpp | 14 ++++ .../pre_inverse_generics_except.swift | 43 ++++++++++ test/SILGen/mangling_inverse_generics.swift | 84 +++++++++++++++++++ test/Sema/preInverseGenerics.swift | 40 +++++++++ test/attr/attr_preInverseGenerics.swift | 16 ++++ 29 files changed, 626 insertions(+), 68 deletions(-) create mode 100644 test/ModuleInterface/pre_inverse_generics_except.swift create mode 100644 test/Sema/preInverseGenerics.swift create mode 100644 test/attr/attr_preInverseGenerics.swift diff --git a/include/swift/AST/ASTBridging.h b/include/swift/AST/ASTBridging.h index 90c6ae857e6..e7226ba34d3 100644 --- a/include/swift/AST/ASTBridging.h +++ b/include/swift/AST/ASTBridging.h @@ -911,6 +911,12 @@ BridgedAllowFeatureSuppressionAttr_createParsed(BridgedASTContext cContext, bool inverted, BridgedArrayRef cFeatures); +SWIFT_NAME("BridgedPreInverseGenericsAttr.createParsed(_:atLoc:range:)") +BridgedPreInverseGenericsAttr +BridgedPreInverseGenericsAttr_createParsed(BridgedASTContext cContext, + swift::SourceLoc atLoc, + swift::SourceRange range); + SWIFT_NAME( "BridgedBackDeployedAttr.createParsed(_:atLoc:range:platform:version:)") BridgedBackDeployedAttr BridgedBackDeployedAttr_createParsed( diff --git a/include/swift/AST/ASTMangler.h b/include/swift/AST/ASTMangler.h index 90620d66f5e..eb222e0378a 100644 --- a/include/swift/AST/ASTMangler.h +++ b/include/swift/AST/ASTMangler.h @@ -16,6 +16,7 @@ #include "swift/AST/ASTContext.h" #include "swift/AST/Decl.h" #include "swift/AST/FreestandingMacroExpansion.h" +#include "swift/ABI/InvertibleProtocols.h" #include "swift/AST/SILThunkKind.h" #include "swift/AST/Types.h" #include "swift/Basic/Mangler.h" @@ -81,8 +82,8 @@ protected: /// If enabled, marker protocols can be encoded in the mangled name. bool AllowMarkerProtocols = true; - /// If enabled, inverses will not be mangled into generic signatures. - bool AllowInverses = true; + /// The set of inverses allowed to be mangled into generic signatures. + InvertibleProtocolSet AllowedInverses = InvertibleProtocolSet::allKnown(); /// If enabled, @isolated(any) can be encoded in the mangled name. /// Suppressing type attributes this way is generally questionable --- @@ -560,8 +561,8 @@ protected: BaseEntitySignature(const Decl *decl); }; - static bool inversesAllowed(const Decl *decl); - static bool inversesAllowedIn(const DeclContext *ctx); + static InvertibleProtocolSet inversesAllowed(const Decl *decl); + static InvertibleProtocolSet inversesAllowedIn(const DeclContext *ctx); void appendContextOf(const ValueDecl *decl, BaseEntitySignature &base); void appendContextualInverses(const GenericTypeDecl *contextDecl, diff --git a/include/swift/AST/Attr.h b/include/swift/AST/Attr.h index ec5531ef24c..e44fa8750f5 100644 --- a/include/swift/AST/Attr.h +++ b/include/swift/AST/Attr.h @@ -16,6 +16,7 @@ #ifndef SWIFT_ATTR_H #define SWIFT_ATTR_H +#include "swift/ABI/InvertibleProtocols.h" #include "swift/AST/ASTAllocated.h" #include "swift/AST/AttrKind.h" #include "swift/AST/AutoDiff.h" @@ -3616,6 +3617,56 @@ public: Decl *attachedTo) const; }; +/// The @_preInverseGenerics attribute +class PreInverseGenericsAttr : public DeclAttribute { + /// The potentially unresolved 'ExceptType'. + TypeRepr *ExceptTypeRepr; + + /// A ProtocolCompositionType whose contained inverses are those that should + /// be KEPT in the mangling of the decl to which this attribute is attached. + /// + /// A bare `@_preInverseGenerics` is semantically equivalent to + /// @_preInverseGenerics(except: Any) because `Any` contains no inverses, thus + /// such an attribute will resolve `ExceptType` to `Any`. + Type ExceptType; + + friend class ResolvePreInverseGenericsRequest; + +public: + PreInverseGenericsAttr(SourceLoc AtLoc, SourceRange Range, + TypeRepr *exceptRepr = nullptr, + Type exceptType = Type()); + + /// If the 'except:' argument was present, this may still be null in the case + /// of a deserialized attribute. The `hasExcept` query is more reliable. + TypeRepr *getExceptTypeRepr() const { return ExceptTypeRepr; } + + /// \returns a ProtocolCompositionType whose inverses represent those that + /// must be kept when mangling. + Type getResolvedExceptType(const Decl *attachedTo) const; + + /// True if this attribute was written with an `except:` argument. + bool hasExcept(const Decl *attachedTo) const { + return getExceptTypeRepr() != nullptr || + !getAllowedInverses(attachedTo).empty(); + } + + /// \returns the set of inverses allowed to be mangled. + InvertibleProtocolSet getAllowedInverses(const Decl *attachedTo) const; + + static bool classof(const DeclAttribute *DA) { + return DA->getKind() == DeclAttrKind::PreInverseGenerics; + } + + PreInverseGenericsAttr *clone(ASTContext &ctx) const { + return new (ctx) + PreInverseGenericsAttr(AtLoc, Range, ExceptTypeRepr, ExceptType); + } + + bool isEquivalent(const PreInverseGenericsAttr *other, + Decl *attachedTo) const; +}; + /// Defines the @abi attribute. class ABIAttr : public DeclAttribute { friend class DeclAttribute; diff --git a/include/swift/AST/DeclAttr.def b/include/swift/AST/DeclAttr.def index de1600a6a80..36338ff81d4 100644 --- a/include/swift/AST/DeclAttr.def +++ b/include/swift/AST/DeclAttr.def @@ -832,7 +832,7 @@ DECL_ATTR(_allowFeatureSuppression, AllowFeatureSuppression, 157) DECL_ATTR_ALIAS(_disallowFeatureSuppression, AllowFeatureSuppression) -SIMPLE_DECL_ATTR(_preInverseGenerics, PreInverseGenerics, +DECL_ATTR(_preInverseGenerics, PreInverseGenerics, OnAbstractFunction | OnSubscript | OnVar | OnExtension, UserInaccessible | ABIBreakingToAdd | ABIBreakingToRemove | APIStableToAdd | APIStableToRemove | UnconstrainedInABIAttr, 158) diff --git a/include/swift/AST/DiagnosticsParse.def b/include/swift/AST/DiagnosticsParse.def index 8751e58020b..b1c68078402 100644 --- a/include/swift/AST/DiagnosticsParse.def +++ b/include/swift/AST/DiagnosticsParse.def @@ -1902,6 +1902,9 @@ ERROR(attr_rawlayout_expected_integer_alignment,none, ERROR(attr_rawlayout_expected_params,none, "expected %1 argument after %0 argument in '@_rawLayout'", (StringRef, StringRef)) +ERROR(attr_pre_inverse_generics_expected_except,none, + "expected 'except:' argument in '@_preInverseGenerics'", ()) + ERROR(attr_extern_expected_label,none, "expected %0 argument to '@_extern'", (StringRef)) diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index a2b9819be0c..acdcf02b8ea 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -4462,6 +4462,16 @@ ERROR(attr_rawlayout_invalid_count_type,none, "'@_rawLayout' count must either be integer literal or a generic " "argument", ()) +ERROR(attr_pre_inverse_generics_invalid_except,none, + "'except' argument to '@_preInverseGenerics' must consist only of " + "inverse constraints such as '~Copyable' or '~Escapable'", ()) +WARNING(attr_pre_inverse_generics_except_all,none, + "'@_preInverseGenerics' that excepts all inverse constraints is " + "equivalent to not using the attribute", ()) +WARNING(attr_pre_inverse_generics_on_extension,none, + "'@_preInverseGenerics' has no effect on an extension; place it on " + "individual members instead", ()) + // lazy ERROR(lazy_not_on_let,none, "'lazy' cannot be used on a let", ()) diff --git a/include/swift/AST/ExistentialLayout.h b/include/swift/AST/ExistentialLayout.h index ad45c33a5e3..11f56536b6d 100644 --- a/include/swift/AST/ExistentialLayout.h +++ b/include/swift/AST/ExistentialLayout.h @@ -126,10 +126,11 @@ struct ExistentialLayout { /// is relevant for the mangler to mangle as a symbolic link where possible /// and for IRGen directly emitting some existentials. /// - /// If 'allowInverses' is false, then regardless of if this existential layout - /// has inverse requirements those will not influence the need for having a - /// shape. - bool needsExtendedShape(bool allowInverses = true) const; + /// If 'allowedInverses' is empty, then regardless of if this existential + /// layout has inverse requirements those will not influence the need for + /// having a shape. + bool needsExtendedShape(InvertibleProtocolSet allowedInverses = + InvertibleProtocolSet::allKnown()) const; private: SmallVector protocols; diff --git a/include/swift/AST/TypeCheckRequests.h b/include/swift/AST/TypeCheckRequests.h index ae1c16c632f..e2b385d8921 100644 --- a/include/swift/AST/TypeCheckRequests.h +++ b/include/swift/AST/TypeCheckRequests.h @@ -16,6 +16,7 @@ #ifndef SWIFT_TYPE_CHECK_REQUESTS_H #define SWIFT_TYPE_CHECK_REQUESTS_H +#include "swift/ABI/InvertibleProtocols.h" #include "swift/AST/ASTNode.h" #include "swift/AST/ASTTypeIDs.h" #include "swift/AST/ActorIsolation.h" @@ -66,6 +67,7 @@ struct PropertyWrapperMutability; class RequirementRepr; class ReturnStmt; class AbstractSpecializeAttr; +class PreInverseGenericsAttr; class TrailingWhereClause; class TypeAliasDecl; class TypeLoc; @@ -3847,6 +3849,26 @@ public: void cacheResult(Type value) const; }; +class ResolvePreInverseGenericsRequest + : public SimpleRequest { +public: + using SimpleRequest::SimpleRequest; + +private: + friend SimpleRequest; + + Type evaluate(Evaluator &evaluator, + Decl *decl, + PreInverseGenericsAttr *attr) const; + +public: + bool isCached() const { return true; } + std::optional getCachedResult() const; + void cacheResult(Type value) const; +}; + class ResolveRawLayoutTypeRequest : public SimpleRequestgetExceptTypeRepr()) + printRec(tyR, Label::optional("except_repr")); + printFoot(); + } void visitRawLayoutAttr(RawLayoutAttr *Attr, Label label) { printCommon(Attr, "raw_layout_attr", label); if (auto *tyR = Attr->getScalarLikeType()) { diff --git a/lib/AST/ASTMangler.cpp b/lib/AST/ASTMangler.cpp index 47591eadd97..cb07e36139f 100644 --- a/lib/AST/ASTMangler.cpp +++ b/lib/AST/ASTMangler.cpp @@ -104,18 +104,21 @@ bool ASTMangler::tryMangleSubstitution(const Decl *decl) { return Mangler::tryMangleSubstitution(decl); } -bool ASTMangler::inversesAllowed(const Decl *decl) { +InvertibleProtocolSet ASTMangler::inversesAllowed(const Decl *decl) { if (!decl) - return true; + return InvertibleProtocolSet::allKnown(); if (auto accessor = dyn_cast(decl)) if (auto *storage = accessor->getStorage()) decl = storage; - return !decl->getAttrs().hasAttribute(); + if (auto *attr = decl->getAttrs().getAttribute()) + return attr->getAllowedInverses(decl); + + return InvertibleProtocolSet::allKnown(); } -bool ASTMangler::inversesAllowedIn(const DeclContext *ctx) { +InvertibleProtocolSet ASTMangler::inversesAllowedIn(const DeclContext *ctx) { assert(ctx); return inversesAllowed(ctx->getInnermostDeclarationDeclContext()); } @@ -159,7 +162,7 @@ static StringRef getCodeForAccessorKind(AccessorKind kind) { std::string ASTMangler::mangleClosureEntity(const AbstractClosureExpr *closure, SymbolKind SKind) { - llvm::SaveAndRestore X(AllowInverses, inversesAllowedIn(closure)); + llvm::SaveAndRestore X(AllowedInverses, inversesAllowedIn(closure)); beginMangling(); appendClosureEntity(closure); appendSymbolKind(SKind); @@ -167,7 +170,7 @@ std::string ASTMangler::mangleClosureEntity(const AbstractClosureExpr *closure, } std::string ASTMangler::mangleEntity(const ValueDecl *decl, SymbolKind SKind) { - llvm::SaveAndRestore X(AllowInverses, inversesAllowed(decl)); + llvm::SaveAndRestore X(AllowedInverses, inversesAllowed(decl)); beginMangling(); appendEntity(decl); appendSymbolKind(SKind); @@ -177,7 +180,7 @@ std::string ASTMangler::mangleEntity(const ValueDecl *decl, SymbolKind SKind) { std::string ASTMangler::mangleDestructorEntity(const DestructorDecl *decl, DestructorKind kind, SymbolKind SKind) { - llvm::SaveAndRestore X(AllowInverses, inversesAllowed(decl)); + llvm::SaveAndRestore X(AllowedInverses, inversesAllowed(decl)); beginMangling(); appendDestructorEntity(decl, kind); appendSymbolKind(SKind); @@ -187,7 +190,7 @@ std::string ASTMangler::mangleDestructorEntity(const DestructorDecl *decl, std::string ASTMangler::mangleConstructorEntity(const ConstructorDecl *ctor, bool isAllocating, SymbolKind SKind) { - llvm::SaveAndRestore X(AllowInverses, inversesAllowed(ctor)); + llvm::SaveAndRestore X(AllowedInverses, inversesAllowed(ctor)); beginMangling(); appendConstructorEntity(ctor, isAllocating); appendSymbolKind(SKind); @@ -197,7 +200,7 @@ std::string ASTMangler::mangleConstructorEntity(const ConstructorDecl *ctor, std::string ASTMangler::mangleIVarInitDestroyEntity(const ClassDecl *decl, bool isDestroyer, SymbolKind SKind) { - llvm::SaveAndRestore X(AllowInverses, inversesAllowed(decl)); + llvm::SaveAndRestore X(AllowedInverses, inversesAllowed(decl)); beginMangling(); BaseEntitySignature base(decl); appendContext(decl, base, decl->getAlternateModuleName()); @@ -210,7 +213,7 @@ std::string ASTMangler::mangleAccessorEntity(AccessorKind kind, const AbstractStorageDecl *decl, bool isStatic, SymbolKind SKind) { - llvm::SaveAndRestore X(AllowInverses, inversesAllowed(decl)); + llvm::SaveAndRestore X(AllowedInverses, inversesAllowed(decl)); beginMangling(); appendAccessorEntity(getCodeForAccessorKind(kind), decl, isStatic); appendSymbolKind(SKind); @@ -220,7 +223,7 @@ std::string ASTMangler::mangleAccessorEntity(AccessorKind kind, std::string ASTMangler::mangleDefaultArgumentEntity(const DeclContext *func, unsigned index, SymbolKind SKind) { - llvm::SaveAndRestore X(AllowInverses, inversesAllowedIn(func)); + llvm::SaveAndRestore X(AllowedInverses, inversesAllowedIn(func)); beginMangling(); appendDefaultArgumentEntity(func, index); appendSymbolKind(SKind); @@ -229,7 +232,7 @@ std::string ASTMangler::mangleDefaultArgumentEntity(const DeclContext *func, std::string ASTMangler::mangleInitializerEntity(const VarDecl *var, SymbolKind SKind) { - llvm::SaveAndRestore X(AllowInverses, inversesAllowed(var)); + llvm::SaveAndRestore X(AllowedInverses, inversesAllowed(var)); beginMangling(); appendInitializerEntity(var); appendSymbolKind(SKind); @@ -238,7 +241,7 @@ std::string ASTMangler::mangleInitializerEntity(const VarDecl *var, std::string ASTMangler::mangleBackingInitializerEntity(const VarDecl *var, SymbolKind SKind) { - llvm::SaveAndRestore X(AllowInverses, inversesAllowed(var)); + llvm::SaveAndRestore X(AllowedInverses, inversesAllowed(var)); beginMangling(); appendBackingInitializerEntity(var); appendSymbolKind(SKind); @@ -248,7 +251,7 @@ std::string ASTMangler::mangleBackingInitializerEntity(const VarDecl *var, std::string ASTMangler::manglePropertyWrappedFieldInitAccessorEntity(const VarDecl *var, SymbolKind SKind) { - llvm::SaveAndRestore X(AllowInverses, inversesAllowed(var)); + llvm::SaveAndRestore X(AllowedInverses, inversesAllowed(var)); beginMangling(); appendPropertyWrappedFieldInitAccessorEntity(var); appendSymbolKind(SKind); @@ -257,7 +260,7 @@ ASTMangler::manglePropertyWrappedFieldInitAccessorEntity(const VarDecl *var, std::string ASTMangler::mangleInitFromProjectedValueEntity(const VarDecl *var, SymbolKind SKind) { - llvm::SaveAndRestore X(AllowInverses, inversesAllowed(var)); + llvm::SaveAndRestore X(AllowedInverses, inversesAllowed(var)); beginMangling(); appendInitFromProjectedValueEntity(var); appendSymbolKind(SKind); @@ -295,7 +298,7 @@ std::string ASTMangler::mangleConstructorVTableThunk( } std::string ASTMangler::mangleWitnessTable(const ProtocolConformance *C) { - llvm::SaveAndRestore X(AllowInverses, + llvm::SaveAndRestore X(AllowedInverses, inversesAllowedIn(C->getDeclContext())); beginMangling(); @@ -1634,7 +1637,7 @@ void ASTMangler::appendType(Type type, GenericSignature sig, // ExtendedExistentialTypeShapes consider existential metatypes to // be part of the existential, so if we're symbolically referencing // shapes, we need to handle that at this level. - if (EMT->getExistentialLayout().needsExtendedShape(AllowInverses)) { + if (EMT->getExistentialLayout().needsExtendedShape(AllowedInverses)) { auto referent = SymbolicReferent::forExtendedExistentialTypeShape(EMT); if (canSymbolicReference(referent)) { appendSymbolicExtendedExistentialType(referent, EMT, sig, forDecl); @@ -1643,7 +1646,7 @@ void ASTMangler::appendType(Type type, GenericSignature sig, } if (EMT->getInstanceType()->isExistentialType() && - EMT->getExistentialLayout().needsExtendedShape(AllowInverses)) + EMT->getExistentialLayout().needsExtendedShape(AllowedInverses)) appendConstrainedExistential(EMT->getInstanceType(), sig, forDecl); else appendType(EMT->getInstanceType(), sig, forDecl); @@ -1699,7 +1702,7 @@ void ASTMangler::appendType(Type type, GenericSignature sig, return appendType(strippedTy, sig, forDecl); } - if (PCT->getExistentialLayout().needsExtendedShape(AllowInverses)) + if (PCT->getExistentialLayout().needsExtendedShape(AllowedInverses)) return appendConstrainedExistential(PCT, sig, forDecl); // We mangle ProtocolType and ProtocolCompositionType using the @@ -1714,7 +1717,7 @@ void ASTMangler::appendType(Type type, GenericSignature sig, case TypeKind::Existential: { auto *ET = cast(tybase); - if (ET->getExistentialLayout().needsExtendedShape(AllowInverses)) { + if (ET->getExistentialLayout().needsExtendedShape(AllowedInverses)) { auto referent = SymbolicReferent::forExtendedExistentialTypeShape(ET); if (canSymbolicReference(referent)) { appendSymbolicExtendedExistentialType(referent, ET, sig, forDecl); @@ -2964,14 +2967,23 @@ void ASTMangler::appendSymbolicReference(SymbolicReferent referent) { static void reconcileInverses( SmallVector &inverses, GenericSignature sig, + InvertibleProtocolSet allowedInverses, std::optional inversesAlreadyMangledDepth, std::optional suppressedInnermostDepth) { + if (inverses.empty()) + return; + CanGenericSignature baseSig; if (sig) baseSig = sig.getCanonicalSignature(); - if (baseSig || inversesAlreadyMangledDepth || suppressedInnermostDepth) - llvm::erase_if(inverses, [&](InverseRequirement const& inv) -> bool { + if (baseSig || inversesAlreadyMangledDepth || suppressedInnermostDepth || + allowedInverses != InvertibleProtocolSet::allKnown()) { + llvm::erase_if(inverses, [&](InverseRequirement const &inv) -> bool { + // Drop inverses that aren't to be mangled due to @_preInverseGenerics. + if (!allowedInverses.contains(inv.getKind())) + return true; + // Drop inverses that aren't applicable in the nested / child signature, // because of an added requirement. if (baseSig && baseSig->requiresProtocol(inv.subject, inv.protocol)) @@ -2991,6 +3003,7 @@ static void reconcileInverses( return false; }); + } // Sort inverse requirements for stability. llvm::array_pod_sort( @@ -3723,14 +3736,10 @@ void ASTMangler::gatherGenericSignatureParts(GenericSignature sig, auto &inverseReqs = parts.inverses; canSig->getRequirementsWithInverses(reqs, inverseReqs); - // Process inverses relative to the base entity's signature. - if (AllowInverses) { - // Simplify and canonicalize inverses. - reconcileInverses(inverseReqs, base.getSignature(), base.getDepth(), - base.getSuppressedInnermostInversesDepth()); - } else { - inverseReqs.clear(); - } + // Simplify and canonicalize the inverses. + reconcileInverses(inverseReqs, base.getSignature(), AllowedInverses, + base.getDepth(), + base.getSuppressedInnermostInversesDepth()); base.setDepth(canSig->getMaxDepth()); unsigned &initialParamDepth = parts.initialParamDepth; @@ -4147,14 +4156,14 @@ void ASTMangler::appendDefaultArgumentEntity(const DeclContext *func, } void ASTMangler::appendInitializerEntity(const VarDecl *var) { - llvm::SaveAndRestore X(AllowInverses, inversesAllowed(var)); + llvm::SaveAndRestore X(AllowedInverses, inversesAllowed(var)); BaseEntitySignature base(var); appendEntity(var, base, "vp", var->isStatic()); appendOperator("fi"); } void ASTMangler::appendBackingInitializerEntity(const VarDecl *var) { - llvm::SaveAndRestore X(AllowInverses, inversesAllowed(var)); + llvm::SaveAndRestore X(AllowedInverses, inversesAllowed(var)); BaseEntitySignature base(var); appendEntity(var, base, "vp", var->isStatic()); appendOperator("fP"); @@ -4162,14 +4171,14 @@ void ASTMangler::appendBackingInitializerEntity(const VarDecl *var) { void ASTMangler::appendPropertyWrappedFieldInitAccessorEntity( const VarDecl *var) { - llvm::SaveAndRestore X(AllowInverses, inversesAllowed(var)); + llvm::SaveAndRestore X(AllowedInverses, inversesAllowed(var)); BaseEntitySignature base(var); appendEntity(var, base, "vp", var->isStatic()); appendOperator("fF"); } void ASTMangler::appendInitFromProjectedValueEntity(const VarDecl *var) { - llvm::SaveAndRestore X(AllowInverses, inversesAllowed(var)); + llvm::SaveAndRestore X(AllowedInverses, inversesAllowed(var)); BaseEntitySignature base(var); appendEntity(var, base, "vp", var->isStatic()); appendOperator("fW"); @@ -5351,6 +5360,9 @@ void ASTMangler::extractExistentialInverseRequirements( return; for (auto ip : PCT->getInverses()) { + if (!AllowedInverses.contains(ip)) + continue; + auto *proto = Context.getProtocol(getKnownProtocolKind(ip)); assert(proto); ASSERT(!getABIDecl(proto) && "can't use @abi on inverse protocols"); @@ -5370,8 +5382,7 @@ void ASTMangler::gatherExistentialRequirements( for (auto memberTy : compositionTy->getMembers()) gatherExistentialRequirements(reqs, inverses, memberTy); - if (AllowInverses) - extractExistentialInverseRequirements(inverses, compositionTy); + extractExistentialInverseRequirements(inverses, compositionTy); } } diff --git a/lib/AST/Attr.cpp b/lib/AST/Attr.cpp index 94f23246ba6..81b7952ef0e 100644 --- a/lib/AST/Attr.cpp +++ b/lib/AST/Attr.cpp @@ -1710,6 +1710,20 @@ bool DeclAttribute::printImpl(ASTPrinter &Printer, const PrintOptions &Options, break; } + case DeclAttrKind::PreInverseGenerics: { + auto *attr = cast(this); + Printer.printAttrName("@_preInverseGenerics"); + Type exceptTy = attr->getResolvedExceptType(D); + // Avoid printing `@_preInverseGenerics(except: Any)` despite that being the + // meaning of the no-arg version. It's rejected as it can confuse people. + if (exceptTy->getCanonicalType() != D->getASTContext().TheAnyType) { + Printer << "(except: "; + exceptTy.print(Printer, Options); + Printer << ")"; + } + break; + } + case DeclAttrKind::RawLayout: { auto *attr = cast(this); Printer.printAttrName("@_rawLayout"); @@ -2052,6 +2066,8 @@ StringRef DeclAttribute::getAttrName() const { case MacroSyntax::Attached: return "attached"; } + case DeclAttrKind::PreInverseGenerics: + return "_preInverseGenerics"; case DeclAttrKind::RawLayout: return "_rawLayout"; case DeclAttrKind::Extern: @@ -2297,6 +2313,43 @@ bool TypeEraserAttr::isEquivalent(const TypeEraserAttr *other, return thisType->getCanonicalType() == otherType->getCanonicalType(); } +PreInverseGenericsAttr::PreInverseGenericsAttr(SourceLoc AtLoc, + SourceRange Range, + TypeRepr *exceptRepr, + Type exceptType) + : DeclAttribute(DeclAttrKind::PreInverseGenerics, AtLoc, Range, + /*Implicit=*/false), + ExceptTypeRepr(exceptRepr), ExceptType(exceptType) { + assert(!exceptType || exceptType->is()); +} + +bool PreInverseGenericsAttr::isEquivalent(const PreInverseGenericsAttr *other, + Decl *attachedTo) const { + return eqTypes(getResolvedExceptType(attachedTo), + other->getResolvedExceptType(attachedTo)); +} + +Type PreInverseGenericsAttr::getResolvedExceptType( + const Decl *attachedTo) const { + if (!ExceptType) { + auto &ctx = attachedTo->getASTContext(); + evaluateOrDefault(ctx.evaluator, + ResolvePreInverseGenericsRequest{ + const_cast(attachedTo), + const_cast(this)}, + ctx.TheAnyType); + } + assert(ExceptType && "resolution didn't save the except type?"); + return ExceptType; +} + +InvertibleProtocolSet +PreInverseGenericsAttr::getAllowedInverses(const Decl *attachedTo) const { + return getResolvedExceptType(attachedTo) + ->castTo() + ->getInverses(); +} + Type RawLayoutAttr::getResolvedLikeType(StructDecl *sd) const { auto &ctx = sd->getASTContext(); return evaluateOrDefault(ctx.evaluator, diff --git a/lib/AST/Bridging/DeclAttributeBridging.cpp b/lib/AST/Bridging/DeclAttributeBridging.cpp index d7092e42d58..662b32257b8 100644 --- a/lib/AST/Bridging/DeclAttributeBridging.cpp +++ b/lib/AST/Bridging/DeclAttributeBridging.cpp @@ -184,6 +184,13 @@ BridgedAllowFeatureSuppressionAttr_createParsed(BridgedASTContext cContext, features); } +BridgedPreInverseGenericsAttr +BridgedPreInverseGenericsAttr_createParsed(BridgedASTContext cContext, + SourceLoc atLoc, + SourceRange range) { + return new (cContext.unbridged()) PreInverseGenericsAttr(atLoc, range); +} + BridgedBackDeployedAttr BridgedBackDeployedAttr_createParsed( BridgedASTContext cContext, SourceLoc atLoc, SourceRange range, swift::PlatformKind platform, BridgedVersionTuple cVersion) { diff --git a/lib/AST/FeatureSet.cpp b/lib/AST/FeatureSet.cpp index eb446842222..96a14fd0e6c 100644 --- a/lib/AST/FeatureSet.cpp +++ b/lib/AST/FeatureSet.cpp @@ -280,6 +280,21 @@ static bool usesFeatureLifetimes(Decl *decl) { return false; } +static PreInverseGenericsAttr *getPreInverseGenericsExcept(Decl *decl) { + if (auto pbd = dyn_cast(decl)) + for (auto i : range(pbd->getNumPatternEntries())) + if (auto anchorVar = pbd->getAnchoringVarDecl(i)) + return getPreInverseGenericsExcept(anchorVar); + + return decl->getAttrs().getAttribute(); +} + +static bool usesFeaturePreInverseGenericsExcept(Decl *decl) { + if (auto *attr = getPreInverseGenericsExcept(decl)) + return attr->hasExcept(decl); + return false; +} + static bool hasLifetimeDependencies(Type type) { if (auto *aft = type->getAs()) { return aft->hasExplicitLifetimeDependencies(); diff --git a/lib/AST/Type.cpp b/lib/AST/Type.cpp index 4f5f64645d8..9ddde061f62 100644 --- a/lib/AST/Type.cpp +++ b/lib/AST/Type.cpp @@ -440,14 +440,14 @@ Type ExistentialLayout::getSuperclass() const { return Type(); } -bool ExistentialLayout::needsExtendedShape(bool allowInverses) const { +bool ExistentialLayout::needsExtendedShape( + InvertibleProtocolSet allowedInverses) const { if (!getParameterizedProtocols().empty()) return true; - if (allowInverses && hasInverses()) - return true; - - return false; + // Would any inverses in this layout would be considered by the mangler? + allowedInverses.intersect(inverses); + return !allowedInverses.empty(); } bool TypeBase::isObjCExistentialType() { diff --git a/lib/AST/TypeCheckRequests.cpp b/lib/AST/TypeCheckRequests.cpp index fa35a2aa44d..a6a486429e7 100644 --- a/lib/AST/TypeCheckRequests.cpp +++ b/lib/AST/TypeCheckRequests.cpp @@ -1633,6 +1633,25 @@ void ResolveTypeEraserTypeRequest::cacheResult(Type value) const { } } +//----------------------------------------------------------------------------// +// ResolvePreInverseGenericsRequest computation. +//----------------------------------------------------------------------------// + +std::optional ResolvePreInverseGenericsRequest::getCachedResult() const { + auto *attr = std::get<1>(getStorage()); + auto Ty = attr->ExceptType; + if (!Ty) + return std::nullopt; + + return Ty; +} + +void ResolvePreInverseGenericsRequest::cacheResult(Type Ty) const { + auto *attr = std::get<1>(getStorage()); + assert(Ty && Ty->is()); + attr->ExceptType = Ty; +} + //----------------------------------------------------------------------------// // ResolveRawLayoutTypeRequest computation. //----------------------------------------------------------------------------// diff --git a/lib/ASTGen/Sources/ASTGen/DeclAttrs.swift b/lib/ASTGen/Sources/ASTGen/DeclAttrs.swift index 27cddcaf8e6..6ce346dc429 100644 --- a/lib/ASTGen/Sources/ASTGen/DeclAttrs.swift +++ b/lib/ASTGen/Sources/ASTGen/DeclAttrs.swift @@ -176,6 +176,8 @@ extension ASTGenVisitor { return handle(self.generateOptimizeAttr(attribute: node)?.asDeclAttribute) case .OriginallyDefinedIn: return self.generateOriginallyDefinedInAttr(attribute: node).forEach { handle($0.asDeclAttribute) } + case .PreInverseGenerics: + return handle(self.generatePreInverseGenericsAttr(attribute: node)?.asDeclAttribute) case .PrivateImport: return handle(self.generatePrivateImportAttr(attribute: node)?.asDeclAttribute) case .ProjectedValueProperty: @@ -296,7 +298,6 @@ extension ASTGenVisitor { .ObjCNonLazyRealization, .Owned, .Preconcurrency, - .PreInverseGenerics, .PropertyWrapper, .Reparentable, .RequiresStoredPropertyInits, @@ -1732,6 +1733,23 @@ extension ASTGenVisitor { ) } + func generatePreInverseGenericsAttr(attribute node: AttributeSyntax) -> BridgedPreInverseGenericsAttr? { + self.generateWithLabeledExprListArguments(attribute: node) { args in + switch args.first?.label?.rawText { + case "except": + fatalError("ASTGen does not yet support the except: argument") + default: + // TODO: Diagnose. + fatalError("invalid argument for @_preInverseGenerics attribute") + } + return .createParsed( + self.ctx, + atLoc: self.generateSourceLoc(node.atSign), + range: self.generateAttrSourceRange(node) + ) + } + } + func generateRawLayoutAttr(attribute node: AttributeSyntax) -> BridgedRawLayoutAttr? { self.generateWithLabeledExprListArguments(attribute: node) { args in switch args.first?.label?.rawText { diff --git a/lib/IRGen/IRGenMangler.cpp b/lib/IRGen/IRGenMangler.cpp index e4815b850c1..f72726dd748 100644 --- a/lib/IRGen/IRGenMangler.cpp +++ b/lib/IRGen/IRGenMangler.cpp @@ -218,7 +218,7 @@ IRGenMangler::mangleTypeForFlatUniqueTypeRef(CanGenericSignature sig, std::string IRGenMangler::mangleProtocolConformanceDescriptor( const RootProtocolConformance *conformance) { - llvm::SaveAndRestore X(AllowInverses, + llvm::SaveAndRestore X(AllowedInverses, inversesAllowedIn(conformance->getDeclContext())); beginMangling(); @@ -235,7 +235,7 @@ std::string IRGenMangler::mangleProtocolConformanceDescriptor( std::string IRGenMangler::mangleProtocolConformanceDescriptorRecord( const RootProtocolConformance *conformance) { - llvm::SaveAndRestore X(AllowInverses, + llvm::SaveAndRestore X(AllowedInverses, inversesAllowedIn(conformance->getDeclContext())); beginMangling(); @@ -246,7 +246,7 @@ std::string IRGenMangler::mangleProtocolConformanceDescriptorRecord( std::string IRGenMangler::mangleProtocolConformanceInstantiationCache( const RootProtocolConformance *conformance) { - llvm::SaveAndRestore X(AllowInverses, + llvm::SaveAndRestore X(AllowedInverses, inversesAllowedIn(conformance->getDeclContext())); beginMangling(); @@ -556,7 +556,7 @@ IRGenMangler::appendExtendedExistentialTypeShape(CanGenericSignature genSig, // Append the generalization signature. if (genSig) { // Generalization signature never mangles inverses. - llvm::SaveAndRestore X(AllowInverses, false); + llvm::SaveAndRestore X(AllowedInverses, InvertibleProtocolSet()); appendGenericSignature(genSig); } @@ -571,7 +571,7 @@ std::string IRGenMangler::mangleConformanceSymbol(Type type, const ProtocolConformance *Conformance, const char *Op) { - llvm::SaveAndRestore X(AllowInverses, + llvm::SaveAndRestore X(AllowedInverses, inversesAllowedIn(Conformance->getDeclContext())); beginMangling(); diff --git a/lib/IRGen/IRGenMangler.h b/lib/IRGen/IRGenMangler.h index d26ed89f431..7c7ffffc646 100644 --- a/lib/IRGen/IRGenMangler.h +++ b/lib/IRGen/IRGenMangler.h @@ -47,7 +47,7 @@ public: IRGenMangler(ASTContext &Ctx) : ASTMangler(Ctx) { } std::string mangleDispatchThunk(const FuncDecl *func) { - llvm::SaveAndRestore X(AllowInverses, inversesAllowed(func)); + llvm::SaveAndRestore X(AllowedInverses, inversesAllowed(func)); beginMangling(); appendEntity(func); appendOperator("Tj"); @@ -59,7 +59,7 @@ public: std::string mangleDerivativeDispatchThunk( const AbstractFunctionDecl *func, AutoDiffDerivativeFunctionIdentifier *derivativeId) { - llvm::SaveAndRestore X(AllowInverses, inversesAllowed(func)); + llvm::SaveAndRestore X(AllowedInverses, inversesAllowed(func)); beginManglingWithAutoDiffOriginalFunction(func); auto kind = Demangle::getAutoDiffFunctionKind(derivativeId->getKind()); auto *resultIndices = @@ -76,7 +76,7 @@ public: std::string mangleConstructorDispatchThunk(const ConstructorDecl *ctor, bool isAllocating) { - llvm::SaveAndRestore X(AllowInverses, inversesAllowed(ctor)); + llvm::SaveAndRestore X(AllowedInverses, inversesAllowed(ctor)); beginMangling(); appendConstructorEntity(ctor, isAllocating); appendOperator("Tj"); @@ -84,7 +84,7 @@ public: } std::string mangleMethodDescriptor(const FuncDecl *func) { - llvm::SaveAndRestore X(AllowInverses, inversesAllowed(func)); + llvm::SaveAndRestore X(AllowedInverses, inversesAllowed(func)); beginMangling(); appendEntity(func); appendOperator("Tq"); @@ -96,7 +96,7 @@ public: std::string mangleDerivativeMethodDescriptor( const AbstractFunctionDecl *func, AutoDiffDerivativeFunctionIdentifier *derivativeId) { - llvm::SaveAndRestore X(AllowInverses, inversesAllowed(func)); + llvm::SaveAndRestore X(AllowedInverses, inversesAllowed(func)); beginManglingWithAutoDiffOriginalFunction(func); auto kind = Demangle::getAutoDiffFunctionKind(derivativeId->getKind()); auto *resultIndices = @@ -113,7 +113,7 @@ public: std::string mangleConstructorMethodDescriptor(const ConstructorDecl *ctor, bool isAllocating) { - llvm::SaveAndRestore X(AllowInverses, inversesAllowed(ctor)); + llvm::SaveAndRestore X(AllowedInverses, inversesAllowed(ctor)); beginMangling(); appendConstructorEntity(ctor, isAllocating); appendOperator("Tq"); @@ -401,7 +401,7 @@ public: const RootProtocolConformance *conformance); std::string manglePropertyDescriptor(const AbstractStorageDecl *storage) { - llvm::SaveAndRestore X(AllowInverses, inversesAllowed(storage)); + llvm::SaveAndRestore X(AllowedInverses, inversesAllowed(storage)); beginMangling(); appendEntity(storage); appendOperator("MV"); @@ -409,7 +409,7 @@ public: } std::string mangleFieldOffset(const ValueDecl *Decl) { - llvm::SaveAndRestore X(AllowInverses, inversesAllowed(Decl)); + llvm::SaveAndRestore X(AllowedInverses, inversesAllowed(Decl)); beginMangling(); appendEntity(Decl); appendOperator("Wvd"); diff --git a/lib/Parse/ParseDecl.cpp b/lib/Parse/ParseDecl.cpp index 4b858df6a1c..8243f166522 100644 --- a/lib/Parse/ParseDecl.cpp +++ b/lib/Parse/ParseDecl.cpp @@ -2743,6 +2743,39 @@ ParserStatus Parser::parseNewDeclAttribute(DeclAttributes &Attributes, case DeclAttrKind::SetterAccess: llvm_unreachable("handled by DeclAttrKind::AccessControl"); + case DeclAttrKind::PreInverseGenerics: { + TypeRepr *exceptType = nullptr; + SourceLoc rParenLoc = Loc; + + if (consumeIfAttributeLParen()) { + if (Tok.getText() != "except" || peekToken().isNot(tok::colon)) { + diagnose(Tok, diag::attr_pre_inverse_generics_expected_except); + skipUntil(tok::r_paren); + consumeIf(tok::r_paren); + return makeParserSuccess(); + } + consumeToken(tok::identifier); + consumeToken(tok::colon); + + auto type = parseType(diag::expected_type); + if (type.isNull()) + return makeParserSuccess(); + exceptType = type.get(); + + if (!consumeIf(tok::r_paren, rParenLoc)) { + diagnose(Tok.getLoc(), diag::attr_expected_rparen, + AttrName, /*isModifier=*/false); + return makeParserSuccess(); + } + } + + if (!DiscardAttribute) + Attributes.add(new (Context) PreInverseGenericsAttr( + AtLoc, SourceRange(AtLoc.isValid() ? AtLoc : Loc, rParenLoc), + exceptType)); + break; + } + #define SIMPLE_DECL_ATTR(_, CLASS, ...) case DeclAttrKind::CLASS: #include "swift/AST/DeclAttr.def" if (!DiscardAttribute) diff --git a/lib/Sema/TypeCheckAttr.cpp b/lib/Sema/TypeCheckAttr.cpp index b9326978851..edcb9425d52 100644 --- a/lib/Sema/TypeCheckAttr.cpp +++ b/lib/Sema/TypeCheckAttr.cpp @@ -222,7 +222,6 @@ public: IGNORED_ATTR(Documentation) IGNORED_ATTR(LexicalLifetimes) IGNORED_ATTR(AllowFeatureSuppression) - IGNORED_ATTR(PreInverseGenerics) IGNORED_ATTR(Safe) IGNORED_ATTR(Diagnose) #undef IGNORED_ATTR @@ -523,7 +522,8 @@ public: void visitSendableAttr(SendableAttr *attr); void visitMacroRoleAttr(MacroRoleAttr *attr); - + + void visitPreInverseGenericsAttr(PreInverseGenericsAttr *attr); void visitRawLayoutAttr(RawLayoutAttr *attr); void visitNonEscapableAttr(NonEscapableAttr *attr); @@ -8710,6 +8710,80 @@ void AttributeChecker::visitMacroRoleAttr(MacroRoleAttr *attr) { {}); } +void AttributeChecker::visitPreInverseGenericsAttr( + PreInverseGenericsAttr *attr) { + if (isa(D)) { + diagnose(attr->getLocation(), + diag::attr_pre_inverse_generics_on_extension); + return; + } + + if (attr->hasExcept(D) && + !Ctx.LangOpts.hasFeature(Feature::PreInverseGenericsExcept)) { + Ctx.Diags + .diagnose(attr->getLocation(), + diag::attribute_requires_experimental_feature, attr, + "PreInverseGenericsExcept") + .warnInSwiftInterface(D->getDeclContext()); + } + + // Trigger the request to resolve and validate the optional 'except:' argument. + (void)attr->getAllowedInverses(D); +} + +Type +ResolvePreInverseGenericsRequest::evaluate(Evaluator &evaluator, + Decl *decl, + PreInverseGenericsAttr *attr) const { + // Declarations deserialized from a module file should have the resolved + // type cached already and never reach here. + if (auto fileUnit = + dyn_cast(decl->getDeclContext()->getModuleScopeContext())) + if (fileUnit->getKind() == FileUnitKind::SerializedAST) + llvm::report_fatal_error("cannot resolve serialized @_preInverseGenerics " + "as it is missing the TypeRepr!"); + + auto &ctx = decl->getASTContext(); + auto *typeRepr = attr->ExceptTypeRepr; + + // Mangle zero inverses by returning the composition that contains none. + // This is also the fall-back if they didn't provide a valid except: argument. + if (!typeRepr) + return ctx.TheAnyType; + + auto resolution = TypeResolution::forInterface( + decl->getDeclContext(), + TypeResolutionOptions(TypeResolverContext::GenericRequirement), + /*unboundTyOpener=*/nullptr, /*placeholderHandler=*/nullptr, + /*packElementOpener=*/nullptr); + Type resolvedTy = resolution.resolveType(typeRepr); + + if (!resolvedTy || resolvedTy->hasError()) { + ctx.Diags.diagnose(attr->getLocation(), + diag::attr_pre_inverse_generics_invalid_except); + return ctx.TheAnyType; + } + + // Don't permit compositions with non-inverse members. + // Don't permit `@_preInverseGenerics(except: Any)` as that's just confusing. + auto *pct = resolvedTy->getCanonicalType()->getAs(); + if (!pct || !pct->getMembers().empty() || pct->getCanonicalType() == ctx.TheAnyType) { + ctx.Diags.diagnose(attr->getLocation(), + diag::attr_pre_inverse_generics_invalid_except); + return ctx.TheAnyType; + } + + // TheUnconstrainedAnyType contains all inverses currently known. + // Just warn that `except: ` is the same as not writing the + // attribute, according to this version of the compiler. + if (pct->getCanonicalType() == ctx.TheUnconstrainedAnyType) { + ctx.Diags.diagnose(attr->getLocation(), + diag::attr_pre_inverse_generics_except_all); + } + + return pct; +} + void AttributeChecker::visitRawLayoutAttr(RawLayoutAttr *attr) { if (!Ctx.LangOpts.hasFeature(Feature::RawLayout)) { diagnoseAndRemoveAttr(attr, diag::attr_rawlayout_experimental); diff --git a/lib/Serialization/Deserialization.cpp b/lib/Serialization/Deserialization.cpp index 9da75f04000..374430b1fea 100644 --- a/lib/Serialization/Deserialization.cpp +++ b/lib/Serialization/Deserialization.cpp @@ -6757,6 +6757,26 @@ llvm::Error DeclDeserializer::deserializeDeclCommon() { } #include "swift/AST/DeclAttr.def" + case decls_block::PreInverseGenerics_DECL_ATTR: { + bool isImplicit; + TypeID typeID; + serialization::decls_block::PreInverseGenericsDeclAttrLayout:: + readRecord(scratch, isImplicit, typeID); + assert(!isImplicit); + + if (typeID) { + auto type = MF.getTypeChecked(typeID); + if (!type) { + return type.takeError(); + } + Attr = new (ctx) PreInverseGenericsAttr( + SourceLoc(), SourceRange(), /*exceptRepr=*/nullptr, type.get()); + } else { + Attr = new (ctx) PreInverseGenericsAttr(SourceLoc(), SourceRange()); + } + break; + } + default: // We don't know how to deserialize this kind of attribute. MF.fatal(llvm::make_error(recordID)); diff --git a/lib/Serialization/ModuleFormat.h b/lib/Serialization/ModuleFormat.h index 91e25439d37..e38582e667c 100644 --- a/lib/Serialization/ModuleFormat.h +++ b/lib/Serialization/ModuleFormat.h @@ -58,7 +58,7 @@ const uint16_t SWIFTMODULE_VERSION_MAJOR = 0; /// describe what change you made. The content of this comment isn't important; /// it just ensures a conflict if two people change the module format. /// Don't worry about adhering to the 80-column limit for this line. -const uint16_t SWIFTMODULE_VERSION_MINOR = 1001; // HiddenType record +const uint16_t SWIFTMODULE_VERSION_MINOR = 1002; // upgrade '@_preInverseGenerics' to support '@_preInverseGenerics(except:) /// A standard hash seed used for all string hashes in a serialized module. /// @@ -2590,6 +2590,12 @@ namespace decls_block { >; #include "swift/AST/DeclAttr.def" + using PreInverseGenericsDeclAttrLayout = BCRecordLayout< + PreInverseGenerics_DECL_ATTR, + BCFixed<1>, // implicit + TypeIDField // except type + >; + using DynamicReplacementDeclAttrLayout = BCRecordLayout< DynamicReplacement_DECL_ATTR, BCFixed<1>, // implicit flag diff --git a/lib/Serialization/Serialization.cpp b/lib/Serialization/Serialization.cpp index 9c5118c5679..38edc0efb2f 100644 --- a/lib/Serialization/Serialization.cpp +++ b/lib/Serialization/Serialization.cpp @@ -3074,6 +3074,20 @@ class Serializer::DeclSerializer : public DeclVisitor { } #include "swift/AST/DeclAttr.def" + case DeclAttrKind::PreInverseGenerics: { + auto *attr = cast(DA); + auto abbrCode = + S.DeclTypeAbbrCodes[PreInverseGenericsDeclAttrLayout::Code]; + auto exceptType = attr->getResolvedExceptType(D); + if (S.skipTypeIfInvalid(exceptType, attr->getExceptTypeRepr())) + return; + + auto typeID = S.addTypeRef(exceptType); + PreInverseGenericsDeclAttrLayout::emitRecord( + S.Out, S.ScratchRecord, abbrCode, attr->isImplicit(), typeID); + return; + } + case DeclAttrKind::ABI: { auto *theAttr = cast(DA); auto abbrCode = S.DeclTypeAbbrCodes[ABIDeclAttrLayout::Code]; diff --git a/test/ModuleInterface/pre_inverse_generics_except.swift b/test/ModuleInterface/pre_inverse_generics_except.swift new file mode 100644 index 00000000000..c13dc774755 --- /dev/null +++ b/test/ModuleInterface/pre_inverse_generics_except.swift @@ -0,0 +1,43 @@ +// RUN: %empty-directory(%t) +// RUN: %target-swift-emit-module-interface(%t/Test.swiftinterface) %s \ +// RUN: -module-name Test \ +// RUN: -enable-experimental-feature PreInverseGenericsExcept + +// RUN: %FileCheck --implicit-check-not '#if' %s < %t/Test.swiftinterface + +// REQUIRES: swift_feature_PreInverseGenericsExcept + +// The bare @_preInverseGenerics needs no feature guard. +// CHECK: @_preInverseGenerics public func bare(_ t: borrowing T) where T : ~Copyable +@_preInverseGenerics +public func bare(_ t: borrowing T) {} + +// The except: form requires a #if $PreInverseGenericsExcept guard. +// Older compilers that don't support the feature will not see the declaration. + +// CHECK: #if compiler(>=5.3) && $PreInverseGenericsExcept +// CHECK-NEXT: @_preInverseGenerics(except: ~Copyable) public func exceptCopyable(_ t: borrowing T) where T : ~Copyable, T : ~Escapable +// CHECK-NEXT: #endif +@_preInverseGenerics(except: ~Copyable) +public func exceptCopyable(_ t: borrowing T) {} + +// CHECK: #if compiler(>=5.3) && $PreInverseGenericsExcept +// CHECK-NEXT: @_preInverseGenerics(except: ~Escapable) public func exceptEscapable(_ t: borrowing T) where T : ~Copyable, T : ~Escapable +// CHECK-NEXT: #endif +@_preInverseGenerics(except: ~Escapable) +public func exceptEscapable(_ t: borrowing T) {} + +// CHECK: #if compiler(>=5.3) && $PreInverseGenericsExcept +// CHECK-NEXT: @_preInverseGenerics(except: ~Copyable & ~Escapable) public func exceptBoth(_ t: borrowing T) where T : ~Copyable, T : ~Escapable +@_preInverseGenerics(except: ~Copyable & ~Escapable) +public func exceptBoth(_ t: borrowing T) {} + + +@frozen +public struct MySpan: ~Copyable { +// CHECK: #if compiler(>=5.3) && $PreInverseGenericsExcept +// CHECK-NEXT: @_preInverseGenerics(except: ~Copyable) public var _count: Swift::Int +// CHECK-NEXT: #endif + @_preInverseGenerics(except: ~Copyable) + public var _count: Int +} diff --git a/test/SILGen/mangling_inverse_generics.swift b/test/SILGen/mangling_inverse_generics.swift index 91a7f7f3edd..015c8abc516 100644 --- a/test/SILGen/mangling_inverse_generics.swift +++ b/test/SILGen/mangling_inverse_generics.swift @@ -1,11 +1,18 @@ // RUN: %empty-directory(%t) // RUN: %target-swift-emit-silgen %s -module-name test \ // RUN: -parse-as-library \ +// RUN: -enable-experimental-feature Lifetimes \ +// RUN: -enable-experimental-feature LifetimeDependence \ +// RUN: -enable-experimental-feature PreInverseGenericsExcept \ // RUN: > %t/test.silgen // RUN: %FileCheck %s < %t/test.silgen // RUN: %swift-demangle < %t/test.silgen | %FileCheck %s --check-prefix=DEMANGLED +// REQUIRES: swift_feature_Lifetimes +// REQUIRES: swift_feature_LifetimeDependence +// REQUIRES: swift_feature_PreInverseGenericsExcept + protocol NoncopyableProto: ~Copyable {} @@ -352,4 +359,81 @@ extension E where T: ~Copyable & NoncopyableProto { func dumb() {} } +//===----------------------------------------------------------------------===// +// @_preInverseGenerics(except:) +//===----------------------------------------------------------------------===// + +// DEMANGLED: test.keepCopyable(A) -> () +// CHECK: sil [ossa] @$s4test12keepCopyableyyxRi_zlF : $@convention(thin) (@in_guaranteed T) -> () { +@_preInverseGenerics(except: ~Copyable) +public func keepCopyable(_ t: borrowing T) {} + +// DEMANGLED: test.keepEscapable(A) -> () +// CHECK: sil [ossa] @$s4test13keepEscapableyyxRi0_zlF : $@convention(thin) (@in_guaranteed T) -> () { +@_preInverseGenerics(except: ~Escapable) +public func keepEscapable(_ t: borrowing T) {} + +// DEMANGLED: test.stripBoth(A) -> () +// CHECK: sil [ossa] @$s4test9stripBothyyxlF : $@convention(thin) (@in_guaranteed T) -> () { +@_preInverseGenerics +public func stripBoth(_ t: borrowing T) {} + + +public struct F: ~Copyable { + // DEMANGLED: (extension in test):test.F< where A: ~Swift.Copyable>.memberKeepCopyable() -> () + // CHECK: sil [ossa] @$s4test1FVAARi_zrlE18memberKeepCopyableyyF : $@convention(method) (@guaranteed F) -> () { + @_preInverseGenerics(except: ~Copyable) + public func memberKeepCopyable() {} + + // Strips ~Copyable from T but keeps ~Escapable from U + // DEMANGLED: test.F.withEscapable(A1) -> () + // CHECK: sil [ossa] @$s4test1FV13withEscapableyyqd__Ri0_d__lF : $@convention(method) (@in_guaranteed U, @guaranteed F) -> () { + @_preInverseGenerics(except: ~Escapable) + public func withEscapable(_ u: borrowing U) {} + + // Keeps ~Copyable from both T and U, strips ~Escapable from U + // DEMANGLED: (extension in test):test.F< where A: ~Swift.Copyable>.withEscapable2(A1) -> () + // CHECK: sil [ossa] @$s4test1FVAARi_zrlE14withEscapable2yyqd__Ri_d__lF : $@convention(method) (@in_guaranteed U, @guaranteed F) -> () { + @_preInverseGenerics(except: ~Copyable) + public func withEscapable2(_ u: borrowing U) {} +} + +// Simulates Span gaining ~Escapable. Only ~Copyable is mangled into its symbols. +@frozen +public struct MySpan: ~Copyable, ~Escapable { + // DEMANGLED: (extension in test):test.MySpan< where A: ~Swift.Copyable>._count.getter : Swift.Int + // CHECK: sil [transparent] [serialized] [ossa] @$s4test6MySpanVAARi_zrlE6_countSivg : $@convention(method) (@guaranteed MySpan) -> Int { + @_preInverseGenerics(except: ~Copyable) + public var _count: Int + + // DEMANGLED: (extension in test):test.MySpan< where A: ~Swift.Copyable>._pointer.getter : Swift.UnsafeRawPointer? + // CHECK: sil [transparent] [serialized] [ossa] @$s4test6MySpanVAARi_zrlE8_pointerSVSgvg : $@convention(method) (@guaranteed MySpan) -> Optional { + @_preInverseGenerics(except: ~Copyable) + public var _pointer: UnsafeRawPointer? + + // DEMANGLED: (extension in test):test.MySpan< where A: ~Swift.Copyable>.oldMethod() -> () + // CHECK: sil [ossa] @$s4test6MySpanVAARi_zrlE9oldMethodyyF : $@convention(method) (@guaranteed MySpan) -> () { + @_preInverseGenerics(except: ~Copyable) + public func oldMethod() {} + + // DEMANGLED: (extension in test):test.MySpan< where A: ~Swift.Copyable>.oldComputed.getter : Swift.Int + // CHECK: sil [ossa] @$s4test6MySpanVAARi_zrlE11oldComputedSivg : $@convention(method) (@guaranteed MySpan) -> Int { + @_preInverseGenerics(except: ~Copyable) + public var oldComputed: Int { return _count } + + @_lifetime(immortal) + public init() { + self._count = 0 + self._pointer = nil + } +} + +// @_preInverseGenerics on an extension was permitted but seems to have no effect on member mangling; warn about that. +@_preInverseGenerics +extension MySpan where T: ~Copyable & ~Escapable { + // Both inverses are still mangled. + // DEMANGLED: (extension in test):test.MySpan< where A: ~Swift.Copyable, A: ~Swift.Escapable>.extMethod() -> () + // CHECK: sil [ossa] @$s4test6MySpanVAARi_zRi0_zrlE9extMethodyyF : $@convention(method) (@guaranteed MySpan) -> () { + public func extMethod() {} +} diff --git a/test/Sema/preInverseGenerics.swift b/test/Sema/preInverseGenerics.swift new file mode 100644 index 00000000000..0a21560b5da --- /dev/null +++ b/test/Sema/preInverseGenerics.swift @@ -0,0 +1,40 @@ +// RUN: %target-typecheck-verify-swift -enable-experimental-feature PreInverseGenericsExcept + +// REQUIRES: swift_feature_PreInverseGenericsExcept + +@_preInverseGenerics +func bare(_ t: borrowing T) {} + +@_preInverseGenerics(except: ~Copyable) +func exceptCopyable(_ t: borrowing T) {} + +@_preInverseGenerics(except: ~Escapable) +func exceptEscapable(_ t: borrowing T) {} + +// excepting all inverses is equivalent to not using the attribute +@_preInverseGenerics(except: ~Copyable & ~Escapable) // expected-warning {{'@_preInverseGenerics' that excepts all inverse constraints is equivalent to not using the attribute}} +func exceptBoth(_ t: borrowing T) {} + +// `except: Any` is confusing; reject it in favor of the bare form. +@_preInverseGenerics(except: Any) // expected-error {{'except' argument to '@_preInverseGenerics' must consist only of inverse constraints such as '~Copyable' or '~Escapable'}} +func exceptAny(_ t: borrowing T) {} + +// wrong label +@_preInverseGenerics(foo: ~Copyable) // expected-error {{expected 'except:' argument in '@_preInverseGenerics'}} +func bad1() {} + +// non-inverse type +@_preInverseGenerics(except: Int) // expected-error {{'except' argument to '@_preInverseGenerics' must consist only of inverse constraints such as '~Copyable' or '~Escapable'}} +func bad2() {} + +// positive protocol (not inverted) +@_preInverseGenerics(except: Copyable) // expected-error {{'except' argument to '@_preInverseGenerics' must consist only of inverse constraints such as '~Copyable' or '~Escapable'}} +func bad3() {} + +// Warning: attribute on extension has no effect +struct S: ~Copyable {} + +@_preInverseGenerics // expected-warning {{'@_preInverseGenerics' has no effect on an extension; place it on individual members instead}} +extension S where T: ~Copyable { + func extMethod() {} +} diff --git a/test/attr/attr_preInverseGenerics.swift b/test/attr/attr_preInverseGenerics.swift new file mode 100644 index 00000000000..0203faaf6aa --- /dev/null +++ b/test/attr/attr_preInverseGenerics.swift @@ -0,0 +1,16 @@ +// RUN: %target-typecheck-verify-swift + +// The bare @_preInverseGenerics does NOT require the experimental feature. +@_preInverseGenerics +func bare(_ t: borrowing T) {} + +// The 'except:' form DOES require the experimental feature. +@_preInverseGenerics(except: ~Copyable) // expected-error {{'@_preInverseGenerics' is an experimental feature; use '-enable-experimental-feature PreInverseGenericsExcept'}} +func exceptCopyable(_ t: borrowing T) {} + +// An invalid attribute with 'except:' still DOES require the feature. +@_preInverseGenerics(except: Int) // expected-error {{'@_preInverseGenerics' is an experimental feature; use '-enable-experimental-feature PreInverseGenericsExcept'}} + // expected-error@-1 {{'except' argument to '@_preInverseGenerics' must consist only of inverse constraints such as '~Copyable' or '~Escapable'}} +func exceptInt(_ t: borrowing T) {} + + From 5149dbcd290dceda3d614368fca6df5686bfd0d8 Mon Sep 17 00:00:00 2001 From: Aidan Hall <51188582+aidan-hall@users.noreply.github.com> Date: Fri, 15 May 2026 11:34:36 +0100 Subject: [PATCH 24/25] Lifetimes: Infer copy dependence kind on `@noescape` closures (#88879) Follow-up to https://github.com/swiftlang/swift/pull/88733, enabling the example in rdar://172511809 ([nonescapable] Allow a nonescaping function to be a lifetime dependency source): ```swift @_lifetime(body) // Inferred dependence kind: copy func foo(body: () -> Span) { body() } ``` or ```swift // Inferred: @_lifetime(copy body) func foo(body: () -> Span) { body() } ``` Follow-up: Consider also disallowing borrow dependence on `@noescape` closures. --------- Co-authored-by: Andrew Trick --- lib/AST/LifetimeDependence.cpp | 13 +++++++- .../lifetime_underscored_dependence.swift | 25 +++++++++++++++ ...lifetime_underscored_dependence_test.swift | 32 +++++++++++++++++++ test/SIL/closure_lifetime_dependence.swift | 10 ++++++ .../verify_diagnostics.swift | 15 +++++---- test/Sema/lifetime_dependence_functype.swift | 17 +++++++++- 6 files changed, 103 insertions(+), 9 deletions(-) diff --git a/lib/AST/LifetimeDependence.cpp b/lib/AST/LifetimeDependence.cpp index 45593d41a1d..d69a98d2ac2 100644 --- a/lib/AST/LifetimeDependence.cpp +++ b/lib/AST/LifetimeDependence.cpp @@ -1125,6 +1125,11 @@ protected: switch (parsedLifetimeKind) { case ParsedLifetimeDependenceKind::Default: { + // Infer copy dependence on @noescape function types by default. + if (type->isNoEscape()) { + return LifetimeDependenceKind::Inherit; + } + if (type->isEscapable()) { if (loweredOwnership == ValueOwnership::Shared || loweredOwnership == ValueOwnership::InOut) { @@ -1823,13 +1828,19 @@ protected: // The usual diagnostic check is sufficient. return; } - // Do not infer non-escapable dependence kind -- it is ambiguous. + // Do not infer non-escapable dependence kind -- it is ambiguous, except for + // noescape function types, for which we should always infer a copy dependence. auto const ¶mInfo = parameterInfos[0]; Type paramTypeInContext = paramInfo.typeInContext; if (paramTypeInContext->hasError()) { return; } if (!paramTypeInContext->isEscapable()) { + if (paramTypeInContext->isNoEscape()) { + resultDeps->addIfNew(/*paramIndex*/ 0, LifetimeDependenceKind::Inherit); + return; + } + diagnose(returnLoc, diag::lifetime_dependence_cannot_infer_kind, diagnosticQualifier(), paramInfo.name()); return; diff --git a/test/ModuleInterface/Inputs/lifetime_underscored_dependence.swift b/test/ModuleInterface/Inputs/lifetime_underscored_dependence.swift index 4f5416654b6..ebf1da809fa 100644 --- a/test/ModuleInterface/Inputs/lifetime_underscored_dependence.swift +++ b/test/ModuleInterface/Inputs/lifetime_underscored_dependence.swift @@ -159,3 +159,28 @@ public func takeReadBorrower(f: @_lifetime(borrow a) (_ a: AnotherView) -> Anoth @inlinable public func takeWriteBorrower(f: @_lifetime(&a) (_ a: inout AnotherView) -> AnotherView) {} + +// Infer @_lifetime(copy f) +@inlinable +public func takeClosureImplicitDependence(f: () -> AnotherView) -> AnotherView { + f() +} + +@_lifetime(f) // Infer @_lifetime(copy f) +@inlinable +public func takeClosureImplicitDependenceKind(f: () -> AnotherView) -> AnotherView { + f() +} + +@inlinable +public func takeTakeImplicitCopyClosure(g: @_lifetime(copy f) (_ f: () -> AnotherView) -> AnotherView) { +} + +// Verify that takeClosureImplicitDependenceKind and takeClosureImplicitDependence +// are both inferred as @_lifetime(copy f). It is illegal to pass a function that +// borrows its context into a function-type parameter that copies its context. +@inlinable +public func callTTICC() { + takeTakeImplicitCopyClosure(g: takeClosureImplicitDependenceKind) + takeTakeImplicitCopyClosure(g: takeClosureImplicitDependence) +} diff --git a/test/ModuleInterface/lifetime_underscored_dependence_test.swift b/test/ModuleInterface/lifetime_underscored_dependence_test.swift index b21fc74a13d..8220dca74bf 100644 --- a/test/ModuleInterface/lifetime_underscored_dependence_test.swift +++ b/test/ModuleInterface/lifetime_underscored_dependence_test.swift @@ -235,3 +235,35 @@ import lifetime_underscored_dependence // CHECK-NEXT: #if compiler(>=5.3) && $ClosureLifetimes // CHECK-NEXT: @inlinable public func takeWriteBorrower(f: @_lifetime(&a) (_ a: inout lifetime_underscored_dependence::AnotherView) -> lifetime_underscored_dependence::AnotherView) {} // CHECK-NEXT: #endif + +// CHECK-NEXT: #if compiler(>=5.3) && $Lifetimes +// CHECK-NEXT: @inlinable public func takeClosureImplicitDependence(f: () -> lifetime_underscored_dependence::AnotherView) -> lifetime_underscored_dependence::AnotherView { +// CHECK-NEXT: f() +// CHECK-NEXT: } +// CHECK-NEXT: #else +// CHECK-NEXT: @inlinable public func takeClosureImplicitDependence(f: () -> lifetime_underscored_dependence::AnotherView) -> lifetime_underscored_dependence::AnotherView { +// CHECK-NEXT: f() +// CHECK-NEXT: } +// CHECK-NEXT: #endif + +// CHECK-NEXT: #if compiler(>=5.3) && $Lifetimes +// CHECK-NEXT: @_lifetime(f) +// CHECK-NEXT: @inlinable public func takeClosureImplicitDependenceKind(f: () -> lifetime_underscored_dependence::AnotherView) -> lifetime_underscored_dependence::AnotherView { +// CHECK-NEXT: f() +// CHECK-NEXT: } +// CHECK-NEXT: #else +// CHECK-NEXT: @lifetime(f) +// CHECK-NEXT: @inlinable public func takeClosureImplicitDependenceKind(f: () -> lifetime_underscored_dependence::AnotherView) -> lifetime_underscored_dependence::AnotherView { +// CHECK-NEXT: f() +// CHECK-NEXT: } +// CHECK-NEXT: #endif + +// CHECK: #if compiler(>=5.3) && $ClosureLifetimes +// CHECK-NEXT: @inlinable public func takeTakeImplicitCopyClosure(g: @_lifetime(copy f) (_ f: () -> lifetime_underscored_dependence::AnotherView) -> lifetime_underscored_dependence::AnotherView) { +// CHECK-NEXT: } +// CHECK-NEXT: #endif + +// CHECK: @inlinable public func callTTICC() { +// CHECK-NEXT: takeTakeImplicitCopyClosure(g: takeClosureImplicitDependenceKind) +// CHECK-NEXT: takeTakeImplicitCopyClosure(g: takeClosureImplicitDependence) +// CHECK-NEXT: } diff --git a/test/SIL/closure_lifetime_dependence.swift b/test/SIL/closure_lifetime_dependence.swift index d86a39f52df..989fcec9654 100644 --- a/test/SIL/closure_lifetime_dependence.swift +++ b/test/SIL/closure_lifetime_dependence.swift @@ -113,3 +113,13 @@ func callContextAndArgDependentPicker() { func copyClosureNE(body: () -> NE) -> NE { body() } + +// CHECK-LABEL: sil hidden @$s27closure_lifetime_dependence23implicitDependClosureNE4bodyAA0G0VAEyXE_tF : $@convention(thin) (@guaranteed @noescape @callee_guaranteed () -> @lifetime(captures) @owned NE) -> @lifetime(copy 0) @owned NE { +// CHECK: bb0(%0 : $@noescape @callee_guaranteed () -> @lifetime(captures) @owned NE): +// CHECK: [[RESULT:%[0-9]+]] = apply %0() : $@noescape @callee_guaranteed () -> @lifetime(captures) @owned NE +// CHECK-NEXT: return [[RESULT]] +// CHECK-LABEL: } // end sil function '$s27closure_lifetime_dependence23implicitDependClosureNE4bodyAA0G0VAEyXE_tF' +@_lifetime(body) +func implicitDependClosureNE(body: () -> NE) -> NE { + body() +} diff --git a/test/SILOptimizer/lifetime_dependence/verify_diagnostics.swift b/test/SILOptimizer/lifetime_dependence/verify_diagnostics.swift index d99dd4f60f1..f6897bccdb7 100644 --- a/test/SILOptimizer/lifetime_dependence/verify_diagnostics.swift +++ b/test/SILOptimizer/lifetime_dependence/verify_diagnostics.swift @@ -450,18 +450,19 @@ func testMutableCapture(arg: consuming NCE, action: @escaping (inout NCE) -> ()) } } +// Explicit dependence on a nonescaping closure context. +@_lifetime(body) +func testBasicExplicitClosureDependency(body: () -> NE) -> NE { + return body() +} + // Implicit dependence on a nonescaping closure context. -// -// TODO: remove the _overrideLifetime when context dependencies are tracked and -// non-escaping function types can be used as (copy) dependence sources (rdar://172511809). -@_lifetime(borrow value) -func testBasicClosureDependency(value: AnyObject, body: () -> NE) -> NE { - return _overrideLifetime(body(), borrowing: value) +func testBasicClosureDependency(body: () -> NE) -> NE { + return body() } // Implicit dependence on a nonescaping closure context. The result is escaping in the current generic context, so // should not be diagnosed as an escape. -@_lifetime(copy f) func testIndirectClosureResult(f: () -> CNE) -> CNE { return f() } diff --git a/test/Sema/lifetime_dependence_functype.swift b/test/Sema/lifetime_dependence_functype.swift index b206bc8e764..d9215d36e5d 100644 --- a/test/Sema/lifetime_dependence_functype.swift +++ b/test/Sema/lifetime_dependence_functype.swift @@ -429,11 +429,26 @@ func callLifetimeFunctions() { } // ~Escapable function types -@_lifetime(body) // expected-error{{cannot infer the lifetime dependence scope on a function with a ~Escapable parameter, specify '@_lifetime(borrow body)' or '@_lifetime(copy body)'}} +@_lifetime(body) // OK: Infer copy +func implicitDependKindClosureNE(body: () -> NE) -> NE { + body() +} + +func callImplicitDependKindClosureNE(ne: NE) -> NE { + let neo = implicitDependClosureNE { ne } + return neo +} + func implicitDependClosureNE(body: () -> NE) -> NE { body() } +func callImplicitDependClosureNE(ne: NE) -> NE { + // OK: Infer copy + let neo = implicitDependClosureNE { ne } + return neo +} + @_lifetime(copy body) func explicitCopyClosureNE(body: () -> NE) -> NE { body() From a8d3d1f733421c036337b78837b23edb9546a04d Mon Sep 17 00:00:00 2001 From: Joe Groff Date: Fri, 15 May 2026 09:26:17 -0700 Subject: [PATCH 25/25] Remove hardcoded integer bit width from test/SILOptimizer/specialize_by_integer_parameter.swift --- test/SILOptimizer/specialize_by_integer_parameter.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/SILOptimizer/specialize_by_integer_parameter.swift b/test/SILOptimizer/specialize_by_integer_parameter.swift index 5a654d908bc..7aa73ebcbba 100644 --- a/test/SILOptimizer/specialize_by_integer_parameter.swift +++ b/test/SILOptimizer/specialize_by_integer_parameter.swift @@ -5,7 +5,7 @@ public struct Foo { // CHECK: [[PARAM_VAL:%.*]] = type_value $Int for count // CHECK: [[SPEC_INT:%.*]] = integer_literal ${{.*}}, 3 // CHECK: [[PARAM_INT:%.*]] = struct_extract [[PARAM_VAL]], #Int._value - // CHECK: builtin "cmp_eq_Int64"([[PARAM_INT]], [[SPEC_INT]]) + // CHECK: builtin "cmp_eq_Int{{.*}}"([[PARAM_INT]], [[SPEC_INT]]) @specialized(where count == 3) public func bar() -> Int { return count