Skip to content

Commit af6e9b9

Browse files
committed
Work around extension installation order
Bridging headers are weird. A general principle in the Clang Importer is that when it comes to the notion of "overrides" or "redeclarations", it's always better to prefer a declaration in the same translation unit, module, header, etc. over a declaration outside of that thing. This behavior, unfortunately, occurred as an emergent property of the re-entrant lookup that was being performed when importing properties. The behavior of the visible categories list is in "deserialization order" - that is, a category defined in a header that imports a module can potentially appear before the module has had its declarations fully deserialized and installed. We were assuming this order was instead "source order", in which modules would be recursively expanded and their declarations installed sequentially. If this assumption were to hold, then the visible categories list would respect the principle above, and we would see categories in defining modules and headers before we saw categories in extant translation units. This is not the case. Delayed deserialization of modules means the first category that gets parsed actually wins. We were not noticing this, however, because the few places where it actually mattered were performing re-entrant lookup and were accidentally deserializing modules and installing decls in this fictitious "source order". Since we've untangled this behavior, we now have to emulate the order the Importer expects. Introduce the notion of a "delayed category" - a category that is defined outside of a module, usually by us. As the name implies, we'll try to load the categories defined in modules first, then go after the delayed categories defined in extant translation units. This gives us a stable "source order", preserves the Principle of Closest Definition, and undoes the behavior change for overriden property declarations in the previous commit.
1 parent 4f04a80 commit af6e9b9

File tree

2 files changed

+30
-15
lines changed

2 files changed

+30
-15
lines changed

lib/ClangImporter/ClangImporter.cpp

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2962,12 +2962,36 @@ void ClangImporter::loadExtensions(NominalTypeDecl *nominal,
29622962
// For an Objective-C class, import all of the visible categories.
29632963
if (auto objcClass = dyn_cast_or_null<clang::ObjCInterfaceDecl>(
29642964
effectiveClangContext.getAsDeclContext())) {
2965+
SmallVector<clang::NamedDecl *, 4> DelayedCategories;
2966+
29652967
// Simply importing the categories adds them to the list of extensions.
29662968
for (auto I = objcClass->visible_categories_begin(),
29672969
E = objcClass->visible_categories_end();
29682970
I != E; ++I) {
2971+
// Delay installing categories that don't have an owning module.
2972+
if (!I->getOwningModule()) {
2973+
DelayedCategories.push_back(*I);
2974+
continue;
2975+
}
2976+
29692977
Impl.importDeclReal(*I, Impl.CurrentVersion);
29702978
}
2979+
2980+
// Install all the delayed categories.
2981+
//
2982+
// The very notion of a delayed category is a result of an emergent behavior
2983+
// of the visible categories list and the order we import modules. The list
2984+
// appears in deserialization order rather than some "source order", so it's
2985+
// possible for, say, a bridging header to import a module that defines an
2986+
// interface and some categories, but for the categories in the bridging
2987+
// header to appear *before* the categories in the module - the imported
2988+
// module will be deserialized on demand. We take it on faith that if there
2989+
// is no owning module for a given category, that it was created in such a
2990+
// way, and thus we install it last to try to emulate what we want
2991+
// "source order" to mean.
2992+
for (const auto *DelayedCat : DelayedCategories) {
2993+
Impl.importDeclReal(DelayedCat, Impl.CurrentVersion);
2994+
}
29712995
}
29722996

29732997
// Dig through each of the Swift lookup tables, creating extensions

test/ClangImporter/objc_redeclared_properties_incompatible.swift

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -71,40 +71,31 @@ func testGenericWithoutInitializer(obj: PropertiesNoInitGeneric<Base>) {
7171
// CHECK-PUBLIC: [[@LINE-1]]:7: error: cannot assign to property: 'readwriteChange' is a get-only property
7272
}
7373

74-
// N.B. We consider the category in the imported header Private.h as the
75-
// canonical declaration of these properties and drop the one in the framework.
76-
7774
func testCategoryWithInitializer() {
7875
let obj = PropertiesInitCategory()
7976

8077
let _: Int = obj.nullabilityChange
81-
// CHECK-PUBLIC: [[@LINE-1]]:20: error: cannot convert value of type 'Base' to specified type 'Int'
82-
// CHECK-PRIVATE: [[@LINE-2]]:20: error: cannot convert value of type 'Base?' to specified type 'Int'
78+
// CHECK: [[@LINE-1]]:20: error: cannot convert value of type 'Base' to specified type 'Int'
8379

8480
let _: Int = obj.missingGenerics
85-
// CHECK-PUBLIC: [[@LINE-1]]:20: error: cannot convert value of type 'GenericClass<Base>' to specified type 'Int'
86-
// CHECK-PRIVATE: [[@LINE-2]]:20: error: cannot convert value of type 'GenericClass<AnyObject>' to specified type 'Int'
81+
// CHECK: [[@LINE-1]]:20: error: cannot convert value of type 'GenericClass<Base>' to specified type 'Int'
8782

8883
let _: Int = obj.typeChange
89-
// CHECK-PUBLIC: [[@LINE-1]]:20: error: cannot convert value of type 'Base' to specified type 'Int'
90-
// CHECK-PRIVATE: [[@LINE-2]]:20: error: cannot convert value of type 'PrivateSubclass' to specified type 'Int'
84+
// CHECK: [[@LINE-1]]:20: error: cannot convert value of type 'Base' to specified type 'Int'
9185

9286
obj.readwriteChange = Base() // CHECK-PRIVATE-NOT: [[@LINE]]:{{.+}}: error
9387
// CHECK-PUBLIC: [[@LINE-1]]:7: error: cannot assign to property: 'readwriteChange' is a get-only property
9488
}
9589

9690
func testCategoryWithoutInitializer(obj: PropertiesNoInitCategory) {
9791
let _: Int = obj.nullabilityChange
98-
// CHECK-PUBLIC: [[@LINE-1]]:20: error: cannot convert value of type 'Base' to specified type 'Int'
99-
// CHECK-PRIVATE: [[@LINE-2]]:20: error: cannot convert value of type 'Base?' to specified type 'Int'
92+
// CHECK: [[@LINE-1]]:20: error: cannot convert value of type 'Base' to specified type 'Int'
10093

10194
let _: Int = obj.missingGenerics
102-
// CHECK-PUBLIC: [[@LINE-1]]:20: error: cannot convert value of type 'GenericClass<Base>' to specified type 'Int'
103-
// CHECK-PRIVATE: [[@LINE-2]]:20: error: cannot convert value of type 'GenericClass<AnyObject>' to specified type 'Int'
95+
// CHECK: [[@LINE-1]]:20: error: cannot convert value of type 'GenericClass<Base>' to specified type 'Int'
10496

10597
let _: Int = obj.typeChange
106-
// CHECK-PUBLIC: [[@LINE-1]]:20: error: cannot convert value of type 'Base' to specified type 'Int'
107-
// CHECK-PRIVATE: [[@LINE-2]]:20: error: cannot convert value of type 'PrivateSubclass' to specified type 'Int'
98+
// CHECK: [[@LINE-1]]:20: error: cannot convert value of type 'Base' to specified type 'Int'
10899

109100
obj.readwriteChange = Base() // CHECK-PRIVATE-NOT: [[@LINE]]:{{.+}}: error
110101
// CHECK-PUBLIC: [[@LINE-1]]:7: error: cannot assign to property: 'readwriteChange' is a get-only property

0 commit comments

Comments
 (0)