Skip to content

Commit 82099b1

Browse files
committed
Diagnose Availability for Parameterized Existential Types
Usages of parameterized existential types that involve runtime type metadata to resolve must be gated on the appropriate OS version in which those features have landed. This means the following usage classes must appear gated: - Checked casts (is, as?, as!) - As arguments to generic types (Foo<any P<T>>) - As type witnesses to protocol conformances - In erasure expressions (and optional-to-any erasure expressions) - In metatypes - Tuples What does this leave? - Concrete usages - any P<T> as a parameter or result type - Any amount of optional types around existential types - Static casts (as) It's worth calling out the fact that usages of parameterized existential types in tuples are banned but usages in struct members are not. This is due to the fact that Tuple identity and runtime layout is determined by a metadata query against each component type. Whereas for a struct, the opaque type layout does not depend upon the type metadata of the fields at runtime, and the identity of the struct is determined nominally rather than structurally. Practically, this means that one can work around the lack of tuples by defining a struct with an equivalent type structure as fields: struct AdHocEraser { var x: any P<T> } rdar://92197245
1 parent 967896c commit 82099b1

File tree

4 files changed

+144
-11
lines changed

4 files changed

+144
-11
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5589,6 +5589,11 @@ ERROR(availability_concurrency_only_version_newer, none,
55895589
"concurrency is only available in %0 %1 or newer",
55905590
(StringRef, llvm::VersionTuple))
55915591

5592+
ERROR(availability_parameterized_protocol_only_version_newer, none,
5593+
"runtime support for parameterized protocol types is only available in "
5594+
"%0 %1 or newer",
5595+
(StringRef, llvm::VersionTuple))
5596+
55925597
NOTE(availability_guard_with_version_check, none,
55935598
"add 'if #available' version check", ())
55945599

lib/Sema/TypeCheckAvailability.cpp

Lines changed: 115 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,12 @@
1515
//===----------------------------------------------------------------------===//
1616

1717
#include "TypeCheckAvailability.h"
18+
#include "MiscDiagnostics.h"
1819
#include "TypeCheckConcurrency.h"
19-
#include "TypeChecker.h"
2020
#include "TypeCheckObjC.h"
21-
#include "MiscDiagnostics.h"
21+
#include "TypeChecker.h"
2222
#include "swift/AST/ASTWalker.h"
23+
#include "swift/AST/GenericEnvironment.h"
2324
#include "swift/AST/Initializer.h"
2425
#include "swift/AST/NameLookup.h"
2526
#include "swift/AST/Pattern.h"
@@ -2773,6 +2774,74 @@ bool isSubscriptReturningString(const ValueDecl *D, ASTContext &Context) {
27732774
return resultTy->isString();
27742775
}
27752776

2777+
static bool diagnosePotentialParameterizedProtocolUnavailability(
2778+
SourceRange ReferenceRange, const DeclContext *ReferenceDC,
2779+
const UnavailabilityReason &Reason) {
2780+
ASTContext &Context = ReferenceDC->getASTContext();
2781+
2782+
auto RequiredRange = Reason.getRequiredOSVersionRange();
2783+
{
2784+
auto Err = Context.Diags.diagnose(
2785+
ReferenceRange.Start,
2786+
diag::availability_parameterized_protocol_only_version_newer,
2787+
prettyPlatformString(targetPlatform(Context.LangOpts)),
2788+
Reason.getRequiredOSVersionRange().getLowerEndpoint());
2789+
2790+
// Direct a fixit to the error if an existing guard is nearly-correct
2791+
if (fixAvailabilityByNarrowingNearbyVersionCheck(
2792+
ReferenceRange, ReferenceDC, RequiredRange, Context, Err))
2793+
return true;
2794+
}
2795+
fixAvailability(ReferenceRange, ReferenceDC, RequiredRange, Context);
2796+
return true;
2797+
}
2798+
2799+
bool swift::diagnoseParameterizedProtocolAvailability(
2800+
SourceRange ReferenceRange, const DeclContext *ReferenceDC) {
2801+
// Check the availability of parameterized existential runtime support.
2802+
ASTContext &ctx = ReferenceDC->getASTContext();
2803+
if (ctx.LangOpts.DisableAvailabilityChecking)
2804+
return false;
2805+
2806+
if (!shouldCheckAvailability(ReferenceDC->getAsDecl()))
2807+
return false;
2808+
2809+
auto runningOS = TypeChecker::overApproximateAvailabilityAtLocation(
2810+
ReferenceRange.Start, ReferenceDC);
2811+
auto availability = ctx.getParameterizedExistentialRuntimeAvailability();
2812+
if (!runningOS.isContainedIn(availability)) {
2813+
return diagnosePotentialParameterizedProtocolUnavailability(
2814+
ReferenceRange, ReferenceDC,
2815+
UnavailabilityReason::requiresVersionRange(
2816+
availability.getOSVersion()));
2817+
}
2818+
return false;
2819+
}
2820+
2821+
static void
2822+
maybeDiagParameterizedExistentialErasure(ErasureExpr *EE,
2823+
const ExportContext &Where) {
2824+
if (auto *OE = dyn_cast<OpaqueValueExpr>(EE->getSubExpr())) {
2825+
auto *OAT = OE->getType()->getAs<OpenedArchetypeType>();
2826+
if (!OAT)
2827+
return;
2828+
2829+
auto opened = OAT->getGenericEnvironment()->getOpenedExistentialType();
2830+
if (!opened || !opened->hasParameterizedExistential())
2831+
return;
2832+
2833+
(void)diagnoseParameterizedProtocolAvailability(EE->getLoc(),
2834+
Where.getDeclContext());
2835+
}
2836+
2837+
if (EE->getType() &&
2838+
EE->getType()->isAny() &&
2839+
EE->getSubExpr()->getType()->hasParameterizedExistential()) {
2840+
(void)diagnoseParameterizedProtocolAvailability(EE->getLoc(),
2841+
Where.getDeclContext());
2842+
}
2843+
}
2844+
27762845
bool swift::diagnoseExplicitUnavailability(
27772846
const ValueDecl *D,
27782847
SourceRange R,
@@ -2989,6 +3058,17 @@ class ExprAvailabilityWalker : public ASTWalker {
29893058
diagnoseDeclRefAvailability(Context.getRegexDecl(), Range);
29903059
diagnoseDeclRefAvailability(RLE->getInitializer(), Range);
29913060
}
3061+
if (auto *EE = dyn_cast<ErasureExpr>(E)) {
3062+
maybeDiagParameterizedExistentialErasure(EE, Where);
3063+
}
3064+
if (auto *CC = dyn_cast<ExplicitCastExpr>(E)) {
3065+
if (!isa<CoerceExpr>(CC) &&
3066+
CC->getCastType()->hasParameterizedExistential()) {
3067+
SourceLoc loc = CC->getCastTypeRepr() ? CC->getCastTypeRepr()->getLoc()
3068+
: E->getLoc();
3069+
diagnoseParameterizedProtocolAvailability(loc, Where.getDeclContext());
3070+
}
3071+
}
29923072
if (auto KP = dyn_cast<KeyPathExpr>(E)) {
29933073
maybeDiagKeyPath(KP);
29943074
}
@@ -3749,7 +3829,12 @@ class ProblematicTypeFinder : public TypeDeclFinder {
37493829

37503830
ModuleDecl *useModule = Where.getDeclContext()->getParentModule();
37513831
auto subs = ty->getContextSubstitutionMap(useModule, ty->getDecl());
3752-
(void) diagnoseSubstitutionMapAvailability(Loc, subs, Where);
3832+
(void)diagnoseSubstitutionMapAvailability(
3833+
Loc, subs, Where,
3834+
/*depTy=*/Type(),
3835+
/*replacementTy=*/Type(),
3836+
/*useConformanceAvailabilityErrorsOption=*/false,
3837+
/*suppressParameterizationCheckForOptional=*/ty->isOptional());
37533838
return Action::Continue;
37543839
}
37553840

@@ -3781,6 +3866,19 @@ class ProblematicTypeFinder : public TypeDeclFinder {
37813866
}
37823867
}
37833868

3869+
if (auto *TT = T->getAs<TupleType>()) {
3870+
for (auto component : TT->getElementTypes()) {
3871+
// Let the walker find inner tuple types, we only want to diagnose
3872+
// non-compound components.
3873+
if (component->is<TupleType>())
3874+
continue;
3875+
3876+
if (component->hasParameterizedExistential())
3877+
(void)diagnoseParameterizedProtocolAvailability(
3878+
Loc, Where.getDeclContext());
3879+
}
3880+
}
3881+
37843882
return TypeDeclFinder::walkToTypePost(T);
37853883
}
37863884
};
@@ -3895,14 +3993,27 @@ swift::diagnoseSubstitutionMapAvailability(SourceLoc loc,
38953993
SubstitutionMap subs,
38963994
const ExportContext &where,
38973995
Type depTy, Type replacementTy,
3898-
bool useConformanceAvailabilityErrorsOption) {
3996+
bool useConformanceAvailabilityErrorsOption,
3997+
bool suppressParameterizationCheckForOptional) {
38993998
bool hadAnyIssues = false;
39003999
for (ProtocolConformanceRef conformance : subs.getConformances()) {
39014000
if (diagnoseConformanceAvailability(loc, conformance, where,
39024001
depTy, replacementTy,
39034002
useConformanceAvailabilityErrorsOption))
39044003
hadAnyIssues = true;
39054004
}
4005+
4006+
// If we're looking at \c (any P)? (or any other depth of optional) then
4007+
// there's no availability problem.
4008+
if (suppressParameterizationCheckForOptional)
4009+
return hadAnyIssues;
4010+
4011+
for (auto replacement : subs.getReplacementTypes()) {
4012+
if (replacement->hasParameterizedExistential())
4013+
if (diagnoseParameterizedProtocolAvailability(loc,
4014+
where.getDeclContext()))
4015+
hadAnyIssues = true;
4016+
}
39064017
return hadAnyIssues;
39074018
}
39084019

lib/Sema/TypeCheckAvailability.h

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -224,13 +224,14 @@ diagnoseConformanceAvailability(SourceLoc loc,
224224
Type replacementTy=Type(),
225225
bool useConformanceAvailabilityErrorsOption = false);
226226

227-
bool
228-
diagnoseSubstitutionMapAvailability(SourceLoc loc,
229-
SubstitutionMap subs,
230-
const ExportContext &context,
231-
Type depTy=Type(),
232-
Type replacementTy=Type(),
233-
bool useConformanceAvailabilityErrorsOption = false);
227+
bool diagnoseSubstitutionMapAvailability(
228+
SourceLoc loc,
229+
SubstitutionMap subs,
230+
const ExportContext &context,
231+
Type depTy = Type(),
232+
Type replacementTy = Type(),
233+
bool useConformanceAvailabilityErrorsOption = false,
234+
bool suppressParameterizationCheckForOptional = false);
234235

235236
/// Diagnose uses of unavailable declarations. Returns true if a diagnostic
236237
/// was emitted.
@@ -267,6 +268,11 @@ bool diagnoseExplicitUnavailability(
267268
const ExportContext &where,
268269
bool useConformanceAvailabilityErrorsOption = false);
269270

271+
/// Diagnose uses of the runtime features of parameterized protools. Returns
272+
/// \c true if a diagnostic was emitted.
273+
bool diagnoseParameterizedProtocolAvailability(SourceRange loc,
274+
const DeclContext *DC);
275+
270276
/// Check if \p decl has a introduction version required by -require-explicit-availability
271277
void checkExplicitAvailability(Decl *decl);
272278

lib/Sema/TypeCheckProtocol.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4919,6 +4919,17 @@ void ConformanceChecker::ensureRequirementsAreSatisfied() {
49194919
if (where.isImplicit())
49204920
return;
49214921

4922+
Conformance->forEachTypeWitness([&](const AssociatedTypeDecl *assoc,
4923+
Type type, TypeDecl *typeDecl) -> bool {
4924+
// Make sure any associated type witnesses don't make reference to a
4925+
// parameterized existential type, or we're going to have trouble at
4926+
// runtime.
4927+
if (type->hasParameterizedExistential())
4928+
(void)diagnoseParameterizedProtocolAvailability(typeDecl->getLoc(),
4929+
where.getDeclContext());
4930+
return false;
4931+
});
4932+
49224933
for (auto req : proto->getRequirementSignature().getRequirements()) {
49234934
if (req.getKind() == RequirementKind::Conformance) {
49244935
auto depTy = req.getFirstType();

0 commit comments

Comments
 (0)