-
Notifications
You must be signed in to change notification settings - Fork 13
[SPT-1572] intro in macro #231
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
NullIsOne
wants to merge
13
commits into
develop
Choose a base branch
from
feature/SPT-1476/macro
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
b5fc6bf
add macro for adding `mutating` func for struct
NullIsOne db14181
extend macro tests
NullIsOne 6161160
apply macro in components and example
NullIsOne 5510a74
update runner images
NullIsOne 6846289
fix linter issues
NullIsOne e45be48
add unit test for mutable macro
NullIsOne 81e0140
switch CI to xcode 15
NullIsOne 4e17ee8
init buildable macro and initialize
NullIsOne 2f36fe1
add propertywrapper protocol
NullIsOne c02405b
add conformance to editor wrapper
NullIsOne 3771e05
fix macrodefinition
NullIsOne e398300
fix building of macros
NullIsOne 1a5c5ad
fix mutable macro test
NullIsOne File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| .DS_Store | ||
| /.build | ||
| /Packages | ||
| xcuserdata/ | ||
| DerivedData/ | ||
| .swiftpm/configuration/registries.json | ||
| .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata | ||
| .netrc |
8 changes: 8 additions & 0 deletions
8
Components/Macro/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| <?xml version="1.0" encoding="UTF-8"?> | ||
| <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||
| <plist version="1.0"> | ||
| <dict> | ||
| <key>IDEDidComputeMac32BitWarning</key> | ||
| <true/> | ||
| </dict> | ||
| </plist> |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| // swift-tools-version: 5.9 | ||
| // The swift-tools-version declares the minimum version of Swift required to build this package. | ||
|
|
||
| import PackageDescription | ||
| import CompilerPluginSupport | ||
|
|
||
| let package = Package( | ||
| name: "Macro", | ||
| platforms: [.macOS(.v10_15), .iOS(.v13), .tvOS(.v13), .watchOS(.v6), .macCatalyst(.v13)], | ||
| products: [ | ||
| // Products define the executables and libraries a package produces, making them visible to other packages. | ||
| .library( | ||
| name: "Macro", | ||
| type: .dynamic, | ||
| targets: ["Macro"] | ||
| ), | ||
| .executable( | ||
| name: "MacroClient", | ||
| targets: ["MacroClient"] | ||
| ) | ||
| ], | ||
| dependencies: [ | ||
| // Depend on the latest Swift 5.9 prerelease of SwiftSyntax | ||
| .package(url: "https://github.com/apple/swift-syntax.git", from: "509.0.0-swift-5.9-DEVELOPMENT-SNAPSHOT-2023-04-25-b") | ||
| ], | ||
| targets: [ | ||
| // Targets are the basic building blocks of a package, defining a module or a test suite. | ||
| // Targets can depend on other targets in this package and products from dependencies. | ||
| // Macro implementation that performs the source transformation of a macro. | ||
| .macro( | ||
| name: "Macros", | ||
| dependencies: [ | ||
| .product(name: "SwiftSyntaxMacros", package: "swift-syntax"), | ||
| .product(name: "SwiftCompilerPlugin", package: "swift-syntax") | ||
| ] | ||
| ), | ||
|
|
||
| // Library that exposes a macro as part of its API, which is used in client programs. | ||
| .target(name: "Macro", | ||
| dependencies: [ | ||
| "Macros" | ||
| ] | ||
| ), | ||
|
|
||
| // A client of the library, which is able to use the macro in its own code. | ||
| .executableTarget(name: "MacroClient", dependencies: ["Macro"]), | ||
|
|
||
| // A test target used to develop the macro implementation. | ||
| .testTarget( | ||
| name: "MacroTests", | ||
| dependencies: [ | ||
| "Macro", | ||
| .product(name: "SwiftSyntaxMacrosTestSupport", package: "swift-syntax") | ||
| ] | ||
| ) | ||
| ] | ||
| ) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| // The Swift Programming Language | ||
| // https://docs.swift.org/swift-book | ||
|
|
||
| /// A macro which generate `mutating func` for all variables inside struct. | ||
| @attached(member, names: named(set)) | ||
| public macro Mutable() = #externalMacro(module: "Macros", type: "MutableMacro") | ||
|
|
||
| @attached(conformance) | ||
| @attached(member, names: named(create), named(Property)) | ||
| public macro Buildable() = #externalMacro(module: "Macros", type: "BuildableMacro") |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| import Macro | ||
|
|
||
| // MARK: - Precondition | ||
|
|
||
| @Mutable | ||
| struct SomeStruct { | ||
| let id: Int = Int.random(in: Int.min...Int.max) | ||
| var someFlag: Bool = false | ||
| var someString: String = "" | ||
| } | ||
|
|
||
| // MARK: - Helpers | ||
|
|
||
| extension SomeStruct { | ||
|
|
||
| func debugPrintAll() { | ||
| debugPrint(someFlag) | ||
| debugPrint(someString) | ||
| } | ||
|
|
||
| } | ||
|
|
||
| // MARK: - Debugging | ||
|
|
||
| var someStruct = SomeStruct() | ||
|
|
||
| someStruct.debugPrintAll() | ||
|
|
||
| someStruct.set(someFlag: true) | ||
| someStruct.set(someString: "echo") | ||
|
|
||
| someStruct.debugPrintAll() | ||
|
|
||
| someStruct.set(someFlag: false) | ||
| someStruct.set(someString: "horray") | ||
|
|
||
| someStruct.debugPrintAll() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,110 @@ | ||
| // | ||
| // BuildableMacro.swift | ||
| // | ||
| // | ||
| // Created by Никита Коробейников on 25.07.2023. | ||
| // | ||
|
|
||
| import SwiftSyntax | ||
| import SwiftSyntaxBuilder | ||
| import SwiftSyntaxMacros | ||
|
|
||
| /// A macro which generate `func build` for all variables inside struct. | ||
| /// - Note: builder is based on resultBuilder from Swift 5.4 | ||
| public struct BuildableMacro: MemberMacro, ConformanceMacro { | ||
|
|
||
| public static func expansion(of node: SwiftSyntax.AttributeSyntax, providingConformancesOf declaration: some SwiftSyntax.DeclGroupSyntax, in context: some SwiftSyntaxMacros.MacroExpansionContext) throws -> [(SwiftSyntax.TypeSyntax, SwiftSyntax.GenericWhereClauseSyntax?)] { | ||
| return [(TypeSyntax(stringLiteral: "EditorWrapper"), nil)] | ||
| } | ||
|
|
||
| public static func expansion(of node: SwiftSyntax.AttributeSyntax, providingMembersOf declaration: some SwiftSyntax.DeclGroupSyntax, in context: some SwiftSyntaxMacros.MacroExpansionContext) throws -> [SwiftSyntax.DeclSyntax] { | ||
| guard let baseStruct = declaration.as(StructDeclSyntax.self) else { | ||
| throw MacroError.onlyApplicableToStruct | ||
| } | ||
|
|
||
| let variables = baseStruct.variables | ||
|
|
||
| let editors = try prepareEditorDeclarations(for: variables) | ||
|
|
||
| let propertyStruct = preparePropertyStruct(for: baseStruct, with: editors) | ||
|
|
||
| let createFunc = try prepareCreateFunction(for: baseStruct) | ||
|
|
||
| var result: [DeclSyntaxProtocol] = [] | ||
|
|
||
| result.append(createFunc) | ||
| result.append(propertyStruct) | ||
|
|
||
| return result.compactMap { $0.as(DeclSyntax.self) } | ||
| } | ||
|
|
||
| } | ||
|
|
||
| // MARK: - Private | ||
|
|
||
| private extension BuildableMacro { | ||
|
|
||
| static func prepareCreateFunction(for baseStruct: StructDeclSyntax) throws -> FunctionDeclSyntax { | ||
| guard let baseStuctType = TypeSyntax(baseStruct) else { | ||
| throw MacroError.failedToExtractTypeOfBaseStruct | ||
| } | ||
|
|
||
| return .init(modifiers: .init(itemsBuilder: { | ||
| DeclModifierSyntax(name: .keyword(.public)) | ||
| DeclModifierSyntax(name: .keyword(.static)) | ||
| }), | ||
| identifier: .identifier("create"), | ||
| signature: .init(input: .init(parameterListBuilder: { }), | ||
| output: .init(returnType: baseStuctType)) | ||
| ) | ||
| } | ||
|
|
||
| static func preparePropertyStruct(for baseStruct: StructDeclSyntax, with editors: [FunctionDeclSyntax]) -> StructDeclSyntax { | ||
| StructDeclSyntax(modifiers: .init(itemsBuilder: { | ||
| DeclModifierSyntax(name: .keyword(.public)) | ||
| }), | ||
| identifier: .identifier("Property"), | ||
| memberBlock: .init(membersBuilder: { | ||
| .init(itemsBuilder: { | ||
| TypealiasDeclSyntax( | ||
| modifiers: .init(itemsBuilder: { | ||
| DeclModifierSyntax(name: .keyword(.public)) | ||
| }), | ||
| identifier: | ||
| .identifier("Model"), | ||
| initializer: .init(baseStruct)!) | ||
|
|
||
| // VariableDeclSyntax(modifiers: .init(itemsBuilder: { | ||
| // DeclModifierSyntax(name: .keyword(.private)) | ||
| // }), name: .init(stringLiteral: "closure"), .keyword(.let)) | ||
| }) | ||
| })) | ||
| } | ||
|
|
||
| static func prepareEditorDeclarations(for variables: [VariableDeclSyntax]) throws -> [FunctionDeclSyntax] { | ||
| try variables.compactMap { variableDecl -> FunctionDeclSyntax? in | ||
|
|
||
| guard let variable = try variableDecl.parseNameAndType() else { | ||
| return nil | ||
| } | ||
|
|
||
| return FunctionDeclSyntax(leadingTrivia: .newlines(2), | ||
| modifiers: .init(itemsBuilder: { | ||
| DeclModifierSyntax(name: .keyword(.static)) | ||
| }), | ||
| identifier: .identifier(variable.name.text), | ||
| signature: .init(input: .init(parameterListBuilder: { | ||
| FunctionParameterSyntax(leadingTrivia: .space, | ||
| firstName: .identifier("value"), | ||
| type: variable.type) | ||
| })), | ||
| body: CodeBlockSyntax(statementsBuilder: { | ||
| StmtSyntax(stringLiteral: "var model = model") | ||
| StmtSyntax(stringLiteral: "model.set(\(variable.name.text):value)") | ||
| StmtSyntax(stringLiteral: "return model") | ||
| }) | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| // | ||
| // Decl+Extensions.swift | ||
| // | ||
| // | ||
| // Created by Никита Коробейников on 25.07.2023. | ||
| // | ||
|
|
||
| import SwiftSyntax | ||
| import SwiftSyntaxBuilder | ||
| import SwiftSyntaxMacros | ||
|
|
||
| // MARK: - Struct | ||
|
|
||
| extension StructDeclSyntax { | ||
|
|
||
| var variables: [VariableDeclSyntax] { | ||
| return memberBlock.members | ||
| .compactMap { $0.decl.as(VariableDeclSyntax.self) } | ||
| .filter { $0.bindingKeyword.text == "var" } | ||
| } | ||
|
|
||
|
|
||
| } | ||
|
|
||
| // MARK: - Variable | ||
|
|
||
| extension VariableDeclSyntax { | ||
|
|
||
| func parseNameAndType() throws -> (name: TokenSyntax, type: TypeSyntax)? { | ||
| guard let variableBinding = bindings.first, | ||
| let variableType = variableBinding.typeAnnotation?.type.trimmed else { | ||
| let variableName = bindings.first?.pattern.trimmedDescription | ||
| throw MacroError.typeAnnotationRequiredFor(variableName: variableName ?? "unknown") | ||
| } | ||
|
|
||
| let variableName = TokenSyntax(stringLiteral: variableBinding.pattern.description) | ||
|
|
||
| return (name: variableName, type: variableType) | ||
| } | ||
|
|
||
| } | ||
|
|
||
| // MARK: - CodeBlockExpression | ||
|
|
||
| extension CodeBlockItemSyntax { | ||
|
|
||
| static func stringItem(_ string: String) -> CodeBlockItemSyntax.Item { | ||
| CodeBlockItemSyntax.Item.expr(.init(stringLiteral: string)) | ||
| } | ||
|
|
||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| // | ||
| // MacroError.swift | ||
| // | ||
| // | ||
| // Created by Никита Коробейников on 16.06.2023. | ||
| // | ||
|
|
||
| public enum MacroError: Error { | ||
|
|
||
| case onlyApplicableToStruct | ||
| case typeAnnotationRequiredFor(variableName: String) | ||
| case failedToExtractTypeOfBaseStruct | ||
|
|
||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Какое то уникальное название надо придумать)