Skip to content

Ban constructing normal conformances where the conforming type is a protocol #60716

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 34 additions & 17 deletions lib/APIDigester/ModuleAnalyzerNodes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ struct swift::ide::api::SDKNodeInitInfo {
SDKNodeInitInfo(SDKContext &Ctx, ValueDecl *VD);
SDKNodeInitInfo(SDKContext &Ctx, OperatorDecl *D);
SDKNodeInitInfo(SDKContext &Ctx, ImportDecl *ID);
SDKNodeInitInfo(SDKContext &Ctx, ProtocolConformance *Conform);
SDKNodeInitInfo(SDKContext &Ctx, ProtocolConformanceRef Conform);
SDKNodeInitInfo(SDKContext &Ctx, Type Ty, TypeInitInfo Info = TypeInitInfo());
SDKNode* createSDKNode(SDKNodeKind Kind);
};
Expand Down Expand Up @@ -1459,17 +1459,21 @@ SDKNodeInitInfo::SDKNodeInitInfo(SDKContext &Ctx, ImportDecl *ID):
Name = PrintedName = Ctx.buffer(content);
}

SDKNodeInitInfo::SDKNodeInitInfo(SDKContext &Ctx, ProtocolConformance *Conform):
SDKNodeInitInfo(Ctx, Conform->getProtocol()) {
SDKNodeInitInfo::SDKNodeInitInfo(SDKContext &Ctx, ProtocolConformanceRef Conform):
SDKNodeInitInfo(Ctx, Conform.getRequirement()) {
// The conformance can be conditional. The generic signature keeps track of
// the requirements.
GenericSig = printGenericSignature(Ctx, Conform, Ctx.checkingABI());
SugaredGenericSig = Ctx.checkingABI() ?
printGenericSignature(Ctx, Conform, false): StringRef();
// Whether this conformance is ABI placeholder depends on the decl context
// of this conformance.
IsABIPlaceholder = isABIPlaceholderRecursive(Conform->getDeclContext()->
getAsDecl());
if (Conform.isConcrete()) {
auto *Concrete = Conform.getConcrete();

GenericSig = printGenericSignature(Ctx, Concrete, Ctx.checkingABI());
SugaredGenericSig = Ctx.checkingABI() ?
printGenericSignature(Ctx, Concrete, false): StringRef();
// Whether this conformance is ABI placeholder depends on the decl context
// of this conformance.
IsABIPlaceholder = isABIPlaceholderRecursive(Concrete->getDeclContext()->
getAsDecl());
}
}

static bool isProtocolRequirement(ValueDecl *VD) {
Expand Down Expand Up @@ -1910,7 +1914,7 @@ SwiftDeclCollector::constructConformanceNode(ProtocolConformance *Conform) {
if (Ctx.checkingABI())
Conform = Conform->getCanonicalConformance();
auto ConfNode = cast<SDKNodeConformance>(SDKNodeInitInfo(Ctx,
Conform).createSDKNode(SDKNodeKind::Conformance));
ProtocolConformanceRef(Conform)).createSDKNode(SDKNodeKind::Conformance));
Conform->forEachTypeWitness(
[&](AssociatedTypeDecl *assoc, Type ty, TypeDecl *typeDecl) -> bool {
ConfNode->addChild(constructTypeWitnessNode(assoc, ty));
Expand All @@ -1922,12 +1926,25 @@ SwiftDeclCollector::constructConformanceNode(ProtocolConformance *Conform) {
void swift::ide::api::
SwiftDeclCollector::addConformancesToTypeDecl(SDKNodeDeclType *Root,
NominalTypeDecl *NTD) {
// Avoid adding the same conformance twice.
SmallPtrSet<ProtocolConformance*, 4> Seen;
for (auto &Conf: NTD->getAllConformances()) {
if (!Ctx.shouldIgnore(Conf->getProtocol()) && !Seen.count(Conf))
Root->addConformance(constructConformanceNode(Conf));
Seen.insert(Conf);
if (auto *PD = dyn_cast<ProtocolDecl>(NTD)) {
PD->walkInheritedProtocols([&](ProtocolDecl *inherited) {
if (PD != inherited && !Ctx.shouldIgnore(inherited)) {
ProtocolConformanceRef Conf(inherited);
auto ConfNode = SDKNodeInitInfo(Ctx, Conf)
.createSDKNode(SDKNodeKind::Conformance);
Root->addConformance(ConfNode);
}

return TypeWalker::Action::Continue;
});
} else {
// Avoid adding the same conformance twice.
SmallPtrSet<ProtocolConformance*, 4> Seen;
for (auto &Conf: NTD->getAllConformances()) {
if (!Ctx.shouldIgnore(Conf->getProtocol()) && !Seen.count(Conf))
Root->addConformance(constructConformanceNode(Conf));
Seen.insert(Conf);
}
}
}

Expand Down
10 changes: 6 additions & 4 deletions lib/AST/ASTPrinter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2314,10 +2314,12 @@ void PrintAST::printMembersOfDecl(Decl *D, bool needComma,
AddMembers(Ext);
}
if (Options.PrintExtensionFromConformingProtocols) {
for (auto Conf : NTD->getAllConformances()) {
for (auto Ext : Conf->getProtocol()->getExtensions()) {
if (Options.printExtensionContentAsMembers(Ext))
AddMembers(Ext);
if (!isa<ProtocolDecl>(NTD)) {
for (auto Conf : NTD->getAllConformances()) {
for (auto Ext : Conf->getProtocol()->getExtensions()) {
if (Options.printExtensionContentAsMembers(Ext))
AddMembers(Ext);
}
}
}
}
Expand Down
23 changes: 11 additions & 12 deletions lib/AST/ConformanceLookupTable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -620,20 +620,14 @@ ConformanceLookupTable::Ordering ConformanceLookupTable::compareConformances(
// If the explicit protocol for the left-hand side is implied by
// the explicit protocol for the right-hand side, the left-hand
// side supersedes the right-hand side.
for (auto rhsProtocol : rhsExplicitProtocol->getAllProtocols()) {
if (rhsProtocol == lhsExplicitProtocol) {
return Ordering::Before;
}
}
if (rhsExplicitProtocol->inheritsFrom(lhsExplicitProtocol))
return Ordering::Before;

// If the explicit protocol for the right-hand side is implied by
// the explicit protocol for the left-hand side, the right-hand
// side supersedes the left-hand side.
for (auto lhsProtocol : lhsExplicitProtocol->getAllProtocols()) {
if (lhsProtocol == rhsExplicitProtocol) {
return Ordering::After;
}
}
if (lhsExplicitProtocol->inheritsFrom(rhsExplicitProtocol))
return Ordering::After;
}

// Prefer the least conditional implier, which we approximate by seeing if one
Expand Down Expand Up @@ -880,13 +874,18 @@ ConformanceLookupTable::getConformance(NominalTypeDecl *nominal,
entry->Conformance =
ctx.getInheritedConformance(type, inheritedConformance.getConcrete());
} else {
// Create or find the normal conformance.
Type conformingType = conformingDC->getDeclaredInterfaceType();
// Protocols don't have conformance lookup tables. Self-conformance is
// handled directly in lookupConformance().
assert(!isa<ProtocolDecl>(conformingNominal));
assert(!isa<ProtocolDecl>(conformingDC->getSelfNominalTypeDecl()));
Type conformingType = conformingDC->getSelfInterfaceType();

SourceLoc conformanceLoc
= conformingNominal == conformingDC
? conformingNominal->getLoc()
: cast<ExtensionDecl>(conformingDC)->getLoc();

// Create or find the normal conformance.
auto normalConf =
ctx.getConformance(conformingType, protocol, conformanceLoc,
conformingDC, ProtocolConformanceState::Incomplete,
Expand Down
15 changes: 11 additions & 4 deletions lib/AST/NameLookup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3121,10 +3121,17 @@ static bool checkForDynamicAttribute(Evaluator &eval, NominalTypeDecl *decl) {
return evaluateOrDefault(eval, Req{decl}, false);
};

// Check the protocols the type conforms to.
for (auto *proto : decl->getAllProtocols()) {
if (hasAttribute(proto))
return true;
if (auto *proto = dyn_cast<ProtocolDecl>(decl)) {
// Check inherited protocols of a protocol.
for (auto *otherProto : proto->getInheritedProtocols())
if (hasAttribute(otherProto))
return true;
} else {
// Check the protocols the type conforms to.
for (auto *otherProto : decl->getAllProtocols()) {
if (hasAttribute(otherProto))
return true;
}
}

// Check the superclass if present.
Expand Down
11 changes: 11 additions & 0 deletions lib/AST/ProtocolConformance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1224,6 +1224,9 @@ ProtocolConformance::getInheritedConformance(ProtocolDecl *protocol) const {

#pragma mark Protocol conformance lookup
void NominalTypeDecl::prepareConformanceTable() const {
assert(!isa<ProtocolDecl>(this) &&
"Protocols don't have a conformance table");

if (ConformanceTable)
return;

Expand Down Expand Up @@ -1292,6 +1295,10 @@ void NominalTypeDecl::prepareConformanceTable() const {
bool NominalTypeDecl::lookupConformance(
ProtocolDecl *protocol,
SmallVectorImpl<ProtocolConformance *> &conformances) const {
assert(!isa<ProtocolDecl>(this) &&
"Self-conformances are only found by the higher-level "
"ModuleDecl::lookupConformance() entry point");

prepareConformanceTable();
return ConformanceTable->lookupConformance(
const_cast<NominalTypeDecl *>(this),
Expand All @@ -1301,6 +1308,10 @@ bool NominalTypeDecl::lookupConformance(

SmallVector<ProtocolDecl *, 2>
NominalTypeDecl::getAllProtocols(bool sorted) const {
assert(!isa<ProtocolDecl>(this) &&
"For inherited protocols, use ProtocolDecl::inheritsFrom() or "
"ProtocolDecl::getInheritedProtocols()");

prepareConformanceTable();
SmallVector<ProtocolDecl *, 2> result;
ConformanceTable->getAllProtocols(const_cast<NominalTypeDecl *>(this), result,
Expand Down
4 changes: 2 additions & 2 deletions lib/ClangImporter/ImportDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6598,8 +6598,8 @@ void SwiftDeclConverter::importObjCProtocols(
SmallVectorImpl<InheritedEntry> &inheritedTypes) {
SmallVector<ProtocolDecl *, 4> protocols;
llvm::SmallPtrSet<ProtocolDecl *, 4> knownProtocols;
if (auto nominal = dyn_cast<NominalTypeDecl>(decl)) {
nominal->getImplicitProtocols(protocols);
if (auto classDecl = dyn_cast<ClassDecl>(decl)) {
classDecl->getImplicitProtocols(protocols);
knownProtocols.insert(protocols.begin(), protocols.end());
}

Expand Down
19 changes: 11 additions & 8 deletions lib/ConstExtract/ConstExtract.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,24 +26,27 @@
#include <sstream>
#include <string>

using namespace swift;

namespace {
/// A helper class to collect all nominal type declarations that conform to
/// specific protocols provided as input.
class NominalTypeConformanceCollector : public swift::ASTWalker {
class NominalTypeConformanceCollector : public ASTWalker {
const std::unordered_set<std::string> &Protocols;
std::vector<swift::NominalTypeDecl *> &ConformanceTypeDecls;
std::vector<NominalTypeDecl *> &ConformanceTypeDecls;

public:
NominalTypeConformanceCollector(
const std::unordered_set<std::string> &Protocols,
std::vector<swift::NominalTypeDecl *> &ConformanceDecls)
std::vector<NominalTypeDecl *> &ConformanceDecls)
: Protocols(Protocols), ConformanceTypeDecls(ConformanceDecls) {}

bool walkToDeclPre(swift::Decl *D) override {
if (auto *NTD = llvm::dyn_cast<swift::NominalTypeDecl>(D))
for (auto &Protocol : NTD->getAllProtocols())
if (Protocols.count(Protocol->getName().str().str()) != 0)
ConformanceTypeDecls.push_back(NTD);
bool walkToDeclPre(Decl *D) override {
if (auto *NTD = llvm::dyn_cast<NominalTypeDecl>(D))
if (!isa<ProtocolDecl>(NTD))
for (auto &Protocol : NTD->getAllProtocols())
if (Protocols.count(Protocol->getName().str().str()) != 0)
ConformanceTypeDecls.push_back(NTD);
return true;
}
};
Expand Down
53 changes: 33 additions & 20 deletions lib/IDE/CodeCompletionResultType.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -208,32 +208,45 @@ const USRBasedType *USRBasedType::fromType(Type Ty, USRBasedTypeArena &Arena) {
}

SmallVector<const USRBasedType *, 2> Supertypes;
;
if (auto Nominal = Ty->getAnyNominal()) {
auto Conformances = Nominal->getAllConformances();
Supertypes.reserve(Conformances.size());
for (auto Conformance : Conformances) {
if (Conformance->getDeclContext()->getParentModule() !=
Nominal->getModuleContext()) {
// Only include conformances that are declared within the module of the
// type to avoid caching retroactive conformances which might not
// exist when using the code completion cache from a different module.
continue;
}
if (Conformance->getProtocol()->isSpecificProtocol(KnownProtocolKind::Sendable)) {
// FIXME: Sendable conformances are lazily synthesized as they are
// needed by the compiler. Depending on whether we checked whether a
// type conforms to Sendable before constructing the USRBasedType, we
// get different results for its conformance. For now, always drop the
// Sendable conformance.
continue;
if (auto *Proto = dyn_cast<ProtocolDecl>(Nominal)) {
Proto->walkInheritedProtocols([&](ProtocolDecl *inherited) {
if (Proto != inherited &&
!inherited->isSpecificProtocol(KnownProtocolKind::Sendable)) {
Supertypes.push_back(USRBasedType::fromType(
inherited->getDeclaredInterfaceType(), Arena));
}

return TypeWalker::Action::Continue;
});
} else {
auto Conformances = Nominal->getAllConformances();
Supertypes.reserve(Conformances.size());
for (auto Conformance : Conformances) {
if (Conformance->getDeclContext()->getParentModule() !=
Nominal->getModuleContext()) {
// Only include conformances that are declared within the module of the
// type to avoid caching retroactive conformances which might not
// exist when using the code completion cache from a different module.
continue;
}
if (Conformance->getProtocol()->isSpecificProtocol(KnownProtocolKind::Sendable)) {
// FIXME: Sendable conformances are lazily synthesized as they are
// needed by the compiler. Depending on whether we checked whether a
// type conforms to Sendable before constructing the USRBasedType, we
// get different results for its conformance. For now, always drop the
// Sendable conformance.
continue;
}
Supertypes.push_back(USRBasedType::fromType(
Conformance->getProtocol()->getDeclaredInterfaceType(), Arena));
}
Supertypes.push_back(USRBasedType::fromType(
Conformance->getProtocol()->getDeclaredInterfaceType(), Arena));
}
}

// You would think that superclass + conformances form a DAG. You are wrong!
// We can achieve a circular supertype hierarcy with
// We can achieve a circular supertype hierarchy with
//
// protocol Proto : Class {}
// class Class : Proto {}
Expand Down
7 changes: 3 additions & 4 deletions lib/IDE/CompletionLookup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2483,7 +2483,7 @@ void CompletionLookup::addTypeRelationFromProtocol(

// The literal can produce any type that conforms to its ExpressibleBy
// protocol. Figure out as which type we want to show it in code completion.
auto *P = Ctx.getProtocol(protocolForLiteralKind(kind));
auto *PD = Ctx.getProtocol(protocolForLiteralKind(kind));
for (auto T : expectedTypeContext.getPossibleTypes()) {
if (!T)
continue;
Expand All @@ -2497,9 +2497,8 @@ void CompletionLookup::addTypeRelationFromProtocol(
}

// Check for conformance to the literal protocol.
if (auto *NTD = T->getAnyNominal()) {
SmallVector<ProtocolConformance *, 2> conformances;
if (NTD->lookupConformance(P, conformances)) {
if (T->getAnyNominal()) {
if (CurrModule->lookupConformance(T, PD)) {
literalType = T;
break;
}
Expand Down
3 changes: 3 additions & 0 deletions lib/IDE/CompletionOverrideLookup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,9 @@ void CompletionOverrideLookup::addAssociatedTypes(NominalTypeDecl *NTD) {
hasOverride || hasOverridabilityModifier || hasStaticOrClass))
return;

if (isa<ProtocolDecl>(NTD))
return;

for (auto Conformance : NTD->getAllConformances()) {
auto Proto = Conformance->getProtocol();
if (!Proto->isAccessibleFrom(CurrDeclContext))
Expand Down
3 changes: 3 additions & 0 deletions lib/PrintAsClang/DeclAndTypePrinter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1742,6 +1742,9 @@ class DeclAndTypePrinter::Implementation
if (nominal->hasClangNode())
return nullptr;

if (isa<ProtocolDecl>(nominal))
return nullptr;

auto &ctx = nominal->getASTContext();

// Dig out the ObjectiveCBridgeable protocol.
Expand Down
10 changes: 6 additions & 4 deletions lib/SILOptimizer/Analysis/ProtocolConformanceAnalysis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,12 @@ class NominalTypeWalker : public ASTWalker {
bool walkToDeclPre(Decl *D) override {
/// (1) Walk over all NominalTypeDecls to determine conformances.
if (auto *NTD = dyn_cast<NominalTypeDecl>(D)) {
auto Protocols = NTD->getAllProtocols();
for (auto &Protocol : Protocols) {
if (Protocol->getEffectiveAccess() <= AccessLevel::Internal) {
ProtocolConformanceCache[Protocol].push_back(NTD);
if (!isa<ProtocolDecl>(NTD)) {
auto Protocols = NTD->getAllProtocols();
for (auto &Protocol : Protocols) {
if (Protocol->getEffectiveAccess() <= AccessLevel::Internal) {
ProtocolConformanceCache[Protocol].push_back(NTD);
}
}
}
}
Expand Down
12 changes: 4 additions & 8 deletions lib/Sema/CSSimplify.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8093,16 +8093,12 @@ allFromConditionalConformances(DeclContext *DC, Type baseTy,
}

if (auto *protocol = candidateDC->getSelfProtocolDecl()) {
SmallVector<ProtocolConformance *, 4> conformances;
if (!NTD->lookupConformance(protocol, conformances))
auto conformance = DC->getParentModule()->lookupConformance(
baseTy, protocol);
if (!conformance.isConcrete())
return false;

// This is opportunistic, there should be a way to narrow the
// list down to a particular declaration member comes from.
return llvm::any_of(
conformances, [](const ProtocolConformance *conformance) {
return !conformance->getConditionalRequirements().empty();
});
return !conformance.getConcrete()->getConditionalRequirements().empty();
}

return false;
Expand Down
Loading