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
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
// 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 '../../../config.dart';
import '../../_core/interfaces/availability.dart';
import '../../_core/interfaces/declaration.dart';
import '../../_core/shared/referred_type.dart';
import '../../ast_node.dart';
import 'members/initializer_declaration.dart';
import 'members/method_declaration.dart';
import 'members/property_declaration.dart';

class ExtensionDeclaration extends AstNode implements Declaration {
@override
String id;

@override
String name;

@override
InputConfig? source;

@override
List<AvailabilityInfo> availability;

@override
final int? lineNumber;

DeclaredType<Declaration> extendedType;

List<PropertyDeclaration> properties;
List<MethodDeclaration> methods;
List<InitializerDeclaration> initializers;

ExtensionDeclaration({
required this.id,
required this.name,
required this.source,
required this.availability,
required this.extendedType,
this.lineNumber,
this.properties = const [],
this.methods = const [],
this.initializers = const [],
});

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

@override
void visitChildren(Visitor visitor) {
super.visitChildren(visitor);
visitor.visit(extendedType);
visitor.visitAll(properties);
visitor.visitAll(methods);
visitor.visitAll(initializers);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ class InitializerDeclaration extends AstNode
bool async;

bool isFailable;
bool isExtensionMember;

@override
List<Parameter> params;
Expand All @@ -73,6 +74,7 @@ class InitializerDeclaration extends AstNode
required this.throws,
required this.async,
required this.isFailable,
this.isExtensionMember = false,
});

@override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ class MethodDeclaration extends AstNode

bool mutating;
bool isOperator = false;
bool isExtensionMember;

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

Expand All @@ -78,6 +79,7 @@ class MethodDeclaration extends AstNode
this.async = false,
this.mutating = false,
this.isOperator = false,
this.isExtensionMember = false,
}) : assert(!isStatic || !isOverriding);

@override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ class PropertyDeclaration extends AstNode
bool lazy;

bool isStatic;
bool isExtensionMember;

PropertyDeclaration({
required this.id,
Expand All @@ -75,6 +76,7 @@ class PropertyDeclaration extends AstNode
this.setter,
this.hasExplicitGetter = false,
this.isStatic = false,
this.isExtensionMember = false,
this.throws = false,
this.async = false,
this.unowned = false,
Expand Down
3 changes: 3 additions & 0 deletions pkgs/swift2objc/lib/src/ast/visitor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import 'ast_node.dart';
import 'declarations/built_in/built_in_declaration.dart';
import 'declarations/compounds/class_declaration.dart';
import 'declarations/compounds/enum_declaration.dart';
import 'declarations/compounds/extension_declaration.dart';
import 'declarations/compounds/members/initializer_declaration.dart';
import 'declarations/compounds/members/method_declaration.dart';
import 'declarations/compounds/members/property_declaration.dart';
Expand Down Expand Up @@ -109,6 +110,8 @@ abstract class Visitation {
void visitTypealiasDeclaration(TypealiasDeclaration node) =>
visitDeclaration(node);
void visitTupleType(TupleType node) => visitReferredType(node);
void visitExtensionDeclaration(ExtensionDeclaration node) =>
visitDeclaration(node);

/// Default behavior for all visit methods.
void visitAstNode(AstNode node) => node.visitChildren(visitor);
Expand Down
3 changes: 3 additions & 0 deletions pkgs/swift2objc/lib/src/generator/generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@

import '../ast/_core/interfaces/declaration.dart';
import '../ast/declarations/compounds/class_declaration.dart';
import '../ast/declarations/compounds/extension_declaration.dart';
import 'generators/class_generator.dart';
import 'generators/extension_generator.dart';

String generate(
List<Declaration> declarations, {
Expand All @@ -22,6 +24,7 @@ String generate(
List<String> generateDeclaration(Declaration declaration) {
return switch (declaration) {
ClassDeclaration() => generateClass(declaration),
ExtensionDeclaration() => generateExtension(declaration),
_ => throw UnimplementedError(
"$declaration generation isn't implemented yet",
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,21 +73,23 @@ String? _generateClassWrappedInstance(ClassDeclaration declaration) {

List<String> _generateInitializers(ClassDeclaration declaration) {
return [
..._generateInitializer(declaration.wrapperInitializer, isPublic: false),
...generateInitializer(declaration.wrapperInitializer, isPublic: false),
for (final init in declaration.initializers)
..._generateInitializer(init, isPublic: true),
...generateInitializer(init, isPublic: true),
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made _generateClassMethod, _generateClassProperty, and _generateInitializer public, so that extension_generator.dart could reuse them directly instead of duplicating the same logic.

];
}

List<String> _generateInitializer(
List<String> generateInitializer(
InitializerDeclaration? initializer, {
required bool isPublic,
bool isConvenience = false,
}) {
if (initializer == null) return [];
final header = [
if (initializer.hasObjCAnnotation) '@objc ',
if (initializer.isOverriding) 'override ',
if (isPublic) 'public ',
if (isConvenience) 'convenience ',
'init',
if (initializer.isFailable) '?',
'(${generateParameters(initializer.params)}) ',
Expand All @@ -103,10 +105,10 @@ List<String> _generateInitializer(
}

List<String> _generateClassMethods(ClassDeclaration declaration) => [
for (final method in declaration.methods) ..._generateClassMethod(method),
for (final method in declaration.methods) ...generateClassMethod(method),
];

List<String> _generateClassMethod(MethodDeclaration method) {
List<String> generateClassMethod(MethodDeclaration method) {
final header = StringBuffer();

if (method.hasObjCAnnotation) {
Expand Down Expand Up @@ -141,10 +143,10 @@ List<String> _generateClassMethod(MethodDeclaration method) {

List<String> _generateClassProperties(ClassDeclaration declaration) => [
for (final property in declaration.properties)
..._generateClassProperty(property),
...generateClassProperty(property),
];

List<String> _generateClassProperty(PropertyDeclaration property) {
List<String> generateClassProperty(PropertyDeclaration property) {
final header = StringBuffer();

if (property.hasObjCAnnotation) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
// 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/declarations/compounds/extension_declaration.dart';
import '../_core/utils.dart';
import 'class_generator.dart';

List<String> generateExtension(ExtensionDeclaration declaration) {
return [
...generateAvailability(declaration),
'@objc extension ${declaration.extendedType.declaration.name} {',
...[
for (final method in declaration.methods) ...generateClassMethod(method),
for (final property in declaration.properties)
...generateClassProperty(property),
for (final init in declaration.initializers)
...generateInitializer(init, isPublic: true, isConvenience: true),
].indent(),
'}\n',
];
}
2 changes: 2 additions & 0 deletions pkgs/swift2objc/lib/src/parser/_core/parsed_symbolgraph.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import '../../ast/_core/interfaces/declaration.dart';
import '../../ast/declarations/built_in/built_in_declaration.dart';
import '../../ast/declarations/compounds/extension_declaration.dart';
import '../../config.dart';
import 'json.dart';

Expand Down Expand Up @@ -66,6 +67,7 @@ class ParsedSymbol {
final InputConfig? source;
final Json json;
Declaration? declaration;
ExtensionDeclaration? extension;

ParsedSymbol({required this.source, required this.json, this.declaration});
}
Expand Down
4 changes: 3 additions & 1 deletion pkgs/swift2objc/lib/src/parser/_core/utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import '../../ast/_core/interfaces/availability.dart';
import '../../ast/_core/interfaces/declaration.dart';
import '../../ast/_core/interfaces/nestable_declaration.dart';
import '../../ast/_core/shared/referred_type.dart';
import '../../ast/declarations/compounds/extension_declaration.dart';
import '../../ast/declarations/globals/globals.dart';
import '../../context.dart';
import '../parsers/parse_type.dart';
Expand All @@ -34,7 +35,8 @@ extension TopLevelOnly<T extends Declaration> on List<T> {
return declaration.nestingParent == null;
}
return declaration is GlobalVariableDeclaration ||
declaration is GlobalFunctionDeclaration;
declaration is GlobalFunctionDeclaration ||
declaration is ExtensionDeclaration;
}).toList();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import '../../../ast/_core/interfaces/compound_declaration.dart';
import '../../../ast/_core/interfaces/declaration.dart';
import '../../../ast/_core/interfaces/nestable_declaration.dart';
import '../../../ast/declarations/compounds/class_declaration.dart';
import '../../../ast/declarations/compounds/extension_declaration.dart';
import '../../../ast/declarations/compounds/members/initializer_declaration.dart';
import '../../../ast/declarations/compounds/members/method_declaration.dart';
import '../../../ast/declarations/compounds/members/property_declaration.dart';
Expand Down Expand Up @@ -65,26 +66,81 @@ ParsedCompound<T> parseCompoundDeclaration<T extends CompoundDeclaration>(
.dedupeBy((decl) => decl.id)
.toList();

// Separate extension members from regular members
final extensionMethods = memberDeclarations
.whereType<MethodDeclaration>()
.where((m) => m.isExtensionMember)
.toList();
final extensionProperties = memberDeclarations
.whereType<PropertyDeclaration>()
.where((p) => p.isExtensionMember)
.toList();
final extensionInitializers = memberDeclarations
.whereType<InitializerDeclaration>()
.where((i) => i.isExtensionMember)
.toList();

final firstExtMember = extensionMethods.isNotEmpty
? extensionMethods.first
: extensionProperties.isNotEmpty
? extensionProperties.first
: extensionInitializers.isNotEmpty
? extensionInitializers.first
: null;

if (firstExtMember != null) {
final isExternal =
symbolgraph.symbols[firstExtMember.id]?.source == builtInInputConfig;

if (isExternal) {
// TODO(https://github.com/dart-lang/native/issues/3228): Support
// extensions on external types.
context.logger.warning(
'Extension on external type ${compound.name} is not '
'supported yet.',
);
} else {
symbol.extension = ExtensionDeclaration(
id: compoundId.addIdSuffix('extension'),
name: compound.name,
source: compound.source,
availability: compound.availability,
extendedType: compound.asDeclaredType,
methods: extensionMethods,
properties: extensionProperties,
initializers: extensionInitializers,
);
}
}

// Only add non-extension members to the compound
final regularMembers = memberDeclarations.where((d) {
if (d is MethodDeclaration) return !d.isExtensionMember;
if (d is PropertyDeclaration) return !d.isExtensionMember;
if (d is InitializerDeclaration) return !d.isExtensionMember;
return true;
}).toList();

compound.methods.addAll(
memberDeclarations.removeWhereType<MethodDeclaration>().dedupeBy(
regularMembers.removeWhereType<MethodDeclaration>().dedupeBy(
(m) => m.fullName,
),
);
compound.properties.addAll(
memberDeclarations.removeWhereType<PropertyDeclaration>(),
regularMembers.removeWhereType<PropertyDeclaration>(),
);
compound.initializers.addAll(
memberDeclarations.removeWhereType<InitializerDeclaration>().dedupeBy(
regularMembers.removeWhereType<InitializerDeclaration>().dedupeBy(
(m) => m.fullName,
),
);
compound.nestedDeclarations.addAll(
memberDeclarations.removeWhereType<InnerNestableDeclaration>(),
regularMembers.removeWhereType<InnerNestableDeclaration>(),
);

compound.nestedDeclarations.fillNestingParents(compound);

return (compound: compound, excessMembers: memberDeclarations);
return (compound: compound, excessMembers: regularMembers);
}

ClassDeclaration parseClassDeclaration(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ MethodDeclaration parseMethodDeclaration(
async: info.async,
mutating: info.mutating,
isOperator: isOperator,
isExtensionMember: symbol.json['swiftExtension'].exists,
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ InitializerDeclaration parseInitializerDeclaration(
isFailable: parseIsFailableInit(id, declarationFragments),
throws: info.throws,
async: info.async,
isExtensionMember: symbol.json['swiftExtension'].exists,
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ PropertyDeclaration parsePropertyDeclaration(
lazy: info.lazy,
hasSetter: info.constant ? false : (info.getter ? info.setter : true),
hasExplicitGetter: info.getter,
isExtensionMember: symbol.json['swiftExtension'].exists,
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,17 @@ List<Declaration> parseDeclarations(
}
}

// Collect ExtensionDeclarations that were created during compound parsing
// and stored on their symbols.
final extensions = symbolgraph.symbols.values
.map((s) => s.extension)
.nonNulls
.toList();

declarations.addAll(extensions);
return declarations.topLevelOnly;
}

// TODO(https://github.com/dart-lang/native/issues/1815): Support for extensions
Declaration parseDeclaration(
Context context,
ParsedSymbol parsedSymbol,
Expand Down
Loading
Loading