//===--- DerivedConformanceCodable.cpp - Derived Codable ------------------===// // // This source file is part of the Swift.org open source project // // Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See https://swift.org/LICENSE.txt for license information // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// // // This file implements explicit derivation of the Encodable and Decodable // protocols for a struct or class. // //===----------------------------------------------------------------------===// #include "CodeSynthesis.h" #include "TypeChecker.h" #include "llvm/ADT/STLExtras.h" #include "swift/AST/ConformanceLookup.h" #include "swift/AST/Decl.h" #include "swift/AST/Expr.h" #include "swift/AST/Module.h" #include "swift/AST/ParameterList.h" #include "swift/AST/Pattern.h" #include "swift/AST/Stmt.h" #include "swift/AST/Types.h" #include "swift/Basic/Assertions.h" #include "swift/Basic/StringExtras.h" #include "DerivedConformances.h" using namespace swift; /// Returns whether the type represented by the given ClassDecl inherits from a /// type which conforms to the given protocol. static bool superclassConformsTo(ClassDecl *target, KnownProtocolKind kpk) { if (!target) { return false; } auto superclass = target->getSuperclassDecl(); if (!superclass) return false; return !lookupConformance(target->getSuperclass(), target->getASTContext().getProtocol(kpk)) .isInvalid(); } /// Retrieve the variable name for the purposes of encoding/decoding. /// /// \param paramIndex if set will be used to generate name in the form of /// '_$paramIndex' when VarDecl has no name. static Identifier getVarNameForCoding(VarDecl *var, std::optional paramIndex = std::nullopt) { auto &C = var->getASTContext(); Identifier identifier; if (auto *PD = dyn_cast(var)) { identifier = PD->getArgumentName(); } else { identifier = var->getName(); } if (auto originalVar = var->getOriginalWrappedProperty()) identifier = originalVar->getName(); if (identifier.empty() && paramIndex.has_value()) return C.getIdentifier("_" + std::to_string(paramIndex.value())); return identifier; } /// Compute the Identifier for the CodingKey of an enum case static Identifier caseCodingKeysIdentifier(const ASTContext &C, EnumElementDecl *elt) { llvm::SmallString<16> scratch; camel_case::appendSentenceCase(scratch, elt->getBaseIdentifier().str()); scratch += C.Id_CodingKeys.str(); return C.getIdentifier(scratch.str()); } /// 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(NominalTypeDecl *target, llvm::SmallVectorImpl &caseIdentifiers, Identifier codingKeysEnumIdentifier) { auto &C = target->getASTContext(); assert(target->lookupDirect(DeclName(codingKeysEnumIdentifier)).empty()); // 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(); InheritedEntry protoTypeLoc[1] = { InheritedEntry(TypeLoc::withoutLoc(codingKeyType))}; ArrayRef inherited = C.AllocateCopy(protoTypeLoc); auto *enumDecl = new (C) EnumDecl(SourceLoc(), codingKeysEnumIdentifier, SourceLoc(), inherited, nullptr, target); enumDecl->setImplicit(); enumDecl->setSynthesized(); enumDecl->setAccess(AccessLevel::Private); // For classes which inherit from something Encodable or Decodable, we // provide case `super` as the first key (to be used in encoding super). auto *classDecl = dyn_cast(target); if (superclassConformsTo(classDecl, KnownProtocolKind::Encodable) || superclassConformsTo(classDecl, KnownProtocolKind::Decodable)) { // TODO: Ensure the class doesn't already have or inherit a variable named // "`super`"; otherwise we will generate an invalid enum. In that case, // diagnose and bail. auto *super = new (C) EnumElementDecl(SourceLoc(), C.Id_super, nullptr, SourceLoc(), nullptr, enumDecl); super->setImplicit(); enumDecl->addMember(super); } for (auto caseIdentifier : caseIdentifiers) { auto *elt = new (C) EnumElementDecl(SourceLoc(), caseIdentifier, nullptr, SourceLoc(), nullptr, enumDecl); elt->setImplicit(); enumDecl->addMember(elt); } // Forcibly derive conformance to CodingKey. TypeChecker::checkConformancesInContext(enumDecl); // Add to the type. target->addMember(enumDecl); return enumDecl; } static EnumDecl *addImplicitCaseCodingKeys(EnumDecl *target, EnumElementDecl *elementDecl, EnumDecl *codingKeysEnum) { auto &C = target->getASTContext(); // Only derive if this case exist in the CodingKeys enum auto *codingKeyCase = lookupEnumCase(C, codingKeysEnum, elementDecl->getBaseIdentifier()); if (!codingKeyCase) return nullptr; auto enumIdentifier = caseCodingKeysIdentifier(C, elementDecl); llvm::SmallVector caseIdentifiers; 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, entry.index()); caseIdentifiers.push_back(paramIdentifier); } } return addImplicitCodingKeys(target, caseIdentifiers, enumIdentifier); } // 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 // depend on those bounds. // // FIXME: Eventually we should find a way to expose this function to the lookup // machinery so it no longer costs two protocol conformance lookups to retrieve // CodingKeys. It will also help in our quest to separate semantic and parsed // members. static EnumDecl *addImplicitCodingKeys(NominalTypeDecl *target) { auto &C = target->getASTContext(); llvm::SmallVector caseIdentifiers; if (auto *enumDecl = dyn_cast(target)) { for (auto *elementDecl : enumDecl->getAllElements()) { caseIdentifiers.push_back(elementDecl->getBaseIdentifier()); } } else { for (auto *varDecl : target->getStoredProperties()) { if (!varDecl->isUserAccessible()) { continue; } caseIdentifiers.push_back(getVarNameForCoding(varDecl)); } } return addImplicitCodingKeys(target, caseIdentifiers, C.Id_CodingKeys); } namespace { /// Container for a set of functions that produces notes used when a /// synthesized conformance fails. struct DelayedNotes : public std::vector> { ~DelayedNotes() { for (const auto &fn : *this) { fn(); } } }; } static EnumDecl *validateCodingKeysType(const DerivedConformance &derived, TypeDecl *_codingKeysTypeDecl, DelayedNotes &delayedNotes) { auto &C = derived.Context; // CodingKeys may be a typealias. If so, follow the alias to its canonical // type. We are creating a copy here, so we can hold on to the original // `TypeDecl` in case we need to produce a diagnostic. auto *codingKeysTypeDecl = _codingKeysTypeDecl; auto codingKeysType = codingKeysTypeDecl->getDeclaredInterfaceType(); if (isa(codingKeysTypeDecl)) codingKeysTypeDecl = codingKeysType->getAnyNominal(); // Ensure that the type we found conforms to the CodingKey protocol. auto *codingKeyProto = C.getProtocol(KnownProtocolKind::CodingKey); if (!lookupConformance(codingKeysType, codingKeyProto)) { // If CodingKeys is a typealias which doesn't point to a valid nominal type, // codingKeysTypeDecl will be nullptr here. In that case, we need to warn on // the location of the usage, since there isn't an underlying type to // diagnose on. SourceLoc loc = codingKeysTypeDecl ? codingKeysTypeDecl->getLoc() : cast(_codingKeysTypeDecl)->getLoc(); delayedNotes.push_back([=] { ASTContext &C = derived.getProtocolType()->getASTContext(); C.Diags.diagnose(loc, diag::codable_codingkeys_type_does_not_conform_here, derived.getProtocolType()); }); return nullptr; } auto *codingKeysDecl = dyn_cast_or_null(codingKeysType->getAnyNominal()); if (!codingKeysDecl) { delayedNotes.push_back([=] { codingKeysTypeDecl->diagnose( diag::codable_codingkeys_type_is_not_an_enum_here, derived.getProtocolType()); }); return nullptr; } return codingKeysDecl; } /// Validates the given CodingKeys enum decl by ensuring its cases are a 1-to-1 /// match with 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, DelayedNotes &delayedNotes) { auto *codingKeysDecl = validateCodingKeysType( derived, codingKeysTypeDecl, delayedNotes); if (!codingKeysDecl) return false; // Look through all var decls. // // If any of the entries in the CodingKeys decl are not present in the type // by name, then this decl doesn't match. // If there are any vars left in the type which don't have a default value // (for Decodable), then this decl doesn't match. bool varDeclsAreValid = true; for (auto elt : codingKeysDecl->getAllElements()) { auto it = varDecls.find(elt->getBaseIdentifier()); if (it == varDecls.end()) { delayedNotes.push_back([=] { 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. varDeclsAreValid = false; continue; } // We have a property to map to. Ensure it's {En,De}codable. auto target = derived.getConformanceContext()->mapTypeIntoContext( it->second->getValueInterfaceType()); if (checkConformance(target, derived.Protocol).isInvalid()) { TypeLoc typeLoc = { it->second->getTypeReprOrParentPatternTypeRepr(), it->second->getTypeInContext(), }; auto var = it->second; auto proto = derived.getProtocolType(); delayedNotes.push_back([=] { var->diagnose(diag::codable_non_conforming_property_here, proto, typeLoc); }); varDeclsAreValid = false; } else { // The property was valid. Remove it from the list. varDecls.erase(it); } } if (!varDeclsAreValid) return false; // 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 : varDecls) { const auto *pbd = entry.second->getParentPatternBinding(); if (pbd && pbd->isDefaultInitializable()) { continue; } if (entry.second->isParentInitialized()) { 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. varDeclsAreValid = false; delayedNotes.push_back([=] { entry.second->diagnose(diag::codable_non_decoded_property_here, derived.getProtocolType(), entry.first); }); } } return varDeclsAreValid; } static bool validateCodingKeysEnum_enum(const DerivedConformance &derived, TypeDecl *codingKeysTypeDecl, DelayedNotes &delayedNotes) { auto *enumDecl = dyn_cast(derived.Nominal); if (!enumDecl) { return false; } llvm::SmallSetVector caseNames; for (auto *elt : enumDecl->getAllElements()) { caseNames.insert(elt->getBaseIdentifier()); } auto *codingKeysDecl = validateCodingKeysType( derived, codingKeysTypeDecl, delayedNotes); if (!codingKeysDecl) return false; bool casesAreValid = true; for (auto *elt : codingKeysDecl->getAllElements()) { if (!caseNames.contains(elt->getBaseIdentifier())) { delayedNotes.push_back([=] { elt->diagnose(diag::codable_extraneous_codingkey_case_here, elt->getBaseIdentifier()); }); casesAreValid = false; } } return casesAreValid; } /// Looks up and validates a CodingKeys enum for the given DerivedConformance. /// If a CodingKeys enum does not exist, one will be derived. static bool validateCodingKeysEnum(const DerivedConformance &derived, DelayedNotes &delayedNotes) { 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) { delayedNotes.push_back([=] { 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, delayedNotes); } 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, delayedNotes); } } /// 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. static bool validateCaseCodingKeysEnum(const DerivedConformance &derived, EnumElementDecl *elementDecl, DelayedNotes &delayedNotes) { 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(); if (!result) { // There is no coding key defined for this element, // which is okay, because not all elements have to // be considered for serialization. Attempts to // en-/decode them will be handled at runtime. return true; } auto *codingKeysTypeDecl = dyn_cast(result); if (!codingKeysTypeDecl) { delayedNotes.push_back([=] { result->diagnose(diag::codable_codingkeys_type_is_not_an_enum_here, derived.getProtocolType()); }); return false; } // Here we'll hold on to parameters by name -- when we've validated a parameter // against its CodingKey entry, it will get removed. llvm::SmallMapVector properties; if (elementDecl->hasAssociatedValues()) { for (auto entry : llvm::enumerate(*elementDecl->getParameterList())) { auto paramDecl = entry.value(); if (!paramDecl->isUserAccessible()) continue; auto identifier = getVarNameForCoding(paramDecl, entry.index()); properties[identifier] = paramDecl; } } return validateCodingKeysEnum( derived, properties, codingKeysTypeDecl, delayedNotes); } /// Creates a new var decl representing /// /// var/let identifier : containerBase /// /// \c containerBase is the name of the type to use as the base (either /// \c KeyedEncodingContainer or \c KeyedDecodingContainer). /// /// \param C The AST context to create the decl in. /// /// \param DC The \c DeclContext to create the decl in. /// /// \param keyedContainerDecl The generic type to bind the key type in. /// /// \param keyType The key type to bind to the container type. /// /// \param introducer Whether to declare the variable as immutable. /// /// \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)); // let container : Keyed*Container auto *containerDecl = new (C) VarDecl(/*IsStatic=*/false, introducer, SourceLoc(), identifier, DC); containerDecl->setImplicit(); containerDecl->setSynthesized(); containerDecl->setInterfaceType(containerType); return containerDecl; } /// Creates a new var decl representing /// /// var/let container : containerBase /// /// \c containerBase is the name of the type to use as the base (either /// \c KeyedEncodingContainer or \c KeyedDecodingContainer). /// /// \param C The AST context to create the decl in. /// /// \param DC The \c DeclContext to create the decl in. /// /// \param keyedContainerDecl The generic type to bind the key type in. /// /// \param keyType The key type to bind to the container type. /// /// \param introducer Whether to declare the variable as immutable. static VarDecl *createKeyedContainer(ASTContext &C, DeclContext *DC, NominalTypeDecl *keyedContainerDecl, Type keyType, VarDecl::Introducer introducer) { return createKeyedContainer(C, DC, keyedContainerDecl, keyType, introducer, C.Id_container); } /// Creates a new \c CallExpr representing /// /// base.container(keyedBy: CodingKeys.self) /// /// \param C The AST context to create the expression in. /// /// \param DC The \c DeclContext to create any decls in. /// /// \param base The base expression to make the call on. /// /// \param returnType The return type of the call. /// /// \param param The parameter to the call. static CallExpr *createContainerKeyedByCall(ASTContext &C, DeclContext *DC, Expr *base, Type returnType, NominalTypeDecl *param) { // (keyedBy:) auto *keyedByDecl = new (C) ParamDecl(SourceLoc(), SourceLoc(), C.Id_keyedBy, SourceLoc(), C.Id_keyedBy, DC); keyedByDecl->setImplicit(); keyedByDecl->setSpecifier(ParamSpecifier::Default); keyedByDecl->setInterfaceType(returnType); // base.container(keyedBy:) expr auto *paramList = ParameterList::createWithoutLoc(keyedByDecl); auto *unboundCall = UnresolvedDotExpr::createImplicit(C, base, C.Id_container, paramList); // CodingKeys.self expr auto *codingKeysExpr = TypeExpr::createImplicitForDecl( DeclNameLoc(), param, param->getDeclContext(), DC->mapTypeIntoContext(param->getInterfaceType())); auto *codingKeysMetaTypeExpr = new (C) DotSelfExpr(codingKeysExpr, SourceLoc(), SourceLoc()); // Full bound base.container(keyedBy: CodingKeys.self) call auto *argList = ArgumentList::forImplicitSingle(C, C.Id_keyedBy, codingKeysMetaTypeExpr); return CallExpr::createImplicit(C, unboundCall, argList); } static CallExpr *createNestedContainerKeyedByForKeyCall( ASTContext &C, DeclContext *DC, Expr *base, NominalTypeDecl *codingKeysType, EnumElementDecl *key) { // base.nestedContainer(keyedBy:, forKey:) expr auto *unboundCall = UnresolvedDotExpr::createImplicit( C, base, C.Id_nestedContainer, {C.Id_keyedBy, C.Id_forKey}); // 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 auto *argList = ArgumentList::forImplicitCallTo( unboundCall->getName(), {codingKeysMetaTypeExpr, keyExpr}, C); return CallExpr::createImplicit(C, unboundCall, argList); } static ThrowStmt *createThrowCodingErrorStmt(ASTContext &C, Expr *containerExpr, NominalTypeDecl *errorDecl, Identifier errorId, std::optional argument, StringRef debugMessage) { auto *contextDecl = lookupErrorContext(C, errorDecl); assert(contextDecl && "Missing Context decl."); auto *debugMessageExpr = new (C) StringLiteralExpr( C.AllocateCopy(debugMessage), SourceRange(), /* Implicit */ true); 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 *underlyingErrorExpr = new (C) NilLiteralExpr(SourceLoc(), /*implicit*/ true); auto *initArgList = ArgumentList::forImplicitCallTo( contextInitCall->getName(), {codingPathExpr, debugMessageExpr, underlyingErrorExpr}, C); auto *contextInitCallExpr = CallExpr::createImplicit(C, contextInitCall, initArgList); llvm::SmallVector arguments; if (argument.has_value()) { arguments.push_back(argument.value()); } arguments.push_back(contextInitCallExpr); SmallVector scratch; auto *decodeArgList = ArgumentList::forImplicitUnlabeled(C, arguments); auto *decodingErrorTypeExpr = TypeExpr::createImplicit(errorDecl->getDeclaredType(), C); auto *decodingErrorCall = UnresolvedDotExpr::createImplicit( C, decodingErrorTypeExpr, errorId, decodeArgList->getArgumentLabels(scratch)); auto *decodingErrorCallExpr = CallExpr::createImplicit(C, decodingErrorCall, decodeArgList); 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. /// \param elt The CodingKeys enum case. /// \param targetDecl The type to look up properties in. /// /// \return A tuple containing the \c VarDecl for the property, the type that /// should be passed when decoding it, and a boolean which is true if /// \c encodeIfPresent/\c decodeIfPresent should be used for this property. static std::tuple lookupVarDeclForCodingKeysCase(DeclContext *conformanceDC, EnumElementDecl *elt, NominalTypeDecl *targetDecl) { for (auto decl : targetDecl->lookupDirect( DeclName(elt->getBaseIdentifier()))) { if (auto *vd = dyn_cast(decl)) { // If we found a property with an attached wrapper, retrieve the // backing property. if (auto backingVar = vd->getPropertyWrapperBackingProperty()) vd = backingVar; if (!vd->isStatic()) { // This is the VarDecl we're looking for. auto varType = conformanceDC->mapTypeIntoContext(vd->getValueInterfaceType()); bool useIfPresentVariant = false; if (auto objType = varType->getOptionalObjectType()) { varType = objType; useIfPresentVariant = true; } return std::make_tuple(vd, varType, useIfPresentVariant); } } } llvm_unreachable("Should have found at least 1 var decl"); } static TryExpr *createEncodeCall(ASTContext &C, Type codingKeysType, EnumElementDecl *codingKey, Expr *containerExpr, Expr *varExpr, bool useIfPresentVariant) { // CodingKeys.x auto *metaTyRef = TypeExpr::createImplicit(codingKeysType, C); auto *keyExpr = new (C) MemberRefExpr(metaTyRef, SourceLoc(), codingKey, DeclNameLoc(), /*Implicit=*/true); // encode(_:forKey:)/encodeIfPresent(_:forKey:) auto methodName = useIfPresentVariant ? C.Id_encodeIfPresent : C.Id_encode; auto *encodeCall = UnresolvedDotExpr::createImplicit( C, containerExpr, methodName, {Identifier(), C.Id_forKey}); // container.encode(x, forKey: CodingKeys.x) auto *argList = ArgumentList::forImplicitCallTo(encodeCall->getName(), {varExpr, keyExpr}, C); auto *callExpr = CallExpr::createImplicit(C, encodeCall, argList); // try container.encode(x, forKey: CodingKeys.x) auto *tryExpr = new (C) TryExpr(SourceLoc(), callExpr, Type(), /*Implicit=*/true); return tryExpr; } /// 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); auto *encodeCallExpr = createEncodeCall( C, codingKeysType, elt, containerExpr, varExpr, useIfPresentVariant); statements.push_back(encodeCallExpr); } // 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 = DotSyntaxCallExpr::create( C, method, SourceLoc(), Argument::unlabeled(containerExpr)); // 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 = DotSyntaxCallExpr::create(C, encodeDeclRef, SourceLoc(), Argument::unlabeled(superRef)); // super.encode(to: container.superEncoder()) auto *args = ArgumentList::forImplicitSingle(C, C.Id_to, superEncoderRef); auto *callExpr = CallExpr::createImplicit(C, encodeCall, args); // 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 SwitchStmt * createEnumSwitch(ASTContext &C, DeclContext *DC, Expr *expr, EnumDecl *enumDecl, EnumDecl *codingKeysEnum, bool createSubpattern, std::function( EnumElementDecl *, EnumElementDecl *, ArrayRef)> createCase) { SmallVector cases; for (auto elt : enumDecl->getAllElements()) { // .(let a0, let a1, ...) SmallVector payloadVars; Pattern *subpattern = nullptr; std::optional> caseBodyVarDecls; if (createSubpattern) { subpattern = DerivedConformance::enumElementPayloadSubpattern( elt, 'a', DC, payloadVars, /* useLabels */ true); auto hasBoundDecls = !payloadVars.empty(); 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); } } // CodingKeys.x auto *codingKeyCase = lookupEnumCase(C, codingKeysEnum, elt->getName().getBaseIdentifier()); EnumElementDecl *targetElt; BraceStmt *caseBody; std::tie(targetElt, caseBody) = createCase(elt, codingKeyCase, payloadVars); if (caseBody) { // generate: case .: auto parentTy = DC->mapTypeIntoContext( targetElt->getParentEnum()->getDeclaredInterfaceType()); auto *pat = EnumElementPattern::createImplicit(parentTy, targetElt, subpattern, DC); auto labelItem = CaseLabelItem(pat); auto stmt = CaseStmt::create(C, CaseParentKind::Switch, SourceLoc(), labelItem, SourceLoc(), SourceLoc(), caseBody, /*case body vardecls*/ createSubpattern ? caseBodyVarDecls : std::nullopt); cases.push_back(stmt); } } // generate: switch $expr { } return SwitchStmt::createImplicit(LabeledStmtInfo(), expr, cases, C); } static DeclRefExpr *createContainer(ASTContext &C, DeclContext *DC, VarDecl::Introducer introducer, NominalTypeDecl *containerTypeDecl, VarDecl *target, EnumDecl *codingKeysEnum, llvm::SmallVectorImpl &statements, bool throws) { // let/var container : KeyedDecodingContainer auto *containerDecl = createKeyedContainer( C, DC, containerTypeDecl, codingKeysEnum->getDeclaredInterfaceType(), introducer); auto *containerExpr = new (C) DeclRefExpr(ConcreteDeclRef(containerDecl), DeclNameLoc(), /*Implicit=*/true, AccessSemantics::DirectToStorage); // de/encoder auto *decoderExpr = new (C) DeclRefExpr(ConcreteDeclRef(target), DeclNameLoc(), /*Implicit=*/true); // Bound de/encoder.container(keyedBy: CodingKeys.self) call auto containerType = containerDecl->getInterfaceType(); Expr *callExpr = createContainerKeyedByCall(C, DC, decoderExpr, containerType, codingKeysEnum); if (throws) { // try decoder.container(keyedBy: CodingKeys.self) callExpr = 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, callExpr, DC); statements.push_back(bindingDecl); statements.push_back(containerDecl); return containerExpr; } static std::pair deriveBodyEncodable_enum_encode(AbstractFunctionDecl *encodeDecl, void *) { // enum Foo : Codable { // case bar(x: Int) // case baz(y: String) // // // Already derived by this point if possible. // @derived enum CodingKeys : CodingKey { // case bar // case baz // // @derived enum BarCodingKeys : CodingKey { // case x // } // // @derived enum BazCodingKeys : CodingKey { // case y // } // } // // @derived func encode(to encoder: Encoder) throws { // var container = encoder.container(keyedBy: CodingKeys.self) // switch self { // case bar(let x): // let nestedContainer = try container.nestedContainer(keyedBy: // BarCodingKeys.self, forKey: .bar) try nestedContainer.encode(x, // forKey: .x) // case baz(let y): // let nestedContainer = try container.nestedContainer(keyedBy: // BazCodingKeys.self, forKey: .baz) try nestedContainer.encode(y, // forKey: .y) // } // } // } // The enclosing type decl. auto conformanceDC = encodeDecl->getDeclContext(); auto *enumDecl = conformanceDC->getSelfEnumDecl(); auto *funcDC = cast(encodeDecl); auto &C = funcDC->getASTContext(); // We'll want the CodingKeys enum for this type, potentially looking through // a typealias. auto *codingKeysEnum = lookupEvaluatedCodingKeysEnum(C, enumDecl); // We should have bailed already if: // a) The type does not have CodingKeys // b) The type is not an enum assert(codingKeysEnum && "Missing CodingKeys decl."); SmallVector statements; // Generate a reference to containerExpr ahead of time in case there are no // properties to encode or decode, but the type is a class which inherits from // something Codable and needs to encode super. // 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 : KeyedEncodingContainer auto *containerExpr = createContainer( C, funcDC, VarDecl::Introducer::Var, C.getKeyedEncodingContainerDecl(), encodeDecl->getParameters()->get(0), codingKeysEnum, statements, /*throws*/ false); auto *selfRef = encodeDecl->getImplicitSelfDecl(); // generate: switch self { } auto enumRef = new (C) DeclRefExpr(ConcreteDeclRef(selfRef), DeclNameLoc(), /*implicit*/ true, AccessSemantics::Ordinary); auto switchStmt = createEnumSwitch( C, funcDC, enumRef, enumDecl, codingKeysEnum, /*createSubpattern*/ true, [&](EnumElementDecl *elt, EnumElementDecl *codingKeyCase, ArrayRef payloadVars) -> std::tuple { SmallVector caseStatements; if (elt->isUnavailable()) { // This case is not encodable because it is unavailable and therefore // should not be instantiable at runtime. Skipping this case will // result in the SIL pipeline giving the switch a default case for // unexpected values. return std::make_tuple(nullptr, nullptr); } if (!codingKeyCase) { // This case should not be encodable, so throw an error if an attempt // is made to encode it auto debugMessage = "Case '" + elt->getBaseIdentifier().str().str() + "' cannot be encoded because it is not defined in CodingKeys."; auto *selfRefExpr = new (C) DeclRefExpr( ConcreteDeclRef(selfRef), DeclNameLoc(), /* Implicit */ true); auto *throwStmt = createThrowCodingErrorStmt( C, containerExpr, C.getEncodingErrorDecl(), C.Id_invalidValue, selfRefExpr, debugMessage); caseStatements.push_back(throwStmt); } else { auto caseIdentifier = caseCodingKeysIdentifier(C, elt); auto *caseCodingKeys = lookupEvaluatedCodingKeysEnum(C, enumDecl, caseIdentifier); auto *nestedContainerDecl = createKeyedContainer( C, funcDC, C.getKeyedEncodingContainerDecl(), caseCodingKeys->getDeclaredInterfaceType(), VarDecl::Introducer::Var, C.Id_nestedContainer); auto *nestedContainerCall = createNestedContainerKeyedByForKeyCall( C, funcDC, containerExpr, caseCodingKeys, codingKeyCase); auto *containerPattern = NamedPattern::createImplicit(C, nestedContainerDecl); auto *bindingDecl = PatternBindingDecl::createImplicit( C, StaticSpellingKind::None, containerPattern, nestedContainerCall, funcDC); caseStatements.push_back(bindingDecl); caseStatements.push_back(nestedContainerDecl); for (auto entry : llvm::enumerate(payloadVars)) { auto *payloadVar = entry.value(); auto *nestedContainerExpr = new (C) DeclRefExpr( ConcreteDeclRef(nestedContainerDecl), DeclNameLoc(), /*Implicit=*/true, AccessSemantics::DirectToStorage); auto payloadVarRef = new (C) DeclRefExpr(payloadVar, DeclNameLoc(), /*implicit*/ true); auto *paramDecl = elt->getParameterList()->get(entry.index()); auto caseCodingKeysIdentifier = getVarNameForCoding(paramDecl, entry.index()); auto *caseCodingKey = lookupEnumCase(C, caseCodingKeys, caseCodingKeysIdentifier); // If there is no key defined for this parameter, skip it. if (!caseCodingKey) continue; auto varType = conformanceDC->mapTypeIntoContext( payloadVar->getValueInterfaceType()); bool useIfPresentVariant = false; if (auto objType = varType->getOptionalObjectType()) { varType = objType; useIfPresentVariant = true; } auto *encodeCallExpr = createEncodeCall( C, caseCodingKeys->getDeclaredType(), caseCodingKey, nestedContainerExpr, payloadVarRef, useIfPresentVariant); caseStatements.push_back(encodeCallExpr); } } auto body = BraceStmt::create(C, SourceLoc(), caseStatements, SourceLoc()); return std::make_tuple(elt, body); }); statements.push_back(switchStmt); auto *body = BraceStmt::create(C, SourceLoc(), statements, SourceLoc(), /*implicit=*/true); return {body, /*isTypeChecked=*/false}; } /// Synthesizes a function declaration for `encode(to: Encoder) throws` with a /// lazily synthesized body for the given type. /// /// Adds the function declaration to the given type before returning it. static FuncDecl *deriveEncodable_encode(DerivedConformance &derived) { auto &C = derived.Context; auto conformanceDC = derived.getConformanceContext(); // Expected type: (Self) -> (Encoder) throws -> () // Constructed as: func type // input: Self // throws // output: function type // input: Encoder // output: () // Create from the inside out: auto encoderType = ExistentialType::get(C.getEncoderType()); auto returnType = TupleType::getEmpty(C); // Params: (Encoder) auto *encoderParam = new (C) ParamDecl(SourceLoc(), SourceLoc(), C.Id_to, SourceLoc(), C.Id_encoder, conformanceDC); encoderParam->setSpecifier(ParamSpecifier::Default); encoderParam->setInterfaceType(encoderType); encoderParam->setImplicit(); ParameterList *params = ParameterList::createWithoutLoc(encoderParam); // Func name: encode(to: Encoder) DeclName name(C, C.Id_encode, params); auto *const encodeDecl = FuncDecl::createImplicit( C, StaticSpellingKind::None, name, /*NameLoc=*/SourceLoc(), /*Async=*/false, /*Throws=*/true, /*ThrownType=*/Type(), /*GenericParams=*/nullptr, params, returnType, conformanceDC); encodeDecl->setSynthesized(); if (dyn_cast(derived.Nominal)) { encodeDecl->setBodySynthesizer(deriveBodyEncodable_enum_encode); } else { encodeDecl->setBodySynthesizer(deriveBodyEncodable_encode); } // This method should be marked as 'override' for classes inheriting Encodable // conformance from a parent class. if (superclassConformsTo(dyn_cast(derived.Nominal), KnownProtocolKind::Encodable)) { auto *attr = new (C) OverrideAttr(/*IsImplicit=*/true); encodeDecl->getAttrs().add(attr); } addNonIsolatedToSynthesized(derived, encodeDecl); encodeDecl->copyFormalAccessFrom(derived.Nominal, /*sourceIsParentContext*/ true); derived.addMembersToConformanceContext({encodeDecl}); return encodeDecl; } static TryExpr *createDecodeCall(ASTContext &C, Type resultType, Type codingKeysType, EnumElementDecl *codingKey, Expr *containerExpr, bool useIfPresentVariant) { auto methodName = useIfPresentVariant ? C.Id_decodeIfPresent : C.Id_decode; // Type.self auto *metaTyRef = TypeExpr::createImplicit(resultType, C); auto *targetExpr = new (C) DotSelfExpr(metaTyRef, SourceLoc(), SourceLoc(), resultType); // CodingKeys.x metaTyRef = TypeExpr::createImplicit(codingKeysType, C); auto *keyExpr = new (C) MemberRefExpr(metaTyRef, SourceLoc(), codingKey, DeclNameLoc(), /*Implicit=*/true); // decode(_:forKey:)/decodeIfPresent(_:forKey:) auto *decodeCall = UnresolvedDotExpr::createImplicit( C, containerExpr, methodName, {Identifier(), C.Id_forKey}); // container.decode(Type.self, forKey: CodingKeys.x) auto *argList = ArgumentList::forImplicitCallTo(decodeCall->getName(), {targetExpr, keyExpr}, C); auto *callExpr = CallExpr::createImplicit(C, decodeCall, argList); // try container.decode(Type.self, forKey: CodingKeys.x) auto *tryExpr = new (C) TryExpr(SourceLoc(), callExpr, Type(), /*Implicit=*/true); return tryExpr; } /// 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 // // // Already derived by this point if possible. // @derived enum CodingKeys : CodingKey { // case x // case y // } // // @derived init(from decoder: Decoder) throws { // let container = try decoder.container(keyedBy: CodingKeys.self) // x = try container.decode(Type.self, forKey: .x) // y = try container.decode(Type.self, forKey: .y) // } // } // The enclosing type decl. auto conformanceDC = initDecl->getDeclContext(); 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, 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."); // Generate a reference to containerExpr ahead of time in case there are no // properties to encode or decode, but the type is a class which inherits from // something Codable and needs to decode super. // let container : KeyedDecodingContainer auto codingKeysType = codingKeysEnum->getDeclaredType(); auto *containerDecl = createKeyedContainer(C, funcDC, C.getKeyedDecodingContainerDecl(), codingKeysEnum->getDeclaredInterfaceType(), VarDecl::Introducer::Let); auto *containerExpr = new (C) DeclRefExpr(ConcreteDeclRef(containerDecl), DeclNameLoc(), /*Implicit=*/true, AccessSemantics::DirectToStorage); SmallVector statements; auto enumElements = codingKeysEnum->getAllElements(); if (!enumElements.empty()) { // Need to generate // `let container = try decoder.container(keyedBy: CodingKeys.self)` // `let container` (containerExpr) is generated above. // decoder auto decoderParam = initDecl->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); // 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; std::tie(varDecl, varType, useIfPresentVariant) = lookupVarDeclForCodingKeysCase(conformanceDC, elt, targetDecl); // 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 = (bool) lookupConformance( targetDecl->getDeclaredInterfaceType(), encodableProto); // 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 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; } } 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"); } continue; } auto *tryExpr = createDecodeCall(C, varType, codingKeysType, elt, containerExpr, useIfPresentVariant); 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); } } // 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())` // container.superDecoder auto *superDecoderRef = UnresolvedDotExpr::createImplicit(C, containerExpr, C.Id_superDecoder); // container.superDecoder() auto *superDecoderCall = CallExpr::createImplicitEmpty(C, superDecoderRef); // super auto *superRef = new (C) SuperRefExpr(initDecl->getImplicitSelfDecl(), SourceLoc(), /*Implicit=*/true); // super.init(from:) auto *initCall = UnresolvedDotExpr::createImplicit( C, superRef, DeclBaseName::createConstructor(), {C.Id_from}); // super.decode(from: container.superDecoder()) auto *argList = ArgumentList::forImplicitSingle(C, C.Id_from, superDecoderCall); auto *callExpr = CallExpr::createImplicit(C, initCall, argList); // 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()); // We need to look this up in the superclass to see if it throws. auto result = superclassDecl->lookupDirect(initName); // We should have bailed one level up if this were not available. assert(!result.empty()); // If the init is failable, we should have already bailed one level // above. ConstructorDecl *superInitDecl = cast(result.front()); assert(!superInitDecl->isFailable()); // super auto *superRef = new (C) SuperRefExpr(initDecl->getImplicitSelfDecl(), SourceLoc(), /*Implicit=*/true); // super.init() auto *superInitRef = UnresolvedDotExpr::createImplicit(C, superRef, initName); // super.init() call Expr *callExpr = CallExpr::createImplicitEmpty(C, superInitRef); // If super.init throws, try super.init() if (superInitDecl->hasThrows()) callExpr = new (C) TryExpr(SourceLoc(), callExpr, Type(), /*Implicit=*/true); statements.push_back(callExpr); } } } 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. static std::pair deriveBodyDecodable_enum_init(AbstractFunctionDecl *initDecl, void *) { // enum Foo : Codable { // case bar(x: Int) // case baz(y: String) // @available(*, unavailable) case qux(z: Double) // // // Already derived by this point if possible. // @derived enum CodingKeys : CodingKey { // case bar // case baz // case qux // // @derived enum BarCodingKeys : CodingKey { // case x // } // // @derived enum BazCodingKeys : CodingKey { // case y // } // // @derived enum QuxCodingKeys : CodingKey { // case z // } // } // // @derived init(from decoder: Decoder) throws { // let container = try decoder.container(keyedBy: CodingKeys.self) // var allKeys = ArraySlice(container.allKeys) // guard let onlyKey = allKeys.popFirst(), allKeys.isEmpty else { // let context = DecodingError.Context( // codingPath: container.codingPath, // debugDescription: "Invalid number of keys found, expected one.") // throw DecodingError.typeMismatch(Foo.self, context) // } // switch onlyKey { // 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: BazCodingKeys.self, forKey: .baz) // let y = try nestedContainer.decode(String.self, forKey: .y) // self = .baz(y: y) // case .qux: // throw DecodingError.dataCorrupted( // DecodingError.Context( // codingPath: decoder.codingPath, // debugDescription: "Unavailable enum element encountered.") // ) // } // } // 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."); SmallVector statements; if (codingKeysEnum->hasCases()) { // Need to generate // `let container = try decoder.container(keyedBy: CodingKeys.self)` auto *containerExpr = createContainer( C, funcDC, VarDecl::Introducer::Let, C.getKeyedDecodingContainerDecl(), initDecl->getParameters()->get(0), codingKeysEnum, statements, /*throws*/ true); // generate: var allKeys = ArraySlice(container.allKeys); auto *allKeysDecl = new (C) VarDecl(/*IsStatic=*/false, VarDecl::Introducer::Var, SourceLoc(), C.Id_allKeys, funcDC); allKeysDecl->setImplicit(); allKeysDecl->setSynthesized(); { auto *arraySliceRef = new (C) DeclRefExpr(ConcreteDeclRef(C.getArraySliceDecl()), DeclNameLoc(), /*Implicit=*/true); auto *containerAllKeys = UnresolvedDotExpr::createImplicit(C, containerExpr, C.Id_allKeys); auto *argList = ArgumentList::createImplicit( C, {Argument::unlabeled(containerAllKeys)}); auto *init = CallExpr::createImplicit(C, arraySliceRef, argList); auto *allKeysPattern = NamedPattern::createImplicit(C, allKeysDecl); auto *allKeysBindingDecl = PatternBindingDecl::createImplicit( C, StaticSpellingKind::None, allKeysPattern, init, funcDC); statements.push_back(allKeysBindingDecl); statements.push_back(allKeysDecl); } // generate: // guard let onlyKey = allKeys.popFirst(), allKeys.isEmpty else { // let context = DecodingError.Context( // codingPath: container.codingPath, // debugDescription: "Invalid number of keys found, expected // one.") // throw DecodingError.typeMismatch(Foo.self, context) // } auto *theKeyDecl = new (C) VarDecl(/*IsStatic=*/false, VarDecl::Introducer::Let, SourceLoc(), C.getIdentifier("onlyKey"), funcDC); theKeyDecl->setImplicit(); theKeyDecl->setSynthesized(); SmallVector guardElements; { auto *allKeysExpr = new (C) DeclRefExpr(ConcreteDeclRef(allKeysDecl), DeclNameLoc(), /*Implicit=*/true); // generate: let onlyKey = allKeys.popFirst; auto *allKeysPopFirstCallExpr = CallExpr::createImplicitEmpty( C, UnresolvedDotExpr::createImplicit(C, allKeysExpr, C.Id_popFirst)); auto *theKeyPattern = BindingPattern::createImplicit( C, VarDecl::Introducer::Let, NamedPattern::createImplicit(C, theKeyDecl)); guardElements.emplace_back(ConditionalPatternBindingInfo::create( C, SourceLoc(), theKeyPattern, allKeysPopFirstCallExpr)); // generate: allKeys.isEmpty; auto *allKeysIsEmptyExpr = UnresolvedDotExpr::createImplicit(C, allKeysExpr, C.Id_isEmpty); guardElements.emplace_back(allKeysIsEmptyExpr); } auto *targetType = TypeExpr::createImplicit( funcDC->mapTypeIntoContext(targetEnum->getDeclaredInterfaceType()), C); auto *targetTypeExpr = new (C) DotSelfExpr(targetType, SourceLoc(), SourceLoc()); auto *throwStmt = createThrowCodingErrorStmt( C, containerExpr, C.getDecodingErrorDecl(), C.Id_typeMismatch, targetTypeExpr, "Invalid number of keys found, expected one."); auto *guardBody = BraceStmt::create(C, SourceLoc(), {throwStmt}, SourceLoc(), /* Implicit */ true); auto *guardStmt = new (C) GuardStmt(SourceLoc(), C.AllocateCopy(guardElements), guardBody, /* Implicit */ true); statements.push_back(guardStmt); // generate: switch onlyKey { } auto *theKeyExpr = new (C) DeclRefExpr(ConcreteDeclRef(theKeyDecl), DeclNameLoc(), /*Implicit=*/true); auto switchStmt = createEnumSwitch( C, funcDC, theKeyExpr, targetEnum, codingKeysEnum, /*createSubpattern*/ false, [&](EnumElementDecl *elt, EnumElementDecl *codingKeyCase, ArrayRef payloadVars) -> std::tuple { // Skip this case if it's not defined in the CodingKeys if (!codingKeyCase) return std::make_tuple(nullptr, nullptr); if (elt->isUnavailable()) { // generate: // throw DecodingError.dataCorrupted( // DecodingError.Context( // codingPath: decoder.codingPath, // debugDescription: "...") auto *throwStmt = createThrowCodingErrorStmt( C, containerExpr, C.getDecodingErrorDecl(), C.Id_dataCorrupted, std::nullopt, "Unavailable enum element encountered."); auto body = BraceStmt::create(C, SourceLoc(), {throwStmt}, SourceLoc()); return std::make_tuple(codingKeyCase, body); } 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::Let, 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 decodeArgs; 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); auto argLabel = 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()); decodeArgs.emplace_back(SourceLoc(), argLabel, paramDecl->getTypeCheckedDefaultExpr()); continue; } auto varType = conformanceDC->mapTypeIntoContext( paramDecl->getValueInterfaceType()); bool useIfPresentVariant = false; if (auto objType = varType->getOptionalObjectType()) { varType = objType; useIfPresentVariant = true; } auto *nestedContainerExpr = new (C) DeclRefExpr( ConcreteDeclRef(nestedContainerDecl), DeclNameLoc(), /*Implicit=*/true, AccessSemantics::DirectToStorage); auto *tryExpr = createDecodeCall( C, varType, caseCodingKeys->getDeclaredType(), caseCodingKey, nestedContainerExpr, useIfPresentVariant); decodeArgs.emplace_back(SourceLoc(), argLabel, tryExpr); } } auto *selfRef = DerivedConformance::createSelfDeclRef(initDecl); // Foo.bar auto *selfTypeExpr = TypeExpr::createImplicit(targetEnum->getDeclaredType(), C); if (decodeArgs.empty()) { auto *selfCaseExpr = new (C) MemberRefExpr(selfTypeExpr, SourceLoc(), elt, DeclNameLoc(), /*Implicit=*/true); auto *selfRef = DerivedConformance::createSelfDeclRef(initDecl); auto *assignExpr = new (C) AssignExpr(selfRef, SourceLoc(), selfCaseExpr, /*Implicit=*/true); caseStatements.push_back(assignExpr); } else { // Foo.bar(x:) SmallVector scratch; auto *argList = ArgumentList::createImplicit(C, decodeArgs); auto *selfCaseExpr = UnresolvedDotExpr::createImplicit( C, selfTypeExpr, elt->getBaseIdentifier(), argList->getArgumentLabels(scratch)); // Foo.bar(x: try nestedContainer.decode(Int.self, forKey: .x)) auto *caseCallExpr = CallExpr::createImplicit(C, selfCaseExpr, argList); // 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()); return std::make_tuple(codingKeyCase, body); }); statements.push_back(switchStmt); } auto *body = BraceStmt::create(C, SourceLoc(), statements, SourceLoc(), /*implicit=*/true); return {body, /*isTypeChecked=*/false}; } /// Synthesizes a function declaration for `init(from: Decoder) throws` with a /// lazily synthesized body for the given type. /// /// Adds the function declaration to the given type before returning it. static ValueDecl *deriveDecodable_init(DerivedConformance &derived) { auto &C = derived.Context; auto classDecl = dyn_cast(derived.Nominal); auto conformanceDC = derived.getConformanceContext(); // Expected type: (Self) -> (Decoder) throws -> (Self) // Constructed as: func type // input: Self // throws // output: function type // input: Encoder // output: Self // Compute from the inside out: // Params: (Decoder) auto decoderType = ExistentialType::get(C.getDecoderType()); auto *decoderParamDecl = new (C) ParamDecl( SourceLoc(), SourceLoc(), C.Id_from, SourceLoc(), C.Id_decoder, conformanceDC); decoderParamDecl->setImplicit(); decoderParamDecl->setSpecifier(ParamSpecifier::Default); decoderParamDecl->setInterfaceType(decoderType); auto *paramList = ParameterList::createWithoutLoc(decoderParamDecl); // Func name: init(from: Decoder) DeclName name(C, DeclBaseName::createConstructor(), paramList); auto *initDecl = new (C) ConstructorDecl(name, SourceLoc(), /*Failable=*/false, SourceLoc(), /*Async=*/false, /*AsyncLoc=*/SourceLoc(), /*Throws=*/true, SourceLoc(), /*ThrownType=*/TypeLoc(), paramList, /*GenericParams=*/nullptr, conformanceDC, /*LifetimeDependentTypeRepr*/ nullptr); initDecl->setImplicit(); initDecl->setSynthesized(); if (dyn_cast(derived.Nominal)) { initDecl->setBodySynthesizer(&deriveBodyDecodable_enum_init); } else { initDecl->setBodySynthesizer(&deriveBodyDecodable_init); } // This constructor should be marked as `required` for non-final classes. if (classDecl && !classDecl->isSemanticallyFinal()) { auto *reqAttr = new (C) RequiredAttr(/*IsImplicit=*/true); initDecl->getAttrs().add(reqAttr); } addNonIsolatedToSynthesized(derived, initDecl); initDecl->copyFormalAccessFrom(derived.Nominal, /*sourceIsParentContext*/ true); derived.addMembersToConformanceContext({initDecl}); return initDecl; } /// Returns whether the given type is valid for synthesizing {En,De}codable. /// /// Checks to see whether the given type has a valid \c CodingKeys enum, and if /// not, attempts to synthesize one for it. /// /// \param requirement The requirement we want to synthesize. static bool canSynthesize(DerivedConformance &derived, ValueDecl *requirement, DelayedNotes &delayedNotes) { // Before we attempt to look up (or more importantly, synthesize) a CodingKeys // entity on target, we need to make sure the type is otherwise valid. // // If we are synthesizing Decodable and the target is a class with a // superclass, our synthesized init(from:) will need to call either // super.init(from:) or super.init() depending on whether the superclass is // Decodable itself. // // If the required initializer is not available, we shouldn't attempt to // synthesize CodingKeys. auto proto = derived.Protocol; auto *classDecl = dyn_cast(derived.Nominal); if (proto->isSpecificProtocol(KnownProtocolKind::Decodable) && classDecl) { if (auto *superclassDecl = classDecl->getSuperclassDecl()) { DeclName memberName; auto superType = superclassDecl->getDeclaredInterfaceType(); if (checkConformance(superType, proto)) { // super.init(from:) must be accessible. memberName = cast(requirement)->getName(); } else { // super.init() must be accessible. // Passing an empty params array constructs a compound name with no // arguments (as opposed to a simple name when omitted). memberName = DeclName(derived.Context, DeclBaseName::createConstructor(), ArrayRef()); } auto result = TypeChecker::lookupMember(superclassDecl, superType, DeclNameRef(memberName)); if (result.empty()) { // No super initializer for us to call. delayedNotes.push_back([=] { superclassDecl->diagnose(diag::decodable_no_super_init_here, requirement->getName(), memberName); }); return false; } else if (result.size() > 1) { // There are multiple results for this lookup. We'll end up producing a // diagnostic later complaining about duplicate methods (if we haven't // already), so just bail with a general error. return false; } else { auto *initializer = cast(result.front().getValueDecl()); auto conformanceDC = derived.getConformanceContext(); if (!initializer->isDesignatedInit()) { // We must call a superclass's designated initializer. delayedNotes.push_back([=] { initializer->diagnose( diag::decodable_super_init_not_designated_here, requirement->getName(), memberName); }); return false; } else if (!initializer->isAccessibleFrom(conformanceDC)) { // Cannot call an inaccessible method. delayedNotes.push_back([=] { auto accessScope = initializer->getFormalAccessScope(conformanceDC); initializer->diagnose(diag::decodable_inaccessible_super_init_here, requirement->getName(), memberName, accessScope.accessLevelForDiagnostics()); }); return false; } else if (initializer->isFailable()) { // We can't call super.init() if it's failable, since init(from:) // isn't failable. delayedNotes.push_back([=] { initializer->diagnose(diag::decodable_super_init_is_failable_here, requirement->getName(), memberName); }); return false; } } } } if (!validateCodingKeysEnum(derived, delayedNotes)) { return false; } 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())) { delayedNotes.push_back([=] { 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; } delayedNotes.push_back([=] { userDefinedParam->diagnose(diag::codable_enum_duplicate_parameter_name_here, derived.getProtocolType(), derived.Nominal->getDeclaredType(), paramIdentifier, elementDecl->getBaseIdentifier()); }); allValid = false; } } } if (!duplicate && !validateCaseCodingKeysEnum(derived, elementDecl, delayedNotes)) { allValid = false; } } } return allValid; } static bool canDeriveCodable(NominalTypeDecl *NTD, KnownProtocolKind Kind) { assert(Kind == KnownProtocolKind::Encodable || Kind == KnownProtocolKind::Decodable); // Structs, classes and enums can explicitly derive Encodable and Decodable // conformance (explicitly meaning we can synthesize an implementation if // a type conforms manually). if (!isa(NTD) && !isa(NTD) && !isa(NTD)) { return false; } auto *PD = NTD->getASTContext().getProtocol(Kind); if (!PD) { return false; } return true; } bool DerivedConformance::canDeriveDecodable(NominalTypeDecl *NTD) { return canDeriveCodable(NTD, KnownProtocolKind::Decodable); } bool DerivedConformance::canDeriveEncodable(NominalTypeDecl *NTD) { return canDeriveCodable(NTD, KnownProtocolKind::Encodable); } ValueDecl *DerivedConformance::deriveEncodable(ValueDecl *requirement) { // We can only synthesize Encodable for structs and classes. if (!isa(Nominal) && !isa(Nominal) && !isa(Nominal)) return nullptr; if (requirement->getBaseName() != Context.Id_encode) { // Unknown requirement. requirement->diagnose(diag::broken_encodable_requirement); return nullptr; } if (checkAndDiagnoseDisallowedContext(requirement)) return nullptr; // Check other preconditions for synthesized conformance. // This synthesizes a CodingKeys enum if possible. DelayedNotes delayedNotes; if (!canSynthesize(*this, requirement, delayedNotes)) { ConformanceDecl->diagnose(diag::type_does_not_conform, Nominal->getDeclaredType(), getProtocolType()); requirement->diagnose(diag::no_witnesses, diag::RequirementKind::Func, requirement, getProtocolType()); return nullptr; } assert(delayedNotes.empty()); return deriveEncodable_encode(*this); } ValueDecl *DerivedConformance::deriveDecodable(ValueDecl *requirement) { // We can only synthesize Encodable for structs and classes. if (!isa(Nominal) && !isa(Nominal) && !isa(Nominal)) return nullptr; if (!requirement->getBaseName().isConstructor()) { // Unknown requirement. requirement->diagnose(diag::broken_decodable_requirement); return nullptr; } if (checkAndDiagnoseDisallowedContext(requirement)) return nullptr; // Check other preconditions for synthesized conformance. // This synthesizes a CodingKeys enum if possible. DelayedNotes delayedNotes; if (!canSynthesize(*this, requirement, delayedNotes)) { ConformanceDecl->diagnose(diag::type_does_not_conform, Nominal->getDeclaredType(), getProtocolType()); requirement->diagnose(diag::no_witnesses, diag::RequirementKind::Constructor, requirement, getProtocolType()); return nullptr; } assert(delayedNotes.empty()); return deriveDecodable_init(*this); }