mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
* Allow CodingKey conformance to be automatically derived for enums which have no raw type (with no associated values) and which have a raw type of String or Int. * Allow Encodable and Decodable conformance to be automatically derived for classes and structs with Encodable/Decodable properties * Add initial unit tests for verifying derived conformance
227 lines
9.0 KiB
C++
227 lines
9.0 KiB
C++
//===--- DerivedConformances.cpp - Derived conformance utilities ----------===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
|
|
// Licensed under Apache License v2.0 with Runtime Library Exception
|
|
//
|
|
// See https://swift.org/LICENSE.txt for license information
|
|
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "TypeChecker.h"
|
|
#include "swift/AST/Decl.h"
|
|
#include "swift/AST/Stmt.h"
|
|
#include "swift/AST/Expr.h"
|
|
#include "swift/AST/Pattern.h"
|
|
#include "swift/AST/ParameterList.h"
|
|
#include "swift/AST/Types.h"
|
|
#include "swift/ClangImporter/ClangModule.h"
|
|
#include "DerivedConformances.h"
|
|
|
|
using namespace swift;
|
|
using namespace DerivedConformance;
|
|
|
|
ValueDecl *DerivedConformance::getDerivableRequirement(NominalTypeDecl *nominal,
|
|
ValueDecl *requirement) {
|
|
// Note: whenever you update this function, also update
|
|
// TypeChecker::deriveProtocolRequirement.
|
|
ASTContext &ctx = nominal->getASTContext();
|
|
auto name = requirement->getFullName();
|
|
|
|
// Local function that retrieves the requirement with the same name as
|
|
// the provided requirement, but within the given known protocol.
|
|
auto getRequirement = [&](KnownProtocolKind kind) -> ValueDecl * {
|
|
// Dig out the protocol.
|
|
auto proto = ctx.getProtocol(kind);
|
|
if (!proto) return nullptr;
|
|
|
|
// Check whether this nominal type derives conformances to the
|
|
if (!nominal->derivesProtocolConformance(proto)) return nullptr;
|
|
|
|
// Retrieve the requirement.
|
|
auto results = proto->lookupDirect(name);
|
|
return results.empty() ? nullptr : results.front();
|
|
};
|
|
|
|
// Properties.
|
|
if (isa<VarDecl>(requirement)) {
|
|
// RawRepresentable.rawValue
|
|
if (name.isSimpleName(ctx.Id_rawValue))
|
|
return getRequirement(KnownProtocolKind::RawRepresentable);
|
|
|
|
// Hashable.hashValue
|
|
if (name.isSimpleName(ctx.Id_hashValue))
|
|
return getRequirement(KnownProtocolKind::Hashable);
|
|
|
|
// _BridgedNSError._nsErrorDomain
|
|
if (name.isSimpleName(ctx.Id_nsErrorDomain))
|
|
return getRequirement(KnownProtocolKind::BridgedNSError);
|
|
|
|
// CodingKey.stringValue
|
|
if (name.isSimpleName(ctx.Id_stringValue))
|
|
return getRequirement(KnownProtocolKind::CodingKey);
|
|
|
|
// CodingKey.intValue
|
|
if (name.isSimpleName(ctx.Id_intValue))
|
|
return getRequirement(KnownProtocolKind::CodingKey);
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
// Functions.
|
|
if (auto func = dyn_cast<FuncDecl>(requirement)) {
|
|
if (func->isOperator() && name.getBaseName().str() == "==")
|
|
return getRequirement(KnownProtocolKind::Equatable);
|
|
|
|
// Encodable.encode(to: Encoder)
|
|
if (name.isCompoundName() && name.getBaseName() == ctx.Id_encode) {
|
|
auto argumentNames = name.getArgumentNames();
|
|
if (argumentNames.size() == 1 && argumentNames[0] == ctx.Id_to)
|
|
return getRequirement(KnownProtocolKind::Encodable);
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
// Initializers.
|
|
if (auto ctor = dyn_cast<ConstructorDecl>(requirement)) {
|
|
auto argumentNames = name.getArgumentNames();
|
|
if (argumentNames.size() == 1) {
|
|
if (argumentNames[0] == ctx.Id_rawValue)
|
|
return getRequirement(KnownProtocolKind::RawRepresentable);
|
|
|
|
// CodingKey.init?(stringValue:), CodingKey.init?(intValue:)
|
|
if (ctor->getFailability() == OTK_Optional &&
|
|
(argumentNames[0] == ctx.Id_stringValue ||
|
|
argumentNames[0] == ctx.Id_intValue))
|
|
return getRequirement(KnownProtocolKind::CodingKey);
|
|
|
|
// Decodable.init(from: Decoder)
|
|
if (argumentNames[0] == ctx.Id_from)
|
|
return getRequirement(KnownProtocolKind::Decodable);
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
// Associated types.
|
|
if (isa<AssociatedTypeDecl>(requirement)) {
|
|
// RawRepresentable.RawValue
|
|
if (name.isSimpleName(ctx.Id_RawValue))
|
|
return getRequirement(KnownProtocolKind::RawRepresentable);
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
DeclRefExpr *
|
|
DerivedConformance::createSelfDeclRef(AbstractFunctionDecl *fn) {
|
|
ASTContext &C = fn->getASTContext();
|
|
|
|
auto selfDecl = fn->getImplicitSelfDecl();
|
|
return new (C) DeclRefExpr(selfDecl, DeclNameLoc(), /*implicit*/true);
|
|
}
|
|
|
|
FuncDecl *DerivedConformance::declareDerivedPropertyGetter(TypeChecker &tc,
|
|
Decl *parentDecl,
|
|
NominalTypeDecl *typeDecl,
|
|
Type propertyInterfaceType,
|
|
Type propertyContextType,
|
|
bool isStatic,
|
|
bool isFinal) {
|
|
auto &C = tc.Context;
|
|
auto parentDC = cast<DeclContext>(parentDecl);
|
|
auto selfDecl = ParamDecl::createSelf(SourceLoc(), parentDC, isStatic);
|
|
ParameterList *params[] = {
|
|
ParameterList::createWithoutLoc(selfDecl),
|
|
ParameterList::createEmpty(C)
|
|
};
|
|
|
|
FuncDecl *getterDecl =
|
|
FuncDecl::create(C, /*StaticLoc=*/SourceLoc(), StaticSpellingKind::None,
|
|
/*FuncLoc=*/SourceLoc(), DeclName(), /*NameLoc=*/SourceLoc(),
|
|
/*Throws=*/false, /*ThrowsLoc=*/SourceLoc(),
|
|
/*AccessorKeywordLoc=*/SourceLoc(),
|
|
nullptr, params,
|
|
TypeLoc::withoutLoc(propertyInterfaceType), parentDC);
|
|
getterDecl->setImplicit();
|
|
getterDecl->setStatic(isStatic);
|
|
|
|
// If this is supposed to be a final method, mark it as such.
|
|
assert(isFinal || !parentDC->getAsClassOrClassExtensionContext());
|
|
if (isFinal && parentDC->getAsClassOrClassExtensionContext() &&
|
|
!getterDecl->isFinal())
|
|
getterDecl->getAttrs().add(new (C) FinalAttr(/*IsImplicit=*/true));
|
|
|
|
// Compute the interface type of the getter.
|
|
Type interfaceType = FunctionType::get(TupleType::getEmpty(C),
|
|
propertyInterfaceType);
|
|
Type selfInterfaceType = getterDecl->computeInterfaceSelfType();
|
|
if (auto sig = parentDC->getGenericSignatureOfContext()) {
|
|
getterDecl->setGenericEnvironment(
|
|
parentDC->getGenericEnvironmentOfContext());
|
|
interfaceType = GenericFunctionType::get(sig, selfInterfaceType,
|
|
interfaceType,
|
|
FunctionType::ExtInfo());
|
|
} else
|
|
interfaceType = FunctionType::get(selfInterfaceType, interfaceType);
|
|
getterDecl->setInterfaceType(interfaceType);
|
|
getterDecl->setAccessibility(std::max(typeDecl->getFormalAccess(),
|
|
Accessibility::Internal));
|
|
|
|
// If the enum was not imported, the derived conformance is either from the
|
|
// enum itself or an extension, in which case we will emit the declaration
|
|
// normally.
|
|
if (isa<ClangModuleUnit>(parentDC->getModuleScopeContext()))
|
|
tc.Context.addExternalDecl(getterDecl);
|
|
|
|
return getterDecl;
|
|
}
|
|
|
|
std::pair<VarDecl *, PatternBindingDecl *>
|
|
DerivedConformance::declareDerivedReadOnlyProperty(TypeChecker &tc,
|
|
Decl *parentDecl,
|
|
NominalTypeDecl *typeDecl,
|
|
Identifier name,
|
|
Type propertyInterfaceType,
|
|
Type propertyContextType,
|
|
FuncDecl *getterDecl,
|
|
bool isStatic,
|
|
bool isFinal) {
|
|
auto &C = tc.Context;
|
|
auto parentDC = cast<DeclContext>(parentDecl);
|
|
|
|
VarDecl *propDecl = new (C) VarDecl(/*IsStatic*/isStatic, /*IsLet*/false,
|
|
/*IsCaptureList*/false, SourceLoc(), name,
|
|
propertyContextType, parentDC);
|
|
propDecl->setImplicit();
|
|
propDecl->makeComputed(SourceLoc(), getterDecl, nullptr, nullptr,
|
|
SourceLoc());
|
|
propDecl->setAccessibility(getterDecl->getFormalAccess());
|
|
propDecl->setInterfaceType(propertyInterfaceType);
|
|
|
|
// If this is supposed to be a final property, mark it as such.
|
|
assert(isFinal || !parentDC->getAsClassOrClassExtensionContext());
|
|
if (isFinal && parentDC->getAsClassOrClassExtensionContext() &&
|
|
!propDecl->isFinal())
|
|
propDecl->getAttrs().add(new (C) FinalAttr(/*IsImplicit=*/true));
|
|
|
|
Pattern *propPat = new (C) NamedPattern(propDecl, /*implicit*/ true);
|
|
propPat->setType(propertyContextType);
|
|
propPat = new (C) TypedPattern(propPat,
|
|
TypeLoc::withoutLoc(propertyContextType),
|
|
/*implicit*/ true);
|
|
|
|
auto pbDecl = PatternBindingDecl::create(C, SourceLoc(),
|
|
StaticSpellingKind::None,
|
|
SourceLoc(), propPat, nullptr,
|
|
parentDC);
|
|
pbDecl->setImplicit();
|
|
|
|
return {propDecl, pbDecl};
|
|
}
|