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

David Chisnall David.Chisnall at cl.cam.ac.uk
Tue Jul 7 00:48:33 PDT 2015


Hi Doug,

Would there be interest in a compilation mode that inserted run-time checks for these cases?  We discussed previously doing it on every down cast and cast-from-id in Objective-C, but came to the conclusion that the false positive rate would be too high.  For generics, I suspect that people either want to not break the rules, or will just use the non-generic version.

Adding an objc_assert_type(id, Class) runtime function (and variations for co/contravariant generic types) and a call on every parameter/return value for a type-generic method would be fairly simple and I’d be happy to put together a patch if this is something that would be of interest.

The patch series seems to be missing a __has_feature() flag to detect for lightweight generics support.

David

> On 7 Jul 2015, at 01:06, Douglas Gregor <dgregor at apple.com> wrote:
> 
> 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
> 
> 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). 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
> 
> <0001-Parsing-semantic-analysis-and-AST-for-Objective-C-ty.patch>
> <0002-Handle-Objective-C-type-arguments.patch>
> <0003-Substitute-type-arguments-into-uses-of-Objective-C-i.patch>
> <0004-Improve-the-Objective-C-common-type-computation-used.patch>
> <0005-C-support-for-Objective-C-lightweight-generics.patch>
> <0006-Warn-when-an-Objective-C-collection-literal-element-.patch>
> <0007-Warn-when-an-intended-Objective-C-specialization-was.patch>
> <0008-Implement-the-Objective-C-__kindof-type-qualifier.patch>
> <0009-Implement-variance-for-Objective-C-type-parameters.patch>
> <0010-Factor-the-simpleTransform-visitor-so-that-it-is-not.patch>
> _______________________________________________
> cfe-dev mailing list
> cfe-dev at cs.uiuc.edu
> http://lists.cs.uiuc.edu/mailman/listinfo/cfe-dev





More information about the cfe-dev mailing list