//===--- 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; bool DerivedConformance::derivesProtocolConformance(TypeChecker &tc, NominalTypeDecl *nominal, ProtocolDecl *protocol) { // Only known protocols can be derived. auto knownProtocol = protocol->getKnownProtocolKind(); if (!knownProtocol) return false; if (auto *enumDecl = dyn_cast(nominal)) { switch (*knownProtocol) { // The presence of a raw type is an explicit declaration that // the compiler should derive a RawRepresentable conformance. case KnownProtocolKind::RawRepresentable: return enumDecl->hasRawType(); // Enums without associated values can implicitly derive Equatable and // Hashable conformance. case KnownProtocolKind::Equatable: return canDeriveEquatable(tc, enumDecl, protocol); case KnownProtocolKind::Hashable: return canDeriveHashable(tc, enumDecl, protocol); // @objc enums can explicitly derive their _BridgedNSError conformance. case KnownProtocolKind::BridgedNSError: return enumDecl->isObjC() && enumDecl->hasCases() && enumDecl->hasOnlyCasesWithoutAssociatedValues(); // Enums without associated values and enums with a raw type of String // or Int can explicitly derive CodingKey conformance. case KnownProtocolKind::CodingKey: { Type rawType = enumDecl->getRawType(); if (rawType) { auto parentDC = enumDecl->getDeclContext(); ASTContext &C = parentDC->getASTContext(); auto nominal = rawType->getAnyNominal(); return nominal == C.getStringDecl() || nominal == C.getIntDecl(); } // hasOnlyCasesWithoutAssociatedValues will return true for empty enums; // empty enumas are allowed to conform as well. return enumDecl->hasOnlyCasesWithoutAssociatedValues(); } default: return false; } } else if (isa(nominal) || isa(nominal)) { // Structs and classes can explicitly derive Encodable and Decodable // conformance (explicitly meaning we can synthesize an implementation if // a type conforms manually). if (*knownProtocol == KnownProtocolKind::Encodable || *knownProtocol == KnownProtocolKind::Decodable) { // FIXME: This is not actually correct. We cannot promise to always // provide a witness here for all structs and classes. Unfortunately, // figuring out whether this is actually possible requires much more // context -- a TypeChecker and the parent decl context at least -- and is // tightly coupled to the logic within DerivedConformance. // This unfortunately means that we expect a witness even if one will not // be produced, which requires DerivedConformance::deriveCodable to output // its own diagnostics. return true; } // Structs can explicitly derive Equatable and Hashable conformance. if (auto structDecl = dyn_cast(nominal)) { switch (*knownProtocol) { case KnownProtocolKind::Equatable: return canDeriveEquatable(tc, structDecl, protocol); case KnownProtocolKind::Hashable: return canDeriveHashable(tc, structDecl, protocol); default: return false; } } } return false; } ValueDecl *DerivedConformance::getDerivableRequirement(TypeChecker &tc, 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 protocol. if (!derivesProtocolConformance(tc, nominal, proto)) return nullptr; // Retrieve the requirement. auto results = proto->lookupDirect(name); return results.empty() ? nullptr : results.front(); }; // Properties. if (isa(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(requirement)) { if (func->isOperator() && name.getBaseName() == "==") 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(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(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(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); auto selfParam = computeSelfParam(getterDecl); if (auto sig = parentDC->getGenericSignatureOfContext()) { getterDecl->setGenericEnvironment( parentDC->getGenericEnvironmentOfContext()); interfaceType = GenericFunctionType::get(sig, {selfParam}, interfaceType, FunctionType::ExtInfo()); } else interfaceType = FunctionType::get({selfParam}, interfaceType, FunctionType::ExtInfo()); getterDecl->setInterfaceType(interfaceType); getterDecl->copyFormalAccessAndVersionedAttrFrom(typeDecl); // 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(parentDC->getModuleScopeContext())) tc.Context.addExternalDecl(getterDecl); return getterDecl; } std::pair 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(parentDecl); VarDecl *propDecl = new (C) VarDecl(/*IsStatic*/isStatic, VarDecl::Specifier::Var, /*IsCaptureList*/false, SourceLoc(), name, propertyContextType, parentDC); propDecl->setImplicit(); propDecl->makeComputed(SourceLoc(), getterDecl, nullptr, nullptr, SourceLoc()); propDecl->copyFormalAccessAndVersionedAttrFrom(typeDecl); 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}; }