/// A string literal whose interpolated segments are captured unevaluated, so a widget /// property built from it can be re-evaluated when the state those segments read changes. /// /// `"plain"` (no interpolation) collapses to ``staticText`` and is applied once with no /// subscription. `"Count: \(count)"` keeps one deferred closure per `\(...)`; evaluating /// it inside a ``DependencyTracker`` registers every state box it reads. public struct InterpolatedText: ExpressibleByStringInterpolation { fileprivate enum Segment { case literal(String) case deferred(() -> String) } private let segments: [Segment] /// The fully resolved text when this value has no interpolations; `nil` when at least /// one segment must be evaluated on demand. Non-`nil` means "can never change". @_spi(Portico) public let staticText: String? /// Creates an ``InterpolatedText`` from a plain string literal with no interpolations. public init(stringLiteral value: String) { segments = [] staticText = value } /// Creates an ``InterpolatedText`` from a string interpolation, capturing each /// `\(...)` segment as a deferred closure. public init(stringInterpolation: StringInterpolation) { if stringInterpolation.deferredCount == 0 { var result = "" for seg in stringInterpolation.segments { if case .literal(let s) = seg { result += s } } staticText = result segments = [] } else { segments = stringInterpolation.segments staticText = nil } } /// Concatenates every segment. Deferred segments run under the active /// ``DependencyTracker``, so their state reads register as dependencies. @_spi(Portico) public func evaluate() -> String { if let s = staticText { return s } var result = "" for seg in segments { switch seg { case .literal(let s): result += s case .deferred(let f): result += f() } } return result } /// ``evaluate()`` with dependency tracking suppressed. @_spi(Portico) public var untrackedText: String { DependencyTracker.untracked { evaluate() } } /// Accumulates literal text and deferred interpolation segments for an /// ``InterpolatedText``. Callers do not construct this directly; the compiler /// synthesises it from string-interpolation syntax. public struct StringInterpolation: StringInterpolationProtocol { fileprivate var segments: [Segment] = [] fileprivate var deferredCount = 0 /// Creates an empty interpolation with capacity hints for the compiler. public init(literalCapacity: Int, interpolationCount: Int) { segments.reserveCapacity(interpolationCount * 2 + 1) } /// Appends a verbatim run of string content to the interpolation. public mutating func appendLiteral(_ literal: String) { segments.append(.literal(literal)) } /// Captures `value` unevaluated so it can be re-read inside a /// ``DependencyTracker`` when the property updates. public mutating func appendInterpolation( _ value: @autoclosure @escaping () -> Value ) { deferredCount += 1 segments.append(.deferred { String(describing: value()) }) } } }