Skip to content

Commit

Permalink
Merge pull request #33767 from theblixguy/chore/remove-self-or-associ…
Browse files Browse the repository at this point in the history
…ated-type-diagnostic

SE-0309: Unlock existential types for all protocols
  • Loading branch information
ktoso authored Aug 26, 2021
2 parents 3fc18f3 + 9a52220 commit f96057e
Show file tree
Hide file tree
Showing 31 changed files with 825 additions and 572 deletions.
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 : public ASTAllocated<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 : public ASTAllocated<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 @@ -4151,21 +4145,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 @@ -4175,7 +4154,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 @@ -4264,12 +4242,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,
"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 @@ -5008,24 +5008,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 @@ -5037,6 +5044,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 @@ -5136,11 +5153,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 @@ -7322,7 +7322,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 @@ -7369,18 +7369,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 @@ -7400,6 +7388,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 @@ -7413,20 +7415,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 @@ -7477,6 +7494,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 @@ -3329,8 +3329,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 @@ -4747,8 +4745,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

0 comments on commit f96057e

Please sign in to comment.