Files
Nate Cook a9b9644153 Stop removing underscores from CodingKey names in InputKey (#548)
When a property wrapper is applied to a property, the property's
storage is given a name with a prefixed underscore. That is,
for a property named `x`, the actual storage is named `_x`.

That prefixed storage is what is visible through reflection, so
when building an ArgumentSet from a command type's Mirror, we
need to remove the leading underscore. This is done when creating
an InputKey for each property.

However, InputKeys are also created from CodingKeys during
decoding of a ParsableCommand. These CodingKeys _do not_ have
the leading underscore that is visible, so any underscores
that appear are actually from the declaration of the property
with an underscored name. Removing leading underscores from
CodingKey names results in a mismatch when trying to find
the decoded value.

This change simplifies the InputKey type to use an array
path instead of an indirect enum and removes the leading
underscore dropping when creating an InputKey from a CodingKey.

rdar://104928743
2023-02-02 08:33:51 -06:00

57 lines
2.0 KiB
Swift

//===----------------------------------------------------------*- swift -*-===//
//
// This source file is part of the Swift Argument Parser open source project
//
// Copyright (c) 2022 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
//
//===----------------------------------------------------------------------===//
/// Represents the path to a parsed field, annotated with ``Flag``, ``Option``
/// or ``Argument``. Fields that are directly declared on a ``ParsableComand``
/// have a path of length 1, while fields that are declared indirectly (and
/// included via an option group) have longer paths.
struct InputKey: Hashable {
/// The name of the input key.
var name: String
/// The path through the field's parents, if any.
var path: [String]
/// The full path of the field.
var fullPath: [String] { path + [name] }
/// Constructs a new input key, cleaning the name, with the specified parent.
///
/// - Parameter name: The name of the key.
/// - Parameter parent: The input key of the parent.
init(name: String, parent: InputKey?) {
// Property wrappers have underscore-prefixed names, so we remove the
// leading `_`, if present.
self.name = name.first == "_"
? String(name.dropFirst(1))
: name
self.path = parent?.fullPath ?? []
}
/// Constructs a new input key from the given coding key and parent path.
///
/// - Parameter codingKey: The base ``CodingKey``. Leading underscores in
/// `codingKey` is preserved.
/// - Parameter path: The list of ``CodingKey`` values that lead to this one.
/// `path` may be empty.
@inlinable
init(codingKey: CodingKey, path: [CodingKey]) {
self.name = codingKey.stringValue
self.path = path.map { $0.stringValue }
}
}
extension InputKey: CustomStringConvertible {
var description: String {
fullPath.joined(separator: ".")
}
}