Skip to content

[swift2objc] Add support for mutating functions #1944

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Mar 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -45,24 +45,27 @@ class MethodDeclaration extends AstNode

bool isStatic;

bool mutating;

String get fullName => [
name,
for (final p in params) p.name,
].join(':');

MethodDeclaration({
required this.id,
required this.name,
required this.returnType,
required this.params,
this.typeParams = const [],
this.hasObjCAnnotation = false,
this.statements = const [],
this.isStatic = false,
this.isOverriding = false,
this.throws = false,
this.async = false,
}) : assert(!isStatic || !isOverriding);
MethodDeclaration(
{required this.id,
required this.name,
required this.returnType,
required this.params,
this.typeParams = const [],
this.hasObjCAnnotation = false,
this.statements = const [],
this.isStatic = false,
this.isOverriding = false,
this.throws = false,
this.async = false,
this.mutating = false})
: assert(!isStatic || !isOverriding);

@override
void visit(Visitation visitation) => visitation.visitMethodDeclaration(this);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,31 +36,32 @@ MethodDeclaration parseMethodDeclaration(
final info =
parseFunctionInfo(methodSymbolJson['declarationFragments'], symbolgraph);
return MethodDeclaration(
id: parseSymbolId(methodSymbolJson),
name: parseSymbolName(methodSymbolJson),
returnType: _parseFunctionReturnType(methodSymbolJson, symbolgraph),
params: info.params,
hasObjCAnnotation: parseSymbolHasObjcAnnotation(methodSymbolJson),
isStatic: isStatic,
throws: info.throws,
async: info.async,
);
id: parseSymbolId(methodSymbolJson),
name: parseSymbolName(methodSymbolJson),
returnType: _parseFunctionReturnType(methodSymbolJson, symbolgraph),
params: info.params,
hasObjCAnnotation: parseSymbolHasObjcAnnotation(methodSymbolJson),
isStatic: isStatic,
throws: info.throws,
async: info.async,
mutating: info.mutating);
}

typedef ParsedFunctionInfo = ({
List<Parameter> params,
bool throws,
bool async,
bool mutating,
});

ParsedFunctionInfo parseFunctionInfo(
Json declarationFragments,
ParsedSymbolgraph symbolgraph,
) {
// `declarationFragments` describes each part of the function declaration,
// things like the `func` keyword, brackets, spaces, etc. We only care about
// the parameter fragments and annotations here, and they always appear in
// this order:
// things like the `func` keyword, brackets, spaces, etc.
// For the most part, We only care about the parameter fragments and
// annotations here, and they always appear in this order:
// [
// ..., '(',
// externalParam, ' ', internalParam, ': ', type..., ', '
Expand All @@ -80,15 +81,34 @@ ParsedFunctionInfo parseFunctionInfo(
);

var tokens = TokenList(declarationFragments);

String? maybeConsume(String kind) {
if (tokens.isEmpty) return null;
final spelling = getSpellingForKind(tokens[0], kind);
if (spelling != null) tokens = tokens.slice(1);
return spelling;
}

final prefixAnnotations = <String>{};

while (true) {
final keyword = maybeConsume('keyword');
if (keyword != null) {
if (keyword == 'func' || keyword == 'init') {
break;
} else {
prefixAnnotations.add(keyword);
}
} else {
if (maybeConsume('text') != '') {
throw malformedInitializerException;
}
}
}

final openParen = tokens.indexWhere((tok) => matchFragment(tok, 'text', '('));
if (openParen == -1) throw malformedInitializerException;

tokens = tokens.slice(openParen + 1);

// Parse parameters until we find a ')'.
Expand Down Expand Up @@ -139,6 +159,7 @@ ParsedFunctionInfo parseFunctionInfo(
params: parameters,
throws: annotations.contains('throws'),
async: annotations.contains('async'),
mutating: prefixAnnotations.contains('mutating')
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ public struct MyStruct {
public func myMethod3() {
1 + 1
}

public mutating func myMethod4() {
2 + 2
}
}

public struct MyOtherStruct {}
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,9 @@ import Foundation
return wrappedInstance.myMethod3()
}

@objc public func myMethod4() {
return wrappedInstance.myMethod4()
}

}

90 changes: 90 additions & 0 deletions pkgs/swift2objc/test/unit/parse_function_info_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,96 @@ void main() {
expect(info.throws, isTrue);
expect(info.async, isTrue);
});

test('Mutating Function that throws', () {
final json = Json(jsonDecode('''
[
{
"kind": "keyword",
"spelling": "mutating"
},
{
"kind": "text",
"spelling": " "
},
{
"kind": "keyword",
"spelling": "func"
},
{
"kind": "text",
"spelling": " "
},
{
"kind": "identifier",
"spelling": "moveBy"
},
{
"kind": "text",
"spelling": "("
},
{
"kind": "externalParam",
"spelling": "x"
},
{
"kind": "text",
"spelling": " "
},
{
"kind": "internalParam",
"spelling": "deltaX"
},
{
"kind": "text",
"spelling": ": "
},
{
"kind": "typeIdentifier",
"spelling": "Double",
"preciseIdentifier": "s:Sd"
},
{
"kind": "text",
"spelling": ", "
},
{
"kind": "externalParam",
"spelling": "y"
},
{
"kind": "text",
"spelling": " "
},
{
"kind": "internalParam",
"spelling": "deltaY"
},
{
"kind": "text",
"spelling": ": "
},
{
"kind": "typeIdentifier",
"spelling": "Double",
"preciseIdentifier": "s:Sd"
},
{
"kind": "text",
"spelling": ") "
},
{
"kind": "keyword",
"spelling": "throws"
}
]
'''));

final info = parseFunctionInfo(json, emptySymbolgraph);

expect(info.throws, isTrue);
expect(info.mutating, isTrue);
});
});

group('Invalid json', () {
Expand Down