Skip to content

Commit 446450a

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 72e546c commit 446450a

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
@@ -5609,6 +5609,11 @@ ERROR(availability_concurrency_only_version_newer, none,
56095609
"concurrency is only available in %0 %1 or newer",
56105610
(StringRef, llvm::VersionTuple))
56115611

5612+
ERROR(availability_parameterized_protocol_only_version_newer, none,
5613+
"runtime support for parameterized protocol types is only available in "
5614+
"%0 %1 or newer",
5615+
(StringRef, llvm::VersionTuple))
5616+
56125617
NOTE(availability_guard_with_version_check, none,
56135618
"add 'if #available' version check", ())
56145619

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"
@@ -2749,6 +2750,74 @@ bool isSubscriptReturningString(const ValueDecl *D, ASTContext &Context) {
27492750
return resultTy->isString();
27502751
}
27512752

2753+
static bool diagnosePotentialParameterizedProtocolUnavailability(
2754+
SourceRange ReferenceRange, const DeclContext *ReferenceDC,
2755+
const UnavailabilityReason &Reason) {
2756+
ASTContext &Context = ReferenceDC->getASTContext();
2757+
2758+
auto RequiredRange = Reason.getRequiredOSVersionRange();
2759+
{
2760+
auto Err = Context.Diags.diagnose(
2761+
ReferenceRange.Start,
2762+
diag::availability_parameterized_protocol_only_version_newer,
2763+
prettyPlatformString(targetPlatform(Context.LangOpts)),
2764+
Reason.getRequiredOSVersionRange().getLowerEndpoint());
2765+
2766+
// Direct a fixit to the error if an existing guard is nearly-correct
2767+
if (fixAvailabilityByNarrowingNearbyVersionCheck(
2768+
ReferenceRange, ReferenceDC, RequiredRange, Context, Err))
2769+
return true;
2770+
}
2771+
fixAvailability(ReferenceRange, ReferenceDC, RequiredRange, Context);
2772+
return true;
2773+
}
2774+
2775+
bool swift::diagnoseParameterizedProtocolAvailability(
2776+
SourceRange ReferenceRange, const DeclContext *ReferenceDC) {
2777+
// Check the availability of parameterized existential runtime support.
2778+
ASTContext &ctx = ReferenceDC->getASTContext();
2779+
if (ctx.LangOpts.DisableAvailabilityChecking)
2780+
return false;
2781+
2782+
if (!shouldCheckAvailability(ReferenceDC->getAsDecl()))
2783+
return false;
2784+
2785+
auto runningOS = TypeChecker::overApproximateAvailabilityAtLocation(
2786+
ReferenceRange.Start, ReferenceDC);
2787+
auto availability = ctx.getParameterizedExistentialRuntimeAvailability();
2788+
if (!runningOS.isContainedIn(availability)) {
2789+
return diagnosePotentialParameterizedProtocolUnavailability(
2790+
ReferenceRange, ReferenceDC,
2791+
UnavailabilityReason::requiresVersionRange(
2792+
availability.getOSVersion()));
2793+
}
2794+
return false;
2795+
}
2796+
2797+
static void
2798+
maybeDiagParameterizedExistentialErasure(ErasureExpr *EE,
2799+
const ExportContext &Where) {
2800+
if (auto *OE = dyn_cast<OpaqueValueExpr>(EE->getSubExpr())) {
2801+
auto *OAT = OE->getType()->getAs<OpenedArchetypeType>();
2802+
if (!OAT)
2803+
return;
2804+
2805+
auto opened = OAT->getGenericEnvironment()->getOpenedExistentialType();
2806+
if (!opened || !opened->hasParameterizedExistential())
2807+
return;
2808+
2809+
(void)diagnoseParameterizedProtocolAvailability(EE->getLoc(),
2810+
Where.getDeclContext());
2811+
}
2812+
2813+
if (EE->getType() &&
2814+
EE->getType()->isAny() &&
2815+
EE->getSubExpr()->getType()->hasParameterizedExistential()) {
2816+
(void)diagnoseParameterizedProtocolAvailability(EE->getLoc(),
2817+
Where.getDeclContext());
2818+
}
2819+
}
2820+
27522821
bool swift::diagnoseExplicitUnavailability(
27532822
const ValueDecl *D,
27542823
SourceRange R,
@@ -2965,6 +3034,17 @@ class ExprAvailabilityWalker : public ASTWalker {
29653034
diagnoseDeclRefAvailability(Context.getRegexDecl(), Range);
29663035
diagnoseDeclRefAvailability(RLE->getInitializer(), Range);
29673036
}
3037+
if (auto *EE = dyn_cast<ErasureExpr>(E)) {
3038+
maybeDiagParameterizedExistentialErasure(EE, Where);
3039+
}
3040+
if (auto *CC = dyn_cast<ExplicitCastExpr>(E)) {
3041+
if (!isa<CoerceExpr>(CC) &&
3042+
CC->getCastType()->hasParameterizedExistential()) {
3043+
SourceLoc loc = CC->getCastTypeRepr() ? CC->getCastTypeRepr()->getLoc()
3044+
: E->getLoc();
3045+
diagnoseParameterizedProtocolAvailability(loc, Where.getDeclContext());
3046+
}
3047+
}
29683048
if (auto KP = dyn_cast<KeyPathExpr>(E)) {
29693049
maybeDiagKeyPath(KP);
29703050
}
@@ -3739,7 +3819,12 @@ class ProblematicTypeFinder : public TypeDeclFinder {
37393819

37403820
ModuleDecl *useModule = Where.getDeclContext()->getParentModule();
37413821
auto subs = ty->getContextSubstitutionMap(useModule, ty->getDecl());
3742-
(void) diagnoseSubstitutionMapAvailability(Loc, subs, Where);
3822+
(void)diagnoseSubstitutionMapAvailability(
3823+
Loc, subs, Where,
3824+
/*depTy=*/Type(),
3825+
/*replacementTy=*/Type(),
3826+
/*useConformanceAvailabilityErrorsOption=*/false,
3827+
/*suppressParameterizationCheckForOptional=*/ty->isOptional());
37433828
return Action::Continue;
37443829
}
37453830

@@ -3771,6 +3856,19 @@ class ProblematicTypeFinder : public TypeDeclFinder {
37713856
}
37723857
}
37733858

3859+
if (auto *TT = T->getAs<TupleType>()) {
3860+
for (auto component : TT->getElementTypes()) {
3861+
// Let the walker find inner tuple types, we only want to diagnose
3862+
// non-compound components.
3863+
if (component->is<TupleType>())
3864+
continue;
3865+
3866+
if (component->hasParameterizedExistential())
3867+
(void)diagnoseParameterizedProtocolAvailability(
3868+
Loc, Where.getDeclContext());
3869+
}
3870+
}
3871+
37743872
return TypeDeclFinder::walkToTypePost(T);
37753873
}
37763874
};
@@ -3885,14 +3983,27 @@ swift::diagnoseSubstitutionMapAvailability(SourceLoc loc,
38853983
SubstitutionMap subs,
38863984
const ExportContext &where,
38873985
Type depTy, Type replacementTy,
3888-
bool useConformanceAvailabilityErrorsOption) {
3986+
bool useConformanceAvailabilityErrorsOption,
3987+
bool suppressParameterizationCheckForOptional) {
38893988
bool hadAnyIssues = false;
38903989
for (ProtocolConformanceRef conformance : subs.getConformances()) {
38913990
if (diagnoseConformanceAvailability(loc, conformance, where,
38923991
depTy, replacementTy,
38933992
useConformanceAvailabilityErrorsOption))
38943993
hadAnyIssues = true;
38953994
}
3995+
3996+
// If we're looking at \c (any P)? (or any other depth of optional) then
3997+
// there's no availability problem.
3998+
if (suppressParameterizationCheckForOptional)
3999+
return hadAnyIssues;
4000+
4001+
for (auto replacement : subs.getReplacementTypes()) {
4002+
if (replacement->hasParameterizedExistential())
4003+
if (diagnoseParameterizedProtocolAvailability(loc,
4004+
where.getDeclContext()))
4005+
hadAnyIssues = true;
4006+
}
38964007
return hadAnyIssues;
38974008
}
38984009

lib/Sema/TypeCheckAvailability.h

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

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

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

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

lib/Sema/TypeCheckProtocol.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5053,6 +5053,17 @@ void ConformanceChecker::ensureRequirementsAreSatisfied() {
50535053
if (where.isImplicit())
50545054
return;
50555055

5056+
Conformance->forEachTypeWitness([&](const AssociatedTypeDecl *assoc,
5057+
Type type, TypeDecl *typeDecl) -> bool {
5058+
// Make sure any associated type witnesses don't make reference to a
5059+
// parameterized existential type, or we're going to have trouble at
5060+
// runtime.
5061+
if (type->hasParameterizedExistential())
5062+
(void)diagnoseParameterizedProtocolAvailability(typeDecl->getLoc(),
5063+
where.getDeclContext());
5064+
return false;
5065+
});
5066+
50565067
for (auto req : proto->getRequirementSignature().getRequirements()) {
50575068
if (req.getKind() == RequirementKind::Conformance) {
50585069
auto depTy = req.getFirstType();

0 commit comments

Comments
 (0)