blob: f8c6b753aa80c61f4416b6ffd6cc684a8e2fb153 [file] [log] [blame]
//===--- OptionSet.swift --------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
/// A type that presents a mathematical set interface to a bit set.
///
/// You use the `OptionSet` protocol to represent bitset types, where
/// individual bits represent members of a set. Adopting this protocol in
/// your custom types lets you perform set-related operations such as
/// membership tests, unions, and intersections on those types. What's more,
/// when implemented using specific criteria, adoption of this protocol
/// requires no extra work on your part.
///
/// When creating an option set, include a `rawValue` property in your type
/// declaration. For your type to automatically receive default implementations
/// for set-related operations, the `rawValue` property must be of a type that
/// conforms to the `FixedWidthInteger` protocol, such as `Int` or `UInt8`.
/// Next, create unique options as static properties of your custom type using
/// unique powers of two (1, 2, 4, 8, 16, and so forth) for each individual
/// property's raw value so that each property can be represented by a single
/// bit of the type's raw value.
///
/// For example, consider a custom type called `ShippingOptions` that is an
/// option set of the possible ways to ship a customer's purchase.
/// `ShippingOptions` includes a `rawValue` property of type `Int` that stores
/// the bit mask of available shipping options. The static members `nextDay`,
/// `secondDay`, `priority`, and `standard` are unique, individual options.
///
/// struct ShippingOptions: OptionSet {
/// let rawValue: Int
///
/// static let nextDay = ShippingOptions(rawValue: 1 << 0)
/// static let secondDay = ShippingOptions(rawValue: 1 << 1)
/// static let priority = ShippingOptions(rawValue: 1 << 2)
/// static let standard = ShippingOptions(rawValue: 1 << 3)
///
/// static let express: ShippingOptions = [.nextDay, .secondDay]
/// static let all: ShippingOptions = [.express, .priority, .standard]
/// }
///
/// Declare additional preconfigured option set values as static properties
/// initialized with an array literal containing other option values. In the
/// example, because the `express` static property is assigned an array
/// literal with the `nextDay` and `secondDay` options, it will contain those
/// two elements.
///
/// Using an Option Set Type
/// ========================
///
/// When you need to create an instance of an option set, assign one of the
/// type's static members to your variable or constant. Alternatively, to
/// create an option set instance with multiple members, assign an array
/// literal with multiple static members of the option set. To create an empty
/// instance, assign an empty array literal to your variable.
///
/// let singleOption: ShippingOptions = .priority
/// let multipleOptions: ShippingOptions = [.nextDay, .secondDay, .priority]
/// let noOptions: ShippingOptions = []
///
/// Use set-related operations to check for membership and to add or remove
/// members from an instance of your custom option set type. The following
/// example shows how you can determine free shipping options based on a
/// customer's purchase price:
///
/// let purchasePrice = 87.55
///
/// var freeOptions: ShippingOptions = []
/// if purchasePrice > 50 {
/// freeOptions.insert(.priority)
/// }
///
/// if freeOptions.contains(.priority) {
/// print("You've earned free priority shipping!")
/// } else {
/// print("Add more to your cart for free priority shipping!")
/// }
/// // Prints "You've earned free priority shipping!"
public protocol OptionSet : SetAlgebra, RawRepresentable {
// We can't constrain the associated Element type to be the same as
// Self, but we can do almost as well with a default and a
// constrained extension
/// The element type of the option set.
///
/// To inherit all the default implementations from the `OptionSet` protocol,
/// the `Element` type must be `Self`, the default.
associatedtype Element = Self
// FIXME: This initializer should just be the failable init from
// RawRepresentable. Unfortunately, current language limitations
// that prevent non-failable initializers from forwarding to
// failable ones would prevent us from generating the non-failing
// default (zero-argument) initializer. Since OptionSet's main
// purpose is to create convenient conformances to SetAlgebra,
// we opt for a non-failable initializer.
/// Creates a new option set from the given raw value.
///
/// This initializer always succeeds, even if the value passed as `rawValue`
/// exceeds the static properties declared as part of the option set. This
/// example creates an instance of `ShippingOptions` with a raw value beyond
/// the highest element, with a bit mask that effectively contains all the
/// declared static members.
///
/// let extraOptions = ShippingOptions(rawValue: 255)
/// print(extraOptions.isStrictSuperset(of: .all))
/// // Prints "true"
///
/// - Parameter rawValue: The raw value of the option set to create. Each bit
/// of `rawValue` potentially represents an element of the option set,
/// though raw values may include bits that are not defined as distinct
/// values of the `OptionSet` type.
init(rawValue: RawValue)
}
/// `OptionSet` requirements for which default implementations
/// are supplied.
///
/// - Note: A type conforming to `OptionSet` can implement any of
/// these initializers or methods, and those implementations will be
/// used in lieu of these defaults.
extension OptionSet {
/// Returns a new option set of the elements contained in this set, in the
/// given set, or in both.
///
/// This example uses the `union(_:)` method to add two more shipping options
/// to the default set.
///
/// let defaultShipping = ShippingOptions.standard
/// let memberShipping = defaultShipping.union([.secondDay, .priority])
/// print(memberShipping.contains(.priority))
/// // Prints "true"
///
/// - Parameter other: An option set.
/// - Returns: A new option set made up of the elements contained in this
/// set, in `other`, or in both.
@inlinable // generic-performance
public func union(_ other: Self) -> Self {
var r: Self = Self(rawValue: self.rawValue)
r.formUnion(other)
return r
}
/// Returns a new option set with only the elements contained in both this
/// set and the given set.
///
/// This example uses the `intersection(_:)` method to limit the available
/// shipping options to what can be used with a PO Box destination.
///
/// // Can only ship standard or priority to PO Boxes
/// let poboxShipping: ShippingOptions = [.standard, .priority]
/// let memberShipping: ShippingOptions =
/// [.standard, .priority, .secondDay]
///
/// let availableOptions = memberShipping.intersection(poboxShipping)
/// print(availableOptions.contains(.priority))
/// // Prints "true"
/// print(availableOptions.contains(.secondDay))
/// // Prints "false"
///
/// - Parameter other: An option set.
/// - Returns: A new option set with only the elements contained in both this
/// set and `other`.
@inlinable // generic-performance
public func intersection(_ other: Self) -> Self {
var r = Self(rawValue: self.rawValue)
r.formIntersection(other)
return r
}
/// Returns a new option set with the elements contained in this set or in
/// the given set, but not in both.
///
/// - Parameter other: An option set.
/// - Returns: A new option set with only the elements contained in either
/// this set or `other`, but not in both.
@inlinable // generic-performance
public func symmetricDifference(_ other: Self) -> Self {
var r = Self(rawValue: self.rawValue)
r.formSymmetricDifference(other)
return r
}
}
/// `OptionSet` requirements for which default implementations are
/// supplied when `Element == Self`, which is the default.
///
/// - Note: A type conforming to `OptionSet` can implement any of
/// these initializers or methods, and those implementations will be
/// used in lieu of these defaults.
extension OptionSet where Element == Self {
/// Returns a Boolean value that indicates whether a given element is a
/// member of the option set.
///
/// This example uses the `contains(_:)` method to check whether next-day
/// shipping is in the `availableOptions` instance.
///
/// let availableOptions = ShippingOptions.express
/// if availableOptions.contains(.nextDay) {
/// print("Next day shipping available")
/// }
/// // Prints "Next day shipping available"
///
/// - Parameter member: The element to look for in the option set.
/// - Returns: `true` if the option set contains `member`; otherwise,
/// `false`.
@inlinable // generic-performance
public func contains(_ member: Self) -> Bool {
return self.isSuperset(of: member)
}
/// Adds the given element to the option set if it is not already a member.
///
/// In the following example, the `.secondDay` shipping option is added to
/// the `freeOptions` option set if `purchasePrice` is greater than 50.0. For
/// the `ShippingOptions` declaration, see the `OptionSet` protocol
/// discussion.
///
/// let purchasePrice = 87.55
///
/// var freeOptions: ShippingOptions = [.standard, .priority]
/// if purchasePrice > 50 {
/// freeOptions.insert(.secondDay)
/// }
/// print(freeOptions.contains(.secondDay))
/// // Prints "true"
///
/// - Parameter newMember: The element to insert.
/// - Returns: `(true, newMember)` if `newMember` was not contained in
/// `self`. Otherwise, returns `(false, oldMember)`, where `oldMember` is
/// the member of the set equal to `newMember`.
@inlinable // generic-performance
@discardableResult
public mutating func insert(
_ newMember: Element
) -> (inserted: Bool, memberAfterInsert: Element) {
let oldMember = self.intersection(newMember)
let shouldInsert = oldMember != newMember
let result = (
inserted: shouldInsert,
memberAfterInsert: shouldInsert ? newMember : oldMember)
if shouldInsert {
self.formUnion(newMember)
}
return result
}
/// Removes the given element and all elements subsumed by it.
///
/// In the following example, the `.priority` shipping option is removed from
/// the `options` option set. Attempting to remove the same shipping option
/// a second time results in `nil`, because `options` no longer contains
/// `.priority` as a member.
///
/// var options: ShippingOptions = [.secondDay, .priority]
/// let priorityOption = options.remove(.priority)
/// print(priorityOption == .priority)
/// // Prints "true"
///
/// print(options.remove(.priority))
/// // Prints "nil"
///
/// In the next example, the `.express` element is passed to `remove(_:)`.
/// Although `.express` is not a member of `options`, `.express` subsumes
/// the remaining `.secondDay` element of the option set. Therefore,
/// `options` is emptied and the intersection between `.express` and
/// `options` is returned.
///
/// let expressOption = options.remove(.express)
/// print(expressOption == .express)
/// // Prints "false"
/// print(expressOption == .secondDay)
/// // Prints "true"
///
/// - Parameter member: The element of the set to remove.
/// - Returns: The intersection of `[member]` and the set, if the
/// intersection was nonempty; otherwise, `nil`.
@inlinable // generic-performance
@discardableResult
public mutating func remove(_ member: Element) -> Element? {
let r = isSuperset(of: member) ? Optional(member) : nil
self.subtract(member)
return r
}
/// Inserts the given element into the set.
///
/// If `newMember` is not contained in the set but subsumes current members
/// of the set, the subsumed members are returned.
///
/// var options: ShippingOptions = [.secondDay, .priority]
/// let replaced = options.update(with: .express)
/// print(replaced == .secondDay)
/// // Prints "true"
///
/// - Returns: The intersection of `[newMember]` and the set if the
/// intersection was nonempty; otherwise, `nil`.
@inlinable // generic-performance
@discardableResult
public mutating func update(with newMember: Element) -> Element? {
let r = self.intersection(newMember)
self.formUnion(newMember)
return r.isEmpty ? nil : r
}
}
/// `OptionSet` requirements for which default implementations are
/// supplied when `RawValue` conforms to `FixedWidthInteger`,
/// which is the usual case. Each distinct bit of an option set's
/// `.rawValue` corresponds to a disjoint value of the `OptionSet`.
///
/// - `union` is implemented as a bitwise "or" (`|`) of `rawValue`s
/// - `intersection` is implemented as a bitwise "and" (`&`) of
/// `rawValue`s
/// - `symmetricDifference` is implemented as a bitwise "exclusive or"
/// (`^`) of `rawValue`s
///
/// - Note: A type conforming to `OptionSet` can implement any of
/// these initializers or methods, and those implementations will be
/// used in lieu of these defaults.
extension OptionSet where RawValue : FixedWidthInteger {
/// Creates an empty option set.
///
/// This initializer creates an option set with a raw value of zero.
@inlinable // generic-performance
public init() {
self.init(rawValue: 0)
}
/// Inserts the elements of another set into this option set.
///
/// This method is implemented as a `|` (bitwise OR) operation on the
/// two sets' raw values.
///
/// - Parameter other: An option set.
@inlinable // generic-performance
public mutating func formUnion(_ other: Self) {
self = Self(rawValue: self.rawValue | other.rawValue)
}
/// Removes all elements of this option set that are not
/// also present in the given set.
///
/// This method is implemented as a `&` (bitwise AND) operation on the
/// two sets' raw values.
///
/// - Parameter other: An option set.
@inlinable // generic-performance
public mutating func formIntersection(_ other: Self) {
self = Self(rawValue: self.rawValue & other.rawValue)
}
/// Replaces this set with a new set containing all elements
/// contained in either this set or the given set, but not in both.
///
/// This method is implemented as a `^` (bitwise XOR) operation on the two
/// sets' raw values.
///
/// - Parameter other: An option set.
@inlinable // generic-performance
public mutating func formSymmetricDifference(_ other: Self) {
self = Self(rawValue: self.rawValue ^ other.rawValue)
}
}