/// Returns whether the given type has a valid nested \c CodingKeys enum. /// /// If the type has an invalid \c CodingKeys entity, produces diagnostics to /// complain about the error. In this case, the error result will be true -- in /// the case where we don't have a valid CodingKeys enum and have produced /// diagnostics here, we don't want to then attempt to synthesize a CodingKeys /// enum. /// /// \returns A \c CodingKeysValidity value representing the result of the check. static CodingKeysValidity hasValidCodingKeysEnum(DerivedConformance &derived) { auto &tc = derived.TC; auto &C = tc.Context; auto codingKeysDecls = derived.Nominal->lookupDirect(DeclName(C.Id_CodingKeys)); if (codingKeysDecls.empty()) return CodingKeysValidity(/*hasType=*/false, /*isValid=*/true); // Only ill-formed code would produce multiple results for this lookup. // This would get diagnosed later anyway, so we're free to only look at the // first result here. auto result = codingKeysDecls.front(); auto *codingKeysTypeDecl = dyn_cast<TypeDecl>(result); if (!codingKeysTypeDecl) { tc.diagnose(result->getLoc(), diag::codable_codingkeys_type_is_not_an_enum_here, derived.getProtocolType()); return CodingKeysValidity(/*hasType=*/true, /*isValid=*/false); } // If the decl hasn't been validated yet, do so. tc.validateDecl(codingKeysTypeDecl); // CodingKeys may be a typealias. If so, follow the alias to its canonical // type. auto codingKeysType = codingKeysTypeDecl->getDeclaredInterfaceType(); if (isa<TypeAliasDecl>(codingKeysTypeDecl)) codingKeysTypeDecl = codingKeysType->getAnyNominal(); // Ensure that the type we found conforms to the CodingKey protocol. auto *codingKeyProto = C.getProtocol(KnownProtocolKind::CodingKey); if (!tc.conformsToProtocol(codingKeysType, codingKeyProto, derived.getConformanceContext(), ConformanceCheckFlags::Used)) { // 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<TypeDecl>(result)->getLoc(); tc.diagnose(loc, diag::codable_codingkeys_type_does_not_conform_here, derived.getProtocolType()); return CodingKeysValidity(/*hasType=*/true, /*isValid=*/false); } // CodingKeys must be an enum for synthesized conformance. auto *codingKeysEnum = dyn_cast<EnumDecl>(codingKeysTypeDecl); if (!codingKeysEnum) { tc.diagnose(codingKeysTypeDecl->getLoc(), diag::codable_codingkeys_type_is_not_an_enum_here, derived.getProtocolType()); return CodingKeysValidity(/*hasType=*/true, /*isValid=*/false); } bool valid = validateCodingKeysEnum(derived, codingKeysEnum); return CodingKeysValidity(/*hasType=*/true, /*isValid=*/valid); }
/// 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(DerivedConformance &derived, EnumDecl *codingKeysDecl) { auto &tc = derived.TC; 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::SmallDenseMap<Identifier, VarDecl *, 8> properties; for (auto *varDecl : derived.Nominal->getStoredProperties(/*skipInaccessible=*/true)) { if (varDecl->getAttrs().hasAttribute<LazyAttr>()) continue; properties[varDecl->getName()] = varDecl; } bool propertiesAreValid = true; for (auto elt : codingKeysDecl->getAllElements()) { auto it = properties.find(elt->getName()); if (it == properties.end()) { tc.diagnose(elt->getLoc(), diag::codable_extraneous_codingkey_case_here, elt->getName()); // 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 conformance = varConformsToCodable(tc, conformanceDC, it->second, derived.Protocol); switch (conformance) { case Conforms: // The property was valid. Remove it from the list. properties.erase(it); break; case DoesNotConform: tc.diagnose(it->second->getLoc(), diag::codable_non_conforming_property_here, derived.getProtocolType(), it->second->getType()); LLVM_FALLTHROUGH; case TypeNotValidated: // We don't produce a diagnostic for a type which failed to validate. // This will produce a diagnostic elsewhere anyway. propertiesAreValid = false; continue; } } 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 (!properties.empty() && derived.Protocol->isSpecificProtocol(KnownProtocolKind::Decodable)) { for (auto it = properties.begin(); it != properties.end(); ++it) { // If the var is default initializable, then it need not have an explicit // initial value. auto *varDecl = it->second; if (auto pbd = varDecl->getParentPatternBinding()) { if (pbd->isDefaultInitializable()) continue; } if (varDecl->getParentInitializer()) continue; // The var was not default initializable, and did not have an explicit // initial value. propertiesAreValid = false; tc.diagnose(it->second->getLoc(), diag::codable_non_decoded_property_here, derived.getProtocolType(), it->first); } } return propertiesAreValid; }