Skip to content

Commit 3ee0f53

Browse files
committed
feat: generate implicit constructors for Swift structs
1 parent 53980c6 commit 3ee0f53

10 files changed

Lines changed: 442 additions & 15 deletions

File tree

pkgs/swift2objc/lib/src/ast/declarations/compounds/members/property_declaration.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ class PropertyDeclaration extends AstNode
2323
@override
2424
InputConfig? source;
2525

26+
int? lineNumber;
27+
2628
@override
2729
List<AvailabilityInfo> availability;
2830

@@ -45,6 +47,8 @@ class PropertyDeclaration extends AstNode
4547

4648
bool hasSetter;
4749

50+
bool hasExplicitGetter;
51+
4852
PropertyStatements? getter;
4953
PropertyStatements? setter;
5054

@@ -62,11 +66,13 @@ class PropertyDeclaration extends AstNode
6266
required this.source,
6367
required this.availability,
6468
required this.type,
69+
this.lineNumber,
6570
this.hasSetter = false,
6671
this.isConstant = false,
6772
this.hasObjCAnnotation = false,
6873
this.getter,
6974
this.setter,
75+
this.hasExplicitGetter = false,
7076
this.isStatic = false,
7177
this.throws = false,
7278
this.async = false,

pkgs/swift2objc/lib/src/parser/parsers/declaration_parsers/parse_variable_declaration.dart

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,21 +18,37 @@ PropertyDeclaration parsePropertyDeclaration(
1818
bool isStatic = false,
1919
}) {
2020
final info = parsePropertyInfo(symbol.json['declarationFragments']);
21+
22+
// Extract line number from location
23+
int? lineNumber;
24+
final locationJson = symbol.json['location'];
25+
if (locationJson.exists) {
26+
final positionJson = locationJson['position'];
27+
if (positionJson.exists) {
28+
final lineJson = positionJson['line'];
29+
if (lineJson.exists) {
30+
lineNumber = lineJson.get<int>();
31+
}
32+
}
33+
}
34+
2135
return PropertyDeclaration(
2236
id: parseSymbolId(symbol.json),
2337
name: parseSymbolName(symbol.json),
2438
source: symbol.source,
2539
availability: parseAvailability(symbol.json),
2640
type: _parseVariableType(context, symbol.json, symbolgraph),
2741
hasObjCAnnotation: parseSymbolHasObjcAnnotation(symbol.json),
42+
lineNumber: lineNumber,
2843
isConstant: info.constant,
2944
isStatic: isStatic,
3045
throws: info.throws,
3146
async: info.async,
3247
unowned: info.unowned,
3348
weak: info.weak,
3449
lazy: info.lazy,
35-
hasSetter: info.constant ? false : info.setter,
50+
hasSetter: info.constant ? false : (info.getter ? info.setter : true),
51+
hasExplicitGetter: info.getter,
3652
);
3753
}
3854

@@ -139,7 +155,7 @@ ParsedPropertyInfo parsePropertyInfo(Json json) {
139155
);
140156
} else {
141157
// has implicit getter and implicit setter
142-
return (true, true);
158+
return (false, false);
143159
}
144160
}
145161
}

pkgs/swift2objc/lib/src/transformer/transformers/transform_compound.dart

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@
55
import '../../ast/_core/interfaces/compound_declaration.dart';
66
import '../../ast/_core/interfaces/declaration.dart';
77
import '../../ast/_core/interfaces/nestable_declaration.dart';
8+
import '../../ast/_core/shared/parameter.dart';
89
import '../../ast/declarations/built_in/built_in_declaration.dart';
910
import '../../ast/declarations/compounds/class_declaration.dart';
1011
import '../../ast/declarations/compounds/members/initializer_declaration.dart';
1112
import '../../ast/declarations/compounds/members/method_declaration.dart';
1213
import '../../ast/declarations/compounds/members/property_declaration.dart';
14+
import '../../ast/declarations/compounds/struct_declaration.dart';
1315
import '../../parser/_core/utils.dart';
1416
import '../_core/unique_namer.dart';
1517
import '../_core/utils.dart';
@@ -79,7 +81,7 @@ ClassDeclaration transformCompound(
7981
.nonNulls
8082
.toList();
8183

82-
final transformedInitializers = originalCompound.initializers
84+
final transformedInitializers = _compoundInitializers(originalCompound)
8385
.map(
8486
(initializer) => transformInitializer(
8587
initializer,
@@ -122,3 +124,44 @@ ClassDeclaration transformCompound(
122124

123125
return transformedCompound;
124126
}
127+
128+
List<InitializerDeclaration> _compoundInitializers(
129+
CompoundDeclaration originalCompound,
130+
) {
131+
final initializers = originalCompound.initializers;
132+
if (originalCompound is! StructDeclaration || initializers.isNotEmpty) {
133+
return initializers;
134+
}
135+
final storedProperties =
136+
originalCompound.properties
137+
.where((prop) => !prop.isStatic && !prop.hasExplicitGetter)
138+
.toList()
139+
..sort((a, b) {
140+
final aLine = a.lineNumber ?? 0;
141+
final bLine = b.lineNumber ?? 0;
142+
return aLine.compareTo(bLine);
143+
});
144+
145+
if (storedProperties.isEmpty) {
146+
return initializers;
147+
}
148+
149+
final implicitInit = InitializerDeclaration(
150+
id: originalCompound.id.addIdSuffix('implicit_init'),
151+
source: originalCompound.source,
152+
availability: originalCompound.availability,
153+
params: storedProperties
154+
.map(
155+
(prop) =>
156+
Parameter(name: prop.name, internalName: null, type: prop.type),
157+
)
158+
.toList(),
159+
hasObjCAnnotation: true,
160+
isOverriding: false,
161+
throws: false,
162+
async: false,
163+
isFailable: false,
164+
);
165+
166+
return [implicitInit];
167+
}

pkgs/swift2objc/test/integration/available_output.swift

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,6 @@ import Foundation
99
get {
1010
globalVar
1111
}
12-
set {
13-
globalVar = newValue
14-
}
1512
}
1613

1714
@available(macOS, introduced: 234.5.6)
@@ -110,6 +107,12 @@ import Foundation
110107
self.wrappedInstance = wrappedInstance
111108
}
112109

110+
@available(macOS, introduced: 123.0.0)
111+
@available(iOS, introduced: 100)
112+
@objc public init(prop1: Int, prop2: Int) {
113+
wrappedInstance = NewStruct(prop1: prop1, prop2: prop2)
114+
}
115+
113116
@available(macOS, introduced: 123.0.0)
114117
@available(iOS, introduced: 100)
115118
@objc public func method1() -> Int {

pkgs/swift2objc/test/integration/global_variables_and_functions_output.swift

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,6 @@ import Foundation
1313
get {
1414
MyOtherClassWrapper(globalCustomVariable)
1515
}
16-
set {
17-
globalCustomVariable = newValue.wrappedInstance
18-
}
1916
}
2017

2118
@objc static public var globalGetterVariableWrapper: Double {
@@ -43,9 +40,6 @@ import Foundation
4340
get {
4441
globalRepresentableVariable
4542
}
46-
set {
47-
globalRepresentableVariable = newValue
48-
}
4943
}
5044

5145
@objc static public func globalCustomFunctionWrapper(label1 param1: Int, param2: MyOtherClassWrapper) -> MyOtherClassWrapper {
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
public struct MyPerson {
2+
public var name: String
3+
public var age: Int
4+
}
5+
6+
public struct MyConfig {
7+
public var title: String
8+
public var count: Int
9+
public var enabled: Bool
10+
}
11+
12+
public struct MyCustomStruct {
13+
public var data: Int
14+
15+
public init(value: Int) {
16+
self.data = value * 2
17+
}
18+
}
19+
20+
public struct MyStaticStruct {
21+
public static var defaultName = "Default"
22+
public var name: String
23+
}
24+
25+
public struct MyComputedStruct {
26+
public var firstName: String
27+
public var lastName: String
28+
public var fullName: String {
29+
return "\(firstName) \(lastName)"
30+
}
31+
}
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
// Test preamble text
2+
3+
import Foundation
4+
5+
@objc public class MyCustomStructWrapper: NSObject {
6+
var wrappedInstance: MyCustomStruct
7+
8+
@objc public var data: Int {
9+
get {
10+
wrappedInstance.data
11+
}
12+
set {
13+
wrappedInstance.data = newValue
14+
}
15+
}
16+
17+
init(_ wrappedInstance: MyCustomStruct) {
18+
self.wrappedInstance = wrappedInstance
19+
}
20+
21+
@objc public init(value: Int) {
22+
wrappedInstance = MyCustomStruct(value: value)
23+
}
24+
25+
}
26+
27+
@objc public class MyStaticStructWrapper: NSObject {
28+
var wrappedInstance: MyStaticStruct
29+
30+
@objc static public var defaultName: String {
31+
get {
32+
MyStaticStruct.defaultName
33+
}
34+
set {
35+
MyStaticStruct.defaultName = newValue
36+
}
37+
}
38+
39+
@objc public var name: String {
40+
get {
41+
wrappedInstance.name
42+
}
43+
set {
44+
wrappedInstance.name = newValue
45+
}
46+
}
47+
48+
init(_ wrappedInstance: MyStaticStruct) {
49+
self.wrappedInstance = wrappedInstance
50+
}
51+
52+
@objc public init(name: String) {
53+
wrappedInstance = MyStaticStruct(name: name)
54+
}
55+
56+
}
57+
58+
@objc public class MyComputedStructWrapper: NSObject {
59+
var wrappedInstance: MyComputedStruct
60+
61+
@objc public var fullName: String {
62+
get {
63+
wrappedInstance.fullName
64+
}
65+
}
66+
67+
@objc public var lastName: String {
68+
get {
69+
wrappedInstance.lastName
70+
}
71+
set {
72+
wrappedInstance.lastName = newValue
73+
}
74+
}
75+
76+
@objc public var firstName: String {
77+
get {
78+
wrappedInstance.firstName
79+
}
80+
set {
81+
wrappedInstance.firstName = newValue
82+
}
83+
}
84+
85+
init(_ wrappedInstance: MyComputedStruct) {
86+
self.wrappedInstance = wrappedInstance
87+
}
88+
89+
@objc public init(firstName: String, lastName: String) {
90+
wrappedInstance = MyComputedStruct(firstName: firstName, lastName: lastName)
91+
}
92+
93+
}
94+
95+
@objc public class MyConfigWrapper: NSObject {
96+
var wrappedInstance: MyConfig
97+
98+
@objc public var count: Int {
99+
get {
100+
wrappedInstance.count
101+
}
102+
set {
103+
wrappedInstance.count = newValue
104+
}
105+
}
106+
107+
@objc public var title: String {
108+
get {
109+
wrappedInstance.title
110+
}
111+
set {
112+
wrappedInstance.title = newValue
113+
}
114+
}
115+
116+
@objc public var enabled: Bool {
117+
get {
118+
wrappedInstance.enabled
119+
}
120+
set {
121+
wrappedInstance.enabled = newValue
122+
}
123+
}
124+
125+
init(_ wrappedInstance: MyConfig) {
126+
self.wrappedInstance = wrappedInstance
127+
}
128+
129+
@objc public init(title: String, count: Int, enabled: Bool) {
130+
wrappedInstance = MyConfig(title: title, count: count, enabled: enabled)
131+
}
132+
133+
}
134+
135+
@objc public class MyPersonWrapper: NSObject {
136+
var wrappedInstance: MyPerson
137+
138+
@objc public var age: Int {
139+
get {
140+
wrappedInstance.age
141+
}
142+
set {
143+
wrappedInstance.age = newValue
144+
}
145+
}
146+
147+
@objc public var name: String {
148+
get {
149+
wrappedInstance.name
150+
}
151+
set {
152+
wrappedInstance.name = newValue
153+
}
154+
}
155+
156+
init(_ wrappedInstance: MyPerson) {
157+
self.wrappedInstance = wrappedInstance
158+
}
159+
160+
@objc public init(name: String, age: Int) {
161+
wrappedInstance = MyPerson(name: name, age: age)
162+
}
163+
164+
}
165+

pkgs/swift2objc/test/integration/optional_output.swift

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,6 @@ import Foundation
77
get {
88
globalOptional == nil ? nil : MyStructWrapper(globalOptional!)
99
}
10-
set {
11-
globalOptional = newValue?.wrappedInstance
12-
}
1310
}
1411

1512
@objc static public func funcOptionalArgsWrapper(label param: MyClassWrapper?) -> MyClassWrapper {

0 commit comments

Comments
 (0)