mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
To be safe, protocol witnesses need to be as available as their requirements.
Otherwise, the programmer could access an unavailable declaration by upcasting
to the protocol type and accessing the declaration via its requirement.
Prior to this commit, we enforced safety by requiring that the annotated
available range of a requirement must be completely contained within the
annotated available range of the witness.
However, there are cases where this requirement is too restrictive. Suppose
there is some super class Super with an availability-restricted method f():
class Super {
@availability(iOS, introduced=6.0)
void func f() { ... }
}
Further, suppose there is a protocol HasF with unrestricted availability:
protocol HasF {
void func f()
}
and then a limited-availability class Sub extends Super and declares a
conformance to HasF:
@availability(iOS, introduced=8.0)
class Sub: Super, HasF {
}
Sub does conform to HasF: the witness for HasF's f() requirement is Super's f().
But Super's f() is less available (iOS 6 and up) than HasF's f() requires
(all versions) and so--prior to this commit--the compiler would emit
an error.
This error is too conservative. The conforming type, Sub,
is only available on iOS 8.0 and later. And, given an environment of iOS 8.0
and later, the availability of the requirement and the witness is the same, so
the conformance is safe.
This false alarm arises in UIKit, where Super is UIView, HasF
is UIGestureRecognizerDelegate, and f() is gestureRecognizerShouldBegin().
The fix is to change the safety requirement for protocol witnesses:
we now require that the intersection of the availabilities of the conforming
type and the protocol requirement is fully contained in the intersection of the
availabilities of the conforming type and the witness. It does not matter if
the containment does not hold for versions on which the conforming type is not
available.
rdar://problem/20693144
Swift SVN r27712
215 lines
6.5 KiB
C++
215 lines
6.5 KiB
C++
//===--- Availability.h - Swift Availability Structures -----*- C++ -*-===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2014 - 2015 Apple Inc. and the Swift project authors
|
|
// Licensed under Apache License v2.0 with Runtime Library Exception
|
|
//
|
|
// See http://swift.org/LICENSE.txt for license information
|
|
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This file defines data structures for API availability.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#ifndef SWIFT_AST_AVAILABILITY_H
|
|
#define SWIFT_AST_AVAILABILITY_H
|
|
|
|
#include "swift/Basic/LLVM.h"
|
|
#include "clang/Basic/VersionTuple.h"
|
|
#include "llvm/ADT/Optional.h"
|
|
|
|
namespace swift {
|
|
class ASTContext;
|
|
class Decl;
|
|
|
|
/// A lattice of version ranges of the form [x.y.z, +Inf).
|
|
class VersionRange {
|
|
// The lattice ordering is linear:
|
|
// Empty <= ... <= [10.10.0,+Inf) <= ... [10.1.0,+Inf) <= ... <= All
|
|
// and corresponds to set inclusion.
|
|
|
|
// The concretization of lattice elements is:
|
|
// Empty: empty
|
|
// All: all versions
|
|
// x.y.x: all versions greater than or equal to to x.y.z
|
|
|
|
enum class ExtremalRange { Empty, All };
|
|
|
|
// A version range is either an extremal value (Empty, All) or
|
|
// a single version tuple value representing the lower end point x.y.z of a
|
|
// range [x.y.z, +Inf).
|
|
union {
|
|
clang::VersionTuple LowerEndpoint;
|
|
ExtremalRange ExtremalValue;
|
|
};
|
|
|
|
unsigned HasLowerEndpoint : 1;
|
|
|
|
public:
|
|
/// Returns true if the range of versions is empty, or false otherwise.
|
|
bool isEmpty() const {
|
|
return !HasLowerEndpoint && ExtremalValue == ExtremalRange::Empty;
|
|
}
|
|
|
|
/// Returns true if the range includes all versions, or false otherwise.
|
|
bool isAll() const {
|
|
return !HasLowerEndpoint && ExtremalValue == ExtremalRange::All;
|
|
}
|
|
|
|
/// Returns true if the range has a lower end point; that is, if it is of
|
|
/// the form [X, +Inf).
|
|
bool hasLowerEndpoint() const { return HasLowerEndpoint; }
|
|
|
|
/// Returns the range's lower endpoint.
|
|
const clang::VersionTuple &getLowerEndpoint() const {
|
|
assert(HasLowerEndpoint);
|
|
return LowerEndpoint;
|
|
}
|
|
|
|
/// Returns a representation of this range as a string for debugging purposes.
|
|
std::string getAsString() const {
|
|
if (isEmpty()) {
|
|
return "empty";
|
|
} else if (isAll()) {
|
|
return "all";
|
|
} else {
|
|
return "[" + getLowerEndpoint().getAsString() + ",+Inf)";
|
|
}
|
|
}
|
|
|
|
/// Returns true if all versions in this range are also in the Other range.
|
|
bool isContainedIn(const VersionRange &Other) const {
|
|
if (isEmpty() || Other.isAll())
|
|
return true;
|
|
|
|
if (isAll() || Other.isEmpty())
|
|
return false;
|
|
|
|
// [v1, +Inf) is contained in [v2, +Inf) if v1 >= v2
|
|
return getLowerEndpoint() >= Other.getLowerEndpoint();
|
|
}
|
|
|
|
/// Mutates this range to be the greatest lower bound of itself and Other.
|
|
void meetWith(const VersionRange &Other) {
|
|
if (isEmpty() || Other.isAll())
|
|
return;
|
|
|
|
if (isAll() || Other.isEmpty()) {
|
|
*this = Other;
|
|
return;
|
|
}
|
|
|
|
// The g.l.b of [v1, +Inf), [v2, +Inf) is [max(v1,v2), +Inf)
|
|
const clang::VersionTuple maxVersion =
|
|
std::max(this->getLowerEndpoint(), Other.getLowerEndpoint());
|
|
|
|
setLowerEndpoint(maxVersion);
|
|
}
|
|
|
|
/// Mutates this range to be a best effort over-approximation of the
|
|
/// intersection of the concretizations of this version range and Other.
|
|
void constrainWith(const VersionRange &Other) {
|
|
// We can use the meet for this because the lattice is multiplicative
|
|
// with respect to concretization--that is, the concretization
|
|
// of Range1 meet Range2 is equal to the intersection of the
|
|
// concretization of Range1 and the concretization of Range2.
|
|
// This will change if we add (-Inf, v) to our version range lattice.
|
|
meetWith(Other);
|
|
}
|
|
|
|
/// Returns a version range representing all versions.
|
|
static VersionRange all() { return VersionRange(ExtremalRange::All); }
|
|
|
|
/// Returns a version range representing no versions.
|
|
static VersionRange empty() { return VersionRange(ExtremalRange::Empty); }
|
|
|
|
/// Returns a version range representing all versions greater than or equal
|
|
/// to the passed-in version.
|
|
static VersionRange allGTE(const clang::VersionTuple &EndPoint) {
|
|
return VersionRange(EndPoint);
|
|
}
|
|
|
|
private:
|
|
VersionRange(const clang::VersionTuple &LowerEndpoint) {
|
|
setLowerEndpoint(LowerEndpoint);
|
|
}
|
|
|
|
VersionRange(ExtremalRange ExtremalValue) {
|
|
setExtremalRange(ExtremalValue);
|
|
}
|
|
|
|
void setExtremalRange(ExtremalRange Version) {
|
|
HasLowerEndpoint = 0;
|
|
ExtremalValue = Version;
|
|
}
|
|
|
|
void setLowerEndpoint(const clang::VersionTuple &Version) {
|
|
HasLowerEndpoint = 1;
|
|
LowerEndpoint = Version;
|
|
}
|
|
};
|
|
|
|
/// Records the reason a declaration is potentially unavailable.
|
|
class UnavailabilityReason {
|
|
public:
|
|
enum class Kind {
|
|
/// The declaration is potentially unavailable because it requires an OS
|
|
/// version range that is not guaranteed by the minimum deployment
|
|
/// target.
|
|
RequiresOSVersionRange,
|
|
|
|
/// The declaration is potentially unavailable because it is explicitly
|
|
/// weakly linked.
|
|
ExplicitlyWeakLinked
|
|
};
|
|
|
|
private:
|
|
// A value of None indicates the declaration is potentially unavailable
|
|
// because it is explicitly weak linked.
|
|
Optional<VersionRange> RequiredDeploymentRange;
|
|
|
|
UnavailabilityReason(const Optional<VersionRange> &RequiredDeploymentRange)
|
|
: RequiredDeploymentRange(RequiredDeploymentRange) {}
|
|
|
|
public:
|
|
static UnavailabilityReason explicitlyWeaklyLinked() {
|
|
return UnavailabilityReason(None);
|
|
}
|
|
|
|
static UnavailabilityReason requiresVersionRange(const VersionRange Range) {
|
|
return UnavailabilityReason(Range);
|
|
}
|
|
|
|
Kind getReasonKind() const {
|
|
if (RequiredDeploymentRange.hasValue()) {
|
|
return Kind::RequiresOSVersionRange;
|
|
} else {
|
|
return Kind::ExplicitlyWeakLinked;
|
|
}
|
|
}
|
|
|
|
const VersionRange &getRequiredOSVersionRange() const {
|
|
assert(getReasonKind() == Kind::RequiresOSVersionRange);
|
|
return RequiredDeploymentRange.getValue();
|
|
}
|
|
};
|
|
|
|
|
|
class AvailabilityInference {
|
|
public:
|
|
/// Infers the common availability required to access an array of
|
|
/// declarations and adds attributes reflecting that availability
|
|
/// to ToDecl.
|
|
static void
|
|
applyInferredAvailabilityAttrs(Decl *ToDecl,
|
|
ArrayRef<const Decl *> InferredFromDecls,
|
|
ASTContext &Context);
|
|
};
|
|
|
|
} // end namespace swift
|
|
|
|
#endif
|