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..b5bf37cb6a 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 @@ -33,6 +33,8 @@ class PropertyDeclaration extends AstNode @override bool async; + bool mutating; + bool hasSetter; PropertyStatements? getter; @@ -40,19 +42,20 @@ class PropertyDeclaration extends AstNode 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.mutating = false}) + : assert(!(isConstant && hasSetter)), assert(!(hasSetter && throws)); @override 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..3de1e5babc 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,17 @@ 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 : info.setter, isStatic: isStatic, - throws: _parseVariableThrows(propertySymbolJson), - async: _parseVariableAsync(propertySymbolJson), + throws: info.throws, + async: info.async, ); } @@ -34,15 +34,14 @@ GlobalVariableDeclaration parseGlobalVariableDeclaration( ParsedSymbolgraph symbolgraph, { bool isStatic = false, }) { - final isConstant = _parseVariableIsConstant(variableSymbolJson); - final hasSetter = _parsePropertyHasSetter(variableSymbolJson); + final info = parsePropertyInfo(variableSymbolJson['declarationFragments']); return GlobalVariableDeclaration( id: parseSymbolId(variableSymbolJson), name: parseSymbolName(variableSymbolJson), type: _parseVariableType(variableSymbolJson, symbolgraph), - isConstant: isConstant || !hasSetter, - throws: _parseVariableThrows(variableSymbolJson), - async: _parseVariableAsync(variableSymbolJson), + isConstant: info.constant || !info.setter, + throws: info.throws, + async: info.async, ); } @@ -53,9 +52,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') || @@ -65,24 +62,39 @@ bool _parseVariableIsConstant(Json variableSymbolJson) { 'Expected to find "var" or "let" as a keyword, found none', ), ); - 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) { + return json.any((frag) => matchFragment(frag, 'keyword', keyword)); } -bool _parseVariableAsync(Json json) { - final async = json['declarationFragments'] - .any((frag) => matchFragment(frag, 'keyword', 'async')); - return async; +typedef ParsedPropertyInfo = ({ + bool async, + bool throws, + bool constant, + bool getter, + bool setter, + bool mutating, +}); + +ParsedPropertyInfo parsePropertyInfo(Json json) { + final (getter, setter) = _parsePropertyGetAndSet(json); + return ( + constant: _parseVariableIsConstant(json), + async: _findKeywordInFragments(json, 'async'), + throws: _findKeywordInFragments(json, 'throws'), + getter: getter, + setter: setter, + mutating: _findKeywordInFragments(json, 'mutating') + ); } -bool _parsePropertyHasSetter(Json propertySymbolJson) { - final fragmentsJson = propertySymbolJson['declarationFragments']; +(bool, bool) _parsePropertyGetAndSet(Json fragmentsJson, {String? path}) { + if (fragmentsJson.any((frag) => matchFragment(frag, 'text', ' { get }'))) { + // has explicit getter and no explicit setter + return (true, false); + } final hasExplicitSetter = fragmentsJson.any((frag) => matchFragment(frag, 'keyword', 'set')); @@ -92,21 +104,21 @@ bool _parsePropertyHasSetter(Json propertySymbolJson) { if (hasExplicitGetter) { if (hasExplicitSetter) { // has explicit getter and has explicit setter - return true; + return (true, true); } else { // has explicit getter and no explicit setter - return false; + return (true, false); } } else { if (hasExplicitSetter) { // has no explicit getter and but has explicit setter throw Exception( - 'Invalid property at ${propertySymbolJson.path}. ' + 'Invalid property${path != null ? ' at $path' : ''}. ' 'Properties can not have a setter without a getter', ); } else { - // has no explicit getter and no explicit setter - return true; + // has implicit getter and implicit setter + return (true, true); } } } diff --git a/pkgs/swift2objc/lib/src/transformer/transformers/transform_variable.dart b/pkgs/swift2objc/lib/src/transformer/transformers/transform_variable.dart index 6580e64774..588b6ddb19 100644 --- a/pkgs/swift2objc/lib/src/transformer/transformers/transform_variable.dart +++ b/pkgs/swift2objc/lib/src/transformer/transformers/transform_variable.dart @@ -33,6 +33,7 @@ Declaration? transformProperty( originalProperty, globalNamer, transformationMap, + property: true, wrapperPropertyName: originalProperty.name, variableReferenceExpression: '$propertySource.${originalProperty.name}', ); @@ -60,6 +61,7 @@ Declaration _transformVariable( VariableDeclaration originalVariable, UniqueNamer globalNamer, TransformationMap transformationMap, { + bool property = false, required String wrapperPropertyName, required String variableReferenceExpression, }) { diff --git a/pkgs/swift2objc/test/integration/structs_and_properties_input.swift b/pkgs/swift2objc/test/integration/structs_and_properties_input.swift index d1793f9be0..e16f5569a0 100644 --- a/pkgs/swift2objc/test/integration/structs_and_properties_input.swift +++ b/pkgs/swift2objc/test/integration/structs_and_properties_input.swift @@ -29,6 +29,10 @@ public struct MyStruct { public let representableConstantProperty: Int + public var mutatingProperty: Int { + mutating get { 1 } + } + init( customVariableProperty: MyOtherStruct, diff --git a/pkgs/swift2objc/test/integration/structs_and_properties_output.swift b/pkgs/swift2objc/test/integration/structs_and_properties_output.swift index 3203e87529..b29e6bd3b7 100644 --- a/pkgs/swift2objc/test/integration/structs_and_properties_output.swift +++ b/pkgs/swift2objc/test/integration/structs_and_properties_output.swift @@ -14,6 +14,12 @@ import Foundation @objc public class MyStructWrapper: NSObject { var wrappedInstance: MyStruct + @objc public var mutatingProperty: Int { + get { + wrappedInstance.mutatingProperty + } + } + @objc public var customGetterProperty: MyOtherStructWrapper { get { MyOtherStructWrapper(wrappedInstance.customGetterProperty) 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..8e47f15f22 --- /dev/null +++ b/pkgs/swift2objc/test/unit/parse_variable_info_test.dart @@ -0,0 +1,312 @@ +import 'dart:convert'; + +// import 'package:swift2objc/src/ast/declarations/built_in/built_in_declaration.dart'; +import 'package:swift2objc/src/parser/_core/json.dart'; +// import 'package:swift2objc/src/parser/_core/parsed_symbolgraph.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('Variable with getter', () { + final json = Json(jsonDecode('''[ + { + "kind": "keyword", + "spelling": "var" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "id" + }, + { + "kind": "text", + "spelling": ": " + }, + { + "kind": "typeIdentifier", + "spelling": "Int", + "preciseIdentifier": "s:Si" + }, + { + "kind": "text", + "spelling": " { get }" + } + ]''')); + + final info = parsePropertyInfo(json); + + expect(info.getter, isTrue); + }); + + test('Variable Computed', () { + final json = Json(jsonDecode('''[ + { + "kind": "keyword", + "spelling": "var" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "computedProperty" + }, + { + "kind": "text", + "spelling": ": " + }, + { + "kind": "typeIdentifier", + "spelling": "Int", + "preciseIdentifier": "s:Si" + }, + { + "kind": "text", + "spelling": " { " + }, + { + "kind": "keyword", + "spelling": "get" + }, + { + "kind": "text", + "spelling": " }" + } + ]''')); + + final info = parsePropertyInfo(json); + + expect(info.getter, isTrue); + }); + + test('Variable with getter and setter', () { + final json = Json(jsonDecode('''[ + { + "kind": "keyword", + "spelling": "var" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "computedWithSet" + }, + { + "kind": "text", + "spelling": ": " + }, + { + "kind": "typeIdentifier", + "spelling": "Int", + "preciseIdentifier": "s:Si" + }, + { + "kind": "text", + "spelling": " { " + }, + { + "kind": "keyword", + "spelling": "get" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "keyword", + "spelling": "set" + }, + { + "kind": "text", + "spelling": " }" + } + ]''')); + + final info = parsePropertyInfo(json); + + expect(info.getter, isTrue); + expect(info.setter, isTrue); + }); + + test('Constant variable', () {}); + + test('Async Get Variable', () { + final json = Json(jsonDecode('''[ + { + "kind": "keyword", + "spelling": "var" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "computedAsyncProperty" + }, + { + "kind": "text", + "spelling": ": " + }, + { + "kind": "typeIdentifier", + "spelling": "String", + "preciseIdentifier": "s:SS" + }, + { + "kind": "text", + "spelling": " { " + }, + { + "kind": "keyword", + "spelling": "get" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "keyword", + "spelling": "async" + }, + { + "kind": "text", + "spelling": " }" + } + ]''')); + + final info = parsePropertyInfo(json); + + expect(info.getter, isTrue); + expect(info.async, isTrue); + }); + + test('Mutating Variable', () { + final json = Json(jsonDecode('''[ + { + "kind": "keyword", + "spelling": "var" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "computedWithSet" + }, + { + "kind": "text", + "spelling": ": " + }, + { + "kind": "typeIdentifier", + "spelling": "Int", + "preciseIdentifier": "s:Si" + }, + { + "kind": "text", + "spelling": " { " + }, + { + "kind": "keyword", + "spelling": "mutating" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "keyword", + "spelling": "get" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "keyword", + "spelling": "set" + }, + { + "kind": "text", + "spelling": " }" + } + ]''')); + + final info = parsePropertyInfo(json); + + expect(info.getter, isTrue); + expect(info.mutating, isTrue); + expect(info.setter, isTrue); + }); + + test('Async Throws Get Variable', () { + final json = Json(jsonDecode(''' +[ + { + "kind": "keyword", + "spelling": "var" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "computedAsyncThrowProperty" + }, + { + "kind": "text", + "spelling": ": " + }, + { + "kind": "typeIdentifier", + "spelling": "String", + "preciseIdentifier": "s:SS" + }, + { + "kind": "text", + "spelling": " { " + }, + { + "kind": "keyword", + "spelling": "get" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "keyword", + "spelling": "async" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "keyword", + "spelling": "throws" + }, + { + "kind": "text", + "spelling": " }" + } + ]''')); + + final info = parsePropertyInfo(json); + + expect(info.getter, isTrue); + expect(info.async, isTrue); + expect(info.throws, isTrue); + }); + }); +}