Skip to content

Commit 1ce0aee

Browse files
committed
Sema: Compute existential member access limitations for storage declarations
1 parent 01c899b commit 1ce0aee

File tree

4 files changed

+86
-22
lines changed

4 files changed

+86
-22
lines changed

lib/Sema/CSSimplify.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9878,8 +9878,15 @@ performMemberLookup(ConstraintKind constraintKind, DeclNameRef memberName,
98789878
// member access if the member's signature references 'Self'.
98799879
if (instanceTy->isExistentialType() &&
98809880
decl->getDeclContext()->getSelfProtocolDecl()) {
9881-
if (!isMemberAvailableOnExistential(instanceTy, decl)) {
9881+
switch (isMemberAvailableOnExistential(instanceTy, decl)) {
9882+
case ExistentialMemberAccessLimitation::Unsupported:
9883+
// TODO: Write-only accesses are not supported yet.
9884+
case ExistentialMemberAccessLimitation::WriteOnly:
98829885
return true;
9886+
9887+
case ExistentialMemberAccessLimitation::ReadOnly:
9888+
case ExistentialMemberAccessLimitation::None:
9889+
break;
98839890
}
98849891
}
98859892

lib/Sema/OpenedExistentials.cpp

Lines changed: 53 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -513,8 +513,8 @@ static bool doesMemberHaveUnfulfillableConstraintsWithExistentialBase(
513513
return false;
514514
}
515515

516-
bool swift::isMemberAvailableOnExistential(
517-
Type baseTy, const ValueDecl *member) {
516+
ExistentialMemberAccessLimitation
517+
swift::isMemberAvailableOnExistential(Type baseTy, const ValueDecl *member) {
518518

519519
auto &ctx = member->getASTContext();
520520
auto existentialSig = ctx.getOpenedExistentialSignature(baseTy);
@@ -525,27 +525,68 @@ bool swift::isMemberAvailableOnExistential(
525525
auto origParam = dc->getSelfInterfaceType()->castTo<GenericTypeParamType>();
526526
auto openedParam = existentialSig.SelfType->castTo<GenericTypeParamType>();
527527

528+
// An accessor or non-storage member is not available if its interface type
529+
// contains a non-covariant reference to a 'Self'-rooted type parameter in the
530+
// context of the base type's existential signature.
528531
auto info = findGenericParameterReferences(
529532
member, existentialSig.OpenedSig, origParam, openedParam,
530533
std::nullopt);
531534

532-
if (info.hasNonCovariantRef()) {
533-
return false;
534-
}
535+
auto result = ExistentialMemberAccessLimitation::None;
536+
if (!info) {
537+
// Nothing to do.
538+
} else if (info.hasRef(TypePosition::Invariant)) {
539+
// An invariant reference is decisive.
540+
result = ExistentialMemberAccessLimitation::Unsupported;
541+
} else if (isa<AbstractFunctionDecl>(member)) {
542+
// Anything non-covariant is decisive for functions.
543+
if (info.hasRef(TypePosition::Contravariant)) {
544+
result = ExistentialMemberAccessLimitation::Unsupported;
545+
}
546+
} else {
547+
const auto isGetterUnavailable = info.hasRef(TypePosition::Contravariant);
548+
auto isSetterUnavailable = true;
549+
550+
if (isa<VarDecl>(member)) {
551+
// For properties, the setter is unavailable if the interface type has a
552+
// covariant reference, which becomes contravariant is the setter.
553+
isSetterUnavailable = info.hasRef(TypePosition::Covariant);
554+
} else {
555+
// For subscripts specifically, we must scan the setter directly because
556+
// whether a covariant reference in the interface type becomes
557+
// contravariant in the setter depends on the location of the reference
558+
// (in the indices or the result type).
559+
auto *setter =
560+
cast<SubscriptDecl>(member)->getAccessor(AccessorKind::Set);
561+
const auto setterInfo = setter ? findGenericParameterReferences(
562+
setter, existentialSig.OpenedSig,
563+
origParam, openedParam, std::nullopt)
564+
: GenericParameterReferenceInfo();
565+
566+
isSetterUnavailable = setterInfo.hasRef(TypePosition::Contravariant);
567+
}
535568

536-
// FIXME: Appropriately diagnose assignments instead.
537-
if (auto *const storageDecl = dyn_cast<AbstractStorageDecl>(member)) {
538-
if (info.hasCovariantGenericParamResult() &&
539-
storageDecl->supportsMutation())
540-
return false;
569+
if (isGetterUnavailable && isSetterUnavailable) {
570+
result = ExistentialMemberAccessLimitation::Unsupported;
571+
} else if (isGetterUnavailable) {
572+
result = ExistentialMemberAccessLimitation::WriteOnly;
573+
} else if (isSetterUnavailable) {
574+
result = ExistentialMemberAccessLimitation::ReadOnly;
575+
}
541576
}
542577

578+
// If the member access is not supported whatsoever, we are done.
579+
if (result == ExistentialMemberAccessLimitation::Unsupported)
580+
return result;
581+
582+
// Before proceeding with the result, see if we find a generic requirement
583+
// that cannot be satisfied; if we do, the member is unavailable after all.
543584
if (doesMemberHaveUnfulfillableConstraintsWithExistentialBase(existentialSig,
544585
member)) {
545-
return false;
586+
return ExistentialMemberAccessLimitation::Unsupported;
546587
}
547588

548-
return true;
589+
return result;
549590
}
550591

551592
std::optional<std::tuple<GenericTypeParamType *, TypeVariableType *,

lib/Sema/OpenedExistentials.h

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,10 @@ class GenericParameterReferenceInfo final {
8181
return DepMemberTyRefs & pos.value();
8282
}
8383

84+
bool hasRef(std::optional<TypePosition> pos = std::nullopt) const {
85+
return hasDirectRef(pos) || hasDependentMemberTypeRef(pos);
86+
}
87+
8488
bool hasNonCovariantRef() const {
8589
const uint8_t notCovariant = ~TypePosition::Covariant;
8690
return (DirectRefs & notCovariant) || (DepMemberTyRefs & notCovariant);
@@ -93,9 +97,7 @@ class GenericParameterReferenceInfo final {
9397
GenericParameterReferenceInfo &
9498
operator|=(const GenericParameterReferenceInfo &other);
9599

96-
explicit operator bool() const {
97-
return hasDirectRef() || hasDependentMemberTypeRef();
98-
}
100+
explicit operator bool() const { return hasRef(); }
99101
};
100102

101103
/// Find references to the given generic parameter in the type signature of the
@@ -112,12 +114,23 @@ findGenericParameterReferences(const ValueDecl *value, CanGenericSignature sig,
112114
/// Find references to 'Self' in the type signature of this declaration.
113115
GenericParameterReferenceInfo findExistentialSelfReferences(const ValueDecl *value);
114116

115-
/// Determine whether referencing the given member on the
116-
/// given existential base type is supported. This is the case only if the
117-
/// type of the member, spelled in the context of \p baseTy, does not contain
118-
/// 'Self' or 'Self'-rooted dependent member types in non-covariant position.
119-
bool isMemberAvailableOnExistential(Type baseTy,
120-
const ValueDecl *member);
117+
/// Describes the limitation on accessing a protocol member on a value of
118+
/// existential type.
119+
enum class ExistentialMemberAccessLimitation : uint8_t {
120+
/// The member can be freely accessed on the existential.
121+
None,
122+
/// The storage member is available only for reads.
123+
ReadOnly,
124+
/// The storage member is available only for writes.
125+
WriteOnly,
126+
/// Accessing the member on the existential is not supported.
127+
Unsupported,
128+
};
129+
130+
/// Compute the limitations on accessing the given member on a value of the
131+
/// given existential base type.
132+
ExistentialMemberAccessLimitation
133+
isMemberAvailableOnExistential(Type baseTy, const ValueDecl *member);
121134

122135
/// Flags that should be applied to the existential argument type after
123136
/// opening.

lib/Sema/TypeOfReference.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -505,6 +505,9 @@ static bool doesStorageProduceLValue(
505505
if (!storage->isSetterAccessibleFrom(useDC))
506506
return false;
507507

508+
// if (baseType) {
509+
// isMemberAvailableOnExistential(baseType, storage)
510+
// }
508511
// If there is no base, or the base is an lvalue, then a reference
509512
// produces an lvalue.
510513
if (!baseType || baseType->is<LValueType>())

0 commit comments

Comments
 (0)