diff --git a/include/swift/AST/Decl.h b/include/swift/AST/Decl.h index fc285c4df5cde..c3d4fb3a0293b 100644 --- a/include/swift/AST/Decl.h +++ b/include/swift/AST/Decl.h @@ -2883,6 +2883,9 @@ class NominalTypeDecl : public GenericTypeDecl, public IterableDeclContext { private: /// Predicate used to filter StoredPropertyRange. struct ToStoredProperty { + ToStoredProperty(bool skipInaccessible = false) : + skipUserInaccessible(skipInaccessible) {} + bool skipUserInaccessible; Optional operator()(Decl *decl) const; }; @@ -2892,8 +2895,9 @@ class NominalTypeDecl : public GenericTypeDecl, public IterableDeclContext { ToStoredProperty>; /// Return a collection of the stored member variables of this type. - StoredPropertyRange getStoredProperties() const { - return StoredPropertyRange(getMembers(), ToStoredProperty()); + StoredPropertyRange getStoredProperties(bool skipInaccessible = false) const { + return StoredPropertyRange(getMembers(), + ToStoredProperty(skipInaccessible)); } // Implement isa/cast/dyncast/etc. @@ -5985,7 +5989,8 @@ inline bool ValueDecl::isSettable(const DeclContext *UseDC, inline Optional NominalTypeDecl::ToStoredProperty::operator()(Decl *decl) const { if (auto var = dyn_cast(decl)) { - if (!var->isStatic() && var->hasStorage()) + if (!var->isStatic() && var->hasStorage() && + (!skipUserInaccessible || var->isUserAccessible())) return var; } diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index f2df11991868c..00d390231d9ef 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -1895,6 +1895,12 @@ ERROR(broken_equatable_eq_operator,none, "Equatable protocol is broken: no infix operator declaration for '=='", ()) ERROR(no_equal_overload_for_int,none, "no overload of '==' for Int", ()) +ERROR(broken_coding_key_requirement,none, + "CodingKey protocol is broken: unexpected requirement", ()) +ERROR(broken_encodable_requirement,none, + "Encodable protocol is broken: unexpected requirement", ()) +ERROR(broken_decodable_requirement,none, + "Decodable protocol is broken: unexpected requirement", ()) // Dynamic Self ERROR(dynamic_self_non_method,none, diff --git a/include/swift/AST/KnownIdentifiers.def b/include/swift/AST/KnownIdentifiers.def index 46e21dc432a33..c5a8b9407863b 100644 --- a/include/swift/AST/KnownIdentifiers.def +++ b/include/swift/AST/KnownIdentifiers.def @@ -29,24 +29,40 @@ IDENTIFIER(Any) IDENTIFIER(atIndexedSubscript) IDENTIFIER_(bridgeToObjectiveC) IDENTIFIER_WITH_NAME(code_, "_code") +IDENTIFIER(CodingKeys) +IDENTIFIER(container) IDENTIFIER(CoreGraphics) IDENTIFIER(CoreMedia) IDENTIFIER(CGFloat) IDENTIFIER(CVarArg) IDENTIFIER(Darwin) IDENTIFIER(dealloc) +IDENTIFIER(Decodable) +IDENTIFIER(decode) +IDENTIFIER(Decoder) +IDENTIFIER(decoder) IDENTIFIER(deinit) IDENTIFIER(Element) +IDENTIFIER(Encodable) +IDENTIFIER(encode) +IDENTIFIER(Encoder) +IDENTIFIER(encoder) IDENTIFIER(error) IDENTIFIER(forKeyedSubscript) IDENTIFIER(Foundation) +IDENTIFIER(forKey) +IDENTIFIER(from) IDENTIFIER(fromRaw) IDENTIFIER(hashValue) IDENTIFIER(init) IDENTIFIER(initialize) IDENTIFIER(initStorage) IDENTIFIER(initialValue) +IDENTIFIER(intValue) IDENTIFIER(Key) +IDENTIFIER(KeyedDecodingContainer) +IDENTIFIER(KeyedEncodingContainer) +IDENTIFIER(keyedBy) IDENTIFIER(makeIterator) IDENTIFIER(Iterator) IDENTIFIER(load) @@ -69,10 +85,16 @@ IDENTIFIER(setObject) IDENTIFIER(simd) IDENTIFIER(some) IDENTIFIER(storage) +IDENTIFIER(stringValue) IDENTIFIER(subscript) +IDENTIFIER(super) +IDENTIFIER(superDecoder) +IDENTIFIER(superEncoder) IDENTIFIER(SwiftObject) +IDENTIFIER(to) IDENTIFIER(toRaw) IDENTIFIER(Type) +IDENTIFIER(type) IDENTIFIER(Value) IDENTIFIER(value) IDENTIFIER_WITH_NAME(value_, "_value") diff --git a/include/swift/AST/KnownProtocols.def b/include/swift/AST/KnownProtocols.def index 6fd321d915979..bf945dd167cb2 100644 --- a/include/swift/AST/KnownProtocols.def +++ b/include/swift/AST/KnownProtocols.def @@ -60,6 +60,9 @@ PROTOCOL_(ErrorCodeProtocol) PROTOCOL(OptionSet) PROTOCOL_(BridgedNSError) PROTOCOL_(BridgedStoredNSError) +PROTOCOL(CodingKey) +PROTOCOL(Encodable) +PROTOCOL(Decodable) PROTOCOL_(ObjectiveCBridgeable) PROTOCOL_(DestructorSafeContainer) diff --git a/include/swift/AST/KnownStdlibTypes.def b/include/swift/AST/KnownStdlibTypes.def index 1fe89d46c546f..1d4ad85d35aab 100644 --- a/include/swift/AST/KnownStdlibTypes.def +++ b/include/swift/AST/KnownStdlibTypes.def @@ -65,4 +65,9 @@ KNOWN_STDLIB_TYPE_DECL(Unmanaged, NominalTypeDecl, 1) KNOWN_STDLIB_TYPE_DECL(Never, NominalTypeDecl, 0) +KNOWN_STDLIB_TYPE_DECL(Encoder, ProtocolDecl, 1) +KNOWN_STDLIB_TYPE_DECL(Decoder, ProtocolDecl, 1) +KNOWN_STDLIB_TYPE_DECL(KeyedEncodingContainer, NominalTypeDecl, 1) +KNOWN_STDLIB_TYPE_DECL(KeyedDecodingContainer, NominalTypeDecl, 1) + #undef KNOWN_STDLIB_TYPE_DECL diff --git a/lib/AST/ASTContext.cpp b/lib/AST/ASTContext.cpp index 545630b07f42c..86ea43d153475 100644 --- a/lib/AST/ASTContext.cpp +++ b/lib/AST/ASTContext.cpp @@ -739,8 +739,8 @@ ProtocolDecl *ASTContext::getProtocol(KnownProtocolKind kind) const { // Find all of the declarations with this name in the appropriate module. SmallVector results; - // _BridgedNSError, _BridgedStoredNSError, and _ErrorCodeProtocol - // are in the Foundation module. + // _BridgedNSError, _BridgedStoredNSError, and _ErrorCodeProtocol are all in + // the Foundation module. if (kind == KnownProtocolKind::BridgedNSError || kind == KnownProtocolKind::BridgedStoredNSError || kind == KnownProtocolKind::ErrorCodeProtocol) { diff --git a/lib/AST/Decl.cpp b/lib/AST/Decl.cpp index 246a1dba41d2a..21bc66eb8ac2b 100644 --- a/lib/AST/Decl.cpp +++ b/lib/AST/Decl.cpp @@ -2092,9 +2092,42 @@ bool NominalTypeDecl::derivesProtocolConformance(ProtocolDecl *protocol) const { case KnownProtocolKind::BridgedNSError: return isObjC() && enumDecl->hasOnlyCasesWithoutAssociatedValues(); + // Enums without associated values and enums with a raw type of String + // or Int can explicitly derive CodingKey conformance. + case KnownProtocolKind::CodingKey: { + Type rawType = enumDecl->getRawType(); + if (rawType) { + auto parentDC = enumDecl->getDeclContext(); + ASTContext &C = parentDC->getASTContext(); + + auto nominal = rawType->getAnyNominal(); + return nominal == C.getStringDecl() || nominal == C.getIntDecl(); + } + + // Empty enums are allowed to conform as well. + return enumDecl->getAllElements().empty() || + enumDecl->hasOnlyCasesWithoutAssociatedValues(); + } + default: return false; } + } else if (isa(this) || isa(this)) { + // Structs and classes can explicitly derive Encodable and Decodable + // conformance (explicitly meaning we can synthesize an implementation if + // a type conforms manually). + if (*knownProtocol == KnownProtocolKind::Encodable || + *knownProtocol == KnownProtocolKind::Decodable) { + // FIXME: This is not actually correct. We cannot promise to always + // provide a witness here for all structs and classes. Unfortunately, + // figuring out whether this is actually possible requires much more + // context -- a TypeChecker and the parent decl context at least -- and is + // tightly coupled to the logic within DerivedConformance. + // This unfortunately means that we expect a witness even if one will not + // be produced, which requires DerivedConformance::deriveCodable to output + // its own diagnostics. + return true; + } } return false; } diff --git a/lib/IRGen/GenMeta.cpp b/lib/IRGen/GenMeta.cpp index c22f9d5c2c35e..6f8b1a61d5e09 100644 --- a/lib/IRGen/GenMeta.cpp +++ b/lib/IRGen/GenMeta.cpp @@ -5548,6 +5548,9 @@ SpecialProtocol irgen::getSpecialProtocolID(ProtocolDecl *P) { case KnownProtocolKind::BridgedNSError: case KnownProtocolKind::BridgedStoredNSError: case KnownProtocolKind::ErrorCodeProtocol: + case KnownProtocolKind::CodingKey: + case KnownProtocolKind::Encodable: + case KnownProtocolKind::Decodable: return SpecialProtocol::None; } diff --git a/lib/Sema/CMakeLists.txt b/lib/Sema/CMakeLists.txt index 7d87dfa8b7bcf..a4417917a10df 100644 --- a/lib/Sema/CMakeLists.txt +++ b/lib/Sema/CMakeLists.txt @@ -16,6 +16,8 @@ add_swift_library(swiftSema STATIC ConstraintGraph.cpp ConstraintLocator.cpp ConstraintSystem.cpp + DerivedConformanceCodable.cpp + DerivedConformanceCodingKey.cpp DerivedConformanceEquatableHashable.cpp DerivedConformanceError.cpp DerivedConformanceRawRepresentable.cpp diff --git a/lib/Sema/DerivedConformanceCodable.cpp b/lib/Sema/DerivedConformanceCodable.cpp new file mode 100644 index 0000000000000..483bf935176ff --- /dev/null +++ b/lib/Sema/DerivedConformanceCodable.cpp @@ -0,0 +1,1022 @@ +//===--- DerivedConformanceCodable.cpp - Derived Codable ------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2017 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 +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// +// +// This file implements explicit derivation of the Encodable and Decodable +// protocols for a struct or class. +// +//===----------------------------------------------------------------------===// +// +#include "TypeChecker.h" +#include "swift/AST/Decl.h" +#include "swift/AST/Expr.h" +#include "swift/AST/Module.h" +#include "swift/AST/ParameterList.h" +#include "swift/AST/Pattern.h" +#include "swift/AST/Stmt.h" +#include "swift/AST/Types.h" +#include "DerivedConformances.h" + +using namespace swift; +using namespace DerivedConformance; + +/// Returns whether the type represented by the given ClassDecl inherits from a +/// type which conforms to the given protocol. +/// +/// \param type The \c ClassDecl whose superclass to look up. +/// +/// \param proto The protocol to check conformance for. +static bool inheritsConformanceTo(ClassDecl *type, ProtocolDecl *proto) { + if (!type->hasSuperclass()) + return false; + + auto &C = type->getASTContext(); + auto *superclassDecl = type->getSuperclassDecl(); + auto *superclassModule = superclassDecl->getModuleContext(); + return (bool)superclassModule->lookupConformance(type->getSuperclass(), + proto, + C.getLazyResolver()); +} + +/// Returns whether the superclass of the given class conforms to Encodable. +/// +/// \param type The \c ClassDecl whose superclass to check. +static bool superclassIsEncodable(ClassDecl *type) { + auto &C = type->getASTContext(); + return inheritsConformanceTo(type, + C.getProtocol(KnownProtocolKind::Encodable)); +} + +/// Returns whether the superclass of the given class conforms to Decodable. +/// +/// \param type The \c ClassDecl whose superclass to check. +static bool superclassIsDecodable(ClassDecl *type) { + auto &C = type->getASTContext(); + return inheritsConformanceTo(type, + C.getProtocol(KnownProtocolKind::Decodable)); +} + +/// Validates that all the variables declared in the given list of declarations +/// conform to the given protocol. +/// +/// Produces a diagnostic on the given typechecker for every var which does not +/// conform. Calls a success callback for every var which does conform. +/// +/// \param tc The typechecker to use in validating {En,Decodable} conformance. +/// +/// \param context The \c DeclContext the var declarations belong to. +/// +/// \param vars The var range to validate. +/// +/// \param proto The protocol to check conformance to. +/// +/// \param callback A callback to call on every valid var decl. +template +static bool +validateVarsConformToProtocol(TypeChecker &tc, DeclContext *context, + NominalTypeDecl::StoredPropertyRange &vars, + ProtocolDecl *proto, ValidVarCallback &callback) { + bool allConform = true; + for (auto varDecl : vars) { + // If the decl doesn't yet have a type, we may be seeing it before the type + // checker has gotten around to evaluating its type. For example: + // + // func foo() { + // let b = Bar(from: decoder) // <- evaluates Bar conformance to Codable, + // // forcing derivation + // } + // + // struct Bar : Codable { + // var x: Int // <- we get to valuate x's var decl here, but its type + // // hasn't yet been evaluated + // } + // + // Validate the decl eagerly. + if (!varDecl->hasType()) + tc.validateDecl(varDecl); + + // If the var decl didn't validate, it may still not have a type; confirm it + // has a type before ensuring the type conforms to Codable. + if (!varDecl->hasType() || + !tc.conformsToProtocol(varDecl->getType(), proto, context, + ConformanceCheckFlags::Used)) { + // TODO: We should produce a diagnostic note here explaining that we found + // a var not conforming to Codable. + allConform = false; + continue; + } + + callback(varDecl); + } + + return allConform; +} + +/// Validates the given CodingKeys enum decl by ensuring its cases are a 1-to-1 +/// match with the stored vars of the given type. +/// +/// \param tc The typechecker to use in validating {En,Decodable} conformance. +/// +/// \param codingKeysDecl The \c CodingKeys enum decl to validate. +/// +/// \param type The nominal type decl to validate the \c CodingKeys against. +/// +/// \param proto The {En,De}codable protocol to validate all the keys conform +/// to. +static bool validateCodingKeysEnum(TypeChecker &tc, EnumDecl *codingKeysDecl, + NominalTypeDecl *type, ProtocolDecl *proto) { + // Look through all var decls in the given type. + // * Filter out lazy/computed vars (currently already done by + // getStoredProperties). + // * Filter out ones which are present in the given decl (by name). + // + // If any of the entries in the CodingKeys decl are not present in the type + // by name, then this decl doesn't match. + // If there are any vars left in the type, then this decl doesn't match. + // + // NOTE: If we change the behavior to ignore vars with default values, then we + // can further filter out the type names to remove those which + // correspond to vars with default values. + llvm::SmallDenseSet names; + + auto storedProperties = type->getStoredProperties(/*skipInaccessible=*/true); + auto validVarCallback = [&names](VarDecl *varDecl) { + names.insert(varDecl->getName()); + }; + + if (!validateVarsConformToProtocol(tc, type->getDeclContext(), + storedProperties, proto, validVarCallback)) + return false; + + for (auto elt : codingKeysDecl->getAllElements()) { + auto it = names.find(elt->getName()); + if (it == names.end()) { + // TODO: Produce diagnostic here complaining that the CodingKeys enum + // contains a case which does not correspond to a var. + // TODO: Investigate typo-correction here; perhaps the case name was + // misspelled and we can provide a fix-it. + return false; + } + + names.erase(it); + } + + // TODO: Produce diagnostic here complaining that there are vars which are not + // listed in the CodingKeys enum. + return names.empty(); +} + +/// Returns whether the given type has a valid nested \c CodingKeys enum. +/// +/// If the type has an invalid \c CodingKeys entity, produces diagnostics to +/// complain about the error. In this case, the error result will be true -- in +/// the case where we don't have a valid CodingKeys enum and have produced +/// diagnostics here, we don't want to then attempt to synthesize a CodingKeys +/// enum. +/// +/// \param tc The typechecker to use in validating {En,Decodable} conformance. +/// +/// \param type The type decl whose nested \c CodingKeys type to validate. +/// +/// \param proto The {En,De}codable protocol to ensure the properties matching +/// the keys conform to. +static std::pair +hasValidCodingKeysEnum(TypeChecker &tc, NominalTypeDecl *type, + ProtocolDecl *proto) { + auto &C = tc.Context; + auto codingKeysDecls = type->lookupDirect(DeclName(C.Id_CodingKeys)); + if (codingKeysDecls.empty()) + return {/* has type? */ false, /* error? */ false}; + + // Only ill-formed code would produce multiple results for this lookup. + // This would get diagnosed later anyway, so we're free to only look at the + // first result here. + auto result = codingKeysDecls.front(); + + auto *codingKeysTypeDecl = dyn_cast(result); + if (!codingKeysTypeDecl) { + // TODO: Produce a diagnostic complaining that the "CodingKeys" entity we + // found is not a type. + return {/* has type? */ true, /* error? */ true}; + } + + // Ensure that the type we found conforms to the CodingKey protocol. + auto *codingKeyProto = C.getProtocol(KnownProtocolKind::CodingKey); + auto codingKeysType = codingKeysTypeDecl->getDeclaredInterfaceType(); + if (!tc.conformsToProtocol(codingKeysType, codingKeyProto, + type->getDeclContext(), + ConformanceCheckFlags::Used)) { + // TODO: Produce a diagnostic complaining that the "CodingKeys" entity we + // found does not conform to CodingKey. + return {/* has type? */ true, /* error? */ true}; + } + + // CodingKeys should eventually be an enum. If it's a typealias, we'll need to + // follow it. + auto *codingKeysEnum = dyn_cast(result); + if (auto *typealias = dyn_cast(result)) { + // TODO: Do we have to follow through multiple layers of typealiases + // here? Or will getCanonicalType() do that for us? + auto canType = codingKeysType->getCanonicalType(); + assert(canType); + + codingKeysEnum = dyn_cast(codingKeysType->getAnyNominal()); + } + + if (!codingKeysEnum) { + // TODO: Produce a diagnostic complaining that we cannot derive Codable + // with a non-enum CodingKeys type. + return {/* has type? */ true, /* error? */ true}; + } + + bool valid = validateCodingKeysEnum(tc, codingKeysEnum, type, proto); + return {/* has type? */ true, /* error? */ !valid}; +} + +/// Synthesizes a new \c CodingKeys enum based on the {En,De}codable members of +/// the given type (\c nullptr if unable to synthesize). +/// +/// If able to synthesize the enum, adds it directly to \c type. +/// +/// \param tc The typechecker to use in validating {En,De}codable conformance. +/// +/// \param type The nominal type decl whose nested \c CodingKeys type to +/// synthesize. +/// +/// \param proto The {En,De}codable protocol to validate all the keys conform +/// to. +static EnumDecl *synthesizeCodingKeysEnum(TypeChecker &tc, + NominalTypeDecl *type, + ProtocolDecl *proto) { + auto &C = tc.Context; + auto *typeDC = cast(type); + + // We want to look through all the var declarations of this type to create + // enum cases based on those var names. + auto *codingKeyProto = C.getProtocol(KnownProtocolKind::CodingKey); + auto *codingKeyType = codingKeyProto->getDeclaredType(); + TypeLoc protoTypeLoc[1] = {TypeLoc::withoutLoc(codingKeyType)}; + MutableArrayRef inherited = C.AllocateCopy(protoTypeLoc); + + auto *enumDecl = new (C) EnumDecl(SourceLoc(), C.Id_CodingKeys, SourceLoc(), + inherited, nullptr, typeDC); + enumDecl->setImplicit(); + enumDecl->setAccessibility(Accessibility::Private); + + auto *enumDC = cast(enumDecl); + auto *mutableEnumDC = cast(enumDecl); + + // For classes which inherit from something Encodable or Decodable, we + // provide case `super` as the first key (to be used in encoding super). + auto *classDecl = dyn_cast(type); + if (classDecl && + (superclassIsEncodable(classDecl) || superclassIsDecodable(classDecl))) { + // TODO: Ensure the class doesn't already have or inherit a variable named + // "`super`"; otherwise we will generate an invalid enum. In that case, + // diagnose and bail. + auto *super = new (C) EnumElementDecl(SourceLoc(), C.Id_super, TypeLoc(), + /*HasArgumentType=*/false, + SourceLoc(), nullptr, enumDC); + super->setImplicit(); + mutableEnumDC->addMember(super); + } + + // Each of these vars needs a case in the enum. For each var decl, if the type + // conforms to {En,De}codable, add it to the enum. + auto storedProperties = type->getStoredProperties(/*skipInaccessible=*/true); + auto validVarCallback = [&C, &enumDC, &mutableEnumDC](VarDecl *varDecl) { + auto *elt = new (C) EnumElementDecl(SourceLoc(), varDecl->getName(), + TypeLoc(), /*HasArgumentType=*/false, + SourceLoc(), nullptr, enumDC); + elt->setImplicit(); + mutableEnumDC->addMember(elt); + }; + + if (!validateVarsConformToProtocol(tc, type->getDeclContext(), + storedProperties, proto, validVarCallback)) + return nullptr; + + // Forcibly derive conformance to CodingKey. + tc.checkConformancesInContext(enumDC, mutableEnumDC); + + // Add to the type. + cast(type)->addMember(enumDecl); + return enumDecl; +} + +/// Creates a new var decl representing +/// +/// var/let container : containerBase +/// +/// \c containerBase is the name of the type to use as the base (either +/// \c KeyedEncodingContainer or \c KeyedDecodingContainer). +/// +/// \param C The AST context to create the decl in. +/// +/// \param DC The \c DeclContext to create the decl in. +/// +/// \param keyedContainerDecl The generic type to bind the key type in. +/// +/// \param keyType The key type to bind to the container type. +/// +/// \param isLet Whether to declare the variable as immutable. +static VarDecl *createKeyedContainer(ASTContext &C, DeclContext *DC, + NominalTypeDecl *keyedContainerDecl, + Type keyType, bool isLet) { + // Bind Keyed*Container to Keyed*Container + Type boundType[1] = {keyType}; + auto containerType = BoundGenericType::get(keyedContainerDecl, Type(), + C.AllocateCopy(boundType)); + + // let container : Keyed*Container + auto *containerDecl = new (C) VarDecl(/*IsStatic=*/false, /*IsLet=*/isLet, + /*IsCaptureList=*/false, SourceLoc(), + C.Id_container, containerType, DC); + containerDecl->setImplicit(); + containerDecl->setInterfaceType(containerType); + return containerDecl; +} + +/// Creates a new \c CallExpr representing +/// +/// base.container(keyedBy: CodingKeys.self) +/// +/// \param C The AST context to create the expression in. +/// +/// \param DC The \c DeclContext to create any decls in. +/// +/// \param base The base expression to make the call on. +/// +/// \param returnType The return type of the call. +/// +/// \param param The parameter to the call. +static CallExpr *createContainerKeyedByCall(ASTContext &C, DeclContext *DC, + Expr *base, Type returnType, + NominalTypeDecl *param) { + // (keyedBy:) + auto *keyedByDecl = new (C) ParamDecl(/*IsLet=*/true, SourceLoc(), + SourceLoc(), C.Id_keyedBy, SourceLoc(), + C.Id_keyedBy, returnType, DC); + keyedByDecl->setImplicit(); + keyedByDecl->setInterfaceType(returnType); + + // container(keyedBy:) method name + auto *paramList = ParameterList::createWithoutLoc(keyedByDecl); + DeclName callName(C, C.Id_container, paramList); + + // base.container(keyedBy:) expr + auto *unboundCall = new (C) UnresolvedDotExpr(base, SourceLoc(), callName, + DeclNameLoc(), + /*Implicit=*/true); + + // CodingKeys.self expr + auto *codingKeysExpr = new (C) DeclRefExpr(ConcreteDeclRef(param), + DeclNameLoc(), /*Implicit=*/true); + auto *codingKeysMetaTypeExpr = new (C) DotSelfExpr(codingKeysExpr, + SourceLoc(), SourceLoc()); + + // Full bound base.container(keyedBy: CodingKeys.self) call + Expr *args[1] = {codingKeysMetaTypeExpr}; + Identifier argLabels[1] = {C.Id_keyedBy}; + return CallExpr::createImplicit(C, unboundCall, C.AllocateCopy(args), + C.AllocateCopy(argLabels)); +} + +/// Synthesizes the body for `func encode(to encoder: Encoder) throws`. +/// +/// \param encodeDecl The function decl whose body to synthesize. +static void deriveBodyEncodable_encode(AbstractFunctionDecl *encodeDecl) { + // struct Foo : Codable { + // var x: Int + // var y: String + // + // // Already derived by this point if possible. + // @derived enum CodingKeys : CodingKey { + // case x + // case y + // } + // + // @derived func encode(to encoder: Encoder) throws { + // var container = encoder.container(keyedBy: CodingKeys.self) + // try container.encode(x, forKey: .x) + // try container.encode(y, forKey: .y) + // } + // } + + // The enclosing type decl. + auto *typeDecl = cast(encodeDecl->getDeclContext()); + + auto *funcDC = cast(encodeDecl); + auto &C = funcDC->getASTContext(); + + // We'll want the CodingKeys enum for this type. + auto *codingKeysDecl = typeDecl->lookupDirect(DeclName(C.Id_CodingKeys))[0]; + // We should have bailed already if: + // a) The type does not have CodingKeys + assert(codingKeysDecl && "Missing CodingKeys decl."); + // b) The type is not an enum + auto *codingKeysEnum = cast(codingKeysDecl); + + SmallVector statements; + + // Generate a reference to containerExpr ahead of time in case there are no + // properties to encode or decode, but the type is a class which inherits from + // something Codable and needs to encode super. + + // let container : KeyedEncodingContainer + auto codingKeysType = codingKeysEnum->getDeclaredType(); + auto *containerDecl = createKeyedContainer(C, funcDC, + C.getKeyedEncodingContainerDecl(), + codingKeysType, /*isLet=*/false); + + auto *containerExpr = new (C) DeclRefExpr(ConcreteDeclRef(containerDecl), + DeclNameLoc(), /*Implicit=*/true, + AccessSemantics::DirectToStorage); + + auto enumElements = codingKeysEnum->getAllElements(); + if (!enumElements.empty()) { + // Need to generate + // `let container = encoder.container(keyedBy: CodingKeys.self)` + // `let container` (containerExpr) is generated above. + + // encoder + auto encoderParam = encodeDecl->getParameterList(1)->get(0); + auto *encoderExpr = new (C) DeclRefExpr(ConcreteDeclRef(encoderParam), + DeclNameLoc(), /*Implicit=*/true); + + // Bound encoder.container(keyedBy: CodingKeys.self) call + auto containerType = containerDecl->getInterfaceType(); + auto *callExpr = createContainerKeyedByCall(C, funcDC, encoderExpr, + containerType, codingKeysEnum); + + // Full `let container = encoder.container(keyedBy: CodingKeys.self)` + // binding. + auto *containerPattern = new (C) NamedPattern(containerDecl, + /*implicit=*/true); + auto *bindingDecl = PatternBindingDecl::create(C, SourceLoc(), + StaticSpellingKind::None, + SourceLoc(), + containerPattern, callExpr, + funcDC); + statements.push_back(bindingDecl); + statements.push_back(containerDecl); + + // Now need to generate `try container.encode(x, forKey: .x)` for all + // existing properties. + for (auto *elt : enumElements) { + // Only ill-formed code would produce multiple results for this lookup. + // This would get diagnosed later anyway, so we're free to only look at + // the first result here. + auto matchingVars = typeDecl->lookupDirect(DeclName(elt->getName())); + + // self.x + auto *selfRef = createSelfDeclRef(encodeDecl); + auto *varExpr = new (C) MemberRefExpr(selfRef, SourceLoc(), + ConcreteDeclRef(matchingVars[0]), + DeclNameLoc(), /*Implicit=*/true); + + // CodingKeys.x + auto *eltRef = new (C) DeclRefExpr(elt, DeclNameLoc(), /*implicit=*/true); + auto *metaTyRef = TypeExpr::createImplicit(codingKeysType, C); + auto *keyExpr = new (C) DotSyntaxCallExpr(eltRef, SourceLoc(), metaTyRef); + + // encode(_:forKey:) + SmallVector argNames{Identifier(), C.Id_forKey}; + DeclName name(C, C.Id_encode, argNames); + auto *encodeCall = new (C) UnresolvedDotExpr(containerExpr, SourceLoc(), + name, DeclNameLoc(), + /*Implicit=*/true); + + // container.encode(self.x, forKey: CodingKeys.x) + Expr *args[2] = {varExpr, keyExpr}; + auto *callExpr = CallExpr::createImplicit(C, encodeCall, + C.AllocateCopy(args), + C.AllocateCopy(argNames)); + + // try container.encode(self.x, forKey: CodingKeys.x) + auto *tryExpr = new (C) TryExpr(SourceLoc(), callExpr, Type(), + /*Implicit=*/true); + statements.push_back(tryExpr); + } + } + + // Classes which inherit from something Codable should encode super as well. + auto *classDecl = dyn_cast(typeDecl); + if (classDecl && superclassIsEncodable(classDecl)) { + // Need to generate `try super.encode(to: container.superEncoder())` + + // superEncoder() + auto *method = new (C) UnresolvedDeclRefExpr( + DeclName(C.Id_superEncoder), DeclRefKind::Ordinary, DeclNameLoc()); + + // container.superEncoder() + auto *superEncoderRef = new (C) DotSyntaxCallExpr(containerExpr, + SourceLoc(), method); + + // encode(to:) expr + auto *encodeDeclRef = new (C) DeclRefExpr(ConcreteDeclRef(encodeDecl), + DeclNameLoc(), /*Implicit=*/true); + + // super + auto *superRef = new (C) SuperRefExpr(encodeDecl->getImplicitSelfDecl(), + SourceLoc(), /*Implicit=*/true); + + // super.encode(to:) + auto *encodeCall = new (C) DotSyntaxCallExpr(superRef, SourceLoc(), + encodeDeclRef); + + // super.encode(to: container.superEncoder()) + Expr *args[1] = {superEncoderRef}; + Identifier argLabels[1] = {C.Id_to}; + auto *callExpr = CallExpr::createImplicit(C, encodeCall, + C.AllocateCopy(args), + C.AllocateCopy(argLabels)); + + // try super.encode(to: container.superEncoder()) + auto *tryExpr = new (C) TryExpr(SourceLoc(), callExpr, Type(), + /*Implicit=*/true); + statements.push_back(tryExpr); + } + + auto *body = BraceStmt::create(C, SourceLoc(), statements, SourceLoc(), + /*implicit=*/true); + encodeDecl->setBody(body); +} + +/// Synthesizes a function declaration for `encode(to: Encoder) throws` with a +/// lazily synthesized body for the given type. +/// +/// Adds the function declaration to the given type before returning it. +/// +/// \param tc The type checker whose AST context to synthesize the decl in. +/// +/// \param parentDecl The parent declaration of the type. +/// +/// \param type The nominal type to synthesize the function for. +static FuncDecl *deriveEncodable_encode(TypeChecker &tc, Decl *parentDecl, + NominalTypeDecl *type) { + auto &C = tc.Context; + auto *typeDC = cast(type); + + // Expected type: (Self) -> (Encoder) throws -> () + // Constructed as: func type + // input: Self + // throws + // output: function type + // input: Encoder + // output: () + // Create from the inside out: + + // (to: Encoder) + auto encoderType = C.getEncoderDecl()->getDeclaredInterfaceType(); + auto inputTypeElt = TupleTypeElt(encoderType, C.Id_to); + auto inputType = TupleType::get(ArrayRef(inputTypeElt), C); + + // throws + auto extInfo = FunctionType::ExtInfo(FunctionTypeRepresentation::Swift, + /*Throws=*/true); + // () + auto returnType = TupleType::getEmpty(C); + + // (to: Encoder) throws -> () + auto innerType = FunctionType::get(inputType, returnType, extInfo); + + // Params: (self [implicit], Encoder) + auto *selfDecl = ParamDecl::createSelf(SourceLoc(), typeDC); + auto *encoderParam = new (C) ParamDecl(/*isLet=*/true, SourceLoc(), + SourceLoc(), C.Id_to, SourceLoc(), + C.Id_encoder, encoderType, typeDC); + encoderParam->setInterfaceType(encoderType); + + ParameterList *params[] = {ParameterList::createWithoutLoc(selfDecl), + ParameterList::createWithoutLoc(encoderParam)}; + + // Func name: encode(to: Encoder) + DeclName name(C, C.Id_encode, params[1]); + auto *encodeDecl = FuncDecl::create(C, SourceLoc(), StaticSpellingKind::None, + SourceLoc(), name, SourceLoc(), + /*Throws=*/true, SourceLoc(), SourceLoc(), + nullptr, params, + TypeLoc::withoutLoc(returnType), typeDC); + encodeDecl->setImplicit(); + encodeDecl->setBodySynthesizer(deriveBodyEncodable_encode); + + // This method should be marked as 'override' for classes inheriting Encodable + // conformance from a parent class. + auto *classDecl = dyn_cast(type); + if (classDecl && superclassIsEncodable(classDecl)) { + auto *attr = new (C) SimpleDeclAttr(/*IsImplicit=*/true); + encodeDecl->getAttrs().add(attr); + } + + // Evaluate the type of Self in (Self) -> (Encoder) throws -> (). + Type selfType = typeDC->getDeclaredInterfaceType(); + Type interfaceType; + if (auto sig = typeDC->getGenericSignatureOfContext()) { + // Evaluate the below, but in a generic environment (if Self is generic). + encodeDecl->setGenericEnvironment(typeDC->getGenericEnvironmentOfContext()); + interfaceType = GenericFunctionType::get(sig, selfType, innerType, + FunctionType::ExtInfo()); + } else { + // (Self) -> innerType == (Encoder) throws -> () + interfaceType = FunctionType::get(selfType, innerType); + } + + encodeDecl->setInterfaceType(interfaceType); + encodeDecl->setAccessibility(std::max(type->getFormalAccess(), + Accessibility::Internal)); + + // If the type was not imported, the derived conformance is either from the + // type itself or an extension, in which case we will emit the declaration + // normally. + if (type->hasClangNode()) + tc.Context.addExternalDecl(encodeDecl); + + cast(type)->addMember(encodeDecl); + return encodeDecl; +} + +/// Synthesizes the body for `init(from decoder: Decoder) throws`. +/// +/// \param initDecl The function decl whose body to synthesize. +static void deriveBodyDecodable_init(AbstractFunctionDecl *initDecl) { + // struct Foo : Codable { + // var x: Int + // var y: String + // + // // Already derived by this point if possible. + // @derived enum CodingKeys : CodingKey { + // case x + // case y + // } + // + // @derived init(from decoder: Decoder) throws { + // let container = try decoder.container(keyedBy: CodingKeys.self) + // x = try container.decode(Type.self, forKey: .x) + // y = try container.decode(Type.self, forKey: .y) + // } + // } + + // The enclosing type decl. + auto *typeDecl = cast(initDecl->getDeclContext()); + + auto *funcDC = cast(initDecl); + auto &C = funcDC->getASTContext(); + + // We'll want the CodingKeys enum for this type. + auto *codingKeysDecl = typeDecl->lookupDirect(DeclName(C.Id_CodingKeys))[0]; + // We should have bailed already if: + // a) The type does not have CodingKeys + assert(codingKeysDecl && "Missing CodingKeys decl."); + // b) The type is not an enum + auto *codingKeysEnum = cast(codingKeysDecl); + + // Generate a reference to containerExpr ahead of time in case there are no + // properties to encode or decode, but the type is a class which inherits from + // something Codable and needs to decode super. + + // let container : KeyedDecodingContainer + auto codingKeysType = codingKeysEnum->getDeclaredType(); + auto *containerDecl = createKeyedContainer(C, funcDC, + C.getKeyedDecodingContainerDecl(), + codingKeysType, /*isLet=*/true); + + auto *containerExpr = new (C) DeclRefExpr(ConcreteDeclRef(containerDecl), + DeclNameLoc(), /*Implicit=*/true, + AccessSemantics::DirectToStorage); + + SmallVector statements; + auto enumElements = codingKeysEnum->getAllElements(); + if (!enumElements.empty()) { + // Need to generate + // `let container = try decoder.container(keyedBy: CodingKeys.self)` + // `let container` (containerExpr) is generated above. + + // decoder + auto decoderParam = initDecl->getParameterList(1)->get(0); + auto *decoderExpr = new (C) DeclRefExpr(ConcreteDeclRef(decoderParam), + DeclNameLoc(), /*Implicit=*/true); + + // Bound decoder.container(keyedBy: CodingKeys.self) call + auto containerType = containerDecl->getInterfaceType(); + auto *callExpr = createContainerKeyedByCall(C, funcDC, decoderExpr, + containerType, codingKeysEnum); + + // try decoder.container(keyedBy: CodingKeys.self) + auto *tryExpr = new (C) TryExpr(SourceLoc(), callExpr, Type(), + /*implicit=*/true); + + // Full `let container = decoder.container(keyedBy: CodingKeys.self)` + // binding. + auto *containerPattern = new (C) NamedPattern(containerDecl, + /*implicit=*/true); + auto *bindingDecl = PatternBindingDecl::create(C, SourceLoc(), + StaticSpellingKind::None, + SourceLoc(), + containerPattern, tryExpr, + funcDC); + statements.push_back(bindingDecl); + statements.push_back(containerDecl); + + // Now need to generate `x = try container.encode(Type.self, forKey: .x)` + // for all existing properties. + for (auto *elt : enumElements) { + // Only ill-formed code would produce multiple results for this lookup. + // This would get diagnosed later anyway, so we're free to only look at + // the first result here. + auto matchingVars = typeDecl->lookupDirect(DeclName(elt->getName())); + auto *varDecl = cast(matchingVars[0]); + + // Don't output a decode statement for a var let with a default value. + if (varDecl->isLet() && varDecl->getParentInitializer() != nullptr) + continue; + + // Type.self (where Type === type(of: x) + auto varType = varDecl->getType(); + auto *metaTyRef = TypeExpr::createImplicit(varType, C); + auto *typeExpr = new (C) DotSelfExpr(metaTyRef, SourceLoc(), SourceLoc(), + varType); + + // CodingKeys.x + auto *eltRef = new (C) DeclRefExpr(elt, DeclNameLoc(), /*implicit=*/true); + metaTyRef = TypeExpr::createImplicit(codingKeysType, C); + auto *keyExpr = new (C) DotSyntaxCallExpr(eltRef, SourceLoc(), metaTyRef); + + // container.decode(_:forKey:) + SmallVector argNames{Identifier(), C.Id_forKey}; + DeclName name(C, C.Id_decode, argNames); + auto *decodeCall = new (C) UnresolvedDotExpr(containerExpr, SourceLoc(), + name, DeclNameLoc(), + /*Implicit=*/true); + + // container.decode(Type.self, forKey: CodingKeys.x) + Expr *args[2] = {typeExpr, keyExpr}; + auto *callExpr = CallExpr::createImplicit(C, decodeCall, + C.AllocateCopy(args), + C.AllocateCopy(argNames)); + + // try container.decode(Type.self, forKey: CodingKeys.x) + auto *tryExpr = new (C) TryExpr(SourceLoc(), callExpr, Type(), + /*Implicit=*/true); + + auto *selfRef = createSelfDeclRef(initDecl); + auto *varExpr = new (C) UnresolvedDotExpr( + selfRef, SourceLoc(), DeclName(varDecl->getName()), DeclNameLoc(), + /*implicit=*/true); + auto *assignExpr = new (C) AssignExpr(varExpr, SourceLoc(), tryExpr, + /*Implicit=*/true); + statements.push_back(assignExpr); + } + } + + // Classes which inherit from something Decodable should decode super as well. + auto *classDecl = dyn_cast(typeDecl); + if (classDecl && superclassIsDecodable(classDecl)) { + // Need to generate `try super.init(from: container.superDecoder())` + + // superDecoder() + auto *method = new (C) UnresolvedDeclRefExpr( + DeclName(C.Id_superDecoder), DeclRefKind::Ordinary, DeclNameLoc()); + + // container.superDecoder() + auto *superDecoderRef = new (C) DotSyntaxCallExpr(containerExpr, + SourceLoc(), method); + + // init(from:) expr + auto *initDeclRef = new (C) DeclRefExpr(ConcreteDeclRef(initDecl), + DeclNameLoc(), /*Implicit=*/true); + + // super + auto *superRef = new (C) SuperRefExpr(initDecl->getImplicitSelfDecl(), + SourceLoc(), /*Implicit=*/true); + + // super.init(from:) + auto *decodeCall = new (C) DotSyntaxCallExpr(superRef, SourceLoc(), + initDeclRef); + + // super.decode(from: container.superDecoder()) + Expr *args[1] = {superDecoderRef}; + Identifier argLabels[1] = {C.Id_from}; + auto *callExpr = CallExpr::createImplicit(C, decodeCall, + C.AllocateCopy(args), + C.AllocateCopy(argLabels)); + + // try super.init(from: container.superDecoder()) + auto *tryExpr = new (C) TryExpr(SourceLoc(), callExpr, Type(), + /*Implicit=*/true); + statements.push_back(tryExpr); + } + + auto *body = BraceStmt::create(C, SourceLoc(), statements, SourceLoc(), + /*implicit=*/true); + initDecl->setBody(body); +} + +/// Synthesizes a function declaration for `init(from: Decoder) throws` with a +/// lazily synthesized body for the given type. +/// +/// Adds the function declaration to the given type before returning it. +/// +/// \param tc The type checker whose AST context to synthesize the decl in. +/// +/// \param parentDecl The parent declaration of the type. +/// +/// \param type The nominal type to synthesize the function for. +static ValueDecl *deriveDecodable_init(TypeChecker &tc, Decl *parentDecl, + NominalTypeDecl *type) { + auto &C = tc.Context; + auto *typeDC = cast(type); + + // Expected type: (Self) -> (Decoder) throws -> (Self) + // Constructed as: func type + // input: Self + // throws + // output: function type + // input: Encoder + // output: Self + // Compute from the inside out: + + // (from: Decoder) + auto decoderType = C.getDecoderDecl()->getDeclaredInterfaceType(); + auto inputTypeElt = TupleTypeElt(decoderType, C.Id_from); + auto inputType = TupleType::get(ArrayRef(inputTypeElt), C); + + // throws + auto extInfo = FunctionType::ExtInfo(FunctionTypeRepresentation::Swift, + /*Throws=*/true); + + // (Self) + auto returnType = typeDC->getDeclaredInterfaceType(); + + // (from: Decoder) throws -> (Self) + Type innerType = FunctionType::get(inputType, returnType, extInfo); + + // Params: (self [implicit], Decoder) + // self should be inout if the type is a value type; not inout otherwise. + auto inOut = !isa(type); + auto *selfDecl = ParamDecl::createSelf(SourceLoc(), typeDC, + /*isStatic=*/false, + /*isInOut=*/inOut); + auto *decoderParamDecl = new (C) ParamDecl(/*isLet=*/true, SourceLoc(), + SourceLoc(), C.Id_from, + SourceLoc(), C.Id_decoder, + decoderType, typeDC); + decoderParamDecl->setImplicit(); + decoderParamDecl->setInterfaceType(decoderType); + + auto *paramList = ParameterList::createWithoutLoc(decoderParamDecl); + + // Func name: init(from: Decoder) + DeclName name(C, C.Id_init, paramList); + + auto *initDecl = new (C) ConstructorDecl( + name, SourceLoc(), + /*Failability=*/OTK_None, + /*FailabilityLoc=*/SourceLoc(), + /*Throws=*/true, /*ThrowsLoc=*/SourceLoc(), selfDecl, paramList, + /*GenericParams=*/nullptr, typeDC); + initDecl->setImplicit(); + initDecl->setBodySynthesizer(deriveBodyDecodable_init); + + // This constructor should be marked as `required` for non-final classes. + if (isa(type) && type->getAttrs().hasAttribute()) { + auto *reqAttr = new (C) SimpleDeclAttr(/*IsImplicit=*/true); + initDecl->getAttrs().add(reqAttr); + } + + Type selfType = initDecl->computeInterfaceSelfType(); + Type selfInitType = initDecl->computeInterfaceSelfType(/*init=*/true); + Type interfaceType; + Type initializerType; + if (auto sig = typeDC->getGenericSignatureOfContext()) { + // Evaluate the below, but in a generic environment (if Self is generic). + initDecl->setGenericEnvironment(typeDC->getGenericEnvironmentOfContext()); + interfaceType = GenericFunctionType::get(sig, selfType, innerType, + FunctionType::ExtInfo()); + initializerType = GenericFunctionType::get(sig, selfInitType, innerType, + FunctionType::ExtInfo()); + } else { + // (Self) -> (Decoder) throws -> (Self) + interfaceType = FunctionType::get(selfType, innerType); + initializerType = FunctionType::get(selfInitType, innerType); + } + + initDecl->setInterfaceType(interfaceType); + initDecl->setInitializerInterfaceType(initializerType); + initDecl->setAccessibility( + std::max(type->getFormalAccess(), Accessibility::Internal)); + + // If the type was not imported, the derived conformance is either from the + // type itself or an extension, in which case we will emit the declaration + // normally. + if (type->hasClangNode()) + tc.Context.addExternalDecl(initDecl); + + cast(type)->addMember(initDecl); + return initDecl; +} + +/// Returns whether the given type is valid for synthesizing {En,De}codable. +/// +/// Checks to see whether the given type has a valid \c CodingKeys enum, and if +/// not, attempts to synthesize one for it. +/// +/// \param tc The typechecker to use in validating {En,Decodable} conformance. +/// +/// \param type The type to validate. +/// +/// \param proto The *codable protocol to check for validity. +static bool canSynthesize(TypeChecker &tc, NominalTypeDecl *type, + ProtocolDecl *proto) { + // First, look up if the type has a valid CodingKeys enum we can use. + bool hasType, error; + std::tie(hasType, error) = hasValidCodingKeysEnum(tc, type, proto); + + // We found a type, but it wasn't valid. + if (error) + return false; + + // We can try to synthesize a type here. + if (!hasType) { + auto *synthesizedEnum = synthesizeCodingKeysEnum(tc, type, proto); + if (!synthesizedEnum) + return false; + } + + return true; +} + +ValueDecl *DerivedConformance::deriveEncodable(TypeChecker &tc, + Decl *parentDecl, + NominalTypeDecl *type, + ValueDecl *requirement) { + // We can only synthesize Encodable for structs and classes. + if (!isa(type) && !isa(type)) + return nullptr; + + if (requirement->getName() != tc.Context.Id_encode) { + // Unknown requirement. + tc.diagnose(requirement->getLoc(), diag::broken_encodable_requirement); + return nullptr; + } + + // Check other preconditions for synthesized conformance. + // This synthesizes a CodingKeys enum if possible. + auto encodableProto = tc.Context.getProtocol(KnownProtocolKind::Encodable); + if (canSynthesize(tc, type, encodableProto)) + return deriveEncodable_encode(tc, parentDecl, type); + + // Known protocol requirement but could not synthesize. + // FIXME: We have to output at least one error diagnostic here because we + // returned true from NominalTypeDecl::derivesProtocolConformance; if we + // don't, we expect to return a witness here later and crash on an + // assertion. Producing an error stops compilation before then. + auto encodableType = encodableProto->getDeclaredType(); + tc.diagnose(type, diag::type_does_not_conform, type->getDeclaredType(), + encodableType); + tc.diagnose(requirement, diag::no_witnesses, diag::RequirementKind::Func, + requirement->getFullName(), encodableType, /*AddFixIt=*/false); + return nullptr; +} + +ValueDecl *DerivedConformance::deriveDecodable(TypeChecker &tc, + Decl *parentDecl, + NominalTypeDecl *type, + ValueDecl *requirement) { + // We can only synthesize Encodable for structs and classes. + if (!isa(type) && !isa(type)) + return nullptr; + + if (requirement->getName() != tc.Context.Id_init) { + // Unknown requirement. + tc.diagnose(requirement->getLoc(), diag::broken_decodable_requirement); + return nullptr; + } + + // Check other preconditions for synthesized conformance. + // This synthesizes a CodingKeys enum if possible. + auto decodableProto = tc.Context.getProtocol(KnownProtocolKind::Decodable); + if (canSynthesize(tc, type, decodableProto)) + return deriveDecodable_init(tc, parentDecl, type); + + // Known protocol requirement but could not synthesize. + // FIXME: We have to output at least one error diagnostic here because we + // returned true from NominalTypeDecl::derivesProtocolConformance; if we + // don't, we expect to return a witness here later and crash on an + // assertion. Producing an error stops compilation before then. + auto decodableType = decodableProto->getDeclaredType(); + tc.diagnose(type, diag::type_does_not_conform, type->getDeclaredType(), + decodableType); + tc.diagnose(requirement, diag::no_witnesses, + diag::RequirementKind::Constructor, requirement->getFullName(), + decodableType, /*AddFixIt=*/false); + return nullptr; +} diff --git a/lib/Sema/DerivedConformanceCodingKey.cpp b/lib/Sema/DerivedConformanceCodingKey.cpp new file mode 100644 index 0000000000000..fde6f0d50a081 --- /dev/null +++ b/lib/Sema/DerivedConformanceCodingKey.cpp @@ -0,0 +1,561 @@ +//===--- DerivedConformanceCodingKey.cpp - Derived CodingKey --------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2017 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 +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// +// +// This file implements explicit derivation of the CodingKey protocol for an +// enum. +// +//===----------------------------------------------------------------------===// + +#include "TypeChecker.h" +#include "swift/AST/Decl.h" +#include "swift/AST/Expr.h" +#include "swift/AST/ParameterList.h" +#include "swift/AST/Pattern.h" +#include "swift/AST/Stmt.h" +#include "swift/AST/Types.h" +#include "DerivedConformances.h" + +using namespace swift; +using namespace DerivedConformance; + +/// Sets the body of the given function to `return nil`. +/// +/// \param funcDecl The function whose body to set. +static void deriveNilReturn(AbstractFunctionDecl *funcDecl) { + auto *parentDC = funcDecl->getDeclContext(); + auto &C = parentDC->getASTContext(); + + auto *nilExpr = new (C) NilLiteralExpr(SourceLoc(), /*Implicit=*/true); + auto *returnStmt = new (C) ReturnStmt(SourceLoc(), nilExpr); + auto *body = BraceStmt::create(C, SourceLoc(), ASTNode(returnStmt), + SourceLoc()); + funcDecl->setBody(body); +} + +/// Sets the body of the given function to `return self.rawValue`. +/// +/// \param funcDecl The function whose body to set. +static void deriveRawValueReturn(AbstractFunctionDecl *funcDecl) { + auto *parentDC = funcDecl->getDeclContext(); + auto &C = parentDC->getASTContext(); + + auto *selfRef = createSelfDeclRef(funcDecl); + auto *memberRef = new (C) UnresolvedDotExpr(selfRef, SourceLoc(), + C.Id_rawValue, DeclNameLoc(), + /*Implicit=*/true); + + auto *returnStmt = new (C) ReturnStmt(SourceLoc(), memberRef); + auto *body = BraceStmt::create(C, SourceLoc(), ASTNode(returnStmt), + SourceLoc()); + funcDecl->setBody(body); +} + +/// Sets the body of the given function to `self.init(rawValue:)`, passing along +/// the parameter of the given constructor. +/// +/// \param initDecl The constructor whose body to set. +static void deriveRawValueInit(AbstractFunctionDecl *initDecl) { + auto *parentDC = initDecl->getDeclContext(); + auto &C = parentDC->getASTContext(); + + // Get the param from init({string,int}Value:). self is the first param in the + // list; stringValue is the second. + auto *valueParam = initDecl->getParameterList(1)->get(0); + auto *valueParamExpr = new (C) DeclRefExpr(ConcreteDeclRef(valueParam), + DeclNameLoc(), /*Implicit=*/true); + + // rawValue param to init(rawValue:) + auto *rawValueDecl = new (C) ParamDecl(/*IsLet=*/true, SourceLoc(), + SourceLoc(), C.Id_rawValue, + SourceLoc(), C.Id_rawValue, + valueParam->getType(), parentDC); + rawValueDecl->setInterfaceType(C.getIntDecl()->getDeclaredType()); + rawValueDecl->setImplicit(); + auto *paramList = ParameterList::createWithoutLoc(rawValueDecl); + + // init(rawValue:) constructor name + DeclName ctorName(C, C.Id_init, paramList); + + // self.init(rawValue:) expr + auto *selfRef = createSelfDeclRef(initDecl); + auto *initExpr = new (C) UnresolvedDotExpr(selfRef, SourceLoc(), ctorName, + DeclNameLoc(), /*Implicit=*/true); + + // Bind the value param in self.init(rawValue: {string,int}Value). + Expr *args[1] = {valueParamExpr}; + Identifier argLabels[1] = {C.Id_rawValue}; + auto *callExpr = CallExpr::createImplicit(C, initExpr, C.AllocateCopy(args), + C.AllocateCopy(argLabels)); + + auto *body = BraceStmt::create(C, SourceLoc(), ASTNode(callExpr), + SourceLoc()); + initDecl->setBody(body); +} + +/// Synthesizes a constructor declaration with the given parameter name and +/// type. +/// +/// \param tc The type checker to use in synthesizing the constructor. +/// +/// \param parentDecl The parent declaration of the enum. +/// +/// \param enumDecl The enum on which to synthesize the constructor. +/// +/// \param paramType The type of the parameter. +/// +/// \param paramName The name of the parameter. +/// +/// \param synthesizer A lambda to call to set the constructor's body. +template +static ValueDecl *deriveInitDecl(TypeChecker &tc, Decl *parentDecl, + EnumDecl *enumDecl, Type paramType, + Identifier paramName, + const Synthesizer &synthesizer) { + auto &C = tc.Context; + auto *parentDC = cast(parentDecl); + + // rawValue + auto *rawDecl = new (C) ParamDecl(/*IsLet*/ true, SourceLoc(), SourceLoc(), + paramName, SourceLoc(), paramName, + paramType, parentDC); + rawDecl->setInterfaceType(paramType); + rawDecl->setImplicit(); + + // init(rawValue:) name + auto *paramList = ParameterList::createWithoutLoc(rawDecl); + DeclName name(C, C.Id_init, paramList); + + // init(rawValue:) decl + auto *selfDecl = ParamDecl::createSelf(SourceLoc(), parentDC, + /*static*/false, /*inout*/true); + auto *initDecl = + new (C) ConstructorDecl(name, SourceLoc(), + /*Failability=*/OTK_Optional, + /*FailabilityLoc=*/SourceLoc(), + /*Throws=*/false, /*ThrowsLoc=*/SourceLoc(), + selfDecl, paramList, + /*GenericParams=*/nullptr, parentDC); + + initDecl->setImplicit(); + + // Synthesize the body. + synthesizer(initDecl); + + // Compute the type of the initializer. + TupleTypeElt element(paramType, paramName); + TupleTypeElt interfaceElement(paramType, paramName); + auto interfaceArgType = TupleType::get(interfaceElement, C); + + // Compute the interface type of the initializer. + Type retInterfaceType = + OptionalType::get(parentDC->getDeclaredInterfaceType()); + Type interfaceType = FunctionType::get(interfaceArgType, retInterfaceType); + Type selfInterfaceType = initDecl->computeInterfaceSelfType(); + Type selfInitializerInterfaceType = + initDecl->computeInterfaceSelfType(/*init*/ true); + + Type allocIfaceType; + Type initIfaceType; + if (auto sig = parentDC->getGenericSignatureOfContext()) { + initDecl->setGenericEnvironment(parentDC->getGenericEnvironmentOfContext()); + + allocIfaceType = GenericFunctionType::get(sig, selfInterfaceType, + interfaceType, + FunctionType::ExtInfo()); + initIfaceType = GenericFunctionType::get(sig, selfInitializerInterfaceType, + interfaceType, + FunctionType::ExtInfo()); + } else { + allocIfaceType = FunctionType::get(selfInterfaceType, + interfaceType); + initIfaceType = FunctionType::get(selfInitializerInterfaceType, + interfaceType); + } + initDecl->setInterfaceType(allocIfaceType); + initDecl->setInitializerInterfaceType(initIfaceType); + initDecl->setAccessibility(std::max(Accessibility::Internal, + enumDecl->getFormalAccess())); + + // If the enum was not imported, the derived conformance is either from the + // enum itself or an extension, in which case we will emit the declaration + // normally. + if (enumDecl->hasClangNode()) + tc.Context.addExternalDecl(initDecl); + + cast(parentDecl)->addMember(initDecl); + return initDecl; +} + +/// Synthesizes a read-only computed property with a given type and name. +/// +/// \param tc The type checker to use in synthesizing the property. +/// +/// \param parentDecl The parent declaration of the enum. +/// +/// \param enumDecl The enum on which to synthesize the property. +/// +/// \param type The type of the property. +/// +/// \param name The name of the property. +/// +/// \param synthesizer A lambda to call to set the property's getter. +template +static ValueDecl *deriveProperty(TypeChecker &tc, Decl *parentDecl, + EnumDecl *enumDecl, Type type, Identifier name, + const Synthesizer &synthesizer) { + // Define the getter. + auto *getterDecl = declareDerivedPropertyGetter(tc, parentDecl, enumDecl, + type, type, + /*isStatic=*/false, + /*isFinal=*/false); + + // Synthesize the body. + synthesizer(getterDecl); + + // Define the property. + VarDecl *propDecl; + PatternBindingDecl *pbDecl; + std::tie(propDecl, pbDecl) + = declareDerivedReadOnlyProperty(tc, parentDecl, enumDecl, name, type, type, + getterDecl, /*isStatic=*/false, + /*isFinal=*/false); + + auto *dc = cast(parentDecl); + dc->addMember(getterDecl); + dc->addMember(propDecl); + dc->addMember(pbDecl); + return propDecl; +} + +/// Sets the body of the given function to return a string value based on +/// switching on `self`. +/// +/// \param strValDecl The function whose body to set. +static void +deriveBodyCodingKey_enum_stringValue(AbstractFunctionDecl *strValDecl) { + // enum SomeEnum { + // case A, B, C + // @derived var stringValue: String { + // switch self { + // case A: + // return "A" + // case B: + // return "B" + // case C: + // return "C" + // } + // } + // } + auto *parentDC = strValDecl->getDeclContext(); + auto &C = parentDC->getASTContext(); + + auto *enumDecl = parentDC->getAsEnumOrEnumExtensionContext(); + Type enumType = parentDC->getDeclaredTypeInContext(); + + BraceStmt *body = nullptr; + auto elements = enumDecl->getAllElements(); + if (elements.empty() /* empty enum */) { + // return "" + auto *emptyStringExpr = new (C) StringLiteralExpr("", SourceRange(), + /*Implicit=*/true); + auto *returnStmt = new (C) ReturnStmt(SourceLoc(), emptyStringExpr); + body = BraceStmt::create(C, SourceLoc(), ASTNode(returnStmt), + SourceLoc()); + } else { + SmallVector cases; + for (auto *elt : elements) { + auto *pat = new (C) EnumElementPattern(TypeLoc::withoutLoc(enumType), + SourceLoc(), SourceLoc(), + Identifier(), elt, nullptr); + pat->setImplicit(); + + auto labelItem = CaseLabelItem(/*IsDefault=*/false, pat, SourceLoc(), + nullptr); + + auto *caseValue = new (C) StringLiteralExpr(elt->getNameStr(), + SourceRange(), + /*Implicit=*/true); + auto *returnStmt = new (C) ReturnStmt(SourceLoc(), caseValue); + auto *caseBody = BraceStmt::create(C, SourceLoc(), ASTNode(returnStmt), + SourceLoc()); + cases.push_back(CaseStmt::create(C, SourceLoc(), labelItem, + /*HasBoundDecls=*/false, SourceLoc(), + caseBody)); + } + + auto *selfRef = createSelfDeclRef(strValDecl); + auto *switchStmt = SwitchStmt::create(LabeledStmtInfo(), SourceLoc(), + selfRef, SourceLoc(), cases, + SourceLoc(), C); + body = BraceStmt::create(C, SourceLoc(), ASTNode(switchStmt), SourceLoc()); + } + + strValDecl->setBody(body); +} + +/// Sets the body of the given constructor to initialize `self` based on the +/// value of the given string param. +/// +/// \param initDecl The function whose body to set. +static void +deriveBodyCodingKey_init_stringValue(AbstractFunctionDecl *initDecl) { + // enum SomeEnum { + // case A, B, C + // @derived init?(stringValue: String) { + // switch stringValue { + // case "A": + // self = .A + // case "B": + // self = .B + // case "C": + // self = .C + // default: + // return nil + // } + // } + // } + auto *parentDC = initDecl->getDeclContext(); + auto &C = parentDC->getASTContext(); + + auto *enumDecl = parentDC->getAsEnumOrEnumExtensionContext(); + Type enumType = parentDC->getDeclaredTypeInContext(); + + auto elements = enumDecl->getAllElements(); + if (elements.empty() /* empty enum */) { + deriveNilReturn(initDecl); + return; + } + + auto *selfRef = createSelfDeclRef(initDecl); + SmallVector cases; + for (auto *elt : elements) { + auto *litExpr = new (C) StringLiteralExpr(elt->getNameStr(), SourceRange(), + /*Implicit=*/true); + auto *litPat = new (C) ExprPattern(litExpr, /*IsResolved=*/true, nullptr, + nullptr); + litPat->setImplicit(); + + auto labelItem = CaseLabelItem(/*IsDefault=*/false, litPat, SourceLoc(), + nullptr); + + auto *eltRef = new (C) DeclRefExpr(elt, DeclNameLoc(), /*Implicit=*/true); + auto *metaTyRef = TypeExpr::createImplicit(enumType, C); + auto *valueExpr = new (C) DotSyntaxCallExpr(eltRef, SourceLoc(), metaTyRef); + + auto *assignment = new (C) AssignExpr(selfRef, SourceLoc(), valueExpr, + /*Implicit=*/true); + + auto *body = BraceStmt::create(C, SourceLoc(), ASTNode(assignment), + SourceLoc()); + cases.push_back(CaseStmt::create(C, SourceLoc(), labelItem, + /*HasBoundDecls=*/false, SourceLoc(), + body)); + } + + auto *anyPat = new (C) AnyPattern(SourceLoc()); + anyPat->setImplicit(); + auto dfltLabelItem = CaseLabelItem(/*IsDefault=*/true, anyPat, SourceLoc(), + nullptr); + + auto *dfltReturnStmt = new (C) FailStmt(SourceLoc(), SourceLoc()); + auto *dfltBody = BraceStmt::create(C, SourceLoc(), ASTNode(dfltReturnStmt), + SourceLoc()); + cases.push_back(CaseStmt::create(C, SourceLoc(), dfltLabelItem, + /*HasBoundDecls=*/false, SourceLoc(), + dfltBody)); + + auto *stringValueDecl = initDecl->getParameterList(1)->get(0); + auto *stringValueRef = new (C) DeclRefExpr(stringValueDecl, DeclNameLoc(), + /*Implicit=*/true); + auto *switchStmt = SwitchStmt::create(LabeledStmtInfo(), SourceLoc(), + stringValueRef, SourceLoc(), cases, + SourceLoc(), C); + auto *body = BraceStmt::create(C, SourceLoc(), ASTNode(switchStmt), + SourceLoc()); + initDecl->setBody(body); +} + +/// Returns whether the given enum is eligible for CodingKey synthesis. +/// +/// \param tc The type checker to use in checking eligibility. +/// +/// \param parentDecl The parent declaration of the enum. +/// +/// \param enumDecl The enum to check. +static bool canSynthesizeCodingKey(TypeChecker &tc, Decl *parentDecl, + EnumDecl *enumDecl) { + // If the enum has a raw type (optional), it must be String or Int. + Type rawType = enumDecl->getRawType(); + if (rawType) { + auto *parentDC = cast(parentDecl); + rawType = parentDC->mapTypeIntoContext(rawType); + + auto &C = tc.Context; + auto *nominal = rawType->getCanonicalType()->getAnyNominal(); + if (nominal != C.getStringDecl() && nominal != C.getIntDecl()) + return false; + } + + if (!enumDecl->getInherited().empty() && + enumDecl->getInherited().front().isError()) + return false; + + // If it meets all of those requirements, we can synthesize CodingKey + // conformance. + return true; +} + +ValueDecl *DerivedConformance::deriveCodingKey(TypeChecker &tc, + Decl *parentDecl, + NominalTypeDecl *type, + ValueDecl *requirement) { + + // We can only synthesize CodingKey for enums. + auto *enumDecl = dyn_cast(type); + if (!enumDecl) + return nullptr; + + // Check other preconditions for synthesized conformance. + if (!canSynthesizeCodingKey(tc, parentDecl, enumDecl)) + return nullptr; + + auto &C = tc.Context; + auto rawType = enumDecl->getRawType(); + auto name = requirement->getName(); + if (name == C.Id_stringValue) { + // Synthesize `var stringValue: String { get }` + auto stringType = C.getStringDecl()->getDeclaredType(); + auto synth = [rawType, stringType](AbstractFunctionDecl *getterDecl) { + if (rawType && rawType->isEqual(stringType)) { + // enum SomeStringEnum : String { + // case A, B, C + // @derived var stringValue: String { + // return self.rawValue + // } + getterDecl->setBodySynthesizer(&deriveRawValueReturn); + } else { + // enum SomeEnum { + // case A, B, C + // @derived var stringValue: String { + // switch self { + // case A: + // return "A" + // case B: + // return "B" + // case C: + // return "C" + // } + // } + // } + getterDecl->setBodySynthesizer(&deriveBodyCodingKey_enum_stringValue); + } + }; + + return deriveProperty(tc, parentDecl, enumDecl, stringType, + C.Id_stringValue, synth); + + } else if (name == C.Id_intValue) { + // Synthesize `var intValue: Int? { get }` + auto intType = C.getIntDecl()->getDeclaredType(); + auto optionalIntType = OptionalType::get(OTK_Optional, intType); + + auto synth = [rawType, intType](AbstractFunctionDecl *getterDecl) { + if (rawType && rawType->isEqual(intType)) { + // enum SomeIntEnum : Int { + // case A = 1, B = 2, C = 3 + // @derived var intValue: Int? { + // return self.rawValue + // } + // } + getterDecl->setBodySynthesizer(&deriveRawValueReturn); + } else { + // enum SomeEnum { + // case A, B, C + // @derived var intValue: Int? { + // return nil + // } + // } + getterDecl->setBodySynthesizer(&deriveNilReturn); + } + }; + + return deriveProperty(tc, parentDecl, enumDecl, optionalIntType, + C.Id_intValue, synth); + } else if (name == C.Id_init) { + auto argumentNames = requirement->getFullName().getArgumentNames(); + if (argumentNames.size() == 1) { + if (argumentNames[0] == C.Id_stringValue) { + // Derive `init?(stringValue:)` + auto stringType = C.getStringDecl()->getDeclaredType(); + auto synth = [rawType, stringType](AbstractFunctionDecl *initDecl) { + if (rawType && rawType->isEqual(stringType)) { + // enum SomeStringEnum : String { + // case A = "a", B = "b", C = "c" + // @derived init?(stringValue: String) { + // self.init(rawValue: stringValue) + // } + // } + initDecl->setBodySynthesizer(&deriveRawValueInit); + } else { + // enum SomeEnum { + // case A, B, C + // @derived init?(stringValue: String) { + // switch stringValue { + // case "A": + // self = .A + // case "B": + // self = .B + // case "C": + // self = .C + // default: + // return nil + // } + // } + // } + initDecl->setBodySynthesizer(&deriveBodyCodingKey_init_stringValue); + } + }; + + return deriveInitDecl(tc, parentDecl, enumDecl, stringType, + C.Id_stringValue, synth); + } else if (argumentNames[0] == C.Id_intValue) { + // Synthesize `init?(intValue:)` + auto intType = C.getIntDecl()->getDeclaredType(); + auto synthesizer = [rawType, intType](AbstractFunctionDecl *initDecl) { + if (rawType && rawType->isEqual(intType)) { + // enum SomeIntEnum : Int { + // case A = 1, B = 2, C = 3 + // @derived init?(intValue: Int) { + // self.init(rawValue: intValue) + // } + // } + initDecl->setBodySynthesizer(&deriveRawValueInit); + } else { + // enum SomeEnum { + // case A, B, C + // @derived init?(intValue: Int) { + // return nil + // } + // } + initDecl->setBodySynthesizer(&deriveNilReturn); + } + }; + + return deriveInitDecl(tc, parentDecl, enumDecl, intType, C.Id_intValue, + synthesizer); + } + } + } + + tc.diagnose(requirement->getLoc(), diag::broken_coding_key_requirement); + return nullptr; +} diff --git a/lib/Sema/DerivedConformances.cpp b/lib/Sema/DerivedConformances.cpp index 5d0dcbd83fe83..02b1563bfc299 100644 --- a/lib/Sema/DerivedConformances.cpp +++ b/lib/Sema/DerivedConformances.cpp @@ -58,6 +58,14 @@ ValueDecl *DerivedConformance::getDerivableRequirement(NominalTypeDecl *nominal, if (name.isSimpleName(ctx.Id_nsErrorDomain)) return getRequirement(KnownProtocolKind::BridgedNSError); + // CodingKey.stringValue + if (name.isSimpleName(ctx.Id_stringValue)) + return getRequirement(KnownProtocolKind::CodingKey); + + // CodingKey.intValue + if (name.isSimpleName(ctx.Id_intValue)) + return getRequirement(KnownProtocolKind::CodingKey); + return nullptr; } @@ -66,14 +74,33 @@ ValueDecl *DerivedConformance::getDerivableRequirement(NominalTypeDecl *nominal, if (func->isOperator() && name.getBaseName().str() == "==") return getRequirement(KnownProtocolKind::Equatable); + // Encodable.encode(to: Encoder) + if (name.isCompoundName() && name.getBaseName() == ctx.Id_encode) { + auto argumentNames = name.getArgumentNames(); + if (argumentNames.size() == 1 && argumentNames[0] == ctx.Id_to) + return getRequirement(KnownProtocolKind::Encodable); + } + return nullptr; } // Initializers. - if (isa(requirement)) { + if (auto ctor = dyn_cast(requirement)) { auto argumentNames = name.getArgumentNames(); - if (argumentNames.size() == 1 && argumentNames[0] == ctx.Id_rawValue) - return getRequirement(KnownProtocolKind::RawRepresentable); + if (argumentNames.size() == 1) { + if (argumentNames[0] == ctx.Id_rawValue) + return getRequirement(KnownProtocolKind::RawRepresentable); + + // CodingKey.init?(stringValue:), CodingKey.init?(intValue:) + if (ctor->getFailability() == OTK_Optional && + (argumentNames[0] == ctx.Id_stringValue || + argumentNames[0] == ctx.Id_intValue)) + return getRequirement(KnownProtocolKind::CodingKey); + + // Decodable.init(from: Decoder) + if (argumentNames[0] == ctx.Id_from) + return getRequirement(KnownProtocolKind::Decodable); + } return nullptr; } diff --git a/lib/Sema/DerivedConformances.h b/lib/Sema/DerivedConformances.h index 6cfe2e3bad08e..9b56a26890bfe 100644 --- a/lib/Sema/DerivedConformances.h +++ b/lib/Sema/DerivedConformances.h @@ -96,6 +96,30 @@ ValueDecl *deriveBridgedNSError(TypeChecker &tc, NominalTypeDecl *type, ValueDecl *requirement); +/// Derive a CodingKey requirement for an enum type. +/// +/// \returns the derived member, which will also be added to the type. +ValueDecl *deriveCodingKey(TypeChecker &tc, + Decl *parentDecl, + NominalTypeDecl *type, + ValueDecl *requirement); + +/// Derive an Encodable requirement for a struct type. +/// +/// \returns the derived member, which will also be added to the type. +ValueDecl *deriveEncodable(TypeChecker &tc, + Decl *parentDecl, + NominalTypeDecl *type, + ValueDecl *requirement); + +/// Derive a Decodable requirement for a struct type. +/// +/// \returns the derived member, which will also be added to the type. +ValueDecl *deriveDecodable(TypeChecker &tc, + Decl *parentDecl, + NominalTypeDecl *type, + ValueDecl *requirement); + /// Declare a getter for a derived property. FuncDecl *declareDerivedPropertyGetter(TypeChecker &tc, Decl *parentDecl, diff --git a/lib/Sema/TypeCheckProtocol.cpp b/lib/Sema/TypeCheckProtocol.cpp index 2dd29038ddd48..cc4bcf1470527 100644 --- a/lib/Sema/TypeCheckProtocol.cpp +++ b/lib/Sema/TypeCheckProtocol.cpp @@ -6234,6 +6234,15 @@ ValueDecl *TypeChecker::deriveProtocolRequirement(DeclContext *DC, return DerivedConformance::deriveBridgedNSError(*this, Decl, TypeDecl, Requirement); + case KnownProtocolKind::CodingKey: + return DerivedConformance::deriveCodingKey(*this, Decl, TypeDecl, Requirement); + + case KnownProtocolKind::Encodable: + return DerivedConformance::deriveEncodable(*this, Decl, TypeDecl, Requirement); + + case KnownProtocolKind::Decodable: + return DerivedConformance::deriveDecodable(*this, Decl, TypeDecl, Requirement); + default: return nullptr; } diff --git a/lib/Sema/TypeCheckType.cpp b/lib/Sema/TypeCheckType.cpp index 03d9332f9d7ca..fea7c60b866e8 100644 --- a/lib/Sema/TypeCheckType.cpp +++ b/lib/Sema/TypeCheckType.cpp @@ -99,6 +99,9 @@ static Type getStdlibType(TypeChecker &TC, Type &cached, DeclContext *dc, Type TypeChecker::getStringType(DeclContext *dc) { return ::getStdlibType(*this, StringType, dc, "String"); } +Type TypeChecker::getIntType(DeclContext *dc) { + return ::getStdlibType(*this, IntType, dc, "Int"); +} Type TypeChecker::getInt8Type(DeclContext *dc) { return ::getStdlibType(*this, Int8Type, dc, "Int8"); } diff --git a/lib/Sema/TypeChecker.h b/lib/Sema/TypeChecker.h index f2c31b4ea5c54..1f18a89a47656 100644 --- a/lib/Sema/TypeChecker.h +++ b/lib/Sema/TypeChecker.h @@ -715,6 +715,7 @@ class TypeChecker final : public LazyResolver { Type ImageLiteralType; Type FileReferenceLiteralType; Type StringType; + Type IntType; Type Int8Type; Type UInt8Type; Type NSObjectType; @@ -805,6 +806,7 @@ class TypeChecker final : public LazyResolver { Type getOptionalType(SourceLoc loc, Type elementType); Type getImplicitlyUnwrappedOptionalType(SourceLoc loc, Type elementType); Type getStringType(DeclContext *dc); + Type getIntType(DeclContext *dc); Type getInt8Type(DeclContext *dc); Type getUInt8Type(DeclContext *dc); Type getNSObjectType(DeclContext *dc); diff --git a/test/decl/protocol/special/coding/Inputs/class_codable_simple_multi1.swift b/test/decl/protocol/special/coding/Inputs/class_codable_simple_multi1.swift new file mode 100644 index 0000000000000..b95dc24d6f906 --- /dev/null +++ b/test/decl/protocol/special/coding/Inputs/class_codable_simple_multi1.swift @@ -0,0 +1,25 @@ +// REQUIRES: objc_interop + +import Foundation + +// Simple classes with all Codable properties should get derived conformance to +// Codable. +class SimpleClass : Codable { + var x: Int = 1 + var y: Double = .pi + static var z: String = "foo" + + // These lines have to be within the SimpleClass type because CodingKeys + // should be private. + func foo() { + // They should receive a synthesized CodingKeys enum. + let _ = SimpleClass.CodingKeys.self + + // The enum should have a case for each of the vars. + let _ = SimpleClass.CodingKeys.x + let _ = SimpleClass.CodingKeys.y + + // Static vars should not be part of the CodingKeys enum. + let _ = SimpleClass.CodingKeys.z // expected-error {{type 'SimpleClass.CodingKeys' has no member 'z'}} + } +} diff --git a/test/decl/protocol/special/coding/Inputs/class_codable_simple_multi2.swift b/test/decl/protocol/special/coding/Inputs/class_codable_simple_multi2.swift new file mode 100644 index 0000000000000..0a8d5d2951912 --- /dev/null +++ b/test/decl/protocol/special/coding/Inputs/class_codable_simple_multi2.swift @@ -0,0 +1,10 @@ +// These are wrapped in a dummy function to avoid binding a global variable. +func foo() { + // They should receive synthesized init(from:) and an encode(to:). + let _ = SimpleClass.init(from:) + let _ = SimpleClass.encode(to:) + + // The synthesized CodingKeys type should not be accessible from outside the + // class. + let _ = SimpleClass.CodingKeys.self // expected-error {{'CodingKeys' is inaccessible due to 'private' protection level}} +} diff --git a/test/decl/protocol/special/coding/Inputs/enum_coding_key_extension_multi1.swift b/test/decl/protocol/special/coding/Inputs/enum_coding_key_extension_multi1.swift new file mode 100644 index 0000000000000..4286f70a985a0 --- /dev/null +++ b/test/decl/protocol/special/coding/Inputs/enum_coding_key_extension_multi1.swift @@ -0,0 +1,11 @@ +enum NoRawTypeKey { + case a, b, c +} + +enum StringKey : String { + case a = "A", b, c = "Foo" +} + +enum IntKey : Int { + case a = 3, b, c = 1 +} diff --git a/test/decl/protocol/special/coding/Inputs/enum_coding_key_extension_multi2.swift b/test/decl/protocol/special/coding/Inputs/enum_coding_key_extension_multi2.swift new file mode 100644 index 0000000000000..b72cda761dd3a --- /dev/null +++ b/test/decl/protocol/special/coding/Inputs/enum_coding_key_extension_multi2.swift @@ -0,0 +1,7 @@ +// REQUIRES: objc_interop + +import Foundation + +extension NoRawTypeKey : CodingKey {} +extension StringKey : CodingKey {} +extension IntKey : CodingKey {} diff --git a/test/decl/protocol/special/coding/Inputs/enum_coding_key_extension_multi3.swift b/test/decl/protocol/special/coding/Inputs/enum_coding_key_extension_multi3.swift new file mode 100644 index 0000000000000..e56a15e194e02 --- /dev/null +++ b/test/decl/protocol/special/coding/Inputs/enum_coding_key_extension_multi3.swift @@ -0,0 +1,16 @@ +func foo() { + let _ = NoRawTypeKey.a.stringValue + let _ = NoRawTypeKey(stringValue: "a") + let _ = NoRawTypeKey.a.intValue + let _ = NoRawTypeKey(intValue: 0) + + let _ = StringKey.a.stringValue + let _ = StringKey(stringValue: "A") + let _ = StringKey.a.intValue + let _ = StringKey(intValue: 0) + + let _ = IntKey.a.stringValue + let _ = IntKey(stringValue: "a") + let _ = IntKey.a.intValue + let _ = IntKey(intValue: 3) +} diff --git a/test/decl/protocol/special/coding/Inputs/enum_coding_key_multi1.swift b/test/decl/protocol/special/coding/Inputs/enum_coding_key_multi1.swift new file mode 100644 index 0000000000000..82e567bc98610 --- /dev/null +++ b/test/decl/protocol/special/coding/Inputs/enum_coding_key_multi1.swift @@ -0,0 +1,15 @@ +// REQUIRES: objc_interop + +import Foundation + +enum NoRawTypeKey : CodingKey { + case a, b, c +} + +enum StringKey : String, CodingKey { + case a = "A", b, c = "Foo" +} + +enum IntKey : Int, CodingKey { + case a = 3, b, c = 1 +} diff --git a/test/decl/protocol/special/coding/Inputs/enum_coding_key_multi2.swift b/test/decl/protocol/special/coding/Inputs/enum_coding_key_multi2.swift new file mode 100644 index 0000000000000..e56a15e194e02 --- /dev/null +++ b/test/decl/protocol/special/coding/Inputs/enum_coding_key_multi2.swift @@ -0,0 +1,16 @@ +func foo() { + let _ = NoRawTypeKey.a.stringValue + let _ = NoRawTypeKey(stringValue: "a") + let _ = NoRawTypeKey.a.intValue + let _ = NoRawTypeKey(intValue: 0) + + let _ = StringKey.a.stringValue + let _ = StringKey(stringValue: "A") + let _ = StringKey.a.intValue + let _ = StringKey(intValue: 0) + + let _ = IntKey.a.stringValue + let _ = IntKey(stringValue: "a") + let _ = IntKey.a.intValue + let _ = IntKey(intValue: 3) +} diff --git a/test/decl/protocol/special/coding/Inputs/struct_codable_simple_multi1.swift b/test/decl/protocol/special/coding/Inputs/struct_codable_simple_multi1.swift new file mode 100644 index 0000000000000..9a72ce03fb1ca --- /dev/null +++ b/test/decl/protocol/special/coding/Inputs/struct_codable_simple_multi1.swift @@ -0,0 +1,27 @@ +// RUN: %target-typecheck-verify-swift + +// REQUIRES: objc_interop + +import Foundation + +// Simple structs with all Codable properties should get derived conformance to +// Codable. +struct SimpleStruct : Codable { + var x: Int + var y: Double + static var z: String = "foo" + + // These lines have to be within the SimpleStruct type because CodingKeys + // should be private. + func foo() { + // They should receive a synthesized CodingKeys enum. + let _ = SimpleStruct.CodingKeys.self + + // The enum should have a case for each of the vars. + let _ = SimpleStruct.CodingKeys.x + let _ = SimpleStruct.CodingKeys.y + + // Static vars should not be part of the CodingKeys enum. + let _ = SimpleStruct.CodingKeys.z // expected-error {{type 'SimpleStruct.CodingKeys' has no member 'z'}} + } +} diff --git a/test/decl/protocol/special/coding/Inputs/struct_codable_simple_multi2.swift b/test/decl/protocol/special/coding/Inputs/struct_codable_simple_multi2.swift new file mode 100644 index 0000000000000..e1c762b8d296a --- /dev/null +++ b/test/decl/protocol/special/coding/Inputs/struct_codable_simple_multi2.swift @@ -0,0 +1,10 @@ +// These are wrapped in a dummy function to avoid binding a global variable. +func foo() { + // They should receive synthesized init(from:) and an encode(to:). + let _ = SimpleStruct.init(from:) + let _ = SimpleStruct.encode(to:) + + // The synthesized CodingKeys type should not be accessible from outside the + // struct. + let _ = SimpleStruct.CodingKeys.self // expected-error {{'CodingKeys' is inaccessible due to 'private' protection level}} +} diff --git a/test/decl/protocol/special/coding/class_codable_computed_vars.swift b/test/decl/protocol/special/coding/class_codable_computed_vars.swift new file mode 100644 index 0000000000000..b4d0adf98713c --- /dev/null +++ b/test/decl/protocol/special/coding/class_codable_computed_vars.swift @@ -0,0 +1,43 @@ +// RUN: %target-typecheck-verify-swift -verify-ignore-unknown + +// REQUIRES: objc_interop + +import Foundation + +// Classes with computed members should get synthesized conformance to Codable, +// but their lazy and computed members should be skipped as part of the +// synthesis. +class ClassWithComputedMembers : Codable { + var x: Int = 1 + lazy var y: Double = .pi + var z: String { + return "foo" + } + + // These lines have to be within the ClassWithComputedMembers type because + // CodingKeys should be private. + func foo() { + // They should receive a synthesized CodingKeys enum. + let _ = ClassWithComputedMembers.CodingKeys.self + + // The enum should have a case for each of the vars. + let _ = ClassWithComputedMembers.CodingKeys.x + + // Lazy vars should not be part of the CodingKeys enum. + let _ = ClassWithComputedMembers.CodingKeys.y // expected-error {{type 'ClassWithComputedMembers.CodingKeys' has no member 'y'}} + + // Computed vars should not be part of the CodingKeys enum. + let _ = ClassWithComputedMembers.CodingKeys.z // expected-error {{type 'ClassWithComputedMembers.CodingKeys' has no member 'z'}} + } +} + +// These are wrapped in a dummy function to avoid binding a global variable. +func foo() { + // They should receive synthesized init(from:) and an encode(to:). + let _ = ClassWithComputedMembers.init(from:) + let _ = ClassWithComputedMembers.encode(to:) + + // The synthesized CodingKeys type should not be accessible from outside the + // class. + let _ = ClassWithComputedMembers.CodingKeys.self // expected-error {{'CodingKeys' is inaccessible due to 'private' protection level}} +} diff --git a/test/decl/protocol/special/coding/class_codable_inheritance.swift b/test/decl/protocol/special/coding/class_codable_inheritance.swift new file mode 100644 index 0000000000000..10960ffb6cac5 --- /dev/null +++ b/test/decl/protocol/special/coding/class_codable_inheritance.swift @@ -0,0 +1,59 @@ +// RUN: %target-typecheck-verify-swift -verify-ignore-unknown + +// REQUIRES: objc_interop + +import Foundation + +class SimpleClass : Codable { + var x: Int = 1 + var y: Double = .pi + static var z: String = "foo" +} + +// The synthesized CodingKeys type should not be accessible from outside the +// class. +let _ = SimpleClass.CodingKeys.self // expected-error {{'CodingKeys' is inaccessible due to 'private' protection level}} + +// Classes which inherit from classes that are codable should synthesize Codable +// conformance as well. +class SimpleChildClass : SimpleClass { + var w: Bool = true + + // NOTE: These tests will fail in the future as Codable classes are updated + // to derive Codable conformance instead of inheriting their + // superclass's. Classes currently inherit their parent's Codable + // conformance and we never get the chance to derive a CodingKeys + // type, nor overridden methods. + + // These lines have to be within the SimpleChildClass type because + // CodingKeys should be private. + func foo() { + // They should receive a synthesized CodingKeys enum. + // NOTE: This expected error will need to be removed in the future. + let _ = SimpleChildClass.CodingKeys.self // expected-error {{'CodingKeys' is inaccessible due to 'private' protection level}} + + // The enum should have a case for each of the vars. + // NOTE: This expectedxerror will need to be removed in the future. + let _ = SimpleChildClass.CodingKeys.w // expected-error {{'CodingKeys' is inaccessible due to 'private' protection level}} + + // Inherited vars should not be part of the CodingKeys enum. + // NOTE: This expected error will need to be updated in the future. + // Should be `type 'SimpleClass.CodingKeys' has no member 'x'` + let _ = SimpleChildClass.CodingKeys.x // expected-error {{'CodingKeys' is inaccessible due to 'private' protection level}} + + // NOTE: This expected error will need to be updated in the future. + // Should be `type 'SimpleClass.CodingKeys' has no member 'y'` + let _ = SimpleChildClass.CodingKeys.y // expected-error {{'CodingKeys' is inaccessible due to 'private' protection level}} + } +} + +// These are wrapped in a dummy function to avoid binding a global variable. +func foo() { + // They should receive synthesized init(from:) and an encode(to:). + let _ = SimpleChildClass.init(from:) + let _ = SimpleChildClass.encode(to:) + + // The synthesized CodingKeys type should not be accessible from outside the + // class. + let _ = SimpleChildClass.CodingKeys.self // expected-error {{'CodingKeys' is inaccessible due to 'private' protection level}} +} diff --git a/test/decl/protocol/special/coding/class_codable_simple.swift b/test/decl/protocol/special/coding/class_codable_simple.swift new file mode 100644 index 0000000000000..a23a77fe188ba --- /dev/null +++ b/test/decl/protocol/special/coding/class_codable_simple.swift @@ -0,0 +1,38 @@ +// RUN: %target-typecheck-verify-swift -verify-ignore-unknown + +// REQUIRES: objc_interop + +import Foundation + +// Simple classes with all Codable properties should get derived conformance to +// Codable. +class SimpleClass : Codable { + var x: Int = 1 + var y: Double = .pi + static var z: String = "foo" + + // These lines have to be within the SimpleClass type because CodingKeys + // should be private. + func foo() { + // They should receive a synthesized CodingKeys enum. + let _ = SimpleClass.CodingKeys.self + + // The enum should have a case for each of the vars. + let _ = SimpleClass.CodingKeys.x + let _ = SimpleClass.CodingKeys.y + + // Static vars should not be part of the CodingKeys enum. + let _ = SimpleClass.CodingKeys.z // expected-error {{type 'SimpleClass.CodingKeys' has no member 'z'}} + } +} + +// These are wrapped in a dummy function to avoid binding a global variable. +func foo() { + // They should receive synthesized init(from:) and an encode(to:). + let _ = SimpleClass.init(from:) + let _ = SimpleClass.encode(to:) + + // The synthesized CodingKeys type should not be accessible from outside the + // class. + let _ = SimpleClass.CodingKeys.self // expected-error {{'CodingKeys' is inaccessible due to 'private' protection level}} +} diff --git a/test/decl/protocol/special/coding/class_codable_simple_extension.swift b/test/decl/protocol/special/coding/class_codable_simple_extension.swift new file mode 100644 index 0000000000000..be5f6b091d5d5 --- /dev/null +++ b/test/decl/protocol/special/coding/class_codable_simple_extension.swift @@ -0,0 +1,40 @@ +// RUN: %target-typecheck-verify-swift -verify-ignore-unknown + +// REQUIRES: objc_interop + +import Foundation + +// Simple classes where Codable conformance is added in extensions should still +// derive conformance. +class SimpleClass { + var x: Int = 1 + var y: Double = .pi + static var z: String = "foo" + + // These lines have to be within the SimpleClass type because CodingKeys + // should be private. + func foo() { + // They should receive a synthesized CodingKeys enum. + let _ = SimpleClass.CodingKeys.self + + // The enum should have a case for each of the vars. + let _ = SimpleClass.CodingKeys.x + let _ = SimpleClass.CodingKeys.y + + // Static vars should not be part of the CodingKeys enum. + let _ = SimpleClass.CodingKeys.z // expected-error {{type 'SimpleClass.CodingKeys' has no member 'z'}} + } +} + +extension SimpleClass : Codable {} + +// These are wrapped in a dummy function to avoid binding a global variable. +func foo() { + // They should receive synthesized init(from:) and an encode(to:). + let _ = SimpleClass.init(from:) + let _ = SimpleClass.encode(to:) + + // The synthesized CodingKeys type should not be accessible from outside the + // class. + let _ = SimpleClass.CodingKeys.self // expected-error {{'CodingKeys' is inaccessible due to 'private' protection level}} +} diff --git a/test/decl/protocol/special/coding/class_codable_simple_multi.swift b/test/decl/protocol/special/coding/class_codable_simple_multi.swift new file mode 100644 index 0000000000000..1c6245e1d41d5 --- /dev/null +++ b/test/decl/protocol/special/coding/class_codable_simple_multi.swift @@ -0,0 +1,2 @@ +// RUN: %target-typecheck-verify-swift -verify-ignore-unknown %S/Inputs/class_codable_simple_multi1.swift %S/Inputs/class_codable_simple_multi2.swift +// RUN: %target-typecheck-verify-swift -verify-ignore-unknown %S/Inputs/class_codable_simple_multi2.swift %S/Inputs/class_codable_simple_multi1.swift diff --git a/test/decl/protocol/special/coding/enum_coding_key.swift b/test/decl/protocol/special/coding/enum_coding_key.swift new file mode 100644 index 0000000000000..fcc6f6412169c --- /dev/null +++ b/test/decl/protocol/special/coding/enum_coding_key.swift @@ -0,0 +1,103 @@ +// RUN: %target-typecheck-verify-swift -verify-ignore-unknown + +// REQUIRES: objc_interop + +import Foundation + +// Enums with no raw type conforming to CodingKey should get implicit derived +// conformance of methods. +enum NoRawTypeKey : CodingKey { + case a, b, c +} + +let _ = NoRawTypeKey.a.stringValue +let _ = NoRawTypeKey(stringValue: "a") +let _ = NoRawTypeKey.a.intValue +let _ = NoRawTypeKey(intValue: 0) + +// Enums with raw type of String conforming to CodingKey should get implicit +// derived conformance of methods. +enum StringKey : String, CodingKey { + case a = "A", b, c = "Foo" +} + +let _ = StringKey.a.stringValue +let _ = StringKey(stringValue: "A") +let _ = StringKey.a.intValue +let _ = StringKey(intValue: 0) + +// Enums with raw type of Int conforming to CodingKey should get implicit +// derived conformance of methods. +enum IntKey : Int, CodingKey { + case a = 3, b, c = 1 +} + +let _ = IntKey.a.stringValue +let _ = IntKey(stringValue: "a") +let _ = IntKey.a.intValue +let _ = IntKey(intValue: 3) + +// Enums with a different raw value conforming to CodingKey should not get +// implicit derived conformance. +enum Int8Key : Int8, CodingKey { // expected-error {{type 'Int8Key' does not conform to protocol 'CodingKey'}} + case a = -1, b = 0, c = 1 +} + +// Structs conforming to CodingKey should not get implicit derived conformance. +struct StructKey : CodingKey { // expected-error {{type 'StructKey' does not conform to protocol 'CodingKey'}} + // expected-note@-1 {{candidate has non-matching type '()'}} + // expected-note@-2 {{candidate has non-matching type '()'}} +} + +// Classes conforming to CodingKey should not get implict derived conformance. +class ClassKey : CodingKey { //expected-error {{type 'ClassKey' does not conform to protocol 'CodingKey'}} + // expected-note@-1 {{candidate has non-matching type '()'}} + // expected-note@-2 {{candidate has non-matching type '()'}} +} + +// Types which are valid for CodingKey derived conformance should not get that +// derivation unless they explicitly conform to CodingKey. +enum X { case a } +enum Y : String { case a } // expected-note {{did you mean the implicitly-synthesized property 'rawValue'?}} +enum Z : Int { case a } // expected-note {{did you mean the implicitly-synthesized property 'rawValue'?}} + +let _ = X.a.stringValue // expected-error {{value of type 'X' has no member 'stringValue'}} +let _ = Y.a.stringValue // expected-error {{value of type 'Y' has no member 'stringValue'}} +let _ = Z.a.stringValue // expected-error {{value of type 'Z' has no member 'stringValue'}} + +let _ = X(stringValue: "a") // expected-error {{'X' cannot be constructed because it has no accessible initializers}} +let _ = Y(stringValue: "a") // expected-error {{incorrect argument label in call (have 'stringValue:', expected 'rawValue:')}} +let _ = Z(stringValue: "a") // expected-error {{incorrect argument label in call (have 'stringValue:', expected 'rawValue:')}} + +let _ = X.a.intValue // expected-error {{value of type 'X' has no member 'intValue'}} +let _ = Y.a.intValue // expected-error {{value of type 'Y' has no member 'intValue'}} +let _ = Z.a.intValue // expected-error {{value of type 'Z' has no member 'intValue'}} + +let _ = X(intValue: 0) // expected-error {{'X' cannot be constructed because it has no accessible initializers}} +let _ = Y(intValue: 0) // expected-error {{incorrect argument label in call (have 'intValue:', expected 'rawValue:')}} +let _ = Z(intValue: 0) // expected-error {{incorrect argument label in call (have 'intValue:', expected 'rawValue:')}} + +// Types which are valid for CodingKey derived conformance should get derivation +// through extensions. +enum X2 { case a } +enum Y2 : String { case a } +enum Z2 : Int { case a } +extension X2 : CodingKey {} +extension Y2 : CodingKey {} +extension Z2 : CodingKey {} + +let _ = X2.a.stringValue +let _ = Y2.a.stringValue +let _ = Z2.a.stringValue + +let _ = X2(stringValue: "a") +let _ = Y2(stringValue: "a") +let _ = Z2(stringValue: "a") + +let _ = X2.a.intValue +let _ = Y2.a.intValue +let _ = Z2.a.intValue + +let _ = X2(intValue: 0) +let _ = Y2(intValue: 0) +let _ = Z2(intValue: 0) diff --git a/test/decl/protocol/special/coding/enum_coding_key_empty.swift b/test/decl/protocol/special/coding/enum_coding_key_empty.swift new file mode 100644 index 0000000000000..32c9f121bbcce --- /dev/null +++ b/test/decl/protocol/special/coding/enum_coding_key_empty.swift @@ -0,0 +1,13 @@ +// RUN: %target-run-simple-swift + +// REQUIRES: executable_test +// REQUIRES: objc_interop + +import Foundation + +// Cannot create an enum with a raw value but no cases. +enum EmptyEnum : CodingKey {} + +// Cannot check accessors since there are no instances of EmptyEnum to test on. +guard EmptyEnum(stringValue: "") == nil else { fatalError() } +guard EmptyEnum(intValue: 0) == nil else { fatalError() } diff --git a/test/decl/protocol/special/coding/enum_coding_key_extension.swift b/test/decl/protocol/special/coding/enum_coding_key_extension.swift new file mode 100644 index 0000000000000..e8179e8bcf84d --- /dev/null +++ b/test/decl/protocol/special/coding/enum_coding_key_extension.swift @@ -0,0 +1,40 @@ +// RUN: %target-typecheck-verify-swift + +// REQUIRES: objc_interop + +import Foundation + +// Enums where CodingKey conformance is added in extensions should still derive +// conformance. +enum NoRawTypeKey { + case a, b, c +} + +extension NoRawTypeKey : CodingKey {} + +let _ = NoRawTypeKey.a.stringValue +let _ = NoRawTypeKey(stringValue: "a") +let _ = NoRawTypeKey.a.intValue +let _ = NoRawTypeKey(intValue: 0) + +enum StringKey : String { + case a = "A", b, c = "Foo" +} + +extension StringKey : CodingKey {} + +let _ = StringKey.a.stringValue +let _ = StringKey(stringValue: "A") +let _ = StringKey.a.intValue +let _ = StringKey(intValue: 0) + +enum IntKey : Int { + case a = 3, b, c = 1 +} + +extension IntKey : CodingKey {} + +let _ = IntKey.a.stringValue +let _ = IntKey(stringValue: "a") +let _ = IntKey.a.intValue +let _ = IntKey(intValue: 3) diff --git a/test/decl/protocol/special/coding/enum_coding_key_extension_multi.swift b/test/decl/protocol/special/coding/enum_coding_key_extension_multi.swift new file mode 100644 index 0000000000000..e40d501f4d590 --- /dev/null +++ b/test/decl/protocol/special/coding/enum_coding_key_extension_multi.swift @@ -0,0 +1,6 @@ +// RUN: %target-swift-frontend -typecheck -verify %S/Inputs/enum_coding_key_extension_multi1.swift %S/Inputs/enum_coding_key_extension_multi2.swift %S/Inputs/enum_coding_key_extension_multi3.swift +// RUN: %target-swift-frontend -typecheck -verify %S/Inputs/enum_coding_key_extension_multi1.swift %S/Inputs/enum_coding_key_extension_multi3.swift %S/Inputs/enum_coding_key_extension_multi2.swift +// RUN: %target-swift-frontend -typecheck -verify %S/Inputs/enum_coding_key_extension_multi2.swift %S/Inputs/enum_coding_key_extension_multi1.swift %S/Inputs/enum_coding_key_extension_multi3.swift +// RUN: %target-swift-frontend -typecheck -verify %S/Inputs/enum_coding_key_extension_multi2.swift %S/Inputs/enum_coding_key_extension_multi3.swift %S/Inputs/enum_coding_key_extension_multi1.swift +// RUN: %target-swift-frontend -typecheck -verify %S/Inputs/enum_coding_key_extension_multi3.swift %S/Inputs/enum_coding_key_extension_multi1.swift %S/Inputs/enum_coding_key_extension_multi2.swift +// RUN: %target-swift-frontend -typecheck -verify %S/Inputs/enum_coding_key_extension_multi3.swift %S/Inputs/enum_coding_key_extension_multi2.swift %S/Inputs/enum_coding_key_extension_multi1.swift diff --git a/test/decl/protocol/special/coding/enum_coding_key_int_raw_type.swift b/test/decl/protocol/special/coding/enum_coding_key_int_raw_type.swift new file mode 100644 index 0000000000000..a49c15f21b1bf --- /dev/null +++ b/test/decl/protocol/special/coding/enum_coding_key_int_raw_type.swift @@ -0,0 +1,52 @@ +// RUN: %target-run-simple-swift + +// REQUIRES: executable_test +// REQUIRES: objc_interop + +import Foundation + +enum IntKey : Int, CodingKey { + case a = 3 + case b // Implicitly 4 + case c = 1 +} + +for (val, str, idx) in [(IntKey.a, "a", 3), (.b, "b", 4), (.c, "c", 1)] { + // Keys with a raw type of Int should get a stringValue which matches the + // case name. + guard val.stringValue == str else { fatalError() } + guard IntKey(stringValue: str) == val else { fatalError() } + + // Keys with a raw type of Int should get an intValue based on their + // rawValue. + guard val.intValue == idx else { fatalError() } + guard IntKey(intValue: idx) == val else { fatalError() } +} + +enum PartialIntKey : Int, CodingKey { + case a = 3 + case b // Implicitly 4 + case c = 1 + + var intValue: Int? { + return self.rawValue + 1 + } + + var stringValue: String { + switch self { + case .a: return "A" + case .b: return "B" + case .c: return "C" + } + } +} + +for (val, str, idx) in [(PartialIntKey.a, "a", 3), (.b, "b", 4), (.c, "c", 1)] { + guard val.stringValue == str.uppercased() else { fatalError() } + guard val.intValue == idx + 1 else { fatalError() } + + // Keys which define some methods should still get derived conformance + // to the others. + guard PartialIntKey(stringValue: str) == val else { fatalError() } + guard PartialIntKey(intValue: idx) == val else { fatalError() } +} diff --git a/test/decl/protocol/special/coding/enum_coding_key_multi.swift b/test/decl/protocol/special/coding/enum_coding_key_multi.swift new file mode 100644 index 0000000000000..620b75341bbb4 --- /dev/null +++ b/test/decl/protocol/special/coding/enum_coding_key_multi.swift @@ -0,0 +1,2 @@ +// RUN: %target-swift-frontend -typecheck -verify %S/Inputs/enum_coding_key_multi1.swift %S/Inputs/enum_coding_key_multi2.swift +// RUN: %target-swift-frontend -typecheck -verify %S/Inputs/enum_coding_key_multi2.swift %S/Inputs/enum_coding_key_multi1.swift diff --git a/test/decl/protocol/special/coding/enum_coding_key_no_raw_type.swift b/test/decl/protocol/special/coding/enum_coding_key_no_raw_type.swift new file mode 100644 index 0000000000000..f43724e2f776c --- /dev/null +++ b/test/decl/protocol/special/coding/enum_coding_key_no_raw_type.swift @@ -0,0 +1,51 @@ +// RUN: %target-run-simple-swift + +// REQUIRES: executable_test +// REQUIRES: objc_interop + +import Foundation + +enum ImplicitKey : CodingKey { + case a, b, c +} + +for (index, (val, str)) in [(ImplicitKey.a, "a"), (.b, "b"), (.c, "c")].enumerated() { + // Keys with no raw type should get a stringValue which matches the case + // name. + guard val.stringValue == str else { fatalError() } + guard ImplicitKey(stringValue: str) == val else { fatalError() } + + // They should not have an intValue. + guard val.intValue == nil else { fatalError() } + guard ImplicitKey(intValue: index) == nil else { fatalError() } +} + +enum PartialImplicitKey : CodingKey { + case a, b, c + + var intValue: Int? { + switch self { + case .a: return 1 + case .b: return 2 + case .c: return 3 + } + } + + var stringValue: String { + switch self { + case .a: return "A" + case .b: return "B" + case .c: return "C" + } + } +} + +for (val, str, idx) in [(PartialImplicitKey.a, "a", 1), (.b, "b", 2), (.c, "c", 3)] { + guard val.stringValue == str.uppercased() else { fatalError() } + guard val.intValue == idx else { fatalError() } + + // Keys which define some methods should still get derived conformance + // to the others. + guard PartialImplicitKey(stringValue: str) == val else { fatalError() } + guard PartialImplicitKey(intValue: idx) == nil else { fatalError() } +} diff --git a/test/decl/protocol/special/coding/enum_coding_key_partial_implementation.swift b/test/decl/protocol/special/coding/enum_coding_key_partial_implementation.swift new file mode 100644 index 0000000000000..204b23ae5b534 --- /dev/null +++ b/test/decl/protocol/special/coding/enum_coding_key_partial_implementation.swift @@ -0,0 +1,151 @@ +// RUN: %target-typecheck-verify-swift + +// REQUIRES: objc_interop + +import Foundation + +// Enums which conform to CodingKey and which should derive conformance should +// get all synthesized methods that they do not implement themselves. +enum NoRawTypeKey1 : CodingKey { + case a, b, c +} + +let _ = NoRawTypeKey1.a.stringValue +let _ = NoRawTypeKey1(stringValue: "a") +let _ = NoRawTypeKey1.a.intValue +let _ = NoRawTypeKey1(intValue: 0) + +enum NoRawTypeKey2 : CodingKey { + case a, b, c + var stringValue: String { return "" } +} + +let _ = NoRawTypeKey2.a.stringValue +let _ = NoRawTypeKey2(stringValue: "a") +let _ = NoRawTypeKey2.a.intValue +let _ = NoRawTypeKey2(intValue: 0) + +enum NoRawTypeKey3 : CodingKey { + case a, b, c + var intValue: Int? { return nil } +} + +let _ = NoRawTypeKey3.a.stringValue +let _ = NoRawTypeKey3(stringValue: "a") +let _ = NoRawTypeKey3.a.intValue +let _ = NoRawTypeKey3(intValue: 0) + +enum NoRawTypeKey4 : CodingKey { + case a, b, c + init?(stringValue: String) { return nil } +} + +let _ = NoRawTypeKey4.a.stringValue +let _ = NoRawTypeKey4(stringValue: "a") +let _ = NoRawTypeKey4.a.intValue +let _ = NoRawTypeKey4(intValue: 0) + +enum NoRawTypeKey5 : CodingKey { + case a, b, c + init?(intValue: Int) { return nil } +} + +let _ = NoRawTypeKey5.a.stringValue +let _ = NoRawTypeKey5(stringValue: "a") +let _ = NoRawTypeKey5.a.intValue +let _ = NoRawTypeKey5(intValue: 0) + +enum NoRawTypeKey6 : CodingKey { + case a, b, c + var stringValue: String { return "" } + var intValue: Int? { return nil } +} + +let _ = NoRawTypeKey6.a.stringValue +let _ = NoRawTypeKey6(stringValue: "a") +let _ = NoRawTypeKey6.a.intValue +let _ = NoRawTypeKey6(intValue: 0) + +enum NoRawTypeKey7 : CodingKey { + case a, b, c + var stringValue: String { return "" } + init?(stringValue: String) { return nil } +} + +let _ = NoRawTypeKey7.a.stringValue +let _ = NoRawTypeKey7(stringValue: "a") +let _ = NoRawTypeKey7.a.intValue +let _ = NoRawTypeKey7(intValue: 0) + +enum NoRawTypeKey8 : CodingKey { + case a, b, c + var stringValue: String { return "" } + init?(intValue: Int) { return nil } +} + +let _ = NoRawTypeKey8.a.stringValue +let _ = NoRawTypeKey8(stringValue: "a") +let _ = NoRawTypeKey8.a.intValue +let _ = NoRawTypeKey8(intValue: 0) + +enum NoRawTypeKey9 : CodingKey { + case a, b, c + var intValue: Int? { return nil } + init?(stringValue: String) { return nil } +} + +let _ = NoRawTypeKey9.a.stringValue +let _ = NoRawTypeKey9(stringValue: "a") +let _ = NoRawTypeKey9.a.intValue +let _ = NoRawTypeKey9(intValue: 0) + +enum NoRawTypeKey10 : CodingKey { + case a, b, c + var intValue: Int? { return nil } + init?(intValue: Int) { return nil } +} + +let _ = NoRawTypeKey10.a.stringValue +let _ = NoRawTypeKey10(stringValue: "a") +let _ = NoRawTypeKey10.a.intValue +let _ = NoRawTypeKey10(intValue: 0) + +enum NoRawTypeKey11 : CodingKey { + case a, b, c + var stringValue: String { return "" } + var intValue: Int? { return nil } + init?(stringValue: String) { return nil } +} + +let _ = NoRawTypeKey11.a.stringValue +let _ = NoRawTypeKey11(stringValue: "a") +let _ = NoRawTypeKey11.a.intValue +let _ = NoRawTypeKey11(intValue: 0) + +enum NoRawTypeKey12 : CodingKey { + case a, b, c + var stringValue: String { return "" } + var intValue: Int? { return nil } + init?(intValue: Int) { return nil } +} + +let _ = NoRawTypeKey12.a.stringValue +let _ = NoRawTypeKey12(stringValue: "a") +let _ = NoRawTypeKey12.a.intValue +let _ = NoRawTypeKey12(intValue: 0) + +// Enums which provide implementations for all CodingKey methods should not +// derive conformance (but derived conformance should not interfere with the +// existing methods). +enum NoRawTypeKey13 : CodingKey { + case a, b, c + var stringValue: String { return "" } + var intValue: Int? { return nil } + init?(stringValue: String) { return nil } + init?(intValue: Int) { return nil } +} + +let _ = NoRawTypeKey13.a.stringValue +let _ = NoRawTypeKey13(stringValue: "a") +let _ = NoRawTypeKey13.a.intValue +let _ = NoRawTypeKey13(intValue: 0) diff --git a/test/decl/protocol/special/coding/enum_coding_key_string_raw_type.swift b/test/decl/protocol/special/coding/enum_coding_key_string_raw_type.swift new file mode 100644 index 0000000000000..bb5c86c89ce5f --- /dev/null +++ b/test/decl/protocol/special/coding/enum_coding_key_string_raw_type.swift @@ -0,0 +1,55 @@ +// RUN: %target-run-simple-swift + +// REQUIRES: executable_test +// REQUIRES: objc_interop + +import Foundation + +enum StringKey : String, CodingKey { + case a = "A" + case b // Implicitly "b" + case c = "Foo" +} + +for (index, (val, str)) in [(StringKey.a, "A"), (.b, "b"), (.c, "Foo")].enumerated() { + // Keys with a raw type of String should get a stringValue based on their + // rawValue. + guard val.stringValue == str else { fatalError() } + guard StringKey(stringValue: str) == val else { fatalError() } + + // They should not have an intValue. + guard val.intValue == nil else { fatalError() } + guard StringKey(intValue: index) == nil else { fatalError() } +} + +enum PartialStringKey : String, CodingKey { + case a = "A" + case b // Implicitly "b" + case c = "Foo" + + var intValue: Int? { + switch self { + case .a: return 1 + case .b: return 2 + case .c: return 3 + } + } + + var stringValue: String { + switch self { + case .a: return "x" + case .b: return "y" + case .c: return "z" + } + } +} + +for (val, realStr, str, idx) in [(PartialStringKey.a, "A", "x", 1), (.b, "b", "y", 2), (.c, "Foo", "z", 3)] { + guard val.stringValue == str else { fatalError() } + guard val.intValue == idx else { fatalError() } + + // Keys which define some methods should still get derived conformance + // to the others. + guard PartialStringKey(stringValue: realStr) == val else { fatalError() } + guard PartialStringKey(intValue: idx) == nil else { fatalError() } +} diff --git a/test/decl/protocol/special/coding/struct_codable_computed_vars.swift b/test/decl/protocol/special/coding/struct_codable_computed_vars.swift new file mode 100644 index 0000000000000..b5b6f6db08a74 --- /dev/null +++ b/test/decl/protocol/special/coding/struct_codable_computed_vars.swift @@ -0,0 +1,40 @@ +// RUN: %target-typecheck-verify-swift -verify-ignore-unknown + +// REQUIRES: objc_interop + +import Foundation + +// Structs with computed members should get synthesized conformance to Codable, +// but their lazy and computed members should be skipped as part of the +// synthesis. +struct StructWithComputedMembers : Codable { + var x: Int + lazy var y: Double = Double.pi + var z: String { + return "foo" + } + + // These lines have to be within the StructWithComputedMembers type because + // CodingKeys should be private. + func foo() { + // They should receive a synthesized CodingKeys enum. + let _ = StructWithComputedMembers.CodingKeys.self + + // The enum should have a case for each of the vars. + let _ = StructWithComputedMembers.CodingKeys.x + + // Lazy vars should not be part of the CodingKeys enum. + let _ = StructWithComputedMembers.CodingKeys.y // expected-error {{type 'StructWithComputedMembers.CodingKeys' has no member 'y'}} + + // Computed vars should not be part of the CodingKeys enum. + let _ = StructWithComputedMembers.CodingKeys.z // expected-error {{type 'StructWithComputedMembers.CodingKeys' has no member 'z'}} + } +} + +// They should receive synthesized init(from:) and an encode(to:). +let _ = StructWithComputedMembers.init(from:) +let _ = StructWithComputedMembers.encode(to:) + +// The synthesized CodingKeys type should not be accessible from outside the +// struct. +let _ = StructWithComputedMembers.CodingKeys.self // expected-error {{'CodingKeys' is inaccessible due to 'private' protection level}} diff --git a/test/decl/protocol/special/coding/struct_codable_simple.swift b/test/decl/protocol/special/coding/struct_codable_simple.swift new file mode 100644 index 0000000000000..83d5fbba2ea52 --- /dev/null +++ b/test/decl/protocol/special/coding/struct_codable_simple.swift @@ -0,0 +1,35 @@ +// RUN: %target-typecheck-verify-swift -verify-ignore-unknown + +// REQUIRES: objc_interop + +import Foundation + +// Simple structs with all Codable properties should get derived conformance to +// Codable. +struct SimpleStruct : Codable { + var x: Int + var y: Double + static var z: String = "foo" + + // These lines have to be within the SimpleStruct type because CodingKeys + // should be private. + func foo() { + // They should receive a synthesized CodingKeys enum. + let _ = SimpleStruct.CodingKeys.self + + // The enum should have a case for each of the vars. + let _ = SimpleStruct.CodingKeys.x + let _ = SimpleStruct.CodingKeys.y + + // Static vars should not be part of the CodingKeys enum. + let _ = SimpleStruct.CodingKeys.z // expected-error {{type 'SimpleStruct.CodingKeys' has no member 'z'}} + } +} + +// They should receive synthesized init(from:) and an encode(to:). +let _ = SimpleStruct.init(from:) +let _ = SimpleStruct.encode(to:) + +// The synthesized CodingKeys type should not be accessible from outside the +// struct. +let _ = SimpleStruct.CodingKeys.self // expected-error {{'CodingKeys' is inaccessible due to 'private' protection level}} diff --git a/test/decl/protocol/special/coding/struct_codable_simple_extension.swift b/test/decl/protocol/special/coding/struct_codable_simple_extension.swift new file mode 100644 index 0000000000000..608460336005e --- /dev/null +++ b/test/decl/protocol/special/coding/struct_codable_simple_extension.swift @@ -0,0 +1,40 @@ +// RUN: %target-typecheck-verify-swift -verify-ignore-unknown + +// REQUIRES: objc_interop + +import Foundation + +// Simple structs where Codable conformance is added in extensions should still +// derive conformance. +struct SimpleStruct { + var x: Int + var y: Double + static var z: String = "foo" + + // These lines have to be within the SimpleStruct type because CodingKeys + // should be private. + func foo() { + // They should receive synthesized init(from:) and an encode(to:). + let _ = SimpleStruct.init(from:) + let _ = SimpleStruct.encode(to:) + + // They should receive a synthesized CodingKeys enum. + let _ = SimpleStruct.CodingKeys.self + + // The enum should have a case for each of the vars. + let _ = SimpleStruct.CodingKeys.x + let _ = SimpleStruct.CodingKeys.y + + // Static vars should not be part of the CodingKeys enum. + let _ = SimpleStruct.CodingKeys.z // expected-error {{type 'SimpleStruct.CodingKeys' has no member 'z'}} + } +} + +extension SimpleStruct : Codable {} + +// These are wrapped in a dummy function to avoid binding a global variable. +func foo() { + // The synthesized CodingKeys type should not be accessible from outside the + // struct. + let _ = SimpleStruct.CodingKeys.self // expected-error {{'CodingKeys' is inaccessible due to 'private' protection level}} +} diff --git a/test/decl/protocol/special/coding/struct_codable_simple_multi.swift b/test/decl/protocol/special/coding/struct_codable_simple_multi.swift new file mode 100644 index 0000000000000..0f6c47cfa0ae0 --- /dev/null +++ b/test/decl/protocol/special/coding/struct_codable_simple_multi.swift @@ -0,0 +1,2 @@ +// RUN: %target-typecheck-verify-swift -verify-ignore-unknown %S/Inputs/struct_codable_simple_multi1.swift %S/Inputs/struct_codable_simple_multi2.swift +// RUN: %target-typecheck-verify-swift -verify-ignore-unknown %S/Inputs/struct_codable_simple_multi2.swift %S/Inputs/struct_codable_simple_multi1.swift