A Swift macro that generates read-only isNot* computed properties for every eligible Bool property on a type.
Instead of writing negated checks by hand, annotate a type with @Not and get peers like isNotEnabled for isEnabled, or isNotDarkMode for isDarkMode.
import Not
@Not
struct Settings {
var isDarkMode: Bool
var notificationsEnabled: Bool
}
let settings = Settings(isDarkMode: true, notificationsEnabled: false)
settings.isDarkMode // true
settings.isNotDarkMode // false
settings.isNotNotificationsEnabled // truePeers are read-only and do not mutate the original property. They evaluate !property through the property's getter.
- Swift 6.2+
- macOS 10.15+
- iOS 13+
- tvOS 13+
- watchOS 6+
- macCatalyst 13+
Macro expansion requires a Swift toolchain with macro support (Xcode 16+ or a recent Swift 6 compiler).
Add Not to your Package.swift dependencies:
dependencies: [
.package(url: "https://github.com/tomisacat/Not.git", from: "1.0.0"),
],
targets: [
.target(
name: "YourTarget",
dependencies: [
.product(name: "Not", package: "Not"),
]
),
]Then import it where you use the macro:
import NotApply @Not to a struct, class, or actor. The macro scans the type's members and generates one read-only peer per eligible Bool property.
@Not
struct FeatureFlags {
var isEnabled: Bool
let isLocked: Bool
var isVisible: Bool { true }
static let isDefaultOn: Bool = false
}Expands to include:
var isNotEnabled: Bool { !isEnabled }
var isNotLocked: Bool { !isLocked }
var isNotVisible: Bool { !isVisible }
static var isNotDefaultOn: Bool { !isDefaultOn }| Kind | Example | Generated peer |
|---|---|---|
Stored var |
var isEnabled: Bool |
var isNotEnabled: Bool { !isEnabled } |
Stored let |
let isLocked: Bool |
var isNotLocked: Bool { !isLocked } |
| Computed (getter) | var isEnabled: Bool { true } |
var isNotEnabled: Bool { !isEnabled } |
| Computed (get/set) | var isEnabled: Bool { get set } |
var isNotEnabled: Bool { !isEnabled } |
static |
static var isEnabled: Bool |
static var isNotEnabled: Bool { !isEnabled } |
static let |
static let isEnabled: Bool = true |
static var isNotEnabled: Bool { !isEnabled } |
class |
class var isEnabled: Bool |
class var isNotEnabled: Bool { !isEnabled } |
| Property wrappers | @State var isEnabled: Bool |
var isNotEnabled: Bool { !isEnabled } |
Inferred Bool |
var isActive = true |
var isNotActive: Bool { !isActive } |
| Multiple bindings | var a: Bool, b: Bool |
isNotA, isNotB |
Works on struct, class, and actor types.
The generated name is derived from the original property name:
| Original property | Generated peer |
|---|---|
enabled |
isNotEnabled |
isEnabled |
isNotEnabled |
isDarkMode |
isNotDarkMode |
notificationsEnabled |
isNotNotificationsEnabled |
Rules:
- If the name starts with
isfollowed by an uppercase letter, replace the leadingiswithisNot.isEnabled→isNotEnabled
- Otherwise, prefix
isNotand capitalize the first character of the original name.enabled→isNotEnabled
@Not and Bool.toggle() solve different problems:
| API | Behavior |
|---|---|
settings.isNotEnabled |
Read-only negated view; original value unchanged |
settings.isEnabled.toggle() |
Mutates the stored property in place |
Use @Not when you want a convenient negated getter. Use toggle() when you want to flip a stored value.
@Not
struct UserPreferences {
var isDarkMode: Bool
var isEnabled: Bool { true }
}Works with property-wrapper-backed properties. Access goes through the wrapper's getter:
@Not
struct ContentViewModel {
@Published var isVisible: Bool
@State var isExpanded = false
}@Not
struct Config {
static let isProduction: Bool = true
}
@Not
class BaseFlags {
class var isActive: Bool { true }
}@Not
actor Gatekeeper {
var isLocked: Bool
init(isLocked: Bool) {
self.isLocked = isLocked
}
}
let gate = Gatekeeper(isLocked: true)
await gate.isNotLocked // falseSee Sources/NotClient/main.swift for a runnable demo of every supported pattern.
The macro does not generate peers for:
| Case | Reason |
|---|---|
Bool? |
Optional negation is ambiguous |
| Setter-only properties | No getter to negate |
Properties marked @Not |
Explicitly excluded |
Non-Bool types |
Including inferred non-Bool initializers like var count = 0 |
extension, enum, protocol |
Invalid attachment site |
If two properties map to the same peer name, the macro emits duplicate declarations and compilation fails. For example, both enabled and isEnabled generate isNotEnabled. Use distinct base names or exclude one with @Not on the property.
Generated peers do not copy access modifiers or attributes (such as @MainActor) from the original property. If you need matching isolation, you may need to adjust access manually or wrap usage accordingly.
Clone the repository and run:
swift build
swift test
swift run NotClientTests use Swift Testing. For Swift Testing-only output:
swift test --disable-xctestNot/
├── Sources/
│ ├── Not/ Public macro declaration
│ ├── NotMacros/ Macro implementation
│ └── NotClient/ Runnable usage examples
├── Tests/
│ └── NotTests/ Expansion, naming, and integration tests
├── CHANGELOG.md
└── LICENSE
Bug reports and feature requests are welcome. Use the GitHub issue templates when opening a new issue, and follow the pull request template when submitting changes.
Please include tests for macro behavior changes:
- Expansion tests for compile-time output in
Tests/NotTests/NotMacroExpansionTests.swift - Integration tests for runtime behavior in
Tests/NotTests/NotIntegrationTests.swift - Naming tests for peer naming rules in
Tests/NotTests/NotNamingTests.swift
MIT License. See LICENSE.
See CHANGELOG.md.