Skip to content

[Coding] Make Codable conformances for Optional and collections conditional. #13178

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ CHANGELOG
Swift 4.1
---------

* [SE-0166][] / [SE-0143][]

The standard library now defines the conformances of `Optional`,
`Array`, `Dictionary`, and `Set` to `Encodable` and `Decodable` as
conditional conformances, available only when their type parameters
conform to `Encodable` or `Decodable`, respectively.

* [SE-0188][]

Index types for most standard library collections now conform to `Hashable`.
Expand Down
17 changes: 1 addition & 16 deletions lib/Sema/DerivedConformanceCodable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -98,22 +98,7 @@ static CodableConformanceType typeConformsToCodable(TypeChecker &tc,
// Implicitly unwrapped optionals need to be unwrapped;
// ImplicitlyUnwrappedOptional does not need to conform to Codable directly
// -- only its inner type does.
if (nominalTypeDecl == tc.Context.getImplicitlyUnwrappedOptionalDecl() ||
// FIXME: Remove the following when conditional conformance lands.
// Some generic types in the stdlib currently conform to Codable even
// when the type they are generic on does not [Optional, Array, Set,
// Dictionary]. For synthesizing conformance, we don't want to
// consider these types as Codable if the nested type is not Codable.
// Look through the generic type parameters of these types recursively
// to avoid synthesizing code that will crash at runtime.
//
// We only want to look through generic params for these types; other
// types may validly conform to Codable even if their generic param
// types do not.
nominalTypeDecl == tc.Context.getOptionalDecl() ||
nominalTypeDecl == tc.Context.getArrayDecl() ||
nominalTypeDecl == tc.Context.getSetDecl() ||
nominalTypeDecl == tc.Context.getDictionaryDecl()) {
if (nominalTypeDecl == tc.Context.getImplicitlyUnwrappedOptionalDecl()) {
for (auto paramType : genericType->getGenericArgs()) {
if (typeConformsToCodable(tc, context, paramType, proto) != Conforms)
return DoesNotConform;
Expand Down
153 changes: 31 additions & 122 deletions stdlib/public/core/Codable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4146,153 +4146,72 @@ public extension RawRepresentable where RawValue == String, Self : Decodable {
// Optional/Collection Type Conformances
//===----------------------------------------------------------------------===//

@_inlineable // FIXME(sil-serialize-all)
@_versioned // FIXME(sil-serialize-all)
internal func assertTypeIsEncodable<T>(_ type: T.Type, in wrappingType: Any.Type) {
guard T.self is Encodable.Type else {
if T.self == Encodable.self || T.self == Codable.self {
preconditionFailure("\(wrappingType) does not conform to Encodable because Encodable does not conform to itself. You must use a concrete type to encode or decode.")
} else {
preconditionFailure("\(wrappingType) does not conform to Encodable because \(T.self) does not conform to Encodable.")
}
}
}

@_inlineable // FIXME(sil-serialize-all)
@_versioned // FIXME(sil-serialize-all)
internal func assertTypeIsDecodable<T>(_ type: T.Type, in wrappingType: Any.Type) {
guard T.self is Decodable.Type else {
if T.self == Decodable.self || T.self == Codable.self {
preconditionFailure("\(wrappingType) does not conform to Decodable because Decodable does not conform to itself. You must use a concrete type to encode or decode.")
} else {
preconditionFailure("\(wrappingType) does not conform to Decodable because \(T.self) does not conform to Decodable.")
}
}
}

// Temporary resolution for SR-5206.
//
// The following two extension on Encodable and Decodable are used below to provide static type information where we don't have any yet.
// The wrapped contents of the below generic types have to expose their Encodable/Decodable conformance via an existential cast/their metatype.
// Since those are dynamic types without static type guarantees, we cannot call generic methods taking those arguments, e.g.
//
// try container.encode((someElement as! Encodable))
//
// One way around this is to get elements to encode into `superEncoder`s and decode from `superDecoder`s because those interfaces are available via the existentials/metatypes.
// However, this direct encoding/decoding never gives containers a chance to intercept and do something custom on types.
//
// If we instead expose this custom private functionality of writing to/reading from containers directly, the containers do get this chance.

// FIXME: Remove when conditional conformance is available.
extension Encodable {
@_inlineable // FIXME(sil-serialize-all)
@_versioned // FIXME(sil-serialize-all)
internal func __encode(to container: inout SingleValueEncodingContainer) throws { try container.encode(self) }
@_inlineable // FIXME(sil-serialize-all)
@_versioned // FIXME(sil-serialize-all)
internal func __encode(to container: inout UnkeyedEncodingContainer) throws { try container.encode(self) }
@_inlineable // FIXME(sil-serialize-all)
@_versioned // FIXME(sil-serialize-all)
internal func __encode<Key>(to container: inout KeyedEncodingContainer<Key>, forKey key: Key) throws { try container.encode(self, forKey: key) }
}

// FIXME: Remove when conditional conformance is available.
extension Decodable {
// Since we cannot call these __init, we'll give the parameter a '__'.
@_inlineable // FIXME(sil-serialize-all)
@_versioned // FIXME(sil-serialize-all)
internal init(__from container: SingleValueDecodingContainer) throws { self = try container.decode(Self.self) }
@_inlineable // FIXME(sil-serialize-all)
@_versioned // FIXME(sil-serialize-all)
internal init(__from container: inout UnkeyedDecodingContainer) throws { self = try container.decode(Self.self) }
@_inlineable // FIXME(sil-serialize-all)
@_versioned // FIXME(sil-serialize-all)
internal init<Key>(__from container: KeyedDecodingContainer<Key>, forKey key: Key) throws { self = try container.decode(Self.self, forKey: key) }
}

// FIXME: Uncomment when conditional conformance is available.
extension Optional : Encodable /* where Wrapped : Encodable */ {
extension Optional : Encodable where Wrapped : Encodable {
@_inlineable // FIXME(sil-serialize-all)
public func encode(to encoder: Encoder) throws {
assertTypeIsEncodable(Wrapped.self, in: type(of: self))

var container = encoder.singleValueContainer()
switch self {
case .none: try container.encodeNil()
case .some(let wrapped): try (wrapped as! Encodable).__encode(to: &container)
case .some(let wrapped): try container.encode(wrapped)
}
}
}

extension Optional : Decodable /* where Wrapped : Decodable */ {
extension Optional : Decodable where Wrapped : Decodable {
@_inlineable // FIXME(sil-serialize-all)
public init(from decoder: Decoder) throws {
// Initialize self here so we can get type(of: self).
self = .none
assertTypeIsDecodable(Wrapped.self, in: type(of: self))

let container = try decoder.singleValueContainer()
if !container.decodeNil() {
let metaType = (Wrapped.self as! Decodable.Type)
let element = try metaType.init(__from: container)
self = .some(element as! Wrapped)
if container.decodeNil() {
self = .none
} else {
let element = try container.decode(Wrapped.self)
self = .some(element)
}
}
}

// FIXME: Uncomment when conditional conformance is available.
extension Array : Encodable /* where Element : Encodable */ {
extension Array : Encodable where Element : Encodable {
@_inlineable // FIXME(sil-serialize-all)
public func encode(to encoder: Encoder) throws {
assertTypeIsEncodable(Element.self, in: type(of: self))

var container = encoder.unkeyedContainer()
for element in self {
try (element as! Encodable).__encode(to: &container)
try container.encode(element)
}
}
}

extension Array : Decodable /* where Element : Decodable */ {
extension Array : Decodable where Element : Decodable {
@_inlineable // FIXME(sil-serialize-all)
public init(from decoder: Decoder) throws {
// Initialize self here so we can get type(of: self).
self.init()
assertTypeIsDecodable(Element.self, in: type(of: self))

let metaType = (Element.self as! Decodable.Type)
var container = try decoder.unkeyedContainer()
while !container.isAtEnd {
let element = try metaType.init(__from: &container)
self.append(element as! Element)
let element = try container.decode(Element.self)
self.append(element)
}
}
}

extension Set : Encodable /* where Element : Encodable */ {
extension Set : Encodable where Element : Encodable {
@_inlineable // FIXME(sil-serialize-all)
public func encode(to encoder: Encoder) throws {
assertTypeIsEncodable(Element.self, in: type(of: self))

var container = encoder.unkeyedContainer()
for element in self {
try (element as! Encodable).__encode(to: &container)
try container.encode(element)
}
}
}

extension Set : Decodable /* where Element : Decodable */ {
extension Set : Decodable where Element : Decodable {
@_inlineable // FIXME(sil-serialize-all)
public init(from decoder: Decoder) throws {
// Initialize self here so we can get type(of: self).
self.init()
assertTypeIsDecodable(Element.self, in: type(of: self))

let metaType = (Element.self as! Decodable.Type)
var container = try decoder.unkeyedContainer()
while !container.isAtEnd {
let element = try metaType.init(__from: &container)
self.insert(element as! Element)
let element = try container.decode(Element.self)
self.insert(element)
}
}
}
Expand Down Expand Up @@ -4321,57 +4240,49 @@ internal struct _DictionaryCodingKey : CodingKey {
}
}

extension Dictionary : Encodable /* where Key : Encodable, Value : Encodable */ {
extension Dictionary : Encodable where Key : Encodable, Value : Encodable {
@_inlineable // FIXME(sil-serialize-all)
public func encode(to encoder: Encoder) throws {
assertTypeIsEncodable(Key.self, in: type(of: self))
assertTypeIsEncodable(Value.self, in: type(of: self))

if Key.self == String.self {
// Since the keys are already Strings, we can use them as keys directly.
var container = encoder.container(keyedBy: _DictionaryCodingKey.self)
for (key, value) in self {
let codingKey = _DictionaryCodingKey(stringValue: key as! String)!
try (value as! Encodable).__encode(to: &container, forKey: codingKey)
try container.encode(value, forKey: codingKey)
}
} else if Key.self == Int.self {
// Since the keys are already Ints, we can use them as keys directly.
var container = encoder.container(keyedBy: _DictionaryCodingKey.self)
for (key, value) in self {
let codingKey = _DictionaryCodingKey(intValue: key as! Int)!
try (value as! Encodable).__encode(to: &container, forKey: codingKey)
try container.encode(value, forKey: codingKey)
}
} else {
// Keys are Encodable but not Strings or Ints, so we cannot arbitrarily convert to keys.
// We can encode as an array of alternating key-value pairs, though.
var container = encoder.unkeyedContainer()
for (key, value) in self {
try (key as! Encodable).__encode(to: &container)
try (value as! Encodable).__encode(to: &container)
try container.encode(key)
try container.encode(value)
}
}
}
}

extension Dictionary : Decodable /* where Key : Decodable, Value : Decodable */ {
extension Dictionary : Decodable where Key : Decodable, Value : Decodable {
@_inlineable // FIXME(sil-serialize-all)
public init(from decoder: Decoder) throws {
// Initialize self here so we can print type(of: self).
self.init()
assertTypeIsDecodable(Key.self, in: type(of: self))
assertTypeIsDecodable(Value.self, in: type(of: self))

if Key.self == String.self {
// The keys are Strings, so we should be able to expect a keyed container.
let container = try decoder.container(keyedBy: _DictionaryCodingKey.self)
let valueMetaType = Value.self as! Decodable.Type
for key in container.allKeys {
let value = try valueMetaType.init(__from: container, forKey: key)
self[key.stringValue as! Key] = (value as! Value)
let value = try container.decode(Value.self, forKey: key)
self[key.stringValue as! Key] = value
}
} else if Key.self == Int.self {
// The keys are Ints, so we should be able to expect a keyed container.
let valueMetaType = Value.self as! Decodable.Type
let container = try decoder.container(keyedBy: _DictionaryCodingKey.self)
for key in container.allKeys {
guard key.intValue != nil else {
Expand All @@ -4385,8 +4296,8 @@ extension Dictionary : Decodable /* where Key : Decodable, Value : Decodable */
debugDescription: "Expected Int key but found String key instead."))
}

let value = try valueMetaType.init(__from: container, forKey: key)
self[key.intValue! as! Key] = (value as! Value)
let value = try container.decode(Value.self, forKey: key)
self[key.intValue! as! Key] = value
}
} else {
// We should have encoded as an array of alternating key-value pairs.
Expand All @@ -4400,18 +4311,16 @@ extension Dictionary : Decodable /* where Key : Decodable, Value : Decodable */
}
}

let keyMetaType = (Key.self as! Decodable.Type)
let valueMetaType = (Value.self as! Decodable.Type)
while !container.isAtEnd {
let key = try keyMetaType.init(__from: &container)
let key = try container.decode(Key.self)

guard !container.isAtEnd else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath,
debugDescription: "Unkeyed container reached end before value in key-value pair."))
}

let value = try valueMetaType.init(__from: &container)
self[key as! Key] = (value as! Value)
let value = try container.decode(Value.self)
self[key] = value
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion test/IDE/complete_generic_optional.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@ struct Foo<T> {
// SR-642 Code completion does not instantiate generic arguments of a type wrapped in an optional
let x: Foo<Bar>? = Foo<Bar>()
x.#^FOO_OPTIONAL_1^#
// FOO_OPTIONAL_1: Begin completions, 7 items
// FOO_OPTIONAL_1: Begin completions, 6 items
// FOO_OPTIONAL_1-DAG: Decl[InstanceMethod]/CurrNominal/Erase[1]: ?.myFunction({#(foobar): Bar#})[#Void#]; name=myFunction(foobar: Bar)
// FOO_OPTIONAL_1: End completions