├── .gitignore ├── Example ├── Example.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── swiftpm │ │ └── Package.resolved └── Example │ └── main.swift ├── Package.resolved ├── Package.swift ├── README.md ├── Sources ├── ProxyMacro │ └── Proxy.swift └── ProxyMacros │ ├── Plugin.swift │ ├── ProxyMacro.swift │ └── ProxyMacroDiagnostic.swift └── Tests └── ProxyMacrosTests └── ProxyMacroTests.swift /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | xcuserdata/ 4 | DerivedData/ 5 | .swiftpm 6 | -------------------------------------------------------------------------------- /Example/Example.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 60; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 722899302B01875C000D7741 /* ProxyMacro in Frameworks */ = {isa = PBXBuildFile; productRef = 7228992F2B01875C000D7741 /* ProxyMacro */; }; 11 | 728335682B0183420084455C /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 728335672B0183420084455C /* main.swift */; }; 12 | 728335762B0183A20084455C /* ProxyMacro in Frameworks */ = {isa = PBXBuildFile; productRef = 728335752B0183A20084455C /* ProxyMacro */; }; 13 | /* End PBXBuildFile section */ 14 | 15 | /* Begin PBXCopyFilesBuildPhase section */ 16 | 728335622B0183420084455C /* CopyFiles */ = { 17 | isa = PBXCopyFilesBuildPhase; 18 | buildActionMask = 2147483647; 19 | dstPath = /usr/share/man/man1/; 20 | dstSubfolderSpec = 0; 21 | files = ( 22 | ); 23 | runOnlyForDeploymentPostprocessing = 1; 24 | }; 25 | /* End PBXCopyFilesBuildPhase section */ 26 | 27 | /* Begin PBXFileReference section */ 28 | 728335642B0183420084455C /* Example */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = Example; sourceTree = BUILT_PRODUCTS_DIR; }; 29 | 728335672B0183420084455C /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; 30 | /* End PBXFileReference section */ 31 | 32 | /* Begin PBXFrameworksBuildPhase section */ 33 | 728335612B0183420084455C /* Frameworks */ = { 34 | isa = PBXFrameworksBuildPhase; 35 | buildActionMask = 2147483647; 36 | files = ( 37 | 722899302B01875C000D7741 /* ProxyMacro in Frameworks */, 38 | 728335762B0183A20084455C /* ProxyMacro in Frameworks */, 39 | ); 40 | runOnlyForDeploymentPostprocessing = 0; 41 | }; 42 | /* End PBXFrameworksBuildPhase section */ 43 | 44 | /* Begin PBXGroup section */ 45 | 7283355B2B0183420084455C = { 46 | isa = PBXGroup; 47 | children = ( 48 | 728335662B0183420084455C /* Example */, 49 | 728335652B0183420084455C /* Products */, 50 | ); 51 | sourceTree = ""; 52 | }; 53 | 728335652B0183420084455C /* Products */ = { 54 | isa = PBXGroup; 55 | children = ( 56 | 728335642B0183420084455C /* Example */, 57 | ); 58 | name = Products; 59 | sourceTree = ""; 60 | }; 61 | 728335662B0183420084455C /* Example */ = { 62 | isa = PBXGroup; 63 | children = ( 64 | 728335672B0183420084455C /* main.swift */, 65 | ); 66 | path = Example; 67 | sourceTree = ""; 68 | }; 69 | /* End PBXGroup section */ 70 | 71 | /* Begin PBXNativeTarget section */ 72 | 728335632B0183420084455C /* Example */ = { 73 | isa = PBXNativeTarget; 74 | buildConfigurationList = 7283356B2B0183420084455C /* Build configuration list for PBXNativeTarget "Example" */; 75 | buildPhases = ( 76 | 728335602B0183420084455C /* Sources */, 77 | 728335612B0183420084455C /* Frameworks */, 78 | 728335622B0183420084455C /* CopyFiles */, 79 | ); 80 | buildRules = ( 81 | ); 82 | dependencies = ( 83 | ); 84 | name = Example; 85 | packageProductDependencies = ( 86 | 728335752B0183A20084455C /* ProxyMacro */, 87 | 7228992F2B01875C000D7741 /* ProxyMacro */, 88 | ); 89 | productName = Example; 90 | productReference = 728335642B0183420084455C /* Example */; 91 | productType = "com.apple.product-type.tool"; 92 | }; 93 | /* End PBXNativeTarget section */ 94 | 95 | /* Begin PBXProject section */ 96 | 7283355C2B0183420084455C /* Project object */ = { 97 | isa = PBXProject; 98 | attributes = { 99 | BuildIndependentTargetsInParallel = 1; 100 | LastSwiftUpdateCheck = 1500; 101 | LastUpgradeCheck = 1500; 102 | TargetAttributes = { 103 | 728335632B0183420084455C = { 104 | CreatedOnToolsVersion = 15.0; 105 | }; 106 | }; 107 | }; 108 | buildConfigurationList = 7283355F2B0183420084455C /* Build configuration list for PBXProject "Example" */; 109 | compatibilityVersion = "Xcode 14.0"; 110 | developmentRegion = en; 111 | hasScannedForEncodings = 0; 112 | knownRegions = ( 113 | en, 114 | Base, 115 | ); 116 | mainGroup = 7283355B2B0183420084455C; 117 | packageReferences = ( 118 | 7228992E2B01875C000D7741 /* XCLocalSwiftPackageReference ".." */, 119 | ); 120 | productRefGroup = 728335652B0183420084455C /* Products */; 121 | projectDirPath = ""; 122 | projectRoot = ""; 123 | targets = ( 124 | 728335632B0183420084455C /* Example */, 125 | ); 126 | }; 127 | /* End PBXProject section */ 128 | 129 | /* Begin PBXSourcesBuildPhase section */ 130 | 728335602B0183420084455C /* Sources */ = { 131 | isa = PBXSourcesBuildPhase; 132 | buildActionMask = 2147483647; 133 | files = ( 134 | 728335682B0183420084455C /* main.swift in Sources */, 135 | ); 136 | runOnlyForDeploymentPostprocessing = 0; 137 | }; 138 | /* End PBXSourcesBuildPhase section */ 139 | 140 | /* Begin XCBuildConfiguration section */ 141 | 728335692B0183420084455C /* Debug */ = { 142 | isa = XCBuildConfiguration; 143 | buildSettings = { 144 | ALWAYS_SEARCH_USER_PATHS = NO; 145 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 146 | CLANG_ANALYZER_NONNULL = YES; 147 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 148 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 149 | CLANG_ENABLE_MODULES = YES; 150 | CLANG_ENABLE_OBJC_ARC = YES; 151 | CLANG_ENABLE_OBJC_WEAK = YES; 152 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 153 | CLANG_WARN_BOOL_CONVERSION = YES; 154 | CLANG_WARN_COMMA = YES; 155 | CLANG_WARN_CONSTANT_CONVERSION = YES; 156 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 157 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 158 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 159 | CLANG_WARN_EMPTY_BODY = YES; 160 | CLANG_WARN_ENUM_CONVERSION = YES; 161 | CLANG_WARN_INFINITE_RECURSION = YES; 162 | CLANG_WARN_INT_CONVERSION = YES; 163 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 164 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 165 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 166 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 167 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 168 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 169 | CLANG_WARN_STRICT_PROTOTYPES = YES; 170 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 171 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 172 | CLANG_WARN_UNREACHABLE_CODE = YES; 173 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 174 | COPY_PHASE_STRIP = NO; 175 | DEBUG_INFORMATION_FORMAT = dwarf; 176 | ENABLE_STRICT_OBJC_MSGSEND = YES; 177 | ENABLE_TESTABILITY = YES; 178 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 179 | GCC_C_LANGUAGE_STANDARD = gnu17; 180 | GCC_DYNAMIC_NO_PIC = NO; 181 | GCC_NO_COMMON_BLOCKS = YES; 182 | GCC_OPTIMIZATION_LEVEL = 0; 183 | GCC_PREPROCESSOR_DEFINITIONS = ( 184 | "DEBUG=1", 185 | "$(inherited)", 186 | ); 187 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 188 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 189 | GCC_WARN_UNDECLARED_SELECTOR = YES; 190 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 191 | GCC_WARN_UNUSED_FUNCTION = YES; 192 | GCC_WARN_UNUSED_VARIABLE = YES; 193 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 194 | MACOSX_DEPLOYMENT_TARGET = 14.0; 195 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 196 | MTL_FAST_MATH = YES; 197 | ONLY_ACTIVE_ARCH = YES; 198 | SDKROOT = macosx; 199 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; 200 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 201 | }; 202 | name = Debug; 203 | }; 204 | 7283356A2B0183420084455C /* Release */ = { 205 | isa = XCBuildConfiguration; 206 | buildSettings = { 207 | ALWAYS_SEARCH_USER_PATHS = NO; 208 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 209 | CLANG_ANALYZER_NONNULL = YES; 210 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 211 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 212 | CLANG_ENABLE_MODULES = YES; 213 | CLANG_ENABLE_OBJC_ARC = YES; 214 | CLANG_ENABLE_OBJC_WEAK = YES; 215 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 216 | CLANG_WARN_BOOL_CONVERSION = YES; 217 | CLANG_WARN_COMMA = YES; 218 | CLANG_WARN_CONSTANT_CONVERSION = YES; 219 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 220 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 221 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 222 | CLANG_WARN_EMPTY_BODY = YES; 223 | CLANG_WARN_ENUM_CONVERSION = YES; 224 | CLANG_WARN_INFINITE_RECURSION = YES; 225 | CLANG_WARN_INT_CONVERSION = YES; 226 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 227 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 228 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 229 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 230 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 231 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 232 | CLANG_WARN_STRICT_PROTOTYPES = YES; 233 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 234 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 235 | CLANG_WARN_UNREACHABLE_CODE = YES; 236 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 237 | COPY_PHASE_STRIP = NO; 238 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 239 | ENABLE_NS_ASSERTIONS = NO; 240 | ENABLE_STRICT_OBJC_MSGSEND = YES; 241 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 242 | GCC_C_LANGUAGE_STANDARD = gnu17; 243 | GCC_NO_COMMON_BLOCKS = YES; 244 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 245 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 246 | GCC_WARN_UNDECLARED_SELECTOR = YES; 247 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 248 | GCC_WARN_UNUSED_FUNCTION = YES; 249 | GCC_WARN_UNUSED_VARIABLE = YES; 250 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 251 | MACOSX_DEPLOYMENT_TARGET = 14.0; 252 | MTL_ENABLE_DEBUG_INFO = NO; 253 | MTL_FAST_MATH = YES; 254 | SDKROOT = macosx; 255 | SWIFT_COMPILATION_MODE = wholemodule; 256 | }; 257 | name = Release; 258 | }; 259 | 7283356C2B0183420084455C /* Debug */ = { 260 | isa = XCBuildConfiguration; 261 | buildSettings = { 262 | CODE_SIGN_STYLE = Automatic; 263 | DEVELOPMENT_TEAM = 8NQFWJHC63; 264 | ENABLE_HARDENED_RUNTIME = YES; 265 | PRODUCT_NAME = "$(TARGET_NAME)"; 266 | SWIFT_VERSION = 5.0; 267 | }; 268 | name = Debug; 269 | }; 270 | 7283356D2B0183420084455C /* Release */ = { 271 | isa = XCBuildConfiguration; 272 | buildSettings = { 273 | CODE_SIGN_STYLE = Automatic; 274 | DEVELOPMENT_TEAM = 8NQFWJHC63; 275 | ENABLE_HARDENED_RUNTIME = YES; 276 | PRODUCT_NAME = "$(TARGET_NAME)"; 277 | SWIFT_VERSION = 5.0; 278 | }; 279 | name = Release; 280 | }; 281 | /* End XCBuildConfiguration section */ 282 | 283 | /* Begin XCConfigurationList section */ 284 | 7283355F2B0183420084455C /* Build configuration list for PBXProject "Example" */ = { 285 | isa = XCConfigurationList; 286 | buildConfigurations = ( 287 | 728335692B0183420084455C /* Debug */, 288 | 7283356A2B0183420084455C /* Release */, 289 | ); 290 | defaultConfigurationIsVisible = 0; 291 | defaultConfigurationName = Release; 292 | }; 293 | 7283356B2B0183420084455C /* Build configuration list for PBXNativeTarget "Example" */ = { 294 | isa = XCConfigurationList; 295 | buildConfigurations = ( 296 | 7283356C2B0183420084455C /* Debug */, 297 | 7283356D2B0183420084455C /* Release */, 298 | ); 299 | defaultConfigurationIsVisible = 0; 300 | defaultConfigurationName = Release; 301 | }; 302 | /* End XCConfigurationList section */ 303 | 304 | /* Begin XCLocalSwiftPackageReference section */ 305 | 7228992E2B01875C000D7741 /* XCLocalSwiftPackageReference ".." */ = { 306 | isa = XCLocalSwiftPackageReference; 307 | relativePath = ..; 308 | }; 309 | /* End XCLocalSwiftPackageReference section */ 310 | 311 | /* Begin XCSwiftPackageProductDependency section */ 312 | 7228992F2B01875C000D7741 /* ProxyMacro */ = { 313 | isa = XCSwiftPackageProductDependency; 314 | productName = ProxyMacro; 315 | }; 316 | 728335752B0183A20084455C /* ProxyMacro */ = { 317 | isa = XCSwiftPackageProductDependency; 318 | productName = ProxyMacro; 319 | }; 320 | /* End XCSwiftPackageProductDependency section */ 321 | }; 322 | rootObject = 7283355C2B0183420084455C /* Project object */; 323 | } 324 | -------------------------------------------------------------------------------- /Example/Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/Example.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Example/Example.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "pins" : [ 3 | { 4 | "identity" : "swift-syntax", 5 | "kind" : "remoteSourceControl", 6 | "location" : "https://github.com/apple/swift-syntax.git", 7 | "state" : { 8 | "revision" : "6ad4ea24b01559dde0773e3d091f1b9e36175036", 9 | "version" : "509.0.2" 10 | } 11 | } 12 | ], 13 | "version" : 2 14 | } 15 | -------------------------------------------------------------------------------- /Example/Example/main.swift: -------------------------------------------------------------------------------- 1 | import ProxyMacro 2 | 3 | final class MyObj { 4 | private final class StateStore { 5 | var number = 42 { 6 | didSet { 7 | print("Did change value in \(type(of: self)) from \(oldValue) to \(number)") 8 | } 9 | } 10 | } 11 | 12 | @Proxy(\Self.stateStore.number) 13 | var number: Int 14 | 15 | private let stateStore = StateStore() 16 | } 17 | 18 | let obj = MyObj() 19 | 20 | print(obj.number) 21 | obj.number = 100 22 | print(obj.number) 23 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "pins" : [ 3 | { 4 | "identity" : "swift-syntax", 5 | "kind" : "remoteSourceControl", 6 | "location" : "https://github.com/apple/swift-syntax.git", 7 | "state" : { 8 | "revision" : "74203046135342e4a4a627476dd6caf8b28fe11b", 9 | "version" : "509.0.0" 10 | } 11 | } 12 | ], 13 | "version" : 2 14 | } 15 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 5.9 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | import CompilerPluginSupport 6 | 7 | let package = Package( 8 | name: "ProxyMacro", 9 | platforms: [.macOS(.v10_15), .iOS(.v13)], 10 | products: [ 11 | .library(name: "ProxyMacro", targets: [ 12 | "ProxyMacro" 13 | ]) 14 | ], 15 | dependencies: [ 16 | .package(url: "https://github.com/swiftlang/swift-syntax", from: "509.0.0") 17 | ], 18 | targets: [ 19 | .target(name: "ProxyMacro", dependencies: [ 20 | "ProxyMacros" 21 | ]), 22 | .macro(name: "ProxyMacros", dependencies: [ 23 | .product(name: "SwiftSyntaxMacros", package: "swift-syntax"), 24 | .product(name: "SwiftCompilerPlugin", package: "swift-syntax") 25 | ]), 26 | .testTarget(name: "ProxyMacrosTests", dependencies: [ 27 | "ProxyMacros", 28 | .product(name: "SwiftSyntaxMacrosTestSupport", package: "swift-syntax") 29 | ]) 30 | ] 31 | ) 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ProxyMacro 2 | 3 | Swift macro that proxies a property from one object to another. 4 | 5 | ```swift 6 | final class MyObj { 7 | private final class StateStore { 8 | var number = 42 9 | } 10 | 11 | @Proxy(\Self.stateStore.number) 12 | var number: Int 13 | 14 | private let stateStore = StateStore() 15 | } 16 | ``` 17 | 18 | This is useful when state needs to be kept in sync between multiple objects but we do not want to forward the state using `willSet`/`didSet`. In this case we pass the state store to the children instead. We can still expose the `number` property on `MyObj` and any reads and writes will be forwarded to the `number` propety on `StateStore`. 19 | 20 | ```swift 21 | final class MyObj { 22 | private final class StateStore { 23 | var number = 42 24 | } 25 | 26 | @Proxy(\Self.stateStore.number) 27 | var number: Int 28 | 29 | private let stateStore = StateStore() 30 | private lazy var childA = Child(stateStore: stateStore) 31 | private lazy var childB = Child(stateStore: stateStore) 32 | } 33 | 34 | final class Child { 35 | private let stateStore: StateStore 36 | 37 | init(stateStore: StateStore) { 38 | self.stateStore = stateStore 39 | } 40 | } 41 | ``` 42 | -------------------------------------------------------------------------------- /Sources/ProxyMacro/Proxy.swift: -------------------------------------------------------------------------------- 1 | @attached(accessor) 2 | public macro Proxy(_ keyPath: ReferenceWritableKeyPath) = #externalMacro( 3 | module: "ProxyMacros", 4 | type: "ProxyMacro" 5 | ) 6 | -------------------------------------------------------------------------------- /Sources/ProxyMacros/Plugin.swift: -------------------------------------------------------------------------------- 1 | import SwiftCompilerPlugin 2 | import SwiftSyntaxMacros 3 | 4 | @main 5 | struct Plugin: CompilerPlugin { 6 | let providingMacros: [Macro.Type] = [ 7 | ProxyMacro.self 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /Sources/ProxyMacros/ProxyMacro.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntax 2 | import SwiftCompilerPluginMessageHandling 3 | import SwiftSyntaxMacros 4 | import SwiftDiagnostics 5 | 6 | public struct ProxyMacro: AccessorMacro { 7 | public static func expansion( 8 | of node: AttributeSyntax, 9 | providingAccessorsOf declaration: some DeclSyntaxProtocol, 10 | in context: some MacroExpansionContext 11 | ) throws -> [AccessorDeclSyntax] { 12 | guard case let .argumentList(argumentList) = node.arguments, let argument = argumentList.first else { 13 | let diagnostic = Diagnostic(node: node, message: ProxyMacroDiagnostic.missingArgument) 14 | context.diagnose(diagnostic) 15 | return [] 16 | } 17 | guard let keyPathExpr = argument.as(LabeledExprSyntax.self)?.expression.as(KeyPathExprSyntax.self) else { 18 | let diagnostic = Diagnostic(node: argument, message: ProxyMacroDiagnostic.notAKeyPath) 19 | context.diagnose(diagnostic) 20 | return [] 21 | } 22 | let getAccessor: AccessorDeclSyntax = 23 | """ 24 | get { 25 | return self[keyPath: \(raw: keyPathExpr.description)] 26 | } 27 | """ 28 | let setAccessor: AccessorDeclSyntax = 29 | """ 30 | set { 31 | self[keyPath: \(raw: keyPathExpr.description)] = newValue 32 | } 33 | """ 34 | return [getAccessor, setAccessor] 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Sources/ProxyMacros/ProxyMacroDiagnostic.swift: -------------------------------------------------------------------------------- 1 | import SwiftDiagnostics 2 | 3 | enum ProxyMacroDiagnostic: String, DiagnosticMessage { 4 | case missingArgument 5 | case notAKeyPath 6 | 7 | var severity: DiagnosticSeverity { 8 | .error 9 | } 10 | var diagnosticID: MessageID { 11 | MessageID(domain: "ExperimentsMacros", id: rawValue) 12 | } 13 | var message: String { 14 | switch self { 15 | case .missingArgument: 16 | "Please supply a key path as argument." 17 | case .notAKeyPath: 18 | "Supplied argument must be a key path." 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Tests/ProxyMacrosTests/ProxyMacroTests.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntaxMacros 2 | import SwiftSyntaxMacrosTestSupport 3 | import XCTest 4 | #if canImport(ProxyMacros) 5 | import ProxyMacros 6 | #endif 7 | 8 | final class ProxyMacroTests: XCTestCase { 9 | func testItGeneratesGetterAndSetter() throws { 10 | #if canImport(ProxyMacros) 11 | assertMacroExpansion( 12 | """ 13 | final class Parent { 14 | @Proxy(\\Parent.state.foo) 15 | var foo: String 16 | 17 | private final class State { 18 | var foo = "foo" 19 | } 20 | 21 | private let state = State() 22 | } 23 | """, 24 | expandedSource: """ 25 | final class Parent { 26 | var foo: String { 27 | get { 28 | return self [keyPath: \\Parent.state.foo] 29 | } 30 | set { 31 | self [keyPath: \\Parent.state.foo] = newValue 32 | } 33 | } 34 | 35 | private final class State { 36 | var foo = "foo" 37 | } 38 | 39 | private let state = State() 40 | } 41 | """, 42 | macros: [ 43 | "Proxy": ProxyMacro.self 44 | ] 45 | ) 46 | #else 47 | throw XCTSkip("macros are only supported when running tests for the host platform") 48 | #endif 49 | } 50 | } 51 | --------------------------------------------------------------------------------