@@ -332,6 +332,75 @@ public class ExportSwift {
332
332
return . skipChildren
333
333
}
334
334
335
+ override func visit( _ node: VariableDeclSyntax ) -> SyntaxVisitorContinueKind {
336
+ guard node. attributes. hasJSAttribute ( ) else { return . skipChildren }
337
+ guard case . classBody( let className, let classKey) = state else {
338
+ diagnose ( node: node, message: " @JS var must be inside a @JS class " )
339
+ return . skipChildren
340
+ }
341
+
342
+ if let jsAttribute = node. attributes. firstJSAttribute,
343
+ extractNamespace ( from: jsAttribute) != nil
344
+ {
345
+ diagnose (
346
+ node: jsAttribute,
347
+ message: " Namespace is not supported for property declarations " ,
348
+ hint: " Remove the namespace from @JS attribute "
349
+ )
350
+ }
351
+
352
+ // Process each binding (variable declaration)
353
+ for binding in node. bindings {
354
+ guard let pattern = binding. pattern. as ( IdentifierPatternSyntax . self) else {
355
+ diagnose ( node: binding. pattern, message: " Complex patterns not supported for @JS properties " )
356
+ continue
357
+ }
358
+
359
+ let propertyName = pattern. identifier. text
360
+
361
+ guard let typeAnnotation = binding. typeAnnotation else {
362
+ diagnose ( node: binding, message: " @JS property must have explicit type annotation " )
363
+ continue
364
+ }
365
+
366
+ guard let propertyType = self . parent. lookupType ( for: typeAnnotation. type) else {
367
+ diagnoseUnsupportedType ( node: typeAnnotation. type, type: typeAnnotation. type. trimmedDescription)
368
+ continue
369
+ }
370
+
371
+ // Check if property is readonly
372
+ let isLet = node. bindingSpecifier. tokenKind == . keyword( . let)
373
+ let isGetterOnly = node. bindings. contains ( where: {
374
+ switch $0. accessorBlock? . accessors {
375
+ case . accessors( let accessors) :
376
+ // Has accessors - check if it only has a getter (no setter, willSet, or didSet)
377
+ return !accessors. contains ( where: { accessor in
378
+ let tokenKind = accessor. accessorSpecifier. tokenKind
379
+ return tokenKind == . keyword( . set) || tokenKind == . keyword( . willSet)
380
+ || tokenKind == . keyword( . didSet)
381
+ } )
382
+ case . getter:
383
+ // Has only a getter block
384
+ return true
385
+ case nil :
386
+ // No accessor block - this is a stored property, not readonly
387
+ return false
388
+ }
389
+ } )
390
+ let isReadonly = isLet || isGetterOnly
391
+
392
+ let exportedProperty = ExportedProperty (
393
+ name: propertyName,
394
+ type: propertyType,
395
+ isReadonly: isReadonly
396
+ )
397
+
398
+ exportedClassByName [ classKey] ? . properties. append ( exportedProperty)
399
+ }
400
+
401
+ return . skipChildren
402
+ }
403
+
335
404
override func visit( _ node: ClassDeclSyntax ) -> SyntaxVisitorContinueKind {
336
405
let name = node. name. text
337
406
@@ -359,6 +428,7 @@ public class ExportSwift {
359
428
swiftCallName: swiftCallName,
360
429
constructor: nil ,
361
430
methods: [ ] ,
431
+ properties: [ ] ,
362
432
namespace: effectiveNamespace
363
433
)
364
434
let uniqueKey = classKey ( name: name, namespace: effectiveNamespace)
@@ -689,7 +759,8 @@ public class ExportSwift {
689
759
690
760
class ExportedThunkBuilder {
691
761
var body : [ CodeBlockItemSyntax ] = [ ]
692
- var abiParameterForwardings : [ LabeledExprSyntax ] = [ ]
762
+ var liftedParameterExprs : [ ExprSyntax ] = [ ]
763
+ var parameters : [ Parameter ] = [ ]
693
764
var abiParameterSignatures : [ ( name: String , type: WasmCoreType ) ] = [ ]
694
765
var abiReturnType : WasmCoreType ?
695
766
let effects : Effects
@@ -708,38 +779,19 @@ public class ExportSwift {
708
779
}
709
780
710
781
func liftParameter( param: Parameter ) {
782
+ parameters. append ( param)
711
783
switch param. type {
712
784
case . bool:
713
- abiParameterForwardings. append (
714
- LabeledExprSyntax (
715
- label: param. label,
716
- expression: ExprSyntax ( " \( raw: param. name) == 1 " )
717
- )
718
- )
785
+ liftedParameterExprs. append ( ExprSyntax ( " \( raw: param. name) == 1 " ) )
719
786
abiParameterSignatures. append ( ( param. name, . i32) )
720
787
case . int:
721
- abiParameterForwardings. append (
722
- LabeledExprSyntax (
723
- label: param. label,
724
- expression: ExprSyntax ( " \( raw: param. type. swiftType) ( \( raw: param. name) ) " )
725
- )
726
- )
788
+ liftedParameterExprs. append ( ExprSyntax ( " \( raw: param. type. swiftType) ( \( raw: param. name) ) " ) )
727
789
abiParameterSignatures. append ( ( param. name, . i32) )
728
790
case . float:
729
- abiParameterForwardings. append (
730
- LabeledExprSyntax (
731
- label: param. label,
732
- expression: ExprSyntax ( " \( raw: param. name) " )
733
- )
734
- )
791
+ liftedParameterExprs. append ( ExprSyntax ( " \( raw: param. name) " ) )
735
792
abiParameterSignatures. append ( ( param. name, . f32) )
736
793
case . double:
737
- abiParameterForwardings. append (
738
- LabeledExprSyntax (
739
- label: param. label,
740
- expression: ExprSyntax ( " \( raw: param. name) " )
741
- )
742
- )
794
+ liftedParameterExprs. append ( ExprSyntax ( " \( raw: param. name) " ) )
743
795
abiParameterSignatures. append ( ( param. name, . f64) )
744
796
case . string:
745
797
let bytesLabel = " \( param. name) Bytes "
@@ -751,21 +803,11 @@ public class ExportSwift {
751
803
}
752
804
"""
753
805
append ( prepare)
754
- abiParameterForwardings. append (
755
- LabeledExprSyntax (
756
- label: param. label,
757
- expression: ExprSyntax ( " \( raw: param. name) " )
758
- )
759
- )
806
+ liftedParameterExprs. append ( ExprSyntax ( " \( raw: param. name) " ) )
760
807
abiParameterSignatures. append ( ( bytesLabel, . i32) )
761
808
abiParameterSignatures. append ( ( lengthLabel, . i32) )
762
809
case . caseEnum( let enumName) :
763
- abiParameterForwardings. append (
764
- LabeledExprSyntax (
765
- label: param. label,
766
- expression: ExprSyntax ( " \( raw: enumName) (bridgeJSRawValue: \( raw: param. name) )! " )
767
- )
768
- )
810
+ liftedParameterExprs. append ( ExprSyntax ( " \( raw: enumName) (bridgeJSRawValue: \( raw: param. name) )! " ) )
769
811
abiParameterSignatures. append ( ( param. name, . i32) )
770
812
case . rawValueEnum( let enumName, let rawType) :
771
813
if rawType == . string {
@@ -778,12 +820,7 @@ public class ExportSwift {
778
820
}
779
821
"""
780
822
append ( prepare)
781
- abiParameterForwardings. append (
782
- LabeledExprSyntax (
783
- label: param. label,
784
- expression: ExprSyntax ( " \( raw: enumName) (rawValue: \( raw: param. name) )! " )
785
- )
786
- )
823
+ liftedParameterExprs. append ( ExprSyntax ( " \( raw: enumName) (rawValue: \( raw: param. name) )! " ) )
787
824
abiParameterSignatures. append ( ( bytesLabel, . i32) )
788
825
abiParameterSignatures. append ( ( lengthLabel, . i32) )
789
826
} else {
@@ -802,12 +839,7 @@ public class ExportSwift {
802
839
conversionExpr = " \( enumName) (rawValue: \( rawType. rawValue) ( \( param. name) ))! "
803
840
}
804
841
805
- abiParameterForwardings. append (
806
- LabeledExprSyntax (
807
- label: param. label,
808
- expression: ExprSyntax ( stringLiteral: conversionExpr)
809
- )
810
- )
842
+ liftedParameterExprs. append ( ExprSyntax ( stringLiteral: conversionExpr) )
811
843
if let wasmType = rawType. wasmCoreType {
812
844
abiParameterSignatures. append ( ( param. name, wasmType) )
813
845
}
@@ -817,36 +849,35 @@ public class ExportSwift {
817
849
case . namespaceEnum:
818
850
break
819
851
case . jsObject( nil ) :
820
- abiParameterForwardings. append (
821
- LabeledExprSyntax (
822
- label: param. label,
823
- expression: ExprSyntax ( " JSObject(id: UInt32(bitPattern: \( raw: param. name) )) " )
824
- )
825
- )
852
+ liftedParameterExprs. append ( ExprSyntax ( " JSObject(id: UInt32(bitPattern: \( raw: param. name) )) " ) )
826
853
abiParameterSignatures. append ( ( param. name, . i32) )
827
854
case . jsObject( let name) :
828
- abiParameterForwardings. append (
829
- LabeledExprSyntax (
830
- label: param. label,
831
- expression: ExprSyntax ( " \( raw: name) (takingThis: UInt32(bitPattern: \( raw: param. name) )) " )
832
- )
855
+ liftedParameterExprs. append (
856
+ ExprSyntax ( " \( raw: name) (takingThis: UInt32(bitPattern: \( raw: param. name) )) " )
833
857
)
834
858
abiParameterSignatures. append ( ( param. name, . i32) )
835
859
case . swiftHeapObject:
836
860
let objectExpr : ExprSyntax =
837
861
" Unmanaged< \( raw: param. type. swiftType) >.fromOpaque( \( raw: param. name) ).takeUnretainedValue() "
838
- abiParameterForwardings. append (
839
- LabeledExprSyntax ( label: param. label, expression: objectExpr)
840
- )
862
+ liftedParameterExprs. append ( objectExpr)
841
863
abiParameterSignatures. append ( ( param. name, . pointer) )
842
864
case . void:
843
865
break
844
866
}
845
867
}
846
868
869
+ private func removeFirstLiftedParameter( ) -> ( parameter: Parameter , expr: ExprSyntax ) {
870
+ let parameter = parameters. removeFirst ( )
871
+ let expr = liftedParameterExprs. removeFirst ( )
872
+ return ( parameter, expr)
873
+ }
874
+
847
875
private func renderCallStatement( callee: ExprSyntax , returnType: BridgeType ) -> CodeBlockItemSyntax {
876
+ let labeledParams = zip ( parameters, liftedParameterExprs) . map { param, expr in
877
+ LabeledExprSyntax ( label: param. label, expression: expr)
878
+ }
848
879
var callExpr : ExprSyntax =
849
- " \( raw: callee) ( \( raw: abiParameterForwardings . map { $0. description } . joined ( separator: " , " ) ) ) "
880
+ " \( raw: callee) ( \( raw: labeledParams . map { $0. description } . joined ( separator: " , " ) ) ) "
850
881
if effects. isAsync {
851
882
callExpr = ExprSyntax (
852
883
AwaitExprSyntax ( awaitKeyword: . keyword( . await ) . with ( \. trailingTrivia, . space) , expression: callExpr)
@@ -884,14 +915,30 @@ public class ExportSwift {
884
915
}
885
916
886
917
func callMethod( klassName: String , methodName: String , returnType: BridgeType ) {
887
- let _selfParam = self . abiParameterForwardings . removeFirst ( )
918
+ let ( _ , selfExpr ) = removeFirstLiftedParameter ( )
888
919
let item = renderCallStatement (
889
- callee: " \( raw: _selfParam ) . \( raw: methodName) " ,
920
+ callee: " \( raw: selfExpr ) . \( raw: methodName) " ,
890
921
returnType: returnType
891
922
)
892
923
append ( item)
893
924
}
894
925
926
+ func callPropertyGetter( klassName: String , propertyName: String , returnType: BridgeType ) {
927
+ let ( _, selfExpr) = removeFirstLiftedParameter ( )
928
+ let retMutability = returnType == . string ? " var " : " let "
929
+ if returnType == . void {
930
+ append ( " \( raw: selfExpr) . \( raw: propertyName) " )
931
+ } else {
932
+ append ( " \( raw: retMutability) ret = \( raw: selfExpr) . \( raw: propertyName) " )
933
+ }
934
+ }
935
+
936
+ func callPropertySetter( klassName: String , propertyName: String ) {
937
+ let ( _, selfExpr) = removeFirstLiftedParameter ( )
938
+ let ( _, newValueExpr) = removeFirstLiftedParameter ( )
939
+ append ( " \( raw: selfExpr) . \( raw: propertyName) = \( raw: newValueExpr) " )
940
+ }
941
+
895
942
func lowerReturnValue( returnType: BridgeType ) {
896
943
if effects. isAsync {
897
944
// Async functions always return a Promise, which is a JSObject
@@ -1157,6 +1204,39 @@ public class ExportSwift {
1157
1204
decls. append ( builder. render ( abiName: method. abiName) )
1158
1205
}
1159
1206
1207
+ // Generate property getters and setters
1208
+ for property in klass. properties {
1209
+ // Generate getter
1210
+ let getterBuilder = ExportedThunkBuilder ( effects: Effects ( isAsync: false , isThrows: false ) )
1211
+ getterBuilder. liftParameter (
1212
+ param: Parameter ( label: nil , name: " _self " , type: . swiftHeapObject( klass. name) )
1213
+ )
1214
+ getterBuilder. callPropertyGetter (
1215
+ klassName: klass. name,
1216
+ propertyName: property. name,
1217
+ returnType: property. type
1218
+ )
1219
+ getterBuilder. lowerReturnValue ( returnType: property. type)
1220
+ decls. append ( getterBuilder. render ( abiName: property. getterAbiName ( className: klass. name) ) )
1221
+
1222
+ // Generate setter if property is not readonly
1223
+ if !property. isReadonly {
1224
+ let setterBuilder = ExportedThunkBuilder ( effects: Effects ( isAsync: false , isThrows: false ) )
1225
+ setterBuilder. liftParameter (
1226
+ param: Parameter ( label: nil , name: " _self " , type: . swiftHeapObject( klass. name) )
1227
+ )
1228
+ setterBuilder. liftParameter (
1229
+ param: Parameter ( label: " value " , name: " value " , type: property. type)
1230
+ )
1231
+ setterBuilder. callPropertySetter (
1232
+ klassName: klass. name,
1233
+ propertyName: property. name
1234
+ )
1235
+ setterBuilder. lowerReturnValue ( returnType: . void)
1236
+ decls. append ( setterBuilder. render ( abiName: property. setterAbiName ( className: klass. name) ) )
1237
+ }
1238
+ }
1239
+
1160
1240
do {
1161
1241
decls. append (
1162
1242
"""
0 commit comments