Repository files navigation

HashableMacro

TestsPlatforms

@Hashable is a Swift macro for adding Hashable conformance. It is particularly useful when synthesised conformance is not possible, such as with classes or a struct with 1 or more non-hashable properties.

The @Hashable macro is applied to the type that will conform to Hashable and the Hashed macro is applied to each of the properties that should contribute to the Hashable conformance.

import HashableMacro
/// A struct that uses the ``stringProperty`` and ``intProperty`` for `Hashable` conformance.
@HashablestructMyStruct{
// Any property that is hashable is supported.
@HashedletstringProperty:String
// Works on private properties, too.
@HashedprivateletintProperty:Int
// Non-decorated properties are ignored
letnotHashableType:NotHashableType}

All decorated properties are included in both the == and hash(into:) implementations, ensuring the contract of Hashable is upheld:

Two instances that are equal must feed the same values to Hasher in hash(into:), in the same order.

DocC documentation for HashableMacro is hosted by Swift Package Index. Most of the implementation can be seen in the Macros.swift file.

@NotHashed Macro

The @NotHashed macro can be applied to properties that should not be included in the Hashable conformance. If this macro is used to decorate a property the @Hashed macro should not be used to decorate a property in the same type.

This can be useful for types that have a smaller number of non-hashable properties than hashable properties.

/// A struct that uses the ``stringProperty`` and ``intProperty`` for `Hashable` conformance.
@HashablestructMyStruct{
// Implicitly used for `Hashable` conformance
letstringProperty:String
// Implicitly used for `Hashable` conformance
privateletintProperty:Int
// Explicitly excluded from `Hashable` conformance
@NotHashedletnotHashableType:NotHashableType}

@Hashable Only

If the @Hashable macro is added but no properties are decorated with @Hashed or @NotHashed then all stored properties will be used.

/// A struct that uses the ``stringProperty`` and ``intProperty`` for `Hashable` conformance.
@HashablestructMyStruct{
// Implicitly used for `Hashable` conformance
letstringProperty:String
// Implicitly used for `Hashable` conformance
privateletintProperty:Int
// Implicitly excluded from `Hashable` conformance
varcomputedProperty:Bool{
intProperty >0}}

One (fairly minor) advantage of this over adding Hashable conformance without the macro is that you can see the code being produce via Right Click → Expand Macro.

NSObject Support

When a type implements NSObjectProtocol (e.g. it inherits from NSObject) it should override hash and isEqual(_:), not hash(into:) and ==. @Hashable detects when it is attached to a type conforming to NSObjectProtocol and will provide the hash property and isEqual(_:) function instead.

@Hashable will also provide an isEqual(to:) function that takes a parameter that matches Self, which will also have an appropriately named Objective-C function.

import HashableMacro
@HashablefinalclassPerson:NSObject{@Hashedvarname:String=""}extensionPerson{overridevarhash:Int{varhasher=Hasher()
hasher.combine(self.name)return hasher.finalize()}}extensionPerson{overridefunc isEqual(_ object:Any?)->Bool{guardlet object = object as?Personelse{returnfalse}guardtype(of:self)==type(of: object)else{returnfalse}returnself.isEqual(to: object)}@objc(isEqualToPerson:)func isEqual(to object:Person)->Bool{returnself.name == object.name
}}

finalhash(into:) Function

When the @Hashable macro is added to a class the generated hash(into:) function is marked final. This is because subclasses should not overload ==. There are many reasons why this can be a bad idea, but specifically in Swift this does not work because:

  • != is not part of the Equatable protocol, but rather an extension on Equatable, causing it to always use the == implementation from the class that adds Equatable conformance
    • It is possible to overload != but this is still not a good idea because...
  • Anything that uses generics to compare the values, for example XCTAssertEqual, will use the == implementation from the class that adds Equatable conformance
    • It is possible to work around this by using a separate function, in a similar way to NSObject, which is then called from ==

If this is an issue for your usage you can pass finalHashInto: false to the macro, but it will not attempt to call super or use properties from the superclass.

This is not something the macro aims to solve.

License

MIT

About

Add Hashable conformance by decorating properties with Swift macros

Resources

Stars

26 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

HashableMacro

TestsPlatforms

@Hashable is a Swift macro for adding Hashable conformance. It is particularly useful when synthesised conformance is not possible, such as with classes or a struct with 1 or more non-hashable properties.

The @Hashable macro is applied to the type that will conform to Hashable and the Hashed macro is applied to each of the properties that should contribute to the Hashable conformance.

import HashableMacro
/// A struct that uses the ``stringProperty`` and ``intProperty`` for `Hashable` conformance.
@HashablestructMyStruct{
// Any property that is hashable is supported.
@HashedletstringProperty:String
// Works on private properties, too.
@HashedprivateletintProperty:Int
// Non-decorated properties are ignored
letnotHashableType:NotHashableType}

All decorated properties are included in both the == and hash(into:) implementations, ensuring the contract of Hashable is upheld:

Two instances that are equal must feed the same values to Hasher in hash(into:), in the same order.

DocC documentation for HashableMacro is hosted by Swift Package Index. Most of the implementation can be seen in the Macros.swift file.

@NotHashed Macro

The @NotHashed macro can be applied to properties that should not be included in the Hashable conformance. If this macro is used to decorate a property the @Hashed macro should not be used to decorate a property in the same type.

This can be useful for types that have a smaller number of non-hashable properties than hashable properties.

/// A struct that uses the ``stringProperty`` and ``intProperty`` for `Hashable` conformance.
@HashablestructMyStruct{
// Implicitly used for `Hashable` conformance
letstringProperty:String
// Implicitly used for `Hashable` conformance
privateletintProperty:Int
// Explicitly excluded from `Hashable` conformance
@NotHashedletnotHashableType:NotHashableType}

@Hashable Only

If the @Hashable macro is added but no properties are decorated with @Hashed or @NotHashed then all stored properties will be used.

/// A struct that uses the ``stringProperty`` and ``intProperty`` for `Hashable` conformance.
@HashablestructMyStruct{
// Implicitly used for `Hashable` conformance
letstringProperty:String
// Implicitly used for `Hashable` conformance
privateletintProperty:Int
// Implicitly excluded from `Hashable` conformance
varcomputedProperty:Bool{
intProperty >0}}

One (fairly minor) advantage of this over adding Hashable conformance without the macro is that you can see the code being produce via Right Click → Expand Macro.

NSObject Support

When a type implements NSObjectProtocol (e.g. it inherits from NSObject) it should override hash and isEqual(_:), not hash(into:) and ==. @Hashable detects when it is attached to a type conforming to NSObjectProtocol and will provide the hash property and isEqual(_:) function instead.

@Hashable will also provide an isEqual(to:) function that takes a parameter that matches Self, which will also have an appropriately named Objective-C function.

import HashableMacro
@HashablefinalclassPerson:NSObject{@Hashedvarname:String=""}extensionPerson{overridevarhash:Int{varhasher=Hasher()
hasher.combine(self.name)return hasher.finalize()}}extensionPerson{overridefunc isEqual(_ object:Any?)->Bool{guardlet object = object as?Personelse{returnfalse}guardtype(of:self)==type(of: object)else{returnfalse}returnself.isEqual(to: object)}@objc(isEqualToPerson:)func isEqual(to object:Person)->Bool{returnself.name == object.name
}}

finalhash(into:) Function

When the @Hashable macro is added to a class the generated hash(into:) function is marked final. This is because subclasses should not overload ==. There are many reasons why this can be a bad idea, but specifically in Swift this does not work because:

  • != is not part of the Equatable protocol, but rather an extension on Equatable, causing it to always use the == implementation from the class that adds Equatable conformance
    • It is possible to overload != but this is still not a good idea because...
  • Anything that uses generics to compare the values, for example XCTAssertEqual, will use the == implementation from the class that adds Equatable conformance
    • It is possible to work around this by using a separate function, in a similar way to NSObject, which is then called from ==

If this is an issue for your usage you can pass finalHashInto: false to the macro, but it will not attempt to call super or use properties from the superclass.

This is not something the macro aims to solve.

License

MIT

About

Add Hashable conformance by decorating properties with Swift macros

Resources

Stars

26 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

HashableMacro

TestsPlatforms

@Hashable is a Swift macro for adding Hashable conformance. It is particularly useful when synthesised conformance is not possible, such as with classes or a struct with 1 or more non-hashable properties.

The @Hashable macro is applied to the type that will conform to Hashable and the Hashed macro is applied to each of the properties that should contribute to the Hashable conformance.

import HashableMacro
/// A struct that uses the ``stringProperty`` and ``intProperty`` for `Hashable` conformance.
@HashablestructMyStruct{
// Any property that is hashable is supported.
@HashedletstringProperty:String
// Works on private properties, too.
@HashedprivateletintProperty:Int
// Non-decorated properties are ignored
letnotHashableType:NotHashableType}

All decorated properties are included in both the == and hash(into:) implementations, ensuring the contract of Hashable is upheld:

Two instances that are equal must feed the same values to Hasher in hash(into:), in the same order.

DocC documentation for HashableMacro is hosted by Swift Package Index. Most of the implementation can be seen in the Macros.swift file.

@NotHashed Macro

The @NotHashed macro can be applied to properties that should not be included in the Hashable conformance. If this macro is used to decorate a property the @Hashed macro should not be used to decorate a property in the same type.

This can be useful for types that have a smaller number of non-hashable properties than hashable properties.

/// A struct that uses the ``stringProperty`` and ``intProperty`` for `Hashable` conformance.
@HashablestructMyStruct{
// Implicitly used for `Hashable` conformance
letstringProperty:String
// Implicitly used for `Hashable` conformance
privateletintProperty:Int
// Explicitly excluded from `Hashable` conformance
@NotHashedletnotHashableType:NotHashableType}

@Hashable Only

If the @Hashable macro is added but no properties are decorated with @Hashed or @NotHashed then all stored properties will be used.

/// A struct that uses the ``stringProperty`` and ``intProperty`` for `Hashable` conformance.
@HashablestructMyStruct{
// Implicitly used for `Hashable` conformance
letstringProperty:String
// Implicitly used for `Hashable` conformance
privateletintProperty:Int
// Implicitly excluded from `Hashable` conformance
varcomputedProperty:Bool{
intProperty >0}}

One (fairly minor) advantage of this over adding Hashable conformance without the macro is that you can see the code being produce via Right Click → Expand Macro.

NSObject Support

When a type implements NSObjectProtocol (e.g. it inherits from NSObject) it should override hash and isEqual(_:), not hash(into:) and ==. @Hashable detects when it is attached to a type conforming to NSObjectProtocol and will provide the hash property and isEqual(_:) function instead.

@Hashable will also provide an isEqual(to:) function that takes a parameter that matches Self, which will also have an appropriately named Objective-C function.

import HashableMacro
@HashablefinalclassPerson:NSObject{@Hashedvarname:String=""}extensionPerson{overridevarhash:Int{varhasher=Hasher()
hasher.combine(self.name)return hasher.finalize()}}extensionPerson{overridefunc isEqual(_ object:Any?)->Bool{guardlet object = object as?Personelse{returnfalse}guardtype(of:self)==type(of: object)else{returnfalse}returnself.isEqual(to: object)}@objc(isEqualToPerson:)func isEqual(to object:Person)->Bool{returnself.name == object.name
}}

finalhash(into:) Function

When the @Hashable macro is added to a class the generated hash(into:) function is marked final. This is because subclasses should not overload ==. There are many reasons why this can be a bad idea, but specifically in Swift this does not work because:

  • != is not part of the Equatable protocol, but rather an extension on Equatable, causing it to always use the == implementation from the class that adds Equatable conformance
    • It is possible to overload != but this is still not a good idea because...
  • Anything that uses generics to compare the values, for example XCTAssertEqual, will use the == implementation from the class that adds Equatable conformance
    • It is possible to work around this by using a separate function, in a similar way to NSObject, which is then called from ==

If this is an issue for your usage you can pass finalHashInto: false to the macro, but it will not attempt to call super or use properties from the superclass.

This is not something the macro aims to solve.

License

MIT

About

Add Hashable conformance by decorating properties with Swift macros

Resources

Stars

26 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages