blob: d414743f7a29ed8cfb673976d1ce8b98942e3d50 [file] [log] [blame]
//===--- DerivedConformanceRawRepresentable.cpp - Derived RawRepresentable ===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// This file implements implicit derivation of the RawRepresentable protocol
// for an enum.
//
//===----------------------------------------------------------------------===//
#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 "llvm/ADT/APInt.h"
#include "DerivedConformances.h"
using namespace swift;
static LiteralExpr *cloneRawLiteralExpr(ASTContext &C, LiteralExpr *expr) {
LiteralExpr *clone;
if (auto intLit = dyn_cast<IntegerLiteralExpr>(expr)) {
clone = new (C) IntegerLiteralExpr(intLit->getDigitsText(), expr->getLoc(),
/*implicit*/ true);
if (intLit->isNegative())
cast<IntegerLiteralExpr>(clone)->setNegative(expr->getLoc());
} else if (isa<NilLiteralExpr>(expr)) {
clone = new (C) NilLiteralExpr(expr->getLoc());
} else if (auto stringLit = dyn_cast<StringLiteralExpr>(expr)) {
clone = new (C) StringLiteralExpr(stringLit->getValue(), expr->getLoc());
} else if (auto floatLit = dyn_cast<FloatLiteralExpr>(expr)) {
clone = new (C) FloatLiteralExpr(floatLit->getDigitsText(), expr->getLoc(),
/*implicit*/ true);
if (floatLit->isNegative())
cast<FloatLiteralExpr>(clone)->setNegative(expr->getLoc());
} else {
llvm_unreachable("invalid raw literal expr");
}
clone->setImplicit();
return clone;
}
static Type deriveRawRepresentable_Raw(DerivedConformance &derived) {
// enum SomeEnum : SomeType {
// @derived
// typealias Raw = SomeType
// }
auto rawInterfaceType = cast<EnumDecl>(derived.Nominal)->getRawType();
return derived.getConformanceContext()->mapTypeIntoContext(rawInterfaceType);
}
static std::pair<BraceStmt *, bool>
deriveBodyRawRepresentable_raw(AbstractFunctionDecl *toRawDecl, void *) {
// enum SomeEnum : SomeType {
// case A = 111, B = 222
// @derived
// var raw: SomeType {
// switch self {
// case A:
// return 111
// case B:
// return 222
// }
// }
// }
auto parentDC = toRawDecl->getDeclContext();
ASTContext &C = parentDC->getASTContext();
auto enumDecl = parentDC->getSelfEnumDecl();
Type rawTy = enumDecl->getRawType();
assert(rawTy);
rawTy = toRawDecl->mapTypeIntoContext(rawTy);
if (enumDecl->isObjC()) {
// Special case: ObjC enums are represented by their raw value, so just use
// a bitcast.
// return unsafeBitCast(self, to: RawType.self)
DeclName name(C, C.getIdentifier("unsafeBitCast"), {Identifier(), C.Id_to});
auto functionRef = new (C) UnresolvedDeclRefExpr(name,
DeclRefKind::Ordinary,
DeclNameLoc());
auto selfRef = DerivedConformance::createSelfDeclRef(toRawDecl);
auto bareTypeExpr = TypeExpr::createImplicit(rawTy, C);
auto typeExpr = new (C) DotSelfExpr(bareTypeExpr, SourceLoc(), SourceLoc());
auto call = CallExpr::createImplicit(C, functionRef, {selfRef, typeExpr},
{Identifier(), C.Id_to});
auto returnStmt = new (C) ReturnStmt(SourceLoc(), call);
auto body = BraceStmt::create(C, SourceLoc(), ASTNode(returnStmt),
SourceLoc());
return { body, /*isTypeChecked=*/false };
}
Type enumType = parentDC->getDeclaredTypeInContext();
SmallVector<ASTNode, 4> cases;
for (auto elt : enumDecl->getAllElements()) {
auto pat = new (C) EnumElementPattern(TypeLoc::withoutLoc(enumType),
SourceLoc(), SourceLoc(),
Identifier(), elt, nullptr);
pat->setImplicit();
auto labelItem = CaseLabelItem(pat);
auto returnExpr = cloneRawLiteralExpr(C, elt->getRawValueExpr());
auto returnStmt = new (C) ReturnStmt(SourceLoc(), returnExpr);
auto body = BraceStmt::create(C, SourceLoc(),
ASTNode(returnStmt), SourceLoc());
cases.push_back(CaseStmt::create(C, SourceLoc(), labelItem, SourceLoc(),
SourceLoc(), body,
/*case body var decls*/ None));
}
auto selfRef = DerivedConformance::createSelfDeclRef(toRawDecl);
auto switchStmt = SwitchStmt::create(LabeledStmtInfo(), SourceLoc(), selfRef,
SourceLoc(), cases, SourceLoc(), C);
auto body = BraceStmt::create(C, SourceLoc(),
ASTNode(switchStmt),
SourceLoc());
return { body, /*isTypeChecked=*/false };
}
static void maybeMarkAsInlinable(DerivedConformance &derived,
AbstractFunctionDecl *afd) {
ASTContext &C = derived.TC.Context;
auto parentDC = derived.getConformanceContext();
if (!parentDC->getParentModule()->isResilient()) {
AccessScope access =
afd->getFormalAccessScope(nullptr,
/*treatUsableFromInlineAsPublic*/true);
if (auto *attr = afd->getAttrs().getAttribute<UsableFromInlineAttr>())
attr->setInvalid();
if (access.isPublic())
afd->getAttrs().add(new (C) InlinableAttr(/*implicit*/false));
}
}
static VarDecl *deriveRawRepresentable_raw(DerivedConformance &derived) {
ASTContext &C = derived.TC.Context;
auto enumDecl = cast<EnumDecl>(derived.Nominal);
auto parentDC = derived.getConformanceContext();
auto rawInterfaceType = enumDecl->getRawType();
auto rawType = parentDC->mapTypeIntoContext(rawInterfaceType);
// Define the property.
VarDecl *propDecl;
PatternBindingDecl *pbDecl;
std::tie(propDecl, pbDecl) = derived.declareDerivedProperty(
C.Id_rawValue, rawInterfaceType, rawType, /*isStatic=*/false,
/*isFinal=*/false);
// Define the getter.
auto getterDecl = DerivedConformance::addGetterToReadOnlyDerivedProperty(
propDecl, rawType);
getterDecl->setBodySynthesizer(&deriveBodyRawRepresentable_raw);
// If the containing module is not resilient, make sure clients can get at
// the raw value without function call overhead.
maybeMarkAsInlinable(derived, getterDecl);
derived.addMembersToConformanceContext({propDecl, pbDecl});
return propDecl;
}
/// Contains information needed to synthesize a runtime version check.
struct RuntimeVersionCheck {
PlatformKind Platform;
llvm::VersionTuple Version;
RuntimeVersionCheck(PlatformKind Platform, llvm::VersionTuple Version)
: Platform(Platform), Version(Version)
{ }
VersionRange getVersionRange() const {
return VersionRange::allGTE(Version);
}
/// Synthesizes a statement which returns nil if the runtime version check
/// fails, e.g. "guard #available(iOS 10, *) else { return nil }".
Stmt *createEarlyReturnStmt(ASTContext &C) const {
// platformSpec = "\(attr.platform) \(attr.introduced)"
auto platformSpec = new (C) PlatformVersionConstraintAvailabilitySpec(
Platform, SourceLoc(),
Version, SourceLoc()
);
// otherSpec = "*"
auto otherSpec = new (C) OtherPlatformAvailabilitySpec(SourceLoc());
// availableInfo = "#available(\(platformSpec), \(otherSpec))"
auto availableInfo = PoundAvailableInfo::create(
C, SourceLoc(), { platformSpec, otherSpec }, SourceLoc());
// This won't be filled in by TypeCheckAvailability because we have
// invalid SourceLocs in this area of the AST.
availableInfo->setAvailableRange(getVersionRange());
// earlyReturnBody = "{ return nil }"
auto earlyReturn = new (C) FailStmt(SourceLoc(), SourceLoc());
auto earlyReturnBody = BraceStmt::create(C, SourceLoc(),
ASTNode(earlyReturn),
SourceLoc(), /*implicit=*/true);
// guardStmt = "guard \(availableInfo) else \(earlyReturnBody)"
StmtConditionElement conds[1] = { availableInfo };
auto guardStmt = new (C) GuardStmt(SourceLoc(), C.AllocateCopy(conds),
earlyReturnBody, /*implicit=*/true);
return guardStmt;
}
};
/// Checks if the case will be available at runtime given the current target
/// platform. If it will never be available, returns false. If it will always
/// be available, returns true. If it will sometimes be available, adds
/// information about the runtime check needed to ensure it is available to
/// \c versionCheck and returns true.
static bool checkAvailability(const EnumElementDecl* elt, ASTContext &C,
Optional<RuntimeVersionCheck> &versionCheck) {
auto *attr = elt->getAttrs().getPotentiallyUnavailable(C);
// Is it always available?
if (!attr)
return true;
AvailableVersionComparison availability = attr->getVersionAvailability(C);
assert(availability != AvailableVersionComparison::Available &&
"DeclAttributes::getPotentiallyUnavailable() shouldn't "
"return an available attribute");
// Is it never available?
if (availability != AvailableVersionComparison::PotentiallyUnavailable)
return false;
// It's conditionally available; create a version constraint and return true.
assert(attr->getPlatformAgnosticAvailability() ==
PlatformAgnosticAvailabilityKind::None &&
"can only express #available(somePlatform version) checks");
versionCheck.emplace(attr->Platform, *attr->Introduced);
return true;
}
static std::pair<BraceStmt *, bool>
deriveBodyRawRepresentable_init(AbstractFunctionDecl *initDecl, void *) {
// enum SomeEnum : SomeType {
// case A = 111, B = 222
// @available(iOS 10, *) case C = 333
// @derived
// init?(rawValue: SomeType) {
// switch rawValue {
// case 111:
// self = .A
// case 222:
// self = .B
// case 333:
// guard #available(iOS 10, *) else { return nil }
// self = .C
// default:
// return nil
// }
// }
// }
auto parentDC = initDecl->getDeclContext();
ASTContext &C = parentDC->getASTContext();
auto nominalTypeDecl = parentDC->getSelfNominalTypeDecl();
auto enumDecl = cast<EnumDecl>(nominalTypeDecl);
Type rawTy = enumDecl->getRawType();
assert(rawTy);
rawTy = initDecl->mapTypeIntoContext(rawTy);
bool isStringEnum =
(rawTy->getNominalOrBoundGenericNominal() == C.getStringDecl());
llvm::SmallVector<Expr *, 16> stringExprs;
Type enumType = parentDC->getDeclaredTypeInContext();
auto selfDecl = cast<ConstructorDecl>(initDecl)->getImplicitSelfDecl();
SmallVector<ASTNode, 4> cases;
unsigned Idx = 0;
for (auto elt : enumDecl->getAllElements()) {
// First, check case availability. If the case will definitely be
// unavailable, skip it. If it might be unavailable at runtime, save
// information about that check in versionCheck and keep processing this
// element.
Optional<RuntimeVersionCheck> versionCheck(None);
if (!checkAvailability(elt, C, versionCheck))
continue;
// litPat = elt.rawValueExpr as a pattern
LiteralExpr *litExpr = cloneRawLiteralExpr(C, elt->getRawValueExpr());
if (isStringEnum) {
// In case of a string enum we are calling the _findStringSwitchCase
// function from the library and switching on the returned Int value.
stringExprs.push_back(litExpr);
litExpr = IntegerLiteralExpr::createFromUnsigned(C, Idx);
}
auto litPat = new (C) ExprPattern(litExpr, /*isResolved*/ true,
nullptr, nullptr);
litPat->setImplicit();
/// Statements in the body of this case.
SmallVector<ASTNode, 2> stmts;
// If checkAvailability() discovered we need a runtime version check,
// add it now.
if (versionCheck.hasValue())
stmts.push_back(ASTNode(versionCheck->createEarlyReturnStmt(C)));
// Create a statement which assigns the case to self.
// valueExpr = "\(enumType).\(elt)"
auto eltRef = new (C) DeclRefExpr(elt, DeclNameLoc(), /*implicit*/true);
auto metaTyRef = TypeExpr::createImplicit(enumType, C);
auto valueExpr = new (C) DotSyntaxCallExpr(eltRef, SourceLoc(), metaTyRef);
// assignment = "self = \(valueExpr)"
auto selfRef = new (C) DeclRefExpr(selfDecl, DeclNameLoc(),
/*implicit*/true,
AccessSemantics::DirectToStorage);
auto assignment = new (C) AssignExpr(selfRef, SourceLoc(), valueExpr,
/*implicit*/ true);
stmts.push_back(ASTNode(assignment));
// body = "{ \(stmts) }" (the braces are silent)
auto body = BraceStmt::create(C, SourceLoc(),
stmts, SourceLoc());
// cases.append("case \(litPat): \(body)")
cases.push_back(CaseStmt::create(C, SourceLoc(), CaseLabelItem(litPat),
SourceLoc(), SourceLoc(), body,
/*case body var decls*/ None));
Idx++;
}
auto anyPat = new (C) AnyPattern(SourceLoc());
anyPat->setImplicit();
auto dfltLabelItem = CaseLabelItem::getDefault(anyPat);
auto dfltReturnStmt = new (C) FailStmt(SourceLoc(), SourceLoc());
auto dfltBody = BraceStmt::create(C, SourceLoc(),
ASTNode(dfltReturnStmt), SourceLoc());
cases.push_back(CaseStmt::create(C, SourceLoc(), dfltLabelItem, SourceLoc(),
SourceLoc(), dfltBody,
/*case body var decls*/ None));
auto rawDecl = initDecl->getParameters()->get(0);
auto rawRef = new (C) DeclRefExpr(rawDecl, DeclNameLoc(), /*implicit*/true);
Expr *switchArg = rawRef;
if (isStringEnum) {
// Call _findStringSwitchCase with an array of strings as argument.
auto *Fun = new (C) UnresolvedDeclRefExpr(
C.getIdentifier("_findStringSwitchCase"),
DeclRefKind::Ordinary, DeclNameLoc());
auto *strArray = ArrayExpr::create(C, SourceLoc(), stringExprs, {},
SourceLoc());;
Identifier tableId = C.getIdentifier("cases");
Identifier strId = C.getIdentifier("string");
auto *Args = TupleExpr::createImplicit(C, {strArray, rawRef},
{tableId, strId});
auto *CallExpr = CallExpr::create(C, Fun, Args, {}, {}, false, false);
switchArg = CallExpr;
}
auto switchStmt = SwitchStmt::create(LabeledStmtInfo(), SourceLoc(), switchArg,
SourceLoc(), cases, SourceLoc(), C);
auto body = BraceStmt::create(C, SourceLoc(),
ASTNode(switchStmt),
SourceLoc());
return { body, /*isTypeChecked=*/false };
}
static ConstructorDecl *
deriveRawRepresentable_init(DerivedConformance &derived) {
auto &tc = derived.TC;
ASTContext &C = tc.Context;
auto enumDecl = cast<EnumDecl>(derived.Nominal);
auto parentDC = derived.getConformanceContext();
auto rawInterfaceType = enumDecl->getRawType();
auto rawType = parentDC->mapTypeIntoContext(rawInterfaceType);
auto equatableProto = tc.getProtocol(enumDecl->getLoc(),
KnownProtocolKind::Equatable);
assert(equatableProto);
assert(TypeChecker::conformsToProtocol(rawType, equatableProto,
enumDecl, None));
(void)equatableProto;
(void)rawType;
auto *rawDecl = new (C)
ParamDecl(ParamDecl::Specifier::Default, SourceLoc(), SourceLoc(),
C.Id_rawValue, SourceLoc(), C.Id_rawValue, parentDC);
rawDecl->setInterfaceType(rawInterfaceType);
rawDecl->setImplicit();
auto paramList = ParameterList::createWithoutLoc(rawDecl);
DeclName name(C, DeclBaseName::createConstructor(), paramList);
auto initDecl =
new (C) ConstructorDecl(name, SourceLoc(),
/*Failable=*/ true, /*FailabilityLoc=*/SourceLoc(),
/*Throws=*/false, /*ThrowsLoc=*/SourceLoc(),
paramList,
/*GenericParams=*/nullptr, parentDC);
initDecl->setImplicit();
initDecl->setBodySynthesizer(&deriveBodyRawRepresentable_init);
// Compute the interface type of the initializer.
initDecl->setGenericSignature(parentDC->getGenericSignatureOfContext());
initDecl->computeType();
initDecl->copyFormalAccessFrom(enumDecl, /*sourceIsParentContext*/true);
// If the containing module is not resilient, make sure clients can construct
// an instance without function call overhead.
maybeMarkAsInlinable(derived, initDecl);
C.addSynthesizedDecl(initDecl);
derived.addMembersToConformanceContext({initDecl});
return initDecl;
}
static bool canSynthesizeRawRepresentable(DerivedConformance &derived) {
auto enumDecl = cast<EnumDecl>(derived.Nominal);
auto &tc = derived.TC;
Type rawType = enumDecl->getRawType();
if (!rawType)
return false;
auto parentDC = cast<DeclContext>(derived.ConformanceDecl);
rawType = parentDC->mapTypeIntoContext(rawType);
auto inherited = enumDecl->getInherited();
if (!inherited.empty() && inherited.front().wasValidated() &&
inherited.front().isError())
return false;
// The raw type must be Equatable, so that we have a suitable ~= for
// synthesized switch statements.
auto equatableProto =
tc.getProtocol(enumDecl->getLoc(), KnownProtocolKind::Equatable);
if (!equatableProto)
return false;
if (!TypeChecker::conformsToProtocol(rawType, equatableProto,
enumDecl, None))
return false;
// There must be enum elements.
if (enumDecl->getAllElements().empty())
return false;
// Have the type-checker validate that:
// - the enum elements all have the same type
// - they all match the enum type
for (auto elt : enumDecl->getAllElements()) {
// We cannot synthesize raw representable conformance for an enum with
// cases that have a payload.
if (elt->hasAssociatedValues())
return false;
// FIXME(InterfaceTypeRequest): isInvalid() should be based on the interface type.
(void)elt->getInterfaceType();
if (elt->isInvalid()) {
return false;
}
}
// If it meets all of those requirements, we can synthesize RawRepresentable conformance.
return true;
}
ValueDecl *DerivedConformance::deriveRawRepresentable(ValueDecl *requirement) {
// We can only synthesize RawRepresentable for enums.
if (!isa<EnumDecl>(Nominal))
return nullptr;
// Check other preconditions for synthesized conformance.
if (!canSynthesizeRawRepresentable(*this))
return nullptr;
if (requirement->getBaseName() == TC.Context.Id_rawValue)
return deriveRawRepresentable_raw(*this);
if (requirement->getBaseName() == DeclBaseName::createConstructor())
return deriveRawRepresentable_init(*this);
TC.diagnose(requirement->getLoc(),
diag::broken_raw_representable_requirement);
return nullptr;
}
Type DerivedConformance::deriveRawRepresentable(AssociatedTypeDecl *assocType) {
// We can only synthesize RawRepresentable for enums.
if (!isa<EnumDecl>(Nominal))
return nullptr;
// Check other preconditions for synthesized conformance.
if (!canSynthesizeRawRepresentable(*this))
return nullptr;
if (assocType->getName() == TC.Context.Id_RawValue) {
return deriveRawRepresentable_Raw(*this);
}
TC.diagnose(assocType->getLoc(), diag::broken_raw_representable_requirement);
return nullptr;
}