@@ -272,6 +272,75 @@ public class ExportSwift {
272272 return . skipChildren
273273 }
274274
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+
275344 override func visit( _ node: ClassDeclSyntax ) -> SyntaxVisitorContinueKind {
276345 let name = node. name. text
277346
@@ -284,6 +353,7 @@ public class ExportSwift {
284353 name: name,
285354 constructor: nil ,
286355 methods: [ ] ,
356+ properties: [ ] ,
287357 namespace: namespace
288358 )
289359 exportedClassNames. append ( name)
@@ -350,7 +420,8 @@ public class ExportSwift {
350420
351421 class ExportedThunkBuilder {
352422 var body : [ CodeBlockItemSyntax ] = [ ]
353- var abiParameterForwardings : [ LabeledExprSyntax ] = [ ]
423+ var liftedParameterExprs : [ ExprSyntax ] = [ ]
424+ var parameters : [ Parameter ] = [ ]
354425 var abiParameterSignatures : [ ( name: String , type: WasmCoreType ) ] = [ ]
355426 var abiReturnType : WasmCoreType ?
356427 let effects : Effects
@@ -369,38 +440,19 @@ public class ExportSwift {
369440 }
370441
371442 func liftParameter( param: Parameter ) {
443+ parameters. append ( param)
372444 switch param. type {
373445 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 " ) )
380447 abiParameterSignatures. append ( ( param. name, . i32) )
381448 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) ) " ) )
388450 abiParameterSignatures. append ( ( param. name, . i32) )
389451 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) " ) )
396453 abiParameterSignatures. append ( ( param. name, . f32) )
397454 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) " ) )
404456 abiParameterSignatures. append ( ( param. name, . f64) )
405457 case . string:
406458 let bytesLabel = " \( param. name) Bytes "
@@ -412,46 +464,40 @@ public class ExportSwift {
412464 }
413465 """
414466 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) " ) )
421468 abiParameterSignatures. append ( ( bytesLabel, . i32) )
422469 abiParameterSignatures. append ( ( lengthLabel, . i32) )
423470 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) )) " ) )
430472 abiParameterSignatures. append ( ( param. name, . i32) )
431473 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) )) " )
437476 )
438477 abiParameterSignatures. append ( ( param. name, . i32) )
439478 case . swiftHeapObject:
440479 // UnsafeMutableRawPointer is passed as an i32 pointer
441480 let objectExpr : ExprSyntax =
442481 " 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)
446483 abiParameterSignatures. append ( ( param. name, . pointer) )
447484 case . void:
448485 break
449486 }
450487 }
451488
489+ private func removeFirstLiftedParameter( ) -> ( parameter: Parameter , expr: ExprSyntax ) {
490+ let parameter = parameters. removeFirst ( )
491+ let expr = liftedParameterExprs. removeFirst ( )
492+ return ( parameter, expr)
493+ }
494+
452495 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+ }
453499 var callExpr : ExprSyntax =
454- " \( raw: callee) ( \( raw: abiParameterForwardings . map { $0. description } . joined ( separator: " , " ) ) ) "
500+ " \( raw: callee) ( \( raw: labeledParams . map { $0. description } . joined ( separator: " , " ) ) ) "
455501 if effects. isAsync {
456502 callExpr = ExprSyntax (
457503 AwaitExprSyntax ( awaitKeyword: . keyword( . await ) . with ( \. trailingTrivia, . space) , expression: callExpr)
@@ -484,14 +530,30 @@ public class ExportSwift {
484530 }
485531
486532 func callMethod( klassName: String , methodName: String , returnType: BridgeType ) {
487- let _selfParam = self . abiParameterForwardings . removeFirst ( )
533+ let ( _ , selfExpr ) = removeFirstLiftedParameter ( )
488534 let item = renderCallStatement (
489- callee: " \( raw: _selfParam ) . \( raw: methodName) " ,
535+ callee: " \( raw: selfExpr ) . \( raw: methodName) " ,
490536 returnType: returnType
491537 )
492538 append ( item)
493539 }
494540
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+
495557 func lowerReturnValue( returnType: BridgeType ) {
496558 if effects. isAsync {
497559 // Async functions always return a Promise, which is a JSObject
@@ -717,6 +779,39 @@ public class ExportSwift {
717779 decls. append ( builder. render ( abiName: method. abiName) )
718780 }
719781
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+
720815 do {
721816 decls. append (
722817 """
0 commit comments