Skip to content

Commit f2102fc

Browse files
committed
Fix name confusion in Decodable synthesis
Decodable’s init(from:) synthesis sometimes mistook a static property for an identically-named instance property, which could cause it to skip a property or possibly make other mistakes. This change factors a common helper function from encode(to:) and init(from:) synthesis which implements the right logic for both.
1 parent d6eb168 commit f2102fc

File tree

2 files changed

+81
-49
lines changed

2 files changed

+81
-49
lines changed

lib/Sema/DerivedConformanceCodable.cpp

Lines changed: 66 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,55 @@ static CallExpr *createContainerKeyedByCall(ASTContext &C, DeclContext *DC,
509509
C.AllocateCopy(argLabels));
510510
}
511511

512+
/// Looks up the property corresponding to the indicated coding key.
513+
///
514+
/// \param conformanceDC The DeclContext we're generating code within.
515+
/// \param elt The CodingKeys enum case.
516+
/// \param targetDecl The type to look up properties in.
517+
///
518+
/// \return A tuple containing the \c VarDecl for the property, the type that
519+
/// should be passed when decoding it, and a boolean which is true if
520+
/// \c encodeIfPresent/\c decodeIfPresent should be used for this property.
521+
static std::tuple<VarDecl *, Type, bool>
522+
lookupVarDeclForCodingKeysCase(DeclContext *conformanceDC,
523+
EnumElementDecl *elt,
524+
NominalTypeDecl *targetDecl) {
525+
ASTContext &C = elt->getASTContext();
526+
527+
for (auto decl : targetDecl->lookupDirect(DeclName(elt->getName()))) {
528+
if (auto *vd = dyn_cast<VarDecl>(decl)) {
529+
if (!vd->isStatic()) {
530+
// This is the VarDecl we're looking for.
531+
532+
auto varType = conformanceDC->mapTypeIntoContext(vd->getInterfaceType());
533+
534+
if (auto referenceType = varType->getAs<ReferenceStorageType>()) {
535+
// This is a weak/unowned/unmanaged var. Get the inner type before
536+
// checking optionality.
537+
varType = referenceType->getReferentType();
538+
}
539+
540+
bool useIfPresentVariant =
541+
varType->getAnyNominal() == C.getOptionalDecl();
542+
543+
if (useIfPresentVariant) {
544+
// The type we request out of decodeIfPresent needs to be unwrapped
545+
// one level.
546+
// e.g. String? => decodeIfPresent(String.self, forKey: ...), not
547+
// decodeIfPresent(String?.self, forKey: ...)
548+
auto boundOptionalType =
549+
dyn_cast<BoundGenericType>(varType->getCanonicalType());
550+
varType = boundOptionalType->getGenericArgs()[0];
551+
}
552+
553+
return std::make_tuple(vd, varType, useIfPresentVariant);
554+
}
555+
}
556+
}
557+
558+
llvm_unreachable("Should have found at least 1 var decl");
559+
}
560+
512561
/// Synthesizes the body for `func encode(to encoder: Encoder) throws`.
513562
///
514563
/// \param encodeDecl The function decl whose body to synthesize.
@@ -531,7 +580,8 @@ static void deriveBodyEncodable_encode(AbstractFunctionDecl *encodeDecl, void *)
531580
// }
532581

533582
// The enclosing type decl.
534-
auto *targetDecl = encodeDecl->getDeclContext()->getSelfNominalTypeDecl();
583+
auto conformanceDC = encodeDecl->getDeclContext();
584+
auto *targetDecl = conformanceDC->getSelfNominalTypeDecl();
535585

536586
auto *funcDC = cast<DeclContext>(encodeDecl);
537587
auto &C = funcDC->getASTContext();
@@ -590,16 +640,12 @@ static void deriveBodyEncodable_encode(AbstractFunctionDecl *encodeDecl, void *)
590640
// Now need to generate `try container.encode(x, forKey: .x)` for all
591641
// existing properties. Optional properties get `encodeIfPresent`.
592642
for (auto *elt : codingKeysEnum->getAllElements()) {
593-
VarDecl *varDecl = nullptr;
594-
for (auto decl : targetDecl->lookupDirect(DeclName(elt->getName()))) {
595-
if (auto *vd = dyn_cast<VarDecl>(decl)) {
596-
if (!vd->isStatic()) {
597-
varDecl = vd;
598-
break;
599-
}
600-
}
601-
}
602-
assert(varDecl && "Should have found at least 1 var decl");
643+
VarDecl *varDecl;
644+
Type varType; // not used in Encodable synthesis
645+
bool useIfPresentVariant;
646+
647+
std::tie(varDecl, varType, useIfPresentVariant) =
648+
lookupVarDeclForCodingKeysCase(conformanceDC, elt, targetDecl);
603649

604650
// self.x
605651
auto *selfRef = DerivedConformance::createSelfDeclRef(encodeDecl);
@@ -613,16 +659,7 @@ static void deriveBodyEncodable_encode(AbstractFunctionDecl *encodeDecl, void *)
613659
auto *keyExpr = new (C) DotSyntaxCallExpr(eltRef, SourceLoc(), metaTyRef);
614660

615661
// encode(_:forKey:)/encodeIfPresent(_:forKey:)
616-
auto methodName = C.Id_encode;
617-
auto varType = varDecl->getType();
618-
if (auto referenceType = varType->getAs<ReferenceStorageType>()) {
619-
// This is a weak/unowned/unmanaged var. Get the inner type before
620-
// checking optionality.
621-
varType = referenceType->getReferentType();
622-
}
623-
624-
if (varType->getAnyNominal() == C.getOptionalDecl())
625-
methodName = C.Id_encodeIfPresent;
662+
auto methodName = useIfPresentVariant ? C.Id_encodeIfPresent : C.Id_encode;
626663

627664
SmallVector<Identifier, 2> argNames{Identifier(), C.Id_forKey};
628665
DeclName name(C, methodName, argNames);
@@ -831,42 +868,22 @@ static void deriveBodyDecodable_init(AbstractFunctionDecl *initDecl, void *) {
831868
// for all existing properties. Optional properties get `decodeIfPresent`.
832869
for (auto *elt : enumElements) {
833870
VarDecl *varDecl;
834-
for (auto decl : targetDecl->lookupDirect(DeclName(elt->getName())))
835-
if ((varDecl = dyn_cast<VarDecl>(decl)))
836-
break;
871+
Type varType;
872+
bool useIfPresentVariant;
873+
874+
std::tie(varDecl, varType, useIfPresentVariant) =
875+
lookupVarDeclForCodingKeysCase(conformanceDC, elt, targetDecl);
837876

838877
// Don't output a decode statement for a var let with a default value.
839878
if (varDecl->isLet() && varDecl->getParentInitializer() != nullptr)
840879
continue;
841880

842-
// Potentially unwrap a layer of optionality from the var type. If the var
843-
// is Optional<T>, we want to decodeIfPresent(T.self, forKey: ...);
844-
// otherwise, we can just decode(T.self, forKey: ...).
845-
// This is also true if the type is an ImplicitlyUnwrappedOptional.
846-
auto varType = conformanceDC->mapTypeIntoContext(
847-
varDecl->getInterfaceType());
848-
auto methodName = C.Id_decode;
849-
if (auto referenceType = varType->getAs<ReferenceStorageType>()) {
850-
// This is a weak/unowned/unmanaged var. Get the inner type before
851-
// checking optionality.
852-
varType = referenceType->getReferentType();
853-
}
854-
855-
if (varType->getAnyNominal() == C.getOptionalDecl()) {
856-
methodName = C.Id_decodeIfPresent;
857-
858-
// The type we request out of decodeIfPresent needs to be unwrapped
859-
// one level.
860-
// e.g. String? => decodeIfPresent(String.self, forKey: ...), not
861-
// decodeIfPresent(String?.self, forKey: ...)
862-
auto boundOptionalType =
863-
dyn_cast<BoundGenericType>(varType->getCanonicalType());
864-
varType = boundOptionalType->getGenericArgs()[0];
865-
}
881+
auto methodName =
882+
useIfPresentVariant ? C.Id_decodeIfPresent : C.Id_decode;
866883

867884
// Type.self (where Type === type(of: x))
868885
// Calculating the metatype needs to happen after potential Optional
869-
// unwrapping above.
886+
// unwrapping in lookupVarDeclForCodingKeysCase().
870887
auto *metaTyRef = TypeExpr::createImplicit(varType, C);
871888
auto *targetExpr = new (C) DotSelfExpr(metaTyRef, SourceLoc(),
872889
SourceLoc(), varType);
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// RUN: %target-typecheck-verify-swift
2+
3+
// Tests that, when synthesizing init(from:), we don't accidentally confuse
4+
// static and instance properties with the same name. (SR-10045)
5+
// The test fails if this file produces errors.
6+
7+
struct X: Codable {
8+
// The static property is a let with an initial value; Codable synthesis skips
9+
// instance properties that look like this.
10+
static let a: String = "a"
11+
12+
// The instance property has no initial value, so the definite initialization
13+
// checker will reject an init(from:) that doesn't decode a value for it.
14+
let a: String
15+
}

0 commit comments

Comments
 (0)