diff --git a/pkgs/swift2objc/lib/src/ast/_core/interfaces/declaration.dart b/pkgs/swift2objc/lib/src/ast/_core/interfaces/declaration.dart index 4c237c8ba..34b356af4 100644 --- a/pkgs/swift2objc/lib/src/ast/_core/interfaces/declaration.dart +++ b/pkgs/swift2objc/lib/src/ast/_core/interfaces/declaration.dart @@ -2,10 +2,11 @@ // 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_node.dart'; import '../shared/referred_type.dart'; /// A common interface for all Swift entities declarations. -abstract interface class Declaration { +abstract interface class Declaration implements AstNode { abstract final String id; abstract final String name; } diff --git a/pkgs/swift2objc/lib/src/ast/_core/interfaces/nestable_declaration.dart b/pkgs/swift2objc/lib/src/ast/_core/interfaces/nestable_declaration.dart index b857a3b68..e38ae4a5f 100644 --- a/pkgs/swift2objc/lib/src/ast/_core/interfaces/nestable_declaration.dart +++ b/pkgs/swift2objc/lib/src/ast/_core/interfaces/nestable_declaration.dart @@ -2,10 +2,11 @@ // 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_node.dart'; import 'declaration.dart'; /// A Swift entity that can be nested inside other declarations. -abstract interface class NestableDeclaration implements Declaration { +abstract interface class NestableDeclaration implements Declaration, AstNode { abstract NestableDeclaration? nestingParent; abstract final List nestedDeclarations; } diff --git a/pkgs/swift2objc/lib/src/ast/_core/shared/parameter.dart b/pkgs/swift2objc/lib/src/ast/_core/shared/parameter.dart index 19b4421fc..768134b6f 100644 --- a/pkgs/swift2objc/lib/src/ast/_core/shared/parameter.dart +++ b/pkgs/swift2objc/lib/src/ast/_core/shared/parameter.dart @@ -2,10 +2,11 @@ // 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_node.dart'; import 'referred_type.dart'; /// Describes parameters of function-like entities (e.g methods). -class Parameter { +class Parameter extends AstNode { String name; String? internalName; ReferredType type; @@ -18,4 +19,10 @@ class Parameter { @override String toString() => '$name $internalName: $type'; + + @override + void visitChildren(Visitor visitor) { + super.visitChildren(visitor); + visitor.visit(type); + } } diff --git a/pkgs/swift2objc/lib/src/ast/_core/shared/referred_type.dart b/pkgs/swift2objc/lib/src/ast/_core/shared/referred_type.dart index 56f50b72d..9bbd4080b 100644 --- a/pkgs/swift2objc/lib/src/ast/_core/shared/referred_type.dart +++ b/pkgs/swift2objc/lib/src/ast/_core/shared/referred_type.dart @@ -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 '../../ast_node.dart'; import '../interfaces/declaration.dart'; import '../interfaces/nestable_declaration.dart'; import '../interfaces/objc_annotatable.dart'; @@ -9,7 +10,7 @@ import '../interfaces/objc_annotatable.dart'; /// Describes a type reference in declaration of Swift /// entities (e.g a method return type). /// See `DeclaredType` and `GenericType` for concrete implementation. -sealed class ReferredType { +sealed class ReferredType extends AstNode { abstract final bool isObjCRepresentable; abstract final String swiftType; @@ -17,10 +18,14 @@ sealed class ReferredType { bool sameAs(ReferredType other); const ReferredType(); + + @override + void visit(Visitation visitation) => visitation.visitReferredType(this); } /// Describes a reference of a declared type (user-defined or built-in). -class DeclaredType implements ReferredType { +class DeclaredType extends AstNode + implements ReferredType { final String id; String get name { @@ -52,11 +57,21 @@ class DeclaredType implements ReferredType { @override String toString() => name; + + @override + void visit(Visitation visitation) => visitation.visitDeclaredType(this); + + @override + void visitChildren(Visitor visitor) { + super.visitChildren(visitor); + visitor.visit(declaration); + visitor.visitAll(typeParams); + } } /// Describes a reference of a generic type /// (e.g a method return type `T` within a generic class). -class GenericType implements ReferredType { +class GenericType extends AstNode implements ReferredType { final String id; final String name; @@ -77,10 +92,13 @@ class GenericType implements ReferredType { @override String toString() => name; + + @override + void visit(Visitation visitation) => visitation.visitGenericType(this); } /// An optional type, like Dart's nullable types. Eg `String?`. -class OptionalType implements ReferredType { +class OptionalType extends AstNode implements ReferredType { final ReferredType child; @override @@ -97,4 +115,13 @@ class OptionalType implements ReferredType { @override String toString() => swiftType; + + @override + void visit(Visitation visitation) => visitation.visitOptionalType(this); + + @override + void visitChildren(Visitor visitor) { + super.visitChildren(visitor); + visitor.visit(child); + } } diff --git a/pkgs/swift2objc/lib/src/ast/ast_node.dart b/pkgs/swift2objc/lib/src/ast/ast_node.dart new file mode 100644 index 000000000..7e13c55af --- /dev/null +++ b/pkgs/swift2objc/lib/src/ast/ast_node.dart @@ -0,0 +1,104 @@ +// 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 'visitor.dart'; +export 'visitor.dart'; + +/// Base interface of all AST nodes, including types, bindings, and other +/// elements. +/// +/// Anything that we might want to visit should be an [AstNode]. Most things +/// that contain [AstNode]s should also be [AstNode]s (except for top level +/// objects like `Library` that are in charge of running the visitors, or +/// caching objects like `BindingsIndex` or `Writer`). +/// +/// The AST and visitor infrastucture is pretty complicated, so here are the +/// common tasks you might need to do as a maintainer: +/// +/// ## Creating a new type of [AstNode] +/// +/// For example, adding a class `Foo` which extends `Bar`, which somewhere up +/// the heirarchy extends [AstNode]: +/// +/// 1. Write your `Foo` class and extend `Bar`. +/// 2. If `Foo` has children (ie fields that are [AstNode]s), override the +/// `visitChildren` method. Make sure to call `super.visitChildren` +/// so that `Bar`'s children are also visited. If all `Foo`'s children +/// are inherited from `Bar`, it's not necessary to implement this method. +/// ```dart +/// @override +/// void visitChildren(Visitor visitor) { +/// super.visitChildren(visitor); +/// visitor.visit(myChild); +/// visitor.visitAll(myChildList); +/// } +/// ``` +/// +/// Since there are many AstNodes that no one is ever going to need to visit, +/// we're using a lazy-loading policy for the `visitFoo` method. When someone +/// wants to write a [Visitation] that is specific to `Foo` (ie not covered by +/// `visitBar`), that's when the `visitFoo` function will be added. +/// +/// The steps are essentially the same to change an existing class to extend +/// [AstNode]. +/// +/// ## Creating a new [Visitation] +/// +/// 1. Write the visitation as a class that extends [Visitation]. The class may +/// be stateful, but shouldn't care about the order that nodes are visited. +/// This class may mutate the nodes, but shouldn't assume that all children +/// have finished being visited when processing the parent. +/// 2. Override the visit methods for whichever type you're interested in. +/// ```dart +/// @override +/// void visitFoo(Foo node) { +/// // Typically this method would visit the children, but that's not always +/// // what you want to do. +/// node.visitChildren(this); +/// +/// // It's not necessarily true that all the children have finished being +/// // visited at this point. +/// +/// // The rest of this method can edit the node in-place. +/// } +/// ``` +/// 3. If you want to visit `Foo` specifically (and not its supertypes), but +/// there's no `visitFoo` method to override, add a `visitFoo` method to +/// [Visitation]. Assuming `Foo` extends `Bar`, `visitFoo` should delegate to +/// `visitBar`: +/// `void visitFoo(Foo node) => visitBar(node);` +/// 4. Then add a `visit` method to `Foo` that invokes `visitFoo`: +/// ```dart +/// @override +/// void visit(Visitation visitation) => visitation.visitFoo(this); +/// ``` +/// 5. Repeat 3 and 4 for `Bar` and any other supertypes as necessary. +/// +/// ## Running a [Visitation] +/// +/// 1. Construct the [Visitation] and wrap it in a [Visitor]: +/// `final visitor = Visitor(MyFancyVisitation(1, 2, 3));` +/// 2. Invoke the [Visitor] on the root nodes of the AST: +/// ```dart +/// visitor.visit(someRootNode); +/// visitor.visitAll(listOfRootNodes); +/// ``` +abstract class AstNode { + const AstNode(); + + /// Visit this node. All this method does is delegate to the visit method that + /// is specific to this type of node. + /// + /// This method should be implemented for a particular type of [AstNode] only + /// when there are [Visitation]s that need to visit that type specifically. + void visit(Visitation visitation) => visitation.visitAstNode(this); + + /// Visit this node's children. This is the method that actually understands + /// the structure of the node. It should invoke [Visitor.visit] etc on all the + /// node's children. + /// + /// This method must be implemented if the node has children. The + /// implementation should call `super.visitChildren(visitor);`. + void visitChildren(Visitor visitor) {} +} diff --git a/pkgs/swift2objc/lib/src/ast/declarations/built_in/built_in_declaration.dart b/pkgs/swift2objc/lib/src/ast/declarations/built_in/built_in_declaration.dart index 9c75ba42f..cfee5d6a0 100644 --- a/pkgs/swift2objc/lib/src/ast/declarations/built_in/built_in_declaration.dart +++ b/pkgs/swift2objc/lib/src/ast/declarations/built_in/built_in_declaration.dart @@ -4,17 +4,11 @@ import '../../_core/interfaces/declaration.dart'; import '../../_core/interfaces/objc_annotatable.dart'; +import '../../ast_node.dart'; /// Describes a built-in Swift type (e.g Int, String, etc). -enum BuiltInDeclaration implements Declaration, ObjCAnnotatable { - swiftNSObject(id: 'c:objc(cs)NSObject', name: 'NSObject'), - swiftString(id: 's:SS', name: 'String'), - swiftInt(id: 's:Si', name: 'Int'), - swiftFloat(id: 's:Sf', name: 'Float'), - swiftDouble(id: 's:Sd', name: 'Double'), - swiftBool(id: 's:Sb', name: 'Bool'), - swiftVoid(id: 's:s4Voida', name: 'Void'); - +class BuiltInDeclaration extends AstNode + implements Declaration, ObjCAnnotatable { @override final String id; @@ -28,12 +22,34 @@ enum BuiltInDeclaration implements Declaration, ObjCAnnotatable { required this.id, required this.name, }); + + @override + void visit(Visitation visitation) => visitation.visitBuiltInDeclaration(this); } -final objectType = BuiltInDeclaration.swiftNSObject.asDeclaredType; -final stringType = BuiltInDeclaration.swiftString.asDeclaredType; -final intType = BuiltInDeclaration.swiftInt.asDeclaredType; -final floatType = BuiltInDeclaration.swiftFloat.asDeclaredType; -final doubleType = BuiltInDeclaration.swiftDouble.asDeclaredType; -final boolType = BuiltInDeclaration.swiftBool.asDeclaredType; -final voidType = BuiltInDeclaration.swiftVoid.asDeclaredType; +const _objectDecl = + BuiltInDeclaration(id: 'c:objc(cs)NSObject', name: 'NSObject'); +const _stringDecl = BuiltInDeclaration(id: 's:SS', name: 'String'); +const _intDecl = BuiltInDeclaration(id: 's:Si', name: 'Int'); +const _floatDecl = BuiltInDeclaration(id: 's:Sf', name: 'Float'); +const _doubleDecl = BuiltInDeclaration(id: 's:Sd', name: 'Double'); +const _boolDecl = BuiltInDeclaration(id: 's:Sb', name: 'Bool'); +const _voidDecl = BuiltInDeclaration(id: 's:s4Voida', name: 'Void'); + +const builtInDeclarations = [ + _objectDecl, + _stringDecl, + _intDecl, + _floatDecl, + _doubleDecl, + _boolDecl, + _voidDecl, +]; + +final objectType = _objectDecl.asDeclaredType; +final stringType = _stringDecl.asDeclaredType; +final intType = _intDecl.asDeclaredType; +final floatType = _floatDecl.asDeclaredType; +final doubleType = _doubleDecl.asDeclaredType; +final boolType = _boolDecl.asDeclaredType; +final voidType = _voidDecl.asDeclaredType; diff --git a/pkgs/swift2objc/lib/src/ast/declarations/compounds/class_declaration.dart b/pkgs/swift2objc/lib/src/ast/declarations/compounds/class_declaration.dart index 9bf875b91..42dedb96e 100644 --- a/pkgs/swift2objc/lib/src/ast/declarations/compounds/class_declaration.dart +++ b/pkgs/swift2objc/lib/src/ast/declarations/compounds/class_declaration.dart @@ -7,6 +7,7 @@ import '../../_core/interfaces/declaration.dart'; import '../../_core/interfaces/nestable_declaration.dart'; import '../../_core/interfaces/objc_annotatable.dart'; import '../../_core/shared/referred_type.dart'; +import '../../ast_node.dart'; import '../built_in/built_in_declaration.dart'; import 'members/initializer_declaration.dart'; import 'members/method_declaration.dart'; @@ -14,7 +15,8 @@ import 'members/property_declaration.dart'; import 'protocol_declaration.dart'; /// Describes the declaration of a Swift class. -class ClassDeclaration implements CompoundDeclaration, ObjCAnnotatable { +class ClassDeclaration extends AstNode + implements CompoundDeclaration, ObjCAnnotatable { @override String id; @@ -75,5 +77,23 @@ class ClassDeclaration implements CompoundDeclaration, ObjCAnnotatable { this.initializers = const [], }) : assert(superClass == null || superClass.declaration is ClassDeclaration || - superClass.declaration == BuiltInDeclaration.swiftNSObject); + superClass.sameAs(objectType)); + + @override + void visit(Visitation visitation) => visitation.visitClassDeclaration(this); + + @override + void visitChildren(Visitor visitor) { + super.visitChildren(visitor); + visitor.visitAll(properties); + visitor.visitAll(methods); + visitor.visitAll(conformedProtocols); + visitor.visitAll(typeParams); + visitor.visit(superClass); + visitor.visit(wrappedInstance); + visitor.visit(wrapperInitializer); + visitor.visitAll(initializers); + visitor.visit(nestingParent); + visitor.visitAll(nestedDeclarations); + } } diff --git a/pkgs/swift2objc/lib/src/ast/declarations/compounds/members/initializer_declaration.dart b/pkgs/swift2objc/lib/src/ast/declarations/compounds/members/initializer_declaration.dart index d7c5431b3..ce4bf5685 100644 --- a/pkgs/swift2objc/lib/src/ast/declarations/compounds/members/initializer_declaration.dart +++ b/pkgs/swift2objc/lib/src/ast/declarations/compounds/members/initializer_declaration.dart @@ -10,9 +10,10 @@ import '../../../_core/interfaces/objc_annotatable.dart'; import '../../../_core/interfaces/overridable.dart'; import '../../../_core/interfaces/parameterizable.dart'; import '../../../_core/shared/parameter.dart'; +import '../../../ast_node.dart'; /// Describes an initializer for a Swift compound entity (e.g, class, structs) -class InitializerDeclaration +class InitializerDeclaration extends AstNode implements Declaration, Executable, @@ -62,4 +63,14 @@ class InitializerDeclaration required this.async, required this.isFailable, }); + + @override + void visit(Visitation visitation) => + visitation.visitInitializerDeclaration(this); + + @override + void visitChildren(Visitor visitor) { + super.visitChildren(visitor); + visitor.visitAll(params); + } } diff --git a/pkgs/swift2objc/lib/src/ast/declarations/compounds/members/method_declaration.dart b/pkgs/swift2objc/lib/src/ast/declarations/compounds/members/method_declaration.dart index c7567fe9c..604b31350 100644 --- a/pkgs/swift2objc/lib/src/ast/declarations/compounds/members/method_declaration.dart +++ b/pkgs/swift2objc/lib/src/ast/declarations/compounds/members/method_declaration.dart @@ -7,10 +7,11 @@ import '../../../_core/interfaces/objc_annotatable.dart'; import '../../../_core/interfaces/overridable.dart'; import '../../../_core/shared/parameter.dart'; import '../../../_core/shared/referred_type.dart'; +import '../../../ast_node.dart'; /// Describes a method declaration for a Swift compound entity /// (e.g, class, structs) -class MethodDeclaration +class MethodDeclaration extends AstNode implements FunctionDeclaration, ObjCAnnotatable, Overridable { @override String id; @@ -62,4 +63,15 @@ class MethodDeclaration this.throws = false, this.async = false, }) : assert(!isStatic || !isOverriding); + + @override + void visit(Visitation visitation) => visitation.visitMethodDeclaration(this); + + @override + void visitChildren(Visitor visitor) { + super.visitChildren(visitor); + visitor.visitAll(params); + visitor.visitAll(typeParams); + visitor.visit(returnType); + } } diff --git a/pkgs/swift2objc/lib/src/ast/declarations/compounds/members/property_declaration.dart b/pkgs/swift2objc/lib/src/ast/declarations/compounds/members/property_declaration.dart index 6bcced2d9..297cdb8b5 100644 --- a/pkgs/swift2objc/lib/src/ast/declarations/compounds/members/property_declaration.dart +++ b/pkgs/swift2objc/lib/src/ast/declarations/compounds/members/property_declaration.dart @@ -6,10 +6,12 @@ import '../../../_core/interfaces/executable.dart'; import '../../../_core/interfaces/objc_annotatable.dart'; import '../../../_core/interfaces/variable_declaration.dart'; import '../../../_core/shared/referred_type.dart'; +import '../../../ast_node.dart'; /// Describes a property declaration for a Swift compound entity /// (e.g, class, structs) -class PropertyDeclaration implements VariableDeclaration, ObjCAnnotatable { +class PropertyDeclaration extends AstNode + implements VariableDeclaration, ObjCAnnotatable { @override String id; @@ -52,6 +54,16 @@ class PropertyDeclaration implements VariableDeclaration, ObjCAnnotatable { this.async = false, }) : assert(!(isConstant && hasSetter)), assert(!(hasSetter && throws)); + + @override + void visit(Visitation visitation) => + visitation.visitPropertyDeclaration(this); + + @override + void visitChildren(Visitor visitor) { + super.visitChildren(visitor); + visitor.visit(type); + } } class PropertyStatements implements Executable { diff --git a/pkgs/swift2objc/lib/src/ast/declarations/compounds/protocol_declaration.dart b/pkgs/swift2objc/lib/src/ast/declarations/compounds/protocol_declaration.dart index 197b5ee6d..5ae946781 100644 --- a/pkgs/swift2objc/lib/src/ast/declarations/compounds/protocol_declaration.dart +++ b/pkgs/swift2objc/lib/src/ast/declarations/compounds/protocol_declaration.dart @@ -5,12 +5,13 @@ import '../../_core/interfaces/compound_declaration.dart'; import '../../_core/interfaces/nestable_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'; /// Describes the declaration of a Swift protocol. -class ProtocolDeclaration implements CompoundDeclaration { +class ProtocolDeclaration extends AstNode implements CompoundDeclaration { @override String id; @@ -49,4 +50,20 @@ class ProtocolDeclaration implements CompoundDeclaration { this.nestingParent, this.nestedDeclarations = const [], }); + + @override + void visit(Visitation visitation) => + visitation.visitProtocolDeclaration(this); + + @override + void visitChildren(Visitor visitor) { + super.visitChildren(visitor); + visitor.visitAll(properties); + visitor.visitAll(methods); + visitor.visitAll(conformedProtocols); + visitor.visitAll(typeParams); + visitor.visitAll(initializers); + visitor.visit(nestingParent); + visitor.visitAll(nestedDeclarations); + } } diff --git a/pkgs/swift2objc/lib/src/ast/declarations/compounds/struct_declaration.dart b/pkgs/swift2objc/lib/src/ast/declarations/compounds/struct_declaration.dart index ba157ff29..8dc19cbae 100644 --- a/pkgs/swift2objc/lib/src/ast/declarations/compounds/struct_declaration.dart +++ b/pkgs/swift2objc/lib/src/ast/declarations/compounds/struct_declaration.dart @@ -5,13 +5,14 @@ import '../../_core/interfaces/compound_declaration.dart'; import '../../_core/interfaces/nestable_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'; import 'protocol_declaration.dart'; /// Describes the declaration of a Swift struct. -class StructDeclaration implements CompoundDeclaration { +class StructDeclaration extends AstNode implements CompoundDeclaration { @override String id; @@ -50,4 +51,19 @@ class StructDeclaration implements CompoundDeclaration { this.nestingParent, this.nestedDeclarations = const [], }); + + @override + void visit(Visitation visitation) => visitation.visitStructDeclaration(this); + + @override + void visitChildren(Visitor visitor) { + super.visitChildren(visitor); + visitor.visitAll(properties); + visitor.visitAll(methods); + visitor.visitAll(conformedProtocols); + visitor.visitAll(typeParams); + visitor.visitAll(initializers); + visitor.visit(nestingParent); + visitor.visitAll(nestedDeclarations); + } } diff --git a/pkgs/swift2objc/lib/src/ast/declarations/enums/associated_value_enum_declaration.dart b/pkgs/swift2objc/lib/src/ast/declarations/enums/associated_value_enum_declaration.dart index d8f374354..b27eca124 100644 --- a/pkgs/swift2objc/lib/src/ast/declarations/enums/associated_value_enum_declaration.dart +++ b/pkgs/swift2objc/lib/src/ast/declarations/enums/associated_value_enum_declaration.dart @@ -7,10 +7,12 @@ import '../../_core/interfaces/nestable_declaration.dart'; import '../../_core/interfaces/parameterizable.dart'; import '../../_core/shared/parameter.dart'; import '../../_core/shared/referred_type.dart'; +import '../../ast_node.dart'; import '../compounds/protocol_declaration.dart'; /// Describes the declaration of a Swift enum with associated values. -class AssociatedValueEnumDeclaration implements EnumDeclaration { +class AssociatedValueEnumDeclaration extends AstNode + implements EnumDeclaration { @override String id; @@ -41,10 +43,25 @@ class AssociatedValueEnumDeclaration implements EnumDeclaration { this.nestingParent, this.nestedDeclarations = const [], }); + + @override + void visit(Visitation visitation) => + visitation.visitAssociatedValueEnumDeclaration(this); + + @override + void visitChildren(Visitor visitor) { + super.visitChildren(visitor); + visitor.visitAll(cases); + visitor.visitAll(typeParams); + visitor.visitAll(conformedProtocols); + visitor.visit(nestingParent); + visitor.visitAll(nestedDeclarations); + } } /// Describes the declaration of a Swift enum case with associated values. -class AssociatedValueEnumCase implements EnumCase, Parameterizable { +class AssociatedValueEnumCase extends AstNode + implements EnumCase, Parameterizable { @override String id; @@ -59,10 +76,16 @@ class AssociatedValueEnumCase implements EnumCase, Parameterizable { required this.name, required this.params, }); + + @override + void visitChildren(Visitor visitor) { + super.visitChildren(visitor); + visitor.visitAll(params); + } } /// Describes an associated value of an Swift enum case. -class AssociatedValueParam implements Parameter { +class AssociatedValueParam extends AstNode implements Parameter { @override String name; @@ -76,4 +99,10 @@ class AssociatedValueParam implements Parameter { required this.name, required this.type, }); + + @override + void visitChildren(Visitor visitor) { + super.visitChildren(visitor); + visitor.visit(type); + } } diff --git a/pkgs/swift2objc/lib/src/ast/declarations/enums/normal_enum_declaration.dart b/pkgs/swift2objc/lib/src/ast/declarations/enums/normal_enum_declaration.dart index cd4e05b70..c021c1883 100644 --- a/pkgs/swift2objc/lib/src/ast/declarations/enums/normal_enum_declaration.dart +++ b/pkgs/swift2objc/lib/src/ast/declarations/enums/normal_enum_declaration.dart @@ -5,11 +5,12 @@ import '../../_core/interfaces/enum_declaration.dart'; import '../../_core/interfaces/nestable_declaration.dart'; import '../../_core/shared/referred_type.dart'; +import '../../ast_node.dart'; import '../compounds/protocol_declaration.dart'; /// Describes the declaration of a basic Swift enum /// (i.e with no raw values or associated values). -class NormalEnumDeclaration implements EnumDeclaration { +class NormalEnumDeclaration extends AstNode implements EnumDeclaration { @override String id; @@ -40,11 +41,25 @@ class NormalEnumDeclaration implements EnumDeclaration { this.nestingParent, this.nestedDeclarations = const [], }); + + @override + void visit(Visitation visitation) => + visitation.visitNormalEnumDeclaration(this); + + @override + void visitChildren(Visitor visitor) { + super.visitChildren(visitor); + visitor.visitAll(cases); + visitor.visitAll(typeParams); + visitor.visitAll(conformedProtocols); + visitor.visit(nestingParent); + visitor.visitAll(nestedDeclarations); + } } /// Describes the declaration of a basic Swift enum case /// (i.e with no raw values or associated values). -class NormalEnumCase implements EnumCase { +class NormalEnumCase extends AstNode implements EnumCase { @override String id; diff --git a/pkgs/swift2objc/lib/src/ast/declarations/enums/raw_value_enum_declaration.dart b/pkgs/swift2objc/lib/src/ast/declarations/enums/raw_value_enum_declaration.dart index 7774a02d8..b52494b25 100644 --- a/pkgs/swift2objc/lib/src/ast/declarations/enums/raw_value_enum_declaration.dart +++ b/pkgs/swift2objc/lib/src/ast/declarations/enums/raw_value_enum_declaration.dart @@ -6,10 +6,12 @@ import '../../_core/interfaces/enum_declaration.dart'; import '../../_core/interfaces/nestable_declaration.dart'; import '../../_core/interfaces/objc_annotatable.dart'; import '../../_core/shared/referred_type.dart'; +import '../../ast_node.dart'; import '../compounds/protocol_declaration.dart'; /// Describes the declaration of a Swift enum with raw values. -class RawValueEnumDeclaration implements EnumDeclaration, ObjCAnnotatable { +class RawValueEnumDeclaration extends AstNode + implements EnumDeclaration, ObjCAnnotatable { @override String id; @@ -47,10 +49,25 @@ class RawValueEnumDeclaration implements EnumDeclaration, ObjCAnnotatable { this.nestingParent, this.nestedDeclarations = const [], }); + + @override + void visit(Visitation visitation) => + visitation.visitRawValueEnumDeclaration(this); + + @override + void visitChildren(Visitor visitor) { + super.visitChildren(visitor); + visitor.visitAll(cases); + visitor.visitAll(typeParams); + visitor.visitAll(conformedProtocols); + visitor.visit(nestingParent); + visitor.visitAll(nestedDeclarations); + visitor.visit(rawValueType); + } } /// Describes the declaration of a Swift enum case with a raw value of type `T`. -class RawValueEnumCase implements EnumCase { +class RawValueEnumCase extends AstNode implements EnumCase { @override String id; diff --git a/pkgs/swift2objc/lib/src/ast/declarations/globals/globals.dart b/pkgs/swift2objc/lib/src/ast/declarations/globals/globals.dart index c06cc5077..e8497bbd9 100644 --- a/pkgs/swift2objc/lib/src/ast/declarations/globals/globals.dart +++ b/pkgs/swift2objc/lib/src/ast/declarations/globals/globals.dart @@ -6,6 +6,7 @@ import '../../_core/interfaces/function_declaration.dart'; import '../../_core/interfaces/variable_declaration.dart'; import '../../_core/shared/parameter.dart'; import '../../_core/shared/referred_type.dart'; +import '../../ast_node.dart'; /// A container for globally defined values (i.e variables & constants) /// and functions. @@ -20,7 +21,7 @@ class Globals { } /// Describes a globally defined function. -class GlobalFunctionDeclaration implements FunctionDeclaration { +class GlobalFunctionDeclaration extends AstNode implements FunctionDeclaration { @override String id; @@ -55,10 +56,18 @@ class GlobalFunctionDeclaration implements FunctionDeclaration { this.throws = false, this.async = false, }); + + @override + void visitChildren(Visitor visitor) { + super.visitChildren(visitor); + visitor.visitAll(params); + visitor.visitAll(typeParams); + visitor.visit(returnType); + } } /// Describes a globally defined values (i.e variable/constant). -class GlobalVariableDeclaration implements VariableDeclaration { +class GlobalVariableDeclaration extends AstNode implements VariableDeclaration { @override String id; @@ -85,4 +94,10 @@ class GlobalVariableDeclaration implements VariableDeclaration { required this.throws, required this.async, }) : assert(!(throws && !isConstant)); + + @override + void visitChildren(Visitor visitor) { + super.visitChildren(visitor); + visitor.visit(type); + } } diff --git a/pkgs/swift2objc/lib/src/ast/visitor.dart b/pkgs/swift2objc/lib/src/ast/visitor.dart new file mode 100644 index 000000000..5c0e0f258 --- /dev/null +++ b/pkgs/swift2objc/lib/src/ast/visitor.dart @@ -0,0 +1,121 @@ +// 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 'package:logging/logging.dart'; + +import '../ast/_core/interfaces/compound_declaration.dart'; +import '../ast/_core/interfaces/declaration.dart'; +import '../ast/_core/interfaces/enum_declaration.dart'; +import '../ast/_core/interfaces/function_declaration.dart'; +import '../ast/_core/interfaces/variable_declaration.dart'; +import '../ast/_core/shared/referred_type.dart'; +import '../ast/ast_node.dart'; +import '../ast/declarations/built_in/built_in_declaration.dart'; +import '../ast/declarations/compounds/class_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'; +import '../ast/declarations/compounds/protocol_declaration.dart'; +import '../ast/declarations/compounds/struct_declaration.dart'; +import '../ast/declarations/enums/associated_value_enum_declaration.dart'; +import '../ast/declarations/enums/normal_enum_declaration.dart'; +import '../ast/declarations/enums/raw_value_enum_declaration.dart'; +import '../ast/declarations/globals/globals.dart'; + +final _logger = Logger('swift2objc.visitor'); + +/// Wrapper around [Visitation] to be used by callers. +final class Visitor { + Visitor(this._visitation, {bool debug = false}) : _debug = debug { + _visitation.visitor = this; + } + + final Visitation _visitation; + final _seen = {}; + final bool _debug; + int _indentLevel = 0; + + /// Visits a node. + void visit(AstNode? node) { + if (node == null) return; + if (_debug) _logger.info('${' ' * _indentLevel++}$node'); + if (!_seen.contains(node)) { + _seen.add(node); + node.visit(_visitation); + } + if (_debug) --_indentLevel; + } + + /// Helper method for visiting an iterable of nodes. + void visitAll(Iterable nodes) { + for (final node in nodes) { + visit(node); + } + } +} + +/// Base class for all AST visitations. +/// +/// Callers should wrap their [Visitation] in a [Visitor] and invoke its +/// methods, instead of interacting directly with the [Visitation]. This +/// distinction is why [Visitor] and [Visitation] are seperate classes. +/// +/// The `visitFoo` methods in this class should reflect the inheritance +/// heirarchy of all AstNodes. Eg `visitChild` should default to calling +/// `visitBase` which should default to calling `visitAstNode`. +/// +/// Implementers should implement the specific visit methods for each node type +/// they care about. Any visit methods not implemented will default to calling +/// `node.visitChildren`, which visits the node's children but otherwise doesn't +/// alter the node itself. +abstract class Visitation { + late final Visitor visitor; + + void visitReferredType(ReferredType node) => visitAstNode(node); + void visitDeclaredType(DeclaredType node) => visitReferredType(node); + void visitGenericType(GenericType node) => visitReferredType(node); + void visitOptionalType(OptionalType node) => visitReferredType(node); + void visitDeclaration(Declaration node) => visitAstNode(node); + void visitBuiltInDeclaration(BuiltInDeclaration node) => + visitDeclaration(node); + void visitInitializerDeclaration(InitializerDeclaration node) => + visitDeclaration(node); + void visitFunctionDeclaration(FunctionDeclaration node) => + visitDeclaration(node); + void visitMethodDeclaration(MethodDeclaration node) => + visitFunctionDeclaration(node); + void visitGlobalFunctionDeclaration(GlobalFunctionDeclaration node) => + visitFunctionDeclaration(node); + void visitVariableDeclaration(VariableDeclaration node) => + visitDeclaration(node); + void visitPropertyDeclaration(PropertyDeclaration node) => + visitVariableDeclaration(node); + void visitGlobalVariableDeclaration(GlobalVariableDeclaration node) => + visitVariableDeclaration(node); + void visitCompoundDeclaration(CompoundDeclaration node) => + visitDeclaration(node); + void visitClassDeclaration(ClassDeclaration node) => + visitCompoundDeclaration(node); + void visitProtocolDeclaration(ProtocolDeclaration node) => + visitCompoundDeclaration(node); + void visitStructDeclaration(StructDeclaration node) => + visitCompoundDeclaration(node); + void visitEnumDeclaration(EnumDeclaration node) => visitDeclaration(node); + void visitAssociatedValueEnumDeclaration( + AssociatedValueEnumDeclaration node) => + visitEnumDeclaration(node); + void visitNormalEnumDeclaration(NormalEnumDeclaration node) => + visitEnumDeclaration(node); + void visitRawValueEnumDeclaration(RawValueEnumDeclaration node) => + visitEnumDeclaration(node); + + /// Default behavior for all visit methods. + void visitAstNode(AstNode node) => node..visitChildren(visitor); +} + +T visit(T visitation, Iterable roots, + {bool debug = false}) { + Visitor(visitation, debug: debug).visitAll(roots); + return visitation; +} diff --git a/pkgs/swift2objc/lib/src/parser/parsers/parse_declarations.dart b/pkgs/swift2objc/lib/src/parser/parsers/parse_declarations.dart index 36b74c4f4..af3f8e443 100644 --- a/pkgs/swift2objc/lib/src/parser/parsers/parse_declarations.dart +++ b/pkgs/swift2objc/lib/src/parser/parsers/parse_declarations.dart @@ -59,7 +59,7 @@ Declaration parseDeclaration( _ => throw Exception( 'Symbol of type $symbolType is not implemented yet.', ), - } as Declaration; + }; return parsedSymbol.declaration!; } diff --git a/pkgs/swift2objc/lib/src/parser/parsers/parse_symbols_map.dart b/pkgs/swift2objc/lib/src/parser/parsers/parse_symbols_map.dart index 72df7de07..e98bdc68b 100644 --- a/pkgs/swift2objc/lib/src/parser/parsers/parse_symbols_map.dart +++ b/pkgs/swift2objc/lib/src/parser/parsers/parse_symbols_map.dart @@ -9,7 +9,7 @@ import '../_core/utils.dart'; ParsedSymbolsMap parseSymbolsMap(Json symbolgraphJson) { final parsedSymbols = { - for (final decl in BuiltInDeclaration.values) + for (final decl in builtInDeclarations) decl.id: ParsedSymbol(json: Json(null), declaration: decl) }; diff --git a/pkgs/swift2objc/lib/src/transformer/_core/dependencies.dart b/pkgs/swift2objc/lib/src/transformer/_core/dependencies.dart index 30e693d88..032367ac8 100644 --- a/pkgs/swift2objc/lib/src/transformer/_core/dependencies.dart +++ b/pkgs/swift2objc/lib/src/transformer/_core/dependencies.dart @@ -1,259 +1,32 @@ -import '../../ast/_core/interfaces/declaration.dart'; -import '../../ast/_core/interfaces/enum_declaration.dart'; -import '../../ast/_core/interfaces/function_declaration.dart'; -import '../../ast/_core/interfaces/variable_declaration.dart'; -import '../../ast/_core/shared/parameter.dart'; -import '../../ast/_core/shared/referred_type.dart'; -import '../../ast/declarations/compounds/class_declaration.dart'; -import '../../ast/declarations/compounds/members/initializer_declaration.dart'; -import '../../ast/declarations/compounds/protocol_declaration.dart'; -import '../../ast/declarations/compounds/struct_declaration.dart'; - -// TODO(https://github.com/dart-lang/native/issues/1814): Type restrictions have not yet been implemented in system -class DependencyVisitor { - final Iterable declarations; - Set visitedDeclarations = {}; - - DependencyVisitor(this.declarations); - - Set visit(Declaration dec) { - final dependencies = {}; - - Iterable d = [dec]; - - while (true) { - final deps = d.fold>( - {}, (previous, element) => previous.union(visitDeclaration(element))); - final depDecls = declarations.where((d) => deps.contains(d.id)); - if (depDecls.isEmpty || - (dependencies.union(depDecls.toSet()).length) == - dependencies.length) { - break; - } else { - dependencies.addAll(depDecls); - d = depDecls; - } - } - - visitedDeclarations.addAll(dependencies); - - return dependencies; - } - - Set visitDeclaration(Declaration decl, [Set? context]) { - final cont = context ??= {}; - - // switch between declarations - if (decl is ClassDeclaration) { - visitClass(decl, cont); - } else if (decl is ProtocolDeclaration) { - visitProtocol(decl, cont); - } else if (decl is StructDeclaration) { - visitStruct(decl, cont); - } else if (decl is FunctionDeclaration) { - visitFunction(decl, cont); - } else if (decl is VariableDeclaration) { - visitVariable(decl, cont); - } else if (decl is EnumDeclaration) { - visitEnum(decl, cont); - } - - return cont; - } - - Set visitEnum(EnumDeclaration decl, [Set? context]) { - final cont = context ??= {}; - - // visit nested declarations - for (var n in decl.nestedDeclarations) { - visitDeclaration(n, cont); - } - - // visit protocols - for (var p in decl.conformedProtocols) { - visitProtocol(p.declaration, cont); - } - - // ensure generic types do not enter - cont.removeWhere( - (t) => decl.typeParams.map((type) => type.name).contains(t)); - - return cont; - } - - Set visitStruct(StructDeclaration decl, [Set? context]) { - final cont = context ??= {}; - - // visit variables - for (var d in decl.properties) { - visitVariable(d, cont); - } - - // visit methods - for (var m in decl.methods) { - visitFunction(m, cont); - } - - // visit initializers - for (var i in decl.initializers) { - visitInitializer(i, cont); - } - - // visit nested declarations - for (var n in decl.nestedDeclarations) { - visitDeclaration(n, cont); - } - - // visit protocols - for (var p in decl.conformedProtocols) { - visitProtocol(p.declaration, cont); - } - - // ensure generic types do not enter - cont.removeWhere( - (t) => decl.typeParams.map((type) => type.name).contains(t)); - - return cont; - } - - Set visitClass(ClassDeclaration decl, [Set? context]) { - final cont = context ??= {}; - - // visit variables - for (var d in decl.properties) { - visitVariable(d, cont); - } +// 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. - // visit methods - for (var m in decl.methods) { - visitFunction(m, cont); - } - - // visit initializers - for (var i in decl.initializers) { - visitInitializer(i, cont); - } - - // visit super if any - if (decl.superClass != null) { - visitDeclaration(decl.superClass!.declaration, cont); - } - - // visit nested declarations - for (var n in decl.nestedDeclarations) { - visitDeclaration(n, cont); - } - - // visit protocols - for (var p in decl.conformedProtocols) { - visitProtocol(p.declaration, cont); - } - - // ensure generic types do not enter - cont.removeWhere( - (t) => decl.typeParams.map((type) => type.name).contains(t)); - - return cont; - } - - Set visitProtocol(ProtocolDeclaration decl, [Set? context]) { - final cont = context ??= {}; - - // visit variables - for (var d in decl.properties) { - visitVariable(d, cont); - } - - // visit methods - for (var m in decl.methods) { - visitFunction(m, cont); - } - - // visit initializers - for (var i in decl.initializers) { - visitInitializer(i, cont); - } - - // visit nested declarations - for (var n in decl.nestedDeclarations) { - visitDeclaration(n, cont); - } - - // visit protocols - for (var p in decl.conformedProtocols) { - visitProtocol(p.declaration, cont); - } - - // ensure generic types do not enter - cont.removeWhere( - (t) => decl.typeParams.map((type) => type.name).contains(t)); - - return cont; - } - - Set visitInitializer(InitializerDeclaration decl, - [Set? context]) { - final cont = context ??= {}; - - // similar to `visitMethod`, except no return type - for (var p in decl.params) { - visitParameter(p, cont); - } - - return cont; - } - - Set visitFunction(FunctionDeclaration decl, [Set? context]) { - final cont = context ??= {}; - - // visit parameters - for (var p in decl.params) { - visitParameter(p, cont); - } - - // ensure generic types do not enter - cont.removeWhere( - (t) => decl.typeParams.map((type) => type.name).contains(t)); - - // visit return type - visitType(decl.returnType, cont); - - return cont; - } - - Set visitParameter(Parameter decl, [Set? context]) { - final cont = context ??= {}; +import '../../ast/_core/interfaces/compound_declaration.dart'; +import '../../ast/_core/interfaces/declaration.dart'; +import '../../ast/declarations/globals/globals.dart'; +import '../../ast/visitor.dart'; - // just visit type of parameter - visitType(decl.type, cont); +class DependencyVisitation extends Visitation { + Set topLevelDeclarations = {}; - return cont; + @override + void visitGlobalFunctionDeclaration(GlobalFunctionDeclaration node) { + node.visitChildren(visitor); + topLevelDeclarations.add(node); } - Set visitVariable(VariableDeclaration decl, [Set? context]) { - final cont = context ??= {}; - - // just return property type - visitType(decl.type, cont); - - return cont; + @override + void visitGlobalVariableDeclaration(GlobalVariableDeclaration node) { + node.visitChildren(visitor); + topLevelDeclarations.add(node); } - Set visitType(ReferredType type, [Set? context]) { - final cont = context ??= {}; + @override + void visitCompoundDeclaration(CompoundDeclaration node) { + node.visitChildren(visitor); - // we need to confirm the types located - // check what kind of type [type] is - switch (type) { - case DeclaredType(): - cont.add(type.id); - break; - case GenericType(): - // do nothing - break; - case OptionalType(): - visitType(type.child, cont); - } - return cont; + // Don't add nested classes etc to the top level declarations. + if (node.nestingParent == null) topLevelDeclarations.add(node); } } diff --git a/pkgs/swift2objc/lib/src/transformer/transform.dart b/pkgs/swift2objc/lib/src/transformer/transform.dart index 750d2efcd..409252c52 100644 --- a/pkgs/swift2objc/lib/src/transformer/transform.dart +++ b/pkgs/swift2objc/lib/src/transformer/transform.dart @@ -8,6 +8,7 @@ import '../ast/_core/interfaces/nestable_declaration.dart'; import '../ast/declarations/compounds/class_declaration.dart'; import '../ast/declarations/compounds/struct_declaration.dart'; import '../ast/declarations/globals/globals.dart'; +import '../ast/visitor.dart'; import '_core/dependencies.dart'; import '_core/unique_namer.dart'; import 'transformers/transform_compound.dart'; @@ -15,15 +16,8 @@ import 'transformers/transform_globals.dart'; typedef TransformationMap = Map; -Set generateDependencies( - Iterable decls, Iterable allDecls) { - final visitor = DependencyVisitor(allDecls); - for (final dec in decls) { - visitor.visit(dec); - } - - return visitor.visitedDeclarations; -} +Set generateDependencies(Iterable decls) => + visit(DependencyVisitation(), decls).topLevelDeclarations; /// Transforms the given declarations into the desired ObjC wrapped declarations List transform(List declarations, @@ -31,7 +25,7 @@ List transform(List declarations, final transformationMap = {}; final declarations0 = declarations.where(filter).toSet(); - declarations0.addAll(generateDependencies(declarations0, declarations)); + declarations0.addAll(generateDependencies(declarations0)); final globalNamer = UniqueNamer( declarations0.map((declaration) => declaration.name), diff --git a/pkgs/swift2objc/lib/src/transformer/transformers/transform_compound.dart b/pkgs/swift2objc/lib/src/transformer/transformers/transform_compound.dart index cd06487a0..3e079f0f2 100644 --- a/pkgs/swift2objc/lib/src/transformer/transformers/transform_compound.dart +++ b/pkgs/swift2objc/lib/src/transformer/transformers/transform_compound.dart @@ -34,7 +34,7 @@ ClassDeclaration transformCompound( id: originalCompound.id.addIdSuffix('wrapper'), name: parentNamer.makeUnique('${originalCompound.name}Wrapper'), hasObjCAnnotation: true, - superClass: BuiltInDeclaration.swiftNSObject.asDeclaredType, + superClass: objectType, isWrapper: true, wrappedInstance: wrappedCompoundInstance, wrapperInitializer: _buildWrapperInitializer(wrappedCompoundInstance), diff --git a/pkgs/swift2objc/lib/src/transformer/transformers/transform_globals.dart b/pkgs/swift2objc/lib/src/transformer/transformers/transform_globals.dart index 47f08a263..b68843d58 100644 --- a/pkgs/swift2objc/lib/src/transformer/transformers/transform_globals.dart +++ b/pkgs/swift2objc/lib/src/transformer/transformers/transform_globals.dart @@ -17,7 +17,7 @@ ClassDeclaration transformGlobals( id: 'globals'.addIdSuffix('wrapper'), name: globalNamer.makeUnique('GlobalsWrapper'), hasObjCAnnotation: true, - superClass: BuiltInDeclaration.swiftNSObject.asDeclaredType, + superClass: objectType, isWrapper: true, ); diff --git a/pkgs/swift2objc/test/unit/parse_function_info_test.dart b/pkgs/swift2objc/test/unit/parse_function_info_test.dart index 3d86a1b7b..c14b30e20 100644 --- a/pkgs/swift2objc/test/unit/parse_function_info_test.dart +++ b/pkgs/swift2objc/test/unit/parse_function_info_test.dart @@ -14,7 +14,7 @@ import 'package:test/test.dart'; void main() { final parsedSymbols = { - for (final decl in BuiltInDeclaration.values) + for (final decl in builtInDeclarations) decl.id: ParsedSymbol(json: Json(null), declaration: decl) }; final emptySymbolgraph = ParsedSymbolgraph(parsedSymbols, {}); diff --git a/pkgs/swift2objc/test/unit/parse_type_test.dart b/pkgs/swift2objc/test/unit/parse_type_test.dart index 309de86f6..e7524e50e 100644 --- a/pkgs/swift2objc/test/unit/parse_type_test.dart +++ b/pkgs/swift2objc/test/unit/parse_type_test.dart @@ -19,7 +19,7 @@ void main() { final classBar = ClassDeclaration(id: 'Bar', name: 'Bar'); final testDecls = [ - ...BuiltInDeclaration.values, + ...builtInDeclarations, classFoo, classBar, ];