Upgrade to Pro — share decks privately, control downloads, hide ads and more …

SwiftSyntaxをうまく使おう

 SwiftSyntaxをうまく使おう

わいわいswiftc #35

3781f49ea2c76d6ecf0c6cda46096d49?s=128

omochimetaru

May 01, 2022
Tweet

More Decks by omochimetaru

Other Decks in Programming

Transcript

  1. SwiftSyntaxΛ ͏·͘࢖͓͏ Θ͍Θ͍swiftc #35 @omochimetaru 1

  2. SwiftίʔυΛಡΈऔΓ͍ͨ • ϝιουΛࣗಈੜ੒͍ͨ͠ • ଞͷݴޠʹม׵͍ͨ͠ 2

  3. SwiftSyntaxΛ࢖͑͹؆୯ʁ ͦ͏Ͱ΋ͳ͍ 3

  4. ࠓ೔ͷ࿩ • BinarySwiftSyntax Λ࡞ͬͨ • SwiftTypeReader Λ࡞ͬͨ 4

  5. SwiftSyntax͓͞Β͍ • SwiftίϯύΠϥ಺෦ʹɺ৽͘͠៉ྷʹ࡞Γ௚͞ΕͨύʔαϞ δϡʔϧ libSyntax ͕͋Δ • ίʔυฤूͳͲΛߟྀͨ͠APIΛ࣋ͭ • ͦΕΛSwift͔Β࢖͏ͨΊͷϥΠϒϥϦ͕

    SwiftSyntax 1 • SwiftPMύοέʔδͱͯ͠ఏڙ 1 https://github.com/apple/swift-syntax 5
  6. Ṗͷόʔδϣϯબ୒ 6

  7. ύʔαຊମ͸ผ഑෍ • SwiftSyntax ͸͋͘·Ͱͨͩͷ libSyntax ΁ͷSwiftϒϦο δ • libSyntax ͸XcodeͱҰॹʹ഑෍

    → ࣮ߦ؀ڥʹΑͬͯόʔ δϣϯ͕ҟͳΔ • SwiftSyntax ͷόʔδϣϯΛݻఆͰ͖ͯ΋ɺ࣮ߦ؀ڥͷ libSyntax ͷόʔδϣϯ͕ݻఆͰ͖ͳ͍ 7
  8. ݱ৔Ͱى͖Δ͜ͱ A͞Μʮ͜ͷίʔυδΣωϨʔλ͸SwiftSyntaxΛ࢖͍ͬͯΔ͔ ΒɺXcode13.3Λ xcode-select Ͱࢦఆ͔ͯ͠Β࣮ߦͯͩ͘͠ ͍͞ɻʯ B͞Μʮ·ͩXcode13.2͔͠ೖͬͯͳ͍Ͱ͢ɻʯ C͞Μ ʮXcode14.0 betaͰ࡞ۀͯ͠Δ͔Β੾Γସ͑ͨ͘ͳ͍Ͱ

    ͢ɻʯ 8
  9. ղܾํ๏ • libSyntax ΋Ұॹʹ഑෍͢Δ • SwiftPMͷ binary target ͳΒɺxcframework ͷதʹ

    libSyntax ΛຒΊࠐΊΔ • ͍ͭͰʹιʔε෦෼ͷϏϧυ࣌ؒ΋ΧοτͰ͖Δ 9
  10. BinarySwiftSyntax 2 • SwiftSyntaxΛϏϧυ͢ΔxcodeprojΛ࡞੒ • खݩͷXcode͔Β libSyntax Λநग़ $ cp

    "$(xcode-select -p)"/Toolchains/XcodeDefault.xctoolchain /usr/lib/swift/macosx/lib_InternalSwiftSyntaxParser.dylib SwiftSyntax/Deps • xcframeworkʹ͢Δ $ xcodebuild archive \ -project SwiftSyntax.xcodeproj \ CODE_SIGN_IDENTITY="" \ CODE_SIGNING_REQUIRED=NO \ CODE_SIGNING_ALLOWED=NO $ xcodebuild -create-xcframework \ -framework build/UninstalledProducts/macosx/SwiftSyntax.framework \ -output dist/Xcode12.5/SwiftSyntax.xcframework 2 https://github.com/omochi/BinarySwiftSyntax 10
  11. 11

  12. ؆୯ʹ࢖͑Δ // package.swift let package = Package(name: "foo", products: [

    .executable(name: "foo", targets: ["foo"]) ], dependencies: [ .package(url: "https://github.com/omochi/BinarySwiftSyntax", branch: "main") ], targets: [ .target(name: "foo", dependencies: [ // v ͜͜ͰόʔδϣϯࢦఆͰ͖Δ .product(name: "SwiftSyntax-Xcode13.0", package: "BinarySwiftSyntax") ]) ] ) // main.swift import SwiftSyntax let sourceFile: SourceFileSyntax = try SyntaxParser.parse(source: source) 12
  13. ͜ΕͰίʔυੜ੒͠์୊ʁ ͦ͏Ͱ΋ͳ͍ 13

  14. SwiftSyntax͸೉͍͠ 14

  15. Swift AST Explorer 3 @k_katsumi 3 https://swift-ast-explorer.com 15

  16. let file = try SyntaxParser.parse(source: source) for statement in file.statements

    { if let structDecl = statement.item.as(StructDeclSyntax.self) { ... } } • .item ? • .as(StructDeclSyntax.self) ? 16
  17. for decl in structDecl.members.members { if let varDecl = decl.decl.as(VariableDeclSyntax.self)

    { ... } } • .members.members ? • decl.decl ? • .as(VariableDeclSyntax.self) ? 17
  18. • حົͳAPI͕೉͍͠ • as ϝιουʹΑΔμ΢ϯΩϟετ͕೉͍͠ (swift-ast- explorerඞਢ) • Xcode͕ॏͯ͘ఆ͕ٛΑ͘ݟ͑ͳ͍ (ܕ͕ଟ͗͢Δʁ)

    18
  19. • SwiftSyntax͸ߏจϨϕϧͷϥΠϒϥϦͳͷͰɺࣗ༝౓΍දݱ ೳྗ͕ߴ͍͕ɺͦͷ෼ෳࡶͰ೉͍͠ • C++޲͚ʹ࡞ͬͨ libSyntax ͱͷϒϦοδͷؔ܎Ͱ(?)ઃܭ ʹΫη͕͋Δ • ߏจϨϕϧͷϥΠϒϥϦͳͷͰɺܕ৘ใͷ໊લղܾͳͲɺҙ

    ຯ࿦Ϩϕϧ͸αϙʔτ͞Εͳ͍ • ༻్ʹԠͨ͡ϥΠϒϥϦΛҰͭڬΜͰ࢖͍͍ͨ 19
  20. SwiftTypeReader 4 • SwiftSyntaxΛϥοϓͯ͠ɺܕ৘ใΛѻ͍΍͍͢APIͰఏڙ͢ ΔϥΠϒϥϦ • ܕҎ֎ͷ৘ใ (ؔ਺ͷຊจͳͲ) ͸ѻΘͳ͍ 4

    https://github.com/omochi/SwiftTypeReader 20
  21. func testSimple() throws { let result = try XCTReadTypes(""" struct

    S { var a: Int? } """ ) let s = try XCTUnwrap(result.module.types[safe: 0]?.struct) XCTAssertEqual(s.name, "S") XCTAssertEqual(s.location, Location([.module(name: "main")])) XCTAssertEqual(s.storedProperties.count, 1) let a = try XCTUnwrap(s.storedProperties[safe: 0]) XCTAssertEqual(a.name, "a") let aType = try XCTUnwrap(a.type().struct) XCTAssertEqual(aType.module?.name, "Swift") XCTAssertEqual(aType.name, "Optional") XCTAssertEqual(try aType.genericArguments().count, 1) let aWrappedType = try XCTUnwrap(aType.genericArguments()[safe: 0]?.struct) XCTAssertEqual(aWrappedType.module?.name, "Swift") XCTAssertEqual(aWrappedType.name, "Int") } • .storedProperties 21
  22. func testEnum() throws { let result = try XCTReadTypes(""" enum

    E { case a case b(Int) case c(x: Int, y: Int) } """ ) let e = try XCTUnwrap(result.module.types[safe: 0]?.enum) do { let c = try XCTUnwrap(e.caseElements[safe: 0]) XCTAssertEqual(c.name, "a") } do { let c = try XCTUnwrap(e.caseElements[safe: 1]) XCTAssertEqual(c.name, "b") let x = try XCTUnwrap(c.associatedValues[safe: 0]) XCTAssertNil(x.name) XCTAssertEqual(try x.type().name, "Int") } do { let c = try XCTUnwrap(e.caseElements[safe: 2]) XCTAssertEqual(c.name, "c") let x = try XCTUnwrap(c.associatedValues[safe: 0]) XCTAssertEqual(x.name, "x") XCTAssertEqual(try x.type().name, "Int") let y = try XCTUnwrap(c.associatedValues[safe: 1]) XCTAssertEqual(y.name, "y") XCTAssertEqual(try y.type().name, "Int") } } • .caseElements • .associatedValues 22
  23. func testGenericParameter() throws { let result = try XCTReadTypes(""" struct

    S<T> { var a: T } """ ) let s = try XCTUnwrap(result.module.types[safe: 0]?.struct) XCTAssertEqual(s.name, "S") XCTAssertEqual(s.genericParameters.count, 1) let t = try XCTUnwrap(s.genericParameters[safe: 0]) XCTAssertEqual(t.name, "T") XCTAssertEqual(s.storedProperties.count, 1) let a = try XCTUnwrap(s.storedProperties[safe: 0]) XCTAssertEqual(a.name, "a") let at = try XCTUnwrap(a.type().genericParameter) XCTAssertEqual( at.location, Location([ .module(name: "main"), .type(name: "S") ]) ) } • .genericParameter.location 23
  24. ར༻ྫ: SE0295Polyfill 5 • SE-0295 (enumͷcodableରԠ) Λίʔυੜ੒͢ΔϥΠϒϥϦ • Swift5.5 ͕ϦϦʔε͞ΕͨͷͰطʹҾୀ

    5 https://github.com/omochi/SE0295Polyfill 24
  25. enum Command: Codable { case load(key: String) case store(key: String,

    value: Int) } 25
  26. extension Command { enum CodingKeys: Swift.CodingKey { case load case

    store } enum LoadCodingKey: Swift.CodingKey { case key } enum StoreCodingKey: Swift.CodingKey { case key case value } } extension Command { public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) switch self { case .load(key: let key): var nestedContainer = container.nestedContainer(keyedBy: LoadCodingKey.self, forKey: .load) try nestedContainer.encode(key, forKey: .key) case .store(key: let key, value: let value): var nestedContainer = container.nestedContainer(keyedBy: StoreCodingKey.self, forKey: .store) try nestedContainer.encode(key, forKey: .key) try nestedContainer.encode(value, forKey: .value) } } } extension Command { public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) if container.allKeys.count != 1 { let context = DecodingError.Context( codingPath: container.codingPath, debugDescription: "Invalid number of keys found, expected one." ) throw DecodingError.typeMismatch(Command.self, context) } switch container.allKeys.first.unsafelyUnwrapped { case .load: let nestedContainer = try container.nestedContainer(keyedBy: LoadCodingKey.self, forKey: .load) let key = try nestedContainer.decode(String.self, forKey: .key) self = .load(key: key) case .store: let nestedContainer = try container.nestedContainer(keyedBy: StoreCodingKey.self, forKey: .store) let key = try nestedContainer.decode(String.self, forKey: .key) let value = try nestedContainer.decode(Int.self, forKey: .value) self = .store(key: key, value: value) } } } 26
  27. ·ͱΊ • SwiftSyntax͸ libSyntax Λಉࠝͯ͠΄͍͠ • SwiftTypeReaderΛ࢖ͬͯίʔυੜ੒Λ͠Α͏ 27