Skip to content

Conform TokenSyntax to ExpressibleByStringLiteral and ExpressibleByStringInterpolation #507

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

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Sources/SwiftSyntaxBuilder/generated/TokenSyntax.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,9 @@ extension TokenSyntax {
public func createExprBuildable()-> ExprBuildable {
return createIdentifierExpr()
}
}
extension TokenSyntax: ExpressibleByStringLiteral, ExpressibleByStringInterpolation {
public init (stringLiteral: String) {
self = .identifier(stringLiteral)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ let buildableBaseProtocolsFile = SourceFile {
"/// - Parameter format: The `Format` to use.",
"/// - Parameter leadingTrivia: Replaces the last leading trivia if not nil.",
].map { .docLineComment($0) + .newline }.reduce([], +),
identifier: .identifier("build\(type.baseName)List"),
identifier: "build\(type.baseName)List",
signature: FunctionSignature(
input: formatLeadingTriviaParameters(),
output: ArrayType(elementType: type.syntax)
Expand All @@ -64,7 +64,7 @@ let buildableBaseProtocolsFile = SourceFile {
"/// - Parameter format: The `Format` to use.",
"/// - Parameter leadingTrivia: Replaces the last leading trivia if not nil.",
].map { .docLineComment($0) + .newline }.reduce([], +),
identifier: .identifier("build\(type.baseName)"),
identifier: "build\(type.baseName)",
signature: FunctionSignature(
input: formatLeadingTriviaParameters(),
output: type.syntax
Expand All @@ -79,7 +79,7 @@ let buildableBaseProtocolsFile = SourceFile {
) {
FunctionDecl(
leadingTrivia: .docLineComment("/// Satisfies conformance to `\(type.expressibleAs)`.") + .newline,
identifier: .identifier("create\(type.buildableBaseName)"),
identifier: "create\(type.buildableBaseName)",
signature: FunctionSignature(
input: ParameterClause(),
output: type.buildable
Expand All @@ -96,7 +96,7 @@ let buildableBaseProtocolsFile = SourceFile {
"///",
"/// Satisfies conformance to `\(type.listBuildable)`",
].map { .docLineComment($0) + .newline }.reduce([], +),
identifier: .identifier("build\(type.baseName)List"),
identifier: "build\(type.baseName)List",
signature: FunctionSignature(
input: formatLeadingTriviaParameters(withDefaultTrivia: true),
output: ArrayType(elementType: type.syntax)
Expand All @@ -120,7 +120,7 @@ let buildableBaseProtocolsFile = SourceFile {
"///",
"/// Satisfies conformance to `SyntaxBuildable`.",
].map { .docLineComment($0) + .newline }.reduce([], +),
identifier: .identifier("buildSyntax"),
identifier: "buildSyntax",
signature: FunctionSignature(
input: formatLeadingTriviaParameters(withDefaultTrivia: true),
output: "Syntax"
Expand All @@ -144,13 +144,13 @@ private func formatLeadingTriviaParameters(withDefaultTrivia: Bool = false) -> P
ParameterClause(
parameterList: [
FunctionParameter(
firstName: .identifier("format"),
firstName: "format",
colon: .colon,
type: "Format",
trailingComma: .comma
),
FunctionParameter(
firstName: .identifier("leadingTrivia"),
firstName: "leadingTrivia",
colon: .colon,
type: OptionalType(wrappedType: "Trivia"),
defaultArgument: withDefaultTrivia ? InitializerClause(value: "nil") : nil
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ let expressibleAsProtocolsFile = SourceFile {
inheritanceClause: createTypeInheritanceClause(conformances: declaredConformances.map(\.expressibleAs))
) {
FunctionDecl(
identifier: .identifier("create\(type.buildableBaseName)"),
identifier: "create\(type.buildableBaseName)",
signature: FunctionSignature(
input: ParameterClause(),
output: type.buildable
Expand All @@ -60,7 +60,7 @@ let expressibleAsProtocolsFile = SourceFile {
for conformance in type.elementInCollections {
FunctionDecl(
leadingTrivia: .docLineComment("/// Conformance to `\(conformance.expressibleAs)`") + .newline,
identifier: .identifier("create\(conformance.buildableBaseName)"),
identifier: "create\(conformance.buildableBaseName)",
signature: FunctionSignature(
input: ParameterClause(),
output: conformance.buildable
Expand All @@ -77,7 +77,7 @@ let expressibleAsProtocolsFile = SourceFile {
let param = Node.from(type: conformance).singleNonDefaultedChild
FunctionDecl(
leadingTrivia: .docLineComment("/// Conformance to \(conformance.expressibleAs)") + .newline,
identifier: .identifier("create\(conformance.buildableBaseName)"),
identifier: "create\(conformance.buildableBaseName)",
signature: FunctionSignature(
input: ParameterClause(),
output: conformance.buildable
Expand All @@ -90,7 +90,7 @@ let expressibleAsProtocolsFile = SourceFile {
}
if let baseType = baseType, baseType.baseName != "SyntaxCollection" {
FunctionDecl(
identifier: .identifier("create\(baseType.buildableBaseName)"),
identifier: "create\(baseType.buildableBaseName)",
signature: FunctionSignature(
input: ParameterClause(),
output: baseType.buildable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ let tokenSyntaxFile = SourceFile {
FunctionDecl(
leadingTrivia: .docLineComment("/// Conformance to \(conformance.expressibleAs)") + .newline,
modifiers: [TokenSyntax.public],
identifier: .identifier("create\(conformance.buildableBaseName)"),
identifier: "create\(conformance.buildableBaseName)",
signature: FunctionSignature(
input: ParameterClause(),
output: conformance.buildable
Expand All @@ -49,7 +49,7 @@ let tokenSyntaxFile = SourceFile {
FunctionDecl(
leadingTrivia: .docLineComment("/// Conformance to \(conformance.expressibleAs)") + .newline,
modifiers: [TokenSyntax.public],
identifier: .identifier("create\(conformance.buildableBaseName)"),
identifier: "create\(conformance.buildableBaseName)",
signature: FunctionSignature(
input: ParameterClause(),
output: conformance.buildable
Expand All @@ -69,7 +69,7 @@ let tokenSyntaxFile = SourceFile {
for buildable in ["SyntaxBuildable", "ExprBuildable"] {
FunctionDecl(
modifiers: [TokenSyntax.public],
identifier: .identifier("create\(buildable)"),
identifier: "create\(buildable)",
signature: FunctionSignature(
input: ParameterClause(),
output: buildable
Expand All @@ -79,4 +79,33 @@ let tokenSyntaxFile = SourceFile {
}
}
}

ExtensionDecl(
extendedType: "TokenSyntax",
inheritanceClause: createTypeInheritanceClause(conformances: [
"ExpressibleByStringLiteral",
"ExpressibleByStringInterpolation"
])
) {
InitializerDecl(
modifiers: [TokenSyntax.public],
parameters: ParameterClause(
parameterList: [
FunctionParameter(
firstName: "stringLiteral",
colon: .colon,
type: "String"
)
]
)
) {
SequenceExpr(elements: ExprList([
"self",
AssignmentExpr(),
FunctionCallExpr(MemberAccessExpr(name: "identifier")) {
TupleExprElement(expression: "stringLiteral")
}
]))
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ let tokensFile = SourceFile {
parameterList: FunctionParameter(
attributes: nil,
firstName: .wildcard,
secondName: .identifier("text"),
secondName: "text",
colon: .colon,
type: "String"
),
Expand All @@ -70,7 +70,7 @@ let tokensFile = SourceFile {

FunctionDecl(
modifiers: [TokenSyntax.static],
identifier: .identifier("`\(token.name.withFirstCharacterLowercased)`"),
identifier: "`\(token.name.withFirstCharacterLowercased)`",
signature: signature
) {
FunctionCallExpr(MemberAccessExpr(base: "SyntaxFactory", name: "make\(token.name)")) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ final class ArrayExpressibleAsTests: XCTestCase {
FunctionParameter(
attributes: nil,
firstName: .wildcard,
secondName: .identifier("args"),
secondName: "args",
colon: .colon,
type: ArrayType(elementType: "String")
)
Expand Down
4 changes: 2 additions & 2 deletions Tests/SwiftSyntaxBuilderTest/FunctionTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ final class FunctionTests: XCTestCase {
let leadingTrivia = Trivia.garbageText("␣")

let input = ParameterClause(parameterList: [
FunctionParameter(firstName: .wildcard, secondName: .identifier("n"), colon: .colon, type: "Int")
FunctionParameter(firstName: .wildcard, secondName: "n", colon: .colon, type: "Int")
])

let ifCodeBlock = ReturnStmt(expression: IntegerLiteralExpr(digits: "n"))

let signature = FunctionSignature(input: input, output: "Int")

let buildable = FunctionDecl(identifier: .identifier("fibonacci"), signature: signature) {
let buildable = FunctionDecl(identifier: "fibonacci", signature: signature) {
IfStmt(conditions: ExprList([
IntegerLiteralExpr(digits: "n"),
BinaryOperatorExpr("<="),
Expand Down
2 changes: 1 addition & 1 deletion Tests/SwiftSyntaxBuilderTest/IdentifierExprTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ final class IdentifierExprTests: XCTestCase {
let leadingTrivia = Trivia.garbageText("␣")

let testCases: [UInt: (ExpressibleAsIdentifierExpr, String)] = [
#line: (IdentifierExpr(identifier: .identifier("Test")), "␣Test"),
#line: (IdentifierExpr(identifier: "Test"), "␣Test"),
#line: (IdentifierExpr("Test"), "␣Test"),
#line: ("Test", "␣Test")
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ final class IdentifierPatternTests: XCTestCase {
let leadingTrivia = Trivia.garbageText("␣")

let testCases: [UInt: (ExpressibleAsIdentifierPattern, String)] = [
#line: (IdentifierPattern(identifier: .identifier("Test")), "␣Test"),
#line: (IdentifierPattern(identifier: "Test"), "␣Test"),
#line: (IdentifierPattern("Test"), "␣Test"),
#line: ("Test", "␣Test")
]
Expand Down
6 changes: 3 additions & 3 deletions Tests/SwiftSyntaxBuilderTest/ProtocolDeclTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ final class ProtocolDeclTests: XCTestCase {
func testProtocolDecl() {
let returnType = ArrayType(elementType: "DeclSyntax")
let input = ParameterClause {
FunctionParameter(firstName: .identifier("format"), colon: .colon, type: "Format")
FunctionParameter(firstName: .identifier("leadingTrivia"), colon: .colon, type: OptionalType(wrappedType: "Trivia"))
FunctionParameter(firstName: "format", colon: .colon, type: "Format")
FunctionParameter(firstName: "leadingTrivia", colon: .colon, type: OptionalType(wrappedType: "Trivia"))
}
let functionSignature = FunctionSignature(input: input, output: returnType)

let buildable = ProtocolDecl(modifiers: [TokenSyntax.public], identifier: "DeclListBuildable") {
FunctionDecl(identifier: .identifier("buildDeclList"), signature: functionSignature, body: nil)
FunctionDecl(identifier: "buildDeclList", signature: functionSignature, body: nil)
}

let syntax = buildable.buildSyntax(format: Format())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ final class SimpleTypeIdentifierTests: XCTestCase {
let leadingTrivia = Trivia.garbageText("␣")

let testCases: [UInt: (ExpressibleAsSimpleTypeIdentifier, String)] = [
#line: (SimpleTypeIdentifier(name: .identifier("Foo")), "␣Foo"),
#line: (SimpleTypeIdentifier(name: "Foo"), "␣Foo"),
#line: (SimpleTypeIdentifier("Foo"), "␣Foo"),
#line: ("Foo", "␣Foo")
]
Expand Down
2 changes: 1 addition & 1 deletion Tests/SwiftSyntaxBuilderTest/StructTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ final class StructTests: XCTestCase {
genericWhereClause: GenericWhereClause {
GenericRequirement(body: ConformanceRequirement(leftTypeIdentifier: "A", rightTypeIdentifier: "X"))
GenericRequirement(body: SameTypeRequirement(
leftTypeIdentifier: "A.P", equalityToken: .identifier("=="), rightTypeIdentifier: "D"))
leftTypeIdentifier: "A.P", equalityToken: "==", rightTypeIdentifier: "D"))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is mostly what I’m worried about. Technically, == should never have been an identifier but .spacedBinaryOperator("=="). This doesn’t really matter if all you do with the syntax tree is print it but e.g. a formatter might care about the distinction.

It’s not the most pretty solution but what do you think about the following: We introduce a new type IdentifierToken and use that as a parameter for all syntax children that have type IdentifierToken

struct IdentifierToken: ExpressibleByStringLiteral {
  let token: TokenSyntax

  init(_ token: TokenSyntax) {
    assert(token.tokenKind == .identifier)
    self.token = token
  }
  
  // If necessary, also add these
  func withLeadingTrivia(_ leadingTrivia: Trivia) -> IdentifierToken { /* … */ }
  func withTrailingTrivia(_ trailing: Trivia) -> IdentifierToken { /* … */ }
}

That would cover the common case of just passing in a string for the identifier. Using any of the functions that are defined on TokenSyntax becomes a little harder because you first need to create TokenSyntax.identifier, then modify it and wrap it in IdentifierToken, but maybe that’s not a big issue in practice.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, yeah, we should fix that test.

I like the idea of statically encoding where we expect an identifier. The only thing I could think about is that there are a few places (like argument labels) where also other tokens like .wildcard are 'allowed', but perhaps it would be fine to keep TokenSyntax in these places to make this explicit at the use site.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I have experimented a bit with this idea over here, unfortunately fewer nodes than I expected are pure/arbitrary identifiers. Most prominently, FunctionDecl still has to take a TokenSyntax since it could e.g. represent a operator too. Since the vast majority of uses (at least in the test suite and among the SwiftSyntaxBuilderGeneration templates is FunctionDecl identifiers, I don't think such a workaround would be worth the hassle for now.

Perhaps we could just add a convenience initializer for FunctionDecl that takes a String parameter? It feels like there ought to be some more general solution, but apart from providing an ExpressibleAsStringLiteral conformance on TokenSyntax directly, I don't really see how we could provide this sugar without disproportionally complicating some other part of SwiftSyntaxBuilder.

}
) {}
let testStruct = StructDecl(
Expand Down