Files
swift-mirror/stdlib/public/core/StringInterpolation.swift
Nate Cook c8c035ec32 [6.2][stdlib] Allow a default for optional interpolations (#81360)
Cherry-pick of #80547 for the 6.2 release branch.

---

Explanation: This cherry picks the implementation of SE-0477 to add a
string interpolation method with a `default:` parameter for optional
interpolation values.
Main Branch PR: https://github.com/swiftlang/swift/pull/80547
Risk: Low.
Reviewed By: @stephentyrone 
Resolves: rdar://150865613
Testing: New tests for the string interpolations and fix-its.
2025-05-09 12:10:58 -05:00

389 lines
15 KiB
Swift

//===--- StringInterpolation.swift - String Interpolation -----------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
/// Represents a string literal with interpolations while it's being built up.
///
/// You don't need to create an instance of this type directly. It's used by the
/// compiler when you create a string using string interpolation. Instead, use
/// string interpolation to create a new string by including values, literals,
/// variables, or expressions enclosed in parentheses, prefixed by a
/// backslash (`\(`...`)`).
///
/// let price = 2
/// let number = 3
/// let message = """
/// If one cookie costs \(price) dollars, \
/// \(number) cookies cost \(price * number) dollars.
/// """
/// print(message)
/// // Prints "If one cookie costs 2 dollars, 3 cookies cost 6 dollars."
///
/// When implementing an `ExpressibleByStringInterpolation` conformance,
/// set the `StringInterpolation` associated type to
/// `DefaultStringInterpolation` to get the same interpolation behavior as
/// Swift's built-in `String` type and construct a `String` with the results.
/// If you don't want the default behavior or don't want to construct a
/// `String`, use a custom type conforming to `StringInterpolationProtocol`
/// instead.
///
/// Extending default string interpolation behavior
/// ===============================================
///
/// Code outside the standard library can extend string interpolation on
/// `String` and many other common types by extending
/// `DefaultStringInterpolation` and adding an `appendInterpolation(...)`
/// method. For example:
///
/// extension DefaultStringInterpolation {
/// fileprivate mutating func appendInterpolation(
/// escaped value: String, asASCII forceASCII: Bool = false) {
/// for char in value.unicodeScalars {
/// appendInterpolation(char.escaped(asASCII: forceASCII))
/// }
/// }
/// }
///
/// print("Escaped string: \(escaped: string)")
///
/// See `StringInterpolationProtocol` for details on `appendInterpolation`
/// methods.
///
/// `DefaultStringInterpolation` extensions should add only `mutating` members
/// and should not copy `self` or capture it in an escaping closure.
@frozen
public struct DefaultStringInterpolation: StringInterpolationProtocol, Sendable {
/// The string contents accumulated by this instance.
@usableFromInline
internal var _storage: String
/// Creates a string interpolation with storage pre-sized for a literal
/// with the indicated attributes.
///
/// You don't need to call this initializer directly. It's used by the
/// compiler when interpreting string interpolations.
@inlinable
public init(literalCapacity: Int, interpolationCount: Int) {
let capacityPerInterpolation = 2
let initialCapacity = literalCapacity +
interpolationCount * capacityPerInterpolation
_storage = String._createEmpty(withInitialCapacity: initialCapacity)
}
/// Appends a literal segment of a string interpolation.
///
/// You don't need to call this method directly. It's used by the compiler
/// when interpreting string interpolations.
@inlinable
public mutating func appendLiteral(_ literal: String) {
literal.write(to: &self)
}
/// Interpolates the given value's textual representation into the
/// string literal being created.
///
/// You don't need to call this method directly. It's used by the compiler
/// when interpreting string interpolations. Instead, use string
/// interpolation to create a new string by including values, literals,
/// variables, or expressions enclosed in parentheses, prefixed by a
/// backslash (`\(`...`)`).
///
/// let price = 2
/// let number = 3
/// let message = """
/// If one cookie costs \(price) dollars, \
/// \(number) cookies cost \(price * number) dollars.
/// """
/// print(message)
/// // Prints "If one cookie costs 2 dollars, 3 cookies cost 6 dollars."
@inlinable
public mutating func appendInterpolation<T>(_ value: T)
where T: TextOutputStreamable, T: CustomStringConvertible
{
value.write(to: &self)
}
/// Interpolates the given value's textual representation into the
/// string literal being created.
///
/// You don't need to call this method directly. It's used by the compiler
/// when interpreting string interpolations. Instead, use string
/// interpolation to create a new string by including values, literals,
/// variables, or expressions enclosed in parentheses, prefixed by a
/// backslash (`\(`...`)`).
///
/// let price = 2
/// let number = 3
/// let message = "If one cookie costs \(price) dollars, " +
/// "\(number) cookies cost \(price * number) dollars."
/// print(message)
/// // Prints "If one cookie costs 2 dollars, 3 cookies cost 6 dollars."
@inlinable
public mutating func appendInterpolation<T>(_ value: T)
where T: TextOutputStreamable
{
value.write(to: &self)
}
/// Interpolates the given value's textual representation into the
/// string literal being created.
///
/// You don't need to call this method directly. It's used by the compiler
/// when interpreting string interpolations. Instead, use string
/// interpolation to create a new string by including values, literals,
/// variables, or expressions enclosed in parentheses, prefixed by a
/// backslash (`\(`...`)`).
///
/// let price = 2
/// let number = 3
/// let message = """
/// If one cookie costs \(price) dollars, \
/// \(number) cookies cost \(price * number) dollars.
/// """
/// print(message)
/// // Prints "If one cookie costs 2 dollars, 3 cookies cost 6 dollars."
@inlinable
public mutating func appendInterpolation<T>(_ value: T)
where T: CustomStringConvertible
{
value.description.write(to: &self)
}
/// Interpolates the given value's textual representation into the
/// string literal being created.
///
/// You don't need to call this method directly. It's used by the compiler
/// when interpreting string interpolations. Instead, use string
/// interpolation to create a new string by including values, literals,
/// variables, or expressions enclosed in parentheses, prefixed by a
/// backslash (`\(`...`)`).
///
/// let price = 2
/// let number = 3
/// let message = """
/// If one cookie costs \(price) dollars, \
/// \(number) cookies cost \(price * number) dollars.
/// """
/// print(message)
/// // Prints "If one cookie costs 2 dollars, 3 cookies cost 6 dollars."
@inlinable
public mutating func appendInterpolation<T>(_ value: T) {
#if !$Embedded
_print_unlocked(value, &self)
#else
"(cannot print value in embedded Swift)".write(to: &self)
#endif
}
@_alwaysEmitIntoClient
@_unavailableInEmbedded
public mutating func appendInterpolation(_ value: Any.Type) {
_typeName(value, qualified: false).write(to: &self)
}
/// Creates a string from this instance, consuming the instance in the
/// process.
@inlinable
internal __consuming func make() -> String {
return _storage
}
}
extension DefaultStringInterpolation {
/// Interpolates the given optional value's textual representation, or the
/// specified default string, into the string literal being created.
///
/// You don't need to call this method directly. It's used by the compiler
/// when interpreting string interpolations where you provide a `default`
/// parameter. For example, the following code implicitly calls this method,
/// using the value of the `default` parameter when `value` is `nil`:
///
/// var age: Int? = 48
/// print("Your age is \(age, default: "unknown")")
/// // Prints: Your age is 48
/// age = nil
/// print("Your age is \(age, default: "unknown")")
/// // Prints: Your age is unknown
///
/// - Parameters:
/// - value: The value to include in a string interpolation, if non-`nil`.
/// - default: The string to include if `value` is `nil`.
@_alwaysEmitIntoClient
public mutating func appendInterpolation<T>(
_ value: T?,
default: @autoclosure () -> some StringProtocol
) where T: TextOutputStreamable, T: CustomStringConvertible {
if let value {
self.appendInterpolation(value)
} else {
self.appendInterpolation(`default`())
}
}
/// Interpolates the given optional value's textual representation, or the
/// specified default string, into the string literal being created.
///
/// You don't need to call this method directly. It's used by the compiler
/// when interpreting string interpolations where you provide a `default`
/// parameter. For example, the following code implicitly calls this method,
/// using the value of the `default` parameter when `value` is `nil`:
///
/// var age: Int? = 48
/// print("Your age is \(age, default: "unknown")")
/// // Prints: Your age is 48
/// age = nil
/// print("Your age is \(age, default: "unknown")")
/// // Prints: Your age is unknown
///
/// - Parameters:
/// - value: The value to include in a string interpolation, if non-`nil`.
/// - default: The string to include if `value` is `nil`.
@_alwaysEmitIntoClient
public mutating func appendInterpolation<T>(
_ value: T?,
default: @autoclosure () -> some StringProtocol
) where T: TextOutputStreamable {
if let value {
self.appendInterpolation(value)
} else {
self.appendInterpolation(`default`())
}
}
/// Interpolates the given optional value's textual representation, or the
/// specified default string, into the string literal being created.
///
/// You don't need to call this method directly. It's used by the compiler
/// when interpreting string interpolations where you provide a `default`
/// parameter. For example, the following code implicitly calls this method,
/// using the value of the `default` parameter when `value` is `nil`:
///
/// var age: Int? = 48
/// print("Your age is \(age, default: "unknown")")
/// // Prints: Your age is 48
/// age = nil
/// print("Your age is \(age, default: "unknown")")
/// // Prints: Your age is unknown
///
/// - Parameters:
/// - value: The value to include in a string interpolation, if non-`nil`.
/// - default: The string to include if `value` is `nil`.
@_alwaysEmitIntoClient
public mutating func appendInterpolation<T>(
_ value: T?,
default: @autoclosure () -> some StringProtocol
) where T: CustomStringConvertible {
if let value {
self.appendInterpolation(value)
} else {
self.appendInterpolation(`default`())
}
}
/// Interpolates the given optional value's textual representation, or the
/// specified default string, into the string literal being created.
///
/// You don't need to call this method directly. It's used by the compiler
/// when interpreting string interpolations where you provide a `default`
/// parameter. For example, the following code implicitly calls this method,
/// using the value of the `default` parameter when `value` is `nil`:
///
/// var age: Int? = 48
/// print("Your age is \(age, default: "unknown")")
/// // Prints: Your age is 48
/// age = nil
/// print("Your age is \(age, default: "unknown")")
/// // Prints: Your age is unknown
///
/// - Parameters:
/// - value: The value to include in a string interpolation, if non-`nil`.
/// - default: The string to include if `value` is `nil`.
@_alwaysEmitIntoClient
public mutating func appendInterpolation<T>(
_ value: T?,
default: @autoclosure () -> some StringProtocol
) {
if let value {
self.appendInterpolation(value)
} else {
self.appendInterpolation(`default`())
}
}
}
extension DefaultStringInterpolation: CustomStringConvertible {
@inlinable
public var description: String {
return _storage
}
}
extension DefaultStringInterpolation: TextOutputStream {
@inlinable
public mutating func write(_ string: String) {
_storage.append(string)
}
public mutating func _writeASCII(_ buffer: UnsafeBufferPointer<UInt8>) {
unsafe _storage._guts.append(_StringGuts(buffer, isASCII: true))
}
}
// While not strictly necessary, declaring these is faster than using the
// default implementation.
extension String {
/// Creates a new instance from an interpolated string literal.
///
/// You don't need to call this initializer directly. It's used by the
/// compiler when you create a string using string interpolation. Instead, use
/// string interpolation to create a new string by including values, literals,
/// variables, or expressions enclosed in parentheses, prefixed by a
/// backslash (`\(`...`)`).
///
/// let price = 2
/// let number = 3
/// let message = """
/// If one cookie costs \(price) dollars, \
/// \(number) cookies cost \(price * number) dollars.
/// """
/// print(message)
/// // Prints "If one cookie costs 2 dollars, 3 cookies cost 6 dollars."
@inlinable
@_effects(readonly)
public init(stringInterpolation: DefaultStringInterpolation) {
self = stringInterpolation.make()
}
}
extension Substring {
/// Creates a new instance from an interpolated string literal.
///
/// You don't need to call this initializer directly. It's used by the
/// compiler when you create a string using string interpolation. Instead, use
/// string interpolation to create a new string by including values, literals,
/// variables, or expressions enclosed in parentheses, prefixed by a
/// backslash (`\(`...`)`).
///
/// let price = 2
/// let number = 3
/// let message = """
/// If one cookie costs \(price) dollars, \
/// \(number) cookies cost \(price * number) dollars.
/// """
/// print(message)
/// // Prints "If one cookie costs 2 dollars, 3 cookies cost 6 dollars."
@inlinable
@_effects(readonly)
public init(stringInterpolation: DefaultStringInterpolation) {
self.init(stringInterpolation.make())
}
}