Skip to content
Open
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
76 changes: 76 additions & 0 deletions pkgs/swift2objc/lib/src/ast/_core/shared/referred_type.dart
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,82 @@ class InoutType extends AstNode implements ReferredType {
}
}

class ClosureType extends AstNode implements ReferredType {
final List<ReferredType> parameters;
final ReferredType returnType;
final bool isEscaping;
final bool isSendable;
final bool isAsync;
final bool isThrowing;

@override
bool get isObjCRepresentable => false;

@override
String get swiftType {
final attrs = [
if (isEscaping) '@escaping',
if (isSendable) '@Sendable',
];
final attrsPrefix = attrs.isEmpty ? '' : '${attrs.join(' ')} ';
final effects = [
if (isAsync) 'async',
if (isThrowing) 'throws',
];
final effectsSuffix = effects.isEmpty ? '' : ' ${effects.join(' ')}';
final params = parameters.map((p) => p.swiftType).join(', ');
return '$attrsPrefix($params)$effectsSuffix -> ${returnType.swiftType}';
}

@override
bool _sameAs(ReferredType other) {
if (other is! ClosureType) return false;
if (isEscaping != other.isEscaping ||
isSendable != other.isSendable ||
isAsync != other.isAsync ||
isThrowing != other.isThrowing) {
return false;
}
if (parameters.length != other.parameters.length) return false;
for (var i = 0; i < parameters.length; i++) {
if (!parameters[i].sameAs(other.parameters[i])) return false;
}
return returnType.sameAs(other.returnType);
}

@override
ReferredType get aliasedType => ClosureType(
parameters: parameters.map((p) => p.aliasedType).toList(),
returnType: returnType.aliasedType,
isEscaping: isEscaping,
isSendable: isSendable,
isAsync: isAsync,
isThrowing: isThrowing,
);

const ClosureType({
required this.parameters,
required this.returnType,
this.isEscaping = false,
this.isSendable = false,
this.isAsync = false,
this.isThrowing = false,
});

@override
String toString() => swiftType;

@override
void visit(Visitation visitation) => visitation.visitClosureType(this);

@override
void visitChildren(Visitor visitor) {
super.visitChildren(visitor);
visitor.visitAll(parameters);
visitor.visit(returnType);
}
}

/// Describes a reference to a Swift Tuple type (e.g., `(Int, String)`).
class TupleType extends AstNode implements ReferredType {
final List<TupleElement> elements;
Expand Down
1 change: 1 addition & 0 deletions pkgs/swift2objc/lib/src/ast/visitor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ abstract class Visitation {
void visitGenericType(GenericType node) => visitReferredType(node);
void visitOptionalType(OptionalType node) => visitReferredType(node);
void visitInoutType(InoutType node) => visitReferredType(node);
void visitClosureType(ClosureType node) => visitReferredType(node);
void visitDeclaration(Declaration node) => visitAstNode(node);
void visitBuiltInDeclaration(BuiltInDeclaration node) =>
visitDeclaration(node);
Expand Down
171 changes: 171 additions & 0 deletions pkgs/swift2objc/lib/src/parser/parsers/parse_type.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ import 'parse_declarations.dart';
Context context,
ParsedSymbolgraph symbolgraph,
TokenList fragments,
) => _parseTypeExpression(context, symbolgraph, fragments);

(ReferredType, TokenList) _parseTypeExpression(
Context context,
ParsedSymbolgraph symbolgraph,
TokenList fragments,
) {
var (type, suffix) = _parsePrefixTypeExpression(
context,
Expand Down Expand Up @@ -55,6 +61,68 @@ import 'parse_declarations.dart';
return parselet(context, symbolgraph, token, fragments.slice(1));
}

(ReferredType, TokenList) _parseTypeExpressionUntilArrow(
Context context,
ParsedSymbolgraph symbolgraph,
TokenList fragments,
) {
var (type, suffix) = _parsePrefixTypeExpression(
context,
symbolgraph,
fragments,
);

while (suffix.isNotEmpty) {
final suffixToken = _tokenId(suffix[0]);
if (suffixToken == 'text: ->' ||
suffixToken == 'keyword: async' ||
suffixToken == 'keyword: throws') {
break;
}
final (nextType, nextSuffix) = _maybeParseSuffixTypeExpression(
context,
symbolgraph,
type,
suffix,
);
if (nextType == null) break;
type = nextType;
suffix = nextSuffix;
}

return (type, suffix);
}

(bool, bool, TokenList) _parseClosureModifiers(TokenList fragments) {
var current = fragments;
var isAsync = false;
var isThrowing = false;

if (current.isNotEmpty && _tokenId(current[0]) == 'keyword: async') {
isAsync = true;
current = current.slice(1);
}

if (current.isNotEmpty && _tokenId(current[0]) == 'keyword: throws') {
isThrowing = true;
current = current.slice(1);
}

if (current.isNotEmpty && _tokenId(current[0]) == 'keyword: async') {
throw Exception('Invalid closure effects order: use "async throws", not "throws async".');
}

return (isAsync, isThrowing, current);
}

List<ReferredType> _closureParametersFromType(ReferredType parameterType) {
if (parameterType.sameAs(voidType)) return const [];
return switch (parameterType) {
TupleType t => [for (final e in t.elements) e.type],
_ => [parameterType],
};
}

// Suffix expressions are infix operators or suffix operators (basically
// anything that isn't a prefix). If we were parsing a programming language,
// these would be things like `x + y`, `z!`, or even `x ? y : z`.
Expand All @@ -65,6 +133,27 @@ import 'parse_declarations.dart';
TokenList fragments,
) {
if (fragments.isEmpty) return (null, fragments);

final (isAsync, isThrowing, afterEffects) = _parseClosureModifiers(fragments);
if ((isAsync || isThrowing) &&
afterEffects.isNotEmpty &&
_tokenId(afterEffects[0]) == 'text: ->') {
final (returnType, suffix) = parseType(
context,
symbolgraph,
afterEffects.slice(1),
);
return (
ClosureType(
parameters: _closureParametersFromType(prefixType),
returnType: returnType,
isAsync: isAsync,
isThrowing: isThrowing,
),
suffix,
);
}

final token = fragments[0];
final parselet = _suffixParsets[_tokenId(token)];
if (parselet == null) return (null, fragments);
Expand Down Expand Up @@ -188,10 +277,74 @@ typedef PrefixParselet =
return (InoutType(type), suffix);
}

(ReferredType, TokenList) _attributeParselet(
Context context,
ParsedSymbolgraph symbolgraph,
Json token,
TokenList fragments,
) {
var isEscaping = false;
var isSendable = false;

void consumeAttribute(Json attributeToken) {
final spelling = attributeToken['spelling'].get<String>();
if (spelling == '@escaping') {
isEscaping = true;
} else if (spelling == '@Sendable') {
isSendable = true;
} else {
throw Exception(
'Unsupported type attribute for closure at '
'"${attributeToken.path}": $spelling',
);
}
}

consumeAttribute(token);

var current = fragments;
while (current.isNotEmpty &&
current[0]['kind'].get<String>() == 'attribute') {
consumeAttribute(current[0]);
current = current.slice(1);
}

final (parameterType, suffix) = _parseTypeExpressionUntilArrow(
context,
symbolgraph,
current,
);

final (isAsync, isThrowing, afterEffects) = _parseClosureModifiers(suffix);

if (afterEffects.isEmpty || _tokenId(afterEffects[0]) != 'text: ->') {
throw Exception('Expected "->" after closure attributes at ${token.path}');
}

final (returnType, remaining) = _parseTypeExpression(
context,
symbolgraph,
afterEffects.slice(1),
);

return (
ClosureType(
parameters: _closureParametersFromType(parameterType),
returnType: returnType,
isEscaping: isEscaping,
isSendable: isSendable,
isAsync: isAsync,
isThrowing: isThrowing,
),
remaining,
);
}

Map<String, PrefixParselet> _prefixParsets = {
'typeIdentifier': _typeIdentifierParselet,
'text: (': _tupleParselet,
'keyword: inout': _inoutParselet,
'attribute': _attributeParselet,
};

// ========================
Expand Down Expand Up @@ -228,7 +381,25 @@ typedef SuffixParselet =
return parseType(context, symbolgraph, fragments);
}

(ReferredType, TokenList) _closureSuffixParselet(
Context context,
ParsedSymbolgraph symbolgraph,
ReferredType prefixType,
Json token,
TokenList fragments,
) {
final (returnType, suffix) = parseType(context, symbolgraph, fragments);
return (
ClosureType(
parameters: _closureParametersFromType(prefixType),
returnType: returnType,
),
suffix,
);
}

Map<String, SuffixParselet> _suffixParsets = {
'text: ?': _optionalParselet,
'text: .': _nestedTypeParselet,
'text: ->': _closureSuffixParselet,
};
4 changes: 4 additions & 0 deletions pkgs/swift2objc/lib/src/transformer/_core/utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ import 'unique_namer.dart';

if (type is GenericType) {
throw UnimplementedError('Generic types are not implemented yet');
} else if (type is ClosureType) {
return (value, type);
} else if (type is DeclaredType) {
final declaration = type.declaration;
if (declaration is TypealiasDeclaration) {
Expand Down Expand Up @@ -134,6 +136,8 @@ import 'unique_namer.dart';

if (type is GenericType) {
throw UnimplementedError('Generic types are not implemented yet');
} else if (type is ClosureType) {
return (value, type);
} else if (type is DeclaredType) {
final declaration = type.declaration;
if (declaration is ClassDeclaration) {
Expand Down
Loading