From d56173eb14ff5b2da68303b120098cfd7e060c64 Mon Sep 17 00:00:00 2001 From: Mike Ash Date: Tue, 8 Oct 2019 11:37:39 -0400 Subject: [PATCH 1/5] [Runtime] Handle existential target types in swift_dynamicCastMetatypeImpl. This fixes cases like `type is T.Type` when T is generic specialized to a protocol type. Note: the compiler can still optimize these checks away and will optimize this check down to `false` in some cases. We'll want to fix that as well. rdar://problem/56044443 --- stdlib/public/runtime/Casting.cpp | 8 ++++++++ test/Interpreter/generic_casts.swift | 20 +++++++++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/stdlib/public/runtime/Casting.cpp b/stdlib/public/runtime/Casting.cpp index c8349b170830d..17cb03dfc39ff 100644 --- a/stdlib/public/runtime/Casting.cpp +++ b/stdlib/public/runtime/Casting.cpp @@ -1115,6 +1115,14 @@ swift_dynamicCastMetatypeImpl(const Metadata *sourceType, } break; + case MetadataKind::Existential: { + auto targetTypeAsExistential = static_cast(targetType); + for (auto protocol : targetTypeAsExistential->getProtocols()) + if (!swift_conformsToProtocol(sourceType, protocol.getSwiftProtocol())) + return nullptr; + return origSourceType; + } + default: // The cast succeeds only if the metadata pointers are statically // equivalent. diff --git a/test/Interpreter/generic_casts.swift b/test/Interpreter/generic_casts.swift index bf87feb633bfb..2939f8418f0f5 100644 --- a/test/Interpreter/generic_casts.swift +++ b/test/Interpreter/generic_casts.swift @@ -1,4 +1,4 @@ -// RUN: %target-run-simple-swift | %FileCheck %s +// RUN: %target-run-simple-swift | %FileCheck --check-prefix CHECK --check-prefix CHECK-ONONE %s // RUN: %target-build-swift -O %s -o %t/a.out.optimized // RUN: %target-codesign %t/a.out.optimized // RUN: %target-run %t/a.out.optimized | %FileCheck %s @@ -101,6 +101,24 @@ func allMetasToAllMetas(_: T.Type, _: U.Type) -> Bool { return T.self is U.Type } +protocol P {} +struct PS: P {} +enum PE: P {} +class PC: P {} + +func nongenericAnyIsP(type: Any.Type) -> Bool { + return type is P.Type +} +func genericAnyIs(type: Any.Type, to: T.Type) -> Bool { + return type is T.Type +} +print("nongenericAnyIsP(type: PS.self)", nongenericAnyIsP(type: PS.self)) // CHECK: nongenericAnyIsP(type: PS.self) true +print("genericAnyIs(type: PS.self, to: P.self)", genericAnyIs(type: PS.self, to: P.self)) // CHECK-ONONE: genericAnyIs(type: PS.self, to: P.self) true +print("nongenericAnyIsP(type: PE.self)", nongenericAnyIsP(type: PE.self)) // CHECK: nongenericAnyIsP(type: PE.self) true +print("genericAnyIs(type: PE.self, to: P.self)", genericAnyIs(type: PE.self, to: P.self)) // CHECK-ONONE: genericAnyIs(type: PE.self, to: P.self) true +print("nongenericAnyIsP(type: PC.self)", nongenericAnyIsP(type: PC.self)) // CHECK: nongenericAnyIsP(type: PC.self) true +print("genericAnyIs(type: PC.self, to: P.self)", genericAnyIs(type: PC.self, to: P.self)) // CHECK-ONONE: genericAnyIs(type: PC.self, to: P.self) true + print(allToInt(22)) // CHECK: 22 print(anyToInt(44)) // CHECK: 44 allToC(C()).print() // CHECK: C! From 3ff277d31dd764159e66eb5fc9ac82cc10cdda11 Mon Sep 17 00:00:00 2001 From: Mike Ash Date: Thu, 10 Oct 2019 14:14:41 -0400 Subject: [PATCH 2/5] [Runtime] Use _conformsToProtocols instead of manually checking protocol conformance. This is cleaner and it fixes a bunch of cases the old code didn't handle: @objc protocols, class bounds, and superclass constraints. rdar://problem/56044443 --- stdlib/public/runtime/Casting.cpp | 7 +-- test/Interpreter/generic_casts.swift | 85 ++++++++++++++++++++++------ 2 files changed, 70 insertions(+), 22 deletions(-) diff --git a/stdlib/public/runtime/Casting.cpp b/stdlib/public/runtime/Casting.cpp index 17cb03dfc39ff..c25154c8d061a 100644 --- a/stdlib/public/runtime/Casting.cpp +++ b/stdlib/public/runtime/Casting.cpp @@ -403,7 +403,7 @@ static bool _conformsToProtocols(const OpaqueValue *value, for (auto protocol : existentialType->getProtocols()) { if (!_conformsToProtocol(value, type, protocol, conformances)) return false; - if (protocol.needsWitnessTable()) { + if (conformances != nullptr && protocol.needsWitnessTable()) { assert(*conformances != nullptr); ++conformances; } @@ -1117,9 +1117,8 @@ swift_dynamicCastMetatypeImpl(const Metadata *sourceType, case MetadataKind::Existential: { auto targetTypeAsExistential = static_cast(targetType); - for (auto protocol : targetTypeAsExistential->getProtocols()) - if (!swift_conformsToProtocol(sourceType, protocol.getSwiftProtocol())) - return nullptr; + if (!_conformsToProtocols(nullptr, sourceType, targetTypeAsExistential, nullptr)) + return nullptr; return origSourceType; } diff --git a/test/Interpreter/generic_casts.swift b/test/Interpreter/generic_casts.swift index 2939f8418f0f5..10b971068f4ce 100644 --- a/test/Interpreter/generic_casts.swift +++ b/test/Interpreter/generic_casts.swift @@ -101,24 +101,6 @@ func allMetasToAllMetas(_: T.Type, _: U.Type) -> Bool { return T.self is U.Type } -protocol P {} -struct PS: P {} -enum PE: P {} -class PC: P {} - -func nongenericAnyIsP(type: Any.Type) -> Bool { - return type is P.Type -} -func genericAnyIs(type: Any.Type, to: T.Type) -> Bool { - return type is T.Type -} -print("nongenericAnyIsP(type: PS.self)", nongenericAnyIsP(type: PS.self)) // CHECK: nongenericAnyIsP(type: PS.self) true -print("genericAnyIs(type: PS.self, to: P.self)", genericAnyIs(type: PS.self, to: P.self)) // CHECK-ONONE: genericAnyIs(type: PS.self, to: P.self) true -print("nongenericAnyIsP(type: PE.self)", nongenericAnyIsP(type: PE.self)) // CHECK: nongenericAnyIsP(type: PE.self) true -print("genericAnyIs(type: PE.self, to: P.self)", genericAnyIs(type: PE.self, to: P.self)) // CHECK-ONONE: genericAnyIs(type: PE.self, to: P.self) true -print("nongenericAnyIsP(type: PC.self)", nongenericAnyIsP(type: PC.self)) // CHECK: nongenericAnyIsP(type: PC.self) true -print("genericAnyIs(type: PC.self, to: P.self)", genericAnyIs(type: PC.self, to: P.self)) // CHECK-ONONE: genericAnyIs(type: PC.self, to: P.self) true - print(allToInt(22)) // CHECK: 22 print(anyToInt(44)) // CHECK: 44 allToC(C()).print() // CHECK: C! @@ -149,6 +131,73 @@ anyClassToCOrE(C()).print() // CHECK: C! anyClassToCOrE(D()).print() // CHECK: D! anyClassToCOrE(X()).print() // CHECK: E! +protocol P {} +@objc protocol PObjC {} +struct PS: P {} +enum PE: P {} +class PC: P, PObjC {} +class PCSub: PC {} + +func nongenericAnyIsP(type: Any.Type) -> Bool { + return type is P.Type +} +func nongenericAnyIsPObjC(type: Any.Type) -> Bool { + return type is PObjC.Type +} +func nongenericAnyIsPAndAnyObject(type: Any.Type) -> Bool { + return type is (P & AnyObject).Type +} +func nongenericAnyIsPAndPCSub(type: Any.Type) -> Bool { + return type is (P & PCSub).Type +} +func genericAnyIs(type: Any.Type, to: T.Type) -> Bool { + return type is T.Type +} +// CHECK-LABEL: casting types to protocols with generics: +print("casting types to protocols with generics:") +print(nongenericAnyIsP(type: PS.self)) // CHECK: true +print(genericAnyIs(type: PS.self, to: P.self)) // CHECK-ONONE: true +print(nongenericAnyIsP(type: PE.self)) // CHECK: true +print(genericAnyIs(type: PE.self, to: P.self)) // CHECK-ONONE: true +print(nongenericAnyIsP(type: PC.self)) // CHECK: true +print(genericAnyIs(type: PC.self, to: P.self)) // CHECK-ONONE: true +print(nongenericAnyIsP(type: PCSub.self)) // CHECK: true +print(genericAnyIs(type: PCSub.self, to: P.self)) // CHECK-ONONE: true + +// CHECK-LABEL: casting types to ObjC protocols with generics: +print("casting types to ObjC protocols with generics:") +print(nongenericAnyIsPObjC(type: PS.self)) // CHECK: false +print(genericAnyIs(type: PS.self, to: PObjC.self)) // CHECK: false +print(nongenericAnyIsPObjC(type: PE.self)) // CHECK: false +print(genericAnyIs(type: PE.self, to: PObjC.self)) // CHECK: false +print(nongenericAnyIsPObjC(type: PC.self)) // CHECK: true +print(genericAnyIs(type: PC.self, to: PObjC.self)) // CHECK-ONONE: true +print(nongenericAnyIsPObjC(type: PCSub.self)) // CHECK: true +print(genericAnyIs(type: PCSub.self, to: PObjC.self)) // CHECK-ONONE: true + +// CHECK-LABEL: casting types to protocol & AnyObject existentials: +print("casting types to protocol & AnyObject existentials:") +print(nongenericAnyIsPAndAnyObject(type: PS.self)) // CHECK: false +print(genericAnyIs(type: PS.self, to: (P & AnyObject).self)) // CHECK: false +print(nongenericAnyIsPAndAnyObject(type: PE.self)) // CHECK: false +print(genericAnyIs(type: PE.self, to: (P & AnyObject).self)) // CHECK: false +print(nongenericAnyIsPAndAnyObject(type: PC.self)) // CHECK: true +print(genericAnyIs(type: PC.self, to: (P & AnyObject).self)) // CHECK-ONONE: true +print(nongenericAnyIsPAndAnyObject(type: PCSub.self)) // CHECK: true +print(genericAnyIs(type: PCSub.self, to: (P & AnyObject).self)) // CHECK-ONONE: true + +// CHECK-LABEL: casting types to protocol & class existentials: +print("casting types to protocol & class existentials:") +print(nongenericAnyIsPAndPCSub(type: PS.self)) // CHECK: false +print(genericAnyIs(type: PS.self, to: (P & PCSub).self)) // CHECK: false +print(nongenericAnyIsPAndPCSub(type: PE.self)) // CHECK: false +print(genericAnyIs(type: PE.self, to: (P & PCSub).self)) // CHECK: false +//print(nongenericAnyIsPAndPCSub(type: PC.self)) // CHECK-SR-11565: false -- FIXME: reenable this when SR-11565 is fixed +print(genericAnyIs(type: PC.self, to: (P & PCSub).self)) // CHECK: false +print(nongenericAnyIsPAndPCSub(type: PCSub.self)) // CHECK: true +print(genericAnyIs(type: PCSub.self, to: (P & PCSub).self)) // CHECK-ONONE: true + + // CHECK-LABEL: type comparisons: print("type comparisons:\n") print(allMetasToAllMetas(Int.self, Int.self)) // CHECK: true From 6a3eecb79c3000a360f0bfaa6ae597b2a09c9e6c Mon Sep 17 00:00:00 2001 From: Mike Ash Date: Fri, 11 Oct 2019 13:15:57 -0400 Subject: [PATCH 3/5] [Test] Split out the @objc bits of the new generic_casts.swift code. rdar://problem/56044443 --- test/Interpreter/generic_casts.swift | 19 ++----------- test/Interpreter/generic_casts_objc.swift | 33 +++++++++++++++++++++++ 2 files changed, 35 insertions(+), 17 deletions(-) create mode 100644 test/Interpreter/generic_casts_objc.swift diff --git a/test/Interpreter/generic_casts.swift b/test/Interpreter/generic_casts.swift index 10b971068f4ce..a218adc5604b6 100644 --- a/test/Interpreter/generic_casts.swift +++ b/test/Interpreter/generic_casts.swift @@ -1,4 +1,4 @@ -// RUN: %target-run-simple-swift | %FileCheck --check-prefix CHECK --check-prefix CHECK-ONONE %s +// RUN: %target-run-simple-swift | %FileCheck --check-prefix CHECK --check-prefix CHECK-ONONE --dump-input fail %s // RUN: %target-build-swift -O %s -o %t/a.out.optimized // RUN: %target-codesign %t/a.out.optimized // RUN: %target-run %t/a.out.optimized | %FileCheck %s @@ -132,18 +132,14 @@ anyClassToCOrE(D()).print() // CHECK: D! anyClassToCOrE(X()).print() // CHECK: E! protocol P {} -@objc protocol PObjC {} struct PS: P {} enum PE: P {} -class PC: P, PObjC {} +class PC: P {} class PCSub: PC {} func nongenericAnyIsP(type: Any.Type) -> Bool { return type is P.Type } -func nongenericAnyIsPObjC(type: Any.Type) -> Bool { - return type is PObjC.Type -} func nongenericAnyIsPAndAnyObject(type: Any.Type) -> Bool { return type is (P & AnyObject).Type } @@ -164,17 +160,6 @@ print(genericAnyIs(type: PC.self, to: P.self)) // CHECK-ONONE: true print(nongenericAnyIsP(type: PCSub.self)) // CHECK: true print(genericAnyIs(type: PCSub.self, to: P.self)) // CHECK-ONONE: true -// CHECK-LABEL: casting types to ObjC protocols with generics: -print("casting types to ObjC protocols with generics:") -print(nongenericAnyIsPObjC(type: PS.self)) // CHECK: false -print(genericAnyIs(type: PS.self, to: PObjC.self)) // CHECK: false -print(nongenericAnyIsPObjC(type: PE.self)) // CHECK: false -print(genericAnyIs(type: PE.self, to: PObjC.self)) // CHECK: false -print(nongenericAnyIsPObjC(type: PC.self)) // CHECK: true -print(genericAnyIs(type: PC.self, to: PObjC.self)) // CHECK-ONONE: true -print(nongenericAnyIsPObjC(type: PCSub.self)) // CHECK: true -print(genericAnyIs(type: PCSub.self, to: PObjC.self)) // CHECK-ONONE: true - // CHECK-LABEL: casting types to protocol & AnyObject existentials: print("casting types to protocol & AnyObject existentials:") print(nongenericAnyIsPAndAnyObject(type: PS.self)) // CHECK: false diff --git a/test/Interpreter/generic_casts_objc.swift b/test/Interpreter/generic_casts_objc.swift new file mode 100644 index 0000000000000..a228d4f18402a --- /dev/null +++ b/test/Interpreter/generic_casts_objc.swift @@ -0,0 +1,33 @@ +// RUN: %target-run-simple-swift | %FileCheck --check-prefix CHECK --check-prefix CHECK-ONONE --dump-input fail %s +// RUN: %target-build-swift -O %s -o %t/a.out.optimized +// RUN: %target-codesign %t/a.out.optimized +// RUN: %target-run %t/a.out.optimized | %FileCheck %s +// REQUIRES: executable_test +// REQUIRES: objc_interop + +import Foundation + +protocol P {} +@objc protocol PObjC {} +struct PS: P {} +enum PE: P {} +class PC: P, PObjC {} +class PCSub: PC {} + +func nongenericAnyIsPObjC(type: Any.Type) -> Bool { + return type is PObjC.Type +} +func genericAnyIs(type: Any.Type, to: T.Type) -> Bool { + return type is T.Type +} + +// CHECK-LABEL: casting types to ObjC protocols with generics: +print("casting types to ObjC protocols with generics:") +print(nongenericAnyIsPObjC(type: PS.self)) // CHECK: false +print(genericAnyIs(type: PS.self, to: PObjC.self)) // CHECK: false +print(nongenericAnyIsPObjC(type: PE.self)) // CHECK: false +print(genericAnyIs(type: PE.self, to: PObjC.self)) // CHECK: false +print(nongenericAnyIsPObjC(type: PC.self)) // CHECK: true +print(genericAnyIs(type: PC.self, to: PObjC.self)) // CHECK-ONONE: true +print(nongenericAnyIsPObjC(type: PCSub.self)) // CHECK: true +print(genericAnyIs(type: PCSub.self, to: PObjC.self)) // CHECK-ONONE: true From 0aab6d236876f0665d16605986b412d26c25c66b Mon Sep 17 00:00:00 2001 From: Mike Ash Date: Fri, 11 Oct 2019 17:25:46 -0400 Subject: [PATCH 4/5] [Test] Remove --dump-input fail from generic_casts.swift. rdar://problem/56044443 --- test/Interpreter/generic_casts.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Interpreter/generic_casts.swift b/test/Interpreter/generic_casts.swift index a218adc5604b6..f01904fd5cb69 100644 --- a/test/Interpreter/generic_casts.swift +++ b/test/Interpreter/generic_casts.swift @@ -1,4 +1,4 @@ -// RUN: %target-run-simple-swift | %FileCheck --check-prefix CHECK --check-prefix CHECK-ONONE --dump-input fail %s +// RUN: %target-run-simple-swift | %FileCheck --check-prefix CHECK --check-prefix CHECK-ONONE %s // RUN: %target-build-swift -O %s -o %t/a.out.optimized // RUN: %target-codesign %t/a.out.optimized // RUN: %target-run %t/a.out.optimized | %FileCheck %s From b05e0099f87984e62ae2b782d06c83c0110a6612 Mon Sep 17 00:00:00 2001 From: Mike Ash Date: Tue, 15 Oct 2019 15:49:03 -0400 Subject: [PATCH 5/5] [Test] Make sure generic_casts.swift tests an unoptimized build of the test even when --test-optimized is passed. rdar://problem/56044443 --- test/Interpreter/generic_casts.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/Interpreter/generic_casts.swift b/test/Interpreter/generic_casts.swift index f01904fd5cb69..0f9c359bdda06 100644 --- a/test/Interpreter/generic_casts.swift +++ b/test/Interpreter/generic_casts.swift @@ -1,4 +1,6 @@ -// RUN: %target-run-simple-swift | %FileCheck --check-prefix CHECK --check-prefix CHECK-ONONE %s +// RUN: %empty-directory(%t) +// RUN: %target-build-swift -Onone %s -o %t/a.out +// RUN: %target-run %t/a.out | %FileCheck --check-prefix CHECK --check-prefix CHECK-ONONE %s // RUN: %target-build-swift -O %s -o %t/a.out.optimized // RUN: %target-codesign %t/a.out.optimized // RUN: %target-run %t/a.out.optimized | %FileCheck %s