static Type deriveCaseIterable_AllCases(DerivedConformance &derived) { // enum SomeEnum : CaseIterable { // @derived // typealias AllCases = [SomeEnum] // } auto *rawInterfaceType = computeAllCasesType(cast<EnumDecl>(derived.Nominal)); return derived.getConformanceContext()->mapTypeIntoContext(rawInterfaceType); }
/// 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; }
/// 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); }
/// 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) { // 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 &tc = derived.TC; ASTContext &C = tc.Context; auto proto = derived.Protocol; auto *classDecl = dyn_cast<ClassDecl>(derived.Nominal); if (proto->isSpecificProtocol(KnownProtocolKind::Decodable) && classDecl) { if (auto *superclassDecl = classDecl->getSuperclassDecl()) { DeclName memberName; auto superType = superclassDecl->getDeclaredInterfaceType(); if (tc.conformsToProtocol(superType, proto, superclassDecl, ConformanceCheckFlags::Used)) { // super.init(from:) must be accessible. memberName = cast<ConstructorDecl>(requirement)->getFullName(); } 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(C, DeclBaseName::createConstructor(), ArrayRef<Identifier>()); } auto result = tc.lookupMember(superclassDecl, superType, memberName); if (result.empty()) { // No super initializer for us to call. tc.diagnose(superclassDecl, diag::decodable_no_super_init_here, requirement->getFullName(), 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<ConstructorDecl>(result.front().getValueDecl()); auto conformanceDC = derived.getConformanceContext(); if (!initializer->isDesignatedInit()) { // We must call a superclass's designated initializer. tc.diagnose(initializer, diag::decodable_super_init_not_designated_here, requirement->getFullName(), memberName); return false; } else if (!initializer->isAccessibleFrom(conformanceDC)) { // Cannot call an inaccessible method. auto accessScope = initializer->getFormalAccessScope(conformanceDC); tc.diagnose(initializer, diag::decodable_inaccessible_super_init_here, requirement->getFullName(), memberName, accessScope.accessLevelForDiagnostics()); return false; } else if (initializer->getFailability() != OTK_None) { // We can't call super.init() if it's failable, since init(from:) // isn't failable. tc.diagnose(initializer, diag::decodable_super_init_is_failable_here, requirement->getFullName(), memberName); return false; } } } } // If the target already has a valid CodingKeys enum, we won't need to // synthesize one. auto validity = hasValidCodingKeysEnum(derived); // We found a type, but it wasn't valid. if (!validity.isValid) return false; // We can try to synthesize a type here. if (!validity.hasType) { auto *synthesizedEnum = synthesizeCodingKeysEnum(derived); if (!synthesizedEnum) return false; } return true; }