Skip to content

Commit ade67ca

Browse files
author
Harlan
authored
[Syntax] Swift libSyntax API (swiftlang#11320)
* Create Swift libSyntax API This patch is an initial implementation of the Swift libSyntax API. It aims to provide all features of the C++ API but exposed to Swift. It currently resides in SwiftExperimental and will likely exist in a molten state for a while. * Only build SwiftSyntax on macOS
1 parent fd253da commit ade67ca

33 files changed

+2366
-9
lines changed

cmake/modules/SwiftComponents.cmake

+2-1
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
# * stdlib -- the Swift standard library.
5858
# * stdlib-experimental -- the Swift standard library module for experimental
5959
# APIs.
60+
# * swift-syntax -- the Swift module for the libSyntax Swift API
6061
# * sdk-overlay -- the Swift SDK overlay.
6162
# * editor-integration -- scripts for Swift integration in IDEs other than
6263
# Xcode;
@@ -66,7 +67,7 @@
6667
# * toolchain-dev-tools -- install development tools useful in a shared toolchain
6768
# * dev -- headers and libraries required to use Swift compiler as a library.
6869
set(_SWIFT_DEFINED_COMPONENTS
69-
"autolink-driver;compiler;clang-builtin-headers;clang-resource-dir-symlink;llvm-resource-dir-symlink;clang-builtin-headers-in-clang-resource-dir;stdlib;stdlib-experimental;sdk-overlay;editor-integration;tools;testsuite-tools;toolchain-dev-tools;dev;license;sourcekit-xpc-service;sourcekit-inproc;swift-remote-mirror;swift-remote-mirror-headers")
70+
"autolink-driver;compiler;clang-builtin-headers;clang-resource-dir-symlink;llvm-resource-dir-symlink;clang-builtin-headers-in-clang-resource-dir;stdlib;stdlib-experimental;swift-syntax;sdk-overlay;editor-integration;tools;testsuite-tools;toolchain-dev-tools;dev;license;sourcekit-xpc-service;sourcekit-inproc;swift-remote-mirror;swift-remote-mirror-headers")
7071

7172
macro(swift_configure_components)
7273
# Set the SWIFT_INSTALL_COMPONENTS variable to the default value if it is not passed in via -D

lib/AST/LegacyASTTransformer.cpp

+4-1
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,10 @@ RC<SyntaxData>
225225
LegacyASTTransformer::visitTopLevelCodeDecl(TopLevelCodeDecl *D,
226226
const SyntaxData *Parent,
227227
const CursorIndex IndexInParent) {
228-
return visitBraceStmt(D->getBody(), Parent, IndexInParent);
228+
auto body = visitBraceStmt(D->getBody(), Parent, IndexInParent);
229+
return SyntaxData::make(RawSyntax::make(SyntaxKind::TopLevelCodeDecl,
230+
{body->getRaw()},
231+
SourcePresence::Present));
229232
}
230233

231234
RC<SyntaxData>

stdlib/private/StdlibUnittest/StdlibUnittest.swift.gyb

+21
Original file line numberDiff line numberDiff line change
@@ -532,6 +532,27 @@ public func expectFalse(_ actual: Bool, ${TRACE}) {
532532
}
533533
}
534534

535+
public func expectThrows<ErrorType: Error & Equatable>(
536+
_ expectedError: ErrorType? = nil, _ test: () throws -> Void, ${TRACE}) {
537+
do {
538+
try test()
539+
} catch let error as ErrorType {
540+
if let expectedError = expectedError {
541+
expectEqual(expectedError, error)
542+
}
543+
} catch {
544+
expectationFailure("unexpected error thrown: \"\(error)\"", trace: ${trace})
545+
}
546+
}
547+
548+
public func expectDoesNotThrow(_ test: () throws -> Void, ${TRACE}) {
549+
do {
550+
try test()
551+
} catch {
552+
expectationFailure("unexpected error thrown: \"\(error)\"", trace: ${trace})
553+
}
554+
}
555+
535556
public func expectNil<T>(_ value: T?, ${TRACE}) {
536557
if value != nil {
537558
expectationFailure(

test/SwiftSyntax/LazyCaching.swift

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// RUN: %target-run-simple-swift
2+
// REQUIRES: executable_test
3+
// REQUIRES: OS=macosx
4+
5+
import StdlibUnittest
6+
import Foundation
7+
import Dispatch
8+
9+
import SwiftSyntax
10+
11+
var LazyCaching = TestSuite("LazyCaching")
12+
13+
LazyCaching.test("Pathological") {
14+
let tuple = SyntaxFactory.makeVoidTupleType()
15+
16+
DispatchQueue.concurrentPerform(iterations: 100) { _ in
17+
expectEqual(tuple.leftParen, tuple.leftParen)
18+
}
19+
}
20+
21+
LazyCaching.test("TwoAccesses") {
22+
let tuple = SyntaxFactory.makeVoidTupleType()
23+
24+
let queue1 = DispatchQueue(label: "queue1")
25+
let queue2 = DispatchQueue(label: "queue2")
26+
27+
var node1: TokenSyntax?
28+
var node2: TokenSyntax?
29+
30+
let group = DispatchGroup()
31+
queue1.async(group: group) {
32+
node1 = tuple.leftParen
33+
}
34+
queue2.async(group: group) {
35+
node2 = tuple.leftParen
36+
}
37+
group.wait()
38+
39+
let final = tuple.leftParen
40+
41+
expectNotNil(node1)
42+
expectNotNil(node2)
43+
expectEqual(node1, node2)
44+
expectEqual(node1, final)
45+
expectEqual(node2, final)
46+
}
47+
48+
runAllTests()

test/SwiftSyntax/ParseFile.swift

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// RUN: %target-run-simple-swift
2+
// REQUIRES: executable_test
3+
// REQUIRES: OS=macosx
4+
5+
import Foundation
6+
import StdlibUnittest
7+
import SwiftSyntax
8+
9+
var ParseFile = TestSuite("ParseFile")
10+
11+
ParseFile.test("ParseSingleFile") {
12+
let currentFile = URL(fileURLWithPath: #file)
13+
expectDoesNotThrow({
14+
let currentFileContents = try String(contentsOf: currentFile)
15+
let parsed = try Syntax.parse(currentFile)
16+
expectEqual("\(parsed)", currentFileContents)
17+
})
18+
}
19+
20+
runAllTests()

test/SwiftSyntax/SyntaxFactory.swift

+113
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
// RUN: %target-run-simple-swift
2+
// REQUIRES: executable_test
3+
// REQUIRES: OS=macosx
4+
5+
import Foundation
6+
import StdlibUnittest
7+
import SwiftSyntax
8+
9+
func cannedStructDecl() -> StructDeclSyntax {
10+
let fooID = SyntaxFactory.makeIdentifier("Foo", trailingTrivia: .spaces(1))
11+
let structKW = SyntaxFactory.makeStructKeyword(trailingTrivia: .spaces(1))
12+
let rBrace = SyntaxFactory.makeRightBraceToken(leadingTrivia: .newlines(1))
13+
return StructDeclSyntax {
14+
$0.useStructKeyword(structKW)
15+
$0.useIdentifier(fooID)
16+
$0.useLeftBrace(SyntaxFactory.makeLeftBraceToken())
17+
$0.useRightBrace(rBrace)
18+
}
19+
}
20+
21+
var SyntaxFactoryAPI = TestSuite("SyntaxFactoryAPI")
22+
23+
SyntaxFactoryAPI.test("Generated") {
24+
25+
let structDecl = cannedStructDecl()
26+
27+
expectEqual("\(structDecl)",
28+
"""
29+
struct Foo {
30+
}
31+
""")
32+
33+
let forType = SyntaxFactory.makeIdentifier("for",
34+
leadingTrivia: .backticks(1),
35+
trailingTrivia: [
36+
.backticks(1), .spaces(1)
37+
])
38+
let newBrace = SyntaxFactory.makeRightBraceToken(leadingTrivia: .newlines(2))
39+
40+
let renamed = structDecl.withIdentifier(forType)
41+
.withRightBrace(newBrace)
42+
43+
expectEqual("\(renamed)",
44+
"""
45+
struct `for` {
46+
47+
}
48+
""")
49+
50+
expectNotEqual(structDecl.leftBrace, renamed.leftBrace)
51+
expectEqual(structDecl, structDecl.root)
52+
expectNil(structDecl.parent)
53+
expectNotNil(structDecl.leftBrace.parent)
54+
expectEqual(structDecl.leftBrace.parent, structDecl)
55+
56+
// Ensure that accessing children via named identifiers is exactly the
57+
// same as accessing them as their underlying data.
58+
expectEqual(structDecl.leftBrace, structDecl.child(at: 7))
59+
60+
expectEqual("\(structDecl.rightBrace)",
61+
"""
62+
63+
}
64+
""")
65+
}
66+
67+
SyntaxFactoryAPI.test("TokenSyntax") {
68+
let tok = SyntaxFactory.makeStructKeyword()
69+
expectEqual("\(tok)", "struct")
70+
expectTrue(tok.isPresent)
71+
72+
let preSpacedTok = tok.withLeadingTrivia(.spaces(3))
73+
expectEqual("\(preSpacedTok)", " struct")
74+
75+
let postSpacedTok = tok.withTrailingTrivia(.spaces(6))
76+
expectEqual("\(postSpacedTok)", "struct ")
77+
78+
let prePostSpacedTok = preSpacedTok.withTrailingTrivia(.spaces(4))
79+
expectEqual("\(prePostSpacedTok)", " struct ")
80+
}
81+
82+
SyntaxFactoryAPI.test("FunctionCallSyntaxBuilder") {
83+
let string = SyntaxFactory.makeStringLiteralExpr("Hello, world!")
84+
let printID = SyntaxFactory.makeVariableExpr("print")
85+
let arg = FunctionCallArgumentSyntax {
86+
$0.useExpression(string)
87+
}
88+
let call = FunctionCallExprSyntax {
89+
$0.useCalledExpression(printID)
90+
$0.useLeftParen(SyntaxFactory.makeLeftParenToken())
91+
$0.addFunctionCallArgument(arg)
92+
$0.useRightParen(SyntaxFactory.makeRightParenToken())
93+
}
94+
expectEqual("\(call)", "print(\"Hello, world!\")")
95+
96+
let terminatorArg = FunctionCallArgumentSyntax {
97+
$0.useLabel(SyntaxFactory.makeIdentifier("terminator"))
98+
$0.useColon(SyntaxFactory.makeColonToken(trailingTrivia: .spaces(1)))
99+
$0.useExpression(SyntaxFactory.makeStringLiteralExpr(" "))
100+
}
101+
let callWithTerminator = call.withArgumentList(
102+
SyntaxFactory.makeFunctionCallArgumentList([
103+
arg.withTrailingComma(
104+
SyntaxFactory.makeCommaToken(trailingTrivia: .spaces(1))),
105+
terminatorArg
106+
])
107+
)
108+
109+
expectEqual("\(callWithTerminator)",
110+
"print(\"Hello, world!\", terminator: \" \")")
111+
}
112+
113+
runAllTests()

tools/CMakeLists.txt

+5
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,14 @@ if(SWIFT_BUILD_SOURCEKIT)
1616
add_swift_tool_subdirectory(SourceKit)
1717
endif()
1818

19+
1920
if(SWIFT_HOST_VARIANT STREQUAL "macosx")
2021
# Only build Darwin-specific tools when deploying to OS X.
2122
add_swift_tool_subdirectory(swift-stdlib-tool)
23+
24+
if(SWIFT_BUILD_STDLIB)
25+
add_subdirectory(SwiftSyntax)
26+
endif()
2227
endif()
2328

2429

tools/SwiftSyntax/AtomicCache.swift

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
//===----------- AtomicCache.swift - Atomically Initialized Cache ---------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import Foundation
14+
15+
/// AtomicCache is a wrapper class around an uninitialized value.
16+
/// It takes a closure that it will use to create the value atomically. The
17+
/// value is guaranteed to be set exactly one time, but the provided closure
18+
/// may be called multiple times by threads racing to initialize the value.
19+
/// Do not rely on the closure being called only one time.
20+
class AtomicCache<Value: AnyObject> {
21+
/// The cached pointer that will be filled in the first time `value` is
22+
/// accessed.
23+
private var _cachedValue: AnyObject?
24+
25+
/// The value inside this cache. If the value has not been initialized when
26+
/// this value is requested, then the closure will be called and its resulting
27+
/// value will be atomically compare-exchanged into the cache.
28+
/// If multiple threads access the value before initialization, they will all
29+
/// end up returning the correct, initialized value.
30+
/// - Parameter create: The closure that will return the fully realized value
31+
/// inside the cache.
32+
func value(_ create: () -> Value) -> Value {
33+
return withUnsafeMutablePointer(to: &_cachedValue) { ptr in
34+
// Perform an atomic load -- if we get a value, then return it.
35+
if let _cached = _stdlib_atomicLoadARCRef(object: ptr) {
36+
return _cached as! Value
37+
}
38+
39+
// Otherwise, create the value...
40+
let value = create()
41+
42+
// ...and attempt to initialize the pointer with that value.
43+
if _stdlib_atomicInitializeARCRef(object: ptr, desired: value) {
44+
// If we won the race, just return the value we made.
45+
return value
46+
}
47+
48+
// Otherwise, perform _another_ load to get the up-to-date value,
49+
// and let the one we just made die.
50+
return _stdlib_atomicLoadARCRef(object: ptr) as! Value
51+
}
52+
}
53+
54+
/// Unsafely attempts to load the value and cast it to the appropriate
55+
/// type.
56+
/// - note: Only for use in the debugger!
57+
@available(*, deprecated, message: "Only for use in the debugger.")
58+
var unsafeValue: Value? {
59+
return withUnsafeMutablePointer(to: &_cachedValue) {
60+
return _stdlib_atomicLoadARCRef(object: $0) as? Value
61+
}
62+
}
63+
}

tools/SwiftSyntax/CMakeLists.txt

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
add_swift_library(swiftSwiftSyntax SHARED
2+
# This file should be listed the first. Module name is inferred from the
3+
# filename.
4+
SwiftSyntax.swift
5+
AtomicCache.swift
6+
RawSyntax.swift
7+
SourcePresence.swift
8+
SwiftcInvocation.swift
9+
Syntax.swift
10+
SyntaxData.swift
11+
SyntaxChildren.swift
12+
SyntaxCollection.swift
13+
SyntaxBuilders.swift.gyb
14+
SyntaxFactory.swift.gyb
15+
SyntaxKind.swift.gyb
16+
SyntaxNodes.swift.gyb
17+
SyntaxRewriter.swift.gyb
18+
TokenKind.swift.gyb
19+
Trivia.swift
20+
21+
SWIFT_MODULE_DEPENDS Foundation
22+
INSTALL_IN_COMPONENT swift-syntax
23+
TARGET_SDKS OSX
24+
IS_STDLIB)

tools/SwiftSyntax/README.md

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# SwiftLanguage
2+
3+
This is an in-progress implementation of a Swift API for the
4+
[libSyntax](https://github.com/apple/swift/tree/master/lib/Syntax) library.

0 commit comments

Comments
 (0)