@@ -272,6 +272,75 @@ public class ExportSwift {
272
272
return . skipChildren
273
273
}
274
274
275
+ override func visit( _ node: VariableDeclSyntax ) -> SyntaxVisitorContinueKind {
276
+ guard node. attributes. hasJSAttribute ( ) else { return . skipChildren }
277
+ guard case . classBody( let className) = state else {
278
+ diagnose ( node: node, message: " @JS var must be inside a @JS class " )
279
+ return . skipChildren
280
+ }
281
+
282
+ if let jsAttribute = node. attributes. firstJSAttribute,
283
+ extractNamespace ( from: jsAttribute) != nil
284
+ {
285
+ diagnose (
286
+ node: jsAttribute,
287
+ message: " Namespace is not supported for property declarations " ,
288
+ hint: " Remove the namespace from @JS attribute "
289
+ )
290
+ }
291
+
292
+ // Process each binding (variable declaration)
293
+ for binding in node. bindings {
294
+ guard let pattern = binding. pattern. as ( IdentifierPatternSyntax . self) else {
295
+ diagnose ( node: binding. pattern, message: " Complex patterns not supported for @JS properties " )
296
+ continue
297
+ }
298
+
299
+ let propertyName = pattern. identifier. text
300
+
301
+ guard let typeAnnotation = binding. typeAnnotation else {
302
+ diagnose ( node: binding, message: " @JS property must have explicit type annotation " )
303
+ continue
304
+ }
305
+
306
+ guard let propertyType = self . parent. lookupType ( for: typeAnnotation. type) else {
307
+ diagnoseUnsupportedType ( node: typeAnnotation. type, type: typeAnnotation. type. trimmedDescription)
308
+ continue
309
+ }
310
+
311
+ // Check if property is readonly
312
+ let isLet = node. bindingSpecifier. tokenKind == . keyword( . let)
313
+ let isGetterOnly = node. bindings. contains ( where: {
314
+ switch $0. accessorBlock? . accessors {
315
+ case . accessors( let accessors) :
316
+ // Has accessors - check if it only has a getter (no setter, willSet, or didSet)
317
+ return !accessors. contains ( where: { accessor in
318
+ let tokenKind = accessor. accessorSpecifier. tokenKind
319
+ return tokenKind == . keyword( . set) || tokenKind == . keyword( . willSet)
320
+ || tokenKind == . keyword( . didSet)
321
+ } )
322
+ case . getter:
323
+ // Has only a getter block
324
+ return true
325
+ case nil :
326
+ // No accessor block - this is a stored property, not readonly
327
+ return false
328
+ }
329
+ } )
330
+ let isReadonly = isLet || isGetterOnly
331
+
332
+ let exportedProperty = ExportedProperty (
333
+ name: propertyName,
334
+ type: propertyType,
335
+ isReadonly: isReadonly
336
+ )
337
+
338
+ exportedClassByName [ className] ? . properties. append ( exportedProperty)
339
+ }
340
+
341
+ return . skipChildren
342
+ }
343
+
275
344
override func visit( _ node: ClassDeclSyntax ) -> SyntaxVisitorContinueKind {
276
345
let name = node. name. text
277
346
@@ -284,6 +353,7 @@ public class ExportSwift {
284
353
name: name,
285
354
constructor: nil ,
286
355
methods: [ ] ,
356
+ properties: [ ] ,
287
357
namespace: namespace
288
358
)
289
359
exportedClassNames. append ( name)
@@ -350,7 +420,8 @@ public class ExportSwift {
350
420
351
421
class ExportedThunkBuilder {
352
422
var body : [ CodeBlockItemSyntax ] = [ ]
353
- var abiParameterForwardings : [ LabeledExprSyntax ] = [ ]
423
+ var liftedParameterExprs : [ ExprSyntax ] = [ ]
424
+ var parameters : [ Parameter ] = [ ]
354
425
var abiParameterSignatures : [ ( name: String , type: WasmCoreType ) ] = [ ]
355
426
var abiReturnType : WasmCoreType ?
356
427
let effects : Effects
@@ -369,38 +440,19 @@ public class ExportSwift {
369
440
}
370
441
371
442
func liftParameter( param: Parameter ) {
443
+ parameters. append ( param)
372
444
switch param. type {
373
445
case . bool:
374
- abiParameterForwardings. append (
375
- LabeledExprSyntax (
376
- label: param. label,
377
- expression: ExprSyntax ( " \( raw: param. name) == 1 " )
378
- )
379
- )
446
+ liftedParameterExprs. append ( ExprSyntax ( " \( raw: param. name) == 1 " ) )
380
447
abiParameterSignatures. append ( ( param. name, . i32) )
381
448
case . int:
382
- abiParameterForwardings. append (
383
- LabeledExprSyntax (
384
- label: param. label,
385
- expression: ExprSyntax ( " \( raw: param. type. swiftType) ( \( raw: param. name) ) " )
386
- )
387
- )
449
+ liftedParameterExprs. append ( ExprSyntax ( " \( raw: param. type. swiftType) ( \( raw: param. name) ) " ) )
388
450
abiParameterSignatures. append ( ( param. name, . i32) )
389
451
case . float:
390
- abiParameterForwardings. append (
391
- LabeledExprSyntax (
392
- label: param. label,
393
- expression: ExprSyntax ( " \( raw: param. name) " )
394
- )
395
- )
452
+ liftedParameterExprs. append ( ExprSyntax ( " \( raw: param. name) " ) )
396
453
abiParameterSignatures. append ( ( param. name, . f32) )
397
454
case . double:
398
- abiParameterForwardings. append (
399
- LabeledExprSyntax (
400
- label: param. label,
401
- expression: ExprSyntax ( " \( raw: param. name) " )
402
- )
403
- )
455
+ liftedParameterExprs. append ( ExprSyntax ( " \( raw: param. name) " ) )
404
456
abiParameterSignatures. append ( ( param. name, . f64) )
405
457
case . string:
406
458
let bytesLabel = " \( param. name) Bytes "
@@ -412,46 +464,40 @@ public class ExportSwift {
412
464
}
413
465
"""
414
466
append ( prepare)
415
- abiParameterForwardings. append (
416
- LabeledExprSyntax (
417
- label: param. label,
418
- expression: ExprSyntax ( " \( raw: param. name) " )
419
- )
420
- )
467
+ liftedParameterExprs. append ( ExprSyntax ( " \( raw: param. name) " ) )
421
468
abiParameterSignatures. append ( ( bytesLabel, . i32) )
422
469
abiParameterSignatures. append ( ( lengthLabel, . i32) )
423
470
case . jsObject( nil ) :
424
- abiParameterForwardings. append (
425
- LabeledExprSyntax (
426
- label: param. label,
427
- expression: ExprSyntax ( " JSObject(id: UInt32(bitPattern: \( raw: param. name) )) " )
428
- )
429
- )
471
+ liftedParameterExprs. append ( ExprSyntax ( " JSObject(id: UInt32(bitPattern: \( raw: param. name) )) " ) )
430
472
abiParameterSignatures. append ( ( param. name, . i32) )
431
473
case . jsObject( let name) :
432
- abiParameterForwardings. append (
433
- LabeledExprSyntax (
434
- label: param. label,
435
- expression: ExprSyntax ( " \( raw: name) (takingThis: UInt32(bitPattern: \( raw: param. name) )) " )
436
- )
474
+ liftedParameterExprs. append (
475
+ ExprSyntax ( " \( raw: name) (takingThis: UInt32(bitPattern: \( raw: param. name) )) " )
437
476
)
438
477
abiParameterSignatures. append ( ( param. name, . i32) )
439
478
case . swiftHeapObject:
440
479
// UnsafeMutableRawPointer is passed as an i32 pointer
441
480
let objectExpr : ExprSyntax =
442
481
" Unmanaged< \( raw: param. type. swiftType) >.fromOpaque( \( raw: param. name) ).takeUnretainedValue() "
443
- abiParameterForwardings. append (
444
- LabeledExprSyntax ( label: param. label, expression: objectExpr)
445
- )
482
+ liftedParameterExprs. append ( objectExpr)
446
483
abiParameterSignatures. append ( ( param. name, . pointer) )
447
484
case . void:
448
485
break
449
486
}
450
487
}
451
488
489
+ private func removeFirstLiftedParameter( ) -> ( parameter: Parameter , expr: ExprSyntax ) {
490
+ let parameter = parameters. removeFirst ( )
491
+ let expr = liftedParameterExprs. removeFirst ( )
492
+ return ( parameter, expr)
493
+ }
494
+
452
495
private func renderCallStatement( callee: ExprSyntax , returnType: BridgeType ) -> CodeBlockItemSyntax {
496
+ let labeledParams = zip ( parameters, liftedParameterExprs) . map { param, expr in
497
+ LabeledExprSyntax ( label: param. label, expression: expr)
498
+ }
453
499
var callExpr : ExprSyntax =
454
- " \( raw: callee) ( \( raw: abiParameterForwardings . map { $0. description } . joined ( separator: " , " ) ) ) "
500
+ " \( raw: callee) ( \( raw: labeledParams . map { $0. description } . joined ( separator: " , " ) ) ) "
455
501
if effects. isAsync {
456
502
callExpr = ExprSyntax (
457
503
AwaitExprSyntax ( awaitKeyword: . keyword( . await ) . with ( \. trailingTrivia, . space) , expression: callExpr)
@@ -484,14 +530,30 @@ public class ExportSwift {
484
530
}
485
531
486
532
func callMethod( klassName: String , methodName: String , returnType: BridgeType ) {
487
- let _selfParam = self . abiParameterForwardings . removeFirst ( )
533
+ let ( _ , selfExpr ) = removeFirstLiftedParameter ( )
488
534
let item = renderCallStatement (
489
- callee: " \( raw: _selfParam ) . \( raw: methodName) " ,
535
+ callee: " \( raw: selfExpr ) . \( raw: methodName) " ,
490
536
returnType: returnType
491
537
)
492
538
append ( item)
493
539
}
494
540
541
+ func callPropertyGetter( klassName: String , propertyName: String , returnType: BridgeType ) {
542
+ let ( _, selfExpr) = removeFirstLiftedParameter ( )
543
+ let retMutability = returnType == . string ? " var " : " let "
544
+ if returnType == . void {
545
+ append ( " \( raw: selfExpr) . \( raw: propertyName) " )
546
+ } else {
547
+ append ( " \( raw: retMutability) ret = \( raw: selfExpr) . \( raw: propertyName) " )
548
+ }
549
+ }
550
+
551
+ func callPropertySetter( klassName: String , propertyName: String ) {
552
+ let ( _, selfExpr) = removeFirstLiftedParameter ( )
553
+ let ( _, newValueExpr) = removeFirstLiftedParameter ( )
554
+ append ( " \( raw: selfExpr) . \( raw: propertyName) = \( raw: newValueExpr) " )
555
+ }
556
+
495
557
func lowerReturnValue( returnType: BridgeType ) {
496
558
if effects. isAsync {
497
559
// Async functions always return a Promise, which is a JSObject
@@ -717,6 +779,39 @@ public class ExportSwift {
717
779
decls. append ( builder. render ( abiName: method. abiName) )
718
780
}
719
781
782
+ // Generate property getters and setters
783
+ for property in klass. properties {
784
+ // Generate getter
785
+ let getterBuilder = ExportedThunkBuilder ( effects: Effects ( isAsync: false , isThrows: false ) )
786
+ getterBuilder. liftParameter (
787
+ param: Parameter ( label: nil , name: " _self " , type: . swiftHeapObject( klass. name) )
788
+ )
789
+ getterBuilder. callPropertyGetter (
790
+ klassName: klass. name,
791
+ propertyName: property. name,
792
+ returnType: property. type
793
+ )
794
+ getterBuilder. lowerReturnValue ( returnType: property. type)
795
+ decls. append ( getterBuilder. render ( abiName: property. getterAbiName ( className: klass. name) ) )
796
+
797
+ // Generate setter if property is not readonly
798
+ if !property. isReadonly {
799
+ let setterBuilder = ExportedThunkBuilder ( effects: Effects ( isAsync: false , isThrows: false ) )
800
+ setterBuilder. liftParameter (
801
+ param: Parameter ( label: nil , name: " _self " , type: . swiftHeapObject( klass. name) )
802
+ )
803
+ setterBuilder. liftParameter (
804
+ param: Parameter ( label: " value " , name: " value " , type: property. type)
805
+ )
806
+ setterBuilder. callPropertySetter (
807
+ klassName: klass. name,
808
+ propertyName: property. name
809
+ )
810
+ setterBuilder. lowerReturnValue ( returnType: . void)
811
+ decls. append ( setterBuilder. render ( abiName: property. setterAbiName ( className: klass. name) ) )
812
+ }
813
+ }
814
+
720
815
do {
721
816
decls. append (
722
817
"""
0 commit comments