Skip to content

Latest commit

 

History

History
136 lines (98 loc) · 5.33 KB

Literals.rst

File metadata and controls

136 lines (98 loc) · 5.33 KB
orphan:

Literals

What happens when a literal expression is used?

The complicated case is for integer, floating-point, character, and string literals, so let's look at those.

High-Level View

window.setTitle("Welcome to Xcode")

In this case, we have a string literal and an enclosing context. If window is an NSWindow, there will only be one possible method named setTitle, which takes an NSString. Therefore, we want the string literal expression to end up being an NSString.

Fortunately, NSString implements StringLiteralConvertible, so the type checker will indeed be able to choose NSString as the type of the string literal. All is well.

In the case of integers or floating-point literals, the value effectively has infinite precision. Once the type has been chosen, the value is checked to see if it is in range for that type.

The StringLiteralConvertible Protocol

Here is the StringLiteralConvertible protocol as defined in the standard library's Policy.swift:

// NOTE: the compiler has builtin knowledge of this protocol
protocol StringLiteralConvertible {
  typealias StringLiteralType : _BuiltinStringLiteralConvertible
  class func convertFromStringLiteral(value : StringLiteralType) -> Self
}

Curiously, the protocol is not defined in terms of primitive types, but in terms of any StringLiteralType that the implementer chooses. In most cases, this will be Swift's own native String type, which means users can implement their own StringLiteralConvertible types while still dealing with a high-level interface.

(Why is this not hardcoded? A String must be a valid Unicode string, but if the string literal contains escape sequences, an invalid series of code points could be constructed...which may be what's desired in some cases.)

The _BuiltinStringLiteralConvertible Protocol

Policy.swift contains a second protocol:

// NOTE: the compiler has builtin knowledge of this protocol
protocol _BuiltinStringLiteralConvertible {
  class func _convertFromBuiltinStringLiteral(value : Builtin.RawPointer,
                                              byteSize : Builtin.Int64,
                                              isASCII: Builtin.Int1) -> Self
}

The use of builtin types makes it clear that this is only for use in the standard library. This is the actual primitive function that is used to construct types from string literals: the compiler knows how to emit raw data from the literal, and the arguments describe that raw data.

So, the general runtime behavior is now clear:

  1. The compiler generates raw string data.
  2. Some type conforming to _BuiltinStringLiteralConvertible is constructed from the raw string data. This will be a standard library type.
  3. Some type conforming to StringLiteralConvertible is constructed from the object constructed in step 2. This may be a user-defined type. This is the result.

The Type-Checker's Algorithm

In order to make this actually happen, the type-checker has to do some fancy footwork. Remember, at this point all we have is a string literal and an expected type; if the function were overloaded, we would have to try all the types.

This algorithm can go forwards or backwards, since it's actually defined in terms of constraints, but it's easiest to understand as a linear process.

  1. Filter the types provided by the context to only include those that are StringLiteralConvertible.
  2. Using the associated StringLiteralType, find the appropriate _convertFromBuiltinStringLiteral.
  3. Using the type from step 1, find the appropriate convertFromStringLiteral.
  4. Build an expression tree with the appropriate calls.

How about cases where there is no context?

var str = "abc"

Here we have nothing to go on, so instead the type checker looks for a global type named StringLiteralType in the current module-scope context, and uses that type if it is actually a StringLiteralConvertible type. This both allows different standard libraries to set different default literal types, and allows a user to override the default type in their own source file.

The real story is even more complicated because of implicit conversions: the type expected by setTitle might not actually be literal-convertible, but something else that is literal-convertible can then implicitly convert to the proper type. If this makes your head spin, don't worry about it.

Arrays, Dictionaries, and Interpolation

Array and dictionary literals don't have a Builtin*Convertible form. Instead, they just always use a variadic list of elements (T...) in the array case and (key, value) tuples in the dictionary case. A variadic list is always exposed using the standard library's Array type, so there is no separate step to jump through.

The default array literal type is always Array, and the default dictionary literal type is always Dictionary.

String interpolations are a bit different: they try to individually convert each element of the interpolation to the type that adopts StringInterpolationConvertible, then calls the variadic convertFromStringInterpolation to put them all together. The default type for an interpolated literal without context is also StringLiteralType.