ํ‹ฐ์Šคํ† ๋ฆฌ ๋ทฐ

728x90
๋ฐ˜์‘ํ˜•

์•ˆ๋…•ํ•˜์„ธ์š”!
์กฐ๊ธˆ ๋Šฆ์€ ๊ฐ์ด ์žˆ์ง€๋งŒ, Swift 5.9์—์„œ๋ถ€ํ„ฐ ์ œ๊ณตํ•˜๊ธฐ ์‹œ์ž‘ํ•œ macro์— ๋Œ€ํ•ด ๊ณต๋ถ€ํ•ด ๋ณด๊ณ ์ž
WWDC 23 - Write Swift macros ์„ธ์…˜์„ ๋“ฃ๊ณ  ์ •๋ฆฌํ•ด ๋ณด์•˜์Šต๋‹ˆ๋‹ค.

 


 

Swift์˜ macro๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๊ธฐ์กด์˜ ๋ฐ˜๋ณต๋˜๋Š” ์ฝ”๋“œ๋ฅผ ์ปดํŒŒ์ผํƒ€์ž„์— ์ƒ์„ฑํ•˜์—ฌ, ๊ฐœ๋ฐœ ์ƒ์‚ฐ์„ฑ์„ ๋†’์—ฌ์ค€๋‹ค.

Overview

let calculations = [
    ( 1 + 1, "1 + 1" ),
    ( 2 + 3, "2 + 3" ),
    ( 7 - 3, "7 - 3" ),
    ( 5 - 2, "5 - 2" ),
    ( 3 * 2, "3 * 2" ),
    ( 3 * 5, "3 * 5" )
]

์ด๋ ‡๊ฒŒ ํ•™์ƒ๋“ค์ด ๊ณ„์‚ฐ ๋Šฅ๋ ฅ์„ ์—ฐ์Šตํ•˜๋Š” ๋ฐ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๊ณ„์‚ฐ ๋ชฉ๋ก์ด ์žˆ๋‹ค.
tuple์˜ ์™ผ์ชฝ์—๋Š” ์ •์ˆ˜ํ˜• ๊ฒฐ๊ณผ๊ฐ€ ํ‘œ์‹œ๋˜๊ณ , tuple์˜ ์˜ค๋ฅธ์ชฝ์—๋Š” ๊ณ„์‚ฐ์‹์ด ๋ฌธ์ž์—ด๋กœ ๋‚˜ํƒ€๋‚œ๋‹ค.



์ด๋•Œ, 2๊ฐ€์ง€์˜ ๋ฌธ์ œ์ ์ด ๋ฐœ์ƒํ•œ๋‹ค.

  • tuple์— ๋Œ€ํ•œ ๋‚ด์šฉ์ธ (๊ฒฐ๊ณผ, ๊ณ„์‚ฐ์‹)์„ ๊ฐœ๋ฐœ์ž๊ฐ€ ์ˆ˜๊ธฐ๋กœ ์ž…๋ ฅํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์—, ๊ฒฐ๊ณผ์™€ ์‹์ด ๋งž์ง€ ์•Š์„ ์ˆ˜ ์žˆ๋‹ค.
  • ๊ฐ’ ๋„์ถœ์„ ์œ„ํ•ด์„ , ๋ฐ˜๋ณต์ ์ด๊ณ  ์ค‘๋ณต์ ์ธ ์ฝ”๋“œ ์ž‘์„ฑ์ด ํ•„์š”ํ•˜๋‹ค.

ํ•˜์ง€๋งŒ, Swift 5.9์—์„œ๋Š” macro๋ฅผ ์ •์˜ํ•˜์—ฌ ์ด ์ž‘์—…์„ ๊ฐ„์†Œํ™”ํ•  ์ˆ˜ ์žˆ๋‹ค!

 

let calculations = [
    #stringify(1 + 1),
    #stringify(2 + 3),
    #stringify(7 - 3),
    #stringify(5 - 2),
    #stringify(3 * 2),
    #stringify(3 * 5)
]

#stringify๋ผ๋Š” ์ด๋ฆ„์˜ macro๋Š” '๊ณ„์‚ฐ'๋งŒ ๋‹จ์ผ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ์ทจ๊ธ‰ํ•œ๋‹ค.
๊ทธ๋ฆฌ๊ณ , ์ปดํŒŒ์ผํƒ€์ž„์— ์œ„์—์„œ ๋ณธ tupleํ˜•์‹์œผ๋กœ ๋ณ€ํ™˜๋˜์–ด ๊ณ„์‚ฐ๊ณผ ๊ฒฐ๊ณผ๊ฐ€ ์ผ์น˜ํ•˜๋„๋ก ๋ณด์žฅํ•œ๋‹ค.

 

@freestanding(expression)
macro stringify(_ value: Int) -> (Int, String)

#stringify๋Š” ํ•จ์ˆ˜์™€ ์ƒ๊น€์ƒˆ๊ฐ€ ๋งค์šฐ ํก์‚ฌํ•œ๋ฐ, ์ •์ˆ˜๋ฅผ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ํ•˜์—ฌ, ๊ฒฐ๊ณผ์™€ ๊ณ„์‚ฐ์‹์„ tupleํ˜•ํƒœ๋กœ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

 

๋˜ํ•œ ๋งคํฌ๋กœ๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ ์•„๋ž˜์˜ ์ผ€์ด์Šค์—๋Š” ์ปดํŒŒ์ผ ์—๋Ÿฌ๋ฅผ ๋ฐœ์ƒ์‹œํ‚จ๋‹ค.

  1. ๋งคํฌ๋กœ ํ‘œํ˜„์‹์˜ ์ธ์ˆ˜๊ฐ€ ๋งค๊ฐœ๋ณ€์ˆ˜์™€ ์ผ์น˜ํ•˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ
  2. ๋งคํฌ๋กœ ํ‘œํ˜„์‹์˜ ์ธ์ˆ˜์— ๋Œ€ํ•œ ํƒ€์ž… ๊ฒ€์‚ฌ๋ฅผ ์ˆ˜ํ–‰ํ•˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ

 

#stringify("hello world") // ERROR >> Cannot convert value of type 'String' to expected argument type 'Int'

์ด์ฒ˜๋Ÿผ ๋งคํฌ๋กœ ํ‘œํ˜„์‹์˜ ์ธ์ˆ˜์™€ ๋‹ค๋ฅธ ํƒ€์ž…์˜ ๊ฐ’์„ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ์ž‘์„ฑํ•˜๋ฉด ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒ

 

์ฆ‰, Compiler๋Š” ๋ชจ๋“  ์ธ์ˆ˜๊ฐ€ macro์˜ ํŒŒ๋ผ๋ฏธํ„ฐ์™€ ์ผ์น˜ํ•˜๋Š”์ง€ ๋จผ์ € ํ™•์ธ ํ›„, macro๋ฅผ ๋™์ž‘์‹œํ‚จ๋‹ค.

 

๊ทธ๋ฆฌ๊ณ  ํ•ด๋‹น #stringify๋Š” ๋…๋ฆฝํ˜• ํ‘œํ˜„์‹ ๋งคํฌ๋กœ(Freestanding Expression macro) ์ด๊ธฐ ๋•Œ๋ฌธ์—,
ํ‘œํ˜„์‹์„ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋Š” ์œ„์น˜๋ผ๋ฉด ์–ด๋””์„œ๋“  ์ผ๋ฐ˜์ ์ธ ๊ฐ’์ฒ˜๋Ÿผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. (ํ•จ์ˆ˜์˜ return ๊ฒฐ๊ณผ์™€ ๊ฐ™์€...)

 

์—ฌ๊ธฐ์„œ ํ‘œํ˜„์‹์„ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋Š” ์œ„์น˜ ๋ž€?
๋ณ€์ˆ˜ ํ• ๋‹น, ํ•จ์ˆ˜ ํ˜ธ์ถœ, ์—ฐ์‚ฐ ๋“ฑ ๊ณผ ๊ฐ™์ด ์ผ๋ฐ˜์ ์ธ ๊ฐ’์ด ์œ„์น˜ํ•  ์ˆ˜ ์žˆ๋Š” ๊ณณ์„ ์˜๋ฏธํ•œ๋‹ค.

์ฆ‰, type ์•ž์— ๋‚˜์˜ค๋Š” ํ‚ค์›Œ๋“œ (ex. final class A {... }์—์„œ์˜ final) ๋“ฑ์€ ํ‘œํ˜„์‹์˜ ์œ„์น˜๊ฐ€ ์•„๋‹ˆ๋‹ค!

 

โœ… ๋‹จ์ผ ๋งคํฌ๋กœ ํ‘œํ˜„์‹

 

macro๋Š” ํ™•์žฅ์„ ์ˆ˜ํ–‰ํ•˜๊ณ ์ž, Compiler Plug-in์—์„œ ๊ตฌํ˜„์„ ์ •์˜ํ•œ๋‹ค.
๊ทธ๋ฆฌ๊ณ , Compiler๋Š” macro ํ‘œํ˜„์‹์˜ ์ „์ฒด ์†Œ์Šค์ฝ”๋“œ๋ฅผ ํ•ด๋‹นํ•˜๋Š” macro Plug-in์— ์ „๋‹ฌํ•œ๋‹ค.

 

macro Plug-in์€ macro์˜ ์†Œ์Šค ์ฝ”๋“œ๋ฅผ SwiftSyntax ํŠธ๋ฆฌ๋กœ ํŒŒ์‹ฑ ํ•˜๋Š”๋ฐ, ์ด SwiftSyntax ํŠธ๋ฆฌ๋Š”, macro์˜ ๊ตฌ์กฐ์  ํ‘œํ˜„์ด์ž macro ๋™์ž‘์˜ ๊ธฐ๋ฐ˜์ด ๋œ๋‹ค.

 

#stringify macro๋Š” SwiftSyntax ํŠธ๋ฆฌ์—์„œ ๋…ธ๋“œ์˜ ๊ตฌ์กฐ๋กœ ํ‘œํ˜„๋œ๋‹ค.

  • identifier : macro์˜ ์ด๋ฆ„์„ ์ •์˜
  • InfixOperatorExprSyntax : 2 + 3์ด๋ผ๋Š” ์ค‘์œ„์—ฐ์‚ฐ์ž ์‹

macro๋Š” ๊ตฌํ˜„ ์ž์ฒด๋ฅผ Swift๋กœ ๊ตฌํ˜„ํ•˜๊ณ , SwiftSyntaxํŠธ๋ฆฌ๋กœ ๋ณ€ํ™˜์ด ๋งค์šฐ ์ž์œ ๋กญ๋‹ค.

 

์œ„ ์˜ˆ์‹œ์˜ ๊ฒฐ๊ณผ์ธ tuple์„ ์ƒ์„ฑํ•˜๊ณ , ์ƒ์„ฑ๋œ SwiftSyntax ํŠธ๋ฆฌ๋ฅผ ๋‹ค์‹œ ์†Œ์Šค ์ฝ”๋“œ๋กœ ์ง๋ ฌํ™”ํ•ด์„œ Compiler๋กœ ๋ณด๋‚ด๋ฉด, ๊ธฐ์กด์˜ #stringify(2+3)๋ผ๋Š” macro ํ‘œํ˜„์‹์ด ํ™•์žฅ๋œ ์ฝ”๋“œ๋กœ ๋Œ€์ฒด๋œ๋‹ค.

 


 

Create a macro

Xcode์—์„œ macro Template๋ฅผ ์ง์ ‘ ๋งŒ๋“ค์–ด๋ณด์ž.

 

1๏ธโƒฃ macro ํ…œํ”Œ๋ฆฟ ์ƒ์„ฑํ•˜๊ธฐ

 

Xcode >> File >> New >> Package... ๋ฅผ ์„ ํƒํ•œ๋‹ค.

 

Swift macro๋ฅผ ์„ ํƒํ•˜๊ณ , ์ƒˆ๋กญ๊ฒŒ ์ƒ์„ฑํ•ด์ค€๋‹ค.

 

์ƒ์„ฑ๋œ Swift macro ํƒฌํ”Œ๋ฆฟ์„ ํ™•์ธํ•ด ๋ณด๋ฉด, ์ด์ „์— ์šฐ๋ฆฌ๊ฐ€ ๋‹ค๋ค˜๋˜ #stringify๊ฐ€ ํ‘œํ˜„์‹์— ์‚ฌ์šฉ๋˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

 

์‹ค์ œ macro๊ฐ€ ํ™•์žฅ๋œ ๋ชจ์Šต์„ ํ™•์ธํ•˜๋ ค๋ฉด, ํ‘œํ˜„์‹์„ ์šฐํด๋ฆญํ•ด์„œ Expand macro๋ฅผ ์„ ํƒํ•˜๋ฉด ๋œ๋‹ค

 

2๏ธโƒฃ macro ์ง์ ‘ ์ •์˜ํ•˜๊ธฐ

์šฐ๋ฆฌ๊ฐ€ ์ง์ ‘ macro๋ฅผ ์ •์˜ํ•ด ๋ณด๊ธฐ ์ „์—, ์šฐ์„  #stringify๊ฐ€ ์–ด๋–ป๊ฒŒ ์ •์˜๋˜์–ด์žˆ๋Š”์ง€๋ฅผ ํ™•์ธํ•ด ๋ณด์ž.

 

์‚ฌ์ง„ ์† ์ฝ”๋“œ์™€ ๊ฐ™์ด ์ •์ˆ˜ํ˜• ๋Œ€์‹ , ์ œ๋„ค๋ฆญ ํƒ€์ž…์œผ๋กœ ์ •์˜๋˜์–ด ๋ชจ๋“  ํƒ€์ž… T๋ฅผ ๋ฐ›์„ ์ˆ˜ ์žˆ๋‹ค.
๋˜ํ•œ macro๋Š” #externalmacro๋กœ ์„ ์–ธ๋œ๋‹ค.

 

์ฆ‰, ํ•ด๋‹น macro๋ฅผ ์‹ค์ œ๋กœ ์ˆ˜ํ–‰ํ•˜๋ ค๋ฉด WWDCmacro ๋ชจ๋“ˆ์—์„œ Stringifymacroํƒ€์ž…์„ ๊ฐ€์ ธ์˜ค๋ผ๊ณ  Compiler์—๊ฒŒ ์•Œ๋ฆฌ๋Š” ๊ฒƒ์ด๋‹ค.

 

#stringify ๋งคํฌ๋กœ๋Š”, ๋…๋ฆฝํ˜• ํ‘œํ˜„์‹ ๋งคํฌ๋กœ(Freestanding Expression macro)๋กœ ์ •์˜๋˜๊ธฐ ๋•Œ๋ฌธ์— Expressionmacro๋ฅผ ์ค€์ˆ˜ํ•ด์•ผ ํ•œ๋‹ค.

 

์œ„ ์ฝ”๋“œ์—์„œ ์•Œ ์ˆ˜ ์žˆ๋“ฏ์ด, Expressionmacro๋Š” expansion(of node: in context) ํ•จ์ˆ˜๋ฅผ ํ•„์ˆ˜์š”๊ตฌ์‚ฌํ•ญ์œผ๋กœ ๊ฐ–๋Š”๋‹ค.

 

expansion(of node: in context)๋Š” Syntax Tree(๊ตฌ๋ฌธ ํŠธ๋ฆฌ)์™€ Context๋ฅผ ์ธ์ž๋กœ ์ทจํ•˜๋Š”๋ฐ, ์ด๋Š” ์ปดํŒŒ์ผ๋Ÿฌ์™€์˜ ํ†ต์‹ ์— ํ•„์š”ํ•œ ์š”์†Œ๋“ค์ด๋‹ค.

 

์ดํ›„์— expansion(of node: in context) ํ•จ์ˆ˜๋Š” ์žฌ์ž‘์„ฑ๋œ ํ‘œํ˜„์‹ ๊ตฌ๋ฌธ์„ return ํ•˜๊ฒŒ ๋œ๋‹ค.

 

ํ•จ์ˆ˜ ๋‚ด๋ถ€ ๊ตฌํ˜„์„ ์‚ดํŽด๋ณด๋ฉด, ์šฐ์„  node์˜ ๋‹จ์ผ ์ธ์ˆ˜(argumentList.first?.expression) ๋ฅผ macro์˜ ํ‘œํ˜„์‹์œผ๋กœ ๊ฐ€์ ธ์˜จ๋‹ค.

 

#stringify๋ฅผ ์„ ์–ธํ•  ๋•Œ ๋‹จ์ผ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์ทจํ•˜๋„๋ก ์„ ์–ธํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— ํ•ด๋‹น ์ธ์ˆ˜๋Š” ๋ฐ˜๋“œ์‹œ ์กด์žฌํ•˜๊ณ , ์กด์žฌํ•˜๋Š” ๋ชจ๋“  ์ธ์ˆ˜์— ๋Œ€ํ•œ ํƒ€์ž… ๊ฒ€์‚ฌ๋ฅผ ํ•ด์•ผ ํ•œ๋‹ค.

 

๊ทธ๋ฆฌ๊ณ  ๋ฌธ์ž์—ด ๋ณด๊ฐ„๋ฒ•์„ ํ™œ์šฉํ•ด์„œ, tuple์˜ Syntax Tree(๊ตฌ๋ฌธ ํŠธ๋ฆฌ)๋ฅผ ์ƒ์„ฑํ•˜๊ฒŒ ๋œ๋‹ค.

 

์ด ํ•จ์ˆ˜์—์„œ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฐ’์€ ๋‹จ์ˆœํ•œ ๋ฌธ์ž์—ด์ด ์•„๋‹ˆ๋ผ ExprSyntax ์ฆ‰, ํ‘œํ˜„์‹ ๊ตฌ๋ฌธ์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.
macro๋Š” ์ด ํ‘œํ˜„์‹ ๊ตฌ๋ฌธ์„ Swift ํŒŒ์„œ๋ฅผ ์ด์šฉํ•˜์—ฌ ํ•ด์„์„ ์ง„ํ–‰ํ•˜๊ณ , ์‹ค์ œ ํ‘œํ˜„์‹์œผ๋กœ ๋ณ€ํ™˜๋œ๋‹ค.

 

3๏ธโƒฃ macro ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ž‘์„ฑํ•˜๊ธฐ

macro๋Š” ๋Ÿฐํƒ€์ž„์—์„œ expansion ๋˜์–ด ์‹ค์ œ ์ฝ”๋“œ๋กœ ์ ์šฉ๋˜๊ธฐ ์ „์—๋Š”, ๋ฒ„๊ทธ๊ฐ€ ์žˆ๋Š”์ง€ ์•Œ์•„์ฐจ๋ฆฌ๊ธฐ ๊นŒ๋‹ค๋กญ๋‹ค.
๋”ฐ๋ผ์„œ macro๊ฐ€ ์ œ๋Œ€๋กœ ๋™์ž‘ํ•˜๋Š”์ง€ ํ…Œ์ŠคํŠธํ•˜๋Š” ๊ณผ์ •์ด ์žˆ์–ด์•ผ ํ•œ๋‹ค.

 

macro ํ…œํ”Œ๋ฆฟ์—์„œ ์ œ๊ณตํ•˜๋Š” Unit-Test๋ฅผ ํ™•์ธํ•ด๋ณด์ž.

 

assertmacroExpansion
  • SwiftSyntax ํŒจํ‚ค์ง€์˜ ํ•จ์ˆ˜์ด๊ณ , #stringify๊ฐ€ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ expansion ๋˜๋Š”์ง€๋ฅผ ๊ฒ€์ฆ
  • ์œ„์˜ ์˜ˆ์‹œ์—์„œ๋Š” ์ฒซ ๋ฒˆ์งธ ์ธ์ž๋กœ #stringify์˜ ํ‘œํ˜„์‹์„ ๋ฆฌํ„ฐ๋Ÿด๋กœ ๋ฐ›๊ณ , expandedSource๋ผ๋Š” ์ด๋ฆ„์˜ ํŒŒ๋ผ๋ฏธํ„ฐ์—๋Š” expansion ๋œ ๊ฒฐ๊ณผ๋ฅผ ๋ฌธ์ž์—ด๋กœ ์ž…๋ ฅ๋ฐ›๋Š”๋‹ค
  • ๊ทธ๋ฆฌ๊ณ  macros ์ธ์ž์—๋Š” [String: macro.Type] ํƒ€์ž…์„ ๋ฐ›๋Š”๋ฐ, ์ด๋Š” expansion์œผ๋กœ ์‚ฌ์šฉํ•  macro๋ฅผ ์ง€์ •ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

์‹ค์ œ ํ•ด๋‹น ํ…Œ์ŠคํŠธ์ฝ”๋“œ๋ฅผ ์‹คํ–‰์‹œํ‚ค๋ฉด, ์„ฑ๊ณตํ•˜๊ฒŒ ๋œ๋‹ค.

 

์ง€๊ธˆ๊นŒ์ง€ ์ •๋ฆฌ๋œ macro์˜ ํŠน์ง•

  • macro ์„ ์–ธ์„ ํ•˜๊ธฐ ์œ„ํ•ด์„œ, macro์˜ signature์™€ ์—ญํ• ์„ ์ •์˜ํ•œ๋‹ค.
  • ์ดํ›„ Compiler plug-in์ด ๋‚ด๋ถ€์ ์œผ๋กœ ๊ตฌ๋ฌธํŠธ๋ฆฌ๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ๊ฒฐ๊ณผ์— ๋Œ€ํ•œ ํ•ด์„์„ ํ•˜๊ณ , ์ด๋ฅผ expansion์—์„œ ๊ฒฐ๊ณผ ์ฝ”๋“œ๋กœ ๋ณ€ํ™˜ํ•œ๋‹ค.
  • ๋˜ํ•œ, macro๋Š” testable ํ•˜๋‹ค.

 


 

macro roles

 

macro๋Š” ์ง€๊ธˆ๊นŒ์ง€ ์•Œ์•„๋ณด์•˜๋˜ @freestanding(expression)(๋…๋ฆฝํ˜• ํ‘œํ˜„์‹ ๋งคํฌ๋กœ) ์ด์™ธ์—๋„ ์ˆ˜๋งŽ์€ ํ˜•ํƒœ๊ฐ€ ์กด์žฌํ•œ๋‹ค.

์ด๋ฒˆ ์„ธ์…˜์—์„œ๋Š” @attached(member)์— ๋Œ€ํ•ด ์ฃผ๋กœ ์•Œ์•„๋ณผ ์˜ˆ์ •์ž„.

 

@attached(member) macro ์†Œ๊ฐœ

@attached(member)๋ฅผ ์ ์šฉํ•˜๊ธฐ ์œ„ํ•ด ๊ฐ€์ •ํ•œ ์ƒํ™ฉ์€ ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

 

''๋‚˜๋Š” ํ˜„์žฌ ์Šคํ‚ค ๊ฐ•์‚ฌ๋กœ ์ผ ํ•˜๊ณ  ์žˆ๊ณ , ํ•™์ƒ๋“ค๊ณผ ํ•จ๊ป˜ ๊ฐˆ ์—ฌํ–‰์„ ๊ณ„ํšํ•˜๋Š” ๋ฐ ์‚ฌ์šฉํ•  ์•ฑ์„ ๊ฐœ๋ฐœ ์ค‘์ด๋‹ค.
๊ทธ๋ฆฌ๊ณ  ์Šคํ‚ค์žฅ์— ๊ฐ”์„ ๋•Œ, ํ•™์ƒ๋“ค์˜ ์Šคํ‚ค ์ˆ˜์ค€์— ๋”ฐ๋ผ ๋„ˆ๋ฌด ์–ด๋ ค์šด ์Šฌ๋กœํ”„์— ์ดˆ๋ณด์ž๋ฅผ ๋ฐ๋ ค๊ฐ€์ง€ ์•Š๋„๋ก ํ•˜๊ณ  ์‹ถ๋‹ค."

enum Slope {
    case beginnersParadise
    case practiceRun
    case livingRoom
    case olympicRun
    case blackBeauty
}

enum EasySlope {

    case beginnersParadise
    case practiceRun

    init? (_ slope: Slope) {
        switch slope {
        case .beginnersParadise: self = .beginnersParadise
        case .practiceRun: self = .practiceRun
        default: return nil
        }
    }

    var slope: Slope {
        switch self {
        case .beginnersParadise: return .beginnersParadise
        case .practiceRun: return .practiceRun
        }
    }
}

์ด๋ ‡๊ฒŒ Slope์™€ EasySlope ์—ด๊ฑฐํ˜•์„ ์ •์˜ํ•จ์œผ๋กœ์จ, ์„ ํƒ๋œ Slope๋ฅผ ๊ธฐ์ค€์œผ๋กœ EasySlope์— ์†ํ•˜๋Š”์ง€๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์€ ํ›Œ๋ฅญํ•œ ํƒ€์ž… ์•ˆ์ •์„ฑ์„ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ๋‹ค.

 

ํ•˜์ง€๋งŒ, ๋ฐ˜๋ณต์ ์ธ ์ฝ”๋“œ๊ฐ€ ๋„ˆ๋ฌด ๋งŽ๊ณ  ์ด๋ฅผ macro๋ฅผ ํ™œ์šฉํ•ด์„œ ๊ฐœ์„ ํ•ด ๋ณด์ž

 

๋ฐ˜๋ณต์ ์ธ Slope์— ๋Œ€ํ•œ macro ์ž‘์„ฑ

๋ชฉํ‘œ๋Š”, EasySlope์˜ initializer์™€ computed-property๋ฅผ ์ž๋™์œผ๋กœ ์ƒ์„ฑํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

  1. initializer์™€ computed-property๋Š” ๋ชจ๋‘ EasySlopeํƒ€์ž…์˜ member์ด๋ฏ€๋กœ, @attatched(member)๋ฅผ ์„ ์–ธํ•ด์•ผ ํ•œ๋‹ค.
  2. macro์˜ ๊ตฌํ˜„์„ ํฌํ•จํ•œ Compiler plug-in์„ ์ƒ์„ฑํ•œ๋‹ค.
    -> ์ด๋•Œ, macro์— ๋Œ€ํ•œ ํ…Œ์ŠคํŠธ๋ฅผ ์ง„ํ–‰ํ•ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๊ตฌํ˜„์ฒด๋Š” ๋น„์›Œ๋‘”๋‹ค(empty implementation)
  3. macro์— ๋Œ€ํ•œ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•œ๋‹ค.
  4. ํ…Œ์ŠคํŠธ๊ฐ€ ์„ฑ๊ณตํ•˜๋ฉด, ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค์™€ ์ผ์น˜ํ•˜๋„๋ก ์‹ค์ œ ๊ตฌํ˜„์ฒด๋ฅผ ๊ตฌํ˜„ํ•œ๋‹ค.
  5. ์™„์„ฑ๋œ macro๋ฅผ ์‹ค์ œ App์˜ ์ฝ”๋“œ์— ํ†ตํ•ฉํ•œ๋‹ค.

 

/// Defines a subset of the `Slope` enum
///
/// Generates an initializer that converts a `Slope` to this type if the slope is
///     declared in this subser, otherewise return `nil`
///
/// - Implement: All enum cases declared in this macro must also exist in the
///              `Slope` enum.
@attached(member, names: named(init))
public macro SlopeSubset() = #externalmacro(module: "WWDCMacros", type: "SlopeSubsetMacro")

macro์˜ ์ด๋ฆ„์€ SlopeSubset์œผ๋กœ ๋ช…๋ช…ํ–ˆ๋‹ค
-> EasySlope๋Š” Slope์˜ ๋ถ€๋ถ„์ง‘ํ•ฉ ์ด๊ธฐ ๋•Œ๋ฌธ์—..!!

 

@attached(member, names: named(init))

๋˜ํ•œ, ์—ฌ๊ธฐ์„œ ์•Œ ์ˆ˜ ์žˆ๋“ฏ์ด macro๋ฅผ ๊ตฌํ˜„์—์„œ ์ž‘์„ฑํ•  ๋ฉค๋ฒ„์˜ ์ด๋ฆ„๋„ ์ •์˜ํ•œ๋‹ค..!

 

public macro SlopeSubset() = #externalmacro(module: "WWDCMacros", type: "SlopeSubsetMacro")

ํ˜„์žฌ๋Š” macro์˜ signature๋ฅผ ์„ ์–ธํ•œ ์ƒํƒœ์ง€๋งŒ, ์‹ค์ œ ๋™์ž‘์„ ์ˆ˜ํ–‰ํ•˜๋Š” expansion์€ ์•„์ง ๊ตฌํ˜„ํ•˜์ง€ ์•Š์•˜๋‹ค.
expansion์„ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด์„œ, WWDCmacros ๋ชจ๋“ˆ์˜ SlopeSubsetmacro ํƒ€์ž…์„ ์ฐธ์กฐํ•œ๋‹ค.

 

์ด์ œ ํ•ด๋‹น ํƒ€์ž…(SlopeSubsetmacro)์„ ์ƒ์„ฑํ•ด์„œ ์‹ค์ œ macro๋ฅผ ๊ตฌํ˜„ํ•ด ๋ณด์ž!

 

/// Implementation of the `SlopeSubset` macro.
public struct SlopeSubsetmacro: MemberMacro {

}

@main
struct WWDCPlugin: CompilerPlugin {
    let providingmacros: [Macro.Type] = [
    ]
}

์šฐ๋ฆฌ๋Š” ํ˜„์žฌ SlopeSubset์„ @attatchd(member)๋กœ ์„ ์–ธํ–ˆ๊ธฐ ๋•Œ๋ฌธ์—, ํ•ด๋‹น SlopeSubset๊ฐ€ ๋”ฐ๋ฅด๋Š” Type์ธ SlopeSubsetmacro์˜ ๊ตฌํ˜„์ฒด๋Š” Membermacro ํ”„๋กœํ† ์ฝœ์„ ์ค€์ˆ˜ํ•ด์•ผ ํ•œ๋‹ค.

 

public struct SlopeSubsetmacro: MemberMacro {
    public static func expansion(
        of node: AttributeSyntax,
        providingMembersOf declaration: some DeclGroupSyntax,
        in context: some macroExpansionContext
    ) throws -> [DeclSyntax] {
        return []  /// > ์‹ค์ œ ๊ตฌํ˜„์ฒด๋ฅผ ๊ตฌํ˜„ํ•˜๊ธฐ ์ „์—, ์šฐ์„  ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๊ธฐ ์œ„ํ•ด ๋นˆ ๋ฐฐ์—ด์„ ๋ฆฌํ„ด
    }
}

/// > ์ปดํŒŒ์ผ๋Ÿฌ์—๊ฒŒ, `SlopeSubsetmacro`๋ผ๋Š” macroํƒ€์ž…์ด ์กด์žฌํ•จ์„ ๋…ธ์ถœํ•จ
@main
struct WWDCPlugin: CompilerPlugin {
    let providingmacros: [Macro.Type] = [
        SlopeSubsetmacro.self
    ]
}

Membermacro ํ”„๋กœํ† ์ฝœ ๋˜ํ•œ Expressionmacro๊ณผ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ, ํ•„์ˆ˜ ์š”๊ตฌ์‚ฌํ•ญ์œผ๋กœ expansion ํ•จ์ˆ˜๋ฅผ ๊ฐ–๋Š”๋‹ค.

  • node: macro๋ฅผ Type์— ์ ์šฉํ•  ๋•Œ ์‚ฌ์šฉํ•˜๋Š” ์‹ค์ œ attribute๋ฅผ ๊ฐ€์ ธ์˜ด
    -> ์—ฌ๊ธฐ์„œ๋Š” @SlopeSubset (macro์˜ Signature)
  • declaration: macro๊ฐ€ ์ ์šฉ๋  ์‹ค์ œ ํƒ€์ž…
    -> ์—ฌ๊ธฐ์„œ๋Š” EasySlope.Self

์ด์ œ ์‹ค์ œ ๊ตฌํ˜„์„ ํ•˜๊ธฐ ์ „์—, macro๊ฐ€ ์ž˜ ๋™์ž‘ํ•˜๋Š”์ง€ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๋ฅผ ์ž‘์„ฑํ•ด๋ณด์ž.

final class WWDCTests: XCTestCase {
    func testSlopeSubset() {
        assertmacroExpansion(
            originalSource: String,
            expandedSource: String,
            macros: [String : any Macro.Type]
        )
    }
}

ํ…œํ”Œ๋ฆฟ์˜ ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค์ฒ˜๋Ÿผ assertmacroExpansion์„ ํ†ตํ•ด macro์˜ ๋™์ž‘์„ ๊ฒ€์ฆํ•œ๋‹ค.

 

let testmacros: [String: Macro.Type] = [
    "SlopeSubset": SlopeSubsetmacro.self,
]

final class WWDCTests: XCTestCase {
    func testSlopeSubset() {
        assertMacroExpansion(
            """
            @SlopeSubset
            public enum EasySlope {
                case beginnersParadice
                case practiceRun
            }
            """,
            expandedSource:
            """
            public enum EasySlope {
                case beginnersParadice
                case practiceRun
            }
            """,
            macros: testmacros
        )
    }
}
  1. originalSource์—๋Š” ์‹ค์ œ๋กœ ๊ฒ€์ฆํ•˜๊ณ ์ž ํ•˜๋Š” ์ฝ”๋“œ์ธ ์•„๋ž˜์˜ string ๋ฆฌํ„ฐ๋Ÿด์„ ์ ๋Š”๋‹ค.
  2. SlopeSubsetmacro์— ๋Œ€ํ•œ expansionํ•จ์ˆ˜์˜ ๊ตฌํ˜„์ฒด๊ฐ€ ํ˜„์žฌ๋Š” ๋นˆ ๋ฐฐ์—ด์„ ๋ฐ˜ํ™˜ํ•˜๊ธฐ ๋•Œ๋ฌธ์—, macro์— ์˜ํ•ด์„œ ๋ณ€ํ™˜๋˜๋Š” ๊ฐ’์€ ์•„๋ฌด๊ฒƒ๋„ ์—†๋‹ค๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•œ๋‹ค. ์ฆ‰, ์›๋ณธ ์ฝ”๋“œ ๊ทธ๋Œ€๋กœ expansion ๋œ๋‹ค.
  3. ๋งˆ์ง€๋ง‰์œผ๋กœ, ํ…Œ์ŠคํŠธํ•  macro์˜ ์ด๋ฆ„์ธ SlopeSubset๊ณผ ํ•ด๋‹น macro์˜ ๊ตฌํ˜„์ฒด์ธ SlopeSubsetmacro๋ฅผ ๋งคํ•‘ํ•œ testmacro๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ํ•จ์ˆ˜์— ์ „๋‹ฌํ•œ๋‹ค.

 


๊ทธ๋ฆฌ๊ณ  ํ•ด๋‹น ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์‹ค์ œ๋กœ ์‹คํ–‰์‹œ์ผœ ๋ณด๋ฉด, ์„ฑ๊ณต์ ์œผ๋กœ ํ†ต๊ณผํ•˜๊ฒŒ ๋œ๋‹ค.

์ด๋•Œ, ์ฃผ์˜ํ•  ์ ์€ macro๋Š” ๊ฐœ๋ฐœ ์ค‘์ธ ํ”Œ๋žซํผ(์˜ˆ: Mac์˜ macOS)์—์„œ๋งŒ ์ง€์›๋˜๋ฏ€๋กœ, ์‹ค์ œ ํ…Œ์ŠคํŠธ๊ฐ€ ์ž‘๋™ํ•˜๋ ค๋ฉด ๋นŒ๋“œ ๋Œ€์ƒ์„ ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ๊ฐ€ ์•„๋‹Œ Mac์œผ๋กœ ์ง€์ •ํ•ด์•ผ ํ•œ๋‹ค!!!

 

์ด๋ฒˆ์—” ์šฐ๋ฆฌ๊ฐ€ ์‹ค์ œ๋กœ ์ƒ์„ฑํ•˜๊ธธ ์›ํ•˜๋Š” initializer ์ฝ”๋“œ๋ฅผ expandedSources์— ๋„ฃ๊ณ  ํ…Œ์ŠคํŠธ๋ฅผ ๋Œ๋ ค๋ณด์ž

 

๋‹น์—ฐํ•˜๊ฒŒ๋„, ์•„์ง macro์˜ ์‹ค์ œ ๊ตฌํ˜„๋ถ€์—์„œ๋Š” ์•„๋ฌด๋Ÿฐ ์ž‘์—…์ด ์—†๊ธฐ ๋•Œ๋ฌธ์— ํ…Œ์ŠคํŠธ๋Š” ์‹คํŒจํ•˜๊ฒŒ ๋œ๋‹ค.

๊ทธ๋Ÿผ ์ด์ œ, ์‹ค์ œ ๊ตฌํ˜„๋ถ€๋ฅผ ์ž‘์„ฑํ•ด ๋ณด์ž

 

1. declaration์„ ๊ฐ€์ ธ์˜ค๊ณ ์ž ํ•˜๋Š” type์œผ๋กœ ์บ์ŠคํŒ…ํ•˜๊ธฐ

public static func expansion(
    of node: AttributeSyntax,
    providingMembersOf declaration: some DeclGroupSyntax,
    in context: some MacroExpansionContext
) throws -> [DeclSyntax] {
    guard let enumDecl = declaration.as(EnumDeclSyntax.self) else { 
        //TODO: Emit an error here
        return [] 
    }
    return []
}

๊ตฌํ˜„ํ•˜๊ณ ์ž ํ•˜๋Š” initializer๋Š” EasySlopes์˜ ์—ด๊ฑฐํ˜•์— ์„ ์–ธ๋œ ๋ชจ๋“  ์š”์†Œ๋ฅผ ์ƒˆ๋กญ๊ฒŒ ์ „ํ™˜ํ•œ๋‹ค.
์ด๋ฅผ ์œ„ํ•ด declaration์—์„œ ๋ชจ๋“  case๋ฅผ ํšŒ์ˆ˜ํ•ด์™€์•ผ ํ•˜๋ฏ€๋กœ declaration์„ enum์œผ๋กœ ์บ์ŠคํŒ…ํ•œ๋‹ค.

 

๊ทธ๋ฆฌ๊ณ , enum์ด ์•„๋‹Œ ํƒ€์ž…์ด macro๋กœ ๋„˜์–ด์˜ค๋ฉด error๋ฅผ ๋ฐฉ์ถœํ•ด์•ผ ํ•˜๋ฏ€๋กœ ์ผ๋‹จ TODO๋กœ ๋‘”๋‹ค.

 

2. ์บ์ŠคํŒ…ํ•œ enum์œผ๋กœ๋ถ€ํ„ฐ ๋ชจ๋“  ์š”์†Œ๋ฅผ ๊ฐ€์ ธ์˜ค๊ธฐ

์ด์ œ ์บ์ŠคํŒ…ํ•œ enum์—์„œ ๋ชจ๋“  ์š”์†Œ(case)๋“ค์„ ๊ฐ€์ ธ์™€์•ผ ํ•˜๋Š”๋ฐ, Syntax Tree(๊ตฌ๋ฌธ ํŠธ๋ฆฌ)๋ฅผ ๋ถ„์„ํ•˜์—ฌ ํ•„์š”ํ•œ ๊ฐ’์„ ๊ฐ€์ ธ์™€์•ผ ํ•œ๋‹ค.

Syntax Tree(๊ตฌ๋ฌธ ํŠธ๋ฆฌ)๋ฅผ ๋ถ„์„ํ•˜๊ธฐ ์œ„ํ•ด์„œ ๋””๋ฒ„๊น… ๋‹จ๊ณ„์—์„œ braking-point๋ฅผ ์ง€์ •ํ•˜์—ฌ print-object(po)๋ฅผ ํ™œ์šฉํ•˜๋ฉด ์‹ค์ œ Syntax Tree(๊ตฌ๋ฌธ ํŠธ๋ฆฌ)์˜ ๊ตฌ์กฐ๋ฅผ ํ™•์ธํ•˜๊ณ  ํ•„์š”ํ•œ ๊ฐ’์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

 

์ถœ๋ ฅ๋œ Syntax Tree(๊ตฌ๋ฌธ ํŠธ๋ฆฌ)์—์„œ ๊ฐ€์žฅ ์•ˆ ์ชฝ์— ์žˆ๋Š” ๋…ธ๋“œ๋Š”, ์šฐ๋ฆฌ๊ฐ€ ํ•„์š”ํ•œ EnumCaseDeclSyntax ์ธ beginnersParadice, practiceRun์„ ํ‘œํ˜„ํ•˜๊ณ  ์žˆ๋‹ค.

 

์ด 2๊ฐ€์ง€ ์š”์†Œ๋ฅผ ๊ฐ€์ ธ์˜ค๊ธฐ ์œ„ํ•ด์„ , Syntax Tree(๊ตฌ๋ฌธ ํŠธ๋ฆฌ)์˜ ๊ตฌ์กฐ๋ฅผ ๋‹จ๊ณ„๋ณ„๋กœ ํƒ€๊ณ  ๊ฐ€์„œ ์ ‘๊ทผํ•ด์•ผ ํ•œ๋‹ค.

 

2-1. Syntax Tree(๊ตฌ๋ฌธ ํŠธ๋ฆฌ)๋ฅผ ๋ถ„์„ํ•˜๊ณ  ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ์— ์ ‘๊ทผํ•˜๊ธฐ

๊ฐ€์žฅ ๋ฐ”๊นฅ์˜ EnumDeclSyntax์— ์žˆ๋Š” memberBlock์€ ์ค‘๊ด„ํ˜ธ(Brace)์™€ ์‹ค์ œ ๋ฉค๋ฒ„(members)๋ฅผ ํฌํ•จํ•œ๋‹ค.
๋‚ด๋ถ€ member๋“ค์— ์ ‘๊ทผํ•˜๊ธฐ ์œ„ํ•ด์„ , enumDecl.memberBlock.members๋กœ ์‹œ์ž‘ํ•ด์•ผ ํ•  ๊ฒƒ์ด๋‹ค.

 

members ๋‚ด๋ถ€์— index๋กœ ๊ตฌ๋ถ„๋œ member๋Š”, ์„ ์–ธ๋ถ€(decl)์™€ optional๋กœ ์„ธ๋ฏธ์ฝœ๋ก (์—ฌ๊ธฐ์„  ์—†์Œ)์„ ํฌํ•จํ•œ๋‹ค.
์šฐ๋ฆฌ๋Š” ์ด ์ค‘์—์„œ case๋ฅผ ์‹ค์ œ๋กœ ์„ ์–ธํ•˜๋Š” ๋ถ€๋ถ„์ธ EnumCaseElememtListSyntax๊ฐ€ ํ•„์š”ํ•˜๋‹ค.


๋”ฐ๋ผ์„œ, compactMap์„ ํ™œ์šฉํ•ด์„œ EnumCaseDeclSyntaxํƒ€์ž…์œผ๋กœ ์บ์ŠคํŒ… ๋˜๋Š” ๋ชจ๋“  ๊ฐ’์„ ๊ฐ€์ ธ์˜จ๋‹ค.
-> EnumCaseDeclSyntax๊ฐ€ ์•„๋‹Œ ํƒ€์ž…์€ ๋ชจ๋‘ ์ œ๊ฑฐํ•˜๊ธฐ ์œ„ํ•ด compactMapํ™œ์šฉ

 

์ด๋ ‡๊ฒŒ ๊ฐ€์ ธ์˜จ let caseDecls๋Š” ๊ฐ ๊ฐ ๋‹จ์ผ ์š”์†Œ์ผ ์ˆ˜๋„ ์žˆ์ง€๋งŒ, ํ•œ ์ค„์— ์—ฌ๋Ÿฌ ์š”์†Œ๋ฅผ ํ•œ ๋ฒˆ์— ์ •์˜ํ•  ์ˆ˜๋„ ์žˆ๋‹ค
-> case beginnersParadice, practiceRun ์ด๋Ÿฐ ์‹์œผ๋กœ..!!

 

์ด๋Ÿฐ ๊ฒฝ์šฐ๋ฅผ ๋Œ€์‘ํ•˜๊ธฐ ์œ„ํ•ด์„œ ๊ฐ ์š”์†Œ๋ณ„๋กœ flatMap์„ ํ†ตํ•ด ๋ชจ๋“  elements๋ฅผ ๊ฐ€์ ธ์™€์•ผ ํ•œ๋‹ค.

 

ํ•„์š”๋กœ ํ–ˆ๋˜ ๋ชจ๋“  case๋ฅผ ํšŒ์ˆ˜ ํ•œ ์ƒํƒœ..!!

 

3. ์‹ค์ œ initializer ๊ตฌ์ถ•ํ•˜๊ธฐ

๋ชจ๋“  ์—ด๊ฑฐํ˜• ์š”์†Œ๋ฅผ ํšŒ์ˆ˜ํ–ˆ์œผ๋ฏ€๋กœ, ์‹ค์ œ initializer๋ฅผ ๊ตฌ์ถ•ํ•ด ๋ณด์ž.

 

initializer์˜ ์„ ์–ธ๋ถ€์—๋Š” switch ํ‘œํ˜„์‹์ด ์กด์žฌํ•œ๋‹ค.
ํ•ด๋‹น ํ‘œํ˜„์‹์—๋Š”, ๊ฐ ์š”์†Œ์— ๋Œ€ํ•œ ๋™์ž‘ ์ผ€์ด์Šค์™€ nil์„ ๋ฐ˜ํ™˜ํ•˜๋Š” default๋ฅผ ๊ฐ–๊ณ , ์šฐ๋ฆฌ๋Š” ์ด ๋ชจ๋“  ์ผ€์ด์Šค์— ๋Œ€ํ•œ Syntax Node(๊ตฌ๋ฌธ ๋…ธ๋“œ)๋ฅผ ์ž‘์„ฑํ•ด์•ผ ํ•œ๋‹ค.

 

๊ทธ๋ฆฌ๊ณ  ์ด๋Ÿฐ Syntax Node(๊ตฌ๋ฌธ ๋…ธ๋“œ)๋ฅผ ์ž‘์„ฑํ•˜๊ธฐ ์œ„ํ•ด์„  ์•„๋ž˜์˜ 2๊ฐ€์ง€ ๋ฐฉ๋ฒ•์ด ์žˆ๋‹ค.

  1. po๋ฅผ ํ™œ์šฉํ•œ Syntax Tree(๊ตฌ๋ฌธ ํŠธ๋ฆฌ) ์ถœ๋ ฅํ•ด์„œ ์ฐพ๋Š” ๋ฐฉ๋ฒ•
  2. SwiftSyntax ๋ฌธ์„œ๋ฅผ ์ง์ ‘ ์ฝ๊ณ  ๋ช…๋ น์–ด ์ฐพ๋Š” ๋ฐฉ๋ฒ•

 

3-1. initializerDeclSyntax ๊ตฌ์ถ•ํ•˜๊ธฐ

์šฐ์„ , initializerDeclSyntax๋ฅผ ๋งŒ๋“ค์–ด๋ณด๋„๋ก ํ•˜์ž.

 

initializerDeclSyntax๋Š” @resultBuilder๋ฅผ ํ™œ์šฉํ•ด์„œ ๋ณธ๋ฌธ์„ ๋นŒ๋“œํ•˜๊ณ , header๋ฅผ ์ง€์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.
header๋Š” init ํ‚ค์›Œ๋“œ์™€ ๋ชจ๋“  ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์ง€์ •ํ•˜๊ณ , ์ด๊ฒƒ์„ @resultBuilder์—์„œ for ๋ฃจํ”„๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ•„์š”ํ•œ ์š”์†Œ๋งˆ๋‹ค ์ „๋ถ€ ๋ฐ˜๋ณตํ•  ์ˆ˜ ์žˆ๋‹ค.

 

๋ณธ๋ฌธ ๋‚ด๋ถ€์—๋Š” switch ํ‘œํ˜„์‹์ด ํ•„์š”ํ•˜๋‹ค.
๋งˆ์ฐฌ๊ฐ€์ง€๋กœ switch ํ‘œํ˜„์‹์„ ์œ„ํ•œ ๊ตฌ๋ฌธ์ธ SwitchExprSyntax ๋„ InitializerDeclSyntax์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ header์™€ @resultBuilder์—์„œ ์‚ฌ์šฉํ•  ๋ณธ๋ฌธ ์ฝ”๋“œ๋ธ”๋ก์ด ํ•„์š”ํ•˜๋‹ค.

 

๊ทธ๋ฆฌ๊ณ  switch์˜ @resultBuilder๋ธ”๋ก์—๋Š” ์•ž์—์„œ ๋ชจ์•˜๋˜ elements๋“ค์„ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.
์ด๋•Œ, ๋™์ผ ๊ตฌ๋ฌธ์— ๋Œ€ํ•œ ๋ฐ˜๋ณต์ด ์žˆ์œผ๋‹ˆ for๋ฌธ์„ ํ™œ์šฉํ•˜๋ฉด ๋œ๋‹ค

 

๋˜ํ•œ, case์— ๋Œ€ํ•œ ํ‘œํ˜„์‹์ธ SwitchCaseSyntax๋ฅผ ํ™œ์šฉํ•˜๊ณ  ๊ฐ ์š”์†Œ์— ๋Œ€ํ•ด์„œ ๋ฌธ์ž์—ด ๋ณด๊ฐ„์„ ์‚ฌ์šฉํ•˜์—ฌ ํ•ญ๋ชฉ์„ ์ƒ์„ฑํ•ด ์ค€๋‹ค.

 

๋ฐ˜๋ณต์ ์ธ case ์ดํ›„์—๋Š” default์— ํ•ด๋‹นํ•˜๋Š” ์š”์†Œ๋„ ๋”ฐ๋กœ for๋ฌธ ๋ฐ–์— ์ž‘์„ฑํ•ด ์ฃผ์ž.

 

๊ทธ๋ฆฌ๊ณ  ๋งˆ์ง€๋ง‰์œผ๋กœ initializer๋ฅผ DeclSyntax๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ return ํ•ด์ค€๋‹ค.

 

(์—ฌ๊ธฐ์„œ, ๋น„ํ•˜์ธ๋“œ ์Šคํ† ๋ฆฌ๋Š”  expandedSource ๋ฅผ ์ž‘์„ฑํ•  ๋•Œ ๋ฏธ์„ธํ•œ ๊ณต๋ฐฑ๊ณผ ์ค„ ๋ฐ”๊ฟˆ ๋•Œ๋ฌธ์— ๋ช‡ ๋ฒˆ ์‹คํŒจํ–ˆ๋‹ค๋Š” ์‚ฌ์‹ค,,,, ์ค„ ๋ฐ”๊ฟˆ๊ณผ ๊ณต๋ฐฑ์€ ๊ผญ ์‹ ๊ฒฝ์“ฐ์ž....)

์•„๋ฆ„๋‹ต๊ฒŒ ์„ฑ๊ณตํ•œ๋‹ค...!

 

4. ์‹ค์ œ ์‚ฌ์šฉ์„ ์œ„ํ•ด App์— macro Package ์ถ”๊ฐ€ํ•˜๊ธฐ

์ด์ œ ์‹ค์ œ App์ธ SampleApp_Ski์— ์šฐ๋ฆฌ๊ฐ€ ๋งŒ๋“  macro๋ฅผ ์ถ”๊ฐ€ํ•ด ๋ณด์ž.

 

File >> Add Package Dependencies๋ฅผ ์„ ํƒ

 

ํ•˜๋‹จ์˜ Add Local... ์„ ํƒ ํ›„, ์ƒ์„ฑํ•œ WWDC๋ฅผ ์ถ”๊ฐ€ํ•ด ์ค€๋‹ค.

 

์ด์ œ ์‹ค์ œ WWDC ๋ชจ๋“ˆ์„ ๊ฐ€์ ธ์™€์„œ, SlopeSubset macro๋ฅผ EasySlope ํƒ€์ž…์— ์ ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

 

ํ•˜์ง€๋งŒ ์ง€๊ธˆ ์ƒํƒœ์—์„œ ๋นŒ๋“œ๋ฅผ ํ•˜๋ฉด, ์œ„์™€ ๊ฐ™์€ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋Š”๋ฐ
๋‹น์—ฐํžˆ macro์— ์˜ํ•ด์„œ ์ƒ์„ฑ๋˜๋Š” init?()๊ณผ ์ค‘๋ณต๋˜๊ธฐ ๋•Œ๋ฌธ์—, ์šฐ๋ฆฌ๊ฐ€ ์ง์ ‘ ์ •์˜ํ–ˆ๋˜ init?() ์ง€์›Œ๋ฒ„๋ ค์•ผ ํ•œ๋‹ค.

 

๊ทธ๋ฆฌ๊ณ  @SlopeSubset์„ ์šฐํด๋ฆญํ•ด์„œ expand macro๋ฅผ ์„ ํƒํ•˜๋ฉด, ์ปดํŒŒ์ผ๋Ÿฌ๋ฅผ ํ†ตํ•ด ํ™•์žฅ๋œ ์ฝ”๋“œ๋ฅผ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.
computed-property์ธ var slope: Slope {... } ๋„ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ macro๋กœ ์ •์˜ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

Write macro ์ •๋ฆฌ

  1. Swift macro package๋ฅผ ์ƒ์„ฑํ•ด์„œ template์„ ๋งŒ๋“ ๋‹ค.
  2. ์‹ค์ œ expand ๋  ์ฝ”๋“œ๋ฅผ Syntax Tree(๊ตฌ๋ฌธ ํŠธ๋ฆฌ)๋กœ ์ž‘์„ฑํ•˜๊ธฐ ์œ„ํ•ด, break-point๋ฅผ ์ง์ ‘ ๊ฑธ์–ด์„œ ํ™•์ธํ•จ
    -> ์ด๋ฅผ ํ†ตํ•ด, ์ถ”์ถœํ•  element๊นŒ์ง€ ์ ‘๊ทผํ•˜๋Š” Syntax ๊ตฌ์กฐ๋ฅผ ์•Œ ์ˆ˜ ์žˆ์Œ
  3. ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๋ฅผ ํ†ตํ•ด, macro ๊ฒฐ๊ณผ๋ฅผ ์ •ํ•ด๋‘๊ณ  ์‹ค์ œ expansionํ•จ์ˆ˜ ๊ตฌํ˜„์ฒด๋ฅผ ๊ฐœ๋ฐœํ•œ๋‹ค.
  4. ๊ตฌํ˜„์ด ์™„๋ฃŒ๋œ macro๋ฅผ Package์˜ ํ˜•ํƒœ๋กœ App์— ์ถ”๊ฐ€ํ•œ๋‹ค.

 


 

Diagnostics

macro๊ฐ€ ์ง€์›ํ•˜์ง€ ์•Š๋Š” ๊ธฐ๋Šฅ์— macro๋ฅผ ์‚ฌ์šฉํ•˜๊ฒŒ ๋œ๋‹ค๋ฉด ์–ด๋–ป๊ฒŒ ๋ ๊นŒ?
์ด๋Ÿด ๋•, ์‚ฌ์šฉ์ž๊ฐ€ ์ž˜๋ชป ํ™•์žฅ๋œ ์ฝ”๋“œ๋ฅผ ํ†ตํ•ด ๋””๋ฒ„๊ทธ ํ•˜๋„๋ก ํ•˜๋Š” ๋Œ€์‹  ํ•ญ์ƒ ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€๋ฅผ ๋‚ด๋ณด๋‚ด๊ฒŒ ๋œ๋‹ค.

 

๊ทธ๋Ÿฐ ์˜๋ฏธ์—์„œ ์œ„์˜ ์ฝ”๋“œ์— ๋‚จ๊ฒจ๋‘” TODO๋ฅผ ์ฒ˜๋ฆฌํ•˜๋„๋ก ํ•˜์ž.
์ด ๊ฒฝ์šฐ๋Š”, @SlopeSubset์ด ์„ ์–ธ๋œ ํƒ€์ž…์ด enum์ด ์•„๋‹Œ ๊ฒฝ์šฐ์— error๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ฒŒ ๋˜๋Š” ์ผ€์ด์Šค์ด๋‹ค.

 

final class WWDCTests: XCTestCase {

    func testSlopeSubsetOnStruct() {
        assertMacroExpansion(
            """
            @SlopeSubset
            struct Skier {
            }
            """,
            expandedSource:
            """
            struct Skier {
            }
            """,
            macros: testMacros
        )
    }
}

์ด์ „์— ๊ตฌํ˜„ํ•  ๋•Œ์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ, ์šฐ์„  ํ…Œ์ŠคํŠธ์ผ€์ด์Šค ๋จผ์ € ์ •์˜ํ•˜๋Š”๋ฐ
์šฐ๋ฆฌ๋Š” error๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค๋Š” ์ผ€์ด์Šค๋ฅผ ํ…Œ์ŠคํŠธํ•ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์—, struct์— @SlopeSubset์„ ์ ์šฉํ•ด ๋ณด์ž

 

final class WWDCTests: XCTestCase {
    func testSlopeSubsetOnStruct() throws {
        assertMacroExpansion(
            """
            @SlopeSubset
            struct Skier {
            }
            """,
            expandedSource:
            """
            struct Skier {
            }
            """,
            diagnostics: [
                DiagnosticSpec(message: "@SlopeSubset can only be applied to an enum", line: 1, column: 1)
            ],
            macros: testMacros
        )
    }
}

์ด๋ ‡๊ฒŒ ๋˜๋ฉด ์—ด๊ฑฐํ˜• ์š”์†Œ๊ฐ€ ์—†๊ธฐ ๋•Œ๋ฌธ์—, ์‹ค์ œ macro์—์„œ๋Š” initializer๋ฅผ ์ƒ์„ฑํ•˜์ง€ ์•Š๊ณ  Diagnostics(์ง„๋‹จ)์„ ๋‚ด๋ณด๋‚ด์•ผ ํ•œ๋‹ค.
-> ์—ฌ๊ธฐ์„œ Diagnostics(์ง„๋‹จ) ์ด๋ž€, 'SlopeSubset์€ ์—ด๊ฑฐํ˜•์—๋งŒ ์ ์šฉ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค'๋ผ๊ณ  ๋ฉ”์‹œ์ง€๋ฅผ ๋‚จ๊ธฐ๋Š” ๊ฒƒ!

 

```swift
final class WWDCTests: XCTestCase {
    func testSlopeSubsetOnStruct() throws {
        assertMacroExpansion( // ERROR: failed - Expected 1 diagnostics but received 0:
            """
            @SlopeSubset
            struct Skier {
            }
            """,
            expandedSource:
            """
            struct Skier {
            }
            """,
            diagnostics: [
                DiagnosticSpec(message: "@SlopeSubset can only be applied to an enum", line: 1, column: 1)
            ],
            macros: testMacros
        )
    }
}

์ด ์ƒํƒœ์—์„œ ํ…Œ์ŠคํŠธ๋ฅผ ์ง„ํ–‰ํ•˜๋ฉด ์‹คํŒจํ•˜๊ฒŒ ๋˜๊ณ , ์•„์ง ์ง„๋‹จ ๋ฉ”์‹œ์ง€๋Š” ์ถœ๋ ฅ๋˜์ง€ ์•Š๋Š”๋‹ค.

 

macro error ์ถœ๋ ฅํ•˜๊ธฐ

macro์— ๋Œ€ํ•œ error๋Š” Swift Error ํ”„๋กœํ† ์ฝœ์„ ์ค€์ˆ˜ํ•˜๋Š” ๋ชจ๋“  ํƒ€์ž…์œผ๋กœ ํ‘œํ˜„ ๊ฐ€๋Šฅํ•˜๋‹ค.

enum SlopeSubsetError: CustomStringConvertible, Error {
    case onlyApplicableToEnum
    var description: String {
        switch self {
        case .onlyApplicableToEnum: return "@SlopeSubset can only be applied to an enum"
        }
    }
}

/// Implementation of the `SlopeSubset` macro.
public struct SlopeSubsetMacro: MemberMacro {
    public static func expansion(
        of node: AttributeSyntax,
        providingMembersOf declaration: some DeclGroupSyntax,
        in context: some MacroExpansionContext
    ) throws -> [DeclSyntax] {
        guard let enumDecl = declaration.as(EnumDeclSyntax.self) else {
            throw SlopeSubsetError.onlyApplicableToEnum
        }
        ...
    }
}

์ด๋ ‡๊ฒŒ expansion๋‚ด๋ถ€์—์„œ error๋ฅผ throw ํ•˜๋ฉด, ์‹ค์ œ macro๋ฅผ ์ ์šฉํ•˜๋Š” attribute์—์„œ ํ‘œ์‹œ๋œ๋‹ค.

 

+ ์ถ”๊ฐ€์ ์œผ๋กœ ์ข€ ๋” ๋งŽ์€ ์ผ€์ด์Šค์— ๋Œ€ํ•œ error ๋Œ€์‘์„ ํ•˜๋ ค๋ฉด, addDiagnostic ๋ฉ”์„œ๋“œ๋ฅผ ํ™œ์šฉํ•˜๋ฉด ๋œ๋‹ค.

 

๊ทธ๋ฆฌ๊ณ  ์‹ค์ œ๋กœ @SlopeSubset๋ฅผ ๊ตฌ์กฐ์ฒด์— ์ ์šฉํ•˜๋ฉด ์œ„์™€ ๊ฐ™์ด error ๋ฉ”์‹œ์ง€๊ฐ€ ์ž˜ ๋‚˜์˜ค๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

 

์™„์„ฑ๋œ macro์˜ ์ œ๋„ค๋ฆญ ํƒ€์ž… ์ถ”๊ฐ€ -> macro์˜ ์ผ๋ฐ˜ํ™”

ํ•ด๋‹น macro๊ฐ€ Slope๋ผ๋Š” ๊ณ ์ •์ ์ธ ์—ด๊ฑฐํ˜• ํƒ€์ž…๋ฟ ์•„๋‹ˆ๋ผ, ์ผ๋ฐ˜์ ์ธ ์—ด๊ฑฐํ˜• ํƒ€์ž…์—๋„ ์ ์šฉ๋˜๋ฉด ๋งŽ์€ ๊ฐœ๋ฐœ์ž๋“ค์ด ํšจ๊ณผ์ ์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์„ ๊ฒƒ์ด๋‹ค.

 

์ด๋ฅผ ์œ„ํ•ด์„ , ์ œ๋„ค๋ฆญ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ macro์˜ ์„ ์–ธ์— ์ถ”๊ฐ€ํ•ด ์ฃผ๋ฉด ๋œ๋‹ค.
๊ทธ๋ฆฌ๊ณ , SlopeSubset์ด๋ผ๋Š” ์ด๋ฆ„๋„ EnumSubset์œผ๋กœ ๋ณ€๊ฒฝํ•˜๋„๋ก ํ•˜์ž

 

    func testSlopeSubset() {
        assertMacroExpansion(
            """
            @EnumSlope<Slope>
            enum EasySlope {
                case beginnersParadice
                case practiceRun
            }
            """,
            ...
        )
    // > **(lldb) po node**
    // >AttributeSyntax
    // >โ”œโ”€atSign: atSign
    // >โ•ฐโ”€attributeName: IdentifierTypeSyntax
    // >โ”œโ”€name: identifier("EnumSlope")
    // >โ•ฐโ”€genericArgumentClause: GenericArgumentClauseSyntax
    // >    โ”œโ”€leftAngle: leftAngle
    // >    โ”œโ”€arguments: GenericArgumentListSyntax
    // >    โ”‚ โ•ฐโ”€[0]: GenericArgumentSyntax
    // >    โ”‚   โ•ฐโ”€argument: IdentifierTypeSyntax
    // >    โ”‚     โ•ฐโ”€name: identifier("Slope")
    // >    โ•ฐโ”€rightAngle: rightAngle

๋˜ํ•œ, macro์˜ ๊ตฌํ˜„์ฒด์—์„œ ํ˜„์žฌ๋Š” Slope๋ผ๋Š” ๊ณ ์ • ํƒ€์ž… ๋Œ€์‹ ์— ์ œ๋„ค๋ฆญ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ํšŒ์ˆ˜ํ•ด์•ผ ํ•œ๋‹ค.
์ด์ „๊ณผ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ break-point๋ฅผ ํ™œ์šฉํ•ด์„œ node์— ๋Œ€ํ•œ Syntax Tree(๊ตฌ๋ฌธ ํŠธ๋ฆฌ)๋ฅผ ์ถœ๋ ฅํ•˜์—ฌ ๋ถ„์„ํ•œ๋‹ค.

 

        guard let superSetType = node
            .attributeName.as(IdentifierTypeSyntax.self)?
            .genericArgumentClause?
            .arguments.first?
            .argument else {
            // TODO: Handle error
            return []
        }


        // > (11db) po superSetType
        // > IdentifierTypeSyntax
        // > โ•ฐโ”€name: identifier("Slope")

์ด๋ฅผ ์‹ค์ œ ์ฝ”๋“œ๋กœ ์ถ”์ถœํ•˜๋ฉด ์œ„์™€ ๊ฐ™๋‹ค.

 

guard let superSetType = node
    .attributeName.as(IdentifierTypeSyntax.self)?
    .genericArgumentClause?
    .arguments.first?
    .argument else {
    // TODO: Handle error
    return []
}

...

let initializer = try InitializerDeclSyntax("init?(_ slope: \(superSetType))") { ... }

๊ทธ๋Ÿผ ๊ธฐ์กด์˜ let initializer๋ฅผ ์œ„์˜ ์ฝ”๋“œ์ฒ˜๋Ÿผ ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.

์ดํ›„ ํ…Œ์ŠคํŠธ๋ฅผ ์ง„ํ–‰ํ•˜๋ฉด, ๋ชจ๋‘ ์ž˜ ํ†ต๊ณผํ•œ๋‹ค!

 


 

Summary

  • macro Package Template๋ฅผ ํ†ตํ•ด ์†์‰ฝ๊ฒŒ ์‹œ์ž‘ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ๋งคํฌ๋กœ๋ฅผ ๊ฐœ๋ฐœํ•˜๋Š” ๋™์•ˆ ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๋ฅผ ๊ผญ ์ž‘์„ฑํ•ด์„œ ๋งคํฌ๋กœ๊ฐ€ ์ƒ์„ฑํ•˜๋Š” ์ฝ”๋“œ๊ฐ€ ์œ ํšจํ•œ์ง€ ํ™•์ธํ•ด์•ผ ํ•œ๋‹ค.
  • ์ง„ํ–‰ํ•˜๋ฉด์„œ expansion ํ•จ์ˆ˜์— ์ค‘๋‹จ์ ์„ ์„ค์ •ํ•˜๊ณ  ๋””๋ฒ„๊ฑฐ์—์„œ Syntax Tree(๊ตฌ๋ฌธ ํŠธ๋ฆฌ)๋ฅผ ์ถœ๋ ฅํ•ด Syntax Tree(๊ตฌ๋ฌธ ํŠธ๋ฆฌ)๋ฅผ ๋ถ„์„ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ํŠน์ • ์ƒํ™ฉ์—์„œ ๋งคํฌ๋กœ๋ฅผ ์ ์šฉํ•  ์ˆ˜ ์—†๋‹ค๋ฉด ์ปค์Šคํ…€ ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€๋ฅผ ํ•ญ์ƒ ๋‚ด๋ณด๋‚ด์•ผ ํ•ฉ๋‹ˆ๋‹ค

 


์ฐธ๊ณ ์ž๋ฃŒ:

https://medium.com/@gar.hovsepyan/macros-in-swift-a-practical-guide-to-using-fa1a24eba8bb

 

Macros in Swift: A Practical Guide to Using

I recently encountered a task that required writing a lot of boilerplate code. Remembering that Swift 5.9 introduced macros specifically…

medium.com

https://developer.apple.com/videos/play/wwdc2023/10166/

 

Write Swift macros - WWDC23 - Videos - Apple Developer

Discover how you can use Swift macros to make your codebase more expressive and easier to read. Code along as we explore how macros can...

developer.apple.com

 

728x90
๋ฐ˜์‘ํ˜•
๋Œ“๊ธ€
๋ฐ˜์‘ํ˜•
๋งํฌ
์ตœ๊ทผ์— ์˜ฌ๋ผ์˜จ ๊ธ€
Total
Today
Yesterday