ambiguous reference to member ‘joined()’

You can readily tell that Swift was created by a C++ fanatic, by its fucking obtuse error messages.

⤹ Me             Swift compiler ⤵︎
"What do you want" scene from The Notebook

In today’s episode of “what the fuck do you want, compiler?”, we tackle:

foo.swift:186:39: error: ambiguous reference to member 'joined()'
       log.debug("\(thingies.joined(separator: ", "))")
Swift.BidirectionalCollection:27:17: note: found this candidate
 public func joined() -> FlattenBidirectionalCollection<Self>
Swift.Sequence:27:17: note: found this candidate
 public func joined() -> FlattenSequence<Self>
Swift.Sequence:18:17: note: found this candidate
 public func joined<Separator : Sequence where Separator.Iterator.Element == Iterator.Element.Iterator.Element>(separator: Separator) -> JoinedSequence<Self>
Swift.Sequence:16:17: note: found this candidate
 public func joined(separator: String = default) -> String
Swift.Collection:27:17: note: found this candidate
 public func joined() -> FlattenCollection<Self>

For context, ‘thingies’ is an array of a custom type.

What the compiler wishes it could say, if it weren’t incompetent, is that every one of Array’s ‘joined’ implementations are conditional.  The one that I want is is the second last one, but it is only defined for Array<String> specifically.  No other Array types.

Similarly every other one is conditional on the Element type within the Array being a specific type or protocol, none of which happen to apply to the types I’m using in my Array.

Now, my intuition is that since my type is CustomDebugStringConvertible, that Swift would know then how to convert my type to a String and then go from there.  For better or worse, however, it does not.  Instead you have to do it manually, e.g.:

log.debug("\({ String(describing: $0) }).joined(separator: ", "))")

And you can probably tell from that alone that I’m very used to Objective-C, where it’s very easy to write what you intend and get the results you intend.

Collection literals

Last week Ted Kremenek added support for container literals and subscripting to Clang. This was noted in various places, though mostly only as a statement and maybe an example of the new container literals. But I wanted to know how they’re implemented.

The answer is pretty easy to find. The relevant code follows a similar path within Clang as the existing NSString literals, which is to say that the guts of it live in CGObjC.cpp. You’ll now find, in addition to the familiar EmitObjCStringLiteral():

  • EmitObjCNumericLiteral()
  • EmitObjCArrayLiteral()
  • EmitObjCDictionaryLiteral()

What is unexpected is that they aren’t implemented the way you’d expect. Which is to say, the way NSString literals are implemented; as special, compile-time constructed instances that always exist. EmitObjCStringLiteral() ultimately just calls GetAddrOfConstantCFString() in CodeGenModule.cpp, which:

  1. Checks that there’s not an existing string constant with the same value, in which case it returns the address of that.
  2. Creates a global variable to be the NSString instance (actually an instance of whatever the constant string type is), and populates it with the class pointer, some flags indicating whether it’s UTF16 or not (in which case I presume it’s UTF8), the length, and a pointer to the actual string data…
  3. Which is allocated as a separate global variable.

So you end up with your actual string plus a 16-byte “NSString” instance in the read-only data section of your library. Pretty efficient at runtime.

Yet the new literals aren’t implemented like this at all. Numeric literals could in fact be implemented even more efficiently – NSNumber supports tagged pointers, which is where the actual pointer contains the entire object, rather than pointing to a heap-allocated instance. It doesn’t work for all numbers, of course, but it handles a lot of common cases, and a fallback to a compile-time constructed instance would be fine.

Instead, it generates a runtime call to the appropriate class constructor method on NSNumber. So you’re going to hit e.g. +[NSNumber numberWithInt:] every single time you execute a line that contains “@5”. Don’t forget that means it gets added to the autorelease pool. While NSNumber does keep a cache of likely-common instances (small integers near zero, for example), that’s not tuned to your particular code and it’s not going to cover many uses.

That’s all amazingly inefficient. I can only assume that this is the naive initial implementation, that will then be improved upon.

Likewise the NSArray and NSDictionary literals are also emitted as runtime calls to the appropriate class constructor method (either +[NSArray arrayWithObjects:count:] or +[NSDictionary dictionaryWithObjects:forKeys:count:], by default).

For the collections at least I can see that being slightly more rational, given that the collections are significantly more complex than simple string or number literals, and those classes have been known to change their implementations at runtime depending on their capacity and other factors.

It’s also possible that it’s implemented this way, for now, in order to support all Objective-C runtimes, not just the Mac and iOS ones. This is in CGObjC.cpp after all, not CGObjCMac.cpp. Though it’s worth noting that string literals are handled differently between the Mac/iOS and the GNU runtimes.

I hope the implementation improves. Even performance aside, there’s some potential initialisation order problems that follow from invoking actual class methods to generate the literals. I don’t know if I’d shy away from using these new literals for the initialisation of static variables or constants, but I’d think twice about it.