@@ -332,6 +332,75 @@ public class ExportSwift {
332332 return . skipChildren
333333 }
334334
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+
335404 override func visit( _ node: ClassDeclSyntax ) -> SyntaxVisitorContinueKind {
336405 let name = node. name. text
337406
@@ -359,6 +428,7 @@ public class ExportSwift {
359428 swiftCallName: swiftCallName,
360429 constructor: nil ,
361430 methods: [ ] ,
431+ properties: [ ] ,
362432 namespace: effectiveNamespace
363433 )
364434 let uniqueKey = classKey ( name: name, namespace: effectiveNamespace)
@@ -689,7 +759,8 @@ public class ExportSwift {
689759
690760 class ExportedThunkBuilder {
691761 var body : [ CodeBlockItemSyntax ] = [ ]
692- var abiParameterForwardings : [ LabeledExprSyntax ] = [ ]
762+ var liftedParameterExprs : [ ExprSyntax ] = [ ]
763+ var parameters : [ Parameter ] = [ ]
693764 var abiParameterSignatures : [ ( name: String , type: WasmCoreType ) ] = [ ]
694765 var abiReturnType : WasmCoreType ?
695766 let effects : Effects
@@ -708,38 +779,19 @@ public class ExportSwift {
708779 }
709780
710781 func liftParameter( param: Parameter ) {
782+ parameters. append ( param)
711783 switch param. type {
712784 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 " ) )
719786 abiParameterSignatures. append ( ( param. name, . i32) )
720787 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) ) " ) )
727789 abiParameterSignatures. append ( ( param. name, . i32) )
728790 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) " ) )
735792 abiParameterSignatures. append ( ( param. name, . f32) )
736793 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) " ) )
743795 abiParameterSignatures. append ( ( param. name, . f64) )
744796 case . string:
745797 let bytesLabel = " \( param. name) Bytes "
@@ -751,21 +803,11 @@ public class ExportSwift {
751803 }
752804 """
753805 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) " ) )
760807 abiParameterSignatures. append ( ( bytesLabel, . i32) )
761808 abiParameterSignatures. append ( ( lengthLabel, . i32) )
762809 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) )! " ) )
769811 abiParameterSignatures. append ( ( param. name, . i32) )
770812 case . rawValueEnum( let enumName, let rawType) :
771813 if rawType == . string {
@@ -778,12 +820,7 @@ public class ExportSwift {
778820 }
779821 """
780822 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) )! " ) )
787824 abiParameterSignatures. append ( ( bytesLabel, . i32) )
788825 abiParameterSignatures. append ( ( lengthLabel, . i32) )
789826 } else {
@@ -802,12 +839,7 @@ public class ExportSwift {
802839 conversionExpr = " \( enumName) (rawValue: \( rawType. rawValue) ( \( param. name) ))! "
803840 }
804841
805- abiParameterForwardings. append (
806- LabeledExprSyntax (
807- label: param. label,
808- expression: ExprSyntax ( stringLiteral: conversionExpr)
809- )
810- )
842+ liftedParameterExprs. append ( ExprSyntax ( stringLiteral: conversionExpr) )
811843 if let wasmType = rawType. wasmCoreType {
812844 abiParameterSignatures. append ( ( param. name, wasmType) )
813845 }
@@ -817,36 +849,35 @@ public class ExportSwift {
817849 case . namespaceEnum:
818850 break
819851 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) )) " ) )
826853 abiParameterSignatures. append ( ( param. name, . i32) )
827854 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) )) " )
833857 )
834858 abiParameterSignatures. append ( ( param. name, . i32) )
835859 case . swiftHeapObject:
836860 let objectExpr : ExprSyntax =
837861 " 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)
841863 abiParameterSignatures. append ( ( param. name, . pointer) )
842864 case . void:
843865 break
844866 }
845867 }
846868
869+ private func removeFirstLiftedParameter( ) -> ( parameter: Parameter , expr: ExprSyntax ) {
870+ let parameter = parameters. removeFirst ( )
871+ let expr = liftedParameterExprs. removeFirst ( )
872+ return ( parameter, expr)
873+ }
874+
847875 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+ }
848879 var callExpr : ExprSyntax =
849- " \( raw: callee) ( \( raw: abiParameterForwardings . map { $0. description } . joined ( separator: " , " ) ) ) "
880+ " \( raw: callee) ( \( raw: labeledParams . map { $0. description } . joined ( separator: " , " ) ) ) "
850881 if effects. isAsync {
851882 callExpr = ExprSyntax (
852883 AwaitExprSyntax ( awaitKeyword: . keyword( . await ) . with ( \. trailingTrivia, . space) , expression: callExpr)
@@ -884,14 +915,30 @@ public class ExportSwift {
884915 }
885916
886917 func callMethod( klassName: String , methodName: String , returnType: BridgeType ) {
887- let _selfParam = self . abiParameterForwardings . removeFirst ( )
918+ let ( _ , selfExpr ) = removeFirstLiftedParameter ( )
888919 let item = renderCallStatement (
889- callee: " \( raw: _selfParam ) . \( raw: methodName) " ,
920+ callee: " \( raw: selfExpr ) . \( raw: methodName) " ,
890921 returnType: returnType
891922 )
892923 append ( item)
893924 }
894925
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+
895942 func lowerReturnValue( returnType: BridgeType ) {
896943 if effects. isAsync {
897944 // Async functions always return a Promise, which is a JSObject
@@ -1157,6 +1204,39 @@ public class ExportSwift {
11571204 decls. append ( builder. render ( abiName: method. abiName) )
11581205 }
11591206
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+
11601240 do {
11611241 decls. append (
11621242 """
0 commit comments