mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
This is the beginning of the extension of the availability model introduced in Swift 2.0 to support two interesting things: inlineable code and binary frameworks not tied to an OS. The former is critical to having a stable standard library that isn't shipped with a client app. (For more information on both of these, see docs/LibraryEvolution.rst.) The existing availability model enforces that API is not used unless the developer has already guaranteed its existence. We want to reuse this logic for these new purposes. Additionally, certain queries about the AST are dependent on this type of information as well, e.g. "can I assume this enum will not grow any additional cases?" If the enum comes from the module being compiled, the answer is usually "yes", but not if the code asking the question may be inlined into another binary! (This latter purpose is currently served by ResilienceExpansion down at the SIL level; my goal is to replace ResilienceExpansion with AvailabilityContext. It's a bit heavier but would also allow additional optimization in the future.) This commit does not change any logic; it only wraps existing uses of VersionRange in AvailabilityContext if they're not strictly referring to the OS version.
347 lines
11 KiB
C++
347 lines
11 KiB
C++
//===--- Availability.h - Swift Availability Structures ---------*- C++ -*-===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2014 - 2016 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/AST/Type.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 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 a best-effort underapproximation of
|
|
/// the intersection of itself and Other. This is the
|
|
/// meet operation (greatest lower bound) in the version range lattice.
|
|
void intersectWith(const VersionRange &Other) {
|
|
// With the existing lattice this operation is precise. If the lattice
|
|
// is ever extended it is important that this operation be an
|
|
// underapproximation of intersection.
|
|
|
|
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 the union of itself and Other. This is the
|
|
/// join operator (least upper bound) in the version range lattice.
|
|
void unionWith(const VersionRange &Other) {
|
|
// With the existing lattice this operation is precise. If the lattice
|
|
// is ever extended it is important that this operation be an
|
|
// overapproximation of union.
|
|
if (isAll() || Other.isEmpty())
|
|
return;
|
|
|
|
if (isEmpty() || Other.isAll()) {
|
|
*this = Other;
|
|
return;
|
|
}
|
|
|
|
// The l.u.b of [v1, +Inf), [v2, +Inf) is [min(v1,v2), +Inf)
|
|
const clang::VersionTuple minVersion =
|
|
std::min(this->getLowerEndpoint(), Other.getLowerEndpoint());
|
|
|
|
setLowerEndpoint(minVersion);
|
|
}
|
|
|
|
/// 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 intersection 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.
|
|
intersectWith(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();
|
|
}
|
|
};
|
|
|
|
/// Represents everything that a particular chunk of code may assume about its
|
|
/// runtime environment.
|
|
///
|
|
/// The AvailabilityContext structure forms a [lattice][], which allows it to
|
|
/// have meaningful union and intersection operations ("join" and "meet"),
|
|
/// which use conservative approximations to prevent availability violations.
|
|
/// See #unionWith, #intersectWith, and #constrainWith.
|
|
///
|
|
/// [lattice]: http://mathworld.wolfram.com/Lattice.html
|
|
class AvailabilityContext {
|
|
VersionRange OSVersion;
|
|
public:
|
|
/// Creates a context that requires certain versions of the target OS.
|
|
explicit AvailabilityContext(VersionRange OSVersion) : OSVersion(OSVersion) {}
|
|
|
|
/// Creates a context that imposes no constraints.
|
|
///
|
|
/// \see isAlwaysAvailable
|
|
static AvailabilityContext alwaysAvailable() {
|
|
return AvailabilityContext(VersionRange::all());
|
|
}
|
|
|
|
/// Creates a context that can never actually occur.
|
|
///
|
|
/// \see isKnownUnreachable
|
|
static AvailabilityContext neverAvailable() {
|
|
return AvailabilityContext(VersionRange::empty());
|
|
}
|
|
|
|
/// Returns the range of possible OS versions required by this context.
|
|
VersionRange getOSVersion() const { return OSVersion; }
|
|
|
|
/// Returns true if \p other makes stronger guarantees than this context.
|
|
///
|
|
/// That is, `a.isContainedIn(b)` implies `a.union(b) == b`.
|
|
bool isContainedIn(AvailabilityContext other) const {
|
|
return OSVersion.isContainedIn(other.OSVersion);
|
|
}
|
|
|
|
/// Returns true if this context has constraints that make it impossible to
|
|
/// actually occur.
|
|
///
|
|
/// For example, the else branch of a `#available` check for iOS 8.0 when the
|
|
/// containing function already requires iOS 9.
|
|
bool isKnownUnreachable() const {
|
|
return OSVersion.isEmpty();
|
|
}
|
|
|
|
/// Returns true if there are no constraints on this context; that is,
|
|
/// nothing can be assumed.
|
|
bool isAlwaysAvailable() const {
|
|
return OSVersion.isAll();
|
|
}
|
|
|
|
/// Produces an under-approximation of the intersection of the two
|
|
/// availability contexts.
|
|
///
|
|
/// That is, if the intersection can't be represented exactly, prefer
|
|
/// treating some valid deployment environments as unavailable. This is the
|
|
/// "meet" operation of the lattice.
|
|
///
|
|
/// As an example, this is used when figuring out the required availability
|
|
/// for a type that references multiple nominal decls.
|
|
void intersectWith(AvailabilityContext other) {
|
|
OSVersion.intersectWith(other.getOSVersion());
|
|
}
|
|
|
|
/// Produces an over-approximation of the intersection of the two
|
|
/// availability contexts.
|
|
///
|
|
/// That is, if the intersection can't be represented exactly, prefer
|
|
/// treating some invalid deployment environments as available.
|
|
///
|
|
/// As an example, this is used for the true branch of `#available`.
|
|
void constrainWith(AvailabilityContext other) {
|
|
OSVersion.constrainWith(other.getOSVersion());
|
|
}
|
|
|
|
/// Produces an over-approximation of the union of two availability contexts.
|
|
///
|
|
/// That is, if the union can't be represented exactly, prefer treating
|
|
/// some invalid deployment environments as available. This is the "join"
|
|
/// operation of the lattice.
|
|
///
|
|
/// As an example, this is used for the else branch of a conditional with
|
|
/// multiple `#available` checks.
|
|
void unionWith(AvailabilityContext other) {
|
|
OSVersion.unionWith(other.getOSVersion());
|
|
}
|
|
};
|
|
|
|
|
|
class AvailabilityInference {
|
|
public:
|
|
/// Infers the common availability required to access an array of
|
|
/// declarations and adds attributes reflecting that availability
|
|
/// to ToDecl.
|
|
static void
|
|
applyInferredAvailableAttrs(Decl *ToDecl,
|
|
ArrayRef<const Decl *> InferredFromDecls,
|
|
ASTContext &Context);
|
|
|
|
static AvailabilityContext inferForType(Type t);
|
|
|
|
/// \brief Returns the context where a declaration is available
|
|
/// We assume a declaration without an annotation is always available.
|
|
static AvailabilityContext availableRange(const Decl *D, ASTContext &C);
|
|
|
|
/// \brief Returns the context for which the declaration
|
|
/// is annotated as available, or None if the declaration
|
|
/// has no availability annotation.
|
|
static Optional<AvailabilityContext> annotatedAvailableRange(const Decl *D,
|
|
ASTContext &C);
|
|
|
|
};
|
|
|
|
} // end namespace swift
|
|
|
|
#endif
|