Skip to content

Commit 3b38dd9

Browse files
authored
Merge pull request #85768 from kperryua/cherry-pick-a994527
[SE-0489] Better `debugDescription` for `EncodingError` and `Decodin…
2 parents a970965 + 1b99e50 commit 3b38dd9

File tree

6 files changed

+422
-1
lines changed

6 files changed

+422
-1
lines changed

stdlib/public/core/Codable.swift

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,58 @@ extension CodingKey {
8989
public var debugDescription: String {
9090
return description
9191
}
92+
93+
/// A simplified description: the int value, if present, in square brackets.
94+
/// Otherwise, the string value by itself. Used when concatenating coding keys
95+
/// to form a path when printing debug information.
96+
/// - parameter isFirst: Whether this is the first key in a coding path, in
97+
/// which case we will omit the prepended '.' delimiter from string keys.
98+
fileprivate func _errorPresentationDescription(isFirstInCodingPath isFirst: Bool = true) -> String {
99+
if let intValue {
100+
return "[\(intValue)]"
101+
} else {
102+
let delimiter = isFirst ? "" : "."
103+
return "\(delimiter)\(stringValue._escapedForCodingKeyErrorPresentationDescription)"
104+
}
105+
}
106+
}
107+
108+
extension [any CodingKey] {
109+
/// Concatenates the elements of an array of coding keys and joins them with
110+
/// "/" separators to make them read like a path.
111+
fileprivate func _errorPresentationDescription() -> String {
112+
return (
113+
self.prefix(1).map { $0._errorPresentationDescription(isFirstInCodingPath: true) }
114+
+ self.dropFirst(1).map { $0._errorPresentationDescription(isFirstInCodingPath: false) }
115+
).joined(separator: "")
116+
}
117+
}
118+
119+
extension String {
120+
/// When printing coding paths, delimit string keys with a '.' (period). If
121+
/// the key contains a period, escape it with backticks so that it can be
122+
/// distinguished from the delimiter. Also escape backslashes and backticks
123+
/// (but *not* periods) to avoid confusion with delimiters.
124+
internal var _escapedForCodingKeyErrorPresentationDescription: String {
125+
let charactersThatNeedBackticks: Set<Character> = [".", "`", "\\"]
126+
let charactersThatNeedEscaping: Set<Character> = ["`", "\\"]
127+
assert(
128+
charactersThatNeedEscaping.isSubset(of: charactersThatNeedBackticks),
129+
"Only some characters in backticks will require further escaping to disambiguate them from the backticks"
130+
)
131+
132+
var escaped = self
133+
var needsBackticks = false
134+
for (character, index) in zip(self, indices).reversed() {
135+
if charactersThatNeedBackticks.contains(character) {
136+
needsBackticks = true
137+
if charactersThatNeedEscaping.contains(character) {
138+
escaped.insert("\\", at: index)
139+
}
140+
}
141+
}
142+
return needsBackticks ? "`\(escaped)`" : self
143+
}
92144
}
93145

94146
//===----------------------------------------------------------------------===//
@@ -3724,6 +3776,80 @@ public enum DecodingError: Error {
37243776
}
37253777
}
37263778

3779+
@available(SwiftStdlib 6.3, *)
3780+
extension EncodingError: CustomDebugStringConvertible {
3781+
/// A textual representation of this encoding error, intended for debugging.
3782+
///
3783+
/// - Important: The contents of the returned string are not guaranteed to
3784+
/// remain stable: they may arbitrarily change in any Swift release.
3785+
@available(SwiftStdlib 6.3, *)
3786+
public var debugDescription: String {
3787+
let (message, context) = switch self {
3788+
case .invalidValue(let value, let context):
3789+
(
3790+
"EncodingError.invalidValue: \(String(reflecting: value)) (\(type(of: value)))",
3791+
context
3792+
)
3793+
}
3794+
3795+
var output = message
3796+
3797+
let contextDebugDescription = context.debugDescription
3798+
3799+
if !context.codingPath.isEmpty {
3800+
output.append(". Path: \(context.codingPath._errorPresentationDescription())")
3801+
}
3802+
3803+
if !contextDebugDescription.isEmpty {
3804+
output.append(". Debug description: \(context.debugDescription)")
3805+
}
3806+
3807+
if let underlyingError = context.underlyingError {
3808+
output.append(". Underlying error: \(underlyingError)")
3809+
}
3810+
3811+
return output
3812+
}
3813+
}
3814+
3815+
@available(SwiftStdlib 6.3, *)
3816+
extension DecodingError: CustomDebugStringConvertible {
3817+
/// A textual representation of this decoding error, intended for debugging.
3818+
///
3819+
/// - Important: The contents of the returned string are not guaranteed to
3820+
/// remain stable: they may arbitrarily change in any Swift release.
3821+
@available(SwiftStdlib 6.3, *)
3822+
public var debugDescription: String {
3823+
let (message, context) = switch self {
3824+
case .typeMismatch(let expectedType, let context):
3825+
("DecodingError.typeMismatch: expected value of type \(expectedType)", context)
3826+
case .valueNotFound(let expectedType, let context):
3827+
("DecodingError.valueNotFound: Expected value of type \(expectedType) but found null instead", context)
3828+
case .keyNotFound(let expectedKey, let context):
3829+
("DecodingError.keyNotFound: Key '\(expectedKey._errorPresentationDescription())' not found in keyed decoding container", context)
3830+
case .dataCorrupted(let context):
3831+
("DecodingError.dataCorrupted: Data was corrupted", context)
3832+
}
3833+
3834+
var output = message
3835+
3836+
if !context.codingPath.isEmpty {
3837+
output.append(". Path: \(context.codingPath._errorPresentationDescription())")
3838+
}
3839+
3840+
let contextDebugDescription = context.debugDescription
3841+
if !contextDebugDescription.isEmpty {
3842+
output.append(". Debug description: \(contextDebugDescription)")
3843+
}
3844+
3845+
if let underlyingError = context.underlyingError {
3846+
output.append(". Underlying error: \(underlyingError)")
3847+
}
3848+
3849+
return output
3850+
}
3851+
}
3852+
37273853
// The following extensions allow for easier error construction.
37283854

37293855
internal struct _GenericIndexKey: CodingKey, Sendable {

test/Macros/macro_plugin_error.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ func test() {
3737
// FIXME: -module-abi-name ABI name is leaking.
3838

3939
let _: String = #fooMacro(1)
40-
// expected-error @-1 {{typeMismatch(_CompilerSwiftCompilerPluginMessageHandling.PluginToHostMessage}}
40+
// expected-error @-1 {{typeMismatch}}
4141
let _: String = #fooMacro(2)
4242
// expected-error @-1 {{failed to receive result from plugin (from macro 'fooMacro')}}
4343
let _: String = #fooMacro(3)

test/abi/macOS/arm64/stdlib.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1151,3 +1151,13 @@ Added: _swift_retain_preservemost
11511151

11521152
// New debug environment variable for the concurrency runtime.
11531153
Added: _concurrencyEnableTaskSlabAllocator
1154+
1155+
// SE-0489 Better debugDescription for EncodingError and DecodingError
1156+
Added: _$ss13DecodingErrorO16debugDescriptionSSvg
1157+
Added: _$ss13DecodingErrorO16debugDescriptionSSvpMV
1158+
Added: _$ss13DecodingErrorOs28CustomDebugStringConvertiblesMc
1159+
Added: _$ss13DecodingErrorOs28CustomDebugStringConvertiblesWP
1160+
Added: _$ss13EncodingErrorO16debugDescriptionSSvg
1161+
Added: _$ss13EncodingErrorO16debugDescriptionSSvpMV
1162+
Added: _$ss13EncodingErrorOs28CustomDebugStringConvertiblesMc
1163+
Added: _$ss13EncodingErrorOs28CustomDebugStringConvertiblesWP

test/abi/macOS/x86_64/stdlib.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1146,3 +1146,13 @@ Added: __swift_debug_metadataAllocatorPageSize
11461146

11471147
// New debug environment variable for the concurrency runtime.
11481148
Added: _concurrencyEnableTaskSlabAllocator
1149+
1150+
// SE-0489 Better debugDescription for EncodingError and DecodingError
1151+
Added: _$ss13DecodingErrorO16debugDescriptionSSvg
1152+
Added: _$ss13DecodingErrorO16debugDescriptionSSvpMV
1153+
Added: _$ss13DecodingErrorOs28CustomDebugStringConvertiblesMc
1154+
Added: _$ss13DecodingErrorOs28CustomDebugStringConvertiblesWP
1155+
Added: _$ss13EncodingErrorO16debugDescriptionSSvg
1156+
Added: _$ss13EncodingErrorO16debugDescriptionSSvpMV
1157+
Added: _$ss13EncodingErrorOs28CustomDebugStringConvertiblesMc
1158+
Added: _$ss13EncodingErrorOs28CustomDebugStringConvertiblesWP

test/api-digester/stability-stdlib-abi-without-asserts.test

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -879,4 +879,8 @@ Func _float64ToStringImpl(_:_:_:_:) is a new API without '@available'
879879
Func _int64ToStringImpl(_:_:_:_:_:) is a new API without '@available'
880880
Func _uint64ToStringImpl(_:_:_:_:_:) is a new API without '@available'
881881

882+
// New conformances from SE-0489: Better debugDescription for EncodingError and DecodingError
883+
Enum DecodingError has added a conformance to an existing protocol CustomDebugStringConvertible
884+
Enum EncodingError has added a conformance to an existing protocol CustomDebugStringConvertible
885+
882886
// *** DO NOT DISABLE OR XFAIL THIS TEST. *** (See comment above.)

0 commit comments

Comments
 (0)