diff --git a/include/swift/ClangImporter/ClangImporterRequests.h b/include/swift/ClangImporter/ClangImporterRequests.h index 256e0234d462a..c8f9ac83ffd79 100644 --- a/include/swift/ClangImporter/ClangImporterRequests.h +++ b/include/swift/ClangImporter/ClangImporterRequests.h @@ -531,10 +531,6 @@ enum class CxxEscapability { Escapable, NonEscapable, Unknown }; struct EscapabilityLookupDescriptor final { const clang::Type *type; ClangImporter::Implementation *impl; - // Only explicitly ~Escapable annotated types are considered ~Escapable. - // This is for backward compatibility, so we continue to import aggregates - // containing pointers as Escapable types. - bool annotationOnly = true; friend llvm::hash_code hash_value(const EscapabilityLookupDescriptor &desc) { return llvm::hash_combine(desc.type); @@ -542,7 +538,7 @@ struct EscapabilityLookupDescriptor final { friend bool operator==(const EscapabilityLookupDescriptor &lhs, const EscapabilityLookupDescriptor &rhs) { - return lhs.type == rhs.type && lhs.annotationOnly == rhs.annotationOnly; + return lhs.type == rhs.type; } friend bool operator!=(const EscapabilityLookupDescriptor &lhs, diff --git a/lib/ClangImporter/ClangImporter.cpp b/lib/ClangImporter/ClangImporter.cpp index c5d9e32c259df..d7b7c84cb9cf6 100644 --- a/lib/ClangImporter/ClangImporter.cpp +++ b/lib/ClangImporter/ClangImporter.cpp @@ -5493,12 +5493,18 @@ ClangTypeEscapability::evaluate(Evaluator &evaluator, // Escapability inference rules: // - array and vector types have the same escapability as their element type - // - pointer and reference types are currently imported as escapable + // - pointer and reference types are currently imported as unknown // (importing them as non-escapable broke backward compatibility) // - a record type is escapable or non-escapable if it is explicitly annotated // as such // - a record type is escapable if it is annotated with SWIFT_ESCAPABLE_IF() // and none of the annotation arguments are non-escapable + // - an aggregate or non-cxx record is escapable if none of their fields or + // bases are non-escapable (as long as they have a definition) + // * we only infer escapability for simple types, with no user-declared + // constructors, virtual bases or virtual member functions + // * for more complex CxxRecordDecls, we rely solely on escapability + // annotations // - in all other cases, the record has unknown escapability (e.g. no // escapability annotations, malformed escapability annotations) @@ -5554,14 +5560,8 @@ ClangTypeEscapability::evaluate(Evaluator &evaluator, continue; } - // The `annotationOnly` flag used to control which types we infered - // escapability for. Currently, this flag is always set to true, meaning - // that any type without an annotation (CxxRecordDecls, aggregates, decls - // lacking definition, etc.) will raise `hasUnknown`. - if (desc.annotationOnly) { - hasUnknown = true; - continue; - } + // Only try to infer escapability if the record doesn't have any + // escapability annotations auto cxxRecordDecl = dyn_cast(recordDecl); if (recordDecl->getDefinition() && (!cxxRecordDecl || cxxRecordDecl->isAggregate())) { @@ -5571,7 +5571,11 @@ ClangTypeEscapability::evaluate(Evaluator &evaluator, } for (auto field : recordDecl->fields()) maybePushToStack(field->getType()->getUnqualifiedDesugaredType()); - continue; + } else { + // We only infer escapability for simple types, such as aggregates and + // RecordDecls that are not CxxRecordDecls. For more complex + // CxxRecordDecls, we rely solely on escapability annotations. + hasUnknown = true; } } else if (type->isArrayType()) { auto elemTy = cast(type) @@ -5582,10 +5586,9 @@ ClangTypeEscapability::evaluate(Evaluator &evaluator, maybePushToStack(vecTy->getElementType()->getUnqualifiedDesugaredType()); } else if (type->isAnyPointerType() || type->isBlockPointerType() || type->isMemberPointerType() || type->isReferenceType()) { - if (desc.annotationOnly) - hasUnknown = true; - else - return CxxEscapability::NonEscapable; + // pointer and reference types are currently imported as unknown + // (importing them as non-escapable broke backward compatibility) + hasUnknown = true; } } return hasUnknown ? CxxEscapability::Unknown : CxxEscapability::Escapable; diff --git a/test/Interop/Cxx/class/nonescapable-errors.swift b/test/Interop/Cxx/class/nonescapable-errors.swift index 29d2a9330e7b8..b8f94a790f530 100644 --- a/test/Interop/Cxx/class/nonescapable-errors.swift +++ b/test/Interop/Cxx/class/nonescapable-errors.swift @@ -118,34 +118,31 @@ struct SWIFT_ESCAPABLE Invalid { struct SWIFT_NONESCAPABLE NonEscapable {}; -template -struct HasAnonUnion { - union { - int known; - T unknown; - }; -}; +using NonEscapableOptional = std::optional; -template -struct HasAnonStruct { - struct { - int known; - T unknown; - }; -}; +// Infered as non-escapable +struct Aggregate { + int a; + View b; + bool c; -template -struct SWIFT_NONESCAPABLE NonEscapableHasAnonUnion { - union { - int known; - T unknown; - }; -}; + void someMethod() {} +}; -using HasAnonUnionNonEscapable = HasAnonUnion; -using HasAnonStructNonEscapable = HasAnonStruct; -using NonEscapableHasAnonUnionNonEscapable = NonEscapableHasAnonUnion; -using NonEscapableOptional = std::optional; +// This is a complex record (has user-declared constructors), so we don't infer escapability. +// By default, it's imported as escapable, which generates an error +// because of the non-escapable field 'View' +struct ComplexRecord { + int a; + View b; + bool c; + + ComplexRecord() : a(1), b(), c(false) {} + ComplexRecord(const ComplexRecord &other) = default; +}; + +Aggregate m1(); +ComplexRecord m2(); //--- test.swift import Test @@ -233,20 +230,24 @@ public func test3(_ x: inout View) { // CHECK-NO-LIFETIMES: pointer to non-escapable type 'View' cannot be imported } -public func anonymousUnions() { - _ = HasAnonUnionNonEscapable() - // CHECK: error: cannot find 'HasAnonUnionNonEscapable' in scope - // CHECK-NO-LIFETIMES: error: cannot find 'HasAnonUnionNonEscapable' in scope - _ = HasAnonStructNonEscapable() - // CHECK: error: cannot find 'HasAnonStructNonEscapable' in scope - // CHECK-NO-LIFETIMES: error: cannot find 'HasAnonStructNonEscapable' in scope - _ = NonEscapableHasAnonUnionNonEscapable() +public func optional() { _ = NonEscapableOptional() // CHECK: error: cannot infer the lifetime dependence scope on an initializer with a ~Escapable parameter, specify '@_lifetime(borrow {{.*}})' or '@_lifetime(copy {{.*}})' // CHECK-NO-LIFETIMES: error: an initializer cannot return a ~Escapable result // CHECK-NO-LIFETIMES: error: an initializer cannot return a ~Escapable result } +public func inferedEscapability() { + m1() + // CHECK: nonescapable.h:130:11: error: a function with a ~Escapable result needs a parameter to depend on + // CHECK-NO-LIFETIMES: nonescapable.h:130:11: error: a function cannot return a ~Escapable result + m2() + // CHECK: error: 'm2()' is unavailable: return type is unavailable in Swift + // CHECK: note: 'm2()' has been explicitly marked unavailable here + // CHECK-NO-LIFETIMES: error: 'm2()' is unavailable: return type is unavailable in Swift + // CHECK-NO-LIFETIMES: note: 'm2()' has been explicitly marked unavailable here +} + // CHECK-NOT: error // CHECK-NOT: warning // CHECK-NO-LIFETIMES-NOT: error diff --git a/test/Interop/Cxx/class/nonescapable-lifetimebound.swift b/test/Interop/Cxx/class/nonescapable-lifetimebound.swift index fe83cc1018354..31101075cedf8 100644 --- a/test/Interop/Cxx/class/nonescapable-lifetimebound.swift +++ b/test/Interop/Cxx/class/nonescapable-lifetimebound.swift @@ -166,6 +166,56 @@ using ReadonlyBytes = ReadonlySpan; using Bytes = Span; } // namespace rdar153081347 +struct SWIFT_NONESCAPABLE NonEscapable { + const int *p; +}; + +template +struct HasAnonUnion { + union { + int known; + T unknown; + }; +}; + +template +struct HasAnonStruct { + struct { + int known; + T unknown; + }; +}; + +template +struct SWIFT_NONESCAPABLE NonEscapableHasAnonUnion { + union { + int known; + T unknown; + }; +}; + +using HasAnonUnionNonEscapable = HasAnonUnion; +using HasAnonStructNonEscapable = HasAnonStruct; +using NonEscapableHasAnonUnionNonEscapable = NonEscapableHasAnonUnion; + +HasAnonUnionNonEscapable makeAnonUnionNonEscapable(const Owner &owner [[clang::lifetimebound]]) { + HasAnonUnionNonEscapable result; + result.unknown = {&owner.data}; + return result; +} + +HasAnonStructNonEscapable makeAnonStructNonEscapable(const Owner &owner [[clang::lifetimebound]]) { + return {1, &owner.data}; +} + +NonEscapableHasAnonUnionNonEscapable makeNonEscapableHasAnonUnionNonEscapable( + const Owner &owner [[clang::lifetimebound]]) { + NonEscapableHasAnonUnionNonEscapable result; + result.unknown = {&owner.data}; + return result; +} + + // CHECK: sil {{.*}}[clang makeOwner] {{.*}}: $@convention(c) () -> Owner // CHECK: sil {{.*}}[clang getView] {{.*}} : $@convention(c) (@in_guaranteed Owner) -> @lifetime(borrow address 0) @owned View // CHECK: sil {{.*}}[clang getViewFromFirst] {{.*}} : $@convention(c) (@in_guaranteed Owner, @in_guaranteed Owner) -> @lifetime(borrow address 0) @owned View @@ -182,6 +232,9 @@ using Bytes = Span; // CHECK: sil {{.*}}[clang CaptureView.handOut] {{.*}} : $@convention(cxx_method) (@lifetime(copy 1) @inout View, @in_guaranteed CaptureView) -> () // CHECK: sil {{.*}}[clang NS.getView] {{.*}} : $@convention(c) (@in_guaranteed Owner) -> @lifetime(borrow address 0) @owned View // CHECK: sil {{.*}}[clang moveOnlyId] {{.*}} : $@convention(c) (@in_guaranteed MoveOnly) -> @lifetime(borrow {{.*}}0) @out MoveOnly +// CHECK: sil {{.*}}[clang makeAnonUnionNonEscapable] {{.*}} : $@convention(c) (@in_guaranteed Owner) -> @lifetime(borrow address 0) @owned HasAnonUnion +// CHECK: sil {{.*}}[clang makeAnonStructNonEscapable] {{.*}} : $@convention(c) (@in_guaranteed Owner) -> @lifetime(borrow address 0) @owned HasAnonStruct +// CHECK: sil {{.*}}[clang makeNonEscapableHasAnonUnionNonEscapable] {{.*}} : $@convention(c) (@in_guaranteed Owner) -> @lifetime(borrow address 0) @owned NonEscapableHasAnonUnion //--- test.swift @@ -215,3 +268,10 @@ func canImportMoveOnlyNonEscapable(_ x: borrowing MoveOnly) { } func testInheritedCtors(_ s: rdar153081347.Bytes) {} + +func anonymousUnionsAndStructs(_ v: borrowing View) { + let o = makeOwner() + let _ = makeAnonUnionNonEscapable(o) + let _ = makeAnonStructNonEscapable(o) + let _ = makeNonEscapableHasAnonUnionNonEscapable(o) +} diff --git a/test/Interop/Cxx/class/safe-interop-mode.swift b/test/Interop/Cxx/class/safe-interop-mode.swift index ab664d16bb6a0..d379cb1658212 100644 --- a/test/Interop/Cxx/class/safe-interop-mode.swift +++ b/test/Interop/Cxx/class/safe-interop-mode.swift @@ -101,8 +101,10 @@ struct HoldsShared { SWIFT_RETURNS_UNRETAINED; }; -template struct TTake2 {}; -template struct PassThru {}; +template struct SWIFT_ESCAPABLE_IF(F, S) TTake2 {}; +template struct PassThru { + T field; +}; struct IsUnsafe { int *p; }; struct HasUnsafe : TTake2, IsUnsafe> {}; using AlsoUnsafe = PassThru; @@ -207,8 +209,7 @@ func useTTakeInt(x: TTakeInt) { } func useTTakePtr(x: TTakePtr) { - // expected-warning@+1{{expression uses unsafe constructs but is not marked with 'unsafe'}} - _ = x // expected-note{{reference to parameter 'x' involves unsafe type}} + _ = x } func useTTakeSafeTuple(x: TTakeSafeTuple) { @@ -216,8 +217,7 @@ func useTTakeSafeTuple(x: TTakeSafeTuple) { } func useTTakeUnsafeTuple(x: TTakeUnsafeTuple) { - // expected-warning@+1{{expression uses unsafe constructs but is not marked with 'unsafe'}} - _ = x // expected-note{{reference to parameter 'x' involves unsafe type}} + _ = x } func useTTakeUnsafeTuple(x: HasUnsafe) {