diff --git a/pkg/_fe_analyzer_shared/lib/src/macros/api/introspection.dart b/pkg/_fe_analyzer_shared/lib/src/macros/api/introspection.dart index 318219b6129ce..09c2de942ffb0 100644 --- a/pkg/_fe_analyzer_shared/lib/src/macros/api/introspection.dart +++ b/pkg/_fe_analyzer_shared/lib/src/macros/api/introspection.dart @@ -14,10 +14,6 @@ abstract class TypeAnnotation { /// A [Code] object representation of this type annotation. Code get code; - - /// Allows you to check the kind of a [TypeAnnotation] in a switch statement, - /// and without `is` checks. - TypeAnnotationKind get kind; } /// The base class for function type declarations. @@ -33,9 +29,6 @@ abstract class FunctionTypeAnnotation implements TypeAnnotation { /// The type parameters for this function. Iterable get typeParameters; - - @override - TypeAnnotationKind get kind => TypeAnnotationKind.functionType; } /// An unresolved reference to a type. @@ -48,9 +41,6 @@ abstract class NamedTypeAnnotation implements TypeAnnotation { /// The type arguments, if applicable. Iterable get typeArguments; - - @override - TypeAnnotationKind get kind => TypeAnnotationKind.namedType; } /// The interface representing a resolved type. @@ -75,10 +65,6 @@ abstract class NamedStaticType implements StaticType { abstract class Declaration { /// The name of this declaration. String get name; - - /// Allows you to check the kind of a [Declaration] in a switch statement, - /// and without `is` checks. - DeclarationKind get kind; } /// A declaration that defines a new type in the program. @@ -105,9 +91,6 @@ abstract class TypeDeclaration implements Declaration { /// Information about fields, methods, and constructors must be retrieved from /// the `builder` objects. abstract class ClassDeclaration implements TypeDeclaration { - @override - DeclarationKind get kind => DeclarationKind.clazz; - /// Whether this class has an `abstract` modifier. bool get isAbstract; @@ -129,18 +112,12 @@ abstract class ClassDeclaration implements TypeDeclaration { /// Type alias introspection information. abstract class TypeAliasDeclaration extends TypeDeclaration { - @override - DeclarationKind get kind => DeclarationKind.typeAlias; - /// The type annotation this is an alias for. TypeAnnotation get type; } /// Function introspection information. abstract class FunctionDeclaration implements Declaration { - @override - DeclarationKind get kind => DeclarationKind.function; - /// Whether this function has an `abstract` modifier. bool get isAbstract; @@ -168,9 +145,6 @@ abstract class FunctionDeclaration implements Declaration { /// Method introspection information. abstract class MethodDeclaration implements FunctionDeclaration { - @override - DeclarationKind get kind => DeclarationKind.method; - /// The class that defines this method. TypeAnnotation get definingClass; } @@ -183,9 +157,6 @@ abstract class ConstructorDeclaration implements MethodDeclaration { /// Variable introspection information. abstract class VariableDeclaration implements Declaration { - @override - DeclarationKind get kind => DeclarationKind.variable; - /// Whether this function has an `abstract` modifier. bool get isAbstract; @@ -201,18 +172,12 @@ abstract class VariableDeclaration implements Declaration { /// Field introspection information .. abstract class FieldDeclaration implements VariableDeclaration { - @override - DeclarationKind get kind => DeclarationKind.field; - /// The class that defines this method. TypeAnnotation get definingClass; } /// Parameter introspection information. abstract class ParameterDeclaration implements Declaration { - @override - DeclarationKind get kind => DeclarationKind.parameter; - /// The type of this parameter. TypeAnnotation get type; @@ -230,28 +195,6 @@ abstract class ParameterDeclaration implements Declaration { /// Type parameter introspection information. abstract class TypeParameterDeclaration implements Declaration { - @override - DeclarationKind get kind => DeclarationKind.typeParameter; - /// The bounds for this type parameter, if it has any. TypeAnnotation? get bounds; } - -// The kinds of type declarations. -enum DeclarationKind { - clazz, - constructor, - field, - function, - method, - parameter, - typeAlias, - typeParameter, - variable, -} - -// The kinds of type annotations. -enum TypeAnnotationKind { - namedType, - functionType, -} diff --git a/pkg/_fe_analyzer_shared/lib/src/macros/bootstrap.dart b/pkg/_fe_analyzer_shared/lib/src/macros/bootstrap.dart index 55ea8bda1d1dd..332144f10d5a3 100644 --- a/pkg/_fe_analyzer_shared/lib/src/macros/bootstrap.dart +++ b/pkg/_fe_analyzer_shared/lib/src/macros/bootstrap.dart @@ -26,6 +26,7 @@ const String template = ''' import 'dart:async'; import 'dart:isolate'; +import 'package:_fe_analyzer_shared/src/macros/executor_shared/introspection_impls.dart'; import 'package:_fe_analyzer_shared/src/macros/executor_shared/response_impls.dart'; import 'package:_fe_analyzer_shared/src/macros/executor_shared/serialization.dart'; import 'package:_fe_analyzer_shared/src/macros/executor_shared/protocol.dart'; @@ -99,12 +100,14 @@ Future _instantiateMacro( return new SerializableResponse( responseType: MessageType.macroInstanceIdentifier, response: identifier, - requestId: request.id); + requestId: request.id, + serializationZoneId: request.serializationZoneId); } catch (e) { return new SerializableResponse( responseType: MessageType.error, error: e.toString(), - requestId: request.id); + requestId: request.id, + serializationZoneId: request.serializationZoneId); } } @@ -118,7 +121,7 @@ Future _executeDefinitionsPhase( } Declaration declaration = request.declaration; if (instance is FunctionDefinitionMacro && - declaration is FunctionDeclaration) { + declaration is FunctionDeclarationImpl) { FunctionDefinitionBuilderImpl builder = new FunctionDefinitionBuilderImpl( declaration, request.typeResolver, @@ -128,7 +131,8 @@ Future _executeDefinitionsPhase( return new SerializableResponse( responseType: MessageType.macroExecutionResult, response: builder.result, - requestId: request.id); + requestId: request.id, + serializationZoneId: request.serializationZoneId); } else { throw new UnsupportedError( ('Only FunctionDefinitionMacros are supported currently')); @@ -137,7 +141,8 @@ Future _executeDefinitionsPhase( return new SerializableResponse( responseType: MessageType.error, error: e.toString(), - requestId: request.id); + requestId: request.id, + serializationZoneId: request.serializationZoneId); } } '''; diff --git a/pkg/_fe_analyzer_shared/lib/src/macros/executor.dart b/pkg/_fe_analyzer_shared/lib/src/macros/executor.dart index 38a9f4b546728..97789508376a3 100644 --- a/pkg/_fe_analyzer_shared/lib/src/macros/executor.dart +++ b/pkg/_fe_analyzer_shared/lib/src/macros/executor.dart @@ -62,14 +62,14 @@ abstract class MacroExecutor { /// /// Throws an exception if there is an error executing the macro. Future executeTypesPhase( - MacroInstanceIdentifier macro, Declaration declaration); + MacroInstanceIdentifier macro, covariant Declaration declaration); /// Runs the declarations phase for [macro] on a given [declaration]. /// /// Throws an exception if there is an error executing the macro. Future executeDeclarationsPhase( MacroInstanceIdentifier macro, - Declaration declaration, + covariant Declaration declaration, TypeResolver typeResolver, ClassIntrospector classIntrospector); @@ -78,7 +78,7 @@ abstract class MacroExecutor { /// Throws an exception if there is an error executing the macro. Future executeDefinitionsPhase( MacroInstanceIdentifier macro, - Declaration declaration, + covariant Declaration declaration, TypeResolver typeResolver, ClassIntrospector classIntrospector, TypeDeclarationResolver typeDeclarationResolver); diff --git a/pkg/_fe_analyzer_shared/lib/src/macros/executor_shared/introspection_impls.dart b/pkg/_fe_analyzer_shared/lib/src/macros/executor_shared/introspection_impls.dart index e237127d92e4f..7706af7d9a38c 100644 --- a/pkg/_fe_analyzer_shared/lib/src/macros/executor_shared/introspection_impls.dart +++ b/pkg/_fe_analyzer_shared/lib/src/macros/executor_shared/introspection_impls.dart @@ -2,12 +2,27 @@ // 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 'remote_instance.dart'; +import 'serialization.dart'; +import 'serialization_extensions.dart'; import '../api.dart'; -abstract class TypeAnnotationImpl implements TypeAnnotation { +abstract class TypeAnnotationImpl extends RemoteInstance + implements TypeAnnotation { final bool isNullable; - TypeAnnotationImpl({required this.isNullable}); + TypeAnnotationImpl({required int id, required this.isNullable}) : super(id); + + @override + void serialize(Serializer serializer) { + super.serialize(serializer); + // Client side we don't encode anything but the ID. + if (serializationMode == SerializationMode.client) { + return; + } + + serializer.addBool(isNullable); + } } class NamedTypeAnnotationImpl extends TypeAnnotationImpl @@ -27,16 +42,33 @@ class NamedTypeAnnotationImpl extends TypeAnnotationImpl final String name; @override - final List typeArguments; + final List typeArguments; @override - TypeAnnotationKind get kind => TypeAnnotationKind.namedType; + RemoteInstanceKind get kind => RemoteInstanceKind.namedTypeAnnotation; NamedTypeAnnotationImpl({ + required int id, required bool isNullable, required this.name, required this.typeArguments, - }) : super(isNullable: isNullable); + }) : super(id: id, isNullable: isNullable); + + @override + void serialize(Serializer serializer) { + super.serialize(serializer); + // Client side we don't encode anything but the ID. + if (serializationMode == SerializationMode.client) { + return; + } + + serializer.addString(name); + serializer.startList(); + for (TypeAnnotationImpl typeArg in typeArguments) { + typeArg.serialize(serializer); + } + serializer.endList(); + } } class FunctionTypeAnnotationImpl extends TypeAnnotationImpl @@ -72,30 +104,75 @@ class FunctionTypeAnnotationImpl extends TypeAnnotationImpl ]); @override - final List namedParameters; + final List namedParameters; @override - final List positionalParameters; + final List positionalParameters; @override - final TypeAnnotation returnType; + final TypeAnnotationImpl returnType; @override - final List typeParameters; + final List typeParameters; @override - TypeAnnotationKind get kind => TypeAnnotationKind.functionType; + RemoteInstanceKind get kind => RemoteInstanceKind.functionTypeAnnotation; FunctionTypeAnnotationImpl({ + required int id, required bool isNullable, required this.namedParameters, required this.positionalParameters, required this.returnType, required this.typeParameters, - }) : super(isNullable: isNullable); + }) : super(id: id, isNullable: isNullable); + + @override + void serialize(Serializer serializer) { + super.serialize(serializer); + // Client side we don't encode anything but the ID. + if (serializationMode == SerializationMode.client) { + return; + } + + returnType.serialize(serializer); + + serializer.startList(); + for (ParameterDeclarationImpl param in positionalParameters) { + param.serialize(serializer); + } + serializer.endList(); + + serializer.startList(); + for (ParameterDeclarationImpl param in namedParameters) { + param.serialize(serializer); + } + serializer.endList(); + + serializer.startList(); + for (TypeParameterDeclarationImpl typeParam in typeParameters) { + typeParam.serialize(serializer); + } + serializer.endList(); + } } -class ParameterDeclarationImpl implements ParameterDeclaration { +abstract class DeclarationImpl extends RemoteInstance implements Declaration { + DeclarationImpl(int id) : super(id); + @override + void serialize(Serializer serializer) { + super.serialize(serializer); + // Client side we don't encode anything but the ID. + if (serializationMode == SerializationMode.client) { + return; + } + + serializer.addString(name); + } +} + +class ParameterDeclarationImpl extends DeclarationImpl + implements ParameterDeclaration { @override final String name; @@ -109,34 +186,73 @@ class ParameterDeclarationImpl implements ParameterDeclaration { final bool isRequired; @override - final TypeAnnotation type; + final TypeAnnotationImpl type; @override - DeclarationKind get kind => DeclarationKind.parameter; + RemoteInstanceKind get kind => RemoteInstanceKind.parameterDeclaration; ParameterDeclarationImpl({ + required int id, required this.name, required this.defaultValue, required this.isNamed, required this.isRequired, required this.type, - }); + }) : super(id); + + @override + void serialize(Serializer serializer) { + super.serialize(serializer); + // Client side we don't encode anything but the ID. + if (serializationMode == SerializationMode.client) { + return; + } + + if (defaultValue == null) { + serializer.addNull(); + } else { + defaultValue!.serialize(serializer); + } + serializer.addBool(isNamed); + serializer.addBool(isRequired); + type.serialize(serializer); + } } -class TypeParameterDeclarationImpl implements TypeParameterDeclaration { +class TypeParameterDeclarationImpl extends DeclarationImpl + implements TypeParameterDeclaration { @override final String name; @override - final TypeAnnotation? bounds; + final TypeAnnotationImpl? bounds; @override - DeclarationKind get kind => DeclarationKind.typeParameter; + RemoteInstanceKind get kind => RemoteInstanceKind.typeParameterDeclaration; + + TypeParameterDeclarationImpl( + {required int id, required this.name, required this.bounds}) + : super(id); - TypeParameterDeclarationImpl({required this.name, required this.bounds}); + @override + void serialize(Serializer serializer) { + super.serialize(serializer); + // Client side we don't encode anything but the ID. + if (serializationMode == SerializationMode.client) { + return; + } + + TypeAnnotationImpl? bounds = this.bounds; + if (bounds == null) { + serializer.addNull(); + } else { + bounds.serialize(serializer); + } + } } -class FunctionDeclarationImpl implements FunctionDeclaration { +class FunctionDeclarationImpl extends DeclarationImpl + implements FunctionDeclaration { @override final String name; @@ -153,21 +269,22 @@ class FunctionDeclarationImpl implements FunctionDeclaration { final bool isSetter; @override - final List namedParameters; + final List namedParameters; @override - final List positionalParameters; + final List positionalParameters; @override - final TypeAnnotation returnType; + final TypeAnnotationImpl returnType; @override - final List typeParameters; + final List typeParameters; @override - DeclarationKind get kind => DeclarationKind.function; + RemoteInstanceKind get kind => RemoteInstanceKind.functionDeclaration; FunctionDeclarationImpl({ + required int id, required this.name, required this.isAbstract, required this.isExternal, @@ -177,5 +294,37 @@ class FunctionDeclarationImpl implements FunctionDeclaration { required this.positionalParameters, required this.returnType, required this.typeParameters, - }); + }) : super(id); + + @override + void serialize(Serializer serializer) { + super.serialize(serializer); + // Client side we don't encode anything but the ID. + if (serializationMode == SerializationMode.client) { + return; + } + + serializer + ..addBool(isAbstract) + ..addBool(isExternal) + ..addBool(isGetter) + ..addBool(isSetter) + ..startList(); + for (ParameterDeclarationImpl named in namedParameters) { + named.serialize(serializer); + } + serializer + ..endList() + ..startList(); + for (ParameterDeclarationImpl positional in positionalParameters) { + positional.serialize(serializer); + } + serializer.endList(); + returnType.serialize(serializer); + serializer.startList(); + for (TypeParameterDeclarationImpl param in typeParameters) { + param.serialize(serializer); + } + serializer.endList(); + } } diff --git a/pkg/_fe_analyzer_shared/lib/src/macros/executor_shared/protocol.dart b/pkg/_fe_analyzer_shared/lib/src/macros/executor_shared/protocol.dart index 3dcc45539123b..68d0f94028716 100644 --- a/pkg/_fe_analyzer_shared/lib/src/macros/executor_shared/protocol.dart +++ b/pkg/_fe_analyzer_shared/lib/src/macros/executor_shared/protocol.dart @@ -9,19 +9,26 @@ library _fe_analyzer_shared.src.macros.executor_shared.protocol; import '../executor.dart'; import '../api.dart'; import '../executor_shared/response_impls.dart'; +import 'introspection_impls.dart'; +import 'remote_instance.dart'; import 'serialization.dart'; -import 'serialization_extensions.dart'; /// Base class all requests extend, provides a unique id for each request. abstract class Request implements Serializable { final int id; - Request({int? id}) : this.id = id ?? _next++; + final int serializationZoneId; + + Request({int? id, required this.serializationZoneId}) + : this.id = id ?? _next++; Request.deserialize(Deserializer deserializer) - : id = (deserializer..moveNext()).expectNum(); + : serializationZoneId = (deserializer..moveNext()).expectNum(), + id = (deserializer..moveNext()).expectNum(); - void serialize(Serializer serializer) => serializer.addNum(id); + void serialize(Serializer serializer) => serializer + ..addNum(serializationZoneId) + ..addNum(id); static int _next = 0; } @@ -44,16 +51,21 @@ class SerializableResponse implements Response, Serializable { final MessageType responseType; final String? error; final int requestId; + final int serializationZoneId; SerializableResponse({ this.error, required this.requestId, this.response, required this.responseType, + required this.serializationZoneId, }) : assert(response != null || error != null), assert(response == null || error == null); - factory SerializableResponse.deserialize(Deserializer deserializer) { + /// You must first parse the [serializationZoneId] yourself, and then + /// call this function in that zone, and pass the ID. + factory SerializableResponse.deserialize( + Deserializer deserializer, int serializationZoneId) { deserializer.moveNext(); MessageType responseType = MessageType.values[deserializer.expectNum()]; Serializable? response; @@ -80,11 +92,14 @@ class SerializableResponse implements Response, Serializable { responseType: responseType, response: response, error: error, - requestId: (deserializer..moveNext()).expectNum()); + requestId: (deserializer..moveNext()).expectNum(), + serializationZoneId: serializationZoneId); } void serialize(Serializer serializer) { - serializer.addNum(responseType.index); + serializer + ..addNum(serializationZoneId) + ..addNum(responseType.index); if (response != null) { response!.serialize(serializer); } else if (error != null) { @@ -99,7 +114,8 @@ class LoadMacroRequest extends Request { final Uri library; final String name; - LoadMacroRequest(this.library, this.name); + LoadMacroRequest(this.library, this.name, {required int serializationZoneId}) + : super(serializationZoneId: serializationZoneId); LoadMacroRequest.deserialize(Deserializer deserializer) : library = Uri.parse((deserializer..moveNext()).expectString()), @@ -122,8 +138,9 @@ class InstantiateMacroRequest extends Request { final String constructorName; final Arguments arguments; - InstantiateMacroRequest( - this.macroClass, this.constructorName, this.arguments); + InstantiateMacroRequest(this.macroClass, this.constructorName, this.arguments, + {required int serializationZoneId}) + : super(serializationZoneId: serializationZoneId); InstantiateMacroRequest.deserialize(Deserializer deserializer) : macroClass = new MacroClassIdentifierImpl.deserialize(deserializer), @@ -145,7 +162,7 @@ class InstantiateMacroRequest extends Request { /// phase. class ExecuteDefinitionsPhaseRequest extends Request { final MacroInstanceIdentifier macro; - final Declaration declaration; + final DeclarationImpl declaration; /// Client/Server specific implementation, not serialized. final TypeResolver typeResolver; @@ -157,14 +174,16 @@ class ExecuteDefinitionsPhaseRequest extends Request { final TypeDeclarationResolver typeDeclarationResolver; ExecuteDefinitionsPhaseRequest(this.macro, this.declaration, - this.typeResolver, this.classIntrospector, this.typeDeclarationResolver); + this.typeResolver, this.classIntrospector, this.typeDeclarationResolver, + {required int serializationZoneId}) + : super(serializationZoneId: serializationZoneId); /// When deserializing we have already consumed the message type, so we don't /// consume it again. ExecuteDefinitionsPhaseRequest.deserialize(Deserializer deserializer, this.typeResolver, this.classIntrospector, this.typeDeclarationResolver) : macro = new MacroInstanceIdentifierImpl.deserialize(deserializer), - declaration = (deserializer..moveNext()).expectDeclaration(), + declaration = RemoteInstance.deserialize(deserializer), super.deserialize(deserializer); void serialize(Serializer serializer) { @@ -177,14 +196,15 @@ class ExecuteDefinitionsPhaseRequest extends Request { /// A request to reflect on a type annotation class ReflectTypeRequest extends Request { - final TypeAnnotation typeAnnotation; + final TypeAnnotationImpl typeAnnotation; - ReflectTypeRequest(this.typeAnnotation); + ReflectTypeRequest(this.typeAnnotation, {required int serializationZoneId}) + : super(serializationZoneId: serializationZoneId); /// When deserializing we have already consumed the message type, so we don't /// consume it again. ReflectTypeRequest.deserialize(Deserializer deserializer) - : typeAnnotation = (deserializer..moveNext()).expectTypeAnnotation(), + : typeAnnotation = RemoteInstance.deserialize(deserializer), super.deserialize(deserializer); void serialize(Serializer serializer) { diff --git a/pkg/_fe_analyzer_shared/lib/src/macros/executor_shared/remote_instance.dart b/pkg/_fe_analyzer_shared/lib/src/macros/executor_shared/remote_instance.dart new file mode 100644 index 0000000000000..5dcd1f8826d1e --- /dev/null +++ b/pkg/_fe_analyzer_shared/lib/src/macros/executor_shared/remote_instance.dart @@ -0,0 +1,86 @@ +// Copyright (c) 2022, 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 'dart:async'; + +import 'package:meta/meta.dart'; + +import 'serialization.dart'; +import 'serialization_extensions.dart'; + +/// The key used to store the remote instance cache in the current serialization +/// zone (in server mode only). +const Symbol remoteInstanceZoneKey = #remoteInstanceCache; + +/// On the server side we keep track of remote instances by their ID. +/// +/// These are a part of the current serialization zone, which all serialization +/// and deserialization must be done in. +/// +/// This means the cache lifetime is that of the serialization zone it is run +/// in. +Map get _remoteInstanceCache => + Zone.current[remoteInstanceZoneKey]; + +/// Base class for types that need to be able to be traced back to a specific +/// instance on the server side. +abstract class RemoteInstance implements Serializable { + /// The unique ID for this instance. + final int id; + + /// The type of instance being encoded. + RemoteInstanceKind get kind; + + /// Static, incrementing ids. + static int _nextId = 0; + + /// Gets the next unique identifier. + static int get uniqueId => _nextId++; + + /// On the client side [id]s are given and you should reconstruct objects with + /// the given ID. On the server side ids should be created using + /// [RemoteInstance.uniqueId]. + RemoteInstance(this.id); + + /// Retrieves a cached instance by ID. + static T cached(int id) => _remoteInstanceCache[id] as T; + + /// Deserializes an instance based on the current [serializationMode]. + static T deserialize(Deserializer deserializer) => + (deserializer..moveNext()).expectRemoteInstance(); + + /// This method should be overridden by all subclasses, which should on their + /// first line call this super function. + /// + /// They should then return immediately if [serializationMode] is + /// [SerializationMode.client], so that only an ID is sent. + @mustCallSuper + void serialize(Serializer serializer) { + serializer.addNum(id); + switch (serializationMode) { + case SerializationMode.client: + // We only send the ID from the client side. + return; + case SerializationMode.server: + serializer.addNum(kind.index); + _remoteInstanceCache[id] = this; + return; + } + } +} + +// The kinds of instances. +enum RemoteInstanceKind { + classDeclaration, + constructorDeclaration, + fieldDeclaration, + functionDeclaration, + functionTypeAnnotation, + methodDeclaration, + namedTypeAnnotation, + parameterDeclaration, + typeAliasDeclaration, + typeParameterDeclaration, + variableDeclaration, +} diff --git a/pkg/_fe_analyzer_shared/lib/src/macros/executor_shared/response_impls.dart b/pkg/_fe_analyzer_shared/lib/src/macros/executor_shared/response_impls.dart index 5155f018d8a14..77556ad53bb3d 100644 --- a/pkg/_fe_analyzer_shared/lib/src/macros/executor_shared/response_impls.dart +++ b/pkg/_fe_analyzer_shared/lib/src/macros/executor_shared/response_impls.dart @@ -4,8 +4,11 @@ import 'dart:async'; +import 'package:_fe_analyzer_shared/src/macros/executor_shared/remote_instance.dart'; + import '../executor.dart'; import '../api.dart'; +import 'introspection_impls.dart'; import 'serialization.dart'; import 'serialization_extensions.dart'; @@ -102,7 +105,7 @@ class FunctionDefinitionBuilderImpl implements FunctionDefinitionBuilder { final ClassIntrospector classIntrospector; /// The declaration this is a builder for. - final FunctionDeclaration declaration; + final FunctionDeclarationImpl declaration; /// The final result, will be built up over `augment` calls. final MacroExecutionResultImpl result; @@ -113,8 +116,7 @@ class FunctionDefinitionBuilderImpl implements FunctionDefinitionBuilder { FunctionDefinitionBuilderImpl.deserialize(Deserializer deserializer, this.typeResolver, this.typeDeclarationResolver, this.classIntrospector) - : declaration = - (deserializer..moveNext()).expectDeclaration(), + : declaration = RemoteInstance.deserialize(deserializer), result = new MacroExecutionResultImpl.deserialize(deserializer); void serialize(Serializer serializer) { diff --git a/pkg/_fe_analyzer_shared/lib/src/macros/executor_shared/serialization.dart b/pkg/_fe_analyzer_shared/lib/src/macros/executor_shared/serialization.dart index f5f9343d2f351..89bbf59cce429 100644 --- a/pkg/_fe_analyzer_shared/lib/src/macros/executor_shared/serialization.dart +++ b/pkg/_fe_analyzer_shared/lib/src/macros/executor_shared/serialization.dart @@ -4,10 +4,19 @@ import 'dart:async'; +import 'remote_instance.dart'; + /// All serialization must be done in a serialization Zone, which tells it /// whether we are the client or server. +/// +/// In [SerializationMode.server], sets up a remote instance cache to use when +/// deserializing remote instances back to their original instance. T withSerializationMode(SerializationMode mode, T Function() fn) => - runZoned(fn, zoneValues: {#serializationMode: mode}); + runZoned(fn, zoneValues: { + #serializationMode: mode, + if (mode == SerializationMode.server) + remoteInstanceZoneKey: {} + }); /// Serializable interface abstract class Serializable { diff --git a/pkg/_fe_analyzer_shared/lib/src/macros/executor_shared/serialization_extensions.dart b/pkg/_fe_analyzer_shared/lib/src/macros/executor_shared/serialization_extensions.dart index 01cb768b54792..1fafb40410ced 100644 --- a/pkg/_fe_analyzer_shared/lib/src/macros/executor_shared/serialization_extensions.dart +++ b/pkg/_fe_analyzer_shared/lib/src/macros/executor_shared/serialization_extensions.dart @@ -1,97 +1,90 @@ import 'package:_fe_analyzer_shared/src/macros/executor_shared/introspection_impls.dart'; +import 'remote_instance.dart'; import 'serialization.dart'; import '../api.dart'; extension DeserializerExtensions on Deserializer { - TypeAnnotation expectTypeAnnotation() { + T expectRemoteInstance() { int id = expectNum(); switch (serializationMode) { - case SerializationMode.server: - return _typeAnnotationsById[id]!; case SerializationMode.client: - TypeAnnotation typeAnnotation; moveNext(); - TypeAnnotationKind type = TypeAnnotationKind.values[expectNum()]; + RemoteInstanceKind kind = RemoteInstanceKind.values[expectNum()]; moveNext(); - switch (type) { - case TypeAnnotationKind.namedType: - typeAnnotation = _expectNamedTypeAnnotation(); - break; - case TypeAnnotationKind.functionType: - typeAnnotation = _expectFunctionTypeAnnotation(); - break; + switch (kind) { + case RemoteInstanceKind.namedTypeAnnotation: + return _expectNamedTypeAnnotation(id) as T; + case RemoteInstanceKind.functionTypeAnnotation: + return _expectFunctionTypeAnnotation(id) as T; + case RemoteInstanceKind.functionDeclaration: + return _expectFunctionDeclaration(id) as T; + case RemoteInstanceKind.parameterDeclaration: + return _expectParameterDeclaration(id) as T; + case RemoteInstanceKind.typeParameterDeclaration: + return _expectTypeParameterDeclaration(id) as T; + default: + throw new UnsupportedError('Unsupported remote object kind: $kind'); } - _typeAnnotationIds[typeAnnotation] = id; - return typeAnnotation; + case SerializationMode.server: + return RemoteInstance.cached(id); } } - NamedTypeAnnotation _expectNamedTypeAnnotation() { + NamedTypeAnnotation _expectNamedTypeAnnotation(int id) { bool isNullable = expectBool(); moveNext(); String name = expectString(); moveNext(); expectList(); - List typeArguments = [ + List typeArguments = [ for (bool hasNext = moveNext(); hasNext; hasNext = moveNext()) - expectTypeAnnotation(), + expectRemoteInstance(), ]; return new NamedTypeAnnotationImpl( - isNullable: isNullable, name: name, typeArguments: typeArguments); + id: id, + isNullable: isNullable, + name: name, + typeArguments: typeArguments); } - FunctionTypeAnnotation _expectFunctionTypeAnnotation() { + FunctionTypeAnnotation _expectFunctionTypeAnnotation(int id) { bool isNullable = expectBool(); - moveNext(); - TypeAnnotation returnType = expectTypeAnnotation(); + TypeAnnotationImpl returnType = RemoteInstance.deserialize(this); moveNext(); expectList(); - List positionalParameters = [ + List positionalParameters = [ for (bool hasNext = moveNext(); hasNext; hasNext = moveNext()) - expectDeclaration(), + expectRemoteInstance(), ]; moveNext(); expectList(); - List namedParameters = [ + List namedParameters = [ for (bool hasNext = moveNext(); hasNext; hasNext = moveNext()) - expectDeclaration(), + expectRemoteInstance(), ]; moveNext(); expectList(); - List typeParameters = [ + List typeParameters = [ for (bool hasNext = moveNext(); hasNext; hasNext = moveNext()) - expectDeclaration(), + expectRemoteInstance(), ]; return new FunctionTypeAnnotationImpl( - isNullable: isNullable, - returnType: returnType, - positionalParameters: positionalParameters, - namedParameters: namedParameters, - typeParameters: typeParameters); + id: id, + isNullable: isNullable, + returnType: returnType, + positionalParameters: positionalParameters, + namedParameters: namedParameters, + typeParameters: typeParameters, + ); } - T expectDeclaration() { - DeclarationKind kind = DeclarationKind.values[expectNum()]; - moveNext(); - switch (kind) { - case DeclarationKind.parameter: - return _expectParameterDeclaration() as T; - case DeclarationKind.typeParameter: - return _expectTypeParameterDeclaration() as T; - case DeclarationKind.function: - return _expectFunctionDeclaration() as T; - default: - throw new UnimplementedError('Cant deserialize $kind yet'); - } - } - - ParameterDeclaration _expectParameterDeclaration() { + ParameterDeclaration _expectParameterDeclaration(int id) { String name = expectString(); moveNext(); Code? defaultValue; @@ -101,10 +94,11 @@ extension DeserializerExtensions on Deserializer { bool isNamed = expectBool(); moveNext(); bool isRequired = expectBool(); - moveNext(); - TypeAnnotation type = expectTypeAnnotation(); + + TypeAnnotationImpl type = RemoteInstance.deserialize(this); return new ParameterDeclarationImpl( + id: id, defaultValue: defaultValue, isNamed: isNamed, isRequired: isRequired, @@ -112,17 +106,17 @@ extension DeserializerExtensions on Deserializer { type: type); } - TypeParameterDeclaration _expectTypeParameterDeclaration() { + TypeParameterDeclaration _expectTypeParameterDeclaration(int id) { String name = expectString(); moveNext(); - TypeAnnotation? bounds; + TypeAnnotationImpl? bounds; if (!checkNull()) { - bounds = expectTypeAnnotation(); + bounds = expectRemoteInstance(); } - return new TypeParameterDeclarationImpl(name: name, bounds: bounds); + return new TypeParameterDeclarationImpl(id: id, name: name, bounds: bounds); } - FunctionDeclaration _expectFunctionDeclaration() { + FunctionDeclaration _expectFunctionDeclaration(int id) { String name = expectString(); moveNext(); bool isAbstract = expectBool(); @@ -135,28 +129,28 @@ extension DeserializerExtensions on Deserializer { moveNext(); expectList(); - List namedParameters = [ + List namedParameters = [ for (bool hasNext = moveNext(); hasNext; hasNext = moveNext()) - expectDeclaration() + expectRemoteInstance(), ]; moveNext(); expectList(); - List positionalParameters = [ + List positionalParameters = [ for (bool hasNext = moveNext(); hasNext; hasNext = moveNext()) - expectDeclaration() + expectRemoteInstance(), ]; - moveNext(); - TypeAnnotation returnType = expectTypeAnnotation(); + TypeAnnotationImpl returnType = RemoteInstance.deserialize(this); moveNext(); expectList(); - List typeParameters = [ + List typeParameters = [ for (bool hasNext = moveNext(); hasNext; hasNext = moveNext()) - expectDeclaration() + expectRemoteInstance(), ]; return new FunctionDeclarationImpl( + id: id, name: name, isAbstract: isAbstract, isExternal: isExternal, @@ -184,7 +178,7 @@ extension DeserializerExtensions on Deserializer { parts.add(expectString()); break; case CodePartKind.typeAnnotation: - parts.add(expectTypeAnnotation()); + parts.add(expectRemoteInstance()); break; } } @@ -212,179 +206,6 @@ extension DeserializerExtensions on Deserializer { } } -/// On the server side we keep track of type annotations by their ID. -final _typeAnnotationsById = {}; - -/// On the client side we keep an expando of ids on [TypeAnnotation]s. -final _typeAnnotationIds = new Expando(); - -/// Incrementing ids for [TypeAnnotationImpl]s -int _nextTypeAnnotationId = 0; - -extension SerializeTypeAnnotation on TypeAnnotation { - void serialize(Serializer serializer) { - TypeAnnotation self = this; - if (self is NamedTypeAnnotationImpl) { - self.serialize(serializer); - } else if (self is FunctionTypeAnnotationImpl) { - self.serialize(serializer); - } else { - throw new UnsupportedError( - 'Type ${this.runtimeType} is not supported for serialization.'); - } - } -} - -/// Does the parts of serialization for type annotations that is shared between -/// implementations. -/// -/// Returns `false` if we should continue serializing the rest of the object, or -/// `true` if the object is fully serialized (just an ID). -bool _doSharedTypeAnnotationSerialization(Serializer serializer, - TypeAnnotation typeAnnotation, TypeAnnotationKind kind) { - switch (serializationMode) { - case SerializationMode.client: - // Only send the ID from the client side, and assume we have one. - int id = _typeAnnotationIds[typeAnnotation]!; - serializer.addNum(id); - return true; - case SerializationMode.server: - // Server side we may create new ids for unseen annotations, - // and continue to serialize the rest of the annotation. - int id = _typeAnnotationIds[typeAnnotation] ?? _nextTypeAnnotationId++; - // TODO: We should clean these up at some point. - _typeAnnotationsById[id] = typeAnnotation; - serializer.addNum(id); - break; - } - serializer.addNum(kind.index); - serializer.addBool(typeAnnotation.isNullable); - return false; -} - -extension SerializeNamedTypeAnnotation on NamedTypeAnnotation { - void serialize(Serializer serializer) { - if (_doSharedTypeAnnotationSerialization( - serializer, this, TypeAnnotationKind.namedType)) { - return; - } - serializer.addString(name); - serializer.startList(); - for (TypeAnnotation typeArg in typeArguments) { - typeArg.serialize(serializer); - } - serializer.endList(); - } -} - -extension SerializeFunctionTypeAnnotation on FunctionTypeAnnotation { - void serialize(Serializer serializer) { - if (_doSharedTypeAnnotationSerialization( - serializer, this, TypeAnnotationKind.functionType)) { - return; - } - - returnType.serialize(serializer); - - serializer.startList(); - for (ParameterDeclaration param in positionalParameters) { - param.serialize(serializer); - } - serializer.endList(); - - serializer.startList(); - for (ParameterDeclaration param in namedParameters) { - param.serialize(serializer); - } - serializer.endList(); - - serializer.startList(); - for (TypeParameterDeclaration typeParam in typeParameters) { - typeParam.serialize(serializer); - } - serializer.endList(); - } -} - -/// Does the shared parts of [Declaration] serialization. -void _serializeDeclaration(Serializer serializer, Declaration declaration) { - serializer.addNum(declaration.kind.index); - serializer.addString(declaration.name); -} - -/// Checks the type and deserializes it appropriately. -extension SerializeDeclaration on Declaration { - void serialize(Serializer serializer) { - switch (kind) { - case DeclarationKind.parameter: - (this as ParameterDeclaration).serialize(serializer); - break; - case DeclarationKind.typeParameter: - (this as TypeParameterDeclaration).serialize(serializer); - break; - case DeclarationKind.function: - (this as FunctionDeclaration).serialize(serializer); - break; - default: - throw new UnimplementedError('Cant serialize $kind yet'); - } - } -} - -extension SerializeParameterDeclaration on ParameterDeclaration { - void serialize(Serializer serializer) { - _serializeDeclaration(serializer, this); - if (defaultValue == null) { - serializer.addNull(); - } else { - defaultValue!.serialize(serializer); - } - serializer.addBool(isNamed); - serializer.addBool(isRequired); - type.serialize(serializer); - } -} - -extension SerializeTypeParameterDeclaration on TypeParameterDeclaration { - void serialize(Serializer serializer) { - _serializeDeclaration(serializer, this); - TypeAnnotation? bounds = this.bounds; - if (bounds == null) { - serializer.addNull(); - } else { - bounds.serialize(serializer); - } - } -} - -extension SerializeFunctionDeclaration on FunctionDeclaration { - void serialize(Serializer serializer) { - _serializeDeclaration(serializer, this); - serializer - ..addBool(isAbstract) - ..addBool(isExternal) - ..addBool(isGetter) - ..addBool(isSetter) - ..startList(); - for (ParameterDeclaration named in namedParameters) { - named.serialize(serializer); - } - serializer - ..endList() - ..startList(); - for (ParameterDeclaration positional in positionalParameters) { - positional.serialize(serializer); - } - serializer.endList(); - returnType.serialize(serializer); - serializer.startList(); - for (TypeParameterDeclaration param in typeParameters) { - param.serialize(serializer); - } - serializer.endList(); - } -} - extension SerializeCode on Code { void serialize(Serializer serializer) { serializer @@ -398,7 +219,7 @@ extension SerializeCode on Code { } else if (part is Code) { serializer.addNum(CodePartKind.code.index); part.serialize(serializer); - } else if (part is TypeAnnotation) { + } else if (part is TypeAnnotationImpl) { serializer.addNum(CodePartKind.typeAnnotation.index); part.serialize(serializer); } else { diff --git a/pkg/_fe_analyzer_shared/lib/src/macros/isolate_mirrors_executor/isolate_mirrors_executor.dart b/pkg/_fe_analyzer_shared/lib/src/macros/isolate_mirrors_executor/isolate_mirrors_executor.dart index 136a6d56c653e..24c7d8051d62f 100644 --- a/pkg/_fe_analyzer_shared/lib/src/macros/isolate_mirrors_executor/isolate_mirrors_executor.dart +++ b/pkg/_fe_analyzer_shared/lib/src/macros/isolate_mirrors_executor/isolate_mirrors_executor.dart @@ -7,6 +7,7 @@ import 'dart:isolate'; import 'dart:mirrors'; import 'isolate_mirrors_impl.dart'; +import '../executor_shared/introspection_impls.dart'; import '../executor_shared/protocol.dart'; import '../executor.dart'; import '../api.dart'; @@ -101,12 +102,14 @@ class _IsolateMirrorMacroExecutor implements MacroExecutor { @override Future executeDefinitionsPhase( MacroInstanceIdentifier macro, - Declaration declaration, + DeclarationImpl declaration, TypeResolver typeResolver, ClassIntrospector classIntrospector, TypeDeclarationResolver typeDeclarationResolver) => _sendRequest(new ExecuteDefinitionsPhaseRequest(macro, declaration, - typeResolver, classIntrospector, typeDeclarationResolver)); + typeResolver, classIntrospector, typeDeclarationResolver, + // Serialization zones are not necessary in this executor. + serializationZoneId: -1)); @override Future executeTypesPhase( @@ -121,7 +124,9 @@ class _IsolateMirrorMacroExecutor implements MacroExecutor { String constructor, Arguments arguments) => _sendRequest( - new InstantiateMacroRequest(macroClass, constructor, arguments)); + new InstantiateMacroRequest(macroClass, constructor, arguments, + // Serialization zones are not necessary in this executor. + serializationZoneId: -1)); @override Future loadMacro(Uri library, String name, @@ -131,7 +136,9 @@ class _IsolateMirrorMacroExecutor implements MacroExecutor { throw new UnsupportedError( 'The IsolateMirrorsExecutor does not support precompiled dill files'); } - return _sendRequest(new LoadMacroRequest(library, name)); + return _sendRequest(new LoadMacroRequest(library, name, + // Serialization zones are not necessary in this executor. + serializationZoneId: -1)); } /// Sends a request and returns the response, casting it to the expected diff --git a/pkg/_fe_analyzer_shared/lib/src/macros/isolate_mirrors_executor/isolate_mirrors_impl.dart b/pkg/_fe_analyzer_shared/lib/src/macros/isolate_mirrors_executor/isolate_mirrors_impl.dart index e6848f8c522e1..fe7e32e664cb2 100644 --- a/pkg/_fe_analyzer_shared/lib/src/macros/isolate_mirrors_executor/isolate_mirrors_impl.dart +++ b/pkg/_fe_analyzer_shared/lib/src/macros/isolate_mirrors_executor/isolate_mirrors_impl.dart @@ -6,6 +6,7 @@ import 'dart:async'; import 'dart:isolate'; import 'dart:mirrors'; +import '../executor_shared/introspection_impls.dart'; import '../executor_shared/response_impls.dart'; import '../executor_shared/protocol.dart'; import '../api.dart'; @@ -83,9 +84,9 @@ Future _executeDefinitionsPhase( throw new StateError('Unrecognized macro instance ${request.macro}\n' 'Known instances: $_macroInstances)'); } - Declaration declaration = request.declaration; + DeclarationImpl declaration = request.declaration; if (instance is FunctionDefinitionMacro && - declaration is FunctionDeclaration) { + declaration is FunctionDeclarationImpl) { FunctionDefinitionBuilderImpl builder = new FunctionDefinitionBuilderImpl( declaration, request.typeResolver, diff --git a/pkg/_fe_analyzer_shared/lib/src/macros/isolated_executor/isolated_executor.dart b/pkg/_fe_analyzer_shared/lib/src/macros/isolated_executor/isolated_executor.dart index b33dd94100f47..4b564b6538d0a 100644 --- a/pkg/_fe_analyzer_shared/lib/src/macros/isolated_executor/isolated_executor.dart +++ b/pkg/_fe_analyzer_shared/lib/src/macros/isolated_executor/isolated_executor.dart @@ -6,9 +6,10 @@ import 'dart:async'; import 'dart:isolate'; import '../api.dart'; +import '../executor_shared/introspection_impls.dart'; import '../executor_shared/protocol.dart'; -import '../executor_shared/serialization.dart'; import '../executor_shared/response_impls.dart'; +import '../executor_shared/serialization.dart'; import '../executor.dart'; /// Returns an instance of [_IsolatedMacroExecutor]. @@ -47,7 +48,7 @@ class _IsolatedMacroExecutor implements MacroExecutor { @override Future executeDeclarationsPhase( MacroInstanceIdentifier macro, - Declaration declaration, + DeclarationImpl declaration, TypeResolver typeResolver, ClassIntrospector classIntrospector) => _executors[macro]!.executeDeclarationsPhase( @@ -56,7 +57,7 @@ class _IsolatedMacroExecutor implements MacroExecutor { @override Future executeDefinitionsPhase( MacroInstanceIdentifier macro, - Declaration declaration, + DeclarationImpl declaration, TypeResolver typeResolver, ClassIntrospector classIntrospector, TypeDeclarationResolver typeDeclarationResolver) => @@ -65,7 +66,7 @@ class _IsolatedMacroExecutor implements MacroExecutor { @override Future executeTypesPhase( - MacroInstanceIdentifier macro, Declaration declaration) => + MacroInstanceIdentifier macro, DeclarationImpl declaration) => _executors[macro]!.executeTypesPhase(macro, declaration); @override @@ -114,6 +115,15 @@ class _SingleIsolatedMacroExecutor extends MacroExecutor { /// A map of response completers by request id. final responseCompleters = >{}; + /// We need to know which serialization zone to deserialize objects in, so + /// that we read them from the correct cache. Each request creates its own + /// zone which it stores here by ID and then responses are deserialized in + /// the same zone. + static final serializationZones = {}; + + /// Incrementing identifier for the serialization zone ids. + static int _nextSerializationZoneId = 0; + _SingleIsolatedMacroExecutor( {required this.onClose, required this.responseStream, @@ -141,13 +151,16 @@ class _SingleIsolatedMacroExecutor extends MacroExecutor { if (!sendPortCompleter.isCompleted) { sendPortCompleter.complete(message as SendPort); } else { - withSerializationMode(SerializationMode.server, () { - JsonDeserializer deserializer = - new JsonDeserializer(message as List); - SerializableResponse response = - new SerializableResponse.deserialize(deserializer); - responseStreamController.add(response); - }); + JsonDeserializer deserializer = + new JsonDeserializer(message as List); + // Ever object starts with a zone ID which dictates the zone in which we + // should deserialize the message. + deserializer.moveNext(); + int zoneId = deserializer.expectNum(); + Zone zone = serializationZones[zoneId]!; + SerializableResponse response = zone.run( + () => new SerializableResponse.deserialize(deserializer, zoneId)); + responseStreamController.add(response); } }).onDone(responseStreamController.close); @@ -172,7 +185,7 @@ class _SingleIsolatedMacroExecutor extends MacroExecutor { @override Future executeDeclarationsPhase( MacroInstanceIdentifier macro, - Declaration declaration, + DeclarationImpl declaration, TypeResolver typeResolver, ClassIntrospector classIntrospector) { // TODO: implement executeDeclarationsPhase @@ -182,12 +195,13 @@ class _SingleIsolatedMacroExecutor extends MacroExecutor { @override Future executeDefinitionsPhase( MacroInstanceIdentifier macro, - Declaration declaration, + DeclarationImpl declaration, TypeResolver typeResolver, ClassIntrospector classIntrospector, TypeDeclarationResolver typeDeclarationResolver) => - _sendRequest(new ExecuteDefinitionsPhaseRequest(macro, declaration, - typeResolver, classIntrospector, typeDeclarationResolver)); + _sendRequest((zoneId) => new ExecuteDefinitionsPhaseRequest(macro, + declaration, typeResolver, classIntrospector, typeDeclarationResolver, + serializationZoneId: zoneId)); @override Future executeTypesPhase( @@ -201,8 +215,9 @@ class _SingleIsolatedMacroExecutor extends MacroExecutor { MacroClassIdentifier macroClass, String constructor, Arguments arguments) => - _sendRequest( - new InstantiateMacroRequest(macroClass, constructor, arguments)); + _sendRequest((zoneId) => new InstantiateMacroRequest( + macroClass, constructor, arguments, + serializationZoneId: zoneId)); /// These calls are handled by the higher level executor. @override @@ -212,16 +227,24 @@ class _SingleIsolatedMacroExecutor extends MacroExecutor { /// Sends a [request] and handles the response, casting it to the expected /// type or throwing the error provided. - Future _sendRequest(Request request) => + Future _sendRequest(Request Function(int) requestFactory) => withSerializationMode(SerializationMode.server, () async { + int zoneId = _nextSerializationZoneId++; + serializationZones[zoneId] = Zone.current; + Request request = requestFactory(zoneId); JsonSerializer serializer = new JsonSerializer(); request.serialize(serializer); sendPort.send(serializer.result); Completer completer = new Completer(); responseCompleters[request.id] = completer; - Response response = await completer.future; - T? result = response.response as T?; - if (result != null) return result; - throw response.error!; + try { + Response response = await completer.future; + T? result = response.response as T?; + if (result != null) return result; + throw response.error!; + } finally { + // Clean up the zone after the request is done. + serializationZones.remove(zoneId); + } }); } diff --git a/pkg/_fe_analyzer_shared/test/macros/executor_shared/serialization_test.dart b/pkg/_fe_analyzer_shared/test/macros/executor_shared/serialization_test.dart index c069423b400ae..9faf079fd6154 100644 --- a/pkg/_fe_analyzer_shared/test/macros/executor_shared/serialization_test.dart +++ b/pkg/_fe_analyzer_shared/test/macros/executor_shared/serialization_test.dart @@ -2,12 +2,14 @@ // 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:_fe_analyzer_shared/src/macros/executor_shared/introspection_impls.dart'; +import 'package:_fe_analyzer_shared/src/macros/executor_shared/remote_instance.dart'; import 'package:_fe_analyzer_shared/src/macros/executor_shared/serialization.dart'; import 'package:test/test.dart'; void main() { group('json serializer', () { - test('can serialize and deserialize data', () { + test('can serialize and deserialize basic data', () { var serializer = JsonSerializer(); serializer ..addNum(1) @@ -74,5 +76,41 @@ void main() { expect(deserializer.moveNext(), false); }); + + test('remote instances', () async { + var string = NamedTypeAnnotationImpl( + id: RemoteInstance.uniqueId, + isNullable: false, + name: 'String', + typeArguments: const []); + var foo = NamedTypeAnnotationImpl( + id: RemoteInstance.uniqueId, + isNullable: false, + name: 'Foo', + typeArguments: [string]); + Object? serializedFoo; + var serializer = JsonSerializer(); + + withSerializationMode(SerializationMode.server, () { + foo.serialize(serializer); + serializedFoo = serializer.result; + var response = roundTrip(serializedFoo); + var deserializer = JsonDeserializer(response as List); + var instance = RemoteInstance.deserialize(deserializer); + expect(instance, foo); + }); + }); + }); +} + +/// Deserializes [serialized] in client mode and sends it back. +Object? roundTrip(Object? serialized) { + return withSerializationMode(SerializationMode.client, () { + var deserializer = JsonDeserializer(serialized as List); + var instance = + RemoteInstance.deserialize(deserializer); + var serializer = JsonSerializer(); + instance.serialize(serializer); + return serializer.result; }); } diff --git a/pkg/_fe_analyzer_shared/test/macros/isolate_mirror_executor/isolate_mirror_executor_test.dart b/pkg/_fe_analyzer_shared/test/macros/isolate_mirror_executor/isolate_mirror_executor_test.dart index 7056208c69909..aae2ad0000abd 100644 --- a/pkg/_fe_analyzer_shared/test/macros/isolate_mirror_executor/isolate_mirror_executor_test.dart +++ b/pkg/_fe_analyzer_shared/test/macros/isolate_mirror_executor/isolate_mirror_executor_test.dart @@ -7,6 +7,7 @@ import 'dart:io'; import 'package:_fe_analyzer_shared/src/macros/api.dart'; import 'package:_fe_analyzer_shared/src/macros/executor.dart'; import 'package:_fe_analyzer_shared/src/macros/executor_shared/introspection_impls.dart'; +import 'package:_fe_analyzer_shared/src/macros/executor_shared/remote_instance.dart'; import 'package:_fe_analyzer_shared/src/macros/isolate_mirrors_executor/isolate_mirrors_executor.dart' as mirrorExecutor; @@ -15,6 +16,16 @@ import 'package:test/test.dart'; void main() { late MacroExecutor executor; + late File simpleMacroFile; + + setUpAll(() { + // We support running from either the root of the SDK or the package root. + simpleMacroFile = + File('pkg/_fe_analyzer_shared/test/macros/simple_macro.dart'); + if (!simpleMacroFile.existsSync()) { + simpleMacroFile = File('test/macros/simple_macro.dart'); + } + }); setUp(() async { executor = await mirrorExecutor.start(); @@ -25,12 +36,8 @@ void main() { }); test('can load macros and create instances', () async { - var clazzId = await executor.loadMacro( - // Tests run from the root of the repo. - File('pkg/_fe_analyzer_shared/test/macros/simple_macro.dart') - .absolute - .uri, - 'SimpleMacro'); + var clazzId = + await executor.loadMacro(simpleMacroFile.absolute.uri, 'SimpleMacro'); expect(clazzId, isNotNull, reason: 'Can load a macro.'); var instanceId = @@ -51,6 +58,7 @@ void main() { var definitionResult = await executor.executeDefinitionsPhase( instanceId, FunctionDeclarationImpl( + id: RemoteInstance.uniqueId, isAbstract: false, isExternal: false, isGetter: false, @@ -59,7 +67,10 @@ void main() { namedParameters: [], positionalParameters: [], returnType: NamedTypeAnnotationImpl( - name: 'String', isNullable: false, typeArguments: const []), + id: RemoteInstance.uniqueId, + name: 'String', + isNullable: false, + typeArguments: const []), typeParameters: [], ), _FakeTypeResolver(), diff --git a/pkg/_fe_analyzer_shared/test/macros/isolated_executor/isolated_executor_test.dart b/pkg/_fe_analyzer_shared/test/macros/isolated_executor/isolated_executor_test.dart index 465997176bfa2..c4cac8b08dbc5 100644 --- a/pkg/_fe_analyzer_shared/test/macros/isolated_executor/isolated_executor_test.dart +++ b/pkg/_fe_analyzer_shared/test/macros/isolated_executor/isolated_executor_test.dart @@ -9,6 +9,7 @@ import 'package:_fe_analyzer_shared/src/macros/api.dart'; import 'package:_fe_analyzer_shared/src/macros/bootstrap.dart'; import 'package:_fe_analyzer_shared/src/macros/executor.dart'; import 'package:_fe_analyzer_shared/src/macros/executor_shared/introspection_impls.dart'; +import 'package:_fe_analyzer_shared/src/macros/executor_shared/remote_instance.dart'; import 'package:_fe_analyzer_shared/src/macros/isolated_executor/isolated_executor.dart' as isolatedExecutor; @@ -18,6 +19,16 @@ import 'package:test/test.dart'; void main() { late MacroExecutor executor; late Directory tmpDir; + late File simpleMacroFile; + + setUpAll(() { + // We support running from either the root of the SDK or the package root. + simpleMacroFile = + File('pkg/_fe_analyzer_shared/test/macros/simple_macro.dart'); + if (!simpleMacroFile.existsSync()) { + simpleMacroFile = File('test/macros/simple_macro.dart'); + } + }); setUp(() async { executor = await isolatedExecutor.start(); @@ -30,10 +41,7 @@ void main() { }); test('can load macros and create instances', () async { - // Tests run from the root of the repo. - var macroUri = File('pkg/_fe_analyzer_shared/test/macros/simple_macro.dart') - .absolute - .uri; + var macroUri = simpleMacroFile.absolute.uri; var macroName = 'SimpleMacro'; var bootstrapContent = @@ -73,6 +81,7 @@ void main() { var definitionResult = await executor.executeDefinitionsPhase( instanceId, FunctionDeclarationImpl( + id: RemoteInstance.uniqueId, isAbstract: false, isExternal: false, isGetter: false, @@ -81,7 +90,10 @@ void main() { namedParameters: [], positionalParameters: [], returnType: NamedTypeAnnotationImpl( - name: 'String', isNullable: false, typeArguments: const []), + id: RemoteInstance.uniqueId, + name: 'String', + isNullable: false, + typeArguments: const []), typeParameters: [], ), _FakeTypeResolver(), diff --git a/pkg/front_end/lib/src/fasta/source/diet_listener.dart b/pkg/front_end/lib/src/fasta/source/diet_listener.dart index 77c43b79e4b88..99fa6f18681a8 100644 --- a/pkg/front_end/lib/src/fasta/source/diet_listener.dart +++ b/pkg/front_end/lib/src/fasta/source/diet_listener.dart @@ -50,6 +50,8 @@ import '../type_inference/type_inference_engine.dart' show InferenceDataForTesting, TypeInferenceEngine; import '../type_inference/type_inferrer.dart' show TypeInferrer; import 'diet_parser.dart'; +import 'source_constructor_builder.dart'; +import 'source_enum_builder.dart'; import 'source_field_builder.dart'; import 'source_function_builder.dart'; import 'source_library_builder.dart' show SourceLibraryBuilder; @@ -962,6 +964,19 @@ class DietListener extends StackListenerImpl { void endEnum(Token enumKeyword, Token leftBrace, int memberCount) { debugEvent("Enum"); checkEmpty(enumKeyword.charOffset); + + SourceEnumBuilder? enumBuilder = currentClass as SourceEnumBuilder?; + if (enumBuilder != null) { + DeclaredSourceConstructorBuilder? defaultConstructorBuilder = + enumBuilder.synthesizedDefaultConstructorBuilder; + if (defaultConstructorBuilder != null) { + BodyBuilder bodyBuilder = + createFunctionListener(defaultConstructorBuilder); + bodyBuilder.finishConstructor( + defaultConstructorBuilder, AsyncMarker.Sync, new EmptyStatement()); + } + } + currentDeclaration = null; memberScope = libraryBuilder.scope; } diff --git a/pkg/front_end/lib/src/fasta/source/outline_builder.dart b/pkg/front_end/lib/src/fasta/source/outline_builder.dart index 820c2d54f28e5..6fe3c15104eb3 100644 --- a/pkg/front_end/lib/src/fasta/source/outline_builder.dart +++ b/pkg/front_end/lib/src/fasta/source/outline_builder.dart @@ -2329,7 +2329,7 @@ class OutlineBuilder extends StackListenerImpl { // We pop more values than needed to reach typeVariables, offset and name. List? interfaces = pop() as List?; - List? mixins = pop() as List?; + Object? mixins = pop(); List? typeVariables = pop() as List?; int charOffset = popCharOffset(); // identifier char offset. @@ -2341,7 +2341,7 @@ class OutlineBuilder extends StackListenerImpl { push(name ?? NullValue.Name); push(charOffset); push(typeVariables ?? NullValue.TypeVariables); - push(mixins ?? NullValue.TypeBuilderList); + push(mixins ?? NullValue.TypeBuilder); push(interfaces ?? NullValue.TypeBuilderList); push(enumKeyword.charOffset); // start char offset. @@ -2364,7 +2364,7 @@ class OutlineBuilder extends StackListenerImpl { int endCharOffset = popCharOffset(); int startCharOffset = popCharOffset(); pop() as List?; // interfaces. - pop() as List?; // mixins. + MixinApplicationBuilder? mixinBuilder = pop() as MixinApplicationBuilder?; List? typeVariables = pop() as List?; int charOffset = popCharOffset(); // identifier char offset. @@ -2373,8 +2373,15 @@ class OutlineBuilder extends StackListenerImpl { checkEmpty(startCharOffset); if (name is! ParserRecovery) { - libraryBuilder.addEnum(metadata, name as String, typeVariables, - enumConstantInfos, startCharOffset, charOffset, endCharOffset); + libraryBuilder.addEnum( + metadata, + name as String, + typeVariables, + mixinBuilder, + enumConstantInfos, + startCharOffset, + charOffset, + endCharOffset); } else { libraryBuilder .endNestedDeclaration( @@ -3199,15 +3206,23 @@ class OutlineBuilder extends StackListenerImpl { if (mixins is ParserRecovery) { push(new ParserRecovery(withKeyword.charOffset)); } else { - // TODO(cstefantsova): Handle enum mixins here. - push(mixins); + NamedTypeBuilder enumType = new NamedTypeBuilder( + "_Enum", + const NullabilityBuilder.omitted(), + /* arguments = */ null, + /* fileUri = */ null, + /* charOffset = */ null, + instanceTypeVariableAccess: + InstanceTypeVariableAccessState.Unexpected); + push(libraryBuilder.addMixinApplication( + enumType, mixins as List, withKeyword.charOffset)); } } @override void handleEnumNoWithClause() { debugEvent("EnumNoWithClause"); - push(NullValue.TypeBuilderList); + push(NullValue.TypeBuilder); } @override diff --git a/pkg/front_end/lib/src/fasta/source/source_class_builder.dart b/pkg/front_end/lib/src/fasta/source/source_class_builder.dart index 3faf67907fa28..c95900cd50b41 100644 --- a/pkg/front_end/lib/src/fasta/source/source_class_builder.dart +++ b/pkg/front_end/lib/src/fasta/source/source_class_builder.dart @@ -105,6 +105,8 @@ class SourceClassBuilder extends ClassBuilderImpl SourceClassBuilder? _patchBuilder; + final bool isEnumMixin; + SourceClassBuilder( List? metadata, int modifiers, @@ -124,7 +126,8 @@ class SourceClassBuilder extends ClassBuilderImpl {Class? cls, this.mixedInTypeBuilder, this.isMixinDeclaration = false, - this.isMacro: false}) + this.isMacro: false, + this.isEnumMixin: false}) : actualCls = initializeClass(cls, typeVariables, name, parent, startCharOffset, nameOffset, charEndOffset, referencesFromIndexed), super(metadata, modifiers, name, typeVariables, supertype, interfaces, @@ -195,6 +198,11 @@ class SourceClassBuilder extends ClassBuilderImpl if (supertypeBuilder != null) { supertypeBuilder = _checkSupertype(supertypeBuilder!); } + if (isEnumMixin) { + assert(supertypeBuilder?.name == "_Enum"); + supertypeBuilder?.resolveIn(coreLibrary.scope, + supertypeBuilder?.charOffset ?? charOffset, fileUri, library); + } Supertype? supertype = supertypeBuilder?.buildSupertype(library, charOffset, fileUri); if (supertype != null) { diff --git a/pkg/front_end/lib/src/fasta/source/source_enum_builder.dart b/pkg/front_end/lib/src/fasta/source/source_enum_builder.dart index 2f84424026266..9a0fe61554c95 100644 --- a/pkg/front_end/lib/src/fasta/source/source_enum_builder.dart +++ b/pkg/front_end/lib/src/fasta/source/source_enum_builder.dart @@ -46,6 +46,7 @@ import '../builder/named_type_builder.dart'; import '../builder/nullability_builder.dart'; import '../builder/procedure_builder.dart'; import '../builder/type_builder.dart'; +import '../builder/type_declaration_builder.dart'; import '../builder/type_variable_builder.dart'; import '../fasta_codes.dart' show @@ -86,7 +87,7 @@ class SourceEnumBuilder extends SourceClassBuilder { final NamedTypeBuilder listType; - DeclaredSourceConstructorBuilder? _synthesizedDefaultConstructorBuilder; + DeclaredSourceConstructorBuilder? synthesizedDefaultConstructorBuilder; SourceEnumBuilder.internal( List? metadata, @@ -99,7 +100,7 @@ class SourceEnumBuilder extends SourceClassBuilder { this.intType, this.listType, this.objectType, - TypeBuilder enumType, + TypeBuilder supertypeBuilder, this.stringType, SourceLibraryBuilder parent, int startCharOffset, @@ -111,7 +112,7 @@ class SourceEnumBuilder extends SourceClassBuilder { 0, name, typeVariables, - enumType, + supertypeBuilder, /* interfaces = */ null, /* onTypes = */ null, scope, @@ -128,6 +129,7 @@ class SourceEnumBuilder extends SourceClassBuilder { List? metadata, String name, List? typeVariables, + TypeBuilder? supertypeBuilder, List? enumConstantInfos, SourceLibraryBuilder parent, int startCharOffset, @@ -173,7 +175,7 @@ class SourceEnumBuilder extends SourceClassBuilder { /* fileUri = */ null, /* charOffset = */ null, instanceTypeVariableAccess: InstanceTypeVariableAccessState.Unexpected); - NamedTypeBuilder enumType = new NamedTypeBuilder( + supertypeBuilder ??= new NamedTypeBuilder( "_Enum", const NullabilityBuilder.omitted(), /* arguments = */ null, @@ -418,14 +420,14 @@ class SourceEnumBuilder extends SourceClassBuilder { intType, listType, objectType, - enumType, + supertypeBuilder, stringType, parent, startCharOffsetComputed, charOffset, charEndOffset, referencesFromIndexed) - .._synthesizedDefaultConstructorBuilder = + ..synthesizedDefaultConstructorBuilder = synthesizedDefaultConstructorBuilder; void setParent(String name, MemberBuilder? builder) { @@ -458,9 +460,21 @@ class SourceEnumBuilder extends SourceClassBuilder { coreLibrary.scope, charOffset, fileUri, libraryBuilder); objectType.resolveIn( coreLibrary.scope, charOffset, fileUri, libraryBuilder); - TypeBuilder supertypeBuilder = this.supertypeBuilder!; - supertypeBuilder.resolveIn( - coreLibrary.scope, charOffset, fileUri, libraryBuilder); + TypeBuilder? supertypeBuilder = this.supertypeBuilder; + NamedTypeBuilder? enumType; + + while (enumType == null && supertypeBuilder is NamedTypeBuilder) { + TypeDeclarationBuilder? superclassBuilder = supertypeBuilder.declaration; + if (superclassBuilder is ClassBuilder && + superclassBuilder.isMixinApplication) { + supertypeBuilder = superclassBuilder.supertypeBuilder; + } else { + enumType = supertypeBuilder; + } + } + assert(enumType is NamedTypeBuilder && enumType.name == "_Enum"); + enumType!.resolveIn(coreLibrary.scope, charOffset, fileUri, libraryBuilder); + listType.resolveIn(coreLibrary.scope, charOffset, fileUri, libraryBuilder); List values = []; @@ -481,34 +495,39 @@ class SourceEnumBuilder extends SourceClassBuilder { valuesBuilder.build(libraryBuilder); // The super initializer for the synthesized default constructor is - // inserted here. Other constructors are handled in - // [BodyBuilder.finishConstructor], as they are processed via the pipeline - // for constructor parsing and building. - if (_synthesizedDefaultConstructorBuilder != null) { - Constructor constructor = - _synthesizedDefaultConstructorBuilder!.build(libraryBuilder); - ClassBuilder objectClass = objectType.declaration as ClassBuilder; - ClassBuilder enumClass = supertypeBuilder.declaration as ClassBuilder; - MemberBuilder? superConstructor = enumClass.findConstructorOrFactory( - "", charOffset, fileUri, libraryBuilder); - if (superConstructor == null || !superConstructor.isConstructor) { - // TODO(ahe): Ideally, we would also want to check that [Object]'s - // unnamed constructor requires no arguments. But that information - // isn't always available at this point, and it's not really a - // situation that can happen unless you start modifying the SDK - // sources. (We should add a correct message. We no longer depend on - // Object here.) - library.addProblem( - messageNoUnnamedConstructorInObject, - objectClass.charOffset, - objectClass.name.length, - objectClass.fileUri); - } else { - constructor.initializers.add(new SuperInitializer( - superConstructor.member as Constructor, - new Arguments.forwarded( - constructor.function, libraryBuilder.library)) - ..parent = constructor); + // inserted here if the enum's supertype is _Enum to preserve the legacy + // behavior or having the old-style enum constants built in the outlines. + // Other constructors are handled in [BodyBuilder.finishConstructor] as + // they are processed via the pipeline for constructor parsing and + // building. + if (identical(this.supertypeBuilder, enumType)) { + if (synthesizedDefaultConstructorBuilder != null) { + Constructor constructor = + synthesizedDefaultConstructorBuilder!.build(libraryBuilder); + ClassBuilder objectClass = objectType.declaration as ClassBuilder; + ClassBuilder enumClass = enumType.declaration as ClassBuilder; + MemberBuilder? superConstructor = enumClass.findConstructorOrFactory( + "", charOffset, fileUri, libraryBuilder); + if (superConstructor == null || !superConstructor.isConstructor) { + // TODO(ahe): Ideally, we would also want to check that [Object]'s + // unnamed constructor requires no arguments. But that information + // isn't always available at this point, and it's not really a + // situation that can happen unless you start modifying the SDK + // sources. (We should add a correct message. We no longer depend on + // Object here.) + library.addProblem( + messageNoUnnamedConstructorInObject, + objectClass.charOffset, + objectClass.name.length, + objectClass.fileUri); + } else { + constructor.initializers.add(new SuperInitializer( + superConstructor.member as Constructor, + new Arguments.forwarded( + constructor.function, libraryBuilder.library)) + ..parent = constructor); + } + synthesizedDefaultConstructorBuilder = null; } } diff --git a/pkg/front_end/lib/src/fasta/source/source_library_builder.dart b/pkg/front_end/lib/src/fasta/source/source_library_builder.dart index 28267eeccea86..0ef86e705d850 100644 --- a/pkg/front_end/lib/src/fasta/source/source_library_builder.dart +++ b/pkg/front_end/lib/src/fasta/source/source_library_builder.dart @@ -2103,7 +2103,8 @@ class SourceLibraryBuilder extends LibraryBuilderImpl { List? typeVariables, int modifiers: 0, List? interfaces, - required bool isMacro}) { + required bool isMacro, + bool isEnumMixin: false}) { if (name == null) { // The following parameters should only be used when building a named // mixin application. @@ -2322,7 +2323,8 @@ class SourceLibraryBuilder extends LibraryBuilderImpl { charEndOffset, referencesFromIndexedClass, mixedInTypeBuilder: isMixinDeclaration ? null : mixin, - isMacro: isNamedMixinApplication && isMacro); + isMacro: isNamedMixinApplication && isMacro, + isEnumMixin: isEnumMixin && i == 0); // TODO(ahe, kmillikin): Should always be true? // pkg/analyzer/test/src/summary/resynthesize_kernel_test.dart can't // handle that :( @@ -2791,6 +2793,7 @@ class SourceLibraryBuilder extends LibraryBuilderImpl { List? metadata, String name, List? typeVariables, + TypeBuilder? supertypeBuilder, List? enumConstantInfos, int startCharOffset, int charOffset, @@ -2812,6 +2815,9 @@ class SourceLibraryBuilder extends LibraryBuilderImpl { metadata, name, typeVariables, + applyMixins(supertypeBuilder, startCharOffset, charOffset, + charEndOffset, name, /* isMixinDeclaration = */ false, + typeVariables: typeVariables, isMacro: false, isEnumMixin: true), enumConstantInfos, this, startCharOffset, diff --git a/pkg/front_end/test/spell_checking_list_code.txt b/pkg/front_end/test/spell_checking_list_code.txt index 7cfd23ad122e4..e105550974681 100644 --- a/pkg/front_end/test/spell_checking_list_code.txt +++ b/pkg/front_end/test/spell_checking_list_code.txt @@ -353,6 +353,7 @@ destructive deterministic dev device +dictates diff differs diffs @@ -735,6 +736,7 @@ lf lhs lib libs +lifetime lifted lifter limiting @@ -1074,6 +1076,7 @@ recompiled recompiling recompute recomputed +reconstruct recorder recoveries recreate @@ -1375,6 +1378,7 @@ toplevel topological tops tput +traced tracker traditional transformers @@ -1602,6 +1606,7 @@ yielding yields yn ynon +yourself ypotentially ys yss @@ -1613,3 +1618,4 @@ zipped zn zone zoned +zones diff --git a/pkg/front_end/test/spell_checking_list_common.txt b/pkg/front_end/test/spell_checking_list_common.txt index 3f775f29eff65..09b0c930ca9bb 100644 --- a/pkg/front_end/test/spell_checking_list_common.txt +++ b/pkg/front_end/test/spell_checking_list_common.txt @@ -1029,6 +1029,7 @@ entity's entries entry enum +enum's enumerated enumeration enumerations diff --git a/pkg/front_end/testcases/enhanced_enums/simple_mixins.dart b/pkg/front_end/testcases/enhanced_enums/simple_mixins.dart new file mode 100644 index 0000000000000..81e592e32ec2c --- /dev/null +++ b/pkg/front_end/testcases/enhanced_enums/simple_mixins.dart @@ -0,0 +1,51 @@ +// Copyright (c) 2022, 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. + +class A { + String get foo => "foo"; +} + +class B { + int bar() => 42; +} + +mixin M { + void set callOnAssignment(void Function() f) { + f(); + } +} + +enum E1 with A { one, two } + +enum E2 with A, B { one, two } + +enum E3 with M { one, two } + +expectEquals(x, y) { + if (x != y) { + throw "Expected '$x' and '$y' to be equal."; + } +} + +expectThrows(void Function() f) { + try { + f(); + throw "Expected function to throw."; + } catch (e) {} +} + +void throwOnCall() { + throw 42; +} + +main() { + expectEquals(E1.one.foo, "foo"); + expectEquals(E1.two.foo, "foo"); + expectEquals(E2.one.foo, "foo"); + expectEquals(E2.two.foo, "foo"); + expectEquals(E2.one.bar(), "bar"); + expectEquals(E2.two.bar(), "bar"); + expectThrows(E3.one.callOnAssignment = throwOnCall); + expectThrows(E3.two.callOnAssignment = throwOnCall); +} diff --git a/pkg/front_end/testcases/enhanced_enums/simple_mixins.dart.strong.expect b/pkg/front_end/testcases/enhanced_enums/simple_mixins.dart.strong.expect new file mode 100644 index 0000000000000..6e10e5c0e072d --- /dev/null +++ b/pkg/front_end/testcases/enhanced_enums/simple_mixins.dart.strong.expect @@ -0,0 +1,137 @@ +library /*isNonNullableByDefault*/; +import self as self; +import "dart:core" as core; + +class A extends core::Object { + synthetic constructor •() → self::A + : super core::Object::•() + ; + get foo() → core::String + return "foo"; +} +class B extends core::Object { + synthetic constructor •() → self::B + : super core::Object::•() + ; + method bar() → core::int + return 42; +} +abstract class M extends core::Object /*isMixinDeclaration*/ { + set callOnAssignment(() → void f) → void { + f(){() → void}; + } +} +abstract class _E1&_Enum&A = core::_Enum with self::A /*isAnonymousMixin,hasConstConstructor*/ { + const synthetic constructor •(core::int index, core::String _name) → self::_E1&_Enum&A + : super core::_Enum::•(index, _name) + ; + mixin-super-stub get foo() → core::String + return super.{self::A::foo}; +} +class E1 extends self::_E1&_Enum&A /*isEnum*/ { + static const field core::List values = #C7; + static const field self::E1 one = #C3; + static const field self::E1 two = #C6; + const constructor •(core::int index, core::String name) → self::E1 + : super self::_E1&_Enum&A::•(index, name) + ; + method toString() → core::String + ; +} +abstract class _E2&_Enum&A = core::_Enum with self::A /*isAnonymousMixin,hasConstConstructor*/ { + const synthetic constructor •(core::int index, core::String _name) → self::_E2&_Enum&A + : super core::_Enum::•(index, _name) + ; + mixin-super-stub get foo() → core::String + return super.{self::A::foo}; +} +abstract class _E2&_Enum&A&B = self::_E2&_Enum&A with self::B /*isAnonymousMixin,hasConstConstructor*/ { + const synthetic constructor •(core::int index, core::String _name) → self::_E2&_Enum&A&B + : super self::_E2&_Enum&A::•(index, _name) + ; + mixin-super-stub method bar() → core::int + return super.{self::B::bar}(); +} +class E2 extends self::_E2&_Enum&A&B /*isEnum*/ { + static const field core::List values = #C10; + static const field self::E2 one = #C8; + static const field self::E2 two = #C9; + const constructor •(core::int index, core::String name) → self::E2 + : super self::_E2&_Enum&A&B::•(index, name) + ; + method toString() → core::String + ; +} +abstract class _E3&_Enum&M = core::_Enum with self::M /*isAnonymousMixin,hasConstConstructor*/ { + const synthetic constructor •(core::int index, core::String _name) → self::_E3&_Enum&M + : super core::_Enum::•(index, _name) + ; + mixin-super-stub set callOnAssignment(() → void f) → void + return super.{self::M::callOnAssignment} = f; +} +class E3 extends self::_E3&_Enum&M /*isEnum*/ { + static const field core::List values = #C13; + static const field self::E3 one = #C11; + static const field self::E3 two = #C12; + const constructor •(core::int index, core::String name) → self::E3 + : super self::_E3&_Enum&M::•(index, name) + ; + method toString() → core::String + ; +} +static method expectEquals(dynamic x, dynamic y) → dynamic { + if(!(x =={core::Object::==}{(core::Object) → core::bool} y)) { + throw "Expected '${x}' and '${y}' to be equal."; + } +} +static method expectThrows(() → void f) → dynamic { + try { + f(){() → void}; + throw "Expected function to throw."; + } + on core::Object catch(final core::Object e) { + } +} +static method throwOnCall() → void { + throw 42; +} +static method main() → dynamic { + self::expectEquals(#C3.{self::_E1&_Enum&A::foo}{core::String}, "foo"); + self::expectEquals(#C6.{self::_E1&_Enum&A::foo}{core::String}, "foo"); + self::expectEquals(#C8.{self::_E2&_Enum&A::foo}{core::String}, "foo"); + self::expectEquals(#C9.{self::_E2&_Enum&A::foo}{core::String}, "foo"); + self::expectEquals(#C8.{self::_E2&_Enum&A&B::bar}(){() → core::int}, "bar"); + self::expectEquals(#C9.{self::_E2&_Enum&A&B::bar}(){() → core::int}, "bar"); + self::expectThrows(#C11.{self::_E3&_Enum&M::callOnAssignment} = #C14); + self::expectThrows(#C12.{self::_E3&_Enum&M::callOnAssignment} = #C14); +} + +constants { + #C1 = 0 + #C2 = "one" + #C3 = self::E1 {index:#C1, _name:#C2} + #C4 = 1 + #C5 = "two" + #C6 = self::E1 {index:#C4, _name:#C5} + #C7 = [#C3, #C6] + #C8 = self::E2 {index:#C1, _name:#C2} + #C9 = self::E2 {index:#C4, _name:#C5} + #C10 = [#C8, #C9] + #C11 = self::E3 {index:#C1, _name:#C2} + #C12 = self::E3 {index:#C4, _name:#C5} + #C13 = [#C11, #C12] + #C14 = static-tearoff self::throwOnCall +} + + +Constructor coverage from constants: +org-dartlang-testcase:///simple_mixins.dart: +- E1. (from org-dartlang-testcase:///simple_mixins.dart:19:6) +- _E1&_Enum&A. (from org-dartlang-testcase:///simple_mixins.dart:19:6) +- _Enum. (from org-dartlang-sdk:///sdk/lib/core/enum.dart:76:9) +- Object. (from org-dartlang-sdk:///sdk/lib/core/object.dart:25:9) +- E2. (from org-dartlang-testcase:///simple_mixins.dart:21:6) +- _E2&_Enum&A&B. (from org-dartlang-testcase:///simple_mixins.dart:21:6) +- _E2&_Enum&A. (from org-dartlang-testcase:///simple_mixins.dart:21:6) +- E3. (from org-dartlang-testcase:///simple_mixins.dart:23:6) +- _E3&_Enum&M. (from org-dartlang-testcase:///simple_mixins.dart:23:6) diff --git a/pkg/front_end/testcases/enhanced_enums/simple_mixins.dart.strong.transformed.expect b/pkg/front_end/testcases/enhanced_enums/simple_mixins.dart.strong.transformed.expect new file mode 100644 index 0000000000000..4a5994bde82e6 --- /dev/null +++ b/pkg/front_end/testcases/enhanced_enums/simple_mixins.dart.strong.transformed.expect @@ -0,0 +1,138 @@ +library /*isNonNullableByDefault*/; +import self as self; +import "dart:core" as core; + +class A extends core::Object { + synthetic constructor •() → self::A + : super core::Object::•() + ; + get foo() → core::String + return "foo"; +} +class B extends core::Object { + synthetic constructor •() → self::B + : super core::Object::•() + ; + method bar() → core::int + return 42; +} +abstract class M extends core::Object /*isMixinDeclaration*/ { + set callOnAssignment(() → void f) → void { + f(){() → void}; + } +} +abstract class _E1&_Enum&A extends core::_Enum implements self::A /*isAnonymousMixin,isEliminatedMixin,hasConstConstructor*/ { + const synthetic constructor •(core::int index, core::String _name) → self::_E1&_Enum&A + : super core::_Enum::•(index, _name) + ; + get foo() → core::String + return "foo"; +} +class E1 extends self::_E1&_Enum&A /*isEnum*/ { + static const field core::List values = #C7; + static const field self::E1 one = #C3; + static const field self::E1 two = #C6; + const constructor •(core::int index, core::String name) → self::E1 + : super self::_E1&_Enum&A::•(index, name) + ; + method toString() → core::String + ; +} +abstract class _E2&_Enum&A extends core::_Enum implements self::A /*isAnonymousMixin,isEliminatedMixin,hasConstConstructor*/ { + const synthetic constructor •(core::int index, core::String _name) → self::_E2&_Enum&A + : super core::_Enum::•(index, _name) + ; + get foo() → core::String + return "foo"; +} +abstract class _E2&_Enum&A&B extends self::_E2&_Enum&A implements self::B /*isAnonymousMixin,isEliminatedMixin,hasConstConstructor*/ { + const synthetic constructor •(core::int index, core::String _name) → self::_E2&_Enum&A&B + : super self::_E2&_Enum&A::•(index, _name) + ; + method bar() → core::int + return 42; +} +class E2 extends self::_E2&_Enum&A&B /*isEnum*/ { + static const field core::List values = #C10; + static const field self::E2 one = #C8; + static const field self::E2 two = #C9; + const constructor •(core::int index, core::String name) → self::E2 + : super self::_E2&_Enum&A&B::•(index, name) + ; + method toString() → core::String + ; +} +abstract class _E3&_Enum&M extends core::_Enum implements self::M /*isAnonymousMixin,isEliminatedMixin,hasConstConstructor*/ { + const synthetic constructor •(core::int index, core::String _name) → self::_E3&_Enum&M + : super core::_Enum::•(index, _name) + ; + set callOnAssignment(() → void f) → void { + f(){() → void}; + } +} +class E3 extends self::_E3&_Enum&M /*isEnum*/ { + static const field core::List values = #C13; + static const field self::E3 one = #C11; + static const field self::E3 two = #C12; + const constructor •(core::int index, core::String name) → self::E3 + : super self::_E3&_Enum&M::•(index, name) + ; + method toString() → core::String + ; +} +static method expectEquals(dynamic x, dynamic y) → dynamic { + if(!(x =={core::Object::==}{(core::Object) → core::bool} y)) { + throw "Expected '${x}' and '${y}' to be equal."; + } +} +static method expectThrows(() → void f) → dynamic { + try { + f(){() → void}; + throw "Expected function to throw."; + } + on core::Object catch(final core::Object e) { + } +} +static method throwOnCall() → void { + throw 42; +} +static method main() → dynamic { + self::expectEquals(#C3.{self::_E1&_Enum&A::foo}{core::String}, "foo"); + self::expectEquals(#C6.{self::_E1&_Enum&A::foo}{core::String}, "foo"); + self::expectEquals(#C8.{self::_E2&_Enum&A::foo}{core::String}, "foo"); + self::expectEquals(#C9.{self::_E2&_Enum&A::foo}{core::String}, "foo"); + self::expectEquals(#C8.{self::_E2&_Enum&A&B::bar}(){() → core::int}, "bar"); + self::expectEquals(#C9.{self::_E2&_Enum&A&B::bar}(){() → core::int}, "bar"); + self::expectThrows(#C11.{self::_E3&_Enum&M::callOnAssignment} = #C14); + self::expectThrows(#C12.{self::_E3&_Enum&M::callOnAssignment} = #C14); +} + +constants { + #C1 = 0 + #C2 = "one" + #C3 = self::E1 {index:#C1, _name:#C2} + #C4 = 1 + #C5 = "two" + #C6 = self::E1 {index:#C4, _name:#C5} + #C7 = [#C3, #C6] + #C8 = self::E2 {index:#C1, _name:#C2} + #C9 = self::E2 {index:#C4, _name:#C5} + #C10 = [#C8, #C9] + #C11 = self::E3 {index:#C1, _name:#C2} + #C12 = self::E3 {index:#C4, _name:#C5} + #C13 = [#C11, #C12] + #C14 = static-tearoff self::throwOnCall +} + + +Constructor coverage from constants: +org-dartlang-testcase:///simple_mixins.dart: +- E1. (from org-dartlang-testcase:///simple_mixins.dart:19:6) +- _E1&_Enum&A. (from org-dartlang-testcase:///simple_mixins.dart:19:6) +- _Enum. (from org-dartlang-sdk:///sdk/lib/core/enum.dart:76:9) +- Object. (from org-dartlang-sdk:///sdk/lib/core/object.dart:25:9) +- E2. (from org-dartlang-testcase:///simple_mixins.dart:21:6) +- _E2&_Enum&A&B. (from org-dartlang-testcase:///simple_mixins.dart:21:6) +- _E2&_Enum&A. (from org-dartlang-testcase:///simple_mixins.dart:21:6) +- E3. (from org-dartlang-testcase:///simple_mixins.dart:23:6) +- _E3&_Enum&M. (from org-dartlang-testcase:///simple_mixins.dart:23:6) diff --git a/pkg/front_end/testcases/enhanced_enums/simple_mixins.dart.textual_outline.expect b/pkg/front_end/testcases/enhanced_enums/simple_mixins.dart.textual_outline.expect new file mode 100644 index 0000000000000..8e3abd89d3140 --- /dev/null +++ b/pkg/front_end/testcases/enhanced_enums/simple_mixins.dart.textual_outline.expect @@ -0,0 +1,16 @@ +class A { + String get foo => "foo"; +} +class B { + int bar() => 42; +} +mixin M { + void set callOnAssignment(void Function() f) {} +} +enum E1 with A { one, two } +enum E2 with A, B { one, two } +enum E3 with M { one, two } +expectEquals(x, y) {} +expectThrows(void Function() f) {} +void throwOnCall() {} +main() {} diff --git a/pkg/front_end/testcases/enhanced_enums/simple_mixins.dart.weak.expect b/pkg/front_end/testcases/enhanced_enums/simple_mixins.dart.weak.expect new file mode 100644 index 0000000000000..82c66e8e3edcf --- /dev/null +++ b/pkg/front_end/testcases/enhanced_enums/simple_mixins.dart.weak.expect @@ -0,0 +1,137 @@ +library /*isNonNullableByDefault*/; +import self as self; +import "dart:core" as core; + +class A extends core::Object { + synthetic constructor •() → self::A + : super core::Object::•() + ; + get foo() → core::String + return "foo"; +} +class B extends core::Object { + synthetic constructor •() → self::B + : super core::Object::•() + ; + method bar() → core::int + return 42; +} +abstract class M extends core::Object /*isMixinDeclaration*/ { + set callOnAssignment(() → void f) → void { + f(){() → void}; + } +} +abstract class _E1&_Enum&A = core::_Enum with self::A /*isAnonymousMixin,hasConstConstructor*/ { + const synthetic constructor •(core::int index, core::String _name) → self::_E1&_Enum&A + : super core::_Enum::•(index, _name) + ; + mixin-super-stub get foo() → core::String + return super.{self::A::foo}; +} +class E1 extends self::_E1&_Enum&A /*isEnum*/ { + static const field core::List values = #C7; + static const field self::E1 one = #C3; + static const field self::E1 two = #C6; + const constructor •(core::int index, core::String name) → self::E1 + : super self::_E1&_Enum&A::•(index, name) + ; + method toString() → core::String + ; +} +abstract class _E2&_Enum&A = core::_Enum with self::A /*isAnonymousMixin,hasConstConstructor*/ { + const synthetic constructor •(core::int index, core::String _name) → self::_E2&_Enum&A + : super core::_Enum::•(index, _name) + ; + mixin-super-stub get foo() → core::String + return super.{self::A::foo}; +} +abstract class _E2&_Enum&A&B = self::_E2&_Enum&A with self::B /*isAnonymousMixin,hasConstConstructor*/ { + const synthetic constructor •(core::int index, core::String _name) → self::_E2&_Enum&A&B + : super self::_E2&_Enum&A::•(index, _name) + ; + mixin-super-stub method bar() → core::int + return super.{self::B::bar}(); +} +class E2 extends self::_E2&_Enum&A&B /*isEnum*/ { + static const field core::List values = #C10; + static const field self::E2 one = #C8; + static const field self::E2 two = #C9; + const constructor •(core::int index, core::String name) → self::E2 + : super self::_E2&_Enum&A&B::•(index, name) + ; + method toString() → core::String + ; +} +abstract class _E3&_Enum&M = core::_Enum with self::M /*isAnonymousMixin,hasConstConstructor*/ { + const synthetic constructor •(core::int index, core::String _name) → self::_E3&_Enum&M + : super core::_Enum::•(index, _name) + ; + mixin-super-stub set callOnAssignment(() → void f) → void + return super.{self::M::callOnAssignment} = f; +} +class E3 extends self::_E3&_Enum&M /*isEnum*/ { + static const field core::List values = #C13; + static const field self::E3 one = #C11; + static const field self::E3 two = #C12; + const constructor •(core::int index, core::String name) → self::E3 + : super self::_E3&_Enum&M::•(index, name) + ; + method toString() → core::String + ; +} +static method expectEquals(dynamic x, dynamic y) → dynamic { + if(!(x =={core::Object::==}{(core::Object) → core::bool} y)) { + throw "Expected '${x}' and '${y}' to be equal."; + } +} +static method expectThrows(() → void f) → dynamic { + try { + f(){() → void}; + throw "Expected function to throw."; + } + on core::Object catch(final core::Object e) { + } +} +static method throwOnCall() → void { + throw 42; +} +static method main() → dynamic { + self::expectEquals(#C3.{self::_E1&_Enum&A::foo}{core::String}, "foo"); + self::expectEquals(#C6.{self::_E1&_Enum&A::foo}{core::String}, "foo"); + self::expectEquals(#C8.{self::_E2&_Enum&A::foo}{core::String}, "foo"); + self::expectEquals(#C9.{self::_E2&_Enum&A::foo}{core::String}, "foo"); + self::expectEquals(#C8.{self::_E2&_Enum&A&B::bar}(){() → core::int}, "bar"); + self::expectEquals(#C9.{self::_E2&_Enum&A&B::bar}(){() → core::int}, "bar"); + self::expectThrows(#C11.{self::_E3&_Enum&M::callOnAssignment} = #C14); + self::expectThrows(#C12.{self::_E3&_Enum&M::callOnAssignment} = #C14); +} + +constants { + #C1 = 0 + #C2 = "one" + #C3 = self::E1 {index:#C1, _name:#C2} + #C4 = 1 + #C5 = "two" + #C6 = self::E1 {index:#C4, _name:#C5} + #C7 = [#C3, #C6] + #C8 = self::E2 {index:#C1, _name:#C2} + #C9 = self::E2 {index:#C4, _name:#C5} + #C10 = [#C8, #C9] + #C11 = self::E3 {index:#C1, _name:#C2} + #C12 = self::E3 {index:#C4, _name:#C5} + #C13 = [#C11, #C12] + #C14 = static-tearoff self::throwOnCall +} + + +Constructor coverage from constants: +org-dartlang-testcase:///simple_mixins.dart: +- E1. (from org-dartlang-testcase:///simple_mixins.dart:19:6) +- _E1&_Enum&A. (from org-dartlang-testcase:///simple_mixins.dart:19:6) +- _Enum. (from org-dartlang-sdk:///sdk/lib/core/enum.dart:76:9) +- Object. (from org-dartlang-sdk:///sdk/lib/core/object.dart:25:9) +- E2. (from org-dartlang-testcase:///simple_mixins.dart:21:6) +- _E2&_Enum&A&B. (from org-dartlang-testcase:///simple_mixins.dart:21:6) +- _E2&_Enum&A. (from org-dartlang-testcase:///simple_mixins.dart:21:6) +- E3. (from org-dartlang-testcase:///simple_mixins.dart:23:6) +- _E3&_Enum&M. (from org-dartlang-testcase:///simple_mixins.dart:23:6) diff --git a/pkg/front_end/testcases/enhanced_enums/simple_mixins.dart.weak.modular.expect b/pkg/front_end/testcases/enhanced_enums/simple_mixins.dart.weak.modular.expect new file mode 100644 index 0000000000000..82c66e8e3edcf --- /dev/null +++ b/pkg/front_end/testcases/enhanced_enums/simple_mixins.dart.weak.modular.expect @@ -0,0 +1,137 @@ +library /*isNonNullableByDefault*/; +import self as self; +import "dart:core" as core; + +class A extends core::Object { + synthetic constructor •() → self::A + : super core::Object::•() + ; + get foo() → core::String + return "foo"; +} +class B extends core::Object { + synthetic constructor •() → self::B + : super core::Object::•() + ; + method bar() → core::int + return 42; +} +abstract class M extends core::Object /*isMixinDeclaration*/ { + set callOnAssignment(() → void f) → void { + f(){() → void}; + } +} +abstract class _E1&_Enum&A = core::_Enum with self::A /*isAnonymousMixin,hasConstConstructor*/ { + const synthetic constructor •(core::int index, core::String _name) → self::_E1&_Enum&A + : super core::_Enum::•(index, _name) + ; + mixin-super-stub get foo() → core::String + return super.{self::A::foo}; +} +class E1 extends self::_E1&_Enum&A /*isEnum*/ { + static const field core::List values = #C7; + static const field self::E1 one = #C3; + static const field self::E1 two = #C6; + const constructor •(core::int index, core::String name) → self::E1 + : super self::_E1&_Enum&A::•(index, name) + ; + method toString() → core::String + ; +} +abstract class _E2&_Enum&A = core::_Enum with self::A /*isAnonymousMixin,hasConstConstructor*/ { + const synthetic constructor •(core::int index, core::String _name) → self::_E2&_Enum&A + : super core::_Enum::•(index, _name) + ; + mixin-super-stub get foo() → core::String + return super.{self::A::foo}; +} +abstract class _E2&_Enum&A&B = self::_E2&_Enum&A with self::B /*isAnonymousMixin,hasConstConstructor*/ { + const synthetic constructor •(core::int index, core::String _name) → self::_E2&_Enum&A&B + : super self::_E2&_Enum&A::•(index, _name) + ; + mixin-super-stub method bar() → core::int + return super.{self::B::bar}(); +} +class E2 extends self::_E2&_Enum&A&B /*isEnum*/ { + static const field core::List values = #C10; + static const field self::E2 one = #C8; + static const field self::E2 two = #C9; + const constructor •(core::int index, core::String name) → self::E2 + : super self::_E2&_Enum&A&B::•(index, name) + ; + method toString() → core::String + ; +} +abstract class _E3&_Enum&M = core::_Enum with self::M /*isAnonymousMixin,hasConstConstructor*/ { + const synthetic constructor •(core::int index, core::String _name) → self::_E3&_Enum&M + : super core::_Enum::•(index, _name) + ; + mixin-super-stub set callOnAssignment(() → void f) → void + return super.{self::M::callOnAssignment} = f; +} +class E3 extends self::_E3&_Enum&M /*isEnum*/ { + static const field core::List values = #C13; + static const field self::E3 one = #C11; + static const field self::E3 two = #C12; + const constructor •(core::int index, core::String name) → self::E3 + : super self::_E3&_Enum&M::•(index, name) + ; + method toString() → core::String + ; +} +static method expectEquals(dynamic x, dynamic y) → dynamic { + if(!(x =={core::Object::==}{(core::Object) → core::bool} y)) { + throw "Expected '${x}' and '${y}' to be equal."; + } +} +static method expectThrows(() → void f) → dynamic { + try { + f(){() → void}; + throw "Expected function to throw."; + } + on core::Object catch(final core::Object e) { + } +} +static method throwOnCall() → void { + throw 42; +} +static method main() → dynamic { + self::expectEquals(#C3.{self::_E1&_Enum&A::foo}{core::String}, "foo"); + self::expectEquals(#C6.{self::_E1&_Enum&A::foo}{core::String}, "foo"); + self::expectEquals(#C8.{self::_E2&_Enum&A::foo}{core::String}, "foo"); + self::expectEquals(#C9.{self::_E2&_Enum&A::foo}{core::String}, "foo"); + self::expectEquals(#C8.{self::_E2&_Enum&A&B::bar}(){() → core::int}, "bar"); + self::expectEquals(#C9.{self::_E2&_Enum&A&B::bar}(){() → core::int}, "bar"); + self::expectThrows(#C11.{self::_E3&_Enum&M::callOnAssignment} = #C14); + self::expectThrows(#C12.{self::_E3&_Enum&M::callOnAssignment} = #C14); +} + +constants { + #C1 = 0 + #C2 = "one" + #C3 = self::E1 {index:#C1, _name:#C2} + #C4 = 1 + #C5 = "two" + #C6 = self::E1 {index:#C4, _name:#C5} + #C7 = [#C3, #C6] + #C8 = self::E2 {index:#C1, _name:#C2} + #C9 = self::E2 {index:#C4, _name:#C5} + #C10 = [#C8, #C9] + #C11 = self::E3 {index:#C1, _name:#C2} + #C12 = self::E3 {index:#C4, _name:#C5} + #C13 = [#C11, #C12] + #C14 = static-tearoff self::throwOnCall +} + + +Constructor coverage from constants: +org-dartlang-testcase:///simple_mixins.dart: +- E1. (from org-dartlang-testcase:///simple_mixins.dart:19:6) +- _E1&_Enum&A. (from org-dartlang-testcase:///simple_mixins.dart:19:6) +- _Enum. (from org-dartlang-sdk:///sdk/lib/core/enum.dart:76:9) +- Object. (from org-dartlang-sdk:///sdk/lib/core/object.dart:25:9) +- E2. (from org-dartlang-testcase:///simple_mixins.dart:21:6) +- _E2&_Enum&A&B. (from org-dartlang-testcase:///simple_mixins.dart:21:6) +- _E2&_Enum&A. (from org-dartlang-testcase:///simple_mixins.dart:21:6) +- E3. (from org-dartlang-testcase:///simple_mixins.dart:23:6) +- _E3&_Enum&M. (from org-dartlang-testcase:///simple_mixins.dart:23:6) diff --git a/pkg/front_end/testcases/enhanced_enums/simple_mixins.dart.weak.outline.expect b/pkg/front_end/testcases/enhanced_enums/simple_mixins.dart.weak.outline.expect new file mode 100644 index 0000000000000..abd958fccdda9 --- /dev/null +++ b/pkg/front_end/testcases/enhanced_enums/simple_mixins.dart.weak.outline.expect @@ -0,0 +1,96 @@ +library /*isNonNullableByDefault*/; +import self as self; +import "dart:core" as core; + +class A extends core::Object { + synthetic constructor •() → self::A + ; + get foo() → core::String + ; +} +class B extends core::Object { + synthetic constructor •() → self::B + ; + method bar() → core::int + ; +} +abstract class M extends core::Object /*isMixinDeclaration*/ { + set callOnAssignment(() → void f) → void + ; +} +abstract class _E1&_Enum&A = core::_Enum with self::A /*isAnonymousMixin,hasConstConstructor*/ { + const synthetic constructor •(core::int index, core::String _name) → self::_E1&_Enum&A + : super core::_Enum::•(index, _name) + ; + mixin-super-stub get foo() → core::String + return super.{self::A::foo}; +} +class E1 extends self::_E1&_Enum&A /*isEnum*/ { + static const field core::List values = const [self::E1::one, self::E1::two]; + static const field self::E1 one = const self::E1::•(0, "one"); + static const field self::E1 two = const self::E1::•(1, "two"); + const constructor •(core::int index, core::String name) → self::E1 + ; + method toString() → core::String + ; +} +abstract class _E2&_Enum&A = core::_Enum with self::A /*isAnonymousMixin,hasConstConstructor*/ { + const synthetic constructor •(core::int index, core::String _name) → self::_E2&_Enum&A + : super core::_Enum::•(index, _name) + ; + mixin-super-stub get foo() → core::String + return super.{self::A::foo}; +} +abstract class _E2&_Enum&A&B = self::_E2&_Enum&A with self::B /*isAnonymousMixin,hasConstConstructor*/ { + const synthetic constructor •(core::int index, core::String _name) → self::_E2&_Enum&A&B + : super self::_E2&_Enum&A::•(index, _name) + ; + mixin-super-stub method bar() → core::int + return super.{self::B::bar}(); +} +class E2 extends self::_E2&_Enum&A&B /*isEnum*/ { + static const field core::List values = const [self::E2::one, self::E2::two]; + static const field self::E2 one = const self::E2::•(0, "one"); + static const field self::E2 two = const self::E2::•(1, "two"); + const constructor •(core::int index, core::String name) → self::E2 + ; + method toString() → core::String + ; +} +abstract class _E3&_Enum&M = core::_Enum with self::M /*isAnonymousMixin,hasConstConstructor*/ { + const synthetic constructor •(core::int index, core::String _name) → self::_E3&_Enum&M + : super core::_Enum::•(index, _name) + ; + mixin-super-stub set callOnAssignment(() → void f) → void + return super.{self::M::callOnAssignment} = f; +} +class E3 extends self::_E3&_Enum&M /*isEnum*/ { + static const field core::List values = const [self::E3::one, self::E3::two]; + static const field self::E3 one = const self::E3::•(0, "one"); + static const field self::E3 two = const self::E3::•(1, "two"); + const constructor •(core::int index, core::String name) → self::E3 + ; + method toString() → core::String + ; +} +static method expectEquals(dynamic x, dynamic y) → dynamic + ; +static method expectThrows(() → void f) → dynamic + ; +static method throwOnCall() → void + ; +static method main() → dynamic + ; + + +Extra constant evaluation status: +Evaluated: ListLiteral @ org-dartlang-testcase:///simple_mixins.dart:19:6 -> ListConstant(const [const E1{}, const E1{}]) +Evaluated: ConstructorInvocation @ org-dartlang-testcase:///simple_mixins.dart:19:18 -> InstanceConstant(const E1{}) +Evaluated: ConstructorInvocation @ org-dartlang-testcase:///simple_mixins.dart:19:23 -> InstanceConstant(const E1{}) +Evaluated: ListLiteral @ org-dartlang-testcase:///simple_mixins.dart:21:6 -> ListConstant(const [const E2{}, const E2{}]) +Evaluated: ConstructorInvocation @ org-dartlang-testcase:///simple_mixins.dart:21:21 -> InstanceConstant(const E2{}) +Evaluated: ConstructorInvocation @ org-dartlang-testcase:///simple_mixins.dart:21:26 -> InstanceConstant(const E2{}) +Evaluated: ListLiteral @ org-dartlang-testcase:///simple_mixins.dart:23:6 -> ListConstant(const [const E3{}, const E3{}]) +Evaluated: ConstructorInvocation @ org-dartlang-testcase:///simple_mixins.dart:23:18 -> InstanceConstant(const E3{}) +Evaluated: ConstructorInvocation @ org-dartlang-testcase:///simple_mixins.dart:23:23 -> InstanceConstant(const E3{}) +Extra constant evaluation: evaluated: 22, effectively constant: 9 diff --git a/pkg/front_end/testcases/enhanced_enums/simple_mixins.dart.weak.transformed.expect b/pkg/front_end/testcases/enhanced_enums/simple_mixins.dart.weak.transformed.expect new file mode 100644 index 0000000000000..af40a65860fd4 --- /dev/null +++ b/pkg/front_end/testcases/enhanced_enums/simple_mixins.dart.weak.transformed.expect @@ -0,0 +1,138 @@ +library /*isNonNullableByDefault*/; +import self as self; +import "dart:core" as core; + +class A extends core::Object { + synthetic constructor •() → self::A + : super core::Object::•() + ; + get foo() → core::String + return "foo"; +} +class B extends core::Object { + synthetic constructor •() → self::B + : super core::Object::•() + ; + method bar() → core::int + return 42; +} +abstract class M extends core::Object /*isMixinDeclaration*/ { + set callOnAssignment(() → void f) → void { + f(){() → void}; + } +} +abstract class _E1&_Enum&A extends core::_Enum implements self::A /*isAnonymousMixin,isEliminatedMixin,hasConstConstructor*/ { + const synthetic constructor •(core::int index, core::String _name) → self::_E1&_Enum&A + : super core::_Enum::•(index, _name) + ; + get foo() → core::String + return "foo"; +} +class E1 extends self::_E1&_Enum&A /*isEnum*/ { + static const field core::List values = #C7; + static const field self::E1 one = #C3; + static const field self::E1 two = #C6; + const constructor •(core::int index, core::String name) → self::E1 + : super self::_E1&_Enum&A::•(index, name) + ; + method toString() → core::String + ; +} +abstract class _E2&_Enum&A extends core::_Enum implements self::A /*isAnonymousMixin,isEliminatedMixin,hasConstConstructor*/ { + const synthetic constructor •(core::int index, core::String _name) → self::_E2&_Enum&A + : super core::_Enum::•(index, _name) + ; + get foo() → core::String + return "foo"; +} +abstract class _E2&_Enum&A&B extends self::_E2&_Enum&A implements self::B /*isAnonymousMixin,isEliminatedMixin,hasConstConstructor*/ { + const synthetic constructor •(core::int index, core::String _name) → self::_E2&_Enum&A&B + : super self::_E2&_Enum&A::•(index, _name) + ; + method bar() → core::int + return 42; +} +class E2 extends self::_E2&_Enum&A&B /*isEnum*/ { + static const field core::List values = #C10; + static const field self::E2 one = #C8; + static const field self::E2 two = #C9; + const constructor •(core::int index, core::String name) → self::E2 + : super self::_E2&_Enum&A&B::•(index, name) + ; + method toString() → core::String + ; +} +abstract class _E3&_Enum&M extends core::_Enum implements self::M /*isAnonymousMixin,isEliminatedMixin,hasConstConstructor*/ { + const synthetic constructor •(core::int index, core::String _name) → self::_E3&_Enum&M + : super core::_Enum::•(index, _name) + ; + set callOnAssignment(() → void f) → void { + f(){() → void}; + } +} +class E3 extends self::_E3&_Enum&M /*isEnum*/ { + static const field core::List values = #C13; + static const field self::E3 one = #C11; + static const field self::E3 two = #C12; + const constructor •(core::int index, core::String name) → self::E3 + : super self::_E3&_Enum&M::•(index, name) + ; + method toString() → core::String + ; +} +static method expectEquals(dynamic x, dynamic y) → dynamic { + if(!(x =={core::Object::==}{(core::Object) → core::bool} y)) { + throw "Expected '${x}' and '${y}' to be equal."; + } +} +static method expectThrows(() → void f) → dynamic { + try { + f(){() → void}; + throw "Expected function to throw."; + } + on core::Object catch(final core::Object e) { + } +} +static method throwOnCall() → void { + throw 42; +} +static method main() → dynamic { + self::expectEquals(#C3.{self::_E1&_Enum&A::foo}{core::String}, "foo"); + self::expectEquals(#C6.{self::_E1&_Enum&A::foo}{core::String}, "foo"); + self::expectEquals(#C8.{self::_E2&_Enum&A::foo}{core::String}, "foo"); + self::expectEquals(#C9.{self::_E2&_Enum&A::foo}{core::String}, "foo"); + self::expectEquals(#C8.{self::_E2&_Enum&A&B::bar}(){() → core::int}, "bar"); + self::expectEquals(#C9.{self::_E2&_Enum&A&B::bar}(){() → core::int}, "bar"); + self::expectThrows(#C11.{self::_E3&_Enum&M::callOnAssignment} = #C14); + self::expectThrows(#C12.{self::_E3&_Enum&M::callOnAssignment} = #C14); +} + +constants { + #C1 = 0 + #C2 = "one" + #C3 = self::E1 {index:#C1, _name:#C2} + #C4 = 1 + #C5 = "two" + #C6 = self::E1 {index:#C4, _name:#C5} + #C7 = [#C3, #C6] + #C8 = self::E2 {index:#C1, _name:#C2} + #C9 = self::E2 {index:#C4, _name:#C5} + #C10 = [#C8, #C9] + #C11 = self::E3 {index:#C1, _name:#C2} + #C12 = self::E3 {index:#C4, _name:#C5} + #C13 = [#C11, #C12] + #C14 = static-tearoff self::throwOnCall +} + + +Constructor coverage from constants: +org-dartlang-testcase:///simple_mixins.dart: +- E1. (from org-dartlang-testcase:///simple_mixins.dart:19:6) +- _E1&_Enum&A. (from org-dartlang-testcase:///simple_mixins.dart:19:6) +- _Enum. (from org-dartlang-sdk:///sdk/lib/core/enum.dart:76:9) +- Object. (from org-dartlang-sdk:///sdk/lib/core/object.dart:25:9) +- E2. (from org-dartlang-testcase:///simple_mixins.dart:21:6) +- _E2&_Enum&A&B. (from org-dartlang-testcase:///simple_mixins.dart:21:6) +- _E2&_Enum&A. (from org-dartlang-testcase:///simple_mixins.dart:21:6) +- E3. (from org-dartlang-testcase:///simple_mixins.dart:23:6) +- _E3&_Enum&M. (from org-dartlang-testcase:///simple_mixins.dart:23:6) diff --git a/pkg/front_end/testcases/strong.status b/pkg/front_end/testcases/strong.status index 2aef19d145664..f57bbdfd511bf 100644 --- a/pkg/front_end/testcases/strong.status +++ b/pkg/front_end/testcases/strong.status @@ -15,6 +15,7 @@ static_field_lowering/opt_in: SemiFuzzFailure constructor_tearoffs/call_instantiation: TypeCheckError constructor_tearoffs/lowering/invalid_redirect: VerificationError +enhanced_enums/simple_mixins: RuntimeError extension_types/access_setter_as_getter: ExpectationFileMismatchSerialized # Expected. extension_types/call_not_get: ExpectationFileMismatchSerialized # Expected. extension_types/extension_on_nullable: ExpectationFileMismatchSerialized # Expected. diff --git a/pkg/front_end/testcases/text_serialization.status b/pkg/front_end/testcases/text_serialization.status index b66d7f8d262dd..5a838fad83255 100644 --- a/pkg/front_end/testcases/text_serialization.status +++ b/pkg/front_end/testcases/text_serialization.status @@ -8,6 +8,7 @@ constructor_tearoffs/call_instantiation: TypeCheckError constructor_tearoffs/lowering/invalid_redirect: VerificationError +enhanced_enums/simple_mixins: RuntimeError extension_types/access_setter_as_getter: ExpectationFileMismatchSerialized # Expected. extension_types/call_not_get: ExpectationFileMismatchSerialized # Expected. extension_types/extension_on_nullable: ExpectationFileMismatchSerialized # Expected. diff --git a/pkg/front_end/testcases/textual_outline.status b/pkg/front_end/testcases/textual_outline.status index 6736b0845b689..0228fca65380a 100644 --- a/pkg/front_end/testcases/textual_outline.status +++ b/pkg/front_end/testcases/textual_outline.status @@ -35,6 +35,7 @@ enhanced_enums/members: FormatterCrash enhanced_enums/qualified_names_with_no_type_arguments: FormatterCrash enhanced_enums/redirecting_initializers: FormatterCrash enhanced_enums/simple_fields: FormatterCrash +enhanced_enums/simple_mixins: FormatterCrash extension_types/basic_show: FormatterCrash extension_types/call_not_get: FormatterCrash extension_types/keyword_in_show_hide_element: FormatterCrash diff --git a/pkg/front_end/testcases/weak.status b/pkg/front_end/testcases/weak.status index 8898c1a136cc9..f75594a50ec98 100644 --- a/pkg/front_end/testcases/weak.status +++ b/pkg/front_end/testcases/weak.status @@ -22,6 +22,7 @@ static_field_lowering/opt_in: SemiFuzzFailure constructor_tearoffs/call_instantiation: TypeCheckError constructor_tearoffs/lowering/invalid_redirect: VerificationError +enhanced_enums/simple_mixins: RuntimeError extension_types/access_setter_as_getter: ExpectationFileMismatchSerialized # Expected. extension_types/call_not_get: ExpectationFileMismatchSerialized # Expected. extension_types/extension_on_nullable: ExpectationFileMismatchSerialized # Expected. diff --git a/runtime/vm/compiler/asm_intrinsifier_arm64.cc b/runtime/vm/compiler/asm_intrinsifier_arm64.cc index f9c178a92b09f..4056cbf938e43 100644 --- a/runtime/vm/compiler/asm_intrinsifier_arm64.cc +++ b/runtime/vm/compiler/asm_intrinsifier_arm64.cc @@ -51,9 +51,8 @@ void AsmIntrinsifier::GrowableArray_Allocate(Assembler* assembler, R1); // Set the length field in the growable array object to 0. - __ LoadImmediate(R1, 0); __ StoreCompressedIntoObjectNoBarrier( - R0, FieldAddress(R0, target::GrowableObjectArray::length_offset()), R1); + R0, FieldAddress(R0, target::GrowableObjectArray::length_offset()), ZR); __ ret(); // Returns the newly allocated object in R0. __ Bind(normal_ir_body); diff --git a/runtime/vm/compiler/backend/il_arm64.cc b/runtime/vm/compiler/backend/il_arm64.cc index 1c5673c3509f3..979c8ba97cc54 100644 --- a/runtime/vm/compiler/backend/il_arm64.cc +++ b/runtime/vm/compiler/backend/il_arm64.cc @@ -5286,10 +5286,16 @@ void TruncDivModInstr::EmitNativeCode(FlowGraphCompiler* compiler) { __ CompareObjectRegisters(result_mod, ZR); __ b(&done, GE); // Result is negative, adjust it. - __ CompareObjectRegisters(right, ZR); - __ sub(TMP2, result_mod, compiler::Operand(right), compiler::kObjectBytes); - __ add(TMP, result_mod, compiler::Operand(right), compiler::kObjectBytes); - __ csel(result_mod, TMP, TMP2, GE); + if (RangeUtils::IsNegative(divisor_range())) { + __ sub(result_mod, result_mod, compiler::Operand(right)); + } else if (RangeUtils::IsPositive(divisor_range())) { + __ add(result_mod, result_mod, compiler::Operand(right)); + } else { + __ CompareObjectRegisters(right, ZR); + __ sub(TMP2, result_mod, compiler::Operand(right), compiler::kObjectBytes); + __ add(TMP, result_mod, compiler::Operand(right), compiler::kObjectBytes); + __ csel(result_mod, TMP, TMP2, GE); + } __ Bind(&done); } @@ -5634,8 +5640,7 @@ static void EmitInt64ModTruncDiv(FlowGraphCompiler* compiler, // Handle modulo/division by zero exception on slow path. if (slow_path->has_divide_by_zero()) { - __ CompareRegisters(right, ZR); - __ b(slow_path->entry_label(), EQ); + __ cbz(slow_path->entry_label(), right); } // Perform actual operation diff --git a/runtime/vm/compiler/backend/range_analysis.cc b/runtime/vm/compiler/backend/range_analysis.cc index 7d02f87188987..20c4ba0a28279 100644 --- a/runtime/vm/compiler/backend/range_analysis.cc +++ b/runtime/vm/compiler/backend/range_analysis.cc @@ -2082,6 +2082,10 @@ bool Range::IsPositive() const { return OnlyGreaterThanOrEqualTo(0); } +bool Range::IsNegative() const { + return OnlyLessThanOrEqualTo(-1); +} + bool Range::OnlyLessThanOrEqualTo(int64_t val) const { const RangeBoundary upper_bound = max().UpperBound(); return !upper_bound.IsPositiveInfinity() && diff --git a/runtime/vm/compiler/backend/range_analysis.h b/runtime/vm/compiler/backend/range_analysis.h index b0037f404c2f6..aa247c7168b86 100644 --- a/runtime/vm/compiler/backend/range_analysis.h +++ b/runtime/vm/compiler/backend/range_analysis.h @@ -413,6 +413,9 @@ class Range : public ZoneAllocated { // [0, +inf] bool IsPositive() const; + // [-inf, -1] + bool IsNegative() const; + // [-inf, val]. bool OnlyLessThanOrEqualTo(int64_t val) const; @@ -560,6 +563,9 @@ class RangeUtils : public AllStatic { static bool IsPositive(Range* range) { return !Range::IsUnknown(range) && range->IsPositive(); } + static bool IsNegative(Range* range) { + return !Range::IsUnknown(range) && range->IsNegative(); + } static bool Overlaps(Range* range, intptr_t min, intptr_t max) { return Range::IsUnknown(range) || range->Overlaps(min, max); diff --git a/runtime/vm/compiler/stub_code_compiler_arm64.cc b/runtime/vm/compiler/stub_code_compiler_arm64.cc index bcdc1f4a0023c..4d222049709a8 100644 --- a/runtime/vm/compiler/stub_code_compiler_arm64.cc +++ b/runtime/vm/compiler/stub_code_compiler_arm64.cc @@ -1961,15 +1961,15 @@ static void GenerateAllocateObjectHelper(Assembler* assembler, Label not_parameterized_case; const Register kClsIdReg = R4; - const Register kTypeOffestReg = R5; + const Register kTypeOffsetReg = R5; __ ExtractClassIdFromTags(kClsIdReg, kTagsReg); // Load class' type_arguments_field offset in words. - __ LoadClassById(kTypeOffestReg, kClsIdReg); + __ LoadClassById(kTypeOffsetReg, kClsIdReg); __ ldr( - kTypeOffestReg, - FieldAddress(kTypeOffestReg, + kTypeOffsetReg, + FieldAddress(kTypeOffsetReg, target::Class:: host_type_arguments_field_offset_in_words_offset()), kFourBytes); @@ -1977,7 +1977,7 @@ static void GenerateAllocateObjectHelper(Assembler* assembler, // Set the type arguments in the new object. __ StoreCompressedIntoObjectNoBarrier( AllocateObjectABI::kResultReg, - Address(AllocateObjectABI::kResultReg, kTypeOffestReg, UXTX, + Address(AllocateObjectABI::kResultReg, kTypeOffsetReg, UXTX, Address::Scaled), AllocateObjectABI::kTypeArgumentsReg); @@ -3723,9 +3723,7 @@ void StubCodeCompiler::GenerateAllocateTypedDataArrayStub(Assembler* assembler, /* R0: new object start as a tagged pointer. */ /* R1: new object end address. */ /* R2: iterator which initially points to the start of the variable */ - /* R3: scratch register. */ /* data area to be initialized. */ - __ mov(R3, ZR); __ AddImmediate(R2, R0, target::TypedData::HeaderSize() - 1); __ StoreInternalPointer( R0, FieldAddress(R0, target::TypedDataBase::data_field_offset()), R2); @@ -3733,7 +3731,7 @@ void StubCodeCompiler::GenerateAllocateTypedDataArrayStub(Assembler* assembler, __ Bind(&init_loop); __ cmp(R2, Operand(R1)); __ b(&done, CS); - __ str(R3, Address(R2, 0)); + __ str(ZR, Address(R2, 0)); __ add(R2, R2, Operand(target::kWordSize)); __ b(&init_loop); __ Bind(&done); diff --git a/sdk/lib/_internal/js_dev_runtime/private/string_helper.dart b/sdk/lib/_internal/js_dev_runtime/private/string_helper.dart index 7a0de57baf9be..474042aec3e36 100644 --- a/sdk/lib/_internal/js_dev_runtime/private/string_helper.dart +++ b/sdk/lib/_internal/js_dev_runtime/private/string_helper.dart @@ -169,8 +169,15 @@ String stringReplaceAllUnchecked(@notNull String receiver, var re = regExpGetGlobalNative(pattern); return stringReplaceJS(receiver, re, replacement); } else { - // TODO(floitsch): implement generic String.replace (with patterns). - throw "String.replaceAll(Pattern) UNIMPLEMENTED"; + int startIndex = 0; + StringBuffer result = StringBuffer(); + for (Match match in pattern.allMatches(receiver)) { + result.write(substring2Unchecked(receiver, startIndex, match.start)); + result.write(replacement); + startIndex = match.end; + } + result.write(substring1Unchecked(receiver, startIndex)); + return result.toString(); } } diff --git a/sdk/lib/_internal/js_runtime/lib/string_helper.dart b/sdk/lib/_internal/js_runtime/lib/string_helper.dart index 5e6c50d632e36..9305f55976f88 100644 --- a/sdk/lib/_internal/js_runtime/lib/string_helper.dart +++ b/sdk/lib/_internal/js_runtime/lib/string_helper.dart @@ -173,9 +173,21 @@ String stringReplaceAllUnchecked( return stringReplaceJS(receiver, re, replacement); } + return stringReplaceAllGeneral(receiver, pattern, replacement); +} + +String stringReplaceAllGeneral( + String receiver, Pattern pattern, String replacement) { checkNull(pattern); - // TODO(floitsch): implement generic String.replace (with patterns). - throw "String.replaceAll(Pattern) UNIMPLEMENTED"; + int startIndex = 0; + StringBuffer result = StringBuffer(); + for (Match match in pattern.allMatches(receiver)) { + result.write(substring2Unchecked(receiver, startIndex, match.start)); + result.write(replacement); + startIndex = match.end; + } + result.write(substring1Unchecked(receiver, startIndex)); + return result.toString(); } /// Replaces all non-overlapping occurences of [pattern] in [receiver] with diff --git a/tests/corelib/string_replace_all_common.dart b/tests/corelib/string_replace_all_common.dart new file mode 100644 index 0000000000000..4bdbff06d43fe --- /dev/null +++ b/tests/corelib/string_replace_all_common.dart @@ -0,0 +1,144 @@ +// Copyright (c) 2012, 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:expect/expect.dart"; + +testAll(Pattern Function(Pattern) wrap) { + testReplaceAll(wrap); + testReplaceAllMapped(wrap); + testSplitMapJoin(wrap); +} + +testReplaceAll(Pattern Function(Pattern) wrap) { + Expect.equals("aXXcaXXdae", "abcabdae".replaceAll(wrap("b"), "XX")); + + // Test with the replaced string at the beginning. + Expect.equals("XXbcXXbdXXe", "abcabdae".replaceAll(wrap("a"), "XX")); + + // Test with the replaced string at the end. + Expect.equals("abcabdaXX", "abcabdae".replaceAll(wrap("e"), "XX")); + + // Test when there are no occurence of the string to replace. + Expect.equals("abcabdae", "abcabdae".replaceAll(wrap("f"), "XX")); + + // Test when the string to change is the empty string. + Expect.equals("", "".replaceAll(wrap("from"), "to")); + + // Test when the string to change is a substring of the string to + // replace. + Expect.equals("fro", "fro".replaceAll(wrap("from"), "to")); + + // Test when the string to change is the replaced string. + Expect.equals("to", "from".replaceAll(wrap("from"), "to")); + + // Test when matches are adjacent + Expect.equals("toto", "fromfrom".replaceAll(wrap("from"), "to")); + + // Test when the string to change is the replacement string. + Expect.equals("to", "to".replaceAll(wrap("from"), "to")); + + // Test replacing by the empty string. + Expect.equals("bcbde", "abcabdae".replaceAll(wrap("a"), "")); + Expect.equals("AB", "AfromB".replaceAll(wrap("from"), "")); + + // Test changing the empty string. + Expect.equals("to", "".replaceAll(wrap(""), "to")); + + // Test replacing the empty string. + Expect.equals("toAtoBtoCto", "ABC".replaceAll(wrap(""), "to")); + + // Pattern strings containing RegExp metacharacters - these are not + // interpreted as RegExps. + Expect.equals(r"$$", "||".replaceAll(wrap("|"), r"$")); + Expect.equals(r"$$$$", "||".replaceAll(wrap("|"), r"$$")); + Expect.equals(r"x$|x", "x|.|x".replaceAll(wrap("|."), r"$")); + Expect.equals(r"$$", "..".replaceAll(wrap("."), r"$")); + Expect.equals(r"[$$$$]", "[..]".replaceAll(wrap("."), r"$$")); + Expect.equals(r"[$]", "[..]".replaceAll(wrap(".."), r"$")); + Expect.equals(r"$$", r"\\".replaceAll(wrap(r"\"), r"$")); +} + +testReplaceAllMapped(Pattern Function(Pattern) wrap) { + String mark(Match m) => "[${m[0]}]"; + Expect.equals("a[b]ca[b]dae", "abcabdae".replaceAllMapped(wrap("b"), mark)); + + // Test with the replaced string at the beginning. + Expect.equals("[a]bc[a]bd[a]e", "abcabdae".replaceAllMapped(wrap("a"), mark)); + + // Test with the replaced string at the end. + Expect.equals("abcabda[e]", "abcabdae".replaceAllMapped(wrap("e"), mark)); + + // Test when there are no occurence of the string to replace. + Expect.equals("abcabdae", "abcabdae".replaceAllMapped(wrap("f"), mark)); + + // Test when the string to change is the empty string. + Expect.equals("", "".replaceAllMapped(wrap("from"), mark)); + + // Test when the string to change is a substring of the string to + // replace. + Expect.equals("fro", "fro".replaceAllMapped(wrap("from"), mark)); + + // Test when matches are adjacent + Expect.equals( + "[from][from]", "fromfrom".replaceAllMapped(wrap("from"), mark)); + + // Test replacing by the empty string. + Expect.equals("bcbde", "abcabdae".replaceAllMapped(wrap("a"), (m) => "")); + Expect.equals("AB", "AfromB".replaceAllMapped(wrap("from"), (m) => "")); + + // Test changing the empty string. + Expect.equals("[]", "".replaceAllMapped(wrap(""), mark)); + + // Test replacing the empty string. + Expect.equals("[]A[]B[]C[]", "ABC".replaceAllMapped(wrap(""), mark)); +} + +testSplitMapJoin(Pattern Function(Pattern) wrap) { + String mark(Match m) => "[${m[0]}]"; + String rest(String s) => "<${s}>"; + + Expect.equals("[b][b]", + "abcabdae".splitMapJoin(wrap("b"), onMatch: mark, onNonMatch: rest)); + + // Test with the replaced string at the beginning. + Expect.equals("<>[a][a][a]", + "abcabdae".splitMapJoin(wrap("a"), onMatch: mark, onNonMatch: rest)); + + // Test with the replaced string at the end. + Expect.equals("[e]<>", + "abcabdae".splitMapJoin(wrap("e"), onMatch: mark, onNonMatch: rest)); + + // Test when there are no occurence of the string to replace. + Expect.equals("", + "abcabdae".splitMapJoin(wrap("f"), onMatch: mark, onNonMatch: rest)); + + // Test when the string to change is the empty string. + Expect.equals( + "<>", "".splitMapJoin(wrap("from"), onMatch: mark, onNonMatch: rest)); + + // Test when the string to change is a substring of the string to + // replace. + Expect.equals("", + "fro".splitMapJoin(wrap("from"), onMatch: mark, onNonMatch: rest)); + + // Test when matches are adjacent + Expect.equals("<>[from]<>[from]<>", + "fromfrom".splitMapJoin(wrap("from"), onMatch: mark, onNonMatch: rest)); + + // Test changing the empty string. + Expect.equals( + "<>[]<>", "".splitMapJoin(wrap(""), onMatch: mark, onNonMatch: rest)); + + // Test replacing the empty string. + Expect.equals("<>[][][][]<>", + "ABC".splitMapJoin(wrap(""), onMatch: mark, onNonMatch: rest)); + + // Test with only onMatch. + Expect.equals( + "[a]bc[a]bd[a]e", "abcabdae".splitMapJoin(wrap("a"), onMatch: mark)); + + // Test with only onNonMatch + Expect.equals( + "<>aaa", "abcabdae".splitMapJoin(wrap("a"), onNonMatch: rest)); +} diff --git a/tests/corelib/string_replace_all_pattern_test.dart b/tests/corelib/string_replace_all_pattern_test.dart new file mode 100644 index 0000000000000..0ca86e7f8f0fe --- /dev/null +++ b/tests/corelib/string_replace_all_pattern_test.dart @@ -0,0 +1,25 @@ +// Copyright (c) 2021, 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 "string_replace_all_common.dart"; + +main() { + testAll(Wrapper.wrap); +} + +/// A wrapper that is not recognizable as a String or RegExp. +class Wrapper implements Pattern { + final Pattern _pattern; + Wrapper(this._pattern); + + static Pattern wrap(Pattern p) => Wrapper(p); + + Iterable allMatches(String string, [int start = 0]) => + _pattern.allMatches(string, start); + + Match? matchAsPrefix(String string, [int start = 0]) => + _pattern.matchAsPrefix(string, start); + + String toString() => "Wrap($_pattern)"; +} diff --git a/tests/corelib/string_replace_all_test.dart b/tests/corelib/string_replace_all_test.dart index 548ad624d7238..b6eb43b35612a 100644 --- a/tests/corelib/string_replace_all_test.dart +++ b/tests/corelib/string_replace_all_test.dart @@ -1,140 +1,9 @@ -// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file +// Copyright (c) 2022, 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:expect/expect.dart"; - -testReplaceAll() { - Expect.equals("aXXcaXXdae", "abcabdae".replaceAll("b", "XX")); - - // Test with the replaced string at the beginning. - Expect.equals("XXbcXXbdXXe", "abcabdae".replaceAll("a", "XX")); - - // Test with the replaced string at the end. - Expect.equals("abcabdaXX", "abcabdae".replaceAll("e", "XX")); - - // Test when there are no occurence of the string to replace. - Expect.equals("abcabdae", "abcabdae".replaceAll("f", "XX")); - - // Test when the string to change is the empty string. - Expect.equals("", "".replaceAll("from", "to")); - - // Test when the string to change is a substring of the string to - // replace. - Expect.equals("fro", "fro".replaceAll("from", "to")); - - // Test when the string to change is the replaced string. - Expect.equals("to", "from".replaceAll("from", "to")); - - // Test when matches are adjacent - Expect.equals("toto", "fromfrom".replaceAll("from", "to")); - - // Test when the string to change is the replacement string. - Expect.equals("to", "to".replaceAll("from", "to")); - - // Test replacing by the empty string. - Expect.equals("bcbde", "abcabdae".replaceAll("a", "")); - Expect.equals("AB", "AfromB".replaceAll("from", "")); - - // Test changing the empty string. - Expect.equals("to", "".replaceAll("", "to")); - - // Test replacing the empty string. - Expect.equals("toAtoBtoCto", "ABC".replaceAll("", "to")); - - // Pattern strings containing RegExp metacharacters - these are not - // interpreted as RegExps. - Expect.equals(r"$$", "||".replaceAll("|", r"$")); - Expect.equals(r"$$$$", "||".replaceAll("|", r"$$")); - Expect.equals(r"x$|x", "x|.|x".replaceAll("|.", r"$")); - Expect.equals(r"$$", "..".replaceAll(".", r"$")); - Expect.equals(r"[$$$$]", "[..]".replaceAll(".", r"$$")); - Expect.equals(r"[$]", "[..]".replaceAll("..", r"$")); - Expect.equals(r"$$", r"\\".replaceAll(r"\", r"$")); -} - -testReplaceAllMapped() { - String mark(Match m) => "[${m[0]}]"; - Expect.equals("a[b]ca[b]dae", "abcabdae".replaceAllMapped("b", mark)); - - // Test with the replaced string at the beginning. - Expect.equals("[a]bc[a]bd[a]e", "abcabdae".replaceAllMapped("a", mark)); - - // Test with the replaced string at the end. - Expect.equals("abcabda[e]", "abcabdae".replaceAllMapped("e", mark)); - - // Test when there are no occurence of the string to replace. - Expect.equals("abcabdae", "abcabdae".replaceAllMapped("f", mark)); - - // Test when the string to change is the empty string. - Expect.equals("", "".replaceAllMapped("from", mark)); - - // Test when the string to change is a substring of the string to - // replace. - Expect.equals("fro", "fro".replaceAllMapped("from", mark)); - - // Test when matches are adjacent - Expect.equals("[from][from]", "fromfrom".replaceAllMapped("from", mark)); - - // Test replacing by the empty string. - Expect.equals("bcbde", "abcabdae".replaceAllMapped("a", (m) => "")); - Expect.equals("AB", "AfromB".replaceAllMapped("from", (m) => "")); - - // Test changing the empty string. - Expect.equals("[]", "".replaceAllMapped("", mark)); - - // Test replacing the empty string. - Expect.equals("[]A[]B[]C[]", "ABC".replaceAllMapped("", mark)); -} - -testSplitMapJoin() { - String mark(Match m) => "[${m[0]}]"; - String wrap(String s) => "<${s}>"; - - Expect.equals("[b][b]", - "abcabdae".splitMapJoin("b", onMatch: mark, onNonMatch: wrap)); - - // Test with the replaced string at the beginning. - Expect.equals("<>[a][a][a]", - "abcabdae".splitMapJoin("a", onMatch: mark, onNonMatch: wrap)); - - // Test with the replaced string at the end. - Expect.equals("[e]<>", - "abcabdae".splitMapJoin("e", onMatch: mark, onNonMatch: wrap)); - - // Test when there are no occurence of the string to replace. - Expect.equals("", - "abcabdae".splitMapJoin("f", onMatch: mark, onNonMatch: wrap)); - - // Test when the string to change is the empty string. - Expect.equals("<>", "".splitMapJoin("from", onMatch: mark, onNonMatch: wrap)); - - // Test when the string to change is a substring of the string to - // replace. - Expect.equals( - "", "fro".splitMapJoin("from", onMatch: mark, onNonMatch: wrap)); - - // Test when matches are adjacent - Expect.equals("<>[from]<>[from]<>", - "fromfrom".splitMapJoin("from", onMatch: mark, onNonMatch: wrap)); - - // Test changing the empty string. - Expect.equals("<>[]<>", "".splitMapJoin("", onMatch: mark, onNonMatch: wrap)); - - // Test replacing the empty string. - Expect.equals("<>[][][][]<>", - "ABC".splitMapJoin("", onMatch: mark, onNonMatch: wrap)); - - // Test with only onMatch. - Expect.equals("[a]bc[a]bd[a]e", "abcabdae".splitMapJoin("a", onMatch: mark)); - - // Test with only onNonMatch - Expect.equals( - "<>aaa", "abcabdae".splitMapJoin("a", onNonMatch: wrap)); -} +import "string_replace_all_common.dart"; main() { - testReplaceAll(); - testReplaceAllMapped(); - testSplitMapJoin(); + testAll((pattern) => pattern); // unwrapped } diff --git a/tests/corelib_2/string_replace_all_common.dart b/tests/corelib_2/string_replace_all_common.dart new file mode 100644 index 0000000000000..780761c09785e --- /dev/null +++ b/tests/corelib_2/string_replace_all_common.dart @@ -0,0 +1,146 @@ +// Copyright (c) 2012, 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. + +// @dart = 2.9 + +import "package:expect/expect.dart"; + +testAll(Pattern Function(Pattern) wrap) { + testReplaceAll(wrap); + testReplaceAllMapped(wrap); + testSplitMapJoin(wrap); +} + +testReplaceAll(Pattern Function(Pattern) wrap) { + Expect.equals("aXXcaXXdae", "abcabdae".replaceAll(wrap("b"), "XX")); + + // Test with the replaced string at the beginning. + Expect.equals("XXbcXXbdXXe", "abcabdae".replaceAll(wrap("a"), "XX")); + + // Test with the replaced string at the end. + Expect.equals("abcabdaXX", "abcabdae".replaceAll(wrap("e"), "XX")); + + // Test when there are no occurence of the string to replace. + Expect.equals("abcabdae", "abcabdae".replaceAll(wrap("f"), "XX")); + + // Test when the string to change is the empty string. + Expect.equals("", "".replaceAll(wrap("from"), "to")); + + // Test when the string to change is a substring of the string to + // replace. + Expect.equals("fro", "fro".replaceAll(wrap("from"), "to")); + + // Test when the string to change is the replaced string. + Expect.equals("to", "from".replaceAll(wrap("from"), "to")); + + // Test when matches are adjacent + Expect.equals("toto", "fromfrom".replaceAll(wrap("from"), "to")); + + // Test when the string to change is the replacement string. + Expect.equals("to", "to".replaceAll(wrap("from"), "to")); + + // Test replacing by the empty string. + Expect.equals("bcbde", "abcabdae".replaceAll(wrap("a"), "")); + Expect.equals("AB", "AfromB".replaceAll(wrap("from"), "")); + + // Test changing the empty string. + Expect.equals("to", "".replaceAll(wrap(""), "to")); + + // Test replacing the empty string. + Expect.equals("toAtoBtoCto", "ABC".replaceAll(wrap(""), "to")); + + // Pattern strings containing RegExp metacharacters - these are not + // interpreted as RegExps. + Expect.equals(r"$$", "||".replaceAll(wrap("|"), r"$")); + Expect.equals(r"$$$$", "||".replaceAll(wrap("|"), r"$$")); + Expect.equals(r"x$|x", "x|.|x".replaceAll(wrap("|."), r"$")); + Expect.equals(r"$$", "..".replaceAll(wrap("."), r"$")); + Expect.equals(r"[$$$$]", "[..]".replaceAll(wrap("."), r"$$")); + Expect.equals(r"[$]", "[..]".replaceAll(wrap(".."), r"$")); + Expect.equals(r"$$", r"\\".replaceAll(wrap(r"\"), r"$")); +} + +testReplaceAllMapped(Pattern Function(Pattern) wrap) { + String mark(Match m) => "[${m[0]}]"; + Expect.equals("a[b]ca[b]dae", "abcabdae".replaceAllMapped(wrap("b"), mark)); + + // Test with the replaced string at the beginning. + Expect.equals("[a]bc[a]bd[a]e", "abcabdae".replaceAllMapped(wrap("a"), mark)); + + // Test with the replaced string at the end. + Expect.equals("abcabda[e]", "abcabdae".replaceAllMapped(wrap("e"), mark)); + + // Test when there are no occurence of the string to replace. + Expect.equals("abcabdae", "abcabdae".replaceAllMapped(wrap("f"), mark)); + + // Test when the string to change is the empty string. + Expect.equals("", "".replaceAllMapped(wrap("from"), mark)); + + // Test when the string to change is a substring of the string to + // replace. + Expect.equals("fro", "fro".replaceAllMapped(wrap("from"), mark)); + + // Test when matches are adjacent + Expect.equals( + "[from][from]", "fromfrom".replaceAllMapped(wrap("from"), mark)); + + // Test replacing by the empty string. + Expect.equals("bcbde", "abcabdae".replaceAllMapped(wrap("a"), (m) => "")); + Expect.equals("AB", "AfromB".replaceAllMapped(wrap("from"), (m) => "")); + + // Test changing the empty string. + Expect.equals("[]", "".replaceAllMapped(wrap(""), mark)); + + // Test replacing the empty string. + Expect.equals("[]A[]B[]C[]", "ABC".replaceAllMapped(wrap(""), mark)); +} + +testSplitMapJoin(Pattern Function(Pattern) wrap) { + String mark(Match m) => "[${m[0]}]"; + String rest(String s) => "<${s}>"; + + Expect.equals("[b][b]", + "abcabdae".splitMapJoin(wrap("b"), onMatch: mark, onNonMatch: rest)); + + // Test with the replaced string at the beginning. + Expect.equals("<>[a][a][a]", + "abcabdae".splitMapJoin(wrap("a"), onMatch: mark, onNonMatch: rest)); + + // Test with the replaced string at the end. + Expect.equals("[e]<>", + "abcabdae".splitMapJoin(wrap("e"), onMatch: mark, onNonMatch: rest)); + + // Test when there are no occurence of the string to replace. + Expect.equals("", + "abcabdae".splitMapJoin(wrap("f"), onMatch: mark, onNonMatch: rest)); + + // Test when the string to change is the empty string. + Expect.equals( + "<>", "".splitMapJoin(wrap("from"), onMatch: mark, onNonMatch: rest)); + + // Test when the string to change is a substring of the string to + // replace. + Expect.equals("", + "fro".splitMapJoin(wrap("from"), onMatch: mark, onNonMatch: rest)); + + // Test when matches are adjacent + Expect.equals("<>[from]<>[from]<>", + "fromfrom".splitMapJoin(wrap("from"), onMatch: mark, onNonMatch: rest)); + + // Test changing the empty string. + Expect.equals( + "<>[]<>", "".splitMapJoin(wrap(""), onMatch: mark, onNonMatch: rest)); + + // Test replacing the empty string. + Expect.equals("<>[][][][]<>", + "ABC".splitMapJoin(wrap(""), onMatch: mark, onNonMatch: rest)); + + // Test with only onMatch. + Expect.equals( + "[a]bc[a]bd[a]e", "abcabdae".splitMapJoin(wrap("a"), onMatch: mark)); + + // Test with only onNonMatch + Expect.equals( + "<>aaa", "abcabdae".splitMapJoin(wrap("a"), onNonMatch: rest)); +} diff --git a/tests/corelib_2/string_replace_all_pattern_test.dart b/tests/corelib_2/string_replace_all_pattern_test.dart new file mode 100644 index 0000000000000..10827c9a02be4 --- /dev/null +++ b/tests/corelib_2/string_replace_all_pattern_test.dart @@ -0,0 +1,27 @@ +// Copyright (c) 2021, 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. + +// @dart = 2.9 + +import "string_replace_all_common.dart"; + +main() { + testAll(Wrapper.wrap); +} + +/// A wrapper that is not recognizable as a String or RegExp. +class Wrapper implements Pattern { + final Pattern _pattern; + Wrapper(this._pattern); + + static Pattern wrap(Pattern p) => Wrapper(p); + + Iterable allMatches(String string, [int start = 0]) => + _pattern.allMatches(string, start); + + Match matchAsPrefix(String string, [int start = 0]) => + _pattern.matchAsPrefix(string, start); + + String toString() => "Wrap($_pattern)"; +} diff --git a/tests/corelib_2/string_replace_all_test.dart b/tests/corelib_2/string_replace_all_test.dart index e4e340b994945..9d43fb7eefd42 100644 --- a/tests/corelib_2/string_replace_all_test.dart +++ b/tests/corelib_2/string_replace_all_test.dart @@ -1,142 +1,11 @@ -// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file +// Copyright (c) 2022, 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. // @dart = 2.9 -import "package:expect/expect.dart"; - -testReplaceAll() { - Expect.equals("aXXcaXXdae", "abcabdae".replaceAll("b", "XX")); - - // Test with the replaced string at the beginning. - Expect.equals("XXbcXXbdXXe", "abcabdae".replaceAll("a", "XX")); - - // Test with the replaced string at the end. - Expect.equals("abcabdaXX", "abcabdae".replaceAll("e", "XX")); - - // Test when there are no occurence of the string to replace. - Expect.equals("abcabdae", "abcabdae".replaceAll("f", "XX")); - - // Test when the string to change is the empty string. - Expect.equals("", "".replaceAll("from", "to")); - - // Test when the string to change is a substring of the string to - // replace. - Expect.equals("fro", "fro".replaceAll("from", "to")); - - // Test when the string to change is the replaced string. - Expect.equals("to", "from".replaceAll("from", "to")); - - // Test when matches are adjacent - Expect.equals("toto", "fromfrom".replaceAll("from", "to")); - - // Test when the string to change is the replacement string. - Expect.equals("to", "to".replaceAll("from", "to")); - - // Test replacing by the empty string. - Expect.equals("bcbde", "abcabdae".replaceAll("a", "")); - Expect.equals("AB", "AfromB".replaceAll("from", "")); - - // Test changing the empty string. - Expect.equals("to", "".replaceAll("", "to")); - - // Test replacing the empty string. - Expect.equals("toAtoBtoCto", "ABC".replaceAll("", "to")); - - // Pattern strings containing RegExp metacharacters - these are not - // interpreted as RegExps. - Expect.equals(r"$$", "||".replaceAll("|", r"$")); - Expect.equals(r"$$$$", "||".replaceAll("|", r"$$")); - Expect.equals(r"x$|x", "x|.|x".replaceAll("|.", r"$")); - Expect.equals(r"$$", "..".replaceAll(".", r"$")); - Expect.equals(r"[$$$$]", "[..]".replaceAll(".", r"$$")); - Expect.equals(r"[$]", "[..]".replaceAll("..", r"$")); - Expect.equals(r"$$", r"\\".replaceAll(r"\", r"$")); -} - -testReplaceAllMapped() { - String mark(Match m) => "[${m[0]}]"; - Expect.equals("a[b]ca[b]dae", "abcabdae".replaceAllMapped("b", mark)); - - // Test with the replaced string at the beginning. - Expect.equals("[a]bc[a]bd[a]e", "abcabdae".replaceAllMapped("a", mark)); - - // Test with the replaced string at the end. - Expect.equals("abcabda[e]", "abcabdae".replaceAllMapped("e", mark)); - - // Test when there are no occurence of the string to replace. - Expect.equals("abcabdae", "abcabdae".replaceAllMapped("f", mark)); - - // Test when the string to change is the empty string. - Expect.equals("", "".replaceAllMapped("from", mark)); - - // Test when the string to change is a substring of the string to - // replace. - Expect.equals("fro", "fro".replaceAllMapped("from", mark)); - - // Test when matches are adjacent - Expect.equals("[from][from]", "fromfrom".replaceAllMapped("from", mark)); - - // Test replacing by the empty string. - Expect.equals("bcbde", "abcabdae".replaceAllMapped("a", (m) => "")); - Expect.equals("AB", "AfromB".replaceAllMapped("from", (m) => "")); - - // Test changing the empty string. - Expect.equals("[]", "".replaceAllMapped("", mark)); - - // Test replacing the empty string. - Expect.equals("[]A[]B[]C[]", "ABC".replaceAllMapped("", mark)); -} - -testSplitMapJoin() { - String mark(Match m) => "[${m[0]}]"; - String wrap(String s) => "<${s}>"; - - Expect.equals("[b][b]", - "abcabdae".splitMapJoin("b", onMatch: mark, onNonMatch: wrap)); - - // Test with the replaced string at the beginning. - Expect.equals("<>[a][a][a]", - "abcabdae".splitMapJoin("a", onMatch: mark, onNonMatch: wrap)); - - // Test with the replaced string at the end. - Expect.equals("[e]<>", - "abcabdae".splitMapJoin("e", onMatch: mark, onNonMatch: wrap)); - - // Test when there are no occurence of the string to replace. - Expect.equals("", - "abcabdae".splitMapJoin("f", onMatch: mark, onNonMatch: wrap)); - - // Test when the string to change is the empty string. - Expect.equals("<>", "".splitMapJoin("from", onMatch: mark, onNonMatch: wrap)); - - // Test when the string to change is a substring of the string to - // replace. - Expect.equals( - "", "fro".splitMapJoin("from", onMatch: mark, onNonMatch: wrap)); - - // Test when matches are adjacent - Expect.equals("<>[from]<>[from]<>", - "fromfrom".splitMapJoin("from", onMatch: mark, onNonMatch: wrap)); - - // Test changing the empty string. - Expect.equals("<>[]<>", "".splitMapJoin("", onMatch: mark, onNonMatch: wrap)); - - // Test replacing the empty string. - Expect.equals("<>[][][][]<>", - "ABC".splitMapJoin("", onMatch: mark, onNonMatch: wrap)); - - // Test with only onMatch. - Expect.equals("[a]bc[a]bd[a]e", "abcabdae".splitMapJoin("a", onMatch: mark)); - - // Test with only onNonMatch - Expect.equals( - "<>aaa", "abcabdae".splitMapJoin("a", onNonMatch: wrap)); -} +import "string_replace_all_common.dart"; main() { - testReplaceAll(); - testReplaceAllMapped(); - testSplitMapJoin(); + testAll((pattern) => pattern); // unwrapped } diff --git a/tools/VERSION b/tools/VERSION index 3bb13e6698371..dc4983bc724bf 100644 --- a/tools/VERSION +++ b/tools/VERSION @@ -27,5 +27,5 @@ CHANNEL dev MAJOR 2 MINOR 17 PATCH 0 -PRERELEASE 12 +PRERELEASE 13 PRERELEASE_PATCH 0 \ No newline at end of file