Skip to content

Commit 4e3804f

Browse files
authored
Merge pull request swiftlang#1945 from hartbit/basic-rework
QOL changes in Basic to replace asString by CustomStringConvertible
2 parents b7d64a3 + e4fe159 commit 4e3804f

File tree

96 files changed

+665
-624
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

96 files changed

+665
-624
lines changed

Sources/Basic/ByteString.swift

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,21 @@ public struct ByteString: ExpressibleByArrayLiteral, Hashable {
5959
public var count: Int {
6060
return _bytes.count
6161
}
62+
}
63+
64+
/// Conform to CustomDebugStringConvertible.
65+
extension ByteString: CustomStringConvertible {
66+
/// Return the string decoded as a UTF8 sequence, or traps if not possible.
67+
public var description: String {
68+
guard let description = validDescription else {
69+
fatalError("invalid byte string: \(cString)")
70+
}
71+
72+
return description
73+
}
6274

6375
/// Return the string decoded as a UTF8 sequence, if possible.
64-
public var asString: String? {
76+
public var validDescription: String? {
6577
// FIXME: This is very inefficient, we need a way to pass a buffer. It
6678
// is also wrong if the string contains embedded '\0' characters.
6779
let tmp = _bytes + [UInt8(0)]
@@ -72,21 +84,18 @@ public struct ByteString: ExpressibleByArrayLiteral, Hashable {
7284

7385
/// Return the string decoded as a UTF8 sequence, substituting replacement
7486
/// characters for ill-formed UTF8 sequences.
75-
public var asReadableString: String {
87+
public var cString: String {
7688
// FIXME: This is very inefficient, we need a way to pass a buffer. It
7789
// is also wrong if the string contains embedded '\0' characters.
7890
let tmp = _bytes + [UInt8(0)]
7991
return tmp.withUnsafeBufferPointer { ptr in
8092
return String(cString: unsafeBitCast(ptr.baseAddress, to: UnsafePointer<CChar>.self))
8193
}
8294
}
83-
}
8495

85-
/// Conform to CustomStringConvertible.
86-
extension ByteString: CustomStringConvertible {
87-
public var description: String {
88-
// For now, default to the "readable string" representation.
89-
return "<ByteString:\"\(asReadableString)\">"
96+
@available(*, deprecated, message: "use description or validDescription instead")
97+
public var asString: String? {
98+
return validDescription
9099
}
91100
}
92101

Sources/Basic/DiagnosticsEngine.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,7 @@ public class DiagnosticsEngine: CustomStringConvertible {
314314
stream <<< diag.localizedDescription <<< ", "
315315
}
316316
stream <<< "]"
317-
return stream.bytes.asString!
317+
return stream.bytes.description
318318
}
319319
}
320320

Sources/Basic/FileSystem.swift

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ public extension FileSystem {
242242
private class LocalFileSystem: FileSystem {
243243

244244
func isExecutableFile(_ path: AbsolutePath) -> Bool {
245-
guard let filestat = try? POSIX.stat(path.asString) else {
245+
guard let filestat = try? POSIX.stat(path.description) else {
246246
return false
247247
}
248248
return filestat.st_mode & SPMLibc.S_IXUSR != 0 && filestat.st_mode & S_IFREG != 0
@@ -287,7 +287,7 @@ private class LocalFileSystem: FileSystem {
287287
}
288288

289289
func getDirectoryContents(_ path: AbsolutePath) throws -> [String] {
290-
guard let dir = SPMLibc.opendir(path.asString) else {
290+
guard let dir = SPMLibc.opendir(path.description) else {
291291
throw FileSystemError(errno: errno)
292292
}
293293
defer { _ = SPMLibc.closedir(dir) }
@@ -329,7 +329,7 @@ private class LocalFileSystem: FileSystem {
329329

330330
func createDirectory(_ path: AbsolutePath, recursive: Bool) throws {
331331
// Try to create the directory.
332-
let result = mkdir(path.asString, SPMLibc.S_IRWXU | SPMLibc.S_IRWXG)
332+
let result = mkdir(path.description, SPMLibc.S_IRWXU | SPMLibc.S_IRWXG)
333333

334334
// If it succeeded, we are done.
335335
if result == 0 { return }
@@ -354,7 +354,7 @@ private class LocalFileSystem: FileSystem {
354354

355355
func readFileContents(_ path: AbsolutePath) throws -> ByteString {
356356
// Open the file.
357-
let fp = fopen(path.asString, "rb")
357+
let fp = fopen(path.description, "rb")
358358
if fp == nil {
359359
throw FileSystemError(errno: errno)
360360
}
@@ -383,7 +383,7 @@ private class LocalFileSystem: FileSystem {
383383

384384
func writeFileContents(_ path: AbsolutePath, bytes: ByteString) throws {
385385
// Open the file.
386-
let fp = fopen(path.asString, "wb")
386+
let fp = fopen(path.description, "wb")
387387
if fp == nil {
388388
throw FileSystemError(errno: errno)
389389
}
@@ -412,7 +412,7 @@ private class LocalFileSystem: FileSystem {
412412
let temp = try TemporaryFile(dir: path.parentDirectory, deleteOnClose: false)
413413
do {
414414
try writeFileContents(temp.path, bytes: bytes)
415-
try POSIX.rename(old: temp.path.asString, new: path.asString)
415+
try POSIX.rename(old: temp.path.description, new: path.description)
416416
} catch {
417417
// Write or rename failed, delete the temporary file.
418418
// Rethrow the original error, however, as that's the
@@ -424,7 +424,7 @@ private class LocalFileSystem: FileSystem {
424424

425425
func removeFileTree(_ path: AbsolutePath) throws {
426426
if self.exists(path, followSymlink: false) {
427-
try FileManager.default.removeItem(atPath: path.asString)
427+
try FileManager.default.removeItem(atPath: path.description)
428428
}
429429
}
430430

@@ -441,7 +441,7 @@ private class LocalFileSystem: FileSystem {
441441
let ftsOptions = recursive ? FTS_PHYSICAL : FTS_LOGICAL
442442

443443
// Get handle to the file hierarchy we want to traverse.
444-
let paths = CStringArray([path.asString])
444+
let paths = CStringArray([path.description])
445445
guard let ftsp = fts_open(paths.cArray, ftsOptions, nil) else {
446446
throw FileSystemError(errno: errno)
447447
}
@@ -809,7 +809,7 @@ public class RerootedFileSystemView: FileSystem {
809809
return root
810810
} else {
811811
// FIXME: Optimize?
812-
return root.appending(RelativePath(String(path.asString.dropFirst(1))))
812+
return root.appending(RelativePath(String(path.description.dropFirst(1))))
813813
}
814814
}
815815

Sources/Basic/JSON.swift

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -109,10 +109,7 @@ extension JSON {
109109

110110
/// Encode a JSON item into a JSON string
111111
public func toString(prettyPrint: Bool = false) -> String {
112-
guard let contents = self.toBytes(prettyPrint: prettyPrint).asString else {
113-
fatalError("Failed to serialize JSON: \(self)")
114-
}
115-
return contents
112+
return toBytes(prettyPrint: prettyPrint).description
116113
}
117114
}
118115

@@ -311,13 +308,25 @@ extension Bool: JSONSerializable {
311308

312309
extension AbsolutePath: JSONSerializable {
313310
public func toJSON() -> JSON {
314-
return .string(asString)
311+
return .string(description)
315312
}
316313
}
317314

318315
extension RelativePath: JSONSerializable {
319316
public func toJSON() -> JSON {
320-
return .string(asString)
317+
return .string(description)
318+
}
319+
}
320+
321+
extension Array: JSONSerializable where Element: JSONSerializable {
322+
public func toJSON() -> JSON {
323+
return .array(self.map({ $0.toJSON() }))
324+
}
325+
}
326+
327+
extension Dictionary: JSONSerializable where Key == String, Value: JSONSerializable {
328+
public func toJSON() -> JSON {
329+
return .dictionary(self.mapValues({ $0.toJSON() }))
321330
}
322331
}
323332

Sources/Basic/Lock.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ public final class FileLock {
5555
public func lock() throws {
5656
// Open the lock file.
5757
if fd == nil {
58-
let fd = SPMLibc.open(lockFile.asString, O_WRONLY | O_CREAT | O_CLOEXEC, 0o666)
58+
let fd = SPMLibc.open(lockFile.description, O_WRONLY | O_CREAT | O_CLOEXEC, 0o666)
5959
if fd == -1 {
6060
throw FileSystemError(errno: errno)
6161
}

Sources/Basic/OutputByteStream.swift

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,18 @@ public func <<< (stream: OutputByteStream, value: ByteStreamable) -> OutputByteS
342342
return stream
343343
}
344344

345+
@discardableResult
346+
public func <<< (stream: OutputByteStream, value: CustomStringConvertible) -> OutputByteStream {
347+
value.description.write(to: stream)
348+
return stream
349+
}
350+
351+
@discardableResult
352+
public func <<< (stream: OutputByteStream, value: ByteStreamable & CustomStringConvertible) -> OutputByteStream {
353+
value.write(to: stream)
354+
return stream
355+
}
356+
345357
extension UInt8: ByteStreamable {
346358
public func write(to stream: OutputByteStream) {
347359
stream.write(self)
@@ -434,6 +446,10 @@ public struct Format {
434446
}
435447
}
436448

449+
/// Write the input CustomStringConvertible encoded as a JSON object.
450+
static public func asJSON<T: CustomStringConvertible>(_ value: T) -> ByteStreamable {
451+
return JSONEscapedStringStreamable(value: value.description)
452+
}
437453
/// Write the input string encoded as a JSON object.
438454
static public func asJSON(_ string: String) -> ByteStreamable {
439455
return JSONEscapedStringStreamable(value: string)
@@ -448,6 +464,10 @@ public struct Format {
448464
}
449465
}
450466

467+
/// Write the input string list encoded as a JSON object.
468+
static public func asJSON<T: CustomStringConvertible>(_ items: [T]) -> ByteStreamable {
469+
return JSONEscapedStringListStreamable(items: items.map({ $0.description }))
470+
}
451471
/// Write the input string list encoded as a JSON object.
452472
//
453473
// FIXME: We might be able to make this more generic through the use of a "JSONEncodable" protocol.
@@ -651,7 +671,7 @@ public final class LocalFileOutputByteStream: FileOutputByteStream {
651671
///
652672
/// - Throws: FileSystemError
653673
public init(_ path: AbsolutePath, closeOnDeinit: Bool = true, buffered: Bool = true) throws {
654-
guard let filePointer = fopen(path.asString, "wb") else {
674+
guard let filePointer = fopen(path.description, "wb") else {
655675
throw FileSystemError(errno: errno)
656676
}
657677
self.filePointer = filePointer

Sources/Basic/Path.swift

Lines changed: 32 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -181,8 +181,8 @@ public struct AbsolutePath: Hashable {
181181
public func appending(components names: String...) -> AbsolutePath {
182182
// FIXME: This doesn't seem a particularly efficient way to do this.
183183
return names.reduce(self, { path, name in
184-
path.appending(component: name)
185-
})
184+
path.appending(component: name)
185+
})
186186
}
187187

188188
/// NOTE: We will most likely want to add other `appending()` methods, such
@@ -199,28 +199,12 @@ public struct AbsolutePath: Hashable {
199199
/// Root directory (whose string representation is just a path separator).
200200
public static let root = AbsolutePath("/")
201201

202-
/// Normalized string representation (the normalization rules are described
203-
/// in the documentation of the initializer). This string is never empty.
204-
public var asString: String {
205-
return _impl.string
206-
}
207-
208-
// FIXME: We should investigate if it would be more efficient to instead
209-
// return a path component iterator that does all its work lazily, moving
210-
// from one path separator to the next on-demand.
211-
//
212202
/// Returns an array of strings that make up the path components of the
213203
/// absolute path. This is the same sequence of strings as the basenames
214204
/// of each successive path component, starting from the root. Therefore
215205
/// the first path component of an absolute path is always `/`.
216206
public var components: [String] {
217-
// FIXME: This isn't particularly efficient; needs optimization, and
218-
// in fact, it might well be best to return a custom iterator so we
219-
// don't have to allocate everything up-front. It would be backed by
220-
// the path string and just return a slice at a time.
221-
return ["/"] + _impl.string.components(separatedBy: "/").filter({
222-
!$0.isEmpty
223-
})
207+
return ["/"] + _impl.components
224208
}
225209
}
226210

@@ -287,34 +271,20 @@ public struct RelativePath: Hashable {
287271
return _impl.extension
288272
}
289273

290-
/// Normalized string representation (the normalization rules are described
291-
/// in the documentation of the initializer). This string is never empty.
292-
public var asString: String {
293-
return _impl.string
294-
}
295-
296-
// FIXME: We should investigate if it would be more efficient to instead
297-
// return a path component iterator that does all its work lazily, moving
298-
// from one path separator to the next on-demand.
299-
//
300274
/// Returns an array of strings that make up the path components of the
301275
/// relative path. This is the same sequence of strings as the basenames
302276
/// of each successive path component. Therefore the returned array of
303277
/// path components is never empty; even an empty path has a single path
304278
/// component: the `.` string.
305279
public var components: [String] {
306-
// FIXME: This isn't particularly efficient; needs optimization, and
307-
// in fact, it might well be best to return a custom iterator so we
308-
// don't have to allocate everything up-front. It would be backed by
309-
// the path string and just return a slice at a time.
310-
return _impl.string.components(separatedBy: "/").filter({ !$0.isEmpty })
280+
return _impl.components
311281
}
312282
}
313283

314284
extension AbsolutePath: Codable {
315285
public func encode(to encoder: Encoder) throws {
316286
var container = encoder.singleValueContainer()
317-
try container.encode(asString)
287+
try container.encode(description)
318288
}
319289

320290
public init(from decoder: Decoder) throws {
@@ -326,7 +296,7 @@ extension AbsolutePath: Codable {
326296
extension RelativePath: Codable {
327297
public func encode(to encoder: Encoder) throws {
328298
var container = encoder.singleValueContainer()
329-
try container.encode(asString)
299+
try container.encode(description)
330300
}
331301

332302
public init(from decoder: Decoder) throws {
@@ -338,23 +308,31 @@ extension RelativePath: Codable {
338308
// Make absolute paths Comparable.
339309
extension AbsolutePath: Comparable {
340310
public static func < (lhs: AbsolutePath, rhs: AbsolutePath) -> Bool {
341-
return lhs.asString < rhs.asString
311+
return lhs.description < rhs.description
342312
}
343313
}
344314

345-
/// Make absolute paths CustomStringConvertible.
346-
extension AbsolutePath: CustomStringConvertible {
315+
/// Make absolute paths CustomStringConvertible and CustomDebugStringConvertible.
316+
extension AbsolutePath: CustomStringConvertible, CustomDebugStringConvertible {
347317
public var description: String {
318+
return _impl.string
319+
}
320+
321+
public var debugDescription: String {
348322
// FIXME: We should really be escaping backslashes and quotes here.
349-
return "<AbsolutePath:\"\(asString)\">"
323+
return "<AbsolutePath:\"\(_impl.string)\">"
350324
}
351325
}
352326

353-
/// Make relative paths CustomStringConvertible.
327+
/// Make relative paths CustomStringConvertible and CustomDebugStringConvertible.
354328
extension RelativePath: CustomStringConvertible {
355329
public var description: String {
330+
return _impl.string
331+
}
332+
333+
public var debugDescription: String {
356334
// FIXME: We should really be escaping backslashes and quotes here.
357-
return "<RelativePath:\"\(asString)\">"
335+
return "<RelativePath:\"\(_impl.string)\">"
358336
}
359337
}
360338

@@ -416,6 +394,18 @@ private struct PathImpl: Hashable {
416394
return suffix(withDot: false)
417395
}
418396

397+
// FIXME: We should investigate if it would be more efficient to instead
398+
// return a path component iterator that does all its work lazily, moving
399+
// from one path separator to the next on-demand.
400+
//
401+
fileprivate var components: [String] {
402+
// FIXME: This isn't particularly efficient; needs optimization, and
403+
// in fact, it might well be best to return a custom iterator so we
404+
// don't have to allocate everything up-front. It would be backed by
405+
// the path string and just return a slice at a time.
406+
return string.components(separatedBy: "/").filter({ !$0.isEmpty })
407+
}
408+
419409
/// Returns suffix with leading `.` if withDot is true otherwise without it.
420410
private func suffix(withDot: Bool) -> String? {
421411
// FIXME: This method seems too complicated; it should be simplified,

0 commit comments

Comments
 (0)