Skip to content

Diagnose Availability for Parameterized Existential Types #58655

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

Merged
merged 4 commits into from
May 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions include/swift/AST/ASTContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -868,6 +868,10 @@ class ASTContext final {
/// swift_isUniquelyReferenced functions.
AvailabilityContext getObjCIsUniquelyReferencedAvailability();

/// Get the runtime availability of metadata manipulation runtime functions
/// for extended existential types.
AvailabilityContext getParameterizedExistentialRuntimeAvailability();

/// Get the runtime availability of features introduced in the Swift 5.2
/// compiler for the target platform.
AvailabilityContext getSwift52Availability();
Expand Down
5 changes: 5 additions & 0 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -5589,6 +5589,11 @@ ERROR(availability_concurrency_only_version_newer, none,
"concurrency is only available in %0 %1 or newer",
(StringRef, llvm::VersionTuple))

ERROR(availability_parameterized_protocol_only_version_newer, none,
"runtime support for parameterized protocol types is only available in "
"%0 %1 or newer",
(StringRef, llvm::VersionTuple))

NOTE(availability_guard_with_version_check, none,
"add 'if #available' version check", ())

Expand Down
16 changes: 15 additions & 1 deletion include/swift/AST/Types.h
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,10 @@ class RecursiveTypeProperties {
/// type sequence
HasTypeSequence = 0x1000,

Last_Property = HasTypeSequence
/// This type contains a parameterized existential type \c any P<T>.
HasParameterizedExistential = 0x2000,

Last_Property = HasParameterizedExistential
};
enum { BitWidth = countBitsUsed(Property::Last_Property) };

Expand Down Expand Up @@ -223,6 +226,12 @@ class RecursiveTypeProperties {

bool hasTypeSequence() const { return Bits & HasTypeSequence; }

/// Does a type with these properties structurally contain a
/// parameterized existential type?
bool hasParameterizedExistential() const {
return Bits & HasParameterizedExistential;
}

/// Returns the set of properties present in either set.
friend RecursiveTypeProperties operator|(Property lhs, Property rhs) {
return RecursiveTypeProperties(unsigned(lhs) | unsigned(rhs));
Expand Down Expand Up @@ -626,6 +635,11 @@ class alignas(1 << TypeAlignInBits) TypeBase
return getRecursiveProperties().hasTypeSequence();
}

/// Determine whether the type involves a parameterized existential type.
bool hasParameterizedExistential() const {
return getRecursiveProperties().hasParameterizedExistential();
}

/// Determine whether the type involves the given opened existential
/// archetype.
bool hasOpenedExistentialWithRoot(const OpenedArchetypeType *root) const;
Expand Down
8 changes: 6 additions & 2 deletions lib/AST/ASTContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3441,6 +3441,8 @@ ExistentialMetatypeType::get(Type T, Optional<MetatypeRepresentation> repr,
T = existential->getConstraintType();

auto properties = T->getRecursiveProperties();
if (T->is<ParameterizedProtocolType>())
properties |= RecursiveTypeProperties::HasParameterizedExistential;
auto arena = getArena(properties);

unsigned reprKey;
Expand Down Expand Up @@ -3536,7 +3538,7 @@ isAnyFunctionTypeCanonical(ArrayRef<AnyFunctionType::Param> params,
static RecursiveTypeProperties
getGenericFunctionRecursiveProperties(ArrayRef<AnyFunctionType::Param> params,
Type result) {
static_assert(RecursiveTypeProperties::BitWidth == 13,
static_assert(RecursiveTypeProperties::BitWidth == 14,
"revisit this if you add new recursive type properties");
RecursiveTypeProperties properties;

Expand Down Expand Up @@ -4139,7 +4141,7 @@ CanSILFunctionType SILFunctionType::get(
void *mem = ctx.Allocate(bytes, alignof(SILFunctionType));

RecursiveTypeProperties properties;
static_assert(RecursiveTypeProperties::BitWidth == 13,
static_assert(RecursiveTypeProperties::BitWidth == 14,
"revisit this if you add new recursive type properties");
for (auto &param : params)
properties |= param.getInterfaceType()->getRecursiveProperties();
Expand Down Expand Up @@ -4257,6 +4259,8 @@ Type ExistentialType::get(Type constraint, bool forceExistential) {
assert(constraint->isConstraintType());

auto properties = constraint->getRecursiveProperties();
if (constraint->is<ParameterizedProtocolType>())
properties |= RecursiveTypeProperties::HasParameterizedExistential;
auto arena = getArena(properties);

auto &entry = C.getImpl().getArena(arena).ExistentialTypes[constraint];
Expand Down
5 changes: 5 additions & 0 deletions lib/AST/Availability.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,11 @@ AvailabilityContext ASTContext::getObjCIsUniquelyReferencedAvailability() {
return getSwift56Availability();
}

AvailabilityContext
ASTContext::getParameterizedExistentialRuntimeAvailability() {
return getSwift57Availability();
}

AvailabilityContext ASTContext::getSwift52Availability() {
auto target = LangOpts.Target;

Expand Down
119 changes: 115 additions & 4 deletions lib/Sema/TypeCheckAvailability.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,12 @@
//===----------------------------------------------------------------------===//

#include "TypeCheckAvailability.h"
#include "MiscDiagnostics.h"
#include "TypeCheckConcurrency.h"
#include "TypeChecker.h"
#include "TypeCheckObjC.h"
#include "MiscDiagnostics.h"
#include "TypeChecker.h"
#include "swift/AST/ASTWalker.h"
#include "swift/AST/GenericEnvironment.h"
#include "swift/AST/Initializer.h"
#include "swift/AST/NameLookup.h"
#include "swift/AST/Pattern.h"
Expand Down Expand Up @@ -2773,6 +2774,74 @@ bool isSubscriptReturningString(const ValueDecl *D, ASTContext &Context) {
return resultTy->isString();
}

static bool diagnosePotentialParameterizedProtocolUnavailability(
SourceRange ReferenceRange, const DeclContext *ReferenceDC,
const UnavailabilityReason &Reason) {
ASTContext &Context = ReferenceDC->getASTContext();

auto RequiredRange = Reason.getRequiredOSVersionRange();
{
auto Err = Context.Diags.diagnose(
ReferenceRange.Start,
diag::availability_parameterized_protocol_only_version_newer,
prettyPlatformString(targetPlatform(Context.LangOpts)),
Reason.getRequiredOSVersionRange().getLowerEndpoint());

// Direct a fixit to the error if an existing guard is nearly-correct
if (fixAvailabilityByNarrowingNearbyVersionCheck(
ReferenceRange, ReferenceDC, RequiredRange, Context, Err))
return true;
}
fixAvailability(ReferenceRange, ReferenceDC, RequiredRange, Context);
return true;
}

bool swift::diagnoseParameterizedProtocolAvailability(
SourceRange ReferenceRange, const DeclContext *ReferenceDC) {
// Check the availability of parameterized existential runtime support.
ASTContext &ctx = ReferenceDC->getASTContext();
if (ctx.LangOpts.DisableAvailabilityChecking)
return false;

if (!shouldCheckAvailability(ReferenceDC->getAsDecl()))
return false;

auto runningOS = TypeChecker::overApproximateAvailabilityAtLocation(
ReferenceRange.Start, ReferenceDC);
auto availability = ctx.getParameterizedExistentialRuntimeAvailability();
if (!runningOS.isContainedIn(availability)) {
return diagnosePotentialParameterizedProtocolUnavailability(
ReferenceRange, ReferenceDC,
UnavailabilityReason::requiresVersionRange(
availability.getOSVersion()));
}
return false;
}

static void
maybeDiagParameterizedExistentialErasure(ErasureExpr *EE,
const ExportContext &Where) {
if (auto *OE = dyn_cast<OpaqueValueExpr>(EE->getSubExpr())) {
auto *OAT = OE->getType()->getAs<OpenedArchetypeType>();
if (!OAT)
return;

auto opened = OAT->getGenericEnvironment()->getOpenedExistentialType();
if (!opened || !opened->hasParameterizedExistential())
return;

(void)diagnoseParameterizedProtocolAvailability(EE->getLoc(),
Where.getDeclContext());
}

if (EE->getType() &&
EE->getType()->isAny() &&
EE->getSubExpr()->getType()->hasParameterizedExistential()) {
(void)diagnoseParameterizedProtocolAvailability(EE->getLoc(),
Where.getDeclContext());
}
}

bool swift::diagnoseExplicitUnavailability(
const ValueDecl *D,
SourceRange R,
Expand Down Expand Up @@ -2989,6 +3058,17 @@ class ExprAvailabilityWalker : public ASTWalker {
diagnoseDeclRefAvailability(Context.getRegexDecl(), Range);
diagnoseDeclRefAvailability(RLE->getInitializer(), Range);
}
if (auto *EE = dyn_cast<ErasureExpr>(E)) {
maybeDiagParameterizedExistentialErasure(EE, Where);
}
if (auto *CC = dyn_cast<ExplicitCastExpr>(E)) {
if (!isa<CoerceExpr>(CC) &&
CC->getCastType()->hasParameterizedExistential()) {
SourceLoc loc = CC->getCastTypeRepr() ? CC->getCastTypeRepr()->getLoc()
: E->getLoc();
diagnoseParameterizedProtocolAvailability(loc, Where.getDeclContext());
}
}
if (auto KP = dyn_cast<KeyPathExpr>(E)) {
maybeDiagKeyPath(KP);
}
Expand Down Expand Up @@ -3749,7 +3829,12 @@ class ProblematicTypeFinder : public TypeDeclFinder {

ModuleDecl *useModule = Where.getDeclContext()->getParentModule();
auto subs = ty->getContextSubstitutionMap(useModule, ty->getDecl());
(void) diagnoseSubstitutionMapAvailability(Loc, subs, Where);
(void)diagnoseSubstitutionMapAvailability(
Loc, subs, Where,
/*depTy=*/Type(),
/*replacementTy=*/Type(),
/*useConformanceAvailabilityErrorsOption=*/false,
/*suppressParameterizationCheckForOptional=*/ty->isOptional());
return Action::Continue;
}

Expand Down Expand Up @@ -3781,6 +3866,19 @@ class ProblematicTypeFinder : public TypeDeclFinder {
}
}

if (auto *TT = T->getAs<TupleType>()) {
for (auto component : TT->getElementTypes()) {
// Let the walker find inner tuple types, we only want to diagnose
// non-compound components.
if (component->is<TupleType>())
continue;

if (component->hasParameterizedExistential())
(void)diagnoseParameterizedProtocolAvailability(
Loc, Where.getDeclContext());
}
}

return TypeDeclFinder::walkToTypePost(T);
}
};
Expand Down Expand Up @@ -3895,14 +3993,27 @@ swift::diagnoseSubstitutionMapAvailability(SourceLoc loc,
SubstitutionMap subs,
const ExportContext &where,
Type depTy, Type replacementTy,
bool useConformanceAvailabilityErrorsOption) {
bool useConformanceAvailabilityErrorsOption,
bool suppressParameterizationCheckForOptional) {
bool hadAnyIssues = false;
for (ProtocolConformanceRef conformance : subs.getConformances()) {
if (diagnoseConformanceAvailability(loc, conformance, where,
depTy, replacementTy,
useConformanceAvailabilityErrorsOption))
hadAnyIssues = true;
}

// If we're looking at \c (any P)? (or any other depth of optional) then
// there's no availability problem.
if (suppressParameterizationCheckForOptional)
return hadAnyIssues;

for (auto replacement : subs.getReplacementTypes()) {
if (replacement->hasParameterizedExistential())
if (diagnoseParameterizedProtocolAvailability(loc,
where.getDeclContext()))
hadAnyIssues = true;
}
return hadAnyIssues;
}

Expand Down
20 changes: 13 additions & 7 deletions lib/Sema/TypeCheckAvailability.h
Original file line number Diff line number Diff line change
Expand Up @@ -224,13 +224,14 @@ diagnoseConformanceAvailability(SourceLoc loc,
Type replacementTy=Type(),
bool useConformanceAvailabilityErrorsOption = false);

bool
diagnoseSubstitutionMapAvailability(SourceLoc loc,
SubstitutionMap subs,
const ExportContext &context,
Type depTy=Type(),
Type replacementTy=Type(),
bool useConformanceAvailabilityErrorsOption = false);
bool diagnoseSubstitutionMapAvailability(
SourceLoc loc,
SubstitutionMap subs,
const ExportContext &context,
Type depTy = Type(),
Type replacementTy = Type(),
bool useConformanceAvailabilityErrorsOption = false,
bool suppressParameterizationCheckForOptional = false);

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

/// Diagnose uses of the runtime features of parameterized protools. Returns
/// \c true if a diagnostic was emitted.
bool diagnoseParameterizedProtocolAvailability(SourceRange loc,
const DeclContext *DC);

/// Check if \p decl has a introduction version required by -require-explicit-availability
void checkExplicitAvailability(Decl *decl);

Expand Down
11 changes: 11 additions & 0 deletions lib/Sema/TypeCheckProtocol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4919,6 +4919,17 @@ void ConformanceChecker::ensureRequirementsAreSatisfied() {
if (where.isImplicit())
return;

Conformance->forEachTypeWitness([&](const AssociatedTypeDecl *assoc,
Type type, TypeDecl *typeDecl) -> bool {
// Make sure any associated type witnesses don't make reference to a
// parameterized existential type, or we're going to have trouble at
// runtime.
if (type->hasParameterizedExistential())
(void)diagnoseParameterizedProtocolAvailability(typeDecl->getLoc(),
where.getDeclContext());
return false;
});

for (auto req : proto->getRequirementSignature().getRequirements()) {
if (req.getKind() == RequirementKind::Conformance) {
auto depTy = req.getFirstType();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// RUN: %target-typecheck-verify-swift -enable-parameterized-existential-types
// RUN: %target-typecheck-verify-swift -enable-parameterized-existential-types -disable-availability-checking
//
// FIXME: Merge this file with existential_metatypes.swift once -enable-parameterized-existential-types becomes the default

Expand Down
2 changes: 1 addition & 1 deletion test/Interpreter/parameterized_existentials.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// RUN: %target-run-simple-swift(-Xfrontend -enable-parameterized-existential-types)
// RUN: %target-run-simple-swift(-Xfrontend -enable-parameterized-existential-types -Xfrontend -disable-availability-checking)
// REQUIRES: executable_test

// This test requires the new existential shape metadata accessors which are
Expand Down
2 changes: 1 addition & 1 deletion test/RemoteAST/parameterized_existentials.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// RUN: %target-swift-remoteast-test -enable-parameterized-existential-types %s | %FileCheck %s
// RUN: %target-swift-remoteast-test -enable-parameterized-existential-types -disable-availability-checking %s | %FileCheck %s

// REQUIRES: swift-remoteast-test

Expand Down
2 changes: 1 addition & 1 deletion test/SILGen/parameterized_existentials.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// RUN: %target-swift-emit-silgen -module-name parameterized -enable-parameterized-existential-types %s | %FileCheck %s
// RUN: %target-swift-emit-silgen -module-name parameterized -enable-parameterized-existential-types -disable-availability-checking %s | %FileCheck %s

protocol P<T, U, V> {
associatedtype T
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// RUN: %target-swift-frontend %s -emit-sil -enable-parameterized-existential-types -O -o - | %FileCheck %s
// RUN: %target-swift-frontend %s -emit-sil -enable-parameterized-existential-types -disable-availability-checking -O -o - | %FileCheck %s

public protocol P<T> {
associatedtype T
Expand Down
Loading