Skip to content

Commit 9c5564f

Browse files
committed
Allow subclasses of specific instantiations of ObjC generic classes.
If a subclass grounds all the type parameters from its base class, we don't have to worry about any erasure edge cases. We should be able to support this, giving existing code that subclasses the nongeneric form of the class a migration path. Spot-fix some places in IRGen where we assume we can't emit static references to ObjC generic classes or metaclasses.
1 parent 737155b commit 9c5564f

File tree

8 files changed

+118
-33
lines changed

8 files changed

+118
-33
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1457,8 +1457,9 @@ NOTE(class_here,none,
14571457
"class %0 declared here", (Identifier))
14581458
ERROR(inheritance_from_final_class,none,
14591459
"inheritance from a final class %0", (Identifier))
1460-
ERROR(inheritance_from_objc_generic_class,none,
1461-
"inheritance from a generic Objective-C class %0", (Identifier))
1460+
ERROR(inheritance_from_unspecialized_objc_generic_class,none,
1461+
"inheritance from a generic Objective-C class %0 must bind "
1462+
"type parameters of %0 to specific concrete types", (Identifier))
14621463
ERROR(inheritance_from_objc_runtime_visible_class,none,
14631464
"inheritance from an Objective-C class %0 only visible via the runtime", (Identifier))
14641465

lib/IRGen/GenClass.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -857,7 +857,7 @@ namespace {
857857
}
858858

859859
llvm::Constant *getMetaclassRefOrNull(ClassDecl *theClass) {
860-
if (theClass->isGenericContext()) {
860+
if (theClass->isGenericContext() && !theClass->hasClangNode()) {
861861
return llvm::ConstantPointerNull::get(IGM.ObjCClassPtrTy);
862862
} else {
863863
return IGM.getAddrOfMetaclassObject(theClass, NotForDefinition);

lib/IRGen/GenDecl.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2254,7 +2254,7 @@ llvm::Constant *IRGenModule::getAddrOfSwiftMetaclassStub(ClassDecl *theClass,
22542254
/// on whether the class is published as an ObjC class.
22552255
llvm::Constant *IRGenModule::getAddrOfMetaclassObject(ClassDecl *decl,
22562256
ForDefinition_t forDefinition) {
2257-
assert(!decl->isGenericContext()
2257+
assert((!decl->isGenericContext() || decl->hasClangNode())
22582258
&& "generic classes do not have a static metaclass object");
22592259
if (decl->checkObjCAncestry() != ObjCClassKind::NonObjC ||
22602260
decl->hasClangNode()) {

lib/IRGen/GenMeta.cpp

Lines changed: 28 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,31 @@ static void emitPolymorphicParametersFromArray(IRGenFunction &IGF,
241241
requirements.bindFromBuffer(IGF, array, getInContext);
242242
}
243243

244+
static bool isTypeErasedGenericClass(NominalTypeDecl *ntd) {
245+
// ObjC classes are type erased.
246+
// TODO: Unless they have magic methods...
247+
if (auto clas = dyn_cast<ClassDecl>(ntd))
248+
return clas->hasClangNode() && clas->isGenericContext();
249+
return false;
250+
}
251+
252+
static bool isTypeErasedGenericClassType(CanType type) {
253+
if (auto nom = type->getAnyNominal())
254+
return isTypeErasedGenericClass(nom);
255+
return false;
256+
}
257+
258+
// Get the type that exists at runtime to represent a compile-time type.
259+
static CanType
260+
getRuntimeReifiedType(IRGenModule &IGM, CanType type) {
261+
return CanType(type.transform([&](Type t) -> Type {
262+
if (isTypeErasedGenericClassType(CanType(t))) {
263+
return t->getAnyNominal()->getDeclaredType()->getCanonicalType();
264+
}
265+
return t;
266+
}));
267+
}
268+
244269
/// Attempts to return a constant heap metadata reference for a
245270
/// class type. This is generally only valid for specific kinds of
246271
/// ObjC reference, like superclasses or category references.
@@ -257,8 +282,9 @@ llvm::Constant *irgen::tryEmitConstantHeapMetadataRef(IRGenModule &IGM,
257282
return nullptr;
258283

259284
// Otherwise, just respect genericity and Objective-C runtime visibility.
260-
} else if (theDecl->isGenericContext() ||
261-
theDecl->isOnlyObjCRuntimeVisible()) {
285+
} else if ((theDecl->isGenericContext()
286+
&& !isTypeErasedGenericClass(theDecl))
287+
|| theDecl->isOnlyObjCRuntimeVisible()) {
262288
return nullptr;
263289
}
264290

@@ -1324,31 +1350,6 @@ static llvm::Value *emitTypeMetadataAccessFunctionBody(IRGenFunction &IGF,
13241350
using MetadataAccessGenerator =
13251351
llvm::function_ref<llvm::Value*(IRGenFunction &IGF, llvm::Constant *cache)>;
13261352

1327-
static bool isTypeErasedGenericClass(NominalTypeDecl *ntd) {
1328-
// ObjC classes are type erased.
1329-
// TODO: Unless they have magic methods...
1330-
if (auto clas = dyn_cast<ClassDecl>(ntd))
1331-
return clas->hasClangNode() && clas->isGenericContext();
1332-
return false;
1333-
}
1334-
1335-
static bool isTypeErasedGenericClassType(CanType type) {
1336-
if (auto nom = type->getAnyNominal())
1337-
return isTypeErasedGenericClass(nom);
1338-
return false;
1339-
}
1340-
1341-
// Get the type that exists at runtime to represent a compile-time type.
1342-
static CanType
1343-
getRuntimeReifiedType(IRGenModule &IGM, CanType type) {
1344-
return CanType(type.transform([&](Type t) -> Type {
1345-
if (isTypeErasedGenericClassType(CanType(t))) {
1346-
return t->getAnyNominal()->getDeclaredType()->getCanonicalType();
1347-
}
1348-
return t;
1349-
}));
1350-
}
1351-
13521353
/// Get or create an accessor function to the given non-dependent type.
13531354
static llvm::Function *getTypeMetadataAccessFunction(IRGenModule &IGM,
13541355
CanType type,

lib/Sema/TypeCheckDecl.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3836,8 +3836,10 @@ class DeclChecker : public DeclVisitor<DeclChecker> {
38363836
return;
38373837
}
38383838

3839-
if (Super->hasClangNode() && Super->getGenericParams()) {
3840-
TC.diagnose(CD, diag::inheritance_from_objc_generic_class,
3839+
if (Super->hasClangNode() && Super->getGenericParams()
3840+
&& superclassTy->hasTypeParameter()) {
3841+
TC.diagnose(CD,
3842+
diag::inheritance_from_unspecialized_objc_generic_class,
38413843
Super->getName());
38423844
}
38433845

test/ClangModules/objc_bridging_generics.swift

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,3 +129,33 @@ extension GenericClass {
129129
_ = #selector(GenericClass.doesntUseGenericParam3)
130130
}
131131
}
132+
133+
// expected-error@+1{{inheritance from a generic Objective-C class 'GenericClass' must bind type parameters of 'GenericClass' to specific concrete types}}
134+
class SwiftGenericSubclassA<X: AnyObject>: GenericClass<X> {}
135+
// expected-error@+1{{inheritance from a generic Objective-C class 'GenericClass' must bind type parameters of 'GenericClass' to specific concrete types}}
136+
class SwiftGenericSubclassB<X: AnyObject>: GenericClass<GenericClass<X>> {}
137+
// expected-error@+1{{inheritance from a generic Objective-C class 'GenericClass' must bind type parameters of 'GenericClass' to specific concrete types}}
138+
class SwiftGenericSubclassC<X: NSCopying>: GenericClass<X> {}
139+
140+
class SwiftConcreteSubclassA: GenericClass<AnyObject> {
141+
override init(thing: AnyObject) { }
142+
override func thing() -> AnyObject? { }
143+
override func count() -> Int32 { }
144+
override class func classThing() -> AnyObject? { }
145+
override func arrayOfThings() -> [AnyObject] {}
146+
}
147+
class SwiftConcreteSubclassB: GenericClass<NSString> {
148+
override init(thing: NSString) { }
149+
override func thing() -> NSString? { }
150+
override func count() -> Int32 { }
151+
override class func classThing() -> NSString? { }
152+
override func arrayOfThings() -> [NSString] {}
153+
}
154+
class SwiftConcreteSubclassC<T>: GenericClass<NSString> {
155+
override init(thing: NSString) { }
156+
override func thing() -> NSString? { }
157+
override func count() -> Int32 { }
158+
override class func classThing() -> NSString? { }
159+
override func arrayOfThings() -> [NSString] {}
160+
}
161+

test/IRGen/objc_generic_class_metadata.sil

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,14 @@ import Swift
88
import Foundation
99
import objc_generics
1010

11+
// CHECK-LABEL: @"OBJC_METACLASS_$__TtC27objc_generic_class_metadata8Subclass" = hidden global %objc_class
12+
// CHECK: %objc_class* @"OBJC_METACLASS_$_NSObject"
13+
// CHECK: %objc_class* @"OBJC_METACLASS_$_GenericClass"
14+
// CHECK-LABEL: @_TMfC27objc_generic_class_metadata8Subclass = internal global
15+
// CHECK: %objc_class* @"OBJC_METACLASS_$__TtC27objc_generic_class_metadata8Subclass"
16+
// CHECK: %objc_class* @"OBJC_CLASS_$_GenericClass"
17+
class Subclass: GenericClass<NSString> {}
18+
1119
sil @metatype_sink : $@convention(thin) <T> (@thick T.Type) -> ()
1220
sil @objc_class_sink : $@convention(thin) <T: AnyObject> (@objc_metatype T.Type) -> ()
1321

test/Interpreter/imported_objc_generics.swift

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,4 +146,47 @@ ImportedObjCGenerics.test("InheritanceFromNongeneric") {
146146
expectTrue(Container<NSObject>.self == Container<NSString>.self)
147147
}
148148

149+
public class InheritInSwift: Container<NSString> {
150+
public override init(object: NSString) {
151+
super.init(object: object.lowercase)
152+
}
153+
public override var object: NSString {
154+
get {
155+
return super.object.uppercase
156+
}
157+
set {
158+
super.object = newValue.lowercase
159+
}
160+
}
161+
162+
public var superObject: NSString {
163+
get {
164+
return super.object
165+
}
166+
}
167+
}
168+
169+
ImportedObjCGenerics.test("InheritInSwift") {
170+
let s = InheritInSwift(object: "HEllo")
171+
let sup: Container = s
172+
173+
expectEqual(s.superObject, "hello")
174+
expectEqual(s.object, "HELLO")
175+
expectEqual(sup.object, "HELLO")
176+
177+
s.object = "GOodbye"
178+
expectEqual(s.superObject, "goodbye")
179+
expectEqual(s.object, "GOODBYE")
180+
expectEqual(sup.object, "GOODBYE")
181+
182+
s.processObject { o in
183+
expectEqual(o, "GOODBYE")
184+
}
185+
186+
s.updateObject { "Aloha" }
187+
expectEqual(s.superObject, "aloha")
188+
expectEqual(s.object, "ALOHA")
189+
expectEqual(sup.object, "ALOHA")
190+
}
191+
149192
runAllTests()

0 commit comments

Comments
 (0)