//===--- 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 intersectionElements = intersection(member) guard !intersectionElements.isEmpty else { return nil } self.subtract(member) return intersectionElements } /// 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) } } extension OptionSet where RawValue: FixedWidthInteger, Element == Self { @_alwaysEmitIntoClient @discardableResult public mutating func insert( _ newMember: Element ) -> (inserted: Bool, memberAfterInsert: Element) { let inserted = !self.contains(newMember) self = Self(rawValue: self.rawValue | newMember.rawValue) return (inserted, newMember) } }