Skip to content

Commit 6cec710

Browse files
[swift2objc] Add support for mutating functions (#1944)
1 parent 2abe6f5 commit 6cec710

File tree

5 files changed

+147
-25
lines changed

5 files changed

+147
-25
lines changed

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

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -45,24 +45,27 @@ class MethodDeclaration extends AstNode
4545

4646
bool isStatic;
4747

48+
bool mutating;
49+
4850
String get fullName => [
4951
name,
5052
for (final p in params) p.name,
5153
].join(':');
5254

53-
MethodDeclaration({
54-
required this.id,
55-
required this.name,
56-
required this.returnType,
57-
required this.params,
58-
this.typeParams = const [],
59-
this.hasObjCAnnotation = false,
60-
this.statements = const [],
61-
this.isStatic = false,
62-
this.isOverriding = false,
63-
this.throws = false,
64-
this.async = false,
65-
}) : assert(!isStatic || !isOverriding);
55+
MethodDeclaration(
56+
{required this.id,
57+
required this.name,
58+
required this.returnType,
59+
required this.params,
60+
this.typeParams = const [],
61+
this.hasObjCAnnotation = false,
62+
this.statements = const [],
63+
this.isStatic = false,
64+
this.isOverriding = false,
65+
this.throws = false,
66+
this.async = false,
67+
this.mutating = false})
68+
: assert(!isStatic || !isOverriding);
6669

6770
@override
6871
void visit(Visitation visitation) => visitation.visitMethodDeclaration(this);

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

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -36,31 +36,32 @@ MethodDeclaration parseMethodDeclaration(
3636
final info =
3737
parseFunctionInfo(methodSymbolJson['declarationFragments'], symbolgraph);
3838
return MethodDeclaration(
39-
id: parseSymbolId(methodSymbolJson),
40-
name: parseSymbolName(methodSymbolJson),
41-
returnType: _parseFunctionReturnType(methodSymbolJson, symbolgraph),
42-
params: info.params,
43-
hasObjCAnnotation: parseSymbolHasObjcAnnotation(methodSymbolJson),
44-
isStatic: isStatic,
45-
throws: info.throws,
46-
async: info.async,
47-
);
39+
id: parseSymbolId(methodSymbolJson),
40+
name: parseSymbolName(methodSymbolJson),
41+
returnType: _parseFunctionReturnType(methodSymbolJson, symbolgraph),
42+
params: info.params,
43+
hasObjCAnnotation: parseSymbolHasObjcAnnotation(methodSymbolJson),
44+
isStatic: isStatic,
45+
throws: info.throws,
46+
async: info.async,
47+
mutating: info.mutating);
4848
}
4949

5050
typedef ParsedFunctionInfo = ({
5151
List<Parameter> params,
5252
bool throws,
5353
bool async,
54+
bool mutating,
5455
});
5556

5657
ParsedFunctionInfo parseFunctionInfo(
5758
Json declarationFragments,
5859
ParsedSymbolgraph symbolgraph,
5960
) {
6061
// `declarationFragments` describes each part of the function declaration,
61-
// things like the `func` keyword, brackets, spaces, etc. We only care about
62-
// the parameter fragments and annotations here, and they always appear in
63-
// this order:
62+
// things like the `func` keyword, brackets, spaces, etc.
63+
// For the most part, We only care about the parameter fragments and
64+
// annotations here, and they always appear in this order:
6465
// [
6566
// ..., '(',
6667
// externalParam, ' ', internalParam, ': ', type..., ', '
@@ -80,15 +81,34 @@ ParsedFunctionInfo parseFunctionInfo(
8081
);
8182

8283
var tokens = TokenList(declarationFragments);
84+
8385
String? maybeConsume(String kind) {
8486
if (tokens.isEmpty) return null;
8587
final spelling = getSpellingForKind(tokens[0], kind);
8688
if (spelling != null) tokens = tokens.slice(1);
8789
return spelling;
8890
}
8991

92+
final prefixAnnotations = <String>{};
93+
94+
while (true) {
95+
final keyword = maybeConsume('keyword');
96+
if (keyword != null) {
97+
if (keyword == 'func' || keyword == 'init') {
98+
break;
99+
} else {
100+
prefixAnnotations.add(keyword);
101+
}
102+
} else {
103+
if (maybeConsume('text') != '') {
104+
throw malformedInitializerException;
105+
}
106+
}
107+
}
108+
90109
final openParen = tokens.indexWhere((tok) => matchFragment(tok, 'text', '('));
91110
if (openParen == -1) throw malformedInitializerException;
111+
92112
tokens = tokens.slice(openParen + 1);
93113

94114
// Parse parameters until we find a ')'.
@@ -139,6 +159,7 @@ ParsedFunctionInfo parseFunctionInfo(
139159
params: parameters,
140160
throws: annotations.contains('throws'),
141161
async: annotations.contains('async'),
162+
mutating: prefixAnnotations.contains('mutating')
142163
);
143164
}
144165

pkgs/swift2objc/test/integration/structs_and_methods_input.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ public struct MyStruct {
1212
public func myMethod3() {
1313
1 + 1
1414
}
15+
16+
public mutating func myMethod4() {
17+
2 + 2
18+
}
1519
}
1620

1721
public struct MyOtherStruct {}

pkgs/swift2objc/test/integration/structs_and_methods_output.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,9 @@ import Foundation
3131
return wrappedInstance.myMethod3()
3232
}
3333

34+
@objc public func myMethod4() {
35+
return wrappedInstance.myMethod4()
36+
}
37+
3438
}
3539

pkgs/swift2objc/test/unit/parse_function_info_test.dart

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,96 @@ void main() {
456456
expect(info.throws, isTrue);
457457
expect(info.async, isTrue);
458458
});
459+
460+
test('Mutating Function that throws', () {
461+
final json = Json(jsonDecode('''
462+
[
463+
{
464+
"kind": "keyword",
465+
"spelling": "mutating"
466+
},
467+
{
468+
"kind": "text",
469+
"spelling": " "
470+
},
471+
{
472+
"kind": "keyword",
473+
"spelling": "func"
474+
},
475+
{
476+
"kind": "text",
477+
"spelling": " "
478+
},
479+
{
480+
"kind": "identifier",
481+
"spelling": "moveBy"
482+
},
483+
{
484+
"kind": "text",
485+
"spelling": "("
486+
},
487+
{
488+
"kind": "externalParam",
489+
"spelling": "x"
490+
},
491+
{
492+
"kind": "text",
493+
"spelling": " "
494+
},
495+
{
496+
"kind": "internalParam",
497+
"spelling": "deltaX"
498+
},
499+
{
500+
"kind": "text",
501+
"spelling": ": "
502+
},
503+
{
504+
"kind": "typeIdentifier",
505+
"spelling": "Double",
506+
"preciseIdentifier": "s:Sd"
507+
},
508+
{
509+
"kind": "text",
510+
"spelling": ", "
511+
},
512+
{
513+
"kind": "externalParam",
514+
"spelling": "y"
515+
},
516+
{
517+
"kind": "text",
518+
"spelling": " "
519+
},
520+
{
521+
"kind": "internalParam",
522+
"spelling": "deltaY"
523+
},
524+
{
525+
"kind": "text",
526+
"spelling": ": "
527+
},
528+
{
529+
"kind": "typeIdentifier",
530+
"spelling": "Double",
531+
"preciseIdentifier": "s:Sd"
532+
},
533+
{
534+
"kind": "text",
535+
"spelling": ") "
536+
},
537+
{
538+
"kind": "keyword",
539+
"spelling": "throws"
540+
}
541+
]
542+
'''));
543+
544+
final info = parseFunctionInfo(json, emptySymbolgraph);
545+
546+
expect(info.throws, isTrue);
547+
expect(info.mutating, isTrue);
548+
});
459549
});
460550

461551
group('Invalid json', () {

0 commit comments

Comments
 (0)