Skip to content

Commit

Permalink
TSCBasic: deprecate <<< operator (#413)
Browse files Browse the repository at this point in the history
Since TSCBasic has become a kitchen sink for multiple types and functions, it's important to be able to selectively import those. It is done with `import struct`, `import func`, and similar statements. Unfortunately, this doesn't work for operators, which means you have to import the whole module when you need access just to a single operator. This inadvertently pollutes the environment of available symbols, which is undesirable.

By deprecating the operator and adding a `send(_:)` function on `WritableByteStream` we allow selectively importing this functionality without importing the whole module.

This also brings the API closer to Swift conventions, which in my understanding don't favor use of operators for stream I/O. Looks like `<<<` was initially brought over to TSC as a C++ idiom.
  • Loading branch information
MaxDesiatov authored Apr 26, 2023
1 parent adefcdf commit 78e53cb
Show file tree
Hide file tree
Showing 20 changed files with 230 additions and 195 deletions.
6 changes: 3 additions & 3 deletions Sources/TSCBasic/DiagnosticsEngine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -148,11 +148,11 @@ public final class DiagnosticsEngine: CustomStringConvertible {

public var description: String {
let stream = BufferedOutputByteStream()
stream <<< "["
stream.send("[")
for diag in diagnostics {
stream <<< diag.description <<< ", "
stream.send(diag.description).send(", ")
}
stream <<< "]"
stream.send("]")
return stream.bytes.description
}
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/TSCBasic/FileSystem.swift
Original file line number Diff line number Diff line change
Expand Up @@ -458,7 +458,7 @@ private struct LocalFileSystem: FileSystem {
}
break
}
data <<< tmpBuffer[0..<n]
data.send(tmpBuffer[0..<n])
}

return data.bytes
Expand Down
34 changes: 17 additions & 17 deletions Sources/TSCBasic/JSON.swift
Original file line number Diff line number Diff line change
Expand Up @@ -126,41 +126,41 @@ extension JSON: ByteStreamable {
let shouldIndent = indent != nil
switch self {
case .null:
stream <<< "null"
stream.send("null")
case .bool(let value):
stream <<< Format.asJSON(value)
stream.send(Format.asJSON(value))
case .int(let value):
stream <<< Format.asJSON(value)
stream.send(Format.asJSON(value))
case .double(let value):
// FIXME: What happens for NaN, etc.?
stream <<< Format.asJSON(value)
stream.send(Format.asJSON(value))
case .string(let value):
stream <<< Format.asJSON(value)
stream.send(Format.asJSON(value))
case .array(let contents):
stream <<< "[" <<< (shouldIndent ? "\n" : "")
stream.send("[").send(shouldIndent ? "\n" : "")
for (i, item) in contents.enumerated() {
if i != 0 { stream <<< "," <<< (shouldIndent ? "\n" : " ") }
stream <<< indentStreamable(offset: 2)
if i != 0 { stream.send(",").send(shouldIndent ? "\n" : " ") }
stream.send(indentStreamable(offset: 2))
item.write(to: stream, indent: indent.flatMap({ $0 + 2 }))
}
stream <<< (shouldIndent ? "\n" : "") <<< indentStreamable() <<< "]"
stream.send(shouldIndent ? "\n" : "").send(indentStreamable()).send("]")
case .dictionary(let contents):
// We always output in a deterministic order.
stream <<< "{" <<< (shouldIndent ? "\n" : "")
stream.send("{").send(shouldIndent ? "\n" : "")
for (i, key) in contents.keys.sorted().enumerated() {
if i != 0 { stream <<< "," <<< (shouldIndent ? "\n" : " ") }
stream <<< indentStreamable(offset: 2) <<< Format.asJSON(key) <<< ": "
if i != 0 { stream.send(",").send(shouldIndent ? "\n" : " ") }
stream.send(indentStreamable(offset: 2)).send(Format.asJSON(key)).send(": ")
contents[key]!.write(to: stream, indent: indent.flatMap({ $0 + 2 }))
}
stream <<< (shouldIndent ? "\n" : "") <<< indentStreamable() <<< "}"
stream.send(shouldIndent ? "\n" : "").send(indentStreamable()).send("}")
case .orderedDictionary(let contents):
stream <<< "{" <<< (shouldIndent ? "\n" : "")
stream.send("{").send(shouldIndent ? "\n" : "")
for (i, item) in contents.enumerated() {
if i != 0 { stream <<< "," <<< (shouldIndent ? "\n" : " ") }
stream <<< indentStreamable(offset: 2) <<< Format.asJSON(item.key) <<< ": "
if i != 0 { stream.send(",").send(shouldIndent ? "\n" : " ") }
stream.send(indentStreamable(offset: 2)).send(Format.asJSON(item.key)).send(": ")
item.value.write(to: stream, indent: indent.flatMap({ $0 + 2 }))
}
stream <<< (shouldIndent ? "\n" : "") <<< indentStreamable() <<< "}"
stream.send(shouldIndent ? "\n" : "").send(indentStreamable()).send("}")
}
}
}
Expand Down
16 changes: 8 additions & 8 deletions Sources/TSCBasic/Process.swift
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,7 @@ public final class Process {
outputRedirection: outputRedirection,
startNewProcessGroup: startNewProcessGroup,
loggingHandler: verbose ? { message in
stdoutStream <<< message <<< "\n"
stdoutStream.send(message).send("\n")
stdoutStream.flush()
} : nil
)
Expand Down Expand Up @@ -1276,13 +1276,13 @@ extension ProcessResult.Error: CustomStringConvertible {
let stream = BufferedOutputByteStream()
switch result.exitStatus {
case .terminated(let code):
stream <<< "terminated(\(code)): "
stream.send("terminated(\(code)): ")
#if os(Windows)
case .abnormal(let exception):
stream <<< "abnormal(\(exception)): "
stream.send("abnormal(\(exception)): ")
#else
case .signalled(let signal):
stream <<< "signalled(\(signal)): "
stream.send("signalled(\(signal)): ")
#endif
}

Expand All @@ -1292,15 +1292,15 @@ extension ProcessResult.Error: CustomStringConvertible {
if args.first == "sandbox-exec", args.count > 3 {
args = args.suffix(from: 3).map({$0})
}
stream <<< args.map({ $0.spm_shellEscaped() }).joined(separator: " ")
stream.send(args.map({ $0.spm_shellEscaped() }).joined(separator: " "))

// Include the output, if present.
if let output = try? result.utf8Output() + result.utf8stderrOutput() {
// We indent the output to keep it visually separated from everything else.
let indentation = " "
stream <<< " output:\n" <<< indentation <<< output.replacingOccurrences(of: "\n", with: "\n" + indentation)
stream.send(" output:\n").send(indentation).send(output.replacingOccurrences(of: "\n", with: "\n" + indentation))
if !output.hasSuffix("\n") {
stream <<< "\n"
stream.send("\n")
}
}

Expand Down Expand Up @@ -1333,7 +1333,7 @@ extension FileHandle: WritableByteStream {
extension Process {
@available(*, deprecated)
fileprivate static func logToStdout(_ message: String) {
stdoutStream <<< message <<< "\n"
stdoutStream.send(message).send("\n")
stdoutStream.flush()
}
}
10 changes: 5 additions & 5 deletions Sources/TSCBasic/TerminalController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -166,13 +166,13 @@ public final class TerminalController {

/// Clears the current line and moves the cursor to beginning of the line..
public func clearLine() {
stream <<< clearLineString <<< "\r"
stream.send(clearLineString).send("\r")
flush()
}

/// Moves the cursor y columns up.
public func moveCursor(up: Int) {
stream <<< "\u{001B}[\(up)A"
stream.send("\u{001B}[\(up)A")
flush()
}

Expand All @@ -184,7 +184,7 @@ public final class TerminalController {

/// Inserts a new line character into the stream.
public func endLine() {
stream <<< "\n"
stream.send("\n")
flush()
}

Expand All @@ -198,9 +198,9 @@ public final class TerminalController {
private func writeWrapped(_ string: String, inColor color: Color, bold: Bool = false, stream: WritableByteStream) {
// Don't wrap if string is empty or color is no color.
guard !string.isEmpty && color != .noColor else {
stream <<< string
stream.send(string)
return
}
stream <<< color.string <<< (bold ? boldString : "") <<< string <<< resetString
stream.send(color.string).send(bold ? boldString : "").send(string).send(resetString)
}
}
85 changes: 59 additions & 26 deletions Sources/TSCBasic/WritableByteStream.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,19 +27,19 @@ public protocol ByteStreamable {
/// different output destinations, e.g., a file or an in memory buffer. This is
/// loosely modeled on LLVM's llvm::raw_ostream class.
///
/// The stream is generally used in conjunction with the custom streaming
/// operator '<<<'. For example:
/// The stream is generally used in conjunction with the `appending` function.
/// For example:
///
/// let stream = BufferedOutputByteStream()
/// stream <<< "Hello, world!"
/// stream.appending("Hello, world!")
///
/// would write the UTF8 encoding of "Hello, world!" to the stream.
///
/// The stream accepts a number of custom formatting operators which are defined
/// in the `Format` struct (used for namespacing purposes). For example:
///
/// let items = ["hello", "world"]
/// stream <<< Format.asSeparatedList(items, separator: " ")
/// stream.appending(Format.asSeparatedList(items, separator: " "))
///
/// would write each item in the list to the stream, separating them with a
/// space.
Expand Down Expand Up @@ -137,6 +137,34 @@ extension WritableByteStream {
}
}
}

// MARK: helpers that return `self`

// FIXME: This override shouldn't be necesary but removing it causes a 30% performance regression. This problem is
// tracked by the following bug: https://bugs.swift.org/browse/SR-8535
@discardableResult
public func send(_ value: ArraySlice<UInt8>) -> WritableByteStream {
value.write(to: self)
return self
}

@discardableResult
public func send(_ value: ByteStreamable) -> WritableByteStream {
value.write(to: self)
return self
}

@discardableResult
public func send(_ value: CustomStringConvertible) -> WritableByteStream {
value.description.write(to: self)
return self
}

@discardableResult
public func send(_ value: ByteStreamable & CustomStringConvertible) -> WritableByteStream {
value.write(to: self)
return self
}
}

/// The `WritableByteStream` base class.
Expand Down Expand Up @@ -366,24 +394,29 @@ precedencegroup StreamingPrecedence {

// FIXME: This override shouldn't be necesary but removing it causes a 30% performance regression. This problem is
// tracked by the following bug: https://bugs.swift.org/browse/SR-8535

@available(*, deprecated, message: "use send(_:) function on WritableByteStream instead")
@discardableResult
public func <<< (stream: WritableByteStream, value: ArraySlice<UInt8>) -> WritableByteStream {
value.write(to: stream)
return stream
}

@available(*, deprecated, message: "use send(_:) function on WritableByteStream instead")
@discardableResult
public func <<< (stream: WritableByteStream, value: ByteStreamable) -> WritableByteStream {
value.write(to: stream)
return stream
}

@available(*, deprecated, message: "use send(_:) function on WritableByteStream instead")
@discardableResult
public func <<< (stream: WritableByteStream, value: CustomStringConvertible) -> WritableByteStream {
value.description.write(to: stream)
return stream
}

@available(*, deprecated, message: "use send(_:) function on WritableByteStream instead")
@discardableResult
public func <<< (stream: WritableByteStream, value: ByteStreamable & CustomStringConvertible) -> WritableByteStream {
value.write(to: stream)
Expand Down Expand Up @@ -450,7 +483,7 @@ public struct Format {
let value: Bool

func write(to stream: WritableByteStream) {
stream <<< (value ? "true" : "false")
stream.send(value ? "true" : "false")
}
}

Expand All @@ -463,7 +496,7 @@ public struct Format {

func write(to stream: WritableByteStream) {
// FIXME: Diagnose integers which cannot be represented in JSON.
stream <<< value.description
stream.send(value.description)
}
}

Expand All @@ -478,7 +511,7 @@ public struct Format {
// FIXME: What should we do about NaN, etc.?
//
// FIXME: Is Double.debugDescription the best representation?
stream <<< value.debugDescription
stream.send(value.debugDescription)
}
}

Expand All @@ -494,9 +527,9 @@ public struct Format {
let value: String

func write(to stream: WritableByteStream) {
stream <<< UInt8(ascii: "\"")
stream.send(UInt8(ascii: "\""))
stream.writeJSONEscaped(value)
stream <<< UInt8(ascii: "\"")
stream.send(UInt8(ascii: "\""))
}
}

Expand All @@ -514,12 +547,12 @@ public struct Format {
let items: [String]

func write(to stream: WritableByteStream) {
stream <<< UInt8(ascii: "[")
stream.send(UInt8(ascii: "["))
for (i, item) in items.enumerated() {
if i != 0 { stream <<< "," }
stream <<< Format.asJSON(item)
if i != 0 { stream.send(",") }
stream.send(Format.asJSON(item))
}
stream <<< UInt8(ascii: "]")
stream.send(UInt8(ascii: "]"))
}
}

Expand All @@ -531,12 +564,12 @@ public struct Format {
let items: [String: String]

func write(to stream: WritableByteStream) {
stream <<< UInt8(ascii: "{")
stream.send(UInt8(ascii: "{"))
for (offset: i, element: (key: key, value: value)) in items.enumerated() {
if i != 0 { stream <<< "," }
stream <<< Format.asJSON(key) <<< ":" <<< Format.asJSON(value)
if i != 0 { stream.send(",") }
stream.send(Format.asJSON(key)).send(":").send(Format.asJSON(value))
}
stream <<< UInt8(ascii: "}")
stream.send(UInt8(ascii: "}"))
}
}

Expand All @@ -551,12 +584,12 @@ public struct Format {
let transform: (T) -> String

func write(to stream: WritableByteStream) {
stream <<< UInt8(ascii: "[")
stream.send(UInt8(ascii: "["))
for (i, item) in items.enumerated() {
if i != 0 { stream <<< "," }
stream <<< Format.asJSON(transform(item))
if i != 0 { stream.send(",") }
stream.send(Format.asJSON(transform(item)))
}
stream <<< UInt8(ascii: "]")
stream.send(UInt8(ascii: "]"))
}
}

Expand All @@ -572,10 +605,10 @@ public struct Format {
for (i, item) in items.enumerated() {
// Add the separator, if necessary.
if i != 0 {
stream <<< separator
stream.send(separator)
}

stream <<< item
stream.send(item)
}
}
}
Expand All @@ -596,8 +629,8 @@ public struct Format {

func write(to stream: WritableByteStream) {
for (i, item) in items.enumerated() {
if i != 0 { stream <<< separator }
stream <<< transform(item)
if i != 0 { stream.send(separator) }
stream.send(transform(item))
}
}
}
Expand All @@ -617,7 +650,7 @@ public struct Format {

func write(to stream: WritableByteStream) {
for _ in 0..<count {
stream <<< string
stream.send(string)
}
}
}
Expand Down
Loading

0 comments on commit 78e53cb

Please sign in to comment.