diff --git a/pkgs/swift2objc/lib/src/ast/declarations/compounds/members/property_declaration.dart b/pkgs/swift2objc/lib/src/ast/declarations/compounds/members/property_declaration.dart index 297cdb8b5e..f90fa4d815 100644 --- a/pkgs/swift2objc/lib/src/ast/declarations/compounds/members/property_declaration.dart +++ b/pkgs/swift2objc/lib/src/ast/declarations/compounds/members/property_declaration.dart @@ -38,21 +38,30 @@ class PropertyDeclaration extends AstNode PropertyStatements? getter; PropertyStatements? setter; + bool unowned; + + bool weak; + + bool lazy; + bool isStatic; - PropertyDeclaration({ - required this.id, - required this.name, - required this.type, - this.hasSetter = false, - this.isConstant = false, - this.hasObjCAnnotation = false, - this.getter, - this.setter, - this.isStatic = false, - this.throws = false, - this.async = false, - }) : assert(!(isConstant && hasSetter)), + PropertyDeclaration( + {required this.id, + required this.name, + required this.type, + this.hasSetter = false, + this.isConstant = false, + this.hasObjCAnnotation = false, + this.getter, + this.setter, + this.isStatic = false, + this.throws = false, + this.async = false, + this.unowned = false, + this.weak = false, + this.lazy = false}) + : assert(!(isConstant && hasSetter)), assert(!(hasSetter && throws)); @override diff --git a/pkgs/swift2objc/lib/src/generator/generators/class_generator.dart b/pkgs/swift2objc/lib/src/generator/generators/class_generator.dart index b5161bfb2c..e89b36b0b9 100644 --- a/pkgs/swift2objc/lib/src/generator/generators/class_generator.dart +++ b/pkgs/swift2objc/lib/src/generator/generators/class_generator.dart @@ -144,27 +144,47 @@ List _generateClassProperty(PropertyDeclaration property) { if (property.isStatic) { header.write('static '); } + final prefixes = [ + if (property.unowned) 'unowned', + if (property.weak) 'weak', + ]; - header.write('public var ${property.name}: ${property.type.swiftType} {'); + var prefix = prefixes.isEmpty ? '' : '${prefixes.join(' ')} '; + var propSwiftType = property.type.swiftType; - final getterLines = [ - 'get ${generateAnnotations(property)}{', - ...(property.getter?.statements.indent() ?? []), - '}' - ]; + if (property.lazy) { + header + .write('public ${prefix}lazy var ${property.name}: $propSwiftType = {'); + final getterLines = [ + ...(property.getter?.statements.indent() ?? []), + ]; + return [ + header.toString(), + ...getterLines.indent(), + '}();\n', + ]; + } else { + header.write('public ${prefix}var ${property.name}: $propSwiftType {'); - final setterLines = [ - 'set {', - ...(property.setter?.statements.indent() ?? []), - '}' - ]; + final getterLines = [ + 'get ${generateAnnotations(property)}{', + ...(property.getter?.statements.indent() ?? []), + '}' + ]; - return [ - header.toString(), - ...getterLines.indent(), - if (property.hasSetter) ...setterLines.indent(), - '}\n', - ]; + final setterLines = [ + 'set {', + ...(property.setter?.statements.indent() ?? []), + '}' + ]; + + return [ + header.toString(), + ...getterLines.indent(), + if (property.hasSetter) ...setterLines.indent(), + '}\n', + ]; + } } List _generateNestedDeclarations(ClassDeclaration declaration) => [ diff --git a/pkgs/swift2objc/lib/src/parser/parsers/declaration_parsers/parse_variable_declaration.dart b/pkgs/swift2objc/lib/src/parser/parsers/declaration_parsers/parse_variable_declaration.dart index 23a3fee39b..8d09aad103 100644 --- a/pkgs/swift2objc/lib/src/parser/parsers/declaration_parsers/parse_variable_declaration.dart +++ b/pkgs/swift2objc/lib/src/parser/parsers/declaration_parsers/parse_variable_declaration.dart @@ -15,17 +15,22 @@ PropertyDeclaration parsePropertyDeclaration( ParsedSymbolgraph symbolgraph, { bool isStatic = false, }) { - final isConstant = _parseVariableIsConstant(propertySymbolJson); + final info = parsePropertyInfo(propertySymbolJson['declarationFragments']); + return PropertyDeclaration( id: parseSymbolId(propertySymbolJson), name: parseSymbolName(propertySymbolJson), type: _parseVariableType(propertySymbolJson, symbolgraph), hasObjCAnnotation: parseSymbolHasObjcAnnotation(propertySymbolJson), - isConstant: isConstant, - hasSetter: isConstant ? false : _parsePropertyHasSetter(propertySymbolJson), + isConstant: info.constant, + hasSetter: + info.constant ? false : _parsePropertyHasSetter(propertySymbolJson), isStatic: isStatic, - throws: _parseVariableThrows(propertySymbolJson), - async: _parseVariableAsync(propertySymbolJson), + throws: info.throws, + async: info.async, + unowned: info.unowned, + weak: info.weak, + lazy: info.lazy, ); } @@ -36,14 +41,15 @@ GlobalVariableDeclaration parseGlobalVariableDeclaration( }) { final isConstant = _parseVariableIsConstant(variableSymbolJson); final hasSetter = _parsePropertyHasSetter(variableSymbolJson); + final variableModifiers = parsePropertyInfo(variableSymbolJson); + return GlobalVariableDeclaration( - id: parseSymbolId(variableSymbolJson), - name: parseSymbolName(variableSymbolJson), - type: _parseVariableType(variableSymbolJson, symbolgraph), - isConstant: isConstant || !hasSetter, - throws: _parseVariableThrows(variableSymbolJson), - async: _parseVariableAsync(variableSymbolJson), - ); + id: parseSymbolId(variableSymbolJson), + name: parseSymbolName(variableSymbolJson), + type: _parseVariableType(variableSymbolJson, symbolgraph), + isConstant: isConstant || !hasSetter, + throws: variableModifiers.throws, + async: variableModifiers.async); } ReferredType _parseVariableType( @@ -53,9 +59,7 @@ ReferredType _parseVariableType( parseTypeAfterSeparator( TokenList(propertySymbolJson['names']['subHeading']), symbolgraph); -bool _parseVariableIsConstant(Json variableSymbolJson) { - final fragmentsJson = variableSymbolJson['declarationFragments']; - +bool _parseVariableIsConstant(Json fragmentsJson) { final declarationKeyword = fragmentsJson.firstWhere( (json) => matchFragment(json, 'keyword', 'var') || @@ -69,16 +73,30 @@ bool _parseVariableIsConstant(Json variableSymbolJson) { return matchFragment(declarationKeyword, 'keyword', 'let'); } -bool _parseVariableThrows(Json json) { - final throws = json['declarationFragments'] - .any((frag) => matchFragment(frag, 'keyword', 'throws')); - return throws; +bool _findKeywordInFragments(Json json, String keyword) { + final keywordIsPresent = + json.any((frag) => matchFragment(frag, 'keyword', keyword)); + return keywordIsPresent; } -bool _parseVariableAsync(Json json) { - final async = json['declarationFragments'] - .any((frag) => matchFragment(frag, 'keyword', 'async')); - return async; +typedef ParsedPropertyInfo = ({ + bool async, + bool throws, + bool unowned, + bool weak, + bool lazy, + bool constant, +}); + +ParsedPropertyInfo parsePropertyInfo(Json json) { + return ( + constant: _parseVariableIsConstant(json), + async: _findKeywordInFragments(json, 'async'), + throws: _findKeywordInFragments(json, 'throws'), + unowned: _findKeywordInFragments(json, 'unowned'), + weak: _findKeywordInFragments(json, 'weak'), + lazy: _findKeywordInFragments(json, 'lazy') + ); } bool _parsePropertyHasSetter(Json propertySymbolJson) { diff --git a/pkgs/swift2objc/lib/src/transformer/transformers/transform_variable.dart b/pkgs/swift2objc/lib/src/transformer/transformers/transform_variable.dart index 6580e64774..2b3e4ee84d 100644 --- a/pkgs/swift2objc/lib/src/transformer/transformers/transform_variable.dart +++ b/pkgs/swift2objc/lib/src/transformer/transformers/transform_variable.dart @@ -109,6 +109,13 @@ Declaration _transformVariable( isConstant: originalVariable.isConstant, throws: originalVariable.throws, async: originalVariable.async, + unowned: originalVariable is PropertyDeclaration + ? originalVariable.unowned + : false, + lazy: + originalVariable is PropertyDeclaration ? originalVariable.lazy : false, + weak: + originalVariable is PropertyDeclaration ? originalVariable.weak : false, ); final getterStatements = _generateGetterStatements( diff --git a/pkgs/swift2objc/test/integration/classes_and_properties_input.swift b/pkgs/swift2objc/test/integration/classes_and_properties_input.swift index 6e24e10b4c..2fdea90232 100644 --- a/pkgs/swift2objc/test/integration/classes_and_properties_input.swift +++ b/pkgs/swift2objc/test/integration/classes_and_properties_input.swift @@ -29,17 +29,23 @@ public class MyClass { public let representableConstantProperty: Int + public weak var weakProperty: MyOtherClass? + public unowned var unownedProperty: MyOtherClass + public lazy var lazyProperty: Int = { 1 }(); + init( customVariableProperty: MyOtherClass, customConstantProperty: MyOtherClass, representableVariableProperty: Int, - representableConstantProperty: Int + representableConstantProperty: Int, + unownedProperty: MyOtherClass ) { self.customVariableProperty = customVariableProperty self.customConstantProperty = customConstantProperty self.representableVariableProperty = representableVariableProperty self.representableConstantProperty = representableConstantProperty + self.unownedProperty = unownedProperty } } diff --git a/pkgs/swift2objc/test/integration/classes_and_properties_output.swift b/pkgs/swift2objc/test/integration/classes_and_properties_output.swift index 550713da19..4df471a204 100644 --- a/pkgs/swift2objc/test/integration/classes_and_properties_output.swift +++ b/pkgs/swift2objc/test/integration/classes_and_properties_output.swift @@ -14,6 +14,28 @@ import Foundation @objc public class MyClassWrapper: NSObject { var wrappedInstance: MyClass + @objc public lazy var lazyProperty: Int = { + wrappedInstance.lazyProperty + }(); + + @objc public weak var weakProperty: MyOtherClassWrapper? { + get { + wrappedInstance.weakProperty == nil ? nil : MyOtherClassWrapper(wrappedInstance.weakProperty!) + } + set { + wrappedInstance.weakProperty = newValue?.wrappedInstance + } + } + + @objc public unowned var unownedProperty: MyOtherClassWrapper { + get { + MyOtherClassWrapper(wrappedInstance.unownedProperty) + } + set { + wrappedInstance.unownedProperty = newValue.wrappedInstance + } + } + @objc public var customGetterProperty: MyOtherClassWrapper { get { MyOtherClassWrapper(wrappedInstance.customGetterProperty) diff --git a/pkgs/swift2objc/test/unit/parse_function_info_test.dart b/pkgs/swift2objc/test/unit/parse_function_info_test.dart index c14b30e207..a06a1880fd 100644 --- a/pkgs/swift2objc/test/unit/parse_function_info_test.dart +++ b/pkgs/swift2objc/test/unit/parse_function_info_test.dart @@ -18,7 +18,7 @@ void main() { decl.id: ParsedSymbol(json: Json(null), declaration: decl) }; final emptySymbolgraph = ParsedSymbolgraph(parsedSymbols, {}); - group('Valid json', () { + group('Function Valid json', () { void expectEqualParams( List actualParams, List expectedParams, diff --git a/pkgs/swift2objc/test/unit/parse_variable_info_test.dart b/pkgs/swift2objc/test/unit/parse_variable_info_test.dart new file mode 100644 index 0000000000..9b88e748e4 --- /dev/null +++ b/pkgs/swift2objc/test/unit/parse_variable_info_test.dart @@ -0,0 +1,189 @@ +import 'dart:convert'; + +import 'package:swift2objc/src/parser/_core/json.dart'; +import 'package:swift2objc/src/parser/parsers/declaration_parsers/parse_variable_declaration.dart'; +import 'package:test/test.dart'; + +void main() { + group('Variable Valid json', () { + test('Weak Variable', () { + final json = Json(jsonDecode('''[ + { + "kind": "keyword", + "spelling": "weak" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "keyword", + "spelling": "var" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "dorm" + }, + { + "kind": "text", + "spelling": ": " + }, + { + "kind": "typeIdentifier", + "spelling": "Dorm", + "preciseIdentifier": "s:24funcs_symbolgraph_module4DormC" + }, + { + "kind": "text", + "spelling": "?" + } + ]''')); + + final info = parsePropertyInfo(json); + + expect(info.weak, isTrue); + expect(info.constant, isFalse); + }); + + test('Unowned Variable', () { + final json = Json(jsonDecode('''[ + { + "kind": "keyword", + "spelling": "unowned" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "keyword", + "spelling": "var" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "school" + }, + { + "kind": "text", + "spelling": ": " + }, + { + "kind": "typeIdentifier", + "spelling": "School", + "preciseIdentifier": "s:24funcs_symbolgraph_module6SchoolC" + } + ]''')); + + final info = parsePropertyInfo(json); + + expect(info.unowned, isTrue); + expect(info.constant, isFalse); + }); + + test('Unowned Constant variable', () { + final json = Json(jsonDecode('''[ + { + "kind": "keyword", + "spelling": "unowned" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "keyword", + "spelling": "let" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "almaMatter" + }, + { + "kind": "text", + "spelling": ": " + }, + { + "kind": "typeIdentifier", + "spelling": "School", + "preciseIdentifier": "s:24funcs_symbolgraph_module6SchoolC" + } + ]''')); + + final info = parsePropertyInfo(json); + + expect(info.unowned, isTrue); + expect(info.constant, isTrue); + }); + + test('Lazy Variable', () { + final json = Json(jsonDecode('''[ + { + "kind": "keyword", + "spelling": "lazy" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "keyword", + "spelling": "var" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "description" + }, + { + "kind": "text", + "spelling": ": " + }, + { + "kind": "typeIdentifier", + "spelling": "String", + "preciseIdentifier": "s:SS" + }, + { + "kind": "text", + "spelling": " { " + }, + { + "kind": "keyword", + "spelling": "get" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "keyword", + "spelling": "set" + }, + { + "kind": "text", + "spelling": " }" + } + ]''')); + + final info = parsePropertyInfo(json); + + expect(info.lazy, isTrue); + expect(info.constant, isFalse); + }); + }); +}