diff --git a/include/swift/AST/Decl.h b/include/swift/AST/Decl.h index 9a222dff5d02a..74092ffcd1c94 100644 --- a/include/swift/AST/Decl.h +++ b/include/swift/AST/Decl.h @@ -6524,7 +6524,7 @@ class EnumElementDecl : public DeclContext, public ValueDecl { bool isIndirect() const { return getAttrs().hasAttribute(); } - + /// Do not call this! /// It exists to let the AST walkers get the raw value without forcing a request. LiteralExpr *getRawValueUnchecked() const { return RawValueExpr; } diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index da3ecf6678d29..4243fa746b802 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -2908,6 +2908,13 @@ NOTE(decodable_suggest_overriding_init_here,none, "did you mean to override 'init(from:)'?", ()) NOTE(codable_suggest_overriding_init_here,none, "did you mean to override 'init(from:)' and 'encode(to:)'?", ()) +NOTE(codable_enum_duplicate_case_name_here,none, + "cannot automatically synthesize %0 because %1 has duplicate " + "case name %2", (Type, Type, Identifier)) +NOTE(codable_enum_duplicate_parameter_name_here,none, + "cannot automatically synthesize %0 for %1 because " + "user defined parameter name %2 in %3 conflicts with " + "automatically generated parameter name", (Type, Type, Identifier, Identifier)) WARNING(decodable_property_will_not_be_decoded, none, "immutable property will not be decoded because it is declared with " diff --git a/include/swift/AST/KnownIdentifiers.def b/include/swift/AST/KnownIdentifiers.def index dd0ec4630a153..d2d69ce4bd1fe 100644 --- a/include/swift/AST/KnownIdentifiers.def +++ b/include/swift/AST/KnownIdentifiers.def @@ -24,6 +24,7 @@ IDENTIFIER(AllCases) IDENTIFIER(allCases) +IDENTIFIER(allKeys) IDENTIFIER(alloc) IDENTIFIER(allocWithZone) IDENTIFIER(allZeros) @@ -43,16 +44,20 @@ IDENTIFIER(callAsFunction) IDENTIFIER(Change) IDENTIFIER_WITH_NAME(code_, "_code") IDENTIFIER(CodingKeys) +IDENTIFIER(codingPath) IDENTIFIER(combine) IDENTIFIER_(Concurrency) IDENTIFIER(container) +IDENTIFIER(Context) IDENTIFIER(CoreGraphics) IDENTIFIER(CoreMedia) IDENTIFIER(CGFloat) IDENTIFIER(CoreFoundation) +IDENTIFIER(count) IDENTIFIER(CVarArg) IDENTIFIER(Darwin) IDENTIFIER(dealloc) +IDENTIFIER(debugDescription) IDENTIFIER(Decodable) IDENTIFIER(decode) IDENTIFIER(decodeIfPresent) @@ -82,11 +87,13 @@ IDENTIFIER(fromRaw) IDENTIFIER(hash) IDENTIFIER(hasher) IDENTIFIER(hashValue) +IDENTIFIER(init) IDENTIFIER(initialize) IDENTIFIER(initStorage) IDENTIFIER(initialValue) IDENTIFIER(into) IDENTIFIER(intValue) +IDENTIFIER(invalidValue) IDENTIFIER(Key) IDENTIFIER(KeyedDecodingContainer) IDENTIFIER(KeyedEncodingContainer) @@ -94,6 +101,7 @@ IDENTIFIER(keyedBy) IDENTIFIER(keyPath) IDENTIFIER(makeIterator) IDENTIFIER(makeAsyncIterator) +IDENTIFIER(nestedContainer) IDENTIFIER(Iterator) IDENTIFIER(AsyncIterator) IDENTIFIER(load) @@ -134,6 +142,8 @@ IDENTIFIER(to) IDENTIFIER(toRaw) IDENTIFIER(Type) IDENTIFIER(type) +IDENTIFIER(typeMismatch) +IDENTIFIER(underlyingError) IDENTIFIER(Value) IDENTIFIER(value) IDENTIFIER_WITH_NAME(value_, "_value") diff --git a/include/swift/AST/KnownStdlibTypes.def b/include/swift/AST/KnownStdlibTypes.def index 94b0e678a61ef..93f99d43caa9b 100644 --- a/include/swift/AST/KnownStdlibTypes.def +++ b/include/swift/AST/KnownStdlibTypes.def @@ -90,6 +90,8 @@ KNOWN_STDLIB_TYPE_DECL(Decoder, ProtocolDecl, 1) KNOWN_STDLIB_TYPE_DECL(KeyedEncodingContainer, NominalTypeDecl, 1) KNOWN_STDLIB_TYPE_DECL(KeyedDecodingContainer, NominalTypeDecl, 1) KNOWN_STDLIB_TYPE_DECL(RangeReplaceableCollection, ProtocolDecl, 1) +KNOWN_STDLIB_TYPE_DECL(EncodingError, NominalTypeDecl, 0) +KNOWN_STDLIB_TYPE_DECL(DecodingError, NominalTypeDecl, 0) KNOWN_STDLIB_TYPE_DECL(Result, NominalTypeDecl, 2) diff --git a/include/swift/Basic/LangOptions.h b/include/swift/Basic/LangOptions.h index 7d3b85b180250..c9f3d3d874a6c 100644 --- a/include/swift/Basic/LangOptions.h +++ b/include/swift/Basic/LangOptions.h @@ -247,6 +247,9 @@ namespace swift { /// Enable experimental flow-sensitive concurrent captures. bool EnableExperimentalFlowSensitiveConcurrentCaptures = false; + /// Enable experimental derivation of `Codable` for enums. + bool EnableExperimentalEnumCodableDerivation = false; + /// Disable the implicit import of the _Concurrency module. bool DisableImplicitConcurrencyModuleImport = false; diff --git a/include/swift/Option/FrontendOptions.td b/include/swift/Option/FrontendOptions.td index 5127e871b3633..161493ade8909 100644 --- a/include/swift/Option/FrontendOptions.td +++ b/include/swift/Option/FrontendOptions.td @@ -222,6 +222,10 @@ def enable_experimental_flow_sensitive_concurrent_captures : Flag<["-"], "enable-experimental-flow-sensitive-concurrent-captures">, HelpText<"Enable flow-sensitive concurrent captures">; +def enable_experimental_enum_codable_derivation : + Flag<["-"], "enable-experimental-enum-codable-derivation">, + HelpText<"Enable experimental derivation of Codable for enums">; + def enable_resilience : Flag<["-"], "enable-resilience">, HelpText<"Deprecated, use -enable-library-evolution instead">; } diff --git a/lib/Frontend/CompilerInvocation.cpp b/lib/Frontend/CompilerInvocation.cpp index 17118ea1326d6..e323b6e2b006a 100644 --- a/lib/Frontend/CompilerInvocation.cpp +++ b/lib/Frontend/CompilerInvocation.cpp @@ -388,6 +388,9 @@ static bool ParseLangArgs(LangOptions &Opts, ArgList &Args, Opts.EnableExperimentalFlowSensitiveConcurrentCaptures |= Args.hasArg(OPT_enable_experimental_flow_sensitive_concurrent_captures); + Opts.EnableExperimentalEnumCodableDerivation |= + Args.hasArg(OPT_enable_experimental_enum_codable_derivation); + Opts.DisableImplicitConcurrencyModuleImport |= Args.hasArg(OPT_disable_implicit_concurrency_module_import); diff --git a/lib/Sema/DerivedConformanceCodable.cpp b/lib/Sema/DerivedConformanceCodable.cpp index 67ddc2041a6e2..d5a04be0b358e 100644 --- a/lib/Sema/DerivedConformanceCodable.cpp +++ b/lib/Sema/DerivedConformanceCodable.cpp @@ -16,6 +16,7 @@ //===----------------------------------------------------------------------===// #include "TypeChecker.h" +#include "llvm/ADT/STLExtras.h" #include "swift/AST/Decl.h" #include "swift/AST/Expr.h" #include "swift/AST/Module.h" @@ -23,6 +24,7 @@ #include "swift/AST/Pattern.h" #include "swift/AST/Stmt.h" #include "swift/AST/Types.h" +#include "swift/Basic/StringExtras.h" #include "DerivedConformances.h" using namespace swift; @@ -53,6 +55,151 @@ static Identifier getVarNameForCoding(VarDecl *var) { return var->getName(); } +/// Compute the Identifier for the CodingKey of an enum case +static Identifier caseCodingKeysIdentifier(const ASTContext &C, + EnumElementDecl *elt) { + llvm::SmallString<16> scratch; + camel_case::appendSentenceCase(scratch, elt->getBaseIdentifier().str()); + llvm::StringRef result = + camel_case::appendSentenceCase(scratch, C.Id_CodingKeys.str()); + return C.getIdentifier(result); +} + +/// Fetches the \c CodingKeys enum nested in \c target, potentially reaching +/// through a typealias if the "CodingKeys" entity is a typealias. +/// +/// This is only useful once a \c CodingKeys enum has been validated (via \c +/// hasValidCodingKeysEnum) or synthesized (via \c synthesizeCodingKeysEnum). +/// +/// \param C The \c ASTContext to perform the lookup in. +/// +/// \param target The target type to look in. +/// +/// \return A retrieved canonical \c CodingKeys enum if \c target has a valid +/// one; \c nullptr otherwise. +static EnumDecl *lookupEvaluatedCodingKeysEnum(ASTContext &C, + NominalTypeDecl *target, + Identifier identifier) { + auto codingKeyDecls = target->lookupDirect(DeclName(identifier)); + if (codingKeyDecls.empty()) + return nullptr; + + auto *codingKeysDecl = codingKeyDecls.front(); + if (auto *typealiasDecl = dyn_cast(codingKeysDecl)) + codingKeysDecl = typealiasDecl->getDeclaredInterfaceType()->getAnyNominal(); + + return dyn_cast(codingKeysDecl); +} + +static EnumDecl *lookupEvaluatedCodingKeysEnum(ASTContext &C, + NominalTypeDecl *target) { + return lookupEvaluatedCodingKeysEnum(C, target, C.Id_CodingKeys); +} + +static EnumElementDecl *lookupEnumCase(ASTContext &C, NominalTypeDecl *target, + Identifier identifier) { + auto elementDecls = target->lookupDirect(DeclName(identifier)); + if (elementDecls.empty()) + return nullptr; + + auto *elementDecl = elementDecls.front(); + + return dyn_cast(elementDecl); +} + +static NominalTypeDecl *lookupErrorContext(ASTContext &C, + NominalTypeDecl *errorDecl) { + auto elementDecls = errorDecl->lookupDirect(C.Id_Context); + if (elementDecls.empty()) + return nullptr; + + auto *decl = elementDecls.front(); + + return dyn_cast(decl); +} + +static EnumDecl *addImplicitCodingKeys_enum(EnumDecl *target) { + auto &C = target->getASTContext(); + + // We want to look through all the case declarations of this enum to create + // enum cases based on those case names. + auto *codingKeyProto = C.getProtocol(KnownProtocolKind::CodingKey); + auto codingKeyType = codingKeyProto->getDeclaredInterfaceType(); + TypeLoc protoTypeLoc[1] = {TypeLoc::withoutLoc(codingKeyType)}; + ArrayRef inherited = C.AllocateCopy(protoTypeLoc); + + llvm::SmallVector codingKeys; + + auto *enumDecl = new (C) EnumDecl(SourceLoc(), C.Id_CodingKeys, SourceLoc(), + inherited, nullptr, target); + enumDecl->setImplicit(); + enumDecl->setAccess(AccessLevel::Private); + + for (auto *elementDecl : target->getAllElements()) { + auto *elt = + new (C) EnumElementDecl(SourceLoc(), elementDecl->getBaseName(), + nullptr, SourceLoc(), nullptr, enumDecl); + elt->setImplicit(); + enumDecl->addMember(elt); + } + // Forcibly derive conformance to CodingKey. + TypeChecker::checkConformancesInContext(enumDecl); + + target->addMember(enumDecl); + + return enumDecl; +} + +static EnumDecl *addImplicitCaseCodingKeys(EnumDecl *target, + EnumElementDecl *elementDecl, + EnumDecl *codingKeysEnum) { + auto &C = target->getASTContext(); + + auto enumIdentifier = caseCodingKeysIdentifier(C, elementDecl); + + auto *codingKeyProto = C.getProtocol(KnownProtocolKind::CodingKey); + auto codingKeyType = codingKeyProto->getDeclaredInterfaceType(); + TypeLoc protoTypeLoc[1] = {TypeLoc::withoutLoc(codingKeyType)}; + ArrayRef inherited = C.AllocateCopy(protoTypeLoc); + + // Only derive if this case exist in the CodingKeys enum + auto *codingKeyCase = + lookupEnumCase(C, codingKeysEnum, elementDecl->getBaseIdentifier()); + if (!codingKeyCase) + return nullptr; + + auto *caseEnum = new (C) EnumDecl(SourceLoc(), enumIdentifier, SourceLoc(), + inherited, nullptr, target); + caseEnum->setImplicit(); + caseEnum->setAccess(AccessLevel::Private); + + if (elementDecl->hasAssociatedValues()) { + for (auto entry : llvm::enumerate(*elementDecl->getParameterList())) { + auto *paramDecl = entry.value(); + + // if the type conforms to {En,De}codable, add it to the enum. + Identifier paramIdentifier = getVarNameForCoding(paramDecl); + bool generatedName = false; + if (paramIdentifier.empty()) { + paramIdentifier = C.getIdentifier("_" + std::to_string(entry.index())); + generatedName = true; + } + + auto *elt = + new (C) EnumElementDecl(SourceLoc(), paramIdentifier, nullptr, + SourceLoc(), nullptr, caseEnum); + elt->setImplicit(); + caseEnum->addMember(elt); + } + } + + // Forcibly derive conformance to CodingKey. + TypeChecker::checkConformancesInContext(caseEnum); + target->addMember(caseEnum); + + return caseEnum; +} + // Create CodingKeys in the parent type always, because both // Encodable and Decodable might want to use it, and they may have // different conditional bounds. CodingKeys is simple and can't @@ -63,6 +210,10 @@ static Identifier getVarNameForCoding(VarDecl *var) { // CodingKeys. It will also help in our quest to separate semantic and parsed // members. static EnumDecl *addImplicitCodingKeys(NominalTypeDecl *target) { + if (auto *enumDecl = dyn_cast(target)) { + return addImplicitCodingKeys_enum(enumDecl); + } + auto &C = target->getASTContext(); assert(target->lookupDirect(DeclName(C.Id_CodingKeys)).empty()); @@ -114,27 +265,10 @@ static EnumDecl *addImplicitCodingKeys(NominalTypeDecl *target) { return enumDecl; } -/// Validates the given CodingKeys enum decl by ensuring its cases are a 1-to-1 -/// match with the stored vars of the given type. -static bool validateCodingKeysEnum(DerivedConformance &derived) { +static EnumDecl *validateCodingKeysType(const DerivedConformance &derived, + TypeDecl *_codingKeysTypeDecl) { auto &C = derived.Context; - auto codingKeysDecls = - derived.Nominal->lookupDirect(DeclName(C.Id_CodingKeys)); - - if (codingKeysDecls.size() > 1) { - return false; - } - - ValueDecl *result = codingKeysDecls.empty() - ? addImplicitCodingKeys(derived.Nominal) - : codingKeysDecls.front(); - auto *codingKeysTypeDecl = dyn_cast(result); - if (!codingKeysTypeDecl) { - result->diagnose(diag::codable_codingkeys_type_is_not_an_enum_here, - derived.getProtocolType()); - return false; - } - + auto *codingKeysTypeDecl = _codingKeysTypeDecl; // CodingKeys may be a typealias. If so, follow the alias to its canonical // type. auto codingKeysType = codingKeysTypeDecl->getDeclaredInterfaceType(); @@ -150,11 +284,11 @@ static bool validateCodingKeysEnum(DerivedConformance &derived) { // the location of the usage, since there isn't an underlying type to // diagnose on. SourceLoc loc = codingKeysTypeDecl ? codingKeysTypeDecl->getLoc() - : cast(result)->getLoc(); + : cast(_codingKeysTypeDecl)->getLoc(); C.Diags.diagnose(loc, diag::codable_codingkeys_type_does_not_conform_here, derived.getProtocolType()); - return false; + return nullptr; } auto *codingKeysDecl = @@ -163,43 +297,45 @@ static bool validateCodingKeysEnum(DerivedConformance &derived) { codingKeysTypeDecl->diagnose( diag::codable_codingkeys_type_is_not_an_enum_here, derived.getProtocolType()); - return false; + return nullptr; } - // Look through all var decls in the given type. - // * Filter out lazy/computed vars. - // * Filter out ones which are present in the given decl (by name). + return codingKeysDecl; +} + +/// Validates the given CodingKeys enum decl by ensuring its cases are a 1-to-1 +/// match with the the given VarDecls. +/// +/// \param varDecls The \c var decls to validate against. +/// \param codingKeysTypeDecl The \c CodingKeys enum decl to validate. +static bool validateCodingKeysEnum(const DerivedConformance &derived, + llvm::SmallMapVector varDecls, + TypeDecl *codingKeysTypeDecl) { + auto *codingKeysDecl = validateCodingKeysType(derived, codingKeysTypeDecl); + if (!codingKeysDecl) + return false; + + // Look through all var decls. // // 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 which don't have a default value // (for Decodable), then this decl doesn't match. - - // Here we'll hold on to properties by name -- when we've validated a property - // against its CodingKey entry, it will get removed. - llvm::SmallMapVector properties; - for (auto *varDecl : derived.Nominal->getStoredProperties()) { - if (!varDecl->isUserAccessible()) - continue; - - properties[getVarNameForCoding(varDecl)] = varDecl; - } - - bool propertiesAreValid = true; + bool varDeclsAreValid = true; for (auto elt : codingKeysDecl->getAllElements()) { - auto it = properties.find(elt->getBaseIdentifier()); - if (it == properties.end()) { + auto it = varDecls.find(elt->getBaseIdentifier()); + if (it == varDecls.end()) { elt->diagnose(diag::codable_extraneous_codingkey_case_here, elt->getBaseIdentifier()); // TODO: Investigate typo-correction here; perhaps the case name was // misspelled and we can provide a fix-it. - propertiesAreValid = false; + varDeclsAreValid = false; continue; } // We have a property to map to. Ensure it's {En,De}codable. auto target = derived.getConformanceContext()->mapTypeIntoContext( - it->second->getValueInterfaceType()); + it->second->getValueInterfaceType()); if (TypeChecker::conformsToProtocol(target, derived.Protocol, derived.getConformanceContext()) .isInvalid()) { @@ -209,21 +345,21 @@ static bool validateCodingKeysEnum(DerivedConformance &derived) { }; it->second->diagnose(diag::codable_non_conforming_property_here, derived.getProtocolType(), typeLoc); - propertiesAreValid = false; + varDeclsAreValid = false; } else { // The property was valid. Remove it from the list. - properties.erase(it); + varDecls.erase(it); } } - if (!propertiesAreValid) + if (!varDeclsAreValid) return false; - // If there are any remaining properties which the CodingKeys did not cover, + // If there are any remaining var decls which the CodingKeys did not cover, // we can skip them on encode. On decode, though, we can only skip them if // they have a default value. if (derived.Protocol->isSpecificProtocol(KnownProtocolKind::Decodable)) { - for (auto &entry : properties) { + for (auto &entry : varDecls) { const auto *pbd = entry.second->getParentPatternBinding(); if (pbd && pbd->isDefaultInitializable()) { continue; @@ -233,45 +369,156 @@ static bool validateCodingKeysEnum(DerivedConformance &derived) { continue; } + if (auto *paramDecl = dyn_cast(entry.second)) { + if (paramDecl->hasDefaultExpr()) { + continue; + } + } + // The var was not default initializable, and did not have an explicit // initial value. - propertiesAreValid = false; + varDeclsAreValid = false; entry.second->diagnose(diag::codable_non_decoded_property_here, derived.getProtocolType(), entry.first); } } - return propertiesAreValid; + return varDeclsAreValid; } -/// Fetches the \c CodingKeys enum nested in \c target, potentially reaching -/// through a typealias if the "CodingKeys" entity is a typealias. -/// -/// This is only useful once a \c CodingKeys enum has been validated (via \c -/// hasValidCodingKeysEnum) or synthesized (via \c synthesizeCodingKeysEnum). -/// -/// \param C The \c ASTContext to perform the lookup in. -/// -/// \param target The target type to look in. +static bool validateCodingKeysEnum_enum(const DerivedConformance &derived, + TypeDecl *codingKeysTypeDecl) { + auto *enumDecl = dyn_cast(derived.Nominal); + if (!enumDecl) { + return false; + } + llvm::SmallSetVector caseNames; + for (auto *elt : enumDecl->getAllElements()) { + caseNames.insert(elt->getBaseIdentifier()); + } + + auto *codingKeysDecl = validateCodingKeysType(derived, + codingKeysTypeDecl); + if (!codingKeysDecl) + return false; + + bool casesAreValid = true; + for (auto *elt : codingKeysDecl->getAllElements()) { + if (!caseNames.contains(elt->getBaseIdentifier())) { + elt->diagnose(diag::codable_extraneous_codingkey_case_here, + elt->getBaseIdentifier()); + casesAreValid = false; + } + } + + return casesAreValid; +} + +/// Looks up and validates a CodingKeys enum for the given DerivedConformance. +/// If a CodingKeys enum does not exist, one will be derived. +static bool validateCodingKeysEnum(const DerivedConformance &derived) { + auto &C = derived.Context; + + auto codingKeysDecls = + derived.Nominal->lookupDirect(DeclName(C.Id_CodingKeys)); + + if (codingKeysDecls.size() > 1) { + return false; + } + + ValueDecl *result = codingKeysDecls.empty() + ? addImplicitCodingKeys(derived.Nominal) + : codingKeysDecls.front(); + auto *codingKeysTypeDecl = dyn_cast(result); + if (!codingKeysTypeDecl) { + result->diagnose(diag::codable_codingkeys_type_is_not_an_enum_here, + derived.getProtocolType()); + return false; + } + + if (dyn_cast(derived.Nominal)) { + return validateCodingKeysEnum_enum(derived, codingKeysTypeDecl); + } else { + + // Look through all var decls in the given type. + // * Filter out lazy/computed vars. + // * Filter out ones which are present in the given decl (by name). + + // Here we'll hold on to properties by name -- when we've validated a property + // against its CodingKey entry, it will get removed. + llvm::SmallMapVector properties; + for (auto *varDecl : derived.Nominal->getStoredProperties()) { + if (!varDecl->isUserAccessible()) + continue; + + properties[getVarNameForCoding(varDecl)] = varDecl; + } + + return validateCodingKeysEnum(derived, properties, codingKeysTypeDecl); + } +} + +/// Looks up and validates a CaseCodingKeys enum for the given elementDecl. +/// If a CaseCodingKeys enum does not exist, one will be derived. /// -/// \return A retrieved canonical \c CodingKeys enum if \c target has a valid -/// one; \c nullptr otherwise. -static EnumDecl *lookupEvaluatedCodingKeysEnum(ASTContext &C, - NominalTypeDecl *target) { - auto codingKeyDecls = target->lookupDirect(DeclName(C.Id_CodingKeys)); - if (codingKeyDecls.empty()) - return nullptr; +/// \param elementDecl The \c EnumElementDecl to validate against. +static bool validateCaseCodingKeysEnum(const DerivedConformance &derived, + EnumElementDecl *elementDecl) { + auto &C = derived.Context; + auto *enumDecl = dyn_cast(derived.Nominal); + if (!enumDecl) { + return false; + } - auto *codingKeysDecl = codingKeyDecls.front(); - if (auto *typealiasDecl = dyn_cast(codingKeysDecl)) - codingKeysDecl = typealiasDecl->getDeclaredInterfaceType()->getAnyNominal(); + auto *codingKeysEnum = lookupEvaluatedCodingKeysEnum(C, enumDecl); - return dyn_cast(codingKeysDecl); + // At this point we ran validation for this and should have + // a CodingKeys decl. + assert(codingKeysEnum && "Missing CodingKeys decl."); + + auto cckIdentifier = caseCodingKeysIdentifier(C, elementDecl); + auto caseCodingKeysDecls = + enumDecl->lookupDirect(DeclName(cckIdentifier)); + + if (caseCodingKeysDecls.size() > 1) { + return false; + } + + ValueDecl *result = caseCodingKeysDecls.empty() + ? addImplicitCaseCodingKeys( + enumDecl, elementDecl, codingKeysEnum) + : caseCodingKeysDecls.front(); + auto *codingKeysTypeDecl = dyn_cast(result); + if (!codingKeysTypeDecl) { + result->diagnose(diag::codable_codingkeys_type_is_not_an_enum_here, + derived.getProtocolType()); + return false; + } + + // Here we'll hold on to parameters by name -- when we've validated a parameter + // against its CodingKey entry, it will get removed. + llvm::SmallMapVector properties; + if (elementDecl->hasAssociatedValues()) { + for (auto entry : llvm::enumerate(*elementDecl->getParameterList())) { + auto paramDecl = entry.value(); + if (!paramDecl->isUserAccessible()) + continue; + + auto identifier = getVarNameForCoding(paramDecl); + if (identifier.empty()) { + identifier = C.getIdentifier("_" + std::to_string(entry.index())); + } + + properties[identifier] = paramDecl; + } + } + + return validateCodingKeysEnum(derived, properties, codingKeysTypeDecl); } /// Creates a new var decl representing /// -/// var/let container : containerBase +/// var/let identifier : containerBase /// /// \c containerBase is the name of the type to use as the base (either /// \c KeyedEncodingContainer or \c KeyedDecodingContainer). @@ -285,10 +532,13 @@ static EnumDecl *lookupEvaluatedCodingKeysEnum(ASTContext &C, /// \param keyType The key type to bind to the container type. /// /// \param introducer Whether to declare the variable as immutable. +/// +/// \param identifier Identifier of the variable. static VarDecl *createKeyedContainer(ASTContext &C, DeclContext *DC, NominalTypeDecl *keyedContainerDecl, Type keyType, - VarDecl::Introducer introducer) { + VarDecl::Introducer introducer, + Identifier identifier) { // Bind Keyed*Container to Keyed*Container Type boundType[1] = {keyType}; auto containerType = BoundGenericType::get(keyedContainerDecl, Type(), @@ -296,13 +546,37 @@ static VarDecl *createKeyedContainer(ASTContext &C, DeclContext *DC, // let container : Keyed*Container auto *containerDecl = new (C) VarDecl(/*IsStatic=*/false, introducer, - SourceLoc(), C.Id_container, DC); + SourceLoc(), identifier, DC); containerDecl->setImplicit(); containerDecl->setSynthesized(); containerDecl->setInterfaceType(containerType); return containerDecl; } +/// 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 introducer Whether to declare the variable as immutable. +static VarDecl *createKeyedContainer(ASTContext &C, DeclContext *DC, + NominalTypeDecl *keyedContainerDecl, + Type keyType, + VarDecl::Introducer introducer) { + return createKeyedContainer(C, DC, keyedContainerDecl, keyType, + introducer, C.Id_container); +} + /// Creates a new \c CallExpr representing /// /// base.container(keyedBy: CodingKeys.self) @@ -346,6 +620,113 @@ static CallExpr *createContainerKeyedByCall(ASTContext &C, DeclContext *DC, C.AllocateCopy(argLabels)); } +static CallExpr *createNestedContainerKeyedByForKeyCall( + ASTContext &C, DeclContext *DC, Expr *base, NominalTypeDecl *codingKeysType, + EnumElementDecl *key) { + SmallVector argNames{C.Id_keyedBy, C.Id_forKey}; + + // base.nestedContainer(keyedBy:, forKey:) expr + auto *unboundCall = UnresolvedDotExpr::createImplicit( + C, base, C.Id_nestedContainer, argNames); + + // CodingKeys.self expr + auto *codingKeysExpr = TypeExpr::createImplicitForDecl( + DeclNameLoc(), codingKeysType, codingKeysType->getDeclContext(), + DC->mapTypeIntoContext(codingKeysType->getInterfaceType())); + auto *codingKeysMetaTypeExpr = + new (C) DotSelfExpr(codingKeysExpr, SourceLoc(), SourceLoc()); + + // key expr + auto *metaTyRef = TypeExpr::createImplicit( + DC->mapTypeIntoContext(key->getParentEnum()->getDeclaredInterfaceType()), + C); + auto *keyExpr = new (C) MemberRefExpr(metaTyRef, SourceLoc(), key, + DeclNameLoc(), /*Implicit=*/true); + + // Full bound base.nestedContainer(keyedBy: CodingKeys.self, forKey: key) call + Expr *args[2] = {codingKeysMetaTypeExpr, keyExpr}; + return CallExpr::createImplicit(C, unboundCall, C.AllocateCopy(args), + argNames); +} + +static ThrowStmt *createThrowDecodingErrorTypeMismatchStmt( + ASTContext &C, DeclContext *DC, NominalTypeDecl *targetDecl, + Expr *containerExpr, Expr *debugMessage) { + auto *errorDecl = C.getDecodingErrorDecl(); + auto *contextDecl = lookupErrorContext(C, errorDecl); + assert(contextDecl && "Missing Context decl."); + + auto *contextTypeExpr = + TypeExpr::createImplicit(contextDecl->getDeclaredType(), C); + + // Context.init(codingPath:, debugDescription:) + auto *contextInitCall = UnresolvedDotExpr::createImplicit( + C, contextTypeExpr, DeclBaseName::createConstructor(), + {C.Id_codingPath, C.Id_debugDescription, C.Id_underlyingError}); + + auto *codingPathExpr = + UnresolvedDotExpr::createImplicit(C, containerExpr, C.Id_codingPath); + + auto *contextInitCallExpr = CallExpr::createImplicit( + C, contextInitCall, + {codingPathExpr, debugMessage, + new (C) NilLiteralExpr(SourceLoc(), /* Implicit */ true)}, + {C.Id_codingPath, C.Id_debugDescription, C.Id_underlyingError}); + + auto *decodingErrorTypeExpr = + TypeExpr::createImplicit(errorDecl->getDeclaredType(), C); + auto *decodingErrorCall = UnresolvedDotExpr::createImplicit( + C, decodingErrorTypeExpr, C.Id_typeMismatch, + {Identifier(), Identifier()}); + auto *targetType = TypeExpr::createImplicit( + DC->mapTypeIntoContext(targetDecl->getDeclaredInterfaceType()), C); + auto *targetTypeExpr = + new (C) DotSelfExpr(targetType, SourceLoc(), SourceLoc()); + + auto *decodingErrorCallExpr = CallExpr::createImplicit( + C, decodingErrorCall, {targetTypeExpr, contextInitCallExpr}, + {Identifier(), Identifier()}); + return new (C) ThrowStmt(SourceLoc(), decodingErrorCallExpr); +} + +static ThrowStmt *createThrowEncodingErrorInvalidValueStmt(ASTContext &C, + DeclContext *DC, + Expr *valueExpr, + Expr *containerExpr, + Expr *debugMessage) { + auto *errorDecl = C.getEncodingErrorDecl(); + auto *contextDecl = lookupErrorContext(C, errorDecl); + assert(contextDecl && "Missing Context decl."); + + auto *contextTypeExpr = + TypeExpr::createImplicit(contextDecl->getDeclaredType(), C); + + // Context.init(codingPath:, debugDescription:) + auto *contextInitCall = UnresolvedDotExpr::createImplicit( + C, contextTypeExpr, DeclBaseName::createConstructor(), + {C.Id_codingPath, C.Id_debugDescription, C.Id_underlyingError}); + + auto *codingPathExpr = + UnresolvedDotExpr::createImplicit(C, containerExpr, C.Id_codingPath); + + auto *contextInitCallExpr = CallExpr::createImplicit( + C, contextInitCall, + {codingPathExpr, debugMessage, + new (C) NilLiteralExpr(SourceLoc(), /* Implicit */ true)}, + {C.Id_codingPath, C.Id_debugDescription, C.Id_underlyingError}); + + auto *decodingErrorTypeExpr = + TypeExpr::createImplicit(errorDecl->getDeclaredType(), C); + auto *decodingErrorCall = UnresolvedDotExpr::createImplicit( + C, decodingErrorTypeExpr, C.Id_invalidValue, + {Identifier(), Identifier()}); + + auto *decodingErrorCallExpr = CallExpr::createImplicit( + C, decodingErrorCall, {valueExpr, contextInitCallExpr}, + {Identifier(), Identifier()}); + return new (C) ThrowStmt(SourceLoc(), decodingErrorCallExpr); +} + /// Looks up the property corresponding to the indicated coding key. /// /// \param conformanceDC The DeclContext we're generating code within. @@ -549,6 +930,248 @@ deriveBodyEncodable_encode(AbstractFunctionDecl *encodeDecl, void *) { return { body, /*isTypeChecked=*/false }; } +static std::pair +deriveBodyEncodable_enum_encode(AbstractFunctionDecl *encodeDecl, void *) { + // enum Foo : Codable { + // case bar(x: Int) + // case baz(y: String) + // + // // Already derived by this point if possible. + // @derived enum CodingKeys : CodingKey { + // case bar + // case baz + // + // @derived enum BarCodingKeys : CodingKey { + // case x + // } + // + // @derived enum BazCodingKeys : CodingKey { + // case y + // } + // } + // + // @derived func encode(to encoder: Encoder) throws { + // var container = encoder.container(keyedBy: CodingKeys.self) + // switch self { + // case bar(let x): + // let nestedContainer = try container.nestedContainer(keyedBy: + // BarCodingKeys.self, forKey: .bar) try nestedContainer.encode(x, + // forKey: .x) + // case baz(let y): + // let nestedContainer = try container.nestedContainer(keyedBy: + // BazCodingKeys.self, forKey: .baz) try nestedContainer.encode(y, + // forKey: .y) + // } + // } + // } + + // The enclosing type decl. + auto conformanceDC = encodeDecl->getDeclContext(); + auto *enumDecl = conformanceDC->getSelfEnumDecl(); + + auto *funcDC = cast(encodeDecl); + auto &C = funcDC->getASTContext(); + + // We'll want the CodingKeys enum for this type, potentially looking through + // a typealias. + auto *codingKeysEnum = lookupEvaluatedCodingKeysEnum(C, enumDecl); + // We should have bailed already if: + // a) The type does not have CodingKeys + // b) The type is not an enum + assert(codingKeysEnum && "Missing CodingKeys decl."); + + 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 *containerDecl = + createKeyedContainer(C, funcDC, C.getKeyedEncodingContainerDecl(), + codingKeysEnum->getDeclaredInterfaceType(), + VarDecl::Introducer::Var); + + auto *containerExpr = + new (C) DeclRefExpr(ConcreteDeclRef(containerDecl), DeclNameLoc(), + /*Implicit=*/true, AccessSemantics::DirectToStorage); + + // Need to generate + // `let container = encoder.container(keyedBy: CodingKeys.self)` + // This is unconditional because a type with no properties should encode as an + // empty container. + // + // `let container` (containerExpr) is generated above. + + // encoder + auto encoderParam = encodeDecl->getParameters()->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 = NamedPattern::createImplicit(C, containerDecl); + auto *bindingDecl = PatternBindingDecl::createImplicit( + C, StaticSpellingKind::None, containerPattern, callExpr, funcDC); + statements.push_back(bindingDecl); + statements.push_back(containerDecl); + + auto *selfRef = encodeDecl->getImplicitSelfDecl(); + + SmallVector cases; + for (auto elt : enumDecl->getAllElements()) { + // CodingKeys.x + auto *codingKeyCase = + lookupEnumCase(C, codingKeysEnum, elt->getName().getBaseIdentifier()); + + SmallVector caseStatements; + + // .(let a0, let a1, ...) + SmallVector payloadVars; + auto subpattern = DerivedConformance::enumElementPayloadSubpattern( + elt, 'a', encodeDecl, payloadVars, /* useLabels */ true); + + auto hasBoundDecls = !payloadVars.empty(); + Optional> caseBodyVarDecls; + if (hasBoundDecls) { + // We allocated a direct copy of our var decls for the case + // body. + auto copy = C.Allocate(payloadVars.size()); + for (unsigned i : indices(payloadVars)) { + auto *vOld = payloadVars[i]; + auto *vNew = new (C) VarDecl( + /*IsStatic*/ false, vOld->getIntroducer(), vOld->getNameLoc(), + vOld->getName(), vOld->getDeclContext()); + vNew->setImplicit(); + copy[i] = vNew; + } + caseBodyVarDecls.emplace(copy); + } + + if (!codingKeyCase) { + // This case should not be encodable, so throw an error if an attempt is + // made to encode it + llvm::SmallString<128> buffer; + buffer.append("Case '"); + buffer.append(elt->getBaseIdentifier().str()); + buffer.append( + "' cannot be decoded because it is not defined in CodingKeys."); + auto *debugMessage = new (C) StringLiteralExpr( + C.AllocateCopy(buffer.str()), SourceRange(), /* Implicit */ true); + auto *selfRefExpr = new (C) DeclRefExpr( + ConcreteDeclRef(selfRef), DeclNameLoc(), /* Implicit */ true); + auto *throwStmt = createThrowEncodingErrorInvalidValueStmt( + C, funcDC, selfRefExpr, containerExpr, debugMessage); + caseStatements.push_back(throwStmt); + } else { + auto caseIdentifier = caseCodingKeysIdentifier(C, elt); + auto *caseCodingKeys = + lookupEvaluatedCodingKeysEnum(C, enumDecl, caseIdentifier); + + auto *nestedContainerDecl = createKeyedContainer( + C, funcDC, C.getKeyedEncodingContainerDecl(), + caseCodingKeys->getDeclaredInterfaceType(), VarDecl::Introducer::Var, + C.Id_nestedContainer); + + auto *nestedContainerCall = createNestedContainerKeyedByForKeyCall( + C, funcDC, containerExpr, caseCodingKeys, codingKeyCase); + + auto *containerPattern = + NamedPattern::createImplicit(C, nestedContainerDecl); + auto *bindingDecl = PatternBindingDecl::createImplicit( + C, StaticSpellingKind::None, containerPattern, nestedContainerCall, + funcDC); + caseStatements.push_back(bindingDecl); + caseStatements.push_back(nestedContainerDecl); + + // TODO: use param decls to get names + for (auto entry : llvm::enumerate(payloadVars)) { + auto *payloadVar = entry.value(); + auto *nestedContainerExpr = new (C) + DeclRefExpr(ConcreteDeclRef(nestedContainerDecl), DeclNameLoc(), + /*Implicit=*/true, AccessSemantics::DirectToStorage); + auto payloadVarRef = new (C) DeclRefExpr(payloadVar, DeclNameLoc(), + /*implicit*/ true); + auto *paramDecl = elt->getParameterList()->get(entry.index()); + auto caseCodingKeysIdentifier = getVarNameForCoding(paramDecl); + if (caseCodingKeysIdentifier.empty()) { + caseCodingKeysIdentifier = C.getIdentifier("_" + std::to_string(entry.index())); + } + auto *caseCodingKey = + lookupEnumCase(C, caseCodingKeys, caseCodingKeysIdentifier); + + // If there is no key defined for this parameter, skip it. + if (!caseCodingKey) + continue; + + auto varType = conformanceDC->mapTypeIntoContext( + payloadVar->getValueInterfaceType()); + + bool useIfPresentVariant = false; + if (auto objType = varType->getOptionalObjectType()) { + varType = objType; + useIfPresentVariant = true; + } + + // BarCodingKeys.x + auto *metaTyRef = + TypeExpr::createImplicit(caseCodingKeys->getDeclaredType(), C); + auto *keyExpr = + new (C) MemberRefExpr(metaTyRef, SourceLoc(), caseCodingKey, + DeclNameLoc(), /*Implicit=*/true); + + // encode(_:forKey:)/encodeIfPresent(_:forKey:) + auto methodName = + useIfPresentVariant ? C.Id_encodeIfPresent : C.Id_encode; + SmallVector argNames{Identifier(), C.Id_forKey}; + + auto *encodeCall = UnresolvedDotExpr::createImplicit( + C, nestedContainerExpr, methodName, argNames); + + // nestedContainer.encode(x, forKey: CodingKeys.x) + Expr *args[2] = {payloadVarRef, keyExpr}; + auto *callExpr = CallExpr::createImplicit( + C, encodeCall, C.AllocateCopy(args), C.AllocateCopy(argNames)); + + // try nestedContainer.encode(x, forKey: CodingKeys.x) + auto *tryExpr = new (C) TryExpr(SourceLoc(), callExpr, Type(), + /*Implicit=*/true); + caseStatements.push_back(tryExpr); + } + } + + // generate: case .: + auto pat = new (C) EnumElementPattern( + TypeExpr::createImplicit(enumDecl->getDeclaredType(), C), SourceLoc(), + DeclNameLoc(), DeclNameRef(), elt, subpattern); + pat->setImplicit(); + + auto labelItem = CaseLabelItem(pat); + auto body = BraceStmt::create(C, SourceLoc(), caseStatements, SourceLoc()); + cases.push_back(CaseStmt::create(C, CaseParentKind::Switch, SourceLoc(), + labelItem, SourceLoc(), SourceLoc(), body, + /*case body vardecls*/ caseBodyVarDecls)); + } + + // generate: switch self { } + auto enumRef = + new (C) DeclRefExpr(ConcreteDeclRef(selfRef), DeclNameLoc(), + /*implicit*/ true, AccessSemantics::Ordinary); + + auto switchStmt = SwitchStmt::create(LabeledStmtInfo(), SourceLoc(), enumRef, + SourceLoc(), cases, SourceLoc(), C); + statements.push_back(switchStmt); + + auto *body = BraceStmt::create(C, SourceLoc(), statements, SourceLoc(), + /*implicit=*/true); + return {body, /*isTypeChecked=*/false}; +} + /// Synthesizes a function declaration for `encode(to: Encoder) throws` with a /// lazily synthesized body for the given type. /// @@ -587,7 +1210,12 @@ static FuncDecl *deriveEncodable_encode(DerivedConformance &derived) { /*Throws=*/true, /*GenericParams=*/nullptr, params, returnType, conformanceDC); encodeDecl->setSynthesized(); - encodeDecl->setBodySynthesizer(deriveBodyEncodable_encode); + + if (dyn_cast(derived.Nominal)) { + encodeDecl->setBodySynthesizer(deriveBodyEncodable_enum_encode); + } else { + encodeDecl->setBodySynthesizer(deriveBodyEncodable_encode); + } // This method should be marked as 'override' for classes inheriting Encodable // conformance from a parent class. @@ -876,6 +1504,313 @@ deriveBodyDecodable_init(AbstractFunctionDecl *initDecl, void *) { return { body, /*isTypeChecked=*/false }; } +/// Synthesizes the body for `init(from decoder: Decoder) throws`. +/// +/// \param initDecl The function decl whose body to synthesize. +static std::pair +deriveBodyDecodable_enum_init(AbstractFunctionDecl *initDecl, void *) { + // enum Foo : Codable { + // case bar(x: Int) + // case baz(y: String) + // + // // Already derived by this point if possible. + // @derived enum CodingKeys : CodingKey { + // case bar + // case baz + // + // @derived enum BarCodingKeys : CodingKey { + // case x + // } + // + // @derived enum BazCodingKeys : CodingKey { + // case y + // } + // } + // + // @derived init(from decoder: Decoder) throws { + // let container = try decoder.container(keyedBy: CodingKeys.self) + // if container.allKeys.count != 1 { + // let context = DecodingError.Context( + // codingPath: container.codingPath, + // debugDescription: "Invalid number of keys found, expected one.") + // throw DecodingError.typeMismatch(Foo.self, context) + // } + // switch container.allKeys.first { + // case .bar: + // let nestedContainer = try container.nestedContainer(keyedBy: + // BarCodingKeys.self, forKey: .bar) let x = try + // nestedContainer.decode(Int.self, forKey: .x) self = .bar(x: x) + // case .baz: + // let nestedContainer = try container.nestedContainer(keyedBy: + // BarCodingKeys.self, forKey: .baz) let y = try + // nestedContainer.decode(String.self, forKey: .y) self = .baz(y: y) + // } + // } + + // The enclosing type decl. + auto conformanceDC = initDecl->getDeclContext(); + auto *targetEnum = conformanceDC->getSelfEnumDecl(); + + auto *funcDC = cast(initDecl); + auto &C = funcDC->getASTContext(); + + // We'll want the CodingKeys enum for this type, potentially looking through + // a typealias. + auto *codingKeysEnum = lookupEvaluatedCodingKeysEnum(C, targetEnum); + // We should have bailed already if: + // a) The type does not have CodingKeys + // b) The type is not an enum + assert(codingKeysEnum && "Missing CodingKeys decl."); + + // 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->getDeclaredInterfaceType(); + auto *containerDecl = + createKeyedContainer(C, funcDC, C.getKeyedDecodingContainerDecl(), + codingKeysEnum->getDeclaredInterfaceType(), + VarDecl::Introducer::Let); + + auto *containerExpr = + new (C) DeclRefExpr(ConcreteDeclRef(containerDecl), DeclNameLoc(), + /*Implicit=*/true, AccessSemantics::DirectToStorage); + + SmallVector statements; + if (codingKeysEnum->hasCases()) { + // Need to generate + // `let container = try decoder.container(keyedBy: CodingKeys.self)` + // `let container` (containerExpr) is generated above. + + // decoder + auto decoderParam = initDecl->getParameters()->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 = NamedPattern::createImplicit(C, containerDecl); + auto *bindingDecl = PatternBindingDecl::createImplicit( + C, StaticSpellingKind::None, containerPattern, tryExpr, funcDC); + statements.push_back(bindingDecl); + statements.push_back(containerDecl); + + SmallVector cases; + + for (auto *elt : targetEnum->getAllElements()) { + auto *codingKeyCase = + lookupEnumCase(C, codingKeysEnum, elt->getName().getBaseIdentifier()); + + // Skip this case if it's not defined in the CodingKeys + if (!codingKeyCase) + continue; + + // generate: case .: + auto pat = new (C) EnumElementPattern( + TypeExpr::createImplicit(funcDC->mapTypeIntoContext(codingKeysType), + C), + SourceLoc(), DeclNameLoc(), DeclNameRef(), codingKeyCase, nullptr); + pat->setImplicit(); + pat->setType(codingKeysType); + + auto labelItem = + CaseLabelItem(new (C) OptionalSomePattern(pat, SourceLoc())); + + llvm::SmallVector caseStatements; + + auto caseIdentifier = caseCodingKeysIdentifier(C, elt); + auto *caseCodingKeys = + lookupEvaluatedCodingKeysEnum(C, targetEnum, caseIdentifier); + + auto *nestedContainerDecl = createKeyedContainer( + C, funcDC, C.getKeyedDecodingContainerDecl(), + caseCodingKeys->getDeclaredInterfaceType(), VarDecl::Introducer::Var, + C.Id_nestedContainer); + + auto *nestedContainerCall = createNestedContainerKeyedByForKeyCall( + C, funcDC, containerExpr, caseCodingKeys, codingKeyCase); + + auto *tryNestedContainerCall = new (C) TryExpr( + SourceLoc(), nestedContainerCall, Type(), /* Implicit */ true); + + auto *containerPattern = + NamedPattern::createImplicit(C, nestedContainerDecl); + auto *bindingDecl = PatternBindingDecl::createImplicit( + C, StaticSpellingKind::None, containerPattern, tryNestedContainerCall, + funcDC); + caseStatements.push_back(bindingDecl); + caseStatements.push_back(nestedContainerDecl); + + llvm::SmallVector decodeCalls; + llvm::SmallVector params; + if (elt->hasAssociatedValues()) { + for (auto entry : llvm::enumerate(*elt->getParameterList())) { + auto *paramDecl = entry.value(); + Identifier identifier = getVarNameForCoding(paramDecl); + if (identifier.empty()) { + identifier = C.getIdentifier("_" + std::to_string(entry.index())); + } + auto *caseCodingKey = lookupEnumCase(C, caseCodingKeys, identifier); + + params.push_back(getVarNameForCoding(paramDecl)); + + // If no key is defined for this parameter, use the default value + if (!caseCodingKey) { + // This should have been verified to have a default expr in the + // CodingKey synthesis + assert(paramDecl->hasDefaultExpr()); + decodeCalls.push_back(paramDecl->getTypeCheckedDefaultExpr()); + continue; + } + + // Type.self + auto *parameterTypeExpr = TypeExpr::createImplicit( + funcDC->mapTypeIntoContext(paramDecl->getInterfaceType()), C); + auto *parameterMetaTypeExpr = + new (C) DotSelfExpr(parameterTypeExpr, SourceLoc(), SourceLoc()); + // BarCodingKeys.x + auto *metaTyRef = + TypeExpr::createImplicit(caseCodingKeys->getDeclaredType(), C); + auto *keyExpr = + new (C) MemberRefExpr(metaTyRef, SourceLoc(), caseCodingKey, + DeclNameLoc(), /*Implicit=*/true); + + auto *nestedContainerExpr = new (C) + DeclRefExpr(ConcreteDeclRef(nestedContainerDecl), DeclNameLoc(), + /*Implicit=*/true, AccessSemantics::DirectToStorage); + // decode(_:, forKey:) + auto *decodeCall = UnresolvedDotExpr::createImplicit( + C, nestedContainerExpr, C.Id_decode, {Identifier(), C.Id_forKey}); + + // nestedContainer.decode(Type.self, forKey: BarCodingKeys.x) + auto *callExpr = CallExpr::createImplicit( + C, decodeCall, {parameterMetaTypeExpr, keyExpr}, + {Identifier(), C.Id_forKey}); + + // try nestedContainer.decode(Type.self, forKey: BarCodingKeys.x) + auto *tryExpr = new (C) TryExpr(SourceLoc(), callExpr, Type(), + /*Implicit=*/true); + + decodeCalls.push_back(tryExpr); + } + } + + auto *selfRef = DerivedConformance::createSelfDeclRef(initDecl); + + // Foo.bar + auto *selfTypeExpr = + TypeExpr::createImplicit(targetEnum->getDeclaredType(), C); + + if (params.empty()) { + auto *selfCaseExpr = new (C) MemberRefExpr( + selfTypeExpr, SourceLoc(), elt, DeclNameLoc(), /*Implicit=*/true); + + auto *selfRef = DerivedConformance::createSelfDeclRef(initDecl); + + auto *assignExpr = + new (C) AssignExpr(selfRef, SourceLoc(), selfCaseExpr, + /*Implicit=*/true); + + caseStatements.push_back(assignExpr); + } else { + // Foo.bar(x:) + auto *selfCaseExpr = UnresolvedDotExpr::createImplicit( + C, selfTypeExpr, elt->getBaseIdentifier(), C.AllocateCopy(params)); + + // Foo.bar(x: try nestedContainer.decode(Int.self, forKey: .x)) + auto *caseCallExpr = CallExpr::createImplicit( + C, selfCaseExpr, C.AllocateCopy(decodeCalls), + C.AllocateCopy(params)); + + // self = Foo.bar(x: try nestedContainer.decode(Int.self)) + auto *assignExpr = + new (C) AssignExpr(selfRef, SourceLoc(), caseCallExpr, + /*Implicit=*/true); + + caseStatements.push_back(assignExpr); + } + + auto body = + BraceStmt::create(C, SourceLoc(), caseStatements, SourceLoc()); + + cases.push_back(CaseStmt::create(C, CaseParentKind::Switch, SourceLoc(), + labelItem, SourceLoc(), SourceLoc(), + body, + /*case body vardecls*/ None)); + } + + // generate: + // + // if container.allKeys.count != 1 { + // let context = DecodingError.Context( + // codingPath: container.codingPath, + // debugDescription: "Invalid number of keys found, expected + // one.") + // throw DecodingError.typeMismatch(Foo.self, context) + // } + auto *debugMessage = new (C) StringLiteralExpr( + StringRef("Invalid number of keys found, expected one."), SourceRange(), + /* Implicit */ true); + auto *throwStmt = createThrowDecodingErrorTypeMismatchStmt( + C, funcDC, targetEnum, containerExpr, debugMessage); + + // container.allKeys + auto *allKeysExpr = + UnresolvedDotExpr::createImplicit(C, containerExpr, C.Id_allKeys); + + // container.allKeys.count + auto *keysCountExpr = + UnresolvedDotExpr::createImplicit(C, allKeysExpr, C.Id_count); + + // container.allKeys.count == 1 + auto *cmpFunc = C.getEqualIntDecl(); + auto *fnType = cmpFunc->getInterfaceType()->castTo(); + auto *cmpFuncExpr = new (C) + DeclRefExpr(cmpFunc, DeclNameLoc(), + /*implicit*/ true, AccessSemantics::Ordinary, fnType); + auto *oneExpr = IntegerLiteralExpr::createFromUnsigned(C, 1); + + auto *tupleExpr = TupleExpr::createImplicit(C, {keysCountExpr, oneExpr}, + {Identifier(), Identifier()}); + + auto *cmpExpr = + new (C) BinaryExpr(cmpFuncExpr, tupleExpr, /*implicit*/ true); + cmpExpr->setThrows(false); + + auto *guardBody = BraceStmt::create(C, SourceLoc(), {throwStmt}, + SourceLoc(), /* Implicit */ true); + + auto *guardStmt = new (C) + GuardStmt(SourceLoc(), cmpExpr, guardBody, /* Implicit */ true, C); + + statements.push_back(guardStmt); + + // generate: switch container.allKeys.first { } + auto *firstExpr = + UnresolvedDotExpr::createImplicit(C, allKeysExpr, C.Id_first); + + auto switchStmt = + SwitchStmt::create(LabeledStmtInfo(), SourceLoc(), firstExpr, + SourceLoc(), cases, SourceLoc(), C); + + statements.push_back(switchStmt); + } + + auto *body = BraceStmt::create(C, SourceLoc(), statements, SourceLoc(), + /*implicit=*/true); + return {body, /*isTypeChecked=*/false}; +} + /// Synthesizes a function declaration for `init(from: Decoder) throws` with a /// lazily synthesized body for the given type. /// @@ -916,7 +1851,12 @@ static ValueDecl *deriveDecodable_init(DerivedConformance &derived) { /*GenericParams=*/nullptr, conformanceDC); initDecl->setImplicit(); initDecl->setSynthesized(); - initDecl->setBodySynthesizer(&deriveBodyDecodable_init); + + if (dyn_cast(derived.Nominal)) { + initDecl->setBodySynthesizer(&deriveBodyDecodable_enum_init); + } else { + initDecl->setBodySynthesizer(&deriveBodyDecodable_init); + } // This constructor should be marked as `required` for non-final classes. if (classDecl && !classDecl->isFinal()) { @@ -1012,7 +1952,58 @@ static bool canSynthesize(DerivedConformance &derived, ValueDecl *requirement) { return false; } - return true; + bool allValid = true; + if (auto *enumDecl = dyn_cast(derived.Nominal)) { + llvm::SmallSetVector caseNames; + for (auto *elementDecl : enumDecl->getAllElements()) { + bool duplicate = false; + if (!caseNames.insert(elementDecl->getBaseIdentifier())) { + elementDecl->diagnose(diag::codable_enum_duplicate_case_name_here, + derived.getProtocolType(), + derived.Nominal->getDeclaredType(), + elementDecl->getBaseIdentifier()); + allValid = false; + duplicate = true; + } + + if (elementDecl->hasAssociatedValues()) { + llvm::SmallMapVector params; + for (auto entry : llvm::enumerate(*elementDecl->getParameterList())) { + auto *paramDecl = entry.value(); + Identifier paramIdentifier = getVarNameForCoding(paramDecl); + bool generatedName = false; + if (paramIdentifier.empty()) { + paramIdentifier = derived.Context.getIdentifier("_" + std::to_string(entry.index())); + generatedName = true; + } + auto inserted = params.insert(std::make_pair(paramIdentifier, paramDecl)); + if (!inserted.second) { + // duplicate identifier found + auto userDefinedParam = paramDecl; + if (generatedName) { + // at most we have one user defined and one generated identifier + // with this name, so if this is the generated, the other one + // must be the user defined + userDefinedParam = inserted.first->second; + } + + userDefinedParam->diagnose(diag::codable_enum_duplicate_parameter_name_here, + derived.getProtocolType(), + derived.Nominal->getDeclaredType(), + paramIdentifier, + elementDecl->getBaseIdentifier()); + allValid = false; + } + } + } + + if (!duplicate && !validateCaseCodingKeysEnum(derived, elementDecl)) { + allValid = false; + } + } + } + + return allValid; } static bool canDeriveCodable(NominalTypeDecl *NTD, @@ -1020,11 +2011,12 @@ static bool canDeriveCodable(NominalTypeDecl *NTD, assert(Kind == KnownProtocolKind::Encodable || Kind == KnownProtocolKind::Decodable); - // Structs and classes can explicitly derive Encodable and Decodable + // Structs, classes and enums can explicitly derive Encodable and Decodable // conformance (explicitly meaning we can synthesize an implementation if // a type conforms manually). - // FIXME: Enums too! - if (!isa(NTD) && !isa(NTD)) { + if (!isa(NTD) && !isa(NTD) && + !(NTD->getASTContext().LangOpts.EnableExperimentalEnumCodableDerivation + && isa(NTD))) { return false; } @@ -1046,7 +2038,9 @@ bool DerivedConformance::canDeriveEncodable(NominalTypeDecl *NTD) { ValueDecl *DerivedConformance::deriveEncodable(ValueDecl *requirement) { // We can only synthesize Encodable for structs and classes. - if (!isa(Nominal) && !isa(Nominal)) + if (!isa(Nominal) && !isa(Nominal) && + !(Context.LangOpts.EnableExperimentalEnumCodableDerivation + && isa(Nominal))) return nullptr; if (requirement->getBaseName() != Context.Id_encode) { @@ -1075,7 +2069,9 @@ ValueDecl *DerivedConformance::deriveEncodable(ValueDecl *requirement) { ValueDecl *DerivedConformance::deriveDecodable(ValueDecl *requirement) { // We can only synthesize Encodable for structs and classes. - if (!isa(Nominal) && !isa(Nominal)) + if (!isa(Nominal) && !isa(Nominal) && + !(Context.LangOpts.EnableExperimentalEnumCodableDerivation + && isa(Nominal))) return nullptr; if (requirement->getBaseName() != DeclBaseName::createConstructor()) { @@ -1100,4 +2096,4 @@ ValueDecl *DerivedConformance::deriveDecodable(ValueDecl *requirement) { } return deriveDecodable_init(*this); -} +} \ No newline at end of file diff --git a/lib/Sema/DerivedConformances.cpp b/lib/Sema/DerivedConformances.cpp index 5c9de4cd246bd..8c6062634ea41 100644 --- a/lib/Sema/DerivedConformances.cpp +++ b/lib/Sema/DerivedConformances.cpp @@ -726,10 +726,11 @@ bool DerivedConformance::allAssociatedValuesConformToProtocol(DeclContext *DC, /// \p varPrefix The prefix character for variable names (e.g., a0, a1, ...). /// \p varContext The context into which payload variables should be declared. /// \p boundVars The array to which the pattern's variables will be appended. -Pattern* -DerivedConformance::enumElementPayloadSubpattern(EnumElementDecl *enumElementDecl, - char varPrefix, DeclContext *varContext, - SmallVectorImpl &boundVars) { +/// \p useLabels If the argument has a label, use it instead of the generated +/// name. +Pattern *DerivedConformance::enumElementPayloadSubpattern( + EnumElementDecl *enumElementDecl, char varPrefix, DeclContext *varContext, + SmallVectorImpl &boundVars, bool useLabels) { auto parentDC = enumElementDecl->getDeclContext(); ASTContext &C = parentDC->getASTContext(); @@ -747,8 +748,16 @@ DerivedConformance::enumElementPayloadSubpattern(EnumElementDecl *enumElementDec SmallVector elementPatterns; int index = 0; for (auto tupleElement : tupleType->getElements()) { - auto payloadVar = indexedVarDecl(varPrefix, index++, - tupleElement.getType(), varContext); + VarDecl *payloadVar; + if (useLabels && tupleElement.hasName()) { + payloadVar = + new (C) VarDecl(/*IsStatic*/ false, VarDecl::Introducer::Let, + SourceLoc(), tupleElement.getName(), varContext); + payloadVar->setInterfaceType(tupleElement.getType()); + } else { + payloadVar = indexedVarDecl(varPrefix, index++, tupleElement.getType(), + varContext); + } boundVars.push_back(payloadVar); auto namedPattern = new (C) NamedPattern(payloadVar); @@ -778,7 +787,6 @@ DerivedConformance::enumElementPayloadSubpattern(EnumElementDecl *enumElementDec return ParenPattern::createImplicit(C, letPattern); } - /// Creates a named variable based on a prefix character and a numeric index. /// \p prefixChar The prefix character for the variable's name. /// \p index The numeric index to append to the variable's name. diff --git a/lib/Sema/DerivedConformances.h b/lib/Sema/DerivedConformances.h index 98bc56340acfc..bdc391b9aa90a 100644 --- a/lib/Sema/DerivedConformances.h +++ b/lib/Sema/DerivedConformances.h @@ -378,10 +378,9 @@ class DerivedConformance { EnumDecl *enumDecl, VarDecl *enumVarDecl, AbstractFunctionDecl *funcDecl, const char *indexName); - static Pattern * - enumElementPayloadSubpattern(EnumElementDecl *enumElementDecl, char varPrefix, - DeclContext *varContext, - SmallVectorImpl &boundVars); + static Pattern *enumElementPayloadSubpattern( + EnumElementDecl *enumElementDecl, char varPrefix, DeclContext *varContext, + SmallVectorImpl &boundVars, bool useLabels = false); static VarDecl *indexedVarDecl(char prefixChar, int index, Type type, DeclContext *varContext); diff --git a/test/IRGen/synthesized_conformance.swift b/test/IRGen/synthesized_conformance.swift index 750303b705cae..04cff69541310 100644 --- a/test/IRGen/synthesized_conformance.swift +++ b/test/IRGen/synthesized_conformance.swift @@ -1,4 +1,4 @@ -// RUN: %target-swift-frontend -disable-generic-metadata-prespecialization -emit-ir %s -swift-version 4 | %FileCheck %s +// RUN: %target-swift-frontend -disable-generic-metadata-prespecialization -emit-ir %s -swift-version 4 -enable-experimental-enum-codable-derivation | %FileCheck %s struct Struct { var x: T @@ -14,6 +14,7 @@ enum Enum { extension Enum: Equatable where T: Equatable {} extension Enum: Hashable where T: Hashable {} +extension Enum: Codable where T: Codable {} final class Final { var x: T @@ -46,6 +47,9 @@ public func encodable() { // CHECK: [[Struct_Encodable:%.*]] = call i8** @"$s23synthesized_conformance6StructVySiGACyxGSEAASeRzSERzlWl"() // CHECK-NEXT: call swiftcc void @"$s23synthesized_conformance11doEncodableyyxSERzlF"(%swift.opaque* noalias nocapture {{%.*}}, %swift.type* {{%.*}}, i8** [[Struct_Encodable]]) doEncodable(Struct(x: 1)) + // CHECK: [[Enum_Encodable:%.*]] = call i8** @"$s23synthesized_conformance4EnumOySiGACyxGSEAASeRzSERzlWl"() + // CHECK-NEXT: call swiftcc void @"$s23synthesized_conformance11doEncodableyyxSERzlF"(%swift.opaque* noalias nocapture {{%.*}}, %swift.type* {{%.*}}, i8** [[Enum_Encodable]]) + doEncodable(Enum.a(1)) // CHECK: [[Final_Encodable:%.*]] = call i8** @"$s23synthesized_conformance5FinalCySiGACyxGSEAASERzlWl"() // CHECK-NEXT: call swiftcc void @"$s23synthesized_conformance11doEncodableyyxSERzlF"(%swift.opaque* noalias nocapture {{%.*}}, %swift.type* {{%.*}}, i8** [[Final_Encodable]]) doEncodable(Final(x: 1)) diff --git a/test/IRGen/synthesized_conformance_future.swift b/test/IRGen/synthesized_conformance_future.swift index 2b34a0b18c9dc..aabcbda962773 100644 --- a/test/IRGen/synthesized_conformance_future.swift +++ b/test/IRGen/synthesized_conformance_future.swift @@ -1,4 +1,4 @@ -// RUN: %target-swift-frontend -prespecialize-generic-metadata -target %module-target-future -emit-ir %s -swift-version 4 | %FileCheck %s -DINT=i%target-ptrsize -DALIGNMENT=%target-alignment +// RUN: %target-swift-frontend -prespecialize-generic-metadata -target %module-target-future -emit-ir %s -swift-version 4 -enable-experimental-enum-codable-derivation | %FileCheck %s -DINT=i%target-ptrsize -DALIGNMENT=%target-alignment // REQUIRES: VENDOR=apple || OS=linux-gnu // UNSUPPORTED: CPU=i386 && OS=ios @@ -19,6 +19,7 @@ enum Enum { extension Enum: Equatable where T: Equatable {} extension Enum: Hashable where T: Hashable {} +extension Enum: Codable where T: Codable {} final class Final { var x: T @@ -111,6 +112,28 @@ public func encodable() { // CHECK-SAME: i8** [[Struct_Encodable]] // CHECK-SAME: ) doEncodable(Struct(x: 1)) + // CHECK: [[Enum_Encodable:%.*]] = call i8** @"$s30synthesized_conformance_future4EnumOySiGACyxGSEAASeRzSERzlWl"() + // CHECK-NEXT: call swiftcc void @"$s30synthesized_conformance_future11doEncodableyyxSERzlF"( + // CHECK-SAME: %swift.opaque* noalias nocapture {{[^,]*}}, + // CHECK-SAME: %swift.type* getelementptr inbounds ( + // CHECK-SAME: %swift.full_type, + // CHECK-SAME: %swift.full_type* bitcast ( + // CHECK-SAME: <{ + // CHECK-SAME: i8**, + // CHECK-SAME: [[INT]], + // CHECK-SAME: %swift.type_descriptor*, + // CHECK-SAME: %swift.type*, + // CHECK-SAME: [[INT]], + // CHECK-SAME: i64 + // CHECK-SAME: }>* @"$s30synthesized_conformance_future4EnumOySiGMf" + // CHECK-SAME: to %swift.full_type* + // CHECK-SAME: ), + // CHECK-SAME: i32 0, + // CHECK-SAME: i32 1 + // CHECK-SAME: ), + // CHECK-SAME: i8** [[Enum_Encodable]] + // CHECK-SAME: ) + doEncodable(Enum.a(1)) // CHECK: [[Final_Encodable:%.*]] = call i8** @"$s30synthesized_conformance_future5FinalCySiGACyxGSEAASERzlWl"() // CHECK-NEXT: call swiftcc void @"$s30synthesized_conformance_future11doEncodableyyxSERzlF"(%swift.opaque* noalias nocapture {{%.*}}, %swift.type* {{%.*}}, i8** [[Final_Encodable]]) doEncodable(Final(x: 1)) diff --git a/test/SILGen/synthesized_conformance_enum.swift b/test/SILGen/synthesized_conformance_enum.swift index 5142943947a26..2750a6dc9e95e 100644 --- a/test/SILGen/synthesized_conformance_enum.swift +++ b/test/SILGen/synthesized_conformance_enum.swift @@ -1,5 +1,5 @@ -// RUN: %target-swift-frontend -emit-silgen %s -swift-version 4 | %FileCheck -check-prefix CHECK -check-prefix CHECK-FRAGILE %s -// RUN: %target-swift-frontend -emit-silgen %s -swift-version 4 -enable-library-evolution | %FileCheck -check-prefix CHECK -check-prefix CHECK-RESILIENT %s +// RUN: %target-swift-frontend -emit-silgen %s -swift-version 4 -enable-experimental-enum-codable-derivation | %FileCheck -check-prefix CHECK -check-prefix CHECK-FRAGILE %s +// RUN: %target-swift-frontend -emit-silgen %s -swift-version 4 -enable-library-evolution -enable-experimental-enum-codable-derivation | %FileCheck -check-prefix CHECK -check-prefix CHECK-RESILIENT %s enum Enum { case a(T), b(T) @@ -47,10 +47,23 @@ extension Enum: Hashable where T: Hashable {} // CHECK-LABEL: // Enum.hash(into:) // CHECK-NEXT: sil hidden [ossa] @$s28synthesized_conformance_enum4EnumOAASHRzlE4hash4intoys6HasherVz_tF : $@convention(method) (@inout Hasher, @in_guaranteed Enum) -> () { +extension Enum: Codable where T: Codable {} +// CHECK-LABEL: // Enum.init(from:) +// CHECK-NEXT: sil hidden [ossa] @$s28synthesized_conformance_enum4EnumOAASeRzSERzlE4fromACyxGs7Decoder_p_tKcfC : $@convention(method) (@in Decoder, @thin Enum.Type) -> (@out Enum, @error Error) + +// CHECK-LABEL: // Enum.encode(to:) +// CHECK-NEXT: sil hidden [ossa] @$s28synthesized_conformance_enum4EnumOAASeRzSERzlE6encode2toys7Encoder_p_tKF : $@convention(method) (@in_guaranteed Encoder, @in_guaranteed Enum) -> @error Error { + extension NoValues: CaseIterable {} // CHECK-LABEL: // static NoValues.allCases.getter // CHECK-NEXT: sil hidden [ossa] @$s28synthesized_conformance_enum8NoValuesO8allCasesSayACGvgZ : $@convention(method) (@thin NoValues.Type) -> @owned Array { +extension NoValues: Codable {} +// CHECK-LABEL: // NoValues.init(from:) +// CHECK-NEXT: sil hidden [ossa] @$s28synthesized_conformance_enum8NoValuesO4fromACs7Decoder_p_tKcfC : $@convention(method) (@in Decoder, @thin NoValues.Type) -> (NoValues, @error Error) + +// CHECK-LABEL: // NoValues.encode(to:) +// CHECK-NEXT: sil hidden [ossa] @$s28synthesized_conformance_enum8NoValuesO6encode2toys7Encoder_p_tKF : $@convention(method) (@in_guaranteed Encoder, NoValues) -> @error Error { // Witness tables for Enum @@ -67,6 +80,18 @@ extension NoValues: CaseIterable {} // CHECK-DAG: conditional_conformance (T: Hashable): dependent // CHECK: } +// CHECK-LABEL: sil_witness_table hidden Enum: Decodable module synthesized_conformance_enum { +// CHECK-NEXT: method #Decodable.init!allocator: (Self.Type) -> (Decoder) throws -> Self : @$s28synthesized_conformance_enum4EnumOyxGSeAASeRzSERzlSe4fromxs7Decoder_p_tKcfCTW // protocol witness for Decodable.init(from:) in conformance Enum +// CHECK-NEXT: conditional_conformance (T: Decodable): dependent +// CHECK-NEXT: conditional_conformance (T: Encodable): dependent +// CHECK-NEXT: } + +// CHECK-LABEL: sil_witness_table hidden Enum: Encodable module synthesized_conformance_enum { +// CHECK-NEXT: method #Encodable.encode: (Self) -> (Encoder) throws -> () : @$s28synthesized_conformance_enum4EnumOyxGSEAASeRzSERzlSE6encode2toys7Encoder_p_tKFTW // protocol witness for Encodable.encode(to:) in conformance Enum +// CHECK-NEXT: conditional_conformance (T: Decodable): dependent +// CHECK-NEXT: conditional_conformance (T: Encodable): dependent +// CHECK-NEXT: } + // Witness tables for NoValues // CHECK-LABEL: sil_witness_table hidden NoValues: CaseIterable module synthesized_conformance_enum { @@ -75,4 +100,10 @@ extension NoValues: CaseIterable {} // CHECK-NEXT: method #CaseIterable.allCases!getter: (Self.Type) -> () -> Self.AllCases : @$s28synthesized_conformance_enum8NoValuesOs12CaseIterableAAsADP8allCases03AllI0QzvgZTW // protocol witness for static CaseIterable.allCases.getter in conformance NoValues // CHECK-NEXT: } +// CHECK-LABEL: sil_witness_table hidden NoValues: Decodable module synthesized_conformance_enum { +// CHECK-NEXT: method #Decodable.init!allocator: (Self.Type) -> (Decoder) throws -> Self : @$s28synthesized_conformance_enum8NoValuesOSeAASe4fromxs7Decoder_p_tKcfCTW // protocol witness for Decodable.init(from:) in conformance NoValues +// CHECK-NEXT: } +// CHECK-LABEL: sil_witness_table hidden NoValues: Encodable module synthesized_conformance_enum { +// CHECK-NEXT: method #Encodable.encode: (Self) -> (Encoder) throws -> () : @$s28synthesized_conformance_enum8NoValuesOSEAASE6encode2toys7Encoder_p_tKFTW // protocol witness for Encodable.encode(to:) in conformance NoValues +// CHECK-NEXT: } \ No newline at end of file diff --git a/test/decl/protocol/special/coding/Inputs/enum_codable_simple_multi1.swift b/test/decl/protocol/special/coding/Inputs/enum_codable_simple_multi1.swift new file mode 100644 index 0000000000000..6611b11e35828 --- /dev/null +++ b/test/decl/protocol/special/coding/Inputs/enum_codable_simple_multi1.swift @@ -0,0 +1,27 @@ +// RUN: %target-typecheck-verify-swift + +// Simple enums with all Codable properties should get derived conformance to +// Codable. +enum SimpleEnum : Codable { + case a(x: Int, y: Double) + case b(z: String) + + // These lines have to be within the SimpleEnum type because CodingKeys + // should be private. + func foo() { + // They should receive a synthesized CodingKeys enum. + let _ = SimpleEnum.CodingKeys.self + let _ = SimpleEnum.ACodingKeys.self + let _ = SimpleEnum.BCodingKeys.self + + // The enum should have a case for each of the cases. + let _ = SimpleEnum.CodingKeys.a + let _ = SimpleEnum.CodingKeys.b + + // The enum should have a case for each of the vars. + let _ = SimpleEnum.ACodingKeys.x + let _ = SimpleEnum.ACodingKeys.y + + let _ = SimpleEnum.BCodingKeys.z + } +} diff --git a/test/decl/protocol/special/coding/Inputs/enum_codable_simple_multi2.swift b/test/decl/protocol/special/coding/Inputs/enum_codable_simple_multi2.swift new file mode 100644 index 0000000000000..d36984c2753ea --- /dev/null +++ b/test/decl/protocol/special/coding/Inputs/enum_codable_simple_multi2.swift @@ -0,0 +1,12 @@ +// 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 _ = SimpleEnum.init(from:) + let _ = SimpleEnum.encode(to:) + + // The synthesized CodingKeys type should not be accessible from outside the + // struct. + let _ = SimpleEnum.CodingKeys.self // expected-error {{'CodingKeys' is inaccessible due to 'private' protection level}} + let _ = SimpleEnum.ACodingKeys.self // expected-error {{'ACodingKeys' is inaccessible due to 'private' protection level}} + let _ = SimpleEnum.BCodingKeys.self // expected-error {{'BCodingKeys' is inaccessible due to 'private' protection level}} +} diff --git a/test/decl/protocol/special/coding/enum_codable_case_identifier_overloads.swift b/test/decl/protocol/special/coding/enum_codable_case_identifier_overloads.swift new file mode 100644 index 0000000000000..ae8bde93883d2 --- /dev/null +++ b/test/decl/protocol/special/coding/enum_codable_case_identifier_overloads.swift @@ -0,0 +1,22 @@ +// RUN: %target-typecheck-verify-swift -verify-ignore-unknown -enable-experimental-enum-codable-derivation + +// Simple enums with all Codable parameters whose CodingKeys come from a +// typealias should get derived conformance to Codable. +enum SimpleEnum : Codable { + // expected-error@-1 {{type 'SimpleEnum' does not conform to protocol 'Decodable'}} + // expected-error@-2 {{type 'SimpleEnum' does not conform to protocol 'Encodable'}} + case x(x: Int) + case x(x: Int, y: String) + // expected-note@-1 {{cannot automatically synthesize 'Decodable' because 'SimpleEnum' has duplicate case name 'x'}} + // expected-note@-2 {{cannot automatically synthesize 'Encodable' because 'SimpleEnum' has duplicate case name 'x'}} + + case y(x: Int) + + case z(x: Int) + case z(x: Int, y: String) + // expected-note@-1 {{cannot automatically synthesize 'Decodable' because 'SimpleEnum' has duplicate case name 'z'}} + // expected-note@-2 {{cannot automatically synthesize 'Encodable' because 'SimpleEnum' has duplicate case name 'z'}} + case z(x: Int, y: String, z: Bool) + // expected-note@-1 {{cannot automatically synthesize 'Decodable' because 'SimpleEnum' has duplicate case name 'z'}} + // expected-note@-2 {{cannot automatically synthesize 'Encodable' because 'SimpleEnum' has duplicate case name 'z'}} +} diff --git a/test/decl/protocol/special/coding/enum_codable_codingkeys_typealias.swift b/test/decl/protocol/special/coding/enum_codable_codingkeys_typealias.swift new file mode 100644 index 0000000000000..4630da5a5a304 --- /dev/null +++ b/test/decl/protocol/special/coding/enum_codable_codingkeys_typealias.swift @@ -0,0 +1,35 @@ +// RUN: %target-typecheck-verify-swift -verify-ignore-unknown -enable-experimental-enum-codable-derivation + +// Simple enums with all Codable parameters whose CodingKeys come from a +// typealias should get derived conformance to Codable. +enum SimpleEnum : Codable { + case x(Int) + case y(String) + + private typealias CodingKeys = A // expected-note {{'CodingKeys' declared here}} + private typealias A = B + private typealias B = C + private typealias C = MyRealCodingKeys + + private enum MyRealCodingKeys : String, CodingKey { + case x + case y + } +} + +// They should receive synthesized init(from:) and an encode(to:). +let _ = SimpleEnum.init(from:) +let _ = SimpleEnum.encode(to:) + +// The synthesized CodingKeys type should not be accessible from outside the +// enum. +let _ = SimpleEnum.CodingKeys.self // expected-error {{'CodingKeys' is inaccessible due to 'private' protection level}} + +// Enums with CodingKeys which are typealiases that don't point to a valid +// nominal type should produce errors. +struct EnumWithUndeclaredCodingKeys : Codable { // expected-error {{type 'EnumWithUndeclaredCodingKeys' does not conform to protocol 'Decodable'}} + // expected-error@-1 {{type 'EnumWithUndeclaredCodingKeys' does not conform to protocol 'Encodable'}} + private typealias CodingKeys = NonExistentType // expected-error {{cannot find type 'NonExistentType' in scope}} + // expected-note@-1 {{cannot automatically synthesize 'Decodable' because 'CodingKeys' does not conform to CodingKey}} + // expected-note@-2 {{cannot automatically synthesize 'Encodable' because 'CodingKeys' does not conform to CodingKey}} +} diff --git a/test/decl/protocol/special/coding/enum_codable_conflicting_parameter_identifier.swift b/test/decl/protocol/special/coding/enum_codable_conflicting_parameter_identifier.swift new file mode 100644 index 0000000000000..f6229f1964fbf --- /dev/null +++ b/test/decl/protocol/special/coding/enum_codable_conflicting_parameter_identifier.swift @@ -0,0 +1,10 @@ +// RUN: %target-typecheck-verify-swift -verify-ignore-unknown -enable-experimental-enum-codable-derivation + +enum Duplicate : Codable { // expected-error {{type 'Duplicate' does not conform to protocol 'Decodable'}} + // expected-error@-1 {{type 'Duplicate' does not conform to protocol 'Encodable'}} + case a(Int, Int, _0: Int, _1: Int) + // expected-note@-1 {{cannot automatically synthesize 'Decodable' for 'Duplicate' because user defined parameter name '_0' in 'a' conflicts with automatically generated parameter name}} + // expected-note@-2 {{cannot automatically synthesize 'Decodable' for 'Duplicate' because user defined parameter name '_1' in 'a' conflicts with automatically generated parameter name}} + // expected-note@-3 {{cannot automatically synthesize 'Encodable' for 'Duplicate' because user defined parameter name '_0' in 'a' conflicts with automatically generated parameter name}} + // expected-note@-4 {{cannot automatically synthesize 'Encodable' for 'Duplicate' because user defined parameter name '_1' in 'a' conflicts with automatically generated parameter name}} +} diff --git a/test/decl/protocol/special/coding/enum_codable_excluded_optional_properties.swift b/test/decl/protocol/special/coding/enum_codable_excluded_optional_properties.swift new file mode 100644 index 0000000000000..ea6449aed8d41 --- /dev/null +++ b/test/decl/protocol/special/coding/enum_codable_excluded_optional_properties.swift @@ -0,0 +1,28 @@ +// RUN: %target-typecheck-verify-swift -enable-experimental-enum-codable-derivation + +enum EnumWithNonExcludedOptionalParameters : Codable { // expected-error {{type 'EnumWithNonExcludedOptionalParameters' does not conform to protocol 'Decodable'}} + // expected-error@-1 {{type 'EnumWithNonExcludedOptionalParameters' does not conform to protocol 'Encodable'}} + + case x( + p1: String?, + p2: String!, + // AnyHashable does not conform to Codable. + p3: AnyHashable?, // expected-note {{cannot automatically synthesize 'Decodable' because 'AnyHashable?' does not conform to 'Decodable'}} + // expected-note@-1 {{cannot automatically synthesize 'Encodable' because 'AnyHashable?' does not conform to 'Encodable'}} + p4: AnyHashable!) // expected-note {{cannot automatically synthesize 'Decodable' because 'AnyHashable!' does not conform to 'Decodable'}} + // expected-note@-1 {{cannot automatically synthesize 'Encodable' because 'AnyHashable!' does not conform to 'Encodable'}} +} + +enum EnumWithExcludedOptionalParameters : Codable { // expected-error {{type 'EnumWithExcludedOptionalParameters' does not conform to protocol 'Decodable'}} + case x( + p1: String?, + p2: String!, + // AnyHashable does not conform to Codable. + p3: AnyHashable?, // expected-note {{cannot automatically synthesize 'Decodable' because 'p3' does not have a matching CodingKey and does not have a default value}} + p4: AnyHashable!) // expected-note {{cannot automatically synthesize 'Decodable' because 'p4' does not have a matching CodingKey and does not have a default value}} + + // Explicitly exclude non-Codable properties. + enum XCodingKeys : String, CodingKey { + case p1, p2 + } +} diff --git a/test/decl/protocol/special/coding/enum_codable_failure_diagnostics.swift b/test/decl/protocol/special/coding/enum_codable_failure_diagnostics.swift new file mode 100644 index 0000000000000..31d7a9bccc93b --- /dev/null +++ b/test/decl/protocol/special/coding/enum_codable_failure_diagnostics.swift @@ -0,0 +1,107 @@ +// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -typecheck -parse-as-library -swift-version 4 %s -verify -enable-experimental-enum-codable-derivation + +// Codable enum with non-Codable parameter. +enum E1 : Codable { +// expected-error@-1 {{type 'E1' does not conform to protocol 'Decodable'}} +// expected-error@-2 {{type 'E1' does not conform to protocol 'Encodable'}} + + struct Nested {} + case x( + a: String = "", + b: Int = 0, + c: Nested = Nested()) + // expected-note@-1 {{cannot automatically synthesize 'Decodable' because 'Nested' does not conform to 'Decodable'}} + // expected-note@-2 {{cannot automatically synthesize 'Encodable' because 'Nested' does not conform to 'Encodable'}} +} + +// Codable enum with non-enum CodingKeys. +enum E2 : Codable { +// expected-error@-1 {{type 'E2' does not conform to protocol 'Decodable'}} +// expected-error@-2 {{type 'E2' does not conform to protocol 'Encodable'}} + + case x( + a: String = "", + b: Int = 0, + c: Double?) + + struct CodingKeys : CodingKey { + // expected-note@-1 {{cannot automatically synthesize 'Decodable' because 'CodingKeys' is not an enum}} + // expected-note@-2 {{cannot automatically synthesize 'Encodable' because 'CodingKeys' is not an enum}} + var stringValue: String = "" + var intValue: Int? = nil + init?(stringValue: String) {} + init?(intValue: Int) {} + } +} + +// Codable struct with CodingKeys not conforming to CodingKey. +enum E3 : Codable { +// expected-error@-1 {{type 'E3' does not conform to protocol 'Decodable'}} +// expected-error@-2 {{type 'E3' does not conform to protocol 'Encodable'}} + + case x( + a: String = "", + b: Int = 0, + c: Double?) + + enum XCodingKeys : String { + // expected-note@-1 {{cannot automatically synthesize 'Decodable' because 'CodingKeys' does not conform to CodingKey}} + // expected-note@-2 {{cannot automatically synthesize 'Encodable' because 'CodingKeys' does not conform to CodingKey}} + case a + case b + case c + } +} + +// Codable enum with extraneous CodingKeys +enum E4 : Codable { +// expected-error@-1 {{type 'E4' does not conform to protocol 'Decodable'}} +// expected-error@-2 {{type 'E4' does not conform to protocol 'Encodable'}} + + case x( + a: String = "", + b: Int = 0, + c: Double?) + + enum XCodingKeys : String, CodingKey { + case a + case a2 + // expected-note@-1 {{CodingKey case 'a2' does not match any stored properties}} + // expected-note@-2 {{CodingKey case 'a2' does not match any stored properties}} + case b + case b2 + // expected-note@-1 {{CodingKey case 'b2' does not match any stored properties}} + // expected-note@-2 {{CodingKey case 'b2' does not match any stored properties}} + case c + case c2 + // expected-note@-1 {{CodingKey case 'c2' does not match any stored properties}} + // expected-note@-2 {{CodingKey case 'c2' does not match any stored properties}} + } +} + +// Codable enum with non-decoded parameter (which has no default value). +enum E5 : Codable { +// expected-error@-1 {{type 'E5' does not conform to protocol 'Decodable'}} + + case x( + a: String = "", + b: Int, + // expected-note@-1 {{cannot automatically synthesize 'Decodable' because 'b' does not have a matching CodingKey and does not have a default value}} + c: Double?) + + enum XCodingKeys : String, CodingKey { + case a + case c + } +} + +struct NotCodable {} +enum E6 { + case x( + a: NotCodable = NotCodable()) + // expected-note@-1 {{cannot automatically synthesize 'Encodable' because 'NotCodable' does not conform to 'Encodable'}} + // expected-note@-2 {{cannot automatically synthesize 'Decodable' because 'NotCodable' does not conform to 'Decodable'}} +} +extension E6: Codable {} +// expected-error@-1 {{type 'E6' does not conform to protocol 'Encodable'}} +// expected-error@-2 {{type 'E6' does not conform to protocol 'Decodable'}} diff --git a/test/decl/protocol/special/coding/enum_codable_ignore_nonconforming_property.swift b/test/decl/protocol/special/coding/enum_codable_ignore_nonconforming_property.swift new file mode 100644 index 0000000000000..43cb5b44a9f26 --- /dev/null +++ b/test/decl/protocol/special/coding/enum_codable_ignore_nonconforming_property.swift @@ -0,0 +1,26 @@ +// RUN: %target-typecheck-verify-swift -verify-ignore-unknown -enable-experimental-enum-codable-derivation + +struct NonCodable {} + +// Enums which have a default parameter that is not Codable, but which has a +// default value and is skipped in its CodingKeys should still derive +// conformance. +enum SimpleEnum : Codable { + case x( + w: NonCodable = NonCodable(), + x: Int, + y: Double) + + private enum XCodingKeys : String, CodingKey { // expected-note {{'XCodingKeys' declared here}} + case x + case y + } +} + +// They should receive synthesized init(from:) and an encode(to:). +let _ = SimpleEnum.init(from:) +let _ = SimpleEnum.encode(to:) + +// The synthesized CodingKeys type should not be accessible from outside the +// struct. +let _ = SimpleEnum.XCodingKeys.self // expected-error {{'XCodingKeys' is inaccessible due to 'private' protection level}} diff --git a/test/decl/protocol/special/coding/enum_codable_invalid_codingkeys.swift b/test/decl/protocol/special/coding/enum_codable_invalid_codingkeys.swift new file mode 100644 index 0000000000000..3de8dc8f23829 --- /dev/null +++ b/test/decl/protocol/special/coding/enum_codable_invalid_codingkeys.swift @@ -0,0 +1,37 @@ +// RUN: %target-typecheck-verify-swift -verify-ignore-unknown -enable-experimental-enum-codable-derivation + +// Enums with a CodingKeys entity which is not a type should not derive +// conformance. +enum InvalidCodingKeys1 : Codable { // expected-error {{type 'InvalidCodingKeys1' does not conform to protocol 'Decodable'}} +// expected-error@-1 {{type 'InvalidCodingKeys1' does not conform to protocol 'Encodable'}} + static let CodingKeys = 5 // expected-note {{cannot automatically synthesize 'Decodable' because 'CodingKeys' is not an enum}} + // expected-note@-1 {{cannot automatically synthesize 'Encodable' because 'CodingKeys' is not an enum}} +} + +// Enums with a CodingKeys entity which does not conform to CodingKey should +// not derive conformance. +enum InvalidCodingKeys2 : Codable { // expected-error {{type 'InvalidCodingKeys2' does not conform to protocol 'Decodable'}} + // expected-error@-1 {{type 'InvalidCodingKeys2' does not conform to protocol 'Encodable'}} + enum CodingKeys {} // expected-note {{cannot automatically synthesize 'Decodable' because 'CodingKeys' does not conform to CodingKey}} + // expected-note@-1 {{cannot automatically synthesize 'Encodable' because 'CodingKeys' does not conform to CodingKey}} +} + +// Enums with a CodingKeys entity which is not an enum should not derive +// conformance. +enum InvalidCodingKeys3 : Codable { // expected-error {{type 'InvalidCodingKeys3' does not conform to protocol 'Decodable'}} + // expected-error@-1 {{type 'InvalidCodingKeys3' does not conform to protocol 'Encodable'}} + struct CodingKeys : CodingKey { // expected-note {{cannot automatically synthesize 'Decodable' because 'CodingKeys' is not an enum}} + // expected-note@-1 {{cannot automatically synthesize 'Encodable' because 'CodingKeys' is not an enum}} + var stringValue: String + init?(stringValue: String) { + self.stringValue = stringValue + self.intValue = nil + } + + var intValue: Int? + init?(intValue: Int) { + self.stringValue = "\(intValue)" + self.intValue = intValue + } + } +} diff --git a/test/decl/protocol/special/coding/enum_codable_member_type_lookup.swift b/test/decl/protocol/special/coding/enum_codable_member_type_lookup.swift new file mode 100644 index 0000000000000..9a9c03a33dcb1 --- /dev/null +++ b/test/decl/protocol/special/coding/enum_codable_member_type_lookup.swift @@ -0,0 +1,11 @@ +// RUN: %target-typecheck-verify-swift -verify-ignore-unknown -enable-experimental-enum-codable-derivation + +// MARK: - Synthesized CodingKeys Enum + +// Enums which get synthesized Codable implementations should have visible +// CodingKey enums during member type lookup. +enum SynthesizedEnum : Codable { + case value + +} + diff --git a/test/decl/protocol/special/coding/enum_codable_nonconforming_property.swift b/test/decl/protocol/special/coding/enum_codable_nonconforming_property.swift new file mode 100644 index 0000000000000..f5e252938e0f1 --- /dev/null +++ b/test/decl/protocol/special/coding/enum_codable_nonconforming_property.swift @@ -0,0 +1,166 @@ +// RUN: %target-typecheck-verify-swift -verify-ignore-unknown -enable-experimental-enum-codable-derivation + +enum NonCodable : Hashable { + func hash(into hasher: inout Hasher) {} + + static func ==(_ lhs: NonCodable, _ rhs: NonCodable) -> Bool { + return true + } +} + +struct CodableGeneric : Codable { +} + +// Enums whose properties are not all Codable should fail to synthesize +// conformance. +enum NonConformingEnum : Codable { // expected-error {{type 'NonConformingEnum' does not conform to protocol 'Decodable'}} + // expected-error@-1 {{type 'NonConformingEnum' does not conform to protocol 'Decodable'}} + // expected-error@-2 {{type 'NonConformingEnum' does not conform to protocol 'Encodable'}} + // expected-error@-3 {{type 'NonConformingEnum' does not conform to protocol 'Encodable'}} + case x( + w: NonCodable, // expected-note {{cannot automatically synthesize 'Decodable' because 'NonCodable' does not conform to 'Decodable'}} + // expected-note@-1 {{cannot automatically synthesize 'Decodable' because 'NonCodable' does not conform to 'Decodable'}} + // expected-note@-2 {{cannot automatically synthesize 'Encodable' because 'NonCodable' does not conform to 'Encodable'}} + // expected-note@-3 {{cannot automatically synthesize 'Encodable' because 'NonCodable' does not conform to 'Encodable'}} + x: Int, + y: Double, + + // FIXME: Remove when conditional conformance lands. + // Because conditional conformance is not yet available, Optional, Array, + // Set, and Dictionary all conform to Codable even when their generic + // parameters do not. + // We want to make sure that these cases prevent derived conformance. + nonCodableOptional: NonCodable? = nil, // expected-note {{cannot automatically synthesize 'Decodable' because 'NonCodable?' does not conform to 'Decodable'}} + // expected-note@-1 {{cannot automatically synthesize 'Decodable' because 'NonCodable?' does not conform to 'Decodable'}} + // expected-note@-2 {{cannot automatically synthesize 'Encodable' because 'NonCodable?' does not conform to 'Encodable'}} + // expected-note@-3 {{cannot automatically synthesize 'Encodable' because 'NonCodable?' does not conform to 'Encodable'}} + nonCodableArray: [NonCodable] = [], // expected-note {{cannot automatically synthesize 'Decodable' because '[NonCodable]' does not conform to 'Decodable'}} + // expected-note@-1 {{cannot automatically synthesize 'Decodable' because '[NonCodable]' does not conform to 'Decodable'}} + // expected-note@-2 {{cannot automatically synthesize 'Encodable' because '[NonCodable]' does not conform to 'Encodable'}} + // expected-note@-3 {{cannot automatically synthesize 'Encodable' because '[NonCodable]' does not conform to 'Encodable'}} + nonCodableSet: Set = [], // expected-note {{cannot automatically synthesize 'Decodable' because 'Set' does not conform to 'Decodable'}} + // expected-note@-1 {{cannot automatically synthesize 'Decodable' because 'Set' does not conform to 'Decodable'}} + // expected-note@-2 {{cannot automatically synthesize 'Encodable' because 'Set' does not conform to 'Encodable'}} + // expected-note@-3 {{cannot automatically synthesize 'Encodable' because 'Set' does not conform to 'Encodable'}} + nonCodableDictionary1: [String : NonCodable] = [:], // expected-note {{cannot automatically synthesize 'Decodable' because '[String : NonCodable]' does not conform to 'Decodable'}} + // expected-note@-1 {{cannot automatically synthesize 'Decodable' because '[String : NonCodable]' does not conform to 'Decodable'}} + // expected-note@-2 {{cannot automatically synthesize 'Encodable' because '[String : NonCodable]' does not conform to 'Encodable'}} + // expected-note@-3 {{cannot automatically synthesize 'Encodable' because '[String : NonCodable]' does not conform to 'Encodable'}} + nonCodableDictionary2: [NonCodable : String] = [:], // expected-note {{cannot automatically synthesize 'Decodable' because '[NonCodable : String]' does not conform to 'Decodable'}} + // expected-note@-1 {{cannot automatically synthesize 'Decodable' because '[NonCodable : String]' does not conform to 'Decodable'}} + // expected-note@-2 {{cannot automatically synthesize 'Encodable' because '[NonCodable : String]' does not conform to 'Encodable'}} + // expected-note@-3 {{cannot automatically synthesize 'Encodable' because '[NonCodable : String]' does not conform to 'Encodable'}} + nonCodableDictionary3: [NonCodable : NonCodable] = [:], // expected-note {{cannot automatically synthesize 'Decodable' because '[NonCodable : NonCodable]' does not conform to 'Decodable'}} + // expected-note@-1 {{cannot automatically synthesize 'Decodable' because '[NonCodable : NonCodable]' does not conform to 'Decodable'}} + // expected-note@-2 {{cannot automatically synthesize 'Encodable' because '[NonCodable : NonCodable]' does not conform to 'Encodable'}} + // expected-note@-3 {{cannot automatically synthesize 'Encodable' because '[NonCodable : NonCodable]' does not conform to 'Encodable'}} + + // These conditions should apply recursively, too. + nonCodableOptionalOptional: NonCodable?? = nil, // expected-note {{cannot automatically synthesize 'Decodable' because 'NonCodable??' does not conform to 'Decodable'}} + // expected-note@-1 {{cannot automatically synthesize 'Decodable' because 'NonCodable??' does not conform to 'Decodable'}} + // expected-note@-2 {{cannot automatically synthesize 'Encodable' because 'NonCodable??' does not conform to 'Encodable'}} + // expected-note@-3 {{cannot automatically synthesize 'Encodable' because 'NonCodable??' does not conform to 'Encodable'}} + nonCodableOptionalArray: [NonCodable]? = nil, // expected-note {{cannot automatically synthesize 'Decodable' because '[NonCodable]?' does not conform to 'Decodable'}} + // expected-note@-1 {{cannot automatically synthesize 'Decodable' because '[NonCodable]?' does not conform to 'Decodable'}} + // expected-note@-2 {{cannot automatically synthesize 'Encodable' because '[NonCodable]?' does not conform to 'Encodable'}} + // expected-note@-3 {{cannot automatically synthesize 'Encodable' because '[NonCodable]?' does not conform to 'Encodable'}} + nonCodableOptionalSet: Set? = nil, // expected-note {{cannot automatically synthesize 'Decodable' because 'Set?' does not conform to 'Decodable'}} + // expected-note@-1 {{cannot automatically synthesize 'Decodable' because 'Set?' does not conform to 'Decodable'}} + // expected-note@-2 {{cannot automatically synthesize 'Encodable' because 'Set?' does not conform to 'Encodable'}} + // expected-note@-3 {{cannot automatically synthesize 'Encodable' because 'Set?' does not conform to 'Encodable'}} + nonCodableOptionalDictionary1: [String : NonCodable]? = nil, // expected-note {{cannot automatically synthesize 'Decodable' because '[String : NonCodable]?' does not conform to 'Decodable'}} + // expected-note@-1 {{cannot automatically synthesize 'Decodable' because '[String : NonCodable]?' does not conform to 'Decodable'}} + // expected-note@-2 {{cannot automatically synthesize 'Encodable' because '[String : NonCodable]?' does not conform to 'Encodable'}} + // expected-note@-3 {{cannot automatically synthesize 'Encodable' because '[String : NonCodable]?' does not conform to 'Encodable'}} + nonCodableOptionalDictionary2: [NonCodable : String]? = nil, // expected-note {{cannot automatically synthesize 'Decodable' because '[NonCodable : String]?' does not conform to 'Decodable'}} + // expected-note@-1 {{cannot automatically synthesize 'Decodable' because '[NonCodable : String]?' does not conform to 'Decodable'}} + // expected-note@-2 {{cannot automatically synthesize 'Encodable' because '[NonCodable : String]?' does not conform to 'Encodable'}} + // expected-note@-3 {{cannot automatically synthesize 'Encodable' because '[NonCodable : String]?' does not conform to 'Encodable'}} + nonCodableOptionalDictionary3: [NonCodable : NonCodable]? = nil, // expected-note {{cannot automatically synthesize 'Decodable' because '[NonCodable : NonCodable]?' does not conform to 'Decodable'}} + // expected-note@-1 {{cannot automatically synthesize 'Decodable' because '[NonCodable : NonCodable]?' does not conform to 'Decodable'}} + // expected-note@-2 {{cannot automatically synthesize 'Encodable' because '[NonCodable : NonCodable]?' does not conform to 'Encodable'}} + // expected-note@-3 {{cannot automatically synthesize 'Encodable' because '[NonCodable : NonCodable]?' does not conform to 'Encodable'}} + + nonCodableArrayOptional: [NonCodable?] = [], // expected-note {{cannot automatically synthesize 'Decodable' because '[NonCodable?]' does not conform to 'Decodable'}} + // expected-note@-1 {{cannot automatically synthesize 'Decodable' because '[NonCodable?]' does not conform to 'Decodable'}} + // expected-note@-2 {{cannot automatically synthesize 'Encodable' because '[NonCodable?]' does not conform to 'Encodable'}} + // expected-note@-3 {{cannot automatically synthesize 'Encodable' because '[NonCodable?]' does not conform to 'Encodable'}} + nonCodableArrayArray: [[NonCodable]] = [], // expected-note {{cannot automatically synthesize 'Decodable' because '[[NonCodable]]' does not conform to 'Decodable'}} + // expected-note@-1 {{cannot automatically synthesize 'Decodable' because '[[NonCodable]]' does not conform to 'Decodable'}} + // expected-note@-2 {{cannot automatically synthesize 'Encodable' because '[[NonCodable]]' does not conform to 'Encodable'}} + // expected-note@-3 {{cannot automatically synthesize 'Encodable' because '[[NonCodable]]' does not conform to 'Encodable'}} + nonCodableArraySet: [Set] = [], // expected-note {{cannot automatically synthesize 'Decodable' because '[Set]' does not conform to 'Decodable'}} + // expected-note@-1 {{cannot automatically synthesize 'Decodable' because '[Set]' does not conform to 'Decodable'}} + // expected-note@-2 {{cannot automatically synthesize 'Encodable' because '[Set]' does not conform to 'Encodable'}} + // expected-note@-3 {{cannot automatically synthesize 'Encodable' because '[Set]' does not conform to 'Encodable'}} + nonCodableArrayDictionary1: [[String : NonCodable]] = [], // expected-note {{cannot automatically synthesize 'Decodable' because '[[String : NonCodable]]' does not conform to 'Decodable'}} + // expected-note@-1 {{cannot automatically synthesize 'Decodable' because '[[String : NonCodable]]' does not conform to 'Decodable'}} + // expected-note@-2 {{cannot automatically synthesize 'Encodable' because '[[String : NonCodable]]' does not conform to 'Encodable'}} + // expected-note@-3 {{cannot automatically synthesize 'Encodable' because '[[String : NonCodable]]' does not conform to 'Encodable'}} + nonCodableArrayDictionary2: [[NonCodable : String]] = [], // expected-note {{cannot automatically synthesize 'Decodable' because '[[NonCodable : String]]' does not conform to 'Decodable'}} + // expected-note@-1 {{cannot automatically synthesize 'Decodable' because '[[NonCodable : String]]' does not conform to 'Decodable'}} + // expected-note@-2 {{cannot automatically synthesize 'Encodable' because '[[NonCodable : String]]' does not conform to 'Encodable'}} + // expected-note@-3 {{cannot automatically synthesize 'Encodable' because '[[NonCodable : String]]' does not conform to 'Encodable'}} + nonCodableArrayDictionary3: [[NonCodable : NonCodable]] = [], // expected-note {{cannot automatically synthesize 'Decodable' because '[[NonCodable : NonCodable]]' does not conform to 'Decodable'}} + // expected-note@-1 {{cannot automatically synthesize 'Decodable' because '[[NonCodable : NonCodable]]' does not conform to 'Decodable'}} + // expected-note@-2 {{cannot automatically synthesize 'Encodable' because '[[NonCodable : NonCodable]]' does not conform to 'Encodable'}} + // expected-note@-3 {{cannot automatically synthesize 'Encodable' because '[[NonCodable : NonCodable]]' does not conform to 'Encodable'}} + + nonCodableDictionaryOptional1: [String : NonCodable?] = [:], // expected-note {{cannot automatically synthesize 'Decodable' because '[String : NonCodable?]' does not conform to 'Decodable'}} + // expected-note@-1 {{cannot automatically synthesize 'Decodable' because '[String : NonCodable?]' does not conform to 'Decodable'}} + // expected-note@-2 {{cannot automatically synthesize 'Encodable' because '[String : NonCodable?]' does not conform to 'Encodable'}} + // expected-note@-3 {{cannot automatically synthesize 'Encodable' because '[String : NonCodable?]' does not conform to 'Encodable'}} + nonCodableDictionaryOptional2: [NonCodable : NonCodable?] = [:], // expected-note {{cannot automatically synthesize 'Decodable' because '[NonCodable : NonCodable?]' does not conform to 'Decodable'}} + // expected-note@-1 {{cannot automatically synthesize 'Decodable' because '[NonCodable : NonCodable?]' does not conform to 'Decodable'}} + // expected-note@-2 {{cannot automatically synthesize 'Encodable' because '[NonCodable : NonCodable?]' does not conform to 'Encodable'}} + // expected-note@-3 {{cannot automatically synthesize 'Encodable' because '[NonCodable : NonCodable?]' does not conform to 'Encodable'}} + nonCodableDictionaryArray1: [String : [NonCodable]] = [:], // expected-note {{cannot automatically synthesize 'Decodable' because '[String : [NonCodable]]' does not conform to 'Decodable'}} + // expected-note@-1 {{cannot automatically synthesize 'Decodable' because '[String : [NonCodable]]' does not conform to 'Decodable'}} + // expected-note@-2 {{cannot automatically synthesize 'Encodable' because '[String : [NonCodable]]' does not conform to 'Encodable'}} + // expected-note@-3 {{cannot automatically synthesize 'Encodable' because '[String : [NonCodable]]' does not conform to 'Encodable'}} + nonCodableDictionaryArray2: [NonCodable : [NonCodable]] = [:], // expected-note {{cannot automatically synthesize 'Decodable' because '[NonCodable : [NonCodable]]' does not conform to 'Decodable'}} + // expected-note@-1 {{cannot automatically synthesize 'Decodable' because '[NonCodable : [NonCodable]]' does not conform to 'Decodable'}} + // expected-note@-2 {{cannot automatically synthesize 'Encodable' because '[NonCodable : [NonCodable]]' does not conform to 'Encodable'}} + // expected-note@-3 {{cannot automatically synthesize 'Encodable' because '[NonCodable : [NonCodable]]' does not conform to 'Encodable'}} + nonCodableDictionarySet1: [String : Set] = [:], // expected-note {{cannot automatically synthesize 'Decodable' because '[String : Set]' does not conform to 'Decodable'}} + // expected-note@-1 {{cannot automatically synthesize 'Decodable' because '[String : Set]' does not conform to 'Decodable'}} + // expected-note@-2 {{cannot automatically synthesize 'Encodable' because '[String : Set]' does not conform to 'Encodable'}} + // expected-note@-3 {{cannot automatically synthesize 'Encodable' because '[String : Set]' does not conform to 'Encodable'}} + nonCodableDictionarySet2: [NonCodable : Set] = [:], // expected-note {{cannot automatically synthesize 'Decodable' because '[NonCodable : Set]' does not conform to 'Decodable'}} + // expected-note@-1 {{cannot automatically synthesize 'Decodable' because '[NonCodable : Set]' does not conform to 'Decodable'}} + // expected-note@-2 {{cannot automatically synthesize 'Encodable' because '[NonCodable : Set]' does not conform to 'Encodable'}} + // expected-note@-3 {{cannot automatically synthesize 'Encodable' because '[NonCodable : Set]' does not conform to 'Encodable'}} + nonCodableDictionaryDictionary1: [String : [String : NonCodable]] = [:], // expected-note {{cannot automatically synthesize 'Decodable' because '[String : [String : NonCodable]]' does not conform to 'Decodable'}} + // expected-note@-1 {{cannot automatically synthesize 'Decodable' because '[String : [String : NonCodable]]' does not conform to 'Decodable'}} + // expected-note@-2 {{cannot automatically synthesize 'Encodable' because '[String : [String : NonCodable]]' does not conform to 'Encodable'}} + // expected-note@-3 {{cannot automatically synthesize 'Encodable' because '[String : [String : NonCodable]]' does not conform to 'Encodable'}} + nonCodableDictionaryDictionary2: [NonCodable : [String : NonCodable]] = [:], // expected-note {{cannot automatically synthesize 'Decodable' because '[NonCodable : [String : NonCodable]]' does not conform to 'Decodable'}} + // expected-note@-1 {{cannot automatically synthesize 'Decodable' because '[NonCodable : [String : NonCodable]]' does not conform to 'Decodable'}} + // expected-note@-2 {{cannot automatically synthesize 'Encodable' because '[NonCodable : [String : NonCodable]]' does not conform to 'Encodable'}} + // expected-note@-3 {{cannot automatically synthesize 'Encodable' because '[NonCodable : [String : NonCodable]]' does not conform to 'Encodable'}} + nonCodableDictionaryDictionary3: [String : [NonCodable : NonCodable]] = [:], // expected-note {{cannot automatically synthesize 'Decodable' because '[String : [NonCodable : NonCodable]]' does not conform to 'Decodable'}} + // expected-note@-1 {{cannot automatically synthesize 'Decodable' because '[String : [NonCodable : NonCodable]]' does not conform to 'Decodable'}} + // expected-note@-2 {{cannot automatically synthesize 'Encodable' because '[String : [NonCodable : NonCodable]]' does not conform to 'Encodable'}} + // expected-note@-3 {{cannot automatically synthesize 'Encodable' because '[String : [NonCodable : NonCodable]]' does not conform to 'Encodable'}} + nonCodableDictionaryDictionary4: [NonCodable : [NonCodable : NonCodable]] = [:], // expected-note {{cannot automatically synthesize 'Decodable' because '[NonCodable : [NonCodable : NonCodable]]' does not conform to 'Decodable'}} + // expected-note@-1 {{cannot automatically synthesize 'Decodable' because '[NonCodable : [NonCodable : NonCodable]]' does not conform to 'Decodable'}} + // expected-note@-2 {{cannot automatically synthesize 'Encodable' because '[NonCodable : [NonCodable : NonCodable]]' does not conform to 'Encodable'}} + // expected-note@-3 {{cannot automatically synthesize 'Encodable' because '[NonCodable : [NonCodable : NonCodable]]' does not conform to 'Encodable'}} + + // However, arbitrary generic types which _do_ conform to Codable should be + // valid. + codableGenericThing1: CodableGeneric? = nil, + codableGenericThing2: CodableGeneric? = nil, + codableGenericThing3: CodableGeneric<[NonCodable]>? = nil, + codableGenericThing4: CodableGeneric>? = nil, + codableGenericThing5: CodableGeneric<[String : NonCodable]>? = nil, + codableGenericThing6: CodableGeneric<[NonCodable : String]>? = nil, + codableGenericThing7: CodableGeneric<[NonCodable : NonCodable]>? = nil) +} + +// They should not receive Codable methods. +let _ = NonConformingEnum.init(from:) // expected-error {{'NonConformingEnum' cannot be constructed because it has no accessible initializers}} +let _ = NonConformingEnum.encode(to:) // expected-error {{type 'NonConformingEnum' has no member 'encode(to:)'}} + +// They should not get a CodingKeys type. +let _ = NonConformingEnum.XCodingKeys.self // expected-error {{'XCodingKeys' is inaccessible due to 'private' protection level}} diff --git a/test/decl/protocol/special/coding/enum_codable_reordered_codingkeys.swift b/test/decl/protocol/special/coding/enum_codable_reordered_codingkeys.swift new file mode 100644 index 0000000000000..0adbc9dc7c358 --- /dev/null +++ b/test/decl/protocol/special/coding/enum_codable_reordered_codingkeys.swift @@ -0,0 +1,12 @@ +// RUN: %target-typecheck-verify-swift -verify-ignore-unknown -enable-experimental-enum-codable-derivation + +// The order of cases in the case specific CodingKeys enum should not matter +enum SimpleEnum : Codable { + case a(x: Int, y: Double, z: Bool) + + enum ACodingKeys: CodingKey { + case z + case x + case y + } +} \ No newline at end of file diff --git a/test/decl/protocol/special/coding/enum_codable_simple.swift b/test/decl/protocol/special/coding/enum_codable_simple.swift new file mode 100644 index 0000000000000..fcce676e1970d --- /dev/null +++ b/test/decl/protocol/special/coding/enum_codable_simple.swift @@ -0,0 +1,44 @@ +// RUN: %target-typecheck-verify-swift -verify-ignore-unknown -enable-experimental-enum-codable-derivation + +// Simple enums with all Codable parameters should get derived conformance to +// Codable. +enum SimpleEnum : Codable { + case a(x: Int, y: Double) + case b(z: String) + case c(Int, String, b: Bool) + + // These lines have to be within the SimpleEnum type because CodingKeys + // should be private. + func foo() { + // They should receive a synthesized CodingKeys enum. + let _ = SimpleEnum.CodingKeys.self + let _ = SimpleEnum.ACodingKeys.self + let _ = SimpleEnum.BCodingKeys.self + let _ = SimpleEnum.CCodingKeys.self + + // The enum should have a case for each of the cases. + let _ = SimpleEnum.CodingKeys.a + let _ = SimpleEnum.CodingKeys.b + + // The enum should have a case for each of the vars. + let _ = SimpleEnum.ACodingKeys.x + let _ = SimpleEnum.ACodingKeys.y + + let _ = SimpleEnum.BCodingKeys.z + + let _ = SimpleEnum.CCodingKeys._0 + let _ = SimpleEnum.CCodingKeys._1 + let _ = SimpleEnum.CCodingKeys.b + } +} + +// They should receive synthesized init(from:) and an encode(to:). +let _ = SimpleEnum.init(from:) +let _ = SimpleEnum.encode(to:) + +// The synthesized CodingKeys type should not be accessible from outside the +// enum. +let _ = SimpleEnum.CodingKeys.self // expected-error {{'CodingKeys' is inaccessible due to 'private' protection level}} +let _ = SimpleEnum.ACodingKeys.self // expected-error {{'ACodingKeys' is inaccessible due to 'private' protection level}} +let _ = SimpleEnum.BCodingKeys.self // expected-error {{'BCodingKeys' is inaccessible due to 'private' protection level}} +let _ = SimpleEnum.CCodingKeys.self // expected-error {{'CCodingKeys' is inaccessible due to 'private' protection level}} diff --git a/test/decl/protocol/special/coding/enum_codable_simple_conditional.swift b/test/decl/protocol/special/coding/enum_codable_simple_conditional.swift new file mode 100644 index 0000000000000..169682fd86803 --- /dev/null +++ b/test/decl/protocol/special/coding/enum_codable_simple_conditional.swift @@ -0,0 +1,42 @@ +// RUN: %target-typecheck-verify-swift -verify-ignore-unknown -swift-version 4 -enable-experimental-enum-codable-derivation + +enum Conditional { + case a(x: T, y: T?) + case b(z: [T]) + + func foo() { + // They should receive a synthesized CodingKeys enum. + let _ = Conditional.CodingKeys.self + let _ = Conditional.ACodingKeys.self + + // The enum should have a case for each of the cases. + let _ = Conditional.CodingKeys.a + let _ = Conditional.CodingKeys.b + + // The enum should have a case for each of the parameters. + let _ = Conditional.ACodingKeys.x + let _ = Conditional.ACodingKeys.y + + let _ = Conditional.BCodingKeys.z + } +} + +extension Conditional: Codable where T: Codable { // expected-note 4{{where 'T' = 'Nonconforming'}} +} + +// They should receive synthesized init(from:) and an encode(to:). +let _ = Conditional.init(from:) +let _ = Conditional.encode(to:) + +// but only for Codable parameters. +struct Nonconforming {} +let _ = Conditional.init(from:) // expected-error {{referencing initializer 'init(from:)' on 'Conditional' requires that 'Nonconforming' conform to 'Encodable'}} +// expected-error@-1 {{referencing initializer 'init(from:)' on 'Conditional' requires that 'Nonconforming' conform to 'Decodable'}} +let _ = Conditional.encode(to:) // expected-error {{referencing instance method 'encode(to:)' on 'Conditional' requires that 'Nonconforming' conform to 'Encodable'}} +// expected-error@-1 {{referencing instance method 'encode(to:)' on 'Conditional' requires that 'Nonconforming' conform to 'Decodable'}} + +// The synthesized CodingKeys type should not be accessible from outside the +// enum. +let _ = Conditional.CodingKeys.self // expected-error {{'CodingKeys' is inaccessible due to 'private' protection level}} +let _ = Conditional.ACodingKeys.self // expected-error {{'ACodingKeys' is inaccessible due to 'private' protection level}} +let _ = Conditional.BCodingKeys.self // expected-error {{'BCodingKeys' is inaccessible due to 'private' protection level}} diff --git a/test/decl/protocol/special/coding/enum_codable_simple_conditional_separate.swift b/test/decl/protocol/special/coding/enum_codable_simple_conditional_separate.swift new file mode 100644 index 0000000000000..58097f21af456 --- /dev/null +++ b/test/decl/protocol/special/coding/enum_codable_simple_conditional_separate.swift @@ -0,0 +1,45 @@ +// RUN: %target-typecheck-verify-swift -verify-ignore-unknown -swift-version 4 -enable-experimental-enum-codable-derivation + +enum Conditional { + case a(x: T, y: T?) + case b(z: [T]) + + func foo() { + // They should receive a synthesized CodingKeys enum. + let _ = Conditional.CodingKeys.self + let _ = Conditional.ACodingKeys.self + let _ = Conditional.BCodingKeys.self + + // The enum should have a case for each of the cases. + let _ = Conditional.CodingKeys.a + let _ = Conditional.CodingKeys.b + + // The enum should have a case for each of the vars. + let _ = Conditional.ACodingKeys.x + let _ = Conditional.ACodingKeys.y + + let _ = Conditional.BCodingKeys.z + } +} + +extension Conditional: Encodable where T: Encodable { // expected-note {{where 'T' = 'OnlyDec'}} +} +extension Conditional: Decodable where T: Decodable { // expected-note {{where 'T' = 'OnlyEnc'}} +} + +struct OnlyEnc: Encodable {} +struct OnlyDec: Decodable {} + +// They should receive synthesized init(from:) and an encode(to:). +let _ = Conditional.init(from:) +let _ = Conditional.encode(to:) + +// but only for the appropriately *codable parameters. +let _ = Conditional.init(from:) // expected-error {{referencing initializer 'init(from:)' on 'Conditional' requires that 'OnlyEnc' conform to 'Decodable'}} +let _ = Conditional.encode(to:) // expected-error {{referencing instance method 'encode(to:)' on 'Conditional' requires that 'OnlyDec' conform to 'Encodable'}} + +// The synthesized CodingKeys type should not be accessible from outside the +// enum. +let _ = Conditional.CodingKeys.self // expected-error {{'CodingKeys' is inaccessible due to 'private' protection level}} +let _ = Conditional.ACodingKeys.self // expected-error {{'ACodingKeys' is inaccessible due to 'private' protection level}} +let _ = Conditional.BCodingKeys.self // expected-error {{'BCodingKeys' is inaccessible due to 'private' protection level}} diff --git a/test/decl/protocol/special/coding/enum_codable_simple_extension.swift b/test/decl/protocol/special/coding/enum_codable_simple_extension.swift new file mode 100644 index 0000000000000..e24ee64e4b085 --- /dev/null +++ b/test/decl/protocol/special/coding/enum_codable_simple_extension.swift @@ -0,0 +1,38 @@ +// RUN: %target-typecheck-verify-swift -verify-ignore-unknown -swift-version 4 -enable-experimental-enum-codable-derivation + +// Simple enums where Codable conformance is added in extensions should derive +// conformance. +enum SimpleEnum { + case a(x: Int, y: Double) + case b(z: String) + + func foo() { + // They should receive a synthesized CodingKeys enum. + let _ = SimpleEnum.CodingKeys.self + let _ = SimpleEnum.ACodingKeys.self + let _ = SimpleEnum.BCodingKeys.self + + // The enum should have a case for each of the cases. + let _ = SimpleEnum.CodingKeys.a + let _ = SimpleEnum.CodingKeys.b + + // The enum should have a case for each of the vars. + let _ = SimpleEnum.ACodingKeys.x + let _ = SimpleEnum.ACodingKeys.y + + let _ = SimpleEnum.BCodingKeys.z + } +} + +extension SimpleEnum : Codable { +} + +// They should receive synthesized init(from:) and an encode(to:). +let _ = SimpleEnum.init(from:) +let _ = SimpleEnum.encode(to:) + +// The synthesized CodingKeys type should not be accessible from outside the +// enum. +let _ = SimpleEnum.CodingKeys.self // expected-error {{'CodingKeys' is inaccessible due to 'private' protection level}} +let _ = SimpleEnum.ACodingKeys.self // expected-error {{'ACodingKeys' is inaccessible due to 'private' protection level}} +let _ = SimpleEnum.BCodingKeys.self // expected-error {{'BCodingKeys' is inaccessible due to 'private' protection level}} diff --git a/test/decl/protocol/special/coding/enum_codable_simple_extension_flipped.swift b/test/decl/protocol/special/coding/enum_codable_simple_extension_flipped.swift new file mode 100644 index 0000000000000..f4ee37f7dcc1a --- /dev/null +++ b/test/decl/protocol/special/coding/enum_codable_simple_extension_flipped.swift @@ -0,0 +1,38 @@ +// RUN: %target-typecheck-verify-swift -verify-ignore-unknown -swift-version 4 -enable-experimental-enum-codable-derivation + +// Simple enums where Codable conformance is added in extensions should derive +// conformance, no matter which order the extension and type occur in. + +extension SimpleEnum : Codable {} + +enum SimpleEnum { + case a(x: Int, y: Double) + case b(z: String) + + func foo() { + // They should receive a synthesized CodingKeys enum. + let _ = SimpleEnum.CodingKeys.self + let _ = SimpleEnum.ACodingKeys.self + let _ = SimpleEnum.BCodingKeys.self + + // The enum should have a case for each of the cases. + let _ = SimpleEnum.CodingKeys.a + let _ = SimpleEnum.CodingKeys.b + + // The enum should have a case for each of the vars. + let _ = SimpleEnum.ACodingKeys.x + let _ = SimpleEnum.ACodingKeys.y + + let _ = SimpleEnum.BCodingKeys.z + } +} + +// They should receive synthesized init(from:) and an encode(to:). +let _ = SimpleEnum.init(from:) +let _ = SimpleEnum.encode(to:) + +// The synthesized CodingKeys type should not be accessible from outside the +// enum. +let _ = SimpleEnum.CodingKeys.self // expected-error {{'CodingKeys' is inaccessible due to 'private' protection level}} +let _ = SimpleEnum.ACodingKeys.self // expected-error {{'ACodingKeys' is inaccessible due to 'private' protection level}} +let _ = SimpleEnum.BCodingKeys.self // expected-error {{'BCodingKeys' is inaccessible due to 'private' protection level}} diff --git a/test/decl/protocol/special/coding/enum_codable_simple_multi.swift b/test/decl/protocol/special/coding/enum_codable_simple_multi.swift new file mode 100644 index 0000000000000..36901c4642780 --- /dev/null +++ b/test/decl/protocol/special/coding/enum_codable_simple_multi.swift @@ -0,0 +1,2 @@ +// RUN: %target-typecheck-verify-swift -verify-ignore-unknown -enable-experimental-enum-codable-derivation %S/Inputs/enum_codable_simple_multi1.swift %S/Inputs/enum_codable_simple_multi2.swift +// RUN: %target-typecheck-verify-swift -verify-ignore-unknown -enable-experimental-enum-codable-derivation %S/Inputs/enum_codable_simple_multi2.swift %S/Inputs/enum_codable_simple_multi1.swift