diff --git a/pkg/dartdev/test/native_assets/build_test.dart b/pkg/dartdev/test/native_assets/build_test.dart index bb3ef88d0382..ec0ad7220368 100644 --- a/pkg/dartdev/test/native_assets/build_test.dart +++ b/pkg/dartdev/test/native_assets/build_test.dart @@ -234,77 +234,57 @@ void main(List args) { }); }); - test('Tree-shaking: No assets are dropped', timeout: longTimeout, () async { - await recordUseTest('drop_dylib_recording', (dartAppUri) async { - // First try using all symbols, so no assets are treeshaken. - await runDart( - arguments: [ - '--enable-experiment=native-assets,record-use', - 'build', - 'bin/drop_dylib_recording_all.dart', - ], - workingDirectory: dartAppUri, - logger: logger, - expectExitCodeZero: true, - ); + for (var filename in [ + 'drop_dylib_recording_calls', + 'drop_dylib_recording_instances', + ]) { + test('Tree-shaking in $filename: An asset is dropped', timeout: longTimeout, + () async { + await recordUseTest('drop_dylib_recording', (dartAppUri) async { + final addLib = + OSImpl.current.libraryFileName('add', DynamicLoadingBundledImpl()); + final mulitplyLib = OSImpl.current + .libraryFileName('multiply', DynamicLoadingBundledImpl()); + // Now try using the add symbol only, so the multiply library is + // tree-shaken. - // The build directory exists - final allDirectory = - Directory.fromUri(dartAppUri.resolve('bin/drop_dylib_recording_all')); - expect(allDirectory.existsSync(), true); + await runDart( + arguments: [ + '--enable-experiment=native-assets,record-use', + 'build', + 'bin/$filename.dart', + ], + workingDirectory: dartAppUri, + logger: logger, + expectExitCodeZero: true, + ); - // No assets have been treeshaken - final addLib = - OSImpl.current.libraryFileName('add', DynamicLoadingBundledImpl()); - final mulitplyLib = OSImpl.current - .libraryFileName('multiply', DynamicLoadingBundledImpl()); - expect( - File.fromUri(allDirectory.uri.resolve('lib/$addLib')).existsSync(), - true, - ); - expect( - File.fromUri(allDirectory.uri.resolve('lib/$mulitplyLib')).existsSync(), - true, - ); - }); - }); + await runProcess( + executable: Uri.file('bin/$filename/$filename.exe'), + logger: logger, + expectedExitCode: 0, + throwOnUnexpectedExitCode: true, + workingDirectory: dartAppUri, + ); - test('Tree-shaking: An asset is dropped', timeout: longTimeout, () async { - await recordUseTest('drop_dylib_recording', (dartAppUri) async { - final addLib = - OSImpl.current.libraryFileName('add', DynamicLoadingBundledImpl()); - final mulitplyLib = OSImpl.current - .libraryFileName('multiply', DynamicLoadingBundledImpl()); - // Now try using the add symbol only, so the multiply library is - // tree-shaken. - await runDart( - arguments: [ - '--enable-experiment=native-assets,record-use', - 'build', - 'bin/drop_dylib_recording_shake.dart', - ], - workingDirectory: dartAppUri, - logger: logger, - expectExitCodeZero: true, - ); + // The build directory exists + final shakeDirectory = + Directory.fromUri(dartAppUri.resolve('bin/$filename')); + expect(shakeDirectory.existsSync(), true); - // The build directory exists - final shakeDirectory = Directory.fromUri( - dartAppUri.resolve('bin/drop_dylib_recording_shake')); - expect(shakeDirectory.existsSync(), true); - - // The multiply asset has been treeshaken - expect( - File.fromUri(shakeDirectory.uri.resolve('lib/$addLib')).existsSync(), - true, - ); - expect( - File.fromUri(shakeDirectory.uri.resolve('lib/$mulitplyLib')) - .existsSync(), - false, - ); + // The multiply asset has been treeshaken + expect( + File.fromUri(shakeDirectory.uri.resolve('lib/$addLib')).existsSync(), + true, + ); + expect( + File.fromUri(shakeDirectory.uri.resolve('lib/$mulitplyLib')) + .existsSync(), + false, + ); + }); }); - }); + } } Future _withTempDir(Future Function(Uri tempUri) fun) async { diff --git a/pkg/front_end/lib/src/kernel/record_use.dart b/pkg/front_end/lib/src/kernel/record_use.dart index 061085b4f797..21929a6d1752 100644 --- a/pkg/front_end/lib/src/kernel/record_use.dart +++ b/pkg/front_end/lib/src/kernel/record_use.dart @@ -25,14 +25,26 @@ Iterable findRecordUseAnnotation(Annotatable node) => // Coverage-ignore(suite): Not run. final Uri _metaLibraryUri = new Uri(scheme: 'package', path: 'meta/meta.dart'); -bool isRecordUse(Class classNode) => - classNode.name == 'RecordUse' && +// Coverage-ignore(suite): Not run. +bool isRecordUse(Class cls) => + cls.name == 'RecordUse' && // Coverage-ignore(suite): Not run. - classNode.enclosingLibrary.importUri == _metaLibraryUri; + cls.enclosingLibrary.importUri == _metaLibraryUri; + +// Coverage-ignore(suite): Not run. +bool isBeingRecorded(Class cls) => isRecordUse(cls) || hasRecordUse(cls); + +/// If [cls] annotation is in turn annotated by a recording annotation. +// Coverage-ignore(suite): Not run. +bool hasRecordUse(Class cls) => cls.annotations + .whereType() + .map((e) => e.constant) + .whereType() + .any((annotation) => isRecordUse(annotation.classNode)); // Coverage-ignore(suite): Not run. /// Report if the resource annotations is placed on anything but a static -/// method. +/// method or a class without a const constructor. void validateRecordUseDeclaration( Annotatable node, ErrorReporter errorReporter, @@ -40,7 +52,10 @@ void validateRecordUseDeclaration( ) { final bool onNonStaticMethod = node is! Procedure || !node.isStatic || node.kind != ProcedureKind.Method; - if (onNonStaticMethod) { + + final bool onClassWithoutConstConstructor = node is! Class || + !node.constructors.any((constructor) => constructor.isConst); + if (onNonStaticMethod && onClassWithoutConstConstructor) { errorReporter.report(messageRecordUseCannotBePlacedHere.withLocation( node.location!.file, node.fileOffset, 1)); } diff --git a/pkg/record_use/CHANGELOG.md b/pkg/record_use/CHANGELOG.md index 3a541d92df04..631d73c3325f 100644 --- a/pkg/record_use/CHANGELOG.md +++ b/pkg/record_use/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.3.0 + +- Make `InstanceConstant` a `Constant`. +- Separate import from location uri. + ## 0.2.0 - Use maps instead of lists in serialization. diff --git a/pkg/record_use/lib/record_use.dart b/pkg/record_use/lib/record_use.dart index d6c4669bb33d..30c756163a8d 100644 --- a/pkg/record_use/lib/record_use.dart +++ b/pkg/record_use/lib/record_use.dart @@ -8,6 +8,7 @@ export 'src/public/constant.dart' show BoolConstant, Constant, + InstanceConstant, IntConstant, ListConstant, MapConstant, @@ -15,7 +16,6 @@ export 'src/public/constant.dart' PrimitiveConstant, StringConstant; export 'src/public/identifier.dart' show Identifier; -export 'src/public/instance_constant.dart' show InstanceConstant; export 'src/public/location.dart' show Location; export 'src/public/metadata.dart' show Metadata; //Not exporting `Reference` as it is not used in the API diff --git a/pkg/record_use/lib/src/internal/definition.dart b/pkg/record_use/lib/src/internal/definition.dart index ed83f247c922..5b53b32fecbc 100644 --- a/pkg/record_use/lib/src/internal/definition.dart +++ b/pkg/record_use/lib/src/internal/definition.dart @@ -21,15 +21,12 @@ class Definition { factory Definition.fromJson( Map json, List identifiers, + List uris, ) { final identifier = identifiers[json['id'] as int]; return Definition( identifier: identifier, - location: Location.fromJson( - json['@'] as Map, - identifier.uri, - null, - ), + location: Location.fromJson(json['@'] as Map, uris), loadingUnit: json['loadingUnit'] as String?, ); } @@ -40,7 +37,7 @@ class Definition { ) => { 'id': identifiers[identifier]!, - '@': location.toJson(), + '@': location.toJson(uris), 'loadingUnit': loadingUnit, }; diff --git a/pkg/record_use/lib/src/internal/usage.dart b/pkg/record_use/lib/src/internal/usage.dart index dbdc9cd59f04..ff8b09319692 100644 --- a/pkg/record_use/lib/src/internal/usage.dart +++ b/pkg/record_use/lib/src/internal/usage.dart @@ -28,6 +28,7 @@ class Usage { definition: Definition.fromJson( json['definition'] as Map, identifiers, + uris, ), references: (json['references'] as List) .map((x) => constr(x as Map, uris, constants)) diff --git a/pkg/record_use/lib/src/internal/usage_record.dart b/pkg/record_use/lib/src/internal/usage_record.dart index 1c59729bb9df..ba63a3ad382e 100644 --- a/pkg/record_use/lib/src/internal/usage_record.dart +++ b/pkg/record_use/lib/src/internal/usage_record.dart @@ -71,7 +71,7 @@ class UsageRecord { }.asMapToIndices; final uris = { - ...identifiers.keys.map((e) => e.uri), + ...identifiers.keys.map((e) => e.importUri), ...calls.expand((call) => [ call.definition.location.uri, ...call.references.map((reference) => reference.location.uri), @@ -87,9 +87,10 @@ class UsageRecord { .map((e) => e.arguments?.constArguments) .whereType() .expand((e) => {...e.named.values, ...e.positional.values})), - ...instances - .expand((element) => element.references) - .expand((e) => e.instanceConstant.fields.values) + ...instances.expand((element) => element.references).expand((e) => { + ...e.instanceConstant.fields.values, + e.instanceConstant, + }) }.flatten().asMapToIndices; return { 'metadata': metadata.toJson(), diff --git a/pkg/record_use/lib/src/public/constant.dart b/pkg/record_use/lib/src/public/constant.dart index 2d583dda0f8b..233647552bff 100644 --- a/pkg/record_use/lib/src/public/constant.dart +++ b/pkg/record_use/lib/src/public/constant.dart @@ -23,6 +23,9 @@ sealed class Constant { MapConstant._type => MapConstant( (value['value'] as Map) .map((key, value) => MapEntry(key, constants[value as int]))), + InstanceConstant._type => InstanceConstant( + fields: (value['value'] as Map) + .map((key, value) => MapEntry(key, constants[value as int]))), String() => throw UnimplementedError('This type is not a supported constant'), }; @@ -134,6 +137,45 @@ final class MapConstant extends Constant { ); } +final class InstanceConstant extends Constant { + static const _type = 'Instance'; + + final Map fields; + + const InstanceConstant({ + required this.fields, + }); + + factory InstanceConstant.fromJson( + Map json, + List constants, + ) { + return InstanceConstant( + fields: json.map((key, constantIndex) => + MapEntry(key, constants[constantIndex as int])), + ); + } + + @override + Map toJson(Map constants) => _toJson( + _type, + fields.isNotEmpty + ? fields.map((name, constantIndex) => + MapEntry(name, constants[constantIndex]!)) + : null, + ); + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is InstanceConstant && deepEquals(other.fields, fields); + } + + @override + int get hashCode => deepHash(fields); +} + Map _toJson(String type, Object? value) { return { 'type': type, diff --git a/pkg/record_use/lib/src/public/identifier.dart b/pkg/record_use/lib/src/public/identifier.dart index 57112e591859..18bf7201f69c 100644 --- a/pkg/record_use/lib/src/public/identifier.dart +++ b/pkg/record_use/lib/src/public/identifier.dart @@ -3,25 +3,25 @@ // BSD-style license that can be found in the LICENSE file. class Identifier { - final String uri; + final String importUri; final String? parent; // Optional since not all elements have parents final String name; const Identifier({ - required this.uri, + required this.importUri, this.parent, required this.name, }); factory Identifier.fromJson(Map json, List uris) => Identifier( - uri: uris[json['uri'] as int], + importUri: uris[json['uri'] as int], parent: json['parent'] as String?, name: json['name'] as String, ); Map toJson(Map uris) => { - 'uri': uris[uri]!, + 'uri': uris[importUri]!, if (parent != null) 'parent': parent, 'name': name, }; @@ -31,11 +31,11 @@ class Identifier { if (identical(this, other)) return true; return other is Identifier && - other.uri == uri && + other.importUri == importUri && other.parent == parent && other.name == name; } @override - int get hashCode => Object.hash(uri, parent, name); + int get hashCode => Object.hash(importUri, parent, name); } diff --git a/pkg/record_use/lib/src/public/instance_constant.dart b/pkg/record_use/lib/src/public/instance_constant.dart deleted file mode 100644 index aa20ee0fbdd4..000000000000 --- a/pkg/record_use/lib/src/public/instance_constant.dart +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -import '../helper.dart'; -import 'constant.dart'; - -final class InstanceConstant { - final Map fields; - - const InstanceConstant({ - required this.fields, - }); - - factory InstanceConstant.fromJson( - Map json, - List constants, - ) { - return InstanceConstant( - fields: (json['fields'] as Map).map( - (key, constantIndex) => MapEntry(key, constants[constantIndex as int]), - ), - ); - } - - Map toJson(Map constants) => { - if (fields.isNotEmpty) - 'fields': fields.map((name, constantIndex) => - MapEntry(name, constants[constantIndex]!)), - }; - - @override - bool operator ==(Object other) { - if (identical(this, other)) return true; - - return other is InstanceConstant && deepEquals(other.fields, fields); - } - - @override - int get hashCode => deepHash(fields); -} diff --git a/pkg/record_use/lib/src/public/location.dart b/pkg/record_use/lib/src/public/location.dart index 480bcc2fdeaf..f0ef6c11a5a4 100644 --- a/pkg/record_use/lib/src/public/location.dart +++ b/pkg/record_use/lib/src/public/location.dart @@ -13,18 +13,17 @@ class Location { required this.column, }); - factory Location.fromJson( - Map map, String? uri, List? uris) { + factory Location.fromJson(Map map, List uris) { return Location( - uri: uri ?? uris![map['uri'] as int], + uri: uris[map['uri'] as int], line: map['line'] as int, column: map['column'] as int, ); } - Map toJson({Map? uris}) { + Map toJson(Map uris) { return { - if (uris != null) 'uri': uris[uri]!, + 'uri': uris[uri]!, 'line': line, 'column': column, }; diff --git a/pkg/record_use/lib/src/public/reference.dart b/pkg/record_use/lib/src/public/reference.dart index e266022b2a77..0894380165ad 100644 --- a/pkg/record_use/lib/src/public/reference.dart +++ b/pkg/record_use/lib/src/public/reference.dart @@ -4,7 +4,6 @@ import 'arguments.dart'; import 'constant.dart'; -import 'instance_constant.dart'; import 'location.dart'; sealed class Reference { @@ -21,7 +20,7 @@ sealed class Reference { ) => { 'loadingUnit': loadingUnit, - '@': location.toJson(uris: uris), + '@': location.toJson(uris), }; @override @@ -57,8 +56,7 @@ final class CallReference extends Reference { json['arguments'] as Map, constants) : null, loadingUnit: json['loadingUnit'] as String?, - location: - Location.fromJson(json['@'] as Map, null, uris), + location: Location.fromJson(json['@'] as Map, uris), ); } @@ -101,16 +99,10 @@ final class InstanceReference extends Reference { List constants, ) { return InstanceReference( - instanceConstant: InstanceConstant.fromJson( - json['instanceConstant'] as Map, - constants, - ), + instanceConstant: + constants[json['instanceConstant'] as int] as InstanceConstant, loadingUnit: json['loadingUnit'] as String?, - location: Location.fromJson( - json['@'] as Map, - null, - uris, - ), + location: Location.fromJson(json['@'] as Map, uris), ); } @@ -120,7 +112,7 @@ final class InstanceReference extends Reference { Map constants, ) => { - 'instanceConstant': instanceConstant.toJson(constants), + 'instanceConstant': constants[instanceConstant]!, ...super.toJson(uris, constants), }; diff --git a/pkg/record_use/pubspec.yaml b/pkg/record_use/pubspec.yaml index 7827687b90b1..7633421b38ca 100644 --- a/pkg/record_use/pubspec.yaml +++ b/pkg/record_use/pubspec.yaml @@ -1,7 +1,7 @@ name: record_use description: > The serialization logic and API for the usage recording SDK feature. -version: 0.2.0 +version: 0.3.0 repository: https://github.com/dart-lang/sdk/tree/main/pkg/record_use environment: diff --git a/pkg/record_use/test/test_data.dart b/pkg/record_use/test/test_data.dart index eef4ddf79f6c..d758481dc63f 100644 --- a/pkg/record_use/test/test_data.dart +++ b/pkg/record_use/test/test_data.dart @@ -6,13 +6,13 @@ import 'package:pub_semver/pub_semver.dart'; import 'package:record_use/record_use_internal.dart'; final callId = Identifier( - uri: Uri.parse('file://lib/_internal/js_runtime/lib/js_helper.dart') + importUri: Uri.parse('file://lib/_internal/js_runtime/lib/js_helper.dart') .toString(), parent: 'MyClass', name: 'get:loadDeferredLibrary', ); final instanceId = Identifier( - uri: Uri.parse('file://lib/_internal/js_runtime/lib/js_helper.dart') + importUri: Uri.parse('file://lib/_internal/js_runtime/lib/js_helper.dart') .toString(), name: 'MyAnnotation', ); @@ -168,13 +168,14 @@ final recordedUsesJson = '''{ "value": {"key": 11} }, {"type": "int", "value": 42}, - {"type": "Null"} + {"type": "Null"}, + {"type": "Instance", "value": {"a": 13, "b": 14}} ], "calls": [ { "definition": { "id": 0, - "@": {"line": 12, "column": 67}, + "@": {"uri": 0, "line": 12, "column": 67}, "loadingUnit": "part_15.js" }, "references": [ @@ -209,14 +210,12 @@ final recordedUsesJson = '''{ { "definition": { "id": 1, - "@": {"line": 15, "column": 30}, + "@": {"uri": 0, "line": 15, "column": 30}, "loadingUnit": null }, "references": [ { - "instanceConstant": { - "fields": {"a": 13, "b": 14} - }, + "instanceConstant": 15, "loadingUnit": "3", "@": {"uri": 0, "line": 40, "column": 30} } @@ -232,7 +231,7 @@ final recordedUsesJson2 = '''{ }, "uris": [ "package:drop_dylib_recording/src/drop_dylib_recording.dart", - "drop_dylib_recording_shake.dart" + "drop_dylib_recording_calls.dart" ], "ids": [ { @@ -251,6 +250,7 @@ final recordedUsesJson2 = '''{ "definition": { "id": 0, "@": { + "uri": 0, "line": 10, "column": 6 }, diff --git a/pkg/record_use/test/usage_test.dart b/pkg/record_use/test/usage_test.dart index e9723ee1d758..bd2e066dbad4 100644 --- a/pkg/record_use/test/usage_test.dart +++ b/pkg/record_use/test/usage_test.dart @@ -30,7 +30,7 @@ void main() { test('Specific API calls', () { final callId = Identifier( - uri: Uri.parse('file://lib/_internal/js_runtime/lib/js_helper.dart') + importUri: Uri.parse('file://lib/_internal/js_runtime/lib/js_helper.dart') .toString(), parent: 'MyClass', name: 'get:loadDeferredLibrary', @@ -75,7 +75,7 @@ void main() { test('Specific API instances', () { final instanceId = Identifier( - uri: Uri.parse('file://lib/_internal/js_runtime/lib/js_helper.dart') + importUri: Uri.parse('file://lib/_internal/js_runtime/lib/js_helper.dart') .toString(), name: 'MyAnnotation', ); @@ -88,7 +88,7 @@ void main() { instanceConstant: const InstanceConstant( fields: {'a': IntConstant(42), 'b': NullConstant()}, ), - location: Location(uri: instanceId.uri, line: 40, column: 30), + location: Location(uri: instanceId.importUri, line: 40, column: 30), loadingUnit: 3.toString(), ), ); @@ -96,7 +96,7 @@ void main() { test('HasNonConstInstance', () { final id = const Identifier( - uri: 'package:drop_dylib_recording/src/drop_dylib_recording.dart', + importUri: 'package:drop_dylib_recording/src/drop_dylib_recording.dart', name: 'getMathMethod', ); diff --git a/pkg/record_use/test_data/drop_dylib_recording/.gitignore b/pkg/record_use/test_data/drop_dylib_recording/.gitignore index 3c200b5c7b47..f486ed6608ed 100644 --- a/pkg/record_use/test_data/drop_dylib_recording/.gitignore +++ b/pkg/record_use/test_data/drop_dylib_recording/.gitignore @@ -1 +1 @@ -bin/drop_dylib_link/ +bin/drop_dylib_recording*/ diff --git a/pkg/record_use/test_data/drop_dylib_recording/README.md b/pkg/record_use/test_data/drop_dylib_recording/README.md index f1b7103439bb..505cd2f975e9 100644 --- a/pkg/record_use/test_data/drop_dylib_recording/README.md +++ b/pkg/record_use/test_data/drop_dylib_recording/README.md @@ -5,7 +5,7 @@ the recorded usages feature to tree-shake unused libraries out. ### Keep all: ``` -devdart --enable-experiment=native-assets,record-use build bin/drop_dylib_recording_all.dart +dart --enable-experiment=native-assets,record-use build bin/drop_dylib_recording_all.dart ``` The `lib/` folder now contains both libraries ``` @@ -14,12 +14,22 @@ The `lib/` folder now contains both libraries Prints `Hello world: 7!` -### Treeshake: +### Treeshake using calls: ``` -devdart --enable-experiment=native-assets,record-use build bin/drop_dylib_recording_shake.dart +dart --enable-experiment=native-assets,record-use build bin/drop_dylib_recording_calls.dart ``` The `lib/` folder now contains only the `add` library. ``` -./bin/drop_dylib_recording_shake/drop_dylib_recording_shake.exe +./bin/drop_dylib_recording_calls/drop_dylib_recording_calls.exe +``` +Prints `Hello world: 7!` + +### Treeshake using instances: +``` +dart --enable-experiment=native-assets,record-use build bin/drop_dylib_recording_instances.dart +``` +The `lib/` folder now contains only the `add` library. +``` +./bin/drop_dylib_recording_calls/drop_dylib_recording_instances.exe ``` Prints `Hello world: 7!` diff --git a/pkg/record_use/test_data/drop_dylib_recording/bin/drop_dylib_recording_all.dart b/pkg/record_use/test_data/drop_dylib_recording/bin/drop_dylib_recording_calls.dart similarity index 87% rename from pkg/record_use/test_data/drop_dylib_recording/bin/drop_dylib_recording_all.dart rename to pkg/record_use/test_data/drop_dylib_recording/bin/drop_dylib_recording_calls.dart index 0ba8f46f45ad..d6d77319f5f6 100644 --- a/pkg/record_use/test_data/drop_dylib_recording/bin/drop_dylib_recording_all.dart +++ b/pkg/record_use/test_data/drop_dylib_recording/bin/drop_dylib_recording_calls.dart @@ -5,5 +5,5 @@ import 'package:drop_dylib_recording/drop_dylib_recording.dart'; void main(List arguments) { - getMathMethod(arguments.first); + print('Hello world: ${MyMath.add(3, 4)}!'); } diff --git a/pkg/record_use/test_data/drop_dylib_recording/bin/drop_dylib_recording_shake.dart b/pkg/record_use/test_data/drop_dylib_recording/bin/drop_dylib_recording_instances.dart similarity index 87% rename from pkg/record_use/test_data/drop_dylib_recording/bin/drop_dylib_recording_shake.dart rename to pkg/record_use/test_data/drop_dylib_recording/bin/drop_dylib_recording_instances.dart index ca477635b69b..f2165871d387 100644 --- a/pkg/record_use/test_data/drop_dylib_recording/bin/drop_dylib_recording_shake.dart +++ b/pkg/record_use/test_data/drop_dylib_recording/bin/drop_dylib_recording_instances.dart @@ -5,5 +5,5 @@ import 'package:drop_dylib_recording/drop_dylib_recording.dart'; void main(List arguments) { - getMathMethod('add'); + print('Hello world: ${MyMath.double(3)}!'); } diff --git a/pkg/record_use/test_data/drop_dylib_recording/hook/link.dart b/pkg/record_use/test_data/drop_dylib_recording/hook/link.dart index 7c114a6f6c52..0b34eb075385 100644 --- a/pkg/record_use/test_data/drop_dylib_recording/hook/link.dart +++ b/pkg/record_use/test_data/drop_dylib_recording/hook/link.dart @@ -8,9 +8,21 @@ import 'dart:io'; import 'package:native_assets_cli/native_assets_cli.dart'; import 'package:record_use/record_use.dart'; -final id = const Identifier( - uri: 'package:drop_dylib_recording/src/drop_dylib_recording.dart', - name: 'getMathMethod', +final callIdAdd = const Identifier( + importUri: 'package:drop_dylib_recording/src/drop_dylib_recording.dart', + parent: 'MyMath', + name: 'add', +); + +final callIdMultiply = const Identifier( + importUri: 'package:drop_dylib_recording/src/drop_dylib_recording.dart', + parent: 'MyMath', + name: 'multiply', +); + +final instanceId = const Identifier( + importUri: 'package:drop_dylib_recording/src/drop_dylib_recording.dart', + name: 'RecordCallToC', ); void main(List arguments) async { @@ -23,39 +35,43 @@ void main(List arguments) async { print(''' Received ${config.assets.length} assets: ${config.assets.map((e) => e.id)}. '''); - final f = File.fromUri(config.outputDirectory.resolve('debug.txt')) - ..createSync(); - f.writeAsStringSync(config.assets - .map( - (e) => e.id, - ) - .join('\n')); - f.writeAsStringSync('\nnow', mode: FileMode.append); - - if (usages.hasNonConstArguments(id)) { - //Keep all assets - output.addAssets(config.assets); - f.writeAsStringSync('\nhasNonConstargs', mode: FileMode.append); - f.writeAsStringSync( - '\n${usages.argumentsTo(id)!.first.nonConstArguments.toJson()}', - mode: FileMode.append); - } else { - f.writeAsStringSync('\nno-hasNonConstargs', mode: FileMode.append); - //Tree-shake unused assets - final arguments = usages.argumentsTo(id) ?? []; - for (final argument in arguments) { - f.writeAsStringSync('\nArg: $argument', mode: FileMode.append); - final symbol = - (argument.constArguments.positional[0] as StringConstant).value; - f.writeAsStringSync('\nsymbol: $symbol', mode: FileMode.append); + final symbols = {}; + final argumentsFile = + await File.fromUri(config.outputDirectory.resolve('arguments.txt')) + ..create(); - output.addAssets( - config.assets.where((asset) => asset.id.endsWith(symbol)), - ); + final dataLines = []; + //Tree-shake unused assets using calls + for (var callId in [callIdAdd, callIdMultiply]) { + var arguments = usages.argumentsTo(callId); + if (arguments?.isNotEmpty ?? false) { + final argument = + (arguments!.first.constArguments.positional[0] as IntConstant) + .value; + dataLines.add('Argument to "${callId.name}": $argument'); + symbols.add(callId.name); } } + argumentsFile.writeAsStringSync(dataLines.join('\n')); + + //Tree-shake unused assets + final instances = usages.instancesOf(instanceId) ?? []; + for (final instance in instances) { + final symbol = + (instance.instanceConstant.fields.values.first as StringConstant) + .value; + + symbols.add(symbol); + } + + for (var symbol in symbols) { + output.addAssets( + config.assets.where((asset) => asset.id.endsWith(symbol)), + ); + } + print(''' Keeping only ${output.assets.map((e) => e.id)}. '''); diff --git a/pkg/record_use/test_data/drop_dylib_recording/lib/src/drop_dylib_recording.dart b/pkg/record_use/test_data/drop_dylib_recording/lib/src/drop_dylib_recording.dart index 4c6e232e83a9..d031ddc580df 100644 --- a/pkg/record_use/test_data/drop_dylib_recording/lib/src/drop_dylib_recording.dart +++ b/pkg/record_use/test_data/drop_dylib_recording/lib/src/drop_dylib_recording.dart @@ -6,19 +6,23 @@ import 'package:meta/meta.dart'; import 'drop_dylib_recording_bindings.dart' as bindings; -@RecordUse() -void getMathMethod(String symbol) { - if (symbol == 'add') { - print('Hello world: ${_MyMath.add(3, 4)}!'); - } else if (symbol == 'multiply') { - print('Hello world: ${_MyMath.multiply(3, 4)}!'); - } else { - throw ArgumentError('Must pass either "add" or "multiply"'); - } -} - -class _MyMath { +class MyMath { + @RecordUse() static int add(int a, int b) => bindings.add(a, b); + @RecordUse() static int multiply(int a, int b) => bindings.multiply(a, b); + + @RecordCallToC('add') + static int double(int a) => bindings.add(a, a); + + @RecordCallToC('multiply') + static int square(int a) => bindings.multiply(a, a); +} + +@RecordUse() +class RecordCallToC { + final String symbol; + + const RecordCallToC(this.symbol); } diff --git a/pkg/record_use/test_data/drop_dylib_recording/pubspec.yaml b/pkg/record_use/test_data/drop_dylib_recording/pubspec.yaml index 7300f6982e0d..d3c1c4092aaa 100644 --- a/pkg/record_use/test_data/drop_dylib_recording/pubspec.yaml +++ b/pkg/record_use/test_data/drop_dylib_recording/pubspec.yaml @@ -24,4 +24,4 @@ dev_dependencies: dependency_overrides: meta: - path: ../../../meta/ \ No newline at end of file + path: ../../../meta/ diff --git a/pkg/record_use/test_data/manifest.yaml b/pkg/record_use/test_data/manifest.yaml index 15cd129909db..ea0d2dbe9f36 100644 --- a/pkg/record_use/test_data/manifest.yaml +++ b/pkg/record_use/test_data/manifest.yaml @@ -2,25 +2,11 @@ - drop_dylib_recording/lib/src/drop_dylib_recording_bindings.dart - drop_dylib_recording/lib/src/drop_dylib_recording.dart - drop_dylib_recording/lib/drop_dylib_recording.dart -- drop_dylib_recording/README.md - drop_dylib_recording/src/native_add.h - drop_dylib_recording/src/native_multiply.h - drop_dylib_recording/src/native_add.c - drop_dylib_recording/src/native_multiply.c - drop_dylib_recording/hook/link.dart - drop_dylib_recording/hook/build.dart -- drop_dylib_recording/bin/drop_dylib_recording_all.dart -- drop_dylib_recording/bin/drop_dylib_recording_shake.dart -- drop_dylib_recording/pubspec.yaml -- drop_dylib_recording/lib/src/drop_dylib_recording_bindings.dart -- drop_dylib_recording/lib/src/drop_dylib_recording.dart -- drop_dylib_recording/lib/drop_dylib_recording.dart -- drop_dylib_recording/README.md -- drop_dylib_recording/src/native_add.h -- drop_dylib_recording/src/native_multiply.h -- drop_dylib_recording/src/native_add.c -- drop_dylib_recording/src/native_multiply.c -- drop_dylib_recording/hook/link.dart -- drop_dylib_recording/hook/build.dart -- drop_dylib_recording/bin/drop_dylib_recording_all.dart -- drop_dylib_recording/bin/drop_dylib_recording_shake.dart +- drop_dylib_recording/bin/drop_dylib_recording_calls.dart +- drop_dylib_recording/bin/drop_dylib_recording_instances.dart diff --git a/pkg/vm/lib/transformations/record_use/record_call.dart b/pkg/vm/lib/transformations/record_use/record_call.dart index c8930c35c5a9..9211e6f7e75e 100644 --- a/pkg/vm/lib/transformations/record_use/record_call.dart +++ b/pkg/vm/lib/transformations/record_use/record_call.dart @@ -53,10 +53,7 @@ class StaticCallRecorder { ), ); return CallReference( - location: node.location!.recordLocation(getIdentifierUri( - enclosingLibrary(node)!, - source, - )), + location: node.location!.recordLocation(source), arguments: Arguments(nonConstArguments: nonConstArguments), ); } @@ -92,10 +89,7 @@ class StaticCallRecorder { final namedGrouped = _groupByNull(namedArguments); return CallReference( - location: node.location!.recordLocation(getIdentifierUri( - enclosingLibrary(node)!, - source, - )), + location: node.location!.recordLocation(source), loadingUnit: loadingUnitForNode(node, _loadingUnits).toString(), arguments: Arguments( constArguments: ConstArguments( @@ -132,15 +126,15 @@ class StaticCallRecorder { Definition _definitionFromMember(ast.Member target) { final enclosingLibrary = target.enclosingLibrary; - String file = getIdentifierUri(enclosingLibrary, source); + String file = getImportUri(enclosingLibrary, source); return Definition( identifier: Identifier( - uri: file, + importUri: file, parent: target.enclosingClass?.name, name: target.name.text, ), - location: target.location!.recordLocation(file), + location: target.location!.recordLocation(source), loadingUnit: loadingUnitForNode(enclosingLibrary, _loadingUnits).toString(), ); diff --git a/pkg/vm/lib/transformations/record_use/record_instance.dart b/pkg/vm/lib/transformations/record_use/record_instance.dart new file mode 100644 index 000000000000..be7f15a0e3fb --- /dev/null +++ b/pkg/vm/lib/transformations/record_use/record_instance.dart @@ -0,0 +1,79 @@ +// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:front_end/src/kernel/record_use.dart' as recordUse; +import 'package:kernel/ast.dart' as ast; +import 'package:record_use/record_use_internal.dart'; +import 'package:vm/metadata/loading_units.dart'; +import 'package:vm/transformations/record_use/record_use.dart'; + +class InstanceUseRecorder { + final Map> instancesForClass = {}; + final List _loadingUnits; + final Uri source; + + InstanceUseRecorder(this.source, this._loadingUnits); + + void recordAnnotationUse(ast.ConstantExpression node) { + final constant = node.constant; + if (constant is ast.InstanceConstant) { + if (recordUse.findRecordUseAnnotation(constant.classNode).isNotEmpty) { + _collectUseInformation(node, constant); + } + } + } + + void _collectUseInformation( + ast.ConstantExpression node, + ast.InstanceConstant constant, + ) { + // Collect the name and definition location of the invocation. This is + // shared across multiple calls to the same method. + final existingInstance = _getCall(constant.classNode); + + // Collect the (int, bool, double, or String) arguments passed in the call. + existingInstance.references.add(_createInstanceReference(node, constant)); + } + + /// Collect the name and definition location of the invocation. This is + /// shared across multiple calls to the same method. + Usage _getCall(ast.Class cls) { + final definition = _definitionFromClass(cls); + return instancesForClass.putIfAbsent( + cls, + () => Usage(definition: definition, references: []), + ); + } + + Definition _definitionFromClass(ast.Class cls) { + final enclosingLibrary = cls.enclosingLibrary; + String file = getImportUri(enclosingLibrary, source); + + return Definition( + identifier: Identifier(importUri: file, name: cls.name), + location: cls.location!.recordLocation(source), + loadingUnit: + loadingUnitForNode(cls.enclosingLibrary, _loadingUnits).toString(), + ); + } + + InstanceReference _createInstanceReference( + ast.ConstantExpression node, + ast.InstanceConstant constant, + ) => + InstanceReference( + location: node.location!.recordLocation(source), + instanceConstant: _fieldsFromConstant(constant), + loadingUnit: loadingUnitForNode(node, _loadingUnits).toString(), + ); + + InstanceConstant _fieldsFromConstant(ast.InstanceConstant constant) => + InstanceConstant( + fields: constant.fieldValues.map( + (key, value) => MapEntry( + key.asField.name.text, + evaluateConstant(value), + ), + )); +} diff --git a/pkg/vm/lib/transformations/record_use/record_use.dart b/pkg/vm/lib/transformations/record_use/record_use.dart index 2af30040fe98..aa3c01282ad6 100644 --- a/pkg/vm/lib/transformations/record_use/record_use.dart +++ b/pkg/vm/lib/transformations/record_use/record_use.dart @@ -13,6 +13,7 @@ import 'package:pub_semver/pub_semver.dart'; import 'package:record_use/record_use_internal.dart'; import 'package:vm/metadata/loading_units.dart'; import 'package:vm/transformations/record_use/record_call.dart'; +import 'package:vm/transformations/record_use/record_instance.dart'; /// Collect calls to methods annotated with `@RecordUse`. /// @@ -36,11 +37,16 @@ ast.Component transformComponent( final loadingUnits = loadingMetadata.mapping[component]?.loadingUnits ?? []; final staticCallRecorder = StaticCallRecorder(source, loadingUnits); + final instanceUseRecorder = InstanceUseRecorder(source, loadingUnits); component.accept(_RecordUseVisitor( staticCallRecorder, + instanceUseRecorder, )); - final usages = _usages(staticCallRecorder.callsForMethod.values, []); + final usages = _usages( + staticCallRecorder.callsForMethod.values, + instanceUseRecorder.instancesForClass.values, + ); var usagesStorageFormat = usages.toJson(); File.fromUri(recordedUsagesFile).writeAsStringSync( JsonEncoder.withIndent(' ').convert(usagesStorageFormat), @@ -51,7 +57,12 @@ ast.Component transformComponent( class _RecordUseVisitor extends ast.RecursiveVisitor { final StaticCallRecorder staticCallRecorder; - _RecordUseVisitor(this.staticCallRecorder); + final InstanceUseRecorder instanceUseRecorder; + + _RecordUseVisitor( + this.staticCallRecorder, + this.instanceUseRecorder, + ); @override void visitStaticInvocation(ast.StaticInvocation node) { @@ -62,6 +73,11 @@ class _RecordUseVisitor extends ast.RecursiveVisitor { @override void visitConstantExpression(ast.ConstantExpression node) { staticCallRecorder.recordTearoff(node); + + final parent = node.parent; + if (parent is ast.Annotatable && parent.annotations.contains(node)) { + instanceUseRecorder.recordAnnotationUse(node); + } super.visitConstantExpression(node); } } @@ -120,14 +136,14 @@ Never _unsupported(String constantType) => throw UnsupportedError('$constantType is not supported for recording.'); extension RecordUseLocation on ast.Location { - Location recordLocation(String uri) => Location( - uri: uri, + Location recordLocation(Uri source) => Location( + uri: relativizeUri(source, this.file, Platform.isWindows), line: line, column: column, ); } -String getIdentifierUri(ast.Library library, Uri source) { +String getImportUri(ast.Library library, Uri source) { String file; final importUri = library.importUri; if (importUri.isScheme('file')) { diff --git a/pkg/vm/lib/transformations/type_flow/transformer.dart b/pkg/vm/lib/transformations/type_flow/transformer.dart index fe428d7239e3..fb3d2d780bcb 100644 --- a/pkg/vm/lib/transformations/type_flow/transformer.dart +++ b/pkg/vm/lib/transformations/type_flow/transformer.dart @@ -278,7 +278,8 @@ class CleanupAnnotations extends RecursiveVisitor { /// We do not want to eliminate /// * `pragma`s /// * Protobuf annotations - /// * `RecordUse` annotations + /// * Annotations needed for tree shaking of non-Dart assets via + /// package:record_use /// /// as we need these later in the pipeline. bool _keepAnnotation(Expression annotation) { @@ -288,8 +289,9 @@ class CleanupAnnotations extends RecursiveVisitor { final cls = constant.classNode; final usesProtobufAnnotation = protobufHandler?.usesAnnotationClass(cls) ?? false; - bool usesRecordUse = RecordUse.isRecordUse(cls); - return cls == pragmaClass || usesProtobufAnnotation || usesRecordUse; + return cls == pragmaClass || + usesProtobufAnnotation || + RecordUse.isBeingRecorded(cls); } } return false; diff --git a/pkg/vm/testcases/transformations/record_use/complex.dart.json.expect b/pkg/vm/testcases/transformations/record_use/complex.dart.json.expect index 9aa4ab1dff44..736da792f253 100644 --- a/pkg/vm/testcases/transformations/record_use/complex.dart.json.expect +++ b/pkg/vm/testcases/transformations/record_use/complex.dart.json.expect @@ -24,6 +24,7 @@ "definition": { "id": 0, "@": { + "uri": 0, "line": 30, "column": 25 }, diff --git a/pkg/vm/testcases/transformations/record_use/extension.dart.json.expect b/pkg/vm/testcases/transformations/record_use/extension.dart.json.expect index 69aeac0d2c8c..7440708c6cf0 100644 --- a/pkg/vm/testcases/transformations/record_use/extension.dart.json.expect +++ b/pkg/vm/testcases/transformations/record_use/extension.dart.json.expect @@ -23,6 +23,7 @@ "definition": { "id": 0, "@": { + "uri": 0, "line": 15, "column": 8 }, diff --git a/pkg/vm/testcases/transformations/record_use/instance_class.dart b/pkg/vm/testcases/transformations/record_use/instance_class.dart new file mode 100644 index 000000000000..c39721de7eb2 --- /dev/null +++ b/pkg/vm/testcases/transformations/record_use/instance_class.dart @@ -0,0 +1,19 @@ +// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:meta/meta.dart' show RecordUse; + +void main() { + print(A()); +} + +@MyClass(42) +class A {} + +@RecordUse() +class MyClass { + final int i; + + const MyClass(this.i); +} diff --git a/pkg/vm/testcases/transformations/record_use/instance_class.dart.aot.expect b/pkg/vm/testcases/transformations/record_use/instance_class.dart.aot.expect new file mode 100644 index 000000000000..72887c164ff0 --- /dev/null +++ b/pkg/vm/testcases/transformations/record_use/instance_class.dart.aot.expect @@ -0,0 +1,31 @@ +library #lib; +import self as self; +import "dart:core" as core; +import "package:meta/meta.dart" as meta; + +import "package:meta/meta.dart" show RecordUse; + +@#C2 +class A extends core::Object { + synthetic constructor •() → self::A + : super core::Object::•() + ; +} +@#C3 +class MyClass extends core::Object /*hasConstConstructor*/ { + + [@vm.inferred-type.metadata=dart.core::_Smi (value: 42)] + [@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasNonThisUses:false,hasTearOffUses:false,getterSelectorId:1] + [@vm.unboxing-info.metadata=()->i] + final field core::int i; +} + +[@vm.inferred-return-type.metadata=dart.core::Null? (value: null)] +static method main() → void { + core::print(new self::A::•()); +} +constants { + #C1 = 42 + #C2 = self::MyClass {i:#C1} + #C3 = meta::RecordUse {} +} diff --git a/pkg/vm/testcases/transformations/record_use/instance_class.dart.json.expect b/pkg/vm/testcases/transformations/record_use/instance_class.dart.json.expect new file mode 100644 index 000000000000..799526f07028 --- /dev/null +++ b/pkg/vm/testcases/transformations/record_use/instance_class.dart.json.expect @@ -0,0 +1,51 @@ +{ + "metadata": { + "comment": "Recorded usages of objects tagged with a `RecordUse` annotation", + "version": "0.1.0" + }, + "uris": [ + "instance_class.dart" + ], + "ids": [ + { + "uri": 0, + "name": "MyClass" + } + ], + "constants": [ + { + "type": "int", + "value": 42 + }, + { + "type": "Instance", + "value": { + "i": 0 + } + } + ], + "instances": [ + { + "definition": { + "id": 0, + "@": { + "uri": 0, + "line": 15, + "column": 7 + }, + "loadingUnit": "1" + }, + "references": [ + { + "instanceConstant": 1, + "loadingUnit": "1", + "@": { + "uri": 0, + "line": 11, + "column": 2 + } + } + ] + } + ] +} \ No newline at end of file diff --git a/pkg/vm/testcases/transformations/record_use/instance_complex.dart b/pkg/vm/testcases/transformations/record_use/instance_complex.dart new file mode 100644 index 000000000000..64661f547a65 --- /dev/null +++ b/pkg/vm/testcases/transformations/record_use/instance_complex.dart @@ -0,0 +1,40 @@ +// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:meta/meta.dart' show RecordUse; + +void main() { + print(A()); +} + +@MyClass( + i: 15, + s: 's', + b: true, + l: [ + {'l': 3} + ], + m: {'h': false}, + n: null, +) +class A {} + +@RecordUse() +class MyClass { + final int i; + final String s; + final Map m; + final bool b; + final List> l; + final String? n; + + const MyClass({ + required this.i, + required this.s, + required this.m, + required this.b, + required this.l, + required this.n, + }); +} diff --git a/pkg/vm/testcases/transformations/record_use/instance_complex.dart.aot.expect b/pkg/vm/testcases/transformations/record_use/instance_complex.dart.aot.expect new file mode 100644 index 000000000000..6a8f1cf95a5f --- /dev/null +++ b/pkg/vm/testcases/transformations/record_use/instance_complex.dart.aot.expect @@ -0,0 +1,61 @@ +library #lib; +import self as self; +import "dart:core" as core; +import "package:meta/meta.dart" as meta; + +import "package:meta/meta.dart" show RecordUse; + +@#C12 +class A extends core::Object { + synthetic constructor •() → self::A + : super core::Object::•() + ; +} +@#C13 +class MyClass extends core::Object /*hasConstConstructor*/ { + + [@vm.inferred-type.metadata=dart.core::_Smi (value: 15)] + [@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasNonThisUses:false,hasTearOffUses:false,getterSelectorId:1] + [@vm.unboxing-info.metadata=()->i] + final field core::int i; + + [@vm.inferred-type.metadata=dart.core::_OneByteString (value: "s")] + [@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasNonThisUses:false,hasTearOffUses:false,getterSelectorId:2] + final field core::String s; + + [@vm.inferred-type.metadata=dart._compact_hash::_ConstMap (value: const {"h": false})] + [@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasNonThisUses:false,hasTearOffUses:false,getterSelectorId:3] + final field core::Map m; + + [@vm.inferred-type.metadata=dart.core::bool (value: true)] + [@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasNonThisUses:false,hasTearOffUses:false,getterSelectorId:4] + final field core::bool b; + + [@vm.inferred-type.metadata=dart.core::_ImmutableList (value: const >[const {"l": 3}])] + [@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasNonThisUses:false,hasTearOffUses:false,getterSelectorId:5] + final field core::List> l; + + [@vm.inferred-type.metadata=dart.core::Null? (value: null)] + [@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasNonThisUses:false,hasTearOffUses:false,getterSelectorId:6] + final field core::String? n; +} + +[@vm.inferred-return-type.metadata=dart.core::Null? (value: null)] +static method main() → void { + core::print(new self::A::•()); +} +constants { + #C1 = 15 + #C2 = "s" + #C3 = "h" + #C4 = false + #C5 = {#C3:#C4} + #C6 = true + #C7 = "l" + #C8 = 3 + #C9 = {#C7:#C8} + #C10 = >[#C9] + #C11 = null + #C12 = self::MyClass {i:#C1, s:#C2, m:#C5, b:#C6, l:#C10, n:#C11} + #C13 = meta::RecordUse {} +} diff --git a/pkg/vm/testcases/transformations/record_use/instance_complex.dart.json.expect b/pkg/vm/testcases/transformations/record_use/instance_complex.dart.json.expect new file mode 100644 index 000000000000..8f9d5ce63612 --- /dev/null +++ b/pkg/vm/testcases/transformations/record_use/instance_complex.dart.json.expect @@ -0,0 +1,93 @@ +{ + "metadata": { + "comment": "Recorded usages of objects tagged with a `RecordUse` annotation", + "version": "0.1.0" + }, + "uris": [ + "instance_complex.dart" + ], + "ids": [ + { + "uri": 0, + "name": "MyClass" + } + ], + "constants": [ + { + "type": "int", + "value": 15 + }, + { + "type": "String", + "value": "s" + }, + { + "type": "bool", + "value": false + }, + { + "type": "map", + "value": { + "h": 2 + } + }, + { + "type": "bool", + "value": true + }, + { + "type": "int", + "value": 3 + }, + { + "type": "map", + "value": { + "l": 5 + } + }, + { + "type": "list", + "value": [ + 6 + ] + }, + { + "type": "Null" + }, + { + "type": "Instance", + "value": { + "i": 0, + "s": 1, + "m": 3, + "b": 4, + "l": 7, + "n": 8 + } + } + ], + "instances": [ + { + "definition": { + "id": 0, + "@": { + "uri": 0, + "line": 24, + "column": 7 + }, + "loadingUnit": "1" + }, + "references": [ + { + "instanceConstant": 9, + "loadingUnit": "1", + "@": { + "uri": 0, + "line": 11, + "column": 2 + } + } + ] + } + ] +} \ No newline at end of file diff --git a/pkg/vm/testcases/transformations/record_use/instance_method.dart b/pkg/vm/testcases/transformations/record_use/instance_method.dart new file mode 100644 index 000000000000..96d91455d3fe --- /dev/null +++ b/pkg/vm/testcases/transformations/record_use/instance_method.dart @@ -0,0 +1,21 @@ +// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:meta/meta.dart' show RecordUse; + +void main() { + doSomething(); +} + +@MyClass(42) +void doSomething() { + print('a'); +} + +@RecordUse() +class MyClass { + final int i; + + const MyClass(this.i); +} diff --git a/pkg/vm/testcases/transformations/record_use/instance_method.dart.aot.expect b/pkg/vm/testcases/transformations/record_use/instance_method.dart.aot.expect new file mode 100644 index 000000000000..447a844783ae --- /dev/null +++ b/pkg/vm/testcases/transformations/record_use/instance_method.dart.aot.expect @@ -0,0 +1,31 @@ +library #lib; +import self as self; +import "package:meta/meta.dart" as meta; +import "dart:core" as core; + +import "package:meta/meta.dart" show RecordUse; + +@#C1 +class MyClass extends core::Object /*hasConstConstructor*/ { + + [@vm.inferred-type.metadata=dart.core::_Smi (value: 42)] + [@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasNonThisUses:false,hasTearOffUses:false,getterSelectorId:1] + [@vm.unboxing-info.metadata=()->i] + final field core::int i; +} + +[@vm.inferred-return-type.metadata=dart.core::Null? (value: null)] +static method main() → void { + self::doSomething(); +} + +[@vm.inferred-return-type.metadata=dart.core::Null? (value: null)] +@#C3 +static method doSomething() → void { + core::print("a"); +} +constants { + #C1 = meta::RecordUse {} + #C2 = 42 + #C3 = self::MyClass {i:#C2} +} diff --git a/pkg/vm/testcases/transformations/record_use/instance_method.dart.json.expect b/pkg/vm/testcases/transformations/record_use/instance_method.dart.json.expect new file mode 100644 index 000000000000..e3ff3df777ef --- /dev/null +++ b/pkg/vm/testcases/transformations/record_use/instance_method.dart.json.expect @@ -0,0 +1,51 @@ +{ + "metadata": { + "comment": "Recorded usages of objects tagged with a `RecordUse` annotation", + "version": "0.1.0" + }, + "uris": [ + "instance_method.dart" + ], + "ids": [ + { + "uri": 0, + "name": "MyClass" + } + ], + "constants": [ + { + "type": "int", + "value": 42 + }, + { + "type": "Instance", + "value": { + "i": 0 + } + } + ], + "instances": [ + { + "definition": { + "id": 0, + "@": { + "uri": 0, + "line": 17, + "column": 7 + }, + "loadingUnit": "1" + }, + "references": [ + { + "instanceConstant": 1, + "loadingUnit": "1", + "@": { + "uri": 0, + "line": 11, + "column": 2 + } + } + ] + } + ] +} \ No newline at end of file diff --git a/pkg/vm/testcases/transformations/record_use/instance_not_annotation.dart b/pkg/vm/testcases/transformations/record_use/instance_not_annotation.dart new file mode 100644 index 000000000000..0f49b0367889 --- /dev/null +++ b/pkg/vm/testcases/transformations/record_use/instance_not_annotation.dart @@ -0,0 +1,15 @@ +// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:meta/meta.dart' show RecordUse; + +void main() { + const myClass = const MyClass(); + print(myClass); +} + +@RecordUse() +class MyClass { + const MyClass(); +} diff --git a/pkg/vm/testcases/transformations/record_use/instance_not_annotation.dart.aot.expect b/pkg/vm/testcases/transformations/record_use/instance_not_annotation.dart.aot.expect new file mode 100644 index 000000000000..67c9a5b0322b --- /dev/null +++ b/pkg/vm/testcases/transformations/record_use/instance_not_annotation.dart.aot.expect @@ -0,0 +1,19 @@ +library #lib; +import self as self; +import "package:meta/meta.dart" as meta; +import "dart:core" as core; + +import "package:meta/meta.dart" show RecordUse; + +@#C1 +class MyClass extends core::Object /*hasConstConstructor*/ { +} + +[@vm.inferred-return-type.metadata=dart.core::Null? (value: null)] +static method main() → void { + core::print(#C2); +} +constants { + #C1 = meta::RecordUse {} + #C2 = self::MyClass {} +} diff --git a/pkg/vm/testcases/transformations/record_use/instance_not_annotation.dart.json.expect b/pkg/vm/testcases/transformations/record_use/instance_not_annotation.dart.json.expect new file mode 100644 index 000000000000..d95dcddd5268 --- /dev/null +++ b/pkg/vm/testcases/transformations/record_use/instance_not_annotation.dart.json.expect @@ -0,0 +1,9 @@ +{ + "metadata": { + "comment": "Recorded usages of objects tagged with a `RecordUse` annotation", + "version": "0.1.0" + }, + "uris": [], + "ids": [], + "constants": [] +} \ No newline at end of file diff --git a/pkg/vm/testcases/transformations/record_use/loading_units_multiple.dart.json.expect b/pkg/vm/testcases/transformations/record_use/loading_units_multiple.dart.json.expect index 246bd5b90afb..049870b0b1d3 100644 --- a/pkg/vm/testcases/transformations/record_use/loading_units_multiple.dart.json.expect +++ b/pkg/vm/testcases/transformations/record_use/loading_units_multiple.dart.json.expect @@ -26,6 +26,7 @@ "definition": { "id": 0, "@": { + "uri": 0, "line": 9, "column": 15 }, diff --git a/pkg/vm/testcases/transformations/record_use/loading_units_simple.dart.json.expect b/pkg/vm/testcases/transformations/record_use/loading_units_simple.dart.json.expect index 1aedeb7f6ad2..e1708ee58613 100644 --- a/pkg/vm/testcases/transformations/record_use/loading_units_simple.dart.json.expect +++ b/pkg/vm/testcases/transformations/record_use/loading_units_simple.dart.json.expect @@ -30,6 +30,7 @@ "definition": { "id": 0, "@": { + "uri": 0, "line": 23, "column": 15 }, @@ -57,6 +58,7 @@ "definition": { "id": 1, "@": { + "uri": 1, "line": 13, "column": 15 }, diff --git a/pkg/vm/testcases/transformations/record_use/partfile_helper.dart b/pkg/vm/testcases/transformations/record_use/partfile_helper.dart new file mode 100644 index 000000000000..e901e72bd788 --- /dev/null +++ b/pkg/vm/testcases/transformations/record_use/partfile_helper.dart @@ -0,0 +1,12 @@ +// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +part of 'partfile_main.dart'; + +class SomeClass { + @RecordUse() + static someStaticMethod(int i) { + return i + 1; + } +} diff --git a/pkg/vm/testcases/transformations/record_use/partfile_main.dart b/pkg/vm/testcases/transformations/record_use/partfile_main.dart new file mode 100644 index 000000000000..6b4e75919078 --- /dev/null +++ b/pkg/vm/testcases/transformations/record_use/partfile_main.dart @@ -0,0 +1,10 @@ +// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:meta/meta.dart' show RecordUse; +part 'partfile_helper.dart'; + +void main() { + print(SomeClass.someStaticMethod(42)); +} diff --git a/pkg/vm/testcases/transformations/record_use/partfile_main.dart.aot.expect b/pkg/vm/testcases/transformations/record_use/partfile_main.dart.aot.expect new file mode 100644 index 000000000000..ae84b919fffc --- /dev/null +++ b/pkg/vm/testcases/transformations/record_use/partfile_main.dart.aot.expect @@ -0,0 +1,25 @@ +library #lib; +import self as self; +import "dart:core" as core; +import "package:meta/meta.dart" as meta; + +import "package:meta/meta.dart" show RecordUse; + +part partfile_helper.dart; +abstract class SomeClass extends core::Object { // from org-dartlang-test:///testcases/transformations/record_use/partfile_helper.dart + + [@vm.inferred-return-type.metadata=int] + [@vm.unboxing-info.metadata=(i)->i] + @#C1 + static method someStaticMethod([@vm.inferred-arg-type.metadata=dart.core::_Smi (value: 42)] core::int i) → dynamic { + return [@vm.direct-call.metadata=dart.core::_IntegerImplementation.+] [@vm.inferred-type.metadata=int (skip check)] i.{core::num::+}(1){(core::num) → core::int}; + } +} + +[@vm.inferred-return-type.metadata=dart.core::Null? (value: null)] +static method main() → void { + core::print([@vm.inferred-type.metadata=int] self::SomeClass::someStaticMethod(42)); +} +constants { + #C1 = meta::RecordUse {} +} diff --git a/pkg/vm/testcases/transformations/record_use/partfile_main.dart.json.expect b/pkg/vm/testcases/transformations/record_use/partfile_main.dart.json.expect new file mode 100644 index 000000000000..397f6bc6102a --- /dev/null +++ b/pkg/vm/testcases/transformations/record_use/partfile_main.dart.json.expect @@ -0,0 +1,53 @@ +{ + "metadata": { + "comment": "Recorded usages of objects tagged with a `RecordUse` annotation", + "version": "0.1.0" + }, + "uris": [ + "partfile_main.dart", + "partfile_helper.dart" + ], + "ids": [ + { + "uri": 0, + "parent": "SomeClass", + "name": "someStaticMethod" + } + ], + "constants": [ + { + "type": "int", + "value": 42 + } + ], + "calls": [ + { + "definition": { + "id": 0, + "@": { + "uri": 1, + "line": 9, + "column": 10 + }, + "loadingUnit": "1" + }, + "references": [ + { + "arguments": { + "const": { + "positional": { + "0": 0 + } + } + }, + "loadingUnit": "1", + "@": { + "uri": 0, + "line": 9, + "column": 19 + } + } + ] + } + ] +} \ No newline at end of file diff --git a/pkg/vm/testcases/transformations/record_use/simple.dart.json.expect b/pkg/vm/testcases/transformations/record_use/simple.dart.json.expect index d67172044d86..72d9f6376b04 100644 --- a/pkg/vm/testcases/transformations/record_use/simple.dart.json.expect +++ b/pkg/vm/testcases/transformations/record_use/simple.dart.json.expect @@ -24,6 +24,7 @@ "definition": { "id": 0, "@": { + "uri": 0, "line": 13, "column": 10 }, diff --git a/pkg/vm/testcases/transformations/record_use/tearoff.dart.json.expect b/pkg/vm/testcases/transformations/record_use/tearoff.dart.json.expect index a07d7c228bb1..7699d754a3ce 100644 --- a/pkg/vm/testcases/transformations/record_use/tearoff.dart.json.expect +++ b/pkg/vm/testcases/transformations/record_use/tearoff.dart.json.expect @@ -19,6 +19,7 @@ "definition": { "id": 0, "@": { + "uri": 0, "line": 15, "column": 10 }, diff --git a/pkg/vm/testcases/transformations/record_use/top_level_method.dart.json.expect b/pkg/vm/testcases/transformations/record_use/top_level_method.dart.json.expect index 0f5883aa2c9c..e0a2a0a3c40f 100644 --- a/pkg/vm/testcases/transformations/record_use/top_level_method.dart.json.expect +++ b/pkg/vm/testcases/transformations/record_use/top_level_method.dart.json.expect @@ -23,6 +23,7 @@ "definition": { "id": 0, "@": { + "uri": 0, "line": 12, "column": 5 }, diff --git a/pkg/vm/testcases/transformations/record_use/types_of_arguments.dart.json.expect b/pkg/vm/testcases/transformations/record_use/types_of_arguments.dart.json.expect index f7ee989219f4..d9c147c75d41 100644 --- a/pkg/vm/testcases/transformations/record_use/types_of_arguments.dart.json.expect +++ b/pkg/vm/testcases/transformations/record_use/types_of_arguments.dart.json.expect @@ -72,6 +72,7 @@ "definition": { "id": 0, "@": { + "uri": 0, "line": 22, "column": 17 },