Skip to content

[5.0] Allow Error to conform to itself #21073

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
3 changes: 3 additions & 0 deletions include/swift/AST/Decl.h
Original file line number Diff line number Diff line change
Expand Up @@ -3975,6 +3975,9 @@ class ProtocolDecl final : public NominalTypeDecl {
->existentialConformsToSelfSlow();
}

/// Does this protocol require a self-conformance witness table?
bool requiresSelfConformanceWitnessTable() const;

/// Find direct Self references within the given requirement.
///
/// \param allowCovariantParameters If true, 'Self' is assumed to be
Expand Down
5 changes: 5 additions & 0 deletions include/swift/AST/Module.h
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,11 @@ class ModuleDecl : public DeclContext, public TypeDecl {
Optional<ProtocolConformanceRef>
lookupConformance(Type type, ProtocolDecl *protocol);

/// Look for the conformance of the given existential type to the given
/// protocol.
Optional<ProtocolConformanceRef>
lookupExistentialConformance(Type type, ProtocolDecl *protocol);

/// Find a member named \p name in \p container that was declared in this
/// module.
///
Expand Down
11 changes: 9 additions & 2 deletions lib/AST/Decl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3863,15 +3863,22 @@ bool ProtocolDecl::requiresClassSlow() {
return Bits.ProtocolDecl.RequiresClass;
}

bool ProtocolDecl::requiresSelfConformanceWitnessTable() const {
return isSpecificProtocol(KnownProtocolKind::Error);
}

bool ProtocolDecl::existentialConformsToSelfSlow() {
// Assume for now that the existential conforms to itself; this
// prevents circularity issues.
Bits.ProtocolDecl.ExistentialConformsToSelfValid = true;
Bits.ProtocolDecl.ExistentialConformsToSelf = true;

// If it's not @objc, it conforms to itself only if it has a
// self-conformance witness table.
if (!isObjC()) {
Bits.ProtocolDecl.ExistentialConformsToSelf = false;
return false;
bool hasSelfConformance = requiresSelfConformanceWitnessTable();
Bits.ProtocolDecl.ExistentialConformsToSelf = hasSelfConformance;
return hasSelfConformance;
}

// Check whether this protocol conforms to itself.
Expand Down
110 changes: 63 additions & 47 deletions lib/AST/Module.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -570,6 +570,66 @@ void ModuleDecl::getDisplayDecls(SmallVectorImpl<Decl*> &Results) const {
FORWARD(getDisplayDecls, (Results));
}

Optional<ProtocolConformanceRef>
ModuleDecl::lookupExistentialConformance(Type type, ProtocolDecl *protocol) {
assert(type->isExistentialType());

// If the existential type cannot be represented or the protocol does not
// conform to itself, there's no point in looking further.
if (!protocol->existentialConformsToSelf())
return None;

auto layout = type->getExistentialLayout();

// Due to an IRGen limitation, witness tables cannot be passed from an
// existential to an archetype parameter, so for now we restrict this to
// @objc protocols.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment is out of date?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It certainly needs updating, yeah. I'll do that on master.

if (!layout.isObjC()) {
// There's a specific exception for protocols with self-conforming
// witness tables, but the existential has to be *exactly* that type.
// TODO: synthesize witness tables on-demand for protocol compositions
// that can satisfy the requirement.
if (protocol->requiresSelfConformanceWitnessTable() &&
type->is<ProtocolType>() &&
type->castTo<ProtocolType>()->getDecl() == protocol)
return ProtocolConformanceRef(protocol);

return None;
}

// If the existential is class-constrained, the class might conform
// concretely.
if (auto superclass = layout.explicitSuperclass) {
if (auto result = lookupConformance(superclass, protocol))
return result;
}

// Otherwise, the existential might conform abstractly.
for (auto proto : layout.getProtocols()) {
auto *protoDecl = proto->getDecl();

// If we found the protocol we're looking for, return an abstract
// conformance to it.
if (protoDecl == protocol)
return ProtocolConformanceRef(protocol);

// If the protocol has a superclass constraint, we might conform
// concretely.
if (auto superclass = protoDecl->getSuperclass()) {
if (auto result = lookupConformance(superclass, protocol))
return result;
}

// Now check refined protocols.
if (protoDecl->inheritsFrom(protocol))
return ProtocolConformanceRef(protocol);
}

// We didn't find our protocol in the existential's list; it doesn't
// conform.
return None;
}

Optional<ProtocolConformanceRef>
ModuleDecl::lookupConformance(Type type, ProtocolDecl *protocol) {
ASTContext &ctx = getASTContext();
Expand Down Expand Up @@ -609,52 +669,8 @@ ModuleDecl::lookupConformance(Type type, ProtocolDecl *protocol) {
// An existential conforms to a protocol if the protocol is listed in the
// existential's list of conformances and the existential conforms to
// itself.
if (type->isExistentialType()) {
// If the existential type cannot be represented or the protocol does not
// conform to itself, there's no point in looking further.
if (!protocol->existentialConformsToSelf())
return None;

auto layout = type->getExistentialLayout();

// Due to an IRGen limitation, witness tables cannot be passed from an
// existential to an archetype parameter, so for now we restrict this to
// @objc protocols.
if (!layout.isObjC())
return None;

// If the existential is class-constrained, the class might conform
// concretely.
if (auto superclass = layout.explicitSuperclass) {
if (auto result = lookupConformance(superclass, protocol))
return result;
}

// Otherwise, the existential might conform abstractly.
for (auto proto : layout.getProtocols()) {
auto *protoDecl = proto->getDecl();

// If we found the protocol we're looking for, return an abstract
// conformance to it.
if (protoDecl == protocol)
return ProtocolConformanceRef(protocol);

// If the protocol has a superclass constraint, we might conform
// concretely.
if (auto superclass = protoDecl->getSuperclass()) {
if (auto result = lookupConformance(superclass, protocol))
return result;
}

// Now check refined protocols.
if (protoDecl->inheritsFrom(protocol))
return ProtocolConformanceRef(protocol);
}

// We didn't find our protocol in the existential's list; it doesn't
// conform.
return None;
}
if (type->isExistentialType())
return lookupExistentialConformance(type, protocol);

// Type variables have trivial conformances.
if (type->isTypeVariableOrMember())
Expand All @@ -669,7 +685,7 @@ ModuleDecl::lookupConformance(Type type, ProtocolDecl *protocol) {
auto nominal = type->getAnyNominal();

// If we don't have a nominal type, there are no conformances.
if (!nominal) return None;
if (!nominal || isa<ProtocolDecl>(nominal)) return None;

// Find the (unspecialized) conformance.
SmallVector<ProtocolConformance *, 2> conformances;
Expand Down
21 changes: 13 additions & 8 deletions lib/AST/ProtocolConformance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -112,14 +112,16 @@ ProtocolConformanceRef::subst(Type origType,
if (substType->isOpenedExistential())
return *this;

// If the substituted type is an existential, we have a self-conforming
// existential being substituted in place of itself. There's no
// conformance information in this case, so just return.
if (substType->isObjCExistentialType())
return *this;

auto *proto = getRequirement();

// If the type is an existential, it must be self-conforming.
if (substType->isExistentialType()) {
auto optConformance =
proto->getModuleContext()->lookupExistentialConformance(substType, proto);
assert(optConformance && "existential type didn't self-conform");
return *optConformance;
}

// Check the conformance map.
if (auto result = conformances(origType->getCanonicalType(),
substType, proto)) {
Expand Down Expand Up @@ -1407,9 +1409,12 @@ DeclContext::getLocalConformances(
if (!nominal)
return result;

// Protocols don't have conformances.
if (isa<ProtocolDecl>(nominal))
// Protocols only have self-conformances.
if (auto protocol = dyn_cast<ProtocolDecl>(nominal)) {
if (protocol->requiresSelfConformanceWitnessTable())
return { protocol->getASTContext().getSelfConformance(protocol) };
return { };
}

// Update to record all potential conformances.
nominal->prepareConformanceTable();
Expand Down
15 changes: 11 additions & 4 deletions lib/IRGen/GenProto.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2847,16 +2847,23 @@ llvm::Value *irgen::emitWitnessTableRef(IRGenFunction &IGF,
// If we don't have concrete conformance information, the type must be
// an archetype and the conformance must be via one of the protocol
// requirements of the archetype. Look at what's locally bound.
ProtocolConformance *concreteConformance;
if (conformance.isAbstract()) {
auto archetype = cast<ArchetypeType>(srcType);
return emitArchetypeWitnessTableRef(IGF, archetype, proto);
}
if (auto archetype = dyn_cast<ArchetypeType>(srcType))
return emitArchetypeWitnessTableRef(IGF, archetype, proto);

// Otherwise, this must be a self-conformance.
assert(proto->requiresSelfConformanceWitnessTable());
assert(cast<ProtocolType>(srcType)->getDecl() == proto);
concreteConformance = IGF.IGM.Context.getSelfConformance(proto);

// All other source types should be concrete enough that we have
// conformance info for them. However, that conformance info might be
// more concrete than we're expecting.
// TODO: make a best effort to devirtualize, maybe?
auto concreteConformance = conformance.getConcrete();
} else {
concreteConformance = conformance.getConcrete();
}
assert(concreteConformance->getProtocol() == proto);

auto cacheKind =
Expand Down
3 changes: 3 additions & 0 deletions lib/SILGen/SILGen.h
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,9 @@ class LLVM_LIBRARY_VISIBILITY SILGenModule : public ASTVisitor<SILGenModule> {
/// Emit the default witness table for a resilient protocol.
void emitDefaultWitnessTable(ProtocolDecl *protocol);

/// Emit the self-conformance witness table for a protocol.
void emitSelfConformanceWitnessTable(ProtocolDecl *protocol);

/// Emit the lazy initializer function for a global pattern binding
/// declaration.
SILFunction *emitLazyGlobalInitializer(StringRef funcName,
Expand Down
3 changes: 2 additions & 1 deletion lib/SILGen/SILGenFunction.h
Original file line number Diff line number Diff line change
Expand Up @@ -651,7 +651,8 @@ class LLVM_LIBRARY_VISIBILITY SILGenFunction
SubstitutionMap reqtSubs,
SILDeclRef witness,
SubstitutionMap witnessSubs,
IsFreeFunctionWitness_t isFree);
IsFreeFunctionWitness_t isFree,
bool isSelfConformance);

/// Convert a block to a native function with a thunk.
ManagedValue emitBlockToFunc(SILLocation loc,
Expand Down
Loading