Improve experience of derivation of Codable types

* Introduce diagnostics that explain why derivation of
  Encodable/Decodable fail rather than just silently failing
* Allow properties with default values to be omitted from CodingKeys
  enum and from encoding/decoding
* If `CodingKeys` is a typealias, reach through it more consistently to
  get at the final target type
* Add unit tests to confirm this new behavior for classes and structs

NOTE: Although this made the diff bigger, standalone variables named
"type" have been renamed to "target" throughout, since the word "type"
may be ambiguous and makes debugging impossible. "target" is unique.
This commit is contained in:
Itai Ferber
2017-05-18 18:37:12 -07:00
parent a46c48438c
commit 5eb58ef8d4
15 changed files with 571 additions and 270 deletions

View File

@@ -2008,6 +2008,9 @@ NOTE(enum_raw_value_incrementing_from_zero,none,
// Derived conformances // Derived conformances
ERROR(cannot_synthesize_in_extension,none,
"implementation of %0 cannot be automatically synthesized in an extension yet", (Type))
ERROR(broken_raw_representable_requirement,none, ERROR(broken_raw_representable_requirement,none,
"RawRepresentable protocol is broken: unexpected requirement", ()) "RawRepresentable protocol is broken: unexpected requirement", ())
ERROR(broken_equatable_requirement,none, ERROR(broken_equatable_requirement,none,
@@ -2029,6 +2032,17 @@ ERROR(broken_encodable_requirement,none,
ERROR(broken_decodable_requirement,none, ERROR(broken_decodable_requirement,none,
"Decodable protocol is broken: unexpected requirement", ()) "Decodable protocol is broken: unexpected requirement", ())
NOTE(codable_extraneous_codingkey_case_here,none,
"CodingKey case %0 does match any stored properties", (Identifier))
NOTE(codable_non_conforming_property_here,none,
"cannot automatically synthesize %0 because %1 does not conform to %0", (Type, Identifier))
NOTE(codable_non_decoded_property_here,none,
"cannot automatically synthesize %0 because %1 does not have a matching CodingKey and does not have a default value", (Type, Identifier))
NOTE(codable_codingkeys_type_is_not_an_enum_here,none,
"cannot automatically synthesize %0 because 'CodingKeys' is not an enum", (Type))
NOTE(codable_codingkeys_type_does_not_conform_here,none,
"cannot automatically synthesize %0 because 'CodingKeys' does not conform to CodingKey", (Type))
// Dynamic Self // Dynamic Self
ERROR(dynamic_self_non_method,none, ERROR(dynamic_self_non_method,none,
"%select{global|local}0 function cannot return 'Self'", (bool)) "%select{global|local}0 function cannot return 'Self'", (bool))

View File

@@ -14,7 +14,7 @@
// protocols for a struct or class. // protocols for a struct or class.
// //
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//
//
#include "TypeChecker.h" #include "TypeChecker.h"
#include "swift/AST/Decl.h" #include "swift/AST/Decl.h"
#include "swift/AST/Expr.h" #include "swift/AST/Expr.h"
@@ -31,108 +31,89 @@ using namespace DerivedConformance;
/// Returns whether the type represented by the given ClassDecl inherits from a /// Returns whether the type represented by the given ClassDecl inherits from a
/// type which conforms to the given protocol. /// type which conforms to the given protocol.
/// ///
/// \param type The \c ClassDecl whose superclass to look up. /// \param target The \c ClassDecl whose superclass to look up.
/// ///
/// \param proto The protocol to check conformance for. /// \param proto The protocol to check conformance for.
static bool inheritsConformanceTo(ClassDecl *type, ProtocolDecl *proto) { static bool inheritsConformanceTo(ClassDecl *target, ProtocolDecl *proto) {
if (!type->hasSuperclass()) if (!target->hasSuperclass())
return false; return false;
auto &C = type->getASTContext(); auto &C = target->getASTContext();
auto *superclassDecl = type->getSuperclassDecl(); auto *superclassDecl = target->getSuperclassDecl();
auto *superclassModule = superclassDecl->getModuleContext(); auto *superclassModule = superclassDecl->getModuleContext();
return (bool)superclassModule->lookupConformance(type->getSuperclass(), return (bool)superclassModule->lookupConformance(target->getSuperclass(),
proto, proto,
C.getLazyResolver()); C.getLazyResolver());
} }
/// Returns whether the superclass of the given class conforms to Encodable. /// Returns whether the superclass of the given class conforms to Encodable.
/// ///
/// \param type The \c ClassDecl whose superclass to check. /// \param target The \c ClassDecl whose superclass to check.
static bool superclassIsEncodable(ClassDecl *type) { static bool superclassIsEncodable(ClassDecl *target) {
auto &C = type->getASTContext(); auto &C = target->getASTContext();
return inheritsConformanceTo(type, return inheritsConformanceTo(target,
C.getProtocol(KnownProtocolKind::Encodable)); C.getProtocol(KnownProtocolKind::Encodable));
} }
/// Returns whether the superclass of the given class conforms to Decodable. /// Returns whether the superclass of the given class conforms to Decodable.
/// ///
/// \param type The \c ClassDecl whose superclass to check. /// \param target The \c ClassDecl whose superclass to check.
static bool superclassIsDecodable(ClassDecl *type) { static bool superclassIsDecodable(ClassDecl *target) {
auto &C = type->getASTContext(); auto &C = target->getASTContext();
return inheritsConformanceTo(type, return inheritsConformanceTo(target,
C.getProtocol(KnownProtocolKind::Decodable)); C.getProtocol(KnownProtocolKind::Decodable));
} }
/// Validates that all the variables declared in the given list of declarations /// Returns whether the given variable conforms to the given protocol.
/// conform to the given protocol.
/// ///
/// Produces a diagnostic on the given typechecker for every var which does not /// \param tc The typechecker to use in validating {En,De}codable conformance.
/// conform. Calls a success callback for every var which does conform.
///
/// \param tc The typechecker to use in validating {En,Decodable} conformance.
/// ///
/// \param context The \c DeclContext the var declarations belong to. /// \param context The \c DeclContext the var declarations belong to.
/// ///
/// \param vars The var range to validate. /// \param varDecl The \c VarDecl to validate.
/// ///
/// \param proto The protocol to check conformance to. /// \param proto The \c ProtocolDecl to check conformance to.
/// static bool varConformsToProtocol(TypeChecker &tc, DeclContext *context,
/// \param callback A callback to call on every valid var decl. VarDecl *varDecl, ProtocolDecl *proto) {
template <typename ValidVarCallback> // If the decl doesn't yet have a type, we may be seeing it before the type
static bool // checker has gotten around to evaluating its type. For example:
validateVarsConformToProtocol(TypeChecker &tc, DeclContext *context, //
NominalTypeDecl::StoredPropertyRange &vars, // func foo() {
ProtocolDecl *proto, ValidVarCallback &callback) { // let b = Bar(from: decoder) // <- evaluates Bar conformance to Codable,
bool allConform = true; // // forcing derivation
for (auto varDecl : vars) { // }
// If the decl doesn't yet have a type, we may be seeing it before the type //
// checker has gotten around to evaluating its type. For example: // struct Bar : Codable {
// // var x: Int // <- we get to valuate x's var decl here, but its type
// func foo() { // // hasn't yet been evaluated
// let b = Bar(from: decoder) // <- evaluates Bar conformance to Codable, // }
// // forcing derivation //
// } // Validate the decl eagerly.
// if (!varDecl->hasType())
// struct Bar : Codable { tc.validateDecl(varDecl);
// var x: Int // <- we get to valuate x's var decl here, but its type
// // hasn't yet been evaluated
// }
//
// Validate the decl eagerly.
if (!varDecl->hasType())
tc.validateDecl(varDecl);
// If the var decl didn't validate, it may still not have a type; confirm it // If the var decl didn't validate, it may still not have a type; confirm it
// has a type before ensuring the type conforms to Codable. // has a type before ensuring the type conforms to Codable.
if (!varDecl->hasType() || // TODO: Peer through Optional and collection types to get at inner type.
!tc.conformsToProtocol(varDecl->getType(), proto, context, return varDecl->hasType() &&
ConformanceCheckFlags::Used)) { tc.conformsToProtocol(varDecl->getType(), proto, context,
// TODO: We should produce a diagnostic note here explaining that we found ConformanceCheckFlags::Used);
// a var not conforming to Codable.
allConform = false;
continue;
}
callback(varDecl);
}
return allConform;
} }
/// Validates the given CodingKeys enum decl by ensuring its cases are a 1-to-1 /// 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 stored vars of the given type.
/// ///
/// \param tc The typechecker to use in validating {En,Decodable} conformance. /// \param tc The typechecker to use in validating {En,De}codable conformance.
/// ///
/// \param codingKeysDecl The \c CodingKeys enum decl to validate. /// \param codingKeysDecl The \c CodingKeys enum decl to validate.
/// ///
/// \param type The nominal type decl to validate the \c CodingKeys against. /// \param target The nominal type decl to validate the \c CodingKeys against.
/// ///
/// \param proto The {En,De}codable protocol to validate all the keys conform /// \param proto The {En,De}codable protocol to validate all the keys conform
/// to. /// to.
static bool validateCodingKeysEnum(TypeChecker &tc, EnumDecl *codingKeysDecl, static bool
NominalTypeDecl *type, ProtocolDecl *proto) { validateCodingKeysEnum(TypeChecker &tc, EnumDecl *codingKeysDecl,
NominalTypeDecl *target, ProtocolDecl *proto) {
// Look through all var decls in the given type. // Look through all var decls in the given type.
// * Filter out lazy/computed vars (currently already done by // * Filter out lazy/computed vars (currently already done by
// getStoredProperties). // getStoredProperties).
@@ -140,38 +121,64 @@ static bool validateCodingKeysEnum(TypeChecker &tc, EnumDecl *codingKeysDecl,
// //
// If any of the entries in the CodingKeys decl are not present in the type // If any of the entries in the CodingKeys decl are not present in the type
// by name, then this decl doesn't match. // by name, then this decl doesn't match.
// If there are any vars left in the type, 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.
// NOTE: If we change the behavior to ignore vars with default values, then we
// can further filter out the type names to remove those which
// correspond to vars with default values.
llvm::SmallDenseSet<Identifier, 8> names;
auto storedProperties = type->getStoredProperties(/*skipInaccessible=*/true); // Here we'll hold on to properties by name -- when we've validated a property
auto validVarCallback = [&names](VarDecl *varDecl) { // against its CodingKey entry, it will get removed.
names.insert(varDecl->getName()); llvm::SmallDenseMap<Identifier, VarDecl *, 8> properties;
}; for (auto *varDecl : target->getStoredProperties(/*skipInaccessible=*/true)) {
properties[varDecl->getName()] = varDecl;
if (!validateVarsConformToProtocol(tc, type->getDeclContext(),
storedProperties, proto, validVarCallback))
return false;
for (auto elt : codingKeysDecl->getAllElements()) {
auto it = names.find(elt->getName());
if (it == names.end()) {
// TODO: Produce diagnostic here complaining that the CodingKeys enum
// contains a case which does not correspond to a var.
// TODO: Investigate typo-correction here; perhaps the case name was
// misspelled and we can provide a fix-it.
return false;
}
names.erase(it);
} }
// TODO: Produce diagnostic here complaining that there are vars which are not bool propertiesAreValid = true;
// listed in the CodingKeys enum. for (auto elt : codingKeysDecl->getAllElements()) {
return names.empty(); 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.
bool conforms = varConformsToProtocol(tc, target->getDeclContext(),
it->second, proto);
if (!conforms) {
tc.diagnose(it->second->getLoc(),
diag::codable_non_conforming_property_here,
proto->getDeclaredType(), it->second->getName());
propertiesAreValid = false;
continue;
}
// 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 (!properties.empty() &&
proto == tc.Context.getProtocol(KnownProtocolKind::Decodable)) {
for (auto it = properties.begin(); it != properties.end(); ++it) {
if (it->second->getParentInitializer() != nullptr) {
// Var has a default value.
continue;
}
propertiesAreValid = false;
tc.diagnose(it->second->getLoc(), diag::codable_non_decoded_property_here,
proto->getDeclaredType(), it->first);
}
}
return propertiesAreValid;
} }
/// Returns whether the given type has a valid nested \c CodingKeys enum. /// Returns whether the given type has a valid nested \c CodingKeys enum.
@@ -184,15 +191,15 @@ static bool validateCodingKeysEnum(TypeChecker &tc, EnumDecl *codingKeysDecl,
/// ///
/// \param tc The typechecker to use in validating {En,Decodable} conformance. /// \param tc The typechecker to use in validating {En,Decodable} conformance.
/// ///
/// \param type The type decl whose nested \c CodingKeys type to validate. /// \param target The type decl whose nested \c CodingKeys type to validate.
/// ///
/// \param proto The {En,De}codable protocol to ensure the properties matching /// \param proto The {En,De}codable protocol to ensure the properties matching
/// the keys conform to. /// the keys conform to.
static std::pair</* has type? */ bool, /* error? */ bool> static std::pair</* has type? */ bool, /* error? */ bool>
hasValidCodingKeysEnum(TypeChecker &tc, NominalTypeDecl *type, hasValidCodingKeysEnum(TypeChecker &tc, NominalTypeDecl *target,
ProtocolDecl *proto) { ProtocolDecl *proto) {
auto &C = tc.Context; auto &C = tc.Context;
auto codingKeysDecls = type->lookupDirect(DeclName(C.Id_CodingKeys)); auto codingKeysDecls = target->lookupDirect(DeclName(C.Id_CodingKeys));
if (codingKeysDecls.empty()) if (codingKeysDecls.empty())
return {/* has type? */ false, /* error? */ false}; return {/* has type? */ false, /* error? */ false};
@@ -203,41 +210,42 @@ hasValidCodingKeysEnum(TypeChecker &tc, NominalTypeDecl *type,
auto *codingKeysTypeDecl = dyn_cast<TypeDecl>(result); auto *codingKeysTypeDecl = dyn_cast<TypeDecl>(result);
if (!codingKeysTypeDecl) { if (!codingKeysTypeDecl) {
// TODO: Produce a diagnostic complaining that the "CodingKeys" entity we tc.diagnose(result->getLoc(),
// found is not a type. diag::codable_codingkeys_type_is_not_an_enum_here,
proto->getDeclaredType());
return {/* has type? */ true, /* error? */ true}; return {/* has type? */ true, /* error? */ true};
} }
// CodingKeys may be a typealias. If so, follow the alias to its canonical
// type.
auto codingKeysType = codingKeysTypeDecl->getDeclaredInterfaceType();
if (isa<TypeAliasDecl>(codingKeysTypeDecl)) {
auto canType = codingKeysType->getCanonicalType();
assert(canType);
codingKeysTypeDecl = canType->getAnyNominal();
}
// Ensure that the type we found conforms to the CodingKey protocol. // Ensure that the type we found conforms to the CodingKey protocol.
auto *codingKeyProto = C.getProtocol(KnownProtocolKind::CodingKey); auto *codingKeyProto = C.getProtocol(KnownProtocolKind::CodingKey);
auto codingKeysType = codingKeysTypeDecl->getDeclaredInterfaceType();
if (!tc.conformsToProtocol(codingKeysType, codingKeyProto, if (!tc.conformsToProtocol(codingKeysType, codingKeyProto,
type->getDeclContext(), target->getDeclContext(),
ConformanceCheckFlags::Used)) { ConformanceCheckFlags::Used)) {
// TODO: Produce a diagnostic complaining that the "CodingKeys" entity we tc.diagnose(codingKeysTypeDecl->getLoc(),
// found does not conform to CodingKey. diag::codable_codingkeys_type_does_not_conform_here,
proto->getDeclaredType());
return {/* has type? */ true, /* error? */ true}; return {/* has type? */ true, /* error? */ true};
} }
// CodingKeys should eventually be an enum. If it's a typealias, we'll need to // CodingKeys must be an enum for synthesized conformance.
// follow it. auto *codingKeysEnum = dyn_cast<EnumDecl>(codingKeysTypeDecl);
auto *codingKeysEnum = dyn_cast<EnumDecl>(result);
if (isa<TypeAliasDecl>(result)) {
// TODO: Do we have to follow through multiple layers of typealiases
// here? Or will getCanonicalType() do that for us?
auto canType = codingKeysType->getCanonicalType();
assert(canType);
codingKeysEnum = dyn_cast<EnumDecl>(codingKeysType->getAnyNominal());
}
if (!codingKeysEnum) { if (!codingKeysEnum) {
// TODO: Produce a diagnostic complaining that we cannot derive Codable tc.diagnose(codingKeysTypeDecl->getLoc(),
// with a non-enum CodingKeys type. diag::codable_codingkeys_type_is_not_an_enum_here,
proto->getDeclaredType());
return {/* has type? */ true, /* error? */ true}; return {/* has type? */ true, /* error? */ true};
} }
bool valid = validateCodingKeysEnum(tc, codingKeysEnum, type, proto); bool valid = validateCodingKeysEnum(tc, codingKeysEnum, target, proto);
return {/* has type? */ true, /* error? */ !valid}; return {/* has type? */ true, /* error? */ !valid};
} }
@@ -248,16 +256,16 @@ hasValidCodingKeysEnum(TypeChecker &tc, NominalTypeDecl *type,
/// ///
/// \param tc The typechecker to use in validating {En,De}codable conformance. /// \param tc The typechecker to use in validating {En,De}codable conformance.
/// ///
/// \param type The nominal type decl whose nested \c CodingKeys type to /// \param target The nominal type decl whose nested \c CodingKeys type to
/// synthesize. /// synthesize.
/// ///
/// \param proto The {En,De}codable protocol to validate all the keys conform /// \param proto The {En,De}codable protocol to validate all the keys conform
/// to. /// to.
static EnumDecl *synthesizeCodingKeysEnum(TypeChecker &tc, static EnumDecl *synthesizeCodingKeysEnum(TypeChecker &tc,
NominalTypeDecl *type, NominalTypeDecl *target,
ProtocolDecl *proto) { ProtocolDecl *proto) {
auto &C = tc.Context; auto &C = tc.Context;
auto *typeDC = cast<DeclContext>(type); auto *targetDC = cast<DeclContext>(target);
// We want to look through all the var declarations of this type to create // We want to look through all the var declarations of this type to create
// enum cases based on those var names. // enum cases based on those var names.
@@ -267,7 +275,7 @@ static EnumDecl *synthesizeCodingKeysEnum(TypeChecker &tc,
MutableArrayRef<TypeLoc> inherited = C.AllocateCopy(protoTypeLoc); MutableArrayRef<TypeLoc> inherited = C.AllocateCopy(protoTypeLoc);
auto *enumDecl = new (C) EnumDecl(SourceLoc(), C.Id_CodingKeys, SourceLoc(), auto *enumDecl = new (C) EnumDecl(SourceLoc(), C.Id_CodingKeys, SourceLoc(),
inherited, nullptr, typeDC); inherited, nullptr, targetDC);
enumDecl->setImplicit(); enumDecl->setImplicit();
enumDecl->setAccessibility(Accessibility::Private); enumDecl->setAccessibility(Accessibility::Private);
@@ -276,7 +284,7 @@ static EnumDecl *synthesizeCodingKeysEnum(TypeChecker &tc,
// For classes which inherit from something Encodable or Decodable, we // For classes which inherit from something Encodable or Decodable, we
// provide case `super` as the first key (to be used in encoding super). // provide case `super` as the first key (to be used in encoding super).
auto *classDecl = dyn_cast<ClassDecl>(type); auto *classDecl = dyn_cast<ClassDecl>(target);
if (classDecl && if (classDecl &&
(superclassIsEncodable(classDecl) || superclassIsDecodable(classDecl))) { (superclassIsEncodable(classDecl) || superclassIsDecodable(classDecl))) {
// TODO: Ensure the class doesn't already have or inherit a variable named // TODO: Ensure the class doesn't already have or inherit a variable named
@@ -291,27 +299,61 @@ static EnumDecl *synthesizeCodingKeysEnum(TypeChecker &tc,
// Each of these vars needs a case in the enum. For each var decl, if the type // Each of these vars needs a case in the enum. For each var decl, if the type
// conforms to {En,De}codable, add it to the enum. // conforms to {En,De}codable, add it to the enum.
auto storedProperties = type->getStoredProperties(/*skipInaccessible=*/true); bool allConform = true;
auto validVarCallback = [&C, &enumDC, &mutableEnumDC](VarDecl *varDecl) { for (auto *varDecl : target->getStoredProperties(/*skipInaccessible=*/true)) {
if (!varConformsToProtocol(tc, target->getDeclContext(), varDecl, proto)) {
tc.diagnose(varDecl->getLoc(),
diag::codable_non_conforming_property_here,
proto->getDeclaredType(), varDecl->getName());
allConform = false;
continue;
}
auto *elt = new (C) EnumElementDecl(SourceLoc(), varDecl->getName(), auto *elt = new (C) EnumElementDecl(SourceLoc(), varDecl->getName(),
TypeLoc(), /*HasArgumentType=*/false, TypeLoc(), /*HasArgumentType=*/false,
SourceLoc(), nullptr, enumDC); SourceLoc(), nullptr, enumDC);
elt->setImplicit(); elt->setImplicit();
mutableEnumDC->addMember(elt); mutableEnumDC->addMember(elt);
}; }
if (!validateVarsConformToProtocol(tc, type->getDeclContext(), if (!allConform)
storedProperties, proto, validVarCallback))
return nullptr; return nullptr;
// Forcibly derive conformance to CodingKey. // Forcibly derive conformance to CodingKey.
tc.checkConformancesInContext(enumDC, mutableEnumDC); tc.checkConformancesInContext(enumDC, mutableEnumDC);
// Add to the type. // Add to the type.
cast<IterableDeclContext>(type)->addMember(enumDecl); cast<IterableDeclContext>(target)->addMember(enumDecl);
return enumDecl; return enumDecl;
} }
/// 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) {
auto codingKeyDecls = target->lookupDirect(DeclName(C.Id_CodingKeys));
if (codingKeyDecls.empty())
return nullptr;
auto *codingKeysDecl = codingKeyDecls.front();
if (auto *typealiasDecl = dyn_cast<TypeAliasDecl>(codingKeysDecl)) {
codingKeysDecl = typealiasDecl->getDeclaredInterfaceType()
->getCanonicalType()->getAnyNominal();
}
return dyn_cast<EnumDecl>(codingKeysDecl);
}
/// Creates a new var decl representing /// Creates a new var decl representing
/// ///
/// var/let container : containerBase<keyType> /// var/let container : containerBase<keyType>
@@ -412,18 +454,18 @@ static void deriveBodyEncodable_encode(AbstractFunctionDecl *encodeDecl) {
// } // }
// The enclosing type decl. // The enclosing type decl.
auto *typeDecl = cast<NominalTypeDecl>(encodeDecl->getDeclContext()); auto *targetDecl = cast<NominalTypeDecl>(encodeDecl->getDeclContext());
auto *funcDC = cast<DeclContext>(encodeDecl); auto *funcDC = cast<DeclContext>(encodeDecl);
auto &C = funcDC->getASTContext(); auto &C = funcDC->getASTContext();
// We'll want the CodingKeys enum for this type. // We'll want the CodingKeys enum for this type, potentially looking through
auto *codingKeysDecl = typeDecl->lookupDirect(DeclName(C.Id_CodingKeys))[0]; // a typealias.
auto *codingKeysEnum = lookupEvaluatedCodingKeysEnum(C, targetDecl);
// We should have bailed already if: // We should have bailed already if:
// a) The type does not have CodingKeys // a) The type does not have CodingKeys
assert(codingKeysDecl && "Missing CodingKeys decl.");
// b) The type is not an enum // b) The type is not an enum
auto *codingKeysEnum = cast<EnumDecl>(codingKeysDecl); assert(codingKeysEnum && "Missing CodingKeys decl.");
SmallVector<ASTNode, 5> statements; SmallVector<ASTNode, 5> statements;
@@ -476,7 +518,7 @@ static void deriveBodyEncodable_encode(AbstractFunctionDecl *encodeDecl) {
// Only ill-formed code would produce multiple results for this lookup. // 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 // This would get diagnosed later anyway, so we're free to only look at
// the first result here. // the first result here.
auto matchingVars = typeDecl->lookupDirect(DeclName(elt->getName())); auto matchingVars = targetDecl->lookupDirect(DeclName(elt->getName()));
// self.x // self.x
auto *selfRef = createSelfDeclRef(encodeDecl); auto *selfRef = createSelfDeclRef(encodeDecl);
@@ -509,7 +551,7 @@ static void deriveBodyEncodable_encode(AbstractFunctionDecl *encodeDecl) {
} }
// Classes which inherit from something Codable should encode super as well. // Classes which inherit from something Codable should encode super as well.
auto *classDecl = dyn_cast<ClassDecl>(typeDecl); auto *classDecl = dyn_cast<ClassDecl>(targetDecl);
if (classDecl && superclassIsEncodable(classDecl)) { if (classDecl && superclassIsEncodable(classDecl)) {
// Need to generate `try super.encode(to: container.superEncoder())` // Need to generate `try super.encode(to: container.superEncoder())`
@@ -560,11 +602,11 @@ static void deriveBodyEncodable_encode(AbstractFunctionDecl *encodeDecl) {
/// ///
/// \param parentDecl The parent declaration of the type. /// \param parentDecl The parent declaration of the type.
/// ///
/// \param type The nominal type to synthesize the function for. /// \param target The nominal type to synthesize the function for.
static FuncDecl *deriveEncodable_encode(TypeChecker &tc, Decl *parentDecl, static FuncDecl *deriveEncodable_encode(TypeChecker &tc, Decl *parentDecl,
NominalTypeDecl *type) { NominalTypeDecl *target) {
auto &C = tc.Context; auto &C = tc.Context;
auto *typeDC = cast<DeclContext>(type); auto *targetDC = cast<DeclContext>(target);
// Expected type: (Self) -> (Encoder) throws -> () // Expected type: (Self) -> (Encoder) throws -> ()
// Constructed as: func type // Constructed as: func type
@@ -590,10 +632,10 @@ static FuncDecl *deriveEncodable_encode(TypeChecker &tc, Decl *parentDecl,
auto innerType = FunctionType::get(inputType, returnType, extInfo); auto innerType = FunctionType::get(inputType, returnType, extInfo);
// Params: (self [implicit], Encoder) // Params: (self [implicit], Encoder)
auto *selfDecl = ParamDecl::createSelf(SourceLoc(), typeDC); auto *selfDecl = ParamDecl::createSelf(SourceLoc(), targetDC);
auto *encoderParam = new (C) ParamDecl(/*isLet=*/true, SourceLoc(), auto *encoderParam = new (C) ParamDecl(/*isLet=*/true, SourceLoc(),
SourceLoc(), C.Id_to, SourceLoc(), SourceLoc(), C.Id_to, SourceLoc(),
C.Id_encoder, encoderType, typeDC); C.Id_encoder, encoderType, targetDC);
encoderParam->setInterfaceType(encoderType); encoderParam->setInterfaceType(encoderType);
ParameterList *params[] = {ParameterList::createWithoutLoc(selfDecl), ParameterList *params[] = {ParameterList::createWithoutLoc(selfDecl),
@@ -605,24 +647,26 @@ static FuncDecl *deriveEncodable_encode(TypeChecker &tc, Decl *parentDecl,
SourceLoc(), name, SourceLoc(), SourceLoc(), name, SourceLoc(),
/*Throws=*/true, SourceLoc(), SourceLoc(), /*Throws=*/true, SourceLoc(), SourceLoc(),
nullptr, params, nullptr, params,
TypeLoc::withoutLoc(returnType), typeDC); TypeLoc::withoutLoc(returnType),
targetDC);
encodeDecl->setImplicit(); encodeDecl->setImplicit();
encodeDecl->setBodySynthesizer(deriveBodyEncodable_encode); encodeDecl->setBodySynthesizer(deriveBodyEncodable_encode);
// This method should be marked as 'override' for classes inheriting Encodable // This method should be marked as 'override' for classes inheriting Encodable
// conformance from a parent class. // conformance from a parent class.
auto *classDecl = dyn_cast<ClassDecl>(type); auto *classDecl = dyn_cast<ClassDecl>(target);
if (classDecl && superclassIsEncodable(classDecl)) { if (classDecl && superclassIsEncodable(classDecl)) {
auto *attr = new (C) SimpleDeclAttr<DAK_Override>(/*IsImplicit=*/true); auto *attr = new (C) SimpleDeclAttr<DAK_Override>(/*IsImplicit=*/true);
encodeDecl->getAttrs().add(attr); encodeDecl->getAttrs().add(attr);
} }
// Evaluate the type of Self in (Self) -> (Encoder) throws -> (). // Evaluate the type of Self in (Self) -> (Encoder) throws -> ().
Type selfType = typeDC->getDeclaredInterfaceType(); Type selfType = targetDC->getDeclaredInterfaceType();
Type interfaceType; Type interfaceType;
if (auto sig = typeDC->getGenericSignatureOfContext()) { if (auto sig = targetDC->getGenericSignatureOfContext()) {
// Evaluate the below, but in a generic environment (if Self is generic). // Evaluate the below, but in a generic environment (if Self is generic).
encodeDecl->setGenericEnvironment(typeDC->getGenericEnvironmentOfContext()); encodeDecl->setGenericEnvironment(
targetDC->getGenericEnvironmentOfContext());
interfaceType = GenericFunctionType::get(sig, selfType, innerType, interfaceType = GenericFunctionType::get(sig, selfType, innerType,
FunctionType::ExtInfo()); FunctionType::ExtInfo());
} else { } else {
@@ -631,16 +675,16 @@ static FuncDecl *deriveEncodable_encode(TypeChecker &tc, Decl *parentDecl,
} }
encodeDecl->setInterfaceType(interfaceType); encodeDecl->setInterfaceType(interfaceType);
encodeDecl->setAccessibility(std::max(type->getFormalAccess(), encodeDecl->setAccessibility(std::max(target->getFormalAccess(),
Accessibility::Internal)); Accessibility::Internal));
// If the type was not imported, the derived conformance is either from the // If the type was not imported, the derived conformance is either from the
// type itself or an extension, in which case we will emit the declaration // type itself or an extension, in which case we will emit the declaration
// normally. // normally.
if (type->hasClangNode()) if (target->hasClangNode())
tc.Context.addExternalDecl(encodeDecl); tc.Context.addExternalDecl(encodeDecl);
cast<IterableDeclContext>(type)->addMember(encodeDecl); cast<IterableDeclContext>(target)->addMember(encodeDecl);
return encodeDecl; return encodeDecl;
} }
@@ -666,18 +710,18 @@ static void deriveBodyDecodable_init(AbstractFunctionDecl *initDecl) {
// } // }
// The enclosing type decl. // The enclosing type decl.
auto *typeDecl = cast<NominalTypeDecl>(initDecl->getDeclContext()); auto *targetDecl = cast<NominalTypeDecl>(initDecl->getDeclContext());
auto *funcDC = cast<DeclContext>(initDecl); auto *funcDC = cast<DeclContext>(initDecl);
auto &C = funcDC->getASTContext(); auto &C = funcDC->getASTContext();
// We'll want the CodingKeys enum for this type. // We'll want the CodingKeys enum for this type, potentially looking through
auto *codingKeysDecl = typeDecl->lookupDirect(DeclName(C.Id_CodingKeys))[0]; // a typealias.
auto *codingKeysEnum = lookupEvaluatedCodingKeysEnum(C, targetDecl);
// We should have bailed already if: // We should have bailed already if:
// a) The type does not have CodingKeys // a) The type does not have CodingKeys
assert(codingKeysDecl && "Missing CodingKeys decl.");
// b) The type is not an enum // b) The type is not an enum
auto *codingKeysEnum = cast<EnumDecl>(codingKeysDecl); assert(codingKeysEnum && "Missing CodingKeys decl.");
// Generate a reference to containerExpr ahead of time in case there are no // 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 // properties to encode or decode, but the type is a class which inherits from
@@ -732,7 +776,7 @@ static void deriveBodyDecodable_init(AbstractFunctionDecl *initDecl) {
// Only ill-formed code would produce multiple results for this lookup. // 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 // This would get diagnosed later anyway, so we're free to only look at
// the first result here. // the first result here.
auto matchingVars = typeDecl->lookupDirect(DeclName(elt->getName())); auto matchingVars = targetDecl->lookupDirect(DeclName(elt->getName()));
auto *varDecl = cast<VarDecl>(matchingVars[0]); auto *varDecl = cast<VarDecl>(matchingVars[0]);
// Don't output a decode statement for a var let with a default value. // Don't output a decode statement for a var let with a default value.
@@ -742,8 +786,8 @@ static void deriveBodyDecodable_init(AbstractFunctionDecl *initDecl) {
// Type.self (where Type === type(of: x) // Type.self (where Type === type(of: x)
auto varType = varDecl->getType(); auto varType = varDecl->getType();
auto *metaTyRef = TypeExpr::createImplicit(varType, C); auto *metaTyRef = TypeExpr::createImplicit(varType, C);
auto *typeExpr = new (C) DotSelfExpr(metaTyRef, SourceLoc(), SourceLoc(), auto *targetExpr = new (C) DotSelfExpr(metaTyRef, SourceLoc(),
varType); SourceLoc(), varType);
// CodingKeys.x // CodingKeys.x
auto *eltRef = new (C) DeclRefExpr(elt, DeclNameLoc(), /*implicit=*/true); auto *eltRef = new (C) DeclRefExpr(elt, DeclNameLoc(), /*implicit=*/true);
@@ -758,7 +802,7 @@ static void deriveBodyDecodable_init(AbstractFunctionDecl *initDecl) {
/*Implicit=*/true); /*Implicit=*/true);
// container.decode(Type.self, forKey: CodingKeys.x) // container.decode(Type.self, forKey: CodingKeys.x)
Expr *args[2] = {typeExpr, keyExpr}; Expr *args[2] = {targetExpr, keyExpr};
auto *callExpr = CallExpr::createImplicit(C, decodeCall, auto *callExpr = CallExpr::createImplicit(C, decodeCall,
C.AllocateCopy(args), C.AllocateCopy(args),
C.AllocateCopy(argNames)); C.AllocateCopy(argNames));
@@ -778,7 +822,7 @@ static void deriveBodyDecodable_init(AbstractFunctionDecl *initDecl) {
} }
// Classes which inherit from something Decodable should decode super as well. // Classes which inherit from something Decodable should decode super as well.
auto *classDecl = dyn_cast<ClassDecl>(typeDecl); auto *classDecl = dyn_cast<ClassDecl>(targetDecl);
if (classDecl && superclassIsDecodable(classDecl)) { if (classDecl && superclassIsDecodable(classDecl)) {
// Need to generate `try super.init(from: container.superDecoder())` // Need to generate `try super.init(from: container.superDecoder())`
@@ -829,11 +873,11 @@ static void deriveBodyDecodable_init(AbstractFunctionDecl *initDecl) {
/// ///
/// \param parentDecl The parent declaration of the type. /// \param parentDecl The parent declaration of the type.
/// ///
/// \param type The nominal type to synthesize the function for. /// \param target The nominal type to synthesize the function for.
static ValueDecl *deriveDecodable_init(TypeChecker &tc, Decl *parentDecl, static ValueDecl *deriveDecodable_init(TypeChecker &tc, Decl *parentDecl,
NominalTypeDecl *type) { NominalTypeDecl *target) {
auto &C = tc.Context; auto &C = tc.Context;
auto *typeDC = cast<DeclContext>(type); auto *targetDC = cast<DeclContext>(target);
// Expected type: (Self) -> (Decoder) throws -> (Self) // Expected type: (Self) -> (Decoder) throws -> (Self)
// Constructed as: func type // Constructed as: func type
@@ -854,21 +898,21 @@ static ValueDecl *deriveDecodable_init(TypeChecker &tc, Decl *parentDecl,
/*Throws=*/true); /*Throws=*/true);
// (Self) // (Self)
auto returnType = typeDC->getDeclaredInterfaceType(); auto returnType = targetDC->getDeclaredInterfaceType();
// (from: Decoder) throws -> (Self) // (from: Decoder) throws -> (Self)
Type innerType = FunctionType::get(inputType, returnType, extInfo); Type innerType = FunctionType::get(inputType, returnType, extInfo);
// Params: (self [implicit], Decoder) // Params: (self [implicit], Decoder)
// self should be inout if the type is a value type; not inout otherwise. // self should be inout if the type is a value type; not inout otherwise.
auto inOut = !isa<ClassDecl>(type); auto inOut = !isa<ClassDecl>(target);
auto *selfDecl = ParamDecl::createSelf(SourceLoc(), typeDC, auto *selfDecl = ParamDecl::createSelf(SourceLoc(), targetDC,
/*isStatic=*/false, /*isStatic=*/false,
/*isInOut=*/inOut); /*isInOut=*/inOut);
auto *decoderParamDecl = new (C) ParamDecl(/*isLet=*/true, SourceLoc(), auto *decoderParamDecl = new (C) ParamDecl(/*isLet=*/true, SourceLoc(),
SourceLoc(), C.Id_from, SourceLoc(), C.Id_from,
SourceLoc(), C.Id_decoder, SourceLoc(), C.Id_decoder,
decoderType, typeDC); decoderType, targetDC);
decoderParamDecl->setImplicit(); decoderParamDecl->setImplicit();
decoderParamDecl->setInterfaceType(decoderType); decoderParamDecl->setInterfaceType(decoderType);
@@ -882,12 +926,12 @@ static ValueDecl *deriveDecodable_init(TypeChecker &tc, Decl *parentDecl,
/*Failability=*/OTK_None, /*Failability=*/OTK_None,
/*FailabilityLoc=*/SourceLoc(), /*FailabilityLoc=*/SourceLoc(),
/*Throws=*/true, /*ThrowsLoc=*/SourceLoc(), selfDecl, paramList, /*Throws=*/true, /*ThrowsLoc=*/SourceLoc(), selfDecl, paramList,
/*GenericParams=*/nullptr, typeDC); /*GenericParams=*/nullptr, targetDC);
initDecl->setImplicit(); initDecl->setImplicit();
initDecl->setBodySynthesizer(deriveBodyDecodable_init); initDecl->setBodySynthesizer(deriveBodyDecodable_init);
// This constructor should be marked as `required` for non-final classes. // This constructor should be marked as `required` for non-final classes.
if (isa<ClassDecl>(type) && !type->getAttrs().hasAttribute<FinalAttr>()) { if (isa<ClassDecl>(target) && !target->getAttrs().hasAttribute<FinalAttr>()) {
auto *reqAttr = new (C) SimpleDeclAttr<DAK_Required>(/*IsImplicit=*/true); auto *reqAttr = new (C) SimpleDeclAttr<DAK_Required>(/*IsImplicit=*/true);
initDecl->getAttrs().add(reqAttr); initDecl->getAttrs().add(reqAttr);
} }
@@ -896,9 +940,9 @@ static ValueDecl *deriveDecodable_init(TypeChecker &tc, Decl *parentDecl,
Type selfInitType = initDecl->computeInterfaceSelfType(/*init=*/true); Type selfInitType = initDecl->computeInterfaceSelfType(/*init=*/true);
Type interfaceType; Type interfaceType;
Type initializerType; Type initializerType;
if (auto sig = typeDC->getGenericSignatureOfContext()) { if (auto sig = targetDC->getGenericSignatureOfContext()) {
// Evaluate the below, but in a generic environment (if Self is generic). // Evaluate the below, but in a generic environment (if Self is generic).
initDecl->setGenericEnvironment(typeDC->getGenericEnvironmentOfContext()); initDecl->setGenericEnvironment(targetDC->getGenericEnvironmentOfContext());
interfaceType = GenericFunctionType::get(sig, selfType, innerType, interfaceType = GenericFunctionType::get(sig, selfType, innerType,
FunctionType::ExtInfo()); FunctionType::ExtInfo());
initializerType = GenericFunctionType::get(sig, selfInitType, innerType, initializerType = GenericFunctionType::get(sig, selfInitType, innerType,
@@ -912,15 +956,15 @@ static ValueDecl *deriveDecodable_init(TypeChecker &tc, Decl *parentDecl,
initDecl->setInterfaceType(interfaceType); initDecl->setInterfaceType(interfaceType);
initDecl->setInitializerInterfaceType(initializerType); initDecl->setInitializerInterfaceType(initializerType);
initDecl->setAccessibility( initDecl->setAccessibility(
std::max(type->getFormalAccess(), Accessibility::Internal)); std::max(target->getFormalAccess(), Accessibility::Internal));
// If the type was not imported, the derived conformance is either from the // If the type was not imported, the derived conformance is either from the
// type itself or an extension, in which case we will emit the declaration // type itself or an extension, in which case we will emit the declaration
// normally. // normally.
if (type->hasClangNode()) if (target->hasClangNode())
tc.Context.addExternalDecl(initDecl); tc.Context.addExternalDecl(initDecl);
cast<IterableDeclContext>(type)->addMember(initDecl); cast<IterableDeclContext>(target)->addMember(initDecl);
return initDecl; return initDecl;
} }
@@ -931,14 +975,14 @@ static ValueDecl *deriveDecodable_init(TypeChecker &tc, Decl *parentDecl,
/// ///
/// \param tc The typechecker to use in validating {En,Decodable} conformance. /// \param tc The typechecker to use in validating {En,Decodable} conformance.
/// ///
/// \param type The type to validate. /// \param target The type to validate.
/// ///
/// \param proto The *codable protocol to check for validity. /// \param proto The *codable protocol to check for validity.
static bool canSynthesize(TypeChecker &tc, NominalTypeDecl *type, static bool canSynthesize(TypeChecker &tc, NominalTypeDecl *target,
ProtocolDecl *proto) { ProtocolDecl *proto) {
// First, look up if the type has a valid CodingKeys enum we can use. // First, look up if the type has a valid CodingKeys enum we can use.
bool hasType, error; bool hasType, error;
std::tie(hasType, error) = hasValidCodingKeysEnum(tc, type, proto); std::tie(hasType, error) = hasValidCodingKeysEnum(tc, target, proto);
// We found a type, but it wasn't valid. // We found a type, but it wasn't valid.
if (error) if (error)
@@ -946,7 +990,7 @@ static bool canSynthesize(TypeChecker &tc, NominalTypeDecl *type,
// We can try to synthesize a type here. // We can try to synthesize a type here.
if (!hasType) { if (!hasType) {
auto *synthesizedEnum = synthesizeCodingKeysEnum(tc, type, proto); auto *synthesizedEnum = synthesizeCodingKeysEnum(tc, target, proto);
if (!synthesizedEnum) if (!synthesizedEnum)
return false; return false;
} }
@@ -956,10 +1000,10 @@ static bool canSynthesize(TypeChecker &tc, NominalTypeDecl *type,
ValueDecl *DerivedConformance::deriveEncodable(TypeChecker &tc, ValueDecl *DerivedConformance::deriveEncodable(TypeChecker &tc,
Decl *parentDecl, Decl *parentDecl,
NominalTypeDecl *type, NominalTypeDecl *target,
ValueDecl *requirement) { ValueDecl *requirement) {
// We can only synthesize Encodable for structs and classes. // We can only synthesize Encodable for structs and classes.
if (!isa<StructDecl>(type) && !isa<ClassDecl>(type)) if (!isa<StructDecl>(target) && !isa<ClassDecl>(target))
return nullptr; return nullptr;
if (requirement->getName() != tc.Context.Id_encode) { if (requirement->getName() != tc.Context.Id_encode) {
@@ -968,19 +1012,26 @@ ValueDecl *DerivedConformance::deriveEncodable(TypeChecker &tc,
return nullptr; return nullptr;
} }
// Conformance can't be synthesized in an extension.
auto encodableProto = tc.Context.getProtocol(KnownProtocolKind::Encodable);
auto encodableType = encodableProto->getDeclaredType();
if (target != parentDecl) {
tc.diagnose(parentDecl->getLoc(), diag::cannot_synthesize_in_extension,
encodableType);
return nullptr;
}
// Check other preconditions for synthesized conformance. // Check other preconditions for synthesized conformance.
// This synthesizes a CodingKeys enum if possible. // This synthesizes a CodingKeys enum if possible.
auto encodableProto = tc.Context.getProtocol(KnownProtocolKind::Encodable); if (canSynthesize(tc, target, encodableProto))
if (canSynthesize(tc, type, encodableProto)) return deriveEncodable_encode(tc, parentDecl, target);
return deriveEncodable_encode(tc, parentDecl, type);
// Known protocol requirement but could not synthesize. // Known protocol requirement but could not synthesize.
// FIXME: We have to output at least one error diagnostic here because we // FIXME: We have to output at least one error diagnostic here because we
// returned true from NominalTypeDecl::derivesProtocolConformance; if we // returned true from NominalTypeDecl::derivesProtocolConformance; if we
// don't, we expect to return a witness here later and crash on an // don't, we expect to return a witness here later and crash on an
// assertion. Producing an error stops compilation before then. // assertion. Producing an error stops compilation before then.
auto encodableType = encodableProto->getDeclaredType(); tc.diagnose(target, diag::type_does_not_conform, target->getDeclaredType(),
tc.diagnose(type, diag::type_does_not_conform, type->getDeclaredType(),
encodableType); encodableType);
tc.diagnose(requirement, diag::no_witnesses, diag::RequirementKind::Func, tc.diagnose(requirement, diag::no_witnesses, diag::RequirementKind::Func,
requirement->getFullName(), encodableType, /*AddFixIt=*/false); requirement->getFullName(), encodableType, /*AddFixIt=*/false);
@@ -989,10 +1040,10 @@ ValueDecl *DerivedConformance::deriveEncodable(TypeChecker &tc,
ValueDecl *DerivedConformance::deriveDecodable(TypeChecker &tc, ValueDecl *DerivedConformance::deriveDecodable(TypeChecker &tc,
Decl *parentDecl, Decl *parentDecl,
NominalTypeDecl *type, NominalTypeDecl *target,
ValueDecl *requirement) { ValueDecl *requirement) {
// We can only synthesize Encodable for structs and classes. // We can only synthesize Encodable for structs and classes.
if (!isa<StructDecl>(type) && !isa<ClassDecl>(type)) if (!isa<StructDecl>(target) && !isa<ClassDecl>(target))
return nullptr; return nullptr;
if (requirement->getName() != tc.Context.Id_init) { if (requirement->getName() != tc.Context.Id_init) {
@@ -1001,19 +1052,26 @@ ValueDecl *DerivedConformance::deriveDecodable(TypeChecker &tc,
return nullptr; return nullptr;
} }
// Conformance can't be synthesized in an extension.
auto decodableProto = tc.Context.getProtocol(KnownProtocolKind::Decodable);
auto decodableType = decodableProto->getDeclaredType();
if (target != parentDecl) {
tc.diagnose(parentDecl->getLoc(), diag::cannot_synthesize_in_extension,
decodableType);
return nullptr;
}
// Check other preconditions for synthesized conformance. // Check other preconditions for synthesized conformance.
// This synthesizes a CodingKeys enum if possible. // This synthesizes a CodingKeys enum if possible.
auto decodableProto = tc.Context.getProtocol(KnownProtocolKind::Decodable); if (canSynthesize(tc, target, decodableProto))
if (canSynthesize(tc, type, decodableProto)) return deriveDecodable_init(tc, parentDecl, target);
return deriveDecodable_init(tc, parentDecl, type);
// Known protocol requirement but could not synthesize. // Known protocol requirement but could not synthesize.
// FIXME: We have to output at least one error diagnostic here because we // FIXME: We have to output at least one error diagnostic here because we
// returned true from NominalTypeDecl::derivesProtocolConformance; if we // returned true from NominalTypeDecl::derivesProtocolConformance; if we
// don't, we expect to return a witness here later and crash on an // don't, we expect to return a witness here later and crash on an
// assertion. Producing an error stops compilation before then. // assertion. Producing an error stops compilation before then.
auto decodableType = decodableProto->getDeclaredType(); tc.diagnose(target, diag::type_does_not_conform, target->getDeclaredType(),
tc.diagnose(type, diag::type_does_not_conform, type->getDeclaredType(),
decodableType); decodableType);
tc.diagnose(requirement, diag::no_witnesses, tc.diagnose(requirement, diag::no_witnesses,
diag::RequirementKind::Constructor, requirement->getFullName(), diag::RequirementKind::Constructor, requirement->getFullName(),

View File

@@ -0,0 +1,27 @@
// RUN: %target-typecheck-verify-swift -verify-ignore-unknown
// Simple classes with all Codable properties whose CodingKeys come from a
// typealias should get derived conformance to Codable.
class SimpleClass : Codable {
var x: Int = 1
var y: Double = .pi
static var z: String = "foo"
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 _ = SimpleClass.init(from:)
let _ = SimpleClass.encode(to:)
// The synthesized CodingKeys type should not be accessible from outside the
// class.
let _ = SimpleClass.CodingKeys.self // expected-error {{'CodingKeys' is inaccessible due to 'private' protection level}}

View File

@@ -27,13 +27,10 @@ class ClassWithComputedMembers : Codable {
} }
} }
// These are wrapped in a dummy function to avoid binding a global variable. // They should receive synthesized init(from:) and an encode(to:).
func foo() { let _ = ClassWithComputedMembers.init(from:)
// They should receive synthesized init(from:) and an encode(to:). let _ = ClassWithComputedMembers.encode(to:)
let _ = ClassWithComputedMembers.init(from:)
let _ = ClassWithComputedMembers.encode(to:)
// The synthesized CodingKeys type should not be accessible from outside the // The synthesized CodingKeys type should not be accessible from outside the
// class. // class.
let _ = ClassWithComputedMembers.CodingKeys.self // expected-error {{'CodingKeys' is inaccessible due to 'private' protection level}} let _ = ClassWithComputedMembers.CodingKeys.self // expected-error {{'CodingKeys' is inaccessible due to 'private' protection level}}
}

View File

@@ -0,0 +1,26 @@
// RUN: %target-typecheck-verify-swift -verify-ignore-unknown
struct NonCodable {}
// Classes which have a default property that is not Codable, but which has a
// default value and is skipped in its CodingKeys should still derive
// conformance.
class SimpleClass : Codable {
var w: NonCodable = NonCodable()
var x: Int = 1
var y: Double = .pi
static var z: String = "foo"
private enum CodingKeys : String, CodingKey { // expected-note {{'CodingKeys' declared here}}
case x
case y
}
}
// They should receive synthesized init(from:) and an encode(to:).
let _ = SimpleClass.init(from:)
let _ = SimpleClass.encode(to:)
// The synthesized CodingKeys type should not be accessible from outside the
// class.
let _ = SimpleClass.CodingKeys.self // expected-error {{'CodingKeys' is inaccessible due to 'private' protection level}}

View File

@@ -43,13 +43,10 @@ class SimpleChildClass : SimpleClass {
} }
} }
// These are wrapped in a dummy function to avoid binding a global variable. // They should receive synthesized init(from:) and an encode(to:).
func foo() { let _ = SimpleChildClass.init(from:)
// They should receive synthesized init(from:) and an encode(to:). let _ = SimpleChildClass.encode(to:)
let _ = SimpleChildClass.init(from:)
let _ = SimpleChildClass.encode(to:)
// The synthesized CodingKeys type should not be accessible from outside the // The synthesized CodingKeys type should not be accessible from outside the
// class. // class.
let _ = SimpleChildClass.CodingKeys.self // expected-error {{'CodingKeys' is inaccessible due to 'private' protection level}} let _ = SimpleChildClass.CodingKeys.self // expected-error {{'CodingKeys' is inaccessible due to 'private' protection level}}
}

View File

@@ -0,0 +1,37 @@
// RUN: %target-typecheck-verify-swift -verify-ignore-unknown
// Classes with a CodingKeys entity which is not a type should not derive
// conformance.
class InvalidCodingKeys1 : Codable { // expected-error {{type 'InvalidCodingKeys1' does not conform to protocol 'Decodable'}}
// expected-error@-1 {{type 'InvalidCodingKeys1' does not conform to protocol 'Encodable'}}
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}}
}
// Classes with a CodingKeys entity which does not conform to CodingKey should
// not derive conformance.
class 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}}
}
// Classes with a CodingKeys entity which is not an enum should not derive
// conformance.
class 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
}
}
}

View File

@@ -0,0 +1,36 @@
// RUN: %target-typecheck-verify-swift -verify-ignore-unknown
struct NonCodable {}
// Classes whose properties are not all Codable should fail to synthesize
// conformance.
class NonConformingClass : Codable { // expected-error {{type 'NonConformingClass' does not conform to protocol 'Decodable'}}
// expected-error@-1 {{type 'NonConformingClass' does not conform to protocol 'Decodable'}}
// expected-error@-2 {{type 'NonConformingClass' does not conform to protocol 'Encodable'}}
// expected-error@-3 {{type 'NonConformingClass' does not conform to protocol 'Encodable'}}
// expected-note@-4 {{did you mean 'init'?}}
var w: NonCodable = NonCodable() // expected-note {{cannot automatically synthesize 'Decodable' because 'w' does not conform to 'Decodable'}}
// expected-note@-1 {{cannot automatically synthesize 'Decodable' because 'w' does not conform to 'Decodable'}}
// expected-note@-2 {{cannot automatically synthesize 'Encodable' because 'w' does not conform to 'Encodable'}}
// expected-note@-3 {{cannot automatically synthesize 'Encodable' because 'w' does not conform to 'Encodable'}}
var x: Int = 1
var y: Double = .pi
static var z: String = "foo"
// These lines have to be within the NonConformingClass type because
// CodingKeys should be private.
func foo() {
// They should not get a CodingKeys type.
let _ = NonConformingClass.CodingKeys.self // expected-error {{type 'NonConformingClass' has no member 'CodingKeys'}}
let _ = NonConformingClass.CodingKeys.x // expected-error {{type 'NonConformingClass' has no member 'CodingKeys'}}
let _ = NonConformingClass.CodingKeys.y // expected-error {{type 'NonConformingClass' has no member 'CodingKeys'}}
let _ = NonConformingClass.CodingKeys.z // expected-error {{type 'NonConformingClass' has no member 'CodingKeys'}}
}
}
// They should not receive Codable methods.
let _ = NonConformingClass.init(from:) // expected-error {{type 'NonConformingClass' has no member 'init(from:)'}}
let _ = NonConformingClass.encode(to:) // expected-error {{type 'NonConformingClass' has no member 'encode(to:)'}}
// They should not get a CodingKeys type.
let _ = NonConformingClass.CodingKeys.self // expected-error {{type 'NonConformingClass' has no member 'CodingKeys'}}

View File

@@ -22,13 +22,10 @@ class SimpleClass : Codable {
} }
} }
// These are wrapped in a dummy function to avoid binding a global variable. // They should receive synthesized init(from:) and an encode(to:).
func foo() { let _ = SimpleClass.init(from:)
// They should receive synthesized init(from:) and an encode(to:). let _ = SimpleClass.encode(to:)
let _ = SimpleClass.init(from:)
let _ = SimpleClass.encode(to:)
// The synthesized CodingKeys type should not be accessible from outside the // The synthesized CodingKeys type should not be accessible from outside the
// class. // class.
let _ = SimpleClass.CodingKeys.self // expected-error {{'CodingKeys' is inaccessible due to 'private' protection level}} let _ = SimpleClass.CodingKeys.self // expected-error {{'CodingKeys' is inaccessible due to 'private' protection level}}
}

View File

@@ -1,36 +1,29 @@
// RUN: %target-typecheck-verify-swift -verify-ignore-unknown // RUN: %target-typecheck-verify-swift
// Simple classes where Codable conformance is added in extensions should still // Simple classes where Codable conformance is added in extensions should not
// derive conformance. // derive conformance yet.
class SimpleClass { class SimpleClass { // expected-note {{did you mean 'init'?}}
var x: Int = 1 var x: Int = 1
var y: Double = .pi var y: Double = .pi
static var z: String = "foo" static var z: String = "foo"
// These lines have to be within the SimpleClass type because CodingKeys
// should be private.
func foo() { func foo() {
// They should receive a synthesized CodingKeys enum. // They should not get a CodingKeys type.
let _ = SimpleClass.CodingKeys.self let _ = SimpleClass.CodingKeys.self // expected-error {{type 'SimpleClass' has no member 'CodingKeys'}}
let _ = SimpleClass.CodingKeys.x // expected-error {{type 'SimpleClass' has no member 'CodingKeys'}}
// The enum should have a case for each of the vars. let _ = SimpleClass.CodingKeys.y // expected-error {{type 'SimpleClass' has no member 'CodingKeys'}}
let _ = SimpleClass.CodingKeys.x let _ = SimpleClass.CodingKeys.z // expected-error {{type 'SimpleClass' has no member 'CodingKeys'}}
let _ = SimpleClass.CodingKeys.y
// Static vars should not be part of the CodingKeys enum.
let _ = SimpleClass.CodingKeys.z // expected-error {{type 'SimpleClass.CodingKeys' has no member 'z'}}
} }
} }
extension SimpleClass : Codable {} extension SimpleClass : Codable {} // expected-error {{implementation of 'Decodable' cannot be automatically synthesized in an extension}}
// expected-error@-1 {{implementation of 'Decodable' cannot be automatically synthesized in an extension}}
// expected-error@-2 {{implementation of 'Encodable' cannot be automatically synthesized in an extension}}
// expected-error@-3 {{implementation of 'Encodable' cannot be automatically synthesized in an extension}}
// These are wrapped in a dummy function to avoid binding a global variable. // They should not receive Codable methods.
func foo() { let _ = SimpleClass.init(from:) // expected-error {{type 'SimpleClass' has no member 'init(from:)'}}
// They should receive synthesized init(from:) and an encode(to:). let _ = SimpleClass.encode(to:) // expected-error {{type 'SimpleClass' has no member 'encode(to:)'}}
let _ = SimpleClass.init(from:)
let _ = SimpleClass.encode(to:)
// The synthesized CodingKeys type should not be accessible from outside the // They should not get a CodingKeys type.
// class. let _ = SimpleClass.CodingKeys.self // expected-error {{type 'SimpleClass' has no member 'CodingKeys'}}
let _ = SimpleClass.CodingKeys.self // expected-error {{'CodingKeys' is inaccessible due to 'private' protection level}}
}

View File

@@ -0,0 +1,27 @@
// RUN: %target-typecheck-verify-swift -verify-ignore-unknown
// Simple structs with all Codable properties whose CodingKeys come from a
// typealias should get derived conformance to Codable.
struct SimpleStruct : Codable {
var x: Int = 1
var y: Double = .pi
static var z: String = "foo"
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 _ = SimpleStruct.init(from:)
let _ = SimpleStruct.encode(to:)
// The synthesized CodingKeys type should not be accessible from outside the
// struct.
let _ = SimpleStruct.CodingKeys.self // expected-error {{'CodingKeys' is inaccessible due to 'private' protection level}}

View File

@@ -0,0 +1,26 @@
// RUN: %target-typecheck-verify-swift -verify-ignore-unknown
struct NonCodable {}
// Structs which have a default property that is not Codable, but which has a
// default value and is skipped in its CodingKeys should still derive
// conformance.
struct SimpleStruct : Codable {
var w: NonCodable = NonCodable()
var x: Int
var y: Double
static var z: String = "foo"
private enum CodingKeys : String, CodingKey { // expected-note {{'CodingKeys' declared here}}
case x
case y
}
}
// They should receive synthesized init(from:) and an encode(to:).
let _ = SimpleStruct.init(from:)
let _ = SimpleStruct.encode(to:)
// The synthesized CodingKeys type should not be accessible from outside the
// struct.
let _ = SimpleStruct.CodingKeys.self // expected-error {{'CodingKeys' is inaccessible due to 'private' protection level}}

View File

@@ -0,0 +1,37 @@
// RUN: %target-typecheck-verify-swift -verify-ignore-unknown
// Structs with a CodingKeys entity which is not a type should not derive
// conformance.
struct InvalidCodingKeys1 : Codable { // expected-error {{type 'InvalidCodingKeys1' does not conform to protocol 'Decodable'}}
// expected-error@-1 {{type 'InvalidCodingKeys1' does not conform to protocol 'Encodable'}}
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}}
}
// Structs with a CodingKeys entity which does not conform to CodingKey should
// not derive conformance.
struct 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}}
}
// Structs with a CodingKeys entity which is not an enum should not derive
// conformance.
struct 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
}
}
}

View File

@@ -0,0 +1,36 @@
// RUN: %target-typecheck-verify-swift -verify-ignore-unknown
struct NonCodable {}
// Structs whose properties are not all Codable should fail to synthesize
// conformance.
struct NonConformingStruct : Codable { // expected-error {{type 'NonConformingStruct' does not conform to protocol 'Decodable'}}
// expected-error@-1 {{type 'NonConformingStruct' does not conform to protocol 'Decodable'}}
// expected-error@-2 {{type 'NonConformingStruct' does not conform to protocol 'Encodable'}}
// expected-error@-3 {{type 'NonConformingStruct' does not conform to protocol 'Encodable'}}
// expected-note@-4 {{did you mean 'init'?}}
var w: NonCodable // expected-note {{cannot automatically synthesize 'Decodable' because 'w' does not conform to 'Decodable'}}
// expected-note@-1 {{cannot automatically synthesize 'Decodable' because 'w' does not conform to 'Decodable'}}
// expected-note@-2 {{cannot automatically synthesize 'Encodable' because 'w' does not conform to 'Encodable'}}
// expected-note@-3 {{cannot automatically synthesize 'Encodable' because 'w' does not conform to 'Encodable'}}
var x: Int
var y: Double
static var z: String = "foo"
// These lines have to be within the NonConformingStruct type because
// CodingKeys should be private.
func foo() {
// They should not get a CodingKeys type.
let _ = NonConformingStruct.CodingKeys.self // expected-error {{type 'NonConformingStruct' has no member 'CodingKeys'}}
let _ = NonConformingStruct.CodingKeys.x // expected-error {{type 'NonConformingStruct' has no member 'CodingKeys'}}
let _ = NonConformingStruct.CodingKeys.y // expected-error {{type 'NonConformingStruct' has no member 'CodingKeys'}}
let _ = NonConformingStruct.CodingKeys.z // expected-error {{type 'NonConformingStruct' has no member 'CodingKeys'}}
}
}
// They should not receive Codable methods.
let _ = NonConformingStruct.init(from:) // expected-error {{type 'NonConformingStruct' has no member 'init(from:)'}}
let _ = NonConformingStruct.encode(to:) // expected-error {{type 'NonConformingStruct' has no member 'encode(to:)'}}
// They should not get a CodingKeys type.
let _ = NonConformingStruct.CodingKeys.self // expected-error {{type 'NonConformingStruct' has no member 'CodingKeys'}}

View File

@@ -1,36 +1,29 @@
// RUN: %target-typecheck-verify-swift -verify-ignore-unknown // RUN: %target-typecheck-verify-swift -verify-ignore-unknown
// Simple structs where Codable conformance is added in extensions should still // Simple structs where Codable conformance is added in extensions should not
// derive conformance. // derive conformance yet.
struct SimpleStruct { struct SimpleStruct { // expected-note {{did you mean 'init'?}}
var x: Int var x: Int
var y: Double var y: Double
static var z: String = "foo" static var z: String = "foo"
// These lines have to be within the SimpleStruct type because CodingKeys
// should be private.
func foo() { func foo() {
// They should receive synthesized init(from:) and an encode(to:). // They should not receive a synthesized CodingKeys enum.
let _ = SimpleStruct.init(from:) let _ = SimpleStruct.CodingKeys.self // expected-error {{type 'SimpleStruct' has no member 'CodingKeys'}}
let _ = SimpleStruct.encode(to:) let _ = SimpleStruct.CodingKeys.x // expected-error {{type 'SimpleStruct' has no member 'CodingKeys'}}
let _ = SimpleStruct.CodingKeys.y // expected-error {{type 'SimpleStruct' has no member 'CodingKeys'}}
// They should receive a synthesized CodingKeys enum. let _ = SimpleStruct.CodingKeys.z // expected-error {{type 'SimpleStruct' has no member 'CodingKeys'}}
let _ = SimpleStruct.CodingKeys.self
// The enum should have a case for each of the vars.
let _ = SimpleStruct.CodingKeys.x
let _ = SimpleStruct.CodingKeys.y
// Static vars should not be part of the CodingKeys enum.
let _ = SimpleStruct.CodingKeys.z // expected-error {{type 'SimpleStruct.CodingKeys' has no member 'z'}}
} }
} }
extension SimpleStruct : Codable {} extension SimpleStruct : Codable {} // expected-error {{implementation of 'Decodable' cannot be automatically synthesized in an extension}}
// expected-error@-1 {{implementation of 'Decodable' cannot be automatically synthesized in an extension}}
// expected-error@-2 {{implementation of 'Encodable' cannot be automatically synthesized in an extension}}
// expected-error@-3 {{implementation of 'Encodable' cannot be automatically synthesized in an extension}}
// These are wrapped in a dummy function to avoid binding a global variable. // They should not receive Codable methods.
func foo() { let _ = SimpleStruct.init(from:) // expected-error {{type 'SimpleStruct' has no member 'init(from:)'}}
// The synthesized CodingKeys type should not be accessible from outside the let _ = SimpleStruct.encode(to:) // expected-error {{type 'SimpleStruct' has no member 'encode(to:)'}}
// struct.
let _ = SimpleStruct.CodingKeys.self // expected-error {{'CodingKeys' is inaccessible due to 'private' protection level}} // They should not get a CodingKeys type.
} let _ = SimpleStruct.CodingKeys.self // expected-error {{type 'SimpleStruct' has no member 'CodingKeys'}}