[cfe-dev] [PATCH]es: Objective-C lightweight generics

Douglas Gregor dgregor at apple.com
Mon Jul 6 17:06:21 PDT 2015


Hi all,

Last month, Apple introduced a new lightweight generics system into Objective-C. The attached patch series provides the complete implementation of this feature, the related “kindof” types feature, supporting warnings, test-cases, and so on. It should get us back to the point where we can parse the headers for Apple’s latest SDKs.

For those of you wondering what this whole lightweight generics thing is, I suggest either watching the following WWDC video starting at @21:45:

	https://developer.apple.com/videos/wwdc/2015/?id=401 <https://developer.apple.com/videos/wwdc/2015/?id=401>

or keep reading for an explanation.

Lightweight generics allows Objective-C classes to be parameterized, e.g., NSArray can be parameterized on the type of its elements:

	@interface NSArray<ObjectType> : NSObject
	-(ObjectType)objectAtIndex:(NSInteger)index;
	@end

Objective-C classes, categories, extensions, and forward declarations can all specify type parameters. Naturally, the type parameters across different declarations associated with the same class must be consistent.

For a given parameterized class, one can provide type arguments, e.g., to state the type of the elements of an NSArray:

	NSArray<NSString *> *strings;
	NSArray<NSNumber *> *numbers;

Naturally, messaging an object of specialized type substitutes the type arguments for the type parameters, e.g.,

	[strings objectAtIndex: 0]; // produces an NSString *
	[numbers objectAtIndex: 0]; // produces an NSNumber *

One can, of course, leave the type arguments unspecified:

	NSArray *array;

Each of the types NSArray<NSString *>, NSArray<NSNumber *>, and NSArray are distinct types. There are implicit conversions between the “specialized” types (NSArray<NSString *>, NSArray<NSNumber *>) and the “unspecialized” type (NSArray *), but not between different specialized types. For example:

	array = strings; // okay, dropping type arguments
	numbers = array; // okay, adding type arguments
	strings = numbers; // not okay! NSArray<NSString *> and NSArray<NSNumber *> are incompatible types

The “lightweight” in lightweight generics comes from the implementation model, which is based on type erasure (a la Java Generics). All of the type argument information is completely erased by IR generation, so this feature requires no runtime or metadata changes. Obviously, this—along with the implicit conversion rules above—means that some "obvious” errors will be missed by the type checker (and won’t be enforced by the runtime). For example, the sequence “array = strings; numbers = array;” is obviously bogus: the elements cannot be both NSStrings and NSNumbers. Sema won’t catch this, just like Sema wouldn’t catch an assignment chain that converts an NSNumber to an NSString by way of ‘id’.

Type parameters have a few advanced features. First of all, they can have upper bounds, such that any type argument must be a subtype of the upper bound of the corresponding type parameter. For example:

	@interface MyMap<KeyType : id<NSCopying>, ObjectType>
	-(KeyType)firstKeyForObject:(ObjectType)object; // yes, this is a weird example; explanation below
	@end

It’s an error to form a MyMap<T, U> where T isn’t a subtype of id<NSCopying>. When the type bound is omitted, it is “id”, i.e., all type parameters/arguments are all Objective-C objects. Type bounds are also important when using unspecialized types (e.g., MyMap *); we’ll come back to that later.

Type parameters can also be co- or contra-variant, similarly to C#’s generic type parameters (see, e.g., https://msdn.microsoft.com/en-us/library/dd799517(v=vs.110).aspx) <https://msdn.microsoft.com/en-us/library/dd799517(v=vs.110).aspx)>. This is particularly useful for the immutable collection classes, which we’ll use for exposition purposes. NSArray is actually declared as:

	@interface NSArray<__covariant ObjectType> : NSObject
	-(ObjectType)objectAtIndex:(NSInteger)index;
	@end

Because ObjectType is covariant, NSArray<T> is implicitly convertible to NSArray<U> when T is a subtype of U, which allows, e.g.,

	NSArray<NSMutableString *> *mutStrings;
	NSArray<NSString *> *strings;
	strings = mutStrings; // okay, because ObjectType is covariant (and NSArray is an immutable collection)

Contravariant type parameters go the opposite way, e.g., NSArray<T> is implicitly convertible to NSArray<U> when U is a subtype of T.

Objective-C lightweight generics also involves a related feature called “kindof” types, which strike a balance between “id” and specific Objective-C pointer types. For example, given:

	__kindof NSString *kindofStr;

we can implicitly convert to supertypes *and* subtypes, but not unrelated types. For example:

	NSObject *object = kindofStr; // okay, implicitly converting to supertype
	NSMutableString *mutString = kindofStr; // okay, implicitly converting to subtype
	NSNumber *number = kindofStr; // not okay! NSString and NSNumber are incompatible

Naturally, the features compose: one can create an array of kindof types, e.g.,

	NSArray<__kindof NSValue *> *values;

which can be *very* helpful when adopting lightweight generics. Kindof types are also important when working with unspecialized types. For example, let’s invent a class that only wants to work with subclasses of ‘View’:

	@interface SomeClass<T : View *> // T must be View or a subclass thereof
	- (T)view;
	@end

As noted earlier, when we message SomeClass, we substitute in the type arguments for the type parameters, e.g.,

	SomeClass<Button *> *sc;
	[sc view]; // produces a "Button *"

But what happens if we don’t have type arguments, because we’re messaging an object of type ’SomeClass *’? We produce a __kindoftype from the type bound, which gives us good static type information without forcing the user to introduce a large number of casts:

	SomeClass *sc;
	[sc view]; // produces a "__kindof View *"


That’s most of it! I’ll be happy to answer any questions. Actual coherent documentation is forthcoming, but we wanted to get this implementation out there for people to play with.

The patches are fairly large, since this is a nontrivial feature. I’ve broken it up into logical pieces, which are, roughly:

	1) Type parameter parsing, ASTs, etc.
	2) Type argument handling
	3) Substitution of type arguments for parameters when using specialized types
	4) Reworking our handling of the ternary operator on Objective-C pointer types
	5) Interaction between C++ templates and Objective-C lightweight generics
	6/7) Warnings for lightweight generics
	8) Kindof types
	9) Co- and contra-variant type parameters
	10) Annoying workaround for old Clang versions that apparently still need to build with

Detailed patch review should go to cfe-commits, so we can keep the discussion here more high-level.

	- Doug

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/cfe-dev/attachments/20150706/1245545a/attachment.html>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: 0001-Parsing-semantic-analysis-and-AST-for-Objective-C-ty.patch
Type: application/octet-stream
Size: 103810 bytes
Desc: not available
URL: <http://lists.llvm.org/pipermail/cfe-dev/attachments/20150706/1245545a/attachment.obj>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/cfe-dev/attachments/20150706/1245545a/attachment-0001.html>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: 0002-Handle-Objective-C-type-arguments.patch
Type: application/octet-stream
Size: 128178 bytes
Desc: not available
URL: <http://lists.llvm.org/pipermail/cfe-dev/attachments/20150706/1245545a/attachment-0001.obj>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/cfe-dev/attachments/20150706/1245545a/attachment-0002.html>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: 0003-Substitute-type-arguments-into-uses-of-Objective-C-i.patch
Type: application/octet-stream
Size: 118046 bytes
Desc: not available
URL: <http://lists.llvm.org/pipermail/cfe-dev/attachments/20150706/1245545a/attachment-0002.obj>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/cfe-dev/attachments/20150706/1245545a/attachment-0003.html>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: 0004-Improve-the-Objective-C-common-type-computation-used.patch
Type: application/octet-stream
Size: 23044 bytes
Desc: not available
URL: <http://lists.llvm.org/pipermail/cfe-dev/attachments/20150706/1245545a/attachment-0003.obj>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/cfe-dev/attachments/20150706/1245545a/attachment-0004.html>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: 0005-C-support-for-Objective-C-lightweight-generics.patch
Type: application/octet-stream
Size: 110128 bytes
Desc: not available
URL: <http://lists.llvm.org/pipermail/cfe-dev/attachments/20150706/1245545a/attachment-0004.obj>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/cfe-dev/attachments/20150706/1245545a/attachment-0005.html>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: 0006-Warn-when-an-Objective-C-collection-literal-element-.patch
Type: application/octet-stream
Size: 8866 bytes
Desc: not available
URL: <http://lists.llvm.org/pipermail/cfe-dev/attachments/20150706/1245545a/attachment-0005.obj>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/cfe-dev/attachments/20150706/1245545a/attachment-0006.html>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: 0007-Warn-when-an-intended-Objective-C-specialization-was.patch
Type: application/octet-stream
Size: 12024 bytes
Desc: not available
URL: <http://lists.llvm.org/pipermail/cfe-dev/attachments/20150706/1245545a/attachment-0006.obj>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/cfe-dev/attachments/20150706/1245545a/attachment-0007.html>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: 0008-Implement-the-Objective-C-__kindof-type-qualifier.patch
Type: application/octet-stream
Size: 70546 bytes
Desc: not available
URL: <http://lists.llvm.org/pipermail/cfe-dev/attachments/20150706/1245545a/attachment-0007.obj>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/cfe-dev/attachments/20150706/1245545a/attachment-0008.html>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: 0009-Implement-variance-for-Objective-C-type-parameters.patch
Type: application/octet-stream
Size: 33539 bytes
Desc: not available
URL: <http://lists.llvm.org/pipermail/cfe-dev/attachments/20150706/1245545a/attachment-0008.obj>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/cfe-dev/attachments/20150706/1245545a/attachment-0009.html>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: 0010-Factor-the-simpleTransform-visitor-so-that-it-is-not.patch
Type: application/octet-stream
Size: 27121 bytes
Desc: not available
URL: <http://lists.llvm.org/pipermail/cfe-dev/attachments/20150706/1245545a/attachment-0009.obj>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/cfe-dev/attachments/20150706/1245545a/attachment-0010.html>


More information about the cfe-dev mailing list