Skip to content

Commit 97b0644

Browse files
authored
Always desugar the base type of an extension when serializing. (#6336)
This "fixes" two issues: - The name of a non-public typealias would leak into the public interface if the extension had any public members. - A common pattern of defining a platform-specific typealias for an imported class and then extending that type would lead to circularity when trying to deserialize the typealias. We /shouldn't/ be loading the extension at that point, but fixing that would be much harder. The "right" answer is to (a) check that the typealias is public if the extension has any public members, and (b) somehow ensure there is no circularity issue (either by not importing the extension as a result of importing the typealias, or by the extension being able to set its sugared base type later). rdar://problem/29694978
1 parent 3e00d00 commit 97b0644

File tree

4 files changed

+101
-2
lines changed

4 files changed

+101
-2
lines changed

lib/Serialization/Serialization.cpp

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2242,9 +2242,20 @@ void Serializer::writeDecl(const Decl *D) {
22422242
auto contextID = addDeclContextRef(extension->getDeclContext());
22432243
Type baseTy = extension->getExtendedType();
22442244

2245+
// FIXME: Use the canonical type here in order to minimize circularity
2246+
// issues at deserialization time. A known problematic case here is
2247+
// "extension of typealias Foo"; "typealias Foo = SomeKit.Bar"; and then
2248+
// trying to import Bar accidentally asking for all of its extensions
2249+
// (perhaps because we're searching for a conformance).
2250+
//
2251+
// We could limit this to only the problematic cases, but it seems like a
2252+
// simpler user model to just always desugar extension types.
2253+
baseTy = baseTy->getCanonicalType();
2254+
22452255
// Make sure the base type has registered itself as a provider of generic
22462256
// parameters.
2247-
(void)addDeclRef(baseTy->getAnyNominal());
2257+
auto baseNominal = baseTy->getAnyNominal();
2258+
(void)addDeclRef(baseNominal);
22482259

22492260
auto conformances = extension->getLocalConformances(
22502261
ConformanceLookupKind::All,
@@ -2265,7 +2276,7 @@ void Serializer::writeDecl(const Decl *D) {
22652276
inheritedTypes);
22662277

22672278
bool isClassExtension = false;
2268-
if (auto baseNominal = baseTy->getAnyNominal()) {
2279+
if (baseNominal) {
22692280
isClassExtension = isa<ClassDecl>(baseNominal) ||
22702281
isa<ProtocolDecl>(baseNominal);
22712282
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// RUN: rm -rf %t && mkdir -p %t
2+
// RUN: %target-swift-frontend -emit-module -module-name Library -o %t -D LIBRARY %s
3+
// RUN: %target-swift-ide-test -print-module -module-to-print=Library -I %t -source-filename=%s | %FileCheck %s
4+
// RUN: %target-swift-frontend -typecheck -I %t %s -verify
5+
6+
// Check that base types of extensions are desugared. This isn't necessarily
7+
// the behavior we want long-term, but it's the behavior we need right now.
8+
9+
#if LIBRARY
10+
11+
public typealias Zahl = Int
12+
13+
// CHECK-LABEL: extension Int {
14+
extension Zahl {
15+
// CHECK-NEXT: addedMember()
16+
public func addedMember() {}
17+
} // CHECK-NEXT: {{^}$}}
18+
19+
public typealias List<T> = Array<T>
20+
21+
// CHECK-LABEL: extension Array {
22+
extension List {
23+
// CHECK-NEXT: addedMember()
24+
public func addedMember() {}
25+
} // CHECK-NEXT: {{^}$}}
26+
27+
// CHECK-LABEL: extension Array where Element == Int {
28+
extension List where Element == Int {
29+
// CHECK-NEXT: addedMemberInt()
30+
public func addedMemberInt() {}
31+
} // CHECK-NEXT: {{^}$}}
32+
33+
// CHECK: typealias List
34+
// CHECK: typealias Zahl
35+
36+
#else
37+
38+
import Library
39+
40+
func test(x: Int) {
41+
x.addedMember()
42+
[x].addedMember()
43+
[x].addedMemberInt()
44+
([] as [Bool]).addedMemberInt() // expected-error {{'[Bool]' is not convertible to 'Array<Int>'}}
45+
}
46+
47+
#endif
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
@import Foundation;
2+
3+
@protocol BaseProto
4+
@end
5+
6+
@protocol SubProto <BaseProto>
7+
@end
8+
9+
@interface NonGenericType: NSObject <SubProto>
10+
@end
11+
12+
@interface GenericType<Element>: NSObject <SubProto>
13+
@end
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// RUN: rm -rf %t && mkdir -p %t
2+
// RUN: %target-build-swift %s -import-objc-header %S/Inputs/rdar29694978.h -emit-module -o %t/Library.swiftmodule
3+
// RUN: %target-swift-ide-test -print-module -module-to-print=Library -source-filename=x -I %S/Inputs/ -I %t | %FileCheck %s
4+
5+
// REQUIRES: objc_interop
6+
7+
// This would be wonderful to fold into a larger test, but I'm worried that
8+
// trying to do so would perturb the original conditions that cause it to
9+
// crash: deserializing a typealias (1) to an imported Objective-C type (2)
10+
// that has a protocol (3) that refines another protocol (4) which causes us
11+
// to load all extensions, including one defined in this file (5) in terms of
12+
// the original typealias (1).
13+
//
14+
// It's probably best to just leave this test about that.
15+
16+
import Foundation
17+
18+
// CHECK-DAG: typealias MyNonGenericType = NonGenericType
19+
typealias MyNonGenericType = NonGenericType
20+
// CHECK-DAG: extension NonGenericType
21+
extension MyNonGenericType {}
22+
23+
// CHECK-DAG: typealias MyGenericType<T> = GenericType<T>
24+
typealias MyGenericType<T: NSObject> = GenericType<T>
25+
// CHECK-DAG: extension GenericType where Element : AnyObject
26+
extension MyGenericType {}
27+
// CHECK-DAG: extension GenericType where Element == NSObject
28+
extension MyGenericType where Element == NSObject {}

0 commit comments

Comments
 (0)