Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Macros to help automating SwiftRex boilerplate

License

NotificationsYou must be signed in to change notification settings

SwiftRex/SwiftRexMacros

Repository files navigation

Macros to help automating SwiftRex boilerplate

MemberwiseInit

A macro that produces memberwise initializers for a struct, with the option to control the visibility accessor modifier.

Examples

@MemberwiseInit(visibility:.fileprivate)publicstructBlackjackCard{varsuit="diamonds",rank="3"lettype:Stringletflipped:BoolletonFlip:(Bool)->Void}

produces:

extensionBlackjackCard{fileprivateinit(suit:String="diamonds", rank:String="3", type:String, flipped:Bool, onFlip:@escaping(Bool)->Void){self.suit= suitself.rank= rankself.type= typeself.flipped= flipped}}

Limitations

Type inference is hard. Swift compiler does an amazing job inferring types, especially in closures. However, re-implementing whatever Swift compilerdoes to infer the types would not be feasible here, and Swift Macros run before type-checkers, so we can't reliably know what type is when a variableis not explicitly telling us:

@MemberwiseInitstructMyStruct{    // BoolvarisToday=Calendar.current.isDateInToday(Date())    // (DateComponents) -> Date?varmyCrazyClosure={Calendar.current.date(from: $0)}}

In such situations, the variable may be omited from the init, causing a compiler error, or it may use a wrong value that we tried our best to infer.If you face such situations, please explicitly annotate your variables with the proper type, and everything should work as expected.

Prism

A macro that produces predicates and prisms for all cases of an Enum.Predicates will be Bool properties in the formatisCaseA that returnstrue whenever that instance points to thecaseA case of the enum.Prism is a property with the same name as the case, but for the instance of the enum. If the instance points to that case, the variable will return a tuple of all associated values of that case, or instance of Void() for case without associated values. However, if the instance points to another case, it will returnnil. This is extremely useful for using KeyPaths.

Example of predicates:

@PrismenumColor{case red, green, blue}

produces:

extensionColor{varisRed:Bool{if case.red=self{true}else{false}}varisGreen:Bool{if case.green=self{true}else{false}}varisBlue:Bool{if case.blue=self{true}else{false}}}

usage:

letcolor1=Color.redcolor1.isRed // truecolor1.isGreen // falsecolor1.isBlue // false

Example of prism:

@PrismenumContact{case email(address:String)case phone(countryCode:String, number:String)case letter(street:String, house:String, postalCode:String, city:String, state:String, country:String)case noContact}

produces:

extensionContact{varemail:String?{guard caselet.email(address)=selfelse{returnnil}return address}varphone:(countryCode:String, number:String)?{guard caselet.phone(countryCode, number)=selfelse{returnnil}return(countryCode: countryCode, number: number)}varletter:(street:String, house:String, postalCode:String, city:String, state:String, country:String)?{guard caselet.letter(street, house, postalCode, city, state, country)=selfelse{returnnil}return(street: street, house: house, postalCode: postalCode, city: city, state: state, country: country)}varnoContact:Void?{guard case.noContact=selfelse{returnnil}return()}}

Please notice that theVoid case is important not only for consistency, but for more advanced cases of composition.Logically, a case with no associated values "holds" a Void associated value (singleton type), or not (nil) if the instance has another case.

usage:

letcontact=Contact.phone(countryCode:"44", number:"078906789")letphone= contact.phone.map{ $0.countryCode+""+ $0.number}??"<No Phone>"     // "44 078906789"letresolveEmail:KeyPath<Contact,String?>= \Contact.email    // passing contact will resolve to `nil`,                                                                // but passing something with email will resolve to the addrees

SetterPrisms also produce setters. In that case, if the enum case has an associated value and you want to change the values in the tuple, that is possible as long as the instance points to the same case, otherwise it will be ignored. For example:

varcontact=Contact.phone(countryCode:"44", number:"078906789")contact.phone=(countryCode:"44", number:"99999999")     // ✅ this change happens with successcontact.email="my@email.com"                              // 🚫 this change is ignored, because the enum instance points to phone, not email

The setter can be really useful if you have a long tree of enums and want to change the leaf. It's also useful forWritableKeyPath situations.

Extra:

  • UsePrism in the enum if you want to generate code for every case
  • UsePrismCase in a case if you want a different visibility only for that case generated code.
  • Use onlyPrismCase withoutPrism in the enum if you want code generated only for that case.
  • UseNoPrism in a case if you don't want code generated for that case.

PrismCase

A macro that produces predicates and prisms for a single case of an Enum.

Example of predicates:

enumColor{case red, black@PrismCasecase green, bluecase yellow, white}

produces:

extensionColor{varisGreen:Bool{if case.green=self{true}else{false}}varisBlue:Bool{if case.blue=self{true}else{false}}}

usage:

letcolor1=Color.greencolor1.isGreen // truecolor1.isBlue // falsecolor1.isRed 🚫 // Compiler error, not generated

Example of prism:

enumContact{case email(address:String)@PrismCasecase phone(countryCode:String, number:String)case letter(street:String, house:String, postalCode:String, city:String, state:String, country:String)case noContact}

produces:

extensionContact{varphone:(countryCode:String, number:String)?{guard caselet.phone(countryCode, number)=selfelse{returnnil}return(countryCode: countryCode, number: number)}}

Please notice that theVoid case is important not only for consistency, but for more advanced cases of composition.Logically, a case with no associated values "holds" a Void associated value (singleton type), or not (nil) if the instance has another case.

usage:

letcontact=Contact.phone(countryCode:"44", number:"078906789")letphone= contact.phone.map{ $0.countryCode+""+ $0.number}??"<No Phone>"     // "44 078906789"letresolveEmail:KeyPath<Contact,String?>= \Contact.phone?.number    // passing contact will resolve to `"078906789"`,                                                                        // but passing something with email will resolve to nil

NoPrism

A macro that prevents the code generatio of predicates and prisms for a specific case, in an Enum marked withPrism

Example of predicates:

@PrismenumColor{case red, green, blue@NoPrismcase white, black}

produces:

extensionColor{varisRed:Bool{if case.red=self{true}else{false}}varisGreen:Bool{if case.green=self{true}else{false}}varisBlue:Bool{if case.blue=self{true}else{false}}}

usage:

letcolor1=Color.redcolor1.isRed // truecolor1.isGreen // falsecolor1.isBlue // falsecolor1.isWhite 🚫 // Compiler error, not generated

Example of prism:

@PrismenumContact{case email(address:String)case phone(countryCode:String, number:String)@NoPrismcase letter(street:String, house:String, postalCode:String, city:String, state:String, country:String)@NoPrismcase noContact}

produces:

extensionContact{varemail:String?{guard caselet.email(address)=selfelse{returnnil}return address}varphone:(countryCode:String, number:String)?{guard caselet.phone(countryCode, number)=selfelse{returnnil}return(countryCode: countryCode, number: number)}}

Please notice that theVoid case is important not only for consistency, but for more advanced cases of composition.Logically, a case with no associated values "holds" a Void associated value (singleton type), or not (nil) if the instance has another case.

usage:

letcontact=Contact.phone(countryCode:"44", number:"078906789")letphone= contact.phone.map{ $0.countryCode+""+ $0.number}??"<No Phone>"     // "44 078906789"letresolveEmail:KeyPath<Contact,String?>= \Contact.email    // passing contact will resolve to `nil`,                                                                // but passing something with email will resolve to the addrees

About

Macros to help automating SwiftRex boilerplate

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages


[8]ページ先頭

©2009-2025 Movatter.jp