Skip to content
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

SE-0309: Unlock existential types for all protocols #33767

Merged
merged 9 commits into from
Aug 26, 2021
30 changes: 1 addition & 29 deletions include/swift/AST/Decl.h
Original file line number Diff line number Diff line change
Expand Up @@ -514,7 +514,7 @@ class alignas(1 << DeclAlignInBits) Decl {
IsComputingSemanticMembers : 1
);

SWIFT_INLINE_BITFIELD_FULL(ProtocolDecl, NominalTypeDecl, 1+1+1+1+1+1+1+1+1+1+1+8+16,
SWIFT_INLINE_BITFIELD_FULL(ProtocolDecl, NominalTypeDecl, 1+1+1+1+1+1+1+1+1+8+16,
/// Whether the \c RequiresClass bit is valid.
RequiresClassValid : 1,

Expand All @@ -527,12 +527,6 @@ class alignas(1 << DeclAlignInBits) Decl {
/// Whether the existential of this protocol conforms to itself.
ExistentialConformsToSelf : 1,

/// Whether the \c ExistentialTypeSupported bit is valid.
ExistentialTypeSupportedValid : 1,

/// Whether the existential of this protocol can be represented.
ExistentialTypeSupported : 1,

/// True if the protocol has requirements that cannot be satisfied (e.g.
/// because they could not be imported from Objective-C).
HasMissingRequirements : 1,
Expand Down Expand Up @@ -4157,21 +4151,6 @@ class ProtocolDecl final : public NominalTypeDecl {
Bits.ProtocolDecl.ExistentialConformsToSelf = result;
}

/// Returns the cached result of \c existentialTypeSupported or \c None if it
/// hasn't yet been computed.
Optional<bool> getCachedExistentialTypeSupported() {
if (Bits.ProtocolDecl.ExistentialTypeSupportedValid)
return Bits.ProtocolDecl.ExistentialTypeSupported;

return None;
}

/// Caches the result of \c existentialTypeSupported
void setCachedExistentialTypeSupported(bool supported) {
Bits.ProtocolDecl.ExistentialTypeSupportedValid = true;
Bits.ProtocolDecl.ExistentialTypeSupported = supported;
}

bool hasLazyRequirementSignature() const {
return Bits.ProtocolDecl.HasLazyRequirementSignature;
}
Expand All @@ -4181,7 +4160,6 @@ class ProtocolDecl final : public NominalTypeDecl {
friend class RequirementSignatureRequest;
friend class ProtocolRequiresClassRequest;
friend class ExistentialConformsToSelfRequest;
friend class ExistentialTypeSupportedRequest;
friend class InheritedProtocolsRequest;

public:
Expand Down Expand Up @@ -4270,12 +4248,6 @@ class ProtocolDecl final : public NominalTypeDecl {
/// contain 'Self' in 'parameter' or 'other' position.
bool isAvailableInExistential(const ValueDecl *decl) const;

/// Determine whether we are allowed to refer to an existential type
/// conforming to this protocol. This is only permitted if the types of
/// all the members do not contain any associated types, and do not
/// contain 'Self' in 'parameter' or 'other' position.
bool existentialTypeSupported() const;

/// Returns a list of protocol requirements that must be assessed to
/// determine a concrete's conformance effect polymorphism kind.
PolymorphicEffectRequirementList getPolymorphicEffectRequirements(
Expand Down
3 changes: 0 additions & 3 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -920,9 +920,6 @@ NOTE(object_literal_resolve_import,none,

ERROR(use_local_before_declaration,none,
"use of local variable %0 before its declaration", (DeclNameRef))
ERROR(unsupported_existential_type,none,
Copy link
Contributor

Choose a reason for hiding this comment

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

I'll miss you most of all, Self-or-associated-type diagnostic! 🥳

"protocol %0 can only be used as a generic constraint because it has "
"Self or associated type requirements", (Identifier))

ERROR(decl_does_not_exist_in_module,none,
"%select{%error|type|struct|class|enum|protocol|variable|function}0 "
Expand Down
4 changes: 2 additions & 2 deletions include/swift/AST/EducationalNotes.def
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@

// EDUCATIONAL_NOTES(DIAG_ID, EDUCATIONAL_NOTE_FILENAMES...)

EDUCATIONAL_NOTES(unsupported_existential_type,
"associated-type-requirements.md")
EDUCATIONAL_NOTES(could_not_use_member_on_existential,
"existential-member-access-limitations.md")

EDUCATIONAL_NOTES(cannot_pass_type_to_non_ephemeral, "temporary-pointers.md")
EDUCATIONAL_NOTES(cannot_pass_type_to_non_ephemeral_warning,
Expand Down
26 changes: 0 additions & 26 deletions include/swift/AST/TypeCheckRequests.h
Original file line number Diff line number Diff line change
Expand Up @@ -287,32 +287,6 @@ class ExistentialConformsToSelfRequest :
void cacheResult(bool value) const;
};

/// Determine whether we are allowed to refer to an existential type conforming
/// to this protocol.
class ExistentialTypeSupportedRequest :
public SimpleRequest<ExistentialTypeSupportedRequest,
bool(ProtocolDecl *),
RequestFlags::SeparatelyCached> {
public:
using SimpleRequest::SimpleRequest;

private:
friend SimpleRequest;

// Evaluation.
bool evaluate(Evaluator &evaluator, ProtocolDecl *decl) const;

public:
// Cycle handling.
void diagnoseCycle(DiagnosticEngine &diags) const;
void noteCycleStep(DiagnosticEngine &diags) const;

// Separate caching.
bool isCached() const { return true; }
Optional<bool> getCachedResult() const;
void cacheResult(bool value) const;
};

class PolymorphicEffectRequirementsRequest :
public SimpleRequest<PolymorphicEffectRequirementsRequest,
PolymorphicEffectRequirementList(EffectKind, ProtocolDecl *),
Expand Down
2 changes: 0 additions & 2 deletions include/swift/AST/TypeCheckerTypeIDZone.def
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,6 @@ SWIFT_REQUEST(TypeChecker, EnumRawTypeRequest,
Type(EnumDecl *), Cached, NoLocationInfo)
SWIFT_REQUEST(TypeChecker, ExistentialConformsToSelfRequest,
bool(ProtocolDecl *), SeparatelyCached, NoLocationInfo)
SWIFT_REQUEST(TypeChecker, ExistentialTypeSupportedRequest,
bool(ProtocolDecl *), SeparatelyCached, NoLocationInfo)
SWIFT_REQUEST(TypeChecker, ExtendedTypeRequest, Type(ExtensionDecl *), Cached,
NoLocationInfo)
SWIFT_REQUEST(TypeChecker, ResultBuilderTypeRequest, Type(ValueDecl *),
Expand Down
52 changes: 32 additions & 20 deletions lib/AST/Decl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5020,24 +5020,31 @@ findProtocolSelfReferences(const ProtocolDecl *proto, Type type,
return findProtocolSelfReferences(proto, selfType->getSelfType(), position);
}

// Most bound generic types are invariant.
if (auto *const bgt = type->getAs<BoundGenericType>()) {
if (auto *const nominal = type->getAs<NominalOrBoundGenericNominalType>()) {
auto info = SelfReferenceInfo();

if (bgt->isArray()) {
// Swift.Array preserves variance in its Value type.
info |= findProtocolSelfReferences(proto, bgt->getGenericArgs().front(),
position);
} else if (bgt->isDictionary()) {
// Swift.Dictionary preserves variance in its Element type.
info |= findProtocolSelfReferences(proto, bgt->getGenericArgs().front(),
SelfReferencePosition::Invariant);
info |= findProtocolSelfReferences(proto, bgt->getGenericArgs().back(),
position);
} else {
for (auto paramType : bgt->getGenericArgs()) {
info |= findProtocolSelfReferences(proto, paramType,
// Don't forget to look in the parent.
if (const auto parent = nominal->getParent()) {
info |= findProtocolSelfReferences(proto, parent, position);
}

// Most bound generic types are invariant.
if (auto *const bgt = type->getAs<BoundGenericType>()) {
if (bgt->isArray()) {
// Swift.Array preserves variance in its Value type.
info |= findProtocolSelfReferences(proto, bgt->getGenericArgs().front(),
position);
} else if (bgt->isDictionary()) {
// Swift.Dictionary preserves variance in its Element type.
info |= findProtocolSelfReferences(proto, bgt->getGenericArgs().front(),
SelfReferencePosition::Invariant);
info |= findProtocolSelfReferences(proto, bgt->getGenericArgs().back(),
position);
} else {
for (auto paramType : bgt->getGenericArgs()) {
info |= findProtocolSelfReferences(proto, paramType,
SelfReferencePosition::Invariant);
}
}
}

Expand All @@ -5049,6 +5056,16 @@ findProtocolSelfReferences(const ProtocolDecl *proto, Type type,
if (type->is<OpaqueTypeArchetypeType>())
return SelfReferenceInfo::forSelfRef(SelfReferencePosition::Invariant);

// Protocol compositions preserve variance.
if (auto *comp = type->getAs<ProtocolCompositionType>()) {
// 'Self' may be referenced only in a superclass component.
if (const auto superclass = comp->getSuperclass()) {
return findProtocolSelfReferences(proto, superclass, position);
}

return SelfReferenceInfo();
}

// A direct reference to 'Self'.
if (proto->getSelfInterfaceType()->isEqual(type))
return SelfReferenceInfo::forSelfRef(position);
Expand Down Expand Up @@ -5148,11 +5165,6 @@ bool ProtocolDecl::isAvailableInExistential(const ValueDecl *decl) const {
return true;
}

bool ProtocolDecl::existentialTypeSupported() const {
return evaluateOrDefault(getASTContext().evaluator,
ExistentialTypeSupportedRequest{const_cast<ProtocolDecl *>(this)}, true);
}

StringRef ProtocolDecl::getObjCRuntimeName(
llvm::SmallVectorImpl<char> &buffer) const {
// If there is an 'objc' attribute with a name, use that name.
Expand Down
25 changes: 0 additions & 25 deletions lib/AST/TypeCheckRequests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -253,31 +253,6 @@ void ExistentialConformsToSelfRequest::cacheResult(bool value) const {
decl->setCachedExistentialConformsToSelf(value);
}

//----------------------------------------------------------------------------//
// existentialTypeSupported computation.
//----------------------------------------------------------------------------//

void ExistentialTypeSupportedRequest::diagnoseCycle(DiagnosticEngine &diags) const {
auto decl = std::get<0>(getStorage());
diags.diagnose(decl, diag::circular_protocol_def, decl->getName());
}

void ExistentialTypeSupportedRequest::noteCycleStep(DiagnosticEngine &diags) const {
auto requirement = std::get<0>(getStorage());
diags.diagnose(requirement, diag::kind_declname_declared_here,
DescriptiveDeclKind::Protocol, requirement->getName());
}

Optional<bool> ExistentialTypeSupportedRequest::getCachedResult() const {
auto decl = std::get<0>(getStorage());
return decl->getCachedExistentialTypeSupported();
}

void ExistentialTypeSupportedRequest::cacheResult(bool value) const {
auto decl = std::get<0>(getStorage());
decl->setCachedExistentialTypeSupported(value);
}

//----------------------------------------------------------------------------//
// isFinal computation.
//----------------------------------------------------------------------------//
Expand Down
75 changes: 49 additions & 26 deletions lib/Sema/CSSimplify.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7302,7 +7302,7 @@ performMemberLookup(ConstraintKind constraintKind, DeclNameRef memberName,
// Dig out the instance type and figure out what members of the instance type
// we are going to see.
auto baseTy = candidate.getBaseType();
auto baseObjTy = baseTy->getRValueType();
const auto baseObjTy = baseTy->getRValueType();

bool hasInstanceMembers = false;
bool hasInstanceMethods = false;
Expand Down Expand Up @@ -7349,18 +7349,6 @@ performMemberLookup(ConstraintKind constraintKind, DeclNameRef memberName,
hasInstanceMethods = true;
}

// If our base is an existential type, we can't make use of any
// member whose signature involves associated types.
if (instanceTy->isExistentialType()) {
if (auto *proto = decl->getDeclContext()->getSelfProtocolDecl()) {
if (!proto->isAvailableInExistential(decl)) {
result.addUnviable(candidate,
MemberLookupResult::UR_UnavailableInExistential);
return;
}
}
}

// If the invocation's argument expression has a favored type,
// use that information to determine whether a specific overload for
// the candidate should be favored.
Expand All @@ -7380,6 +7368,20 @@ performMemberLookup(ConstraintKind constraintKind, DeclNameRef memberName,
}
}

const auto isUnsupportedExistentialMemberAccess = [&] {
// If our base is an existential type, we can't make use of any
// member whose signature involves associated types.
if (instanceTy->isExistentialType()) {
if (auto *proto = decl->getDeclContext()->getSelfProtocolDecl()) {
if (!proto->isAvailableInExistential(decl)) {
return true;
}
}
}

return false;
};

// See if we have an instance method, instance member or static method,
// and check if it can be accessed on our base type.

Expand All @@ -7393,20 +7395,35 @@ performMemberLookup(ConstraintKind constraintKind, DeclNameRef memberName,
? candidate
: OverloadChoice(instanceTy, decl,
FunctionRefKind::SingleApply);
// If this is an instance member referenced from metatype
// let's add unviable result to the set because it could be
// either curried reference or an invalid call.
//
// New candidate shouldn't affect performance because such
// choice would only be attempted when solver is in diagnostic mode.
result.addUnviable(choice, MemberLookupResult::UR_InstanceMemberOnType);

bool invalidMethodRef = isa<FuncDecl>(decl) && !hasInstanceMethods;
bool invalidMemberRef = !isa<FuncDecl>(decl) && !hasInstanceMembers;
// If this is definitely an invalid way to reference a method or member
// on the metatype, let's stop here.
if (invalidMethodRef || invalidMemberRef)

const bool invalidMethodRef = isa<FuncDecl>(decl) && !hasInstanceMethods;
const bool invalidMemberRef = !isa<FuncDecl>(decl) && !hasInstanceMembers;

if (invalidMethodRef || invalidMemberRef) {
// If this is definitely an invalid way to reference a method or member
// on the metatype, let's stop here.
result.addUnviable(choice,
MemberLookupResult::UR_InstanceMemberOnType);
return;
} else if (isUnsupportedExistentialMemberAccess()) {
// If the member reference itself is legal, but it turns out to be an
// unsupported existential member access, do not make further
// assumptions about the correctness of a potential call -- let
// the unsupported member access error prevail.
result.addUnviable(candidate,
MemberLookupResult::UR_UnavailableInExistential);
return;
} else {
// Otherwise, still add an unviable result to the set, because it
// could be an invalid call that was supposed to be performed on an
// instance of the type.
//
// New candidate shouldn't affect performance because such
// choice would only be attempted when solver is in diagnostic mode.
result.addUnviable(choice,
MemberLookupResult::UR_InstanceMemberOnType);

}
}

// If the underlying type of a typealias is fully concrete, it is legal
Expand Down Expand Up @@ -7457,6 +7474,12 @@ performMemberLookup(ConstraintKind constraintKind, DeclNameRef memberName,
}
}

if (isUnsupportedExistentialMemberAccess()) {
result.addUnviable(candidate,
MemberLookupResult::UR_UnavailableInExistential);
return;
}

// If we have an rvalue base, make sure that the result isn't 'mutating'
// (only valid on lvalues).
if (!baseTy->is<AnyMetatypeType>() &&
Expand Down
4 changes: 0 additions & 4 deletions lib/Sema/MiscDiagnostics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3342,8 +3342,6 @@ static void checkSwitch(ASTContext &ctx, const SwitchStmt *stmt) {
// We want to warn about "case .Foo, .Bar where 1 != 100:" since the where
// clause only applies to the second case, and this is surprising.
for (auto cs : stmt->getCases()) {
TypeChecker::checkUnsupportedProtocolType(ctx, cs);

// The case statement can have multiple case items, each can have a where.
// If we find a "where", and there is a preceding item without a where, and
// if they are on the same source line, then warn.
Expand Down Expand Up @@ -4760,8 +4758,6 @@ void swift::performSyntacticExprDiagnostics(const Expr *E,
void swift::performStmtDiagnostics(const Stmt *S, DeclContext *DC) {
auto &ctx = DC->getASTContext();

TypeChecker::checkUnsupportedProtocolType(ctx, const_cast<Stmt *>(S));

if (auto switchStmt = dyn_cast<SwitchStmt>(S))
checkSwitch(ctx, switchStmt);

Expand Down
28 changes: 0 additions & 28 deletions lib/Sema/TypeCheckDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -672,34 +672,6 @@ ExistentialConformsToSelfRequest::evaluate(Evaluator &evaluator,
return true;
}

bool
ExistentialTypeSupportedRequest::evaluate(Evaluator &evaluator,
ProtocolDecl *decl) const {
// ObjC protocols can always be existential.
if (decl->isObjC())
return true;

for (auto member : decl->getMembers()) {
// Existential types cannot be used if the protocol has an associated type.
if (isa<AssociatedTypeDecl>(member))
return false;

// For value members, look at their type signatures.
if (auto valueMember = dyn_cast<ValueDecl>(member)) {
if (!decl->isAvailableInExistential(valueMember))
return false;
}
}

// Check whether all of the inherited protocols support existential types.
for (auto proto : decl->getInheritedProtocols()) {
if (!proto->existentialTypeSupported())
return false;
}

return true;
}

bool
IsFinalRequest::evaluate(Evaluator &evaluator, ValueDecl *decl) const {
if (isa<ClassDecl>(decl))
Expand Down
Loading