mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
This is all effectively NFC, but lays out the shape of the iterative type checker: requests are packaged up in TypeCheckRequest, we can check whether the request has been satisfied already (isSatisfied), enumerate its dependencies (enumerateDependenciesOf) in terms of other TypeCheckRequests, and satisfy a request (satisfy). Lazily-computed semantic information is captured directly in the AST, but has been set aside in its own structure to allow us to experiment with moving it into a lookaside table. The only request that exists now is to type-check the superclass of the given class. It currently performs unhealthy recursion into the existing type checker. As we detangle dependencies, this recursion between the IterativeTypeChecker and the TypeChecker can go away. Swift SVN r32558
1124 lines
40 KiB
C++
1124 lines
40 KiB
C++
//===--- ConformanceLookupTable - Conformance Lookup Table ------*- C++ -*-===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2014 - 2015 Apple Inc. and the Swift project authors
|
|
// Licensed under Apache License v2.0 with Runtime Library Exception
|
|
//
|
|
// See http://swift.org/LICENSE.txt for license information
|
|
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This file implements the ConformanceLookupTable class.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "ConformanceLookupTable.h"
|
|
#include "swift/AST/ASTContext.h"
|
|
#include "swift/AST/Decl.h"
|
|
#include "swift/AST/Module.h"
|
|
#include "llvm/Support/SaveAndRestore.h"
|
|
|
|
using namespace swift;
|
|
|
|
DeclContext *ConformanceLookupTable::ConformanceSource::getDeclContext() const {
|
|
switch (getKind()) {
|
|
case ConformanceEntryKind::Inherited:
|
|
return getInheritingClass();
|
|
|
|
case ConformanceEntryKind::Explicit:
|
|
return getExplicitDeclContext();
|
|
|
|
case ConformanceEntryKind::Implied:
|
|
return getImpliedSource()->Source.getDeclContext();
|
|
|
|
case ConformanceEntryKind::Synthesized:
|
|
return getSynthesizedDecl();
|
|
}
|
|
}
|
|
|
|
ProtocolDecl *ConformanceLookupTable::ConformanceEntry::getProtocol() const {
|
|
if (auto protocol = Conformance.dyn_cast<ProtocolDecl *>())
|
|
return protocol;
|
|
|
|
return Conformance.get<ProtocolConformance *>()->getProtocol();
|
|
}
|
|
|
|
void *ConformanceLookupTable::ConformanceEntry::operator new(
|
|
size_t Bytes,
|
|
ASTContext &C,
|
|
unsigned Alignment) {
|
|
return C.Allocate(Bytes, Alignment);
|
|
}
|
|
|
|
void ConformanceLookupTable::ConformanceEntry::markSupersededBy(
|
|
ConformanceLookupTable &table,
|
|
ConformanceEntry *entry,
|
|
bool diagnose) {
|
|
assert(!isSuperseded() && "Already superseded");
|
|
|
|
// Note that we've been superseded.
|
|
SupersededBy = entry;
|
|
|
|
if (diagnose) {
|
|
// Record the problem in the conformance table. We'll
|
|
// diagnose these in semantic analysis.
|
|
table.AllSupersededDiagnostics[getDeclContext()].push_back(this);
|
|
}
|
|
}
|
|
|
|
void ConformanceLookupTable::ConformanceEntry::dump() const {
|
|
dump(llvm::errs());
|
|
}
|
|
|
|
void ConformanceLookupTable::ConformanceEntry::dump(raw_ostream &os,
|
|
unsigned indent) const {
|
|
os.indent(indent) << "(conformance @" << static_cast<const void *>(this);
|
|
|
|
os << " protocol=";
|
|
getProtocol()->dumpRef(os);
|
|
|
|
if (Loc.isValid()) {
|
|
os << " loc=";
|
|
Loc.dump(getProtocol()->getASTContext().SourceMgr);
|
|
}
|
|
|
|
switch (getKind()) {
|
|
case ConformanceEntryKind::Implied:
|
|
os << " implied_by=@"
|
|
<< static_cast<const void *>(Source.getImpliedSource());
|
|
break;
|
|
|
|
case ConformanceEntryKind::Explicit:
|
|
os << " explicit";
|
|
break;
|
|
|
|
case ConformanceEntryKind::Inherited:
|
|
os << " inherited";
|
|
break;
|
|
|
|
case ConformanceEntryKind::Synthesized:
|
|
os << " synthesized";
|
|
break;
|
|
}
|
|
|
|
if (auto conf = getConformance()) {
|
|
os << " fixed_conformance=@" << static_cast<const void *>(conf);
|
|
}
|
|
|
|
if (SupersededBy)
|
|
os << " superseded_by=@" << static_cast<const void *>(SupersededBy);
|
|
|
|
os << ")\n";
|
|
}
|
|
|
|
void *ConformanceLookupTable::operator new(size_t Bytes,
|
|
ASTContext &C,
|
|
unsigned Alignment) {
|
|
return C.Allocate(Bytes, Alignment);
|
|
}
|
|
|
|
ConformanceLookupTable::ConformanceLookupTable(ASTContext &ctx,
|
|
NominalTypeDecl *nominal,
|
|
LazyResolver *resolver) {
|
|
// Register a cleanup with the ASTContext to call the conformance
|
|
// table destructor.
|
|
ctx.addCleanup([this]() {
|
|
this->destroy();
|
|
});
|
|
}
|
|
|
|
void ConformanceLookupTable::destroy() {
|
|
this->~ConformanceLookupTable();
|
|
}
|
|
|
|
template<typename NominalFunc, typename ExtensionFunc>
|
|
void ConformanceLookupTable::forEachInStage(ConformanceStage stage,
|
|
NominalTypeDecl *nominal,
|
|
LazyResolver *resolver,
|
|
NominalFunc nominalFunc,
|
|
ExtensionFunc extensionFunc) {
|
|
assert(static_cast<unsigned>(stage) < NumConformanceStages &&
|
|
"NumConformanceStages has not been updated");
|
|
LastProcessedEntry &lastProcessed
|
|
= LastProcessed[static_cast<unsigned>(stage)];
|
|
|
|
// Handle the nominal type.
|
|
if (!lastProcessed.getInt()) {
|
|
lastProcessed.setInt(true);
|
|
|
|
// If we have conformances we can load, do so.
|
|
// FIXME: This could be more lazy.
|
|
auto loader = nominal->takeConformanceLoader();
|
|
if (loader.first) {
|
|
SmallVector<ProtocolConformance *, 2> conformances;
|
|
loader.first->loadAllConformances(nominal, loader.second, conformances);
|
|
loadAllConformances(nominal, nominal, conformances);
|
|
} else if (nominal->getParentSourceFile() && resolver) {
|
|
resolver->resolveDeclSignature(nominal);
|
|
}
|
|
|
|
nominalFunc(nominal);
|
|
}
|
|
|
|
// Protocol extensions do not contribute protocol conformances. This
|
|
// is enforced by semantic analysis, so the early exit here is a
|
|
// performance optimization and also prevents us from erroneously
|
|
// including those protocols before they get diagnosed.
|
|
if (isa<ProtocolDecl>(nominal))
|
|
return;
|
|
|
|
// Handle the extensions that we have not yet visited.
|
|
llvm::SetVector<ExtensionDecl *> &delayedExtensionDecls
|
|
= DelayedExtensionDecls[static_cast<unsigned>(stage)];
|
|
nominal->prepareExtensions();
|
|
while (auto next = lastProcessed.getPointer()
|
|
? lastProcessed.getPointer()->NextExtension.getPointer()
|
|
: nominal->FirstExtension) {
|
|
lastProcessed.setPointer(next);
|
|
|
|
// If we have conformances we can load, do so.
|
|
// FIXME: This could be more lazy.
|
|
auto loader = next->takeConformanceLoader();
|
|
if (loader.first) {
|
|
SmallVector<ProtocolConformance *, 2> conformances;
|
|
loader.first->loadAllConformances(next, loader.second, conformances);
|
|
loadAllConformances(nominal, next, conformances);
|
|
} else if (next->getParentSourceFile()) {
|
|
if (!resolver) {
|
|
// We have a parsed extension that we can't resolve well enough to
|
|
// get any information from. Queue it for later.
|
|
// FIXME: Per the comment on DelayedExtensionDecls, this is insane.
|
|
delayedExtensionDecls.insert(next);
|
|
continue;
|
|
}
|
|
|
|
// Resolve this extension.
|
|
delayedExtensionDecls.remove(next);
|
|
resolver->resolveExtension(next);
|
|
}
|
|
|
|
extensionFunc(next);
|
|
}
|
|
|
|
// If we delayed any extension declarations because we didn't have a resolver
|
|
// then, but we have a resolver now, process them.
|
|
if (resolver) {
|
|
while (!delayedExtensionDecls.empty()) {
|
|
// Remove the last extension declaration.
|
|
auto ext = delayedExtensionDecls.back();
|
|
delayedExtensionDecls.remove(ext);
|
|
|
|
resolver->resolveExtension(ext);
|
|
extensionFunc(ext);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ConformanceLookupTable::inheritConformances(ClassDecl *classDecl,
|
|
ClassDecl *superclassDecl,
|
|
ExtensionDecl *superclassExt,
|
|
LazyResolver *resolver) {
|
|
// Local function to return the location of the superclass. This
|
|
// takes a little digging, so compute on first use and cache it.
|
|
SourceLoc superclassLoc;
|
|
auto getSuperclassLoc = [&] {
|
|
if (superclassLoc.isValid())
|
|
return superclassLoc;
|
|
|
|
for (const auto &inherited : classDecl->getInherited()) {
|
|
if (auto inheritedType = inherited.getType()) {
|
|
if (inheritedType->getClassOrBoundGenericClass()) {
|
|
superclassLoc = inherited.getSourceRange().Start;
|
|
return superclassLoc;
|
|
}
|
|
}
|
|
}
|
|
|
|
superclassLoc = superclassDecl->getLoc();
|
|
return superclassLoc;
|
|
};
|
|
|
|
llvm::SmallPtrSet<ProtocolDecl *, 4> protocols;
|
|
auto addInheritedConformance = [&](ConformanceEntry *entry) {
|
|
auto protocol = entry->getProtocol();
|
|
|
|
// Don't add redundant conformances here. This is merely an
|
|
// optimization; resolveConformances() would zap the duplicates
|
|
// anyway.
|
|
if (!protocols.insert(protocol).second)
|
|
return;
|
|
|
|
// Add the inherited entry.
|
|
(void)addProtocol(classDecl, protocol, getSuperclassLoc(),
|
|
ConformanceSource::forInherited(classDecl));
|
|
};
|
|
|
|
// Add inherited conformances.
|
|
DeclContext *superDC = superclassExt;
|
|
if (!superclassExt)
|
|
superDC = superclassDecl;
|
|
|
|
for (auto entry : superclassDecl->ConformanceTable->AllConformances[superDC]){
|
|
addInheritedConformance(entry);
|
|
}
|
|
}
|
|
|
|
void ConformanceLookupTable::updateLookupTable(NominalTypeDecl *nominal,
|
|
ConformanceStage stage,
|
|
LazyResolver *resolver) {
|
|
switch (stage) {
|
|
case ConformanceStage::RecordedExplicit:
|
|
// Record all of the explicit conformances.
|
|
forEachInStage(stage, nominal, resolver,
|
|
[&](NominalTypeDecl *nominal) {
|
|
if (resolver)
|
|
resolver->resolveInheritanceClause(nominal);
|
|
|
|
addProtocols(nominal, nominal->getInherited(),
|
|
ConformanceSource::forExplicit(nominal),
|
|
resolver);
|
|
},
|
|
[&](ExtensionDecl *ext) {
|
|
if (resolver)
|
|
resolver->resolveInheritanceClause(ext);
|
|
|
|
addProtocols(nominal, ext->getInherited(),
|
|
ConformanceSource::forExplicit(ext),
|
|
resolver);
|
|
});
|
|
break;
|
|
|
|
case ConformanceStage::Inherited:
|
|
updateLookupTable(nominal, ConformanceStage::RecordedExplicit, resolver);
|
|
|
|
// For classes, expand implied conformances of the superclass,
|
|
// because an implied conformance in the superclass is considered
|
|
// "fixed" in the subclass.
|
|
if (auto classDecl = dyn_cast<ClassDecl>(nominal)) {
|
|
if (resolver)
|
|
resolver->resolveSuperclass(classDecl);
|
|
|
|
if (Type superclass = classDecl->getSuperclass()) {
|
|
if (auto superclassDecl
|
|
= superclass->getClassOrBoundGenericClass()) {
|
|
// Break infinite recursion when visiting ill-formed classes
|
|
// with circular inheritance.
|
|
if (VisitingSuperclass)
|
|
return;
|
|
llvm::SaveAndRestore<bool> visiting(VisitingSuperclass, true);
|
|
|
|
// Resolve the conformances of the superclass.
|
|
superclassDecl->prepareConformanceTable();
|
|
superclassDecl->ConformanceTable->updateLookupTable(
|
|
superclassDecl,
|
|
ConformanceStage::Resolved,
|
|
resolver);
|
|
|
|
// Expand inherited conformances.
|
|
forEachInStage(stage, superclassDecl, resolver,
|
|
[&](NominalTypeDecl *superclass) {
|
|
inheritConformances(classDecl, superclassDecl,
|
|
nullptr, resolver);
|
|
},
|
|
[&](ExtensionDecl *ext) {
|
|
inheritConformances(classDecl, superclassDecl, ext,
|
|
resolver);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case ConformanceStage::ExpandedImplied:
|
|
// Record explicit conformances and import inherited conformances
|
|
// before expanding.
|
|
updateLookupTable(nominal, ConformanceStage::Inherited, resolver);
|
|
|
|
// Expand inherited conformances.
|
|
forEachInStage(stage, nominal, resolver,
|
|
[&](NominalTypeDecl *nominal) {
|
|
expandImpliedConformances(nominal, nominal, resolver);
|
|
},
|
|
[&](ExtensionDecl *ext) {
|
|
expandImpliedConformances(nominal, ext, resolver);
|
|
});
|
|
break;
|
|
|
|
case ConformanceStage::Resolved:
|
|
// Expand inherited conformances so we have the complete set of
|
|
// conformances.
|
|
updateLookupTable(nominal, ConformanceStage::ExpandedImplied, resolver);
|
|
|
|
/// Determine whether any extensions were added that might require
|
|
/// us to compute conformances again.
|
|
bool anyChanged = false;
|
|
forEachInStage(stage, nominal, resolver,
|
|
[&](NominalTypeDecl *nominal) {
|
|
anyChanged = true;
|
|
},
|
|
[&](ExtensionDecl *ext) {
|
|
anyChanged = true;
|
|
});
|
|
|
|
if (anyChanged) {
|
|
// Compute the conformances for each protocol.
|
|
bool anySuperseded = false;
|
|
for (const auto &entry : Conformances) {
|
|
if (resolveConformances(nominal, entry.first, resolver))
|
|
anySuperseded = true;
|
|
}
|
|
|
|
if (anySuperseded) {
|
|
// Update the lists of all conformances to remove superseded
|
|
// conformances.
|
|
for (auto &conformances : AllConformances) {
|
|
conformances.second.erase(
|
|
std::remove_if(conformances.second.begin(),
|
|
conformances.second.end(),
|
|
[&](ConformanceEntry *entry) {
|
|
return entry->isSuperseded();
|
|
}),
|
|
conformances.second.end());
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
void ConformanceLookupTable::loadAllConformances(
|
|
NominalTypeDecl *nominal,
|
|
DeclContext *dc,
|
|
ArrayRef<ProtocolConformance*> conformances) {
|
|
// If this declaration context came from source, there's nothing to
|
|
// do here.
|
|
if (dc->getParentSourceFile())
|
|
return;
|
|
|
|
// Add entries for each loaded conformance.
|
|
for (auto conformance : conformances) {
|
|
registerProtocolConformance(conformance);
|
|
}
|
|
}
|
|
|
|
namespace {
|
|
/// Visit the protocols referenced by the given type, which was
|
|
/// uttered at the given location.
|
|
template<typename AddProtocolFunc>
|
|
void visitProtocols(Type type, SourceLoc loc, AddProtocolFunc addProtocol) {
|
|
if (!type) return;
|
|
|
|
// Protocol types.
|
|
if (auto protocol = type->getAs<ProtocolType>()) {
|
|
addProtocol(protocol->getDecl(), loc);
|
|
return;
|
|
}
|
|
|
|
// Protocol compositions.
|
|
if (auto composition = type->getAs<ProtocolCompositionType>()) {
|
|
for (auto protocol : composition->getProtocols())
|
|
visitProtocols(protocol, loc, addProtocol);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool ConformanceLookupTable::addProtocol(NominalTypeDecl *nominal,
|
|
ProtocolDecl *protocol, SourceLoc loc,
|
|
ConformanceSource source) {
|
|
DeclContext *dc = source.getDeclContext();
|
|
ASTContext &ctx = dc->getASTContext();
|
|
|
|
// Determine the kind of conformance.
|
|
ConformanceEntryKind kind = source.getKind();
|
|
|
|
// If this entry is synthesized or implied, scan to determine
|
|
// whether there are any explicit better conformances that make this
|
|
// conformance trivially superseded (and, therefore, no worth
|
|
// recording).
|
|
auto &conformanceEntries = Conformances[protocol];
|
|
if (kind == ConformanceEntryKind::Implied ||
|
|
kind == ConformanceEntryKind::Synthesized) {
|
|
for (const auto *existingEntry : conformanceEntries) {
|
|
switch (existingEntry->getKind()) {
|
|
case ConformanceEntryKind::Explicit:
|
|
case ConformanceEntryKind::Inherited:
|
|
return false;
|
|
|
|
case ConformanceEntryKind::Implied:
|
|
// An implied conformance is better than a synthesized one.
|
|
if (kind == ConformanceEntryKind::Synthesized)
|
|
return false;
|
|
break;
|
|
|
|
case ConformanceEntryKind::Synthesized:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Build the conformance entry (if it hasn't been built before).
|
|
ConformanceEntry *entry = new (ctx) ConformanceEntry(loc, protocol, source);
|
|
conformanceEntries.push_back(entry);
|
|
|
|
// Record this as a conformance within the given declaration
|
|
// context.
|
|
AllConformances[dc].push_back(entry);
|
|
|
|
return true;
|
|
}
|
|
|
|
void ConformanceLookupTable::addProtocols(NominalTypeDecl *nominal,
|
|
ArrayRef<TypeLoc> inherited,
|
|
ConformanceSource source,
|
|
LazyResolver *resolver) {
|
|
// Visit each of the types in the inheritance list to find
|
|
// protocols.
|
|
for (const auto &entry : inherited) {
|
|
visitProtocols(entry.getType(), entry.getLoc(),
|
|
[&](ProtocolDecl *protocol, SourceLoc loc) {
|
|
addProtocol(nominal, protocol, loc, source);
|
|
});
|
|
}
|
|
}
|
|
|
|
void ConformanceLookupTable::expandImpliedConformances(NominalTypeDecl *nominal,
|
|
DeclContext *dc,
|
|
LazyResolver *resolver) {
|
|
// Note: recursive type-checking implies that that AllConformances
|
|
// may be reallocated during this traversal, so pay the lookup cost
|
|
// during each iteration.
|
|
for (unsigned i = 0; i != AllConformances[dc].size(); ++i) {
|
|
ConformanceEntry *conformanceEntry = AllConformances[dc][i];
|
|
ProtocolDecl *conformingProtocol = conformanceEntry->getProtocol();
|
|
|
|
// Visit the protocols inherited by this protocol, adding them as
|
|
// implied conformances.
|
|
if (resolver) {
|
|
if (nominal == dc)
|
|
resolver->resolveInheritanceClause(nominal);
|
|
else
|
|
resolver->resolveInheritanceClause(cast<ExtensionDecl>(dc));
|
|
}
|
|
|
|
// An @objc enum that explicitly conforms to the ErrorType protocol also
|
|
// implicitly conforms to _ObjectiveCBridgeableErrorType, via the known
|
|
// protocol _BridgedNSError.
|
|
if (conformingProtocol->isSpecificProtocol(KnownProtocolKind::ErrorType) &&
|
|
isa<EnumDecl>(nominal) && nominal->isObjC() &&
|
|
cast<EnumDecl>(nominal)->hasOnlyCasesWithoutAssociatedValues()) {
|
|
ASTContext &ctx = nominal->getASTContext();
|
|
if (auto bridgedNSError
|
|
= ctx.getProtocol(KnownProtocolKind::_BridgedNSError)) {
|
|
addProtocol(nominal, bridgedNSError, SourceLoc(),
|
|
ConformanceSource::forImplied(conformanceEntry));
|
|
}
|
|
}
|
|
|
|
// Add inherited protocols.
|
|
addProtocols(nominal, conformingProtocol->getInherited(),
|
|
ConformanceSource::forImplied(conformanceEntry),
|
|
resolver);
|
|
}
|
|
}
|
|
|
|
/// Determine whether the given conformance entry kind can be replaced.
|
|
static bool isReplaceable(ConformanceEntryKind kind) {
|
|
switch (kind) {
|
|
case ConformanceEntryKind::Implied:
|
|
case ConformanceEntryKind::Synthesized:
|
|
return true;
|
|
|
|
case ConformanceEntryKind::Explicit:
|
|
case ConformanceEntryKind::Inherited:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
ConformanceLookupTable::Ordering ConformanceLookupTable::compareConformances(
|
|
ConformanceEntry *lhs,
|
|
ConformanceEntry *rhs,
|
|
bool &diagnoseSuperseded) {
|
|
// If one entry is fixed and the other is not, we have our answer.
|
|
if (lhs->isFixed() != rhs->isFixed()) {
|
|
// If the non-fixed conformance is not replacable, we have a failure to
|
|
// diagnose.
|
|
diagnoseSuperseded = (lhs->isFixed() &&
|
|
!isReplaceable(rhs->getRankingKind())) ||
|
|
(rhs->isFixed() &&
|
|
!isReplaceable(lhs->getRankingKind()));
|
|
|
|
return lhs->isFixed() ? Ordering::Before : Ordering::After;
|
|
}
|
|
|
|
ConformanceEntryKind lhsKind = lhs->getRankingKind();
|
|
ConformanceEntryKind rhsKind = rhs->getRankingKind();
|
|
|
|
if (lhsKind != ConformanceEntryKind::Implied ||
|
|
rhsKind != ConformanceEntryKind::Implied) {
|
|
// If both conformances are non-replaceable, diagnose the
|
|
// superseded one.
|
|
diagnoseSuperseded = !isReplaceable(lhsKind) && !isReplaceable(rhsKind) &&
|
|
!(lhsKind == ConformanceEntryKind::Inherited &&
|
|
rhsKind == ConformanceEntryKind::Inherited);
|
|
|
|
// If we can order by kind, do so.
|
|
if (lhsKind != rhsKind) {
|
|
return (static_cast<unsigned>(lhsKind) < static_cast<unsigned>(rhsKind))
|
|
? Ordering::Before
|
|
: Ordering::After;
|
|
}
|
|
|
|
// We shouldn't get two synthesized conformances. It's not harmful
|
|
// per se, but it's indicative of redundant logic in the frontend.
|
|
assert((lhs->getKind() != ConformanceEntryKind::Synthesized ||
|
|
rhs->getKind() != ConformanceEntryKind::Synthesized) &&
|
|
"Shouldn't ever get two truly synthesized conformances");
|
|
|
|
// FIXME: Deterministic ordering.
|
|
return Ordering::Before;
|
|
}
|
|
|
|
// Both the left- and right-hand sides are implied, so determine where the
|
|
// conformance should go.
|
|
assert(lhsKind == ConformanceEntryKind::Implied &&
|
|
"Expected implied conformance");
|
|
assert(rhsKind == ConformanceEntryKind::Implied &&
|
|
"Expected implied conformance");
|
|
diagnoseSuperseded = false;
|
|
|
|
// First, try to use the stated explicit conformances to determine where the
|
|
// conformance should go.
|
|
auto lhsExplicit = lhs->getDeclaredConformance();
|
|
auto lhsExplicitProtocol = lhsExplicit->getProtocol();
|
|
auto rhsExplicit = rhs->getDeclaredConformance();
|
|
auto rhsExplicitProtocol = rhsExplicit->getProtocol();
|
|
if (lhsExplicitProtocol != rhsExplicitProtocol) {
|
|
// 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 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 the two conformances come from the same file, pick the first context
|
|
// in the file.
|
|
auto lhsSF = lhs->getDeclContext()->getParentSourceFile();
|
|
auto rhsSF = rhs->getDeclContext()->getParentSourceFile();
|
|
if (lhsSF && lhsSF == rhsSF) {
|
|
ASTContext &ctx = lhsSF->getASTContext();
|
|
return ctx.SourceMgr.isBeforeInBuffer(lhs->getDeclaredLoc(),
|
|
rhs->getDeclaredLoc())
|
|
? Ordering::Before
|
|
: Ordering::After;
|
|
}
|
|
|
|
// Otherwise, pick the earlier file unit.
|
|
auto lhsFileUnit
|
|
= dyn_cast<FileUnit>(lhs->getDeclContext()->getModuleScopeContext());
|
|
auto rhsFileUnit
|
|
= dyn_cast<FileUnit>(rhs->getDeclContext()->getModuleScopeContext());
|
|
assert(lhsFileUnit && rhsFileUnit && "Not from a file unit?");
|
|
if (lhsFileUnit == rhsFileUnit) {
|
|
// If the file units are the same, just pick arbitrarily; we're not
|
|
// actually emitting anything.
|
|
// FIXME: Only because we're synthesizing conformances for deserialized
|
|
// protocols. Once that's no longer true (because we're serializing
|
|
// everything appropriately in the module), we should assert that this
|
|
// does not happen.
|
|
assert(!lhsSF && !rhsSF && "Source files shouldn't conflict");
|
|
return Ordering::Before;
|
|
}
|
|
auto module = lhs->getDeclContext()->getParentModule();
|
|
assert(lhs->getDeclContext()->getParentModule()
|
|
== rhs->getDeclContext()->getParentModule() &&
|
|
"conformances should be in the same module");
|
|
for (auto file : module->getFiles()) {
|
|
if (file == lhsFileUnit)
|
|
return Ordering::Before;
|
|
if (file == rhsFileUnit)
|
|
return Ordering::After;
|
|
}
|
|
|
|
llvm_unreachable("files weren't in the parent module?");
|
|
}
|
|
|
|
bool ConformanceLookupTable::resolveConformances(NominalTypeDecl *nominal,
|
|
ProtocolDecl *protocol,
|
|
LazyResolver *resolver) {
|
|
// Find any entries that are superseded by other entries.
|
|
ConformanceEntries &entries = Conformances[protocol];
|
|
llvm::SmallPtrSet<DeclContext *, 4> knownConformances;
|
|
bool anySuperseded = false;
|
|
for (auto entry : entries) {
|
|
// If this entry has a conformance associated with it, note that.
|
|
if (entry->getConformance())
|
|
knownConformances.insert(entry->getDeclContext());
|
|
|
|
// If this entry was superseded, move on.
|
|
if (entry->isSuperseded()) {
|
|
anySuperseded = true;
|
|
continue;
|
|
}
|
|
|
|
// Determine whether this entry is superseded by (or supersedes)
|
|
// some other entry.
|
|
for (auto otherEntry : entries) {
|
|
if (entry == otherEntry)
|
|
continue;
|
|
|
|
if (otherEntry->isSuperseded()) {
|
|
anySuperseded = true;
|
|
continue;
|
|
}
|
|
|
|
bool diagnoseSuperseded = false;
|
|
bool doneWithEntry = false;
|
|
switch (compareConformances(entry, otherEntry, diagnoseSuperseded)) {
|
|
case Ordering::Equivalent:
|
|
break;
|
|
|
|
case Ordering::Before:
|
|
otherEntry->markSupersededBy(*this, entry, diagnoseSuperseded);
|
|
anySuperseded = true;
|
|
break;
|
|
|
|
case Ordering::After:
|
|
entry->markSupersededBy(*this, otherEntry, diagnoseSuperseded);
|
|
anySuperseded = true;
|
|
doneWithEntry = true;
|
|
break;
|
|
}
|
|
|
|
if (doneWithEntry)
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If any entries were superseded, remove them now.
|
|
if (anySuperseded) {
|
|
entries.erase(std::remove_if(entries.begin(), entries.end(),
|
|
[&](ConformanceEntry *entry) {
|
|
return entry->isSuperseded();
|
|
}),
|
|
entries.end());
|
|
}
|
|
|
|
return anySuperseded;
|
|
}
|
|
|
|
DeclContext *ConformanceLookupTable::getConformingContext(
|
|
NominalTypeDecl *nominal,
|
|
LazyResolver *resolver,
|
|
ConformanceEntry *entry) {
|
|
ProtocolDecl *protocol = entry->getProtocol();
|
|
|
|
// Dig through the inherited entries to find a non-inherited one.
|
|
// Handle recursive inheritance.
|
|
SmallPtrSet<ClassDecl *, 4> visited;
|
|
while (entry->getKind() == ConformanceEntryKind::Inherited) {
|
|
// Make sure we have an up-to-date conformance table for the
|
|
// superclass.
|
|
auto classDecl = cast<ClassDecl>(nominal);
|
|
if (!visited.insert(classDecl).second)
|
|
return nullptr;
|
|
|
|
// If we had a circular dependency, the superclass may not exist.
|
|
if (!classDecl->getSuperclass())
|
|
return nullptr;
|
|
|
|
auto superclassDecl
|
|
= classDecl->getSuperclass()->getClassOrBoundGenericClass();
|
|
|
|
if (!classDecl->ConformanceTable->VisitingSuperclass) {
|
|
llvm::SaveAndRestore<bool> visiting(
|
|
classDecl->ConformanceTable
|
|
->VisitingSuperclass,
|
|
true);
|
|
|
|
superclassDecl->prepareConformanceTable();
|
|
superclassDecl->ConformanceTable->resolveConformances(superclassDecl,
|
|
protocol,
|
|
resolver);
|
|
}
|
|
|
|
// Grab the superclass entry and continue searching for a
|
|
// non-inherited conformance.
|
|
// FIXME: Ambiguity detection and resolution.
|
|
entry = superclassDecl->ConformanceTable->Conformances[protocol].front();
|
|
nominal = superclassDecl;
|
|
}
|
|
|
|
return entry->getDeclContext();
|
|
}
|
|
|
|
ProtocolConformance *ConformanceLookupTable::getConformance(
|
|
NominalTypeDecl *nominal,
|
|
LazyResolver *resolver,
|
|
ConformanceEntry *entry) {
|
|
// If we already have a conformance, we're done.
|
|
if (auto conformance = entry->getConformance())
|
|
return conformance;
|
|
|
|
ProtocolDecl *protocol = entry->getProtocol();
|
|
|
|
// Determine where the explicit conformance actually lives.
|
|
// FIXME: This is a hack to ensure that inherited conformances are
|
|
// always "single step", which is bad for resilience but is assumed
|
|
// elsewhere in the compiler.
|
|
DeclContext *conformingDC = getConformingContext(nominal, resolver, entry);
|
|
if (!conformingDC)
|
|
return nullptr;
|
|
|
|
NominalTypeDecl *conformingNominal
|
|
= conformingDC->isNominalTypeOrNominalTypeExtensionContext();
|
|
|
|
// Form the conformance.
|
|
Type type = entry->getDeclContext()->getDeclaredTypeInContext();
|
|
ProtocolConformance *conformance;
|
|
ASTContext &ctx = nominal->getASTContext();
|
|
if (entry->getKind() == ConformanceEntryKind::Inherited) {
|
|
// For an inherited conformance, the conforming nominal type will
|
|
// be different from the nominal type.
|
|
assert(conformingNominal != nominal && "Broken inherited conformance");
|
|
|
|
// Find the superclass type that matches where the conformance was
|
|
// declared.
|
|
Type superclassTy = type->getSuperclass(resolver);
|
|
while (superclassTy->getAnyNominal() != conformingNominal)
|
|
superclassTy = superclassTy->getSuperclass(resolver);
|
|
|
|
// Look up the inherited conformance.
|
|
Module *module = entry->getDeclContext()->getParentModule();
|
|
auto inheritedConformance = module->lookupConformance(superclassTy,
|
|
protocol,
|
|
resolver)
|
|
.getPointer();
|
|
|
|
// Form the inherited conformance.
|
|
conformance = ctx.getInheritedConformance(type, inheritedConformance);
|
|
} else {
|
|
// Create or find the normal conformance.
|
|
Type conformingType = conformingDC->getDeclaredTypeInContext();
|
|
SourceLoc conformanceLoc
|
|
= conformingNominal == conformingDC
|
|
? conformingNominal->getLoc()
|
|
: cast<ExtensionDecl>(conformingDC)->getLoc();
|
|
|
|
conformance = ctx.getConformance(conformingType, protocol, conformanceLoc,
|
|
conformingDC,
|
|
ProtocolConformanceState::Incomplete);
|
|
}
|
|
|
|
// Record the conformance.
|
|
entry->Conformance = conformance;
|
|
return conformance;
|
|
}
|
|
|
|
void ConformanceLookupTable::addSynthesizedConformance(NominalTypeDecl *nominal,
|
|
ProtocolDecl *protocol) {
|
|
addProtocol(nominal, protocol, nominal->getLoc(),
|
|
ConformanceSource::forSynthesized(nominal));
|
|
}
|
|
|
|
void ConformanceLookupTable::registerProtocolConformance(
|
|
ProtocolConformance *conformance) {
|
|
auto protocol = conformance->getProtocol();
|
|
auto dc = conformance->getDeclContext();
|
|
auto nominal = dc->isNominalTypeOrNominalTypeExtensionContext();
|
|
|
|
// If there is an entry to update, do so.
|
|
auto &dcConformances = AllConformances[dc];
|
|
for (auto entry : dcConformances) {
|
|
if (entry->getProtocol() == protocol) {
|
|
assert(!entry->getConformance() ||
|
|
entry->getConformance() == conformance &&
|
|
"Mismatched conformances");
|
|
entry->Conformance = conformance;
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Otherwise, add a new entry.
|
|
auto inherited = dyn_cast<InheritedProtocolConformance>(conformance);
|
|
ConformanceSource source
|
|
= inherited ? ConformanceSource::forInherited(cast<ClassDecl>(nominal))
|
|
: ConformanceSource::forExplicit(dc);
|
|
|
|
ASTContext &ctx = nominal->getASTContext();
|
|
ConformanceEntry *entry = new (ctx) ConformanceEntry(SourceLoc(),
|
|
protocol,
|
|
source);
|
|
entry->Conformance = conformance;
|
|
|
|
// Record that this type conforms to the given protocol.
|
|
Conformances[protocol].push_back(entry);
|
|
|
|
// Record this as a conformance within the given declaration
|
|
// context.
|
|
dcConformances.push_back(entry);
|
|
}
|
|
|
|
bool ConformanceLookupTable::lookupConformance(
|
|
Module *module,
|
|
NominalTypeDecl *nominal,
|
|
ProtocolDecl *protocol,
|
|
LazyResolver *resolver,
|
|
SmallVectorImpl<ProtocolConformance *> &conformances) {
|
|
// Update to record all explicit and inherited conformances.
|
|
updateLookupTable(nominal, ConformanceStage::Inherited, resolver);
|
|
|
|
// Look for conformances to this protocol.
|
|
auto known = Conformances.find(protocol);
|
|
if (known == Conformances.end()) {
|
|
// If we didn't find anything, expand implied conformances.
|
|
updateLookupTable(nominal, ConformanceStage::ExpandedImplied, resolver);
|
|
known = Conformances.find(protocol);
|
|
|
|
// We didn't find anything.
|
|
if (known == Conformances.end())
|
|
return false;
|
|
}
|
|
|
|
// Resolve the conformances for this protocol.
|
|
resolveConformances(nominal, protocol, resolver);
|
|
for (auto entry : Conformances[protocol]) {
|
|
if (auto conformance = getConformance(nominal, resolver, entry)) {
|
|
conformances.push_back(conformance);
|
|
}
|
|
}
|
|
return !conformances.empty();
|
|
}
|
|
|
|
void ConformanceLookupTable::lookupConformances(
|
|
NominalTypeDecl *nominal,
|
|
DeclContext *dc,
|
|
LazyResolver *resolver,
|
|
ConformanceLookupKind lookupKind,
|
|
SmallVectorImpl<ProtocolDecl *> *protocols,
|
|
SmallVectorImpl<ProtocolConformance *> *conformances,
|
|
SmallVectorImpl<ConformanceDiagnostic> *diagnostics) {
|
|
// We need to expand all implied conformances before we can find
|
|
// those conformances that pertain to this declaration context.
|
|
updateLookupTable(nominal, ConformanceStage::ExpandedImplied, resolver);
|
|
|
|
/// Resolve conformances for each of the protocols to which this
|
|
/// declaration may provide a conformance. Only some of these will
|
|
/// result in conformances that are attributed to this declaration
|
|
/// context.
|
|
auto &potentialConformances = AllConformances[dc];
|
|
for (const auto &potential : potentialConformances) {
|
|
resolveConformances(nominal, potential->getProtocol(), resolver);
|
|
}
|
|
|
|
// Remove any superseded conformances from AllConformances.
|
|
potentialConformances.erase(
|
|
std::remove_if(potentialConformances.begin(),
|
|
potentialConformances.end(),
|
|
[&](ConformanceEntry *entry) {
|
|
if (entry->isSuperseded())
|
|
return true;
|
|
|
|
// If we are to filter out this result, do so now.
|
|
if (lookupKind == ConformanceLookupKind::OnlyExplicit &&
|
|
entry->getKind() != ConformanceEntryKind::Explicit &&
|
|
entry->getKind() != ConformanceEntryKind::Synthesized)
|
|
return false;
|
|
|
|
// Record the protocol.
|
|
if (protocols)
|
|
protocols->push_back(entry->getProtocol());
|
|
|
|
// Record the conformance.
|
|
if (conformances) {
|
|
if (auto conformance = getConformance(nominal, resolver,
|
|
entry))
|
|
conformances->push_back(conformance);
|
|
}
|
|
return false;
|
|
}),
|
|
potentialConformances.end());
|
|
|
|
// Gather any diagnostics we've produced.
|
|
if (diagnostics) {
|
|
auto knownDiags = AllSupersededDiagnostics.find(dc);
|
|
if (knownDiags != AllSupersededDiagnostics.end()) {
|
|
for (const auto *entry : knownDiags->second) {
|
|
ConformanceEntry *supersededBy = entry->getSupersededBy();
|
|
|
|
diagnostics->push_back({entry->getProtocol(),
|
|
entry->getDeclaredLoc(),
|
|
entry->getKind(),
|
|
entry->getDeclaredConformance()->getProtocol(),
|
|
supersededBy->getDeclContext(),
|
|
supersededBy->getKind(),
|
|
supersededBy->getDeclaredConformance()
|
|
->getProtocol()});
|
|
}
|
|
|
|
// We have transferred these diagnostics; erase them.
|
|
AllSupersededDiagnostics.erase(knownDiags);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ConformanceLookupTable::getAllProtocols(
|
|
NominalTypeDecl *nominal,
|
|
LazyResolver *resolver,
|
|
SmallVectorImpl<ProtocolDecl *> &scratch) {
|
|
// We need to expand all implied conformances to find the complete
|
|
// set of protocols to which this nominal type conforms.
|
|
updateLookupTable(nominal, ConformanceStage::ExpandedImplied, resolver);
|
|
|
|
// Gather all of the protocols.
|
|
for (const auto &conformance : Conformances) {
|
|
if (conformance.second.empty())
|
|
continue;
|
|
|
|
scratch.push_back(conformance.first);
|
|
}
|
|
|
|
// FIXME: sort the protocols in some canonical order?
|
|
}
|
|
|
|
int ConformanceLookupTable::compareProtocolConformances(
|
|
ProtocolConformance * const *lhsPtr,
|
|
ProtocolConformance * const *rhsPtr) {
|
|
ProtocolConformance *lhs = *lhsPtr;
|
|
ProtocolConformance *rhs = *rhsPtr;
|
|
|
|
// If the two conformances are normal conformances with locations,
|
|
// sort by location.
|
|
if (auto lhsNormal = dyn_cast<NormalProtocolConformance>(lhs)) {
|
|
if (auto rhsNormal = dyn_cast<NormalProtocolConformance>(rhs)) {
|
|
if (lhsNormal->getLoc().isValid() && rhsNormal->getLoc().isValid()) {
|
|
ASTContext &ctx = lhs->getDeclContext()->getASTContext();
|
|
unsigned lhsBuffer
|
|
= ctx.SourceMgr.findBufferContainingLoc(lhsNormal->getLoc());
|
|
unsigned rhsBuffer
|
|
= ctx.SourceMgr.findBufferContainingLoc(rhsNormal->getLoc());
|
|
|
|
// If the buffers are the same, use source location ordering.
|
|
if (lhsBuffer == rhsBuffer) {
|
|
return ctx.SourceMgr.isBeforeInBuffer(lhsNormal->getLoc(),
|
|
rhsNormal->getLoc());
|
|
}
|
|
|
|
// Otherwise, order by buffer identifier.
|
|
return StringRef(ctx.SourceMgr.getIdentifierForBuffer(lhsBuffer))
|
|
.compare(ctx.SourceMgr.getIdentifierForBuffer(rhsBuffer));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Otherwise, sort by protocol.
|
|
ProtocolDecl *lhsProto = lhs->getProtocol();
|
|
ProtocolDecl *rhsProto = rhs->getProtocol();
|
|
return ProtocolType::compareProtocols(&lhsProto, &rhsProto);
|
|
}
|
|
|
|
void ConformanceLookupTable::getAllConformances(
|
|
NominalTypeDecl *nominal,
|
|
LazyResolver *resolver,
|
|
bool sorted,
|
|
SmallVectorImpl<ProtocolConformance *> &scratch) {
|
|
// We need to expand and resolve all conformances to enumerate them.
|
|
updateLookupTable(nominal, ConformanceStage::Resolved, resolver);
|
|
|
|
// Gather all of the protocols.
|
|
for (const auto &conformance : AllConformances) {
|
|
for (auto entry : conformance.second) {
|
|
if (auto conformance = getConformance(nominal, resolver, entry))
|
|
scratch.push_back(conformance);
|
|
}
|
|
}
|
|
|
|
// If requested, sort the results.
|
|
if (sorted) {
|
|
llvm::array_pod_sort(scratch.begin(), scratch.end(),
|
|
&compareProtocolConformances);
|
|
}
|
|
}
|
|
|
|
void ConformanceLookupTable::getImplicitProtocols(
|
|
NominalTypeDecl *nominal,
|
|
SmallVectorImpl<ProtocolDecl *> &protocols) {
|
|
for (auto conformance : AllConformances[nominal]) {
|
|
if (conformance->getKind() == ConformanceEntryKind::Synthesized) {
|
|
protocols.push_back(conformance->getProtocol());
|
|
}
|
|
}
|
|
}
|
|
|
|
ArrayRef<ValueDecl *>
|
|
ConformanceLookupTable::getSatisfiedProtocolRequirementsForMember(
|
|
const ValueDecl *member,
|
|
NominalTypeDecl *nominal,
|
|
LazyResolver *resolver,
|
|
bool sorted) {
|
|
auto It = ConformingDeclMap.find(member);
|
|
if (It != ConformingDeclMap.end())
|
|
return It->second;
|
|
|
|
SmallVector<ProtocolConformance *, 4> result;
|
|
getAllConformances(nominal, resolver, sorted, result);
|
|
|
|
auto &reqs = ConformingDeclMap[member];
|
|
if (isa<TypeDecl>(member)) {
|
|
for (auto *conf : result) {
|
|
if (conf->isInvalid())
|
|
continue;
|
|
|
|
conf->forEachTypeWitness(resolver, [&](const AssociatedTypeDecl *assoc,
|
|
const Substitution &subst,
|
|
TypeDecl *typeDecl) -> bool {
|
|
if (typeDecl == member)
|
|
reqs.push_back(const_cast<AssociatedTypeDecl*>(assoc));
|
|
return false;
|
|
});
|
|
}
|
|
} else {
|
|
for (auto *conf : result) {
|
|
if (conf->isInvalid())
|
|
continue;
|
|
|
|
conf->forEachValueWitness(resolver, [&](ValueDecl *req,
|
|
ConcreteDeclRef witness) {
|
|
if (witness.getDecl() == member)
|
|
reqs.push_back(req);
|
|
});
|
|
}
|
|
}
|
|
|
|
return reqs;
|
|
}
|
|
|
|
void ConformanceLookupTable::dump() const {
|
|
dump(llvm::errs());
|
|
}
|
|
|
|
void ConformanceLookupTable::dump(raw_ostream &os) const {
|
|
for (const auto &dcEntries : AllConformances) {
|
|
os << "Conformances in context:\n";
|
|
dcEntries.first->printContext(os);
|
|
for (auto entry : dcEntries.second) {
|
|
entry->dump(os);
|
|
}
|
|
}
|
|
}
|
|
|