From 40e5fc5ca119eae46a587685496dfb9c0a573cfb Mon Sep 17 00:00:00 2001 From: Dario Rexin Date: Fri, 20 Nov 2020 16:26:11 -0800 Subject: [PATCH 01/11] [Sema]: Add Codable synthesis for enums with associated values --- include/swift/AST/Decl.h | 5 +- include/swift/AST/KnownDecls.def | 2 + include/swift/AST/KnownIdentifiers.def | 4 + include/swift/AST/KnownStdlibTypes.def | 4 + lib/AST/Decl.cpp | 7 + lib/Sema/DerivedConformanceCodable.cpp | 984 ++++++++++++++++++++++++- lib/Sema/DerivedConformances.cpp | 34 +- lib/Sema/DerivedConformances.h | 7 +- 8 files changed, 1015 insertions(+), 32 deletions(-) diff --git a/include/swift/AST/Decl.h b/include/swift/AST/Decl.h index 9a222dff5d02a..5f151d88023e0 100644 --- a/include/swift/AST/Decl.h +++ b/include/swift/AST/Decl.h @@ -6524,7 +6524,10 @@ class EnumElementDecl : public DeclContext, public ValueDecl { bool isIndirect() const { return getAttrs().hasAttribute(); } - + + /// True if any of the parameter are unnamed. + bool hasAnyUnnamedParameters() const; + /// 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/KnownDecls.def b/include/swift/AST/KnownDecls.def index 15a5081285d1d..e0325d2966741 100644 --- a/include/swift/AST/KnownDecls.def +++ b/include/swift/AST/KnownDecls.def @@ -88,4 +88,6 @@ FUNC_DECL(Swap, "swap") FUNC_DECL(UnimplementedInitializer, "_unimplementedInitializer") FUNC_DECL(Undefined, "_undefined") +FUNC_DECL(FatalError, "fatalError") + #undef FUNC_DECL diff --git a/include/swift/AST/KnownIdentifiers.def b/include/swift/AST/KnownIdentifiers.def index dd0ec4630a153..f54aad64de137 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) @@ -82,6 +83,7 @@ IDENTIFIER(fromRaw) IDENTIFIER(hash) IDENTIFIER(hasher) IDENTIFIER(hashValue) +IDENTIFIER(init) IDENTIFIER(initialize) IDENTIFIER(initStorage) IDENTIFIER(initialValue) @@ -94,6 +96,8 @@ IDENTIFIER(keyedBy) IDENTIFIER(keyPath) IDENTIFIER(makeIterator) IDENTIFIER(makeAsyncIterator) +IDENTIFIER(nestedContainer) +IDENTIFIER(nestedUnkeyedContainer) IDENTIFIER(Iterator) IDENTIFIER(AsyncIterator) IDENTIFIER(load) diff --git a/include/swift/AST/KnownStdlibTypes.def b/include/swift/AST/KnownStdlibTypes.def index 94b0e678a61ef..3271c349945be 100644 --- a/include/swift/AST/KnownStdlibTypes.def +++ b/include/swift/AST/KnownStdlibTypes.def @@ -89,7 +89,11 @@ KNOWN_STDLIB_TYPE_DECL(Encoder, ProtocolDecl, 1) KNOWN_STDLIB_TYPE_DECL(Decoder, ProtocolDecl, 1) KNOWN_STDLIB_TYPE_DECL(KeyedEncodingContainer, NominalTypeDecl, 1) KNOWN_STDLIB_TYPE_DECL(KeyedDecodingContainer, NominalTypeDecl, 1) +KNOWN_STDLIB_TYPE_DECL(UnkeyedEncodingContainer, NominalTypeDecl, 1) +KNOWN_STDLIB_TYPE_DECL(UnkeyedDecodingContainer, 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/lib/AST/Decl.cpp b/lib/AST/Decl.cpp index 198d93574438c..3b4950e509158 100644 --- a/lib/AST/Decl.cpp +++ b/lib/AST/Decl.cpp @@ -7726,6 +7726,13 @@ void EnumElementDecl::setParameterList(ParameterList *params) { params->setDeclContextOfParamDecls(this); } +bool EnumElementDecl::hasAnyUnnamedParameters() const { + auto *params = getParameterList(); + return params && + std::any_of(params->begin(), params->end(), + [](auto *paramDecl) { return !paramDecl->hasName(); }); +} + EnumCaseDecl *EnumElementDecl::getParentCase() const { for (EnumCaseDecl *EC : getParentEnum()->getAllCases()) { ArrayRef CaseElements = EC->getElements(); diff --git a/lib/Sema/DerivedConformanceCodable.cpp b/lib/Sema/DerivedConformanceCodable.cpp index 67ddc2041a6e2..62b1858867302 100644 --- a/lib/Sema/DerivedConformanceCodable.cpp +++ b/lib/Sema/DerivedConformanceCodable.cpp @@ -53,6 +53,13 @@ static Identifier getVarNameForCoding(VarDecl *var) { return var->getName(); } +/// Combine two identifiers separated by an '_' +static Identifier combineIdentifiers(const ASTContext &C, Identifier first, + Identifier second) { + std::string enumIdentifierName = first.str().str() + "_" + second.str().str(); + return C.getIdentifier(StringRef(enumIdentifierName)); +} + // 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 @@ -244,6 +251,92 @@ static bool validateCodingKeysEnum(DerivedConformance &derived) { return propertiesAreValid; } +/// Validates the given CodingKeys enum decl by ensuring its cases are a 1-to-1 +/// match with the stored vars of the given EnumElementDecl. +/// +/// \param elementDecl The \c EnumElementDecl to validate against. +/// \param codingKeysDecl The \c CodingKeys enum decl to validate. +static bool validateCaseCodingKeysEnum(const DerivedConformance &derived, + EnumElementDecl *elementDecl, + EnumDecl *codingKeysDecl) { + auto conformanceDC = derived.getConformanceContext(); + + // 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). + // + // 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; + if (elementDecl->hasAssociatedValues()) { + for (auto *varDecl : elementDecl->getParameterList()->getArray()) { + if (!varDecl->isUserAccessible()) + continue; + + properties[getVarNameForCoding(varDecl)] = varDecl; + } + } + + bool propertiesAreValid = true; + for (auto elt : codingKeysDecl->getAllElements()) { + auto it = properties.find(elt->getBaseIdentifier()); + if (it == properties.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; + continue; + } + + // We have a property to map to. Ensure it's {En,De}codable. + auto target = + conformanceDC->mapTypeIntoContext(it->second->getValueInterfaceType()); + if (TypeChecker::conformsToProtocol(target, derived.Protocol, conformanceDC) + .isInvalid()) { + TypeLoc typeLoc = { + it->second->getTypeReprOrParentPatternTypeRepr(), + it->second->getType(), + }; + it->second->diagnose(diag::codable_non_conforming_property_here, + derived.getProtocolType(), typeLoc); + propertiesAreValid = false; + } else { + // The property was valid. Remove it from the list. + properties.erase(it); + } + } + + if (!propertiesAreValid) + return false; + + // If there are any remaining properties 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) { + if (entry.second->hasDefaultExpr()) { + continue; + } + + // The var was not default initializable, and did not have an explicit + // initial value. + propertiesAreValid = false; + entry.second->diagnose(diag::codable_non_decoded_property_here, + derived.getProtocolType(), entry.first); + } + } + + return propertiesAreValid; +} + + + /// Fetches the \c CodingKeys enum nested in \c target, potentially reaching /// through a typealias if the "CodingKeys" entity is a typealias. /// @@ -269,6 +362,142 @@ static EnumDecl *lookupEvaluatedCodingKeysEnum(ASTContext &C, return dyn_cast(codingKeysDecl); } +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 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 bool synthesizeCodingKeysEnum_enum(DerivedConformance &derived, + EnumDecl *target) { + auto &C = derived.Context; + + // We want to look through all the var declarations of this type to create + // enum cases based on those var names. + auto *codingKeyProto = C.getProtocol(KnownProtocolKind::CodingKey); + auto codingKeyType = codingKeyProto->getDeclaredInterfaceType(); + TypeLoc protoTypeLoc[1] = {TypeLoc::withoutLoc(codingKeyType)}; + ArrayRef inherited = C.AllocateCopy(protoTypeLoc); + + bool allConform = true; + auto *conformanceDC = derived.getConformanceContext(); + + auto add = [conformanceDC, &C, &derived](VarDecl *varDecl, + EnumDecl *enumDecl) { + if (!varDecl->isUserAccessible()) { + return true; + } + + auto target = + conformanceDC->mapTypeIntoContext(varDecl->getValueInterfaceType()); + if (TypeChecker::conformsToProtocol(target, derived.Protocol, conformanceDC) + .isInvalid()) { + TypeLoc typeLoc = { + varDecl->getTypeReprOrParentPatternTypeRepr(), + varDecl->getType(), + }; + varDecl->diagnose(diag::codable_non_conforming_property_here, + derived.getProtocolType(), typeLoc); + + return false; + } else { + // if the type conforms to {En,De}codable, add it to the enum. + auto *elt = + new (C) EnumElementDecl(SourceLoc(), getVarNameForCoding(varDecl), + nullptr, SourceLoc(), nullptr, enumDecl); + elt->setImplicit(); + enumDecl->addMember(elt); + + return true; + } + }; + + auto *codingKeysEnum = lookupEvaluatedCodingKeysEnum(C, target); + + // Only derive CodingKeys enum if it is not already defined + if (!codingKeysEnum) { + 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); + + // Add to the type. + target->addMember(enumDecl); + codingKeysEnum = enumDecl; + } + + for (auto *elementDecl : target->getAllElements()) { + auto enumIdentifier = combineIdentifiers(C, C.Id_CodingKeys, + elementDecl->getBaseIdentifier()); + + // Only derive if this case exist in the CodingKeys enum + auto *codingKeyCase = + lookupEnumCase(C, codingKeysEnum, elementDecl->getBaseIdentifier()); + if (!codingKeyCase) + continue; + + // Only derive if it is not already defined + if (!derived.Nominal->lookupDirect(DeclName(enumIdentifier)).empty()) + continue; + + // If there are any unnamed parameters, we can't generate CodingKeys for + // this element and it will be encoded into an unkeyed container. + if (elementDecl->hasAnyUnnamedParameters()) + continue; + + auto *nestedEnum = new (C) EnumDecl( + SourceLoc(), enumIdentifier, SourceLoc(), inherited, nullptr, target); + nestedEnum->setImplicit(); + nestedEnum->setAccess(AccessLevel::Private); + + auto *elementParams = elementDecl->getParameterList(); + if (elementParams) { + for (auto *paramDecl : elementParams->getArray()) { + allConform = allConform && add(paramDecl, nestedEnum); + } + } + + // Forcibly derive conformance to CodingKey. + TypeChecker::checkConformancesInContext(nestedEnum); + + target->addMember(nestedEnum); + } + + if (!allConform) + return false; + + return true; +} + + /// Creates a new var decl representing /// /// var/let container : containerBase @@ -285,24 +514,56 @@ 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 name Name of the resulting variable static VarDecl *createKeyedContainer(ASTContext &C, DeclContext *DC, NominalTypeDecl *keyedContainerDecl, Type keyType, - VarDecl::Introducer introducer) { + VarDecl::Introducer introducer, + Identifier name) { // Bind Keyed*Container to Keyed*Container Type boundType[1] = {keyType}; auto containerType = BoundGenericType::get(keyedContainerDecl, Type(), C.AllocateCopy(boundType)); // let container : Keyed*Container - auto *containerDecl = new (C) VarDecl(/*IsStatic=*/false, introducer, - SourceLoc(), C.Id_container, DC); + auto *containerDecl = + new (C) VarDecl(/*IsStatic=*/false, introducer, SourceLoc(), name, 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 UnkeyedEncodingContainer or \c UnkeyedDecodingContainer). +/// +/// \param C The AST context to create the decl in. +/// +/// \param DC The \c DeclContext to create the decl in. +/// +/// \param unkeyedContainerDecl The generic type to bind the key type in. +/// +/// \param introducer Whether to declare the variable as immutable. +/// +/// \param name Name of the resulting variable +static VarDecl *createUnkeyedContainer(ASTContext &C, DeclContext *DC, + NominalTypeDecl *unkeyedContainerDecl, + VarDecl::Introducer introducer, + Identifier name) { + // let container : Keyed*Container + auto *containerDecl = + new (C) VarDecl(/*IsStatic=*/false, introducer, SourceLoc(), name, DC); + containerDecl->setImplicit(); + containerDecl->setInterfaceType( + unkeyedContainerDecl->getDeclaredInterfaceType()); + return containerDecl; +} + /// Creates a new \c CallExpr representing /// /// base.container(keyedBy: CodingKeys.self) @@ -346,6 +607,63 @@ 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 CallExpr *createNestedUnkeyedContainerForKeyCall(ASTContext &C, + DeclContext *DC, + Expr *base, + Type returnType, + EnumElementDecl *key) { + // (forKey:) + auto *forKeyDecl = new (C) ParamDecl(SourceLoc(), SourceLoc(), C.Id_forKey, + SourceLoc(), C.Id_forKey, DC); + forKeyDecl->setImplicit(); + forKeyDecl->setSpecifier(ParamSpecifier::Default); + forKeyDecl->setInterfaceType(returnType); + + // base.nestedUnkeyedContainer(forKey:) expr + auto *paramList = ParameterList::createWithoutLoc(forKeyDecl); + auto *unboundCall = UnresolvedDotExpr::createImplicit( + C, base, C.Id_nestedUnkeyedContainer, paramList); + + // 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.nestedUnkeyedContainer(forKey: key) call + return CallExpr::createImplicit(C, unboundCall, {keyExpr}, {C.Id_forKey}); +} + /// Looks up the property corresponding to the indicated coding key. /// /// \param conformanceDC The DeclContext we're generating code within. @@ -388,6 +706,272 @@ lookupVarDeclForCodingKeysCase(DeclContext *conformanceDC, llvm_unreachable("Should have found at least 1 var decl"); } +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 CodingKeys_bar : CodingKey { + // case x + // } + // + // @derived enum CodingKeys_baz : 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: + // CodingKeys_bar.self, forKey: .bar) try nestedContainer.encode(x, + // forKey: .x) + // case baz(let y): + // let nestedContainer = try container.nestedContainer(keyedBy: + // CodingKeys_baz.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, C.Id_container); + + 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 + // FIXME: throw EncodingError + continue; + } else if (elt->hasAnyUnnamedParameters()) { + auto *nestedContainerDecl = + createUnkeyedContainer(C, funcDC, C.getUnkeyedEncodingContainerDecl(), + VarDecl::Introducer::Var, + C.getIdentifier(StringRef("nestedContainer"))); + + auto *nestedContainerExpr = new (C) + DeclRefExpr(ConcreteDeclRef(nestedContainerDecl), DeclNameLoc(), + /*Implicit=*/true, AccessSemantics::DirectToStorage); + auto *nestedContainerCall = createNestedUnkeyedContainerForKeyCall( + C, funcDC, containerExpr, nestedContainerDecl->getInterfaceType(), + 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); + + for (auto *payloadVar : payloadVars) { + auto payloadVarRef = new (C) DeclRefExpr(payloadVar, DeclNameLoc(), + /*implicit*/ true); + + // encode(_:) + auto *encodeCall = UnresolvedDotExpr::createImplicit( + C, nestedContainerExpr, C.Id_encode, {Identifier()}); + + // nestedContainer.encode(x) + auto *callExpr = CallExpr::createImplicit( + C, encodeCall, {payloadVarRef}, {Identifier()}); + + // try nestedContainer.encode(x, forKey: CodingKeys.x) + auto *tryExpr = new (C) TryExpr(SourceLoc(), callExpr, Type(), + /*Implicit=*/true); + caseStatements.push_back(tryExpr); + } + } else { + auto caseIdentifier = + combineIdentifiers(C, C.Id_CodingKeys, elt->getBaseIdentifier()); + auto *caseCodingKeys = + lookupEvaluatedCodingKeysEnum(C, enumDecl, caseIdentifier); + + auto *nestedContainerDecl = createKeyedContainer( + C, funcDC, C.getKeyedEncodingContainerDecl(), + caseCodingKeys->getDeclaredInterfaceType(), VarDecl::Introducer::Var, + C.getIdentifier(StringRef("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); + + for (auto *payloadVar : payloadVars) { + auto *nestedContainerExpr = new (C) + DeclRefExpr(ConcreteDeclRef(nestedContainerDecl), DeclNameLoc(), + /*Implicit=*/true, AccessSemantics::DirectToStorage); + auto payloadVarRef = new (C) DeclRefExpr(payloadVar, DeclNameLoc(), + /*implicit*/ true); + + auto *caseCodingKey = + lookupEnumCase(C, caseCodingKeys, payloadVar->getName()); + + // 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; + } + + // CodingKeys_bar.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 the body for `func encode(to encoder: Encoder) throws`. /// /// \param encodeDecl The function decl whose body to synthesize. @@ -433,10 +1017,10 @@ deriveBodyEncodable_encode(AbstractFunctionDecl *encodeDecl, void *) { // let container : KeyedEncodingContainer auto codingKeysType = codingKeysEnum->getDeclaredType(); - auto *containerDecl = createKeyedContainer(C, funcDC, - C.getKeyedEncodingContainerDecl(), - codingKeysEnum->getDeclaredInterfaceType(), - VarDecl::Introducer::Var); + auto *containerDecl = + createKeyedContainer(C, funcDC, C.getKeyedEncodingContainerDecl(), + codingKeysEnum->getDeclaredInterfaceType(), + VarDecl::Introducer::Var, C.Id_container); auto *containerExpr = new (C) DeclRefExpr(ConcreteDeclRef(containerDecl), DeclNameLoc(), /*Implicit=*/true, @@ -479,9 +1063,9 @@ deriveBodyEncodable_encode(AbstractFunctionDecl *encodeDecl, void *) { // self.x auto *selfRef = DerivedConformance::createSelfDeclRef(encodeDecl); - auto *varExpr = new (C) MemberRefExpr(selfRef, SourceLoc(), - ConcreteDeclRef(varDecl), - DeclNameLoc(), /*Implicit=*/true); + auto *varExpr = + new (C) MemberRefExpr(selfRef, SourceLoc(), ConcreteDeclRef(varDecl), + DeclNameLoc(), /*Implicit=*/true); // CodingKeys.x auto *metaTyRef = TypeExpr::createImplicit(codingKeysType, C); @@ -556,6 +1140,7 @@ deriveBodyEncodable_encode(AbstractFunctionDecl *encodeDecl, void *) { static FuncDecl *deriveEncodable_encode(DerivedConformance &derived) { auto &C = derived.Context; auto conformanceDC = derived.getConformanceContext(); + auto *targetDecl = conformanceDC->getSelfNominalTypeDecl(); // Expected type: (Self) -> (Encoder) throws -> () // Constructed as: func type @@ -587,7 +1172,13 @@ static FuncDecl *deriveEncodable_encode(DerivedConformance &derived) { /*Throws=*/true, /*GenericParams=*/nullptr, params, returnType, conformanceDC); encodeDecl->setSynthesized(); - encodeDecl->setBodySynthesizer(deriveBodyEncodable_encode); + + if (auto *enumDecl = dyn_cast(targetDecl)) { + // TODO: differentiate between cases + 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. @@ -605,6 +1196,355 @@ static FuncDecl *deriveEncodable_encode(DerivedConformance &derived) { return encodeDecl; } +/// 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 CodingKeys_bar : CodingKey { + // case x + // } + // + // @derived enum CodingKeys_baz : CodingKey { + // case y + // } + // } + // + // @derived init(from decoder: Decoder) throws { + // let container = try decoder.container(keyedBy: CodingKeys.self) + // switch container.allKeys.first { + // case .bar: + // let nestedContainer = try container.nestedContainer(keyedBy: + // CodingKeys_bar.self, forKey: .bar) let x = try + // nestedContainer.decode(Int.self, forKey: .x) self = .bar(x: x) + // case .baz: + // let nestedContainer = try container.nestedContainer(keyedBy: + // CodingKeys_baz.self, forKey: .baz) let y = try + // nestedContainer.decode(String.self, forKey: .y) self = .baz(y: y) + // default: + // let context = DecodingError.Context( + // codingPath: container.codingPath, + // debugDescription: "Could not find value of type Foo") + // throw DecodingError.valueNotFound(Foo.self, context) + // } + // } + + // 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, C.Id_container); + + 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; + if (!elt->hasAssociatedValues()) { + // Foo.bar + auto *selfTypeExpr = + TypeExpr::createImplicit(targetEnum->getDeclaredType(), C); + 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 if (elt->hasAnyUnnamedParameters()) { + auto *nestedContainerDecl = createUnkeyedContainer( + C, funcDC, C.getUnkeyedDecodingContainerDecl(), + VarDecl::Introducer::Var, + C.getIdentifier(StringRef("nestedContainer"))); + + auto *nestedContainerCall = createNestedUnkeyedContainerForKeyCall( + C, funcDC, containerExpr, nestedContainerDecl->getInterfaceType(), + 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; + for (auto *paramDecl : elt->getParameterList()->getArray()) { + Identifier identifier = getVarNameForCoding(paramDecl); + params.push_back(identifier); + + // Type.self + auto *parameterTypeExpr = + TypeExpr::createImplicit(paramDecl->getType(), C); + auto *parameterMetaTypeExpr = + new (C) DotSelfExpr(parameterTypeExpr, SourceLoc(), SourceLoc()); + auto *nestedContainerExpr = new (C) + DeclRefExpr(ConcreteDeclRef(nestedContainerDecl), DeclNameLoc(), + /*Implicit=*/true, AccessSemantics::DirectToStorage); + // decode(_:) + auto *decodeCall = UnresolvedDotExpr::createImplicit( + C, nestedContainerExpr, C.Id_decode, {Identifier()}); + + // nestedContainer.decode(Type.self) + auto *callExpr = CallExpr::createImplicit( + C, decodeCall, {parameterMetaTypeExpr}, {Identifier()}); + + // try nestedContainer.decode(Type.self) + 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); + + // Foo.bar(x:) + auto *selfCaseExpr = UnresolvedDotExpr::createImplicit( + C, selfTypeExpr, elt->getBaseIdentifier(), C.AllocateCopy(params)); + + // Foo.bar(x: try nestedContainer.decode(Int.self)) + 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); + } else { + auto caseIdentifier = + combineIdentifiers(C, C.Id_CodingKeys, elt->getBaseIdentifier()); + auto *caseCodingKeys = + lookupEvaluatedCodingKeysEnum(C, targetEnum, caseIdentifier); + + auto *nestedContainerDecl = + createKeyedContainer(C, funcDC, C.getKeyedDecodingContainerDecl(), + caseCodingKeys->getDeclaredInterfaceType(), + VarDecl::Introducer::Var, + C.getIdentifier(StringRef("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; + for (auto *paramDecl : elt->getParameterList()->getArray()) { + auto *caseCodingKey = lookupEnumCase( + C, caseCodingKeys, paramDecl->getBaseName().getIdentifier()); + + Identifier identifier = getVarNameForCoding(paramDecl); + params.push_back(identifier); + + // If no key is defined for this parameter, use the default value + if (!caseCodingKey) { + // FIXME: Better use DefaultArgumentExpr instead? + // 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(paramDecl->getType(), C); + auto *parameterMetaTypeExpr = + new (C) DotSelfExpr(parameterTypeExpr, SourceLoc(), SourceLoc()); + // CodingKeys_bar.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: CodingKeys_bar.x) + auto *callExpr = CallExpr::createImplicit( + C, decodeCall, {parameterMetaTypeExpr, keyExpr}, + {Identifier(), C.Id_forKey}); + + // try nestedContainer.decode(Type.self, forKey: CodingKeys_bar.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); + + // 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)); + } + + auto *fatalErrorExpr = + new (C) DeclRefExpr(C.getFatalError(), DeclNameLoc(), true); + auto *errorMessage = + new (C) StringLiteralExpr(StringRef("foo"), SourceRange(), true); + auto *fatalErrorCall = CallExpr::createImplicit( + C, fatalErrorExpr, {errorMessage}, {Identifier()}); + + auto labelItem = CaseLabelItem::getDefault(AnyPattern::createImplicit(C)); + auto body = + BraceStmt::create(C, SourceLoc(), ASTNode(fatalErrorCall), SourceLoc()); + cases.push_back(CaseStmt::create(C, CaseParentKind::Switch, SourceLoc(), + labelItem, SourceLoc(), SourceLoc(), body, + /*case body vardecls*/ None)); + + // generate: switch container.allKeys.first { } + + auto *allKeysExpr = + UnresolvedDotExpr::createImplicit(C, containerExpr, C.Id_allKeys); + + 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 the body for `init(from decoder: Decoder) throws`. /// /// \param initDecl The function decl whose body to synthesize. @@ -648,10 +1588,10 @@ deriveBodyDecodable_init(AbstractFunctionDecl *initDecl, void *) { // let container : KeyedDecodingContainer auto codingKeysType = codingKeysEnum->getDeclaredType(); - auto *containerDecl = createKeyedContainer(C, funcDC, - C.getKeyedDecodingContainerDecl(), - codingKeysEnum->getDeclaredInterfaceType(), - VarDecl::Introducer::Let); + auto *containerDecl = + createKeyedContainer(C, funcDC, C.getKeyedDecodingContainerDecl(), + codingKeysEnum->getDeclaredInterfaceType(), + VarDecl::Introducer::Let, C.Id_container); auto *containerExpr = new (C) DeclRefExpr(ConcreteDeclRef(containerDecl), DeclNameLoc(), /*Implicit=*/true, @@ -916,7 +1856,11 @@ static ValueDecl *deriveDecodable_init(DerivedConformance &derived) { /*GenericParams=*/nullptr, conformanceDC); initDecl->setImplicit(); initDecl->setSynthesized(); - initDecl->setBodySynthesizer(&deriveBodyDecodable_init); + if (auto enumDecl = 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()) { @@ -1010,7 +1954,6 @@ static bool canSynthesize(DerivedConformance &derived, ValueDecl *requirement) { if (!validateCodingKeysEnum(derived)) { return false; - } return true; } @@ -1033,7 +1976,6 @@ static bool canDeriveCodable(NominalTypeDecl *NTD, return false; } - return true; } bool DerivedConformance::canDeriveDecodable(NominalTypeDecl *NTD) { @@ -1046,7 +1988,8 @@ 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) && + !isa(Nominal)) return nullptr; if (requirement->getBaseName() != Context.Id_encode) { @@ -1075,7 +2018,8 @@ 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) && + !isa(Nominal)) return nullptr; if (requirement->getBaseName() != DeclBaseName::createConstructor()) { diff --git a/lib/Sema/DerivedConformances.cpp b/lib/Sema/DerivedConformances.cpp index 5c9de4cd246bd..cf1d6a996cb37 100644 --- a/lib/Sema/DerivedConformances.cpp +++ b/lib/Sema/DerivedConformances.cpp @@ -142,6 +142,18 @@ bool DerivedConformance::derivesProtocolConformance(DeclContext *DC, return enumDecl->hasOnlyCasesWithoutAssociatedValues(); } + case KnownDerivableProtocolKind::Encodable: + case KnownDerivableProtocolKind::Decodable: + // FIXME: This is not actually correct. We cannot promise to always + // provide a witness here for all structs and classes. Unfortunately, + // figuring out whether this is actually possible requires much more + // context -- a TypeChecker and the parent decl context at least -- and + // is tightly coupled to the logic within DerivedConformance. This + // unfortunately means that we expect a witness even if one will not be + // produced, which requires DerivedConformance::deriveCodable to output + // its own diagnostics. + return true; + default: return false; } @@ -726,10 +738,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 +760,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 +799,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); From cb888d2ab21602af16c7f268796b9864093466ca Mon Sep 17 00:00:00 2001 From: Dario Rexin Date: Mon, 7 Dec 2020 16:03:59 -0800 Subject: [PATCH 02/11] Incorporate review feedback for enum Codable synthesis --- include/swift/AST/DiagnosticsSema.def | 2 + include/swift/AST/KnownIdentifiers.def | 8 + lib/Sema/DerivedConformanceCodable.cpp | 399 ++++++++++++++++++------- 3 files changed, 297 insertions(+), 112 deletions(-) diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index da3ecf6678d29..f8018de7c33dc 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -2908,6 +2908,8 @@ 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)) 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 f54aad64de137..eefef39f147af 100644 --- a/include/swift/AST/KnownIdentifiers.def +++ b/include/swift/AST/KnownIdentifiers.def @@ -44,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) @@ -67,6 +71,7 @@ IDENTIFIER_(enclosingInstance) IDENTIFIER(Encodable) IDENTIFIER(encode) IDENTIFIER(encodeIfPresent) +IDENTIFIER(encodeNil) IDENTIFIER(Encoder) IDENTIFIER(encoder) IDENTIFIER(enqueue) @@ -89,6 +94,7 @@ IDENTIFIER(initStorage) IDENTIFIER(initialValue) IDENTIFIER(into) IDENTIFIER(intValue) +IDENTIFIER(invalidValue) IDENTIFIER(Key) IDENTIFIER(KeyedDecodingContainer) IDENTIFIER(KeyedEncodingContainer) @@ -138,6 +144,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/lib/Sema/DerivedConformanceCodable.cpp b/lib/Sema/DerivedConformanceCodable.cpp index 62b1858867302..7d7218658cf85 100644 --- a/lib/Sema/DerivedConformanceCodable.cpp +++ b/lib/Sema/DerivedConformanceCodable.cpp @@ -23,6 +23,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,11 +54,12 @@ static Identifier getVarNameForCoding(VarDecl *var) { return var->getName(); } -/// Combine two identifiers separated by an '_' -static Identifier combineIdentifiers(const ASTContext &C, Identifier first, - Identifier second) { - std::string enumIdentifierName = first.str().str() + "_" + second.str().str(); - return C.getIdentifier(StringRef(enumIdentifierName)); +/// Compute the Identifier for the CodingKey of an enum case +static Identifier caseCodingKeyIdentifier(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); } // Create CodingKeys in the parent type always, because both @@ -386,6 +388,16 @@ static EnumElementDecl *lookupEnumCase(ASTContext &C, NominalTypeDecl *target, 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 bool synthesizeCodingKeysEnum_enum(DerivedConformance &derived, EnumDecl *target) { auto &C = derived.Context; @@ -454,9 +466,14 @@ static bool synthesizeCodingKeysEnum_enum(DerivedConformance &derived, codingKeysEnum = enumDecl; } + llvm::SmallSetVector caseNames; for (auto *elementDecl : target->getAllElements()) { - auto enumIdentifier = combineIdentifiers(C, C.Id_CodingKeys, - elementDecl->getBaseIdentifier()); + if (!caseNames.insert(elementDecl->getBaseIdentifier())) { + elementDecl->diagnose(diag::codable_enum_duplicate_case_name_here, + derived.getProtocolType(), target->getDeclaredType(), elementDecl->getBaseIdentifier()); + return false; + } + auto enumIdentifier = caseCodingKeyIdentifier(C, elementDecl); // Only derive if this case exist in the CodingKeys enum auto *codingKeyCase = @@ -664,6 +681,69 @@ static CallExpr *createNestedUnkeyedContainerForKeyCall(ASTContext &C, return CallExpr::createImplicit(C, unboundCall, {keyExpr}, {C.Id_forKey}); } +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. @@ -832,50 +912,93 @@ deriveBodyEncodable_enum_encode(AbstractFunctionDecl *encodeDecl, void *) { if (!codingKeyCase) { // This case should not be encodable, so throw an error if an attempt is // made to encode it - // FIXME: throw EncodingError - continue; + 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 if (elt->hasAnyUnnamedParameters()) { - auto *nestedContainerDecl = - createUnkeyedContainer(C, funcDC, C.getUnkeyedEncodingContainerDecl(), - VarDecl::Introducer::Var, - C.getIdentifier(StringRef("nestedContainer"))); + if (payloadVars.size() == 1) { + for (auto *payloadVar : payloadVars) { + auto payloadVarRef = new(C) DeclRefExpr(payloadVar, DeclNameLoc(), + /*implicit*/ true); + + // encode(_:, forKey:) + auto *encodeCall = UnresolvedDotExpr::createImplicit( + C, containerExpr, C.Id_encode, {Identifier(), C.Id_forKey}); + + // key expr + auto *keyTypeExpr = TypeExpr::createImplicit( + funcDC->mapTypeIntoContext(codingKeysEnum->getDeclaredInterfaceType()), + C); + auto *keyExpr = new (C) MemberRefExpr(keyTypeExpr, SourceLoc(), codingKeyCase, + DeclNameLoc(), /*Implicit=*/true); + + // container.encode(x, forKey: CodingKeys.bar) + auto *callExpr = CallExpr::createImplicit( + C, encodeCall, {payloadVarRef, keyExpr}, {Identifier(), C.Id_forKey}); - auto *nestedContainerExpr = new (C) - DeclRefExpr(ConcreteDeclRef(nestedContainerDecl), DeclNameLoc(), - /*Implicit=*/true, AccessSemantics::DirectToStorage); - auto *nestedContainerCall = createNestedUnkeyedContainerForKeyCall( - C, funcDC, containerExpr, nestedContainerDecl->getInterfaceType(), - codingKeyCase); + // try nestedContainer.encode(x, forKey: CodingKeys.x) + auto *tryExpr = new(C) TryExpr(SourceLoc(), callExpr, Type(), + /*Implicit=*/true); + caseStatements.push_back(tryExpr); + } + } else { + auto *nestedContainerDecl = + createUnkeyedContainer(C, funcDC, C.getUnkeyedEncodingContainerDecl(), + VarDecl::Introducer::Var, + C.getIdentifier(StringRef("nestedContainer"))); - auto *containerPattern = - NamedPattern::createImplicit(C, nestedContainerDecl); - auto *bindingDecl = PatternBindingDecl::createImplicit( - C, StaticSpellingKind::None, containerPattern, nestedContainerCall, - funcDC); + auto *nestedContainerExpr = new(C) + DeclRefExpr(ConcreteDeclRef(nestedContainerDecl), DeclNameLoc(), + /*Implicit=*/true, AccessSemantics::DirectToStorage); + auto *nestedContainerCall = createNestedUnkeyedContainerForKeyCall( + C, funcDC, containerExpr, nestedContainerDecl->getInterfaceType(), + codingKeyCase); - caseStatements.push_back(bindingDecl); - caseStatements.push_back(nestedContainerDecl); + auto *containerPattern = + NamedPattern::createImplicit(C, nestedContainerDecl); + auto *bindingDecl = PatternBindingDecl::createImplicit( + C, StaticSpellingKind::None, containerPattern, nestedContainerCall, + funcDC); - for (auto *payloadVar : payloadVars) { - auto payloadVarRef = new (C) DeclRefExpr(payloadVar, DeclNameLoc(), - /*implicit*/ true); + caseStatements.push_back(bindingDecl); + caseStatements.push_back(nestedContainerDecl); - // encode(_:) - auto *encodeCall = UnresolvedDotExpr::createImplicit( - C, nestedContainerExpr, C.Id_encode, {Identifier()}); + for (auto *payloadVar : payloadVars) { + auto payloadVarRef = new(C) DeclRefExpr(payloadVar, DeclNameLoc(), + /*implicit*/ true); - // nestedContainer.encode(x) - auto *callExpr = CallExpr::createImplicit( - C, encodeCall, {payloadVarRef}, {Identifier()}); + // encode(_:) + auto *encodeCall = UnresolvedDotExpr::createImplicit( + C, nestedContainerExpr, C.Id_encode, {Identifier()}); - // try nestedContainer.encode(x, forKey: CodingKeys.x) - auto *tryExpr = new (C) TryExpr(SourceLoc(), callExpr, Type(), - /*Implicit=*/true); - caseStatements.push_back(tryExpr); + // nestedContainer.encode(x) + auto *callExpr = CallExpr::createImplicit( + C, encodeCall, {payloadVarRef}, {Identifier()}); + + // try nestedContainer.encode(x, forKey: CodingKeys.x) + auto *tryExpr = new(C) TryExpr(SourceLoc(), callExpr, Type(), + /*Implicit=*/true); + caseStatements.push_back(tryExpr); + } } + } else if (!elt->hasAssociatedValues()) { + auto *encodeNilExpr = UnresolvedDotExpr::createImplicit(C, containerExpr, C.Id_encodeNil, {C.Id_forKey}); + auto *metaTyRef = TypeExpr::createImplicit( + funcDC->mapTypeIntoContext(codingKeysEnum->getDeclaredInterfaceType()), + C); + auto *keyExpr = new (C) MemberRefExpr(metaTyRef, SourceLoc(), codingKeyCase, + DeclNameLoc(), /*Implicit=*/true); + auto *callExpr = CallExpr::createImplicit(C, encodeNilExpr, {keyExpr}, {C.Id_forKey}); + auto *tryExpr = new (C) TryExpr(SourceLoc(), callExpr, Type(), + /*Implicit=*/true); + caseStatements.push_back(tryExpr); } else { - auto caseIdentifier = - combineIdentifiers(C, C.Id_CodingKeys, elt->getBaseIdentifier()); + auto caseIdentifier = caseCodingKeyIdentifier(C, elt); auto *caseCodingKeys = lookupEvaluatedCodingKeysEnum(C, enumDecl, caseIdentifier); @@ -1174,7 +1297,6 @@ static FuncDecl *deriveEncodable_encode(DerivedConformance &derived) { encodeDecl->setSynthesized(); if (auto *enumDecl = dyn_cast(targetDecl)) { - // TODO: differentiate between cases encodeDecl->setBodySynthesizer(deriveBodyEncodable_enum_encode); } else { encodeDecl->setBodySynthesizer(deriveBodyEncodable_encode); @@ -1333,79 +1455,112 @@ deriveBodyDecodable_enum_init(AbstractFunctionDecl *initDecl, void *) { caseStatements.push_back(assignExpr); } else if (elt->hasAnyUnnamedParameters()) { - auto *nestedContainerDecl = createUnkeyedContainer( - C, funcDC, C.getUnkeyedDecodingContainerDecl(), - VarDecl::Introducer::Var, - C.getIdentifier(StringRef("nestedContainer"))); - - auto *nestedContainerCall = createNestedUnkeyedContainerForKeyCall( - C, funcDC, containerExpr, nestedContainerDecl->getInterfaceType(), - 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 < Expr * , 3 > decodeCalls; llvm::SmallVector params; - for (auto *paramDecl : elt->getParameterList()->getArray()) { - Identifier identifier = getVarNameForCoding(paramDecl); - params.push_back(identifier); - - // Type.self - auto *parameterTypeExpr = - TypeExpr::createImplicit(paramDecl->getType(), C); - auto *parameterMetaTypeExpr = - new (C) DotSelfExpr(parameterTypeExpr, SourceLoc(), SourceLoc()); - auto *nestedContainerExpr = new (C) - DeclRefExpr(ConcreteDeclRef(nestedContainerDecl), DeclNameLoc(), - /*Implicit=*/true, AccessSemantics::DirectToStorage); - // decode(_:) - auto *decodeCall = UnresolvedDotExpr::createImplicit( - C, nestedContainerExpr, C.Id_decode, {Identifier()}); - - // nestedContainer.decode(Type.self) - auto *callExpr = CallExpr::createImplicit( - C, decodeCall, {parameterMetaTypeExpr}, {Identifier()}); - - // try nestedContainer.decode(Type.self) - auto *tryExpr = new (C) TryExpr(SourceLoc(), callExpr, Type(), - /*Implicit=*/true); - - decodeCalls.push_back(tryExpr); + if (elt->getParameterList()->size() == 1) { + for (auto *paramDecl : elt->getParameterList()->getArray()) { + Identifier identifier = getVarNameForCoding(paramDecl); + params.push_back(identifier); + + // Type.self + auto *parameterTypeExpr = + TypeExpr::createImplicit(paramDecl->getType(), C); + auto *parameterMetaTypeExpr = + new(C) DotSelfExpr(parameterTypeExpr, SourceLoc(), SourceLoc()); + // decode(_:) + auto *decodeCall = UnresolvedDotExpr::createImplicit( + C, containerExpr, C.Id_decode, {Identifier(), C.Id_forKey}); + + // key expr + auto *keyTypeExpr = TypeExpr::createImplicit( + funcDC->mapTypeIntoContext(codingKeysEnum->getDeclaredInterfaceType()), + C); + auto *keyExpr = new (C) MemberRefExpr(keyTypeExpr, SourceLoc(), codingKeyCase, + DeclNameLoc(), /*Implicit=*/true); + + // nestedContainer.decode(Type.self) + auto *callExpr = CallExpr::createImplicit( + C, decodeCall, {parameterMetaTypeExpr, keyExpr}, + {Identifier(), C.Id_forKey}); + + // try nestedContainer.decode(Type.self) + auto *tryExpr = new(C) TryExpr(SourceLoc(), callExpr, Type(), + /*Implicit=*/true); + + decodeCalls.push_back(tryExpr); + } + } else { + auto *nestedContainerDecl = createUnkeyedContainer( + C, funcDC, C.getUnkeyedDecodingContainerDecl(), + VarDecl::Introducer::Var, + C.getIdentifier(StringRef("nestedContainer"))); + + auto *nestedContainerCall = createNestedUnkeyedContainerForKeyCall( + C, funcDC, containerExpr, nestedContainerDecl->getInterfaceType(), + 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); + + for (auto *paramDecl : elt->getParameterList()->getArray()) { + Identifier identifier = getVarNameForCoding(paramDecl); + params.push_back(identifier); + + // Type.self + auto *parameterTypeExpr = + TypeExpr::createImplicit(paramDecl->getType(), C); + auto *parameterMetaTypeExpr = + new(C) DotSelfExpr(parameterTypeExpr, SourceLoc(), SourceLoc()); + auto *nestedContainerExpr = new(C) + DeclRefExpr(ConcreteDeclRef(nestedContainerDecl), DeclNameLoc(), + /*Implicit=*/true, AccessSemantics::DirectToStorage); + // decode(_:) + auto *decodeCall = UnresolvedDotExpr::createImplicit( + C, nestedContainerExpr, C.Id_decode, {Identifier()}); + + // nestedContainer.decode(Type.self) + auto *callExpr = CallExpr::createImplicit( + C, decodeCall, {parameterMetaTypeExpr}, {Identifier()}); + + // try nestedContainer.decode(Type.self) + 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); + TypeExpr::createImplicit(targetEnum->getDeclaredType(), C); // Foo.bar(x:) auto *selfCaseExpr = UnresolvedDotExpr::createImplicit( - C, selfTypeExpr, elt->getBaseIdentifier(), C.AllocateCopy(params)); + C, selfTypeExpr, elt->getBaseIdentifier(), C.AllocateCopy(params)); // Foo.bar(x: try nestedContainer.decode(Int.self)) auto *caseCallExpr = CallExpr::createImplicit( - C, selfCaseExpr, C.AllocateCopy(decodeCalls), - C.AllocateCopy(params)); + 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); + new(C) AssignExpr(selfRef, SourceLoc(), caseCallExpr, + /*Implicit=*/true); caseStatements.push_back(assignExpr); } else { - auto caseIdentifier = - combineIdentifiers(C, C.Id_CodingKeys, elt->getBaseIdentifier()); + auto caseIdentifier = caseCodingKeyIdentifier(C, elt); auto *caseCodingKeys = lookupEvaluatedCodingKeysEnum(C, targetEnum, caseIdentifier); @@ -1440,7 +1595,6 @@ deriveBodyDecodable_enum_init(AbstractFunctionDecl *initDecl, void *) { // If no key is defined for this parameter, use the default value if (!caseCodingKey) { - // FIXME: Better use DefaultArgumentExpr instead? // This should have been verified to have a default expr in the // CodingKey synthesis assert(paramDecl->hasDefaultExpr()); @@ -1511,25 +1665,46 @@ deriveBodyDecodable_enum_init(AbstractFunctionDecl *initDecl, void *) { /*case body vardecls*/ None)); } - auto *fatalErrorExpr = - new (C) DeclRefExpr(C.getFatalError(), DeclNameLoc(), true); - auto *errorMessage = - new (C) StringLiteralExpr(StringRef("foo"), SourceRange(), true); - auto *fatalErrorCall = CallExpr::createImplicit( - C, fatalErrorExpr, {errorMessage}, {Identifier()}); + // 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); - auto labelItem = CaseLabelItem::getDefault(AnyPattern::createImplicit(C)); - auto body = - BraceStmt::create(C, SourceLoc(), ASTNode(fatalErrorCall), SourceLoc()); - cases.push_back(CaseStmt::create(C, CaseParentKind::Switch, SourceLoc(), - labelItem, SourceLoc(), SourceLoc(), body, - /*case body vardecls*/ None)); + // container.allKeys.count + auto *keysCountExpr = UnresolvedDotExpr::createImplicit(C, allKeysExpr, C.Id_count); - // generate: switch container.allKeys.first { } + // 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 *allKeysExpr = - UnresolvedDotExpr::createImplicit(C, containerExpr, C.Id_allKeys); + 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); From 3065fead516f7b20a451090848e74bdff5fa9814 Mon Sep 17 00:00:00 2001 From: Dario Rexin Date: Mon, 14 Dec 2020 17:23:28 -0800 Subject: [PATCH 03/11] Implement enum specific versions of existing Codable tests --- lib/Sema/DerivedConformanceCodable.cpp | 86 ++- test/IRGen/synthesized_conformance.swift | 4 + .../synthesized_conformance_future.swift | 23 + .../SILGen/synthesized_conformance_enum.swift | 31 + .../Inputs/enum_codable_simple_multi1.swift | 27 + .../Inputs/enum_codable_simple_multi2.swift | 12 + .../enum_codable_codingkeys_typealias.swift | 35 + ...codable_excluded_optional_properties.swift | 28 + .../enum_codable_failure_diagnostics.swift | 107 +++ ...odable_ignore_nonconforming_property.swift | 26 + .../enum_codable_invalid_codingkeys.swift | 37 + .../enum_codable_member_type_lookup.swift | 653 ++++++++++++++++++ .../enum_codable_nonconforming_property.swift | 166 +++++ .../special/coding/enum_codable_simple.swift | 36 + .../enum_codable_simple_conditional.swift | 42 ++ ..._codable_simple_conditional_separate.swift | 45 ++ .../enum_codable_simple_extension.swift | 38 + ...num_codable_simple_extension_flipped.swift | 38 + .../coding/enum_codable_simple_multi.swift | 2 + 19 files changed, 1390 insertions(+), 46 deletions(-) create mode 100644 test/decl/protocol/special/coding/Inputs/enum_codable_simple_multi1.swift create mode 100644 test/decl/protocol/special/coding/Inputs/enum_codable_simple_multi2.swift create mode 100644 test/decl/protocol/special/coding/enum_codable_codingkeys_typealias.swift create mode 100644 test/decl/protocol/special/coding/enum_codable_excluded_optional_properties.swift create mode 100644 test/decl/protocol/special/coding/enum_codable_failure_diagnostics.swift create mode 100644 test/decl/protocol/special/coding/enum_codable_ignore_nonconforming_property.swift create mode 100644 test/decl/protocol/special/coding/enum_codable_invalid_codingkeys.swift create mode 100644 test/decl/protocol/special/coding/enum_codable_member_type_lookup.swift create mode 100644 test/decl/protocol/special/coding/enum_codable_nonconforming_property.swift create mode 100644 test/decl/protocol/special/coding/enum_codable_simple.swift create mode 100644 test/decl/protocol/special/coding/enum_codable_simple_conditional.swift create mode 100644 test/decl/protocol/special/coding/enum_codable_simple_conditional_separate.swift create mode 100644 test/decl/protocol/special/coding/enum_codable_simple_extension.swift create mode 100644 test/decl/protocol/special/coding/enum_codable_simple_extension_flipped.swift create mode 100644 test/decl/protocol/special/coding/enum_codable_simple_multi.swift diff --git a/lib/Sema/DerivedConformanceCodable.cpp b/lib/Sema/DerivedConformanceCodable.cpp index 7d7218658cf85..7500830885a92 100644 --- a/lib/Sema/DerivedConformanceCodable.cpp +++ b/lib/Sema/DerivedConformanceCodable.cpp @@ -411,37 +411,6 @@ static bool synthesizeCodingKeysEnum_enum(DerivedConformance &derived, bool allConform = true; auto *conformanceDC = derived.getConformanceContext(); - - auto add = [conformanceDC, &C, &derived](VarDecl *varDecl, - EnumDecl *enumDecl) { - if (!varDecl->isUserAccessible()) { - return true; - } - - auto target = - conformanceDC->mapTypeIntoContext(varDecl->getValueInterfaceType()); - if (TypeChecker::conformsToProtocol(target, derived.Protocol, conformanceDC) - .isInvalid()) { - TypeLoc typeLoc = { - varDecl->getTypeReprOrParentPatternTypeRepr(), - varDecl->getType(), - }; - varDecl->diagnose(diag::codable_non_conforming_property_here, - derived.getProtocolType(), typeLoc); - - return false; - } else { - // if the type conforms to {En,De}codable, add it to the enum. - auto *elt = - new (C) EnumElementDecl(SourceLoc(), getVarNameForCoding(varDecl), - nullptr, SourceLoc(), nullptr, enumDecl); - elt->setImplicit(); - enumDecl->addMember(elt); - - return true; - } - }; - auto *codingKeysEnum = lookupEvaluatedCodingKeysEnum(C, target); // Only derive CodingKeys enum if it is not already defined @@ -485,27 +454,49 @@ static bool synthesizeCodingKeysEnum_enum(DerivedConformance &derived, if (!derived.Nominal->lookupDirect(DeclName(enumIdentifier)).empty()) continue; - // If there are any unnamed parameters, we can't generate CodingKeys for - // this element and it will be encoded into an unkeyed container. - if (elementDecl->hasAnyUnnamedParameters()) - continue; - - auto *nestedEnum = new (C) EnumDecl( + auto *caseEnum = new (C) EnumDecl( SourceLoc(), enumIdentifier, SourceLoc(), inherited, nullptr, target); - nestedEnum->setImplicit(); - nestedEnum->setAccess(AccessLevel::Private); + caseEnum->setImplicit(); + caseEnum->setAccess(AccessLevel::Private); auto *elementParams = elementDecl->getParameterList(); + bool conforms = true; if (elementParams) { for (auto *paramDecl : elementParams->getArray()) { - allConform = allConform && add(paramDecl, nestedEnum); + auto target = + conformanceDC->mapTypeIntoContext(paramDecl->getValueInterfaceType()); + if (TypeChecker::conformsToProtocol(target, derived.Protocol, conformanceDC) + .isInvalid()) { + TypeLoc typeLoc = { + paramDecl->getTypeReprOrParentPatternTypeRepr(), + paramDecl->getType(), + }; + paramDecl->diagnose(diag::codable_non_conforming_property_here, + derived.getProtocolType(), typeLoc); + + conforms = false; + } else { + // if the type conforms to {En,De}codable, add it to the enum. + auto *elt = + new (C) EnumElementDecl(SourceLoc(), getVarNameForCoding(paramDecl), + nullptr, SourceLoc(), nullptr, caseEnum); + elt->setImplicit(); + caseEnum->addMember(elt); + } } + allConform = allConform && conforms; } - // Forcibly derive conformance to CodingKey. - TypeChecker::checkConformancesInContext(nestedEnum); + // If there are any unnamed parameters, we can't generate CodingKeys for + // this element and it will be encoded into an unkeyed container. + if (elementDecl->hasAnyUnnamedParameters()) + continue; - target->addMember(nestedEnum); + if (conforms) { + // Forcibly derive conformance to CodingKey. + TypeChecker::checkConformancesInContext(caseEnum); + target->addMember(caseEnum); + } } if (!allConform) @@ -1464,7 +1455,8 @@ deriveBodyDecodable_enum_init(AbstractFunctionDecl *initDecl, void *) { // Type.self auto *parameterTypeExpr = - TypeExpr::createImplicit(paramDecl->getType(), C); + TypeExpr::createImplicit( + funcDC->mapTypeIntoContext(paramDecl->getInterfaceType()), C); auto *parameterMetaTypeExpr = new(C) DotSelfExpr(parameterTypeExpr, SourceLoc(), SourceLoc()); // decode(_:) @@ -1516,7 +1508,8 @@ deriveBodyDecodable_enum_init(AbstractFunctionDecl *initDecl, void *) { // Type.self auto *parameterTypeExpr = - TypeExpr::createImplicit(paramDecl->getType(), C); + TypeExpr::createImplicit( + funcDC->mapTypeIntoContext(paramDecl->getInterfaceType()), C); auto *parameterMetaTypeExpr = new(C) DotSelfExpr(parameterTypeExpr, SourceLoc(), SourceLoc()); auto *nestedContainerExpr = new(C) @@ -1604,7 +1597,8 @@ deriveBodyDecodable_enum_init(AbstractFunctionDecl *initDecl, void *) { // Type.self auto *parameterTypeExpr = - TypeExpr::createImplicit(paramDecl->getType(), C); + TypeExpr::createImplicit( + funcDC->mapTypeIntoContext(paramDecl->getInterfaceType()), C); auto *parameterMetaTypeExpr = new (C) DotSelfExpr(parameterTypeExpr, SourceLoc(), SourceLoc()); // CodingKeys_bar.x diff --git a/test/IRGen/synthesized_conformance.swift b/test/IRGen/synthesized_conformance.swift index 750303b705cae..d0ee05c50b42e 100644 --- a/test/IRGen/synthesized_conformance.swift +++ b/test/IRGen/synthesized_conformance.swift @@ -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..96fc3c79fb196 100644 --- a/test/IRGen/synthesized_conformance_future.swift +++ b/test/IRGen/synthesized_conformance_future.swift @@ -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..83367d001e297 100644 --- a/test/SILGen/synthesized_conformance_enum.swift +++ b/test/SILGen/synthesized_conformance_enum.swift @@ -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_codingkeys_typealias.swift b/test/decl/protocol/special/coding/enum_codable_codingkeys_typealias.swift new file mode 100644 index 0000000000000..17b53610f3fe6 --- /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 + +// 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_excluded_optional_properties.swift b/test/decl/protocol/special/coding/enum_codable_excluded_optional_properties.swift new file mode 100644 index 0000000000000..d308bc7306278 --- /dev/null +++ b/test/decl/protocol/special/coding/enum_codable_excluded_optional_properties.swift @@ -0,0 +1,28 @@ +// RUN: %target-typecheck-verify-swift + +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..6a4435c742cfb --- /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 + +// 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..6b1a98d15ea38 --- /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 + +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..bcd9ffcf14220 --- /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 + +// 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..850584bf08628 --- /dev/null +++ b/test/decl/protocol/special/coding/enum_codable_member_type_lookup.swift @@ -0,0 +1,653 @@ +// RUN: %target-typecheck-verify-swift -verify-ignore-unknown + +// A top-level CodingKeys type to fall back to in lookups below. +public enum CodingKeys : String, CodingKey { + case topLevel +} + +// MARK: - Synthesized CodingKeys Enum + +// Enums which get synthesized Codable implementations should have visible +// CodingKey enums during member type lookup. +enum SynthesizedEnum : Codable { + case value + + // Qualified type lookup should always be unambiguous. + public func qualifiedFoo(_ key: SynthesizedEnum.CodingKeys) {} // expected-error {{method cannot be declared public because its parameter uses a private type}} + internal func qualifiedBar(_ key: SynthesizedEnum.CodingKeys) {} // expected-error {{method cannot be declared internal because its parameter uses a private type}} + fileprivate func qualfiedBaz(_ key: SynthesizedEnum.CodingKeys) {} // expected-error {{method cannot be declared fileprivate because its parameter uses a private type}} + private func qualifiedQux(_ key: SynthesizedEnum.CodingKeys) {} + + // Unqualified lookups should find the synthesized CodingKeys type instead + // of the top-level type above. + public func unqualifiedFoo(_ key: CodingKeys) { // expected-error {{method cannot be declared public because its parameter uses a private type}} + print(CodingKeys.value) // Not found on top-level. + } + + internal func unqualifiedBar(_ key: CodingKeys) { // expected-error {{method cannot be declared internal because its parameter uses a private type}} + print(CodingKeys.value) // Not found on top-level. + } + + fileprivate func unqualifiedBaz(_ key: CodingKeys) { // expected-error {{method cannot be declared fileprivate because its parameter uses a private type}} + print(CodingKeys.value) // Not found on top-level. + } + + private func unqualifiedQux(_ key: CodingKeys) { + print(CodingKeys.value) // Not found on top-level. + } + + // Unqualified lookups should find the most local CodingKeys type available. + public func nestedUnqualifiedFoo(_ key: CodingKeys) { // expected-error {{method cannot be declared public because its parameter uses a private type}} + enum CodingKeys : String, CodingKey { + case nested + } + + // CodingKeys should refer to the local unqualified enum. + func foo(_ key: CodingKeys) { + print(CodingKeys.nested) // Not found on synthesized type or top-level type. + } + + foo(CodingKeys.nested) + } + + internal func nestedUnqualifiedBar(_ key: CodingKeys) { // expected-error {{method cannot be declared internal because its parameter uses a private type}} + enum CodingKeys : String, CodingKey { + case nested + } + + // CodingKeys should refer to the local unqualified enum. + func bar(_ key: CodingKeys) { + print(CodingKeys.nested) // Not found on synthesized type or top-level type. + } + + bar(CodingKeys.nested) + } + + fileprivate func nestedUnqualifiedBaz(_ key: CodingKeys) { // expected-error {{method cannot be declared fileprivate because its parameter uses a private type}} + enum CodingKeys : String, CodingKey { + case nested + } + + // CodingKeys should refer to the local unqualified enum. + func baz(_ key: CodingKeys) { + print(CodingKeys.nested) // Not found on synthesized type or top-level type. + } + + baz(CodingKeys.nested) + } + + private func nestedUnqualifiedQux(_ key: CodingKeys) { + enum CodingKeys : String, CodingKey { + case nested + } + + // CodingKeys should refer to the local unqualified enum. + func qux(_ key: CodingKeys) { + print(CodingKeys.nested) // Not found on synthesized type or top-level type. + } + + qux(CodingKeys.nested) + } + + // Lookup within nested types should look outside of the type. + struct Nested { + // Qualified lookup should remain as-is. + public func qualifiedFoo(_ key: SynthesizedEnum.CodingKeys) {} // expected-error {{method cannot be declared public because its parameter uses a private type}} + internal func qualifiedBar(_ key: SynthesizedEnum.CodingKeys) {} // expected-error {{method cannot be declared internal because its parameter uses a private type}} + fileprivate func qualfiedBaz(_ key: SynthesizedEnum.CodingKeys) {} // expected-error {{method cannot be declared fileprivate because its parameter uses a private type}} + private func qualifiedQux(_ key: SynthesizedEnum.CodingKeys) {} + + // Unqualified lookups should find the SynthesizedEnum's synthesized + // CodingKeys type instead of the top-level type above. + public func unqualifiedFoo(_ key: CodingKeys) { // expected-error {{method cannot be declared public because its parameter uses a private type}} + print(CodingKeys.value) // Not found on top-level. + } + + internal func unqualifiedBar(_ key: CodingKeys) { // expected-error {{method cannot be declared internal because its parameter uses a private type}} + print(CodingKeys.value) // Not found on top-level. + } + + fileprivate func unqualifiedBaz(_ key: CodingKeys) { // expected-error {{method cannot be declared fileprivate because its parameter uses a private type}} + print(CodingKeys.value) // Not found on top-level. + } + + private func unqualifiedQux(_ key: CodingKeys) { + print(CodingKeys.value) // Not found on top-level. + } + + // Unqualified lookups should find the most local CodingKeys type available. + public func nestedUnqualifiedFoo(_ key: CodingKeys) { // expected-error {{method cannot be declared public because its parameter uses a private type}} + enum CodingKeys : String, CodingKey { + case nested + } + + // CodingKeys should refer to the local unqualified enum. + func foo(_ key: CodingKeys) { + print(CodingKeys.nested) // Not found on synthesized type or top-level type. + } + + foo(CodingKeys.nested) + } + + internal func nestedUnqualifiedBar(_ key: CodingKeys) { // expected-error {{method cannot be declared internal because its parameter uses a private type}} + enum CodingKeys : String, CodingKey { + case nested + } + + // CodingKeys should refer to the local unqualified enum. + func bar(_ key: CodingKeys) { + print(CodingKeys.nested) // Not found on synthesized type or top-level type. + } + + bar(CodingKeys.nested) + } + + fileprivate func nestedUnqualifiedBaz(_ key: CodingKeys) { // expected-error {{method cannot be declared fileprivate because its parameter uses a private type}} + enum CodingKeys : String, CodingKey { + case nested + } + + // CodingKeys should refer to the local unqualified enum. + func baz(_ key: CodingKeys) { + print(CodingKeys.nested) // Not found on synthesized type or top-level type. + } + + baz(CodingKeys.nested) + } + + private func nestedUnqualifiedQux(_ key: CodingKeys) { + enum CodingKeys : String, CodingKey { + case nested + } + + // CodingKeys should refer to the local unqualified enum. + func qux(_ key: CodingKeys) { + print(CodingKeys.nested) // Not found on synthesized type or top-level type. + } + + qux(CodingKeys.nested) + } + } +} + +// MARK: - No CodingKeys Enum + +// Enums which don't get synthesized Codable implementations should expose the +// appropriate CodingKeys type. +enum NonSynthesizedEnum : Codable { // expected-note 4 {{'NonSynthesizedEnum' declared here}} + case value + + // No synthesized type since we implemented both methods. + init(from decoder: Decoder) throws {} + func encode(to encoder: Encoder) throws {} + + // Qualified type lookup should clearly fail -- we shouldn't get a synthesized + // type here. + public func qualifiedFoo(_ key: NonSynthesizedEnum.CodingKeys) {} // expected-error {{'CodingKeys' is not a member type of enum 'enum_codable_member_type_lookup.NonSynthesizedEnum'}} + internal func qualifiedBar(_ key: NonSynthesizedEnum.CodingKeys) {} // expected-error {{'CodingKeys' is not a member type of enum 'enum_codable_member_type_lookup.NonSynthesizedEnum'}} + fileprivate func qualfiedBaz(_ key: NonSynthesizedEnum.CodingKeys) {} // expected-error {{'CodingKeys' is not a member type of enum 'enum_codable_member_type_lookup.NonSynthesizedEnum'}} + private func qualifiedQux(_ key: NonSynthesizedEnum.CodingKeys) {} // expected-error {{'CodingKeys' is not a member type of enum 'enum_codable_member_type_lookup.NonSynthesizedEnum'}} + + // Unqualified lookups should find the public top-level CodingKeys type. + public func unqualifiedFoo(_ key: CodingKeys) { print(CodingKeys.topLevel) } + internal func unqualifiedBar(_ key: CodingKeys) { print(CodingKeys.topLevel) } + fileprivate func unqualifiedBaz(_ key: CodingKeys) { print(CodingKeys.topLevel) } + private func unqualifiedQux(_ key: CodingKeys) { print(CodingKeys.topLevel) } + + // Unqualified lookups should find the most local CodingKeys type available. + public func nestedUnqualifiedFoo(_ key: CodingKeys) { + enum CodingKeys : String, CodingKey { + case nested + } + + // CodingKeys should refer to the local unqualified enum. + func foo(_ key: CodingKeys) { + print(CodingKeys.nested) // Not found on top-level type. + } + + foo(CodingKeys.nested) + } + + internal func nestedUnqualifiedBar(_ key: CodingKeys) { + enum CodingKeys : String, CodingKey { + case nested + } + + // CodingKeys should refer to the local unqualified enum. + func bar(_ key: CodingKeys) { + print(CodingKeys.nested) // Not found on top-level type. + } + + bar(CodingKeys.nested) + } + + fileprivate func nestedUnqualifiedBaz(_ key: CodingKeys) { + enum CodingKeys : String, CodingKey { + case nested + } + + // CodingKeys should refer to the local unqualified enum. + func baz(_ key: CodingKeys) { + print(CodingKeys.nested) // Not found on top-level type. + } + + baz(CodingKeys.nested) + } + + private func nestedUnqualifiedQux(_ key: CodingKeys) { + enum CodingKeys : String, CodingKey { + case nested + } + + // CodingKeys should refer to the local unqualified enum. + func qux(_ key: CodingKeys) { + print(CodingKeys.nested) // Not found on top-level type. + } + + qux(CodingKeys.nested) + } +} + +// MARK: - Explicit CodingKeys Enum + +// Enums which explicitly define their own CodingKeys types should have +// visible CodingKey enums during member type lookup. +enum ExplicitStruct : Codable { + case value + + public enum CodingKeys { + case a + case b + case c + } + + init(from decoder: Decoder) throws {} + func encode(to encoder: Encoder) throws {} + + // Qualified type lookup should always be unambiguous. + public func qualifiedFoo(_ key: ExplicitStruct.CodingKeys) {} + internal func qualifiedBar(_ key: ExplicitStruct.CodingKeys) {} + fileprivate func qualfiedBaz(_ key: ExplicitStruct.CodingKeys) {} + private func qualifiedQux(_ key: ExplicitStruct.CodingKeys) {} + + // Unqualified lookups should find the synthesized CodingKeys type instead + // of the top-level type above. + public func unqualifiedFoo(_ key: CodingKeys) { + print(CodingKeys.a) // Not found on top-level. + } + + internal func unqualifiedBar(_ key: CodingKeys) { + print(CodingKeys.a) // Not found on top-level. + } + + fileprivate func unqualifiedBaz(_ key: CodingKeys) { + print(CodingKeys.a) // Not found on top-level. + } + + private func unqualifiedQux(_ key: CodingKeys) { + print(CodingKeys.a) // Not found on top-level. + } + + // Unqualified lookups should find the most local CodingKeys type available. + public func nestedUnqualifiedFoo(_ key: CodingKeys) { + enum CodingKeys : String, CodingKey { + case nested + } + + // CodingKeys should refer to the local unqualified enum. + func foo(_ key: CodingKeys) { + print(CodingKeys.nested) // Not found on synthesized type or top-level type. + } + + foo(CodingKeys.nested) + } + + internal func nestedUnqualifiedBar(_ key: CodingKeys) { + enum CodingKeys : String, CodingKey { + case nested + } + + // CodingKeys should refer to the local unqualified enum. + func bar(_ key: CodingKeys) { + print(CodingKeys.nested) // Not found on synthesized type or top-level type. + } + + bar(CodingKeys.nested) + } + + fileprivate func nestedUnqualifiedBaz(_ key: CodingKeys) { + enum CodingKeys : String, CodingKey { + case nested + } + + // CodingKeys should refer to the local unqualified enum. + func baz(_ key: CodingKeys) { + print(CodingKeys.nested) // Not found on synthesized type or top-level type. + } + + baz(CodingKeys.nested) + } + + private func nestedUnqualifiedQux(_ key: CodingKeys) { + enum CodingKeys : String, CodingKey { + case nested + } + + // CodingKeys should refer to the local unqualified enum. + func qux(_ key: CodingKeys) { + print(CodingKeys.nested) // Not found on synthesized type or top-level type. + } + + qux(CodingKeys.nested) + } + + // Lookup within nested types should look outside of the type. + struct Nested { + // Qualified lookup should remain as-is. + public func qualifiedFoo(_ key: ExplicitStruct.CodingKeys) {} + internal func qualifiedBar(_ key: ExplicitStruct.CodingKeys) {} + fileprivate func qualfiedBaz(_ key: ExplicitStruct.CodingKeys) {} + private func qualifiedQux(_ key: ExplicitStruct.CodingKeys) {} + + // Unqualified lookups should find the ExplicitStruct's synthesized + // CodingKeys type instead of the top-level type above. + public func unqualifiedFoo(_ key: CodingKeys) { + print(CodingKeys.a) // Not found on top-level. + } + + internal func unqualifiedBar(_ key: CodingKeys) { + print(CodingKeys.a) // Not found on top-level. + } + + fileprivate func unqualifiedBaz(_ key: CodingKeys) { + print(CodingKeys.a) // Not found on top-level. + } + + private func unqualifiedQux(_ key: CodingKeys) { + print(CodingKeys.a) // Not found on top-level. + } + + // Unqualified lookups should find the most local CodingKeys type available. + public func nestedUnqualifiedFoo(_ key: CodingKeys) { + enum CodingKeys : String, CodingKey { + case nested + } + + // CodingKeys should refer to the local unqualified enum. + func foo(_ key: CodingKeys) { + print(CodingKeys.nested) // Not found on synthesized type or top-level type. + } + + foo(CodingKeys.nested) + } + + internal func nestedUnqualifiedBar(_ key: CodingKeys) { + enum CodingKeys : String, CodingKey { + case nested + } + + // CodingKeys should refer to the local unqualified enum. + func bar(_ key: CodingKeys) { + print(CodingKeys.nested) // Not found on synthesized type or top-level type. + } + + bar(CodingKeys.nested) + } + + fileprivate func nestedUnqualifiedBaz(_ key: CodingKeys) { + enum CodingKeys : String, CodingKey { + case nested + } + + // CodingKeys should refer to the local unqualified enum. + func baz(_ key: CodingKeys) { + print(CodingKeys.nested) // Not found on synthesized type or top-level type. + } + + baz(CodingKeys.nested) + } + + private func nestedUnqualifiedQux(_ key: CodingKeys) { + enum CodingKeys : String, CodingKey { + case nested + } + + // CodingKeys should refer to the local unqualified enum. + func qux(_ key: CodingKeys) { + print(CodingKeys.nested) // Not found on synthesized type or top-level type. + } + + qux(CodingKeys.nested) + } + } +} + +// MARK: - CodingKeys Enums in Extensions + +// Enumss which get a CodingKeys type in an extension should be able to see +// that type during member type lookup. +enum ExtendedEnum : Codable { + case value + + // Don't get an auto-synthesized type. + init(from decoder: Decoder) throws {} + func encode(to encoder: Encoder) throws {} + + // Qualified type lookup should always be unambiguous. + public func qualifiedFoo(_ key: ExtendedEnum.CodingKeys) {} + internal func qualifiedBar(_ key: ExtendedEnum.CodingKeys) {} + fileprivate func qualfiedBaz(_ key: ExtendedEnum.CodingKeys) {} + private func qualifiedQux(_ key: ExtendedEnum.CodingKeys) {} + + // Unqualified lookups should find the synthesized CodingKeys type instead + // of the top-level type above. + public func unqualifiedFoo(_ key: CodingKeys) { + print(CodingKeys.a) // Not found on top-level. + } + + internal func unqualifiedBar(_ key: CodingKeys) { + print(CodingKeys.a) // Not found on top-level. + } + + fileprivate func unqualifiedBaz(_ key: CodingKeys) { + print(CodingKeys.a) // Not found on top-level. + } + + private func unqualifiedQux(_ key: CodingKeys) { + print(CodingKeys.a) // Not found on top-level. + } + + // Unqualified lookups should find the most local CodingKeys type available. + public func nestedUnqualifiedFoo(_ key: CodingKeys) { + enum CodingKeys : String, CodingKey { + case nested + } + + // CodingKeys should refer to the local unqualified enum. + func foo(_ key: CodingKeys) { + print(CodingKeys.nested) // Not found on synthesized type or top-level type. + } + + foo(CodingKeys.nested) + } + + internal func nestedUnqualifiedBar(_ key: CodingKeys) { + enum CodingKeys : String, CodingKey { + case nested + } + + // CodingKeys should refer to the local unqualified enum. + func bar(_ key: CodingKeys) { + print(CodingKeys.nested) // Not found on synthesized type or top-level type. + } + + bar(CodingKeys.nested) + } + + fileprivate func nestedUnqualifiedBaz(_ key: CodingKeys) { + enum CodingKeys : String, CodingKey { + case nested + } + + // CodingKeys should refer to the local unqualified enum. + func baz(_ key: CodingKeys) { + print(CodingKeys.nested) // Not found on synthesized type or top-level type. + } + + baz(CodingKeys.nested) + } + + private func nestedUnqualifiedQux(_ key: CodingKeys) { + enum CodingKeys : String, CodingKey { + case nested + } + + // CodingKeys should refer to the local unqualified enum. + func qux(_ key: CodingKeys) { + print(CodingKeys.nested) // Not found on synthesized type or top-level type. + } + + qux(CodingKeys.nested) + } + + // Lookup within nested types should look outside of the type. + struct Nested { + // Qualified lookup should remain as-is. + public func qualifiedFoo(_ key: ExtendedEnum.CodingKeys) {} + internal func qualifiedBar(_ key: ExtendedEnum.CodingKeys) {} + fileprivate func qualfiedBaz(_ key: ExtendedEnum.CodingKeys) {} + private func qualifiedQux(_ key: ExtendedEnum.CodingKeys) {} + + // Unqualified lookups should find the ExtendedEnum's synthesized + // CodingKeys type instead of the top-level type above. + public func unqualifiedFoo(_ key: CodingKeys) { + print(CodingKeys.a) // Not found on top-level. + } + + internal func unqualifiedBar(_ key: CodingKeys) { + print(CodingKeys.a) // Not found on top-level. + } + + fileprivate func unqualifiedBaz(_ key: CodingKeys) { + print(CodingKeys.a) // Not found on top-level. + } + + private func unqualifiedQux(_ key: CodingKeys) { + print(CodingKeys.a) // Not found on top-level. + } + + // Unqualified lookups should find the most local CodingKeys type available. + public func nestedUnqualifiedFoo(_ key: CodingKeys) { + enum CodingKeys : String, CodingKey { + case nested + } + + // CodingKeys should refer to the local unqualified enum. + func foo(_ key: CodingKeys) { + print(CodingKeys.nested) // Not found on synthesized type or top-level type. + } + + foo(CodingKeys.nested) + } + + internal func nestedUnqualifiedBar(_ key: CodingKeys) { + enum CodingKeys : String, CodingKey { + case nested + } + + // CodingKeys should refer to the local unqualified enum. + func bar(_ key: CodingKeys) { + print(CodingKeys.nested) // Not found on synthesized type or top-level type. + } + + bar(CodingKeys.nested) + } + + fileprivate func nestedUnqualifiedBaz(_ key: CodingKeys) { + enum CodingKeys : String, CodingKey { + case nested + } + + // CodingKeys should refer to the local unqualified enum. + func baz(_ key: CodingKeys) { + print(CodingKeys.nested) // Not found on synthesized type or top-level type. + } + + baz(CodingKeys.nested) + } + + private func nestedUnqualifiedQux(_ key: CodingKeys) { + enum CodingKeys : String, CodingKey { + case nested + } + + // CodingKeys should refer to the local unqualified enum. + func qux(_ key: CodingKeys) { + print(CodingKeys.nested) // Not found on synthesized type or top-level type. + } + + qux(CodingKeys.nested) + } + } +} + +extension ExtendedEnum { + enum CodingKeys : String, CodingKey { + case a, b, c + } +} + +struct A { + enum Inner : Codable { + case value + + func foo() { + print(CodingKeys.value) // Not found on A.CodingKeys or top-level type. + } + } +} + +extension A { + enum CodingKeys : String, CodingKey { + case a + } +} + +enum B : Codable { + // So B conforms to Codable using CodingKeys.b below. + case b + + enum Inner { + case value + + func foo() { + print(CodingKeys.b) // Not found on top-level type. + } + } +} + +extension B { + enum CodingKeys : String, CodingKey { + case b + } +} + +enum C : Codable { + enum Inner : Codable { + case value + + func foo() { + print(CodingKeys.value) // Not found on C.CodingKeys or top-level type. + } + } +} + +extension C.Inner { + enum CodingKeys : String, CodingKey { + case value + } +} + +enum GenericCodableEnum : Codable {} + +func foo(_: GenericCodableEnum.CodingKeys) // expected-error {{'CodingKeys' is inaccessible due to 'private' protection level}} 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..e35fdf920a8e3 --- /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 + +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 {{type 'NonConformingEnum' has no member 'XCodingKeys'}} 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..fe8fe88ba1008 --- /dev/null +++ b/test/decl/protocol/special/coding/enum_codable_simple.swift @@ -0,0 +1,36 @@ +// RUN: %target-typecheck-verify-swift -verify-ignore-unknown + +// 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) + + // 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 + } +} + +// 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}} 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..bb8af6d96e4a0 --- /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 + +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..04ea2fe53d127 --- /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 + +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..66d4d3ae82a2d --- /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 + +// 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..2c6fcb0793a39 --- /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 + +// 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..457c41d332018 --- /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 %S/Inputs/enum_codable_simple_multi1.swift %S/Inputs/enum_codable_simple_multi2.swift +// RUN: %target-typecheck-verify-swift -verify-ignore-unknown %S/Inputs/enum_codable_simple_multi2.swift %S/Inputs/enum_codable_simple_multi1.swift From 2e8df15dcd0500d019176b1bc1ad5f573bfa9e58 Mon Sep 17 00:00:00 2001 From: Dario Rexin Date: Tue, 19 Jan 2021 23:29:30 -0800 Subject: [PATCH 04/11] Encode parameterless enum cases as --- include/swift/AST/KnownIdentifiers.def | 1 - lib/Sema/DerivedConformanceCodable.cpp | 6 ++++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/include/swift/AST/KnownIdentifiers.def b/include/swift/AST/KnownIdentifiers.def index eefef39f147af..70b87d680cbc0 100644 --- a/include/swift/AST/KnownIdentifiers.def +++ b/include/swift/AST/KnownIdentifiers.def @@ -71,7 +71,6 @@ IDENTIFIER_(enclosingInstance) IDENTIFIER(Encodable) IDENTIFIER(encode) IDENTIFIER(encodeIfPresent) -IDENTIFIER(encodeNil) IDENTIFIER(Encoder) IDENTIFIER(encoder) IDENTIFIER(enqueue) diff --git a/lib/Sema/DerivedConformanceCodable.cpp b/lib/Sema/DerivedConformanceCodable.cpp index 7500830885a92..69bbe198c680c 100644 --- a/lib/Sema/DerivedConformanceCodable.cpp +++ b/lib/Sema/DerivedConformanceCodable.cpp @@ -978,13 +978,15 @@ deriveBodyEncodable_enum_encode(AbstractFunctionDecl *encodeDecl, void *) { } } } else if (!elt->hasAssociatedValues()) { - auto *encodeNilExpr = UnresolvedDotExpr::createImplicit(C, containerExpr, C.Id_encodeNil, {C.Id_forKey}); + auto *encodeExpr = UnresolvedDotExpr::createImplicit(C, containerExpr, C.Id_encode, {Identifier(), C.Id_forKey}); auto *metaTyRef = TypeExpr::createImplicit( funcDC->mapTypeIntoContext(codingKeysEnum->getDeclaredInterfaceType()), C); auto *keyExpr = new (C) MemberRefExpr(metaTyRef, SourceLoc(), codingKeyCase, DeclNameLoc(), /*Implicit=*/true); - auto *callExpr = CallExpr::createImplicit(C, encodeNilExpr, {keyExpr}, {C.Id_forKey}); + auto *trueExpr = new (C) BooleanLiteralExpr(true, SourceLoc(), /*Implicit=*/true); + + auto *callExpr = CallExpr::createImplicit(C, encodeExpr, {trueExpr, keyExpr}, {Identifier(), C.Id_forKey}); auto *tryExpr = new (C) TryExpr(SourceLoc(), callExpr, Type(), /*Implicit=*/true); caseStatements.push_back(tryExpr); From e3105cee018ea36bcee333e87f69c8cf3244470c Mon Sep 17 00:00:00 2001 From: Dario Rexin Date: Thu, 21 Jan 2021 15:43:58 -0800 Subject: [PATCH 05/11] Add test for overloaded case identifiers --- lib/Sema/DerivedConformanceCodable.cpp | 14 ++++++++---- ...um_codable_case_identifier_overloads.swift | 22 +++++++++++++++++++ 2 files changed, 32 insertions(+), 4 deletions(-) create mode 100644 test/decl/protocol/special/coding/enum_codable_case_identifier_overloads.swift diff --git a/lib/Sema/DerivedConformanceCodable.cpp b/lib/Sema/DerivedConformanceCodable.cpp index 69bbe198c680c..f7b32eb0b08a8 100644 --- a/lib/Sema/DerivedConformanceCodable.cpp +++ b/lib/Sema/DerivedConformanceCodable.cpp @@ -413,6 +413,8 @@ static bool synthesizeCodingKeysEnum_enum(DerivedConformance &derived, auto *conformanceDC = derived.getConformanceContext(); auto *codingKeysEnum = lookupEvaluatedCodingKeysEnum(C, target); + llvm::SmallVector codingKeys; + // Only derive CodingKeys enum if it is not already defined if (!codingKeysEnum) { auto *enumDecl = new (C) EnumDecl(SourceLoc(), C.Id_CodingKeys, SourceLoc(), @@ -430,9 +432,8 @@ static bool synthesizeCodingKeysEnum_enum(DerivedConformance &derived, // Forcibly derive conformance to CodingKey. TypeChecker::checkConformancesInContext(enumDecl); - // Add to the type. - target->addMember(enumDecl); codingKeysEnum = enumDecl; + codingKeys.push_back(enumDecl); } llvm::SmallSetVector caseNames; @@ -440,7 +441,8 @@ static bool synthesizeCodingKeysEnum_enum(DerivedConformance &derived, if (!caseNames.insert(elementDecl->getBaseIdentifier())) { elementDecl->diagnose(diag::codable_enum_duplicate_case_name_here, derived.getProtocolType(), target->getDeclaredType(), elementDecl->getBaseIdentifier()); - return false; + allConform = false; + continue; } auto enumIdentifier = caseCodingKeyIdentifier(C, elementDecl); @@ -495,13 +497,17 @@ static bool synthesizeCodingKeysEnum_enum(DerivedConformance &derived, if (conforms) { // Forcibly derive conformance to CodingKey. TypeChecker::checkConformancesInContext(caseEnum); - target->addMember(caseEnum); + codingKeys.push_back(caseEnum); } } if (!allConform) return false; + for (auto* codingKeysEnum : codingKeys) { + target->addMember(codingKeysEnum); + } + return true; } 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..3007f89c4c00e --- /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 + +// 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'}} +} From 43b720355efb750f6c6211b16e6365438bebe84b Mon Sep 17 00:00:00 2001 From: Dario Rexin Date: Thu, 18 Feb 2021 13:50:38 -0800 Subject: [PATCH 06/11] Align code generation with latest proposal revision --- include/swift/AST/Decl.h | 3 - include/swift/AST/KnownDecls.def | 2 - include/swift/AST/KnownIdentifiers.def | 1 - include/swift/AST/KnownStdlibTypes.def | 2 - lib/AST/Decl.cpp | 7 - lib/Sema/DerivedConformanceCodable.cpp | 416 +++--------- .../enum_codable_member_type_lookup.swift | 642 ------------------ .../enum_codable_reordered_codingkeys.swift | 12 + .../special/coding/enum_codable_simple.swift | 8 + ..._codable_simple_conditional_separate.swift | 2 +- 10 files changed, 120 insertions(+), 975 deletions(-) create mode 100644 test/decl/protocol/special/coding/enum_codable_reordered_codingkeys.swift diff --git a/include/swift/AST/Decl.h b/include/swift/AST/Decl.h index 5f151d88023e0..74092ffcd1c94 100644 --- a/include/swift/AST/Decl.h +++ b/include/swift/AST/Decl.h @@ -6525,9 +6525,6 @@ class EnumElementDecl : public DeclContext, public ValueDecl { return getAttrs().hasAttribute(); } - /// True if any of the parameter are unnamed. - bool hasAnyUnnamedParameters() const; - /// 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/KnownDecls.def b/include/swift/AST/KnownDecls.def index e0325d2966741..15a5081285d1d 100644 --- a/include/swift/AST/KnownDecls.def +++ b/include/swift/AST/KnownDecls.def @@ -88,6 +88,4 @@ FUNC_DECL(Swap, "swap") FUNC_DECL(UnimplementedInitializer, "_unimplementedInitializer") FUNC_DECL(Undefined, "_undefined") -FUNC_DECL(FatalError, "fatalError") - #undef FUNC_DECL diff --git a/include/swift/AST/KnownIdentifiers.def b/include/swift/AST/KnownIdentifiers.def index 70b87d680cbc0..d2d69ce4bd1fe 100644 --- a/include/swift/AST/KnownIdentifiers.def +++ b/include/swift/AST/KnownIdentifiers.def @@ -102,7 +102,6 @@ IDENTIFIER(keyPath) IDENTIFIER(makeIterator) IDENTIFIER(makeAsyncIterator) IDENTIFIER(nestedContainer) -IDENTIFIER(nestedUnkeyedContainer) IDENTIFIER(Iterator) IDENTIFIER(AsyncIterator) IDENTIFIER(load) diff --git a/include/swift/AST/KnownStdlibTypes.def b/include/swift/AST/KnownStdlibTypes.def index 3271c349945be..93f99d43caa9b 100644 --- a/include/swift/AST/KnownStdlibTypes.def +++ b/include/swift/AST/KnownStdlibTypes.def @@ -89,8 +89,6 @@ KNOWN_STDLIB_TYPE_DECL(Encoder, ProtocolDecl, 1) KNOWN_STDLIB_TYPE_DECL(Decoder, ProtocolDecl, 1) KNOWN_STDLIB_TYPE_DECL(KeyedEncodingContainer, NominalTypeDecl, 1) KNOWN_STDLIB_TYPE_DECL(KeyedDecodingContainer, NominalTypeDecl, 1) -KNOWN_STDLIB_TYPE_DECL(UnkeyedEncodingContainer, NominalTypeDecl, 1) -KNOWN_STDLIB_TYPE_DECL(UnkeyedDecodingContainer, NominalTypeDecl, 1) KNOWN_STDLIB_TYPE_DECL(RangeReplaceableCollection, ProtocolDecl, 1) KNOWN_STDLIB_TYPE_DECL(EncodingError, NominalTypeDecl, 0) KNOWN_STDLIB_TYPE_DECL(DecodingError, NominalTypeDecl, 0) diff --git a/lib/AST/Decl.cpp b/lib/AST/Decl.cpp index 3b4950e509158..198d93574438c 100644 --- a/lib/AST/Decl.cpp +++ b/lib/AST/Decl.cpp @@ -7726,13 +7726,6 @@ void EnumElementDecl::setParameterList(ParameterList *params) { params->setDeclContextOfParamDecls(this); } -bool EnumElementDecl::hasAnyUnnamedParameters() const { - auto *params = getParameterList(); - return params && - std::any_of(params->begin(), params->end(), - [](auto *paramDecl) { return !paramDecl->hasName(); }); -} - EnumCaseDecl *EnumElementDecl::getParentCase() const { for (EnumCaseDecl *EC : getParentEnum()->getAllCases()) { ArrayRef CaseElements = EC->getElements(); diff --git a/lib/Sema/DerivedConformanceCodable.cpp b/lib/Sema/DerivedConformanceCodable.cpp index f7b32eb0b08a8..0a6b20adb261e 100644 --- a/lib/Sema/DerivedConformanceCodable.cpp +++ b/lib/Sema/DerivedConformanceCodable.cpp @@ -198,6 +198,7 @@ static bool validateCodingKeysEnum(DerivedConformance &derived) { for (auto elt : codingKeysDecl->getAllElements()) { auto it = properties.find(elt->getBaseIdentifier()); if (it == properties.end()) { + elt->diagnose(diag::codable_extraneous_codingkey_case_here, elt->getBaseIdentifier()); // TODO: Investigate typo-correction here; perhaps the case name was @@ -261,6 +262,7 @@ static bool validateCodingKeysEnum(DerivedConformance &derived) { static bool validateCaseCodingKeysEnum(const DerivedConformance &derived, EnumElementDecl *elementDecl, EnumDecl *codingKeysDecl) { + auto &C = derived.Context; auto conformanceDC = derived.getConformanceContext(); // Look through all var decls in the given type. @@ -276,11 +278,19 @@ static bool validateCaseCodingKeysEnum(const DerivedConformance &derived, // against its CodingKey entry, it will get removed. llvm::SmallMapVector properties; if (elementDecl->hasAssociatedValues()) { - for (auto *varDecl : elementDecl->getParameterList()->getArray()) { + int i = 0; + auto params = elementDecl->getParameterList(); + for (auto it = params->begin(); it != params->end(); it++, i++) { + auto varDecl = *it; if (!varDecl->isUserAccessible()) continue; - properties[getVarNameForCoding(varDecl)] = varDecl; + auto identifier = getVarNameForCoding(varDecl); + if (identifier.empty()) { + identifier = C.getIdentifier("_" + std::to_string(i)); + } + + properties[identifier] = varDecl; } } @@ -464,7 +474,8 @@ static bool synthesizeCodingKeysEnum_enum(DerivedConformance &derived, auto *elementParams = elementDecl->getParameterList(); bool conforms = true; if (elementParams) { - for (auto *paramDecl : elementParams->getArray()) { + for (size_t i = 0; i < elementParams->size(); i++) { + auto *paramDecl = elementParams->get(i); auto target = conformanceDC->mapTypeIntoContext(paramDecl->getValueInterfaceType()); if (TypeChecker::conformsToProtocol(target, derived.Protocol, conformanceDC) @@ -479,8 +490,12 @@ static bool synthesizeCodingKeysEnum_enum(DerivedConformance &derived, conforms = false; } else { // if the type conforms to {En,De}codable, add it to the enum. + Identifier paramIdentifier = getVarNameForCoding(paramDecl); + if (paramIdentifier.empty()) { + paramIdentifier = C.getIdentifier("_" + std::to_string(i)); + } auto *elt = - new (C) EnumElementDecl(SourceLoc(), getVarNameForCoding(paramDecl), + new (C) EnumElementDecl(SourceLoc(), paramIdentifier, nullptr, SourceLoc(), nullptr, caseEnum); elt->setImplicit(); caseEnum->addMember(elt); @@ -489,11 +504,6 @@ static bool synthesizeCodingKeysEnum_enum(DerivedConformance &derived, allConform = allConform && conforms; } - // If there are any unnamed parameters, we can't generate CodingKeys for - // this element and it will be encoded into an unkeyed container. - if (elementDecl->hasAnyUnnamedParameters()) - continue; - if (conforms) { // Forcibly derive conformance to CodingKey. TypeChecker::checkConformancesInContext(caseEnum); @@ -549,35 +559,6 @@ static VarDecl *createKeyedContainer(ASTContext &C, DeclContext *DC, 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 UnkeyedEncodingContainer or \c UnkeyedDecodingContainer). -/// -/// \param C The AST context to create the decl in. -/// -/// \param DC The \c DeclContext to create the decl in. -/// -/// \param unkeyedContainerDecl The generic type to bind the key type in. -/// -/// \param introducer Whether to declare the variable as immutable. -/// -/// \param name Name of the resulting variable -static VarDecl *createUnkeyedContainer(ASTContext &C, DeclContext *DC, - NominalTypeDecl *unkeyedContainerDecl, - VarDecl::Introducer introducer, - Identifier name) { - // let container : Keyed*Container - auto *containerDecl = - new (C) VarDecl(/*IsStatic=*/false, introducer, SourceLoc(), name, DC); - containerDecl->setImplicit(); - containerDecl->setInterfaceType( - unkeyedContainerDecl->getDeclaredInterfaceType()); - return containerDecl; -} - /// Creates a new \c CallExpr representing /// /// base.container(keyedBy: CodingKeys.self) @@ -650,34 +631,6 @@ static CallExpr *createNestedContainerKeyedByForKeyCall( argNames); } -static CallExpr *createNestedUnkeyedContainerForKeyCall(ASTContext &C, - DeclContext *DC, - Expr *base, - Type returnType, - EnumElementDecl *key) { - // (forKey:) - auto *forKeyDecl = new (C) ParamDecl(SourceLoc(), SourceLoc(), C.Id_forKey, - SourceLoc(), C.Id_forKey, DC); - forKeyDecl->setImplicit(); - forKeyDecl->setSpecifier(ParamSpecifier::Default); - forKeyDecl->setInterfaceType(returnType); - - // base.nestedUnkeyedContainer(forKey:) expr - auto *paramList = ParameterList::createWithoutLoc(forKeyDecl); - auto *unboundCall = UnresolvedDotExpr::createImplicit( - C, base, C.Id_nestedUnkeyedContainer, paramList); - - // 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.nestedUnkeyedContainer(forKey: key) call - return CallExpr::createImplicit(C, unboundCall, {keyExpr}, {C.Id_forKey}); -} - static ThrowStmt *createThrowDecodingErrorTypeMismatchStmt(ASTContext &C, DeclContext *DC, NominalTypeDecl *targetDecl, Expr *containerExpr, Expr *debugMessage) { auto *errorDecl = C.getDecodingErrorDecl(); @@ -794,11 +747,11 @@ deriveBodyEncodable_enum_encode(AbstractFunctionDecl *encodeDecl, void *) { // case bar // case baz // - // @derived enum CodingKeys_bar : CodingKey { + // @derived enum BarCodingKeys : CodingKey { // case x // } // - // @derived enum CodingKeys_baz : CodingKey { + // @derived enum BazCodingKeys : CodingKey { // case y // } // } @@ -808,11 +761,11 @@ deriveBodyEncodable_enum_encode(AbstractFunctionDecl *encodeDecl, void *) { // switch self { // case bar(let x): // let nestedContainer = try container.nestedContainer(keyedBy: - // CodingKeys_bar.self, forKey: .bar) try nestedContainer.encode(x, + // BarCodingKeys.self, forKey: .bar) try nestedContainer.encode(x, // forKey: .x) // case baz(let y): // let nestedContainer = try container.nestedContainer(keyedBy: - // CodingKeys_baz.self, forKey: .baz) try nestedContainer.encode(y, + // BazCodingKeys.self, forKey: .baz) try nestedContainer.encode(y, // forKey: .y) // } // } @@ -917,85 +870,6 @@ deriveBodyEncodable_enum_encode(AbstractFunctionDecl *encodeDecl, void *) { auto *selfRefExpr = new (C) DeclRefExpr(ConcreteDeclRef(selfRef), DeclNameLoc(), /* Implicit */ true); auto *throwStmt = createThrowEncodingErrorInvalidValueStmt(C, funcDC, selfRefExpr, containerExpr, debugMessage); caseStatements.push_back(throwStmt); - } else if (elt->hasAnyUnnamedParameters()) { - if (payloadVars.size() == 1) { - for (auto *payloadVar : payloadVars) { - auto payloadVarRef = new(C) DeclRefExpr(payloadVar, DeclNameLoc(), - /*implicit*/ true); - - // encode(_:, forKey:) - auto *encodeCall = UnresolvedDotExpr::createImplicit( - C, containerExpr, C.Id_encode, {Identifier(), C.Id_forKey}); - - // key expr - auto *keyTypeExpr = TypeExpr::createImplicit( - funcDC->mapTypeIntoContext(codingKeysEnum->getDeclaredInterfaceType()), - C); - auto *keyExpr = new (C) MemberRefExpr(keyTypeExpr, SourceLoc(), codingKeyCase, - DeclNameLoc(), /*Implicit=*/true); - - // container.encode(x, forKey: CodingKeys.bar) - auto *callExpr = CallExpr::createImplicit( - C, encodeCall, {payloadVarRef, keyExpr}, {Identifier(), C.Id_forKey}); - - // try nestedContainer.encode(x, forKey: CodingKeys.x) - auto *tryExpr = new(C) TryExpr(SourceLoc(), callExpr, Type(), - /*Implicit=*/true); - caseStatements.push_back(tryExpr); - } - } else { - auto *nestedContainerDecl = - createUnkeyedContainer(C, funcDC, C.getUnkeyedEncodingContainerDecl(), - VarDecl::Introducer::Var, - C.getIdentifier(StringRef("nestedContainer"))); - - auto *nestedContainerExpr = new(C) - DeclRefExpr(ConcreteDeclRef(nestedContainerDecl), DeclNameLoc(), - /*Implicit=*/true, AccessSemantics::DirectToStorage); - auto *nestedContainerCall = createNestedUnkeyedContainerForKeyCall( - C, funcDC, containerExpr, nestedContainerDecl->getInterfaceType(), - 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); - - for (auto *payloadVar : payloadVars) { - auto payloadVarRef = new(C) DeclRefExpr(payloadVar, DeclNameLoc(), - /*implicit*/ true); - - // encode(_:) - auto *encodeCall = UnresolvedDotExpr::createImplicit( - C, nestedContainerExpr, C.Id_encode, {Identifier()}); - - // nestedContainer.encode(x) - auto *callExpr = CallExpr::createImplicit( - C, encodeCall, {payloadVarRef}, {Identifier()}); - - // try nestedContainer.encode(x, forKey: CodingKeys.x) - auto *tryExpr = new(C) TryExpr(SourceLoc(), callExpr, Type(), - /*Implicit=*/true); - caseStatements.push_back(tryExpr); - } - } - } else if (!elt->hasAssociatedValues()) { - auto *encodeExpr = UnresolvedDotExpr::createImplicit(C, containerExpr, C.Id_encode, {Identifier(), C.Id_forKey}); - auto *metaTyRef = TypeExpr::createImplicit( - funcDC->mapTypeIntoContext(codingKeysEnum->getDeclaredInterfaceType()), - C); - auto *keyExpr = new (C) MemberRefExpr(metaTyRef, SourceLoc(), codingKeyCase, - DeclNameLoc(), /*Implicit=*/true); - auto *trueExpr = new (C) BooleanLiteralExpr(true, SourceLoc(), /*Implicit=*/true); - - auto *callExpr = CallExpr::createImplicit(C, encodeExpr, {trueExpr, keyExpr}, {Identifier(), C.Id_forKey}); - auto *tryExpr = new (C) TryExpr(SourceLoc(), callExpr, Type(), - /*Implicit=*/true); - caseStatements.push_back(tryExpr); } else { auto caseIdentifier = caseCodingKeyIdentifier(C, elt); auto *caseCodingKeys = @@ -1017,15 +891,23 @@ deriveBodyEncodable_enum_encode(AbstractFunctionDecl *encodeDecl, void *) { caseStatements.push_back(bindingDecl); caseStatements.push_back(nestedContainerDecl); - for (auto *payloadVar : payloadVars) { + + // TODO: use param decls to get names + int i = 0; + for (auto it = payloadVars.begin(); it != payloadVars.end(); it++, i++) { + auto *payloadVar = *it; 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(i); + auto caseCodingKeyIdentifier = getVarNameForCoding(paramDecl); + if (caseCodingKeyIdentifier.empty()) { + caseCodingKeyIdentifier = C.getIdentifier("_" + std::to_string(i)); + } auto *caseCodingKey = - lookupEnumCase(C, caseCodingKeys, payloadVar->getName()); + lookupEnumCase(C, caseCodingKeys, caseCodingKeyIdentifier); // If there is no key defined for this parameter, skip it. if (!caseCodingKey) @@ -1040,7 +922,7 @@ deriveBodyEncodable_enum_encode(AbstractFunctionDecl *encodeDecl, void *) { useIfPresentVariant = true; } - // CodingKeys_bar.x + // BarCodingKeys.x auto *metaTyRef = TypeExpr::createImplicit(caseCodingKeys->getDeclaredType(), C); auto *keyExpr = @@ -1331,33 +1213,34 @@ deriveBodyDecodable_enum_init(AbstractFunctionDecl *initDecl, void *) { // case bar // case baz // - // @derived enum CodingKeys_bar : CodingKey { + // @derived enum BarCodingKeys : CodingKey { // case x // } // - // @derived enum CodingKeys_baz : CodingKey { + // @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: - // CodingKeys_bar.self, forKey: .bar) let x = try + // 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: - // CodingKeys_baz.self, forKey: .baz) let y = try + // BarCodingKeys.self, forKey: .baz) let y = try // nestedContainer.decode(String.self, forKey: .y) self = .baz(y: y) - // default: - // let context = DecodingError.Context( - // codingPath: container.codingPath, - // debugDescription: "Could not find value of type Foo") - // throw DecodingError.valueNotFound(Foo.self, context) + // } // } - // } // The enclosing type decl. auto conformanceDC = initDecl->getDeclContext(); @@ -1439,160 +1322,46 @@ deriveBodyDecodable_enum_init(AbstractFunctionDecl *initDecl, void *) { CaseLabelItem(new (C) OptionalSomePattern(pat, SourceLoc())); llvm::SmallVector caseStatements; - if (!elt->hasAssociatedValues()) { - // Foo.bar - auto *selfTypeExpr = - TypeExpr::createImplicit(targetEnum->getDeclaredType(), C); - 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 if (elt->hasAnyUnnamedParameters()) { - llvm::SmallVector < Expr * , 3 > decodeCalls; - llvm::SmallVector params; - if (elt->getParameterList()->size() == 1) { - for (auto *paramDecl : elt->getParameterList()->getArray()) { - Identifier identifier = getVarNameForCoding(paramDecl); - params.push_back(identifier); - - // Type.self - auto *parameterTypeExpr = - TypeExpr::createImplicit( - funcDC->mapTypeIntoContext(paramDecl->getInterfaceType()), C); - auto *parameterMetaTypeExpr = - new(C) DotSelfExpr(parameterTypeExpr, SourceLoc(), SourceLoc()); - // decode(_:) - auto *decodeCall = UnresolvedDotExpr::createImplicit( - C, containerExpr, C.Id_decode, {Identifier(), C.Id_forKey}); - - // key expr - auto *keyTypeExpr = TypeExpr::createImplicit( - funcDC->mapTypeIntoContext(codingKeysEnum->getDeclaredInterfaceType()), - C); - auto *keyExpr = new (C) MemberRefExpr(keyTypeExpr, SourceLoc(), codingKeyCase, - DeclNameLoc(), /*Implicit=*/true); - - // nestedContainer.decode(Type.self) - auto *callExpr = CallExpr::createImplicit( - C, decodeCall, {parameterMetaTypeExpr, keyExpr}, - {Identifier(), C.Id_forKey}); - - // try nestedContainer.decode(Type.self) - auto *tryExpr = new(C) TryExpr(SourceLoc(), callExpr, Type(), - /*Implicit=*/true); - - decodeCalls.push_back(tryExpr); - } - } else { - auto *nestedContainerDecl = createUnkeyedContainer( - C, funcDC, C.getUnkeyedDecodingContainerDecl(), - VarDecl::Introducer::Var, - C.getIdentifier(StringRef("nestedContainer"))); - - auto *nestedContainerCall = createNestedUnkeyedContainerForKeyCall( - C, funcDC, containerExpr, nestedContainerDecl->getInterfaceType(), - 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); - - for (auto *paramDecl : elt->getParameterList()->getArray()) { - Identifier identifier = getVarNameForCoding(paramDecl); - params.push_back(identifier); - - // Type.self - auto *parameterTypeExpr = - TypeExpr::createImplicit( - funcDC->mapTypeIntoContext(paramDecl->getInterfaceType()), C); - auto *parameterMetaTypeExpr = - new(C) DotSelfExpr(parameterTypeExpr, SourceLoc(), SourceLoc()); - auto *nestedContainerExpr = new(C) - DeclRefExpr(ConcreteDeclRef(nestedContainerDecl), DeclNameLoc(), - /*Implicit=*/true, AccessSemantics::DirectToStorage); - // decode(_:) - auto *decodeCall = UnresolvedDotExpr::createImplicit( - C, nestedContainerExpr, C.Id_decode, {Identifier()}); - - // nestedContainer.decode(Type.self) - auto *callExpr = CallExpr::createImplicit( - C, decodeCall, {parameterMetaTypeExpr}, {Identifier()}); - - // try nestedContainer.decode(Type.self) - auto *tryExpr = new(C) TryExpr(SourceLoc(), callExpr, Type(), - /*Implicit=*/true); - - decodeCalls.push_back(tryExpr); - } - } - - auto *selfRef = DerivedConformance::createSelfDeclRef(initDecl); + + auto caseIdentifier = caseCodingKeyIdentifier(C, elt); + auto *caseCodingKeys = + lookupEvaluatedCodingKeysEnum(C, targetEnum, caseIdentifier); - // Foo.bar - auto *selfTypeExpr = - TypeExpr::createImplicit(targetEnum->getDeclaredType(), C); + auto *nestedContainerDecl = + createKeyedContainer(C, funcDC, C.getKeyedDecodingContainerDecl(), + caseCodingKeys->getDeclaredInterfaceType(), + VarDecl::Introducer::Var, + C.getIdentifier(StringRef("nestedContainer"))); - // Foo.bar(x:) - auto *selfCaseExpr = UnresolvedDotExpr::createImplicit( - C, selfTypeExpr, elt->getBaseIdentifier(), C.AllocateCopy(params)); + auto *nestedContainerCall = createNestedContainerKeyedByForKeyCall( + C, funcDC, containerExpr, caseCodingKeys, codingKeyCase); - // Foo.bar(x: try nestedContainer.decode(Int.self)) - auto *caseCallExpr = CallExpr::createImplicit( - C, selfCaseExpr, C.AllocateCopy(decodeCalls), - C.AllocateCopy(params)); + auto *tryNestedContainerCall = new (C) TryExpr( + SourceLoc(), nestedContainerCall, Type(), /* Implicit */ true); - // self = Foo.bar(x: try nestedContainer.decode(Int.self)) - auto *assignExpr = - new(C) AssignExpr(selfRef, SourceLoc(), caseCallExpr, - /*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); - caseStatements.push_back(assignExpr); - } else { - auto caseIdentifier = caseCodingKeyIdentifier(C, elt); - auto *caseCodingKeys = - lookupEvaluatedCodingKeysEnum(C, targetEnum, caseIdentifier); - - auto *nestedContainerDecl = - createKeyedContainer(C, funcDC, C.getKeyedDecodingContainerDecl(), - caseCodingKeys->getDeclaredInterfaceType(), - VarDecl::Introducer::Var, - C.getIdentifier(StringRef("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; - for (auto *paramDecl : elt->getParameterList()->getArray()) { + llvm::SmallVector decodeCalls; + llvm::SmallVector params; + int i = 0; + auto *paramList = elt->getParameterList(); + if (paramList) { + for (auto it = paramList->begin(); it != paramList->end(); it++, i++) { + auto *paramDecl = *it; + Identifier identifier = getVarNameForCoding(paramDecl); + if (identifier.empty()) { + identifier = C.getIdentifier("_" + std::to_string(i)); + } auto *caseCodingKey = lookupEnumCase( - C, caseCodingKeys, paramDecl->getBaseName().getIdentifier()); + C, caseCodingKeys, identifier); - Identifier identifier = getVarNameForCoding(paramDecl); - params.push_back(identifier); + params.push_back(getVarNameForCoding(paramDecl)); // If no key is defined for this parameter, use the default value if (!caseCodingKey) { @@ -1609,7 +1378,7 @@ deriveBodyDecodable_enum_init(AbstractFunctionDecl *initDecl, void *) { funcDC->mapTypeIntoContext(paramDecl->getInterfaceType()), C); auto *parameterMetaTypeExpr = new (C) DotSelfExpr(parameterTypeExpr, SourceLoc(), SourceLoc()); - // CodingKeys_bar.x + // BarCodingKeys.x auto *metaTyRef = TypeExpr::createImplicit(caseCodingKeys->getDeclaredType(), C); auto *keyExpr = @@ -1623,24 +1392,37 @@ deriveBodyDecodable_enum_init(AbstractFunctionDecl *initDecl, void *) { auto *decodeCall = UnresolvedDotExpr::createImplicit( C, nestedContainerExpr, C.Id_decode, {Identifier(), C.Id_forKey}); - // nestedContainer.decode(Type.self, forKey: CodingKeys_bar.x) + // 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: CodingKeys_bar.x) + // 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 *selfRef = DerivedConformance::createSelfDeclRef(initDecl); - // Foo.bar - auto *selfTypeExpr = - TypeExpr::createImplicit(targetEnum->getDeclaredType(), C); + 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)); 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 index 850584bf08628..d729a26e3b085 100644 --- a/test/decl/protocol/special/coding/enum_codable_member_type_lookup.swift +++ b/test/decl/protocol/special/coding/enum_codable_member_type_lookup.swift @@ -1,10 +1,5 @@ // RUN: %target-typecheck-verify-swift -verify-ignore-unknown -// A top-level CodingKeys type to fall back to in lookups below. -public enum CodingKeys : String, CodingKey { - case topLevel -} - // MARK: - Synthesized CodingKeys Enum // Enums which get synthesized Codable implementations should have visible @@ -12,642 +7,5 @@ public enum CodingKeys : String, CodingKey { enum SynthesizedEnum : Codable { case value - // Qualified type lookup should always be unambiguous. - public func qualifiedFoo(_ key: SynthesizedEnum.CodingKeys) {} // expected-error {{method cannot be declared public because its parameter uses a private type}} - internal func qualifiedBar(_ key: SynthesizedEnum.CodingKeys) {} // expected-error {{method cannot be declared internal because its parameter uses a private type}} - fileprivate func qualfiedBaz(_ key: SynthesizedEnum.CodingKeys) {} // expected-error {{method cannot be declared fileprivate because its parameter uses a private type}} - private func qualifiedQux(_ key: SynthesizedEnum.CodingKeys) {} - - // Unqualified lookups should find the synthesized CodingKeys type instead - // of the top-level type above. - public func unqualifiedFoo(_ key: CodingKeys) { // expected-error {{method cannot be declared public because its parameter uses a private type}} - print(CodingKeys.value) // Not found on top-level. - } - - internal func unqualifiedBar(_ key: CodingKeys) { // expected-error {{method cannot be declared internal because its parameter uses a private type}} - print(CodingKeys.value) // Not found on top-level. - } - - fileprivate func unqualifiedBaz(_ key: CodingKeys) { // expected-error {{method cannot be declared fileprivate because its parameter uses a private type}} - print(CodingKeys.value) // Not found on top-level. - } - - private func unqualifiedQux(_ key: CodingKeys) { - print(CodingKeys.value) // Not found on top-level. - } - - // Unqualified lookups should find the most local CodingKeys type available. - public func nestedUnqualifiedFoo(_ key: CodingKeys) { // expected-error {{method cannot be declared public because its parameter uses a private type}} - enum CodingKeys : String, CodingKey { - case nested - } - - // CodingKeys should refer to the local unqualified enum. - func foo(_ key: CodingKeys) { - print(CodingKeys.nested) // Not found on synthesized type or top-level type. - } - - foo(CodingKeys.nested) - } - - internal func nestedUnqualifiedBar(_ key: CodingKeys) { // expected-error {{method cannot be declared internal because its parameter uses a private type}} - enum CodingKeys : String, CodingKey { - case nested - } - - // CodingKeys should refer to the local unqualified enum. - func bar(_ key: CodingKeys) { - print(CodingKeys.nested) // Not found on synthesized type or top-level type. - } - - bar(CodingKeys.nested) - } - - fileprivate func nestedUnqualifiedBaz(_ key: CodingKeys) { // expected-error {{method cannot be declared fileprivate because its parameter uses a private type}} - enum CodingKeys : String, CodingKey { - case nested - } - - // CodingKeys should refer to the local unqualified enum. - func baz(_ key: CodingKeys) { - print(CodingKeys.nested) // Not found on synthesized type or top-level type. - } - - baz(CodingKeys.nested) - } - - private func nestedUnqualifiedQux(_ key: CodingKeys) { - enum CodingKeys : String, CodingKey { - case nested - } - - // CodingKeys should refer to the local unqualified enum. - func qux(_ key: CodingKeys) { - print(CodingKeys.nested) // Not found on synthesized type or top-level type. - } - - qux(CodingKeys.nested) - } - - // Lookup within nested types should look outside of the type. - struct Nested { - // Qualified lookup should remain as-is. - public func qualifiedFoo(_ key: SynthesizedEnum.CodingKeys) {} // expected-error {{method cannot be declared public because its parameter uses a private type}} - internal func qualifiedBar(_ key: SynthesizedEnum.CodingKeys) {} // expected-error {{method cannot be declared internal because its parameter uses a private type}} - fileprivate func qualfiedBaz(_ key: SynthesizedEnum.CodingKeys) {} // expected-error {{method cannot be declared fileprivate because its parameter uses a private type}} - private func qualifiedQux(_ key: SynthesizedEnum.CodingKeys) {} - - // Unqualified lookups should find the SynthesizedEnum's synthesized - // CodingKeys type instead of the top-level type above. - public func unqualifiedFoo(_ key: CodingKeys) { // expected-error {{method cannot be declared public because its parameter uses a private type}} - print(CodingKeys.value) // Not found on top-level. - } - - internal func unqualifiedBar(_ key: CodingKeys) { // expected-error {{method cannot be declared internal because its parameter uses a private type}} - print(CodingKeys.value) // Not found on top-level. - } - - fileprivate func unqualifiedBaz(_ key: CodingKeys) { // expected-error {{method cannot be declared fileprivate because its parameter uses a private type}} - print(CodingKeys.value) // Not found on top-level. - } - - private func unqualifiedQux(_ key: CodingKeys) { - print(CodingKeys.value) // Not found on top-level. - } - - // Unqualified lookups should find the most local CodingKeys type available. - public func nestedUnqualifiedFoo(_ key: CodingKeys) { // expected-error {{method cannot be declared public because its parameter uses a private type}} - enum CodingKeys : String, CodingKey { - case nested - } - - // CodingKeys should refer to the local unqualified enum. - func foo(_ key: CodingKeys) { - print(CodingKeys.nested) // Not found on synthesized type or top-level type. - } - - foo(CodingKeys.nested) - } - - internal func nestedUnqualifiedBar(_ key: CodingKeys) { // expected-error {{method cannot be declared internal because its parameter uses a private type}} - enum CodingKeys : String, CodingKey { - case nested - } - - // CodingKeys should refer to the local unqualified enum. - func bar(_ key: CodingKeys) { - print(CodingKeys.nested) // Not found on synthesized type or top-level type. - } - - bar(CodingKeys.nested) - } - - fileprivate func nestedUnqualifiedBaz(_ key: CodingKeys) { // expected-error {{method cannot be declared fileprivate because its parameter uses a private type}} - enum CodingKeys : String, CodingKey { - case nested - } - - // CodingKeys should refer to the local unqualified enum. - func baz(_ key: CodingKeys) { - print(CodingKeys.nested) // Not found on synthesized type or top-level type. - } - - baz(CodingKeys.nested) - } - - private func nestedUnqualifiedQux(_ key: CodingKeys) { - enum CodingKeys : String, CodingKey { - case nested - } - - // CodingKeys should refer to the local unqualified enum. - func qux(_ key: CodingKeys) { - print(CodingKeys.nested) // Not found on synthesized type or top-level type. - } - - qux(CodingKeys.nested) - } - } -} - -// MARK: - No CodingKeys Enum - -// Enums which don't get synthesized Codable implementations should expose the -// appropriate CodingKeys type. -enum NonSynthesizedEnum : Codable { // expected-note 4 {{'NonSynthesizedEnum' declared here}} - case value - - // No synthesized type since we implemented both methods. - init(from decoder: Decoder) throws {} - func encode(to encoder: Encoder) throws {} - - // Qualified type lookup should clearly fail -- we shouldn't get a synthesized - // type here. - public func qualifiedFoo(_ key: NonSynthesizedEnum.CodingKeys) {} // expected-error {{'CodingKeys' is not a member type of enum 'enum_codable_member_type_lookup.NonSynthesizedEnum'}} - internal func qualifiedBar(_ key: NonSynthesizedEnum.CodingKeys) {} // expected-error {{'CodingKeys' is not a member type of enum 'enum_codable_member_type_lookup.NonSynthesizedEnum'}} - fileprivate func qualfiedBaz(_ key: NonSynthesizedEnum.CodingKeys) {} // expected-error {{'CodingKeys' is not a member type of enum 'enum_codable_member_type_lookup.NonSynthesizedEnum'}} - private func qualifiedQux(_ key: NonSynthesizedEnum.CodingKeys) {} // expected-error {{'CodingKeys' is not a member type of enum 'enum_codable_member_type_lookup.NonSynthesizedEnum'}} - - // Unqualified lookups should find the public top-level CodingKeys type. - public func unqualifiedFoo(_ key: CodingKeys) { print(CodingKeys.topLevel) } - internal func unqualifiedBar(_ key: CodingKeys) { print(CodingKeys.topLevel) } - fileprivate func unqualifiedBaz(_ key: CodingKeys) { print(CodingKeys.topLevel) } - private func unqualifiedQux(_ key: CodingKeys) { print(CodingKeys.topLevel) } - - // Unqualified lookups should find the most local CodingKeys type available. - public func nestedUnqualifiedFoo(_ key: CodingKeys) { - enum CodingKeys : String, CodingKey { - case nested - } - - // CodingKeys should refer to the local unqualified enum. - func foo(_ key: CodingKeys) { - print(CodingKeys.nested) // Not found on top-level type. - } - - foo(CodingKeys.nested) - } - - internal func nestedUnqualifiedBar(_ key: CodingKeys) { - enum CodingKeys : String, CodingKey { - case nested - } - - // CodingKeys should refer to the local unqualified enum. - func bar(_ key: CodingKeys) { - print(CodingKeys.nested) // Not found on top-level type. - } - - bar(CodingKeys.nested) - } - - fileprivate func nestedUnqualifiedBaz(_ key: CodingKeys) { - enum CodingKeys : String, CodingKey { - case nested - } - - // CodingKeys should refer to the local unqualified enum. - func baz(_ key: CodingKeys) { - print(CodingKeys.nested) // Not found on top-level type. - } - - baz(CodingKeys.nested) - } - - private func nestedUnqualifiedQux(_ key: CodingKeys) { - enum CodingKeys : String, CodingKey { - case nested - } - - // CodingKeys should refer to the local unqualified enum. - func qux(_ key: CodingKeys) { - print(CodingKeys.nested) // Not found on top-level type. - } - - qux(CodingKeys.nested) - } -} - -// MARK: - Explicit CodingKeys Enum - -// Enums which explicitly define their own CodingKeys types should have -// visible CodingKey enums during member type lookup. -enum ExplicitStruct : Codable { - case value - - public enum CodingKeys { - case a - case b - case c - } - - init(from decoder: Decoder) throws {} - func encode(to encoder: Encoder) throws {} - - // Qualified type lookup should always be unambiguous. - public func qualifiedFoo(_ key: ExplicitStruct.CodingKeys) {} - internal func qualifiedBar(_ key: ExplicitStruct.CodingKeys) {} - fileprivate func qualfiedBaz(_ key: ExplicitStruct.CodingKeys) {} - private func qualifiedQux(_ key: ExplicitStruct.CodingKeys) {} - - // Unqualified lookups should find the synthesized CodingKeys type instead - // of the top-level type above. - public func unqualifiedFoo(_ key: CodingKeys) { - print(CodingKeys.a) // Not found on top-level. - } - - internal func unqualifiedBar(_ key: CodingKeys) { - print(CodingKeys.a) // Not found on top-level. - } - - fileprivate func unqualifiedBaz(_ key: CodingKeys) { - print(CodingKeys.a) // Not found on top-level. - } - - private func unqualifiedQux(_ key: CodingKeys) { - print(CodingKeys.a) // Not found on top-level. - } - - // Unqualified lookups should find the most local CodingKeys type available. - public func nestedUnqualifiedFoo(_ key: CodingKeys) { - enum CodingKeys : String, CodingKey { - case nested - } - - // CodingKeys should refer to the local unqualified enum. - func foo(_ key: CodingKeys) { - print(CodingKeys.nested) // Not found on synthesized type or top-level type. - } - - foo(CodingKeys.nested) - } - - internal func nestedUnqualifiedBar(_ key: CodingKeys) { - enum CodingKeys : String, CodingKey { - case nested - } - - // CodingKeys should refer to the local unqualified enum. - func bar(_ key: CodingKeys) { - print(CodingKeys.nested) // Not found on synthesized type or top-level type. - } - - bar(CodingKeys.nested) - } - - fileprivate func nestedUnqualifiedBaz(_ key: CodingKeys) { - enum CodingKeys : String, CodingKey { - case nested - } - - // CodingKeys should refer to the local unqualified enum. - func baz(_ key: CodingKeys) { - print(CodingKeys.nested) // Not found on synthesized type or top-level type. - } - - baz(CodingKeys.nested) - } - - private func nestedUnqualifiedQux(_ key: CodingKeys) { - enum CodingKeys : String, CodingKey { - case nested - } - - // CodingKeys should refer to the local unqualified enum. - func qux(_ key: CodingKeys) { - print(CodingKeys.nested) // Not found on synthesized type or top-level type. - } - - qux(CodingKeys.nested) - } - - // Lookup within nested types should look outside of the type. - struct Nested { - // Qualified lookup should remain as-is. - public func qualifiedFoo(_ key: ExplicitStruct.CodingKeys) {} - internal func qualifiedBar(_ key: ExplicitStruct.CodingKeys) {} - fileprivate func qualfiedBaz(_ key: ExplicitStruct.CodingKeys) {} - private func qualifiedQux(_ key: ExplicitStruct.CodingKeys) {} - - // Unqualified lookups should find the ExplicitStruct's synthesized - // CodingKeys type instead of the top-level type above. - public func unqualifiedFoo(_ key: CodingKeys) { - print(CodingKeys.a) // Not found on top-level. - } - - internal func unqualifiedBar(_ key: CodingKeys) { - print(CodingKeys.a) // Not found on top-level. - } - - fileprivate func unqualifiedBaz(_ key: CodingKeys) { - print(CodingKeys.a) // Not found on top-level. - } - - private func unqualifiedQux(_ key: CodingKeys) { - print(CodingKeys.a) // Not found on top-level. - } - - // Unqualified lookups should find the most local CodingKeys type available. - public func nestedUnqualifiedFoo(_ key: CodingKeys) { - enum CodingKeys : String, CodingKey { - case nested - } - - // CodingKeys should refer to the local unqualified enum. - func foo(_ key: CodingKeys) { - print(CodingKeys.nested) // Not found on synthesized type or top-level type. - } - - foo(CodingKeys.nested) - } - - internal func nestedUnqualifiedBar(_ key: CodingKeys) { - enum CodingKeys : String, CodingKey { - case nested - } - - // CodingKeys should refer to the local unqualified enum. - func bar(_ key: CodingKeys) { - print(CodingKeys.nested) // Not found on synthesized type or top-level type. - } - - bar(CodingKeys.nested) - } - - fileprivate func nestedUnqualifiedBaz(_ key: CodingKeys) { - enum CodingKeys : String, CodingKey { - case nested - } - - // CodingKeys should refer to the local unqualified enum. - func baz(_ key: CodingKeys) { - print(CodingKeys.nested) // Not found on synthesized type or top-level type. - } - - baz(CodingKeys.nested) - } - - private func nestedUnqualifiedQux(_ key: CodingKeys) { - enum CodingKeys : String, CodingKey { - case nested - } - - // CodingKeys should refer to the local unqualified enum. - func qux(_ key: CodingKeys) { - print(CodingKeys.nested) // Not found on synthesized type or top-level type. - } - - qux(CodingKeys.nested) - } - } -} - -// MARK: - CodingKeys Enums in Extensions - -// Enumss which get a CodingKeys type in an extension should be able to see -// that type during member type lookup. -enum ExtendedEnum : Codable { - case value - - // Don't get an auto-synthesized type. - init(from decoder: Decoder) throws {} - func encode(to encoder: Encoder) throws {} - - // Qualified type lookup should always be unambiguous. - public func qualifiedFoo(_ key: ExtendedEnum.CodingKeys) {} - internal func qualifiedBar(_ key: ExtendedEnum.CodingKeys) {} - fileprivate func qualfiedBaz(_ key: ExtendedEnum.CodingKeys) {} - private func qualifiedQux(_ key: ExtendedEnum.CodingKeys) {} - - // Unqualified lookups should find the synthesized CodingKeys type instead - // of the top-level type above. - public func unqualifiedFoo(_ key: CodingKeys) { - print(CodingKeys.a) // Not found on top-level. - } - - internal func unqualifiedBar(_ key: CodingKeys) { - print(CodingKeys.a) // Not found on top-level. - } - - fileprivate func unqualifiedBaz(_ key: CodingKeys) { - print(CodingKeys.a) // Not found on top-level. - } - - private func unqualifiedQux(_ key: CodingKeys) { - print(CodingKeys.a) // Not found on top-level. - } - - // Unqualified lookups should find the most local CodingKeys type available. - public func nestedUnqualifiedFoo(_ key: CodingKeys) { - enum CodingKeys : String, CodingKey { - case nested - } - - // CodingKeys should refer to the local unqualified enum. - func foo(_ key: CodingKeys) { - print(CodingKeys.nested) // Not found on synthesized type or top-level type. - } - - foo(CodingKeys.nested) - } - - internal func nestedUnqualifiedBar(_ key: CodingKeys) { - enum CodingKeys : String, CodingKey { - case nested - } - - // CodingKeys should refer to the local unqualified enum. - func bar(_ key: CodingKeys) { - print(CodingKeys.nested) // Not found on synthesized type or top-level type. - } - - bar(CodingKeys.nested) - } - - fileprivate func nestedUnqualifiedBaz(_ key: CodingKeys) { - enum CodingKeys : String, CodingKey { - case nested - } - - // CodingKeys should refer to the local unqualified enum. - func baz(_ key: CodingKeys) { - print(CodingKeys.nested) // Not found on synthesized type or top-level type. - } - - baz(CodingKeys.nested) - } - - private func nestedUnqualifiedQux(_ key: CodingKeys) { - enum CodingKeys : String, CodingKey { - case nested - } - - // CodingKeys should refer to the local unqualified enum. - func qux(_ key: CodingKeys) { - print(CodingKeys.nested) // Not found on synthesized type or top-level type. - } - - qux(CodingKeys.nested) - } - - // Lookup within nested types should look outside of the type. - struct Nested { - // Qualified lookup should remain as-is. - public func qualifiedFoo(_ key: ExtendedEnum.CodingKeys) {} - internal func qualifiedBar(_ key: ExtendedEnum.CodingKeys) {} - fileprivate func qualfiedBaz(_ key: ExtendedEnum.CodingKeys) {} - private func qualifiedQux(_ key: ExtendedEnum.CodingKeys) {} - - // Unqualified lookups should find the ExtendedEnum's synthesized - // CodingKeys type instead of the top-level type above. - public func unqualifiedFoo(_ key: CodingKeys) { - print(CodingKeys.a) // Not found on top-level. - } - - internal func unqualifiedBar(_ key: CodingKeys) { - print(CodingKeys.a) // Not found on top-level. - } - - fileprivate func unqualifiedBaz(_ key: CodingKeys) { - print(CodingKeys.a) // Not found on top-level. - } - - private func unqualifiedQux(_ key: CodingKeys) { - print(CodingKeys.a) // Not found on top-level. - } - - // Unqualified lookups should find the most local CodingKeys type available. - public func nestedUnqualifiedFoo(_ key: CodingKeys) { - enum CodingKeys : String, CodingKey { - case nested - } - - // CodingKeys should refer to the local unqualified enum. - func foo(_ key: CodingKeys) { - print(CodingKeys.nested) // Not found on synthesized type or top-level type. - } - - foo(CodingKeys.nested) - } - - internal func nestedUnqualifiedBar(_ key: CodingKeys) { - enum CodingKeys : String, CodingKey { - case nested - } - - // CodingKeys should refer to the local unqualified enum. - func bar(_ key: CodingKeys) { - print(CodingKeys.nested) // Not found on synthesized type or top-level type. - } - - bar(CodingKeys.nested) - } - - fileprivate func nestedUnqualifiedBaz(_ key: CodingKeys) { - enum CodingKeys : String, CodingKey { - case nested - } - - // CodingKeys should refer to the local unqualified enum. - func baz(_ key: CodingKeys) { - print(CodingKeys.nested) // Not found on synthesized type or top-level type. - } - - baz(CodingKeys.nested) - } - - private func nestedUnqualifiedQux(_ key: CodingKeys) { - enum CodingKeys : String, CodingKey { - case nested - } - - // CodingKeys should refer to the local unqualified enum. - func qux(_ key: CodingKeys) { - print(CodingKeys.nested) // Not found on synthesized type or top-level type. - } - - qux(CodingKeys.nested) - } - } } -extension ExtendedEnum { - enum CodingKeys : String, CodingKey { - case a, b, c - } -} - -struct A { - enum Inner : Codable { - case value - - func foo() { - print(CodingKeys.value) // Not found on A.CodingKeys or top-level type. - } - } -} - -extension A { - enum CodingKeys : String, CodingKey { - case a - } -} - -enum B : Codable { - // So B conforms to Codable using CodingKeys.b below. - case b - - enum Inner { - case value - - func foo() { - print(CodingKeys.b) // Not found on top-level type. - } - } -} - -extension B { - enum CodingKeys : String, CodingKey { - case b - } -} - -enum C : Codable { - enum Inner : Codable { - case value - - func foo() { - print(CodingKeys.value) // Not found on C.CodingKeys or top-level type. - } - } -} - -extension C.Inner { - enum CodingKeys : String, CodingKey { - case value - } -} - -enum GenericCodableEnum : Codable {} - -func foo(_: GenericCodableEnum.CodingKeys) // expected-error {{'CodingKeys' 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..2cad57b326abe --- /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 + +// 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 index fe8fe88ba1008..3573acc249fde 100644 --- a/test/decl/protocol/special/coding/enum_codable_simple.swift +++ b/test/decl/protocol/special/coding/enum_codable_simple.swift @@ -5,6 +5,7 @@ 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. @@ -13,6 +14,7 @@ enum SimpleEnum : Codable { 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 @@ -23,6 +25,10 @@ enum SimpleEnum : Codable { let _ = SimpleEnum.ACodingKeys.y let _ = SimpleEnum.BCodingKeys.z + + let _ = SimpleEnum.CCodingKeys._0 + let _ = SimpleEnum.CCodingKeys._1 + let _ = SimpleEnum.CCodingKeys.b } } @@ -34,3 +40,5 @@ let _ = SimpleEnum.encode(to:) // 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_separate.swift b/test/decl/protocol/special/coding/enum_codable_simple_conditional_separate.swift index 04ea2fe53d127..4d7cf1470b8c3 100644 --- a/test/decl/protocol/special/coding/enum_codable_simple_conditional_separate.swift +++ b/test/decl/protocol/special/coding/enum_codable_simple_conditional_separate.swift @@ -1,7 +1,7 @@ // RUN: %target-typecheck-verify-swift -verify-ignore-unknown -swift-version 4 enum Conditional { - case a( x: T, y: T?) + case a(x: T, y: T?) case b(z: [T]) func foo() { From fc619b6737448dda5003440c8a178dc9884beaa5 Mon Sep 17 00:00:00 2001 From: Dario Rexin Date: Thu, 18 Feb 2021 16:20:52 -0800 Subject: [PATCH 07/11] Put enum codable derivation behind flag --- include/swift/Basic/LangOptions.h | 3 +++ include/swift/Option/FrontendOptions.td | 4 ++++ lib/Frontend/CompilerInvocation.cpp | 3 +++ lib/Sema/DerivedConformanceCodable.cpp | 8 ++++---- lib/Sema/DerivedConformances.cpp | 2 +- .../coding/enum_codable_case_identifier_overloads.swift | 2 +- .../coding/enum_codable_codingkeys_typealias.swift | 2 +- .../enum_codable_excluded_optional_properties.swift | 2 +- .../special/coding/enum_codable_failure_diagnostics.swift | 2 +- .../enum_codable_ignore_nonconforming_property.swift | 2 +- .../special/coding/enum_codable_invalid_codingkeys.swift | 2 +- .../special/coding/enum_codable_member_type_lookup.swift | 2 +- .../coding/enum_codable_nonconforming_property.swift | 2 +- .../coding/enum_codable_reordered_codingkeys.swift | 2 +- .../protocol/special/coding/enum_codable_simple.swift | 2 +- .../special/coding/enum_codable_simple_conditional.swift | 2 +- .../coding/enum_codable_simple_conditional_separate.swift | 2 +- .../special/coding/enum_codable_simple_extension.swift | 2 +- .../coding/enum_codable_simple_extension_flipped.swift | 2 +- .../special/coding/enum_codable_simple_multi.swift | 4 ++-- 20 files changed, 31 insertions(+), 21 deletions(-) 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..01b07d9cef973 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 0a6b20adb261e..537a1c4f3523d 100644 --- a/lib/Sema/DerivedConformanceCodable.cpp +++ b/lib/Sema/DerivedConformanceCodable.cpp @@ -1946,9 +1946,9 @@ bool DerivedConformance::canDeriveEncodable(NominalTypeDecl *NTD) { } ValueDecl *DerivedConformance::deriveEncodable(ValueDecl *requirement) { - // We can only synthesize Encodable for structs and classes. + // We can only synthesize Encodable for structs, classes, and enums. if (!isa(Nominal) && !isa(Nominal) && - !isa(Nominal)) + !(Context.LangOpts.EnableExperimentalEnumCodableDerivation && isa(Nominal))) return nullptr; if (requirement->getBaseName() != Context.Id_encode) { @@ -1976,9 +1976,9 @@ ValueDecl *DerivedConformance::deriveEncodable(ValueDecl *requirement) { } ValueDecl *DerivedConformance::deriveDecodable(ValueDecl *requirement) { - // We can only synthesize Encodable for structs and classes. + // We can only synthesize Encodable for structs, classes, and enums. if (!isa(Nominal) && !isa(Nominal) && - !isa(Nominal)) + (Context.LangOpts.EnableExperimentalEnumCodableDerivation && !isa(Nominal))) return nullptr; if (requirement->getBaseName() != DeclBaseName::createConstructor()) { diff --git a/lib/Sema/DerivedConformances.cpp b/lib/Sema/DerivedConformances.cpp index cf1d6a996cb37..debd5788d4073 100644 --- a/lib/Sema/DerivedConformances.cpp +++ b/lib/Sema/DerivedConformances.cpp @@ -152,7 +152,7 @@ bool DerivedConformance::derivesProtocolConformance(DeclContext *DC, // unfortunately means that we expect a witness even if one will not be // produced, which requires DerivedConformance::deriveCodable to output // its own diagnostics. - return true; + return DC->getASTContext().LangOpts.EnableExperimentalEnumCodableDerivation; default: return false; 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 index 3007f89c4c00e..ae8bde93883d2 100644 --- a/test/decl/protocol/special/coding/enum_codable_case_identifier_overloads.swift +++ b/test/decl/protocol/special/coding/enum_codable_case_identifier_overloads.swift @@ -1,4 +1,4 @@ -// RUN: %target-typecheck-verify-swift -verify-ignore-unknown +// 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. diff --git a/test/decl/protocol/special/coding/enum_codable_codingkeys_typealias.swift b/test/decl/protocol/special/coding/enum_codable_codingkeys_typealias.swift index 17b53610f3fe6..4630da5a5a304 100644 --- a/test/decl/protocol/special/coding/enum_codable_codingkeys_typealias.swift +++ b/test/decl/protocol/special/coding/enum_codable_codingkeys_typealias.swift @@ -1,4 +1,4 @@ -// RUN: %target-typecheck-verify-swift -verify-ignore-unknown +// 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. 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 index d308bc7306278..ea6449aed8d41 100644 --- a/test/decl/protocol/special/coding/enum_codable_excluded_optional_properties.swift +++ b/test/decl/protocol/special/coding/enum_codable_excluded_optional_properties.swift @@ -1,4 +1,4 @@ -// RUN: %target-typecheck-verify-swift +// 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'}} diff --git a/test/decl/protocol/special/coding/enum_codable_failure_diagnostics.swift b/test/decl/protocol/special/coding/enum_codable_failure_diagnostics.swift index 6a4435c742cfb..31d7a9bccc93b 100644 --- a/test/decl/protocol/special/coding/enum_codable_failure_diagnostics.swift +++ b/test/decl/protocol/special/coding/enum_codable_failure_diagnostics.swift @@ -1,4 +1,4 @@ -// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -typecheck -parse-as-library -swift-version 4 %s -verify +// 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 { 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 index 6b1a98d15ea38..43cb5b44a9f26 100644 --- a/test/decl/protocol/special/coding/enum_codable_ignore_nonconforming_property.swift +++ b/test/decl/protocol/special/coding/enum_codable_ignore_nonconforming_property.swift @@ -1,4 +1,4 @@ -// RUN: %target-typecheck-verify-swift -verify-ignore-unknown +// RUN: %target-typecheck-verify-swift -verify-ignore-unknown -enable-experimental-enum-codable-derivation struct NonCodable {} diff --git a/test/decl/protocol/special/coding/enum_codable_invalid_codingkeys.swift b/test/decl/protocol/special/coding/enum_codable_invalid_codingkeys.swift index bcd9ffcf14220..3de8dc8f23829 100644 --- a/test/decl/protocol/special/coding/enum_codable_invalid_codingkeys.swift +++ b/test/decl/protocol/special/coding/enum_codable_invalid_codingkeys.swift @@ -1,4 +1,4 @@ -// RUN: %target-typecheck-verify-swift -verify-ignore-unknown +// 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. 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 index d729a26e3b085..9a9c03a33dcb1 100644 --- a/test/decl/protocol/special/coding/enum_codable_member_type_lookup.swift +++ b/test/decl/protocol/special/coding/enum_codable_member_type_lookup.swift @@ -1,4 +1,4 @@ -// RUN: %target-typecheck-verify-swift -verify-ignore-unknown +// RUN: %target-typecheck-verify-swift -verify-ignore-unknown -enable-experimental-enum-codable-derivation // MARK: - Synthesized CodingKeys Enum diff --git a/test/decl/protocol/special/coding/enum_codable_nonconforming_property.swift b/test/decl/protocol/special/coding/enum_codable_nonconforming_property.swift index e35fdf920a8e3..c31ea38bd5a9d 100644 --- a/test/decl/protocol/special/coding/enum_codable_nonconforming_property.swift +++ b/test/decl/protocol/special/coding/enum_codable_nonconforming_property.swift @@ -1,4 +1,4 @@ -// RUN: %target-typecheck-verify-swift -verify-ignore-unknown +// RUN: %target-typecheck-verify-swift -verify-ignore-unknown -enable-experimental-enum-codable-derivation enum NonCodable : Hashable { func hash(into hasher: inout Hasher) {} diff --git a/test/decl/protocol/special/coding/enum_codable_reordered_codingkeys.swift b/test/decl/protocol/special/coding/enum_codable_reordered_codingkeys.swift index 2cad57b326abe..0adbc9dc7c358 100644 --- a/test/decl/protocol/special/coding/enum_codable_reordered_codingkeys.swift +++ b/test/decl/protocol/special/coding/enum_codable_reordered_codingkeys.swift @@ -1,4 +1,4 @@ -// RUN: %target-typecheck-verify-swift -verify-ignore-unknown +// 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 { diff --git a/test/decl/protocol/special/coding/enum_codable_simple.swift b/test/decl/protocol/special/coding/enum_codable_simple.swift index 3573acc249fde..fcce676e1970d 100644 --- a/test/decl/protocol/special/coding/enum_codable_simple.swift +++ b/test/decl/protocol/special/coding/enum_codable_simple.swift @@ -1,4 +1,4 @@ -// RUN: %target-typecheck-verify-swift -verify-ignore-unknown +// 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. diff --git a/test/decl/protocol/special/coding/enum_codable_simple_conditional.swift b/test/decl/protocol/special/coding/enum_codable_simple_conditional.swift index bb8af6d96e4a0..169682fd86803 100644 --- a/test/decl/protocol/special/coding/enum_codable_simple_conditional.swift +++ b/test/decl/protocol/special/coding/enum_codable_simple_conditional.swift @@ -1,4 +1,4 @@ -// RUN: %target-typecheck-verify-swift -verify-ignore-unknown -swift-version 4 +// RUN: %target-typecheck-verify-swift -verify-ignore-unknown -swift-version 4 -enable-experimental-enum-codable-derivation enum Conditional { case a(x: T, y: T?) 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 index 4d7cf1470b8c3..58097f21af456 100644 --- a/test/decl/protocol/special/coding/enum_codable_simple_conditional_separate.swift +++ b/test/decl/protocol/special/coding/enum_codable_simple_conditional_separate.swift @@ -1,4 +1,4 @@ -// RUN: %target-typecheck-verify-swift -verify-ignore-unknown -swift-version 4 +// RUN: %target-typecheck-verify-swift -verify-ignore-unknown -swift-version 4 -enable-experimental-enum-codable-derivation enum Conditional { case a(x: T, y: T?) diff --git a/test/decl/protocol/special/coding/enum_codable_simple_extension.swift b/test/decl/protocol/special/coding/enum_codable_simple_extension.swift index 66d4d3ae82a2d..e24ee64e4b085 100644 --- a/test/decl/protocol/special/coding/enum_codable_simple_extension.swift +++ b/test/decl/protocol/special/coding/enum_codable_simple_extension.swift @@ -1,4 +1,4 @@ -// RUN: %target-typecheck-verify-swift -verify-ignore-unknown -swift-version 4 +// 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. 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 index 2c6fcb0793a39..f4ee37f7dcc1a 100644 --- a/test/decl/protocol/special/coding/enum_codable_simple_extension_flipped.swift +++ b/test/decl/protocol/special/coding/enum_codable_simple_extension_flipped.swift @@ -1,4 +1,4 @@ -// RUN: %target-typecheck-verify-swift -verify-ignore-unknown -swift-version 4 +// 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. diff --git a/test/decl/protocol/special/coding/enum_codable_simple_multi.swift b/test/decl/protocol/special/coding/enum_codable_simple_multi.swift index 457c41d332018..36901c4642780 100644 --- a/test/decl/protocol/special/coding/enum_codable_simple_multi.swift +++ b/test/decl/protocol/special/coding/enum_codable_simple_multi.swift @@ -1,2 +1,2 @@ -// RUN: %target-typecheck-verify-swift -verify-ignore-unknown %S/Inputs/enum_codable_simple_multi1.swift %S/Inputs/enum_codable_simple_multi2.swift -// RUN: %target-typecheck-verify-swift -verify-ignore-unknown %S/Inputs/enum_codable_simple_multi2.swift %S/Inputs/enum_codable_simple_multi1.swift +// 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 From 9f84a87ea39cd875d9892ffc428c8dc65617b131 Mon Sep 17 00:00:00 2001 From: Dario Rexin Date: Thu, 18 Feb 2021 16:27:38 -0800 Subject: [PATCH 08/11] clang-format sources --- lib/Frontend/CompilerInvocation.cpp | 2 +- lib/Sema/DerivedConformanceCodable.cpp | 228 ++++++++++++++----------- lib/Sema/DerivedConformances.cpp | 3 +- 3 files changed, 132 insertions(+), 101 deletions(-) diff --git a/lib/Frontend/CompilerInvocation.cpp b/lib/Frontend/CompilerInvocation.cpp index 01b07d9cef973..e323b6e2b006a 100644 --- a/lib/Frontend/CompilerInvocation.cpp +++ b/lib/Frontend/CompilerInvocation.cpp @@ -389,7 +389,7 @@ static bool ParseLangArgs(LangOptions &Opts, ArgList &Args, Args.hasArg(OPT_enable_experimental_flow_sensitive_concurrent_captures); Opts.EnableExperimentalEnumCodableDerivation |= - Args.hasArg(OPT_enable_experimental_enum_codable_derivation); + 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 537a1c4f3523d..51c106aab0cb7 100644 --- a/lib/Sema/DerivedConformanceCodable.cpp +++ b/lib/Sema/DerivedConformanceCodable.cpp @@ -15,6 +15,7 @@ // //===----------------------------------------------------------------------===// +#include "DerivedConformances.h" #include "TypeChecker.h" #include "swift/AST/Decl.h" #include "swift/AST/Expr.h" @@ -24,7 +25,6 @@ #include "swift/AST/Stmt.h" #include "swift/AST/Types.h" #include "swift/Basic/StringExtras.h" -#include "DerivedConformances.h" using namespace swift; @@ -55,10 +55,12 @@ static Identifier getVarNameForCoding(VarDecl *var) { } /// Compute the Identifier for the CodingKey of an enum case -static Identifier caseCodingKeyIdentifier(const ASTContext &C, EnumElementDecl* elt) { +static Identifier caseCodingKeyIdentifier(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()); + llvm::StringRef result = + camel_case::appendSentenceCase(scratch, C.Id_CodingKeys.str()); return C.getIdentifier(result); } @@ -398,7 +400,8 @@ static EnumElementDecl *lookupEnumCase(ASTContext &C, NominalTypeDecl *target, return dyn_cast(elementDecl); } -static NominalTypeDecl *lookupErrorContext(ASTContext &C, NominalTypeDecl *errorDecl) { +static NominalTypeDecl *lookupErrorContext(ASTContext &C, + NominalTypeDecl *errorDecl) { auto elementDecls = errorDecl->lookupDirect(C.Id_Context); if (elementDecls.empty()) return nullptr; @@ -423,7 +426,7 @@ static bool synthesizeCodingKeysEnum_enum(DerivedConformance &derived, auto *conformanceDC = derived.getConformanceContext(); auto *codingKeysEnum = lookupEvaluatedCodingKeysEnum(C, target); - llvm::SmallVector codingKeys; + llvm::SmallVector codingKeys; // Only derive CodingKeys enum if it is not already defined if (!codingKeysEnum) { @@ -450,7 +453,9 @@ static bool synthesizeCodingKeysEnum_enum(DerivedConformance &derived, for (auto *elementDecl : target->getAllElements()) { if (!caseNames.insert(elementDecl->getBaseIdentifier())) { elementDecl->diagnose(diag::codable_enum_duplicate_case_name_here, - derived.getProtocolType(), target->getDeclaredType(), elementDecl->getBaseIdentifier()); + derived.getProtocolType(), + target->getDeclaredType(), + elementDecl->getBaseIdentifier()); allConform = false; continue; } @@ -466,8 +471,8 @@ static bool synthesizeCodingKeysEnum_enum(DerivedConformance &derived, if (!derived.Nominal->lookupDirect(DeclName(enumIdentifier)).empty()) continue; - auto *caseEnum = new (C) EnumDecl( - SourceLoc(), enumIdentifier, SourceLoc(), inherited, nullptr, target); + auto *caseEnum = new (C) EnumDecl(SourceLoc(), enumIdentifier, SourceLoc(), + inherited, nullptr, target); caseEnum->setImplicit(); caseEnum->setAccess(AccessLevel::Private); @@ -476,16 +481,17 @@ static bool synthesizeCodingKeysEnum_enum(DerivedConformance &derived, if (elementParams) { for (size_t i = 0; i < elementParams->size(); i++) { auto *paramDecl = elementParams->get(i); - auto target = - conformanceDC->mapTypeIntoContext(paramDecl->getValueInterfaceType()); - if (TypeChecker::conformsToProtocol(target, derived.Protocol, conformanceDC) + auto target = conformanceDC->mapTypeIntoContext( + paramDecl->getValueInterfaceType()); + if (TypeChecker::conformsToProtocol(target, derived.Protocol, + conformanceDC) .isInvalid()) { TypeLoc typeLoc = { - paramDecl->getTypeReprOrParentPatternTypeRepr(), - paramDecl->getType(), + paramDecl->getTypeReprOrParentPatternTypeRepr(), + paramDecl->getType(), }; paramDecl->diagnose(diag::codable_non_conforming_property_here, - derived.getProtocolType(), typeLoc); + derived.getProtocolType(), typeLoc); conforms = false; } else { @@ -495,8 +501,8 @@ static bool synthesizeCodingKeysEnum_enum(DerivedConformance &derived, paramIdentifier = C.getIdentifier("_" + std::to_string(i)); } auto *elt = - new (C) EnumElementDecl(SourceLoc(), paramIdentifier, - nullptr, SourceLoc(), nullptr, caseEnum); + new (C) EnumElementDecl(SourceLoc(), paramIdentifier, nullptr, + SourceLoc(), nullptr, caseEnum); elt->setImplicit(); caseEnum->addMember(elt); } @@ -514,7 +520,7 @@ static bool synthesizeCodingKeysEnum_enum(DerivedConformance &derived, if (!allConform) return false; - for (auto* codingKeysEnum : codingKeys) { + for (auto *codingKeysEnum : codingKeys) { target->addMember(codingKeysEnum); } @@ -631,66 +637,81 @@ static CallExpr *createNestedContainerKeyedByForKeyCall( argNames); } -static ThrowStmt *createThrowDecodingErrorTypeMismatchStmt(ASTContext &C, DeclContext *DC, - NominalTypeDecl *targetDecl, Expr *containerExpr, Expr *debugMessage) { +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); + 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()}); + 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()); + DC->mapTypeIntoContext(targetDecl->getDeclaredInterfaceType()), C); + auto *targetTypeExpr = + new (C) DotSelfExpr(targetType, SourceLoc(), SourceLoc()); - auto *decodingErrorCallExpr = CallExpr::createImplicit(C, decodingErrorCall, - {targetTypeExpr, contextInitCallExpr}, - {Identifier(), Identifier()}); + 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) { +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); + 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()}); + 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); } @@ -865,10 +886,14 @@ deriveBodyEncodable_enum_encode(AbstractFunctionDecl *encodeDecl, void *) { 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); + 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 = caseCodingKeyIdentifier(C, elt); @@ -891,7 +916,6 @@ deriveBodyEncodable_enum_encode(AbstractFunctionDecl *encodeDecl, void *) { caseStatements.push_back(bindingDecl); caseStatements.push_back(nestedContainerDecl); - // TODO: use param decls to get names int i = 0; for (auto it = payloadVars.begin(); it != payloadVars.end(); it++, i++) { @@ -1322,16 +1346,15 @@ deriveBodyDecodable_enum_init(AbstractFunctionDecl *initDecl, void *) { CaseLabelItem(new (C) OptionalSomePattern(pat, SourceLoc())); llvm::SmallVector caseStatements; - + auto caseIdentifier = caseCodingKeyIdentifier(C, elt); auto *caseCodingKeys = lookupEvaluatedCodingKeysEnum(C, targetEnum, caseIdentifier); - auto *nestedContainerDecl = - createKeyedContainer(C, funcDC, C.getKeyedDecodingContainerDecl(), - caseCodingKeys->getDeclaredInterfaceType(), - VarDecl::Introducer::Var, - C.getIdentifier(StringRef("nestedContainer"))); + auto *nestedContainerDecl = createKeyedContainer( + C, funcDC, C.getKeyedDecodingContainerDecl(), + caseCodingKeys->getDeclaredInterfaceType(), VarDecl::Introducer::Var, + C.getIdentifier(StringRef("nestedContainer"))); auto *nestedContainerCall = createNestedContainerKeyedByForKeyCall( C, funcDC, containerExpr, caseCodingKeys, codingKeyCase); @@ -1342,8 +1365,8 @@ deriveBodyDecodable_enum_init(AbstractFunctionDecl *initDecl, void *) { auto *containerPattern = NamedPattern::createImplicit(C, nestedContainerDecl); auto *bindingDecl = PatternBindingDecl::createImplicit( - C, StaticSpellingKind::None, containerPattern, - tryNestedContainerCall, funcDC); + C, StaticSpellingKind::None, containerPattern, tryNestedContainerCall, + funcDC); caseStatements.push_back(bindingDecl); caseStatements.push_back(nestedContainerDecl); @@ -1358,8 +1381,7 @@ deriveBodyDecodable_enum_init(AbstractFunctionDecl *initDecl, void *) { if (identifier.empty()) { identifier = C.getIdentifier("_" + std::to_string(i)); } - auto *caseCodingKey = lookupEnumCase( - C, caseCodingKeys, identifier); + auto *caseCodingKey = lookupEnumCase(C, caseCodingKeys, identifier); params.push_back(getVarNameForCoding(paramDecl)); @@ -1373,9 +1395,8 @@ deriveBodyDecodable_enum_init(AbstractFunctionDecl *initDecl, void *) { } // Type.self - auto *parameterTypeExpr = - TypeExpr::createImplicit( - funcDC->mapTypeIntoContext(paramDecl->getInterfaceType()), C); + auto *parameterTypeExpr = TypeExpr::createImplicit( + funcDC->mapTypeIntoContext(paramDecl->getInterfaceType()), C); auto *parameterMetaTypeExpr = new (C) DotSelfExpr(parameterTypeExpr, SourceLoc(), SourceLoc()); // BarCodingKeys.x @@ -1413,16 +1434,16 @@ deriveBodyDecodable_enum_init(AbstractFunctionDecl *initDecl, void *) { if (params.empty()) { auto *selfCaseExpr = new (C) MemberRefExpr( - selfTypeExpr, SourceLoc(), elt, DeclNameLoc(), /*Implicit=*/true); + selfTypeExpr, SourceLoc(), elt, DeclNameLoc(), /*Implicit=*/true); - auto *selfRef = DerivedConformance::createSelfDeclRef(initDecl); + auto *selfRef = DerivedConformance::createSelfDeclRef(initDecl); - auto *assignExpr = - new (C) AssignExpr(selfRef, SourceLoc(), selfCaseExpr, - /*Implicit=*/true); + auto *assignExpr = + new (C) AssignExpr(selfRef, SourceLoc(), selfCaseExpr, + /*Implicit=*/true); - caseStatements.push_back(assignExpr); - } else { + caseStatements.push_back(assignExpr); + } else { // Foo.bar(x:) auto *selfCaseExpr = UnresolvedDotExpr::createImplicit( C, selfTypeExpr, elt->getBaseIdentifier(), C.AllocateCopy(params)); @@ -1454,37 +1475,44 @@ deriveBodyDecodable_enum_init(AbstractFunctionDecl *initDecl, void *) { // if container.allKeys.count != 1 { // let context = DecodingError.Context( // codingPath: container.codingPath, - // debugDescription: "Invalid number of keys found, expected one.") + // 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); + 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); + UnresolvedDotExpr::createImplicit(C, containerExpr, C.Id_allKeys); // container.allKeys.count - auto *keysCountExpr = UnresolvedDotExpr::createImplicit(C, allKeysExpr, C.Id_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 *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 *tupleExpr = TupleExpr::createImplicit(C, {keysCountExpr, oneExpr}, + {Identifier(), Identifier()}); - auto *cmpExpr = new (C) BinaryExpr( - cmpFuncExpr, tupleExpr, /*implicit*/ true); + auto *cmpExpr = + new (C) BinaryExpr(cmpFuncExpr, tupleExpr, /*implicit*/ true); cmpExpr->setThrows(false); - auto *guardBody = BraceStmt::create(C, SourceLoc(), { throwStmt }, SourceLoc(), /* Implicit */ true); + auto *guardBody = BraceStmt::create(C, SourceLoc(), {throwStmt}, + SourceLoc(), /* Implicit */ true); - auto *guardStmt = new (C) GuardStmt(SourceLoc(), cmpExpr, guardBody, /* Implicit */ true, C); + auto *guardStmt = new (C) + GuardStmt(SourceLoc(), cmpExpr, guardBody, /* Implicit */ true, C); statements.push_back(guardStmt); @@ -1948,7 +1976,8 @@ bool DerivedConformance::canDeriveEncodable(NominalTypeDecl *NTD) { ValueDecl *DerivedConformance::deriveEncodable(ValueDecl *requirement) { // We can only synthesize Encodable for structs, classes, and enums. if (!isa(Nominal) && !isa(Nominal) && - !(Context.LangOpts.EnableExperimentalEnumCodableDerivation && isa(Nominal))) + !(Context.LangOpts.EnableExperimentalEnumCodableDerivation && + isa(Nominal))) return nullptr; if (requirement->getBaseName() != Context.Id_encode) { @@ -1978,7 +2007,8 @@ ValueDecl *DerivedConformance::deriveEncodable(ValueDecl *requirement) { ValueDecl *DerivedConformance::deriveDecodable(ValueDecl *requirement) { // We can only synthesize Encodable for structs, classes, and enums. if (!isa(Nominal) && !isa(Nominal) && - (Context.LangOpts.EnableExperimentalEnumCodableDerivation && !isa(Nominal))) + (Context.LangOpts.EnableExperimentalEnumCodableDerivation && + !isa(Nominal))) return nullptr; if (requirement->getBaseName() != DeclBaseName::createConstructor()) { diff --git a/lib/Sema/DerivedConformances.cpp b/lib/Sema/DerivedConformances.cpp index debd5788d4073..d92c6d1ceecff 100644 --- a/lib/Sema/DerivedConformances.cpp +++ b/lib/Sema/DerivedConformances.cpp @@ -152,7 +152,8 @@ bool DerivedConformance::derivesProtocolConformance(DeclContext *DC, // unfortunately means that we expect a witness even if one will not be // produced, which requires DerivedConformance::deriveCodable to output // its own diagnostics. - return DC->getASTContext().LangOpts.EnableExperimentalEnumCodableDerivation; + return DC->getASTContext() + .LangOpts.EnableExperimentalEnumCodableDerivation; default: return false; From 84406b75f16c1159d0fa10ebb948ec14931b73d9 Mon Sep 17 00:00:00 2001 From: Dario Rexin Date: Fri, 19 Feb 2021 01:14:34 -0800 Subject: [PATCH 09/11] Address review feedback and fix tests --- include/swift/AST/DiagnosticsSema.def | 3 +- lib/Sema/DerivedConformanceCodable.cpp | 191 ++++++------------ test/IRGen/synthesized_conformance.swift | 2 +- .../synthesized_conformance_future.swift | 2 +- .../SILGen/synthesized_conformance_enum.swift | 4 +- 5 files changed, 73 insertions(+), 129 deletions(-) diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index f8018de7c33dc..a02e849efdf18 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -2909,7 +2909,8 @@ NOTE(decodable_suggest_overriding_init_here,none, 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)) + "cannot automatically synthesize %0 because %1 has duplicate " + "case name %2", (Type, Type, Identifier)) WARNING(decodable_property_will_not_be_decoded, none, "immutable property will not be decoded because it is declared with " diff --git a/lib/Sema/DerivedConformanceCodable.cpp b/lib/Sema/DerivedConformanceCodable.cpp index 51c106aab0cb7..6e9236aafcc36 100644 --- a/lib/Sema/DerivedConformanceCodable.cpp +++ b/lib/Sema/DerivedConformanceCodable.cpp @@ -16,6 +16,7 @@ //===----------------------------------------------------------------------===// #include "DerivedConformances.h" +#include "llvm/ADT/STLExtras.h" #include "TypeChecker.h" #include "swift/AST/Decl.h" #include "swift/AST/Expr.h" @@ -126,7 +127,7 @@ static EnumDecl *addImplicitCodingKeys(NominalTypeDecl *target) { } /// Validates the given CodingKeys enum decl by ensuring its cases are a 1-to-1 -/// match with the stored vars of the given type. +/// match with the the given VarDecls. static bool validateCodingKeysEnum(DerivedConformance &derived) { auto &C = derived.Context; auto codingKeysDecls = @@ -177,35 +178,22 @@ static bool validateCodingKeysEnum(DerivedConformance &derived) { return false; } - // 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). + // 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; } @@ -221,21 +209,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; @@ -245,108 +233,74 @@ 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; } /// Validates the given CodingKeys enum decl by ensuring its cases are a 1-to-1 -/// match with the stored vars of the given EnumElementDecl. +/// match with the stored vars of the given type. /// -/// \param elementDecl The \c EnumElementDecl to validate against. /// \param codingKeysDecl The \c CodingKeys enum decl to validate. -static bool validateCaseCodingKeysEnum(const DerivedConformance &derived, - EnumElementDecl *elementDecl, - EnumDecl *codingKeysDecl) { - auto &C = derived.Context; - auto conformanceDC = derived.getConformanceContext(); - +static bool validateCodingKeysEnum(const DerivedConformance &derived, + EnumDecl *codingKeysDecl) { // 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). - // - // 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; - if (elementDecl->hasAssociatedValues()) { - int i = 0; - auto params = elementDecl->getParameterList(); - for (auto it = params->begin(); it != params->end(); it++, i++) { - auto varDecl = *it; - if (!varDecl->isUserAccessible()) - continue; - - auto identifier = getVarNameForCoding(varDecl); - if (identifier.empty()) { - identifier = C.getIdentifier("_" + std::to_string(i)); - } - - properties[identifier] = varDecl; - } - } - - bool propertiesAreValid = true; - for (auto elt : codingKeysDecl->getAllElements()) { - auto it = properties.find(elt->getBaseIdentifier()); - if (it == properties.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; + llvm::SmallMapVector properties; + for (auto *varDecl : derived.Nominal->getStoredProperties()) { + if (!varDecl->isUserAccessible()) continue; - } - // We have a property to map to. Ensure it's {En,De}codable. - auto target = - conformanceDC->mapTypeIntoContext(it->second->getValueInterfaceType()); - if (TypeChecker::conformsToProtocol(target, derived.Protocol, conformanceDC) - .isInvalid()) { - TypeLoc typeLoc = { - it->second->getTypeReprOrParentPatternTypeRepr(), - it->second->getType(), - }; - it->second->diagnose(diag::codable_non_conforming_property_here, - derived.getProtocolType(), typeLoc); - propertiesAreValid = false; - } else { - // The property was valid. Remove it from the list. - properties.erase(it); - } + properties[getVarNameForCoding(varDecl)] = varDecl; } - if (!propertiesAreValid) - return false; + return validateCodingKeysEnum(derived, properties, codingKeysDecl); +} - // If there are any remaining properties 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) { - if (entry.second->hasDefaultExpr()) { +/// Validates the given CodingKeys enum decl by ensuring its cases are a 1-to-1 +/// match with the parameters of the given EnumElementDecl. +/// +/// \param elementDecl The \c EnumElementDecl to validate against. +/// \param codingKeysDecl The \c CodingKeys enum decl to validate. +static bool validateCaseCodingKeysEnum(const DerivedConformance &derived, + EnumElementDecl *elementDecl, + EnumDecl *codingKeysDecl) { + auto &C = derived.Context; + // 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())); } - // The var was not default initializable, and did not have an explicit - // initial value. - propertiesAreValid = false; - entry.second->diagnose(diag::codable_non_decoded_property_here, - derived.getProtocolType(), entry.first); + properties[identifier] = paramDecl; } } - return propertiesAreValid; + return validateCodingKeysEnum(derived, properties, codingKeysDecl); } @@ -363,19 +317,6 @@ static bool validateCaseCodingKeysEnum(const DerivedConformance &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; - - 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, Identifier identifier) { @@ -390,6 +331,11 @@ static EnumDecl *lookupEvaluatedCodingKeysEnum(ASTContext &C, 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)); @@ -884,10 +830,10 @@ deriveBodyEncodable_enum_encode(AbstractFunctionDecl *encodeDecl, void *) { // 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("Case '"); buffer.append(elt->getBaseIdentifier().str()); buffer.append( - "` cannot be decoded because it is not defined in CodingKeys."); + "' 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( @@ -903,7 +849,7 @@ deriveBodyEncodable_enum_encode(AbstractFunctionDecl *encodeDecl, void *) { auto *nestedContainerDecl = createKeyedContainer( C, funcDC, C.getKeyedEncodingContainerDecl(), caseCodingKeys->getDeclaredInterfaceType(), VarDecl::Introducer::Var, - C.getIdentifier(StringRef("nestedContainer"))); + C.Id_nestedContainer); auto *nestedContainerCall = createNestedContainerKeyedByForKeyCall( C, funcDC, containerExpr, caseCodingKeys, codingKeyCase); @@ -917,18 +863,17 @@ deriveBodyEncodable_enum_encode(AbstractFunctionDecl *encodeDecl, void *) { caseStatements.push_back(nestedContainerDecl); // TODO: use param decls to get names - int i = 0; - for (auto it = payloadVars.begin(); it != payloadVars.end(); it++, i++) { - auto *payloadVar = *it; + 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(i); + auto *paramDecl = elt->getParameterList()->get(entry.index()); auto caseCodingKeyIdentifier = getVarNameForCoding(paramDecl); if (caseCodingKeyIdentifier.empty()) { - caseCodingKeyIdentifier = C.getIdentifier("_" + std::to_string(i)); + caseCodingKeyIdentifier = C.getIdentifier("_" + std::to_string(entry.index())); } auto *caseCodingKey = lookupEnumCase(C, caseCodingKeys, caseCodingKeyIdentifier); @@ -1372,14 +1317,12 @@ deriveBodyDecodable_enum_init(AbstractFunctionDecl *initDecl, void *) { llvm::SmallVector decodeCalls; llvm::SmallVector params; - int i = 0; - auto *paramList = elt->getParameterList(); - if (paramList) { - for (auto it = paramList->begin(); it != paramList->end(); it++, i++) { - auto *paramDecl = *it; + 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(i)); + identifier = C.getIdentifier("_" + std::to_string(entry.index())); } auto *caseCodingKey = lookupEnumCase(C, caseCodingKeys, identifier); diff --git a/test/IRGen/synthesized_conformance.swift b/test/IRGen/synthesized_conformance.swift index d0ee05c50b42e..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 diff --git a/test/IRGen/synthesized_conformance_future.swift b/test/IRGen/synthesized_conformance_future.swift index 96fc3c79fb196..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 diff --git a/test/SILGen/synthesized_conformance_enum.swift b/test/SILGen/synthesized_conformance_enum.swift index 83367d001e297..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) From 4a49724f1736ab30d32ab73b70ca4ab7cc327c14 Mon Sep 17 00:00:00 2001 From: Dario Rexin Date: Mon, 22 Feb 2021 17:04:30 -0800 Subject: [PATCH 10/11] Add diagnostic for conflicting parameter identifiers --- include/swift/AST/DiagnosticsSema.def | 4 +++ lib/Sema/DerivedConformanceCodable.cpp | 36 +++++++++++++++---- ...ble_conflicting_parameter_identifier.swift | 10 ++++++ 3 files changed, 43 insertions(+), 7 deletions(-) create mode 100644 test/decl/protocol/special/coding/enum_codable_conflicting_parameter_identifier.swift diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index a02e849efdf18..4243fa746b802 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -2911,6 +2911,10 @@ NOTE(codable_suggest_overriding_init_here,none, 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/lib/Sema/DerivedConformanceCodable.cpp b/lib/Sema/DerivedConformanceCodable.cpp index 6e9236aafcc36..9a58641dc3082 100644 --- a/lib/Sema/DerivedConformanceCodable.cpp +++ b/lib/Sema/DerivedConformanceCodable.cpp @@ -422,14 +422,14 @@ static bool synthesizeCodingKeysEnum_enum(DerivedConformance &derived, caseEnum->setImplicit(); caseEnum->setAccess(AccessLevel::Private); - auto *elementParams = elementDecl->getParameterList(); bool conforms = true; - if (elementParams) { - for (size_t i = 0; i < elementParams->size(); i++) { - auto *paramDecl = elementParams->get(i); - auto target = conformanceDC->mapTypeIntoContext( + llvm::SmallMapVector params; + if (elementDecl->hasAssociatedValues()) { + for (auto entry : llvm::enumerate(*elementDecl->getParameterList())) { + auto *paramDecl = entry.value(); + auto paramType = conformanceDC->mapTypeIntoContext( paramDecl->getValueInterfaceType()); - if (TypeChecker::conformsToProtocol(target, derived.Protocol, + if (TypeChecker::conformsToProtocol(paramType, derived.Protocol, conformanceDC) .isInvalid()) { TypeLoc typeLoc = { @@ -443,8 +443,30 @@ static bool synthesizeCodingKeysEnum_enum(DerivedConformance &derived, } else { // 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(i)); + paramIdentifier = C.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 + // TODO: add diagnostic + userDefinedParam = inserted.first->second; + } + + userDefinedParam->diagnose(diag::codable_enum_duplicate_parameter_name_here, + derived.getProtocolType(), + target->getDeclaredType(), + paramIdentifier, + elementDecl->getBaseIdentifier()); + conforms = false; + continue; } auto *elt = new (C) EnumElementDecl(SourceLoc(), paramIdentifier, nullptr, 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}} +} From 677a701d4f7aafd3ca2533281d03737aa3c801e5 Mon Sep 17 00:00:00 2001 From: Dario Rexin Date: Wed, 24 Feb 2021 09:57:22 -0800 Subject: [PATCH 11/11] Restructure code after rebase --- lib/Sema/DerivedConformanceCodable.cpp | 1738 +++++++++-------- lib/Sema/DerivedConformances.cpp | 13 - .../enum_codable_nonconforming_property.swift | 2 +- 3 files changed, 919 insertions(+), 834 deletions(-) diff --git a/lib/Sema/DerivedConformanceCodable.cpp b/lib/Sema/DerivedConformanceCodable.cpp index 9a58641dc3082..d5a04be0b358e 100644 --- a/lib/Sema/DerivedConformanceCodable.cpp +++ b/lib/Sema/DerivedConformanceCodable.cpp @@ -15,9 +15,8 @@ // //===----------------------------------------------------------------------===// -#include "DerivedConformances.h" -#include "llvm/ADT/STLExtras.h" #include "TypeChecker.h" +#include "llvm/ADT/STLExtras.h" #include "swift/AST/Decl.h" #include "swift/AST/Expr.h" #include "swift/AST/Module.h" @@ -26,6 +25,7 @@ #include "swift/AST/Stmt.h" #include "swift/AST/Types.h" #include "swift/Basic/StringExtras.h" +#include "DerivedConformances.h" using namespace swift; @@ -56,8 +56,8 @@ static Identifier getVarNameForCoding(VarDecl *var) { } /// Compute the Identifier for the CodingKey of an enum case -static Identifier caseCodingKeyIdentifier(const ASTContext &C, - EnumElementDecl *elt) { +static Identifier caseCodingKeysIdentifier(const ASTContext &C, + EnumElementDecl *elt) { llvm::SmallString<16> scratch; camel_case::appendSentenceCase(scratch, elt->getBaseIdentifier().str()); llvm::StringRef result = @@ -65,6 +65,141 @@ static Identifier caseCodingKeyIdentifier(const ASTContext &C, 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 @@ -75,6 +210,10 @@ static Identifier caseCodingKeyIdentifier(const ASTContext &C, // 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()); @@ -126,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 the given VarDecls. -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(); @@ -162,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 = @@ -175,9 +297,24 @@ static bool validateCodingKeysEnum(DerivedConformance &derived) { codingKeysTypeDecl->diagnose( diag::codable_codingkeys_type_is_not_an_enum_here, derived.getProtocolType()); - return false; + return nullptr; } + 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 @@ -188,7 +325,6 @@ static bool validateCodingKeysEnum(DerivedConformance &derived) { for (auto elt : codingKeysDecl->getAllElements()) { 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 @@ -199,7 +335,7 @@ static bool validateCodingKeysEnum(DerivedConformance &derived) { // 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()) { @@ -250,38 +386,115 @@ static bool validateCodingKeysEnum(DerivedConformance &derived) { return varDeclsAreValid; } -/// Validates the given CodingKeys enum decl by ensuring its cases are a 1-to-1 -/// match with the stored vars of the given type. -/// -/// \param codingKeysDecl The \c CodingKeys enum decl to validate. -static bool validateCodingKeysEnum(const DerivedConformance &derived, - EnumDecl *codingKeysDecl) { - // 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). +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()); + } - // 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; + auto *codingKeysDecl = validateCodingKeysType(derived, + codingKeysTypeDecl); + if (!codingKeysDecl) + return false; - properties[getVarNameForCoding(varDecl)] = varDecl; + 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 validateCodingKeysEnum(derived, properties, codingKeysDecl); + return casesAreValid; } -/// Validates the given CodingKeys enum decl by ensuring its cases are a 1-to-1 -/// match with the parameters of the given EnumElementDecl. +/// 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. /// /// \param elementDecl The \c EnumElementDecl to validate against. -/// \param codingKeysDecl The \c CodingKeys enum decl to validate. static bool validateCaseCodingKeysEnum(const DerivedConformance &derived, - EnumElementDecl *elementDecl, - EnumDecl *codingKeysDecl) { + EnumElementDecl *elementDecl) { auto &C = derived.Context; + auto *enumDecl = dyn_cast(derived.Nominal); + if (!enumDecl) { + return false; + } + + auto *codingKeysEnum = lookupEvaluatedCodingKeysEnum(C, enumDecl); + + // 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; @@ -300,202 +513,46 @@ static bool validateCaseCodingKeysEnum(const DerivedConformance &derived, } } - return validateCodingKeysEnum(derived, properties, codingKeysDecl); + return validateCodingKeysEnum(derived, properties, codingKeysTypeDecl); } - - -/// Fetches the \c CodingKeys enum nested in \c target, potentially reaching -/// through a typealias if the "CodingKeys" entity is a typealias. +/// Creates a new var decl representing /// -/// This is only useful once a \c CodingKeys enum has been validated (via \c -/// hasValidCodingKeysEnum) or synthesized (via \c synthesizeCodingKeysEnum). +/// var/let identifier : containerBase /// -/// \param C The \c ASTContext to perform the lookup in. +/// \c containerBase is the name of the type to use as the base (either +/// \c KeyedEncodingContainer or \c KeyedDecodingContainer). /// -/// \param target The target type to look in. +/// \param C The AST context to create the decl 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); -} +/// \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. +/// +/// \param identifier Identifier of the variable. +static VarDecl *createKeyedContainer(ASTContext &C, DeclContext *DC, + NominalTypeDecl *keyedContainerDecl, + Type keyType, + VarDecl::Introducer introducer, + Identifier identifier) { + // Bind Keyed*Container to Keyed*Container + Type boundType[1] = {keyType}; + auto containerType = BoundGenericType::get(keyedContainerDecl, Type(), + C.AllocateCopy(boundType)); -static EnumDecl *lookupEvaluatedCodingKeysEnum(ASTContext &C, - NominalTypeDecl *target) { - return lookupEvaluatedCodingKeysEnum(C, target, C.Id_CodingKeys); + // let container : Keyed*Container + auto *containerDecl = new (C) VarDecl(/*IsStatic=*/false, introducer, + SourceLoc(), identifier, DC); + containerDecl->setImplicit(); + containerDecl->setSynthesized(); + containerDecl->setInterfaceType(containerType); + return containerDecl; } -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 bool synthesizeCodingKeysEnum_enum(DerivedConformance &derived, - EnumDecl *target) { - auto &C = derived.Context; - - // We want to look through all the var declarations of this type to create - // enum cases based on those var names. - auto *codingKeyProto = C.getProtocol(KnownProtocolKind::CodingKey); - auto codingKeyType = codingKeyProto->getDeclaredInterfaceType(); - TypeLoc protoTypeLoc[1] = {TypeLoc::withoutLoc(codingKeyType)}; - ArrayRef inherited = C.AllocateCopy(protoTypeLoc); - - bool allConform = true; - auto *conformanceDC = derived.getConformanceContext(); - auto *codingKeysEnum = lookupEvaluatedCodingKeysEnum(C, target); - - llvm::SmallVector codingKeys; - - // Only derive CodingKeys enum if it is not already defined - if (!codingKeysEnum) { - 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); - - codingKeysEnum = enumDecl; - codingKeys.push_back(enumDecl); - } - - llvm::SmallSetVector caseNames; - for (auto *elementDecl : target->getAllElements()) { - if (!caseNames.insert(elementDecl->getBaseIdentifier())) { - elementDecl->diagnose(diag::codable_enum_duplicate_case_name_here, - derived.getProtocolType(), - target->getDeclaredType(), - elementDecl->getBaseIdentifier()); - allConform = false; - continue; - } - auto enumIdentifier = caseCodingKeyIdentifier(C, elementDecl); - - // Only derive if this case exist in the CodingKeys enum - auto *codingKeyCase = - lookupEnumCase(C, codingKeysEnum, elementDecl->getBaseIdentifier()); - if (!codingKeyCase) - continue; - - // Only derive if it is not already defined - if (!derived.Nominal->lookupDirect(DeclName(enumIdentifier)).empty()) - continue; - - auto *caseEnum = new (C) EnumDecl(SourceLoc(), enumIdentifier, SourceLoc(), - inherited, nullptr, target); - caseEnum->setImplicit(); - caseEnum->setAccess(AccessLevel::Private); - - bool conforms = true; - llvm::SmallMapVector params; - if (elementDecl->hasAssociatedValues()) { - for (auto entry : llvm::enumerate(*elementDecl->getParameterList())) { - auto *paramDecl = entry.value(); - auto paramType = conformanceDC->mapTypeIntoContext( - paramDecl->getValueInterfaceType()); - if (TypeChecker::conformsToProtocol(paramType, derived.Protocol, - conformanceDC) - .isInvalid()) { - TypeLoc typeLoc = { - paramDecl->getTypeReprOrParentPatternTypeRepr(), - paramDecl->getType(), - }; - paramDecl->diagnose(diag::codable_non_conforming_property_here, - derived.getProtocolType(), typeLoc); - - conforms = false; - } else { - // 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 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 - // TODO: add diagnostic - userDefinedParam = inserted.first->second; - } - - userDefinedParam->diagnose(diag::codable_enum_duplicate_parameter_name_here, - derived.getProtocolType(), - target->getDeclaredType(), - paramIdentifier, - elementDecl->getBaseIdentifier()); - conforms = false; - continue; - } - auto *elt = - new (C) EnumElementDecl(SourceLoc(), paramIdentifier, nullptr, - SourceLoc(), nullptr, caseEnum); - elt->setImplicit(); - caseEnum->addMember(elt); - } - } - allConform = allConform && conforms; - } - - if (conforms) { - // Forcibly derive conformance to CodingKey. - TypeChecker::checkConformancesInContext(caseEnum); - codingKeys.push_back(caseEnum); - } - } - - if (!allConform) - return false; - - for (auto *codingKeysEnum : codingKeys) { - target->addMember(codingKeysEnum); - } - - return true; -} - - /// Creates a new var decl representing /// /// var/let container : containerBase @@ -512,25 +569,12 @@ static bool synthesizeCodingKeysEnum_enum(DerivedConformance &derived, /// \param keyType The key type to bind to the container type. /// /// \param introducer Whether to declare the variable as immutable. -/// -/// \param name Name of the resulting variable static VarDecl *createKeyedContainer(ASTContext &C, DeclContext *DC, NominalTypeDecl *keyedContainerDecl, Type keyType, - VarDecl::Introducer introducer, - Identifier name) { - // Bind Keyed*Container to Keyed*Container - Type boundType[1] = {keyType}; - auto containerType = BoundGenericType::get(keyedContainerDecl, Type(), - C.AllocateCopy(boundType)); - - // let container : Keyed*Container - auto *containerDecl = - new (C) VarDecl(/*IsStatic=*/false, introducer, SourceLoc(), name, DC); - containerDecl->setImplicit(); - containerDecl->setSynthesized(); - containerDecl->setInterfaceType(containerType); - return containerDecl; + VarDecl::Introducer introducer) { + return createKeyedContainer(C, DC, keyedContainerDecl, keyType, + introducer, C.Id_container); } /// Creates a new \c CallExpr representing @@ -725,6 +769,167 @@ lookupVarDeclForCodingKeysCase(DeclContext *conformanceDC, llvm_unreachable("Should have found at least 1 var decl"); } +/// Synthesizes the body for `func encode(to encoder: Encoder) throws`. +/// +/// \param encodeDecl The function decl whose body to synthesize. +static std::pair +deriveBodyEncodable_encode(AbstractFunctionDecl *encodeDecl, void *) { + // struct Foo : Codable { + // var x: Int + // var y: String + // + // // Already derived by this point if possible. + // @derived enum CodingKeys : CodingKey { + // case x + // case y + // } + // + // @derived func encode(to encoder: Encoder) throws { + // var container = encoder.container(keyedBy: CodingKeys.self) + // try container.encode(x, forKey: .x) + // try container.encode(y, forKey: .y) + // } + // } + + // The enclosing type decl. + auto conformanceDC = encodeDecl->getDeclContext(); + auto *targetDecl = conformanceDC->getSelfNominalTypeDecl(); + + 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, targetDecl); + // 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 codingKeysType = codingKeysEnum->getDeclaredType(); + 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); + + // Now need to generate `try container.encode(x, forKey: .x)` for all + // existing properties. Optional properties get `encodeIfPresent`. + for (auto *elt : codingKeysEnum->getAllElements()) { + VarDecl *varDecl; + Type varType; // not used in Encodable synthesis + bool useIfPresentVariant; + + std::tie(varDecl, varType, useIfPresentVariant) = + lookupVarDeclForCodingKeysCase(conformanceDC, elt, targetDecl); + + // self.x + auto *selfRef = DerivedConformance::createSelfDeclRef(encodeDecl); + auto *varExpr = new (C) MemberRefExpr(selfRef, SourceLoc(), + ConcreteDeclRef(varDecl), + DeclNameLoc(), /*Implicit=*/true); + + // CodingKeys.x + auto *metaTyRef = TypeExpr::createImplicit(codingKeysType, C); + auto *keyExpr = new (C) MemberRefExpr(metaTyRef, SourceLoc(), elt, + 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, containerExpr, + methodName, argNames); + + // container.encode(self.x, forKey: CodingKeys.x) + Expr *args[2] = {varExpr, keyExpr}; + auto *callExpr = CallExpr::createImplicit(C, encodeCall, + C.AllocateCopy(args), + C.AllocateCopy(argNames)); + + // try container.encode(self.x, forKey: CodingKeys.x) + auto *tryExpr = new (C) TryExpr(SourceLoc(), callExpr, Type(), + /*Implicit=*/true); + statements.push_back(tryExpr); + } + + // Classes which inherit from something Codable should encode super as well. + if (superclassConformsTo(dyn_cast(targetDecl), + KnownProtocolKind::Encodable)) { + // Need to generate `try super.encode(to: container.superEncoder())` + + // superEncoder() + auto *method = UnresolvedDeclRefExpr::createImplicit(C, C.Id_superEncoder); + + // container.superEncoder() + auto *superEncoderRef = new (C) DotSyntaxCallExpr(containerExpr, + SourceLoc(), method); + + // encode(to:) expr + auto *encodeDeclRef = new (C) DeclRefExpr(ConcreteDeclRef(encodeDecl), + DeclNameLoc(), /*Implicit=*/true); + + // super + auto *superRef = new (C) SuperRefExpr(encodeDecl->getImplicitSelfDecl(), + SourceLoc(), /*Implicit=*/true); + + // super.encode(to:) + auto *encodeCall = new (C) DotSyntaxCallExpr(superRef, SourceLoc(), + encodeDeclRef); + + // super.encode(to: container.superEncoder()) + Expr *args[1] = {superEncoderRef}; + Identifier argLabels[1] = {C.Id_to}; + auto *callExpr = CallExpr::createImplicit(C, encodeCall, + C.AllocateCopy(args), + C.AllocateCopy(argLabels)); + + // try super.encode(to: container.superEncoder()) + auto *tryExpr = new (C) TryExpr(SourceLoc(), callExpr, Type(), + /*Implicit=*/true); + statements.push_back(tryExpr); + } + + auto *body = BraceStmt::create(C, SourceLoc(), statements, SourceLoc(), + /*implicit=*/true); + return { body, /*isTypeChecked=*/false }; +} + static std::pair deriveBodyEncodable_enum_encode(AbstractFunctionDecl *encodeDecl, void *) { // enum Foo : Codable { @@ -785,7 +990,7 @@ deriveBodyEncodable_enum_encode(AbstractFunctionDecl *encodeDecl, void *) { auto *containerDecl = createKeyedContainer(C, funcDC, C.getKeyedEncodingContainerDecl(), codingKeysEnum->getDeclaredInterfaceType(), - VarDecl::Introducer::Var, C.Id_container); + VarDecl::Introducer::Var); auto *containerExpr = new (C) DeclRefExpr(ConcreteDeclRef(containerDecl), DeclNameLoc(), @@ -864,7 +1069,7 @@ deriveBodyEncodable_enum_encode(AbstractFunctionDecl *encodeDecl, void *) { C, funcDC, selfRefExpr, containerExpr, debugMessage); caseStatements.push_back(throwStmt); } else { - auto caseIdentifier = caseCodingKeyIdentifier(C, elt); + auto caseIdentifier = caseCodingKeysIdentifier(C, elt); auto *caseCodingKeys = lookupEvaluatedCodingKeysEnum(C, enumDecl, caseIdentifier); @@ -893,12 +1098,12 @@ deriveBodyEncodable_enum_encode(AbstractFunctionDecl *encodeDecl, void *) { auto payloadVarRef = new (C) DeclRefExpr(payloadVar, DeclNameLoc(), /*implicit*/ true); auto *paramDecl = elt->getParameterList()->get(entry.index()); - auto caseCodingKeyIdentifier = getVarNameForCoding(paramDecl); - if (caseCodingKeyIdentifier.empty()) { - caseCodingKeyIdentifier = C.getIdentifier("_" + std::to_string(entry.index())); + auto caseCodingKeysIdentifier = getVarNameForCoding(paramDecl); + if (caseCodingKeysIdentifier.empty()) { + caseCodingKeysIdentifier = C.getIdentifier("_" + std::to_string(entry.index())); } auto *caseCodingKey = - lookupEnumCase(C, caseCodingKeys, caseCodingKeyIdentifier); + lookupEnumCase(C, caseCodingKeys, caseCodingKeysIdentifier); // If there is no key defined for this parameter, skip it. if (!caseCodingKey) @@ -967,167 +1172,6 @@ deriveBodyEncodable_enum_encode(AbstractFunctionDecl *encodeDecl, void *) { return {body, /*isTypeChecked=*/false}; } -/// Synthesizes the body for `func encode(to encoder: Encoder) throws`. -/// -/// \param encodeDecl The function decl whose body to synthesize. -static std::pair -deriveBodyEncodable_encode(AbstractFunctionDecl *encodeDecl, void *) { - // struct Foo : Codable { - // var x: Int - // var y: String - // - // // Already derived by this point if possible. - // @derived enum CodingKeys : CodingKey { - // case x - // case y - // } - // - // @derived func encode(to encoder: Encoder) throws { - // var container = encoder.container(keyedBy: CodingKeys.self) - // try container.encode(x, forKey: .x) - // try container.encode(y, forKey: .y) - // } - // } - - // The enclosing type decl. - auto conformanceDC = encodeDecl->getDeclContext(); - auto *targetDecl = conformanceDC->getSelfNominalTypeDecl(); - - 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, targetDecl); - // 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 codingKeysType = codingKeysEnum->getDeclaredType(); - auto *containerDecl = - createKeyedContainer(C, funcDC, C.getKeyedEncodingContainerDecl(), - codingKeysEnum->getDeclaredInterfaceType(), - VarDecl::Introducer::Var, C.Id_container); - - 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); - - // Now need to generate `try container.encode(x, forKey: .x)` for all - // existing properties. Optional properties get `encodeIfPresent`. - for (auto *elt : codingKeysEnum->getAllElements()) { - VarDecl *varDecl; - Type varType; // not used in Encodable synthesis - bool useIfPresentVariant; - - std::tie(varDecl, varType, useIfPresentVariant) = - lookupVarDeclForCodingKeysCase(conformanceDC, elt, targetDecl); - - // self.x - auto *selfRef = DerivedConformance::createSelfDeclRef(encodeDecl); - auto *varExpr = - new (C) MemberRefExpr(selfRef, SourceLoc(), ConcreteDeclRef(varDecl), - DeclNameLoc(), /*Implicit=*/true); - - // CodingKeys.x - auto *metaTyRef = TypeExpr::createImplicit(codingKeysType, C); - auto *keyExpr = new (C) MemberRefExpr(metaTyRef, SourceLoc(), elt, - 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, containerExpr, - methodName, argNames); - - // container.encode(self.x, forKey: CodingKeys.x) - Expr *args[2] = {varExpr, keyExpr}; - auto *callExpr = CallExpr::createImplicit(C, encodeCall, - C.AllocateCopy(args), - C.AllocateCopy(argNames)); - - // try container.encode(self.x, forKey: CodingKeys.x) - auto *tryExpr = new (C) TryExpr(SourceLoc(), callExpr, Type(), - /*Implicit=*/true); - statements.push_back(tryExpr); - } - - // Classes which inherit from something Codable should encode super as well. - if (superclassConformsTo(dyn_cast(targetDecl), - KnownProtocolKind::Encodable)) { - // Need to generate `try super.encode(to: container.superEncoder())` - - // superEncoder() - auto *method = UnresolvedDeclRefExpr::createImplicit(C, C.Id_superEncoder); - - // container.superEncoder() - auto *superEncoderRef = new (C) DotSyntaxCallExpr(containerExpr, - SourceLoc(), method); - - // encode(to:) expr - auto *encodeDeclRef = new (C) DeclRefExpr(ConcreteDeclRef(encodeDecl), - DeclNameLoc(), /*Implicit=*/true); - - // super - auto *superRef = new (C) SuperRefExpr(encodeDecl->getImplicitSelfDecl(), - SourceLoc(), /*Implicit=*/true); - - // super.encode(to:) - auto *encodeCall = new (C) DotSyntaxCallExpr(superRef, SourceLoc(), - encodeDeclRef); - - // super.encode(to: container.superEncoder()) - Expr *args[1] = {superEncoderRef}; - Identifier argLabels[1] = {C.Id_to}; - auto *callExpr = CallExpr::createImplicit(C, encodeCall, - C.AllocateCopy(args), - C.AllocateCopy(argLabels)); - - // try super.encode(to: container.superEncoder()) - auto *tryExpr = new (C) TryExpr(SourceLoc(), callExpr, Type(), - /*Implicit=*/true); - statements.push_back(tryExpr); - } - - auto *body = BraceStmt::create(C, SourceLoc(), statements, SourceLoc(), - /*implicit=*/true); - return { body, /*isTypeChecked=*/false }; -} - /// Synthesizes a function declaration for `encode(to: Encoder) throws` with a /// lazily synthesized body for the given type. /// @@ -1135,7 +1179,6 @@ deriveBodyEncodable_encode(AbstractFunctionDecl *encodeDecl, void *) { static FuncDecl *deriveEncodable_encode(DerivedConformance &derived) { auto &C = derived.Context; auto conformanceDC = derived.getConformanceContext(); - auto *targetDecl = conformanceDC->getSelfNominalTypeDecl(); // Expected type: (Self) -> (Encoder) throws -> () // Constructed as: func type @@ -1168,7 +1211,7 @@ static FuncDecl *deriveEncodable_encode(DerivedConformance &derived) { conformanceDC); encodeDecl->setSynthesized(); - if (auto *enumDecl = dyn_cast(targetDecl)) { + if (dyn_cast(derived.Nominal)) { encodeDecl->setBodySynthesizer(deriveBodyEncodable_enum_encode); } else { encodeDecl->setBodySynthesizer(deriveBodyEncodable_encode); @@ -1194,55 +1237,34 @@ static FuncDecl *deriveEncodable_encode(DerivedConformance &derived) { /// /// \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) +deriveBodyDecodable_init(AbstractFunctionDecl *initDecl, void *) { + // struct Foo : Codable { + // var x: Int + // var 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 - // } + // case x + // 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) - // } + // x = try container.decode(Type.self, forKey: .x) + // y = try container.decode(Type.self, forKey: .y) // } + // } // The enclosing type decl. auto conformanceDC = initDecl->getDeclContext(); - auto *targetEnum = conformanceDC->getSelfEnumDecl(); + auto *targetDecl = conformanceDC->getSelfNominalTypeDecl(); 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); + auto *codingKeysEnum = lookupEvaluatedCodingKeysEnum(C, targetDecl); // We should have bailed already if: // a) The type does not have CodingKeys // b) The type is not an enum @@ -1253,18 +1275,19 @@ deriveBodyDecodable_enum_init(AbstractFunctionDecl *initDecl, void *) { // 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, C.Id_container); + auto codingKeysType = codingKeysEnum->getDeclaredType(); + auto *containerDecl = createKeyedContainer(C, funcDC, + C.getKeyedDecodingContainerDecl(), + codingKeysEnum->getDeclaredInterfaceType(), + VarDecl::Introducer::Let); - auto *containerExpr = - new (C) DeclRefExpr(ConcreteDeclRef(containerDecl), DeclNameLoc(), - /*Implicit=*/true, AccessSemantics::DirectToStorage); + auto *containerExpr = new (C) DeclRefExpr(ConcreteDeclRef(containerDecl), + DeclNameLoc(), /*Implicit=*/true, + AccessSemantics::DirectToStorage); SmallVector statements; - if (codingKeysEnum->hasCases()) { + auto enumElements = codingKeysEnum->getAllElements(); + if (!enumElements.empty()) { // Need to generate // `let container = try decoder.container(keyedBy: CodingKeys.self)` // `let container` (containerExpr) is generated above. @@ -1291,244 +1314,249 @@ deriveBodyDecodable_enum_init(AbstractFunctionDecl *initDecl, void *) { 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; + // Now need to generate `x = try container.decode(Type.self, forKey: .x)` + // for all existing properties. Optional properties get `decodeIfPresent`. + for (auto *elt : enumElements) { + VarDecl *varDecl; + Type varType; + bool useIfPresentVariant; - // generate: case .: - auto pat = new (C) EnumElementPattern( - TypeExpr::createImplicit(funcDC->mapTypeIntoContext(codingKeysType), - C), - SourceLoc(), DeclNameLoc(), DeclNameRef(), codingKeyCase, nullptr); - pat->setImplicit(); - pat->setType(codingKeysType); + std::tie(varDecl, varType, useIfPresentVariant) = + lookupVarDeclForCodingKeysCase(conformanceDC, elt, targetDecl); - auto labelItem = - CaseLabelItem(new (C) OptionalSomePattern(pat, SourceLoc())); + // Don't output a decode statement for a let with an initial value. + if (varDecl->isLet() && varDecl->isParentInitialized()) { + // But emit a warning to let the user know that it won't be decoded. + auto lookupResult = + codingKeysEnum->lookupDirect(varDecl->getBaseName()); + auto keyExistsInCodingKeys = + llvm::any_of(lookupResult, [&](ValueDecl *VD) { + if (isa(VD)) { + return VD->getBaseName() == varDecl->getBaseName(); + } + return false; + }); + auto *encodableProto = C.getProtocol(KnownProtocolKind::Encodable); + bool conformsToEncodable = + conformanceDC->getParentModule()->lookupConformance( + targetDecl->getDeclaredInterfaceType(), encodableProto) != nullptr; - llvm::SmallVector caseStatements; - - auto caseIdentifier = caseCodingKeyIdentifier(C, elt); - auto *caseCodingKeys = - lookupEvaluatedCodingKeysEnum(C, targetEnum, caseIdentifier); - - auto *nestedContainerDecl = createKeyedContainer( - C, funcDC, C.getKeyedDecodingContainerDecl(), - caseCodingKeys->getDeclaredInterfaceType(), VarDecl::Introducer::Var, - C.getIdentifier(StringRef("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()); + // Strategy to use for CodingKeys enum diagnostic part - this is to + // make the behaviour more explicit: + // + // 1. If we have an *implicit* CodingKeys enum: + // (a) If the type is Decodable only, explicitly define the enum and + // remove the key from it. This makes it explicit that the key + // will not be decoded. + // (b) If the type is Codable, explicitly define the enum and keep the + // key in it. This is because removing the key will break encoding + // which is mostly likely not what the user expects. + // + // 2. If we have an *explicit* CodingKeys enum: + // (a) If the type is Decodable only and the key exists in the enum, + // then explicitly remove the key from the enum. This makes it + // explicit that the key will not be decoded. + // (b) If the type is Decodable only and the key does not exist in + // the enum, do nothing. This is because the user has explicitly + // made it clear that that they don't want the key to be decoded. + // (c) If the type is Codable, do nothing. This is because removing + // the key will break encoding which is most likely not what the + // user expects. + if (!codingKeysEnum->isImplicit()) { + if (conformsToEncodable || !keyExistsInCodingKeys) { 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); + varDecl->diagnose(diag::decodable_property_will_not_be_decoded); + if (codingKeysEnum->isImplicit()) { + varDecl->diagnose( + diag::decodable_property_init_or_codingkeys_implicit, + conformsToEncodable ? 0 : 1, varDecl->getName()); + } else { + varDecl->diagnose( + diag::decodable_property_init_or_codingkeys_explicit, + varDecl->getName()); + } + if (auto *PBD = varDecl->getParentPatternBinding()) { + varDecl->diagnose(diag::decodable_make_property_mutable) + .fixItReplace(PBD->getLoc(), "var"); + } - // Foo.bar - auto *selfTypeExpr = - TypeExpr::createImplicit(targetEnum->getDeclaredType(), C); + continue; + } - if (params.empty()) { - auto *selfCaseExpr = new (C) MemberRefExpr( - selfTypeExpr, SourceLoc(), elt, DeclNameLoc(), /*Implicit=*/true); + auto methodName = + useIfPresentVariant ? C.Id_decodeIfPresent : C.Id_decode; - auto *selfRef = DerivedConformance::createSelfDeclRef(initDecl); + // Type.self (where Type === type(of: x)) + // Calculating the metatype needs to happen after potential Optional + // unwrapping in lookupVarDeclForCodingKeysCase(). + auto *metaTyRef = TypeExpr::createImplicit(varType, C); + auto *targetExpr = new (C) DotSelfExpr(metaTyRef, SourceLoc(), + SourceLoc(), varType); - auto *assignExpr = - new (C) AssignExpr(selfRef, SourceLoc(), selfCaseExpr, - /*Implicit=*/true); + // CodingKeys.x + metaTyRef = TypeExpr::createImplicit(codingKeysType, C); + auto *keyExpr = new (C) MemberRefExpr(metaTyRef, SourceLoc(), + elt, DeclNameLoc(), /*Implicit=*/true); - caseStatements.push_back(assignExpr); - } else { - // Foo.bar(x:) - auto *selfCaseExpr = UnresolvedDotExpr::createImplicit( - C, selfTypeExpr, elt->getBaseIdentifier(), C.AllocateCopy(params)); + // decode(_:forKey:)/decodeIfPresent(_:forKey:) + SmallVector argNames{Identifier(), C.Id_forKey}; + auto *decodeCall = UnresolvedDotExpr::createImplicit( + C, containerExpr, methodName, argNames); - // Foo.bar(x: try nestedContainer.decode(Int.self, forKey: .x)) - auto *caseCallExpr = CallExpr::createImplicit( - C, selfCaseExpr, C.AllocateCopy(decodeCalls), - C.AllocateCopy(params)); + // container.decode(Type.self, forKey: CodingKeys.x) + Expr *args[2] = {targetExpr, keyExpr}; + auto *callExpr = CallExpr::createImplicit(C, decodeCall, + C.AllocateCopy(args), + C.AllocateCopy(argNames)); - // self = Foo.bar(x: try nestedContainer.decode(Int.self)) - auto *assignExpr = - new (C) AssignExpr(selfRef, SourceLoc(), caseCallExpr, - /*Implicit=*/true); + // try container.decode(Type.self, forKey: CodingKeys.x) + auto *tryExpr = new (C) TryExpr(SourceLoc(), callExpr, Type(), + /*Implicit=*/true); - caseStatements.push_back(assignExpr); - } + auto *selfRef = DerivedConformance::createSelfDeclRef(initDecl); + auto *varExpr = UnresolvedDotExpr::createImplicit(C, selfRef, + varDecl->getName()); + auto *assignExpr = new (C) AssignExpr(varExpr, SourceLoc(), tryExpr, + /*Implicit=*/true); + statements.push_back(assignExpr); + } + } - auto body = - BraceStmt::create(C, SourceLoc(), caseStatements, SourceLoc()); + // Classes which have a superclass must call super.init(from:) if the + // superclass is Decodable, or super.init() if it is not. + if (auto *classDecl = dyn_cast(targetDecl)) { + if (auto *superclassDecl = classDecl->getSuperclassDecl()) { + if (superclassConformsTo(classDecl, KnownProtocolKind::Decodable)) { + // Need to generate `try super.init(from: container.superDecoder())` - cases.push_back(CaseStmt::create(C, CaseParentKind::Switch, SourceLoc(), - labelItem, SourceLoc(), SourceLoc(), - body, - /*case body vardecls*/ None)); - } + // container.superDecoder + auto *superDecoderRef = + UnresolvedDotExpr::createImplicit(C, containerExpr, + C.Id_superDecoder); - // 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.superDecoder() + auto *superDecoderCall = + CallExpr::createImplicit(C, superDecoderRef, ArrayRef(), + ArrayRef()); - // container.allKeys - auto *allKeysExpr = - UnresolvedDotExpr::createImplicit(C, containerExpr, C.Id_allKeys); + // super + auto *superRef = new (C) SuperRefExpr(initDecl->getImplicitSelfDecl(), + SourceLoc(), /*Implicit=*/true); - // container.allKeys.count - auto *keysCountExpr = - UnresolvedDotExpr::createImplicit(C, allKeysExpr, C.Id_count); + // super.init(from:) + auto *initCall = UnresolvedDotExpr::createImplicit( + C, superRef, DeclBaseName::createConstructor(), {C.Id_from}); - // 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); + // super.decode(from: container.superDecoder()) + Expr *args[1] = {superDecoderCall}; + Identifier argLabels[1] = {C.Id_from}; + auto *callExpr = CallExpr::createImplicit(C, initCall, + C.AllocateCopy(args), + C.AllocateCopy(argLabels)); - auto *tupleExpr = TupleExpr::createImplicit(C, {keysCountExpr, oneExpr}, - {Identifier(), Identifier()}); + // try super.init(from: container.superDecoder()) + auto *tryExpr = new (C) TryExpr(SourceLoc(), callExpr, Type(), + /*Implicit=*/true); + statements.push_back(tryExpr); + } else { + // The explicit constructor name is a compound name taking no arguments. + DeclName initName(C, DeclBaseName::createConstructor(), + ArrayRef()); - auto *cmpExpr = - new (C) BinaryExpr(cmpFuncExpr, tupleExpr, /*implicit*/ true); - cmpExpr->setThrows(false); + // We need to look this up in the superclass to see if it throws. + auto result = superclassDecl->lookupDirect(initName); - auto *guardBody = BraceStmt::create(C, SourceLoc(), {throwStmt}, - SourceLoc(), /* Implicit */ true); + // We should have bailed one level up if this were not available. + assert(!result.empty()); - auto *guardStmt = new (C) - GuardStmt(SourceLoc(), cmpExpr, guardBody, /* Implicit */ true, C); + // If the init is failable, we should have already bailed one level + // above. + ConstructorDecl *superInitDecl = cast(result.front()); + assert(!superInitDecl->isFailable()); - statements.push_back(guardStmt); + // super + auto *superRef = new (C) SuperRefExpr(initDecl->getImplicitSelfDecl(), + SourceLoc(), /*Implicit=*/true); - // generate: switch container.allKeys.first { } - auto *firstExpr = - UnresolvedDotExpr::createImplicit(C, allKeysExpr, C.Id_first); + // super.init() + auto *superInitRef = UnresolvedDotExpr::createImplicit(C, superRef, + initName); + // super.init() call + Expr *callExpr = CallExpr::createImplicit(C, superInitRef, + ArrayRef(), + ArrayRef()); - auto switchStmt = - SwitchStmt::create(LabeledStmtInfo(), SourceLoc(), firstExpr, - SourceLoc(), cases, SourceLoc(), C); + // If super.init throws, try super.init() + if (superInitDecl->hasThrows()) + callExpr = new (C) TryExpr(SourceLoc(), callExpr, Type(), + /*Implicit=*/true); - statements.push_back(switchStmt); + statements.push_back(callExpr); + } + } } auto *body = BraceStmt::create(C, SourceLoc(), statements, SourceLoc(), /*implicit=*/true); - return {body, /*isTypeChecked=*/false}; + 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_init(AbstractFunctionDecl *initDecl, void *) { - // struct Foo : Codable { - // var x: Int - // var y: String +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 x - // case y + // 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) - // x = try container.decode(Type.self, forKey: .x) - // y = try container.decode(Type.self, forKey: .y) + // 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 *targetDecl = conformanceDC->getSelfNominalTypeDecl(); + 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, targetDecl); + 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 @@ -1539,19 +1567,18 @@ deriveBodyDecodable_init(AbstractFunctionDecl *initDecl, void *) { // something Codable and needs to decode super. // let container : KeyedDecodingContainer - auto codingKeysType = codingKeysEnum->getDeclaredType(); + auto codingKeysType = codingKeysEnum->getDeclaredInterfaceType(); auto *containerDecl = createKeyedContainer(C, funcDC, C.getKeyedDecodingContainerDecl(), codingKeysEnum->getDeclaredInterfaceType(), - VarDecl::Introducer::Let, C.Id_container); + VarDecl::Introducer::Let); - auto *containerExpr = new (C) DeclRefExpr(ConcreteDeclRef(containerDecl), - DeclNameLoc(), /*Implicit=*/true, - AccessSemantics::DirectToStorage); + auto *containerExpr = + new (C) DeclRefExpr(ConcreteDeclRef(containerDecl), DeclNameLoc(), + /*Implicit=*/true, AccessSemantics::DirectToStorage); SmallVector statements; - auto enumElements = codingKeysEnum->getAllElements(); - if (!enumElements.empty()) { + if (codingKeysEnum->hasCases()) { // Need to generate // `let container = try decoder.container(keyedBy: CodingKeys.self)` // `let container` (containerExpr) is generated above. @@ -1578,194 +1605,210 @@ deriveBodyDecodable_init(AbstractFunctionDecl *initDecl, void *) { statements.push_back(bindingDecl); statements.push_back(containerDecl); - // Now need to generate `x = try container.decode(Type.self, forKey: .x)` - // for all existing properties. Optional properties get `decodeIfPresent`. - for (auto *elt : enumElements) { - VarDecl *varDecl; - Type varType; - bool useIfPresentVariant; + SmallVector cases; - std::tie(varDecl, varType, useIfPresentVariant) = - lookupVarDeclForCodingKeysCase(conformanceDC, elt, targetDecl); + for (auto *elt : targetEnum->getAllElements()) { + auto *codingKeyCase = + lookupEnumCase(C, codingKeysEnum, elt->getName().getBaseIdentifier()); - // Don't output a decode statement for a let with an initial value. - if (varDecl->isLet() && varDecl->isParentInitialized()) { - // But emit a warning to let the user know that it won't be decoded. - auto lookupResult = - codingKeysEnum->lookupDirect(varDecl->getBaseName()); - auto keyExistsInCodingKeys = - llvm::any_of(lookupResult, [&](ValueDecl *VD) { - if (isa(VD)) { - return VD->getBaseName() == varDecl->getBaseName(); - } - return false; - }); - auto *encodableProto = C.getProtocol(KnownProtocolKind::Encodable); - bool conformsToEncodable = - conformanceDC->getParentModule()->lookupConformance( - targetDecl->getDeclaredInterfaceType(), encodableProto) != nullptr; + // Skip this case if it's not defined in the CodingKeys + if (!codingKeyCase) + continue; - // Strategy to use for CodingKeys enum diagnostic part - this is to - // make the behaviour more explicit: - // - // 1. If we have an *implicit* CodingKeys enum: - // (a) If the type is Decodable only, explicitly define the enum and - // remove the key from it. This makes it explicit that the key - // will not be decoded. - // (b) If the type is Codable, explicitly define the enum and keep the - // key in it. This is because removing the key will break encoding - // which is mostly likely not what the user expects. - // - // 2. If we have an *explicit* CodingKeys enum: - // (a) If the type is Decodable only and the key exists in the enum, - // then explicitly remove the key from the enum. This makes it - // explicit that the key will not be decoded. - // (b) If the type is Decodable only and the key does not exist in - // the enum, do nothing. This is because the user has explicitly - // made it clear that that they don't want the key to be decoded. - // (c) If the type is Codable, do nothing. This is because removing - // the key will break encoding which is most likely not what the - // user expects. - if (!codingKeysEnum->isImplicit()) { - if (conformsToEncodable || !keyExistsInCodingKeys) { + // 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; } - } - varDecl->diagnose(diag::decodable_property_will_not_be_decoded); - if (codingKeysEnum->isImplicit()) { - varDecl->diagnose( - diag::decodable_property_init_or_codingkeys_implicit, - conformsToEncodable ? 0 : 1, varDecl->getName()); - } else { - varDecl->diagnose( - diag::decodable_property_init_or_codingkeys_explicit, - varDecl->getName()); - } - if (auto *PBD = varDecl->getParentPatternBinding()) { - varDecl->diagnose(diag::decodable_make_property_mutable) - .fixItReplace(PBD->getLoc(), "var"); - } + // 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); - continue; + 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 methodName = - useIfPresentVariant ? C.Id_decodeIfPresent : C.Id_decode; + auto *selfRef = DerivedConformance::createSelfDeclRef(initDecl); - // Type.self (where Type === type(of: x)) - // Calculating the metatype needs to happen after potential Optional - // unwrapping in lookupVarDeclForCodingKeysCase(). - auto *metaTyRef = TypeExpr::createImplicit(varType, C); - auto *targetExpr = new (C) DotSelfExpr(metaTyRef, SourceLoc(), - SourceLoc(), varType); + // Foo.bar + auto *selfTypeExpr = + TypeExpr::createImplicit(targetEnum->getDeclaredType(), C); - // CodingKeys.x - metaTyRef = TypeExpr::createImplicit(codingKeysType, C); - auto *keyExpr = new (C) MemberRefExpr(metaTyRef, SourceLoc(), - elt, DeclNameLoc(), /*Implicit=*/true); + if (params.empty()) { + auto *selfCaseExpr = new (C) MemberRefExpr( + selfTypeExpr, SourceLoc(), elt, DeclNameLoc(), /*Implicit=*/true); - // decode(_:forKey:)/decodeIfPresent(_:forKey:) - SmallVector argNames{Identifier(), C.Id_forKey}; - auto *decodeCall = UnresolvedDotExpr::createImplicit( - C, containerExpr, methodName, argNames); + auto *selfRef = DerivedConformance::createSelfDeclRef(initDecl); - // container.decode(Type.self, forKey: CodingKeys.x) - Expr *args[2] = {targetExpr, keyExpr}; - auto *callExpr = CallExpr::createImplicit(C, decodeCall, - C.AllocateCopy(args), - C.AllocateCopy(argNames)); + auto *assignExpr = + new (C) AssignExpr(selfRef, SourceLoc(), selfCaseExpr, + /*Implicit=*/true); - // try container.decode(Type.self, forKey: CodingKeys.x) - auto *tryExpr = new (C) TryExpr(SourceLoc(), callExpr, Type(), - /*Implicit=*/true); + caseStatements.push_back(assignExpr); + } else { + // Foo.bar(x:) + auto *selfCaseExpr = UnresolvedDotExpr::createImplicit( + C, selfTypeExpr, elt->getBaseIdentifier(), C.AllocateCopy(params)); - auto *selfRef = DerivedConformance::createSelfDeclRef(initDecl); - auto *varExpr = UnresolvedDotExpr::createImplicit(C, selfRef, - varDecl->getName()); - auto *assignExpr = new (C) AssignExpr(varExpr, SourceLoc(), tryExpr, - /*Implicit=*/true); - statements.push_back(assignExpr); - } - } + // Foo.bar(x: try nestedContainer.decode(Int.self, forKey: .x)) + auto *caseCallExpr = CallExpr::createImplicit( + C, selfCaseExpr, C.AllocateCopy(decodeCalls), + C.AllocateCopy(params)); - // Classes which have a superclass must call super.init(from:) if the - // superclass is Decodable, or super.init() if it is not. - if (auto *classDecl = dyn_cast(targetDecl)) { - if (auto *superclassDecl = classDecl->getSuperclassDecl()) { - if (superclassConformsTo(classDecl, KnownProtocolKind::Decodable)) { - // Need to generate `try super.init(from: container.superDecoder())` + // self = Foo.bar(x: try nestedContainer.decode(Int.self)) + auto *assignExpr = + new (C) AssignExpr(selfRef, SourceLoc(), caseCallExpr, + /*Implicit=*/true); - // container.superDecoder - auto *superDecoderRef = - UnresolvedDotExpr::createImplicit(C, containerExpr, - C.Id_superDecoder); + caseStatements.push_back(assignExpr); + } - // container.superDecoder() - auto *superDecoderCall = - CallExpr::createImplicit(C, superDecoderRef, ArrayRef(), - ArrayRef()); + auto body = + BraceStmt::create(C, SourceLoc(), caseStatements, SourceLoc()); - // super - auto *superRef = new (C) SuperRefExpr(initDecl->getImplicitSelfDecl(), - SourceLoc(), /*Implicit=*/true); + cases.push_back(CaseStmt::create(C, CaseParentKind::Switch, SourceLoc(), + labelItem, SourceLoc(), SourceLoc(), + body, + /*case body vardecls*/ None)); + } - // super.init(from:) - auto *initCall = UnresolvedDotExpr::createImplicit( - C, superRef, DeclBaseName::createConstructor(), {C.Id_from}); + // 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); - // super.decode(from: container.superDecoder()) - Expr *args[1] = {superDecoderCall}; - Identifier argLabels[1] = {C.Id_from}; - auto *callExpr = CallExpr::createImplicit(C, initCall, - C.AllocateCopy(args), - C.AllocateCopy(argLabels)); + // container.allKeys + auto *allKeysExpr = + UnresolvedDotExpr::createImplicit(C, containerExpr, C.Id_allKeys); - // try super.init(from: container.superDecoder()) - auto *tryExpr = new (C) TryExpr(SourceLoc(), callExpr, Type(), - /*Implicit=*/true); - statements.push_back(tryExpr); - } else { - // The explicit constructor name is a compound name taking no arguments. - DeclName initName(C, DeclBaseName::createConstructor(), - ArrayRef()); + // container.allKeys.count + auto *keysCountExpr = + UnresolvedDotExpr::createImplicit(C, allKeysExpr, C.Id_count); - // We need to look this up in the superclass to see if it throws. - auto result = superclassDecl->lookupDirect(initName); + // 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); - // We should have bailed one level up if this were not available. - assert(!result.empty()); + auto *tupleExpr = TupleExpr::createImplicit(C, {keysCountExpr, oneExpr}, + {Identifier(), Identifier()}); - // If the init is failable, we should have already bailed one level - // above. - ConstructorDecl *superInitDecl = cast(result.front()); - assert(!superInitDecl->isFailable()); + auto *cmpExpr = + new (C) BinaryExpr(cmpFuncExpr, tupleExpr, /*implicit*/ true); + cmpExpr->setThrows(false); - // super - auto *superRef = new (C) SuperRefExpr(initDecl->getImplicitSelfDecl(), - SourceLoc(), /*Implicit=*/true); + auto *guardBody = BraceStmt::create(C, SourceLoc(), {throwStmt}, + SourceLoc(), /* Implicit */ true); - // super.init() - auto *superInitRef = UnresolvedDotExpr::createImplicit(C, superRef, - initName); - // super.init() call - Expr *callExpr = CallExpr::createImplicit(C, superInitRef, - ArrayRef(), - ArrayRef()); + auto *guardStmt = new (C) + GuardStmt(SourceLoc(), cmpExpr, guardBody, /* Implicit */ true, C); - // If super.init throws, try super.init() - if (superInitDecl->hasThrows()) - callExpr = new (C) TryExpr(SourceLoc(), callExpr, Type(), - /*Implicit=*/true); + statements.push_back(guardStmt); - statements.push_back(callExpr); - } - } + // 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 }; + return {body, /*isTypeChecked=*/false}; } /// Synthesizes a function declaration for `init(from: Decoder) throws` with a @@ -1808,7 +1851,8 @@ static ValueDecl *deriveDecodable_init(DerivedConformance &derived) { /*GenericParams=*/nullptr, conformanceDC); initDecl->setImplicit(); initDecl->setSynthesized(); - if (auto enumDecl = dyn_cast(derived.Nominal)) { + + if (dyn_cast(derived.Nominal)) { initDecl->setBodySynthesizer(&deriveBodyDecodable_enum_init); } else { initDecl->setBodySynthesizer(&deriveBodyDecodable_init); @@ -1906,8 +1950,60 @@ static bool canSynthesize(DerivedConformance &derived, ValueDecl *requirement) { if (!validateCodingKeysEnum(derived)) { 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, @@ -1915,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; } @@ -1928,6 +2025,7 @@ static bool canDeriveCodable(NominalTypeDecl *NTD, return false; } + return true; } bool DerivedConformance::canDeriveDecodable(NominalTypeDecl *NTD) { @@ -1939,10 +2037,10 @@ bool DerivedConformance::canDeriveEncodable(NominalTypeDecl *NTD) { } ValueDecl *DerivedConformance::deriveEncodable(ValueDecl *requirement) { - // We can only synthesize Encodable for structs, classes, and enums. + // We can only synthesize Encodable for structs and classes. if (!isa(Nominal) && !isa(Nominal) && - !(Context.LangOpts.EnableExperimentalEnumCodableDerivation && - isa(Nominal))) + !(Context.LangOpts.EnableExperimentalEnumCodableDerivation + && isa(Nominal))) return nullptr; if (requirement->getBaseName() != Context.Id_encode) { @@ -1970,10 +2068,10 @@ ValueDecl *DerivedConformance::deriveEncodable(ValueDecl *requirement) { } ValueDecl *DerivedConformance::deriveDecodable(ValueDecl *requirement) { - // We can only synthesize Encodable for structs, classes, and enums. + // We can only synthesize Encodable for structs and classes. if (!isa(Nominal) && !isa(Nominal) && - (Context.LangOpts.EnableExperimentalEnumCodableDerivation && - !isa(Nominal))) + !(Context.LangOpts.EnableExperimentalEnumCodableDerivation + && isa(Nominal))) return nullptr; if (requirement->getBaseName() != DeclBaseName::createConstructor()) { @@ -1998,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 d92c6d1ceecff..8c6062634ea41 100644 --- a/lib/Sema/DerivedConformances.cpp +++ b/lib/Sema/DerivedConformances.cpp @@ -142,19 +142,6 @@ bool DerivedConformance::derivesProtocolConformance(DeclContext *DC, return enumDecl->hasOnlyCasesWithoutAssociatedValues(); } - case KnownDerivableProtocolKind::Encodable: - case KnownDerivableProtocolKind::Decodable: - // FIXME: This is not actually correct. We cannot promise to always - // provide a witness here for all structs and classes. Unfortunately, - // figuring out whether this is actually possible requires much more - // context -- a TypeChecker and the parent decl context at least -- and - // is tightly coupled to the logic within DerivedConformance. This - // unfortunately means that we expect a witness even if one will not be - // produced, which requires DerivedConformance::deriveCodable to output - // its own diagnostics. - return DC->getASTContext() - .LangOpts.EnableExperimentalEnumCodableDerivation; - default: return false; } diff --git a/test/decl/protocol/special/coding/enum_codable_nonconforming_property.swift b/test/decl/protocol/special/coding/enum_codable_nonconforming_property.swift index c31ea38bd5a9d..f5e252938e0f1 100644 --- a/test/decl/protocol/special/coding/enum_codable_nonconforming_property.swift +++ b/test/decl/protocol/special/coding/enum_codable_nonconforming_property.swift @@ -163,4 +163,4 @@ let _ = NonConformingEnum.init(from:) // expected-error {{'NonConformingEnum' ca 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 {{type 'NonConformingEnum' has no member 'XCodingKeys'}} +let _ = NonConformingEnum.XCodingKeys.self // expected-error {{'XCodingKeys' is inaccessible due to 'private' protection level}}