Skip to content

Commit fa8f030

Browse files
committed
Split CodingKeys Synthesis From Validation
1 parent 6df3fcf commit fa8f030

File tree

1 file changed

+119
-175
lines changed

1 file changed

+119
-175
lines changed

lib/Sema/DerivedConformanceCodable.cpp

Lines changed: 119 additions & 175 deletions
Original file line numberDiff line numberDiff line change
@@ -53,13 +53,118 @@ static Identifier getVarNameForCoding(VarDecl *var) {
5353
return var->getName();
5454
}
5555

56+
// Create CodingKeys in the parent type always, because both
57+
// Encodable and Decodable might want to use it, and they may have
58+
// different conditional bounds. CodingKeys is simple and can't
59+
// depend on those bounds.
60+
//
61+
// FIXME: Eventually we should find a way to expose this function to the lookup
62+
// machinery so it no longer costs two protocol conformance lookups to retrieve
63+
// CodingKeys. It will also help in our quest to separate semantic and parsed
64+
// members.
65+
static EnumDecl *addImplicitCodingKeys(NominalTypeDecl *target) {
66+
auto &C = target->getASTContext();
67+
assert(target->lookupDirect(DeclName(C.Id_CodingKeys)).empty());
68+
69+
// We want to look through all the var declarations of this type to create
70+
// enum cases based on those var names.
71+
auto *codingKeyProto = C.getProtocol(KnownProtocolKind::CodingKey);
72+
auto codingKeyType = codingKeyProto->getDeclaredInterfaceType();
73+
TypeLoc protoTypeLoc[1] = {TypeLoc::withoutLoc(codingKeyType)};
74+
ArrayRef<TypeLoc> inherited = C.AllocateCopy(protoTypeLoc);
75+
76+
auto *enumDecl = new (C) EnumDecl(SourceLoc(), C.Id_CodingKeys, SourceLoc(),
77+
inherited, nullptr, target);
78+
enumDecl->setImplicit();
79+
enumDecl->setSynthesized();
80+
enumDecl->setAccess(AccessLevel::Private);
81+
82+
// For classes which inherit from something Encodable or Decodable, we
83+
// provide case `super` as the first key (to be used in encoding super).
84+
auto *classDecl = dyn_cast<ClassDecl>(target);
85+
if (superclassConformsTo(classDecl, KnownProtocolKind::Encodable) ||
86+
superclassConformsTo(classDecl, KnownProtocolKind::Decodable)) {
87+
// TODO: Ensure the class doesn't already have or inherit a variable named
88+
// "`super`"; otherwise we will generate an invalid enum. In that case,
89+
// diagnose and bail.
90+
auto *super = new (C) EnumElementDecl(SourceLoc(), C.Id_super, nullptr,
91+
SourceLoc(), nullptr, enumDecl);
92+
super->setImplicit();
93+
enumDecl->addMember(super);
94+
}
95+
96+
for (auto *varDecl : target->getStoredProperties()) {
97+
if (!varDecl->isUserAccessible()) {
98+
continue;
99+
}
100+
101+
auto *elt =
102+
new (C) EnumElementDecl(SourceLoc(), getVarNameForCoding(varDecl),
103+
nullptr, SourceLoc(), nullptr, enumDecl);
104+
elt->setImplicit();
105+
enumDecl->addMember(elt);
106+
}
107+
108+
// Forcibly derive conformance to CodingKey.
109+
TypeChecker::checkConformancesInContext(enumDecl);
110+
111+
// Add to the type.
112+
target->addMember(enumDecl);
113+
114+
return enumDecl;
115+
}
116+
56117
/// Validates the given CodingKeys enum decl by ensuring its cases are a 1-to-1
57118
/// match with the stored vars of the given type.
58-
///
59-
/// \param codingKeysDecl The \c CodingKeys enum decl to validate.
60-
static bool validateCodingKeysEnum(const DerivedConformance &derived,
61-
EnumDecl *codingKeysDecl) {
62-
auto conformanceDC = derived.getConformanceContext();
119+
static bool validateCodingKeysEnum(DerivedConformance &derived) {
120+
auto &C = derived.Context;
121+
auto codingKeysDecls =
122+
derived.Nominal->lookupDirect(DeclName(C.Id_CodingKeys));
123+
124+
if (codingKeysDecls.size() > 1) {
125+
return false;
126+
}
127+
128+
ValueDecl *result = codingKeysDecls.empty()
129+
? addImplicitCodingKeys(derived.Nominal)
130+
: codingKeysDecls.front();
131+
auto *codingKeysTypeDecl = dyn_cast<TypeDecl>(result);
132+
if (!codingKeysTypeDecl) {
133+
result->diagnose(diag::codable_codingkeys_type_is_not_an_enum_here,
134+
derived.getProtocolType());
135+
return false;
136+
}
137+
138+
// CodingKeys may be a typealias. If so, follow the alias to its canonical
139+
// type.
140+
auto codingKeysType = codingKeysTypeDecl->getDeclaredInterfaceType();
141+
if (isa<TypeAliasDecl>(codingKeysTypeDecl))
142+
codingKeysTypeDecl = codingKeysType->getAnyNominal();
143+
144+
// Ensure that the type we found conforms to the CodingKey protocol.
145+
auto *codingKeyProto = C.getProtocol(KnownProtocolKind::CodingKey);
146+
if (!TypeChecker::conformsToProtocol(codingKeysType, codingKeyProto,
147+
derived.getConformanceContext())) {
148+
// If CodingKeys is a typealias which doesn't point to a valid nominal type,
149+
// codingKeysTypeDecl will be nullptr here. In that case, we need to warn on
150+
// the location of the usage, since there isn't an underlying type to
151+
// diagnose on.
152+
SourceLoc loc = codingKeysTypeDecl ? codingKeysTypeDecl->getLoc()
153+
: cast<TypeDecl>(result)->getLoc();
154+
155+
C.Diags.diagnose(loc, diag::codable_codingkeys_type_does_not_conform_here,
156+
derived.getProtocolType());
157+
return false;
158+
}
159+
160+
auto *codingKeysDecl =
161+
dyn_cast_or_null<EnumDecl>(codingKeysType->getAnyNominal());
162+
if (!codingKeysDecl) {
163+
codingKeysTypeDecl->diagnose(
164+
diag::codable_codingkeys_type_is_not_an_enum_here,
165+
derived.getProtocolType());
166+
return false;
167+
}
63168

64169
// Look through all var decls in the given type.
65170
// * Filter out lazy/computed vars.
@@ -93,9 +198,10 @@ static bool validateCodingKeysEnum(const DerivedConformance &derived,
93198
}
94199

95200
// We have a property to map to. Ensure it's {En,De}codable.
96-
auto target =
97-
conformanceDC->mapTypeIntoContext(it->second->getValueInterfaceType());
98-
if (TypeChecker::conformsToProtocol(target, derived.Protocol, conformanceDC)
201+
auto target = derived.getConformanceContext()->mapTypeIntoContext(
202+
it->second->getValueInterfaceType());
203+
if (TypeChecker::conformsToProtocol(target, derived.Protocol,
204+
derived.getConformanceContext())
99205
.isInvalid()) {
100206
TypeLoc typeLoc = {
101207
it->second->getTypeReprOrParentPatternTypeRepr(),
@@ -138,164 +244,6 @@ static bool validateCodingKeysEnum(const DerivedConformance &derived,
138244
return propertiesAreValid;
139245
}
140246

141-
/// A type which has information about the validity of an encountered
142-
/// \c CodingKeys type.
143-
enum class CodingKeysClassification {
144-
/// A \c CodingKeys declaration was found, but it is invalid.
145-
Invalid,
146-
/// No \c CodingKeys declaration was found, so it must be synthesized.
147-
NeedsSynthesizedCodingKeys,
148-
/// A valid \c CodingKeys declaration was found.
149-
Valid,
150-
};
151-
152-
/// Returns whether the given type has a valid nested \c CodingKeys enum.
153-
///
154-
/// If the type has an invalid \c CodingKeys entity, produces diagnostics to
155-
/// complain about the error. In this case, the error result will be true -- in
156-
/// the case where we don't have a valid CodingKeys enum and have produced
157-
/// diagnostics here, we don't want to then attempt to synthesize a CodingKeys
158-
/// enum.
159-
///
160-
/// \returns A \c CodingKeysValidity value representing the result of the check.
161-
static CodingKeysClassification
162-
classifyCodingKeys(const DerivedConformance &derived) {
163-
auto &C = derived.Context;
164-
auto codingKeysDecls =
165-
derived.Nominal->lookupDirect(DeclName(C.Id_CodingKeys));
166-
if (codingKeysDecls.empty()) {
167-
return CodingKeysClassification::NeedsSynthesizedCodingKeys;
168-
}
169-
170-
// Only ill-formed code would produce multiple results for this lookup.
171-
// This would get diagnosed later anyway, so we're free to only look at the
172-
// first result here.
173-
auto result = codingKeysDecls.front();
174-
175-
auto *codingKeysTypeDecl = dyn_cast<TypeDecl>(result);
176-
if (!codingKeysTypeDecl) {
177-
result->diagnose(diag::codable_codingkeys_type_is_not_an_enum_here,
178-
derived.getProtocolType());
179-
return CodingKeysClassification::Invalid;
180-
}
181-
182-
// CodingKeys may be a typealias. If so, follow the alias to its canonical
183-
// type.
184-
auto codingKeysType = codingKeysTypeDecl->getDeclaredInterfaceType();
185-
if (isa<TypeAliasDecl>(codingKeysTypeDecl))
186-
codingKeysTypeDecl = codingKeysType->getAnyNominal();
187-
188-
// Ensure that the type we found conforms to the CodingKey protocol.
189-
auto *codingKeyProto = C.getProtocol(KnownProtocolKind::CodingKey);
190-
if (!TypeChecker::conformsToProtocol(codingKeysType, codingKeyProto,
191-
derived.getConformanceContext())) {
192-
// If CodingKeys is a typealias which doesn't point to a valid nominal type,
193-
// codingKeysTypeDecl will be nullptr here. In that case, we need to warn on
194-
// the location of the usage, since there isn't an underlying type to
195-
// diagnose on.
196-
SourceLoc loc = codingKeysTypeDecl ?
197-
codingKeysTypeDecl->getLoc() :
198-
cast<TypeDecl>(result)->getLoc();
199-
200-
C.Diags.diagnose(loc, diag::codable_codingkeys_type_does_not_conform_here,
201-
derived.getProtocolType());
202-
203-
return CodingKeysClassification::Invalid;
204-
}
205-
206-
// CodingKeys must be an enum for synthesized conformance.
207-
auto *codingKeysEnum = dyn_cast<EnumDecl>(codingKeysTypeDecl);
208-
if (!codingKeysEnum) {
209-
codingKeysTypeDecl->diagnose(
210-
diag::codable_codingkeys_type_is_not_an_enum_here,
211-
derived.getProtocolType());
212-
return CodingKeysClassification::Invalid;
213-
}
214-
215-
return validateCodingKeysEnum(derived, codingKeysEnum)
216-
? CodingKeysClassification::Valid
217-
: CodingKeysClassification::Invalid;
218-
}
219-
220-
/// Synthesizes a new \c CodingKeys enum based on the {En,De}codable members of
221-
/// the given type (\c nullptr if unable to synthesize).
222-
///
223-
/// If able to synthesize the enum, adds it directly to \c derived.Nominal.
224-
static EnumDecl *synthesizeCodingKeysEnum(DerivedConformance &derived) {
225-
auto &C = derived.Context;
226-
// Create CodingKeys in the parent type always, because both
227-
// Encodable and Decodable might want to use it, and they may have
228-
// different conditional bounds. CodingKeys is simple and can't
229-
// depend on those bounds.
230-
auto target = derived.Nominal;
231-
232-
// We want to look through all the var declarations of this type to create
233-
// enum cases based on those var names.
234-
auto *codingKeyProto = C.getProtocol(KnownProtocolKind::CodingKey);
235-
auto codingKeyType = codingKeyProto->getDeclaredInterfaceType();
236-
TypeLoc protoTypeLoc[1] = {TypeLoc::withoutLoc(codingKeyType)};
237-
ArrayRef<TypeLoc> inherited = C.AllocateCopy(protoTypeLoc);
238-
239-
auto *enumDecl = new (C) EnumDecl(SourceLoc(), C.Id_CodingKeys, SourceLoc(),
240-
inherited, nullptr, target);
241-
enumDecl->setImplicit();
242-
enumDecl->setSynthesized();
243-
enumDecl->setAccess(AccessLevel::Private);
244-
245-
// For classes which inherit from something Encodable or Decodable, we
246-
// provide case `super` as the first key (to be used in encoding super).
247-
auto *classDecl = dyn_cast<ClassDecl>(target);
248-
if (superclassConformsTo(classDecl, KnownProtocolKind::Encodable) ||
249-
superclassConformsTo(classDecl, KnownProtocolKind::Decodable)) {
250-
// TODO: Ensure the class doesn't already have or inherit a variable named
251-
// "`super`"; otherwise we will generate an invalid enum. In that case,
252-
// diagnose and bail.
253-
auto *super = new (C) EnumElementDecl(SourceLoc(), C.Id_super, nullptr,
254-
SourceLoc(), nullptr, enumDecl);
255-
super->setImplicit();
256-
enumDecl->addMember(super);
257-
}
258-
259-
// Each of these vars needs a case in the enum. For each var decl, if the type
260-
// conforms to {En,De}codable, add it to the enum.
261-
bool allConform = true;
262-
auto *conformanceDC = derived.getConformanceContext();
263-
for (auto *varDecl : target->getStoredProperties()) {
264-
if (!varDecl->isUserAccessible()) {
265-
continue;
266-
}
267-
268-
auto target =
269-
conformanceDC->mapTypeIntoContext(varDecl->getValueInterfaceType());
270-
if (TypeChecker::conformsToProtocol(target, derived.Protocol, conformanceDC)
271-
.isInvalid()) {
272-
TypeLoc typeLoc = {
273-
varDecl->getTypeReprOrParentPatternTypeRepr(),
274-
varDecl->getType(),
275-
};
276-
varDecl->diagnose(diag::codable_non_conforming_property_here,
277-
derived.getProtocolType(), typeLoc);
278-
allConform = false;
279-
} else {
280-
auto *elt =
281-
new (C) EnumElementDecl(SourceLoc(), getVarNameForCoding(varDecl),
282-
nullptr, SourceLoc(), nullptr, enumDecl);
283-
elt->setImplicit();
284-
enumDecl->addMember(elt);
285-
}
286-
}
287-
288-
if (!allConform)
289-
return nullptr;
290-
291-
// Forcibly derive conformance to CodingKey.
292-
TypeChecker::checkConformancesInContext(enumDecl);
293-
294-
// Add to the type.
295-
target->addMember(enumDecl);
296-
return enumDecl;
297-
}
298-
299247
/// Fetches the \c CodingKeys enum nested in \c target, potentially reaching
300248
/// through a typealias if the "CodingKeys" entity is a typealias.
301249
///
@@ -1060,17 +1008,13 @@ static bool canSynthesize(DerivedConformance &derived, ValueDecl *requirement) {
10601008
}
10611009
}
10621010

1063-
switch (classifyCodingKeys(derived)) {
1064-
case CodingKeysClassification::Invalid:
1011+
if (!validateCodingKeysEnum(derived)) {
10651012
return false;
1066-
case CodingKeysClassification::NeedsSynthesizedCodingKeys: {
1067-
auto *synthesizedEnum = synthesizeCodingKeysEnum(derived);
1068-
if (!synthesizedEnum)
1069-
return false;
10701013
}
1071-
LLVM_FALLTHROUGH;
1072-
case CodingKeysClassification::Valid:
1073-
return true;
1014+
1015+
return true;
1016+
}
1017+
10741018
static bool canDeriveCodable(NominalTypeDecl *NTD,
10751019
KnownProtocolKind Kind) {
10761020
assert(Kind == KnownProtocolKind::Encodable ||

0 commit comments

Comments
 (0)