Skip to content

[swift2objc] Support Parsing Generic Types and Type Constraints #1831

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

Draft
wants to merge 10 commits into
base: main
Choose a base branch
from
12 changes: 7 additions & 5 deletions pkgs/swift2objc/lib/src/ast/_core/shared/referred_type.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import '../interfaces/compound_declaration.dart';
import '../../ast_node.dart';
import '../interfaces/declaration.dart';
import '../interfaces/nestable_declaration.dart';
Expand Down Expand Up @@ -76,6 +77,9 @@ class GenericType extends AstNode implements ReferredType {

final String name;

/// type constraints the generic type might have
final List<DeclaredType<CompoundDeclaration>> constraints;

@override
bool get isObjCRepresentable => false;

Expand All @@ -84,11 +88,9 @@ class GenericType extends AstNode implements ReferredType {

@override
bool sameAs(ReferredType other) => other is GenericType && other.id == id;

const GenericType({
required this.id,
required this.name,
});

const GenericType(
{required this.id, required this.name, this.constraints = const []});

@override
String toString() => name;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import '../../_core/interfaces/objc_annotatable.dart';
import '../../ast_node.dart';

/// Describes a built-in Swift type (e.g Int, String, etc).
/// TODO(https://github.com/dart-lang/native/issues/1827): Include builtin protocols like `Hashable`, `Numeric`
class BuiltInDeclaration extends AstNode
implements Declaration, ObjCAnnotatable {
@override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.

import '../../../ast/_core/interfaces/compound_declaration.dart';
import '../../../ast/_core/shared/referred_type.dart';
import '../../../ast/_core/interfaces/nestable_declaration.dart';
import '../../../ast/declarations/compounds/class_declaration.dart';
import '../../../ast/declarations/compounds/members/initializer_declaration.dart';
Expand All @@ -12,13 +13,15 @@ import '../../../ast/declarations/compounds/struct_declaration.dart';
import '../../_core/parsed_symbolgraph.dart';
import '../../_core/utils.dart';
import '../parse_declarations.dart';
import '../utils/parse_generics.dart';

typedef CompoundTearOff<T extends CompoundDeclaration> = T Function({
required String id,
required String name,
required List<PropertyDeclaration> properties,
required List<MethodDeclaration> methods,
required List<InitializerDeclaration> initializers,
required List<GenericType> typeParams,
required List<NestableDeclaration> nestedDeclarations,
});

Expand All @@ -37,11 +40,17 @@ T _parseCompoundDeclaration<T extends CompoundDeclaration>(
methods: [],
properties: [],
initializers: [],
typeParams: [],
nestedDeclarations: [],
);

compoundSymbol.declaration = compound;

final typeParams = parseTypeParams(compoundSymbol.json, symbolgraph);
compound.typeParams.addAll(
typeParams
);

final memberDeclarations = compoundRelations
.where(
(relation) {
Expand Down Expand Up @@ -77,6 +86,7 @@ T _parseCompoundDeclaration<T extends CompoundDeclaration>(
.whereType<InitializerDeclaration>()
.dedupeBy((m) => m.fullName),
);

compound.nestedDeclarations.addAll(
memberDeclarations.whereType<NestableDeclaration>(),
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,36 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import '../../../ast/_core/interfaces/compound_declaration.dart';
import '../../../ast/_core/interfaces/declaration.dart';
import '../../../ast/_core/shared/parameter.dart';
import '../../../ast/_core/shared/referred_type.dart';
import '../../../ast/declarations/compounds/members/method_declaration.dart';
import '../../../ast/declarations/globals/globals.dart';
import '../../_core/json.dart';
import '../../_core/parsed_symbolgraph.dart';
import '../../_core/token_list.dart';
import '../../_core/utils.dart';
import '../parse_declarations.dart';
import '../utils/parse_generics.dart';
import '../../_core/token_list.dart';
import '../parse_type.dart';

GlobalFunctionDeclaration parseGlobalFunctionDeclaration(
Json globalFunctionSymbolJson,
ParsedSymbolgraph symbolgraph,
) {
final info = parseFunctionInfo(
globalFunctionSymbolJson['declarationFragments'], symbolgraph);
// final info = parseFunctionInfo(
// globalFunctionSymbolJson['declarationFragments'], symbolgraph);
return GlobalFunctionDeclaration(
id: parseSymbolId(globalFunctionSymbolJson),
name: parseSymbolName(globalFunctionSymbolJson),
returnType: _parseFunctionReturnType(globalFunctionSymbolJson, symbolgraph),
params: info.params,
throws: info.throws,
async: info.async,
// params: info.params,
// throws: info.throws,
// async: info.async,
params: _parseFunctionParams(globalFunctionSymbolJson, symbolgraph),
typeParams:
parseTypeParams(globalFunctionSymbolJson, symbolgraph)
);
}

Expand All @@ -33,18 +40,118 @@ MethodDeclaration parseMethodDeclaration(
ParsedSymbolgraph symbolgraph, {
bool isStatic = false,
}) {
final info =
parseFunctionInfo(methodSymbolJson['declarationFragments'], symbolgraph);
// 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,
mutating: info.mutating);
id: parseSymbolId(methodSymbolJson),
name: parseSymbolName(methodSymbolJson),
returnType: _parseFunctionReturnType(methodSymbolJson, symbolgraph),
params: _parseFunctionParams(methodSymbolJson, symbolgraph),
hasObjCAnnotation: parseSymbolHasObjcAnnotation(methodSymbolJson),
typeParams: parseTypeParams(methodSymbolJson, symbolgraph),
isStatic: isStatic,
// params: info.params,
// throws: info.throws,
// async: info.async,
// mutating: info.mutating
);
}

ReferredType _parseFunctionReturnType(
Json methodSymbolJson,
ParsedSymbolgraph symbolgraph,
) {
final returnJson = methodSymbolJson['functionSignature']['returns'][0];

// if it is a type generic it may not even have a spelling
if (returnJson['spelling'].get<String?>() == null) {
// check if the item is a generic registered
try {
final type = returnJson['spelling'].get<String>();
final generics = methodSymbolJson['swiftGenerics']['parameters'];
if (generics.map((e) => e['name'].get<String>()).contains(type)) {
// generic located
return parseDeclGenericType(methodSymbolJson['swiftGenerics'], type,
symbolgraph, methodSymbolJson);
}
} on Exception catch (e) {
// continue
}
} else if (returnJson['spelling'].get<String>() == '()') {
// This means there's no return type
return null;
}

// final returnTypeId = returnJson['preciseIdentifier'].get<String>();

// final returnTypeSymbol = symbolgraph.symbols[returnTypeId];

// if (returnTypeSymbol == null) {
// throw Exception(
// 'The method at path "${methodSymbolJson.path}" has a return type that '
// 'does not exist among parsed symbols.',
// );
// }

// final returnTypeDeclaration = parseDeclaration(
// returnTypeSymbol,
// symbolgraph,
// );

// return returnTypeDeclaration.asDeclaredType;
final returnJson =
TokenList(methodSymbolJson['functionSignature']['returns']);
final (returnType, unparsed) = parseType(symbolgraph, returnJson);
assert(unparsed.isEmpty, '$returnJson\n\n$returnType\n\n$unparsed\n');
return returnType;
}

List<Parameter> _parseFunctionParams(
Json methodSymbolJson,
ParsedSymbolgraph symbolgraph,
) {
final paramList = methodSymbolJson['functionSignature']['parameters'];

if (!paramList.exists) return [];

return paramList
.map(
// TODO: Add parameter type generic parsing
(param) => Parameter(
name: param['name'].get(),
internalName: param['internalName'].get(),
type: _parseParamType(param, symbolgraph, methodSymbolJson: methodSymbolJson),
),
)
.toList();
}

ReferredType _parseParamType(
Json paramSymbolJson,
ParsedSymbolgraph symbolgraph,
{Json? methodSymbolJson}
) {
final fragments = paramSymbolJson['declarationFragments'];

var typeDeclFragment = fragments
.firstJsonWhereKey('kind', 'typeIdentifier');

if (methodSymbolJson != null) {
if (methodSymbolJson['swiftGenerics'].get<Map<String, dynamic>?>() != null) {
if (methodSymbolJson['swiftGenerics']['parameters'].map(
(e) => e['name'].get<String>()
).contains(typeDeclFragment['spelling'].get<String>())) {
return parseDeclGenericType(methodSymbolJson['swiftGenerics'],
typeDeclFragment['spelling'].get<String>(), symbolgraph,
methodSymbolJson);
}
}
}

final paramTypeId = typeDeclFragment['preciseIdentifier']
.get<String>();

return parseTypeFromId(paramTypeId, symbolgraph);
}

typedef ParsedFunctionInfo = ({
Expand Down Expand Up @@ -162,14 +269,3 @@ ParsedFunctionInfo parseFunctionInfo(
mutating: prefixAnnotations.contains('mutating')
);
}

ReferredType _parseFunctionReturnType(
Json methodSymbolJson,
ParsedSymbolgraph symbolgraph,
) {
final returnJson =
TokenList(methodSymbolJson['functionSignature']['returns']);
final (returnType, unparsed) = parseType(symbolgraph, returnJson);
assert(unparsed.isEmpty, '$returnJson\n\n$returnType\n\n$unparsed\n');
return returnType;
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import '../../_core/parsed_symbolgraph.dart';
import '../../_core/token_list.dart';
import '../../_core/utils.dart';

/// TODO: Support for generic types: Requires larger scope
PropertyDeclaration parsePropertyDeclaration(
Json propertySymbolJson,
ParsedSymbolgraph symbolgraph, {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ Declaration parseDeclaration(

final symbolType = symbolJson['kind']['identifier'].get<String>();

// TODO(https://github.com/dart-lang/native/issues/1828): Support protocols
parsedSymbol.declaration = switch (symbolType) {
'swift.class' => parseClassDeclaration(parsedSymbol, symbolgraph),
'swift.struct' => parseStructDeclaration(parsedSymbol, symbolgraph),
Expand Down
60 changes: 60 additions & 0 deletions pkgs/swift2objc/lib/src/parser/parsers/utils/parse_generics.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import '../../../ast/_core/interfaces/compound_declaration.dart';
import '../../../ast/_core/interfaces/declaration.dart';
import '../../../ast/_core/shared/referred_type.dart';
import '../../_core/json.dart';
import '../../_core/parsed_symbolgraph.dart';
import '../parse_declarations.dart';


List<GenericType> parseTypeParams(
Json declarationSymbolJson,
ParsedSymbolgraph symbolgraph,
) {
// get type params
final genericInfo = declarationSymbolJson['swiftGenerics'];

final parameters = genericInfo['parameters'];

if (genericInfo.jsonWithKeyExists('constraints')) {
return parameters.map((e) {
return parseDeclGenericType(genericInfo, e['name'].get<String>(),
symbolgraph, declarationSymbolJson);
}).toList();
} else {
// how to make a good id for generic types
return parameters
.map((e) => GenericType(
id: e['name'].get<String>(), name: e['name'].get<String>()))
.toList();
}
}

GenericType parseDeclGenericType(Json genericInfo, String name,
ParsedSymbolgraph symbolgraph,
Json declSymbolJson, {String? id}) {
final constraintsDesc = genericInfo['constraints'].where(
(element) => element['lhs'].get<String>() == name);

return GenericType(
id: id ?? name,
name: name,
constraints: constraintsDesc.map((c) {
final constraintId = c['rhsPrecise'].get<String>();

final constraintTypeSymbol = symbolgraph.symbols[constraintId];

if (constraintTypeSymbol == null) {
throw Exception(
'The type constraint at path "${declSymbolJson.path}"'
' has a return type that does not exist among parsed symbols.',
);
}

final constraintDeclaration = parseDeclaration(
constraintTypeSymbol,
symbolgraph,
) as CompoundDeclaration;

return constraintDeclaration.asDeclaredType;
}).toList());
}
Loading