//===--- 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(expr)) { clone = new (C) IntegerLiteralExpr(intLit->getDigitsText(), expr->getLoc(), /*implicit*/ true); if (intLit->isNegative()) cast(clone)->setNegative(expr->getLoc()); } else if (isa(expr)) { clone = new (C) NilLiteralExpr(expr->getLoc()); } else if (auto stringLit = dyn_cast(expr)) { clone = new (C) StringLiteralExpr(stringLit->getValue(), expr->getLoc()); } else if (auto floatLit = dyn_cast(expr)) { clone = new (C) FloatLiteralExpr(floatLit->getDigitsText(), expr->getLoc(), /*implicit*/ true); if (floatLit->isNegative()) cast(clone)->setNegative(expr->getLoc()); } else if (auto boolLit = dyn_cast(expr)) { clone = new (C) BooleanLiteralExpr(boolLit->getValue(), expr->getLoc(), /*implicit*/true); } 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(derived.Nominal)->getRawType(); return derived.getConformanceContext()->mapTypeIntoContext(rawInterfaceType); } static std::pair 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) auto functionRef = UnresolvedDeclRefExpr::createImplicit( C, C.getIdentifier("unsafeBitCast"), {Identifier(), C.Id_to}); 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 cases; for (auto elt : enumDecl->getAllElements()) { auto pat = new (C) EnumElementPattern(TypeLoc::withoutLoc(enumType), SourceLoc(), DeclNameLoc(), DeclNameRef(), 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.Context; auto parentDC = derived.getConformanceContext(); if (!parentDC->getParentModule()->isResilient()) { AccessScope access = afd->getFormalAccessScope(nullptr, /*treatUsableFromInlineAsPublic*/true); if (auto *attr = afd->getAttrs().getAttribute()) attr->setInvalid(); if (access.isPublic()) afd->getAttrs().add(new (C) InlinableAttr(/*implicit*/false)); } } static VarDecl *deriveRawRepresentable_raw(DerivedConformance &derived) { ASTContext &C = derived.Context; auto enumDecl = cast(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 &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 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(nominalTypeDecl); Type rawTy = enumDecl->getRawType(); assert(rawTy); rawTy = initDecl->mapTypeIntoContext(rawTy); bool isStringEnum = (rawTy->getNominalOrBoundGenericNominal() == C.getStringDecl()); llvm::SmallVector stringExprs; Type enumType = parentDC->getDeclaredTypeInContext(); auto selfDecl = cast(initDecl)->getImplicitSelfDecl(); SmallVector 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 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 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 = UnresolvedDeclRefExpr::createImplicit( C, C.getIdentifier("_findStringSwitchCase")); 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) { ASTContext &C = derived.Context; auto enumDecl = cast(derived.Nominal); auto parentDC = derived.getConformanceContext(); auto rawInterfaceType = enumDecl->getRawType(); auto rawType = parentDC->mapTypeIntoContext(rawInterfaceType); auto equatableProto = TypeChecker::getProtocol(C, enumDecl->getLoc(), KnownProtocolKind::Equatable); assert(equatableProto); assert( TypeChecker::conformsToProtocol(rawType, equatableProto, enumDecl, None)); (void)equatableProto; (void)rawType; auto *rawDecl = new (C) ParamDecl(SourceLoc(), SourceLoc(), C.Id_rawValue, SourceLoc(), C.Id_rawValue, parentDC); rawDecl->setSpecifier(ParamSpecifier::Default); 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); 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); derived.addMembersToConformanceContext({initDecl}); return initDecl; } static bool canSynthesizeRawRepresentable(DerivedConformance &derived) { auto enumDecl = cast(derived.Nominal); Type rawType = enumDecl->getRawType(); if (!rawType) return false; auto parentDC = cast(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 = TypeChecker::getProtocol(enumDecl->getASTContext(), enumDecl->getLoc(), KnownProtocolKind::Equatable); if (!equatableProto) return false; if (TypeChecker::conformsToProtocol(rawType, equatableProto, enumDecl, None) .isInvalid()) 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; 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(Nominal)) return nullptr; // Check other preconditions for synthesized conformance. if (!canSynthesizeRawRepresentable(*this)) return nullptr; if (requirement->getBaseName() == Context.Id_rawValue) return deriveRawRepresentable_raw(*this); if (requirement->getBaseName() == DeclBaseName::createConstructor()) return deriveRawRepresentable_init(*this); Context.Diags.diagnose(requirement->getLoc(), diag::broken_raw_representable_requirement); return nullptr; } Type DerivedConformance::deriveRawRepresentable(AssociatedTypeDecl *assocType) { // We can only synthesize RawRepresentable for enums. if (!isa(Nominal)) return nullptr; // Check other preconditions for synthesized conformance. if (!canSynthesizeRawRepresentable(*this)) return nullptr; if (assocType->getName() == Context.Id_RawValue) { return deriveRawRepresentable_Raw(*this); } Context.Diags.diagnose(assocType->getLoc(), diag::broken_raw_representable_requirement); return nullptr; }