Skip to content

Commit 81885ea

Browse files
BridgeJS: Perf-tune SwiftToSkeleton by avoiding unnecessary Syntax node allocation (#577)
`trimmedDescription` allocates two new Syntax nodes just to get the trimmed description of a node. ```swift extension SyntaxProtocol { public var trimmedDescription: String { // TODO: We shouldn't need to create to copies just to get the trimmed // description. return self.trimmed.description } public var trimmed: Self { // TODO: Should only need one new node here return self.with(\.leadingTrivia, []).with(\.trailingTrivia, []) } } ``` ``` $ cat check.d.ts export function getCanvas(x: string): HTMLCanvasElement; $ node Plugins/BridgeJS/Sources/TS2Swift/JavaScript/bin/ts2swift.js check.d.ts -p tsconfig.json -o out.swift $ hyperfine "./Plugins/BridgeJS/.build/debug/BridgeJSToolInternal.before emit-skeleton out.swift" "./Plugins/BridgeJS/.build/debug/BridgeJSToolInternal.after emit-skeleton out.swift" Benchmark 1: ./Plugins/BridgeJS/.build/debug/BridgeJSToolInternal.before emit-skeleton out.swift Time (mean ± σ): 16.531 s ± 0.317 s [User: 16.355 s, System: 0.107 s] Range (min … max): 16.026 s … 17.252 s 10 runs Benchmark 2: ./Plugins/BridgeJS/.build/debug/BridgeJSToolInternal.after emit-skeleton out.swift Time (mean ± σ): 4.068 s ± 0.134 s [User: 3.976 s, System: 0.039 s] Range (min … max): 4.000 s … 4.445 s 10 runs Summary ./Plugins/BridgeJS/.build/debug/BridgeJSToolInternal.after emit-skeleton out.swift ran 4.06 ± 0.15 times faster than ./Plugins/BridgeJS/.build/debug/BridgeJSToolInternal.before emit-skeleton out.swift ```
1 parent 56aabfa commit 81885ea

File tree

1 file changed

+19
-17
lines changed

1 file changed

+19
-17
lines changed

Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -394,22 +394,28 @@ private enum ExportSwiftConstants {
394394
static let supportedRawTypes = SwiftEnumRawType.allCases.map { $0.rawValue }
395395
}
396396

397+
extension AttributeSyntax {
398+
/// The attribute name as text when it is a simple identifier (e.g. "JS", "JSFunction").
399+
/// Prefer this over `attributeName.trimmedDescription` for name checks to avoid unnecessary string work.
400+
fileprivate var attributeNameText: String? {
401+
attributeName.as(IdentifierTypeSyntax.self)?.name.text
402+
}
403+
}
404+
397405
extension AttributeListSyntax {
398406
func hasJSAttribute() -> Bool {
399407
firstJSAttribute != nil
400408
}
401409

402410
var firstJSAttribute: AttributeSyntax? {
403-
first(where: {
404-
$0.as(AttributeSyntax.self)?.attributeName.trimmedDescription == "JS"
405-
})?.as(AttributeSyntax.self)
411+
first(where: { $0.as(AttributeSyntax.self)?.attributeNameText == "JS" })?.as(AttributeSyntax.self)
406412
}
407413

408414
/// Returns true if any attribute has the given name (e.g. "JSClass").
409415
func hasAttribute(name: String) -> Bool {
410416
contains { attribute in
411417
guard let syntax = attribute.as(AttributeSyntax.self) else { return false }
412-
return syntax.attributeName.trimmedDescription == name
418+
return syntax.attributeNameText == name
413419
}
414420
}
415421
}
@@ -1916,46 +1922,42 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor {
19161922
}
19171923

19181924
static func firstJSFunctionAttribute(_ attributes: AttributeListSyntax?) -> AttributeSyntax? {
1919-
attributes?.first { attribute in
1920-
attribute.as(AttributeSyntax.self)?.attributeName.trimmedDescription == "JSFunction"
1921-
}?.as(AttributeSyntax.self)
1925+
firstAttribute(attributes, named: "JSFunction")
19221926
}
19231927

19241928
static func hasJSGetterAttribute(_ attributes: AttributeListSyntax?) -> Bool {
19251929
hasAttribute(attributes, name: "JSGetter")
19261930
}
19271931

19281932
static func firstJSGetterAttribute(_ attributes: AttributeListSyntax?) -> AttributeSyntax? {
1929-
attributes?.first { attribute in
1930-
attribute.as(AttributeSyntax.self)?.attributeName.trimmedDescription == "JSGetter"
1931-
}?.as(AttributeSyntax.self)
1933+
firstAttribute(attributes, named: "JSGetter")
19321934
}
19331935

19341936
static func hasJSSetterAttribute(_ attributes: AttributeListSyntax?) -> Bool {
19351937
hasAttribute(attributes, name: "JSSetter")
19361938
}
19371939

19381940
static func firstJSSetterAttribute(_ attributes: AttributeListSyntax?) -> AttributeSyntax? {
1939-
attributes?.first { attribute in
1940-
attribute.as(AttributeSyntax.self)?.attributeName.trimmedDescription == "JSSetter"
1941-
}?.as(AttributeSyntax.self)
1941+
firstAttribute(attributes, named: "JSSetter")
19421942
}
19431943

19441944
static func hasJSClassAttribute(_ attributes: AttributeListSyntax?) -> Bool {
19451945
hasAttribute(attributes, name: "JSClass")
19461946
}
19471947

19481948
static func firstJSClassAttribute(_ attributes: AttributeListSyntax?) -> AttributeSyntax? {
1949-
attributes?.first { attribute in
1950-
attribute.as(AttributeSyntax.self)?.attributeName.trimmedDescription == "JSClass"
1951-
}?.as(AttributeSyntax.self)
1949+
firstAttribute(attributes, named: "JSClass")
1950+
}
1951+
1952+
static func firstAttribute(_ attributes: AttributeListSyntax?, named name: String) -> AttributeSyntax? {
1953+
attributes?.first { $0.as(AttributeSyntax.self)?.attributeNameText == name }?.as(AttributeSyntax.self)
19521954
}
19531955

19541956
static func hasAttribute(_ attributes: AttributeListSyntax?, name: String) -> Bool {
19551957
guard let attributes else { return false }
19561958
return attributes.contains { attribute in
19571959
guard let syntax = attribute.as(AttributeSyntax.self) else { return false }
1958-
return syntax.attributeName.trimmedDescription == name
1960+
return syntax.attributeNameText == name
19591961
}
19601962
}
19611963

0 commit comments

Comments
 (0)