Skip to content

Commit 7a6d643

Browse files
committed
BridgeJS: Reuse existing validation for exported type names
1 parent 2dd3e86 commit 7a6d643

6 files changed

Lines changed: 8 additions & 314 deletions

File tree

Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1765,18 +1765,8 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor {
17651765
return name
17661766
}
17671767

1768-
private static let reservedTypeNames: Set<String> = [
1769-
"arguments", "await", "break", "case", "catch", "class", "const", "continue", "debugger", "default",
1770-
"delete", "do", "else", "enum", "eval", "export", "extends", "false", "finally", "for", "function",
1771-
"if", "implements", "import", "in", "instanceof", "interface", "let", "new", "null", "package",
1772-
"private", "protected", "public", "return", "static", "super", "switch", "this", "throw", "true",
1773-
"try", "typeof", "var", "void", "while", "with", "yield",
1774-
"any", "bigint", "boolean", "never", "number", "object", "string", "symbol", "undefined", "unknown",
1775-
]
1776-
17771768
private func extractValidatedJSName(
1778-
from jsAttribute: AttributeSyntax,
1779-
isTypeName: Bool = false
1769+
from jsAttribute: AttributeSyntax
17801770
) -> String? {
17811771
guard let jsName = extractJSName(from: jsAttribute) else { return nil }
17821772
guard SwiftToSkeleton.isValidJSIdentifier(jsName) else {
@@ -1786,10 +1776,6 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor {
17861776
)
17871777
return nil
17881778
}
1789-
if isTypeName && Self.reservedTypeNames.contains(jsName) {
1790-
diagnose(node: jsAttribute, message: "`\(jsName)` cannot be used as an exported type name")
1791-
return nil
1792-
}
17931779
return jsName
17941780
}
17951781

@@ -2039,7 +2025,7 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor {
20392025
return .skipChildren
20402026
}
20412027

2042-
let jsName = extractValidatedJSName(from: jsAttribute, isTypeName: true)
2028+
let jsName = extractValidatedJSName(from: jsAttribute)
20432029

20442030
if let aliasTarget = parent.extractAliasTarget(from: jsAttribute) {
20452031
recordAlias(node: node, jsAttribute: jsAttribute, aliasTarget: aliasTarget)
@@ -2196,7 +2182,7 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor {
21962182
return .skipChildren
21972183
}
21982184

2199-
let jsName = extractValidatedJSName(from: jsAttribute, isTypeName: true)
2185+
let jsName = extractValidatedJSName(from: jsAttribute)
22002186

22012187
if let aliasTarget = parent.extractAliasTarget(from: jsAttribute) {
22022188
recordAlias(node: node, jsAttribute: jsAttribute, aliasTarget: aliasTarget)
@@ -2322,7 +2308,7 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor {
23222308
return .skipChildren
23232309
}
23242310

2325-
let jsName = extractValidatedJSName(from: jsAttribute, isTypeName: true)
2311+
let jsName = extractValidatedJSName(from: jsAttribute)
23262312

23272313
if let aliasTarget = parent.extractAliasTarget(from: jsAttribute), jsName != nil {
23282314
recordAlias(node: node, jsAttribute: jsAttribute, aliasTarget: aliasTarget)
@@ -2393,7 +2379,7 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor {
23932379
return .skipChildren
23942380
}
23952381

2396-
let jsName = extractValidatedJSName(from: jsAttribute, isTypeName: true)
2382+
let jsName = extractValidatedJSName(from: jsAttribute)
23972383

23982384
if let aliasTarget = parent.extractAliasTarget(from: jsAttribute) {
23992385
recordAlias(node: node, jsAttribute: jsAttribute, aliasTarget: aliasTarget)

Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift

Lines changed: 0 additions & 193 deletions
Original file line numberDiff line numberDiff line change
@@ -1346,7 +1346,6 @@ public struct BridgeJSLink {
13461346
}
13471347

13481348
public func link(sharedMemory: Bool = false) throws -> (outputJs: String, outputDts: String) {
1349-
try validateRenamedTypeExports()
13501349
intrinsicRegistry.reset()
13511350
importedModuleRegistry.configure(skeletons: skeletons)
13521351
intrinsicRegistry.classNamespaces = skeletons.reduce(into: [:]) { result, unified in
@@ -1374,198 +1373,6 @@ public struct BridgeJSLink {
13741373
return (outputJs, outputDts)
13751374
}
13761375

1377-
private func validateRenamedTypeExports() throws {
1378-
let exported = skeletons.compactMap(\.exported)
1379-
guard
1380-
exported.contains(where: { skeleton in
1381-
skeleton.classes.contains { $0.resolvedJSName != $0.name }
1382-
|| skeleton.structs.contains { $0.resolvedJSName != $0.name }
1383-
|| skeleton.enums.contains { $0.resolvedJSName != $0.name }
1384-
|| skeleton.protocols.contains { $0.resolvedJSName != $0.name }
1385-
})
1386-
else { return }
1387-
1388-
var renamedNamespaces: [[String]] = []
1389-
for skeleton in exported {
1390-
renamedNamespaces += skeleton.classes.filter { $0.resolvedJSName != $0.name }.map(\.tsPathComponents)
1391-
renamedNamespaces += skeleton.structs.filter { $0.resolvedJSName != $0.name }.map(\.tsPathComponents)
1392-
renamedNamespaces += skeleton.enums.filter { $0.resolvedJSName != $0.name }.map(\.tsPathComponents)
1393-
}
1394-
func isRenamed(_ type: any NamespacedExportedType) -> Bool {
1395-
type.resolvedJSName != type.name
1396-
|| renamedNamespaces.contains { type.tsPathComponents.starts(with: $0) }
1397-
}
1398-
1399-
struct Symbol {
1400-
let owner: String
1401-
let renamed: Bool
1402-
let isNamespace: Bool
1403-
let canContainNamespace: Bool
1404-
}
1405-
var symbols: [[String]: [Symbol]] = [:]
1406-
let reservedBindingNames = JSGlueVariableScope.reservedVariables.union([
1407-
"exports", "js", "_exports", "bjs", "swiftHeapObjectFinalizationRegistry",
1408-
"Object", "Array", "Map", "WeakMap", "WeakRef", "FinalizationRegistry", "Promise", "Symbol",
1409-
"Math", "Number", "BigInt", "Error", "TypeError", "RangeError", "Uint8Array", "ArrayBuffer",
1410-
"DataView", "globalThis",
1411-
])
1412-
1413-
func claim(
1414-
_ path: [String],
1415-
owner: String,
1416-
renamed: Bool,
1417-
isNamespace: Bool = false,
1418-
canContainNamespace: Bool = false
1419-
) throws {
1420-
let symbol = Symbol(
1421-
owner: owner,
1422-
renamed: renamed,
1423-
isNamespace: isNamespace,
1424-
canContainNamespace: canContainNamespace
1425-
)
1426-
for existing in symbols[path] ?? [] {
1427-
guard existing.owner != owner, existing.renamed || renamed else { continue }
1428-
if existing.isNamespace && isNamespace { continue }
1429-
if existing.isNamespace && canContainNamespace && existing.owner.hasPrefix(owner + ".") { continue }
1430-
if isNamespace && existing.canContainNamespace && owner.hasPrefix(existing.owner + ".") { continue }
1431-
throw BridgeJSLinkError(
1432-
message:
1433-
"JavaScript export name '\(path.joined(separator: "."))' for '\(owner)' collides with '\(existing.owner)' after renaming a type"
1434-
)
1435-
}
1436-
symbols[path, default: []].append(symbol)
1437-
}
1438-
1439-
func claimNamespaces(_ path: [String], owner: String) throws {
1440-
for count in 1..<path.count {
1441-
try claim(Array(path.prefix(count)), owner: owner, renamed: false, isNamespace: true)
1442-
}
1443-
}
1444-
1445-
func claimMembers(
1446-
_ path: [String],
1447-
owner: String,
1448-
renamed: Bool,
1449-
methods: [ExportedFunction],
1450-
properties: [ExportedProperty]
1451-
) throws {
1452-
for method in methods {
1453-
try claim(path + [method.resolvedJSName], owner: owner + "." + method.name, renamed: renamed)
1454-
}
1455-
for property in properties {
1456-
try claim(path + [property.resolvedJSName], owner: owner + "." + property.name, renamed: renamed)
1457-
}
1458-
}
1459-
1460-
for unified in skeletons {
1461-
guard let skeleton = unified.exported else { continue }
1462-
for klass in skeleton.classes {
1463-
let owner = unified.moduleName + "." + klass.swiftCallName
1464-
let renamed = isRenamed(klass)
1465-
if renamed && reservedBindingNames.contains(klass.resolvedJSName) {
1466-
throw BridgeJSLinkError(
1467-
message: "JavaScript class name '\(klass.resolvedJSName)' conflicts with a generated binding"
1468-
)
1469-
}
1470-
try claimNamespaces(klass.tsPathComponents, owner: owner)
1471-
try claim(klass.tsPathComponents, owner: owner, renamed: renamed, canContainNamespace: true)
1472-
try claim([klass.resolvedJSName], owner: owner, renamed: renamed, canContainNamespace: true)
1473-
try claimMembers(
1474-
klass.tsPathComponents,
1475-
owner: owner,
1476-
renamed: renamed,
1477-
methods: klass.methods.filter(\.effects.isStatic),
1478-
properties: klass.properties.filter(\.isStatic)
1479-
)
1480-
for name in ["__construct", "prototype"]
1481-
+ (shouldUseIdentityCache(for: klass) ? ["__identityCache"] : [])
1482-
{
1483-
try claim(klass.tsPathComponents + [name], owner: owner + "." + name, renamed: false)
1484-
}
1485-
}
1486-
for structDef in skeleton.structs {
1487-
let owner = unified.moduleName + "." + structDef.swiftCallName
1488-
try claimNamespaces(structDef.tsPathComponents, owner: owner)
1489-
try claim(
1490-
structDef.tsPathComponents,
1491-
owner: owner,
1492-
renamed: isRenamed(structDef),
1493-
canContainNamespace: true
1494-
)
1495-
try claimMembers(
1496-
structDef.tsPathComponents,
1497-
owner: owner,
1498-
renamed: isRenamed(structDef),
1499-
methods: structDef.methods.filter(\.effects.isStatic),
1500-
properties: structDef.properties.filter(\.isStatic)
1501-
)
1502-
if structDef.constructor != nil {
1503-
try claim(structDef.tsPathComponents + ["init"], owner: owner + ".init", renamed: false)
1504-
}
1505-
}
1506-
for enumDef in skeleton.enums {
1507-
let owner = unified.moduleName + "." + enumDef.swiftCallName
1508-
let renamed = isRenamed(enumDef)
1509-
let namespace = enumDef.resolvedJSNamespace ?? []
1510-
try claimNamespaces(enumDef.tsPathComponents, owner: owner)
1511-
try claim(
1512-
enumDef.tsPathComponents,
1513-
owner: owner,
1514-
renamed: renamed,
1515-
isNamespace: enumDef.enumType == .namespace,
1516-
canContainNamespace: true
1517-
)
1518-
if enumDef.enumType != .namespace {
1519-
if renamed && reservedBindingNames.contains(enumDef.valuesName) {
1520-
throw BridgeJSLinkError(
1521-
message:
1522-
"JavaScript enum binding '\(enumDef.valuesName)' conflicts with a generated binding"
1523-
)
1524-
}
1525-
try claim([enumDef.valuesName], owner: owner, renamed: renamed)
1526-
try claim(namespace + [enumDef.valuesName], owner: owner, renamed: renamed)
1527-
if enumDef.emitStyle != .tsEnum {
1528-
try claim(namespace + [enumDef.resolvedJSName + "Tag"], owner: owner, renamed: renamed)
1529-
try claim([enumDef.objectTypeName], owner: owner, renamed: renamed)
1530-
}
1531-
}
1532-
try claimMembers(
1533-
enumDef.tsPathComponents,
1534-
owner: owner,
1535-
renamed: renamed,
1536-
methods: enumDef.staticMethods,
1537-
properties: enumDef.staticProperties
1538-
)
1539-
}
1540-
for proto in skeleton.protocols {
1541-
try claim(
1542-
[proto.resolvedJSName],
1543-
owner: unified.moduleName + "." + proto.name,
1544-
renamed: proto.resolvedJSName != proto.name
1545-
)
1546-
}
1547-
for function in skeleton.functions {
1548-
let path = (function.namespace ?? []) + [function.resolvedJSName]
1549-
let owner = unified.moduleName + "." + function.abiName
1550-
try claimNamespaces(path, owner: owner)
1551-
try claim(path, owner: owner, renamed: false)
1552-
}
1553-
}
1554-
for unified in skeletons {
1555-
for imported in unified.imported?.children ?? [] {
1556-
for type in imported.types {
1557-
try claim([type.name], owner: unified.moduleName + ".import." + type.name, renamed: false)
1558-
}
1559-
}
1560-
}
1561-
let reservedNames =
1562-
["Exports", "Imports", "createInstantiator"]
1563-
+ (exported.contains { !$0.classes.isEmpty } ? ["SwiftHeapObject"] : [])
1564-
for name in reservedNames {
1565-
try claim([name], owner: "BridgeJS.\(name)", renamed: false)
1566-
}
1567-
}
1568-
15691376
private func collectTypeOwnerModules() -> [String: String] {
15701377
var result: [String: String] = [:]
15711378
func record(_ name: String, _ moduleName: String) {

Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ final class JSGlueVariableScope {
4343

4444
private let intrinsicRegistry: JSIntrinsicRegistry
4545

46-
static let reservedVariables: Set<String> = [
46+
private var variables: Set<String> = [
4747
reservedSwift,
4848
reservedInstance,
4949
reservedMemory,
@@ -77,8 +77,6 @@ final class JSGlueVariableScope {
7777
reservedRegisterTypeHandles,
7878
]
7979

80-
private var variables = JSGlueVariableScope.reservedVariables
81-
8280
init(intrinsicRegistry: JSIntrinsicRegistry) {
8381
self.intrinsicRegistry = intrinsicRegistry
8482
}

Plugins/BridgeJS/Tests/BridgeJSToolTests/DiagnosticsTests.swift

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -752,15 +752,6 @@ import Testing
752752
#expect(diagnostics.description.contains("A separate name for JavaScript is not supported here"))
753753
}
754754

755-
@Test(arguments: ["class", "default", "await", "string", "number"])
756-
func reservedExportedTypeNamesAreRejected(name: String) throws {
757-
let source = """
758-
@JS("\(name)") class Box { @JS init() {} }
759-
"""
760-
let diagnostics = try #require(moduleDiagnostics(source: source))
761-
#expect(diagnostics.description.contains("cannot be used as an exported type name"))
762-
}
763-
764755
@Test
765756
func jsNameOnProtocolRequirementDiagnostic() throws {
766757
let source = """

0 commit comments

Comments
 (0)