//===--- 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 RequiredDeploymentRange; UnavailabilityReason(const Optional &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 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 annotatedAvailableRange(const Decl *D, ASTContext &C); }; } // end namespace swift #endif