mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
Merge pull request #11045 from itaiferber/codable-class-fixes
Codable Class Fixes
This commit is contained in:
@@ -2072,6 +2072,20 @@ NOTE(codable_codingkeys_type_is_not_an_enum_here,none,
|
|||||||
"cannot automatically synthesize %0 because 'CodingKeys' is not an enum", (Type))
|
"cannot automatically synthesize %0 because 'CodingKeys' is not an enum", (Type))
|
||||||
NOTE(codable_codingkeys_type_does_not_conform_here,none,
|
NOTE(codable_codingkeys_type_does_not_conform_here,none,
|
||||||
"cannot automatically synthesize %0 because 'CodingKeys' does not conform to CodingKey", (Type))
|
"cannot automatically synthesize %0 because 'CodingKeys' does not conform to CodingKey", (Type))
|
||||||
|
NOTE(decodable_no_super_init_here,none,
|
||||||
|
"cannot automatically synthesize %0 because superclass does not have a callable %1", (DeclName, DeclName))
|
||||||
|
NOTE(decodable_super_init_not_designated_here,none,
|
||||||
|
"cannot automatically synthesize %0 because implementation would need to call %1, which is not designated", (DeclName, DeclName))
|
||||||
|
NOTE(decodable_inaccessible_super_init_here,none,
|
||||||
|
"cannot automatically synthesize %0 because implementation would need to call %1, which is inaccessible due to "
|
||||||
|
"'%select{private|fileprivate|internal|%error|%error}2' protection level",
|
||||||
|
(DeclName, DeclName, Accessibility))
|
||||||
|
NOTE(decodable_super_init_is_failable_here,none,
|
||||||
|
"cannot automatically synthesize %0 because implementation would need to call %1, which is failable", (DeclName, DeclName))
|
||||||
|
NOTE(decodable_suggest_overriding_init_here,none,
|
||||||
|
"did you mean to override 'init(from:)'?", ())
|
||||||
|
NOTE(codable_suggest_overriding_init_here,none,
|
||||||
|
"did you mean to override 'init(from:)' and 'encode(to:)'?", ())
|
||||||
|
|
||||||
// Dynamic Self
|
// Dynamic Self
|
||||||
ERROR(dynamic_self_non_method,none,
|
ERROR(dynamic_self_non_method,none,
|
||||||
|
|||||||
@@ -184,8 +184,7 @@ static bool
|
|||||||
validateCodingKeysEnum(TypeChecker &tc, EnumDecl *codingKeysDecl,
|
validateCodingKeysEnum(TypeChecker &tc, EnumDecl *codingKeysDecl,
|
||||||
NominalTypeDecl *target, ProtocolDecl *proto) {
|
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.
|
||||||
// getStoredProperties).
|
|
||||||
// * Filter out ones which are present in the given decl (by name).
|
// * 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
|
// If any of the entries in the CodingKeys decl are not present in the type
|
||||||
@@ -197,6 +196,9 @@ validateCodingKeysEnum(TypeChecker &tc, EnumDecl *codingKeysDecl,
|
|||||||
// against its CodingKey entry, it will get removed.
|
// against its CodingKey entry, it will get removed.
|
||||||
llvm::SmallDenseMap<Identifier, VarDecl *, 8> properties;
|
llvm::SmallDenseMap<Identifier, VarDecl *, 8> properties;
|
||||||
for (auto *varDecl : target->getStoredProperties(/*skipInaccessible=*/true)) {
|
for (auto *varDecl : target->getStoredProperties(/*skipInaccessible=*/true)) {
|
||||||
|
if (varDecl->getAttrs().hasAttribute<LazyAttr>())
|
||||||
|
continue;
|
||||||
|
|
||||||
properties[varDecl->getName()] = varDecl;
|
properties[varDecl->getName()] = varDecl;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -242,7 +244,7 @@ validateCodingKeysEnum(TypeChecker &tc, EnumDecl *codingKeysDecl,
|
|||||||
// we can skip them on encode. On decode, though, we can only skip them if
|
// we can skip them on encode. On decode, though, we can only skip them if
|
||||||
// they have a default value.
|
// they have a default value.
|
||||||
if (!properties.empty() &&
|
if (!properties.empty() &&
|
||||||
proto == tc.Context.getProtocol(KnownProtocolKind::Decodable)) {
|
proto->isSpecificProtocol(KnownProtocolKind::Decodable)) {
|
||||||
for (auto it = properties.begin(); it != properties.end(); ++it) {
|
for (auto it = properties.begin(); it != properties.end(); ++it) {
|
||||||
if (it->second->getParentInitializer() != nullptr) {
|
if (it->second->getParentInitializer() != nullptr) {
|
||||||
// Var has a default value.
|
// Var has a default value.
|
||||||
@@ -303,6 +305,9 @@ static CodingKeysValidity hasValidCodingKeysEnum(TypeChecker &tc,
|
|||||||
return CodingKeysValidity(/*hasType=*/true, /*isValid=*/false);
|
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
|
// CodingKeys may be a typealias. If so, follow the alias to its canonical
|
||||||
// type.
|
// type.
|
||||||
auto codingKeysType = codingKeysTypeDecl->getDeclaredInterfaceType();
|
auto codingKeysType = codingKeysTypeDecl->getDeclaredInterfaceType();
|
||||||
@@ -381,6 +386,9 @@ static EnumDecl *synthesizeCodingKeysEnum(TypeChecker &tc,
|
|||||||
// conforms to {En,De}codable, add it to the enum.
|
// conforms to {En,De}codable, add it to the enum.
|
||||||
bool allConform = true;
|
bool allConform = true;
|
||||||
for (auto *varDecl : target->getStoredProperties(/*skipInaccessible=*/true)) {
|
for (auto *varDecl : target->getStoredProperties(/*skipInaccessible=*/true)) {
|
||||||
|
if (varDecl->getAttrs().hasAttribute<LazyAttr>())
|
||||||
|
continue;
|
||||||
|
|
||||||
auto conformance = varConformsToCodable(tc, target->getDeclContext(),
|
auto conformance = varConformsToCodable(tc, target->getDeclContext(),
|
||||||
varDecl, proto);
|
varDecl, proto);
|
||||||
switch (conformance) {
|
switch (conformance) {
|
||||||
@@ -663,8 +671,9 @@ static void deriveBodyEncodable_encode(AbstractFunctionDecl *encodeDecl) {
|
|||||||
// Need to generate `try super.encode(to: container.superEncoder())`
|
// Need to generate `try super.encode(to: container.superEncoder())`
|
||||||
|
|
||||||
// superEncoder()
|
// superEncoder()
|
||||||
auto *method = new (C) UnresolvedDeclRefExpr(
|
auto *method = new (C) UnresolvedDeclRefExpr(DeclName(C.Id_superEncoder),
|
||||||
DeclName(C.Id_superEncoder), DeclRefKind::Ordinary, DeclNameLoc());
|
DeclRefKind::Ordinary,
|
||||||
|
DeclNameLoc());
|
||||||
|
|
||||||
// container.superEncoder()
|
// container.superEncoder()
|
||||||
auto *superEncoderRef = new (C) DotSyntaxCallExpr(containerExpr,
|
auto *superEncoderRef = new (C) DotSyntaxCallExpr(containerExpr,
|
||||||
@@ -944,51 +953,91 @@ static void deriveBodyDecodable_init(AbstractFunctionDecl *initDecl) {
|
|||||||
/*Implicit=*/true);
|
/*Implicit=*/true);
|
||||||
|
|
||||||
auto *selfRef = createSelfDeclRef(initDecl);
|
auto *selfRef = createSelfDeclRef(initDecl);
|
||||||
auto *varExpr = new (C) UnresolvedDotExpr(
|
auto *varExpr = new (C) UnresolvedDotExpr(selfRef, SourceLoc(),
|
||||||
selfRef, SourceLoc(), DeclName(varDecl->getName()), DeclNameLoc(),
|
DeclName(varDecl->getName()),
|
||||||
/*implicit=*/true);
|
DeclNameLoc(),
|
||||||
|
/*implicit=*/true);
|
||||||
auto *assignExpr = new (C) AssignExpr(varExpr, SourceLoc(), tryExpr,
|
auto *assignExpr = new (C) AssignExpr(varExpr, SourceLoc(), tryExpr,
|
||||||
/*Implicit=*/true);
|
/*Implicit=*/true);
|
||||||
statements.push_back(assignExpr);
|
statements.push_back(assignExpr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Classes which inherit from something Decodable should decode super as well.
|
// Classes which have a superclass must call super.init(from:) if the
|
||||||
auto *classDecl = dyn_cast<ClassDecl>(targetDecl);
|
// superclass is Decodable, or super.init() if it is not.
|
||||||
if (classDecl && superclassIsDecodable(classDecl)) {
|
if (auto *classDecl = dyn_cast<ClassDecl>(targetDecl)) {
|
||||||
// Need to generate `try super.init(from: container.superDecoder())`
|
if (auto *superclassDecl = classDecl->getSuperclassDecl()) {
|
||||||
|
if (superclassIsDecodable(classDecl)) {
|
||||||
|
// Need to generate `try super.init(from: container.superDecoder())`
|
||||||
|
|
||||||
// superDecoder()
|
// container.superDecoder
|
||||||
auto *method = new (C) UnresolvedDeclRefExpr(
|
auto *superDecoderRef =
|
||||||
DeclName(C.Id_superDecoder), DeclRefKind::Ordinary, DeclNameLoc());
|
new (C) UnresolvedDotExpr(containerExpr, SourceLoc(),
|
||||||
|
DeclName(C.Id_superDecoder),
|
||||||
|
DeclNameLoc(), /*Implicit=*/true);
|
||||||
|
|
||||||
// container.superDecoder()
|
// container.superDecoder()
|
||||||
auto *superDecoderRef = new (C) DotSyntaxCallExpr(containerExpr,
|
auto *superDecoderCall =
|
||||||
SourceLoc(), method);
|
CallExpr::createImplicit(C, superDecoderRef, ArrayRef<Expr *>(),
|
||||||
|
ArrayRef<Identifier>());
|
||||||
|
|
||||||
// init(from:) expr
|
// super
|
||||||
auto *initDeclRef = new (C) DeclRefExpr(ConcreteDeclRef(initDecl),
|
auto *superRef = new (C) SuperRefExpr(initDecl->getImplicitSelfDecl(),
|
||||||
DeclNameLoc(), /*Implicit=*/true);
|
SourceLoc(), /*Implicit=*/true);
|
||||||
|
|
||||||
// super
|
// super.init(from:)
|
||||||
auto *superRef = new (C) SuperRefExpr(initDecl->getImplicitSelfDecl(),
|
auto initName = DeclName(C, C.Id_init, C.Id_from);
|
||||||
SourceLoc(), /*Implicit=*/true);
|
auto *initCall = new (C) UnresolvedDotExpr(superRef, SourceLoc(),
|
||||||
|
initName, DeclNameLoc(),
|
||||||
|
/*Implicit=*/true);
|
||||||
|
|
||||||
// super.init(from:)
|
// super.decode(from: container.superDecoder())
|
||||||
auto *decodeCall = new (C) DotSyntaxCallExpr(superRef, SourceLoc(),
|
Expr *args[1] = {superDecoderCall};
|
||||||
initDeclRef);
|
Identifier argLabels[1] = {C.Id_from};
|
||||||
|
auto *callExpr = CallExpr::createImplicit(C, initCall,
|
||||||
|
C.AllocateCopy(args),
|
||||||
|
C.AllocateCopy(argLabels));
|
||||||
|
|
||||||
// super.decode(from: container.superDecoder())
|
// try super.init(from: container.superDecoder())
|
||||||
Expr *args[1] = {superDecoderRef};
|
auto *tryExpr = new (C) TryExpr(SourceLoc(), callExpr, Type(),
|
||||||
Identifier argLabels[1] = {C.Id_from};
|
/*Implicit=*/true);
|
||||||
auto *callExpr = CallExpr::createImplicit(C, decodeCall,
|
statements.push_back(tryExpr);
|
||||||
C.AllocateCopy(args),
|
} else {
|
||||||
C.AllocateCopy(argLabels));
|
// The explicit constructor name is a compound name taking no arguments.
|
||||||
|
DeclName initName(C, C.Id_init, ArrayRef<Identifier>());
|
||||||
|
|
||||||
// try super.init(from: container.superDecoder())
|
// We need to look this up in the superclass to see if it throws.
|
||||||
auto *tryExpr = new (C) TryExpr(SourceLoc(), callExpr, Type(),
|
auto result = superclassDecl->lookupDirect(initName);
|
||||||
/*Implicit=*/true);
|
|
||||||
statements.push_back(tryExpr);
|
// 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<ConstructorDecl>(result.front());
|
||||||
|
assert(superInitDecl->getFailability() == OTK_None);
|
||||||
|
|
||||||
|
// super
|
||||||
|
auto *superRef = new (C) SuperRefExpr(initDecl->getImplicitSelfDecl(),
|
||||||
|
SourceLoc(), /*Implicit=*/true);
|
||||||
|
|
||||||
|
// super.init()
|
||||||
|
auto *superInitRef = new (C) UnresolvedDotExpr(superRef, SourceLoc(),
|
||||||
|
initName, DeclNameLoc(),
|
||||||
|
/*Implicit=*/true);
|
||||||
|
// super.init() call
|
||||||
|
Expr *callExpr = CallExpr::createImplicit(C, superInitRef,
|
||||||
|
ArrayRef<Expr *>(),
|
||||||
|
ArrayRef<Identifier>());
|
||||||
|
|
||||||
|
// 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(),
|
auto *body = BraceStmt::create(C, SourceLoc(), statements, SourceLoc(),
|
||||||
@@ -1053,12 +1102,10 @@ static ValueDecl *deriveDecodable_init(TypeChecker &tc, Decl *parentDecl,
|
|||||||
// Func name: init(from: Decoder)
|
// Func name: init(from: Decoder)
|
||||||
DeclName name(C, C.Id_init, paramList);
|
DeclName name(C, C.Id_init, paramList);
|
||||||
|
|
||||||
auto *initDecl = new (C) ConstructorDecl(
|
auto *initDecl = new (C) ConstructorDecl(name, SourceLoc(), OTK_None,
|
||||||
name, SourceLoc(),
|
SourceLoc(), /*Throws=*/true,
|
||||||
/*Failability=*/OTK_None,
|
SourceLoc(), selfDecl, paramList,
|
||||||
/*FailabilityLoc=*/SourceLoc(),
|
/*GenericParams=*/nullptr, target);
|
||||||
/*Throws=*/true, /*ThrowsLoc=*/SourceLoc(), selfDecl, paramList,
|
|
||||||
/*GenericParams=*/nullptr, target);
|
|
||||||
initDecl->setImplicit();
|
initDecl->setImplicit();
|
||||||
initDecl->setBodySynthesizer(deriveBodyDecodable_init);
|
initDecl->setBodySynthesizer(deriveBodyDecodable_init);
|
||||||
|
|
||||||
@@ -1089,8 +1136,8 @@ 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(target->getFormalAccess(),
|
||||||
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
|
||||||
@@ -1111,10 +1158,80 @@ static ValueDecl *deriveDecodable_init(TypeChecker &tc, Decl *parentDecl,
|
|||||||
///
|
///
|
||||||
/// \param target The type to validate.
|
/// \param target The type to validate.
|
||||||
///
|
///
|
||||||
|
/// \param requirement The requirement we want to synthesize.
|
||||||
|
///
|
||||||
/// \param proto The *codable protocol to check for validity.
|
/// \param proto The *codable protocol to check for validity.
|
||||||
static bool canSynthesize(TypeChecker &tc, NominalTypeDecl *target,
|
static bool canSynthesize(TypeChecker &tc, NominalTypeDecl *target,
|
||||||
ProtocolDecl *proto) {
|
ValueDecl *requirement, ProtocolDecl *proto) {
|
||||||
// First, look up if the type has a valid CodingKeys enum we can use.
|
// 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.
|
||||||
|
ASTContext &C = tc.Context;
|
||||||
|
auto *classDecl = dyn_cast<ClassDecl>(target);
|
||||||
|
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(C.Id_init),
|
||||||
|
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());
|
||||||
|
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(target)) {
|
||||||
|
// Cannot call an inaccessible method.
|
||||||
|
auto accessScope = initializer->getFormalAccessScope(target);
|
||||||
|
tc.diagnose(initializer, diag::decodable_inaccessible_super_init_here,
|
||||||
|
requirement->getFullName(), memberName,
|
||||||
|
accessScope.accessibilityForDiagnostics());
|
||||||
|
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(tc, target, proto);
|
auto validity = hasValidCodingKeysEnum(tc, target, proto);
|
||||||
|
|
||||||
// We found a type, but it wasn't valid.
|
// We found a type, but it wasn't valid.
|
||||||
@@ -1172,23 +1289,17 @@ ValueDecl *DerivedConformance::deriveEncodable(TypeChecker &tc,
|
|||||||
auto diagnosticTransaction = DiagnosticTransaction(tc.Context.Diags);
|
auto diagnosticTransaction = DiagnosticTransaction(tc.Context.Diags);
|
||||||
tc.diagnose(target, diag::type_does_not_conform, target->getDeclaredType(),
|
tc.diagnose(target, diag::type_does_not_conform, target->getDeclaredType(),
|
||||||
encodableType);
|
encodableType);
|
||||||
|
tc.diagnose(requirement, diag::no_witnesses, diag::RequirementKind::Func,
|
||||||
|
requirement->getFullName(), encodableType, /*AddFixIt=*/false);
|
||||||
|
|
||||||
// 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.
|
||||||
ValueDecl *witness = nullptr;
|
if (canSynthesize(tc, target, requirement, encodableProto)) {
|
||||||
if (canSynthesize(tc, target, encodableProto))
|
|
||||||
witness = deriveEncodable_encode(tc, parentDecl, target);
|
|
||||||
|
|
||||||
if (witness == nullptr) {
|
|
||||||
// We didn't end up synthesizing encode(to:).
|
|
||||||
tc.diagnose(requirement, diag::no_witnesses, diag::RequirementKind::Func,
|
|
||||||
requirement->getFullName(), encodableType, /*AddFixIt=*/false);
|
|
||||||
} else {
|
|
||||||
// We succeeded -- no need to output the false error generated above.
|
|
||||||
diagnosticTransaction.abort();
|
diagnosticTransaction.abort();
|
||||||
|
return deriveEncodable_encode(tc, parentDecl, target);
|
||||||
}
|
}
|
||||||
|
|
||||||
return witness;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
ValueDecl *DerivedConformance::deriveDecodable(TypeChecker &tc,
|
ValueDecl *DerivedConformance::deriveDecodable(TypeChecker &tc,
|
||||||
@@ -1222,22 +1333,16 @@ ValueDecl *DerivedConformance::deriveDecodable(TypeChecker &tc,
|
|||||||
auto diagnosticTransaction = DiagnosticTransaction(tc.Context.Diags);
|
auto diagnosticTransaction = DiagnosticTransaction(tc.Context.Diags);
|
||||||
tc.diagnose(target, diag::type_does_not_conform, target->getDeclaredType(),
|
tc.diagnose(target, diag::type_does_not_conform, target->getDeclaredType(),
|
||||||
decodableType);
|
decodableType);
|
||||||
|
tc.diagnose(requirement, diag::no_witnesses,
|
||||||
|
diag::RequirementKind::Constructor, requirement->getFullName(),
|
||||||
|
decodableType, /*AddFixIt=*/false);
|
||||||
|
|
||||||
// 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.
|
||||||
ValueDecl *witness = nullptr;
|
if (canSynthesize(tc, target, requirement, decodableProto)) {
|
||||||
if (canSynthesize(tc, target, decodableProto))
|
|
||||||
witness = deriveDecodable_init(tc, parentDecl, target);
|
|
||||||
|
|
||||||
if (witness == nullptr) {
|
|
||||||
// We didn't end up synthesizing init(from:).
|
|
||||||
tc.diagnose(requirement, diag::no_witnesses,
|
|
||||||
diag::RequirementKind::Constructor, requirement->getFullName(),
|
|
||||||
decodableType, /*AddFixIt=*/false);
|
|
||||||
} else {
|
|
||||||
// We succeeded -- no need to output the false error generated above.
|
|
||||||
diagnosticTransaction.abort();
|
diagnosticTransaction.abort();
|
||||||
|
return deriveDecodable_init(tc, parentDecl, target);
|
||||||
}
|
}
|
||||||
|
|
||||||
return witness;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -407,8 +407,9 @@ static bool canSynthesizeCodingKey(TypeChecker &tc, Decl *parentDecl,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!enumDecl->getInherited().empty() &&
|
auto inherited = enumDecl->getInherited();
|
||||||
enumDecl->getInherited().front().isError())
|
if (!inherited.empty() && inherited.front().wasValidated() &&
|
||||||
|
inherited.front().isError())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// If it meets all of those requirements, we can synthesize CodingKey
|
// If it meets all of those requirements, we can synthesize CodingKey
|
||||||
|
|||||||
@@ -343,8 +343,9 @@ static bool canSynthesizeRawRepresentable(TypeChecker &tc, Decl *parentDecl,
|
|||||||
auto parentDC = cast<DeclContext>(parentDecl);
|
auto parentDC = cast<DeclContext>(parentDecl);
|
||||||
rawType = parentDC->mapTypeIntoContext(rawType);
|
rawType = parentDC->mapTypeIntoContext(rawType);
|
||||||
|
|
||||||
if (!enumDecl->getInherited().empty() &&
|
auto inherited = enumDecl->getInherited();
|
||||||
enumDecl->getInherited().front().isError())
|
if (!inherited.empty() && inherited.front().wasValidated() &&
|
||||||
|
inherited.front().isError())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// The raw type must be Equatable, so that we have a suitable ~= for
|
// The raw type must be Equatable, so that we have a suitable ~= for
|
||||||
|
|||||||
@@ -7944,6 +7944,67 @@ static void diagnoseClassWithoutInitializers(TypeChecker &tc,
|
|||||||
tc.diagnose(classDecl, diag::class_without_init,
|
tc.diagnose(classDecl, diag::class_without_init,
|
||||||
classDecl->getDeclaredType());
|
classDecl->getDeclaredType());
|
||||||
|
|
||||||
|
// HACK: We've got a special case to look out for and diagnose specifically to
|
||||||
|
// improve the experience of seeing this, and mitigate some confusion.
|
||||||
|
//
|
||||||
|
// For a class A which inherits from Decodable class B, class A may have
|
||||||
|
// additional members which prevent default initializer synthesis (and
|
||||||
|
// inheritance of other initializers). The user may have assumed that this
|
||||||
|
// case would synthesize Encodable/Decodable conformance for class A the same
|
||||||
|
// way it may have for class B, or other classes.
|
||||||
|
//
|
||||||
|
// It is helpful to suggest here that the user may have forgotten to override
|
||||||
|
// init(from:) (and encode(to:), if applicable) in a note, before we start
|
||||||
|
// listing the members that prevented initializer synthesis.
|
||||||
|
// TODO: Add a fixit along with this suggestion.
|
||||||
|
if (auto *superclassDecl = classDecl->getSuperclassDecl()) {
|
||||||
|
ASTContext &C = tc.Context;
|
||||||
|
auto *decodableProto = C.getProtocol(KnownProtocolKind::Decodable);
|
||||||
|
auto superclassType = superclassDecl->getDeclaredInterfaceType();
|
||||||
|
if (auto ref = tc.conformsToProtocol(superclassType, decodableProto,
|
||||||
|
superclassDecl,
|
||||||
|
ConformanceCheckOptions(),
|
||||||
|
SourceLoc())) {
|
||||||
|
// super conforms to Decodable, so we've failed to inherit init(from:).
|
||||||
|
// Let's suggest overriding it here.
|
||||||
|
//
|
||||||
|
// We're going to diagnose on the concrete init(from:) decl if it exists
|
||||||
|
// and isn't implicit; otherwise, on the subclass itself.
|
||||||
|
ValueDecl *diagDest = classDecl;
|
||||||
|
auto initFrom = DeclName(C, C.Id_init, C.Id_from);
|
||||||
|
auto result = tc.lookupMember(superclassDecl, superclassType, initFrom,
|
||||||
|
NameLookupFlags::ProtocolMembers |
|
||||||
|
NameLookupFlags::IgnoreAccessibility);
|
||||||
|
|
||||||
|
if (!result.empty() && !result.front().getValueDecl()->isImplicit())
|
||||||
|
diagDest = result.front().getValueDecl();
|
||||||
|
|
||||||
|
auto diagName = diag::decodable_suggest_overriding_init_here;
|
||||||
|
|
||||||
|
// This is also a bit of a hack, but the best place we've got at the
|
||||||
|
// moment to suggest this.
|
||||||
|
//
|
||||||
|
// If the superclass also conforms to Encodable, it's quite
|
||||||
|
// likely that the user forgot to override its encode(to:). In this case,
|
||||||
|
// we can produce a slightly different diagnostic to suggest doing so.
|
||||||
|
auto *encodableProto = C.getProtocol(KnownProtocolKind::Encodable);
|
||||||
|
if ((ref = tc.conformsToProtocol(superclassType, encodableProto,
|
||||||
|
superclassDecl,
|
||||||
|
ConformanceCheckOptions(),
|
||||||
|
SourceLoc()))) {
|
||||||
|
// We only want to produce this version of the diagnostic if the
|
||||||
|
// subclass doesn't directly implement encode(to:).
|
||||||
|
// The direct lookup here won't see an encode(to:) if it is inherited
|
||||||
|
// from the superclass.
|
||||||
|
auto encodeTo = DeclName(C, C.Id_encode, C.Id_to);
|
||||||
|
if (classDecl->lookupDirect(encodeTo).empty())
|
||||||
|
diagName = diag::codable_suggest_overriding_init_here;
|
||||||
|
}
|
||||||
|
|
||||||
|
tc.diagnose(diagDest, diagName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (auto member : classDecl->getMembers()) {
|
for (auto member : classDecl->getMembers()) {
|
||||||
auto pbd = dyn_cast<PatternBindingDecl>(member);
|
auto pbd = dyn_cast<PatternBindingDecl>(member);
|
||||||
if (!pbd)
|
if (!pbd)
|
||||||
@@ -8131,14 +8192,49 @@ void TypeChecker::addImplicitConstructors(NominalTypeDecl *decl) {
|
|||||||
bool FoundMemberwiseInitializedProperty = false;
|
bool FoundMemberwiseInitializedProperty = false;
|
||||||
bool SuppressDefaultInitializer = false;
|
bool SuppressDefaultInitializer = false;
|
||||||
bool SuppressMemberwiseInitializer = false;
|
bool SuppressMemberwiseInitializer = false;
|
||||||
|
bool FoundSynthesizedInit = false;
|
||||||
bool FoundDesignatedInit = false;
|
bool FoundDesignatedInit = false;
|
||||||
decl->setAddedImplicitInitializers();
|
decl->setAddedImplicitInitializers();
|
||||||
|
|
||||||
|
// Before we look for constructors, we need to make sure that all synthesized
|
||||||
|
// initializers are properly synthesized.
|
||||||
|
//
|
||||||
|
// NOTE: Lookups of synthesized initializers MUST come after
|
||||||
|
// decl->setAddedImplicitInitializers() in case synthesis requires
|
||||||
|
// protocol conformance checking, which might be recursive here.
|
||||||
|
// FIXME: Disable this code and prevent _any_ implicit constructors from doing
|
||||||
|
// this. Investigate why this hasn't worked otherwise.
|
||||||
|
DeclName synthesizedInitializers[1] = {
|
||||||
|
// init(from:) is synthesized by derived conformance to Decodable.
|
||||||
|
DeclName(Context, DeclBaseName(Context.Id_init), Context.Id_from)
|
||||||
|
};
|
||||||
|
|
||||||
|
auto initializerIsSynthesized = [=](ConstructorDecl *initializer) {
|
||||||
|
if (!initializer->isImplicit())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
for (auto &name : synthesizedInitializers)
|
||||||
|
if (initializer->getFullName() == name)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
for (auto &name : synthesizedInitializers) {
|
||||||
|
synthesizeMemberForLookup(decl, name);
|
||||||
|
}
|
||||||
|
|
||||||
SmallPtrSet<CanType, 4> initializerParamTypes;
|
SmallPtrSet<CanType, 4> initializerParamTypes;
|
||||||
llvm::SmallPtrSet<ConstructorDecl *, 4> overriddenInits;
|
llvm::SmallPtrSet<ConstructorDecl *, 4> overriddenInits;
|
||||||
for (auto member : decl->getMembers()) {
|
for (auto member : decl->getMembers()) {
|
||||||
if (auto ctor = dyn_cast<ConstructorDecl>(member)) {
|
if (auto ctor = dyn_cast<ConstructorDecl>(member)) {
|
||||||
if (ctor->isDesignatedInit())
|
// Synthesized initializers others than the default initializer should
|
||||||
|
// not prevent default initializer synthesis.
|
||||||
|
if (initializerIsSynthesized(ctor)) {
|
||||||
|
FoundSynthesizedInit = true;
|
||||||
|
} else if (ctor->isDesignatedInit()) {
|
||||||
FoundDesignatedInit = true;
|
FoundDesignatedInit = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (!ctor->isInvalid())
|
if (!ctor->isInvalid())
|
||||||
initializerParamTypes.insert(getInitializerParamType(ctor));
|
initializerParamTypes.insert(getInitializerParamType(ctor));
|
||||||
@@ -8233,7 +8329,8 @@ void TypeChecker::addImplicitConstructors(NominalTypeDecl *decl) {
|
|||||||
|
|
||||||
// We can't define these overrides if we have any uninitialized
|
// We can't define these overrides if we have any uninitialized
|
||||||
// stored properties.
|
// stored properties.
|
||||||
if (SuppressDefaultInitializer && !FoundDesignatedInit) {
|
if (SuppressDefaultInitializer && !FoundDesignatedInit
|
||||||
|
&& !FoundSynthesizedInit) {
|
||||||
diagnoseClassWithoutInitializers(*this, classDecl);
|
diagnoseClassWithoutInitializers(*this, classDecl);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -8325,7 +8422,9 @@ void TypeChecker::addImplicitConstructors(NominalTypeDecl *decl) {
|
|||||||
|
|
||||||
// ... unless there are uninitialized stored properties.
|
// ... unless there are uninitialized stored properties.
|
||||||
if (SuppressDefaultInitializer) {
|
if (SuppressDefaultInitializer) {
|
||||||
diagnoseClassWithoutInitializers(*this, classDecl);
|
if (!FoundSynthesizedInit)
|
||||||
|
diagnoseClassWithoutInitializers(*this, classDecl);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
// RUN: %target-typecheck-verify-swift -verify-ignore-unknown
|
||||||
|
|
||||||
|
// A class with no initializers (which has non-initialized properties so a
|
||||||
|
// default constructor can be synthesized) should produce an error.
|
||||||
|
class NoInitializers { // expected-error {{class 'NoInitializers' has no initializers}}
|
||||||
|
// expected-note@-1 {{did you mean 'deinit'?}}
|
||||||
|
var x: Double // expected-note {{stored property 'x' without initial value prevents synthesized initializers}}
|
||||||
|
|
||||||
|
func foo() {
|
||||||
|
// The class should not receive a default constructor.
|
||||||
|
let _ = NoInitializers.init() // expected-error {{type 'NoInitializers' has no member 'init'}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A similar class with Codable properties adopting Codable should get a
|
||||||
|
// synthesized init(from:), and thus not warn.
|
||||||
|
class CodableNoExplicitInitializers : Codable {
|
||||||
|
var x: Double
|
||||||
|
|
||||||
|
func foo() {
|
||||||
|
// The class should receive a synthesized init(from:) and encode(to:)
|
||||||
|
let _ = CodableNoExplicitInitializers.init(from:)
|
||||||
|
let _ = CodableNoExplicitInitializers.encode(to:)
|
||||||
|
|
||||||
|
// It should not, however, receive a default constructor.
|
||||||
|
let _ = CodableNoExplicitInitializers.init() // expected-error {{missing argument for parameter 'from' in call}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A class with all initialized properties should receive a default constructor.
|
||||||
|
class DefaultConstructed {
|
||||||
|
var x: Double = .pi
|
||||||
|
|
||||||
|
func foo() {
|
||||||
|
let _ = DefaultConstructed.init()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A class with all initialized, Codable properties adopting Codable should get
|
||||||
|
// the default constructor, along with a synthesized init(from:).
|
||||||
|
class CodableDefaultConstructed : Codable {
|
||||||
|
var x: Double = .pi
|
||||||
|
|
||||||
|
func foo() {
|
||||||
|
let _ = CodableDefaultConstructed.init()
|
||||||
|
let _ = CodableDefaultConstructed.init(from:)
|
||||||
|
let _ = CodableDefaultConstructed.encode(to:)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,12 +8,12 @@ class C1 : Codable {
|
|||||||
var c: Nested = Nested()
|
var c: Nested = Nested()
|
||||||
|
|
||||||
// CHECK: error: type 'C1' does not conform to protocol 'Decodable'
|
// CHECK: error: type 'C1' does not conform to protocol 'Decodable'
|
||||||
// CHECK: note: cannot automatically synthesize 'Decodable' because 'C1.Nested' does not conform to 'Decodable'
|
|
||||||
// CHECK: note: protocol requires initializer 'init(from:)' with type 'Decodable'
|
// CHECK: note: protocol requires initializer 'init(from:)' with type 'Decodable'
|
||||||
|
// CHECK: note: cannot automatically synthesize 'Decodable' because 'C1.Nested' does not conform to 'Decodable'
|
||||||
|
|
||||||
// CHECK: error: type 'C1' does not conform to protocol 'Encodable'
|
// CHECK: error: type 'C1' does not conform to protocol 'Encodable'
|
||||||
// CHECK: note: cannot automatically synthesize 'Encodable' because 'C1.Nested' does not conform to 'Encodable'
|
|
||||||
// CHECK: note: protocol requires function 'encode(to:)' with type 'Encodable'
|
// CHECK: note: protocol requires function 'encode(to:)' with type 'Encodable'
|
||||||
|
// CHECK: note: cannot automatically synthesize 'Encodable' because 'C1.Nested' does not conform to 'Encodable'
|
||||||
}
|
}
|
||||||
|
|
||||||
// Codable class with non-enum CodingKeys.
|
// Codable class with non-enum CodingKeys.
|
||||||
@@ -30,12 +30,12 @@ class C2 : Codable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CHECK: error: type 'C2' does not conform to protocol 'Decodable'
|
// CHECK: error: type 'C2' does not conform to protocol 'Decodable'
|
||||||
// CHECK: note: cannot automatically synthesize 'Decodable' because 'CodingKeys' is not an enum
|
|
||||||
// CHECK: note: protocol requires initializer 'init(from:)' with type 'Decodable'
|
// CHECK: note: protocol requires initializer 'init(from:)' with type 'Decodable'
|
||||||
|
// CHECK: note: cannot automatically synthesize 'Decodable' because 'CodingKeys' is not an enum
|
||||||
|
|
||||||
// CHECK: error: type 'C2' does not conform to protocol 'Encodable'
|
// CHECK: error: type 'C2' does not conform to protocol 'Encodable'
|
||||||
// CHECK: note: cannot automatically synthesize 'Encodable' because 'CodingKeys' is not an enum
|
|
||||||
// CHECK: note: protocol requires function 'encode(to:)' with type 'Encodable'
|
// CHECK: note: protocol requires function 'encode(to:)' with type 'Encodable'
|
||||||
|
// CHECK: note: cannot automatically synthesize 'Encodable' because 'CodingKeys' is not an enum
|
||||||
}
|
}
|
||||||
|
|
||||||
// Codable class with CodingKeys not conforming to CodingKey.
|
// Codable class with CodingKeys not conforming to CodingKey.
|
||||||
@@ -51,12 +51,12 @@ class C3 : Codable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CHECK: error: type 'C3' does not conform to protocol 'Decodable'
|
// CHECK: error: type 'C3' does not conform to protocol 'Decodable'
|
||||||
// CHECK: note: cannot automatically synthesize 'Decodable' because 'CodingKeys' does not conform to CodingKey
|
|
||||||
// CHECK: note: protocol requires initializer 'init(from:)' with type 'Decodable'
|
// CHECK: note: protocol requires initializer 'init(from:)' with type 'Decodable'
|
||||||
|
// CHECK: note: cannot automatically synthesize 'Decodable' because 'CodingKeys' does not conform to CodingKey
|
||||||
|
|
||||||
// CHECK: error: type 'C3' does not conform to protocol 'Encodable'
|
// CHECK: error: type 'C3' does not conform to protocol 'Encodable'
|
||||||
// CHECK: note: cannot automatically synthesize 'Encodable' because 'CodingKeys' does not conform to CodingKey
|
|
||||||
// CHECK: note: protocol requires function 'encode(to:)' with type 'Encodable'
|
// CHECK: note: protocol requires function 'encode(to:)' with type 'Encodable'
|
||||||
|
// CHECK: note: cannot automatically synthesize 'Encodable' because 'CodingKeys' does not conform to CodingKey
|
||||||
}
|
}
|
||||||
|
|
||||||
// Codable class with extraneous CodingKeys
|
// Codable class with extraneous CodingKeys
|
||||||
@@ -75,16 +75,16 @@ class C4 : Codable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CHECK: error: type 'C4' does not conform to protocol 'Decodable'
|
// CHECK: error: type 'C4' does not conform to protocol 'Decodable'
|
||||||
|
// CHECK: note: protocol requires initializer 'init(from:)' with type 'Decodable'
|
||||||
// CHECK: note: CodingKey case 'a2' does not match any stored properties
|
// CHECK: note: CodingKey case 'a2' does not match any stored properties
|
||||||
// CHECK: note: CodingKey case 'b2' does not match any stored properties
|
// CHECK: note: CodingKey case 'b2' does not match any stored properties
|
||||||
// CHECK: note: CodingKey case 'c2' does not match any stored properties
|
// CHECK: note: CodingKey case 'c2' does not match any stored properties
|
||||||
// CHECK: note: protocol requires initializer 'init(from:)' with type 'Decodable'
|
|
||||||
|
|
||||||
// CHECK: error: type 'C4' does not conform to protocol 'Encodable'
|
// CHECK: error: type 'C4' does not conform to protocol 'Encodable'
|
||||||
|
// CHECK: note: protocol requires function 'encode(to:)' with type 'Encodable'
|
||||||
// CHECK: note: CodingKey case 'a2' does not match any stored properties
|
// CHECK: note: CodingKey case 'a2' does not match any stored properties
|
||||||
// CHECK: note: CodingKey case 'b2' does not match any stored properties
|
// CHECK: note: CodingKey case 'b2' does not match any stored properties
|
||||||
// CHECK: note: CodingKey case 'c2' does not match any stored properties
|
// CHECK: note: CodingKey case 'c2' does not match any stored properties
|
||||||
// CHECK: note: protocol requires function 'encode(to:)' with type 'Encodable'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Codable class with non-decoded property (which has no default value).
|
// Codable class with non-decoded property (which has no default value).
|
||||||
@@ -98,12 +98,12 @@ class C5 : Codable {
|
|||||||
case c
|
case c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CHECK: error: type 'C5' does not conform to protocol 'Decodable'
|
||||||
|
// CHECK: note: protocol requires initializer 'init(from:)' with type 'Decodable'
|
||||||
|
// CHECK: note: cannot automatically synthesize 'Decodable' because 'b' does not have a matching CodingKey and does not have a default value
|
||||||
|
|
||||||
// CHECK: error: class 'C5' has no initializers
|
// CHECK: error: class 'C5' has no initializers
|
||||||
// CHECK: note: stored property 'b' without initial value prevents synthesized initializers
|
// CHECK: note: stored property 'b' without initial value prevents synthesized initializers
|
||||||
|
|
||||||
// CHECK: error: type 'C5' does not conform to protocol 'Decodable'
|
|
||||||
// CHECK: note: cannot automatically synthesize 'Decodable' because 'b' does not have a matching CodingKey and does not have a default value
|
|
||||||
// CHECK: note: protocol requires initializer 'init(from:)' with type 'Decodable'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Codable class with non-decoded property (which has no default value).
|
// Codable class with non-decoded property (which has no default value).
|
||||||
@@ -122,8 +122,8 @@ class C6 : Codable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CHECK: error: type 'C6' does not conform to protocol 'Decodable'
|
// CHECK: error: type 'C6' does not conform to protocol 'Decodable'
|
||||||
// CHECK: note: cannot automatically synthesize 'Decodable' because 'b' does not have a matching CodingKey and does not have a default value
|
|
||||||
// CHECK: note: protocol requires initializer 'init(from:)' with type 'Decodable'
|
// CHECK: note: protocol requires initializer 'init(from:)' with type 'Decodable'
|
||||||
|
// CHECK: note: cannot automatically synthesize 'Decodable' because 'b' does not have a matching CodingKey and does not have a default value
|
||||||
}
|
}
|
||||||
|
|
||||||
// Classes cannot yet synthesize Encodable or Decodable in extensions.
|
// Classes cannot yet synthesize Encodable or Decodable in extensions.
|
||||||
|
|||||||
@@ -0,0 +1,95 @@
|
|||||||
|
// RUN: %target-typecheck-verify-swift -verify-ignore-unknown
|
||||||
|
|
||||||
|
// Non-Decodable superclasses of synthesized Decodable classes must implement
|
||||||
|
// init().
|
||||||
|
class NonDecodableSuper { // expected-note {{cannot automatically synthesize 'init(from:)' because superclass does not have a callable 'init()'}}
|
||||||
|
init(_: Int) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
class NonDecodableSub : NonDecodableSuper, Decodable { // expected-error {{type 'NonDecodableSub' does not conform to protocol 'Decodable'}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Non-Decodable superclasses of synthesized Decodable classes must have
|
||||||
|
// designated init()'s.
|
||||||
|
class NonDesignatedNonDecodableSuper {
|
||||||
|
convenience init() { // expected-note {{cannot automatically synthesize 'init(from:)' because implementation would need to call 'init()', which is not designated}}
|
||||||
|
self.init(42)
|
||||||
|
}
|
||||||
|
|
||||||
|
init(_: Int) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
class NonDesignatedNonDecodableSub : NonDesignatedNonDecodableSuper, Decodable { // expected-error {{type 'NonDesignatedNonDecodableSub' does not conform to protocol 'Decodable'}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Non-Decodable superclasses of synthesized Decodable classes must have an
|
||||||
|
// accessible init().
|
||||||
|
class InaccessibleNonDecodableSuper {
|
||||||
|
private init() {} // expected-note {{cannot automatically synthesize 'init(from:)' because implementation would need to call 'init()', which is inaccessible due to 'private' protection level}}
|
||||||
|
}
|
||||||
|
|
||||||
|
class InaccessibleNonDecodableSub : InaccessibleNonDecodableSuper, Decodable { // expected-error {{type 'InaccessibleNonDecodableSub' does not conform to protocol 'Decodable'}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Non-Decodable superclasses of synthesized Decodable classes must have a
|
||||||
|
// non-failable init().
|
||||||
|
class FailableNonDecodableSuper {
|
||||||
|
init?() {} // expected-note {{cannot automatically synthesize 'init(from:)' because implementation would need to call 'init()', which is failable}}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FailableNonDecodableSub : FailableNonDecodableSuper, Decodable { // expected-error {{type 'FailableNonDecodableSub' does not conform to protocol 'Decodable'}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subclasses of classes whose Decodable synthesis fails should not inherit
|
||||||
|
// conformance.
|
||||||
|
class FailedSynthesisDecodableSuper : Decodable { // expected-error {{type 'FailedSynthesisDecodableSuper' does not conform to protocol 'Decodable'}}
|
||||||
|
enum CodingKeys : String, CodingKey {
|
||||||
|
case nonexistent // expected-note {{CodingKey case 'nonexistent' does not match any stored properties}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FailedSynthesisDecodableSub : FailedSynthesisDecodableSuper { // expected-note {{did you mean 'init'?}}
|
||||||
|
func foo() {
|
||||||
|
// Decodable should fail to synthesis or be inherited.
|
||||||
|
let _ = FailedSynthesisDecodableSub.init(from:) // expected-error {{type 'FailedSynthesisDecodableSub' has no member 'init(from:)'}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subclasses of Decodable classes which can't inherit their initializers should
|
||||||
|
// produce diagnostics.
|
||||||
|
class DecodableSuper : Decodable {
|
||||||
|
var value = 5
|
||||||
|
}
|
||||||
|
|
||||||
|
class DecodableSubWithoutInitialValue : DecodableSuper { // expected-error {{class 'DecodableSubWithoutInitialValue' has no initializers}}
|
||||||
|
// expected-note@-1 {{did you mean to override 'init(from:)'?}}
|
||||||
|
var value2: Int // expected-note {{stored property 'value2' without initial value prevents synthesized initializers}}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DecodableSubWithInitialValue : DecodableSuper {
|
||||||
|
var value2 = 10
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subclasses of Codable classes which can't inherit their initializers should
|
||||||
|
// produce diagnostics.
|
||||||
|
class CodableSuper : Codable {
|
||||||
|
var value = 5
|
||||||
|
}
|
||||||
|
|
||||||
|
class CodableSubWithoutInitialValue : CodableSuper { // expected-error {{class 'CodableSubWithoutInitialValue' has no initializers}}
|
||||||
|
// expected-note@-1 {{did you mean to override 'init(from:)' and 'encode(to:)'?}}
|
||||||
|
var value2: Int // expected-note {{stored property 'value2' without initial value prevents synthesized initializers}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We should only mention encode(to:) in the diagnostic if the subclass does not
|
||||||
|
// override it.
|
||||||
|
class EncodableSubWithoutInitialValue : CodableSuper { // expected-error {{class 'EncodableSubWithoutInitialValue' has no initializers}}
|
||||||
|
// expected-note@-1 {{did you mean to override 'init(from:)'?}}
|
||||||
|
var value2: Int // expected-note {{stored property 'value2' without initial value prevents synthesized initializers}}
|
||||||
|
|
||||||
|
override func encode(to: Encoder) throws {}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CodableSubWithInitialValue : CodableSuper {
|
||||||
|
var value2 = 10
|
||||||
|
}
|
||||||
@@ -6,14 +6,6 @@ struct S1 : Codable {
|
|||||||
var a: String = ""
|
var a: String = ""
|
||||||
var b: Int = 0
|
var b: Int = 0
|
||||||
var c: Nested = Nested()
|
var c: Nested = Nested()
|
||||||
|
|
||||||
// CHECK: error: type 'S1' does not conform to protocol 'Decodable'
|
|
||||||
// CHECK: note: cannot automatically synthesize 'Decodable' because 'S1.Nested' does not conform to 'Decodable'
|
|
||||||
// CHECK: note: protocol requires initializer 'init(from:)' with type 'Decodable'
|
|
||||||
|
|
||||||
// CHECK: error: type 'S1' does not conform to protocol 'Encodable'
|
|
||||||
// CHECK: note: cannot automatically synthesize 'Encodable' because 'S1.Nested' does not conform to 'Encodable'
|
|
||||||
// CHECK: note: protocol requires function 'encode(to:)' with type 'Encodable'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Codable struct with non-enum CodingKeys.
|
// Codable struct with non-enum CodingKeys.
|
||||||
@@ -28,14 +20,6 @@ struct S2 : Codable {
|
|||||||
init?(stringValue: String) {}
|
init?(stringValue: String) {}
|
||||||
init?(intValue: Int) {}
|
init?(intValue: Int) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
// CHECK: error: type 'S2' does not conform to protocol 'Decodable'
|
|
||||||
// CHECK: note: cannot automatically synthesize 'Decodable' because 'CodingKeys' is not an enum
|
|
||||||
// CHECK: note: protocol requires initializer 'init(from:)' with type 'Decodable'
|
|
||||||
|
|
||||||
// CHECK: error: type 'S2' does not conform to protocol 'Encodable'
|
|
||||||
// CHECK: note: cannot automatically synthesize 'Encodable' because 'CodingKeys' is not an enum
|
|
||||||
// CHECK: note: protocol requires function 'encode(to:)' with type 'Encodable'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Codable struct with CodingKeys not conforming to CodingKey.
|
// Codable struct with CodingKeys not conforming to CodingKey.
|
||||||
@@ -49,14 +33,6 @@ struct S3 : Codable {
|
|||||||
case b
|
case b
|
||||||
case c
|
case c
|
||||||
}
|
}
|
||||||
|
|
||||||
// CHECK: error: type 'S3' does not conform to protocol 'Decodable'
|
|
||||||
// CHECK: note: cannot automatically synthesize 'Decodable' because 'CodingKeys' does not conform to CodingKey
|
|
||||||
// CHECK: note: protocol requires initializer 'init(from:)' with type 'Decodable'
|
|
||||||
|
|
||||||
// CHECK: error: type 'S3' does not conform to protocol 'Encodable'
|
|
||||||
// CHECK: note: cannot automatically synthesize 'Encodable' because 'CodingKeys' does not conform to CodingKey
|
|
||||||
// CHECK: note: protocol requires function 'encode(to:)' with type 'Encodable'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Codable struct with extraneous CodingKeys
|
// Codable struct with extraneous CodingKeys
|
||||||
@@ -73,18 +49,6 @@ struct S4 : Codable {
|
|||||||
case c
|
case c
|
||||||
case c2
|
case c2
|
||||||
}
|
}
|
||||||
|
|
||||||
// CHECK: error: type 'S4' does not conform to protocol 'Decodable'
|
|
||||||
// CHECK: note: CodingKey case 'a2' does not match any stored properties
|
|
||||||
// CHECK: note: CodingKey case 'b2' does not match any stored properties
|
|
||||||
// CHECK: note: CodingKey case 'c2' does not match any stored properties
|
|
||||||
// CHECK: note: protocol requires initializer 'init(from:)' with type 'Decodable'
|
|
||||||
|
|
||||||
// CHECK: error: type 'S4' does not conform to protocol 'Encodable'
|
|
||||||
// CHECK: note: CodingKey case 'a2' does not match any stored properties
|
|
||||||
// CHECK: note: CodingKey case 'b2' does not match any stored properties
|
|
||||||
// CHECK: note: CodingKey case 'c2' does not match any stored properties
|
|
||||||
// CHECK: note: protocol requires function 'encode(to:)' with type 'Encodable'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Codable struct with non-decoded property (which has no default value).
|
// Codable struct with non-decoded property (which has no default value).
|
||||||
@@ -97,14 +61,60 @@ struct S5 : Codable {
|
|||||||
case a
|
case a
|
||||||
case c
|
case c
|
||||||
}
|
}
|
||||||
|
|
||||||
// CHECK: error: type 'S5' does not conform to protocol 'Decodable'
|
|
||||||
// CHECK: note: cannot automatically synthesize 'Decodable' because 'b' does not have a matching CodingKey and does not have a default value
|
|
||||||
// CHECK: note: protocol requires initializer 'init(from:)' with type 'Decodable'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Structs cannot yet synthesize Encodable or Decodable in extensions.
|
// Structs cannot yet synthesize Encodable or Decodable in extensions.
|
||||||
struct S6 {}
|
struct S6 {}
|
||||||
extension S6 : Codable {}
|
extension S6 : Codable {}
|
||||||
|
|
||||||
|
// Decodable diagnostics are output first here {
|
||||||
|
|
||||||
|
// CHECK: error: type 'S1' does not conform to protocol 'Decodable'
|
||||||
|
// CHECK: note: protocol requires initializer 'init(from:)' with type 'Decodable'
|
||||||
|
// CHECK: note: cannot automatically synthesize 'Decodable' because 'S1.Nested' does not conform to 'Decodable'
|
||||||
|
|
||||||
|
// CHECK: error: type 'S2' does not conform to protocol 'Decodable'
|
||||||
|
// CHECK: note: protocol requires initializer 'init(from:)' with type 'Decodable'
|
||||||
|
// CHECK: note: cannot automatically synthesize 'Decodable' because 'CodingKeys' is not an enum
|
||||||
|
|
||||||
|
// CHECK: error: type 'S3' does not conform to protocol 'Decodable'
|
||||||
|
// CHECK: note: protocol requires initializer 'init(from:)' with type 'Decodable'
|
||||||
|
// CHECK: note: cannot automatically synthesize 'Decodable' because 'CodingKeys' does not conform to CodingKey
|
||||||
|
|
||||||
|
// CHECK: error: type 'S4' does not conform to protocol 'Decodable'
|
||||||
|
// CHECK: note: protocol requires initializer 'init(from:)' with type 'Decodable'
|
||||||
|
// CHECK: note: CodingKey case 'a2' does not match any stored properties
|
||||||
|
// CHECK: note: CodingKey case 'b2' does not match any stored properties
|
||||||
|
// CHECK: note: CodingKey case 'c2' does not match any stored properties
|
||||||
|
|
||||||
|
// CHECK: error: type 'S5' does not conform to protocol 'Decodable'
|
||||||
|
// CHECK: note: protocol requires initializer 'init(from:)' with type 'Decodable'
|
||||||
|
// CHECK: note: cannot automatically synthesize 'Decodable' because 'b' does not have a matching CodingKey and does not have a default value
|
||||||
|
|
||||||
// CHECK: error: implementation of 'Decodable' cannot be automatically synthesized in an extension yet
|
// CHECK: error: implementation of 'Decodable' cannot be automatically synthesized in an extension yet
|
||||||
|
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Encodable {
|
||||||
|
|
||||||
|
// CHECK: error: type 'S1' does not conform to protocol 'Encodable'
|
||||||
|
// CHECK: note: protocol requires function 'encode(to:)' with type 'Encodable'
|
||||||
|
// CHECK: note: cannot automatically synthesize 'Encodable' because 'S1.Nested' does not conform to 'Encodable'
|
||||||
|
|
||||||
|
// CHECK: error: type 'S2' does not conform to protocol 'Encodable'
|
||||||
|
// CHECK: note: protocol requires function 'encode(to:)' with type 'Encodable'
|
||||||
|
// CHECK: note: cannot automatically synthesize 'Encodable' because 'CodingKeys' is not an enum
|
||||||
|
|
||||||
|
// CHECK: error: type 'S3' does not conform to protocol 'Encodable'
|
||||||
|
// CHECK: note: protocol requires function 'encode(to:)' with type 'Encodable'
|
||||||
|
// CHECK: note: cannot automatically synthesize 'Encodable' because 'CodingKeys' does not conform to CodingKey
|
||||||
|
|
||||||
|
// CHECK: error: type 'S4' does not conform to protocol 'Encodable'
|
||||||
|
// CHECK: note: protocol requires function 'encode(to:)' with type 'Encodable'
|
||||||
|
// CHECK: note: CodingKey case 'a2' does not match any stored properties
|
||||||
|
// CHECK: note: CodingKey case 'b2' does not match any stored properties
|
||||||
|
// CHECK: note: CodingKey case 'c2' does not match any stored properties
|
||||||
|
|
||||||
// CHECK: error: implementation of 'Encodable' cannot be automatically synthesized in an extension yet
|
// CHECK: error: implementation of 'Encodable' cannot be automatically synthesized in an extension yet
|
||||||
|
|
||||||
|
// }
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
// RUN: %target-typecheck-verify-swift -verify-ignore-unknown
|
||||||
|
|
||||||
|
// Structs with no initializers should get a memberwise initializer.
|
||||||
|
struct NoInitializers {
|
||||||
|
var x: Double // need not have an initial value
|
||||||
|
|
||||||
|
func foo() {
|
||||||
|
// The struct should receive a memberwise initializer.
|
||||||
|
let _ = NoInitializers.init(x:)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A similar struct with Codable properties adopting Codable should get a
|
||||||
|
// synthesized init(from:), along with the memberwise initializer.
|
||||||
|
struct CodableNoExplicitInitializers : Codable {
|
||||||
|
var x: Double
|
||||||
|
|
||||||
|
func foo() {
|
||||||
|
// The struct should receive a synthesized init(from:) and encode(to:).
|
||||||
|
let _ = CodableNoExplicitInitializers.init(from:)
|
||||||
|
let _ = CodableNoExplicitInitializers.encode(to:)
|
||||||
|
|
||||||
|
// It should still receive a memberwise initializer.
|
||||||
|
let _ = CodableNoExplicitInitializers.init(x:)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The same should hold for structs whose members all have initial values.
|
||||||
|
struct InitialValueNoInitializers {
|
||||||
|
var x: Double = .pi
|
||||||
|
|
||||||
|
func foo() {
|
||||||
|
// The struct should receive a memberwise initializer.
|
||||||
|
let _ = InitialValueNoInitializers.init(x:)
|
||||||
|
|
||||||
|
// The struct should receive a no-argument initializer.
|
||||||
|
let _ = InitialValueNoInitializers.init()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct InitialValueCodableNoExplicitInitializers : Codable {
|
||||||
|
var x: Double = .pi
|
||||||
|
|
||||||
|
func foo() {
|
||||||
|
// The struct should receive a synthesized init(from:) and encode(to:).
|
||||||
|
let _ = InitialValueCodableNoExplicitInitializers.init(from:)
|
||||||
|
let _ = InitialValueCodableNoExplicitInitializers.encode(to:)
|
||||||
|
|
||||||
|
// It should still receive a memberwise initializer.
|
||||||
|
let _ = InitialValueCodableNoExplicitInitializers.init(x:)
|
||||||
|
|
||||||
|
// It should still receive a no-argument initializer.
|
||||||
|
let _ = InitialValueCodableNoExplicitInitializers.init()
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user