`@property (nonatomic, readonly, weak) NSArray<Link *> *links` is imported as [Any]

Is it a known bug that ObjC @property (nonatomic, readonly, weak) NSArray<Link *> *links is imported as [Any] instead of [Link] in Swift?

This issue only happens when the property is weak.

I don’t think that’s a bug. To import this as an Array you’d need something like:


weak var links: [Link]

but weak is restricted to classes, and Array is not a class.

Also, regarding this:

is imported as [Any]

when I tested this myself (Xcode 26.0.1) I saw it imported as weak var links: NSArray?, not [Any].

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

2 Likes

You are right. And because lightweight generic cannot be imported as real generic, we don’t have NSArray<Link>, so can only access elements as Any.

However, a very insidious issue is this:

if let firstLink = foo.links?.first as? Link {
  // do something useful
}

This casting fails! Because somehow, after bridging foo.links to [Array], the first is resolved to func first(where: (Self.Element) throws -> Bool) rethrows -> Self.Element? instead of the expected var first: Self.Element?. This I believe is a real bug.

There is a missing feature here (specifically, a warning when trying to cast a value of function type to a value of a concrete non-function type), but this is actually the language behaving as expected. Your code is not actually bridging to a Swift array (which would be (foo.links as [Any]?)?.first as? Link, I think), it’s still accessing the actual NSArray instance. Per the docs, NSArray only conforms to Sequence and not Collection. Collection is the protocol that exposes var first: Element?, while Sequence only exposes the first(where:) method. To access the first element of an NSArray without bridging, you can use the firstObject property.

3 Likes

You are right. I later realized that first comes from Sequence because when I tried last I got compiling error.

However, this is truly an issue

There is a missing feature here (specifically, a warning when trying to cast a value of function type to a value of a concrete non-function type)

In fact, after I broken down the optional chaining into

if let foo, let links = foo.links, let firstLink = links.first as? Link

I did got a compiling error:

cast from ' ((NSArray. Element)
throws
Bool)
throws →> NSArray Element?' (aka '((Any) throws -> Bool) throws → Optional<Any>') to unrelated type 'Link' always fails

I think this error should also appear in

if let firstLink = foo?.links?.first as? Link
1 Like

Agreed — definitely worth opening an issue about. I suspect the issue here is that you’re trying to cast an optional function type to another type, and the type checker doesn’t realize that is also impossible.