mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
1005 lines
38 KiB
C++
1005 lines
38 KiB
C++
//===--- DerivedConformance.cpp ---------------------------------*- C++ -*-===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2014 - 2025 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 "DerivedConformance.h"
|
|
#include "TypeCheckConcurrency.h"
|
|
#include "TypeChecker.h"
|
|
#include "swift/AST/ASTPrinter.h"
|
|
#include "swift/AST/ConformanceLookup.h"
|
|
#include "swift/AST/Decl.h"
|
|
#include "swift/AST/Expr.h"
|
|
#include "swift/AST/ParameterList.h"
|
|
#include "swift/AST/Pattern.h"
|
|
#include "swift/AST/ProtocolConformance.h"
|
|
#include "swift/AST/SourceFile.h"
|
|
#include "swift/AST/Stmt.h"
|
|
#include "swift/AST/Types.h"
|
|
#include "swift/Basic/Assertions.h"
|
|
#include "swift/ClangImporter/ClangModule.h"
|
|
|
|
using namespace swift;
|
|
|
|
enum NonconformingMemberKind { AssociatedValue, StoredProperty };
|
|
|
|
DerivedConformance::DerivedConformance(
|
|
const NormalProtocolConformance *conformance, NominalTypeDecl *nominal,
|
|
ProtocolDecl *protocol)
|
|
: Context(nominal->getASTContext()), Conformance(conformance),
|
|
Nominal(nominal), Protocol(protocol) {
|
|
auto *DC = Conformance->getDeclContext();
|
|
ConformanceDecl = DC->getInnermostDeclarationDeclContext();
|
|
assert(ConformanceDecl);
|
|
assert(DC->getSelfNominalTypeDecl() == nominal);
|
|
}
|
|
|
|
DeclContext *DerivedConformance::getConformanceContext() const {
|
|
return Conformance->getDeclContext();
|
|
}
|
|
|
|
ModuleDecl *DerivedConformance::getParentModule() const {
|
|
return getConformanceContext()->getParentModule();
|
|
}
|
|
|
|
void DerivedConformance::addMembersToConformanceContext(
|
|
ArrayRef<Decl *> children) {
|
|
auto IDC = cast<IterableDeclContext>(ConformanceDecl);
|
|
for (auto child : children)
|
|
IDC->addMember(child);
|
|
}
|
|
|
|
void DerivedConformance::addMemberToConformanceContext(
|
|
Decl *member, Decl *hint) {
|
|
auto IDC = cast<IterableDeclContext>(ConformanceDecl);
|
|
IDC->addMember(member, hint, /*insertAtHead=*/false);
|
|
}
|
|
|
|
void DerivedConformance::addMemberToConformanceContext(
|
|
Decl *member, bool insertAtHead) {
|
|
auto IDC = cast<IterableDeclContext>(ConformanceDecl);
|
|
IDC->addMember(member, /*hint=*/nullptr, insertAtHead);
|
|
}
|
|
|
|
Type DerivedConformance::getProtocolType() const {
|
|
return Protocol->getDeclaredInterfaceType();
|
|
}
|
|
|
|
bool DerivedConformance::derivesProtocolConformance(DeclContext *DC,
|
|
NominalTypeDecl *Nominal,
|
|
ProtocolDecl *Protocol) {
|
|
const auto derivableKind = Protocol->getKnownDerivableProtocolKind();
|
|
if (!derivableKind)
|
|
return false;
|
|
|
|
// When the necessary requirements are met, the conformance to OptionSet
|
|
// is serendipitously derived via memberwise initializer synthesis.
|
|
if (*derivableKind == KnownDerivableProtocolKind::OptionSet) {
|
|
return false;
|
|
}
|
|
|
|
if (*derivableKind == KnownDerivableProtocolKind::Hashable) {
|
|
// We can always complete a partial Hashable implementation, and we can
|
|
// synthesize a full Hashable implementation for structs and enums with
|
|
// Hashable components.
|
|
return canDeriveHashable(Nominal);
|
|
}
|
|
|
|
if (*derivableKind == KnownDerivableProtocolKind::Actor)
|
|
return canDeriveActor(DC, Nominal);
|
|
|
|
if (*derivableKind == KnownDerivableProtocolKind::Identifiable)
|
|
return canDeriveIdentifiable(Nominal, DC);
|
|
if (*derivableKind == KnownDerivableProtocolKind::DistributedActor)
|
|
return canDeriveDistributedActor(Nominal, DC);
|
|
if (*derivableKind == KnownDerivableProtocolKind::DistributedActorSystem)
|
|
return canDeriveDistributedActorSystem(Nominal, DC);
|
|
|
|
if (*derivableKind == KnownDerivableProtocolKind::AdditiveArithmetic)
|
|
return canDeriveAdditiveArithmetic(Nominal, DC);
|
|
|
|
// Eagerly return true here. Actual synthesis conditions are checked in
|
|
// `DerivedConformance::deriveDifferentiable`: they are complicated and depend
|
|
// on the requirement being derived.
|
|
if (*derivableKind == KnownDerivableProtocolKind::Differentiable)
|
|
return true;
|
|
|
|
if (*derivableKind == KnownDerivableProtocolKind::Encodable) {
|
|
return canDeriveEncodable(Nominal);
|
|
}
|
|
|
|
if (*derivableKind == KnownDerivableProtocolKind::Decodable) {
|
|
return canDeriveDecodable(Nominal);
|
|
}
|
|
|
|
if (auto *enumDecl = dyn_cast<EnumDecl>(Nominal)) {
|
|
switch (*derivableKind) {
|
|
// The presence of a raw type is an explicit declaration that
|
|
// the compiler should derive a RawRepresentable conformance.
|
|
case KnownDerivableProtocolKind::RawRepresentable:
|
|
return canDeriveRawRepresentable(DC, Nominal);
|
|
|
|
// Enums without associated values can implicitly derive Equatable
|
|
// conformance.
|
|
case KnownDerivableProtocolKind::Equatable:
|
|
return canDeriveEquatable(DC, Nominal);
|
|
|
|
case KnownDerivableProtocolKind::Comparable:
|
|
return !enumDecl->hasPotentiallyUnavailableCaseValue()
|
|
&& canDeriveComparable(DC, enumDecl);
|
|
|
|
// "Simple" enums without availability attributes can explicitly derive
|
|
// a CaseIterable conformance.
|
|
//
|
|
// FIXME: Lift the availability restriction.
|
|
case KnownDerivableProtocolKind::CaseIterable:
|
|
return !enumDecl->hasPotentiallyUnavailableCaseValue()
|
|
&& enumDecl->hasOnlyCasesWithoutAssociatedValues();
|
|
|
|
// @objc enums can explicitly derive their _BridgedNSError conformance.
|
|
case KnownDerivableProtocolKind::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 KnownDerivableProtocolKind::CodingKey: {
|
|
Type rawType = enumDecl->getRawType();
|
|
if (rawType) {
|
|
return rawType->isString() || rawType->isInt();
|
|
}
|
|
|
|
// hasOnlyCasesWithoutAssociatedValues will return true for empty enums;
|
|
// empty enums are allowed to conform as well.
|
|
return enumDecl->hasOnlyCasesWithoutAssociatedValues();
|
|
}
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
} else if (isa<StructDecl>(Nominal)) {
|
|
switch (*derivableKind) {
|
|
case KnownDerivableProtocolKind::Equatable:
|
|
// Structs can explicitly derive Equatable conformance.
|
|
return canDeriveEquatable(DC, Nominal);
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
SmallVector<VarDecl *, 3>
|
|
DerivedConformance::storedPropertiesNotConformingToProtocol(
|
|
DeclContext *DC, StructDecl *theStruct, ProtocolDecl *protocol) {
|
|
auto storedProperties = theStruct->getStoredProperties();
|
|
SmallVector<VarDecl *, 3> nonconformingProperties;
|
|
for (auto propertyDecl : storedProperties) {
|
|
if (!propertyDecl->isUserAccessible())
|
|
continue;
|
|
|
|
auto type = propertyDecl->getValueInterfaceType();
|
|
if (!type)
|
|
nonconformingProperties.push_back(propertyDecl);
|
|
|
|
if (!checkConformance(DC->mapTypeIntoContext(type), protocol)) {
|
|
nonconformingProperties.push_back(propertyDecl);
|
|
}
|
|
}
|
|
return nonconformingProperties;
|
|
}
|
|
|
|
void DerivedConformance::tryDiagnoseFailedDerivation(DeclContext *DC,
|
|
NominalTypeDecl *nominal,
|
|
ProtocolDecl *protocol) {
|
|
auto knownProtocol = protocol->getKnownProtocolKind();
|
|
if (!knownProtocol)
|
|
return;
|
|
|
|
if (*knownProtocol == KnownProtocolKind::Equatable) {
|
|
tryDiagnoseFailedEquatableDerivation(DC, nominal);
|
|
}
|
|
|
|
if (*knownProtocol == KnownProtocolKind::Hashable) {
|
|
tryDiagnoseFailedHashableDerivation(DC, nominal);
|
|
}
|
|
|
|
if (*knownProtocol == KnownProtocolKind::Comparable) {
|
|
tryDiagnoseFailedComparableDerivation(DC, nominal);
|
|
}
|
|
|
|
if (*knownProtocol == KnownProtocolKind::DistributedActor) {
|
|
tryDiagnoseFailedDistributedActorDerivation(DC, nominal);
|
|
}
|
|
|
|
if (*knownProtocol == KnownProtocolKind::DistributedActorSystem) {
|
|
tryDiagnoseFailedDistributedActorSystemDerivation(DC, nominal);
|
|
}
|
|
}
|
|
|
|
void DerivedConformance::diagnoseAnyNonConformingMemberTypes(
|
|
DeclContext *DC, NominalTypeDecl *nominal, ProtocolDecl *protocol) {
|
|
ASTContext &ctx = DC->getASTContext();
|
|
|
|
if (auto *enumDecl = dyn_cast<EnumDecl>(nominal)) {
|
|
auto nonconformingAssociatedTypes =
|
|
associatedValuesNotConformingToProtocol(DC, enumDecl, protocol);
|
|
for (auto *typeToDiagnose : nonconformingAssociatedTypes) {
|
|
SourceLoc reprLoc;
|
|
if (auto *repr = typeToDiagnose->getTypeRepr())
|
|
reprLoc = repr->getStartLoc();
|
|
ctx.Diags.diagnose(
|
|
reprLoc, diag::missing_member_type_conformance_prevents_synthesis,
|
|
NonconformingMemberKind::AssociatedValue,
|
|
typeToDiagnose->getInterfaceType(),
|
|
protocol->getDeclaredInterfaceType(),
|
|
nominal->getDeclaredInterfaceType());
|
|
}
|
|
}
|
|
|
|
if (auto *structDecl = dyn_cast<StructDecl>(nominal)) {
|
|
auto nonconformingStoredProperties =
|
|
storedPropertiesNotConformingToProtocol(DC, structDecl, protocol);
|
|
for (auto *propertyToDiagnose : nonconformingStoredProperties) {
|
|
ctx.Diags.diagnose(
|
|
propertyToDiagnose->getLoc(),
|
|
diag::missing_member_type_conformance_prevents_synthesis,
|
|
NonconformingMemberKind::StoredProperty,
|
|
propertyToDiagnose->getInterfaceType(),
|
|
protocol->getDeclaredInterfaceType(),
|
|
nominal->getDeclaredInterfaceType());
|
|
}
|
|
}
|
|
}
|
|
|
|
void DerivedConformance::diagnoseIfSynthesisUnsupportedForDecl(
|
|
NominalTypeDecl *nominal, ProtocolDecl *protocol) {
|
|
auto shouldDiagnose = false;
|
|
|
|
if (protocol->isSpecificProtocol(KnownProtocolKind::Equatable) ||
|
|
protocol->isSpecificProtocol(KnownProtocolKind::Hashable)) {
|
|
shouldDiagnose = isa<ClassDecl>(nominal);
|
|
}
|
|
|
|
if (protocol->isSpecificProtocol(KnownProtocolKind::Comparable)) {
|
|
shouldDiagnose = !isa<EnumDecl>(nominal);
|
|
}
|
|
|
|
if (shouldDiagnose) {
|
|
auto &ctx = nominal->getASTContext();
|
|
ctx.Diags.diagnose(nominal->getLoc(),
|
|
diag::automatic_protocol_synthesis_unsupported, protocol,
|
|
nominal);
|
|
}
|
|
}
|
|
|
|
ValueDecl *DerivedConformance::getDerivableRequirement(NominalTypeDecl *nominal,
|
|
ValueDecl *requirement) {
|
|
// Note: whenever you update this function, also update
|
|
// deriveProtocolRequirement.
|
|
ASTContext &ctx = nominal->getASTContext();
|
|
const auto name = requirement->getName();
|
|
|
|
// 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;
|
|
|
|
auto conformance = lookupConformance(
|
|
nominal->getDeclaredInterfaceType(), proto);
|
|
if (conformance) {
|
|
auto DC = conformance.getConcrete()->getDeclContext();
|
|
// Check whether this nominal type derives conformances to the protocol.
|
|
if (!DerivedConformance::derivesProtocolConformance(DC, nominal, proto))
|
|
return nullptr;
|
|
}
|
|
|
|
// Retrieve the requirement.
|
|
return proto->getSingleRequirement(name);
|
|
};
|
|
|
|
// 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);
|
|
|
|
// CaseIterable.allValues
|
|
if (name.isSimpleName(ctx.Id_allCases))
|
|
return getRequirement(KnownProtocolKind::CaseIterable);
|
|
|
|
// _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);
|
|
|
|
// AdditiveArithmetic.zero
|
|
if (name.isSimpleName(ctx.Id_zero))
|
|
return getRequirement(KnownProtocolKind::AdditiveArithmetic);
|
|
|
|
// Actor.unownedExecutor
|
|
if (name.isSimpleName(ctx.Id_unownedExecutor)) {
|
|
if (nominal->isDistributedActor()) {
|
|
return getRequirement(KnownProtocolKind::DistributedActor);
|
|
} else {
|
|
return getRequirement(KnownProtocolKind::Actor);
|
|
}
|
|
}
|
|
|
|
// DistributedActor.id
|
|
if (name.isSimpleName(ctx.Id_id))
|
|
return getRequirement(KnownProtocolKind::DistributedActor);
|
|
|
|
// DistributedActor.actorSystem
|
|
if (name.isSimpleName(ctx.Id_actorSystem))
|
|
return getRequirement(KnownProtocolKind::DistributedActor);
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
// Functions.
|
|
if (auto func = dyn_cast<FuncDecl>(requirement)) {
|
|
if (func->isOperator() && name.getBaseName() == "<")
|
|
return getRequirement(KnownProtocolKind::Comparable);
|
|
|
|
if (func->isOperator() && name.getBaseName() == "==")
|
|
return getRequirement(KnownProtocolKind::Equatable);
|
|
|
|
// AdditiveArithmetic.+
|
|
// AdditiveArithmetic.-
|
|
if (func->isOperator() && name.getArgumentNames().size() == 2 &&
|
|
(name.getBaseName() == "+" || name.getBaseName() == "-")) {
|
|
return getRequirement(KnownProtocolKind::AdditiveArithmetic);
|
|
}
|
|
|
|
// Differentiable.move(by:)
|
|
if (name.isCompoundName() && name.getBaseName() == ctx.Id_move) {
|
|
auto argumentNames = name.getArgumentNames();
|
|
if (argumentNames.size() == 1 && argumentNames[0] == ctx.Id_by)
|
|
return getRequirement(KnownProtocolKind::Differentiable);
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
|
|
// Hashable.hash(into: inout Hasher)
|
|
if (name.isCompoundName() && name.getBaseName() == ctx.Id_hash) {
|
|
auto argumentNames = name.getArgumentNames();
|
|
if (argumentNames.size() == 1 && argumentNames[0] == ctx.Id_into)
|
|
return getRequirement(KnownProtocolKind::Hashable);
|
|
}
|
|
|
|
// static DistributedActor.resolve(id:using:)
|
|
if (name.isCompoundName() && name.getBaseName() == ctx.Id_resolve &&
|
|
func->isStatic()) {
|
|
auto argumentNames = name.getArgumentNames();
|
|
if (argumentNames.size() == 2 &&
|
|
argumentNames[0] == ctx.Id_id &&
|
|
argumentNames[1] == ctx.Id_using) {
|
|
return getRequirement(KnownProtocolKind::DistributedActor);
|
|
}
|
|
}
|
|
|
|
// DistributedActor.actorSystem
|
|
if (name.isCompoundName() &&
|
|
name.getBaseName() == ctx.Id_invokeHandlerOnReturn)
|
|
return getRequirement(KnownProtocolKind::DistributedActorSystem);
|
|
|
|
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->isFailable() &&
|
|
!ctor->isImplicitlyUnwrappedOptional() &&
|
|
(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);
|
|
|
|
// CaseIterable.AllCases
|
|
if (name.isSimpleName(ctx.Id_AllCases))
|
|
return getRequirement(KnownProtocolKind::CaseIterable);
|
|
|
|
// Differentiable.TangentVector
|
|
if (name.isSimpleName(ctx.Id_TangentVector))
|
|
return getRequirement(KnownProtocolKind::Differentiable);
|
|
|
|
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);
|
|
}
|
|
|
|
CallExpr *
|
|
DerivedConformance::createBuiltinCall(ASTContext &ctx,
|
|
BuiltinValueKind builtin,
|
|
ArrayRef<Type> typeArgs,
|
|
ArrayRef<Expr *> args) {
|
|
auto name = ctx.getIdentifier(getBuiltinName(builtin));
|
|
auto decl = getBuiltinValueDecl(ctx, name);
|
|
assert(decl);
|
|
|
|
ConcreteDeclRef declRef = decl;
|
|
auto fnType = decl->getInterfaceType();
|
|
if (auto genericFnType = fnType->getAs<GenericFunctionType>()) {
|
|
auto generics = genericFnType->getGenericSignature();
|
|
auto subs = SubstitutionMap::get(generics, typeArgs,
|
|
LookUpConformanceInModule{});
|
|
declRef = ConcreteDeclRef(decl, subs);
|
|
fnType = genericFnType->substGenericArgs(subs);
|
|
} else {
|
|
assert(typeArgs.empty());
|
|
}
|
|
|
|
auto resultType = fnType->castTo<FunctionType>()->getResult();
|
|
|
|
Expr *ref = new (ctx) DeclRefExpr(declRef, DeclNameLoc(),
|
|
/*Implicit=*/true,
|
|
AccessSemantics::Ordinary, fnType);
|
|
auto *argList = ArgumentList::forImplicitUnlabeled(ctx, args);
|
|
auto *call = CallExpr::createImplicit(ctx, ref, argList);
|
|
call->setType(resultType);
|
|
call->setThrows(nullptr);
|
|
|
|
return call;
|
|
}
|
|
|
|
CallExpr *DerivedConformance::createDiagnoseUnavailableCodeReachedCallExpr(
|
|
ASTContext &ctx) {
|
|
FuncDecl *diagnoseDecl = ctx.getDiagnoseUnavailableCodeReached();
|
|
assert(diagnoseDecl);
|
|
auto diagnoseDeclRefExpr =
|
|
new (ctx) DeclRefExpr(diagnoseDecl, DeclNameLoc(), true);
|
|
diagnoseDeclRefExpr->setType(diagnoseDecl->getInterfaceType());
|
|
auto argList = ArgumentList::createImplicit(ctx, {});
|
|
auto callExpr = CallExpr::createImplicit(ctx, diagnoseDeclRefExpr, argList);
|
|
callExpr->setType(ctx.getNeverType());
|
|
callExpr->setThrows(nullptr);
|
|
return callExpr;
|
|
}
|
|
|
|
AccessorDecl *
|
|
DerivedConformance::addGetterToReadOnlyDerivedProperty(VarDecl *property) {
|
|
auto getter = declareDerivedPropertyGetter(property);
|
|
|
|
property->setImplInfo(StorageImplInfo::getImmutableComputed());
|
|
property->setAccessors(SourceLoc(), {getter}, SourceLoc());
|
|
|
|
return getter;
|
|
}
|
|
|
|
AccessorDecl *
|
|
DerivedConformance::declareDerivedPropertyGetter(VarDecl *property) {
|
|
auto &C = property->getASTContext();
|
|
auto parentDC = property->getDeclContext();
|
|
ParameterList *params = ParameterList::createEmpty(C);
|
|
|
|
auto getterDecl = AccessorDecl::create(
|
|
C,
|
|
/*FuncLoc=*/SourceLoc(), /*AccessorKeywordLoc=*/SourceLoc(),
|
|
AccessorKind::Get, property,
|
|
/*Async=*/false, /*AsyncLoc=*/SourceLoc(),
|
|
/*Throws=*/false, /*ThrowsLoc=*/SourceLoc(), /*ThrownType=*/TypeLoc(),
|
|
params, property->getInterfaceType(), parentDC);
|
|
getterDecl->setImplicit();
|
|
getterDecl->setIsTransparent(false);
|
|
getterDecl->copyFormalAccessFrom(property);
|
|
|
|
return getterDecl;
|
|
}
|
|
|
|
static VarDecl::Introducer
|
|
mapIntroducer(DerivedConformance::SynthesizedIntroducer intro) {
|
|
switch (intro) {
|
|
case DerivedConformance::SynthesizedIntroducer::Let:
|
|
return VarDecl::Introducer::Let;
|
|
case DerivedConformance::SynthesizedIntroducer::Var:
|
|
return VarDecl::Introducer::Var;
|
|
}
|
|
llvm_unreachable("Invalid synthesized introducer!");
|
|
}
|
|
|
|
std::pair<VarDecl *, PatternBindingDecl *>
|
|
DerivedConformance::declareDerivedProperty(SynthesizedIntroducer intro,
|
|
Identifier name,
|
|
Type propertyInterfaceType,
|
|
bool isStatic, bool isFinal) {
|
|
auto parentDC = getConformanceContext();
|
|
|
|
VarDecl *propDecl = new (Context) VarDecl(
|
|
/*IsStatic*/ isStatic, mapIntroducer(intro), SourceLoc(), name, parentDC);
|
|
propDecl->setImplicit();
|
|
propDecl->setSynthesized();
|
|
propDecl->copyFormalAccessFrom(Nominal, /*sourceIsParentContext*/ true);
|
|
propDecl->setInterfaceType(propertyInterfaceType);
|
|
|
|
auto propertyContextType =
|
|
getConformanceContext()->mapTypeIntoContext(propertyInterfaceType);
|
|
|
|
Pattern *propPat =
|
|
NamedPattern::createImplicit(Context, propDecl, propertyContextType);
|
|
|
|
propPat = TypedPattern::createImplicit(Context, propPat, propertyContextType);
|
|
|
|
auto *pbDecl = PatternBindingDecl::createImplicit(
|
|
Context, StaticSpellingKind::None, propPat, /*InitExpr*/ nullptr,
|
|
parentDC);
|
|
return {propDecl, pbDecl};
|
|
}
|
|
|
|
bool DerivedConformance::checkAndDiagnoseDisallowedContext(
|
|
ValueDecl *synthesizing) const {
|
|
// In general, conformances can't be synthesized in extensions across files;
|
|
// but we have to allow it as a special case for Equatable and Hashable on
|
|
// enums with no associated values to preserve source compatibility.
|
|
bool allowCrossfileExtensions = false;
|
|
if (Protocol->isSpecificProtocol(KnownProtocolKind::Equatable) ||
|
|
Protocol->isSpecificProtocol(KnownProtocolKind::Hashable)) {
|
|
auto ED = dyn_cast<EnumDecl>(Nominal);
|
|
allowCrossfileExtensions = ED && ED->hasOnlyCasesWithoutAssociatedValues();
|
|
}
|
|
|
|
if (!allowCrossfileExtensions &&
|
|
Nominal->getModuleScopeContext() !=
|
|
getConformanceContext()->getModuleScopeContext()) {
|
|
ConformanceDecl->diagnose(diag::cannot_synthesize_in_crossfile_extension,
|
|
Nominal, synthesizing->getName(),
|
|
getProtocolType());
|
|
Nominal->diagnose(diag::kind_declared_here, DescriptiveDeclKind::Type);
|
|
|
|
// Try to insert a stub.
|
|
auto Extension = cast<ExtensionDecl>(getConformanceContext());
|
|
auto FixitLocation = Extension->getBraces().Start;
|
|
llvm::SmallString<128> Text;
|
|
{
|
|
llvm::raw_svector_ostream SS(Text);
|
|
swift::printRequirementStub(synthesizing, Nominal,
|
|
Nominal->getDeclaredType(),
|
|
Extension->getStartLoc(), SS);
|
|
if (!Text.empty()) {
|
|
ConformanceDecl->diagnose(diag::missing_witnesses_general)
|
|
.fixItInsertAfter(FixitLocation, Text.str());
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// A non-final class can't have a protocol-witnesses initializer in an
|
|
// extension.
|
|
if (auto CD = dyn_cast<ClassDecl>(Nominal)) {
|
|
if (!CD->isSemanticallyFinal() && isa<ConstructorDecl>(synthesizing) &&
|
|
isa<ExtensionDecl>(ConformanceDecl)) {
|
|
ConformanceDecl->diagnose(
|
|
diag::cannot_synthesize_init_in_extension_of_nonfinal,
|
|
getProtocolType(), synthesizing->getName());
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (auto ED = dyn_cast<EnumDecl>(Nominal)) {
|
|
if (ED->getAllCases().empty() &&
|
|
(Protocol->isSpecificProtocol(KnownProtocolKind::Encodable) ||
|
|
Protocol->isSpecificProtocol(KnownProtocolKind::Decodable))) {
|
|
ED->diagnose(diag::codable_synthesis_empty_enum_not_supported,
|
|
getProtocolType(), Nominal->getBaseIdentifier());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/// Returns a generated guard statement that checks whether the given lhs and
|
|
/// rhs expressions are equal. If not equal, the else block for the guard
|
|
/// returns `guardReturnValue`.
|
|
/// \p C The AST context.
|
|
/// \p lhsExpr The first expression to compare for equality.
|
|
/// \p rhsExpr The second expression to compare for equality.
|
|
/// \p guardReturnValue The expression to return if the two sides are not equal
|
|
GuardStmt *DerivedConformance::returnIfNotEqualGuard(ASTContext &C,
|
|
Expr *lhsExpr,
|
|
Expr *rhsExpr,
|
|
Expr *guardReturnValue) {
|
|
SmallVector<StmtConditionElement, 1> conditions;
|
|
SmallVector<ASTNode, 1> statements;
|
|
|
|
auto *returnStmt = ReturnStmt::createImplicit(C, guardReturnValue);
|
|
statements.push_back(returnStmt);
|
|
|
|
// Next, generate the condition being checked.
|
|
// lhs == rhs
|
|
auto cmpFuncExpr = new (C) UnresolvedDeclRefExpr(
|
|
DeclNameRef(C.Id_EqualsOperator), DeclRefKind::BinaryOperator,
|
|
DeclNameLoc());
|
|
auto *cmpExpr = BinaryExpr::create(C, lhsExpr, cmpFuncExpr, rhsExpr,
|
|
/*implicit*/ true);
|
|
conditions.emplace_back(cmpExpr);
|
|
|
|
// Build and return the complete guard statement.
|
|
// guard lhs == rhs else { return lhs < rhs }
|
|
auto body = BraceStmt::create(C, SourceLoc(), statements, SourceLoc());
|
|
return new (C) GuardStmt(SourceLoc(), C.AllocateCopy(conditions), body);
|
|
}
|
|
/// Returns a generated guard statement that checks whether the given lhs and
|
|
/// rhs expressions are equal. If not equal, the else block for the guard
|
|
/// returns `false`.
|
|
/// \p C The AST context.
|
|
/// \p lhsExpr The first expression to compare for equality.
|
|
/// \p rhsExpr The second expression to compare for equality.
|
|
GuardStmt *DerivedConformance::returnFalseIfNotEqualGuard(ASTContext &C,
|
|
Expr *lhsExpr,
|
|
Expr *rhsExpr) {
|
|
// return false
|
|
auto falseExpr = new (C) BooleanLiteralExpr(false, SourceLoc(), true);
|
|
return returnIfNotEqualGuard(C, lhsExpr, rhsExpr, falseExpr);
|
|
}
|
|
/// Returns a generated guard statement that checks whether the given expr is true.
|
|
/// If it is false, the else block for the guard returns `nil`.
|
|
/// \p C The AST context.
|
|
/// \p testExpr The expression that should be tested.
|
|
/// \p baseType The wrapped type of the to-be-returned Optional<Wrapped>.
|
|
GuardStmt *DerivedConformance::returnNilIfFalseGuardTypeChecked(ASTContext &C,
|
|
Expr *testExpr,
|
|
Type optionalWrappedType) {
|
|
auto nilExpr = new (C) NilLiteralExpr(SourceLoc(), /*implicit=*/true);
|
|
nilExpr->setType(optionalWrappedType->wrapInOptionalType());
|
|
|
|
SmallVector<StmtConditionElement, 1> conditions;
|
|
SmallVector<ASTNode, 1> statements;
|
|
|
|
auto *returnStmt = ReturnStmt::createImplicit(C, nilExpr);
|
|
statements.push_back(returnStmt);
|
|
|
|
// Next, generate the condition being checked.
|
|
conditions.emplace_back(testExpr);
|
|
|
|
// Build and return the complete guard statement.
|
|
// guard lhs == rhs else { return lhs < rhs }
|
|
auto body = BraceStmt::create(C, SourceLoc(), statements, SourceLoc());
|
|
return new (C) GuardStmt(SourceLoc(), C.AllocateCopy(conditions), body);
|
|
}
|
|
/// Returns a generated guard statement that checks whether the given lhs and
|
|
/// rhs expressions are equal. If not equal, the else block for the guard
|
|
/// returns lhs < rhs.
|
|
/// \p C The AST context.
|
|
/// \p lhsExpr The first expression to compare for equality.
|
|
/// \p rhsExpr The second expression to compare for equality.
|
|
GuardStmt *DerivedConformance::returnComparisonIfNotEqualGuard(ASTContext &C,
|
|
Expr *lhsExpr,
|
|
Expr *rhsExpr) {
|
|
// return lhs < rhs
|
|
auto ltFuncExpr = new (C) UnresolvedDeclRefExpr(
|
|
DeclNameRef(C.Id_LessThanOperator), DeclRefKind::BinaryOperator,
|
|
DeclNameLoc());
|
|
auto *ltExpr = BinaryExpr::create(C, lhsExpr, ltFuncExpr, rhsExpr,
|
|
/*implicit*/ true);
|
|
return returnIfNotEqualGuard(C, lhsExpr, rhsExpr, ltExpr);
|
|
}
|
|
|
|
/// Build a type-checked integer literal.
|
|
static IntegerLiteralExpr *buildIntegerLiteral(ASTContext &C, unsigned index) {
|
|
Type intType = C.getIntType();
|
|
|
|
auto literal = IntegerLiteralExpr::createFromUnsigned(C, index, SourceLoc());
|
|
literal->setType(intType);
|
|
literal->setBuiltinInitializer(C.getIntBuiltinInitDecl(C.getIntDecl()));
|
|
|
|
return literal;
|
|
}
|
|
|
|
/// Create AST statements which convert from an enum to an Int with a switch.
|
|
/// \p stmts The generated statements are appended to this vector.
|
|
/// \p parentDC Either an extension or the enum itself.
|
|
/// \p enumDecl The enum declaration.
|
|
/// \p enumVarDecl The enum input variable.
|
|
/// \p funcDecl The parent function.
|
|
/// \p indexName The name of the output variable.
|
|
/// \return A DeclRefExpr of the output variable (of type Int).
|
|
DeclRefExpr *DerivedConformance::convertEnumToIndex(SmallVectorImpl<ASTNode> &stmts,
|
|
DeclContext *parentDC,
|
|
EnumDecl *enumDecl,
|
|
VarDecl *enumVarDecl,
|
|
AbstractFunctionDecl *funcDecl,
|
|
const char *indexName) {
|
|
ASTContext &C = enumDecl->getASTContext();
|
|
Type enumType = enumVarDecl->getTypeInContext();
|
|
Type intType = C.getIntType();
|
|
|
|
auto indexVar = new (C) VarDecl(/*IsStatic*/false, VarDecl::Introducer::Var,
|
|
SourceLoc(), C.getIdentifier(indexName),
|
|
funcDecl);
|
|
indexVar->setInterfaceType(intType);
|
|
indexVar->setImplicit();
|
|
|
|
// generate: var indexVar
|
|
Pattern *indexPat = NamedPattern::createImplicit(C, indexVar, intType);
|
|
indexPat = TypedPattern::createImplicit(C, indexPat, intType);
|
|
|
|
auto *indexBind = PatternBindingDecl::createImplicit(
|
|
C, StaticSpellingKind::None, indexPat, /*InitExpr*/ nullptr, funcDecl);
|
|
|
|
unsigned index = 0;
|
|
SmallVector<CaseStmt *, 4> cases;
|
|
for (auto elt : enumDecl->getAllElements()) {
|
|
if (auto *unavailableElementCase =
|
|
DerivedConformance::unavailableEnumElementCaseStmt(enumType, elt,
|
|
funcDecl)) {
|
|
cases.push_back(unavailableElementCase);
|
|
continue;
|
|
}
|
|
|
|
// generate: case .<Case>:
|
|
auto *pat = EnumElementPattern::createImplicit(
|
|
enumType, elt, /*subPattern*/ nullptr, /*DC*/ funcDecl);
|
|
|
|
auto labelItem = CaseLabelItem(pat);
|
|
|
|
// generate: indexVar = <index>
|
|
auto indexExpr = buildIntegerLiteral(C, index++);
|
|
|
|
auto indexRef = new (C) DeclRefExpr(indexVar, DeclNameLoc(),
|
|
/*implicit*/true,
|
|
AccessSemantics::Ordinary,
|
|
LValueType::get(intType));
|
|
auto assignExpr = new (C) AssignExpr(indexRef, SourceLoc(),
|
|
indexExpr, /*implicit*/ true);
|
|
assignExpr->setType(TupleType::getEmpty(C));
|
|
auto body = BraceStmt::create(C, SourceLoc(), ASTNode(assignExpr),
|
|
SourceLoc());
|
|
cases.push_back(CaseStmt::create(C, CaseParentKind::Switch, SourceLoc(),
|
|
labelItem, SourceLoc(), SourceLoc(), body,
|
|
/*case body vardecls*/ std::nullopt));
|
|
}
|
|
|
|
// generate: switch enumVar { }
|
|
auto enumRef = new (C) DeclRefExpr(enumVarDecl, DeclNameLoc(),
|
|
/*implicit*/true,
|
|
AccessSemantics::Ordinary,
|
|
enumVarDecl->getTypeInContext());
|
|
auto switchStmt =
|
|
SwitchStmt::createImplicit(LabeledStmtInfo(), enumRef, cases, C);
|
|
|
|
stmts.push_back(indexBind);
|
|
stmts.push_back(switchStmt);
|
|
|
|
return new (C) DeclRefExpr(indexVar, DeclNameLoc(), /*implicit*/ true,
|
|
AccessSemantics::Ordinary, intType);
|
|
}
|
|
|
|
/// Returns the ParamDecl for each associated value of the given enum whose type
|
|
/// does not conform to a protocol
|
|
/// \p theEnum The enum whose elements and associated values should be checked.
|
|
/// \p protocol The protocol being requested.
|
|
/// \return The ParamDecl of each associated value whose type does not conform.
|
|
SmallVector<ParamDecl *, 4>
|
|
DerivedConformance::associatedValuesNotConformingToProtocol(
|
|
DeclContext *DC, EnumDecl *theEnum, ProtocolDecl *protocol) {
|
|
SmallVector<ParamDecl *, 4> nonconformingAssociatedValues;
|
|
for (auto elt : theEnum->getAllElements()) {
|
|
auto PL = elt->getParameterList();
|
|
if (!PL)
|
|
continue;
|
|
|
|
for (auto param : *PL) {
|
|
auto type = param->getInterfaceType();
|
|
if (checkConformance(DC->mapTypeIntoContext(type), protocol).isInvalid()) {
|
|
nonconformingAssociatedValues.push_back(param);
|
|
}
|
|
}
|
|
}
|
|
return nonconformingAssociatedValues;
|
|
}
|
|
|
|
/// Returns true if, for every element of the given enum, it either has no
|
|
/// associated values or all of them conform to a protocol.
|
|
/// \p theEnum The enum whose elements and associated values should be checked.
|
|
/// \p protocol The protocol being requested.
|
|
/// \return True if all associated values of all elements of the enum conform.
|
|
bool DerivedConformance::allAssociatedValuesConformToProtocol(DeclContext *DC,
|
|
EnumDecl *theEnum,
|
|
ProtocolDecl *protocol) {
|
|
return associatedValuesNotConformingToProtocol(DC, theEnum, protocol).empty();
|
|
}
|
|
|
|
/// Returns the pattern used to match and bind the associated values (if any) of
|
|
/// an enum case.
|
|
/// \p enumElementDecl The enum element to match.
|
|
/// \p varPrefix The prefix character for variable names (e.g., a0, a1, ...).
|
|
/// \p varContext The context into which payload variables should be declared.
|
|
/// \p boundVars The array to which the pattern's variables will be appended.
|
|
/// \p useLabels If the argument has a label, use it instead of the generated
|
|
/// name.
|
|
Pattern *DerivedConformance::enumElementPayloadSubpattern(
|
|
EnumElementDecl *enumElementDecl, char varPrefix, DeclContext *varContext,
|
|
SmallVectorImpl<VarDecl *> &boundVars, bool useLabels) {
|
|
auto parentDC = enumElementDecl->getDeclContext();
|
|
ASTContext &C = parentDC->getASTContext();
|
|
|
|
// No arguments, so no subpattern to match.
|
|
if (!enumElementDecl->hasAssociatedValues())
|
|
return nullptr;
|
|
|
|
auto payloadType = enumElementDecl->getPayloadInterfaceType();
|
|
if (auto tupleType = payloadType->getAs<TupleType>()) {
|
|
// Either multiple (labeled or unlabeled) arguments, or one labeled
|
|
// argument. Return a tuple pattern that matches the enum element in arity,
|
|
// types, and labels. For example:
|
|
// case a(x: Int) => (x: let a0)
|
|
// case b(Int, String) => (let a0, let a1)
|
|
SmallVector<TuplePatternElt, 4> elementPatterns;
|
|
int index = 0;
|
|
for (auto tupleElement : tupleType->getElements()) {
|
|
VarDecl *payloadVar;
|
|
if (useLabels && tupleElement.hasName()) {
|
|
payloadVar =
|
|
new (C) VarDecl(/*IsStatic*/ false, VarDecl::Introducer::Let,
|
|
SourceLoc(), tupleElement.getName(), varContext);
|
|
payloadVar->setInterfaceType(tupleElement.getType());
|
|
} else {
|
|
payloadVar = indexedVarDecl(varPrefix, index++, tupleElement.getType(),
|
|
varContext);
|
|
}
|
|
boundVars.push_back(payloadVar);
|
|
|
|
auto namedPattern = new (C) NamedPattern(payloadVar);
|
|
namedPattern->setImplicit();
|
|
auto letPattern = BindingPattern::createImplicit(
|
|
C, VarDecl::Introducer::Let, namedPattern);
|
|
elementPatterns.push_back(TuplePatternElt(tupleElement.getName(),
|
|
SourceLoc(), letPattern));
|
|
}
|
|
|
|
auto pat = TuplePattern::createImplicit(C, elementPatterns);
|
|
pat->setImplicit();
|
|
return pat;
|
|
}
|
|
|
|
// Otherwise, a one-argument unlabeled payload. Return a paren pattern whose
|
|
// underlying type is the same as the payload. For example:
|
|
// case a(Int) => (let a0)
|
|
auto payloadVar = indexedVarDecl(varPrefix, 0, payloadType, varContext);
|
|
boundVars.push_back(payloadVar);
|
|
|
|
auto namedPattern = new (C) NamedPattern(payloadVar);
|
|
namedPattern->setImplicit();
|
|
auto letPattern = new (C)
|
|
BindingPattern(SourceLoc(), VarDecl::Introducer::Let, namedPattern);
|
|
return ParenPattern::createImplicit(C, letPattern);
|
|
}
|
|
|
|
CaseStmt *DerivedConformance::unavailableEnumElementCaseStmt(
|
|
Type enumType, EnumElementDecl *elt, DeclContext *parentDC,
|
|
unsigned subPatternCount) {
|
|
assert(subPatternCount > 0);
|
|
|
|
ASTContext &C = parentDC->getASTContext();
|
|
if (!elt->isUnreachableAtRuntime() ||
|
|
elt->getParentEnum()->isUnreachableAtRuntime())
|
|
return nullptr;
|
|
|
|
// If the stdlib isn't new enough to contain the helper function for
|
|
// diagnosing execution of unavailable code then just synthesize this case
|
|
// normally.
|
|
if (!C.getDiagnoseUnavailableCodeReached())
|
|
return nullptr;
|
|
|
|
auto createElementPattern = [&]() -> EnumElementPattern * {
|
|
// .<elt>
|
|
return EnumElementPattern::createImplicit(
|
|
enumType, elt, /*subPattern*/ nullptr, /*DC*/ parentDC);
|
|
};
|
|
|
|
Pattern *labelItemPattern;
|
|
if (subPatternCount > 1) {
|
|
SmallVector<TuplePatternElt, 2> tuplePatternElts;
|
|
for (unsigned i = 0; i < subPatternCount; i++) {
|
|
tuplePatternElts.push_back(TuplePatternElt(createElementPattern()));
|
|
}
|
|
|
|
// (.<elt>, ..., .<elt>)
|
|
auto caseTuplePattern = TuplePattern::createImplicit(C, tuplePatternElts);
|
|
caseTuplePattern->setImplicit();
|
|
labelItemPattern = caseTuplePattern;
|
|
} else {
|
|
labelItemPattern = createElementPattern();
|
|
}
|
|
|
|
auto labelItem = CaseLabelItem(labelItemPattern);
|
|
auto *callExpr =
|
|
DerivedConformance::createDiagnoseUnavailableCodeReachedCallExpr(C);
|
|
auto body = BraceStmt::create(C, SourceLoc(), {callExpr}, SourceLoc());
|
|
return CaseStmt::create(C, CaseParentKind::Switch, SourceLoc(), labelItem,
|
|
SourceLoc(), SourceLoc(), body, {},
|
|
/*implicit*/ true);
|
|
}
|
|
|
|
/// Creates a named variable based on a prefix character and a numeric index.
|
|
/// \p prefixChar The prefix character for the variable's name.
|
|
/// \p index The numeric index to append to the variable's name.
|
|
/// \p type The type of the variable.
|
|
/// \p varContext The context of the variable.
|
|
/// \return A VarDecl named with the prefix and number.
|
|
VarDecl *DerivedConformance::indexedVarDecl(char prefixChar, int index, Type type,
|
|
DeclContext *varContext) {
|
|
ASTContext &C = varContext->getASTContext();
|
|
|
|
llvm::SmallString<8> indexVal;
|
|
indexVal.append(1, prefixChar);
|
|
APInt(32, index).toString(indexVal, 10, /*signed*/ false);
|
|
auto indexStr = C.AllocateCopy(indexVal);
|
|
auto indexStrRef = StringRef(indexStr.data(), indexStr.size());
|
|
|
|
auto varDecl = new (C) VarDecl(/*IsStatic*/false, VarDecl::Introducer::Let,
|
|
SourceLoc(), C.getIdentifier(indexStrRef),
|
|
varContext);
|
|
varDecl->setInterfaceType(type);
|
|
return varDecl;
|
|
}
|
|
|
|
bool swift::memberwiseAccessorsRequireActorIsolation(NominalTypeDecl *nominal) {
|
|
if (!getActorIsolation(nominal).isActorIsolated())
|
|
return false;
|
|
|
|
for (auto property : nominal->getStoredProperties()) {
|
|
if (!property->isUserAccessible())
|
|
continue;
|
|
|
|
if (!property->isLet())
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|